archgraph-argo 0.24.3 → 0.26.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.
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Argo agent-cost collector plugin for opencode.
|
|
2
|
+
//
|
|
3
|
+
// The framework records ONE consolidated agent-cost log per workspace at
|
|
4
|
+
// <workspace>/.argo/temp/agent-cost-log.ndjson, so the user fetches a single
|
|
5
|
+
// file instead of stitching logs from several places. This plugin is the ONLY
|
|
6
|
+
// collector: it sees EVERY tool call the agent makes — MCP interface calls
|
|
7
|
+
// (e.g. getSystemArchitecture), graph writes (e.g. applySystemArchitectureMutation)
|
|
8
|
+
// and repository calls (read / grep / glob) alike — plus assistant token/cost
|
|
9
|
+
// usage, which an MCP-side hook could never observe completely.
|
|
10
|
+
//
|
|
11
|
+
// Recording only observes; it never changes retrieval. Disable with
|
|
12
|
+
// ARGO_COST_PROFILER=0.
|
|
13
|
+
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
let log = null;
|
|
18
|
+
try {
|
|
19
|
+
log = require('../scripts/graph-rag/agentCostLog.js');
|
|
20
|
+
} catch {
|
|
21
|
+
// The shared log module ships next to the plugins under ~/.argo; if it is
|
|
22
|
+
// missing this plugin is a no-op so it can never break a session.
|
|
23
|
+
log = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function argoCostCollector(input) {
|
|
27
|
+
if (!log) return {};
|
|
28
|
+
const workspaceRoot = (input && (input.directory || input.worktree)) || process.cwd();
|
|
29
|
+
const hooks = log.createHostCollectorHooks(workspaceRoot);
|
|
30
|
+
return {
|
|
31
|
+
"tool.execute.before": async (i) => { try { hooks.before(i); } catch { /* best-effort */ } },
|
|
32
|
+
"tool.execute.after": async (i, o) => { try { hooks.after(i, o); } catch { /* best-effort */ } },
|
|
33
|
+
event: async (payload) => { try { hooks.event(payload); } catch { /* best-effort */ } },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Agent search diagnosis (framework, zero-dependency).
|
|
4
|
+
*
|
|
5
|
+
* Turns ONE agent session into a self-contained diagnostic bundle so the user
|
|
6
|
+
* can hand it back for analysis:
|
|
7
|
+
*
|
|
8
|
+
* <workspace>/.argo/temp/diagnosis/<session-id>/
|
|
9
|
+
* ├─ diagnosis.md human-readable summary (incl. heuristic hints)
|
|
10
|
+
* ├─ metrics.json the numbers behind the summary
|
|
11
|
+
* ├─ session.ndjson the raw host session (full tool input/output)
|
|
12
|
+
* ├─ cost-log.slice.ndjson the matching slice of the background cost log
|
|
13
|
+
* └─ manifest.json file list + sizes + versions
|
|
14
|
+
*
|
|
15
|
+
* Data sources (user's choice: both):
|
|
16
|
+
* - PRIMARY: the host session NDJSON (opencode export / run --format json),
|
|
17
|
+
* which carries every tool's full input/output + timestamps + tokens.
|
|
18
|
+
* - SUPPLEMENT: the framework cost log <workspace>/.argo/temp/agent-cost-log.ndjson.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node agentSearchDiagnose.js --session <session.ndjson> [--session-id ID]
|
|
22
|
+
* [--cost-log <path>] [--workspace <root>] [--out <dir>] [--json]
|
|
23
|
+
*
|
|
24
|
+
* Read-only except for writing the bundle dir. Never mutates the graph or repo.
|
|
25
|
+
*/
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
const crypto = require('node:crypto');
|
|
29
|
+
|
|
30
|
+
const BUNDLE_VERSION = 'temp2.2';
|
|
31
|
+
const SCHEMA_VERSION = 1;
|
|
32
|
+
const GRAPH_TOOLS = ['getSystemArchitecture', 'getIntentElementContext', 'getArchitectureViewContext', 'queryNeo4jGraph', 'memory_search'];
|
|
33
|
+
const GRAPH_WRITE_TOOLS = ['previewSystemArchitectureMutation', 'applySystemArchitectureMutation', 'addArchitectureElement', 'updateArchitectureElement', 'removeArchitectureElement', 'addArchitectureRelationship', 'updateArchitectureRelationship', 'removeArchitectureRelationship', 'addArchitectureView', 'updateArchitectureView', 'removeArchitectureView'];
|
|
34
|
+
const VALIDATOR_TOOLS = ['validateSystemArchitecture', 'runArchitectureTests', 'initializeWorkspace'];
|
|
35
|
+
const REPO_TOOLS = ['read', 'grep', 'glob', 'list', 'bash', 'webfetch', 'edit', 'write'];
|
|
36
|
+
|
|
37
|
+
function backendOf(tool) {
|
|
38
|
+
const n = String(tool || '');
|
|
39
|
+
const hit = (l) => l.some(t => n.includes(t));
|
|
40
|
+
if (hit(GRAPH_WRITE_TOOLS)) return 'graph';
|
|
41
|
+
if (hit(GRAPH_TOOLS)) return 'graph';
|
|
42
|
+
if (hit(VALIDATOR_TOOLS)) return 'framework';
|
|
43
|
+
if (hit(REPO_TOOLS)) return 'repo';
|
|
44
|
+
return 'other';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function classifyQuery(tool, input) {
|
|
48
|
+
const n = String(tool || '');
|
|
49
|
+
if (n.includes('getSystemArchitecture') || n.includes('memory_search')) return 'semantic';
|
|
50
|
+
if (n.includes('queryNeo4jGraph')) return 'structured-cypher';
|
|
51
|
+
if (n.includes('getIntentElementContext') || n.includes('getArchitectureViewContext')) return 'structured-context';
|
|
52
|
+
if (n.includes('read')) return 'file-read';
|
|
53
|
+
if (n.includes('grep')) return 'grep';
|
|
54
|
+
if (n.includes('glob') || n.includes('list')) return 'list';
|
|
55
|
+
if (GRAPH_WRITE_TOOLS.some(t => n.includes(t))) return 'write';
|
|
56
|
+
return 'other';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function estimateTokens(text) {
|
|
60
|
+
if (!text) return 0;
|
|
61
|
+
const s = String(text);
|
|
62
|
+
const cjk = (s.match(/[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g) || []).length;
|
|
63
|
+
return cjk + Math.ceil((s.length - cjk) / 4);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function stableStringify(value) {
|
|
67
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
68
|
+
if (Array.isArray(value)) return '[' + value.map(stableStringify).join(',') + ']';
|
|
69
|
+
return '{' + Object.keys(value).sort().map(k => JSON.stringify(k) + ':' + stableStringify(value[k])).join(',') + '}';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function signature(tool, input) {
|
|
73
|
+
try { return crypto.createHash('sha1').update(String(tool) + '|' + stableStringify(input || {})).digest('hex').slice(0, 12); }
|
|
74
|
+
catch (_) { return 'unknown'; }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function inputPreview(tool, input) {
|
|
78
|
+
if (!input || typeof input !== 'object') return '';
|
|
79
|
+
const pick = input.query && (input.query.intent || input.query) || input.pattern || input.elementId || input.elementName || input.view_id || input.path || input.filePath || input.cypher || input.intent;
|
|
80
|
+
const s = typeof pick === 'string' ? pick : (pick ? JSON.stringify(pick) : JSON.stringify(input));
|
|
81
|
+
return String(s).slice(0, 160);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function eventTime(e) {
|
|
85
|
+
const cands = [e.time, e.part && e.part.time, e.part && e.part.state && e.part.state.time];
|
|
86
|
+
for (const t of cands) {
|
|
87
|
+
if (!t) continue;
|
|
88
|
+
if (typeof t === 'number') return t;
|
|
89
|
+
if (typeof t.start === 'number' && typeof t.end === 'number') return t.end;
|
|
90
|
+
if (typeof t.start === 'number') return t.start;
|
|
91
|
+
if (typeof t.end === 'number') return t.end;
|
|
92
|
+
}
|
|
93
|
+
return typeof e.timestamp === 'number' ? e.timestamp : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseSession(text) {
|
|
97
|
+
const raw = String(text || '');
|
|
98
|
+
const toolCalls = [];
|
|
99
|
+
const usages = [];
|
|
100
|
+
let steps = 0; let tMin = null; let tMax = null; let cost = 0;
|
|
101
|
+
const texts = [];
|
|
102
|
+
for (const line of raw.split('\n')) {
|
|
103
|
+
const t = line.trim();
|
|
104
|
+
if (!t.startsWith('{')) continue;
|
|
105
|
+
let e; try { e = JSON.parse(t); } catch (_) { continue; }
|
|
106
|
+
const part = e.part || e;
|
|
107
|
+
if (part && part.type === 'text' && part.text) texts.push(part.text);
|
|
108
|
+
if (part && part.type === 'tool') {
|
|
109
|
+
const tool = part.tool || part.name || (part.state && part.state.tool) || 'tool';
|
|
110
|
+
const st = part.state || {};
|
|
111
|
+
const tm = st.time || {};
|
|
112
|
+
const out = st.output != null ? String(st.output) : '';
|
|
113
|
+
toolCalls.push({
|
|
114
|
+
tool, backend: backendOf(tool), queryClass: classifyQuery(tool, st.input),
|
|
115
|
+
durationMs: (typeof tm.start === 'number' && typeof tm.end === 'number') ? Math.max(0, tm.end - tm.start) : null,
|
|
116
|
+
ok: st.status ? st.status !== 'error' : true,
|
|
117
|
+
error: st.error ? String(st.error).slice(0, 200) : null,
|
|
118
|
+
signature: signature(tool, st.input), inputPreview: inputPreview(tool, st.input),
|
|
119
|
+
outputBytes: out.length, outputTokens: estimateTokens(out),
|
|
120
|
+
path: (st.input && (st.input.filePath || st.input.path || st.input.file)) || null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (e.type === 'step_start') steps += 1;
|
|
124
|
+
if (e.type === 'step_finish') {
|
|
125
|
+
const fin = (e.part && e.part.finish) ? e.part.finish : (e.part || e);
|
|
126
|
+
if (fin && typeof fin.cost === 'number') cost += fin.cost;
|
|
127
|
+
const tk = fin && (fin.tokens || (fin.finish && fin.finish.tokens));
|
|
128
|
+
if (tk) usages.push({ input: tk.input || 0, output: tk.output || 0, reasoning: tk.reasoning || 0, cacheRead: (tk.cache && tk.cache.read) || 0, cacheWrite: (tk.cache && tk.cache.write) || 0 });
|
|
129
|
+
}
|
|
130
|
+
const time = eventTime(e);
|
|
131
|
+
if (time !== null) { tMin = tMin === null ? time : Math.min(tMin, time); tMax = tMax === null ? time : Math.max(tMax, time); }
|
|
132
|
+
}
|
|
133
|
+
const tokensIn = usages.reduce((a, u) => a + u.input, 0);
|
|
134
|
+
const tokensOut = usages.reduce((a, u) => a + u.output, 0);
|
|
135
|
+
const tokensReasoning = usages.reduce((a, u) => a + u.reasoning, 0);
|
|
136
|
+
return { toolCalls, usages, texts, steps, cost, wallMs: (tMin !== null && tMax !== null && tMax >= tMin) ? tMax - tMin : null, tokensIn, tokensOut, tokensReasoning, tokens: tokensIn + tokensOut + tokensReasoning };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function countRoundTrips(toolCalls) {
|
|
140
|
+
const seq = toolCalls.map(c => c.backend).filter(b => b === 'graph' || b === 'repo');
|
|
141
|
+
let n = 0;
|
|
142
|
+
for (let i = 1; i < seq.length; i++) if (seq[i] !== seq[i - 1]) n += 1;
|
|
143
|
+
return n;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function diagnose(session, opts = {}) {
|
|
147
|
+
const tc = session.toolCalls;
|
|
148
|
+
const byTool = {};
|
|
149
|
+
const byBackend = { graph: { calls: 0, ms: 0 }, repo: { calls: 0, ms: 0 }, framework: { calls: 0, ms: 0 }, other: { calls: 0, ms: 0 } };
|
|
150
|
+
const byQueryClass = {};
|
|
151
|
+
const sigCount = {};
|
|
152
|
+
const pathReads = {};
|
|
153
|
+
let toolMs = 0; let errors = 0; let empty = 0;
|
|
154
|
+
for (const c of tc) {
|
|
155
|
+
const b = byBackend[c.backend] || (byBackend[c.backend] = { calls: 0, ms: 0 });
|
|
156
|
+
b.calls += 1; b.ms += c.durationMs || 0;
|
|
157
|
+
toolMs += c.durationMs || 0;
|
|
158
|
+
const bt = byTool[c.tool] || (byTool[c.tool] = { calls: 0, ms: 0, tokens: 0, errors: 0 });
|
|
159
|
+
bt.calls += 1; bt.ms += c.durationMs || 0; bt.tokens += c.outputTokens || 0;
|
|
160
|
+
if (c.ok === false) { bt.errors += 1; errors += 1; }
|
|
161
|
+
if ((c.outputBytes || 0) < 2) empty += 1;
|
|
162
|
+
byQueryClass[c.queryClass] = (byQueryClass[c.queryClass] || 0) + 1;
|
|
163
|
+
sigCount[c.signature] = (sigCount[c.signature] || 0) + 1;
|
|
164
|
+
if (c.queryClass === 'file-read' && c.path) pathReads[c.path] = (pathReads[c.path] || 0) + 1;
|
|
165
|
+
}
|
|
166
|
+
const duplicates = [];
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
for (const c of tc) {
|
|
169
|
+
if (seen.has(c.signature)) continue;
|
|
170
|
+
seen.add(c.signature);
|
|
171
|
+
if (sigCount[c.signature] > 1) duplicates.push({ tool: c.tool, queryClass: c.queryClass, count: sigCount[c.signature], preview: c.inputPreview });
|
|
172
|
+
}
|
|
173
|
+
const repeatedReads = Object.entries(pathReads).filter(([, n]) => n > 1).map(([p, n]) => ({ path: p, count: n }));
|
|
174
|
+
// longest no-progress streak: consecutive calls that were empty/failed/duplicate
|
|
175
|
+
let streak = 0; let best = 0;
|
|
176
|
+
for (const c of tc) {
|
|
177
|
+
const bad = c.ok === false || (c.outputBytes || 0) < 2 || sigCount[c.signature] > 1;
|
|
178
|
+
streak = bad ? streak + 1 : 0;
|
|
179
|
+
if (streak > best) best = streak;
|
|
180
|
+
}
|
|
181
|
+
const cumulativeInput = [];
|
|
182
|
+
let acc = 0;
|
|
183
|
+
for (const u of session.usages) { acc += u.input; cumulativeInput.push(u.input); }
|
|
184
|
+
const topByMs = [...tc].sort((a, b) => (b.durationMs || 0) - (a.durationMs || 0)).slice(0, 5).map(c => ({ tool: c.tool, ms: c.durationMs, ok: c.ok, preview: c.inputPreview }));
|
|
185
|
+
const topByTokens = [...tc].sort((a, b) => (b.outputTokens || 0) - (a.outputTokens || 0)).slice(0, 5).map(c => ({ tool: c.tool, tokens: c.outputTokens, preview: c.inputPreview }));
|
|
186
|
+
|
|
187
|
+
const wallMs = opts.wallMs != null ? opts.wallMs : session.wallMs;
|
|
188
|
+
const modelMs = wallMs != null ? Math.max(0, wallMs - toolMs) : null;
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
schemaVersion: SCHEMA_VERSION, bundleVersion: BUNDLE_VERSION, generatedAt: new Date().toISOString(),
|
|
192
|
+
workspace: opts.workspace || null, sessionId: opts.sessionId || null,
|
|
193
|
+
overview: {
|
|
194
|
+
steps: session.steps, toolCalls: tc.length, roundTrips: countRoundTrips(tc),
|
|
195
|
+
wallMs, modelMs, mcpMs: byBackend.graph.ms, repoToolMs: byBackend.repo.ms, toolMs,
|
|
196
|
+
tokensIn: session.tokensIn, tokensOut: session.tokensOut, tokensReasoning: session.tokensReasoning, tokens: session.tokens,
|
|
197
|
+
cost: session.cost, toolErrors: errors, emptyResults: empty,
|
|
198
|
+
distinctSignatures: new Set(tc.map(c => c.signature)).size,
|
|
199
|
+
},
|
|
200
|
+
byTool, byBackend, byQueryClass,
|
|
201
|
+
tokenGrowth: { perStepInput: cumulativeInput, steps: cumulativeInput.length },
|
|
202
|
+
overSearch: {
|
|
203
|
+
duplicateCalls: duplicates,
|
|
204
|
+
repeatedReads,
|
|
205
|
+
emptyOrError: errors + empty,
|
|
206
|
+
noProgressStreak: best,
|
|
207
|
+
},
|
|
208
|
+
topOffendersByTime: topByMs,
|
|
209
|
+
topOffendersByTokens: topByTokens,
|
|
210
|
+
hints: buildHints({ toolCalls: tc.length, duplicates: duplicates.length, emptyOrError: errors + empty, repeatedReads: repeatedReads.length, noProgressStreak: best, modelMs, mcpMs: byBackend.graph.ms, repoToolMs: byBackend.repo.ms, roundTrips: countRoundTrips(tc) }),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildHints(m) {
|
|
215
|
+
const hints = [];
|
|
216
|
+
if (m.duplicates > 0) hints.push(`重复/近似重复调用 ${m.duplicates} 组 → 考虑结果缓存或查询归一(同参不重搜)。`);
|
|
217
|
+
if (m.emptyOrError > 0) hints.push(`空结果/失败 ${m.emptyOrError} 次 → 考虑改进查询构造/回退策略,避免"空手→换词→再搜"的循环。`);
|
|
218
|
+
if (m.repeatedReads > 0) hints.push(`同一文件被重复读 ${m.repeatedReads} 处 → 考虑读取缓存或先摘要后精读。`);
|
|
219
|
+
if (m.noProgressStreak >= 3) hints.push(`最长"无进展"连续 ${m.noProgressStreak} 次 → 缺停止判据/预算,建议显式设停止条件。`);
|
|
220
|
+
if (m.roundTrips >= 2) hints.push(`图↔仓往返 ${m.roundTrips} 次 → 检查是否可在一次规划内并发取数。`);
|
|
221
|
+
if (m.modelMs != null && m.modelMs > (m.mcpMs + m.repoToolMs)) hints.push(`模型耗时主导(${m.modelMs}ms > 工具 ${(m.mcpMs + m.repoToolMs)}ms)→ 考虑上下文压缩/减少轮次。`);
|
|
222
|
+
if (hints.length === 0) hints.push('未发现明显过度搜索信号。');
|
|
223
|
+
return hints;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderDiagnosis(m, meta) {
|
|
227
|
+
const L = [];
|
|
228
|
+
L.push(`# Agent 搜索诊断报告`);
|
|
229
|
+
L.push('');
|
|
230
|
+
L.push(`- 生成时间:${m.generatedAt}`);
|
|
231
|
+
L.push(`- 会话:${m.sessionId || '(unknown)'} 工作区:${m.workspace || '(unknown)'}`);
|
|
232
|
+
L.push(`- bundle 版本:${m.bundleVersion} schema:${m.schemaVersion}`);
|
|
233
|
+
L.push('');
|
|
234
|
+
L.push(`## 概览`);
|
|
235
|
+
L.push('');
|
|
236
|
+
L.push(`| 指标 | 值 |`);
|
|
237
|
+
L.push(`|---|---|`);
|
|
238
|
+
L.push(`| 轮次 steps | ${m.overview.steps} |`);
|
|
239
|
+
L.push(`| 工具调用 | ${m.overview.toolCalls}(去重签名 ${m.overview.distinctSignatures}) |`);
|
|
240
|
+
L.push(`| 图↔仓往返 | ${m.overview.roundTrips} |`);
|
|
241
|
+
L.push(`| 墙钟 | ${fmtMs(m.overview.wallMs)} = 模型 ${fmtMs(m.overview.modelMs)} + MCP ${fmtMs(m.overview.mcpMs)} + 仓 ${fmtMs(m.overview.repoToolMs)} |`);
|
|
242
|
+
L.push(`| tokens | 总 ${m.overview.tokens}(in ${m.overview.tokensIn} / out ${m.overview.tokensOut} / reason ${m.overview.tokensReasoning}) |`);
|
|
243
|
+
L.push(`| 工具失败 / 空结果 | ${m.overview.toolErrors} / ${m.overview.emptyResults} |`);
|
|
244
|
+
L.push('');
|
|
245
|
+
L.push(`## 过度搜索信号`);
|
|
246
|
+
L.push('');
|
|
247
|
+
L.push(`- 重复调用组:${m.overSearch.duplicateCalls.length}`);
|
|
248
|
+
for (const d of m.overSearch.duplicateCalls.slice(0, 10)) L.push(` - ×${d.count} ${d.tool} [${d.queryClass}] ${d.preview}`);
|
|
249
|
+
L.push(`- 重复读同一文件:${m.overSearch.repeatedReads.length}`);
|
|
250
|
+
for (const r of m.overSearch.repeatedReads.slice(0, 10)) L.push(` - ×${r.count} ${r.path}`);
|
|
251
|
+
L.push(`- 空结果/失败合计:${m.overSearch.emptyOrError}`);
|
|
252
|
+
L.push(`- 最长无进展连续:${m.overSearch.noProgressStreak}`);
|
|
253
|
+
L.push('');
|
|
254
|
+
L.push(`## 后端 / 查询类分布`);
|
|
255
|
+
L.push('');
|
|
256
|
+
L.push(`- 后端:` + Object.entries(m.byBackend).map(([k, v]) => `${k}=${v.calls}`).join(' '));
|
|
257
|
+
L.push(`- 查询类:` + Object.entries(m.byQueryClass).map(([k, v]) => `${k}=${v}`).join(' '));
|
|
258
|
+
L.push('');
|
|
259
|
+
L.push(`## 最贵调用(按耗时)`);
|
|
260
|
+
L.push('');
|
|
261
|
+
for (const c of m.topOffendersByTime) L.push(`- ${fmtMs(c.ms)} ${c.tool}${c.ok ? '' : ' (error)'} ${c.preview}`);
|
|
262
|
+
L.push('');
|
|
263
|
+
L.push(`## token 随轮次`);
|
|
264
|
+
L.push('');
|
|
265
|
+
L.push(`每步 input tokens(累计上下文规模):${m.tokenGrowth.perStepInput.join(', ') || '(无)'}`);
|
|
266
|
+
L.push('');
|
|
267
|
+
L.push(`## 诊断建议(启发式,供 Agent 复核)`);
|
|
268
|
+
L.push('');
|
|
269
|
+
for (const h of m.hints) L.push(`- ${h}`);
|
|
270
|
+
L.push('');
|
|
271
|
+
L.push(`---`);
|
|
272
|
+
L.push(`> 本报告由 agentSearchDiagnose 自动生成;请 Agent 结合 metrics.json 与会话片段复核并补充解读。`);
|
|
273
|
+
L.push('');
|
|
274
|
+
return L.join('\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function fmtMs(ms) {
|
|
278
|
+
if (ms == null) return 'n/a';
|
|
279
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function writeBundle(opts) {
|
|
283
|
+
const workspace = opts.workspace || process.cwd();
|
|
284
|
+
const sessionText = fs.readFileSync(opts.session, 'utf8');
|
|
285
|
+
const session = parseSession(sessionText);
|
|
286
|
+
const sessionId = opts.sessionId || inferSessionId(sessionText) || 'session';
|
|
287
|
+
const outDir = opts.out || path.join(workspace, '.argo', 'temp', 'diagnosis', sessionId);
|
|
288
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
289
|
+
|
|
290
|
+
const costLogPath = opts.costLog || path.join(workspace, '.argo', 'temp', 'agent-cost-log.ndjson');
|
|
291
|
+
const costLogText = readMaybe(costLogPath);
|
|
292
|
+
|
|
293
|
+
const metrics = diagnose(session, { workspace, sessionId, wallMs: opts.wallMs });
|
|
294
|
+
const files = [];
|
|
295
|
+
const write = (name, body) => { const p = path.join(outDir, name); fs.writeFileSync(p, body); files.push({ path: name, bytes: Buffer.byteLength(body) }); };
|
|
296
|
+
|
|
297
|
+
write('session.ndjson', sessionText);
|
|
298
|
+
write('cost-log.slice.ndjson', sliceCostLog(costLogText, sessionId));
|
|
299
|
+
write('metrics.json', JSON.stringify(metrics, null, 2) + '\n');
|
|
300
|
+
write('diagnosis.md', renderDiagnosis(metrics, { costLogPath }));
|
|
301
|
+
|
|
302
|
+
const manifest = {
|
|
303
|
+
schemaVersion: SCHEMA_VERSION, bundleVersion: BUNDLE_VERSION, generatedAt: metrics.generatedAt,
|
|
304
|
+
workspace, sessionId, sources: { session: path.resolve(opts.session), costLog: costLogPath },
|
|
305
|
+
files,
|
|
306
|
+
};
|
|
307
|
+
fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
308
|
+
return { outDir, metrics, manifest };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function readMaybe(file) { try { return fs.readFileSync(file, 'utf8'); } catch (_) { return ''; } }
|
|
312
|
+
|
|
313
|
+
function inferSessionId(text) {
|
|
314
|
+
const m = String(text).match(/"sessionID"\s*:\s*"([^"]+)"/);
|
|
315
|
+
return m ? m[1] : null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function sliceCostLog(text, sessionId) {
|
|
319
|
+
if (!text) return '';
|
|
320
|
+
const lines = text.split('\n').filter(Boolean);
|
|
321
|
+
const hit = lines.filter(l => !sessionId || l.includes(sessionId));
|
|
322
|
+
return (hit.length ? hit : []).join('\n') + (hit.length ? '\n' : '');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function parseArgs(argv) {
|
|
326
|
+
const a = { session: null, sessionId: null, costLog: null, workspace: process.cwd(), out: null, json: false };
|
|
327
|
+
for (let i = 0; i < argv.length; i++) {
|
|
328
|
+
const k = argv[i];
|
|
329
|
+
if (k === '--session') a.session = argv[++i];
|
|
330
|
+
else if (k === '--session-id') a.sessionId = argv[++i];
|
|
331
|
+
else if (k === '--cost-log') a.costLog = argv[++i];
|
|
332
|
+
else if (k === '--workspace') a.workspace = argv[++i];
|
|
333
|
+
else if (k === '--out') a.out = argv[++i];
|
|
334
|
+
else if (k === '--json') a.json = true;
|
|
335
|
+
}
|
|
336
|
+
return a;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function main(argv) {
|
|
340
|
+
const args = parseArgs(argv || process.argv.slice(2));
|
|
341
|
+
if (!args.session || !fs.existsSync(args.session)) {
|
|
342
|
+
console.error('agentSearchDiagnose: --session <session.ndjson> is required');
|
|
343
|
+
return 2;
|
|
344
|
+
}
|
|
345
|
+
const { outDir, metrics, manifest } = writeBundle(args);
|
|
346
|
+
if (args.json) { console.log(JSON.stringify({ outDir, overview: metrics.overview, hints: metrics.hints }, null, 2)); return 0; }
|
|
347
|
+
console.log(`diagnosis bundle -> ${outDir}`);
|
|
348
|
+
console.log(`files: ${manifest.files.map(f => f.path).join(', ')}`);
|
|
349
|
+
console.log(`overview: steps=${metrics.overview.steps} tools=${metrics.overview.toolCalls} roundTrips=${metrics.overview.roundTrips} tokens=${metrics.overview.tokens} model=${fmtMs(metrics.overview.modelMs)} mcp=${fmtMs(metrics.overview.mcpMs)} repo=${fmtMs(metrics.overview.repoToolMs)}`);
|
|
350
|
+
for (const h of metrics.hints) console.log(`hint: ${h}`);
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = { BUNDLE_VERSION, SCHEMA_VERSION, parseSession, classifyQuery, backendOf, signature, diagnose, renderDiagnosis, writeBundle, sliceCostLog, main };
|
|
355
|
+
|
|
356
|
+
if (require.main === module) process.exit(main());
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Agent cost LOG (framework, zero-config, background) — the single consolidated
|
|
4
|
+
// store for everything needed to reason about an agent's retrieval cost.
|
|
5
|
+
//
|
|
6
|
+
// WHY ONE FILE: in a real project the agent's scope is the whole repository (an
|
|
7
|
+
// unbounded content source) plus the curated intent graph (KG, only a semantic
|
|
8
|
+
// index / routing layer). Cost is dominated by the agent's round-trips BETWEEN
|
|
9
|
+
// the two backends (graph: getSystemArchitecture / getIntentElementContext /
|
|
10
|
+
// getArchitectureViewContext / queryNeo4jGraph / memory_search; repository:
|
|
11
|
+
// read / grep / glob), not by the size of either backend. To optimise without
|
|
12
|
+
// hurting recall we must measure that — in ONE place the user fetches once:
|
|
13
|
+
//
|
|
14
|
+
// <workspace>/.argo/temp/agent-cost-log.ndjson
|
|
15
|
+
//
|
|
16
|
+
// WHO WRITES: the OpenCode plugin argo/plugins/argo-cost-collector.js. Every
|
|
17
|
+
// tool call the agent makes — MCP interface calls, graph writes, and repository
|
|
18
|
+
// calls alike — is a host tool, so the plugin records ALL of them, plus the
|
|
19
|
+
// assistant's token/cost usage. There is deliberately NO MCP-side instrumentation
|
|
20
|
+
// (a record without the host side would be incomplete and thus misleading).
|
|
21
|
+
//
|
|
22
|
+
// Posture (mirrors mcpCrashDiagnostics): best-effort, never throws, never logs
|
|
23
|
+
// secret values, and NEVER changes retrieval (it observes results only).
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
|
|
28
|
+
const LOG_FILE_NAME = 'agent-cost-log.ndjson';
|
|
29
|
+
const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
30
|
+
|
|
31
|
+
const GRAPH_TOOLS = ['getSystemArchitecture', 'getIntentElementContext', 'getArchitectureViewContext', 'queryNeo4jGraph', 'memory_search'];
|
|
32
|
+
const GRAPH_WRITE_TOOLS = [
|
|
33
|
+
'previewSystemArchitectureMutation', 'applySystemArchitectureMutation',
|
|
34
|
+
'addArchitectureElement', 'updateArchitectureElement', 'removeArchitectureElement',
|
|
35
|
+
'addArchitectureRelationship', 'updateArchitectureRelationship', 'removeArchitectureRelationship',
|
|
36
|
+
'addArchitectureView', 'updateArchitectureView', 'removeArchitectureView',
|
|
37
|
+
];
|
|
38
|
+
const VALIDATOR_TOOLS = ['validateSystemArchitecture', 'runArchitectureTests', 'initializeWorkspace'];
|
|
39
|
+
const REPO_TOOLS = ['read', 'grep', 'glob', 'list', 'bash', 'webfetch', 'edit', 'write'];
|
|
40
|
+
|
|
41
|
+
function enabled() {
|
|
42
|
+
return process.env.ARGO_COST_PROFILER !== '0';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function maxBytes() {
|
|
46
|
+
const n = Number(process.env.ARGO_COST_TRACE_MAX_BYTES);
|
|
47
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_BYTES;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function tempDir(workspaceRoot) {
|
|
51
|
+
return path.join(workspaceRoot, '.argo', 'temp');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function logFilePath(workspaceRoot) {
|
|
55
|
+
return path.join(tempDir(workspaceRoot), LOG_FILE_NAME);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Pure, total classification. Host tool names may be prefixed (e.g.
|
|
59
|
+
// mcp__argo__getSystemArchitecture), so match by substring.
|
|
60
|
+
function classifyTool(tool) {
|
|
61
|
+
const name = String(tool || '');
|
|
62
|
+
const hit = (list) => list.some(t => name.includes(t));
|
|
63
|
+
if (hit(GRAPH_WRITE_TOOLS)) return { backend: 'graph', kind: 'write' };
|
|
64
|
+
if (hit(GRAPH_TOOLS)) return { backend: 'graph', kind: 'read' };
|
|
65
|
+
if (hit(VALIDATOR_TOOLS)) return { backend: 'framework', kind: 'framework' };
|
|
66
|
+
if (hit(REPO_TOOLS)) return { backend: 'repo', kind: 'read' };
|
|
67
|
+
return { backend: 'other', kind: 'other' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Deterministic coarse token estimate: CJK ≈ 1 token/char, else ≈ 1/4 chars.
|
|
71
|
+
function estimateTokens(text) {
|
|
72
|
+
if (!text) return 0;
|
|
73
|
+
const s = String(text);
|
|
74
|
+
const cjk = (s.match(/[\u4e00-\u9fff\u3400-\u4dbf\u3000-\u303f\uff00-\uffef]/g) || []).length;
|
|
75
|
+
return cjk + Math.ceil((s.length - cjk) / 4);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function rotateIfNeeded(file, limit) {
|
|
79
|
+
try {
|
|
80
|
+
if (fs.statSync(file).size >= limit) fs.renameSync(file, `${file}.1`);
|
|
81
|
+
} catch (_) { /* no existing log */ }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Append one record to the single consolidated log. Best-effort: any failure is
|
|
85
|
+
// swallowed so logging can never affect a tool call.
|
|
86
|
+
function appendRecord(workspaceRoot, record) {
|
|
87
|
+
if (!enabled() || !workspaceRoot || !record || typeof record !== 'object') return;
|
|
88
|
+
try {
|
|
89
|
+
const file = logFilePath(workspaceRoot);
|
|
90
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
91
|
+
rotateIfNeeded(file, maxBytes());
|
|
92
|
+
const line = JSON.stringify({ at: new Date().toISOString(), pid: process.pid, ...record }) + '\n';
|
|
93
|
+
fs.appendFileSync(file, line, 'utf8');
|
|
94
|
+
} catch (_) {
|
|
95
|
+
// best-effort only
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function readLog(workspaceRoot) {
|
|
100
|
+
const file = logFilePath(workspaceRoot);
|
|
101
|
+
let raw = '';
|
|
102
|
+
try { raw = fs.readFileSync(file, 'utf8'); } catch (_) { return { file, records: [] }; }
|
|
103
|
+
const records = [];
|
|
104
|
+
for (const line of raw.split('\n')) {
|
|
105
|
+
const t = line.trim();
|
|
106
|
+
if (!t.startsWith('{')) continue;
|
|
107
|
+
try { records.push(JSON.parse(t)); } catch (_) { /* skip malformed */ }
|
|
108
|
+
}
|
|
109
|
+
return { file, records };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function byteLen(value) {
|
|
113
|
+
try {
|
|
114
|
+
const s = JSON.stringify(value);
|
|
115
|
+
return (typeof Buffer !== 'undefined') ? Buffer.byteLength(s) : s.length;
|
|
116
|
+
} catch (_) { return 0; }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function keysOf(value) {
|
|
120
|
+
return (value && typeof value === 'object' && !Array.isArray(value)) ? Object.keys(value).sort() : [];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Host-collector hook logic (used by the OpenCode plugin; unit-tested in CJS).
|
|
124
|
+
// It records EVERY host tool call — MCP interface calls, graph writes, and
|
|
125
|
+
// repository calls alike — plus assistant token/cost usage into the single log.
|
|
126
|
+
function createHostCollectorHooks(workspaceRoot) {
|
|
127
|
+
const starts = new Map();
|
|
128
|
+
const seenUsage = new Set();
|
|
129
|
+
return {
|
|
130
|
+
before(input) {
|
|
131
|
+
if (!input || !input.callID) return;
|
|
132
|
+
const args = input.args || {};
|
|
133
|
+
starts.set(input.callID, { t: Date.now(), keys: keysOf(args), bytes: byteLen(args) });
|
|
134
|
+
},
|
|
135
|
+
after(input, output) {
|
|
136
|
+
if (!input) return;
|
|
137
|
+
const s = starts.get(input.callID) || {};
|
|
138
|
+
const cls = classifyTool(input.tool);
|
|
139
|
+
const out = (output && output.output) || '';
|
|
140
|
+
appendRecord(workspaceRoot, {
|
|
141
|
+
source: 'host',
|
|
142
|
+
type: 'tool',
|
|
143
|
+
sessionID: input.sessionID,
|
|
144
|
+
callID: input.callID,
|
|
145
|
+
tool: input.tool,
|
|
146
|
+
backend: cls.backend,
|
|
147
|
+
kind: cls.kind,
|
|
148
|
+
durationMs: s.t ? (Date.now() - s.t) : 0,
|
|
149
|
+
ok: true,
|
|
150
|
+
args: { keys: s.keys || keysOf(input.args), bytes: s.bytes != null ? s.bytes : byteLen(input.args) },
|
|
151
|
+
resultBytes: String(out).length,
|
|
152
|
+
resultTokens: estimateTokens(out),
|
|
153
|
+
});
|
|
154
|
+
starts.delete(input.callID);
|
|
155
|
+
},
|
|
156
|
+
event(payload) {
|
|
157
|
+
const event = payload && payload.event;
|
|
158
|
+
const props = (event && event.properties) || {};
|
|
159
|
+
const info = props.info || (event && event.info) || null;
|
|
160
|
+
if (info && info.role === 'assistant' && info.tokens && info.id && !seenUsage.has(info.id)) {
|
|
161
|
+
seenUsage.add(info.id);
|
|
162
|
+
appendRecord(workspaceRoot, {
|
|
163
|
+
source: 'host',
|
|
164
|
+
type: 'usage',
|
|
165
|
+
sessionID: info.sessionID,
|
|
166
|
+
messageID: info.id,
|
|
167
|
+
tokens: info.tokens,
|
|
168
|
+
cost: info.cost,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
LOG_FILE_NAME,
|
|
177
|
+
enabled, tempDir, logFilePath, classifyTool, estimateTokens,
|
|
178
|
+
appendRecord, readLog, createHostCollectorHooks,
|
|
179
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-search-diagnosis
|
|
3
|
+
description: "诊断一次 Agent 会话的检索/搜索行为(是否过度搜索、轮次/token/耗时花在哪、是否重复或空手、图↔仓往返),产出一份自包含的『诊断包』(diagnosis.md 总结 + metrics.json + 会话 NDJSON + 插件日志切片 + manifest.json),供人类伙伴打包回传以便进一步分析优化。Use when the user says an agent session searched too many rounds / wants to diagnose retrieval cost or over-search, or asks to produce a diagnostic bundle to send back. Keywords: 过度搜索, 诊断, over-search, search diagnosis, retrieval cost, diagnostic bundle, agent-search-diagnosis."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent Search Diagnosis
|
|
7
|
+
|
|
8
|
+
把**一次 Agent 会话**变成一份自包含的**诊断包**:现场出诊断总结 + 聚合关键数据到一个目录,用户打包回传即可。
|
|
9
|
+
|
|
10
|
+
## 数据来源
|
|
11
|
+
|
|
12
|
+
- **主**:宿主会话 NDJSON(含每个工具的**完整入参/输出**、时间戳、tokens)。
|
|
13
|
+
- **辅**:框架后台日志 `<workspace>/.argo/temp/agent-cost-log.ndjson`(跨会话成本形态)。
|
|
14
|
+
|
|
15
|
+
## 输出(固定目录)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
<workspace>/.argo/temp/diagnosis/<session-id>/
|
|
19
|
+
├─ diagnosis.md # 人读诊断总结(含启发式建议,供 Agent 复核)
|
|
20
|
+
├─ metrics.json # 总结背后的数字
|
|
21
|
+
├─ session.ndjson # 原始会话(全保真)
|
|
22
|
+
├─ cost-log.slice.ndjson # 匹配该会话的后台日志切片
|
|
23
|
+
└─ manifest.json # 文件清单 + 大小 + 版本
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
### 1. 定位并导出会话
|
|
29
|
+
|
|
30
|
+
- 若用户给出了 sessionID:`opencode export <sessionID> > <tmp>.ndjson`。
|
|
31
|
+
- 若未给出:`opencode session list` 取最近会话,或请用户指定。
|
|
32
|
+
- 宿主不支持导出时:退化为**仅用后台日志**(`agent-cost-log.ndjson`,诊断能力受限,须在报告中注明)。
|
|
33
|
+
|
|
34
|
+
### 2. 运行诊断脚本(确定性)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
node ~/.argo/scripts/agentSearchDiagnose.js --session <session.ndjson> \
|
|
38
|
+
[--session-id <id>] [--workspace .] [--cost-log <path>] [--json]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
脚本**只读**会话与日志,写出上面的 bundle 目录,输出概览与启发式 hints。
|
|
42
|
+
|
|
43
|
+
### 3. 复核并补充诊断总结(Agent)
|
|
44
|
+
|
|
45
|
+
- 读 `diagnosis.md` 与 `metrics.json`。
|
|
46
|
+
- 在报告末尾追加一节 `## Agent 复核解读`:结合会话片段指出**最可能的过度搜索根因**与**建议的优化方向**(去重/缓存、查询构造、停止判据、上下文压缩、图↔仓往返合并等)。
|
|
47
|
+
- 不臆造:结论必须能从 metrics/会话片段找到依据。
|
|
48
|
+
|
|
49
|
+
### 4. 交付给用户
|
|
50
|
+
|
|
51
|
+
- 报告 bundle 目录路径,请用户**打包该目录**回传。
|
|
52
|
+
- 给一段简短结论(轮次/token/耗时拆分 + 主要信号)。
|
|
53
|
+
|
|
54
|
+
## Rules(MUST / MUST NOT)
|
|
55
|
+
|
|
56
|
+
- **MUST** 只用确定性脚本 + 会话/日志读取;**MUST NOT** 修改图谱、仓库或任何框架内容。
|
|
57
|
+
- **MUST NOT** 读取或打印 `.env`/凭据;若会话片段疑似含密钥,**MUST** 提醒用户回传前先审查 `session.ndjson`(其中是原始工具 I/O)。
|
|
58
|
+
- **MUST** 在结论中区分「事实(metrics)」与「推断(根因假设)」,推断须标注依据。
|
|
59
|
+
- 若无法导出会话或脚本不可用,**MUST** 如实说明并给出退化方案,不得臆测诊断结果。
|
package/install-argo.ps1
CHANGED
|
@@ -788,8 +788,11 @@ $skillDest = Join-Path $SkillsRoot 'argo-init'
|
|
|
788
788
|
Write-Host "[4/22] argo\skills\argo-init -> $skillDest"
|
|
789
789
|
Copy-Tree -Source $skillSrc -Destination $skillDest
|
|
790
790
|
$reconcileSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'ea-human-reconcile'
|
|
791
|
+
$diagSkillSrc = Join-Path (Join-Path $argoDir 'skills') 'agent-search-diagnosis'
|
|
791
792
|
Write-Host ' argo\skills\ea-human-reconcile -> $SkillsRoot\ea-human-reconcile (EA human draft reconcile skill)'
|
|
792
793
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $SkillsRoot 'ea-human-reconcile')
|
|
794
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $SkillsRoot\agent-search-diagnosis (agent search diagnosis skill)'
|
|
795
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $SkillsRoot 'agent-search-diagnosis')
|
|
793
796
|
|
|
794
797
|
$ruleSrc = Join-Path (Join-Path $argoDir 'rules') 'archgraph.instructions.md'
|
|
795
798
|
$ruleDest = Join-Path $PromptsRoot 'archgraph.instructions.md'
|
|
@@ -807,6 +810,8 @@ Write-Host "[7/22] argo\skills\argo-init -> $cursorSkillDest (Cursor)"
|
|
|
807
810
|
Copy-Tree -Source $skillSrc -Destination $cursorSkillDest
|
|
808
811
|
Write-Host ' argo\skills\ea-human-reconcile -> $CursorSkillsRoot\ea-human-reconcile (Cursor)'
|
|
809
812
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $CursorSkillsRoot 'ea-human-reconcile')
|
|
813
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $CursorSkillsRoot\agent-search-diagnosis (Cursor)'
|
|
814
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $CursorSkillsRoot 'agent-search-diagnosis')
|
|
810
815
|
|
|
811
816
|
$mcpBridgeSrc = Join-Path $argoDir 'mcp-bridges'
|
|
812
817
|
$mcpBridgeDest = Join-Path $CursorMcpBridgesRoot ''
|
|
@@ -818,6 +823,8 @@ Write-Host "[8/22] argo\skills\argo-init -> $openCodeSkillDest (OpenCode)"
|
|
|
818
823
|
Copy-Tree -Source $skillSrc -Destination $openCodeSkillDest
|
|
819
824
|
Write-Host ' argo\skills\ea-human-reconcile -> $OpenCodeSkillsRoot\ea-human-reconcile (OpenCode)'
|
|
820
825
|
Copy-Tree -Source $reconcileSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'ea-human-reconcile')
|
|
826
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $OpenCodeSkillsRoot\agent-search-diagnosis (OpenCode)'
|
|
827
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path $OpenCodeSkillsRoot 'agent-search-diagnosis')
|
|
821
828
|
|
|
822
829
|
Write-Host "[9/22] argo\rules\archgraph.instructions.md -> $OpenCodeAgentsPath (OpenCode global AGENTS.md)"
|
|
823
830
|
Add-AgentsRule -AgentsPath $OpenCodeAgentsPath -RulePath $ruleSrc
|
|
@@ -857,6 +864,8 @@ if ($SkipDsh) {
|
|
|
857
864
|
Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $dshSkillDest
|
|
858
865
|
Write-Host ' argo\skills\ea-human-reconcile -> $DshHome\skills\ea-human-reconcile (DeepSeek Harness skill)'
|
|
859
866
|
Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $DshHome 'skills') 'ea-human-reconcile')
|
|
867
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $DshHome\skills\agent-search-diagnosis (DeepSeek Harness skill)'
|
|
868
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $DshHome 'skills') 'agent-search-diagnosis')
|
|
860
869
|
|
|
861
870
|
Write-Host "[17/22] argo\rules\<WakeupGuideline> -> $DshHome\plugins\dsh-argo-wakeup\index.js (DeepSeek Harness wakeup plugin)"
|
|
862
871
|
$wakeupDshPath = New-DshWakeupPlugin -DshHome $DshHome -RuleText $ruleSrcContent
|
|
@@ -918,8 +927,10 @@ if ($SkipOpenClaw) {
|
|
|
918
927
|
|
|
919
928
|
Write-Host "[21/22] argo\skills\argo-init -> $openClawSkillDest (OpenClaw managed skill, all agents)"
|
|
920
929
|
Copy-Tree -Source (Join-Path $argoDir 'skills\argo-init') -Destination $openClawSkillDest
|
|
921
|
-
|
|
922
|
-
|
|
930
|
+
Write-Host ' argo\skills\ea-human-reconcile -> $OpenClawHome\skills\ea-human-reconcile (OpenClaw managed skill, all agents)'
|
|
931
|
+
Copy-Tree -Source (Join-Path $argoDir 'skills\ea-human-reconcile') -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'ea-human-reconcile')
|
|
932
|
+
Write-Host ' argo\skills\agent-search-diagnosis -> $OpenClawHome\skills\agent-search-diagnosis (OpenClaw managed skill, all agents)'
|
|
933
|
+
Copy-Tree -Source $diagSkillSrc -Destination (Join-Path (Join-Path $OpenClawHome 'skills') 'agent-search-diagnosis')
|
|
923
934
|
|
|
924
935
|
Write-Host ' OpenClaw injects AGENTS.md into Project Context on every session, so the wakeup'
|
|
925
936
|
Write-Host ' gate (UNCONDITIONAL STARTUP GATE) is active on the next OpenClaw session; restart'
|
|
@@ -1109,5 +1120,14 @@ if (Test-Path $wakeupPluginPath) {
|
|
|
1109
1120
|
Write-Host "argo-wakeup plugin registered -> $OpenCodeConfigPath"
|
|
1110
1121
|
}
|
|
1111
1122
|
|
|
1123
|
+
# Agent-cost collector: the complete host-side writer of the single consolidated
|
|
1124
|
+
# agent-cost log (<workspace>/.argo/temp/agent-cost-log.ndjson).
|
|
1125
|
+
$costCollectorPath = Join-Path $PluginsRoot 'argo-cost-collector.js'
|
|
1126
|
+
if (Test-Path $costCollectorPath) {
|
|
1127
|
+
Write-Host '==> Registering argo-cost-collector plugin in OpenCode'
|
|
1128
|
+
Register-OpenCodePlugin -ConfigPath $OpenCodeConfigPath -PluginFilePath $costCollectorPath
|
|
1129
|
+
Write-Host "argo-cost-collector plugin registered -> $OpenCodeConfigPath"
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1112
1132
|
Write-Host ''
|
|
1113
1133
|
Write-Host 'Argo deployment complete.'
|
package/package.json
CHANGED
|
@@ -1,52 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "archgraph-argo",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"bin": {
|
|
7
|
-
"argo-deploy": "bin/argo-deploy.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"argo/scripts",
|
|
11
|
-
"argo/schema",
|
|
12
|
-
"argo/defaults",
|
|
13
|
-
"argo/agents",
|
|
14
|
-
"argo/plugins",
|
|
15
|
-
"argo/mcp-bridges",
|
|
16
|
-
"argo/skills/argo-init",
|
|
17
|
-
"argo/skills/ea-human-reconcile",
|
|
18
|
-
"argo/
|
|
19
|
-
"argo/
|
|
20
|
-
"argo
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"dsh-argo-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"./dsh-argo-
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "archgraph-argo",
|
|
3
|
+
"version": "0.26.0",
|
|
4
|
+
"description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"argo-deploy": "bin/argo-deploy.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"argo/scripts",
|
|
11
|
+
"argo/schema",
|
|
12
|
+
"argo/defaults",
|
|
13
|
+
"argo/agents",
|
|
14
|
+
"argo/plugins",
|
|
15
|
+
"argo/mcp-bridges",
|
|
16
|
+
"argo/skills/argo-init",
|
|
17
|
+
"argo/skills/ea-human-reconcile",
|
|
18
|
+
"argo/skills/agent-search-diagnosis",
|
|
19
|
+
"argo/rules",
|
|
20
|
+
"argo/package.json",
|
|
21
|
+
"argo/.env.example",
|
|
22
|
+
"vendor",
|
|
23
|
+
"install-argo.ps1",
|
|
24
|
+
"bin",
|
|
25
|
+
"cordis.patch.yml",
|
|
26
|
+
"dsh-argo-workspace",
|
|
27
|
+
"dsh-argo-wakeup"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
"./dsh-argo-workspace": "./dsh-argo-workspace/index.js",
|
|
31
|
+
"./dsh-argo-wakeup": "./dsh-argo-wakeup/index.js",
|
|
32
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"dsh": {
|
|
36
|
+
"bundle": {
|
|
37
|
+
"patch": "./cordis.patch.yml"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"neo4j-driver": "^6.2.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
45
|
+
"playwright": "1.61.1"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"test": "node --test \"tests/*.test.js\""
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
}
|
|
53
|
+
}
|