vellum 0.2.1 → 0.2.7
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/README.md +15 -2
- package/bun.lock +71 -100
- package/package.json +5 -3
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +305 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twilio-config.test.ts +221 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +71 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +100 -2
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-conflict-gate.test.ts +28 -25
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +8 -8
- package/src/calls/twilio-provider.ts +13 -9
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/twilio-webhook-urls.ts +50 -0
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +34 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +165 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +205 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +32 -4
- package/src/daemon/ipc-contract.ts +156 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +75 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-conflict-gate.ts +18 -109
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/conflict-intent.ts +114 -0
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/job-handlers/conflict.ts +23 -1
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/gateway-client.ts +36 -0
- package/src/runtime/http-server.ts +166 -22
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +125 -88
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +207 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +269 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session messaging methods: enqueue, persistUserMessage,
|
|
3
|
+
* redirectToSecurePrompt, and queue/confirmation helpers.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from Session to keep the class focused on coordination.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { v4 as uuid } from 'uuid';
|
|
9
|
+
import type { Message } from '../providers/types.js';
|
|
10
|
+
import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
|
|
11
|
+
import { createUserMessage } from '../agent/message-types.js';
|
|
12
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
13
|
+
import type { SecretPrompter } from '../permissions/secret-prompter.js';
|
|
14
|
+
import type { MessageQueue } from './session-queue-manager.js';
|
|
15
|
+
import { getLogger } from '../util/logger.js';
|
|
16
|
+
|
|
17
|
+
const log = getLogger('session-messaging');
|
|
18
|
+
|
|
19
|
+
// ── Context Interface ────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export interface MessagingSessionContext {
|
|
22
|
+
readonly conversationId: string;
|
|
23
|
+
messages: Message[];
|
|
24
|
+
processing: boolean;
|
|
25
|
+
abortController: AbortController | null;
|
|
26
|
+
currentRequestId?: string;
|
|
27
|
+
readonly queue: MessageQueue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── enqueueMessage ───────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export function enqueueMessage(
|
|
33
|
+
ctx: MessagingSessionContext,
|
|
34
|
+
content: string,
|
|
35
|
+
attachments: UserMessageAttachment[],
|
|
36
|
+
onEvent: (msg: ServerMessage) => void,
|
|
37
|
+
requestId: string,
|
|
38
|
+
activeSurfaceId?: string,
|
|
39
|
+
currentPage?: string,
|
|
40
|
+
metadata?: Record<string, unknown>,
|
|
41
|
+
): { queued: boolean; rejected?: boolean; requestId: string } {
|
|
42
|
+
if (!ctx.processing) {
|
|
43
|
+
return { queued: false, requestId };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const pushed = ctx.queue.push({ content, attachments, requestId, onEvent, activeSurfaceId, currentPage, metadata });
|
|
47
|
+
if (!pushed) {
|
|
48
|
+
return { queued: false, rejected: true, requestId };
|
|
49
|
+
}
|
|
50
|
+
return { queued: true, requestId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── persistUserMessage ───────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export function persistUserMessage(
|
|
56
|
+
ctx: MessagingSessionContext,
|
|
57
|
+
content: string,
|
|
58
|
+
attachments: UserMessageAttachment[],
|
|
59
|
+
requestId?: string,
|
|
60
|
+
metadata?: Record<string, unknown>,
|
|
61
|
+
): string {
|
|
62
|
+
if (ctx.processing) {
|
|
63
|
+
throw new Error('Session is already processing a message');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!content.trim() && attachments.length === 0) {
|
|
67
|
+
throw new Error('Message content or attachments are required');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const reqId = requestId ?? uuid();
|
|
71
|
+
ctx.currentRequestId = reqId;
|
|
72
|
+
ctx.processing = true;
|
|
73
|
+
ctx.abortController = new AbortController();
|
|
74
|
+
|
|
75
|
+
const userMessage = createUserMessage(content, attachments.map((attachment) => ({
|
|
76
|
+
id: attachment.id,
|
|
77
|
+
filename: attachment.filename,
|
|
78
|
+
mimeType: attachment.mimeType,
|
|
79
|
+
data: attachment.data,
|
|
80
|
+
extractedText: attachment.extractedText,
|
|
81
|
+
})));
|
|
82
|
+
ctx.messages.push(userMessage);
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const persistedUserMessage = conversationStore.addMessage(
|
|
86
|
+
ctx.conversationId,
|
|
87
|
+
'user',
|
|
88
|
+
JSON.stringify(userMessage.content),
|
|
89
|
+
metadata,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!persistedUserMessage.id) {
|
|
93
|
+
throw new Error('Failed to persist user message');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return persistedUserMessage.id;
|
|
97
|
+
} catch (err) {
|
|
98
|
+
ctx.messages.pop();
|
|
99
|
+
ctx.processing = false;
|
|
100
|
+
ctx.abortController = null;
|
|
101
|
+
ctx.currentRequestId = undefined;
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── redirectToSecurePrompt ───────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export function redirectToSecurePrompt(
|
|
109
|
+
conversationId: string,
|
|
110
|
+
secretPrompter: SecretPrompter,
|
|
111
|
+
detectedTypes: string[],
|
|
112
|
+
onComplete?: () => void,
|
|
113
|
+
): void {
|
|
114
|
+
const service = 'detected';
|
|
115
|
+
const field = detectedTypes.join(',');
|
|
116
|
+
secretPrompter.prompt(
|
|
117
|
+
service, field,
|
|
118
|
+
'Secure Credential Entry',
|
|
119
|
+
'Your message contained a secret. Please enter it here instead — it will be stored securely and never sent to the AI.',
|
|
120
|
+
undefined, conversationId,
|
|
121
|
+
).then(async (result) => {
|
|
122
|
+
if (!result.value) return;
|
|
123
|
+
|
|
124
|
+
const { setSecureKey } = await import('../security/secure-keys.js');
|
|
125
|
+
const { upsertCredentialMetadata } = await import('../tools/credentials/metadata-store.js');
|
|
126
|
+
|
|
127
|
+
if (result.delivery === 'transient_send') {
|
|
128
|
+
const { credentialBroker } = await import('../tools/credentials/broker.js');
|
|
129
|
+
credentialBroker.injectTransient(service, field, result.value);
|
|
130
|
+
try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
|
|
131
|
+
log.info({ service, field, delivery: 'transient_send' }, 'Ingress redirect: transient credential injected');
|
|
132
|
+
} else {
|
|
133
|
+
const key = `credential:${service}:${field}`;
|
|
134
|
+
const stored = setSecureKey(key, result.value);
|
|
135
|
+
if (stored) {
|
|
136
|
+
try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
|
|
137
|
+
log.info({ service, field }, 'Ingress redirect: credential stored');
|
|
138
|
+
} else {
|
|
139
|
+
log.warn({ service, field }, 'Ingress redirect: secure storage write failed');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}).catch(() => { /* prompt timeout or cancel is fine */ }).finally(() => {
|
|
143
|
+
onComplete?.();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch and call notifier registration/unregistration, extracted from
|
|
3
|
+
* the Session constructor and dispose/abort methods.
|
|
4
|
+
*
|
|
5
|
+
* Notifier callbacks read from the provided context object at invocation
|
|
6
|
+
* time (not registration time), so they always see the latest sendToClient
|
|
7
|
+
* and messages references even after updateClient().
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Message } from '../providers/types.js';
|
|
11
|
+
import type { ServerMessage } from './ipc-protocol.js';
|
|
12
|
+
import { createAssistantMessage } from '../agent/message-types.js';
|
|
13
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
14
|
+
import {
|
|
15
|
+
registerWatchStartNotifier,
|
|
16
|
+
unregisterWatchStartNotifier,
|
|
17
|
+
registerWatchCommentaryNotifier,
|
|
18
|
+
unregisterWatchCommentaryNotifier,
|
|
19
|
+
registerWatchCompletionNotifier,
|
|
20
|
+
unregisterWatchCompletionNotifier,
|
|
21
|
+
pruneWatchSessions,
|
|
22
|
+
} from '../tools/watch/watch-state.js';
|
|
23
|
+
import type { WatchSession } from '../tools/watch/watch-state.js';
|
|
24
|
+
import { lastCommentaryBySession, lastSummaryBySession } from './watch-handler.js';
|
|
25
|
+
import {
|
|
26
|
+
registerCallQuestionNotifier,
|
|
27
|
+
unregisterCallQuestionNotifier,
|
|
28
|
+
registerCallCompletionNotifier,
|
|
29
|
+
unregisterCallCompletionNotifier,
|
|
30
|
+
} from '../calls/call-state.js';
|
|
31
|
+
import { getCallSession, getCallEvents } from '../calls/call-store.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subset of Session state that notifier callbacks need to read at
|
|
35
|
+
* invocation time. Properties are read lazily from this reference.
|
|
36
|
+
*/
|
|
37
|
+
export interface NotifierSessionContext {
|
|
38
|
+
sendToClient: (msg: ServerMessage) => void;
|
|
39
|
+
messages: Message[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register watch and call notifiers for a session. Call once during
|
|
44
|
+
* construction; the notifier callbacks close over `ctx` so they see
|
|
45
|
+
* live sendToClient/messages values.
|
|
46
|
+
*/
|
|
47
|
+
export function registerSessionNotifiers(
|
|
48
|
+
conversationId: string,
|
|
49
|
+
ctx: NotifierSessionContext,
|
|
50
|
+
): void {
|
|
51
|
+
registerWatchStartNotifier(conversationId, (session: WatchSession) => {
|
|
52
|
+
ctx.sendToClient({
|
|
53
|
+
type: 'watch_started',
|
|
54
|
+
sessionId: conversationId,
|
|
55
|
+
watchId: session.watchId,
|
|
56
|
+
durationSeconds: session.durationSeconds,
|
|
57
|
+
intervalSeconds: session.intervalSeconds,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
registerWatchCommentaryNotifier(conversationId, (_session: WatchSession) => {
|
|
62
|
+
const commentary = lastCommentaryBySession.get(conversationId);
|
|
63
|
+
if (commentary) {
|
|
64
|
+
lastCommentaryBySession.delete(conversationId);
|
|
65
|
+
ctx.sendToClient({
|
|
66
|
+
type: 'assistant_text_delta',
|
|
67
|
+
text: commentary,
|
|
68
|
+
sessionId: conversationId,
|
|
69
|
+
});
|
|
70
|
+
ctx.sendToClient({
|
|
71
|
+
type: 'message_complete',
|
|
72
|
+
sessionId: conversationId,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
registerWatchCompletionNotifier(conversationId, (_session: WatchSession) => {
|
|
78
|
+
const summary = lastSummaryBySession.get(conversationId);
|
|
79
|
+
if (summary) {
|
|
80
|
+
lastSummaryBySession.delete(conversationId);
|
|
81
|
+
ctx.sendToClient({
|
|
82
|
+
type: 'assistant_text_delta',
|
|
83
|
+
text: summary,
|
|
84
|
+
sessionId: conversationId,
|
|
85
|
+
});
|
|
86
|
+
ctx.sendToClient({
|
|
87
|
+
type: 'message_complete',
|
|
88
|
+
sessionId: conversationId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
registerCallQuestionNotifier(conversationId, (callSessionId: string, question: string) => {
|
|
94
|
+
const callSession = getCallSession(callSessionId);
|
|
95
|
+
const callee = callSession?.toNumber ?? 'the caller';
|
|
96
|
+
const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Reply in this thread to answer._`;
|
|
97
|
+
|
|
98
|
+
conversationStore.addMessage(
|
|
99
|
+
conversationId,
|
|
100
|
+
'assistant',
|
|
101
|
+
JSON.stringify([{ type: 'text', text: questionText }]),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
ctx.messages.push(createAssistantMessage(questionText));
|
|
105
|
+
|
|
106
|
+
ctx.sendToClient({
|
|
107
|
+
type: 'assistant_text_delta',
|
|
108
|
+
text: questionText,
|
|
109
|
+
sessionId: conversationId,
|
|
110
|
+
});
|
|
111
|
+
ctx.sendToClient({
|
|
112
|
+
type: 'message_complete',
|
|
113
|
+
sessionId: conversationId,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
registerCallCompletionNotifier(conversationId, (callSessionId: string) => {
|
|
118
|
+
const callSession = getCallSession(callSessionId);
|
|
119
|
+
const events = getCallEvents(callSessionId);
|
|
120
|
+
const duration = callSession?.endedAt && callSession?.startedAt
|
|
121
|
+
? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
|
|
122
|
+
: null;
|
|
123
|
+
const durationStr = duration !== null ? ` (${duration}s)` : '';
|
|
124
|
+
const summaryText = `**Call completed**${durationStr}. ${events.length} event(s) recorded.`;
|
|
125
|
+
|
|
126
|
+
conversationStore.addMessage(
|
|
127
|
+
conversationId,
|
|
128
|
+
'assistant',
|
|
129
|
+
JSON.stringify([{ type: 'text', text: summaryText }]),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
ctx.messages.push(createAssistantMessage(summaryText));
|
|
133
|
+
|
|
134
|
+
ctx.sendToClient({
|
|
135
|
+
type: 'assistant_text_delta',
|
|
136
|
+
text: summaryText,
|
|
137
|
+
sessionId: conversationId,
|
|
138
|
+
});
|
|
139
|
+
ctx.sendToClient({
|
|
140
|
+
type: 'message_complete',
|
|
141
|
+
sessionId: conversationId,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Unregister watch notifiers and prune watch sessions. Called during
|
|
148
|
+
* abort when the session is actively processing.
|
|
149
|
+
*/
|
|
150
|
+
export function unregisterWatchNotifiers(conversationId: string): void {
|
|
151
|
+
unregisterWatchStartNotifier(conversationId);
|
|
152
|
+
unregisterWatchCommentaryNotifier(conversationId);
|
|
153
|
+
unregisterWatchCompletionNotifier(conversationId);
|
|
154
|
+
pruneWatchSessions(conversationId);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Unregister call notifiers. Called during dispose regardless of
|
|
159
|
+
* processing state (notifiers are registered in the constructor).
|
|
160
|
+
*/
|
|
161
|
+
export function unregisterCallNotifiers(conversationId: string): void {
|
|
162
|
+
unregisterCallQuestionNotifier(conversationId);
|
|
163
|
+
unregisterCallCompletionNotifier(conversationId);
|
|
164
|
+
}
|
|
@@ -55,7 +55,7 @@ export interface ProcessSessionContext {
|
|
|
55
55
|
currentPage?: string;
|
|
56
56
|
/** Request-scoped skill IDs preactivated via slash resolution. */
|
|
57
57
|
preactivatedSkillIds?: string[];
|
|
58
|
-
persistUserMessage(content: string, attachments: UserMessageAttachment[], requestId?: string): string;
|
|
58
|
+
persistUserMessage(content: string, attachments: UserMessageAttachment[], requestId?: string, metadata?: Record<string, unknown>): string;
|
|
59
59
|
runAgentLoop(
|
|
60
60
|
content: string,
|
|
61
61
|
userMessageId: string,
|
|
@@ -155,7 +155,7 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
|
|
|
155
155
|
// resolves early (no runAgentLoop call), so we must continue draining.
|
|
156
156
|
let userMessageId: string;
|
|
157
157
|
try {
|
|
158
|
-
userMessageId = session.persistUserMessage(resolvedContent, next.attachments, next.requestId);
|
|
158
|
+
userMessageId = session.persistUserMessage(resolvedContent, next.attachments, next.requestId, next.metadata);
|
|
159
159
|
} catch (err) {
|
|
160
160
|
const message = err instanceof Error ? err.message : String(err);
|
|
161
161
|
log.error({ err, conversationId: session.conversationId, requestId: next.requestId }, 'Failed to persist queued message');
|
|
@@ -312,6 +312,44 @@ export function stripWorkspaceTopLevelContext(messages: Message[]): Message[] {
|
|
|
312
312
|
}).filter((message): message is NonNullable<typeof message> => message !== null);
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Prepend temporal context to a user message so the model has
|
|
317
|
+
* authoritative date/time grounding each turn.
|
|
318
|
+
*/
|
|
319
|
+
export function injectTemporalContext(message: Message, temporalContext: string): Message {
|
|
320
|
+
return {
|
|
321
|
+
...message,
|
|
322
|
+
content: [
|
|
323
|
+
{ type: 'text', text: temporalContext },
|
|
324
|
+
...message.content,
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Strip `<temporal_context>` blocks injected by `injectTemporalContext`.
|
|
331
|
+
* Called after the agent run to prevent temporal context from persisting
|
|
332
|
+
* in session history.
|
|
333
|
+
*
|
|
334
|
+
* Uses a specific prefix (`<temporal_context>\nToday:`) so that
|
|
335
|
+
* user-authored text that happens to start with `<temporal_context>`
|
|
336
|
+
* is preserved.
|
|
337
|
+
*/
|
|
338
|
+
const TEMPORAL_INJECTED_PREFIX = '<temporal_context>\nToday:';
|
|
339
|
+
|
|
340
|
+
export function stripTemporalContext(messages: Message[]): Message[] {
|
|
341
|
+
return messages.map((message) => {
|
|
342
|
+
if (message.role !== 'user') return message;
|
|
343
|
+
const nextContent = message.content.filter((block) => {
|
|
344
|
+
if (block.type !== 'text') return true;
|
|
345
|
+
return !block.text.startsWith(TEMPORAL_INJECTED_PREFIX);
|
|
346
|
+
});
|
|
347
|
+
if (nextContent.length === message.content.length) return message;
|
|
348
|
+
if (nextContent.length === 0) return null;
|
|
349
|
+
return { ...message, content: nextContent };
|
|
350
|
+
}).filter((message): message is NonNullable<typeof message> => message !== null);
|
|
351
|
+
}
|
|
352
|
+
|
|
315
353
|
/**
|
|
316
354
|
* Strip `<active_workspace>` (and legacy `<active_dynamic_page>`) blocks
|
|
317
355
|
* injected by `injectActiveSurfaceContext`. Called after the agent run to
|
|
@@ -344,6 +382,7 @@ export function applyRuntimeInjections(
|
|
|
344
382
|
activeSurface?: ActiveSurfaceContext | null;
|
|
345
383
|
workspaceTopLevelContext?: string | null;
|
|
346
384
|
channelCapabilities?: ChannelCapabilities | null;
|
|
385
|
+
temporalContext?: string | null;
|
|
347
386
|
},
|
|
348
387
|
): Message[] {
|
|
349
388
|
let result = runMessages;
|
|
@@ -378,6 +417,19 @@ export function applyRuntimeInjections(
|
|
|
378
417
|
}
|
|
379
418
|
}
|
|
380
419
|
|
|
420
|
+
// Temporal context is injected before workspace top-level so it
|
|
421
|
+
// appears after workspace context in the final message content
|
|
422
|
+
// (both are prepended, so later injections appear first).
|
|
423
|
+
if (options.temporalContext) {
|
|
424
|
+
const userTail = result[result.length - 1];
|
|
425
|
+
if (userTail && userTail.role === 'user') {
|
|
426
|
+
result = [
|
|
427
|
+
...result.slice(0, -1),
|
|
428
|
+
injectTemporalContext(userTail, options.temporalContext),
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
381
433
|
// Workspace top-level context is injected last so it appears first
|
|
382
434
|
// (prepended) in the user message content, keeping cache breakpoints
|
|
383
435
|
// anchored to the trailing blocks.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Message, ToolDefinition } from '../providers/types.js';
|
|
12
12
|
import type { SkillSummary, SkillToolManifest } from '../config/skills.js';
|
|
13
|
+
import type { ActiveSkillEntry } from '../skills/active-skill-tools.js';
|
|
13
14
|
import { loadSkillCatalog } from '../config/skills.js';
|
|
14
15
|
import { deriveActiveSkills } from '../skills/active-skill-tools.js';
|
|
15
16
|
|
|
@@ -38,6 +39,32 @@ export interface SkillToolProjection {
|
|
|
38
39
|
allowedToolNames: Set<string>;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Session-scoped cache for skill projection. Avoids re-scanning the entire
|
|
44
|
+
* conversation history and re-reading the filesystem on every agent turn.
|
|
45
|
+
*
|
|
46
|
+
* Each session should own its own cache instance to prevent cross-session
|
|
47
|
+
* state bleed.
|
|
48
|
+
*/
|
|
49
|
+
export interface SkillProjectionCache {
|
|
50
|
+
/** Cached deriveActiveSkills result. */
|
|
51
|
+
derived?: {
|
|
52
|
+
/** Number of messages in history when this cache was last computed. */
|
|
53
|
+
messageCount: number;
|
|
54
|
+
/** Reference to the first message when cache was computed. Compaction
|
|
55
|
+
* replaces the first message with a new summary object, so a reference
|
|
56
|
+
* mismatch signals that history was rewritten even if the count matches. */
|
|
57
|
+
firstMessage: Message | undefined;
|
|
58
|
+
/** IDs already seen — used for deduplication during incremental scans. */
|
|
59
|
+
seenIds: Set<string>;
|
|
60
|
+
/** The accumulated active skill entries. */
|
|
61
|
+
entries: ActiveSkillEntry[];
|
|
62
|
+
};
|
|
63
|
+
/** Cached skill catalog. Invalidated when the session is marked stale
|
|
64
|
+
* (e.g. skill directories changed on disk while a run is in progress). */
|
|
65
|
+
catalog?: SkillSummary[];
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
export interface ProjectSkillToolsOptions {
|
|
42
69
|
/** Skill IDs that should be treated as active regardless of history markers. */
|
|
43
70
|
preactivatedSkillIds?: string[];
|
|
@@ -49,6 +76,12 @@ export interface ProjectSkillToolsOptions {
|
|
|
49
76
|
* unregistered and re-registered with the updated definitions.
|
|
50
77
|
*/
|
|
51
78
|
previouslyActiveSkillIds?: Map<string, string>;
|
|
79
|
+
/**
|
|
80
|
+
* Session-scoped projection cache. When provided, projectSkillTools will
|
|
81
|
+
* avoid redundant deriveActiveSkills scans and loadSkillCatalog filesystem
|
|
82
|
+
* reads across agent turns.
|
|
83
|
+
*/
|
|
84
|
+
cache?: SkillProjectionCache;
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
// ---------------------------------------------------------------------------
|
|
@@ -73,6 +106,82 @@ function loadManifestForSkill(skill: SkillSummary): SkillToolManifest | null {
|
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
108
|
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Cache helpers
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Return active skill entries, using the projection cache when available.
|
|
115
|
+
*
|
|
116
|
+
* History is append-only within a session (messages are only added, never
|
|
117
|
+
* mutated in place). If history.length hasn't changed since the last scan,
|
|
118
|
+
* the cached result is returned immediately. If new messages were appended,
|
|
119
|
+
* only the delta is scanned and merged. If history shrank (e.g. compression
|
|
120
|
+
* replaced earlier messages), the cache is invalidated and a full rescan
|
|
121
|
+
* is performed.
|
|
122
|
+
*/
|
|
123
|
+
function getCachedActiveSkills(
|
|
124
|
+
history: Message[],
|
|
125
|
+
cache?: SkillProjectionCache,
|
|
126
|
+
): ActiveSkillEntry[] {
|
|
127
|
+
if (!cache) return deriveActiveSkills(history);
|
|
128
|
+
|
|
129
|
+
const cached = cache.derived;
|
|
130
|
+
|
|
131
|
+
// Fast path: history unchanged since last scan. Both the count and the
|
|
132
|
+
// first message reference must match — compaction can rewrite history
|
|
133
|
+
// without changing the total count.
|
|
134
|
+
if (cached && cached.messageCount === history.length && cached.firstMessage === history[0]) {
|
|
135
|
+
return cached.entries;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// History grew (and first message is unchanged) — scan only the new messages.
|
|
139
|
+
if (cached && cached.messageCount < history.length && cached.firstMessage === history[0]) {
|
|
140
|
+
const delta = history.slice(cached.messageCount);
|
|
141
|
+
const newEntries = deriveActiveSkills(delta);
|
|
142
|
+
|
|
143
|
+
// Merge: add any entries not already seen.
|
|
144
|
+
let changed = false;
|
|
145
|
+
for (const entry of newEntries) {
|
|
146
|
+
if (!cached.seenIds.has(entry.id)) {
|
|
147
|
+
cached.seenIds.add(entry.id);
|
|
148
|
+
cached.entries.push(entry);
|
|
149
|
+
changed = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
cached.messageCount = history.length;
|
|
154
|
+
if (changed) {
|
|
155
|
+
log.debug(
|
|
156
|
+
{ newEntries: newEntries.length, total: cached.entries.length },
|
|
157
|
+
'Incremental skill derivation found new entries',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return cached.entries;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// History shrank, compaction rewrote it, or no cache yet — full rescan.
|
|
164
|
+
const entries = deriveActiveSkills(history);
|
|
165
|
+
const seenIds = new Set(entries.map((e) => e.id));
|
|
166
|
+
cache.derived = { messageCount: history.length, firstMessage: history[0], seenIds, entries };
|
|
167
|
+
return entries;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Return the skill catalog, caching it across agent turns.
|
|
172
|
+
*
|
|
173
|
+
* The cache is invalidated when the session is marked stale (e.g. skill
|
|
174
|
+
* directories changed on disk while the session is still processing).
|
|
175
|
+
*/
|
|
176
|
+
function getCachedCatalog(cache?: SkillProjectionCache): SkillSummary[] {
|
|
177
|
+
if (!cache) return loadSkillCatalog();
|
|
178
|
+
|
|
179
|
+
if (!cache.catalog) {
|
|
180
|
+
cache.catalog = loadSkillCatalog();
|
|
181
|
+
}
|
|
182
|
+
return cache.catalog;
|
|
183
|
+
}
|
|
184
|
+
|
|
76
185
|
// ---------------------------------------------------------------------------
|
|
77
186
|
// Main export
|
|
78
187
|
// ---------------------------------------------------------------------------
|
|
@@ -90,7 +199,7 @@ export function projectSkillTools(
|
|
|
90
199
|
history: Message[],
|
|
91
200
|
options?: ProjectSkillToolsOptions,
|
|
92
201
|
): SkillToolProjection {
|
|
93
|
-
const contextEntries =
|
|
202
|
+
const contextEntries = getCachedActiveSkills(history, options?.cache);
|
|
94
203
|
const preactivated = options?.preactivatedSkillIds ?? [];
|
|
95
204
|
const prevActive = options?.previouslyActiveSkillIds ?? new Map<string, string>();
|
|
96
205
|
|
|
@@ -128,8 +237,8 @@ export function projectSkillTools(
|
|
|
128
237
|
return { toolDefinitions: [], allowedToolNames: new Set() };
|
|
129
238
|
}
|
|
130
239
|
|
|
131
|
-
// Load the catalog
|
|
132
|
-
const catalog =
|
|
240
|
+
// Load the catalog (cached for session lifetime) and index by ID
|
|
241
|
+
const catalog = getCachedCatalog(options?.cache);
|
|
133
242
|
const catalogById = new Map<string, SkillSummary>();
|
|
134
243
|
for (const skill of catalog) {
|
|
135
244
|
catalogById.set(skill.id, skill);
|
|
@@ -138,6 +247,9 @@ export function projectSkillTools(
|
|
|
138
247
|
const allToolDefinitions: ToolDefinition[] = [];
|
|
139
248
|
const allToolNames = new Set<string>();
|
|
140
249
|
const successfulEntries = new Map<string, string>();
|
|
250
|
+
// Track skills already unregistered in the version-change branch so the
|
|
251
|
+
// transiently-failed cleanup loop doesn't double-decrement their refcount.
|
|
252
|
+
const alreadyUnregistered = new Set<string>();
|
|
141
253
|
|
|
142
254
|
for (const skillId of activeIds) {
|
|
143
255
|
const skill = catalogById.get(skillId);
|
|
@@ -178,7 +290,14 @@ export function projectSkillTools(
|
|
|
178
290
|
// Hash changed — unregister stale tools, then re-register with new definitions
|
|
179
291
|
log.info({ skillId, prevHash, currentHash }, 'Skill version changed, re-registering tools');
|
|
180
292
|
unregisterSkillTools(skillId);
|
|
181
|
-
|
|
293
|
+
alreadyUnregistered.add(skillId);
|
|
294
|
+
try {
|
|
295
|
+
registerSkillTools(tools);
|
|
296
|
+
} catch (err) {
|
|
297
|
+
log.error({ err, skillId }, 'Failed to re-register skill tools after version change');
|
|
298
|
+
// Don't add to successfulEntries — will be cleaned up as transiently-failed
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
182
301
|
} else {
|
|
183
302
|
// Hash unchanged — check if the bundled status drifted (e.g. a
|
|
184
303
|
// managed skill override was added/removed with identical content).
|
|
@@ -204,7 +323,7 @@ export function projectSkillTools(
|
|
|
204
323
|
// skill would be re-registered when it recovers next turn, inflating the
|
|
205
324
|
// refcount since the prior registration was never decremented.
|
|
206
325
|
for (const id of prevActive.keys()) {
|
|
207
|
-
if (activeIds.has(id) && !successfulEntries.has(id)) {
|
|
326
|
+
if (activeIds.has(id) && !successfulEntries.has(id) && !alreadyUnregistered.has(id)) {
|
|
208
327
|
log.info({ skillId: id }, 'Unregistering tools for transiently-failed skill');
|
|
209
328
|
unregisterSkillTools(id);
|
|
210
329
|
}
|
|
@@ -51,6 +51,9 @@ const PROVIDER_MODEL_SHORTCUTS: Record<string, { provider: string; model: string
|
|
|
51
51
|
|
|
52
52
|
// Fireworks
|
|
53
53
|
'fireworks': { provider: 'fireworks', model: 'accounts/fireworks/models/kimi-k2p5', displayName: 'Kimi K2.5' },
|
|
54
|
+
|
|
55
|
+
// OpenRouter
|
|
56
|
+
'openrouter': { provider: 'openrouter', model: 'x-ai/grok-4', displayName: 'Grok 4 (OpenRouter)' },
|
|
54
57
|
};
|
|
55
58
|
|
|
56
59
|
/** Reverse lookup: model ID → provider, derived from PROVIDER_MODEL_SHORTCUTS. */
|