@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,286 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { Message } from "../providers/types.js";
|
|
4
|
+
import { compactAxTreeHistory, escapeAxTreeContent } from "./loop.js";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
/** Build a user message with a single tool_result containing an AX tree. */
|
|
11
|
+
function axTreeToolResult(id: string, axContent: string): Message {
|
|
12
|
+
return {
|
|
13
|
+
role: "user",
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: "tool_result",
|
|
17
|
+
tool_use_id: id,
|
|
18
|
+
content: `Some preamble\n<ax-tree>\n${axContent}\n</ax-tree>`,
|
|
19
|
+
is_error: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build an assistant message (no tool use). */
|
|
26
|
+
function assistantText(text: string): Message {
|
|
27
|
+
return {
|
|
28
|
+
role: "assistant",
|
|
29
|
+
content: [{ type: "text", text }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build a user message without AX tree content. */
|
|
34
|
+
function userText(text: string): Message {
|
|
35
|
+
return {
|
|
36
|
+
role: "user",
|
|
37
|
+
content: [{ type: "text", text }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// compactAxTreeHistory
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
describe("compactAxTreeHistory", () => {
|
|
46
|
+
test("returns messages unchanged when fewer than MAX_AX_TREES_IN_HISTORY AX trees", () => {
|
|
47
|
+
const messages: Message[] = [
|
|
48
|
+
axTreeToolResult("t1", "tree-1"),
|
|
49
|
+
assistantText("ok"),
|
|
50
|
+
axTreeToolResult("t2", "tree-2"),
|
|
51
|
+
];
|
|
52
|
+
const result = compactAxTreeHistory(messages);
|
|
53
|
+
expect(result).toBe(messages); // same reference — no copy
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("returns messages unchanged when exactly MAX_AX_TREES_IN_HISTORY AX trees", () => {
|
|
57
|
+
const messages: Message[] = [
|
|
58
|
+
axTreeToolResult("t1", "tree-1"),
|
|
59
|
+
assistantText("ok"),
|
|
60
|
+
axTreeToolResult("t2", "tree-2"),
|
|
61
|
+
];
|
|
62
|
+
const result = compactAxTreeHistory(messages);
|
|
63
|
+
expect(result).toBe(messages);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("strips oldest AX trees, keeps only last 2 from 5", () => {
|
|
67
|
+
const messages: Message[] = [
|
|
68
|
+
axTreeToolResult("t1", "tree-1"),
|
|
69
|
+
assistantText("ok"),
|
|
70
|
+
axTreeToolResult("t2", "tree-2"),
|
|
71
|
+
assistantText("ok"),
|
|
72
|
+
axTreeToolResult("t3", "tree-3"),
|
|
73
|
+
assistantText("ok"),
|
|
74
|
+
axTreeToolResult("t4", "tree-4"),
|
|
75
|
+
assistantText("ok"),
|
|
76
|
+
axTreeToolResult("t5", "tree-5"),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const result = compactAxTreeHistory(messages);
|
|
80
|
+
|
|
81
|
+
// Messages at indices 0, 2, 4 should have AX trees stripped (t1, t2, t3)
|
|
82
|
+
for (const idx of [0, 2, 4]) {
|
|
83
|
+
const block = result[idx].content[0];
|
|
84
|
+
expect(block.type).toBe("tool_result");
|
|
85
|
+
if (block.type === "tool_result") {
|
|
86
|
+
expect(block.content).not.toContain("<ax-tree>");
|
|
87
|
+
expect(block.content).toContain("<ax_tree_omitted />");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Messages at indices 6, 8 should still have AX trees (t4, t5)
|
|
92
|
+
for (const idx of [6, 8]) {
|
|
93
|
+
const block = result[idx].content[0];
|
|
94
|
+
expect(block.type).toBe("tool_result");
|
|
95
|
+
if (block.type === "tool_result") {
|
|
96
|
+
expect(block.content).toContain("<ax-tree>");
|
|
97
|
+
expect(block.content).not.toContain("<ax_tree_omitted />");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("does not modify assistant messages", () => {
|
|
103
|
+
const messages: Message[] = [
|
|
104
|
+
axTreeToolResult("t1", "tree-1"),
|
|
105
|
+
assistantText("ok"),
|
|
106
|
+
axTreeToolResult("t2", "tree-2"),
|
|
107
|
+
assistantText("response with <ax-tree>fake</ax-tree>"),
|
|
108
|
+
axTreeToolResult("t3", "tree-3"),
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const result = compactAxTreeHistory(messages);
|
|
112
|
+
|
|
113
|
+
// Assistant message should be unchanged
|
|
114
|
+
const assistantMsg = result[3];
|
|
115
|
+
expect(assistantMsg.content[0].type).toBe("text");
|
|
116
|
+
if (assistantMsg.content[0].type === "text") {
|
|
117
|
+
expect(assistantMsg.content[0].text).toContain("<ax-tree>");
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("does not modify user messages without tool_result blocks", () => {
|
|
122
|
+
const messages: Message[] = [
|
|
123
|
+
axTreeToolResult("t1", "tree-1"),
|
|
124
|
+
assistantText("ok"),
|
|
125
|
+
axTreeToolResult("t2", "tree-2"),
|
|
126
|
+
assistantText("ok"),
|
|
127
|
+
userText("Please help"),
|
|
128
|
+
axTreeToolResult("t3", "tree-3"),
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const result = compactAxTreeHistory(messages);
|
|
132
|
+
|
|
133
|
+
// The plain user text message should be untouched
|
|
134
|
+
expect(result[4]).toBe(messages[4]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("preserves non-AX-tree tool_result blocks in stripped messages", () => {
|
|
138
|
+
const messages: Message[] = [
|
|
139
|
+
{
|
|
140
|
+
role: "user",
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "tool_result",
|
|
144
|
+
tool_use_id: "t1",
|
|
145
|
+
content: "normal result without ax tree",
|
|
146
|
+
is_error: false,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
type: "tool_result",
|
|
150
|
+
tool_use_id: "t1-ax",
|
|
151
|
+
content: "<ax-tree>\ntree-1\n</ax-tree>",
|
|
152
|
+
is_error: false,
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
assistantText("ok"),
|
|
157
|
+
axTreeToolResult("t2", "tree-2"),
|
|
158
|
+
assistantText("ok"),
|
|
159
|
+
axTreeToolResult("t3", "tree-3"),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const result = compactAxTreeHistory(messages);
|
|
163
|
+
|
|
164
|
+
// First message should have the AX tree stripped but normal result preserved
|
|
165
|
+
const firstMsg = result[0];
|
|
166
|
+
const normalBlock = firstMsg.content[0];
|
|
167
|
+
expect(normalBlock.type).toBe("tool_result");
|
|
168
|
+
if (normalBlock.type === "tool_result") {
|
|
169
|
+
expect(normalBlock.content).toBe("normal result without ax tree");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const axBlock = firstMsg.content[1];
|
|
173
|
+
expect(axBlock.type).toBe("tool_result");
|
|
174
|
+
if (axBlock.type === "tool_result") {
|
|
175
|
+
expect(axBlock.content).toContain("<ax_tree_omitted />");
|
|
176
|
+
expect(axBlock.content).not.toContain("<ax-tree>");
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("returns empty array for empty input", () => {
|
|
181
|
+
const result = compactAxTreeHistory([]);
|
|
182
|
+
expect(result).toEqual([]);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("counts AX trees per block, not per message", () => {
|
|
186
|
+
// One message has two AX tree blocks — they should count as 2 trees
|
|
187
|
+
const messages: Message[] = [
|
|
188
|
+
{
|
|
189
|
+
role: "user",
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "tool_result",
|
|
193
|
+
tool_use_id: "t1a",
|
|
194
|
+
content: "<ax-tree>\ntree-1a\n</ax-tree>",
|
|
195
|
+
is_error: false,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
type: "tool_result",
|
|
199
|
+
tool_use_id: "t1b",
|
|
200
|
+
content: "<ax-tree>\ntree-1b\n</ax-tree>",
|
|
201
|
+
is_error: false,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
assistantText("ok"),
|
|
206
|
+
axTreeToolResult("t2", "tree-2"),
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const result = compactAxTreeHistory(messages);
|
|
210
|
+
|
|
211
|
+
// 3 total AX tree blocks, keep last 2 → strip only first block (t1a)
|
|
212
|
+
const msg0 = result[0];
|
|
213
|
+
const block0 = msg0.content[0];
|
|
214
|
+
expect(block0.type).toBe("tool_result");
|
|
215
|
+
if (block0.type === "tool_result") {
|
|
216
|
+
expect(block0.content).toContain("<ax_tree_omitted />");
|
|
217
|
+
expect(block0.content).not.toContain("<ax-tree>");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Second block in same message (t1b) should be kept
|
|
221
|
+
const block1 = msg0.content[1];
|
|
222
|
+
expect(block1.type).toBe("tool_result");
|
|
223
|
+
if (block1.type === "tool_result") {
|
|
224
|
+
expect(block1.content).toContain("<ax-tree>");
|
|
225
|
+
expect(block1.content).not.toContain("<ax_tree_omitted />");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Last message (t2) should also be kept
|
|
229
|
+
const lastBlock = result[2].content[0];
|
|
230
|
+
expect(lastBlock.type).toBe("tool_result");
|
|
231
|
+
if (lastBlock.type === "tool_result") {
|
|
232
|
+
expect(lastBlock.content).toContain("<ax-tree>");
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("is pure — does not mutate input messages", () => {
|
|
237
|
+
const messages: Message[] = [
|
|
238
|
+
axTreeToolResult("t1", "tree-1"),
|
|
239
|
+
assistantText("ok"),
|
|
240
|
+
axTreeToolResult("t2", "tree-2"),
|
|
241
|
+
assistantText("ok"),
|
|
242
|
+
axTreeToolResult("t3", "tree-3"),
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
// Deep copy to compare later
|
|
246
|
+
const originalContent = messages[0].content[0];
|
|
247
|
+
const originalText =
|
|
248
|
+
originalContent.type === "tool_result" ? originalContent.content : "";
|
|
249
|
+
|
|
250
|
+
compactAxTreeHistory(messages);
|
|
251
|
+
|
|
252
|
+
// Original message should be unchanged
|
|
253
|
+
const afterContent = messages[0].content[0];
|
|
254
|
+
const afterText =
|
|
255
|
+
afterContent.type === "tool_result" ? afterContent.content : "";
|
|
256
|
+
expect(afterText).toBe(originalText);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// escapeAxTreeContent
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
describe("escapeAxTreeContent", () => {
|
|
265
|
+
test("escapes literal </ax-tree> inside content", () => {
|
|
266
|
+
const input = "Some XML: <div></ax-tree></div>";
|
|
267
|
+
const result = escapeAxTreeContent(input);
|
|
268
|
+
expect(result).toBe("Some XML: <div></ax-tree></div>");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("handles case-insensitive matches", () => {
|
|
272
|
+
const input = "</AX-TREE> and </Ax-Tree>";
|
|
273
|
+
const result = escapeAxTreeContent(input);
|
|
274
|
+
expect(result).toBe("</ax-tree> and </ax-tree>");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("returns content unchanged when no closing tags present", () => {
|
|
278
|
+
const input = "Normal AX tree content with <ax-tree> opening tag";
|
|
279
|
+
const result = escapeAxTreeContent(input);
|
|
280
|
+
expect(result).toBe(input);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("handles empty string", () => {
|
|
284
|
+
expect(escapeAxTreeContent("")).toBe("");
|
|
285
|
+
});
|
|
286
|
+
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
applyStreamingSubstitution,
|
|
15
15
|
applySubstitutions,
|
|
16
16
|
} from "../tools/sensitive-output-placeholders.js";
|
|
17
|
-
import { getLogger
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
18
|
|
|
19
19
|
const log = getLogger("agent-loop");
|
|
20
20
|
|
|
@@ -35,6 +35,7 @@ export interface CheckpointInfo {
|
|
|
35
35
|
turnIndex: number;
|
|
36
36
|
toolCount: number;
|
|
37
37
|
hasToolUse: boolean;
|
|
38
|
+
history: Message[]; // current history snapshot for token estimation
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export type CheckpointDecision = "continue" | "yield";
|
|
@@ -71,7 +72,13 @@ export type AgentEvent =
|
|
|
71
72
|
toolUseId: string;
|
|
72
73
|
accumulatedJson: string;
|
|
73
74
|
}
|
|
74
|
-
| {
|
|
75
|
+
| {
|
|
76
|
+
type: "server_tool_start";
|
|
77
|
+
name: string;
|
|
78
|
+
toolUseId: string;
|
|
79
|
+
input: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
| { type: "server_tool_complete"; toolUseId: string }
|
|
75
82
|
| { type: "error"; error: Error }
|
|
76
83
|
| {
|
|
77
84
|
type: "usage";
|
|
@@ -179,7 +186,6 @@ export class AgentLoop {
|
|
|
179
186
|
let toolUseTurns = 0;
|
|
180
187
|
let nudgedForEmptyResponse = false;
|
|
181
188
|
let lastLlmCallTime = 0;
|
|
182
|
-
const debug = isDebug();
|
|
183
189
|
const rlog = requestId ? log.child({ requestId }) : log;
|
|
184
190
|
|
|
185
191
|
// Per-run substitution map for sensitive output placeholders.
|
|
@@ -191,7 +197,6 @@ export class AgentLoop {
|
|
|
191
197
|
while (true) {
|
|
192
198
|
if (signal?.aborted) break;
|
|
193
199
|
|
|
194
|
-
const turnStart = Date.now();
|
|
195
200
|
let toolUseBlocks: Extract<ContentBlock, { type: "tool_use" }>[] = [];
|
|
196
201
|
|
|
197
202
|
try {
|
|
@@ -227,22 +232,6 @@ export class AgentLoop {
|
|
|
227
232
|
providerConfig.tool_choice = this.config.toolChoice;
|
|
228
233
|
}
|
|
229
234
|
|
|
230
|
-
if (debug) {
|
|
231
|
-
rlog.debug(
|
|
232
|
-
{
|
|
233
|
-
systemPrompt: truncateForLog(turnSystemPrompt, 200),
|
|
234
|
-
messageCount: history.length,
|
|
235
|
-
lastMessage:
|
|
236
|
-
history.length > 0
|
|
237
|
-
? summarizeMessage(history[history.length - 1])
|
|
238
|
-
: null,
|
|
239
|
-
toolCount: currentTools.length,
|
|
240
|
-
config: providerConfig,
|
|
241
|
-
},
|
|
242
|
-
"Sending request to provider",
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
235
|
const preLlmResult = await getHookManager().trigger("pre-llm-call", {
|
|
247
236
|
systemPrompt: turnSystemPrompt,
|
|
248
237
|
messages: history,
|
|
@@ -275,7 +264,11 @@ export class AgentLoop {
|
|
|
275
264
|
// screenshots from accumulating in the context window. The LLM
|
|
276
265
|
// already saw each image on the turn it was captured; keeping
|
|
277
266
|
// base64 blobs in history rapidly exhausts the context budget.
|
|
278
|
-
|
|
267
|
+
// Also strip old AX tree snapshots to keep TTFT from growing
|
|
268
|
+
// linearly with step count in computer-use sessions.
|
|
269
|
+
const providerHistory = compactAxTreeHistory(
|
|
270
|
+
stripOldImageBlocks(history),
|
|
271
|
+
);
|
|
279
272
|
|
|
280
273
|
const response = await this.provider.sendMessage(
|
|
281
274
|
providerHistory,
|
|
@@ -319,6 +312,12 @@ export class AgentLoop {
|
|
|
319
312
|
type: "server_tool_start",
|
|
320
313
|
name: event.name,
|
|
321
314
|
toolUseId: event.toolUseId,
|
|
315
|
+
input: event.input,
|
|
316
|
+
});
|
|
317
|
+
} else if (event.type === "server_tool_complete") {
|
|
318
|
+
onEvent({
|
|
319
|
+
type: "server_tool_complete",
|
|
320
|
+
toolUseId: event.toolUseId,
|
|
322
321
|
});
|
|
323
322
|
}
|
|
324
323
|
},
|
|
@@ -328,33 +327,6 @@ export class AgentLoop {
|
|
|
328
327
|
|
|
329
328
|
const providerDurationMs = Date.now() - providerStart;
|
|
330
329
|
|
|
331
|
-
if (debug) {
|
|
332
|
-
rlog.debug(
|
|
333
|
-
{
|
|
334
|
-
providerDurationMs,
|
|
335
|
-
model: response.model,
|
|
336
|
-
stopReason: response.stopReason,
|
|
337
|
-
inputTokens: response.usage.inputTokens,
|
|
338
|
-
outputTokens: response.usage.outputTokens,
|
|
339
|
-
cacheCreationInputTokens: response.usage.cacheCreationInputTokens,
|
|
340
|
-
cacheReadInputTokens: response.usage.cacheReadInputTokens,
|
|
341
|
-
contentBlocks: response.content.map((b) => ({
|
|
342
|
-
type: b.type,
|
|
343
|
-
...(b.type === "text"
|
|
344
|
-
? { text: truncateForLog(b.text, 1200) }
|
|
345
|
-
: {}),
|
|
346
|
-
...(b.type === "tool_use"
|
|
347
|
-
? {
|
|
348
|
-
name: b.name,
|
|
349
|
-
input: truncateForLog(JSON.stringify(b.input), 1200),
|
|
350
|
-
}
|
|
351
|
-
: {}),
|
|
352
|
-
})),
|
|
353
|
-
},
|
|
354
|
-
"Provider response received",
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
330
|
onEvent({
|
|
359
331
|
type: "usage",
|
|
360
332
|
inputTokens: response.usage.inputTokens,
|
|
@@ -443,16 +415,6 @@ export class AgentLoop {
|
|
|
443
415
|
name: toolUse.name,
|
|
444
416
|
input: toolUse.input,
|
|
445
417
|
});
|
|
446
|
-
|
|
447
|
-
if (debug) {
|
|
448
|
-
rlog.debug(
|
|
449
|
-
{
|
|
450
|
-
tool: toolUse.name,
|
|
451
|
-
input: truncateForLog(JSON.stringify(toolUse.input), 300),
|
|
452
|
-
},
|
|
453
|
-
"Executing tool",
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
418
|
}
|
|
457
419
|
|
|
458
420
|
// If already cancelled, synthesize cancelled results and stop
|
|
@@ -469,49 +431,11 @@ export class AgentLoop {
|
|
|
469
431
|
break;
|
|
470
432
|
}
|
|
471
433
|
|
|
472
|
-
// Guard against dual-control-mode conflicts in a single turn.
|
|
473
|
-
// If the model escalates to foreground computer control, browser_* tools
|
|
474
|
-
// in the same response create competing browser sessions/windows and can
|
|
475
|
-
// thrash renderer CPU. Reject browser_* calls in that turn.
|
|
476
|
-
const hasComputerUseEscalation = toolUseBlocks.some(
|
|
477
|
-
(toolUse) => toolUse.name === "computer_use_request_control",
|
|
478
|
-
);
|
|
479
|
-
const blockedBrowserToolIds = hasComputerUseEscalation
|
|
480
|
-
? new Set(
|
|
481
|
-
toolUseBlocks
|
|
482
|
-
.filter((toolUse) => toolUse.name.startsWith("browser_"))
|
|
483
|
-
.map((toolUse) => toolUse.id),
|
|
484
|
-
)
|
|
485
|
-
: new Set<string>();
|
|
486
|
-
|
|
487
|
-
if (blockedBrowserToolIds.size > 0) {
|
|
488
|
-
log.warn(
|
|
489
|
-
{
|
|
490
|
-
blockedBrowserToolCount: blockedBrowserToolIds.size,
|
|
491
|
-
toolNames: toolUseBlocks.map((toolUse) => toolUse.name),
|
|
492
|
-
},
|
|
493
|
-
"Blocking browser_* tools: computer_use_request_control was requested in same turn",
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
434
|
// Execute all tools concurrently for reduced latency.
|
|
498
435
|
// Race against the abort signal so cancellation isn't blocked by
|
|
499
436
|
// stuck tools (e.g. a hung browser navigation).
|
|
500
437
|
const toolExecutionPromise = Promise.all(
|
|
501
438
|
toolUseBlocks.map(async (toolUse) => {
|
|
502
|
-
const toolStart = Date.now();
|
|
503
|
-
|
|
504
|
-
if (blockedBrowserToolIds.has(toolUse.id)) {
|
|
505
|
-
return {
|
|
506
|
-
toolUse,
|
|
507
|
-
result: {
|
|
508
|
-
content:
|
|
509
|
-
"Error: browser_* tools cannot run in the same turn as computer_use_request_control. Continue using the foreground computer-use session only.",
|
|
510
|
-
isError: true,
|
|
511
|
-
},
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
|
|
515
439
|
const result = await this.toolExecutor!(
|
|
516
440
|
toolUse.name,
|
|
517
441
|
toolUse.input,
|
|
@@ -525,20 +449,6 @@ export class AgentLoop {
|
|
|
525
449
|
toolUse.id,
|
|
526
450
|
);
|
|
527
451
|
|
|
528
|
-
const toolDurationMs = Date.now() - toolStart;
|
|
529
|
-
|
|
530
|
-
if (debug) {
|
|
531
|
-
rlog.debug(
|
|
532
|
-
{
|
|
533
|
-
tool: toolUse.name,
|
|
534
|
-
toolDurationMs,
|
|
535
|
-
isError: result.isError,
|
|
536
|
-
output: truncateForLog(result.content, 300),
|
|
537
|
-
},
|
|
538
|
-
"Tool execution complete",
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
452
|
return { toolUse, result };
|
|
543
453
|
}),
|
|
544
454
|
);
|
|
@@ -658,25 +568,13 @@ export class AgentLoop {
|
|
|
658
568
|
// Add tool results as a user message and continue the loop
|
|
659
569
|
history.push({ role: "user", content: resultBlocks });
|
|
660
570
|
|
|
661
|
-
if (debug) {
|
|
662
|
-
const turnDurationMs = Date.now() - turnStart;
|
|
663
|
-
rlog.debug(
|
|
664
|
-
{
|
|
665
|
-
turnDurationMs,
|
|
666
|
-
providerDurationMs,
|
|
667
|
-
toolCount: toolUseBlocks.length,
|
|
668
|
-
turn: toolUseTurns,
|
|
669
|
-
},
|
|
670
|
-
"Turn complete",
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
571
|
// Invoke checkpoint callback after tool results are in history
|
|
675
572
|
if (onCheckpoint) {
|
|
676
573
|
const decision = onCheckpoint({
|
|
677
574
|
turnIndex: toolUseTurns - 1, // 0-based (toolUseTurns was already incremented)
|
|
678
575
|
toolCount: toolUseBlocks.length,
|
|
679
576
|
hasToolUse: true,
|
|
577
|
+
history,
|
|
680
578
|
});
|
|
681
579
|
if (decision === "yield") {
|
|
682
580
|
break;
|
|
@@ -715,14 +613,89 @@ export class AgentLoop {
|
|
|
715
613
|
}
|
|
716
614
|
}
|
|
717
615
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
616
|
+
/** Number of most-recent AX tree snapshots to keep in conversation history. */
|
|
617
|
+
const MAX_AX_TREES_IN_HISTORY = 2;
|
|
618
|
+
|
|
619
|
+
/** Regex that matches the `<ax-tree>...</ax-tree>` markers. */
|
|
620
|
+
const AX_TREE_PATTERN = /<ax-tree>[\s\S]*?<\/ax-tree>/g;
|
|
621
|
+
const AX_TREE_PLACEHOLDER = "<ax_tree_omitted />";
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Escapes any literal `</ax-tree>` occurrences inside AX tree content so
|
|
625
|
+
* that the non-greedy compaction regex (`AX_TREE_PATTERN`) does not stop
|
|
626
|
+
* prematurely when the user happens to be viewing XML/HTML source that
|
|
627
|
+
* contains the closing tag. The escaped content does not need to be
|
|
628
|
+
* unescaped because compaction replaces the entire block with a placeholder.
|
|
629
|
+
*/
|
|
630
|
+
export function escapeAxTreeContent(content: string): string {
|
|
631
|
+
return content.replace(/<\/ax-tree>/gi, "</ax-tree>");
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Returns a shallow copy of `messages` where all but the most recent
|
|
636
|
+
* `MAX_AX_TREES_IN_HISTORY` `<ax-tree>` blocks have been replaced with a
|
|
637
|
+
* short placeholder. This keeps the conversation context small so that
|
|
638
|
+
* TTFT does not grow linearly with step count in computer-use sessions.
|
|
639
|
+
*
|
|
640
|
+
* Counting is per-block, not per-message — a single user message can
|
|
641
|
+
* contain multiple tool_result blocks each with their own AX tree snapshot.
|
|
642
|
+
*/
|
|
643
|
+
export function compactAxTreeHistory(messages: Message[]): Message[] {
|
|
644
|
+
// Collect (messageIndex, blockIndex) for every tool_result block with <ax-tree>
|
|
645
|
+
const axBlocks: Array<{ msgIdx: number; blockIdx: number }> = [];
|
|
646
|
+
for (let i = 0; i < messages.length; i++) {
|
|
647
|
+
const msg = messages[i];
|
|
648
|
+
if (msg.role !== "user") continue;
|
|
649
|
+
for (let j = 0; j < msg.content.length; j++) {
|
|
650
|
+
const block = msg.content[j];
|
|
651
|
+
if (
|
|
652
|
+
block.type === "tool_result" &&
|
|
653
|
+
typeof block.content === "string" &&
|
|
654
|
+
block.content.includes("<ax-tree>")
|
|
655
|
+
) {
|
|
656
|
+
axBlocks.push({ msgIdx: i, blockIdx: j });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (axBlocks.length <= MAX_AX_TREES_IN_HISTORY) {
|
|
662
|
+
return messages;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Build a set of "msgIdx:blockIdx" keys for blocks that should be stripped
|
|
666
|
+
const toStrip = new Set(
|
|
667
|
+
axBlocks
|
|
668
|
+
.slice(0, -MAX_AX_TREES_IN_HISTORY)
|
|
669
|
+
.map((b) => `${b.msgIdx}:${b.blockIdx}`),
|
|
670
|
+
);
|
|
671
|
+
|
|
672
|
+
return messages.map((msg, idx) => {
|
|
673
|
+
// Quick check: does this message have any blocks to strip?
|
|
674
|
+
const hasStripTarget = msg.content.some((_, j) =>
|
|
675
|
+
toStrip.has(`${idx}:${j}`),
|
|
676
|
+
);
|
|
677
|
+
if (!hasStripTarget) return msg;
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
...msg,
|
|
681
|
+
content: msg.content.map((block, j) => {
|
|
682
|
+
if (
|
|
683
|
+
toStrip.has(`${idx}:${j}`) &&
|
|
684
|
+
block.type === "tool_result" &&
|
|
685
|
+
typeof block.content === "string"
|
|
686
|
+
) {
|
|
687
|
+
return {
|
|
688
|
+
...block,
|
|
689
|
+
content: block.content.replace(
|
|
690
|
+
AX_TREE_PATTERN,
|
|
691
|
+
AX_TREE_PLACEHOLDER,
|
|
692
|
+
),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
return block;
|
|
696
|
+
}),
|
|
697
|
+
};
|
|
698
|
+
});
|
|
726
699
|
}
|
|
727
700
|
|
|
728
701
|
/**
|
package/src/approvals/AGENTS.md
CHANGED
|
@@ -16,7 +16,7 @@ Conversational guardian verification control-plane invocation is guardian-only.
|
|
|
16
16
|
|
|
17
17
|
## Memory Provenance Invariant
|
|
18
18
|
|
|
19
|
-
All memory
|
|
19
|
+
All memory retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not receive memory recall results. This invariant is enforced in `indexer.ts` (write gate) and `session-memory.ts` (read gate).
|
|
20
20
|
|
|
21
21
|
## Guardian Privilege Isolation Invariant
|
|
22
22
|
|
|
@@ -424,11 +424,17 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
424
424
|
dedupeKey: `trusted-contact:denied:${request.id}`,
|
|
425
425
|
});
|
|
426
426
|
} else if (desktopDeliverUrl && requesterChatId) {
|
|
427
|
+
// For Slack, route to DM via requesterExternalUserId (user ID) instead
|
|
428
|
+
// of requesterChatId (channel ID) to avoid posting in public channels.
|
|
429
|
+
const targetChatId =
|
|
430
|
+
channel === "slack" && requesterExternalUserId
|
|
431
|
+
? requesterExternalUserId
|
|
432
|
+
: requesterChatId;
|
|
427
433
|
try {
|
|
428
434
|
await deliverChannelReply(
|
|
429
435
|
desktopDeliverUrl,
|
|
430
436
|
{
|
|
431
|
-
chatId:
|
|
437
|
+
chatId: targetChatId,
|
|
432
438
|
text: "Your access request has been denied by the guardian.",
|
|
433
439
|
assistantId,
|
|
434
440
|
},
|
|
@@ -601,11 +607,17 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
601
607
|
});
|
|
602
608
|
}
|
|
603
609
|
} else if (desktopDeliverUrl && requesterChatId) {
|
|
610
|
+
// For Slack, route to DM via requesterExternalUserId (user ID) instead
|
|
611
|
+
// of requesterChatId (channel ID) to avoid posting in public channels.
|
|
612
|
+
const targetChatId =
|
|
613
|
+
channel === "slack" && requesterExternalUserId
|
|
614
|
+
? requesterExternalUserId
|
|
615
|
+
: requesterChatId;
|
|
604
616
|
try {
|
|
605
617
|
await deliverChannelReply(
|
|
606
618
|
desktopDeliverUrl,
|
|
607
619
|
{
|
|
608
|
-
chatId:
|
|
620
|
+
chatId: targetChatId,
|
|
609
621
|
text:
|
|
610
622
|
"Your access request has been approved! " +
|
|
611
623
|
"Please enter the 6-digit verification code you receive from the guardian.",
|