@vellumai/assistant 0.6.1 → 0.6.3
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/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +21 -6
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +74 -55
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +16 -2
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +46 -7
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +16 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +5 -5
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +24 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- package/src/tools/system/set-permission-mode.ts +0 -103
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// Runs daily (or on demand). Processes nodes in partitions:
|
|
5
5
|
// 1. Recency: nodes from last 7 days — merge duplicates, initial narrative
|
|
6
6
|
// 2. Significance: top N by significance — update narrative arcs
|
|
7
|
-
// 3.
|
|
8
|
-
// 4. Random sample: cross-pollination and pattern detection
|
|
7
|
+
// 3. Random sample: cross-pollination and pattern detection
|
|
9
8
|
//
|
|
10
9
|
// Each partition is a separate LLM call. The LLM produces a MemoryDiff
|
|
11
10
|
// (same format as extraction) that is applied to the graph.
|
|
@@ -91,25 +90,25 @@ Today is ${today}.
|
|
|
91
90
|
|
|
92
91
|
## Your Tasks
|
|
93
92
|
|
|
94
|
-
1. **Merge duplicates**: If two or more nodes describe the same event or fact, merge them into one by:
|
|
93
|
+
1. **Merge duplicates**: If two or more nodes describe the exact same specific event or fact with substantially the same details, merge them into one by:
|
|
95
94
|
- Keeping the richer/more complete version (UPDATE it to incorporate details from duplicates)
|
|
96
95
|
- DELETE the duplicates
|
|
97
96
|
- Preserve the highest significance, reinforcement count, and stability from the merged nodes
|
|
98
|
-
- Create a "supersedes" edge from the surviving node to each deleted node
|
|
97
|
+
- Create a "supersedes" edge from the surviving node to each deleted node
|
|
98
|
+
- Two nodes about the same person or topic but with DIFFERENT details, timestamps, or context are NOT duplicates — leave them both intact.
|
|
99
99
|
|
|
100
|
-
2. **
|
|
100
|
+
2. **Rewrite faded content**: For nodes at "faded" or "gist" fidelity, rewrite their content to be shorter and more abstract — like how a real memory fades. A "faded" memory should be 1-2 sentences. A "gist" memory should be one sentence capturing only the essence.
|
|
101
101
|
|
|
102
102
|
3. **Update narrative roles**: If a node is clearly a turning point, inciting incident, or thesis in a larger story arc, set its narrativeRole and partOfStory.
|
|
103
103
|
|
|
104
|
-
4. **
|
|
105
|
-
|
|
106
|
-
5. **Resolve stale prospective nodes**: If a prospective node (type=prospective) is older than 7 days and has no "resolved-by" edge, it's likely a stale commitment. Downgrade its fidelity to "gist" and rewrite it as a past observation (e.g. "Had planned to X" instead of "Need to X"). If it's already at gist with significance < 0.2, mark it for deletion. If the node has an event_date in the past, clear it by setting event_date to null.
|
|
104
|
+
4. **Resolve stale prospective nodes**: If a prospective node (type=prospective) is older than 7 days and has no "resolved-by" edge, downgrade its fidelity to "gist" and rewrite it as a past observation (e.g. "Had planned to X" instead of "Need to X"). If the node has an event_date in the past, clear it by setting event_date to null.
|
|
107
105
|
|
|
108
106
|
## Constraints
|
|
109
107
|
|
|
110
|
-
- Do NOT create new nodes — consolidation only merges, updates, and
|
|
108
|
+
- Do NOT create new nodes — consolidation only merges, updates, and rewrites
|
|
111
109
|
- Do NOT change a node's type
|
|
112
110
|
- Do NOT increase fidelity (memories only fade, never sharpen)
|
|
111
|
+
- Do NOT delete non-duplicate nodes — only delete the non-survivor in a merge. Fading and eventual cleanup are handled by the decay engine, not consolidation.
|
|
113
112
|
- Preserve first-person prose style in content rewrites
|
|
114
113
|
- When merging, keep the node with higher reinforcementCount as the survivor
|
|
115
114
|
|
|
@@ -159,7 +158,7 @@ const CONSOLIDATE_TOOL_SCHEMA = {
|
|
|
159
158
|
},
|
|
160
159
|
delete_ids: {
|
|
161
160
|
type: "array" as const,
|
|
162
|
-
description: "Node IDs to delete (merged duplicates
|
|
161
|
+
description: "Node IDs to delete (merged duplicates only)",
|
|
163
162
|
items: { type: "string" as const },
|
|
164
163
|
},
|
|
165
164
|
merge_edges: {
|
|
@@ -206,17 +205,6 @@ function getTopSignificanceNodes(
|
|
|
206
205
|
.slice(0, n);
|
|
207
206
|
}
|
|
208
207
|
|
|
209
|
-
function getDecayedNodes(scopeId: string): MemoryNode[] {
|
|
210
|
-
const all = queryNodes({
|
|
211
|
-
scopeId,
|
|
212
|
-
limit: 10000,
|
|
213
|
-
});
|
|
214
|
-
return all.filter(
|
|
215
|
-
(n) =>
|
|
216
|
-
(n.fidelity === "faded" || n.fidelity === "gist") && !isCapabilityNode(n),
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
208
|
function getRandomSample(scopeId: string, n: number = 30): MemoryNode[] {
|
|
221
209
|
const all = queryNodes({
|
|
222
210
|
scopeId,
|
|
@@ -286,7 +274,7 @@ async function identifyDuplicateGroups(
|
|
|
286
274
|
})
|
|
287
275
|
.join("\n");
|
|
288
276
|
|
|
289
|
-
const systemPrompt = `You are scanning a list of memory nodes for DUPLICATES — nodes that describe the same event
|
|
277
|
+
const systemPrompt = `You are scanning a list of memory nodes for DUPLICATES — nodes that describe the exact same specific event or fact. Group duplicates together. Two nodes are duplicates ONLY if they describe the same underlying thing with substantially the same details. Be conservative — nodes about the same person or topic but with different details, timestamps, or context are NOT duplicates. Only include nodes that have at least one true duplicate.`;
|
|
290
278
|
|
|
291
279
|
const response = await provider.sendMessage(
|
|
292
280
|
[userMessage(listing)],
|
|
@@ -657,7 +645,6 @@ export async function runConsolidation(
|
|
|
657
645
|
const partitions: Array<{ name: string; nodes: MemoryNode[] }> = [
|
|
658
646
|
{ name: "recent", nodes: getRecentNodes(scopeId) },
|
|
659
647
|
{ name: "significant", nodes: getTopSignificanceNodes(scopeId) },
|
|
660
|
-
{ name: "decayed", nodes: getDecayedNodes(scopeId) },
|
|
661
648
|
{ name: "random", nodes: getRandomSample(scopeId) },
|
|
662
649
|
];
|
|
663
650
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { AssistantConfig } from "../../config/types.js";
|
|
10
10
|
import { getLogger } from "../../util/logger.js";
|
|
11
11
|
import { getMemoryCheckpoint, setMemoryCheckpoint } from "../checkpoints.js";
|
|
12
|
+
import { maybeEnqueueConversationStartersJob } from "../conversation-starters-cadence.js";
|
|
12
13
|
import { asString } from "../job-utils.js";
|
|
13
14
|
import type { MemoryJob } from "../jobs-store.js";
|
|
14
15
|
import { runGraphExtraction } from "./extraction.js";
|
|
@@ -64,6 +65,20 @@ export async function graphExtractJob(
|
|
|
64
65
|
},
|
|
65
66
|
"Graph extraction job complete",
|
|
66
67
|
);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
maybeEnqueueConversationStartersJob(scopeId);
|
|
71
|
+
} catch (cadenceErr) {
|
|
72
|
+
log.warn(
|
|
73
|
+
{
|
|
74
|
+
err:
|
|
75
|
+
cadenceErr instanceof Error
|
|
76
|
+
? cadenceErr.message
|
|
77
|
+
: String(cadenceErr),
|
|
78
|
+
},
|
|
79
|
+
"Conversation starters cadence check failed (non-fatal)",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
67
82
|
} catch (err) {
|
|
68
83
|
log.error(
|
|
69
84
|
{ conversationId, err: err instanceof Error ? err.message : String(err) },
|
|
@@ -371,8 +371,9 @@ export async function loadContextMemory(
|
|
|
371
371
|
opts: ContextLoadOpts,
|
|
372
372
|
): Promise<ContextLoadResult> {
|
|
373
373
|
const start = Date.now();
|
|
374
|
-
const
|
|
375
|
-
const
|
|
374
|
+
const ctxLoadCfg = opts.config.memory.retrieval.injection.contextLoad;
|
|
375
|
+
const maxNodes = opts.maxNodes ?? ctxLoadCfg.maxNodes;
|
|
376
|
+
const serendipitySlots = opts.serendipitySlots ?? ctxLoadCfg.serendipitySlots;
|
|
376
377
|
const now = new Date();
|
|
377
378
|
const nowMs = now.getTime();
|
|
378
379
|
|
|
@@ -547,13 +548,16 @@ export async function loadContextMemory(
|
|
|
547
548
|
// 5b. Reserve slots for skill/CLI capabilities. Queried directly from
|
|
548
549
|
// SQLite — no Qdrant vectors needed — so capabilities surface even on
|
|
549
550
|
// fresh assistants whose embedding jobs haven't completed yet.
|
|
550
|
-
const
|
|
551
|
-
const rawCapabilityNodes =
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
551
|
+
const capabilityReserve = ctxLoadCfg.capabilityReserve;
|
|
552
|
+
const rawCapabilityNodes =
|
|
553
|
+
capabilityReserve > 0
|
|
554
|
+
? queryNodes({
|
|
555
|
+
scopeId: opts.scopeId,
|
|
556
|
+
types: ["procedural"],
|
|
557
|
+
fidelityNot: ["gone"],
|
|
558
|
+
limit: capabilityReserve * 4,
|
|
559
|
+
})
|
|
560
|
+
: [];
|
|
557
561
|
|
|
558
562
|
// Dedup: both seeding systems may create nodes for the same capability.
|
|
559
563
|
// Extract capability ID from content and keep only the first node per ID.
|
|
@@ -574,14 +578,14 @@ export async function loadContextMemory(
|
|
|
574
578
|
|
|
575
579
|
// Rank by semantic similarity when a query vector exists
|
|
576
580
|
let selectedCapabilities: MemoryNode[];
|
|
577
|
-
if (queryVector && capabilityNodes.length >
|
|
581
|
+
if (queryVector && capabilityNodes.length > capabilityReserve) {
|
|
578
582
|
selectedCapabilities = capabilityNodes
|
|
579
583
|
.map((node) => ({ node, sim: semanticCandidateIds.get(node.id) ?? 0 }))
|
|
580
584
|
.sort((a, b) => b.sim - a.sim)
|
|
581
|
-
.slice(0,
|
|
585
|
+
.slice(0, capabilityReserve)
|
|
582
586
|
.map((e) => e.node);
|
|
583
587
|
} else {
|
|
584
|
-
selectedCapabilities = capabilityNodes.slice(0,
|
|
588
|
+
selectedCapabilities = capabilityNodes.slice(0, capabilityReserve);
|
|
585
589
|
}
|
|
586
590
|
|
|
587
591
|
const reservedCapabilities: ScoredNode[] = selectedCapabilities.map(
|
|
@@ -987,7 +991,8 @@ export async function retrieveForTurn(
|
|
|
987
991
|
// 5b. Reserve slots for capability nodes (skills/CLI).
|
|
988
992
|
// Sourced from vector search candidates — only semantically relevant
|
|
989
993
|
// capabilities compete for reserved slots.
|
|
990
|
-
const
|
|
994
|
+
const perTurnCfg = opts.config.memory.retrieval.injection.perTurn;
|
|
995
|
+
const capabilityReserve = perTurnCfg.capabilityReserve;
|
|
991
996
|
|
|
992
997
|
const proceduralCandidates = capabilityCandidates
|
|
993
998
|
.filter(({ node }) => !opts.tracker.isInContext(node.id))
|
|
@@ -1006,7 +1011,7 @@ export async function retrieveForTurn(
|
|
|
1006
1011
|
}
|
|
1007
1012
|
return true;
|
|
1008
1013
|
})
|
|
1009
|
-
.slice(0,
|
|
1014
|
+
.slice(0, capabilityReserve);
|
|
1010
1015
|
|
|
1011
1016
|
const proceduralScored: ScoredNode[] = rankedProcedural.map(({ node, sim }) =>
|
|
1012
1017
|
scoreCandidate(
|
|
@@ -1033,8 +1038,14 @@ export async function retrieveForTurn(
|
|
|
1033
1038
|
// Sort and apply threshold — pull a wider pool for dedup, then trim
|
|
1034
1039
|
scored.sort((a, b) => b.score - a.score);
|
|
1035
1040
|
const INJECTION_THRESHOLD = 0.3;
|
|
1041
|
+
// Hard cap on candidates fed to the dedup LLM — effectively caps maxNodes
|
|
1036
1042
|
const PRE_DEDUP_POOL = 20;
|
|
1037
|
-
const
|
|
1043
|
+
const maxGeneralNodes = Math.max(
|
|
1044
|
+
0,
|
|
1045
|
+
perTurnCfg.maxNodes -
|
|
1046
|
+
perTurnCfg.serendipitySlots -
|
|
1047
|
+
proceduralInjected.length,
|
|
1048
|
+
);
|
|
1038
1049
|
const pool = scored
|
|
1039
1050
|
.filter((s) => s.score >= INJECTION_THRESHOLD)
|
|
1040
1051
|
.slice(0, PRE_DEDUP_POOL);
|
|
@@ -1042,8 +1053,12 @@ export async function retrieveForTurn(
|
|
|
1042
1053
|
// Dedup + rerank with a fast model when the pool is large enough to warrant it
|
|
1043
1054
|
let injected: ScoredNode[];
|
|
1044
1055
|
let llmDedupApplied = false;
|
|
1045
|
-
if (pool.length >
|
|
1046
|
-
const result = await dedupForTurn(
|
|
1056
|
+
if (pool.length > maxGeneralNodes) {
|
|
1057
|
+
const result = await dedupForTurn(
|
|
1058
|
+
pool,
|
|
1059
|
+
maxGeneralNodes,
|
|
1060
|
+
opts.userLastMessage,
|
|
1061
|
+
);
|
|
1047
1062
|
injected = result.nodes;
|
|
1048
1063
|
llmDedupApplied = result.llmApplied;
|
|
1049
1064
|
} else {
|
|
@@ -1054,17 +1069,17 @@ export async function retrieveForTurn(
|
|
|
1054
1069
|
const generalInjected = injected.filter((s) => !proceduralIds.has(s.node.id));
|
|
1055
1070
|
|
|
1056
1071
|
// Backfill vacated general slots from the remaining pool so we always
|
|
1057
|
-
// return up to
|
|
1072
|
+
// return up to maxGeneralNodes when eligible candidates exist.
|
|
1058
1073
|
// Only skip backfill when LLM dedup genuinely ran — it intentionally rejected
|
|
1059
1074
|
// items as duplicates/irrelevant. When dedupForTurn fell back to a plain
|
|
1060
1075
|
// top-N slice (no provider, tool call failure), backfill is still appropriate.
|
|
1061
|
-
if (generalInjected.length <
|
|
1076
|
+
if (generalInjected.length < maxGeneralNodes && !llmDedupApplied) {
|
|
1062
1077
|
const usedIds = new Set([
|
|
1063
1078
|
...generalInjected.map((s) => s.node.id),
|
|
1064
1079
|
...proceduralIds,
|
|
1065
1080
|
]);
|
|
1066
1081
|
const backfillCandidates = pool.filter((s) => !usedIds.has(s.node.id));
|
|
1067
|
-
const needed =
|
|
1082
|
+
const needed = maxGeneralNodes - generalInjected.length;
|
|
1068
1083
|
for (let i = 0; i < Math.min(needed, backfillCandidates.length); i++) {
|
|
1069
1084
|
generalInjected.push(backfillCandidates[i]);
|
|
1070
1085
|
}
|
|
@@ -1073,11 +1088,14 @@ export async function retrieveForTurn(
|
|
|
1073
1088
|
const allDeterministic = [...generalInjected, ...proceduralInjected];
|
|
1074
1089
|
const deterministicIds = new Set(allDeterministic.map((n) => n.node.id));
|
|
1075
1090
|
|
|
1076
|
-
// Reserve
|
|
1091
|
+
// Reserve serendipity slots from scored candidates not in the deterministic set
|
|
1077
1092
|
const serendipityPool = scored.filter(
|
|
1078
1093
|
(s) => s.score >= INJECTION_THRESHOLD && !deterministicIds.has(s.node.id),
|
|
1079
1094
|
);
|
|
1080
|
-
const serendipityPicks = sampleSerendipity(
|
|
1095
|
+
const serendipityPicks = sampleSerendipity(
|
|
1096
|
+
serendipityPool,
|
|
1097
|
+
perTurnCfg.serendipitySlots,
|
|
1098
|
+
);
|
|
1081
1099
|
const allInjected = [...allDeterministic, ...serendipityPicks];
|
|
1082
1100
|
|
|
1083
1101
|
const TOP_N = 20;
|
|
@@ -179,10 +179,12 @@ describe("node CRUD", () => {
|
|
|
179
179
|
expect(updated!.eventDate).toBeNull();
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
test("deleteNode
|
|
182
|
+
test("deleteNode soft-deletes the node by setting fidelity to gone", () => {
|
|
183
183
|
const node = createNode(makeNewNode());
|
|
184
184
|
deleteNode(node.id);
|
|
185
|
-
|
|
185
|
+
const deleted = getNode(node.id);
|
|
186
|
+
expect(deleted).not.toBeNull();
|
|
187
|
+
expect(deleted!.fidelity).toBe("gone");
|
|
186
188
|
});
|
|
187
189
|
});
|
|
188
190
|
|
|
@@ -755,7 +757,9 @@ describe("applyDiff", () => {
|
|
|
755
757
|
reinforceNodeIds: [],
|
|
756
758
|
});
|
|
757
759
|
expect(result.nodesDeleted).toBe(1);
|
|
758
|
-
|
|
760
|
+
const deleted = getNode(node.id);
|
|
761
|
+
expect(deleted).not.toBeNull();
|
|
762
|
+
expect(deleted!.fidelity).toBe("gone");
|
|
759
763
|
});
|
|
760
764
|
|
|
761
765
|
test("updates nodes", () => {
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Memory Graph — Data access layer
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
import { and, desc, eq, inArray, sql } from "drizzle-orm";
|
|
5
|
+
import { and, desc, eq, inArray, or, sql } from "drizzle-orm";
|
|
6
6
|
import { v4 as uuid } from "uuid";
|
|
7
7
|
|
|
8
8
|
import { getDb } from "../db.js";
|
|
9
|
+
import { enqueueMemoryJob } from "../jobs-store.js";
|
|
9
10
|
import {
|
|
10
11
|
memoryGraphEdges,
|
|
11
12
|
memoryGraphNodeEdits,
|
|
@@ -270,7 +271,14 @@ export function updateNode(
|
|
|
270
271
|
|
|
271
272
|
export function deleteNode(id: string): void {
|
|
272
273
|
const db = getDb();
|
|
273
|
-
db.
|
|
274
|
+
db.update(memoryGraphNodes)
|
|
275
|
+
.set({ fidelity: "gone", lastAccessed: Date.now() })
|
|
276
|
+
.where(eq(memoryGraphNodes.id, id))
|
|
277
|
+
.run();
|
|
278
|
+
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
279
|
+
targetType: "graph_node",
|
|
280
|
+
targetId: id,
|
|
281
|
+
});
|
|
274
282
|
}
|
|
275
283
|
|
|
276
284
|
// ---------------------------------------------------------------------------
|
|
@@ -343,7 +351,7 @@ export function queryNodes(filters: NodeQueryFilters): MemoryNode[] {
|
|
|
343
351
|
.where(conditions.length > 0 ? and(...conditions) : undefined)
|
|
344
352
|
.orderBy(sql`${memoryGraphNodes.significance} DESC`);
|
|
345
353
|
|
|
346
|
-
if (filters.limit) {
|
|
354
|
+
if (filters.limit != null) {
|
|
347
355
|
query = query.limit(filters.limit) as typeof query;
|
|
348
356
|
}
|
|
349
357
|
|
|
@@ -396,12 +404,19 @@ export function getEdgesForNode(
|
|
|
396
404
|
direction?: "incoming" | "outgoing",
|
|
397
405
|
): MemoryEdge[] {
|
|
398
406
|
const db = getDb();
|
|
399
|
-
const
|
|
407
|
+
const dirCondition =
|
|
400
408
|
direction === "outgoing"
|
|
401
409
|
? eq(memoryGraphEdges.sourceNodeId, nodeId)
|
|
402
410
|
: direction === "incoming"
|
|
403
411
|
? eq(memoryGraphEdges.targetNodeId, nodeId)
|
|
404
|
-
:
|
|
412
|
+
: or(eq(memoryGraphEdges.sourceNodeId, nodeId), eq(memoryGraphEdges.targetNodeId, nodeId));
|
|
413
|
+
|
|
414
|
+
// Exclude edges where either endpoint has fidelity='gone' (soft-deleted)
|
|
415
|
+
const condition = and(
|
|
416
|
+
dirCondition,
|
|
417
|
+
sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.sourceNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
|
|
418
|
+
sql`NOT EXISTS (SELECT 1 FROM ${memoryGraphNodes} WHERE ${memoryGraphNodes.id} = ${memoryGraphEdges.targetNodeId} AND ${memoryGraphNodes.fidelity} = 'gone')`,
|
|
419
|
+
);
|
|
405
420
|
|
|
406
421
|
return db
|
|
407
422
|
.select()
|
|
@@ -512,12 +527,23 @@ export function getActiveTriggersByType(
|
|
|
512
527
|
return rows.map((r) => rowToTrigger(r.trigger));
|
|
513
528
|
}
|
|
514
529
|
|
|
515
|
-
|
|
516
|
-
.select(
|
|
530
|
+
const rows = db
|
|
531
|
+
.select({
|
|
532
|
+
trigger: memoryGraphTriggers,
|
|
533
|
+
})
|
|
517
534
|
.from(memoryGraphTriggers)
|
|
518
|
-
.
|
|
519
|
-
|
|
520
|
-
|
|
535
|
+
.innerJoin(
|
|
536
|
+
memoryGraphNodes,
|
|
537
|
+
eq(memoryGraphTriggers.nodeId, memoryGraphNodes.id),
|
|
538
|
+
)
|
|
539
|
+
.where(
|
|
540
|
+
and(
|
|
541
|
+
...conditions,
|
|
542
|
+
sql`${memoryGraphNodes.fidelity} != 'gone'`,
|
|
543
|
+
),
|
|
544
|
+
)
|
|
545
|
+
.all();
|
|
546
|
+
return rows.map((r) => rowToTrigger(r.trigger));
|
|
521
547
|
}
|
|
522
548
|
|
|
523
549
|
// ---------------------------------------------------------------------------
|
|
@@ -614,9 +640,18 @@ export function applyDiff(
|
|
|
614
640
|
};
|
|
615
641
|
|
|
616
642
|
db.transaction((tx) => {
|
|
617
|
-
//
|
|
643
|
+
// Soft-delete nodes (set fidelity='gone' and enqueue Qdrant cleanup)
|
|
618
644
|
for (const id of diff.deleteNodeIds) {
|
|
619
|
-
tx.
|
|
645
|
+
tx.update(memoryGraphNodes)
|
|
646
|
+
.set({ fidelity: "gone", lastAccessed: Date.now() })
|
|
647
|
+
.where(eq(memoryGraphNodes.id, id))
|
|
648
|
+
.run();
|
|
649
|
+
enqueueMemoryJob(
|
|
650
|
+
"delete_qdrant_vectors",
|
|
651
|
+
{ targetType: "graph_node", targetId: id },
|
|
652
|
+
Date.now(),
|
|
653
|
+
tx,
|
|
654
|
+
);
|
|
620
655
|
result.nodesDeleted++;
|
|
621
656
|
}
|
|
622
657
|
|
package/src/memory/group-crud.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function listGroups(): ConversationGroupRow[] {
|
|
|
33
33
|
created_at: number;
|
|
34
34
|
updated_at: number;
|
|
35
35
|
}>(
|
|
36
|
-
"SELECT id, name, sort_position, is_system_group, created_at, updated_at FROM conversation_groups WHERE id
|
|
36
|
+
"SELECT id, name, sort_position, is_system_group, created_at, updated_at FROM conversation_groups WHERE id NOT IN ('_backfill_complete', '_backfill_all_complete', '_sort_shift_complete') ORDER BY sort_position ASC",
|
|
37
37
|
);
|
|
38
38
|
return rows.map((r) => ({
|
|
39
39
|
id: r.id,
|
|
@@ -79,8 +79,8 @@ export function getGroup(groupId: string): ConversationGroupRow | null {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Create a custom group. Server assigns sort_position as max(custom) + 1.
|
|
82
|
-
* System groups occupy positions 0 (pinned
|
|
83
|
-
* First custom group gets position
|
|
82
|
+
* System groups occupy positions 0–3 (pinned, scheduled, background, all).
|
|
83
|
+
* First custom group gets position 4. Fallback ?? 3 ensures 3 + 1 = 4 when
|
|
84
84
|
* no custom groups exist.
|
|
85
85
|
*/
|
|
86
86
|
export function createGroup(name: string): ConversationGroupRow {
|
|
@@ -88,7 +88,7 @@ export function createGroup(name: string): ConversationGroupRow {
|
|
|
88
88
|
const maxPos =
|
|
89
89
|
rawGet<{ max: number | null }>(
|
|
90
90
|
"SELECT MAX(sort_position) as max FROM conversation_groups WHERE is_system_group = 0",
|
|
91
|
-
)?.max ??
|
|
91
|
+
)?.max ?? 3;
|
|
92
92
|
const sortPosition = maxPos + 1;
|
|
93
93
|
const id = uuid();
|
|
94
94
|
const now = Math.floor(Date.now() / 1000);
|
|
@@ -153,13 +153,16 @@ export function updateGroup(
|
|
|
153
153
|
// Delete
|
|
154
154
|
// ---------------------------------------------------------------------------
|
|
155
155
|
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// non-existent group.
|
|
156
|
+
// Reassign conversations to system:all before deleting the group so they
|
|
157
|
+
// don't end up with NULL group_id (which would violate the system:all
|
|
158
|
+
// invariant). The FK ON DELETE SET NULL would otherwise leave NULLs that
|
|
159
|
+
// the one-time backfill won't re-fix.
|
|
161
160
|
export function deleteGroup(groupId: string): boolean {
|
|
162
161
|
ensureGroupMigration();
|
|
162
|
+
rawRun(
|
|
163
|
+
"UPDATE conversations SET group_id = 'system:all' WHERE group_id = ?",
|
|
164
|
+
groupId,
|
|
165
|
+
);
|
|
163
166
|
rawRun("DELETE FROM conversation_groups WHERE id = ?", groupId);
|
|
164
167
|
return true;
|
|
165
168
|
}
|
|
@@ -176,6 +179,19 @@ export function reorderGroups(
|
|
|
176
179
|
rawExec("BEGIN");
|
|
177
180
|
try {
|
|
178
181
|
for (const update of updates) {
|
|
182
|
+
// Look up the group first — skip unknown/stale IDs and system groups
|
|
183
|
+
const group = rawGet<{ id: string; is_system_group: number }>(
|
|
184
|
+
"SELECT id, is_system_group FROM conversation_groups WHERE id = ?",
|
|
185
|
+
update.groupId,
|
|
186
|
+
);
|
|
187
|
+
if (!group) continue;
|
|
188
|
+
if (group.is_system_group === 1) continue;
|
|
189
|
+
|
|
190
|
+
if (update.sortPosition < 4) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Custom group sort_position must be >= 4 (got ${update.sortPosition} for ${update.groupId})`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
179
195
|
rawRun(
|
|
180
196
|
"UPDATE conversation_groups SET sort_position = ?, updated_at = ? WHERE id = ?",
|
|
181
197
|
update.sortPosition,
|
|
@@ -301,7 +301,7 @@ export function getUsageHourBuckets(range: UsageTimeRange): UsageDayBucket[] {
|
|
|
301
301
|
}));
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
type GroupByDimension = "actor" | "provider" | "model";
|
|
304
|
+
type GroupByDimension = "actor" | "provider" | "model" | "conversation";
|
|
305
305
|
|
|
306
306
|
/**
|
|
307
307
|
* Return grouped breakdowns across the given time range, ordered by total
|
|
@@ -312,10 +312,51 @@ export function getUsageGroupBreakdown(
|
|
|
312
312
|
groupBy: GroupByDimension,
|
|
313
313
|
): UsageGroupBreakdown[] {
|
|
314
314
|
// Runtime allowlist — defense-in-depth against SQL injection via type assertions.
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
315
|
+
const ALLOWED_DIMENSIONS = new Set<string>([
|
|
316
|
+
"actor",
|
|
317
|
+
"provider",
|
|
318
|
+
"model",
|
|
319
|
+
"conversation",
|
|
320
|
+
]);
|
|
321
|
+
if (!ALLOWED_DIMENSIONS.has(groupBy)) {
|
|
322
|
+
throw new Error(`Invalid groupBy dimension: ${groupBy}`);
|
|
318
323
|
}
|
|
324
|
+
|
|
325
|
+
// Conversation grouping requires a JOIN with conversations to resolve titles.
|
|
326
|
+
if (groupBy === "conversation") {
|
|
327
|
+
const rows = rawAll<GroupRow>(
|
|
328
|
+
/*sql*/ `
|
|
329
|
+
SELECT
|
|
330
|
+
CASE WHEN e.conversation_id IS NULL THEN 'Other'
|
|
331
|
+
ELSE COALESCE(c.title, 'Untitled')
|
|
332
|
+
END AS group_key,
|
|
333
|
+
COALESCE(SUM(e.input_tokens), 0) AS total_input_tokens,
|
|
334
|
+
COALESCE(SUM(e.output_tokens), 0) AS total_output_tokens,
|
|
335
|
+
COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
336
|
+
COALESCE(SUM(e.cache_read_input_tokens), 0) AS total_cache_read_tokens,
|
|
337
|
+
COALESCE(SUM(e.estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
338
|
+
COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
|
|
339
|
+
FROM llm_usage_events e
|
|
340
|
+
LEFT JOIN conversations c ON e.conversation_id = c.id
|
|
341
|
+
WHERE e.created_at >= ?1 AND e.created_at <= ?2
|
|
342
|
+
GROUP BY e.conversation_id
|
|
343
|
+
ORDER BY total_estimated_cost_usd DESC
|
|
344
|
+
LIMIT 50
|
|
345
|
+
`,
|
|
346
|
+
range.from,
|
|
347
|
+
range.to,
|
|
348
|
+
);
|
|
349
|
+
return rows.map((r) => ({
|
|
350
|
+
group: r.group_key,
|
|
351
|
+
totalInputTokens: r.total_input_tokens,
|
|
352
|
+
totalOutputTokens: r.total_output_tokens,
|
|
353
|
+
totalCacheCreationTokens: r.total_cache_creation_tokens,
|
|
354
|
+
totalCacheReadTokens: r.total_cache_read_tokens,
|
|
355
|
+
totalEstimatedCostUsd: r.total_estimated_cost_usd ?? 0,
|
|
356
|
+
eventCount: r.event_count,
|
|
357
|
+
}));
|
|
358
|
+
}
|
|
359
|
+
|
|
319
360
|
const column = groupBy;
|
|
320
361
|
const rows = rawAll<GroupRow>(
|
|
321
362
|
/*sql*/ `
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersScopeSeparator(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(
|
|
8
|
+
`ALTER TABLE oauth_providers ADD COLUMN scope_separator TEXT NOT NULL DEFAULT ' '`,
|
|
9
|
+
);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already exists — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersRefreshUrl(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN refresh_url TEXT`);
|
|
8
|
+
} catch {
|
|
9
|
+
// Column already exists — nothing to do.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersRevoke(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
const columns = ["revoke_url TEXT", "revoke_body_template TEXT"];
|
|
7
|
+
for (const col of columns) {
|
|
8
|
+
try {
|
|
9
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN ${col}`);
|
|
10
|
+
} catch {
|
|
11
|
+
// Column already exists — nothing to do.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Backfill `oauth_providers.token_endpoint_auth_method` for any rows where
|
|
6
|
+
* the value is NULL or empty string, setting them to the new default
|
|
7
|
+
* "client_secret_post". This brings existing rows in line with the
|
|
8
|
+
* Drizzle schema's new `.notNull().default("client_secret_post")`
|
|
9
|
+
* constraint, which is enforced at write time via the TypeScript layer.
|
|
10
|
+
*
|
|
11
|
+
* SQLite cannot retroactively add a NOT NULL constraint to an existing
|
|
12
|
+
* column without a full table rebuild, so the underlying column remains
|
|
13
|
+
* nullable at the SQLite level. All writes go through Drizzle, which
|
|
14
|
+
* applies the default for any insert that omits the field.
|
|
15
|
+
*
|
|
16
|
+
* The UPDATE is inherently idempotent and safe to re-run. Errors are
|
|
17
|
+
* allowed to propagate to the migration runner in `db-init.ts`, which
|
|
18
|
+
* records the failure, logs it, and continues to the next migration.
|
|
19
|
+
*/
|
|
20
|
+
export function migrateOAuthProvidersTokenAuthMethodDefault(
|
|
21
|
+
database: DrizzleDb,
|
|
22
|
+
): void {
|
|
23
|
+
const raw = getSqliteFrom(database);
|
|
24
|
+
raw.exec(
|
|
25
|
+
`UPDATE oauth_providers
|
|
26
|
+
SET token_endpoint_auth_method = 'client_secret_post'
|
|
27
|
+
WHERE token_endpoint_auth_method IS NULL
|
|
28
|
+
OR token_endpoint_auth_method = ''`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
import { withCrashRecovery } from "./validate-migration-state.js";
|
|
4
|
+
|
|
5
|
+
const CHECKPOINT_KEY = "migration_conversation_host_access_v1";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Add conversation-scoped host access state with a safe default of disabled.
|
|
9
|
+
*
|
|
10
|
+
* Idempotent: ALTER TABLE is guarded and the backfill only touches NULL rows.
|
|
11
|
+
*/
|
|
12
|
+
export function migrateConversationHostAccess(database: DrizzleDb): void {
|
|
13
|
+
withCrashRecovery(database, CHECKPOINT_KEY, () => {
|
|
14
|
+
const raw = getSqliteFrom(database);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
raw.exec(
|
|
18
|
+
`ALTER TABLE conversations ADD COLUMN host_access INTEGER NOT NULL DEFAULT 0`,
|
|
19
|
+
);
|
|
20
|
+
} catch {
|
|
21
|
+
// Column already exists.
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
raw.exec(`
|
|
25
|
+
UPDATE conversations
|
|
26
|
+
SET host_access = 0
|
|
27
|
+
WHERE host_access IS NULL
|
|
28
|
+
`);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Reverse: no-op.
|
|
34
|
+
*
|
|
35
|
+
* The forward migration is additive and SQLite cannot drop one column without
|
|
36
|
+
* rebuilding the table.
|
|
37
|
+
*/
|
|
38
|
+
export function downConversationHostAccess(_database: DrizzleDb): void {
|
|
39
|
+
// Intentionally empty.
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
2
|
+
import { getSqliteFrom } from "../db-connection.js";
|
|
3
|
+
|
|
4
|
+
export function migrateOAuthProvidersLogoUrl(database: DrizzleDb): void {
|
|
5
|
+
const raw = getSqliteFrom(database);
|
|
6
|
+
try {
|
|
7
|
+
raw.exec(`ALTER TABLE oauth_providers ADD COLUMN logo_url TEXT`);
|
|
8
|
+
} catch {
|
|
9
|
+
// Column already exists — nothing to do.
|
|
10
|
+
}
|
|
11
|
+
}
|