@duckmind/dm-darwin-x64 0.43.4 → 0.43.8

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 (251) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +241 -49
  3. package/extensions/dm-9router-ext/package.json +2 -19
  4. package/extensions/dm-9router-ext/src/index.js +5 -0
  5. package/extensions/dm-ask-user/index.js +10 -0
  6. package/extensions/dm-ask-user/package.json +1 -21
  7. package/extensions/dm-cli-anything/package.json +0 -6
  8. package/extensions/dm-context/package.json +2 -12
  9. package/extensions/dm-context/src/context.js +1 -0
  10. package/extensions/dm-context/src/index.js +7 -0
  11. package/extensions/dm-context/src/utils.js +1 -0
  12. package/extensions/dm-cua/package.json +0 -7
  13. package/extensions/dm-fff/package.json +1 -28
  14. package/extensions/dm-fff/src/index.js +15 -0
  15. package/extensions/dm-fff/src/query.js +1 -0
  16. package/extensions/dm-goal/package.json +2 -31
  17. package/extensions/dm-goal/src/goal.js +43 -0
  18. package/extensions/dm-grill-me/index.js +174 -0
  19. package/extensions/dm-grill-me/package.json +1 -26
  20. package/extensions/dm-image2/package.json +0 -7
  21. package/extensions/dm-subagents/package.json +1 -32
  22. package/extensions/dm-subagents/src/agents/agent-management.js +37 -0
  23. package/extensions/dm-subagents/src/agents/agent-memory.js +6 -0
  24. package/extensions/dm-subagents/src/agents/agent-scope.js +1 -0
  25. package/extensions/dm-subagents/src/agents/agent-selection.js +1 -0
  26. package/extensions/dm-subagents/src/agents/agent-serializer.js +7 -0
  27. package/extensions/dm-subagents/src/agents/agents.js +11 -0
  28. package/extensions/dm-subagents/src/agents/chain-serializer.js +12 -0
  29. package/extensions/dm-subagents/src/agents/frontmatter.js +6 -0
  30. package/extensions/dm-subagents/src/agents/identity.js +1 -0
  31. package/extensions/dm-subagents/src/agents/proactive-skills.js +1 -0
  32. package/extensions/dm-subagents/src/agents/skills.js +6 -0
  33. package/extensions/dm-subagents/src/extension/config.js +2 -0
  34. package/extensions/dm-subagents/src/extension/control-notices.js +4 -0
  35. package/extensions/dm-subagents/src/extension/doctor.js +16 -0
  36. package/extensions/dm-subagents/src/extension/fanout-child.js +231 -0
  37. package/extensions/dm-subagents/src/extension/index.js +367 -0
  38. package/extensions/dm-subagents/src/extension/rpc.js +8 -0
  39. package/extensions/dm-subagents/src/extension/schemas.js +1 -0
  40. package/extensions/dm-subagents/src/extension/{tool-description.ts → tool-description.js} +6 -126
  41. package/extensions/dm-subagents/src/intercom/intercom-bridge.js +13 -0
  42. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +5 -0
  43. package/extensions/dm-subagents/src/intercom/result-intercom.js +3 -0
  44. package/extensions/dm-subagents/src/profiles/profiles.js +3 -0
  45. package/extensions/dm-subagents/src/runs/background/async-execution.js +46 -0
  46. package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +14 -0
  47. package/extensions/dm-subagents/src/runs/background/async-resume.js +8 -0
  48. package/extensions/dm-subagents/src/runs/background/async-status.js +12 -0
  49. package/extensions/dm-subagents/src/runs/background/chain-append.js +2 -0
  50. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +1 -0
  51. package/extensions/dm-subagents/src/runs/background/completion-batcher.js +1 -0
  52. package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +1 -0
  53. package/extensions/dm-subagents/src/runs/background/control-channel.js +1 -0
  54. package/extensions/dm-subagents/src/runs/background/fleet-view.js +17 -0
  55. package/extensions/dm-subagents/src/runs/background/notify.js +3 -0
  56. package/extensions/dm-subagents/src/runs/background/parallel-groups.js +1 -0
  57. package/extensions/dm-subagents/src/runs/background/result-watcher.js +8 -0
  58. package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +4 -0
  59. package/extensions/dm-subagents/src/runs/background/run-status.js +23 -0
  60. package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +4 -0
  61. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +11 -0
  62. package/extensions/dm-subagents/src/runs/background/subagent-runner.js +78 -0
  63. package/extensions/dm-subagents/src/runs/background/top-level-async.js +1 -0
  64. package/extensions/dm-subagents/src/runs/background/wait.js +11 -0
  65. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +12 -0
  66. package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +102 -0
  67. package/extensions/dm-subagents/src/runs/foreground/execution.js +51 -0
  68. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +228 -0
  69. package/extensions/dm-subagents/src/runs/shared/acceptance.js +3 -0
  70. package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +1 -0
  71. package/extensions/dm-subagents/src/runs/shared/completion-guard.js +3 -0
  72. package/extensions/dm-subagents/src/runs/shared/dm-args.js +1 -0
  73. package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +1 -0
  74. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +1 -0
  75. package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +1 -0
  76. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +1 -0
  77. package/extensions/dm-subagents/src/runs/shared/model-fallback.js +1 -0
  78. package/extensions/dm-subagents/src/runs/shared/model-scope.js +1 -0
  79. package/extensions/dm-subagents/src/runs/shared/nested-events.js +8 -0
  80. package/extensions/dm-subagents/src/runs/shared/nested-path.js +1 -0
  81. package/extensions/dm-subagents/src/runs/shared/nested-render.js +1 -0
  82. package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +5 -0
  83. package/extensions/dm-subagents/src/runs/shared/run-history.js +5 -0
  84. package/extensions/dm-subagents/src/runs/shared/single-output.js +14 -0
  85. package/extensions/dm-subagents/src/runs/shared/structured-output.js +1 -0
  86. package/extensions/dm-subagents/src/runs/shared/subagent-control.js +5 -0
  87. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +22 -0
  88. package/extensions/dm-subagents/src/runs/shared/tool-budget.js +1 -0
  89. package/extensions/dm-subagents/src/runs/shared/turn-budget.js +7 -0
  90. package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +1 -0
  91. package/extensions/dm-subagents/src/runs/shared/worktree.js +3 -0
  92. package/extensions/dm-subagents/src/shared/artifacts.js +2 -0
  93. package/extensions/dm-subagents/src/shared/atomic-json.js +1 -0
  94. package/extensions/dm-subagents/src/shared/child-transcript.js +5 -0
  95. package/extensions/dm-subagents/src/shared/file-coalescer.js +1 -0
  96. package/extensions/dm-subagents/src/shared/fork-context.js +6 -0
  97. package/extensions/dm-subagents/src/shared/formatters.js +9 -0
  98. package/extensions/dm-subagents/src/shared/jsonl-writer.js +2 -0
  99. package/extensions/dm-subagents/src/shared/model-info.js +1 -0
  100. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +1 -0
  101. package/extensions/dm-subagents/src/shared/session-identity.js +1 -0
  102. package/extensions/dm-subagents/src/shared/session-tokens.js +2 -0
  103. package/extensions/dm-subagents/src/shared/settings.js +23 -0
  104. package/extensions/dm-subagents/src/shared/status-format.js +1 -0
  105. package/extensions/dm-subagents/src/shared/types.js +8 -0
  106. package/extensions/dm-subagents/src/shared/utils.js +2 -0
  107. package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +1 -0
  108. package/extensions/dm-subagents/src/slash/prompt-workflows.js +7 -0
  109. package/extensions/dm-subagents/src/slash/slash-bridge.js +1 -0
  110. package/extensions/dm-subagents/src/slash/slash-commands.js +38 -0
  111. package/extensions/dm-subagents/src/slash/slash-live-state.js +6 -0
  112. package/extensions/dm-subagents/src/tui/render-helpers.js +1 -0
  113. package/extensions/dm-subagents/src/tui/render.js +4 -0
  114. package/extensions/dm-tasks/package.json +1 -30
  115. package/extensions/dm-tasks/src/auto-clear.js +1 -0
  116. package/extensions/dm-tasks/src/index.js +212 -0
  117. package/extensions/dm-tasks/src/process-tracker.js +1 -0
  118. package/extensions/dm-tasks/src/task-store.js +1 -0
  119. package/extensions/dm-tasks/src/tasks-config.js +1 -0
  120. package/extensions/dm-tasks/src/types.js +0 -0
  121. package/extensions/dm-tasks/src/ui/settings-menu.js +1 -0
  122. package/extensions/dm-tasks/src/ui/task-widget.js +1 -0
  123. package/extensions/dm-usage/index.js +9 -0
  124. package/extensions/dm-usage/package.json +1 -18
  125. package/extensions/greedysearch-dm/index.js +20 -0
  126. package/extensions/greedysearch-dm/package.json +1 -22
  127. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  128. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  129. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  130. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  131. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  132. package/extensions/greedysearch-dm/src/types.js +0 -0
  133. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  134. package/package.json +1 -1
  135. package/extensions/dm-9router-ext/src/index.ts +0 -541
  136. package/extensions/dm-ask-user/index.ts +0 -857
  137. package/extensions/dm-context/src/context.ts +0 -158
  138. package/extensions/dm-context/src/index.ts +0 -439
  139. package/extensions/dm-context/src/utils.ts +0 -6
  140. package/extensions/dm-fff/src/index.ts +0 -1023
  141. package/extensions/dm-fff/src/query.ts +0 -87
  142. package/extensions/dm-goal/src/goal.ts +0 -1073
  143. package/extensions/dm-grill-me/index.ts +0 -1085
  144. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  145. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  146. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  147. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  148. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  149. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  150. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  151. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  152. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  153. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  154. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  155. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  156. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  157. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  158. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  159. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  160. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  161. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  162. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  163. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  164. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  165. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  166. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  167. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  168. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  169. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  170. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  171. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  172. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  173. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  174. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  175. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  176. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  177. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  178. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  179. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  180. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  181. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  182. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  183. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  184. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  185. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  186. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  187. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  188. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  189. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  190. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  191. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  192. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  193. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  194. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  195. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  196. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  197. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  198. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  199. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  200. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  201. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  202. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  203. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  204. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  205. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  206. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  207. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  208. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  209. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  210. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  211. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  212. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  213. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  214. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  215. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  216. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  217. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  218. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  219. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  220. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  221. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  222. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  223. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  224. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  225. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  226. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  227. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  228. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  229. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  230. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  231. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  232. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  233. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  234. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  235. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  236. package/extensions/dm-tasks/src/index.ts +0 -1145
  237. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  238. package/extensions/dm-tasks/src/task-store.ts +0 -305
  239. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  240. package/extensions/dm-tasks/src/types.ts +0 -40
  241. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  242. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  243. package/extensions/dm-usage/index.ts +0 -1718
  244. package/extensions/greedysearch-dm/index.ts +0 -177
  245. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  246. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  247. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  248. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  249. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  250. package/extensions/greedysearch-dm/src/types.ts +0 -110
  251. package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
