tokenmaw 0.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/README.md +150 -0
- package/agents/coordinator.md +13 -0
- package/agents/explorer.md +21 -0
- package/agents/implement.md +23 -0
- package/agents/main.md +25 -0
- package/agents/review.md +21 -0
- package/dist/backend.js +595 -0
- package/dist/cli.js +101 -0
- package/dist/config.js +155 -0
- package/dist/diff.js +45 -0
- package/dist/domain/agent.js +1 -0
- package/dist/fetch.js +110 -0
- package/dist/infra/file-snapshot.js +54 -0
- package/dist/infra/tools.js +1300 -0
- package/dist/markdown.js +274 -0
- package/dist/model-config.js +48 -0
- package/dist/policy.js +80 -0
- package/dist/responses.js +81 -0
- package/dist/runtime/agent-registry.js +139 -0
- package/dist/runtime/agent-runtime.js +993 -0
- package/dist/runtime/agent-store.js +152 -0
- package/dist/runtime/locks.js +46 -0
- package/dist/runtime/session-timeline.js +92 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/types.js +1 -0
- package/dist/ui/clipboard.js +24 -0
- package/dist/ui/commands.js +20 -0
- package/dist/ui/composer-layout.js +31 -0
- package/dist/ui/fullscreen-tui.js +1405 -0
- package/dist/ui/markdown.js +81 -0
- package/dist/ui/syntax.js +17 -0
- package/dist/ui/tui-design.js +94 -0
- package/dist/ui/welcome.js +24 -0
- package/dist/version.js +4 -0
- package/docs/architecture-revision.md +281 -0
- package/package.json +47 -0
- package/skills/debugging.md +18 -0
- package/skills/git-workflow.md +14 -0
- package/skills/node-express.md +27 -0
- package/skills/python-flask.md +22 -0
- package/skills/react-component.md +24 -0
- package/skills/sql-database.md +18 -0
- package/skills/testing.md +12 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import blessed from 'blessed';
|
|
2
|
+
import { diffKind, renderMarkdown } from '../markdown.js';
|
|
3
|
+
import { highlightCode } from './syntax.js';
|
|
4
|
+
// Historical messages re-render on every frame; memoize the (expensive) result.
|
|
5
|
+
// Entries are keyed by content+width and evicted least-recently-used.
|
|
6
|
+
const renderCache = new Map();
|
|
7
|
+
const RENDER_CACHE_LIMIT = 600;
|
|
8
|
+
/** Blessed tags keep patch colors independent of Chalk's stdout/NO_COLOR detection. */
|
|
9
|
+
export function renderTuiMarkdown(content, columns) {
|
|
10
|
+
const cacheKey = `${columns}\u0000${content}`;
|
|
11
|
+
const cached = renderCache.get(cacheKey);
|
|
12
|
+
if (cached !== undefined) {
|
|
13
|
+
renderCache.delete(cacheKey);
|
|
14
|
+
renderCache.set(cacheKey, cached);
|
|
15
|
+
return cached;
|
|
16
|
+
}
|
|
17
|
+
const out = [];
|
|
18
|
+
let prose = [];
|
|
19
|
+
let diff = false;
|
|
20
|
+
let otherCode = false;
|
|
21
|
+
const flush = () => {
|
|
22
|
+
if (prose.length)
|
|
23
|
+
out.push(blessed.escape(renderMarkdown(prose.join('\n'), columns)));
|
|
24
|
+
prose = [];
|
|
25
|
+
};
|
|
26
|
+
for (const line of content.split('\n')) {
|
|
27
|
+
if (!diff && !otherCode && /^```(?:diff|patch)\s*$/.test(line)) {
|
|
28
|
+
flush();
|
|
29
|
+
diff = true;
|
|
30
|
+
}
|
|
31
|
+
else if (diff && /^```\s*$/.test(line)) {
|
|
32
|
+
diff = false;
|
|
33
|
+
}
|
|
34
|
+
else if (diff) {
|
|
35
|
+
const kind = diffKind(line);
|
|
36
|
+
if (kind === 'add' || kind === 'del') {
|
|
37
|
+
// Deep, near-black tinted backgrounds keep the code readable while
|
|
38
|
+
// still signaling added/removed lines.
|
|
39
|
+
const background = kind === 'add' ? '#10281a' : '#2b1215';
|
|
40
|
+
const base = kind === 'add' ? '#9fd0a6' : '#d99f9f';
|
|
41
|
+
const width = blessed.unicode.strWidth(line);
|
|
42
|
+
out.push(`{${background}-bg}{${base}-fg}${highlightCode(line)}${' '.repeat(Math.max(0, columns - width))}{/${base}-fg}{/${background}-bg}`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const color = kind === 'hunk' ? 'cyan' : 'white';
|
|
46
|
+
out.push(`{${color}-fg}${kind === 'context' ? highlightCode(line) : blessed.escape(line)}{/${color}-fg}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (/^```/.test(line)) {
|
|
51
|
+
flush();
|
|
52
|
+
otherCode = !otherCode;
|
|
53
|
+
}
|
|
54
|
+
else if (otherCode)
|
|
55
|
+
out.push(highlightCode(line));
|
|
56
|
+
else
|
|
57
|
+
prose.push(line);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
flush();
|
|
61
|
+
const rendered = out.join('\n');
|
|
62
|
+
renderCache.set(cacheKey, rendered);
|
|
63
|
+
if (renderCache.size > RENDER_CACHE_LIMIT) {
|
|
64
|
+
renderCache.delete(renderCache.keys().next().value);
|
|
65
|
+
}
|
|
66
|
+
return rendered;
|
|
67
|
+
}
|
|
68
|
+
export function toolDiff(tool, output) {
|
|
69
|
+
if (tool === 'edit_file' || tool === 'write_file') {
|
|
70
|
+
return output.match(/```diff\r?\n[\s\S]*?\r?\n```/)?.[0];
|
|
71
|
+
}
|
|
72
|
+
if (tool === 'git_diff') {
|
|
73
|
+
try {
|
|
74
|
+
const result = JSON.parse(output);
|
|
75
|
+
if (typeof result.diff === 'string' && result.diff.trim())
|
|
76
|
+
return `\`\`\`diff\n${result.diff.trimEnd()}\n\`\`\``;
|
|
77
|
+
}
|
|
78
|
+
catch { /* Tool errors are displayed as ordinary activity. */ }
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import blessed from 'blessed';
|
|
2
|
+
/** Small lexical highlighter. Unknown languages remain readable plain text. */
|
|
3
|
+
export function highlightCode(source, light = false) {
|
|
4
|
+
const colors = light
|
|
5
|
+
? ['#58665c', '#98502c', '#6141a0', '#175d93', '#9a3570']
|
|
6
|
+
: ['#84918b', '#cead83', '#ba9ce0', '#87b9db', '#d493b5'];
|
|
7
|
+
const pattern = /(\/\/[^\n]*|\/\*[\s\S]*?\*\/|<!--.*?-->)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)|(\b(?:const|let|var|function|return|if|else|for|while|class|import|from|export|async|await|new|true|false|null|undefined|def|print|in|None|True|False|public|private|interface|type)\b)|(<\/?[\w-]+|\b[\w-]+(?=\s*=))|(\b\d+(?:\.\d+)?(?:px|em|rem|%)?\b)/g;
|
|
8
|
+
let result = '';
|
|
9
|
+
let offset = 0;
|
|
10
|
+
for (const match of source.matchAll(pattern)) {
|
|
11
|
+
result += blessed.escape(source.slice(offset, match.index));
|
|
12
|
+
const color = colors[match.slice(1).findIndex(value => value !== undefined)];
|
|
13
|
+
result += `{${color}-fg}${blessed.escape(match[0])}{/${color}-fg}`;
|
|
14
|
+
offset = match.index + match[0].length;
|
|
15
|
+
}
|
|
16
|
+
return result + blessed.escape(source.slice(offset));
|
|
17
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Stable layout breakpoints keep the transcript readable when Agents is open. */
|
|
2
|
+
export function tuiLayout(width, activityRequested) {
|
|
3
|
+
const columns = Math.max(20, Math.floor(width));
|
|
4
|
+
const horizontalPadding = columns < 64 ? 1 : 2;
|
|
5
|
+
if (!activityRequested) {
|
|
6
|
+
return { activity: 'hidden', activityWidth: 0, conversationWidth: columns, horizontalPadding, showSecondaryStatus: columns >= 58 };
|
|
7
|
+
}
|
|
8
|
+
if (columns >= 110) {
|
|
9
|
+
const activityWidth = Math.min(38, Math.max(32, Math.floor(columns * 0.3)));
|
|
10
|
+
return { activity: 'split', activityWidth, conversationWidth: columns - activityWidth, horizontalPadding, showSecondaryStatus: true };
|
|
11
|
+
}
|
|
12
|
+
const activityWidth = columns < 64 ? columns : Math.min(38, Math.floor(columns * 0.46));
|
|
13
|
+
return { activity: 'overlay', activityWidth, conversationWidth: columns, horizontalPadding, showSecondaryStatus: columns >= 58 };
|
|
14
|
+
}
|
|
15
|
+
export const STATUS_PRESENTATION = {
|
|
16
|
+
queued: { icon: '○', label: 'Queued', tone: 'muted' },
|
|
17
|
+
running: { icon: '●', label: 'Running', tone: 'accent' },
|
|
18
|
+
idle: { icon: '✓', label: 'Done', tone: 'success' },
|
|
19
|
+
waiting: { icon: '◌', label: 'Waiting', tone: 'warning' },
|
|
20
|
+
failed: { icon: '!', label: 'Failed', tone: 'error' },
|
|
21
|
+
cancelled: { icon: '×', label: 'Stopped', tone: 'muted' },
|
|
22
|
+
};
|
|
23
|
+
const TOOL_LABELS = {
|
|
24
|
+
bash: 'Run',
|
|
25
|
+
edit_file: 'Edit',
|
|
26
|
+
file_info: 'Inspect',
|
|
27
|
+
git_diff: 'Review changes',
|
|
28
|
+
git_log: 'Read history',
|
|
29
|
+
git_status: 'Check repository',
|
|
30
|
+
list_dir: 'List files',
|
|
31
|
+
load_skill: 'Load skill',
|
|
32
|
+
read_file: 'Read',
|
|
33
|
+
read_files: 'Read files',
|
|
34
|
+
repo_map: 'Map repository',
|
|
35
|
+
search_files: 'Find files',
|
|
36
|
+
search_history: 'Search history',
|
|
37
|
+
search_text: 'Search',
|
|
38
|
+
spawn_agent: 'Start agent',
|
|
39
|
+
send_agent: 'Message agent',
|
|
40
|
+
wait_agent: 'Wait for agent',
|
|
41
|
+
cancel_agent: 'Stop agent',
|
|
42
|
+
compact_context: 'Compact context',
|
|
43
|
+
web_search: 'Search web',
|
|
44
|
+
write_file: 'Write',
|
|
45
|
+
};
|
|
46
|
+
function parsedInput(input) {
|
|
47
|
+
if (!input)
|
|
48
|
+
return {};
|
|
49
|
+
try {
|
|
50
|
+
const value = JSON.parse(input);
|
|
51
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function firstString(input, keys) {
|
|
58
|
+
for (const key of keys) {
|
|
59
|
+
const value = input[key];
|
|
60
|
+
if (typeof value === 'string' && value.trim())
|
|
61
|
+
return value.trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export function toolPresentation(tool, input) {
|
|
65
|
+
const values = parsedInput(input);
|
|
66
|
+
const label = TOOL_LABELS[tool] ?? tool.replaceAll('_', ' ').replace(/^./, (letter) => letter.toUpperCase());
|
|
67
|
+
let detail = firstString(values, ['path', 'query', 'glob', 'command', 'agent', 'instance_id', 'name']);
|
|
68
|
+
if (!detail && tool === 'read_files' && Array.isArray(values.paths))
|
|
69
|
+
detail = values.paths.filter((value) => typeof value === 'string').slice(0, 2).join(', ');
|
|
70
|
+
return { label, ...(detail ? { detail: detail.replace(/\s+/g, ' ') } : {}) };
|
|
71
|
+
}
|
|
72
|
+
export function elapsedLabel(startedAt, currentTime = Date.now()) {
|
|
73
|
+
if (!startedAt)
|
|
74
|
+
return '';
|
|
75
|
+
const seconds = Math.max(0, Math.floor((currentTime - startedAt) / 1000));
|
|
76
|
+
if (seconds < 60)
|
|
77
|
+
return `${seconds}s`;
|
|
78
|
+
return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
79
|
+
}
|
|
80
|
+
export function diffPreview(diff, maxLines = 12) {
|
|
81
|
+
const lines = diff.split('\n');
|
|
82
|
+
if (lines.length <= maxLines + 2)
|
|
83
|
+
return diff;
|
|
84
|
+
const fence = lines.at(-1)?.trim() === '```';
|
|
85
|
+
const body = lines.slice(0, maxLines + 1);
|
|
86
|
+
const hidden = lines.length - body.length - (fence ? 1 : 0);
|
|
87
|
+
body.push(`... ${hidden} more lines`, ...(fence ? ['```'] : []));
|
|
88
|
+
return body.join('\n');
|
|
89
|
+
}
|
|
90
|
+
export function visibleTimelineEntries(entries, limit = 400) {
|
|
91
|
+
const safeLimit = Math.max(1, Math.floor(limit));
|
|
92
|
+
const omitted = Math.max(0, entries.length - safeLimit);
|
|
93
|
+
return { entries: omitted ? entries.slice(omitted) : entries, omitted };
|
|
94
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** A quiet, static wordmark. Center against the window, not its scrollback. */
|
|
2
|
+
export function renderWelcome(width, height, terminalHeight = height, frame = 0) {
|
|
3
|
+
width = Math.max(1, Math.floor(width));
|
|
4
|
+
height = Math.max(1, Math.floor(height));
|
|
5
|
+
const rows = Array.from({ length: height }, () => '');
|
|
6
|
+
const wordmark = width >= 9 ? 'C O D E R' : 'CODER'.slice(0, width);
|
|
7
|
+
const center = Math.max(0, Math.min(height - 1, Math.floor((terminalHeight - 1) / 2)));
|
|
8
|
+
const left = ' '.repeat(Math.max(0, Math.floor((width - wordmark.length) / 2)));
|
|
9
|
+
rows[center] = `${left}{white-fg}{bold}${wordmark}{/bold}{/white-fg}`;
|
|
10
|
+
if (center + 2 < height && width >= 9) {
|
|
11
|
+
// A four-second, eased breath in one subdued hue, with a slight spatial
|
|
12
|
+
// falloff. No discrete moving cell or white flash at the turning points.
|
|
13
|
+
const breath = (1 - Math.cos((frame % 80) / 80 * Math.PI * 2)) / 2;
|
|
14
|
+
const rule = [0, 1, 2].map((index) => {
|
|
15
|
+
const intensity = breath * (index === 1 ? 1 : 0.85);
|
|
16
|
+
const low = [48, 66, 72];
|
|
17
|
+
const high = [91, 135, 146];
|
|
18
|
+
const color = '#' + low.map((value, channel) => Math.round(value + (high[channel] - value) * intensity).toString(16).padStart(2, '0')).join('');
|
|
19
|
+
return `{${color}-fg}─{/${color}-fg}`;
|
|
20
|
+
}).join('');
|
|
21
|
+
rows[center + 2] = `${' '.repeat(Math.floor((width - 3) / 2))}${rule}`;
|
|
22
|
+
}
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Agent Runtime Architecture
|
|
2
|
+
|
|
3
|
+
> 状态:核心实现已完成,持续验收中
|
|
4
|
+
>
|
|
5
|
+
> 更新日期:2026-08-30
|
|
6
|
+
>
|
|
7
|
+
> 目标版本:0.3.0
|
|
8
|
+
|
|
9
|
+
## 1. 为什么重构
|
|
10
|
+
|
|
11
|
+
当前实现把 agent 的组织方式写死在 TypeScript 中:`MasterCoordinator` 同时承担接待、路由、规划、执行、展示、任务管理和子 agent 调度,`RouteAction`、planner intent、Reception/Brain/Worker 模型角色以及大量条件分支共同定义了一条固定工作流。
|
|
12
|
+
|
|
13
|
+
这造成了三个直接问题:
|
|
14
|
+
|
|
15
|
+
1. 普通用户消息也被创建成独立任务,连续对话变成任务管理器。
|
|
16
|
+
2. 新增或改变一种 agent 协作方式必须修改 Runtime 代码。
|
|
17
|
+
3. main agent 要等待复杂路由和规划,无法快速回应用户。
|
|
18
|
+
|
|
19
|
+
本次重构将项目定位为通用的 **Agent Framework / Actor Runtime**。代码提供执行机制;agent 是什么、可以调用谁、应该如何协作,由 Markdown Agent Spec 定义,并由 LLM 在运行时自主决策。
|
|
20
|
+
|
|
21
|
+
## 2. 已确认的设计
|
|
22
|
+
|
|
23
|
+
### 2.1 总体结构
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
用户
|
|
27
|
+
↕
|
|
28
|
+
main agent(轻量入口、快速响应、唯一用户出口)
|
|
29
|
+
↕
|
|
30
|
+
一个或多个 coordinator agent(复杂推理与协调)
|
|
31
|
+
↕
|
|
32
|
+
explorer / implement / review / 项目自定义 agents
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `main` 只负责用户交互、简单问题和快速委派,不承担复杂协调。
|
|
36
|
+
- 默认倾向复用一个 coordinator;面对多套复杂且无关的工作,main 可以启动多个 coordinator 并行处理。
|
|
37
|
+
- coordinator 根据自己的 spec 自主决定调用哪些 specialist、是否并行、何时追问或取消。
|
|
38
|
+
- 所有 agent 结果都必须经过 main 才能展示给用户。
|
|
39
|
+
- main、coordinator 和 specialist 在 Runtime 中是同一种 AgentInstance,不存在代码级角色特权。
|
|
40
|
+
|
|
41
|
+
### 2.2 Runtime 与 Spec 的边界
|
|
42
|
+
|
|
43
|
+
Runtime 只负责不可绕过的机制:
|
|
44
|
+
|
|
45
|
+
- Agent Spec 的发现、解析、覆盖与权限校验
|
|
46
|
+
- 模型调用、流式输出和上下文裁剪
|
|
47
|
+
- AgentInstance 的创建、调度、mailbox、暂停与取消
|
|
48
|
+
- 并发、超时、最大调用深度和循环保护
|
|
49
|
+
- 文件工具、Shell 策略、路径边界、文件锁和原子写入
|
|
50
|
+
- Session、消息、运行实例和事件的持久化及崩溃恢复
|
|
51
|
+
|
|
52
|
+
Agent Spec 负责可变化的策略:
|
|
53
|
+
|
|
54
|
+
- agent 的职责和行为说明
|
|
55
|
+
- 使用哪个模型
|
|
56
|
+
- 可以使用哪些工具
|
|
57
|
+
- 可以调用哪些具体 agent 或 agent 命名空间
|
|
58
|
+
- 何时委派、复用、并行、汇总和回应
|
|
59
|
+
|
|
60
|
+
Runtime 不得包含 router、planner、writer、reviewer 等角色语义,也不得根据 agent 名称进入特殊代码分支。唯一的入口约定是 Session 从名为 `main` 的 spec 启动。
|
|
61
|
+
|
|
62
|
+
## 3. Agent Spec
|
|
63
|
+
|
|
64
|
+
### 3.1 发现和覆盖
|
|
65
|
+
|
|
66
|
+
Agent Spec 按以下顺序加载,后者同名整体覆盖前者:
|
|
67
|
+
|
|
68
|
+
1. 包内置:`agents/**/*.md`
|
|
69
|
+
2. 用户级:`~/.coder/agents/**/*.md`
|
|
70
|
+
3. 项目级:`.coder/agents/**/*.md`
|
|
71
|
+
|
|
72
|
+
Agent ID 取相对于 agents 根目录的路径并移除 `.md`。例如:
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
.coder/agents/review/security.md → review/security
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3.2 文件格式
|
|
79
|
+
|
|
80
|
+
```md
|
|
81
|
+
---
|
|
82
|
+
description: 快速接收用户输入并委派复杂工作
|
|
83
|
+
model: fast
|
|
84
|
+
tools: []
|
|
85
|
+
agents:
|
|
86
|
+
- coordinator
|
|
87
|
+
- coordinator/*
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
你是用户的直接交互入口……
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
字段约束:
|
|
94
|
+
|
|
95
|
+
- `description`:必填,向可调用该 agent 的 LLM 描述其能力。
|
|
96
|
+
- `model`:可选;模型 alias 或 `inherit`。省略时继承 Session 默认模型。
|
|
97
|
+
- `tools`:具体工具名列表或 `*`。Spec 只能缩小权限,不能突破全局安全策略。
|
|
98
|
+
- `agents`:允许调用的 agent 选择器列表。
|
|
99
|
+
|
|
100
|
+
Agent 选择器支持:
|
|
101
|
+
|
|
102
|
+
- `explorer`:允许一个具体 agent。
|
|
103
|
+
- `review/*`:允许一个命名空间。
|
|
104
|
+
- `*`:允许所有已注册 agent。
|
|
105
|
+
- `[]`:禁止调用其他 agent。
|
|
106
|
+
|
|
107
|
+
Runtime 只把匹配后的 agent ID 和 description 注入当前 agent 上下文;目标 agent 的完整正文仅在实际调用时加载。
|
|
108
|
+
|
|
109
|
+
## 4. Actor 执行模型
|
|
110
|
+
|
|
111
|
+
### 4.1 核心实体
|
|
112
|
+
|
|
113
|
+
- **Session**:一段持续的用户对话,拥有一个持久 main instance。
|
|
114
|
+
- **AgentInstance**:某个 Agent Spec 的有状态运行实例,保留独立消息历史和 mailbox。
|
|
115
|
+
- **Turn**:AgentInstance 因一条输入被唤醒后进行的一轮模型与工具循环。
|
|
116
|
+
- **Event**:面向 TUI 和持久化层的增量状态变化。
|
|
117
|
+
|
|
118
|
+
用户每次输入只创建 Session message 和 Turn,不创建 Task。
|
|
119
|
+
|
|
120
|
+
AgentInstance 状态使用:
|
|
121
|
+
|
|
122
|
+
- `queued`:等待调度
|
|
123
|
+
- `running`:正在执行模型或工具
|
|
124
|
+
- `idle`:本轮完成,可以继续接收消息
|
|
125
|
+
- `waiting`:等待其他 agent 的结果
|
|
126
|
+
- `failed`:本轮失败,实例仍保留上下文
|
|
127
|
+
- `cancelled`:实例已关闭
|
|
128
|
+
|
|
129
|
+
### 4.2 Agent 生命周期原语
|
|
130
|
+
|
|
131
|
+
对有 agent 调用权限的 LLM 暴露统一工具:
|
|
132
|
+
|
|
133
|
+
- `spawn_agent(agent, message)`:创建实例并返回 instance ID。
|
|
134
|
+
- `send_agent(instanceId, message)`:向已有实例发送补充、修正或结果。
|
|
135
|
+
- `wait_agent(instanceIds)`:等待一个或多个实例产生结果或进入 idle。
|
|
136
|
+
- `cancel_agent(instanceId)`:中断并关闭实例。
|
|
137
|
+
|
|
138
|
+
消息是双向的。父 agent 可以向子 agent 发要求,子 agent 可以向父 agent 汇报。子消息到达 idle 的父 agent 时,Runtime 自动唤醒父 agent。
|
|
139
|
+
|
|
140
|
+
默认约束:
|
|
141
|
+
|
|
142
|
+
- 全局同时执行的 Turn 上限为 4。
|
|
143
|
+
- agent 嵌套深度上限为 4。
|
|
144
|
+
- 禁止调用祖先链中已存在的 agent,避免递归环。
|
|
145
|
+
- 子 agent 默认只收到自己的 spec、调用消息、工作区元数据和显式传入的上下文,不复制完整用户会话。
|
|
146
|
+
|
|
147
|
+
## 5. 用户交互语义
|
|
148
|
+
|
|
149
|
+
### 5.1 首次响应
|
|
150
|
+
|
|
151
|
+
main 使用轻量模型。复杂请求到达时,main 应立即给出自然、简短的确认,同时启动或通知 coordinator,不等待复杂推理完成。
|
|
152
|
+
|
|
153
|
+
### 5.2 连续输入和纠正
|
|
154
|
+
|
|
155
|
+
main 正在生成时用户继续输入:
|
|
156
|
+
|
|
157
|
+
1. Runtime 中断当前 main 模型生成。
|
|
158
|
+
2. 新消息追加到同一个 Session。
|
|
159
|
+
3. 已启动的 coordinator 和 specialist 保持运行。
|
|
160
|
+
4. main 根据新上下文决定补充现有 coordinator、取消它或启动新的 coordinator。
|
|
161
|
+
|
|
162
|
+
### 5.3 输出边界
|
|
163
|
+
|
|
164
|
+
- 只有 Session 的 main instance 可以产生用户可见文本。
|
|
165
|
+
- coordinator 和 specialist 的进展与结果写入 main mailbox。
|
|
166
|
+
- main 被 mailbox 唤醒后决定是否立即通知用户以及如何表达。
|
|
167
|
+
|
|
168
|
+
## 6. TUI 设计
|
|
169
|
+
|
|
170
|
+
Web UI 将被完全删除,项目只保留现代化 TUI。
|
|
171
|
+
|
|
172
|
+
TUI 不再模拟任务管理器,而采用现代桌面聊天应用布局:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
┌ TokenMaw · session · model ───────────────────────────────┐
|
|
176
|
+
│ │
|
|
177
|
+
│ 对话时间线 │
|
|
178
|
+
│ 用户与 main 的消息、流式输出、轻量状态提示 │
|
|
179
|
+
│ │
|
|
180
|
+
├───────────────────────────────┬────────────────────────┤
|
|
181
|
+
│ composer │ Agent Activity │
|
|
182
|
+
│ 输入、快捷提示、当前状态 │ 可折叠运行树和工具活动 │
|
|
183
|
+
└───────────────────────────────┴────────────────────────┘
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
视觉与交互要求:
|
|
187
|
+
|
|
188
|
+
- 减少粗重边框,使用留白、层次色、弱分隔线和统一状态色。
|
|
189
|
+
- 对话是主区域,Agent Activity 是次要且可隐藏的侧栏。
|
|
190
|
+
- activity 以父子树展示 instance,不产生任务卡。
|
|
191
|
+
- 流式内容原位更新,避免整屏闪烁。
|
|
192
|
+
- 输入框始终可用;运行中的 agent 不阻塞继续输入。
|
|
193
|
+
- `/provider` 打开统一 provider 管理弹窗。
|
|
194
|
+
- `/model` 打开模型选择弹窗。
|
|
195
|
+
- `/agents` 展示实际生效的 spec、来源、模型和权限。
|
|
196
|
+
- `/sessions` 管理会话;`/new` 新建会话;`/clear` 清空当前对话。
|
|
197
|
+
- `Ctrl+K` 打开命令面板,`Ctrl+B` 显示或隐藏 Agent Activity。
|
|
198
|
+
|
|
199
|
+
## 7. 配置、存储和 CLI
|
|
200
|
+
|
|
201
|
+
- 删除 `reception/brain/worker` 和 `roleModels`。
|
|
202
|
+
- `/model` 设置 Session 默认模型;Agent Spec 的 `model` 可以覆盖它。
|
|
203
|
+
- 保留 `/provider` 的用户级 provider 配置。
|
|
204
|
+
- 默认执行 `maw` 进入 TUI。
|
|
205
|
+
- 提供 `maw run --prompt <text>` 作为一次性非交互会话。
|
|
206
|
+
- 删除 `coder web`、Web API、SSE、静态前端和 Web 测试。
|
|
207
|
+
- 删除旧 `submit/get/execute-plan` 任务协议。
|
|
208
|
+
|
|
209
|
+
新数据写入 `~/.coder/runtime/`:
|
|
210
|
+
|
|
211
|
+
- Session 与用户可见消息
|
|
212
|
+
- AgentInstance 元数据和独立历史
|
|
213
|
+
- mailbox 与运行状态
|
|
214
|
+
- 可重放事件
|
|
215
|
+
|
|
216
|
+
旧 `~/.coder/tasks` 不再加载,也不自动删除。旧 conversation 文件可以只读迁移,历史里的 `taskId` 字段忽略。
|
|
217
|
+
|
|
218
|
+
## 8. 内置 Agent Specs
|
|
219
|
+
|
|
220
|
+
首版内置以下 Markdown 文档,不设置任何代码特权:
|
|
221
|
+
|
|
222
|
+
- `main`:快速用户交互、简单回答、选择或复用 coordinator。
|
|
223
|
+
- `coordinator`:复杂目标理解、工作拆分、协调和向 main 汇报。
|
|
224
|
+
- `explorer`:只读代码调查。
|
|
225
|
+
- `implement`:代码修改与验证。
|
|
226
|
+
- `review`:只读审查与风险检查。
|
|
227
|
+
|
|
228
|
+
项目和用户可以覆盖任何内置定义,也可以增加 `coordinator/frontend`、`coordinator/backend` 等命名空间。
|
|
229
|
+
|
|
230
|
+
## 9. 当前状态与实施清单
|
|
231
|
+
|
|
232
|
+
### 9.1 当前仍然存在的旧实现
|
|
233
|
+
|
|
234
|
+
截至本文更新时,以下旧实现已处理:
|
|
235
|
+
|
|
236
|
+
- [x] `MasterCoordinator` 已替换为通用 `AgentRuntime`。
|
|
237
|
+
- [x] 每条 prompt 改为 Session message + Turn,不再创建 `PromptTask`。
|
|
238
|
+
- [x] router、planner、presentation、writer 等固定流程已从编译树删除。
|
|
239
|
+
- [x] `roleModels` 和 Reception/Brain/Worker 配置已移除;模型由 Agent Spec 决定。
|
|
240
|
+
- [x] TUI 已改为现代对话布局,Activity 作为可折叠侧栏。
|
|
241
|
+
- [x] Web 服务、Web API 和静态前端已删除。
|
|
242
|
+
- [x] Agent Spec 注册表和 Actor Runtime 已实现。
|
|
243
|
+
|
|
244
|
+
### 9.2 实施顺序
|
|
245
|
+
|
|
246
|
+
1. [x] 建立 Agent Spec 类型、解析器、三层 Registry 和选择器权限测试。
|
|
247
|
+
2. [x] 建立 Session、AgentInstance、mailbox、事件和原子持久化。
|
|
248
|
+
3. [x] 实现模型/工具循环及 `spawn/send/wait/cancel` 原语。
|
|
249
|
+
4. [x] 实现 main 中断续聊、后台 agent 保持运行及 mailbox 唤醒。
|
|
250
|
+
5. [x] 添加五个内置 Agent Specs。
|
|
251
|
+
6. [x] 将 CLI 和现代 TUI 切换到新 Runtime。
|
|
252
|
+
7. [x] 删除 Web、旧 coordinator/planner/task 工作流和角色模型配置。
|
|
253
|
+
8. [x] 更新 README、测试和 benchmark 入口。
|
|
254
|
+
9. [x] 执行 typecheck、build、全量测试并重新全局链接 `maw`。
|
|
255
|
+
|
|
256
|
+
以上清单表示本轮已完成。仍未完成的长期事项单独列在下一节,不得把它们误读为已实现。
|
|
257
|
+
|
|
258
|
+
### 9.3 仍未完成或需要后续加强
|
|
259
|
+
|
|
260
|
+
- [ ] AgentInstance 目前按 Session 快照持久化,尚未拆成独立 append-only 日志;多进程并发写仍不在支持范围。
|
|
261
|
+
- [ ] Agent Spec 解析器是受限 YAML frontmatter,不是完整 YAML 兼容实现;复杂 YAML 结构需后续扩展。
|
|
262
|
+
- [ ] 全局工具安全仍由现有进程级 policy 负责,尚未提供 OS 级沙箱。
|
|
263
|
+
- [ ] main 的“相关工作复用还是新建 coordinator”由 LLM/spec 决定,Runtime 尚未提供语义相似度或去重兜底。
|
|
264
|
+
- [ ] TUI 已现代化但仍是 Blessed 单体界面,尚未拆成可复用组件;暂不提供 Web 客户端。
|
|
265
|
+
- [ ] 多用户、多进程服务化和远程 agent 执行尚未实现。
|
|
266
|
+
- [ ] Agent 级 token/cost 统计和完整 trace 导出尚未实现。
|
|
267
|
+
|
|
268
|
+
## 10. 验收标准
|
|
269
|
+
|
|
270
|
+
- 连续输入十条普通消息,Session 中增加十条消息,但不出现十个任务。
|
|
271
|
+
- main 可以在 coordinator 完成前先向用户输出自然回应。
|
|
272
|
+
- 相关补充会发给现有 coordinator;main 也能为无关复杂工作启动第二个 coordinator。
|
|
273
|
+
- coordinator 可以只依靠 spec 描述选择并调用 specialist,Runtime 中不存在角色名称分支。
|
|
274
|
+
- 项目、用户和内置 spec 的覆盖顺序正确。
|
|
275
|
+
- agent 精确选择器、命名空间选择器、`*` 和空权限均被 Runtime 强制执行。
|
|
276
|
+
- 用户中途输入会中断 main 当前生成,但不会自动取消后台 agent。
|
|
277
|
+
- 只有 main 的输出会进入用户对话时间线。
|
|
278
|
+
- TUI 保持输入可用,并能展开查看 agent 与工具活动。
|
|
279
|
+
- 项目中不存在 Web 服务入口、Web 静态资源或 `coder web` 命令。
|
|
280
|
+
- 重启后 Session、AgentInstance 和未处理 mailbox 可以恢复。
|
|
281
|
+
- `npm run typecheck`、`npm run build` 和全量测试通过。
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tokenmaw",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"agents",
|
|
8
|
+
"skills",
|
|
9
|
+
"README.md",
|
|
10
|
+
"docs/architecture-revision.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"clean": "node -e \"const fs=require('fs'),path=require('path'),p=path.resolve('dist');if(path.basename(p)!=='dist')throw new Error('unsafe clean target');fs.rmSync(p,{recursive:true,force:true})\"",
|
|
14
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
15
|
+
"start": "node dist/cli.js",
|
|
16
|
+
"dev": "tsx src/cli.ts",
|
|
17
|
+
"test": "node --import tsx/esm --test tests/*.test.ts tests/**/*.test.ts",
|
|
18
|
+
"test:watch": "node --import tsx/esm --test --watch tests/*.test.ts tests/**/*.test.ts",
|
|
19
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
20
|
+
"eval:bench": "tsx tests/benchmarks/run_eval.ts"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@types/blessed": "^0.1.27",
|
|
24
|
+
"ansi-escapes": "^7.3.0",
|
|
25
|
+
"blessed": "^0.1.81",
|
|
26
|
+
"chalk": "^5.6.2",
|
|
27
|
+
"cli-spinners": "^3.4.0",
|
|
28
|
+
"commander": "^12.1.0",
|
|
29
|
+
"marked": "^15.0.12",
|
|
30
|
+
"marked-terminal": "^7.3.0",
|
|
31
|
+
"ora": "^9.4.0",
|
|
32
|
+
"strip-ansi": "^7.2.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/marked-terminal": "^6.1.1",
|
|
36
|
+
"@types/node": "^22.10.5",
|
|
37
|
+
"tsx": "^4.19.2",
|
|
38
|
+
"typescript": "^5.6.3"
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"maw": "dist/cli.js",
|
|
42
|
+
"tokenmaw": "dist/cli.js",
|
|
43
|
+
"coder": "dist/cli.js",
|
|
44
|
+
"coding-agent": "dist/cli.js"
|
|
45
|
+
},
|
|
46
|
+
"description": "TUI-first, document-driven multi-agent coding runtime"
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Debugging Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when diagnosing and fixing bugs.
|
|
4
|
+
|
|
5
|
+
## Approach
|
|
6
|
+
1. **Reproduce** — Confirm the bug with a minimal, repeatable test case
|
|
7
|
+
2. **Read the error** — Parse stack traces, error messages, and exit codes carefully
|
|
8
|
+
3. **Isolate** — Narrow scope: which file, function, or line?
|
|
9
|
+
4. **Read before fix** — Always read the relevant source code before making changes
|
|
10
|
+
5. **Fix the root cause** — Don't patch symptoms; fix the underlying issue
|
|
11
|
+
6. **Verify** — Run the reproduction case to confirm the fix
|
|
12
|
+
7. **Check side effects** — Run the full test suite to catch regressions
|
|
13
|
+
|
|
14
|
+
## Common patterns
|
|
15
|
+
- TypeError: null/undefined → check for missing null guards or optional chaining
|
|
16
|
+
- ImportError / Module not found → check file paths, case sensitivity, extensions
|
|
17
|
+
- Test failures → read the diff, not just "FAIL"
|
|
18
|
+
- Build errors → check TypeScript strict mode, missing dependencies
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Git Workflow Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when working with git repositories.
|
|
4
|
+
|
|
5
|
+
## Guidelines
|
|
6
|
+
- Check `git status` before making changes
|
|
7
|
+
- Read `git log --oneline -10` for recent history and commit style
|
|
8
|
+
- Create feature branches from main: `git checkout -b feature/description`
|
|
9
|
+
- Write conventional commit messages: `type(scope): description`
|
|
10
|
+
- Types: feat, fix, refactor, docs, test, chore, perf
|
|
11
|
+
- Stage specific files, not `git add .`
|
|
12
|
+
- Never force push to main/master
|
|
13
|
+
- Run tests before committing
|
|
14
|
+
- Check for uncommitted changes before switching branches
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Node.js Express API Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when building or modifying Node.js Express web applications.
|
|
4
|
+
|
|
5
|
+
## Project structure
|
|
6
|
+
```
|
|
7
|
+
project/
|
|
8
|
+
src/
|
|
9
|
+
index.ts # Express app entry point
|
|
10
|
+
routes/ # Route handlers
|
|
11
|
+
middleware/ # Custom middleware
|
|
12
|
+
models/ # Data models / schemas
|
|
13
|
+
services/ # Business logic
|
|
14
|
+
utils/ # Utility functions
|
|
15
|
+
tests/ # Test files
|
|
16
|
+
package.json
|
|
17
|
+
tsconfig.json
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Conventions
|
|
21
|
+
- Use TypeScript with ES module syntax
|
|
22
|
+
- Organize routes with Express Router
|
|
23
|
+
- Use Zod for request/response validation
|
|
24
|
+
- Use async/await with proper error handling
|
|
25
|
+
- Centralized error middleware
|
|
26
|
+
- Environment config via dotenv or env vars
|
|
27
|
+
- Jest or Vitest for testing
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python Flask API Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when building or modifying Python Flask web applications.
|
|
4
|
+
|
|
5
|
+
## Project structure
|
|
6
|
+
```
|
|
7
|
+
project/
|
|
8
|
+
app.py # Flask app entry point
|
|
9
|
+
requirements.txt # Dependencies
|
|
10
|
+
config.py # Configuration
|
|
11
|
+
models/ # SQLAlchemy models
|
|
12
|
+
routes/ # Route blueprints
|
|
13
|
+
services/ # Business logic
|
|
14
|
+
tests/ # pytest tests
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Conventions
|
|
18
|
+
- Use Flask blueprints for route organization
|
|
19
|
+
- Use SQLAlchemy for database
|
|
20
|
+
- Use pytest with pytest-flask for testing
|
|
21
|
+
- Type hints on all functions
|
|
22
|
+
- Use pydantic or dataclasses for request/response schemas
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# React Component Skill
|
|
2
|
+
|
|
3
|
+
Use this skill when building or modifying React components.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
- Use TypeScript (`.tsx`)
|
|
7
|
+
- Use functional components with hooks
|
|
8
|
+
- Props defined as TypeScript interfaces
|
|
9
|
+
- Each component in its own file
|
|
10
|
+
- CSS modules or Tailwind for styling
|
|
11
|
+
- React Testing Library + Vitest for tests
|
|
12
|
+
|
|
13
|
+
## Component structure
|
|
14
|
+
```typescript
|
|
15
|
+
// MyComponent.tsx
|
|
16
|
+
interface MyComponentProps {
|
|
17
|
+
title: string;
|
|
18
|
+
onAction: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function MyComponent({ title, onAction }: MyComponentProps) {
|
|
22
|
+
return <div>{title}</div>;
|
|
23
|
+
}
|
|
24
|
+
```
|