@vellumai/assistant 0.8.2 → 0.8.3
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/ARCHITECTURE.md +11 -12
- package/docker-entrypoint.sh +13 -1
- package/docker-init-apt-root.sh +79 -6
- package/openapi.yaml +336 -21
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/context-token-estimator.test.ts +30 -65
- package/src/__tests__/conversation-agent-loop.test.ts +57 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-runtime-assembly.test.ts +26 -4
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +5 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +218 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +6 -73
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/loop.ts +167 -18
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/notifications.ts +65 -35
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +21 -29
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +1 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +72 -12
- package/src/context/token-estimator.ts +32 -34
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +29 -2
- package/src/daemon/conversation-runtime-assembly.ts +9 -0
- package/src/daemon/conversation.ts +0 -7
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/conversations.ts +1 -0
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +49 -61
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/process-message.ts +3 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -0
- package/src/heartbeat/heartbeat-service.ts +34 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +14 -2
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +190 -3
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +49 -20
- package/src/memory/v2/page-index.ts +38 -13
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/home-feed-side-effect.ts +85 -6
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/plugins/defaults/injectors.ts +38 -19
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +6 -51
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/system-prompt.ts +0 -8
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/system-sections.ts +0 -9
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +9 -20
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +2 -0
- package/src/providers/model-catalog.ts +199 -244
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +159 -6
- package/src/providers/openrouter/client.ts +42 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/runtime/agent-wake.ts +61 -1
- package/src/runtime/auth/route-policy.ts +13 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +0 -47
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +66 -4
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/conversation-query-routes.ts +70 -11
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +134 -1
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/subagent/manager.ts +2 -0
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/registry.ts +2 -2
- package/src/tools/types.ts +37 -2
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -99,6 +99,11 @@ describe("schedules command", () => {
|
|
|
99
99
|
expect(schedules!.commands.map((command) => command.name())).toEqual([
|
|
100
100
|
"list",
|
|
101
101
|
"runs",
|
|
102
|
+
"create",
|
|
103
|
+
"enable",
|
|
104
|
+
"disable",
|
|
105
|
+
"cancel",
|
|
106
|
+
"delete",
|
|
102
107
|
"execute",
|
|
103
108
|
]);
|
|
104
109
|
});
|
|
@@ -361,6 +366,470 @@ describe("schedules runs", () => {
|
|
|
361
366
|
});
|
|
362
367
|
});
|
|
363
368
|
|
|
369
|
+
describe("schedules create", () => {
|
|
370
|
+
test("calls createSchedule with the required fields", async () => {
|
|
371
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
372
|
+
|
|
373
|
+
const { exitCode } = await runCommand([
|
|
374
|
+
"schedules",
|
|
375
|
+
"create",
|
|
376
|
+
"Heartbeat",
|
|
377
|
+
"--expression",
|
|
378
|
+
"*/30 * * * *",
|
|
379
|
+
"--message",
|
|
380
|
+
"run heartbeat",
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
expect(exitCode).toBe(0);
|
|
384
|
+
expect(ipcCalls).toEqual([
|
|
385
|
+
{
|
|
386
|
+
method: "createSchedule",
|
|
387
|
+
params: {
|
|
388
|
+
body: {
|
|
389
|
+
name: "Heartbeat",
|
|
390
|
+
expression: "*/30 * * * *",
|
|
391
|
+
message: "run heartbeat",
|
|
392
|
+
enabled: true,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
]);
|
|
397
|
+
expect(logLines).toEqual(["Created schedule: Heartbeat"]);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("passes --timezone through to the request body", async () => {
|
|
401
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
402
|
+
|
|
403
|
+
const { exitCode } = await runCommand([
|
|
404
|
+
"schedules",
|
|
405
|
+
"create",
|
|
406
|
+
"Morning",
|
|
407
|
+
"--expression",
|
|
408
|
+
"0 9 * * MON-FRI",
|
|
409
|
+
"--message",
|
|
410
|
+
"morning summary",
|
|
411
|
+
"--timezone",
|
|
412
|
+
"America/New_York",
|
|
413
|
+
]);
|
|
414
|
+
|
|
415
|
+
expect(exitCode).toBe(0);
|
|
416
|
+
expect(ipcCalls).toEqual([
|
|
417
|
+
{
|
|
418
|
+
method: "createSchedule",
|
|
419
|
+
params: {
|
|
420
|
+
body: {
|
|
421
|
+
name: "Morning",
|
|
422
|
+
expression: "0 9 * * MON-FRI",
|
|
423
|
+
message: "morning summary",
|
|
424
|
+
enabled: true,
|
|
425
|
+
timezone: "America/New_York",
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
]);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test("sends enabled:false when --no-enabled is set", async () => {
|
|
433
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
434
|
+
|
|
435
|
+
const { exitCode } = await runCommand([
|
|
436
|
+
"schedules",
|
|
437
|
+
"create",
|
|
438
|
+
"Drafted",
|
|
439
|
+
"--expression",
|
|
440
|
+
"0 0 * * *",
|
|
441
|
+
"--message",
|
|
442
|
+
"placeholder",
|
|
443
|
+
"--no-enabled",
|
|
444
|
+
]);
|
|
445
|
+
|
|
446
|
+
expect(exitCode).toBe(0);
|
|
447
|
+
expect(ipcCalls).toEqual([
|
|
448
|
+
{
|
|
449
|
+
method: "createSchedule",
|
|
450
|
+
params: {
|
|
451
|
+
body: {
|
|
452
|
+
name: "Drafted",
|
|
453
|
+
expression: "0 0 * * *",
|
|
454
|
+
message: "placeholder",
|
|
455
|
+
enabled: false,
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
]);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("emits JSON when --json is set", async () => {
|
|
463
|
+
mockIpcResult = {
|
|
464
|
+
ok: true,
|
|
465
|
+
result: {
|
|
466
|
+
schedules: [
|
|
467
|
+
{
|
|
468
|
+
id: "new-schedule-id",
|
|
469
|
+
name: "Heartbeat",
|
|
470
|
+
enabled: true,
|
|
471
|
+
syntax: "cron",
|
|
472
|
+
expression: "*/30 * * * *",
|
|
473
|
+
cronExpression: "*/30 * * * *",
|
|
474
|
+
timezone: null,
|
|
475
|
+
message: "run heartbeat",
|
|
476
|
+
script: null,
|
|
477
|
+
nextRunAt: 1_778_800_000_000,
|
|
478
|
+
lastRunAt: null,
|
|
479
|
+
lastStatus: null,
|
|
480
|
+
retryCount: 0,
|
|
481
|
+
maxRetries: 3,
|
|
482
|
+
retryBackoffMs: 60_000,
|
|
483
|
+
description: "Every 30 minutes",
|
|
484
|
+
mode: "execute",
|
|
485
|
+
status: "active",
|
|
486
|
+
routingIntent: "all_channels",
|
|
487
|
+
reuseConversation: false,
|
|
488
|
+
wakeConversationId: null,
|
|
489
|
+
isOneShot: false,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const { stdout, exitCode } = await runCommand([
|
|
496
|
+
"schedules",
|
|
497
|
+
"create",
|
|
498
|
+
"Heartbeat",
|
|
499
|
+
"--expression",
|
|
500
|
+
"*/30 * * * *",
|
|
501
|
+
"--message",
|
|
502
|
+
"run heartbeat",
|
|
503
|
+
"--json",
|
|
504
|
+
]);
|
|
505
|
+
|
|
506
|
+
expect(exitCode).toBe(0);
|
|
507
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
508
|
+
schedules: [expect.objectContaining({ id: "new-schedule-id" })],
|
|
509
|
+
});
|
|
510
|
+
expect(logLines).toEqual([]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
test("routes IPC failure through exitFromIpcResult", async () => {
|
|
514
|
+
mockIpcResult = {
|
|
515
|
+
ok: false,
|
|
516
|
+
error: "expression could not be parsed as cron or rrule",
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const { exitCode } = await runCommand([
|
|
520
|
+
"schedules",
|
|
521
|
+
"create",
|
|
522
|
+
"Bad",
|
|
523
|
+
"--expression",
|
|
524
|
+
"not-a-cron",
|
|
525
|
+
"--message",
|
|
526
|
+
"noop",
|
|
527
|
+
]);
|
|
528
|
+
|
|
529
|
+
expect(exitCode).toBe(10);
|
|
530
|
+
expect(exitFromIpcResultCalls).toEqual([mockIpcResult]);
|
|
531
|
+
expect(errorLines).toEqual([]);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe("schedules enable/disable", () => {
|
|
536
|
+
test("enable calls toggleSchedule with enabled true", async () => {
|
|
537
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
538
|
+
|
|
539
|
+
const { exitCode } = await runCommand([
|
|
540
|
+
"schedules",
|
|
541
|
+
"enable",
|
|
542
|
+
"schedule-1",
|
|
543
|
+
]);
|
|
544
|
+
|
|
545
|
+
expect(exitCode).toBe(0);
|
|
546
|
+
expect(ipcCalls).toEqual([
|
|
547
|
+
{
|
|
548
|
+
method: "toggleSchedule",
|
|
549
|
+
params: {
|
|
550
|
+
pathParams: { id: "schedule-1" },
|
|
551
|
+
body: { enabled: true },
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
expect(logLines).toEqual(["Enabled schedule: schedule-1"]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
test("disable calls toggleSchedule with enabled false", async () => {
|
|
559
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
560
|
+
|
|
561
|
+
const { exitCode } = await runCommand([
|
|
562
|
+
"schedules",
|
|
563
|
+
"disable",
|
|
564
|
+
"schedule-1",
|
|
565
|
+
]);
|
|
566
|
+
|
|
567
|
+
expect(exitCode).toBe(0);
|
|
568
|
+
expect(ipcCalls).toEqual([
|
|
569
|
+
{
|
|
570
|
+
method: "toggleSchedule",
|
|
571
|
+
params: {
|
|
572
|
+
pathParams: { id: "schedule-1" },
|
|
573
|
+
body: { enabled: false },
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
]);
|
|
577
|
+
expect(logLines).toEqual(["Disabled schedule: schedule-1"]);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("emits JSON result when --json is set", async () => {
|
|
581
|
+
mockIpcResult = {
|
|
582
|
+
ok: true,
|
|
583
|
+
result: {
|
|
584
|
+
schedules: [
|
|
585
|
+
{
|
|
586
|
+
id: "schedule-1",
|
|
587
|
+
name: "Heartbeat",
|
|
588
|
+
enabled: false,
|
|
589
|
+
syntax: "cron",
|
|
590
|
+
expression: "*/30 * * * *",
|
|
591
|
+
cronExpression: "*/30 * * * *",
|
|
592
|
+
timezone: "UTC",
|
|
593
|
+
message: "run heartbeat",
|
|
594
|
+
script: null,
|
|
595
|
+
nextRunAt: 1_778_800_000_000,
|
|
596
|
+
lastRunAt: null,
|
|
597
|
+
lastStatus: "ok",
|
|
598
|
+
retryCount: 0,
|
|
599
|
+
maxRetries: 3,
|
|
600
|
+
retryBackoffMs: 60_000,
|
|
601
|
+
description: "Every 30 minutes",
|
|
602
|
+
mode: "execute",
|
|
603
|
+
status: "active",
|
|
604
|
+
routingIntent: "all_channels",
|
|
605
|
+
reuseConversation: false,
|
|
606
|
+
wakeConversationId: null,
|
|
607
|
+
isOneShot: false,
|
|
608
|
+
},
|
|
609
|
+
],
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const { stdout, exitCode } = await runCommand([
|
|
614
|
+
"schedules",
|
|
615
|
+
"disable",
|
|
616
|
+
"schedule-1",
|
|
617
|
+
"--json",
|
|
618
|
+
]);
|
|
619
|
+
|
|
620
|
+
expect(exitCode).toBe(0);
|
|
621
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
622
|
+
schedules: [
|
|
623
|
+
expect.objectContaining({ id: "schedule-1", enabled: false }),
|
|
624
|
+
],
|
|
625
|
+
});
|
|
626
|
+
expect(logLines).toEqual([]);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
test("routes IPC failure through exitFromIpcResult", async () => {
|
|
630
|
+
mockIpcResult = { ok: false, error: "Schedule not found" };
|
|
631
|
+
|
|
632
|
+
const { exitCode } = await runCommand([
|
|
633
|
+
"schedules",
|
|
634
|
+
"enable",
|
|
635
|
+
"missing-schedule",
|
|
636
|
+
]);
|
|
637
|
+
|
|
638
|
+
expect(exitCode).toBe(10);
|
|
639
|
+
expect(exitFromIpcResultCalls).toEqual([mockIpcResult]);
|
|
640
|
+
expect(errorLines).toEqual([]);
|
|
641
|
+
});
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
describe("schedules cancel", () => {
|
|
645
|
+
test("calls cancelSchedule with the schedule ID path param", async () => {
|
|
646
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
647
|
+
|
|
648
|
+
const { exitCode } = await runCommand([
|
|
649
|
+
"schedules",
|
|
650
|
+
"cancel",
|
|
651
|
+
"schedule-1",
|
|
652
|
+
]);
|
|
653
|
+
|
|
654
|
+
expect(exitCode).toBe(0);
|
|
655
|
+
expect(ipcCalls).toEqual([
|
|
656
|
+
{
|
|
657
|
+
method: "cancelSchedule",
|
|
658
|
+
params: { pathParams: { id: "schedule-1" } },
|
|
659
|
+
},
|
|
660
|
+
]);
|
|
661
|
+
expect(logLines).toEqual(["Cancelled schedule: schedule-1"]);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test("emits JSON result when --json is set", async () => {
|
|
665
|
+
mockIpcResult = {
|
|
666
|
+
ok: true,
|
|
667
|
+
result: {
|
|
668
|
+
schedules: [
|
|
669
|
+
{
|
|
670
|
+
id: "remaining-schedule",
|
|
671
|
+
name: "Heartbeat",
|
|
672
|
+
enabled: true,
|
|
673
|
+
syntax: "cron",
|
|
674
|
+
expression: "*/30 * * * *",
|
|
675
|
+
cronExpression: "*/30 * * * *",
|
|
676
|
+
timezone: "UTC",
|
|
677
|
+
message: "run heartbeat",
|
|
678
|
+
script: null,
|
|
679
|
+
nextRunAt: 1_778_800_000_000,
|
|
680
|
+
lastRunAt: null,
|
|
681
|
+
lastStatus: "ok",
|
|
682
|
+
retryCount: 0,
|
|
683
|
+
maxRetries: 3,
|
|
684
|
+
retryBackoffMs: 60_000,
|
|
685
|
+
description: "Every 30 minutes",
|
|
686
|
+
mode: "execute",
|
|
687
|
+
status: "active",
|
|
688
|
+
routingIntent: "all_channels",
|
|
689
|
+
reuseConversation: false,
|
|
690
|
+
wakeConversationId: null,
|
|
691
|
+
isOneShot: false,
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
},
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const { stdout, exitCode } = await runCommand([
|
|
698
|
+
"schedules",
|
|
699
|
+
"cancel",
|
|
700
|
+
"schedule-1",
|
|
701
|
+
"--json",
|
|
702
|
+
]);
|
|
703
|
+
|
|
704
|
+
expect(exitCode).toBe(0);
|
|
705
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
706
|
+
schedules: [expect.objectContaining({ id: "remaining-schedule" })],
|
|
707
|
+
});
|
|
708
|
+
expect(logLines).toEqual([]);
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
test("routes IPC failure through exitFromIpcResult", async () => {
|
|
712
|
+
mockIpcResult = {
|
|
713
|
+
ok: false,
|
|
714
|
+
error: "Schedule not found or not cancellable",
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const { exitCode } = await runCommand([
|
|
718
|
+
"schedules",
|
|
719
|
+
"cancel",
|
|
720
|
+
"missing-schedule",
|
|
721
|
+
]);
|
|
722
|
+
|
|
723
|
+
expect(exitCode).toBe(10);
|
|
724
|
+
expect(exitFromIpcResultCalls).toEqual([mockIpcResult]);
|
|
725
|
+
expect(errorLines).toEqual([]);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
describe("schedules delete", () => {
|
|
730
|
+
test("calls deleteSchedule with the schedule ID path param when --force is set", async () => {
|
|
731
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
732
|
+
|
|
733
|
+
const { exitCode } = await runCommand([
|
|
734
|
+
"schedules",
|
|
735
|
+
"delete",
|
|
736
|
+
"schedule-1",
|
|
737
|
+
"--force",
|
|
738
|
+
]);
|
|
739
|
+
|
|
740
|
+
expect(exitCode).toBe(0);
|
|
741
|
+
expect(ipcCalls).toEqual([
|
|
742
|
+
{
|
|
743
|
+
method: "deleteSchedule",
|
|
744
|
+
params: { pathParams: { id: "schedule-1" } },
|
|
745
|
+
},
|
|
746
|
+
]);
|
|
747
|
+
expect(logLines).toEqual(["Deleted schedule: schedule-1"]);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test("emits JSON result when --json is set with --force", async () => {
|
|
751
|
+
mockIpcResult = {
|
|
752
|
+
ok: true,
|
|
753
|
+
result: {
|
|
754
|
+
schedules: [
|
|
755
|
+
{
|
|
756
|
+
id: "remaining-schedule",
|
|
757
|
+
name: "Heartbeat",
|
|
758
|
+
enabled: true,
|
|
759
|
+
syntax: "cron",
|
|
760
|
+
expression: "*/30 * * * *",
|
|
761
|
+
cronExpression: "*/30 * * * *",
|
|
762
|
+
timezone: "UTC",
|
|
763
|
+
message: "run heartbeat",
|
|
764
|
+
script: null,
|
|
765
|
+
nextRunAt: 1_778_800_000_000,
|
|
766
|
+
lastRunAt: null,
|
|
767
|
+
lastStatus: "ok",
|
|
768
|
+
retryCount: 0,
|
|
769
|
+
maxRetries: 3,
|
|
770
|
+
retryBackoffMs: 60_000,
|
|
771
|
+
description: "Every 30 minutes",
|
|
772
|
+
mode: "execute",
|
|
773
|
+
status: "active",
|
|
774
|
+
routingIntent: "all_channels",
|
|
775
|
+
reuseConversation: false,
|
|
776
|
+
wakeConversationId: null,
|
|
777
|
+
isOneShot: false,
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
const { stdout, exitCode } = await runCommand([
|
|
784
|
+
"schedules",
|
|
785
|
+
"delete",
|
|
786
|
+
"schedule-1",
|
|
787
|
+
"--force",
|
|
788
|
+
"--json",
|
|
789
|
+
]);
|
|
790
|
+
|
|
791
|
+
expect(exitCode).toBe(0);
|
|
792
|
+
expect(JSON.parse(stdout)).toEqual({
|
|
793
|
+
schedules: [expect.objectContaining({ id: "remaining-schedule" })],
|
|
794
|
+
});
|
|
795
|
+
expect(logLines).toEqual([]);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
test("routes IPC failure through exitFromIpcResult", async () => {
|
|
799
|
+
mockIpcResult = {
|
|
800
|
+
ok: false,
|
|
801
|
+
error: "Schedule not found",
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
const { exitCode } = await runCommand([
|
|
805
|
+
"schedules",
|
|
806
|
+
"delete",
|
|
807
|
+
"missing-schedule",
|
|
808
|
+
"--force",
|
|
809
|
+
]);
|
|
810
|
+
|
|
811
|
+
expect(exitCode).toBe(10);
|
|
812
|
+
expect(exitFromIpcResultCalls).toEqual([mockIpcResult]);
|
|
813
|
+
expect(errorLines).toEqual([]);
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
test("refuses to delete non-interactively without --force", async () => {
|
|
817
|
+
// bun's test runner attaches a non-TTY stdin, so confirmPrompt takes the
|
|
818
|
+
// non-interactive branch and the IPC is never invoked. This locks in the
|
|
819
|
+
// safety guarantee that scripts must opt in via --force.
|
|
820
|
+
mockIpcResult = { ok: true, result: { schedules: [] } };
|
|
821
|
+
|
|
822
|
+
const { exitCode } = await runCommand([
|
|
823
|
+
"schedules",
|
|
824
|
+
"delete",
|
|
825
|
+
"schedule-1",
|
|
826
|
+
]);
|
|
827
|
+
|
|
828
|
+
expect(exitCode).toBe(1);
|
|
829
|
+
expect(ipcCalls).toEqual([]);
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
364
833
|
describe("schedules execute", () => {
|
|
365
834
|
test("calls runScheduleNow with the schedule ID path param", async () => {
|
|
366
835
|
mockIpcResult = {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
cliIpcCall,
|
|
5
|
+
exitCodeFromIpcResult,
|
|
6
|
+
exitFromIpcResult,
|
|
7
|
+
} from "../../ipc/cli-client.js";
|
|
4
8
|
import { registerCommand } from "../lib/register-command.js";
|
|
5
9
|
import { log } from "../logger.js";
|
|
6
10
|
import { shouldOutputJson, writeOutput } from "../output.js";
|
|
@@ -26,11 +30,14 @@ source channel, event name, and attention hints. The decision engine evaluates
|
|
|
26
30
|
whether and where to deliver the notification based on connected channels,
|
|
27
31
|
urgency, and user preferences.
|
|
28
32
|
|
|
33
|
+
Minimal usage: only --message is required. Add --urgent for a push + visual
|
|
34
|
+
flag in the inbox. Source channel/event name fall back to assistant_tool /
|
|
35
|
+
assistant.share when omitted.
|
|
36
|
+
|
|
29
37
|
Examples:
|
|
30
|
-
$ assistant notifications send --
|
|
31
|
-
$ assistant notifications send --
|
|
32
|
-
$ assistant notifications send --
|
|
33
|
-
$ assistant notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Deploy complete" --preferred-channels vellum,telegram --json`,
|
|
38
|
+
$ assistant notifications send --message "Build finished"
|
|
39
|
+
$ assistant notifications send --message "Pager: prod is down" --urgent
|
|
40
|
+
$ assistant notifications send --message "Build green" --conversation-id 649c4645-3a6f-4ded-a713-504f02ca806b`,
|
|
34
41
|
);
|
|
35
42
|
|
|
36
43
|
// -------------------------------------------------------------------------
|
|
@@ -40,28 +47,33 @@ Examples:
|
|
|
40
47
|
notifications
|
|
41
48
|
.command("send")
|
|
42
49
|
.description(
|
|
43
|
-
"Send a notification through the unified notification router",
|
|
50
|
+
"Send a notification through the unified notification router. Only --message is required; pass --urgent for a push + visual flag.",
|
|
44
51
|
)
|
|
45
52
|
.requiredOption(
|
|
53
|
+
"--message <message>",
|
|
54
|
+
"Notification message the user should receive",
|
|
55
|
+
)
|
|
56
|
+
.option(
|
|
57
|
+
"--urgent",
|
|
58
|
+
"Mark this notification as urgent (fires push + visual flag in inbox)",
|
|
59
|
+
false,
|
|
60
|
+
)
|
|
61
|
+
.option(
|
|
46
62
|
"--source-channel <channel>",
|
|
47
|
-
"Source channel producing this notification",
|
|
63
|
+
"Source channel producing this notification (default: assistant_tool)",
|
|
48
64
|
)
|
|
49
|
-
.
|
|
65
|
+
.option(
|
|
50
66
|
"--source-event-name <name>",
|
|
51
|
-
"Event name for audit, routing, and dedupe grouping",
|
|
52
|
-
)
|
|
53
|
-
.requiredOption(
|
|
54
|
-
"--message <message>",
|
|
55
|
-
"Notification message the user should receive",
|
|
67
|
+
"Event name for audit, routing, and dedupe grouping (default: assistant.share)",
|
|
56
68
|
)
|
|
57
69
|
.option("--title <title>", "Optional notification title")
|
|
58
70
|
.option(
|
|
59
71
|
"--urgency <urgency>",
|
|
60
|
-
"Urgency hint: low, medium, high, critical (default:
|
|
72
|
+
"Urgency hint: low, medium, high, critical (default: low; use --urgent for critical)",
|
|
61
73
|
)
|
|
62
74
|
.option(
|
|
63
75
|
"--requires-action",
|
|
64
|
-
"Whether the notification expects user action (default: true)",
|
|
76
|
+
"Whether the notification expects user action (default: false; use --urgent to force true)",
|
|
65
77
|
)
|
|
66
78
|
.option(
|
|
67
79
|
"--no-requires-action",
|
|
@@ -111,15 +123,16 @@ Examples:
|
|
|
111
123
|
"after",
|
|
112
124
|
`
|
|
113
125
|
Arguments:
|
|
114
|
-
--source-channel One of the registered source channels (see "assistant notifications --help")
|
|
115
|
-
--source-event-name One of the registered event names (see "assistant notifications --help")
|
|
116
126
|
--message The notification body text (required, must be non-empty)
|
|
127
|
+
--urgent Shortcut that maps to urgency=critical + requires-action=true
|
|
117
128
|
|
|
118
129
|
Behavioral notes:
|
|
119
130
|
- The signal is emitted through the full notification pipeline: event store,
|
|
120
131
|
decision engine, deterministic checks, and channel dispatch.
|
|
121
|
-
- --
|
|
122
|
-
|
|
132
|
+
- --urgent overrides --urgency and --requires-action defaults so the signal
|
|
133
|
+
is treated as critical and requires user action. Explicit --urgency /
|
|
134
|
+
--requires-action flags still win for back-compat.
|
|
135
|
+
- Without --urgent, --urgency defaults to low and --requires-action to false.
|
|
123
136
|
- --preferred-channels are hints only; the decision engine may override them.
|
|
124
137
|
- --dedupe-key suppresses duplicate signals with the same key.
|
|
125
138
|
- --conversation-id pins delivery to an existing vellum conversation
|
|
@@ -127,20 +140,20 @@ Behavioral notes:
|
|
|
127
140
|
binding-based pairing for their external threads.
|
|
128
141
|
|
|
129
142
|
Examples:
|
|
130
|
-
$ assistant notifications send --
|
|
131
|
-
$ assistant notifications send --
|
|
132
|
-
$ assistant notifications send --
|
|
133
|
-
$ assistant notifications send --source-channel assistant_tool --source-event-name user.send_notification --message "Build green" --conversation-id 649c4645-3a6f-4ded-a713-504f02ca806b`,
|
|
143
|
+
$ assistant notifications send --message "Task complete"
|
|
144
|
+
$ assistant notifications send --message "Pager: prod is down" --urgent
|
|
145
|
+
$ assistant notifications send --message "Build green" --conversation-id 649c4645-3a6f-4ded-a713-504f02ca806b`,
|
|
134
146
|
)
|
|
135
147
|
.action(
|
|
136
148
|
async (
|
|
137
149
|
opts: {
|
|
138
|
-
sourceChannel
|
|
139
|
-
sourceEventName
|
|
150
|
+
sourceChannel?: string;
|
|
151
|
+
sourceEventName?: string;
|
|
140
152
|
message: string;
|
|
153
|
+
urgent: boolean;
|
|
141
154
|
title?: string;
|
|
142
155
|
urgency?: string;
|
|
143
|
-
requiresAction
|
|
156
|
+
requiresAction?: boolean;
|
|
144
157
|
isAsyncBackground: boolean;
|
|
145
158
|
visibleInSourceNow: boolean;
|
|
146
159
|
deadlineAt?: string;
|
|
@@ -153,6 +166,11 @@ Examples:
|
|
|
153
166
|
cmd: Command,
|
|
154
167
|
) => {
|
|
155
168
|
try {
|
|
169
|
+
// Apply defaults for optional source fields (minimal-surface
|
|
170
|
+
// ergonomics; explicit values from the CLI still win).
|
|
171
|
+
const sourceChannel = opts.sourceChannel ?? "assistant_tool";
|
|
172
|
+
const sourceEventName = opts.sourceEventName ?? "assistant.share";
|
|
173
|
+
|
|
156
174
|
// Validate --message (keep basic validation for immediate CLI feedback)
|
|
157
175
|
const message = opts.message.trim();
|
|
158
176
|
if (message.length === 0) {
|
|
@@ -164,8 +182,15 @@ Examples:
|
|
|
164
182
|
return;
|
|
165
183
|
}
|
|
166
184
|
|
|
185
|
+
// --urgent is a shortcut for urgency=critical + requiresAction=true.
|
|
186
|
+
// Explicit --urgency / --requires-action flags still win so the
|
|
187
|
+
// back-compat path keeps working during the deprecation window.
|
|
188
|
+
const urgentDefaults = opts.urgent
|
|
189
|
+
? { urgency: "critical", requiresAction: true }
|
|
190
|
+
: { urgency: "low", requiresAction: false };
|
|
191
|
+
|
|
167
192
|
// Validate --urgency
|
|
168
|
-
const urgency = opts.urgency ??
|
|
193
|
+
const urgency = opts.urgency ?? urgentDefaults.urgency;
|
|
169
194
|
if (
|
|
170
195
|
urgency !== "low" &&
|
|
171
196
|
urgency !== "medium" &&
|
|
@@ -179,6 +204,8 @@ Examples:
|
|
|
179
204
|
process.exitCode = 1;
|
|
180
205
|
return;
|
|
181
206
|
}
|
|
207
|
+
const requiresAction =
|
|
208
|
+
opts.requiresAction ?? urgentDefaults.requiresAction;
|
|
182
209
|
|
|
183
210
|
// Parse --deadline-at
|
|
184
211
|
let deadlineAt: number | undefined;
|
|
@@ -241,11 +268,11 @@ Examples:
|
|
|
241
268
|
reason: string;
|
|
242
269
|
}>("emit_notification_signal", {
|
|
243
270
|
body: {
|
|
244
|
-
sourceChannel
|
|
245
|
-
sourceEventName
|
|
271
|
+
sourceChannel,
|
|
272
|
+
sourceEventName,
|
|
246
273
|
sourceContextId,
|
|
247
274
|
attentionHints: {
|
|
248
|
-
requiresAction
|
|
275
|
+
requiresAction,
|
|
249
276
|
urgency,
|
|
250
277
|
deadlineAt,
|
|
251
278
|
isAsyncBackground: opts.isAsyncBackground ?? false,
|
|
@@ -253,7 +280,7 @@ Examples:
|
|
|
253
280
|
},
|
|
254
281
|
contextPayload: {
|
|
255
282
|
requestedMessage: message,
|
|
256
|
-
requestedBySource:
|
|
283
|
+
requestedBySource: sourceChannel,
|
|
257
284
|
...(opts.title ? { requestedTitle: opts.title } : {}),
|
|
258
285
|
...(preferredChannels?.length ? { preferredChannels } : {}),
|
|
259
286
|
...(deepLinkMetadata ? { deepLinkMetadata } : {}),
|
|
@@ -267,9 +294,12 @@ Examples:
|
|
|
267
294
|
});
|
|
268
295
|
|
|
269
296
|
if (!result.ok) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
297
|
+
if (shouldOutputJson(cmd)) {
|
|
298
|
+
writeOutput(cmd, { ok: false, error: result.error });
|
|
299
|
+
process.exitCode = exitCodeFromIpcResult(result);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
return exitFromIpcResult(result);
|
|
273
303
|
}
|
|
274
304
|
|
|
275
305
|
const signal = result.result!;
|
|
@@ -382,7 +412,7 @@ Examples:
|
|
|
382
412
|
|
|
383
413
|
if (!result.ok) {
|
|
384
414
|
writeOutput(cmd, { ok: false, error: result.error });
|
|
385
|
-
process.exitCode =
|
|
415
|
+
process.exitCode = exitCodeFromIpcResult(result);
|
|
386
416
|
return;
|
|
387
417
|
}
|
|
388
418
|
|