@vellumai/assistant 0.11.4-staging.2 → 0.11.4-staging.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.
Files changed (189) hide show
  1. package/AGENTS.md +8 -2
  2. package/ARCHITECTURE.md +2 -0
  3. package/docs/architecture/memory.md +15 -0
  4. package/docs/browser-use-architecture-phase2.md +128 -56
  5. package/docs/flux-turn-detection-spike.md +11 -6
  6. package/knip.json +3 -0
  7. package/openapi.yaml +136 -76
  8. package/package.json +1 -1
  9. package/scripts/write-plugin-api-shim.ts +10 -0
  10. package/src/__tests__/app-control-flow.test.ts +1 -0
  11. package/src/__tests__/approval-routes-http.test.ts +2 -2
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  13. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  14. package/src/__tests__/compaction-events.test.ts +8 -10
  15. package/src/__tests__/conversation-agent-loop.test.ts +4 -1
  16. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  17. package/src/__tests__/conversation-notifiers-provenance.test.ts +1 -1
  18. package/src/__tests__/conversation-queue.test.ts +39 -62
  19. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  20. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  21. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  22. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  23. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  24. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  25. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  26. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  27. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  28. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  29. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  30. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  31. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  32. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  35. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  36. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  37. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  38. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  39. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  40. package/src/__tests__/document-sync-tags.test.ts +0 -75
  41. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  42. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  43. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  44. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  45. package/src/__tests__/media-generate-image.test.ts +131 -21
  46. package/src/__tests__/memory-retrieval-hook.test.ts +94 -2
  47. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  48. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -3
  49. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  50. package/src/__tests__/qdrant-manager.test.ts +14 -1
  51. package/src/__tests__/run-due-schedules.test.ts +21 -0
  52. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  53. package/src/__tests__/schedule-routes.test.ts +23 -0
  54. package/src/__tests__/schedule-store.test.ts +17 -0
  55. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  56. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  57. package/src/__tests__/starter-task-flow.test.ts +5 -4
  58. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  59. package/src/__tests__/subagent-spawn-and-await.test.ts +4 -7
  60. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  61. package/src/__tests__/subagent-tools.test.ts +81 -101
  62. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  63. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  64. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  65. package/src/__tests__/ui-voice-picker-surface.test.ts +1 -1
  66. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  67. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  68. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  69. package/src/acp/session-manager.ts +8 -1
  70. package/src/api/surfaces.ts +5 -0
  71. package/src/calls/__tests__/voice-session-bridge.test.ts +21 -10
  72. package/src/calls/__tests__/voice-triage-escalate.test.ts +8 -0
  73. package/src/calls/voice-session-bridge.ts +44 -25
  74. package/src/calls/voice-triage-escalate.ts +1 -0
  75. package/src/cli/bundled-modules.ts +29 -0
  76. package/src/cli/commands/db/repair.ts +4 -8
  77. package/src/cli/commands/domain.ts +6 -3
  78. package/src/cli/commands/email.ts +6 -3
  79. package/src/cli/commands/keys.ts +8 -3
  80. package/src/cli/commands/plugins.ts +85 -36
  81. package/src/cli/commands/schedules.ts +35 -1
  82. package/src/cli/lib/bundled-marketplace.json +1 -1
  83. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  84. package/src/config/assistant-feature-flags.ts +36 -15
  85. package/src/config/balanced-model-experiment.ts +35 -0
  86. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  87. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  88. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  89. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  90. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  91. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  92. package/src/config/call-site-defaults.ts +7 -0
  93. package/src/config/default-profile-catalog.ts +96 -4
  94. package/src/config/feature-flag-registry.json +11 -11
  95. package/src/config/skills.ts +9 -2
  96. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  97. package/src/daemon/conversation-agent-loop.ts +14 -13
  98. package/src/daemon/conversation-notifiers.ts +11 -9
  99. package/src/daemon/conversation-process.ts +0 -27
  100. package/src/daemon/conversation-store.ts +4 -4
  101. package/src/daemon/conversation-surfaces.ts +27 -13
  102. package/src/daemon/conversation-tool-setup.ts +2 -4
  103. package/src/daemon/conversation.ts +81 -44
  104. package/src/daemon/doordash-steps.ts +2 -2
  105. package/src/daemon/lifecycle.ts +14 -1
  106. package/src/daemon/process-message.ts +0 -13
  107. package/src/daemon/windows-compiled-entry.ts +4 -0
  108. package/src/documents/document-store.ts +5 -235
  109. package/src/hooks/types.ts +5 -0
  110. package/src/ipc/gateway-flag-listener.ts +17 -3
  111. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +118 -0
  112. package/src/live-voice/live-voice-manager.ts +16 -3
  113. package/src/live-voice/live-voice-session.ts +63 -9
  114. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  115. package/src/monitoring/control.ts +1 -0
  116. package/src/monitoring/db-integrity-sample.ts +4 -5
  117. package/src/permissions/prompter.ts +1 -5
  118. package/src/persistence/conversation-queries.ts +66 -16
  119. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  120. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  121. package/src/persistence/schema/documents.ts +4 -4
  122. package/src/plugin-api/constants.ts +8 -0
  123. package/src/plugin-api/index.ts +5 -1
  124. package/src/plugin-api/webhook-url.ts +13 -11
  125. package/src/plugins/defaults/main.ts +6 -7
  126. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  127. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  128. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  129. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  130. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  131. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  132. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  133. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  134. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  135. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  136. package/src/plugins/mtime-cache.ts +17 -0
  137. package/src/prompts/templates/system-sections.ts +0 -7
  138. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  139. package/src/providers/__tests__/retry-callsite.test.ts +20 -0
  140. package/src/providers/openai/chat-completions-provider.ts +11 -1
  141. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +11 -6
  142. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +9 -49
  143. package/src/routes/control.ts +1 -0
  144. package/src/routes/route-host-client.ts +1 -0
  145. package/src/runtime/AGENTS.md +16 -17
  146. package/src/runtime/agent-wake.ts +15 -12
  147. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +170 -1
  148. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  149. package/src/runtime/routes/conversation-list-routes.ts +54 -22
  150. package/src/runtime/routes/conversation-management-routes.ts +2 -3
  151. package/src/runtime/routes/conversation-routes.ts +11 -13
  152. package/src/runtime/routes/documents-routes.ts +3 -222
  153. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  154. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  155. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  156. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  157. package/src/runtime/routes/schedule-routes.ts +94 -7
  158. package/src/runtime/routes/workspace-routes.ts +0 -9
  159. package/src/runtime/routes/workspace-utils.ts +3 -13
  160. package/src/runtime/services/conversation-serializer.ts +7 -2
  161. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  162. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  163. package/src/schedule/plugin-schedule-availability.ts +58 -0
  164. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  165. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  166. package/src/schedule/schedule-store.ts +5 -1
  167. package/src/schedule/scheduler.ts +9 -4
  168. package/src/schedule/worker-control.ts +1 -0
  169. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  170. package/src/subagent/consult-context.ts +11 -11
  171. package/src/subagent/consult-prompt.ts +26 -35
  172. package/src/subagent/manager.ts +20 -37
  173. package/src/subagent/notify.ts +7 -1
  174. package/src/subagent/types.ts +8 -6
  175. package/src/tools/acp/spawn.ts +6 -4
  176. package/src/tools/skills/scaffold-managed.ts +25 -7
  177. package/src/tools/subagent/spawn.ts +28 -88
  178. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  179. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  180. package/src/util/logger.ts +16 -0
  181. package/src/util/worker-process.ts +37 -4
  182. package/src/windows-compiled-cli.ts +32 -0
  183. package/src/windows-compiled-entry.ts +4 -0
  184. package/src/windows-compiled-logger.ts +6 -0
  185. package/src/windows-compiled-worker-entry.ts +29 -0
  186. package/src/__tests__/document-workspace-file.test.ts +0 -467
  187. package/src/daemon/interactive-turn-sender.ts +0 -59
  188. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  189. package/src/subagent/consult-transcript.ts +0 -90
