vellum 0.2.0 → 0.2.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/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- 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 +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- 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 +5 -8
- 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 +454 -0
- 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-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- 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 +348 -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__/doordash-session.test.ts +9 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -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 +96 -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-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-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 +17 -10
- 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 +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- 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-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +89 -16
- 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 +273 -2
- 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 +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -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 +163 -0
- 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.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- 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 +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -0
- 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 +192 -4
- 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 -271
- 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 +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- 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 +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- 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 -1151
- package/src/media/gemini-image-service.ts +1 -1
- 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/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 +362 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- 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 +65 -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/http-server.ts +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- 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 +220 -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 +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +77 -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 +21 -5
- 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/registry.ts +2 -4
- 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 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -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 +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- 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
package/scripts/test.sh
CHANGED
|
@@ -36,6 +36,11 @@ while IFS= read -r test_file; do
|
|
|
36
36
|
continue
|
|
37
37
|
fi
|
|
38
38
|
fi
|
|
39
|
+
# Always exclude benchmark files — run them with `bun run test:bench` instead
|
|
40
|
+
if [[ "$(basename "${test_file}")" == *.benchmark.test.ts ]]; then
|
|
41
|
+
continue
|
|
42
|
+
fi
|
|
43
|
+
|
|
39
44
|
test_files+=("${test_file}")
|
|
40
45
|
done < <(find src/__tests__ -maxdepth 1 -type f -name '*.test.ts' | sort)
|
|
41
46
|
|
|
@@ -87,6 +87,13 @@ exports[`IPC message snapshots ClientMessage types model_set serializes to expec
|
|
|
87
87
|
}
|
|
88
88
|
`;
|
|
89
89
|
|
|
90
|
+
exports[`IPC message snapshots ClientMessage types image_gen_model_set serializes to expected JSON 1`] = `
|
|
91
|
+
{
|
|
92
|
+
"model": "gemini-2.5-flash-image",
|
|
93
|
+
"type": "image_gen_model_set",
|
|
94
|
+
}
|
|
95
|
+
`;
|
|
96
|
+
|
|
90
97
|
exports[`IPC message snapshots ClientMessage types history_request serializes to expected JSON 1`] = `
|
|
91
98
|
{
|
|
92
99
|
"sessionId": "sess-001",
|
|
@@ -530,6 +537,25 @@ exports[`IPC message snapshots ClientMessage types vercel_api_config serializes
|
|
|
530
537
|
}
|
|
531
538
|
`;
|
|
532
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
|
+
|
|
533
559
|
exports[`IPC message snapshots ClientMessage types link_open_request serializes to expected JSON 1`] = `
|
|
534
560
|
{
|
|
535
561
|
"type": "link_open_request",
|
|
@@ -661,17 +687,6 @@ exports[`IPC message snapshots ClientMessage types work_item_get serializes to e
|
|
|
661
687
|
}
|
|
662
688
|
`;
|
|
663
689
|
|
|
664
|
-
exports[`IPC message snapshots ClientMessage types work_item_create serializes to expected JSON 1`] = `
|
|
665
|
-
{
|
|
666
|
-
"notes": "High priority",
|
|
667
|
-
"priorityTier": 1,
|
|
668
|
-
"sortIndex": 0,
|
|
669
|
-
"taskId": "task-001",
|
|
670
|
-
"title": "Process report",
|
|
671
|
-
"type": "work_item_create",
|
|
672
|
-
}
|
|
673
|
-
`;
|
|
674
|
-
|
|
675
690
|
exports[`IPC message snapshots ClientMessage types work_item_update serializes to expected JSON 1`] = `
|
|
676
691
|
{
|
|
677
692
|
"id": "wi-001",
|
|
@@ -688,6 +703,13 @@ exports[`IPC message snapshots ClientMessage types work_item_complete serializes
|
|
|
688
703
|
}
|
|
689
704
|
`;
|
|
690
705
|
|
|
706
|
+
exports[`IPC message snapshots ClientMessage types work_item_delete serializes to expected JSON 1`] = `
|
|
707
|
+
{
|
|
708
|
+
"id": "wi-001",
|
|
709
|
+
"type": "work_item_delete",
|
|
710
|
+
}
|
|
711
|
+
`;
|
|
712
|
+
|
|
691
713
|
exports[`IPC message snapshots ClientMessage types work_item_run_task serializes to expected JSON 1`] = `
|
|
692
714
|
{
|
|
693
715
|
"id": "wi-001",
|
|
@@ -695,6 +717,38 @@ exports[`IPC message snapshots ClientMessage types work_item_run_task serializes
|
|
|
695
717
|
}
|
|
696
718
|
`;
|
|
697
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
|
+
|
|
698
752
|
exports[`IPC message snapshots ClientMessage types document_save serializes to expected JSON 1`] = `
|
|
699
753
|
{
|
|
700
754
|
"content": "# Hello",
|
|
@@ -729,7 +783,6 @@ exports[`IPC message snapshots ClientMessage types subagent_abort serializes to
|
|
|
729
783
|
|
|
730
784
|
exports[`IPC message snapshots ClientMessage types subagent_status serializes to expected JSON 1`] = `
|
|
731
785
|
{
|
|
732
|
-
"sessionId": "sess-001",
|
|
733
786
|
"subagentId": "sub-001",
|
|
734
787
|
"type": "subagent_status",
|
|
735
788
|
}
|
|
@@ -1437,12 +1490,14 @@ exports[`IPC message snapshots ServerMessage types schedules_list_response seria
|
|
|
1437
1490
|
"cronExpression": "0 9 * * 1-5",
|
|
1438
1491
|
"description": "Every weekday at 9:00 AM",
|
|
1439
1492
|
"enabled": true,
|
|
1493
|
+
"expression": "0 9 * * 1-5",
|
|
1440
1494
|
"id": "sched-001",
|
|
1441
1495
|
"lastRunAt": 1700000000000,
|
|
1442
1496
|
"lastStatus": "ok",
|
|
1443
1497
|
"message": "Remind me about the standup",
|
|
1444
1498
|
"name": "Daily standup reminder",
|
|
1445
1499
|
"nextRunAt": 1700100000000,
|
|
1500
|
+
"syntax": "cron",
|
|
1446
1501
|
"timezone": "America/Los_Angeles",
|
|
1447
1502
|
},
|
|
1448
1503
|
],
|
|
@@ -1723,6 +1778,34 @@ exports[`IPC message snapshots ServerMessage types vercel_api_config_response se
|
|
|
1723
1778
|
}
|
|
1724
1779
|
`;
|
|
1725
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
|
+
|
|
1726
1809
|
exports[`IPC message snapshots ServerMessage types open_url serializes to expected JSON 1`] = `
|
|
1727
1810
|
{
|
|
1728
1811
|
"title": "Example",
|
|
@@ -1973,28 +2056,6 @@ exports[`IPC message snapshots ServerMessage types work_item_get_response serial
|
|
|
1973
2056
|
}
|
|
1974
2057
|
`;
|
|
1975
2058
|
|
|
1976
|
-
exports[`IPC message snapshots ServerMessage types work_item_create_response serializes to expected JSON 1`] = `
|
|
1977
|
-
{
|
|
1978
|
-
"item": {
|
|
1979
|
-
"createdAt": 1700000000,
|
|
1980
|
-
"id": "wi-001",
|
|
1981
|
-
"lastRunConversationId": null,
|
|
1982
|
-
"lastRunId": null,
|
|
1983
|
-
"lastRunStatus": null,
|
|
1984
|
-
"notes": null,
|
|
1985
|
-
"priorityTier": 1,
|
|
1986
|
-
"sortIndex": null,
|
|
1987
|
-
"sourceId": null,
|
|
1988
|
-
"sourceType": null,
|
|
1989
|
-
"status": "queued",
|
|
1990
|
-
"taskId": "task-001",
|
|
1991
|
-
"title": "Process report",
|
|
1992
|
-
"updatedAt": 1700000000,
|
|
1993
|
-
},
|
|
1994
|
-
"type": "work_item_create_response",
|
|
1995
|
-
}
|
|
1996
|
-
`;
|
|
1997
|
-
|
|
1998
2059
|
exports[`IPC message snapshots ServerMessage types work_item_update_response serializes to expected JSON 1`] = `
|
|
1999
2060
|
{
|
|
2000
2061
|
"item": {
|
|
@@ -2017,6 +2078,14 @@ exports[`IPC message snapshots ServerMessage types work_item_update_response ser
|
|
|
2017
2078
|
}
|
|
2018
2079
|
`;
|
|
2019
2080
|
|
|
2081
|
+
exports[`IPC message snapshots ServerMessage types work_item_delete_response serializes to expected JSON 1`] = `
|
|
2082
|
+
{
|
|
2083
|
+
"id": "wi-001",
|
|
2084
|
+
"success": true,
|
|
2085
|
+
"type": "work_item_delete_response",
|
|
2086
|
+
}
|
|
2087
|
+
`;
|
|
2088
|
+
|
|
2020
2089
|
exports[`IPC message snapshots ServerMessage types work_item_run_task_response serializes to expected JSON 1`] = `
|
|
2021
2090
|
{
|
|
2022
2091
|
"id": "wi-001",
|
|
@@ -2026,6 +2095,58 @@ exports[`IPC message snapshots ServerMessage types work_item_run_task_response s
|
|
|
2026
2095
|
}
|
|
2027
2096
|
`;
|
|
2028
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
|
+
|
|
2029
2150
|
exports[`IPC message snapshots ServerMessage types work_item_status_changed serializes to expected JSON 1`] = `
|
|
2030
2151
|
{
|
|
2031
2152
|
"item": {
|
|
@@ -2042,6 +2163,12 @@ exports[`IPC message snapshots ServerMessage types work_item_status_changed seri
|
|
|
2042
2163
|
}
|
|
2043
2164
|
`;
|
|
2044
2165
|
|
|
2166
|
+
exports[`IPC message snapshots ServerMessage types tasks_changed serializes to expected JSON 1`] = `
|
|
2167
|
+
{
|
|
2168
|
+
"type": "tasks_changed",
|
|
2169
|
+
}
|
|
2170
|
+
`;
|
|
2171
|
+
|
|
2045
2172
|
exports[`IPC message snapshots ServerMessage types open_tasks_window serializes to expected JSON 1`] = `
|
|
2046
2173
|
{
|
|
2047
2174
|
"type": "open_tasks_window",
|
|
@@ -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
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
2
|
|
|
4
3
|
// Mock the logger before importing the module under test
|
|
5
4
|
mock.module('../util/logger.js', () => ({
|
|
@@ -11,27 +10,6 @@ mock.module('../util/logger.js', () => ({
|
|
|
11
10
|
|
|
12
11
|
import { extractRemoteUrls, materializeAssets } from '../bundler/app-bundler.js';
|
|
13
12
|
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Helpers
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
/** Compute expected asset filename for a URL (mirrors the production logic). */
|
|
19
|
-
function expectedFilename(url: string): string {
|
|
20
|
-
const hash = createHash('sha256').update(url).digest('hex').slice(0, 12);
|
|
21
|
-
let ext = '';
|
|
22
|
-
try {
|
|
23
|
-
const parsed = new URL(url);
|
|
24
|
-
const match = parsed.pathname.match(/\.\w+$/);
|
|
25
|
-
ext = match ? match[0] : '';
|
|
26
|
-
} catch {
|
|
27
|
-
// no extension
|
|
28
|
-
}
|
|
29
|
-
if (!ext || ext.length > 10 || !/^\.\w+$/.test(ext)) {
|
|
30
|
-
ext = '';
|
|
31
|
-
}
|
|
32
|
-
return `${hash}${ext}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
13
|
// ---------------------------------------------------------------------------
|
|
36
14
|
// extractRemoteUrls
|
|
37
15
|
// ---------------------------------------------------------------------------
|
|
@@ -167,10 +145,9 @@ describe('materializeAssets', () => {
|
|
|
167
145
|
const html = `<img src="${imageUrl}">`;
|
|
168
146
|
const result = await materializeAssets(html);
|
|
169
147
|
|
|
170
|
-
|
|
171
|
-
expect(result.rewrittenHtml).toBe(`<img src="assets/${filename}">`);
|
|
148
|
+
expect(result.rewrittenHtml).toBe('<img src="assets/e724846245db.png">');
|
|
172
149
|
expect(result.assets).toHaveLength(1);
|
|
173
|
-
expect(result.assets[0].archivePath).toBe(
|
|
150
|
+
expect(result.assets[0].archivePath).toBe('assets/e724846245db.png');
|
|
174
151
|
expect(result.assets[0].data).toEqual(imageData);
|
|
175
152
|
});
|
|
176
153
|
|
|
@@ -193,9 +170,14 @@ describe('materializeAssets', () => {
|
|
|
193
170
|
const result = await materializeAssets(html);
|
|
194
171
|
|
|
195
172
|
expect(result.assets).toHaveLength(3);
|
|
173
|
+
|
|
174
|
+
const expectedFilenames: Record<string, string> = {
|
|
175
|
+
'https://cdn.example.com/a.png': '6155f67efa62.png',
|
|
176
|
+
'https://cdn.example.com/b.css': '5e6d8d571910.css',
|
|
177
|
+
'https://cdn.example.com/c.js': '20fb1ea9b4c9.js',
|
|
178
|
+
};
|
|
196
179
|
for (const url of urls) {
|
|
197
|
-
|
|
198
|
-
expect(result.rewrittenHtml).toContain(`assets/${filename}`);
|
|
180
|
+
expect(result.rewrittenHtml).toContain(`assets/${expectedFilenames[url]}`);
|
|
199
181
|
expect(result.rewrittenHtml).not.toContain(url);
|
|
200
182
|
}
|
|
201
183
|
});
|
|
@@ -242,8 +224,7 @@ describe('materializeAssets', () => {
|
|
|
242
224
|
const html = `<img src="${goodUrl}"><img src="${badUrl}">`;
|
|
243
225
|
const result = await materializeAssets(html);
|
|
244
226
|
|
|
245
|
-
|
|
246
|
-
expect(result.rewrittenHtml).toContain(`assets/${goodFilename}`);
|
|
227
|
+
expect(result.rewrittenHtml).toContain('assets/691e2a787421.png');
|
|
247
228
|
expect(result.rewrittenHtml).toContain(badUrl);
|
|
248
229
|
expect(result.assets).toHaveLength(1);
|
|
249
230
|
});
|
|
@@ -265,9 +246,8 @@ describe('materializeAssets', () => {
|
|
|
265
246
|
expect(result.assets).toHaveLength(1);
|
|
266
247
|
|
|
267
248
|
// Both occurrences should be rewritten
|
|
268
|
-
const filename = expectedFilename(imageUrl);
|
|
269
249
|
expect(result.rewrittenHtml).not.toContain(imageUrl);
|
|
270
|
-
const matches = result.rewrittenHtml.match(
|
|
250
|
+
const matches = result.rewrittenHtml.match(/assets\/2f7fc0f99275\.png/g);
|
|
271
251
|
expect(matches).toHaveLength(2);
|
|
272
252
|
});
|
|
273
253
|
|
|
@@ -306,8 +286,7 @@ describe('materializeAssets', () => {
|
|
|
306
286
|
const html = '<style>body { background: url("https://cdn.example.com/bg.jpg"); }</style>';
|
|
307
287
|
const result = await materializeAssets(html);
|
|
308
288
|
|
|
309
|
-
|
|
310
|
-
expect(result.rewrittenHtml).toContain(`assets/${filename}`);
|
|
289
|
+
expect(result.rewrittenHtml).toContain('assets/8550eecd4975.jpg');
|
|
311
290
|
expect(result.rewrittenHtml).not.toContain(cssUrl);
|
|
312
291
|
});
|
|
313
292
|
});
|