@vellumai/assistant 0.8.1 → 0.8.2
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 +2 -7
- package/Dockerfile +75 -1
- package/bun.lock +11 -1
- package/docker-entrypoint.sh +5 -0
- package/docker-init-apt-root.sh +94 -0
- package/docker-kata-apt-env.sh +39 -0
- package/docs/plugins.md +88 -47
- package/docs/skills.md +9 -7
- package/examples/plugins/echo/README.md +27 -27
- package/examples/plugins/echo/package.json +3 -0
- package/examples/plugins/echo/register.ts +31 -31
- package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
- package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
- package/openapi.yaml +325 -3
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +83 -10
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/sync-web-search-catalog.ts +47 -25
- package/src/__tests__/agent-image-optimize.test.ts +11 -3
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
- package/src/__tests__/anthropic-provider.test.ts +45 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
- package/src/__tests__/app-executors.test.ts +220 -4
- package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
- package/src/__tests__/bundled-asset.test.ts +6 -6
- package/src/__tests__/channel-availability-routes.test.ts +206 -0
- package/src/__tests__/channel-delivery-store.test.ts +289 -1
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
- package/src/__tests__/clawhub.test.ts +75 -16
- package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
- package/src/__tests__/config-schema.test.ts +21 -0
- package/src/__tests__/config-set-route.test.ts +80 -0
- package/src/__tests__/config-sounds-sync.test.ts +97 -0
- package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
- package/src/__tests__/context-search-conversations-source.test.ts +117 -2
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +7 -0
- package/src/__tests__/context-token-estimator.test.ts +1 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
- package/src/__tests__/conversation-agent-loop.test.ts +2 -0
- package/src/__tests__/conversation-error.test.ts +42 -3
- package/src/__tests__/conversation-fork-crud.test.ts +82 -0
- package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
- package/src/__tests__/conversation-lifecycle.test.ts +173 -0
- package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
- package/src/__tests__/conversation-pairing.test.ts +54 -0
- package/src/__tests__/conversation-process-callsite.test.ts +4 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
- package/src/__tests__/conversation-queue.test.ts +4 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
- package/src/__tests__/conversation-slash-queue.test.ts +59 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
- package/src/__tests__/conversation-sync-tags.test.ts +235 -0
- 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 +3 -2
- package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
- package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
- package/src/__tests__/disk-pressure-tools.test.ts +1 -0
- package/src/__tests__/dm-backfill.test.ts +121 -10
- package/src/__tests__/document-tool-security.test.ts +258 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/edit-propagation.test.ts +33 -0
- package/src/__tests__/empty-response-pipeline.test.ts +0 -4
- package/src/__tests__/external-plugin-loader.test.ts +60 -36
- package/src/__tests__/filing-service.test.ts +140 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
- package/src/__tests__/helpers/tar-fixtures.ts +39 -0
- package/src/__tests__/helpers/wait-for.ts +21 -0
- package/src/__tests__/history-repair-pipeline.test.ts +0 -3
- package/src/__tests__/history-repair.test.ts +73 -0
- package/src/__tests__/host-app-control-proxy.test.ts +266 -10
- package/src/__tests__/image-credentials.test.ts +1 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
- package/src/__tests__/inference-profile-reaper.test.ts +4 -2
- package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
- package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
- package/src/__tests__/injector-chain.test.ts +10 -8
- package/src/__tests__/install-skill-routing.test.ts +155 -37
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
- package/src/__tests__/list-messages-page-latest.test.ts +55 -0
- package/src/__tests__/llm-call-pipeline.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +55 -13
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
- package/src/__tests__/llm-usage-store.test.ts +114 -0
- package/src/__tests__/managed-profile-guard.test.ts +31 -29
- package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
- package/src/__tests__/managed-store.test.ts +84 -192
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
- package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
- package/src/__tests__/oauth-commands-routes.test.ts +168 -16
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
- package/src/__tests__/openai-provider.test.ts +24 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
- package/src/__tests__/persistence-pipeline.test.ts +0 -2
- package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
- package/src/__tests__/platform.test.ts +2 -0
- package/src/__tests__/plugin-api-shim.test.ts +125 -0
- package/src/__tests__/plugin-bootstrap.test.ts +10 -36
- package/src/__tests__/plugin-external-api.test.ts +68 -0
- package/src/__tests__/plugin-registry.test.ts +0 -77
- package/src/__tests__/plugin-route-contribution.test.ts +0 -1
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
- package/src/__tests__/plugin-types.test.ts +3 -13
- package/src/__tests__/process-message-background-slack.test.ts +8 -1
- package/src/__tests__/process-message-display-content.test.ts +421 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
- package/src/__tests__/provider-error-scenarios.test.ts +111 -0
- package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
- package/src/__tests__/schedule-routes.test.ts +50 -3
- package/src/__tests__/schedule-store.test.ts +94 -0
- package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
- package/src/__tests__/schema-transforms.test.ts +20 -0
- package/src/__tests__/search-skills-unified.test.ts +0 -5
- package/src/__tests__/server-history-render.test.ts +43 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
- package/src/__tests__/skill-load-tool.test.ts +27 -89
- package/src/__tests__/skill-memory.test.ts +23 -3
- package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
- package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
- package/src/__tests__/skills-install-extract.test.ts +49 -38
- package/src/__tests__/skills-install-staging.test.ts +159 -0
- package/src/__tests__/skills-uninstall.test.ts +9 -41
- package/src/__tests__/skills.test.ts +51 -58
- package/src/__tests__/slack-channel-config.test.ts +9 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/system-prompt.test.ts +737 -63
- package/src/__tests__/terminal-tools.test.ts +28 -1
- package/src/__tests__/thread-backfill.test.ts +557 -27
- package/src/__tests__/title-generate-pipeline.test.ts +0 -13
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
- package/src/__tests__/tool-error-pipeline.test.ts +0 -3
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +16 -4
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
- package/src/__tests__/turn-events-store.test.ts +256 -0
- package/src/__tests__/twilio-routes.test.ts +4 -0
- package/src/__tests__/user-plugin-loader.test.ts +0 -7
- package/src/__tests__/voice-session-bridge.test.ts +198 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
- package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
- package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
- package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
- package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
- package/src/acp/resolve-agent.ts +1 -1
- package/src/agent/image-optimize.ts +13 -5
- package/src/calls/voice-session-bridge.ts +61 -42
- package/src/channels/types.ts +108 -0
- package/src/cli/__tests__/unknown-command.test.ts +24 -0
- package/src/cli/commands/__tests__/changelog.test.ts +304 -319
- package/src/cli/commands/__tests__/schedules.test.ts +491 -0
- package/src/cli/commands/changelog.ts +106 -42
- package/src/cli/commands/conversations.ts +102 -17
- package/src/cli/commands/default-action.ts +10 -53
- package/src/cli/commands/notifications.ts +329 -317
- package/src/cli/commands/plugins.ts +185 -0
- package/src/cli/commands/schedules.ts +391 -0
- package/src/cli/commands/telemetry.ts +40 -0
- package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
- package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
- package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
- package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
- package/src/cli/lib/cli-colors.ts +12 -0
- package/src/cli/lib/confirm-prompt.ts +79 -0
- package/src/cli/lib/install-from-github.ts +304 -0
- package/src/cli/lib/list-installed-plugins.ts +137 -0
- package/src/cli/lib/uninstall-plugin.ts +82 -0
- package/src/cli/lib/unknown-command.ts +111 -0
- package/src/cli/program.ts +38 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
- package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
- package/src/config/bundled-skills/document/SKILL.md +23 -3
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
- package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
- package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
- package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
- package/src/config/bundled-tool-registry.ts +6 -0
- package/src/config/feature-flag-registry.json +41 -1
- package/src/config/loader.ts +64 -38
- package/src/config/schema.ts +7 -10
- package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/compaction.ts +28 -0
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm-request-logs.ts +31 -7
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-retrieval.ts +18 -0
- package/src/config/schemas/tools.ts +14 -0
- package/src/config/skills.ts +3 -96
- package/src/context/compactor.ts +1047 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/context/window-manager.ts +197 -1520
- package/src/credential-execution/managed-catalog.ts +37 -0
- package/src/credential-health/credential-health-service.ts +280 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
- package/src/daemon/approval-generators.ts +8 -6
- package/src/daemon/config-watcher.ts +94 -31
- package/src/daemon/conversation-agent-loop.ts +169 -9
- package/src/daemon/conversation-error.ts +171 -37
- package/src/daemon/conversation-lifecycle.ts +53 -40
- package/src/daemon/conversation-messaging.ts +25 -6
- package/src/daemon/conversation-process.ts +49 -12
- package/src/daemon/conversation-runtime-assembly.ts +16 -1
- package/src/daemon/conversation-slash.ts +12 -5
- package/src/daemon/conversation-store.ts +11 -4
- package/src/daemon/conversation-tool-setup.ts +39 -7
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/external-plugins-bootstrap.ts +217 -181
- package/src/daemon/first-greeting.ts +22 -2
- package/src/daemon/handlers/config-model.ts +6 -5
- package/src/daemon/handlers/config-slack-channel.ts +15 -3
- package/src/daemon/handlers/shared.ts +14 -5
- package/src/daemon/handlers/skills.ts +111 -108
- package/src/daemon/history-repair.ts +28 -1
- package/src/daemon/host-app-control-proxy.ts +98 -23
- package/src/daemon/lifecycle.ts +45 -35
- package/src/daemon/meet-host-supervisor.ts +5 -4
- package/src/daemon/memory-v2-startup.ts +49 -0
- package/src/daemon/message-protocol.ts +1 -0
- package/src/daemon/message-types/conversations.ts +25 -0
- package/src/daemon/message-types/messages.ts +61 -0
- package/src/daemon/message-types/subagents.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pkb-reminder-builder.test.ts +1 -1
- package/src/daemon/pkb-reminder-builder.ts +1 -1
- package/src/daemon/plugin-source-watcher.ts +146 -0
- package/src/daemon/process-message.ts +21 -3
- package/src/daemon/server.ts +11 -2
- package/src/daemon/skill-memory-refresh.ts +29 -0
- package/src/documents/document-store.ts +221 -3
- package/src/embedded/plugin-api.ts +40 -0
- package/src/filing/filing-service.ts +39 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +41 -0
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +22 -0
- package/src/home/post-connect-feed.ts +1 -0
- package/src/index.ts +18 -1
- package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
- package/src/mcp/client.ts +20 -4
- package/src/media/image-credentials.ts +3 -3
- package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
- package/src/memory/__tests__/conversation-queries.test.ts +263 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
- package/src/memory/__tests__/message-content.test.ts +35 -0
- package/src/memory/bookmark-crud.ts +42 -10
- package/src/memory/context-search/sources/conversations.ts +62 -2
- package/src/memory/context-search/sources/workspace.ts +4 -0
- package/src/memory/conversation-crud.ts +63 -19
- package/src/memory/conversation-queries.ts +110 -10
- package/src/memory/db-init.ts +6 -0
- package/src/memory/delivery-crud.ts +152 -5
- package/src/memory/embedding-backend.ts +4 -4
- package/src/memory/external-conversation-store.ts +66 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
- package/src/memory/graph/conversation-graph-memory.ts +31 -15
- package/src/memory/graph/tools.ts +3 -3
- package/src/memory/indexer.ts +34 -29
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
- package/src/memory/jobs/embed-concept-page.ts +20 -11
- package/src/memory/jobs-worker.ts +6 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
- package/src/memory/llm-request-log-source.ts +19 -52
- package/src/memory/llm-usage-store.ts +125 -5
- package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
- package/src/memory/message-content.ts +1 -1
- package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
- package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
- package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
- package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
- package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/onboarding-events-store.ts +106 -0
- package/src/memory/schema/bookmarks.ts +0 -2
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/inference.ts +1 -3
- package/src/memory/schema/infrastructure.ts +12 -0
- package/src/memory/turn-events-store.ts +127 -2
- package/src/memory/v2/__tests__/activation.test.ts +0 -8
- package/src/memory/v2/__tests__/injection.test.ts +98 -8
- package/src/memory/v2/__tests__/migration.test.ts +87 -0
- package/src/memory/v2/__tests__/page-index.test.ts +83 -0
- package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
- package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
- package/src/memory/v2/__tests__/router.test.ts +15 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
- package/src/memory/v2/injection.ts +32 -6
- package/src/memory/v2/migration.ts +49 -19
- package/src/memory/v2/page-index.ts +35 -5
- package/src/memory/v2/prompts/router.ts +11 -8
- package/src/memory/v2/prompts/sweep.ts +2 -2
- package/src/memory/v2/qdrant.ts +135 -7
- package/src/memory/v2/router.ts +9 -8
- package/src/memory/v2/skill-store.ts +120 -35
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
- package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
- package/src/messaging/providers/slack/adapter.ts +43 -5
- package/src/messaging/providers/slack/client.ts +27 -0
- package/src/messaging/providers/slack/deep-link.ts +65 -0
- package/src/messaging/providers/slack/download.ts +104 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
- package/src/messaging/providers/slack/message-metadata.ts +27 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
- package/src/messaging/providers/slack/render-transcript.ts +69 -5
- package/src/messaging/providers/slack/types.ts +20 -1
- package/src/notifications/conversation-pairing.ts +2 -1
- package/src/notifications/decision-engine.ts +2 -1
- package/src/notifications/emit-signal.ts +20 -1
- package/src/notifications/home-feed-side-effect.ts +54 -0
- package/src/notifications/signal.ts +3 -1
- package/src/oauth/connection-resolver.ts +8 -4
- package/src/oauth/platform-connection.ts +6 -2
- package/src/oauth/seed-providers.ts +10 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/question-prompter.test.ts +416 -0
- package/src/permissions/question-prompter.ts +294 -0
- package/src/platform/client.test.ts +1 -1
- package/src/platform/client.ts +1 -1
- package/src/plugin-api/constants.ts +26 -0
- package/src/plugin-api/index.ts +34 -1
- package/src/plugin-api/types.ts +104 -22
- package/src/plugins/defaults/circuit-breaker.ts +0 -5
- package/src/plugins/defaults/compaction.ts +0 -4
- package/src/plugins/defaults/empty-response.ts +0 -2
- package/src/plugins/defaults/history-repair.ts +0 -2
- package/src/plugins/defaults/injectors.ts +36 -3
- package/src/plugins/defaults/llm-call.ts +0 -2
- package/src/plugins/defaults/memory-retrieval.ts +0 -1
- package/src/plugins/defaults/overflow-reduce.ts +0 -1
- package/src/plugins/defaults/persistence.ts +0 -2
- package/src/plugins/defaults/title-generate.ts +0 -5
- package/src/plugins/defaults/token-estimate.ts +0 -2
- package/src/plugins/defaults/tool-error.ts +0 -7
- package/src/plugins/defaults/tool-execute.ts +0 -2
- package/src/plugins/defaults/tool-result-truncate.ts +0 -4
- package/src/plugins/ensure-plugin-api-shim.ts +96 -0
- package/src/plugins/external-api.ts +104 -0
- package/src/plugins/external-plugin-loader.ts +105 -32
- package/src/plugins/feature-gate.ts +22 -0
- package/src/plugins/pipeline.ts +37 -0
- package/src/plugins/registry.ts +48 -80
- package/src/plugins/types.ts +31 -26
- package/src/plugins/user-loader.ts +21 -2
- package/src/proactive-artifact/aux-message-injector.ts +11 -0
- package/src/proactive-artifact/job.test.ts +37 -5
- package/src/prompts/__tests__/system-prompt.test.ts +12 -0
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
- package/src/prompts/normalize-onboarding.ts +27 -0
- package/src/prompts/sections.ts +302 -0
- package/src/prompts/system-prompt.ts +63 -166
- package/src/prompts/templates/BOOTSTRAP.md +17 -1
- package/src/prompts/templates/system-sections.ts +173 -0
- package/src/providers/__tests__/inference.test.ts +22 -7
- package/src/providers/anthropic/client.ts +28 -28
- package/src/providers/connection-resolution.ts +7 -0
- package/src/providers/inference/adapter-factory.ts +41 -4
- package/src/providers/inference/connections.ts +74 -29
- package/src/providers/inference/resolve-auth.ts +12 -4
- package/src/providers/model-catalog.ts +294 -12
- package/src/providers/openai/chat-completions-provider.ts +10 -2
- package/src/providers/openrouter/client.ts +7 -0
- package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
- package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
- package/src/providers/provider-availability.ts +17 -2
- package/src/providers/provider-catalog-visibility.ts +36 -0
- package/src/providers/registry.ts +22 -14
- package/src/providers/retry.ts +47 -1
- package/src/runtime/__tests__/agent-wake.test.ts +152 -0
- package/src/runtime/agent-wake.ts +42 -14
- package/src/runtime/auth/route-policy.ts +8 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/http-types.ts +19 -0
- package/src/runtime/migrations/origin-mode.ts +1 -1
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
- package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
- package/src/runtime/routes/acp-routes-list.test.ts +143 -0
- package/src/runtime/routes/acp-routes.ts +5 -3
- package/src/runtime/routes/auth-routes.ts +1 -1
- package/src/runtime/routes/bookmark-routes.ts +5 -3
- package/src/runtime/routes/btw-routes.ts +5 -1
- package/src/runtime/routes/channel-availability-routes.ts +121 -0
- package/src/runtime/routes/conversation-cli-routes.ts +44 -3
- package/src/runtime/routes/conversation-list-routes.ts +3 -20
- package/src/runtime/routes/conversation-management-routes.ts +17 -42
- package/src/runtime/routes/conversation-query-routes.ts +40 -35
- package/src/runtime/routes/conversation-routes.ts +90 -11
- package/src/runtime/routes/documents-routes.ts +25 -86
- package/src/runtime/routes/group-routes.ts +5 -0
- package/src/runtime/routes/inbound-conversation.ts +28 -8
- package/src/runtime/routes/inbound-message-handler.ts +236 -41
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
- package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
- package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
- package/src/runtime/routes/integrations/slack/share.ts +4 -52
- package/src/runtime/routes/integrations/slack/token.ts +43 -0
- package/src/runtime/routes/integrations/twilio.ts +6 -13
- package/src/runtime/routes/notification-routes.ts +1 -1
- package/src/runtime/routes/oauth-commands-routes.ts +105 -15
- package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
- package/src/runtime/routes/question-routes.ts +259 -0
- package/src/runtime/routes/rename-conversation-routes.ts +2 -33
- package/src/runtime/routes/schedule-routes.ts +4 -7
- package/src/runtime/routes/subagents-routes.ts +57 -18
- package/src/runtime/routes/telemetry-routes.ts +27 -0
- package/src/runtime/routes/tts-routes.ts +27 -2
- package/src/runtime/routes/workspace-routes.test.ts +43 -0
- package/src/runtime/routes/workspace-routes.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +39 -7
- package/src/runtime/sync/resource-sync-events.ts +93 -1
- package/src/schedule/schedule-store.ts +27 -2
- package/src/schedule/scheduler.ts +9 -1
- package/src/security/__tests__/untrusted-content.test.ts +86 -0
- package/src/security/untrusted-content.ts +93 -8
- package/src/skills/catalog-files.ts +1 -1
- package/src/skills/catalog-install.ts +233 -116
- package/src/skills/clawhub.ts +70 -13
- package/src/skills/managed-store.ts +4 -119
- package/src/skills/skillssh-registry.ts +27 -48
- package/src/subagent/manager.ts +15 -7
- package/src/telemetry/types.ts +113 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
- package/src/telemetry/usage-telemetry-reporter.ts +113 -7
- package/src/tools/apps/executors.ts +58 -7
- package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
- package/src/tools/ask-question/ask-question-tool.ts +304 -0
- package/src/tools/browser/browser-execution.ts +15 -11
- package/src/tools/computer-use/definitions.ts +3 -3
- package/src/tools/credentials/vault.ts +1 -1
- package/src/tools/document/document-tool.ts +124 -1
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +5 -2
- package/src/tools/host-filesystem/transfer.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/registry.ts +17 -7
- package/src/tools/schedule/create.ts +2 -2
- package/src/tools/schema-transforms.ts +7 -2
- package/src/tools/side-effects.ts +1 -0
- package/src/tools/skills/delete-managed.ts +4 -4
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/scaffold-managed.ts +3 -2
- package/src/tools/subagent/notify-parent.ts +1 -1
- package/src/tools/system/request-permission.ts +2 -2
- package/src/tools/terminal/safe-env.ts +60 -1
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/types.ts +72 -21
- package/src/tools/ui-surface/definitions.ts +6 -5
- package/src/tts/__tests__/provider-adapters.test.ts +76 -2
- package/src/tts/providers/elevenlabs-provider.ts +75 -1
- package/src/types/onboarding-context.ts +2 -0
- package/src/util/errors.ts +17 -0
- package/src/util/platform.ts +10 -0
- package/src/watcher/__tests__/engine.test.ts +22 -0
- package/src/watcher/engine.ts +6 -2
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
- package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
- package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
- package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +39 -9
- package/src/workspace/migrations/types.ts +4 -0
- package/examples/plugins/echo/bun.lock +0 -25
- package/src/__tests__/context-window-manager.test.ts +0 -2481
- package/src/context/__tests__/compact-prompt.test.ts +0 -63
- package/src/context/prompts/compact.md +0 -26
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
- /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
|
@@ -17,6 +17,11 @@ const log = getLogger("filing-service");
|
|
|
17
17
|
|
|
18
18
|
const FILING_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
19
19
|
|
|
20
|
+
// When compaction skips because a filing run holds the serialization lock,
|
|
21
|
+
// retry on this near-term cadence so phase-aligned 24h timers don't starve
|
|
22
|
+
// compaction across consecutive ticks.
|
|
23
|
+
const COMPACTION_CONTENDED_RETRY_MS = 10 * 60 * 1000; // 10 minutes
|
|
24
|
+
|
|
20
25
|
const FILING_PROMPT_TEMPLATE = `You are running a periodic knowledge base filing job. This is a background maintenance task focused on the buffer.
|
|
21
26
|
|
|
22
27
|
Read \`pkb/buffer.md\`. For each item in the buffer:
|
|
@@ -66,6 +71,7 @@ This is your knowledge base — keep it sharp.`;
|
|
|
66
71
|
|
|
67
72
|
export interface FilingDeps {
|
|
68
73
|
getCurrentHour?: () => number;
|
|
74
|
+
compactionContendedRetryMs?: number;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export class FilingService {
|
|
@@ -79,8 +85,10 @@ export class FilingService {
|
|
|
79
85
|
private readonly deps: FilingDeps;
|
|
80
86
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
81
87
|
private compactionTimer: ReturnType<typeof setInterval> | null = null;
|
|
88
|
+
private compactionRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
82
89
|
private activeRun: Promise<void> | null = null;
|
|
83
90
|
private activeCompactionRun: Promise<void> | null = null;
|
|
91
|
+
private stopped = false;
|
|
84
92
|
private _lastRunAt: number | null = null;
|
|
85
93
|
private _nextRunAt: number | null = null;
|
|
86
94
|
private _lastCompactionAt: number | null = null;
|
|
@@ -108,6 +116,7 @@ export class FilingService {
|
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
start(): void {
|
|
119
|
+
this.stopped = false;
|
|
111
120
|
const fullConfig = getConfig();
|
|
112
121
|
if (fullConfig.memory.v2.enabled) {
|
|
113
122
|
log.info("Filing service disabled — memory v2 is active");
|
|
@@ -157,12 +166,14 @@ export class FilingService {
|
|
|
157
166
|
clearInterval(this.compactionTimer);
|
|
158
167
|
this.compactionTimer = null;
|
|
159
168
|
}
|
|
169
|
+
this.clearCompactionRetry();
|
|
160
170
|
this._nextRunAt = null;
|
|
161
171
|
this._nextCompactionAt = null;
|
|
162
172
|
this.start();
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
async stop(): Promise<void> {
|
|
176
|
+
this.stopped = true;
|
|
166
177
|
if (this.timer) {
|
|
167
178
|
clearInterval(this.timer);
|
|
168
179
|
this.timer = null;
|
|
@@ -171,6 +182,7 @@ export class FilingService {
|
|
|
171
182
|
clearInterval(this.compactionTimer);
|
|
172
183
|
this.compactionTimer = null;
|
|
173
184
|
}
|
|
185
|
+
this.clearCompactionRetry();
|
|
174
186
|
this._nextRunAt = null;
|
|
175
187
|
this._nextCompactionAt = null;
|
|
176
188
|
const inflight: Promise<void>[] = [];
|
|
@@ -269,9 +281,13 @@ export class FilingService {
|
|
|
269
281
|
log.debug(
|
|
270
282
|
"Filing run in progress, skipping compaction to avoid concurrent PKB writes",
|
|
271
283
|
);
|
|
284
|
+
this.scheduleCompactionRetry(
|
|
285
|
+
this.deps.compactionContendedRetryMs ?? COMPACTION_CONTENDED_RETRY_MS,
|
|
286
|
+
);
|
|
272
287
|
return false;
|
|
273
288
|
}
|
|
274
289
|
|
|
290
|
+
this.clearCompactionRetry();
|
|
275
291
|
const run = this.executeCompactionRun();
|
|
276
292
|
this.activeCompactionRun = run;
|
|
277
293
|
try {
|
|
@@ -301,6 +317,29 @@ export class FilingService {
|
|
|
301
317
|
this._nextCompactionAt = Date.now() + intervalMs;
|
|
302
318
|
}
|
|
303
319
|
|
|
320
|
+
private scheduleCompactionRetry(delayMs: number): void {
|
|
321
|
+
this.clearCompactionRetry();
|
|
322
|
+
if (this.stopped) return;
|
|
323
|
+
this.compactionRetryTimer = setTimeout(() => {
|
|
324
|
+
this.compactionRetryTimer = null;
|
|
325
|
+
if (this.stopped) return;
|
|
326
|
+
this.runCompactionOnce().catch((err) => {
|
|
327
|
+
log.error({ err }, "Compaction retry failed");
|
|
328
|
+
});
|
|
329
|
+
}, delayMs);
|
|
330
|
+
// unref so the pending retry doesn't keep the daemon process alive on
|
|
331
|
+
// shutdown paths that don't call stop().
|
|
332
|
+
this.compactionRetryTimer.unref?.();
|
|
333
|
+
this._nextCompactionAt = Date.now() + delayMs;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private clearCompactionRetry(): void {
|
|
337
|
+
if (this.compactionRetryTimer) {
|
|
338
|
+
clearTimeout(this.compactionRetryTimer);
|
|
339
|
+
this.compactionRetryTimer = null;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
304
343
|
private shouldSkipForDiskPressure(source: "filing" | "compaction"): boolean {
|
|
305
344
|
const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
|
|
306
345
|
if (diskPressureGate.action === "allow") return false;
|
|
@@ -47,12 +47,22 @@ mock.module("../../util/platform.js", () => ({
|
|
|
47
47
|
|
|
48
48
|
// Stub config so heartbeat is enabled. Must export every symbol from
|
|
49
49
|
// the real module because Bun's mock.module replaces the entire module.
|
|
50
|
-
|
|
50
|
+
// Tests that need to flex maxConsecutiveRuns mutate this in-place.
|
|
51
|
+
const stubConfig: {
|
|
52
|
+
heartbeat: {
|
|
53
|
+
enabled: boolean;
|
|
54
|
+
intervalMs: number;
|
|
55
|
+
activeHoursStart: number | null;
|
|
56
|
+
activeHoursEnd: number | null;
|
|
57
|
+
maxConsecutiveRuns: number | null;
|
|
58
|
+
};
|
|
59
|
+
} = {
|
|
51
60
|
heartbeat: {
|
|
52
61
|
enabled: true,
|
|
53
62
|
intervalMs: 60_000,
|
|
54
63
|
activeHoursStart: null,
|
|
55
64
|
activeHoursEnd: null,
|
|
65
|
+
maxConsecutiveRuns: null,
|
|
56
66
|
},
|
|
57
67
|
};
|
|
58
68
|
mock.module("../../config/loader.js", () => ({
|
|
@@ -91,7 +101,6 @@ mock.module("../../prompts/system-prompt.js", () => ({
|
|
|
91
101
|
SYSTEM_PROMPT_CACHE_BOUNDARY: "<<CACHE_BOUNDARY>>",
|
|
92
102
|
buildCoreIdentityContext: () => "",
|
|
93
103
|
buildSystemPrompt: () => "",
|
|
94
|
-
buildCliReferenceSection: () => "",
|
|
95
104
|
ensurePromptFiles: () => {},
|
|
96
105
|
stripCommentLines: (s: string) => s,
|
|
97
106
|
}));
|
|
@@ -165,7 +174,6 @@ mock.module("../../runtime/pre-first-message-gate.js", () => ({
|
|
|
165
174
|
hasReceivedUserMessage: () => preFirstMessageGateOpen,
|
|
166
175
|
}));
|
|
167
176
|
|
|
168
|
-
|
|
169
177
|
const { HeartbeatService } = await import("../heartbeat-service.js");
|
|
170
178
|
|
|
171
179
|
let origWorkspaceDir: string | undefined;
|
|
@@ -178,6 +186,7 @@ beforeEach(() => {
|
|
|
178
186
|
runBackgroundJobCalls.length = 0;
|
|
179
187
|
skipHeartbeatRunCalls.length = 0;
|
|
180
188
|
preFirstMessageGateOpen = true;
|
|
189
|
+
stubConfig.heartbeat.maxConsecutiveRuns = null;
|
|
181
190
|
runBackgroundJobImpl = async () => ({
|
|
182
191
|
conversationId: STUB_CONVERSATION_ID,
|
|
183
192
|
ok: true,
|
|
@@ -351,9 +360,85 @@ describe("HeartbeatService", () => {
|
|
|
351
360
|
|
|
352
361
|
expect(runBackgroundJobCalls).toHaveLength(1);
|
|
353
362
|
expect(
|
|
354
|
-
skipHeartbeatRunCalls.some(
|
|
355
|
-
(c) => c.reason === "pre_first_user_message",
|
|
356
|
-
),
|
|
363
|
+
skipHeartbeatRunCalls.some((c) => c.reason === "pre_first_user_message"),
|
|
357
364
|
).toBe(false);
|
|
358
365
|
});
|
|
366
|
+
|
|
367
|
+
describe("max consecutive runs cap", () => {
|
|
368
|
+
test("skips with reason 'max_consecutive_runs' after the cap is hit", async () => {
|
|
369
|
+
stubConfig.heartbeat.maxConsecutiveRuns = 2;
|
|
370
|
+
const service = new HeartbeatService({ alerter: () => {} });
|
|
371
|
+
|
|
372
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
373
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
374
|
+
expect(await service.runOnce({ force: false })).toBe(false);
|
|
375
|
+
|
|
376
|
+
expect(runBackgroundJobCalls).toHaveLength(2);
|
|
377
|
+
expect(
|
|
378
|
+
skipHeartbeatRunCalls.some((c) => c.reason === "max_consecutive_runs"),
|
|
379
|
+
).toBe(true);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("resetTimer() clears the counter so auto runs resume", async () => {
|
|
383
|
+
stubConfig.heartbeat.maxConsecutiveRuns = 1;
|
|
384
|
+
const service = new HeartbeatService({ alerter: () => {} });
|
|
385
|
+
service.start();
|
|
386
|
+
try {
|
|
387
|
+
await service.runOnce({ force: false });
|
|
388
|
+
expect(await service.runOnce({ force: false })).toBe(false);
|
|
389
|
+
|
|
390
|
+
service.resetTimer();
|
|
391
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
392
|
+
expect(runBackgroundJobCalls).toHaveLength(2);
|
|
393
|
+
} finally {
|
|
394
|
+
await service.stop();
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("null disables the cap entirely", async () => {
|
|
399
|
+
stubConfig.heartbeat.maxConsecutiveRuns = null;
|
|
400
|
+
const service = new HeartbeatService({ alerter: () => {} });
|
|
401
|
+
|
|
402
|
+
for (let i = 0; i < 5; i++) {
|
|
403
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
404
|
+
}
|
|
405
|
+
expect(runBackgroundJobCalls).toHaveLength(5);
|
|
406
|
+
expect(
|
|
407
|
+
skipHeartbeatRunCalls.some((c) => c.reason === "max_consecutive_runs"),
|
|
408
|
+
).toBe(false);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("force runs bypass the cap and do not increment the counter", async () => {
|
|
412
|
+
stubConfig.heartbeat.maxConsecutiveRuns = 2;
|
|
413
|
+
const service = new HeartbeatService({ alerter: () => {} });
|
|
414
|
+
|
|
415
|
+
// Five force runs would push us well past the cap if force counted.
|
|
416
|
+
for (let i = 0; i < 5; i++) {
|
|
417
|
+
expect(await service.runOnce({ force: true })).toBe(true);
|
|
418
|
+
}
|
|
419
|
+
// Two auto runs should still proceed because the counter is at zero.
|
|
420
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
421
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
422
|
+
// The third auto run trips the cap.
|
|
423
|
+
expect(await service.runOnce({ force: false })).toBe(false);
|
|
424
|
+
expect(
|
|
425
|
+
skipHeartbeatRunCalls.some((c) => c.reason === "max_consecutive_runs"),
|
|
426
|
+
).toBe(true);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("reconfigure() resets the counter", async () => {
|
|
430
|
+
stubConfig.heartbeat.maxConsecutiveRuns = 1;
|
|
431
|
+
const service = new HeartbeatService({ alerter: () => {} });
|
|
432
|
+
service.start();
|
|
433
|
+
try {
|
|
434
|
+
await service.runOnce({ force: false });
|
|
435
|
+
expect(await service.runOnce({ force: false })).toBe(false);
|
|
436
|
+
|
|
437
|
+
service.reconfigure();
|
|
438
|
+
expect(await service.runOnce({ force: false })).toBe(true);
|
|
439
|
+
} finally {
|
|
440
|
+
await service.stop();
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
359
444
|
});
|
|
@@ -198,6 +198,10 @@ export class HeartbeatService {
|
|
|
198
198
|
private _startupMissedCount = 0;
|
|
199
199
|
private _startupCrashedCount = 0;
|
|
200
200
|
private _hasRunStartupRecovery = false;
|
|
201
|
+
// Counter of consecutive auto-heartbeats since the last guardian message.
|
|
202
|
+
// Reset by resetTimer (guardian message), reconfigure, and stop. Force runs
|
|
203
|
+
// bypass the cap and do not increment.
|
|
204
|
+
private _consecutiveRuns = 0;
|
|
201
205
|
|
|
202
206
|
constructor(deps: HeartbeatDeps) {
|
|
203
207
|
this.deps = deps;
|
|
@@ -262,6 +266,11 @@ export class HeartbeatService {
|
|
|
262
266
|
isAsyncBackground: true,
|
|
263
267
|
visibleInSourceNow: false,
|
|
264
268
|
},
|
|
269
|
+
conversationMetadata: {
|
|
270
|
+
source: "heartbeat",
|
|
271
|
+
groupId: "system:background",
|
|
272
|
+
conversationType: "background",
|
|
273
|
+
},
|
|
265
274
|
}).catch((err) => {
|
|
266
275
|
log.warn(
|
|
267
276
|
{ err },
|
|
@@ -350,6 +359,7 @@ export class HeartbeatService {
|
|
|
350
359
|
|
|
351
360
|
/** Restart the timer with the latest config (e.g. after settings change). */
|
|
352
361
|
reconfigure(): void {
|
|
362
|
+
this._consecutiveRuns = 0;
|
|
353
363
|
this.configEpoch++;
|
|
354
364
|
if (this._pendingRunId) {
|
|
355
365
|
supersedePendingRun(this._pendingRunId);
|
|
@@ -371,6 +381,9 @@ export class HeartbeatService {
|
|
|
371
381
|
* after an active conversation.
|
|
372
382
|
*/
|
|
373
383
|
resetTimer(): void {
|
|
384
|
+
// Counter resets even when the timer is null so a guardian message during
|
|
385
|
+
// a stopped window still clears the count.
|
|
386
|
+
this._consecutiveRuns = 0;
|
|
374
387
|
if (!this.timer) return;
|
|
375
388
|
if (this.cronMode) {
|
|
376
389
|
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
@@ -390,6 +403,7 @@ export class HeartbeatService {
|
|
|
390
403
|
}
|
|
391
404
|
|
|
392
405
|
async stop(): Promise<void> {
|
|
406
|
+
this._consecutiveRuns = 0;
|
|
393
407
|
this.stopped = true;
|
|
394
408
|
if (this.timer) {
|
|
395
409
|
clearTimeout(this.timer as ReturnType<typeof setTimeout>);
|
|
@@ -500,6 +514,28 @@ export class HeartbeatService {
|
|
|
500
514
|
}
|
|
501
515
|
}
|
|
502
516
|
|
|
517
|
+
// Cap consecutive auto-runs without a guardian message so the assistant
|
|
518
|
+
// stops burning LLM tokens when the user is away. Force runs (manual
|
|
519
|
+
// operator action) bypass the cap and do not increment the counter.
|
|
520
|
+
if (
|
|
521
|
+
!force &&
|
|
522
|
+
config.maxConsecutiveRuns != null &&
|
|
523
|
+
this._consecutiveRuns >= config.maxConsecutiveRuns
|
|
524
|
+
) {
|
|
525
|
+
log.debug(
|
|
526
|
+
{
|
|
527
|
+
consecutiveRuns: this._consecutiveRuns,
|
|
528
|
+
maxConsecutiveRuns: config.maxConsecutiveRuns,
|
|
529
|
+
},
|
|
530
|
+
"Max consecutive runs reached, skipping",
|
|
531
|
+
);
|
|
532
|
+
if (runId) skipHeartbeatRun(runId, "max_consecutive_runs");
|
|
533
|
+
if (!this.cronMode) {
|
|
534
|
+
this.scheduleNextRun(config.intervalMs);
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
|
|
503
539
|
// Overlap prevention
|
|
504
540
|
if (this.activeRun) {
|
|
505
541
|
log.debug("Previous heartbeat run still active, skipping");
|
|
@@ -525,6 +561,9 @@ export class HeartbeatService {
|
|
|
525
561
|
this.activeRun = null;
|
|
526
562
|
}
|
|
527
563
|
this._lastRunAt = Date.now();
|
|
564
|
+
if (!force) {
|
|
565
|
+
this._consecutiveRuns++;
|
|
566
|
+
}
|
|
528
567
|
if (!this.cronMode) {
|
|
529
568
|
this.scheduleNextRun(getConfig().heartbeat.intervalMs);
|
|
530
569
|
}
|
|
@@ -648,6 +687,7 @@ export class HeartbeatService {
|
|
|
648
687
|
conversationMetadata: {
|
|
649
688
|
source: "heartbeat",
|
|
650
689
|
groupId: "system:background",
|
|
690
|
+
conversationType: "background",
|
|
651
691
|
},
|
|
652
692
|
});
|
|
653
693
|
} catch (err) {
|
|
@@ -714,6 +754,7 @@ export class HeartbeatService {
|
|
|
714
754
|
conversationMetadata: {
|
|
715
755
|
source: "heartbeat",
|
|
716
756
|
groupId: "system:background",
|
|
757
|
+
conversationType: "background",
|
|
717
758
|
},
|
|
718
759
|
});
|
|
719
760
|
}
|
|
@@ -82,6 +82,40 @@ describe("feedItemSchema — valid minimal items", () => {
|
|
|
82
82
|
expect(parsed.expiresAt).toBe("2026-04-15T00:00:00.000Z");
|
|
83
83
|
expect(parsed.detailPanel?.kind).toBe("emailDraft");
|
|
84
84
|
});
|
|
85
|
+
|
|
86
|
+
test("category field passes through when present", () => {
|
|
87
|
+
for (const cat of [
|
|
88
|
+
"security",
|
|
89
|
+
"scheduling",
|
|
90
|
+
"background",
|
|
91
|
+
"email",
|
|
92
|
+
"system",
|
|
93
|
+
] as const) {
|
|
94
|
+
const parsed = feedItemSchema.parse({
|
|
95
|
+
...minimalNotification(),
|
|
96
|
+
category: cat,
|
|
97
|
+
});
|
|
98
|
+
expect(parsed.category).toBe(cat);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("metadata field passes through when present", () => {
|
|
103
|
+
const parsed = feedItemSchema.parse({
|
|
104
|
+
...minimalNotification(),
|
|
105
|
+
metadata: { subject: "Hello", count: 3, nested: { ok: true } },
|
|
106
|
+
});
|
|
107
|
+
expect(parsed.metadata).toEqual({
|
|
108
|
+
subject: "Hello",
|
|
109
|
+
count: 3,
|
|
110
|
+
nested: { ok: true },
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("items without category or metadata still parse (backward compat)", () => {
|
|
115
|
+
const parsed = feedItemSchema.parse(minimalNotification());
|
|
116
|
+
expect(parsed.category).toBeUndefined();
|
|
117
|
+
expect(parsed.metadata).toBeUndefined();
|
|
118
|
+
});
|
|
85
119
|
});
|
|
86
120
|
|
|
87
121
|
// ---------------------------------------------------------------------------
|
|
@@ -142,6 +176,12 @@ describe("feedItemSchema — enum validation", () => {
|
|
|
142
176
|
}
|
|
143
177
|
});
|
|
144
178
|
|
|
179
|
+
test("rejects unknown `category`", () => {
|
|
180
|
+
expect(() =>
|
|
181
|
+
feedItemSchema.parse({ ...minimalNotification(), category: "weather" }),
|
|
182
|
+
).toThrow();
|
|
183
|
+
});
|
|
184
|
+
|
|
145
185
|
test("rejects unknown `status`", () => {
|
|
146
186
|
expect(() =>
|
|
147
187
|
feedItemSchema.parse({ ...minimalNotification(), status: "archived" }),
|
package/src/home/feed-types.ts
CHANGED
|
@@ -38,6 +38,14 @@ export type FeedItemStatus = "new" | "seen" | "acted_on" | "dismissed";
|
|
|
38
38
|
/** Visual urgency treatment — controls badge color independently of sort priority. */
|
|
39
39
|
export type FeedItemUrgency = "low" | "medium" | "high" | "critical";
|
|
40
40
|
|
|
41
|
+
/** Broad category for grouping and filtering feed items. */
|
|
42
|
+
export type FeedItemCategory =
|
|
43
|
+
| "security"
|
|
44
|
+
| "scheduling"
|
|
45
|
+
| "background"
|
|
46
|
+
| "email"
|
|
47
|
+
| "system";
|
|
48
|
+
|
|
41
49
|
/**
|
|
42
50
|
* A single action button attached to a feed item.
|
|
43
51
|
*
|
|
@@ -96,6 +104,10 @@ export interface FeedItem {
|
|
|
96
104
|
conversationId?: string;
|
|
97
105
|
/** Server-driven detail panel descriptor; when present, the client opens this panel kind. */
|
|
98
106
|
detailPanel?: FeedItemDetailPanel;
|
|
107
|
+
/** Broad category for grouping and filtering feed items. */
|
|
108
|
+
category?: FeedItemCategory;
|
|
109
|
+
/** Arbitrary structured data the detail panel or other consumers can use. */
|
|
110
|
+
metadata?: Record<string, unknown>;
|
|
99
111
|
/** Internal: ISO-8601 writer-record time, used for ordering + TTL. */
|
|
100
112
|
createdAt: string;
|
|
101
113
|
}
|
|
@@ -164,6 +176,14 @@ const feedItemDetailPanelSchema = z.object({
|
|
|
164
176
|
kind: feedItemDetailPanelKindSchema,
|
|
165
177
|
});
|
|
166
178
|
|
|
179
|
+
const feedItemCategorySchema = z.enum([
|
|
180
|
+
"security",
|
|
181
|
+
"scheduling",
|
|
182
|
+
"background",
|
|
183
|
+
"email",
|
|
184
|
+
"system",
|
|
185
|
+
]);
|
|
186
|
+
|
|
167
187
|
/**
|
|
168
188
|
* Schema for a single `FeedItem`.
|
|
169
189
|
*
|
|
@@ -187,6 +207,8 @@ export const feedItemSchema = z.object({
|
|
|
187
207
|
urgency: feedItemUrgencySchema.optional(),
|
|
188
208
|
conversationId: z.string().optional(),
|
|
189
209
|
detailPanel: feedItemDetailPanelSchema.optional(),
|
|
210
|
+
category: feedItemCategorySchema.optional(),
|
|
211
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
190
212
|
createdAt: z.string(),
|
|
191
213
|
});
|
|
192
214
|
|
|
@@ -44,6 +44,7 @@ export async function emitPostConnectNudge(service: string): Promise<void> {
|
|
|
44
44
|
"I can triage your inbox, summarize new emails, or draft replies to important threads.",
|
|
45
45
|
timestamp: now.toISOString(),
|
|
46
46
|
status: "new",
|
|
47
|
+
category: "email",
|
|
47
48
|
expiresAt,
|
|
48
49
|
createdAt: now.toISOString(),
|
|
49
50
|
actions: [
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
import { red } from "./cli/lib/cli-colors.js";
|
|
4
|
+
import {
|
|
5
|
+
detectUnknownCommand,
|
|
6
|
+
formatUnknownCommandMessage,
|
|
7
|
+
} from "./cli/lib/unknown-command.js";
|
|
3
8
|
import { buildCliProgram } from "./cli/program.js";
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
const program = await buildCliProgram();
|
|
11
|
+
|
|
12
|
+
// Commander processes `--help` before any action or hook fires, so
|
|
13
|
+
// `assistant <unknown> --help` would dump the root help instead of flagging
|
|
14
|
+
// the typo. Pre-scan argv so the unknown-command error wins over the help
|
|
15
|
+
// short-circuit. See cli/lib/unknown-command.ts.
|
|
16
|
+
const unknown = detectUnknownCommand(program, process.argv.slice(2));
|
|
17
|
+
if (unknown) {
|
|
18
|
+
process.stderr.write(`${red(formatUnknownCommandMessage(unknown))}\n`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
program.parse();
|
|
@@ -229,6 +229,63 @@ describe("LiveVoiceSession STT", () => {
|
|
|
229
229
|
]);
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
test("retains transcriber handle when stop() throws so close() can clean up", async () => {
|
|
233
|
+
class ThrowingStopTranscriber extends MockStreamingTranscriber {
|
|
234
|
+
stopCalls = 0;
|
|
235
|
+
override stop(): void {
|
|
236
|
+
this.stopCalls += 1;
|
|
237
|
+
if (this.stopCalls === 1) {
|
|
238
|
+
throw new Error("stop failed");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const transcriber = new ThrowingStopTranscriber();
|
|
244
|
+
const { frames, session } = createSessionWithTranscriber(transcriber);
|
|
245
|
+
|
|
246
|
+
await session.start();
|
|
247
|
+
await session.handleClientFrame({ type: "ptt_release" });
|
|
248
|
+
|
|
249
|
+
expect(transcriber.stopCalls).toBe(1);
|
|
250
|
+
expect(
|
|
251
|
+
frames.some(
|
|
252
|
+
(frame) =>
|
|
253
|
+
frame.type === "error" &&
|
|
254
|
+
frame.message.includes(
|
|
255
|
+
"Live voice transcription could not be stopped",
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
).toBe(true);
|
|
259
|
+
|
|
260
|
+
await session.close("websocket_close");
|
|
261
|
+
|
|
262
|
+
expect(transcriber.stopCalls).toBe(2);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("retains transcriber handle when stop() throws so interrupt() can clean up", async () => {
|
|
266
|
+
class ThrowingStopTranscriber extends MockStreamingTranscriber {
|
|
267
|
+
stopCalls = 0;
|
|
268
|
+
override stop(): void {
|
|
269
|
+
this.stopCalls += 1;
|
|
270
|
+
if (this.stopCalls === 1) {
|
|
271
|
+
throw new Error("stop failed");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const transcriber = new ThrowingStopTranscriber();
|
|
277
|
+
const { session } = createSessionWithTranscriber(transcriber);
|
|
278
|
+
|
|
279
|
+
await session.start();
|
|
280
|
+
await session.handleClientFrame({ type: "ptt_release" });
|
|
281
|
+
|
|
282
|
+
expect(transcriber.stopCalls).toBe(1);
|
|
283
|
+
|
|
284
|
+
await session.handleClientFrame({ type: "interrupt" });
|
|
285
|
+
|
|
286
|
+
expect(transcriber.stopCalls).toBe(2);
|
|
287
|
+
});
|
|
288
|
+
|
|
232
289
|
test("uses the production streaming transcriber resolver by default", () => {
|
|
233
290
|
const source = readFileSync(
|
|
234
291
|
new URL("../live-voice-session.ts", import.meta.url),
|
package/src/mcp/client.ts
CHANGED
|
@@ -14,6 +14,24 @@ const log = getLogger("mcp-client");
|
|
|
14
14
|
|
|
15
15
|
const CONNECT_TIMEOUT_MS = 30_000;
|
|
16
16
|
|
|
17
|
+
// MCP servers occasionally return tools with a missing or non-object
|
|
18
|
+
// `inputSchema` (spec violation, but seen in the wild). Coerce to a valid
|
|
19
|
+
// empty object schema so downstream code that assumes `input_schema: object`
|
|
20
|
+
// (e.g. `injectActivityField`) doesn't crash.
|
|
21
|
+
function normalizeInputSchema(
|
|
22
|
+
raw: unknown,
|
|
23
|
+
toolName: string,
|
|
24
|
+
): Record<string, unknown> {
|
|
25
|
+
if (raw != null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
26
|
+
return raw as Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
log.warn(
|
|
29
|
+
{ toolName, received: typeof raw },
|
|
30
|
+
"MCP tool returned non-object inputSchema; defaulting to empty object schema",
|
|
31
|
+
);
|
|
32
|
+
return { type: "object", properties: {} };
|
|
33
|
+
}
|
|
34
|
+
|
|
17
35
|
export interface McpToolInfo {
|
|
18
36
|
name: string;
|
|
19
37
|
description: string;
|
|
@@ -80,9 +98,7 @@ export class McpClient {
|
|
|
80
98
|
`mcp:${this.serverId}:tokens`,
|
|
81
99
|
);
|
|
82
100
|
if (cachedTokens) {
|
|
83
|
-
const callbackTransport = getIsPlatform()
|
|
84
|
-
? "gateway"
|
|
85
|
-
: "loopback";
|
|
101
|
+
const callbackTransport = getIsPlatform() ? "gateway" : "loopback";
|
|
86
102
|
this.oauthProvider = new McpOAuthProvider(
|
|
87
103
|
this.serverId,
|
|
88
104
|
transportConfig.url,
|
|
@@ -164,7 +180,7 @@ export class McpClient {
|
|
|
164
180
|
return result.tools.map((tool) => ({
|
|
165
181
|
name: tool.name,
|
|
166
182
|
description: tool.description ?? "",
|
|
167
|
-
inputSchema: tool.inputSchema
|
|
183
|
+
inputSchema: normalizeInputSchema(tool.inputSchema, tool.name),
|
|
168
184
|
}));
|
|
169
185
|
}
|
|
170
186
|
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* credentials are unavailable.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import { resolveManagedProxyContext } from "../providers/
|
|
11
|
+
import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
|
|
12
|
+
import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
|
|
13
13
|
import { getProviderKeyAsync } from "../security/secure-keys.js";
|
|
14
14
|
import type { ImageGenCredentials, ImageGenProvider } from "./types.js";
|
|
15
15
|
|
|
@@ -33,7 +33,7 @@ export async function resolveImageGenCredentials(opts: {
|
|
|
33
33
|
// Resolve platform URL + assistant API key from a single snapshot so
|
|
34
34
|
// baseUrl and assistantApiKey can't diverge if the credential is cleared
|
|
35
35
|
// between lookups.
|
|
36
|
-
const meta =
|
|
36
|
+
const meta = PLATFORM_PROVIDER_META[provider];
|
|
37
37
|
const ctx = await resolveManagedProxyContext();
|
|
38
38
|
if (
|
|
39
39
|
!meta?.managed ||
|