@smartmemory/compose 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/compose/SKILL.md +12 -3
- package/.compose-deps.json +51 -25
- package/README.md +79 -7
- package/bin/compose.js +495 -360
- package/bin/judgment-migrate.js +387 -0
- package/contracts/comp-obs-contract.schema.json +9 -3
- package/contracts/fluid-record.schema.json +209 -0
- package/contracts/lifecycle-backfill.schema.json +322 -0
- package/dist/assets/App-Z4MU-H_F.js +916 -0
- package/dist/assets/{_baseUniq-Bo837sRJ.js → _baseUniq-ClWoCPFl.js} +1 -1
- package/dist/assets/{arc-BafGpyqE.js → arc-DY26UIVo.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-BOBfUsqL.js → architectureDiagram-Q4EWVU46-6Ggq4DqJ.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-Dwodev1a.js → blockDiagram-DXYQGD6D-CH3Ked0l.js} +1 -1
- package/dist/assets/{browser-1ntj1-x_.js → browser-BWkrenen.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-CU_bhYag.js → c4Diagram-AHTNJAMY-Bk8dYilu.js} +1 -1
- package/dist/assets/channel-SnZzzh7k.js +1 -0
- package/dist/assets/{chunk-4BX2VUAB-p8WsDwnO.js → chunk-4BX2VUAB-BMR0XaAQ.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-B8h7-eR0.js → chunk-4TB4RGXK-JytR14a9.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-DxeEr98s.js → chunk-55IACEB6-B4Q97BCP.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-BYt8F151.js → chunk-EDXVE4YY-R_qarkSf.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DGSOVeie.js → chunk-FMBD7UC4-C9s7KR9m.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-B-QdgYR2.js → chunk-OYMX7WX6-BySQzVxc.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-Du5UAZLs.js → chunk-QZHKN3VN-DdpSYZsW.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-C8JbNBSk.js → chunk-YZCP3GAM-iE_tzriw.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CBu92dSH.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CBu92dSH.js +1 -0
- package/dist/assets/clone-DgklGjHm.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-O1ESaqge.js → cose-bilkent-S5V4N54A-BdlU6ZX_.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-CPTmFPHw.js → dagre-KV5264BT-Cp3F5KTn.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-B3PNrWs5.js → diagram-5BDNPKRD-DiR6_2q_.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Cscfr6vc.js → diagram-G4DWMVQ6-w0i-p5HX.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-CSfqZ-TM.js → diagram-MMDJMWI5-tIHhwUv3.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Cg4aYS7W.js → diagram-TYMM5635-BAeY3B19.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-_ZqwG5pl.js → erDiagram-SMLLAGMA-Ckx_Knko.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-C83boxFT.js → flowDiagram-DWJPFMVM-DeoNka6J.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-CWnIjuEi.js → ganttDiagram-T4ZO3ILL-BmGnFbEg.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DrMdxZfH.js → gitGraphDiagram-UUTBAWPF-Dk48IHsx.js} +1 -1
- package/dist/assets/{graph-RE4I7Ty7.js → graph-BNzKGvoy.js} +1 -1
- package/dist/assets/{graph-Bi99_6Yf.js → graph-CI_1htl0.js} +1 -1
- package/dist/assets/{index-Rm2RE-c0.js → index-BEfrNBp8.js} +3 -3
- package/dist/assets/index-yyrA5OZd.css +1 -0
- package/dist/assets/{infoDiagram-42DDH7IO-BLmP4Epr.js → infoDiagram-42DDH7IO-BRf827i0.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-yuWWshKN.js → ishikawaDiagram-UXIWVN3A-0kCZaeCM.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-BOfhaJov.js → journeyDiagram-VCZTEJTY-rvU7ayRt.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-Bbolde15.js → kanban-definition-6JOO6SKY-DpQwX1C5.js} +1 -1
- package/dist/assets/{layout-BSf33zm8.js → layout-BI8cXFPI.js} +1 -1
- package/dist/assets/{linear-AvSTWMqx.js → linear-a0glcDiw.js} +1 -1
- package/dist/assets/{min-QBM8H4xN.js → min-vPHfnXcC.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-BuvgtqIc.js → mindmap-definition-QFDTVHPH-D14eF-7C.js} +1 -1
- package/dist/assets/mobile-B7m9EO9D.js +17 -0
- package/dist/assets/{pieDiagram-DEJITSTG-DIzF16vh.js → pieDiagram-DEJITSTG-Cno-gETh.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-D-mbUIjS.js → quadrantDiagram-34T5L4WZ-BUQM1Hfm.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CEs4kCLd.js → requirementDiagram-MS252O5E-pOXlN2-q.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-DFsnCr9n.js → sankeyDiagram-XADWPNL6-Crynd3_b.js} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-BEJYdTjQ.js → sequenceDiagram-FGHM5R23-D9fZdCM8.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-BBXs57uY.js → stateDiagram-FHFEXIEX-CW9qVec8.js} +1 -1
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-DkVLzHbY.js +1 -0
- package/dist/assets/{timeline-definition-GMOUNBTQ-BGvLoVAY.js → timeline-definition-GMOUNBTQ-BcHzhm_8.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-9LaBTMe0.js → vennDiagram-DHZGUBPP-BfytJcWk.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-P4MEqMTP.js → wardley-RL74JXVD-DLj-IjyB.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-o-tmxnlC.js → wardleyDiagram-NUSXRM2D-Ds0Ue68c.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-Dpn7V6qk.js → xychartDiagram-5P7HB3ND-vjWDXFL6.js} +1 -1
- package/dist/index.html +3 -3
- package/lib/agent-string.js +7 -5
- package/lib/append-integrity.js +81 -0
- package/lib/backfill-evidence.js +109 -0
- package/lib/bug-escalation.js +9 -0
- package/lib/build-stream-schema.js +3 -1
- package/lib/build-stream-writer.js +25 -0
- package/lib/build.js +874 -170
- package/lib/canon-guard.js +28 -6
- package/lib/canon-override.js +196 -0
- package/lib/canon-registry.js +104 -0
- package/lib/cli-commands.js +144 -0
- package/lib/codex-preflight.js +26 -13
- package/lib/colleague/context.js +215 -0
- package/lib/colleague/writeback.js +95 -0
- package/lib/completion-gate.js +1421 -0
- package/lib/completion-writer.js +47 -47
- package/lib/consumer-fanout.js +105 -11
- package/lib/coverage-gate.js +200 -0
- package/lib/deps.js +164 -7
- package/lib/dir-lock.js +170 -0
- package/lib/dispatch-ledger.js +3 -3
- package/lib/feature-json.js +1 -1
- package/lib/feature-reconciler.js +8 -0
- package/lib/feature-validator.js +64 -1
- package/lib/feature-writer.js +57 -2
- package/lib/fluid/factory.js +167 -0
- package/lib/fluid/ideabox-dates.js +73 -0
- package/lib/fluid/ideabox-migrate.js +154 -0
- package/lib/fluid/ideabox-ops.js +585 -0
- package/lib/fluid/ideabox-view.js +146 -0
- package/lib/fluid/import-ideabox.js +186 -0
- package/lib/fluid/local-provider.js +606 -0
- package/lib/fluid/provider.js +684 -0
- package/lib/fluid/record-shape.js +214 -0
- package/lib/fluid/record-store.js +328 -0
- package/lib/fluid/render-ideabox.js +261 -0
- package/lib/fluid/schema.js +40 -0
- package/lib/fluid/smartmemory-provider.js +1695 -0
- package/lib/gsd.js +63 -23
- package/lib/guard-cli.js +175 -0
- package/lib/guard-custody.js +141 -0
- package/lib/guard-descriptors.js +530 -0
- package/lib/guard-enrol.js +254 -0
- package/lib/health-score.js +1 -1
- package/lib/ideabox-cli.js +315 -0
- package/lib/ideabox.js +121 -21
- package/lib/judgment/store/index.js +9 -1
- package/lib/judgment/store/records.js +1 -1
- package/lib/judgment/trace.js +380 -0
- package/lib/judgment-decision-write.js +277 -0
- package/lib/judgment-decisions.js +466 -0
- package/lib/judgment-gen.js +5 -1
- package/lib/judgment-writer.js +56 -2
- package/lib/lifecycle-modes.js +4 -4
- package/lib/lineage.js +400 -0
- package/lib/local-claude-connector.js +52 -1
- package/lib/maya-client.js +302 -0
- package/lib/maya-config.js +53 -0
- package/lib/maya-identity.js +283 -0
- package/lib/migrate-anon.js +5 -0
- package/lib/migrate-roadmap.js +15 -0
- package/lib/new.js +13 -1
- package/lib/pipeline-compat.js +104 -0
- package/lib/policy-catalog.js +295 -0
- package/lib/policy-check.js +0 -0
- package/lib/process-termination.js +98 -0
- package/lib/resolve-workspace.js +5 -1
- package/lib/result-normalizer.js +396 -199
- package/lib/roadmap-errors.js +65 -0
- package/lib/roadmap-preservers.js +24 -4
- package/lib/roadmap-residue.js +299 -0
- package/lib/smartmemory-client.js +614 -78
- package/lib/smartmemory-config.js +54 -0
- package/lib/smartmemory-ingest.js +19 -2
- package/lib/step-prompt.js +7 -6
- package/lib/stratum-engine.js +53 -4
- package/lib/stratum-mcp-client.js +271 -36
- package/lib/test-bootstrap.js +31 -0
- package/lib/tool-inventory.js +122 -0
- package/lib/version-check.js +91 -19
- package/lib/vision-writer.js +88 -1
- package/package.json +7 -6
- package/pipelines/bug-fix.stratum.yaml +205 -211
- package/pipelines/build-quick.profiles.json +12 -0
- package/pipelines/build-quick.stratum.yaml +263 -350
- package/pipelines/content.stratum.yaml +81 -77
- package/pipelines/coverage-sweep.stratum.yaml +49 -30
- package/pipelines/plan.stratum.yaml +76 -86
- package/pipelines/refactor.stratum.yaml +125 -125
- package/pipelines/research.stratum.yaml +56 -58
- package/pipelines/review-fix.profiles.json +6 -0
- package/pipelines/review-fix.stratum.yaml +110 -83
- package/presets/team-feature.profiles.json +6 -0
- package/presets/team-feature.stratum.yaml +93 -66
- package/presets/team-research.profiles.json +6 -0
- package/presets/team-research.stratum.yaml +89 -80
- package/presets/team-review.profiles.json +8 -0
- package/presets/team-review.stratum.yaml +98 -80
- package/scripts/cost-census.mjs +70 -0
- package/scripts/guard-sign/compose-guard-sign.sh +62 -0
- package/server/agent-health.js +22 -0
- package/server/agent-hooks.js +14 -1
- package/server/agent-server.js +5 -248
- package/server/agent-spawn.js +3 -4
- package/server/agent-workspace.js +294 -0
- package/server/build-routes.js +6 -5
- package/server/build-stream-bridge.js +53 -0
- package/server/cc-session-watcher.js +4 -1
- package/server/coalescing-buffer.js +7 -1
- package/server/completion-projection.js +228 -0
- package/server/compose-mcp-tools.js +109 -23
- package/server/compose-mcp.js +88 -882
- package/server/decision-event-emit.js +41 -2
- package/server/decision-event-id.js +17 -0
- package/server/decision-events-snapshot.js +3 -0
- package/server/design-routes.js +14 -8
- package/server/feature-scan.js +76 -2
- package/server/file-watcher.js +170 -21
- package/server/ideabox-routes.js +166 -224
- package/server/index.js +70 -100
- package/server/lifecycle-guard.js +240 -10
- package/server/lifecycle-phase-history.js +276 -0
- package/server/maya-routes.js +507 -0
- package/server/mcp-tool-defs.js +940 -0
- package/server/mcp-tool-policy.js +34 -2
- package/server/model-tiers.js +22 -5
- package/server/pipeline-routes.js +21 -11
- package/server/project-root.js +58 -19
- package/server/remote-utils.js +3 -1
- package/server/schema-validator.js +7 -1
- package/server/session-manager.js +5 -6
- package/server/session-routes.js +3 -1
- package/server/stratum-client.js +57 -10
- package/server/stratum-sync.js +6 -3
- package/server/summarizer.js +3 -4
- package/server/supervisor.js +0 -1
- package/server/vision-routes.js +208 -98
- package/server/vision-server.js +86 -23
- package/server/vision-store.js +60 -6
- package/server/vision-utils.js +3 -4
- package/server/workspace-activity.js +18 -0
- package/server/workspace-middleware.js +2 -2
- package/server/workspace-runtime.js +243 -0
- package/server/worktree-gc.js +1 -0
- package/dist/assets/App-PkZzHeMj.js +0 -894
- package/dist/assets/channel-qVK_qn4E.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +0 -1
- package/dist/assets/clone-Pu3RyLUh.js +0 -1
- package/dist/assets/index-LIwREYgH.css +0 -1
- package/dist/assets/mobile-BnXEOE3U.js +0 -17
- package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +0 -1
- package/lib/staleness.js +0 -87
- package/server/ideabox-cache.js +0 -77
package/lib/result-normalizer.js
CHANGED
|
@@ -251,6 +251,75 @@ function copyDispatchId(source, target) {
|
|
|
251
251
|
* call-sites do not all need to be edited in a single sweep. New required opt:
|
|
252
252
|
* `opts.stratum` — the StratumMcpClient instance.
|
|
253
253
|
*/
|
|
254
|
+
/**
|
|
255
|
+
* Sum two usage records into one, for a step whose work took more than one
|
|
256
|
+
* agent call (e.g. a COMP-POLICY-CHECK revision replacing the primary draft).
|
|
257
|
+
* Token/cost fields add; `model` keeps the later run's value when it has one.
|
|
258
|
+
* Either side may be null.
|
|
259
|
+
*
|
|
260
|
+
* @param {object|null} a
|
|
261
|
+
* @param {object|null} b
|
|
262
|
+
* @returns {object|null}
|
|
263
|
+
*/
|
|
264
|
+
export function mergeUsage(a, b) {
|
|
265
|
+
if (!a) return b ?? null;
|
|
266
|
+
if (!b) return a;
|
|
267
|
+
return {
|
|
268
|
+
input_tokens: (a.input_tokens ?? 0) + (b.input_tokens ?? 0),
|
|
269
|
+
output_tokens: (a.output_tokens ?? 0) + (b.output_tokens ?? 0),
|
|
270
|
+
cache_creation_input_tokens: (a.cache_creation_input_tokens ?? 0) + (b.cache_creation_input_tokens ?? 0),
|
|
271
|
+
cache_read_input_tokens: (a.cache_read_input_tokens ?? 0) + (b.cache_read_input_tokens ?? 0),
|
|
272
|
+
cost_usd: (a.cost_usd ?? 0) + (b.cost_usd ?? 0),
|
|
273
|
+
model: b.model ?? a.model ?? null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function hasReportedUsage(usage) {
|
|
278
|
+
if (!usage || typeof usage !== 'object') return false;
|
|
279
|
+
return [
|
|
280
|
+
usage.tokens, usage.input_tokens, usage.output_tokens,
|
|
281
|
+
usage.cache_read, usage.cache_read_input_tokens,
|
|
282
|
+
usage.cache_creation, usage.cache_creation_input_tokens,
|
|
283
|
+
usage.cost_usd, usage.usd, usage.ms, usage.duration_ms,
|
|
284
|
+
].some((value) => typeof value === 'number' && Number.isFinite(value) && value !== 0);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function usageRecordFromRaw(usage, telemetry, dispatchId, fallback = {}, split = null) {
|
|
288
|
+
if (!hasReportedUsage(usage)) return null;
|
|
289
|
+
// STRAT-USAGE-SPLIT: prefer the connector-reported split over reconstruction
|
|
290
|
+
// (the reconstruction cannot tell input from output; it filed the aggregate
|
|
291
|
+
// as output on every record before surface 16).
|
|
292
|
+
const input = split?.input ?? usage.input_tokens ?? 0;
|
|
293
|
+
const output = split?.output
|
|
294
|
+
?? usage.output_tokens
|
|
295
|
+
?? (typeof usage.tokens === 'number' ? Math.max(0, usage.tokens - input) : 0);
|
|
296
|
+
const record = {
|
|
297
|
+
dispatch_id: dispatchId ?? randomUUID(),
|
|
298
|
+
model: telemetry?.model ?? usage.model ?? fallback.model ?? 'unknown',
|
|
299
|
+
...(telemetry?.effort ?? fallback.effort
|
|
300
|
+
? { effort: telemetry?.effort ?? fallback.effort }
|
|
301
|
+
: {}),
|
|
302
|
+
duration_ms: telemetry?.durationMs ?? usage.duration_ms ?? usage.ms ?? fallback.duration_ms ?? 0,
|
|
303
|
+
input_tokens: input,
|
|
304
|
+
output_tokens: output,
|
|
305
|
+
...(typeof (split?.cacheRead ?? usage.cache_read ?? usage.cache_read_input_tokens) === 'number'
|
|
306
|
+
? { cache_read: split?.cacheRead ?? usage.cache_read ?? usage.cache_read_input_tokens }
|
|
307
|
+
: {}),
|
|
308
|
+
...(typeof (split?.cacheCreation ?? usage.cache_creation ?? usage.cache_creation_input_tokens) === 'number'
|
|
309
|
+
? { cache_creation: split?.cacheCreation ?? usage.cache_creation ?? usage.cache_creation_input_tokens }
|
|
310
|
+
: {}),
|
|
311
|
+
};
|
|
312
|
+
const cost = usage.cost_usd ?? usage.usd;
|
|
313
|
+
const usdSource = ['reported', 'estimated'].includes(usage.usd_source)
|
|
314
|
+
? usage.usd_source
|
|
315
|
+
: (['reported', 'estimated'].includes(fallback.usd_source) ? fallback.usd_source : null);
|
|
316
|
+
if (typeof cost === 'number' && Number.isFinite(cost) && cost > 0 && usdSource) {
|
|
317
|
+
record.cost_usd = cost;
|
|
318
|
+
record.usd_source = usdSource;
|
|
319
|
+
}
|
|
320
|
+
return record;
|
|
321
|
+
}
|
|
322
|
+
|
|
254
323
|
export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, opts = {}) {
|
|
255
324
|
const progress = opts.progress;
|
|
256
325
|
const streamWriter = opts.streamWriter;
|
|
@@ -267,6 +336,11 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
267
336
|
|
|
268
337
|
const stepId = stepDispatch.step_id ?? 'unknown';
|
|
269
338
|
const agentType = stepDispatch.agent ?? 'claude';
|
|
339
|
+
// COMP-AGENT-LANES: when the caller (a parallel fanout item) supplies a lane
|
|
340
|
+
// envelope, stamp it on every relayed stream write so the cockpit can route
|
|
341
|
+
// this run's output to its worker lane. Absent the opt, writes stay
|
|
342
|
+
// byte-identical (single-step runs unchanged).
|
|
343
|
+
const laneStamp = (opts.lane && typeof opts.lane === 'object') ? { lane: opts.lane } : {};
|
|
270
344
|
// D6: the engine ships only the bare provider literal (stepDispatch.agent), so
|
|
271
345
|
// the full profile string (with tool restrictions + model tier) is supplied
|
|
272
346
|
// compose-side via opts.profile, keyed off the compose-owned sidecar. It
|
|
@@ -285,11 +359,22 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
285
359
|
primaryTelemetry.attempt = stepDispatch.attempt;
|
|
286
360
|
}
|
|
287
361
|
primaryTelemetry.effort_intended = cfg.effort ?? null;
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
362
|
+
// Claude profiles bind through SDK tool filters. Codex has an OS sandbox;
|
|
363
|
+
// write permission must be explicitly requested by the implementation caller.
|
|
364
|
+
const sandboxMode = cfg.provider === 'codex'
|
|
365
|
+
? (opts.sandboxMode ?? 'read-only')
|
|
366
|
+
: undefined;
|
|
367
|
+
const executionOptions = {
|
|
368
|
+
modelID: cfg.modelID ?? undefined,
|
|
369
|
+
...(cfg.provider === 'claude' ? {
|
|
370
|
+
allowedTools: cfg.allowedTools ?? undefined,
|
|
371
|
+
disallowedTools: cfg.disallowedTools ?? undefined,
|
|
372
|
+
thinking: cfg.thinking ?? undefined,
|
|
373
|
+
} : {}),
|
|
374
|
+
effort: cfg.effort ?? undefined,
|
|
375
|
+
sandboxMode,
|
|
376
|
+
cwd: opts.cwd ?? undefined,
|
|
377
|
+
};
|
|
293
378
|
|
|
294
379
|
const outputFields = stepDispatch.output_fields;
|
|
295
380
|
const hasSchema = outputFields && typeof outputFields === 'object' && Object.keys(outputFields).length > 0;
|
|
@@ -325,6 +410,7 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
325
410
|
cost_usd: 0,
|
|
326
411
|
model: null,
|
|
327
412
|
};
|
|
413
|
+
let primaryUsdSource = null;
|
|
328
414
|
|
|
329
415
|
let timedOut = false;
|
|
330
416
|
let userInterruptAction = null;
|
|
@@ -333,21 +419,19 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
333
419
|
// the run mid-stream by returning a truthy reason from a tool event.
|
|
334
420
|
let abortReason = null;
|
|
335
421
|
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// restrictions AND be interrupted (the sync engine agent_run can do neither;
|
|
339
|
-
// its background mode is codex+read-only-only). Abort replaces cancelAgentRun
|
|
340
|
-
// as the stop mechanism, and the connector streams real tool events so the
|
|
341
|
-
// stuck detector / timeout actually stop a runaway agent.
|
|
422
|
+
// Both local SDK and MCP executions own a cancellable handle. The MCP
|
|
423
|
+
// client translates this signal to an acknowledged foreground cancellation.
|
|
342
424
|
const useLocalClaude = opts.localExecution === true && cfg.provider === 'claude';
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
425
|
+
const primaryFailure = (source, target) => {
|
|
426
|
+
const record = usageRecordFromRaw(source?.usage, source?.telemetry, source?.dispatchId, {
|
|
427
|
+
model: cfg.modelID, effort: cfg.effort, duration_ms: Date.now() - startTime,
|
|
428
|
+
usd_source: source?.usdSource ?? (useLocalClaude ? 'reported' : undefined),
|
|
429
|
+
}, source?.split);
|
|
430
|
+
if (record) target.usages = [record];
|
|
431
|
+
return copyDispatchId(source, target);
|
|
350
432
|
};
|
|
433
|
+
const abortController = new AbortController();
|
|
434
|
+
const stopRun = () => abortController.abort();
|
|
351
435
|
|
|
352
436
|
// Subscribe BEFORE calling agentRun — events fire during the call.
|
|
353
437
|
const unsub = stratum.onEvent(correlationId, subStepId, (env) => {
|
|
@@ -360,14 +444,14 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
360
444
|
case 'agent_relay':
|
|
361
445
|
if (m.role === 'assistant' && typeof m.text === 'string' && m.text.length > 0) {
|
|
362
446
|
textParts.push(m.text);
|
|
363
|
-
if (streamWriter) streamWriter.write({ type: 'assistant', content: m.text });
|
|
447
|
+
if (streamWriter) streamWriter.write({ type: 'assistant', content: m.text, ...laneStamp });
|
|
364
448
|
}
|
|
365
449
|
break;
|
|
366
450
|
case 'tool_use_summary': {
|
|
367
451
|
const tool = m.tool;
|
|
368
452
|
if (tool) {
|
|
369
453
|
if (streamWriter) {
|
|
370
|
-
streamWriter.write({ type: 'tool_use', tool, input: m.input ?? {} });
|
|
454
|
+
streamWriter.write({ type: 'tool_use', tool, input: m.input ?? {}, ...laneStamp });
|
|
371
455
|
}
|
|
372
456
|
if (onToolUse) onToolUse({ tool, input: m.input ?? {}, timestamp: Date.now() });
|
|
373
457
|
if (progress) {
|
|
@@ -377,7 +461,7 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
377
461
|
}
|
|
378
462
|
if (m.summary) {
|
|
379
463
|
if (streamWriter) {
|
|
380
|
-
streamWriter.write({ type: 'tool_use_summary', summary: m.summary, output: m.output ?? '' });
|
|
464
|
+
streamWriter.write({ type: 'tool_use_summary', summary: m.summary, output: m.output ?? '', ...laneStamp });
|
|
381
465
|
}
|
|
382
466
|
if (progress) progress.toolSummary(m.summary);
|
|
383
467
|
}
|
|
@@ -396,6 +480,9 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
396
480
|
const stepCost = m.cost_usd != null
|
|
397
481
|
? m.cost_usd
|
|
398
482
|
: calculateCost(m.model, inTok, outTok, ccit, crit);
|
|
483
|
+
primaryUsdSource = m.cost_usd != null && primaryUsdSource !== 'estimated'
|
|
484
|
+
? 'reported'
|
|
485
|
+
: 'estimated';
|
|
399
486
|
usageTotals.cost_usd += stepCost;
|
|
400
487
|
if (streamWriter) {
|
|
401
488
|
streamWriter.write({
|
|
@@ -406,6 +493,7 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
406
493
|
cache_read_input_tokens: crit,
|
|
407
494
|
cost_usd: stepCost,
|
|
408
495
|
model: m.model ?? null,
|
|
496
|
+
...laneStamp,
|
|
409
497
|
});
|
|
410
498
|
}
|
|
411
499
|
break;
|
|
@@ -447,7 +535,7 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
447
535
|
// abort a spinning run.
|
|
448
536
|
const localOnToolUse = ({ tool, input }) => {
|
|
449
537
|
if (onToolUse) onToolUse({ tool, input, timestamp: Date.now() });
|
|
450
|
-
if (streamWriter) streamWriter.write({ type: 'tool_use', tool, input: input ?? {} });
|
|
538
|
+
if (streamWriter) streamWriter.write({ type: 'tool_use', tool, input: input ?? {}, ...laneStamp });
|
|
451
539
|
if (progress) progress.toolUse(tool, input?.command ?? input?.pattern ?? input?.file_path ?? '');
|
|
452
540
|
if (opts.onAgentEvent && !abortReason) {
|
|
453
541
|
const env = { schema_version: '0.2.6', kind: 'tool_use_summary', metadata: { tool, input: input ?? {}, summary: '', output: '' } };
|
|
@@ -456,207 +544,316 @@ export async function runAndNormalize(_connectorIgnored, prompt, stepDispatch, o
|
|
|
456
544
|
}
|
|
457
545
|
};
|
|
458
546
|
|
|
459
|
-
|
|
460
|
-
let primaryDispatchId = null;
|
|
461
|
-
let repairDispatchId = null;
|
|
547
|
+
// Keep the deadline and interrupt controls active through review repair.
|
|
462
548
|
try {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
549
|
+
let runResult;
|
|
550
|
+
let primaryDispatchId = null;
|
|
551
|
+
let repairDispatchId = null;
|
|
552
|
+
try {
|
|
553
|
+
if (useLocalClaude) {
|
|
554
|
+
// Test seam: an installed factory shim exposes an SDK-shaped query adapter
|
|
555
|
+
// so the goldens drive the local path without spawning a real claude.
|
|
556
|
+
// Gated on NODE_ENV=test so production always uses the real SDK.
|
|
557
|
+
const localQuery = opts.localQuery
|
|
558
|
+
?? (process.env.NODE_ENV === 'test' && stratum ? stratum._localQuery : undefined);
|
|
559
|
+
runResult = await runLocalClaudeAgent(actualPrompt, {
|
|
560
|
+
cwd: opts.cwd ?? undefined,
|
|
561
|
+
model: cfg.modelID ?? undefined,
|
|
562
|
+
allowedTools: cfg.allowedTools ?? undefined,
|
|
563
|
+
disallowedTools: cfg.disallowedTools ?? undefined,
|
|
564
|
+
thinking: cfg.thinking ?? undefined,
|
|
565
|
+
effort: cfg.effort ?? undefined,
|
|
566
|
+
abortController,
|
|
567
|
+
onToolUse: localOnToolUse,
|
|
568
|
+
// COMP-AGENT-LANES: only a lane-carrying run (parallel fanout item)
|
|
569
|
+
// relays local assistant text to the stream — engine-path parity for
|
|
570
|
+
// the lanes UI. Lane-less local runs keep their historical shape (no
|
|
571
|
+
// assistant stream writes).
|
|
572
|
+
...(laneStamp.lane && streamWriter ? {
|
|
573
|
+
onAssistantText: (text) => {
|
|
574
|
+
streamWriter.write({ type: 'assistant', content: text, ...laneStamp });
|
|
575
|
+
},
|
|
576
|
+
} : {}),
|
|
577
|
+
telemetry: primaryTelemetry,
|
|
578
|
+
...(localQuery ? { query: localQuery } : {}),
|
|
579
|
+
});
|
|
580
|
+
} else {
|
|
581
|
+
runResult = await stratum.agentRun(agentType, actualPrompt, {
|
|
582
|
+
...executionOptions,
|
|
583
|
+
signal: abortController.signal,
|
|
584
|
+
correlationId,
|
|
585
|
+
telemetry: primaryTelemetry,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
primaryDispatchId = typeof runResult?.dispatchId === 'string'
|
|
589
|
+
? runResult.dispatchId
|
|
590
|
+
: null;
|
|
591
|
+
} catch (err) {
|
|
592
|
+
if (['CANCELLATION_UNCONFIRMED', 'CANCELLATION_TEARDOWN_TIMEOUT'].includes(err?.code)) throw primaryFailure(err, err);
|
|
593
|
+
// F3/G3: preserve any billable usage the failed run reported (the local
|
|
594
|
+
// connector attaches it on a non-success result / usage-bearing rejection) so
|
|
595
|
+
// the consumer failure envelope can still debit the engine/GSD ledgers. G3:
|
|
596
|
+
// the timeout/abort throws happen here too — attach the usage to THOSE errors
|
|
597
|
+
// (not only the generic AgentError) so timeout/stuck attempts are billed.
|
|
598
|
+
// The local Claude SDK's price is provider-reported on failures too; label it
|
|
599
|
+
// here so the receipt funnel does not drop it as unlabelled raw usd.
|
|
600
|
+
const errUsage = (err && typeof err === 'object' && err.usage)
|
|
601
|
+
? (useLocalClaude && err.usage.usd_source === undefined ? { ...err.usage, usd_source: 'reported' } : err.usage)
|
|
602
|
+
: null;
|
|
603
|
+
if (timedOut) {
|
|
604
|
+
const e = new AgentTimeoutError(stepId, Date.now() - startTime);
|
|
605
|
+
if (errUsage) e.usage = errUsage;
|
|
606
|
+
throw primaryFailure(err, e);
|
|
607
|
+
}
|
|
608
|
+
if (userInterruptAction) {
|
|
609
|
+
const e = new UserInterruptError(stepId, userInterruptAction);
|
|
610
|
+
if (errUsage) e.usage = errUsage;
|
|
611
|
+
throw primaryFailure(err, e);
|
|
612
|
+
}
|
|
613
|
+
if (abortReason) {
|
|
614
|
+
const e = new AgentAbortedError(stepId, abortReason);
|
|
615
|
+
if (errUsage) e.usage = errUsage;
|
|
616
|
+
throw primaryFailure(err, e);
|
|
617
|
+
}
|
|
618
|
+
const agentError = new AgentError(err?.message ?? 'Agent run failed');
|
|
619
|
+
if (errUsage) agentError.usage = errUsage;
|
|
620
|
+
throw primaryFailure(err, agentError);
|
|
493
621
|
}
|
|
494
|
-
|
|
495
|
-
|
|
622
|
+
|
|
623
|
+
// H2: when the underlying run RESOLVES late (rather than rejecting) after a
|
|
624
|
+
// timeout/abort fired, its billable usage is on runResult.usage. Copy it onto
|
|
625
|
+
// the thrown error — the same single channel the rejection path (G3) uses — so
|
|
626
|
+
// the consumer timeout envelope / stuck-ledger accounting still bill the attempt.
|
|
627
|
+
// Throwing here means usageTotals is never returned, so this is the ONLY channel
|
|
628
|
+
// (no double count).
|
|
629
|
+
const lateUsage = (runResult && typeof runResult === 'object' && runResult.usage && typeof runResult.usage === 'object')
|
|
630
|
+
? runResult.usage
|
|
496
631
|
: null;
|
|
497
|
-
} catch (err) {
|
|
498
|
-
// F3/G3: preserve any billable usage the failed run reported (the local
|
|
499
|
-
// connector attaches it on a non-success result / usage-bearing rejection) so
|
|
500
|
-
// the consumer failure envelope can still debit the engine/GSD ledgers. G3:
|
|
501
|
-
// the timeout/abort throws happen here too — attach the usage to THOSE errors
|
|
502
|
-
// (not only the generic AgentError) so timeout/stuck attempts are billed.
|
|
503
|
-
const errUsage = (err && typeof err === 'object' && err.usage) ? err.usage : null;
|
|
504
632
|
if (timedOut) {
|
|
505
633
|
const e = new AgentTimeoutError(stepId, Date.now() - startTime);
|
|
506
|
-
if (
|
|
507
|
-
throw
|
|
634
|
+
if (lateUsage) e.usage = lateUsage;
|
|
635
|
+
throw primaryFailure(runResult, e);
|
|
508
636
|
}
|
|
509
637
|
if (userInterruptAction) {
|
|
510
|
-
|
|
638
|
+
const e = new UserInterruptError(stepId, userInterruptAction);
|
|
639
|
+
if (lateUsage) e.usage = lateUsage;
|
|
640
|
+
throw primaryFailure(runResult, e);
|
|
511
641
|
}
|
|
512
642
|
if (abortReason) {
|
|
513
643
|
const e = new AgentAbortedError(stepId, abortReason);
|
|
514
|
-
if (
|
|
515
|
-
throw
|
|
644
|
+
if (lateUsage) e.usage = lateUsage;
|
|
645
|
+
throw primaryFailure(runResult, e);
|
|
516
646
|
}
|
|
517
|
-
const agentError = new AgentError(err?.message ?? 'Agent run failed');
|
|
518
|
-
if (errUsage) agentError.usage = errUsage;
|
|
519
|
-
throw copyDispatchId(err, agentError);
|
|
520
|
-
} finally {
|
|
521
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
522
|
-
if (onInterrupt && progress?.removeListener) progress.removeListener('interrupt', onInterrupt);
|
|
523
|
-
unsub();
|
|
524
|
-
}
|
|
525
647
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
648
|
+
// D2(b): the TS agent_run path returns a synchronous `complete` envelope with
|
|
649
|
+
// aggregate usage ({usd?, tokens, ms}) and streams NO step_usage progress
|
|
650
|
+
// events — without folding it in, budget accounting debits nothing on the TS
|
|
651
|
+
// route. The python / factory-shim path streams step_usage events (usageTotals
|
|
652
|
+
// already populated), so adopt runResult.usage only when the event stream
|
|
653
|
+
// contributed nothing (avoids double counting).
|
|
654
|
+
const runUsage = runResult && typeof runResult === 'object' ? runResult.usage : null;
|
|
655
|
+
const runSplit = runResult && typeof runResult === 'object' && runResult.split && typeof runResult.split === 'object'
|
|
656
|
+
? runResult.split
|
|
657
|
+
: null;
|
|
658
|
+
const usageFromEvents = usageTotals.input_tokens || usageTotals.output_tokens || usageTotals.cost_usd;
|
|
659
|
+
if (runUsage && typeof runUsage === 'object' && !usageFromEvents) {
|
|
660
|
+
// STRAT-USAGE-SPLIT: the TS envelope now carries the true input/output
|
|
661
|
+
// detail beside its Budget-shaped usage. Adopt it; only a split-less
|
|
662
|
+
// (pre-surface-16) envelope falls back to the aggregate, which is filed
|
|
663
|
+
// as output for continuity — mislabeled, but the legacy column it always
|
|
664
|
+
// occupied. That path retires with surface 15.
|
|
665
|
+
if (runSplit) {
|
|
666
|
+
usageTotals.input_tokens += runSplit.input ?? 0;
|
|
667
|
+
usageTotals.output_tokens += runSplit.output ?? 0;
|
|
668
|
+
if (typeof runSplit.cacheRead === 'number') {
|
|
669
|
+
usageTotals.cache_read_input_tokens = (usageTotals.cache_read_input_tokens ?? 0) + runSplit.cacheRead;
|
|
670
|
+
}
|
|
671
|
+
if (typeof runSplit.cacheCreation === 'number') {
|
|
672
|
+
usageTotals.cache_creation_input_tokens = (usageTotals.cache_creation_input_tokens ?? 0) + runSplit.cacheCreation;
|
|
673
|
+
}
|
|
674
|
+
} else if (typeof runUsage.tokens === 'number') {
|
|
675
|
+
usageTotals.output_tokens += runUsage.tokens;
|
|
676
|
+
}
|
|
677
|
+
if (typeof runUsage.usd === 'number') usageTotals.cost_usd += runUsage.usd;
|
|
678
|
+
if (typeof runUsage.ms === 'number') usageTotals.duration_ms = (usageTotals.duration_ms ?? 0) + runUsage.ms;
|
|
679
|
+
if (!usageTotals.model && runResult.telemetry?.model) usageTotals.model = runResult.telemetry.model;
|
|
680
|
+
}
|
|
548
681
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
682
|
+
const usages = [];
|
|
683
|
+
const primaryFromEvents = usageFromEvents || primaryUsdSource !== null;
|
|
684
|
+
if (primaryFromEvents) {
|
|
685
|
+
const primary = {
|
|
686
|
+
dispatch_id: primaryDispatchId ?? randomUUID(),
|
|
687
|
+
model: usageTotals.model ?? runResult?.telemetry?.model ?? cfg.modelID ?? 'unknown',
|
|
688
|
+
...(runResult?.telemetry?.effort ?? cfg.effort
|
|
689
|
+
? { effort: runResult?.telemetry?.effort ?? cfg.effort }
|
|
690
|
+
: {}),
|
|
691
|
+
duration_ms: runResult?.telemetry?.durationMs ?? usageTotals.duration_ms ?? (Date.now() - startTime),
|
|
692
|
+
input_tokens: usageTotals.input_tokens,
|
|
693
|
+
output_tokens: usageTotals.output_tokens,
|
|
694
|
+
...(usageTotals.cache_read_input_tokens
|
|
695
|
+
? { cache_read: usageTotals.cache_read_input_tokens }
|
|
696
|
+
: {}),
|
|
697
|
+
...(usageTotals.cache_creation_input_tokens
|
|
698
|
+
? { cache_creation: usageTotals.cache_creation_input_tokens }
|
|
699
|
+
: {}),
|
|
700
|
+
...(usageTotals.cost_usd > 0
|
|
701
|
+
? { cost_usd: usageTotals.cost_usd, usd_source: primaryUsdSource ?? 'estimated' }
|
|
702
|
+
: { usd_source: primaryUsdSource ?? 'estimated' }),
|
|
703
|
+
};
|
|
704
|
+
usages.push(primary);
|
|
705
|
+
} else {
|
|
706
|
+
const primary = usageRecordFromRaw(runUsage, runResult?.telemetry, primaryDispatchId, {
|
|
707
|
+
model: cfg.modelID,
|
|
708
|
+
effort: cfg.effort,
|
|
709
|
+
duration_ms: Date.now() - startTime,
|
|
710
|
+
usd_source: runResult?.usdSource ?? (useLocalClaude ? 'reported' : undefined),
|
|
711
|
+
}, runSplit);
|
|
712
|
+
if (primary) usages.push(primary);
|
|
713
|
+
}
|
|
563
714
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
715
|
+
const text = (runResult && typeof runResult.text === 'string' && runResult.text.length > 0)
|
|
716
|
+
? runResult.text
|
|
717
|
+
: textParts.join('');
|
|
567
718
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
719
|
+
if (progress) {
|
|
720
|
+
progress.debug(`normalizer: textParts=${textParts.length}, text length=${text.length}`);
|
|
721
|
+
if (text.length > 0) progress.debug(`text preview: ${text.slice(0, 300)}`);
|
|
722
|
+
} else if (process.env.COMPOSE_DEBUG) {
|
|
723
|
+
process.stderr.write(` [normalizer] textParts=${textParts.length}, text length=${text.length}\n`);
|
|
724
|
+
}
|
|
574
725
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
726
|
+
// review_mode hook — MUST be before the !hasSchema early return (MF-3 in blueprint).
|
|
727
|
+
// Parallel lens steps often have empty output_fields (hasSchema=false), but review
|
|
728
|
+
// normalization must still run. The Stratum server validates the post-normalize result
|
|
729
|
+
// via `ensure` expressions after stratum_step_done — not against raw text.
|
|
730
|
+
if (opts.reviewMode === true) {
|
|
731
|
+
const reviewAgentType = agentType; // already resolved from stepDispatch.agent at line 178
|
|
732
|
+
const reviewModelId = usageTotals.model ?? cfg.modelID ?? null;
|
|
733
|
+
// The repair dispatch is only CREDITED (dispatchIds.repair) when its output
|
|
734
|
+
// actually replaced the primary parse — a failed or unparseable repair still
|
|
735
|
+
// bills its usage but must not absorb the step's settlement.
|
|
736
|
+
let repairUsed = false;
|
|
737
|
+
let repairFailure = null;
|
|
738
|
+
let repairResultForControl = null;
|
|
739
|
+
const foldRepairUsage = (usage) => {
|
|
740
|
+
if (!usage || typeof usage !== 'object') return;
|
|
741
|
+
if (typeof usage.tokens === 'number') usageTotals.output_tokens += usage.tokens;
|
|
742
|
+
if (typeof usage.usd === 'number') usageTotals.cost_usd += usage.usd;
|
|
743
|
+
};
|
|
744
|
+
const repairFn = stratum
|
|
745
|
+
? async (repairPrompt) => {
|
|
746
|
+
try {
|
|
747
|
+
const repairResult = await stratum.agentRun(reviewAgentType, repairPrompt, {
|
|
748
|
+
...executionOptions,
|
|
749
|
+
signal: abortController.signal,
|
|
750
|
+
telemetry: { ...primaryTelemetry, site: 'review-repair' },
|
|
751
|
+
});
|
|
752
|
+
repairResultForControl = repairResult;
|
|
753
|
+
repairDispatchId = typeof repairResult?.dispatchId === 'string'
|
|
754
|
+
? repairResult.dispatchId
|
|
755
|
+
: null;
|
|
756
|
+
foldRepairUsage(repairResult?.usage);
|
|
757
|
+
const record = usageRecordFromRaw(
|
|
758
|
+
repairResult?.usage,
|
|
759
|
+
repairResult?.telemetry,
|
|
760
|
+
repairDispatchId,
|
|
761
|
+
{ model: cfg.modelID, effort: cfg.effort, usd_source: repairResult?.usdSource },
|
|
762
|
+
repairResult?.split && typeof repairResult.split === 'object' ? repairResult.split : null,
|
|
763
|
+
);
|
|
764
|
+
if (record) usages.push(record);
|
|
765
|
+
return repairResult?.text ?? '';
|
|
766
|
+
} catch (error) {
|
|
767
|
+
repairFailure = error;
|
|
768
|
+
repairDispatchId = typeof error?.dispatchId === 'string'
|
|
769
|
+
? error.dispatchId
|
|
770
|
+
: null;
|
|
771
|
+
foldRepairUsage(error?.usage);
|
|
772
|
+
const record = usageRecordFromRaw(
|
|
773
|
+
error?.usage,
|
|
774
|
+
error?.telemetry,
|
|
775
|
+
repairDispatchId,
|
|
776
|
+
{ model: cfg.modelID, effort: cfg.effort, usd_source: error?.usdSource },
|
|
777
|
+
error?.split,
|
|
778
|
+
);
|
|
779
|
+
if (record) usages.push(record);
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
610
782
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
783
|
+
: undefined;
|
|
784
|
+
const reviewResult = await normalizeReviewResult(text, {
|
|
785
|
+
agentType: reviewAgentType,
|
|
786
|
+
modelId: reviewModelId,
|
|
787
|
+
confidenceGate: opts.confidenceGate ?? 7,
|
|
788
|
+
lens: opts.lens ?? 'general',
|
|
789
|
+
repairFn,
|
|
790
|
+
onRepairUsed: () => { repairUsed = true; },
|
|
791
|
+
});
|
|
792
|
+
// The tolerant text parser can recover ordinary repair failures, but it
|
|
793
|
+
// must never turn a cancelled or still-running repair into a clean review.
|
|
794
|
+
if (['CANCELLATION_UNCONFIRMED', 'CANCELLATION_TEARDOWN_TIMEOUT'].includes(repairFailure?.code)) {
|
|
795
|
+
repairFailure.usages = usages;
|
|
796
|
+
throw repairFailure;
|
|
797
|
+
}
|
|
798
|
+
const controlSource = repairFailure ?? repairResultForControl;
|
|
799
|
+
const controlError = timedOut
|
|
800
|
+
? new AgentTimeoutError(stepId, Date.now() - startTime)
|
|
801
|
+
: userInterruptAction
|
|
802
|
+
? new UserInterruptError(stepId, userInterruptAction)
|
|
803
|
+
: abortReason ? new AgentAbortedError(stepId, abortReason) : null;
|
|
804
|
+
if (controlError) {
|
|
805
|
+
if (controlSource?.usage) controlError.usage = controlSource.usage;
|
|
806
|
+
controlError.usages = usages;
|
|
807
|
+
throw copyDispatchId(controlSource, controlError);
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
text,
|
|
811
|
+
result: reviewResult,
|
|
812
|
+
usage: usageTotals,
|
|
813
|
+
usages,
|
|
814
|
+
dispatchIds: { primary: primaryDispatchId, repair: repairUsed ? repairDispatchId : null },
|
|
815
|
+
};
|
|
816
|
+
}
|
|
628
817
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
818
|
+
if (!hasStructuredOutput) {
|
|
819
|
+
return {
|
|
820
|
+
text,
|
|
821
|
+
result: null,
|
|
822
|
+
usage: usageTotals,
|
|
823
|
+
usages,
|
|
824
|
+
dispatchIds: { primary: primaryDispatchId, repair: repairDispatchId },
|
|
825
|
+
};
|
|
826
|
+
}
|
|
637
827
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
828
|
+
const result = extractJson(text);
|
|
829
|
+
if (result === null) {
|
|
830
|
+
if (progress) {
|
|
831
|
+
progress.warn('Could not extract JSON from agent output, using fallback');
|
|
832
|
+
} else {
|
|
833
|
+
process.stderr.write(' ⚠ Could not extract JSON from agent output, using fallback\n');
|
|
834
|
+
}
|
|
835
|
+
const summary = text.slice(0, 200).replace(/\n/g, ' ').trim();
|
|
836
|
+
const normalizationFailure = summary || 'Could not extract structured output';
|
|
837
|
+
return {
|
|
838
|
+
text,
|
|
839
|
+
result: { summary: normalizationFailure },
|
|
840
|
+
usage: usageTotals,
|
|
841
|
+
usages,
|
|
842
|
+
normalizationFailure,
|
|
843
|
+
dispatchIds: { primary: primaryDispatchId, repair: repairDispatchId },
|
|
844
|
+
};
|
|
644
845
|
}
|
|
645
|
-
|
|
646
|
-
const normalizationFailure = summary || 'Could not extract structured output';
|
|
846
|
+
|
|
647
847
|
return {
|
|
648
848
|
text,
|
|
649
|
-
result
|
|
849
|
+
result,
|
|
650
850
|
usage: usageTotals,
|
|
651
|
-
|
|
851
|
+
usages,
|
|
652
852
|
dispatchIds: { primary: primaryDispatchId, repair: repairDispatchId },
|
|
653
853
|
};
|
|
854
|
+
} finally {
|
|
855
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
856
|
+
if (onInterrupt && progress?.removeListener) progress.removeListener('interrupt', onInterrupt);
|
|
857
|
+
unsub();
|
|
654
858
|
}
|
|
655
|
-
|
|
656
|
-
return {
|
|
657
|
-
text,
|
|
658
|
-
result,
|
|
659
|
-
usage: usageTotals,
|
|
660
|
-
dispatchIds: { primary: primaryDispatchId, repair: repairDispatchId },
|
|
661
|
-
};
|
|
662
859
|
}
|