@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-beta.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +130 -3
- package/README.md +2 -2
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +21 -5
- package/dist/cli/program.js +61 -4
- package/dist/commands/client-recovery.js +8 -36
- 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/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +39 -12
- package/dist/executors/pi-sdk-executor.js +36 -5
- package/dist/executors/shell-executor.js +64 -17
- package/dist/governance/checks.js +1 -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/{worker → infrastructure}/console/operation-store.js +28 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -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 +23 -1
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +2 -1
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +84 -13
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +487 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +28 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +32 -3
- package/dist/worker/console/chat/user-questions.js +1 -1
- 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 +21 -3
- 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/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 +124 -8
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +864 -13
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +177 -44
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
- package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
- package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
- package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +56 -5
- package/dist/worker/console/workspace-registry.js +335 -62
- 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/static/api.js +69 -5
- 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/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1108 -135
- package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +210 -10
- 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-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- 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/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +151 -4
- 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 +555 -1
- package/dist/workflows/dag/rerun-run.js +601 -15
- package/dist/workflows/dag/runner.js +20 -1
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +183 -7
- package/docs/architecture/runtime-boundaries.md +8 -7
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/agent-dag.schema.json +32 -0
- package/docs/templates/backend-test-dag.json +6 -5
- package/docs/templates/init-managed-agents.md +1 -1
- package/docs/templates/product-line/task.yaml +1 -1
- package/package.json +5 -2
- package/skills/loop-agent/references/command-reference.md +21 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
- /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
|
+
}
|
|
@@ -51,6 +51,8 @@ const OFFICIAL_ACTION_DESCRIPTIONS = {
|
|
|
51
51
|
workerSchedulerStatus: "Night-scheduler status overview (agent-worker). Use to check whether overnight jobs are healthy.",
|
|
52
52
|
workerSchedulerLedger: "Night-scheduler harvest ledger (agent-worker). Use to audit what overnight runs produced.",
|
|
53
53
|
workerSchedulerDoctor: "Diagnose night-scheduler health (agent-worker). Use when overnight jobs fail to fire or harvest.",
|
|
54
|
+
dagResume: "Unpause a run paused at a human decision checkpoint. Orphaned runs (dead runner, liveness=orphaned) are NOT continuable — do not resume them: use dagReconcileRun --action supersede to archive and then advance/rerun the task fresh.",
|
|
55
|
+
dagReconcileRun: "Reconcile an orphaned run (dead runner, liveness=orphaned). These are NOT continuable: supersede archives the run for a fresh same-task advance/rerun; abandon discards it. resume only unpauses human-decision checkpoints — never for orphaned runs.",
|
|
54
56
|
};
|
|
55
57
|
const officialAction = (action, cli, kind, coverage) => ({
|
|
56
58
|
action,
|
|
@@ -817,7 +819,7 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
817
819
|
"BINDING_DRIFT",
|
|
818
820
|
"HUMAN_CONFIRMATION_REQUIRED",
|
|
819
821
|
],
|
|
820
|
-
description: "
|
|
822
|
+
description: "Returns SYNCHRONOUSLY (not an operation): it immediately yields a session-bound single-use executionId + receipt, which you then pass to runDag to actually execute. It is a server-side G2 assessment (bounded writeSet ⊆ allowedPaths, no forbidden overlap, no broad/destructive risk, structured verification). The model never passes a raw DAG path.",
|
|
821
823
|
inputParams: [
|
|
822
824
|
{
|
|
823
825
|
name: "taskId",
|
|
@@ -1067,6 +1069,12 @@ export function buildOperatorCapabilitiesDocument() {
|
|
|
1067
1069
|
required: false,
|
|
1068
1070
|
description: "verification entries as label:command strings",
|
|
1069
1071
|
},
|
|
1072
|
+
{
|
|
1073
|
+
name: "approveGate",
|
|
1074
|
+
type: "string",
|
|
1075
|
+
required: false,
|
|
1076
|
+
description: "optional write-set gate approval token `<gate-id>:sha256:<64-hex>` exactly as reported by task status gate.approveCommand; forwards as --approve-gate and executes the approved DAG as this background operation (preferred over a foreground terminal)",
|
|
1077
|
+
},
|
|
1070
1078
|
],
|
|
1071
1079
|
modelCallable: "always",
|
|
1072
1080
|
humanConfirmation: "none",
|
|
@@ -2244,6 +2252,20 @@ export const OPERATOR_COMMAND_COVERAGE = Object.freeze([
|
|
|
2244
2252
|
action: "dagFinalVerification",
|
|
2245
2253
|
source: "loop-agent",
|
|
2246
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
|
+
},
|
|
2247
2269
|
{
|
|
2248
2270
|
command: "loop-agent workflow list",
|
|
2249
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
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const CHECKPOINT_MAX_BYTES = 12 * 1024;
|
|
2
|
+
function boundText(value, maxChars) {
|
|
3
|
+
if (typeof value !== "string" || !value.trim())
|
|
4
|
+
return "";
|
|
5
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars - 1)}…`;
|
|
6
|
+
}
|
|
7
|
+
function asRecord(value) {
|
|
8
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
function extractLastUserGoal(messages) {
|
|
13
|
+
if (!Array.isArray(messages))
|
|
14
|
+
return "";
|
|
15
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
16
|
+
const message = asRecord(messages[index]);
|
|
17
|
+
if (!message || message.role !== "user")
|
|
18
|
+
continue;
|
|
19
|
+
const content = message.content;
|
|
20
|
+
if (typeof content === "string")
|
|
21
|
+
return boundText(content, 800);
|
|
22
|
+
if (Array.isArray(content)) {
|
|
23
|
+
const texts = content
|
|
24
|
+
.map((block) => {
|
|
25
|
+
const rec = asRecord(block);
|
|
26
|
+
return typeof rec?.text === "string" ? rec.text : "";
|
|
27
|
+
})
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
if (texts.length > 0)
|
|
30
|
+
return boundText(texts.join("\n"), 800);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
function extractTodos(details) {
|
|
36
|
+
const record = asRecord(details);
|
|
37
|
+
const todos = record?.todos ?? record?.items;
|
|
38
|
+
if (!Array.isArray(todos))
|
|
39
|
+
return "";
|
|
40
|
+
const lines = todos
|
|
41
|
+
.slice(0, 24)
|
|
42
|
+
.map((item) => {
|
|
43
|
+
const rec = asRecord(item);
|
|
44
|
+
const status = typeof rec?.status === "string" ? rec.status : "pending";
|
|
45
|
+
const content = typeof rec?.content === "string"
|
|
46
|
+
? rec.content
|
|
47
|
+
: typeof rec?.title === "string"
|
|
48
|
+
? rec.title
|
|
49
|
+
: "";
|
|
50
|
+
return content ? `- [${status}] ${content}` : "";
|
|
51
|
+
})
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
return boundText(lines.join("\n"), 1200);
|
|
54
|
+
}
|
|
55
|
+
function extractFileOps(fileOps) {
|
|
56
|
+
const record = asRecord(fileOps);
|
|
57
|
+
if (!record)
|
|
58
|
+
return "";
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const key of ["edited", "written", "read", "deleted", "ops"]) {
|
|
61
|
+
const value = record[key];
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
parts.push(`${key}: ${value
|
|
64
|
+
.slice(0, 20)
|
|
65
|
+
.map((item) => (typeof item === "string" ? item : JSON.stringify(item)))
|
|
66
|
+
.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
else if (typeof value === "string" && value) {
|
|
69
|
+
parts.push(`${key}: ${value}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (parts.length === 0) {
|
|
73
|
+
try {
|
|
74
|
+
return boundText(JSON.stringify(fileOps), 800);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return boundText(parts.join("\n"), 800);
|
|
81
|
+
}
|
|
82
|
+
export function buildOverflowCheckpoint(input) {
|
|
83
|
+
const preparation = input.preparation;
|
|
84
|
+
const firstKeptEntryId = typeof preparation?.firstKeptEntryId === "string" &&
|
|
85
|
+
preparation.firstKeptEntryId
|
|
86
|
+
? preparation.firstKeptEntryId
|
|
87
|
+
: undefined;
|
|
88
|
+
const tokensBefore = typeof preparation?.tokensBefore === "number" &&
|
|
89
|
+
Number.isFinite(preparation.tokensBefore)
|
|
90
|
+
? preparation.tokensBefore
|
|
91
|
+
: undefined;
|
|
92
|
+
if (!firstKeptEntryId || tokensBefore === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const sections = [
|
|
95
|
+
"Deterministic overflow checkpoint (no extra summary request).",
|
|
96
|
+
boundText(preparation?.previousSummary, 2000)
|
|
97
|
+
? `Previous summary:\n${boundText(preparation?.previousSummary, 2000)}`
|
|
98
|
+
: "",
|
|
99
|
+
extractLastUserGoal(preparation?.messagesToSummarize)
|
|
100
|
+
? `Latest user goal:\n${extractLastUserGoal(preparation?.messagesToSummarize)}`
|
|
101
|
+
: "",
|
|
102
|
+
extractTodos(input.todos) ? `Open todos:\n${extractTodos(input.todos)}` : "",
|
|
103
|
+
extractFileOps(preparation?.fileOps)
|
|
104
|
+
? `File ops:\n${extractFileOps(preparation?.fileOps)}`
|
|
105
|
+
: "",
|
|
106
|
+
typeof input.lastError === "string" && input.lastError
|
|
107
|
+
? `Latest error:\n${boundText(input.lastError, 400)}`
|
|
108
|
+
: "",
|
|
109
|
+
"Next step: continue the interrupted turn from the kept recent messages. Do not repeat completed file writes.",
|
|
110
|
+
].filter(Boolean);
|
|
111
|
+
let summary = sections.join("\n\n");
|
|
112
|
+
if (Buffer.byteLength(summary, "utf8") > CHECKPOINT_MAX_BYTES) {
|
|
113
|
+
summary = `${summary.slice(0, CHECKPOINT_MAX_BYTES - 1)}…`;
|
|
114
|
+
}
|
|
115
|
+
return { summary, firstKeptEntryId, tokensBefore };
|
|
116
|
+
}
|