codehere 0.8.0 → 1.0.2
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 +37 -184
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +329 -0
- package/dist/index.js +74 -1004
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -265
- package/dist/presentation/cli/commands/check-command.js +251 -0
- package/dist/presentation/cli/commands/help-command.js +22 -286
- package/dist/ui.js +19 -438
- package/dist/utils/version.js +0 -12
- package/package.json +18 -27
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/generate-final-report.js +0 -134
- package/dist/application/use-cases/generate-narration.js +0 -128
- package/dist/application/use-cases/generate-thread-title.js +0 -178
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/thread-export.js +0 -155
- package/dist/application/use-cases/verify-claim.js +0 -274
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/entities/task.js +0 -61
- package/dist/domain/services/codebase-detector.js +0 -91
- package/dist/error-handler.js +0 -251
- package/dist/infrastructure/completion/bash-completion.js +0 -25
- package/dist/infrastructure/completion/fish-completion.js +0 -22
- package/dist/infrastructure/completion/zsh-completion.js +0 -30
- package/dist/infrastructure/config/config-manager.js +0 -173
- package/dist/infrastructure/config/product-context.js +0 -67
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -163
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -159
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -119
- package/dist/infrastructure/context/registry-bootstrap.js +0 -99
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -113
- package/dist/infrastructure/cost/cost-router.js +0 -104
- package/dist/infrastructure/cost/spend-aggregator.js +0 -147
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -888
- package/dist/infrastructure/hooks/hook-mode.js +0 -71
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -43
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -56
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -447
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -478
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -538
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -377
- package/dist/infrastructure/orchestration/context-handoff.js +0 -144
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -77
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -583
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -558
- package/dist/infrastructure/orchestration/graphify-context.js +0 -254
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -233
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -510
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -689
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1091
- package/dist/infrastructure/orchestration/session-persistence.js +0 -258
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -46
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -2853
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -4418
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/protocols/verify-page.js +0 -983
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/security/permissions-config.js +0 -234
- package/dist/infrastructure/security/trust-scorer.js +0 -657
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -524
- package/dist/infrastructure/storage/project-thread-reader.js +0 -172
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/infrastructure/storage/task-repository.js +0 -206
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -126
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -198
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -578
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -116
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -387
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -94
- package/dist/presentation/cli/commands/setup-command.js +0 -307
- package/dist/presentation/cli/commands/status-command.js +0 -661
- package/dist/presentation/cli/commands/thread-command.js +0 -288
- package/dist/presentation/cli/commands/trust-command.js +0 -306
- package/dist/presentation/cli/commands/verify-command.js +0 -233
- package/dist/presentation/cli/error-display.js +0 -336
- package/dist/presentation/cli/repl.js +0 -797
- package/dist/presentation/cli/task-deliverable.js +0 -127
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -166
|
@@ -1,689 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ProgressAggregator — PM UX Layer 2 (principles #1 "web is not a terminal", #2 "info density").
|
|
3
|
-
*
|
|
4
|
-
* Ingests the raw TaskEvent stream and produces semantic ProgressStep[] that a
|
|
5
|
-
* product manager would actually read ("Read src/session.ts", "Run npm test",
|
|
6
|
-
* "Writing reply…") rather than the underlying token-level chatter.
|
|
7
|
-
*
|
|
8
|
-
* Agent-aware by design: Claude's stream-json tool_use blocks and Codex's
|
|
9
|
-
* item.completed entries label differently, so we dispatch on the agent id.
|
|
10
|
-
* Keeps the per-adapter files free of PM-layer concerns.
|
|
11
|
-
*/
|
|
12
|
-
const MAX_LABEL = 60;
|
|
13
|
-
function shortPath(p) {
|
|
14
|
-
const parts = String(p).split('/');
|
|
15
|
-
return parts.length <= 2 ? p : '…/' + parts.slice(-2).join('/');
|
|
16
|
-
}
|
|
17
|
-
function truncate(s, max = MAX_LABEL) {
|
|
18
|
-
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
19
|
-
}
|
|
20
|
-
function labelForClaudeToolUse(name, input) {
|
|
21
|
-
const i = (input && typeof input === 'object' ? input : {});
|
|
22
|
-
switch (name) {
|
|
23
|
-
case 'Read':
|
|
24
|
-
return 'Read ' + shortPath(String(i.file_path ?? 'file'));
|
|
25
|
-
case 'Write':
|
|
26
|
-
return 'Write ' + shortPath(String(i.file_path ?? 'file'));
|
|
27
|
-
case 'Edit':
|
|
28
|
-
return 'Edit ' + shortPath(String(i.file_path ?? 'file'));
|
|
29
|
-
case 'MultiEdit': {
|
|
30
|
-
const edits = Array.isArray(i.edits) ? i.edits : [];
|
|
31
|
-
const first = edits[0];
|
|
32
|
-
return `Edit ${shortPath(String(first?.file_path ?? 'file'))} (${edits.length})`;
|
|
33
|
-
}
|
|
34
|
-
case 'NotebookEdit':
|
|
35
|
-
return 'Edit notebook ' + shortPath(String(i.notebook_path ?? 'notebook'));
|
|
36
|
-
case 'Bash': {
|
|
37
|
-
const cmd = String(i.command ?? '')
|
|
38
|
-
.replace(/\n/g, ' ')
|
|
39
|
-
.trim();
|
|
40
|
-
return truncate('Run ' + cmd);
|
|
41
|
-
}
|
|
42
|
-
case 'Grep':
|
|
43
|
-
// M2 plain-English pass: "Grep" is a unix command name, jargon
|
|
44
|
-
// for non-technical users. "Search code for" tells the user what
|
|
45
|
-
// it actually does.
|
|
46
|
-
return 'Search code for ' + truncate(String(i.pattern ?? ''), 40);
|
|
47
|
-
case 'Glob':
|
|
48
|
-
return 'List files matching ' + truncate(String(i.pattern ?? ''), 40);
|
|
49
|
-
case 'WebFetch':
|
|
50
|
-
return 'Fetch ' + truncate(String(i.url ?? ''), 50);
|
|
51
|
-
case 'WebSearch':
|
|
52
|
-
return 'Search the web for ' + truncate(String(i.query ?? ''), 50);
|
|
53
|
-
case 'TodoWrite':
|
|
54
|
-
return 'Plan ' + (Array.isArray(i.todos) ? `${i.todos.length} items` : 'tasks');
|
|
55
|
-
case 'Task':
|
|
56
|
-
// M2 plain-English pass: "subagent" is internal vocab. "Specialist"
|
|
57
|
-
// matches the team-roster framing the user already sees in the
|
|
58
|
-
// dashboard ("codehere · Conductor", "Claude Code specialist", etc.)
|
|
59
|
-
return 'Hand off to a specialist';
|
|
60
|
-
default:
|
|
61
|
-
return truncate(String(name || 'tool'));
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Extract human-readable text from a Claude tool_result content block.
|
|
66
|
-
* Handles the two shapes the SDK emits:
|
|
67
|
-
* { content: 'plain text' } (most)
|
|
68
|
-
* { content: [{type:'text', text:'...'}, {type:'image', ...}, ...] } (multi-block)
|
|
69
|
-
*/
|
|
70
|
-
function extractClaudeToolResultText(b) {
|
|
71
|
-
const c = b.content;
|
|
72
|
-
if (typeof c === 'string')
|
|
73
|
-
return c;
|
|
74
|
-
if (Array.isArray(c)) {
|
|
75
|
-
const parts = [];
|
|
76
|
-
for (const part of c) {
|
|
77
|
-
if (part && typeof part === 'object') {
|
|
78
|
-
const p = part;
|
|
79
|
-
if (p.type === 'text' && typeof p.text === 'string')
|
|
80
|
-
parts.push(p.text);
|
|
81
|
-
}
|
|
82
|
-
else if (typeof part === 'string') {
|
|
83
|
-
parts.push(part);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return parts.join('\n');
|
|
87
|
-
}
|
|
88
|
-
return '';
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Truncate output for step preview. Keeps the LAST ~400 chars (tail is
|
|
92
|
-
* usually more useful than head for command output — errors land
|
|
93
|
-
* there). Adds a leading ellipsis when truncation occurs.
|
|
94
|
-
*/
|
|
95
|
-
function truncateOutput(s) {
|
|
96
|
-
const max = 400;
|
|
97
|
-
const stripped = s.replace(/\[[0-9;]*m/g, ''); // strip ANSI
|
|
98
|
-
if (stripped.length <= max)
|
|
99
|
-
return stripped;
|
|
100
|
-
return '…' + stripped.slice(-(max - 1));
|
|
101
|
-
}
|
|
102
|
-
function unwrapShellWrapper(command) {
|
|
103
|
-
// Codex wraps its shell calls in `/bin/zsh -lc "…"` or `sh -c "…"`. Strip that
|
|
104
|
-
// so the step label reads as the actual command instead of the wrapper.
|
|
105
|
-
const m = command.match(/^(?:\/bin\/(?:zsh|bash|sh)|sh|bash|zsh)\s+-l?c\s+["'](.+)["']\s*$/);
|
|
106
|
-
return m ? m[1].trim() : command.trim();
|
|
107
|
-
}
|
|
108
|
-
function labelForCodexCommand(command) {
|
|
109
|
-
const raw = unwrapShellWrapper(String(command).replace(/\n/g, ' ').trim());
|
|
110
|
-
// Heuristic friendly prefix for common operations
|
|
111
|
-
if (/^cat\s+/.test(raw))
|
|
112
|
-
return truncate('Read ' + raw.replace(/^cat\s+/, '').split(/\s+/)[0]);
|
|
113
|
-
if (/^sed\s+-n\s+/.test(raw)) {
|
|
114
|
-
// e.g. `sed -n '1,260p' README.md` → "Read README.md"
|
|
115
|
-
const tail = raw.split(/\s+/).slice(-1)[0];
|
|
116
|
-
return truncate('Read ' + tail);
|
|
117
|
-
}
|
|
118
|
-
if (/^(?:ls|find)\b/.test(raw))
|
|
119
|
-
return truncate('List ' + raw.replace(/^(?:ls|find)\s*/, ''));
|
|
120
|
-
// M2 plain-English pass: "File op:" is awkward; describe what's
|
|
121
|
-
// happening per command kind.
|
|
122
|
-
if (/^mkdir\b/.test(raw))
|
|
123
|
-
return truncate('Create folder: ' + raw.replace(/^mkdir\s+(?:-\S+\s+)*/, ''));
|
|
124
|
-
if (/^cp\b/.test(raw))
|
|
125
|
-
return truncate('Copy: ' + raw.replace(/^cp\s+(?:-\S+\s+)*/, ''));
|
|
126
|
-
if (/^mv\b/.test(raw))
|
|
127
|
-
return truncate('Move/rename: ' + raw.replace(/^mv\s+(?:-\S+\s+)*/, ''));
|
|
128
|
-
if (/^rm\b/.test(raw))
|
|
129
|
-
return truncate('Remove: ' + raw.replace(/^rm\s+(?:-\S+\s+)*/, ''));
|
|
130
|
-
if (/^touch\b/.test(raw))
|
|
131
|
-
return truncate('Create file: ' + raw.replace(/^touch\s+/, ''));
|
|
132
|
-
if (/\becho\s+.+>/.test(raw))
|
|
133
|
-
return truncate('Write to file: ' + raw);
|
|
134
|
-
if (/^git\s+/.test(raw))
|
|
135
|
-
return truncate(raw);
|
|
136
|
-
if (/^npm\s+test\b/.test(raw) || /^pytest\b/.test(raw) || /^vitest\b/.test(raw))
|
|
137
|
-
return truncate('Run tests');
|
|
138
|
-
return truncate('Run ' + raw);
|
|
139
|
-
}
|
|
140
|
-
export class ProgressAggregator {
|
|
141
|
-
agentId;
|
|
142
|
-
_steps = [];
|
|
143
|
-
toolUseIndex = new Map(); // tool_use_id → step idx (Claude)
|
|
144
|
-
constructor(agentId) {
|
|
145
|
-
this.agentId = agentId;
|
|
146
|
-
}
|
|
147
|
-
get steps() {
|
|
148
|
-
return this._steps;
|
|
149
|
-
}
|
|
150
|
-
/** @returns true if the step list changed (caller emits synthetic event). */
|
|
151
|
-
ingest(event) {
|
|
152
|
-
const type = String(event.type || '');
|
|
153
|
-
const data = (event.data ?? {});
|
|
154
|
-
if (this.agentId === 'claude-code')
|
|
155
|
-
return this.ingestClaude(type, data);
|
|
156
|
-
if (this.agentId === 'codex')
|
|
157
|
-
return this.ingestCodex(type, data);
|
|
158
|
-
if (this.agentId === 'gemini')
|
|
159
|
-
return this.ingestGemini(type, data);
|
|
160
|
-
if (this.agentId === 'aider')
|
|
161
|
-
return this.ingestAider(type, data);
|
|
162
|
-
if (this.agentId === 'opencode')
|
|
163
|
-
return this.ingestOpencode(type, data);
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
// OpenCode · `run --format json` emits tool_use with shape
|
|
167
|
-
// { type:'tool_use', part:{ tool, state:{ input, output, status } } }. Was
|
|
168
|
-
// never wired into the aggregator (opencode showed ZERO timeline steps — a
|
|
169
|
-
// real gap surfaced by the mock harness). Build a step on the terminal
|
|
170
|
-
// (status==='completed') event so opencode is a first-class agent.
|
|
171
|
-
ingestOpencode(type, data) {
|
|
172
|
-
// OpenCode's narrative text rides `text` events with part.text (the same
|
|
173
|
-
// shape the adapter's summary extraction reads) — cross-vendor say capture
|
|
174
|
-
// per the v1 spec, so opencode's Final Report stops degrading to the
|
|
175
|
-
// deterministic fallback.
|
|
176
|
-
if (type === 'text') {
|
|
177
|
-
const part = (data.part ?? {});
|
|
178
|
-
return this.pushSay(String(part.text ?? ''));
|
|
179
|
-
}
|
|
180
|
-
if (type !== 'tool_use')
|
|
181
|
-
return false;
|
|
182
|
-
const part = (data.part ?? {});
|
|
183
|
-
if (part.state?.status && part.state.status !== 'completed')
|
|
184
|
-
return false; // skip in-flight
|
|
185
|
-
const map = {
|
|
186
|
-
bash: 'Bash',
|
|
187
|
-
read: 'Read',
|
|
188
|
-
write: 'Write',
|
|
189
|
-
edit: 'Edit',
|
|
190
|
-
grep: 'Grep',
|
|
191
|
-
glob: 'Glob',
|
|
192
|
-
webfetch: 'WebFetch',
|
|
193
|
-
websearch: 'WebSearch',
|
|
194
|
-
};
|
|
195
|
-
const tool = map[String(part.tool ?? '').toLowerCase()] || 'Bash';
|
|
196
|
-
const label = labelForClaudeToolUse(tool, part.state?.input);
|
|
197
|
-
const output = typeof part.state?.output === 'string' ? part.state.output : '';
|
|
198
|
-
this._steps.push({
|
|
199
|
-
label,
|
|
200
|
-
state: 'done',
|
|
201
|
-
startedAt: Date.now(),
|
|
202
|
-
completedAt: Date.now(),
|
|
203
|
-
...(output ? { output: truncateOutput(output) } : {}),
|
|
204
|
-
});
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
/** Flush: mark every still-active step as failed (called on abnormal exit). */
|
|
208
|
-
finalize(status) {
|
|
209
|
-
// Surface any trailing aider reasoning that had no following action.
|
|
210
|
-
let changed = this.flushAiderSay();
|
|
211
|
-
for (const s of this._steps) {
|
|
212
|
-
if (s.state === 'active' || s.state === 'pending') {
|
|
213
|
-
s.state = status === 'completed' ? 'done' : 'failed';
|
|
214
|
-
s.completedAt = Date.now();
|
|
215
|
-
changed = true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return changed;
|
|
219
|
-
}
|
|
220
|
-
addStep(label, state = 'active') {
|
|
221
|
-
const idx = this._steps.push({ label, state, startedAt: Date.now() }) - 1;
|
|
222
|
-
return idx;
|
|
223
|
-
}
|
|
224
|
-
/** The agent's own narration as a kind:'say' step — the "watch it work"
|
|
225
|
-
* signal AND what the Final Report engine reuses as the summary. Cross-
|
|
226
|
-
* vendor by decision of the v1 spec (the room thread is "tool steps woven
|
|
227
|
-
* with the agent's plain-English narration" — for every agent, not just
|
|
228
|
-
* claude/aider). Same 500-char cap as the original claude capture. */
|
|
229
|
-
pushSay(text) {
|
|
230
|
-
const said = String(text ?? '').trim();
|
|
231
|
-
if (!said)
|
|
232
|
-
return false;
|
|
233
|
-
this._steps.push({
|
|
234
|
-
kind: 'say',
|
|
235
|
-
label: said.length > 500 ? said.slice(0, 500) + '\\u2026' : said,
|
|
236
|
-
state: 'done',
|
|
237
|
-
startedAt: Date.now(),
|
|
238
|
-
completedAt: Date.now(),
|
|
239
|
-
});
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
ingestClaude(type, data) {
|
|
243
|
-
if (type === 'assistant') {
|
|
244
|
-
const msg = (data.message ?? {});
|
|
245
|
-
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
246
|
-
let changed = false;
|
|
247
|
-
for (const blk of content) {
|
|
248
|
-
const b = (blk && typeof blk === 'object' ? blk : {});
|
|
249
|
-
if (b.type === 'text' && typeof b.text === 'string' && b.text.trim()) {
|
|
250
|
-
// The agent's own words/reasoning. In claude's stream the text block
|
|
251
|
-
// precedes the tool_use in the same message — capture it first so the
|
|
252
|
-
// timeline reads "explains → acts". This is the "watch it work" signal
|
|
253
|
-
// the agent already produces; we were dropping it.
|
|
254
|
-
const said = b.text.trim();
|
|
255
|
-
this._steps.push({
|
|
256
|
-
kind: 'say',
|
|
257
|
-
label: said.length > 500 ? said.slice(0, 500) + '\\u2026' : said,
|
|
258
|
-
state: 'done',
|
|
259
|
-
startedAt: Date.now(),
|
|
260
|
-
completedAt: Date.now(),
|
|
261
|
-
});
|
|
262
|
-
changed = true;
|
|
263
|
-
}
|
|
264
|
-
else if (b.type === 'tool_use') {
|
|
265
|
-
const id = String(b.id ?? '');
|
|
266
|
-
const label = labelForClaudeToolUse(String(b.name ?? ''), b.input);
|
|
267
|
-
const idx = this.addStep(label, 'active');
|
|
268
|
-
if (id)
|
|
269
|
-
this.toolUseIndex.set(id, idx);
|
|
270
|
-
changed = true;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return changed;
|
|
274
|
-
}
|
|
275
|
-
if (type === 'user') {
|
|
276
|
-
// Match tool_result blocks back to their tool_use step.
|
|
277
|
-
const msg = (data.message ?? {});
|
|
278
|
-
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
279
|
-
let changed = false;
|
|
280
|
-
for (const blk of content) {
|
|
281
|
-
const b = (blk && typeof blk === 'object' ? blk : {});
|
|
282
|
-
if (b.type === 'tool_result') {
|
|
283
|
-
const id = String(b.tool_use_id ?? '');
|
|
284
|
-
const idx = id ? this.toolUseIndex.get(id) : undefined;
|
|
285
|
-
if (idx !== undefined) {
|
|
286
|
-
const step = this._steps[idx];
|
|
287
|
-
step.state = b.is_error ? 'failed' : 'done';
|
|
288
|
-
step.completedAt = Date.now();
|
|
289
|
-
// P22 / Track B #8 — capture tool-result text for the
|
|
290
|
-
// dashboard's bash-output preview. Claude SDK shape:
|
|
291
|
-
// content: string (most cases)
|
|
292
|
-
// content: Array<{type:'text', text:string}> (multi-block)
|
|
293
|
-
// Truncate to last ~400 chars so the SSE payload + render
|
|
294
|
-
// cost stay bounded.
|
|
295
|
-
const out = extractClaudeToolResultText(b);
|
|
296
|
-
if (out)
|
|
297
|
-
step.output = truncateOutput(out);
|
|
298
|
-
changed = true;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return changed;
|
|
303
|
-
}
|
|
304
|
-
return false;
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Gemini's stream-json schema (verified against @google/gemini-cli source):
|
|
308
|
-
* tool_use: { tool_name, tool_id, parameters } — adds an active step
|
|
309
|
-
* tool_result: { tool_id, ...output } — closes by tool_id
|
|
310
|
-
*
|
|
311
|
-
* We reuse the Claude-shaped tool labelling as a best-effort default — Gemini's
|
|
312
|
-
* tool surface (Read, Edit, Write, Shell, Grep, Glob, etc.) overlaps heavily
|
|
313
|
-
* with the Claude SDK tool naming since both emerged from the same agent
|
|
314
|
-
* lineage. If a specific Gemini tool name doesn't match, the default branch in
|
|
315
|
-
* `labelForClaudeToolUse` falls back to the raw tool name.
|
|
316
|
-
*/
|
|
317
|
-
ingestGemini(type, data) {
|
|
318
|
-
if (type === 'tool_use') {
|
|
319
|
-
const id = String(data.tool_id ?? '');
|
|
320
|
-
const name = String(data.tool_name ?? 'tool');
|
|
321
|
-
const label = labelForClaudeToolUse(name, data.parameters);
|
|
322
|
-
const idx = this.addStep(label, 'active');
|
|
323
|
-
if (id)
|
|
324
|
-
this.toolUseIndex.set(id, idx);
|
|
325
|
-
return true;
|
|
326
|
-
}
|
|
327
|
-
if (type === 'tool_result') {
|
|
328
|
-
const id = String(data.tool_id ?? '');
|
|
329
|
-
const idx = id ? this.toolUseIndex.get(id) : undefined;
|
|
330
|
-
if (idx !== undefined) {
|
|
331
|
-
const step = this._steps[idx];
|
|
332
|
-
// Gemini's tool_result may carry an `error` field on failure; treat any
|
|
333
|
-
// non-undefined `error` as failed. Otherwise mark done.
|
|
334
|
-
const isError = data.error !== undefined && data.error !== null;
|
|
335
|
-
step.state = isError ? 'failed' : 'done';
|
|
336
|
-
step.completedAt = Date.now();
|
|
337
|
-
// P22 slice 3 — Gemini tool_result output. Schema is sparse;
|
|
338
|
-
// accept the common output/result/content fields, with error
|
|
339
|
-
// text as fallback when isError is true.
|
|
340
|
-
const out = (typeof data.output === 'string' && data.output) ||
|
|
341
|
-
(typeof data.result === 'string' && data.result) ||
|
|
342
|
-
(typeof data.content === 'string' && data.content) ||
|
|
343
|
-
(isError && data.error && typeof data.error === 'object'
|
|
344
|
-
? `${data.error.type ?? ''} ${data.error.message ?? ''}`
|
|
345
|
-
: '');
|
|
346
|
-
if (out)
|
|
347
|
-
step.output = truncateOutput(String(out));
|
|
348
|
-
return true;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// Gemini's narrative text rides `content` events — same cross-vendor say
|
|
352
|
-
// capture as claude/aider/codex (v1 spec: narration woven into the thread,
|
|
353
|
-
// and the Final Report engine reuses it as the summary).
|
|
354
|
-
if (type === 'content' && typeof data.text === 'string') {
|
|
355
|
-
return this.pushSay(data.text);
|
|
356
|
-
}
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Aider observability parity (v0.5 Path B per docs/design/f3_memory_spec.md
|
|
361
|
-
* + ~/.claude/.../memory/learning_aider.md).
|
|
362
|
-
*
|
|
363
|
-
* Aider doesn't expose a structured event stream — we parse stdout lines
|
|
364
|
-
* matching the EXACT patterns aider's `base_coder.py` writes when it
|
|
365
|
-
* applies an edit, creates a new file, or runs a shell command (verified
|
|
366
|
-
* against installed aider 0.86.2 source — line refs below).
|
|
367
|
-
*
|
|
368
|
-
* Patterns we match:
|
|
369
|
-
* "Running <command>" — base_coder.py:2472 — fires AFTER
|
|
370
|
-
* aider's confirm_ask returns yes (which `--yes-always` answers
|
|
371
|
-
* automatically). Aider has already started executing by the time we see
|
|
372
|
-
* this — OBSERVABILITY, not blocking.
|
|
373
|
-
* "Applied edit to <path>" — base_coder.py:2334 — fires after
|
|
374
|
-
* a SEARCH/REPLACE block lands. Used for both edits AND net-new files
|
|
375
|
-
* created via the same flow (aider doesn't distinguish in this message).
|
|
376
|
-
* "Creating empty file <path>" — base_coder.py:461 — fires when
|
|
377
|
-
* aider creates a brand-new empty file as scaffolding before edits.
|
|
378
|
-
*
|
|
379
|
-
* Patterns we DELIBERATELY do NOT match:
|
|
380
|
-
* "Did not apply edit to ..." — explicit non-event; we drop these
|
|
381
|
-
* (would be a false-positive Edit step otherwise).
|
|
382
|
-
* "Create new file?" — that's a CONFIRM_ASK prompt, not a
|
|
383
|
-
* tool_output. With `--yes-always` it never fires interactively, and
|
|
384
|
-
* it's not the action-taken signal anyway.
|
|
385
|
-
*
|
|
386
|
-
* Each matched pattern → one ProgressStep marked done immediately (aider
|
|
387
|
-
* doesn't emit separate completion events; the line itself IS the completion).
|
|
388
|
-
*
|
|
389
|
-
* Label vocabulary is aligned with Claude / Gemini / Codex labels for
|
|
390
|
-
* cross-agent UI consistency: `Run X` for shell, `Edit <path>` for file
|
|
391
|
-
* edits, `Write <path>` for net-new file creation.
|
|
392
|
-
*
|
|
393
|
-
* ANSI handling: HEADLESS_FLAGS in aider-adapter.ts include `--no-pretty`
|
|
394
|
-
* which strips colors. As defense-in-depth (in case extraArgs override that
|
|
395
|
-
* or aider regresses), we strip ANSI escape sequences before matching.
|
|
396
|
-
*
|
|
397
|
-
* Limitation: codehere CANNOT block aider's actions via this path. The
|
|
398
|
-
* upstream-PR Path A (adding `--pre-action-hook` to aider) is what enables
|
|
399
|
-
* blocking parity. v0.5 ships observability; v0.5.x or v0.6 ships blocking
|
|
400
|
-
* once Path A merges.
|
|
401
|
-
*/
|
|
402
|
-
static ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
403
|
-
/**
|
|
404
|
-
* Per-instance set of file paths aider just created via "Creating empty file ...".
|
|
405
|
-
* Aider emits BOTH `Creating empty file <p>` AND `Applied edit to <p>` for one
|
|
406
|
-
* logical net-new-file creation (file gets touched, then initial content
|
|
407
|
-
* gets applied). Without this set we'd record two steps (`Write <p>` then
|
|
408
|
-
* `Edit <p>`) for one action — codex Round 2 caught this. We suppress the
|
|
409
|
-
* subsequent `Applied edit to` for the same path so the user sees `Write`
|
|
410
|
-
* once, not `Write` followed by `Edit`.
|
|
411
|
-
*/
|
|
412
|
-
aiderJustCreated = new Set();
|
|
413
|
-
/**
|
|
414
|
-
* P22 slice 4 (Aider) — output buffering. Aider's command output
|
|
415
|
-
* streams as raw stdout lines AFTER a "Running X" announcement and
|
|
416
|
-
* BEFORE the next aider-pattern (or end of session). This index is
|
|
417
|
-
* the most-recent "Run" step that's still collecting its tail. When
|
|
418
|
-
* a new aider-pattern fires we flush the buffer onto step.output and
|
|
419
|
-
* reset.
|
|
420
|
-
*
|
|
421
|
-
* Only "Running" steps collect output — file-edit/write actions
|
|
422
|
-
* don't have a captured stdout; their target IS the visible state.
|
|
423
|
-
*/
|
|
424
|
-
aiderRunningStepIdx = null;
|
|
425
|
-
aiderOutputBuffer = [];
|
|
426
|
-
/**
|
|
427
|
-
* A1 (PRODUCT_STATE §0 NEXT #1) — Aider's natural-language reasoning.
|
|
428
|
-
* Aider streams its plain-English explanation to stdout BEFORE the
|
|
429
|
-
* SEARCH/REPLACE blocks and "Applied edit" lines. We were dropping every
|
|
430
|
-
* such line. Mirror ingestClaude's text-block handling: collect consecutive
|
|
431
|
-
* prose lines (skipping diff fences + SEARCH/REPLACE markers + aider's own
|
|
432
|
-
* status noise) and flush them as ONE kind:'say' step before the next
|
|
433
|
-
* action — the "watch it work" signal across aider, not just claude.
|
|
434
|
-
*/
|
|
435
|
-
aiderSayBuffer = [];
|
|
436
|
-
aiderInFence = false;
|
|
437
|
-
aiderInDiff = false;
|
|
438
|
-
/** Aider stdout lines that are status/noise, never reasoning prose. The
|
|
439
|
-
* "must contain a space + length >= 4" guard in collectAiderSay already
|
|
440
|
-
* drops bare filenames, fence langs and spinner rules; this catches the
|
|
441
|
-
* noisy lines that DO contain spaces. */
|
|
442
|
-
static AIDER_NONPROSE_RE = /^(=======\s*$|Tokens:|Cost:|Commit [0-9a-f]|Applied edit to |Running |Creating empty file |Did not |Added \S+ to the chat|Removed \S+ from the chat|No changes made|Warning: |Scanning repo|Initial repo scan|Repo-map:|Main model:|Weak model:|Model:|Git repo:|Use \/|aider |Committing )/;
|
|
443
|
-
aiderFlushOutput() {
|
|
444
|
-
if (this.aiderRunningStepIdx === null)
|
|
445
|
-
return;
|
|
446
|
-
const step = this._steps[this.aiderRunningStepIdx];
|
|
447
|
-
if (!step) {
|
|
448
|
-
this.aiderRunningStepIdx = null;
|
|
449
|
-
this.aiderOutputBuffer = [];
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
if (this.aiderOutputBuffer.length > 0) {
|
|
453
|
-
step.output = truncateOutput(this.aiderOutputBuffer.join('\n'));
|
|
454
|
-
}
|
|
455
|
-
this.aiderRunningStepIdx = null;
|
|
456
|
-
this.aiderOutputBuffer = [];
|
|
457
|
-
}
|
|
458
|
-
/** Flush buffered aider prose as one kind:'say' step. Returns true if a step
|
|
459
|
-
* was pushed (so finalize can fold it into its changed flag). */
|
|
460
|
-
flushAiderSay() {
|
|
461
|
-
if (this.aiderSayBuffer.length === 0)
|
|
462
|
-
return false;
|
|
463
|
-
const said = this.aiderSayBuffer.join(' ').replace(/\s+/g, ' ').trim();
|
|
464
|
-
this.aiderSayBuffer = [];
|
|
465
|
-
if (!said)
|
|
466
|
-
return false;
|
|
467
|
-
this._steps.push({
|
|
468
|
-
kind: 'say',
|
|
469
|
-
label: said.length > 500 ? said.slice(0, 500) + '…' : said,
|
|
470
|
-
state: 'done',
|
|
471
|
-
startedAt: Date.now(),
|
|
472
|
-
completedAt: Date.now(),
|
|
473
|
-
});
|
|
474
|
-
return true;
|
|
475
|
-
}
|
|
476
|
-
/** Collect one aider stdout line as reasoning prose unless it's a fence, a
|
|
477
|
-
* SEARCH/REPLACE marker/body, status noise, or a bare token. */
|
|
478
|
-
collectAiderSay(line) {
|
|
479
|
-
if (line.startsWith('```')) {
|
|
480
|
-
this.aiderInFence = !this.aiderInFence;
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (this.aiderInFence)
|
|
484
|
-
return;
|
|
485
|
-
if (/^<<<<<<< SEARCH/.test(line)) {
|
|
486
|
-
this.aiderInDiff = true;
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
if (/^>>>>>>> REPLACE/.test(line)) {
|
|
490
|
-
this.aiderInDiff = false;
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
if (this.aiderInDiff)
|
|
494
|
-
return;
|
|
495
|
-
if (ProgressAggregator.AIDER_NONPROSE_RE.test(line))
|
|
496
|
-
return;
|
|
497
|
-
// Require a space + minimum length so filenames/code tokens don't leak in.
|
|
498
|
-
if (line.length < 4 || !line.includes(' '))
|
|
499
|
-
return;
|
|
500
|
-
this.aiderSayBuffer.push(line);
|
|
501
|
-
}
|
|
502
|
-
ingestAider(type, data) {
|
|
503
|
-
if (type !== 'stdout')
|
|
504
|
-
return false;
|
|
505
|
-
const raw = typeof data.line === 'string' ? data.line : '';
|
|
506
|
-
// Strip ANSI escape codes BEFORE matching so the parser survives a
|
|
507
|
-
// `--no-pretty` regression or override.
|
|
508
|
-
const line = raw.replace(ProgressAggregator.ANSI_RE, '').trim();
|
|
509
|
-
if (!line)
|
|
510
|
-
return false;
|
|
511
|
-
// EXPLICIT NON-EVENT: defense-in-depth guard. The `^Applied edit to` regex
|
|
512
|
-
// below wouldn't match "Did not apply edit to ..." because of the anchor,
|
|
513
|
-
// but if either pattern ever loosens, this short-circuit prevents a false
|
|
514
|
-
// Edit step from creeping in.
|
|
515
|
-
if (/^Did not apply edit to\s+/.test(line))
|
|
516
|
-
return false;
|
|
517
|
-
// "Running <command>" — base_coder.py:2472
|
|
518
|
-
let m = line.match(/^Running\s+(.+)$/);
|
|
519
|
-
if (m) {
|
|
520
|
-
// P22 slice 4 — flush the previous Running step's buffer before
|
|
521
|
-
// starting the new one. New step is the active output target.
|
|
522
|
-
this.aiderFlushOutput();
|
|
523
|
-
this.flushAiderSay(); // emit any reasoning prose that preceded this run
|
|
524
|
-
const idx = this._steps.length;
|
|
525
|
-
this._steps.push({
|
|
526
|
-
label: truncate('Run ' + m[1]),
|
|
527
|
-
state: 'done',
|
|
528
|
-
startedAt: Date.now(),
|
|
529
|
-
completedAt: Date.now(),
|
|
530
|
-
});
|
|
531
|
-
this.aiderRunningStepIdx = idx;
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
// "Applied edit to <path>" — base_coder.py:2334
|
|
535
|
-
m = line.match(/^Applied edit to\s+(.+)$/);
|
|
536
|
-
if (m) {
|
|
537
|
-
const path = m[1];
|
|
538
|
-
// P22 slice 4 — flush any in-flight running-step output before
|
|
539
|
-
// recording the edit (which is a new aider-pattern signal).
|
|
540
|
-
this.aiderFlushOutput();
|
|
541
|
-
this.flushAiderSay(); // emit reasoning prose that preceded this edit
|
|
542
|
-
// If aider JUST created this file, the Write step already represents the
|
|
543
|
-
// action; skip the Edit step that aider emits for the initial content.
|
|
544
|
-
// Clear from the set after consuming so subsequent edits to the same
|
|
545
|
-
// path (in this same session) DO show as Edit.
|
|
546
|
-
if (this.aiderJustCreated.has(path)) {
|
|
547
|
-
this.aiderJustCreated.delete(path);
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
this._steps.push({
|
|
551
|
-
label: 'Edit ' + shortPath(path),
|
|
552
|
-
state: 'done',
|
|
553
|
-
startedAt: Date.now(),
|
|
554
|
-
completedAt: Date.now(),
|
|
555
|
-
});
|
|
556
|
-
return true;
|
|
557
|
-
}
|
|
558
|
-
// "Creating empty file <path>" — base_coder.py:461 — net-new file scaffold
|
|
559
|
-
m = line.match(/^Creating empty file\s+(.+)$/);
|
|
560
|
-
if (m) {
|
|
561
|
-
const path = m[1];
|
|
562
|
-
this.aiderFlushOutput();
|
|
563
|
-
this.flushAiderSay(); // emit reasoning prose that preceded this write
|
|
564
|
-
this.aiderJustCreated.add(path);
|
|
565
|
-
// Use "Write" to align with Claude/Gemini's net-new-file label vocabulary.
|
|
566
|
-
this._steps.push({
|
|
567
|
-
label: 'Write ' + shortPath(path),
|
|
568
|
-
state: 'done',
|
|
569
|
-
startedAt: Date.now(),
|
|
570
|
-
completedAt: Date.now(),
|
|
571
|
-
});
|
|
572
|
-
return true;
|
|
573
|
-
}
|
|
574
|
-
// P22 slice 4 — non-pattern stdout line WITHIN a Running window
|
|
575
|
-
// gets buffered as that step's output. Cap at 50 lines so a long-
|
|
576
|
-
// running command (e.g., `npm test` printing thousands) doesn't
|
|
577
|
-
// bloat memory; truncateOutput at flush time keeps the on-step
|
|
578
|
-
// value bounded too.
|
|
579
|
-
if (this.aiderRunningStepIdx !== null && this.aiderOutputBuffer.length < 50) {
|
|
580
|
-
this.aiderOutputBuffer.push(line);
|
|
581
|
-
// Update the active step's output incrementally so the dashboard
|
|
582
|
-
// sees the tail before the next aider-pattern flushes.
|
|
583
|
-
const step = this._steps[this.aiderRunningStepIdx];
|
|
584
|
-
if (step)
|
|
585
|
-
step.output = truncateOutput(this.aiderOutputBuffer.join('\n'));
|
|
586
|
-
return true; // mark as "step changed" so the SSE re-emits codehere:steps
|
|
587
|
-
}
|
|
588
|
-
// Outside a Running window: aider's natural-language reasoning. Buffer it
|
|
589
|
-
// (skipping diff fences + markers + status noise); flushed as one kind:'say'
|
|
590
|
-
// step before the next action or at finalize. (PRODUCT_STATE §0 NEXT #1)
|
|
591
|
-
if (this.aiderRunningStepIdx === null)
|
|
592
|
-
this.collectAiderSay(line);
|
|
593
|
-
return false;
|
|
594
|
-
}
|
|
595
|
-
ingestCodex(type, data) {
|
|
596
|
-
if (type !== 'item.completed')
|
|
597
|
-
return false;
|
|
598
|
-
const item = (data.item ?? {});
|
|
599
|
-
const itemType = String(item.type ?? '');
|
|
600
|
-
if (itemType === 'command_execution') {
|
|
601
|
-
const label = labelForCodexCommand(String(item.command ?? ''));
|
|
602
|
-
const ok = item.exit_code === 0 || item.status === 'completed';
|
|
603
|
-
// P22 slice 2 — capture aggregated_output for the bash preview.
|
|
604
|
-
// Codex emits the full stdout+stderr in this field on completion.
|
|
605
|
-
const aggregated = typeof item.aggregated_output === 'string' ? item.aggregated_output : '';
|
|
606
|
-
this._steps.push({
|
|
607
|
-
label,
|
|
608
|
-
state: ok ? 'done' : 'failed',
|
|
609
|
-
startedAt: Date.now(),
|
|
610
|
-
completedAt: Date.now(),
|
|
611
|
-
...(aggregated ? { output: truncateOutput(aggregated) } : {}),
|
|
612
|
-
});
|
|
613
|
-
return true;
|
|
614
|
-
}
|
|
615
|
-
if (itemType === 'tool_call' || itemType === 'local_shell_call') {
|
|
616
|
-
const name = String(item.tool_name ?? item.name ?? 'tool');
|
|
617
|
-
// Tool-call items may carry a `result` or `output` field too.
|
|
618
|
-
const result = (typeof item.result === 'string' ? item.result : '') || (typeof item.output === 'string' ? item.output : '');
|
|
619
|
-
this._steps.push({
|
|
620
|
-
label: truncate(name),
|
|
621
|
-
state: 'done',
|
|
622
|
-
startedAt: Date.now(),
|
|
623
|
-
completedAt: Date.now(),
|
|
624
|
-
...(result ? { output: truncateOutput(result) } : {}),
|
|
625
|
-
});
|
|
626
|
-
return true;
|
|
627
|
-
}
|
|
628
|
-
// file_change — codex emits one item per Edit/Write/Delete batch. The audit
|
|
629
|
-
// mirror (codex-adapter.ts) already handles these; the timeline-step path was
|
|
630
|
-
// dropping them, so real codex file edits were invisible in "watch it work"
|
|
631
|
-
// (mock-currency audit 2026-06-02 caught this — the mock emits the real shape).
|
|
632
|
-
if (itemType === 'file_change') {
|
|
633
|
-
const changes = Array.isArray(item.changes)
|
|
634
|
-
? item.changes
|
|
635
|
-
: [];
|
|
636
|
-
let changed = false;
|
|
637
|
-
for (const ch of changes) {
|
|
638
|
-
const path = String(ch.path ?? '');
|
|
639
|
-
const kind = String(ch.kind ?? 'update');
|
|
640
|
-
const toolName = kind === 'add' ? 'Write' : kind === 'delete' ? 'Delete' : 'Edit';
|
|
641
|
-
// Label directly so the path always shows (labelForClaudeToolUse only
|
|
642
|
-
// knows Edit/Write/Read — it drops the path for Delete).
|
|
643
|
-
this._steps.push({
|
|
644
|
-
label: path ? toolName + ' ' + path : toolName,
|
|
645
|
-
state: 'done',
|
|
646
|
-
startedAt: Date.now(),
|
|
647
|
-
completedAt: Date.now(),
|
|
648
|
-
});
|
|
649
|
-
changed = true;
|
|
650
|
-
}
|
|
651
|
-
return changed;
|
|
652
|
-
}
|
|
653
|
-
// mcp_tool_call + web_search — real visible actions the codex-adapter mirrors
|
|
654
|
-
// to audit; surface them as timeline steps too (2026-06-02 follow-up, Ega-approved
|
|
655
|
-
// class: "watch it work"). Labels mirror codex-adapter conventions.
|
|
656
|
-
if (itemType === 'mcp_tool_call') {
|
|
657
|
-
const server = String(item.server ?? '');
|
|
658
|
-
const tool = String(item.tool ?? 'mcp');
|
|
659
|
-
this._steps.push({
|
|
660
|
-
label: 'MCP · ' + (server ? server + '.' : '') + tool,
|
|
661
|
-
state: 'done',
|
|
662
|
-
startedAt: Date.now(),
|
|
663
|
-
completedAt: Date.now(),
|
|
664
|
-
});
|
|
665
|
-
return true;
|
|
666
|
-
}
|
|
667
|
-
if (itemType === 'web_search') {
|
|
668
|
-
const q = String(item.query ?? '').trim().slice(0, 80);
|
|
669
|
-
this._steps.push({
|
|
670
|
-
label: q ? 'Web search · ' + q : 'Web search',
|
|
671
|
-
state: 'done',
|
|
672
|
-
startedAt: Date.now(),
|
|
673
|
-
completedAt: Date.now(),
|
|
674
|
-
});
|
|
675
|
-
return true;
|
|
676
|
-
}
|
|
677
|
-
// agent_message = codex's own narration. Captured as kind:'say' since the
|
|
678
|
-
// v1 spec settled the taste call that used to be flagged here (pm-layers
|
|
679
|
-
// "just chatter" decision): the room thread is "tool steps woven with the
|
|
680
|
-
// agent's plain-English narration" for EVERY vendor, and the Final Report
|
|
681
|
-
// engine reuses 'say' steps as the summary — without this, codex always
|
|
682
|
-
// degraded to the deterministic fallback.
|
|
683
|
-
if (itemType === 'agent_message') {
|
|
684
|
-
return this.pushSay(String(item.text ?? ''));
|
|
685
|
-
}
|
|
686
|
-
return false;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
//# sourceMappingURL=progress-aggregator.js.map
|