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.
- package/dist/core/agent.js +4 -0
- package/dist/core/config.js +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/loop.js +9 -0
- package/dist/core/prompt-registry.d.ts +2 -2
- package/dist/core/prompt-registry.js +42 -3
- package/dist/core/slash.js +40 -6
- package/dist/core/types.d.ts +10 -1
- package/dist/core/workflow.d.ts +1 -1
- package/dist/core/workflow.js +54 -6
- package/dist/core/workflows/runtime.d.ts +3 -0
- package/dist/core/workflows/runtime.js +2 -2
- package/dist/project/resolve.d.ts +75 -0
- package/dist/project/resolve.js +253 -1
- package/dist/server/app.js +3 -0
- package/dist/server/context.js +2 -1
- package/dist/server/dev.js +3 -1
- package/dist/server/routes/chat.js +12 -0
- package/dist/server/routes/commands.js +1 -0
- package/dist/server/routes/files.js +252 -4
- package/dist/server/routes/terminal.js +2 -1
- package/dist/server/routes/todo.d.ts +4 -0
- package/dist/server/routes/todo.js +107 -0
- package/dist/server/routes/workflows.js +51 -11
- package/dist/server/server.d.ts +8 -1
- package/dist/server/server.js +113 -23
- package/dist/server/terminal/pty-manager.d.ts +14 -0
- package/dist/server/terminal/pty-manager.js +105 -7
- package/dist/shared/types/agent.d.ts +9 -0
- package/dist/shared/types/config.d.ts +6 -0
- package/dist/shared/types/tool.d.ts +13 -0
- package/dist/tools/builtin/todo.d.ts +67 -0
- package/dist/tools/builtin/todo.js +517 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/types.d.ts +32 -1
- package/package.json +2 -1
package/dist/core/agent.js
CHANGED
|
@@ -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
|
package/dist/core/config.js
CHANGED
|
@@ -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'] },
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
},
|
package/dist/core/slash.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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,
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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, };
|
package/dist/core/workflow.d.ts
CHANGED
|
@@ -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
|
|
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
|
* 无工作流时退化为纯基础通知(向后兼容)。
|
package/dist/core/workflow.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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;
|