@vellumai/assistant 0.8.3 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -94,6 +94,8 @@ mock.module("../pre-first-message-gate.js", () => ({
|
|
|
94
94
|
|
|
95
95
|
// Import after mocks are in place.
|
|
96
96
|
const { runBackgroundJob } = await import("../background-job-runner.js");
|
|
97
|
+
const { bufferIfDeferred, resetDeferredForTest } =
|
|
98
|
+
await import("../../notifications/deferred-emit.js");
|
|
97
99
|
|
|
98
100
|
// ── Shared fixtures ──────────────────────────────────────────────────
|
|
99
101
|
|
|
@@ -121,6 +123,7 @@ beforeEach(() => {
|
|
|
121
123
|
processMessageCalls.length = 0;
|
|
122
124
|
emitCalls.length = 0;
|
|
123
125
|
addMessageCalls.length = 0;
|
|
126
|
+
resetDeferredForTest();
|
|
124
127
|
preFirstMessageGateOpen = true;
|
|
125
128
|
processMessageImpl = async () => ({ messageId: "msg-1" });
|
|
126
129
|
emitImpl = async () => ({
|
|
@@ -354,4 +357,129 @@ describe("runBackgroundJob", () => {
|
|
|
354
357
|
expect(processMessageCalls).toHaveLength(1);
|
|
355
358
|
});
|
|
356
359
|
});
|
|
360
|
+
|
|
361
|
+
describe("deferNotifications", () => {
|
|
362
|
+
function buildSkillNotificationParams(message: string) {
|
|
363
|
+
return {
|
|
364
|
+
sourceEventName: "assistant.share",
|
|
365
|
+
sourceChannel: "assistant_tool" as const,
|
|
366
|
+
sourceContextId: "skill-ctx",
|
|
367
|
+
contextPayload: { requestedMessage: message },
|
|
368
|
+
attentionHints: {
|
|
369
|
+
requiresAction: false,
|
|
370
|
+
urgency: "low" as const,
|
|
371
|
+
isAsyncBackground: false,
|
|
372
|
+
visibleInSourceNow: false,
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
test("success path commits buffered in-band notifications", async () => {
|
|
378
|
+
processMessageImpl = async () => {
|
|
379
|
+
// Stand in for the IPC route's bufferIfDeferred call when the model
|
|
380
|
+
// invokes `notifications send` mid-turn.
|
|
381
|
+
const buffered = bufferIfDeferred(
|
|
382
|
+
STUB_CONVERSATION_ID,
|
|
383
|
+
buildSkillNotificationParams("all green"),
|
|
384
|
+
);
|
|
385
|
+
expect(buffered).not.toBeNull();
|
|
386
|
+
expect(buffered!.dispatched).toBe(false);
|
|
387
|
+
return { messageId: "msg-success" };
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const result = await runBackgroundJob(
|
|
391
|
+
baseOpts({ deferNotifications: true }),
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
expect(result.ok).toBe(true);
|
|
395
|
+
// Commit flushed the buffered notification through emitNotificationSignal.
|
|
396
|
+
const successEmits = emitCalls.filter(
|
|
397
|
+
(e) => e.sourceEventName !== "activity.failed",
|
|
398
|
+
);
|
|
399
|
+
expect(successEmits).toHaveLength(1);
|
|
400
|
+
expect(successEmits[0].sourceEventName).toBe("assistant.share");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Regression for the PR #31216 Codex P1 finding: a heartbeat that calls
|
|
404
|
+
// the notifications skill and then times out must not leave a "success"
|
|
405
|
+
// notification standing alongside the runner's `activity.failed` emit.
|
|
406
|
+
test("timeout drops buffered in-band notifications; only activity.failed emits", async () => {
|
|
407
|
+
processMessageImpl = () => {
|
|
408
|
+
bufferIfDeferred(
|
|
409
|
+
STUB_CONVERSATION_ID,
|
|
410
|
+
buildSkillNotificationParams("premature success"),
|
|
411
|
+
);
|
|
412
|
+
return new Promise(() => {});
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const result = await runBackgroundJob(
|
|
416
|
+
baseOpts({ deferNotifications: true, timeoutMs: 30 }),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
expect(result.ok).toBe(false);
|
|
420
|
+
expect(result.errorKind).toBe("timeout");
|
|
421
|
+
// Only the runner's failure signal makes it out — the buffered
|
|
422
|
+
// "success" notification is discarded.
|
|
423
|
+
expect(emitCalls).toHaveLength(1);
|
|
424
|
+
expect(emitCalls[0].sourceEventName).toBe("activity.failed");
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("thrown exception also drops buffered notifications", async () => {
|
|
428
|
+
processMessageImpl = async () => {
|
|
429
|
+
bufferIfDeferred(
|
|
430
|
+
STUB_CONVERSATION_ID,
|
|
431
|
+
buildSkillNotificationParams("doomed"),
|
|
432
|
+
);
|
|
433
|
+
throw new Error("kaboom");
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const result = await runBackgroundJob(
|
|
437
|
+
baseOpts({ deferNotifications: true }),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(result.ok).toBe(false);
|
|
441
|
+
expect(emitCalls).toHaveLength(1);
|
|
442
|
+
expect(emitCalls[0].sourceEventName).toBe("activity.failed");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Regression: after timeout, `processMessage` keeps running and may
|
|
446
|
+
// emit a late skill call. The tombstone must swallow it instead of
|
|
447
|
+
// letting it bypass the buffer and reach the dispatch pipeline.
|
|
448
|
+
test("late skill call after timeout is swallowed by the tombstone", async () => {
|
|
449
|
+
processMessageImpl = () => new Promise(() => {});
|
|
450
|
+
|
|
451
|
+
const result = await runBackgroundJob(
|
|
452
|
+
baseOpts({ deferNotifications: true, timeoutMs: 20 }),
|
|
453
|
+
);
|
|
454
|
+
expect(result.ok).toBe(false);
|
|
455
|
+
|
|
456
|
+
const late = bufferIfDeferred(
|
|
457
|
+
STUB_CONVERSATION_ID,
|
|
458
|
+
buildSkillNotificationParams("post-timeout"),
|
|
459
|
+
);
|
|
460
|
+
expect(late).not.toBeNull();
|
|
461
|
+
expect(late!.dispatched).toBe(false);
|
|
462
|
+
expect(late!.reason).toMatch(/did not complete/);
|
|
463
|
+
|
|
464
|
+
// Only the runner's failure signal made it out.
|
|
465
|
+
expect(emitCalls).toHaveLength(1);
|
|
466
|
+
expect(emitCalls[0].sourceEventName).toBe("activity.failed");
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("without deferNotifications, bufferIfDeferred is a no-op", async () => {
|
|
470
|
+
processMessageImpl = async () => {
|
|
471
|
+
const buffered = bufferIfDeferred(
|
|
472
|
+
STUB_CONVERSATION_ID,
|
|
473
|
+
buildSkillNotificationParams("immediate"),
|
|
474
|
+
);
|
|
475
|
+
// Buffer was never armed, so the call returns null and the IPC
|
|
476
|
+
// handler would emit directly.
|
|
477
|
+
expect(buffered).toBeNull();
|
|
478
|
+
return { messageId: "msg-success" };
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const result = await runBackgroundJob(baseOpts());
|
|
482
|
+
expect(result.ok).toBe(true);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
357
485
|
});
|
|
@@ -68,9 +68,6 @@ import { getLogger } from "../util/logger.js";
|
|
|
68
68
|
|
|
69
69
|
const log = getLogger("agent-wake");
|
|
70
70
|
|
|
71
|
-
/** Number of messages injected for the wake hint (user + assistant + user). */
|
|
72
|
-
const WAKE_HINT_MESSAGE_COUNT = 3;
|
|
73
|
-
|
|
74
71
|
/** Static preamble user message — no dynamic content, injection-safe. */
|
|
75
72
|
const WAKE_PREAMBLE =
|
|
76
73
|
"[system] The following assistant message comes from an external system.";
|
|
@@ -196,6 +193,51 @@ export interface WakeOptions {
|
|
|
196
193
|
* tune the model/profile and observability bucket independently.
|
|
197
194
|
*/
|
|
198
195
|
callSite?: LLMCallSite;
|
|
196
|
+
/**
|
|
197
|
+
* Role to use for the injected hint message. Defaults to `"assistant"` so
|
|
198
|
+
* the hint is sandwiched between two static user bookends — the canonical
|
|
199
|
+
* anti-injection pattern for hints that may carry text from an external
|
|
200
|
+
* source. Trusted internal callers (e.g. fork-based memory retrospectives)
|
|
201
|
+
* can pass `"user"` to inject a single user-role message containing the
|
|
202
|
+
* hint directly, which reads more naturally as an instruction from the
|
|
203
|
+
* user/system rather than a self-directed assistant note.
|
|
204
|
+
*/
|
|
205
|
+
hintRole?: "assistant" | "user";
|
|
206
|
+
/**
|
|
207
|
+
* Documented intent: this wake must not trigger auto-threshold compaction.
|
|
208
|
+
*
|
|
209
|
+
* Today this is automatically satisfied because the wake invokes
|
|
210
|
+
* `target.agentLoop.run()` directly, bypassing the daemon orchestrator
|
|
211
|
+
* (`conversation-agent-loop.ts`) where the compaction pipeline lives. The
|
|
212
|
+
* flag is recorded in the wake's structured log line so operators can
|
|
213
|
+
* verify the contract holds across refactors. If compaction is ever moved
|
|
214
|
+
* into `AgentLoop.run` or invoked from the wake path, callers that pass
|
|
215
|
+
* `true` here MUST be updated to suppress it; callers that pass `false`
|
|
216
|
+
* (or omit it) MUST tolerate compaction firing.
|
|
217
|
+
*
|
|
218
|
+
* Used by fork-based memory retrospectives: the wake operates on a
|
|
219
|
+
* freshly-forked conversation that may already be near (or past) the
|
|
220
|
+
* source's auto-threshold, but the goal is to operate on that exact
|
|
221
|
+
* context — running a compaction LLM call before the wake's own first
|
|
222
|
+
* call would waste tokens and defeat prompt-cache reuse.
|
|
223
|
+
*/
|
|
224
|
+
suppressAutoCompaction?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Skip injection of the hint sandwich entirely. Used when the caller has
|
|
227
|
+
* already persisted the instruction as a real message in the conversation
|
|
228
|
+
* (e.g. fork-based memory retrospectives that append a user message to the
|
|
229
|
+
* forked conversation before waking). When `true`, `hint` is ignored.
|
|
230
|
+
*/
|
|
231
|
+
skipHintInjection?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Skip injection of the "Conversation Woke" `ui_surface` card into the
|
|
234
|
+
* first assistant tail message and the corresponding live
|
|
235
|
+
* `onWakeProducedOutput` broadcast. Default false (existing behavior).
|
|
236
|
+
* Used by callers whose conversation context already makes it obvious
|
|
237
|
+
* that the agent's output came from a wake (e.g. fork-based memory
|
|
238
|
+
* retrospectives whose conversation title already says "(Retrospective)").
|
|
239
|
+
*/
|
|
240
|
+
suppressWakeSurface?: boolean;
|
|
199
241
|
}
|
|
200
242
|
|
|
201
243
|
/**
|
|
@@ -224,13 +266,18 @@ export interface WakeResult {
|
|
|
224
266
|
*/
|
|
225
267
|
export interface WakeDeps {
|
|
226
268
|
/**
|
|
227
|
-
* Resolve the wake target for a
|
|
269
|
+
* Resolve the wake target for a wake invocation.
|
|
228
270
|
* Returns `null` if the conversation doesn't exist, `"archived"` if it
|
|
229
271
|
* exists but is archived, or a `WakeTarget` to proceed with the wake.
|
|
272
|
+
*
|
|
273
|
+
* Receives the full {@link WakeOptions} so the default resolver can
|
|
274
|
+
* thread `trustContext` into `getOrCreateConversation`. Without that
|
|
275
|
+
* threading, the conversation hydrates with `trustContext === undefined`
|
|
276
|
+
* and `loadFromDb` fail-closes to `trustClass: "unknown"`, which filters
|
|
277
|
+
* out every guardian-provenance message — fatal for fork-based memory
|
|
278
|
+
* retrospectives.
|
|
230
279
|
*/
|
|
231
|
-
resolveTarget: (
|
|
232
|
-
conversationId: string,
|
|
233
|
-
) => Promise<WakeTarget | null | "archived">;
|
|
280
|
+
resolveTarget: (opts: WakeOptions) => Promise<WakeTarget | null | "archived">;
|
|
234
281
|
/** Timestamp source (for deterministic tests). */
|
|
235
282
|
now?: () => number;
|
|
236
283
|
}
|
|
@@ -242,8 +289,9 @@ export interface WakeDeps {
|
|
|
242
289
|
// `getOrCreateConversation`, and `conversationToWakeTarget`.
|
|
243
290
|
|
|
244
291
|
async function defaultResolveTarget(
|
|
245
|
-
|
|
292
|
+
opts: WakeOptions,
|
|
246
293
|
): Promise<WakeTarget | null | "archived"> {
|
|
294
|
+
const { conversationId } = opts;
|
|
247
295
|
// Lazy-import daemon modules to avoid pulling heavyweight transitive
|
|
248
296
|
// deps (conversation store → config/loader → provider catalogs) at
|
|
249
297
|
// module-evaluation time. Callers that only import agent-wake for
|
|
@@ -264,7 +312,15 @@ async function defaultResolveTarget(
|
|
|
264
312
|
);
|
|
265
313
|
return "archived";
|
|
266
314
|
}
|
|
267
|
-
|
|
315
|
+
// Thread trustContext through to getOrCreateConversation so the
|
|
316
|
+
// hydration path applies setTrustContext + ensureActorScopedHistory
|
|
317
|
+
// (conversation-store.ts:281-289) BEFORE the agent loop's per-turn
|
|
318
|
+
// snapshot reads. Without this, fork-based memory retrospectives see
|
|
319
|
+
// an empty history because loadFromDb ran with trustClass="unknown"
|
|
320
|
+
// and filtered out every guardian-provenance message.
|
|
321
|
+
const conversation = await getOrCreateConversation(conversationId, {
|
|
322
|
+
trustContext: opts.trustContext,
|
|
323
|
+
});
|
|
268
324
|
return conversationToWakeTarget(conversation);
|
|
269
325
|
} catch (err) {
|
|
270
326
|
log.warn(
|
|
@@ -376,6 +432,7 @@ function buildWakeTurnContext(
|
|
|
376
432
|
*/
|
|
377
433
|
function inspectWakeOutput(
|
|
378
434
|
baselineLength: number,
|
|
435
|
+
hintMessageCount: number,
|
|
379
436
|
updatedHistory: Message[],
|
|
380
437
|
): {
|
|
381
438
|
tailMessages: Message[];
|
|
@@ -383,10 +440,10 @@ function inspectWakeOutput(
|
|
|
383
440
|
toolUseNames: string[];
|
|
384
441
|
} {
|
|
385
442
|
// The agent loop appends messages onto the history it was given. We
|
|
386
|
-
// injected
|
|
387
|
-
//
|
|
388
|
-
// the run.
|
|
389
|
-
const firstAssistantIndex = baselineLength +
|
|
443
|
+
// injected `hintMessageCount` hint messages (0, 1, or 3 depending on
|
|
444
|
+
// hint mode), so anything at index >= baselineLength + hintMessageCount
|
|
445
|
+
// came from the run.
|
|
446
|
+
const firstAssistantIndex = baselineLength + hintMessageCount;
|
|
390
447
|
if (updatedHistory.length <= firstAssistantIndex) {
|
|
391
448
|
return { tailMessages: [], hasVisibleText: false, toolUseNames: [] };
|
|
392
449
|
}
|
|
@@ -436,7 +493,7 @@ export async function wakeAgentForOpportunity(
|
|
|
436
493
|
const startedAt = nowFn();
|
|
437
494
|
|
|
438
495
|
return runSingleFlight(conversationId, async () => {
|
|
439
|
-
const resolved = await resolveTarget(
|
|
496
|
+
const resolved = await resolveTarget(opts);
|
|
440
497
|
if (resolved === "archived") {
|
|
441
498
|
log.info(
|
|
442
499
|
{ conversationId, source },
|
|
@@ -505,28 +562,45 @@ export async function wakeAgentForOpportunity(
|
|
|
505
562
|
// tail-slice math would skip every message.
|
|
506
563
|
const baselineLength = baseline.length;
|
|
507
564
|
const wakeTurnContext = buildWakeTurnContext(opts, diskPressureDecision);
|
|
508
|
-
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
565
|
+
// Build the hint injection. Three modes:
|
|
566
|
+
// - `skipHintInjection`: caller has already persisted an instruction
|
|
567
|
+
// message into the conversation history (typical for fork-based
|
|
568
|
+
// memory retrospectives that append a user message before waking).
|
|
569
|
+
// - `hintRole === "user"`: single user-role message containing the
|
|
570
|
+
// hint directly. Used by trusted internal callers where the hint
|
|
571
|
+
// reads naturally as an instruction.
|
|
572
|
+
// - default (`hintRole === "assistant"`): sandwich the hint as an
|
|
573
|
+
// assistant message between two hardcoded user bookends. The
|
|
574
|
+
// assistant role defangs prompt injection (LLMs don't follow
|
|
575
|
+
// instructions in their own prior output) and the trailing user
|
|
576
|
+
// message satisfies providers that reject assistant prefill.
|
|
577
|
+
const hintRole = opts.hintRole ?? "assistant";
|
|
578
|
+
const wakeMessages: Message[] = opts.skipHintInjection
|
|
579
|
+
? []
|
|
580
|
+
: hintRole === "user"
|
|
581
|
+
? [
|
|
582
|
+
{
|
|
583
|
+
role: "user",
|
|
584
|
+
content: [{ type: "text", text: hint }],
|
|
585
|
+
},
|
|
586
|
+
]
|
|
587
|
+
: [
|
|
588
|
+
{
|
|
589
|
+
role: "user",
|
|
590
|
+
content: [{ type: "text", text: WAKE_PREAMBLE }],
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
role: "assistant",
|
|
594
|
+
content: [
|
|
595
|
+
{ type: "text", text: `[opportunity:${source}] ${hint}` },
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
role: "user",
|
|
600
|
+
content: [{ type: "text", text: WAKE_POSTAMBLE }],
|
|
601
|
+
},
|
|
602
|
+
];
|
|
603
|
+
const wakeHintMessageCount = wakeMessages.length;
|
|
530
604
|
const runInput: Message[] = [...baseline, ...wakeMessages];
|
|
531
605
|
|
|
532
606
|
// Event handling runs in two modes. While `mode === "buffering"`,
|
|
@@ -663,26 +737,28 @@ export async function wakeAgentForOpportunity(
|
|
|
663
737
|
const goLive = (currentHistory: Message[]): void => {
|
|
664
738
|
if (mode === "live") return;
|
|
665
739
|
if (!surfaceInjected) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
firstAssistant.content
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
data: {
|
|
740
|
+
if (!opts.suppressWakeSurface) {
|
|
741
|
+
const tailStart = baselineLength + wakeHintMessageCount;
|
|
742
|
+
const tail = currentHistory.slice(tailStart);
|
|
743
|
+
const firstAssistant = tail.find((m) => m.role === "assistant");
|
|
744
|
+
if (firstAssistant && Array.isArray(firstAssistant.content)) {
|
|
745
|
+
firstAssistant.content.unshift({
|
|
746
|
+
type: "ui_surface",
|
|
747
|
+
surfaceId: wakeSurfaceId,
|
|
748
|
+
surfaceType: "card",
|
|
676
749
|
title: "Conversation Woke",
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
750
|
+
data: {
|
|
751
|
+
title: "Conversation Woke",
|
|
752
|
+
body: hint,
|
|
753
|
+
metadata: [{ label: "Source", value: source }],
|
|
754
|
+
},
|
|
755
|
+
display: "inline",
|
|
756
|
+
} as never);
|
|
757
|
+
}
|
|
682
758
|
}
|
|
683
759
|
surfaceInjected = true;
|
|
684
760
|
}
|
|
685
|
-
if (target.onWakeProducedOutput) {
|
|
761
|
+
if (!opts.suppressWakeSurface && target.onWakeProducedOutput) {
|
|
686
762
|
try {
|
|
687
763
|
target.onWakeProducedOutput(source, hint, wakeSurfaceId);
|
|
688
764
|
} catch (err) {
|
|
@@ -721,8 +797,7 @@ export async function wakeAgentForOpportunity(
|
|
|
721
797
|
const flushPendingTail = async (
|
|
722
798
|
currentHistory: Message[],
|
|
723
799
|
): Promise<void> => {
|
|
724
|
-
const start =
|
|
725
|
-
baselineLength + WAKE_HINT_MESSAGE_COUNT + persistedTailIndex;
|
|
800
|
+
const start = baselineLength + wakeHintMessageCount + persistedTailIndex;
|
|
726
801
|
if (start >= currentHistory.length) return;
|
|
727
802
|
const newMessages = currentHistory.slice(start);
|
|
728
803
|
for (const msg of newMessages) {
|
|
@@ -825,7 +900,11 @@ export async function wakeAgentForOpportunity(
|
|
|
825
900
|
tailMessages,
|
|
826
901
|
hasVisibleText,
|
|
827
902
|
toolUseNames: names,
|
|
828
|
-
} = inspectWakeOutput(
|
|
903
|
+
} = inspectWakeOutput(
|
|
904
|
+
baselineLength,
|
|
905
|
+
wakeHintMessageCount,
|
|
906
|
+
updatedHistory,
|
|
907
|
+
);
|
|
829
908
|
toolUseNames = names;
|
|
830
909
|
producedToolCalls = names.length > 0;
|
|
831
910
|
const producedOutput = producedToolCalls || hasVisibleText;
|
|
@@ -904,9 +983,19 @@ export async function wakeAgentForOpportunity(
|
|
|
904
983
|
}
|
|
905
984
|
|
|
906
985
|
const durationMs = nowFn() - startedAt;
|
|
986
|
+
const suppressAutoCompaction = opts.suppressAutoCompaction === true;
|
|
987
|
+
const suppressWakeSurface = opts.suppressWakeSurface === true;
|
|
907
988
|
if (runError) {
|
|
908
989
|
log.error(
|
|
909
|
-
{
|
|
990
|
+
{
|
|
991
|
+
conversationId,
|
|
992
|
+
source,
|
|
993
|
+
durationMs,
|
|
994
|
+
suppressAutoCompaction,
|
|
995
|
+
suppressWakeSurface,
|
|
996
|
+
hintRole,
|
|
997
|
+
err: runError,
|
|
998
|
+
},
|
|
910
999
|
"agent-wake: agent loop threw; treating as no-op",
|
|
911
1000
|
);
|
|
912
1001
|
} else if (tailMessageCount === 0) {
|
|
@@ -915,6 +1004,9 @@ export async function wakeAgentForOpportunity(
|
|
|
915
1004
|
source,
|
|
916
1005
|
conversationId,
|
|
917
1006
|
durationMs,
|
|
1007
|
+
suppressAutoCompaction,
|
|
1008
|
+
suppressWakeSurface,
|
|
1009
|
+
hintRole,
|
|
918
1010
|
producedToolCalls: false,
|
|
919
1011
|
toolNamesCalled: [],
|
|
920
1012
|
},
|
|
@@ -926,6 +1018,9 @@ export async function wakeAgentForOpportunity(
|
|
|
926
1018
|
source,
|
|
927
1019
|
conversationId,
|
|
928
1020
|
durationMs,
|
|
1021
|
+
suppressAutoCompaction,
|
|
1022
|
+
suppressWakeSurface,
|
|
1023
|
+
hintRole,
|
|
929
1024
|
producedToolCalls,
|
|
930
1025
|
toolNamesCalled: toolUseNames,
|
|
931
1026
|
tailMessageCount,
|
|
@@ -153,6 +153,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
153
153
|
{ endpoint: "conversations/undo", scopes: ["chat.write"] },
|
|
154
154
|
{ endpoint: "conversations/regenerate", scopes: ["chat.write"] },
|
|
155
155
|
{ endpoint: "conversations/attention", scopes: ["chat.read"] },
|
|
156
|
+
{ endpoint: "conversations/slack-channel/resolve", scopes: ["chat.read"] },
|
|
156
157
|
{ endpoint: "conversations/seen", scopes: ["chat.write"] },
|
|
157
158
|
{ endpoint: "conversations/unread", scopes: ["chat.write"] },
|
|
158
159
|
{ endpoint: "conversations/import", scopes: ["chat.write"] },
|
|
@@ -451,9 +452,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
451
452
|
{ endpoint: "bookmarks:POST", scopes: ["chat.write"] },
|
|
452
453
|
{ endpoint: "bookmarks/by-message:DELETE", scopes: ["chat.write"] },
|
|
453
454
|
|
|
454
|
-
// Interfaces
|
|
455
|
-
{ endpoint: "interfaces", scopes: ["settings.read"] },
|
|
456
|
-
|
|
457
455
|
// Skills
|
|
458
456
|
{ endpoint: "skills:GET", scopes: ["settings.read"] },
|
|
459
457
|
{ endpoint: "skills:POST", scopes: ["settings.write"] },
|
|
@@ -471,6 +469,8 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
471
469
|
{ endpoint: "memory/v2/list-concept-pages:POST", scopes: ["settings.read"] },
|
|
472
470
|
{ endpoint: "memory/v2/reembed-skills:POST", scopes: ["settings.write"] },
|
|
473
471
|
{ endpoint: "memory/v2/concept-frequency:POST", scopes: ["settings.read"] },
|
|
472
|
+
{ endpoint: "memory/v2/ema-scores:POST", scopes: ["settings.read"] },
|
|
473
|
+
{ endpoint: "memory/v2/simulate-router:POST", scopes: ["settings.read"] },
|
|
474
474
|
|
|
475
475
|
// Trust rule listing
|
|
476
476
|
{ endpoint: "trust-rules/manage:GET", scopes: ["settings.read"] },
|
|
@@ -992,6 +992,10 @@ registerPolicy("conversations/cli/export", {
|
|
|
992
992
|
requiredScopes: ["settings.read"],
|
|
993
993
|
allowedPrincipalTypes: ["local"],
|
|
994
994
|
});
|
|
995
|
+
registerPolicy("conversations/cli/slack/detach", {
|
|
996
|
+
requiredScopes: ["settings.write"],
|
|
997
|
+
allowedPrincipalTypes: ["local"],
|
|
998
|
+
});
|
|
995
999
|
// `conversations/cli/clear` wipes every conversation + message + vector
|
|
996
1000
|
// collection. Elevated to settings.write and locked to local callers,
|
|
997
1001
|
// mirroring the `conversations/clear-all` and `conversations/wipe` gates.
|
|
@@ -24,6 +24,11 @@ import type { TrustContext } from "../daemon/trust-context.js";
|
|
|
24
24
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
25
25
|
import { addMessage } from "../memory/conversation-crud.js";
|
|
26
26
|
import type { TitleOrigin } from "../memory/conversation-title-service.js";
|
|
27
|
+
import {
|
|
28
|
+
commitDeferredConversation,
|
|
29
|
+
discardDeferredConversation,
|
|
30
|
+
registerDeferredConversation,
|
|
31
|
+
} from "../notifications/deferred-emit.js";
|
|
27
32
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
28
33
|
import type { AttentionHints } from "../notifications/signal.js";
|
|
29
34
|
import { getLogger } from "../util/logger.js";
|
|
@@ -121,6 +126,11 @@ export interface RunBackgroundJobOptions {
|
|
|
121
126
|
* the `assistant` role and cannot override the action prompt.
|
|
122
127
|
*/
|
|
123
128
|
assistantSandwich?: { preamble: string; content: string; postamble: string };
|
|
129
|
+
/**
|
|
130
|
+
* Buffer in-band `notifications send` calls and only flush them after the
|
|
131
|
+
* run completes successfully. See `notifications/deferred-emit.ts`.
|
|
132
|
+
*/
|
|
133
|
+
deferNotifications?: boolean;
|
|
124
134
|
}
|
|
125
135
|
|
|
126
136
|
export interface RunBackgroundJobResult {
|
|
@@ -205,6 +215,10 @@ export async function runBackgroundJob(
|
|
|
205
215
|
...(opts.scheduleJobId ? { scheduleJobId: opts.scheduleJobId } : {}),
|
|
206
216
|
});
|
|
207
217
|
|
|
218
|
+
if (opts.deferNotifications) {
|
|
219
|
+
registerDeferredConversation(conversation.id);
|
|
220
|
+
}
|
|
221
|
+
|
|
208
222
|
// Fire the sidebar-creation callback synchronously after bootstrap so
|
|
209
223
|
// connected clients (macOS sidebar, etc.) see the conversation appear
|
|
210
224
|
// immediately rather than after `processMessage` returns. Wrapped so a
|
|
@@ -273,6 +287,14 @@ export async function runBackgroundJob(
|
|
|
273
287
|
});
|
|
274
288
|
|
|
275
289
|
await Promise.race([work, timeout]);
|
|
290
|
+
// Symmetric with the `work.catch` above: once `work` has won the race,
|
|
291
|
+
// the orphan timeout promise can still reject during the await below
|
|
292
|
+
// (commitDeferredConversation). Swallow so it doesn't surface as an
|
|
293
|
+
// unhandled rejection that Bun can use to terminate the process.
|
|
294
|
+
timeout.catch(() => {});
|
|
295
|
+
if (opts.deferNotifications) {
|
|
296
|
+
await commitDeferredConversation(conversation.id);
|
|
297
|
+
}
|
|
276
298
|
return { conversationId: conversation.id, ok: true };
|
|
277
299
|
} catch (err) {
|
|
278
300
|
const errorKind = classifyError(err);
|
|
@@ -281,6 +303,10 @@ export async function runBackgroundJob(
|
|
|
281
303
|
// so the structured failure result still flows to the caller.
|
|
282
304
|
const conversationId = conversation?.id ?? "";
|
|
283
305
|
|
|
306
|
+
if (opts.deferNotifications && conversationId) {
|
|
307
|
+
discardDeferredConversation(conversationId);
|
|
308
|
+
}
|
|
309
|
+
|
|
284
310
|
log.error(
|
|
285
311
|
{
|
|
286
312
|
err: error.message,
|