@cmetech/otto 1.1.1 → 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 +71 -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/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 +85 -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,45 @@
|
|
|
1
|
+
// packages/coworker-memory/src/errors.test.ts
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import {
|
|
5
|
+
MemoryNotInitialized, BackendUnavailable, DrawerKindRejected,
|
|
6
|
+
LayerAWriteBlocked, RecallQueryMalformed, MemoryEntryMalformed,
|
|
7
|
+
} from './errors.js';
|
|
8
|
+
|
|
9
|
+
describe('memory errors', () => {
|
|
10
|
+
it('MemoryNotInitialized carries reason', () => {
|
|
11
|
+
const e = new MemoryNotInitialized('workspace.json corrupted');
|
|
12
|
+
assert.equal(e.name, 'MemoryNotInitialized');
|
|
13
|
+
assert.equal(e.reason, 'workspace.json corrupted');
|
|
14
|
+
assert.ok(e.message.includes('workspace.json corrupted'));
|
|
15
|
+
});
|
|
16
|
+
it('BackendUnavailable carries reason', () => {
|
|
17
|
+
const e = new BackendUnavailable('SQLITE_BUSY after retries');
|
|
18
|
+
assert.equal(e.name, 'BackendUnavailable');
|
|
19
|
+
assert.equal(e.reason, 'SQLITE_BUSY after retries');
|
|
20
|
+
});
|
|
21
|
+
it('DrawerKindRejected carries kind', () => {
|
|
22
|
+
const e = new DrawerKindRejected('mystery');
|
|
23
|
+
assert.equal(e.name, 'DrawerKindRejected');
|
|
24
|
+
assert.equal(e.kind, 'mystery');
|
|
25
|
+
assert.ok(e.message.includes('mystery'));
|
|
26
|
+
});
|
|
27
|
+
it('LayerAWriteBlocked carries secret_kind', () => {
|
|
28
|
+
const e = new LayerAWriteBlocked('anthropic_api_key');
|
|
29
|
+
assert.equal(e.name, 'LayerAWriteBlocked');
|
|
30
|
+
assert.equal(e.secretKind, 'anthropic_api_key');
|
|
31
|
+
assert.ok(e.message.includes('anthropic_api_key'));
|
|
32
|
+
assert.ok(e.message.includes('/connect'));
|
|
33
|
+
});
|
|
34
|
+
it('RecallQueryMalformed carries reason', () => {
|
|
35
|
+
const e = new RecallQueryMalformed('empty query');
|
|
36
|
+
assert.equal(e.name, 'RecallQueryMalformed');
|
|
37
|
+
assert.equal(e.reason, 'empty query');
|
|
38
|
+
});
|
|
39
|
+
it('MemoryEntryMalformed carries path', () => {
|
|
40
|
+
const e = new MemoryEntryMalformed('/tmp/profile.md', 'bad frontmatter');
|
|
41
|
+
assert.equal(e.name, 'MemoryEntryMalformed');
|
|
42
|
+
assert.equal(e.path, '/tmp/profile.md');
|
|
43
|
+
assert.equal(e.reason, 'bad frontmatter');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// packages/coworker-memory/src/errors.ts
|
|
2
|
+
export class MemoryNotInitialized extends Error {
|
|
3
|
+
constructor(public readonly reason: string) {
|
|
4
|
+
super(`Memory not initialized: ${reason}. /memory status to inspect.`);
|
|
5
|
+
this.name = 'MemoryNotInitialized';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class BackendUnavailable extends Error {
|
|
10
|
+
constructor(public readonly reason: string) {
|
|
11
|
+
super(`Memory backend unavailable: ${reason}.`);
|
|
12
|
+
this.name = 'BackendUnavailable';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class DrawerKindRejected extends Error {
|
|
17
|
+
constructor(public readonly kind: string) {
|
|
18
|
+
super(`Drawer kind '${kind}' is not in v1 vocabulary. Allowed: turn, paste, file_load, ticket, email, rca, note.`);
|
|
19
|
+
this.name = 'DrawerKindRejected';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class LayerAWriteBlocked extends Error {
|
|
24
|
+
constructor(public readonly secretKind: string) {
|
|
25
|
+
super(`Refused to store: contains secret-shaped value (kind: ${secretKind}). Remove the secret and retry. Vault entries should land in /connect, not memorize.`);
|
|
26
|
+
this.name = 'LayerAWriteBlocked';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class RecallQueryMalformed extends Error {
|
|
31
|
+
constructor(public readonly reason: string) {
|
|
32
|
+
super(`Bad recall query: ${reason}.`);
|
|
33
|
+
this.name = 'RecallQueryMalformed';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class MemoryEntryMalformed extends Error {
|
|
38
|
+
constructor(public readonly path: string, public readonly reason: string) {
|
|
39
|
+
super(`Layer A file ${path} is malformed: ${reason}. Move it aside and re-create.`);
|
|
40
|
+
this.name = 'MemoryEntryMalformed';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import * as memory from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('@otto/coworker-memory barrel', () => {
|
|
6
|
+
it('exports the key surface', () => {
|
|
7
|
+
assert.equal(typeof memory.LocalSqliteBackend, 'function');
|
|
8
|
+
assert.equal(typeof memory.LayerAStore, 'function');
|
|
9
|
+
assert.equal(typeof memory.MemoryRecorder, 'function');
|
|
10
|
+
assert.equal(typeof memory.resolveWorkspaceId, 'function');
|
|
11
|
+
assert.equal(typeof memory.resolveScope, 'function');
|
|
12
|
+
assert.equal(typeof memory.detectPaste, 'function');
|
|
13
|
+
assert.equal(typeof memory.formatRecall, 'function');
|
|
14
|
+
assert.equal(typeof memory.buildLayerAContext, 'function');
|
|
15
|
+
assert.equal(typeof memory.applyPersonaSeed, 'function');
|
|
16
|
+
});
|
|
17
|
+
it('exports error classes', () => {
|
|
18
|
+
assert.equal(typeof memory.LayerAWriteBlocked, 'function');
|
|
19
|
+
assert.equal(typeof memory.RecallQueryMalformed, 'function');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './errors.js';
|
|
3
|
+
export * from './memory-backend.js';
|
|
4
|
+
export * from './workspace-id.js';
|
|
5
|
+
export * from './scope-resolver.js';
|
|
6
|
+
export * from './paste-detector.js';
|
|
7
|
+
export * from './layer-a-store.js';
|
|
8
|
+
export * from './local-sqlite-backend.js';
|
|
9
|
+
export * from './memory-recorder.js';
|
|
10
|
+
export * from './recall-formatter.js';
|
|
11
|
+
export * from './context-injection.js';
|
|
12
|
+
export * from './persona-seed.js';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// packages/coworker-memory/src/layer-a-store.test.ts
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdtempSync, readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { AuditLog, SecretScanner } from '@otto/coworker-utils';
|
|
8
|
+
import { LayerAStore } from './layer-a-store.js';
|
|
9
|
+
import { LayerAWriteBlocked, MemoryEntryMalformed } from './errors.js';
|
|
10
|
+
|
|
11
|
+
function ctx() {
|
|
12
|
+
const root = mkdtempSync(join(tmpdir(), 'la-'));
|
|
13
|
+
return {
|
|
14
|
+
root,
|
|
15
|
+
audit: new AuditLog({ path: join(root, 'audit.jsonl') }),
|
|
16
|
+
scanner: new SecretScanner(),
|
|
17
|
+
dir: join(root, 'memory'),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('LayerAStore', () => {
|
|
22
|
+
it('append lesson creates lessons.md with frontmatter and bullet', async () => {
|
|
23
|
+
const c = ctx();
|
|
24
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
25
|
+
await store.append({ kind: 'lesson', text: 'MTTR target is 30 minutes for P1', source: 'user', ts: '2026-06-02T10:00:00Z' });
|
|
26
|
+
const md = readFileSync(join(c.dir, 'lessons.md'), 'utf8');
|
|
27
|
+
assert.match(md, /^---\nschema_version: 1\n/);
|
|
28
|
+
assert.match(md, /- \(2026-06-02T10:00:00Z\) MTTR target is 30 minutes for P1/);
|
|
29
|
+
});
|
|
30
|
+
it('append profile uses timestamped section', async () => {
|
|
31
|
+
const c = ctx();
|
|
32
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
33
|
+
await store.append({ kind: 'profile', text: 'Prefers polars over pandas.', source: 'user', ts: '2026-06-02T10:00:00Z' });
|
|
34
|
+
const md = readFileSync(join(c.dir, 'profile.md'), 'utf8');
|
|
35
|
+
assert.match(md, /## 2026-06-02T10:00:00Z\nPrefers polars over pandas\./);
|
|
36
|
+
});
|
|
37
|
+
it('throws LayerAWriteBlocked when text contains a secret pattern', async () => {
|
|
38
|
+
const c = ctx();
|
|
39
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
40
|
+
await assert.rejects(
|
|
41
|
+
() => store.append({ kind: 'rule', text: 'use AKIAABCDEFGHIJKLMNOP', source: 'user', ts: '2026-06-02T10:00:00Z' }),
|
|
42
|
+
LayerAWriteBlocked,
|
|
43
|
+
);
|
|
44
|
+
assert.equal(existsSync(join(c.dir, 'rules.md')), false);
|
|
45
|
+
});
|
|
46
|
+
it('read returns parsed content with frontmatter stripped', async () => {
|
|
47
|
+
const c = ctx();
|
|
48
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
49
|
+
await store.append({ kind: 'rule', text: 'Always include MTTR in RCA.', source: 'user', ts: '2026-06-02T10:00:00Z' });
|
|
50
|
+
const body = await store.read('rule');
|
|
51
|
+
assert.match(body, /Always include MTTR in RCA\./);
|
|
52
|
+
assert.equal(body.startsWith('---'), false);
|
|
53
|
+
});
|
|
54
|
+
it('read returns empty string when file missing', async () => {
|
|
55
|
+
const c = ctx();
|
|
56
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
57
|
+
assert.equal(await store.read('lesson'), '');
|
|
58
|
+
});
|
|
59
|
+
it('emits write-layer-a audit on success', async () => {
|
|
60
|
+
const c = ctx();
|
|
61
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
62
|
+
await store.append({ kind: 'lesson', text: 'short lesson', source: 'user', ts: '2026-06-02T10:00:00Z' });
|
|
63
|
+
const rows: { action: string }[] = [];
|
|
64
|
+
for await (const r of c.audit.read({ producer: 'memory', action: 'write-layer-a' })) rows.push(r as never);
|
|
65
|
+
assert.equal(rows.length, 1);
|
|
66
|
+
});
|
|
67
|
+
it('re-appending to the same kind does not duplicate the title heading', async () => {
|
|
68
|
+
const c = ctx();
|
|
69
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
70
|
+
await store.append({ kind: 'lesson', text: 'first lesson', source: 'user', ts: '2026-06-02T10:00:00Z' });
|
|
71
|
+
await store.append({ kind: 'lesson', text: 'second lesson', source: 'user', ts: '2026-06-02T10:05:00Z' });
|
|
72
|
+
const raw = readFileSync(join(c.dir, 'lessons.md'), 'utf8');
|
|
73
|
+
const titleCount = (raw.match(/^# Lessons$/gm) ?? []).length;
|
|
74
|
+
assert.equal(titleCount, 1, 'duplicate title heading after re-append');
|
|
75
|
+
assert.match(raw, /first lesson/);
|
|
76
|
+
assert.match(raw, /second lesson/);
|
|
77
|
+
});
|
|
78
|
+
it('read throws MemoryEntryMalformed on unterminated frontmatter', async () => {
|
|
79
|
+
const c = ctx();
|
|
80
|
+
mkdirSync(c.dir, { recursive: true, mode: 0o700 });
|
|
81
|
+
writeFileSync(join(c.dir, 'rules.md'), '---\nbroken-yaml: [\n', { mode: 0o600 });
|
|
82
|
+
const store = new LayerAStore({ scopeDir: c.dir, scope: 'workspace', audit: c.audit, scanner: c.scanner });
|
|
83
|
+
await assert.rejects(() => store.read('rule'), MemoryEntryMalformed);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// packages/coworker-memory/src/layer-a-store.ts
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
5
|
+
import { AuditLog, SecretScanner } from '@otto/coworker-utils';
|
|
6
|
+
import type { LayerAEntry, LayerAKind } from './types.js';
|
|
7
|
+
import { LayerAWriteBlocked, MemoryEntryMalformed } from './errors.js';
|
|
8
|
+
|
|
9
|
+
const FILE_FOR: Record<LayerAKind, string> = {
|
|
10
|
+
profile: 'profile.md',
|
|
11
|
+
rule: 'rules.md',
|
|
12
|
+
lesson: 'lessons.md',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const TITLE_FOR: Record<LayerAKind, string> = {
|
|
16
|
+
profile: 'Profile',
|
|
17
|
+
rule: 'Rules',
|
|
18
|
+
lesson: 'Lessons',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface LayerAStoreOptions {
|
|
22
|
+
scopeDir: string; // absolute path to scope's memory dir
|
|
23
|
+
scope: 'global' | 'workspace';
|
|
24
|
+
audit: AuditLog;
|
|
25
|
+
scanner: SecretScanner;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class LayerAStore {
|
|
29
|
+
constructor(private readonly opts: LayerAStoreOptions) {}
|
|
30
|
+
|
|
31
|
+
async append(entry: LayerAEntry): Promise<void> {
|
|
32
|
+
const hits = this.opts.scanner.scan(entry.text);
|
|
33
|
+
if (hits.length > 0) {
|
|
34
|
+
this.opts.audit.append({
|
|
35
|
+
_schema: 1, ts: new Date().toISOString(), producer: 'memory', action: 'block', severity: 'warn',
|
|
36
|
+
detail: { scope: this.opts.scope, kind: entry.kind, reason: 'secret', secret_kind: hits[0]!.kind },
|
|
37
|
+
});
|
|
38
|
+
throw new LayerAWriteBlocked(hits[0]!.kind);
|
|
39
|
+
}
|
|
40
|
+
mkdirSync(this.opts.scopeDir, { recursive: true, mode: 0o700 });
|
|
41
|
+
const path = join(this.opts.scopeDir, FILE_FOR[entry.kind]);
|
|
42
|
+
const existing = existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
43
|
+
const { body } = existing ? this.split(existing, path) : { body: '' };
|
|
44
|
+
const addition = entry.kind === 'lesson'
|
|
45
|
+
? `- (${entry.ts}) ${entry.text}\n`
|
|
46
|
+
: `## ${entry.ts}\n${entry.text}\n\n`;
|
|
47
|
+
const newBody = (body && !body.endsWith('\n') ? body + '\n' : body) + addition;
|
|
48
|
+
const newFile = this.composeFile(entry.kind, newBody, entry.ts, entry.source);
|
|
49
|
+
const tmp = `${path}.tmp`;
|
|
50
|
+
writeFileSync(tmp, newFile, { mode: 0o600 });
|
|
51
|
+
chmodSync(tmp, 0o600);
|
|
52
|
+
renameSync(tmp, path);
|
|
53
|
+
this.opts.audit.append({
|
|
54
|
+
_schema: 1, ts: new Date().toISOString(), producer: 'memory', action: 'write-layer-a',
|
|
55
|
+
detail: { scope: this.opts.scope, kind: entry.kind, source: entry.source, byte_count: Buffer.byteLength(entry.text, 'utf8') },
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async read(kind: LayerAKind): Promise<string> {
|
|
60
|
+
const path = join(this.opts.scopeDir, FILE_FOR[kind]);
|
|
61
|
+
if (!existsSync(path)) return '';
|
|
62
|
+
const raw = readFileSync(path, 'utf8');
|
|
63
|
+
const { body } = this.split(raw, path);
|
|
64
|
+
return body.trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private split(raw: string, path: string): { frontmatter: Record<string, unknown>; body: string } {
|
|
68
|
+
if (!raw.startsWith('---')) {
|
|
69
|
+
return { frontmatter: {}, body: raw };
|
|
70
|
+
}
|
|
71
|
+
const end = raw.indexOf('\n---\n', 4);
|
|
72
|
+
if (end < 0) throw new MemoryEntryMalformed(path, 'unterminated frontmatter');
|
|
73
|
+
try {
|
|
74
|
+
const fm = parseYaml(raw.slice(4, end)) as Record<string, unknown>;
|
|
75
|
+
const body = raw.slice(end + 5);
|
|
76
|
+
return { frontmatter: fm ?? {}, body };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
throw new MemoryEntryMalformed(path, `frontmatter parse: ${(err as Error).message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private composeFile(kind: LayerAKind, body: string, ts: string, source: LayerAEntry['source']): string {
|
|
83
|
+
const fm = { schema_version: 1, last_modified_at: ts, source };
|
|
84
|
+
const fmStr = stringifyYaml(fm).trimEnd();
|
|
85
|
+
const header = `---\n${fmStr}\n---\n\n# ${TITLE_FOR[kind]}\n\n`;
|
|
86
|
+
return header + body.replace(new RegExp(`^\\n?# ${TITLE_FOR[kind]}\\n\\n?`), '');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// packages/coworker-memory/src/local-sqlite-backend.test.ts
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdtempSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { LocalSqliteBackend } from './local-sqlite-backend.js';
|
|
8
|
+
|
|
9
|
+
function tmp(): string { return mkdtempSync(join(tmpdir(), 'lb-')); }
|
|
10
|
+
|
|
11
|
+
describe('LocalSqliteBackend', () => {
|
|
12
|
+
it('bootstraps schema and is ready', async () => {
|
|
13
|
+
const dir = tmp();
|
|
14
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
15
|
+
await be.open();
|
|
16
|
+
const st = await be.status();
|
|
17
|
+
assert.equal(st.ready, true);
|
|
18
|
+
assert.equal(st.drawer_count, 0);
|
|
19
|
+
assert.equal(st.schema_version, 2);
|
|
20
|
+
await be.close();
|
|
21
|
+
});
|
|
22
|
+
it('retain + recall round-trip; result includes snippet with <mark>', async () => {
|
|
23
|
+
const dir = tmp();
|
|
24
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
25
|
+
await be.open();
|
|
26
|
+
await be.retain({ wing: 'global', room: 'inbox', kind: 'paste',
|
|
27
|
+
content: 'customer said the load balancer started returning 503s around 14:00 UTC',
|
|
28
|
+
metadata: {}, redacted: false });
|
|
29
|
+
const results = await be.recall({ query: 'load balancer' });
|
|
30
|
+
assert.equal(results.length, 1);
|
|
31
|
+
assert.equal(results[0]!.drawer.kind, 'paste');
|
|
32
|
+
assert.match(results[0]!.snippet, /<mark>load<\/mark>/);
|
|
33
|
+
assert.ok(results[0]!.score > 0);
|
|
34
|
+
await be.close();
|
|
35
|
+
});
|
|
36
|
+
it('filters by wing, room, kind, days_back', async () => {
|
|
37
|
+
const dir = tmp();
|
|
38
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
39
|
+
await be.open();
|
|
40
|
+
await be.retain({ wing: 'a', room: 'r1', kind: 'turn', content: 'red apples', metadata: {}, redacted: false });
|
|
41
|
+
await be.retain({ wing: 'b', room: 'r2', kind: 'paste', content: 'red apples', metadata: {}, redacted: false });
|
|
42
|
+
const filteredWing = await be.recall({ query: 'apples', wing: 'a' });
|
|
43
|
+
assert.equal(filteredWing.length, 1);
|
|
44
|
+
assert.equal(filteredWing[0]!.drawer.wing, 'a');
|
|
45
|
+
const filteredKind = await be.recall({ query: 'apples', kind: 'paste' });
|
|
46
|
+
assert.equal(filteredKind.length, 1);
|
|
47
|
+
assert.equal(filteredKind[0]!.drawer.kind, 'paste');
|
|
48
|
+
await be.close();
|
|
49
|
+
});
|
|
50
|
+
it('escapes FTS5 special characters in query', async () => {
|
|
51
|
+
const dir = tmp();
|
|
52
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
53
|
+
await be.open();
|
|
54
|
+
await be.retain({ wing: 'g', room: 'r', kind: 'note', content: 'CIDR is 10.0.0.0/24', metadata: {}, redacted: false });
|
|
55
|
+
const r = await be.recall({ query: '10.0.0.0/24 "AND" *' }); // would otherwise blow up
|
|
56
|
+
assert.equal(r.length, 1);
|
|
57
|
+
await be.close();
|
|
58
|
+
});
|
|
59
|
+
it('listWings + listRooms reflect inserted data', async () => {
|
|
60
|
+
const dir = tmp();
|
|
61
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
62
|
+
await be.open();
|
|
63
|
+
await be.retain({ wing: 'w1', room: 'r1', kind: 'turn', content: 'a', metadata: {}, redacted: false });
|
|
64
|
+
await be.retain({ wing: 'w2', room: 'r2', kind: 'turn', content: 'b', metadata: {}, redacted: false });
|
|
65
|
+
assert.deepEqual((await be.listWings()).sort(), ['w1', 'w2']);
|
|
66
|
+
assert.deepEqual(await be.listRooms('w1'), ['r1']);
|
|
67
|
+
await be.close();
|
|
68
|
+
});
|
|
69
|
+
it('clear({wing}) deletes only that wing\'s drawers', async () => {
|
|
70
|
+
const dir = tmp();
|
|
71
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
72
|
+
await be.open();
|
|
73
|
+
await be.retain({ wing: 'w1', room: 'r1', kind: 'turn', content: 'a', metadata: {}, redacted: false });
|
|
74
|
+
await be.retain({ wing: 'w2', room: 'r2', kind: 'turn', content: 'b', metadata: {}, redacted: false });
|
|
75
|
+
const out = await be.clear({ wing: 'w1', confirm: true });
|
|
76
|
+
assert.equal(out.deleted, 1);
|
|
77
|
+
assert.deepEqual(await be.listWings(), ['w2']);
|
|
78
|
+
await be.close();
|
|
79
|
+
});
|
|
80
|
+
it('retain preserves redacted flag', async () => {
|
|
81
|
+
const dir = tmp();
|
|
82
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
83
|
+
await be.open();
|
|
84
|
+
const d = await be.retain({ wing: 'g', room: 'r', kind: 'paste', content: 'x [REDACTED:aws_access_key_id] y', metadata: {}, redacted: true });
|
|
85
|
+
assert.equal(d.redacted, true);
|
|
86
|
+
const r = await be.recall({ query: 'REDACTED' });
|
|
87
|
+
assert.equal(r[0]!.drawer.redacted, true);
|
|
88
|
+
await be.close();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Local backend migrations (Phase 4 Task 8)', () => {
|
|
93
|
+
it('migration 002 lets us insert kind:artifact', async () => {
|
|
94
|
+
const dir = mkdtempSync(join(tmpdir(), 'be-mig-'));
|
|
95
|
+
const be = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
96
|
+
await be.open();
|
|
97
|
+
const st = await be.status();
|
|
98
|
+
assert.equal(st.schema_version, 2);
|
|
99
|
+
const drawer = await be.retain({
|
|
100
|
+
wing: 'g', room: 'r', kind: 'artifact',
|
|
101
|
+
content: JSON.stringify({ slug: 'rca-1', kind: 'report', uri: 'artifact://rca-1' }),
|
|
102
|
+
metadata: { scratchpad: 'p1' }, redacted: false,
|
|
103
|
+
});
|
|
104
|
+
assert.equal(drawer.kind, 'artifact');
|
|
105
|
+
const r = await be.recall({ query: 'rca', kind: 'artifact' });
|
|
106
|
+
assert.equal(r.length, 1);
|
|
107
|
+
assert.equal(r[0]!.drawer.kind, 'artifact');
|
|
108
|
+
await be.close();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// packages/coworker-memory/src/local-sqlite-backend.ts
|
|
2
|
+
import Database, { type Database as DB } from 'better-sqlite3';
|
|
3
|
+
import { readFileSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { v4 as uuid } from 'uuid';
|
|
7
|
+
import type { MemoryBackend } from './memory-backend.js';
|
|
8
|
+
import type { Drawer, RecallQuery, RecallResult, BackendStatus, Wing, Room } from './types.js';
|
|
9
|
+
import { RecallQueryMalformed, BackendUnavailable } from './errors.js';
|
|
10
|
+
|
|
11
|
+
export interface LocalSqliteBackendOptions {
|
|
12
|
+
dbPath: string;
|
|
13
|
+
now?: () => string;
|
|
14
|
+
busyTimeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function migrationDir(): string {
|
|
18
|
+
return join(dirname(fileURLToPath(import.meta.url)), 'migrations');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function escapeFts5(q: string): string {
|
|
22
|
+
// Wrap each word containing special chars in double quotes; doubled internal quotes.
|
|
23
|
+
// Empty query → throw upstream.
|
|
24
|
+
const tokens = q.match(/\S+/g) ?? [];
|
|
25
|
+
return tokens.map(t => `"${t.replace(/"/g, '""')}"`).join(' OR ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class LocalSqliteBackend implements MemoryBackend {
|
|
29
|
+
private db: DB | null = null;
|
|
30
|
+
private readonly path: string;
|
|
31
|
+
private readonly now: () => string;
|
|
32
|
+
private readonly busyTimeoutMs: number;
|
|
33
|
+
|
|
34
|
+
constructor(opts: LocalSqliteBackendOptions) {
|
|
35
|
+
this.path = opts.dbPath;
|
|
36
|
+
this.now = opts.now ?? (() => new Date().toISOString());
|
|
37
|
+
this.busyTimeoutMs = opts.busyTimeoutMs ?? 2000;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async open(): Promise<void> {
|
|
41
|
+
if (this.db) return;
|
|
42
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
43
|
+
try {
|
|
44
|
+
this.db = new Database(this.path);
|
|
45
|
+
this.db.pragma(`busy_timeout = ${this.busyTimeoutMs}`);
|
|
46
|
+
const dir = migrationDir();
|
|
47
|
+
const migrations = [
|
|
48
|
+
{ version: 1, file: '001-init.sql' },
|
|
49
|
+
{ version: 2, file: '002-artifact-kind.sql' },
|
|
50
|
+
];
|
|
51
|
+
const userVersion = this.db.pragma('user_version', { simple: true }) as number;
|
|
52
|
+
for (const m of migrations) {
|
|
53
|
+
if (userVersion < m.version) {
|
|
54
|
+
const sql = readFileSync(join(dir, m.file), 'utf8');
|
|
55
|
+
this.db.exec(sql);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new BackendUnavailable(`open failed: ${(err as Error).message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async close(): Promise<void> {
|
|
64
|
+
this.db?.close();
|
|
65
|
+
this.db = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private requireDb(): DB {
|
|
69
|
+
if (!this.db) throw new BackendUnavailable('not opened');
|
|
70
|
+
return this.db;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async retain(input: Omit<Drawer, 'id' | 'created_at'>): Promise<Drawer> {
|
|
74
|
+
const db = this.requireDb();
|
|
75
|
+
const id = uuid();
|
|
76
|
+
const created_at = this.now();
|
|
77
|
+
const stmt = db.prepare(`
|
|
78
|
+
INSERT INTO drawers (id, wing, room, kind, content, metadata_json, parent_id, redacted, created_at)
|
|
79
|
+
VALUES (@id, @wing, @room, @kind, @content, @metadata_json, @parent_id, @redacted, @created_at)
|
|
80
|
+
`);
|
|
81
|
+
stmt.run({
|
|
82
|
+
id, wing: input.wing, room: input.room, kind: input.kind, content: input.content,
|
|
83
|
+
metadata_json: JSON.stringify(input.metadata ?? {}),
|
|
84
|
+
parent_id: input.parent_id ?? null,
|
|
85
|
+
redacted: input.redacted ? 1 : 0,
|
|
86
|
+
created_at,
|
|
87
|
+
});
|
|
88
|
+
return { id, created_at, ...input };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async recall(query: RecallQuery): Promise<RecallResult[]> {
|
|
92
|
+
if (!query.query || !query.query.trim()) throw new RecallQueryMalformed('empty query');
|
|
93
|
+
const db = this.requireDb();
|
|
94
|
+
const matchExpr = escapeFts5(query.query.trim());
|
|
95
|
+
const conditions: string[] = ['drawers_fts MATCH ?'];
|
|
96
|
+
const params: unknown[] = [matchExpr];
|
|
97
|
+
|
|
98
|
+
if (query.wing) {
|
|
99
|
+
if (Array.isArray(query.wing)) {
|
|
100
|
+
conditions.push(`d.wing IN (${query.wing.map(() => '?').join(',')})`);
|
|
101
|
+
params.push(...query.wing);
|
|
102
|
+
} else {
|
|
103
|
+
conditions.push('d.wing = ?');
|
|
104
|
+
params.push(query.wing);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (query.room) { conditions.push('d.room = ?'); params.push(query.room); }
|
|
108
|
+
if (query.kind) {
|
|
109
|
+
const kinds = Array.isArray(query.kind) ? query.kind : [query.kind];
|
|
110
|
+
conditions.push(`d.kind IN (${kinds.map(() => '?').join(',')})`);
|
|
111
|
+
params.push(...kinds);
|
|
112
|
+
}
|
|
113
|
+
if (query.days_back && query.days_back > 0) {
|
|
114
|
+
const cutoff = new Date(Date.now() - query.days_back * 86400_000).toISOString();
|
|
115
|
+
conditions.push('d.created_at >= ?');
|
|
116
|
+
params.push(cutoff);
|
|
117
|
+
}
|
|
118
|
+
const limit = Math.min(Math.max(query.max_results ?? 8, 1), 64);
|
|
119
|
+
params.push(limit);
|
|
120
|
+
|
|
121
|
+
const sql = `
|
|
122
|
+
SELECT d.id, d.wing, d.room, d.kind, d.content, d.metadata_json, d.parent_id, d.redacted, d.created_at,
|
|
123
|
+
bm25(drawers_fts) AS rank,
|
|
124
|
+
snippet(drawers_fts, 0, '<mark>', '</mark>', '...', 16) AS snippet
|
|
125
|
+
FROM drawers_fts
|
|
126
|
+
JOIN drawers d ON d.rowid = drawers_fts.rowid
|
|
127
|
+
WHERE ${conditions.join(' AND ')}
|
|
128
|
+
ORDER BY rank
|
|
129
|
+
LIMIT ?
|
|
130
|
+
`;
|
|
131
|
+
const rows = db.prepare(sql).all(...params) as Array<{
|
|
132
|
+
id: string; wing: string; room: string; kind: Drawer['kind']; content: string;
|
|
133
|
+
metadata_json: string; parent_id: string | null; redacted: number; created_at: string;
|
|
134
|
+
rank: number; snippet: string;
|
|
135
|
+
}>;
|
|
136
|
+
return rows.map(r => ({
|
|
137
|
+
drawer: {
|
|
138
|
+
id: r.id, wing: r.wing, room: r.room, kind: r.kind, content: r.content,
|
|
139
|
+
metadata: JSON.parse(r.metadata_json) as Record<string, unknown>,
|
|
140
|
+
parent_id: r.parent_id ?? undefined, redacted: r.redacted === 1,
|
|
141
|
+
created_at: r.created_at,
|
|
142
|
+
},
|
|
143
|
+
score: -r.rank, // BM25 lower=better in sqlite; invert for descending
|
|
144
|
+
snippet: r.snippet,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async listRooms(wing?: Wing): Promise<Room[]> {
|
|
149
|
+
const db = this.requireDb();
|
|
150
|
+
const sql = wing
|
|
151
|
+
? `SELECT DISTINCT room FROM drawers WHERE wing = ? ORDER BY room`
|
|
152
|
+
: `SELECT DISTINCT room FROM drawers ORDER BY room`;
|
|
153
|
+
const rows = wing ? db.prepare(sql).all(wing) : db.prepare(sql).all();
|
|
154
|
+
return (rows as Array<{ room: string }>).map(r => r.room);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async listWings(): Promise<Wing[]> {
|
|
158
|
+
const db = this.requireDb();
|
|
159
|
+
const rows = db.prepare(`SELECT DISTINCT wing FROM drawers ORDER BY wing`).all() as Array<{ wing: string }>;
|
|
160
|
+
return rows.map(r => r.wing);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async status(): Promise<BackendStatus> {
|
|
164
|
+
const db = this.requireDb();
|
|
165
|
+
const { c } = db.prepare(`SELECT COUNT(*) AS c FROM drawers`).get() as { c: number };
|
|
166
|
+
const { user_version } = db.prepare(`PRAGMA user_version`).get() as { user_version: number };
|
|
167
|
+
return {
|
|
168
|
+
ready: true,
|
|
169
|
+
workspace_wing: '', // caller (memory-singleton) overlays this from scope info
|
|
170
|
+
drawer_count: c,
|
|
171
|
+
layer_b_db_path: this.path,
|
|
172
|
+
schema_version: user_version,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async clear(args: { wing?: Wing; confirm: true }): Promise<{ deleted: number }> {
|
|
177
|
+
if (args.confirm !== true) throw new RecallQueryMalformed('confirm must be true');
|
|
178
|
+
const db = this.requireDb();
|
|
179
|
+
const stmt = args.wing
|
|
180
|
+
? db.prepare(`DELETE FROM drawers WHERE wing = ?`)
|
|
181
|
+
: db.prepare(`DELETE FROM drawers`);
|
|
182
|
+
const result = args.wing ? stmt.run(args.wing) : stmt.run();
|
|
183
|
+
return { deleted: result.changes };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Drawer, RecallQuery, RecallResult, BackendStatus, Wing, Room } from './types.js';
|
|
2
|
+
|
|
3
|
+
export interface MemoryBackend {
|
|
4
|
+
recall(query: RecallQuery): Promise<RecallResult[]>;
|
|
5
|
+
retain(input: Omit<Drawer, 'id' | 'created_at'>): Promise<Drawer>;
|
|
6
|
+
listRooms(wing?: Wing): Promise<Room[]>;
|
|
7
|
+
listWings(): Promise<Wing[]>;
|
|
8
|
+
status(): Promise<BackendStatus>;
|
|
9
|
+
clear(args: { wing?: Wing; confirm: true }): Promise<{ deleted: number }>;
|
|
10
|
+
}
|