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,329 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const testDir = mkdtempSync(join(tmpdir(), 'scheduler-recurrence-test-'));
|
|
7
|
+
|
|
8
|
+
mock.module('../util/platform.js', () => ({
|
|
9
|
+
getDataDir: () => testDir,
|
|
10
|
+
isMacOS: () => process.platform === 'darwin',
|
|
11
|
+
isLinux: () => process.platform === 'linux',
|
|
12
|
+
isWindows: () => process.platform === 'win32',
|
|
13
|
+
getSocketPath: () => join(testDir, 'test.sock'),
|
|
14
|
+
getPidPath: () => join(testDir, 'test.pid'),
|
|
15
|
+
getDbPath: () => join(testDir, 'test.db'),
|
|
16
|
+
getLogPath: () => join(testDir, 'test.log'),
|
|
17
|
+
ensureDataDir: () => {},
|
|
18
|
+
migrateToDataLayout: () => {},
|
|
19
|
+
migrateToWorkspaceLayout: () => {},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
mock.module('../util/logger.js', () => ({
|
|
23
|
+
getLogger: () => new Proxy({} as Record<string, unknown>, {
|
|
24
|
+
get: () => () => {},
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
29
|
+
import { createTask } from '../tasks/task-store.js';
|
|
30
|
+
import {
|
|
31
|
+
createSchedule,
|
|
32
|
+
getSchedule,
|
|
33
|
+
getScheduleRuns,
|
|
34
|
+
} from '../schedule/schedule-store.js';
|
|
35
|
+
import { startScheduler } from '../schedule/scheduler.js';
|
|
36
|
+
|
|
37
|
+
initializeDb();
|
|
38
|
+
|
|
39
|
+
/** Access the underlying bun:sqlite Database for raw parameterized queries. */
|
|
40
|
+
function getRawDb(): import('bun:sqlite').Database {
|
|
41
|
+
return (getDb() as unknown as { $client: import('bun:sqlite').Database }).$client;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Force a schedule to be due by setting next_run_at in the past. */
|
|
45
|
+
function forceScheduleDue(scheduleId: string): void {
|
|
46
|
+
getRawDb().run('UPDATE cron_jobs SET next_run_at = ? WHERE id = ?', [Date.now() - 1000, scheduleId]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
afterAll(() => {
|
|
50
|
+
resetDb();
|
|
51
|
+
try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Build an RRULE expression anchored at the given start date, recurring every minute.
|
|
55
|
+
// This ensures the rule always has future occurrences relative to the test clock.
|
|
56
|
+
function buildEveryMinuteRrule(dtstart: Date = new Date()): string {
|
|
57
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
58
|
+
const ds = `${dtstart.getUTCFullYear()}${pad(dtstart.getUTCMonth() + 1)}${pad(dtstart.getUTCDate())}T${pad(dtstart.getUTCHours())}${pad(dtstart.getUTCMinutes())}${pad(dtstart.getUTCSeconds())}Z`;
|
|
59
|
+
return `DTSTART:${ds}\nRRULE:FREQ=MINUTELY;INTERVAL=1`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Build an RRULE expression that ended in the past (UNTIL already passed).
|
|
63
|
+
function buildEndedRrule(): string {
|
|
64
|
+
const past = new Date(Date.now() - 86_400_000 * 30); // 30 days ago
|
|
65
|
+
const until = new Date(Date.now() - 86_400_000); // 1 day ago
|
|
66
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
67
|
+
const ds = `${past.getUTCFullYear()}${pad(past.getUTCMonth() + 1)}${pad(past.getUTCDate())}T000000Z`;
|
|
68
|
+
const us = `${until.getUTCFullYear()}${pad(until.getUTCMonth() + 1)}${pad(until.getUTCDate())}T235959Z`;
|
|
69
|
+
return `DTSTART:${ds}\nRRULE:FREQ=DAILY;INTERVAL=1;UNTIL=${us}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── RRULE schedule fires through the scheduler ──────────────────────
|
|
73
|
+
|
|
74
|
+
describe('scheduler RRULE execution', () => {
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
const db = getDb();
|
|
77
|
+
db.run('DELETE FROM cron_runs');
|
|
78
|
+
db.run('DELETE FROM cron_jobs');
|
|
79
|
+
db.run('DELETE FROM task_runs');
|
|
80
|
+
db.run('DELETE FROM tasks');
|
|
81
|
+
db.run('DELETE FROM messages');
|
|
82
|
+
db.run('DELETE FROM conversations');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('RRULE schedule fires and creates cron_runs entry', async () => {
|
|
86
|
+
const rruleExpr = buildEveryMinuteRrule();
|
|
87
|
+
const schedule = createSchedule({
|
|
88
|
+
name: 'RRULE Test',
|
|
89
|
+
cronExpression: rruleExpr,
|
|
90
|
+
message: 'Hello from RRULE',
|
|
91
|
+
syntax: 'rrule',
|
|
92
|
+
expression: rruleExpr,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Verify it was stored with rrule syntax
|
|
96
|
+
const stored = getSchedule(schedule.id);
|
|
97
|
+
expect(stored).not.toBeNull();
|
|
98
|
+
expect(stored!.syntax).toBe('rrule');
|
|
99
|
+
|
|
100
|
+
// Force it to be due
|
|
101
|
+
forceScheduleDue(schedule.id);
|
|
102
|
+
|
|
103
|
+
const processedMessages: { conversationId: string; message: string }[] = [];
|
|
104
|
+
const processMessage = async (conversationId: string, message: string) => {
|
|
105
|
+
processedMessages.push({ conversationId, message });
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
110
|
+
scheduler.stop();
|
|
111
|
+
|
|
112
|
+
// processMessage should have been called with the RRULE message
|
|
113
|
+
expect(processedMessages.some(m => m.message === 'Hello from RRULE')).toBe(true);
|
|
114
|
+
|
|
115
|
+
// A cron_runs entry should have been created
|
|
116
|
+
const runs = getScheduleRuns(schedule.id);
|
|
117
|
+
expect(runs.length).toBeGreaterThanOrEqual(1);
|
|
118
|
+
expect(runs[0].status).toBe('ok');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('RRULE run_task:<id> triggers task runner', async () => {
|
|
122
|
+
const task = createTask({
|
|
123
|
+
title: 'RRULE Task',
|
|
124
|
+
template: 'Execute RRULE task',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const rruleExpr = buildEveryMinuteRrule();
|
|
128
|
+
const schedule = createSchedule({
|
|
129
|
+
name: 'RRULE Task Schedule',
|
|
130
|
+
cronExpression: rruleExpr,
|
|
131
|
+
message: `run_task:${task.id}`,
|
|
132
|
+
syntax: 'rrule',
|
|
133
|
+
expression: rruleExpr,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
forceScheduleDue(schedule.id);
|
|
137
|
+
|
|
138
|
+
const directCalls: { conversationId: string; message: string }[] = [];
|
|
139
|
+
const processMessage = async (conversationId: string, message: string) => {
|
|
140
|
+
directCalls.push({ conversationId, message });
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
144
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
145
|
+
scheduler.stop();
|
|
146
|
+
|
|
147
|
+
// runTask renders the template, so processMessage gets the template text
|
|
148
|
+
const runTaskCalls = directCalls.filter(c => c.message === 'Execute RRULE task');
|
|
149
|
+
const rawCalls = directCalls.filter(c => c.message.startsWith('run_task:'));
|
|
150
|
+
|
|
151
|
+
expect(runTaskCalls.length).toBe(1);
|
|
152
|
+
expect(rawCalls.length).toBe(0);
|
|
153
|
+
|
|
154
|
+
// A cron_runs entry should exist
|
|
155
|
+
const runs = getScheduleRuns(schedule.id);
|
|
156
|
+
expect(runs.length).toBeGreaterThanOrEqual(1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('ended RRULE (UNTIL in past) is not repeatedly claimed', async () => {
|
|
160
|
+
const endedExpr = buildEndedRrule();
|
|
161
|
+
|
|
162
|
+
// Insert directly via raw SQL because createSchedule would throw when
|
|
163
|
+
// computing nextRunAt for an already-ended RRULE. This simulates a
|
|
164
|
+
// schedule that was valid when created but has since expired.
|
|
165
|
+
const id = crypto.randomUUID();
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
getRawDb().run(
|
|
168
|
+
`INSERT INTO cron_jobs (id, name, enabled, cron_expression, schedule_syntax, timezone, message, next_run_at, last_run_at, last_status, retry_count, created_by, created_at, updated_at)
|
|
169
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
170
|
+
[id, 'Ended RRULE', 1, endedExpr, 'rrule', null, 'Should not fire', now - 1000, null, null, 0, 'agent', now, now],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const processedMessages: string[] = [];
|
|
174
|
+
const processMessage = async (_conversationId: string, message: string) => {
|
|
175
|
+
processedMessages.push(message);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
179
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
180
|
+
scheduler.stop();
|
|
181
|
+
|
|
182
|
+
// The ended RRULE should NOT have fired
|
|
183
|
+
expect(processedMessages).not.toContain('Should not fire');
|
|
184
|
+
|
|
185
|
+
// No runs should have been created
|
|
186
|
+
const runs = getScheduleRuns(id);
|
|
187
|
+
expect(runs.length).toBe(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('existing cron schedule behavior is unchanged', async () => {
|
|
191
|
+
const schedule = createSchedule({
|
|
192
|
+
name: 'Cron Schedule',
|
|
193
|
+
cronExpression: '* * * * *',
|
|
194
|
+
message: 'Cron message',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Verify it defaults to cron syntax
|
|
198
|
+
const stored = getSchedule(schedule.id);
|
|
199
|
+
expect(stored).not.toBeNull();
|
|
200
|
+
expect(stored!.syntax).toBe('cron');
|
|
201
|
+
|
|
202
|
+
forceScheduleDue(schedule.id);
|
|
203
|
+
|
|
204
|
+
const processedMessages: { conversationId: string; message: string }[] = [];
|
|
205
|
+
const processMessage = async (conversationId: string, message: string) => {
|
|
206
|
+
processedMessages.push({ conversationId, message });
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
210
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
211
|
+
scheduler.stop();
|
|
212
|
+
|
|
213
|
+
// processMessage should have been called with the cron message
|
|
214
|
+
expect(processedMessages.some(m => m.message === 'Cron message')).toBe(true);
|
|
215
|
+
|
|
216
|
+
// A cron_runs entry should have been created
|
|
217
|
+
const runs = getScheduleRuns(schedule.id);
|
|
218
|
+
expect(runs.length).toBeGreaterThanOrEqual(1);
|
|
219
|
+
expect(runs[0].status).toBe('ok');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('RRULE set with EXDATE skips excluded occurrence and advances to next valid date', async () => {
|
|
223
|
+
// Build an RRULE set that fires every minute but excludes the next immediate occurrence.
|
|
224
|
+
// The scheduler should skip the excluded date and advance to the one after.
|
|
225
|
+
const now = new Date();
|
|
226
|
+
const pad = (n: number) => String(n).padStart(2, '0');
|
|
227
|
+
|
|
228
|
+
// DTSTART one hour ago so there are plenty of past occurrences
|
|
229
|
+
const pastDate = new Date(now.getTime() - 3_600_000);
|
|
230
|
+
const ds = `${pastDate.getUTCFullYear()}${pad(pastDate.getUTCMonth() + 1)}${pad(pastDate.getUTCDate())}T${pad(pastDate.getUTCHours())}${pad(pastDate.getUTCMinutes())}${pad(pastDate.getUTCSeconds())}Z`;
|
|
231
|
+
|
|
232
|
+
// Exclude the current minute's occurrence
|
|
233
|
+
const currentMinuteDate = new Date(now);
|
|
234
|
+
currentMinuteDate.setUTCSeconds(0);
|
|
235
|
+
currentMinuteDate.setUTCMilliseconds(0);
|
|
236
|
+
// Round to the previous minute boundary relative to dtstart
|
|
237
|
+
const exDate = `${currentMinuteDate.getUTCFullYear()}${pad(currentMinuteDate.getUTCMonth() + 1)}${pad(currentMinuteDate.getUTCDate())}T${pad(currentMinuteDate.getUTCHours())}${pad(currentMinuteDate.getUTCMinutes())}00Z`;
|
|
238
|
+
|
|
239
|
+
const expression = `DTSTART:${ds}\nRRULE:FREQ=MINUTELY;INTERVAL=1\nEXDATE:${exDate}`;
|
|
240
|
+
|
|
241
|
+
const schedule = createSchedule({
|
|
242
|
+
name: 'RRULE set EXDATE test',
|
|
243
|
+
cronExpression: expression,
|
|
244
|
+
message: 'Set exclusion test',
|
|
245
|
+
syntax: 'rrule',
|
|
246
|
+
expression,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Force the schedule to be due
|
|
250
|
+
forceScheduleDue(schedule.id);
|
|
251
|
+
|
|
252
|
+
const processedMessages: string[] = [];
|
|
253
|
+
const processMessage = async (_conversationId: string, message: string) => {
|
|
254
|
+
processedMessages.push(message);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
258
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
259
|
+
scheduler.stop();
|
|
260
|
+
|
|
261
|
+
// The schedule should have been claimed and nextRunAt advanced
|
|
262
|
+
const after = getSchedule(schedule.id);
|
|
263
|
+
expect(after).not.toBeNull();
|
|
264
|
+
expect(after!.lastRunAt).not.toBeNull();
|
|
265
|
+
// nextRunAt should be in the future (not the excluded date)
|
|
266
|
+
expect(after!.nextRunAt).toBeGreaterThan(Date.now() - 5000);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('RRULE set schedule fires and creates cron_runs entry', async () => {
|
|
270
|
+
const expression = [
|
|
271
|
+
'DTSTART:20250101T000000Z',
|
|
272
|
+
'RRULE:FREQ=MINUTELY;INTERVAL=1',
|
|
273
|
+
'EXDATE:20250101T000100Z',
|
|
274
|
+
].join('\n');
|
|
275
|
+
|
|
276
|
+
const schedule = createSchedule({
|
|
277
|
+
name: 'Set schedule fire test',
|
|
278
|
+
cronExpression: expression,
|
|
279
|
+
message: 'Set fire test',
|
|
280
|
+
syntax: 'rrule',
|
|
281
|
+
expression,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
forceScheduleDue(schedule.id);
|
|
285
|
+
|
|
286
|
+
const processedMessages: string[] = [];
|
|
287
|
+
const processMessage = async (_conversationId: string, message: string) => {
|
|
288
|
+
processedMessages.push(message);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
292
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
293
|
+
scheduler.stop();
|
|
294
|
+
|
|
295
|
+
expect(processedMessages).toContain('Set fire test');
|
|
296
|
+
|
|
297
|
+
const runs = getScheduleRuns(schedule.id);
|
|
298
|
+
expect(runs.length).toBeGreaterThanOrEqual(1);
|
|
299
|
+
expect(runs[0].status).toBe('ok');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('RRULE schedule advances nextRunAt after firing', async () => {
|
|
303
|
+
const rruleExpr = buildEveryMinuteRrule();
|
|
304
|
+
const schedule = createSchedule({
|
|
305
|
+
name: 'Advancing RRULE',
|
|
306
|
+
cronExpression: rruleExpr,
|
|
307
|
+
message: 'Advance test',
|
|
308
|
+
syntax: 'rrule',
|
|
309
|
+
expression: rruleExpr,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
const originalNextRunAt = getSchedule(schedule.id)!.nextRunAt;
|
|
313
|
+
forceScheduleDue(schedule.id);
|
|
314
|
+
const forcedDueAt = getSchedule(schedule.id)!.nextRunAt;
|
|
315
|
+
|
|
316
|
+
const processMessage = async () => {};
|
|
317
|
+
const scheduler = startScheduler(processMessage, () => {}, () => {});
|
|
318
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
319
|
+
scheduler.stop();
|
|
320
|
+
|
|
321
|
+
const after = getSchedule(schedule.id);
|
|
322
|
+
expect(after).not.toBeNull();
|
|
323
|
+
// nextRunAt must have moved forward from the forced-due value
|
|
324
|
+
expect(after!.nextRunAt).toBeGreaterThan(forcedDueAt);
|
|
325
|
+
// It should be at or near the original computed value (within a few seconds tolerance)
|
|
326
|
+
expect(Math.abs(after!.nextRunAt - originalNextRunAt)).toBeLessThan(5000);
|
|
327
|
+
expect(after!.lastRunAt).not.toBeNull();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -24,12 +24,12 @@ mock.module('../util/logger.js', () => ({
|
|
|
24
24
|
}),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
-
import { initializeDb, getDb } from '../memory/db.js';
|
|
27
|
+
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
28
28
|
import { renderHistoryContent, mergeToolResults } from '../daemon/handlers.js';
|
|
29
29
|
import {
|
|
30
30
|
uploadAttachment,
|
|
31
31
|
linkAttachmentToMessage,
|
|
32
|
-
|
|
32
|
+
getAttachmentsForMessage,
|
|
33
33
|
} from '../memory/attachments-store.js';
|
|
34
34
|
import {
|
|
35
35
|
createConversation,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
initializeDb();
|
|
40
40
|
|
|
41
41
|
afterAll(() => {
|
|
42
|
+
resetDb();
|
|
42
43
|
try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
|
|
43
44
|
});
|
|
44
45
|
|
|
@@ -370,7 +371,7 @@ describe('mergeToolResults', () => {
|
|
|
370
371
|
});
|
|
371
372
|
});
|
|
372
373
|
|
|
373
|
-
describe('
|
|
374
|
+
describe('getAttachmentsForMessage', () => {
|
|
374
375
|
beforeEach(() => {
|
|
375
376
|
const db = getDb();
|
|
376
377
|
db.run('DELETE FROM message_attachments');
|
|
@@ -387,10 +388,10 @@ describe('getAttachmentsForMessageUnscoped', () => {
|
|
|
387
388
|
|
|
388
389
|
test('returns attachments linked to a message', () => {
|
|
389
390
|
const msgId = createMessage('assistant', 'Here is a chart');
|
|
390
|
-
const stored = uploadAttachment('
|
|
391
|
+
const stored = uploadAttachment('chart.png', 'image/png', 'iVBOR');
|
|
391
392
|
linkAttachmentToMessage(msgId, stored.id, 0);
|
|
392
393
|
|
|
393
|
-
const result =
|
|
394
|
+
const result = getAttachmentsForMessage(msgId);
|
|
394
395
|
expect(result).toHaveLength(1);
|
|
395
396
|
expect(result[0].id).toBe(stored.id);
|
|
396
397
|
expect(result[0].originalFilename).toBe('chart.png');
|
|
@@ -399,32 +400,32 @@ describe('getAttachmentsForMessageUnscoped', () => {
|
|
|
399
400
|
});
|
|
400
401
|
|
|
401
402
|
test('returns empty array when no attachments are linked', () => {
|
|
402
|
-
expect(
|
|
403
|
+
expect(getAttachmentsForMessage('msg-nonexistent')).toEqual([]);
|
|
403
404
|
});
|
|
404
405
|
|
|
405
406
|
test('returns multiple attachments in position order', () => {
|
|
406
407
|
const msgId = createMessage('assistant', 'Two files');
|
|
407
|
-
const a1 = uploadAttachment('
|
|
408
|
-
const a2 = uploadAttachment('
|
|
408
|
+
const a1 = uploadAttachment('first.txt', 'text/plain', 'AAAA');
|
|
409
|
+
const a2 = uploadAttachment('second.txt', 'text/plain', 'BBBB');
|
|
409
410
|
|
|
410
411
|
linkAttachmentToMessage(msgId, a2.id, 1);
|
|
411
412
|
linkAttachmentToMessage(msgId, a1.id, 0);
|
|
412
413
|
|
|
413
|
-
const result =
|
|
414
|
+
const result = getAttachmentsForMessage(msgId);
|
|
414
415
|
expect(result).toHaveLength(2);
|
|
415
416
|
expect(result[0].originalFilename).toBe('first.txt');
|
|
416
417
|
expect(result[1].originalFilename).toBe('second.txt');
|
|
417
418
|
});
|
|
418
419
|
|
|
419
|
-
test('
|
|
420
|
+
test('returns all attachments linked to a message', () => {
|
|
420
421
|
const msgId = createMessage('assistant', 'Mixed');
|
|
421
|
-
const a1 = uploadAttachment('
|
|
422
|
-
const a2 = uploadAttachment('
|
|
422
|
+
const a1 = uploadAttachment('a.png', 'image/png', 'AAAA');
|
|
423
|
+
const a2 = uploadAttachment('b.png', 'image/png', 'BBBB');
|
|
423
424
|
|
|
424
425
|
linkAttachmentToMessage(msgId, a1.id, 0);
|
|
425
426
|
linkAttachmentToMessage(msgId, a2.id, 1);
|
|
426
427
|
|
|
427
|
-
const result =
|
|
428
|
+
const result = getAttachmentsForMessage(msgId);
|
|
428
429
|
expect(result).toHaveLength(2);
|
|
429
430
|
});
|
|
430
431
|
});
|
|
@@ -117,6 +117,34 @@ describe('classifySessionError', () => {
|
|
|
117
117
|
}
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
describe('timeout errors (generic, not network/gateway)', () => {
|
|
121
|
+
const cases = [
|
|
122
|
+
'timeout',
|
|
123
|
+
'deadline exceeded',
|
|
124
|
+
'request timed out',
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
for (const msg of cases) {
|
|
128
|
+
it(`classifies "${msg}" as PROVIDER_API with timeout message`, () => {
|
|
129
|
+
const result = classifySessionError(new Error(msg), baseCtx);
|
|
130
|
+
expect(result.code).toBe('PROVIDER_API');
|
|
131
|
+
expect(result.userMessage).toContain('took too long');
|
|
132
|
+
expect(result.retryable).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it('does not steal "connection timeout" from PROVIDER_NETWORK', () => {
|
|
137
|
+
const result = classifySessionError(new Error('connection timeout'), baseCtx);
|
|
138
|
+
expect(result.code).toBe('PROVIDER_NETWORK');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('does not steal "Gateway timeout" from PROVIDER_API', () => {
|
|
142
|
+
const result = classifySessionError(new Error('Gateway timeout'), baseCtx);
|
|
143
|
+
expect(result.code).toBe('PROVIDER_API');
|
|
144
|
+
expect(result.userMessage).toContain('returned an error');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
120
148
|
describe('context-too-large errors', () => {
|
|
121
149
|
const cases = [
|
|
122
150
|
'context_length_exceeded',
|