@cleocode/core 2026.6.12 → 2026.6.13
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/dist/conduit/local-transport.d.ts +12 -0
- package/dist/conduit/local-transport.d.ts.map +1 -1
- package/dist/conduit/local-transport.js +53 -23
- package/dist/conduit/local-transport.js.map +1 -1
- package/dist/crypto/credentials.d.ts +37 -0
- package/dist/crypto/credentials.d.ts.map +1 -1
- package/dist/crypto/credentials.js +110 -0
- package/dist/crypto/credentials.js.map +1 -1
- package/dist/dispatch/contracts/derive-output-contract.d.ts +86 -0
- package/dist/dispatch/contracts/derive-output-contract.d.ts.map +1 -0
- package/dist/dispatch/contracts/derive-output-contract.js +347 -0
- package/dist/dispatch/contracts/derive-output-contract.js.map +1 -0
- package/dist/dispatch/contracts/ensures-schema.d.ts +77 -0
- package/dist/dispatch/contracts/ensures-schema.d.ts.map +1 -0
- package/dist/dispatch/contracts/ensures-schema.js +91 -0
- package/dist/dispatch/contracts/ensures-schema.js.map +1 -0
- package/dist/dispatch/contracts/output-contracts.d.ts +27 -5
- package/dist/dispatch/contracts/output-contracts.d.ts.map +1 -1
- package/dist/dispatch/contracts/output-contracts.js +29 -6
- package/dist/dispatch/contracts/output-contracts.js.map +1 -1
- package/dist/docs/export-document.js +2294 -1250
- package/dist/docs/export-document.js.map +3 -3
- package/dist/go/driver.d.ts +132 -7
- package/dist/go/driver.d.ts.map +1 -1
- package/dist/go/driver.js +135 -10
- package/dist/go/driver.js.map +1 -1
- package/dist/go/index.d.ts +1 -1
- package/dist/go/index.d.ts.map +1 -1
- package/dist/go/index.js +1 -1
- package/dist/go/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +13 -10
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +11 -11
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle/engine-ops.d.ts.map +1 -1
- package/dist/lifecycle/engine-ops.js +7 -4
- package/dist/lifecycle/engine-ops.js.map +1 -1
- package/dist/lifecycle/ivtr-loop.d.ts +108 -183
- package/dist/lifecycle/ivtr-loop.d.ts.map +1 -1
- package/dist/lifecycle/ivtr-loop.js +207 -522
- package/dist/lifecycle/ivtr-loop.js.map +1 -1
- package/dist/llm/auxiliary-fallback.js +1 -1
- package/dist/llm/auxiliary-fallback.js.map +1 -1
- package/dist/llm/cli-ops.d.ts +14 -1
- package/dist/llm/cli-ops.d.ts.map +1 -1
- package/dist/llm/cli-ops.js +41 -12
- package/dist/llm/cli-ops.js.map +1 -1
- package/dist/llm/concrete-session.d.ts.map +1 -1
- package/dist/llm/concrete-session.js +4 -0
- package/dist/llm/concrete-session.js.map +1 -1
- package/dist/llm/credentials.d.ts +35 -0
- package/dist/llm/credentials.d.ts.map +1 -1
- package/dist/llm/credentials.js +49 -0
- package/dist/llm/credentials.js.map +1 -1
- package/dist/llm/index.d.ts +4 -0
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +4 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/pi/gondolin-loader.d.ts +241 -0
- package/dist/llm/pi/gondolin-loader.d.ts.map +1 -0
- package/dist/llm/pi/gondolin-loader.js +195 -0
- package/dist/llm/pi/gondolin-loader.js.map +1 -0
- package/dist/llm/pi/index.d.ts +28 -0
- package/dist/llm/pi/index.d.ts.map +1 -0
- package/dist/llm/pi/index.js +27 -0
- package/dist/llm/pi/index.js.map +1 -0
- package/dist/llm/pi/pi-agent-adapter.d.ts +135 -0
- package/dist/llm/pi/pi-agent-adapter.d.ts.map +1 -0
- package/dist/llm/pi/pi-agent-adapter.js +329 -0
- package/dist/llm/pi/pi-agent-adapter.js.map +1 -0
- package/dist/llm/pi/pi-errors.d.ts +161 -0
- package/dist/llm/pi/pi-errors.d.ts.map +1 -0
- package/dist/llm/pi/pi-errors.js +296 -0
- package/dist/llm/pi/pi-errors.js.map +1 -0
- package/dist/llm/pi/pi-execution-env.d.ts +286 -0
- package/dist/llm/pi/pi-execution-env.d.ts.map +1 -0
- package/dist/llm/pi/pi-execution-env.js +406 -0
- package/dist/llm/pi/pi-execution-env.js.map +1 -0
- package/dist/llm/pi/pi-gondolin-env.d.ts +227 -0
- package/dist/llm/pi/pi-gondolin-env.d.ts.map +1 -0
- package/dist/llm/pi/pi-gondolin-env.js +541 -0
- package/dist/llm/pi/pi-gondolin-env.js.map +1 -0
- package/dist/llm/pi/pi-session-storage.d.ts +154 -0
- package/dist/llm/pi/pi-session-storage.d.ts.map +1 -0
- package/dist/llm/pi/pi-session-storage.js +314 -0
- package/dist/llm/pi/pi-session-storage.js.map +1 -0
- package/dist/llm/pi/pi-stream-fn.d.ts +124 -0
- package/dist/llm/pi/pi-stream-fn.d.ts.map +1 -0
- package/dist/llm/pi/pi-stream-fn.js +504 -0
- package/dist/llm/pi/pi-stream-fn.js.map +1 -0
- package/dist/llm/pi/pi-tool-bridge.d.ts +99 -0
- package/dist/llm/pi/pi-tool-bridge.d.ts.map +1 -0
- package/dist/llm/pi/pi-tool-bridge.js +117 -0
- package/dist/llm/pi/pi-tool-bridge.js.map +1 -0
- package/dist/llm/pi/pi-types.d.ts +56 -0
- package/dist/llm/pi/pi-types.d.ts.map +1 -0
- package/dist/llm/pi/pi-types.js +16 -0
- package/dist/llm/pi/pi-types.js.map +1 -0
- package/dist/llm/pi/resolve-execution-env.d.ts +194 -0
- package/dist/llm/pi/resolve-execution-env.d.ts.map +1 -0
- package/dist/llm/pi/resolve-execution-env.js +132 -0
- package/dist/llm/pi/resolve-execution-env.js.map +1 -0
- package/dist/llm/plugin-facade.js +2253 -1151
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/llm/role-executor.d.ts.map +1 -1
- package/dist/llm/role-executor.js +8 -5
- package/dist/llm/role-executor.js.map +1 -1
- package/dist/llm/role-resolver.d.ts +46 -14
- package/dist/llm/role-resolver.d.ts.map +1 -1
- package/dist/llm/role-resolver.js +178 -20
- package/dist/llm/role-resolver.js.map +1 -1
- package/dist/llm/sealed-credential.d.ts +125 -0
- package/dist/llm/sealed-credential.d.ts.map +1 -0
- package/dist/llm/sealed-credential.js +133 -0
- package/dist/llm/sealed-credential.js.map +1 -0
- package/dist/llm/session-factory.d.ts.map +1 -1
- package/dist/llm/session-factory.js +6 -2
- package/dist/llm/session-factory.js.map +1 -1
- package/dist/llm/system-key.d.ts +67 -0
- package/dist/llm/system-key.d.ts.map +1 -0
- package/dist/llm/system-key.js +87 -0
- package/dist/llm/system-key.js.map +1 -0
- package/dist/llm/system-of-use-registry.d.ts +119 -0
- package/dist/llm/system-of-use-registry.d.ts.map +1 -0
- package/dist/llm/system-of-use-registry.js +200 -0
- package/dist/llm/system-of-use-registry.js.map +1 -0
- package/dist/llm/system-resolver.d.ts +32 -10
- package/dist/llm/system-resolver.d.ts.map +1 -1
- package/dist/llm/system-resolver.js +70 -18
- package/dist/llm/system-resolver.js.map +1 -1
- package/dist/memory/brain-writer-thread.d.ts.map +1 -1
- package/dist/memory/brain-writer-thread.js +116 -11
- package/dist/memory/brain-writer-thread.js.map +1 -1
- package/dist/memory/claude-mem-migration.d.ts.map +1 -1
- package/dist/memory/claude-mem-migration.js +21 -0
- package/dist/memory/claude-mem-migration.js.map +1 -1
- package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
- package/dist/memory/graph-memory-bridge.js +32 -25
- package/dist/memory/graph-memory-bridge.js.map +1 -1
- package/dist/memory/llm-backend-resolver.js +23 -5
- package/dist/memory/llm-backend-resolver.js.map +1 -1
- package/dist/nexus/migrations/M-split-graph.d.ts.map +1 -1
- package/dist/nexus/migrations/M-split-graph.js +60 -53
- package/dist/nexus/migrations/M-split-graph.js.map +1 -1
- package/dist/orchestrate/pivot.d.ts.map +1 -1
- package/dist/orchestrate/pivot.js +20 -2
- package/dist/orchestrate/pivot.js.map +1 -1
- package/dist/release/engine-ops.js +2 -2
- package/dist/release/engine-ops.js.map +1 -1
- package/dist/routing/capability-matrix.d.ts.map +1 -1
- package/dist/routing/capability-matrix.js +0 -57
- package/dist/routing/capability-matrix.js.map +1 -1
- package/dist/selfimprove/budget.d.ts +206 -0
- package/dist/selfimprove/budget.d.ts.map +1 -0
- package/dist/selfimprove/budget.js +203 -0
- package/dist/selfimprove/budget.js.map +1 -0
- package/dist/selfimprove/dhq-adapter.d.ts +105 -0
- package/dist/selfimprove/dhq-adapter.d.ts.map +1 -0
- package/dist/selfimprove/dhq-adapter.js +95 -0
- package/dist/selfimprove/dhq-adapter.js.map +1 -0
- package/dist/selfimprove/draft-pr.d.ts +129 -0
- package/dist/selfimprove/draft-pr.d.ts.map +1 -0
- package/dist/selfimprove/draft-pr.js +158 -0
- package/dist/selfimprove/draft-pr.js.map +1 -0
- package/dist/selfimprove/envelope-diff.d.ts +162 -0
- package/dist/selfimprove/envelope-diff.d.ts.map +1 -0
- package/dist/selfimprove/envelope-diff.js +321 -0
- package/dist/selfimprove/envelope-diff.js.map +1 -0
- package/dist/selfimprove/replay.d.ts +121 -0
- package/dist/selfimprove/replay.d.ts.map +1 -0
- package/dist/selfimprove/replay.js +109 -0
- package/dist/selfimprove/replay.js.map +1 -0
- package/dist/selfimprove/run-loop.d.ts +148 -0
- package/dist/selfimprove/run-loop.d.ts.map +1 -0
- package/dist/selfimprove/run-loop.js +240 -0
- package/dist/selfimprove/run-loop.js.map +1 -0
- package/dist/selfimprove/scenario.d.ts +145 -0
- package/dist/selfimprove/scenario.d.ts.map +1 -0
- package/dist/selfimprove/scenario.js +243 -0
- package/dist/selfimprove/scenario.js.map +1 -0
- package/dist/sentient/daemon-entry.js +5 -0
- package/dist/sentient/daemon-entry.js.map +1 -1
- package/dist/sentient/daemon.d.ts.map +1 -1
- package/dist/sentient/daemon.js +19 -0
- package/dist/sentient/daemon.js.map +1 -1
- package/dist/sentient/hygiene-scan.d.ts.map +1 -1
- package/dist/sentient/hygiene-scan.js +14 -6
- package/dist/sentient/hygiene-scan.js.map +1 -1
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +13 -9
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/connection-session-handle.d.ts +161 -0
- package/dist/sessions/connection-session-handle.d.ts.map +1 -0
- package/dist/sessions/connection-session-handle.js +168 -0
- package/dist/sessions/connection-session-handle.js.map +1 -0
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/handoff.js +9 -0
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/sessions/index.d.ts +4 -1
- package/dist/sessions/index.d.ts.map +1 -1
- package/dist/sessions/index.js +31 -1
- package/dist/sessions/index.js.map +1 -1
- package/dist/sessions/session-id.d.ts +34 -0
- package/dist/sessions/session-id.d.ts.map +1 -1
- package/dist/sessions/session-id.js +37 -0
- package/dist/sessions/session-id.js.map +1 -1
- package/dist/sessions/session-manifest-mirror.d.ts +115 -0
- package/dist/sessions/session-manifest-mirror.d.ts.map +1 -0
- package/dist/sessions/session-manifest-mirror.js +250 -0
- package/dist/sessions/session-manifest-mirror.js.map +1 -0
- package/dist/sessions/session-switch.d.ts.map +1 -1
- package/dist/sessions/session-switch.js +5 -2
- package/dist/sessions/session-switch.js.map +1 -1
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +2 -0
- package/dist/store/converters.js.map +1 -1
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +2 -0
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +87 -47
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/pi-session-store.d.ts +151 -0
- package/dist/store/pi-session-store.d.ts.map +1 -0
- package/dist/store/pi-session-store.js +166 -0
- package/dist/store/pi-session-store.js.map +1 -0
- package/dist/store/safety-data-accessor.d.ts +1 -0
- package/dist/store/safety-data-accessor.d.ts.map +1 -1
- package/dist/store/safety-data-accessor.js +5 -0
- package/dist/store/safety-data-accessor.js.map +1 -1
- package/dist/store/schema/cleo-global/accounts.d.ts +357 -0
- package/dist/store/schema/cleo-global/accounts.d.ts.map +1 -0
- package/dist/store/schema/cleo-global/accounts.js +136 -0
- package/dist/store/schema/cleo-global/accounts.js.map +1 -0
- package/dist/store/schema/cleo-global/index.d.ts +2 -0
- package/dist/store/schema/cleo-global/index.d.ts.map +1 -1
- package/dist/store/schema/cleo-global/index.js +2 -0
- package/dist/store/schema/cleo-global/index.js.map +1 -1
- package/dist/store/schema/cleo-global/session-manifest.d.ts +217 -0
- package/dist/store/schema/cleo-global/session-manifest.d.ts.map +1 -0
- package/dist/store/schema/cleo-global/session-manifest.js +99 -0
- package/dist/store/schema/cleo-global/session-manifest.js.map +1 -0
- package/dist/store/schema/cleo-project/tasks-core.d.ts +16 -0
- package/dist/store/schema/cleo-project/tasks-core.d.ts.map +1 -1
- package/dist/store/schema/cleo-project/tasks-core.js +14 -0
- package/dist/store/schema/cleo-project/tasks-core.js.map +1 -1
- package/dist/store/schema/tasks.d.ts +16 -0
- package/dist/store/schema/tasks.d.ts.map +1 -1
- package/dist/store/schema/tasks.js +10 -0
- package/dist/store/schema/tasks.js.map +1 -1
- package/dist/store/selfimprove-dhq-schema.d.ts +284 -0
- package/dist/store/selfimprove-dhq-schema.d.ts.map +1 -0
- package/dist/store/selfimprove-dhq-schema.js +128 -0
- package/dist/store/selfimprove-dhq-schema.js.map +1 -0
- package/dist/store/selfimprove-dhq-store.d.ts +175 -0
- package/dist/store/selfimprove-dhq-store.d.ts.map +1 -0
- package/dist/store/selfimprove-dhq-store.js +182 -0
- package/dist/store/selfimprove-dhq-store.js.map +1 -0
- package/dist/store/session-store.d.ts +52 -13
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +71 -13
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +7 -0
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/umbrella-data-accessor.d.ts +1 -0
- package/dist/store/umbrella-data-accessor.d.ts.map +1 -1
- package/dist/store/umbrella-data-accessor.js +5 -0
- package/dist/store/umbrella-data-accessor.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +32 -0
- package/dist/store/validation-schemas.d.ts.map +1 -1
- package/dist/store/writer-lease-schema.d.ts +392 -0
- package/dist/store/writer-lease-schema.d.ts.map +1 -0
- package/dist/store/writer-lease-schema.js +142 -0
- package/dist/store/writer-lease-schema.js.map +1 -0
- package/dist/store/writer-lease.d.ts +368 -0
- package/dist/store/writer-lease.d.ts.map +1 -0
- package/dist/store/writer-lease.js +1002 -0
- package/dist/store/writer-lease.js.map +1 -0
- package/dist/tasks/backfill-child-projections.d.ts.map +1 -1
- package/dist/tasks/backfill-child-projections.js +14 -9
- package/dist/tasks/backfill-child-projections.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +7 -10
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/duplicate-detector.d.ts.map +1 -1
- package/dist/tasks/duplicate-detector.js +14 -7
- package/dist/tasks/duplicate-detector.js.map +1 -1
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +10 -4
- package/dist/telemetry/index.js.map +1 -1
- package/dist/tools/agent-registry.d.ts +250 -0
- package/dist/tools/agent-registry.d.ts.map +1 -0
- package/dist/tools/agent-registry.js +292 -0
- package/dist/tools/agent-registry.js.map +1 -0
- package/dist/tools/agent-tool-families.d.ts +107 -0
- package/dist/tools/agent-tool-families.d.ts.map +1 -0
- package/dist/tools/agent-tool-families.js +527 -0
- package/dist/tools/agent-tool-families.js.map +1 -0
- package/dist/tools/browser-driver.d.ts +183 -0
- package/dist/tools/browser-driver.d.ts.map +1 -0
- package/dist/tools/browser-driver.js +242 -0
- package/dist/tools/browser-driver.js.map +1 -0
- package/dist/tools/builtin-agent-tools.d.ts +36 -0
- package/dist/tools/builtin-agent-tools.d.ts.map +1 -0
- package/dist/tools/builtin-agent-tools.js +150 -0
- package/dist/tools/builtin-agent-tools.js.map +1 -0
- package/dist/tools/dispatch.d.ts +370 -0
- package/dist/tools/dispatch.d.ts.map +1 -0
- package/dist/tools/dispatch.js +505 -0
- package/dist/tools/dispatch.js.map +1 -0
- package/dist/tools/env-scrub.d.ts +93 -0
- package/dist/tools/env-scrub.d.ts.map +1 -0
- package/dist/tools/env-scrub.js +176 -0
- package/dist/tools/env-scrub.js.map +1 -0
- package/dist/tools/fs.d.ts +30 -0
- package/dist/tools/fs.d.ts.map +1 -1
- package/dist/tools/fs.js +55 -2
- package/dist/tools/fs.js.map +1 -1
- package/dist/tools/guard.d.ts +15 -0
- package/dist/tools/guard.d.ts.map +1 -1
- package/dist/tools/guard.js +49 -12
- package/dist/tools/guard.js.map +1 -1
- package/dist/tools/index.d.ts +10 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +37 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/pty.d.ts +45 -0
- package/dist/tools/pty.d.ts.map +1 -0
- package/dist/tools/pty.js +166 -0
- package/dist/tools/pty.js.map +1 -0
- package/dist/tools/schema-gen.d.ts +57 -0
- package/dist/tools/schema-gen.d.ts.map +1 -0
- package/dist/tools/schema-gen.js +52 -0
- package/dist/tools/schema-gen.js.map +1 -0
- package/dist/tools/shell.d.ts +12 -0
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +14 -1
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/web-agent-tools.d.ts +72 -0
- package/dist/tools/web-agent-tools.d.ts.map +1 -0
- package/dist/tools/web-agent-tools.js +393 -0
- package/dist/tools/web-agent-tools.js.map +1 -0
- package/dist/tools/web-search-backends.d.ts +156 -0
- package/dist/tools/web-search-backends.d.ts.map +1 -0
- package/dist/tools/web-search-backends.js +290 -0
- package/dist/tools/web-search-backends.js.map +1 -0
- package/dist/validation/ops.d.ts +1 -15
- package/dist/validation/ops.d.ts.map +1 -1
- package/dist/validation/ops.js +0 -20
- package/dist/validation/ops.js.map +1 -1
- package/migrations/drizzle-cleo-global/20260607000000_t11891-writer-leases/migration.sql +55 -0
- package/migrations/drizzle-cleo-global/20260607010000_t11639-session-manifest/migration.sql +43 -0
- package/migrations/drizzle-cleo-global/20260608000000_t11899-pi-session-tree/migration.sql +52 -0
- package/migrations/drizzle-cleo-global/20260608010000_t11709-accounts-credential-pool/migration.sql +72 -0
- package/migrations/drizzle-cleo-global/20260608020000_t11889-selfimprove-dhq/migration.sql +64 -0
- package/migrations/drizzle-cleo-project/20260607000000_t11891-writer-leases/migration.sql +55 -0
- package/migrations/drizzle-cleo-project/20260607010000_t11639-session-parent/migration.sql +22 -0
- package/migrations/drizzle-cleo-project/20260608000000_t11899-pi-session-tree/migration.sql +52 -0
- package/migrations/drizzle-cleo-project/20260608020000_t11889-selfimprove-dhq/migration.sql +64 -0
- package/package.json +15 -13
- package/dist/lifecycle/chain-composition.d.ts +0 -30
- package/dist/lifecycle/chain-composition.d.ts.map +0 -1
- package/dist/lifecycle/chain-composition.js +0 -152
- package/dist/lifecycle/chain-composition.js.map +0 -1
- package/dist/lifecycle/chain-store.d.ts +0 -70
- package/dist/lifecycle/chain-store.d.ts.map +0 -1
- package/dist/lifecycle/chain-store.js +0 -246
- package/dist/lifecycle/chain-store.js.map +0 -1
- package/dist/lifecycle/default-chain.d.ts +0 -39
- package/dist/lifecycle/default-chain.d.ts.map +0 -1
- package/dist/lifecycle/default-chain.js +0 -176
- package/dist/lifecycle/default-chain.js.map +0 -1
- package/dist/lifecycle/tessera-engine.d.ts +0 -48
- package/dist/lifecycle/tessera-engine.d.ts.map +0 -1
- package/dist/lifecycle/tessera-engine.js +0 -249
- package/dist/lifecycle/tessera-engine.js.map +0 -1
- package/dist/validation/chain-validation.d.ts +0 -43
- package/dist/validation/chain-validation.d.ts.map +0 -1
- package/dist/validation/chain-validation.js +0 -146
- package/dist/validation/chain-validation.js.map +0 -1
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guarded Pi `ExecutionEnv` (T11761 · S1 · T11897).
|
|
3
|
+
*
|
|
4
|
+
* Pi's agent loop performs ALL filesystem + shell work through a single
|
|
5
|
+
* pluggable seam: `ExecutionEnv extends FileSystem, Shell`. This module supplies
|
|
6
|
+
* Cleo's implementation of that seam — {@link GuardedExecutionEnv} — which routes
|
|
7
|
+
* every operation through the deny-first {@link ToolGuard} chokepoint and an
|
|
8
|
+
* injected **workspace boundary**, so Pi can touch nothing outside the
|
|
9
|
+
* allowlisted roots and runs no command on the denylist.
|
|
10
|
+
*
|
|
11
|
+
* Two layers of confinement (deny-first):
|
|
12
|
+
* 1. **Workspace boundary** — every fs path is canonicalized with SYMLINK
|
|
13
|
+
* RESOLUTION (`fs.realpath` on the nearest existing ancestor) and the REAL
|
|
14
|
+
* target MUST fall under the injected `workspaceRoot`; a `../` escape, an
|
|
15
|
+
* out-of-root absolute path, OR a symlink (file or parent dir) whose real
|
|
16
|
+
* target leaves the root is rejected as a Pi `Result.err(FileError)` BEFORE
|
|
17
|
+
* the guard is consulted. A purely lexical check is symlink-blind, so the
|
|
18
|
+
* boundary resolves symlinks first. (The guard's own `allowedRoots` is a
|
|
19
|
+
* second, ALSO-symlink-resolving net.)
|
|
20
|
+
* 2. **ToolGuard** — the allowed operations delegate to the `ToolGuard` surface
|
|
21
|
+
* (`readFileText`/`writeFileAtomic`/`pathExists`/`executeShell`/`runGit`),
|
|
22
|
+
* which applies the project's symlink-resolved path allowlist + shell
|
|
23
|
+
* denylist + subprocess-env scrub. A `GuardDeniedError` from enforce-mode is
|
|
24
|
+
* CAUGHT and converted to a Pi `Result.err` — Pi's `FileSystem`/`Shell` ops
|
|
25
|
+
* must NEVER throw. The guard MUST be constructed in `enforce` mode (the
|
|
26
|
+
* factory asserts it) — in `warn` mode its allowlist/denylist are advisory
|
|
27
|
+
* and confinement would collapse to the workspace boundary alone.
|
|
28
|
+
*
|
|
29
|
+
* Every other capability (binary reads, raw temp/dir mutation, listing, …) for
|
|
30
|
+
* which there is no atomic primitive in v0 is DENIED with a typed error rather
|
|
31
|
+
* than reaching the real filesystem. The structural Pi interfaces are declared
|
|
32
|
+
* locally here (the `@earendil-works/pi-agent-core` package is not yet a
|
|
33
|
+
* dependency); S2 replaces these with the real type-only imports — the shapes
|
|
34
|
+
* match `@earendil-works/pi-agent-core@0.78.1` `harness/types.ts:268-332`.
|
|
35
|
+
*
|
|
36
|
+
* Scope discipline (S1): NO `pi-ai`/`pi-agent-core` imports, NO DB access, NO
|
|
37
|
+
* LLM calls. Import-time side-effect free.
|
|
38
|
+
*
|
|
39
|
+
* @epic T10403
|
|
40
|
+
* @task T11761
|
|
41
|
+
* @task T11897
|
|
42
|
+
*/
|
|
43
|
+
import { isAbsolute, relative, resolve as resolvePath } from 'node:path';
|
|
44
|
+
import { scrubSubprocessEnv } from '../../tools/env-scrub.js';
|
|
45
|
+
import { canonicalizePath } from '../../tools/fs.js';
|
|
46
|
+
import { GuardDeniedError } from '../../tools/guard.js';
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Result constructors
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
/** Build a successful {@link PiResult}. */
|
|
51
|
+
function ok(value) {
|
|
52
|
+
return { ok: true, value };
|
|
53
|
+
}
|
|
54
|
+
/** Build a failed {@link PiResult} with a {@link PiFileError}. */
|
|
55
|
+
function fsErr(code, message, path) {
|
|
56
|
+
return { ok: false, error: { code, message, ...(path !== undefined ? { path } : {}) } };
|
|
57
|
+
}
|
|
58
|
+
/** Build a failed {@link PiResult} with a {@link PiExecutionError}. */
|
|
59
|
+
function execErr(code, message) {
|
|
60
|
+
return { ok: false, error: { code, message } };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether `err` is a "file does not exist" failure (`ENOENT`). Used by
|
|
64
|
+
* {@link GuardedExecutionEnv.appendFile} to treat append-to-missing as
|
|
65
|
+
* append-creates (empty prefix) while still propagating every OTHER failure
|
|
66
|
+
* (guard denial, permission, …).
|
|
67
|
+
*/
|
|
68
|
+
function isMissingFileError(err) {
|
|
69
|
+
return (typeof err === 'object' &&
|
|
70
|
+
err !== null &&
|
|
71
|
+
'code' in err &&
|
|
72
|
+
err.code === 'ENOENT');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Deny-first {@link PiExecutionEnv} backed by the Cleo {@link ToolGuard} surface
|
|
76
|
+
* and a workspace boundary.
|
|
77
|
+
*
|
|
78
|
+
* - File reads/writes/existence checks route through the guard's atomic
|
|
79
|
+
* primitives, confined to `workspaceRoot`.
|
|
80
|
+
* - `exec` routes through the guard's `executeShell` (shell denylist applies).
|
|
81
|
+
* - Capabilities with no atomic primitive in v0 (binary read, `listDir`,
|
|
82
|
+
* `createDir`, `remove`, temp dir/file) are DENIED with a typed
|
|
83
|
+
* `Result.err` — they never reach the real filesystem.
|
|
84
|
+
* - Pure path arithmetic (`cwd`/`absolutePath`/`joinPath`) is computed locally
|
|
85
|
+
* (no fs access), anchored to `workspaceRoot`.
|
|
86
|
+
*
|
|
87
|
+
* Every method honours the Pi contract: it returns a {@link PiResult} and never
|
|
88
|
+
* throws — a {@link GuardDeniedError} from the guard is caught and converted.
|
|
89
|
+
*/
|
|
90
|
+
export class GuardedExecutionEnv {
|
|
91
|
+
#guard;
|
|
92
|
+
#root;
|
|
93
|
+
/**
|
|
94
|
+
* @param deps - The guard surface + the workspace root to confine to.
|
|
95
|
+
*/
|
|
96
|
+
constructor(deps) {
|
|
97
|
+
this.#guard = deps.guard;
|
|
98
|
+
this.#root = resolvePath(deps.workspaceRoot);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Lexically resolve a (possibly relative) path against the workspace root and
|
|
102
|
+
* assert it does not climb out. This is the fast FIRST gate only — it is
|
|
103
|
+
* symlink-blind, so {@link #confine} additionally re-checks the REAL target.
|
|
104
|
+
*
|
|
105
|
+
* @returns the lexically-resolved absolute path, or `null` on a `../`/absolute
|
|
106
|
+
* escape.
|
|
107
|
+
*/
|
|
108
|
+
#lexicalConfine(path) {
|
|
109
|
+
const abs = isAbsolute(path) ? resolvePath(path) : resolvePath(this.#root, path);
|
|
110
|
+
const rel = relative(this.#root, abs);
|
|
111
|
+
if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel)))
|
|
112
|
+
return abs;
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a (possibly relative) path against the workspace root and assert its
|
|
117
|
+
* REAL filesystem target stays inside the boundary. Two gates:
|
|
118
|
+
* 1. lexical (`#lexicalConfine`) — rejects `../` / out-of-root absolute paths;
|
|
119
|
+
* 2. SYMLINK resolution (`canonicalizePath`) — follows every symlink component
|
|
120
|
+
* (incl. a symlinked parent of a fresh write target) and re-checks
|
|
121
|
+
* containment against the REAL path, defeating the classic symlink/TOCTOU
|
|
122
|
+
* escape that a purely lexical check is blind to.
|
|
123
|
+
*
|
|
124
|
+
* @returns the symlink-resolved absolute path, or `null` when it escapes.
|
|
125
|
+
*/
|
|
126
|
+
async #confine(path) {
|
|
127
|
+
const lexical = this.#lexicalConfine(path);
|
|
128
|
+
if (lexical === null)
|
|
129
|
+
return null;
|
|
130
|
+
const real = await canonicalizePath(lexical);
|
|
131
|
+
// Compare against the REAL root too, so a workspace whose own path traverses
|
|
132
|
+
// a symlink does not spuriously reject its own contained files.
|
|
133
|
+
const realRoot = await canonicalizePath(this.#root);
|
|
134
|
+
const rel = relative(realRoot, real);
|
|
135
|
+
// The REAL target must not climb out of the (also-real) root.
|
|
136
|
+
if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel)))
|
|
137
|
+
return real;
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
// --- pure path arithmetic (no fs access) ---------------------------------
|
|
141
|
+
/** The workspace root (the in-process Pi run's working directory). */
|
|
142
|
+
cwd() {
|
|
143
|
+
return this.#root;
|
|
144
|
+
}
|
|
145
|
+
/** Resolve `path` to an absolute path anchored at the workspace root. */
|
|
146
|
+
absolutePath(path) {
|
|
147
|
+
return isAbsolute(path) ? resolvePath(path) : resolvePath(this.#root, path);
|
|
148
|
+
}
|
|
149
|
+
/** Join path segments (pure). */
|
|
150
|
+
joinPath(...segments) {
|
|
151
|
+
return resolvePath(this.#root, ...segments);
|
|
152
|
+
}
|
|
153
|
+
// --- file reads ----------------------------------------------------------
|
|
154
|
+
/** Read a file's text, confined to the workspace + guarded. */
|
|
155
|
+
async readTextFile(path) {
|
|
156
|
+
const abs = await this.#confine(path);
|
|
157
|
+
if (abs === null)
|
|
158
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
159
|
+
try {
|
|
160
|
+
const res = await this.#guard.readFileText({ path: abs });
|
|
161
|
+
return ok(res.content);
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
return this.#toFsErr(err, abs);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Read a file's text as an array of lines. */
|
|
168
|
+
async readTextLines(path) {
|
|
169
|
+
const res = await this.readTextFile(path);
|
|
170
|
+
return res.ok ? ok(res.value.split(/\r?\n/)) : res;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Binary read — DENIED in v0. The atomic `readFileText` primitive is
|
|
174
|
+
* text-only (`ReadFileInput.encoding` has no binary variant); a binary
|
|
175
|
+
* primitive must be added under `core/src/tools` (Gate 11) before this is
|
|
176
|
+
* supported. Deny-first: never reaches the filesystem.
|
|
177
|
+
*/
|
|
178
|
+
async readBinaryFile(path) {
|
|
179
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'binary read is not supported in v0', path);
|
|
180
|
+
}
|
|
181
|
+
// --- file writes ---------------------------------------------------------
|
|
182
|
+
/** Atomically write a file, confined to the workspace + guarded. */
|
|
183
|
+
async writeFile(path, content) {
|
|
184
|
+
const abs = await this.#confine(path);
|
|
185
|
+
if (abs === null)
|
|
186
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
187
|
+
try {
|
|
188
|
+
await this.#guard.writeFileAtomic({ path: abs, content });
|
|
189
|
+
return ok(undefined);
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return this.#toFsErr(err, abs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Append to a file. The atomic surface offers only whole-file atomic writes,
|
|
197
|
+
* so this reads-then-writes the concatenation (still confined + guarded). A
|
|
198
|
+
* missing file is treated as empty.
|
|
199
|
+
*
|
|
200
|
+
* Confinement happens EXACTLY ONCE here, then the read + write delegate
|
|
201
|
+
* straight to the guard with the already-resolved `abs`. Re-entering the
|
|
202
|
+
* public `readTextFile`/`writeFile` would re-`#confine` the value `#confine`
|
|
203
|
+
* just returned — and because `#confine` yields the SYMLINK-RESOLVED real path
|
|
204
|
+
* (e.g. macOS `/tmp`→`/private/tmp`, or a symlinked workspace root), that real
|
|
205
|
+
* path is `../`-outside the lexical `workspaceRoot`, so the second lexical
|
|
206
|
+
* gate would spuriously reject it (`E_PI_FS_DENIED`). Reading once through the
|
|
207
|
+
* guard — whose own allowlist canonicalizes both sides — avoids the
|
|
208
|
+
* double-confine while keeping the symlink-escape denial intact.
|
|
209
|
+
*/
|
|
210
|
+
async appendFile(path, content) {
|
|
211
|
+
const abs = await this.#confine(path);
|
|
212
|
+
if (abs === null)
|
|
213
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
214
|
+
// Read existing content directly via the guard (a missing file → ENOENT,
|
|
215
|
+
// treated as empty). Do NOT route through readTextFile: that would re-confine
|
|
216
|
+
// the resolved real path against the lexical root and falsely reject it.
|
|
217
|
+
let prefix = '';
|
|
218
|
+
try {
|
|
219
|
+
const res = await this.#guard.readFileText({ path: abs });
|
|
220
|
+
prefix = res.content;
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
// A missing file is the only non-fatal case (append-creates). Any other
|
|
224
|
+
// failure (guard denial, permission, …) propagates as the Pi error.
|
|
225
|
+
if (!isMissingFileError(err))
|
|
226
|
+
return this.#toFsErr(err, abs);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
await this.#guard.writeFileAtomic({ path: abs, content: prefix + content });
|
|
230
|
+
return ok(undefined);
|
|
231
|
+
}
|
|
232
|
+
catch (err) {
|
|
233
|
+
return this.#toFsErr(err, abs);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// --- metadata ------------------------------------------------------------
|
|
237
|
+
/** Report whether a path is a file or directory, confined + guarded. */
|
|
238
|
+
async fileInfo(path) {
|
|
239
|
+
const abs = await this.#confine(path);
|
|
240
|
+
if (abs === null)
|
|
241
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
242
|
+
try {
|
|
243
|
+
const res = await this.#guard.pathExists({ path: abs });
|
|
244
|
+
if (!res.exists)
|
|
245
|
+
return fsErr('E_PI_FS_NOT_FOUND', 'path does not exist', abs);
|
|
246
|
+
return ok({ isFile: res.kind === 'file', isDirectory: res.kind === 'directory' });
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
return this.#toFsErr(err, abs);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** Whether a path exists, confined + guarded. */
|
|
253
|
+
async exists(path) {
|
|
254
|
+
const abs = await this.#confine(path);
|
|
255
|
+
if (abs === null)
|
|
256
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
257
|
+
try {
|
|
258
|
+
const res = await this.#guard.pathExists({ path: abs });
|
|
259
|
+
return ok(res.exists);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
return this.#toFsErr(err, abs);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Canonicalize a path inside the workspace. SYMLINK-RESOLVING: `#confine`
|
|
267
|
+
* follows every symlink component via `fs.realpath`, so the returned value is
|
|
268
|
+
* the REAL on-disk location (matching Pi's `NodeExecutionEnv.canonicalPath`
|
|
269
|
+
* contract), and a path whose real target escapes the workspace is denied.
|
|
270
|
+
*/
|
|
271
|
+
async canonicalPath(path) {
|
|
272
|
+
const abs = await this.#confine(path);
|
|
273
|
+
if (abs === null)
|
|
274
|
+
return fsErr('E_PI_FS_DENIED', 'path escapes workspace boundary', path);
|
|
275
|
+
return ok(abs);
|
|
276
|
+
}
|
|
277
|
+
// --- denied fs-mutating / listing ops (no atomic primitive in v0) --------
|
|
278
|
+
/** DENIED in v0 — no `listDir` atomic primitive. */
|
|
279
|
+
async listDir(path) {
|
|
280
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'directory listing is not supported in v0', path);
|
|
281
|
+
}
|
|
282
|
+
/** DENIED in v0 — no `createDir` atomic primitive. */
|
|
283
|
+
async createDir(path) {
|
|
284
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'directory creation is not supported in v0', path);
|
|
285
|
+
}
|
|
286
|
+
/** DENIED in v0 — no `remove` atomic primitive. */
|
|
287
|
+
async remove(path) {
|
|
288
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'remove is not supported in v0', path);
|
|
289
|
+
}
|
|
290
|
+
/** DENIED in v0 — no temp-dir atomic primitive. */
|
|
291
|
+
async createTempDir() {
|
|
292
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'temp dir creation is not supported in v0');
|
|
293
|
+
}
|
|
294
|
+
/** DENIED in v0 — no temp-file atomic primitive. */
|
|
295
|
+
async createTempFile() {
|
|
296
|
+
return fsErr('E_PI_FS_UNSUPPORTED', 'temp file creation is not supported in v0');
|
|
297
|
+
}
|
|
298
|
+
// --- shell ---------------------------------------------------------------
|
|
299
|
+
/**
|
|
300
|
+
* Execute a command through the guard's shell surface (denylist applies). The
|
|
301
|
+
* Pi `command` string is a bare executable name (args are passed by Pi via the
|
|
302
|
+
* loop, but the v0 seam takes only `command`); we forward it as the guard's
|
|
303
|
+
* `command` with empty args. `cwd` is symlink-confined to the workspace.
|
|
304
|
+
*
|
|
305
|
+
* ## Env is SCRUBBED before it leaves this seam (T11897 · security)
|
|
306
|
+
*
|
|
307
|
+
* Pi's `options.env` is UNTRUSTED model-driven input. Forwarding it verbatim
|
|
308
|
+
* would let Pi set `LD_PRELOAD`/`NODE_OPTIONS`/`GIT_SSH_COMMAND` (arbitrary
|
|
309
|
+
* native code execution outside any allowlist) or hijack `PATH` to make a
|
|
310
|
+
* workspace-resident impostor satisfy an allowed command basename. So the env
|
|
311
|
+
* is run through {@link scrubSubprocessEnv} HERE — forbidden keys (loader
|
|
312
|
+
* hooks, `PATH`, secrets) are dropped and `PATH` is pinned to a trusted value
|
|
313
|
+
* — before it reaches the guard (which scrubs again as a redundant net, as
|
|
314
|
+
* does `defaultShellExecutor`). This also prevents the daemon's own secrets
|
|
315
|
+
* from leaking into the child (the scrubbed env never inherits `process.env`).
|
|
316
|
+
*/
|
|
317
|
+
async exec(command, options) {
|
|
318
|
+
const cwd = options?.cwd ? await this.#confine(options.cwd) : this.#root;
|
|
319
|
+
if (cwd === null) {
|
|
320
|
+
return execErr('E_PI_EXEC_DENIED', 'cwd escapes workspace boundary');
|
|
321
|
+
}
|
|
322
|
+
// Scrub Pi-supplied env to a minimal allowlist at the seam (defense in depth
|
|
323
|
+
// — the guard + executor scrub again). Pi can NOT inject a loader hook,
|
|
324
|
+
// hijack PATH, or read inherited daemon secrets through this channel.
|
|
325
|
+
const env = scrubSubprocessEnv({ extra: options?.env });
|
|
326
|
+
try {
|
|
327
|
+
const res = await this.#guard.executeShell({
|
|
328
|
+
command,
|
|
329
|
+
cwd,
|
|
330
|
+
env,
|
|
331
|
+
...(options?.timeout !== undefined ? { timeoutMs: options.timeout } : {}),
|
|
332
|
+
});
|
|
333
|
+
return ok({ stdout: res.stdout, stderr: res.stderr, exitCode: res.code });
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
if (err instanceof GuardDeniedError) {
|
|
337
|
+
return execErr('E_PI_EXEC_DENIED', err.message);
|
|
338
|
+
}
|
|
339
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
340
|
+
return execErr('E_PI_EXEC_FAILED', message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// --- cleanup (best-effort, must not throw) -------------------------------
|
|
344
|
+
/** Best-effort cleanup; the guarded surface owns no resources, so a no-op. */
|
|
345
|
+
async cleanup() {
|
|
346
|
+
// no-op — required on both FileSystem + Shell; owns nothing to release.
|
|
347
|
+
}
|
|
348
|
+
// --- error mapping -------------------------------------------------------
|
|
349
|
+
/** Convert a thrown error from the guarded surface into a Pi `Result.err`. */
|
|
350
|
+
#toFsErr(err, path) {
|
|
351
|
+
if (err instanceof GuardDeniedError) {
|
|
352
|
+
return fsErr('E_PI_FS_DENIED', err.message, path);
|
|
353
|
+
}
|
|
354
|
+
const code = typeof err === 'object' && err !== null && 'code' in err
|
|
355
|
+
? String(err.code)
|
|
356
|
+
: 'E_PI_FS_FAILED';
|
|
357
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
358
|
+
return fsErr(code, message, path);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Thrown by {@link createGuardedExecutionEnv} when the injected guard is NOT in
|
|
363
|
+
* `enforce` mode. In `warn` mode the guard's path allowlist + command denylist
|
|
364
|
+
* are advisory (log-and-proceed), so the second confinement net does not exist
|
|
365
|
+
* and the boundary would collapse to the workspace check alone. The Pi adapter
|
|
366
|
+
* therefore REFUSES a non-enforce guard rather than running untrusted Pi work
|
|
367
|
+
* behind an advisory boundary.
|
|
368
|
+
*/
|
|
369
|
+
export class PiGuardModeError extends Error {
|
|
370
|
+
/** Machine-readable code. */
|
|
371
|
+
code = 'E_PI_GUARD_NOT_ENFORCING';
|
|
372
|
+
/**
|
|
373
|
+
* @param mode - The (non-enforce) mode the guard was constructed with.
|
|
374
|
+
*/
|
|
375
|
+
constructor(mode) {
|
|
376
|
+
super(`Pi ExecutionEnv requires an enforce-mode ToolGuard; got "${mode}". ` +
|
|
377
|
+
'In warn mode the path allowlist + command denylist are advisory and provide no boundary.');
|
|
378
|
+
this.name = 'PiGuardModeError';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Construct a deny-first {@link GuardedExecutionEnv}.
|
|
383
|
+
*
|
|
384
|
+
* REQUIRES an `enforce`-mode guard: a `warn`-mode guard's allowlist/denylist are
|
|
385
|
+
* advisory, so handing untrusted Pi work behind one would leave confinement to
|
|
386
|
+
* the workspace boundary alone. The factory ASSERTS the mode and throws
|
|
387
|
+
* {@link PiGuardModeError} otherwise — the Pi adapter never silently degrades to
|
|
388
|
+
* an advisory boundary.
|
|
389
|
+
*
|
|
390
|
+
* @param deps - The guard surface + the workspace root to confine to.
|
|
391
|
+
* @returns A {@link PiExecutionEnv} safe to hand to Pi's agent loop.
|
|
392
|
+
* @throws {PiGuardModeError} When `deps.guard.mode !== 'enforce'`.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const guard = createToolGuard({ allowedRoots: [projectRoot], mode: 'enforce' });
|
|
397
|
+
* const env = createGuardedExecutionEnv({ guard, workspaceRoot: projectRoot });
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
export function createGuardedExecutionEnv(deps) {
|
|
401
|
+
if (deps.guard.mode !== 'enforce') {
|
|
402
|
+
throw new PiGuardModeError(deps.guard.mode);
|
|
403
|
+
}
|
|
404
|
+
return new GuardedExecutionEnv(deps);
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=pi-execution-env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-execution-env.js","sourceRoot":"","sources":["../../../src/llm/pi/pi-execution-env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gondolin micro-VM Pi `ExecutionEnv` — the SECOND backend of the
|
|
3
|
+
* {@link PiExecutionEnv} seam (T11909 · T11888-B · P5 Gondolin · epic T11599).
|
|
4
|
+
*
|
|
5
|
+
* Where the in-process {@link import('./pi-execution-env.js').GuardedExecutionEnv}
|
|
6
|
+
* (S1 · T11761 · T11897) confines Pi with a deny-first `ToolGuard` + workspace
|
|
7
|
+
* boundary INSIDE the host process, this backend's confinement is the
|
|
8
|
+
* **micro-VM boundary itself**. Guest code runs with:
|
|
9
|
+
*
|
|
10
|
+
* - **ZERO host authority** — no `cleo.db` handle, no writer-lease IPC socket, no
|
|
11
|
+
* host `process.env` secrets, no host filesystem outside the single RW
|
|
12
|
+
* `/workspace` mount. The guest is a different kernel; it cannot reach the
|
|
13
|
+
* daemon, the lease arbiter, or BRAIN even if Pi-driven code tries.
|
|
14
|
+
* - **Egress only via the credential-injection proxy** — `createHttpHooks` is
|
|
15
|
+
* constructed with `allowedHosts: []` (present-and-empty = DENY ALL per the
|
|
16
|
+
* gondolin docs; OMITTING it = allow-all — the egress footgun this guards) and
|
|
17
|
+
* host-side secret PLACEHOLDERS, so the guest only ever sees the `placeholder`
|
|
18
|
+
* bytes, never the real token. The loop appends specific hosts per run.
|
|
19
|
+
* - **Live DBs NEVER mounted** — `.cleo/tasks.db` / `.cleo/brain.db` are NOT in
|
|
20
|
+
* the VFS mount set. A replay operates on a DISPOSABLE seeded copy
|
|
21
|
+
* (`VACUUM INTO` snapshot), so the T5158 data-loss vector (git overwrites the
|
|
22
|
+
* live WAL-backed DB) is structurally impossible: there is no live handle
|
|
23
|
+
* inside the VM to corrupt.
|
|
24
|
+
*
|
|
25
|
+
* This is strictly stronger than S1's env-scrub: S1 protects against an in-process
|
|
26
|
+
* Pi escape via the guard allowlist; Gondolin protects via kernel + VFS + network
|
|
27
|
+
* namespace separation. A thin in-guest deny layer (lexical `/workspace`
|
|
28
|
+
* confinement + a command denylist for real egress verbs) is kept as DEFENSE IN
|
|
29
|
+
* DEPTH — the boundary is the VM, but a Pi escape that runs a binary still cannot
|
|
30
|
+
* do damage that survives the run.
|
|
31
|
+
*
|
|
32
|
+
* Optional-dep discipline (D11142): `@earendil-works/gondolin` is loaded ONLY via
|
|
33
|
+
* the {@link import('./gondolin-loader.js').loadGondolin} dynamic import — it is
|
|
34
|
+
* in NEITHER `dependencies` NOR `optionalDependencies`, and this module declares
|
|
35
|
+
* NO `import type` from the package (the consumed structural shapes come from the
|
|
36
|
+
* loader's LOCAL types). `core` builds + all non-gondolin tests pass with gondolin
|
|
37
|
+
* ABSENT, and this module is import-time side-effect-free (no top-level VM boot).
|
|
38
|
+
*
|
|
39
|
+
* Never-throw discipline: every `vm.fs.*` / `vm.exec` call is wrapped exactly as
|
|
40
|
+
* S1's `#toFsErr` / `execErr` — a thrown gondolin/guest error becomes a
|
|
41
|
+
* {@link PiResult} `err` with `E_PI_FS_*` / `E_PI_EXEC_*` codes. The
|
|
42
|
+
* `PiResult<T, E>` contract is byte-for-byte identical to S1, so the Pi loop
|
|
43
|
+
* cannot tell the two backends apart.
|
|
44
|
+
*
|
|
45
|
+
* @epic T11599
|
|
46
|
+
* @task T11909
|
|
47
|
+
* @see ./pi-execution-env.js — the S1 `GuardedExecutionEnv` this mirrors over a VM
|
|
48
|
+
* @see ./gondolin-loader.js — the optional-dep loader supplying the structural types
|
|
49
|
+
*/
|
|
50
|
+
import { type GondolinModule, type SecretDefinition, type VM } from './gondolin-loader.js';
|
|
51
|
+
import type { PiExecOptions, PiExecResult, PiExecutionEnv, PiExecutionError, PiFileError, PiFileInfo, PiResult } from './pi-execution-env.js';
|
|
52
|
+
/** The single RW mount point inside the guest — the ONLY writable location. */
|
|
53
|
+
export declare const GONDOLIN_WORKSPACE_ROOT: "/workspace";
|
|
54
|
+
/**
|
|
55
|
+
* The injectable factory the {@link createGondolinExecutionEnv} uses to obtain
|
|
56
|
+
* the gondolin module. Defaults to the real lazy {@link loadGondolin}; unit
|
|
57
|
+
* tests inject a fake that returns a MOCK module so NO real QEMU VM is launched.
|
|
58
|
+
*/
|
|
59
|
+
export type GondolinLoader = () => Promise<GondolinModule | null>;
|
|
60
|
+
/** Construction options for {@link createGondolinExecutionEnv}. */
|
|
61
|
+
export interface CreateGondolinExecutionEnvOptions {
|
|
62
|
+
/**
|
|
63
|
+
* The host directory mounted RW at `/workspace` inside the guest. This MUST be
|
|
64
|
+
* a DISPOSABLE seeded copy (e.g. a `VACUUM INTO` snapshot dir) — NEVER a path
|
|
65
|
+
* containing the live `.cleo/tasks.db` / `.cleo/brain.db`. The factory mounts
|
|
66
|
+
* ONLY this directory; live DBs are structurally absent from the guest VFS.
|
|
67
|
+
*/
|
|
68
|
+
readonly seededCopyDir: string;
|
|
69
|
+
/**
|
|
70
|
+
* Outbound host allowlist. Defaults to `[]` (DENY ALL) when omitted — the
|
|
71
|
+
* egress footgun guard. The loop appends ONLY the specific hosts it needs
|
|
72
|
+
* (e.g. the model endpoint via the Vault proxy) per run.
|
|
73
|
+
*/
|
|
74
|
+
readonly allowedHosts?: readonly string[];
|
|
75
|
+
/**
|
|
76
|
+
* Host-side secret injections. The guest only ever sees each entry's
|
|
77
|
+
* `placeholder`; the real `value` bytes never enter the VM.
|
|
78
|
+
*/
|
|
79
|
+
readonly vaultSecrets?: Readonly<Record<string, SecretDefinition>>;
|
|
80
|
+
/** Guest memory bound (e.g. `"1G"`). Defaults to {@link DEFAULT_GUEST_MEMORY}. */
|
|
81
|
+
readonly memory?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Guest environment — the ONLY env the guest sees (host `process.env` is NEVER
|
|
84
|
+
* inherited; that isolation is structural). Defaults to an empty map.
|
|
85
|
+
*/
|
|
86
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
87
|
+
/**
|
|
88
|
+
* Optional loader override (tests inject a fake → a MOCK VM, no QEMU). Defaults
|
|
89
|
+
* to the real {@link loadGondolin}.
|
|
90
|
+
*/
|
|
91
|
+
readonly load?: GondolinLoader;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A {@link PiExecutionEnv} whose confinement is a gondolin micro-VM. Owns exactly
|
|
95
|
+
* ONE {@link VM} and releases it in {@link cleanup} via `vm.close()`.
|
|
96
|
+
*
|
|
97
|
+
* Every fs op maps to `vm.fs.*`; `exec` maps to `vm.exec`. Each call is wrapped
|
|
98
|
+
* try/catch → {@link PiResult} `err`, so no method ever throws. A thin in-guest
|
|
99
|
+
* deny layer (lexical `/workspace` confinement + the {@link DENIED_EXEC_PREFIXES}
|
|
100
|
+
* command denylist) is the defense-in-depth net BEHIND the VM boundary.
|
|
101
|
+
*
|
|
102
|
+
* Unlike the stateless {@link import('./pi-execution-env.js').GuardedExecutionEnv}
|
|
103
|
+
* (whose `cleanup()` is a no-op), this env OWNS the VM and MUST release it.
|
|
104
|
+
* `cleanup()` is idempotent (guarded by a `#closed` flag).
|
|
105
|
+
*/
|
|
106
|
+
export declare class GondolinExecutionEnv implements PiExecutionEnv {
|
|
107
|
+
#private;
|
|
108
|
+
/**
|
|
109
|
+
* @param vm - The booted gondolin VM this env owns. Construct via
|
|
110
|
+
* {@link createGondolinExecutionEnv}, not directly — the factory wires the
|
|
111
|
+
* deny-by-default egress hooks + seeded-copy-only mount set.
|
|
112
|
+
*/
|
|
113
|
+
constructor(vm: VM);
|
|
114
|
+
/** The guest workspace root — the in-VM Pi run's working directory. */
|
|
115
|
+
cwd(): string;
|
|
116
|
+
/** Resolve `path` to a guest-absolute path anchored at `/workspace`. */
|
|
117
|
+
absolutePath(path: string): string;
|
|
118
|
+
/** Join path segments (pure POSIX) anchored at the guest workspace root. */
|
|
119
|
+
joinPath(...segments: string[]): string;
|
|
120
|
+
/** Read a file's text from the guest workspace. */
|
|
121
|
+
readTextFile(path: string): Promise<PiResult<string, PiFileError>>;
|
|
122
|
+
/** Read a file's text as an array of lines. */
|
|
123
|
+
readTextLines(path: string): Promise<PiResult<string[], PiFileError>>;
|
|
124
|
+
/**
|
|
125
|
+
* Binary read — FILLS S1's v0 denial. The guest VFS supports binary reads
|
|
126
|
+
* directly via `vm.fs.readFile(p)` (returns a `Uint8Array`).
|
|
127
|
+
*/
|
|
128
|
+
readBinaryFile(path: string): Promise<PiResult<Uint8Array, PiFileError>>;
|
|
129
|
+
/** Write a file to the guest workspace. */
|
|
130
|
+
writeFile(path: string, content: string): Promise<PiResult<void, PiFileError>>;
|
|
131
|
+
/**
|
|
132
|
+
* Append to a file. The guest fs surface offers only whole-file writes, so this
|
|
133
|
+
* reads-then-writes the concatenation (still confined). A missing file is
|
|
134
|
+
* treated as empty (append-creates).
|
|
135
|
+
*/
|
|
136
|
+
appendFile(path: string, content: string): Promise<PiResult<void, PiFileError>>;
|
|
137
|
+
/** Report whether a guest path is a file or directory. */
|
|
138
|
+
fileInfo(path: string): Promise<PiResult<PiFileInfo, PiFileError>>;
|
|
139
|
+
/** Whether a guest path exists. */
|
|
140
|
+
exists(path: string): Promise<PiResult<boolean, PiFileError>>;
|
|
141
|
+
/**
|
|
142
|
+
* Canonicalize a guest path via `realpath` inside the guest. Runs the binary in
|
|
143
|
+
* ARRAY form (no `$PATH` / shell expansion) so the path argument cannot be
|
|
144
|
+
* reinterpreted as shell.
|
|
145
|
+
*/
|
|
146
|
+
canonicalPath(path: string): Promise<PiResult<string, PiFileError>>;
|
|
147
|
+
/** List a guest directory — FILLS S1's v0 denial. */
|
|
148
|
+
listDir(path: string): Promise<PiResult<string[], PiFileError>>;
|
|
149
|
+
/** Create a guest directory (recursive) — FILLS S1's v0 denial. */
|
|
150
|
+
createDir(path: string): Promise<PiResult<void, PiFileError>>;
|
|
151
|
+
/** Remove a guest path (recursive, force) — FILLS S1's v0 denial. */
|
|
152
|
+
remove(path: string): Promise<PiResult<void, PiFileError>>;
|
|
153
|
+
/**
|
|
154
|
+
* Create a temp directory under `/workspace/.tmp` — FILLS S1's v0 denial. Runs
|
|
155
|
+
* `mktemp -d` in ARRAY form (no `$PATH` / shell expansion).
|
|
156
|
+
*/
|
|
157
|
+
createTempDir(prefix?: string): Promise<PiResult<string, PiFileError>>;
|
|
158
|
+
/**
|
|
159
|
+
* Create a temp file under `/workspace/.tmp` — FILLS S1's v0 denial. Runs
|
|
160
|
+
* `mktemp` in ARRAY form (no `$PATH` / shell expansion).
|
|
161
|
+
*/
|
|
162
|
+
createTempFile(prefix?: string): Promise<PiResult<string, PiFileError>>;
|
|
163
|
+
/**
|
|
164
|
+
* Execute a command in the guest via `vm.exec`. Deny-first: a real-egress verb
|
|
165
|
+
* ({@link DENIED_EXEC_PREFIXES} — `gh` / `git push` / `npm publish` / `cleo` /
|
|
166
|
+
* `git remote`) is rejected BEFORE reaching the VM, even though the VM itself
|
|
167
|
+
* has no egress (the network namespace is deny-by-default). `cwd` is confined
|
|
168
|
+
* to the workspace; the host `AbortSignal` is forwarded only when supplied.
|
|
169
|
+
*
|
|
170
|
+
* ## exitCode mapping
|
|
171
|
+
*
|
|
172
|
+
* gondolin's `ExecResult.exitCode` is `number` (NON-nullable) with a SEPARATE
|
|
173
|
+
* optional `signal` field; Pi's `PiExecResult.exitCode` is `number | null`
|
|
174
|
+
* ("null when killed by signal"). So a signal-kill is mapped to `null`, NOT
|
|
175
|
+
* the raw `exitCode`.
|
|
176
|
+
*/
|
|
177
|
+
exec(command: string, options?: PiExecOptions): Promise<PiResult<PiExecResult, PiExecutionError>>;
|
|
178
|
+
/**
|
|
179
|
+
* Tear down the owned VM (`vm.close()`). Idempotent: a second call is a no-op.
|
|
180
|
+
* Best-effort and never throws (a close failure is logged, not propagated) so
|
|
181
|
+
* the Pi contract — `cleanup()` must not throw — holds.
|
|
182
|
+
*/
|
|
183
|
+
cleanup(): Promise<void>;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Thrown by {@link createGondolinExecutionEnv} when the optional
|
|
187
|
+
* `@earendil-works/gondolin` package (or its QEMU/KVM host infra) is not
|
|
188
|
+
* available, so a VM cannot be booted. Callers that want graceful degradation
|
|
189
|
+
* should check `isGondolinAvailable()` first (the selector
|
|
190
|
+
* {@link import('./resolve-execution-env.js')} does this) and fall back to the
|
|
191
|
+
* in-process {@link import('./pi-execution-env.js').GuardedExecutionEnv}.
|
|
192
|
+
*/
|
|
193
|
+
export declare class GondolinUnavailableError extends Error {
|
|
194
|
+
/** Machine-readable code. */
|
|
195
|
+
readonly code = "E_GONDOLIN_UNAVAILABLE";
|
|
196
|
+
constructor(message: string);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Boot a gondolin micro-VM and wrap it in a {@link GondolinExecutionEnv}.
|
|
200
|
+
*
|
|
201
|
+
* The VM is configured for ZERO host authority:
|
|
202
|
+
* - **mount set** = ONLY `{ '/workspace': new RealFSProvider(seededCopyDir) }`.
|
|
203
|
+
* The disposable seeded copy is the single RW mount; live `tasks.db`/`brain.db`
|
|
204
|
+
* are structurally absent from the guest VFS (T5158 impossible in-VM).
|
|
205
|
+
* - **egress** = `createHttpHooks({ allowedHosts, secrets })` with `allowedHosts`
|
|
206
|
+
* defaulting to `[]` (DENY ALL — the footgun guard) and host-side secret
|
|
207
|
+
* PLACEHOLDERS (the guest never sees real token bytes).
|
|
208
|
+
* - **env** = ONLY `opts.env` (host `process.env` is NEVER inherited).
|
|
209
|
+
* - **memory** = `opts.memory` (default `"1G"`), set EXPLICITLY.
|
|
210
|
+
*
|
|
211
|
+
* Import-time side-effect-free: the gondolin module is loaded lazily HERE (not at
|
|
212
|
+
* module top-level) via the injectable {@link GondolinLoader} (tests pass a fake
|
|
213
|
+
* → a MOCK VM, so NO real QEMU boots).
|
|
214
|
+
*
|
|
215
|
+
* @param opts - The seeded-copy mount dir + egress allowlist/secrets + guest env.
|
|
216
|
+
* @returns A {@link PiExecutionEnv} backed by the booted VM.
|
|
217
|
+
* @throws {GondolinUnavailableError} When the optional package cannot be loaded.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const env = await createGondolinExecutionEnv({ seededCopyDir: snapshotDir });
|
|
222
|
+
* // ... drive Pi's loop over `env` ...
|
|
223
|
+
* await env.cleanup(); // releases the VM
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export declare function createGondolinExecutionEnv(opts: CreateGondolinExecutionEnvOptions): Promise<PiExecutionEnv>;
|
|
227
|
+
//# sourceMappingURL=pi-gondolin-env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-gondolin-env.d.ts","sourceRoot":"","sources":["../../../src/llm/pi/pi-gondolin-env.ts"],"names":[],"mappings":"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"}
|