@@ -1,1145 +0,0 @@
1
- /**
2
- * dm-tasks — A DM extension providing Claude Code-style task tracking and coordination.
3
- *
4
- * Tools:
5
- * TaskCreate — Create a structured task
6
- * TaskList — List all tasks with status
7
- * TaskGet — Get full task details
8
- * TaskUpdate — Update task fields, status, dependencies
9
- * TaskOutput — Get output from a background task process
10
- * TaskStop — Stop a running background task process
11
- * TaskExecute — Execute tasks as subagents (uses bundled dm-subagents)
12
- *
13
- * Commands:
14
- * /tasks — Interactive task management menu
15
- */
16
-
17
- import { join, resolve } from "node:path";
18
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@duckmind/dm-coding-agent";
19
- import { Type } from "typebox";
20
- import { AutoClearManager } from "./auto-clear.js";
21
- import { ProcessTracker } from "./process-tracker.js";
22
- import { TaskStore } from "./task-store.js";
23
- import { loadTasksConfig } from "./tasks-config.js";
24
- import { openSettingsMenu } from "./ui/settings-menu.js";
25
- import { TaskWidget, type UICtx } from "./ui/task-widget.js";
26
-
27
- // ---- Debug ----
28
-
29
- const DEBUG = !!(process.env.DM_TASKS_DEBUG || process.env.PI_TASKS_DEBUG);
30
- function debug(...args: unknown[]) {
31
- if (DEBUG) console.error("[dm-tasks]", ...args);
32
- }
33
-
34
- // ---- Helpers ----
35
-
36
- function textResult(msg: string) {
37
- return { content: [{ type: "text" as const, text: msg }], details: undefined as any };
38
- }
39
-
40
- type SubagentRecordSnapshot = {
41
- id: string;
42
- status: string;
43
- result?: string;
44
- error?: string;
45
- };
46
-
47
- type SubagentsServiceSnapshot = {
48
- spawn?: (type: string, prompt: string, options?: any) => string | Promise<string>;
49
- abort?: (id: string) => boolean | Promise<boolean>;
50
- getRecord?: (id: string) => SubagentRecordSnapshot | undefined;
51
- };
52
-
53
- const SUBAGENTS_SERVICE_KEY = Symbol.for("dm-subagents:service");
54
-
55
- /** Task tool names — used to detect task tool usage for reminder suppression. */
56
- const TASK_TOOL_NAMES = new Set(["TaskCreate", "TaskList", "TaskGet", "TaskUpdate", "TaskOutput", "TaskStop", "TaskExecute"]);
57
-
58
- /** How many turns without task tool usage before injecting a reminder. */
59
- const REMINDER_INTERVAL = 4;
60
-
61
- /** How many turns completed tasks linger before auto-clearing. */
62
- const AUTO_CLEAR_DELAY = 4;
63
-
64
- const SYSTEM_REMINDER = `<system-reminder>
65
- The task tools haven't been used recently. If you're working on tasks that would benefit from tracking progress, consider using TaskCreate to add new tasks and TaskUpdate to update task status (set to in_progress when starting, completed when done). Also consider cleaning up the task list if it has become stale. Only use these if relevant to the current work. This is just a gentle reminder - ignore if not applicable. Make sure that you NEVER mention this reminder to the user
66
- </system-reminder>`;
67
-
68
- export default function (pi: ExtensionAPI) {
69
- // Initialize store and config
70
- const cfg = loadTasksConfig();
71
- const dmTasks = process.env.DM_TASKS ?? process.env.PI_TASKS;
72
- const taskScope = cfg.taskScope ?? "session";
73
-
74
- /** Resolve the task store path from env/config (without session ID). */
75
- function resolveStorePath(sessionId?: string): string | undefined {
76
- if (dmTasks === "off") return undefined;
77
- if (dmTasks?.startsWith("/")) return dmTasks;
78
- if (dmTasks?.startsWith(".")) return resolve(dmTasks);
79
- if (dmTasks) return dmTasks;
80
- if (taskScope === "memory") return undefined;
81
- if (taskScope === "session" && sessionId) {
82
- return join(process.cwd(), ".dm", "tasks", `tasks-${sessionId}.json`);
83
- }
84
- if (taskScope === "session") return undefined; // no session ID yet, start in-memory
85
- return join(process.cwd(), ".dm", "tasks", "tasks.json");
86
- }
87
-
88
- // For project scope (or env override), create store immediately.
89
- // For session scope, start with in-memory and upgrade once we have the session ID.
90
- let store = new TaskStore(resolveStorePath());
91
- const tracker = new ProcessTracker();
92
- const widget = new TaskWidget(store);
93
-
94
- // ── Subagent integration state ──
95
- /** Latest ExtensionContext — refreshed on every tool execution so cascade always has a valid one. */
96
- let latestCtx: ExtensionContext | undefined;
97
- /** Cascade config — set by TaskExecute, consumed by completion listener. */
98
- let cascadeConfig: { additionalContext?: string; model?: string; maxTurns?: number } | undefined;
99
- /** Maps agent IDs to task IDs for O(1) completion lookup. */
100
- const agentTaskMap = new Map<string, string>();
101
-
102
- function getSubagentsService(): SubagentsServiceSnapshot | undefined {
103
- return (globalThis as Record<symbol, unknown>)[SUBAGENTS_SERVICE_KEY] as SubagentsServiceSnapshot | undefined;
104
- }
105
-
106
- function getReadySubagentsService(): SubagentsServiceSnapshot | undefined {
107
- const service = getSubagentsService();
108
- return typeof service?.spawn === "function" ? service : undefined;
109
- }
110
-
111
- function findTaskIdByAgentId(agentId: string, includeFinished = false): string | undefined {
112
- const mapped = agentTaskMap.get(agentId);
113
- if (mapped) return mapped;
114
-
115
- return store.list().find(task => {
116
- if (!includeFinished && task.status !== "in_progress") return false;
117
- return task.metadata?.agentId === agentId;
118
- })?.id;
119
- }
120
-
121
- function findTaskIdByAgentIdPrefix(agentIdOrPrefix: string, includeFinished = false): string | undefined {
122
- const exact = findTaskIdByAgentId(agentIdOrPrefix, includeFinished);
123
- if (exact) return exact;
124
-
125
- for (const [agentId, taskId] of agentTaskMap) {
126
- if (agentId.startsWith(agentIdOrPrefix)) return taskId;
127
- }
128
-
129
- return store.list().find(task => {
130
- if (!includeFinished && task.status !== "in_progress") return false;
131
- const agentId = task.metadata?.agentId;
132
- return typeof agentId === "string" && agentId.startsWith(agentIdOrPrefix);
133
- })?.id;
134
- }
135
-
136
- // ── Subagent service helpers ──
137
-
138
- /** Spawn a subagent via the bundled dm-subagents service. */
139
- async function spawnSubagent(type: string, prompt: string, options?: any): Promise<string> {
140
- const service = getReadySubagentsService();
141
- if (!service?.spawn) {
142
- throw new Error("Bundled dm-subagents is not loaded yet; restart dm or check bundled extensions.");
143
- }
144
- debug("spawn:call", { type, options: { ...options, prompt: undefined } });
145
- const id = await service.spawn(type, prompt, options);
146
- if (typeof id !== "string" || id.length === 0) {
147
- throw new Error("dm-subagents did not return an agent id");
148
- }
149
- debug("spawn:ok", { id });
150
- return id;
151
- }
152
-
153
- /** Stop a subagent via the bundled dm-subagents service. */
154
- async function stopSubagent(agentId: string): Promise<void> {
155
- const service = getSubagentsService();
156
- if (typeof service?.abort !== "function") return;
157
- try {
158
- await service.abort(agentId);
159
- } catch (err) {
160
- debug("stop:error", { agentId, err });
161
- }
162
- }
163
-
164
- /** Build a prompt for a task being executed by a subagent.
165
- * Injects completed dependency results so cascaded agents have context from prerequisites.
166
- */
167
- function buildTaskPrompt(
168
- task: { id: string; subject: string; description: string; blockedBy?: string[] },
169
- additionalContext?: string,
170
- ): string {
171
- let prompt = `You are executing task #${task.id}: "${task.subject}"\n\n${task.description}`;
172
-
173
- // Inject completed dependency results so cascaded agents have full context
174
- if (task.blockedBy && task.blockedBy.length > 0) {
175
- const depResults: string[] = [];
176
- for (const depId of task.blockedBy) {
177
- const dep = store.get(depId);
178
- if (dep?.metadata?.result) {
179
- const result = dep.metadata.result.length > 4000
180
- ? dep.metadata.result.slice(0, 4000) + "\n\n[... truncated — use TaskGet for full output]"
181
- : dep.metadata.result;
182
- depResults.push(`### Task #${depId}: ${dep.subject}\n${result}`);
183
- }
184
- }
185
- if (depResults.length > 0) {
186
- prompt += `\n\n## Prerequisite task results\n\n${depResults.join("\n\n")}`;
187
- }
188
- }
189
-
190
- if (additionalContext) prompt += `\n\n${additionalContext}`;
191
- prompt += `\n\nComplete this task fully. Do not attempt to manage tasks yourself.`;
192
- return prompt;
193
- }
194
-
195
- const autoClear = new AutoClearManager(() => store, () => cfg.autoClearCompleted ?? "on_list_complete", AUTO_CLEAR_DELAY);
196
-
197
- function reconcileTerminalSubagentRecord(taskId: string, record: SubagentRecordSnapshot): boolean {
198
- const task = store.get(taskId);
199
- if (!task || task.status !== "in_progress") return false;
200
-
201
- if (record.status === "completed" || record.status === "steered" || record.status === "stopped") {
202
- agentTaskMap.delete(record.id);
203
- const metadata = { ...task.metadata };
204
- if (record.result !== undefined) metadata.result = record.result;
205
- store.update(task.id, { status: "completed", metadata });
206
- widget.setActiveTask(task.id, false);
207
- autoClear.trackCompletion(task.id, currentTurn);
208
- debug("reconciled terminal subagent", { taskId: task.id, agentId: record.id, status: record.status });
209
- return true;
210
- }
211
-
212
- if (record.status === "error" || record.status === "aborted") {
213
- agentTaskMap.delete(record.id);
214
- store.update(task.id, {
215
- status: "pending",
216
- metadata: { ...task.metadata, lastError: record.error ?? record.status },
217
- });
218
- widget.setActiveTask(task.id, false);
219
- autoClear.resetBatchCountdown();
220
- debug("reconciled failed subagent", { taskId: task.id, agentId: record.id, status: record.status });
221
- return true;
222
- }
223
-
224
- return false;
225
- }
226
-
227
- function reconcileSubagentTasks(): void {
228
- const service = getSubagentsService();
229
- if (typeof service?.getRecord !== "function") return;
230
-
231
- let changed = false;
232
- for (const task of store.list()) {
233
- if (task.status !== "in_progress") continue;
234
- const agentId = task.metadata?.agentId;
235
- if (typeof agentId !== "string" || agentId.length === 0) continue;
236
-
237
- const record = service.getRecord(agentId);
238
- if (!record) continue;
239
- if (reconcileTerminalSubagentRecord(task.id, record)) changed = true;
240
- }
241
-
242
- if (changed) widget.update();
243
- }
244
-
245
- // ── Subagent completion listener ──
246
- // Listens for subagent lifecycle events to update task status and optionally cascade.
247
-
248
- // Success → mark task completed, cascade if enabled
249
- pi.events.on("subagents:completed", async (data) => {
250
- const { id, result } = data as { id: string; result?: string };
251
- const taskId = findTaskIdByAgentId(id);
252
- if (!taskId) return;
253
- agentTaskMap.delete(id);
254
- const task = store.get(taskId);
255
- if (!task) return;
256
-
257
- store.update(task.id, { status: "completed", metadata: { ...task.metadata, result } });
258
- widget.setActiveTask(task.id, false);
259
-
260
- // Auto-cascade: find unblocked dependents with agentType
261
- if ((cfg.autoCascade ?? false) && cascadeConfig && latestCtx) {
262
- const unblocked = store.list().filter(t =>
263
- t.status === "pending" &&
264
- t.metadata?.agentType &&
265
- t.blockedBy.includes(task.id) &&
266
- t.blockedBy.every(depId => store.get(depId)?.status === "completed")
267
- );
268
- for (const next of unblocked) {
269
- store.update(next.id, { status: "in_progress" });
270
- const prompt = buildTaskPrompt(next, cascadeConfig.additionalContext);
271
- try {
272
- const agentId = await spawnSubagent(next.metadata.agentType, prompt, {
273
- description: next.subject,
274
- isBackground: true,
275
- maxTurns: cascadeConfig.maxTurns,
276
- ...(cascadeConfig.model ? { model: cascadeConfig.model } : {}),
277
- });
278
- agentTaskMap.set(agentId, next.id);
279
- store.update(next.id, { owner: agentId, metadata: { ...next.metadata, agentId } });
280
- widget.setActiveTask(next.id);
281
- } catch (err: any) {
282
- store.update(next.id, { status: "pending", metadata: { ...next.metadata, lastError: err.message } });
283
- }
284
- }
285
- }
286
- autoClear.trackCompletion(task.id, currentTurn);
287
- widget.update();
288
- });
289
-
290
- // Failure → store error, revert to pending, don't cascade (branch stops)
291
- // Intentional stop (status === "stopped") → mark completed, preserve partial result
292
- pi.events.on("subagents:failed", (data) => {
293
- const { id, error, result, status } = data as { id: string; error?: string; result?: string; status: string };
294
- const taskId = findTaskIdByAgentId(id);
295
- if (!taskId) return;
296
- agentTaskMap.delete(id);
297
- const task = store.get(taskId);
298
- if (!task) return;
299
-
300
- if (status === "stopped") {
301
- // Intentional stop — mark completed, preserve partial result
302
- store.update(task.id, { status: "completed", metadata: { ...task.metadata, result: result || task.metadata?.result } });
303
- autoClear.trackCompletion(task.id, currentTurn);
304
- } else {
305
- // Actual error — revert to pending
306
- store.update(task.id, { status: "pending", metadata: { ...task.metadata, lastError: error || status } });
307
- autoClear.resetBatchCountdown();
308
- }
309
- widget.setActiveTask(task.id, false);
310
- widget.update();
311
- });
312
-
313
- // ── Session-scoped store upgrade ──
314
- // For session scope, the store starts in-memory (no session ID at init time).
315
- // Upgrade to file-backed on first context arrival (turn_start, before_agent_start,
316
- // or tool_execution_start — whichever fires first).
317
- let storeUpgraded = false;
318
- let persistedTasksShown = false;
319
- function upgradeStoreIfNeeded(ctx: ExtensionContext) {
320
- if (storeUpgraded) return;
321
- if (taskScope === "session" && !dmTasks) {
322
- const sessionId = ctx.sessionManager.getSessionId();
323
- const path = resolveStorePath(sessionId);
324
- store = new TaskStore(path);
325
- widget.setStore(store);
326
- }
327
- storeUpgraded = true;
328
- }
329
-
330
- /** Restore widget on session start/resume if there's unfinished work.
331
- * On new sessions, auto-clear if all tasks are completed (clean slate).
332
- * On resume, always show tasks (user may want to review).
333
- * Only runs once — the first caller wins. */
334
- function showPersistedTasks(isResume = false) {
335
- if (persistedTasksShown) return;
336
- persistedTasksShown = true;
337
- const tasks = store.list();
338
- if (tasks.length > 0) {
339
- if (!isResume && tasks.every(t => t.status === "completed")) {
340
- store.clearCompleted();
341
- if (taskScope === "session") store.deleteFileIfEmpty();
342
- } else {
343
- widget.update();
344
- }
345
- }
346
- }
347
-
348
- // ── Turn tracking for system-reminder injection ──
349
- let currentTurn = 0;
350
- let lastTaskToolUseTurn = 0;
351
- let reminderInjectedThisCycle = false;
352
-
353
- pi.on("turn_start", async (_event, ctx) => {
354
- currentTurn++;
355
- latestCtx = ctx;
356
- widget.setUICtx(ctx.ui as UICtx);
357
- upgradeStoreIfNeeded(ctx);
358
- if (autoClear.onTurnStart(currentTurn)) widget.update();
359
- });
360
-
361
- // ── Token usage tracking ──
362
- // Feed per-turn token counts from assistant messages into the widget.
363
- pi.on("turn_end", async (event) => {
364
- const msg = event.message as any;
365
- if (msg?.role === "assistant" && msg.usage) {
366
- widget.addTokenUsage(msg.usage.input ?? 0, msg.usage.output ?? 0);
367
- }
368
- });
369
-
370
- // ── System-reminder injection via tool_result event ──
371
- // Appends a <system-reminder> nudge to non-task tool results when tasks exist
372
- // but task tools haven't been used recently (mimics Claude Code's behavior).
373
- pi.on("tool_result", async (event) => {
374
- // Task tool usage resets the reminder timer
375
- if (TASK_TOOL_NAMES.has(event.toolName)) {
376
- lastTaskToolUseTurn = currentTurn;
377
- reminderInjectedThisCycle = false;
378
- return {};
379
- }
380
-
381
- // Cheap checks first — avoid store.list() disk I/O when possible
382
- if (currentTurn - lastTaskToolUseTurn < REMINDER_INTERVAL) return {};
383
- if (reminderInjectedThisCycle) return {};
384
-
385
- const tasks = store.list();
386
- if (tasks.length === 0) return {};
387
-
388
- // Append system-reminder to tool result content.
389
- // Reset the baseline so the next reminder fires REMINDER_INTERVAL turns later.
390
- reminderInjectedThisCycle = true;
391
- lastTaskToolUseTurn = currentTurn;
392
- return {
393
- content: [...event.content, { type: "text" as const, text: SYSTEM_REMINDER }],
394
- };
395
- });
396
-
397
- // Grab UI context early — before_agent_start fires before any tool calls,
398
- // so persisted tasks show up immediately on session start.
399
- pi.on("before_agent_start", async (_event, ctx) => {
400
- latestCtx = ctx;
401
- widget.setUICtx(ctx.ui as UICtx);
402
- upgradeStoreIfNeeded(ctx);
403
- showPersistedTasks();
404
- });
405
-
406
- // session_switch fires on /new (reason: "new") and /resume (reason: "resume").
407
- // On /new: reset all session-scoped state so the store switches to the new session file.
408
- // On resume: reload persisted tasks from the existing session file.
409
- pi.on("session_switch" as any, async (event: any, ctx: ExtensionContext) => {
410
- latestCtx = ctx;
411
- widget.setUICtx(ctx.ui as UICtx);
412
-
413
- const isResume = event?.reason === "resume";
414
-
415
- // Reset session-scoped state for both /new and /resume
416
- storeUpgraded = false;
417
- persistedTasksShown = false;
418
- currentTurn = 0;
419
- lastTaskToolUseTurn = 0;
420
- reminderInjectedThisCycle = false;
421
- autoClear.reset();
422
-
423
- // Memory mode has no file-backed store to switch — clear explicitly on /new
424
- if (!isResume && taskScope === "memory") {
425
- store.clearAll();
426
- }
427
-
428
- upgradeStoreIfNeeded(ctx);
429
- showPersistedTasks(isResume);
430
- });
431
-
432
- // Keep latestCtx fresh on every tool execution as well.
433
- pi.on("tool_execution_start", async (_event, ctx) => {
434
- latestCtx = ctx;
435
- widget.setUICtx(ctx.ui as UICtx);
436
- upgradeStoreIfNeeded(ctx);
437
- widget.update();
438
- });
439
-
440
- // ──────────────────────────────────────────────────
441
- // Tool 1: TaskCreate
442
- // ──────────────────────────────────────────────────
443
-
444
- pi.registerTool({
445
- name: "TaskCreate",
446
- label: "TaskCreate",
447
- description: `Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
448
- It also helps the user understand the progress of the task and overall progress of their requests.
449
-
450
- ## When to Use This Tool
451
-
452
- Use this tool proactively in these scenarios:
453
-
454
- - Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
455
- - Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
456
- - Plan mode - When using plan mode, create a task list to track the work
457
- - User explicitly requests todo list - When the user directly asks you to use the todo list
458
- - User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
459
- - After receiving new instructions - Immediately capture user requirements as tasks
460
- - When you start working on a task - Mark it as in_progress BEFORE beginning work
461
- - After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
462
-
463
- ## When NOT to Use This Tool
464
-
465
- Skip using this tool when:
466
- - There is only a single, straightforward task
467
- - The task is trivial and tracking it provides no organizational benefit
468
- - The task can be completed in less than 3 trivial steps
469
- - The task is purely conversational or informational
470
-
471
- NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
472
-
473
- ## Task Fields
474
-
475
- - **subject**: A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")
476
- - **description**: Detailed description of what needs to be done, including context and acceptance criteria
477
- - **activeForm** (optional): Present continuous form shown in the spinner when the task is in_progress (e.g., "Fixing authentication bug"). If omitted, the spinner shows the subject instead.
478
-
479
- All tasks are created with status \`pending\`.
480
-
481
- ## Tips
482
-
483
- - Create tasks with clear, specific subjects that describe the outcome
484
- - Include enough detail in the description for another agent to understand and complete the task
485
- - After creating tasks, use TaskUpdate to set up dependencies (blocks/blockedBy) if needed
486
- - Check TaskList first to avoid creating duplicate tasks
487
- - Include \`agentType\` (e.g., "general-purpose", "Explore") to mark tasks for subagent execution via TaskExecute`,
488
- promptGuidelines: [
489
- "When working on complex multi-step tasks, use TaskCreate to track progress and TaskUpdate to update status.",
490
- "Mark tasks as in_progress before starting work and completed when done.",
491
- "Use TaskList to check for available work after completing a task.",
492
- ],
493
- parameters: Type.Object({
494
- subject: Type.String({ description: "A brief title for the task" }),
495
- description: Type.String({ description: "A detailed description of what needs to be done" }),
496
- activeForm: Type.Optional(Type.String({ description: "Present continuous form shown in spinner when in_progress (e.g., 'Running tests')" })),
497
- agentType: Type.Optional(Type.String({ description: "Agent type for subagent execution (e.g., 'general-purpose', 'Explore'). Tasks with agentType can be started via TaskExecute." })),
498
- metadata: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "Arbitrary metadata to attach to the task" })),
499
- }),
500
-
501
- execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
502
- autoClear.resetBatchCountdown();
503
- const meta = params.metadata ?? {};
504
- if (params.agentType) meta.agentType = params.agentType;
505
- const task = store.create(params.subject, params.description, params.activeForm, Object.keys(meta).length > 0 ? meta : undefined);
506
- widget.update();
507
- return Promise.resolve(textResult(`Task #${task.id} created successfully: ${task.subject}`));
508
- },
509
- });
510
-
511
- // ──────────────────────────────────────────────────
512
- // Tool 2: TaskList
513
- // ──────────────────────────────────────────────────
514
-
515
- pi.registerTool({
516
- name: "TaskList",
517
- label: "TaskList",
518
- description: `Use this tool to list all tasks in the task list.
519
-
520
- ## When to Use This Tool
521
-
522
- - To see what tasks are available to work on (status: 'pending', no owner, not blocked)
523
- - To check overall progress on the project
524
- - To find tasks that are blocked and need dependencies resolved
525
- - After completing a task, to check for newly unblocked work or claim the next available task
526
- - **Prefer working on tasks in ID order** (lowest ID first) when multiple tasks are available, as earlier tasks often set up context for later ones
527
-
528
- ## Output
529
-
530
- Returns a summary of each task:
531
- - **id**: Task identifier (use with TaskGet, TaskUpdate)
532
- - **subject**: Brief description of the task
533
- - **status**: 'pending', 'in_progress', or 'completed'
534
- - **owner**: Agent ID if assigned, empty if available
535
- - **blockedBy**: List of open task IDs that must be resolved first (tasks with blockedBy cannot be claimed until dependencies resolve)
536
-
537
- Use TaskGet with a specific task ID to view full details including description and comments.`,
538
- parameters: Type.Object({}),
539
-
540
- execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
541
- reconcileSubagentTasks();
542
- const tasks = store.list();
543
- if (tasks.length === 0) return Promise.resolve(textResult("No tasks found"));
544
-
545
- // Sort: pending first (by ID), then in_progress (by ID), then completed (by ID)
546
- const statusOrder: Record<string, number> = { pending: 0, in_progress: 1, completed: 2 };
547
- const sorted = [...tasks].sort((a, b) => {
548
- const so = (statusOrder[a.status] ?? 0) - (statusOrder[b.status] ?? 0);
549
- if (so !== 0) return so;
550
- return Number(a.id) - Number(b.id);
551
- });
552
-
553
- const lines = sorted.map(task => {
554
- let line = `#${task.id} [${task.status}] ${task.subject}`;
555
-
556
- if (task.owner) {
557
- line += ` (${task.owner})`;
558
- }
559
-
560
- // Only show non-completed blockers
561
- if (task.blockedBy.length > 0) {
562
- const openBlockers = task.blockedBy.filter(bid => {
563
- const blocker = store.get(bid);
564
- return blocker && blocker.status !== "completed";
565
- });
566
- if (openBlockers.length > 0) {
567
- line += ` [blocked by ${openBlockers.map(id => "#" + id).join(", ")}]`;
568
- }
569
- }
570
-
571
- return line;
572
- });
573
-
574
- return Promise.resolve(textResult(lines.join("\n")));
575
- },
576
- });
577
-
578
- // ──────────────────────────────────────────────────
579
- // Tool 3: TaskGet
580
- // ──────────────────────────────────────────────────
581
-
582
- pi.registerTool({
583
- name: "TaskGet",
584
- label: "TaskGet",
585
- description: `Use this tool to retrieve a task by its ID from the task list.
586
-
587
- ## When to Use This Tool
588
-
589
- - When you need the full description and context before starting work on a task
590
- - To understand task dependencies (what it blocks, what blocks it)
591
- - After being assigned a task, to get complete requirements
592
-
593
- ## Output
594
-
595
- Returns full task details:
596
- - **subject**: Task title
597
- - **description**: Detailed requirements and context
598
- - **status**: 'pending', 'in_progress', or 'completed'
599
- - **blocks**: Tasks waiting on this one to complete
600
- - **blockedBy**: Tasks that must complete before this one can start
601
-
602
- ## Tips
603
-
604
- - After fetching a task, verify its blockedBy list is empty before beginning work.
605
- - Use TaskList to see all tasks in summary form.`,
606
- parameters: Type.Object({
607
- taskId: Type.String({ description: "The ID of the task to retrieve" }),
608
- }),
609
-
610
- execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
611
- reconcileSubagentTasks();
612
- const task = store.get(params.taskId);
613
- if (!task) return Promise.resolve(textResult(`Task not found`));
614
-
615
- // Unescape literal \n sequences the LLM may have double-escaped in JSON
616
- const desc = task.description.replace(/\\n/g, "\n");
617
-
618
- const lines: string[] = [
619
- `Task #${task.id}: ${task.subject}`,
620
- `Status: ${task.status}`,
621
- ];
622
- if (task.owner) {
623
- lines.push(`Owner: ${task.owner}`);
624
- }
625
- lines.push(`Description: ${desc}`);
626
-
627
- if (task.blockedBy.length > 0) {
628
- const openBlockers = task.blockedBy.filter(bid => {
629
- const blocker = store.get(bid);
630
- return blocker && blocker.status !== "completed";
631
- });
632
- if (openBlockers.length > 0) {
633
- lines.push(`Blocked by: ${openBlockers.map(id => "#" + id).join(", ")}`);
634
- }
635
- }
636
- if (task.blocks.length > 0) {
637
- lines.push(`Blocks: ${task.blocks.map(id => "#" + id).join(", ")}`);
638
- }
639
-
640
- // Show metadata if non-empty
641
- const metaKeys = Object.keys(task.metadata);
642
- if (metaKeys.length > 0) {
643
- lines.push(`Metadata: ${JSON.stringify(task.metadata)}`);
644
- }
645
-
646
- return Promise.resolve(textResult(lines.join("\n")));
647
- },
648
- });
649
-
650
- // ──────────────────────────────────────────────────
651
- // Tool 4: TaskUpdate
652
- // ──────────────────────────────────────────────────
653
-
654
- pi.registerTool({
655
- name: "TaskUpdate",
656
- label: "TaskUpdate",
657
- description: `Use this tool to update a task in the task list.
658
-
659
- ## When to Use This Tool
660
-
661
- **Before starting work on a task:**
662
- - Mark it in_progress BEFORE beginning — do not start work without updating status first
663
- - After resolving, call TaskList to find your next task
664
-
665
- **Mark tasks as resolved:**
666
- - When you have completed the work described in a task
667
- - When a task is no longer needed or has been superseded
668
- - IMPORTANT: Always mark your assigned tasks as resolved when you finish them
669
- - After resolving, call TaskList to find your next task
670
-
671
- - ONLY mark a task as completed when you have FULLY accomplished it
672
- - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
673
- - When blocked, create a new task describing what needs to be resolved
674
- - Never mark a task as completed if:
675
- - Tests are failing
676
- - Implementation is partial
677
- - You encountered unresolved errors
678
- - You couldn't find necessary files or dependencies
679
-
680
- **Delete tasks:**
681
- - When a task is no longer relevant or was created in error
682
- - Setting status to \`deleted\` permanently removes the task
683
-
684
- **Update task details:**
685
- - When requirements change or become clearer
686
- - When establishing dependencies between tasks
687
-
688
- ## Fields You Can Update
689
-
690
- - **status**: The task status (see Status Workflow below)
691
- - **subject**: Change the task title (imperative form, e.g., "Run tests")
692
- - **description**: Change the task description
693
- - **activeForm**: Present continuous form shown in spinner when in_progress (e.g., "Running tests")
694
- - **owner**: Change the task owner (agent name)
695
- - **metadata**: Merge metadata keys into the task (set a key to null to delete it)
696
- - **addBlocks**: Mark tasks that cannot start until this one completes
697
- - **addBlockedBy**: Mark tasks that must complete before this one can start
698
-
699
- ## Status Workflow
700
-
701
- Status progresses: \`pending\` → \`in_progress\` → \`completed\`
702
-
703
- Use \`deleted\` to permanently remove a task.
704
-
705
- ## Staleness
706
-
707
- Make sure to read a task's latest state using \`TaskGet\` before updating it.
708
-
709
- ## Examples
710
-
711
- Mark task as in progress when starting work:
712
- \`\`\`json
713
- {"taskId": "1", "status": "in_progress"}
714
- \`\`\`
715
-
716
- Mark task as completed after finishing work:
717
- \`\`\`json
718
- {"taskId": "1", "status": "completed"}
719
- \`\`\`
720
-
721
- Delete a task:
722
- \`\`\`json
723
- {"taskId": "1", "status": "deleted"}
724
- \`\`\`
725
-
726
- Claim a task by setting owner:
727
- \`\`\`json
728
- {"taskId": "1", "owner": "my-name"}
729
- \`\`\`
730
-
731
- Set up task dependencies:
732
- \`\`\`json
733
- {"taskId": "2", "addBlockedBy": ["1"]}
734
- \`\`\``,
735
- parameters: Type.Object({
736
- taskId: Type.String({ description: "The ID of the task to update" }),
737
- status: Type.Optional(Type.Unsafe<"pending" | "in_progress" | "completed" | "deleted">({
738
- type: "string",
739
- enum: ["pending", "in_progress", "completed", "deleted"],
740
- description: "New status for the task",
741
- })),
742
- subject: Type.Optional(Type.String({ description: "New subject for the task" })),
743
- description: Type.Optional(Type.String({ description: "New description for the task" })),
744
- activeForm: Type.Optional(Type.String({ description: "Present continuous form shown in spinner when in_progress" })),
745
- owner: Type.Optional(Type.String({ description: "New owner for the task" })),
746
- metadata: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "Metadata keys to merge into the task. Set a key to null to delete it." })),
747
- addBlocks: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that this task blocks" })),
748
- addBlockedBy: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that block this task" })),
749
- }),
750
-
751
- execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
752
- reconcileSubagentTasks();
753
- const { taskId, ...fields } = params;
754
- const { task, changedFields, warnings } = store.update(taskId, fields);
755
-
756
- if (changedFields.length === 0 && !task) {
757
- return Promise.resolve(textResult(`Task #${taskId} not found`));
758
- }
759
-
760
- // Update widget active task tracking
761
- if (fields.status === "in_progress") {
762
- widget.setActiveTask(taskId);
763
- autoClear.resetBatchCountdown();
764
- } else if (fields.status === "pending") {
765
- autoClear.resetBatchCountdown();
766
- } else if (fields.status === "completed" || fields.status === "deleted") {
767
- widget.setActiveTask(taskId, false);
768
- if (fields.status === "completed") autoClear.trackCompletion(taskId, currentTurn);
769
- }
770
-
771
- widget.update();
772
- let msg = `Updated task #${taskId} ${changedFields.join(", ")}`;
773
- if (warnings.length > 0) {
774
- msg += ` (warning: ${warnings.join("; ")})`;
775
- }
776
- return Promise.resolve(textResult(msg));
777
- },
778
- });
779
-
780
- // ──────────────────────────────────────────────────
781
- // Tool 5: TaskOutput
782
- // ──────────────────────────────────────────────────
783
-
784
- pi.registerTool({
785
- name: "TaskOutput",
786
- label: "TaskOutput",
787
- description: `- Retrieves output from a running or completed task (background shell, agent, or remote session)
788
- - Takes a task_id parameter identifying the task
789
- - Returns the task output along with status information
790
- - Use block=true (default) to wait for task completion
791
- - Use block=false for non-blocking check of current status
792
- - Task IDs can be found using the /tasks command
793
- - Works with all task types: background shells, async agents, and remote sessions`,
794
- parameters: Type.Object({
795
- task_id: Type.String({ description: "The task ID to get output from" }),
796
- block: Type.Boolean({ description: "Whether to wait for completion", default: true }),
797
- timeout: Type.Number({ description: "Max wait time in ms", default: 30000, minimum: 0, maximum: 600000 }),
798
- }),
799
-
800
- async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
801
- reconcileSubagentTasks();
802
- const { task_id, block, timeout } = params;
803
-
804
- const processOutput = tracker.getOutput(task_id);
805
- if (!processOutput) {
806
- // No shell process — check if this is a subagent task
807
- // Support both task IDs and agent IDs (resolve agent ID → task ID)
808
- let resolvedId = task_id;
809
- if (!store.get(resolvedId)) {
810
- resolvedId = findTaskIdByAgentIdPrefix(task_id, true) ?? resolvedId;
811
- }
812
- const task = store.get(resolvedId);
813
- if (!task) throw new Error(`No task found with ID ${task_id}`);
814
-
815
- if (task.metadata?.agentId) {
816
- // Subagent task — wait for completion if blocking
817
- if (block && task.status === "in_progress") {
818
- await new Promise<void>((resolve) => {
819
- const timer = setTimeout(() => { unsubOk(); unsubFail(); resolve(); }, timeout ?? 30000);
820
- const cleanup = () => { clearTimeout(timer); resolve(); };
821
- const unsubOk = pi.events.on("subagents:completed", (d: unknown) => {
822
- if ((d as any).id === task.metadata?.agentId) { unsubOk(); unsubFail(); cleanup(); }
823
- });
824
- const unsubFail = pi.events.on("subagents:failed", (d: unknown) => {
825
- if ((d as any).id === task.metadata?.agentId) { unsubOk(); unsubFail(); cleanup(); }
826
- });
827
- // Re-check in case status changed between the outer check and listener registration
828
- const current = store.get(resolvedId);
829
- if (current && current.status !== "in_progress") { unsubOk(); unsubFail(); cleanup(); }
830
- signal?.addEventListener("abort", () => { unsubOk(); unsubFail(); cleanup(); }, { once: true });
831
- });
832
- }
833
- reconcileSubagentTasks();
834
- const updated = store.get(resolvedId) ?? task;
835
- return textResult(`Task #${resolvedId} [${updated.status}] — subagent ${task.metadata.agentId}`);
836
- }
837
- throw new Error(`No background process for task ${task_id}`);
838
- }
839
-
840
- if (block && processOutput.status === "running") {
841
- const result = await tracker.waitForCompletion(task_id, timeout ?? 30000, signal ?? undefined);
842
- if (result) {
843
- return textResult(
844
- `Task #${task_id} (${result.status})${result.exitCode !== undefined ? ` exit code: ${result.exitCode}` : ""}\n\n${result.output}`,
845
- );
846
- }
847
- }
848
-
849
- return textResult(
850
- `Task #${task_id} (${processOutput.status})${processOutput.exitCode !== undefined ? ` exit code: ${processOutput.exitCode}` : ""}\n\n${processOutput.output}`,
851
- );
852
- },
853
- });
854
-
855
- // ──────────────────────────────────────────────────
856
- // Tool 6: TaskStop
857
- // ──────────────────────────────────────────────────
858
-
859
- pi.registerTool({
860
- name: "TaskStop",
861
- label: "TaskStop",
862
- description: `
863
- - Stops a running background task by its ID
864
- - Takes a task_id parameter identifying the task to stop
865
- - Returns a success or failure status
866
- - Use this tool when you need to terminate a long-running task`,
867
- parameters: Type.Object({
868
- task_id: Type.Optional(Type.String({ description: "The ID of the background task to stop" })),
869
- shell_id: Type.Optional(Type.String({ description: "Deprecated: use task_id instead" })),
870
- }),
871
-
872
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
873
- reconcileSubagentTasks();
874
- const taskId = params.task_id ?? params.shell_id;
875
- if (!taskId) throw new Error("task_id is required");
876
-
877
- const stopped = await tracker.stop(taskId);
878
- if (!stopped) {
879
- // No shell process — check if this is a subagent task
880
- // Support both task IDs and agent IDs
881
- let resolvedId = taskId;
882
- if (!store.get(resolvedId)) {
883
- resolvedId = findTaskIdByAgentIdPrefix(taskId, true) ?? resolvedId;
884
- }
885
- const task = store.get(resolvedId);
886
- if (task?.metadata?.agentId && task.status === "in_progress") {
887
- store.update(resolvedId, { status: "completed" });
888
- autoClear.trackCompletion(resolvedId, currentTurn);
889
- await stopSubagent(task.metadata.agentId);
890
- widget.setActiveTask(resolvedId, false);
891
- widget.update();
892
- return textResult(`Task #${resolvedId} stopped successfully`);
893
- }
894
- throw new Error(`No running background process for task ${taskId}`);
895
- }
896
-
897
- store.update(taskId, { status: "completed" });
898
- autoClear.trackCompletion(taskId, currentTurn);
899
- widget.setActiveTask(taskId, false);
900
- widget.update();
901
- return textResult(`Task #${taskId} stopped successfully`);
902
- },
903
- });
904
-
905
- // ──────────────────────────────────────────────────
906
- // Tool 7: TaskExecute
907
- // ──────────────────────────────────────────────────
908
-
909
- pi.registerTool({
910
- name: "TaskExecute",
911
- label: "TaskExecute",
912
- description: `Execute one or more tasks as subagents.
913
-
914
- ## When to Use This Tool
915
-
916
- - To start execution of tasks that have \`agentType\` set (created via TaskCreate with agentType parameter)
917
- - Tasks must be \`pending\` with all blockedBy dependencies \`completed\`
918
- - Each task runs as an independent background subagent
919
-
920
- ## Parameters
921
-
922
- - **task_ids**: Array of task IDs to execute
923
- - **additional_context**: Extra context appended to each agent's prompt
924
- - **model**: Model override for agents (e.g., "sonnet", "haiku")
925
- - **max_turns**: Maximum turns per agent`,
926
- promptGuidelines: [
927
- "Never use the Agent tool for tasks launched via TaskExecute — agents are already running.",
928
- ],
929
- parameters: Type.Object({
930
- task_ids: Type.Array(Type.String(), { description: "Task IDs to execute as subagents" }),
931
- additional_context: Type.Optional(Type.String({ description: "Extra context for agent prompts" })),
932
- model: Type.Optional(Type.String({ description: "Model override for agents" })),
933
- max_turns: Type.Optional(Type.Number({ description: "Max turns per agent", minimum: 1 })),
934
- }),
935
-
936
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
937
- reconcileSubagentTasks();
938
- if (!getReadySubagentsService()) {
939
- return textResult(
940
- "Subagent execution is currently unavailable. " +
941
- "Bundled dm-subagents is not loaded yet; restart dm or check bundled extensions."
942
- );
943
- }
944
-
945
- const results: string[] = [];
946
- const launched: string[] = [];
947
-
948
- for (const taskId of params.task_ids) {
949
- const task = store.get(taskId);
950
- if (!task) {
951
- results.push(`#${taskId}: not found`);
952
- continue;
953
- }
954
- if (task.status !== "pending") {
955
- results.push(`#${taskId}: not pending (status: ${task.status})`);
956
- continue;
957
- }
958
- if (!task.metadata?.agentType) {
959
- results.push(`#${taskId}: no agentType set — create with agentType parameter or update metadata`);
960
- continue;
961
- }
962
-
963
- // Check all blockers are completed
964
- const openBlockers = task.blockedBy.filter(bid => {
965
- const blocker = store.get(bid);
966
- return !blocker || blocker.status !== "completed";
967
- });
968
- if (openBlockers.length > 0) {
969
- results.push(`#${taskId}: blocked by ${openBlockers.map(id => "#" + id).join(", ")}`);
970
- continue;
971
- }
972
-
973
- // Mark in_progress and spawn agent via RPC
974
- store.update(taskId, { status: "in_progress" });
975
- const prompt = buildTaskPrompt(task, params.additional_context);
976
- try {
977
- const agentId = await spawnSubagent(task.metadata.agentType, prompt, {
978
- description: task.subject,
979
- isBackground: true,
980
- maxTurns: params.max_turns,
981
- ...(params.model ? { model: params.model } : {}),
982
- });
983
- agentTaskMap.set(agentId, taskId);
984
- store.update(taskId, { owner: agentId, metadata: { ...task.metadata, agentId } });
985
- widget.setActiveTask(taskId);
986
- launched.push(`#${taskId} → agent ${agentId}`);
987
- } catch (err: any) {
988
- debug(`spawn:error task=#${taskId}`, err);
989
- store.update(taskId, { status: "pending" });
990
- results.push(`#${taskId}: spawn failed — ${err.message}`);
991
- }
992
- }
993
-
994
- // Save cascade config for the completion listener
995
- cascadeConfig = {
996
- additionalContext: params.additional_context,
997
- model: params.model,
998
- maxTurns: params.max_turns,
999
- };
1000
-
1001
- widget.update();
1002
-
1003
- const lines: string[] = [];
1004
- if (launched.length > 0) {
1005
- lines.push(
1006
- `Launched ${launched.length} agent(s):\n${launched.join("\n")}\n` +
1007
- `Use TaskOutput to check progress. Do not spawn additional agents for these tasks.`
1008
- );
1009
- }
1010
- if (results.length > 0) lines.push(`Skipped:\n${results.join("\n")}`);
1011
- if (lines.length === 0) lines.push("No tasks to execute.");
1012
-
1013
- return textResult(lines.join("\n\n"));
1014
- },
1015
- });
1016
-
1017
- // ──────────────────────────────────────────────────
1018
- // /tasks command
1019
- // ──────────────────────────────────────────────────
1020
-
1021
- pi.registerCommand("tasks", {
1022
- description: "Manage tasks — view, create, clear completed",
1023
- handler: async (_args: string, ctx: ExtensionCommandContext) => {
1024
- const ui = ctx.ui;
1025
-
1026
- const mainMenu = async (): Promise<void> => {
1027
- const tasks = store.list();
1028
- const taskCount = tasks.length;
1029
- const completedCount = tasks.filter(t => t.status === "completed").length;
1030
-
1031
- const choices: string[] = [
1032
- `View all tasks (${taskCount})`,
1033
- "Create task",
1034
- ];
1035
- if (completedCount > 0) choices.push(`Clear completed (${completedCount})`);
1036
- if (taskCount > 0) choices.push(`Clear all (${taskCount})`);
1037
- choices.push("Settings");
1038
-
1039
- const choice = await ui.select("Tasks", choices);
1040
- if (!choice) return;
1041
-
1042
- if (choice.startsWith("View")) {
1043
- await viewTasks();
1044
- } else if (choice === "Create task") {
1045
- await createTask();
1046
- } else if (choice === "Settings") {
1047
- await settingsMenu();
1048
- } else if (choice.startsWith("Clear completed")) {
1049
- store.clearCompleted();
1050
- if (taskScope === "session") store.deleteFileIfEmpty();
1051
- widget.update();
1052
- await mainMenu();
1053
- } else if (choice.startsWith("Clear all")) {
1054
- store.clearAll();
1055
- if (taskScope === "session") store.deleteFileIfEmpty();
1056
- widget.update();
1057
- await mainMenu();
1058
- }
1059
- };
1060
-
1061
- const viewTasks = async (): Promise<void> => {
1062
- const tasks = store.list();
1063
- if (tasks.length === 0) {
1064
- await ui.select("No tasks", ["← Back"]);
1065
- return mainMenu();
1066
- }
1067
-
1068
- const statusIcon = (status: string) => {
1069
- switch (status) {
1070
- case "completed": return "✔";
1071
- case "in_progress": return "◼";
1072
- default: return "◻";
1073
- }
1074
- };
1075
-
1076
- const choices = tasks.map(t =>
1077
- `${statusIcon(t.status)} #${t.id} [${t.status}] ${t.subject}`
1078
- );
1079
- choices.push("← Back");
1080
-
1081
- const selected = await ui.select("Tasks", choices);
1082
- if (!selected || selected === "← Back") return mainMenu();
1083
-
1084
- // Extract task ID from selection
1085
- const match = selected.match(/#(\d+)/);
1086
- if (match) await viewTaskDetail(match[1]);
1087
- else return viewTasks();
1088
- };
1089
-
1090
- const viewTaskDetail = async (taskId: string): Promise<void> => {
1091
- const task = store.get(taskId);
1092
- if (!task) return viewTasks();
1093
-
1094
- const actions: string[] = [];
1095
-
1096
- if (task.status === "pending") {
1097
- actions.push("▸ Start (in_progress)");
1098
- }
1099
- if (task.status === "in_progress") {
1100
- actions.push("✓ Complete");
1101
- }
1102
- actions.push("✗ Delete");
1103
- actions.push("← Back");
1104
-
1105
- const title = `#${task.id} [${task.status}] ${task.subject}\n${task.description}`;
1106
- const action = await ui.select(title, actions);
1107
-
1108
- if (action === "▸ Start (in_progress)") {
1109
- store.update(taskId, { status: "in_progress" });
1110
- widget.setActiveTask(taskId);
1111
- widget.update();
1112
- return viewTasks();
1113
- } else if (action === "✓ Complete") {
1114
- store.update(taskId, { status: "completed" });
1115
- autoClear.trackCompletion(taskId, currentTurn);
1116
- widget.setActiveTask(taskId, false);
1117
- widget.update();
1118
- return viewTasks();
1119
- } else if (action === "✗ Delete") {
1120
- store.update(taskId, { status: "deleted" });
1121
- widget.setActiveTask(taskId, false);
1122
- widget.update();
1123
- return viewTasks();
1124
- }
1125
- return viewTasks();
1126
- };
1127
-
1128
- const settingsMenu = (): Promise<void> =>
1129
- openSettingsMenu(ui, cfg, mainMenu, AUTO_CLEAR_DELAY);
1130
-
1131
- const createTask = async (): Promise<void> => {
1132
- const subject = await ui.input("Task subject");
1133
- if (!subject) return mainMenu();
1134
- const description = await ui.input("Task description");
1135
- if (!description) return mainMenu();
1136
-
1137
- store.create(subject, description);
1138
- widget.update();
1139
- return mainMenu();
1140
- };
1141
-
1142
- await mainMenu();
1143
- },
1144
- });
1145
- }