@tea-agent/loop-agent 0.41.0 → 0.42.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/CHANGELOG.md +102 -14
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +12 -0
  6. package/dist/cli/program.js +23 -1
  7. package/dist/commands/dag-artifact.js +284 -0
  8. package/dist/commands/dag-context.js +184 -0
  9. package/dist/commands/dag-rerun.js +206 -1
  10. package/dist/executors/dag-pi-executor.js +39 -12
  11. package/dist/executors/pi-sdk-executor.js +3 -0
  12. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  13. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  14. package/dist/infrastructure/console/context-export-store.js +160 -0
  15. package/dist/infrastructure/console/dir-lock.js +132 -0
  16. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  17. package/dist/infrastructure/harness/artifact-store.js +10 -1
  18. package/dist/infrastructure/harness/atomic-write.js +12 -2
  19. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  20. package/dist/shared/context-transfer.js +418 -0
  21. package/dist/shared/operator/capabilities.js +14 -0
  22. package/dist/shared/operator/safe-run-summary.js +1 -0
  23. package/dist/shared/path-safety.js +93 -0
  24. package/dist/shared/preview.js +28 -4
  25. package/dist/task/config-types.js +2 -1
  26. package/dist/task/task-demand-routing.js +3 -0
  27. package/dist/worker/console/chat/chat-event-store.js +2 -2
  28. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  29. package/dist/worker/console/chat/routes.js +1 -1
  30. package/dist/worker/console/chat/session-store.js +1 -1
  31. package/dist/worker/console/chat/user-questions.js +1 -1
  32. package/dist/worker/console/console-update-runtime.js +1 -1
  33. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  34. package/dist/worker/console/dag-confirmation.js +1 -1
  35. package/dist/worker/console/dag-execution-receipt.js +1 -1
  36. package/dist/worker/console/doctor.js +1 -1
  37. package/dist/worker/console/draft-store.js +1 -1
  38. package/dist/worker/console/human-gate-token.js +1 -1
  39. package/dist/worker/console/index.js +3 -3
  40. package/dist/worker/console/interview/assessment.js +1 -1
  41. package/dist/worker/console/interview/session.js +1 -1
  42. package/dist/worker/console/operation-runner.js +45 -4
  43. package/dist/worker/console/operation-sse.js +1 -1
  44. package/dist/worker/console/operation-wait.js +1 -1
  45. package/dist/worker/console/operator-actions.js +104 -3
  46. package/dist/worker/console/operator-surface-health.js +1 -1
  47. package/dist/worker/console/operator-user-error.js +169 -0
  48. package/dist/worker/console/pi-plugins.js +57 -0
  49. package/dist/worker/console/routes.js +637 -8
  50. package/dist/worker/console/security.js +35 -0
  51. package/dist/worker/console/server.js +6 -3
  52. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
  53. package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-D6PvaVd-.js} +1 -1
  54. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
  55. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
  56. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
  57. package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
  58. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
  68. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
  69. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
  70. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
  71. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
  72. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
  73. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
  79. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
  80. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
  81. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
  82. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
  83. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
  84. package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
  85. package/dist/worker/console/static/assets/{index-Bih9w9h9.js → index-xwu9GxEc.js} +80 -80
  86. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
  87. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
  88. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
  89. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
  90. package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-CfUiDDB3.js} +1 -1
  91. package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-BQe6fpqj.js} +5 -5
  92. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
  93. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
  94. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
  95. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
  96. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
  97. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
  98. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
  99. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
  100. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
  101. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
  102. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
  103. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
  104. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
  105. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
  106. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
  107. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
  108. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
  109. package/dist/worker/console/static/index.html +7 -2
  110. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  111. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  112. package/dist/worker/console/workspace-context.js +28 -2
  113. package/dist/worker/console/workspace-registry.js +2 -2
  114. package/dist/worker/continuation/worker-continuation.js +278 -0
  115. package/dist/worker/observe/health.js +1 -1
  116. package/dist/worker/observe/node-transparency.js +572 -0
  117. package/dist/worker/observe/routes.js +51 -1
  118. package/dist/worker/observe/static/api.js +69 -5
  119. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  120. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  121. package/dist/worker/observe/static/state.js +3 -1
  122. package/dist/worker/observe/static/styles.css +1037 -126
  123. package/dist/worker/observe/static/views/dag-inspector.js +1215 -58
  124. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  125. package/dist/worker/outcomes/adapters.js +19 -0
  126. package/dist/worker/outcomes/types.js +1 -0
  127. package/dist/worker/pool/attempt-lease.js +97 -66
  128. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  129. package/dist/worker/pool/reconcile.js +46 -1
  130. package/dist/worker/pool/run-store.js +2 -0
  131. package/dist/worker/pool/state-projection.js +2 -0
  132. package/dist/worker/task-spec/workflow-routing.js +8 -3
  133. package/dist/workflows/dag/artifact-bindings.js +149 -0
  134. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  135. package/dist/workflows/dag/budget-enforcement.js +10 -0
  136. package/dist/workflows/dag/context-receipt.js +305 -0
  137. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  138. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  139. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  140. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  141. package/dist/workflows/dag/init-hybrid.js +146 -0
  142. package/dist/workflows/dag/node-execution.js +112 -27
  143. package/dist/workflows/dag/path-safety.js +1 -0
  144. package/dist/workflows/dag/prompt.js +29 -4
  145. package/dist/workflows/dag/rerun-plan.js +555 -1
  146. package/dist/workflows/dag/rerun-run.js +601 -15
  147. package/dist/workflows/dag/runner.js +20 -1
  148. package/dist/workflows/dag/skill-snapshot.js +17 -0
  149. package/dist/workflows/dag/types.js +183 -7
  150. package/docs/architecture/runtime-boundaries.md +6 -6
  151. package/docs/architecture/worker-and-feature.md +1 -1
  152. package/docs/templates/agent-dag.schema.json +32 -0
  153. package/docs/templates/product-line/task.yaml +1 -1
  154. package/package.json +3 -1
  155. package/skills/loop-agent/references/command-reference.md +6 -0
  156. package/dist/worker/console/static/assets/channel-CL1NRAmS.js +0 -1
  157. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-PBGzzjZA.js +0 -1
  158. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-PBGzzjZA.js +0 -1
  159. package/dist/worker/console/static/assets/index-BfleX-qj.css +0 -1
  160. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
  161. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
  162. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -307,6 +307,19 @@ export async function runDag(spec, opts) {
307
307
  }
