@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.1-beta.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/AGENTS.md +2 -6
- package/CHANGELOG.md +72 -163
- package/README.md +1 -1
- package/bin/loop-agent.js +1 -37
- package/dist/application/task-lifecycle/advance.js +0 -1
- package/dist/application/task-lifecycle/observe.js +0 -15
- package/dist/application/task-lifecycle/plan-transitions.js +0 -15
- package/dist/cli/command-definitions.js +0 -7
- package/dist/cli/program.js +3 -8
- package/dist/commands/init-upgrade.js +19 -351
- package/dist/commands/init.js +67 -14
- package/dist/commands/run-dag-progress.js +0 -14
- package/dist/commands/task-advance.js +3 -33
- package/dist/executors/dag-pi-executor.js +30 -47
- package/dist/executors/pi-sdk-executor.js +0 -34
- package/dist/executors/shell-executor.js +95 -6
- package/dist/infrastructure/harness/atomic-write.js +1 -2
- package/dist/shared/operator/capabilities.js +13 -200
- package/dist/shared/package-metadata.js +0 -42
- package/dist/task/frontend-project-capability.js +12 -162
- package/dist/task/source-prepare/completeness.js +0 -17
- package/dist/task/source-prepare/parse-intent.js +1 -15
- package/dist/task/source-prepare/semantic-intake.js +23 -144
- package/dist/worker/console/chat/artifact-card.js +1 -8
- package/dist/worker/console/chat/chat-event-store.js +0 -61
- package/dist/worker/console/chat/human-gate-card.js +1 -9
- package/dist/worker/console/chat/operation-card.js +2 -71
- package/dist/worker/console/chat/pi-runtime.js +62 -86
- package/dist/worker/console/chat/routes.js +8 -36
- package/dist/worker/console/chat/session-store.js +0 -3
- package/dist/worker/console/chat/shortcuts.js +7 -9
- package/dist/worker/console/chat/turn-process.js +23 -97
- package/dist/worker/console/chat/workspace-landing.js +1 -2
- package/dist/worker/console/operation-runner.js +6 -155
- package/dist/worker/console/operator-actions.js +13 -373
- package/dist/worker/console/operator-user-error.js +0 -4
- package/dist/worker/console/recovery-cta.js +3 -50
- package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
- package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -13
- package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
- package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -89
- package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +11 -30
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
- package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
- package/dist/worker/loop-agent/loop-agent-client.js +3 -17
- package/dist/worker/observability/read-model.js +0 -20
- package/dist/worker/observe/spec-evidence.js +8 -3
- package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
- package/dist/worker/observe/static/operator-chrome.js +1 -6
- package/dist/worker/observe/static/views/dag-inspector.js +71 -6
- package/dist/worker/observe/static/views/dag.js +0 -12
- package/dist/worker/preflight.js +1 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
- package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
- package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
- package/dist/workflows/dag/failure-routing.js +4 -9
- package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
- package/dist/workflows/dag/frontend-prewrite-gate.js +62 -394
- package/dist/workflows/dag/frontend-repair.js +18 -219
- package/dist/workflows/dag/frontend-verification-trace.js +32 -47
- package/dist/workflows/dag/init-hybrid.js +86 -238
- package/dist/workflows/dag/lifecycle.js +0 -4
- package/dist/workflows/dag/node-execution.js +6 -105
- package/dist/workflows/dag/recovery-recommendation.js +0 -58
- package/dist/workflows/dag/report.js +0 -6
- package/dist/workflows/dag/retry-policy.js +0 -11
- package/dist/workflows/dag/runner.js +12 -314
- package/dist/workflows/dag/scheduler.js +3 -257
- package/dist/workflows/dag/types.js +9 -132
- package/dist/workflows/dag/validate.js +2 -7
- package/docs/README.md +3 -3
- package/docs/architecture/evolution.md +67 -102
- package/docs/templates/backend-test-dag.json +50 -32
- package/docs/templates/frontend-design-contract.md +14 -34
- package/docs/templates/frontend-task-constraints.md +13 -33
- package/docs/templates/frontend-task-requirement.md +20 -54
- package/docs/templates/init-managed-agents.md +2 -5
- package/harness.json +2 -2
- package/package.json +3 -4
- package/skills/loop-agent/SKILL.md +0 -1
- package/skills/loop-agent/references/command-reference.md +0 -3
- package/dist/build-stamp.json +0 -6
- package/dist/commands/dag-request-interrupt.js +0 -20
- package/dist/worker/console/chat/assistant-content.js +0 -121
- package/dist/worker/console/chat/semantic-activity.js +0 -471
- package/dist/worker/console/operation-run-facts.js +0 -190
- package/dist/worker/console/operation-wait.js +0 -355
- package/dist/worker/console/recovery-error-copy.js +0 -198
- package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
- package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- package/dist/worker/observability/interrupt-eligibility.js +0 -264
- package/dist/workflows/dag/contract-output-registry.js +0 -14
- package/dist/workflows/dag/contract-validator-registrations.js +0 -8
- package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
- package/dist/workflows/dag/frontend-recovery-run.js +0 -539
- package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
- package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
- package/dist/workflows/dag/interrupt-request.js +0 -559
|
@@ -13,7 +13,6 @@ import { z } from "zod";
|
|
|
13
13
|
import { executePiStep } from "../../executors/pi-executor.js";
|
|
14
14
|
import { resolveDagPiModelConfig } from "../../executors/dag-pi-executor.js";
|
|
15
15
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
16
|
-
import { listPiSdkAvailableModels } from "../../executors/pi-sdk-executor.js";
|
|
17
16
|
import { loadHarnessManifest } from "../../governance/harness.js";
|
|
18
17
|
import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
|
|
19
18
|
import { getTaskPaths } from "../runtime.js";
|
|
@@ -21,14 +20,6 @@ import { detectProductArtifactMeta } from "./artifact-meta.js";
|
|
|
21
20
|
import { filterPlaceholderPaths } from "./placeholder-paths.js";
|
|
22
21
|
export const SEMANTIC_INTAKE_ARTIFACT_REL = "artifacts/intake/semantic-draft.json";
|
|
23
22
|
export const SEMANTIC_INTAKE_ATTEMPT_MARKER = "artifacts/intake/semantic-intake-attempted.json";
|
|
24
|
-
/** Per-model wall clock for one semantic-intake Pi attempt. */
|
|
25
|
-
export const SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS = 300_000;
|
|
26
|
-
/** Extra Pi-catalog models after MED → HIGH → LOW. */
|
|
27
|
-
export const SEMANTIC_INTAKE_MAX_EXTRA_MODELS = 5;
|
|
28
|
-
/** Parent CLI budget covering harness tiers plus extra catalog models. */
|
|
29
|
-
export const SEMANTIC_INTAKE_CLI_TIMEOUT_MS = SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS *
|
|
30
|
-
(3 + SEMANTIC_INTAKE_MAX_EXTRA_MODELS);
|
|
31
|
-
const NON_CHAT_MODEL = /embed|whisper|tts|dall-e|dalle|image|rerank|moderation/i;
|
|
32
23
|
const productArtifactBlockers = new Set([
|
|
33
24
|
"PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
34
25
|
"PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
@@ -135,58 +126,6 @@ export function evaluateSemanticIntakeEligibility(input) {
|
|
|
135
126
|
reason: "structural or engineering-boundary gaps with imported sources",
|
|
136
127
|
};
|
|
137
128
|
}
|
|
138
|
-
function intakeModelKey(ref) {
|
|
139
|
-
return ref.trim().replace(/\\/g, "/").toLowerCase();
|
|
140
|
-
}
|
|
141
|
-
function intakeModelRef(provider, id) {
|
|
142
|
-
const trimmedId = id.trim();
|
|
143
|
-
if (trimmedId.includes("/"))
|
|
144
|
-
return trimmedId.replace(/\\/g, "/");
|
|
145
|
-
return `${provider.trim()}/${trimmedId}`;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* MED → HIGH → LOW from harness, then Pi-supported catalog models
|
|
149
|
-
* (credentialed first). Duplicates and non-chat ids are skipped.
|
|
150
|
-
*/
|
|
151
|
-
export function buildSemanticIntakeModelQueue(input) {
|
|
152
|
-
const seen = new Set();
|
|
153
|
-
const queue = [];
|
|
154
|
-
const push = (raw) => {
|
|
155
|
-
const ref = raw?.trim().replace(/\\/g, "/");
|
|
156
|
-
if (!ref)
|
|
157
|
-
return;
|
|
158
|
-
const key = intakeModelKey(ref);
|
|
159
|
-
if (seen.has(key))
|
|
160
|
-
return;
|
|
161
|
-
seen.add(key);
|
|
162
|
-
queue.push(ref);
|
|
163
|
-
};
|
|
164
|
-
push(input.harness.MED);
|
|
165
|
-
push(input.harness.HIGH);
|
|
166
|
-
push(input.harness.LOW);
|
|
167
|
-
const extras = [...(input.available ?? [])]
|
|
168
|
-
.filter((entry) => {
|
|
169
|
-
const hay = `${entry.id} ${entry.name ?? ""} ${entry.provider}`;
|
|
170
|
-
return !NON_CHAT_MODEL.test(hay);
|
|
171
|
-
})
|
|
172
|
-
.sort((left, right) => {
|
|
173
|
-
const leftCred = left.hasCredentials === true ? 0 : 1;
|
|
174
|
-
const rightCred = right.hasCredentials === true ? 0 : 1;
|
|
175
|
-
if (leftCred !== rightCred)
|
|
176
|
-
return leftCred - rightCred;
|
|
177
|
-
return intakeModelRef(left.provider, left.id).localeCompare(intakeModelRef(right.provider, right.id));
|
|
178
|
-
});
|
|
179
|
-
let extraCount = 0;
|
|
180
|
-
for (const entry of extras) {
|
|
181
|
-
if (extraCount >= SEMANTIC_INTAKE_MAX_EXTRA_MODELS)
|
|
182
|
-
break;
|
|
183
|
-
const before = queue.length;
|
|
184
|
-
push(intakeModelRef(entry.provider, entry.id));
|
|
185
|
-
if (queue.length > before)
|
|
186
|
-
extraCount += 1;
|
|
187
|
-
}
|
|
188
|
-
return queue;
|
|
189
|
-
}
|
|
190
129
|
export function extractJsonObject(text) {
|
|
191
130
|
const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(text);
|
|
192
131
|
const candidate = fenced?.[1]?.trim() || text.trim();
|
|
@@ -554,7 +493,6 @@ export async function runSemanticIntake(input) {
|
|
|
554
493
|
};
|
|
555
494
|
}
|
|
556
495
|
let assistantText;
|
|
557
|
-
let attemptedModels = [];
|
|
558
496
|
if (input.fixtureAssistantText !== undefined) {
|
|
559
497
|
assistantText = input.fixtureAssistantText;
|
|
560
498
|
}
|
|
@@ -562,33 +500,7 @@ export async function runSemanticIntake(input) {
|
|
|
562
500
|
const execute = input.executePi ?? executePiStep;
|
|
563
501
|
const manifest = await loadHarnessManifest(input.repoRoot);
|
|
564
502
|
const models = resolveExecutorModelMatrices(manifest);
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
available = input.listAvailableModels
|
|
568
|
-
? await input.listAvailableModels()
|
|
569
|
-
: await listPiSdkAvailableModels();
|
|
570
|
-
}
|
|
571
|
-
catch {
|
|
572
|
-
available = [];
|
|
573
|
-
}
|
|
574
|
-
const queue = buildSemanticIntakeModelQueue({
|
|
575
|
-
harness: models.pi,
|
|
576
|
-
available,
|
|
577
|
-
});
|
|
578
|
-
if (queue.length === 0) {
|
|
579
|
-
await writeAttemptMarker(input.repoRoot, input.taskId, {
|
|
580
|
-
ok: false,
|
|
581
|
-
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
582
|
-
failureCategory: "unavailable",
|
|
583
|
-
at: new Date().toISOString(),
|
|
584
|
-
attemptedModels: [],
|
|
585
|
-
});
|
|
586
|
-
return {
|
|
587
|
-
ok: false,
|
|
588
|
-
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
589
|
-
message: "semantic intake has no MED/HIGH/LOW or Pi-supported models to try",
|
|
590
|
-
};
|
|
591
|
-
}
|
|
503
|
+
const model = models.pi.MED;
|
|
592
504
|
const attached = usableDocs
|
|
593
505
|
.map((d) => d.absolutePath)
|
|
594
506
|
.filter((p) => Boolean(p));
|
|
@@ -598,71 +510,39 @@ export async function runSemanticIntake(input) {
|
|
|
598
510
|
documents: usableDocs,
|
|
599
511
|
repoRoot: input.repoRoot,
|
|
600
512
|
});
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
stallTimeoutMs: SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS,
|
|
615
|
-
userMessage: "Structure the imported requirement documents into the required JSON only.",
|
|
616
|
-
validateOutput: (text) => {
|
|
617
|
-
try {
|
|
618
|
-
const parsed = parseSemanticRequirementDraft(text);
|
|
619
|
-
assertSemanticDraftAcceptable(parsed);
|
|
620
|
-
return [];
|
|
621
|
-
}
|
|
622
|
-
catch (error) {
|
|
623
|
-
return [error instanceof Error ? error.message : String(error)];
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
});
|
|
627
|
-
attempted.push({
|
|
628
|
-
model: result.modelDisplay || modelRef,
|
|
629
|
-
ok: result.ok,
|
|
630
|
-
failureCategory: result.failureCategory,
|
|
631
|
-
timedOut: result.timedOut,
|
|
632
|
-
});
|
|
633
|
-
if (result.ok) {
|
|
634
|
-
chosenText = result.assistantText;
|
|
635
|
-
break;
|
|
513
|
+
const result = await execute({
|
|
514
|
+
attachedFiles: attached,
|
|
515
|
+
modelConfig: resolveDagPiModelConfig(model),
|
|
516
|
+
prompt,
|
|
517
|
+
repoRoot: input.repoRoot,
|
|
518
|
+
step: "analyze",
|
|
519
|
+
toolNames: ["read", "grep", "find", "ls"],
|
|
520
|
+
userMessage: "Structure the imported requirement documents into the required JSON only.",
|
|
521
|
+
validateOutput: (text) => {
|
|
522
|
+
try {
|
|
523
|
+
const parsed = parseSemanticRequirementDraft(text);
|
|
524
|
+
assertSemanticDraftAcceptable(parsed);
|
|
525
|
+
return [];
|
|
636
526
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
model: modelRef,
|
|
644
|
-
ok: false,
|
|
645
|
-
failureCategory: "unknown",
|
|
646
|
-
});
|
|
647
|
-
lastFailure = message.slice(0, 2000);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
attemptedModels = attempted;
|
|
651
|
-
if (chosenText === undefined) {
|
|
527
|
+
catch (error) {
|
|
528
|
+
return [error instanceof Error ? error.message : String(error)];
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
});
|
|
532
|
+
if (!result.ok) {
|
|
652
533
|
await writeAttemptMarker(input.repoRoot, input.taskId, {
|
|
653
534
|
ok: false,
|
|
654
535
|
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
655
|
-
failureCategory:
|
|
536
|
+
failureCategory: result.failureCategory,
|
|
656
537
|
at: new Date().toISOString(),
|
|
657
|
-
attemptedModels: attempted,
|
|
658
538
|
});
|
|
659
539
|
return {
|
|
660
540
|
ok: false,
|
|
661
541
|
code: "SEMANTIC_INTAKE_PI_FAILED",
|
|
662
|
-
message: `semantic intake pi failed
|
|
542
|
+
message: `semantic intake pi failed: ${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000),
|
|
663
543
|
};
|
|
664
544
|
}
|
|
665
|
-
assistantText =
|
|
545
|
+
assistantText = result.assistantText;
|
|
666
546
|
}
|
|
667
547
|
try {
|
|
668
548
|
const semantic = parseSemanticRequirementDraft(assistantText);
|
|
@@ -696,7 +576,6 @@ export async function runSemanticIntake(input) {
|
|
|
696
576
|
code: "SEMANTIC_INTAKE_OK",
|
|
697
577
|
artifactPath: SEMANTIC_INTAKE_ARTIFACT_REL,
|
|
698
578
|
at: new Date().toISOString(),
|
|
699
|
-
...(attemptedModels.length > 0 ? { attemptedModels } : {}),
|
|
700
579
|
});
|
|
701
580
|
return { ok: true, draft, artifactPath, semantic };
|
|
702
581
|
}
|
|
@@ -19,12 +19,5 @@ export function mergeArtifactCardState(current, incoming) {
|
|
|
19
19
|
const index = current.findIndex((item) => item.artifactId === incoming.artifactId);
|
|
20
20
|
if (index < 0)
|
|
21
21
|
return [...current, incoming];
|
|
22
|
-
return current.map((item, candidate) => candidate === index
|
|
23
|
-
? {
|
|
24
|
-
...item,
|
|
25
|
-
...incoming,
|
|
26
|
-
eventSeq: item.eventSeq ?? incoming.eventSeq,
|
|
27
|
-
activityRef: item.activityRef ?? incoming.activityRef,
|
|
28
|
-
}
|
|
29
|
-
: item);
|
|
22
|
+
return current.map((item, candidate) => candidate === index ? { ...item, ...incoming } : item);
|
|
30
23
|
}
|
|
@@ -50,62 +50,6 @@ function controllerSummary(operation) {
|
|
|
50
50
|
.join("@");
|
|
51
51
|
return safeIdentity(label);
|
|
52
52
|
}
|
|
53
|
-
/** Re-project an already-safe stored run summary through a bounded re-check. */
|
|
54
|
-
function safeRunSummaryProjection(value) {
|
|
55
|
-
if (!value || typeof value !== "object")
|
|
56
|
-
return undefined;
|
|
57
|
-
const record = value;
|
|
58
|
-
const runId = safeIdentity(record.runId);
|
|
59
|
-
const status = safeIdentity(record.status);
|
|
60
|
-
if (!runId || !status)
|
|
61
|
-
return undefined;
|
|
62
|
-
const nodes = Array.isArray(record.nodes)
|
|
63
|
-
? record.nodes
|
|
64
|
-
.filter((node) => Boolean(node && typeof node === "object"))
|
|
65
|
-
.map((node) => ({
|
|
66
|
-
id: safeIdentity(node.id) ?? "",
|
|
67
|
-
status: safeIdentity(node.status) ?? "",
|
|
68
|
-
}))
|
|
69
|
-
.filter((node) => node.id && node.status)
|
|
70
|
-
.slice(0, 20)
|
|
71
|
-
: [];
|
|
72
|
-
const verdict = typeof record.verdict === "string"
|
|
73
|
-
? boundedRedactedText(record.verdict)
|
|
74
|
-
: undefined;
|
|
75
|
-
const failureCategory = typeof record.failureCategory === "string"
|
|
76
|
-
? boundedRedactedText(record.failureCategory)
|
|
77
|
-
: undefined;
|
|
78
|
-
const outOfBounds = Array.isArray(record.outOfBounds)
|
|
79
|
-
? record.outOfBounds
|
|
80
|
-
.filter((entry) => Boolean(entry && typeof entry === "object"))
|
|
81
|
-
.map((entry) => ({
|
|
82
|
-
nodeId: safeIdentity(entry.nodeId) ?? "",
|
|
83
|
-
failureCategory: safeIdentity(entry.failureCategory) ?? "",
|
|
84
|
-
}))
|
|
85
|
-
.filter((entry) => entry.nodeId && entry.failureCategory)
|
|
86
|
-
.slice(0, 20)
|
|
87
|
-
: undefined;
|
|
88
|
-
const nextRec = record.next && typeof record.next === "object"
|
|
89
|
-
? record.next
|
|
90
|
-
: undefined;
|
|
91
|
-
const next = nextRec
|
|
92
|
-
? {
|
|
93
|
-
kind: safeIdentity(nextRec.kind) ?? "",
|
|
94
|
-
description: typeof nextRec.description === "string"
|
|
95
|
-
? boundedRedactedText(nextRec.description)
|
|
96
|
-
: undefined,
|
|
97
|
-
}
|
|
98
|
-
: undefined;
|
|
99
|
-
return {
|
|
100
|
-
runId,
|
|
101
|
-
status,
|
|
102
|
-
nodes,
|
|
103
|
-
...(verdict ? { verdict } : {}),
|
|
104
|
-
...(failureCategory ? { failureCategory } : {}),
|
|
105
|
-
...(outOfBounds && outOfBounds.length > 0 ? { outOfBounds } : {}),
|
|
106
|
-
...(next && next.kind ? { next } : {}),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
53
|
/** Chat/browser-safe projection. Never includes actionParams, CLI args, paths or raw results. */
|
|
110
54
|
export function projectOperationForChat(operation) {
|
|
111
55
|
const params = operation.actionParams ?? {};
|
|
@@ -117,16 +61,12 @@ export function projectOperationForChat(operation) {
|
|
|
117
61
|
const featureId = safeIdentity(params.featureId);
|
|
118
62
|
const workerRunId = safeIdentity(params.workerRunId);
|
|
119
63
|
const controller = controllerSummary(operation);
|
|
120
|
-
const runSummary = safeRunSummaryProjection(operation.runSummary);
|
|
121
64
|
const previewHash = createHash("sha256")
|
|
122
65
|
.update(JSON.stringify({
|
|
123
66
|
stdoutPreview,
|
|
124
67
|
stderrPreview,
|
|
125
68
|
summaryPreview,
|
|
126
69
|
state: operation.state,
|
|
127
|
-
// runSummary participates in the dedupe hash: terminal summary updates
|
|
128
|
-
// must not be swallowed by state+previewHash dedup (2026-08-14).
|
|
129
|
-
runSummary: runSummary ?? null,
|
|
130
70
|
}))
|
|
131
71
|
.digest("hex");
|
|
132
72
|
return {
|
|
@@ -148,7 +88,6 @@ export function projectOperationForChat(operation) {
|
|
|
148
88
|
...(operation.errorCode
|
|
149
89
|
? { errorCode: safeIdentity(operation.errorCode) }
|
|
150
90
|
: {}),
|
|
151
|
-
...(runSummary ? { runSummary } : {}),
|
|
152
91
|
previewHash,
|
|
153
92
|
};
|
|
154
93
|
}
|
|
@@ -32,14 +32,6 @@ export function mergeHumanGateCardState(cards, next) {
|
|
|
32
32
|
if (index < 0)
|
|
33
33
|
return [...cards, next];
|
|
34
34
|
const copy = [...cards];
|
|
35
|
-
|
|
36
|
-
if (!current)
|
|
37
|
-
return [...cards, next];
|
|
38
|
-
copy[index] = {
|
|
39
|
-
...current,
|
|
40
|
-
...next,
|
|
41
|
-
eventSeq: current.eventSeq ?? next.eventSeq,
|
|
42
|
-
activityRef: current.activityRef ?? next.activityRef,
|
|
43
|
-
};
|
|
35
|
+
copy[index] = next;
|
|
44
36
|
return copy;
|
|
45
37
|
}
|
|
@@ -2,24 +2,7 @@ export function mergeOperationCardState(current, incoming) {
|
|
|
2
2
|
const index = current.findIndex((operation) => operation.operationId === incoming.operationId);
|
|
3
3
|
if (index < 0)
|
|
4
4
|
return [...current, incoming];
|
|
5
|
-
return current.map((operation, candidateIndex) => {
|
|
6
|
-
if (candidateIndex !== index)
|
|
7
|
-
return operation;
|
|
8
|
-
let eventSeq = operation.eventSeq;
|
|
9
|
-
if (eventSeq === undefined)
|
|
10
|
-
eventSeq = incoming.eventSeq;
|
|
11
|
-
else if (incoming.eventSeq !== undefined) {
|
|
12
|
-
eventSeq = Math.min(eventSeq, incoming.eventSeq);
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
...operation,
|
|
16
|
-
...incoming,
|
|
17
|
-
...(eventSeq === undefined ? {} : { eventSeq }),
|
|
18
|
-
updatedSeq: Math.max(operation.updatedSeq ?? operation.eventSeq ?? 0, incoming.updatedSeq ?? incoming.eventSeq ?? 0),
|
|
19
|
-
toolCallId: operation.toolCallId ?? incoming.toolCallId,
|
|
20
|
-
activityRef: operation.activityRef ?? incoming.activityRef,
|
|
21
|
-
};
|
|
22
|
-
});
|
|
5
|
+
return current.map((operation, candidateIndex) => candidateIndex === index ? { ...operation, ...incoming } : operation);
|
|
23
6
|
}
|
|
24
7
|
export function operationFromEventPayload(payload) {
|
|
25
8
|
const operation = payload.operation;
|
|
@@ -36,57 +19,5 @@ export function operationFromEventPayload(payload) {
|
|
|
36
19
|
typeof candidate.previewHash !== "string") {
|
|
37
20
|
return undefined;
|
|
38
21
|
}
|
|
39
|
-
|
|
40
|
-
// field and drop it entirely when malformed (legacy events simply lack it).
|
|
41
|
-
const safe = safeRunSummary(candidate.runSummary);
|
|
42
|
-
const { runSummary: _unsafeRunSummary, ...base } = candidate;
|
|
43
|
-
return {
|
|
44
|
-
...base,
|
|
45
|
-
...(safe ? { runSummary: safe } : {}),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
/** Field-by-field validation for the optional browser-side runSummary. */
|
|
49
|
-
function safeRunSummary(value) {
|
|
50
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51
|
-
return undefined;
|
|
52
|
-
const rec = value;
|
|
53
|
-
if (typeof rec.runId !== "string" || !rec.runId)
|
|
54
|
-
return undefined;
|
|
55
|
-
if (typeof rec.status !== "string" || !rec.status)
|
|
56
|
-
return undefined;
|
|
57
|
-
let nodes = [];
|
|
58
|
-
if (Array.isArray(rec.nodes)) {
|
|
59
|
-
nodes = rec.nodes
|
|
60
|
-
.flatMap((node) => {
|
|
61
|
-
if (!node || typeof node !== "object")
|
|
62
|
-
return [];
|
|
63
|
-
const id = String(node.id ?? "");
|
|
64
|
-
const status = String(node.status ?? "");
|
|
65
|
-
if (!id || !status)
|
|
66
|
-
return [];
|
|
67
|
-
return [{ id, status }];
|
|
68
|
-
})
|
|
69
|
-
.slice(0, 20);
|
|
70
|
-
}
|
|
71
|
-
let next;
|
|
72
|
-
if (rec.next && typeof rec.next === "object") {
|
|
73
|
-
const nextRecord = rec.next;
|
|
74
|
-
next = {
|
|
75
|
-
kind: String(nextRecord.kind ?? ""),
|
|
76
|
-
...(typeof nextRecord.description === "string"
|
|
77
|
-
? { description: nextRecord.description }
|
|
78
|
-
: {}),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
const out = {
|
|
82
|
-
runId: rec.runId,
|
|
83
|
-
status: rec.status,
|
|
84
|
-
nodes,
|
|
85
|
-
...(typeof rec.verdict === "string" ? { verdict: rec.verdict } : {}),
|
|
86
|
-
...(typeof rec.failureCategory === "string"
|
|
87
|
-
? { failureCategory: rec.failureCategory }
|
|
88
|
-
: {}),
|
|
89
|
-
...(next && next.kind ? { next } : {}),
|
|
90
|
-
};
|
|
91
|
-
return out;
|
|
22
|
+
return candidate;
|
|
92
23
|
}
|