@bastani/atomic 0.8.4 → 0.8.5-0

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 (245) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +24 -23
  3. package/dist/builtin/intercom/README.md +5 -5
  4. package/dist/builtin/intercom/index.ts +1 -1
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/intercom/ui/compose.ts +19 -1
  7. package/dist/builtin/intercom/ui/session-list.ts +19 -1
  8. package/dist/builtin/mcp/README.md +3 -3
  9. package/dist/builtin/mcp/commands.ts +1 -1
  10. package/dist/builtin/mcp/host-html-template.ts +1 -1
  11. package/dist/builtin/mcp/mcp-panel.ts +14 -14
  12. package/dist/builtin/mcp/mcp-setup-panel.ts +4 -4
  13. package/dist/builtin/mcp/package.json +1 -1
  14. package/dist/builtin/mcp/tool-result-renderer.ts +1 -1
  15. package/dist/builtin/subagents/README.md +3 -3
  16. package/dist/builtin/subagents/package.json +1 -1
  17. package/dist/builtin/subagents/src/tui/render.ts +1844 -1062
  18. package/dist/builtin/web-access/README.md +1 -1
  19. package/dist/builtin/web-access/curator-page.ts +2 -2
  20. package/dist/builtin/web-access/index.ts +1 -1
  21. package/dist/builtin/web-access/package.json +1 -1
  22. package/dist/builtin/workflows/README.md +34 -7
  23. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +23 -4
  24. package/dist/builtin/workflows/builtin/ralph.ts +1 -1
  25. package/dist/builtin/workflows/package.json +1 -1
  26. package/dist/builtin/workflows/skills/workflow/SKILL.md +75 -16
  27. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +34 -11
  28. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +111 -20
  29. package/dist/builtin/workflows/src/extension/discovery.ts +32 -4
  30. package/dist/builtin/workflows/src/extension/index.ts +347 -63
  31. package/dist/builtin/workflows/src/extension/render-call.ts +3 -1
  32. package/dist/builtin/workflows/src/extension/render-result.ts +7 -0
  33. package/dist/builtin/workflows/src/extension/runtime.ts +4 -2
  34. package/dist/builtin/workflows/src/extension/wiring.ts +32 -8
  35. package/dist/builtin/workflows/src/extension/workflow-schema.ts +36 -14
  36. package/dist/builtin/workflows/src/runs/background/runner.ts +2 -2
  37. package/dist/builtin/workflows/src/runs/background/status.ts +89 -0
  38. package/dist/builtin/workflows/src/runs/foreground/executor.ts +338 -78
  39. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +2 -0
  40. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +55 -7
  41. package/dist/builtin/workflows/src/runs/shared/workflow-runner.ts +146 -10
  42. package/dist/builtin/workflows/src/shared/store.ts +29 -0
  43. package/dist/builtin/workflows/src/shared/types.ts +25 -4
  44. package/dist/builtin/workflows/src/tui/graph-canvas.ts +69 -2
  45. package/dist/builtin/workflows/src/tui/graph-view.ts +97 -182
  46. package/dist/builtin/workflows/src/tui/header.ts +36 -20
  47. package/dist/builtin/workflows/src/tui/inline-form-card.ts +129 -46
  48. package/dist/builtin/workflows/src/tui/inline-form-editor.ts +111 -36
  49. package/dist/builtin/workflows/src/tui/inputs-picker.ts +311 -91
  50. package/dist/builtin/workflows/src/tui/layout.ts +1 -1
  51. package/dist/builtin/workflows/src/tui/node-card.ts +66 -37
  52. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +20 -6
  53. package/dist/builtin/workflows/src/tui/prompt-card.ts +262 -85
  54. package/dist/builtin/workflows/src/tui/run-detail.ts +50 -31
  55. package/dist/builtin/workflows/src/tui/session-confirm.ts +21 -14
  56. package/dist/builtin/workflows/src/tui/session-picker.ts +35 -26
  57. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +531 -960
  58. package/dist/builtin/workflows/src/tui/status-helpers.ts +6 -0
  59. package/dist/builtin/workflows/src/tui/status-list.ts +8 -4
  60. package/dist/builtin/workflows/src/tui/store-widget-installer.ts +7 -2
  61. package/dist/builtin/workflows/src/tui/switcher.ts +55 -25
  62. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +33 -1
  63. package/dist/builtin/workflows/src/tui/workflow-list.ts +10 -6
  64. package/dist/cli/args.d.ts.map +1 -1
  65. package/dist/cli/args.js +1 -1
  66. package/dist/cli/args.js.map +1 -1
  67. package/dist/config.d.ts.map +1 -1
  68. package/dist/config.js +20 -6
  69. package/dist/config.js.map +1 -1
  70. package/dist/core/agent-session-services.d.ts +3 -3
  71. package/dist/core/agent-session-services.d.ts.map +1 -1
  72. package/dist/core/agent-session-services.js.map +1 -1
  73. package/dist/core/agent-session.d.ts +7 -7
  74. package/dist/core/agent-session.d.ts.map +1 -1
  75. package/dist/core/agent-session.js.map +1 -1
  76. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  77. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  78. package/dist/core/compaction/branch-summarization.js.map +1 -1
  79. package/dist/core/compaction/compaction.d.ts +3 -3
  80. package/dist/core/compaction/compaction.d.ts.map +1 -1
  81. package/dist/core/compaction/compaction.js.map +1 -1
  82. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  83. package/dist/core/export-html/tool-renderer.js.map +1 -1
  84. package/dist/core/extensions/loader.d.ts +3 -2
  85. package/dist/core/extensions/loader.d.ts.map +1 -1
  86. package/dist/core/extensions/loader.js +24 -12
  87. package/dist/core/extensions/loader.js.map +1 -1
  88. package/dist/core/extensions/runner.d.ts.map +1 -1
  89. package/dist/core/extensions/runner.js +6 -0
  90. package/dist/core/extensions/runner.js.map +1 -1
  91. package/dist/core/extensions/types.d.ts +28 -17
  92. package/dist/core/extensions/types.d.ts.map +1 -1
  93. package/dist/core/extensions/types.js.map +1 -1
  94. package/dist/core/package-manager.d.ts +1 -0
  95. package/dist/core/package-manager.d.ts.map +1 -1
  96. package/dist/core/package-manager.js +65 -28
  97. package/dist/core/package-manager.js.map +1 -1
  98. package/dist/core/resource-loader.d.ts.map +1 -1
  99. package/dist/core/resource-loader.js +13 -5
  100. package/dist/core/resource-loader.js.map +1 -1
  101. package/dist/core/sdk.d.ts +3 -3
  102. package/dist/core/sdk.d.ts.map +1 -1
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts.map +1 -1
  105. package/dist/core/session-manager.js +1 -1
  106. package/dist/core/session-manager.js.map +1 -1
  107. package/dist/core/settings-manager.d.ts +2 -0
  108. package/dist/core/settings-manager.d.ts.map +1 -1
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/slash-commands.d.ts.map +1 -1
  111. package/dist/core/slash-commands.js +1 -1
  112. package/dist/core/slash-commands.js.map +1 -1
  113. package/dist/core/system-prompt.d.ts.map +1 -1
  114. package/dist/core/system-prompt.js +5 -3
  115. package/dist/core/system-prompt.js.map +1 -1
  116. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +1 -1
  117. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  118. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js +1 -1
  119. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  120. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  121. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  122. package/dist/core/tools/ask-user-question/view/dialog-builder.js +6 -6
  123. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  124. package/dist/core/tools/bash.d.ts.map +1 -1
  125. package/dist/core/tools/bash.js +1 -1
  126. package/dist/core/tools/bash.js.map +1 -1
  127. package/dist/core/tools/find.d.ts.map +1 -1
  128. package/dist/core/tools/find.js +1 -1
  129. package/dist/core/tools/find.js.map +1 -1
  130. package/dist/core/tools/grep.d.ts.map +1 -1
  131. package/dist/core/tools/grep.js +7 -4
  132. package/dist/core/tools/grep.js.map +1 -1
  133. package/dist/core/tools/index.d.ts +3 -2
  134. package/dist/core/tools/index.d.ts.map +1 -1
  135. package/dist/core/tools/index.js.map +1 -1
  136. package/dist/core/tools/ls.d.ts.map +1 -1
  137. package/dist/core/tools/ls.js +3 -2
  138. package/dist/core/tools/ls.js.map +1 -1
  139. package/dist/core/tools/read.d.ts.map +1 -1
  140. package/dist/core/tools/read.js +2 -2
  141. package/dist/core/tools/read.js.map +1 -1
  142. package/dist/core/tools/render-utils.d.ts +2 -1
  143. package/dist/core/tools/render-utils.d.ts.map +1 -1
  144. package/dist/core/tools/render-utils.js.map +1 -1
  145. package/dist/core/tools/todos.d.ts.map +1 -1
  146. package/dist/core/tools/todos.js +1 -1
  147. package/dist/core/tools/todos.js.map +1 -1
  148. package/dist/core/tools/tool-definition-wrapper.d.ts +4 -3
  149. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  150. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  151. package/dist/core/tools/write.d.ts.map +1 -1
  152. package/dist/core/tools/write.js +1 -1
  153. package/dist/core/tools/write.js.map +1 -1
  154. package/dist/index.d.ts +2 -1
  155. package/dist/index.d.ts.map +1 -1
  156. package/dist/index.js +2 -1
  157. package/dist/index.js.map +1 -1
  158. package/dist/main.d.ts.map +1 -1
  159. package/dist/main.js +2 -2
  160. package/dist/main.js.map +1 -1
  161. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  162. package/dist/modes/interactive/components/assistant-message.js +3 -3
  163. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  164. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/bash-execution.js +3 -3
  166. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  167. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/branch-summary-message.js +1 -1
  169. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  170. package/dist/modes/interactive/components/chat-message-renderer.d.ts +2 -1
  171. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  172. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  173. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/compaction-summary-message.js +1 -1
  175. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  176. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/config-selector.js +1 -1
  178. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/custom-editor.d.ts +3 -0
  180. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/custom-editor.js +13 -3
  182. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  183. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  184. package/dist/modes/interactive/components/footer.js +1 -1
  185. package/dist/modes/interactive/components/footer.js.map +1 -1
  186. package/dist/modes/interactive/components/index.d.ts +2 -1
  187. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  188. package/dist/modes/interactive/components/index.js +2 -1
  189. package/dist/modes/interactive/components/index.js.map +1 -1
  190. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
  191. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  192. package/dist/modes/interactive/components/keybinding-hints.js +47 -5
  193. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  194. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/login-dialog.js +5 -5
  196. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  197. package/dist/modes/interactive/components/model-selector.d.ts +3 -3
  198. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  199. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  200. package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
  201. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  202. package/dist/modes/interactive/components/scoped-models-selector.js +7 -7
  203. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  204. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  205. package/dist/modes/interactive/components/session-selector.js +8 -8
  206. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  207. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  208. package/dist/modes/interactive/components/settings-selector.js +3 -3
  209. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  210. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  211. package/dist/modes/interactive/components/skill-invocation-message.js +2 -2
  212. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  213. package/dist/modes/interactive/components/tool-execution.d.ts +10 -12
  214. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  215. package/dist/modes/interactive/components/tool-execution.js +3 -3
  216. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  217. package/dist/modes/interactive/components/working-status.d.ts +25 -0
  218. package/dist/modes/interactive/components/working-status.d.ts.map +1 -0
  219. package/dist/modes/interactive/components/working-status.js +28 -0
  220. package/dist/modes/interactive/components/working-status.js.map +1 -0
  221. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  222. package/dist/modes/interactive/interactive-mode.js +8 -7
  223. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  224. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  225. package/dist/modes/rpc/rpc-mode.js +8 -0
  226. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  227. package/dist/modes/rpc/rpc-types.d.ts +5 -5
  228. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  229. package/dist/modes/rpc/rpc-types.js.map +1 -1
  230. package/dist/utils/tools-manager.d.ts.map +1 -1
  231. package/dist/utils/tools-manager.js.map +1 -1
  232. package/docs/development.md +2 -2
  233. package/docs/extensions.md +7 -7
  234. package/docs/packages.md +11 -8
  235. package/docs/quickstart.md +2 -2
  236. package/docs/rpc.md +1 -1
  237. package/docs/sdk.md +14 -11
  238. package/docs/session-format.md +1 -1
  239. package/docs/sessions.md +10 -10
  240. package/docs/settings.md +1 -1
  241. package/docs/terminal-setup.md +9 -9
  242. package/docs/tmux.md +10 -10
  243. package/docs/tui.md +2 -2
  244. package/docs/usage.md +9 -9
  245. package/package.json +6 -1
