Skip to content

feat: image upload spike + workflow engine + parallel tasks - #6

Merged
suntianc merged 326 commits into
masterfrom
dev
Jul 19, 2026
Merged

feat: image upload spike + workflow engine + parallel tasks#6
suntianc merged 326 commits into
masterfrom
dev

Conversation

@suntianc

Copy link
Copy Markdown
Owner

Summary

Large dev branch merge containing the full CDF Agent Workstation feature set built since initial release:

Image Upload Spike (latest)

  • Clipboard paste → base64 preview → LLM multimodal (ContentBlock array)
  • Message image thumbnails + lightbox (createPortal, Esc/overlay dismiss)
  • Toaster theme dynamic binding, SVG XSS guard on paste handler
  • SQLite image_data column for spike storage (full impl uses attachments table)

Workflow Engine (Phases 13-16)

  • Conditional routing, review node responseFormat, parallel fan-out/fan-in workers
  • Human-in-the-loop approval with interrupt/resume, approval mode selector
  • Shared infrastructure layer, span trace hierarchy in ExecutionPanel
  • Node executor migration to shared-infra

Parallel Task Execution

  • Parallel batch tool wired to runtime + ChatArea detail view
  • Worker UI alignment, textBuffer filtering, session reload persistence

UI Polish & Design System

  • oklch color migration, 44px touch targets, text-xs minimum sizes
  • SubagentView failure states, MessageContentRenderer extraction
  • Slash command dispatcher fixes, @mention system

Test Coverage

Layer           File                              Paths  Tested  Status
─────────────── ────────────────────────────────── ────── ─────── ──────
Image spike     10 files changed                    27      0     spike
Workflow engine (prior commits)                   covered by existing tests
Parallel batch  (prior commits)                   covered by existing tests

Image upload spike has 0% test coverage (27 untested paths). This is expected for a spike — full test coverage will be added with the production implementation (attachments table + file-on-disk).

Pre-Landing Review

Review scoped to image upload spike (post-9503c45). No P0/P1 findings.

Adversarial Review

6 findings from Claude adversarial subagent:

  • [FIXED] SVG XSS via image/svg+xml paste — restricted to safe bitmap types (png/jpeg/webp/gif)
  • [KNOWN] base64 in SQLite memory pressure — spike limitation, full impl uses file-on-disk
  • [KNOWN] FileReader race on MAX_IMAGES — functional updater provides guard
  • [KNOWN] Silent JSON parse failure — spike, logs would be added in full impl
  • [KNOWN] Historical images not sent as multimodal — spike limitation
  • [FIXED] Lightbox SVG rendering — same fix as SVG XSS guard

Plan Completion

