@cmetech/otto 1.1.0 → 1.2.4
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/coworker/persona-commands.d.ts +1 -0
- package/dist/coworker/persona-commands.js +5 -0
- package/dist/coworker/persona-commands.test.d.ts +1 -0
- package/dist/coworker/persona-commands.test.js +45 -0
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/_coworker-paths.js +8 -0
- package/dist/resources/extensions/coworker-artifacts/artifacts-command.js +31 -0
- package/dist/resources/extensions/coworker-artifacts/artifacts-singleton.js +17 -0
- package/dist/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
- package/dist/resources/extensions/coworker-artifacts/index.js +125 -0
- package/dist/resources/extensions/coworker-artifacts/list-tool.js +27 -0
- package/dist/resources/extensions/coworker-artifacts/open-tool.js +25 -0
- package/dist/resources/extensions/coworker-memory/extension-manifest.json +13 -0
- package/dist/resources/extensions/coworker-memory/index.js +219 -0
- package/dist/resources/extensions/coworker-memory/memorize-tool.js +10 -0
- package/dist/resources/extensions/coworker-memory/memory-command.js +157 -0
- package/dist/resources/extensions/coworker-memory/memory-singleton.js +55 -0
- package/dist/resources/extensions/coworker-memory/recall-tool.js +18 -0
- package/dist/resources/extensions/coworker-memory/session-hooks.js +45 -0
- package/dist/resources/extensions/coworker-scratchpad/attach-banners.js +53 -0
- package/dist/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
- package/dist/resources/extensions/coworker-scratchpad/format-age.js +9 -0
- package/dist/resources/extensions/coworker-scratchpad/helpers.js +38 -0
- package/dist/resources/extensions/coworker-scratchpad/index.js +199 -0
- package/dist/resources/extensions/coworker-scratchpad/mime-bundle.js +20 -0
- package/dist/resources/extensions/coworker-scratchpad/scratchpad-tool.js +118 -0
- package/dist/resources/extensions/coworker-scratchpad/session-sidecar.js +60 -0
- package/dist/resources/extensions/coworker-scratchpad/sp-command.js +597 -0
- package/dist/resources/extensions/coworker-scratchpad/workspace-pointer.js +41 -0
- package/dist/resources/extensions/coworker-scratchpad/workspace-root.js +17 -0
- package/dist/resources/extensions/coworker-vault/audit-command.js +35 -0
- package/dist/resources/extensions/coworker-vault/connect-command.js +42 -0
- package/dist/resources/extensions/coworker-vault/datasource-command.js +50 -0
- package/dist/resources/extensions/coworker-vault/extension-manifest.json +12 -0
- package/dist/resources/extensions/coworker-vault/index.js +171 -0
- package/dist/resources/extensions/coworker-vault/test-helpers.js +86 -0
- package/dist/resources/extensions/coworker-vault/vault-singleton.js +24 -0
- package/dist/resources/extensions/otto/commands/release-notes/_data.js +82 -0
- package/dist/resources/extensions/otto/commands/release-notes/command.js +15 -4
- package/dist/resources/extensions/subagent/index.js +8 -1
- package/dist/resources/extensions/subagent/launch.js +37 -5
- package/dist/resources/extensions/subagent/run-store.js +1 -0
- package/dist/resources/extensions/workflow/bootstrap/register-extension.js +2 -0
- package/dist/resources/extensions/workflow/bootstrap/register-hooks.js +10 -0
- package/dist/resources/extensions/workflow/persona-status.js +87 -0
- package/dist/update-cmd.d.ts +19 -0
- package/dist/update-cmd.js +177 -6
- package/package.json +25 -10
- package/packages/contracts/package.json +1 -1
- package/packages/coworker-artifacts/dist/artifact-store.d.ts +25 -0
- package/packages/coworker-artifacts/dist/artifact-store.js +187 -0
- package/packages/coworker-artifacts/dist/dir-snapshot.d.ts +7 -0
- package/packages/coworker-artifacts/dist/dir-snapshot.js +54 -0
- package/packages/coworker-artifacts/dist/errors.d.ts +18 -0
- package/packages/coworker-artifacts/dist/errors.js +37 -0
- package/packages/coworker-artifacts/dist/index.d.ts +7 -0
- package/packages/coworker-artifacts/dist/index.js +7 -0
- package/packages/coworker-artifacts/dist/readme-renderer.d.ts +5 -0
- package/packages/coworker-artifacts/dist/readme-renderer.js +47 -0
- package/packages/coworker-artifacts/dist/resolve-uri.d.ts +3 -0
- package/packages/coworker-artifacts/dist/resolve-uri.js +29 -0
- package/packages/coworker-artifacts/dist/slug.d.ts +4 -0
- package/packages/coworker-artifacts/dist/slug.js +32 -0
- package/packages/coworker-artifacts/dist/types.d.ts +52 -0
- package/packages/coworker-artifacts/dist/types.js +1 -0
- package/packages/coworker-artifacts/package.json +20 -0
- package/packages/coworker-artifacts/src/artifact-store.test.ts +188 -0
- package/packages/coworker-artifacts/src/artifact-store.ts +206 -0
- package/packages/coworker-artifacts/src/artifacts-integration.test.ts +109 -0
- package/packages/coworker-artifacts/src/dir-snapshot.test.ts +71 -0
- package/packages/coworker-artifacts/src/dir-snapshot.ts +52 -0
- package/packages/coworker-artifacts/src/errors.test.ts +37 -0
- package/packages/coworker-artifacts/src/errors.ts +28 -0
- package/packages/coworker-artifacts/src/index.test.ts +22 -0
- package/packages/coworker-artifacts/src/index.ts +7 -0
- package/packages/coworker-artifacts/src/readme-renderer.test.ts +72 -0
- package/packages/coworker-artifacts/src/readme-renderer.ts +56 -0
- package/packages/coworker-artifacts/src/resolve-uri.test.ts +46 -0
- package/packages/coworker-artifacts/src/resolve-uri.ts +29 -0
- package/packages/coworker-artifacts/src/slug.test.ts +47 -0
- package/packages/coworker-artifacts/src/slug.ts +31 -0
- package/packages/coworker-artifacts/src/types.ts +61 -0
- package/packages/coworker-artifacts/tsconfig.json +15 -0
- package/packages/coworker-artifacts/tsconfig.publish.json +4 -0
- package/packages/coworker-memory/dist/context-injection.d.ts +9 -0
- package/packages/coworker-memory/dist/context-injection.js +41 -0
- package/packages/coworker-memory/dist/errors.d.ts +25 -0
- package/packages/coworker-memory/dist/errors.js +51 -0
- package/packages/coworker-memory/dist/index.d.ts +12 -0
- package/packages/coworker-memory/dist/index.js +12 -0
- package/packages/coworker-memory/dist/layer-a-store.d.ts +16 -0
- package/packages/coworker-memory/dist/layer-a-store.js +78 -0
- package/packages/coworker-memory/dist/local-sqlite-backend.d.ts +28 -0
- package/packages/coworker-memory/dist/local-sqlite-backend.js +167 -0
- package/packages/coworker-memory/dist/memory-backend.d.ts +14 -0
- package/packages/coworker-memory/dist/memory-backend.js +1 -0
- package/packages/coworker-memory/dist/memory-recorder.d.ts +50 -0
- package/packages/coworker-memory/dist/memory-recorder.js +69 -0
- package/packages/coworker-memory/dist/migrations/001-init.sql +38 -0
- package/packages/coworker-memory/dist/migrations/002-artifact-kind.sql +50 -0
- package/packages/coworker-memory/dist/paste-detector.d.ts +5 -0
- package/packages/coworker-memory/dist/paste-detector.js +14 -0
- package/packages/coworker-memory/dist/persona-seed.d.ts +10 -0
- package/packages/coworker-memory/dist/persona-seed.js +38 -0
- package/packages/coworker-memory/dist/recall-formatter.d.ts +2 -0
- package/packages/coworker-memory/dist/recall-formatter.js +14 -0
- package/packages/coworker-memory/dist/scope-resolver.d.ts +9 -0
- package/packages/coworker-memory/dist/scope-resolver.js +10 -0
- package/packages/coworker-memory/dist/types.d.ts +51 -0
- package/packages/coworker-memory/dist/types.js +2 -0
- package/packages/coworker-memory/dist/workspace-id.d.ts +3 -0
- package/packages/coworker-memory/dist/workspace-id.js +54 -0
- package/packages/coworker-memory/package.json +35 -0
- package/packages/coworker-memory/src/activator-integration.test.ts +141 -0
- package/packages/coworker-memory/src/context-injection.test.ts +72 -0
- package/packages/coworker-memory/src/context-injection.ts +57 -0
- package/packages/coworker-memory/src/errors.test.ts +45 -0
- package/packages/coworker-memory/src/errors.ts +42 -0
- package/packages/coworker-memory/src/index.test.ts +21 -0
- package/packages/coworker-memory/src/index.ts +12 -0
- package/packages/coworker-memory/src/layer-a-store.test.ts +85 -0
- package/packages/coworker-memory/src/layer-a-store.ts +88 -0
- package/packages/coworker-memory/src/local-sqlite-backend.test.ts +110 -0
- package/packages/coworker-memory/src/local-sqlite-backend.ts +185 -0
- package/packages/coworker-memory/src/memory-backend.ts +10 -0
- package/packages/coworker-memory/src/memory-integration.test.ts +89 -0
- package/packages/coworker-memory/src/memory-recorder.test.ts +101 -0
- package/packages/coworker-memory/src/memory-recorder.ts +95 -0
- package/packages/coworker-memory/src/migrations/001-init.sql +38 -0
- package/packages/coworker-memory/src/migrations/002-artifact-kind.sql +50 -0
- package/packages/coworker-memory/src/paste-detector.test.ts +23 -0
- package/packages/coworker-memory/src/paste-detector.ts +18 -0
- package/packages/coworker-memory/src/persona-seed.test.ts +57 -0
- package/packages/coworker-memory/src/persona-seed.ts +46 -0
- package/packages/coworker-memory/src/recall-formatter.test.ts +34 -0
- package/packages/coworker-memory/src/recall-formatter.ts +15 -0
- package/packages/coworker-memory/src/scope-resolver.test.ts +23 -0
- package/packages/coworker-memory/src/scope-resolver.ts +18 -0
- package/packages/coworker-memory/src/types.ts +61 -0
- package/packages/coworker-memory/src/workspace-id.test.ts +48 -0
- package/packages/coworker-memory/src/workspace-id.ts +56 -0
- package/packages/coworker-memory/tsconfig.json +15 -0
- package/packages/coworker-memory/tsconfig.publish.json +4 -0
- package/packages/coworker-persona/dist/commands.d.ts +7 -0
- package/packages/coworker-persona/dist/commands.js +35 -0
- package/packages/coworker-persona/dist/defaults/manifest.yaml +12 -0
- package/packages/coworker-persona/dist/defaults/steering/identity.md +3 -0
- package/packages/coworker-persona/dist/index.d.ts +3 -0
- package/packages/coworker-persona/dist/index.js +3 -0
- package/packages/coworker-persona/dist/manifest.d.ts +24 -0
- package/packages/coworker-persona/dist/manifest.js +21 -0
- package/packages/coworker-persona/dist/registry.d.ts +22 -0
- package/packages/coworker-persona/dist/registry.js +142 -0
- package/packages/coworker-persona/package.json +28 -0
- package/packages/coworker-persona/scripts/copy-defaults.cjs +17 -0
- package/packages/coworker-persona/src/commands.ts +47 -0
- package/packages/coworker-persona/src/defaults/manifest.yaml +12 -0
- package/packages/coworker-persona/src/defaults/steering/identity.md +3 -0
- package/packages/coworker-persona/src/index.ts +3 -0
- package/packages/coworker-persona/src/manifest.test.ts +67 -0
- package/packages/coworker-persona/src/manifest.ts +49 -0
- package/packages/coworker-persona/src/registry.test.ts +89 -0
- package/packages/coworker-persona/src/registry.ts +147 -0
- package/packages/coworker-persona/tsconfig.json +15 -0
- package/packages/coworker-persona/tsconfig.publish.json +4 -0
- package/packages/coworker-scratchpad/dist/cell-archive.d.ts +39 -0
- package/packages/coworker-scratchpad/dist/cell-archive.js +77 -0
- package/packages/coworker-scratchpad/dist/cell-tree.d.ts +14 -0
- package/packages/coworker-scratchpad/dist/cell-tree.js +72 -0
- package/packages/coworker-scratchpad/dist/child-process-runtime.d.ts +129 -0
- package/packages/coworker-scratchpad/dist/child-process-runtime.js +427 -0
- package/packages/coworker-scratchpad/dist/collector-registry.d.ts +12 -0
- package/packages/coworker-scratchpad/dist/collector-registry.js +29 -0
- package/packages/coworker-scratchpad/dist/detect-kind.d.ts +3 -0
- package/packages/coworker-scratchpad/dist/detect-kind.js +19 -0
- package/packages/coworker-scratchpad/dist/file-collector.d.ts +15 -0
- package/packages/coworker-scratchpad/dist/file-collector.js +99 -0
- package/packages/coworker-scratchpad/dist/index.d.ts +13 -0
- package/packages/coworker-scratchpad/dist/index.js +13 -0
- package/packages/coworker-scratchpad/dist/kernel-bindings.d.ts +49 -0
- package/packages/coworker-scratchpad/dist/kernel-bindings.js +220 -0
- package/packages/coworker-scratchpad/dist/kernel-entry.d.ts +1 -0
- package/packages/coworker-scratchpad/dist/kernel-entry.js +355 -0
- package/packages/coworker-scratchpad/dist/kernel-protocol.d.ts +171 -0
- package/packages/coworker-scratchpad/dist/kernel-protocol.js +48 -0
- package/packages/coworker-scratchpad/dist/kernel-spawn.d.ts +3 -0
- package/packages/coworker-scratchpad/dist/kernel-spawn.js +54 -0
- package/packages/coworker-scratchpad/dist/namespace-codec.d.ts +22 -0
- package/packages/coworker-scratchpad/dist/namespace-codec.js +61 -0
- package/packages/coworker-scratchpad/dist/scratchpad-lock.d.ts +24 -0
- package/packages/coworker-scratchpad/dist/scratchpad-lock.js +86 -0
- package/packages/coworker-scratchpad/dist/scratchpad-manager.d.ts +193 -0
- package/packages/coworker-scratchpad/dist/scratchpad-manager.js +866 -0
- package/packages/coworker-scratchpad/dist/staleness-banner.d.ts +12 -0
- package/packages/coworker-scratchpad/dist/staleness-banner.js +27 -0
- package/packages/coworker-scratchpad/package.json +31 -0
- package/packages/coworker-scratchpad/src/cell-archive.test.ts +150 -0
- package/packages/coworker-scratchpad/src/cell-archive.ts +97 -0
- package/packages/coworker-scratchpad/src/cell-tree.test.ts +105 -0
- package/packages/coworker-scratchpad/src/cell-tree.ts +90 -0
- package/packages/coworker-scratchpad/src/child-process-runtime.test.ts +413 -0
- package/packages/coworker-scratchpad/src/child-process-runtime.ts +493 -0
- package/packages/coworker-scratchpad/src/collector-registry.test.ts +69 -0
- package/packages/coworker-scratchpad/src/collector-registry.ts +33 -0
- package/packages/coworker-scratchpad/src/detect-kind.test.ts +33 -0
- package/packages/coworker-scratchpad/src/detect-kind.ts +22 -0
- package/packages/coworker-scratchpad/src/file-collector.test.ts +109 -0
- package/packages/coworker-scratchpad/src/file-collector.ts +114 -0
- package/packages/coworker-scratchpad/src/index.ts +74 -0
- package/packages/coworker-scratchpad/src/kernel-bindings.test.ts +188 -0
- package/packages/coworker-scratchpad/src/kernel-bindings.ts +279 -0
- package/packages/coworker-scratchpad/src/kernel-entry.test.ts +123 -0
- package/packages/coworker-scratchpad/src/kernel-entry.ts +390 -0
- package/packages/coworker-scratchpad/src/kernel-protocol.test.ts +105 -0
- package/packages/coworker-scratchpad/src/kernel-protocol.ts +230 -0
- package/packages/coworker-scratchpad/src/kernel-spawn.test.ts +60 -0
- package/packages/coworker-scratchpad/src/kernel-spawn.ts +54 -0
- package/packages/coworker-scratchpad/src/namespace-codec.test.ts +102 -0
- package/packages/coworker-scratchpad/src/namespace-codec.ts +90 -0
- package/packages/coworker-scratchpad/src/scratchpad-lock.test.ts +98 -0
- package/packages/coworker-scratchpad/src/scratchpad-lock.ts +102 -0
- package/packages/coworker-scratchpad/src/scratchpad-manager.test.ts +1343 -0
- package/packages/coworker-scratchpad/src/scratchpad-manager.ts +891 -0
- package/packages/coworker-scratchpad/src/staleness-banner.test.ts +53 -0
- package/packages/coworker-scratchpad/src/staleness-banner.ts +33 -0
- package/packages/coworker-scratchpad/src/vault-integration.test.ts +221 -0
- package/packages/coworker-scratchpad/tsconfig.json +15 -0
- package/packages/coworker-scratchpad/tsconfig.publish.json +4 -0
- package/packages/coworker-types/dist/artifacts.d.ts +31 -0
- package/packages/coworker-types/dist/artifacts.js +2 -0
- package/packages/coworker-types/dist/contracts.d.ts +32 -0
- package/packages/coworker-types/dist/contracts.js +1 -0
- package/packages/coworker-types/dist/index.d.ts +5 -0
- package/packages/coworker-types/dist/index.js +5 -0
- package/packages/coworker-types/dist/memory.d.ts +61 -0
- package/packages/coworker-types/dist/memory.js +3 -0
- package/packages/coworker-types/dist/scratchpad.d.ts +43 -0
- package/packages/coworker-types/dist/scratchpad.js +2 -0
- package/packages/coworker-types/dist/vault.d.ts +34 -0
- package/packages/coworker-types/dist/vault.js +2 -0
- package/packages/coworker-types/package.json +24 -0
- package/packages/coworker-types/src/artifacts.test.ts +52 -0
- package/packages/coworker-types/src/artifacts.ts +35 -0
- package/packages/coworker-types/src/contracts.test.ts +43 -0
- package/packages/coworker-types/src/contracts.ts +36 -0
- package/packages/coworker-types/src/index.ts +5 -0
- package/packages/coworker-types/src/memory.test.ts +50 -0
- package/packages/coworker-types/src/memory.ts +79 -0
- package/packages/coworker-types/src/scratchpad.test.ts +46 -0
- package/packages/coworker-types/src/scratchpad.ts +51 -0
- package/packages/coworker-types/src/smoke.test.ts +34 -0
- package/packages/coworker-types/src/vault.test.ts +49 -0
- package/packages/coworker-types/src/vault.ts +40 -0
- package/packages/coworker-types/tsconfig.json +15 -0
- package/packages/coworker-types/tsconfig.publish.json +4 -0
- package/packages/coworker-utils/dist/audit-log.d.ts +34 -0
- package/packages/coworker-utils/dist/audit-log.js +88 -0
- package/packages/coworker-utils/dist/index.d.ts +6 -0
- package/packages/coworker-utils/dist/index.js +6 -0
- package/packages/coworker-utils/dist/lease.d.ts +7 -0
- package/packages/coworker-utils/dist/lease.js +67 -0
- package/packages/coworker-utils/dist/logger.d.ts +13 -0
- package/packages/coworker-utils/dist/logger.js +26 -0
- package/packages/coworker-utils/dist/migration-runner.d.ts +7 -0
- package/packages/coworker-utils/dist/migration-runner.js +36 -0
- package/packages/coworker-utils/dist/ndjson-channel.d.ts +3 -0
- package/packages/coworker-utils/dist/ndjson-channel.js +38 -0
- package/packages/coworker-utils/dist/secret-scanner.d.ts +10 -0
- package/packages/coworker-utils/dist/secret-scanner.js +42 -0
- package/packages/coworker-utils/package.json +24 -0
- package/packages/coworker-utils/src/audit-log.test.ts +140 -0
- package/packages/coworker-utils/src/audit-log.ts +107 -0
- package/packages/coworker-utils/src/index.ts +6 -0
- package/packages/coworker-utils/src/lease.test.ts +64 -0
- package/packages/coworker-utils/src/lease.ts +76 -0
- package/packages/coworker-utils/src/logger.test.ts +50 -0
- package/packages/coworker-utils/src/logger.ts +45 -0
- package/packages/coworker-utils/src/migration-runner.test.ts +65 -0
- package/packages/coworker-utils/src/migration-runner.ts +50 -0
- package/packages/coworker-utils/src/ndjson-channel.test.ts +76 -0
- package/packages/coworker-utils/src/ndjson-channel.ts +41 -0
- package/packages/coworker-utils/src/secret-scanner.test.ts +61 -0
- package/packages/coworker-utils/src/secret-scanner.ts +56 -0
- package/packages/coworker-utils/tsconfig.json +15 -0
- package/packages/coworker-utils/tsconfig.publish.json +4 -0
- package/packages/coworker-vault/dist/data-vault.d.ts +41 -0
- package/packages/coworker-vault/dist/data-vault.js +223 -0
- package/packages/coworker-vault/dist/engine-registry.d.ts +34 -0
- package/packages/coworker-vault/dist/engine-registry.js +90 -0
- package/packages/coworker-vault/dist/engines/jira.yaml +17 -0
- package/packages/coworker-vault/dist/errors.d.ts +28 -0
- package/packages/coworker-vault/dist/errors.js +57 -0
- package/packages/coworker-vault/dist/index.d.ts +6 -0
- package/packages/coworker-vault/dist/index.js +6 -0
- package/packages/coworker-vault/dist/injector.d.ts +19 -0
- package/packages/coworker-vault/dist/injector.js +77 -0
- package/packages/coworker-vault/dist/types.d.ts +28 -0
- package/packages/coworker-vault/dist/types.js +1 -0
- package/packages/coworker-vault/dist/vault-keep.d.ts +4 -0
- package/packages/coworker-vault/dist/vault-keep.js +21 -0
- package/packages/coworker-vault/package.json +29 -0
- package/packages/coworker-vault/src/data-vault.test.ts +199 -0
- package/packages/coworker-vault/src/data-vault.ts +257 -0
- package/packages/coworker-vault/src/engine-registry.test.ts +120 -0
- package/packages/coworker-vault/src/engine-registry.ts +107 -0
- package/packages/coworker-vault/src/engines/jira.yaml +17 -0
- package/packages/coworker-vault/src/errors.test.ts +58 -0
- package/packages/coworker-vault/src/errors.ts +50 -0
- package/packages/coworker-vault/src/index.test.ts +24 -0
- package/packages/coworker-vault/src/index.ts +6 -0
- package/packages/coworker-vault/src/injector.test.ts +109 -0
- package/packages/coworker-vault/src/injector.ts +98 -0
- package/packages/coworker-vault/src/types.ts +33 -0
- package/packages/coworker-vault/src/vault-keep.test.ts +49 -0
- package/packages/coworker-vault/src/vault-keep.ts +31 -0
- package/packages/coworker-vault/tsconfig.json +15 -0
- package/packages/coworker-vault/tsconfig.publish.json +4 -0
- package/packages/daemon/package.json +3 -3
- package/packages/mcp-server/package.json +3 -3
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/packages/native/package.json +1 -1
- package/packages/native/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +6 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +22 -3
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +11 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts +47 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js +107 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts +19 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js +121 -0
- package/packages/pi-coding-agent/dist/modes/rpc/raw-stdout.regression.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +17 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +2 -2
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +22 -3
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +11 -0
- package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.regression.test.ts +129 -0
- package/packages/pi-coding-agent/src/modes/rpc/raw-stdout.ts +117 -0
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +18 -1
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
- package/pkg/package.json +1 -1
- package/scripts/install.js +6 -5
- package/src/resources/extensions/_coworker-paths.test.ts +40 -0
- package/src/resources/extensions/_coworker-paths.ts +10 -0
- package/src/resources/extensions/coworker-artifacts/artifacts-command.test.ts +54 -0
- package/src/resources/extensions/coworker-artifacts/artifacts-command.ts +43 -0
- package/src/resources/extensions/coworker-artifacts/artifacts-singleton.test.ts +25 -0
- package/src/resources/extensions/coworker-artifacts/artifacts-singleton.ts +29 -0
- package/src/resources/extensions/coworker-artifacts/extension-manifest.json +13 -0
- package/src/resources/extensions/coworker-artifacts/index.test.ts +46 -0
- package/src/resources/extensions/coworker-artifacts/index.ts +154 -0
- package/src/resources/extensions/coworker-artifacts/list-tool.test.ts +29 -0
- package/src/resources/extensions/coworker-artifacts/list-tool.ts +53 -0
- package/src/resources/extensions/coworker-artifacts/open-tool.test.ts +30 -0
- package/src/resources/extensions/coworker-artifacts/open-tool.ts +43 -0
- package/src/resources/extensions/coworker-memory/extension-manifest.json +13 -0
- package/src/resources/extensions/coworker-memory/index.test.ts +137 -0
- package/src/resources/extensions/coworker-memory/index.ts +257 -0
- package/src/resources/extensions/coworker-memory/memorize-tool.test.ts +41 -0
- package/src/resources/extensions/coworker-memory/memorize-tool.ts +20 -0
- package/src/resources/extensions/coworker-memory/memory-command.test.ts +134 -0
- package/src/resources/extensions/coworker-memory/memory-command.ts +131 -0
- package/src/resources/extensions/coworker-memory/memory-singleton.test.ts +41 -0
- package/src/resources/extensions/coworker-memory/memory-singleton.ts +89 -0
- package/src/resources/extensions/coworker-memory/recall-tool.test.ts +50 -0
- package/src/resources/extensions/coworker-memory/recall-tool.ts +35 -0
- package/src/resources/extensions/coworker-memory/session-hooks.test.ts +77 -0
- package/src/resources/extensions/coworker-memory/session-hooks.ts +61 -0
- package/src/resources/extensions/coworker-scratchpad/attach-banners.test.ts +124 -0
- package/src/resources/extensions/coworker-scratchpad/attach-banners.ts +67 -0
- package/src/resources/extensions/coworker-scratchpad/extension-manifest.json +13 -0
- package/src/resources/extensions/coworker-scratchpad/format-age.test.ts +30 -0
- package/src/resources/extensions/coworker-scratchpad/format-age.ts +6 -0
- package/src/resources/extensions/coworker-scratchpad/helpers.test.ts +93 -0
- package/src/resources/extensions/coworker-scratchpad/helpers.ts +42 -0
- package/src/resources/extensions/coworker-scratchpad/index.test.ts +514 -0
- package/src/resources/extensions/coworker-scratchpad/index.ts +207 -0
- package/src/resources/extensions/coworker-scratchpad/mime-bundle.test.ts +61 -0
- package/src/resources/extensions/coworker-scratchpad/mime-bundle.ts +23 -0
- package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.test.ts +137 -0
- package/src/resources/extensions/coworker-scratchpad/scratchpad-tool.ts +165 -0
- package/src/resources/extensions/coworker-scratchpad/session-sidecar.test.ts +133 -0
- package/src/resources/extensions/coworker-scratchpad/session-sidecar.ts +68 -0
- package/src/resources/extensions/coworker-scratchpad/sp-command.test.ts +836 -0
- package/src/resources/extensions/coworker-scratchpad/sp-command.ts +602 -0
- package/src/resources/extensions/coworker-scratchpad/workspace-pointer.test.ts +74 -0
- package/src/resources/extensions/coworker-scratchpad/workspace-pointer.ts +55 -0
- package/src/resources/extensions/coworker-scratchpad/workspace-root.test.ts +51 -0
- package/src/resources/extensions/coworker-scratchpad/workspace-root.ts +16 -0
- package/src/resources/extensions/coworker-vault/audit-command.test.ts +109 -0
- package/src/resources/extensions/coworker-vault/audit-command.ts +56 -0
- package/src/resources/extensions/coworker-vault/connect-command.test.ts +103 -0
- package/src/resources/extensions/coworker-vault/connect-command.ts +69 -0
- package/src/resources/extensions/coworker-vault/datasource-command.test.ts +80 -0
- package/src/resources/extensions/coworker-vault/datasource-command.ts +81 -0
- package/src/resources/extensions/coworker-vault/extension-manifest.json +12 -0
- package/src/resources/extensions/coworker-vault/index.test.ts +82 -0
- package/src/resources/extensions/coworker-vault/index.ts +181 -0
- package/src/resources/extensions/coworker-vault/test-helpers.ts +120 -0
- package/src/resources/extensions/coworker-vault/vault-singleton.test.ts +27 -0
- package/src/resources/extensions/coworker-vault/vault-singleton.ts +40 -0
- package/src/resources/extensions/otto/commands/release-notes/_data.ts +96 -0
- package/src/resources/extensions/otto/commands/release-notes/command.ts +16 -3
- package/src/resources/extensions/subagent/index.ts +9 -0
- package/src/resources/extensions/subagent/launch.test.ts +97 -0
- package/src/resources/extensions/subagent/launch.ts +42 -5
- package/src/resources/extensions/subagent/run-store.ts +3 -1
- package/src/resources/extensions/workflow/bootstrap/register-extension.ts +2 -0
- package/src/resources/extensions/workflow/bootstrap/register-hooks.ts +10 -0
- package/src/resources/extensions/workflow/persona-status.ts +109 -0
- package/src/resources/extensions/workflow/tests/auto-recovery.test.ts +34 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { writeNdjson, readNdjson } from '@otto/coworker-utils';
|
|
4
|
+
import type { CredentialInjector } from '@otto/coworker-vault';
|
|
5
|
+
import { filterEnv, kernelExecArgv, resolveKernelEntry } from './kernel-spawn.js';
|
|
6
|
+
import {
|
|
7
|
+
isDataLoadEvent,
|
|
8
|
+
isProgressEvent,
|
|
9
|
+
isStartupErrorEvent,
|
|
10
|
+
isSnapshotResult,
|
|
11
|
+
isArtifactCreateEvent,
|
|
12
|
+
isArtifactCreateRequest,
|
|
13
|
+
isArtifactUpdateRequest,
|
|
14
|
+
} from './kernel-protocol.js';
|
|
15
|
+
import type {
|
|
16
|
+
ArtifactCreateDrawer,
|
|
17
|
+
ArtifactCreateRequest,
|
|
18
|
+
ArtifactCreateResponse,
|
|
19
|
+
ArtifactUpdateRequest,
|
|
20
|
+
ArtifactUpdateResponse,
|
|
21
|
+
DataLoadDrawer,
|
|
22
|
+
KernelFrame,
|
|
23
|
+
RecoveryNote,
|
|
24
|
+
SnapshotResult,
|
|
25
|
+
} from './kernel-protocol.js';
|
|
26
|
+
|
|
27
|
+
export interface CellResult {
|
|
28
|
+
value: unknown;
|
|
29
|
+
stdout: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ChildProcessRuntimeOptions {
|
|
33
|
+
workspace: string;
|
|
34
|
+
onDataLoad?: (drawer: DataLoadDrawer) => void;
|
|
35
|
+
cellTimeoutMs?: number; // total wall-clock hard cap per cell
|
|
36
|
+
inactivityTimeoutMs?: number; // silence cap before the first progress() heartbeat
|
|
37
|
+
inactivityAfterProgressMs?: number; // silence cap after a progress() heartbeat
|
|
38
|
+
cancelGraceMs?: number; // SIGINT -> SIGTERM/SIGKILL escalation window
|
|
39
|
+
entryPath?: string;
|
|
40
|
+
scratchpadDir?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Phase 2 Task 13: optional vault credential injector. When provided alongside
|
|
43
|
+
* a non-empty `bindings`, the runtime adds OTTO_DS_* env vars to the spawned
|
|
44
|
+
* kernel's environment (after the existing env filter). Absent => no-op; the
|
|
45
|
+
* runtime behaves exactly as it did pre-Phase-2.
|
|
46
|
+
*/
|
|
47
|
+
injector?: CredentialInjector;
|
|
48
|
+
bindings?: string[];
|
|
49
|
+
/**
|
|
50
|
+
* Phase 2 Task 13: identifies this runtime's scratchpad for audit records the
|
|
51
|
+
* injector emits. The runtime itself doesn't otherwise use this field.
|
|
52
|
+
*/
|
|
53
|
+
scratchpadName?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Phase 2 Task 13: session id stamped on audit records the injector emits.
|
|
56
|
+
* Empty string is a valid no-session value.
|
|
57
|
+
*/
|
|
58
|
+
sessionId?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Phase 4 Task 10: Layer-B fan-out for artifact_create events. The kernel
|
|
61
|
+
* emits one of these after every successful otto.artifact.create RPC; the
|
|
62
|
+
* runtime forwards the drawer to this callback so the manager can record it
|
|
63
|
+
* into memory. Absent => artifact_create events are dropped.
|
|
64
|
+
*/
|
|
65
|
+
onArtifactCreate?: (drawer: ArtifactCreateDrawer) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Phase 4 Task 10: parent-side handler for `artifact_create` RPC requests.
|
|
68
|
+
* The runtime calls this when it sees an `{type:'request', request:'artifact_create'}`
|
|
69
|
+
* frame on the child's stdout and writes the resolved response (or an error
|
|
70
|
+
* frame) back to the child's stdin. If absent, the request is rejected with
|
|
71
|
+
* "artifacts unavailable" so the cell fails fast instead of hanging.
|
|
72
|
+
*/
|
|
73
|
+
handleArtifactCreate?: (req: { kind: string; name: string }) =>
|
|
74
|
+
Promise<{ slug: string; uri: string; primary_path: string }>;
|
|
75
|
+
/**
|
|
76
|
+
* Phase 4 Task 10: parent-side handler for `artifact_update` RPC requests.
|
|
77
|
+
* Mirror of handleArtifactCreate for the update path.
|
|
78
|
+
*/
|
|
79
|
+
handleArtifactUpdate?: (req: { slug: string; files: Array<{ path: string; content: string }> }) =>
|
|
80
|
+
Promise<{ files_touched: string[] }>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface Pending {
|
|
84
|
+
resolve: (result: CellResult) => void;
|
|
85
|
+
reject: (err: Error) => void;
|
|
86
|
+
totalTimer: NodeJS.Timeout;
|
|
87
|
+
inactivityTimer: NodeJS.Timeout;
|
|
88
|
+
inactivityWindowMs: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const DEFAULT_CELL_TIMEOUT_MS = 120_000;
|
|
92
|
+
const DEFAULT_INACTIVITY_MS = 30_000;
|
|
93
|
+
const DEFAULT_INACTIVITY_AFTER_PROGRESS_MS = 60_000;
|
|
94
|
+
const DEFAULT_CANCEL_GRACE_MS = 2_000;
|
|
95
|
+
const MAX_RESTARTS_BEFORE_SUCCESS = 1;
|
|
96
|
+
|
|
97
|
+
export class ChildProcessRuntime {
|
|
98
|
+
private child: ChildProcessWithoutNullStreams | null = null;
|
|
99
|
+
private readonly pending = new Map<number, Pending>();
|
|
100
|
+
private readonly pendingSnapshots = new Map<number, (res: SnapshotResult) => void>();
|
|
101
|
+
private activeId: number | null = null;
|
|
102
|
+
private nextId = 1;
|
|
103
|
+
private alive = false;
|
|
104
|
+
private childReady = false;
|
|
105
|
+
private disposed = false;
|
|
106
|
+
private restartsSinceSuccess = 0;
|
|
107
|
+
private recoveryNotes_: RecoveryNote[] = [];
|
|
108
|
+
private resolveReady: () => void = () => {};
|
|
109
|
+
private rejectReady: (err: Error) => void = () => {};
|
|
110
|
+
private ready: Promise<void> = Promise.resolve();
|
|
111
|
+
/**
|
|
112
|
+
* Phase 2 Task 13: timestamp of the most recent successful spawn(). Used by
|
|
113
|
+
* higher-level staleness checks (Task 15) to decide whether a warm kernel's
|
|
114
|
+
* env-injected credentials need to be refreshed. Pre-start() value is epoch
|
|
115
|
+
* so callers can distinguish "never spawned" without nullable juggling.
|
|
116
|
+
*/
|
|
117
|
+
public spawnTime: Date = new Date(0);
|
|
118
|
+
|
|
119
|
+
constructor(private readonly options: ChildProcessRuntimeOptions) {}
|
|
120
|
+
|
|
121
|
+
async start(): Promise<void> {
|
|
122
|
+
if (this.disposed) throw new Error('runtime disposed');
|
|
123
|
+
await this.spawnChild();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Phase 2 Task 13: env construction split out so the injector hop has a clean
|
|
128
|
+
* seam to extend. Existing semantics unchanged: filterEnv applies the kernel's
|
|
129
|
+
* allowlist/denylist, then (when configured) the injector overlays OTTO_DS_*
|
|
130
|
+
* vars from vault entries. Only the spawned child sees the result; the parent
|
|
131
|
+
* process.env is never mutated.
|
|
132
|
+
*/
|
|
133
|
+
private async buildBaseEnv(): Promise<NodeJS.ProcessEnv> {
|
|
134
|
+
const base = filterEnv(process.env);
|
|
135
|
+
const { injector, bindings } = this.options;
|
|
136
|
+
if (!injector || !bindings || bindings.length === 0) return base;
|
|
137
|
+
return injector.injectEnv(base, bindings, {
|
|
138
|
+
scratchpadName: this.options.scratchpadName ?? '',
|
|
139
|
+
sessionId: this.options.sessionId ?? '',
|
|
140
|
+
pid: process.pid,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async spawnChild(): Promise<void> {
|
|
145
|
+
this.ready = new Promise<void>((resolve, reject) => {
|
|
146
|
+
this.resolveReady = resolve;
|
|
147
|
+
this.rejectReady = reject;
|
|
148
|
+
});
|
|
149
|
+
const entry = this.options.entryPath ?? resolveKernelEntry();
|
|
150
|
+
const args = [...kernelExecArgv(), entry, this.options.workspace];
|
|
151
|
+
if (this.options.scratchpadDir !== undefined) args.push(this.options.scratchpadDir);
|
|
152
|
+
const env = await this.buildBaseEnv();
|
|
153
|
+
const child = spawn(
|
|
154
|
+
process.execPath,
|
|
155
|
+
args,
|
|
156
|
+
{ stdio: ['pipe', 'pipe', 'inherit'], cwd: process.cwd(), env },
|
|
157
|
+
) as unknown as ChildProcessWithoutNullStreams;
|
|
158
|
+
this.child = child;
|
|
159
|
+
this.alive = true;
|
|
160
|
+
this.childReady = false;
|
|
161
|
+
// Phase 2 Task 13: stamp spawnTime immediately after a successful spawn.
|
|
162
|
+
// Done synchronously so the ready promise (which the caller awaits) reflects
|
|
163
|
+
// a non-epoch spawnTime by the time start() returns.
|
|
164
|
+
this.spawnTime = new Date();
|
|
165
|
+
child.on('exit', (code, signal) => {
|
|
166
|
+
if (this.child !== child) return; // superseded by a restart
|
|
167
|
+
this.alive = false;
|
|
168
|
+
this.childReady = false;
|
|
169
|
+
if (this.disposed) return;
|
|
170
|
+
const err = new Error(`kernel exited (code=${code ?? 'null'}, signal=${signal ?? 'null'})`);
|
|
171
|
+
this.rejectReady(err); // no-op if ready already resolved
|
|
172
|
+
this.failAllPending(err);
|
|
173
|
+
});
|
|
174
|
+
void this.readLoop(child);
|
|
175
|
+
return this.ready;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async readLoop(child: ChildProcessWithoutNullStreams): Promise<void> {
|
|
179
|
+
try {
|
|
180
|
+
for await (const raw of readNdjson(child.stdout)) {
|
|
181
|
+
if (this.child !== child) break; // superseded by a restart
|
|
182
|
+
const frame = raw as KernelFrame;
|
|
183
|
+
if (frame.type === 'event') {
|
|
184
|
+
if (frame.event === 'ready') {
|
|
185
|
+
if (frame.recovery_notes) this.recoveryNotes_ = [...frame.recovery_notes];
|
|
186
|
+
this.childReady = true;
|
|
187
|
+
this.resolveReady();
|
|
188
|
+
}
|
|
189
|
+
else if (isStartupErrorEvent(frame)) {
|
|
190
|
+
const err = new Error(frame.error.message);
|
|
191
|
+
err.name = `startup_error/${frame.kind}`;
|
|
192
|
+
this.rejectReady(err);
|
|
193
|
+
}
|
|
194
|
+
else if (isDataLoadEvent(frame)) this.options.onDataLoad?.(frame.drawer);
|
|
195
|
+
else if (isArtifactCreateEvent(frame)) this.options.onArtifactCreate?.(frame.drawer);
|
|
196
|
+
else if (isProgressEvent(frame)) this.resetInactivity();
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (isSnapshotResult(frame)) {
|
|
200
|
+
const resolver = this.pendingSnapshots.get(frame.id);
|
|
201
|
+
if (resolver) {
|
|
202
|
+
this.pendingSnapshots.delete(frame.id);
|
|
203
|
+
resolver(frame);
|
|
204
|
+
}
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
// Phase 4 Task 10: artifact RPC requests originate from the kernel and
|
|
208
|
+
// are serviced by the manager via handleArtifactCreate/Update. Handle
|
|
209
|
+
// before the result-fallthrough — these frames carry a string `id`
|
|
210
|
+
// (kernel mints `art-<pid>-<seq>`) and would otherwise be miskeyed
|
|
211
|
+
// against the numeric `pending` map.
|
|
212
|
+
if (isArtifactCreateRequest(frame)) {
|
|
213
|
+
void this.handleArtifactCreateRpc(frame);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (isArtifactUpdateRequest(frame)) {
|
|
217
|
+
void this.handleArtifactUpdateRpc(frame);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (frame.type !== 'result') continue; // defensive: unknown frame shape
|
|
221
|
+
const p = this.pending.get(frame.id);
|
|
222
|
+
if (!p) continue;
|
|
223
|
+
clearTimeout(p.totalTimer);
|
|
224
|
+
clearTimeout(p.inactivityTimer);
|
|
225
|
+
this.pending.delete(frame.id);
|
|
226
|
+
if (this.activeId === frame.id) this.activeId = null;
|
|
227
|
+
this.restartsSinceSuccess = 0; // a completed cell proves the kernel is healthy
|
|
228
|
+
if (frame.ok) {
|
|
229
|
+
p.resolve({ value: frame.value, stdout: frame.stdout });
|
|
230
|
+
} else {
|
|
231
|
+
const err = new Error(frame.error.message);
|
|
232
|
+
err.name = frame.error.name;
|
|
233
|
+
if (frame.error.stack) err.stack = frame.error.stack;
|
|
234
|
+
p.reject(err);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
this.failAllPending(err as Error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private resetInactivity(): void {
|
|
243
|
+
if (this.activeId === null) return;
|
|
244
|
+
const id = this.activeId;
|
|
245
|
+
const p = this.pending.get(id);
|
|
246
|
+
if (!p) return;
|
|
247
|
+
p.inactivityWindowMs =
|
|
248
|
+
this.options.inactivityAfterProgressMs ?? DEFAULT_INACTIVITY_AFTER_PROGRESS_MS;
|
|
249
|
+
clearTimeout(p.inactivityTimer);
|
|
250
|
+
p.inactivityTimer = setTimeout(() => this.onInactivityTimeout(id), p.inactivityWindowMs);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private onInactivityTimeout(id: number): void {
|
|
254
|
+
const p = this.pending.get(id);
|
|
255
|
+
if (!p) return;
|
|
256
|
+
clearTimeout(p.totalTimer);
|
|
257
|
+
this.pending.delete(id);
|
|
258
|
+
if (this.activeId === id) this.activeId = null;
|
|
259
|
+
this.markDead();
|
|
260
|
+
p.reject(new Error(`cell ${id} timed out after ${p.inactivityWindowMs}ms of inactivity`));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private onTotalTimeout(id: number, totalMs: number): void {
|
|
264
|
+
const p = this.pending.get(id);
|
|
265
|
+
if (!p) return;
|
|
266
|
+
clearTimeout(p.inactivityTimer);
|
|
267
|
+
this.pending.delete(id);
|
|
268
|
+
if (this.activeId === id) this.activeId = null;
|
|
269
|
+
this.markDead();
|
|
270
|
+
p.reject(new Error(`cell ${id} timed out after ${totalMs}ms (total wall-clock)`));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async runCell(code: string): Promise<CellResult> {
|
|
274
|
+
if (this.disposed) throw new Error('runtime disposed');
|
|
275
|
+
if (!this.alive) {
|
|
276
|
+
if (this.restartsSinceSuccess >= MAX_RESTARTS_BEFORE_SUCCESS) {
|
|
277
|
+
throw new Error(
|
|
278
|
+
`kernel repeatedly crashed (${this.restartsSinceSuccess} restart(s) without a successful cell); giving up`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
this.restartsSinceSuccess++;
|
|
282
|
+
this.spawnChild(); // start a fresh kernel; the write below waits for its ready
|
|
283
|
+
}
|
|
284
|
+
const id = this.nextId++;
|
|
285
|
+
const totalMs = this.options.cellTimeoutMs ?? DEFAULT_CELL_TIMEOUT_MS;
|
|
286
|
+
const inactivityMs = this.options.inactivityTimeoutMs ?? DEFAULT_INACTIVITY_MS;
|
|
287
|
+
const result = new Promise<CellResult>((resolve, reject) => {
|
|
288
|
+
const totalTimer = setTimeout(() => this.onTotalTimeout(id, totalMs), totalMs);
|
|
289
|
+
const inactivityTimer = setTimeout(() => this.onInactivityTimeout(id), inactivityMs);
|
|
290
|
+
this.pending.set(id, { resolve, reject, totalTimer, inactivityTimer, inactivityWindowMs: inactivityMs });
|
|
291
|
+
});
|
|
292
|
+
// Register the active cell synchronously so cancel()/timeouts can act on it even
|
|
293
|
+
// while a restarted kernel is still starting up. Send the code once it's ready.
|
|
294
|
+
this.activeId = id;
|
|
295
|
+
const ready = this.ready;
|
|
296
|
+
void ready
|
|
297
|
+
.then(() => {
|
|
298
|
+
const child = this.child;
|
|
299
|
+
if (child && this.pending.has(id)) return writeNdjson(child.stdin, { id, type: 'run', code });
|
|
300
|
+
})
|
|
301
|
+
.catch(() => {
|
|
302
|
+
// ready rejected (kernel died during startup); the exit handler/cancel rejects this cell.
|
|
303
|
+
});
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async cancel(): Promise<void> {
|
|
308
|
+
const child = this.child;
|
|
309
|
+
if (!child || !this.alive) return;
|
|
310
|
+
const id = this.activeId;
|
|
311
|
+
if (!this.childReady) {
|
|
312
|
+
// The kernel is still starting (e.g. a fresh restart). SIGINT would race the
|
|
313
|
+
// child's SIG_IGN handler install and could kill it before it's installed, so
|
|
314
|
+
// escalate straight to a hard kill and reject the active cell as cancelled.
|
|
315
|
+
this.rejectActive(id, `cell ${id} cancelled`);
|
|
316
|
+
this.markDead();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
child.kill('SIGINT'); // child ignores SIGINT between cells; no-op for sync vm code mid-cell
|
|
320
|
+
if (id === null) return; // nothing running: the gentle signal is harmless
|
|
321
|
+
await new Promise((r) => setTimeout(r, this.options.cancelGraceMs ?? DEFAULT_CANCEL_GRACE_MS));
|
|
322
|
+
const p = this.pending.get(id);
|
|
323
|
+
if (!p) return; // settled within the grace window
|
|
324
|
+
clearTimeout(p.totalTimer);
|
|
325
|
+
clearTimeout(p.inactivityTimer);
|
|
326
|
+
this.pending.delete(id);
|
|
327
|
+
if (this.activeId === id) this.activeId = null;
|
|
328
|
+
this.markDead(); // escalate: SIGTERM -> SIGKILL
|
|
329
|
+
p.reject(new Error(`cell ${id} cancelled`));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async snapshot(): Promise<SnapshotResult> {
|
|
333
|
+
if (this.disposed) {
|
|
334
|
+
return { id: 0, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDisposed', message: 'runtime disposed' } };
|
|
335
|
+
}
|
|
336
|
+
if (!this.alive || !this.child) {
|
|
337
|
+
return { id: 0, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDead', message: 'kernel is not alive' } };
|
|
338
|
+
}
|
|
339
|
+
const id = this.nextId++;
|
|
340
|
+
const result = new Promise<SnapshotResult>((resolve) => {
|
|
341
|
+
this.pendingSnapshots.set(id, resolve);
|
|
342
|
+
});
|
|
343
|
+
try {
|
|
344
|
+
await this.ready;
|
|
345
|
+
const child = this.child;
|
|
346
|
+
if (!child) {
|
|
347
|
+
this.pendingSnapshots.delete(id);
|
|
348
|
+
return { id, type: 'snapshot_result', ok: false, error: { name: 'RuntimeDead', message: 'kernel died before snapshot' } };
|
|
349
|
+
}
|
|
350
|
+
await writeNdjson(child.stdin, { id, type: 'snapshot' });
|
|
351
|
+
} catch (err) {
|
|
352
|
+
this.pendingSnapshots.delete(id);
|
|
353
|
+
const e = err as Error;
|
|
354
|
+
return { id, type: 'snapshot_result', ok: false, error: { name: e.name, message: e.message } };
|
|
355
|
+
}
|
|
356
|
+
return result;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Phase 4 Task 10: service an `artifact_create` RPC request from the kernel.
|
|
361
|
+
* Calls the manager-supplied handler (if any), serializes the result into a
|
|
362
|
+
* `{type:'response', request:'artifact_create'}` frame, and writes it back
|
|
363
|
+
* to the child's stdin. Errors are surfaced to the kernel as `ok:false`
|
|
364
|
+
* frames so the awaiting cell rejects cleanly rather than hanging.
|
|
365
|
+
*/
|
|
366
|
+
private async handleArtifactCreateRpc(req: ArtifactCreateRequest): Promise<void> {
|
|
367
|
+
const child = this.child;
|
|
368
|
+
if (!child) return;
|
|
369
|
+
let resp: ArtifactCreateResponse;
|
|
370
|
+
try {
|
|
371
|
+
const handler = this.options.handleArtifactCreate;
|
|
372
|
+
if (!handler) throw new Error('artifacts unavailable');
|
|
373
|
+
const result = await handler({ kind: req.kind, name: req.name });
|
|
374
|
+
resp = {
|
|
375
|
+
type: 'response',
|
|
376
|
+
request: 'artifact_create',
|
|
377
|
+
id: req.id,
|
|
378
|
+
ok: true,
|
|
379
|
+
slug: result.slug,
|
|
380
|
+
uri: result.uri,
|
|
381
|
+
primary_path: result.primary_path,
|
|
382
|
+
};
|
|
383
|
+
} catch (err) {
|
|
384
|
+
const e = err as Error;
|
|
385
|
+
resp = {
|
|
386
|
+
type: 'response',
|
|
387
|
+
request: 'artifact_create',
|
|
388
|
+
id: req.id,
|
|
389
|
+
ok: false,
|
|
390
|
+
error: e.message,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
await writeNdjson(child.stdin, resp);
|
|
395
|
+
} catch {
|
|
396
|
+
// child died between request and response — exit handler fails pending cells.
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Phase 4 Task 10: service an `artifact_update` RPC request. Mirror of
|
|
402
|
+
* handleArtifactCreateRpc for the update path.
|
|
403
|
+
*/
|
|
404
|
+
private async handleArtifactUpdateRpc(req: ArtifactUpdateRequest): Promise<void> {
|
|
405
|
+
const child = this.child;
|
|
406
|
+
if (!child) return;
|
|
407
|
+
let resp: ArtifactUpdateResponse;
|
|
408
|
+
try {
|
|
409
|
+
const handler = this.options.handleArtifactUpdate;
|
|
410
|
+
if (!handler) throw new Error('artifacts unavailable');
|
|
411
|
+
const result = await handler({ slug: req.slug, files: req.files });
|
|
412
|
+
resp = {
|
|
413
|
+
type: 'response',
|
|
414
|
+
request: 'artifact_update',
|
|
415
|
+
id: req.id,
|
|
416
|
+
ok: true,
|
|
417
|
+
files_touched: result.files_touched,
|
|
418
|
+
};
|
|
419
|
+
} catch (err) {
|
|
420
|
+
const e = err as Error;
|
|
421
|
+
resp = {
|
|
422
|
+
type: 'response',
|
|
423
|
+
request: 'artifact_update',
|
|
424
|
+
id: req.id,
|
|
425
|
+
ok: false,
|
|
426
|
+
error: e.message,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
try {
|
|
430
|
+
await writeNdjson(child.stdin, resp);
|
|
431
|
+
} catch {
|
|
432
|
+
// child died between request and response — exit handler fails pending cells.
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private rejectActive(id: number | null, message: string): void {
|
|
437
|
+
if (id === null) return;
|
|
438
|
+
const p = this.pending.get(id);
|
|
439
|
+
if (!p) return;
|
|
440
|
+
clearTimeout(p.totalTimer);
|
|
441
|
+
clearTimeout(p.inactivityTimer);
|
|
442
|
+
this.pending.delete(id);
|
|
443
|
+
if (this.activeId === id) this.activeId = null;
|
|
444
|
+
p.reject(new Error(message));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private failAllPending(err: Error): void {
|
|
448
|
+
for (const p of this.pending.values()) {
|
|
449
|
+
clearTimeout(p.totalTimer);
|
|
450
|
+
clearTimeout(p.inactivityTimer);
|
|
451
|
+
p.reject(err);
|
|
452
|
+
}
|
|
453
|
+
this.pending.clear();
|
|
454
|
+
this.activeId = null;
|
|
455
|
+
for (const resolve of this.pendingSnapshots.values()) {
|
|
456
|
+
resolve({ id: 0, type: 'snapshot_result', ok: false, error: { name: err.name, message: err.message } });
|
|
457
|
+
}
|
|
458
|
+
this.pendingSnapshots.clear();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private markDead(): void {
|
|
462
|
+
const child = this.child;
|
|
463
|
+
if (!child) return;
|
|
464
|
+
this.alive = false;
|
|
465
|
+
this.childReady = false;
|
|
466
|
+
child.kill('SIGTERM');
|
|
467
|
+
child.kill('SIGKILL');
|
|
468
|
+
// child stays referenced until its 'exit' fires; identity guards in readLoop/exit
|
|
469
|
+
// ignore the dead child once a restart reassigns this.child.
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
get hasActiveCell(): boolean {
|
|
473
|
+
return this.activeId !== null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
get recoveryNotes(): readonly RecoveryNote[] {
|
|
477
|
+
return this.recoveryNotes_;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async dispose(): Promise<void> {
|
|
481
|
+
if (this.disposed) return;
|
|
482
|
+
this.disposed = true;
|
|
483
|
+
this.alive = false;
|
|
484
|
+
this.childReady = false;
|
|
485
|
+
this.failAllPending(new Error('runtime disposed'));
|
|
486
|
+
const child = this.child;
|
|
487
|
+
this.child = null;
|
|
488
|
+
if (child) {
|
|
489
|
+
child.stdin.end();
|
|
490
|
+
child.kill('SIGTERM');
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { DefaultCollectorRegistry, uriMatchesPattern } from './collector-registry.js';
|
|
8
|
+
import { FileCollector } from './file-collector.js';
|
|
9
|
+
|
|
10
|
+
describe('uriMatchesPattern', () => {
|
|
11
|
+
it('matches trailing-wildcard prefixes', () => {
|
|
12
|
+
assert.equal(uriMatchesPattern('file:///x/a.csv', 'file://*'), true);
|
|
13
|
+
assert.equal(uriMatchesPattern('http://x/a', 'file://*'), false);
|
|
14
|
+
});
|
|
15
|
+
it('matches exact patterns without a wildcard', () => {
|
|
16
|
+
assert.equal(uriMatchesPattern('mcp://res', 'mcp://res'), true);
|
|
17
|
+
assert.equal(uriMatchesPattern('mcp://other', 'mcp://res'), false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('DefaultCollectorRegistry', () => {
|
|
22
|
+
it('registers, lists, and gets collectors by id', () => {
|
|
23
|
+
const reg = new DefaultCollectorRegistry();
|
|
24
|
+
const fc = new FileCollector({ workspace: '/tmp/x' });
|
|
25
|
+
reg.register(fc);
|
|
26
|
+
assert.equal(reg.get('file'), fc);
|
|
27
|
+
assert.equal(reg.get('nope'), null);
|
|
28
|
+
assert.deepEqual(reg.list().map((c) => c.id), ['file']);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('resolve()', () => {
|
|
32
|
+
let workspace: string;
|
|
33
|
+
let inputs: string;
|
|
34
|
+
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
workspace = await mkdtemp(join(tmpdir(), 'reg-ws-'));
|
|
37
|
+
inputs = join(workspace, '.otto', 'inputs');
|
|
38
|
+
await mkdir(inputs, { recursive: true });
|
|
39
|
+
});
|
|
40
|
+
afterEach(async () => {
|
|
41
|
+
await rm(workspace, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('resolves a known file:// uri to its collector + ref', async () => {
|
|
45
|
+
await writeFile(join(inputs, 'cmdb.csv'), 'a,b\n');
|
|
46
|
+
const reg = new DefaultCollectorRegistry();
|
|
47
|
+
reg.register(new FileCollector({ workspace }));
|
|
48
|
+
const uri = pathToFileURL(join(inputs, 'cmdb.csv')).href;
|
|
49
|
+
const hit = await reg.resolve(uri);
|
|
50
|
+
assert.ok(hit);
|
|
51
|
+
assert.equal(hit.collector.id, 'file');
|
|
52
|
+
assert.equal(hit.ref.uri, uri);
|
|
53
|
+
assert.equal(hit.ref.kind, 'csv');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns null for an unknown file under a matching collector', async () => {
|
|
57
|
+
const reg = new DefaultCollectorRegistry();
|
|
58
|
+
reg.register(new FileCollector({ workspace }));
|
|
59
|
+
const missing = pathToFileURL(join(inputs, 'absent.csv')).href;
|
|
60
|
+
assert.equal(await reg.resolve(missing), null);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns null when no collector matches the uri scheme', async () => {
|
|
64
|
+
const reg = new DefaultCollectorRegistry();
|
|
65
|
+
reg.register(new FileCollector({ workspace }));
|
|
66
|
+
assert.equal(await reg.resolve('http://example.com/x.csv'), null);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Collector, CollectorRegistry, DataSourceRef } from '@otto/coworker-types';
|
|
2
|
+
|
|
3
|
+
export function uriMatchesPattern(uri: string, pattern: string): boolean {
|
|
4
|
+
if (pattern.endsWith('*')) return uri.startsWith(pattern.slice(0, -1));
|
|
5
|
+
return uri === pattern;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class DefaultCollectorRegistry implements CollectorRegistry {
|
|
9
|
+
private readonly collectors = new Map<string, Collector>();
|
|
10
|
+
|
|
11
|
+
register(collector: Collector): void {
|
|
12
|
+
this.collectors.set(collector.id, collector);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
list(): Collector[] {
|
|
16
|
+
return [...this.collectors.values()];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(id: string): Collector | null {
|
|
20
|
+
return this.collectors.get(id) ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async resolve(uri: string): Promise<{ collector: Collector; ref: DataSourceRef } | null> {
|
|
24
|
+
for (const collector of this.collectors.values()) {
|
|
25
|
+
const patterns = collector.describe().supports_uris;
|
|
26
|
+
if (!patterns.some((p) => uriMatchesPattern(uri, p))) continue;
|
|
27
|
+
for await (const ref of collector.list()) {
|
|
28
|
+
if (ref.uri === uri) return { collector, ref };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { detectKind, FILE_COLLECTOR_KINDS } from './detect-kind.js';
|
|
4
|
+
|
|
5
|
+
describe('detectKind', () => {
|
|
6
|
+
it('maps each supported extension to its DataKind', () => {
|
|
7
|
+
assert.equal(detectKind('/x/cmdb.csv'), 'csv');
|
|
8
|
+
assert.equal(detectKind('/x/report.xlsx'), 'xlsx');
|
|
9
|
+
assert.equal(detectKind('/x/data.json'), 'json');
|
|
10
|
+
assert.equal(detectKind('/x/big.parquet'), 'parquet');
|
|
11
|
+
assert.equal(detectKind('/x/notes.txt'), 'txt');
|
|
12
|
+
assert.equal(detectKind('/x/README.md'), 'md');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('is case-insensitive on the extension', () => {
|
|
16
|
+
assert.equal(detectKind('/x/CMDB.CSV'), 'csv');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('handles file:// URIs and strips query/hash', () => {
|
|
20
|
+
assert.equal(detectKind('file:///workspace/.otto/inputs/a.csv'), 'csv');
|
|
21
|
+
assert.equal(detectKind('file:///x/a.json?v=2#top'), 'json');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('returns null for unsupported or extensionless paths', () => {
|
|
25
|
+
assert.equal(detectKind('/x/report.pdf'), null);
|
|
26
|
+
assert.equal(detectKind('/x/Makefile'), null);
|
|
27
|
+
assert.equal(detectKind('/x/archive.tar.gz'), null);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('exposes the supported kinds as a stable list', () => {
|
|
31
|
+
assert.deepEqual([...FILE_COLLECTOR_KINDS].sort(), ['csv', 'json', 'md', 'parquet', 'txt', 'xlsx']);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DataKind } from '@otto/coworker-types';
|
|
2
|
+
|
|
3
|
+
const EXT_TO_KIND: Record<string, DataKind> = {
|
|
4
|
+
'.csv': 'csv',
|
|
5
|
+
'.xlsx': 'xlsx',
|
|
6
|
+
'.json': 'json',
|
|
7
|
+
'.parquet': 'parquet',
|
|
8
|
+
'.txt': 'txt',
|
|
9
|
+
'.md': 'md',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// The six file kinds FileCollector enumerates. (DataKind also includes
|
|
13
|
+
// rest/mcp-resource/acp-stream, which belong to future non-file collectors.)
|
|
14
|
+
export const FILE_COLLECTOR_KINDS: readonly DataKind[] = ['csv', 'xlsx', 'json', 'parquet', 'txt', 'md'];
|
|
15
|
+
|
|
16
|
+
export function detectKind(pathOrUri: string): DataKind | null {
|
|
17
|
+
const clean = pathOrUri.split('?')[0].split('#')[0];
|
|
18
|
+
const dot = clean.lastIndexOf('.');
|
|
19
|
+
if (dot === -1) return null;
|
|
20
|
+
const ext = clean.slice(dot).toLowerCase();
|
|
21
|
+
return EXT_TO_KIND[ext] ?? null;
|
|
22
|
+
}
|