c0de-agent 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/core/agent.js +4 -0
  2. package/dist/core/config.js +1 -1
  3. package/dist/core/index.d.ts +1 -1
  4. package/dist/core/loop.js +9 -0
  5. package/dist/core/prompt-registry.d.ts +2 -2
  6. package/dist/core/prompt-registry.js +42 -3
  7. package/dist/core/slash.js +40 -6
  8. package/dist/core/types.d.ts +10 -1
  9. package/dist/core/workflow.d.ts +1 -1
  10. package/dist/core/workflow.js +54 -6
  11. package/dist/core/workflows/runtime.d.ts +3 -0
  12. package/dist/core/workflows/runtime.js +2 -2
  13. package/dist/project/resolve.d.ts +75 -0
  14. package/dist/project/resolve.js +253 -1
  15. package/dist/server/app.js +3 -0
  16. package/dist/server/context.js +2 -1
  17. package/dist/server/dev.js +3 -1
  18. package/dist/server/routes/chat.js +12 -0
  19. package/dist/server/routes/commands.js +1 -0
  20. package/dist/server/routes/files.js +252 -4
  21. package/dist/server/routes/terminal.js +2 -1
  22. package/dist/server/routes/todo.d.ts +4 -0
  23. package/dist/server/routes/todo.js +107 -0
  24. package/dist/server/routes/workflows.js +51 -11
  25. package/dist/server/server.d.ts +8 -1
  26. package/dist/server/server.js +113 -23
  27. package/dist/server/terminal/pty-manager.d.ts +14 -0
  28. package/dist/server/terminal/pty-manager.js +105 -7
  29. package/dist/shared/types/agent.d.ts +9 -0
  30. package/dist/shared/types/config.d.ts +6 -0
  31. package/dist/shared/types/tool.d.ts +13 -0
  32. package/dist/tools/builtin/todo.d.ts +67 -0
  33. package/dist/tools/builtin/todo.js +517 -0
  34. package/dist/tools/index.d.ts +2 -0
  35. package/dist/tools/index.js +3 -0
  36. package/dist/tools/types.d.ts +32 -1
  37. package/package.json +2 -1
@@ -2,6 +2,7 @@ import { appendMessage, getMessages } from '../session/message.js';
2
2
  import { getLLMSegments } from '../session/session.js';
3
3
  import { generateId } from '../shared/index.js';
4
4
  import { listTools } from '../tools/registry.js';
5
+ import { getLatestTodoPhasesFromMessages } from '../tools/builtin/todo.js';
5
6
  import { estimateBudget } from './context.js';
6
7
  import { createTokenBudget } from './context.js';
7
8
  import { agentLoop } from './loop.js';
@@ -39,6 +40,9 @@ async function createAgent(session, config, deps) {
39
40
  segments,
40
41
  tokenBudget: { ...tokenBudget, used },
41
42
  calibrationFactor: 1.0,
43
+ // 从历史 tool result 恢复 todo 状态(跨 session resume / compaction 后重建)。
44
+ // 遍历所有消息找最后一条带 phases metadata 的 todo result。
45
+ todoPhases: getLatestTodoPhasesFromMessages(messages),
42
46
  // 压缩模型覆盖:从全局配置映射到 agent state,compactContext 读取后用于
43
47
  // 创建 summarizer。未配置时为 undefined → 回退到会话主模型。
44
48
  ...(deps.config.compaction.compactionModel
@@ -16,7 +16,7 @@ const DEFAULT_CONFIG = {
16
16
  keepRecentTokens: 4000,
17
17
  midTurnEnabled: false,
18
18
  },
19
- tools: { enabled: ['read', 'write', 'edit', 'glob', 'grep', 'bash', 'websearch'], disabled: [] },
19
+ tools: { enabled: ['read', 'write', 'edit', 'glob', 'grep', 'bash', 'todo', 'websearch'], disabled: [] },
20
20
  plugins: { enabled: [] },
21
21
  mcpServers: [],
22
22
  slashCommands: { enabled: ['/compact', '/model', '/clear', '/help', '/fork', '/config'] },
@@ -10,7 +10,7 @@ export { builtinCommands, createSlashRegistry, parseSlashInput } from './slash.j
10
10
  export { clearSteering, drainSteering, injectSteering } from './steering.js';
11
11
  export type { CollectedToolCall, ToolCallResult } from './tool-exec.js';
12
12
  export { executeToolCall, executeToolCalls, partitionByConflict } from './tool-exec.js';
13
- export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, CommandContext, CommandResult, LLMSegment, PendingToolCall, ProjectInfo, PromptContext, SlashCommand, TokenBudget, } from './types.js';
13
+ export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, CommandContext, CommandResult, LLMSegment, PendingToolCall, ProjectInfo, PromptContext, SlashCommand, SubcommandDef, TokenBudget, } from './types.js';
14
14
  export { buildWorkflowNotice, containsWorkflow, WORKFLOW_NOTICE } from './workflow.js';
15
15
  export type { SaveResult, SaveTarget, WorkflowAgentResult, WorkflowContext, WorkflowEntry, WorkflowMeta, WorkflowRegistry, WorkflowResult, WorkflowUtils, } from './workflows/index.js';
16
16
  export { BUILTIN_WORKFLOWS, buildWorkflowContext, createAndPopulateRegistry, createBuiltinWorkflows, createWorkflowRegistry, discoverWorkflows, executeWorkflow, reloadRegistry, saveWorkflow, } from './workflows/index.js';
package/dist/core/loop.js CHANGED
@@ -598,6 +598,15 @@ async function* persistAssistantAndTools(state, deps, collectedText, validCalls)
598
598
  ...(deps.debugSpawn ? { debugSpawn: deps.debugSpawn } : {}),
599
599
  runSubAgent: (req) => runSubAgent({ ...deps, _subagentEventSink: eventSink }, state, req),
600
600
  ...(deps._subagentYieldCollector ? { collectYield: deps._subagentYieldCollector } : {}),
