vellum 0.2.1 → 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 +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 +299 -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-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 +62 -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 +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-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/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-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- 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 +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 +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 +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -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/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 +163 -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 +28 -4
- package/src/daemon/ipc-contract.ts +133 -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 +74 -10
- package/src/daemon/server.ts +143 -26
- 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 +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/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/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/http-server.ts +108 -20
- 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 +5 -10
- 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 +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 +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 +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 +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 +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { buildTemporalContext } from '../daemon/date-context.js';
|
|
3
|
+
|
|
4
|
+
// Fixed timestamps for deterministic assertions (all UTC midday to avoid DST edge cases).
|
|
5
|
+
|
|
6
|
+
/** Wednesday 2026-02-18 12:00 UTC */
|
|
7
|
+
const WED_FEB_18 = Date.UTC(2026, 1, 18, 12, 0, 0);
|
|
8
|
+
|
|
9
|
+
/** Saturday 2026-02-21 12:00 UTC */
|
|
10
|
+
const SAT_FEB_21 = Date.UTC(2026, 1, 21, 12, 0, 0);
|
|
11
|
+
|
|
12
|
+
/** Sunday 2026-02-22 12:00 UTC */
|
|
13
|
+
const SUN_FEB_22 = Date.UTC(2026, 1, 22, 12, 0, 0);
|
|
14
|
+
|
|
15
|
+
/** Tuesday 2026-12-29 12:00 UTC — year boundary */
|
|
16
|
+
const TUE_DEC_29 = Date.UTC(2026, 11, 29, 12, 0, 0);
|
|
17
|
+
|
|
18
|
+
/** Friday 2026-02-27 12:00 UTC */
|
|
19
|
+
const FRI_FEB_27 = Date.UTC(2026, 1, 27, 12, 0, 0);
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Basic structure
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
describe('buildTemporalContext', () => {
|
|
26
|
+
test('returns output wrapped in <temporal_context> tags', () => {
|
|
27
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
28
|
+
expect(result).toStartWith('<temporal_context>');
|
|
29
|
+
expect(result).toEndWith('</temporal_context>');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('includes today date and weekday', () => {
|
|
33
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
34
|
+
expect(result).toContain('Today: 2026-02-18 (Wednesday)');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('includes timezone', () => {
|
|
38
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'America/New_York' });
|
|
39
|
+
expect(result).toContain('Timezone: America/New_York');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('includes week definitions', () => {
|
|
43
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
44
|
+
expect(result).toContain('work week = Monday–Friday');
|
|
45
|
+
expect(result).toContain('weekend = Saturday–Sunday');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Weekday baseline — today is Wednesday
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
describe('weekday baseline (Wednesday)', () => {
|
|
54
|
+
test('next weekend is the upcoming Saturday-Sunday', () => {
|
|
55
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
56
|
+
// Wednesday Feb 18 → next Saturday is Feb 21, Sunday is Feb 22
|
|
57
|
+
expect(result).toContain('Next weekend: 2026-02-21 – 2026-02-22');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('next work week is the following Monday-Friday', () => {
|
|
61
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
62
|
+
// Wednesday Feb 18 → next Monday is Feb 23, Friday is Feb 27
|
|
63
|
+
expect(result).toContain('Next work week: 2026-02-23 – 2026-02-27');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Weekend baseline — today is Saturday
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe('weekend baseline (Saturday)', () => {
|
|
72
|
+
test('next weekend is the *following* Saturday-Sunday, not today', () => {
|
|
73
|
+
const result = buildTemporalContext({ nowMs: SAT_FEB_21, timeZone: 'UTC' });
|
|
74
|
+
// Saturday Feb 21 → next Saturday is Feb 28, Sunday is Mar 1
|
|
75
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('next work week is the upcoming Monday-Friday', () => {
|
|
79
|
+
const result = buildTemporalContext({ nowMs: SAT_FEB_21, timeZone: 'UTC' });
|
|
80
|
+
// Saturday Feb 21 → next Monday is Feb 23, Friday is Feb 27
|
|
81
|
+
expect(result).toContain('Next work week: 2026-02-23 – 2026-02-27');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Weekend baseline — today is Sunday
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
describe('weekend baseline (Sunday)', () => {
|
|
90
|
+
test('next weekend is the following Saturday-Sunday', () => {
|
|
91
|
+
const result = buildTemporalContext({ nowMs: SUN_FEB_22, timeZone: 'UTC' });
|
|
92
|
+
// Sunday Feb 22 → next Saturday is Feb 28, Sunday is Mar 1
|
|
93
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('next work week is the upcoming Monday-Friday', () => {
|
|
97
|
+
const result = buildTemporalContext({ nowMs: SUN_FEB_22, timeZone: 'UTC' });
|
|
98
|
+
// Sunday Feb 22 → next Monday is Feb 23, Friday is Feb 27
|
|
99
|
+
expect(result).toContain('Next work week: 2026-02-23 – 2026-02-27');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Friday baseline
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
describe('Friday baseline', () => {
|
|
108
|
+
test('next weekend is tomorrow (Saturday) and Sunday', () => {
|
|
109
|
+
const result = buildTemporalContext({ nowMs: FRI_FEB_27, timeZone: 'UTC' });
|
|
110
|
+
// Friday Feb 27 → next Saturday is Feb 28, Sunday is Mar 1
|
|
111
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('next work week is the following Monday-Friday', () => {
|
|
115
|
+
const result = buildTemporalContext({ nowMs: FRI_FEB_27, timeZone: 'UTC' });
|
|
116
|
+
// Friday Feb 27 → next Monday is Mar 2, Friday is Mar 6
|
|
117
|
+
expect(result).toContain('Next work week: 2026-03-02 – 2026-03-06');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Month / year boundary
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
describe('month/year boundary', () => {
|
|
126
|
+
test('handles year boundary correctly', () => {
|
|
127
|
+
const result = buildTemporalContext({ nowMs: TUE_DEC_29, timeZone: 'UTC' });
|
|
128
|
+
expect(result).toContain('Today: 2026-12-29 (Tuesday)');
|
|
129
|
+
// Tuesday Dec 29 → next Saturday is Jan 2 2027
|
|
130
|
+
expect(result).toContain('Next weekend: 2027-01-02 – 2027-01-03');
|
|
131
|
+
// Next Monday is Jan 4 2027 (skips current work week)
|
|
132
|
+
// Wait — Dec 29 is Tuesday, so next Monday = Jan 4? Let me think:
|
|
133
|
+
// Dec 29 Tue → Mon is (1-2+7)%7 = 6 days → Jan 4 Mon
|
|
134
|
+
expect(result).toContain('Next work week: 2027-01-04 – 2027-01-08');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('horizon entries cross year boundary', () => {
|
|
138
|
+
const result = buildTemporalContext({ nowMs: TUE_DEC_29, timeZone: 'UTC', horizonDays: 5 });
|
|
139
|
+
expect(result).toContain('2026-12-30 Wednesday');
|
|
140
|
+
expect(result).toContain('2026-12-31 Thursday');
|
|
141
|
+
expect(result).toContain('2027-01-01 Friday');
|
|
142
|
+
expect(result).toContain('2027-01-02 Saturday');
|
|
143
|
+
expect(result).toContain('2027-01-03 Sunday');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Output size caps
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
describe('output size caps', () => {
|
|
152
|
+
test('output is at most 1500 characters', () => {
|
|
153
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC', horizonDays: 14 });
|
|
154
|
+
expect(result.length).toBeLessThanOrEqual(1500);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('horizon entries are capped at 14 even if more requested', () => {
|
|
158
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC', horizonDays: 30 });
|
|
159
|
+
const horizonMatches = result.match(/^\s+\d{4}-\d{2}-\d{2} \w+$/gm);
|
|
160
|
+
expect(horizonMatches).not.toBeNull();
|
|
161
|
+
expect(horizonMatches!.length).toBeLessThanOrEqual(14);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('default horizon is 14 days', () => {
|
|
165
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
166
|
+
const horizonMatches = result.match(/^\s+\d{4}-\d{2}-\d{2} \w+$/gm);
|
|
167
|
+
expect(horizonMatches).not.toBeNull();
|
|
168
|
+
expect(horizonMatches!.length).toBe(14);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('respects smaller horizonDays', () => {
|
|
172
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC', horizonDays: 3 });
|
|
173
|
+
const horizonMatches = result.match(/^\s+\d{4}-\d{2}-\d{2} \w+$/gm);
|
|
174
|
+
expect(horizonMatches).not.toBeNull();
|
|
175
|
+
expect(horizonMatches!.length).toBe(3);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// DST-safe timezone behavior
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
describe('DST-safe timezone behavior', () => {
|
|
184
|
+
test('date labels are correct in US Eastern timezone', () => {
|
|
185
|
+
// Feb 18 12:00 UTC = Feb 18 07:00 EST (same calendar date)
|
|
186
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'America/New_York' });
|
|
187
|
+
expect(result).toContain('Today: 2026-02-18 (Wednesday)');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('date labels are correct in timezone ahead of UTC', () => {
|
|
191
|
+
// Use a timestamp near midnight UTC so the local date differs
|
|
192
|
+
// Feb 18 23:00 UTC = Feb 19 08:00 JST
|
|
193
|
+
const nearMidnight = Date.UTC(2026, 1, 18, 23, 0, 0);
|
|
194
|
+
const result = buildTemporalContext({ nowMs: nearMidnight, timeZone: 'Asia/Tokyo' });
|
|
195
|
+
expect(result).toContain('Today: 2026-02-19 (Thursday)');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('addDays is correct across DST spring-forward boundary', () => {
|
|
199
|
+
// 2026-03-08 is spring-forward day in America/New_York (clocks jump 2:00→3:00 AM).
|
|
200
|
+
// Use a timestamp at local 23:30 on Friday March 6 (04:30 UTC March 7).
|
|
201
|
+
const preDST = Date.UTC(2026, 2, 7, 4, 30, 0); // local: Fri Mar 6 23:30 EST
|
|
202
|
+
const result = buildTemporalContext({ nowMs: preDST, timeZone: 'America/New_York', horizonDays: 5 });
|
|
203
|
+
// Today should be Friday March 6
|
|
204
|
+
expect(result).toContain('Today: 2026-03-06 (Friday)');
|
|
205
|
+
// Horizon should have 5 consecutive days with no duplicates/skips
|
|
206
|
+
expect(result).toContain('2026-03-07 Saturday');
|
|
207
|
+
expect(result).toContain('2026-03-08 Sunday');
|
|
208
|
+
expect(result).toContain('2026-03-09 Monday');
|
|
209
|
+
expect(result).toContain('2026-03-10 Tuesday');
|
|
210
|
+
expect(result).toContain('2026-03-11 Wednesday');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test('addDays is correct across DST fall-back boundary', () => {
|
|
214
|
+
// 2026-11-01 is fall-back day in America/New_York (clocks jump 2:00→1:00 AM).
|
|
215
|
+
// Use a timestamp at local 00:30 on Sunday Nov 1 (04:30 UTC Nov 1).
|
|
216
|
+
const preFallback = Date.UTC(2026, 10, 1, 4, 30, 0); // local: Sun Nov 1 00:30 EDT
|
|
217
|
+
const result = buildTemporalContext({ nowMs: preFallback, timeZone: 'America/New_York', horizonDays: 3 });
|
|
218
|
+
// Today should be Sunday Nov 1
|
|
219
|
+
expect(result).toContain('Today: 2026-11-01 (Sunday)');
|
|
220
|
+
// Horizon should have 3 consecutive days
|
|
221
|
+
expect(result).toContain('2026-11-02 Monday');
|
|
222
|
+
expect(result).toContain('2026-11-03 Tuesday');
|
|
223
|
+
expect(result).toContain('2026-11-04 Wednesday');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('dates are correct in far-east UTC+13 timezone (Pacific/Auckland NZDT)', () => {
|
|
227
|
+
// Feb 18 12:00 UTC = Feb 19 01:00 NZDT (UTC+13 during daylight saving)
|
|
228
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'Pacific/Auckland', horizonDays: 3 });
|
|
229
|
+
// In Auckland, Feb 18 12:00 UTC is already Feb 19 (Thursday)
|
|
230
|
+
expect(result).toContain('Today: 2026-02-19 (Thursday)');
|
|
231
|
+
// Horizon should show consecutive days without +1 shift
|
|
232
|
+
expect(result).toContain('2026-02-20 Friday');
|
|
233
|
+
expect(result).toContain('2026-02-21 Saturday');
|
|
234
|
+
expect(result).toContain('2026-02-22 Sunday');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Trip-planning regression: "next weekend" resolution
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
describe('trip-planning: next weekend resolution', () => {
|
|
243
|
+
test('Wednesday → "next weekend" anchors resolve to upcoming Sat-Sun', () => {
|
|
244
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
245
|
+
// A user asking "plan a trip for next weekend" on Wednesday Feb 18
|
|
246
|
+
// expects Sat Feb 21 – Sun Feb 22.
|
|
247
|
+
expect(result).toContain('Next weekend: 2026-02-21 – 2026-02-22');
|
|
248
|
+
// Both dates must appear in the horizon so the model can reference them.
|
|
249
|
+
expect(result).toContain('2026-02-21 Saturday');
|
|
250
|
+
expect(result).toContain('2026-02-22 Sunday');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('Saturday → "next weekend" skips current weekend', () => {
|
|
254
|
+
const result = buildTemporalContext({ nowMs: SAT_FEB_21, timeZone: 'UTC' });
|
|
255
|
+
// Already on Saturday → "next weekend" means the *following* weekend.
|
|
256
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test('Sunday → "next weekend" skips current weekend', () => {
|
|
260
|
+
const result = buildTemporalContext({ nowMs: SUN_FEB_22, timeZone: 'UTC' });
|
|
261
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('Friday → "next weekend" is tomorrow', () => {
|
|
265
|
+
const result = buildTemporalContext({ nowMs: FRI_FEB_27, timeZone: 'UTC' });
|
|
266
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
// Trip-planning regression: "next work week" resolution
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
describe('trip-planning: next work week resolution', () => {
|
|
275
|
+
test('Wednesday → "next work week" skips remainder of current week', () => {
|
|
276
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
277
|
+
expect(result).toContain('Next work week: 2026-02-23 – 2026-02-27');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('Monday → "next work week" is the following Monday-Friday', () => {
|
|
281
|
+
/** Monday 2026-02-23 12:00 UTC */
|
|
282
|
+
const MON_FEB_23 = Date.UTC(2026, 1, 23, 12, 0, 0);
|
|
283
|
+
const result = buildTemporalContext({ nowMs: MON_FEB_23, timeZone: 'UTC' });
|
|
284
|
+
expect(result).toContain('Next work week: 2026-03-02 – 2026-03-06');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('Saturday → "next work week" is the upcoming Monday-Friday', () => {
|
|
288
|
+
const result = buildTemporalContext({ nowMs: SAT_FEB_21, timeZone: 'UTC' });
|
|
289
|
+
expect(result).toContain('Next work week: 2026-02-23 – 2026-02-27');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// ---------------------------------------------------------------------------
|
|
294
|
+
// Trip-planning regression: month-without-year disambiguation
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
describe('trip-planning: month-without-year disambiguation via temporal anchors', () => {
|
|
298
|
+
test('Today line includes full YYYY-MM-DD format with year for month disambiguation', () => {
|
|
299
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC' });
|
|
300
|
+
// The Today line must include the full year so the model can resolve bare
|
|
301
|
+
// month names (e.g. "May" → May 2026 because today is Feb 2026).
|
|
302
|
+
// Regex ensures YYYY-MM-DD format is present (regression if year is dropped).
|
|
303
|
+
expect(result).toMatch(/Today: \d{4}-\d{2}-\d{2} \(\w+\)/);
|
|
304
|
+
expect(result).toContain('2026-02-18');
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('future-month anchors: horizon dates are all in the future relative to today', () => {
|
|
308
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'UTC', horizonDays: 14 });
|
|
309
|
+
// Extract all horizon dates (indented YYYY-MM-DD lines)
|
|
310
|
+
const horizonDates = result.match(/^\s+(\d{4}-\d{2}-\d{2}) \w+$/gm);
|
|
311
|
+
expect(horizonDates).not.toBeNull();
|
|
312
|
+
// All horizon dates must be after today (2026-02-18)
|
|
313
|
+
for (const line of horizonDates!) {
|
|
314
|
+
const dateStr = line.trim().split(' ')[0];
|
|
315
|
+
expect(dateStr > '2026-02-18').toBe(true);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('year-end context: horizon spans into next year for Dec disambiguation', () => {
|
|
320
|
+
const result = buildTemporalContext({ nowMs: TUE_DEC_29, timeZone: 'UTC', horizonDays: 14 });
|
|
321
|
+
// Today is Dec 29 2026 — horizon must include 2027 dates so the model can
|
|
322
|
+
// distinguish "January" (Jan 2027) from past January (Jan 2026).
|
|
323
|
+
expect(result).toContain('Today: 2026-12-29');
|
|
324
|
+
expect(result).toMatch(/2027-01-\d{2} \w+/); // At least one January 2027 date
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('timezone is always present for correct local-month resolution', () => {
|
|
328
|
+
const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: 'America/New_York' });
|
|
329
|
+
// Timezone must be present so the model resolves months in the user's
|
|
330
|
+
// local calendar, not UTC.
|
|
331
|
+
expect(result).toMatch(/Timezone: .+/);
|
|
332
|
+
expect(result).toContain('America/New_York');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Trip-planning regression: cross-month weekend resolution
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
describe('trip-planning: cross-month weekend resolution', () => {
|
|
341
|
+
test('weekend that spans a month boundary (Feb → Mar)', () => {
|
|
342
|
+
const result = buildTemporalContext({ nowMs: FRI_FEB_27, timeZone: 'UTC' });
|
|
343
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('year-boundary weekend (Dec 2026 → Jan 2027)', () => {
|
|
347
|
+
const result = buildTemporalContext({ nowMs: TUE_DEC_29, timeZone: 'UTC' });
|
|
348
|
+
expect(result).toContain('Next weekend: 2027-01-02 – 2027-01-03');
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Trip-planning regression: timezone-shifted weekend anchors
|
|
354
|
+
// ---------------------------------------------------------------------------
|
|
355
|
+
|
|
356
|
+
describe('trip-planning: timezone-shifted weekend anchors', () => {
|
|
357
|
+
test('late Friday UTC is already Saturday in Auckland → skips to next weekend', () => {
|
|
358
|
+
// Friday Feb 27 23:00 UTC = Saturday Feb 28 12:00 NZDT
|
|
359
|
+
const lateFriUTC = Date.UTC(2026, 1, 27, 23, 0, 0);
|
|
360
|
+
const result = buildTemporalContext({ nowMs: lateFriUTC, timeZone: 'Pacific/Auckland' });
|
|
361
|
+
expect(result).toContain('Today: 2026-02-28 (Saturday)');
|
|
362
|
+
// "Next weekend" skips current weekend → Mar 7-8.
|
|
363
|
+
expect(result).toContain('Next weekend: 2026-03-07 – 2026-03-08');
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('early Saturday UTC is still Friday in US Pacific → next weekend is tomorrow', () => {
|
|
367
|
+
// Saturday Feb 28 02:00 UTC = Friday Feb 27 18:00 PST
|
|
368
|
+
const earlySatUTC = Date.UTC(2026, 1, 28, 2, 0, 0);
|
|
369
|
+
const result = buildTemporalContext({ nowMs: earlySatUTC, timeZone: 'America/Los_Angeles' });
|
|
370
|
+
expect(result).toContain('Today: 2026-02-27 (Friday)');
|
|
371
|
+
expect(result).toContain('Next weekend: 2026-02-28 – 2026-03-01');
|
|
372
|
+
});
|
|
373
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { Database } from 'bun:sqlite';
|
|
3
|
+
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
4
|
+
import * as schema from '../memory/schema.js';
|
|
5
|
+
import { scheduleJobs } from '../memory/schema.js';
|
|
6
|
+
import { eq } from 'drizzle-orm';
|
|
7
|
+
|
|
8
|
+
function createTestDb() {
|
|
9
|
+
const sqlite = new Database(':memory:');
|
|
10
|
+
sqlite.exec('PRAGMA journal_mode=WAL');
|
|
11
|
+
sqlite.exec('PRAGMA foreign_keys = ON');
|
|
12
|
+
return drizzle(sqlite, { schema });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getRawSqlite(db: ReturnType<typeof drizzle<typeof schema>>): Database {
|
|
16
|
+
return (db as unknown as { $client: Database }).$client;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe('schedule_syntax column migration', () => {
|
|
20
|
+
test('fresh DB includes schedule_syntax with default cron', () => {
|
|
21
|
+
const db = createTestDb();
|
|
22
|
+
const raw = getRawSqlite(db);
|
|
23
|
+
|
|
24
|
+
raw.exec(/*sql*/ `
|
|
25
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
26
|
+
id TEXT PRIMARY KEY,
|
|
27
|
+
name TEXT NOT NULL,
|
|
28
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
29
|
+
cron_expression TEXT NOT NULL,
|
|
30
|
+
schedule_syntax TEXT NOT NULL DEFAULT 'cron',
|
|
31
|
+
timezone TEXT,
|
|
32
|
+
message TEXT NOT NULL,
|
|
33
|
+
next_run_at INTEGER NOT NULL,
|
|
34
|
+
last_run_at INTEGER,
|
|
35
|
+
last_status TEXT,
|
|
36
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
created_by TEXT NOT NULL,
|
|
38
|
+
created_at INTEGER NOT NULL,
|
|
39
|
+
updated_at INTEGER NOT NULL
|
|
40
|
+
)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
db.insert(scheduleJobs).values({
|
|
45
|
+
id: 'test-1',
|
|
46
|
+
name: 'Test Job',
|
|
47
|
+
enabled: true,
|
|
48
|
+
cronExpression: '0 9 * * *',
|
|
49
|
+
timezone: null,
|
|
50
|
+
message: 'hello',
|
|
51
|
+
nextRunAt: now + 60000,
|
|
52
|
+
lastRunAt: null,
|
|
53
|
+
lastStatus: null,
|
|
54
|
+
retryCount: 0,
|
|
55
|
+
createdBy: 'agent',
|
|
56
|
+
createdAt: now,
|
|
57
|
+
updatedAt: now,
|
|
58
|
+
}).run();
|
|
59
|
+
|
|
60
|
+
const row = db.select().from(scheduleJobs).where(eq(scheduleJobs.id, 'test-1')).get();
|
|
61
|
+
expect(row).toBeTruthy();
|
|
62
|
+
expect(row!.scheduleSyntax).toBe('cron');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('upgraded DB gains schedule_syntax column via ALTER TABLE', () => {
|
|
66
|
+
const db = createTestDb();
|
|
67
|
+
const raw = getRawSqlite(db);
|
|
68
|
+
|
|
69
|
+
// Old schema without schedule_syntax
|
|
70
|
+
raw.exec(/*sql*/ `
|
|
71
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
72
|
+
id TEXT PRIMARY KEY,
|
|
73
|
+
name TEXT NOT NULL,
|
|
74
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
75
|
+
cron_expression TEXT NOT NULL,
|
|
76
|
+
timezone TEXT,
|
|
77
|
+
message TEXT NOT NULL,
|
|
78
|
+
next_run_at INTEGER NOT NULL,
|
|
79
|
+
last_run_at INTEGER,
|
|
80
|
+
last_status TEXT,
|
|
81
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
82
|
+
created_by TEXT NOT NULL,
|
|
83
|
+
created_at INTEGER NOT NULL,
|
|
84
|
+
updated_at INTEGER NOT NULL
|
|
85
|
+
)
|
|
86
|
+
`);
|
|
87
|
+
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
raw.exec(`INSERT INTO cron_jobs (id, name, enabled, cron_expression, timezone, message, next_run_at, last_run_at, last_status, retry_count, created_by, created_at, updated_at) VALUES ('old-1', 'Old Job', 1, '0 9 * * *', NULL, 'hello', ${now + 60000}, NULL, NULL, 0, 'agent', ${now}, ${now})`);
|
|
90
|
+
|
|
91
|
+
// Run the migration
|
|
92
|
+
try { raw.exec(`ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* already exists */ }
|
|
93
|
+
|
|
94
|
+
const row = db.select().from(scheduleJobs).where(eq(scheduleJobs.id, 'old-1')).get();
|
|
95
|
+
expect(row).toBeTruthy();
|
|
96
|
+
expect(row!.scheduleSyntax).toBe('cron');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('migration is idempotent', () => {
|
|
100
|
+
const db = createTestDb();
|
|
101
|
+
const raw = getRawSqlite(db);
|
|
102
|
+
|
|
103
|
+
raw.exec(/*sql*/ `
|
|
104
|
+
CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
105
|
+
id TEXT PRIMARY KEY,
|
|
106
|
+
name TEXT NOT NULL,
|
|
107
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
108
|
+
cron_expression TEXT NOT NULL,
|
|
109
|
+
timezone TEXT,
|
|
110
|
+
message TEXT NOT NULL,
|
|
111
|
+
next_run_at INTEGER NOT NULL,
|
|
112
|
+
last_run_at INTEGER,
|
|
113
|
+
last_status TEXT,
|
|
114
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
created_by TEXT NOT NULL,
|
|
116
|
+
created_at INTEGER NOT NULL,
|
|
117
|
+
updated_at INTEGER NOT NULL
|
|
118
|
+
)
|
|
119
|
+
`);
|
|
120
|
+
|
|
121
|
+
try { raw.exec(`ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* ok */ }
|
|
122
|
+
try { raw.exec(`ALTER TABLE cron_jobs ADD COLUMN schedule_syntax TEXT NOT NULL DEFAULT 'cron'`); } catch { /* ok */ }
|
|
123
|
+
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
raw.exec(`INSERT INTO cron_jobs (id, name, enabled, cron_expression, timezone, message, next_run_at, retry_count, created_by, created_at, updated_at) VALUES ('idem-1', 'Test', 1, '0 9 * * *', NULL, 'hi', ${now + 60000}, 0, 'agent', ${now}, ${now})`);
|
|
126
|
+
const row = db.select().from(scheduleJobs).where(eq(scheduleJobs.id, 'idem-1')).get();
|
|
127
|
+
expect(row!.scheduleSyntax).toBe('cron');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -31,33 +31,33 @@ const NOW = 1700000000000;
|
|
|
31
31
|
/** A fake selfie image attachment with deterministic IDs. */
|
|
32
32
|
export const FAKE_SELFIE_ATTACHMENT: StoredAttachment = {
|
|
33
33
|
id: 'att-selfie-001',
|
|
34
|
-
assistantId: 'asst-test-001',
|
|
35
34
|
originalFilename: 'selfie.png',
|
|
36
35
|
mimeType: 'image/png',
|
|
37
36
|
sizeBytes: Buffer.from(TINY_PNG_BASE64, 'base64').length,
|
|
38
37
|
kind: 'image',
|
|
38
|
+
thumbnailBase64: null,
|
|
39
39
|
createdAt: NOW,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/** A fake document attachment. */
|
|
43
43
|
export const FAKE_DOCUMENT_ATTACHMENT: StoredAttachment = {
|
|
44
44
|
id: 'att-doc-001',
|
|
45
|
-
assistantId: 'asst-test-001',
|
|
46
45
|
originalFilename: 'report.pdf',
|
|
47
46
|
mimeType: 'application/pdf',
|
|
48
47
|
sizeBytes: 4096,
|
|
49
48
|
kind: 'document',
|
|
49
|
+
thumbnailBase64: null,
|
|
50
50
|
createdAt: NOW,
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
/** A fake JPEG photo attachment. */
|
|
54
54
|
export const FAKE_PHOTO_ATTACHMENT: StoredAttachment = {
|
|
55
55
|
id: 'att-photo-001',
|
|
56
|
-
assistantId: 'asst-test-001',
|
|
57
56
|
originalFilename: 'photo.jpg',
|
|
58
57
|
mimeType: 'image/jpeg',
|
|
59
58
|
sizeBytes: Buffer.from(TINY_JPEG_BASE64, 'base64').length,
|
|
60
59
|
kind: 'image',
|
|
60
|
+
thumbnailBase64: null,
|
|
61
61
|
createdAt: NOW,
|
|
62
62
|
};
|
|
63
63
|
|