@duckcodeailabs/dql-agent 1.14.3-rc.1 → 1.14.4
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/dist/agent-run-engine.d.ts +177 -3
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +2061 -57
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +51 -3
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agent-run-store.d.ts.map +1 -1
- package/dist/agent-run-store.js +544 -9
- package/dist/agent-run-store.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +2 -0
- package/dist/agentic/analyst-loop.d.ts.map +1 -1
- package/dist/agentic/analyst-loop.js +41 -4
- package/dist/agentic/analyst-loop.js.map +1 -1
- package/dist/agentic/ledger-tools.d.ts.map +1 -1
- package/dist/agentic/ledger-tools.js +8 -1
- package/dist/agentic/ledger-tools.js.map +1 -1
- package/dist/agentic/orchestrator-policy.d.ts +3 -1
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -1
- package/dist/agentic/orchestrator-policy.js +1 -1
- package/dist/agentic/orchestrator-policy.js.map +1 -1
- package/dist/agentic/tool-loop.d.ts +13 -1
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +441 -32
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/analytical-frame.d.ts +19 -0
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +170 -12
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +949 -10
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +499 -56
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/answer-loop.d.ts +166 -2
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +654 -18
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +27 -0
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +55 -0
- package/dist/answer-shape.js.map +1 -1
- package/dist/ask-observability/instrumentation.d.ts.map +1 -1
- package/dist/ask-observability/instrumentation.js +52 -1
- package/dist/ask-observability/instrumentation.js.map +1 -1
- package/dist/ask-observability/portable.js +562 -0
- package/dist/ask-observability/portable.js.map +1 -1
- package/dist/ask-observability/types.d.ts +26 -3
- package/dist/ask-observability/types.d.ts.map +1 -1
- package/dist/ask-observability/types.js.map +1 -1
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts +850 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.d.ts.map +1 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js +1716 -0
- package/dist/ask-runtime/ask-agent-runtime-v2.js.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts +95 -0
- package/dist/ask-runtime/ask-analyst-runtime.d.ts.map +1 -0
- package/dist/ask-runtime/ask-analyst-runtime.js +8280 -0
- package/dist/ask-runtime/ask-analyst-runtime.js.map +1 -0
- package/dist/conversation/session-store.d.ts +31 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +38 -0
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +4 -1
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +85 -3
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/conversation/turn-trust.d.ts.map +1 -1
- package/dist/conversation/turn-trust.js +2 -0
- package/dist/conversation/turn-trust.js.map +1 -1
- package/dist/embeddings/provider.d.ts +1 -0
- package/dist/embeddings/provider.d.ts.map +1 -1
- package/dist/embeddings/provider.js +18 -0
- package/dist/embeddings/provider.js.map +1 -1
- package/dist/generated-analytical-proposal.js +37 -2
- package/dist/generated-analytical-proposal.js.map +1 -1
- package/dist/index.d.ts +15 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +77 -1
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +1 -1
- package/dist/intent-controller.js.map +1 -1
- package/dist/kg/build.js +32 -2
- package/dist/kg/build.js.map +1 -1
- package/dist/meaning-resolution.d.ts +129 -0
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +108 -15
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.js +57 -6
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/catalog.d.ts +14 -0
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +28 -1
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +19 -0
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +113 -6
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/provider-egress.d.ts +4 -1
- package/dist/provider-egress.d.ts.map +1 -1
- package/dist/provider-egress.js +50 -5
- package/dist/provider-egress.js.map +1 -1
- package/dist/providers/claude.d.ts +2 -2
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +191 -28
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -2
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +191 -21
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +79 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/requirement-clauses.d.ts +23 -0
- package/dist/requirement-clauses.d.ts.map +1 -0
- package/dist/requirement-clauses.js +70 -0
- package/dist/requirement-clauses.js.map +1 -0
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +201 -15
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +109 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +753 -76
- package/dist/router.js.map +1 -1
- package/package.json +4 -4
package/dist/agent-run-store.js
CHANGED
|
@@ -9,11 +9,28 @@
|
|
|
9
9
|
* a user actually reopens stay complete while history stops growing unbounded.
|
|
10
10
|
*/
|
|
11
11
|
import Database from 'better-sqlite3';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
12
13
|
import { existsSync, mkdirSync, readFileSync, renameSync } from 'node:fs';
|
|
13
14
|
import { dirname, join } from 'node:path';
|
|
14
15
|
import { normalizeProviderEgressReceiptV1 } from '@duckcodeailabs/dql-core';
|
|
15
16
|
const DEFAULT_MAX_RUNS = 300;
|
|
16
17
|
const DEFAULT_FULL_PAYLOAD_RUNS = 50;
|
|
18
|
+
const EVIDENCE_CANDIDATE_ROLES = new Set([
|
|
19
|
+
'metric',
|
|
20
|
+
'entity_key',
|
|
21
|
+
'entity_label',
|
|
22
|
+
'categorical_dimension',
|
|
23
|
+
'time_dimension',
|
|
24
|
+
'member',
|
|
25
|
+
'relationship',
|
|
26
|
+
'context',
|
|
27
|
+
]);
|
|
28
|
+
const ANALYTICAL_TASK_OUTCOME_STATUSES = new Set([
|
|
29
|
+
'completed', 'partial', 'gap', 'blocked', 'dependency_blocked',
|
|
30
|
+
]);
|
|
31
|
+
const ANALYTICAL_TASK_OUTCOME_TRUST_STATES = new Set([
|
|
32
|
+
'certified', 'governed', 'review_required', 'blocked', 'not_applicable',
|
|
33
|
+
]);
|
|
17
34
|
export function resolveAgentRunRetention(env = process.env) {
|
|
18
35
|
const configured = Number(env.DQL_AGENT_RUN_RETENTION);
|
|
19
36
|
if (!Number.isFinite(configured) || configured <= 0)
|
|
@@ -68,7 +85,8 @@ export class SqliteAgentRunStore {
|
|
|
68
85
|
this.compactOldRuns();
|
|
69
86
|
}
|
|
70
87
|
saveProgress(progress) {
|
|
71
|
-
const
|
|
88
|
+
const persistedProgress = sanitizeProgressRoleCoverage(progress);
|
|
89
|
+
const updatedAt = persistedProgress.lifecycle.updatedAt;
|
|
72
90
|
this.db.prepare(`
|
|
73
91
|
INSERT INTO agent_runs (id, question, route, status, started_at, completed_at, compacted, payload_json, updated_at)
|
|
74
92
|
VALUES (?, ?, ?, NULL, ?, NULL, 0, ?, ?)
|
|
@@ -82,7 +100,7 @@ export class SqliteAgentRunStore {
|
|
|
82
100
|
payload_json = excluded.payload_json,
|
|
83
101
|
updated_at = excluded.updated_at
|
|
84
102
|
WHERE agent_runs.completed_at IS NULL
|
|
85
|
-
`).run(
|
|
103
|
+
`).run(persistedProgress.id, persistedProgress.question, persistedProgress.route ?? 'blocked', persistedProgress.lifecycle.startedAt, JSON.stringify(persistedProgress), updatedAt);
|
|
86
104
|
}
|
|
87
105
|
get(id) {
|
|
88
106
|
const row = this.db.prepare('SELECT payload_json FROM agent_runs WHERE id = ?').get(id);
|
|
@@ -127,12 +145,16 @@ export class SqliteAgentRunStore {
|
|
|
127
145
|
const userCancelled = progress.lifecycle.state === 'cancelling'
|
|
128
146
|
|| progress.events.some((event) => event.type === 'run.cancelled');
|
|
129
147
|
const route = progress.route ?? (userCancelled ? 'cancelled' : 'blocked');
|
|
148
|
+
const retainedArtifacts = retainedInterruptedArtifacts(progress, userCancelled);
|
|
149
|
+
const retainedIndependentResult = retainedArtifacts.some((artifact) => artifact.trustState !== 'blocked');
|
|
130
150
|
const failure = {
|
|
131
151
|
code: userCancelled ? 'RUN_CANCELLED' : 'RUN_INTERRUPTED',
|
|
132
152
|
phase: progress.lifecycle.phase,
|
|
133
153
|
message: userCancelled
|
|
134
154
|
? 'Stopped by user.'
|
|
135
|
-
:
|
|
155
|
+
: retainedIndependentResult
|
|
156
|
+
? 'The local DQL runtime restarted before this agent run completed. Completed independent task results remain available for inspection.'
|
|
157
|
+
: 'The local DQL runtime restarted before this agent run completed. No result was accepted.',
|
|
136
158
|
recoverable: !userCancelled,
|
|
137
159
|
safeActions: userCancelled ? [] : ['retry_same_request'],
|
|
138
160
|
};
|
|
@@ -143,16 +165,24 @@ export class SqliteAgentRunStore {
|
|
|
143
165
|
route,
|
|
144
166
|
plan: progress.plan,
|
|
145
167
|
steps: progress.steps,
|
|
146
|
-
artifacts:
|
|
168
|
+
artifacts: retainedArtifacts,
|
|
147
169
|
evaluations: progress.evaluations,
|
|
148
170
|
failure,
|
|
149
171
|
};
|
|
172
|
+
const runtimeReceipt = interruptedRuntimeReceiptV5(progress, userCancelled, route);
|
|
173
|
+
const runtimeReceiptV6 = runtimeReceipt
|
|
174
|
+
? interruptedRuntimeReceiptV6(progress, userCancelled, runtimeReceipt)
|
|
175
|
+
: undefined;
|
|
150
176
|
const diagnosticArtifact = {
|
|
151
177
|
id: `${progress.id}:diagnostic`,
|
|
152
178
|
kind: 'answer',
|
|
153
179
|
title: userCancelled ? 'Cancelled agent run' : 'Interrupted agent run',
|
|
154
180
|
trustState: 'blocked',
|
|
155
|
-
payload: {
|
|
181
|
+
payload: {
|
|
182
|
+
diagnosticReceipt: receipt,
|
|
183
|
+
...(runtimeReceipt ? { diagnosticReceiptV5: runtimeReceipt } : {}),
|
|
184
|
+
...(runtimeReceiptV6 ? { diagnosticReceiptV6: runtimeReceiptV6 } : {}),
|
|
185
|
+
},
|
|
156
186
|
};
|
|
157
187
|
const run = {
|
|
158
188
|
id: progress.id,
|
|
@@ -169,8 +199,8 @@ export class SqliteAgentRunStore {
|
|
|
169
199
|
steps: progress.steps,
|
|
170
200
|
summary: failure.message,
|
|
171
201
|
artifacts: userCancelled
|
|
172
|
-
?
|
|
173
|
-
: [...
|
|
202
|
+
? retainedArtifacts
|
|
203
|
+
: [...retainedArtifacts, diagnosticArtifact],
|
|
174
204
|
evaluations: [
|
|
175
205
|
...progress.evaluations,
|
|
176
206
|
userCancelled
|
|
@@ -209,6 +239,16 @@ export class SqliteAgentRunStore {
|
|
|
209
239
|
repairAttempts: 0,
|
|
210
240
|
escalationAttempts: 0,
|
|
211
241
|
diagnosticReceipt: receipt,
|
|
242
|
+
...(runtimeReceipt ? { diagnosticReceiptV5: runtimeReceipt } : {}),
|
|
243
|
+
...(runtimeReceiptV6 ? { diagnosticReceiptV6: runtimeReceiptV6 } : {}),
|
|
244
|
+
...(progress.askAnalystState ? { askAnalystState: progress.askAnalystState } : {}),
|
|
245
|
+
...(progress.analyticalTaskOutcomes?.length
|
|
246
|
+
? { analyticalTaskOutcomes: progress.analyticalTaskOutcomes }
|
|
247
|
+
: {}),
|
|
248
|
+
...(progress.analyticalTaskOutcomeSummary
|
|
249
|
+
? { analyticalTaskOutcomeSummary: progress.analyticalTaskOutcomeSummary }
|
|
250
|
+
: {}),
|
|
251
|
+
...(progress.traceReference ? { traceReference: progress.traceReference } : {}),
|
|
212
252
|
lifecycle: {
|
|
213
253
|
...progress.lifecycle,
|
|
214
254
|
state: 'terminal',
|
|
@@ -289,6 +329,202 @@ export class SqliteAgentRunStore {
|
|
|
289
329
|
}
|
|
290
330
|
}
|
|
291
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Restart recovery retains the typed continuation state for a deliberate retry,
|
|
334
|
+
* but its diagnostic artifact/export receipt must remain content-free. Never
|
|
335
|
+
* copy the question, filter values, member values, answer, SQL, or rows here.
|
|
336
|
+
*/
|
|
337
|
+
function interruptedRuntimeReceiptV5(progress, cancelled, route) {
|
|
338
|
+
const state = progress.askAnalystState;
|
|
339
|
+
if (!state)
|
|
340
|
+
return undefined;
|
|
341
|
+
const diagnosticState = interruptedDiagnosticState(state);
|
|
342
|
+
const summaryInput = {
|
|
343
|
+
version: 2,
|
|
344
|
+
runtimeMode: state.mode,
|
|
345
|
+
whatHappened: cancelled
|
|
346
|
+
? 'The Ask runtime was cancelled before its final result was accepted.'
|
|
347
|
+
: 'The Ask runtime was interrupted before its final result was accepted.',
|
|
348
|
+
why: cancelled
|
|
349
|
+
? 'The local run was cancelled before finalization.'
|
|
350
|
+
: 'The local DQL runtime restarted before finalization.',
|
|
351
|
+
impact: 'No executable data answer was accepted for this run.',
|
|
352
|
+
nextAction: cancelled ? 'none' : 'retry_same_plan',
|
|
353
|
+
...(state.resolvedPlan?.compiler ? { selectedCompiler: state.resolvedPlan.compiler } : {}),
|
|
354
|
+
programTaskCount: state.program.taskIds.length,
|
|
355
|
+
admittedCandidateCount: state.workspace.admittedCandidateIds.length,
|
|
356
|
+
toolCallCount: state.toolCalls,
|
|
357
|
+
executionAttempts: state.executionAttempts,
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
version: 5,
|
|
361
|
+
runId: progress.id,
|
|
362
|
+
state: diagnosticState,
|
|
363
|
+
summary: {
|
|
364
|
+
...summaryInput,
|
|
365
|
+
summaryFingerprint: `sha256:${createHash('sha256').update(JSON.stringify(summaryInput)).digest('hex')}`,
|
|
366
|
+
},
|
|
367
|
+
businessAnswer: {
|
|
368
|
+
version: 1,
|
|
369
|
+
mode: 'deterministic_fallback',
|
|
370
|
+
trustState: cancelled ? 'not_applicable' : 'blocked',
|
|
371
|
+
factIds: [],
|
|
372
|
+
limitationCount: 1,
|
|
373
|
+
},
|
|
374
|
+
finalStopReason: cancelled ? 'cancelled' : 'blocked',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Restart recovery has no live trace collector, so V6 is projected only from
|
|
379
|
+
* the persisted V2 state and its content-free V5 envelope. Do not infer a
|
|
380
|
+
* connection or SQL attempt from elapsed time/prose: an interrupted execution
|
|
381
|
+
* count is retained separately and the connection boundary remains skipped
|
|
382
|
+
* unless a terminal run receipt recorded it before restart.
|
|
383
|
+
*/
|
|
384
|
+
function interruptedRuntimeReceiptV6(progress, cancelled, receipt) {
|
|
385
|
+
const state = progress.askAnalystState;
|
|
386
|
+
const planning = state.version === 2 || state.version === 3
|
|
387
|
+
? state.planningReceipt
|
|
388
|
+
: undefined;
|
|
389
|
+
const plannerTool = state.workspace.tools.find((tool) => tool.kind === 'provider_meaning');
|
|
390
|
+
const recoveryTool = state.workspace.tools.find((tool) => tool.kind === 'candidate_extension');
|
|
391
|
+
const planFrozen = state.resolvedPlan?.planFrozen === true;
|
|
392
|
+
const verification = planning?.verification ?? {
|
|
393
|
+
version: 1,
|
|
394
|
+
status: state.phase === 'clarify' ? 'ambiguous' : state.phase === 'blocked' ? 'invalid' : 'valid',
|
|
395
|
+
missingRoles: [],
|
|
396
|
+
candidateIds: [],
|
|
397
|
+
reasonCode: state.phase === 'clarify'
|
|
398
|
+
? 'persisted_clarification_state'
|
|
399
|
+
: state.phase === 'blocked'
|
|
400
|
+
? 'persisted_blocked_state'
|
|
401
|
+
: 'persisted_program_state',
|
|
402
|
+
};
|
|
403
|
+
// A durable failed provider tool is a planner attempt too. This restores a
|
|
404
|
+
// truthful concise V6 restart story for V2 checkpoints saved between
|
|
405
|
+
// dispatch and planning-receipt finalization.
|
|
406
|
+
const plannerAttempted = plannerTool?.status === 'completed' || plannerTool?.status === 'failed';
|
|
407
|
+
const plannerCalls = Math.max(planning?.plannerCalls ?? 0, plannerAttempted ? Math.max(1, state.planningContinuations) : 0);
|
|
408
|
+
const revisionCalls = planning?.revisionCalls
|
|
409
|
+
?? (recoveryTool?.status === 'completed' && plannerCalls > 1 ? 1 : 0);
|
|
410
|
+
const planningMode = planning?.mode
|
|
411
|
+
?? (plannerCalls === 0
|
|
412
|
+
? 'deterministic_binding'
|
|
413
|
+
: revisionCalls > 0 ? 'targeted_revision' : 'initial_planner');
|
|
414
|
+
const executionAttempts = state.executionAttempts;
|
|
415
|
+
return {
|
|
416
|
+
...receipt,
|
|
417
|
+
version: 6,
|
|
418
|
+
planning: {
|
|
419
|
+
version: 1,
|
|
420
|
+
mode: planningMode,
|
|
421
|
+
plannerCalls,
|
|
422
|
+
revisionCalls,
|
|
423
|
+
verification,
|
|
424
|
+
},
|
|
425
|
+
// V2 stores count-only admission coverage, so reload can retain the
|
|
426
|
+
// meaningful "required versus admitted" story without exposing labels,
|
|
427
|
+
// prompts, or result values. Older V1 states remain content-free.
|
|
428
|
+
roleCoverage: state.workspace.version === 2
|
|
429
|
+
? (state.workspace.roleCoverage ?? [])
|
|
430
|
+
.filter((entry) => Number.isFinite(entry.candidateCount) && entry.candidateCount >= 0)
|
|
431
|
+
.map((entry) => ({
|
|
432
|
+
role: entry.role,
|
|
433
|
+
candidateCount: entry.candidateCount,
|
|
434
|
+
...(entry.state === 'alternatives' || entry.state === 'proven' ? { state: entry.state } : {}),
|
|
435
|
+
}))
|
|
436
|
+
: [],
|
|
437
|
+
cascade: {
|
|
438
|
+
attempts: [],
|
|
439
|
+
...(state.resolvedPlan?.selectedTier ? { selectedTier: state.resolvedPlan.selectedTier } : {}),
|
|
440
|
+
...(state.resolvedPlan?.compiler && state.resolvedPlan.compiler !== 'none'
|
|
441
|
+
? { stopReason: `persisted_${state.resolvedPlan.compiler}_plan` }
|
|
442
|
+
: {}),
|
|
443
|
+
planFrozen,
|
|
444
|
+
},
|
|
445
|
+
connection: { attempted: false },
|
|
446
|
+
execution: { attempts: executionAttempts },
|
|
447
|
+
facts: { factCount: 0 },
|
|
448
|
+
safeNextAction: receipt.summary.nextAction,
|
|
449
|
+
story: [
|
|
450
|
+
{ stage: 'retrieval', status: state.workspace.snapshotId ? 'completed' : 'skipped', reasonCode: state.workspace.snapshotId ? 'persisted_snapshot' : 'snapshot_not_retained' },
|
|
451
|
+
{
|
|
452
|
+
stage: 'role_coverage',
|
|
453
|
+
status: state.workspace.version === 2 && (state.workspace.roleCoverage?.length ?? 0) > 0
|
|
454
|
+
? 'completed'
|
|
455
|
+
: 'skipped',
|
|
456
|
+
reasonCode: state.workspace.version === 2 && (state.workspace.roleCoverage?.length ?? 0) > 0
|
|
457
|
+
? 'persisted_role_admission_coverage'
|
|
458
|
+
: 'content_free_restart_projection',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
stage: 'planner',
|
|
462
|
+
status: plannerTool?.status === 'failed' ? 'blocked' : plannerCalls > 0 ? 'completed' : 'skipped',
|
|
463
|
+
reasonCode: plannerTool?.reasonCode ?? planningMode,
|
|
464
|
+
},
|
|
465
|
+
{ stage: 'verification', status: verification.status === 'valid' ? 'completed' : 'blocked', reasonCode: verification.reasonCode },
|
|
466
|
+
{ stage: 'targeted_recovery', status: recoveryTool?.status === 'completed' ? 'completed' : 'skipped', reasonCode: recoveryTool?.reasonCode ?? 'not_required' },
|
|
467
|
+
{ stage: 'cascade', status: planFrozen ? 'completed' : state.phase === 'blocked' ? 'blocked' : 'skipped', reasonCode: state.resolvedPlan?.compiler ?? 'no_persisted_compiler' },
|
|
468
|
+
{ stage: 'freeze', status: planFrozen ? 'completed' : 'skipped', reasonCode: planFrozen ? 'persisted_plan_frozen' : 'no_persisted_plan' },
|
|
469
|
+
{ stage: 'connection', status: 'skipped', reasonCode: 'connection_boundary_not_retained_on_restart' },
|
|
470
|
+
{ stage: 'execution', status: executionAttempts > 0 ? 'blocked' : 'skipped', reasonCode: executionAttempts > 0 ? 'interrupted_after_execution_attempt' : 'execution_not_attempted' },
|
|
471
|
+
{ stage: 'facts', status: 'skipped', reasonCode: cancelled ? 'run_cancelled_before_final_facts' : 'run_interrupted_before_final_facts' },
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function interruptedDiagnosticState(state) {
|
|
476
|
+
return {
|
|
477
|
+
version: 1,
|
|
478
|
+
mode: state.mode,
|
|
479
|
+
phase: state.phase,
|
|
480
|
+
questionFingerprint: state.frame.questionFingerprint,
|
|
481
|
+
kind: state.frame.kind,
|
|
482
|
+
requirementCounts: {
|
|
483
|
+
measures: state.frame.requirements.measures.length,
|
|
484
|
+
dimensions: state.frame.requirements.dimensions.length,
|
|
485
|
+
entityTerms: state.frame.requirements.entityTerms.length + state.frame.requirements.entityDisplayTerms.length,
|
|
486
|
+
members: state.frame.requirements.memberTerms.length,
|
|
487
|
+
filters: state.program.filters.length,
|
|
488
|
+
},
|
|
489
|
+
mission: {
|
|
490
|
+
mode: state.mission.mode,
|
|
491
|
+
taskCount: state.mission.tasks.length,
|
|
492
|
+
deferredTaskCount: state.mission.deferredTasks?.length ?? 0,
|
|
493
|
+
hypothesisCount: state.mission.hypotheses.length,
|
|
494
|
+
},
|
|
495
|
+
workspace: {
|
|
496
|
+
...(state.workspace.snapshotId ? { snapshotId: state.workspace.snapshotId } : {}),
|
|
497
|
+
...(state.workspace.sourceFingerprint ? { sourceFingerprint: state.workspace.sourceFingerprint } : {}),
|
|
498
|
+
admittedCandidateCount: state.workspace.admittedCandidateIds.length,
|
|
499
|
+
excludedCandidateCount: state.workspace.excludedCandidates.length,
|
|
500
|
+
sourceCoverage: state.workspace.sourceCoverage.map((coverage) => ({
|
|
501
|
+
source: coverage.source,
|
|
502
|
+
status: coverage.status,
|
|
503
|
+
candidateCount: coverage.candidateIds.length,
|
|
504
|
+
})),
|
|
505
|
+
tools: state.workspace.tools.map((tool) => ({
|
|
506
|
+
id: tool.id,
|
|
507
|
+
kind: tool.kind,
|
|
508
|
+
status: tool.status,
|
|
509
|
+
reasonCode: tool.reasonCode,
|
|
510
|
+
})),
|
|
511
|
+
},
|
|
512
|
+
program: {
|
|
513
|
+
id: state.program.id,
|
|
514
|
+
taskCount: state.program.taskIds.length,
|
|
515
|
+
candidateCount: state.program.candidateIds.length,
|
|
516
|
+
requiredRoles: state.program.requiredRoles,
|
|
517
|
+
outputAssertionCount: state.program.outputs.assertions.length,
|
|
518
|
+
},
|
|
519
|
+
...(state.resolvedPlan ? { resolvedPlan: state.resolvedPlan } : {}),
|
|
520
|
+
counters: {
|
|
521
|
+
planningContinuations: state.planningContinuations,
|
|
522
|
+
toolCalls: state.toolCalls,
|
|
523
|
+
executionAttempts: state.executionAttempts,
|
|
524
|
+
repairAttempts: state.repairAttempts,
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
}
|
|
292
528
|
export function defaultAgentRunSqlitePath(projectRoot) {
|
|
293
529
|
return join(projectRoot, '.dql', 'local', 'agent-runs.sqlite');
|
|
294
530
|
}
|
|
@@ -333,24 +569,323 @@ function normalizeRunProviderEgressReceipts(run) {
|
|
|
333
569
|
? artifact
|
|
334
570
|
: { ...artifact, payload: { ...payload, diagnosticReceipt: normalizedDiagnosticReceipt } };
|
|
335
571
|
});
|
|
572
|
+
const askAnalystState = sanitizeAskAnalystStateRoleCoverage(run.askAnalystState);
|
|
573
|
+
const taskReceipts = sanitizeTaskOutcomeReceipts({
|
|
574
|
+
outcomes: run.analyticalTaskOutcomes,
|
|
575
|
+
summary: run.analyticalTaskOutcomeSummary,
|
|
576
|
+
steps: run.steps,
|
|
577
|
+
});
|
|
578
|
+
// Do not spread untrusted V3 receipts back onto the returned object. Older
|
|
579
|
+
// V1/V2 runs simply omit the additive fields; malformed V3 JSON is stripped
|
|
580
|
+
// or reduced to a blocked receipt before the notebook/API can render it.
|
|
581
|
+
const { analyticalTaskOutcomes: _rawTaskOutcomes, analyticalTaskOutcomeSummary: _rawTaskOutcomeSummary, ...durableRun } = run;
|
|
336
582
|
return {
|
|
337
|
-
...
|
|
583
|
+
...durableRun,
|
|
338
584
|
...(rootReceipts === undefined ? {} : { providerEgressReceipts: rootReceipts }),
|
|
339
585
|
...(diagnosticReceipt === run.diagnosticReceipt
|
|
340
586
|
? {}
|
|
341
587
|
: { diagnosticReceipt: diagnosticReceipt }),
|
|
342
588
|
artifacts,
|
|
589
|
+
...(askAnalystState === run.askAnalystState ? {} : { askAnalystState }),
|
|
590
|
+
...(taskReceipts.outcomes ? { analyticalTaskOutcomes: taskReceipts.outcomes } : {}),
|
|
591
|
+
...(taskReceipts.summary ? { analyticalTaskOutcomeSummary: taskReceipts.summary } : {}),
|
|
343
592
|
};
|
|
344
593
|
}
|
|
345
594
|
function parseProgress(payload) {
|
|
346
595
|
try {
|
|
347
596
|
const value = JSON.parse(payload);
|
|
348
|
-
return isAgentRunProgressRecord(value) ? value : undefined;
|
|
597
|
+
return isAgentRunProgressRecord(value) ? sanitizeProgressRoleCoverage(value) : undefined;
|
|
349
598
|
}
|
|
350
599
|
catch {
|
|
351
600
|
return undefined;
|
|
352
601
|
}
|
|
353
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* V2 role coverage is an observability summary, not a payload lane. Persist
|
|
605
|
+
* only the typed count tuples so an injected label, raw query fragment, or
|
|
606
|
+
* result value cannot survive a restart through the Ask state or diagnostic
|
|
607
|
+
* projection. The rest of the local continuation state remains intact.
|
|
608
|
+
*/
|
|
609
|
+
function sanitizeAskAnalystStateRoleCoverage(state) {
|
|
610
|
+
if (!state || (state.version !== 2 && state.version !== 3) || !Array.isArray(state.workspace.roleCoverage))
|
|
611
|
+
return state;
|
|
612
|
+
const roleCoverage = state.workspace.roleCoverage.flatMap((entry) => {
|
|
613
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
614
|
+
return [];
|
|
615
|
+
const record = entry;
|
|
616
|
+
if (typeof record.role !== 'string'
|
|
617
|
+
|| !EVIDENCE_CANDIDATE_ROLES.has(record.role)
|
|
618
|
+
|| !Number.isFinite(record.candidateCount)
|
|
619
|
+
|| Number(record.candidateCount) < 0)
|
|
620
|
+
return [];
|
|
621
|
+
const coverageState = record.state === 'alternatives' || record.state === 'proven'
|
|
622
|
+
? record.state
|
|
623
|
+
: undefined;
|
|
624
|
+
return [{
|
|
625
|
+
role: record.role,
|
|
626
|
+
candidateCount: Math.floor(Number(record.candidateCount)),
|
|
627
|
+
...(coverageState ? { state: coverageState } : {}),
|
|
628
|
+
}];
|
|
629
|
+
});
|
|
630
|
+
return {
|
|
631
|
+
...state,
|
|
632
|
+
workspace: {
|
|
633
|
+
...state.workspace,
|
|
634
|
+
roleCoverage,
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function sanitizeProgressRoleCoverage(progress) {
|
|
639
|
+
const askAnalystState = sanitizeAskAnalystStateRoleCoverage(progress.askAnalystState);
|
|
640
|
+
const taskReceipts = sanitizeTaskOutcomeReceipts({
|
|
641
|
+
outcomes: progress.analyticalTaskOutcomes,
|
|
642
|
+
summary: progress.analyticalTaskOutcomeSummary,
|
|
643
|
+
steps: progress.steps,
|
|
644
|
+
});
|
|
645
|
+
const { analyticalTaskOutcomes: _rawTaskOutcomes, analyticalTaskOutcomeSummary: _rawTaskOutcomeSummary, ...durableProgress } = progress;
|
|
646
|
+
return {
|
|
647
|
+
...durableProgress,
|
|
648
|
+
...(askAnalystState === progress.askAnalystState ? {} : { askAnalystState }),
|
|
649
|
+
...(taskReceipts.outcomes ? { analyticalTaskOutcomes: taskReceipts.outcomes } : {}),
|
|
650
|
+
...(taskReceipts.summary ? { analyticalTaskOutcomeSummary: taskReceipts.summary } : {}),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function isDurableString(value) {
|
|
654
|
+
return typeof value === 'string' && value.trim().length > 0 && value.length <= 1_000;
|
|
655
|
+
}
|
|
656
|
+
function durableStringList(value, maxItems = 32) {
|
|
657
|
+
if (!Array.isArray(value))
|
|
658
|
+
return [];
|
|
659
|
+
return [...new Set(value.filter(isDurableString).map((item) => item.trim()))].slice(0, maxItems);
|
|
660
|
+
}
|
|
661
|
+
function durableRecord(value) {
|
|
662
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
663
|
+
? value
|
|
664
|
+
: undefined;
|
|
665
|
+
}
|
|
666
|
+
function taskReceiptTrustForArtifact(value) {
|
|
667
|
+
if (value === 'grounded')
|
|
668
|
+
return 'governed';
|
|
669
|
+
return ANALYTICAL_TASK_OUTCOME_TRUST_STATES.has(value)
|
|
670
|
+
? value
|
|
671
|
+
: undefined;
|
|
672
|
+
}
|
|
673
|
+
function canonicalTaskResultProofForArtifact(value) {
|
|
674
|
+
const artifact = durableRecord(value);
|
|
675
|
+
const trustState = taskReceiptTrustForArtifact(artifact?.trustState);
|
|
676
|
+
if (!artifact || artifact.kind !== 'answer' || !trustState || trustState === 'blocked')
|
|
677
|
+
return undefined;
|
|
678
|
+
const payload = durableRecord(artifact.payload);
|
|
679
|
+
const result = durableRecord(payload?.result);
|
|
680
|
+
const fingerprint = result?.resultFingerprint;
|
|
681
|
+
if (!result || !isDurableString(fingerprint)
|
|
682
|
+
|| !Array.isArray(result.columns) || result.columns.length === 0
|
|
683
|
+
|| !Array.isArray(result.rows))
|
|
684
|
+
return undefined;
|
|
685
|
+
return { resultFingerprint: fingerprint, trustState };
|
|
686
|
+
}
|
|
687
|
+
function canonicalTaskResultProofsByTask(steps) {
|
|
688
|
+
const byTask = new Map();
|
|
689
|
+
if (!Array.isArray(steps))
|
|
690
|
+
return byTask;
|
|
691
|
+
for (const rawStep of steps) {
|
|
692
|
+
const step = durableRecord(rawStep);
|
|
693
|
+
const taskId = step?.askAnalystTaskId;
|
|
694
|
+
if (!step || !isDurableString(taskId) || !Array.isArray(step.artifacts))
|
|
695
|
+
continue;
|
|
696
|
+
const proofs = step.artifacts
|
|
697
|
+
.map(canonicalTaskResultProofForArtifact)
|
|
698
|
+
.filter((proof) => Boolean(proof));
|
|
699
|
+
if (proofs.length > 0) {
|
|
700
|
+
const byFingerprint = new Map();
|
|
701
|
+
for (const proof of proofs) {
|
|
702
|
+
const existing = byFingerprint.get(proof.resultFingerprint);
|
|
703
|
+
byFingerprint.set(proof.resultFingerprint, existing
|
|
704
|
+
? leastTrustedTaskReceiptState([existing, proof.trustState])
|
|
705
|
+
: proof.trustState);
|
|
706
|
+
}
|
|
707
|
+
byTask.set(taskId, byFingerprint);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return byTask;
|
|
711
|
+
}
|
|
712
|
+
function sanitizeTaskOutcomeFailure(value) {
|
|
713
|
+
const failure = durableRecord(value);
|
|
714
|
+
if (!failure || !isDurableString(failure.code) || !isDurableString(failure.message))
|
|
715
|
+
return undefined;
|
|
716
|
+
if (failure.phase !== 'planning' && failure.phase !== 'execution' && failure.phase !== 'dependency')
|
|
717
|
+
return undefined;
|
|
718
|
+
return { version: 1, code: failure.code, message: failure.message, phase: failure.phase };
|
|
719
|
+
}
|
|
720
|
+
function sanitizeTaskOutcomeGap(value) {
|
|
721
|
+
const gap = durableRecord(value);
|
|
722
|
+
const allowedCodes = new Set([
|
|
723
|
+
'MISSING_MEASURE', 'MISSING_DIMENSION', 'MISSING_ATTRIBUTE', 'MISSING_RELATIONSHIP',
|
|
724
|
+
'MISSING_RUNTIME_CAPABILITY', 'RESULT_CONTRACT_MISMATCH', 'PROVIDER_UNAVAILABLE',
|
|
725
|
+
'EXECUTION_FAILED', 'AMBIGUOUS_MEANING', 'POLICY_BLOCKED',
|
|
726
|
+
]);
|
|
727
|
+
const allowedPhases = new Set([
|
|
728
|
+
'retrieval', 'meaning', 'planning', 'compilation', 'execution', 'presentation',
|
|
729
|
+
]);
|
|
730
|
+
const allowedRoutes = new Set([
|
|
731
|
+
'certified', 'semantic', 'governed_relational', 'generated', 'research',
|
|
732
|
+
]);
|
|
733
|
+
if (!gap || !allowedCodes.has(gap.code)
|
|
734
|
+
|| !allowedPhases.has(gap.phase)
|
|
735
|
+
|| !isDurableString(gap.message) || typeof gap.recoverable !== 'boolean'
|
|
736
|
+
|| typeof gap.planFrozen !== 'boolean')
|
|
737
|
+
return undefined;
|
|
738
|
+
const attemptedRoutes = Array.isArray(gap.attemptedRoutes)
|
|
739
|
+
? gap.attemptedRoutes.filter((route) => typeof route === 'string' && allowedRoutes.has(route))
|
|
740
|
+
: [];
|
|
741
|
+
return {
|
|
742
|
+
version: 1,
|
|
743
|
+
code: gap.code,
|
|
744
|
+
phase: gap.phase,
|
|
745
|
+
message: gap.message,
|
|
746
|
+
searchedSources: durableStringList(gap.searchedSources),
|
|
747
|
+
attemptedRoutes,
|
|
748
|
+
missing: durableStringList(gap.missing),
|
|
749
|
+
recoverable: gap.recoverable,
|
|
750
|
+
planFrozen: gap.planFrozen,
|
|
751
|
+
nextActions: durableStringList(gap.nextActions),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function leastTrustedTaskReceiptState(states) {
|
|
755
|
+
if (states.length === 0)
|
|
756
|
+
return 'blocked';
|
|
757
|
+
const score = {
|
|
758
|
+
certified: 4, governed: 3, review_required: 2, not_applicable: 1, blocked: 0,
|
|
759
|
+
};
|
|
760
|
+
return states.reduce((least, candidate) => score[candidate] < score[least] ? candidate : least);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Read local V3 task receipts as untrusted JSON. Successful status is valid
|
|
764
|
+
* only when the same task step contains a canonical immutable result artifact
|
|
765
|
+
* with the advertised fingerprint. This also keeps V1/V2 records readable:
|
|
766
|
+
* absent additive fields remain absent.
|
|
767
|
+
*/
|
|
768
|
+
function sanitizeTaskOutcomeReceipts(input) {
|
|
769
|
+
const rawSummary = durableRecord(input.summary);
|
|
770
|
+
const rawOutcomes = Array.isArray(input.outcomes) ? input.outcomes : undefined;
|
|
771
|
+
if (!rawOutcomes && !rawSummary)
|
|
772
|
+
return {};
|
|
773
|
+
const canonicalByTask = canonicalTaskResultProofsByTask(input.steps);
|
|
774
|
+
const outcomes = [];
|
|
775
|
+
const seenTaskIds = new Set();
|
|
776
|
+
for (const rawOutcome of rawOutcomes ?? []) {
|
|
777
|
+
const outcome = durableRecord(rawOutcome);
|
|
778
|
+
const taskId = outcome?.taskId;
|
|
779
|
+
const status = outcome?.status;
|
|
780
|
+
if (!outcome || !isDurableString(taskId)
|
|
781
|
+
|| !ANALYTICAL_TASK_OUTCOME_STATUSES.has(status)
|
|
782
|
+
|| seenTaskIds.has(taskId))
|
|
783
|
+
continue;
|
|
784
|
+
seenTaskIds.add(taskId);
|
|
785
|
+
const persistedTrustState = ANALYTICAL_TASK_OUTCOME_TRUST_STATES.has(outcome.trustState)
|
|
786
|
+
? outcome.trustState
|
|
787
|
+
: undefined;
|
|
788
|
+
const requestedSuccess = status === 'completed' || status === 'partial';
|
|
789
|
+
const resultFingerprint = isDurableString(outcome.resultFingerprint) ? outcome.resultFingerprint : undefined;
|
|
790
|
+
const canonicalTrustState = resultFingerprint
|
|
791
|
+
? canonicalByTask.get(taskId)?.get(resultFingerprint)
|
|
792
|
+
: undefined;
|
|
793
|
+
const canonicalResult = Boolean(canonicalTrustState);
|
|
794
|
+
if (requestedSuccess && !canonicalResult) {
|
|
795
|
+
outcomes.push({
|
|
796
|
+
version: 1,
|
|
797
|
+
taskId,
|
|
798
|
+
status: 'blocked',
|
|
799
|
+
trustState: 'blocked',
|
|
800
|
+
summary: 'This persisted task result has no matching immutable canonical artifact.',
|
|
801
|
+
failure: {
|
|
802
|
+
version: 1,
|
|
803
|
+
code: 'TASK_RESULT_ARTIFACT_UNVERIFIED',
|
|
804
|
+
message: 'This persisted task result has no matching immutable canonical artifact.',
|
|
805
|
+
phase: 'execution',
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
continue;
|
|
809
|
+
}
|
|
810
|
+
const trustState = canonicalTrustState
|
|
811
|
+
? persistedTrustState
|
|
812
|
+
? leastTrustedTaskReceiptState([persistedTrustState, canonicalTrustState])
|
|
813
|
+
: canonicalTrustState
|
|
814
|
+
: persistedTrustState ?? 'blocked';
|
|
815
|
+
outcomes.push({
|
|
816
|
+
version: 1,
|
|
817
|
+
taskId,
|
|
818
|
+
status: status,
|
|
819
|
+
trustState,
|
|
820
|
+
...(isDurableString(outcome.summary) ? { summary: outcome.summary } : {}),
|
|
821
|
+
...(canonicalResult && resultFingerprint ? { resultFingerprint } : {}),
|
|
822
|
+
...(sanitizeTaskOutcomeGap(outcome.gap) ? { gap: sanitizeTaskOutcomeGap(outcome.gap) } : {}),
|
|
823
|
+
...(sanitizeTaskOutcomeFailure(outcome.failure) ? { failure: sanitizeTaskOutcomeFailure(outcome.failure) } : {}),
|
|
824
|
+
...(status === 'dependency_blocked' ? { dependencyTaskIds: durableStringList(outcome.dependencyTaskIds) } : {}),
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
const requestedTaskCount = typeof rawSummary?.taskCount === 'number' && Number.isFinite(rawSummary.taskCount)
|
|
828
|
+
? Math.max(0, Math.min(100, Math.floor(rawSummary.taskCount)))
|
|
829
|
+
: 0;
|
|
830
|
+
// Preserve an execution-pending compiler checkpoint only when it makes no
|
|
831
|
+
// success claim. It becomes execution-final after the first child step.
|
|
832
|
+
const mayKeepEmptySummary = outcomes.length === 0
|
|
833
|
+
&& requestedTaskCount > 0
|
|
834
|
+
&& durableStringList(rawSummary?.successfulTaskIds).length === 0;
|
|
835
|
+
if (outcomes.length === 0 && !mayKeepEmptySummary)
|
|
836
|
+
return {};
|
|
837
|
+
const successfulTaskIds = outcomes
|
|
838
|
+
.filter((outcome) => outcome.status === 'completed' || outcome.status === 'partial')
|
|
839
|
+
.map((outcome) => outcome.taskId);
|
|
840
|
+
const failedTaskIds = outcomes
|
|
841
|
+
.filter((outcome) => outcome.status !== 'completed' && outcome.status !== 'partial' && outcome.status !== 'dependency_blocked')
|
|
842
|
+
.map((outcome) => outcome.taskId);
|
|
843
|
+
const dependencyBlockedTaskIds = outcomes
|
|
844
|
+
.filter((outcome) => outcome.status === 'dependency_blocked')
|
|
845
|
+
.map((outcome) => outcome.taskId);
|
|
846
|
+
const taskCount = Math.max(requestedTaskCount, outcomes.length);
|
|
847
|
+
const summary = {
|
|
848
|
+
version: 1,
|
|
849
|
+
status: successfulTaskIds.length === 0
|
|
850
|
+
? 'blocked'
|
|
851
|
+
: failedTaskIds.length > 0 || dependencyBlockedTaskIds.length > 0 || successfulTaskIds.length < taskCount
|
|
852
|
+
? 'partial'
|
|
853
|
+
: 'completed',
|
|
854
|
+
trustState: leastTrustedTaskReceiptState(outcomes
|
|
855
|
+
.filter((outcome) => outcome.status === 'completed' || outcome.status === 'partial')
|
|
856
|
+
.map((outcome) => outcome.trustState ?? 'blocked')),
|
|
857
|
+
taskCount,
|
|
858
|
+
successfulTaskIds,
|
|
859
|
+
failedTaskIds,
|
|
860
|
+
dependencyBlockedTaskIds,
|
|
861
|
+
};
|
|
862
|
+
return {
|
|
863
|
+
...(outcomes.length > 0 ? { outcomes } : {}),
|
|
864
|
+
summary,
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* A restart never turns an in-flight Ask into a completed parent answer, but
|
|
869
|
+
* it must not erase a completed independent child whose canonical result was
|
|
870
|
+
* checkpointed before the interruption. Keep only blocked diagnostics plus
|
|
871
|
+
* artifact/result pairs proved by the V3 task receipt.
|
|
872
|
+
*/
|
|
873
|
+
function retainedInterruptedArtifacts(progress, userCancelled) {
|
|
874
|
+
if (userCancelled)
|
|
875
|
+
return progress.artifacts;
|
|
876
|
+
const retainedIds = new Set(progress.artifacts
|
|
877
|
+
.filter((artifact) => artifact.trustState === 'blocked')
|
|
878
|
+
.map((artifact) => artifact.id));
|
|
879
|
+
for (const outcome of progress.analyticalTaskOutcomes ?? []) {
|
|
880
|
+
if ((outcome.status !== 'completed' && outcome.status !== 'partial') || !outcome.resultFingerprint)
|
|
881
|
+
continue;
|
|
882
|
+
const step = progress.steps.find((candidate) => candidate.askAnalystTaskId === outcome.taskId);
|
|
883
|
+
const artifact = step?.artifacts.find((candidate) => canonicalTaskResultProofForArtifact(candidate)?.resultFingerprint === outcome.resultFingerprint);
|
|
884
|
+
if (artifact)
|
|
885
|
+
retainedIds.add(artifact.id);
|
|
886
|
+
}
|
|
887
|
+
return progress.artifacts.filter((artifact) => retainedIds.has(artifact.id));
|
|
888
|
+
}
|
|
354
889
|
function isAgentRunRecord(value) {
|
|
355
890
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
356
891
|
return false;
|