@duckmind/dm-darwin-x64 0.43.2 → 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 (256) hide show
  1. package/dm +0 -0
  2. package/extensions/.dm-extensions.json +242 -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/bin/search.mjs +135 -135
  126. package/extensions/greedysearch-dm/index.js +20 -0
  127. package/extensions/greedysearch-dm/package.json +1 -22
  128. package/extensions/greedysearch-dm/src/formatters/results.js +8 -0
  129. package/extensions/greedysearch-dm/src/formatters/sources.js +1 -0
  130. package/extensions/greedysearch-dm/src/formatters/synthesis.js +1 -0
  131. package/extensions/greedysearch-dm/src/search/engines.mjs +9 -9
  132. package/extensions/greedysearch-dm/src/search/paths.mjs +1 -0
  133. package/extensions/greedysearch-dm/src/search/research.mjs +100 -100
  134. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +10 -10
  135. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +20 -0
  136. package/extensions/greedysearch-dm/src/tools/shared.js +9 -0
  137. package/extensions/greedysearch-dm/src/types.js +0 -0
  138. package/extensions/greedysearch-dm/src/utils/helpers.js +1 -0
  139. package/package.json +1 -1
  140. package/extensions/dm-9router-ext/src/index.ts +0 -541
  141. package/extensions/dm-ask-user/index.ts +0 -857
  142. package/extensions/dm-context/src/context.ts +0 -158
  143. package/extensions/dm-context/src/index.ts +0 -439
  144. package/extensions/dm-context/src/utils.ts +0 -6
  145. package/extensions/dm-fff/src/index.ts +0 -1023
  146. package/extensions/dm-fff/src/query.ts +0 -87
  147. package/extensions/dm-goal/src/goal.ts +0 -1073
  148. package/extensions/dm-grill-me/index.ts +0 -1085
  149. package/extensions/dm-subagents/src/agents/agent-management.ts +0 -1052
  150. package/extensions/dm-subagents/src/agents/agent-memory.ts +0 -254
  151. package/extensions/dm-subagents/src/agents/agent-scope.ts +0 -6
  152. package/extensions/dm-subagents/src/agents/agent-selection.ts +0 -25
  153. package/extensions/dm-subagents/src/agents/agent-serializer.ts +0 -121
  154. package/extensions/dm-subagents/src/agents/agents.ts +0 -1553
  155. package/extensions/dm-subagents/src/agents/chain-serializer.ts +0 -277
  156. package/extensions/dm-subagents/src/agents/frontmatter.ts +0 -93
  157. package/extensions/dm-subagents/src/agents/identity.ts +0 -30
  158. package/extensions/dm-subagents/src/agents/proactive-skills.ts +0 -191
  159. package/extensions/dm-subagents/src/agents/skills.ts +0 -729
  160. package/extensions/dm-subagents/src/extension/config.ts +0 -39
  161. package/extensions/dm-subagents/src/extension/control-notices.ts +0 -92
  162. package/extensions/dm-subagents/src/extension/doctor.ts +0 -214
  163. package/extensions/dm-subagents/src/extension/fanout-child.ts +0 -172
  164. package/extensions/dm-subagents/src/extension/index.ts +0 -656
  165. package/extensions/dm-subagents/src/extension/rpc.ts +0 -369
  166. package/extensions/dm-subagents/src/extension/schemas.ts +0 -309
  167. package/extensions/dm-subagents/src/intercom/intercom-bridge.ts +0 -180
  168. package/extensions/dm-subagents/src/intercom/native-supervisor-channel.ts +0 -519
  169. package/extensions/dm-subagents/src/intercom/result-intercom.ts +0 -377
  170. package/extensions/dm-subagents/src/profiles/profiles.ts +0 -637
  171. package/extensions/dm-subagents/src/runs/background/async-execution.ts +0 -1065
  172. package/extensions/dm-subagents/src/runs/background/async-job-tracker.ts +0 -441
  173. package/extensions/dm-subagents/src/runs/background/async-resume.ts +0 -391
  174. package/extensions/dm-subagents/src/runs/background/async-status.ts +0 -395
  175. package/extensions/dm-subagents/src/runs/background/chain-append.ts +0 -282
  176. package/extensions/dm-subagents/src/runs/background/chain-root-attachment.ts +0 -191
  177. package/extensions/dm-subagents/src/runs/background/completion-batcher.ts +0 -166
  178. package/extensions/dm-subagents/src/runs/background/completion-dedupe.ts +0 -63
  179. package/extensions/dm-subagents/src/runs/background/control-channel.ts +0 -332
  180. package/extensions/dm-subagents/src/runs/background/fleet-view.ts +0 -515
  181. package/extensions/dm-subagents/src/runs/background/notify.ts +0 -225
  182. package/extensions/dm-subagents/src/runs/background/parallel-groups.ts +0 -45
  183. package/extensions/dm-subagents/src/runs/background/result-watcher.ts +0 -315
  184. package/extensions/dm-subagents/src/runs/background/run-id-resolver.ts +0 -84
  185. package/extensions/dm-subagents/src/runs/background/run-status.ts +0 -434
  186. package/extensions/dm-subagents/src/runs/background/scheduled-runs.ts +0 -514
  187. package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.ts +0 -368
  188. package/extensions/dm-subagents/src/runs/background/subagent-runner.ts +0 -3171
  189. package/extensions/dm-subagents/src/runs/background/top-level-async.ts +0 -13
  190. package/extensions/dm-subagents/src/runs/background/wait.ts +0 -353
  191. package/extensions/dm-subagents/src/runs/foreground/chain-clarify.ts +0 -1333
  192. package/extensions/dm-subagents/src/runs/foreground/chain-execution.ts +0 -1313
  193. package/extensions/dm-subagents/src/runs/foreground/execution.ts +0 -1239
  194. package/extensions/dm-subagents/src/runs/foreground/subagent-executor.ts +0 -3613
  195. package/extensions/dm-subagents/src/runs/shared/acceptance.ts +0 -879
  196. package/extensions/dm-subagents/src/runs/shared/chain-outputs.ts +0 -116
  197. package/extensions/dm-subagents/src/runs/shared/completion-guard.ts +0 -147
  198. package/extensions/dm-subagents/src/runs/shared/dm-args.ts +0 -271
  199. package/extensions/dm-subagents/src/runs/shared/dm-spawn.ts +0 -147
  200. package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.ts +0 -295
  201. package/extensions/dm-subagents/src/runs/shared/long-running-guard.ts +0 -175
  202. package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +0 -365
  203. package/extensions/dm-subagents/src/runs/shared/model-fallback.ts +0 -292
  204. package/extensions/dm-subagents/src/runs/shared/model-scope.ts +0 -128
  205. package/extensions/dm-subagents/src/runs/shared/nested-events.ts +0 -908
  206. package/extensions/dm-subagents/src/runs/shared/nested-path.ts +0 -52
  207. package/extensions/dm-subagents/src/runs/shared/nested-render.ts +0 -115
  208. package/extensions/dm-subagents/src/runs/shared/parallel-utils.ts +0 -198
  209. package/extensions/dm-subagents/src/runs/shared/run-history.ts +0 -60
  210. package/extensions/dm-subagents/src/runs/shared/single-output.ts +0 -180
  211. package/extensions/dm-subagents/src/runs/shared/structured-output.ts +0 -77
  212. package/extensions/dm-subagents/src/runs/shared/subagent-control.ts +0 -223
  213. package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.ts +0 -342
  214. package/extensions/dm-subagents/src/runs/shared/tool-budget.ts +0 -74
  215. package/extensions/dm-subagents/src/runs/shared/turn-budget.ts +0 -52
  216. package/extensions/dm-subagents/src/runs/shared/workflow-graph.ts +0 -206
  217. package/extensions/dm-subagents/src/runs/shared/worktree.ts +0 -600
  218. package/extensions/dm-subagents/src/shared/artifacts.ts +0 -113
  219. package/extensions/dm-subagents/src/shared/atomic-json.ts +0 -86
  220. package/extensions/dm-subagents/src/shared/child-transcript.ts +0 -212
  221. package/extensions/dm-subagents/src/shared/file-coalescer.ts +0 -40
  222. package/extensions/dm-subagents/src/shared/fork-context.ts +0 -194
  223. package/extensions/dm-subagents/src/shared/formatters.ts +0 -133
  224. package/extensions/dm-subagents/src/shared/jsonl-writer.ts +0 -81
  225. package/extensions/dm-subagents/src/shared/model-info.ts +0 -78
  226. package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.ts +0 -85
  227. package/extensions/dm-subagents/src/shared/session-identity.ts +0 -10
  228. package/extensions/dm-subagents/src/shared/session-tokens.ts +0 -44
  229. package/extensions/dm-subagents/src/shared/settings.ts +0 -450
  230. package/extensions/dm-subagents/src/shared/status-format.ts +0 -49
  231. package/extensions/dm-subagents/src/shared/types.ts +0 -1257
  232. package/extensions/dm-subagents/src/shared/utils.ts +0 -554
  233. package/extensions/dm-subagents/src/slash/prompt-template-bridge.ts +0 -420
  234. package/extensions/dm-subagents/src/slash/prompt-workflows.ts +0 -330
  235. package/extensions/dm-subagents/src/slash/slash-bridge.ts +0 -176
  236. package/extensions/dm-subagents/src/slash/slash-commands.ts +0 -1296
  237. package/extensions/dm-subagents/src/slash/slash-live-state.ts +0 -292
  238. package/extensions/dm-subagents/src/tui/render-helpers.ts +0 -80
  239. package/extensions/dm-subagents/src/tui/render.ts +0 -1748
  240. package/extensions/dm-tasks/src/auto-clear.ts +0 -91
  241. package/extensions/dm-tasks/src/index.ts +0 -1145
  242. package/extensions/dm-tasks/src/process-tracker.ts +0 -140
  243. package/extensions/dm-tasks/src/task-store.ts +0 -305
  244. package/extensions/dm-tasks/src/tasks-config.ts +0 -23
  245. package/extensions/dm-tasks/src/types.ts +0 -40
  246. package/extensions/dm-tasks/src/ui/settings-menu.ts +0 -100
  247. package/extensions/dm-tasks/src/ui/task-widget.ts +0 -265
  248. package/extensions/dm-usage/index.ts +0 -1718
  249. package/extensions/greedysearch-dm/index.ts +0 -177
  250. package/extensions/greedysearch-dm/src/formatters/results.ts +0 -163
  251. package/extensions/greedysearch-dm/src/formatters/sources.ts +0 -116
  252. package/extensions/greedysearch-dm/src/formatters/synthesis.ts +0 -87
  253. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.ts +0 -370
  254. package/extensions/greedysearch-dm/src/tools/shared.ts +0 -187
  255. package/extensions/greedysearch-dm/src/types.ts +0 -110
  256. package/extensions/greedysearch-dm/src/utils/helpers.ts +0 -40
