@namzu/cli 29.1.0 → 32.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/CHANGELOG.md +296 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +5 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/acp.d.ts +20 -2
  6. package/dist/commands/acp.d.ts.map +1 -1
  7. package/dist/commands/acp.js +42 -5
  8. package/dist/commands/acp.js.map +1 -1
  9. package/dist/commands/drain.d.ts.map +1 -1
  10. package/dist/commands/drain.js +2 -1
  11. package/dist/commands/drain.js.map +1 -1
  12. package/dist/commands/exec-json.d.ts.map +1 -1
  13. package/dist/commands/exec-json.js +24 -1
  14. package/dist/commands/exec-json.js.map +1 -1
  15. package/dist/commands/exec.d.ts.map +1 -1
  16. package/dist/commands/exec.js +5 -1
  17. package/dist/commands/exec.js.map +1 -1
  18. package/dist/commands/prior-messages.d.ts.map +1 -1
  19. package/dist/commands/prior-messages.js +42 -2
  20. package/dist/commands/prior-messages.js.map +1 -1
  21. package/dist/commands/schedule.d.ts.map +1 -1
  22. package/dist/commands/schedule.js +7 -5
  23. package/dist/commands/schedule.js.map +1 -1
  24. package/dist/config/composer-triggers.d.ts +41 -0
  25. package/dist/config/composer-triggers.d.ts.map +1 -0
  26. package/dist/config/composer-triggers.js +80 -0
  27. package/dist/config/composer-triggers.js.map +1 -0
  28. package/dist/config/load.d.ts.map +1 -1
  29. package/dist/config/load.js +58 -0
  30. package/dist/config/load.js.map +1 -1
  31. package/dist/config/schema.d.ts +34 -0
  32. package/dist/config/schema.d.ts.map +1 -1
  33. package/dist/config/schema.js.map +1 -1
  34. package/dist/context/doctrine.d.ts +3 -3
  35. package/dist/context/doctrine.d.ts.map +1 -1
  36. package/dist/context/doctrine.js +3 -3
  37. package/dist/context/doctrine.js.map +1 -1
  38. package/dist/context/environment.d.ts +26 -3
  39. package/dist/context/environment.d.ts.map +1 -1
  40. package/dist/context/environment.js +68 -9
  41. package/dist/context/environment.js.map +1 -1
  42. package/dist/context/runtime-message.d.ts.map +1 -1
  43. package/dist/context/runtime-message.js +4 -0
  44. package/dist/context/runtime-message.js.map +1 -1
  45. package/dist/integrations/mcp/servers.d.ts +72 -5
  46. package/dist/integrations/mcp/servers.d.ts.map +1 -1
  47. package/dist/integrations/mcp/servers.js +144 -23
  48. package/dist/integrations/mcp/servers.js.map +1 -1
  49. package/dist/integrations/notifications/desktop.d.ts +2 -0
  50. package/dist/integrations/notifications/desktop.d.ts.map +1 -1
  51. package/dist/integrations/notifications/desktop.js +6 -5
  52. package/dist/integrations/notifications/desktop.js.map +1 -1
  53. package/dist/integrations/plugins/runtime.d.ts +10 -2
  54. package/dist/integrations/plugins/runtime.d.ts.map +1 -1
  55. package/dist/integrations/plugins/runtime.js +16 -29
  56. package/dist/integrations/plugins/runtime.js.map +1 -1
  57. package/dist/integrations/providers/harness-credentials.d.ts +9 -4
  58. package/dist/integrations/providers/harness-credentials.d.ts.map +1 -1
  59. package/dist/integrations/providers/harness-credentials.js +16 -8
  60. package/dist/integrations/providers/harness-credentials.js.map +1 -1
  61. package/dist/integrations/resident/session-step.d.ts.map +1 -1
  62. package/dist/integrations/resident/session-step.js +2 -1
  63. package/dist/integrations/resident/session-step.js.map +1 -1
  64. package/dist/integrations/sessions/store.d.ts.map +1 -1
  65. package/dist/integrations/sessions/store.js +20 -2
  66. package/dist/integrations/sessions/store.js.map +1 -1
  67. package/dist/integrations/subagents/NamzuCliAgent.d.ts +8 -0
  68. package/dist/integrations/subagents/NamzuCliAgent.d.ts.map +1 -0
  69. package/dist/integrations/subagents/NamzuCliAgent.js +9 -0
  70. package/dist/integrations/subagents/NamzuCliAgent.js.map +1 -0
  71. package/dist/integrations/subagents/runtime.d.ts +5 -4
  72. package/dist/integrations/subagents/runtime.d.ts.map +1 -1
  73. package/dist/integrations/subagents/runtime.js +46 -11
  74. package/dist/integrations/subagents/runtime.js.map +1 -1
  75. package/dist/integrations/web/open-url.d.ts +24 -0
  76. package/dist/integrations/web/open-url.d.ts.map +1 -0
  77. package/dist/integrations/web/open-url.js +73 -0
  78. package/dist/integrations/web/open-url.js.map +1 -0
  79. package/dist/integrations/web/search.d.ts.map +1 -1
  80. package/dist/integrations/web/search.js +8 -6
  81. package/dist/integrations/web/search.js.map +1 -1
  82. package/dist/permissions/rules.d.ts +5 -1
  83. package/dist/permissions/rules.d.ts.map +1 -1
  84. package/dist/permissions/rules.js +85 -3
  85. package/dist/permissions/rules.js.map +1 -1
  86. package/dist/schedule/build.d.ts +26 -2
  87. package/dist/schedule/build.d.ts.map +1 -1
  88. package/dist/schedule/build.js +139 -20
  89. package/dist/schedule/build.js.map +1 -1
  90. package/dist/schedule/changes.d.ts +6 -0
  91. package/dist/schedule/changes.d.ts.map +1 -1
  92. package/dist/schedule/changes.js +23 -0
  93. package/dist/schedule/changes.js.map +1 -1
  94. package/dist/schedule/commands/add.d.ts +1 -1
  95. package/dist/schedule/commands/add.d.ts.map +1 -1
  96. package/dist/schedule/commands/add.js +188 -54
  97. package/dist/schedule/commands/add.js.map +1 -1
  98. package/dist/schedule/commands/lifecycle.d.ts.map +1 -1
  99. package/dist/schedule/commands/lifecycle.js +2 -1
  100. package/dist/schedule/commands/lifecycle.js.map +1 -1
  101. package/dist/schedule/commands/list.d.ts +2 -0
  102. package/dist/schedule/commands/list.d.ts.map +1 -1
  103. package/dist/schedule/commands/list.js +69 -20
  104. package/dist/schedule/commands/list.js.map +1 -1
  105. package/dist/schedule/daemon/daemon.d.ts +1 -1
  106. package/dist/schedule/daemon/daemon.d.ts.map +1 -1
  107. package/dist/schedule/daemon/daemon.js +22 -5
  108. package/dist/schedule/daemon/daemon.js.map +1 -1
  109. package/dist/schedule/daemon/notify.d.ts +29 -2
  110. package/dist/schedule/daemon/notify.d.ts.map +1 -1
  111. package/dist/schedule/daemon/notify.js +71 -2
  112. package/dist/schedule/daemon/notify.js.map +1 -1
  113. package/dist/schedule/env.d.ts +8 -4
  114. package/dist/schedule/env.d.ts.map +1 -1
  115. package/dist/schedule/env.js +13 -6
  116. package/dist/schedule/env.js.map +1 -1
  117. package/dist/schedule/fire/calls.d.ts +49 -0
  118. package/dist/schedule/fire/calls.d.ts.map +1 -0
  119. package/dist/schedule/fire/calls.js +114 -0
  120. package/dist/schedule/fire/calls.js.map +1 -0
  121. package/dist/schedule/fire/fire.d.ts +1 -1
  122. package/dist/schedule/fire/fire.d.ts.map +1 -1
  123. package/dist/schedule/fire/fire.js +209 -15
  124. package/dist/schedule/fire/fire.js.map +1 -1
  125. package/dist/schedule/fire/result.d.ts +1 -1
  126. package/dist/schedule/fire/result.d.ts.map +1 -1
  127. package/dist/schedule/fire/result.js +2 -1
  128. package/dist/schedule/fire/result.js.map +1 -1
  129. package/dist/schedule/fire/run-script.d.ts +46 -0
  130. package/dist/schedule/fire/run-script.d.ts.map +1 -0
  131. package/dist/schedule/fire/run-script.js +81 -0
  132. package/dist/schedule/fire/run-script.js.map +1 -0
  133. package/dist/schedule/fire/unattended-note.d.ts +8 -0
  134. package/dist/schedule/fire/unattended-note.d.ts.map +1 -1
  135. package/dist/schedule/fire/unattended-note.js +5 -0
  136. package/dist/schedule/fire/unattended-note.js.map +1 -1
  137. package/dist/schedule/fire/wake-gate.d.ts +39 -0
  138. package/dist/schedule/fire/wake-gate.d.ts.map +1 -0
  139. package/dist/schedule/fire/wake-gate.js +90 -0
  140. package/dist/schedule/fire/wake-gate.js.map +1 -0
  141. package/dist/schedule/floor.d.ts +28 -8
  142. package/dist/schedule/floor.d.ts.map +1 -1
  143. package/dist/schedule/floor.js +624 -114
  144. package/dist/schedule/floor.js.map +1 -1
  145. package/dist/schedule/policy.d.ts +40 -0
  146. package/dist/schedule/policy.d.ts.map +1 -1
  147. package/dist/schedule/policy.js +36 -0
  148. package/dist/schedule/policy.js.map +1 -1
  149. package/dist/schedule/script-check.d.ts +53 -0
  150. package/dist/schedule/script-check.d.ts.map +1 -0
  151. package/dist/schedule/script-check.js +145 -0
  152. package/dist/schedule/script-check.js.map +1 -0
  153. package/dist/schedule/script-shell.d.ts +3 -0
  154. package/dist/schedule/script-shell.d.ts.map +1 -0
  155. package/dist/schedule/script-shell.js +5 -0
  156. package/dist/schedule/script-shell.js.map +1 -0
  157. package/dist/schedule/service/index.d.ts +1 -1
  158. package/dist/schedule/service/index.d.ts.map +1 -1
  159. package/dist/schedule/service/index.js +20 -11
  160. package/dist/schedule/service/index.js.map +1 -1
  161. package/dist/schedule/service/manifest.js +1 -1
  162. package/dist/schedule/service/manifest.js.map +1 -1
  163. package/dist/schedule/service/windows-task.d.ts +7 -0
  164. package/dist/schedule/service/windows-task.d.ts.map +1 -1
  165. package/dist/schedule/service/windows-task.js +65 -0
  166. package/dist/schedule/service/windows-task.js.map +1 -1
  167. package/dist/schedule/service/wsl.d.ts +13 -1
  168. package/dist/schedule/service/wsl.d.ts.map +1 -1
  169. package/dist/schedule/service/wsl.js +16 -6
  170. package/dist/schedule/service/wsl.js.map +1 -1
  171. package/dist/schedule/store/atomic.d.ts +5 -2
  172. package/dist/schedule/store/atomic.d.ts.map +1 -1
  173. package/dist/schedule/store/atomic.js +7 -4
  174. package/dist/schedule/store/atomic.js.map +1 -1
  175. package/dist/schedule/store/claims.js +1 -1
  176. package/dist/schedule/store/claims.js.map +1 -1
  177. package/dist/schedule/store/history.d.ts +1 -1
  178. package/dist/schedule/store/history.d.ts.map +1 -1
  179. package/dist/schedule/store/history.js +4 -1
  180. package/dist/schedule/store/history.js.map +1 -1
  181. package/dist/schedule/store/jobs.d.ts +19 -3
  182. package/dist/schedule/store/jobs.d.ts.map +1 -1
  183. package/dist/schedule/store/jobs.js +83 -16
  184. package/dist/schedule/store/jobs.js.map +1 -1
  185. package/dist/schedule/store/state.js +1 -1
  186. package/dist/schedule/store/state.js.map +1 -1
  187. package/dist/schedule/types.d.ts +98 -6
  188. package/dist/schedule/types.d.ts.map +1 -1
  189. package/dist/schedule/types.js +25 -1
  190. package/dist/schedule/types.js.map +1 -1
  191. package/dist/schedule/visible-source.d.ts +18 -0
  192. package/dist/schedule/visible-source.d.ts.map +1 -0
  193. package/dist/schedule/visible-source.js +59 -0
  194. package/dist/schedule/visible-source.js.map +1 -0
  195. package/dist/skills/directory.d.ts +38 -0
  196. package/dist/skills/directory.d.ts.map +1 -0
  197. package/dist/skills/directory.js +51 -0
  198. package/dist/skills/directory.js.map +1 -0
  199. package/dist/test-support/toolset.d.ts +23 -0
  200. package/dist/test-support/toolset.d.ts.map +1 -0
  201. package/dist/test-support/toolset.js +27 -0
  202. package/dist/test-support/toolset.js.map +1 -0
  203. package/dist/tui/App.d.ts.map +1 -1
  204. package/dist/tui/App.js +343 -64
  205. package/dist/tui/App.js.map +1 -1
  206. package/dist/tui/ChoicePicker.d.ts +2 -0
  207. package/dist/tui/ChoicePicker.d.ts.map +1 -1
  208. package/dist/tui/ChoicePicker.js +10 -2
  209. package/dist/tui/ChoicePicker.js.map +1 -1
  210. package/dist/tui/Composer.d.ts +62 -3
  211. package/dist/tui/Composer.d.ts.map +1 -1
  212. package/dist/tui/Composer.js +162 -25
  213. package/dist/tui/Composer.js.map +1 -1
  214. package/dist/tui/ComposerFrame.d.ts +1 -1
  215. package/dist/tui/ComposerFrame.js +3 -3
  216. package/dist/tui/ComposerFrame.js.map +1 -1
  217. package/dist/tui/EffortSlider.d.ts +8 -9
  218. package/dist/tui/EffortSlider.d.ts.map +1 -1
  219. package/dist/tui/EffortSlider.js +15 -12
  220. package/dist/tui/EffortSlider.js.map +1 -1
  221. package/dist/tui/PermissionOverlay.d.ts +13 -1
  222. package/dist/tui/PermissionOverlay.d.ts.map +1 -1
  223. package/dist/tui/PermissionOverlay.js +26 -4
  224. package/dist/tui/PermissionOverlay.js.map +1 -1
  225. package/dist/tui/StatusBar.d.ts +12 -12
  226. package/dist/tui/StatusBar.d.ts.map +1 -1
  227. package/dist/tui/StatusBar.js +15 -15
  228. package/dist/tui/StatusBar.js.map +1 -1
  229. package/dist/tui/agent.d.ts +73 -12
  230. package/dist/tui/agent.d.ts.map +1 -1
  231. package/dist/tui/agent.js +430 -180
  232. package/dist/tui/agent.js.map +1 -1
  233. package/dist/tui/hypermode.d.ts +37 -0
  234. package/dist/tui/hypermode.d.ts.map +1 -0
  235. package/dist/tui/hypermode.js +58 -0
  236. package/dist/tui/hypermode.js.map +1 -0
  237. package/dist/tui/open-browser.d.ts +29 -1
  238. package/dist/tui/open-browser.d.ts.map +1 -1
  239. package/dist/tui/open-browser.js +83 -11
  240. package/dist/tui/open-browser.js.map +1 -1
  241. package/dist/tui/permission-review.d.ts +11 -2
  242. package/dist/tui/permission-review.d.ts.map +1 -1
  243. package/dist/tui/permission-review.js +176 -19
  244. package/dist/tui/permission-review.js.map +1 -1
  245. package/dist/tui/schedule/host-commands.d.ts.map +1 -1
  246. package/dist/tui/schedule/host-commands.js +95 -33
  247. package/dist/tui/schedule/host-commands.js.map +1 -1
  248. package/dist/tui/schedule/integration.d.ts +6 -2
  249. package/dist/tui/schedule/integration.d.ts.map +1 -1
  250. package/dist/tui/schedule/integration.js +3 -1
  251. package/dist/tui/schedule/integration.js.map +1 -1
  252. package/dist/tui/schedule/resume.d.ts.map +1 -1
  253. package/dist/tui/schedule/resume.js +10 -1
  254. package/dist/tui/schedule/resume.js.map +1 -1
  255. package/dist/tui/schedule/tool-host.d.ts +18 -1
  256. package/dist/tui/schedule/tool-host.d.ts.map +1 -1
  257. package/dist/tui/schedule/tool-host.js +384 -84
  258. package/dist/tui/schedule/tool-host.js.map +1 -1
  259. package/dist/tui/slashCommands.d.ts +23 -5
  260. package/dist/tui/slashCommands.d.ts.map +1 -1
  261. package/dist/tui/slashCommands.js +80 -31
  262. package/dist/tui/slashCommands.js.map +1 -1
  263. package/dist/tui/theme.d.ts +10 -4
  264. package/dist/tui/theme.d.ts.map +1 -1
  265. package/dist/tui/theme.js +4 -3
  266. package/dist/tui/theme.js.map +1 -1
  267. package/dist/tui/triggers/analyze.d.ts +57 -0
  268. package/dist/tui/triggers/analyze.d.ts.map +1 -0
  269. package/dist/tui/triggers/analyze.js +221 -0
  270. package/dist/tui/triggers/analyze.js.map +1 -0
  271. package/dist/tui/triggers/context-text.d.ts +21 -0
  272. package/dist/tui/triggers/context-text.d.ts.map +1 -0
  273. package/dist/tui/triggers/context-text.js +44 -0
  274. package/dist/tui/triggers/context-text.js.map +1 -0
  275. package/dist/tui/triggers/copy.d.ts +38 -0
  276. package/dist/tui/triggers/copy.d.ts.map +1 -0
  277. package/dist/tui/triggers/copy.js +163 -0
  278. package/dist/tui/triggers/copy.js.map +1 -0
  279. package/dist/tui/triggers/detect.d.ts +83 -0
  280. package/dist/tui/triggers/detect.d.ts.map +1 -0
  281. package/dist/tui/triggers/detect.js +295 -0
  282. package/dist/tui/triggers/detect.js.map +1 -0
  283. package/dist/tui/triggers/fold.d.ts +34 -0
  284. package/dist/tui/triggers/fold.d.ts.map +1 -0
  285. package/dist/tui/triggers/fold.js +44 -0
  286. package/dist/tui/triggers/fold.js.map +1 -0
  287. package/dist/tui/triggers/pattern.d.ts +66 -0
  288. package/dist/tui/triggers/pattern.d.ts.map +1 -0
  289. package/dist/tui/triggers/pattern.js +243 -0
  290. package/dist/tui/triggers/pattern.js.map +1 -0
  291. package/dist/tui/triggers/provenance.d.ts +58 -0
  292. package/dist/tui/triggers/provenance.d.ts.map +1 -0
  293. package/dist/tui/triggers/provenance.js +98 -0
  294. package/dist/tui/triggers/provenance.js.map +1 -0
  295. package/dist/tui/triggers/registry.d.ts +113 -0
  296. package/dist/tui/triggers/registry.d.ts.map +1 -0
  297. package/dist/tui/triggers/registry.js +164 -0
  298. package/dist/tui/triggers/registry.js.map +1 -0
  299. package/dist/tui/triggers/setting.d.ts +18 -0
  300. package/dist/tui/triggers/setting.d.ts.map +1 -0
  301. package/dist/tui/triggers/setting.js +61 -0
  302. package/dist/tui/triggers/setting.js.map +1 -0
  303. package/dist/tui/triggers/verbs.d.ts +29 -0
  304. package/dist/tui/triggers/verbs.d.ts.map +1 -0
  305. package/dist/tui/triggers/verbs.js +140 -0
  306. package/dist/tui/triggers/verbs.js.map +1 -0
  307. package/dist/tui/types.d.ts +3 -1
  308. package/dist/tui/types.d.ts.map +1 -1
  309. package/package.json +12 -12
  310. package/skills/README.md +5 -0
  311. package/skills/schedule-task/SKILL.md +123 -5