601
+ // todo 工具状态通过 dependency-reversal hook 注入:get/set 直接读写
602
+ // state.todoPhases(in-memory),tool result 的 metadata.phases 充当
603
+ // 持久化层——createAgent 时从历史消息恢复。
604
+ todoState: {
605
+ get: () => state.todoPhases,
606
+ set: (phases) => {
607
+ state.todoPhases = phases;
608
+ },
609
+ },
601
610
  }, validCalls, deps.hookRunner);
602
611
  const toolLatency = Date.now() - toolExecStart;
603
612
  const metricsEnabled = deps.config.toolMetrics.enabled;
@@ -3,14 +3,14 @@ declare const ROLE_DESCRIPTION = "You are c0de-agent, an open-source AI coding a
3
3
  declare const ENGINEERING_PRINCIPLES = "# Engineering Principles\n- Optimize for correctness first, then for the next maintainer six months out.\n- You have agency and taste: delete code that isn't pulling its weight, refuse unnecessary abstractions, prefer boring when it's called for.\n- Consider what code compiles to. Never allocate avoidably; no needless copies or computation.\n- You are not alone in this repo. Treat unexpected changes as the user's work and adapt \u2014 never revert edits you did not make unless explicitly asked.";
4
4
  declare const TOOL_USAGE = "# Tool Usage\nYou have dedicated tools \u2014 prefer them over shell commands for file operations:\n- Reading a file or listing a directory \u2192 `read` (NOT `cat`, `head`, `tail`).\n- Finding files by name or pattern \u2192 `glob` (NOT `find`, `ls -R`).\n- Searching file contents \u2192 `grep` (NOT shell `grep`/`rg`/`ack`, NOT `awk`/`sed`).\n- Modifying files \u2192 `edit`/`write` (NOT `sed`, `echo >`, heredocs).\n\nReserve `bash` for genuine command execution: builds, tests, git, or short pipelines that compute a fact (`wc -l`, `git status`, `diff`, a checksum). Never explore a codebase with `find`/`ls`/`cat` when `read`/`glob`/`grep` can.\n\nBatch independent tool calls in a single response \u2014 parallelize file reads and independent lookups. Only sequence when one call's result informs the next.\n\nWhen referencing code, use the `file_path:line_number` pattern so the user can navigate directly.";
5
5
  declare const CODEBASE = "# Working with the Codebase\nBefore changing files, understand the existing conventions. Mimic code style, reuse existing utilities, and follow established patterns.\n- NEVER assume a library is available, even if well known. Check package.json (or equivalent) and neighboring files first.\n- When creating a new component, look at existing ones first for naming, typing, and framework conventions.\n- When editing, read the surrounding context (especially imports) to make the change idiomatic. Never introduce code that exposes or logs secrets.";
6
- declare const EXECUTION_WORKFLOW = "# Execution Workflow\n1. Scope \u2014 plan before touching files; research existing code and conventions.\n2. Research \u2014 read sections, not snippets. Reuse existing patterns; a second convention beside an existing one is prohibited.\n3. Decompose \u2014 break multi-step work into steps and track them; skip for trivial requests. Plan only what makes the request work.\n4. Implement \u2014 fix problems at the source. Remove obsolete code \u2014 no leftover comments, aliases, or re-exports. Prefer editing existing files over new ones.\n5. Verify \u2014 never yield non-trivial work without proof: run the relevant tests. Prefer testing behavior, not plumbing. Don't test defaults.\n6. Cleanup \u2014 changelog, tests, docs, and removing scaffolding are the LAST phase, gated on the request demonstrably working. Never pre-plan cleanup before the request works.";
6
+ declare const EXECUTION_WORKFLOW = "# Execution Workflow\n1. Scope \u2014 plan before touching files; research existing code and conventions.\n2. Research \u2014 read sections, not snippets. Reuse existing patterns; a second convention beside an existing one is prohibited.\n3. Decompose \u2014 break multi-step work into steps and track them with the `todo` tool; skip for trivial requests. Plan only what makes the request work.\n4. Implement \u2014 fix problems at the source. Remove obsolete code \u2014 no leftover comments, aliases, or re-exports. Prefer editing existing files over new ones.\n5. Verify \u2014 never yield non-trivial work without proof: run the relevant tests. Prefer testing behavior, not plumbing. Don't test defaults.\n6. Cleanup \u2014 changelog, tests, docs, and removing scaffolding are the LAST phase, gated on the request demonstrably working. Never pre-plan cleanup before the request works.";
7
7
  declare const VERIFICATION = "# Verification & Evidence\n- Never yield non-trivial work without proof: tests, builds, or QA.\n- Run lint and typecheck after changes if the project provides them.\n- Every claim about code, tools, or tests must be grounded. Mark anything not directly observed as [INFERENCE].\n- Verification claims must match what was actually exercised. A passing typecheck does not prove an integration.";
8
8
  declare const DELIVERY_CONTRACT = "# Delivery Contract\n- \"Done\" means the deliverable behaves as specified end-to-end \u2014 not that a scaffold compiles.\n- Never yield unless complete. A phase boundary is never a yield point.\n- Never suppress tests to make code pass. Never fabricate outputs.\n- Never substitute an easier problem: don't infer extra scope, don't treat the symptom unless asked.\n- Never ship stubs, placeholders, mocks, no-ops, or fake fallbacks as finished work.\n- Default to clean cutover: migrate every caller; leave no shims or aliases.";
9
9
  declare const GIT_SAFETY = "# Git & Safety\n- NEVER commit unless the user explicitly asks. Committing is too proactive.\n- NEVER use `git reset --hard` or `git checkout --` unless explicitly approved.\n- Do not amend a commit unless asked.\n- You may be in a dirty worktree. NEVER revert changes you did not make; if unrelated changes conflict with your task, stop and ask.";
10
10
  declare const TONE = "# Tone & Output\n- Be concise and direct. Lead with the conclusion, then the evidence.\n- No preamble or postamble (\"The answer is\u2026\", \"Here is what I'll do next\u2026\").\n- Use GitHub-flavored markdown. Only use emojis if explicitly asked.\n- Don't hide uncertainty: state it at the specific claim, name the tradeoff.\n- For a simple question, a one-liner is best.";
