@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
@@ -0,0 +1,418 @@
1
+ import { createHash } from "node:crypto";
2
+ import { z } from "zod";
3
+ export const CONTEXT_TRANSFER_REASON_CODES = [
4
+ "CONTEXT_RECEIPT_MISSING",
5
+ "CONTEXT_COMPONENT_UNAVAILABLE",
6
+ "CONTEXT_BUNDLE_INEXACT",
7
+ "CONTEXT_BUNDLE_TOO_LARGE",
8
+ "RAW_EXPORT_ACKNOWLEDGEMENT_REQUIRED",
9
+ "SECRET_SCAN_CONFIRMATION_REQUIRED",
10
+ "ARTIFACT_BINDING_UNAVAILABLE",
11
+ "ARTIFACT_NOT_REVISION_ELIGIBLE",
12
+ "ARTIFACT_IDENTITY_AMBIGUOUS",
13
+ "ARTIFACT_REVISION_SCHEMA_INVALID",
14
+ "ARTIFACT_REVISION_PATH_INVALID",
15
+ "ARTIFACT_REVISION_EXPIRED",
16
+ "ARTIFACT_REVISION_DRIFT",
17
+ "ARTIFACT_REVISION_QUOTA_EXCEEDED",
18
+ "ARTIFACT_CONSUMER_UNKNOWN",
19
+ "ARTIFACT_IMPACT_UNSAFE",
20
+ "RERUN_PLAN_DRIFT",
21
+ "WORKER_MANAGED_CONTINUATION_UNSUPPORTED",
22
+ "WORKER_CONTINUATION_AUTHORITY_MISMATCH",
23
+ "WORKER_CONTINUATION_NEEDS_RECONCILE",
24
+ ];
25
+ export const contextTransferReasonCodeSchema = z.enum(CONTEXT_TRANSFER_REASON_CODES);
26
+ export const CONTEXT_BUNDLE_LIMITS = Object.freeze({
27
+ previewBytes: 64 * 1024,
28
+ singleFileBytes: 16 * 1024 * 1024,
29
+ totalUncompressedBytes: 64 * 1024 * 1024,
30
+ fileCount: 2_048,
31
+ });
32
+ const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
33
+ const safeRelativePosixPathSchema = z
34
+ .string()
35
+ .min(1)
36
+ .refine((value) => !value.includes("\0"), "path must not contain NUL")
37
+ .refine((value) => !value.includes("\\"), "path must use POSIX separators")
38
+ .refine((value) => !value.startsWith("/") &&
39
+ !value.startsWith("//") &&
40
+ !/^[A-Za-z]:/.test(value), "path must be relative")
41
+ .refine((value) => value.split("/").every((segment) => segment !== ".." && segment !== ""), "path must not escape or contain empty segments");
42
+ const contextComponentSchema = z
43
+ .object({
44
+ index: z.number().int().nonnegative(),
45
+ kind: z.enum([
46
+ "system-prompt",
47
+ "developer-prompt",
48
+ "user-prompt",
49
+ "task-contract",
50
+ "execution-constraint",
51
+ "context-file",
52
+ "upstream-artifact",
53
+ "tool-contract",
54
+ "runtime-fact",
55
+ ]),
56
+ logicalName: z.string().min(1),
57
+ source: z
58
+ .object({
59
+ type: z.string().min(1),
60
+ identity: z.string().min(1),
61
+ revision: z.string().min(1).optional(),
62
+ })
63
+ .strict(),
64
+ materializedPath: safeRelativePosixPathSchema.optional(),
65
+ sha256: sha256Schema,
66
+ bytes: z.number().int().nonnegative(),
67
+ mediaType: z.string().min(1),
68
+ inclusion: z.enum([
69
+ "full",
70
+ "excerpt",
71
+ "summary",
72
+ "reference-only",
73
+ "omitted",
74
+ ]),
75
+ authoritativeSource: z
76
+ .object({ identity: z.string().min(1), sha256: sha256Schema.optional() })
77
+ .strict()
78
+ .optional(),
79
+ omissionReason: z.string().min(1).optional(),
80
+ })
81
+ .strict();
82
+ export const effectiveNodeContextV1Schema = z
83
+ .object({
84
+ schemaVersion: z.literal(1),
85
+ identity: z
86
+ .object({
87
+ runId: z.string().min(1),
88
+ nodeId: z.string().min(1),
89
+ attempt: z.number().int().positive(),
90
+ capturedAt: z.string().min(1),
91
+ })
92
+ .strict(),
93
+ orderedComponents: z.array(contextComponentSchema),
94
+ promptAssembly: z
95
+ .object({
96
+ strategy: z.string().min(1),
97
+ finalPromptSha256: sha256Schema.optional(),
98
+ tokenEstimate: z.number().int().nonnegative().optional(),
99
+ truncations: z.array(z
100
+ .object({
101
+ componentIndex: z.number().int().nonnegative(),
102
+ reason: z.string().min(1),
103
+ retainedBytes: z.number().int().nonnegative().optional(),
104
+ })
105
+ .strict()),
106
+ })
107
+ .strict(),
108
+ boundaries: z
109
+ .object({
110
+ allowedPaths: z.array(z.string()),
111
+ forbiddenPaths: z.array(z.string()),
112
+ writeSet: z.array(z.string()),
113
+ })
114
+ .strict(),
115
+ runtime: z
116
+ .object({
117
+ executor: z.string().min(1),
118
+ modelHint: z.string().min(1).optional(),
119
+ resolvedModel: z.string().min(1).optional(),
120
+ skills: z.array(z.string()),
121
+ extensions: z.array(z.string()),
122
+ activeTools: z.array(z.string()),
123
+ })
124
+ .strict(),
125
+ contextDigest: sha256Schema,
126
+ })
127
+ .strict()
128
+ .superRefine((value, ctx) => {
129
+ value.orderedComponents.forEach((component, index) => {
130
+ if (component.index !== index) {
131
+ ctx.addIssue({
132
+ code: z.ZodIssueCode.custom,
133
+ path: ["orderedComponents", index, "index"],
134
+ message: "component indexes must be contiguous and ordered",
135
+ });
136
+ }
137
+ });
138
+ });
139
+ const bundleFileSchema = z
140
+ .object({
141
+ path: safeRelativePosixPathSchema,
142
+ sha256: sha256Schema,
143
+ bytes: z.number().int().nonnegative(),
144
+ mediaType: z.string().min(1),
145
+ origin: z.string().min(1),
146
+ redaction: z.enum(["none", "redacted", "omitted"]),
147
+ sourceSha256: sha256Schema.optional(),
148
+ })
149
+ .strict();
150
+ export const contextBundleManifestV1Schema = z
151
+ .object({
152
+ schemaVersion: z.literal(1),
153
+ bundleId: z.string().min(1),
154
+ createdAt: z.string().min(1),
155
+ source: z
156
+ .object({
157
+ repoIdentity: z.string().min(1),
158
+ taskId: z.string().min(1).optional(),
159
+ runId: z.string().min(1),
160
+ nodeId: z.string().min(1),
161
+ attempt: z.number().int().positive(),
162
+ pass: z.number().int().positive().optional(),
163
+ parentRunId: z.string().min(1).optional(),
164
+ })
165
+ .strict(),
166
+ profile: z.enum(["redacted", "raw"]),
167
+ target: z.enum(["generic", "pi-session", "pi-handoff", "readable"]),
168
+ reproducibility: z
169
+ .object({
170
+ level: z.enum([
171
+ "exact-pi-session",
172
+ "semantic-handoff",
173
+ "readable-transcript",
174
+ ]),
175
+ reasons: z.array(z.string()),
176
+ limitations: z.array(z.string()),
177
+ })
178
+ .strict(),
179
+ bindings: z
180
+ .object({
181
+ frozenRunSpecSha256: sha256Schema,
182
+ contextReceiptSha256: sha256Schema,
183
+ contextDigest: sha256Schema,
184
+ taskContractSha256: sha256Schema.optional(),
185
+ sourceBindingSha256: sha256Schema.optional(),
186
+ })
187
+ .strict(),
188
+ runtime: z
189
+ .object({
190
+ executor: z.string().min(1),
191
+ model: z.string().min(1).optional(),
192
+ provider: z.string().min(1).optional(),
193
+ piVersion: z.string().min(1).optional(),
194
+ skills: z.array(z
195
+ .object({
196
+ id: z.string().min(1),
197
+ version: z.string().min(1).optional(),
198
+ sha256: sha256Schema.optional(),
199
+ })
200
+ .strict()),
201
+ extensions: z.array(z
202
+ .object({
203
+ id: z.string().min(1),
204
+ packageName: z.string().min(1).optional(),
205
+ version: z.string().min(1).optional(),
206
+ status: z.string().min(1),
207
+ })
208
+ .strict()),
209
+ tools: z.array(z
210
+ .object({
211
+ name: z.string().min(1),
212
+ contractSha256: sha256Schema.optional(),
213
+ available: z.boolean(),
214
+ })
215
+ .strict()),
216
+ })
217
+ .strict(),
218
+ files: z.array(bundleFileSchema),
219
+ omissions: z.array(z
220
+ .object({
221
+ subject: z.string().min(1),
222
+ reasonCode: z.string().min(1),
223
+ detail: z.string().min(1).optional(),
224
+ })
225
+ .strict()),
226
+ security: z
227
+ .object({
228
+ secretScan: z.enum(["pass", "warning", "not-run"]),
229
+ findingCounts: z.record(z.string(), z.number().int().nonnegative()),
230
+ rawAcknowledgement: z
231
+ .object({
232
+ requestId: z.string().min(1),
233
+ acknowledgedAt: z.string().min(1),
234
+ })
235
+ .strict()
236
+ .optional(),
237
+ })
238
+ .strict(),
239
+ bundleSha256: sha256Schema,
240
+ })
241
+ .strict()
242
+ .superRefine((value, ctx) => {
243
+ const seen = new Set();
244
+ for (const [index, file] of value.files.entries()) {
245
+ const folded = file.path.normalize("NFC").toLocaleLowerCase("en-US");
246
+ if (seen.has(folded)) {
247
+ ctx.addIssue({
248
+ code: z.ZodIssueCode.custom,
249
+ path: ["files", index, "path"],
250
+ message: "bundle file paths must be unique under case folding",
251
+ });
252
+ }
253
+ seen.add(folded);
254
+ }
255
+ if (value.profile === "raw" && !value.security.rawAcknowledgement) {
256
+ ctx.addIssue({
257
+ code: z.ZodIssueCode.custom,
258
+ path: ["security", "rawAcknowledgement"],
259
+ message: "raw bundle requires explicit acknowledgement",
260
+ });
261
+ }
262
+ });
263
+ export const artifactRevisionV1Schema = z
264
+ .object({
265
+ schemaVersion: z.literal(1),
266
+ revisionId: z.string().min(1),
267
+ state: z.enum([
268
+ "staged",
269
+ "validated",
270
+ "rejected",
271
+ "expired",
272
+ "consumed",
273
+ "discarded",
274
+ ]),
275
+ parent: z
276
+ .object({
277
+ runId: z.string().min(1),
278
+ nodeId: z.string().min(1),
279
+ artifactPath: safeRelativePosixPathSchema,
280
+ artifactSha256: sha256Schema,
281
+ artifactSchemaId: z.string().min(1).optional(),
282
+ })
283
+ .strict(),
284
+ candidate: z
285
+ .object({
286
+ stagingPath: z.string().min(1),
287
+ sha256: sha256Schema,
288
+ bytes: z.number().int().nonnegative(),
289
+ mediaType: z.string().min(1),
290
+ uploadedAt: z.string().min(1),
291
+ uploadedBy: z.literal("operator"),
292
+ })
293
+ .strict(),
294
+ validation: z
295
+ .object({
296
+ status: z.enum(["pending", "pass", "fail"]),
297
+ checks: z.array(z
298
+ .object({
299
+ id: z.string().min(1),
300
+ status: z.enum(["pass", "warning", "fail"]),
301
+ detail: z.string().min(1).optional(),
302
+ })
303
+ .strict()),
304
+ })
305
+ .strict(),
306
+ diff: z
307
+ .object({
308
+ kind: z.enum(["text", "json", "binary"]),
309
+ summary: z.string(),
310
+ previewPath: safeRelativePosixPathSchema.optional(),
311
+ truncated: z.boolean(),
312
+ })
313
+ .strict(),
314
+ expiresAt: z.string().min(1),
315
+ /** Explicit staging lease (M4c): fresh lease blocks conflicting holders. */
316
+ lease: z
317
+ .object({
318
+ holder: z.string().min(1),
319
+ acquiredAt: z.string().min(1),
320
+ expiresAt: z.string().min(1),
321
+ })
322
+ .strict()
323
+ .optional(),
324
+ })
325
+ .strict();
326
+ export const artifactOverrideSetV1Schema = z
327
+ .object({
328
+ schemaVersion: z.literal(1),
329
+ overrides: z
330
+ .array(z
331
+ .object({
332
+ revisionId: z.string().min(1),
333
+ parentArtifact: z
334
+ .object({
335
+ runId: z.string().min(1),
336
+ nodeId: z.string().min(1),
337
+ path: safeRelativePosixPathSchema,
338
+ sha256: sha256Schema,
339
+ })
340
+ .strict(),
341
+ replacement: z
342
+ .object({
343
+ sha256: sha256Schema,
344
+ bytes: z.number().int().nonnegative(),
345
+ mediaType: z.string().min(1),
346
+ })
347
+ .strict(),
348
+ supersedes: z.string().min(1),
349
+ })
350
+ .strict())
351
+ .length(1),
352
+ overrideSetHash: sha256Schema,
353
+ })
354
+ .strict();
355
+ const workerContinuationIdSchema = z
356
+ .string()
357
+ .min(1)
358
+ .regex(/^[A-Za-z0-9._-]+$/);
359
+ /**
360
+ * Console-to-kernel authority for a Worker-owned artifact continuation.
361
+ * Every field is immutable and plan-hash-bound; the child identity is derived
362
+ * from the parent Worker run plus the single-use revision id.
363
+ */
364
+ export const workerContinuationAuthorityV1Schema = z
365
+ .object({
366
+ schemaVersion: z.literal(1),
367
+ parentWorkerRunId: workerContinuationIdSchema,
368
+ childWorkerRunId: workerContinuationIdSchema,
369
+ featureId: workerContinuationIdSchema,
370
+ taskId: workerContinuationIdSchema,
371
+ batchRunId: workerContinuationIdSchema,
372
+ revisionId: workerContinuationIdSchema,
373
+ })
374
+ .strict()
375
+ .superRefine((authority, ctx) => {
376
+ const expected = deriveWorkerContinuationRunId(authority.parentWorkerRunId, authority.revisionId);
377
+ if (authority.childWorkerRunId !== expected) {
378
+ ctx.addIssue({
379
+ code: z.ZodIssueCode.custom,
380
+ path: ["childWorkerRunId"],
381
+ message: `childWorkerRunId must equal ${expected}`,
382
+ });
383
+ }
384
+ });
385
+ export function deriveWorkerContinuationRunId(parentWorkerRunId, revisionId) {
386
+ const digest = createHash("sha256")
387
+ .update(`worker-continuation-v1\0${parentWorkerRunId}\0${revisionId}`)
388
+ .digest("hex")
389
+ .slice(0, 32);
390
+ return `wrc-${digest}`;
391
+ }
392
+ export function sortKeysDeep(value) {
393
+ if (Array.isArray(value))
394
+ return value.map(sortKeysDeep);
395
+ if (value && typeof value === "object") {
396
+ return Object.fromEntries(Object.entries(value)
397
+ .sort(([a], [b]) => a.localeCompare(b))
398
+ .map(([key, child]) => [key, sortKeysDeep(child)]));
399
+ }
400
+ return value;
401
+ }
402
+ export function canonicalSha256(value) {
403
+ return createHash("sha256")
404
+ .update(JSON.stringify(sortKeysDeep(value)))
405
+ .digest("hex");
406
+ }
407
+ export function computeContextDigest(context) {
408
+ return canonicalSha256(context);
409
+ }
410
+ export function computeBundleManifestSha256(manifest) {
411
+ return canonicalSha256(manifest);
412
+ }
413
+ export function computeOverrideSetHash(overrides) {
414
+ return canonicalSha256({ schemaVersion: 1, overrides });
415
+ }
416
+ export function isKnownContextTransferReasonCode(value) {
417
+ return contextTransferReasonCodeSchema.safeParse(value).success;
418
+ }
@@ -2252,6 +2252,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
2252
2252
  action: "dagFinalVerification",