@@ -1,514 +0,0 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import type { AgentToolResult } from "@duckmind/dm-agent-core";
5
- import type { ExtensionContext } from "@duckmind/dm-coding-agent";
6
- import { writeAtomicJson } from "../../shared/atomic-json.ts";
7
- import { formatDuration, shortenPath } from "../../shared/formatters.ts";
8
- import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
9
- import {
10
- TEMP_ROOT_DIR,
11
- type Details,
12
- type ExtensionConfig,
13
- } from "../../shared/types.ts";
14
- import type { SubagentParamsLike } from "../foreground/subagent-executor.ts";
15
-
16
- export const SCHEDULED_RUNS_DIR = path.join(TEMP_ROOT_DIR, "scheduled-subagent-runs");
17
- export const SCHEDULED_RUN_ACTIONS = ["schedule", "schedule-list", "schedule-status", "schedule-cancel"] as const;
18
-
19
- const MAX_TIMER_DELAY_MS = 2_147_483_647;
20
- const DEFAULT_MAX_LATENESS_MS = 5 * 60 * 1000;
21
- const DEFAULT_MAX_PENDING = 20;
22
-
23
- export type ScheduledRunAction = typeof SCHEDULED_RUN_ACTIONS[number];
24
- export type ScheduledRunState = "scheduled" | "running" | "fired" | "canceled" | "missed" | "failed";
25
-
26
- type ScheduledRunJob = {
27
- id: string;
28
- name: string;
29
- schedule: string;
30
- runAt: number;
31
- state: ScheduledRunState;
32
- createdAt: number;
33
- updatedAt: number;
34
- cwd: string;
35
- sessionId: string;
36
- params: SubagentParamsLike;
37
- lastRunId?: string;
38
- lastAsyncDir?: string;
39
- lastError?: string;
40
- firedAt?: number;
41
- canceledAt?: number;
42
- };
43
-
44
- type ScheduledRunStoreData = {
45
- version: 1;
46
- cwd: string;
47
- sessionId: string;
48
- jobs: ScheduledRunJob[];
49
- };
50
-
51
- type ScheduledRunTimers = Pick<typeof globalThis, "setTimeout" | "clearTimeout">;
52
-
53
- type ScheduledRunManagerDeps = {
54
- config: ExtensionConfig;
55
- launch(params: SubagentParamsLike, ctx: ExtensionContext, signal: AbortSignal): Promise<AgentToolResult<Details>>;
56
- storeRoot?: string;
57
- now?: () => number;
58
- randomId?: () => string;
59
- timers?: ScheduledRunTimers;
60
- };
61
-
62
- export function isScheduledRunAction(action: unknown): action is ScheduledRunAction {
63
- return typeof action === "string" && (SCHEDULED_RUN_ACTIONS as readonly string[]).includes(action);
64
- }
65
-
66
- export function scheduledRunsEnabled(config: ExtensionConfig): boolean {
67
- return config.scheduledRuns?.enabled === true;
68
- }
69
-
70
- export function scheduledRunStorePath(cwd: string, sessionId: string, root = SCHEDULED_RUNS_DIR): string {
71
- const digest = createHash("sha256").update(`${path.resolve(cwd)}\0${sessionId}`).digest("hex").slice(0, 20);
72
- return path.join(root, `${digest}.json`);
73
- }
74
-
75
- export function parseScheduledRunTime(schedule: string, now = Date.now()): number {
76
- const trimmed = schedule.trim();
77
- const relative = trimmed.match(/^\+(\d+)(s|m|h|d)$/);
78
- if (relative) {
79
- const amount = Number(relative[1]);
80
- if (!Number.isSafeInteger(amount) || amount < 1) throw new Error(`Invalid schedule "${schedule}". Relative schedules must be positive, such as "+10m".`);
81
- const unitMs = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[relative[2] as "s" | "m" | "h" | "d"];
82
- const runAt = now + amount * unitMs;
83
- if (!Number.isSafeInteger(runAt) || Number.isNaN(new Date(runAt).getTime())) throw new Error(`Invalid schedule "${schedule}". Relative delay is too large.`);
84
- return runAt;
85
- }
86
- if (/^\d{4}-\d{2}-\d{2}T/.test(trimmed)) {
87
- const iso = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/);
88
- if (!iso) throw new Error(`Invalid schedule "${schedule}". Absolute ISO timestamps must include a timezone, such as "2030-01-01T09:00:00Z".`);
89
- const year = Number(iso[1]);
90
- const month = Number(iso[2]);
91
- const day = Number(iso[3]);
92
- const hour = Number(iso[4]);
93
- const minute = Number(iso[5]);
94
- const second = iso[6] === undefined ? 0 : Number(iso[6]);
95
- const offset = iso[7]!;
96
- const offsetHour = offset === "Z" ? 0 : Number(offset.slice(1, 3));
97
- const offsetMinute = offset === "Z" ? 0 : Number(offset.slice(4, 6));
98
- const daysInMonth = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
99
- if (month < 1 || month > 12 || day < 1 || day > daysInMonth || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) {
100
- throw new Error(`Invalid schedule "${schedule}". Use a valid future ISO timestamp.`);
101
- }
102
- const parsed = new Date(trimmed).getTime();
103
- if (!Number.isNaN(parsed)) {
104
- if (parsed <= now) throw new Error(`Scheduled time ${new Date(parsed).toISOString()} is in the past.`);
105
- return parsed;
106
- }
107
- }
108
- throw new Error(`Invalid schedule "${schedule}". Use a one-shot relative delay like "+10m" or a future ISO timestamp with timezone.`);
109
- }
110
-
111
- function readStoreData(filePath: string, cwd: string, sessionId: string): ScheduledRunStoreData {
112
- if (!fs.existsSync(filePath)) return { version: 1, cwd, sessionId, jobs: [] };
113
- let parsed: unknown;
114
- try {
115
- parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
116
- } catch (error) {
117
- const message = error instanceof Error ? error.message : String(error);
118
- throw new Error(`Failed to parse scheduled subagent store '${filePath}': ${message}`, { cause: error instanceof Error ? error : undefined });
119
- }
120
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
121
- throw new Error(`Scheduled subagent store '${filePath}' must be a JSON object.`);
122
- }
123
- const data = parsed as Partial<ScheduledRunStoreData>;
124
- if (data.version !== 1) throw new Error(`Unsupported scheduled subagent store version in '${filePath}'.`);
125
- if (!Array.isArray(data.jobs)) throw new Error(`Scheduled subagent store '${filePath}' must contain a jobs array.`);
126
- const jobs: ScheduledRunJob[] = [];
127
- const validStates = new Set<ScheduledRunState>(["scheduled", "running", "fired", "canceled", "missed", "failed"]);
128
- for (const [index, job] of data.jobs.entries()) {
129
- if (!job || typeof job !== "object" || Array.isArray(job)) throw new Error(`Scheduled subagent store '${filePath}' job ${index} must be an object.`);
130
- const candidate = job as Partial<ScheduledRunJob>;
131
- if (typeof candidate.id !== "string" || typeof candidate.name !== "string" || typeof candidate.schedule !== "string" || typeof candidate.cwd !== "string" || typeof candidate.sessionId !== "string") {
132
- throw new Error(`Scheduled subagent store '${filePath}' job ${index} has invalid string fields.`);
133
- }
134
- const timestamps = [candidate.runAt, candidate.createdAt, candidate.updatedAt];
135
- if (timestamps.some((value) => typeof value !== "number" || !Number.isFinite(value) || Number.isNaN(new Date(value).getTime()))) {
136
- throw new Error(`Scheduled subagent store '${filePath}' job ${index} has invalid timestamps.`);
137
- }
138
- if (!candidate.state || !validStates.has(candidate.state)) throw new Error(`Scheduled subagent store '${filePath}' job ${index} has invalid state.`);
139
- if (!candidate.params || typeof candidate.params !== "object" || Array.isArray(candidate.params)) throw new Error(`Scheduled subagent store '${filePath}' job ${index} has invalid params.`);
140
- jobs.push(candidate as ScheduledRunJob);
141
- }
142
- return {
143
- version: 1,
144
- cwd: typeof data.cwd === "string" ? data.cwd : cwd,
145
- sessionId: typeof data.sessionId === "string" ? data.sessionId : sessionId,
146
- jobs,
147
- };
148
- }
149
-
150
- class ScheduledRunStore {
151
- private readonly filePath: string;
152
- private readonly cwd: string;
153
- private readonly sessionId: string;
154
-
155
- constructor(filePath: string, cwd: string, sessionId: string) {
156
- this.filePath = filePath;
157
- this.cwd = cwd;
158
- this.sessionId = sessionId;
159
- }
160
-
161
- list(): ScheduledRunJob[] {
162
- return readStoreData(this.filePath, this.cwd, this.sessionId).jobs;
163
- }
164
-
165
- get(id: string): ScheduledRunJob | undefined {
166
- return this.list().find((job) => job.id === id);
167
- }
168
-
169
- mutate<T>(fn: (data: ScheduledRunStoreData) => T): T {
170
- const data = readStoreData(this.filePath, this.cwd, this.sessionId);
171
- const result = fn(data);
172
- writeAtomicJson(this.filePath, data);
173
- return result;
174
- }
175
- }
176
-
177
- function resolveMaxLatenessMs(config: ExtensionConfig): number {
178
- const value = config.scheduledRuns?.maxLatenessMs;
179
- return Number.isInteger(value) && value >= 0 ? value : DEFAULT_MAX_LATENESS_MS;
180
- }
181
-
182
- function resolveMaxPending(config: ExtensionConfig): number {
183
- const value = config.scheduledRuns?.maxPending;
184
- return Number.isInteger(value) && value >= 1 ? value : DEFAULT_MAX_PENDING;
185
- }
186
-
187
- function terminalState(state: ScheduledRunState): boolean {
188
- return state === "fired" || state === "canceled" || state === "missed" || state === "failed";
189
- }
190
-
191
- function jobMode(params: SubagentParamsLike): Details["mode"] {
192
- if ((params.chain?.length ?? 0) > 0) return "chain";
193
- if ((params.tasks?.length ?? 0) > 0) return "parallel";
194
- return "single";
195
- }
196
-
197
- function describeScheduledTarget(params: SubagentParamsLike): string {
198
- if ((params.chain?.length ?? 0) > 0) return `chain (${params.chain!.length})`;
199
- if ((params.tasks?.length ?? 0) > 0) return `parallel (${params.tasks!.length})`;
200
- return params.agent ? `agent ${params.agent}` : "subagent run";
201
- }
202
-
203
- function textResult(text: string, isError = false): AgentToolResult<Details> {
204
- return {
205
- content: [{ type: "text", text }],
206
- ...(isError ? { isError: true } : {}),
207
- details: { mode: "management", results: [] },
208
- };
209
- }
210
-
211
- function resolveJobById(jobs: ScheduledRunJob[], requestedId: string): ScheduledRunJob {
212
- const exact = jobs.find((job) => job.id === requestedId);
213
- if (exact) return exact;
214
- const matches = jobs.filter((job) => job.id.startsWith(requestedId));
215
- if (matches.length === 1) return matches[0]!;
216
- if (matches.length > 1) throw new Error(`Ambiguous scheduled run id prefix '${requestedId}' matched: ${matches.map((job) => job.id).join(", ")}. Provide a longer id.`);
217
- throw new Error(`Scheduled run '${requestedId}' not found.`);
218
- }
219
-
220
- function sanitizeScheduledParams(params: SubagentParamsLike): { params?: SubagentParamsLike; error?: string } {
221
- const hasChain = (params.chain?.length ?? 0) > 0;
222
- const hasTasks = (params.tasks?.length ?? 0) > 0;
223
- const hasSingle = !hasChain && !hasTasks && Boolean(params.agent);
224
- if (Number(hasChain) + Number(hasTasks) + Number(hasSingle) !== 1) {
225
- return { error: "action='schedule' requires exactly one execution mode: agent, tasks, or chain." };
226
- }
227
- if (!params.schedule?.trim()) return { error: "action='schedule' requires schedule, such as '+10m' or a future ISO timestamp." };
228
- if (params.context === "fork") return { error: "Scheduled subagent runs require fresh context. Forked parent-session context is not safe at fire time." };
229
- if (params.async === false) return { error: "Scheduled subagent runs are always async; omit async or set async: true." };
230
- if (params.clarify === true) return { error: "Scheduled subagent runs cannot open clarify UI; omit clarify or set clarify: false." };
231
-
232
- const {
233
- action: _action,
234
- id: _id,
235
- runId: _runId,
236
- dir: _dir,
237
- index: _index,
238
- message: _message,
239
- chainName: _chainName,
240
- config: _config,
241
- schedule: _schedule,
242
- scheduleName: _scheduleName,
243
- ...executionParams
244
- } = params;
245
- return { params: { ...executionParams, async: true, clarify: false, context: "fresh" } };
246
- }
247
-
248
- export class ScheduledRunManager {
249
- private store: ScheduledRunStore | undefined;
250
- private ctx: ExtensionContext | undefined;
251
- private timers = new Map<string, ReturnType<typeof setTimeout>>();
252
- private readonly storeRoot: string;
253
- private readonly now: () => number;
254
- private readonly randomId: () => string;
255
- private readonly timersApi: ScheduledRunTimers;
256
- private readonly deps: ScheduledRunManagerDeps;
257
-
258
- constructor(deps: ScheduledRunManagerDeps) {
259
- this.deps = deps;
260
- this.storeRoot = deps.storeRoot ?? SCHEDULED_RUNS_DIR;
261
- this.now = deps.now ?? Date.now;
262
- this.randomId = deps.randomId ?? (() => randomUUID().slice(0, 8));
263
- this.timersApi = deps.timers ?? globalThis;
264
- }
265
-
266
- bindSession(ctx: ExtensionContext): void {
267
- this.stopTimers();
268
- this.ctx = ctx;
269
- if (!scheduledRunsEnabled(this.deps.config)) {
270
- this.store = undefined;
271
- return;
272
- }
273
- const sessionId = resolveCurrentSessionId(ctx.sessionManager);
274
- this.store = new ScheduledRunStore(scheduledRunStorePath(ctx.cwd, sessionId, this.storeRoot), ctx.cwd, sessionId);
275
- this.rearmScheduledJobs();
276
- }
277
-
278
- stop(): void {
279
- this.stopTimers();
280
- this.store = undefined;
281
- this.ctx = undefined;
282
- }
283
-
284
- async handleToolCall(params: SubagentParamsLike, ctx: ExtensionContext): Promise<AgentToolResult<Details>> {
285
- this.ctx = ctx;
286
- try {
287
- if (!scheduledRunsEnabled(this.deps.config)) {
288
- return textResult("Scheduled subagent runs are disabled. Set { \"scheduledRuns\": { \"enabled\": true } } in ~/.dm/agent/extensions/subagent/config.json, then reload Pi. Schedule only explicit delayed runs the user asked for.", true);
289
- }
290
- if (!this.store) this.bindSession(ctx);
291
- if (!this.store) return textResult("Scheduled subagent store is unavailable for this session.", true);
292
- switch (params.action) {
293
- case "schedule": return this.createJob(params, ctx);
294
- case "schedule-list": return this.listJobs();
295
- case "schedule-status": return this.statusJob(params);
296
- case "schedule-cancel": return this.cancelJob(params);
297
- default: return textResult(`Unknown scheduled-run action: ${params.action}`, true);
298
- }
299
- } catch (error) {
300
- const message = error instanceof Error ? error.message : String(error);
301
- return textResult(message, true);
302
- }
303
- }
304
-
305
- private createJob(params: SubagentParamsLike, ctx: ExtensionContext): AgentToolResult<Details> {
306
- const store = this.requireStore();
307
- const sanitized = sanitizeScheduledParams(params);
308
- if (sanitized.error) return textResult(sanitized.error, true);
309
- const scheduleInput = params.schedule!.trim();
310
- const runAt = parseScheduledRunTime(scheduleInput, this.now());
311
- const pendingCount = store.list().filter((job) => job.state === "scheduled" || job.state === "running").length;
312
- const maxPending = resolveMaxPending(this.deps.config);
313
- if (pendingCount >= maxPending) return textResult(`Scheduled subagent limit reached (${pendingCount}/${maxPending} pending or running). Cancel an existing scheduled run before adding another.`, true);
314
- const id = this.randomId();
315
- const sessionId = resolveCurrentSessionId(ctx.sessionManager);
316
- const scheduleName = params.scheduleName?.trim();
317
- const executionParams = sanitized.params!;
318
- const now = this.now();
319
- const job: ScheduledRunJob = {
320
- id,
321
- name: scheduleName || describeScheduledTarget(executionParams),
322
- schedule: scheduleInput,
323
- runAt,
324
- state: "scheduled",
325
- createdAt: now,
326
- updatedAt: now,
327
- cwd: ctx.cwd,
328
- sessionId,
329
- params: executionParams,
330
- };
331
- store.mutate((data) => {
332
- data.jobs.push(job);
333
- });
334
- this.arm(job);
335
- return textResult([
336
- `Scheduled subagent run ${job.id}.`,
337
- `Name: ${job.name}`,
338
- `When: ${new Date(job.runAt).toISOString()}`,
339
- `Mode: ${jobMode(executionParams)}`,
340
- `Context: fresh (scheduled runs never fork parent-session context)`,
341
- `Status: subagent({ action: "schedule-status", id: "${job.id}" })`,
342
- `Cancel before it fires: subagent({ action: "schedule-cancel", id: "${job.id}" })`,
343
- ].join("\n"));
344
- }
345
-
346
- private listJobs(): AgentToolResult<Details> {
347
- const jobs = this.requireStore().list().sort((left, right) => left.runAt - right.runAt);
348
- if (jobs.length === 0) return textResult("No scheduled subagent runs for this session.");
349
- const lines = [`Scheduled subagent runs: ${jobs.length}`, ""];
350
- for (const job of jobs) {
351
- const parts = [job.id, job.state, new Date(job.runAt).toISOString(), job.name];
352
- if (job.lastRunId) parts.push(`run ${job.lastRunId}`);
353
- if (job.lastError) parts.push(`error: ${job.lastError}`);
354
- lines.push(`- ${parts.join(" | ")}`);
355
- }
356
- return textResult(lines.join("\n"));
357
- }
358
-
359
- private statusJob(params: SubagentParamsLike): AgentToolResult<Details> {
360
- const requestedId = params.id ?? params.runId;
361
- if (!requestedId) return textResult("action='schedule-status' requires id.", true);
362
- const job = resolveJobById(this.requireStore().list(), requestedId);
363
- const lines = [
364
- `Scheduled run: ${job.id}`,
365
- `Name: ${job.name}`,
366
- `State: ${job.state}`,
367
- `Schedule: ${job.schedule}`,
368
- `Run at: ${new Date(job.runAt).toISOString()}`,
369
- `Mode: ${jobMode(job.params)}`,
370
- `CWD: ${shortenPath(job.cwd)}`,
371
- `Created: ${new Date(job.createdAt).toISOString()}`,
372
- `Updated: ${new Date(job.updatedAt).toISOString()}`,
373
- job.lastRunId ? `Launched async run: ${job.lastRunId}` : undefined,
374
- job.lastAsyncDir ? `Async dir: ${job.lastAsyncDir}` : undefined,
375
- job.lastError ? `Error: ${job.lastError}` : undefined,
376
- job.state === "scheduled" ? `Cancel: subagent({ action: "schedule-cancel", id: "${job.id}" })` : undefined,
377
- job.lastRunId ? `Async status: subagent({ action: "status", id: "${job.lastRunId}" })` : undefined,
378
- ].filter((line): line is string => Boolean(line));
379
- return textResult(lines.join("\n"));
380
- }
381
-
382
- private cancelJob(params: SubagentParamsLike): AgentToolResult<Details> {
383
- const requestedId = params.id ?? params.runId;
384
- if (!requestedId) return textResult("action='schedule-cancel' requires id.", true);
385
- const store = this.requireStore();
386
- const job = resolveJobById(store.list(), requestedId);
387
- if (job.state === "running") return textResult(`Scheduled run ${job.id} already launched async run ${job.lastRunId ?? "unknown"}; interrupt that async run instead.`, true);
388
- if (terminalState(job.state)) return textResult(`Scheduled run ${job.id} is already ${job.state}.`, true);
389
- const now = this.now();
390
- this.clearTimer(job.id);
391
- store.mutate((data) => {
392
- const stored = data.jobs.find((candidate) => candidate.id === job.id);
393
- if (!stored) return;
394
- stored.state = "canceled";
395
- stored.canceledAt = now;
396
- stored.updatedAt = now;
397
- });
398
- return textResult(`Canceled scheduled subagent run ${job.id}.`);
399
- }
400
-
401
- private rearmScheduledJobs(): void {
402
- const store = this.requireStore();
403
- const now = this.now();
404
- const maxLatenessMs = resolveMaxLatenessMs(this.deps.config);
405
- const dueToMiss = store.list().filter((job) => job.state === "scheduled" && job.runAt + maxLatenessMs < now);
406
- if (dueToMiss.length > 0) {
407
- store.mutate((data) => {
408
- for (const missed of dueToMiss) {
409
- const job = data.jobs.find((candidate) => candidate.id === missed.id);
410
- if (!job || job.state !== "scheduled") continue;
411
- job.state = "missed";
412
- job.updatedAt = now;
413
- job.lastError = `Missed scheduled time by more than ${formatDuration(maxLatenessMs)} while DM was not available.`;
414
- }
415
- });
416
- }
417
- for (const job of store.list()) {
418
- if (job.state === "scheduled") this.arm(job);
419
- }
420
- }
421
-
422
- private arm(job: ScheduledRunJob): void {
423
- this.clearTimer(job.id);
424
- const delayMs = Math.max(0, job.runAt - this.now());
425
- const timer = this.timersApi.setTimeout(() => {
426
- void this.fire(job.id);
427
- }, Math.min(delayMs, MAX_TIMER_DELAY_MS));
428
- timer.unref?.();
429
- this.timers.set(job.id, timer);
430
- }
431
-
432
- private async fire(jobId: string): Promise<void> {
433
- this.clearTimer(jobId);
434
- const store = this.store;
435
- const ctx = this.ctx;
436
- if (!store || !ctx) return;
437
- let job = store.get(jobId);
438
- if (!job || job.state !== "scheduled") return;
439
- const now = this.now();
440
- // A timer capped at MAX_TIMER_DELAY_MS may fire before runAt for far-future schedules; re-arm and wait.
441
- if (now < job.runAt) {
442
- this.arm(job);
443
- return;
444
- }
445
- const maxLatenessMs = resolveMaxLatenessMs(this.deps.config);
446
- if (job.runAt + maxLatenessMs < now) {
447
- store.mutate((data) => {
448
- const stored = data.jobs.find((candidate) => candidate.id === jobId);
449
- if (!stored || stored.state !== "scheduled") return;
450
- stored.state = "missed";
451
- stored.updatedAt = now;
452
- stored.lastError = `Missed scheduled time by more than ${formatDuration(maxLatenessMs)}.`;
453
- });
454
- return;
455
- }
456
- store.mutate((data) => {
457
- const stored = data.jobs.find((candidate) => candidate.id === jobId);
458
- if (!stored || stored.state !== "scheduled") return;
459
- stored.state = "running";
460
- stored.firedAt = now;
461
- stored.updatedAt = now;
462
- });
463
- job = store.get(jobId);
464
- if (!job || job.state !== "running") return;
465
- const controller = new AbortController();
466
- try {
467
- const result = await this.deps.launch(job.params, ctx, controller.signal);
468
- const launchRunId = result.details?.asyncId ?? result.details?.runId;
469
- store.mutate((data) => {
470
- const stored = data.jobs.find((candidate) => candidate.id === jobId);
471
- if (!stored) return;
472
- stored.updatedAt = this.now();
473
- if (result.isError || !launchRunId) {
474
- stored.state = "failed";
475
- stored.lastError = result.content.find((item) => item.type === "text")?.text ?? "Scheduled subagent launch failed.";
476
- return;
477
- }
478
- stored.state = "fired";
479
- stored.lastRunId = launchRunId;
480
- stored.lastAsyncDir = result.details?.asyncDir;
481
- });
482
- } catch (error) {
483
- const message = error instanceof Error ? error.message : String(error);
484
- store.mutate((data) => {
485
- const stored = data.jobs.find((candidate) => candidate.id === jobId);
486
- if (!stored) return;
487
- stored.state = "failed";
488
- stored.lastError = message;
489
- stored.updatedAt = this.now();
490
- });
491
- }
492
- }
493
-
494
- private requireStore(): ScheduledRunStore {
495
- if (!this.store) throw new Error("Scheduled subagent store is not bound to a session.");
496
- return this.store;
497
- }
498
-
499
- private clearTimer(jobId: string): void {
500
- const timer = this.timers.get(jobId);
501
- if (!timer) return;
502
- this.timersApi.clearTimeout(timer);
503
- this.timers.delete(jobId);
504
- }
505
-
506
- private stopTimers(): void {
507
- for (const timer of this.timers.values()) this.timersApi.clearTimeout(timer);
508
- this.timers.clear();
509
- }
510
- }
511
-
512
- export function createScheduledRunManager(deps: ScheduledRunManagerDeps): ScheduledRunManager {
513
- return new ScheduledRunManager(deps);
514
- }