11
11
  export { CODEBASE, DELIVERY_CONTRACT, ENGINEERING_PRINCIPLES, EXECUTION_WORKFLOW, GIT_SAFETY, ROLE_DESCRIPTION, TONE, TOOL_USAGE, VERIFICATION, };
12
12
  /** Built-in section ids in priority order. */
13
- export declare const BUILTIN_SECTION_IDS: readonly ["role", "systemPrompt", "engineering", "tool-usage", "codebase", "constraints", "execution-workflow", "verification", "delivery-contract", "git-safety", "tone", "project", "skills", "agents", "slash-commands"];
13
+ export declare const BUILTIN_SECTION_IDS: readonly ["role", "systemPrompt", "engineering", "tool-usage", "codebase", "constraints", "execution-workflow", "verification", "delivery-contract", "git-safety", "tone", "project", "skills", "agents", "slash-commands", "workflow-format"];
14
14
  /** Create a registry pre-loaded with the built-in sections. */
15
15
  declare function createPromptRegistry(): PromptRegistry;
16
16
  /** Register a section. A later registration with the same id overrides the earlier
@@ -37,7 +37,7 @@ This project follows a strict data + functions paradigm:
37
37
  const EXECUTION_WORKFLOW = `# Execution Workflow
38
38
  1. Scope — plan before touching files; research existing code and conventions.
39
39
  2. Research — read sections, not snippets. Reuse existing patterns; a second convention beside an existing one is prohibited.
40
- 3. Decompose — break multi-step work into steps and track them; skip for trivial requests. Plan only what makes the request work.
40
+ 3. Decompose — break multi-step work into steps and track them with the \`todo\` tool; skip for trivial requests. Plan only what makes the request work.
41
41
  4. Implement — fix problems at the source. Remove obsolete code — no leftover comments, aliases, or re-exports. Prefer editing existing files over new ones.
42
42
  5. Verify — never yield non-trivial work without proof: run the relevant tests. Prefer testing behavior, not plumbing. Don't test defaults.
43
43
  6. Cleanup — changelog, tests, docs, and removing scaffolding are the LAST phase, gated on the request demonstrably working. Never pre-plan cleanup before the request works.`;
@@ -83,6 +83,7 @@ export const BUILTIN_SECTION_IDS = [
83
83
  'skills',
84
84
  'agents',
85
85
  'slash-commands',
86
+ 'workflow-format',
86
87
  ];
87
88
  /** Built-in sections. Dynamic bodies (tool list, project info, skills) use `render`. */
