vellum 0.2.1 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/bun.lock +71 -100
- package/package.json +5 -3
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +4 -3
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +130 -4
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +305 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twilio-config.test.ts +221 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +71 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-regressions.test.ts +100 -2
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +5 -3
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-conflict-gate.test.ts +28 -25
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +71 -48
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +218 -3
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +8 -8
- package/src/calls/twilio-provider.ts +13 -9
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/twilio-webhook-urls.ts +50 -0
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +270 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +82 -23
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +34 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +165 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -0
- package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +205 -5
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -277
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +384 -68
- package/src/daemon/ipc-contract-inventory.json +32 -4
- package/src/daemon/ipc-contract.ts +156 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +75 -10
- package/src/daemon/server.ts +143 -26
- package/src/daemon/session-agent-loop.ts +922 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-conflict-gate.ts +18 -109
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1153
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/conflict-intent.ts +114 -0
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +96 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/job-handlers/conflict.ts +23 -1
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +23 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/gateway-client.ts +36 -0
- package/src/runtime/http-server.ts +166 -22
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +125 -88
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +293 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- package/src/tools/claude-code/claude-code.ts +207 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +20 -4
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +269 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -537,6 +537,25 @@ exports[`IPC message snapshots ClientMessage types vercel_api_config serializes
|
|
|
537
537
|
}
|
|
538
538
|
`;
|
|
539
539
|
|
|
540
|
+
exports[`IPC message snapshots ClientMessage types twitter_integration_config serializes to expected JSON 1`] = `
|
|
541
|
+
{
|
|
542
|
+
"action": "get",
|
|
543
|
+
"type": "twitter_integration_config",
|
|
544
|
+
}
|
|
545
|
+
`;
|
|
546
|
+
|
|
547
|
+
exports[`IPC message snapshots ClientMessage types twitter_auth_start serializes to expected JSON 1`] = `
|
|
548
|
+
{
|
|
549
|
+
"type": "twitter_auth_start",
|
|
550
|
+
}
|
|
551
|
+
`;
|
|
552
|
+
|
|
553
|
+
exports[`IPC message snapshots ClientMessage types twitter_auth_status serializes to expected JSON 1`] = `
|
|
554
|
+
{
|
|
555
|
+
"type": "twitter_auth_status",
|
|
556
|
+
}
|
|
557
|
+
`;
|
|
558
|
+
|
|
540
559
|
exports[`IPC message snapshots ClientMessage types link_open_request serializes to expected JSON 1`] = `
|
|
541
560
|
{
|
|
542
561
|
"type": "link_open_request",
|
|
@@ -668,17 +687,6 @@ exports[`IPC message snapshots ClientMessage types work_item_get serializes to e
|
|
|
668
687
|
}
|
|
669
688
|
`;
|
|
670
689
|
|
|
671
|
-
exports[`IPC message snapshots ClientMessage types work_item_create serializes to expected JSON 1`] = `
|
|
672
|
-
{
|
|
673
|
-
"notes": "High priority",
|
|
674
|
-
"priorityTier": 1,
|
|
675
|
-
"sortIndex": 0,
|
|
676
|
-
"taskId": "task-001",
|
|
677
|
-
"title": "Process report",
|
|
678
|
-
"type": "work_item_create",
|
|
679
|
-
}
|
|
680
|
-
`;
|
|
681
|
-
|
|
682
690
|
exports[`IPC message snapshots ClientMessage types work_item_update serializes to expected JSON 1`] = `
|
|
683
691
|
{
|
|
684
692
|
"id": "wi-001",
|
|
@@ -709,6 +717,38 @@ exports[`IPC message snapshots ClientMessage types work_item_run_task serializes
|
|
|
709
717
|
}
|
|
710
718
|
`;
|
|
711
719
|
|
|
720
|
+
exports[`IPC message snapshots ClientMessage types work_item_output serializes to expected JSON 1`] = `
|
|
721
|
+
{
|
|
722
|
+
"id": "wi-001",
|
|
723
|
+
"type": "work_item_output",
|
|
724
|
+
}
|
|
725
|
+
`;
|
|
726
|
+
|
|
727
|
+
exports[`IPC message snapshots ClientMessage types work_item_preflight serializes to expected JSON 1`] = `
|
|
728
|
+
{
|
|
729
|
+
"id": "wi-001",
|
|
730
|
+
"type": "work_item_preflight",
|
|
731
|
+
}
|
|
732
|
+
`;
|
|
733
|
+
|
|
734
|
+
exports[`IPC message snapshots ClientMessage types work_item_approve_permissions serializes to expected JSON 1`] = `
|
|
735
|
+
{
|
|
736
|
+
"approvedTools": [
|
|
737
|
+
"bash",
|
|
738
|
+
"file_write",
|
|
739
|
+
],
|
|
740
|
+
"id": "wi-001",
|
|
741
|
+
"type": "work_item_approve_permissions",
|
|
742
|
+
}
|
|
743
|
+
`;
|
|
744
|
+
|
|
745
|
+
exports[`IPC message snapshots ClientMessage types work_item_cancel serializes to expected JSON 1`] = `
|
|
746
|
+
{
|
|
747
|
+
"id": "wi-001",
|
|
748
|
+
"type": "work_item_cancel",
|
|
749
|
+
}
|
|
750
|
+
`;
|
|
751
|
+
|
|
712
752
|
exports[`IPC message snapshots ClientMessage types document_save serializes to expected JSON 1`] = `
|
|
713
753
|
{
|
|
714
754
|
"content": "# Hello",
|
|
@@ -743,7 +783,6 @@ exports[`IPC message snapshots ClientMessage types subagent_abort serializes to
|
|
|
743
783
|
|
|
744
784
|
exports[`IPC message snapshots ClientMessage types subagent_status serializes to expected JSON 1`] = `
|
|
745
785
|
{
|
|
746
|
-
"sessionId": "sess-001",
|
|
747
786
|
"subagentId": "sub-001",
|
|
748
787
|
"type": "subagent_status",
|
|
749
788
|
}
|
|
@@ -1451,12 +1490,14 @@ exports[`IPC message snapshots ServerMessage types schedules_list_response seria
|
|
|
1451
1490
|
"cronExpression": "0 9 * * 1-5",
|
|
1452
1491
|
"description": "Every weekday at 9:00 AM",
|
|
1453
1492
|
"enabled": true,
|
|
1493
|
+
"expression": "0 9 * * 1-5",
|
|
1454
1494
|
"id": "sched-001",
|
|
1455
1495
|
"lastRunAt": 1700000000000,
|
|
1456
1496
|
"lastStatus": "ok",
|
|
1457
1497
|
"message": "Remind me about the standup",
|
|
1458
1498
|
"name": "Daily standup reminder",
|
|
1459
1499
|
"nextRunAt": 1700100000000,
|
|
1500
|
+
"syntax": "cron",
|
|
1460
1501
|
"timezone": "America/Los_Angeles",
|
|
1461
1502
|
},
|
|
1462
1503
|
],
|
|
@@ -1737,6 +1778,34 @@ exports[`IPC message snapshots ServerMessage types vercel_api_config_response se
|
|
|
1737
1778
|
}
|
|
1738
1779
|
`;
|
|
1739
1780
|
|
|
1781
|
+
exports[`IPC message snapshots ServerMessage types twitter_integration_config_response serializes to expected JSON 1`] = `
|
|
1782
|
+
{
|
|
1783
|
+
"connected": false,
|
|
1784
|
+
"localClientConfigured": true,
|
|
1785
|
+
"managedAvailable": false,
|
|
1786
|
+
"mode": "local_byo",
|
|
1787
|
+
"success": true,
|
|
1788
|
+
"type": "twitter_integration_config_response",
|
|
1789
|
+
}
|
|
1790
|
+
`;
|
|
1791
|
+
|
|
1792
|
+
exports[`IPC message snapshots ServerMessage types twitter_auth_result serializes to expected JSON 1`] = `
|
|
1793
|
+
{
|
|
1794
|
+
"accountInfo": "@vellum_test",
|
|
1795
|
+
"success": true,
|
|
1796
|
+
"type": "twitter_auth_result",
|
|
1797
|
+
}
|
|
1798
|
+
`;
|
|
1799
|
+
|
|
1800
|
+
exports[`IPC message snapshots ServerMessage types twitter_auth_status_response serializes to expected JSON 1`] = `
|
|
1801
|
+
{
|
|
1802
|
+
"accountInfo": "@vellum_test",
|
|
1803
|
+
"connected": true,
|
|
1804
|
+
"mode": "local_byo",
|
|
1805
|
+
"type": "twitter_auth_status_response",
|
|
1806
|
+
}
|
|
1807
|
+
`;
|
|
1808
|
+
|
|
1740
1809
|
exports[`IPC message snapshots ServerMessage types open_url serializes to expected JSON 1`] = `
|
|
1741
1810
|
{
|
|
1742
1811
|
"title": "Example",
|
|
@@ -1987,28 +2056,6 @@ exports[`IPC message snapshots ServerMessage types work_item_get_response serial
|
|
|
1987
2056
|
}
|
|
1988
2057
|
`;
|
|
1989
2058
|
|
|
1990
|
-
exports[`IPC message snapshots ServerMessage types work_item_create_response serializes to expected JSON 1`] = `
|
|
1991
|
-
{
|
|
1992
|
-
"item": {
|
|
1993
|
-
"createdAt": 1700000000,
|
|
1994
|
-
"id": "wi-001",
|
|
1995
|
-
"lastRunConversationId": null,
|
|
1996
|
-
"lastRunId": null,
|
|
1997
|
-
"lastRunStatus": null,
|
|
1998
|
-
"notes": null,
|
|
1999
|
-
"priorityTier": 1,
|
|
2000
|
-
"sortIndex": null,
|
|
2001
|
-
"sourceId": null,
|
|
2002
|
-
"sourceType": null,
|
|
2003
|
-
"status": "queued",
|
|
2004
|
-
"taskId": "task-001",
|
|
2005
|
-
"title": "Process report",
|
|
2006
|
-
"updatedAt": 1700000000,
|
|
2007
|
-
},
|
|
2008
|
-
"type": "work_item_create_response",
|
|
2009
|
-
}
|
|
2010
|
-
`;
|
|
2011
|
-
|
|
2012
2059
|
exports[`IPC message snapshots ServerMessage types work_item_update_response serializes to expected JSON 1`] = `
|
|
2013
2060
|
{
|
|
2014
2061
|
"item": {
|
|
@@ -2048,6 +2095,58 @@ exports[`IPC message snapshots ServerMessage types work_item_run_task_response s
|
|
|
2048
2095
|
}
|
|
2049
2096
|
`;
|
|
2050
2097
|
|
|
2098
|
+
exports[`IPC message snapshots ServerMessage types work_item_output_response serializes to expected JSON 1`] = `
|
|
2099
|
+
{
|
|
2100
|
+
"id": "wi-001",
|
|
2101
|
+
"output": {
|
|
2102
|
+
"completedAt": 1700002000,
|
|
2103
|
+
"conversationId": "conv-001",
|
|
2104
|
+
"highlights": [
|
|
2105
|
+
"- Key finding 1",
|
|
2106
|
+
"- Key finding 2",
|
|
2107
|
+
],
|
|
2108
|
+
"runId": "run-001",
|
|
2109
|
+
"status": "completed",
|
|
2110
|
+
"summary": "Report processed successfully.",
|
|
2111
|
+
"title": "Process report",
|
|
2112
|
+
},
|
|
2113
|
+
"success": true,
|
|
2114
|
+
"type": "work_item_output_response",
|
|
2115
|
+
}
|
|
2116
|
+
`;
|
|
2117
|
+
|
|
2118
|
+
exports[`IPC message snapshots ServerMessage types work_item_preflight_response serializes to expected JSON 1`] = `
|
|
2119
|
+
{
|
|
2120
|
+
"id": "wi-001",
|
|
2121
|
+
"permissions": [
|
|
2122
|
+
{
|
|
2123
|
+
"currentDecision": "prompt",
|
|
2124
|
+
"description": "Run shell commands",
|
|
2125
|
+
"riskLevel": "medium",
|
|
2126
|
+
"tool": "bash",
|
|
2127
|
+
},
|
|
2128
|
+
],
|
|
2129
|
+
"success": true,
|
|
2130
|
+
"type": "work_item_preflight_response",
|
|
2131
|
+
}
|
|
2132
|
+
`;
|
|
2133
|
+
|
|
2134
|
+
exports[`IPC message snapshots ServerMessage types work_item_approve_permissions_response serializes to expected JSON 1`] = `
|
|
2135
|
+
{
|
|
2136
|
+
"id": "wi-001",
|
|
2137
|
+
"success": true,
|
|
2138
|
+
"type": "work_item_approve_permissions_response",
|
|
2139
|
+
}
|
|
2140
|
+
`;
|
|
2141
|
+
|
|
2142
|
+
exports[`IPC message snapshots ServerMessage types work_item_cancel_response serializes to expected JSON 1`] = `
|
|
2143
|
+
{
|
|
2144
|
+
"id": "wi-001",
|
|
2145
|
+
"success": true,
|
|
2146
|
+
"type": "work_item_cancel_response",
|
|
2147
|
+
}
|
|
2148
|
+
`;
|
|
2149
|
+
|
|
2051
2150
|
exports[`IPC message snapshots ServerMessage types work_item_status_changed serializes to expected JSON 1`] = `
|
|
2052
2151
|
{
|
|
2053
2152
|
"item": {
|
|
@@ -28,7 +28,7 @@ mock.module('../tools/registry.js', () => ({
|
|
|
28
28
|
registerTool: () => {},
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
|
-
import { initializeDb, getDb } from '../memory/db.js';
|
|
31
|
+
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
32
32
|
import {
|
|
33
33
|
createAccount,
|
|
34
34
|
listAccounts,
|
|
@@ -47,6 +47,7 @@ const _ctx: ToolContext = {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
afterAll(() => {
|
|
50
|
+
resetDb();
|
|
50
51
|
mock.restore();
|
|
51
52
|
try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
|
|
52
53
|
});
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, mock } from 'bun:test';
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
|
|
6
|
+
// Mock platform to use a temp workspace dir
|
|
7
|
+
let testWorkspaceDir: string;
|
|
8
|
+
|
|
9
|
+
mock.module('../util/platform.js', () => ({
|
|
10
|
+
getWorkspacePromptPath: (file: string) => join(testWorkspaceDir, file),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Mock config loader
|
|
14
|
+
let mockConfig = {
|
|
15
|
+
agentHeartbeat: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
intervalMs: 60_000,
|
|
18
|
+
activeHoursStart: undefined as number | undefined,
|
|
19
|
+
activeHoursEnd: undefined as number | undefined,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
mock.module('../config/loader.js', () => ({
|
|
24
|
+
getConfig: () => mockConfig,
|
|
25
|
+
loadConfig: () => mockConfig,
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// Mock conversation store
|
|
29
|
+
const createdConversations: Array<{ title: string; threadType: string }> = [];
|
|
30
|
+
let conversationIdCounter = 0;
|
|
31
|
+
|
|
32
|
+
mock.module('../memory/conversation-store.js', () => ({
|
|
33
|
+
createConversation: (opts: { title: string; threadType: string }) => {
|
|
34
|
+
createdConversations.push(opts);
|
|
35
|
+
return { id: `conv-${++conversationIdCounter}`, ...opts };
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
// Mock logger
|
|
40
|
+
mock.module('../util/logger.js', () => ({
|
|
41
|
+
getLogger: () => ({
|
|
42
|
+
info: () => {},
|
|
43
|
+
debug: () => {},
|
|
44
|
+
warn: () => {},
|
|
45
|
+
error: () => {},
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// Import after mocks are set up
|
|
50
|
+
const { AgentHeartbeatService } = await import('../agent-heartbeat/agent-heartbeat-service.js');
|
|
51
|
+
|
|
52
|
+
describe('AgentHeartbeatService', () => {
|
|
53
|
+
let processMessageCalls: Array<{ conversationId: string; content: string }>;
|
|
54
|
+
let alerterCalls: Array<{ type: string; title: string; body: string }>;
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
testWorkspaceDir = join(tmpdir(), `vellum-agent-hb-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
58
|
+
mkdirSync(testWorkspaceDir, { recursive: true });
|
|
59
|
+
|
|
60
|
+
processMessageCalls = [];
|
|
61
|
+
alerterCalls = [];
|
|
62
|
+
createdConversations.length = 0;
|
|
63
|
+
conversationIdCounter = 0;
|
|
64
|
+
|
|
65
|
+
mockConfig = {
|
|
66
|
+
agentHeartbeat: {
|
|
67
|
+
enabled: true,
|
|
68
|
+
intervalMs: 60_000,
|
|
69
|
+
activeHoursStart: undefined,
|
|
70
|
+
activeHoursEnd: undefined,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function createService(overrides?: {
|
|
76
|
+
processMessage?: (id: string, content: string) => Promise<{ messageId: string }>;
|
|
77
|
+
getCurrentHour?: () => number;
|
|
78
|
+
}) {
|
|
79
|
+
return new AgentHeartbeatService({
|
|
80
|
+
processMessage: overrides?.processMessage ?? (async (conversationId: string, content: string) => {
|
|
81
|
+
processMessageCalls.push({ conversationId, content });
|
|
82
|
+
return { messageId: 'msg-1' };
|
|
83
|
+
}),
|
|
84
|
+
alerter: (alert: { type: string; title: string; body: string }) => {
|
|
85
|
+
alerterCalls.push(alert);
|
|
86
|
+
},
|
|
87
|
+
getCurrentHour: overrides?.getCurrentHour,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
test('runOnce() calls processMessage with correct prompt', async () => {
|
|
92
|
+
const service = createService();
|
|
93
|
+
await service.runOnce();
|
|
94
|
+
|
|
95
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
96
|
+
expect(processMessageCalls[0].conversationId).toBe('conv-1');
|
|
97
|
+
expect(processMessageCalls[0].content).toContain('<heartbeat-checklist>');
|
|
98
|
+
expect(processMessageCalls[0].content).toContain('<heartbeat-disposition>');
|
|
99
|
+
expect(processMessageCalls[0].content).toContain('HEARTBEAT_OK');
|
|
100
|
+
expect(processMessageCalls[0].content).toContain('HEARTBEAT_ALERT');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('HEARTBEAT.md content is embedded in prompt when file exists', async () => {
|
|
104
|
+
const customChecklist = '- Check the weather\n- Water the plants';
|
|
105
|
+
writeFileSync(join(testWorkspaceDir, 'HEARTBEAT.md'), customChecklist);
|
|
106
|
+
|
|
107
|
+
const service = createService();
|
|
108
|
+
await service.runOnce();
|
|
109
|
+
|
|
110
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
111
|
+
expect(processMessageCalls[0].content).toContain('Check the weather');
|
|
112
|
+
expect(processMessageCalls[0].content).toContain('Water the plants');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('default checklist used when no HEARTBEAT.md', async () => {
|
|
116
|
+
const service = createService();
|
|
117
|
+
await service.runOnce();
|
|
118
|
+
|
|
119
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
120
|
+
expect(processMessageCalls[0].content).toContain('Check the current weather');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('creates background conversation titled "Agent Heartbeat"', async () => {
|
|
124
|
+
const service = createService();
|
|
125
|
+
await service.runOnce();
|
|
126
|
+
|
|
127
|
+
expect(createdConversations).toHaveLength(1);
|
|
128
|
+
expect(createdConversations[0].title).toBe('Agent Heartbeat');
|
|
129
|
+
expect(createdConversations[0].threadType).toBe('background');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('active hours guard skips outside window', async () => {
|
|
133
|
+
mockConfig.agentHeartbeat.activeHoursStart = 9;
|
|
134
|
+
mockConfig.agentHeartbeat.activeHoursEnd = 17;
|
|
135
|
+
|
|
136
|
+
const service = createService({ getCurrentHour: () => 3 });
|
|
137
|
+
await service.runOnce();
|
|
138
|
+
|
|
139
|
+
expect(processMessageCalls).toHaveLength(0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('active hours guard allows within window', async () => {
|
|
143
|
+
mockConfig.agentHeartbeat.activeHoursStart = 9;
|
|
144
|
+
mockConfig.agentHeartbeat.activeHoursEnd = 17;
|
|
145
|
+
|
|
146
|
+
const service = createService({ getCurrentHour: () => 12 });
|
|
147
|
+
await service.runOnce();
|
|
148
|
+
|
|
149
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('active hours handles overnight window', async () => {
|
|
153
|
+
mockConfig.agentHeartbeat.activeHoursStart = 22;
|
|
154
|
+
mockConfig.agentHeartbeat.activeHoursEnd = 6;
|
|
155
|
+
|
|
156
|
+
// 23:00 should be within the window
|
|
157
|
+
const service = createService({ getCurrentHour: () => 23 });
|
|
158
|
+
await service.runOnce();
|
|
159
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
160
|
+
|
|
161
|
+
// 10:00 should be outside the window
|
|
162
|
+
processMessageCalls.length = 0;
|
|
163
|
+
createdConversations.length = 0;
|
|
164
|
+
const service2 = createService({ getCurrentHour: () => 10 });
|
|
165
|
+
await service2.runOnce();
|
|
166
|
+
expect(processMessageCalls).toHaveLength(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('overlap prevention works', async () => {
|
|
170
|
+
let resolveFirst: () => void;
|
|
171
|
+
const firstPromise = new Promise<void>((r) => { resolveFirst = r; });
|
|
172
|
+
|
|
173
|
+
const service = createService({
|
|
174
|
+
processMessage: async () => {
|
|
175
|
+
await firstPromise;
|
|
176
|
+
processMessageCalls.push({ conversationId: 'slow', content: 'slow' });
|
|
177
|
+
return { messageId: 'msg-1' };
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Start first run (will block)
|
|
182
|
+
const run1 = service.runOnce();
|
|
183
|
+
// Give the first run a tick to set activeRun
|
|
184
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
185
|
+
|
|
186
|
+
// Second run should be skipped due to overlap
|
|
187
|
+
await service.runOnce();
|
|
188
|
+
|
|
189
|
+
// Resolve the first run
|
|
190
|
+
resolveFirst!();
|
|
191
|
+
await run1;
|
|
192
|
+
|
|
193
|
+
// Only the first run should have called processMessage
|
|
194
|
+
expect(processMessageCalls).toHaveLength(1);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('disabled config prevents start', () => {
|
|
198
|
+
mockConfig.agentHeartbeat.enabled = false;
|
|
199
|
+
const service = createService();
|
|
200
|
+
service.start();
|
|
201
|
+
// No error, just a no-op. We can verify by calling stop which should also be a no-op.
|
|
202
|
+
// The key assertion is that no timer is set (verified by stop not hanging).
|
|
203
|
+
service.stop();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('disabled config prevents runOnce', async () => {
|
|
207
|
+
mockConfig.agentHeartbeat.enabled = false;
|
|
208
|
+
const service = createService();
|
|
209
|
+
await service.runOnce();
|
|
210
|
+
|
|
211
|
+
expect(processMessageCalls).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('alerts on processMessage failure', async () => {
|
|
215
|
+
const service = createService({
|
|
216
|
+
processMessage: async () => {
|
|
217
|
+
throw new Error('LLM timeout');
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
await service.runOnce();
|
|
222
|
+
|
|
223
|
+
expect(alerterCalls).toHaveLength(1);
|
|
224
|
+
expect(alerterCalls[0].type).toBe('agent_heartbeat_alert');
|
|
225
|
+
expect(alerterCalls[0].title).toBe('Agent Heartbeat Failed');
|
|
226
|
+
expect(alerterCalls[0].body).toBe('LLM timeout');
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test('alerts on conversation creation failure', async () => {
|
|
230
|
+
// Override createConversation to throw via a fresh import trick:
|
|
231
|
+
// Since createConversation is mocked at module level, we simulate
|
|
232
|
+
// this by having processMessage throw before it's called — but the
|
|
233
|
+
// real fix is that executeRun wraps createConversation in the try/catch.
|
|
234
|
+
// We verify by checking that any error in executeRun triggers the alert.
|
|
235
|
+
const service = createService({
|
|
236
|
+
processMessage: async () => {
|
|
237
|
+
throw new Error('DB locked');
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await service.runOnce();
|
|
242
|
+
|
|
243
|
+
expect(alerterCalls).toHaveLength(1);
|
|
244
|
+
expect(alerterCalls[0].body).toBe('DB locked');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('cleanup', () => {
|
|
248
|
+
try { rmSync(testWorkspaceDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -36,7 +36,7 @@ mock.module('../config/loader.js', () => ({
|
|
|
36
36
|
}),
|
|
37
37
|
}));
|
|
38
38
|
|
|
39
|
-
import { initializeDb, getDb } from '../memory/db.js';
|
|
39
|
+
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
40
40
|
import { uploadAttachment, linkAttachmentToMessage } from '../memory/attachments-store.js';
|
|
41
41
|
import { createConversation, addMessage } from '../memory/conversation-store.js';
|
|
42
42
|
import { assetMaterializeTool } from '../tools/assets/materialize.js';
|
|
@@ -49,6 +49,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
49
49
|
mkdirSync(sandboxDir, { recursive: true });
|
|
50
50
|
|
|
51
51
|
afterAll(() => {
|
|
52
|
+
resetDb();
|
|
52
53
|
try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
|
|
53
54
|
});
|
|
54
55
|
|
|
@@ -103,7 +104,7 @@ describe('AssetMaterializeTool sandbox path enforcement', () => {
|
|
|
103
104
|
beforeEach(resetTables);
|
|
104
105
|
|
|
105
106
|
test('rejects path that escapes sandbox via ../', async () => {
|
|
106
|
-
const stored = uploadAttachment('
|
|
107
|
+
const stored = uploadAttachment('test.png', 'image/png', 'AAAA');
|
|
107
108
|
const result = await assetMaterializeTool.execute(
|
|
108
109
|
{ attachment_id: stored.id, destination_path: '../../etc/evil.png' },
|
|
109
110
|
dummyContext,
|
|
@@ -113,7 +114,7 @@ describe('AssetMaterializeTool sandbox path enforcement', () => {
|
|
|
113
114
|
});
|
|
114
115
|
|
|
115
116
|
test('rejects absolute path outside sandbox', async () => {
|
|
116
|
-
const stored = uploadAttachment('
|
|
117
|
+
const stored = uploadAttachment('test.png', 'image/png', 'AAAA');
|
|
117
118
|
const result = await assetMaterializeTool.execute(
|
|
118
119
|
{ attachment_id: stored.id, destination_path: '/tmp/outside-sandbox/evil.png' },
|
|
119
120
|
dummyContext,
|
|
@@ -123,7 +124,7 @@ describe('AssetMaterializeTool sandbox path enforcement', () => {
|
|
|
123
124
|
});
|
|
124
125
|
|
|
125
126
|
test('accepts relative path inside sandbox', async () => {
|
|
126
|
-
const stored = uploadAttachment('
|
|
127
|
+
const stored = uploadAttachment('test.png', 'image/png', 'AAAA');
|
|
127
128
|
const result = await assetMaterializeTool.execute(
|
|
128
129
|
{ attachment_id: stored.id, destination_path: 'output.png' },
|
|
129
130
|
dummyContext,
|
|
@@ -133,7 +134,7 @@ describe('AssetMaterializeTool sandbox path enforcement', () => {
|
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
test('accepts nested path inside sandbox with auto-created subdirs', async () => {
|
|
136
|
-
const stored = uploadAttachment('
|
|
137
|
+
const stored = uploadAttachment('test.png', 'image/png', 'AAAA');
|
|
137
138
|
const result = await assetMaterializeTool.execute(
|
|
138
139
|
{ attachment_id: stored.id, destination_path: 'subdir/deep/output.png' },
|
|
139
140
|
dummyContext,
|
|
@@ -172,7 +173,7 @@ describe('AssetMaterializeTool materialization', () => {
|
|
|
172
173
|
const originalContent = 'Hello, World!';
|
|
173
174
|
const base64Content = Buffer.from(originalContent).toString('base64');
|
|
174
175
|
|
|
175
|
-
const stored = uploadAttachment('
|
|
176
|
+
const stored = uploadAttachment('hello.txt', 'text/plain', base64Content);
|
|
176
177
|
|
|
177
178
|
const destPath = 'materialized-hello.txt';
|
|
178
179
|
const result = await assetMaterializeTool.execute(
|
|
@@ -194,7 +195,7 @@ describe('AssetMaterializeTool materialization', () => {
|
|
|
194
195
|
const binaryBytes = Buffer.from([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
|
|
195
196
|
const base64Content = binaryBytes.toString('base64');
|
|
196
197
|
|
|
197
|
-
const stored = uploadAttachment('
|
|
198
|
+
const stored = uploadAttachment('tiny.png', 'image/png', base64Content);
|
|
198
199
|
|
|
199
200
|
const result = await assetMaterializeTool.execute(
|
|
200
201
|
{ attachment_id: stored.id, destination_path: 'images/tiny.png' },
|
|
@@ -209,7 +210,7 @@ describe('AssetMaterializeTool materialization', () => {
|
|
|
209
210
|
|
|
210
211
|
test('result includes resolved path', async () => {
|
|
211
212
|
const base64Content = Buffer.from('test').toString('base64');
|
|
212
|
-
const stored = uploadAttachment('
|
|
213
|
+
const stored = uploadAttachment('doc.txt', 'text/plain', base64Content);
|
|
213
214
|
|
|
214
215
|
const result = await assetMaterializeTool.execute(
|
|
215
216
|
{ attachment_id: stored.id, destination_path: 'output/doc.txt' },
|
|
@@ -222,7 +223,7 @@ describe('AssetMaterializeTool materialization', () => {
|
|
|
222
223
|
|
|
223
224
|
test('result includes filename, MIME type and size info', async () => {
|
|
224
225
|
const base64Content = Buffer.from('some data here').toString('base64');
|
|
225
|
-
const stored = uploadAttachment('
|
|
226
|
+
const stored = uploadAttachment('report.pdf', 'application/pdf', base64Content);
|
|
226
227
|
|
|
227
228
|
const result = await assetMaterializeTool.execute(
|
|
228
229
|
{ attachment_id: stored.id, destination_path: 'report.pdf' },
|
|
@@ -307,7 +308,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
307
308
|
test('materializing from a standard thread works from any context', async () => {
|
|
308
309
|
const standardConv = createConversation({ title: 'standard-conv' });
|
|
309
310
|
const base64Content = Buffer.from('standard content').toString('base64');
|
|
310
|
-
const attachment = uploadAttachment('
|
|
311
|
+
const attachment = uploadAttachment('public.txt', 'text/plain', base64Content);
|
|
311
312
|
const msg = addMessage(standardConv.id, 'user', 'standard message');
|
|
312
313
|
linkAttachmentToMessage(msg.id, attachment.id, 0);
|
|
313
314
|
|
|
@@ -330,7 +331,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
330
331
|
test('materializing from a private thread works within the same private thread', async () => {
|
|
331
332
|
const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
|
|
332
333
|
const base64Content = Buffer.from('private content').toString('base64');
|
|
333
|
-
const attachment = uploadAttachment('
|
|
334
|
+
const attachment = uploadAttachment('secret.txt', 'text/plain', base64Content);
|
|
334
335
|
const msg = addMessage(privateConv.id, 'user', 'private message');
|
|
335
336
|
linkAttachmentToMessage(msg.id, attachment.id, 0);
|
|
336
337
|
|
|
@@ -352,7 +353,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
352
353
|
test('materializing from a private thread is REJECTED from a different conversation', async () => {
|
|
353
354
|
const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
|
|
354
355
|
const base64Content = Buffer.from('private content').toString('base64');
|
|
355
|
-
const attachment = uploadAttachment('
|
|
356
|
+
const attachment = uploadAttachment('secret.txt', 'text/plain', base64Content);
|
|
356
357
|
const msg = addMessage(privateConv.id, 'user', 'private message');
|
|
357
358
|
linkAttachmentToMessage(msg.id, attachment.id, 0);
|
|
358
359
|
|
|
@@ -376,7 +377,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
376
377
|
test('error message is user-actionable', async () => {
|
|
377
378
|
const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
|
|
378
379
|
const base64Content = Buffer.from('private content').toString('base64');
|
|
379
|
-
const attachment = uploadAttachment('
|
|
380
|
+
const attachment = uploadAttachment('confidential.pdf', 'application/pdf', base64Content);
|
|
380
381
|
const msg = addMessage(privateConv.id, 'user', 'private message');
|
|
381
382
|
linkAttachmentToMessage(msg.id, attachment.id, 0);
|
|
382
383
|
|
|
@@ -402,7 +403,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
402
403
|
test('materializing from a different private thread is REJECTED', async () => {
|
|
403
404
|
const privateConv1 = createConversation({ title: 'private-conv-1', threadType: 'private' });
|
|
404
405
|
const base64Content = Buffer.from('private content').toString('base64');
|
|
405
|
-
const attachment = uploadAttachment('
|
|
406
|
+
const attachment = uploadAttachment('secret.txt', 'text/plain', base64Content);
|
|
406
407
|
const msg = addMessage(privateConv1.id, 'user', 'private message');
|
|
407
408
|
linkAttachmentToMessage(msg.id, attachment.id, 0);
|
|
408
409
|
|
|
@@ -426,7 +427,7 @@ describe('AssetMaterializeTool visibility policy', () => {
|
|
|
426
427
|
const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
|
|
427
428
|
const standardConv = createConversation({ title: 'standard-conv' });
|
|
428
429
|
const base64Content = Buffer.from('shared content').toString('base64');
|
|
429
|
-
const attachment = uploadAttachment('
|
|
430
|
+
const attachment = uploadAttachment('shared.txt', 'text/plain', base64Content);
|
|
430
431
|
|
|
431
432
|
const msg1 = addMessage(privateConv.id, 'user', 'private message');
|
|
432
433
|
const msg2 = addMessage(standardConv.id, 'user', 'standard message');
|