@vellumai/assistant 0.4.48 → 0.4.50
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 +26 -35
- package/README.md +5 -26
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/architecture/memory.md +180 -119
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +2 -2
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +249 -2
- package/src/__tests__/approval-cascade.test.ts +796 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +4 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +13 -20
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +284 -49
- package/src/__tests__/credential-vault.test.ts +150 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +791 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-redemption-service.test.ts +65 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +14 -46
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +941 -15
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +870 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -3
- package/src/__tests__/relay-server.test.ts +46 -1
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secure-keys.test.ts +7 -2
- package/src/__tests__/send-endpoint-busy.test.ts +24 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +7 -13
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-invite-redemption.test.ts +32 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +286 -0
- package/src/agent/loop.ts +104 -131
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +133 -6
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +52 -18
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +3 -8
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/types.ts +3 -1
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/doctor.ts +4 -3
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +284 -0
- package/src/cli/commands/oauth/connections.ts +633 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +256 -0
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +177 -339
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +6 -11
- package/src/cli/reference.ts +1 -3
- package/src/cli.ts +4 -10
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
- package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/skills.ts +21 -2
- package/src/config/types.ts +0 -4
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +53 -11
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/config-watcher.ts +61 -3
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +27 -36
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +430 -0
- package/src/daemon/lifecycle.ts +67 -71
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +1 -129
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +4 -0
- package/src/daemon/message-types/sessions.ts +4 -0
- package/src/daemon/server.ts +25 -21
- package/src/daemon/session-agent-loop-handlers.ts +40 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +43 -28
- package/src/daemon/session-tool-setup.ts +9 -10
- package/src/daemon/session.ts +150 -17
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/instrument.ts +61 -1
- package/src/logfire.ts +16 -5
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/conversation-queries.ts +22 -3
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +62 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +67 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +133 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +582 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +167 -0
- package/src/oauth/token-persistence.ts +81 -77
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +1 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +36 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +46 -42
- package/src/providers/anthropic/client.ts +59 -20
- package/src/providers/retry.ts +1 -27
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -6
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +10 -8
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +19 -1
- package/src/runtime/invite-service.ts +25 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +81 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/runtime/routes/workspace-routes.ts +2 -1
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +22 -10
- package/src/security/oauth2.ts +1 -1
- package/src/security/secure-keys.ts +25 -3
- package/src/security/token-manager.ts +137 -64
- package/src/skills/catalog-install.ts +414 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +36 -11
- package/src/tools/computer-use/registry.ts +5 -6
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +92 -167
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/registry.ts +2 -7
- package/src/tools/schedule/create.ts +8 -1
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +85 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/routes/mcp-routes.ts +0 -20
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses image dimensions from base64-encoded image data by reading binary headers.
|
|
3
|
+
* Supports PNG, JPEG, GIF, and WebP formats.
|
|
4
|
+
* Returns null if parsing fails for any reason (corrupt, truncated, unrecognized).
|
|
5
|
+
*/
|
|
6
|
+
export function parseImageDimensions(
|
|
7
|
+
base64Data: string,
|
|
8
|
+
mediaType: string,
|
|
9
|
+
): { width: number; height: number } | null {
|
|
10
|
+
try {
|
|
11
|
+
switch (mediaType) {
|
|
12
|
+
case "image/png":
|
|
13
|
+
return parsePng(base64Data);
|
|
14
|
+
case "image/jpeg":
|
|
15
|
+
return parseJpeg(base64Data);
|
|
16
|
+
case "image/gif":
|
|
17
|
+
return parseGif(base64Data);
|
|
18
|
+
case "image/webp":
|
|
19
|
+
return parseWebp(base64Data);
|
|
20
|
+
default:
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decodeBase64Bytes(
|
|
29
|
+
base64Data: string,
|
|
30
|
+
maxBytes: number,
|
|
31
|
+
): Buffer | null {
|
|
32
|
+
// Estimate how much base64 we need: every 4 base64 chars = 3 bytes
|
|
33
|
+
const charsNeeded = Math.ceil((maxBytes * 4) / 3);
|
|
34
|
+
const slice = base64Data.slice(0, charsNeeded + 4); // a little extra for padding
|
|
35
|
+
try {
|
|
36
|
+
const buf = Buffer.from(slice, "base64");
|
|
37
|
+
return buf.length > 0 ? buf : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readUint32BE(buf: Buffer, offset: number): number {
|
|
44
|
+
if (offset + 4 > buf.length) return -1;
|
|
45
|
+
return buf.readUInt32BE(offset);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readUint16BE(buf: Buffer, offset: number): number {
|
|
49
|
+
if (offset + 2 > buf.length) return -1;
|
|
50
|
+
return buf.readUInt16BE(offset);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readUint16LE(buf: Buffer, offset: number): number {
|
|
54
|
+
if (offset + 2 > buf.length) return -1;
|
|
55
|
+
return buf.readUInt16LE(offset);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readUint32LE(buf: Buffer, offset: number): number {
|
|
59
|
+
if (offset + 4 > buf.length) return -1;
|
|
60
|
+
return buf.readUInt32LE(offset);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readUint24LE(buf: Buffer, offset: number): number {
|
|
64
|
+
if (offset + 3 > buf.length) return -1;
|
|
65
|
+
return buf[offset]! | (buf[offset + 1]! << 8) | (buf[offset + 2]! << 16);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parsePng(
|
|
69
|
+
base64Data: string,
|
|
70
|
+
): { width: number; height: number } | null {
|
|
71
|
+
const buf = decodeBase64Bytes(base64Data, 32);
|
|
72
|
+
if (!buf || buf.length < 24) return null;
|
|
73
|
+
|
|
74
|
+
// Validate PNG signature: 89 50 4E 47
|
|
75
|
+
if (
|
|
76
|
+
buf[0] !== 0x89 ||
|
|
77
|
+
buf[1] !== 0x50 ||
|
|
78
|
+
buf[2] !== 0x4e ||
|
|
79
|
+
buf[3] !== 0x47
|
|
80
|
+
) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const width = readUint32BE(buf, 16);
|
|
85
|
+
const height = readUint32BE(buf, 20);
|
|
86
|
+
if (width <= 0 || height <= 0) return null;
|
|
87
|
+
|
|
88
|
+
return { width, height };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function parseJpeg(
|
|
92
|
+
base64Data: string,
|
|
93
|
+
): { width: number; height: number } | null {
|
|
94
|
+
// Scan up to 1 MiB to handle JPEGs with large EXIF/ICC metadata before the SOF marker
|
|
95
|
+
const buf = decodeBase64Bytes(base64Data, 1_048_576);
|
|
96
|
+
if (!buf || buf.length < 2) return null;
|
|
97
|
+
|
|
98
|
+
// Validate JPEG SOI marker
|
|
99
|
+
if (buf[0] !== 0xff || buf[1] !== 0xd8) return null;
|
|
100
|
+
|
|
101
|
+
let offset = 2;
|
|
102
|
+
while (offset < buf.length - 1) {
|
|
103
|
+
// Find next marker
|
|
104
|
+
if (buf[offset] !== 0xff) {
|
|
105
|
+
offset++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Skip padding 0xFF bytes
|
|
110
|
+
while (offset < buf.length && buf[offset] === 0xff) {
|
|
111
|
+
offset++;
|
|
112
|
+
}
|
|
113
|
+
if (offset >= buf.length) return null;
|
|
114
|
+
|
|
115
|
+
const marker = buf[offset]!;
|
|
116
|
+
offset++;
|
|
117
|
+
|
|
118
|
+
// Check for SOF markers: C0-CF excluding C4 (DHT) and CC (DAC)
|
|
119
|
+
if (
|
|
120
|
+
marker >= 0xc0 &&
|
|
121
|
+
marker <= 0xcf &&
|
|
122
|
+
marker !== 0xc4 &&
|
|
123
|
+
marker !== 0xcc
|
|
124
|
+
) {
|
|
125
|
+
// SOF marker found: skip 2-byte length + 1-byte precision
|
|
126
|
+
if (offset + 7 > buf.length) return null;
|
|
127
|
+
const height = readUint16BE(buf, offset + 3);
|
|
128
|
+
const width = readUint16BE(buf, offset + 5);
|
|
129
|
+
if (width <= 0 || height <= 0) return null;
|
|
130
|
+
return { width, height };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Skip this marker's payload
|
|
134
|
+
if (offset + 1 >= buf.length) return null;
|
|
135
|
+
const segmentLength = readUint16BE(buf, offset);
|
|
136
|
+
if (segmentLength < 2) return null;
|
|
137
|
+
offset += segmentLength;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseGif(
|
|
144
|
+
base64Data: string,
|
|
145
|
+
): { width: number; height: number } | null {
|
|
146
|
+
const buf = decodeBase64Bytes(base64Data, 12);
|
|
147
|
+
if (!buf || buf.length < 10) return null;
|
|
148
|
+
|
|
149
|
+
// Validate GIF signature: 47 49 46 38 (GIF8)
|
|
150
|
+
if (
|
|
151
|
+
buf[0] !== 0x47 ||
|
|
152
|
+
buf[1] !== 0x49 ||
|
|
153
|
+
buf[2] !== 0x46 ||
|
|
154
|
+
buf[3] !== 0x38
|
|
155
|
+
) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const width = readUint16LE(buf, 6);
|
|
160
|
+
const height = readUint16LE(buf, 8);
|
|
161
|
+
if (width <= 0 || height <= 0) return null;
|
|
162
|
+
|
|
163
|
+
return { width, height };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseWebp(
|
|
167
|
+
base64Data: string,
|
|
168
|
+
): { width: number; height: number } | null {
|
|
169
|
+
const buf = decodeBase64Bytes(base64Data, 32);
|
|
170
|
+
if (!buf || buf.length < 16) return null;
|
|
171
|
+
|
|
172
|
+
// Validate RIFF signature
|
|
173
|
+
if (
|
|
174
|
+
buf[0] !== 0x52 ||
|
|
175
|
+
buf[1] !== 0x49 ||
|
|
176
|
+
buf[2] !== 0x46 ||
|
|
177
|
+
buf[3] !== 0x46
|
|
178
|
+
) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
// Validate WEBP signature at offset 8
|
|
182
|
+
if (
|
|
183
|
+
buf[8] !== 0x57 ||
|
|
184
|
+
buf[9] !== 0x45 ||
|
|
185
|
+
buf[10] !== 0x42 ||
|
|
186
|
+
buf[11] !== 0x50
|
|
187
|
+
) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Identify sub-format at offset 12
|
|
192
|
+
const subFormat =
|
|
193
|
+
String.fromCharCode(buf[12]!) +
|
|
194
|
+
String.fromCharCode(buf[13]!) +
|
|
195
|
+
String.fromCharCode(buf[14]!) +
|
|
196
|
+
String.fromCharCode(buf[15]!);
|
|
197
|
+
|
|
198
|
+
if (subFormat === "VP8 ") {
|
|
199
|
+
// VP8 lossy
|
|
200
|
+
if (buf.length < 30) return null;
|
|
201
|
+
const width = readUint16LE(buf, 26) & 0x3fff;
|
|
202
|
+
const height = readUint16LE(buf, 28) & 0x3fff;
|
|
203
|
+
if (width <= 0 || height <= 0) return null;
|
|
204
|
+
return { width, height };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (subFormat === "VP8L") {
|
|
208
|
+
// VP8L lossless — validate signature byte 0x2f at offset 20
|
|
209
|
+
if (buf.length < 25) return null;
|
|
210
|
+
if (buf[20] !== 0x2f) return null;
|
|
211
|
+
const bits = readUint32LE(buf, 21);
|
|
212
|
+
if (bits < 0) return null;
|
|
213
|
+
const width = (bits & 0x3fff) + 1;
|
|
214
|
+
const height = ((bits >> 14) & 0x3fff) + 1;
|
|
215
|
+
if (width <= 0 || height <= 0) return null;
|
|
216
|
+
return { width, height };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (subFormat === "VP8X") {
|
|
220
|
+
// VP8X extended
|
|
221
|
+
if (buf.length < 30) return null;
|
|
222
|
+
const width = readUint24LE(buf, 24) + 1;
|
|
223
|
+
const height = readUint24LE(buf, 27) + 1;
|
|
224
|
+
if (width <= 0 || height <= 0) return null;
|
|
225
|
+
return { width, height };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContentBlock, Message } from "../providers/types.js";
|
|
2
|
+
import { parseImageDimensions } from "./image-dimensions.js";
|
|
2
3
|
|
|
3
4
|
const CHARS_PER_TOKEN = 4;
|
|
4
5
|
const MESSAGE_OVERHEAD_TOKENS = 4;
|
|
@@ -12,6 +13,22 @@ const OTHER_BLOCK_TOKENS = 16;
|
|
|
12
13
|
const SYSTEM_PROMPT_OVERHEAD_TOKENS = 8;
|
|
13
14
|
const GEMINI_INLINE_FILE_MIME_TYPES = new Set(["application/pdf"]);
|
|
14
15
|
|
|
16
|
+
// Anthropic scales images to fit within 1568x1568 maintaining aspect ratio,
|
|
17
|
+
// then charges ~(width * height) / 750 tokens.
|
|
18
|
+
const ANTHROPIC_IMAGE_MAX_DIMENSION = 1568;
|
|
19
|
+
const ANTHROPIC_IMAGE_TOKENS_PER_PIXEL = 1 / 750;
|
|
20
|
+
const ANTHROPIC_IMAGE_MAX_TOKENS = Math.ceil(
|
|
21
|
+
ANTHROPIC_IMAGE_MAX_DIMENSION *
|
|
22
|
+
ANTHROPIC_IMAGE_MAX_DIMENSION *
|
|
23
|
+
ANTHROPIC_IMAGE_TOKENS_PER_PIXEL,
|
|
24
|
+
); // ~3,277 tokens
|
|
25
|
+
|
|
26
|
+
// Anthropic renders each PDF page as an image (~1,568 tokens at standard
|
|
27
|
+
// resolution) plus any extracted text. Typical PDF pages are 50-150 KB.
|
|
28
|
+
// Using ~100 KB/page and ~1,600 tokens/page gives ~0.016 tokens/byte.
|
|
29
|
+
const ANTHROPIC_PDF_TOKENS_PER_BYTE = 0.016;
|
|
30
|
+
const ANTHROPIC_PDF_MIN_TOKENS = 1600; // At least one page
|
|
31
|
+
|
|
15
32
|
export interface TokenEstimatorOptions {
|
|
16
33
|
providerName?: string;
|
|
17
34
|
}
|
|
@@ -21,21 +38,69 @@ export function estimateTextTokens(text: string): number {
|
|
|
21
38
|
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
22
39
|
}
|
|
23
40
|
|
|
24
|
-
function
|
|
41
|
+
function estimateAnthropicPdfTokens(base64Data: string): number {
|
|
42
|
+
const rawBytes = Math.ceil((base64Data.length * 3) / 4);
|
|
43
|
+
return Math.max(
|
|
44
|
+
ANTHROPIC_PDF_MIN_TOKENS,
|
|
45
|
+
Math.ceil(rawBytes * ANTHROPIC_PDF_TOKENS_PER_BYTE),
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function estimateFileDataTokens(
|
|
25
50
|
block: Extract<ContentBlock, { type: "file" }>,
|
|
26
51
|
options?: TokenEstimatorOptions,
|
|
27
|
-
):
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
): number {
|
|
53
|
+
const providerName = options?.providerName;
|
|
54
|
+
|
|
55
|
+
// Anthropic sends PDFs as native document blocks and renders each page as an image
|
|
56
|
+
if (
|
|
57
|
+
providerName === "anthropic" &&
|
|
58
|
+
block.source.media_type === "application/pdf"
|
|
59
|
+
) {
|
|
60
|
+
return estimateAnthropicPdfTokens(block.source.data);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Gemini sends certain file types inline as base64
|
|
64
|
+
if (
|
|
65
|
+
providerName === "gemini" &&
|
|
66
|
+
GEMINI_INLINE_FILE_MIME_TYPES.has(block.source.media_type)
|
|
67
|
+
) {
|
|
68
|
+
return estimateTextTokens(block.source.data);
|
|
30
69
|
}
|
|
31
|
-
|
|
70
|
+
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function estimateAnthropicImageTokens(width: number, height: number): number {
|
|
75
|
+
// Scale down to fit within 1568x1568 bounding box, maintaining aspect ratio
|
|
76
|
+
const scale = Math.min(
|
|
77
|
+
1,
|
|
78
|
+
ANTHROPIC_IMAGE_MAX_DIMENSION / Math.max(width, height),
|
|
79
|
+
);
|
|
80
|
+
const scaledWidth = Math.round(width * scale);
|
|
81
|
+
const scaledHeight = Math.round(height * scale);
|
|
82
|
+
return Math.max(
|
|
83
|
+
IMAGE_BLOCK_TOKENS, // minimum 1024
|
|
84
|
+
Math.ceil(scaledWidth * scaledHeight * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL),
|
|
85
|
+
);
|
|
32
86
|
}
|
|
33
87
|
|
|
34
|
-
function
|
|
88
|
+
function estimateImageTokens(
|
|
35
89
|
block: Extract<ContentBlock, { type: "image" }>,
|
|
90
|
+
options?: TokenEstimatorOptions,
|
|
36
91
|
): number {
|
|
37
|
-
|
|
38
|
-
|
|
92
|
+
if (options?.providerName === "anthropic") {
|
|
93
|
+
const dims = parseImageDimensions(
|
|
94
|
+
block.source.data,
|
|
95
|
+
block.source.media_type,
|
|
96
|
+
);
|
|
97
|
+
if (dims) {
|
|
98
|
+
return estimateAnthropicImageTokens(dims.width, dims.height);
|
|
99
|
+
}
|
|
100
|
+
// Fallback: if dimensions can't be parsed, use Anthropic's max
|
|
101
|
+
return ANTHROPIC_IMAGE_MAX_TOKENS;
|
|
102
|
+
}
|
|
103
|
+
// Non-Anthropic: keep existing base64-size heuristic
|
|
39
104
|
return estimateTextTokens(block.source.data);
|
|
40
105
|
}
|
|
41
106
|
|
|
@@ -69,16 +134,14 @@ export function estimateContentBlockTokens(
|
|
|
69
134
|
IMAGE_BLOCK_TOKENS,
|
|
70
135
|
IMAGE_BLOCK_OVERHEAD_TOKENS +
|
|
71
136
|
estimateTextTokens(block.source.media_type) +
|
|
72
|
-
|
|
137
|
+
estimateImageTokens(block, options),
|
|
73
138
|
);
|
|
74
139
|
case "file":
|
|
75
140
|
return (
|
|
76
141
|
FILE_BLOCK_OVERHEAD_TOKENS +
|
|
77
142
|
estimateTextTokens(block.source.filename) +
|
|
78
143
|
estimateTextTokens(block.source.media_type) +
|
|
79
|
-
(
|
|
80
|
-
? estimateTextTokens(block.source.data)
|
|
81
|
-
: 0) +
|
|
144
|
+
estimateFileDataTokens(block, options) +
|
|
82
145
|
estimateTextTokens(block.extracted_text ?? "")
|
|
83
146
|
);
|
|
84
147
|
case "thinking":
|
|
@@ -83,21 +83,44 @@ export interface ContextWindowCompactOptions {
|
|
|
83
83
|
|
|
84
84
|
export interface ContextWindowManagerOptions {
|
|
85
85
|
provider: Provider;
|
|
86
|
-
systemPrompt: string;
|
|
86
|
+
systemPrompt: string | (() => string);
|
|
87
87
|
config: ContextWindowConfig;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
export class ContextWindowManager {
|
|
91
91
|
private readonly provider: Provider;
|
|
92
|
-
private readonly
|
|
92
|
+
private readonly _systemPrompt: string | (() => string);
|
|
93
93
|
private readonly config: ContextWindowConfig;
|
|
94
|
+
/**
|
|
95
|
+
* Cached resolved system prompt. Lazily populated on first access via the
|
|
96
|
+
* `systemPrompt` getter and cleared after each compaction pass so the next
|
|
97
|
+
* pass picks up any prompt changes.
|
|
98
|
+
*/
|
|
99
|
+
private _resolvedSystemPrompt: string | undefined;
|
|
94
100
|
|
|
95
101
|
constructor(options: ContextWindowManagerOptions) {
|
|
96
102
|
this.provider = options.provider;
|
|
97
|
-
this.
|
|
103
|
+
this._systemPrompt = options.systemPrompt;
|
|
98
104
|
this.config = options.config;
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
/** Lazily resolve and cache the system prompt for the duration of a compaction pass. */
|
|
108
|
+
private get systemPrompt(): string {
|
|
109
|
+
if (this._resolvedSystemPrompt !== undefined) {
|
|
110
|
+
return this._resolvedSystemPrompt;
|
|
111
|
+
}
|
|
112
|
+
const resolved =
|
|
113
|
+
typeof this._systemPrompt === "function"
|
|
114
|
+
? this._systemPrompt()
|
|
115
|
+
: this._systemPrompt;
|
|
116
|
+
this._resolvedSystemPrompt = resolved;
|
|
117
|
+
return resolved;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private clearSystemPromptCache(): void {
|
|
121
|
+
this._resolvedSystemPrompt = undefined;
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
/**
|
|
102
125
|
* Cheap pre-check: returns whether the estimated token count exceeds
|
|
103
126
|
* the compaction threshold, along with the estimated token count so
|
|
@@ -106,19 +129,35 @@ export class ContextWindowManager {
|
|
|
106
129
|
*/
|
|
107
130
|
shouldCompact(messages: Message[]): ShouldCompactResult {
|
|
108
131
|
if (!this.config.enabled) return { needed: false, estimatedTokens: 0 };
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
try {
|
|
133
|
+
const estimated = estimatePromptTokens(messages, this.systemPrompt, {
|
|
134
|
+
providerName: this.provider.name,
|
|
135
|
+
});
|
|
136
|
+
const threshold = Math.floor(
|
|
137
|
+
this.config.maxInputTokens * this.config.compactThreshold,
|
|
138
|
+
);
|
|
139
|
+
return { needed: estimated >= threshold, estimatedTokens: estimated };
|
|
140
|
+
} finally {
|
|
141
|
+
this.clearSystemPromptCache();
|
|
142
|
+
}
|
|
116
143
|
}
|
|
117
144
|
|
|
118
145
|
async maybeCompact(
|
|
119
146
|
messages: Message[],
|
|
120
147
|
signal?: AbortSignal,
|
|
121
148
|
options?: ContextWindowCompactOptions,
|
|
149
|
+
): Promise<ContextWindowResult> {
|
|
150
|
+
try {
|
|
151
|
+
return await this._maybeCompact(messages, signal, options);
|
|
152
|
+
} finally {
|
|
153
|
+
this.clearSystemPromptCache();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private async _maybeCompact(
|
|
158
|
+
messages: Message[],
|
|
159
|
+
signal?: AbortSignal,
|
|
160
|
+
options?: ContextWindowCompactOptions,
|
|
122
161
|
): Promise<ContextWindowResult> {
|
|
123
162
|
const previousEstimatedInputTokens =
|
|
124
163
|
options?.precomputedEstimate ??
|
|
@@ -632,7 +671,10 @@ function countPersistedMessages(messages: Message[]): number {
|
|
|
632
671
|
function isToolResultOnly(message: Message): boolean {
|
|
633
672
|
return (
|
|
634
673
|
message.content.length > 0 &&
|
|
635
|
-
message.content.every(
|
|
674
|
+
message.content.every(
|
|
675
|
+
(block) =>
|
|
676
|
+
block.type === "tool_result" || block.type === "web_search_tool_result",
|
|
677
|
+
)
|
|
636
678
|
);
|
|
637
679
|
}
|
|
638
680
|
|
|
@@ -17,9 +17,6 @@ import {
|
|
|
17
17
|
// Constants
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
|
|
20
|
-
/** Maximum number of attachments the assistant may emit per turn. */
|
|
21
|
-
export const MAX_ASSISTANT_ATTACHMENTS = 5;
|
|
22
|
-
|
|
23
20
|
/** Maximum size in bytes for a single assistant attachment (20 MB). */
|
|
24
21
|
export const MAX_ASSISTANT_ATTACHMENT_BYTES = 20 * 1024 * 1024;
|
|
25
22
|
|
|
@@ -122,10 +119,9 @@ export interface ValidatedDrafts {
|
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
/**
|
|
125
|
-
* Enforce per-
|
|
122
|
+
* Enforce per-attachment size cap.
|
|
126
123
|
*
|
|
127
124
|
* - Rejects individual drafts that exceed `MAX_ASSISTANT_ATTACHMENT_BYTES`.
|
|
128
|
-
* - Truncates the list at `MAX_ASSISTANT_ATTACHMENTS`.
|
|
129
125
|
*/
|
|
130
126
|
export function validateDrafts(
|
|
131
127
|
drafts: AssistantAttachmentDraft[],
|
|
@@ -144,14 +140,6 @@ export function validateDrafts(
|
|
|
144
140
|
continue;
|
|
145
141
|
}
|
|
146
142
|
|
|
147
|
-
if (accepted.length >= MAX_ASSISTANT_ATTACHMENTS) {
|
|
148
|
-
warnings.push(
|
|
149
|
-
`Skipped attachment "${draft.filename}": ` +
|
|
150
|
-
`exceeded maximum of ${MAX_ASSISTANT_ATTACHMENTS} attachments per turn.`,
|
|
151
|
-
);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
143
|
accepted.push(draft);
|
|
156
144
|
}
|
|
157
145
|
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
* Watches workspace files (config, prompts), protected directory
|
|
4
4
|
* (trust rules, secret allowlist), and skills directories for changes.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
existsSync,
|
|
8
|
+
type FSWatcher,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
watch,
|
|
12
|
+
} from "node:fs";
|
|
7
13
|
import { join } from "node:path";
|
|
8
14
|
|
|
9
15
|
import { getConfig, invalidateConfigCache } from "../config/loader.js";
|
|
@@ -98,8 +104,14 @@ export class ConfigWatcher {
|
|
|
98
104
|
* Start all file watchers. `onSessionEvict` is called when watched
|
|
99
105
|
* files change and sessions need to be evicted for reload.
|
|
100
106
|
* `onIdentityChanged` is called when IDENTITY.md changes on disk.
|
|
107
|
+
* `onMcpReload` is called when the MCP section of config.json changes
|
|
108
|
+
* or when a signal file appears in the workspace `signals/` directory.
|
|
101
109
|
*/
|
|
102
|
-
start(
|
|
110
|
+
start(
|
|
111
|
+
onSessionEvict: () => void,
|
|
112
|
+
onIdentityChanged?: () => void,
|
|
113
|
+
onMcpReload?: () => void,
|
|
114
|
+
): void {
|
|
103
115
|
const workspaceDir = getWorkspaceDir();
|
|
104
116
|
const protectedDir = join(getRootDir(), "protected");
|
|
105
117
|
|
|
@@ -107,8 +119,17 @@ export class ConfigWatcher {
|
|
|
107
119
|
"config.json": () => {
|
|
108
120
|
if (this.suppressReload) return;
|
|
109
121
|
try {
|
|
122
|
+
const prevConfig = getConfig();
|
|
123
|
+
const prevMcpFingerprint = JSON.stringify(prevConfig.mcp ?? {});
|
|
110
124
|
const changed = this.refreshConfigFromSources();
|
|
111
|
-
if (changed)
|
|
125
|
+
if (changed) {
|
|
126
|
+
onSessionEvict();
|
|
127
|
+
const newConfig = getConfig();
|
|
128
|
+
const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
|
|
129
|
+
if (newMcpFingerprint !== prevMcpFingerprint) {
|
|
130
|
+
onMcpReload?.();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
112
133
|
} catch (err) {
|
|
113
134
|
log.error(
|
|
114
135
|
{ err, configPath: join(workspaceDir, "config.json") },
|
|
@@ -185,6 +206,7 @@ export class ConfigWatcher {
|
|
|
185
206
|
);
|
|
186
207
|
}
|
|
187
208
|
|
|
209
|
+
this.startSignalsWatcher(onMcpReload);
|
|
188
210
|
this.startSkillsWatchers(onSessionEvict);
|
|
189
211
|
}
|
|
190
212
|
|
|
@@ -196,6 +218,42 @@ export class ConfigWatcher {
|
|
|
196
218
|
this.watchers = [];
|
|
197
219
|
}
|
|
198
220
|
|
|
221
|
+
private startSignalsWatcher(onMcpReload?: () => void): void {
|
|
222
|
+
const signalsDir = join(getWorkspaceDir(), "signals");
|
|
223
|
+
try {
|
|
224
|
+
if (!existsSync(signalsDir)) {
|
|
225
|
+
mkdirSync(signalsDir, { recursive: true });
|
|
226
|
+
}
|
|
227
|
+
} catch {
|
|
228
|
+
// If we can't create it, watching will also fail — handled below.
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const signalHandlers: Record<string, () => void> = {
|
|
232
|
+
"mcp-reload": () => {
|
|
233
|
+
onMcpReload?.();
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const watcher = watch(signalsDir, (_eventType, filename) => {
|
|
239
|
+
if (!filename) return;
|
|
240
|
+
const file = String(filename);
|
|
241
|
+
if (!signalHandlers[file]) return;
|
|
242
|
+
this.debounceTimers.schedule(`signal:${file}`, () => {
|
|
243
|
+
log.info({ file }, "Signal file detected");
|
|
244
|
+
signalHandlers[file]();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
this.watchers.push(watcher);
|
|
248
|
+
log.info({ dir: signalsDir }, "Watching signals directory");
|
|
249
|
+
} catch (err) {
|
|
250
|
+
log.warn(
|
|
251
|
+
{ err, dir: signalsDir },
|
|
252
|
+
"Failed to watch signals directory. Signal-based reload will be unavailable.",
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
199
257
|
private startSkillsWatchers(onSessionEvict: () => void): void {
|
|
200
258
|
const skillsDir = getWorkspaceSkillsDir();
|
|
201
259
|
if (!existsSync(skillsDir)) return;
|
|
@@ -143,7 +143,7 @@ function healthCheckHost(host: string): string {
|
|
|
143
143
|
/** Hit the daemon's HTTP /healthz endpoint. Returns true if it responds
|
|
144
144
|
* with HTTP 200 within the timeout — false on connection refused, timeout,
|
|
145
145
|
* or any other error. */
|
|
146
|
-
async function isHttpHealthy(): Promise<boolean> {
|
|
146
|
+
export async function isHttpHealthy(): Promise<boolean> {
|
|
147
147
|
const host = healthCheckHost(getRuntimeHttpHost());
|
|
148
148
|
const port = getRuntimeHttpPort();
|
|
149
149
|
try {
|