2253
2253
  source: "loop-agent",
2254
2254
  },
2255
+ {
2256
+ command: "loop-agent dag context",
2257
+ coverage: "excluded",
2258
+ action: null,
2259
+ source: "loop-agent",
2260
+ exclusionReason: "Context export can disclose raw execution bytes and requires a human-selected output path; Console uses dedicated gated actions instead of the generic CLI wrapper.",
2261
+ },
2262
+ {
2263
+ command: "loop-agent dag artifact",
2264
+ coverage: "excluded",
2265
+ action: null,
2266
+ source: "loop-agent",
2267
+ exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
2268
+ },
2255
2269
  {
2256
2270
  command: "loop-agent workflow list",
2257
2271
  coverage: "advanced",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,93 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+ function assertContained(root, target, label) {
5
+ if (target === root || !target.startsWith(`${root}${path.sep}`)) {
6
+ throw new Error(`${label}: path escapes boundary: ${target}`);
7
+ }
8
+ }
9
+ /**
10
+ * Reject any symlink on the path between `boundaryRoot` and `targetPath`.
11
+ * This is the lexical/path-walk phase; callers that consume file bytes must
12
+ * still use readFileBoundarySafe so the checked object is the opened object.
13
+ */
14
+ export async function assertRealPathInsideBoundary(input) {
15
+ const root = path.resolve(input.boundaryRoot);
16
+ const target = path.resolve(input.targetPath);
17
+ assertContained(root, target, input.label);
18
+ const rootStat = await lstat(root).catch(() => undefined);
19
+ if (!rootStat) {
20
+ throw new Error(`${input.label}: boundary root is not a real directory`);
21
+ }
22
+ if (rootStat.isSymbolicLink()) {
23
+ throw new Error(`${input.label}: boundary root must not be a symlink`);
24
+ }
25
+ if (!rootStat.isDirectory()) {
26
+ throw new Error(`${input.label}: boundary root is not a real directory`);
27
+ }
28
+ const segments = path.relative(root, target).split(path.sep).filter(Boolean);
29
+ let current = root;
30
+ for (let index = 0; index < segments.length; index += 1) {
31
+ current = path.join(current, segments[index]);
32
+ const stat = await lstat(current).catch(() => undefined);
33
+ if (!stat) {
34
+ throw new Error(`${input.label}: path component missing: ${segments[index]}`);
35
+ }
36
+ if (stat.isSymbolicLink()) {
37
+ throw new Error(`${input.label}: symlink is forbidden on path: ${segments[index]}`);
38
+ }
39
+ if (index < segments.length - 1 && !stat.isDirectory()) {
40
+ throw new Error(`${input.label}: path ancestor is not a directory: ${segments[index]}`);
41
+ }
42
+ }
43
+ return target;
44
+ }
45
+ /** Reject a non-regular, symlinked, or hardlinked final entry before opening. */
46
+ export async function assertReadableRegularFile(input) {
47
+ const target = await assertRealPathInsideBoundary(input);
48
+ const stat = await lstat(target).catch(() => undefined);
49
+ if (!stat)
50
+ throw new Error(`${input.label}: path component missing`);
51
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1) {
52
+ throw new Error(`${input.label}: path must be a regular single-link file`);
53
+ }
54
+ return target;
55
+ }
56
+ /**
57
+ * Read one owned regular file without reopening an unchecked pathname.
58
+ *
59
+ * The preflight rejects normal FIFO/device/hardlink cases without opening
60
+ * them. O_NOFOLLOW rejects a final symlink introduced after preflight and
61
+ * O_NONBLOCK prevents a swapped FIFO from blocking. The post-open identity and
62
+ * realpath checks make an ancestor swap detectable before bytes are consumed.
63
+ */
64
+ export async function readFileBoundarySafe(input) {
65
+ const target = await assertReadableRegularFile(input);
66
+ const flags = fsConstants.O_RDONLY |
67
+ (fsConstants.O_NOFOLLOW ?? 0) |
68
+ (fsConstants.O_NONBLOCK ?? 0);
69
+ const handle = await open(target, flags);
70
+ try {
71
+ const opened = await handle.stat();
72
+ if (!opened.isFile() || opened.nlink !== 1) {
73
+ throw new Error(`${input.label}: path must be a regular single-link file`);
74
+ }
75
+ const [resolvedRoot, resolvedTarget, current] = await Promise.all([
76
+ realpath(path.resolve(input.boundaryRoot)),
77
+ realpath(target),
78
+ lstat(target),
79
+ ]);
80
+ assertContained(resolvedRoot, resolvedTarget, input.label);
81
+ if (current.isSymbolicLink() ||
82
+ !current.isFile() ||
83
+ current.nlink !== 1 ||
84
+ current.dev !== opened.dev ||
85
+ current.ino !== opened.ino) {
86
+ throw new Error(`${input.label}: path changed while opening`);
87
+ }
88
+ return await handle.readFile();
89
+ }
90
+ finally {
91
+ await handle.close().catch(() => { });
92
+ }
93
+ }
@@ -1,10 +1,34 @@
1
1
  const ENCODER = new TextEncoder();
