@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
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/AGENTS.md +9 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* without going through the HTTP layer.
|
|
7
7
|
*/
|
|
8
8
|
import { rawAll, rawGet, rawRun } from "../persistence/raw-query.js";
|
|
9
|
-
import { writeWorkspaceFile } from "../runtime/routes/workspace-utils.js";
|
|
10
9
|
import { getLogger } from "../util/logger.js";
|
|
11
10
|
|
|
12
11
|
const log = getLogger("document-store");
|
|
@@ -24,18 +23,13 @@ export interface DocumentRecord {
|
|
|
24
23
|
wordCount: number;
|
|
25
24
|
createdAt: number;
|
|
26
25
|
updatedAt: number;
|
|
27
|
-
/**
|
|
28
|
-
* Workspace-relative path of the markdown file this document is bound to,
|
|
29
|
-
* or `null` when the document has no file behind it.
|
|
30
|
-
*/
|
|
31
|
-
workspacePath: string | null;
|
|
32
26
|
}
|
|
33
27
|
|
|
34
28
|
/**
|
|
35
|
-
* The listing projection:
|
|
36
|
-
*
|
|
29
|
+
* The listing projection: everything but the body, which the list and search
|
|
30
|
+
* queries deliberately leave out of their SELECTs.
|
|
37
31
|
*/
|
|
38
|
-
export type DocumentSummary = Omit<DocumentRecord, "content"
|
|
32
|
+
export type DocumentSummary = Omit<DocumentRecord, "content">;
|
|
39
33
|
|
|
40
34
|
/** Words in a markdown body, the count the `word_count` column stores. */
|
|
41
35
|
function countWords(content: string): number {
|
|
@@ -72,10 +66,9 @@ interface DocumentRow {
|
|
|
72
66
|
word_count: number;
|
|
73
67
|
created_at: number;
|
|
74
68
|
updated_at: number;
|
|
75
|
-
workspace_path: string | null;
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
type DocumentListRow = Omit<DocumentRow, "content"
|
|
71
|
+
type DocumentListRow = Omit<DocumentRow, "content">;
|
|
79
72
|
|
|
80
73
|
function escapeSqlLikePattern(value: string): string {
|
|
81
74
|
return value.replace(/[\\%_]/g, "\\$&");
|
|
@@ -90,7 +83,6 @@ function mapRowToRecord(row: DocumentRow): DocumentRecord {
|
|
|
90
83
|
wordCount: row.word_count,
|
|
91
84
|
createdAt: row.created_at,
|
|
92
85
|
updatedAt: row.updated_at,
|
|
93
|
-
workspacePath: row.workspace_path,
|
|
94
86
|
};
|
|
95
87
|
}
|
|
96
88
|
|
|
@@ -99,7 +91,7 @@ export function getDocumentById(surfaceId: string): DocumentRecord | null {
|
|
|
99
91
|
try {
|
|
100
92
|
const row = rawGet<DocumentRow>(
|
|
101
93
|
"documents:getDocumentById",
|
|
102
|
-
/*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at
|
|
94
|
+
/*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at
|
|
103
95
|
FROM documents
|
|
104
96
|
WHERE surface_id = ?`,
|
|
105
97
|
surfaceId,
|
|
@@ -118,29 +110,6 @@ export function getDocumentById(surfaceId: string): DocumentRecord | null {
|
|
|
118
110
|
}
|
|
119
111
|
}
|
|
120
112
|
|
|
121
|
-
/**
|
|
122
|
-
* Look up the document bound to a workspace file. `workspacePath` must be the
|
|
123
|
-
* normalized workspace-relative path stored on the row. Returns `null` when no
|
|
124
|
-
* document is bound to that file.
|
|
125
|
-
*/
|
|
126
|
-
export function getDocumentByWorkspacePath(
|
|
127
|
-
workspacePath: string,
|
|
128
|
-
): DocumentRecord | null {
|
|
129
|
-
try {
|
|
130
|
-
const row = rawGet<DocumentRow>(
|
|
131
|
-
"documents:getDocumentByWorkspacePath",
|
|
132
|
-
/*sql*/ `SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at, workspace_path
|
|
133
|
-
FROM documents
|
|
134
|
-
WHERE workspace_path = ?`,
|
|
135
|
-
workspacePath,
|
|
136
|
-
);
|
|
137
|
-
return row ? mapRowToRecord(row) : null;
|
|
138
|
-
} catch (error) {
|
|
139
|
-
log.error({ err: error, workspacePath }, "Load-by-workspace-path error");
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
113
|
/** Return true when a document is associated with a conversation. */
|
|
145
114
|
export function isDocumentAssociatedWithConversation(
|
|
146
115
|
surfaceId: string,
|
|
@@ -429,202 +398,6 @@ export function findInDocument(
|
|
|
429
398
|
}
|
|
430
399
|
}
|
|
431
400
|
|
|
432
|
-
// ---------------------------------------------------------------------------
|
|
433
|
-
// Workspace file binding
|
|
434
|
-
// ---------------------------------------------------------------------------
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Mirror `content` onto the workspace file a document is bound to.
|
|
438
|
-
*
|
|
439
|
-
* This is the single write-through choke point: every path that persists
|
|
440
|
-
* document content — {@link saveDocument} (client save and `document_create`),
|
|
441
|
-
* {@link updateDocumentContent} (`document_update`), and
|
|
442
|
-
* {@link replaceInDocument} (`document_replace_text`) — calls it immediately
|
|
443
|
-
* before its own row write, so a file-backed document's markdown on disk always
|
|
444
|
-
* matches the stored row.
|
|
445
|
-
*
|
|
446
|
-
* The file is written first and a failure throws: a rejected write aborts the
|
|
447
|
-
* update before the row changes rather than leaving the two silently diverged.
|
|
448
|
-
* Documents with no file behind them are a no-op.
|
|
449
|
-
*/
|
|
450
|
-
function writeThroughToWorkspaceFile(surfaceId: string, content: string): void {
|
|
451
|
-
const row = rawGet<{ workspace_path: string | null }>(
|
|
452
|
-
"documents:writeThroughToWorkspaceFile:getPath",
|
|
453
|
-
/*sql*/ `SELECT workspace_path FROM documents WHERE surface_id = ?`,
|
|
454
|
-
surfaceId,
|
|
455
|
-
);
|
|
456
|
-
const workspacePath = row?.workspace_path;
|
|
457
|
-
if (!workspacePath) {
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
writeWorkspaceFile(workspacePath, Buffer.from(content, "utf-8"));
|
|
463
|
-
log.info({ surfaceId, workspacePath }, "Wrote document through to file");
|
|
464
|
-
} catch (error) {
|
|
465
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
466
|
-
throw new Error(
|
|
467
|
-
`Failed to write file-backed document to ${workspacePath}: ${message}`,
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Create a document seeded from a workspace markdown file and bind it to that
|
|
474
|
-
* file. The returned surface ID is the file's document identity — comments,
|
|
475
|
-
* assistant iteration, and PDF export all key off it.
|
|
476
|
-
*
|
|
477
|
-
* The partial unique index on `workspace_path` makes a concurrent second
|
|
478
|
-
* create fail rather than produce a duplicate binding; callers re-read by path
|
|
479
|
-
* on failure.
|
|
480
|
-
*/
|
|
481
|
-
export function createFileBackedDocument(params: {
|
|
482
|
-
surfaceId: string;
|
|
483
|
-
conversationId: string;
|
|
484
|
-
title: string;
|
|
485
|
-
content: string;
|
|
486
|
-
workspacePath: string;
|
|
487
|
-
}): { success: true } | { success: false; error: string } {
|
|
488
|
-
try {
|
|
489
|
-
const now = Date.now();
|
|
490
|
-
rawRun(
|
|
491
|
-
"documents:createFileBackedDocument",
|
|
492
|
-
/*sql*/ `INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at, workspace_path)
|
|
493
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
494
|
-
params.surfaceId,
|
|
495
|
-
params.conversationId,
|
|
496
|
-
params.title,
|
|
497
|
-
params.content,
|
|
498
|
-
countWords(params.content),
|
|
499
|
-
now,
|
|
500
|
-
now,
|
|
501
|
-
params.workspacePath,
|
|
502
|
-
);
|
|
503
|
-
addDocumentConversation(params.surfaceId, params.conversationId);
|
|
504
|
-
log.info(
|
|
505
|
-
{ surfaceId: params.surfaceId, workspacePath: params.workspacePath },
|
|
506
|
-
"Created file-backed document",
|
|
507
|
-
);
|
|
508
|
-
return { success: true };
|
|
509
|
-
} catch (error) {
|
|
510
|
-
log.error(
|
|
511
|
-
{ err: error, workspacePath: params.workspacePath },
|
|
512
|
-
"File-backed document create error",
|
|
513
|
-
);
|
|
514
|
-
return {
|
|
515
|
-
success: false,
|
|
516
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Move file-backed documents onto the path a workspace rename moved their file
|
|
523
|
-
* to. `oldPath` and `newPath` are canonical workspace-relative paths, the same
|
|
524
|
-
* form {@link createFileBackedDocument} stores.
|
|
525
|
-
*
|
|
526
|
-
* A document bound to `oldPath` itself moves, and so does every document nested
|
|
527
|
-
* under it (`oldPath + "/"` prefix), which is what a directory rename produces.
|
|
528
|
-
*
|
|
529
|
-
* The rename on disk has already happened by the time this runs, so it is best
|
|
530
|
-
* effort and never throws: the caller has no way to undo the rename, and a
|
|
531
|
-
* document left on a stale path is recoverable while a failed rename response
|
|
532
|
-
* is not. Rows move one statement at a time so a single row that cannot move
|
|
533
|
-
* does not strand its siblings.
|
|
534
|
-
*
|
|
535
|
-
* A row whose destination is already taken by another binding is one such case:
|
|
536
|
-
* the partial unique index on `workspace_path` rejects the update, and the
|
|
537
|
-
* losing row keeps pointing at the old path. That is deliberate. Overwriting
|
|
538
|
-
* the winner would drop a binding that a client may have open, whereas the
|
|
539
|
-
* stale binding self-heals: opening the file refreshes the row's content from
|
|
540
|
-
* disk, and a document whose file is gone is reported as gone rather than
|
|
541
|
-
* rewritten from the row.
|
|
542
|
-
*/
|
|
543
|
-
export function rebindDocumentsToRenamedPath(params: {
|
|
544
|
-
oldPath: string;
|
|
545
|
-
newPath: string;
|
|
546
|
-
}): void {
|
|
547
|
-
const { oldPath, newPath } = params;
|
|
548
|
-
if (!oldPath || !newPath || oldPath === newPath) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
const rows = rawAll<{ surface_id: string; workspace_path: string }>(
|
|
554
|
-
"documents:rebindRenamedPath:select",
|
|
555
|
-
/*sql*/ `SELECT surface_id, workspace_path
|
|
556
|
-
FROM documents
|
|
557
|
-
WHERE workspace_path = ? OR workspace_path LIKE ? ESCAPE '\\'`,
|
|
558
|
-
oldPath,
|
|
559
|
-
`${escapeSqlLikePattern(oldPath)}/%`,
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
for (const row of rows) {
|
|
563
|
-
const rebound =
|
|
564
|
-
row.workspace_path === oldPath
|
|
565
|
-
? newPath
|
|
566
|
-
: newPath + row.workspace_path.slice(oldPath.length);
|
|
567
|
-
try {
|
|
568
|
-
rawRun(
|
|
569
|
-
"documents:rebindRenamedPath:update",
|
|
570
|
-
/*sql*/ `UPDATE documents SET workspace_path = ? WHERE surface_id = ?`,
|
|
571
|
-
rebound,
|
|
572
|
-
row.surface_id,
|
|
573
|
-
);
|
|
574
|
-
log.info(
|
|
575
|
-
{
|
|
576
|
-
surfaceId: row.surface_id,
|
|
577
|
-
from: row.workspace_path,
|
|
578
|
-
to: rebound,
|
|
579
|
-
},
|
|
580
|
-
"Rebound file-backed document to renamed path",
|
|
581
|
-
);
|
|
582
|
-
} catch (error) {
|
|
583
|
-
log.warn(
|
|
584
|
-
{
|
|
585
|
-
err: error,
|
|
586
|
-
surfaceId: row.surface_id,
|
|
587
|
-
from: row.workspace_path,
|
|
588
|
-
to: rebound,
|
|
589
|
-
},
|
|
590
|
-
"Left file-backed document on its old path after a rename",
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
} catch (error) {
|
|
595
|
-
log.error({ err: error, oldPath, newPath }, "Path rebind error");
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* Replace a document's stored content with the current text of the file it is
|
|
601
|
-
* bound to. The file is the source of truth when a document is opened, so this
|
|
602
|
-
* deliberately skips the write-through step — disk and row already agree.
|
|
603
|
-
*/
|
|
604
|
-
export function refreshDocumentContentFromFile(
|
|
605
|
-
surfaceId: string,
|
|
606
|
-
content: string,
|
|
607
|
-
): { success: true } | { success: false; error: string } {
|
|
608
|
-
try {
|
|
609
|
-
rawRun(
|
|
610
|
-
"documents:refreshDocumentContentFromFile",
|
|
611
|
-
/*sql*/ `UPDATE documents SET content = ?, word_count = ?, updated_at = ? WHERE surface_id = ?`,
|
|
612
|
-
content,
|
|
613
|
-
countWords(content),
|
|
614
|
-
Date.now(),
|
|
615
|
-
surfaceId,
|
|
616
|
-
);
|
|
617
|
-
log.info({ surfaceId }, "Refreshed document content from file");
|
|
618
|
-
return { success: true };
|
|
619
|
-
} catch (error) {
|
|
620
|
-
log.error({ err: error, surfaceId }, "Document refresh error");
|
|
621
|
-
return {
|
|
622
|
-
success: false,
|
|
623
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
401
|
// ---------------------------------------------------------------------------
|
|
629
402
|
// Document persistence
|
|
630
403
|
// ---------------------------------------------------------------------------
|
|
@@ -637,7 +410,6 @@ export function saveDocument(params: {
|
|
|
637
410
|
wordCount: number;
|
|
638
411
|
}): { success: true; surfaceId: string } | { success: false; error: string } {
|
|
639
412
|
try {
|
|
640
|
-
writeThroughToWorkspaceFile(params.surfaceId, params.content);
|
|
641
413
|
const now = Date.now();
|
|
642
414
|
rawRun(
|
|
643
415
|
"documents:saveDocument",
|
|
@@ -773,7 +545,6 @@ export function replaceInDocument(
|
|
|
773
545
|
replacementsMade = totalMatches;
|
|
774
546
|
}
|
|
775
547
|
|
|
776
|
-
writeThroughToWorkspaceFile(surfaceId, newContent);
|
|
777
548
|
const wordCount = countWords(newContent);
|
|
778
549
|
rawRun(
|
|
779
550
|
"documents:replaceInDocument:update",
|
|
@@ -798,12 +569,125 @@ export function replaceInDocument(
|
|
|
798
569
|
}
|
|
799
570
|
}
|
|
800
571
|
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// Append idempotency
|
|
574
|
+
// ---------------------------------------------------------------------------
|
|
575
|
+
|
|
576
|
+
/** A blank-line-delimited block of markdown, located in its source string. */
|
|
577
|
+
interface MarkdownBlock {
|
|
578
|
+
/** Whitespace-trimmed block text, the unit duplicate detection compares. */
|
|
579
|
+
text: string;
|
|
580
|
+
/** Offset of the block's first character in the source string. */
|
|
581
|
+
start: number;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Split markdown into blank-line-delimited blocks, dropping empty ones but
|
|
586
|
+
* keeping each surviving block's offset so a suffix of the source can be
|
|
587
|
+
* recovered verbatim.
|
|
588
|
+
*/
|
|
589
|
+
function splitIntoBlocks(content: string): MarkdownBlock[] {
|
|
590
|
+
const blocks: MarkdownBlock[] = [];
|
|
591
|
+
const separator = /\n[^\S\n]*\n/g;
|
|
592
|
+
let cursor = 0;
|
|
593
|
+
let match: RegExpExecArray | null;
|
|
594
|
+
const push = (start: number, end: number): void => {
|
|
595
|
+
const text = content.slice(start, end).trim();
|
|
596
|
+
if (text.length > 0) {
|
|
597
|
+
blocks.push({ text, start });
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
while ((match = separator.exec(content)) !== null) {
|
|
601
|
+
push(cursor, match.index);
|
|
602
|
+
cursor = match.index + match[0].length;
|
|
603
|
+
}
|
|
604
|
+
push(cursor, content.length);
|
|
605
|
+
return blocks;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* An append has to repeat at least this many characters of the document's tail
|
|
610
|
+
* before the repetition is treated as an accident.
|
|
611
|
+
*
|
|
612
|
+
* The floor is what keeps legitimate repetition intact. A document can honestly
|
|
613
|
+
* repeat a heading, a refrain, a table row, or a bare "TODO" back to back, and
|
|
614
|
+
* those are all short. Only a substantial restatement (an opening paragraph
|
|
615
|
+
* passed to both `document_create` and the first append) clears the bar.
|
|
616
|
+
*/
|
|
617
|
+
const MIN_DUPLICATE_APPEND_CHARS = 40;
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Drop a leading run of blocks from `markdown` that exactly repeats the tail of
|
|
621
|
+
* `existing`, so an append that restates content already committed does not
|
|
622
|
+
* write it a second time.
|
|
623
|
+
*
|
|
624
|
+
* The guard is deliberately narrow: the repeated run must be an exact,
|
|
625
|
+
* block-aligned match, it must sit at the very start of the append and the very
|
|
626
|
+
* end of the document, and it must be at least
|
|
627
|
+
* {@link MIN_DUPLICATE_APPEND_CHARS} long. Anything else, including the same
|
|
628
|
+
* text reappearing further down the append, is passed through untouched.
|
|
629
|
+
*/
|
|
630
|
+
function stripDuplicateLeadingBlocks(
|
|
631
|
+
existing: string,
|
|
632
|
+
markdown: string,
|
|
633
|
+
): string {
|
|
634
|
+
const existingBlocks = splitIntoBlocks(existing);
|
|
635
|
+
const incomingBlocks = splitIntoBlocks(markdown);
|
|
636
|
+
const maxOverlap = Math.min(existingBlocks.length, incomingBlocks.length);
|
|
637
|
+
|
|
638
|
+
let overlap = 0;
|
|
639
|
+
for (let candidate = maxOverlap; candidate >= 1; candidate--) {
|
|
640
|
+
const offset = existingBlocks.length - candidate;
|
|
641
|
+
let matches = true;
|
|
642
|
+
for (let i = 0; i < candidate; i++) {
|
|
643
|
+
if (existingBlocks[offset + i].text !== incomingBlocks[i].text) {
|
|
644
|
+
matches = false;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (matches) {
|
|
649
|
+
overlap = candidate;
|
|
650
|
+
break;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (overlap === 0) {
|
|
654
|
+
return markdown;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const duplicatedChars = incomingBlocks
|
|
658
|
+
.slice(0, overlap)
|
|
659
|
+
.reduce((total, block) => total + block.text.length, 0);
|
|
660
|
+
if (duplicatedChars < MIN_DUPLICATE_APPEND_CHARS) {
|
|
661
|
+
return markdown;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (overlap === incomingBlocks.length) {
|
|
665
|
+
return "";
|
|
666
|
+
}
|
|
667
|
+
return markdown
|
|
668
|
+
.slice(incomingBlocks[overlap].start)
|
|
669
|
+
.replace(/^(?:[^\S\n]*\n)+/, "");
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/** Outcome of a successful {@link updateDocumentContent} call. */
|
|
673
|
+
export interface DocumentContentUpdated {
|
|
674
|
+
success: true;
|
|
675
|
+
/**
|
|
676
|
+
* The markdown that actually landed. Equals the submitted markdown except
|
|
677
|
+
* when an append's leading blocks duplicated the document's tail, in which
|
|
678
|
+
* case it is the remainder. Clients render this so their view matches the row.
|
|
679
|
+
*/
|
|
680
|
+
appliedMarkdown: string;
|
|
681
|
+
/** True when a duplicated leading run was dropped from an append. */
|
|
682
|
+
duplicateLeadingContentSkipped: boolean;
|
|
683
|
+
}
|
|
684
|
+
|
|
801
685
|
/** Update persisted document content (append or replace). */
|
|
802
686
|
export function updateDocumentContent(
|
|
803
687
|
surfaceId: string,
|
|
804
688
|
markdown: string,
|
|
805
689
|
mode: string,
|
|
806
|
-
):
|
|
690
|
+
): DocumentContentUpdated | { success: false; error: string } {
|
|
807
691
|
try {
|
|
808
692
|
const existing = rawGet<{ content: string }>(
|
|
809
693
|
"documents:updateDocumentContent:get",
|
|
@@ -814,10 +698,19 @@ export function updateDocumentContent(
|
|
|
814
698
|
log.info({ surfaceId }, "No persisted document to update");
|
|
815
699
|
return { success: false, error: "Document not found" };
|
|
816
700
|
}
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
701
|
+
const appending = mode === "append";
|
|
702
|
+
const appliedMarkdown = appending
|
|
703
|
+
? stripDuplicateLeadingBlocks(existing.content, markdown)
|
|
704
|
+
: markdown;
|
|
705
|
+
const duplicateLeadingContentSkipped =
|
|
706
|
+
appending && appliedMarkdown !== markdown;
|
|
707
|
+
const sep =
|
|
708
|
+
appending && existing.content.length > 0 && appliedMarkdown.length > 0
|
|
709
|
+
? "\n\n"
|
|
710
|
+
: "";
|
|
711
|
+
const newContent = appending
|
|
712
|
+
? existing.content + sep + appliedMarkdown
|
|
713
|
+
: appliedMarkdown;
|
|
821
714
|
const wordCount = countWords(newContent);
|
|
822
715
|
rawRun(
|
|
823
716
|
"documents:updateDocumentContent:update",
|
|
@@ -827,8 +720,18 @@ export function updateDocumentContent(
|
|
|
827
720
|
Date.now(),
|
|
828
721
|
surfaceId,
|
|
829
722
|
);
|
|
723
|
+
if (duplicateLeadingContentSkipped) {
|
|
724
|
+
log.info(
|
|
725
|
+
{ surfaceId, skippedChars: markdown.length - appliedMarkdown.length },
|
|
726
|
+
"Skipped append content duplicating the document tail",
|
|
727
|
+
);
|
|
728
|
+
}
|
|
830
729
|
log.info({ surfaceId, mode }, "Updated document content");
|
|
831
|
-
return {
|
|
730
|
+
return {
|
|
731
|
+
success: true,
|
|
732
|
+
appliedMarkdown,
|
|
733
|
+
duplicateLeadingContentSkipped,
|
|
734
|
+
};
|
|
832
735
|
} catch (error) {
|
|
833
736
|
log.error({ err: error, surfaceId }, "Document content update error");
|
|
834
737
|
return {
|
package/src/hooks/hook-loader.ts
CHANGED
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
* registry, so the next read re-resolves fresh.
|
|
40
40
|
*
|
|
41
41
|
* Plugin *discovery* (which plugin directories exist, in what order) lives in
|
|
42
|
-
* `../plugins/mtime-cache.ts`;
|
|
43
|
-
*
|
|
44
|
-
* this module lets it sit below the plugin cache with no import cycle.
|
|
42
|
+
* `../plugins/mtime-cache.ts`; hook lookup in `./registry.ts` passes the
|
|
43
|
+
* discovered names into {@link collectUserHookEntries}. Keeping discovery out
|
|
44
|
+
* of this module lets it sit below the plugin cache with no import cycle.
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
import {
|
package/src/hooks/registry.ts
CHANGED
|
@@ -6,20 +6,25 @@
|
|
|
6
6
|
* hook-chain ordering.
|
|
7
7
|
*
|
|
8
8
|
* User-land hooks (from the filesystem) are owned by
|
|
9
|
-
* {@link ./hook-loader.ts} and surfaced through
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* {@link ./hook-loader.ts} and surfaced through {@link getUserHooksFor}.
|
|
10
|
+
* This module owns the in-process hooks that default plugins register at
|
|
11
|
+
* boot, plus the user-land lookup that walks discovered plugin names from
|
|
12
|
+
* the plugin cache.
|
|
12
13
|
*
|
|
13
|
-
* {@link getHooksFor}
|
|
14
|
-
*
|
|
15
|
-
* from
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* {@link getHooksFor} and {@link getUserHooksFor} both resolve through
|
|
15
|
+
* {@link getUserHookEntriesFor}, which prepends in-process default-plugin
|
|
16
|
+
* hooks from this registry (filtered by `isPluginDisabled` at read time)
|
|
17
|
+
* ahead of user-land hooks from the plugin cache. The read-time filtering
|
|
18
|
+
* is what makes `assistant plugins disable default-*` take effect immediately
|
|
19
|
+
* in a running assistant: the hooks stay registered but are filtered out on
|
|
20
|
+
* the next turn.
|
|
18
21
|
*/
|
|
19
22
|
|
|
23
|
+
import { isFirstPartyDefaultPlugin } from "../plugins/defaults/main.js";
|
|
20
24
|
import { isPluginDisabled } from "../plugins/disabled-state.js";
|
|
21
|
-
import {
|
|
25
|
+
import { getDiscoveredUserPluginNames } from "../plugins/mtime-cache.js";
|
|
22
26
|
import type { HookEntry, HookFunction } from "../plugins/types.js";
|
|
27
|
+
import { collectUserHookEntries } from "./hook-loader.js";
|
|
23
28
|
|
|
24
29
|
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
25
30
|
|
|
@@ -75,6 +80,87 @@ export function unregisterPluginHooks(pluginName: string): void {
|
|
|
75
80
|
|
|
76
81
|
// ─── Queries ─────────────────────────────────────────────────────────────────
|
|
77
82
|
|
|
83
|
+
/**
|
|
84
|
+
* In-process (default plugin) hook entries for `name`, filtered by the
|
|
85
|
+
* `.disabled` sentinel at read time. First-party default plugins always pass
|
|
86
|
+
* the per-chat allowlist; other in-process registrations (test fixtures) honor
|
|
87
|
+
* it. Used by {@link getUserHookEntriesFor} so `getUserHooksFor` returns the
|
|
88
|
+
* same default-plugin hooks {@link getHooksFor} does.
|
|
89
|
+
*/
|
|
90
|
+
function collectInProcessHookEntries<TCtx = unknown>(
|
|
91
|
+
name: string,
|
|
92
|
+
effectiveEnabledPlugins?: Set<string> | null,
|
|
93
|
+
): HookEntry<TCtx>[] {
|
|
94
|
+
const defaultEntries: HookEntry<TCtx>[] = [];
|
|
95
|
+
for (const entry of hookRegistry.get(name) ?? []) {
|
|
96
|
+
if (isPluginDisabled(entry.pluginName)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
effectiveEnabledPlugins != null &&
|
|
101
|
+
!effectiveEnabledPlugins.has(entry.pluginName) &&
|
|
102
|
+
!isFirstPartyDefaultPlugin(entry.pluginName)
|
|
103
|
+
) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
defaultEntries.push({
|
|
107
|
+
fn: entry.fn as HookFunction<TCtx>,
|
|
108
|
+
owner: { kind: "plugin", id: entry.pluginName },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return defaultEntries;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get all hooks for a given event name: in-process default-plugin hooks
|
|
116
|
+
* first, then user plugins and standalone workspace hooks. Plugin hooks run
|
|
117
|
+
* in install-date order, the workspace hook runs last.
|
|
118
|
+
*
|
|
119
|
+
* Default-plugin hooks come from the in-process registry (registration at
|
|
120
|
+
* boot). User-land hooks are a pure cache read: this never scans disk,
|
|
121
|
+
* activates a plugin, or runs `init`. Activation happens only at boot
|
|
122
|
+
* (`populateCacheAtBoot`) and through the imperative install/uninstall
|
|
123
|
+
* poke (`reconcilePluginSourcesNow`), both main-daemon paths, so a
|
|
124
|
+
* sidecar process that dispatches hooks (a worker running conversation
|
|
125
|
+
* turns) can never bring a plugin up in its own process.
|
|
126
|
+
*
|
|
127
|
+
* `effectiveEnabledPlugins` carries the per-chat plugin scope: when non-null,
|
|
128
|
+
* user plugins outside the set are skipped. First-party default plugins
|
|
129
|
+
* always pass that allowlist (a workspace `.disabled` sentinel still
|
|
130
|
+
* excludes them). Standalone workspace hooks always run. `null`/omitted
|
|
131
|
+
* means no per-chat restriction.
|
|
132
|
+
*/
|
|
133
|
+
export async function getUserHookEntriesFor<TCtx = unknown>(
|
|
134
|
+
hookName: string,
|
|
135
|
+
effectiveEnabledPlugins?: Set<string> | null,
|
|
136
|
+
): Promise<HookEntry<TCtx>[]> {
|
|
137
|
+
const defaultEntries = collectInProcessHookEntries<TCtx>(
|
|
138
|
+
hookName,
|
|
139
|
+
effectiveEnabledPlugins,
|
|
140
|
+
);
|
|
141
|
+
const userEntries = await collectUserHookEntries<TCtx>(
|
|
142
|
+
hookName,
|
|
143
|
+
getDiscoveredUserPluginNames(),
|
|
144
|
+
effectiveEnabledPlugins,
|
|
145
|
+
);
|
|
146
|
+
return [...defaultEntries, ...userEntries];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* {@link getUserHookEntriesFor} without owner attribution. Returns just the
|
|
151
|
+
* hook functions in the same order, including in-process default-plugin hooks.
|
|
152
|
+
*/
|
|
153
|
+
export async function getUserHooksFor<TCtx = unknown>(
|
|
154
|
+
hookName: string,
|
|
155
|
+
effectiveEnabledPlugins?: Set<string> | null,
|
|
156
|
+
): Promise<HookFunction<TCtx>[]> {
|
|
157
|
+
const entries = await getUserHookEntriesFor<TCtx>(
|
|
158
|
+
hookName,
|
|
159
|
+
effectiveEnabledPlugins,
|
|
160
|
+
);
|
|
161
|
+
return entries.map((e) => e.fn);
|
|
162
|
+
}
|
|
163
|
+
|
|
78
164
|
/**
|
|
79
165
|
* Collect every registered hook for the given name, in registration order.
|
|
80
166
|
* Plugins that don't declare a hook for `name` are skipped. Used by the
|
|
@@ -87,17 +173,19 @@ export function unregisterPluginHooks(pluginName: string): void {
|
|
|
87
173
|
* (async, pure cache read). Default hooks are prepended so they compose
|
|
88
174
|
* innermost, ahead of any user plugins.
|
|
89
175
|
*
|
|
90
|
-
* The `TCtx` generic mirrors {@link HookFunction}'s
|
|
176
|
+
* The `TCtx` generic mirrors {@link HookFunction}'s: callers parameterize
|
|
91
177
|
* over the concrete context type their hook receives. Hooks that mutate the
|
|
92
178
|
* context in place return `void`; hooks that return a new context replace
|
|
93
179
|
* the threaded value for the next hook in the chain.
|
|
94
180
|
*
|
|
95
181
|
* When `conversationId` is given, the conversation's effective plugin scope is
|
|
96
182
|
* resolved from it (memory, then DB) and layered on top of the global disabled
|
|
97
|
-
* check: a
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
183
|
+
* check: a user plugin must also be a member of that set or its hook is
|
|
184
|
+
* excluded for this turn. First-party default plugins are not subject to the
|
|
185
|
+
* per-chat allowlist (they are core runtime infrastructure; the chat pills
|
|
186
|
+
* only list user-installed plugins), but a workspace `.disabled` sentinel
|
|
187
|
+
* still excludes them. Omit `conversationId` (or pass a conversation with no
|
|
188
|
+
* per-chat restriction) and every globally-enabled plugin's hooks run.
|
|
101
189
|
*/
|
|
102
190
|
export async function getHookEntriesFor<TCtx = unknown>(
|
|
103
191
|
name: string,
|
|
@@ -116,37 +204,7 @@ export async function getHookEntriesFor<TCtx = unknown>(
|
|
|
116
204
|
options.conversationId,
|
|
117
205
|
);
|
|
118
206
|
}
|
|
119
|
-
|
|
120
|
-
// sentinel at read time. This is what makes `assistant plugins disable
|
|
121
|
-
// default-*` take effect immediately in a running assistant: the hooks stay
|
|
122
|
-
// registered but are filtered out on the next turn.
|
|
123
|
-
const defaultEntries: HookEntry<TCtx>[] = [];
|
|
124
|
-
for (const entry of hookRegistry.get(name) ?? []) {
|
|
125
|
-
if (isPluginDisabled(entry.pluginName)) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (
|
|
129
|
-
effectiveEnabledPlugins != null &&
|
|
130
|
-
!effectiveEnabledPlugins.has(entry.pluginName)
|
|
131
|
-
) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
defaultEntries.push({
|
|
135
|
-
fn: entry.fn as HookFunction<TCtx>,
|
|
136
|
-
owner: { kind: "plugin", id: entry.pluginName },
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// User-land hooks from the plugin cache (async, pure cache read; dispatch
|
|
141
|
-
// never activates plugins). The per-chat
|
|
142
|
-
// scope is threaded through so a deselected user plugin's hooks are excluded
|
|
143
|
-
// too — standalone workspace hooks (not owned by a plugin) always run.
|
|
144
|
-
const userEntries = await getUserHookEntriesFor<TCtx>(
|
|
145
|
-
name,
|
|
146
|
-
effectiveEnabledPlugins,
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
return [...defaultEntries, ...userEntries];
|
|
207
|
+
return getUserHookEntriesFor<TCtx>(name, effectiveEnabledPlugins);
|
|
150
208
|
}
|
|
151
209
|
|
|
152
210
|
/**
|
package/src/hooks/types.ts
CHANGED
|
@@ -173,6 +173,11 @@ export interface UserPromptSubmitInputContext {
|
|
|
173
173
|
* user speech (e.g. title generation) should skip these turns.
|
|
174
174
|
*/
|
|
175
175
|
readonly isHiddenPrompt?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Daemon-authored kind from the triggering row's `metadata.messageKind`,
|
|
178
|
+
* when the turn needs a more specific identity than role or visibility.
|
|
179
|
+
*/
|
|
180
|
+
readonly messageKind?: string;
|
|
176
181
|
/**
|
|
177
182
|
* The user's original message list, immutable for the hook. Plugins
|
|
178
183
|
* may snapshot or compare against this but MUST NOT mutate it.
|