dsh-plugin-agentflow 0.3.1 → 0.4.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 +97 -22
- package/cordis.patch.yml +0 -187
- package/index.js +43 -0
- package/lib/client.js +3 -3
- package/lib/commands.js +1058 -0
- package/lib/flow.js +611 -0
- package/lib/observe.js +286 -0
- package/package.json +5 -3
- package/runtime/boards.mjs +8 -1
- package/runtime/commands.mjs +195 -0
- package/runtime/orchestrator.mjs +57 -0
- package/runtime/server.mjs +67 -96
- package/runtime/web/dist/assets/index-BRBK2BoT.css +1 -0
- package/runtime/web/dist/assets/{index-DhM_avMx.js → index-BxE5pyuf.js} +5 -5
- package/runtime/web/dist/index.html +2 -2
- package/scripts/observe-report.mjs +131 -0
- package/runtime/preset-gen.mjs +0 -1117
- package/runtime/web/dist/assets/index-DEZcpxgr.css +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>AgentFlow · 物料画布</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BxE5pyuf.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BRBK2BoT.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把观察器的 jsonl 读成人能看的时间线。
|
|
3
|
+
*
|
|
4
|
+
* 用途:阶段 1 的「看清模型实际怎么调工具」——跑完几次真实对话后,
|
|
5
|
+
* 用它看每一步进了什么、调了哪个工具、有没有被拒、在哪里收工。
|
|
6
|
+
*
|
|
7
|
+
* 用法:
|
|
8
|
+
* node scripts/observe-report.mjs # 默认 ~/.dsh/agent-flow-data/observe
|
|
9
|
+
* node scripts/observe-report.mjs <dir> # 指定目录
|
|
10
|
+
* node scripts/observe-report.mjs <dir> --tool spawn_teammate # 只看某个工具
|
|
11
|
+
*
|
|
12
|
+
* 只读,不改任何文件。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs/promises';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
|
|
19
|
+
const DEFAULT_ROOT = path.join(os.homedir(), '.dsh', 'agent-flow-data', 'observe');
|
|
20
|
+
|
|
21
|
+
/** 参数:第一个非 -- 开头的是目录,--tool 过滤工具名。 */
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
let root = DEFAULT_ROOT;
|
|
24
|
+
let only = null;
|
|
25
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
26
|
+
const arg = argv[i];
|
|
27
|
+
if (arg === '--tool') {
|
|
28
|
+
only = argv[i + 1] ?? null;
|
|
29
|
+
i += 1;
|
|
30
|
+
} else if (!arg.startsWith('--')) {
|
|
31
|
+
root = path.resolve(arg);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { root, only };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readEvents(text) {
|
|
38
|
+
const events = [];
|
|
39
|
+
for (const line of text.split('\n')) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
if (!trimmed) continue;
|
|
42
|
+
try {
|
|
43
|
+
events.push(JSON.parse(trimmed));
|
|
44
|
+
} catch {
|
|
45
|
+
/* 半行(进程被杀时)直接跳过 */
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return events;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ms(value) {
|
|
52
|
+
return typeof value === 'number' ? `${value}ms` : '?';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 一条 tool 事件压成一行。 */
|
|
56
|
+
function toolLine(event) {
|
|
57
|
+
const mark = event.isError ? '✗' : '✓';
|
|
58
|
+
return ` ${mark} ${event.name} ${event.args ?? ''} ${ms(event.ms)}${event.isError && event.text ? ` ← ${event.text}` : ''}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function report(file, events, only) {
|
|
62
|
+
const tools = events.filter((event) => event.t === 'tool');
|
|
63
|
+
const steps = events.filter((event) => event.t === 'step');
|
|
64
|
+
const stops = events.filter((event) => event.t === 'stop');
|
|
65
|
+
const denied = tools.filter((event) => event.isError).length;
|
|
66
|
+
console.log(
|
|
67
|
+
`\n${path.basename(file, '.jsonl')} · turn ${stops.length} · step ${steps.length} · 工具 ${tools.length} · 失败 ${denied}`,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// 按 turn → step 归组;工具调用挂在它当时所在的 step 上
|
|
71
|
+
const byTurn = new Map();
|
|
72
|
+
for (const event of events) {
|
|
73
|
+
const turn = event.turn ?? 0;
|
|
74
|
+
if (!byTurn.has(turn)) byTurn.set(turn, { steps: new Map(), stops: [] });
|
|
75
|
+
const bucket = byTurn.get(turn);
|
|
76
|
+
if (event.t === 'step') bucket.steps.set(event.step, { step: event, tools: [] });
|
|
77
|
+
if (event.t === 'tool') {
|
|
78
|
+
const spot = bucket.steps.get(event.step);
|
|
79
|
+
if (spot) spot.tools.push(event);
|
|
80
|
+
else bucket.steps.set(event.step ?? 0, { step: null, tools: [event] });
|
|
81
|
+
}
|
|
82
|
+
if (event.t === 'stop') bucket.stops.push(event);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const turn of [...byTurn.keys()].sort((a, b) => a - b)) {
|
|
86
|
+
const bucket = byTurn.get(turn);
|
|
87
|
+
console.log(` turn ${turn}`);
|
|
88
|
+
for (const stepNo of [...bucket.steps.keys()].sort((a, b) => a - b)) {
|
|
89
|
+
const { step, tools: stepTools } = bucket.steps.get(stepNo);
|
|
90
|
+
const incoming = step ? step.msgs.map((m) => `${m.kind}(${m.chars})`).join(' ') : '';
|
|
91
|
+
const verdict = step ? (step.decision.kind === 'reject' ? 'reject' : step.decision.kind) : '?';
|
|
92
|
+
console.log(` step ${stepNo} ${incoming || '(无输入)'} → ${verdict}`);
|
|
93
|
+
for (const event of stepTools) {
|
|
94
|
+
if (only && event.name !== only) continue;
|
|
95
|
+
console.log(toolLine(event));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (bucket.stops.length) console.log(' ── 收工');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 频次:一眼看出谁在刷工具
|
|
102
|
+
const tally = new Map();
|
|
103
|
+
for (const event of tools) tally.set(event.name, (tally.get(event.name) ?? 0) + 1);
|
|
104
|
+
if (tally.size) {
|
|
105
|
+
const top = [...tally.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => `${name}×${count}`);
|
|
106
|
+
console.log(` 工具频次:${top.join(' ')}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function main() {
|
|
111
|
+
const { root, only } = parseArgs(process.argv.slice(2));
|
|
112
|
+
const files = await fs.readdir(root).catch((err) => {
|
|
113
|
+
if (err && err.code === 'ENOENT') {
|
|
114
|
+
console.log(`没有观察数据:${root}\n(先跑几次对话,或者确认插件配置里 observe 没被关掉)`);
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
throw err;
|
|
118
|
+
});
|
|
119
|
+
const jsonl = files.filter((name) => name.endsWith('.jsonl')).sort();
|
|
120
|
+
if (jsonl.length === 0) return;
|
|
121
|
+
|
|
122
|
+
for (const name of jsonl) {
|
|
123
|
+
const text = await fs.readFile(path.join(root, name), 'utf8');
|
|
124
|
+
report(path.join(root, name), readEvents(text), only);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
main().catch((err) => {
|
|
129
|
+
console.error('读观察数据失败:', err);
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
});
|