@cmetech/otto 1.1.1 → 1.2.5
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-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 +83 -0
- package/dist/resources/extensions/otto/commands/release-notes/command.js +15 -4
- package/dist/resources/extensions/otto/index.js +31 -6
- package/dist/resources/extensions/shared/coworker-paths.js +8 -0
- package/dist/resources/extensions/slash-commands/{audit.js → audit-codebase.js} +4 -4
- package/dist/resources/extensions/slash-commands/extension-manifest.json +1 -1
- package/dist/resources/extensions/slash-commands/index.js +2 -2
- 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/health-widget-core.js +1 -1
- package/dist/resources/extensions/workflow/persona-status.js +87 -0
- package/package.json +26 -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-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 +97 -0
- package/src/resources/extensions/otto/commands/release-notes/command.ts +16 -3
- package/src/resources/extensions/otto/index.ts +29 -6
- package/src/resources/extensions/shared/coworker-paths.test.ts +40 -0
- package/src/resources/extensions/shared/coworker-paths.ts +10 -0
- package/src/resources/extensions/slash-commands/{audit.ts → audit-codebase.ts} +4 -4
- package/src/resources/extensions/slash-commands/extension-manifest.json +1 -1
- package/src/resources/extensions/slash-commands/index.ts +2 -2
- 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/health-widget-core.ts +1 -1
- 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,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
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// packages/coworker-memory/src/memory-integration.test.ts
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { createMemoryBundle } from '../../../src/resources/extensions/coworker-memory/memory-singleton.js';
|
|
8
|
+
import { runMemorize } from '../../../src/resources/extensions/coworker-memory/memorize-tool.js';
|
|
9
|
+
import { runRecall } from '../../../src/resources/extensions/coworker-memory/recall-tool.js';
|
|
10
|
+
import { onSessionStart, onSessionShutdown } from '../../../src/resources/extensions/coworker-memory/session-hooks.js';
|
|
11
|
+
|
|
12
|
+
describe('Memory integration — Day-2 verbatim recall', () => {
|
|
13
|
+
it('paste Monday → recall Tuesday in a fresh session', async () => {
|
|
14
|
+
const homeMon = mkdtempSync(join(tmpdir(), 'mem-home-mon-'));
|
|
15
|
+
const ws = mkdtempSync(join(tmpdir(), 'mem-ws-mon-'));
|
|
16
|
+
mkdirSync(ws, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// ===== MONDAY =====
|
|
19
|
+
const monBundle = await createMemoryBundle({
|
|
20
|
+
globalDir: homeMon, workspaceDir: ws,
|
|
21
|
+
scopeMode: 'per-project-tagged',
|
|
22
|
+
currentScratchpadName: () => 'p1-1234',
|
|
23
|
+
});
|
|
24
|
+
await monBundle.recorder.recordPaste({
|
|
25
|
+
sessionId: 'sess-mon',
|
|
26
|
+
content: 'customer says the load balancer started returning 503s around 14:00 UTC; the on-call escalated to the network team at 14:18',
|
|
27
|
+
turnId: 't1',
|
|
28
|
+
});
|
|
29
|
+
await onSessionShutdown(monBundle);
|
|
30
|
+
|
|
31
|
+
// Simulate a different Otto process by re-creating the bundle with the same workspace.
|
|
32
|
+
// ===== TUESDAY =====
|
|
33
|
+
const tueBundle = await createMemoryBundle({
|
|
34
|
+
globalDir: homeMon /* same as Monday */, workspaceDir: ws,
|
|
35
|
+
scopeMode: 'per-project-tagged',
|
|
36
|
+
currentScratchpadName: () => null,
|
|
37
|
+
});
|
|
38
|
+
const start = await onSessionStart(tueBundle, { tokenLimit: 3000 });
|
|
39
|
+
// Layer A was empty Monday, so the inject is empty on Tuesday.
|
|
40
|
+
assert.equal(start.contextBlock, '');
|
|
41
|
+
const r = await runRecall(tueBundle, { query: 'load balancer' });
|
|
42
|
+
assert.equal(r.results.length, 1);
|
|
43
|
+
assert.match(r.results[0]!.drawer.content, /load balancer started returning 503s around 14:00 UTC/);
|
|
44
|
+
assert.equal(r.results[0]!.drawer.room, 'p1-1234');
|
|
45
|
+
assert.match(r.markdown, /drawer:\/\//);
|
|
46
|
+
await onSessionShutdown(tueBundle);
|
|
47
|
+
});
|
|
48
|
+
it('memorize lessons → next session_start injects them', async () => {
|
|
49
|
+
const home = mkdtempSync(join(tmpdir(), 'mem-home-A-'));
|
|
50
|
+
const ws = mkdtempSync(join(tmpdir(), 'mem-ws-A-'));
|
|
51
|
+
mkdirSync(ws, { recursive: true });
|
|
52
|
+
const b1 = await createMemoryBundle({
|
|
53
|
+
globalDir: home, workspaceDir: ws,
|
|
54
|
+
scopeMode: 'per-project-tagged', currentScratchpadName: () => null,
|
|
55
|
+
});
|
|
56
|
+
await runMemorize(b1, { text: 'MTTR target is 30 minutes for P1', kind: 'lesson' });
|
|
57
|
+
await runMemorize(b1, { text: 'Always escalate to mgr within 5 min on customer-facing P1', kind: 'rule' });
|
|
58
|
+
await onSessionShutdown(b1);
|
|
59
|
+
|
|
60
|
+
const b2 = await createMemoryBundle({
|
|
61
|
+
globalDir: home, workspaceDir: ws,
|
|
62
|
+
scopeMode: 'per-project-tagged', currentScratchpadName: () => null,
|
|
63
|
+
});
|
|
64
|
+
const start = await onSessionStart(b2, { tokenLimit: 3000 });
|
|
65
|
+
assert.match(start.contextBlock, /MTTR target is 30 minutes/);
|
|
66
|
+
assert.match(start.contextBlock, /Always escalate to mgr/);
|
|
67
|
+
await onSessionShutdown(b2);
|
|
68
|
+
});
|
|
69
|
+
it('secret in paste is redacted; recall surrounding context still works', async () => {
|
|
70
|
+
const home = mkdtempSync(join(tmpdir(), 'mem-home-B-'));
|
|
71
|
+
const ws = mkdtempSync(join(tmpdir(), 'mem-ws-B-'));
|
|
72
|
+
mkdirSync(ws, { recursive: true });
|
|
73
|
+
const b = await createMemoryBundle({
|
|
74
|
+
globalDir: home, workspaceDir: ws,
|
|
75
|
+
scopeMode: 'per-project-tagged', currentScratchpadName: () => null,
|
|
76
|
+
});
|
|
77
|
+
await b.recorder.recordPaste({
|
|
78
|
+
sessionId: 's',
|
|
79
|
+
content: 'login token AKIAABCDEFGHIJKLMNOP, used for Datadog API',
|
|
80
|
+
turnId: 't',
|
|
81
|
+
});
|
|
82
|
+
const r = await runRecall(b, { query: 'Datadog' });
|
|
83
|
+
assert.equal(r.results.length, 1);
|
|
84
|
+
assert.match(r.results[0]!.drawer.content, /\[REDACTED:aws_access_key_id\]/);
|
|
85
|
+
assert.equal(r.results[0]!.drawer.content.includes('AKIAABCDEFGHIJKLMNOP'), false);
|
|
86
|
+
assert.equal(r.results[0]!.drawer.redacted, true);
|
|
87
|
+
await onSessionShutdown(b);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// packages/coworker-memory/src/memory-recorder.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 { AuditLog, SecretScanner } from '@otto/coworker-utils';
|
|
8
|
+
import { LocalSqliteBackend } from './local-sqlite-backend.js';
|
|
9
|
+
import { MemoryRecorder } from './memory-recorder.js';
|
|
10
|
+
|
|
11
|
+
async function ctx() {
|
|
12
|
+
const dir = mkdtempSync(join(tmpdir(), 'mr-'));
|
|
13
|
+
const audit = new AuditLog({ path: join(dir, 'audit.jsonl') });
|
|
14
|
+
const scanner = new SecretScanner();
|
|
15
|
+
const backend = new LocalSqliteBackend({ dbPath: join(dir, 'layer-b.db') });
|
|
16
|
+
await backend.open();
|
|
17
|
+
return { dir, audit, scanner, backend };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('MemoryRecorder', () => {
|
|
21
|
+
it('recordTurn writes kind:turn drawer for short text', async () => {
|
|
22
|
+
const c = await ctx();
|
|
23
|
+
const recorder = new MemoryRecorder({
|
|
24
|
+
backend: c.backend, scanner: c.scanner, audit: c.audit,
|
|
25
|
+
writeWing: 'global', currentScratchpadName: () => null,
|
|
26
|
+
});
|
|
27
|
+
await recorder.recordTurn({ sessionId: 's', userText: 'hi', turnId: 't1' });
|
|
28
|
+
const wings = await c.backend.listWings();
|
|
29
|
+
assert.deepEqual(wings, ['global']);
|
|
30
|
+
const rooms = await c.backend.listRooms('global');
|
|
31
|
+
assert.deepEqual(rooms, ['inbox']);
|
|
32
|
+
await c.backend.close();
|
|
33
|
+
});
|
|
34
|
+
it('recordTurn writes kind:paste drawer for long text', async () => {
|
|
35
|
+
const c = await ctx();
|
|
36
|
+
const recorder = new MemoryRecorder({
|
|
37
|
+
backend: c.backend, scanner: c.scanner, audit: c.audit,
|
|
38
|
+
writeWing: 'global', currentScratchpadName: () => 'p1',
|
|
39
|
+
});
|
|
40
|
+
// Use whitespace-separated tokens so FTS5 can index/match (single huge token wouldn't match 'x').
|
|
41
|
+
const longText = 'lorem ipsum '.repeat(60); // > 500 chars, tokenizable
|
|
42
|
+
await recorder.recordTurn({ sessionId: 's', userText: longText, turnId: 't1' });
|
|
43
|
+
const r = await c.backend.recall({ query: 'lorem' });
|
|
44
|
+
assert.equal(r[0]!.drawer.kind, 'paste');
|
|
45
|
+
assert.equal(r[0]!.drawer.room, 'p1');
|
|
46
|
+
await c.backend.close();
|
|
47
|
+
});
|
|
48
|
+
it('redacts secret content and sets redacted=true', async () => {
|
|
49
|
+
const c = await ctx();
|
|
50
|
+
const recorder = new MemoryRecorder({
|
|
51
|
+
backend: c.backend, scanner: c.scanner, audit: c.audit,
|
|
52
|
+
writeWing: 'global', currentScratchpadName: () => null,
|
|
53
|
+
});
|
|
54
|
+
await recorder.recordPaste({ sessionId: 's', content: 'token AKIAABCDEFGHIJKLMNOP', turnId: 't1' });
|
|
55
|
+
const r = await c.backend.recall({ query: 'token' });
|
|
56
|
+
assert.equal(r[0]!.drawer.redacted, true);
|
|
57
|
+
assert.match(r[0]!.drawer.content, /\[REDACTED:aws_access_key_id\]/);
|
|
58
|
+
const rows: { action: string }[] = [];
|
|
59
|
+
for await (const x of c.audit.read({ producer: 'memory', action: 'redact' })) rows.push(x as never);
|
|
60
|
+
assert.equal(rows.length, 1);
|
|
61
|
+
await c.backend.close();
|
|
62
|
+
});
|
|
63
|
+
it('recordFileLoad stores structured JSON in content', async () => {
|
|
64
|
+
const c = await ctx();
|
|
65
|
+
const recorder = new MemoryRecorder({
|
|
66
|
+
backend: c.backend, scanner: c.scanner, audit: c.audit,
|
|
67
|
+
writeWing: 'global', currentScratchpadName: () => 'p1',
|
|
68
|
+
});
|
|
69
|
+
await recorder.recordFileLoad({
|
|
70
|
+
scratchpadName: 'p1', collector: 'file', uri: 'file:///x.csv',
|
|
71
|
+
bytes: 1000, rows_loaded: 50, schema: { cols: ['a','b'] }, turnId: 't1',
|
|
72
|
+
});
|
|
73
|
+
const r = await c.backend.recall({ query: 'file' });
|
|
74
|
+
assert.equal(r[0]!.drawer.kind, 'file_load');
|
|
75
|
+
const parsed = JSON.parse(r[0]!.drawer.content);
|
|
76
|
+
assert.equal(parsed.uri, 'file:///x.csv');
|
|
77
|
+
assert.equal(parsed.rows_loaded, 50);
|
|
78
|
+
await c.backend.close();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('MemoryRecorder.recordArtifact (Phase 4 Task 8)', () => {
|
|
83
|
+
it('writes a kind:artifact drawer with slug/kind/uri JSON content', async () => {
|
|
84
|
+
const c = await ctx();
|
|
85
|
+
const recorder = new MemoryRecorder({
|
|
86
|
+
backend: c.backend, scanner: c.scanner, audit: c.audit,
|
|
87
|
+
writeWing: 'global', currentScratchpadName: () => null,
|
|
88
|
+
});
|
|
89
|
+
await recorder.recordArtifact({
|
|
90
|
+
scratchpadName: 'p1', slug: 'rca-1', kind: 'report',
|
|
91
|
+
uri: 'artifact://rca-1', turnId: 't1',
|
|
92
|
+
});
|
|
93
|
+
const r = await c.backend.recall({ query: 'rca', kind: 'artifact' });
|
|
94
|
+
assert.equal(r.length, 1);
|
|
95
|
+
const parsed = JSON.parse(r[0]!.drawer.content);
|
|
96
|
+
assert.equal(parsed.slug, 'rca-1');
|
|
97
|
+
assert.equal(parsed.uri, 'artifact://rca-1');
|
|
98
|
+
assert.equal(r[0]!.drawer.room, 'p1');
|
|
99
|
+
await c.backend.close();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// packages/coworker-memory/src/memory-recorder.ts
|
|
2
|
+
import { AuditLog, SecretScanner } from '@otto/coworker-utils';
|
|
3
|
+
import type { MemoryBackend } from './memory-backend.js';
|
|
4
|
+
import type { Wing, Room, Drawer } from './types.js';
|
|
5
|
+
import { detectPaste } from './paste-detector.js';
|
|
6
|
+
|
|
7
|
+
export interface CurrentScratchpadProvider {
|
|
8
|
+
(sessionId: string): string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MemoryRecorderOptions {
|
|
12
|
+
backend: MemoryBackend;
|
|
13
|
+
scanner: SecretScanner;
|
|
14
|
+
audit: AuditLog;
|
|
15
|
+
writeWing: Wing;
|
|
16
|
+
currentScratchpadName: CurrentScratchpadProvider;
|
|
17
|
+
pasteOptions?: { lengthThreshold?: number; newlineThreshold?: number };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MemoryRecorder {
|
|
21
|
+
constructor(private readonly opts: MemoryRecorderOptions) {}
|
|
22
|
+
|
|
23
|
+
async recordTurn(args: { sessionId: string; userText: string; turnId: string; room?: Room }): Promise<Drawer> {
|
|
24
|
+
const kind = detectPaste(args.userText, this.opts.pasteOptions);
|
|
25
|
+
const room = args.room ?? this.opts.currentScratchpadName(args.sessionId) ?? 'inbox';
|
|
26
|
+
return this.writeDrawer({
|
|
27
|
+
wing: this.opts.writeWing, room, kind,
|
|
28
|
+
content: args.userText, metadata: { turn_id: args.turnId, session_id: args.sessionId },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async recordPaste(args: { sessionId: string; content: string; turnId: string; room?: Room }): Promise<Drawer> {
|
|
33
|
+
const room = args.room ?? this.opts.currentScratchpadName(args.sessionId) ?? 'inbox';
|
|
34
|
+
return this.writeDrawer({
|
|
35
|
+
wing: this.opts.writeWing, room, kind: 'paste',
|
|
36
|
+
content: args.content, metadata: { turn_id: args.turnId, session_id: args.sessionId },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async recordFileLoad(args: {
|
|
41
|
+
scratchpadName: string; collector: string; uri: string;
|
|
42
|
+
bytes: number; rows_loaded?: number; schema?: object; turnId: string;
|
|
43
|
+
}): Promise<Drawer> {
|
|
44
|
+
const content = JSON.stringify({
|
|
45
|
+
collector: args.collector, uri: args.uri, bytes: args.bytes,
|
|
46
|
+
rows_loaded: args.rows_loaded, schema: args.schema,
|
|
47
|
+
});
|
|
48
|
+
return this.writeDrawer({
|
|
49
|
+
wing: this.opts.writeWing, room: args.scratchpadName, kind: 'file_load',
|
|
50
|
+
content, metadata: { turn_id: args.turnId, scratchpad: args.scratchpadName },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async recordArtifact(args: {
|
|
55
|
+
scratchpadName: string; slug: string; kind: string; uri: string;
|
|
56
|
+
turnId: string;
|
|
57
|
+
}): Promise<Drawer> {
|
|
58
|
+
const content = JSON.stringify({
|
|
59
|
+
slug: args.slug, kind: args.kind, uri: args.uri,
|
|
60
|
+
});
|
|
61
|
+
return this.writeDrawer({
|
|
62
|
+
wing: this.opts.writeWing, room: args.scratchpadName, kind: 'artifact',
|
|
63
|
+
content, metadata: { turn_id: args.turnId, scratchpad: args.scratchpadName },
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async writeDrawer(input: Omit<Drawer, 'id' | 'created_at' | 'redacted'>): Promise<Drawer> {
|
|
68
|
+
const hits = this.opts.scanner.scan(input.content);
|
|
69
|
+
let content = input.content;
|
|
70
|
+
let redacted = false;
|
|
71
|
+
if (hits.length > 0) {
|
|
72
|
+
content = this.opts.scanner.redact(input.content);
|
|
73
|
+
redacted = true;
|
|
74
|
+
const ts = new Date().toISOString();
|
|
75
|
+
for (const h of hits) {
|
|
76
|
+
this.opts.audit.append({
|
|
77
|
+
_schema: 1, ts, producer: 'memory', action: 'redact', severity: 'warn',
|
|
78
|
+
detail: {
|
|
79
|
+
wing: input.wing, room: input.room, kind: input.kind,
|
|
80
|
+
secret_kind: h.kind, offset: h.start, length: h.end - h.start,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const drawer = await this.opts.backend.retain({ ...input, content, redacted });
|
|
86
|
+
this.opts.audit.append({
|
|
87
|
+
_schema: 1, ts: new Date().toISOString(), producer: 'memory', action: 'write-drawer',
|
|
88
|
+
detail: {
|
|
89
|
+
wing: drawer.wing, room: drawer.room, kind: drawer.kind,
|
|
90
|
+
byte_count: Buffer.byteLength(content, 'utf8'), redacted,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
return drawer;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- packages/coworker-memory/src/migrations/001-init.sql
|
|
2
|
+
PRAGMA journal_mode = WAL;
|
|
3
|
+
PRAGMA user_version = 1;
|
|
4
|
+
PRAGMA foreign_keys = ON;
|
|
5
|
+
|
|
6
|
+
CREATE TABLE IF NOT EXISTS drawers (
|
|
7
|
+
id TEXT PRIMARY KEY,
|
|
8
|
+
wing TEXT NOT NULL,
|
|
9
|
+
room TEXT NOT NULL,
|
|
10
|
+
kind TEXT NOT NULL CHECK (kind IN ('turn','paste','file_load','ticket','email','rca','note')),
|
|
11
|
+
content TEXT NOT NULL,
|
|
12
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
13
|
+
parent_id TEXT REFERENCES drawers(id) ON DELETE SET NULL,
|
|
14
|
+
redacted INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
created_at TEXT NOT NULL
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_drawers_wing_room ON drawers (wing, room);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_drawers_kind ON drawers (kind);
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_drawers_created_at ON drawers (created_at);
|
|
21
|
+
|
|
22
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS drawers_fts USING fts5 (
|
|
23
|
+
content,
|
|
24
|
+
content='drawers',
|
|
25
|
+
content_rowid='rowid',
|
|
26
|
+
tokenize='unicode61 remove_diacritics 2'
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TRIGGER IF NOT EXISTS drawers_ai AFTER INSERT ON drawers BEGIN
|
|
30
|
+
INSERT INTO drawers_fts (rowid, content) VALUES (new.rowid, new.content);
|
|
31
|
+
END;
|
|
32
|
+
CREATE TRIGGER IF NOT EXISTS drawers_ad AFTER DELETE ON drawers BEGIN
|
|
33
|
+
INSERT INTO drawers_fts (drawers_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
34
|
+
END;
|
|
35
|
+
CREATE TRIGGER IF NOT EXISTS drawers_au AFTER UPDATE ON drawers BEGIN
|
|
36
|
+
INSERT INTO drawers_fts (drawers_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
37
|
+
INSERT INTO drawers_fts (rowid, content) VALUES (new.rowid, new.content);
|
|
38
|
+
END;
|