@@ -14,10 +14,13 @@ import { restoreOnSessionStart } from "../shared/persistence-restore.js";
14
14
  import type { SessionManager } from "../shared/persistence-restore.js";
15
15
  import { installCompactionHook } from "../shared/persistence-compaction-policy.js";
16
16
  import {
17
- killRun,
18
17
  killAllRuns,
18
+ destroyRun,
19
+ destroyAllRuns,
19
20
  resumeRun,
20
21
  pauseRun,
22
+ interruptRun,
23
+ interruptAllRuns,
21
24
  inspectRun,
22
25
  } from "../runs/background/status.js";
23
26
  import { cancellationRegistry } from "../runs/background/cancellation-registry.js";
@@ -242,6 +245,17 @@ export interface PiExecuteContext extends PiModelContext {
242
245
  * mocks can stub a minimal surface; production runtime supplies all of
243
246
  * them.
244
247
  */
248
+ export interface WorkflowResourceInfo {
249
+ readonly path: string;
250
+ readonly enabled: boolean;
251
+ readonly metadata?: {
252
+ readonly source?: string;
253
+ readonly scope?: string;
254
+ readonly origin?: string;
255
+ readonly baseDir?: string;
256
+ };
257
+ }
258
+
245
259
  export interface ExtensionAPI {
246
260
  registerTool?: <TArgs, TResult>(opts: PiToolOpts<TArgs, TResult>) => void;
247
261
  /**
@@ -272,6 +286,8 @@ export interface ExtensionAPI {
272
286
  },
273
287
  ) => void | Promise<void>;
274
288
  registerFlag?: (name: string, opts: PiFlagNamedOpts) => void;
289
+ /** Return package-provided workflow files discovered by Atomic's package loader. */
290
+ getWorkflowResources?: () => readonly WorkflowResourceInfo[];
275
291
  /**
276
292
  * Register a keyboard shortcut.
277
293
  * Present on pi >= 1.x; absent on older runtimes.
@@ -378,10 +394,17 @@ export interface WorkflowToolArgs extends StageOptions {
378
394
  | "get"
379
395
  | "status"
380
396
  | "interrupt"
397
+ | "kill"
381
398
  | "resume"
382
399
  | "inputs";
383
- /** Canonical run identifier for status/interrupt/resume. */
400
+ /** Canonical run identifier or unique prefix for status/interrupt/kill/resume. */
384
401
  runId?: string;
402
+ /** Apply supported run-control actions to all in-flight runs. */
403
+ all?: boolean;
404
+ /** Stage id, unique prefix, or name for stage-scoped resume. */
405
+ stageId?: string;
406
+ /** Optional message forwarded when resuming paused work. */
407
+ message?: string;
385
408
  /** Direct single-task mode, or root task string when chain is present. */
386
409
  task?: WorkflowDirectTaskItem | string;
387
410
  /** Direct top-level parallel mode. */
@@ -393,6 +416,7 @@ export interface WorkflowToolArgs extends StageOptions {
393
416
  /** Internal host-derived parent session file for context:"fork". */
394
417
  forkFromSessionFile?: string;
395
418
  concurrency?: number;
419
+ failFast?: boolean;
396
420
  async?: boolean;
397
421
  intercom?: {
398
422
  enabled?: boolean;
@@ -404,10 +428,10 @@ export interface WorkflowToolArgs extends StageOptions {
404
428
  };
405
429
  output?: string | false;
406
430
  outputMode?: "inline" | "file-only";
431
+ reads?: readonly string[] | false;
407
432
  chainDir?: string;
408
433
  maxOutput?: WorkflowMaxOutput;
409
434
  artifacts?: boolean;
410
- progress?: boolean;
411
435
  worktree?: boolean;
412
436
  }
413
437
 
@@ -592,10 +616,10 @@ export function makeExecuteWorkflowTool(
592
616
  };
593
617
  }
594
618
 
595
- case "interrupt": {
596
- // Support canonical interrupt-all via runId sentinel.
597
- if (runId === "--all") {
598
- const results = killAllRuns({
619
+ case "kill": {
620
+ const target = resolveToolRunTarget(args, "No in-flight runs to kill.");
621
+ if (target.kind === "all") {
622
+ const results = destroyAllRuns({
599
623
  cancellation: cancellationRegistry,
600
624
  persistence: getPersistence(),
601
625
  });
@@ -606,11 +630,17 @@ export function makeExecuteWorkflowTool(
606
630
  status: killed > 0 ? "killed" : "noop",
607
631
  message:
608
632
  killed > 0
609
- ? `Interrupted ${killed} run(s).`
610
- : "No in-flight runs to interrupt.",
633
+ ? `Killed and removed ${killed} run(s).`
634
+ : "No in-flight runs to kill.",
611
635
  };
612
636
  }
613
- const result = killRun(runId, {
637
+ if (target.kind === "ambiguous") {
638
+ return { action, runId: target.target, status: "noop", message: ambiguousRunMessage(target.target, target.matches) };
639
+ }
640
+ if (target.kind === "not_found") {
641
+ return { action, runId: target.target, status: "noop", message: target.message };
642
+ }
643
+ const result = destroyRun(target.runId, {
614
644
  cancellation: cancellationRegistry,
615
645
  persistence: getPersistence(),
616
646
  });
@@ -619,35 +649,101 @@ export function makeExecuteWorkflowTool(
619
649
  action,
620
650
  runId: result.runId,
621
651
  status: "killed",
622
- message: `Run ${result.runId} interrupted (was ${result.previousStatus}).`,
652
+ message: `Run ${result.runId} killed and removed (was ${result.previousStatus}).`,
653
+ };
654
+ }
655
+ return {
656
+ action,
657
+ runId: target.runId,
658
+ status: "noop",
659
+ message: `Run not found: ${target.runId}`,
660
+ };
661
+ }
662
+
663
+ case "interrupt": {
664
+ // Interrupt is resumable: it pauses live work and keeps runs in history/status.
665
+ const target = resolveToolRunTarget(args, "No in-flight runs to interrupt.");
666
+ if (target.kind === "all") {
667
+ const results = interruptAllRuns();
668
+ const interrupted = results.filter((r) => r.ok).length;
669
+ return {
670
+ action,
671
+ runId: "--all",
672
+ status: interrupted > 0 ? "paused" : "noop",
673
+ message:
674
+ interrupted > 0
675
+ ? `Interrupted ${interrupted} run(s).`
676
+ : "No in-flight runs to interrupt.",
677
+ };
678
+ }
679
+ if (target.kind === "ambiguous") {
680
+ return { action, runId: target.target, status: "noop", message: ambiguousRunMessage(target.target, target.matches) };
681
+ }
682
+ if (target.kind === "not_found") {
683
+ return { action, runId: target.target, status: "noop", message: target.message };
684
+ }
685
+ const result = interruptRun(target.runId);
686
+ if (result.ok) {
687
+ return {
688
+ action,
689
+ runId: result.runId,
690
+ status: "paused",
691
+ message: `Run ${result.runId} interrupted and can be resumed.`,
623
692
  };
624
693
  }
625
694
  return {
626
695
  action,
627
- runId,
696
+ runId: target.runId,
628
697
  status: "noop",
629
698
  message:
630
699
  result.reason === "not_found"
631
- ? `Run not found: ${runId}`
632
- : `Run already ended: ${runId}`,
700
+ ? `Run not found: ${target.runId}`
701
+ : result.reason === "already_ended"
702
+ ? `Run already ended: ${target.runId}`
703
+ : result.reason === "stage_not_found"
704
+ ? `Stage not found for run: ${target.runId}`
705
+ : `No active stages to interrupt for run: ${target.runId}`,
633
706
  };
634
707
  }
635
708
 
636
709
  case "resume": {
637
- const result = resumeRun(runId);
710
+ const target = resolveToolRunTarget(args, "No active run to resume.");
711
+ if (target.kind === "all") {
712
+ return { action: "resume", runId: "--all", status: "noop", message: "Resume does not support --all." };
713
+ }
714
+ if (target.kind === "ambiguous") {
715
+ return { action: "resume", runId: target.target, status: "noop", message: ambiguousRunMessage(target.target, target.matches) };
716
+ }
717
+ if (target.kind === "not_found") {
718
+ return { action: "resume", runId: target.target, status: "noop", message: target.message };
719
+ }
720
+ const stage = resolveToolStageTarget(target.runId, args.stageId);
721
+ if (!stage.ok) {
722
+ return { action: "resume", runId: target.runId, status: "noop", message: stage.message };
723
+ }
724
+ const run = store.runs().find((r) => r.id === target.runId);
725
+ const isPaused =
726
+ run?.status === "paused" ||
727
+ (run?.stages.some((s) => s.status === "paused") ?? false);
728
+ const result = resumeRun(target.runId, { stageId: stage.stageId, message: args.message });
638
729
  if (result.ok) {
730
+ const message = isPaused
731
+ ? result.resumed.length === 0
732
+ ? `No paused stages on run ${result.runId.slice(0, 8)}.`
733
+ : `Resumed ${result.resumed.length} stage(s) on run ${result.runId.slice(0, 8)}${args.message ? ` with message: "${args.message}"` : ""}.`
734
+ : `Snapshot available: run ${result.runId} (${result.snapshot.name}) — status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`;
639
735
  return {
640
736
  action: "resume",
641
737
  runId: result.runId,
642
738
  status: "ok",
643
- message: `Snapshot available: run ${result.runId} (${result.snapshot.name}) \u2014 status: ${result.snapshot.status}, stages: ${result.snapshot.stages.length}`,
739
+ message,
644
740
  };
645
741
  }
646
742
  return {
647
743
  action: "resume",
648
- runId,
744
+ runId: target.runId,
649
745
  status: "noop",
650
- message: `Run not found: ${runId}`,
746
+ message: `Run not found: ${target.runId}`,
651
747
  };
652
748
  }
653
749
 
@@ -669,7 +765,7 @@ export function makeExecuteWorkflowTool(
669
765
  * `registerWorkflowCommand` alongside the host registration so the
670
766
  * `on("input", …)` interceptor below can dispatch our commands directly
671
767
  * — bypassing pi's optimistic `startPendingSubmission` flow which
672
- * fires the `Working… (Esc to interrupt)` loader before the host knows
768
+ * fires the `Working… (esc to interrupt)` loader before the host knows
673
769
  * the input is a synchronous picker/connect UI, not a streaming turn.
674
770
  *
675
771
  * See `installInputInterceptor()` for the dispatch path and rationale.
@@ -710,7 +806,7 @@ function registerWorkflowCommand(
710
806
  * pi's editor `onSubmit` handler unconditionally calls
711
807
  * `startPendingSubmission` for any text that isn't a built-in slash /
712
808
  * skill / bash / python command — this echoes the message into chat
713
- * scrollback AND starts the `Working… (Esc to interrupt)` loader in
809
+ * scrollback AND starts the `Working… (esc to interrupt)` loader in
714
810
  * `statusContainer` before `session.prompt` even runs. The loader is
715
811
  * an optimistic affordance for the agent-streaming case; for our
716
812
  * synchronous picker/connect UIs (`/workflow connect`, `/workflow run`,
@@ -772,6 +868,30 @@ function installInputInterceptor(
772
868
  });
773
869
  }
774
870
 
871
+ function formatStartupDiagnostics(
872
+ configResult: ConfigLoadResult | null,
873
+ discoveryResult: DiscoveryResult | null,
874
+ ): string | null {
875
+ const lines: string[] = [];
876
+ for (const diagnostic of configResult?.diagnostics ?? []) {
877
+ lines.push(`- [${diagnostic.level} ${diagnostic.code}] ${diagnostic.source ?? "workflow config"}: ${diagnostic.message}`);
878
+ }
879
+ for (const diagnostic of discoveryResult?.errors ?? []) {
880
+ lines.push(`- [${diagnostic.level} ${diagnostic.code}] ${diagnostic.source ?? "workflow discovery"}: ${diagnostic.message}`);
881
+ }
882
+
883
+ if (lines.length === 0) return null;
884
+
885
+ const maxVisible = 8;
886
+ const visible = lines.slice(0, maxVisible);
887
+ const remaining = lines.length - visible.length;
888
+ return [
889
+ `Workflow discovery diagnostics (${lines.length}): some workflow resources were skipped or need attention.`,
890
+ ...visible,
891
+ ...(remaining > 0 ? [`- … ${remaining} more`] : []),
892
+ ].join("\n");
893
+ }
894
+
775
895
  /**
776
896
  * Resolve a user-supplied run identifier (full UUID or unique prefix) to
777
897
  * a concrete runId. The widget surfaces an 8-char prefix to keep the
@@ -794,6 +914,52 @@ function resolveRunIdPrefix(target: string): RunIdResolution {
794
914
  return { kind: "ambiguous", matches: prefixed.map((r) => r.id) };
795
915
  }
796
916
 
917
+ type ToolRunTarget =
918
+ | { kind: "all" }
919
+ | { kind: "run"; runId: string }
920
+ | { kind: "ambiguous"; target: string; matches: string[] }
921
+ | { kind: "not_found"; target: string; message: string };
922
+
923
+ function resolveToolRunTarget(
924
+ args: WorkflowToolArgs,
925
+ emptyMessage: string,
926
+ ): ToolRunTarget {
927
+ const rawTarget = args.runId?.trim() ?? "";
928
+ if (args.all === true || rawTarget === "--all") return { kind: "all" };
929
+
930
+ const target = rawTarget || store.activeRunId() || "";
931
+ if (!target) return { kind: "not_found", target: rawTarget, message: emptyMessage };
932
+
933
+ const resolved = resolveRunIdPrefix(target);
934
+ if (resolved.kind === "exact") return { kind: "run", runId: resolved.runId };
935
+ if (resolved.kind === "ambiguous") {
936
+ return { kind: "ambiguous", target, matches: resolved.matches };
937
+ }
938
+ return { kind: "not_found", target, message: `Run not found: ${target}` };
939
+ }
940
+
941
+ type ToolStageTarget =
942
+ | { ok: true; stageId?: string }
943
+ | { ok: false; message: string };
944
+
945
+ function resolveToolStageTarget(runId: string, stageTarget?: string): ToolStageTarget {
946
+ const target = stageTarget?.trim();
947
+ if (!target) return { ok: true };
948
+
949
+ const run = store.runs().find((r) => r.id === runId);
950
+ const stage = run?.stages.find(
951
+ (s) => s.id === target || s.id.startsWith(target) || s.name === target,
952
+ );
953
+ if (!stage) return { ok: false, message: `Stage not found in run ${runId.slice(0, 8)}: ${target}` };
954
+ return { ok: true, stageId: stage.id };
955
+ }
956
+
957
+ function ambiguousRunMessage(target: string, matches: readonly string[]): string {
958
+ return `Ambiguous run prefix "${target}" matches: ${matches
959
+ .map((id) => id.slice(0, 12))
960
+ .join(", ")}`;
961
+ }
962
+
797
963
  function overlaySurfaceFromContext(ctx?: {
798
964
  ui?: PiUISurface;
799
965
  }): OverlayPiSurface | undefined {
@@ -806,7 +972,7 @@ function overlaySurfaceFromContext(ctx?: {
806
972
 
807
973
  /**
808
974
  * Strip the clack-style `--yes` / `-y` confirmation skip flag from a token
809
- * list. Used by `/workflow interrupt` to skip the confirmation overlay.
975
+ * list. Used by `/workflow interrupt` and `/workflow kill` to skip the confirmation overlay.
810
976
  */
811
977
  export function stripYesFlag(tokens: string[]): {
812
978
  tokens: string[];
@@ -991,10 +1157,6 @@ function factory(pi: ExtensionAPI): void {
991
1157
  // `installInputInterceptor` for the rationale.
992
1158
  const workflowCommands = new Map<string, WorkflowCommandHandler>();
993
1159
 
994
- // Build graph overlay adapter — wraps GraphView + pi.ui.custom.
995
- // noopOverlay returned when pi.ui?.custom is absent (degraded runtime).
996
- const overlay: GraphOverlayPort = buildGraphOverlayAdapter(pi, store);
997
-
998
1160
  // -------------------------------------------------------------------------
999
1161
  // 1. Create ExtensionRuntime — mutable ref seeded from startup discovery,
1000
1162
  // upgraded to unified async discovery once discoverWorkflows() resolves.
@@ -1006,6 +1168,18 @@ function factory(pi: ExtensionAPI): void {
1006
1168
  const persistenceRef: { current: WorkflowPersistencePort | undefined } = {
1007
1169
  current: makePersistencePort(pi, WORKFLOW_CONFIG_DEFAULTS.persistRuns),
1008
1170
  };
1171
+
1172
+ // Build graph overlay adapter — wraps GraphView + pi.ui.custom.
1173
+ // noopOverlay returned when pi.ui?.custom is absent (degraded runtime).
1174
+ const overlay: GraphOverlayPort = buildGraphOverlayAdapter(pi, store, {
1175
+ onKillRun: (runId) => {
1176
+ destroyRun(runId, {
1177
+ cancellation: cancellationRegistry,
1178
+ persistence: persistenceRef.current,
1179
+ });
1180
+ },
1181
+ });
1182
+
1009
1183
  const mcpPort: WorkflowMcpPort | undefined = makeMcpPort(pi);
1010
1184
 
1011
1185
  /**
@@ -1158,7 +1332,10 @@ function factory(pi: ExtensionAPI): void {
1158
1332
  )
1159
1333
  : undefined;
1160
1334
 
1161
- const result = await discoverWorkflows({ config: discoveryConfig });
1335
+ const packageWorkflowPaths = (pi.getWorkflowResources?.() ?? [])
1336
+ .filter((resource) => resource.enabled !== false)
1337
+ .map((resource) => resource.path);
1338
+ const result = await discoverWorkflows({ config: discoveryConfig, packageWorkflowPaths });
1162
1339
  discoveryRef.current = result;
1163
1340
 
1164
1341
  // Resolve effective config (fills in all defaults) and build WorkflowRuntimeConfig.
@@ -1230,11 +1407,12 @@ function factory(pi: ExtensionAPI): void {
1230
1407
  * connect [runId|prefix] no arg → picker overlay; arg → attach to graph
1231
1408
  * attach [runId|prefix [stageId]] open the in-place attach pane on a stage
1232
1409
  * interrupt [runId|prefix|--all] [-y] confirmation overlay unless -y
1233
- * pause [runId|prefix [stageId]] pause a run or specific stage
1410
+ * kill [runId|prefix|--all] [-y] kill and remove from history/status
1411
+ * pause [runId|prefix [stageId]] pause a run or specific stage
1234
1412
  * resume [runId|prefix [stageId] …] resume paused work or reopen snapshot
1235
1413
  */
1236
1414
  async function handleRunControlCommand(
1237
- action: "connect" | "interrupt" | "attach" | "pause" | "resume",
1415
+ action: "connect" | "interrupt" | "kill" | "attach" | "pause" | "resume",
1238
1416
  rest: string[],
1239
1417
  ctx: PiCommandContext,
1240
1418
  ): Promise<boolean> {
@@ -1271,14 +1449,14 @@ function factory(pi: ExtensionAPI): void {
1271
1449
  );
1272
1450
  return true;
1273
1451
  }
1274
- const killed = killRun(result.runId, {
1452
+ const killed = destroyRun(result.runId, {
1275
1453
  cancellation: cancellationRegistry,
1276
1454
  persistence: persistenceRef.current,
1277
1455
  });
1278
1456
  print(
1279
1457
  killed.ok
1280
- ? `Run ${killed.runId.slice(0, 8)} interrupted.`
1281
- : `Run ${result.runId.slice(0, 8)} already ended.`,
1458
+ ? `Run ${killed.runId.slice(0, 8)} killed and removed.`
1459
+ : `Run not found: ${result.runId.slice(0, 8)}.`,
1282
1460
  );
1283
1461
  return true;
1284
1462
  }
@@ -1301,7 +1479,7 @@ function factory(pi: ExtensionAPI): void {
1301
1479
  }
1302
1480
  overlay.open(resolved.runId, overlaySurfaceFromContext(ctx));
1303
1481
  print(
1304
- `Attached to ${resolved.runId.slice(0, 8)}. Press "h" or ctrl+d to hide, "q" to interrupt, esc to close.`,
1482
+ `Attached to ${resolved.runId.slice(0, 8)}. h/ctrl+d hide · q kill · esc close.`,
1305
1483
  );
1306
1484
  return true;
1307
1485
  }
@@ -1326,6 +1504,87 @@ function factory(pi: ExtensionAPI): void {
1326
1504
  if (!yes && ctx.ui && typeof ctx.ui.confirm === "function") {
1327
1505
  const ok = await ctx.ui.confirm(
1328
1506
  `Interrupt all ${inFlight.length} in-flight workflow runs?`,
1507
+ `Pauses: ${inFlight.map((r) => `${r.name} (${r.id.slice(0, 8)})`).join(", ")}`,
1508
+ );
1509
+ if (!ok) {
1510
+ print("Cancelled.");
1511
+ return true;
1512
+ }
1513
+ }
1514
+ const results = interruptAllRuns();
1515
+ const interrupted = results.filter((r) => r.ok).length;
1516
+ print(
1517
+ interrupted > 0
1518
+ ? `Interrupted ${interrupted} run(s).`
1519
+ : "No in-flight runs to interrupt.",
1520
+ );
1521
+ return true;
1522
+ }
1523
+ const resolved = resolveRunIdPrefix(target!);
1524
+ if (resolved.kind === "not_found") {
1525
+ print(`Run not found: ${target}`);
1526
+ return true;
1527
+ }
1528
+ if (resolved.kind === "ambiguous") {
1529
+ print(
1530
+ `Ambiguous run prefix "${target}" matches multiple runs: ${resolved.matches
1531
+ .map((id) => id.slice(0, 12))
1532
+ .join(", ")}`,
1533
+ );
1534
+ return true;
1535
+ }
1536
+ const run = store.runs().find((r) => r.id === resolved.runId);
1537
+ if (!yes && run && run.endedAt === undefined && typeof ctx.ui.confirm === "function") {
1538
+ const confirmed = await ctx.ui.confirm(
1539
+ `Interrupt workflow run ${run.name} (${run.id.slice(0, 8)})?`,
1540
+ "Pauses live work so it can be resumed later.",
1541
+ );
1542
+ if (!confirmed) {
1543
+ print(
1544
+ `Cancelled. Run ${resolved.runId.slice(0, 8)} is still active.`,
1545
+ );
1546
+ return true;
1547
+ }
1548
+ }
1549
+ const result = interruptRun(resolved.runId);
1550
+ if (result.ok) {
1551
+ print(
1552
+ `Run ${result.runId.slice(0, 8)} interrupted and can be resumed.`,
1553
+ );
1554
+ } else {
1555
+ print(
1556
+ result.reason === "not_found"
1557
+ ? `Run not found: ${target}`
1558
+ : result.reason === "already_ended"
1559
+ ? `Run already ended: ${target}`
1560
+ : result.reason === "stage_not_found"
1561
+ ? `Stage not found for run ${resolved.runId.slice(0, 8)}.`
1562
+ : `No active stages to interrupt on run ${resolved.runId.slice(0, 8)}.`,
1563
+ );
1564
+ }
1565
+ return true;
1566
+ }
1567
+
1568
+ if (action === "kill") {
1569
+ const { tokens: killArgs, yes } = stripYesFlag(rest);
1570
+ let target = killArgs.find((t) => !t.startsWith("--"));
1571
+ const wantsAll = killArgs.includes("--all");
1572
+ if (!target && !wantsAll) {
1573
+ target = store.activeRunId() ?? undefined;
1574
+ if (!target) {
1575
+ print("No in-flight runs to kill.");
1576
+ return true;
1577
+ }
1578
+ }
1579
+ if (wantsAll) {
1580
+ const inFlight = store.runs().filter((r) => r.endedAt === undefined);
1581
+ if (inFlight.length === 0) {
1582
+ print("No in-flight runs to kill.");
1583
+ return true;
1584
+ }
1585
+ if (!yes && ctx.ui && typeof ctx.ui.confirm === "function") {
1586
+ const ok = await ctx.ui.confirm(
1587
+ `Kill and remove all ${inFlight.length} in-flight workflow runs?`,
1329
1588
  `Aborts: ${inFlight.map((r) => `${r.name} (${r.id.slice(0, 8)})`).join(", ")}`,
1330
1589
  );
1331
1590
  if (!ok) {
@@ -1333,15 +1592,15 @@ function factory(pi: ExtensionAPI): void {
1333
1592
  return true;
1334
1593
  }
1335
1594
  }
1336
- const results = killAllRuns({
1595
+ const results = destroyAllRuns({
1337
1596
  cancellation: cancellationRegistry,
1338
1597
  persistence: persistenceRef.current,
1339
1598
  });
1340
1599
  const killed = results.filter((r) => r.ok).length;
1341
1600
  print(
1342
1601
  killed > 0
1343
- ? `Interrupted ${killed} run(s).`
1344
- : "No in-flight runs to interrupt.",
1602
+ ? `Killed and removed ${killed} run(s).`
1603
+ : "No in-flight runs to kill.",
1345
1604
  );
1346
1605
  return true;
1347
1606
  }
@@ -1359,29 +1618,25 @@ function factory(pi: ExtensionAPI): void {
1359
1618
  return true;
1360
1619
  }
1361
1620
  const run = store.runs().find((r) => r.id === resolved.runId);
1362
- if (!yes && run && run.endedAt === undefined && ctx.ui) {
1621
+ if (!yes && run && ctx.ui) {
1363
1622
  const confirmed = await openKillConfirm(ctx.ui, run, theme);
1364
1623
  if (!confirmed) {
1365
1624
  print(
1366
- `Cancelled. Run ${resolved.runId.slice(0, 8)} is still active.`,
1625
+ `Cancelled. Run ${resolved.runId.slice(0, 8)} is still in history/status.`,
1367
1626
  );
1368
1627
  return true;
1369
1628
  }
1370
1629
  }
1371
- const result = killRun(resolved.runId, {
1630
+ const result = destroyRun(resolved.runId, {
1372
1631
  cancellation: cancellationRegistry,
1373
1632
  persistence: persistenceRef.current,
1374
1633
  });
1375
1634
  if (result.ok) {
1376
1635
  print(
1377
- `Run ${result.runId.slice(0, 8)} interrupted (was ${result.previousStatus}).`,
1636
+ `Run ${result.runId.slice(0, 8)} killed and removed (was ${result.previousStatus}).`,
1378
1637
  );
1379
1638
  } else {
1380
- print(
1381
- result.reason === "not_found"
1382
- ? `Run not found: ${target}`
1383
- : `Run already ended: ${target}`,
1384
- );
1639
+ print(`Run not found: ${target}`);
1385
1640
  }
1386
1641
  return true;
1387
1642
  }
@@ -1405,7 +1660,7 @@ function factory(pi: ExtensionAPI): void {
1405
1660
  // Forward through the existing interrupt flow for clarity.
1406
1661
  if (picked.kind === "kill") {
1407
1662
  return handleRunControlCommand(
1408
- "interrupt",
1663
+ "kill",
1409
1664
  [picked.runId, "-y"],
1410
1665
  ctx,
1411
1666
  );
@@ -1443,8 +1698,8 @@ function factory(pi: ExtensionAPI): void {
1443
1698
  overlay.open(runId, overlaySurfaceFromContext(ctx), stageId);
1444
1699
  print(
1445
1700
  stageId
1446
- ? `Attached to ${runId.slice(0, 8)} stage ${stageId.slice(0, 8)}. Ctrl+D returns to graph, Escape closes.`
1447
- : `Attached to ${runId.slice(0, 8)}. Press on a node to chat, Ctrl+D to detach.`,
1701
+ ? `Attached to ${runId.slice(0, 8)} stage ${stageId.slice(0, 8)}. ctrl+d return to graph · esc close.`
1702
+ : `Attached to ${runId.slice(0, 8)}. ↵ chat · ctrl+d detach.`,
1448
1703
  );
1449
1704
  return true;
1450
1705
  }
@@ -1513,7 +1768,7 @@ function factory(pi: ExtensionAPI): void {
1513
1768
  }
1514
1769
  // Open the orchestrator overlay (graph for run-level pause, stage
1515
1770
  // chat when a stage was named). This mirrors connect/attach/resume:
1516
- // the full-screen overlay hides Pi's "Working… (Esc to interrupt)"
1771
+ // the full-screen overlay hides Pi's "Working… (esc to interrupt)"
1517
1772
  // spinner, which otherwise stays visible because the host session
1518
1773
  // is still streaming whatever was happening before the pause hit.
1519
1774
  if (typeof ctx.ui?.custom === "function") {
@@ -1608,7 +1863,7 @@ function factory(pi: ExtensionAPI): void {
1608
1863
  "workflow",
1609
1864
  {
1610
1865
  description:
1611
- "Run or inspect pi workflows. Usage: /workflow <name> [key=value…] | /workflow [list|status|connect|attach|interrupt|pause|resume|inputs] [args]",
1866
+ "Run or inspect pi workflows. Usage: /workflow <name> [key=value…] | /workflow [list|status|connect|attach|interrupt|kill|pause|resume|inputs] [args]",
1612
1867
  handler: async (args: string, ctx: PiCommandContext) => {
1613
1868
  const print = (msg: string): void => ctx.ui.notify(msg, "info");
1614
1869
  // Quote-aware split so `prompt="map the codebase"` stays a single
@@ -1702,7 +1957,7 @@ function factory(pi: ExtensionAPI): void {
1702
1957
  if (subcommand === "interrupt") {
1703
1958
  // The top-level chat command is the fast interrupt path surfaced by the
1704
1959
  // widget hint (`/workflow interrupt <id>`). The user's explicit slash
1705
- // command should abort immediately, even when a confirm surface is
1960
+ // command should pause immediately, even when a confirm surface is
1706
1961
  // unavailable or would steal focus from the running workflow.
1707
1962
  const interruptArgs = parts.slice(1);
1708
1963
  const hasYes = interruptArgs.some((t) => t === "--yes" || t === "-y");
@@ -1714,6 +1969,20 @@ function factory(pi: ExtensionAPI): void {
1714
1969
  return;
1715
1970
  }
1716
1971
 
1972
+ // -----------------------------------------------------------------------
1973
+ // kill — destructive fast path: abort and remove from history/status.
1974
+ // -----------------------------------------------------------------------
1975
+ if (subcommand === "kill") {
1976
+ const killArgs = parts.slice(1);
1977
+ const hasYes = killArgs.some((t) => t === "--yes" || t === "-y");
1978
+ await handleRunControlCommand(
1979
+ "kill",
1980
+ hasYes ? killArgs : [...killArgs, "-y"],
1981
+ ctx,
1982
+ );
1983
+ return;
1984
+ }
1985
+
1717
1986
  // -----------------------------------------------------------------------
1718
1987
  // resume — non-paused runs reopen the orchestrator pane (legacy
1719
1988
  // behaviour); paused runs resume live work through the registry.
@@ -1814,7 +2083,7 @@ function factory(pi: ExtensionAPI): void {
1814
2083
  // Track whether the inputs picker actually showed a UI to the user.
1815
2084
  // We use this below to mount the orchestrator overlay on dispatch
1816
2085
  // success — same UX as `/workflow connect|attach|pause|resume`,
1817
- // which all cover Pi's `⠴ Working… (Esc to interrupt)` spinner
2086
+ // which all cover Pi's `⠴ Working… (esc to interrupt)` spinner
1818
2087
  // with the full-screen overlay instead of leaving it visible in
1819
2088
  // the chat while the workflow runs in the background.
1820
2089
  let pickerWasShown = false;
@@ -1988,6 +2257,11 @@ function factory(pi: ExtensionAPI): void {
1988
2257
  label: "interrupt",
1989
2258
  description: "Interrupt a run",
1990
2259
  },
2260
+ {
2261
+ value: "kill ",
2262
+ label: "kill",
2263
+ description: "Kill and remove a run",
2264
+ },
1991
2265
  {
1992
2266
  value: "pause ",
1993
2267
  label: "pause",
@@ -2041,12 +2315,13 @@ function factory(pi: ExtensionAPI): void {
2041
2315
  return completeToken(partial, runIdItems());
2042
2316
  }
2043
2317
 
2044
- if (subcommand === "interrupt") {
2318
+ if (subcommand === "interrupt" || subcommand === "kill") {
2319
+ const verb = subcommand === "kill" ? "Kill and remove" : "Interrupt";
2045
2320
  return completeToken(partial, [
2046
2321
  {
2047
2322
  value: "--all ",
2048
2323
  label: "--all",
2049
- description: "Interrupt all in-flight runs",
2324
+ description: `${verb} all in-flight runs`,
2050
2325
  },
2051
2326
  {
2052
2327
  value: "--yes ",
@@ -2187,6 +2462,10 @@ function factory(pi: ExtensionAPI): void {
2187
2462
  // tunables must be resolved first.
2188
2463
  await discoveryPromise;
2189
2464
  if (ctx?.ui) {
2465
+ const diagnostics = formatStartupDiagnostics(configLoadRef.current, discoveryRef.current);
2466
+ if (diagnostics !== null) {
2467
+ ctx.ui.notify?.(diagnostics, "warning");
2468
+ }
2190
2469
  storeWidgetUnsubscribe?.();
2191
2470
  storeWidgetUnsubscribe = installStoreWidget({ ui: ctx.ui }, store);
2192
2471
  }
@@ -2206,17 +2485,22 @@ function factory(pi: ExtensionAPI): void {
2206
2485
  });
2207
2486
 
2208
2487
  installCompactionHook(pi, store);
2209
- pi.on("session_shutdown", () => {
2210
- // Tie workflow lifecycle to the chat: when the chat ends, every
2211
- // in-flight workflow is killed so we don't leave subprocesses
2212
- // burning tokens with no UI to surface their progress.
2488
+ pi.on("session_shutdown", (event) => {
2489
+ // Only application exit owns workflow teardown. Session replacement
2490
+ // paths (reload/new/resume/fork) are handled by session_start restore
2491
+ // logic so they do not masquerade as app-exit kills.
2492
+ const reason = typeof event === "object" && event !== null && "reason" in event
2493
+ ? (event as { readonly reason?: string }).reason
2494
+ : undefined;
2213
2495
  intercomControlUnsubscribe?.();
2214
2496
  intercomControlUnsubscribe = null;
2215
- killAllRuns({
2216
- store,
2217
- cancellation: cancellationRegistry,
2218
- persistence: persistenceRef.current,
2219
- });
2497
+ if (reason === "quit") {
2498
+ killAllRuns({
2499
+ store,
2500
+ cancellation: cancellationRegistry,
2501
+ persistence: persistenceRef.current,
2502
+ });
2503
+ }
2220
2504
  storeWidgetUnsubscribe?.();
2221
2505
  storeWidgetUnsubscribe = null;
2222
2506
  });
@@ -2279,7 +2563,7 @@ function factory(pi: ExtensionAPI): void {
2279
2563
  );
2280
2564
 
2281
2565
  // -------------------------------------------------------------------------
2282
- // 7. Suppress pi's optimistic "Working… (Esc to interrupt)" loader
2566
+ // 7. Suppress pi's optimistic "Working… (esc to interrupt)" loader
2283
2567
  // for our slash commands. Workflow commands are synchronous picker /
2284
2568
  // connect / inspect UIs, not streaming turns — the loader is noise
2285
2569
  // that pads chrome above the picker. The `on("input")` hook fires