c0de-agent 1.2.0 → 1.3.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/index.d.ts +3 -1
- package/dist/core/index.js +2 -1
- package/dist/core/loop.js +16 -1
- package/dist/core/slash.js +73 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/workflow.d.ts +14 -3
- package/dist/core/workflow.js +34 -12
- package/dist/core/workflows/builtins.d.ts +7 -0
- package/dist/core/workflows/builtins.js +192 -0
- package/dist/core/workflows/context.d.ts +17 -0
- package/dist/core/workflows/context.js +232 -0
- package/dist/core/workflows/discovery.d.ts +11 -0
- package/dist/core/workflows/discovery.js +69 -0
- package/dist/core/workflows/index.d.ts +7 -0
- package/dist/core/workflows/index.js +5 -0
- package/dist/core/workflows/registry.d.ts +20 -0
- package/dist/core/workflows/registry.js +49 -0
- package/dist/core/workflows/runtime.d.ts +18 -0
- package/dist/core/workflows/runtime.js +70 -0
- package/dist/core/workflows/types.d.ts +95 -0
- package/dist/core/workflows/types.js +1 -0
- package/dist/server/app.js +3 -0
- package/dist/server/context.js +11 -0
- package/dist/server/routes/chat.js +13 -3
- package/dist/server/routes/session.js +6 -1
- package/dist/server/routes/workflows.d.ts +5 -0
- package/dist/server/routes/workflows.js +149 -0
- package/dist/server/server.js +14 -1
- package/dist/server/types.d.ts +2 -0
- package/dist/session/branch.d.ts +3 -1
- package/dist/session/branch.js +9 -2
- package/dist/session/session.d.ts +3 -1
- package/dist/session/session.js +12 -1
- package/dist/shared/types/message.d.ts +2 -0
- package/package.json +1 -1
package/dist/core/index.d.ts
CHANGED
|
@@ -11,4 +11,6 @@ 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
13
|
export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, CommandContext, CommandResult, LLMSegment, PendingToolCall, ProjectInfo, PromptContext, SlashCommand, TokenBudget, } from './types.js';
|
|
14
|
-
export { containsWorkflow, WORKFLOW_NOTICE } from './workflow.js';
|
|
14
|
+
export { buildWorkflowNotice, containsWorkflow, WORKFLOW_NOTICE } from './workflow.js';
|
|
15
|
+
export type { WorkflowAgentResult, WorkflowContext, WorkflowEntry, WorkflowMeta, WorkflowRegistry, WorkflowResult, WorkflowUtils, } from './workflows/index.js';
|
|
16
|
+
export { BUILTIN_WORKFLOWS, buildWorkflowContext, createAndPopulateRegistry, createBuiltinWorkflows, createWorkflowRegistry, discoverWorkflows, executeWorkflow, } from './workflows/index.js';
|
package/dist/core/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { buildSystemPrompt } from './prompt.js';
|
|
|
9
9
|
export { builtinCommands, createSlashRegistry, parseSlashInput } from './slash.js';
|
|
10
10
|
export { clearSteering, drainSteering, injectSteering } from './steering.js';
|
|
11
11
|
export { executeToolCall, executeToolCalls, partitionByConflict } from './tool-exec.js';
|
|
12
|
-
export { containsWorkflow, WORKFLOW_NOTICE } from './workflow.js';
|
|
12
|
+
export { buildWorkflowNotice, containsWorkflow, WORKFLOW_NOTICE } from './workflow.js';
|
|
13
|
+
export { BUILTIN_WORKFLOWS, buildWorkflowContext, createAndPopulateRegistry, createBuiltinWorkflows, createWorkflowRegistry, discoverWorkflows, executeWorkflow, } from './workflows/index.js';
|
package/dist/core/loop.js
CHANGED
|
@@ -712,8 +712,23 @@ export async function* agentLoop(state, deps) {
|
|
|
712
712
|
const steering = drainSteering(state);
|
|
713
713
|
const { entries, snapshots } = await getSessionContext(deps.db, state.session.id);
|
|
714
714
|
let chatMessages = entriesToChatMessages(entries, snapshots);
|
|
715
|
+
// steering 消息必须插入到最后一条 user 消息之前(而非末尾 push),
|
|
716
|
+
// 使 notice 在相关 user 消息的同一 turn 生效。
|
|
717
|
+
// 参考 oh-my-pi magic-keyword fix:末尾 system 消息会被模型忽略。
|
|
715
718
|
for (const s of steering) {
|
|
716
|
-
|
|
719
|
+
let lastUserIdx = -1;
|
|
720
|
+
for (let i = chatMessages.length - 1; i >= 0; i--) {
|
|
721
|
+
if (chatMessages[i]?.role === 'user') {
|
|
722
|
+
lastUserIdx = i;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (lastUserIdx >= 0) {
|
|
727
|
+
chatMessages.splice(lastUserIdx, 0, { role: 'system', content: s });
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
chatMessages.push({ role: 'system', content: s });
|
|
731
|
+
}
|
|
717
732
|
}
|
|
718
733
|
if (deps.hookRunner) {
|
|
719
734
|
const hookResult = await deps.hookRunner.runHooks('message:before', {
|
package/dist/core/slash.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { createSession } from '../session/session.js';
|
|
2
|
+
import { createAgent } from './agent.js';
|
|
3
|
+
import { BUILTIN_WORKFLOWS, createWorkflowRegistry, executeWorkflow } from './workflows/index.js';
|
|
1
4
|
function parseSlashInput(input) {
|
|
2
5
|
const trimmed = input.trim();
|
|
3
6
|
if (!trimmed.startsWith('/'))
|
|
@@ -32,6 +35,7 @@ const helpCommand = {
|
|
|
32
35
|
' /help Show this help',
|
|
33
36
|
' /fork [index] Fork session from a message',
|
|
34
37
|
' /config [k][v] View or set configuration',
|
|
38
|
+
' /workflow Manage and run workflows',
|
|
35
39
|
];
|
|
36
40
|
return { _tag: 'text', text: lines.join('\n') };
|
|
37
41
|
},
|
|
@@ -102,6 +106,74 @@ const configCommand = {
|
|
|
102
106
|
return { _tag: 'success', message: 'Config updates are handled via the config API' };
|
|
103
107
|
},
|
|
104
108
|
};
|
|
109
|
+
const workflowCommand = {
|
|
110
|
+
name: 'workflow',
|
|
111
|
+
description: 'Manage and run workflows',
|
|
112
|
+
argsHint: '[run|show|list] [name] [args]',
|
|
113
|
+
execute: async (args, ctx) => {
|
|
114
|
+
const parts = args.split(/\s+/).filter(Boolean);
|
|
115
|
+
const subcommand = parts[0] ?? 'list';
|
|
116
|
+
let registry = ctx.workflowRegistry;
|
|
117
|
+
if (!registry) {
|
|
118
|
+
// 回退:创建仅含内置的注册表
|
|
119
|
+
registry = createWorkflowRegistry();
|
|
120
|
+
for (const wf of BUILTIN_WORKFLOWS) {
|
|
121
|
+
registry.register(wf);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (subcommand === 'list') {
|
|
125
|
+
const workflows = registry.list();
|
|
126
|
+
const lines = ['Available workflows:'];
|
|
127
|
+
for (const wf of workflows) {
|
|
128
|
+
const phases = wf.meta.phases ? ` [${wf.meta.phases.join('→')}]` : '';
|
|
129
|
+
lines.push(` /${wf.meta.name}${phases} — ${wf.meta.description} (${wf.source})`);
|
|
130
|
+
}
|
|
131
|
+
lines.push('');
|
|
132
|
+
lines.push('Usage: /workflow run <name> [args]');
|
|
133
|
+
return { _tag: 'text', text: lines.join('\n') };
|
|
134
|
+
}
|
|
135
|
+
if (subcommand === 'show') {
|
|
136
|
+
const name = parts[1];
|
|
137
|
+
if (!name)
|
|
138
|
+
return { _tag: 'error', message: 'Usage: /workflow show <name>' };
|
|
139
|
+
const wf = registry.get(name);
|
|
140
|
+
if (!wf) {
|
|
141
|
+
return { _tag: 'error', message: `Unknown workflow: ${name}` };
|
|
142
|
+
}
|
|
143
|
+
const code = wf.sourceCode ?? '// source not available';
|
|
144
|
+
return {
|
|
145
|
+
_tag: 'text',
|
|
146
|
+
text: `// ${wf.meta.name}: ${wf.meta.description}\n\n${code}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (subcommand === 'run') {
|
|
150
|
+
const name = parts[1];
|
|
151
|
+
if (!name)
|
|
152
|
+
return { _tag: 'error', message: 'Usage: /workflow run <name> [args]' };
|
|
153
|
+
const wfArgs = parts.slice(2).join(' ');
|
|
154
|
+
const agentConfig = {
|
|
155
|
+
provider: ctx.config.defaultProvider,
|
|
156
|
+
model: ctx.config.defaultModel,
|
|
157
|
+
tools: [],
|
|
158
|
+
plugins: ctx.config.plugins.enabled,
|
|
159
|
+
agentName: 'default',
|
|
160
|
+
};
|
|
161
|
+
const session = await createSession(ctx.deps.db, `workflow:${name}`, undefined, 'workflow');
|
|
162
|
+
const parent = await createAgent(session, agentConfig, ctx.deps);
|
|
163
|
+
return executeWorkflow({
|
|
164
|
+
registry,
|
|
165
|
+
name,
|
|
166
|
+
args: wfArgs,
|
|
167
|
+
deps: ctx.deps,
|
|
168
|
+
parent,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
_tag: 'error',
|
|
173
|
+
message: `Unknown subcommand: ${subcommand}. Use: list, run, show`,
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
};
|
|
105
177
|
const builtinCommands = [
|
|
106
178
|
helpCommand,
|
|
107
179
|
compactCommand,
|
|
@@ -109,5 +181,6 @@ const builtinCommands = [
|
|
|
109
181
|
clearCommand,
|
|
110
182
|
forkCommand,
|
|
111
183
|
configCommand,
|
|
184
|
+
workflowCommand,
|
|
112
185
|
];
|
|
113
186
|
export { builtinCommands, createSlashRegistry, parseSlashInput };
|
package/dist/core/types.d.ts
CHANGED
package/dist/core/workflow.d.ts
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
* - 单任务模式:`{ subagent_type?, prompt, description? }`
|
|
12
12
|
*/
|
|
13
13
|
/**
|
|
14
|
-
* 判断 `text` 是否在 prose 中包含独立关键词 "workflowz"
|
|
15
|
-
*
|
|
14
|
+
* 判断 `text` 是否在 prose 中包含独立关键词 "workflowz"。
|
|
15
|
+
*
|
|
16
|
+
* 边界为「非 ASCII 词/路径字符」,因此 CJK 字符和中文标点也算合法边界
|
|
17
|
+
* (中文无词间空格);但仍拒绝 workflowzed / reworkflowz / workflowz.test.ts
|
|
18
|
+
* 等拉丁词续或路径内嵌形式。
|
|
16
19
|
*
|
|
17
20
|
* 简化版 prose 检测:移除 ``` 代码块和 `行内代码` 后再匹配。
|
|
18
21
|
*/
|
|
@@ -22,4 +25,12 @@ export declare function containsWorkflow(text: string): boolean;
|
|
|
22
25
|
*
|
|
23
26
|
* 适配 c0de-agent 的 task 工具 schema(subagent_type + context + tasks[])。
|
|
24
27
|
*/
|
|
25
|
-
export declare const WORKFLOW_NOTICE = "<workflow-notice>\nThe user's message contains the **workflowz** keyword: drive this task as a deterministic multi-subagent workflow
|
|
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</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
|
+
/**
|
|
30
|
+
* 构建工作流 steering 通知:基础通知 + 可选的已注册工作流列表。
|
|
31
|
+
* 无工作流时退化为纯基础通知(向后兼容)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildWorkflowNotice(workflows?: Array<{
|
|
34
|
+
name: string;
|
|
35
|
+
description: string;
|
|
36
|
+
}>): string;
|
package/dist/core/workflow.js
CHANGED
|
@@ -10,11 +10,17 @@
|
|
|
10
10
|
* - 批量模式:`{ subagent_type, context, tasks: [{ prompt, description? }] }`
|
|
11
11
|
* - 单任务模式:`{ subagent_type?, prompt, description? }`
|
|
12
12
|
*/
|
|
13
|
-
//
|
|
14
|
-
|
|
13
|
+
// 检测:小写关键词,两侧不得是 ASCII 词/路径字符(字母、数字、_、.、/、-)。
|
|
14
|
+
// 用 [\w./-] 而非 \S 做边界,使 CJK 字符和中文标点也能当边界——
|
|
15
|
+
// 中文无词间空格,用 \S 会拒绝「请workflowz」「workflowz。」等合法写法。
|
|
16
|
+
// 仍拒绝 reworkflowz / workflowzed / workflowz.test.ts 等。
|
|
17
|
+
const WORKFLOW_WORD = /(?<![\w./-])workflowz(?![\w./-])/;
|
|
15
18
|
/**
|
|
16
|
-
* 判断 `text` 是否在 prose 中包含独立关键词 "workflowz"
|
|
17
|
-
*
|
|
19
|
+
* 判断 `text` 是否在 prose 中包含独立关键词 "workflowz"。
|
|
20
|
+
*
|
|
21
|
+
* 边界为「非 ASCII 词/路径字符」,因此 CJK 字符和中文标点也算合法边界
|
|
22
|
+
* (中文无词间空格);但仍拒绝 workflowzed / reworkflowz / workflowz.test.ts
|
|
23
|
+
* 等拉丁词续或路径内嵌形式。
|
|
18
24
|
*
|
|
19
25
|
* 简化版 prose 检测:移除 ``` 代码块和 `行内代码` 后再匹配。
|
|
20
26
|
*/
|
|
@@ -31,10 +37,10 @@ export function containsWorkflow(text) {
|
|
|
31
37
|
* 适配 c0de-agent 的 task 工具 schema(subagent_type + context + tasks[])。
|
|
32
38
|
*/
|
|
33
39
|
export const WORKFLOW_NOTICE = `<workflow-notice>
|
|
34
|
-
The user's message contains the **workflowz** keyword: drive this task as a deterministic multi-subagent workflow
|
|
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.
|
|
35
41
|
|
|
36
42
|
<when>
|
|
37
|
-
|
|
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:
|
|
38
44
|
- **Understand** — parallel readers over subsystems → structured map.
|
|
39
45
|
- **Design** — independent approaches → scored synthesis.
|
|
40
46
|
- **Review** — split dimensions → find per dimension → adversarially verify each finding.
|
|
@@ -49,8 +55,8 @@ Call \`task\` once per independent fan-out batch using the batch form:
|
|
|
49
55
|
subagent_type: "coder",
|
|
50
56
|
context: "shared background all subagents need",
|
|
51
57
|
tasks: [
|
|
52
|
-
{
|
|
53
|
-
{
|
|
58
|
+
{ role: "coder", assignment: "specific assignment for agent 1", description: "short label" },
|
|
59
|
+
{ role: "reviewer", assignment: "specific assignment for agent 2", description: "short label" },
|
|
54
60
|
]
|
|
55
61
|
})
|
|
56
62
|
|
|
@@ -59,7 +65,8 @@ Available subagent types: \`general\` (full tools, recursive), \`coder\` (implem
|
|
|
59
65
|
\`context\` carries shared background prepended to every subagent's prompt — put the shared contract, conventions, and coordination rules here.
|
|
60
66
|
|
|
61
67
|
Each task in \`tasks[]\` must be self-contained:
|
|
62
|
-
- \`
|
|
68
|
+
- \`role\`: specialist type for this sub-task (e.g. coder, reviewer, researcher)
|
|
69
|
+
- \`assignment\`: exact target (files, symbols, subsystem) + what to do + acceptance criteria
|
|
63
70
|
- \`description\`: short label for the UI
|
|
64
71
|
|
|
65
72
|
Each subagent runs in an isolated session and returns its result via the \`yield\` tool. Subagents skip formatters, linters, and project-wide tests — the parent runs shared proof once after all results return.
|
|
@@ -70,10 +77,10 @@ Decompose first, then batch the independent leaves:
|
|
|
70
77
|
|
|
71
78
|
task({
|
|
72
79
|
subagent_type: "coder",
|
|
73
|
-
context: "# Goal
|
|
80
|
+
context: "# Goal\nImplement feature X across the codebase\n# Constraints\nFollow existing patterns...\n# Contract\nReturn findings as structured data...",
|
|
74
81
|
tasks: [
|
|
75
|
-
{
|
|
76
|
-
{
|
|
82
|
+
{ 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" },
|
|
83
|
+
{ role: "coder", assignment: "# Target\nsrc/auth/signup.ts\n# Change\nAdd input validation\n# Acceptance\nValidation blocks invalid emails", description: "Signup validation" },
|
|
77
84
|
]
|
|
78
85
|
})
|
|
79
86
|
|
|
@@ -89,6 +96,7 @@ Prefer one wide batch over serial calls when work items do not share files. If t
|
|
|
89
96
|
</patterns>
|
|
90
97
|
|
|
91
98
|
<execution>
|
|
99
|
+
- **You MUST call the \`task\` tool** — 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.
|
|
92
100
|
- Capture multi-phase workflow state in the visible todo system when available.
|
|
93
101
|
- Batch independent subagents in one \`task\` call.
|
|
94
102
|
- Give every subagent a narrow target, explicit non-goals, and a concrete return packet.
|
|
@@ -96,3 +104,17 @@ Prefer one wide batch over serial calls when work items do not share files. If t
|
|
|
96
104
|
- Keep going until the task is closed — returned fan-out is a step, not a stopping point.
|
|
97
105
|
</execution>
|
|
98
106
|
</workflow-notice>`;
|
|
107
|
+
/**
|
|
108
|
+
* 构建工作流 steering 通知:基础通知 + 可选的已注册工作流列表。
|
|
109
|
+
* 无工作流时退化为纯基础通知(向后兼容)。
|
|
110
|
+
*/
|
|
111
|
+
export function buildWorkflowNotice(workflows) {
|
|
112
|
+
const registeredSection = workflows && workflows.length > 0
|
|
113
|
+
? `\n<registered-workflows>
|
|
114
|
+
Available workflow templates you can invoke with the task tool's workflow parameter:
|
|
115
|
+
${workflows.map((w) => `- ${w.name}: ${w.description}`).join('\n')}
|
|
116
|
+
If none fit, orchestrate inline using runSubagent fan-out as described below.
|
|
117
|
+
</registered-workflows>`
|
|
118
|
+
: '';
|
|
119
|
+
return WORKFLOW_NOTICE + registeredSection;
|
|
120
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkflowEntry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* 构建全部内置工作流条目(动态导入源码字符串)。幂等、内部缓存。
|
|
4
|
+
*/
|
|
5
|
+
declare function createBuiltinWorkflows(): Promise<WorkflowEntry[]>;
|
|
6
|
+
declare const BUILTIN_WORKFLOWS: WorkflowEntry[];
|
|
7
|
+
export { BUILTIN_WORKFLOWS, createBuiltinWorkflows };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
// ── security-audit ──
|
|
6
|
+
const SECURITY_AUDIT_SOURCE = `export const meta = {
|
|
7
|
+
name: 'security-audit',
|
|
8
|
+
description: '并行安全审计:按目录拆分扫描 → 独立审查员交叉验证 → 汇总报告',
|
|
9
|
+
argsHint: '[扫描目标描述]',
|
|
10
|
+
phases: ['scan', 'verify', 'report'],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async function workflow(ctx) {
|
|
14
|
+
const { runSubagents, utils, progress, project } = ctx
|
|
15
|
+
|
|
16
|
+
progress('拆分代码库为模块...')
|
|
17
|
+
const modules = await utils.splitByDirectory(project.rootDir, { depth: 2 })
|
|
18
|
+
|
|
19
|
+
progress(\`并行扫描 \${modules.length} 个模块...\`, { phase: 'scan' })
|
|
20
|
+
const scans = await runSubagents('researcher', modules.map((m) => ({
|
|
21
|
+
assignment: \`你是安全扫描专家。扫描目录 \${m.path} 下的代码,检查以下安全风险:
|
|
22
|
+
- SQL 注入风险
|
|
23
|
+
- 硬编码密钥 / 密码 / Token
|
|
24
|
+
- 权限绕过模式
|
|
25
|
+
- XSS / CSRF 风险
|
|
26
|
+
- 不安全的依赖使用
|
|
27
|
+
|
|
28
|
+
文件列表:\${m.files.slice(0, 50).join(', ')}
|
|
29
|
+
|
|
30
|
+
返回 JSON:{ findings: [{ severity: 'critical|warning|info', file, line, issue, evidence }] }\`,
|
|
31
|
+
description: \`扫描 \${m.name}\`,
|
|
32
|
+
})))
|
|
33
|
+
|
|
34
|
+
const allFindings = scans
|
|
35
|
+
.filter((r) => r.ok)
|
|
36
|
+
.flatMap((r) => { try { return JSON.parse(r.output).findings ?? [] } catch { return [] } })
|
|
37
|
+
|
|
38
|
+
progress(\`交叉验证 \${allFindings.length} 个发现...\`, { phase: 'verify' })
|
|
39
|
+
const verified = await runSubagents('reviewer', allFindings.map((f) => ({
|
|
40
|
+
assignment: \`对抗审查以下安全发现,判断是否为真实问题还是误报:
|
|
41
|
+
\${JSON.stringify(f, null, 2)}
|
|
42
|
+
|
|
43
|
+
返回 JSON:{ confirmed: boolean, reason: string, adjustedSeverity?: 'critical|warning|info' }\`,
|
|
44
|
+
description: '验证发现',
|
|
45
|
+
})))
|
|
46
|
+
|
|
47
|
+
const confirmed = verified
|
|
48
|
+
.filter((r) => r.ok)
|
|
49
|
+
.map((r) => { try { return JSON.parse(r.output) } catch { return null } })
|
|
50
|
+
.filter((v) => v?.confirmed)
|
|
51
|
+
|
|
52
|
+
progress('生成报告...', { phase: 'report' })
|
|
53
|
+
const summary = \`扫描 \${modules.length} 个模块,发现 \${allFindings.length} 个候选问题,\${confirmed.length} 个经交叉验证确认。\`
|
|
54
|
+
|
|
55
|
+
return { output: summary, data: { confirmed, totalCandidates: allFindings.length } }
|
|
56
|
+
}`;
|
|
57
|
+
// ── code-review ──
|
|
58
|
+
const CODE_REVIEW_SOURCE = `export const meta = {
|
|
59
|
+
name: 'code-review',
|
|
60
|
+
description: '多维度代码审查:correctness/security/performance/maintainability 各派独立 reviewer',
|
|
61
|
+
argsHint: '[审查目标路径]',
|
|
62
|
+
phases: ['review', 'merge'],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default async function workflow(ctx) {
|
|
66
|
+
const { runSubagents, progress, project, args } = ctx
|
|
67
|
+
const target = args || project.rootDir
|
|
68
|
+
|
|
69
|
+
const dimensions = ['correctness', 'security', 'performance', 'maintainability']
|
|
70
|
+
|
|
71
|
+
progress(\`并行 \${dimensions.length} 个维度审查...\`, { phase: 'review' })
|
|
72
|
+
const reviews = await runSubagents('reviewer', dimensions.map((dim) => ({
|
|
73
|
+
assignment: \`你是 \${dim} 维度的代码审查专家。审查 \${target} 下的代码。
|
|
74
|
+
|
|
75
|
+
关注点:
|
|
76
|
+
\${dim === 'correctness' ? '- 逻辑正确性、边界条件、错误处理' : ''}
|
|
77
|
+
\${dim === 'security' ? '- 安全漏洞、输入验证、权限控制' : ''}
|
|
78
|
+
\${dim === 'performance' ? '- 性能瓶颈、不必要计算、内存泄漏' : ''}
|
|
79
|
+
\${dim === 'maintainability' ? '- 代码可读性、重复代码、命名规范' : ''}
|
|
80
|
+
|
|
81
|
+
返回 JSON:{ findings: [{ severity: 'critical|warning|info', file, line, issue, suggestion }] }\`,
|
|
82
|
+
description: \`\${dim} 审查\`,
|
|
83
|
+
role: dim,
|
|
84
|
+
})))
|
|
85
|
+
|
|
86
|
+
const allFindings = reviews
|
|
87
|
+
.filter((r) => r.ok)
|
|
88
|
+
.flatMap((r) => { try { return JSON.parse(r.output).findings ?? [] } catch { return [] } })
|
|
89
|
+
|
|
90
|
+
progress('合并去重并生成报告...', { phase: 'merge' })
|
|
91
|
+
const seen = new Set()
|
|
92
|
+
const deduped = allFindings.filter((f) => {
|
|
93
|
+
const key = \`\${f.file}:\${f.line}:\${f.issue}\`
|
|
94
|
+
if (seen.has(key)) return false
|
|
95
|
+
seen.add(key)
|
|
96
|
+
return true
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const critical = deduped.filter((f) => f.severity === 'critical').length
|
|
100
|
+
const warning = deduped.filter((f) => f.severity === 'warning').length
|
|
101
|
+
const info = deduped.filter((f) => f.severity === 'info').length
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
output: \`审查完成:\${critical} critical, \${warning} warning, \${info} info(共 \${deduped.length} 条)\`,
|
|
105
|
+
data: { findings: deduped, summary: { critical, warning, info, total: deduped.length } },
|
|
106
|
+
}
|
|
107
|
+
}`;
|
|
108
|
+
// ── migration-check ──
|
|
109
|
+
const MIGRATION_CHECK_SOURCE = `export const meta = {
|
|
110
|
+
name: 'migration-check',
|
|
111
|
+
description: '迁移影响检查:分析变更的 breaking changes / deprecated / new features',
|
|
112
|
+
argsHint: '[base-branch或commit]',
|
|
113
|
+
phases: ['diff', 'analyze', 'report'],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default async function workflow(ctx) {
|
|
117
|
+
const { runSubagents, progress, project, args } = ctx
|
|
118
|
+
const baseRef = args || 'HEAD~1'
|
|
119
|
+
|
|
120
|
+
progress(\`分析 \${baseRef} 到当前版本的变更...\`, { phase: 'diff' })
|
|
121
|
+
|
|
122
|
+
const categories = ['breaking-changes', 'deprecated', 'new-features']
|
|
123
|
+
|
|
124
|
+
progress(\`并行分析 \${categories.length} 个类别...\`, { phase: 'analyze' })
|
|
125
|
+
const analyses = await runSubagents('researcher', categories.map((cat) => ({
|
|
126
|
+
assignment: \`你是代码迁移分析专家。分析项目 \${project.rootDir} 从 \${baseRef} 到当前的变更,
|
|
127
|
+
聚焦 \${cat === 'breaking-changes' ? '破坏性变更(API 签名变更、删除、行为变更)' : cat === 'deprecated' ? '已废弃的功能和 API' : '新增功能和特性'}。
|
|
128
|
+
|
|
129
|
+
返回 JSON:{ items: [{ category: '\${cat}', description, files, impact: 'high|medium|low' }] }\`,
|
|
130
|
+
description: \`\${cat} 分析\`,
|
|
131
|
+
role: cat,
|
|
132
|
+
})))
|
|
133
|
+
|
|
134
|
+
const allItems = analyses
|
|
135
|
+
.filter((r) => r.ok)
|
|
136
|
+
.flatMap((r) => { try { return JSON.parse(r.output).items ?? [] } catch { return [] } })
|
|
137
|
+
|
|
138
|
+
progress('生成迁移报告...', { phase: 'report' })
|
|
139
|
+
const high = allItems.filter((i) => i.impact === 'high').length
|
|
140
|
+
const medium = allItems.filter((i) => i.impact === 'medium').length
|
|
141
|
+
const low = allItems.filter((i) => i.impact === 'low').length
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
output: \`迁移检查完成:\${allItems.length} 个变更项(\${high} high, \${medium} medium, \${low} low)\`,
|
|
145
|
+
data: { items: allItems, summary: { high, medium, low, total: allItems.length } },
|
|
146
|
+
}
|
|
147
|
+
}`;
|
|
148
|
+
// ── factory:源码字符串 → WorkflowEntry(保证 show === run)──
|
|
149
|
+
/**
|
|
150
|
+
* 把内置工作流的源码字符串编译为 WorkflowEntry:写入临时 .js → dynamic import →
|
|
151
|
+
* 取出 meta 与 default。这样 /workflow show 展示的源码就是实际执行的代码,
|
|
152
|
+
* 杜绝「源码字符串」与「TS 函数」两份副本各自漂移的维护陷阱。
|
|
153
|
+
*/
|
|
154
|
+
async function sourceToEntry(source) {
|
|
155
|
+
const tmpDir = mkdtempSync(join(tmpdir(), 'wf-builtin-'));
|
|
156
|
+
const tmpFile = join(tmpDir, 'builtin.js');
|
|
157
|
+
writeFileSync(tmpFile, source, 'utf-8');
|
|
158
|
+
try {
|
|
159
|
+
const mod = (await import(pathToFileURL(tmpFile).href));
|
|
160
|
+
if (!mod.meta || typeof mod.default !== 'function') {
|
|
161
|
+
throw new Error('builtin workflow source missing `export const meta` or default export');
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
meta: mod.meta,
|
|
165
|
+
source: 'builtin',
|
|
166
|
+
execute: mod.default,
|
|
167
|
+
sourceCode: source,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** 缓存:内置工作流只动态导入一次。 */
|
|
175
|
+
let _builtinWorkflowsCache;
|
|
176
|
+
/**
|
|
177
|
+
* 构建全部内置工作流条目(动态导入源码字符串)。幂等、内部缓存。
|
|
178
|
+
*/
|
|
179
|
+
async function createBuiltinWorkflows() {
|
|
180
|
+
if (_builtinWorkflowsCache)
|
|
181
|
+
return _builtinWorkflowsCache;
|
|
182
|
+
_builtinWorkflowsCache = await Promise.all([
|
|
183
|
+
sourceToEntry(SECURITY_AUDIT_SOURCE),
|
|
184
|
+
sourceToEntry(CODE_REVIEW_SOURCE),
|
|
185
|
+
sourceToEntry(MIGRATION_CHECK_SOURCE),
|
|
186
|
+
]);
|
|
187
|
+
return _builtinWorkflowsCache;
|
|
188
|
+
}
|
|
189
|
+
// 顶层 await:模块加载时一次性解析,使同步消费方(注册表惰性 getter、slash 回退、
|
|
190
|
+
// 单元测试)无需改造即可拿到已解析的内置条目;解析失败则 fail-fast。
|
|
191
|
+
const BUILTIN_WORKFLOWS = await createBuiltinWorkflows();
|
|
192
|
+
export { BUILTIN_WORKFLOWS, createBuiltinWorkflows };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SubAgentRequest, SubAgentResult } from '../../shared/types/tool.js';
|
|
2
|
+
import type { AgentDependencies, AgentState } from '../types.js';
|
|
3
|
+
import type { WorkflowContext } from './types.js';
|
|
4
|
+
/** buildWorkflowContext 的参数。 */
|
|
5
|
+
type BuildContextOpts = {
|
|
6
|
+
deps: AgentDependencies;
|
|
7
|
+
parent: AgentState;
|
|
8
|
+
args: string;
|
|
9
|
+
onProgress: (message: string, detail?: unknown) => void;
|
|
10
|
+
/** 项目名(从 ProjectInfo 传入)。 */
|
|
11
|
+
projectName?: string;
|
|
12
|
+
/** 测试注入:覆盖内部 runSubAgent 调用。生产环境省略,走 deps 关联的 loop.runSubAgent。 */
|
|
13
|
+
runSubAgentFn?: (request: SubAgentRequest) => Promise<SubAgentResult>;
|
|
14
|
+
};
|
|
15
|
+
/** 构建 WorkflowContext,注入 runSubagent/utils/progress。 */
|
|
16
|
+
declare function buildWorkflowContext(opts: BuildContextOpts): WorkflowContext;
|
|
17
|
+
export { buildWorkflowContext };
|