@vellumai/assistant 0.9.0 → 0.9.1-staging.1

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.
Files changed (222) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +4 -4
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
  12. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  13. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  14. package/openapi.yaml +135 -59
  15. package/package.json +2 -1
  16. package/scripts/sync-llm-catalog.ts +6 -15
  17. package/scripts/sync-web-search-catalog.ts +3 -11
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
  19. package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
  20. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  21. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  22. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  23. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  24. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  25. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  26. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  27. package/src/__tests__/btw-routes.test.ts +6 -8
  28. package/src/__tests__/checker.test.ts +0 -3
  29. package/src/__tests__/config-loader-backfill.test.ts +103 -6
  30. package/src/__tests__/config-watcher.test.ts +0 -18
  31. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  32. package/src/__tests__/credential-broker.test.ts +449 -1
  33. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  34. package/src/__tests__/credential-prompt-route.test.ts +3 -4
  35. package/src/__tests__/credential-routes.test.ts +360 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -13
  37. package/src/__tests__/dynamic-page-surface.test.ts +101 -1
  38. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  39. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  40. package/src/__tests__/identity-routes.test.ts +0 -189
  41. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  42. package/src/__tests__/invite-redemption-service.test.ts +4 -4
  43. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  44. package/src/__tests__/llm-catalog-parity.test.ts +0 -22
  45. package/src/__tests__/llm-resolver.test.ts +49 -24
  46. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  47. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  48. package/src/__tests__/persona-resolver.test.ts +11 -14
  49. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  50. package/src/__tests__/registry.test.ts +2 -7
  51. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  52. package/src/__tests__/schedule-routes.test.ts +0 -30
  53. package/src/__tests__/schedule-tools.test.ts +2 -18
  54. package/src/__tests__/skill-execute-input.test.ts +46 -1
  55. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  56. package/src/__tests__/subagent-tools.test.ts +116 -0
  57. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  58. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  59. package/src/__tests__/token-manager.test.ts +519 -0
  60. package/src/__tests__/tool-executor.test.ts +0 -79
  61. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  62. package/src/__tests__/trusted-contact-verification.test.ts +6 -6
  63. package/src/__tests__/voice-invite-redemption.test.ts +2 -2
  64. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  65. package/src/__tests__/workspace-greetings.test.ts +152 -0
  66. package/src/agent/loop.ts +25 -5
  67. package/src/api/README.md +6 -6
  68. package/src/api/responses/conversation-message.ts +2 -4
  69. package/src/api/responses/home.ts +0 -4
  70. package/src/approvals/guardian-request-resolvers.ts +2 -2
  71. package/src/calls/relay-access-wait.ts +1 -1
  72. package/src/calls/voice-session-bridge.ts +2 -2
  73. package/src/cli/commands/plugins.ts +143 -2
  74. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  75. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
  76. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
  77. package/src/cli/lib/diff-plugin.ts +346 -0
  78. package/src/cli/lib/install-from-github.ts +105 -17
  79. package/src/cli/lib/merge-plugin-tree.ts +228 -0
  80. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  81. package/src/cli/lib/upgrade-plugin.ts +270 -10
  82. package/src/cli/program.ts +0 -2
  83. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  84. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  85. package/src/config/bundled-skills/workflows/SKILL.md +0 -1
  86. package/src/config/bundled-tool-registry.ts +2 -7
  87. package/src/config/call-site-defaults.ts +12 -2
  88. package/src/config/feature-flag-registry.json +1 -17
  89. package/src/config/inference-profile-validation.ts +26 -0
  90. package/src/config/loader.ts +4 -0
  91. package/src/config/profile-order.ts +28 -0
  92. package/src/config/schemas/elevenlabs.ts +0 -1
  93. package/src/config/schemas/platform.ts +0 -8
  94. package/src/config/seed-inference-profiles.ts +25 -20
  95. package/src/contacts/contact-store.ts +87 -96
  96. package/src/contacts/contacts-write.ts +5 -21
  97. package/src/context/compactor.ts +2 -2
  98. package/src/credential-execution/process-manager.ts +55 -14
  99. package/src/credential-execution/prompted-credential.ts +2 -3
  100. package/src/daemon/config-watcher.ts +0 -4
  101. package/src/daemon/conversation-agent-loop.ts +15 -4
  102. package/src/daemon/conversation-slash.ts +2 -23
  103. package/src/daemon/conversation-tool-setup.ts +11 -3
  104. package/src/daemon/conversation.ts +2 -0
  105. package/src/daemon/handlers/config-channels.ts +20 -16
  106. package/src/daemon/handlers/config-slack-channel.ts +2 -3
  107. package/src/daemon/lifecycle.ts +0 -7
  108. package/src/daemon/message-types/conversations.ts +3 -3
  109. package/src/daemon/message-types/sync.ts +0 -1
  110. package/src/daemon/orphan-reaper.test.ts +0 -19
  111. package/src/daemon/orphan-reaper.ts +2 -24
  112. package/src/daemon/server.ts +0 -10
  113. package/src/home/relationship-state.ts +2 -4
  114. package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
  115. package/src/memory/bookmark-crud.ts +1 -2
  116. package/src/memory/db-init.ts +7 -17
  117. package/src/memory/embedding-backend.ts +23 -0
  118. package/src/memory/embedding-billing-breaker.ts +96 -0
  119. package/src/memory/jobs-store.ts +25 -13
  120. package/src/memory/jobs-worker.ts +52 -0
  121. package/src/memory/memory-retrospective-constants.ts +4 -4
  122. package/src/memory/memory-retrospective-job.ts +19 -227
  123. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
  124. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
  125. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  126. package/src/memory/migrations/index.ts +1 -0
  127. package/src/memory/migrations/run-migrations.ts +41 -0
  128. package/src/memory/migrations/validate-migration-state.ts +1 -1
  129. package/src/memory/schema/contacts.ts +0 -4
  130. package/src/messaging/providers/slack/adapter.ts +1 -1
  131. package/src/notifications/adapters/shared.ts +29 -0
  132. package/src/notifications/adapters/slack.ts +5 -32
  133. package/src/notifications/adapters/telegram.ts +2 -20
  134. package/src/notifications/broadcaster.ts +10 -1
  135. package/src/notifications/home-feed-side-effect.ts +4 -3
  136. package/src/notifications/notification-utils.ts +17 -19
  137. package/src/notifications/types.ts +7 -0
  138. package/src/oauth/AGENTS.md +5 -24
  139. package/src/plugin-api/constants.ts +1 -1
  140. package/src/plugin-api/index.ts +6 -1
  141. package/src/plugin-api/model-profiles.ts +33 -0
  142. package/src/plugin-api/types.ts +50 -2
  143. package/src/plugins/defaults/index.ts +25 -0
  144. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
  145. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
  146. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  147. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  148. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  149. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  150. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
  151. package/src/prompts/persona-resolver.ts +2 -2
  152. package/src/runtime/AGENTS.md +0 -1
  153. package/src/runtime/actor-trust-resolver.ts +12 -44
  154. package/src/runtime/btw-sidechain.ts +3 -6
  155. package/src/runtime/channel-approval-types.ts +18 -45
  156. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  157. package/src/runtime/channel-verification-service.ts +4 -3
  158. package/src/runtime/invite-redemption-service.ts +3 -3
  159. package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
  160. package/src/runtime/routes/app-routes.ts +1 -1
  161. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  162. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  163. package/src/runtime/routes/btw-routes.ts +1 -27
  164. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  165. package/src/runtime/routes/conversation-routes.ts +2 -2
  166. package/src/runtime/routes/credential-routes.ts +40 -16
  167. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  168. package/src/runtime/routes/identity-routes.ts +1 -296
  169. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
  170. package/src/runtime/routes/plugins-routes.ts +171 -5
  171. package/src/runtime/routes/schedule-routes.ts +0 -22
  172. package/src/runtime/routes/workflow-routes.test.ts +4 -43
  173. package/src/runtime/routes/workflow-routes.ts +0 -28
  174. package/src/runtime/routes/workspace-greetings.ts +55 -0
  175. package/src/runtime/sync/resource-sync-events.ts +1 -11
  176. package/src/schedule/inference-profile.ts +2 -14
  177. package/src/subagent/manager.ts +6 -0
  178. package/src/subagent/types.ts +6 -0
  179. package/src/tools/AGENTS.md +3 -3
  180. package/src/tools/browser/browser-execution.ts +1 -1
  181. package/src/tools/network/web-search-error.ts +1 -1
  182. package/src/tools/permission-checker.ts +1 -1
  183. package/src/tools/schedule/create.ts +3 -9
  184. package/src/tools/schedule/update.ts +2 -10
  185. package/src/tools/side-effects.ts +2 -17
  186. package/src/tools/skills/execute.ts +34 -0
  187. package/src/tools/subagent/spawn.ts +34 -9
  188. package/src/tools/tool-approval-handler.ts +1 -3
  189. package/src/tools/tool-manifest.ts +0 -2
  190. package/src/tools/ui-surface/definitions.ts +39 -1
  191. package/src/tools/workflows/run-workflow.test.ts +8 -18
  192. package/src/util/platform.ts +2 -2
  193. package/src/workflows/capabilities.ts +2 -3
  194. package/src/workflows/run-manager.test.ts +0 -25
  195. package/src/workflows/run-manager.ts +2 -24
  196. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  197. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  198. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  199. package/src/__tests__/credential-vault.test.ts +0 -1706
  200. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  201. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  202. package/src/cli/commands/__tests__/task.test.ts +0 -914
  203. package/src/cli/commands/task.ts +0 -771
  204. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  205. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  206. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  207. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  208. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  209. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  210. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  211. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  212. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  213. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  214. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  215. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  216. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  217. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  218. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  219. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  220. package/src/memory/preloaded-apps.ts +0 -116
  221. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  222. package/src/tools/credentials/vault.ts +0 -712