@@ -8,22 +8,38 @@
8
8
  * "Create paused" creates a job. A job created here records
9
9
  * `confirmation.surface: tool-confirmed`.
10
10
  */
11
+ import { realpathSync } from 'node:fs';
11
12
  import { isAbsolute, relative, resolve } from 'node:path';
12
- import { describeSchedule, hostTimeZone, revealHiddenCharacters, upcomingFireTimes, } from '@namzu/sdk';
13
+ import { describeSchedule, hostTimeZone, installedCommandShellForDialect, upcomingFireTimes, validateTimeZone, } from '@namzu/sdk';
13
14
  import { readPermissionLayers } from '../../config/load.js';
14
15
  import { discoverProviders } from '../../integrations/providers/discover.js';
15
- import { DEFAULT_MAX_ITERATIONS, DEFAULT_TIMEOUT_MS, DEFAULT_TOKEN_BUDGET, buildJob, confirmJob, previewLines, runsPerDay, } from '../../schedule/build.js';
16
+ import { DEFAULT_MAX_ITERATIONS, DEFAULT_TIMEOUT_MS, DEFAULT_TOKEN_BUDGET, JobRequestError, buildJob, confirmJob, editedJob, previewLines, runsPerDay, } from '../../schedule/build.js';
17
+ import { changesBlock, changesSinceConfirmed, confirmationView, describeChanges, permissionsChanged, } from '../../schedule/changes.js';
18
+ import { callsCount } from '../../schedule/fire/calls.js';
16
19
  import { schedulePaths } from '../../schedule/paths.js';