308
308
  }
309
309
  export async function runDagContinuation(opts) {
310
+ assertValidDagSpec(opts.spec);
311
+ assertFrozenBudget(opts.spec.budget, opts.state.budget, opts.state.runId);
312
+ if (opts.spec.budget) {
313
+ const ledger = opts.state.budgetLedger;
314
+ if (!ledger ||
315
+ ledger.mode !== opts.spec.budget.mode ||
316
+ JSON.stringify(ledger.limits) !== JSON.stringify(opts.spec.budget.limits)) {
317
+ throw new Error(`dag continuation ${opts.state.runId} is missing a matching cumulative budget ledger`);
318
+ }
319
+ }
320
+ const { assertDagSpecAllowsNewWriterExecution, assertTaskContractBindingConsistent, } = await import("./task-contract-binding.js");
321
+ assertDagSpecAllowsNewWriterExecution(opts.spec);
322
+ await assertTaskContractBindingConsistent({ repoRoot: opts.cwd, spec: opts.spec });
310
323
  const { maxConcurrent } = resolveEffectiveMaxConcurrent(Math.max(1, opts.maxConcurrent ?? 4), opts.spec.budget);
311
324
  const state = opts.state;
312
325
  state.status = "running";
@@ -328,6 +341,7 @@ export async function runDagContinuation(opts) {
328
341
  cwd: opts.cwd,
329
342
  maxConcurrent,
330
343
  executeNode: opts.executeNode,
344
+ beforeTerminalArchive: opts.beforeTerminalArchive,
331
345
  observer: opts.observer,
332
346
  abortSignal: opts.abortSignal,
333
347
  activeRunDir,
@@ -712,6 +726,10 @@ async function executeDagCheckpoint(input) {
712
726
  }
713
727
  await notifyRunObserver(input.observer, "onRunFinish", state);
714
728
  if (!recoveryPending) {
729
+ if (input.beforeTerminalArchive) {
730
+ await input.beforeTerminalArchive({ runDir, state });
731
+ await persistState();
732
+ }
715
733
  try {
716
734
  const terminalCheckpoint = await captureWorkspaceCheckpoint(cwd);
717
735
  await writeWorkspaceCheckpoint(runDir, WORKSPACE_CHECKPOINT_TERMINAL_REL, terminalCheckpoint);
@@ -942,7 +960,8 @@ export async function finalizeTerminalRunStatus(state, taskCount, runDir, cwd) {
942
960
  // mechanical partial_failed just because prewrite FINISHED while the writer
943
961
  // was SKIPPED. Fail closed on retryable-invalid/blocked before aggregation.
944
962
  let recoveryPending = false;
945
- const skipFrontendRecovery = state.failureCategory === "controller-interrupted";
963
+ const skipFrontendRecovery = state.failureCategory === "controller-interrupted" ||
964
+ state.continuation?.writerTransaction !== undefined;
946
965
  const hasFrontendWriter = Object.keys(state.nodes).some((id) => FRONTEND_WRITER_NODE_IDS.includes(id));
947
966
  if (hasFrontendWriter && !skipFrontendRecovery) {
948
967
  const admission = await readFrontendPrewriteResult(runDir);
@@ -333,11 +333,27 @@ function parseSnapshot(value) {
333
333
  export async function cloneParentSkillSnapshotForContinuation(input) {
334
334
  const parentSnapshot = await readSkillSnapshot(input.parentRunDir, input.parentRef, { expectedRunId: input.parentRunId });
335
335
  const parentSnapshotSha256 = hashSkillSnapshot(parentSnapshot);
336
+ const initialTaskIds = input.spec
337
+ ? input.spec.tasks.map((task) => task.id)
338
+ : parentSnapshot.initialTaskIds;
339
+ const bindings = input.spec
340
+ ? collectDagSkillSnapshotBindings(input.spec, initialTaskIds)
341
+ : parentSnapshot.bindings;
342
+ const boundProfileIds = new Set(bindings.map((binding) => binding.profileId));
343
+ const profiles = parentSnapshot.profiles.filter((profile) => boundProfileIds.has(profile.id));
344
+ for (const profileId of boundProfileIds) {
345
+ if (!profiles.some((profile) => profile.id === profileId)) {
346
+ throw new DagSkillSnapshotIntegrityError(`continuation run spec requires profile missing from parent snapshot: ${profileId}`);
347
+ }
348
+ }
336
349
  const cloned = {
337
350
  ...structuredClone(parentSnapshot),
338
351
  runId: input.newRunId,
339
352
  createdAt: (input.now ?? new Date()).toISOString(),
340
353
  mode: "continuation-from-parent",
354
+ initialTaskIds,
355
+ bindings,
356
+ profiles,
341
357
  };
342
358
  const ref = await writeSkillSnapshot(input.newRunDir, cloned);
343
359
  await writeJsonAtomic(path.join(input.newRunDir, ...SKILL_SNAPSHOT_CONTINUATION_REL_PATH.split("/")), {
@@ -540,6 +556,7 @@ export function buildNodePromptFromSnapshot(input) {
540
556
  spec: input.spec,
541
557
  task: input.task,
542
558
  upstream: input.upstream,
559
+ runDir: input.runDir,
543
560
  resolvedSkills: skillNames,
544
561
  resolvedSkillInstructions,
545
562
  maxUpstreamChars: policy.resolveMaxUpstreamChars(input.task),
@@ -80,7 +80,10 @@ export const dagShellVerifyEvidenceSchema = z.object({
80
80
  selectionReasons: z.array(z.string()).default([]),
81
81
  /** Non-executing generation-time cwd/local-script validation evidence. */
82
82
  preflight: z
83
- .array(z.object({ label: z.string(), status: z.enum(["ok", "future-delivery-script"]) }))
83
+ .array(z.object({
84
+ label: z.string(),
85
+ status: z.enum(["ok", "future-delivery-script"]),
86
+ }))
84
87
  .default([]),
85
88
  /** Effective timeout applied independently to each shell command. */
86
89
  commandTimeoutMs: z.number().int().positive().optional(),
@@ -734,7 +737,58 @@ export const structuredContractOutputSchemaIds = [
734
737
  "frontend-implementation-contract-plan-patch-v1",
735
738
  "frontend-implementation-contract-revision-patch-v1",
736
739
  ];
737
- export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/, "task id must be kebab-case"),
740
+ export const dagArtifactIdentitySchema = z
741
+ .string()
742
+ .regex(/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/, "artifact id must be stable lower-case dot/kebab syntax");
743
+ const dagArtifactRelativePathSchema = z
744
+ .string()
745
+ .min(1)
746
+ .refine((value) => !value.includes("\0"), "artifact path must not contain NUL")
747
+ .refine((value) => !value.includes("\\"), "artifact path must use POSIX separators")
748
+ .refine((value) => !value.startsWith("/") && !/^[A-Za-z]:/.test(value), "artifact path must be run-relative")
749
+ .refine((value) => value.split("/").every((segment) => segment !== "" && segment !== ".."), "artifact path must not escape the run");
750
+ export const dagProducedArtifactSchema = z
751
+ .object({
752
+ artifactId: dagArtifactIdentitySchema,
753
+ kind: z.enum(["structured", "assistant", "stdout"]),
754
+ path: dagArtifactRelativePathSchema,
755
+ mediaType: z.string().min(1),
756
+ schemaId: z.string().min(1).optional(),
757
+ revisionPolicy: z.enum(["immutable", "replace-file"]),
758
+ })
759
+ .strict()
760
+ .superRefine((value, ctx) => {
761
+ if (value.kind === "structured" && !value.schemaId) {
762
+ ctx.addIssue({
763
+ code: z.ZodIssueCode.custom,
764
+ path: ["schemaId"],
765
+ message: "structured artifact requires schemaId",
766
+ });
767
+ }
768
+ if (value.kind !== "structured" && value.schemaId) {
769
+ ctx.addIssue({
770
+ code: z.ZodIssueCode.custom,
771
+ path: ["schemaId"],
772
+ message: "only structured artifacts may declare schemaId",
773
+ });
774
+ }
775
+ if (value.revisionPolicy === "replace-file" &&
776
+ value.kind !== "structured") {
777
+ ctx.addIssue({
778
+ code: z.ZodIssueCode.custom,
779
+ path: ["revisionPolicy"],
780
+ message: "MVP revisions are limited to structured artifacts",
781
+ });
782
+ }
783
+ });
784
+ export const dagConsumedArtifactSchema = z
785
+ .object({
786
+ artifactId: dagArtifactIdentitySchema,
787
+ required: z.boolean().default(true),
788
+ })
789
+ .strict();
790
+ export const dagTaskSchema = z.object({
791
+ id: z.string().regex(/^[a-z][a-z0-9-]*$/, "task id must be kebab-case"),
738
792
  depends_on: z.array(z.string()).default([]),
739
793
  /**
740
794
  * How SKIPPED upstreams affect readiness.
@@ -797,6 +851,15 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
797
851
  shell: dagShellConfigSchema.optional(),
798
852
  static: dagStaticConfigSchema.optional(),
799
853
  outputContract: z.string().optional(),
854
+ /**
855
+ * Frozen producer declarations for run-owned artifacts. These declarations,
856
+ * together with consumesArtifacts, are the only authority for artifact
857
+ * lineage and impact analysis. Prompt/outputContract/path heuristics are
858
+ * intentionally forbidden.
859
+ */
860
+ producesArtifacts: z.array(dagProducedArtifactSchema).optional(),
861
+ /** Frozen logical artifact inputs consumed by this node. */
862
+ consumesArtifacts: z.array(dagConsumedArtifactSchema).optional(),
800
863
  outputMode: dagOutputModeSchema.optional(),
801
864
  firstProtocolLine: z.string().min(1).optional(),
802
865
  /**
@@ -825,8 +888,7 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
825
888
  })
826
889
  .strict()
827
890
  .superRefine((value, ctx) => {
828
- if (value.schemaId ===
829
- "frontend-implementation-contract-plan-patch-v1" &&
891
+ if (value.schemaId === "frontend-implementation-contract-plan-patch-v1" &&
830
892
  !value.skeleton) {
831
893
  ctx.addIssue({
832
894
  code: "custom",
@@ -881,6 +943,121 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
881
943
  dynamicCondition: dagDynamicConditionSchema.optional(),
882
944
  dynamicLoopUntil: dagDynamicLoopUntilSchema.optional(),
883
945
  });
946
+ function hasTransitiveDependency(tasksById, consumerNodeId, producerNodeId) {
947
+ const queue = [...(tasksById.get(consumerNodeId)?.depends_on ?? [])];
948
+ const seen = new Set();
949
+ while (queue.length > 0) {
950
+ const nodeId = queue.shift();
951
+ if (nodeId === producerNodeId)
952
+ return true;
953
+ if (seen.has(nodeId))
954
+ continue;
955
+ seen.add(nodeId);
956
+ queue.push(...(tasksById.get(nodeId)?.depends_on ?? []));
957
+ }
958
+ return false;
959
+ }
960
+ function validateFrozenArtifactBindings(tasks, ctx) {
961
+ const tasksById = new Map(tasks.map((task) => [task.id, task]));
962
+ const producers = new Map();
963
+ const paths = new Map();
964
+ for (const [taskIndex, task] of tasks.entries()) {
965
+ const localIds = new Set();
966
+ for (const [artifactIndex, artifact] of (task.producesArtifacts ?? []).entries()) {
967
+ if (localIds.has(artifact.artifactId) ||
968
+ producers.has(artifact.artifactId)) {
969
+ ctx.addIssue({
970
+ code: z.ZodIssueCode.custom,
971
+ path: [
972
+ "tasks",
973
+ taskIndex,
974
+ "producesArtifacts",
975
+ artifactIndex,
976
+ "artifactId",
977
+ ],
978
+ message: `artifact ${artifact.artifactId} must have exactly one producer`,
979
+ });
980
+ }
981
+ else {
982
+ producers.set(artifact.artifactId, {
983
+ nodeId: task.id,
984
+ taskIndex,
985
+ artifactIndex,
986
+ path: artifact.path,
987
+ });
988
+ }
989
+ localIds.add(artifact.artifactId);
990
+ const foldedPath = artifact.path
991
+ .normalize("NFC")
992
+ .toLocaleLowerCase("en-US");
993
+ const existingArtifactId = paths.get(foldedPath);
994
+ if (existingArtifactId && existingArtifactId !== artifact.artifactId) {
995
+ ctx.addIssue({
996
+ code: z.ZodIssueCode.custom,
997
+ path: [
998
+ "tasks",
999
+ taskIndex,
1000
+ "producesArtifacts",
1001
+ artifactIndex,
1002
+ "path",
1003
+ ],
1004
+ message: `artifact path collides with ${existingArtifactId} under case folding`,
1005
+ });
1006
+ }
1007
+ else {
1008
+ paths.set(foldedPath, artifact.artifactId);
1009
+ }
1010
+ }
1011
+ }
1012
+ for (const [taskIndex, task] of tasks.entries()) {
1013
+ const consumed = new Set();
1014
+ for (const [artifactIndex, binding] of (task.consumesArtifacts ?? []).entries()) {
1015
+ if (consumed.has(binding.artifactId)) {
1016
+ ctx.addIssue({
1017
+ code: z.ZodIssueCode.custom,
1018
+ path: [
1019
+ "tasks",
1020
+ taskIndex,
1021
+ "consumesArtifacts",
1022
+ artifactIndex,
1023
+ "artifactId",
1024
+ ],
1025
+ message: `artifact ${binding.artifactId} is consumed more than once by ${task.id}`,
1026
+ });
1027
+ }
1028
+ consumed.add(binding.artifactId);
1029
+ const producer = producers.get(binding.artifactId);
1030
+ if (!producer) {
1031
+ ctx.addIssue({
1032
+ code: z.ZodIssueCode.custom,
1033
+ path: [
1034
+ "tasks",
1035
+ taskIndex,
1036
+ "consumesArtifacts",
1037
+ artifactIndex,
1038
+ "artifactId",
1039
+ ],
1040
+ message: `artifact ${binding.artifactId} has no declared producer`,
1041
+ });
1042
+ continue;
1043
+ }
1044
+ if (producer.nodeId === task.id ||
1045
+ !hasTransitiveDependency(tasksById, task.id, producer.nodeId)) {
1046
+ ctx.addIssue({
1047
+ code: z.ZodIssueCode.custom,
1048
+ path: [
1049
+ "tasks",
1050
+ taskIndex,
1051
+ "consumesArtifacts",
1052
+ artifactIndex,
1053
+ "artifactId",
1054
+ ],
1055
+ message: `consumer ${task.id} must be downstream of producer ${producer.nodeId}`,
1056
+ });
1057
+ }
1058
+ }
1059
+ }
1060
+ }
884
1061
  export const dagModelsSchema = z
885
1062
  .object({
886
1063
  HIGH: z.string().optional(),
@@ -910,9 +1087,7 @@ export const dagBudgetSchema = campaignBudgetSchema;
910
1087
  export const dagSourceBindingSourceSchema = z.object({
911
1088
  kind: z.enum(["requirement", "constraint", "reference"]),
912
1089
  path: z.string().min(1),
913
- sha256: z
914
- .string()
915
- .regex(/^[a-f0-9]{64}$/, "sha256 must be lowercase hex"),
1090
+ sha256: z.string().regex(/^[a-f0-9]{64}$/, "sha256 must be lowercase hex"),
916
1091
  });
917
1092
  /**
918
1093
  * DagSourceBinding v1:无 ledger 字段(schemaVersion=1,.strict() 拒绝 ledger
@@ -1063,6 +1238,7 @@ export const dagSpecSchema = z
1063
1238
  })
1064
1239
  .superRefine((spec, ctx) => {
1065
1240
  const supportsV3Fields = spec.version === 3 || spec.version === 4;
1241
+ validateFrozenArtifactBindings(spec.tasks, ctx);
1066
1242
  if (spec.evaluation && !supportsV3Fields) {
1067
1243
  ctx.addIssue({
1068
1244
  code: z.ZodIssueCode.custom,
@@ -160,18 +160,18 @@ Runner / Loop ──(迁移中)──> 逐步改为仅经 Store / Appli
160
160
 
161
161
  | From | May import | Must not import |
162
162
  | ------ | ------------ | ----------------- |
163
- | `src/commands/**` | application, workflows, infrastructure, task, records, executors, shared | — |
164
- | `src/workflows/**` | executors, task, records, shared, application(目标) | `src/commands/**` |
163
+ | `src/commands/**` | application, workflows, infrastructure, task, records, executors, shared | `src/worker/**` |
164
+ | `src/workflows/**` | executors, task, records, shared, infrastructure, application(目标) | `src/commands/**` |
165
165
  | `src/executors/**` | shared, 外部 SDK | `src/commands/**`, `src/cli/**` |
166
- | `src/worker/**` | worker-local modules, task contracts, shared, Node APIs | `src/commands/**`, `src/cli/**`, `src/application/**` |
167
- | `src/cli/**` | commands, adapters, shared, application(目标) | — |
166
+ | `src/worker/**` | worker-local modules, task contracts, shared, infrastructure, Node APIs | `src/commands/**`, `src/cli/**`, `src/application/**` |
167
+ | `src/cli/**` | commands, adapters, shared, infrastructure, application(目标) | — |
168
168
  | `.agents/skills/**` | (Markdown 引用,非 TS import) | 运行时 TS 模块 |
169
169
 
170
170
  ## 已知过渡耦合
171
171
 
172
- 当前没有允许的 `src/workflows/**` / `src/executors/**` forbidden import,也没有允许的 `src/worker/**` → `src/{cli,commands,application}/**` TypeScript import。
172
+ 当前没有允许的 `src/workflows/**` / `src/executors/**` forbidden import,也没有允许的 `src/worker/**` → `src/{cli,commands,application}/**` 或 `src/commands/**` → `src/worker/**` TypeScript import。Console app-data 存储(operations、context exports、artifact revisions、app-data path helpers)位于 `src/infrastructure/console/**`,由 Console(worker 层)与 CLI 命令共同向下依赖;Console 专属的 route/action 编排仍留在 `src/worker/console/**`。
173
173
 
174
- `scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors/worker` forbidden import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
174
+ `scripts/check-architecture-boundaries.sh` 的 transitional allowlist 保持为空。任何新增的 `workflows/executors/worker/commands` forbidden import 必须导致检查 **exit 1**;如果未来确有临时例外,必须先写入 active exec plan,说明移除时间和验证门禁。
175
175
 
176
176
  ## Governance 钩子
177
177
 
@@ -61,7 +61,7 @@ controller identity 与 DAG skill snapshot 是两个不同冻结层(前者跨
61
61
 
62
62
  - schema/validate:`src/worker/task-spec/{schema,validate}.ts`。
63
63
  - 校验验收条件、依赖、验证命令;`agent-worker task validate-feature` 等用之。
64
- - **0.16.0+**:可选 `execution.workflow` 映射到 loop-agent `taskKind`(如 agent-dag / frontend-implementation / backend-test / frontend-test);typed Task Outcome 与 artifact-aware Ready 门禁见 completed `2026-07-19-taskspec-workflow-routing.md`。
64
+ - **0.16.0+**:可选 `execution.workflow` 映射到 loop-agent `taskKind`(agent-dag / frontend-implementation / backend-test-analysis / backend-test / frontend-test);`backend-test-analysis` 只读生成严格 analysis artifact,不执行测试;typed Task Outcome 与 artifact-aware Ready 门禁见 completed `2026-07-19-taskspec-workflow-routing.md`。
65
65
  - 文档映射镜像:`docs/architecture/taskspec-to-loop-agent-mapping.md`(runtime 真源仍在 `src/worker/` 与 materialize 路径)。
66
66
 
67
67
  ### Task Pool
@@ -483,6 +483,28 @@
483
483
  "sha256": { "type": "string", "minLength": 1 }
484
484
  }
485
485
  },
486
+ "producedArtifact": {
487
+ "type": "object",
488
+ "additionalProperties": false,
489
+ "required": ["artifactId", "kind", "path", "mediaType", "revisionPolicy"],
490
+ "properties": {
491
+ "artifactId": { "type": "string", "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" },
492
+ "kind": { "enum": ["structured", "assistant", "stdout"] },
493
+ "path": { "type": "string", "pattern": "^(?!/)(?![A-Za-z]:)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*\\\\).+$" },
494
+ "mediaType": { "type": "string", "minLength": 1 },
495
+ "schemaId": { "type": "string", "minLength": 1 },
496
+ "revisionPolicy": { "enum": ["immutable", "replace-file"] }
497
+ }
498
+ },
499
+ "consumedArtifact": {
500
+ "type": "object",
501
+ "additionalProperties": false,
502
+ "required": ["artifactId"],
503
+ "properties": {
504
+ "artifactId": { "type": "string", "pattern": "^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$" },
505
+ "required": { "type": "boolean", "default": true }
506
+ }
507
+ },
486
508
  "task": {
487
509
  "type": "object",
488
510
  "additionalProperties": false,
@@ -570,6 +592,16 @@
570
592
  "type": "string",
571
593
  "minLength": 1
572
594
  },
595
+ "producesArtifacts": {
596
+ "type": "array",
597
+ "items": { "$ref": "#/$defs/producedArtifact" },
598
+ "description": "Frozen producer declarations. Artifact lineage and impact analysis must use these declarations, never prompt/outputContract/path heuristics."
599
+ },
600
+ "consumesArtifacts": {
601
+ "type": "array",
602
+ "items": { "$ref": "#/$defs/consumedArtifact" },
603
+ "description": "Frozen logical artifact inputs consumed by this node."
604
+ },
573
605
  "outputMode": {
574
606
  "enum": ["default", "structured-required"],
575
607
  "description": "Optional output semantics for Pi nodes. structured-required means oversized assistant output must not be accepted as a truncated success; eligible read-only nodes may compact-retry and downstream gates should rely on canonical structured artifacts."
@@ -31,7 +31,7 @@ constraints:
31
31
  hard_constraints:
32
32
  - Do not access production secrets
33
33
  # Explicit runtime workflow → taskKind. For fullstack-v1 packets this should be set
34
- # on every TaskSpec. Allowed: agent-dag | frontend-implementation | backend-test | frontend-test
34
+ # on every TaskSpec. Allowed: agent-dag | frontend-implementation | backend-test-analysis | backend-test | frontend-test
35
35
  execution:
36
36
  workflow: agent-dag
37
37
  verify:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tea-agent/loop-agent",
3
- "version": "0.41.0",
3
+ "version": "0.42.0-next.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "loop-agent": "bin/loop-agent.js",
@@ -72,7 +72,9 @@
72
72
  "verify:tree": "node scripts/pre-push-verify.mjs --verify-current-tree"
73
73
  },
74
74
  "dependencies": {
75
+ "@deepseek-ai/cordis": "4.0.1",
75
76
  "@deepseek-ai/dsh-host-directory-picker-native": "0.1.1-rc.2",
77
+ "@deepseek-ai/dsh-invariants": "0.1.1-rc.2",
76
78
  "@earendil-works/pi-ai": "0.83.0",
77
79
  "@earendil-works/pi-coding-agent": "0.83.0",
78
80
  "commander": "^12.1.0",
@@ -347,6 +347,12 @@ loop-agent dag reconcile-run --run-id <run-id> --action abandon --reason "..."
347
347
  loop-agent dag request-interrupt --run-id <run-id> --request-id <id> --target-operation-id <id> --reason-code <code> --reason <detail> [--json] # 协作中止:写入 interrupt.json(identity CAS);需配 --expected-runner-pid/--expected-runner-hostname 校验 runner 身份
348
348
  loop-agent dag rerun --run-id <run-id> --from-node <node-id> --plan [--json] # 从节点重跑资格预检(不执行)
349
349
  loop-agent dag rerun --run-id <run-id> --from-node <node-id> --plan-hash <sha256> --request-id <key> --reason "..." [--json] # 安全子图 continuation
350
+ loop-agent dag context show --run-id <run-id> --node-id <node-id> --attempt <n> --json # 有界 effective-context 摘要
351
+ loop-agent dag context export --run-id <run-id> --node-id <node-id> --attempt <n> --target <generic|pi-session|pi-handoff|readable> --output <bundle.zip> # 默认 redacted;raw 另需显式确认
352
+ loop-agent dag artifact consumers --run-id <run-id> --node-id <producer> --artifact <relative-path> --json # 只读 frozen binding consumer
353
+ loop-agent dag artifact revision stage --run-id <run-id> --node-id <producer> --artifact <relative-path> --file <replacement> --request-id <key> --json # 暂存 revision,不修改 parent
354
+ loop-agent dag artifact revision discard --revision-id <revision-id> --request-id <key> --json
355
+ loop-agent dag rerun --run-id <run-id> --artifact-revision <revision-id> --plan [--json] # 单产物影响闭包预检
350
356
  loop-agent dag rerun-task --run-id <run-id> --reason "..." --request-id <key> [--profile auto] [--task-id <id>] [--json] # standalone 完整任务重跑
351
357
  loop-agent dag request-interrupt --run-id <run-id> --request-id <key> --target-operation-id <op-id> --reason-code <code> --reason "..." --expected-runner-pid <pid> --expected-runner-hostname <host> --expected-runner-started-at <ts> --expected-controller-fingerprint <fingerprint> # 协作式中止:经 Human Gate 登记 interrupt.json,runner 等当前节点安全收敛后再停调度,不直接 SIGKILL
352
358
  loop-agent dag reconcile-tasks --glob '<pattern>' # 仅报告的 task/run/artifact/verify drift audit
@@ -1 +0,0 @@
1
- import{ag as o,ah as n}from"./mermaid.core-Bpa6PmuE.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D-r1MxLK.js";import{_ as i}from"./mermaid.core-Bpa6PmuE.js";import"./chunk-5VM5RSS4-BiTg127i.js";import"./chunk-XXDRQBXY-BVe1b1gL.js";import"./chunk-KBJHAD2P-DMndGvCT.js";import"./chunk-2GRJ4B5K-BvfNHkAz.js";import"./index-Bih9w9h9.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-GF5L2VYU-D-r1MxLK.js";import{_ as i}from"./mermaid.core-Bpa6PmuE.js";import"./chunk-5VM5RSS4-BiTg127i.js";import"./chunk-XXDRQBXY-BVe1b1gL.js";import"./chunk-KBJHAD2P-DMndGvCT.js";import"./chunk-2GRJ4B5K-BvfNHkAz.js";import"./index-Bih9w9h9.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};