@@ -95,9 +95,16 @@ function catalogSeam(...entries: { id: string; source: SkillSource }[]) {
95
95
 
96
96
  import { loadSkillCatalog } from "../config/skills.js";
97
97
  import { readInstallMeta, writeInstallMeta } from "../skills/install-meta.js";
98
+ import { validateInputAgainstSchema } from "../skills/validate-input.js";
98
99
  import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
99
100
  import type { ToolContext } from "../tools/types.js";
100
101
 
102
+ /**
103
+ * scaffold_managed_skill requires activation hints. Tests that are not about
104
+ * hints pass this fixed set so the input they exercise stays the subject.
105
+ */
106
+ const HINTS = ["user asks to run the procedure under test"];
107
+
101
108
  function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
102
109
  return {
103
110
  workingDir: "/tmp",
@@ -118,6 +125,27 @@ function installMetaFor(skillId: string) {
118
125
  return readInstallMeta(join(TEST_DIR, "skills", skillId));
119
126
  }
120
127
 
128
+ /** The scaffold_managed_skill entry as declared in the skill-management manifest. */
129
+ function readScaffoldToolEntry(): {
130
+ input_schema: {
131
+ properties: Record<string, unknown>;
132
+ required: string[];
133
+ };
134
+ } {
135
+ const tools = JSON.parse(
136
+ readFileSync(
137
+ join(
138
+ import.meta.dirname,
139
+ "../config/bundled-skills/skill-management/TOOLS.json",
140
+ ),
141
+ "utf-8",
142
+ ),
143
+ );
144
+ return tools.tools.find(
145
+ (tool: { name: string }) => tool.name === "scaffold_managed_skill",
146
+ );
147
+ }
148
+
121
149
  beforeEach(() => {
122
150
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
123
151
  mockRefreshSkillCapabilityMemories.mockClear();
@@ -133,18 +161,7 @@ afterEach(() => {
133
161
 
134
162
  describe("scaffold_managed_skill tool", () => {
135
163
  test("keeps legacy index control as a deprecated no-op schema field", () => {
136
- const tools = JSON.parse(
137
- readFileSync(
138
- join(
139
- import.meta.dirname,
140
- "../config/bundled-skills/skill-management/TOOLS.json",
141
- ),
142
- "utf-8",
143
- ),
144
- );
145
- const scaffoldTool = tools.tools.find(
146
- (tool: { name: string }) => tool.name === "scaffold_managed_skill",
147
- );
164
+ const scaffoldTool = readScaffoldToolEntry();
148
165
 
149
166
  expect(scaffoldTool).toBeDefined();
150
167
  expect(scaffoldTool.input_schema.properties.add_to_index).toEqual({
@@ -154,6 +171,29 @@ describe("scaffold_managed_skill tool", () => {
154
171
  });
155
172
  });
156
173
 
174
+ test("the registered tool's schema rejects an omitted activation_hints before the executor runs", () => {
175
+ // A call through the registered tool is validated against TOOLS.json
176
+ // first (skill-tool-factory), so the requirement has to live in the
177
+ // schema's `required` list, not only in the executor.
178
+ const scaffoldTool = readScaffoldToolEntry();
179
+ expect(scaffoldTool.input_schema.required).toContain("activation_hints");
180
+
181
+ const result = validateInputAgainstSchema(
182
+ "scaffold_managed_skill",
183
+ {
184
+ skill_id: "s",
185
+ name: "N",
186
+ description: "D",
187
+ body_markdown: "B",
188
+ },
189
+ scaffoldTool.input_schema,
190
+ );
191
+ expect(result.ok).toBe(false);
192
+ if (!result.ok) {
193
+ expect(result.errors).toEqual(["activation_hints is required"]);
194
+ }
195
+ });
196
+
157
197
  test("creates a valid skill discovered from its SKILL.md directory", async () => {
158
198
  const result = await executeScaffoldManagedSkill(
159
199
  {
@@ -161,6 +201,7 @@ describe("scaffold_managed_skill tool", () => {
161
201
  name: "Test Skill",
162
202
  description: "A test skill",
163
203
  body_markdown: "Do the thing.",
204
+ activation_hints: HINTS,
164
205
  },
165
206
  makeContext(),
166
207
  );
@@ -202,6 +243,7 @@ describe("scaffold_managed_skill tool", () => {
202
243
  name: "Legacy Input",
203
244
  description: "A test skill",
204
245
  body_markdown: "Do the thing.",
246
+ activation_hints: HINTS,
205
247
  add_to_index: true,
206
248
  },
207
249
  makeContext(),
@@ -222,6 +264,7 @@ describe("scaffold_managed_skill tool", () => {
222
264
  name: "Original",
223
265
  description: "First",
224
266
  body_markdown: "V1.",
267
+ activation_hints: HINTS,
225
268
  },
226
269
  makeContext(),
227
270
  );
@@ -232,6 +275,7 @@ describe("scaffold_managed_skill tool", () => {
232
275
  name: "Duplicate",
233
276
  description: "Second",
234
277
  body_markdown: "V2.",
278
+ activation_hints: HINTS,
235
279
  },
236
280
  makeContext(),
237
281
  );
@@ -244,6 +288,7 @@ describe("scaffold_managed_skill tool", () => {
244
288
  name: "Overwritten",
245
289
  description: "Third",
246
290
  body_markdown: "V3.",
291
+ activation_hints: HINTS,
247
292
  overwrite: true,
248
293
  },
249
294
  makeContext(),
@@ -278,6 +323,7 @@ describe("scaffold_managed_skill tool", () => {
278
323
  name: 'Test\ninjected_field: "evil"',
279
324
  description: "Desc\rwith\r\ncarriage returns",
280
325
  body_markdown: "Body content.",
326
+ activation_hints: HINTS,
281
327
  emoji: "🔥\nextra: true",
282
328
  },
283
329
  makeContext(),
@@ -306,6 +352,7 @@ describe("scaffold_managed_skill tool", () => {
306
352
  name: "Parent",
307
353
  description: "Has children",
308
354
  body_markdown: "Parent body.",
355
+ activation_hints: HINTS,
309
356
  includes: ["child-a", "child-b"],
310
357
  },
311
358
  makeContext(),
@@ -326,6 +373,7 @@ describe("scaffold_managed_skill tool", () => {
326
373
  name: "Normalized",
327
374
  description: "Tests normalization",
328
375
  body_markdown: "Body.",
376
+ activation_hints: HINTS,
329
377
  includes: [" child-a ", "child-b", "child-a"],
330
378
  },
331
379
  makeContext(),
@@ -346,6 +394,7 @@ describe("scaffold_managed_skill tool", () => {
346
394
  name: "Bad",
347
395
  description: "Has non-string",
348
396
  body_markdown: "Body.",
397
+ activation_hints: HINTS,
349
398
  includes: ["child-a", 42],
350
399
  },
351
400
  makeContext(),
@@ -362,6 +411,7 @@ describe("scaffold_managed_skill tool", () => {
362
411
  name: "Empty",
363
412
  description: "Has empty string",
364
413
  body_markdown: "Body.",
414
+ activation_hints: HINTS,
365
415
  includes: ["", "child-a"],
366
416
  },
367
417
  makeContext(),
@@ -378,6 +428,7 @@ describe("scaffold_managed_skill tool", () => {
378
428
  name: "Whitespace",
379
429
  description: "Has whitespace-only",
380
430
  body_markdown: "Body.",
431
+ activation_hints: HINTS,
381
432
  includes: ["child-a", " "],
382
433
  },
383
434
  makeContext(),
@@ -394,6 +445,7 @@ describe("scaffold_managed_skill tool", () => {
394
445
  name: "Solo",
395
446
  description: "No children",
396
447
  body_markdown: "Body.",
448
+ activation_hints: HINTS,
397
449
  },
398
450
  makeContext(),
399
451
  );
@@ -521,26 +573,93 @@ describe("scaffold_managed_skill tool", () => {
521
573
  expect(result.content).toContain("must be an array");
522
574
  });
523
575
 
524
- test("omits activation-hints / avoid-when when not provided", async () => {
576
+ test("omits avoid-when when not provided", async () => {
525
577
  const result = await executeScaffoldManagedSkill(
526
578
  {
527
- skill_id: "no-hints",
528
- name: "No Hints",
529
- description: "No triggers",
579
+ skill_id: "no-avoid",
580
+ name: "No Avoid",
581
+ description: "No avoid clause",
530
582
  body_markdown: "Body.",
583
+ activation_hints: HINTS,
531
584
  },
532
585
  makeContext(),
533
586
  );
534
587
 
535
588
  expect(result.isError).toBe(false);
536
589
  const content = readFileSync(
537
- join(TEST_DIR, "skills", "no-hints", "SKILL.md"),
590
+ join(TEST_DIR, "skills", "no-avoid", "SKILL.md"),
538
591
  "utf-8",
539
592
  );
540
- expect(content).not.toContain("activation-hints");
593
+ expect(content).toContain("activation-hints");
541
594
  expect(content).not.toContain("avoid-when");
542
595
  });
543
596
 
597
+ test("rejects a scaffold with no activation_hints and writes nothing, for user and retrospective origins alike", async () => {
598
+ // Omitted and empty are the same omission: both leave the skill with no
599
+ // "Use when" retrieval text.
600
+ const inputs = [
601
+ { skill_id: "no-hints-omitted" },
602
+ { skill_id: "no-hints-empty", activation_hints: [] },
603
+ ];
604
+ for (const context of [makeContext(), makeRetrospectiveContext()]) {
605
+ for (const extra of inputs) {
606
+ const result = await executeScaffoldManagedSkill(
607
+ {
608
+ name: "No Hints",
609
+ description: "No triggers",
610
+ body_markdown: "Body.",
611
+ ...extra,
612
+ },
613
+ context,
614
+ );
615
+ expect(result.isError).toBe(true);
616
+ expect(result.content).toContain("activation_hints is required");
617
+ // A rejected create leaves no half-written skill behind.
618
+ expect(
619
+ existsSync(join(TEST_DIR, "skills", extra.skill_id, "SKILL.md")),
620
+ ).toBe(false);
621
+ }
622
+ }
623
+ });
624
+
625
+ test("an overwrite that omits activation_hints is rejected and leaves the skill untouched", async () => {
626
+ const hints = ["user asks to deploy staging", "user asks to cut a release"];
627
+ await executeScaffoldManagedSkill(
628
+ {
629
+ skill_id: "keep-hints",
630
+ name: "Keep Hints",
631
+ description: "V1",
632
+ body_markdown: "V1 body.",
633
+ activation_hints: hints,
634
+ },
635
+ makeContext(),
636
+ );
637
+
638
+ const result = await executeScaffoldManagedSkill(
639
+ {
640
+ skill_id: "keep-hints",
641
+ name: "Keep Hints",
642
+ description: "V2",
643
+ body_markdown: "V2 body.",
644
+ overwrite: true,
645
+ },
646
+ makeContext(),
647
+ );
648
+
649
+ expect(result.isError).toBe(true);
650
+ expect(result.content).toContain("activation_hints is required");
651
+ // Scaffolding rewrites the whole SKILL.md, so the rejected overwrite must
652
+ // not have reached disk: body and hints are still V1's.
653
+ const content = readFileSync(
654
+ join(TEST_DIR, "skills", "keep-hints", "SKILL.md"),
655
+ "utf-8",
656
+ );
657
+ expect(content).toContain("V1 body.");
658
+ expect(content).not.toContain("V2 body.");
659
+ const skill = loadSkillCatalog().find((s) => s.id === "keep-hints");
660
+ expect(skill!.activationHints).toEqual(hints);
661
+ });
662
+
544
663
  test("passes category through to the written skill, lowercased and trimmed", async () => {
545
664
  const result = await executeScaffoldManagedSkill(
546
665
  {
@@ -548,6 +667,7 @@ describe("scaffold_managed_skill tool", () => {
548
667
  name: "Categorized",
549
668
  description: "Has a category",
550
669
  body_markdown: "Body.",
670
+ activation_hints: HINTS,
551
671
  category: " Development ",
552
672
  },
553
673
  makeContext(),
@@ -571,6 +691,7 @@ describe("scaffold_managed_skill tool", () => {
571
691
  name: "Bad Category",
572
692
  description: "Non-string category",
573
693
  body_markdown: "Body.",
694
+ activation_hints: HINTS,
574
695
  category: 42,
575
696
  },
576
697
  makeContext(),
@@ -587,6 +708,7 @@ describe("scaffold_managed_skill tool", () => {
587
708
  name: "No Category",
588
709
  description: "Uncategorized",
589
710
  body_markdown: "Body.",
711
+ activation_hints: HINTS,
590
712
  },
591
713
  makeContext(),
592
714
  );
@@ -606,6 +728,7 @@ describe("scaffold_managed_skill tool", () => {
606
728
  name: "Bad",
607
729
  description: "Bad",
608
730
  body_markdown: "Bad.",
731
+ activation_hints: HINTS,
609
732
  },
610
733
  makeContext(),
611
734
  );
@@ -621,6 +744,7 @@ describe("scaffold_managed_skill tool", () => {
621
744
  name: "Files Skill",
622
745
  description: "Has companion files",
623
746
  body_markdown: "See references/failure-modes.md.",
747
+ activation_hints: HINTS,
624
748
  files: [
625
749
  {
626
750
  path: "references/failure-modes.md",
@@ -654,6 +778,7 @@ describe("scaffold_managed_skill tool", () => {
654
778
  name: "Copy From Skill",
655
779
  description: "Bundles a proven script",
656
780
  body_markdown: "Run scripts/proven-script.py.",
781
+ activation_hints: HINTS,
657
782
  files: [{ path: "scripts/proven-script.py", copy_from: sourcePath }],
658
783
  },
659
784
  makeContext(),
@@ -684,6 +809,7 @@ describe("scaffold_managed_skill tool", () => {
684
809
  name: "Both",
685
810
  description: "Both content and copy_from",
686
811
  body_markdown: "Body.",
812
+ activation_hints: HINTS,
687
813
  files: [
688
814
  { path: "scripts/dupe.py", content: "x", copy_from: sourcePath },
689
815
  ],
@@ -703,6 +829,7 @@ describe("scaffold_managed_skill tool", () => {
703
829
  name: "Bad Type",
704
830
  description: "copy_from wrong type",
705
831
  body_markdown: "Body.",
832
+ activation_hints: HINTS,
706
833
  files: [{ path: "scripts/x.py", copy_from: 42 }],
707
834
  },
708
835
  makeContext(),
@@ -719,6 +846,7 @@ describe("scaffold_managed_skill tool", () => {
719
846
  name: "Traversal",
720
847
  description: "Bad path",
721
848
  body_markdown: "Body.",
849
+ activation_hints: HINTS,
722
850
  files: [{ path: "../escape.md", content: "owned" }],
723
851
  },
724
852
  makeContext(),
@@ -750,6 +878,7 @@ describe("scaffold_managed_skill tool", () => {
750
878
  name: "Bad",
751
879
  description: "Bad files",
752
880
  body_markdown: "Body.",
881
+ activation_hints: HINTS,
753
882
  files,
754
883
  },
755
884
  makeContext(),
@@ -765,6 +894,7 @@ describe("scaffold_managed_skill tool", () => {
765
894
  name: "E2E Child",
766
895
  description: "Child for e2e test",
767
896
  body_markdown: "Child instructions.",
897
+ activation_hints: HINTS,
768
898
  },
769
899
  makeContext(),
770
900
  );
@@ -776,6 +906,7 @@ describe("scaffold_managed_skill tool", () => {
776
906
  name: "E2E Parent",
777
907
  description: "Parent with includes",
778
908
  body_markdown: "Parent instructions.",
909
+ activation_hints: HINTS,
779
910
  includes: ["e2e-child"],
780
911
  },
781
912
  makeContext(),
@@ -806,6 +937,7 @@ describe("scaffold_managed_skill tool", () => {
806
937
  name: "Retro Skill",
807
938
  description: "Authored by a retrospective pass",
808
939
  body_markdown: "Do the procedure.",
940
+ activation_hints: HINTS,
809
941
  },
810
942
  makeRetrospectiveContext(),
811
943
  );
@@ -830,6 +962,7 @@ describe("scaffold_managed_skill tool", () => {
830
962
  name: "User Skill",
831
963
  description: "Authored interactively",
832
964
  body_markdown: "Do the thing.",
965
+ activation_hints: HINTS,
833
966
  },
834
967
  makeContext(),
835
968
  );
@@ -846,6 +979,7 @@ describe("scaffold_managed_skill tool", () => {
846
979
  name: "Protected",
847
980
  description: "User authored",
848
981
  body_markdown: "Original body.",
982
+ activation_hints: HINTS,
849
983
  },
850
984
  makeContext(),
851
985
  );
@@ -858,6 +992,7 @@ describe("scaffold_managed_skill tool", () => {
858
992
  name: "Protected",
859
993
  description: "Rewritten by retrospective",
860
994
  body_markdown: "Rewritten body.",
995
+ activation_hints: HINTS,
861
996
  overwrite: true,
862
997
  },
863
998
  makeRetrospectiveContext(),
@@ -878,6 +1013,7 @@ describe("scaffold_managed_skill tool", () => {
878
1013
  name: "Protected Files",
879
1014
  description: "User authored",
880
1015
  body_markdown: "Original body.",
1016
+ activation_hints: HINTS,
881
1017
  },
882
1018
  makeContext(),
883
1019
  );
@@ -888,6 +1024,7 @@ describe("scaffold_managed_skill tool", () => {
888
1024
  name: "Protected Files",
889
1025
  description: "Refine attempt",
890
1026
  body_markdown: "Original body.",
1027
+ activation_hints: HINTS,
891
1028
  overwrite: true,
892
1029
  files: [{ path: "references/notes.md", content: "gotchas" }],
893
1030
  },
@@ -912,6 +1049,7 @@ describe("scaffold_managed_skill tool", () => {
912
1049
  name: "Untagged",
913
1050
  description: "No author",
914
1051
  body_markdown: "Original body.",
1052
+ activation_hints: HINTS,
915
1053
  },
916
1054
  makeContext(),
917
1055
  );
@@ -929,6 +1067,7 @@ describe("scaffold_managed_skill tool", () => {
929
1067
  name: "Untagged",
930
1068
  description: "Rewritten by retrospective",
931
1069
  body_markdown: "Rewritten body.",
1070
+ activation_hints: HINTS,
932
1071
  overwrite: true,
933
1072
  },
934
1073
  makeRetrospectiveContext(),
@@ -949,6 +1088,7 @@ describe("scaffold_managed_skill tool", () => {
949
1088
  name: "Assistant Owned",
950
1089
  description: "First pass",
951
1090
  body_markdown: "V1 procedure.",
1091
+ activation_hints: HINTS,
952
1092
  },
953
1093
  makeRetrospectiveContext(),
954
1094
  );
@@ -960,6 +1100,7 @@ describe("scaffold_managed_skill tool", () => {
960
1100
  name: "Assistant Owned",
961
1101
  description: "Refined",
962
1102
  body_markdown: "V2 procedure.",
1103
+ activation_hints: HINTS,
963
1104
  overwrite: true,
964
1105
  files: [{ path: "references/failure-modes.md", content: "gotchas" }],
965
1106
  },
@@ -998,6 +1139,7 @@ describe("scaffold_managed_skill tool", () => {
998
1139
  name: "Lineage Skill",
999
1140
  description: "Distilled from an observed procedure",
1000
1141
  body_markdown: "Do the procedure.",
1142
+ activation_hints: HINTS,
1001
1143
  },
1002
1144
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1003
1145
  {
@@ -1023,6 +1165,7 @@ describe("scaffold_managed_skill tool", () => {
1023
1165
  name: "User No Lineage",
1024
1166
  description: "Authored interactively",
1025
1167
  body_markdown: "Do the thing.",
1168
+ activation_hints: HINTS,
1026
1169
  },
1027
1170
  makeContext(),
1028
1171
  { getConversation: lookup },
@@ -1054,6 +1197,7 @@ describe("scaffold_managed_skill tool", () => {
1054
1197
  name: "Orphan Lineage",
1055
1198
  description: "Parent not resolvable",
1056
1199
  body_markdown: "Do the procedure.",
1200
+ activation_hints: HINTS,
1057
1201
  },
1058
1202
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1059
1203
  { getConversation: lookup },
@@ -1075,6 +1219,7 @@ describe("scaffold_managed_skill tool", () => {
1075
1219
  name: "Lookup Throws",
1076
1220
  description: "DB unavailable during lineage resolution",
1077
1221
  body_markdown: "Do the procedure.",
1222
+ activation_hints: HINTS,
1078
1223
  },
1079
1224
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1080
1225
  {
@@ -1103,6 +1248,7 @@ describe("scaffold_managed_skill tool", () => {
1103
1248
  name: "No Conversation",
1104
1249
  description: "Context carries no conversation id",
1105
1250
  body_markdown: "Do the procedure.",
1251
+ activation_hints: HINTS,
1106
1252
  },
1107
1253
  context,
1108
1254
  { getConversation: lookup },
@@ -1125,6 +1271,7 @@ describe("scaffold_managed_skill tool", () => {
1125
1271
  name: "Deep Research",
1126
1272
  description: "Shadow attempt",
1127
1273
  body_markdown: "Body.",
1274
+ activation_hints: HINTS,
1128
1275
  },
1129
1276
  makeRetrospectiveContext(),
1130
1277
  catalogSeam({ id: "deep-research", source: "bundled" }),
@@ -1146,6 +1293,7 @@ describe("scaffold_managed_skill tool", () => {
1146
1293
  name: "Blitz",
1147
1294
  description: "Shadow-overwrite attempt",
1148
1295
  body_markdown: "Body.",
1296
+ activation_hints: HINTS,
1149
1297
  overwrite: true,
1150
1298
  },
1151
1299
  makeRetrospectiveContext(),
@@ -1165,6 +1313,7 @@ describe("scaffold_managed_skill tool", () => {
1165
1313
  name: "Covered",
1166
1314
  description: "Shadow attempt",
1167
1315
  body_markdown: "Body.",
1316
+ activation_hints: HINTS,
1168
1317
  },
1169
1318
  makeRetrospectiveContext(),
1170
1319
  catalogSeam({ id: "covered-proc", source }),
@@ -1184,6 +1333,7 @@ describe("scaffold_managed_skill tool", () => {
1184
1333
  name: "No Meta",
1185
1334
  description: "Lost provenance",
1186
1335
  body_markdown: "Original body.",
1336
+ activation_hints: HINTS,
1187
1337
  },
1188
1338
  makeContext(),
1189
1339
  );
@@ -1197,6 +1347,7 @@ describe("scaffold_managed_skill tool", () => {
1197
1347
  name: "No Meta",
1198
1348
  description: "Rewrite attempt",
1199
1349
  body_markdown: "Rewritten body.",
1350
+ activation_hints: HINTS,
1200
1351
  overwrite: true,
1201
1352
  },
1202
1353
  makeRetrospectiveContext(),
@@ -1217,6 +1368,7 @@ describe("scaffold_managed_skill tool", () => {
1217
1368
  name: "Corrupt Meta",
1218
1369
  description: "Bad provenance",
1219
1370
  body_markdown: "Original body.",
1371
+ activation_hints: HINTS,
1220
1372
  },
1221
1373
  makeContext(),
1222
1374
  );
@@ -1229,6 +1381,7 @@ describe("scaffold_managed_skill tool", () => {
1229
1381
  name: "Corrupt Meta",
1230
1382
  description: "Rewrite attempt",
1231
1383
  body_markdown: "Rewritten body.",
1384
+ activation_hints: HINTS,
1232
1385
  overwrite: true,
1233
1386
  },
1234
1387
  makeRetrospectiveContext(),
@@ -1250,6 +1403,7 @@ describe("scaffold_managed_skill tool", () => {
1250
1403
  name: "Fresh Proc",
1251
1404
  description: "Newly observed procedure",
1252
1405
  body_markdown: "Do the procedure.",
1406
+ activation_hints: HINTS,
1253
1407
  },
1254
1408
  makeRetrospectiveContext(),
1255
1409
  catalogSeam({ id: "something-else", source: "bundled" }),
@@ -1281,6 +1435,7 @@ describe("scaffold_managed_skill tool", () => {
1281
1435
  name: " Card\nSkill ",
1282
1436
  description: " Does\r\ncard things ",
1283
1437
  body_markdown: "Do the procedure.",
1438
+ activation_hints: HINTS,
1284
1439
  emoji: " 🧭 ",
1285
1440
  },
1286
1441
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1313,6 +1468,7 @@ describe("scaffold_managed_skill tool", () => {
1313
1468
  name: "No Emoji",
1314
1469
  description: "Card without an emoji",
1315
1470
  body_markdown: "Do the procedure.",
1471
+ activation_hints: HINTS,
1316
1472
  emoji: " \n ",
1317
1473
  },
1318
1474
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1355,6 +1511,7 @@ describe("scaffold_managed_skill tool", () => {
1355
1511
  name: "No Card",
1356
1512
  description: "Fork parent not resolvable",
1357
1513
  body_markdown: "Do the procedure.",
1514
+ activation_hints: HINTS,
1358
1515
  },
1359
1516
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1360
1517
  { getConversation: lookup },
@@ -1376,6 +1533,7 @@ describe("scaffold_managed_skill tool", () => {
1376
1533
  name: "Refined Skill",
1377
1534
  description: "First pass",
1378
1535
  body_markdown: "V1 procedure.",
1536
+ activation_hints: HINTS,
1379
1537
  },
1380
1538
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1381
1539
  lineageSeam(),
@@ -1390,6 +1548,7 @@ describe("scaffold_managed_skill tool", () => {
1390
1548
  name: "Refined Skill",
1391
1549
  description: "Refined",
1392
1550
  body_markdown: "V2 procedure.",
1551
+ activation_hints: HINTS,
1393
1552
  overwrite: true,
1394
1553
  },
1395
1554
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1407,6 +1566,7 @@ describe("scaffold_managed_skill tool", () => {
1407
1566
  name: "Overwrite Fresh",
1408
1567
  description: "Overwrite flag on a fresh id",
1409
1568
  body_markdown: "Do the procedure.",
1569
+ activation_hints: HINTS,
1410
1570
  overwrite: true,
1411
1571
  },
1412
1572
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1431,6 +1591,7 @@ describe("scaffold_managed_skill tool", () => {
1431
1591
  name: "User No Card",
1432
1592
  description: "Authored interactively",
1433
1593
  body_markdown: "Do the thing.",
1594
+ activation_hints: HINTS,
1434
1595
  },
1435
1596
  makeContext({ conversationId: "retro-run-conv" }),
1436
1597
  lineageSeam(),
@@ -1449,6 +1610,7 @@ describe("scaffold_managed_skill tool", () => {
1449
1610
  name: "Enqueue Throws",
1450
1611
  description: "Jobs DB unavailable at enqueue time",
1451
1612
  body_markdown: "Do the procedure.",
1613
+ activation_hints: HINTS,
1452
1614
  },
1453
1615
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1454
1616
  lineageSeam(),
@@ -1469,6 +1631,7 @@ describe("scaffold_managed_skill tool", () => {
1469
1631
  name: `Skill ${skillId}`,
1470
1632
  description: `Does ${skillId}`,
1471
1633
  body_markdown: "Do the procedure.",
1634
+ activation_hints: HINTS,
1472
1635
  },
1473
1636
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1474
1637
  lineageSeam(),
@@ -1507,6 +1670,7 @@ describe("background skill update notification", () => {
1507
1670
  name: "Weekly Report Export",
1508
1671
  description: `seeded ${id}`,
1509
1672
  body_markdown: body,
1673
+ activation_hints: HINTS,
1510
1674
  },
1511
1675
  makeContext(),
1512
1676
  );
@@ -1536,6 +1700,7 @@ describe("background skill update notification", () => {
1536
1700
  name: "Weekly Report Export",
1537
1701
  description: "export the weekly usage report",
1538
1702
  body_markdown: "1. Refined steps.",
1703
+ activation_hints: HINTS,
1539
1704
  overwrite: true,
1540
1705
  },
1541
1706
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1578,6 +1743,7 @@ describe("background skill update notification", () => {
1578
1743
  name: "Weekly Report Export",
1579
1744
  description: "export the weekly usage report",
1580
1745
  body_markdown: "1. Refined steps.",
1746
+ activation_hints: HINTS,
1581
1747
  overwrite: true,
1582
1748
  },
1583
1749
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
@@ -1597,6 +1763,7 @@ describe("background skill update notification", () => {
1597
1763
  name: "Brand New Skill",
1598
1764
  description: "something never seen before",
1599
1765
  body_markdown: "1. Do it.",
1766
+ activation_hints: HINTS,
1600
1767
  },
1601
1768
  makeRetrospectiveContext({ conversationId: "retro-run-conv" }),
1602
1769
  lineage(),
@@ -1614,6 +1781,7 @@ describe("background skill update notification", () => {
1614
1781
  name: "User Skill",
1615
1782
  description: "asked for",
1616
1783
  body_markdown: "V1.",
1784
+ activation_hints: HINTS,
1617
1785
  },
1618
1786
  makeContext(),
1619
1787
  );
@@ -1625,6 +1793,7 @@ describe("background skill update notification", () => {
1625
1793
  name: "User Skill",
1626
1794
  description: "asked for",
1627
1795
  body_markdown: "V2.",
1796
+ activation_hints: HINTS,
1628
1797
  overwrite: true,
1629
1798
  },
1630
1799
  makeContext(),