codehere 0.8.0 → 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/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 +269 -25
- 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 +3 -2
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +90 -15
- 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 +133 -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 +22 -10
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- 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 +0 -10
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +132 -1
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +164 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +238 -21
- 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/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1462 -380
- 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 -4214
- package/dist/infrastructure/protocols/verify-page.js +849 -155
- 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 +150 -3
- package/dist/infrastructure/storage/project-thread-reader.js +261 -24
- 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 +26 -14
- 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
|
|
@@ -206,8 +294,10 @@ export class ProgressAggregator {
|
|
|
206
294
|
}
|
|
207
295
|
/** Flush: mark every still-active step as failed (called on abnormal exit). */
|
|
208
296
|
finalize(status) {
|
|
209
|
-
// Surface any trailing
|
|
297
|
+
// Surface any trailing reasoning that had no following action.
|
|
210
298
|
let changed = this.flushAiderSay();
|
|
299
|
+
if (this.flushGeminiSay())
|
|
300
|
+
changed = true;
|
|
211
301
|
for (const s of this._steps) {
|
|
212
302
|
if (s.state === 'active' || s.state === 'pending') {
|
|
213
303
|
s.state = status === 'completed' ? 'done' : 'failed';
|
|
@@ -221,6 +311,43 @@ export class ProgressAggregator {
|
|
|
221
311
|
const idx = this._steps.push({ label, state, startedAt: Date.now() }) - 1;
|
|
222
312
|
return idx;
|
|
223
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
|
+
}
|
|
224
351
|
/** The agent's own narration as a kind:'say' step — the "watch it work"
|
|
225
352
|
* signal AND what the Final Report engine reuses as the summary. Cross-
|
|
226
353
|
* vendor by decision of the v1 spec (the room thread is "tool steps woven
|
|
@@ -232,7 +359,7 @@ export class ProgressAggregator {
|
|
|
232
359
|
return false;
|
|
233
360
|
this._steps.push({
|
|
234
361
|
kind: 'say',
|
|
235
|
-
label: said.length >
|
|
362
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
236
363
|
state: 'done',
|
|
237
364
|
startedAt: Date.now(),
|
|
238
365
|
completedAt: Date.now(),
|
|
@@ -254,19 +381,56 @@ export class ProgressAggregator {
|
|
|
254
381
|
const said = b.text.trim();
|
|
255
382
|
this._steps.push({
|
|
256
383
|
kind: 'say',
|
|
257
|
-
label: said.length >
|
|
384
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
258
385
|
state: 'done',
|
|
259
386
|
startedAt: Date.now(),
|
|
260
387
|
completedAt: Date.now(),
|
|
261
388
|
});
|
|
262
389
|
changed = true;
|
|
263
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
|
+
}
|
|
264
406
|
else if (b.type === 'tool_use') {
|
|
265
407
|
const id = String(b.id ?? '');
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
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
|
+
}
|
|
270
434
|
changed = true;
|
|
271
435
|
}
|
|
272
436
|
}
|
|
@@ -284,17 +448,29 @@ export class ProgressAggregator {
|
|
|
284
448
|
const idx = id ? this.toolUseIndex.get(id) : undefined;
|
|
285
449
|
if (idx !== undefined) {
|
|
286
450
|
const step = this._steps[idx];
|
|
287
|
-
|
|
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';
|
|
288
455
|
step.completedAt = Date.now();
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
// content: Array<{type:'text', text:string}> (multi-block)
|
|
293
|
-
// Truncate to last ~400 chars so the SSE payload + render
|
|
294
|
-
// 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.
|
|
295
459
|
const out = extractClaudeToolResultText(b);
|
|
296
|
-
if (out)
|
|
460
|
+
if (out) {
|
|
461
|
+
step.fullOutput = out;
|
|
297
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
|
+
}
|
|
298
474
|
changed = true;
|
|
299
475
|
}
|
|
300
476
|
}
|
|
@@ -316,6 +492,9 @@ export class ProgressAggregator {
|
|
|
316
492
|
*/
|
|
317
493
|
ingestGemini(type, data) {
|
|
318
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();
|
|
319
498
|
const id = String(data.tool_id ?? '');
|
|
320
499
|
const name = String(data.tool_name ?? 'tool');
|
|
321
500
|
const label = labelForClaudeToolUse(name, data.parameters);
|
|
@@ -348,14 +527,43 @@ export class ProgressAggregator {
|
|
|
348
527
|
return true;
|
|
349
528
|
}
|
|
350
529
|
}
|
|
351
|
-
// Gemini's narrative text
|
|
352
|
-
//
|
|
353
|
-
//
|
|
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.
|
|
354
552
|
if (type === 'content' && typeof data.text === 'string') {
|
|
355
553
|
return this.pushSay(data.text);
|
|
356
554
|
}
|
|
357
555
|
return false;
|
|
358
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
|
+
}
|
|
359
567
|
/**
|
|
360
568
|
* Aider observability parity (v0.5 Path B per docs/design/f3_memory_spec.md
|
|
361
569
|
* + ~/.claude/.../memory/learning_aider.md).
|
|
@@ -439,7 +647,12 @@ export class ProgressAggregator {
|
|
|
439
647
|
* "must contain a space + length >= 4" guard in collectAiderSay already
|
|
440
648
|
* drops bare filenames, fence langs and spinner rules; this catches the
|
|
441
649
|
* 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 )/;
|
|
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;
|
|
443
656
|
aiderFlushOutput() {
|
|
444
657
|
if (this.aiderRunningStepIdx === null)
|
|
445
658
|
return;
|
|
@@ -466,7 +679,7 @@ export class ProgressAggregator {
|
|
|
466
679
|
return false;
|
|
467
680
|
this._steps.push({
|
|
468
681
|
kind: 'say',
|
|
469
|
-
label: said.length >
|
|
682
|
+
label: said.length > 20000 ? said.slice(0, 20000) + '…' : said,
|
|
470
683
|
state: 'done',
|
|
471
684
|
startedAt: Date.now(),
|
|
472
685
|
completedAt: Date.now(),
|
|
@@ -494,6 +707,8 @@ export class ProgressAggregator {
|
|
|
494
707
|
return;
|
|
495
708
|
if (ProgressAggregator.AIDER_NONPROSE_RE.test(line))
|
|
496
709
|
return;
|
|
710
|
+
if (ProgressAggregator.AIDER_BOILERPLATE_RE.test(line))
|
|
711
|
+
return; // CLI startup noise, anywhere in the line
|
|
497
712
|
// Require a space + minimum length so filenames/code tokens don't leak in.
|
|
498
713
|
if (line.length < 4 || !line.includes(' '))
|
|
499
714
|
return;
|
|
@@ -665,7 +880,9 @@ export class ProgressAggregator {
|
|
|
665
880
|
return true;
|
|
666
881
|
}
|
|
667
882
|
if (itemType === 'web_search') {
|
|
668
|
-
const q = String(item.query ?? '')
|
|
883
|
+
const q = String(item.query ?? '')
|
|
884
|
+
.trim()
|
|
885
|
+
.slice(0, 80);
|
|
669
886
|
this._steps.push({
|
|
670
887
|
label: q ? 'Web search · ' + q : 'Web search',
|
|
671
888
|
state: 'done',
|