@mastra/code-sdk 1.8.4-alpha.5 → 1.9.0-alpha.10

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 (126) hide show
  1. package/README.md +190 -0
  2. package/dist/acp/agent.d.ts +30 -17
  3. package/dist/acp/agent.d.ts.map +1 -1
  4. package/dist/acp/agent.js +294 -103
  5. package/dist/acp/agent.js.map +1 -1
  6. package/dist/acp/errors.d.ts +3 -0
  7. package/dist/acp/errors.d.ts.map +1 -0
  8. package/dist/acp/errors.js +14 -0
  9. package/dist/acp/errors.js.map +1 -0
  10. package/dist/acp/event-mapper.d.ts +6 -1
  11. package/dist/acp/event-mapper.d.ts.map +1 -1
  12. package/dist/acp/event-mapper.js +116 -56
  13. package/dist/acp/event-mapper.js.map +1 -1
  14. package/dist/acp/index.d.ts +1 -4
  15. package/dist/acp/index.d.ts.map +1 -1
  16. package/dist/acp/index.js +6 -48
  17. package/dist/acp/index.js.map +1 -1
  18. package/dist/acp/runtime.d.ts +5 -0
  19. package/dist/acp/runtime.d.ts.map +1 -0
  20. package/dist/acp/runtime.js +80 -0
  21. package/dist/acp/runtime.js.map +1 -0
  22. package/dist/acp/server.d.ts +3 -6
  23. package/dist/acp/server.d.ts.map +1 -1
  24. package/dist/acp/server.js +22 -19
  25. package/dist/acp/server.js.map +1 -1
  26. package/dist/acp/skills.d.ts +6 -0
  27. package/dist/acp/skills.d.ts.map +1 -0
  28. package/dist/acp/skills.js +33 -0
  29. package/dist/acp/skills.js.map +1 -0
  30. package/dist/agents/model.js +1 -1
  31. package/dist/agents/prompts/agent-instructions.js +2 -2
  32. package/dist/agents/tools.d.ts +1 -1
  33. package/dist/agents/tools.d.ts.map +1 -1
  34. package/dist/agents/tools.js +2 -1
  35. package/dist/agents/tools.js.map +1 -1
  36. package/dist/agents/workspace.js +1 -1
  37. package/dist/analytics.js +2 -2
  38. package/dist/auth/providers/kimi-coding.js +1 -1
  39. package/dist/auth/storage.js +2 -2
  40. package/dist/clipboard/index.js +2 -2
  41. package/dist/headless/run-mc.d.ts.map +1 -1
  42. package/dist/headless/run-mc.js +14 -0
  43. package/dist/headless/run-mc.js.map +1 -1
  44. package/dist/hooks/config.js +1 -1
  45. package/dist/index.d.ts +14 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +75 -8
  48. package/dist/index.js.map +1 -1
  49. package/dist/lsp/server.js +1 -1
  50. package/dist/lsp/workspace.js +1 -1
  51. package/dist/mcp/config.js +1 -1
  52. package/dist/mcp/manager.d.ts.map +1 -1
  53. package/dist/mcp/manager.js +2 -1
  54. package/dist/mcp/manager.js.map +1 -1
  55. package/dist/mcp/state.js +1 -1
  56. package/dist/mcp/types.d.ts +2 -0
  57. package/dist/mcp/types.d.ts.map +1 -1
  58. package/dist/onboarding/settings.d.ts +2 -0
  59. package/dist/onboarding/settings.d.ts.map +1 -1
  60. package/dist/onboarding/settings.js +6 -4
  61. package/dist/onboarding/settings.js.map +1 -1
  62. package/dist/permissions.d.ts.map +1 -1
  63. package/dist/permissions.js +6 -1
  64. package/dist/permissions.js.map +1 -1
  65. package/dist/plugins/dependencies.js +1 -1
  66. package/dist/plugins/install.js +1 -1
  67. package/dist/plugins/loader.js +3 -3
  68. package/dist/plugins/manager.js +1 -1
  69. package/dist/plugins/manifest.js +1 -1
  70. package/dist/plugins/package-link.js +1 -1
  71. package/dist/plugins/paths.js +1 -1
  72. package/dist/plugins/registry.js +1 -1
  73. package/dist/plugins/scaffold.js +1 -1
  74. package/dist/process-memory-diagnostics.js +3 -3
  75. package/dist/providers/amazon-bedrock-gateway.js +1 -1
  76. package/dist/providers/openai-codex.js +1 -1
  77. package/dist/schedules/index.d.ts +10 -0
  78. package/dist/schedules/index.d.ts.map +1 -0
  79. package/dist/schedules/index.js +6 -0
  80. package/dist/schedules/interval.d.ts +49 -0
  81. package/dist/schedules/interval.d.ts.map +1 -0
  82. package/dist/schedules/interval.js +215 -0
  83. package/dist/schedules/interval.js.map +1 -0
  84. package/dist/schedules/prompt.d.ts +36 -0
  85. package/dist/schedules/prompt.d.ts.map +1 -0
  86. package/dist/schedules/prompt.js +39 -0
  87. package/dist/schedules/prompt.js.map +1 -0
  88. package/dist/schedules/run-script.d.ts +8 -0
  89. package/dist/schedules/run-script.d.ts.map +1 -0
  90. package/dist/schedules/run-script.js +83 -0
  91. package/dist/schedules/run-script.js.map +1 -0
  92. package/dist/schedules/scheduler.d.ts +101 -0
  93. package/dist/schedules/scheduler.d.ts.map +1 -0
  94. package/dist/schedules/scheduler.js +169 -0
  95. package/dist/schedules/scheduler.js.map +1 -0
  96. package/dist/schedules/tools.d.ts +57 -0
  97. package/dist/schedules/tools.d.ts.map +1 -0
  98. package/dist/schedules/tools.js +220 -0
  99. package/dist/schedules/tools.js.map +1 -0
  100. package/dist/schema.js +1 -1
  101. package/dist/tools/request-sandbox-access.js +5 -4
  102. package/dist/tools/request-sandbox-access.js.map +1 -1
  103. package/dist/utils/debug-log.js +1 -1
  104. package/dist/utils/discarding-scores-storage.d.ts +33 -0
  105. package/dist/utils/discarding-scores-storage.d.ts.map +1 -0
  106. package/dist/utils/discarding-scores-storage.js +54 -0
  107. package/dist/utils/discarding-scores-storage.js.map +1 -0
  108. package/dist/utils/maintenance-lock.d.ts +42 -0
  109. package/dist/utils/maintenance-lock.d.ts.map +1 -0
  110. package/dist/utils/maintenance-lock.js +199 -0
  111. package/dist/utils/maintenance-lock.js.map +1 -0
  112. package/dist/utils/project.js +3 -3
  113. package/dist/utils/prune-cli.d.ts +24 -0
  114. package/dist/utils/prune-cli.d.ts.map +1 -0
  115. package/dist/utils/prune-cli.js +125 -0
  116. package/dist/utils/prune-cli.js.map +1 -0
  117. package/dist/utils/signals-pubsub.js +1 -1
  118. package/dist/utils/slash-command-loader.js +1 -1
  119. package/dist/utils/slash-command-processor.js +1 -1
  120. package/dist/utils/storage-maintenance.d.ts.map +1 -1
  121. package/dist/utils/storage-maintenance.js +34 -4
  122. package/dist/utils/storage-maintenance.js.map +1 -1
  123. package/dist/utils/thread-lock.js +1 -1
  124. package/dist/utils/update-check.js +2 -2
  125. package/dist/workflows/register-primitives.js +1 -1
  126. package/package.json +23 -13
