@vellumai/assistant 0.5.1 → 0.5.3
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 +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/followups/followup-store.ts +47 -1
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +40 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +114 -21
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +99 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +160 -14
- package/src/permissions/defaults.ts +14 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +94 -5
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +140 -6
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +11 -1
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
* Requires the conversation to already exist (does not create new conversations).
|
|
6
6
|
*/
|
|
7
7
|
import { getLogger } from "../../util/logger.js";
|
|
8
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
9
|
+
import type { AuthContext } from "../auth/types.js";
|
|
10
|
+
import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
|
|
8
11
|
import { httpError } from "../http-errors.js";
|
|
9
12
|
import type { RouteDefinition } from "../http-router.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveTrustContext,
|
|
15
|
+
withSourceChannel,
|
|
16
|
+
} from "../trust-context-resolver.js";
|
|
10
17
|
|
|
11
18
|
const log = getLogger("surface-action-routes");
|
|
12
19
|
|
|
@@ -18,6 +25,11 @@ interface SurfaceActionTarget {
|
|
|
18
25
|
data?: Record<string, unknown>,
|
|
19
26
|
): void;
|
|
20
27
|
handleSurfaceUndo?(surfaceId: string): void;
|
|
28
|
+
setTrustContext?(ctx: {
|
|
29
|
+
trustClass: "guardian" | "trusted_contact" | "unknown";
|
|
30
|
+
sourceChannel: string;
|
|
31
|
+
}): void;
|
|
32
|
+
trustContext?: { trustClass: string } | null;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export type ConversationLookup = (
|
|
@@ -28,6 +40,53 @@ export type ConversationLookupBySurfaceId = (
|
|
|
28
40
|
surfaceId: string,
|
|
29
41
|
) => SurfaceActionTarget | undefined;
|
|
30
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Resolve trust context from the request's auth context and set it on the
|
|
45
|
+
* conversation, following the same pattern as POST /v1/messages. This ensures
|
|
46
|
+
* surface actions inherit the correct trust class (guardian vs trusted_contact)
|
|
47
|
+
* rather than defaulting to unknown.
|
|
48
|
+
*/
|
|
49
|
+
function applyTrustContext(
|
|
50
|
+
conversation: SurfaceActionTarget,
|
|
51
|
+
authContext: AuthContext,
|
|
52
|
+
): void {
|
|
53
|
+
if (!conversation.setTrustContext) return;
|
|
54
|
+
|
|
55
|
+
const sourceChannel = "vellum";
|
|
56
|
+
|
|
57
|
+
if (authContext.actorPrincipalId) {
|
|
58
|
+
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
59
|
+
let trustCtx = resolveTrustContext({
|
|
60
|
+
assistantId,
|
|
61
|
+
sourceChannel,
|
|
62
|
+
conversationExternalId: "local",
|
|
63
|
+
actorExternalId: authContext.actorPrincipalId,
|
|
64
|
+
});
|
|
65
|
+
if (trustCtx.trustClass === "unknown") {
|
|
66
|
+
const healed = healGuardianBindingDrift(authContext.actorPrincipalId);
|
|
67
|
+
if (healed) {
|
|
68
|
+
trustCtx = resolveTrustContext({
|
|
69
|
+
assistantId,
|
|
70
|
+
sourceChannel,
|
|
71
|
+
conversationExternalId: "local",
|
|
72
|
+
actorExternalId: authContext.actorPrincipalId,
|
|
73
|
+
});
|
|
74
|
+
log.info(
|
|
75
|
+
{
|
|
76
|
+
actorPrincipalId: authContext.actorPrincipalId,
|
|
77
|
+
trustClass: trustCtx.trustClass,
|
|
78
|
+
},
|
|
79
|
+
"Trust re-resolved after guardian binding drift heal (surface action)",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
conversation.setTrustContext(withSourceChannel(sourceChannel, trustCtx));
|
|
84
|
+
} else {
|
|
85
|
+
// Service principals or tokens without an actor ID get guardian context.
|
|
86
|
+
conversation.setTrustContext({ trustClass: "guardian", sourceChannel });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
31
90
|
/**
|
|
32
91
|
* POST /v1/surface-actions — handle a UI surface action.
|
|
33
92
|
*
|
|
@@ -37,6 +96,7 @@ export async function handleSurfaceAction(
|
|
|
37
96
|
req: Request,
|
|
38
97
|
findConversation: ConversationLookup,
|
|
39
98
|
findConversationBySurfaceId?: ConversationLookupBySurfaceId,
|
|
99
|
+
authContext?: AuthContext,
|
|
40
100
|
): Promise<Response> {
|
|
41
101
|
const body = (await req.json()) as {
|
|
42
102
|
conversationId?: string | null;
|
|
@@ -65,6 +125,12 @@ export async function handleSurfaceAction(
|
|
|
65
125
|
return httpError("NOT_FOUND", "No active conversation found", 404);
|
|
66
126
|
}
|
|
67
127
|
|
|
128
|
+
// Resolve trust context from the request's auth headers so the conversation
|
|
129
|
+
// has the correct trust class for tool approval decisions.
|
|
130
|
+
if (authContext) {
|
|
131
|
+
applyTrustContext(conversation, authContext);
|
|
132
|
+
}
|
|
133
|
+
|
|
68
134
|
try {
|
|
69
135
|
conversation.handleSurfaceAction(surfaceId, actionId, data);
|
|
70
136
|
log.info(
|
|
@@ -143,7 +209,7 @@ export function surfaceActionRouteDefinitions(deps: {
|
|
|
143
209
|
{
|
|
144
210
|
endpoint: "surface-actions",
|
|
145
211
|
method: "POST",
|
|
146
|
-
handler: async ({ req }) => {
|
|
212
|
+
handler: async ({ req, authContext }) => {
|
|
147
213
|
if (!deps.findConversation) {
|
|
148
214
|
return httpError(
|
|
149
215
|
"NOT_IMPLEMENTED",
|
|
@@ -155,6 +221,7 @@ export function surfaceActionRouteDefinitions(deps: {
|
|
|
155
221
|
req,
|
|
156
222
|
deps.findConversation,
|
|
157
223
|
deps.findConversationBySurfaceId,
|
|
224
|
+
authContext,
|
|
158
225
|
);
|
|
159
226
|
},
|
|
160
227
|
},
|
|
@@ -42,7 +42,10 @@ export function traceEventRouteDefinitions(): RouteDefinition[] {
|
|
|
42
42
|
const afterSequence = afterSequenceParam
|
|
43
43
|
? parseInt(afterSequenceParam, 10)
|
|
44
44
|
: undefined;
|
|
45
|
-
if (
|
|
45
|
+
if (
|
|
46
|
+
afterSequenceParam &&
|
|
47
|
+
(isNaN(afterSequence!) || afterSequence! < 0)
|
|
48
|
+
) {
|
|
46
49
|
return httpError(
|
|
47
50
|
"BAD_REQUEST",
|
|
48
51
|
"afterSequence must be a non-negative integer",
|
|
@@ -15,10 +15,7 @@ import type { ScheduleSyntax } from "./recurrence-types.js";
|
|
|
15
15
|
const logger = getLogger("schedule-store");
|
|
16
16
|
|
|
17
17
|
export type ScheduleMode = "notify" | "execute";
|
|
18
|
-
export type RoutingIntent =
|
|
19
|
-
| "single_channel"
|
|
20
|
-
| "multi_channel"
|
|
21
|
-
| "all_channels";
|
|
18
|
+
export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
|
|
22
19
|
export type ScheduleStatus = "active" | "firing" | "fired" | "cancelled";
|
|
23
20
|
|
|
24
21
|
export interface ScheduleJob {
|
|
@@ -193,9 +190,7 @@ export function listSchedules(options?: {
|
|
|
193
190
|
conditions.push(isNull(scheduleJobs.cronExpression));
|
|
194
191
|
}
|
|
195
192
|
if (options?.recurringOnly) {
|
|
196
|
-
conditions.push(
|
|
197
|
-
sql`${scheduleJobs.cronExpression} IS NOT NULL`,
|
|
198
|
-
);
|
|
193
|
+
conditions.push(sql`${scheduleJobs.cronExpression} IS NOT NULL`);
|
|
199
194
|
}
|
|
200
195
|
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
201
196
|
const rows = db
|
|
@@ -207,6 +202,27 @@ export function listSchedules(options?: {
|
|
|
207
202
|
return rows.map(parseJobRow);
|
|
208
203
|
}
|
|
209
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Return enabled schedules whose next run falls within a time window.
|
|
207
|
+
* Used by the memory brief compiler to surface due-soon schedule entries.
|
|
208
|
+
*/
|
|
209
|
+
export function getDueSoonSchedules(
|
|
210
|
+
now: number,
|
|
211
|
+
horizonMs: number,
|
|
212
|
+
): ScheduleJob[] {
|
|
213
|
+
const db = getDb();
|
|
214
|
+
const cutoff = now + horizonMs;
|
|
215
|
+
const rows = db
|
|
216
|
+
.select()
|
|
217
|
+
.from(scheduleJobs)
|
|
218
|
+
.where(
|
|
219
|
+
and(eq(scheduleJobs.enabled, true), lte(scheduleJobs.nextRunAt, cutoff)),
|
|
220
|
+
)
|
|
221
|
+
.orderBy(asc(scheduleJobs.nextRunAt))
|
|
222
|
+
.all();
|
|
223
|
+
return rows.map(parseJobRow);
|
|
224
|
+
}
|
|
225
|
+
|
|
210
226
|
export function updateSchedule(
|
|
211
227
|
id: string,
|
|
212
228
|
updates: {
|
|
@@ -415,10 +431,7 @@ export function claimDueSchedules(now: number): ScheduleJob[] {
|
|
|
415
431
|
updatedAt: now,
|
|
416
432
|
})
|
|
417
433
|
.where(
|
|
418
|
-
and(
|
|
419
|
-
eq(scheduleJobs.id, row.id),
|
|
420
|
-
eq(scheduleJobs.status, "active"),
|
|
421
|
-
),
|
|
434
|
+
and(eq(scheduleJobs.id, row.id), eq(scheduleJobs.status, "active")),
|
|
422
435
|
)
|
|
423
436
|
.run();
|
|
424
437
|
|
|
@@ -450,9 +463,7 @@ export function completeOneShot(id: string): void {
|
|
|
450
463
|
enabled: false,
|
|
451
464
|
updatedAt: now,
|
|
452
465
|
})
|
|
453
|
-
.where(
|
|
454
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")),
|
|
455
|
-
)
|
|
466
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
|
|
456
467
|
.run();
|
|
457
468
|
}
|
|
458
469
|
|
|
@@ -468,9 +479,7 @@ export function failOneShot(id: string): void {
|
|
|
468
479
|
status: "active",
|
|
469
480
|
updatedAt: now,
|
|
470
481
|
})
|
|
471
|
-
.where(
|
|
472
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")),
|
|
473
|
-
)
|
|
482
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
|
|
474
483
|
.run();
|
|
475
484
|
}
|
|
476
485
|
|
|
@@ -487,9 +496,7 @@ export function cancelSchedule(id: string): boolean {
|
|
|
487
496
|
enabled: false,
|
|
488
497
|
updatedAt: now,
|
|
489
498
|
})
|
|
490
|
-
.where(
|
|
491
|
-
and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "active")),
|
|
492
|
-
)
|
|
499
|
+
.where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "active")))
|
|
493
500
|
.run();
|
|
494
501
|
return rawChanges() > 0;
|
|
495
502
|
}
|
|
@@ -770,7 +777,9 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
|
|
|
770
777
|
};
|
|
771
778
|
}
|
|
772
779
|
|
|
773
|
-
function safeParseJson(
|
|
780
|
+
function safeParseJson(
|
|
781
|
+
json: string | null | undefined,
|
|
782
|
+
): Record<string, unknown> {
|
|
774
783
|
if (!json) return {};
|
|
775
784
|
try {
|
|
776
785
|
return JSON.parse(json) as Record<string, unknown>;
|
|
@@ -193,6 +193,27 @@ export async function getProviderKeyAsync(
|
|
|
193
193
|
return envVar ? process.env[envVar] : undefined;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
// Masked provider key — for safe display in client UIs
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Retrieve a provider API key and return a masked version suitable for
|
|
202
|
+
* display. Shows the first 10 characters and last 4, with `...` in between,
|
|
203
|
+
* always hiding at least 3 characters. Returns `null` if no key is stored.
|
|
204
|
+
*/
|
|
205
|
+
export async function getMaskedProviderKey(
|
|
206
|
+
provider: string,
|
|
207
|
+
): Promise<string | null> {
|
|
208
|
+
const key = await getProviderKeyAsync(provider);
|
|
209
|
+
if (!key || key.length === 0) return null;
|
|
210
|
+
const minHidden = 3;
|
|
211
|
+
const maxVisible = Math.max(1, key.length - minHidden);
|
|
212
|
+
const prefixLen = Math.min(10, maxVisible);
|
|
213
|
+
const suffixLen = Math.min(4, Math.max(0, maxVisible - prefixLen));
|
|
214
|
+
return `${key.slice(0, prefixLen)}...${suffixLen > 0 ? key.slice(-suffixLen) : ""}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
196
217
|
// ---------------------------------------------------------------------------
|
|
197
218
|
// Test helpers
|
|
198
219
|
// ---------------------------------------------------------------------------
|
package/src/signals/bash.ts
CHANGED
|
@@ -80,7 +80,7 @@ export function handleBashSignal(filename: string): void {
|
|
|
80
80
|
exitCode: null,
|
|
81
81
|
timedOut: false,
|
|
82
82
|
error:
|
|
83
|
-
"Bash signals are disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: VELLUM_DEBUG=1
|
|
83
|
+
"Bash signals are disabled. The running assistant process must have been started with VELLUM_DEBUG=1 (setting it on the CLI command alone is not enough). Restart the assistant with: vellum sleep && VELLUM_DEBUG=1 vellum wake",
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
return;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical parser for inline command expansion tokens in skill bodies.
|
|
3
|
+
*
|
|
4
|
+
* Syntax: !\`command\`
|
|
5
|
+
*
|
|
6
|
+
* These tokens are parsed from the markdown body of a SKILL.md file (after
|
|
7
|
+
* frontmatter extraction). Tokens inside fenced code blocks are ignored so
|
|
8
|
+
* that documentation examples or literal snippets do not accidentally execute.
|
|
9
|
+
*
|
|
10
|
+
* The parser fails closed on malformed tokens: unmatched backticks, empty
|
|
11
|
+
* commands, or nested backticks that make the command text ambiguous are
|
|
12
|
+
* rejected rather than best-effort expanded.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getLogger } from "../util/logger.js";
|
|
16
|
+
|
|
17
|
+
const log = getLogger("inline-command-expansions");
|
|
18
|
+
|
|
19
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
/** A single parsed inline command expansion descriptor. */
|
|
22
|
+
export interface InlineCommandExpansion {
|
|
23
|
+
/** The raw command text between the backticks (trimmed). */
|
|
24
|
+
command: string;
|
|
25
|
+
/** Byte offset of the `!` character in the original body string. */
|
|
26
|
+
startOffset: number;
|
|
27
|
+
/** Byte offset one past the closing backtick in the original body string. */
|
|
28
|
+
endOffset: number;
|
|
29
|
+
/** Stable placeholder ID derived from encounter order (0-indexed). */
|
|
30
|
+
placeholderId: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Result of parsing a skill body for inline command expansions. */
|
|
34
|
+
export interface InlineCommandExpansionResult {
|
|
35
|
+
/** Successfully parsed expansion descriptors, in encounter order. */
|
|
36
|
+
expansions: InlineCommandExpansion[];
|
|
37
|
+
/** Malformed tokens that were rejected (fail-closed). */
|
|
38
|
+
errors: InlineCommandExpansionError[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A malformed inline command expansion token. */
|
|
42
|
+
export interface InlineCommandExpansionError {
|
|
43
|
+
/** The raw matched text that was rejected. */
|
|
44
|
+
raw: string;
|
|
45
|
+
/** Byte offset in the original body. */
|
|
46
|
+
offset: number;
|
|
47
|
+
/** Human-readable reason for rejection. */
|
|
48
|
+
reason: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ─── Fenced code block stripping ──────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build a set of character ranges that fall inside fenced code blocks.
|
|
55
|
+
* A fenced code block starts with a line matching ``` (with optional info
|
|
56
|
+
* string) and ends with a line matching ``` (or end of string).
|
|
57
|
+
*/
|
|
58
|
+
function buildFencedCodeRanges(body: string): Array<[number, number]> {
|
|
59
|
+
const ranges: Array<[number, number]> = [];
|
|
60
|
+
// Match fenced code block delimiters: ``` optionally followed by info string
|
|
61
|
+
const fenceRe = /^(`{3,}|~{3,})(.*)?$/gm;
|
|
62
|
+
let openFence: { index: number; delimiter: string } | undefined;
|
|
63
|
+
|
|
64
|
+
let match: RegExpExecArray | undefined;
|
|
65
|
+
while ((match = fenceRe.exec(body) ?? undefined) !== undefined) {
|
|
66
|
+
const delimiter = match[1];
|
|
67
|
+
if (openFence === undefined) {
|
|
68
|
+
// Opening fence
|
|
69
|
+
openFence = {
|
|
70
|
+
index: match.index,
|
|
71
|
+
delimiter: delimiter[0].repeat(delimiter.length),
|
|
72
|
+
};
|
|
73
|
+
} else if (
|
|
74
|
+
delimiter[0] === openFence.delimiter[0] &&
|
|
75
|
+
delimiter.length >= openFence.delimiter.length &&
|
|
76
|
+
// Closing fence must be bare (no info string after it)
|
|
77
|
+
(!match[2] || match[2].trim() === "")
|
|
78
|
+
) {
|
|
79
|
+
// Closing fence — range covers from opening fence to end of closing fence line
|
|
80
|
+
ranges.push([openFence.index, match.index + match[0].length]);
|
|
81
|
+
openFence = undefined;
|
|
82
|
+
}
|
|
83
|
+
// Otherwise ignore (nested fence-like lines inside a code block)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If a fence was opened but never closed, treat everything from the opening
|
|
87
|
+
// fence to EOF as inside a code block.
|
|
88
|
+
if (openFence !== undefined) {
|
|
89
|
+
ranges.push([openFence.index, body.length]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return ranges;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isInsideFencedCode(
|
|
96
|
+
offset: number,
|
|
97
|
+
ranges: Array<[number, number]>,
|
|
98
|
+
): boolean {
|
|
99
|
+
for (const [start, end] of ranges) {
|
|
100
|
+
if (offset >= start && offset < end) return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Parse inline command expansion tokens (`!\`...\``) from a skill body.
|
|
109
|
+
*
|
|
110
|
+
* The body must be the markdown content _after_ frontmatter has been stripped.
|
|
111
|
+
* Tokens inside fenced code blocks are skipped.
|
|
112
|
+
*
|
|
113
|
+
* Returns both the successfully parsed expansions and any malformed tokens
|
|
114
|
+
* that were rejected (fail-closed).
|
|
115
|
+
*/
|
|
116
|
+
export function parseInlineCommandExpansions(
|
|
117
|
+
body: string,
|
|
118
|
+
): InlineCommandExpansionResult {
|
|
119
|
+
const expansions: InlineCommandExpansion[] = [];
|
|
120
|
+
const errors: InlineCommandExpansionError[] = [];
|
|
121
|
+
|
|
122
|
+
const fencedRanges = buildFencedCodeRanges(body);
|
|
123
|
+
|
|
124
|
+
// Match !\`...\` tokens. The regex captures the content between the backticks.
|
|
125
|
+
// We use a non-greedy match to find the first closing backtick.
|
|
126
|
+
const tokenRe = /!\`([^`]*)\`/g;
|
|
127
|
+
|
|
128
|
+
let match: RegExpExecArray | undefined;
|
|
129
|
+
let placeholderCounter = 0;
|
|
130
|
+
|
|
131
|
+
while ((match = tokenRe.exec(body) ?? undefined) !== undefined) {
|
|
132
|
+
const startOffset = match.index;
|
|
133
|
+
const endOffset = startOffset + match[0].length;
|
|
134
|
+
const rawCommand = match[1];
|
|
135
|
+
|
|
136
|
+
// Skip tokens inside fenced code blocks
|
|
137
|
+
if (isInsideFencedCode(startOffset, fencedRanges)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fail closed: empty command
|
|
142
|
+
if (rawCommand.trim().length === 0) {
|
|
143
|
+
errors.push({
|
|
144
|
+
raw: match[0],
|
|
145
|
+
offset: startOffset,
|
|
146
|
+
reason: "Empty command text",
|
|
147
|
+
});
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fail closed: nested backticks (would make command text ambiguous)
|
|
152
|
+
if (rawCommand.includes("`")) {
|
|
153
|
+
errors.push({
|
|
154
|
+
raw: match[0],
|
|
155
|
+
offset: startOffset,
|
|
156
|
+
reason: "Nested backticks in command text",
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
expansions.push({
|
|
162
|
+
command: rawCommand.trim(),
|
|
163
|
+
startOffset,
|
|
164
|
+
endOffset,
|
|
165
|
+
placeholderId: placeholderCounter++,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Also detect malformed tokens: !\` without a closing backtick.
|
|
170
|
+
// These are unmatched opening tokens that didn't match the regex above.
|
|
171
|
+
const unmatchedRe = /!\`/g;
|
|
172
|
+
const matchedStarts = new Set<number>();
|
|
173
|
+
// Re-run the token regex to collect all matched positions
|
|
174
|
+
tokenRe.lastIndex = 0;
|
|
175
|
+
while ((match = tokenRe.exec(body) ?? undefined) !== undefined) {
|
|
176
|
+
matchedStarts.add(match.index);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let unmatchedMatch: RegExpExecArray | undefined;
|
|
180
|
+
while ((unmatchedMatch = unmatchedRe.exec(body) ?? undefined) !== undefined) {
|
|
181
|
+
const offset = unmatchedMatch.index;
|
|
182
|
+
|
|
183
|
+
// Skip if this was already matched as a complete token
|
|
184
|
+
if (matchedStarts.has(offset)) continue;
|
|
185
|
+
|
|
186
|
+
// Skip if inside a fenced code block
|
|
187
|
+
if (isInsideFencedCode(offset, fencedRanges)) continue;
|
|
188
|
+
|
|
189
|
+
errors.push({
|
|
190
|
+
raw: body.slice(offset, Math.min(offset + 40, body.length)),
|
|
191
|
+
offset,
|
|
192
|
+
reason: "Unmatched opening backtick (no closing backtick found)",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (errors.length > 0) {
|
|
197
|
+
log.warn(
|
|
198
|
+
{ errorCount: errors.length, errors },
|
|
199
|
+
"Malformed inline command expansion tokens detected",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { expansions, errors };
|
|
204
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer for inline command expansion tokens in skill bodies.
|
|
3
|
+
*
|
|
4
|
+
* Given a skill body and its parsed `InlineCommandExpansion` descriptors,
|
|
5
|
+
* replaces each `!\`command\`` token by executing the command through the
|
|
6
|
+
* sandbox-only runner and wrapping the result in XML tags:
|
|
7
|
+
*
|
|
8
|
+
* <inline_skill_command index="0">...output...</inline_skill_command>
|
|
9
|
+
*
|
|
10
|
+
* Render failures produce stable inline stubs rather than dumping raw
|
|
11
|
+
* shell stderr into the prompt:
|
|
12
|
+
*
|
|
13
|
+
* <inline_skill_command index="0">[inline command unavailable: <reason>]</inline_skill_command>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { getLogger } from "../util/logger.js";
|
|
17
|
+
import type { InlineCommandExpansion } from "./inline-command-expansions.js";
|
|
18
|
+
import type { InlineCommandResult } from "./inline-command-runner.js";
|
|
19
|
+
import { runInlineCommand } from "./inline-command-runner.js";
|
|
20
|
+
|
|
21
|
+
const log = getLogger("inline-command-render");
|
|
22
|
+
|
|
23
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
/** Result of rendering all inline command expansions in a skill body. */
|
|
26
|
+
export interface InlineCommandRenderResult {
|
|
27
|
+
/** The body with all inline command tokens replaced. */
|
|
28
|
+
renderedBody: string;
|
|
29
|
+
/** Count of successfully expanded tokens. */
|
|
30
|
+
expandedCount: number;
|
|
31
|
+
/** Count of tokens that failed to expand (rendered as stubs). */
|
|
32
|
+
failedCount: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── Failure reason mapping ───────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Map a machine-readable failure reason to a human-readable stub message
|
|
39
|
+
* suitable for inclusion in the prompt. These messages are intentionally
|
|
40
|
+
* terse and deterministic so they don't leak raw stderr or confuse the LLM.
|
|
41
|
+
*/
|
|
42
|
+
function failureReasonToStub(result: InlineCommandResult): string {
|
|
43
|
+
switch (result.failureReason) {
|
|
44
|
+
case "timeout":
|
|
45
|
+
return "command timed out";
|
|
46
|
+
case "non_zero_exit":
|
|
47
|
+
return "command failed";
|
|
48
|
+
case "binary_output":
|
|
49
|
+
return "command produced binary output";
|
|
50
|
+
case "spawn_failure":
|
|
51
|
+
return "command could not be started";
|
|
52
|
+
default:
|
|
53
|
+
return "unknown error";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Render all inline command expansion tokens in a skill body.
|
|
61
|
+
*
|
|
62
|
+
* Each `!\`command\`` token is executed through the sandbox-only runner and
|
|
63
|
+
* replaced with its output wrapped in XML tags. Expansions are processed
|
|
64
|
+
* sequentially (not in parallel) to keep execution order deterministic and
|
|
65
|
+
* avoid overwhelming the sandbox.
|
|
66
|
+
*
|
|
67
|
+
* @param body The skill body containing `!\`command\`` tokens.
|
|
68
|
+
* @param expansions Parsed expansion descriptors from `parseInlineCommandExpansions`.
|
|
69
|
+
* @param workingDir The conversation's working directory (repo root).
|
|
70
|
+
*/
|
|
71
|
+
export async function renderInlineCommands(
|
|
72
|
+
body: string,
|
|
73
|
+
expansions: InlineCommandExpansion[],
|
|
74
|
+
workingDir: string,
|
|
75
|
+
): Promise<InlineCommandRenderResult> {
|
|
76
|
+
if (expansions.length === 0) {
|
|
77
|
+
return { renderedBody: body, expandedCount: 0, failedCount: 0 };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let expandedCount = 0;
|
|
81
|
+
let failedCount = 0;
|
|
82
|
+
|
|
83
|
+
// Process replacements in reverse offset order so that earlier offsets
|
|
84
|
+
// remain valid after splicing in replacement text.
|
|
85
|
+
const sorted = [...expansions].sort((a, b) => b.startOffset - a.startOffset);
|
|
86
|
+
|
|
87
|
+
let result = body;
|
|
88
|
+
|
|
89
|
+
for (const expansion of sorted) {
|
|
90
|
+
const commandResult = await runInlineCommand(expansion.command, workingDir);
|
|
91
|
+
|
|
92
|
+
let replacement: string;
|
|
93
|
+
if (commandResult.ok) {
|
|
94
|
+
replacement = wrapInXml(expansion.placeholderId, commandResult.output);
|
|
95
|
+
expandedCount++;
|
|
96
|
+
} else {
|
|
97
|
+
const stub = failureReasonToStub(commandResult);
|
|
98
|
+
replacement = wrapInXml(
|
|
99
|
+
expansion.placeholderId,
|
|
100
|
+
`[inline command unavailable: ${stub}]`,
|
|
101
|
+
);
|
|
102
|
+
failedCount++;
|
|
103
|
+
log.warn(
|
|
104
|
+
{
|
|
105
|
+
command: expansion.command,
|
|
106
|
+
placeholderId: expansion.placeholderId,
|
|
107
|
+
failureReason: commandResult.failureReason,
|
|
108
|
+
},
|
|
109
|
+
"Inline command expansion failed, rendering stub",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Replace the original token with the rendered output
|
|
114
|
+
result =
|
|
115
|
+
result.slice(0, expansion.startOffset) +
|
|
116
|
+
replacement +
|
|
117
|
+
result.slice(expansion.endOffset);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { renderedBody: result, expandedCount, failedCount };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
function wrapInXml(index: number, content: string): string {
|
|
126
|
+
return `<inline_skill_command index="${index}">${content}</inline_skill_command>`;
|
|
127
|
+
}
|