@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.
Files changed (118) hide show
  1. package/AGENTS.md +2 -6
  2. package/CHANGELOG.md +72 -163
  3. package/README.md +1 -1
  4. package/bin/loop-agent.js +1 -37
  5. package/dist/application/task-lifecycle/advance.js +0 -1
  6. package/dist/application/task-lifecycle/observe.js +0 -15
  7. package/dist/application/task-lifecycle/plan-transitions.js +0 -15
  8. package/dist/cli/command-definitions.js +0 -7
  9. package/dist/cli/program.js +3 -8
  10. package/dist/commands/init-upgrade.js +19 -351
  11. package/dist/commands/init.js +67 -14
  12. package/dist/commands/run-dag-progress.js +0 -14
  13. package/dist/commands/task-advance.js +3 -33
  14. package/dist/executors/dag-pi-executor.js +30 -47
  15. package/dist/executors/pi-sdk-executor.js +0 -34
  16. package/dist/executors/shell-executor.js +95 -6
  17. package/dist/infrastructure/harness/atomic-write.js +1 -2
  18. package/dist/shared/operator/capabilities.js +13 -200
  19. package/dist/shared/package-metadata.js +0 -42
  20. package/dist/task/frontend-project-capability.js +12 -162
  21. package/dist/task/source-prepare/completeness.js +0 -17
  22. package/dist/task/source-prepare/parse-intent.js +1 -15
  23. package/dist/task/source-prepare/semantic-intake.js +23 -144
  24. package/dist/worker/console/chat/artifact-card.js +1 -8
  25. package/dist/worker/console/chat/chat-event-store.js +0 -61
  26. package/dist/worker/console/chat/human-gate-card.js +1 -9
  27. package/dist/worker/console/chat/operation-card.js +2 -71
  28. package/dist/worker/console/chat/pi-runtime.js +62 -86
  29. package/dist/worker/console/chat/routes.js +8 -36
  30. package/dist/worker/console/chat/session-store.js +0 -3
  31. package/dist/worker/console/chat/shortcuts.js +7 -9
  32. package/dist/worker/console/chat/turn-process.js +23 -97
  33. package/dist/worker/console/chat/workspace-landing.js +1 -2
  34. package/dist/worker/console/operation-runner.js +6 -155
  35. package/dist/worker/console/operator-actions.js +13 -373
  36. package/dist/worker/console/operator-user-error.js +0 -4
  37. package/dist/worker/console/recovery-cta.js +3 -50
  38. package/dist/worker/console/static/assets/index-HX1pbOyl.css +1 -0
  39. package/dist/worker/console/static/assets/index-M0BLEBfh.js +56 -0
  40. package/dist/worker/console/static/index.html +2 -2
  41. package/dist/worker/console/static-src/app/console-types.js +9 -13
  42. package/dist/worker/console/static-src/app/useOperatorActions.js +2 -4
  43. package/dist/worker/console/static-src/app/useRecoveryActions.js +56 -65
  44. package/dist/worker/console/static-src/app/useRecoveryConsole.js +1 -73
  45. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +30 -89
  46. package/dist/worker/console/static-src/operator-chat/refs.js +0 -3
  47. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
  48. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +11 -30
  49. package/dist/worker/console/static-src/operator-chat/useChatThread.js +6 -12
  50. package/dist/worker/console/static-src/operator-chat/useComposer.js +8 -45
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -7
  52. package/dist/worker/loop-agent/loop-agent-client.js +3 -17
  53. package/dist/worker/observability/read-model.js +0 -20
  54. package/dist/worker/observe/spec-evidence.js +8 -3
  55. package/dist/worker/observe/static/operator-chrome.d.ts +0 -1
  56. package/dist/worker/observe/static/operator-chrome.js +1 -6
  57. package/dist/worker/observe/static/views/dag-inspector.js +71 -6
  58. package/dist/worker/observe/static/views/dag.js +0 -12
  59. package/dist/worker/preflight.js +1 -2
  60. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +707 -37
  61. package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
  62. package/dist/workflows/dag/backend-test-markdown-workflow.js +109 -7
  63. package/dist/workflows/dag/backend-test-module-stem.js +5 -0
  64. package/dist/workflows/dag/backend-test-pytest-collection.js +345 -24
  65. package/dist/workflows/dag/backend-test-scenario-param.js +890 -164
  66. package/dist/workflows/dag/backend-test-writer-completeness.js +47 -16
  67. package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
  68. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -9
  69. package/dist/workflows/dag/failure-routing.js +4 -9
  70. package/dist/workflows/dag/frontend-implementation-contract.js +39 -233
  71. package/dist/workflows/dag/frontend-prewrite-gate.js +62 -394
  72. package/dist/workflows/dag/frontend-repair.js +18 -219
  73. package/dist/workflows/dag/frontend-verification-trace.js +32 -47
  74. package/dist/workflows/dag/init-hybrid.js +86 -238
  75. package/dist/workflows/dag/lifecycle.js +0 -4
  76. package/dist/workflows/dag/node-execution.js +6 -105
  77. package/dist/workflows/dag/recovery-recommendation.js +0 -58
  78. package/dist/workflows/dag/report.js +0 -6
  79. package/dist/workflows/dag/retry-policy.js +0 -11
  80. package/dist/workflows/dag/runner.js +12 -314
  81. package/dist/workflows/dag/scheduler.js +3 -257
  82. package/dist/workflows/dag/types.js +9 -132
  83. package/dist/workflows/dag/validate.js +2 -7
  84. package/docs/README.md +3 -3
  85. package/docs/architecture/evolution.md +67 -102
  86. package/docs/templates/backend-test-dag.json +50 -32
  87. package/docs/templates/frontend-design-contract.md +14 -34
  88. package/docs/templates/frontend-task-constraints.md +13 -33
  89. package/docs/templates/frontend-task-requirement.md +20 -54
  90. package/docs/templates/init-managed-agents.md +2 -5
  91. package/harness.json +2 -2
  92. package/package.json +3 -4
  93. package/skills/loop-agent/SKILL.md +0 -1
  94. package/skills/loop-agent/references/command-reference.md +0 -3
  95. package/dist/build-stamp.json +0 -6
  96. package/dist/commands/dag-request-interrupt.js +0 -20
  97. package/dist/worker/console/chat/assistant-content.js +0 -121
  98. package/dist/worker/console/chat/semantic-activity.js +0 -471
  99. package/dist/worker/console/operation-run-facts.js +0 -190
  100. package/dist/worker/console/operation-wait.js +0 -355
  101. package/dist/worker/console/recovery-error-copy.js +0 -198
  102. package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
  103. package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
  104. package/dist/worker/console/static-src/operator-chat/activity-journey.js +0 -125
  105. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
  106. package/dist/worker/console/static-src/operator-chat/activity-references.js +0 -20
  107. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +0 -24
  108. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +0 -141
  109. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
  110. package/dist/worker/observability/interrupt-eligibility.js +0 -264
  111. package/dist/workflows/dag/contract-output-registry.js +0 -14
  112. package/dist/workflows/dag/contract-validator-registrations.js +0 -8
  113. package/dist/workflows/dag/frontend-recovery-plan.js +0 -73
  114. package/dist/workflows/dag/frontend-recovery-root-manifest.js +0 -123
  115. package/dist/workflows/dag/frontend-recovery-run.js +0 -539
  116. package/dist/workflows/dag/frontend-writer-recovery.js +0 -106
  117. package/dist/workflows/dag/frontend-writer-rollback.js +0 -821
  118. 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
- let available = [];
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 attempted = [];
602
- let lastFailure = "";
603
- let chosenText;
604
- for (const modelRef of queue) {
605
- try {
606
- const result = await execute({
607
- attachedFiles: attached,
608
- modelConfig: resolveDagPiModelConfig(modelRef),
609
- prompt,
610
- repoRoot: input.repoRoot,
611
- step: "analyze",
612
- toolNames: ["read", "grep", "find", "ls"],
613
- timeoutMs: SEMANTIC_INTAKE_ATTEMPT_TIMEOUT_MS,
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
- lastFailure =
638
- `${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000);
639
- }
640
- catch (error) {
641
- const message = error instanceof Error ? error.message : String(error);
642
- attempted.push({
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: attempted.at(-1)?.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 after ${attempted.length} model(s): ${lastFailure}`.slice(0, 2000),
542
+ message: `semantic intake pi failed: ${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000),
663
543
  };
664
544
  }
665
- assistantText = chosenText;
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
- const current = copy[index];
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
- // Optional runSummary stays backward-compatible: validate shape field by
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
  }