codehere 0.7.1 → 1.0.1
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 +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/narrator-prompt-template.js +1 -1
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +279 -31
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +4 -3
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +91 -16
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +1 -11
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/memory-injector.js +4 -1
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/cloud-sync.js +2 -2
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
- package/dist/infrastructure/protocols/verify-page.js +884 -148
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
- package/dist/infrastructure/storage/project-thread-reader.js +262 -25
- package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +89 -66
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -17,6 +17,36 @@ function shortPath(p) {
|
|
|
17
17
|
function truncate(s, max = MAX_LABEL) {
|
|
18
18
|
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
19
19
|
}
|
|
20
|
+
/** A successful tool_result sometimes extracts to no text (structured payloads),
|
|
21
|
+
* leaving the step row bare. For tools whose SUCCESS carries a computable claim
|
|
22
|
+
* (Edit succeeded = the replacement applied), derive the outcome from the input
|
|
23
|
+
* we captured at tool_use time. Only states what the success guarantees. */
|
|
24
|
+
function synthesizeOutcome(tool, input) {
|
|
25
|
+
if (!tool || !input)
|
|
26
|
+
return '';
|
|
27
|
+
if (tool === 'Edit' || tool === 'MultiEdit') {
|
|
28
|
+
// MultiEdit carries {edits:[{old_string,new_string,replace_all?}]}; Edit is flat.
|
|
29
|
+
const edits = Array.isArray(input.edits) ? input.edits : [input];
|
|
30
|
+
let d = 0;
|
|
31
|
+
for (const e of edits) {
|
|
32
|
+
// replace_all applies per-occurrence — a computed delta would understate
|
|
33
|
+
// the real change, so claim only what success guarantees.
|
|
34
|
+
if (e.replace_all)
|
|
35
|
+
return 'change applied';
|
|
36
|
+
const o = String(e.old_string ?? '');
|
|
37
|
+
const n = String(e.new_string ?? '');
|
|
38
|
+
if (!o && !n)
|
|
39
|
+
continue;
|
|
40
|
+
d += n.split('\n').length - o.split('\n').length;
|
|
41
|
+
}
|
|
42
|
+
return d === 0 ? 'change applied' : `${d > 0 ? '+' + d : d} lines · change applied`;
|
|
43
|
+
}
|
|
44
|
+
if (tool === 'Write') {
|
|
45
|
+
const c = String(input.content ?? '');
|
|
46
|
+
return c ? `${c.split('\n').length} lines written` : 'file written';
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
20
50
|
function labelForClaudeToolUse(name, input) {
|
|
21
51
|
const i = (input && typeof input === 'object' ? input : {});
|
|
22
52
|
switch (name) {
|
|
@@ -99,6 +129,26 @@ function truncateOutput(s) {
|
|
|
99
129
|
return stripped;
|
|
100
130
|
return '…' + stripped.slice(-(max - 1));
|
|
101
131
|
}
|
|
132
|
+
/** Parse a Claude AskUserQuestion tool_use input into the step.question shape.
|
|
133
|
+
* Defensive — SDK shape is { questions: [{ question, header?, multiSelect?,
|
|
134
|
+
* options: [{ label, description? }] }] }; missing pieces degrade gracefully. */
|
|
135
|
+
function parseAskUserQuestion(toolUseId, input) {
|
|
136
|
+
const raw = Array.isArray(input?.questions) ? input.questions : [];
|
|
137
|
+
const questions = raw.map((qq) => {
|
|
138
|
+
const q = (qq && typeof qq === 'object' ? qq : {});
|
|
139
|
+
const opts = Array.isArray(q.options) ? q.options : [];
|
|
140
|
+
return {
|
|
141
|
+
question: String(q.question ?? ''),
|
|
142
|
+
...(q.header ? { header: String(q.header) } : {}),
|
|
143
|
+
...(q.multiSelect ? { multiSelect: true } : {}),
|
|
144
|
+
options: opts.map((oo) => {
|
|
145
|
+
const o = (oo && typeof oo === 'object' ? oo : {});
|
|
146
|
+
return { label: String(o.label ?? ''), ...(o.description ? { description: String(o.description) } : {}) };
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
return { toolUseId, questions };
|
|
151
|
+
}
|
|
102
152
|
function unwrapShellWrapper(command) {
|
|
103
153
|
// Codex wraps its shell calls in `/bin/zsh -lc "…"` or `sh -c "…"`. Strip that
|
|
104
154
|
// so the step label reads as the actual command instead of the wrapper.
|
|
@@ -161,6 +211,44 @@ export class ProgressAggregator {
|
|
|
161
211
|
return this.ingestAider(type, data);
|
|
162
212
|
if (this.agentId === 'opencode')
|
|
163
213
|
return this.ingestOpencode(type, data);
|
|
214
|
+
// Generic-config agents (cursor, etc.): the generic-cli-adapter normalizes
|
|
215
|
+
// their stream-json to flat tool_use + stdout events. Build steps from those
|
|
216
|
+
// so "watch it work" isn't empty (real dogfood 2026-06-23: cursor runs edited
|
|
217
|
+
// files + verified green but rendered 0 steps).
|
|
218
|
+
return this.ingestGeneric(type, data);
|
|
219
|
+
}
|
|
220
|
+
ingestGeneric(type, data) {
|
|
221
|
+
if (type === 'tool_use') {
|
|
222
|
+
const name = String(data.name ?? data.tool_name ?? 'tool');
|
|
223
|
+
const input = (data.input ?? data.parameters ?? {});
|
|
224
|
+
const arg = (typeof input.path === 'string' && input.path) ||
|
|
225
|
+
(typeof input.file_path === 'string' && input.file_path) ||
|
|
226
|
+
(typeof input.command === 'string' && input.command) ||
|
|
227
|
+
(typeof input.query === 'string' && input.query) ||
|
|
228
|
+
'';
|
|
229
|
+
const short = arg ? arg.split('/').pop() || arg : '';
|
|
230
|
+
const verb = name ? name.charAt(0).toUpperCase() + name.slice(1) : 'Tool';
|
|
231
|
+
this._steps.push({
|
|
232
|
+
label: truncate(short ? `${verb} ${short}` : verb),
|
|
233
|
+
state: 'done',
|
|
234
|
+
startedAt: Date.now(),
|
|
235
|
+
completedAt: Date.now(),
|
|
236
|
+
});
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (type === 'stdout') {
|
|
240
|
+
const line = String(data.line ?? '').trim();
|
|
241
|
+
if (!line)
|
|
242
|
+
return false;
|
|
243
|
+
this._steps.push({
|
|
244
|
+
kind: 'say',
|
|
245
|
+
label: truncate(line),
|
|
246
|
+
state: 'done',
|
|
247
|
+
startedAt: Date.now(),
|
|
248
|
+
completedAt: Date.now(),
|
|
249
|
+
});
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
164
252
|
return false;
|
|
165
253
|
}
|
|
166
254
|
// OpenCode · `run --format json` emits tool_use with shape
|
|
@@ -169,6 +257,14 @@ export class ProgressAggregator {
|
|
|
169
257
|
// real gap surfaced by the mock harness). Build a step on the terminal
|
|
170
258
|
// (status==='completed') event so opencode is a first-class agent.
|
|
171
259
|
ingestOpencode(type, data) {
|
|
260
|
+
// OpenCode's narrative text rides `text` events with part.text (the same
|
|
261
|
+
// shape the adapter's summary extraction reads) — cross-vendor say capture
|
|
262
|
+
// per the v1 spec, so opencode's Final Report stops degrading to the
|
|
263
|
+
// deterministic fallback.
|
|
264
|
+
if (type === 'text') {
|
|
265
|
+
const part = (data.part ?? {});
|
|
266
|
+
return this.pushSay(String(part.text ?? ''));
|
|
267
|
+
}
|
|
172
268
|
if (type !== 'tool_use')
|
|
173
269
|
return false;
|
|
174
270
|
const part = (data.part ?? {});
|
|
@@ -198,8 +294,10 @@ export class ProgressAggregator {
|
|
|
198
294
|
}
|
|
199
295
|
/** Flush: mark every still-active step as failed (called on abnormal exit). */
|
|
200
296
|
finalize(status) {
|
|
201
|
-
// Surface any trailing
|
|
297
|
+
// Surface any trailing reasoning that had no following action.
|
|
202
298
|
let changed = this.flushAiderSay();
|
|
299
|
+
if (this.flushGeminiSay())
|
|
300
|
+
changed = true;
|
|
203
301
|
for (const s of this._steps) {
|
|
204
302
|
if (s.state === 'active' || s.state === 'pending') {
|
|
205
303
|
s.state = status === 'completed' ? 'done' : 'failed';
|
|
@@ -213,6 +311,61 @@ export class ProgressAggregator {
|
|
|
213
311
|
const idx = this._steps.push({ label, state, startedAt: Date.now() }) - 1;
|
|
214
312
|
return idx;
|
|
215
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Git-diff fallback (Phase 1 — aider parity). Aider has no structured event
|
|
316
|
+
* stream; its file-edit steps are parsed from stdout lines ("Applied edit to
|
|
317
|
+
* X" / "Creating empty file X") matched against aider's `base_coder.py`
|
|
318
|
+
* wording. A version bump that re-words those lines would silently yield ZERO
|
|
319
|
+
* file-action steps — a run that really changed files would then render an
|
|
320
|
+
* empty timeline and a "no files changed" report (the exact "did nothing"
|
|
321
|
+
* lie user-#1 hit on a real aider run).
|
|
322
|
+
*
|
|
323
|
+
* This is the format-independent safety net. Given the git-diff file list the
|
|
324
|
+
* artifacts-extractor already computed (which the adapter passes on exit), if
|
|
325
|
+
* NO file-action step exists, synthesize one per changed file from the diff —
|
|
326
|
+
* git is more authoritative than stdout parsing, not less, so these aren't
|
|
327
|
+
* guesses. No-op when the stdout parse already produced file-action steps (so
|
|
328
|
+
* we never double-count). Returns true iff a synthetic step was added.
|
|
329
|
+
*/
|
|
330
|
+
applyFileFallback(files) {
|
|
331
|
+
if (!files || files.length === 0)
|
|
332
|
+
return false;
|
|
333
|
+
const hasFileAction = this._steps.some((s) => /^(Edit|Write|Delete) /.test(s.label));
|
|
334
|
+
if (hasFileAction)
|
|
335
|
+
return false;
|
|
336
|
+
let added = false;
|
|
337
|
+
for (const f of files) {
|
|
338
|
+
const path = String(f?.path ?? '').trim();
|
|
339
|
+
if (!path)
|
|
340
|
+
continue;
|
|
341
|
+
this._steps.push({
|
|
342
|
+
label: 'Edit ' + shortPath(path),
|
|
343
|
+
state: 'done',
|
|
344
|
+
startedAt: Date.now(),
|
|
345
|
+
completedAt: Date.now(),
|
|
346
|
+
});
|
|
347
|
+
added = true;
|
|
348
|
+
}
|
|
349
|
+
return added;
|
|
350
|
+
}
|
|
351
|
+
/** The agent's own narration as a kind:'say' step — the "watch it work"
|
|
352
|
+
* signal AND what the Final Report engine reuses as the summary. Cross-
|
|
353
|
+
* vendor by decision of the v1 spec (the room thread is "tool steps woven
|
|
354
|
+
* with the agent's plain-English narration" — for every agent, not just
|
|
355
|
+
* claude/aider). Same 500-char cap as the original claude capture. */
|
|
356
|
+
pushSay(text) {
|
|
357
|
+
const said = String(text ?? '').trim();
|
|
358
|
+
if (!said)
|
|
359
|
+
return false;
|
|
360
|
+
this._steps.push({
|
|
361
|
+
kind: 'say',
|
|
362
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
363
|
+
state: 'done',
|
|
364
|
+
startedAt: Date.now(),
|
|
365
|
+
completedAt: Date.now(),
|
|
366
|
+
});
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
216
369
|
ingestClaude(type, data) {
|
|
217
370
|
if (type === 'assistant') {
|
|
218
371
|
const msg = (data.message ?? {});
|
|
@@ -228,19 +381,56 @@ export class ProgressAggregator {
|
|
|
228
381
|
const said = b.text.trim();
|
|
229
382
|
this._steps.push({
|
|
230
383
|
kind: 'say',
|
|
231
|
-
label: said.length >
|
|
384
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
232
385
|
state: 'done',
|
|
233
386
|
startedAt: Date.now(),
|
|
234
387
|
completedAt: Date.now(),
|
|
235
388
|
});
|
|
236
389
|
changed = true;
|
|
237
390
|
}
|
|
391
|
+
else if (b.type === 'thinking') {
|
|
392
|
+
// Claude's interleaved reasoning block — was 100% dropped. Surface it
|
|
393
|
+
// as a muted 'think' step so the user can watch the agent reason.
|
|
394
|
+
const think = typeof b.thinking === 'string' ? b.thinking.trim() : '';
|
|
395
|
+
if (think) {
|
|
396
|
+
this._steps.push({
|
|
397
|
+
kind: 'think',
|
|
398
|
+
label: think.length > 20000 ? think.slice(0, 20000) + '…' : think,
|
|
399
|
+
state: 'done',
|
|
400
|
+
startedAt: Date.now(),
|
|
401
|
+
completedAt: Date.now(),
|
|
402
|
+
});
|
|
403
|
+
changed = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
238
406
|
else if (b.type === 'tool_use') {
|
|
239
407
|
const id = String(b.id ?? '');
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
|
|
408
|
+
const name = String(b.name ?? '');
|
|
409
|
+
const input = b.input && typeof b.input === 'object' ? b.input : undefined;
|
|
410
|
+
if (name === 'AskUserQuestion') {
|
|
411
|
+
// The agent is asking the user. Capture the questions+options (was
|
|
412
|
+
// discarded) and mark it 'ask' so the UI renders a question card —
|
|
413
|
+
// not a generic failed tool step.
|
|
414
|
+
const q = parseAskUserQuestion(id, input);
|
|
415
|
+
const n = q.questions.length;
|
|
416
|
+
const idx = this.addStep(`Asking you ${n} question${n === 1 ? '' : 's'}`, 'active');
|
|
417
|
+
const step = this._steps[idx];
|
|
418
|
+
step.kind = 'ask';
|
|
419
|
+
step.toolName = name;
|
|
420
|
+
step.question = q;
|
|
421
|
+
if (id)
|
|
422
|
+
this.toolUseIndex.set(id, idx);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const label = labelForClaudeToolUse(name, input);
|
|
426
|
+
const idx = this.addStep(label, 'active');
|
|
427
|
+
const step = this._steps[idx];
|
|
428
|
+
step.toolName = name;
|
|
429
|
+
if (input)
|
|
430
|
+
step.toolInput = input;
|
|
431
|
+
if (id)
|
|
432
|
+
this.toolUseIndex.set(id, idx);
|
|
433
|
+
}
|
|
244
434
|
changed = true;
|
|
245
435
|
}
|
|
246
436
|
}
|
|
@@ -258,17 +448,29 @@ export class ProgressAggregator {
|
|
|
258
448
|
const idx = id ? this.toolUseIndex.get(id) : undefined;
|
|
259
449
|
if (idx !== undefined) {
|
|
260
450
|
const step = this._steps[idx];
|
|
261
|
-
|
|
451
|
+
// An un-answered AskUserQuestion returns is_error headless, but it's
|
|
452
|
+
// a PENDING question, not a failure — don't flip it to 'failed'.
|
|
453
|
+
if (step.kind !== 'ask')
|
|
454
|
+
step.state = b.is_error ? 'failed' : 'done';
|
|
262
455
|
step.completedAt = Date.now();
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
// content: Array<{type:'text', text:string}> (multi-block)
|
|
267
|
-
// Truncate to last ~400 chars so the SSE payload + render
|
|
268
|
-
// cost stay bounded.
|
|
456
|
+
// Capture BOTH: fullOutput (untruncated, ANSI preserved — for the
|
|
457
|
+
// expandable view) and output (bounded, ANSI-stripped preview so the
|
|
458
|
+
// per-step SSE snapshot stays small). On failure, keep the error body.
|
|
269
459
|
const out = extractClaudeToolResultText(b);
|
|
270
|
-
if (out)
|
|
460
|
+
if (out) {
|
|
461
|
+
step.fullOutput = out;
|
|
271
462
|
step.output = truncateOutput(out);
|
|
463
|
+
if (b.is_error)
|
|
464
|
+
step.errorText = out;
|
|
465
|
+
}
|
|
466
|
+
else if (!b.is_error && step.kind !== 'ask') {
|
|
467
|
+
// Structured results extract to '' and the row would read as a bare
|
|
468
|
+
// "Edit x · 0.5s". For tools whose success has a computable meaning,
|
|
469
|
+
// synthesize a truthful outcome from the input we already hold.
|
|
470
|
+
const synth = synthesizeOutcome(step.toolName, step.toolInput);
|
|
471
|
+
if (synth)
|
|
472
|
+
step.output = synth;
|
|
473
|
+
}
|
|
272
474
|
changed = true;
|
|
273
475
|
}
|
|
274
476
|
}
|
|
@@ -290,6 +492,9 @@ export class ProgressAggregator {
|
|
|
290
492
|
*/
|
|
291
493
|
ingestGemini(type, data) {
|
|
292
494
|
if (type === 'tool_use') {
|
|
495
|
+
// Narration precedes the action in gemini's stream — flush any buffered
|
|
496
|
+
// assistant text first so the timeline reads "explains → acts".
|
|
497
|
+
this.flushGeminiSay();
|
|
293
498
|
const id = String(data.tool_id ?? '');
|
|
294
499
|
const name = String(data.tool_name ?? 'tool');
|
|
295
500
|
const label = labelForClaudeToolUse(name, data.parameters);
|
|
@@ -322,8 +527,43 @@ export class ProgressAggregator {
|
|
|
322
527
|
return true;
|
|
323
528
|
}
|
|
324
529
|
}
|
|
530
|
+
// Gemini's narrative text — THE REAL STREAM SHAPE (verified against
|
|
531
|
+
// @google/gemini-cli StreamJsonFormatter, mirrored in gemini-adapter.ts):
|
|
532
|
+
// `message` events `{ role:'assistant', content, delta?: bool }`, where
|
|
533
|
+
// content arrives as delta CHUNKS. The old `content`-type branch below
|
|
534
|
+
// never matched a real run — so real gemini sessions produced ZERO say
|
|
535
|
+
// steps and every Final Report degraded to the deterministic "didn't
|
|
536
|
+
// leave a written summary" fallback (R10 dogfood gap c). Buffer deltas;
|
|
537
|
+
// flush as ONE say step at a message boundary (non-delta), before the
|
|
538
|
+
// next action (tool_use above), or at stream end (result/error/finalize).
|
|
539
|
+
if (type === 'message') {
|
|
540
|
+
if (data.role === 'assistant' && typeof data.content === 'string' && data.content) {
|
|
541
|
+
this.geminiSayBuffer.push(data.content);
|
|
542
|
+
if (data.delta !== true)
|
|
543
|
+
return this.flushGeminiSay();
|
|
544
|
+
return false; // chunk buffered; the flush is the visible step
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
if (type === 'result' || type === 'error') {
|
|
549
|
+
return this.flushGeminiSay();
|
|
550
|
+
}
|
|
551
|
+
// Legacy shape kept for old cassettes: `content` events with a text field.
|
|
552
|
+
if (type === 'content' && typeof data.text === 'string') {
|
|
553
|
+
return this.pushSay(data.text);
|
|
554
|
+
}
|
|
325
555
|
return false;
|
|
326
556
|
}
|
|
557
|
+
/** Buffered gemini assistant delta chunks awaiting a boundary flush. */
|
|
558
|
+
geminiSayBuffer = [];
|
|
559
|
+
/** Flush buffered gemini narration as one kind:'say' step. */
|
|
560
|
+
flushGeminiSay() {
|
|
561
|
+
if (this.geminiSayBuffer.length === 0)
|
|
562
|
+
return false;
|
|
563
|
+
const text = this.geminiSayBuffer.join('');
|
|
564
|
+
this.geminiSayBuffer = [];
|
|
565
|
+
return this.pushSay(text);
|
|
566
|
+
}
|
|
327
567
|
/**
|
|
328
568
|
* Aider observability parity (v0.5 Path B per docs/design/f3_memory_spec.md
|
|
329
569
|
* + ~/.claude/.../memory/learning_aider.md).
|
|
@@ -407,7 +647,12 @@ export class ProgressAggregator {
|
|
|
407
647
|
* "must contain a space + length >= 4" guard in collectAiderSay already
|
|
408
648
|
* drops bare filenames, fence langs and spinner rules; this catches the
|
|
409
649
|
* noisy lines that DO contain spaces. */
|
|
410
|
-
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 )/;
|
|
650
|
+
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:|Repo-map can|Main model:|Weak model:|Model:|Git repo:|Cur working dir|Git working dir|Use \/|aider |Aider v|Committing )/;
|
|
651
|
+
/** Aider CLI startup/warning boilerplate that arrives as prose-shaped lines
|
|
652
|
+
* (often prefixed "Note: ..."), so it slips past the line-start regex above.
|
|
653
|
+
* Matched ANYWHERE in the line by its distinctive phrases — this is the noise
|
|
654
|
+
* that leaked into "the agent's words" and made the room read like stderr. */
|
|
655
|
+
static AIDER_BOILERPLATE_RE = /in-chat filenames|working dir|repo-map can'?t|deleted from the file system|but not from git\?/i;
|
|
411
656
|
aiderFlushOutput() {
|
|
412
657
|
if (this.aiderRunningStepIdx === null)
|
|
413
658
|
return;
|
|
@@ -434,7 +679,7 @@ export class ProgressAggregator {
|
|
|
434
679
|
return false;
|
|
435
680
|
this._steps.push({
|
|
436
681
|
kind: 'say',
|
|
437
|
-
label: said.length >
|
|
682
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
438
683
|
state: 'done',
|
|
439
684
|
startedAt: Date.now(),
|
|
440
685
|
completedAt: Date.now(),
|
|
@@ -462,6 +707,8 @@ export class ProgressAggregator {
|
|
|
462
707
|
return;
|
|
463
708
|
if (ProgressAggregator.AIDER_NONPROSE_RE.test(line))
|
|
464
709
|
return;
|
|
710
|
+
if (ProgressAggregator.AIDER_BOILERPLATE_RE.test(line))
|
|
711
|
+
return; // CLI startup noise, anywhere in the line
|
|
465
712
|
// Require a space + minimum length so filenames/code tokens don't leak in.
|
|
466
713
|
if (line.length < 4 || !line.includes(' '))
|
|
467
714
|
return;
|
|
@@ -633,7 +880,9 @@ export class ProgressAggregator {
|
|
|
633
880
|
return true;
|
|
634
881
|
}
|
|
635
882
|
if (itemType === 'web_search') {
|
|
636
|
-
const q = String(item.query ?? '')
|
|
883
|
+
const q = String(item.query ?? '')
|
|
884
|
+
.trim()
|
|
885
|
+
.slice(0, 80);
|
|
637
886
|
this._steps.push({
|
|
638
887
|
label: q ? 'Web search · ' + q : 'Web search',
|
|
639
888
|
state: 'done',
|
|
@@ -642,12 +891,15 @@ export class ProgressAggregator {
|
|
|
642
891
|
});
|
|
643
892
|
return true;
|
|
644
893
|
}
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
//
|
|
894
|
+
// agent_message = codex's own narration. Captured as kind:'say' since the
|
|
895
|
+
// v1 spec settled the taste call that used to be flagged here (pm-layers
|
|
896
|
+
// "just chatter" decision): the room thread is "tool steps woven with the
|
|
897
|
+
// agent's plain-English narration" for EVERY vendor, and the Final Report
|
|
898
|
+
// engine reuses 'say' steps as the summary — without this, codex always
|
|
899
|
+
// degraded to the deterministic fallback.
|
|
900
|
+
if (itemType === 'agent_message') {
|
|
901
|
+
return this.pushSay(String(item.text ?? ''));
|
|
902
|
+
}
|
|
651
903
|
return false;
|
|
652
904
|
}
|
|
653
905
|
}
|