@vellumai/assistant 0.6.3 → 0.6.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/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -57,21 +57,62 @@ export function textToSlackBlocks(text: string): Block[] | undefined {
|
|
|
57
57
|
|
|
58
58
|
if (segment.type === "code") {
|
|
59
59
|
const lang = segment.lang ?? "";
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
const codeChunks = splitCodeSegmentContent(segment.content, lang);
|
|
61
|
+
for (let c = 0; c < codeChunks.length; c++) {
|
|
62
|
+
if (c > 0) {
|
|
63
|
+
blocks.push({ type: "divider" });
|
|
64
|
+
}
|
|
65
|
+
blocks.push({
|
|
66
|
+
type: "section",
|
|
67
|
+
text: {
|
|
68
|
+
type: "mrkdwn",
|
|
69
|
+
text: "```" + lang + "\n" + codeChunks[c] + "\n```",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
65
73
|
} else if (segment.type === "header") {
|
|
66
74
|
blocks.push({
|
|
67
75
|
type: "header",
|
|
68
76
|
text: { type: "plain_text", text: segment.content },
|
|
69
77
|
});
|
|
78
|
+
} else if (segment.type === "table") {
|
|
79
|
+
const structured = convertTableToStructuredText(
|
|
80
|
+
segment.headers,
|
|
81
|
+
segment.rows,
|
|
82
|
+
);
|
|
83
|
+
const mrkdwn = markdownToMrkdwn(structured);
|
|
84
|
+
const chunks = splitLongTextSegment(mrkdwn);
|
|
85
|
+
for (let c = 0; c < chunks.length; c++) {
|
|
86
|
+
if (c > 0) {
|
|
87
|
+
blocks.push({ type: "divider" });
|
|
88
|
+
}
|
|
89
|
+
blocks.push({
|
|
90
|
+
type: "section",
|
|
91
|
+
text: { type: "mrkdwn", text: chunks[c] },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
70
94
|
} else {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
95
|
+
// Transform to Slack mrkdwn FIRST, then split. Splitting raw markdown
|
|
96
|
+
// can bisect `[link text](url)` spans or `**bold**` markers, leaving
|
|
97
|
+
// orphan tokens that `markdownToMrkdwn`'s regex won't match — raw
|
|
98
|
+
// markdown would then leak through to Slack. The splitter is also
|
|
99
|
+
// span-aware (see `splitLongTextSegment`) so candidate boundaries
|
|
100
|
+
// landing inside a `<url|text>` or `*bold*` span are rejected, which
|
|
101
|
+
// matters when link text contains `. `/`! `/`? ` (e.g.
|
|
102
|
+
// `<url|First sentence. Second sentence>`) or when a span straddles
|
|
103
|
+
// the maxChars window. The preceding table branch uses the same
|
|
104
|
+
// ordering.
|
|
105
|
+
const mrkdwn = markdownToMrkdwn(segment.content);
|
|
106
|
+
const chunks = splitLongTextSegment(mrkdwn);
|
|
107
|
+
for (let c = 0; c < chunks.length; c++) {
|
|
108
|
+
if (c > 0) {
|
|
109
|
+
blocks.push({ type: "divider" });
|
|
110
|
+
}
|
|
111
|
+
blocks.push({
|
|
112
|
+
type: "section",
|
|
113
|
+
text: { type: "mrkdwn", text: chunks[c] },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
75
116
|
}
|
|
76
117
|
}
|
|
77
118
|
|
|
@@ -113,7 +154,13 @@ interface HeaderSegment {
|
|
|
113
154
|
content: string;
|
|
114
155
|
}
|
|
115
156
|
|
|
116
|
-
|
|
157
|
+
interface TableSegment {
|
|
158
|
+
type: "table";
|
|
159
|
+
headers: string[];
|
|
160
|
+
rows: string[][];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
type Segment = TextSegment | CodeSegment | HeaderSegment | TableSegment;
|
|
117
164
|
|
|
118
165
|
function splitIntoSegments(text: string): Segment[] {
|
|
119
166
|
const lines = text.split("\n");
|
|
@@ -155,6 +202,15 @@ function splitIntoSegments(text: string): Segment[] {
|
|
|
155
202
|
continue;
|
|
156
203
|
}
|
|
157
204
|
|
|
205
|
+
// Detect markdown tables: header row + separator row + data rows
|
|
206
|
+
const tableSegment = tryParseTable(lines, i);
|
|
207
|
+
if (tableSegment) {
|
|
208
|
+
flushText();
|
|
209
|
+
segments.push(tableSegment.segment);
|
|
210
|
+
i = tableSegment.nextIndex;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
158
214
|
currentTextLines.push(line);
|
|
159
215
|
i++;
|
|
160
216
|
}
|
|
@@ -163,6 +219,104 @@ function splitIntoSegments(text: string): Segment[] {
|
|
|
163
219
|
return segments;
|
|
164
220
|
}
|
|
165
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Parse cells from a pipe-delimited table row, trimming whitespace.
|
|
224
|
+
*/
|
|
225
|
+
function parseTableRow(line: string): string[] {
|
|
226
|
+
const ESCAPED_PIPE_PLACEHOLDER = "\x00PIPE\x00";
|
|
227
|
+
const ESCAPED_BACKSLASH_PLACEHOLDER = "\x00BSLASH\x00";
|
|
228
|
+
return line
|
|
229
|
+
// First, protect escaped backslashes (\\) so they don't interfere
|
|
230
|
+
.replace(/\\\\/g, ESCAPED_BACKSLASH_PLACEHOLDER)
|
|
231
|
+
// Now a remaining \| is a genuinely escaped pipe (odd backslash)
|
|
232
|
+
.replace(/\\\|/g, ESCAPED_PIPE_PLACEHOLDER)
|
|
233
|
+
.replace(/^\|/, "")
|
|
234
|
+
.replace(/\|$/, "")
|
|
235
|
+
.split("|")
|
|
236
|
+
.map((cell) =>
|
|
237
|
+
cell
|
|
238
|
+
.replaceAll(ESCAPED_PIPE_PLACEHOLDER, "|")
|
|
239
|
+
.replaceAll(ESCAPED_BACKSLASH_PLACEHOLDER, "\\\\")
|
|
240
|
+
.trim(),
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Check if a line is a markdown table separator row (e.g., |---|---|).
|
|
246
|
+
*/
|
|
247
|
+
function isSeparatorRow(line: string): boolean {
|
|
248
|
+
return /^\|[\s:-]+(\|[\s:-]+)*\|?\s*$/.test(line);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Try to parse a markdown table starting at the given line index.
|
|
253
|
+
* Returns the parsed table segment and the next line index, or null
|
|
254
|
+
* if the lines don't form a valid table (header + separator + at least
|
|
255
|
+
* one data row).
|
|
256
|
+
*/
|
|
257
|
+
function tryParseTable(
|
|
258
|
+
lines: string[],
|
|
259
|
+
startIndex: number,
|
|
260
|
+
): { segment: TableSegment; nextIndex: number } | null {
|
|
261
|
+
// Need at least 3 lines: header, separator, one data row
|
|
262
|
+
if (startIndex + 2 >= lines.length) return null;
|
|
263
|
+
|
|
264
|
+
const headerLine = lines[startIndex];
|
|
265
|
+
const separatorLine = lines[startIndex + 1];
|
|
266
|
+
|
|
267
|
+
// Header must be a pipe-delimited row
|
|
268
|
+
if (!headerLine.includes("|") || !headerLine.match(/^\|.*\|$/)) return null;
|
|
269
|
+
if (!isSeparatorRow(separatorLine)) return null;
|
|
270
|
+
|
|
271
|
+
const headers = parseTableRow(headerLine);
|
|
272
|
+
|
|
273
|
+
// Collect data rows
|
|
274
|
+
const rows: string[][] = [];
|
|
275
|
+
let i = startIndex + 2;
|
|
276
|
+
while (
|
|
277
|
+
i < lines.length &&
|
|
278
|
+
lines[i].includes("|") &&
|
|
279
|
+
lines[i].match(/^\|.*\|$/)
|
|
280
|
+
) {
|
|
281
|
+
if (!isSeparatorRow(lines[i])) {
|
|
282
|
+
rows.push(parseTableRow(lines[i]));
|
|
283
|
+
}
|
|
284
|
+
i++;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Need at least one data row to qualify as a table
|
|
288
|
+
if (rows.length === 0) return null;
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
segment: { type: "table", headers, rows },
|
|
292
|
+
nextIndex: i,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Convert a parsed table into structured bullet-point text suitable
|
|
298
|
+
* for Slack rendering. Uses the first column as the entry label,
|
|
299
|
+
* listing remaining columns as sub-bullets.
|
|
300
|
+
*/
|
|
301
|
+
function convertTableToStructuredText(
|
|
302
|
+
headers: string[],
|
|
303
|
+
rows: string[][],
|
|
304
|
+
): string {
|
|
305
|
+
const lines: string[] = [];
|
|
306
|
+
|
|
307
|
+
for (const row of rows) {
|
|
308
|
+
const label = row[0] ?? "";
|
|
309
|
+
lines.push(`**${label}**`);
|
|
310
|
+
for (let c = 1; c < headers.length; c++) {
|
|
311
|
+
const value = row[c] ?? "";
|
|
312
|
+
lines.push(` - ${headers[c]}: ${value}`);
|
|
313
|
+
}
|
|
314
|
+
lines.push("");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return lines.join("\n").trim();
|
|
318
|
+
}
|
|
319
|
+
|
|
166
320
|
function markdownToMrkdwn(text: string): string {
|
|
167
321
|
let result = text;
|
|
168
322
|
// [text](url) → <url|text>
|
|
@@ -174,3 +328,276 @@ function markdownToMrkdwn(text: string): string {
|
|
|
174
328
|
result = result.replace(/\*\*(.+?)\*\*/g, "*$1*");
|
|
175
329
|
return result;
|
|
176
330
|
}
|
|
331
|
+
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
// Long-text splitting
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Slack's `section` block has a documented ~3000-character limit on the
|
|
338
|
+
* `text.text` value. Keep a margin under that so downstream transforms
|
|
339
|
+
* (e.g. `markdownToMrkdwn` expansions) don't push a chunk over.
|
|
340
|
+
*/
|
|
341
|
+
export const SLACK_SECTION_MAX_CHARS = 2800;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Split `text` into chunks no larger than `maxChars`, preferring natural
|
|
345
|
+
* boundaries. Pure helper: no Block Kit knowledge, safe to unit test.
|
|
346
|
+
*
|
|
347
|
+
* Preference order for split points:
|
|
348
|
+
* 1. Paragraph boundary (`\n\n`)
|
|
349
|
+
* 2. Single newline (`\n`)
|
|
350
|
+
* 3. Sentence boundary (`. `, `! `, `? `)
|
|
351
|
+
* 4. Hard slice at `maxChars` (backed up to the start of any straddling
|
|
352
|
+
* mrkdwn span, when possible)
|
|
353
|
+
*
|
|
354
|
+
* Span-aware: candidate boundaries that fall strictly inside a Slack
|
|
355
|
+
* `<...>` link/user token or a `*...*` bold span are rejected so chunks
|
|
356
|
+
* never end with an unclosed `<` or orphan `*`. This matters because
|
|
357
|
+
* `markdownToMrkdwn` runs before splitting, and link text can legitimately
|
|
358
|
+
* contain `. `/`! `/`? ` (e.g. `<url|First sentence. Second sentence>`).
|
|
359
|
+
*
|
|
360
|
+
* Short inputs (length ≤ `maxChars`) return `[text]` unchanged. Each chunk
|
|
361
|
+
* is trimmed of leading/trailing whitespace at chunk boundaries; interior
|
|
362
|
+
* whitespace is preserved.
|
|
363
|
+
*/
|
|
364
|
+
export function splitLongTextSegment(
|
|
365
|
+
text: string,
|
|
366
|
+
maxChars: number = SLACK_SECTION_MAX_CHARS,
|
|
367
|
+
): string[] {
|
|
368
|
+
if (maxChars <= 0 || text.length <= maxChars) return [text];
|
|
369
|
+
|
|
370
|
+
const chunks: string[] = [];
|
|
371
|
+
let remaining = text;
|
|
372
|
+
|
|
373
|
+
while (remaining.length > maxChars) {
|
|
374
|
+
const window = remaining.slice(0, maxChars);
|
|
375
|
+
const spans = computeMrkdwnSpans(window);
|
|
376
|
+
let splitAt = findBoundary(window, ["\n\n"], spans);
|
|
377
|
+
if (splitAt < 0) splitAt = findBoundary(window, ["\n"], spans);
|
|
378
|
+
if (splitAt < 0) splitAt = findBoundary(window, [". ", "! ", "? "], spans);
|
|
379
|
+
if (splitAt < 0) splitAt = hardSliceAvoidingSpans(maxChars, spans);
|
|
380
|
+
|
|
381
|
+
const chunk = remaining.slice(0, splitAt).trim();
|
|
382
|
+
if (chunk.length > 0) chunks.push(chunk);
|
|
383
|
+
remaining = remaining.slice(splitAt);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const tail = remaining.trim();
|
|
387
|
+
if (tail.length > 0) chunks.push(tail);
|
|
388
|
+
|
|
389
|
+
return chunks;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Slack link/mention tokens always start with one of these prefixes after
|
|
394
|
+
* the opening `<`. A plain `<` in technical prose (e.g. `a < b`) does not
|
|
395
|
+
* match any of these and must not be treated as a span start, or the
|
|
396
|
+
* splitter would reject every boundary past the `<` and fall back to a
|
|
397
|
+
* hard slice that can land mid-word.
|
|
398
|
+
*/
|
|
399
|
+
const SLACK_LINK_PREFIXES = [
|
|
400
|
+
"http://",
|
|
401
|
+
"https://",
|
|
402
|
+
"mailto:",
|
|
403
|
+
"#C", // channel ref: <#C0123ABCD|name>
|
|
404
|
+
"@U", // user mention: <@U0123ABCD>
|
|
405
|
+
"@W", // workspace user: <@W0123ABCD>
|
|
406
|
+
"!channel",
|
|
407
|
+
"!here",
|
|
408
|
+
"!everyone",
|
|
409
|
+
"!subteam",
|
|
410
|
+
"!date",
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
// Matches `scheme://` at the start of a string where `scheme` follows RFC 3986
|
|
414
|
+
// syntax: `ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`. Covers http, https,
|
|
415
|
+
// ftp, ssh, git+ssh, custom app schemes, etc.
|
|
416
|
+
const URL_SCHEME_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
417
|
+
|
|
418
|
+
function looksLikeLinkStart(window: string, openIdx: number): boolean {
|
|
419
|
+
const rest = window.slice(openIdx + 1);
|
|
420
|
+
if (rest.length === 0) return false;
|
|
421
|
+
const first = rest.charCodeAt(0);
|
|
422
|
+
// Fast reject: whitespace or another `<` immediately after `<` is never a link.
|
|
423
|
+
if (first === 0x20 || first === 0x09 || first === 0x0a || first === 0x3c) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
for (const prefix of SLACK_LINK_PREFIXES) {
|
|
427
|
+
if (rest.startsWith(prefix)) return true;
|
|
428
|
+
// Near the window edge the prefix may be truncated — e.g. `<https`
|
|
429
|
+
// when `://` is past the cutoff. If `rest` is a strict prefix of a
|
|
430
|
+
// known link prefix, treat it as link-shaped so the continuation
|
|
431
|
+
// past the window is still protected from mid-token hard slicing.
|
|
432
|
+
if (rest.length < prefix.length && prefix.startsWith(rest)) return true;
|
|
433
|
+
}
|
|
434
|
+
// `markdownToMrkdwn` wraps any markdown link target in `<url|text>`,
|
|
435
|
+
// including schemes beyond the whitelist above (ftp, ssh, custom app
|
|
436
|
+
// schemes, etc.). Recognize any `scheme://` prefix so those spans are
|
|
437
|
+
// protected too. Also treat a truncated `<scheme` at the window edge as
|
|
438
|
+
// link-shaped so the continuation past the window is not hard-sliced.
|
|
439
|
+
if (URL_SCHEME_RE.test(rest)) return true;
|
|
440
|
+
const colonIdx = rest.indexOf(":");
|
|
441
|
+
if (colonIdx < 0) {
|
|
442
|
+
if (/^[a-z][a-z0-9+.-]*$/i.test(rest)) return true;
|
|
443
|
+
} else if (colonIdx + 1 === rest.length) {
|
|
444
|
+
if (/^[a-z][a-z0-9+.-]*:$/i.test(rest)) return true;
|
|
445
|
+
} else if (rest[colonIdx + 1] === "/" && colonIdx + 2 === rest.length) {
|
|
446
|
+
if (/^[a-z][a-z0-9+.-]*:\/$/i.test(rest)) return true;
|
|
447
|
+
}
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Find half-open intervals `[start, end)` covering Slack mrkdwn spans in
|
|
453
|
+
* `window` that the splitter must not bisect: `<...>` link/mention tokens
|
|
454
|
+
* and `*...*` single-asterisk bold runs. A `<` is only treated as a span
|
|
455
|
+
* start when it is followed by a recognized Slack link/mention prefix
|
|
456
|
+
* (see `SLACK_LINK_PREFIXES`); plain `<` in technical text is skipped.
|
|
457
|
+
* An unclosed link-shaped `<...` is treated as a span extending to the
|
|
458
|
+
* end of the window so the splitter avoids landing inside a span that
|
|
459
|
+
* straddles the cutoff.
|
|
460
|
+
*/
|
|
461
|
+
function computeMrkdwnSpans(window: string): Array<[number, number]> {
|
|
462
|
+
const intervals: Array<[number, number]> = [];
|
|
463
|
+
|
|
464
|
+
let i = 0;
|
|
465
|
+
while (i < window.length) {
|
|
466
|
+
const open = window.indexOf("<", i);
|
|
467
|
+
if (open < 0) break;
|
|
468
|
+
if (!looksLikeLinkStart(window, open)) {
|
|
469
|
+
i = open + 1;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const close = window.indexOf(">", open + 1);
|
|
473
|
+
// For unclosed `<...` (span continues past the window), use a sentinel
|
|
474
|
+
// larger than any in-window position so split-point checks treat the
|
|
475
|
+
// window's right edge as inside the span and back the hard-slice up.
|
|
476
|
+
const end = close < 0 ? window.length + 1 : close + 1;
|
|
477
|
+
intervals.push([open, end]);
|
|
478
|
+
i = close < 0 ? window.length : end;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Bold spans cannot contain `*` or `\n`.
|
|
482
|
+
const boldRe = /\*[^*\n]+\*/g;
|
|
483
|
+
let m: RegExpExecArray | null;
|
|
484
|
+
while ((m = boldRe.exec(window)) !== null) {
|
|
485
|
+
intervals.push([m.index, m.index + m[0].length]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return intervals;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isInsideSpan(
|
|
492
|
+
pos: number,
|
|
493
|
+
spans: Array<[number, number]>,
|
|
494
|
+
): boolean {
|
|
495
|
+
for (const [start, end] of spans) {
|
|
496
|
+
if (pos > start && pos < end) return true;
|
|
497
|
+
}
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function hardSliceAvoidingSpans(
|
|
502
|
+
maxChars: number,
|
|
503
|
+
spans: Array<[number, number]>,
|
|
504
|
+
): number {
|
|
505
|
+
for (const [start, end] of spans) {
|
|
506
|
+
if (maxChars > start && maxChars < end) {
|
|
507
|
+
// Back up to the start of the straddling span so it stays intact in
|
|
508
|
+
// the next chunk. If the span starts at 0, we have no choice but to
|
|
509
|
+
// hard-slice through it.
|
|
510
|
+
return start > 0 ? start : maxChars;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return maxChars;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Split a code segment's inner content into chunks that, once wrapped in
|
|
518
|
+
* ```lang … ``` fences, each fit inside Slack's section-text limit.
|
|
519
|
+
*
|
|
520
|
+
* Prefers line boundaries so code stays readable across chunks; falls back
|
|
521
|
+
* to a hard character slice for pathological single-line content.
|
|
522
|
+
*/
|
|
523
|
+
export function splitCodeSegmentContent(
|
|
524
|
+
content: string,
|
|
525
|
+
lang: string = "",
|
|
526
|
+
maxChars: number = SLACK_SECTION_MAX_CHARS,
|
|
527
|
+
): string[] {
|
|
528
|
+
// Fence overhead: "```" + lang + "\n" + content + "\n```"
|
|
529
|
+
const overhead = 3 + lang.length + 1 + 1 + 3;
|
|
530
|
+
const budget = maxChars - overhead;
|
|
531
|
+
if (budget <= 0 || content.length <= budget) return [content];
|
|
532
|
+
|
|
533
|
+
const chunks: string[] = [];
|
|
534
|
+
const lines = content.split("\n");
|
|
535
|
+
let current: string[] = [];
|
|
536
|
+
let currentLen = 0;
|
|
537
|
+
|
|
538
|
+
const flush = (): void => {
|
|
539
|
+
if (current.length > 0) {
|
|
540
|
+
chunks.push(current.join("\n"));
|
|
541
|
+
current = [];
|
|
542
|
+
currentLen = 0;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
for (const line of lines) {
|
|
547
|
+
// +1 for the joining "\n" (only if current is non-empty)
|
|
548
|
+
const added = current.length === 0 ? line.length : line.length + 1;
|
|
549
|
+
if (currentLen + added <= budget) {
|
|
550
|
+
current.push(line);
|
|
551
|
+
currentLen += added;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
flush();
|
|
556
|
+
|
|
557
|
+
// A single line longer than the budget — hard-slice it across chunks.
|
|
558
|
+
if (line.length > budget) {
|
|
559
|
+
let remaining = line;
|
|
560
|
+
while (remaining.length > budget) {
|
|
561
|
+
chunks.push(remaining.slice(0, budget));
|
|
562
|
+
remaining = remaining.slice(budget);
|
|
563
|
+
}
|
|
564
|
+
if (remaining.length > 0) {
|
|
565
|
+
current.push(remaining);
|
|
566
|
+
currentLen = remaining.length;
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
current.push(line);
|
|
570
|
+
currentLen = line.length;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
flush();
|
|
575
|
+
return chunks;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Return the end index of the last occurrence of any delimiter in `window`
|
|
580
|
+
* whose split point does not fall inside a mrkdwn span. Returns -1 if no
|
|
581
|
+
* delimiter has a valid split point.
|
|
582
|
+
*/
|
|
583
|
+
function findBoundary(
|
|
584
|
+
window: string,
|
|
585
|
+
delimiters: string[],
|
|
586
|
+
spans: Array<[number, number]> = [],
|
|
587
|
+
): number {
|
|
588
|
+
let best = -1;
|
|
589
|
+
for (const delim of delimiters) {
|
|
590
|
+
let from = window.length;
|
|
591
|
+
while (from > 0) {
|
|
592
|
+
const idx = window.lastIndexOf(delim, from - 1);
|
|
593
|
+
if (idx < 0) break;
|
|
594
|
+
const end = idx + delim.length;
|
|
595
|
+
if (!isInsideSpan(end, spans)) {
|
|
596
|
+
if (end > best) best = end;
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
from = idx;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return best;
|
|
603
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { emitFeedEvent } from "../home/emit-feed-event.js";
|
|
1
2
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
2
3
|
import { getConversation } from "../memory/conversation-crud.js";
|
|
3
4
|
import { invalidateAssistantInferredItemsForConversation } from "../memory/task-memory-cleanup.js";
|
|
@@ -141,11 +142,21 @@ async function runScheduleOnce(
|
|
|
141
142
|
});
|
|
142
143
|
if (isOneShot) {
|
|
143
144
|
completeOneShot(job.id);
|
|
145
|
+
emitScheduleFeedEvent({
|
|
146
|
+
title: job.name,
|
|
147
|
+
summary: "Reminder fired.",
|
|
148
|
+
dedupKey: `schedule-notify-oneshot:${job.id}`,
|
|
149
|
+
});
|
|
144
150
|
} else {
|
|
145
151
|
// Track recurring notify-mode success so lastStatus resets to ok
|
|
146
152
|
// and retryCount clears after a transient failure.
|
|
147
153
|
const runId = createScheduleRun(job.id, `notify-ok:${job.id}`);
|
|
148
154
|
completeScheduleRun(runId, { status: "ok" });
|
|
155
|
+
emitScheduleFeedEvent({
|
|
156
|
+
title: job.name,
|
|
157
|
+
summary: "Scheduled notification fired.",
|
|
158
|
+
dedupKey: `schedule-run:${runId}`,
|
|
159
|
+
});
|
|
149
160
|
}
|
|
150
161
|
} catch (err) {
|
|
151
162
|
log.warn(
|
|
@@ -223,6 +234,11 @@ async function runScheduleOnce(
|
|
|
223
234
|
completeScheduleRun(runId, { status: "ok" });
|
|
224
235
|
if (!job.quiet) {
|
|
225
236
|
notifySchedule({ id: job.id, name: job.name });
|
|
237
|
+
emitScheduleFeedEvent({
|
|
238
|
+
title: job.name,
|
|
239
|
+
summary: "Scheduled task ran.",
|
|
240
|
+
dedupKey: `schedule-run:${runId}`,
|
|
241
|
+
});
|
|
226
242
|
}
|
|
227
243
|
if (isOneShot) completeOneShot(job.id);
|
|
228
244
|
}
|
|
@@ -317,6 +333,11 @@ async function runScheduleOnce(
|
|
|
317
333
|
completeScheduleRun(runId, { status: "ok" });
|
|
318
334
|
if (!job.quiet) {
|
|
319
335
|
notifySchedule({ id: job.id, name: job.name });
|
|
336
|
+
emitScheduleFeedEvent({
|
|
337
|
+
title: job.name,
|
|
338
|
+
summary: isOneShot ? "One-shot reminder ran." : "Scheduled job ran.",
|
|
339
|
+
dedupKey: `schedule-run:${runId}`,
|
|
340
|
+
});
|
|
320
341
|
}
|
|
321
342
|
if (isOneShot) completeOneShot(job.id);
|
|
322
343
|
processed += 1;
|
|
@@ -384,3 +405,32 @@ async function runScheduleOnce(
|
|
|
384
405
|
}
|
|
385
406
|
return processed;
|
|
386
407
|
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Fire-and-forget home-feed emit for a successful schedule run.
|
|
411
|
+
*
|
|
412
|
+
* Wraps {@link emitFeedEvent} with local error handling so a schema
|
|
413
|
+
* failure or writer hiccup can never interrupt the scheduler tick
|
|
414
|
+
* loop. The dedupKey is always derived from the schedule run id (or
|
|
415
|
+
* the job id, for one-shot notify-mode which fires before a run
|
|
416
|
+
* record is created) so each run lands as its own entry in the
|
|
417
|
+
* activity log — the writer's per-source cap keeps total volume
|
|
418
|
+
* bounded.
|
|
419
|
+
*/
|
|
420
|
+
function emitScheduleFeedEvent(params: {
|
|
421
|
+
title: string;
|
|
422
|
+
summary: string;
|
|
423
|
+
dedupKey: string;
|
|
424
|
+
}): void {
|
|
425
|
+
void emitFeedEvent({
|
|
426
|
+
source: "assistant",
|
|
427
|
+
title: params.title,
|
|
428
|
+
summary: params.summary,
|
|
429
|
+
dedupKey: params.dedupKey,
|
|
430
|
+
}).catch((err) => {
|
|
431
|
+
log.warn(
|
|
432
|
+
{ err, dedupKey: params.dedupKey },
|
|
433
|
+
"Failed to emit schedule feed event",
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
}
|
package/src/security/oauth2.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface OAuth2Config {
|
|
|
56
56
|
* use `","` (comma).
|
|
57
57
|
*/
|
|
58
58
|
scopeSeparator: string;
|
|
59
|
+
/**
|
|
60
|
+
* Body encoding format for the token exchange and refresh requests.
|
|
61
|
+
* - `"form"` (default): `application/x-www-form-urlencoded` with `URLSearchParams`.
|
|
62
|
+
* - `"json"`: `application/json` with `JSON.stringify`.
|
|
63
|
+
* Providers like Notion require JSON-encoded bodies at their token endpoint.
|
|
64
|
+
*/
|
|
65
|
+
tokenExchangeBodyFormat?: "form" | "json";
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
export interface OAuth2TokenResult {
|
|
@@ -113,6 +120,7 @@ async function exchangeCodeForTokens(
|
|
|
113
120
|
codeVerifier: string,
|
|
114
121
|
): Promise<OAuth2FlowResult> {
|
|
115
122
|
const authMethod = config.tokenEndpointAuthMethod ?? "client_secret_post";
|
|
123
|
+
const bodyFormat = config.tokenExchangeBodyFormat ?? "form";
|
|
116
124
|
|
|
117
125
|
const tokenBody: Record<string, string> = {
|
|
118
126
|
grant_type: "authorization_code",
|
|
@@ -122,7 +130,11 @@ async function exchangeCodeForTokens(
|
|
|
122
130
|
};
|
|
123
131
|
|
|
124
132
|
const headers: Record<string, string> = {
|
|
125
|
-
"Content-Type":
|
|
133
|
+
"Content-Type":
|
|
134
|
+
bodyFormat === "json"
|
|
135
|
+
? "application/json"
|
|
136
|
+
: "application/x-www-form-urlencoded",
|
|
137
|
+
Accept: "application/json",
|
|
126
138
|
};
|
|
127
139
|
|
|
128
140
|
if (config.clientSecret && authMethod === "client_secret_basic") {
|
|
@@ -140,7 +152,10 @@ async function exchangeCodeForTokens(
|
|
|
140
152
|
const tokenResp = await fetch(config.tokenExchangeUrl, {
|
|
141
153
|
method: "POST",
|
|
142
154
|
headers,
|
|
143
|
-
body:
|
|
155
|
+
body:
|
|
156
|
+
bodyFormat === "json"
|
|
157
|
+
? JSON.stringify(tokenBody)
|
|
158
|
+
: new URLSearchParams(tokenBody),
|
|
144
159
|
});
|
|
145
160
|
|
|
146
161
|
if (!tokenResp.ok) {
|
|
@@ -785,8 +800,10 @@ export async function refreshOAuth2Token(
|
|
|
785
800
|
refreshToken: string,
|
|
786
801
|
clientSecret?: string,
|
|
787
802
|
tokenEndpointAuthMethod?: TokenEndpointAuthMethod,
|
|
803
|
+
tokenExchangeBodyFormat?: "form" | "json",
|
|
788
804
|
): Promise<OAuth2TokenResult> {
|
|
789
805
|
const authMethod = tokenEndpointAuthMethod ?? "client_secret_post";
|
|
806
|
+
const bodyFormat = tokenExchangeBodyFormat ?? "form";
|
|
790
807
|
|
|
791
808
|
const body: Record<string, string> = {
|
|
792
809
|
grant_type: "refresh_token",
|
|
@@ -794,7 +811,11 @@ export async function refreshOAuth2Token(
|
|
|
794
811
|
};
|
|
795
812
|
|
|
796
813
|
const headers: Record<string, string> = {
|
|
797
|
-
"Content-Type":
|
|
814
|
+
"Content-Type":
|
|
815
|
+
bodyFormat === "json"
|
|
816
|
+
? "application/json"
|
|
817
|
+
: "application/x-www-form-urlencoded",
|
|
818
|
+
Accept: "application/json",
|
|
798
819
|
};
|
|
799
820
|
|
|
800
821
|
if (clientSecret && authMethod === "client_secret_basic") {
|
|
@@ -812,7 +833,8 @@ export async function refreshOAuth2Token(
|
|
|
812
833
|
const resp = await fetch(tokenExchangeUrl, {
|
|
813
834
|
method: "POST",
|
|
814
835
|
headers,
|
|
815
|
-
body:
|
|
836
|
+
body:
|
|
837
|
+
bodyFormat === "json" ? JSON.stringify(body) : new URLSearchParams(body),
|
|
816
838
|
});
|
|
817
839
|
|
|
818
840
|
if (!resp.ok) {
|