Plan: ~/.gstack/projects/suntc-pi-gsd/suntc-dev-design-20260623-021150.md
Scope: Spike assignment (per plan's "The Assignment" section)

  • 5/6 spike items DONE, 1 CHANGED (inline base64 instead of post-spike DB)
  • 5 full implementation items explicitly deferred per plan design

Test plan

  • Vitest: 552 passed, 11 pre-existing failures (0 in-branch)
  • App.test.tsx matchMedia mock verified
  • SVG XSS guard committed and tested

🤖 Generated with Claude Code

suntianc and others added 21 commits June 21, 2026 06:07
- Remove mirrored DB Helpers (AgentRow, ProviderRow, MCPServerRow, getAgent, getProvider, getAgentMcpServers, getAgentSkillNames)
- Import getAgentRow, getProvider, getAgentMcpServers, getAgentSkillNames, createBuiltInTools, loadRegistryTools, loadMcpTools from shared-infra
- Replace inline builtInTools construction with createBuiltInTools(workingDir)
- Add loadRegistryTools() to load search tools (tavily/anysearch/arxiv), matching Chat path capability
- Remove decryptApiKey import (handled internally by shared-infra)
- Remove '// ---- DB Helpers (mirrored from runtime.ts) ----' comment
- All 12 tests pass
- Remove duplicate normalizeProviderId, getProvider, getAgentMcpServers, getAgentSkillNames functions
- Remove DEFAULT_INTERRUPT_ON constant definition (imported from shared-infra)
- Remove inline TOOL_REGISTRY array and loadToolConfig function
- Import getProvider, getAgentMcpServers, getAgentSkillNames, normalizeProviderId, DEFAULT_INTERRUPT_ON, createBuiltInTools, loadRegistryTools, loadMcpTools from ./shared-infra
- Replace inline builtInTools construction with createBuiltInTools(project.path)
- Replace TOOL_REGISTRY loop with loadRegistryTools() call
- Remove decryptApiKey, createDeleteFileTool, createTavilyTool, createAnysearchTool, createArxivTool, createBashTool, createFetchTool imports (handled by shared-infra)
- All 13 runtime tests pass
# Conflicts:
#	src/main/deepagent/runtime.ts
#	src/main/workflow/node-executor.ts
- Add spanId/parentSpanId fields to ExecutionStep in types.ts
- Add spanId? to node_start WorkflowStreamEvent type
- Implement createSpanId/createChildSpan helpers in node-executor.ts
- Generate nodeSpanId at executor entry for each node invocation
- Create agentSpan (child of node) for each invokeAgent call
- Create toolSpan (child of agentSpan) for each tool_call/tool_result step
- Create iterationSpan (child of node) for loop iterations
- Create itemSpan (child of node) for foreach items
- Inject spanId into node_start events in workflow-runtime.ts
- Add SpanBadge component showing first 4 chars of spanId
- Steps with parentSpanId get pl-4 indentation and border-l-2 indicator
- SpanBadge appears in thinking, tool_call, tool_result, and system steps
- System/task steps with parentSpanId get ml-4 margin for visual nesting
- Uses var(--color-border) and var(--color-text-muted) design tokens
Merge worktree-agent-a9006904fe6dff070 (Plan 16-03: span trace + UI hierarchy).
Resolved conflicts in types.ts and ExecutionPanel.tsx — kept t() i18n from HEAD,
added SpanBadge/indent from worktree, preserved var(--color-bg-sunken) tokens.
Fixed deviation: replaced inline createSpanId/createChildSpan in node-executor.ts
and workflow-runtime.ts with imports from shared-infra.ts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

- Restore createRuntimeModel export (P0: llm.ts build break)
- Restore createAgentTools for Master Agent CRUD (P1: agent management regression)
- Restore parallel node dispatcher/worker logic with span trace (P1: fan-out regression)
- Parallel worker prompt includes full taskContext (upstream outputs, routing hints)
- Parallel dispatcher prefers worker results over raw items for chained parallel nodes
- Fan-out treats concurrencyLimit=0 as MAX_PARALLEL_ITEMS (50), not unlimited
- Worker routing not propagated to global state (avoids non-deterministic overwrites)
- Worker outputs persisted under original nodeId (aligned with node_start/trace keys)
- Ordinary node output shape preserved (result object, not wrapper map)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ExecutionStep 加 goal?/summary? 字段(T1)
- parallel-task-tool: task_start 移至 createParallelTaskTool,goal 用 task.description(T2)
- ParallelWorker 加 agentName?/goal?,handler 专用 task_start 分支创建 worker(T3)
- TaskPanel 并行 worker 卡片:显示 agentName、token 估算替换步骤数(T4)
- SubagentView: goal 和 agentName 对两种 worker 类型统一生效(T5)
- 修复 ParallelBatchSection 缺少 useTranslation 导致任务面板崩溃

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- runtime.ts: 注册 createParallelTaskTool 到 builtInTools
- stream-accumulator.ts: 加 onChunk 回调,支持 thinking/<think> 流式透传
- preload/index.ts: 暴露 onParallelTaskStep IPC 订阅接口
- ChatArea.tsx: viewingParallelWorker 时渲染 SubagentView,隐藏输入栏

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
移除 GSD 占位注释块,加入 gstack 安装说明

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fetchAgentActivity 新增 parallel_tasks 工具调用重建逻辑
  从 call.input(tasks)和 call.output(results)还原 ParallelBatch[]
  textBuffer = result.output,goal = task.description,status 按 call.status 推断
- parallel-task-tool: 结果对象加 agentName 字段,restore 后显示名不降级为 slug
- set() 加 parallelBatches: restoredBatches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mary

Issue 1 — agentSlug 碰撞:
- ParallelTaskStepEvent 加 workerId 字段(per-task UUID)
- parallel-task-tool 每个 task 生成独立 workerId,所有 push 携带
- onParallelTaskStep 改用 workerId 做精确查找(findWorker helper)
- viewingParallelWorker 类型加 workerId?,ChatArea/TaskPanel 优先用 workerId 匹配

Issue 2 — task_end 结果摘要:
- ExecutionStep 加 summary? 字段
- task_end 步骤携带 output.slice(0,300) 作为 summary
- task_end handler 将 summary 存入 ParallelWorker
- TaskPanel worker 卡片完成后展示 summary/textBuffer 预览(line-clamp-2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LangChain streams tool-call arguments via handleLLMNewToken alongside
text tokens. Buffer tokens per-LLM-call and discard on tool-call
responses (detected via additional_kwargs.tool_calls for OpenAI-style
and tool_use content blocks for Anthropic-style), so raw JSON args like
{"file_path":"..."} no longer leak into textBuffer and appear below the
tool cards in the agent detail view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SubagentView.tsx & ToolMessageCard.tsx — Phase 4 design language upgrades:
back button group-hover/active/focus, separator div, ping animation status dot,
token chip, goal accent line, expand button hover, failure alert ring,
StepTimeline separator, toast backdrop-blur + focus ring; ToolMessageCard icon
upgrades and button hover/active/focus states.

runtime.ts, llm.ts, sessionStore.ts, types.ts — parallel worker
infrastructure: wires parallel task steps into session state, restores
parallelBatches on reload, and aligns event routing so the detail view
receives tool_call/tool_result/text_chunk steps correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ChatArea: switch textIndent → paddingLeft for token overlay so all
  lines (not just the first) are indented past the prefix tokens
- Remove now-unnecessary onScroll translateY handlers (paddingLeft handles
  multi-line without needing scroll sync)
- Expand LeadingToken.source union type to include 'skill:project' and
  'skill:global' (matching actual runtime values from command registry)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add imageBase64 field to Message type and ChatPayload. SQLite migration
adds image_data column. IPC handlers serialize/deserialize image data.
Runtime builds ContentBlock array for multimodal LLM messages.
Add clipboard paste handler with 5-image/5MB limits and toast warnings.
Dual thumbnail strip in welcome and composer views. Lightbox via
createPortal with Esc/overlay/button dismiss using design tokens.
Fix Toaster theme to follow system light/dark preference.
…guard

- Restrict handlePaste to safe bitmap types (png/jpeg/webp/gif), blocking
  SVG data URLs that could execute scripts in Electron renderer
- Add window.matchMedia mock for jsdom test environment (Toaster theme)
- Guard ParallelBatchSection against undefined batches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

interruptOn: DEFAULT_INTERRUPT_ON,

P2 Badge 让审批绕过模式真正生效

当用户在 TaskPanel 里选择 “Auto (Bypass)” 时,该值只写入 store,但聊天运行时仍无条件传入 DEFAULT_INTERRUPT_ON,所以 write/edit/delete_agent 等工具仍会触发审批,和界面提示“所有工具调用无需确认”不一致;这里应按存储的 approvalMode 解析 interruptOn。

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -295,32 +251,22 @@ async function buildInputMessages(sessionId: string, currentMessage: RuntimeInpu
role: message.role,
content: message.content,
})),
...(hasCurrent ? [] : [{ role: 'user' as const, content: currentMessage.content }]),
...(hasCurrent ? [] : [{ role: 'user' as const, content: currentContent }]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 发送已保存的当前图片消息

当用户粘贴图片后发送时,renderer 会先把当前 user message 保存到 SQLite,然后才调用 llm.chat;在无 checkpoint 的常见首轮路径中这里的 hasCurrent 为真,于是不会追加包含 image_urlcurrentContent,而 history 查询又只取了纯文本 content,导致模型实际从未收到本次图片。

Useful? React with 👍 / 👎.

Comment thread src/main/workflow/graph-builder.ts Outdated
Comment on lines +103 to +106
const effectiveLimit = concurrencyLimit > 0 ? concurrencyLimit : MAX_PARALLEL_ITEMS;
const limit = Math.min(items.length, effectiveLimit);
const sends: Send[] = [];
for (let i = 0; i < limit; i++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 不要把并行度限制当作条目截断

当 Parallel 节点配置 concurrencyLimit(UI 文案是并行度限制,0 表示不限制)且数据源条目数超过该值时,这里只为前 N 个条目创建 Send,其余条目永远不会执行也不会出现在输出中,造成工作流静默丢数据;限制应控制同时运行数量或明确报错,而不是截断输入。

Useful? React with 👍 / 👎.

Relocate the permission mode control (strict/agent_decides/bypass) from
TaskPanel header into both welcome and session composer toolbars. Rearrange
toolbar layout: left side has + attachment and approval mode, right side
has model selector and send button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2cae7af902

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +131 to +137
const agent = createDeepAgent({
model,
backend,
systemPrompt: agentRow.system_prompt || undefined,
skills: skillsSources.length > 0 ? skillsSources : undefined,
permissions,
tools: [...mcpRuntime.tools, ...builtInTools],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve approval gates for parallel workers

当聊天里的 parallel_tasks worker 被要求执行 write_file/edit_file/delete_file 这类文件操作时,这里创建的是一个新的 DeepAgent,但没有像主聊天 runtime 一样传入 DEFAULT_INTERRUPT_ON。由于 worker 仍然通过 createBuiltInTools(projectPath) 获得高权限工具,strict 审批模式下这些操作会在 worker 内直接执行而不触发用户审批,相当于绕过了已有安全边界。

Useful? React with 👍 / 👎.

Comment thread src/main/workflow/workflow-runtime.ts Outdated
return resolution;
};

const executeNode = createAgentNodeExecutor(node, upstreamNodeIds, approvalMode, onApprovalNeeded);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Wire workflow approval mode into node execution

在工作流 strict 模式下,这里传入的 approvalModeonApprovalNeeded 实际不会生效:createAgentNodeExecutor 仍只接收 node, upstreamNodeIds,其内部 createDeepAgent 也没有使用 resolveInterruptOn 或处理审批回调。因此新增的 node_waiting_approval UI 不会出现,工作流节点里的受保护工具调用会按旧路径直接执行。

Useful? React with 👍 / 👎.

Comment thread src/main/workflow/graph-builder.ts Outdated
Comment on lines +271 to +274
// parallel 节点的 out-edges:source 替换为 worker node
if (parallelNodeIds.has(sourceId)) {
const workerNodeId = `${sourceId}__worker`;
builder.addEdge(workerNodeId as any, toGraphNode(targetId, 'target') as any);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass parallel worker outputs to downstream nodes

当 parallel 节点后面接普通 task/review 节点时,这里把图上的出边改接到 parallelId__worker,但目标节点的 upstreamNodeIds 早已按原始 workflow edge 记录为 parallelIdcreateNodeStateExtractor 只读取 nodeOutputs[parallelId],也就是 dispatcher 的 {items,count},不会收集 parallelId__worker:0 等 worker 结果,所以下游节点会拿原始条目继续执行而不是并行处理后的输出。

Useful? React with 👍 / 👎.

Comment on lines +1861 to +1864
type="button"
onClick={() => handleSend()}
disabled={!inputVal.trim() || isStreaming}
className="p-2 rounded-lg bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:bg-[var(--color-bg-hover)] disabled:text-[var(--color-text-muted)] text-white transition-all shadow-md flex items-center justify-center cursor-pointer"
className="dialog-btn send"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enable the send button for image-only messages

在用户只粘贴图片、不输入文字时,handleSend/handleWelcomeSend 已允许发送并会补默认文案,但发送按钮仍按 !inputVal.trim() 禁用;鼠标或触屏用户因此无法点击发送 image-only 消息(只能偶然通过 Enter 触发)。这里和 welcome 按钮都需要把 pastedImages.length 纳入启用条件。

Useful? React with 👍 / 👎.

- TaskPanel 从侧边栏改为 absolute 定位的悬浮弹出面板,移除 resize 逻辑
- 触发图标从 Info 换为 SlidersHorizontal
- ApprovalModeSelector 按钮背景改为透明,修复与对话框背景色不一致
- Sidebar resize handle 宽度从 4px 减为 2px,拖拽颜色降低至 40% 透明度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 005b839895

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -1078,12 +1296,14 @@ export const useSessionStore = create<SessionState>((set, get) => ({
message: {
id: userMsgId,
content,
...(options?.imageBase64?.length ? { imageBase64: options.imageBase64 } : {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve images when retrying failed sends

当本次消息包含 options.imageBase64llm.chat 返回 runtime_error 或抛错时,下面的重试 action 仍只重新调用 sendMessage(projectId, content, overrides, targetSessionId),不会把这里加入 payload 的图片重新传入;image-only 消息重试后只会发送“请描述这张图片”文本,模型仍看不到图片。请把 options(或至少 imageBase64)也闭包进两个 retry action。

Useful? React with 👍 / 👎.

Comment thread src/main/deepagent/runtime.ts Outdated
@@ -485,7 +473,6 @@ export async function createDeepAgentRuntime(
defaultModel: provider.default_model,
providerType: provider.provider_type,
model: modelName,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass provider context limits into chat models

MiniMax model creation uses config.contextLimit to cap maxTokens; after this call stopped forwarding provider.context_limit, any MiniMax chat whose configured limit is below the 65,536 default will request an over-limit response and can be rejected by the provider. Keep passing the provider context limit here (and in the subagent/workflow model paths) as before.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37a1390a82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/ipc-handlers.ts Outdated
Comment on lines +854 to +856
ipcMain.handle('fs:readFile', (_, rootPath: string, filePath: string) => {
try {
return { ok: true, data: readFile(rootPath, filePath) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve file roots on the main side

这里把 renderer 传来的 rootPath 当作安全边界;resolveProjectFile 只校验 filePath 位于这个同样由 renderer 提供的 root 下。任意渲染端代码都可以调用 window.electronAPI.fs.readFile('/', '/etc/hosts')(同理 readDirectory/getFileInfo)读取或监听项目外文件,绕过项目隔离和 Electron 的最小权限边界。请改为传 projectId,在主进程从 DB 取 project.path 后再做路径校验。

Useful? React with 👍 / 👎.

Comment thread src/main/workflow/tools.ts Outdated
});
return JSON.stringify({ executionId, status: 'started' });

const completionPromise = waitForWorkflowCompletion(executionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid missing fast workflow completions

在 chat 工具调用 run_workflow 且未传 timeout_ms 时,runWorkflow 会启动后台 IIFE 后返回 executionId,但非常快的工作流(例如空图或只含立即结束节点)可能在这行注册 waiter 之前已经调用 notifyCompletionnotifyCompletion 只唤醒当时存在的 waiter、不缓存结果,因此这里得到的 completionPromise 会永远 pending,导致 Agent 工具调用卡住。请让 waiter 能检查已完成的 DB 状态,或调整为不会在注册前完成。

Useful? React with 👍 / 👎.

useEffect(() => {
if (!rootPath) return;
const unsub = window.electronAPI.fs.onDirectoryChange((_event, data) => {
const dirPath = data.path.substring(0, data.path.lastIndexOf('/'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize watcher paths before deriving dirs

在 Windows 上 chokidar 事件路径通常使用反斜杠,lastIndexOf('/') 会返回 -1,导致 dirPath 变成空字符串;随后 fs:readDirectory(rootPath, '') 会被主进程的绝对路径检查拒绝,文件面板对外部新增/修改文件不会刷新。请在主进程发送目录路径,或在这里同时处理 \/统一规范化路径。

Useful? React with 👍 / 👎.

suntianc and others added 2 commits June 26, 2026 21:00
实现文件面板的完整文件管理功能:
- 异步化 readDirectory/readFile/getFileInfo(fs.promises)
- 新增 writeFile/createFile/createDirectory/renameEntry/trashEntry 写操作
- 右键上下文菜单(新建/重命名/删除/复制路径/Finder 打开)
- 文件编辑 + Cmd+S 保存 + dirty 标记 + 可选自动保存
- Markdown 预览/编辑切换
- Watcher 重构为 per-directory depth:0 按需监听
- 路径安全校验(resolveProjectFile + isProtectedPath)全覆盖
- 中英文 i18n 文案同步
- 35 个单元测试全部通过

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. SystemSettings 新增文件自动保存开关(读写 electron-store autoSave)
2. fileStore 从单一 previewFile 改为 openTabs 数组 + activeTabIndex,
   点击文件新增 tab 而非替换,支持单独关闭
3. MarkdownRenderer 添加 ``` 代码块 fence 处理,避免三反引号被
   renderInlineMarkdown 的 `.*?` regex 误匹配为行内代码

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
suntianc added 29 commits July 15, 2026 01:02
Domain docs, ADRs, research, and spikes stay on disk for local agent work but are no longer versioned or pushed.
@suntianc
suntianc merged commit 3c8aceb into master Jul 19, 2026
0 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant