@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,505 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent tool-call dispatch engine (T1740 · epic T11456 · SG-TOOLS).
|
|
3
|
+
*
|
|
4
|
+
* The SINGLE chokepoint that turns a model-emitted tool-call `{ name, args }`
|
|
5
|
+
* into an executed, LLM-safe result. It is the SDK-side dispatch path the T1738
|
|
6
|
+
* architecture (D11141) names: **core DISPATCHES, cleo-os DRIVES**. Given a tool
|
|
7
|
+
* call from the agent loop, it:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Looks up** the tool in the injected {@link AgentToolRegistry} (the
|
|
10
|
+
* name→descriptor catalog). An unknown name is a typed
|
|
11
|
+
* `tool-not-found` result — never a throw.
|
|
12
|
+
* 2. **Validates** the model-supplied arguments against the tool's Zod schema
|
|
13
|
+
* (defense in depth: the wire layer advertised the JSON-schema, but the
|
|
14
|
+
* model's emitted args are re-validated here before any side effect). A
|
|
15
|
+
* schema failure is a typed `invalid-args` result carrying the FLATTENED
|
|
16
|
+
* issue list — not the raw Zod error (no internals leak).
|
|
17
|
+
* 3. **Checks availability** for the current {@link ToolAvailabilityContext}
|
|
18
|
+
* (network egress, binaries on PATH, capability flags). An unavailable tool
|
|
19
|
+
* is a typed `guard-denied` result — the model is told the tool is not
|
|
20
|
+
* offered right now, with no precondition internals.
|
|
21
|
+
* 4. **Charges the budget** ({@link ToolCallBudget}) — per-call count / wall-time
|
|
22
|
+
* ceilings. An exhausted budget short-circuits with a typed `guard-denied`
|
|
23
|
+
* result BEFORE the tool runs; a per-call timeout races the execution and
|
|
24
|
+
* yields a typed `timeout` result.
|
|
25
|
+
* 5. **Executes** the bound {@link AgentToolExecutable} over the injected
|
|
26
|
+
* {@link GuardedToolSurface} — the deny-first chokepoint
|
|
27
|
+
* ({@link ./guard.js}). Every fs/shell side effect therefore still funnels
|
|
28
|
+
* through the guard's path allowlist + command denylist + env scrub; there
|
|
29
|
+
* is NO raw-primitive bypass.
|
|
30
|
+
* 6. **Formats** the outcome for LLM consumption ({@link formatToolResultForLlm})
|
|
31
|
+
* — a `tool_result`-shaped payload the Pi loop feeds back to the model. On
|
|
32
|
+
* ANY error the message is a redacted, classified summary; a raw secret,
|
|
33
|
+
* stack trace, or guard internal NEVER reaches the model-facing string.
|
|
34
|
+
*
|
|
35
|
+
* ## Sync + async handlers (AC2)
|
|
36
|
+
*
|
|
37
|
+
* A registered {@link AgentToolExecutable} returns a `Promise<unknown>`, but a
|
|
38
|
+
* synchronous handler that returns a plain value is equally supported: the
|
|
39
|
+
* engine `await`s the call so a non-thenable return is normalized to a resolved
|
|
40
|
+
* value, and a synchronous throw is caught the same as a rejected promise.
|
|
41
|
+
*
|
|
42
|
+
* ## Why it lives in `core/src/tools` (Gate-11)
|
|
43
|
+
*
|
|
44
|
+
* The dispatch engine is a CORE-SDK concern, built entirely against the frozen
|
|
45
|
+
* {@link AgentToolRegistry} + {@link GuardedToolSurface} contracts. It defines
|
|
46
|
+
* NO new atomic tool primitive (Gate-11 Tools-vs-Skills boundary) and makes NO
|
|
47
|
+
* LLM call (Gate-13): result-formatting is pure serialization. The harness
|
|
48
|
+
* (`cleo-os`) and the in-process Pi adapter merely SUPPLY the registry, the
|
|
49
|
+
* guard, and the workspace context, then read the {@link ToolDispatchResult}.
|
|
50
|
+
*
|
|
51
|
+
* @task T1740
|
|
52
|
+
* @epic T11456
|
|
53
|
+
* @see ./agent-registry.js — the name→descriptor catalog + availability checks
|
|
54
|
+
* @see ./guard.js — the deny-first surface every executable routes side effects through
|
|
55
|
+
* @see ../llm/pi/pi-tool-bridge.js — the AC6 consumer wiring this into the Pi loop
|
|
56
|
+
*/
|
|
57
|
+
import { getLogger } from '../logger.js';
|
|
58
|
+
import { ALWAYS_AVAILABLE } from './agent-registry.js';
|
|
59
|
+
const log = getLogger('tool-dispatch');
|
|
60
|
+
/**
|
|
61
|
+
* Stable machine-readable error codes, one per {@link ToolDispatchErrorKind}.
|
|
62
|
+
* Surfaced on the failure envelope so a programmatic caller never string-matches
|
|
63
|
+
* the human message.
|
|
64
|
+
*/
|
|
65
|
+
export const TOOL_DISPATCH_ERROR_CODE = {
|
|
66
|
+
'tool-not-found': 'E_TOOL_NOT_FOUND',
|
|
67
|
+
'invalid-args': 'E_TOOL_INVALID_ARGS',
|
|
68
|
+
'guard-denied': 'E_TOOL_GUARD_DENIED',
|
|
69
|
+
'execution-error': 'E_TOOL_EXECUTION_ERROR',
|
|
70
|
+
timeout: 'E_TOOL_TIMEOUT',
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Per-run tool-call budget (AC5). Tracks call count + cumulative wall-time
|
|
74
|
+
* against the configured {@link ToolBudgetLimits}. The engine consults
|
|
75
|
+
* {@link admit} BEFORE running a tool (rejecting once a ceiling is hit) and
|
|
76
|
+
* {@link charge}s the elapsed time AFTER. A single budget instance is threaded
|
|
77
|
+
* across all calls of one run, so the count/time caps are run-scoped.
|
|
78
|
+
*/
|
|
79
|
+
export class ToolCallBudget {
|
|
80
|
+
#limits;
|
|
81
|
+
#callsUsed = 0;
|
|
82
|
+
#timeUsedMs = 0;
|
|
83
|
+
/**
|
|
84
|
+
* @param limits - The ceilings this budget enforces (each optional → uncapped).
|
|
85
|
+
*/
|
|
86
|
+
constructor(limits = {}) {
|
|
87
|
+
this.#limits = limits;
|
|
88
|
+
}
|
|
89
|
+
/** The per-call timeout ceiling in ms, or `undefined` when uncapped. */
|
|
90
|
+
get perCallTimeoutMs() {
|
|
91
|
+
return this.#limits.perCallTimeoutMs;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether another call is admitted right now. Returns a typed denial reason
|
|
95
|
+
* (or `null` when admitted) so the engine can map it onto the precise
|
|
96
|
+
* `guard-denied` message WITHOUT exposing the limit internals to the model.
|
|
97
|
+
*
|
|
98
|
+
* @returns `null` when a call is admitted; otherwise the human-readable reason.
|
|
99
|
+
*/
|
|
100
|
+
admit() {
|
|
101
|
+
if (this.#limits.maxCalls !== undefined && this.#callsUsed >= this.#limits.maxCalls) {
|
|
102
|
+
return `tool-call budget exhausted (max ${this.#limits.maxCalls} calls)`;
|
|
103
|
+
}
|
|
104
|
+
if (this.#limits.totalTimeBudgetMs !== undefined &&
|
|
105
|
+
this.#timeUsedMs >= this.#limits.totalTimeBudgetMs) {
|
|
106
|
+
return `tool-call time budget exhausted (max ${this.#limits.totalTimeBudgetMs}ms)`;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Charge one completed call against the budget.
|
|
112
|
+
*
|
|
113
|
+
* @param elapsedMs - The call's measured wall-time in milliseconds.
|
|
114
|
+
*/
|
|
115
|
+
charge(elapsedMs) {
|
|
116
|
+
this.#callsUsed += 1;
|
|
117
|
+
this.#timeUsedMs += Math.max(0, elapsedMs);
|
|
118
|
+
}
|
|
119
|
+
/** A read-only snapshot of current consumption (AC5). */
|
|
120
|
+
snapshot() {
|
|
121
|
+
const callsRemaining = this.#limits.maxCalls === undefined
|
|
122
|
+
? Number.POSITIVE_INFINITY
|
|
123
|
+
: Math.max(0, this.#limits.maxCalls - this.#callsUsed);
|
|
124
|
+
const timeRemainingMs = this.#limits.totalTimeBudgetMs === undefined
|
|
125
|
+
? Number.POSITIVE_INFINITY
|
|
126
|
+
: Math.max(0, this.#limits.totalTimeBudgetMs - this.#timeUsedMs);
|
|
127
|
+
return {
|
|
128
|
+
callsUsed: this.#callsUsed,
|
|
129
|
+
callsRemaining,
|
|
130
|
+
timeUsedMs: this.#timeUsedMs,
|
|
131
|
+
timeRemainingMs,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// Engine
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
/** Maximum length of a formatted `tool_result` body before truncation (AC4). */
|
|
139
|
+
export const MAX_TOOL_RESULT_CHARS = 16_000;
|
|
140
|
+
/**
|
|
141
|
+
* The agent tool-call dispatch engine (AC1).
|
|
142
|
+
*
|
|
143
|
+
* One instance per agent run: it holds the frozen registry, the guarded surface,
|
|
144
|
+
* the availability context, and the run-scoped budget, and dispatches each
|
|
145
|
+
* model-emitted {@link ToolCall} to a classified {@link ToolDispatchResult}.
|
|
146
|
+
* {@link dispatch} NEVER throws — every failure mode is encoded as a typed
|
|
147
|
+
* {@link ToolDispatchFailure} (AC3), so the agent loop can always feed a
|
|
148
|
+
* `tool_result` back and continue.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const registry = await createAgentToolRegistry();
|
|
153
|
+
* const tools = createToolGuard({ allowedRoots: [root], mode: 'enforce' });
|
|
154
|
+
* const engine = new ToolDispatchEngine({
|
|
155
|
+
* registry,
|
|
156
|
+
* tools,
|
|
157
|
+
* budget: new ToolCallBudget({ maxCalls: 50, perCallTimeoutMs: 30_000 }),
|
|
158
|
+
* });
|
|
159
|
+
* const result = await engine.dispatch({ id: 'c1', name: 'read_file_paged', arguments: { path } });
|
|
160
|
+
* // result.ok ? feed result.display : feed the classified result.message
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export class ToolDispatchEngine {
|
|
164
|
+
#registry;
|
|
165
|
+
#tools;
|
|
166
|
+
#availability;
|
|
167
|
+
#budget;
|
|
168
|
+
/**
|
|
169
|
+
* @param deps - The registry, guarded surface, availability context, and
|
|
170
|
+
* optional run-scoped budget.
|
|
171
|
+
*/
|
|
172
|
+
constructor(deps) {
|
|
173
|
+
this.#registry = deps.registry;
|
|
174
|
+
this.#tools = deps.tools;
|
|
175
|
+
this.#availability = deps.availability ?? {};
|
|
176
|
+
this.#budget = deps.budget ?? new ToolCallBudget();
|
|
177
|
+
}
|
|
178
|
+
/** A read-only snapshot of the run's budget consumption (AC5). */
|
|
179
|
+
budgetSnapshot() {
|
|
180
|
+
return this.#budget.snapshot();
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Dispatch a single model-emitted tool call to a classified result (AC1–AC5).
|
|
184
|
+
*
|
|
185
|
+
* The full pipeline — lookup → validate → availability → budget admit →
|
|
186
|
+
* (timed) execute → charge → format — runs here. It NEVER throws: a thrown
|
|
187
|
+
* executable (incl. a deny-first `GuardDeniedError` during the side effect)
|
|
188
|
+
* becomes an `execution-error`, a slow executable a `timeout`, a cancelled call
|
|
189
|
+
* an `execution-error`, an unknown name a `tool-not-found`, bad args an
|
|
190
|
+
* `invalid-args`, and an unavailable tool / exhausted budget a `guard-denied`.
|
|
191
|
+
*
|
|
192
|
+
* ## Abort handling (honest contract)
|
|
193
|
+
*
|
|
194
|
+
* A `signal` (when supplied) is honoured REGARDLESS of whether a per-call
|
|
195
|
+
* timeout is configured — including the default no-budget production path: an
|
|
196
|
+
* already-aborted signal short-circuits before the executable is awaited, and
|
|
197
|
+
* an abort RACED in flight rejects with an `execution-error`. BUT this is
|
|
198
|
+
* abandon-the-promise cancellation, NOT preemptive kill: the frozen
|
|
199
|
+
* {@link AgentToolExecutable}/{@link GuardedToolSurface} contracts expose no
|
|
200
|
+
* signal channel, so a tool that already spawned an OS process keeps that
|
|
201
|
+
* process running until the tool's own internal bound fires. The engine
|
|
202
|
+
* returns promptly; the side effect may still be settling. See
|
|
203
|
+
* {@link ToolDispatchEngine.#runWithTimeout} for the full rationale.
|
|
204
|
+
*
|
|
205
|
+
* @param call - The model-emitted tool call (`{ id, name, arguments }`).
|
|
206
|
+
* @param signal - Optional abort signal honoured on every path (timeout or
|
|
207
|
+
* not); aborting yields an `execution-error` but cannot preemptively kill an
|
|
208
|
+
* in-flight OS process (frozen-contract limitation).
|
|
209
|
+
* @returns The terminal {@link ToolDispatchResult}.
|
|
210
|
+
*/
|
|
211
|
+
async dispatch(call, signal) {
|
|
212
|
+
// 1. Lookup (AC1).
|
|
213
|
+
const descriptor = this.#registry.get(call.name);
|
|
214
|
+
if (!descriptor) {
|
|
215
|
+
return this.#fail(call.name, 'tool-not-found', `unknown tool "${call.name}"`);
|
|
216
|
+
}
|
|
217
|
+
// 2. Validate arguments against the tool's Zod schema (AC3 — defense in depth).
|
|
218
|
+
const parsed = descriptor.parameters.safeParse(call.arguments);
|
|
219
|
+
if (!parsed.success) {
|
|
220
|
+
const issues = flattenZodIssues(parsed.error);
|
|
221
|
+
return this.#fail(call.name, 'invalid-args', `arguments for "${call.name}" failed validation`, issues);
|
|
222
|
+
}
|
|
223
|
+
// 3. Availability check (AC5 — registry availability predicates).
|
|
224
|
+
const available = (descriptor.available ?? ALWAYS_AVAILABLE)(this.#availability);
|
|
225
|
+
if (!available) {
|
|
226
|
+
return this.#fail(call.name, 'guard-denied', `tool "${call.name}" is not available in the current context`);
|
|
227
|
+
}
|
|
228
|
+
// 4. Budget admit (AC5 — count/time ceilings BEFORE running).
|
|
229
|
+
const denial = this.#budget.admit();
|
|
230
|
+
if (denial !== null) {
|
|
231
|
+
return this.#fail(call.name, 'guard-denied', denial);
|
|
232
|
+
}
|
|
233
|
+
// 5. Timed execution through the guarded surface (AC2 sync/async + AC5 timeout).
|
|
234
|
+
// `parsed.data` is the schema-validated argument object; a tool's `parameters`
|
|
235
|
+
// is always a Zod object schema, so the validated value is a record. Coerce to
|
|
236
|
+
// the executable's input shape (the generic `z.ZodType` infers `unknown`).
|
|
237
|
+
const validatedArgs = isRecord(parsed.data)
|
|
238
|
+
? parsed.data
|
|
239
|
+
: {};
|
|
240
|
+
const startedAt = Date.now();
|
|
241
|
+
try {
|
|
242
|
+
const value = await this.#runWithTimeout(descriptor, validatedArgs, signal);
|
|
243
|
+
this.#budget.charge(Date.now() - startedAt);
|
|
244
|
+
// 6. Format the success for LLM consumption (AC4).
|
|
245
|
+
return { ok: true, name: call.name, value, display: formatToolValueForLlm(value) };
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
// Charge the elapsed time even on failure so a long-running failed call
|
|
249
|
+
// still counts against the time budget (AC5).
|
|
250
|
+
this.#budget.charge(Date.now() - startedAt);
|
|
251
|
+
if (err instanceof ToolTimeoutError) {
|
|
252
|
+
return this.#fail(call.name, 'timeout', err.message);
|
|
253
|
+
}
|
|
254
|
+
if (err instanceof ToolAbortError) {
|
|
255
|
+
// Cancellation is an execution-error (the call did not complete). The
|
|
256
|
+
// engine-authored abort message carries no secret, so it is safe verbatim.
|
|
257
|
+
log.debug({ tool: call.name, kind: 'execution-error' }, 'tool dispatch aborted');
|
|
258
|
+
return this.#fail(call.name, 'execution-error', err.message);
|
|
259
|
+
}
|
|
260
|
+
// Any thrown executable error (incl. a deny-first GuardDeniedError raised
|
|
261
|
+
// DURING the side effect) is an execution-error. The message is REDACTED
|
|
262
|
+
// (AC3) — no raw secret/stack reaches the model-facing result.
|
|
263
|
+
const redacted = redactErrorMessage(err);
|
|
264
|
+
log.debug({ tool: call.name, kind: 'execution-error' }, 'tool dispatch execution failed');
|
|
265
|
+
return this.#fail(call.name, 'execution-error', redacted);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Run the executable, racing it against the per-call timeout AND the abort
|
|
270
|
+
* signal whenever EITHER is present.
|
|
271
|
+
*
|
|
272
|
+
* Both a synchronous return value and a `Promise` are handled: `await`ing a
|
|
273
|
+
* non-thenable normalizes it to a resolved value, and a synchronous throw is
|
|
274
|
+
* caught by {@link dispatch}'s surrounding `try` exactly like a rejection
|
|
275
|
+
* (AC2). The timeout rejects with a {@link ToolTimeoutError} the caller maps
|
|
276
|
+
* to a `timeout` result; an abort rejects with a {@link ToolAbortError} the
|
|
277
|
+
* caller maps to an `execution-error` (the call was cancelled, not slow).
|
|
278
|
+
*
|
|
279
|
+
* ## Fast path
|
|
280
|
+
*
|
|
281
|
+
* When NO timeout is configured AND no `signal` is supplied there is nothing
|
|
282
|
+
* to race, so the executable promise is returned directly — the engine adds no
|
|
283
|
+
* overhead to the common bounded-by-the-loop case.
|
|
284
|
+
*
|
|
285
|
+
* ## Cancellation is cooperative-or-abandon, NOT preemptive
|
|
286
|
+
*
|
|
287
|
+
* Winning the race (timeout or abort) only ABANDONS the executable promise —
|
|
288
|
+
* it does NOT cancel the running tool. The frozen {@link AgentToolExecutable}
|
|
289
|
+
* signature (`(args, tools) => Promise<unknown>`) and the frozen
|
|
290
|
+
* {@link GuardedToolSurface} (whose `executeShell`/`executePty`/`runGit` carry
|
|
291
|
+
* no `AbortSignal` parameter) expose NO cancellation channel, so a tool that
|
|
292
|
+
* spawned a real OS process keeps that process running in the background until
|
|
293
|
+
* the tool's OWN internal bound (its per-call `timeoutMs`, when the model
|
|
294
|
+
* supplied one) fires. The engine therefore returns a `timeout`/`execution-error`
|
|
295
|
+
* result to the model PROMPTLY while the underlying side effect may still be
|
|
296
|
+
* settling. This is the honest contract: the dispatch-level cap bounds how long
|
|
297
|
+
* the LOOP waits, not how long the OS process lives. Wiring a real signal
|
|
298
|
+
* through to the surface requires evolving the frozen registry +
|
|
299
|
+
* `GuardedToolSurface` contracts (out of T1740's scope — tracked for the
|
|
300
|
+
* contract owner of #1013).
|
|
301
|
+
*/
|
|
302
|
+
async #runWithTimeout(descriptor, args, signal) {
|
|
303
|
+
const timeoutMs = this.#budget.perCallTimeoutMs;
|
|
304
|
+
// `Promise.resolve(...)` normalizes a synchronous (non-thenable) return into
|
|
305
|
+
// a resolved promise; a synchronous throw inside `execute` rejects it.
|
|
306
|
+
const run = Promise.resolve(descriptor.execute(args, this.#tools));
|
|
307
|
+
// Fast path: nothing to race against → return the executable directly.
|
|
308
|
+
if (timeoutMs === undefined && signal === undefined)
|
|
309
|
+
return run;
|
|
310
|
+
// A pre-aborted signal short-circuits BEFORE awaiting the run — the abort was
|
|
311
|
+
// already requested, so the engine must not wait for (nor charge a full
|
|
312
|
+
// timeout for) the in-flight call. This is the default-production path the
|
|
313
|
+
// no-budget config exercises: the signal is honoured even with no timeout.
|
|
314
|
+
if (signal?.aborted) {
|
|
315
|
+
throw new ToolAbortError(`tool "${descriptor.name}" call aborted`);
|
|
316
|
+
}
|
|
317
|
+
let timer;
|
|
318
|
+
let onAbort;
|
|
319
|
+
const interrupt = new Promise((_resolve, reject) => {
|
|
320
|
+
// Wire the per-call timeout only when one is configured.
|
|
321
|
+
if (timeoutMs !== undefined) {
|
|
322
|
+
timer = setTimeout(() => reject(new ToolTimeoutError(`tool "${descriptor.name}" exceeded the per-call timeout of ${timeoutMs}ms`)), timeoutMs);
|
|
323
|
+
}
|
|
324
|
+
// Wire the abort listener whenever a (not-yet-aborted) signal is present —
|
|
325
|
+
// independent of whether a timeout is set, so the default no-budget path
|
|
326
|
+
// still honours containment-managed cancellation.
|
|
327
|
+
if (signal) {
|
|
328
|
+
onAbort = () => reject(new ToolAbortError(`tool "${descriptor.name}" call aborted`));
|
|
329
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
return await Promise.race([run, interrupt]);
|
|
334
|
+
}
|
|
335
|
+
finally {
|
|
336
|
+
if (timer)
|
|
337
|
+
clearTimeout(timer);
|
|
338
|
+
if (signal && onAbort)
|
|
339
|
+
signal.removeEventListener('abort', onAbort);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Build a classified {@link ToolDispatchFailure}. */
|
|
343
|
+
#fail(name, kind, message, issues) {
|
|
344
|
+
return {
|
|
345
|
+
ok: false,
|
|
346
|
+
name,
|
|
347
|
+
kind,
|
|
348
|
+
code: TOOL_DISPATCH_ERROR_CODE[kind],
|
|
349
|
+
message,
|
|
350
|
+
...(issues !== undefined ? { issues } : {}),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** Thrown internally when a tool call exceeds its per-call timeout (AC5). */
|
|
355
|
+
export class ToolTimeoutError extends Error {
|
|
356
|
+
/** Machine-readable code. */
|
|
357
|
+
code = 'E_TOOL_TIMEOUT';
|
|
358
|
+
constructor(message) {
|
|
359
|
+
super(message);
|
|
360
|
+
this.name = 'ToolTimeoutError';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Thrown internally when a tool call is cancelled via its {@link AbortSignal}
|
|
365
|
+
* (containment-managed run cancel / OOM-guard / budget kill). Distinct from
|
|
366
|
+
* {@link ToolTimeoutError}: a timeout means "the call was too slow", an abort
|
|
367
|
+
* means "the run was cancelled out from under the call". The engine maps it to a
|
|
368
|
+
* typed `execution-error` result (the call did not complete).
|
|
369
|
+
*
|
|
370
|
+
* @remarks Winning the abort race only ABANDONS the executable promise — it does
|
|
371
|
+
* NOT preemptively kill a tool that already spawned an OS process, because the
|
|
372
|
+
* frozen {@link AgentToolExecutable}/{@link GuardedToolSurface} contracts carry
|
|
373
|
+
* no signal channel (see {@link ToolDispatchEngine.dispatch}).
|
|
374
|
+
*/
|
|
375
|
+
export class ToolAbortError extends Error {
|
|
376
|
+
/** Machine-readable code. */
|
|
377
|
+
code = 'E_TOOL_ABORTED';
|
|
378
|
+
constructor(message) {
|
|
379
|
+
super(message);
|
|
380
|
+
this.name = 'ToolAbortError';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// ---------------------------------------------------------------------------
|
|
384
|
+
// Result formatting (AC4)
|
|
385
|
+
// ---------------------------------------------------------------------------
|
|
386
|
+
/**
|
|
387
|
+
* Render a tool's raw return value into the LLM-facing `tool_result` body (AC4).
|
|
388
|
+
*
|
|
389
|
+
* A string passes through; everything else is JSON-serialized (pretty, stable).
|
|
390
|
+
* A circular / non-serializable value degrades to its `String(...)` form rather
|
|
391
|
+
* than throwing. The result is hard-capped at {@link MAX_TOOL_RESULT_CHARS} with
|
|
392
|
+
* a truncation marker so one tool call cannot blow the model's context window.
|
|
393
|
+
*
|
|
394
|
+
* @param value - The raw tool return.
|
|
395
|
+
* @returns The model-safe `tool_result` string.
|
|
396
|
+
*/
|
|
397
|
+
export function formatToolValueForLlm(value) {
|
|
398
|
+
let rendered;
|
|
399
|
+
if (typeof value === 'string') {
|
|
400
|
+
rendered = value;
|
|
401
|
+
}
|
|
402
|
+
else if (value === undefined) {
|
|
403
|
+
rendered = '';
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
try {
|
|
407
|
+
rendered = JSON.stringify(value, null, 2) ?? String(value);
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// Circular / BigInt / non-serializable — degrade rather than throw.
|
|
411
|
+
rendered = String(value);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return truncateForLlm(rendered);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Project a terminal {@link ToolDispatchResult} onto the `tool_result` message
|
|
418
|
+
* shape the agent loop feeds back to the model (AC4).
|
|
419
|
+
*
|
|
420
|
+
* The `content` is `result.display` on success or the redacted, classified
|
|
421
|
+
* `result.message` on failure (with the flattened arg issues appended when
|
|
422
|
+
* present). `isError` lets the loop / transport mark the turn so the model knows
|
|
423
|
+
* the call did not succeed.
|
|
424
|
+
*
|
|
425
|
+
* @param result - The dispatch outcome.
|
|
426
|
+
* @returns A `{ toolCallId, toolName, content, isError }` envelope.
|
|
427
|
+
*/
|
|
428
|
+
export function formatToolResultForLlm(call, result) {
|
|
429
|
+
if (result.ok) {
|
|
430
|
+
return { toolCallId: call.id, toolName: result.name, content: result.display, isError: false };
|
|
431
|
+
}
|
|
432
|
+
const issuesText = result.issues && result.issues.length > 0
|
|
433
|
+
? `\n${result.issues.map((i) => `- ${i.path ? `${i.path}: ` : ''}${i.message}`).join('\n')}`
|
|
434
|
+
: '';
|
|
435
|
+
return {
|
|
436
|
+
toolCallId: call.id,
|
|
437
|
+
toolName: result.name,
|
|
438
|
+
content: `[${result.code}] ${result.message}${issuesText}`,
|
|
439
|
+
isError: true,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// ---------------------------------------------------------------------------
|
|
443
|
+
// Redaction + helpers
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
/** Truncate a rendered result to {@link MAX_TOOL_RESULT_CHARS} with a marker. */
|
|
446
|
+
function truncateForLlm(text) {
|
|
447
|
+
if (text.length <= MAX_TOOL_RESULT_CHARS)
|
|
448
|
+
return text;
|
|
449
|
+
const head = text.slice(0, MAX_TOOL_RESULT_CHARS);
|
|
450
|
+
const omitted = text.length - MAX_TOOL_RESULT_CHARS;
|
|
451
|
+
return `${head}\n…[truncated ${omitted} chars]`;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Produce a REDACTED, model-safe message from a thrown executable error (AC3).
|
|
455
|
+
*
|
|
456
|
+
* Only the error's single-line `message` is kept — never the stack trace (which
|
|
457
|
+
* can leak absolute paths / internal module structure) and never an attached
|
|
458
|
+
* `cause`. The message itself is scrubbed for obvious secret-looking tokens so a
|
|
459
|
+
* tool that interpolates a credential into its error string cannot leak it to
|
|
460
|
+
* the model. A non-`Error` throw degrades to a fixed, generic summary.
|
|
461
|
+
*
|
|
462
|
+
* @param err - The thrown value.
|
|
463
|
+
* @returns A single-line, secret-scrubbed message safe for the model.
|
|
464
|
+
*/
|
|
465
|
+
export function redactErrorMessage(err) {
|
|
466
|
+
const raw = err instanceof Error ? err.message : typeof err === 'string' ? err : 'tool execution failed';
|
|
467
|
+
// Keep a single line (drop any embedded newlines/stack-like content) and cap
|
|
468
|
+
// the length so a runaway message cannot dominate the context window.
|
|
469
|
+
const firstLine = raw.split('\n', 1)[0] ?? 'tool execution failed';
|
|
470
|
+
return scrubSecrets(firstLine).slice(0, 500);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Best-effort secret scrub for a model-facing string. Masks the high-signal
|
|
474
|
+
* secret shapes (provider key prefixes, bearer tokens, long base64-ish blobs)
|
|
475
|
+
* so a leaked credential cannot reach the LLM-facing result. This is a
|
|
476
|
+
* defense-in-depth net on TOP of the architecture's rule that no plaintext key
|
|
477
|
+
* is ever in scope here — not a substitute for it.
|
|
478
|
+
*
|
|
479
|
+
* @param text - The candidate message.
|
|
480
|
+
* @returns The message with secret-looking tokens masked.
|
|
481
|
+
*/
|
|
482
|
+
function scrubSecrets(text) {
|
|
483
|
+
return text
|
|
484
|
+
.replace(/\b(sk|pk|rk|ghp|gho|ghs|xoxb|xoxp|AKIA)[-_a-zA-Z0-9]{12,}\b/g, '[redacted]')
|
|
485
|
+
.replace(/\bBearer\s+[A-Za-z0-9._-]{12,}\b/gi, 'Bearer [redacted]')
|
|
486
|
+
.replace(/\beyJ[A-Za-z0-9._-]{20,}\b/g, '[redacted-jwt]');
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Flatten a {@link z.ZodError} into the minimal, model-safe
|
|
490
|
+
* {@link ToolArgIssue}[] — path + reason only, no raw Zod internals.
|
|
491
|
+
*
|
|
492
|
+
* @param error - The Zod validation error.
|
|
493
|
+
* @returns The flattened issues.
|
|
494
|
+
*/
|
|
495
|
+
export function flattenZodIssues(error) {
|
|
496
|
+
return error.issues.map((issue) => ({
|
|
497
|
+
path: issue.path.map(String).join('.'),
|
|
498
|
+
message: issue.message,
|
|
499
|
+
}));
|
|
500
|
+
}
|
|
501
|
+
/** Narrow an unknown to a plain (non-array) record. */
|
|
502
|
+
function isRecord(value) {
|
|
503
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/tools/dispatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subprocess environment scrubbing (T11897 · S1 · security hardening).
|
|
3
|
+
*
|
|
4
|
+
* Building the environment a child process inherits is a SECURITY boundary, not
|
|
5
|
+
* a convenience. Two distinct threats are closed here:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Sandbox escape via Pi-controlled env** — a model-driven loop that can
|
|
8
|
+
* set `LD_PRELOAD`, `NODE_OPTIONS`, `GIT_SSH_COMMAND`, `DYLD_*`, or a custom
|
|
9
|
+
* `PATH` gains arbitrary native code execution outside any command allowlist
|
|
10
|
+
* (the OS loader / Node honour them) and can satisfy a basename denylist with
|
|
11
|
+
* a workspace-resident impostor on a hijacked `PATH`. The scrubber NEVER
|
|
12
|
+
* forwards any of these dangerous variables and PINS `PATH` to a trusted
|
|
13
|
+
* absolute value.
|
|
14
|
+
* 2. **Daemon-secret exfiltration** — the daemon's own `process.env` carries
|
|
15
|
+
* resolved provider credentials (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …),
|
|
16
|
+
* OAuth headers, and vault material (`CLEO_VAULT_*`). Inheriting the full
|
|
17
|
+
* parent env into a Pi-spawned child means a single `env`/`printenv`/log line
|
|
18
|
+
* exfiltrates them. The scrubber builds a MINIMAL, explicitly-constructed env
|
|
19
|
+
* instead of inheriting `process.env`.
|
|
20
|
+
*
|
|
21
|
+
* The doctrine is **allowlist, not denylist**: the scrubbed env starts EMPTY and
|
|
22
|
+
* only a small set of benign, named variables are copied through from the parent
|
|
23
|
+
* (locale, terminal, home/user, tmp). Everything else — including every secret,
|
|
24
|
+
* every loader hook, and `PATH` itself — is reconstructed from trusted inputs.
|
|
25
|
+
*
|
|
26
|
+
* Pure function of input — no global/session coupling, import-time side-effect
|
|
27
|
+
* free (S1).
|
|
28
|
+
*
|
|
29
|
+
* @epic T10403
|
|
30
|
+
* @task T11761
|
|
31
|
+
* @task T11897
|
|
32
|
+
* @saga T11387
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* The trusted, absolute `PATH` a scrubbed subprocess runs under. A fixed list of
|
|
36
|
+
* standard system binary directories — NEVER the caller's (possibly Pi-poisoned)
|
|
37
|
+
* `PATH`. Pinning this defeats the "workspace-resident impostor on a hijacked
|
|
38
|
+
* PATH satisfies an allowed basename" escape: the workspace is not on it.
|
|
39
|
+
*/
|
|
40
|
+
export declare const TRUSTED_PATH: string;
|
|
41
|
+
/** Options for {@link scrubSubprocessEnv}. */
|
|
42
|
+
export interface ScrubEnvOptions {
|
|
43
|
+
/**
|
|
44
|
+
* The parent environment to copy the benign {@link PASSTHROUGH_KEYS} from.
|
|
45
|
+
* Defaults to `process.env`. Injectable for tests so a clean env can be
|
|
46
|
+
* asserted without mutating the real process environment.
|
|
47
|
+
*/
|
|
48
|
+
readonly parentEnv?: Readonly<Record<string, string | undefined>>;
|
|
49
|
+
/**
|
|
50
|
+
* Extra variables to set on the scrubbed env (e.g. a caller-supplied `env`
|
|
51
|
+
* merged ON TOP of the minimal base). These are themselves SCRUBBED — any
|
|
52
|
+
* dangerous key (loader hooks, `PATH`, secrets) is dropped, so an untrusted
|
|
53
|
+
* caller (Pi) cannot reintroduce an escape through this channel.
|
|
54
|
+
*/
|
|
55
|
+
readonly extra?: Readonly<Record<string, string | undefined>>;
|
|
56
|
+
/**
|
|
57
|
+
* The trusted `PATH` to pin. Defaults to {@link TRUSTED_PATH}. Callers MUST
|
|
58
|
+
* pass an absolute, trusted value — never the inbound/Pi-controlled `PATH`.
|
|
59
|
+
*/
|
|
60
|
+
readonly path?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether a variable NAME is forbidden in a scrubbed subprocess env (a loader
|
|
64
|
+
* hook, `PATH`, or a credential).
|
|
65
|
+
*
|
|
66
|
+
* @param name - The environment variable name.
|
|
67
|
+
* @returns `true` when the variable must NOT be forwarded.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isForbiddenEnvName(name: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Build a MINIMAL, explicitly-constructed subprocess environment.
|
|
72
|
+
*
|
|
73
|
+
* The result starts EMPTY; only the benign {@link PASSTHROUGH_KEYS} present in
|
|
74
|
+
* `parentEnv` are copied, `PATH` is pinned to a trusted absolute value, and any
|
|
75
|
+
* caller-supplied `extra` is merged ON TOP after being filtered through
|
|
76
|
+
* {@link isForbiddenEnvName}. The daemon's secrets and any loader hooks in the
|
|
77
|
+
* parent environment are therefore NEVER visible to the child, and an untrusted
|
|
78
|
+
* caller cannot reintroduce an escape via `extra`.
|
|
79
|
+
*
|
|
80
|
+
* @param options - {@link ScrubEnvOptions}.
|
|
81
|
+
* @returns A fresh `Record<string, string>` safe to hand to `spawn(..., { env })`.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* // A Pi-supplied env that tries to inject a loader hook + hijack PATH:
|
|
86
|
+
* const env = scrubSubprocessEnv({
|
|
87
|
+
* extra: { LD_PRELOAD: '/tmp/evil.so', PATH: '/workspace', SAFE: '1' },
|
|
88
|
+
* });
|
|
89
|
+
* // env has no LD_PRELOAD, PATH is the pinned TRUSTED_PATH, only SAFE survives.
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function scrubSubprocessEnv(options?: ScrubEnvOptions): Record<string, string>;
|
|
93
|
+
//# sourceMappingURL=env-scrub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-scrub.d.ts","sourceRoot":"","sources":["../../src/tools/env-scrub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH;;;;;GAKG;AACH,eAAO,MAAM,YAAY,QAOR,CAAC;AAsBlB,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC9D;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAiDD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMxD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,eAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0BxF"}
|