@cjhyy/code-shell-core 0.5.0-rc.1 → 0.5.0-rc.2

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 (278) hide show
  1. package/dist/agent/agent-definition-registry.d.ts +18 -3
  2. package/dist/agent/agent-definition-registry.js +47 -18
  3. package/dist/agent/agent-definition.d.ts +17 -0
  4. package/dist/agent/agent-definition.js +22 -1
  5. package/dist/arena/arena.js +11 -12
  6. package/dist/arena/context/context-tools.js +2 -4
  7. package/dist/arena/context/within-root.d.ts +7 -0
  8. package/dist/arena/context/within-root.js +15 -0
  9. package/dist/arena/iterate/phases/argue.js +8 -2
  10. package/dist/arena/iterate/phases/revise.js +1 -1
  11. package/dist/arena/iterate/phases/tournament.js +3 -3
  12. package/dist/arena/phases/adjudication.js +1 -4
  13. package/dist/arena/phases/build-consensus.js +1 -4
  14. package/dist/arena/phases/cross-review.js +2 -8
  15. package/dist/arena/phases/debate-rounds.js +1 -4
  16. package/dist/arena/phases/participant-research.js +1 -4
  17. package/dist/arena/phases/planning-detail-expansion.js +8 -10
  18. package/dist/arena/planner.js +0 -1
  19. package/dist/arena/providers/docs.d.ts +1 -0
  20. package/dist/arena/providers/docs.js +5 -2
  21. package/dist/arena/providers/repo.d.ts +1 -0
  22. package/dist/arena/providers/repo.js +5 -2
  23. package/dist/arena/strategies/utils.js +36 -3
  24. package/dist/arena/transitions.d.ts +7 -2
  25. package/dist/arena/transitions.js +9 -4
  26. package/dist/arena/types.d.ts +8 -2
  27. package/dist/automation/cron-expr.d.ts +31 -0
  28. package/dist/automation/cron-expr.js +151 -0
  29. package/dist/automation/index.d.ts +41 -0
  30. package/dist/automation/index.js +39 -0
  31. package/dist/automation/runner.d.ts +67 -0
  32. package/dist/automation/runner.js +56 -0
  33. package/dist/automation/scheduler.d.ts +156 -0
  34. package/dist/automation/scheduler.js +546 -0
  35. package/dist/automation/store.d.ts +39 -0
  36. package/dist/automation/store.js +119 -0
  37. package/dist/automation/write-policy.d.ts +34 -0
  38. package/dist/automation/write-policy.js +113 -0
  39. package/dist/automation/write-run.d.ts +51 -0
  40. package/dist/automation/write-run.js +38 -0
  41. package/dist/capability-control/index.d.ts +5 -0
  42. package/dist/capability-control/index.js +3 -0
  43. package/dist/capability-control/overlay.d.ts +49 -0
  44. package/dist/capability-control/overlay.js +89 -0
  45. package/dist/capability-control/project.d.ts +65 -0
  46. package/dist/capability-control/project.js +147 -0
  47. package/dist/capability-control/service.d.ts +70 -0
  48. package/dist/capability-control/service.js +147 -0
  49. package/dist/capability-control/types.d.ts +62 -0
  50. package/dist/capability-control/types.js +17 -0
  51. package/dist/cli/agent-server-stdio.d.ts +12 -1
  52. package/dist/cli/agent-server-stdio.js +114 -19
  53. package/dist/cli/agent-server-tcp.d.ts +20 -0
  54. package/dist/cli/agent-server-tcp.js +120 -0
  55. package/dist/cli/graceful-shutdown.d.ts +27 -0
  56. package/dist/cli/graceful-shutdown.js +30 -0
  57. package/dist/context/compaction.js +23 -15
  58. package/dist/context/manager.d.ts +0 -20
  59. package/dist/context/manager.js +0 -37
  60. package/dist/cron/cron-runtime.d.ts +2 -0
  61. package/dist/cron/cron-runtime.js +2 -0
  62. package/dist/cron/cron-store.d.ts +2 -0
  63. package/dist/cron/cron-store.js +2 -0
  64. package/dist/cron/scheduler.d.ts +5 -32
  65. package/dist/cron/scheduler.js +5 -113
  66. package/dist/data/openrouter-sync.js +1 -1
  67. package/dist/engine/engine.d.ts +208 -14
  68. package/dist/engine/engine.js +754 -169
  69. package/dist/engine/goal.d.ts +42 -0
  70. package/dist/engine/goal.js +52 -0
  71. package/dist/engine/image-compression.d.ts +81 -0
  72. package/dist/engine/image-compression.js +186 -0
  73. package/dist/engine/image-policy.d.ts +152 -0
  74. package/dist/engine/image-policy.js +165 -0
  75. package/dist/engine/reactive-threshold.d.ts +13 -0
  76. package/dist/engine/reactive-threshold.js +17 -0
  77. package/dist/engine/runtime.js +7 -0
  78. package/dist/engine/session-title.d.ts +13 -0
  79. package/dist/engine/session-title.js +34 -0
  80. package/dist/engine/turn-loop.d.ts +29 -8
  81. package/dist/engine/turn-loop.js +498 -316
  82. package/dist/git/parse-log.d.ts +13 -0
  83. package/dist/git/parse-log.js +21 -0
  84. package/dist/git/utils.d.ts +8 -6
  85. package/dist/git/utils.js +57 -26
  86. package/dist/git/worktree.js +33 -18
  87. package/dist/hooks/events.d.ts +23 -1
  88. package/dist/hooks/goal-stop-hook.d.ts +30 -0
  89. package/dist/hooks/goal-stop-hook.js +102 -0
  90. package/dist/hooks/hook-output.d.ts +33 -0
  91. package/dist/hooks/hook-output.js +79 -0
  92. package/dist/hooks/registry.d.ts +7 -0
  93. package/dist/hooks/registry.js +38 -1
  94. package/dist/hooks/shell-runner.d.ts +2 -0
  95. package/dist/hooks/shell-runner.js +43 -2
  96. package/dist/index.d.ts +31 -6
  97. package/dist/index.js +30 -3
  98. package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
  99. package/dist/llm/capabilities/reasoning-control.js +38 -0
  100. package/dist/llm/capabilities/rules.js +33 -3
  101. package/dist/llm/capabilities/types.d.ts +22 -2
  102. package/dist/llm/clamp-max-tokens.d.ts +11 -0
  103. package/dist/llm/clamp-max-tokens.js +17 -0
  104. package/dist/llm/client-base.d.ts +41 -4
  105. package/dist/llm/client-base.js +67 -13
  106. package/dist/llm/client-factory.d.ts +3 -3
  107. package/dist/llm/client-factory.js +2 -2
  108. package/dist/llm/model-pool.d.ts +15 -11
  109. package/dist/llm/model-pool.js +23 -20
  110. package/dist/llm/provider-catalog.d.ts +2 -2
  111. package/dist/llm/providers/anthropic.d.ts +32 -2
  112. package/dist/llm/providers/anthropic.js +147 -8
  113. package/dist/llm/providers/openai.d.ts +11 -2
  114. package/dist/llm/providers/openai.js +253 -52
  115. package/dist/llm/reasoning-setting.d.ts +51 -0
  116. package/dist/llm/reasoning-setting.js +32 -0
  117. package/dist/llm/stop-reason.d.ts +13 -0
  118. package/dist/llm/stop-reason.js +21 -0
  119. package/dist/llm/strip-vision.d.ts +32 -0
  120. package/dist/llm/strip-vision.js +94 -0
  121. package/dist/llm/types.d.ts +5 -6
  122. package/dist/logging/logger.js +18 -4
  123. package/dist/logging/sanitize-messages.d.ts +10 -0
  124. package/dist/logging/sanitize-messages.js +90 -0
  125. package/dist/lsp/client.js +28 -8
  126. package/dist/lsp/manager.js +2 -1
  127. package/dist/lsp/root-path.d.ts +9 -0
  128. package/dist/lsp/root-path.js +12 -0
  129. package/dist/onboarding.js +18 -11
  130. package/dist/plugins/gitOps.d.ts +34 -0
  131. package/dist/plugins/gitOps.js +59 -3
  132. package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
  133. package/dist/plugins/installer/codex/convertAgents.js +55 -0
  134. package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
  135. package/dist/plugins/installer/codex/convertMcp.js +45 -0
  136. package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
  137. package/dist/plugins/installer/codex/convertSkills.js +33 -0
  138. package/dist/plugins/installer/detectFormat.d.ts +2 -0
  139. package/dist/plugins/installer/detectFormat.js +6 -0
  140. package/dist/plugins/installer/install.d.ts +7 -0
  141. package/dist/plugins/installer/install.js +99 -0
  142. package/dist/plugins/installer/installFromSource.d.ts +13 -0
  143. package/dist/plugins/installer/installFromSource.js +45 -0
  144. package/dist/plugins/installer/list.d.ts +9 -0
  145. package/dist/plugins/installer/list.js +30 -0
  146. package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
  147. package/dist/plugins/installer/loadPluginAgents.js +23 -0
  148. package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
  149. package/dist/plugins/installer/loadPluginMcp.js +74 -0
  150. package/dist/plugins/installer/parseSource.d.ts +20 -0
  151. package/dist/plugins/installer/parseSource.js +74 -0
  152. package/dist/plugins/installer/paths.d.ts +5 -0
  153. package/dist/plugins/installer/paths.js +25 -0
  154. package/dist/plugins/installer/types.d.ts +49 -0
  155. package/dist/plugins/installer/types.js +27 -0
  156. package/dist/plugins/installer/uninstall.d.ts +2 -0
  157. package/dist/plugins/installer/uninstall.js +14 -0
  158. package/dist/plugins/installer/update.d.ts +14 -0
  159. package/dist/plugins/installer/update.js +65 -0
  160. package/dist/plugins/loadPluginHooks.d.ts +8 -1
  161. package/dist/plugins/loadPluginHooks.js +11 -1
  162. package/dist/plugins/marketplaceManager.d.ts +8 -1
  163. package/dist/plugins/marketplaceManager.js +38 -4
  164. package/dist/plugins/pluginCommandHook.js +34 -0
  165. package/dist/plugins/pluginInstaller.d.ts +21 -0
  166. package/dist/plugins/pluginInstaller.js +128 -29
  167. package/dist/plugins/schemas.js +40 -6
  168. package/dist/plugins/types.d.ts +4 -0
  169. package/dist/preset/index.js +16 -0
  170. package/dist/prompt/composer.d.ts +4 -0
  171. package/dist/prompt/composer.js +28 -4
  172. package/dist/prompt/sections/orchestration.md +8 -0
  173. package/dist/protocol/chat-session-manager.d.ts +8 -1
  174. package/dist/protocol/chat-session-manager.js +10 -0
  175. package/dist/protocol/chat-session.d.ts +17 -0
  176. package/dist/protocol/chat-session.js +28 -0
  177. package/dist/protocol/redact.d.ts +50 -0
  178. package/dist/protocol/redact.js +71 -0
  179. package/dist/protocol/server.d.ts +41 -2
  180. package/dist/protocol/server.js +231 -33
  181. package/dist/protocol/tcp-transport.d.ts +45 -0
  182. package/dist/protocol/tcp-transport.js +74 -0
  183. package/dist/protocol/types.d.ts +23 -0
  184. package/dist/protocol/types.js +6 -0
  185. package/dist/remote/bridge.d.ts +11 -2
  186. package/dist/remote/bridge.js +61 -33
  187. package/dist/run/ArtifactTracker.js +5 -4
  188. package/dist/run/EngineRunner.d.ts +27 -1
  189. package/dist/run/EngineRunner.js +46 -8
  190. package/dist/run/FileRunStore.js +32 -8
  191. package/dist/run/RunLock.d.ts +28 -2
  192. package/dist/run/RunLock.js +49 -7
  193. package/dist/run/RunManager.js +51 -3
  194. package/dist/run/RunQueue.d.ts +1 -0
  195. package/dist/run/RunQueue.js +8 -2
  196. package/dist/run/factory.d.ts +7 -0
  197. package/dist/run/factory.js +1 -0
  198. package/dist/run/index.d.ts +2 -2
  199. package/dist/run/index.js +1 -1
  200. package/dist/run/redirect-target.d.ts +7 -0
  201. package/dist/run/redirect-target.js +13 -0
  202. package/dist/run/types.js +1 -1
  203. package/dist/services/browser-open.d.ts +13 -0
  204. package/dist/services/browser-open.js +16 -0
  205. package/dist/services/dream-consolidation.d.ts +57 -0
  206. package/dist/services/dream-consolidation.js +151 -0
  207. package/dist/services/memory-orchestrator.js +18 -0
  208. package/dist/services/notifier.d.ts +8 -0
  209. package/dist/services/notifier.js +39 -25
  210. package/dist/services/oauth.js +5 -7
  211. package/dist/services/session-memory-sort.d.ts +8 -0
  212. package/dist/services/session-memory-sort.js +9 -0
  213. package/dist/services/session-memory.js +8 -9
  214. package/dist/session/file-history.js +10 -3
  215. package/dist/session/session-manager.d.ts +23 -1
  216. package/dist/session/session-manager.js +73 -2
  217. package/dist/settings/disk-defaults.d.ts +35 -0
  218. package/dist/settings/disk-defaults.js +24 -0
  219. package/dist/settings/manager.d.ts +33 -0
  220. package/dist/settings/manager.js +94 -1
  221. package/dist/settings/personalization.d.ts +19 -0
  222. package/dist/settings/personalization.js +7 -0
  223. package/dist/settings/schema.d.ts +1142 -126
  224. package/dist/settings/schema.js +119 -10
  225. package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
  226. package/dist/tool-system/builtin/add-marketplace.js +76 -0
  227. package/dist/tool-system/builtin/agent-registry.d.ts +11 -0
  228. package/dist/tool-system/builtin/agent-registry.js +8 -4
  229. package/dist/tool-system/builtin/agent.d.ts +29 -10
  230. package/dist/tool-system/builtin/agent.js +108 -52
  231. package/dist/tool-system/builtin/apply-patch/index.js +22 -3
  232. package/dist/tool-system/builtin/arena.js +6 -7
  233. package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
  234. package/dist/tool-system/builtin/complete-goal.js +45 -0
  235. package/dist/tool-system/builtin/cron.js +53 -9
  236. package/dist/tool-system/builtin/edit.d.ts +2 -1
  237. package/dist/tool-system/builtin/edit.js +6 -1
  238. package/dist/tool-system/builtin/generate-image.d.ts +25 -0
  239. package/dist/tool-system/builtin/generate-image.js +146 -0
  240. package/dist/tool-system/builtin/glob.js +7 -0
  241. package/dist/tool-system/builtin/grep.js +6 -0
  242. package/dist/tool-system/builtin/index.d.ts +18 -1
  243. package/dist/tool-system/builtin/index.js +62 -1
  244. package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
  245. package/dist/tool-system/builtin/notebook-edit.js +8 -1
  246. package/dist/tool-system/builtin/read.d.ts +2 -1
  247. package/dist/tool-system/builtin/read.js +9 -1
  248. package/dist/tool-system/builtin/sleep.js +9 -3
  249. package/dist/tool-system/builtin/task.js +4 -1
  250. package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
  251. package/dist/tool-system/builtin/update-automation-memory.js +60 -0
  252. package/dist/tool-system/builtin/view-image.d.ts +18 -0
  253. package/dist/tool-system/builtin/view-image.js +96 -0
  254. package/dist/tool-system/builtin/web-search.d.ts +4 -1
  255. package/dist/tool-system/builtin/web-search.js +36 -3
  256. package/dist/tool-system/builtin/write.d.ts +2 -1
  257. package/dist/tool-system/builtin/write.js +7 -1
  258. package/dist/tool-system/context.d.ts +12 -0
  259. package/dist/tool-system/executor.d.ts +0 -1
  260. package/dist/tool-system/executor.js +34 -40
  261. package/dist/tool-system/investigation-guard.d.ts +4 -1
  262. package/dist/tool-system/investigation-guard.js +19 -3
  263. package/dist/tool-system/mcp-manager.d.ts +36 -12
  264. package/dist/tool-system/mcp-manager.js +173 -10
  265. package/dist/tool-system/path-policy.d.ts +94 -0
  266. package/dist/tool-system/path-policy.js +279 -0
  267. package/dist/tool-system/permission.js +43 -50
  268. package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
  269. package/dist/tool-system/plan-mode-allowlist.js +43 -0
  270. package/dist/tool-system/registry.js +12 -1
  271. package/dist/tool-system/validation.d.ts +10 -0
  272. package/dist/tool-system/validation.js +16 -3
  273. package/dist/types.d.ts +90 -15
  274. package/dist/utils/format.js +4 -2
  275. package/dist/utils/lockfile.js +9 -2
  276. package/dist/utils/theme.d.ts +6 -0
  277. package/dist/utils/theme.js +4 -2
  278. package/package.json +2 -1
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Minimal 5-field cron expression parser + timezone-aware next-trigger
3
+ * calculator. No third-party dependency (core keeps its dep set tight).
4
+ *
5
+ * Fields: minute(0-59) hour(0-23) dayOfMonth(1-31) month(1-12) dayOfWeek(0-6, 0=Sun)
6
+ * Syntax per field: star | star-slash-n | a | a-b | a-b-slash-n | comma-lists of those.
7
+ *
8
+ * Timezone handling uses Intl.DateTimeFormat with a timeZone option to derive
9
+ * the wall-clock parts of a candidate UTC instant in the target zone — correct
10
+ * across DST without a tz library. nextCronTime steps minute-by-minute from the
11
+ * reference and returns the first future instant whose wall-clock parts (in tz)
12
+ * match every field. Bounded to ~2 years so an unsatisfiable expression can't
13
+ * loop forever.
14
+ */
15
+ export interface ParsedCron {
16
+ minutes: Set<number>;
17
+ hours: Set<number>;
18
+ daysOfMonth: Set<number>;
19
+ months: Set<number>;
20
+ daysOfWeek: Set<number>;
21
+ }
22
+ /** Cheap check: is this string a 5-field cron expression (vs an interval like "5m")? */
23
+ export declare function isCronExpression(s: string): boolean;
24
+ /** Parse a 5-field cron expression into per-field value sets. Throws on invalid input. */
25
+ export declare function parseCronExpression(expr: string): ParsedCron;
26
+ /**
27
+ * Return the next UTC instant (ms) strictly after `fromMs` whose wall-clock
28
+ * time in `timeZone` matches `cron`. Returns null if none within ~2 years
29
+ * (e.g. an impossible expression like Feb 30).
30
+ */
31
+ export declare function nextCronTime(cron: ParsedCron, timeZone: string, fromMs: number): number | null;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Minimal 5-field cron expression parser + timezone-aware next-trigger
3
+ * calculator. No third-party dependency (core keeps its dep set tight).
4
+ *
5
+ * Fields: minute(0-59) hour(0-23) dayOfMonth(1-31) month(1-12) dayOfWeek(0-6, 0=Sun)
6
+ * Syntax per field: star | star-slash-n | a | a-b | a-b-slash-n | comma-lists of those.
7
+ *
8
+ * Timezone handling uses Intl.DateTimeFormat with a timeZone option to derive
9
+ * the wall-clock parts of a candidate UTC instant in the target zone — correct
10
+ * across DST without a tz library. nextCronTime steps minute-by-minute from the
11
+ * reference and returns the first future instant whose wall-clock parts (in tz)
12
+ * match every field. Bounded to ~2 years so an unsatisfiable expression can't
13
+ * loop forever.
14
+ */
15
+ const SPECS = [
16
+ { min: 0, max: 59 }, // minute
17
+ { min: 0, max: 23 }, // hour
18
+ { min: 1, max: 31 }, // day of month
19
+ { min: 1, max: 12 }, // month
20
+ { min: 0, max: 6 }, // day of week
21
+ ];
22
+ /** Cheap check: is this string a 5-field cron expression (vs an interval like "5m")? */
23
+ export function isCronExpression(s) {
24
+ if (typeof s !== "string")
25
+ return false;
26
+ const parts = s.trim().split(/\s+/);
27
+ if (parts.length !== 5)
28
+ return false;
29
+ // Every field must contain only cron field characters.
30
+ return parts.every((p) => /^[\d*/,-]+$/.test(p));
31
+ }
32
+ function parseField(field, spec) {
33
+ const out = new Set();
34
+ for (const part of field.split(",")) {
35
+ // Split optional step: "a-b/n" or "*/n" or "a/n"
36
+ const [rangePart, stepPart] = part.split("/");
37
+ const step = stepPart === undefined ? 1 : parseInt(stepPart, 10);
38
+ if (!Number.isInteger(step) || step <= 0) {
39
+ throw new Error(`Invalid cron step in "${field}"`);
40
+ }
41
+ let lo;
42
+ let hi;
43
+ if (rangePart === "*") {
44
+ lo = spec.min;
45
+ hi = spec.max;
46
+ }
47
+ else if (rangePart.includes("-")) {
48
+ const [a, b] = rangePart.split("-");
49
+ lo = parseInt(a, 10);
50
+ hi = parseInt(b, 10);
51
+ }
52
+ else {
53
+ lo = parseInt(rangePart, 10);
54
+ hi = lo;
55
+ }
56
+ if (!Number.isInteger(lo) || !Number.isInteger(hi)) {
57
+ throw new Error(`Invalid cron field "${field}"`);
58
+ }
59
+ if (lo < spec.min || hi > spec.max || lo > hi) {
60
+ throw new Error(`cron field "${field}" out of range [${spec.min}-${spec.max}]`);
61
+ }
62
+ for (let v = lo; v <= hi; v += step)
63
+ out.add(v);
64
+ }
65
+ return out;
66
+ }
67
+ /** Parse a 5-field cron expression into per-field value sets. Throws on invalid input. */
68
+ export function parseCronExpression(expr) {
69
+ const parts = expr.trim().split(/\s+/);
70
+ if (parts.length !== 5) {
71
+ throw new Error(`cron expression must have 5 fields, got ${parts.length}: "${expr}"`);
72
+ }
73
+ return {
74
+ minutes: parseField(parts[0], SPECS[0]),
75
+ hours: parseField(parts[1], SPECS[1]),
76
+ daysOfMonth: parseField(parts[2], SPECS[2]),
77
+ months: parseField(parts[3], SPECS[3]),
78
+ daysOfWeek: parseField(parts[4], SPECS[4]),
79
+ };
80
+ }
81
+ // Cache one formatter per timezone — constructing Intl.DateTimeFormat is costly.
82
+ const formatterCache = new Map();
83
+ function getFormatter(timeZone) {
84
+ let fmt = formatterCache.get(timeZone);
85
+ if (!fmt) {
86
+ fmt = new Intl.DateTimeFormat("en-US", {
87
+ timeZone,
88
+ hour12: false,
89
+ year: "numeric",
90
+ month: "2-digit",
91
+ day: "2-digit",
92
+ hour: "2-digit",
93
+ minute: "2-digit",
94
+ weekday: "short",
95
+ });
96
+ formatterCache.set(timeZone, fmt);
97
+ }
98
+ return fmt;
99
+ }
100
+ const WEEKDAY_INDEX = {
101
+ Sun: 0,
102
+ Mon: 1,
103
+ Tue: 2,
104
+ Wed: 3,
105
+ Thu: 4,
106
+ Fri: 5,
107
+ Sat: 6,
108
+ };
109
+ /** Derive the wall-clock parts of a UTC instant `ms` as seen in `timeZone`. */
110
+ function wallClockInZone(ms, timeZone) {
111
+ const parts = getFormatter(timeZone).formatToParts(new Date(ms));
112
+ const get = (type) => parts.find((p) => p.type === type)?.value ?? "";
113
+ // hour can come back as "24" at midnight in some environments; normalize.
114
+ let hour = parseInt(get("hour"), 10);
115
+ if (hour === 24)
116
+ hour = 0;
117
+ return {
118
+ minute: parseInt(get("minute"), 10),
119
+ hour,
120
+ dayOfMonth: parseInt(get("day"), 10),
121
+ month: parseInt(get("month"), 10),
122
+ dayOfWeek: WEEKDAY_INDEX[get("weekday")] ?? 0,
123
+ };
124
+ }
125
+ function matches(cron, wc) {
126
+ return (cron.minutes.has(wc.minute) &&
127
+ cron.hours.has(wc.hour) &&
128
+ cron.daysOfMonth.has(wc.dayOfMonth) &&
129
+ cron.months.has(wc.month) &&
130
+ cron.daysOfWeek.has(wc.dayOfWeek));
131
+ }
132
+ const MINUTE_MS = 60_000;
133
+ // ~2 years of minutes — bounds the search for an unsatisfiable expression.
134
+ const MAX_STEPS = 2 * 366 * 24 * 60;
135
+ /**
136
+ * Return the next UTC instant (ms) strictly after `fromMs` whose wall-clock
137
+ * time in `timeZone` matches `cron`. Returns null if none within ~2 years
138
+ * (e.g. an impossible expression like Feb 30).
139
+ */
140
+ export function nextCronTime(cron, timeZone, fromMs) {
141
+ // Start at the next whole minute strictly after fromMs (cron has minute
142
+ // granularity; we never return the reference instant itself).
143
+ let candidate = Math.floor(fromMs / MINUTE_MS) * MINUTE_MS + MINUTE_MS;
144
+ for (let i = 0; i < MAX_STEPS; i++) {
145
+ const wc = wallClockInZone(candidate, timeZone);
146
+ if (matches(cron, wc))
147
+ return candidate;
148
+ candidate += MINUTE_MS;
149
+ }
150
+ return null;
151
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * automation/ — zero-environment-dependency scheduling module.
3
+ *
4
+ * Hosts (Electron main, future CLI server) load this module and inject their
5
+ * own store + runner. The module imports nothing from Electron/Ink and makes
6
+ * no GUI/TTY assumptions, so the same code runs in any host
7
+ * (docs/automation-plan-2026-05-31.md, D1).
8
+ */
9
+ import { CronScheduler } from "./scheduler.js";
10
+ import { type CronRunner, type RunSubmitter } from "./runner.js";
11
+ import type { CronStore } from "./store.js";
12
+ export interface StartAutomationDeps {
13
+ /** Persistence backend (injected; module never picks a path itself). */
14
+ store: CronStore;
15
+ /**
16
+ * Execution backend. Provide exactly one:
17
+ * - `runner`: a one-shot run callback (Phase 1 direct-Engine path).
18
+ * - `runManager`: a RunManager submitter (Phase 2 — runs land in RunStore
19
+ * with full history/checkpoint/resume).
20
+ */
21
+ runner?: CronRunner;
22
+ runManager?: RunSubmitter;
23
+ }
24
+ export interface AutomationHandle {
25
+ scheduler: CronScheduler;
26
+ /** Halt all timers and release the scheduler. Idempotent. */
27
+ stop(): void;
28
+ }
29
+ /**
30
+ * Wire a scheduler to a host-provided store + execution backend and restore
31
+ * persisted jobs. Returns a handle the host keeps for its lifetime. Prefers
32
+ * `runManager` when both are supplied (it gives run history).
33
+ */
34
+ export declare function startAutomation(deps: StartAutomationDeps): AutomationHandle;
35
+ export { CronScheduler, cronScheduler, type CronJob } from "./scheduler.js";
36
+ export { CronStore, defaultCronStorePath } from "./store.js";
37
+ export { bindCronToEngine, bindCronToRunManager, type CronRunner, type CronRunRequest, type CronRunResult, type RunSubmitter, } from "./runner.js";
38
+ export { isCronExpression, parseCronExpression, nextCronTime, type ParsedCron, } from "./cron-expr.js";
39
+ export type { CronPermissionLevel, CreateJobOptions, UpdateJobPatch } from "./scheduler.js";
40
+ export { resolveWritePolicy, wrapUntrustedInput, type WritePolicy, } from "./write-policy.js";
41
+ export { runWriteJobInWorktree, type WriteJobGitOps, type RunWriteJobInput, type RunWriteJobResult, } from "./write-run.js";
@@ -0,0 +1,39 @@
1
+ /**
2
+ * automation/ — zero-environment-dependency scheduling module.
3
+ *
4
+ * Hosts (Electron main, future CLI server) load this module and inject their
5
+ * own store + runner. The module imports nothing from Electron/Ink and makes
6
+ * no GUI/TTY assumptions, so the same code runs in any host
7
+ * (docs/automation-plan-2026-05-31.md, D1).
8
+ */
9
+ import { CronScheduler } from "./scheduler.js";
10
+ import { bindCronToEngine, bindCronToRunManager, } from "./runner.js";
11
+ /**
12
+ * Wire a scheduler to a host-provided store + execution backend and restore
13
+ * persisted jobs. Returns a handle the host keeps for its lifetime. Prefers
14
+ * `runManager` when both are supplied (it gives run history).
15
+ */
16
+ export function startAutomation(deps) {
17
+ const scheduler = new CronScheduler(deps.store);
18
+ if (deps.runManager) {
19
+ bindCronToRunManager(scheduler, deps.runManager);
20
+ }
21
+ else if (deps.runner) {
22
+ bindCronToEngine(scheduler, deps.runner);
23
+ }
24
+ else {
25
+ throw new Error("startAutomation requires either a runner or a runManager");
26
+ }
27
+ scheduler.loadJobs();
28
+ return {
29
+ scheduler,
30
+ stop: () => scheduler.stopAll(),
31
+ };
32
+ }
33
+ // Re-export the building blocks so hosts import everything from one place.
34
+ export { CronScheduler, cronScheduler } from "./scheduler.js";
35
+ export { CronStore, defaultCronStorePath } from "./store.js";
36
+ export { bindCronToEngine, bindCronToRunManager, } from "./runner.js";
37
+ export { isCronExpression, parseCronExpression, nextCronTime, } from "./cron-expr.js";
38
+ export { resolveWritePolicy, wrapUntrustedInput, } from "./write-policy.js";
39
+ export { runWriteJobInWorktree, } from "./write-run.js";
@@ -0,0 +1,67 @@
1
+ /**
2
+ * cron-runtime — wire the CronScheduler's executor to a real run backend.
3
+ *
4
+ * B1 fix: `CronScheduler.setExecutor` was never called in production, so cron
5
+ * jobs were created and ticked but did nothing on fire. `bindCronToEngine`
6
+ * installs an executor that, for each fired job, hands a one-shot headless run
7
+ * request to the caller-supplied `runner`.
8
+ *
9
+ * Security contract: cron runs unattended, so each job's permission tier
10
+ * (`job.permissionLevel`) is resolved through `resolveWritePolicy`, which maps
11
+ * the tier to a permissionMode + approval backend (see write-policy.ts). The
12
+ * mode stays "default" so the classifier doesn't add its own acceptEdits
13
+ * auto-allow rules ahead of the backend — the backend is the single source of
14
+ * truth for what a tier permits. An undefined/unknown tier falls back to the
15
+ * read-only backend.
16
+ */
17
+ import type { CronJob } from "./scheduler.js";
18
+ import type { CronScheduler } from "./scheduler.js";
19
+ import type { PermissionMode } from "../types.js";
20
+ import type { ApprovalBackend } from "../tool-system/permission.js";
21
+ /** What the executor hands to the run backend for one fired job. */
22
+ export interface CronRunRequest {
23
+ job: CronJob;
24
+ /** The prompt to run — `job.prompt`, surfaced for convenience. */
25
+ prompt: string;
26
+ /** Permission mode the run must use (see security contract above). */
27
+ permissionMode: PermissionMode;
28
+ /** Approval backend the run must install (resolved from the job's tier). */
29
+ approvalBackend: ApprovalBackend;
30
+ /** Abort signal for the run — tripped by `CronScheduler.abort(jobId)` when
31
+ * the run must be cancelled mid-flight (e.g. the user deletes its session
32
+ * while it's still executing). Forward to `Engine.run({ signal })`. */
33
+ signal?: AbortSignal;
34
+ }
35
+ export interface CronRunResult {
36
+ text: string;
37
+ reason: string;
38
+ }
39
+ /** The pluggable run backend. Production wires this to a headless Engine run;
40
+ * tests pass a stub. Phase 5 will swap this for a RunManager.submit() path. */
41
+ export type CronRunner = (req: CronRunRequest) => Promise<CronRunResult>;
42
+ /**
43
+ * Install the cron executor. After this call, a fired job invokes `runner`
44
+ * with a run request whose permission tier comes from the job. Errors from
45
+ * `runner` are swallowed by the
46
+ * scheduler's existing try/catch (scheduler.ts) so one bad run never stops
47
+ * future ticks — we still log here so failures are visible.
48
+ */
49
+ export declare function bindCronToEngine(scheduler: CronScheduler, runner: CronRunner): void;
50
+ /** Minimal structural view of RunManager.submit the executor needs. */
51
+ export interface RunSubmitter {
52
+ submit(input: {
53
+ objective: string;
54
+ cwd?: string;
55
+ metadata?: Record<string, unknown>;
56
+ }): Promise<{
57
+ runId: string;
58
+ }>;
59
+ }
60
+ /**
61
+ * Wire the scheduler to submit each fired job into a RunManager. Records the
62
+ * resulting runId back onto the job (`lastRunId`) so the UI can link a job to
63
+ * its latest run in the RunStore. Read-only is enforced at the RunManager level
64
+ * by the host injecting `approvalBackend: HeadlessApprovalBackend("approve-read-only")`
65
+ * into createRunManager (Phase 2) until sandbox+write tiers land (Phase 4/5).
66
+ */
67
+ export declare function bindCronToRunManager(scheduler: CronScheduler, runManager: RunSubmitter): void;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * cron-runtime — wire the CronScheduler's executor to a real run backend.
3
+ *
4
+ * B1 fix: `CronScheduler.setExecutor` was never called in production, so cron
5
+ * jobs were created and ticked but did nothing on fire. `bindCronToEngine`
6
+ * installs an executor that, for each fired job, hands a one-shot headless run
7
+ * request to the caller-supplied `runner`.
8
+ *
9
+ * Security contract: cron runs unattended, so each job's permission tier
10
+ * (`job.permissionLevel`) is resolved through `resolveWritePolicy`, which maps
11
+ * the tier to a permissionMode + approval backend (see write-policy.ts). The
12
+ * mode stays "default" so the classifier doesn't add its own acceptEdits
13
+ * auto-allow rules ahead of the backend — the backend is the single source of
14
+ * truth for what a tier permits. An undefined/unknown tier falls back to the
15
+ * read-only backend.
16
+ */
17
+ import { AUTOMATION_RUN_SOURCE } from "../run/EngineRunner.js";
18
+ import { resolveWritePolicy } from "./write-policy.js";
19
+ /**
20
+ * Install the cron executor. After this call, a fired job invokes `runner`
21
+ * with a run request whose permission tier comes from the job. Errors from
22
+ * `runner` are swallowed by the
23
+ * scheduler's existing try/catch (scheduler.ts) so one bad run never stops
24
+ * future ticks — we still log here so failures are visible.
25
+ */
26
+ export function bindCronToEngine(scheduler, runner) {
27
+ scheduler.setExecutor(async (job, signal) => {
28
+ const policy = resolveWritePolicy(job.permissionLevel);
29
+ const req = {
30
+ job,
31
+ prompt: job.prompt,
32
+ permissionMode: policy.permissionMode,
33
+ approvalBackend: policy.approvalBackend,
34
+ signal,
35
+ };
36
+ await runner(req);
37
+ });
38
+ }
39
+ /**
40
+ * Wire the scheduler to submit each fired job into a RunManager. Records the
41
+ * resulting runId back onto the job (`lastRunId`) so the UI can link a job to
42
+ * its latest run in the RunStore. Read-only is enforced at the RunManager level
43
+ * by the host injecting `approvalBackend: HeadlessApprovalBackend("approve-read-only")`
44
+ * into createRunManager (Phase 2) until sandbox+write tiers land (Phase 4/5).
45
+ */
46
+ export function bindCronToRunManager(scheduler, runManager) {
47
+ scheduler.setExecutor(async (job, _signal) => {
48
+ const snapshot = await runManager.submit({
49
+ objective: job.prompt,
50
+ cwd: job.cwd,
51
+ metadata: { source: AUTOMATION_RUN_SOURCE, cronJobId: job.id, cronJobName: job.name },
52
+ });
53
+ // Record the run id on the job so the UI can link to run history.
54
+ job.lastRunId = snapshot.runId;
55
+ });
56
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Cron scheduler — schedule and manage recurring agent tasks.
3
+ *
4
+ * Supports simple cron-like intervals. Each job runs the Engine
5
+ * with a predefined prompt on schedule.
6
+ */
7
+ import type { CronStore } from "./store.js";
8
+ /** Permission tier a scheduled job runs under (Phase 5 enforces write tiers). */
9
+ export type CronPermissionLevel = "read-only" | "workspace-write" | "full";
10
+ export interface CronJob {
11
+ id: string;
12
+ name: string;
13
+ schedule: string;
14
+ prompt: string;
15
+ enabled: boolean;
16
+ lastRun?: number;
17
+ nextRun?: number;
18
+ runCount: number;
19
+ createdAt: number;
20
+ /** Working directory the job runs in (the project it monitors/edits). */
21
+ cwd?: string;
22
+ /** IANA timezone for cron-expression schedules (e.g. "Asia/Shanghai"). Default "UTC". */
23
+ timezone?: string;
24
+ /** Permission tier; defaults to read-only when unset. */
25
+ permissionLevel?: CronPermissionLevel;
26
+ /** RunStore run id of the most recent execution (Phase 2 RunManager path). */
27
+ lastRunId?: string;
28
+ }
29
+ /** Optional metadata accepted by create(). */
30
+ export interface CreateJobOptions {
31
+ cwd?: string;
32
+ timezone?: string;
33
+ permissionLevel?: CronPermissionLevel;
34
+ }
35
+ /** Fields editable via update(). Any omitted field is left unchanged. */
36
+ export interface UpdateJobPatch {
37
+ name?: string;
38
+ prompt?: string;
39
+ schedule?: string;
40
+ timezone?: string;
41
+ cwd?: string;
42
+ permissionLevel?: CronPermissionLevel;
43
+ }
44
+ export declare class CronScheduler {
45
+ private jobs;
46
+ private timers;
47
+ /** Job ids with an execution currently in flight — prevents a tick from
48
+ * starting a second run of a job whose previous run hasn't finished. */
49
+ private running;
50
+ /** In-flight runs keyed by job id: the AbortController that cancels the run,
51
+ * plus `done` — a promise that resolves when the run has FULLY settled
52
+ * (executor returned, including any post-abort bookkeeping like Engine's
53
+ * final saveState). The re-entrancy guard (`running`) ensures at most one
54
+ * run per job, so one entry per job id is sufficient. `abort(jobId)` trips
55
+ * the controller and returns `done` so callers can wait for the run to
56
+ * actually stop before acting (e.g. deleting its session dir without racing
57
+ * a final write that would recreate it). */
58
+ private runningControllers;
59
+ private nextId;
60
+ private onExecute?;
61
+ /** Optional persistence backend. When set, every create/delete/pause/resume
62
+ * writes the full job set to disk so jobs survive a restart. */
63
+ private store?;
64
+ /** When false, the scheduler persists + tracks jobs but NEVER arms timers
65
+ * (so it can't execute). Used by the desktop agent worker, a separate
66
+ * process from the main process that owns execution. Default true. */
67
+ private executionEnabled;
68
+ constructor(store?: CronStore);
69
+ /**
70
+ * Disable (or re-enable) timer arming / execution. A host that only needs to
71
+ * persist + read jobs — never run them — calls setExecutionEnabled(false).
72
+ * Stops any timers already armed when turning off.
73
+ */
74
+ setExecutionEnabled(enabled: boolean): void;
75
+ /** Attach (or replace) the persistence backend. Used to give the shared
76
+ * singleton a store at runtime startup without changing its identity. */
77
+ setStore(store: CronStore): void;
78
+ setExecutor(fn: (job: CronJob, signal: AbortSignal) => Promise<void>): void;
79
+ /**
80
+ * Abort the in-flight run of `jobId`, if any. Aborts the AbortSignal handed
81
+ * to the executor (which Engine.run cooperates with — it resolves with
82
+ * reason "aborted_streaming" rather than throwing). Returns false when the
83
+ * job has no run currently in flight (already settled, never started, or
84
+ * unknown id). The run's own finally block clears the controller, so a
85
+ * settled run is a no-op here.
86
+ */
87
+ abort(jobId: string): Promise<boolean>;
88
+ /**
89
+ * Restore persisted jobs and rebuild their timers. Call once at startup
90
+ * after `setStore`. nextRun is recomputed forward from now — we do NOT
91
+ * catch up on runs missed while the process was down (avoids a restart
92
+ * thundering-herd; aligns with Codex's stateless philosophy). Disabled
93
+ * jobs are restored without a timer.
94
+ */
95
+ /**
96
+ * Reconcile in-memory jobs against the on-disk store (the store is the source
97
+ * of truth). Safe to call repeatedly — used both at startup and as a periodic
98
+ * re-sync when another process (e.g. the desktop agent worker) may have
99
+ * created/deleted/changed jobs through the same store. Reconciliation:
100
+ * - jobs on disk but not in memory → added (and armed if enabled)
101
+ * - jobs in memory but not on disk → removed + timer cleared
102
+ * - jobs whose schedule/enabled differ → re-armed to match disk
103
+ *
104
+ * Pass `{ arm: false }` in a host that must NOT execute jobs (the worker —
105
+ * separate process from the one that owns execution); it still tracks +
106
+ * persists but starts no timers, so jobs never run twice across processes.
107
+ */
108
+ loadJobs(opts?: {
109
+ arm?: boolean;
110
+ }): void;
111
+ private reconcileJobs;
112
+ private nextPersistedId;
113
+ /** Recompute nextRun without arming a timer (for display in disabled/no-arm hosts). */
114
+ private refreshNextRunForDisplay;
115
+ private persist;
116
+ private persistRunStats;
117
+ create(name: string, schedule: string, prompt: string, opts?: CreateJobOptions): CronJob;
118
+ delete(id: string): boolean;
119
+ list(): CronJob[];
120
+ get(id: string): CronJob | undefined;
121
+ pause(id: string): boolean;
122
+ resume(id: string): boolean;
123
+ /**
124
+ * Edit an existing job's fields (name/prompt/schedule/timezone/cwd/
125
+ * permissionLevel) without recreating it. A changed schedule is validated up
126
+ * front (throws on invalid, leaving the job untouched), then the timer is
127
+ * re-armed so the new schedule takes effect immediately. Enabled state is
128
+ * preserved — a paused job stays paused (no timer). Returns the updated job,
129
+ * or null if the id is unknown.
130
+ */
131
+ update(id: string, patch: UpdateJobPatch): CronJob | null;
132
+ stopAll(): void;
133
+ /**
134
+ * Fire a job immediately, out of band of its schedule (the "Run now" button).
135
+ * Respects the re-entrancy guard (no-op if a run is already in flight) and
136
+ * does not disturb the existing timer. Returns false if the id is unknown.
137
+ */
138
+ runNow(id: string): boolean;
139
+ /**
140
+ * Arm a job's timer. Interval schedules ("5m") use setInterval; cron-
141
+ * expression schedules ("0 9 * * 1-5") compute the next trigger in the
142
+ * job's timezone and use setTimeout, re-arming after each fire. nextRun is
143
+ * updated to reflect the scheduled instant.
144
+ */
145
+ private arm;
146
+ private refreshIntervalNextRun;
147
+ private armCron;
148
+ private clearTimer;
149
+ /**
150
+ * Shared fire path for both schedule kinds. Re-entrancy guard, run-stat
151
+ * bookkeeping, persistence, then the executor. `afterStats` updates nextRun
152
+ * for the next occurrence (interval: now+interval; cron: re-armed inside).
153
+ */
154
+ private fire;
155
+ }
156
+ export declare const cronScheduler: CronScheduler;