@@ -1,914 +0,0 @@
1
- /**
2
- * Tests for the `assistant task` CLI command.
3
- *
4
- * Validates:
5
- * - Template subcommands: list, save, run, delete
6
- * - Queue subcommands: show, add, update, remove, run
7
- * - Conversation ID resolution (explicit, __SKILL_CONTEXT_JSON, __CONVERSATION_ID)
8
- * - --json output mode for both template and queue commands
9
- * - Error propagation (IPC failure -> exitCode 1)
10
- * - --required-tools comma-splitting for queue add
11
- */
12
-
13
- import { beforeEach, describe, expect, mock, test } from "bun:test";
14
-
15
- import { Command } from "commander";
16
-
17
- // ---------------------------------------------------------------------------
18
- // Mock state
19
- // ---------------------------------------------------------------------------
20
-
21
- /** The last `cliIpcCall` invocation captured for assertions. */
22
-
23
- let lastIpcCall: {
24
- method: string;
25
- params?: any;
26
- } | null = null;
27
-
28
- /** The result that cliIpcCall will return. */
29
- let mockIpcResult: {
30
- ok: boolean;
31
- result?: unknown;
32
- error?: string;
33
- } = { ok: true, result: {} };
34
-
35
- // ---------------------------------------------------------------------------
36
- // Mocks
37
- // ---------------------------------------------------------------------------
38
-
39
- mock.module("../../../ipc/cli-client.js", () => ({
40
- cliIpcCall: async (method: string, params?: Record<string, unknown>) => {
41
- lastIpcCall = { method, params };
42
- return mockIpcResult;
43
- },
44
- }));
45
-
46
- mock.module("../../../util/logger.js", () => ({
47
- getLogger: () => ({
48
- info: () => {},
49
- warn: () => {},
50
- error: () => {},
51
- debug: () => {},
52
- }),
53
- getCliLogger: () => ({
54
- info: () => {},
55
- warn: () => {},
56
- error: () => {},
57
- debug: () => {},
58
- }),
59
- }));
60
-
61
- // ---------------------------------------------------------------------------
62
- // Import module under test (after mocks)
63
- // ---------------------------------------------------------------------------
64
-
65
- const { registerTaskCommand } = await import("../task.js");
66
-
67
- // ---------------------------------------------------------------------------
68
- // Test helper
69
- // ---------------------------------------------------------------------------
70
-
71
- async function runCommand(
72
- args: string[],
73
- ): Promise<{ stdout: string; exitCode: number }> {
74
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
75
- const stdoutChunks: string[] = [];
76
-
77
- process.stdout.write = ((chunk: unknown) => {
78
- stdoutChunks.push(typeof chunk === "string" ? chunk : String(chunk));
79
- return true;
80
- }) as typeof process.stdout.write;
81
-
82
- process.exitCode = 0;
83
-
84
- try {
85
- const program = new Command();
86
- program.exitOverride();
87
- program.configureOutput({
88
- writeErr: () => {},
89
- writeOut: (str: string) => stdoutChunks.push(str),
90
- });
91
- registerTaskCommand(program);
92
- await program.parseAsync(["node", "assistant", ...args]);
93
- } catch {
94
- if (process.exitCode === 0) process.exitCode = 1;
95
- } finally {
96
- process.stdout.write = originalStdoutWrite;
97
- }
98
-
99
- const exitCode = process.exitCode ?? 0;
100
- process.exitCode = 0;
101
-
102
- return {
103
- exitCode,
104
- stdout: stdoutChunks.join(""),
105
- };
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // Setup
110
- // ---------------------------------------------------------------------------
111
-
112
- /** Saved env vars to restore after each test. */
113
- let savedConvId: string | undefined;
114
- let savedSkillCtx: string | undefined;
115
-
116
- beforeEach(() => {
117
- lastIpcCall = null;
118
- mockIpcResult = { ok: true, result: {} };
119
- process.exitCode = 0;
120
-
121
- // Save and clear env vars that resolveConversationId reads
122
- savedConvId = process.env.__CONVERSATION_ID;
123
- savedSkillCtx = process.env.__SKILL_CONTEXT_JSON;
124
- delete process.env.__CONVERSATION_ID;
125
- delete process.env.__SKILL_CONTEXT_JSON;
126
- });
127
-
128
- // Restore env vars after each test
129
- import { afterEach } from "bun:test";
130
-
131
- afterEach(() => {
132
- if (savedConvId !== undefined) {
133
- process.env.__CONVERSATION_ID = savedConvId;
134
- } else {
135
- delete process.env.__CONVERSATION_ID;
136
- }
137
- if (savedSkillCtx !== undefined) {
138
- process.env.__SKILL_CONTEXT_JSON = savedSkillCtx;
139
- } else {
140
- delete process.env.__SKILL_CONTEXT_JSON;
141
- }
142
- });
143
-
144
- // ===========================================================================
145
- // Template subcommands
146
- // ===========================================================================
147
-
148
- // ---------------------------------------------------------------------------
149
- // task list
150
- // ---------------------------------------------------------------------------
151
-
152
- describe("task list", () => {
153
- test("calls task/list IPC and succeeds", async () => {
154
- mockIpcResult = {
155
- ok: true,
156
- result: [{ id: "t1", name: "deploy" }],
157
- };
158
-
159
- const { exitCode } = await runCommand(["task", "list"]);
160
-
161
- expect(exitCode).toBe(0);
162
- expect(lastIpcCall).toBeDefined();
163
- expect(lastIpcCall!.method).toBe("task_list");
164
- });
165
-
166
- test("--json outputs { ok: true, result: ... }", async () => {
167
- mockIpcResult = {
168
- ok: true,
169
- result: [{ id: "t1", name: "deploy" }],
170
- };
171
-
172
- const { exitCode, stdout } = await runCommand(["task", "list", "--json"]);
173
-
174
- expect(exitCode).toBe(0);
175
- const parsed = JSON.parse(stdout);
176
- expect(parsed).toEqual({
177
- ok: true,
178
- result: [{ id: "t1", name: "deploy" }],
179
- });
180
- });
181
-
182
- test("IPC failure sets exitCode 1", async () => {
183
- mockIpcResult = { ok: false, error: "Connection refused" };
184
-
185
- const { exitCode } = await runCommand(["task", "list"]);
186
-
187
- expect(exitCode).toBe(1);
188
- });
189
- });
190
-
191
- // ---------------------------------------------------------------------------
192
- // task save
193
- // ---------------------------------------------------------------------------
194
-
195
- describe("task save", () => {
196
- test("calls task/save with explicit --conversation-id and --title", async () => {
197
- mockIpcResult = { ok: true, result: { id: "task-1" } };
198
-
199
- const { exitCode } = await runCommand([
200
- "task",
201
- "save",
202
- "--conversation-id",
203
- "conv-123",
204
- "--title",
205
- "My Task",
206
- ]);
207
-
208
- expect(exitCode).toBe(0);
209
- expect(lastIpcCall).toBeDefined();
210
- expect(lastIpcCall!.method).toBe("task_save");
211
- expect(lastIpcCall!.params!.body).toEqual({
212
- conversation_id: "conv-123",
213
- title: "My Task",
214
- });
215
- });
216
-
217
- test("resolves conversation ID from __CONVERSATION_ID env var", async () => {
218
- process.env.__CONVERSATION_ID = "env-conv-456";
219
- mockIpcResult = { ok: true, result: { id: "task-2" } };
220
-
221
- const { exitCode } = await runCommand(["task", "save"]);
222
-
223
- expect(exitCode).toBe(0);
224
- expect(lastIpcCall!.method).toBe("task_save");
225
- expect(lastIpcCall!.params!.body!.conversation_id).toBe("env-conv-456");
226
- });
227
-
228
- test("prefers __SKILL_CONTEXT_JSON over __CONVERSATION_ID", async () => {
229
- process.env.__CONVERSATION_ID = "env-conv-should-not-use";
230
- process.env.__SKILL_CONTEXT_JSON = JSON.stringify({
231
- conversationId: "skill-conv-789",
232
- });
233
- mockIpcResult = { ok: true, result: { id: "task-3" } };
234
-
235
- const { exitCode } = await runCommand(["task", "save"]);
236
-
237
- expect(exitCode).toBe(0);
238
- expect(lastIpcCall!.params!.body!.conversation_id).toBe("skill-conv-789");
239
- });
240
-
241
- test("exits with error when no conversation ID is available", async () => {
242
- // No env vars set, no explicit flag
243
- const { exitCode } = await runCommand(["task", "save"]);
244
-
245
- expect(exitCode).toBe(1);
246
- expect(lastIpcCall).toBeNull();
247
- });
248
-
249
- test("--json outputs error when no conversation ID is available", async () => {
250
- const { exitCode, stdout } = await runCommand(["task", "save", "--json"]);
251
-
252
- expect(exitCode).toBe(1);
253
- const parsed = JSON.parse(stdout);
254
- expect(parsed.ok).toBe(false);
255
- expect(parsed.error).toContain("No conversation ID");
256
- });
257
-
258
- test("--json outputs { ok: true, result: ... } on success", async () => {
259
- process.env.__CONVERSATION_ID = "conv-json-test";
260
- mockIpcResult = { ok: true, result: { id: "task-json" } };
261
-
262
- const { exitCode, stdout } = await runCommand(["task", "save", "--json"]);
263
-
264
- expect(exitCode).toBe(0);
265
- const parsed = JSON.parse(stdout);
266
- expect(parsed).toEqual({ ok: true, result: { id: "task-json" } });
267
- });
268
-
269
- test("IPC failure sets exitCode 1", async () => {
270
- process.env.__CONVERSATION_ID = "conv-fail";
271
- mockIpcResult = { ok: false, error: "Internal error" };
272
-
273
- const { exitCode } = await runCommand(["task", "save"]);
274
-
275
- expect(exitCode).toBe(1);
276
- });
277
- });
278
-
279
- // ---------------------------------------------------------------------------
280
- // task run
281
- // ---------------------------------------------------------------------------
282
-
283
- describe("task run", () => {
284
- test("calls task/run with --name and --inputs", async () => {
285
- mockIpcResult = { ok: true, result: { conversationId: "c1" } };
286
-
287
- const { exitCode } = await runCommand([
288
- "task",
289
- "run",
290
- "--name",
291
- "deploy",
292
- "--inputs",
293
- '{"env":"prod"}',
294
- ]);
295
-
296
- expect(exitCode).toBe(0);
297
- expect(lastIpcCall).toBeDefined();
298
- expect(lastIpcCall!.method).toBe("task_run");
299
- expect(lastIpcCall!.params!.body).toEqual({
300
- task_name: "deploy",
301
- inputs: { env: "prod" },
302
- });
303
- });
304
-
305
- test("calls task/run with --id", async () => {
306
- mockIpcResult = { ok: true, result: { conversationId: "c2" } };
307
-
308
- const { exitCode } = await runCommand([
309
- "task",
310
- "run",
311
- "--id",
312
- "task_abc123",
313
- ]);
314
-
315
- expect(exitCode).toBe(0);
316
- expect(lastIpcCall!.params!.body).toEqual({ task_id: "task_abc123" });
317
- });
318
-
319
- test("exits with error on invalid JSON --inputs", async () => {
320
- const { exitCode } = await runCommand([
321
- "task",
322
- "run",
323
- "--name",
324
- "deploy",
325
- "--inputs",
326
- "{bad json}",
327
- ]);
328
-
329
- expect(exitCode).toBe(1);
330
- expect(lastIpcCall).toBeNull();
331
- });
332
-
333
- test("--json outputs error on invalid --inputs", async () => {
334
- const { exitCode, stdout } = await runCommand([
335
- "task",
336
- "run",
337
- "--name",
338
- "deploy",
339
- "--inputs",
340
- "{bad}",
341
- "--json",
342
- ]);
343
-
344
- expect(exitCode).toBe(1);
345
- const parsed = JSON.parse(stdout);
346
- expect(parsed.ok).toBe(false);
347
- expect(parsed.error).toContain("Invalid JSON for --inputs");
348
- });
349
-
350
- test("--json outputs { ok: true, result: ... } on success", async () => {
351
- mockIpcResult = { ok: true, result: { conversationId: "c3" } };
352
-
353
- const { exitCode, stdout } = await runCommand([
354
- "task",
355
- "run",
356
- "--name",
357
- "deploy",
358
- "--json",
359
- ]);
360
-
361
- expect(exitCode).toBe(0);
362
- const parsed = JSON.parse(stdout);
363
- expect(parsed).toEqual({
364
- ok: true,
365
- result: { conversationId: "c3" },
366
- });
367
- });
368
-
369
- test("IPC failure sets exitCode 1", async () => {
370
- mockIpcResult = { ok: false, error: "Task not found" };
371
-
372
- const { exitCode } = await runCommand([
373
- "task",
374
- "run",
375
- "--name",
376
- "nonexistent",
377
- ]);
378
-
379
- expect(exitCode).toBe(1);
380
- });
381
- });
382
-
383
- // ---------------------------------------------------------------------------
384
- // task delete
385
- // ---------------------------------------------------------------------------
386
-
387
- describe("task delete", () => {
388
- test("passes variadic IDs as task_ids array", async () => {
389
- mockIpcResult = { ok: true, result: { deleted: 2 } };
390
-
391
- const { exitCode } = await runCommand(["task", "delete", "id1", "id2"]);
392
-
393
- expect(exitCode).toBe(0);
394
- expect(lastIpcCall).toBeDefined();
395
- expect(lastIpcCall!.method).toBe("task_delete");
396
- expect(lastIpcCall!.params!.body).toEqual({ task_ids: ["id1", "id2"] });
397
- });
398
-
399
- test("passes single ID as task_ids array", async () => {
400
- mockIpcResult = { ok: true, result: { deleted: 1 } };
401
-
402
- const { exitCode } = await runCommand(["task", "delete", "id1"]);
403
-
404
- expect(exitCode).toBe(0);
405
- expect(lastIpcCall!.params!.body).toEqual({ task_ids: ["id1"] });
406
- });
407
-
408
- test("--json outputs { ok: true, result: ... } on success", async () => {
409
- mockIpcResult = { ok: true, result: { deleted: 1 } };
410
-
411
- const { exitCode, stdout } = await runCommand([
412
- "task",
413
- "delete",
414
- "id1",
415
- "--json",
416
- ]);
417
-
418
- expect(exitCode).toBe(0);
419
- const parsed = JSON.parse(stdout);
420
- expect(parsed).toEqual({ ok: true, result: { deleted: 1 } });
421
- });
422
-
423
- test("IPC failure sets exitCode 1", async () => {
424
- mockIpcResult = { ok: false, error: "Not found" };
425
-
426
- const { exitCode } = await runCommand(["task", "delete", "id1"]);
427
-
428
- expect(exitCode).toBe(1);
429
- });
430
-
431
- test("--json outputs error on IPC failure", async () => {
432
- mockIpcResult = { ok: false, error: "Not found" };
433
-
434
- const { exitCode, stdout } = await runCommand([
435
- "task",
436
- "delete",
437
- "id1",
438
- "--json",
439
- ]);
440
-
441
- expect(exitCode).toBe(1);
442
- const parsed = JSON.parse(stdout);
443
- expect(parsed).toEqual({ ok: false, error: "Not found" });
444
- });
445
- });
446
-
447
- // ===========================================================================
448
- // Queue subcommands
449
- // ===========================================================================
450
-
451
- // ---------------------------------------------------------------------------
452
- // task queue show
453
- // ---------------------------------------------------------------------------
454
-
455
- describe("task queue show", () => {
456
- test("calls task/queue/show IPC with no params", async () => {
457
- mockIpcResult = {
458
- ok: true,
459
- result: { content: "No items in queue" },
460
- };
461
-
462
- const { exitCode } = await runCommand(["task", "queue", "show"]);
463
-
464
- expect(exitCode).toBe(0);
465
- expect(lastIpcCall).toBeDefined();
466
- expect(lastIpcCall!.method).toBe("task_queue_show");
467
- expect(lastIpcCall!.params!.body).toEqual({});
468
- });
469
-
470
- test("passes --status filter", async () => {
471
- mockIpcResult = {
472
- ok: true,
473
- result: { content: "1 queued item" },
474
- };
475
-
476
- const { exitCode } = await runCommand([
477
- "task",
478
- "queue",
479
- "show",
480
- "--status",
481
- "queued",
482
- ]);
483
-
484
- expect(exitCode).toBe(0);
485
- expect(lastIpcCall!.params!.body).toEqual({ status: "queued" });
486
- });
487
-
488
- test("--json outputs { ok: true, result: ... } on success", async () => {
489
- mockIpcResult = {
490
- ok: true,
491
- result: { content: "items" },
492
- };
493
-
494
- const { exitCode, stdout } = await runCommand([
495
- "task",
496
- "queue",
497
- "show",
498
- "--json",
499
- ]);
500
-
501
- expect(exitCode).toBe(0);
502
- const parsed = JSON.parse(stdout);
503
- expect(parsed).toEqual({ ok: true, result: { content: "items" } });
504
- });
505
-
506
- test("IPC failure sets exitCode 1", async () => {
507
- mockIpcResult = { ok: false, error: "Connection refused" };
508
-
509
- const { exitCode } = await runCommand(["task", "queue", "show"]);
510
-
511
- expect(exitCode).toBe(1);
512
- });
513
- });
514
-
515
- // ---------------------------------------------------------------------------
516
- // task queue add
517
- // ---------------------------------------------------------------------------
518
-
519
- describe("task queue add", () => {
520
- test("maps flags to snake_case params", async () => {
521
- mockIpcResult = {
522
- ok: true,
523
- result: { content: "Added" },
524
- };
525
-
526
- const { exitCode } = await runCommand([
527
- "task",
528
- "queue",
529
- "add",
530
- "--title",
531
- "Fix bug",
532
- "--priority",
533
- "0",
534
- ]);
535
-
536
- expect(exitCode).toBe(0);
537
- expect(lastIpcCall).toBeDefined();
538
- expect(lastIpcCall!.method).toBe("task_queue_add");
539
- expect(lastIpcCall!.params!.body!.title).toBe("Fix bug");
540
- expect(lastIpcCall!.params!.body!.priority_tier).toBe(0);
541
- });
542
-
543
- test("splits --required-tools by comma", async () => {
544
- mockIpcResult = {
545
- ok: true,
546
- result: { content: "Added" },
547
- };
548
-
549
- const { exitCode } = await runCommand([
550
- "task",
551
- "queue",
552
- "add",
553
- "--title",
554
- "Build",
555
- "--required-tools",
556
- "host_bash,host_file_read",
557
- ]);
558
-
559
- expect(exitCode).toBe(0);
560
- expect(lastIpcCall!.params!.body!.required_tools).toEqual([
561
- "host_bash",
562
- "host_file_read",
563
- ]);
564
- });
565
-
566
- test("handles --required-tools with spaces after commas", async () => {
567
- mockIpcResult = {
568
- ok: true,
569
- result: { content: "Added" },
570
- };
571
-
572
- const { exitCode } = await runCommand([
573
- "task",
574
- "queue",
575
- "add",
576
- "--title",
577
- "Build",
578
- "--required-tools",
579
- "host_bash, host_file_read, browser",
580
- ]);
581
-
582
- expect(exitCode).toBe(0);
583
- expect(lastIpcCall!.params!.body!.required_tools).toEqual([
584
- "host_bash",
585
- "host_file_read",
586
- "browser",
587
- ]);
588
- });
589
-
590
- test("passes --if-exists strategy", async () => {
591
- mockIpcResult = {
592
- ok: true,
593
- result: { content: "Reused" },
594
- };
595
-
596
- const { exitCode } = await runCommand([
597
- "task",
598
- "queue",
599
- "add",
600
- "--title",
601
- "Deploy",
602
- "--if-exists",
603
- "reuse_existing",
604
- ]);
605
-
606
- expect(exitCode).toBe(0);
607
- expect(lastIpcCall!.params!.body!.if_exists).toBe("reuse_existing");
608
- });
609
-
610
- test("passes --name as task_name", async () => {
611
- mockIpcResult = {
612
- ok: true,
613
- result: { content: "Added" },
614
- };
615
-
616
- const { exitCode } = await runCommand([
617
- "task",
618
- "queue",
619
- "add",
620
- "--name",
621
- "Deploy workflow",
622
- "--title",
623
- "Deploy v2",
624
- ]);
625
-
626
- expect(exitCode).toBe(0);
627
- expect(lastIpcCall!.params!.body!.task_name).toBe("Deploy workflow");
628
- expect(lastIpcCall!.params!.body!.title).toBe("Deploy v2");
629
- });
630
-
631
- test("--json outputs { ok: true, result: ... } on success", async () => {
632
- mockIpcResult = {
633
- ok: true,
634
- result: { content: "Added item" },
635
- };
636
-
637
- const { exitCode, stdout } = await runCommand([
638
- "task",
639
- "queue",
640
- "add",
641
- "--title",
642
- "Test",
643
- "--json",
644
- ]);
645
-
646
- expect(exitCode).toBe(0);
647
- const parsed = JSON.parse(stdout);
648
- expect(parsed).toEqual({ ok: true, result: { content: "Added item" } });
649
- });
650
-
651
- test("IPC failure sets exitCode 1", async () => {
652
- mockIpcResult = { ok: false, error: "Internal error" };
653
-
654
- const { exitCode } = await runCommand([
655
- "task",
656
- "queue",
657
- "add",
658
- "--title",
659
- "Test",
660
- ]);
661
-
662
- expect(exitCode).toBe(1);
663
- });
664
- });
665
-
666
- // ---------------------------------------------------------------------------
667
- // task queue update
668
- // ---------------------------------------------------------------------------
669
-
670
- describe("task queue update", () => {
671
- test("passes --work-item-id and --status", async () => {
672
- mockIpcResult = {
673
- ok: true,
674
- result: { content: "Updated" },
675
- };
676
-
677
- const { exitCode } = await runCommand([
678
- "task",
679
- "queue",
680
- "update",
681
- "--work-item-id",
682
- "wi-1",
683
- "--status",
684
- "done",
685
- ]);
686
-
687
- expect(exitCode).toBe(0);
688
- expect(lastIpcCall).toBeDefined();
689
- expect(lastIpcCall!.method).toBe("task_queue_update");
690
- expect(lastIpcCall!.params!.body!.work_item_id).toBe("wi-1");
691
- expect(lastIpcCall!.params!.body!.status).toBe("done");
692
- });
693
-
694
- test("passes --priority as priority_tier", async () => {
695
- mockIpcResult = {
696
- ok: true,
697
- result: { content: "Updated" },
698
- };
699
-
700
- const { exitCode } = await runCommand([
701
- "task",
702
- "queue",
703
- "update",
704
- "--work-item-id",
705
- "wi-2",
706
- "--priority",
707
- "1",
708
- ]);
709
-
710
- expect(exitCode).toBe(0);
711
- expect(lastIpcCall!.params!.body!.priority_tier).toBe(1);
712
- });
713
-
714
- test("--json outputs { ok: true, result: ... } on success", async () => {
715
- mockIpcResult = {
716
- ok: true,
717
- result: { content: "Updated" },
718
- };
719
-
720
- const { exitCode, stdout } = await runCommand([
721
- "task",
722
- "queue",
723
- "update",
724
- "--work-item-id",
725
- "wi-1",
726
- "--status",
727
- "done",
728
- "--json",
729
- ]);
730
-
731
- expect(exitCode).toBe(0);
732
- const parsed = JSON.parse(stdout);
733
- expect(parsed).toEqual({ ok: true, result: { content: "Updated" } });
734
- });
735
-
736
- test("IPC failure sets exitCode 1", async () => {
737
- mockIpcResult = { ok: false, error: "Not found" };
738
-
739
- const { exitCode } = await runCommand([
740
- "task",
741
- "queue",
742
- "update",
743
- "--work-item-id",
744
- "wi-1",
745
- "--status",
746
- "done",
747
- ]);
748
-
749
- expect(exitCode).toBe(1);
750
- });
751
- });
752
-
753
- // ---------------------------------------------------------------------------
754
- // task queue remove
755
- // ---------------------------------------------------------------------------
756
-
757
- describe("task queue remove", () => {
758
- test("passes --title filter", async () => {
759
- mockIpcResult = {
760
- ok: true,
761
- result: { content: "Removed" },
762
- };
763
-
764
- const { exitCode } = await runCommand([
765
- "task",
766
- "queue",
767
- "remove",
768
- "--title",
769
- "Fix bug",
770
- ]);
771
-
772
- expect(exitCode).toBe(0);
773
- expect(lastIpcCall).toBeDefined();
774
- expect(lastIpcCall!.method).toBe("task_queue_remove");
775
- expect(lastIpcCall!.params!.body!.title).toBe("Fix bug");
776
- });
777
-
778
- test("passes --work-item-id", async () => {
779
- mockIpcResult = {
780
- ok: true,
781
- result: { content: "Removed" },
782
- };
783
-
784
- const { exitCode } = await runCommand([
785
- "task",
786
- "queue",
787
- "remove",
788
- "--work-item-id",
789
- "wi-abc",
790
- ]);
791
-
792
- expect(exitCode).toBe(0);
793
- expect(lastIpcCall!.params!.body!.work_item_id).toBe("wi-abc");
794
- });
795
-
796
- test("--json outputs { ok: true, result: ... } on success", async () => {
797
- mockIpcResult = {
798
- ok: true,
799
- result: { content: "Removed 1 item" },
800
- };
801
-
802
- const { exitCode, stdout } = await runCommand([
803
- "task",
804
- "queue",
805
- "remove",
806
- "--title",
807
- "Fix bug",
808
- "--json",
809
- ]);
810
-
811
- expect(exitCode).toBe(0);
812
- const parsed = JSON.parse(stdout);
813
- expect(parsed).toEqual({
814
- ok: true,
815
- result: { content: "Removed 1 item" },
816
- });
817
- });
818
-
819
- test("IPC failure sets exitCode 1", async () => {
820
- mockIpcResult = { ok: false, error: "Queue error" };
821
-
822
- const { exitCode } = await runCommand([
823
- "task",
824
- "queue",
825
- "remove",
826
- "--title",
827
- "Fix bug",
828
- ]);
829
-
830
- expect(exitCode).toBe(1);
831
- });
832
- });
833
-
834
- // ---------------------------------------------------------------------------
835
- // task queue run
836
- // ---------------------------------------------------------------------------
837
-
838
- describe("task queue run", () => {
839
- test("calls task/queue/run with --work-item-id", async () => {
840
- mockIpcResult = {
841
- ok: true,
842
- result: { content: "Running" },
843
- };
844
-
845
- const { exitCode } = await runCommand([
846
- "task",
847
- "queue",
848
- "run",
849
- "--work-item-id",
850
- "wi-1",
851
- ]);
852
-
853
- expect(exitCode).toBe(0);
854
- expect(lastIpcCall).toBeDefined();
855
- expect(lastIpcCall!.method).toBe("task_queue_run");
856
- expect(lastIpcCall!.params!.body!.work_item_id).toBe("wi-1");
857
- });
858
-
859
- test("calls task/queue/run with no params (next eligible)", async () => {
860
- mockIpcResult = {
861
- ok: true,
862
- result: { content: "Running next item" },
863
- };
864
-
865
- const { exitCode } = await runCommand(["task", "queue", "run"]);
866
-
867
- expect(exitCode).toBe(0);
868
- expect(lastIpcCall!.method).toBe("task_queue_run");
869
- expect(lastIpcCall!.params!.body).toEqual({});
870
- });
871
-
872
- test("--json outputs { ok: true, result: ... } on success", async () => {
873
- mockIpcResult = {
874
- ok: true,
875
- result: { content: "Running" },
876
- };
877
-
878
- const { exitCode, stdout } = await runCommand([
879
- "task",
880
- "queue",
881
- "run",
882
- "--work-item-id",
883
- "wi-1",
884
- "--json",
885
- ]);
886
-
887
- expect(exitCode).toBe(0);
888
- const parsed = JSON.parse(stdout);
889
- expect(parsed).toEqual({ ok: true, result: { content: "Running" } });
890
- });
891
-
892
- test("IPC failure sets exitCode 1", async () => {
893
- mockIpcResult = { ok: false, error: "No items available" };
894
-
895
- const { exitCode } = await runCommand(["task", "queue", "run"]);
896
-
897
- expect(exitCode).toBe(1);
898
- });
899
-
900
- test("--json outputs error on IPC failure", async () => {
901
- mockIpcResult = { ok: false, error: "No items available" };
902
-
903
- const { exitCode, stdout } = await runCommand([
904
- "task",
905
- "queue",
906
- "run",
907
- "--json",
908
- ]);
909
-
910
- expect(exitCode).toBe(1);
911
- const parsed = JSON.parse(stdout);
912
- expect(parsed).toEqual({ ok: false, error: "No items available" });
913
- });
914
- });