@vellumai/assistant 0.6.0 → 0.6.2
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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -18
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/secret-detection-handler.ts +0 -1
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -34,15 +34,17 @@ export interface SubagentDetailResult {
|
|
|
34
34
|
}>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Parse raw message rows into subagent detail events. Extracted as a pure
|
|
39
|
+
* function so it can be unit-tested without a database.
|
|
40
|
+
*/
|
|
41
|
+
export function parseSubagentMessages(
|
|
38
42
|
subagentId: string,
|
|
39
|
-
|
|
43
|
+
messages: Array<{ role: string; content: string }>,
|
|
40
44
|
): SubagentDetailResult {
|
|
41
|
-
const subagentMsgs = getMessages(conversationId);
|
|
42
|
-
|
|
43
45
|
// Extract objective from the first user message
|
|
44
46
|
let objective: string | undefined;
|
|
45
|
-
const firstUser =
|
|
47
|
+
const firstUser = messages.find((m) => m.role === "user");
|
|
46
48
|
if (firstUser) {
|
|
47
49
|
try {
|
|
48
50
|
const parsed = JSON.parse(firstUser.content);
|
|
@@ -67,7 +69,7 @@ export function getSubagentDetail(
|
|
|
67
69
|
isError?: boolean;
|
|
68
70
|
}> = [];
|
|
69
71
|
const pendingTools = new Map<string, string>();
|
|
70
|
-
for (const m of
|
|
72
|
+
for (const m of messages) {
|
|
71
73
|
if (m.role !== "assistant" && m.role !== "user") continue;
|
|
72
74
|
let content: unknown[];
|
|
73
75
|
try {
|
|
@@ -101,7 +103,19 @@ export function getSubagentDetail(
|
|
|
101
103
|
const toolUseId =
|
|
102
104
|
typeof block.tool_use_id === "string" ? block.tool_use_id : "";
|
|
103
105
|
const resultContent =
|
|
104
|
-
typeof block.content === "string"
|
|
106
|
+
typeof block.content === "string"
|
|
107
|
+
? block.content
|
|
108
|
+
: Array.isArray(block.content)
|
|
109
|
+
? (block.content as unknown[])
|
|
110
|
+
.filter(
|
|
111
|
+
(b): b is Record<string, unknown> =>
|
|
112
|
+
isRecord(b) &&
|
|
113
|
+
(b as Record<string, unknown>).type === "text" &&
|
|
114
|
+
typeof (b as Record<string, unknown>).text === "string",
|
|
115
|
+
)
|
|
116
|
+
.map((b) => b.text as string)
|
|
117
|
+
.join("\n")
|
|
118
|
+
: "";
|
|
105
119
|
const isError = block.is_error === true;
|
|
106
120
|
const toolName = toolUseId ? pendingTools.get(toolUseId) : undefined;
|
|
107
121
|
events.push({
|
|
@@ -117,6 +131,13 @@ export function getSubagentDetail(
|
|
|
117
131
|
return { subagentId, objective, events };
|
|
118
132
|
}
|
|
119
133
|
|
|
134
|
+
export function getSubagentDetail(
|
|
135
|
+
subagentId: string,
|
|
136
|
+
conversationId: string,
|
|
137
|
+
): SubagentDetailResult {
|
|
138
|
+
return parseSubagentMessages(subagentId, getMessages(conversationId));
|
|
139
|
+
}
|
|
140
|
+
|
|
120
141
|
// ---------------------------------------------------------------------------
|
|
121
142
|
// Route definitions
|
|
122
143
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based route dispatcher for user-defined HTTP endpoints.
|
|
3
|
+
*
|
|
4
|
+
* Maps requests under the `/x/*` path prefix to handler modules in the
|
|
5
|
+
* workspace routes directory (`$VELLUM_WORKSPACE_DIR/routes/`). Each handler file
|
|
6
|
+
* exports named functions for HTTP methods (GET, POST, PUT, etc.) using
|
|
7
|
+
* the standard Web API Request/Response signature.
|
|
8
|
+
*
|
|
9
|
+
* Modules are lazily loaded on first request and cached by file path +
|
|
10
|
+
* mtime. When a file changes on disk, the next request reloads it via
|
|
11
|
+
* Bun's dynamic `import()` with a cache-busting query parameter.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, statSync } from "node:fs";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
import { getLogger } from "../../util/logger.js";
|
|
18
|
+
import { getWorkspaceRoutesDir } from "../../util/platform.js";
|
|
19
|
+
import { httpError } from "../http-errors.js";
|
|
20
|
+
|
|
21
|
+
const log = getLogger("user-routes");
|
|
22
|
+
|
|
23
|
+
/** HTTP methods that can be exported from a handler module. */
|
|
24
|
+
const HTTP_METHODS = [
|
|
25
|
+
"GET",
|
|
26
|
+
"POST",
|
|
27
|
+
"PUT",
|
|
28
|
+
"PATCH",
|
|
29
|
+
"DELETE",
|
|
30
|
+
"HEAD",
|
|
31
|
+
"OPTIONS",
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
35
|
+
|
|
36
|
+
/** The function signature that user-defined route handlers must follow. */
|
|
37
|
+
type RouteHandler = (request: Request) => Response | Promise<Response>;
|
|
38
|
+
|
|
39
|
+
/** A loaded handler module with its cached metadata. */
|
|
40
|
+
interface CachedModule {
|
|
41
|
+
/** The module's exports (keyed by HTTP method name). */
|
|
42
|
+
handlers: Partial<Record<HttpMethod, RouteHandler>>;
|
|
43
|
+
/** Optional description exported by the module for display in CLI. */
|
|
44
|
+
description?: string;
|
|
45
|
+
/** The file's mtime at the time of loading, in milliseconds. */
|
|
46
|
+
mtimeMs: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Default per-request timeout for user-defined route handlers (30 seconds). */
|
|
50
|
+
const DEFAULT_HANDLER_TIMEOUT_MS = 30_000;
|
|
51
|
+
|
|
52
|
+
/** Supported file extensions for handler modules. */
|
|
53
|
+
const HANDLER_EXTENSIONS = [".ts", ".js"] as const;
|
|
54
|
+
|
|
55
|
+
export class UserRouteDispatcher {
|
|
56
|
+
private moduleCache = new Map<string, CachedModule>();
|
|
57
|
+
private handlerTimeoutMs: number;
|
|
58
|
+
|
|
59
|
+
constructor(options?: { handlerTimeoutMs?: number }) {
|
|
60
|
+
this.handlerTimeoutMs =
|
|
61
|
+
options?.handlerTimeoutMs ?? DEFAULT_HANDLER_TIMEOUT_MS;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Dispatch a request to the appropriate user-defined handler file.
|
|
66
|
+
*
|
|
67
|
+
* @param routePath The path after the `x/` prefix (e.g. `my-app/status`).
|
|
68
|
+
* @param request The original HTTP request.
|
|
69
|
+
* @returns A Response from the handler, or an error response (404, 405, 500).
|
|
70
|
+
*/
|
|
71
|
+
async dispatch(routePath: string, request: Request): Promise<Response> {
|
|
72
|
+
if (routePath.includes("..")) {
|
|
73
|
+
return httpError("BAD_REQUEST", "Path traversal is not allowed", 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const routesDir = getWorkspaceRoutesDir();
|
|
77
|
+
const filePath = this.resolveHandlerFile(routesDir, routePath);
|
|
78
|
+
|
|
79
|
+
if (!filePath) {
|
|
80
|
+
return httpError(
|
|
81
|
+
"NOT_FOUND",
|
|
82
|
+
`No route handler found for /x/${routePath}`,
|
|
83
|
+
404,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const mod = await this.loadModule(filePath);
|
|
88
|
+
const method = request.method as HttpMethod;
|
|
89
|
+
const handler = mod.handlers[method];
|
|
90
|
+
|
|
91
|
+
if (!handler) {
|
|
92
|
+
const allowed = HTTP_METHODS.filter((m) => m in mod.handlers);
|
|
93
|
+
return new Response(null, {
|
|
94
|
+
status: 405,
|
|
95
|
+
headers: { Allow: allowed.join(", ") },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return this.executeHandler(handler, request, routePath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a route path to a handler file on disk.
|
|
104
|
+
*
|
|
105
|
+
* Checks for direct file matches first (`<path>.ts`, `<path>.js`),
|
|
106
|
+
* then falls back to index files (`<path>/index.ts`, `<path>/index.js`).
|
|
107
|
+
*
|
|
108
|
+
* Returns the absolute path to the handler file, or null if not found.
|
|
109
|
+
*/
|
|
110
|
+
private resolveHandlerFile(
|
|
111
|
+
routesDir: string,
|
|
112
|
+
routePath: string,
|
|
113
|
+
): string | null {
|
|
114
|
+
const basePath = join(routesDir, routePath);
|
|
115
|
+
const resolved = resolve(basePath);
|
|
116
|
+
|
|
117
|
+
// Ensure the resolved path is within the routes directory to prevent
|
|
118
|
+
// any path traversal that slipped through the initial check.
|
|
119
|
+
if (!resolved.startsWith(resolve(routesDir))) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Direct file match: routes/<path>.ts or routes/<path>.js
|
|
124
|
+
for (const ext of HANDLER_EXTENSIONS) {
|
|
125
|
+
const candidate = `${resolved}${ext}`;
|
|
126
|
+
if (existsSync(candidate)) {
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Index file convention: routes/<path>/index.ts or routes/<path>/index.js
|
|
132
|
+
for (const ext of HANDLER_EXTENSIONS) {
|
|
133
|
+
const candidate = join(resolved, `index${ext}`);
|
|
134
|
+
if (existsSync(candidate)) {
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Load a handler module, using the mtime-based cache when possible.
|
|
144
|
+
*
|
|
145
|
+
* On cache miss or stale mtime, the module is re-imported via Bun's
|
|
146
|
+
* dynamic `import()` with a cache-busting query parameter derived
|
|
147
|
+
* from the file's current mtime.
|
|
148
|
+
*/
|
|
149
|
+
private async loadModule(filePath: string): Promise<CachedModule> {
|
|
150
|
+
const stat = statSync(filePath);
|
|
151
|
+
const mtimeMs = stat.mtimeMs;
|
|
152
|
+
|
|
153
|
+
const cached = this.moduleCache.get(filePath);
|
|
154
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
155
|
+
return cached;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Cache-bust Bun's module cache by appending mtime as a query param.
|
|
159
|
+
const mod = (await import(`${filePath}?t=${mtimeMs}`)) as Record<
|
|
160
|
+
string,
|
|
161
|
+
unknown
|
|
162
|
+
>;
|
|
163
|
+
|
|
164
|
+
const handlers: Partial<Record<HttpMethod, RouteHandler>> = {};
|
|
165
|
+
for (const method of HTTP_METHODS) {
|
|
166
|
+
if (typeof mod[method] === "function") {
|
|
167
|
+
handlers[method] = mod[method] as RouteHandler;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const description =
|
|
172
|
+
typeof mod.description === "string" ? mod.description : undefined;
|
|
173
|
+
|
|
174
|
+
const entry: CachedModule = { handlers, description, mtimeMs };
|
|
175
|
+
this.moduleCache.set(filePath, entry);
|
|
176
|
+
|
|
177
|
+
log.info(
|
|
178
|
+
{ filePath, methods: Object.keys(handlers), description },
|
|
179
|
+
"Loaded user route handler",
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Execute a handler function with a per-request timeout and error boundary.
|
|
187
|
+
*/
|
|
188
|
+
private async executeHandler(
|
|
189
|
+
handler: RouteHandler,
|
|
190
|
+
request: Request,
|
|
191
|
+
routePath: string,
|
|
192
|
+
): Promise<Response> {
|
|
193
|
+
try {
|
|
194
|
+
const result = await Promise.race([
|
|
195
|
+
Promise.resolve(handler(request)),
|
|
196
|
+
new Promise<never>((_, reject) =>
|
|
197
|
+
setTimeout(
|
|
198
|
+
() => reject(new Error("Handler timed out")),
|
|
199
|
+
this.handlerTimeoutMs,
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
]);
|
|
203
|
+
return result;
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof Error && err.message === "Handler timed out") {
|
|
206
|
+
log.error(
|
|
207
|
+
{ routePath, timeoutMs: this.handlerTimeoutMs },
|
|
208
|
+
"User route handler timed out",
|
|
209
|
+
);
|
|
210
|
+
return httpError(
|
|
211
|
+
"SERVICE_UNAVAILABLE",
|
|
212
|
+
`Route handler for /x/${routePath} timed out after ${this.handlerTimeoutMs}ms`,
|
|
213
|
+
504,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
log.error({ err, routePath }, "User route handler threw an error");
|
|
218
|
+
const message =
|
|
219
|
+
err instanceof Error ? err.message : "Internal server error";
|
|
220
|
+
return httpError("INTERNAL_ERROR", message, 500);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route definitions for user-defined endpoints under `/x/*`.
|
|
3
|
+
*
|
|
4
|
+
* Registers a single catch-all route that delegates to the
|
|
5
|
+
* UserRouteDispatcher for file-based dispatch from
|
|
6
|
+
* `$VELLUM_WORKSPACE_DIR/routes/`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { RouteDefinition } from "../http-router.js";
|
|
10
|
+
import { UserRouteDispatcher } from "./user-route-dispatcher.js";
|
|
11
|
+
|
|
12
|
+
const dispatcher = new UserRouteDispatcher();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* HTTP methods supported by user-defined route handlers.
|
|
16
|
+
*
|
|
17
|
+
* Each method gets its own route definition so the HttpRouter can match
|
|
18
|
+
* on method before dispatching. The catch-all `x/:path*` pattern ensures
|
|
19
|
+
* all sub-paths are captured regardless of depth.
|
|
20
|
+
*/
|
|
21
|
+
const METHODS = [
|
|
22
|
+
"GET",
|
|
23
|
+
"POST",
|
|
24
|
+
"PUT",
|
|
25
|
+
"PATCH",
|
|
26
|
+
"DELETE",
|
|
27
|
+
"HEAD",
|
|
28
|
+
"OPTIONS",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
export function userRouteDefinitions(): RouteDefinition[] {
|
|
32
|
+
return METHODS.map((method) => ({
|
|
33
|
+
endpoint: "x/:path*",
|
|
34
|
+
method,
|
|
35
|
+
policyKey: "x",
|
|
36
|
+
summary: `User-defined ${method} route`,
|
|
37
|
+
description: `Dispatches ${method} requests to user-defined handler files in the workspace routes directory.`,
|
|
38
|
+
tags: ["user-routes"],
|
|
39
|
+
handler: ({ params, req }) => dispatcher.dispatch(params.path, req),
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
@@ -36,6 +36,7 @@ export interface ScheduleJob {
|
|
|
36
36
|
routingIntent: RoutingIntent;
|
|
37
37
|
routingHints: Record<string, unknown>;
|
|
38
38
|
quiet: boolean;
|
|
39
|
+
reuseConversation: boolean;
|
|
39
40
|
status: ScheduleStatus;
|
|
40
41
|
createdAt: number;
|
|
41
42
|
updatedAt: number;
|
|
@@ -93,6 +94,7 @@ export function createSchedule(params: {
|
|
|
93
94
|
routingIntent?: RoutingIntent;
|
|
94
95
|
routingHints?: Record<string, unknown>;
|
|
95
96
|
quiet?: boolean;
|
|
97
|
+
reuseConversation?: boolean;
|
|
96
98
|
}): ScheduleJob {
|
|
97
99
|
const expression = params.expression ?? params.cronExpression ?? null;
|
|
98
100
|
const isOneShot = expression == null;
|
|
@@ -121,6 +123,7 @@ export function createSchedule(params: {
|
|
|
121
123
|
const routingIntent = params.routingIntent ?? "all_channels";
|
|
122
124
|
const routingHints = params.routingHints ?? {};
|
|
123
125
|
const quiet = params.quiet ?? false;
|
|
126
|
+
const reuseConversation = params.reuseConversation ?? false;
|
|
124
127
|
|
|
125
128
|
let nextRunAt: number;
|
|
126
129
|
if (isOneShot) {
|
|
@@ -148,6 +151,7 @@ export function createSchedule(params: {
|
|
|
148
151
|
routingIntent,
|
|
149
152
|
routingHintsJson: JSON.stringify(routingHints),
|
|
150
153
|
quiet,
|
|
154
|
+
reuseConversation,
|
|
151
155
|
status: "active" as ScheduleStatus,
|
|
152
156
|
createdAt: now,
|
|
153
157
|
updatedAt: now,
|
|
@@ -220,6 +224,7 @@ export function updateSchedule(
|
|
|
220
224
|
routingIntent?: RoutingIntent;
|
|
221
225
|
routingHints?: Record<string, unknown>;
|
|
222
226
|
quiet?: boolean;
|
|
227
|
+
reuseConversation?: boolean;
|
|
223
228
|
},
|
|
224
229
|
): ScheduleJob | null {
|
|
225
230
|
const db = getDb();
|
|
@@ -275,6 +280,8 @@ export function updateSchedule(
|
|
|
275
280
|
if (updates.routingHints !== undefined)
|
|
276
281
|
set.routingHintsJson = JSON.stringify(updates.routingHints);
|
|
277
282
|
if (updates.quiet !== undefined) set.quiet = updates.quiet;
|
|
283
|
+
if (updates.reuseConversation !== undefined)
|
|
284
|
+
set.reuseConversation = updates.reuseConversation;
|
|
278
285
|
|
|
279
286
|
// Recompute nextRunAt if schedule timing may have changed (only for recurring)
|
|
280
287
|
if (
|
|
@@ -563,6 +570,28 @@ export function completeScheduleRun(
|
|
|
563
570
|
}
|
|
564
571
|
}
|
|
565
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Return the conversation ID from the most recent successful run
|
|
575
|
+
* for a given schedule, or null if none exists.
|
|
576
|
+
*/
|
|
577
|
+
export function getLastScheduleConversationId(jobId: string): string | null {
|
|
578
|
+
const db = getDb();
|
|
579
|
+
const row = db
|
|
580
|
+
.select({ conversationId: scheduleRuns.conversationId })
|
|
581
|
+
.from(scheduleRuns)
|
|
582
|
+
.where(
|
|
583
|
+
and(
|
|
584
|
+
eq(scheduleRuns.jobId, jobId),
|
|
585
|
+
eq(scheduleRuns.status, "ok"),
|
|
586
|
+
sql`${scheduleRuns.conversationId} IS NOT NULL`,
|
|
587
|
+
),
|
|
588
|
+
)
|
|
589
|
+
.orderBy(desc(scheduleRuns.createdAt))
|
|
590
|
+
.limit(1)
|
|
591
|
+
.get();
|
|
592
|
+
return row?.conversationId ?? null;
|
|
593
|
+
}
|
|
594
|
+
|
|
566
595
|
export function getScheduleRuns(jobId: string, limit?: number): ScheduleRun[] {
|
|
567
596
|
const db = getDb();
|
|
568
597
|
const rows = db
|
|
@@ -757,6 +786,7 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
|
|
|
757
786
|
routingIntent: (row.routingIntent ?? "all_channels") as RoutingIntent,
|
|
758
787
|
routingHints: safeParseJson(row.routingHintsJson),
|
|
759
788
|
quiet: row.quiet ?? false,
|
|
789
|
+
reuseConversation: row.reuseConversation ?? false,
|
|
760
790
|
status: (row.status ?? "active") as ScheduleStatus,
|
|
761
791
|
createdAt: row.createdAt,
|
|
762
792
|
updatedAt: row.updatedAt,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
|
|
2
|
+
import { getConversation } from "../memory/conversation-crud.js";
|
|
2
3
|
import { invalidateAssistantInferredItemsForConversation } from "../memory/task-memory-cleanup.js";
|
|
3
4
|
import { runSequencesOnce } from "../sequence/engine.js";
|
|
4
5
|
import { getLogger } from "../util/logger.js";
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
completeScheduleRun,
|
|
15
16
|
createScheduleRun,
|
|
16
17
|
failOneShot,
|
|
18
|
+
getLastScheduleConversationId,
|
|
17
19
|
type RoutingIntent,
|
|
18
20
|
} from "./schedule-store.js";
|
|
19
21
|
|
|
@@ -240,6 +242,7 @@ async function runScheduleOnce(
|
|
|
240
242
|
);
|
|
241
243
|
// Create a fallback conversation for the schedule run record
|
|
242
244
|
const fallbackConversation = bootstrapConversation({
|
|
245
|
+
conversationType: "scheduled",
|
|
243
246
|
source: "schedule",
|
|
244
247
|
scheduleJobId: job.id,
|
|
245
248
|
groupId: "system:scheduled",
|
|
@@ -258,19 +261,36 @@ async function runScheduleOnce(
|
|
|
258
261
|
continue;
|
|
259
262
|
}
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
264
|
+
// Reuse the conversation from the last successful run when the flag is set
|
|
265
|
+
// and a prior conversation still exists; otherwise bootstrap a new one.
|
|
266
|
+
let conversationId: string | null = null;
|
|
267
|
+
let conversationReused = false;
|
|
268
|
+
if (job.reuseConversation && !isOneShot) {
|
|
269
|
+
const lastId = getLastScheduleConversationId(job.id);
|
|
270
|
+
if (lastId && getConversation(lastId)) {
|
|
271
|
+
conversationId = lastId;
|
|
272
|
+
conversationReused = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (!conversationId) {
|
|
276
|
+
const conversation = bootstrapConversation({
|
|
277
|
+
conversationType: "scheduled",
|
|
278
|
+
source: "schedule",
|
|
279
|
+
scheduleJobId: job.id,
|
|
280
|
+
groupId: "system:scheduled",
|
|
281
|
+
origin: "schedule",
|
|
282
|
+
systemHint: isOneShot
|
|
283
|
+
? `Reminder: ${job.name}`
|
|
284
|
+
: `Schedule: ${job.name}`,
|
|
285
|
+
});
|
|
286
|
+
conversationId = conversation.id;
|
|
287
|
+
}
|
|
268
288
|
onScheduleConversationCreated?.({
|
|
269
|
-
conversationId
|
|
289
|
+
conversationId,
|
|
270
290
|
scheduleJobId: job.id,
|
|
271
291
|
title: job.name,
|
|
272
292
|
});
|
|
273
|
-
const runId = createScheduleRun(job.id,
|
|
293
|
+
const runId = createScheduleRun(job.id, conversationId);
|
|
274
294
|
const isRruleSetMsg =
|
|
275
295
|
job.syntax === "rrule" &&
|
|
276
296
|
job.expression != null &&
|
|
@@ -285,11 +305,11 @@ async function runScheduleOnce(
|
|
|
285
305
|
expression: job.expression,
|
|
286
306
|
isRruleSet: isRruleSetMsg,
|
|
287
307
|
isOneShot,
|
|
288
|
-
conversationId
|
|
308
|
+
conversationId,
|
|
289
309
|
},
|
|
290
310
|
isOneShot ? "Executing one-shot schedule" : "Executing schedule",
|
|
291
311
|
);
|
|
292
|
-
await processMessage(
|
|
312
|
+
await processMessage(conversationId, job.message, {
|
|
293
313
|
trustClass: "guardian",
|
|
294
314
|
});
|
|
295
315
|
completeScheduleRun(runId, { status: "ok" });
|
|
@@ -317,13 +337,20 @@ async function runScheduleOnce(
|
|
|
317
337
|
completeScheduleRun(runId, { status: "error", error: message });
|
|
318
338
|
if (isOneShot) failOneShot(job.id);
|
|
319
339
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
340
|
+
// Only skip invalidation when the conversation was *actually* reused,
|
|
341
|
+
// i.e. it contains prior successful context worth preserving. When
|
|
342
|
+
// reuseConversation is true but no prior conversation existed (first run
|
|
343
|
+
// or deleted), the conversation is brand-new and should be invalidated
|
|
344
|
+
// like any other failed conversation.
|
|
345
|
+
if (!conversationReused) {
|
|
346
|
+
try {
|
|
347
|
+
invalidateAssistantInferredItemsForConversation(conversationId);
|
|
348
|
+
} catch (cleanupErr) {
|
|
349
|
+
log.warn(
|
|
350
|
+
{ err: cleanupErr, conversationId },
|
|
351
|
+
"Failed to invalidate assistant-inferred memory items",
|
|
352
|
+
);
|
|
353
|
+
}
|
|
327
354
|
}
|
|
328
355
|
}
|
|
329
356
|
}
|
|
@@ -14,10 +14,10 @@ import { dirname, join, posix, resolve, sep } from "node:path";
|
|
|
14
14
|
import { gunzipSync } from "node:zlib";
|
|
15
15
|
|
|
16
16
|
import { getPlatformBaseUrl } from "../config/env.js";
|
|
17
|
+
import { deleteSkillCapabilityNode } from "../memory/graph/capability-seed.js";
|
|
17
18
|
import { getLogger } from "../util/logger.js";
|
|
18
19
|
import { getWorkspaceSkillsDir, readPlatformToken } from "../util/platform.js";
|
|
19
20
|
import { computeSkillHash, writeInstallMeta } from "./install-meta.js";
|
|
20
|
-
import { deleteSkillCapabilityMemory } from "./skill-memory.js";
|
|
21
21
|
|
|
22
22
|
const log = getLogger("catalog-install");
|
|
23
23
|
|
|
@@ -264,7 +264,7 @@ export function uninstallSkillLocally(skillId: string): void {
|
|
|
264
264
|
|
|
265
265
|
rmSync(skillDir, { recursive: true, force: true });
|
|
266
266
|
removeSkillsIndexEntry(skillId);
|
|
267
|
-
|
|
267
|
+
deleteSkillCapabilityNode(skillId);
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
export async function installSkillLocally(
|
|
@@ -392,6 +392,14 @@ export async function autoInstallFromCatalog(
|
|
|
392
392
|
return false;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
// If the skill already exists on disk (stale index), re-index it instead
|
|
396
|
+
// of attempting a fresh install that would fail.
|
|
397
|
+
const skillDir = join(getWorkspaceSkillsDir(), skillId);
|
|
398
|
+
if (existsSync(join(skillDir, "SKILL.md"))) {
|
|
399
|
+
upsertSkillsIndex(skillId);
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
|
|
395
403
|
// installSkillLocally handles dependency installation and SKILLS.md indexing.
|
|
396
404
|
await installSkillLocally(skillId, entry, false);
|
|
397
405
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runner for inline command expansions (`!\`command\``).
|
|
3
3
|
*
|
|
4
|
-
* Executes the literal command string
|
|
5
|
-
*
|
|
4
|
+
* Executes the literal command string without going through the general `bash`
|
|
5
|
+
* tool's permission path. Security constraints:
|
|
6
6
|
*
|
|
7
|
-
* - Network mode forced off (no outbound connections)
|
|
8
7
|
* - Sanitized environment variables only (no API keys, tokens, credentials)
|
|
9
8
|
* - No credential proxy, no CES client, no host fallback
|
|
9
|
+
* - Runs in Docker/platform-managed environments (network/filesystem isolation
|
|
10
|
+
* is provided by the container, not OS-level sandboxing)
|
|
10
11
|
* - Uses the conversation working directory as `cwd` so repo-local commands
|
|
11
12
|
* remain interoperable with externally authored skills that expect project
|
|
12
13
|
* context.
|
|
@@ -22,7 +23,6 @@
|
|
|
22
23
|
|
|
23
24
|
import { spawn } from "node:child_process";
|
|
24
25
|
|
|
25
|
-
import { getConfig } from "../config/loader.js";
|
|
26
26
|
import { buildSanitizedEnv } from "../tools/terminal/safe-env.js";
|
|
27
27
|
import { wrapCommand } from "../tools/terminal/sandbox.js";
|
|
28
28
|
import { getLogger } from "../util/logger.js";
|
|
@@ -91,7 +91,7 @@ export interface InlineCommandRunnerOptions {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Run an inline command expansion
|
|
94
|
+
* Run an inline command expansion.
|
|
95
95
|
*
|
|
96
96
|
* @param command The literal command string from the `!\`...\`` token.
|
|
97
97
|
* @param workingDir The conversation's working directory (repo root).
|
|
@@ -105,14 +105,12 @@ export async function runInlineCommand(
|
|
|
105
105
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
106
106
|
const maxChars = options?.maxOutputChars ?? MAX_OUTPUT_CHARS;
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const wrapped = wrapCommand(command, workingDir, sandboxConfig
|
|
114
|
-
networkMode: "off",
|
|
115
|
-
});
|
|
108
|
+
// Sandbox is disabled — the assistant runs in Docker or platform-managed
|
|
109
|
+
// environments where the container provides isolation. The networkMode
|
|
110
|
+
// option is a no-op when sandbox is disabled (wrapCommand returns a plain
|
|
111
|
+
// bash invocation); network isolation is handled at the container level.
|
|
112
|
+
const sandboxConfig = { enabled: false } as const;
|
|
113
|
+
const wrapped = wrapCommand(command, workingDir, sandboxConfig);
|
|
116
114
|
|
|
117
115
|
// Build a minimal, sanitized environment. Explicitly exclude gateway URL,
|
|
118
116
|
// workspace dir, and data dir since inline commands have no business calling
|
|
@@ -11,10 +11,10 @@ import { dirname, join } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
import { stringify as stringifyYaml } from "yaml";
|
|
13
13
|
|
|
14
|
+
import { deleteSkillCapabilityNode } from "../memory/graph/capability-seed.js";
|
|
14
15
|
import { getLogger } from "../util/logger.js";
|
|
15
16
|
import { getWorkspaceSkillsDir } from "../util/platform.js";
|
|
16
17
|
import { writeInstallMeta } from "./install-meta.js";
|
|
17
|
-
import { deleteSkillCapabilityMemory } from "./skill-memory.js";
|
|
18
18
|
|
|
19
19
|
const log = getLogger("managed-store");
|
|
20
20
|
|
|
@@ -319,7 +319,7 @@ export function deleteManagedSkill(
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
rmSync(skillDir, { recursive: true });
|
|
322
|
-
|
|
322
|
+
deleteSkillCapabilityNode(id);
|
|
323
323
|
log.info({ id, path: skillDir }, "Deleted managed skill");
|
|
324
324
|
|
|
325
325
|
let indexUpdated = false;
|