@@ -1,8 +1,8 @@
1
1
  import "../constants.js";
2
2
  import { upsertPluginManifestEntry } from "./manifest.js";
3
3
  import { ensureMastraCodePackageLink } from "./package-link.js";
4
- import fs from "fs";
5
4
  import path from "path";
5
+ import fs from "fs";
6
6
  //#region src/plugins/scaffold.ts
7
7
  function resolveScaffoldTarget(target, options = {}) {
8
8
  if (isBarePluginName(target)) return path.join(options.projectRoot ?? process.cwd(), options.configDir ?? ".mastracode", "plugins", "sources", "local", target);
@@ -1,11 +1,11 @@
1
- import { randomUUID } from "crypto";
2
- import { homedir } from "os";
3
1
  import { join } from "path";
2
+ import { homedir } from "os";
3
+ import { randomUUID } from "crypto";
4
4
  import { appendFile, chmod, mkdir, rename, rm, writeFile } from "fs/promises";
5
+ import { getHeapSpaceStatistics, getHeapStatistics } from "v8";
5
6
  import { Session } from "inspector/promises";
6
7
  import { PerformanceObserver } from "perf_hooks";
7
8
  import { arch as arch$1, platform as platform$1 } from "process";
8
- import { getHeapSpaceStatistics, getHeapStatistics } from "v8";
9
9
  //#region src/process-memory-diagnostics.ts
10
10
  const PROCESS_MEMORY_DIAGNOSTICS_DEFAULTS = {
11
11
  sampleIntervalMs: 1e4,
@@ -1,6 +1,6 @@
1
1
  import { getBedrockModelCatalog } from "./amazon-bedrock.js";
2
- import { existsSync } from "fs";
3
2
  import path from "path";
3
+ import { existsSync } from "fs";
4
4
  import { MastraModelGateway } from "@mastra/core/llm";
5
5
  import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
6
6
  import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
@@ -1,6 +1,6 @@
1
+ import { supportsMaxReasoningEffort } from "../thinking.js";
1
2
  import { AuthStorage } from "../auth/storage.js";
2
3
  import { ProviderAuthRequiredError } from "../auth/provider-auth-error.js";
3
- import { supportsMaxReasoningEffort } from "../thinking.js";
4
4
  import { createOpenAI } from "@ai-sdk/openai";
5
5
  import { wrapLanguageModel } from "ai";
6
6
  //#region src/providers/openai-codex.ts
@@ -0,0 +1,10 @@
1
+ export { parseInterval, validateInterval, nextFireTime } from './interval.js';
2
+ export type { ParsedInterval, IntervalError, IntervalCheck } from './interval.js';
3
+ export { assembleSchedulePrompt, formatScriptOutput, SCRIPT_TIMEOUT_MS } from './prompt.js';
4
+ export type { RunScript, ScriptResult, AssemblePromptOptions, AssembledPrompt } from './prompt.js';
5
+ export { ThreadScheduler, shortScheduleId, describeScheduleSource, scheduleSignalAttributes, SCHEDULE_SIGNAL_SOURCE, LATE_FIRE_GRACE_MS, } from './scheduler.js';
6
+ export type { ThreadSchedule, ThreadScheduleSpec, ThreadScheduleSource, ScheduleTrigger, ScheduleFile, ThreadSchedulerOptions, ScheduleRunResult, } from './scheduler.js';
7
+ export { runScript, resolveScriptCommand, isExecutableFile } from './run-script.js';
8
+ export { createScheduleTools, SCHEDULE_TOOL_IDS } from './tools.js';
9
+ export type { ScheduleToolsOptions, ScheduleFileOptions } from './tools.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schedules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC9E,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5F,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EACL,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { nextFireTime, parseInterval, validateInterval } from "./interval.js";
2
+ import { SCRIPT_TIMEOUT_MS, assembleSchedulePrompt, formatScriptOutput } from "./prompt.js";
3
+ import { LATE_FIRE_GRACE_MS, SCHEDULE_SIGNAL_SOURCE, ThreadScheduler, describeScheduleSource, scheduleSignalAttributes, shortScheduleId } from "./scheduler.js";
4
+ import { isExecutableFile, resolveScriptCommand, runScript } from "./run-script.js";
5
+ import { SCHEDULE_TOOL_IDS, createScheduleTools } from "./tools.js";
6
+ export { LATE_FIRE_GRACE_MS, SCHEDULE_SIGNAL_SOURCE, SCHEDULE_TOOL_IDS, SCRIPT_TIMEOUT_MS, ThreadScheduler, assembleSchedulePrompt, createScheduleTools, describeScheduleSource, formatScriptOutput, isExecutableFile, nextFireTime, parseInterval, resolveScriptCommand, runScript, scheduleSignalAttributes, shortScheduleId, validateInterval };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Interval parsing and fire-time math for `/schedules`.
3
+ *
4
+ * Users type `5m`, `2h`, `1 day`. Schedules fire on local wall-clock
5
+ * boundaries (5m fires at :00, :05, ...), so only intervals that divide the
6
+ * hour (minutes) or the day (hours) give a stable cadence; anything else is
7
+ * rejected with the nearest valid suggestion instead of silently drifting.
8
+ */
9
+ export type ParsedInterval = {
10
+ ms: number;
11
+ label: string;
12
+ };
13
+ export type IntervalError = {
14
+ error: string;
15
+ };
16
+ /** Parse `5m`, `2h`, `1 day`, `90 minutes` into milliseconds plus a normalized label. */
17
+ export declare function parseInterval(text: string): ParsedInterval | IntervalError;
18
+ export type IntervalCheck = {
19
+ ok: true;
20
+ } | {
21
+ error: string;
22
+ suggestion?: string;
23
+ };
24
+ /**
25
+ * Check that an interval fires on stable clock boundaries: whole minutes that
26
+ * divide the hour, whole hours that divide the day, or exactly one day.
27
+ */
28
+ export declare function validateInterval({ ms }: {
29
+ ms: number;
30
+ }): IntervalCheck;
31
+ /**
32
+ * The first boundary strictly after `after`, on the local wall clock
33
+ * (5m → :00, :05, ...; 2h → 00:00, 02:00, ...; 1d → midnight). `intervalMs`
34
+ * must pass {@link validateInterval}.
35
+ *
36
+ * Around daylight-saving changes this behaves like cron:
37
+ * - Sub-hour cadences follow the minute hand, so they keep firing every N real
38
+ * minutes through both the skipped and the repeated hour. Across a
39
+ * half-hour change, the gap between fires never exceeds the interval.
40
+ * - Hourly and daily cadences fire once per wall-clock slot. A boundary that
41
+ * falls in a skipped hour fires at the first minute after the jump (02:00 →
42
+ * 03:00), and a repeated hour does not fire a second time.
43
+ *
44
+ * Computing from `after` rather than from the previous fire means a late
45
+ * timer (sleep, busy event loop) skips missed boundaries instead of bursting
46
+ * to catch up.
47
+ */
48
+ export declare function nextFireTime(intervalMs: number, after: number): number;
49
+ //# sourceMappingURL=interval.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interval.d.ts","sourceRoot":"","sources":["../../src/schedules/interval.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAC3D,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAyB9C,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,aAAa,CAc1E;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAWlF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CA6CtE;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBtE"}
@@ -0,0 +1,215 @@
1
+ //#region src/schedules/interval.ts
2
+ /**
3
+ * Interval parsing and fire-time math for `/schedules`.
4
+ *
5
+ * Users type `5m`, `2h`, `1 day`. Schedules fire on local wall-clock
6
+ * boundaries (5m fires at :00, :05, ...), so only intervals that divide the
7
+ * hour (minutes) or the day (hours) give a stable cadence; anything else is
8
+ * rejected with the nearest valid suggestion instead of silently drifting.
9
+ */
10
+ const SECOND_MS = 1e3;
11
+ const MINUTE_MS = 60 * SECOND_MS;
12
+ const HOUR_MS = 60 * MINUTE_MS;
13
+ const DAY_MS = 24 * HOUR_MS;
14
+ const UNIT_MS = {
15
+ s: {
16
+ ms: SECOND_MS,
17
+ short: "s"
18
+ },
19
+ sec: {
20
+ ms: SECOND_MS,
21
+ short: "s"
22
+ },
23
+ secs: {
24
+ ms: SECOND_MS,
25
+ short: "s"
26
+ },
27
+ second: {
28
+ ms: SECOND_MS,
29
+ short: "s"
30
+ },
31
+ seconds: {
32
+ ms: SECOND_MS,
33
+ short: "s"
34
+ },
35
+ m: {
36
+ ms: MINUTE_MS,
37
+ short: "m"
38
+ },
39
+ min: {
40
+ ms: MINUTE_MS,
41
+ short: "m"
42
+ },
43
+ mins: {
44
+ ms: MINUTE_MS,
45
+ short: "m"
46
+ },
47
+ minute: {
48
+ ms: MINUTE_MS,
49
+ short: "m"
50
+ },
51
+ minutes: {
52
+ ms: MINUTE_MS,
53
+ short: "m"
54
+ },
55
+ h: {
56
+ ms: HOUR_MS,
57
+ short: "h"
58
+ },
59
+ hr: {
60
+ ms: HOUR_MS,
61
+ short: "h"
62
+ },
63
+ hrs: {
64
+ ms: HOUR_MS,
65
+ short: "h"
66
+ },
67
+ hour: {
68
+ ms: HOUR_MS,
69
+ short: "h"
70
+ },
71
+ hours: {
72
+ ms: HOUR_MS,
73
+ short: "h"
74
+ },
75
+ d: {
76
+ ms: DAY_MS,
77
+ short: "d"
78
+ },
79
+ day: {
80
+ ms: DAY_MS,
81
+ short: "d"
82
+ },
83
+ days: {
84
+ ms: DAY_MS,
85
+ short: "d"
86
+ }
87
+ };
88
+ const INTERVAL_RE = /^(\d+)\s*([a-z]+)$/i;
89
+ /** Parse `5m`, `2h`, `1 day`, `90 minutes` into milliseconds plus a normalized label. */
90
+ function parseInterval(text) {
91
+ const match = INTERVAL_RE.exec(text.trim());
92
+ if (!match) return { error: `Invalid interval "${text}". Use a number followed by a unit, e.g. 5m, 2h, 1d.` };
93
+ const value = Number(match[1]);
94
+ const unit = UNIT_MS[match[2].toLowerCase()];
95
+ if (!unit) return { error: `Unknown interval unit "${match[2]}". Use s, m, h, or d.` };
96
+ if (value <= 0) return { error: "Interval must be greater than zero." };
97
+ return {
98
+ ms: value * unit.ms,
99
+ label: `${value}${unit.short}`
100
+ };
101
+ }
102
+ const MINUTE_DIVISORS = [
103
+ 1,
104
+ 2,
105
+ 3,
106
+ 4,
107
+ 5,
108
+ 6,
109
+ 10,
110
+ 12,
111
+ 15,
112
+ 20,
113
+ 30,
114
+ 60
115
+ ];
116
+ const HOUR_DIVISORS = [
117
+ 1,
118
+ 2,
119
+ 3,
120
+ 4,
121
+ 6,
122
+ 8,
123
+ 12,
124
+ 24
125
+ ];
126
+ function nearest(candidates, target) {
127
+ return candidates.reduce((best, candidate) => Math.abs(candidate - target) < Math.abs(best - target) ? candidate : best);
128
+ }
129
+ /**
130
+ * Check that an interval fires on stable clock boundaries: whole minutes that
131
+ * divide the hour, whole hours that divide the day, or exactly one day.
132
+ */
133
+ function validateInterval({ ms }) {
134
+ if (ms < MINUTE_MS) return {
135
+ error: "Schedules fire at most once per minute.",
136
+ suggestion: "1m"
137
+ };
138
+ if (ms % MINUTE_MS !== 0) {
139
+ const minutes = Math.round(ms / MINUTE_MS);
140
+ return {
141
+ error: "Interval must be a whole number of minutes.",
142
+ suggestion: `${Math.max(1, nearest(MINUTE_DIVISORS, minutes))}m`
143
+ };
144
+ }
145
+ const minutes = ms / MINUTE_MS;
146
+ if (minutes < 60) {
147
+ if (60 % minutes === 0) return { ok: true };
148
+ return {
149
+ error: `${minutes}m does not divide an hour evenly.`,
150
+ suggestion: `${nearest(MINUTE_DIVISORS, minutes)}m`
151
+ };
152
+ }
153
+ if (minutes % 60 !== 0) {
154
+ const hours = Math.round(minutes / 60);
155
+ return {
156
+ error: `${minutes}m is not a whole number of hours.`,
157
+ suggestion: hours < 24 ? `${nearest(HOUR_DIVISORS, hours)}h` : "1d"
158
+ };
159
+ }
160
+ const hours = minutes / 60;
161
+ if (hours < 24) {
162
+ if (24 % hours === 0) return { ok: true };
163
+ return {
164
+ error: `${hours}h does not divide a day evenly.`,
165
+ suggestion: `${nearest(HOUR_DIVISORS, hours)}h`
166
+ };
167
+ }
168
+ if (hours === 24) return { ok: true };
169
+ return {
170
+ error: "Intervals longer than one day are not supported.",
171
+ suggestion: "1d"
172
+ };
173
+ }
174
+ /** Hour-and-longer cadences: which local wall-clock slot a minute belongs to. */
175
+ function hourSlotKey(date, stepHours) {
176
+ return ((date.getFullYear() * 12 + date.getMonth()) * 31 + date.getDate()) * 24 + Math.floor(date.getHours() / stepHours) * stepHours;
177
+ }
178
+ /**
179
+ * The first boundary strictly after `after`, on the local wall clock
180
+ * (5m → :00, :05, ...; 2h → 00:00, 02:00, ...; 1d → midnight). `intervalMs`
181
+ * must pass {@link validateInterval}.
182
+ *
183
+ * Around daylight-saving changes this behaves like cron:
184
+ * - Sub-hour cadences follow the minute hand, so they keep firing every N real
185
+ * minutes through both the skipped and the repeated hour. Across a
186
+ * half-hour change, the gap between fires never exceeds the interval.
187
+ * - Hourly and daily cadences fire once per wall-clock slot. A boundary that
188
+ * falls in a skipped hour fires at the first minute after the jump (02:00 →
189
+ * 03:00), and a repeated hour does not fire a second time.
190
+ *
191
+ * Computing from `after` rather than from the previous fire means a late
192
+ * timer (sleep, busy event loop) skips missed boundaries instead of bursting
193
+ * to catch up.
194
+ */
195
+ function nextFireTime(intervalMs, after) {
196
+ const cursor = /* @__PURE__ */ new Date(Math.floor(after / MINUTE_MS) * MINUTE_MS);
197
+ const stepMinutes = intervalMs / MINUTE_MS;
198
+ if (stepMinutes < 60) {
199
+ const latest = cursor.getTime() + intervalMs;
200
+ do
201
+ cursor.setTime(cursor.getTime() + MINUTE_MS);
202
+ while (cursor.getMinutes() % stepMinutes !== 0 && cursor.getTime() < latest);
203
+ return cursor.getTime();
204
+ }
205
+ const stepHours = stepMinutes / 60;
206
+ const startSlot = hourSlotKey(new Date(after), stepHours);
207
+ do
208
+ cursor.setTime(cursor.getTime() + MINUTE_MS);
209
+ while (hourSlotKey(cursor, stepHours) <= startSlot);
210
+ return cursor.getTime();
211
+ }
212
+ //#endregion
213
+ export { nextFireTime, parseInterval, validateInterval };
214
+
215
+ //# sourceMappingURL=interval.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interval.js","names":[],"sources":["../../src/schedules/interval.ts"],"sourcesContent":["/**\n * Interval parsing and fire-time math for `/schedules`.\n *\n * Users type `5m`, `2h`, `1 day`. Schedules fire on local wall-clock\n * boundaries (5m fires at :00, :05, ...), so only intervals that divide the\n * hour (minutes) or the day (hours) give a stable cadence; anything else is\n * rejected with the nearest valid suggestion instead of silently drifting.\n */\n\nconst SECOND_MS = 1_000;\nconst MINUTE_MS = 60 * SECOND_MS;\nconst HOUR_MS = 60 * MINUTE_MS;\nconst DAY_MS = 24 * HOUR_MS;\n\nexport type ParsedInterval = { ms: number; label: string };\nexport type IntervalError = { error: string };\n\nconst UNIT_MS: Record<string, { ms: number; short: string }> = {\n s: { ms: SECOND_MS, short: 's' },\n sec: { ms: SECOND_MS, short: 's' },\n secs: { ms: SECOND_MS, short: 's' },\n second: { ms: SECOND_MS, short: 's' },\n seconds: { ms: SECOND_MS, short: 's' },\n m: { ms: MINUTE_MS, short: 'm' },\n min: { ms: MINUTE_MS, short: 'm' },\n mins: { ms: MINUTE_MS, short: 'm' },\n minute: { ms: MINUTE_MS, short: 'm' },\n minutes: { ms: MINUTE_MS, short: 'm' },\n h: { ms: HOUR_MS, short: 'h' },\n hr: { ms: HOUR_MS, short: 'h' },\n hrs: { ms: HOUR_MS, short: 'h' },\n hour: { ms: HOUR_MS, short: 'h' },\n hours: { ms: HOUR_MS, short: 'h' },\n d: { ms: DAY_MS, short: 'd' },\n day: { ms: DAY_MS, short: 'd' },\n days: { ms: DAY_MS, short: 'd' },\n};\n\nconst INTERVAL_RE = /^(\\d+)\\s*([a-z]+)$/i;\n\n/** Parse `5m`, `2h`, `1 day`, `90 minutes` into milliseconds plus a normalized label. */\nexport function parseInterval(text: string): ParsedInterval | IntervalError {\n const match = INTERVAL_RE.exec(text.trim());\n if (!match) {\n return { error: `Invalid interval \"${text}\". Use a number followed by a unit, e.g. 5m, 2h, 1d.` };\n }\n const value = Number(match[1]);\n const unit = UNIT_MS[match[2]!.toLowerCase()];\n if (!unit) {\n return { error: `Unknown interval unit \"${match[2]}\". Use s, m, h, or d.` };\n }\n if (value <= 0) {\n return { error: 'Interval must be greater than zero.' };\n }\n return { ms: value * unit.ms, label: `${value}${unit.short}` };\n}\n\nexport type IntervalCheck = { ok: true } | { error: string; suggestion?: string };\n\nconst MINUTE_DIVISORS = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60];\nconst HOUR_DIVISORS = [1, 2, 3, 4, 6, 8, 12, 24];\n\nfunction nearest(candidates: number[], target: number): number {\n return candidates.reduce((best, candidate) =>\n Math.abs(candidate - target) < Math.abs(best - target) ? candidate : best,\n );\n}\n\n/**\n * Check that an interval fires on stable clock boundaries: whole minutes that\n * divide the hour, whole hours that divide the day, or exactly one day.\n */\nexport function validateInterval({ ms }: { ms: number }): IntervalCheck {\n if (ms < MINUTE_MS) {\n return { error: 'Schedules fire at most once per minute.', suggestion: '1m' };\n }\n if (ms % MINUTE_MS !== 0) {\n const minutes = Math.round(ms / MINUTE_MS);\n return {\n error: 'Interval must be a whole number of minutes.',\n suggestion: `${Math.max(1, nearest(MINUTE_DIVISORS, minutes))}m`,\n };\n }\n const minutes = ms / MINUTE_MS;\n if (minutes < 60) {\n if (60 % minutes === 0) {\n return { ok: true };\n }\n return {\n error: `${minutes}m does not divide an hour evenly.`,\n suggestion: `${nearest(MINUTE_DIVISORS, minutes)}m`,\n };\n }\n if (minutes % 60 !== 0) {\n const hours = Math.round(minutes / 60);\n return {\n error: `${minutes}m is not a whole number of hours.`,\n suggestion: hours < 24 ? `${nearest(HOUR_DIVISORS, hours)}h` : '1d',\n };\n }\n const hours = minutes / 60;\n if (hours < 24) {\n if (24 % hours === 0) {\n return { ok: true };\n }\n return {\n error: `${hours}h does not divide a day evenly.`,\n suggestion: `${nearest(HOUR_DIVISORS, hours)}h`,\n };\n }\n if (hours === 24) {\n return { ok: true };\n }\n return {\n error: 'Intervals longer than one day are not supported.',\n suggestion: '1d',\n };\n}\n\n/** Hour-and-longer cadences: which local wall-clock slot a minute belongs to. */\nfunction hourSlotKey(date: Date, stepHours: number): number {\n return (\n ((date.getFullYear() * 12 + date.getMonth()) * 31 + date.getDate()) * 24 +\n Math.floor(date.getHours() / stepHours) * stepHours\n );\n}\n\n/**\n * The first boundary strictly after `after`, on the local wall clock\n * (5m → :00, :05, ...; 2h → 00:00, 02:00, ...; 1d → midnight). `intervalMs`\n * must pass {@link validateInterval}.\n *\n * Around daylight-saving changes this behaves like cron:\n * - Sub-hour cadences follow the minute hand, so they keep firing every N real\n * minutes through both the skipped and the repeated hour. Across a\n * half-hour change, the gap between fires never exceeds the interval.\n * - Hourly and daily cadences fire once per wall-clock slot. A boundary that\n * falls in a skipped hour fires at the first minute after the jump (02:00 →\n * 03:00), and a repeated hour does not fire a second time.\n *\n * Computing from `after` rather than from the previous fire means a late\n * timer (sleep, busy event loop) skips missed boundaries instead of bursting\n * to catch up.\n */\nexport function nextFireTime(intervalMs: number, after: number): number {\n // Truncate in UTC: local setters resolve a repeated hour to its first occurrence.\n const cursor = new Date(Math.floor(after / MINUTE_MS) * MINUTE_MS);\n const stepMinutes = intervalMs / MINUTE_MS;\n // Walk minute by minute: at most an hour for sub-hour cadences, ~a day otherwise.\n if (stepMinutes < 60) {\n // Never wait longer than one interval: a half-hour clock change (Lord Howe\n // Island) can otherwise shift the minute hand past the next boundary.\n const latest = cursor.getTime() + intervalMs;\n do cursor.setTime(cursor.getTime() + MINUTE_MS);\n while (cursor.getMinutes() % stepMinutes !== 0 && cursor.getTime() < latest);\n return cursor.getTime();\n }\n const stepHours = stepMinutes / 60;\n const startSlot = hourSlotKey(new Date(after), stepHours);\n do cursor.setTime(cursor.getTime() + MINUTE_MS);\n while (hourSlotKey(cursor, stepHours) <= startSlot);\n return cursor.getTime();\n}\n"],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Fire-time prompt assembly for Mastra Code schedules.
3
+ *
4
+ * File-backed schedules only hold a descriptor; each fire runs the script or
5
+ * re-reads the file so the model always sees current output. Failures become
6
+ * the prompt text rather than a skipped fire — a schedule that silently stops
7
+ * delivering is worse than one that reports "the script broke".
8
+ */
9
+ import type { ThreadSchedule } from './scheduler.js';
10
+ export declare const SCRIPT_TIMEOUT_MS = 60000;
11
+ export type ScriptResult = {
12
+ stdout: string;
13
+ stderr: string;
14
+ exitCode: number | null;
15
+ };
16
+ export type RunScript = (absPath: string, options: {
17
+ cwd: string;
18
+ timeoutMs: number;
19
+ }) => Promise<ScriptResult>;
20
+ /**
21
+ * One fire's prompt. `outcome` summarizes a file-backed fire for the
22
+ * transcript header: `exit 0`, `exit 3`, `killed` (timeout/signal), or
23
+ * `failed` (the file could not be run or read).
24
+ */
25
+ export type AssembledPrompt = {
26
+ prompt: string;
27
+ outcome?: string;
28
+ };
29
+ export type AssemblePromptOptions = {
30
+ cwd: string;
31
+ runScript: RunScript;
32
+ readFile: (absPath: string) => Promise<string>;
33
+ };
34
+ export declare function formatScriptOutput(displayPath: string, result: ScriptResult): string;
35
+ export declare function assembleSchedulePrompt(spec: Pick<ThreadSchedule, 'prompt' | 'file' | 'extraPrompt'>, options: AssemblePromptOptions): Promise<AssembledPrompt>;
36
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/schedules/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,eAAO,MAAM,iBAAiB,QAAS,CAAC;AAExC,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEvF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAEhH;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAChD,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,CAMpF;AAMD,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,MAAM,GAAG,aAAa,CAAC,EAC7D,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAmB1B"}
@@ -0,0 +1,39 @@
1
+ //#region src/schedules/prompt.ts
2
+ const SCRIPT_TIMEOUT_MS = 6e4;
3
+ function appendExtra(text, extra) {
4
+ return extra ? `${text}\n\n${extra}` : text;
5
+ }
6
+ function formatScriptOutput(displayPath, result) {
7
+ const body = [result.stdout, result.stderr].map((part) => part.trimEnd()).filter(Boolean).join("\n");
8
+ return `Output of ${displayPath} (exit ${result.exitCode ?? "null"}):\n${body || "(no output)"}`;
9
+ }
10
+ function errorMessage(error) {
11
+ return error instanceof Error ? error.message : String(error);
12
+ }
13
+ async function assembleSchedulePrompt(spec, options) {
14
+ const { file } = spec;
15
+ if (!file) return { prompt: spec.prompt ?? "" };
16
+ let text;
17
+ let outcome;
18
+ try {
19
+ if (file.mode === "script") {
20
+ const result = await options.runScript(file.path, {
21
+ cwd: options.cwd,
22
+ timeoutMs: SCRIPT_TIMEOUT_MS
23
+ });
24
+ text = formatScriptOutput(file.displayPath, result);
25
+ outcome = result.exitCode === null ? "killed" : `exit ${result.exitCode}`;
26
+ } else text = (await options.readFile(file.path)).trim();
27
+ } catch (error) {
28
+ text = `Schedule ${file.displayPath} failed: ${errorMessage(error)}`;
29
+ outcome = "failed";
30
+ }
31
+ return {
32
+ prompt: appendExtra(text, spec.extraPrompt),
33
+ ...outcome ? { outcome } : {}
34
+ };
35
+ }
36
+ //#endregion
37
+ export { SCRIPT_TIMEOUT_MS, assembleSchedulePrompt, formatScriptOutput };
38
+
39
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","names":[],"sources":["../../src/schedules/prompt.ts"],"sourcesContent":["/**\n * Fire-time prompt assembly for Mastra Code schedules.\n *\n * File-backed schedules only hold a descriptor; each fire runs the script or\n * re-reads the file so the model always sees current output. Failures become\n * the prompt text rather than a skipped fire — a schedule that silently stops\n * delivering is worse than one that reports \"the script broke\".\n */\nimport type { ThreadSchedule } from './scheduler.js';\n\nexport const SCRIPT_TIMEOUT_MS = 60_000;\n\nexport type ScriptResult = { stdout: string; stderr: string; exitCode: number | null };\n\nexport type RunScript = (absPath: string, options: { cwd: string; timeoutMs: number }) => Promise<ScriptResult>;\n\n/**\n * One fire's prompt. `outcome` summarizes a file-backed fire for the\n * transcript header: `exit 0`, `exit 3`, `killed` (timeout/signal), or\n * `failed` (the file could not be run or read).\n */\nexport type AssembledPrompt = { prompt: string; outcome?: string };\n\nexport type AssemblePromptOptions = {\n cwd: string;\n runScript: RunScript;\n readFile: (absPath: string) => Promise<string>;\n};\n\nfunction appendExtra(text: string, extra: string | undefined): string {\n return extra ? `${text}\\n\\n${extra}` : text;\n}\n\nexport function formatScriptOutput(displayPath: string, result: ScriptResult): string {\n const body = [result.stdout, result.stderr]\n .map(part => part.trimEnd())\n .filter(Boolean)\n .join('\\n');\n return `Output of ${displayPath} (exit ${result.exitCode ?? 'null'}):\\n${body || '(no output)'}`;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport async function assembleSchedulePrompt(\n spec: Pick<ThreadSchedule, 'prompt' | 'file' | 'extraPrompt'>,\n options: AssemblePromptOptions,\n): Promise<AssembledPrompt> {\n const { file } = spec;\n if (!file) return { prompt: spec.prompt ?? '' };\n\n let text: string;\n let outcome: string | undefined;\n try {\n if (file.mode === 'script') {\n const result = await options.runScript(file.path, { cwd: options.cwd, timeoutMs: SCRIPT_TIMEOUT_MS });\n text = formatScriptOutput(file.displayPath, result);\n outcome = result.exitCode === null ? 'killed' : `exit ${result.exitCode}`;\n } else {\n text = (await options.readFile(file.path)).trim();\n }\n } catch (error) {\n text = `Schedule ${file.displayPath} failed: ${errorMessage(error)}`;\n outcome = 'failed';\n }\n return { prompt: appendExtra(text, spec.extraPrompt), ...(outcome ? { outcome } : {}) };\n}\n"],"mappings":";AAUA,MAAa,oBAAoB;AAmBjC,SAAS,YAAY,MAAc,OAAmC;CACpE,OAAO,QAAQ,GAAG,KAAK,MAAM,UAAU;AACzC;AAEA,SAAgB,mBAAmB,aAAqB,QAA8B;CACpF,MAAM,OAAO,CAAC,OAAO,QAAQ,OAAO,MAAM,CAAC,CACxC,KAAI,SAAQ,KAAK,QAAQ,CAAC,CAAC,CAC3B,OAAO,OAAO,CAAC,CACf,KAAK,IAAI;CACZ,OAAO,aAAa,YAAY,SAAS,OAAO,YAAY,OAAO,MAAM,QAAQ;AACnF;AAEA,SAAS,aAAa,OAAwB;CAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,eAAsB,uBACpB,MACA,SAC0B;CAC1B,MAAM,EAAE,SAAS;CACjB,IAAI,CAAC,MAAM,OAAO,EAAE,QAAQ,KAAK,UAAU,GAAG;CAE9C,IAAI;CACJ,IAAI;CACJ,IAAI;EACF,IAAI,KAAK,SAAS,UAAU;GAC1B,MAAM,SAAS,MAAM,QAAQ,UAAU,KAAK,MAAM;IAAE,KAAK,QAAQ;IAAK,WAAW;GAAkB,CAAC;GACpG,OAAO,mBAAmB,KAAK,aAAa,MAAM;GAClD,UAAU,OAAO,aAAa,OAAO,WAAW,QAAQ,OAAO;EACjE,OACE,QAAQ,MAAM,QAAQ,SAAS,KAAK,IAAI,EAAA,CAAG,KAAK;CAEpD,SAAS,OAAO;EACd,OAAO,YAAY,KAAK,YAAY,WAAW,aAAa,KAAK;EACjE,UAAU;CACZ;CACA,OAAO;EAAE,QAAQ,YAAY,MAAM,KAAK,WAAW;EAAG,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAAG;AACxF"}
@@ -0,0 +1,8 @@
1
+ import type { RunScript } from './prompt.js';
2
+ export declare function isExecutableFile(absPath: string): boolean;
3
+ export declare function resolveScriptCommand(absPath: string): {
4
+ command: string;
5
+ args: string[];
6
+ };
7
+ export declare const runScript: RunScript;
8
+ //# sourceMappingURL=run-script.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-script.d.ts","sourceRoot":"","sources":["../../src/schedules/run-script.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAgB,MAAM,aAAa,CAAC;AAc3D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAOzF;AAED,eAAO,MAAM,SAAS,EAAE,SAyBvB,CAAC"}
@@ -0,0 +1,83 @@
1
+ import * as path$1 from "path";
2
+ import { execFile } from "child_process";
3
+ import * as fs$1 from "fs";
4
+ //#region src/schedules/run-script.ts
5
+ /**
6
+ * Runs a schedule's script file at fire time.
7
+ *
8
+ * The file path is always passed as the literal command (or as the single
9
+ * argument to a fixed interpreter) — never through a shell string — so the
10
+ * user's extra prompt can never be interpolated into a command line.
11
+ */
12
+ /** Interpreter for scripts that lack an executable bit, keyed by extension. */
13
+ const INTERPRETERS = {
14
+ ".sh": () => "/bin/sh",
15
+ ".js": () => process.execPath,
16
+ ".mjs": () => process.execPath,
17
+ ".cjs": () => process.execPath,
18
+ ".ts": () => process.execPath,
19
+ ".py": () => "python3"
20
+ };
21
+ const MAX_OUTPUT_BYTES = 1024 * 1024;
22
+ function isExecutableFile(absPath) {
23
+ try {
24
+ fs$1.accessSync(absPath, fs$1.constants.X_OK);
25
+ return fs$1.statSync(absPath).isFile();
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ function resolveScriptCommand(absPath) {
31
+ if (isExecutableFile(absPath)) return {
32
+ command: absPath,
33
+ args: []
34
+ };
35
+ const interpreter = INTERPRETERS[path$1.extname(absPath).toLowerCase()];
36
+ if (!interpreter) throw new Error(`${absPath} is not executable and has no known interpreter`);
37
+ return {
38
+ command: interpreter(),
39
+ args: [absPath]
40
+ };
41
+ }
42
+ const runScript = async (absPath, { cwd, timeoutMs }) => {
43
+ const { command, args } = resolveScriptCommand(absPath);
44
+ return new Promise((resolve, reject) => {
45
+ execFile(command, args, {
46
+ cwd,
47
+ timeout: timeoutMs,
48
+ maxBuffer: MAX_OUTPUT_BYTES,
49
+ windowsHide: true
50
+ }, (error, stdout, stderr) => {
51
+ if (!error) {
52
+ resolve({
53
+ stdout,
54
+ stderr,
55
+ exitCode: 0
56
+ });
57
+ return;
58
+ }
59
+ const spawnError = error;
60
+ if (typeof spawnError.code === "number") {
61
+ resolve({
62
+ stdout,
63
+ stderr,
64
+ exitCode: spawnError.code
65
+ });
66
+ return;
67
+ }
68
+ if (spawnError.killed) {
69
+ resolve({
70
+ stdout,
71
+ stderr: `${stderr}\n[timed out after ${timeoutMs}ms]`.trim(),
72
+ exitCode: null
73
+ });
74
+ return;
75
+ }
76
+ reject(error);
77
+ });
78
+ });
79
+ };
80
+ //#endregion
81
+ export { isExecutableFile, resolveScriptCommand, runScript };
82
+
83
+ //# sourceMappingURL=run-script.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-script.js","names":["fs","path"],"sources":["../../src/schedules/run-script.ts"],"sourcesContent":["/**\n * Runs a schedule's script file at fire time.\n *\n * The file path is always passed as the literal command (or as the single\n * argument to a fixed interpreter) — never through a shell string — so the\n * user's extra prompt can never be interpolated into a command line.\n */\nimport { execFile } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { RunScript, ScriptResult } from './prompt.js';\n\n/** Interpreter for scripts that lack an executable bit, keyed by extension. */\nconst INTERPRETERS: Record<string, () => string> = {\n '.sh': () => '/bin/sh',\n '.js': () => process.execPath,\n '.mjs': () => process.execPath,\n '.cjs': () => process.execPath,\n '.ts': () => process.execPath,\n '.py': () => 'python3',\n};\n\nconst MAX_OUTPUT_BYTES = 1024 * 1024;\n\nexport function isExecutableFile(absPath: string): boolean {\n try {\n fs.accessSync(absPath, fs.constants.X_OK);\n return fs.statSync(absPath).isFile();\n } catch {\n return false;\n }\n}\n\nexport function resolveScriptCommand(absPath: string): { command: string; args: string[] } {\n if (isExecutableFile(absPath)) return { command: absPath, args: [] };\n const interpreter = INTERPRETERS[path.extname(absPath).toLowerCase()];\n if (!interpreter) {\n throw new Error(`${absPath} is not executable and has no known interpreter`);\n }\n return { command: interpreter(), args: [absPath] };\n}\n\nexport const runScript: RunScript = async (absPath, { cwd, timeoutMs }) => {\n const { command, args } = resolveScriptCommand(absPath);\n return new Promise<ScriptResult>((resolve, reject) => {\n execFile(\n command,\n args,\n { cwd, timeout: timeoutMs, maxBuffer: MAX_OUTPUT_BYTES, windowsHide: true },\n (error, stdout, stderr) => {\n if (!error) {\n resolve({ stdout, stderr, exitCode: 0 });\n return;\n }\n const spawnError = error as NodeJS.ErrnoException & { code?: number | string; killed?: boolean };\n if (typeof spawnError.code === 'number') {\n resolve({ stdout, stderr, exitCode: spawnError.code });\n return;\n }\n if (spawnError.killed) {\n resolve({ stdout, stderr: `${stderr}\\n[timed out after ${timeoutMs}ms]`.trim(), exitCode: null });\n return;\n }\n reject(error);\n },\n );\n });\n};\n"],"mappings":";;;;;;;;;;;;AAaA,MAAM,eAA6C;CACjD,aAAa;CACb,aAAa,QAAQ;CACrB,cAAc,QAAQ;CACtB,cAAc,QAAQ;CACtB,aAAa,QAAQ;CACrB,aAAa;AACf;AAEA,MAAM,mBAAmB,OAAO;AAEhC,SAAgB,iBAAiB,SAA0B;CACzD,IAAI;EACF,KAAG,WAAW,SAASA,KAAG,UAAU,IAAI;EACxC,OAAOA,KAAG,SAAS,OAAO,CAAC,CAAC,OAAO;CACrC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,qBAAqB,SAAsD;CACzF,IAAI,iBAAiB,OAAO,GAAG,OAAO;EAAE,SAAS;EAAS,MAAM,CAAC;CAAE;CACnE,MAAM,cAAc,aAAaC,OAAK,QAAQ,OAAO,CAAC,CAAC,YAAY;CACnE,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,GAAG,QAAQ,gDAAgD;CAE7E,OAAO;EAAE,SAAS,YAAY;EAAG,MAAM,CAAC,OAAO;CAAE;AACnD;AAEA,MAAa,YAAuB,OAAO,SAAS,EAAE,KAAK,gBAAgB;CACzE,MAAM,EAAE,SAAS,SAAS,qBAAqB,OAAO;CACtD,OAAO,IAAI,SAAuB,SAAS,WAAW;EACpD,SACE,SACA,MACA;GAAE;GAAK,SAAS;GAAW,WAAW;GAAkB,aAAa;EAAK,IACzE,OAAO,QAAQ,WAAW;GACzB,IAAI,CAAC,OAAO;IACV,QAAQ;KAAE;KAAQ;KAAQ,UAAU;IAAE,CAAC;IACvC;GACF;GACA,MAAM,aAAa;GACnB,IAAI,OAAO,WAAW,SAAS,UAAU;IACvC,QAAQ;KAAE;KAAQ;KAAQ,UAAU,WAAW;IAAK,CAAC;IACrD;GACF;GACA,IAAI,WAAW,QAAQ;IACrB,QAAQ;KAAE;KAAQ,QAAQ,GAAG,OAAO,qBAAqB,UAAU,KAAK,KAAK;KAAG,UAAU;IAAK,CAAC;IAChG;GACF;GACA,OAAO,KAAK;EACd,CACF;CACF,CAAC;AACH"}
@@ -0,0 +1,101 @@
1
+ import type { ParsedInterval } from './interval.js';
2
+ import type { AssembledPrompt } from './prompt.js';
3
+ /** `source` attribute on fired signals; the TUI labels these turns `schedule`. */
4
+ export declare const SCHEDULE_SIGNAL_SOURCE = "schedule";
5
+ /** When a schedule fires. Only recurring `every` triggers exist today. */
6
+ export type ScheduleTrigger = {
7
+ kind: 'every';
8
+ interval: ParsedInterval;
9
+ };
10
+ /** A file a schedule runs as a script, or re-reads and sends as the prompt, on every fire. */
11
+ export type ScheduleFile = {
12
+ path: string;
13
+ displayPath: string;
14
+ mode: 'script' | 'prompt';
15
+ };
16
+ /** What to send on each fire: `prompt` text, or a `file` (plus `extraPrompt` appended after it). */
17
+ export type ThreadScheduleSource = {
18
+ prompt: string;
19
+ } | {
20
+ file: ScheduleFile;
21
+ extraPrompt?: string;
22
+ };
23
+ export type ThreadScheduleSpec = ThreadScheduleSource & {
24
+ trigger: ScheduleTrigger;
25
+ /** Who is creating it: the user via `/schedules`, or the agent via its schedule tools. Defaults to `user`. */
26
+ createdBy?: 'user' | 'agent';
27
+ };
28
+ export type ThreadSchedule = {
29
+ id: string;
30
+ threadId: string;
31
+ resourceId: string;
32
+ trigger: ScheduleTrigger;
33
+ prompt?: string;
34
+ file?: ScheduleFile;
35
+ extraPrompt?: string;
36
+ createdBy: 'user' | 'agent';
37
+ status: 'active' | 'paused';
38
+ /** Next boundary this schedule fires at; only meaningful while active. */
39
+ nextFireAt: number;
40
+ createdAt: number;
41
+ };
42
+ export type ThreadSchedulerOptions = {
43
+ /** Build the prompt for one fire (runs scripts / reads files). */
44
+ assemblePrompt: (schedule: ThreadSchedule) => Promise<AssembledPrompt>;
45
+ /** Hand the prompt to the agent for the schedule's thread. */
46
+ deliver: (schedule: ThreadSchedule, assembled: AssembledPrompt) => Promise<void>;
47
+ /** A fire failed before reaching the agent. */
48
+ onError?: (error: unknown, schedule: ThreadSchedule) => void;
49
+ now?: () => number;
50
+ };
51
+ /** How late a timer may run before its fire counts as missed (sleep, stalled event loop). */
52
+ export declare const LATE_FIRE_GRACE_MS = 60000;
53
+ /**
54
+ * Outcome of a manual `run()`. `failed` means nothing reached the agent: the
55
+ * prompt could not be built or the agent refused it.
56
+ */
57
+ export type ScheduleRunResult = {
58
+ status: 'fired';
59
+ } | {
60
+ status: 'failed';
61
+ error: unknown;
62
+ } | {
63
+ status: 'busy';
64
+ } | {
65
+ status: 'not-found';
66
+ };
67
+ export declare class ThreadScheduler {
68
+ #private;
69
+ constructor(options: ThreadSchedulerOptions);
70
+ /** Throws when the interval is not a supported wall-clock cadence (see `validateInterval`) or the prompt is empty. */
71
+ create(spec: ThreadScheduleSpec, target: {
72
+ threadId: string;
73
+ resourceId: string;
74
+ }): ThreadSchedule;
75
+ list(filter?: {
76
+ threadId?: string;
77
+ }): ThreadSchedule[];
78
+ delete(id: string): boolean;
79
+ pause(id: string): boolean;
80
+ resume(id: string): boolean;
81
+ /** Whether a fire of this schedule (timer or manual) is still assembling or delivering. */
82
+ isFiring(id: string): boolean;
83
+ /**
84
+ * Fire once now, outside the cadence. Returns `busy` without firing when a
85
+ * fire of this schedule is still running (a slow script, say), so the same
86
+ * script never runs twice at once. Resolves once the fire finished; a fire
87
+ * that never reached the agent resolves `failed` instead of calling `onError`.
88
+ */
89
+ run(id: string): Promise<ScheduleRunResult>;
90
+ /** Drop every schedule and timer (process shutdown). */
91
+ stop(): void;
92
+ }
93
+ export declare function shortScheduleId(id: string): string;
94
+ /** One-line human summary of what a schedule sends: `run ./check.sh + "extra"`, `read notes.md`, `"prompt"`. */
95
+ export declare function describeScheduleSource(schedule: Pick<ThreadSchedule, 'prompt' | 'file' | 'extraPrompt'>): string;
96
+ /**
97
+ * Attributes on a fired schedule signal. They carry everything the transcript
98
+ * header needs, so reloaded history renders without the process-local entry.
99
+ */
100
+ export declare function scheduleSignalAttributes(schedule: ThreadSchedule, assembled?: Pick<AssembledPrompt, 'outcome'>): Record<string, string>;
101
+ //# sourceMappingURL=scheduler.d.ts.map