17
- import { compileJobPolicy } from '../../schedule/policy.js';
20
+ import { allowsCommands, compileJobPolicy, compileScriptCheckPolicy, } from '../../schedule/policy.js';
21
+ import { verifyScheduledScript } from '../../schedule/script-check.js';
22
+ import { scriptShellUnavailableReason } from '../../schedule/script-shell.js';
18
23
  import { readManifest } from '../../schedule/service/manifest.js';
19
- import { appendHistory } from '../../schedule/store/history.js';
20
- import { createJob, deleteJob, findJob, listJobs, updateJob } from '../../schedule/store/jobs.js';
24
+ import { appendHistory, readHistory } from '../../schedule/store/history.js';
25
+ import { ScheduleConflictError, createJob, deleteJob, findJob, listJobs, updateJob, } from '../../schedule/store/jobs.js';
21
26
  import { nextFireOf, readState } from '../../schedule/store/state.js';
27
+ import { visibleScheduleMessage } from '../../schedule/visible-source.js';
22
28
  function within(root, path) {
23
29
  const rel = relative(root, path);
24
30
  return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
25
31
  }
26
- function summary(job, withPrompt, home) {
32
+ /** The session's folder as a job records its own: canonical, symbolic links resolved. */
33
+ function canonicalCwd(cwd) {
34
+ try {
35
+ return realpathSync(cwd);
36
+ }
37
+ catch {
38
+ return resolve(cwd);
39
+ }
40
+ }
41
+ /** A job as the tool reports it; `here` when it runs in the session's folder, which alone shows its prompt. */
42
+ function summary(job, here, home) {
27
43
  const tz = job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone();
28
44
  const state = readState(schedulePaths(home), job.id);
29
45
  return {
@@ -32,8 +48,14 @@ function summary(job, withPrompt, home) {
32
48
  state: job.state,
33
49
  schedule: describeSchedule(job.schedule, { tz }),
34
50
  ...(nextFireOf(job, state) ? { nextFireAt: nextFireOf(job, state) } : {}),
35
- ...(state.lastRun ? { lastStatus: state.lastRun.status } : {}),
36
- ...(withPrompt ? { prompt: job.prompt } : {}),
51
+ ...(state.lastRun
52
+ ? {
53
+ lastStatus: callsCount(state.lastRun)
54
+ ? `${state.lastRun.status} (${callsCount(state.lastRun)})`
55
+ : state.lastRun.status,
56
+ }
57
+ : {}),
58
+ ...(here ? { prompt: job.prompt, inSessionFolder: true } : {}),
37
59
  };
38
60
  }
39
61
  async function credentialText(provider) {
@@ -50,20 +72,145 @@ async function credentialText(provider) {
50
72
  return 'unknown';
51
73
  }
52
74
  }
53
- /** The preview lines, the full prompt with hidden characters shown, and the tripwire's findings. */
54
- export function renderConfirmation(request, lines) {
55
- const p = request.preview;
75
+ /** The preview lines, the credential, the warnings, and the full prompt with hidden characters shown. */
76
+ function confirmationBody(p, findings, lines) {
77
+ const scriptSection = p.runKind && p.runKind !== 'agent' && p.script
78
+ ? [
79
+ `${p.runKind === 'script' ? 'Script' : 'Wake-gate script'} (exactly as it will run, ${p.script.shell}; verified against the scheduled-run floor and any deny rules)`,
80
+ ...p.script.body.split('\n').map((l) => ` │ ${l}`),
81
+ p.runKind === 'script'
82
+ ? 'Runs exactly as shown; the scheduled-run floor and every deny rule apply to this script'
83
+ : 'Runs exactly as shown; the scheduled-run floor and every deny rule apply to the gate, and the permission set governs the agent phase',
84
+ ]
85
+ : [];
86
+ const promptSection = p.prompt.trim()
87
+ ? [
88
+ p.runKind === 'script+agent'
89
+ ? 'Prompt (used only when the wake-gate says wake: true)'
90
+ : 'Prompt (exactly as the run will read it)',
91
+ ...p.prompt.split('\n').map((l) => ` │ ${l}`),
92
+ ]
93
+ : [];
56
94
  return [
57
- '⏲ PROPOSED BY THE MODEL, NOT BY YOU — a scheduled job that runs later with nobody watching.',
58
95
  ...lines,
59
- `Credential ${p.credentialSource ?? 'unknown'}`,
96
+ ...(p.runKind === 'script' ? [] : [`Credential ${p.credentialSource ?? 'unknown'}`]),
60
97
  ...p.warnings.map((w) => `Warning ${w}`),
61
- ...request.promptFindings.map((f) => `Warning ${f}`),
62
- 'Prompt (exactly as the run will read it)',
63
- ...revealHiddenCharacters(p.prompt)
64
- .split('\n')
65
- .map((l) => ` │ ${l}`),
66
- ].join('\n');
98
+ ...findings.map((f) => `Warning ${f}`),
99
+ ...scriptSection,
100
+ ...promptSection,
101
+ ];
102
+ }
103
+ /** The preview lines, the full prompt with hidden characters shown, and the tripwire's findings. */
104
+ export function renderConfirmation(request, lines) {
105
+ return visibleScheduleMessage([
106
+ '⏲ PROPOSED BY THE MODEL, NOT BY YOU — a scheduled job that runs later with nobody watching.',
107
+ ...confirmationBody(request.preview, request.promptFindings, lines),
108
+ ].join('\n'));
109
+ }
110
+ /**
111
+ * A change the model proposed: what changes first (`-`/`+` lines, as
112
+ * `schedule edit` shows them), a warning when the permissions change, then
113
+ * the job as it would run, whole.
114
+ */
115
+ export function renderUpdateConfirmation(request, lines) {
116
+ return visibleScheduleMessage([
117
+ `⏲ PROPOSED BY THE MODEL, NOT BY YOU — a change to the scheduled job ${request.preview.name}, which runs later with nobody watching.`,
118
+ ...changesBlock(request.changes),
119
+ ...(request.permissionsChange
120
+ ? [
121
+ 'Warning THE PERMISSIONS CHANGE: from its next run the job may do what the rules below allow.',
122
+ ]
123
+ : []),
124
+ ...confirmationBody(request.preview, request.promptFindings, lines),
125
+ ].join('\n'));
126
+ }
127
+ /**
128
+ * The request `buildJob` rebuilds a job from, for a change the model
129
+ * proposed: every field it left out as the job has it. A new permission set
130
+ * keeps the job's `execution` unless it names one, and the job's additional
131
+ * directories (which only the operator sets).
132
+ */
133
+ export function updateRequest(current, changes, cwd, fallbackModel) {
134
+ const cron = current.schedule.kind === 'cron' ? current.schedule : undefined;
135
+ let spec;
136
+ if (changes.when === undefined) {
137
+ if (changes.tz === undefined)
138
+ spec = current.schedule;
139
+ else if (cron)
140
+ spec = { ...cron, tz: validateTimeZone(changes.tz) };
141
+ else
142
+ throw new JobRequestError(`a time zone changes only a cron schedule, and "${current.name}" is not one; give when as well.`);
143
+ }
144
+ const tz = changes.tz ?? cron?.tz;
145
+ const dirs = current.permissions.additionalDirectories;
146
+ const kept = dirs && dirs.length > 0 ? { additionalDirectories: [...dirs] } : {};
147
+ const proposed = changes.permissions;
148
+ const permissions = proposed
149
+ ? {
150
+ ...(proposed.preset ? { preset: proposed.preset } : {}),
151
+ ...(proposed.rules ? { rules: proposed.rules } : {}),
152
+ unmatched: proposed.unmatched,
153
+ execution: proposed.execution ?? current.permissions.execution,
154
+ ...kept,
155
+ ...(proposed.browser ? { browser: proposed.browser } : {}),
156
+ }
157
+ : {
158
+ rules: current.permissions.rules,
159
+ unmatched: current.permissions.unmatched,
160
+ execution: current.permissions.execution,
161
+ ...kept,
162
+ ...(current.permissions.browser ? { browser: current.permissions.browser } : {}),
163
+ };
164
+ // A kind or script the tool did not touch carries the job's own forward
165
+ // unchanged, exactly as `editedJob` does for a terminal edit. Moving TO
166
+ // `'agent'` always drops the script — whether or not one was given here,
167
+ // since an agent job cannot carry one (`buildJob` refuses that
168
+ // combination outright) — and the wake-gate cap only ever applies to a
169
+ // `runKind` that stays (or becomes) `'script+agent'`.
170
+ const runKind = changes.runKind ?? current.runKind;
171
+ if (runKind === 'script' && changes.prompt !== undefined)
172
+ throw new JobRequestError('a pure script job has no prompt; remove prompt');
173
+ if (runKind === 'script' && changes.budget !== undefined)
174
+ throw new JobRequestError('a pure script job has no agent budget; remove budget and use script.timeoutMs for its timeout');
175
+ if (runKind === 'script' &&
176
+ permissions.browser &&
177
+ (current.runKind !== 'script' || changes.permissions !== undefined))
178
+ throw new JobRequestError('a pure script job cannot use a browser grant; remove permissions.browser');
179
+ const script = runKind === 'agent' ? undefined : (changes.script ?? current.script);
180
+ return {
181
+ name: current.name,
182
+ prompt: runKind === 'script' ? '' : (changes.prompt ?? current.prompt),
183
+ when: changes.when ?? '',
184
+ ...(spec ? { spec } : {}),
185
+ folder: changes.folder !== undefined ? resolve(cwd, changes.folder) : current.folder.path,
186
+ ...(tz ? { tz } : {}),
187
+ ...(runKind ? { runKind } : {}),
188
+ ...(script ? { script } : {}),
189
+ ...(runKind === 'script+agent' && current.wakeGate ? { wakeGate: current.wakeGate } : {}),
190
+ permissions,
191
+ budget: { ...current.budget, ...(changes.budget ?? {}) },
192
+ ...(runKind === 'script'
193
+ ? {}
194
+ : current.model
195
+ ? {
196
+ model: `${current.model.provider}${current.model.model ? `/${current.model.model}` : ''}`,
197
+ ...(current.model.effort ? { effort: current.model.effort } : {}),
198
+ }
199
+ : fallbackModel
200
+ ? {
201
+ model: `${fallbackModel.provider}${fallbackModel.model ? `/${fallbackModel.model}` : ''}`,
202
+ }
203
+ : {}),
204
+ includeSummary: current.notify.includeSummary,
205
+ keepSessions: current.retention.keepSessions,
206
+ pauseAfterFailures: current.failurePolicy.pauseAfterFailures,
207
+ approvalTtlMs: current.approvalTtlMs,
208
+ createdBy: current.createdBy,
209
+ // `unmatched: allow` on the host is the operator's choice
210
+ // (`--allow-unattended-host`); a change that keeps the permissions
211
+ // keeps it, and the tool can never propose it.
212
+ allowUnattendedHost: proposed === undefined && current.permissions.unmatched === 'allow',
213
+ };
67
214
  }
68
215
  /**
69
216
  * Each optional value the model set to something other than what the
@@ -97,6 +244,96 @@ export function chosenByTheModel(draft, job, cwd, config) {
97
244
  }
98
245
  export function createScheduleToolHost(ui) {
99
246
  const built = new WeakMap();
247
+ const proposedUpdates = new WeakMap();
248
+ /** What the person is shown for a job, every field computed here; `extra` warnings last. */
249
+ const previewOf = async (job, extra, now) => {
250
+ const layers = readPermissionLayers({ cwd: job.folder.canonical });
251
+ const policy = compileJobPolicy(job.permissions, {
252
+ layers,
253
+ namzuHome: ui.home(),
254
+ folder: job.folder,
255
+ });
256
+ if (policy.diagnostics.length > 0)
257
+ throw new Error(`The rules do not compile: ${policy.diagnostics.join('; ')}`);
258
+ if (job.runKind && job.runKind !== 'agent' && !job.script)
259
+ throw new Error(`The ${job.runKind} job has no script to preview.`);
260
+ // A model-proposed script/wake-gate gets the same static check
261
+ // `schedule add` runs before anything is shown, so a proposal the
262
+ // floor or a `deny` rule refuses never reaches this confirmation
263
+ // screen at all.
264
+ if (job.runKind && job.runKind !== 'agent' && job.script) {
265
+ const selectedShell = installedCommandShellForDialect(job.script.shell);
266
+ if (!selectedShell)
267
+ throw new Error(scriptShellUnavailableReason(job.script.shell));
268
+ const scriptPolicy = compileScriptCheckPolicy(job.permissions, {
269
+ layers,
270
+ namzuHome: ui.home(),
271
+ folder: job.folder,
272
+ });
273
+ const checked = verifyScheduledScript(job.script.body, selectedShell.dialect, scriptPolicy);
274
+ if (!checked.ok) {
275
+ throw new Error(`The ${job.runKind === 'script' ? 'script' : 'wake-gate script'} was refused: ${checked.reason}`);
276
+ }
277
+ }
278
+ const roots = [ui.cwd(), ...ui.extraRoots()];
279
+ const outside = !roots.some((root) => within(root, job.folder.canonical));
280
+ const perDay = runsPerDay(job.schedule, now);
281
+ const hasScriptPhase = job.runKind === 'script' || job.runKind === 'script+agent';
282
+ const networkCapable = policy.network ||
283
+ (job.permissions.execution === 'host' && (hasScriptPhase || allowsCommands(job.permissions)));
284
+ const warnings = [
285
+ ...(outside
286
+ ? ['The folder is outside this session’s working directory and added directories.']
287
+ : []),
288
+ ...(networkCapable ? ['This run can reach the network.'] : []),
289
+ ...(job.permissions.browser && job.runKind !== 'script'
290
+ ? [
291
+ `This run drives the browser signed in as you (profile ${job.permissions.browser.profile}) on ${Object.keys(job.permissions.browser.sites).join(', ')}.`,
292
+ ]
293
+ : []),
294
+ ...extra,
295
+ ];
296
+ const preview = {
297
+ name: job.name,
298
+ folder: job.folder.canonical,
299
+ outsideSessionRoots: outside,
300
+ prompt: job.prompt,
301
+ ...(job.runKind && job.runKind !== 'agent' ? { runKind: job.runKind } : {}),
302
+ ...(job.script ? { script: job.script } : {}),
303
+ schedule: describeSchedule(job.schedule, {
304
+ tz: job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone(),
305
+ }),
306
+ nextFireTimes: upcomingFireTimes(job.schedule, now, 3).map((t) => t.toISOString()),
307
+ rules: policy.lines,
308
+ unmatched: job.permissions.unmatched,
309
+ execution: job.permissions.execution,
310
+ networkAccess: networkCapable,
311
+ networkGrantAccess: policy.network,
312
+ budget: job.runKind === 'script' && job.script
313
+ ? {
314
+ maxIterations: 0,
315
+ tokenBudget: 0,
316
+ timeoutMs: job.script.timeoutMs,
317
+ }
318
+ : {
319
+ maxIterations: job.budget.maxIterations,
320
+ tokenBudget: job.budget.tokenBudget,
321
+ timeoutMs: job.budget.timeoutMs,
322
+ },
323
+ ...(job.runKind === 'script' || job.schedule.kind === 'at'
324
+ ? {}
325
+ : { dailyTokenCeiling: perDay * job.budget.tokenBudget }),
326
+ ...(job.runKind === 'script' || !job.model
327
+ ? {}
328
+ : {
329
+ model: `${job.model.provider}${job.model.model ? `/${job.model.model}` : ''}`,
330
+ credentialSource: await credentialText(job.model.provider),
331
+ }),
332
+ warnings,
333
+ };
334
+ return { preview, policy };
335
+ };
336
+ const NOT_INSTALLED_NOTE = 'No scheduler is installed on this machine, so the job does not run until the operator runs `namzu schedule install`. Tell them; do not say it will run.';
100
337
  const paths = () => schedulePaths(ui.home());
101
338
  const history = (job, action) => appendHistory(paths(), job.id, {
102
339
  v: 1,
@@ -110,16 +347,22 @@ export function createScheduleToolHost(ui) {
110
347
  // confirmation shows it line by line, so the model may propose one.
111
348
  browserGrants: true,
112
349
  async preview(draft) {
350
+ if (draft.runKind === 'script' && draft.budget !== undefined)
351
+ throw new JobRequestError('a pure script job has no agent budget; remove budget and use script.timeoutMs for its timeout');
352
+ if (draft.runKind === 'script' && draft.permissions.browser !== undefined)
353
+ throw new JobRequestError('a pure script job cannot use a browser grant; remove permissions.browser');
113
354
  const model = ui.model();
114
- if (!model)
355
+ if (!model && draft.runKind !== 'script')
115
356
  throw new Error('This session has no model; a scheduled job runs on the session’s model.');
116
357
  const now = new Date();
117
358
  const folder = resolve(ui.cwd(), draft.folder ?? '.');
118
359
  const job = buildJob({
119
360
  name: draft.name,
120
- prompt: draft.prompt,
361
+ prompt: draft.prompt ?? '',
121
362
  when: draft.when,
122
363
  folder,
364
+ ...(draft.runKind ? { runKind: draft.runKind } : {}),
365
+ ...(draft.script ? { script: draft.script } : {}),
123
366
  ...(draft.tz ? { tz: draft.tz } : {}),
124
367
  permissions: {
125
368
  ...(draft.permissions.preset ? { preset: draft.permissions.preset } : {}),
@@ -129,59 +372,18 @@ export function createScheduleToolHost(ui) {
129
372
  ...(draft.permissions.browser ? { browser: draft.permissions.browser } : {}),
130
373
  },
131
374
  ...(draft.budget ? { budget: draft.budget } : {}),
132
- model: `${model.provider}${model.model ? `/${model.model}` : ''}`,
375
+ ...(model && draft.runKind !== 'script'
376
+ ? {
377
+ model: `${model.provider}${model.model ? `/${model.model}` : ''}`,
378
+ }
379
+ : {}),
133
380
  createdBy: {
134
381
  surface: 'tool',
135
382
  ...(ui.sessionId() ? { sessionId: ui.sessionId() } : {}),
136
383
  },
137
384
  }, { paths: paths(), config: ui.config(), now });
138
- const policy = compileJobPolicy(job.permissions, {
139
- layers: readPermissionLayers({ cwd: job.folder.canonical }),
140
- namzuHome: ui.home(),
141
- folder: job.folder,
142
- });
143
- if (policy.diagnostics.length > 0)
144
- throw new Error(`The rules do not compile: ${policy.diagnostics.join('; ')}`);
145
- const roots = [ui.cwd(), ...ui.extraRoots()];
146
- const outside = !roots.some((root) => within(root, job.folder.canonical));
147
- const perDay = runsPerDay(job.schedule, now);
148
- const warnings = [
149
- ...(outside
150
- ? ['The folder is outside this session’s working directory and added directories.']
151
- : []),
152
- ...(policy.network ? ['This run can reach the network.'] : []),
153
- ...(job.permissions.browser
154
- ? [
155
- `This run drives the browser signed in as you (profile ${job.permissions.browser.profile}) on ${Object.keys(job.permissions.browser.sites).join(', ')}.`,
156
- ]
157
- : []),
158
- ...chosenByTheModel(draft, job, ui.cwd(), ui.config()).map((line) => `Chosen by the model, not the default: ${line}`),
159
- ];
160
- const preview = {
161
- name: job.name,
162
- folder: job.folder.canonical,
163
- outsideSessionRoots: outside,
164
- prompt: job.prompt,
165
- schedule: describeSchedule(job.schedule, {
166
- tz: job.schedule.kind === 'cron' ? job.schedule.tz : hostTimeZone(),
167
- }),
168
- nextFireTimes: upcomingFireTimes(job.schedule, now, 3).map((t) => t.toISOString()),
169
- rules: policy.lines,
170
- unmatched: job.permissions.unmatched,
171
- execution: job.permissions.execution,
172
- networkAccess: policy.network,
173
- budget: {
174
- maxIterations: job.budget.maxIterations,
175
- tokenBudget: job.budget.tokenBudget,
176
- timeoutMs: job.budget.timeoutMs,
177
- },
178
- ...(job.schedule.kind === 'at'
179
- ? {}
180
- : { dailyTokenCeiling: perDay * job.budget.tokenBudget }),
181
- model: `${job.model.provider}${job.model.model ? `/${job.model.model}` : ''}`,
182
- credentialSource: await credentialText(job.model.provider),
183
- warnings,
184
- };
385
+ const chosen = chosenByTheModel(draft, job, ui.cwd(), ui.config()).map((line) => `Chosen by the model, not the default: ${line}`);
386
+ const { preview, policy } = await previewOf(job, chosen, now);
185
387
  built.set(preview, { job, lines: previewLines(job, policy, now) });
186
388
  return preview;
187
389
  },
@@ -192,7 +394,7 @@ export function createScheduleToolHost(ui) {
192
394
  ui.say(renderConfirmation(request, entry.lines));
193
395
  const answer = await ui.ask({
194
396
  questionId: `schedule:${entry.job.id}`,
195
- question: `Create the scheduled job "${entry.job.name}" the model proposed? (details above)`,
397
+ question: visibleScheduleMessage(`Create the scheduled job "${entry.job.name}" the model proposed? (details above)`),
196
398
  header: 'Proposed by the model',
197
399
  options: [
198
400
  { id: 'cancel', label: 'Cancel', description: 'Create nothing' },
@@ -219,7 +421,9 @@ export function createScheduleToolHost(ui) {
219
421
  const entry = built.get(preview);
220
422
  if (!entry)
221
423
  throw new Error('That proposal is no longer current; propose it again.');
222
- const job = createJob(paths(), confirmJob(entry.job, 'tool-confirmed', new Date(), { paused: options.paused }));
424
+ const job = createJob(paths(), confirmJob(entry.job, 'tool-confirmed', new Date(), {
425
+ paused: options.paused,
426
+ }));
223
427
  history(job, 'created');
224
428
  // A job with no scheduler to run it does nothing, and the model's
225
429
  // "done" said nothing about that.
@@ -227,23 +431,115 @@ export function createScheduleToolHost(ui) {
227
431
  ui.say(`⏲ Scheduled job ${job.name} created${options.paused ? ' (paused)' : ''}. /schedule lists it.${installed ? '' : ' The scheduler is not installed, so it does not run until you install it: namzu schedule install.'}`);
228
432
  return {
229
433
  name: job.name,
230
- ...(installed
231
- ? {}
232
- : {
233
- note: 'No scheduler is installed on this machine, so the job does not run until the operator runs `namzu schedule install`. Tell them; do not say it will run.',
234
- }),
434
+ ...(installed ? {} : { note: NOT_INSTALLED_NOTE }),
235
435
  };
236
436
  },
237
- async list(options) {
238
- const cwd = ui.cwd();
239
- return listJobs(paths())
240
- .jobs.filter((job) => options.allFolders || job.folder.canonical === cwd)
241
- .map((job) => summary(job, job.folder.canonical === cwd, ui.home()));
437
+ // A change is confirmed like a new job, whole, with what changes
438
+ // above it, and saved to the same job: its id, name and history stay.
439
+ // Every field it can touch is in the confirmation's digest, so the
440
+ // saved job carries the operator's new confirmation, as an edit on a
441
+ // terminal does.
442
+ async previewUpdate(ref, changes) {
443
+ const current = findJob(paths(), ref);
444
+ const now = new Date();
445
+ const job = editedJob(current, buildJob(updateRequest(current, changes, ui.cwd(), ui.model()), {
446
+ paths: paths(),
447
+ config: ui.config(),
448
+ now,
449
+ }));
450
+ const { preview, policy } = await previewOf(job, [], now);
451
+ const view = (j) => confirmationView(j, compileJobPolicy(j.permissions, {
452
+ layers: readPermissionLayers({ cwd: j.folder.canonical }),
453
+ namzuHome: ui.home(),
454
+ folder: j.folder,
455
+ }), now);
456
+ const differs = describeChanges(view(current), view(job));
457
+ if (differs.length === 0)
458
+ throw new Error(`That changes nothing in "${current.name}".`);
459
+ const lines = [...changesSinceConfirmed(readHistory(paths(), current.id)), ...differs];
460
+ proposedUpdates.set(preview, {
461
+ current,
462
+ job,
463
+ lines: previewLines(job, policy, now),
464
+ changes: lines,
465
+ });
466
+ return {
467
+ preview,
468
+ changes: lines,
469
+ permissionsChange: permissionsChanged(current, job),
470
+ };
471
+ },
472
+ async confirmUpdate(request, signal) {
473
+ const entry = proposedUpdates.get(request.preview);
474
+ if (!entry)
475
+ return false;
476
+ ui.say(renderUpdateConfirmation(request, entry.lines));
477
+ const answer = await ui.ask({
478
+ questionId: `schedule-update:${entry.current.id}`,
479
+ question: visibleScheduleMessage(`Save the change the model proposed to the scheduled job "${entry.current.name}"? (details above)`),
480
+ header: 'Proposed by the model',
481
+ options: [
482
+ { id: 'cancel', label: 'Cancel', description: 'Change nothing' },
483
+ {
484
+ id: 'save',
485
+ label: 'Save',
486
+ description: request.permissionsChange
487
+ ? 'It keeps its history and runs under the new permissions above'
488
+ : 'It keeps its history and runs as shown above',
489
+ },
490
+ ],
491
+ multiSelect: false,
492
+ allowFreeText: false,
493
+ }, signal);
494
+ return answer.kind === 'answer' && answer.selectedOptionIds[0] === 'save';
495
+ },
496
+ async update(preview) {
497
+ const entry = proposedUpdates.get(preview);
498
+ if (!entry)
499
+ throw new Error('That proposal is no longer current; propose it again.');
500
+ const now = new Date();
501
+ let next;
502
+ try {
503
+ next = updateJob(paths(), entry.current.id, entry.current.revision, () => confirmJob(entry.job, 'tool-confirmed', now, {
504
+ paused: entry.current.state === 'paused',
505
+ }));
506
+ }
507
+ catch (error) {
508
+ if (error instanceof ScheduleConflictError)
509
+ throw new Error(`"${entry.current.name}" changed while the operator was being asked; propose the change again.`);
510
+ throw error;
511
+ }
512
+ proposedUpdates.delete(preview);
513
+ appendHistory(paths(), next.id, {
514
+ v: 1,
515
+ kind: 'job',
516
+ at: now.toISOString(),
517
+ action: 'edited',
518
+ by: 'tool',
519
+ changes: entry.changes,
520
+ });
521
+ const installed = readManifest(paths()) !== undefined;
522
+ ui.say(`⏲ Scheduled job ${next.name} changed (${next.state}); it keeps its history. /schedule shows it.${installed ? '' : ' The scheduler is not installed, so it does not run until you install it: namzu schedule install.'}`);
523
+ return {
524
+ name: next.name,
525
+ ...(installed ? {} : { note: NOT_INSTALLED_NOTE }),
526
+ };
527
+ },
528
+ // Every job, whatever `allFolders` says: a model that has just created
529
+ // a job in another folder, and does not pass `allFolders`, was told
530
+ // "No scheduled jobs." while `namzu schedule list` showed it. The
531
+ // session folder's are marked, and only theirs carry the prompt.
532
+ async list() {
533
+ const cwd = canonicalCwd(ui.cwd());
534
+ const { jobs, errors } = listJobs(paths());
535
+ if (errors.length > 0)
536
+ throw new Error(`${errors.length} job file${errors.length === 1 ? '' : 's'} could not be read; inspect or repair the files under NAMZU_HOME before listing scheduled jobs.`);
537
+ return jobs.map((job) => summary(job, job.folder.canonical === cwd, ui.home()));
242
538
  },
243
539
  async find(ref) {
244
540
  try {
245
541
  const job = findJob(paths(), ref);
246
- return summary(job, job.folder.canonical === ui.cwd(), ui.home());
542
+ return summary(job, job.folder.canonical === canonicalCwd(ui.cwd()), ui.home());
247
543
  }
248
544
  catch {
249
545
  return undefined;
@@ -252,11 +548,15 @@ export function createScheduleToolHost(ui) {
252
548
  async confirmAction(job, action, signal) {
253
549
  const answer = await ui.ask({
254
550
  questionId: `schedule-${action}:${job.name}`,
255
- question: `${action === 'delete' ? 'Delete' : 'Resume'} the scheduled job "${job.name}" (${job.schedule}, in ${job.folder})? The model asked.`,
551
+ question: visibleScheduleMessage(`${action === 'delete' ? 'Delete' : 'Resume'} the scheduled job "${job.name}" (${job.schedule}, in ${job.folder})? The model asked.`),
256
552
  header: 'Proposed by the model',
257
553
  options: [
258
554
  { id: 'no', label: 'No', description: 'Leave it as it is' },
259
- { id: 'yes', label: action === 'delete' ? 'Delete it' : 'Resume it', description: '' },
555
+ {
556
+ id: 'yes',
557
+ label: action === 'delete' ? 'Delete it' : 'Resume it',
558
+ description: '',
559
+ },
260
560
  ],
261
561
  multiSelect: false,
262
562
  allowFreeText: false,