2
2
  const DECODER = new TextDecoder();
3
+ export function redactSecretsWithFindings(text) {
4
+ let findingCount = 0;
5
+ const replaceChanged = (match, replacement) => {
6
+ if (match !== replacement)
7
+ findingCount += 1;
8
+ return replacement;
9
+ };
10
+ let redacted = text.replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)((?:Bearer\s+)?[^"'\s,}]+)/gi, (match, prefix) => replaceChanged(match, `${prefix}***`));
11
+ redacted = redacted.replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, (match, prefix) => replaceChanged(match, `${prefix}***`));
12
+ redacted = redacted.replace(/\bBearer\s+[^\s"']+/gi, (match) => replaceChanged(match, "Bearer ***"));
13
+ // High-confidence provider-style tokens (OpenAI sk-, GitHub ghp_/gho_,
14
+ // Slack xox*, AWS AKIA) are credential-like even without a key=value
15
+ // prefix (exec-plan 2026-08-25 D4: credential-like 默认阻断).
16
+ redacted = redacted.replace(/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pos]_[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})\b/g, (match) => replaceChanged(match, "***"));
17
+ return { redacted, findingCount };
18
+ }
19
+ export function redactSensitivePathsWithFindings(text) {
20
+ let findingCount = 0;
21
+ const replaceHome = (match) => {
22
+ if (match !== "<redacted-home>")
23
+ findingCount += 1;
24
+ return "<redacted-home>";
25
+ };
26
+ let redacted = text.replace(/\/(?:Users|home)\/[^/\s"'\\]+/g, replaceHome);
27
+ redacted = redacted.replace(/\b[A-Za-z]:\\Users\\[^\\/\s"']+/gi, replaceHome);
28
+ return { redacted, findingCount };
29
+ }
3
30
  export function redactSecrets(text) {
4
- return text
5
- .replace(/\bBearer\s+[^\s"']+/gi, "Bearer ***")
6
- .replace(/(\b[A-Z][A-Z0-9_]*=)[^\s]+/g, "$1***")
7
- .replace(/(["']?(?:api[_-]?key|token|password|secret|authorization)["']?\s*[:=]\s*["']?)([^"'\s,}]+)/gi, "$1***");
31
+ return redactSecretsWithFindings(text).redacted;
8
32
  }
9
33
  export function truncateUtf8Preview(text, maxBytes = 4096) {
10
34
  if (maxBytes <= 0)
@@ -14,6 +14,7 @@ export const taskKindSchema = z.enum([
14
14
  "frontend-implementation",
15
15
  "frontend-test",
16
16
  "backend-test",
17
+ "backend-test-analysis",
17
18
  "knowledge-sync",
18
19
  "knowledge-graph-bootstrap",
19
20
  ]);
@@ -165,7 +166,7 @@ const taskConfigObjectSchema = z.object({
165
166
  taskId: z.string(),
166
167
  title: z.string(),
167
168
  sourceFiles: z.array(z.string()),
168
- /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
169
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;frontend-test: 前端浏览器测试 DAG;backend-test-analysis: 只读后端测试分析 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
169
170
  taskKind: taskKindSchema.optional().default("standard"),
170
171
  /** true = taskKind 由人类显式选择(含显式 standard);DAG 路由必须尊重,不做语义改写。 */
171
172
  taskKindExplicit: z.boolean().optional(),
@@ -76,6 +76,7 @@ const SPECIAL_TEMPLATE_BY_TASK_KIND = {
76
76
  "frontend-implementation": "frontend-implementation",
77
77
  "frontend-test": "frontend-test-dag",
78
78
  "backend-test": "backend-test-dag",
79
+ "backend-test-analysis": "backend-test-analysis-dag",
79
80
  "knowledge-sync": "knowledge-sync-dag",
80
81
  "knowledge-graph-bootstrap": "knowledge-graph-bootstrap-dag",
81
82
  };
@@ -83,6 +84,7 @@ const TASK_KIND_ROUTING_REASON = {
83
84
  "frontend-implementation": 'taskKind "frontend-implementation" selects the dedicated frontend DAG template',
84
85
  "frontend-test": 'taskKind "frontend-test" selects the dedicated frontend test DAG template',
85
86
  "backend-test": 'taskKind "backend-test" selects the dedicated backend test DAG template',
87
+ "backend-test-analysis": 'taskKind "backend-test-analysis" selects the read-only backend test analysis DAG template',
86
88
  "knowledge-sync": 'taskKind "knowledge-sync" selects the dedicated knowledge-sync DAG template',
87
89
  "knowledge-graph-bootstrap": 'taskKind "knowledge-graph-bootstrap" selects the dedicated knowledge-graph bootstrap DAG template',
88
90
  };
@@ -90,6 +92,7 @@ const SPECIAL_REQUESTED_TEMPLATES = new Set([
90
92
  "frontend-implementation",
91
93
  "frontend-test-dag",
92
94
  "backend-test-dag",
95
+ "backend-test-analysis-dag",
93
96
  "knowledge-sync-dag",
94
97
  "knowledge-graph-bootstrap-dag",
95
98
  ]);
@@ -19,8 +19,8 @@
19
19
  import { createHash } from "node:crypto";
20
20
  import { existsSync, readFileSync, rmSync } from "node:fs";
21
21
  import path from "node:path";
22
- import { ensureSecureDir, writeSecureJsonSync, } from "../app-data.js";
23
- import { isTerminalOperationState, } from "../operation-store.js";
22
+ import { ensureSecureDir, writeSecureJsonSync, } from "../../../infrastructure/console/app-data.js";
23
+ import { isTerminalOperationState, } from "../../../infrastructure/console/operation-store.js";
24
24
  import { scrubSecrets } from "./explore-tools.js";
25
25
  const OPERATION_PREVIEW_MAX_CHARS = 1200;
26
26
  function boundedRedactedText(value, maxChars = OPERATION_PREVIEW_MAX_CHARS) {
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { createHash } from "node:crypto";
9
9
  import { existsSync, lstatSync } from "node:fs";
10
- import { readJsonIfExists, writeSecureJson, } from "../app-data.js";
10
+ import { readJsonIfExists, writeSecureJson, } from "../../../infrastructure/console/app-data.js";
11
11
  export const RESOURCE_PREFERENCES_SCHEMA_VERSION = 1;
12
12
  export class ResourcePreferencesError extends Error {
13
13
  code;
@@ -41,7 +41,7 @@ import { openSseResponse, writeSseEvent } from "../operation-sse.js";
41
41
  import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
42
42
  import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
43
43
  import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
44
- import { isTerminalOperationState } from "../operation-store.js";
44
+ import { isTerminalOperationState } from "../../../infrastructure/console/operation-store.js";
45
45
  /**
46
46
  * Reattach the durable event-ring order to session-store rows. The session
47
47
  * store remains the content authority; the ring only supplies chronology.
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import path from "node:path";
14
14
  import { readdir, rm } from "node:fs/promises";
15
- import { readJsonIfExists, writeSecureJson, } from "../app-data.js";
15
+ import { readJsonIfExists, writeSecureJson, } from "../../../infrastructure/console/app-data.js";
16
16
  import { dispatchOperatorAction } from "../operator-actions.js";
17
17
  import { authorizeOperatorChatTool } from "./tools.js";
18
18
  import { projectOperationForChat } from "./chat-event-store.js";
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import path from "node:path";
3
- import { readJsonIfExists, writeSecureJson } from "../app-data.js";
3
+ import { readJsonIfExists, writeSecureJson, } from "../../../infrastructure/console/app-data.js";
4
4
  function questionFile(root, sessionId) {
5
5
  const safe = sessionId.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 180);
6
6
  return path.join(root, `${safe}.json`);
@@ -4,7 +4,7 @@ import { createNpmUpdateClient } from "../../shared/update/npm-client.js";
4
4
  import { acquireUpdateLock, updateLockPath } from "../../shared/update/state.js";
5
5
  import { readInitRuntimeActivity } from "../observability/init-runtime-activity.js";
6
6
  import { performConsoleUpdateAndInit, } from "./console-update-and-init.js";
7
- import { TERMINAL_OPERATION_STATES } from "./operation-store.js";
7
+ import { TERMINAL_OPERATION_STATES } from "../../infrastructure/console/operation-store.js";
8
8
  export function createConsoleUpdateExecutor(input) {
9
9
  return async (channel) => {
10
10
  const currentVersion = readPackageVersion(input.packageRoot);