88
89
  function builtinSections() {
@@ -164,8 +165,46 @@ function builtinSections() {
164
165
  priority: 120,
165
166
  render: () => {
166
167
  const lines = ['## Slash Commands'];
167
- for (const cmd of builtinCommands)
168
- lines.push(`- /${cmd.name}: ${cmd.description}`);
168
+ for (const cmd of builtinCommands) {
169
+ let line = `- /${cmd.name}: ${cmd.description}`;
170
+ if (cmd.subcommands) {
171
+ const subs = cmd.subcommands.map((s) => s.name).join('|');
172
+ line += ` (${subs})`;
173
+ }
174
+ lines.push(line);
175
+ }
176
+ return lines.join('\n');
177
+ },
178
+ },
179
+ // 工作流脚本格式提示:让模型知道工作流脚本是 JS 格式,放在 .c0de/workflows/。
180
+ {
181
+ id: 'workflow-format',
182
+ content: '',
183
+ priority: 121,
184
+ render: () => {
185
+ const lines = [
186
+ '## Dynamic Workflows',
187
+ 'When the user asks to create/save a workflow (e.g. "workflowz 创建工作流"), you MUST generate a **JavaScript** file (NOT bash/shell/python) with this structure:',
188
+ '```js',
189
+ '// File: .c0de/workflows/<name>.js',
190
+ 'export const meta = {',
191
+ " name: '<name>', // lowercase, kebab-case",
192
+ " description: '<description>',",
193
+ " phases: ['phase1', 'phase2'],",
194
+ '}',
195
+ '',
196
+ 'export default async function workflow(ctx) {',
197
+ ' const { runSubagents, runSubagent, utils, progress, project, args } = ctx',
198
+ ' // runSubagents(type, tasks[]) → parallel fan-out',
199
+ ' // runSubagent(type, { assignment, description? }) → single dispatch',
200
+ ' // utils: { glob, grep, read, splitByDirectory }',
201
+ ' // progress(message, { phase? }) → SSE progress',
202
+ " return { output: 'summary', data: {} }",
203
+ '}',
204
+ '```',
205
+ 'Save to `<project>/.c0de/workflows/<name>.js`. Available subagent types: general, coder, researcher, reviewer.',
206
+ 'Run with `/workflow run <name>` or POST /api/workflows/:name/run. NEVER create bash/shell scripts for workflows.',
207
+ ];
169
208
  return lines.join('\n');
170
209
  },
171
210
  },
@@ -1,6 +1,6 @@
1
1
  import { createSession } from '../session/session.js';
2
2
  import { createAgent } from './agent.js';
3
- import { BUILTIN_WORKFLOWS, createWorkflowRegistry, executeWorkflow, reloadRegistry, saveWorkflow, } from './workflows/index.js';
3
+ import { BUILTIN_WORKFLOWS, createWorkflowRegistry, discoverWorkflows, executeWorkflow, reloadRegistry, saveWorkflow, } from './workflows/index.js';
4
4
  function parseSlashInput(input) {
5
5
  const trimmed = input.trim();
6
6
  if (!trimmed.startsWith('/'))
@@ -110,6 +110,13 @@ const workflowCommand = {
110
110
  name: 'workflow',
111
111
  description: 'Manage and run workflows',
112
112
  argsHint: '[list|run|show|create|edit] [name] [args]',
113
+ subcommands: [
114
+ { name: 'list', description: 'List available workflows' },
115
+ { name: 'run', description: 'Run a workflow', usage: '<name> [args]' },
116
+ { name: 'show', description: 'Show workflow source', usage: '<name>' },
117
+ { name: 'create', description: 'Create a workflow from file', usage: '<name> --file <path>' },
118
+ { name: 'edit', description: 'Edit workflow source', usage: '<name>' },
119
+ ],
113
120
  execute: async (args, ctx) => {
114
121
  const parts = args.split(/\s+/).filter(Boolean);
115
122
  const subcommand = parts[0] ?? 'list';
@@ -121,8 +128,17 @@ const workflowCommand = {
121
128
  registry.register(wf);
122
129
  }
123
130
  }
131
+ // 项目级工作流:从 agent cwd(= project worktree)动态发现。
132
+ // registry 是 server 单例(含 builtin + global + server-cwd),不含其他项目的工作流。
133
+ const projectWorkflows = await discoverWorkflows(ctx.cwd);
134
+ const projectByName = new Map(projectWorkflows.map((w) => [w.meta.name, w]));
135
+ const resolveEntry = (name) => registry.get(name) ?? projectByName.get(name);
124
136
  if (subcommand === 'list') {
125
- const workflows = registry.list();
137
+ // 合并 registry + 项目级(去重:同名项目级覆盖)
138
+ const byName = new Map(registry.list().map((w) => [w.meta.name, w]));
139
+ for (const wf of projectWorkflows)
140
+ byName.set(wf.meta.name, wf);
141
+ const workflows = Array.from(byName.values());
126
142
  const lines = ['Available workflows:'];
127
143
  for (const wf of workflows) {
128
144
  const phases = wf.meta.phases ? ` [${wf.meta.phases.join('→')}]` : '';
@@ -140,7 +156,7 @@ const workflowCommand = {
140
156
  const name = parts[1];
141
157
  if (!name)
142
158
  return { _tag: 'error', message: 'Usage: /workflow show <name>' };
143
- const wf = registry.get(name);
159
+ const wf = resolveEntry(name);
144
160
  if (!wf) {
145
161
  return { _tag: 'error', message: `Unknown workflow: ${name}` };
146
162
  }
@@ -157,7 +173,10 @@ const workflowCommand = {
157
173
  // 解析 --file <path> 参数
158
174
  const fileIdx = parts.indexOf('--file');
159
175
  if (fileIdx === -1 || !parts[fileIdx + 1]) {
160
- return { _tag: 'error', message: 'Usage: /workflow create <name> --file <path>\nTip: 也可通过 REST API POST /api/workflows { name, source } 创建' };
176
+ return {
177
+ _tag: 'error',
178
+ message: 'Usage: /workflow create <name> --file <path>\nTip: 也可通过 REST API POST /api/workflows { name, source } 创建',
179
+ };
161
180
  }
162
181
  const filePath = parts[fileIdx + 1] ?? '';
163
182
  let source;
@@ -184,12 +203,15 @@ const workflowCommand = {
184
203
  const name = parts[1];
185
204
  if (!name)
186
205
  return { _tag: 'error', message: 'Usage: /workflow edit <name>' };
187
- const wf = registry.get(name);
206
+ const wf = resolveEntry(name);
188
207
  if (!wf) {
189
208
  return { _tag: 'error', message: `Unknown workflow: ${name}` };
190
209
  }
191
210
  if (wf.source === 'builtin') {
192
- return { _tag: 'error', message: 'Cannot edit builtin workflow. Fork it first: /workflow create <new-name> --file <path>' };
211
+ return {
212
+ _tag: 'error',
213
+ message: 'Cannot edit builtin workflow. Fork it first: /workflow create <new-name> --file <path>',
214
+ };
193
215
  }
194
216
  if (!wf.filePath) {
195
217
  return { _tag: 'error', message: `Workflow file path not available for "${name}"` };
@@ -213,6 +235,17 @@ const workflowCommand = {
213
235
  if (!name)
214
236
  return { _tag: 'error', message: 'Usage: /workflow run <name> [args]' };
215
237
  const wfArgs = parts.slice(2).join(' ');
238
+ const entry = resolveEntry(name);
239
+ if (!entry) {
240
+ const available = [
241
+ ...registry.list().map((e) => e.meta.name),
242
+ ...projectWorkflows.map((e) => e.meta.name),
243
+ ].join(', ');
244
+ return {
245
+ _tag: 'error',
246
+ message: `Unknown workflow: "${name}". Available: ${available || '(none)'}`,
247
+ };
248
+ }
216
249
  const agentConfig = {
217
250
  provider: ctx.config.defaultProvider,
218
251
  model: ctx.config.defaultModel,
@@ -225,6 +258,7 @@ const workflowCommand = {
225
258
  return executeWorkflow({
226
259
  registry,
227
260
  name,
261
+ entry,
228
262
  args: wfArgs,
229
263
  deps: ctx.deps,
230
264
  parent,
@@ -89,10 +89,19 @@ type CommandContext = {
89
89
  deps: AgentDependencies;
90
90
  workflowRegistry?: import('./workflows/registry.js').WorkflowRegistry;
91
91
  };
92
+ /** Declarative subcommand definition for commands like /workflow. */
93
+ type SubcommandDef = {
94
+ name: string;
95
+ description: string;
96
+ /** Usage hint shown in popover, e.g. "<name> [args]". */
97
+ usage?: string;
98
+ };
92
99
  type SlashCommand = {
93
100
  name: string;
94
101
  description: string;
95
102
  argsHint?: string;
103
+ /** Subcommands for dropdown completion (e.g. /workflow list, /workflow run). */
104
+ subcommands?: SubcommandDef[];
96
105
  execute: (args: string, ctx: CommandContext) => Promise<CommandResult>;
97
106
  };
98
- export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, ChatTool, CommandContext, CommandResult, HookRunner, LLMSegment, PendingToolCall, ProjectInfo, PromptBuildContext, PromptContext, PromptRegistry, PromptSection, SlashCommand, TokenBudget, };
107
+ export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, ChatTool, CommandContext, CommandResult, HookRunner, LLMSegment, PendingToolCall, ProjectInfo, PromptBuildContext, PromptContext, PromptRegistry, PromptSection, SlashCommand, SubcommandDef, TokenBudget, };
@@ -25,7 +25,7 @@ export declare function containsWorkflow(text: string): boolean;
25
25
  *
26
26
  * 适配 c0de-agent 的 task 工具 schema(subagent_type + context + tasks[])。
27
27
  */
28
- export declare const WORKFLOW_NOTICE = "<workflow-notice>\nThe user's message contains the **workflowz** keyword: you MUST drive this task as a deterministic multi-subagent workflow using the `task` tool for batched fan-out. This is not optional \u2014 the keyword signals the user explicitly wants parallel sub-agent decomposition, not inline work. This overrides any default tendency to do the whole task inline.\n\n<when>\nThe user typed workflowz intentionally \u2014 they want fan-out. You may do a brief inline scout (1-2 tool calls to list files/scope the work), but your NEXT action after scouting MUST be a `task` tool call to fan out. Do NOT spend the entire turn reading files inline \u2014 that defeats the purpose. Common shapes:\n- **Understand** \u2014 parallel readers over subsystems \u2192 structured map.\n- **Design** \u2014 independent approaches \u2192 scored synthesis.\n- **Review** \u2014 split dimensions \u2192 find per dimension \u2192 adversarially verify each finding.\n- **Research** \u2014 multi-modal sweep \u2192 deep-read the hits \u2192 synthesize.\n- **Migrate** \u2014 discover sites \u2192 transform each \u2192 verify.\n</when>\n\n<task-contract>\nCall `task` once per independent fan-out batch using the batch form:\n\n task({\n subagent_type: \"coder\",\n context: \"shared background all subagents need\",\n tasks: [\n { role: \"coder\", assignment: \"specific assignment for agent 1\", description: \"short label\" },\n { role: \"reviewer\", assignment: \"specific assignment for agent 2\", description: \"short label\" },\n ]\n })\n\nAvailable subagent types: `general` (full tools, recursive), `coder` (implementation), `researcher` (read-only scout), `reviewer` (code review). Pick the type that matches each task's intent.\n\n`context` carries shared background prepended to every subagent's prompt \u2014 put the shared contract, conventions, and coordination rules here.\n\nEach task in `tasks[]` must be self-contained:\n- `role`: specialist type for this sub-task (e.g. coder, reviewer, researcher)\n- `assignment`: exact target (files, symbols, subsystem) + what to do + acceptance criteria\n- `description`: short label for the UI\n\nEach subagent runs in an isolated session and returns its result via the `yield` tool. Subagents skip formatters, linters, and project-wide tests \u2014 the parent runs shared proof once after all results return.\n</task-contract>\n\n<structure>\nDecompose first, then batch the independent leaves:\n\n task({\n subagent_type: \"coder\",\n context: \"# Goal\nImplement feature X across the codebase\n# Constraints\nFollow existing patterns...\n# Contract\nReturn findings as structured data...\",\n tasks: [\n { role: \"coder\", assignment: \"# Target\nsrc/auth/login.ts\n# Change\nAdd rate limiting to login endpoint\n# Acceptance\nRate limiter works, tests pass\", description: \"Login rate limiting\" },\n { role: \"coder\", assignment: \"# Target\nsrc/auth/signup.ts\n# Change\nAdd input validation\n# Acceptance\nValidation blocks invalid emails\", description: \"Signup validation\" },\n ]\n })\n\nPrefer one wide batch over serial calls when work items do not share files. If tasks overlap, have agents coordinate before editing.\n</structure>\n\n<patterns>\n- **Adversarial verify** \u2014 dispatch skeptical reviewers with distinct targets, then keep only findings you can verify against source.\n- **Perspective-diverse review** \u2014 use separate correctness, security, performance roles instead of identical reviewers.\n- **Completeness critic** \u2014 after the first batch, dispatch one read-only critic that asks what was missed.\n- **No silent caps** \u2014 if you bound coverage (top-N, sampling), state what was dropped and why.\n- **Parent owns closure** \u2014 subagents return evidence; the parent reads it, resolves contradictions, runs proof, and makes the final decision.\n- **Save reusable workflows** \u2014 if the fan-out pattern is reusable, save it as a `/workflow create <name>` command. Generate a JS file with `export const meta = { name, description, phases }` and a default async function that orchestrates via `ctx.runSubagents`. Use the REST API `POST /api/workflows` with body `{ name, source }` to save it, or write the file directly to `.c0de/workflows/<name>.js`. Saved workflows become slash commands (`/<name>`) that can be re-run anytime.\n</patterns>\n\n<execution>\n- **You MUST call the `task` tool** \u2014 this is the defining action of a workflowz request. A response that only does inline read/glob/edit without dispatching sub-agents is a failure.\n- Capture multi-phase workflow state in the visible todo system when available.\n- Batch independent subagents in one `task` call.\n- Give every subagent a narrow target, explicit non-goals, and a concrete return packet.\n- After fan-out returns, read the results, patch or decide, and run the shared gate.\n- Keep going until the task is closed \u2014 returned fan-out is a step, not a stopping point.\n</execution>\n</workflow-notice>";
28
+ export declare const WORKFLOW_NOTICE = "<workflow-notice>\nThe user's message contains the **workflowz** keyword. This triggers the Dynamic Workflow system. You MUST first determine the user's intent:\n\n<intent-detection>\n- **Create/save a workflow** \u2014 the user says \"workflowz \u521B\u5EFA/\u5EFA/save/make a workflow\" \u2014 they want you to GENERATE a reusable JavaScript workflow script and save it. Jump to <create-workflow> below.\n- **Execute a workflow** \u2014 the user says \"workflowz \u626B\u63CF/\u5BA1\u67E5/\u91CD\u6784/\u6D4B\u8BD5...\" \u2014 they want you to drive a deterministic multi-subagent fan-out using the `task` tool. Jump to <task-contract> below.\n- **Both** \u2014 \"workflowz \u521B\u5EFA\u4E00\u4E2A\u626B\u63CF\u5DE5\u4F5C\u6D41\" means create the workflow script first, then optionally run it.\n</intent-detection>\n\n<create-workflow>\nWhen the intent is to CREATE a workflow, you MUST generate a **JavaScript** file (NOT bash/shell/python) with this exact structure:\n\n```js\n// File: .c0de/workflows/<name>.js\nexport const meta = {\n name: '<name>', // lowercase, kebab-case, matches filename\n description: '<description>',\n argsHint: '[optional args]',\n phases: ['phase1', 'phase2', ...],\n timeout: 300, // optional, seconds\n}\n\nexport default async function workflow(ctx) {\n const { runSubagents, runSubagent, utils, progress, project, args } = ctx\n\n // Phase 1: progress('starting...', { phase: 'phase1' })\n // Call runSubagents for parallel fan-out:\n const results = await runSubagents('researcher', [\n { assignment: '...', description: 'task 1' },\n { assignment: '...', description: 'task 2' },\n ])\n // results: [{ ok: true, output: '...' }, { ok: false, error: '...' }]\n\n // Phase 2: verify / cross-check results\n // Phase 3: return summary\n return { output: 'summary text', data: { ... } }\n}\n```\n\n**Rules:**\n1. The file MUST be `.js` with ES module exports (`export const meta` + `export default async function`).\n2. Save to `<project_root>/.c0de/workflows/<name>.js` \u2014 use the `write` tool.\n3. Use `ctx.runSubagents(type, tasks[])` for parallel dispatch, `ctx.runSubagent(type, params)` for single dispatch.\n4. Available subagent types: `general`, `coder`, `researcher`, `reviewer`.\n5. Use `ctx.utils` for file ops: `glob(pattern)`, `grep(pattern, path?)`, `read(path, range?)`, `splitByDirectory(dir, {depth, ignore})`.\n6. Use `ctx.progress(message, { phase })` to report progress.\n7. Parse subagent output with `JSON.parse(r.output)` \u2014 subagents return text.\n8. Return `{ output: 'summary', data: {...} }` as the final result.\n9. NEVER generate bash/shell scripts, python scripts, or any non-JS format for workflows.\n10. After writing the file, tell the user they can run it with `/workflow run <name>` or via POST /api/workflows/:name/run.\n</create-workflow>\n\n<when>\nWhen the intent is to EXECUTE a workflow (not create), you may do a brief inline scout (1-2 tool calls to list files/scope the work), but your NEXT action after scouting MUST be a `task` tool call to fan out. Do NOT spend the entire turn reading files inline \u2014 that defeats the purpose. Common shapes:\n- **Understand** \u2014 parallel readers over subsystems \u2192 structured map.\n- **Design** \u2014 independent approaches \u2192 scored synthesis.\n- **Review** \u2014 split dimensions \u2192 find per dimension \u2192 adversarially verify each finding.\n- **Research** \u2014 multi-modal sweep \u2192 deep-read the hits \u2192 synthesize.\n- **Migrate** \u2014 discover sites \u2192 transform each \u2192 verify.\n</when>\n\n<task-contract>\nCall `task` once per independent fan-out batch using the batch form:\n\n task({\n subagent_type: \"coder\",\n context: \"shared background all subagents need\",\n tasks: [\n { role: \"coder\", assignment: \"specific assignment for agent 1\", description: \"short label\" },\n { role: \"reviewer\", assignment: \"specific assignment for agent 2\", description: \"short label\" },\n ]\n })\n\nAvailable subagent types: `general` (full tools, recursive), `coder` (implementation), `researcher` (read-only scout), `reviewer` (code review). Pick the type that matches each task's intent.\n\n`context` carries shared background prepended to every subagent's prompt \u2014 put the shared contract, conventions, and coordination rules here.\n\nEach task in `tasks[]` must be self-contained:\n- `role`: specialist type for this sub-task (e.g. coder, reviewer, researcher)\n- `assignment`: exact target (files, symbols, subsystem) + what to do + acceptance criteria\n- `description`: short label for the UI\n\nEach subagent runs in an isolated session and returns its result via the `yield` tool. Subagents skip formatters, linters, and project-wide tests \u2014 the parent runs shared proof once after all results return.\n</task-contract>\n\n<structure>\nDecompose first, then batch the independent leaves:\n\n task({\n subagent_type: \"coder\",\n context: \"# Goal\nImplement feature X across the codebase\n# Constraints\nFollow existing patterns...\n# Contract\nReturn findings as structured data...\",\n tasks: [\n { role: \"coder\", assignment: \"# Target\nsrc/auth/login.ts\n# Change\nAdd rate limiting to login endpoint\n# Acceptance\nRate limiter works, tests pass\", description: \"Login rate limiting\" },\n { role: \"coder\", assignment: \"# Target\nsrc/auth/signup.ts\n# Change\nAdd input validation\n# Acceptance\nValidation blocks invalid emails\", description: \"Signup validation\" },\n ]\n })\n\nPrefer one wide batch over serial calls when work items do not share files. If tasks overlap, have agents coordinate before editing.\n</structure>\n\n<patterns>\n- **Adversarial verify** \u2014 dispatch skeptical reviewers with distinct targets, then keep only findings you can verify against source.\n- **Perspective-diverse review** \u2014 use separate correctness, security, performance roles instead of identical reviewers.\n- **Completeness critic** \u2014 after the first batch, dispatch one read-only critic that asks what was missed.\n- **No silent caps** \u2014 if you bound coverage (top-N, sampling), state what was dropped and why.\n- **Parent owns closure** \u2014 subagents return evidence; the parent reads it, resolves contradictions, runs proof, and makes the final decision.\n</patterns>\n\n<execution>\n- **You MUST call the `task` tool** \u2014 this is the defining action of a workflowz request. A response that only does inline read/glob/edit without dispatching sub-agents is a failure.\n- Capture multi-phase workflow state in the visible todo system when available.\n- Batch independent subagents in one `task` call.\n- Give every subagent a narrow target, explicit non-goals, and a concrete return packet.\n- After fan-out returns, read the results, patch or decide, and run the shared gate.\n- Keep going until the task is closed \u2014 returned fan-out is a step, not a stopping point.\n</execution>\n</workflow-notice>";
29
29
  /**
30
30
  * 构建工作流 steering 通知:基础通知 + 可选的已注册工作流列表。
31
31
  * 无工作流时退化为纯基础通知(向后兼容)。
@@ -37,10 +37,59 @@ export function containsWorkflow(text) {
37
37
  * 适配 c0de-agent 的 task 工具 schema(subagent_type + context + tasks[])。
38
38
  */
39
39
  export const WORKFLOW_NOTICE = `<workflow-notice>
40
- The user's message contains the **workflowz** keyword: you MUST drive this task as a deterministic multi-subagent workflow using the \`task\` tool for batched fan-out. This is not optional — the keyword signals the user explicitly wants parallel sub-agent decomposition, not inline work. This overrides any default tendency to do the whole task inline.
40
+ The user's message contains the **workflowz** keyword. This triggers the Dynamic Workflow system. You MUST first determine the user's intent:
41
+
42
+ <intent-detection>
43
+ - **Create/save a workflow** — the user says "workflowz 创建/建/save/make a workflow" — they want you to GENERATE a reusable JavaScript workflow script and save it. Jump to <create-workflow> below.
44
+ - **Execute a workflow** — the user says "workflowz 扫描/审查/重构/测试..." — they want you to drive a deterministic multi-subagent fan-out using the \`task\` tool. Jump to <task-contract> below.
45
+ - **Both** — "workflowz 创建一个扫描工作流" means create the workflow script first, then optionally run it.
46
+ </intent-detection>
47
+
48
+ <create-workflow>
49
+ When the intent is to CREATE a workflow, you MUST generate a **JavaScript** file (NOT bash/shell/python) with this exact structure:
50
+
51
+ \`\`\`js
52
+ // File: .c0de/workflows/<name>.js
53
+ export const meta = {
54
+ name: '<name>', // lowercase, kebab-case, matches filename
55
+ description: '<description>',
56
+ argsHint: '[optional args]',
57
+ phases: ['phase1', 'phase2', ...],
58
+ timeout: 300, // optional, seconds
59
+ }
60
+
61
+ export default async function workflow(ctx) {
62
+ const { runSubagents, runSubagent, utils, progress, project, args } = ctx
63
+
64
+ // Phase 1: progress('starting...', { phase: 'phase1' })
65
+ // Call runSubagents for parallel fan-out:
66
+ const results = await runSubagents('researcher', [
67
+ { assignment: '...', description: 'task 1' },
68
+ { assignment: '...', description: 'task 2' },
69
+ ])
70
+ // results: [{ ok: true, output: '...' }, { ok: false, error: '...' }]
71
+
72
+ // Phase 2: verify / cross-check results
73
+ // Phase 3: return summary
74
+ return { output: 'summary text', data: { ... } }
75
+ }
76
+ \`\`\`
77
+
78
+ **Rules:**
79
+ 1. The file MUST be \`.js\` with ES module exports (\`export const meta\` + \`export default async function\`).
80
+ 2. Save to \`<project_root>/.c0de/workflows/<name>.js\` — use the \`write\` tool.
81
+ 3. Use \`ctx.runSubagents(type, tasks[])\` for parallel dispatch, \`ctx.runSubagent(type, params)\` for single dispatch.
82
+ 4. Available subagent types: \`general\`, \`coder\`, \`researcher\`, \`reviewer\`.
83
+ 5. Use \`ctx.utils\` for file ops: \`glob(pattern)\`, \`grep(pattern, path?)\`, \`read(path, range?)\`, \`splitByDirectory(dir, {depth, ignore})\`.
84
+ 6. Use \`ctx.progress(message, { phase })\` to report progress.
85
+ 7. Parse subagent output with \`JSON.parse(r.output)\` — subagents return text.
86
+ 8. Return \`{ output: 'summary', data: {...} }\` as the final result.
87
+ 9. NEVER generate bash/shell scripts, python scripts, or any non-JS format for workflows.
88
+ 10. After writing the file, tell the user they can run it with \`/workflow run <name>\` or via POST /api/workflows/:name/run.
89
+ </create-workflow>
41
90
 
42
91
  <when>
43
- The user typed workflowz intentionally they want fan-out. You may do a brief inline scout (1-2 tool calls to list files/scope the work), but your NEXT action after scouting MUST be a \`task\` tool call to fan out. Do NOT spend the entire turn reading files inline — that defeats the purpose. Common shapes:
92
+ When the intent is to EXECUTE a workflow (not create), you may do a brief inline scout (1-2 tool calls to list files/scope the work), but your NEXT action after scouting MUST be a \`task\` tool call to fan out. Do NOT spend the entire turn reading files inline — that defeats the purpose. Common shapes:
44
93
  - **Understand** — parallel readers over subsystems → structured map.
45
94
  - **Design** — independent approaches → scored synthesis.
46
95
  - **Review** — split dimensions → find per dimension → adversarially verify each finding.
@@ -93,7 +142,6 @@ Prefer one wide batch over serial calls when work items do not share files. If t
93
142
  - **Completeness critic** — after the first batch, dispatch one read-only critic that asks what was missed.
94
143
  - **No silent caps** — if you bound coverage (top-N, sampling), state what was dropped and why.
95
144
  - **Parent owns closure** — subagents return evidence; the parent reads it, resolves contradictions, runs proof, and makes the final decision.
96
- - **Save reusable workflows** — if the fan-out pattern is reusable, save it as a \`/workflow create <name>\` command. Generate a JS file with \`export const meta = { name, description, phases }\` and a default async function that orchestrates via \`ctx.runSubagents\`. Use the REST API \`POST /api/workflows\` with body \`{ name, source }\` to save it, or write the file directly to \`.c0de/workflows/<name>.js\`. Saved workflows become slash commands (\`/<name>\`) that can be re-run anytime.
97
145
  </patterns>
98
146
 
99
147
  <execution>
@@ -112,12 +160,12 @@ Prefer one wide batch over serial calls when work items do not share files. If t
112
160
  export function buildWorkflowNotice(workflows) {
113
161
  const registeredSection = workflows && workflows.length > 0
114
162
  ? `\n<registered-workflows>
115
- Available workflow templates you can invoke with the task tool's workflow parameter:
163
+ Available saved workflow scripts (run with /workflow run <name> or POST /api/workflows/:name/run):
116
164
  ${workflows.map((w) => `- ${w.name}: ${w.description}`).join('\n')}
117
- If none fit, orchestrate inline using runSubagent fan-out as described below, then save your workflow with POST /api/workflows { name, source } for reuse.
165
+ To create a new workflow, generate a .js file with export const meta + export default async function(ctx) and save it to .c0de/workflows/<name>.js.
118
166
  </registered-workflows>`
119
167
  : `\n<registered-workflows>
120
- No saved workflows yet. After orchestrating a successful fan-out, save it with POST /api/workflows { name, source } for reuse.
168
+ No saved workflow scripts yet. When the user asks to create a workflow, generate a .js file (export const meta + export default async function) and save it to .c0de/workflows/<name>.js.
121
169
  </registered-workflows>`;
122
170
  return WORKFLOW_NOTICE + registeredSection;
123
171
  }
@@ -1,5 +1,6 @@
1
1
  import type { AgentDependencies, AgentState, CommandResult } from '../types.js';
2
2
  import type { WorkflowRegistry } from './registry.js';
3
+ import type { WorkflowEntry } from './types.js';
3
4
  /** executeWorkflow 的参数。 */
4
5
  type ExecuteWorkflowOpts = {
5
6
  registry: WorkflowRegistry;
@@ -7,6 +8,8 @@ type ExecuteWorkflowOpts = {
7
8
  args: string;
8
9
  deps: AgentDependencies;
9
10
  parent: AgentState;
11
+ /** 已解析的 entry(如项目级工作流),优先于 registry.get(name)。 */
12
+ entry?: WorkflowEntry;
10
13
  onProgress?: (message: string, detail?: unknown) => void;
11
14
  };
12
15
  /**
@@ -20,8 +20,8 @@ function createTimeoutPromise(timeoutMs, timeoutSeconds) {
20
20
  * 工作流 return 的 output 作为 text 返回;异常捕获为 error。
21
21
  */
22
22
  async function executeWorkflow(opts) {
23
- const { registry, name, args, deps, parent, onProgress } = opts;
24
- const entry = registry.get(name);
23
+ const { registry, name, args, deps, parent, onProgress, entry: resolvedEntry } = opts;
24
+ const entry = resolvedEntry ?? registry.get(name);
25
25
  if (!entry) {
26
26
  const available = registry
27
27
  .list()
@@ -5,4 +5,79 @@ export type ResolvedProject = {
5
5
  gitRemote: string | null;
6
6
  gitBranch: string | null;
7
7
  };
8
+ /** 文件 git 状态分类(用于文件树高亮)。按「最值得注意」优先级排列。 */
9
+ export type GitStatusCode = 'modified' | 'staged' | 'untracked' | 'conflict' | 'deleted' | 'ignored';
10
+ /** 目录聚合时的优先级(越大越优先展示)。ignored 最低:被忽略目录下若有真实变更仍显示变更态。 */
11
+ export declare const GIT_STATUS_PRIORITY: Record<GitStatusCode, number>;
12
+ export declare function getGitStatus(cwd: string): Record<string, GitStatusCode> | null;
13
+ /** 检查给定路径中哪些被 gitignore 规则覆盖。
14
+ *
15
+ * 用 git check-ignore 只查询传入的路径(不递归),返回被忽略路径的集合。
16
+ * 只检查当前展开目录的直接子项(通常 10-50 个),不递归进 node_modules 等。
17
+ * 非 git 仓库或无忽略文件时返回空集。
18
+ *
19
+ * 注意:git check-ignore 在「无路径被忽略」或「非 git 仓库」时退出码为 1,
20
+ * 这两种情况都返回空集。 */
21
+ export declare function checkIgnored(cwd: string, paths: string[]): Set<string>;
22
+ /** 追加条目到 .gitignore(去重,文件不存在则创建)。 */
23
+ export declare function appendToGitignore(cwd: string, patterns: string[]): void;
24
+ /**
25
+ * 取当前分支名(非 git 仓库返回 null)。
26
+ */
27
+ export declare function getGitBranch(cwd: string): string | null;
28
+ /** 最后一次提交摘要(供分支名 hover tooltip)。 */
29
+ export interface GitLastCommit {
30
+ subject: string;
31
+ hash: string;
32
+ author: string;
33
+ date: string;
34
+ }
35
+ /**
36
+ * 取最后一次提交信息(subject/hash/author/相对时间)。
37
+ * 非 git 仓库或无提交(如全新仓库)返回 null。
38
+ */
39
+ export declare function getGitLastCommit(cwd: string): GitLastCommit | null;
40
+ /**
41
+ * 取工作区变更摘要(供 LLM 生成 commit message)。
42
+ * 包含 staged + unstaged diff(相对 HEAD)和 untracked 文件名列表。
43
+ * 非 git 仓库或无变更返回 null。
44
+ */
45
+ export declare function getGitDiffSummary(cwd: string): {
46
+ diff: string;
47
+ fileCount: number;
48
+ } | null;
49
+ /**
50
+ * 执行 git add -A + git commit。成功返回 commit 短 hash,失败返回 error 信息。
51
+ */
52
+ export declare function performGitCommit(cwd: string, message: string): {
53
+ hash: string;
54
+ } | {
55
+ error: string;
56
+ };
57
+ /** 分支信息。 */
58
+ export interface GitBranchInfo {
59
+ name: string;
60
+ current: boolean;
61
+ lastSubject: string | null;
62
+ }
63
+ /**
64
+ * 列出本地分支及当前分支标记(非 git 仓库返回 null)。
65
+ */
66
+ export declare function listGitBranches(cwd: string): GitBranchInfo[] | null;
67
+ /**
68
+ * 切换到指定分支(git checkout)。成功返回分支名,失败返回 error。
69
+ */
70
+ export declare function checkoutGitBranch(cwd: string, branch: string): {
71
+ branch: string;
72
+ } | {
73
+ error: string;
74
+ };
75
+ /**
76
+ * 创建并切换到新分支。成功返回分支名,失败返回 error。
77
+ */
78
+ export declare function createGitBranch(cwd: string, name: string): {
79
+ branch: string;
80
+ } | {
81
+ error: string;
82
+ };
8
83
  export declare function resolveProject(directory: string): ResolvedProject;