@vellumai/assistant 0.10.0 → 0.10.1-staging.1
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/ARCHITECTURE.md +36 -37
- package/bun.lock +3 -0
- package/docs/workflows.md +12 -7
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@slack/types/LICENSE +23 -0
- package/node_modules/@slack/types/README.md +32 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts +953 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts +474 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts +237 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts +88 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js.map +1 -0
- package/node_modules/@slack/types/dist/calls.d.ts +26 -0
- package/node_modules/@slack/types/dist/calls.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/calls.js +6 -0
- package/node_modules/@slack/types/dist/calls.js.map +1 -0
- package/node_modules/@slack/types/dist/chunk.d.ts +52 -0
- package/node_modules/@slack/types/dist/chunk.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/chunk.js +3 -0
- package/node_modules/@slack/types/dist/chunk.js.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts +12 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js +3 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts +6 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js +3 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js.map +1 -0
- package/node_modules/@slack/types/dist/dialog.d.ts +36 -0
- package/node_modules/@slack/types/dist/dialog.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/dialog.js +3 -0
- package/node_modules/@slack/types/dist/dialog.js.map +1 -0
- package/node_modules/@slack/types/dist/events/app.d.ts +204 -0
- package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/app.js +3 -0
- package/node_modules/@slack/types/dist/events/app.js.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts +29 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.js +3 -0
- package/node_modules/@slack/types/dist/events/assistant.js.map +1 -0
- package/node_modules/@slack/types/dist/events/call.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/call.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/call.js +3 -0
- package/node_modules/@slack/types/dist/events/call.js.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts +85 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.js +3 -0
- package/node_modules/@slack/types/dist/events/channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts +24 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.js +3 -0
- package/node_modules/@slack/types/dist/events/dnd.js.map +1 -0
- package/node_modules/@slack/types/dist/events/email.d.ts +6 -0
- package/node_modules/@slack/types/dist/events/email.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/email.js +3 -0
- package/node_modules/@slack/types/dist/events/email.js.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts +11 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.js +3 -0
- package/node_modules/@slack/types/dist/events/emoji.js.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts +21 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js +3 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js.map +1 -0
- package/node_modules/@slack/types/dist/events/file.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/file.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/file.js +4 -0
- package/node_modules/@slack/types/dist/events/file.js.map +1 -0
- package/node_modules/@slack/types/dist/events/function.d.ts +33 -0
- package/node_modules/@slack/types/dist/events/function.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/function.js +3 -0
- package/node_modules/@slack/types/dist/events/function.js.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts +9 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js +3 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js.map +1 -0
- package/node_modules/@slack/types/dist/events/group.d.ts +55 -0
- package/node_modules/@slack/types/dist/events/group.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/group.js +3 -0
- package/node_modules/@slack/types/dist/events/group.js.map +1 -0
- package/node_modules/@slack/types/dist/events/im.d.ts +26 -0
- package/node_modules/@slack/types/dist/events/im.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/im.js +3 -0
- package/node_modules/@slack/types/dist/events/im.js.map +1 -0
- package/node_modules/@slack/types/dist/events/index.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/index.js +43 -0
- package/node_modules/@slack/types/dist/events/index.js.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts +20 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.js +3 -0
- package/node_modules/@slack/types/dist/events/invite.js.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts +16 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.js +3 -0
- package/node_modules/@slack/types/dist/events/link-shared.js.map +1 -0
- package/node_modules/@slack/types/dist/events/member.d.ts +19 -0
- package/node_modules/@slack/types/dist/events/member.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/member.js +3 -0
- package/node_modules/@slack/types/dist/events/member.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts +38 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js +3 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message.d.ts +306 -0
- package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message.js +3 -0
- package/node_modules/@slack/types/dist/events/message.js.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.js +3 -0
- package/node_modules/@slack/types/dist/events/pin.js.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts +23 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.js +3 -0
- package/node_modules/@slack/types/dist/events/reaction.js.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts +134 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js +3 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/star.d.ts +13 -0
- package/node_modules/@slack/types/dist/events/star.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/star.js +3 -0
- package/node_modules/@slack/types/dist/events/star.js.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts +82 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js +3 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts +66 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.js +3 -0
- package/node_modules/@slack/types/dist/events/subteam.js.map +1 -0
- package/node_modules/@slack/types/dist/events/team.d.ts +99 -0
- package/node_modules/@slack/types/dist/events/team.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/team.js +3 -0
- package/node_modules/@slack/types/dist/events/team.js.map +1 -0
- package/node_modules/@slack/types/dist/events/token.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/token.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/token.js +3 -0
- package/node_modules/@slack/types/dist/events/token.js.map +1 -0
- package/node_modules/@slack/types/dist/events/user.d.ts +313 -0
- package/node_modules/@slack/types/dist/events/user.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/user.js +3 -0
- package/node_modules/@slack/types/dist/events/user.js.map +1 -0
- package/node_modules/@slack/types/dist/index.d.ts +12 -0
- package/node_modules/@slack/types/dist/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/index.js +28 -0
- package/node_modules/@slack/types/dist/index.js.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts +171 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.js +3 -0
- package/node_modules/@slack/types/dist/message-attachments.js.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts +281 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.js +27 -0
- package/node_modules/@slack/types/dist/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/views.d.ts +71 -0
- package/node_modules/@slack/types/dist/views.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/views.js +3 -0
- package/node_modules/@slack/types/dist/views.js.map +1 -0
- package/node_modules/@slack/types/package.json +47 -0
- package/node_modules/@vellumai/gateway-client/bun.lock +3 -0
- package/node_modules/@vellumai/gateway-client/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +69 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +65 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +162 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +4 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +3 -2
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +78 -0
- package/openapi.yaml +345 -18
- package/package.json +2 -1
- package/scripts/memory-inspect.ts +24 -14
- package/src/__tests__/access-request-seed-content-blocks.test.ts +83 -103
- package/src/__tests__/activation-early-marking.test.ts +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -3
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +1 -40
- package/src/__tests__/agent-loop-compaction-events.test.ts +0 -1
- package/src/__tests__/agent-loop-compaction-strip.test.ts +0 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +0 -1
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/anthropic-provider.test.ts +143 -9
- package/src/__tests__/app-builder-skill-instructions.test.ts +47 -5
- package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -1
- package/src/__tests__/app-source-watcher.test.ts +30 -10
- package/src/__tests__/approval-cascade.test.ts +6 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +151 -0
- package/src/__tests__/approval-primitive.test.ts +1 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +155 -0
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +2 -4
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-events-sse-shed.test.ts +1 -1
- package/src/__tests__/attachment-upload-trusted-source.test.ts +13 -8
- package/src/__tests__/attachments-store.test.ts +1 -1
- package/src/__tests__/audit-log-rotation.test.ts +50 -54
- package/src/__tests__/auth-fallback-events-store.test.ts +1 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -14
- package/src/__tests__/background-shell-bash.test.ts +4 -1
- package/src/__tests__/background-shell-host-bash.test.ts +17 -3
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
- package/src/__tests__/call-controller.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +1 -1
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-pointer-messages.test.ts +3 -4
- package/src/__tests__/call-recovery.test.ts +1 -1
- package/src/__tests__/call-routes-http.test.ts +1 -1
- package/src/__tests__/call-store.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/canonical-guardian-store.test.ts +24 -1
- package/src/__tests__/channel-approval-routes.test.ts +73 -1119
- package/src/__tests__/channel-delivery-store.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +265 -641
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -2
- package/src/__tests__/channel-retry-sweep.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +6 -0
- package/src/__tests__/compaction-trail-store.test.ts +6 -5
- package/src/__tests__/compaction.benchmark.test.ts +0 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/config-loader-backfill.test.ts +183 -51
- package/src/__tests__/config-schema.test.ts +34 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -2
- package/src/__tests__/contact-store-user-file.test.ts +2 -2
- package/src/__tests__/contacts-relay-reads.test.ts +409 -0
- package/src/__tests__/contacts-tools.test.ts +4 -4
- package/src/__tests__/contacts-write.test.ts +1 -2
- package/src/__tests__/context-search-conversations-source.test.ts +1 -1
- package/src/__tests__/context-window-manager-compact-retry.test.ts +6 -2
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +6 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop.test.ts +3 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -5
- package/src/__tests__/conversation-attention-store.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +1 -2
- package/src/__tests__/conversation-clear-safety.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +12 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +10 -8
- package/src/__tests__/conversation-fork-retrospective.test.ts +250 -0
- package/src/__tests__/conversation-fork-route.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-list.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +117 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -3
- package/src/__tests__/conversation-process-callsite.test.ts +6 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +6 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +115 -12
- package/src/__tests__/conversation-slash-queue.test.ts +6 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +6 -0
- package/src/__tests__/conversation-starter-routes.test.ts +5 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +1 -1
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-wipe.test.ts +9 -8
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/conversations-import-system-filter.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +17 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +2 -2
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +5 -7
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -7
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +5 -10
- package/src/__tests__/db-migration-rollback.test.ts +129 -39
- package/src/__tests__/db-proxy-transaction.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -11
- package/src/__tests__/db-test-helpers.ts +36 -19
- package/src/__tests__/delete-propagation.test.ts +1 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +26 -8
- package/src/__tests__/disk-pressure-tools.test.ts +41 -1
- package/src/__tests__/dm-backfill.test.ts +1 -1
- package/src/__tests__/drop-capability-card-state-migration.test.ts +0 -8
- package/src/__tests__/edit-propagation.test.ts +1 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +83 -0
- package/src/__tests__/empty-response-hook.test.ts +42 -0
- package/src/__tests__/events-client-registration.test.ts +1 -1
- package/src/__tests__/followup-tools.test.ts +1 -1
- package/src/__tests__/gemini-count-tokens.test.ts +70 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +1 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +1 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -12
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/guardian-routing-state.test.ts +1 -2
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/headless-browser-mode.test.ts +2 -2
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +6 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +98 -0
- package/src/__tests__/http-conversation-lineage.test.ts +1 -1
- package/src/__tests__/image-recovery-hook.test.ts +1 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -2
- package/src/__tests__/inbound-trust-verdict.test.ts +254 -0
- package/src/__tests__/inference-profile-reaper.test.ts +1 -1
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-disk-pressure.test.ts +11 -6
- package/src/__tests__/internal-telemetry-routes.test.ts +1 -1
- package/src/__tests__/invite-redemption-service.test.ts +244 -43
- package/src/__tests__/invite-routes-http.test.ts +35 -186
- package/src/__tests__/invite-service-ipc.test.ts +287 -0
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +5 -5
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +9 -12
- package/src/__tests__/list-messages-attachments.test.ts +42 -1
- package/src/__tests__/list-messages-client-message-id.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +1 -1
- package/src/__tests__/list-messages-page-latest.test.ts +1 -1
- package/src/__tests__/list-messages-tool-merge.test.ts +1 -1
- package/src/__tests__/llm-context-route-provider.test.ts +69 -4
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +9 -5
- package/src/__tests__/llm-request-log-call-site.test.ts +6 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +27 -13
- package/src/__tests__/llm-usage-store.test.ts +40 -1
- package/src/__tests__/log-export-routes.test.ts +1 -1
- package/src/__tests__/log-export-workspace.test.ts +3 -3
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +5 -5
- package/src/__tests__/memory-recall-log-store.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +3 -4
- package/src/__tests__/messages-after-tiebreaker.test.ts +1 -1
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +375 -0
- package/src/__tests__/non-member-access-request.test.ts +1 -2
- package/src/__tests__/notification-candidate-guardian-context.test.ts +203 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -1
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +1 -1
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +1 -1
- package/src/__tests__/persist-unsendable-image.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +39 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/playbook-execution.test.ts +1 -1
- package/src/__tests__/playbook-tools.test.ts +1 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +74 -21
- package/src/__tests__/plugin-bootstrap.test.ts +78 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +25 -5
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/prune-old-conversations-job.test.ts +1 -1
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +357 -56
- package/src/__tests__/runtime-attachment-metadata.test.ts +10 -1
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +7 -9
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/schedule-retry.test.ts +1 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -1
- package/src/__tests__/schedule-routes.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schedule-tools.test.ts +1 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +1 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +1 -1
- package/src/__tests__/scheduler-wake.test.ts +2 -1
- package/src/__tests__/scoped-approval-grants.test.ts +1 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -5
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +0 -8
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +1 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +40 -1
- package/src/__tests__/settings-routes.test.ts +11 -10
- package/src/__tests__/skill-load-tool.test.ts +72 -0
- package/src/__tests__/slack-inbound-verification.test.ts +1 -3
- package/src/__tests__/slack-messaging-token-resolution.test.ts +13 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -73
- package/src/__tests__/subagent-tools.test.ts +1 -31
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/task-management-tools.test.ts +1 -1
- package/src/__tests__/task-memory-cleanup.test.ts +9 -6
- package/src/__tests__/task-scheduler.test.ts +1 -1
- package/src/__tests__/thread-backfill.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +32 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +73 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +34 -34
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -2
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +3 -3
- package/src/__tests__/turn-events-store.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/usage-cache-backfill-migration.test.ts +20 -10
- package/src/__tests__/usage-routes.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +34 -29
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +134 -36
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-git-service.test.ts +114 -1
- package/src/__tests__/workspace-heartbeat-service.test.ts +45 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +88 -18
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +6 -6
- package/src/__tests__/workspace-migration-109-swap-quality-profile-to-glm-5p2.test.ts +281 -0
- package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +167 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +55 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -1
- package/src/a2a/__tests__/task-store.test.ts +1 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +22 -11
- package/src/acp/__tests__/session-manager-startup.test.ts +1 -1
- package/src/acp/__tests__/session-manager.test.ts +72 -1
- package/src/acp/index.ts +10 -0
- package/src/acp/session-manager.ts +35 -0
- package/src/agent/loop.ts +45 -27
- package/src/api/index.ts +0 -6
- package/src/approvals/AGENTS.md +1 -2
- package/src/approvals/guardian-decision-primitive.ts +13 -210
- package/src/approvals/guardian-request-resolvers.ts +104 -58
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/inbound-trust-reader.test.ts +110 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +88 -62
- package/src/calls/inbound-trust-reader.ts +40 -0
- package/src/calls/relay-server.ts +65 -23
- package/src/calls/relay-setup-router.ts +20 -6
- package/src/calls/relay-verification.ts +7 -7
- package/src/cli/commands/contacts.ts +6 -24
- package/src/cli/commands/db/__tests__/repair.test.ts +15 -6
- package/src/cli/commands/db/__tests__/status.test.ts +7 -3
- package/src/cli/commands/db/status.ts +212 -33
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +6 -1
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-retrospective.ts +129 -0
- package/src/cli/commands/memory/memory-v3.ts +176 -4
- package/src/cli/commands/plugins.ts +268 -11
- package/src/cli/lib/__tests__/install-from-github.test.ts +40 -0
- package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +158 -0
- package/src/cli/lib/install-from-github.ts +47 -6
- package/src/cli/lib/plugin-marketplace.ts +11 -0
- package/src/cli/lib/plugin-pin-history.ts +257 -0
- package/src/cli/lib/toggle-plugin.ts +146 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +15 -33
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +3 -8
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +64 -37
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +1 -1
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +14 -72
- package/src/config/bundled-skills/app-builder/references/examples/README.md +1 -2
- package/src/config/bundled-skills/contacts/SKILL.md +7 -12
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
- package/src/config/bundled-skills/workflows/SKILL.md +4 -3
- package/src/config/call-site-defaults.ts +11 -2
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/profile-dispatchability.ts +11 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +5 -3
- package/src/config/schemas/timeouts.ts +24 -0
- package/src/config/seed-inference-profiles.ts +133 -45
- package/src/config/sync-gated-profiles.ts +13 -1
- package/src/contacts/contact-store.ts +21 -0
- package/src/contacts/member-status.ts +9 -0
- package/src/credential-health/credential-health-service.ts +1 -5
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
- package/src/daemon/app-source-watcher.ts +31 -18
- package/src/daemon/assistant-attachments.ts +94 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +3 -0
- package/src/daemon/conversation-agent-loop.ts +9 -36
- package/src/daemon/conversation-runtime-assembly.ts +91 -66
- package/src/daemon/conversation-tool-setup.ts +20 -63
- package/src/daemon/conversation.ts +144 -52
- package/src/daemon/event-loop-watchdog.test.ts +85 -0
- package/src/daemon/event-loop-watchdog.ts +133 -0
- package/src/daemon/external-plugins-bootstrap.ts +26 -80
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -1
- package/src/daemon/handlers/config-channels.ts +32 -18
- package/src/daemon/handlers/conversations.ts +7 -0
- package/src/daemon/handlers/shared.ts +7 -0
- package/src/daemon/lifecycle.ts +16 -3
- package/src/daemon/message-types/inbox.ts +0 -6
- package/src/daemon/message-types/messages.ts +0 -4
- package/src/daemon/message-types/surfaces.ts +18 -8
- package/src/daemon/server.ts +0 -4
- package/src/daemon/tool-setup-types.ts +0 -7
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +70 -0
- package/src/daemon/workspace-tools-watcher.ts +7 -3
- package/src/documents/document-comments-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
- package/src/heartbeat/heartbeat-service.ts +3 -4
- package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +73 -2
- package/src/ipc/__tests__/watcher-ipc.test.ts +59 -39
- package/src/ipc/assistant-server.ts +8 -0
- package/src/ipc/gateway-client.ts +2 -1
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +58 -0
- package/src/ipc/routes/invite-ipc-routes.ts +66 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
- package/src/memory/__tests__/activation-session-store.test.ts +1 -1
- package/src/memory/__tests__/auto-analysis-guard.test.ts +1 -1
- package/src/memory/__tests__/conversation-group-migration.test.ts +1 -1
- package/src/memory/__tests__/conversation-queries.test.ts +1 -1
- package/src/memory/__tests__/db-async-query.test.ts +1 -1
- package/src/memory/__tests__/db-logs-attach.test.ts +110 -0
- package/src/memory/__tests__/db-maintenance.test.ts +28 -36
- package/src/memory/__tests__/db-memory-attach.test.ts +113 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +1 -1
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -1
- package/src/memory/__tests__/fork-message-copy.test.ts +232 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +3 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +5 -5
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +8 -6
- package/src/memory/__tests__/memory-retrospective-job.test.ts +30 -37
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +69 -66
- package/src/memory/__tests__/memory-retrospective-state.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +1 -1
- package/src/memory/__tests__/onboarding-events-store.test.ts +1 -1
- package/src/memory/__tests__/table-relocation.test.ts +129 -0
- package/src/memory/conversation-crud.ts +461 -152
- package/src/memory/db-async-query.ts +89 -5
- package/src/memory/db-connection.ts +101 -18
- package/src/memory/db-init.ts +409 -234
- package/src/memory/db-maintenance.ts +43 -38
- package/src/memory/db-singleton.ts +45 -19
- package/src/memory/fork-message-copy.ts +170 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +92 -0
- package/src/memory/graph/bootstrap.test.ts +6 -3
- package/src/memory/graph/retriever.test.ts +12 -12
- package/src/memory/graph/store.test.ts +15 -25
- package/src/memory/graph/store.ts +23 -14
- package/src/memory/graph/tool-handlers.ts +34 -5
- package/src/memory/graph/tools.ts +5 -2
- package/src/memory/indexer.ts +21 -9
- package/src/memory/job-handlers/cleanup.ts +10 -3
- package/src/memory/job-handlers/embedding.test.ts +4 -4
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +4 -4
- package/src/memory/jobs/embed-pkb-file.test.ts +7 -7
- package/src/memory/jobs-store.ts +36 -24
- package/src/memory/llm-request-log-store.ts +51 -19
- package/src/memory/llm-usage-store.ts +31 -1
- package/src/memory/memory-retrospective-job.ts +27 -19
- package/src/memory/memory-retrospective-startup-cleanup.ts +10 -2
- package/src/memory/migrations/{100-core-tables.ts → 000-core-tables.ts} +6 -10
- package/src/memory/migrations/104-core-indexes.ts +1 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +189 -196
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +98 -105
- package/src/memory/migrations/134-contacts-notes-column.ts +66 -69
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +19 -22
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +227 -230
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +204 -209
- package/src/memory/migrations/141-rename-verification-table.ts +45 -48
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +16 -23
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +23 -30
- package/src/memory/migrations/144-rename-voice-to-phone.ts +133 -136
- package/src/memory/migrations/145-drop-accounts-table.ts +4 -7
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +79 -82
- package/src/memory/migrations/148-drop-reminders-table.ts +3 -6
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +71 -78
- package/src/memory/migrations/157-invite-contact-id.ts +73 -76
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +44 -58
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +36 -43
- package/src/memory/migrations/174-rename-thread-starters-table.ts +30 -37
- package/src/memory/migrations/176-drop-capability-card-state.ts +17 -22
- package/src/memory/migrations/177-create-trace-events-table.ts +23 -28
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +36 -43
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +14 -21
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +17 -24
- package/src/memory/migrations/192-contacts-user-file-column.ts +6 -9
- package/src/memory/migrations/193-add-source-type-columns.ts +33 -36
- package/src/memory/migrations/194-memory-recall-logs.ts +34 -39
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +59 -66
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +41 -48
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +11 -18
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +76 -83
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +50 -57
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +6 -11
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +4 -9
- package/src/memory/migrations/217-conversation-host-access.ts +13 -18
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +86 -93
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +41 -48
- package/src/memory/migrations/230-acp-session-history.ts +23 -28
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +58 -62
- package/src/memory/migrations/232-activation-state.ts +11 -16
- package/src/memory/migrations/233-document-conversations.ts +20 -25
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +26 -31
- package/src/memory/migrations/235-slack-compaction-watermark.ts +5 -10
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +6 -11
- package/src/memory/migrations/237-heartbeat-runs.ts +22 -27
- package/src/memory/migrations/239-trace-events-created-at-index.ts +4 -9
- package/src/memory/migrations/242-message-bookmarks.ts +17 -22
- package/src/memory/migrations/245-memory-retrospective-state.ts +8 -13
- package/src/memory/migrations/249-normalize-slack-external-content.ts +37 -41
- package/src/memory/migrations/251-a2a-tasks.ts +27 -32
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +12 -17
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +10 -15
- package/src/memory/migrations/256-memory-v2-injection-events.ts +70 -74
- package/src/memory/migrations/259-conversation-cleaned-at.ts +4 -9
- package/src/memory/migrations/260-rename-cleaned-at.ts +11 -16
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +3 -8
- package/src/memory/migrations/262-memory-v3-coactivation.ts +21 -26
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +14 -19
- package/src/memory/migrations/270-schedule-description.ts +7 -12
- package/src/memory/migrations/272-acp-session-history-cwd.ts +8 -13
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +8 -13
- package/src/memory/migrations/297-move-llm-request-logs-to-logs-db.ts +111 -0
- package/src/memory/migrations/298-move-memory-jobs-to-memory-db.ts +128 -0
- package/src/memory/migrations/299-canonical-guardian-deliveries-conversation-index.ts +19 -0
- package/src/memory/migrations/__tests__/297-move-llm-request-logs.test.ts +180 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +333 -7
- package/src/memory/migrations/helpers/relocation.ts +227 -0
- package/src/memory/migrations/registry.ts +63 -0
- package/src/memory/migrations/run-migrations.ts +187 -16
- package/src/memory/migrations/validate-migration-state.ts +50 -145
- package/src/memory/raw-query.ts +47 -2
- package/src/memory/skill-loaded-events-store.test.ts +1 -1
- package/src/memory/task-memory-cleanup.ts +62 -41
- package/src/memory/tool-executed-events-store.test.ts +1 -1
- package/src/memory/turn-trace-store.test.ts +1 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +16 -15
- package/src/memory/v2/__tests__/harness-compare.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-oracle.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
- package/src/memory/v2/__tests__/sweep-job.test.ts +2 -2
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +38 -0
- package/src/memory/v3-eval/__tests__/eval-tally.test.ts +139 -0
- package/src/memory/v3-eval/eval-packets.ts +197 -12
- package/src/memory/v3-eval/eval-tally.ts +234 -0
- package/src/messaging/provider.ts +10 -0
- package/src/messaging/providers/gmail/adapter.ts +1 -0
- package/src/messaging/providers/gmail/client.ts +14 -0
- package/src/messaging/providers/index.ts +1 -1
- package/src/messaging/providers/slack/send.test.ts +87 -39
- package/src/messaging/providers/slack/send.ts +84 -105
- package/src/notifications/README.md +9 -5
- package/src/notifications/__tests__/deterministic-checks.test.ts +43 -1
- package/src/notifications/adapters/slack.ts +12 -10
- package/src/notifications/approval-card-builder.ts +81 -20
- package/src/notifications/approval-card-data.ts +8 -5
- package/src/notifications/canonical-delivery-recorder.ts +7 -5
- package/src/notifications/conversation-candidates.ts +24 -59
- package/src/notifications/copy-composer.ts +48 -68
- package/src/notifications/deterministic-checks.ts +19 -16
- package/src/notifications/emit-signal.ts +29 -1
- package/src/notifications/trusted-contact-payloads.ts +70 -0
- package/src/oauth/byo-connection.test.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +146 -6
- package/src/oauth/connection-resolver.ts +132 -5
- package/src/oauth/oauth-store.ts +16 -3
- package/src/oauth/scope-utils.ts +21 -0
- package/src/plugin-api/index.ts +9 -4
- package/src/plugin-api/model-profiles.test.ts +123 -0
- package/src/plugin-api/model-profiles.ts +5 -1
- package/src/plugin-api/vision-support.test.ts +149 -0
- package/src/plugin-api/vision-support.ts +78 -0
- package/src/plugins/defaults/compaction/window-manager.ts +45 -64
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +13 -4
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +302 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +103 -0
- package/src/plugins/defaults/image-fallback/package.json +14 -0
- package/src/plugins/defaults/image-fallback/src/caption-cache.ts +49 -0
- package/src/plugins/defaults/image-fallback/src/image-persist.ts +59 -0
- package/src/plugins/defaults/image-fallback/src/vision-caption.ts +120 -0
- package/src/plugins/defaults/index.ts +23 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +14 -1
- package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
- package/src/plugins/external-plugin-loader.ts +47 -6
- package/src/plugins/mtime-cache.ts +772 -0
- package/src/plugins/pipeline.ts +7 -2
- package/src/plugins/registry.ts +16 -5
- package/src/plugins/user-loader.ts +22 -76
- package/src/prompts/persona-resolver.ts +29 -11
- package/src/prompts/system-prompt.ts +1 -1
- package/src/prompts/templates/system-sections.ts +4 -4
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
- package/src/providers/anthropic/client.ts +254 -185
- package/src/providers/call-site-routing.ts +10 -0
- package/src/providers/gemini/client.ts +43 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/inference/connections.ts +6 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/ratelimit.ts +9 -0
- package/src/providers/retry.ts +10 -0
- package/src/providers/together/client.ts +35 -0
- package/src/providers/types.ts +16 -0
- package/src/providers/usage-tracking.ts +7 -0
- package/src/runtime/AGENTS.md +9 -1
- package/src/runtime/__tests__/agent-wake.test.ts +259 -4
- package/src/runtime/__tests__/slack-block-formatting.test.ts +39 -10
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +417 -0
- package/src/runtime/actor-trust-resolver.ts +8 -16
- package/src/runtime/agent-wake.ts +183 -60
- package/src/runtime/channel-reply-delivery.ts +6 -3
- package/src/runtime/guardian-decision-types.ts +3 -22
- package/src/runtime/http-server.ts +1 -15
- package/src/runtime/invite-redemption-service.ts +155 -6
- package/src/runtime/invite-service.ts +113 -62
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +3 -0
- package/src/runtime/routes/__tests__/acp-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +277 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +140 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +26 -7
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +164 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +8 -8
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +1 -3
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +240 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +4 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +143 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes-list.test.ts +4 -0
- package/src/runtime/routes/acp-routes.test.ts +5 -6
- package/src/runtime/routes/attachment-routes.ts +21 -17
- package/src/runtime/routes/browser-routes.ts +19 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -9
- package/src/runtime/routes/channel-verification-routes.ts +12 -1
- package/src/runtime/routes/contact-routes.ts +275 -164
- package/src/runtime/routes/conversation-query-routes.ts +15 -5
- package/src/runtime/routes/conversation-routes.ts +24 -3
- package/src/runtime/routes/conversation-starter-routes.ts +7 -8
- package/src/runtime/routes/guardian-approval-interception.ts +13 -274
- package/src/runtime/routes/inbound-message-handler.ts +20 -15
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +285 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +45 -34
- package/src/runtime/routes/inbound-stages/admission-policy.ts +20 -5
- package/src/runtime/routes/log-export-routes.ts +2 -2
- package/src/runtime/routes/memory-eval-routes.ts +92 -0
- package/src/runtime/routes/memory-item-routes.test.ts +12 -11
- package/src/runtime/routes/migration-routes.ts +51 -40
- package/src/runtime/routes/plugins-routes.ts +164 -8
- package/src/runtime/routes/schedule-routes.ts +1 -0
- package/src/runtime/routes/usage-routes.ts +3 -0
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/slack-block-formatting.ts +46 -48
- package/src/runtime/trust-verdict-consumer.ts +172 -0
- package/src/schedule/scheduler.ts +6 -9
- package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +60 -52
- package/src/tools/ask-question/ask-question-tool.ts +14 -73
- package/src/tools/browser/__tests__/browser-status.test.ts +20 -0
- package/src/tools/browser/browser-execution.ts +16 -4
- package/src/tools/document/document-comment-tool.test.ts +1 -1
- package/src/tools/executor.ts +15 -3
- package/src/tools/host-terminal/host-shell.ts +28 -9
- package/src/tools/memory/register.test.ts +32 -0
- package/src/tools/skills/load.ts +43 -2
- package/src/tools/subagent/spawn.ts +4 -10
- package/src/tools/terminal/shell.ts +16 -5
- package/src/tools/types.ts +1 -0
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/logs-db-path.ts +22 -0
- package/src/util/memory-db-path.ts +23 -0
- package/src/watcher/providers/gmail.ts +7 -2
- package/src/workflows/engine-integration.test.ts +1 -1
- package/src/workflows/engine.test.ts +1 -1
- package/src/workflows/engine.ts +22 -0
- package/src/workflows/fanout-load.test.ts +1 -1
- package/src/workflows/journal-store.test.ts +1 -1
- package/src/workflows/leaf-runner.test.ts +40 -1
- package/src/workflows/leaf-runner.ts +26 -1
- package/src/workspace/git-service.ts +144 -29
- package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +121 -0
- package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +82 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/migrations/runner.ts +32 -2
- package/src/__tests__/access-request-decision.test.ts +0 -375
- package/src/__tests__/guardian-grant-minting.test.ts +0 -607
- package/src/__tests__/plugin-source-watcher.test.ts +0 -302
- package/src/api/events/turn-profile-auto-routed.ts +0 -28
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +0 -107
- package/src/daemon/plugin-source-watcher.ts +0 -278
- package/src/daemon/switch-inference-profile-tool.ts +0 -62
- package/src/memory/guardian-approvals.ts +0 -361
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +0 -66
- package/src/memory/migrations/038-actor-token-records.ts +0 -45
- package/src/memory/migrations/039-actor-refresh-token-records.ts +0 -57
- package/src/memory/migrations/103-complex-migrations.ts +0 -23
- package/src/memory/migrations/113-late-migrations.ts +0 -30
- package/src/memory/migrations/index.ts +0 -301
- package/src/runtime/routes/access-request-decision.ts +0 -297
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -963
- package/src/runtime/routes/channel-guardian-routes.ts +0 -19
- package/src/runtime/routes/guardian-expiry-sweep.ts +0 -132
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-surface mtime cache for user plugins.
|
|
3
|
+
*
|
|
4
|
+
* Instead of caching whole `Plugin` objects, this module caches individual
|
|
5
|
+
* hooks and tools keyed by their source file's mtime. This means:
|
|
6
|
+
*
|
|
7
|
+
* - A changed hook file triggers a re-import of just that hook, not a full
|
|
8
|
+
* plugin rebuild.
|
|
9
|
+
* - The same machinery extends to workspace-driven hooks and tools in the
|
|
10
|
+
* future (PR B), since each surface is cached independently.
|
|
11
|
+
* - Plugins are never "registered" as a unit — we just register their tools
|
|
12
|
+
* and hooks into the global registries, then cache-bust them using mtime
|
|
13
|
+
* on reads.
|
|
14
|
+
*
|
|
15
|
+
* The cache is populated at boot by `loadUserPlugins()` and read on every
|
|
16
|
+
* `getHooksFor` / `getAllTools` call. When a surface file's mtime changes,
|
|
17
|
+
* the next read detects the mismatch, re-imports the file, and swaps the
|
|
18
|
+
* cached entry.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
|
|
24
|
+
import { getConfig } from "../config/loader.js";
|
|
25
|
+
import { registerShutdownHook } from "../daemon/shutdown-registry.js";
|
|
26
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
27
|
+
import type {
|
|
28
|
+
PluginHookFn,
|
|
29
|
+
PluginInitContext,
|
|
30
|
+
PluginShutdownContext,
|
|
31
|
+
} from "../plugin-api/types.js";
|
|
32
|
+
import {
|
|
33
|
+
registerPluginTools,
|
|
34
|
+
unregisterPluginTools,
|
|
35
|
+
} from "../tools/registry.js";
|
|
36
|
+
import { finalizeTool } from "../tools/tool-defaults.js";
|
|
37
|
+
import type { Tool, ToolDefinition } from "../tools/types.js";
|
|
38
|
+
import { getLogger } from "../util/logger.js";
|
|
39
|
+
import { getWorkspaceDir, getWorkspacePluginsDir } from "../util/platform.js";
|
|
40
|
+
import { APP_VERSION } from "../version.js";
|
|
41
|
+
import {
|
|
42
|
+
deriveToolName,
|
|
43
|
+
importDefault,
|
|
44
|
+
listSurfaceDir,
|
|
45
|
+
parsePluginManifest,
|
|
46
|
+
} from "./external-plugin-loader.js";
|
|
47
|
+
|
|
48
|
+
// Re-export for type compat — consumers that import PluginHookFn from
|
|
49
|
+
// the mtime cache module still resolve.
|
|
50
|
+
export type { PluginHookFn } from "./types.js";
|
|
51
|
+
|
|
52
|
+
const log = getLogger("plugin-mtime-cache");
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Import timeout for surface file imports. Set by `populateCacheAtBoot` from
|
|
56
|
+
* the value passed by `loadUserPlugins`, and used by `getUserHooksFor` and
|
|
57
|
+
* `reconcilePluginTools` for runtime re-imports. Defaults to 10s.
|
|
58
|
+
*/
|
|
59
|
+
let importTimeoutMs = 10_000;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cached install-date timestamps per plugin directory, so `scanPlugins`
|
|
63
|
+
* doesn't re-read `install-meta.json` on every turn. Populated on first
|
|
64
|
+
* discovery, cleared on eviction and in test reset. The install date
|
|
65
|
+
* doesn't change during a process lifetime.
|
|
66
|
+
*/
|
|
67
|
+
const installDateCache = new Map<string, number>();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The filename of the provenance sidecar written by the plugin install CLI.
|
|
71
|
+
* We read only the `installedAt` field for ordering — the full `InstallMeta`
|
|
72
|
+
* type lives in `src/cli/lib/install-from-github.ts` and we avoid pulling
|
|
73
|
+
* the CLI dependency graph into the daemon.
|
|
74
|
+
*/
|
|
75
|
+
const INSTALL_META_FILENAME = "install-meta.json";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get a sortable timestamp for a plugin directory, used to order plugins
|
|
79
|
+
* deterministically by their original install date.
|
|
80
|
+
*
|
|
81
|
+
* Resolution order:
|
|
82
|
+
* 1. `install-meta.json` → `installedAt` field (ISO-8601 string → epoch ms)
|
|
83
|
+
* 2. `statSync(pluginDir).birthtimeMs` (directory creation time as fallback)
|
|
84
|
+
* 3. `Infinity` (unknown — sorts after all dated plugins)
|
|
85
|
+
*
|
|
86
|
+
* Results are cached in `installDateCache` so repeated `scanPlugins` calls
|
|
87
|
+
* during a process lifetime don't re-read the sidecar.
|
|
88
|
+
*/
|
|
89
|
+
function getInstallDate(pluginDir: string): number {
|
|
90
|
+
const cached = installDateCache.get(pluginDir);
|
|
91
|
+
if (cached !== undefined) return cached;
|
|
92
|
+
|
|
93
|
+
// Try install-meta.json first.
|
|
94
|
+
const metaPath = join(pluginDir, INSTALL_META_FILENAME);
|
|
95
|
+
if (existsSync(metaPath)) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
98
|
+
if (
|
|
99
|
+
typeof raw === "object" &&
|
|
100
|
+
raw !== null &&
|
|
101
|
+
typeof raw.installedAt === "string"
|
|
102
|
+
) {
|
|
103
|
+
const parsed = Date.parse(raw.installedAt);
|
|
104
|
+
if (!Number.isNaN(parsed)) {
|
|
105
|
+
installDateCache.set(pluginDir, parsed);
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Malformed sidecar — fall through to birthtime.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fall back to directory birthtime. On Linux ext4 and other filesystems
|
|
115
|
+
// that don't support birth time, statSync returns 0 — treat that as
|
|
116
|
+
// unknown so undated plugins sort after dated ones.
|
|
117
|
+
let timestamp = Infinity;
|
|
118
|
+
try {
|
|
119
|
+
const birthtime = statSync(pluginDir).birthtimeMs;
|
|
120
|
+
if (birthtime > 0) {
|
|
121
|
+
timestamp = birthtime;
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
// statSync failed — leave as Infinity.
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
installDateCache.set(pluginDir, timestamp);
|
|
128
|
+
return timestamp;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Cache entries ───────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A cached hook function plus the mtime of its source file. When the on-disk
|
|
135
|
+
* mtime changes, the hook is re-imported and the entry is replaced.
|
|
136
|
+
*/
|
|
137
|
+
interface CachedHook {
|
|
138
|
+
readonly hook: PluginHookFn;
|
|
139
|
+
/** mtimeMs of the source file this hook was imported from. */
|
|
140
|
+
readonly sourceMtime: number;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* A cached tool plus the mtime of its source file. When the on-disk mtime
|
|
145
|
+
* changes, the tool is re-imported, the old tool is unregistered from the
|
|
146
|
+
* global tool registry, and the new one is registered.
|
|
147
|
+
*/
|
|
148
|
+
interface CachedTool {
|
|
149
|
+
readonly tool: Tool;
|
|
150
|
+
/** mtimeMs of the source file this tool was imported from. */
|
|
151
|
+
readonly sourceMtime: number;
|
|
152
|
+
/** The plugin name that owns this tool (for unregister). */
|
|
153
|
+
readonly pluginName: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Internal state ──────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Cached hooks keyed by `${pluginName}/${hookName}`. The key includes the
|
|
160
|
+
* plugin name so hooks from different plugins don't collide.
|
|
161
|
+
*/
|
|
162
|
+
const hookCache = new Map<string, CachedHook>();
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Cached tools keyed by `${pluginName}/${toolName}`. The key includes the
|
|
166
|
+
* plugin name so tools from different plugins don't collide.
|
|
167
|
+
*/
|
|
168
|
+
const toolCache = new Map<string, CachedTool>();
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* In-flight import promises, keyed by file path. Prevents duplicate
|
|
172
|
+
* `import()` calls when multiple readers request the same surface
|
|
173
|
+
* concurrently.
|
|
174
|
+
*/
|
|
175
|
+
const inflight = new Map<string, Promise<unknown>>();
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Plugin directories discovered at boot, in discovery order. Maps directory
|
|
179
|
+
* path to the plugin's scope-stripped manifest name so eviction can find
|
|
180
|
+
* the right cache key prefix without reading the (now-deleted) manifest.
|
|
181
|
+
*/
|
|
182
|
+
const discoveredPluginDirs = new Map<string, string>();
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Plugin directories that have a `.disabled` sentinel and were logged as
|
|
186
|
+
* disabled. Tracked so we only emit the "plugin disabled" log line once
|
|
187
|
+
* per scan cycle (the scan runs on every hook read). Cleared when a plugin
|
|
188
|
+
* transitions back to active or is evicted entirely.
|
|
189
|
+
*/
|
|
190
|
+
const disabledPluginDirs = new Set<string>();
|
|
191
|
+
|
|
192
|
+
// ─── Mtime helpers ───────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the mtimeMs of a file, or 0 if the file doesn't exist or can't be
|
|
196
|
+
* stat'd.
|
|
197
|
+
*/
|
|
198
|
+
function getMtime(filePath: string): number {
|
|
199
|
+
try {
|
|
200
|
+
return statSync(filePath).mtimeMs;
|
|
201
|
+
} catch {
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Hook cache ──────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Cache key for a hook: `${pluginName}/${hookName}`.
|
|
210
|
+
*/
|
|
211
|
+
function hookKey(pluginName: string, hookName: string): string {
|
|
212
|
+
return `${pluginName}/${hookName}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get all hooks for a given event name from user plugins, re-importing
|
|
217
|
+
* any whose source files have changed since the cache was populated.
|
|
218
|
+
*
|
|
219
|
+
* Also scans for newly added plugins and hooks (via directory listing).
|
|
220
|
+
* Deleted plugins/hooks are skipped naturally (their directories/files
|
|
221
|
+
* no longer appear in the listing).
|
|
222
|
+
*/
|
|
223
|
+
export async function getUserHooksFor<TCtx = unknown>(
|
|
224
|
+
hookName: string,
|
|
225
|
+
): Promise<PluginHookFn<TCtx>[]> {
|
|
226
|
+
await scanPlugins();
|
|
227
|
+
|
|
228
|
+
const out: PluginHookFn<TCtx>[] = [];
|
|
229
|
+
|
|
230
|
+
for (const [pluginDir, pluginName] of discoveredPluginDirs) {
|
|
231
|
+
const hooksDir = join(pluginDir, "hooks");
|
|
232
|
+
const surfaceFiles = listSurfaceDir(hooksDir);
|
|
233
|
+
const hookFile = surfaceFiles.find((f) => f.name === hookName);
|
|
234
|
+
if (hookFile === undefined) continue;
|
|
235
|
+
|
|
236
|
+
const key = hookKey(pluginName, hookName);
|
|
237
|
+
const currentMtime = getMtime(hookFile.path);
|
|
238
|
+
|
|
239
|
+
// Cache hit — same mtime.
|
|
240
|
+
const cached = hookCache.get(key);
|
|
241
|
+
if (
|
|
242
|
+
cached !== undefined &&
|
|
243
|
+
cached.sourceMtime === currentMtime &&
|
|
244
|
+
currentMtime > 0
|
|
245
|
+
) {
|
|
246
|
+
out.push(cached.hook as PluginHookFn<TCtx>);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Cache miss — re-import.
|
|
251
|
+
if (currentMtime === 0) {
|
|
252
|
+
// File was deleted — evict cache entry.
|
|
253
|
+
hookCache.delete(key);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const hook = await importWithTimeout<PluginHookFn>(
|
|
259
|
+
hookFile.path,
|
|
260
|
+
importTimeoutMs,
|
|
261
|
+
);
|
|
262
|
+
if (hook === undefined || typeof hook !== "function") {
|
|
263
|
+
log.error(
|
|
264
|
+
{ plugin: pluginName, hook: hookName, path: hookFile.path },
|
|
265
|
+
`hook ${hookName} default export must be a function (got ${typeof hook}) — skipping`,
|
|
266
|
+
);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
hookCache.set(key, { hook, sourceMtime: currentMtime });
|
|
270
|
+
out.push(hook as PluginHookFn<TCtx>);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
log.error(
|
|
273
|
+
{ err, plugin: pluginName, hook: hookName, path: hookFile.path },
|
|
274
|
+
`Failed to import hook ${hookName} from ${hookFile.path}`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── Tool cache ──────────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Cache key for a tool: `${pluginName}/${toolName}`.
|
|
286
|
+
*/
|
|
287
|
+
function toolKey(pluginName: string, toolName: string): string {
|
|
288
|
+
return `${pluginName}/${toolName}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Reconcile the tool cache for a single plugin directory. Re-imports
|
|
293
|
+
* changed tool files, unregisters deleted tools, and registers new ones.
|
|
294
|
+
*
|
|
295
|
+
* Called during `scanPlugins()` so that by the time any consumer reads
|
|
296
|
+
* the tool registry, the cache is fresh.
|
|
297
|
+
*/
|
|
298
|
+
async function reconcilePluginTools(
|
|
299
|
+
pluginDir: string,
|
|
300
|
+
pluginName: string,
|
|
301
|
+
): Promise<void> {
|
|
302
|
+
const toolsDir = join(pluginDir, "tools");
|
|
303
|
+
const surfaceFiles = listSurfaceDir(toolsDir);
|
|
304
|
+
const onDiskNames = new Set<string>();
|
|
305
|
+
|
|
306
|
+
for (const file of surfaceFiles) {
|
|
307
|
+
const toolName = deriveToolName(file.name);
|
|
308
|
+
onDiskNames.add(toolName);
|
|
309
|
+
const key = toolKey(pluginName, toolName);
|
|
310
|
+
const currentMtime = getMtime(file.path);
|
|
311
|
+
|
|
312
|
+
// Cache hit — same mtime.
|
|
313
|
+
const cached = toolCache.get(key);
|
|
314
|
+
if (
|
|
315
|
+
cached !== undefined &&
|
|
316
|
+
cached.sourceMtime === currentMtime &&
|
|
317
|
+
currentMtime > 0
|
|
318
|
+
) {
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Cache miss — re-import.
|
|
323
|
+
if (currentMtime === 0) {
|
|
324
|
+
// File was deleted — will be handled by the eviction loop below.
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const toolSpec = await importWithTimeout<ToolDefinition>(
|
|
330
|
+
file.path,
|
|
331
|
+
importTimeoutMs,
|
|
332
|
+
);
|
|
333
|
+
if (
|
|
334
|
+
toolSpec === undefined ||
|
|
335
|
+
toolSpec === null ||
|
|
336
|
+
typeof toolSpec !== "object"
|
|
337
|
+
) {
|
|
338
|
+
log.error(
|
|
339
|
+
{ plugin: pluginName, tool: toolName, path: file.path },
|
|
340
|
+
`tool default export must be an object — skipping`,
|
|
341
|
+
);
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const tool = finalizeTool(toolSpec, toolName);
|
|
345
|
+
toolCache.set(key, { tool, sourceMtime: currentMtime, pluginName });
|
|
346
|
+
} catch (err) {
|
|
347
|
+
log.error(
|
|
348
|
+
{ err, plugin: pluginName, tool: toolName, path: file.path },
|
|
349
|
+
`Failed to import tool ${toolName} from ${file.path}`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Evict cached tools whose files no longer exist on disk.
|
|
355
|
+
for (const key of toolCache.keys()) {
|
|
356
|
+
const [cachedPluginName, cachedToolName] = key.split("/");
|
|
357
|
+
if (cachedPluginName !== pluginName) continue;
|
|
358
|
+
if (!onDiskNames.has(cachedToolName)) {
|
|
359
|
+
toolCache.delete(key);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get all cached tools from user plugins. Called by the tool registry
|
|
366
|
+
* to supplement the core + default plugin tools.
|
|
367
|
+
*/
|
|
368
|
+
export function getCachedUserTools(): Tool[] {
|
|
369
|
+
return Array.from(toolCache.values()).map((c) => c.tool);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ─── Plugin discovery ────────────────────────────────────────────────────────
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Scan the plugins directory, update the discovered set, and reconcile
|
|
376
|
+
* tools for each plugin. Also evicts cache entries for deleted plugins.
|
|
377
|
+
*
|
|
378
|
+
* This is the "pull" — called on every hook read and at boot. The cost
|
|
379
|
+
* is one `readdirSync` + N `statSync` calls where N = total surface files
|
|
380
|
+
* across all plugins. For a typical workspace with 2-3 plugins, that's
|
|
381
|
+
* ~10-15 stats — sub-millisecond.
|
|
382
|
+
*/
|
|
383
|
+
async function scanPlugins(): Promise<void> {
|
|
384
|
+
const pluginsDir = getWorkspacePluginsDir();
|
|
385
|
+
|
|
386
|
+
if (!existsSync(pluginsDir)) {
|
|
387
|
+
// No plugins directory — evict everything.
|
|
388
|
+
await evictAll();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let entries: string[];
|
|
393
|
+
try {
|
|
394
|
+
entries = readdirSync(pluginsDir);
|
|
395
|
+
} catch {
|
|
396
|
+
log.warn({ pluginsDir }, "scanPlugins: failed to read plugins directory");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const currentDirs = new Map<string, string>();
|
|
401
|
+
|
|
402
|
+
for (const entry of entries) {
|
|
403
|
+
const pluginDir = join(pluginsDir, entry);
|
|
404
|
+
try {
|
|
405
|
+
if (!statSync(pluginDir).isDirectory()) continue;
|
|
406
|
+
} catch {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!existsSync(join(pluginDir, "package.json"))) continue;
|
|
410
|
+
|
|
411
|
+
// Check for the .disabled sentinel. A plugin is disabled when a file
|
|
412
|
+
// named `.disabled` exists inside its plugin directory. Disabled
|
|
413
|
+
// plugins are skipped entirely — no hooks, no tools, no cache entries.
|
|
414
|
+
// If the plugin was previously active, its cache entries are evicted.
|
|
415
|
+
if (existsSync(join(pluginDir, ".disabled"))) {
|
|
416
|
+
const manifest = await parsePluginManifest(pluginDir);
|
|
417
|
+
const pluginName = manifest?.name ?? entry;
|
|
418
|
+
if (discoveredPluginDirs.has(pluginDir)) {
|
|
419
|
+
await evictPlugin(pluginDir, pluginName);
|
|
420
|
+
}
|
|
421
|
+
if (!disabledPluginDirs.has(pluginDir)) {
|
|
422
|
+
log.info(
|
|
423
|
+
{ plugin: pluginName, pluginDir },
|
|
424
|
+
"plugin disabled via .disabled sentinel — skipping",
|
|
425
|
+
);
|
|
426
|
+
disabledPluginDirs.add(pluginDir);
|
|
427
|
+
}
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const manifest = await parsePluginManifest(pluginDir);
|
|
432
|
+
if (manifest === undefined) continue;
|
|
433
|
+
const { name: pluginName } = manifest;
|
|
434
|
+
|
|
435
|
+
currentDirs.set(pluginDir, pluginName);
|
|
436
|
+
disabledPluginDirs.delete(pluginDir);
|
|
437
|
+
|
|
438
|
+
if (!discoveredPluginDirs.has(pluginDir)) {
|
|
439
|
+
log.info({ plugin: pluginName, pluginDir }, "plugin discovered");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Reconcile this plugin's tools (re-imports changed files).
|
|
443
|
+
await reconcilePluginTools(pluginDir, pluginName);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Evict cache entries for deleted plugins.
|
|
447
|
+
for (const [pluginDir, pluginName] of discoveredPluginDirs) {
|
|
448
|
+
if (!currentDirs.has(pluginDir)) {
|
|
449
|
+
await evictPlugin(pluginDir, pluginName);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Update the discovered set, sorted by original install date so
|
|
454
|
+
// hook execution order and tool registration order are deterministic.
|
|
455
|
+
discoveredPluginDirs.clear();
|
|
456
|
+
const sorted = [...currentDirs.entries()].sort(
|
|
457
|
+
([dirA], [dirB]) => getInstallDate(dirA) - getInstallDate(dirB),
|
|
458
|
+
);
|
|
459
|
+
for (const [dir, name] of sorted) {
|
|
460
|
+
discoveredPluginDirs.set(dir, name);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Evict all cache entries for a deleted plugin directory. The plugin name
|
|
466
|
+
* is passed in from the discoveredPluginDirs map (captured when the plugin
|
|
467
|
+
* was last scanned), so we don't need to read the now-deleted manifest.
|
|
468
|
+
*/
|
|
469
|
+
async function evictPlugin(
|
|
470
|
+
pluginDir: string,
|
|
471
|
+
pluginName: string,
|
|
472
|
+
): Promise<void> {
|
|
473
|
+
// Evict hooks.
|
|
474
|
+
const hookPrefix = `${pluginName}/`;
|
|
475
|
+
for (const key of hookCache.keys()) {
|
|
476
|
+
if (key.startsWith(hookPrefix)) {
|
|
477
|
+
hookCache.delete(key);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Evict tools.
|
|
482
|
+
for (const key of toolCache.keys()) {
|
|
483
|
+
if (key.startsWith(hookPrefix)) {
|
|
484
|
+
toolCache.delete(key);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
log.info(
|
|
489
|
+
{ plugin: pluginName, pluginDir },
|
|
490
|
+
"plugin evicted (directory removed)",
|
|
491
|
+
);
|
|
492
|
+
discoveredPluginDirs.delete(pluginDir);
|
|
493
|
+
installDateCache.delete(pluginDir);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Evict all cache entries (when the plugins directory is gone entirely).
|
|
498
|
+
*/
|
|
499
|
+
async function evictAll(): Promise<void> {
|
|
500
|
+
hookCache.clear();
|
|
501
|
+
toolCache.clear();
|
|
502
|
+
discoveredPluginDirs.clear();
|
|
503
|
+
installDateCache.clear();
|
|
504
|
+
disabledPluginDirs.clear();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ─── Import dedup ────────────────────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Import a module's default export with a timeout. If the import doesn't
|
|
511
|
+
* resolve within `timeoutMs`, logs a warning and returns `undefined` so
|
|
512
|
+
* a hanging plugin module doesn't block daemon startup indefinitely.
|
|
513
|
+
*/
|
|
514
|
+
async function importWithTimeout<T>(
|
|
515
|
+
filePath: string,
|
|
516
|
+
timeoutMs: number,
|
|
517
|
+
): Promise<T | undefined> {
|
|
518
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
519
|
+
try {
|
|
520
|
+
const timeoutSentinel = Symbol("import-timeout");
|
|
521
|
+
const importPromise = importWithDedup<T>(filePath);
|
|
522
|
+
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
523
|
+
timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
|
|
524
|
+
});
|
|
525
|
+
const result = await Promise.race([importPromise, timeoutPromise]);
|
|
526
|
+
if (result === timeoutSentinel) {
|
|
527
|
+
importPromise.catch(() => {
|
|
528
|
+
/* swallow — late rejection from abandoned import */
|
|
529
|
+
});
|
|
530
|
+
log.warn(
|
|
531
|
+
{ filePath, timeoutMs },
|
|
532
|
+
`Import timed out after ${timeoutMs}ms — skipping surface`,
|
|
533
|
+
);
|
|
534
|
+
return undefined;
|
|
535
|
+
}
|
|
536
|
+
return result as T;
|
|
537
|
+
} finally {
|
|
538
|
+
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Import a module's default export, deduplicating concurrent imports for
|
|
544
|
+
* the same file path. This prevents two readers from triggering duplicate
|
|
545
|
+
* `import()` calls when they request the same surface simultaneously.
|
|
546
|
+
*
|
|
547
|
+
* Note: Bun caches `import()` by URL within a process, so the dedup is
|
|
548
|
+
* primarily about avoiding redundant async work, not about cache-busting.
|
|
549
|
+
*/
|
|
550
|
+
async function importWithDedup<T>(filePath: string): Promise<T> {
|
|
551
|
+
let promise = inflight.get(filePath);
|
|
552
|
+
if (promise === undefined) {
|
|
553
|
+
promise = importDefault<T>(filePath);
|
|
554
|
+
inflight.set(filePath, promise);
|
|
555
|
+
}
|
|
556
|
+
try {
|
|
557
|
+
return (await promise) as T;
|
|
558
|
+
} finally {
|
|
559
|
+
inflight.delete(filePath);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ─── Boot population ─────────────────────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Plugins that were fully activated at boot (tools registered + init hook
|
|
567
|
+
* run). Used by the shutdown hook to tear down only what was actually
|
|
568
|
+
* brought up.
|
|
569
|
+
*/
|
|
570
|
+
const activatedPlugins: Array<{ name: string }> = [];
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Populate the cache at boot by scanning the plugins directory once,
|
|
574
|
+
* importing all surfaces, registering tools into the tool registry,
|
|
575
|
+
* running `init` hooks, and installing a shutdown hook.
|
|
576
|
+
*
|
|
577
|
+
* This replaces the old `loadExternalPlugin` → `registerPlugin` →
|
|
578
|
+
* `bootstrapPlugins` path for user plugins. Instead of registering whole
|
|
579
|
+
* `Plugin` objects into the plugin registry, we register individual tools
|
|
580
|
+
* into the tool registry and cache hooks by mtime. The `init` and
|
|
581
|
+
* `shutdown` hooks are still run exactly once per boot, preserving the
|
|
582
|
+
* activation lifecycle that plugins rely on.
|
|
583
|
+
*
|
|
584
|
+
* Called by `loadUserPlugins()` during daemon startup.
|
|
585
|
+
*/
|
|
586
|
+
export async function populateCacheAtBoot(
|
|
587
|
+
opts: { importTimeoutMs?: number } = {},
|
|
588
|
+
): Promise<void> {
|
|
589
|
+
if (opts.importTimeoutMs !== undefined) {
|
|
590
|
+
importTimeoutMs = opts.importTimeoutMs;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
await scanPlugins();
|
|
594
|
+
|
|
595
|
+
const shutdownContext: PluginShutdownContext = {
|
|
596
|
+
assistantVersion: APP_VERSION,
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
for (const [pluginDir, pluginName] of discoveredPluginDirs) {
|
|
600
|
+
// Pre-import all hooks so the first turn doesn't pay the import cost.
|
|
601
|
+
const hooksDir = join(pluginDir, "hooks");
|
|
602
|
+
const hookFiles = listSurfaceDir(hooksDir);
|
|
603
|
+
for (const file of hookFiles) {
|
|
604
|
+
const key = hookKey(pluginName, file.name);
|
|
605
|
+
const currentMtime = getMtime(file.path);
|
|
606
|
+
if (currentMtime === 0) continue;
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const hook = await importWithTimeout<PluginHookFn>(
|
|
610
|
+
file.path,
|
|
611
|
+
importTimeoutMs,
|
|
612
|
+
);
|
|
613
|
+
if (hook !== undefined && typeof hook === "function") {
|
|
614
|
+
hookCache.set(key, { hook, sourceMtime: currentMtime });
|
|
615
|
+
}
|
|
616
|
+
} catch (err) {
|
|
617
|
+
log.error(
|
|
618
|
+
{ err, plugin: pluginName, hook: file.name, path: file.path },
|
|
619
|
+
`Failed to pre-import hook ${file.name}`,
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Register user plugin tools into the global tool registry so
|
|
625
|
+
// `getAllTools()` and `getTool()` can find them. Tools were already
|
|
626
|
+
// imported and cached by `reconcilePluginTools` during `scanPlugins`.
|
|
627
|
+
const pluginTools = Array.from(toolCache.values())
|
|
628
|
+
.filter((c) => c.pluginName === pluginName)
|
|
629
|
+
.map((c) => c.tool);
|
|
630
|
+
if (pluginTools.length > 0) {
|
|
631
|
+
try {
|
|
632
|
+
registerPluginTools(pluginName, pluginTools);
|
|
633
|
+
log.info(
|
|
634
|
+
{ plugin: pluginName, count: pluginTools.length },
|
|
635
|
+
"user plugin tools registered",
|
|
636
|
+
);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
log.error(
|
|
639
|
+
{ err, plugin: pluginName },
|
|
640
|
+
`Failed to register tools for user plugin ${pluginName}`,
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Run the `init` hook if present.
|
|
646
|
+
const initHookEntry = hookCache.get(hookKey(pluginName, HOOKS.INIT));
|
|
647
|
+
if (initHookEntry !== undefined) {
|
|
648
|
+
try {
|
|
649
|
+
const initContext: PluginInitContext = {
|
|
650
|
+
config: getConfig().plugins?.[pluginName],
|
|
651
|
+
credentials: {},
|
|
652
|
+
logger: log.child({ plugin: pluginName }),
|
|
653
|
+
pluginStorageDir: ensurePluginStorageDir(pluginName),
|
|
654
|
+
assistantVersion: APP_VERSION,
|
|
655
|
+
};
|
|
656
|
+
await initHookEntry.hook(initContext);
|
|
657
|
+
log.info({ plugin: pluginName }, "user plugin initialized");
|
|
658
|
+
} catch (err) {
|
|
659
|
+
log.error(
|
|
660
|
+
{ err, plugin: pluginName },
|
|
661
|
+
`User plugin ${pluginName} init() failed — continuing`,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
activatedPlugins.push({ name: pluginName });
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Register a single shutdown hook that walks all activated user plugins
|
|
670
|
+
// in reverse order, unregistering tools and running shutdown hooks.
|
|
671
|
+
const shutdownSnapshot = [...activatedPlugins];
|
|
672
|
+
registerShutdownHook("user-plugins", async (reason) => {
|
|
673
|
+
for (let i = shutdownSnapshot.length - 1; i >= 0; i--) {
|
|
674
|
+
const { name } = shutdownSnapshot[i]!;
|
|
675
|
+
|
|
676
|
+
// Unregister tools before running shutdown so onShutdown sees a
|
|
677
|
+
// clean model-visible surface.
|
|
678
|
+
try {
|
|
679
|
+
unregisterPluginTools(name);
|
|
680
|
+
} catch (err) {
|
|
681
|
+
log.warn(
|
|
682
|
+
{ err, plugin: name, reason },
|
|
683
|
+
"user plugin tool unregister failed (continuing)",
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Run the `shutdown` hook if present.
|
|
688
|
+
const shutdownHookEntry = hookCache.get(hookKey(name, HOOKS.SHUTDOWN));
|
|
689
|
+
if (shutdownHookEntry !== undefined) {
|
|
690
|
+
try {
|
|
691
|
+
await shutdownHookEntry.hook(shutdownContext);
|
|
692
|
+
} catch (err) {
|
|
693
|
+
log.warn(
|
|
694
|
+
{ err, plugin: name, reason },
|
|
695
|
+
"user plugin shutdown hook failed (continuing)",
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Ensure `<workspaceDir>/plugins-data/<name>/` exists and return its path.
|
|
705
|
+
*/
|
|
706
|
+
function ensurePluginStorageDir(pluginName: string): string {
|
|
707
|
+
const dir = join(getWorkspaceDir(), "plugins-data", pluginName);
|
|
708
|
+
mkdirSync(dir, { recursive: true });
|
|
709
|
+
return dir;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ─── Test hooks ──────────────────────────────────────────────────────────────
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Clear all caches. Test-only.
|
|
716
|
+
*/
|
|
717
|
+
export function resetPluginCacheForTests(): void {
|
|
718
|
+
const isTest =
|
|
719
|
+
process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
720
|
+
if (!isTest) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
"resetPluginCacheForTests may only be called in test environments",
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
hookCache.clear();
|
|
726
|
+
toolCache.clear();
|
|
727
|
+
inflight.clear();
|
|
728
|
+
discoveredPluginDirs.clear();
|
|
729
|
+
installDateCache.clear();
|
|
730
|
+
activatedPlugins.length = 0;
|
|
731
|
+
disabledPluginDirs.clear();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Test-only: inspect the hook cache.
|
|
736
|
+
*/
|
|
737
|
+
export function _inspectHookCacheForTests(): Array<{
|
|
738
|
+
key: string;
|
|
739
|
+
sourceMtime: number;
|
|
740
|
+
}> {
|
|
741
|
+
const isTest =
|
|
742
|
+
process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
743
|
+
if (!isTest) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
"_inspectHookCacheForTests may only be called in test environments",
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return Array.from(hookCache.entries()).map(([key, c]) => ({
|
|
749
|
+
key,
|
|
750
|
+
sourceMtime: c.sourceMtime,
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Test-only: inspect the tool cache.
|
|
756
|
+
*/
|
|
757
|
+
export function _inspectToolCacheForTests(): Array<{
|
|
758
|
+
key: string;
|
|
759
|
+
sourceMtime: number;
|
|
760
|
+
}> {
|
|
761
|
+
const isTest =
|
|
762
|
+
process.env.BUN_TEST === "1" || process.env.NODE_ENV === "test";
|
|
763
|
+
if (!isTest) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
"_inspectToolCacheForTests may only be called in test environments",
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return Array.from(toolCache.entries()).map(([key, c]) => ({
|
|
769
|
+
key,
|
|
770
|
+
sourceMtime: c.sourceMtime,
|
|
771
|
+
}));
|
|
772
|
+
}
|