@tea-agent/loop-agent 0.39.3 → 0.40.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -92
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +12 -0
- package/dist/cli/program.js +23 -1
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/executors/dag-pi-executor.js +108 -19
- package/dist/executors/pi-executor.js +4 -2
- package/dist/executors/pi-sdk-executor.js +49 -1
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +42 -2
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +35 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +14 -0
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +12 -1
- package/dist/task/source-prepare/fragment-inventory.js +1 -14
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/chat-event-store.js +77 -7
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +389 -56
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +94 -64
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +1 -1
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +104 -3
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +128 -0
- package/dist/worker/console/routes.js +927 -8
- package/dist/worker/console/security.js +39 -4
- package/dist/worker/console/server.js +532 -187
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CTx1TrNa.js → abnfDiagram-N423BO3Z-CpWvwARc.js} +1 -1
- package/dist/worker/console/static/assets/{arc-B4i9cVxX.js → arc-DctXgO-g.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-BAXE8_Rl.js → architectureDiagram-T3A2C74G-D4mIkoQl.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-C5Xh59at.js → blockDiagram-VBNYF7ZC-ClG0--KO.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B_asOzAu.js → c4Diagram-5PPSVZJV-CwdLk78T.js} +1 -1
- package/dist/worker/console/static/assets/channel-C9VQZ-ru.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-wRO_iRNc.js → chunk-2GRJ4B5K-DKczcu-O.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-ByOaSh_G.js → chunk-2Q5K7J3B-BV6F-vBI.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLzudan.js → chunk-5RXB4S5H-DtojVnvA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-JCNGkXYT.js → chunk-5VM5RSS4-BjDQRGnO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BhrPXXMZ.js → chunk-6Q2QTUOP-I0fV6ngU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-TJ5JuEBT.js → chunk-GF5L2VYU-BM8C-mmC.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-VVthn_mD.js → chunk-JWPE2WC7-BEK-a15e.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D60-Mny4.js → chunk-KBJHAD2P-CQsg_fSR.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-zXv7aVye.js → chunk-RYQCIY6F-B-BcaUm3.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-By9VQoi1.js → chunk-XXDRQBXY-D1Bu08fg.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BsCkw5MF.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-C4zQLe-M.js → cose-bilkent-JH36ORCC-q8nwtxBR.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-wh5N2ReN.js → cynefin-VYW2F7L2-CruhpURw.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Dlu5wIqs.js → cynefinDiagram-MW4NZA55-CJne6iYF.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CrDgthc6.js → dagre-VZM6K2ZE-CbeLvBU5.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-zN1clg27.js → diagram-7IWD3JNH-DmCxH1Pe.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BN-o7J7-.js → diagram-B4RE2ZJO-DJ5OYhtC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-C8SuPt0d.js → diagram-LBJQPF4R-B-UN-hln.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BoizuHbZ.js → diagram-Q27KOJAE-CcEro8Ey.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BzUzXixG.js → diagram-UB23O5K3-BBBzDden.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Ev3lA4Cf.js → ebnfDiagram-BXEA7PRR-xKEyH-oo.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-GALUzRbs.js → erDiagram-JOGREHBK-C7xl63VC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CZJN65Sb.js → flowDiagram-UKHOOZJN-nthQQ9nR.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-JLB9OESl.js → ganttDiagram-PKOTCBZU-CWN5mE33.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CcvCr4GD.js → gitGraphDiagram-DS77QQ5N-Wn86tQ6N.js} +1 -1
- package/dist/worker/console/static/assets/index-D-0NVyMk.css +1 -0
- package/dist/worker/console/static/assets/index-gwyOxQiS.js +407 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-HG5z7aVg.js → infoDiagram-6WML65LV-_WQymGtW.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BHEu9chR.js → ishikawaDiagram-WSZJBQD7-COBeivBm.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Cp_Up8-a.js → journeyDiagram-NVQOT4AX-CwGOJl3Q.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DvVeY-em.js → kanban-definition-27J2QSJJ-Ci0CJ5Vi.js} +1 -1
- package/dist/worker/console/static/assets/{linear-aGmcjgTn.js → linear-CPjMq6vN.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Biyo-tqA.js → mermaid.core-Z0wGoe0b.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DyxY2pIb.js → mindmap-definition-FAOFIHXS-dISked0d.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-CkdGuc50.js → pegDiagram-VL7TDLO6-BJxgof7Y.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-NNQI9xVR.js → pieDiagram-7S7Q4E2Y-CIblU8eW.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-PAJkuz_G.js → quadrantDiagram-CIZ2JOQS-vwYiqh8E.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-ZwrQod0O.js → railroadDiagram-AXF67PYL-_iOsVeFm.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D3ZfrzM2.js → requirementDiagram-LRYGKXZP-Dk0MLxO_.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-DDGFmWk5.js → sankeyDiagram-W5VNT64P-Dx7HtLVW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DO6mmXTA.js → sequenceDiagram-SI44F4Z6-DRuWjlpL.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-DMbCQiJt.js → sizeCapture-X5ZJPWSS-Bhy9DQAA.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Di_YUEfC.js → stateDiagram-OKZ733FA-DAx9twnU.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Dmdf8cGy.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-Bn2PphNJ.js → swimlanes-SLNWSIFB-okMlvVrM.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-W1IcAFff.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DPh8pRsp.js → timeline-definition-Z64GVDOM-DXqCKe37.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-GMfl26pW.js → vennDiagram-T6HMQDX7-CeIUkwBh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DTlmVrmF.js → wardleyDiagram-T6FBY63Y-BP76BKTN.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YGBjKAS5.js → xychartDiagram-ELKLHX3M-BpVI0OpF.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/console-types.js +29 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +36 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +10 -11
- package/dist/worker/console/static-src/app/useRunProgress.js +2 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +2 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +3 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +159 -44
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +260 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/static/api.js +94 -10
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/index.html +1 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -100
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +175 -1
- package/dist/worker/observe/static/styles.css +1039 -128
- package/dist/worker/observe/static/views/dag-inspector.js +1218 -60
- package/dist/worker/observe/static/views/dag.js +2 -1
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +75 -10
- package/dist/worker/observe/static/views/task.js +1 -1
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +11 -10
- package/dist/workflows/dag/backend-test-writer-completeness.js +7 -12
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +1 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1 -89
- package/dist/workflows/dag/init-hybrid.js +259 -47
- package/dist/workflows/dag/interrupt-request.js +3 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +272 -0
- package/dist/workflows/dag/rerun-run.js +390 -12
- package/dist/workflows/dag/skill-snapshot.js +1 -0
- package/dist/workflows/dag/types.js +203 -8
- package/docs/README.md +1 -0
- package/docs/architecture/runtime-boundaries.md +6 -6
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/agent-dag.schema.json +50 -0
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/product-line/task.yaml +1 -1
- package/harness.json +3 -3
- package/package.json +5 -1
- package/skills/loop-agent/references/command-reference.md +6 -0
- package/dist/worker/console/static/assets/channel-Dj_4Moei.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C0YYAyCa.js +0 -1
- package/dist/worker/console/static/assets/index-CW88Fjbw.js +0 -406
- package/dist/worker/console/static/assets/index-DP0ZJl39.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrdDvela.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPkEAULl.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
- /package/dist/{worker → infrastructure}/console/operation-store.js +0 -0
- /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
|
+
}
|
|
@@ -2244,6 +2244,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
2244
2244
|
action: "dagFinalVerification",
|
|
2245
2245
|
source: "loop-agent",
|
|
2246
2246
|
},
|
|
2247
|
+
{
|
|
2248
|
+
command: "loop-agent dag context",
|
|
2249
|
+
coverage: "excluded",
|
|
2250
|
+
action: null,
|
|
2251
|
+
source: "loop-agent",
|
|
2252
|
+
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.",
|
|
2253
|
+
},
|
|
2254
|
+
{
|
|
2255
|
+
command: "loop-agent dag artifact",
|
|
2256
|
+
coverage: "excluded",
|
|
2257
|
+
action: null,
|
|
2258
|
+
source: "loop-agent",
|
|
2259
|
+
exclusionReason: "Artifact revision staging consumes a human-selected local file and remains on dedicated Console/CLI mutation gates.",
|
|
2260
|
+
},
|
|
2247
2261
|
{
|
|
2248
2262
|
command: "loop-agent workflow list",
|
|
2249
2263
|
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
|
+
}
|
package/dist/shared/preview.js
CHANGED
|
@@ -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
|
]);
|
|
@@ -147,6 +148,8 @@ export const backendTestConfigSchema = z
|
|
|
147
148
|
gapDoc: z.string().min(1).optional(),
|
|
148
149
|
/** gap-fill pytest execution scope: only affected items (default) or all eligible. */
|
|
149
150
|
executeScope: z.enum(["affected", "all"]).optional(),
|
|
151
|
+
/** Backend-test-only workspace mutation control; omitted means Git guard. */
|
|
152
|
+
workspaceControl: z.enum(["git", "filesystem-only"]).optional(),
|
|
150
153
|
})
|
|
151
154
|
.strict()
|
|
152
155
|
.optional();
|
|
@@ -161,7 +164,7 @@ const taskConfigObjectSchema = z.object({
|
|
|
161
164
|
taskId: z.string(),
|
|
162
165
|
title: z.string(),
|
|
163
166
|
sourceFiles: z.array(z.string()),
|
|
164
|
-
/** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
|
|
167
|
+
/** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;frontend-test: 前端浏览器测试 DAG;backend-test-analysis: 只读后端测试分析 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
|
|
165
168
|
taskKind: taskKindSchema.optional().default("standard"),
|
|
166
169
|
/** true = taskKind 由人类显式选择(含显式 standard);DAG 路由必须尊重,不做语义改写。 */
|
|
167
170
|
taskKindExplicit: z.boolean().optional(),
|
|
@@ -237,5 +240,13 @@ export const taskConfigSchema = z.preprocess((raw) => {
|
|
|
237
240
|
if ("executor" in obj || "cursorModel" in obj) {
|
|
238
241
|
throw new Error(CURSOR_TASK_FIELD_REMOVED_ERROR);
|
|
239
242
|
}
|
|
243
|
+
const backendTest = obj.backendTest;
|
|
244
|
+
if (backendTest &&
|
|
245
|
+
typeof backendTest === "object" &&
|
|
246
|
+
!Array.isArray(backendTest) &&
|
|
247
|
+
"workspaceControl" in backendTest &&
|
|
248
|
+
obj.taskKind !== "backend-test") {
|
|
249
|
+
throw new Error("backendTest.workspaceControl is only valid when taskKind is backend-test");
|
|
250
|
+
}
|
|
240
251
|
return raw;
|
|
241
252
|
}, taskConfigObjectSchema);
|
|
@@ -92,18 +92,6 @@ function isAtxHeading(line) {
|
|
|
92
92
|
return null;
|
|
93
93
|
return { level: match[1].length, text: match[2].trim() };
|
|
94
94
|
}
|
|
95
|
-
/** Find the nearest explicit AC heading in a nested Markdown heading path.
|
|
96
|
-
* A subsection such as "异常场景" belongs to its enclosing `#### AC-*` block,
|
|
97
|
-
* even though the subsection heading itself has no ID. */
|
|
98
|
-
function nearestAncestorExplicitAc(headingPath) {
|
|
99
|
-
const ancestors = headingPath.split(" > ").slice(0, -1).reverse();
|
|
100
|
-
for (const heading of ancestors) {
|
|
101
|
-
const matched = matchExplicitAcHeading(heading);
|
|
102
|
-
if (matched)
|
|
103
|
-
return matched;
|
|
104
|
-
}
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
95
|
function isListItem(line) {
|
|
108
96
|
return /^\s*(?:[-*+]|\d+[.)])\s+(?:\[(?: |x|X)\]\s+)?/.test(line);
|
|
109
97
|
}
|
|
@@ -185,7 +173,6 @@ export function splitDocumentIntoFragments(doc) {
|
|
|
185
173
|
// V4 显式 AC 标题(`#### AC-*`)本身即验收段:标题行的 ID 原样保留。
|
|
186
174
|
const lastHeadingSegment = section.headingPath.split(" > ").pop() ?? section.headingPath;
|
|
187
175
|
const explicitSectionAc = matchExplicitAcHeading(lastHeadingSegment);
|
|
188
|
-
const ancestorExplicitAc = nearestAncestorExplicitAc(section.headingPath);
|
|
189
176
|
const sectionKind = classifySectionHeading(section.headingPath);
|
|
190
177
|
const effectiveKind = explicitSectionAc ? "acceptance" : sectionKind;
|
|
191
178
|
const bodyStart = section.headingPath
|
|
@@ -194,7 +181,7 @@ export function splitDocumentIntoFragments(doc) {
|
|
|
194
181
|
const bodyEnd = section.endLine;
|
|
195
182
|
const bodyLines = lines.slice(Math.max(0, bodyStart - 1), Math.min(bodyEnd, lines.length));
|
|
196
183
|
// 当前验收章节内最近的显式 AC ID(V4 标题或 AC 条目),正文片段继承它。
|
|
197
|
-
let pendingAcId =
|
|
184
|
+
let pendingAcId = null;
|
|
198
185
|
const emit = (fragment) => {
|
|
199
186
|
const explicit = fragment.explicitId ?? null;
|
|
200
187
|
let id = explicit;
|
|
@@ -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
|
]);
|