@kubitnodes/stellaris 0.3.13 → 0.3.14

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 (52) hide show
  1. package/dist/cli/index.js +1101 -539
  2. package/dist/server/index.js +1262 -592
  3. package/package.json +2 -1
  4. package/web/dist/assets/{BackLink-d0xz7ZLb.js → BackLink-giAdtos8.js} +1 -1
  5. package/web/dist/assets/{ChannelForms-BIRwT9CY.js → ChannelForms-b6eKogvP.js} +1 -1
  6. package/web/dist/assets/ChannelView-DXoJ5w3z.js +1 -0
  7. package/web/dist/assets/CitizenView-yOnQtNz4.js +2 -0
  8. package/web/dist/assets/{CitizensView-Bv04iH-y.js → CitizensView-Dbo47F82.js} +1 -1
  9. package/web/dist/assets/{CliIcon-tYTwI_RQ.js → CliIcon-DxDvRRcX.js} +1 -1
  10. package/web/dist/assets/CronsView-DYx7dQcr.js +1 -0
  11. package/web/dist/assets/KnowledgeView-BXLj85OH.js +1 -0
  12. package/web/dist/assets/{Markdown-BLiIulgZ.js → Markdown-Dw04_ImA.js} +4 -4
  13. package/web/dist/assets/{MessageItem-CHrmjF7P.js → MessageItem-DnfMDxDB.js} +1 -1
  14. package/web/dist/assets/{MetricsView-DG1alTgs.js → MetricsView-_h2TWeo8.js} +1 -1
  15. package/web/dist/assets/{NeedsYouView-Doc0fKPi.js → NeedsYouView-BI1-_bYI.js} +1 -1
  16. package/web/dist/assets/{Overview-ChsOgIEW.js → Overview-BdZiHglw.js} +1 -1
  17. package/web/dist/assets/{Pane-MJgGXQBb.js → Pane-CODhA5zE.js} +1 -1
  18. package/web/dist/assets/ProjectView-CScK4F7F.js +1 -0
  19. package/web/dist/assets/ProposalView-BQ1rflqo.js +1 -0
  20. package/web/dist/assets/{ProposalsView-CSA8neXR.js → ProposalsView-DSi2u6qO.js} +1 -1
  21. package/web/dist/assets/{RunnersView-Dx21LDFu.js → RunnersView-9E2qUSO5.js} +1 -1
  22. package/web/dist/assets/{SocietyView-CdVW2DgG.js → SocietyView-DEeUo4wb.js} +1 -1
  23. package/web/dist/assets/SubjectThread-DhGB5RPc.js +1 -0
  24. package/web/dist/assets/{TaskFileView-BqqzxJKb.js → TaskFileView-DQC_ZUdY.js} +2 -2
  25. package/web/dist/assets/TaskView-yVpUG83f.js +3 -0
  26. package/web/dist/assets/{TasksView-Ba5fPAvy.js → TasksView-CJbJt5dM.js} +1 -1
  27. package/web/dist/assets/ThreadCard-DVAx3gGc.js +1 -0
  28. package/web/dist/assets/{ThreadForms-ChTFRu-P.js → ThreadForms-RnrV7V5X.js} +1 -1
  29. package/web/dist/assets/{ThreadView-CnJPNS_8.js → ThreadView-CMwiVZFq.js} +1 -1
  30. package/web/dist/assets/{citizen-D-dxX00V.js → citizen-aLkZ4owL.js} +1 -1
  31. package/web/dist/assets/{compose-BgpUWGYE.js → compose-D7DOz6kC.js} +1 -1
  32. package/web/dist/assets/format-BORdp4w-.js +55 -0
  33. package/web/dist/assets/{governance-Di4MijNG.js → governance-WrDQawLN.js} +1 -1
  34. package/web/dist/assets/index-7uInG5D9.js +23 -0
  35. package/web/dist/assets/index-D02Wgo_C.css +2 -0
  36. package/web/dist/assets/{link-DAMxBItI.js → link-1v5lugnf.js} +1 -1
  37. package/web/dist/assets/{tasks-bwj_EdwF.js → tasks-C-9dAYQV.js} +1 -1
  38. package/web/dist/assets/useMatch-CyeFfbcR.js +1 -0
  39. package/web/dist/assets/{useStickyScroll-BILPUgGj.js → useStickyScroll-CE7dyz0Q.js} +1 -1
  40. package/web/dist/index.html +9 -9
  41. package/web/dist/assets/ChannelView-BvMlzMiN.js +0 -1
  42. package/web/dist/assets/CitizenView-DNQwjpIp.js +0 -2
  43. package/web/dist/assets/KnowledgeView-L0pdaM_2.js +0 -1
  44. package/web/dist/assets/ProjectView-CQTn7yDo.js +0 -1
  45. package/web/dist/assets/ProposalView-C92OJ_w1.js +0 -1
  46. package/web/dist/assets/SubjectThread-egXeFhT1.js +0 -1
  47. package/web/dist/assets/TaskView-rks26vuF.js +0 -3
  48. package/web/dist/assets/ThreadCard-D6i69_15.js +0 -1
  49. package/web/dist/assets/format-C3aawMM6.js +0 -55
  50. package/web/dist/assets/index-VVnGdAXc.css +0 -2
  51. package/web/dist/assets/index-krHkeIbX.js +0 -23
  52. package/web/dist/assets/useMatch-B-8IcuU6.js +0 -1
package/dist/cli/index.js CHANGED
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
  import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rename as rename3, stat as stat2 } from "node:fs/promises";
10
10
  import path6 from "node:path";
11
11
  import { decodeTime, monotonicFactory } from "ulid";
12
- import { z as z12 } from "zod";
12
+ import { z as z13 } from "zod";
13
13
 
14
14
  // packages/shared/dist/ids.js
15
15
  import { z } from "zod";
@@ -60,7 +60,10 @@ var VERB_NAMES = [
60
60
  "remove_knowledge",
61
61
  "create_channel",
62
62
  "archive_channel",
63
- "update_dashboard"
63
+ "update_dashboard",
64
+ "create_cron",
65
+ "update_cron",
66
+ "remove_cron"
64
67
  ];
65
68
  var VerbNameSchema = z2.enum(VERB_NAMES);
66
69
  var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
@@ -107,7 +110,10 @@ var MEMBER_VERBS = [
107
110
  "remove_knowledge",
108
111
  "plan_task",
109
112
  "advance_task",
110
- "update_dashboard"
113
+ "update_dashboard",
114
+ "create_cron",
115
+ "update_cron",
116
+ "remove_cron"
111
117
  ];
112
118
  var GOVERNANCE_VERBS = ["approve", "reject"];
113
119
  var FRONT_DESK_VERBS = ["create_project"];
@@ -160,7 +166,126 @@ var SEED_ROLES = [
160
166
  ];
161
167
 
162
168
  // packages/shared/dist/board.js
169
+ import { z as z4 } from "zod";
170
+
171
+ // packages/shared/dist/crons.js
172
+ import { Cron as Pattern } from "croner";
163
173
  import { z as z3 } from "zod";
174
+ var CRON_FLOOR_MS = 15 * 6e4;
175
+ var FIRE_SLACK_MS = 6e4;
176
+ var FLOOR_LOOKAHEAD = 200;
177
+ var DEFAULT_TIME_ZONE = "UTC";
178
+ function canonicalTimeZone(zone) {
179
+ try {
180
+ return new Intl.DateTimeFormat("en-US", { timeZone: zone }).resolvedOptions().timeZone;
181
+ } catch {
182
+ return null;
183
+ }
184
+ }
185
+ var TimeZoneSchema = z3.string().trim().min(1).max(64).transform((zone, ctx) => {
186
+ const canonical = canonicalTimeZone(zone);
187
+ if (canonical === null) {
188
+ ctx.addIssue({
189
+ code: "custom",
190
+ message: `${zone} is not a time zone; name one as IANA does, such as Europe/Berlin, America/New_York, or UTC`
191
+ });
192
+ return z3.NEVER;
193
+ }
194
+ return canonical;
195
+ });
196
+ function pattern(expression, timeZone) {
197
+ return new Pattern(expression, { mode: "5-part", timezone: timeZone, paused: true });
198
+ }
199
+ function cronExpressionError(expression) {
200
+ try {
201
+ pattern(expression, DEFAULT_TIME_ZONE);
202
+ return null;
203
+ } catch (error) {
204
+ return error instanceof Error ? error.message : String(error);
205
+ }
206
+ }
207
+ var CronExpressionSchema = z3.string().trim().min(1).max(120).superRefine((expression, ctx) => {
208
+ const error = cronExpressionError(expression);
209
+ if (error !== null) {
210
+ ctx.addIssue({
211
+ code: "custom",
212
+ message: `not a five-field cron expression (minute, hour, day of month, month, day of week): ${error}`
213
+ });
214
+ }
215
+ });
216
+ var CronScheduleSchema = z3.union([
217
+ z3.object({ cron: CronExpressionSchema, timezone: TimeZoneSchema.optional() }),
218
+ z3.object({ at: IsoDateTimeSchema })
219
+ ]);
220
+ var CronRunSchema = z3.object({
221
+ turnId: UlidSchema,
222
+ at: IsoDateTimeSchema,
223
+ outcome: z3.enum(["completed", "failed", "stopped"])
224
+ });
225
+ var CronFrontmatterSchema = z3.object({
226
+ id: UlidSchema,
227
+ title: z3.string().trim().min(1).max(120),
228
+ agent: NameSchema,
229
+ scope: NameSchema,
230
+ thread: UlidSchema.optional(),
231
+ channel: NameSchema.optional(),
232
+ schedule: CronScheduleSchema,
233
+ createdBy: NameSchema,
234
+ createdAt: IsoDateTimeSchema,
235
+ /** When the current schedule took effect: set, changed, or resumed. It fires only after this. */
236
+ since: IsoDateTimeSchema,
237
+ paused: z3.object({ at: IsoDateTimeSchema, by: NameSchema }).optional(),
238
+ lastFiredAt: IsoDateTimeSchema.optional(),
239
+ /** When a time came while its previous fire's turn was still queued or running, so it passed. */
240
+ lastSkippedAt: IsoDateTimeSchema.optional(),
241
+ /** How the turn of its latest fire ended. */
242
+ lastRun: CronRunSchema.optional(),
243
+ /** Its turns that failed in a row, up to the latest. */
244
+ failures: z3.number().int().nonnegative().default(0),
245
+ ended: z3.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z3.string().min(1) }).optional()
246
+ });
247
+ function isOneTime(schedule) {
248
+ return "at" in schedule;
249
+ }
250
+ function scheduleZone(schedule, societyZone) {
251
+ return isOneTime(schedule) ? societyZone : schedule.timezone ?? societyZone;
252
+ }
253
+ function fireTimes(schedule, societyZone, after, count) {
254
+ if (isOneTime(schedule)) {
255
+ const at = new Date(schedule.at);
256
+ return at > after && count > 0 ? [at] : [];
257
+ }
258
+ return pattern(schedule.cron, scheduleZone(schedule, societyZone)).nextRuns(count, after);
259
+ }
260
+ function shortestGapMs(schedule, societyZone, after) {
261
+ const times = fireTimes(schedule, societyZone, after, FLOOR_LOOKAHEAD);
262
+ let shortest = null;
263
+ for (let index = 1; index < times.length; index += 1) {
264
+ const gap = (times[index]?.getTime() ?? 0) - (times[index - 1]?.getTime() ?? 0);
265
+ shortest = shortest === null ? gap : Math.min(shortest, gap);
266
+ }
267
+ return shortest;
268
+ }
269
+ function nextFire(cron2, societyZone, floorMs = CRON_FLOOR_MS) {
270
+ if (cron2.ended !== void 0 || cron2.paused !== void 0) {
271
+ return null;
272
+ }
273
+ const fired = cron2.lastFiredAt === void 0 ? null : Date.parse(cron2.lastFiredAt);
274
+ const since = Date.parse(cron2.since);
275
+ if (isOneTime(cron2.schedule)) {
276
+ return fired !== null && fired >= since ? null : new Date(cron2.schedule.at);
277
+ }
278
+ const from = Math.max(since, fired === null ? since : fired + floorMs - FIRE_SLACK_MS, cron2.lastSkippedAt === void 0 ? since : Date.parse(cron2.lastSkippedAt));
279
+ return fireTimes(cron2.schedule, societyZone, new Date(from), 1)[0] ?? null;
280
+ }
281
+ function describeSchedule(schedule, societyZone) {
282
+ if (isOneTime(schedule)) {
283
+ return `once at ${schedule.at}`;
284
+ }
285
+ return schedule.timezone === void 0 ? `\`${schedule.cron}\` in the society's time zone, ${societyZone}` : `\`${schedule.cron}\` in ${schedule.timezone}`;
286
+ }
287
+
288
+ // packages/shared/dist/board.js
164
289
  var ASK_CHANNEL = "asks";
165
290
  var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
166
291
  var PROJECT_DEFAULT_CHANNELS = ["general"];
@@ -172,30 +297,32 @@ var SOCIETY_SCOPE = "society";
172
297
  function wakeScope(member, project2) {
173
298
  return project2 !== null && member.memberships.includes(project2) ? project2 : SOCIETY_SCOPE;
174
299
  }
175
- var CliKindSchema = z3.enum(["claude", "codex"]);
176
- var ArchivedChannelSchema = z3.object({
300
+ var CliKindSchema = z4.enum(["claude", "codex"]);
301
+ var ArchivedChannelSchema = z4.object({
177
302
  name: NameSchema,
178
303
  at: IsoDateTimeSchema,
179
304
  by: NameSchema,
180
- reason: z3.string().min(1)
305
+ reason: z4.string().min(1)
181
306
  });
182
- var SocietySchema = z3.object({
183
- name: z3.string().min(1),
184
- version: z3.literal(1),
307
+ var SocietySchema = z4.object({
308
+ name: z4.string().min(1),
309
+ version: z4.literal(1),
185
310
  createdAt: IsoDateTimeSchema,
186
311
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
187
- channels: z3.array(NameSchema),
188
- archivedChannels: z3.array(ArchivedChannelSchema).default([])
312
+ channels: z4.array(NameSchema),
313
+ archivedChannels: z4.array(ArchivedChannelSchema).default([]),
314
+ /** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
315
+ timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
189
316
  });
190
- var CompletionEffectSchema = z3.enum(["none", "merge", "ghpr"]);
317
+ var CompletionEffectSchema = z4.enum(["none", "merge", "ghpr"]);
191
318
  var GITHUB_REMOTE = [
192
319
  /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
193
320
  /^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
194
321
  /^ssh:\/\/git@github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/
195
322
  ];
196
323
  function githubRepository(remote) {
197
- for (const pattern of GITHUB_REMOTE) {
198
- const match = remote === null ? null : pattern.exec(remote);
324
+ for (const pattern2 of GITHUB_REMOTE) {
325
+ const match = remote === null ? null : pattern2.exec(remote);
199
326
  if (match?.[1] !== void 0 && match[2] !== void 0) {
200
327
  return { owner: match[1], name: match[2] };
201
328
  }
@@ -203,7 +330,7 @@ function githubRepository(remote) {
203
330
  return null;
204
331
  }
205
332
  var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
206
- var PullRequestUrlSchema = z3.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
333
+ var PullRequestUrlSchema = z4.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
207
334
  function parsePullRequestUrl(url) {
208
335
  const match = PULL_REQUEST_URL.exec(url.trim());
209
336
  return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
@@ -211,65 +338,65 @@ function parsePullRequestUrl(url) {
211
338
  function sameRepository(a, b) {
212
339
  return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
213
340
  }
214
- var MergeSubjectSchema = z3.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
215
- var PullRequestLinkSchema = z3.object({
341
+ var MergeSubjectSchema = z4.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
342
+ var PullRequestLinkSchema = z4.object({
216
343
  url: PullRequestUrlSchema,
217
- number: z3.number().int().positive(),
344
+ number: z4.number().int().positive(),
218
345
  subject: MergeSubjectSchema.optional(),
219
- body: z3.string().max(2e4).optional(),
346
+ body: z4.string().max(2e4).optional(),
220
347
  linkedBy: NameSchema,
221
348
  linkedAt: IsoDateTimeSchema,
222
349
  /**
223
350
  * Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
224
351
  * leaves it open, for the reason `detail` gives.
225
352
  */
226
- closed: z3.object({ at: IsoDateTimeSchema, ok: z3.boolean(), detail: z3.string() }).optional()
353
+ closed: z4.object({ at: IsoDateTimeSchema, ok: z4.boolean(), detail: z4.string() }).optional()
227
354
  });
228
- var DefaultBranchSchema = z3.string().trim().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "a branch name is letters, digits, and . _ / -").refine((branch) => !branch.includes("..") && !branch.includes("//") && !branch.endsWith("/") && !branch.endsWith(".") && !branch.endsWith(".lock"), "not a branch name git takes").refine((branch) => !branch.startsWith("task/") && !branch.startsWith("agent/"), "task/ and agent/ hold the board's own branches");
229
- var StageIdSchema = z3.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
355
+ var DefaultBranchSchema = z4.string().trim().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "a branch name is letters, digits, and . _ / -").refine((branch) => !branch.includes("..") && !branch.includes("//") && !branch.endsWith("/") && !branch.endsWith(".") && !branch.endsWith(".lock"), "not a branch name git takes").refine((branch) => !branch.startsWith("task/") && !branch.startsWith("agent/"), "task/ and agent/ hold the board's own branches");
356
+ var StageIdSchema = z4.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
230
357
  var stageFields = {
231
- name: z3.string().trim().min(1).max(120),
358
+ name: z4.string().trim().min(1).max(120),
232
359
  role: NameSchema.optional(),
233
360
  agent: NameSchema.optional(),
234
- gate: z3.boolean().default(false)
361
+ gate: z4.boolean().default(false)
235
362
  };
236
363
  function oneAssignee(stage) {
237
364
  return stage.role === void 0 || stage.agent === void 0;
238
365
  }
239
366
  var ONE_ASSIGNEE = { message: "a stage names a role or an agent, not both" };
240
- var PlanStageSchema = z3.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
241
- var PlanEditStageSchema = z3.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
242
- var PlanSchema = z3.array(PlanStageSchema);
243
- var PlanEditSchema = z3.array(PlanEditStageSchema);
244
- var ProjectSchema = z3.object({
367
+ var PlanStageSchema = z4.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
368
+ var PlanEditStageSchema = z4.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
369
+ var PlanSchema = z4.array(PlanStageSchema);
370
+ var PlanEditSchema = z4.array(PlanEditStageSchema);
371
+ var ProjectSchema = z4.object({
245
372
  slug: NameSchema,
246
- name: z3.string().min(1),
247
- repo: z3.string().nullable(),
248
- defaultBranch: z3.string().min(1),
373
+ name: z4.string().min(1),
374
+ repo: z4.string().nullable(),
375
+ defaultBranch: z4.string().min(1),
249
376
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
250
- channels: z3.array(NameSchema),
251
- archivedChannels: z3.array(ArchivedChannelSchema).default([]),
252
- members: z3.array(NameSchema),
253
- approvers: z3.array(NameSchema),
254
- requiredCapabilities: z3.array(z3.string()),
377
+ channels: z4.array(NameSchema),
378
+ archivedChannels: z4.array(ArchivedChannelSchema).default([]),
379
+ members: z4.array(NameSchema),
380
+ approvers: z4.array(NameSchema),
381
+ requiredCapabilities: z4.array(z4.string()),
255
382
  createdAt: IsoDateTimeSchema,
256
383
  onDone: CompletionEffectSchema.default("none"),
257
384
  /** The runner the project lives on, which holds its repository; set on the project's first turn. */
258
385
  runner: NameSchema.optional(),
259
386
  /** Set once the project is archived: nobody belongs to it, and nothing more is posted, filed, or joined there. */
260
- archived: z3.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z3.string().min(1) }).optional()
387
+ archived: z4.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z4.string().min(1) }).optional()
261
388
  });
262
- var TaskStepSchema = z3.object({
263
- action: z3.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
389
+ var TaskStepSchema = z4.object({
390
+ action: z4.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
264
391
  stage: StageIdSchema,
265
392
  to: StageIdSchema.nullable()
266
393
  });
267
- var ProposalStepSchema = z3.object({ action: z3.enum(["approved", "rejected"]) });
268
- var MessageStepSchema = z3.union([TaskStepSchema, ProposalStepSchema]);
394
+ var ProposalStepSchema = z4.object({ action: z4.enum(["approved", "rejected"]) });
395
+ var MessageStepSchema = z4.union([TaskStepSchema, ProposalStepSchema]);
269
396
  function isTaskStep(step) {
270
397
  return "stage" in step;
271
398
  }
272
- var MessageFrontmatterSchema = z3.object({
399
+ var MessageFrontmatterSchema = z4.object({
273
400
  id: UlidSchema,
274
401
  author: NameSchema,
275
402
  channel: ChannelRefSchema,
@@ -281,18 +408,18 @@ var MessageFrontmatterSchema = z3.object({
281
408
  /** On a post a task verb made in the task's thread, or a decision in a proposal's: the step it recorded. */
282
409
  step: MessageStepSchema.optional(),
283
410
  ts: IsoDateTimeSchema,
284
- mentions: z3.array(NameSchema)
411
+ mentions: z4.array(NameSchema)
285
412
  });
286
- var TaskStatusSchema = z3.enum(["open", "claimed", "done", "abandoned"]);
287
- var ThreadStateSchema = z3.enum(["open", "closed"]);
288
- var ThreadSubjectSchema = z3.object({
289
- kind: z3.enum(["task", "proposal"]),
413
+ var TaskStatusSchema = z4.enum(["open", "claimed", "done", "abandoned"]);
414
+ var ThreadStateSchema = z4.enum(["open", "closed"]);
415
+ var ThreadSubjectSchema = z4.object({
416
+ kind: z4.enum(["task", "proposal"]),
290
417
  id: UlidSchema
291
418
  });
292
- var ThreadFrontmatterSchema = z3.object({
419
+ var ThreadFrontmatterSchema = z4.object({
293
420
  id: UlidSchema,
294
421
  channel: ChannelRefSchema,
295
- title: z3.string().min(1).max(200),
422
+ title: z4.string().min(1).max(200),
296
423
  subject: ThreadSubjectSchema.optional(),
297
424
  state: ThreadStateSchema,
298
425
  openedBy: NameSchema,
@@ -300,28 +427,28 @@ var ThreadFrontmatterSchema = z3.object({
300
427
  closedBy: NameSchema.optional(),
301
428
  closedAt: IsoDateTimeSchema.optional()
302
429
  });
303
- var StageSchema = z3.object({
430
+ var StageSchema = z4.object({
304
431
  id: StageIdSchema,
305
- name: z3.string().min(1),
432
+ name: z4.string().min(1),
306
433
  role: NameSchema.optional(),
307
434
  agent: NameSchema.optional(),
308
- gate: z3.boolean().default(false),
435
+ gate: z4.boolean().default(false),
309
436
  /** Everyone who has held the stage, oldest first. */
310
- holders: z3.array(NameSchema).default([]),
437
+ holders: z4.array(NameSchema).default([]),
311
438
  completedBy: NameSchema.optional(),
312
439
  completedAt: IsoDateTimeSchema.optional()
313
440
  });
314
- var TaskReturnSchema = z3.object({
441
+ var TaskReturnSchema = z4.object({
315
442
  from: StageIdSchema,
316
443
  by: NameSchema,
317
444
  at: IsoDateTimeSchema
318
445
  });
319
- var TaskFrontmatterSchema = z3.object({
446
+ var TaskFrontmatterSchema = z4.object({
320
447
  id: UlidSchema,
321
448
  project: NameSchema,
322
449
  /** The channel of the project the task is filed in, where its thread hangs. */
323
450
  channel: NameSchema.default("general"),
324
- title: z3.string().min(1),
451
+ title: z4.string().min(1),
325
452
  status: TaskStatusSchema,
326
453
  createdBy: NameSchema,
327
454
  createdAt: IsoDateTimeSchema,
@@ -330,19 +457,19 @@ var TaskFrontmatterSchema = z3.object({
330
457
  claimedBy: NameSchema.optional(),
331
458
  leaseExpiresAt: IsoDateTimeSchema.optional(),
332
459
  parentId: UlidSchema.optional(),
333
- blockedBy: z3.array(UlidSchema),
334
- requiredCapabilities: z3.array(z3.string()),
335
- stages: z3.array(StageSchema).min(1),
460
+ blockedBy: z4.array(UlidSchema),
461
+ requiredCapabilities: z4.array(z4.string()),
462
+ stages: z4.array(StageSchema).min(1),
336
463
  stage: StageIdSchema,
337
464
  /** When the current stage last became current or lost its holder: the waiting clock. */
338
465
  stageSince: IsoDateTimeSchema,
339
466
  /** The highest stage number issued, so a removed stage's id is never reused. */
340
- stageSeq: z3.number().int().positive(),
467
+ stageSeq: z4.number().int().positive(),
341
468
  onDone: CompletionEffectSchema.default("none"),
342
469
  /** The pull request a `ghpr` task lands through. */
343
470
  pullRequest: PullRequestLinkSchema.optional(),
344
471
  /** The last stage is complete and the completion effect is running. */
345
- completing: z3.boolean().default(false),
472
+ completing: z4.boolean().default(false),
346
473
  /** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
347
474
  returned: TaskReturnSchema.optional()
348
475
  });
@@ -366,79 +493,79 @@ function mayHoldStage(member, task2) {
366
493
  }
367
494
  return !(stage.gate && task2.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
368
495
  }
369
- var ModelNameSchema = z3.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
370
- var EffortSchema = z3.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
371
- var EffortOptionSchema = z3.object({
496
+ var ModelNameSchema = z4.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
497
+ var EffortSchema = z4.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
498
+ var EffortOptionSchema = z4.object({
372
499
  id: EffortSchema,
373
- description: z3.string().default("")
500
+ description: z4.string().default("")
374
501
  });
375
- var ModelOptionSchema = z3.object({
502
+ var ModelOptionSchema = z4.object({
376
503
  id: ModelNameSchema,
377
- name: z3.string(),
378
- description: z3.string().default(""),
379
- isDefault: z3.boolean().default(false),
380
- efforts: z3.array(EffortOptionSchema).default([]),
504
+ name: z4.string(),
505
+ description: z4.string().default(""),
506
+ isDefault: z4.boolean().default(false),
507
+ efforts: z4.array(EffortOptionSchema).default([]),
381
508
  defaultEffort: EffortSchema.optional()
382
509
  });
383
- var RunnerModelsSchema = z3.object({
510
+ var RunnerModelsSchema = z4.object({
384
511
  runner: NameSchema,
385
512
  cli: CliKindSchema,
386
- models: z3.array(ModelOptionSchema)
513
+ models: z4.array(ModelOptionSchema)
387
514
  });
388
- var AgentStatusSchema = z3.enum(["active", "retired"]);
389
- var AgentSchema = z3.object({
515
+ var AgentStatusSchema = z4.enum(["active", "retired"]);
516
+ var AgentSchema = z4.object({
390
517
  name: NameSchema,
391
518
  role: NameSchema,
392
519
  cli: CliKindSchema.nullable(),
393
- model: z3.string().optional(),
520
+ model: z4.string().optional(),
394
521
  /** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
395
- effort: z3.string().optional(),
522
+ effort: z4.string().optional(),
396
523
  /** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
397
524
  homeRunner: NameSchema.optional(),
398
- memberships: z3.array(NameSchema),
399
- subscriptions: z3.array(ChannelRefSchema),
525
+ memberships: z4.array(NameSchema),
526
+ subscriptions: z4.array(ChannelRefSchema),
400
527
  status: AgentStatusSchema,
401
528
  createdAt: IsoDateTimeSchema,
402
- tokenHash: z3.string().min(1),
529
+ tokenHash: z4.string().min(1),
403
530
  retiredAt: IsoDateTimeSchema.optional(),
404
- retiredReason: z3.string().optional()
531
+ retiredReason: z4.string().optional()
405
532
  });
406
- var MemberSchema = z3.object({
533
+ var MemberSchema = z4.object({
407
534
  name: NameSchema,
408
535
  role: NameSchema,
409
536
  cli: CliKindSchema.nullable(),
410
537
  homeRunner: NameSchema.optional(),
411
538
  status: AgentStatusSchema,
412
- resident: z3.boolean().default(false),
539
+ resident: z4.boolean().default(false),
413
540
  /** The model configured on the record, if any; the CLI's own default applies otherwise. */
414
- model: z3.string().optional(),
541
+ model: z4.string().optional(),
415
542
  /** The model the CLI reported on the citizen's last turn. */
416
- lastModel: z3.string().optional(),
543
+ lastModel: z4.string().optional(),
417
544
  /** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
418
- effort: z3.string().optional(),
545
+ effort: z4.string().optional(),
419
546
  /** The citizen's own skills, by name. */
420
- skills: z3.array(NameSchema).default([]),
421
- memberships: z3.array(NameSchema),
422
- subscriptions: z3.array(ChannelRefSchema),
423
- claimsHeld: z3.number().int().nonnegative().default(0),
424
- tasksDone: z3.number().int().nonnegative().default(0),
547
+ skills: z4.array(NameSchema).default([]),
548
+ memberships: z4.array(NameSchema),
549
+ subscriptions: z4.array(ChannelRefSchema),
550
+ claimsHeld: z4.number().int().nonnegative().default(0),
551
+ tasksDone: z4.number().int().nonnegative().default(0),
425
552
  lastTurnAt: IsoDateTimeSchema.optional(),
426
- lastTurnOutcome: z3.string().optional(),
553
+ lastTurnOutcome: z4.string().optional(),
427
554
  createdAt: IsoDateTimeSchema,
428
555
  retiredAt: IsoDateTimeSchema.optional()
429
556
  });
430
- var RunnerOsSchema = z3.enum(["linux", "windows", "darwin"]);
431
- var RunnerSchema = z3.object({
557
+ var RunnerOsSchema = z4.enum(["linux", "windows", "darwin"]);
558
+ var RunnerSchema = z4.object({
432
559
  name: NameSchema,
433
560
  os: RunnerOsSchema,
434
- clis: z3.array(CliKindSchema),
435
- capabilities: z3.array(z3.string()),
561
+ clis: z4.array(CliKindSchema),
562
+ capabilities: z4.array(z4.string()),
436
563
  /** The release the runner reported when it last registered; absent until it has. */
437
- version: z3.string().optional(),
438
- status: z3.enum(["connected", "disconnected"]),
564
+ version: z4.string().optional(),
565
+ status: z4.enum(["connected", "disconnected"]),
439
566
  lastSeen: IsoDateTimeSchema.optional()
440
567
  });
441
- var ProposalKindSchema = z3.enum([
568
+ var ProposalKindSchema = z4.enum([
442
569
  "role",
443
570
  "member",
444
571
  "channel",
@@ -447,43 +574,43 @@ var ProposalKindSchema = z3.enum([
447
574
  "skill",
448
575
  "archive"
449
576
  ]);
450
- var ProposalStatusSchema = z3.enum([
577
+ var ProposalStatusSchema = z4.enum([
451
578
  "proposed",
452
579
  "approved",
453
580
  "rejected",
454
581
  "provisioned",
455
582
  "retired"
456
583
  ]);
457
- var MemberProposalSchema = z3.object({
584
+ var MemberProposalSchema = z4.object({
458
585
  name: NameSchema,
459
586
  role: NameSchema,
460
587
  cli: CliKindSchema,
461
- model: z3.string().optional(),
588
+ model: z4.string().optional(),
462
589
  homeRunner: NameSchema.optional(),
463
- memberships: z3.array(NameSchema).default([]),
464
- subscriptions: z3.array(ChannelRefSchema).default([]),
465
- seedInstructions: z3.string().optional()
590
+ memberships: z4.array(NameSchema).default([]),
591
+ subscriptions: z4.array(ChannelRefSchema).default([]),
592
+ seedInstructions: z4.string().optional()
466
593
  });
467
- var ChannelProposalSchema = z3.object({
594
+ var ChannelProposalSchema = z4.object({
468
595
  project: NameSchema.nullable().default(null),
469
596
  name: NameSchema,
470
- purpose: z3.string().min(1)
597
+ purpose: z4.string().min(1)
471
598
  });
472
- var ReallocationProposalSchema = z3.object({
473
- description: z3.string().min(1)
599
+ var ReallocationProposalSchema = z4.object({
600
+ description: z4.string().min(1)
474
601
  });
475
- var RetirementProposalSchema = z3.object({
602
+ var RetirementProposalSchema = z4.object({
476
603
  agent: NameSchema,
477
- reason: z3.string().min(1)
604
+ reason: z4.string().min(1)
478
605
  });
479
- var ArchiveProposalSchema = z3.object({
606
+ var ArchiveProposalSchema = z4.object({
480
607
  project: NameSchema,
481
- reason: z3.string().min(1)
608
+ reason: z4.string().min(1)
482
609
  });
483
- var SkillProposalSchema = z3.object({
610
+ var SkillProposalSchema = z4.object({
484
611
  name: NameSchema,
485
- summary: z3.string().min(1).max(200),
486
- body: z3.string().min(1)
612
+ summary: z4.string().min(1).max(200),
613
+ body: z4.string().min(1)
487
614
  });
488
615
  var ProposalCharterSchemas = {
489
616
  role: RoleCharterSchema,
@@ -555,17 +682,17 @@ function describeCharter(kind, charter) {
555
682
  }
556
683
  return `${kind} ${JSON.stringify(charter)}`;
557
684
  }
558
- var SkillSchema = z3.object({
685
+ var SkillSchema = z4.object({
559
686
  name: NameSchema,
560
- summary: z3.string().default(""),
561
- scope: z3.enum(["own", "society"]),
562
- path: z3.string().min(1)
687
+ summary: z4.string().default(""),
688
+ scope: z4.enum(["own", "society"]),
689
+ path: z4.string().min(1)
563
690
  });
564
- var HomeConflictSchema = z3.object({
691
+ var HomeConflictSchema = z4.object({
565
692
  /** The copy, relative to the home. */
566
- path: z3.string().min(1),
693
+ path: z4.string().min(1),
567
694
  /** The file beside it, whose version the board kept. */
568
- file: z3.string().min(1),
695
+ file: z4.string().min(1),
569
696
  /** When the copy reached the board. */
570
697
  since: IsoDateTimeSchema
571
698
  });
@@ -573,60 +700,60 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
573
700
  function conflictCopyOf(copy) {
574
701
  return CONFLICT_COPY.exec(copy)?.[1] ?? null;
575
702
  }
576
- var CommitIdSchema = z3.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
577
- var HomeFileChangeSchema = z3.object({
578
- path: z3.string().min(1),
579
- status: z3.enum(["added", "modified", "deleted"]),
703
+ var CommitIdSchema = z4.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
704
+ var HomeFileChangeSchema = z4.object({
705
+ path: z4.string().min(1),
706
+ status: z4.enum(["added", "modified", "deleted"]),
580
707
  /** Lines added and removed, or null for a binary file. */
581
- added: z3.number().int().nonnegative().nullable(),
582
- removed: z3.number().int().nonnegative().nullable()
708
+ added: z4.number().int().nonnegative().nullable(),
709
+ removed: z4.number().int().nonnegative().nullable()
583
710
  });
584
- var HomeChangeSchema = z3.object({
711
+ var HomeChangeSchema = z4.object({
585
712
  commit: CommitIdSchema,
586
713
  at: IsoDateTimeSchema,
587
- kind: z3.enum(["turn", "board", "merge"]),
588
- author: z3.string(),
714
+ kind: z4.enum(["turn", "board", "merge"]),
715
+ author: z4.string(),
589
716
  /** The turn that made it, from the runner's commit message. */
590
717
  turnId: UlidSchema.optional(),
591
- subject: z3.string(),
592
- files: z3.array(HomeFileChangeSchema)
718
+ subject: z4.string(),
719
+ files: z4.array(HomeFileChangeSchema)
593
720
  });
594
- var HomeHistorySchema = z3.object({
595
- changes: z3.array(HomeChangeSchema),
721
+ var HomeHistorySchema = z4.object({
722
+ changes: z4.array(HomeChangeSchema),
596
723
  /** Whether older commits remain past the ones read. */
597
- more: z3.boolean()
724
+ more: z4.boolean()
598
725
  });
599
726
  var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
600
- patch: z3.string().nullable(),
727
+ patch: z4.string().nullable(),
601
728
  /** The patch was cut at its size limit. */
602
- truncated: z3.boolean()
729
+ truncated: z4.boolean()
603
730
  });
604
- var KnowledgeSchema = z3.object({
731
+ var KnowledgeSchema = z4.object({
605
732
  topic: NameSchema,
606
733
  project: NameSchema.nullable(),
607
734
  updatedBy: NameSchema,
608
735
  updatedAt: IsoDateTimeSchema
609
736
  });
610
- var RemovedKnowledgeSchema = z3.object({
737
+ var RemovedKnowledgeSchema = z4.object({
611
738
  topic: NameSchema,
612
739
  project: NameSchema.nullable(),
613
740
  removedBy: NameSchema,
614
741
  removedAt: IsoDateTimeSchema
615
742
  });
616
- var DashboardSchema = z3.object({
743
+ var DashboardSchema = z4.object({
617
744
  project: NameSchema,
618
- revision: z3.number().int().min(0),
745
+ revision: z4.number().int().min(0),
619
746
  /** Absent until someone updates the dashboard the project began with. */
620
747
  updatedBy: NameSchema.optional(),
621
748
  updatedAt: IsoDateTimeSchema.optional(),
622
- body: z3.string()
749
+ body: z4.string()
623
750
  });
624
- var ProposalOriginSchema = z3.object({
751
+ var ProposalOriginSchema = z4.object({
625
752
  scope: NameSchema,
626
753
  thread: UlidSchema.optional(),
627
754
  channel: NameSchema.optional()
628
755
  });
629
- var ProposalFrontmatterSchema = z3.object({
756
+ var ProposalFrontmatterSchema = z4.object({
630
757
  id: UlidSchema,
631
758
  kind: ProposalKindSchema,
632
759
  proposedBy: NameSchema,
@@ -636,144 +763,179 @@ var ProposalFrontmatterSchema = z3.object({
636
763
  filedFrom: ProposalOriginSchema.optional(),
637
764
  decidedBy: NameSchema.optional(),
638
765
  decidedAt: IsoDateTimeSchema.optional(),
639
- reason: z3.string().optional(),
640
- charter: z3.record(z3.string(), z3.unknown()),
766
+ reason: z4.string().optional(),
767
+ charter: z4.record(z4.string(), z4.unknown()),
641
768
  /** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
642
769
  provisionedAt: IsoDateTimeSchema.optional(),
643
- provision: z3.record(z3.string(), z3.unknown()).optional()
770
+ provision: z4.record(z4.string(), z4.unknown()).optional()
644
771
  });
645
- var DecisionSchema = z3.object({
772
+ var DecisionSchema = z4.object({
646
773
  id: UlidSchema,
647
774
  proposalId: UlidSchema,
648
775
  decidedBy: NameSchema,
649
- outcome: z3.enum(["approved", "rejected"]),
650
- reason: z3.string().optional(),
776
+ outcome: z4.enum(["approved", "rejected"]),
777
+ reason: z4.string().optional(),
651
778
  ts: IsoDateTimeSchema
652
779
  });
653
780
 
654
781
  // packages/shared/dist/verbs.js
655
- import { z as z4 } from "zod";
782
+ import { z as z5 } from "zod";
783
+ var MomentSchema = z5.iso.datetime({ offset: true });
784
+ var CronTitleSchema = z5.string().trim().min(1).max(120);
785
+ var CronNoteSchema = z5.string().trim().min(1).max(4e3);
656
786
  var VerbInputs = {
657
- post_message: z4.object({
787
+ post_message: z5.object({
658
788
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
659
789
  channel: ChannelRefSchema.optional(),
660
- body: z4.string().min(1),
790
+ body: z5.string().min(1),
661
791
  thread_id: UlidSchema.optional()
662
792
  }),
663
- read_inbox: z4.object({
793
+ read_inbox: z5.object({
664
794
  since_cursor: UlidSchema.nullable().optional(),
665
- limit: z4.number().int().positive().max(200).default(50),
666
- advance: z4.boolean().default(true)
795
+ limit: z5.number().int().positive().max(200).default(50),
796
+ advance: z5.boolean().default(true)
667
797
  }),
668
- search: z4.object({
669
- query: z4.string().min(1),
798
+ search: z5.object({
799
+ query: z5.string().min(1),
670
800
  project: NameSchema.optional(),
671
801
  channel: ChannelRefSchema.optional(),
672
- limit: z4.number().int().positive().max(100).default(20)
802
+ limit: z5.number().int().positive().max(100).default(20)
673
803
  }),
674
804
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
675
- open_thread: z4.object({
805
+ open_thread: z5.object({
676
806
  task_id: UlidSchema.optional(),
677
807
  proposal_id: UlidSchema.optional(),
678
808
  channel: ChannelRefSchema.optional(),
679
- title: z4.string().min(1).max(200).optional()
809
+ title: z5.string().min(1).max(200).optional()
680
810
  }),
681
- close_thread: z4.object({ thread_id: UlidSchema, summary: z4.string().min(1) }),
682
- create_task: z4.object({
811
+ close_thread: z5.object({ thread_id: UlidSchema, summary: z5.string().min(1) }),
812
+ create_task: z5.object({
683
813
  project: NameSchema,
684
- title: z4.string().min(1).max(200),
685
- body: z4.string().default(""),
814
+ title: z5.string().min(1).max(200),
815
+ body: z5.string().default(""),
686
816
  parent_id: UlidSchema.optional(),
687
- required_capabilities: z4.array(z4.string()).default([]),
817
+ required_capabilities: z5.array(z5.string()).default([]),
688
818
  /** The plan; without it the task gets one stage, work, that anyone in the project may take. */
689
- stages: z4.array(PlanStageSchema).min(1).optional(),
819
+ stages: z5.array(PlanStageSchema).min(1).optional(),
690
820
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
691
821
  channel: NameSchema.default("general")
692
822
  }),
693
- claim_task: z4.object({ task_id: UlidSchema }),
694
- release_task: z4.object({ task_id: UlidSchema }),
695
- update_task: z4.object({
823
+ claim_task: z5.object({ task_id: UlidSchema }),
824
+ release_task: z5.object({ task_id: UlidSchema }),
825
+ update_task: z5.object({
696
826
  task_id: UlidSchema,
697
827
  /** Move the task back to this earlier stage. */
698
828
  stage: StageIdSchema.optional(),
699
- status: z4.enum(["abandoned"]).optional(),
829
+ status: z5.enum(["abandoned"]).optional(),
700
830
  /** Posted to the task's thread as the caller, marked with the step when the task moved. */
701
- note: z4.string().min(1).optional(),
702
- blocked_by: z4.array(UlidSchema).optional(),
831
+ note: z5.string().min(1).optional(),
832
+ blocked_by: z5.array(UlidSchema).optional(),
703
833
  /**
704
834
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
705
835
  * commit's subject and body; null unlinks it.
706
836
  */
707
- pull_request: z4.object({
837
+ pull_request: z5.object({
708
838
  url: PullRequestUrlSchema,
709
839
  merge_subject: MergeSubjectSchema.optional(),
710
- merge_body: z4.string().max(2e4).optional()
840
+ merge_body: z5.string().max(2e4).optional()
711
841
  }).nullable().optional()
712
842
  }),
713
- get_task: z4.object({ task_id: UlidSchema }),
714
- subscribe: z4.object({ channel: ChannelRefSchema }),
715
- unsubscribe: z4.object({ channel: ChannelRefSchema }),
716
- propose: z4.object({
843
+ get_task: z5.object({ task_id: UlidSchema }),
844
+ subscribe: z5.object({ channel: ChannelRefSchema }),
845
+ unsubscribe: z5.object({ channel: ChannelRefSchema }),
846
+ propose: z5.object({
717
847
  kind: ProposalKindSchema,
718
- charter: z4.record(z4.string(), z4.unknown()),
719
- rationale: z4.string().default("")
848
+ charter: z5.record(z5.string(), z5.unknown()),
849
+ rationale: z5.string().default("")
720
850
  }),
721
- approve: z4.object({ proposal_id: UlidSchema, reason: z4.string().optional() }),
722
- reject: z4.object({ proposal_id: UlidSchema, reason: z4.string().min(1) }),
723
- create_project: z4.object({
851
+ approve: z5.object({ proposal_id: UlidSchema, reason: z5.string().optional() }),
852
+ reject: z5.object({ proposal_id: UlidSchema, reason: z5.string().min(1) }),
853
+ create_project: z5.object({
724
854
  slug: NameSchema,
725
- name: z4.string().min(1).optional(),
726
- repo: z4.string().min(1).nullable().default(null),
855
+ name: z5.string().min(1).optional(),
856
+ repo: z5.string().min(1).nullable().default(null),
727
857
  default_branch: DefaultBranchSchema.default("main"),
728
858
  on_done: CompletionEffectSchema.optional()
729
859
  }),
730
- join_project: z4.object({ project: NameSchema, agent: NameSchema.optional() }),
731
- leave_project: z4.object({ project: NameSchema, agent: NameSchema.optional() }),
732
- write_knowledge: z4.object({
860
+ join_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
861
+ leave_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
862
+ write_knowledge: z5.object({
733
863
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
734
864
  project: NameSchema.nullable().default(null),
735
865
  topic: NameSchema,
736
- body: z4.string().min(1)
866
+ body: z5.string().min(1)
737
867
  }),
738
- remove_knowledge: z4.object({
868
+ remove_knowledge: z5.object({
739
869
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
740
870
  project: NameSchema.nullable().default(null),
741
871
  topic: NameSchema
742
872
  }),
743
- plan_task: z4.object({
873
+ plan_task: z5.object({
744
874
  task_id: UlidSchema,
745
- stages: z4.array(PlanEditStageSchema),
875
+ stages: z5.array(PlanEditStageSchema),
746
876
  on_done: CompletionEffectSchema.optional()
747
877
  }),
748
878
  /** The note is posted to the task's thread as the caller, marked with the step. */
749
- advance_task: z4.object({ task_id: UlidSchema, note: z4.string().min(1).optional() }),
750
- configure_project: z4.object({
879
+ advance_task: z5.object({ task_id: UlidSchema, note: z5.string().min(1).optional() }),
880
+ configure_project: z5.object({
751
881
  project: NameSchema,
752
882
  on_done: CompletionEffectSchema.optional(),
753
883
  /** A new display name; the slug, which everything refers to the project by, never changes. */
754
- name: z4.string().trim().min(1).max(120).optional(),
884
+ name: z5.string().trim().min(1).max(120).optional(),
755
885
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
756
886
  default_branch: DefaultBranchSchema.optional()
757
887
  }),
758
- archive_project: z4.object({ project: NameSchema, reason: z4.string().min(1) }),
759
- create_channel: z4.object({
888
+ archive_project: z5.object({ project: NameSchema, reason: z5.string().min(1) }),
889
+ create_channel: z5.object({
760
890
  /** A project, or null for a channel of the society. */
761
891
  project: NameSchema.nullable().default(null),
762
892
  name: NameSchema,
763
- purpose: z4.string().min(1)
893
+ purpose: z5.string().min(1)
764
894
  }),
765
- archive_channel: z4.object({ channel: ChannelRefSchema, reason: z4.string().min(1) }),
766
- update_dashboard: z4.object({
895
+ archive_channel: z5.object({ channel: ChannelRefSchema, reason: z5.string().min(1) }),
896
+ update_dashboard: z5.object({
767
897
  project: NameSchema,
768
898
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
769
- body: z4.string().min(1),
899
+ body: z5.string().min(1),
770
900
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
771
- revision: z4.number().int().min(0)
772
- })
901
+ revision: z5.number().int().min(0)
902
+ }),
903
+ create_cron: z5.object({
904
+ title: CronTitleSchema,
905
+ /** What to do when it fires; quoted in the prompt of every turn it starts. */
906
+ note: CronNoteSchema,
907
+ /** Minute, hour, day of month, month, day of week, such as `0 9 * * 1-5`; or `at` instead. */
908
+ cron: CronExpressionSchema.optional(),
909
+ /** The IANA time zone `cron` is read in; the society's when left out. */
910
+ timezone: TimeZoneSchema.optional(),
911
+ /** One time to fire, instead of `cron`; the cron ends once it has fired. */
912
+ at: MomentSchema.optional(),
913
+ /** The citizen it wakes: yourself when left out. */
914
+ agent: NameSchema.optional(),
915
+ /** Where it fires: a project the citizen belongs to, or `society`; this turn's scope when left out. */
916
+ project: NameSchema.optional(),
917
+ /** An open thread there, whose conversation it fires in. */
918
+ thread_id: UlidSchema.optional(),
919
+ /** An open channel of that place other than general, whose conversation it fires in. */
920
+ channel: NameSchema.optional()
921
+ }),
922
+ update_cron: z5.object({
923
+ cron_id: UlidSchema,
924
+ /** Pause it, or resume it, which starts its schedule afresh from now. */
925
+ paused: z5.boolean().optional(),
926
+ title: CronTitleSchema.optional(),
927
+ note: CronNoteSchema.optional(),
928
+ /** A new expression, or `at` for one time; either starts the schedule afresh. */
929
+ cron: CronExpressionSchema.optional(),
930
+ /** The zone `cron` is read in; null for the society's. */
931
+ timezone: TimeZoneSchema.nullable().optional(),
932
+ at: MomentSchema.optional()
933
+ }),
934
+ remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) })
773
935
  };
774
936
 
775
937
  // packages/shared/dist/events.js
776
- import { z as z5 } from "zod";
938
+ import { z as z6 } from "zod";
777
939
  var BOARD_EVENT_TYPES = [
778
940
  "society.initialized",
779
941
  "project.added",
@@ -828,30 +990,36 @@ var BOARD_EVENT_TYPES = [
828
990
  "merge.failed",
829
991
  "pull_request.closed",
830
992
  "pull_request.close_failed",
831
- "workspace.leftovers"
993
+ "workspace.leftovers",
994
+ "society.configured",
995
+ "cron.created",
996
+ "cron.updated",
997
+ "cron.fired",
998
+ "cron.skipped",
999
+ "cron.ended"
832
1000
  ];
833
- var BoardEventTypeSchema = z5.enum(BOARD_EVENT_TYPES);
834
- var BoardEventSchema = z5.object({
1001
+ var BoardEventTypeSchema = z6.enum(BOARD_EVENT_TYPES);
1002
+ var BoardEventSchema = z6.object({
835
1003
  id: UlidSchema,
836
1004
  ts: IsoDateTimeSchema,
837
1005
  type: BoardEventTypeSchema,
838
1006
  actor: NameSchema,
839
- payload: z5.record(z5.string(), z5.unknown())
840
- });
841
- var UsageSchema = z5.object({
842
- inputTokens: z5.number().int().nonnegative(),
843
- outputTokens: z5.number().int().nonnegative(),
844
- cacheReadTokens: z5.number().int().nonnegative().default(0),
845
- cacheWriteTokens: z5.number().int().nonnegative().default(0)
846
- });
847
- var TurnStatusSchema = z5.object({
848
- summary: z5.string().min(1),
849
- claimsHeld: z5.array(UlidSchema).default([]),
850
- blockedOn: z5.array(z5.string()).default([]),
851
- needsUserDecision: z5.boolean().default(false),
852
- memoryUpdated: z5.boolean().default(false)
853
- });
854
- var TurnExitReasonSchema = z5.enum([
1007
+ payload: z6.record(z6.string(), z6.unknown())
1008
+ });
1009
+ var UsageSchema = z6.object({
1010
+ inputTokens: z6.number().int().nonnegative(),
1011
+ outputTokens: z6.number().int().nonnegative(),
1012
+ cacheReadTokens: z6.number().int().nonnegative().default(0),
1013
+ cacheWriteTokens: z6.number().int().nonnegative().default(0)
1014
+ });
1015
+ var TurnStatusSchema = z6.object({
1016
+ summary: z6.string().min(1),
1017
+ claimsHeld: z6.array(UlidSchema).default([]),
1018
+ blockedOn: z6.array(z6.string()).default([]),
1019
+ needsUserDecision: z6.boolean().default(false),
1020
+ memoryUpdated: z6.boolean().default(false)
1021
+ });
1022
+ var TurnExitReasonSchema = z6.enum([
855
1023
  "completed",
856
1024
  "timeout",
857
1025
  "error",
@@ -859,50 +1027,50 @@ var TurnExitReasonSchema = z5.enum([
859
1027
  "blocked",
860
1028
  "stopped"
861
1029
  ]);
862
- var AgentEventSchema = z5.discriminatedUnion("type", [
863
- z5.object({
864
- type: z5.literal("turn_started"),
1030
+ var AgentEventSchema = z6.discriminatedUnion("type", [
1031
+ z6.object({
1032
+ type: z6.literal("turn_started"),
865
1033
  agent: NameSchema,
866
- session: z5.string(),
1034
+ session: z6.string(),
867
1035
  runner: NameSchema,
868
1036
  /** The model the CLI reports running, when it reports one. */
869
- model: z5.string().optional()
1037
+ model: z6.string().optional()
870
1038
  }),
871
1039
  /**
872
1040
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
873
1041
  * messages it delivered and the text the CLI was given.
874
1042
  */
875
- z5.object({
876
- type: z5.literal("steered"),
877
- steer: z5.string().min(1),
878
- messages: z5.array(UlidSchema).optional(),
879
- text: z5.string().optional()
1043
+ z6.object({
1044
+ type: z6.literal("steered"),
1045
+ steer: z6.string().min(1),
1046
+ messages: z6.array(UlidSchema).optional(),
1047
+ text: z6.string().optional()
880
1048
  }),
881
- z5.object({ type: z5.literal("text"), delta: z5.string() }),
882
- z5.object({ type: z5.literal("tool_call"), name: z5.string(), input: z5.unknown() }),
883
- z5.object({
884
- type: z5.literal("tool_result"),
885
- name: z5.string(),
886
- ok: z5.boolean(),
1049
+ z6.object({ type: z6.literal("text"), delta: z6.string() }),
1050
+ z6.object({ type: z6.literal("tool_call"), name: z6.string(), input: z6.unknown() }),
1051
+ z6.object({
1052
+ type: z6.literal("tool_result"),
1053
+ name: z6.string(),
1054
+ ok: z6.boolean(),
887
1055
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
888
- output: z5.string().optional()
1056
+ output: z6.string().optional()
889
1057
  }),
890
- z5.object({ type: z5.literal("approval_requested"), kind: z5.string(), detail: z5.unknown() }),
891
- z5.object({
892
- type: z5.literal("turn_completed"),
1058
+ z6.object({ type: z6.literal("approval_requested"), kind: z6.string(), detail: z6.unknown() }),
1059
+ z6.object({
1060
+ type: z6.literal("turn_completed"),
893
1061
  usage: UsageSchema,
894
- costUsd: z5.number().nonnegative(),
1062
+ costUsd: z6.number().nonnegative(),
895
1063
  status: TurnStatusSchema.nullable(),
896
1064
  exitReason: TurnExitReasonSchema
897
1065
  }),
898
- z5.object({ type: z5.literal("error"), message: z5.string() })
1066
+ z6.object({ type: z6.literal("error"), message: z6.string() })
899
1067
  ]);
900
- var TranscriptEntrySchema = z5.object({
1068
+ var TranscriptEntrySchema = z6.object({
901
1069
  ts: IsoDateTimeSchema,
902
1070
  event: AgentEventSchema
903
1071
  });
904
- var LiveTurnEventSchema = z5.object({
905
- seq: z5.number().int().positive(),
1072
+ var LiveTurnEventSchema = z6.object({
1073
+ seq: z6.number().int().positive(),
906
1074
  ts: IsoDateTimeSchema,
907
1075
  agent: NameSchema,
908
1076
  project: NameSchema,
@@ -914,97 +1082,97 @@ var LiveTurnEventSchema = z5.object({
914
1082
  });
915
1083
 
916
1084
  // packages/shared/dist/turns.js
917
- import { z as z7 } from "zod";
1085
+ import { z as z8 } from "zod";
918
1086
 
919
1087
  // packages/shared/dist/runner.js
920
- import { z as z6 } from "zod";
1088
+ import { z as z7 } from "zod";
921
1089
  var HOME_SCRATCH = "scratch";
922
1090
  var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
923
- var RunnerHelloSchema = z6.object({
924
- protocol: z6.number().int().positive(),
925
- version: z6.string().min(1),
1091
+ var RunnerHelloSchema = z7.object({
1092
+ protocol: z7.number().int().positive(),
1093
+ version: z7.string().min(1),
926
1094
  os: RunnerOsSchema,
927
- clis: z6.array(CliKindSchema),
1095
+ clis: z7.array(CliKindSchema),
928
1096
  /** The CLIs whose adapter can keep a session warm between turns. */
929
- residentClis: z6.array(CliKindSchema).default([]),
1097
+ residentClis: z7.array(CliKindSchema).default([]),
930
1098
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
931
- steerableClis: z6.array(CliKindSchema).default([]),
1099
+ steerableClis: z7.array(CliKindSchema).default([]),
932
1100
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
933
- stoppableClis: z6.array(CliKindSchema).default([]),
1101
+ stoppableClis: z7.array(CliKindSchema).default([]),
934
1102
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
935
- branchReads: z6.boolean().default(false),
1103
+ branchReads: z7.boolean().default(false),
936
1104
  /** Whether the runner lists what a branch changed; one from before never does. */
937
- branchChanges: z6.boolean().default(false),
938
- capabilities: z6.array(z6.string()).default([]),
1105
+ branchChanges: z7.boolean().default(false),
1106
+ capabilities: z7.array(z7.string()).default([]),
939
1107
  /** Turns the runner runs at once, or null for no limit. */
940
- slots: z6.number().int().positive().nullable(),
1108
+ slots: z7.number().int().positive().nullable(),
941
1109
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
942
- turns: z6.array(UlidSchema).default([])
1110
+ turns: z7.array(UlidSchema).default([])
943
1111
  });
944
- var RunnerWelcomeSchema = z6.object({ name: NameSchema, version: z6.string() });
945
- var RunnerEnrollRequestSchema = z6.object({
946
- protocol: z6.number().int().positive(),
947
- version: z6.string().min(1).max(64),
948
- hostname: z6.string().min(1).max(253),
1112
+ var RunnerWelcomeSchema = z7.object({ name: NameSchema, version: z7.string() });
1113
+ var RunnerEnrollRequestSchema = z7.object({
1114
+ protocol: z7.number().int().positive(),
1115
+ version: z7.string().min(1).max(64),
1116
+ hostname: z7.string().min(1).max(253),
949
1117
  os: RunnerOsSchema,
950
- clis: z6.array(CliKindSchema).max(8),
951
- capabilities: z6.array(z6.string().min(1).max(64)).max(32).default([])
1118
+ clis: z7.array(CliKindSchema).max(8),
1119
+ capabilities: z7.array(z7.string().min(1).max(64)).max(32).default([])
952
1120
  });
953
- var RunnerEnrollmentSchema = z6.object({
954
- deviceCode: z6.string().min(1),
955
- userCode: z6.string().min(1),
1121
+ var RunnerEnrollmentSchema = z7.object({
1122
+ deviceCode: z7.string().min(1),
1123
+ userCode: z7.string().min(1),
956
1124
  expiresAt: IsoDateTimeSchema,
957
- intervalMs: z6.number().int().positive()
1125
+ intervalMs: z7.number().int().positive()
958
1126
  });
959
- var RunnerEnrollPollSchema = z6.object({ deviceCode: z6.string().min(1) });
960
- var RunnerEnrollStatusSchema = z6.discriminatedUnion("status", [
961
- z6.object({ status: z6.literal("pending") }),
962
- z6.object({ status: z6.literal("approved"), name: NameSchema, token: z6.string().min(1) }),
963
- z6.object({ status: z6.literal("denied") })
1127
+ var RunnerEnrollPollSchema = z7.object({ deviceCode: z7.string().min(1) });
1128
+ var RunnerEnrollStatusSchema = z7.discriminatedUnion("status", [
1129
+ z7.object({ status: z7.literal("pending") }),
1130
+ z7.object({ status: z7.literal("approved"), name: NameSchema, token: z7.string().min(1) }),
1131
+ z7.object({ status: z7.literal("denied") })
964
1132
  ]);
965
- var PendingEnrollmentSchema = z6.object({
966
- userCode: z6.string().min(1),
967
- hostname: z6.string(),
1133
+ var PendingEnrollmentSchema = z7.object({
1134
+ userCode: z7.string().min(1),
1135
+ hostname: z7.string(),
968
1136
  os: RunnerOsSchema,
969
- clis: z6.array(CliKindSchema),
970
- capabilities: z6.array(z6.string()),
971
- version: z6.string(),
1137
+ clis: z7.array(CliKindSchema),
1138
+ capabilities: z7.array(z7.string()),
1139
+ version: z7.string(),
972
1140
  requestedAt: IsoDateTimeSchema,
973
1141
  expiresAt: IsoDateTimeSchema
974
1142
  });
975
- var ProjectRepoSchema = z6.object({
1143
+ var ProjectRepoSchema = z7.object({
976
1144
  slug: NameSchema,
977
- origin: z6.string().nullable(),
978
- defaultBranch: z6.string().min(1),
1145
+ origin: z7.string().nullable(),
1146
+ defaultBranch: z7.string().min(1),
979
1147
  /**
980
1148
  * The board lands finished tasks on the default branch, itself or through their pull requests,
981
1149
  * so the runner lets nothing else move it.
982
1150
  */
983
- boardLands: z6.boolean()
1151
+ boardLands: z7.boolean()
984
1152
  });
985
- var TurnWorkspaceSchema = z6.discriminatedUnion("kind", [
986
- z6.object({
987
- kind: z6.literal("home"),
1153
+ var TurnWorkspaceSchema = z7.discriminatedUnion("kind", [
1154
+ z7.object({
1155
+ kind: z7.literal("home"),
988
1156
  thread: UlidSchema.optional(),
989
1157
  channel: NameSchema.optional()
990
1158
  }),
991
- z6.object({
992
- kind: z6.literal("project"),
1159
+ z7.object({
1160
+ kind: z7.literal("project"),
993
1161
  repo: ProjectRepoSchema,
994
- branches: z6.array(z6.string().min(1)),
1162
+ branches: z7.array(z7.string().min(1)),
995
1163
  thread: UlidSchema.optional(),
996
1164
  channel: NameSchema.optional()
997
1165
  }),
998
- z6.object({ kind: z6.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1166
+ z7.object({ kind: z7.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
999
1167
  ]);
1000
- var TurnJobSchema = z6.object({
1168
+ var TurnJobSchema = z7.object({
1001
1169
  turnId: UlidSchema,
1002
1170
  agent: NameSchema,
1003
1171
  role: NameSchema,
1004
1172
  cli: CliKindSchema,
1005
- model: z6.string().optional(),
1173
+ model: z7.string().optional(),
1006
1174
  /** The reasoning effort set on the agent; absent for the model's own default. */
1007
- effort: z6.string().optional(),
1175
+ effort: z7.string().optional(),
1008
1176
  /** A project slug, or the society scope. */
1009
1177
  scope: NameSchema,
1010
1178
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1015,167 +1183,167 @@ var TurnJobSchema = z6.object({
1015
1183
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1016
1184
  * the outcome reports it.
1017
1185
  */
1018
- session: z6.string().min(1).nullable(),
1186
+ session: z7.string().min(1).nullable(),
1019
1187
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1020
- costSoFarUsd: z6.number().nonnegative(),
1188
+ costSoFarUsd: z7.number().nonnegative(),
1021
1189
  /** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
1022
- instructions: z6.string(),
1023
- prompt: z6.string(),
1024
- mcp: z6.object({ url: z6.string().min(1), token: z6.string().min(1) }),
1025
- limits: z6.object({
1026
- timeoutMs: z6.number().int().positive().nullable(),
1027
- maxTurns: z6.number().int().positive().nullable()
1190
+ instructions: z7.string(),
1191
+ prompt: z7.string(),
1192
+ mcp: z7.object({ url: z7.string().min(1), token: z7.string().min(1) }),
1193
+ limits: z7.object({
1194
+ timeoutMs: z7.number().int().positive().nullable(),
1195
+ maxTurns: z7.number().int().positive().nullable()
1028
1196
  }),
1029
1197
  /** Present when the runner keeps the conversation's session warm between turns, under `key`. */
1030
- resident: z6.object({ key: z6.string().min(1), idleMs: z6.number().int().positive() }).optional(),
1198
+ resident: z7.object({ key: z7.string().min(1), idleMs: z7.number().int().positive() }).optional(),
1031
1199
  workspace: TurnWorkspaceSchema
1032
1200
  });
1033
- var TurnWorkSchema = z6.object({ branch: z6.string().min(1), head: z6.string().min(1) });
1034
- var TurnEventsSchema = z6.object({ entries: z6.array(TranscriptEntrySchema) });
1035
- var TurnOutcomeSchema = z6.object({
1201
+ var TurnWorkSchema = z7.object({ branch: z7.string().min(1), head: z7.string().min(1) });
1202
+ var TurnEventsSchema = z7.object({ entries: z7.array(TranscriptEntrySchema) });
1203
+ var TurnOutcomeSchema = z7.object({
1036
1204
  exitReason: TurnExitReasonSchema,
1037
1205
  status: TurnStatusSchema.nullable(),
1038
- error: z6.string().nullable(),
1206
+ error: z7.string().nullable(),
1039
1207
  usage: UsageSchema,
1040
- costUsd: z6.number().nonnegative(),
1208
+ costUsd: z7.number().nonnegative(),
1041
1209
  /** The session's running total after this turn, for CLIs that report one. */
1042
- sessionCostUsd: z6.number().nonnegative().optional(),
1210
+ sessionCostUsd: z7.number().nonnegative().optional(),
1043
1211
  /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1044
- session: z6.string().min(1),
1212
+ session: z7.string().min(1),
1045
1213
  /** The model the CLI reported, when it reported one. */
1046
- model: z6.string().nullable(),
1214
+ model: z7.string().nullable(),
1047
1215
  work: TurnWorkSchema.nullable(),
1048
1216
  /**
1049
1217
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1050
1218
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1051
1219
  */
1052
- leftovers: z6.boolean().default(false)
1220
+ leftovers: z7.boolean().default(false)
1053
1221
  });
1054
- var TurnAckSchema = z6.object({ dropWorkspace: z6.boolean() });
1055
- var LandRequestSchema = z6.object({
1222
+ var TurnAckSchema = z7.object({ dropWorkspace: z7.boolean() });
1223
+ var LandRequestSchema = z7.object({
1056
1224
  repo: ProjectRepoSchema,
1057
- branch: z6.string().min(1),
1225
+ branch: z7.string().min(1),
1058
1226
  /**
1059
1227
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1060
1228
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1061
1229
  * empty body when left out.
1062
1230
  */
1063
- pullRequest: z6.object({
1064
- url: z6.string().min(1),
1065
- subject: z6.string().min(1).optional(),
1066
- body: z6.string().optional()
1231
+ pullRequest: z7.object({
1232
+ url: z7.string().min(1),
1233
+ subject: z7.string().min(1).optional(),
1234
+ body: z7.string().optional()
1067
1235
  }).optional()
1068
1236
  });
1069
- var CloseRequestSchema = z6.object({
1237
+ var CloseRequestSchema = z7.object({
1070
1238
  repo: ProjectRepoSchema,
1071
- url: z6.string().min(1),
1072
- comment: z6.string().min(1)
1239
+ url: z7.string().min(1),
1240
+ comment: z7.string().min(1)
1073
1241
  });
1074
- var MergeOutcomeSchema = z6.object({ ok: z6.boolean(), detail: z6.string() });
1075
- var ModelListSchema = z6.array(ModelOptionSchema);
1242
+ var MergeOutcomeSchema = z7.object({ ok: z7.boolean(), detail: z7.string() });
1243
+ var ModelListSchema = z7.array(ModelOptionSchema);
1076
1244
  var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1077
- var BranchPathSchema = z6.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
1078
- var BranchNameSchema = z6.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1079
- var BranchReadSchema = z6.object({
1245
+ var BranchPathSchema = z7.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
1246
+ var BranchNameSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1247
+ var BranchReadSchema = z7.object({
1080
1248
  repo: ProjectRepoSchema,
1081
1249
  branch: BranchNameSchema,
1082
1250
  path: BranchPathSchema
1083
1251
  });
1084
- var BranchCommitSchema = z6.object({
1085
- id: z6.string().min(1),
1252
+ var BranchCommitSchema = z7.object({
1253
+ id: z7.string().min(1),
1086
1254
  at: IsoDateTimeSchema,
1087
- author: z6.string()
1255
+ author: z7.string()
1088
1256
  });
1089
- var BranchFileSchema = z6.discriminatedUnion("kind", [
1090
- z6.object({
1091
- kind: z6.literal("file"),
1257
+ var BranchFileSchema = z7.discriminatedUnion("kind", [
1258
+ z7.object({
1259
+ kind: z7.literal("file"),
1092
1260
  path: BranchPathSchema,
1093
1261
  commit: BranchCommitSchema,
1094
- size: z6.number().int().nonnegative(),
1095
- content: z6.string().nullable()
1262
+ size: z7.number().int().nonnegative(),
1263
+ content: z7.string().nullable()
1096
1264
  }),
1097
- z6.object({
1098
- kind: z6.literal("dir"),
1265
+ z7.object({
1266
+ kind: z7.literal("dir"),
1099
1267
  path: BranchPathSchema,
1100
1268
  commit: BranchCommitSchema,
1101
- entries: z6.array(z6.object({
1102
- name: z6.string().min(1),
1103
- kind: z6.enum(["file", "dir"]),
1104
- size: z6.number().int().nonnegative().nullable()
1269
+ entries: z7.array(z7.object({
1270
+ name: z7.string().min(1),
1271
+ kind: z7.enum(["file", "dir"]),
1272
+ size: z7.number().int().nonnegative().nullable()
1105
1273
  }))
1106
1274
  })
1107
1275
  ]);
1108
- var BranchChangesReadSchema = z6.object({
1276
+ var BranchChangesReadSchema = z7.object({
1109
1277
  repo: ProjectRepoSchema,
1110
1278
  branch: BranchNameSchema
1111
1279
  });
1112
- var BranchFileChangeSchema = z6.object({
1113
- path: z6.string().min(1),
1114
- status: z6.enum(["added", "modified", "deleted"]),
1280
+ var BranchFileChangeSchema = z7.object({
1281
+ path: z7.string().min(1),
1282
+ status: z7.enum(["added", "modified", "deleted"]),
1115
1283
  /** Lines added and removed, or null for a binary file. */
1116
- added: z6.number().int().nonnegative().nullable(),
1117
- removed: z6.number().int().nonnegative().nullable(),
1284
+ added: z7.number().int().nonnegative().nullable(),
1285
+ removed: z7.number().int().nonnegative().nullable(),
1118
1286
  /** Absent when the file changed only through a merge of another branch. */
1119
- lastChange: z6.object({ author: z6.string(), at: IsoDateTimeSchema }).optional()
1287
+ lastChange: z7.object({ author: z7.string(), at: IsoDateTimeSchema }).optional()
1120
1288
  });
1121
- var BranchChangesSchema = z6.object({
1289
+ var BranchChangesSchema = z7.object({
1122
1290
  head: BranchCommitSchema,
1123
- files: z6.array(BranchFileChangeSchema),
1124
- total: z6.number().int().nonnegative()
1291
+ files: z7.array(BranchFileChangeSchema),
1292
+ total: z7.number().int().nonnegative()
1125
1293
  });
1126
- var WorkspaceConversationSchema = z6.discriminatedUnion("kind", [
1127
- z6.object({ kind: z6.literal("thread"), id: UlidSchema }),
1128
- z6.object({ kind: z6.literal("channel"), scope: NameSchema, name: NameSchema })
1294
+ var WorkspaceConversationSchema = z7.discriminatedUnion("kind", [
1295
+ z7.object({ kind: z7.literal("thread"), id: UlidSchema }),
1296
+ z7.object({ kind: z7.literal("channel"), scope: NameSchema, name: NameSchema })
1129
1297
  ]);
1130
- var HeldWorkspaceSchema = z6.object({
1298
+ var HeldWorkspaceSchema = z7.object({
1131
1299
  agent: NameSchema,
1132
1300
  scope: NameSchema,
1133
1301
  conversation: WorkspaceConversationSchema
1134
1302
  });
1135
- var HeldWorkspacesSchema = z6.object({ workspaces: z6.array(HeldWorkspaceSchema) });
1136
- var SweepResultSchema = z6.object({ kept: z6.array(HeldWorkspaceSchema) });
1137
- var TurnSteerSchema = z6.object({ id: z6.uuid(), text: z6.string().min(1) });
1138
- var RunnerMessageSchema = z6.discriminatedUnion("type", [
1139
- z6.object({ type: z6.literal("turn"), job: TurnJobSchema }),
1140
- z6.object({ type: z6.literal("land"), request: z6.string().min(1), land: LandRequestSchema }),
1303
+ var HeldWorkspacesSchema = z7.object({ workspaces: z7.array(HeldWorkspaceSchema) });
1304
+ var SweepResultSchema = z7.object({ kept: z7.array(HeldWorkspaceSchema) });
1305
+ var TurnSteerSchema = z7.object({ id: z7.uuid(), text: z7.string().min(1) });
1306
+ var RunnerMessageSchema = z7.discriminatedUnion("type", [
1307
+ z7.object({ type: z7.literal("turn"), job: TurnJobSchema }),
1308
+ z7.object({ type: z7.literal("land"), request: z7.string().min(1), land: LandRequestSchema }),
1141
1309
  /** Answered with a `MergeOutcome`. */
1142
- z6.object({ type: z6.literal("close"), request: z6.string().min(1), close: CloseRequestSchema }),
1143
- z6.object({ type: z6.literal("models"), request: z6.string().min(1), cli: CliKindSchema }),
1310
+ z7.object({ type: z7.literal("close"), request: z7.string().min(1), close: CloseRequestSchema }),
1311
+ z7.object({ type: z7.literal("models"), request: z7.string().min(1), cli: CliKindSchema }),
1144
1312
  /** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
1145
- z6.object({ type: z6.literal("file"), request: z6.string().min(1), read: BranchReadSchema }),
1313
+ z7.object({ type: z7.literal("file"), request: z7.string().min(1), read: BranchReadSchema }),
1146
1314
  /** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
1147
- z6.object({
1148
- type: z6.literal("changes"),
1149
- request: z6.string().min(1),
1315
+ z7.object({
1316
+ type: z7.literal("changes"),
1317
+ request: z7.string().min(1),
1150
1318
  read: BranchChangesReadSchema
1151
1319
  }),
1152
1320
  /** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
1153
- z6.object({
1154
- type: z6.literal("steer"),
1155
- request: z6.string().min(1),
1321
+ z7.object({
1322
+ type: z7.literal("steer"),
1323
+ request: z7.string().min(1),
1156
1324
  turnId: UlidSchema,
1157
1325
  steer: TurnSteerSchema
1158
1326
  }),
1159
1327
  /** Answered with whether the turn was there to stop; it ends `stopped`. */
1160
- z6.object({ type: z6.literal("stop"), request: z6.string().min(1), turnId: UlidSchema }),
1328
+ z7.object({ type: z7.literal("stop"), request: z7.string().min(1), turnId: UlidSchema }),
1161
1329
  /**
1162
1330
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1163
1331
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1164
1332
  */
1165
- z6.object({
1166
- type: z6.literal("sweep"),
1167
- request: z6.string().min(1),
1333
+ z7.object({
1334
+ type: z7.literal("sweep"),
1335
+ request: z7.string().min(1),
1168
1336
  conversation: WorkspaceConversationSchema
1169
1337
  })
1170
1338
  ]);
1171
- var RunnerAnswerSchema = z6.discriminatedUnion("ok", [
1172
- z6.object({ ok: z6.literal(true), value: z6.unknown() }),
1173
- z6.object({ ok: z6.literal(false), error: z6.string() })
1339
+ var RunnerAnswerSchema = z7.discriminatedUnion("ok", [
1340
+ z7.object({ ok: z7.literal(true), value: z7.unknown() }),
1341
+ z7.object({ ok: z7.literal(false), error: z7.string() })
1174
1342
  ]);
1175
- var WarmSessionsSchema = z6.object({ warm: z6.array(z6.string()) });
1176
- var FileManifestSchema = z6.object({ files: z6.record(z6.string(), z6.string()) });
1177
- var FileReadSchema = z6.object({ paths: z6.array(z6.string()) });
1178
- var FileContentsSchema = z6.object({ files: z6.record(z6.string(), z6.string()) });
1343
+ var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
1344
+ var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1345
+ var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
1346
+ var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1179
1347
  function isSafeRelativePath(file) {
1180
1348
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1181
1349
  return false;
@@ -1184,7 +1352,7 @@ function isSafeRelativePath(file) {
1184
1352
  }
1185
1353
 
1186
1354
  // packages/shared/dist/turns.js
1187
- var TriggerKindSchema = z7.enum([
1355
+ var TriggerKindSchema = z8.enum([
1188
1356
  "mention",
1189
1357
  "stage",
1190
1358
  "task_done",
@@ -1195,27 +1363,32 @@ var TriggerKindSchema = z7.enum([
1195
1363
  "manual",
1196
1364
  "ops_event",
1197
1365
  "user_post",
1198
- "closing"
1366
+ "closing",
1367
+ "cron"
1199
1368
  ]);
1200
- var TriggerSchema = z7.object({
1369
+ var TriggerSchema = z8.object({
1201
1370
  kind: TriggerKindSchema,
1202
1371
  from: NameSchema.optional(),
1203
- fromUser: z7.boolean().default(false),
1204
- reason: z7.string().default(""),
1372
+ fromUser: z8.boolean().default(false),
1373
+ reason: z8.string().default(""),
1205
1374
  taskId: UlidSchema.optional(),
1206
1375
  messageId: UlidSchema.optional(),
1207
1376
  /** The channel the waking message was posted in. */
1208
- channel: ChannelRefSchema.optional()
1377
+ channel: ChannelRefSchema.optional(),
1378
+ /** The cron that came due, for a `cron` wake. */
1379
+ cronId: UlidSchema.optional()
1209
1380
  });
1210
- var TurnThreadSchema = z7.object({ id: UlidSchema, task: z7.boolean() });
1211
- var TurnDispatchSchema = z7.object({
1381
+ var TurnThreadSchema = z8.object({ id: UlidSchema, task: z8.boolean() });
1382
+ var TurnDispatchSchema = z8.object({
1212
1383
  agent: NameSchema,
1213
1384
  project: NameSchema,
1214
1385
  thread: TurnThreadSchema.optional(),
1215
1386
  channel: NameSchema.optional(),
1216
1387
  trigger: TriggerSchema,
1217
- priority: z7.number().int().min(0).max(2),
1218
- onboarding: z7.boolean().default(false)
1388
+ priority: z8.number().int().min(0).max(2),
1389
+ onboarding: z8.boolean().default(false),
1390
+ /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1391
+ crons: z8.array(UlidSchema).optional()
1219
1392
  });
1220
1393
  function conversationPart(conversation) {
1221
1394
  return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
@@ -1226,7 +1399,7 @@ function channelConversation(scope, channel) {
1226
1399
  const name = at < 0 ? channel : channel.slice(at + 1);
1227
1400
  return place === scope && name !== "general" ? name : void 0;
1228
1401
  }
1229
- var TurnRecordSchema = z7.object({
1402
+ var TurnRecordSchema = z8.object({
1230
1403
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1231
1404
  id: UlidSchema.optional(),
1232
1405
  agent: NameSchema,
@@ -1237,29 +1410,31 @@ var TurnRecordSchema = z7.object({
1237
1410
  channel: NameSchema.optional(),
1238
1411
  runner: NameSchema,
1239
1412
  cli: CliKindSchema.nullable(),
1240
- session: z7.string().nullable(),
1413
+ session: z8.string().nullable(),
1241
1414
  trigger: TriggerSchema,
1242
1415
  startedAt: IsoDateTimeSchema,
1243
1416
  endedAt: IsoDateTimeSchema.nullable(),
1244
1417
  exitReason: TurnExitReasonSchema.nullable(),
1245
1418
  status: TurnStatusSchema.nullable(),
1246
- error: z7.string().nullable(),
1419
+ error: z8.string().nullable(),
1247
1420
  usage: UsageSchema.nullable(),
1248
- costUsd: z7.number().nonnegative().default(0),
1421
+ costUsd: z8.number().nonnegative().default(0),
1249
1422
  /**
1250
1423
  * The running total the CLI reported for the session after this turn, when it reports one. A
1251
1424
  * resumed session starts from it, so the next turn's own cost is measured from here.
1252
1425
  */
1253
- sessionCostUsd: z7.number().nonnegative().optional(),
1254
- toolCalls: z7.number().int().nonnegative().default(0),
1426
+ sessionCostUsd: z8.number().nonnegative().optional(),
1427
+ toolCalls: z8.number().int().nonnegative().default(0),
1255
1428
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1256
- model: z7.string().nullable().default(null),
1429
+ model: z8.string().nullable().default(null),
1257
1430
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1258
1431
  work: TurnWorkSchema.optional(),
1259
1432
  /** Who stopped the turn, for one that ended `stopped`. */
1260
- stoppedBy: NameSchema.optional()
1433
+ stoppedBy: NameSchema.optional(),
1434
+ /** The crons whose fire started the turn, which record how it ended. */
1435
+ crons: z8.array(UlidSchema).optional()
1261
1436
  });
1262
- var RunningTurnSchema = z7.object({
1437
+ var RunningTurnSchema = z8.object({
1263
1438
  turnId: UlidSchema,
1264
1439
  agent: NameSchema,
1265
1440
  scope: NameSchema,
@@ -1267,49 +1442,49 @@ var RunningTurnSchema = z7.object({
1267
1442
  channel: NameSchema.optional(),
1268
1443
  cli: CliKindSchema,
1269
1444
  askedIn: ChannelRefSchema.optional(),
1270
- steerable: z7.boolean(),
1271
- stoppable: z7.boolean()
1445
+ steerable: z8.boolean(),
1446
+ stoppable: z8.boolean()
1272
1447
  });
1273
- var SessionsFileSchema = z7.object({
1274
- claude: z7.string().optional(),
1275
- codex: z7.string().optional(),
1448
+ var SessionsFileSchema = z8.object({
1449
+ claude: z8.string().optional(),
1450
+ codex: z8.string().optional(),
1276
1451
  runner: NameSchema.optional()
1277
1452
  });
1278
- var WakeRequestSchema = z7.object({
1453
+ var WakeRequestSchema = z8.object({
1279
1454
  agent: NameSchema,
1280
1455
  project: NameSchema,
1281
- reason: z7.string().default("manual wake"),
1456
+ reason: z8.string().default("manual wake"),
1282
1457
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1283
- kind: z7.enum(["manual", "reflection"]).default("manual")
1458
+ kind: z8.enum(["manual", "reflection"]).default("manual")
1284
1459
  });
1285
- var TurnHistoryEntrySchema = z7.object({
1460
+ var TurnHistoryEntrySchema = z8.object({
1286
1461
  /** The id of the event that ended the turn. */
1287
1462
  id: UlidSchema,
1288
1463
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1289
1464
  turnId: UlidSchema.optional(),
1290
1465
  ts: IsoDateTimeSchema,
1291
- outcome: z7.enum(["completed", "failed"]),
1466
+ outcome: z8.enum(["completed", "failed"]),
1292
1467
  /** A project slug, or the society scope. */
1293
1468
  project: NameSchema,
1294
1469
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1295
1470
  thread: UlidSchema.optional(),
1296
1471
  /** The channel, never general, whose conversation the turn was in. */
1297
1472
  channel: NameSchema.optional(),
1298
- trigger: z7.string(),
1299
- exitReason: z7.string().nullable(),
1300
- costUsd: z7.number(),
1473
+ trigger: z8.string(),
1474
+ exitReason: z8.string().nullable(),
1475
+ costUsd: z8.number(),
1301
1476
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1302
1477
  usage: UsageSchema.optional(),
1303
- model: z7.string().nullable(),
1304
- summary: z7.string().nullable(),
1305
- error: z7.string().nullable(),
1478
+ model: z8.string().nullable(),
1479
+ summary: z8.string().nullable(),
1480
+ error: z8.string().nullable(),
1306
1481
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1307
1482
  startedAt: IsoDateTimeSchema.optional(),
1308
- toolCalls: z7.number().int().nonnegative().optional()
1483
+ toolCalls: z8.number().int().nonnegative().optional()
1309
1484
  });
1310
1485
 
1311
1486
  // packages/shared/dist/ops.js
1312
- import { z as z8 } from "zod";
1487
+ import { z as z9 } from "zod";
1313
1488
  var OPS_SIGNAL_KINDS = [
1314
1489
  "waiting_stage",
1315
1490
  "backlog",
@@ -1322,18 +1497,19 @@ var OPS_SIGNAL_KINDS = [
1322
1497
  "scaled",
1323
1498
  "runner",
1324
1499
  "home_conflict",
1325
- "stuck_workspace"
1500
+ "stuck_workspace",
1501
+ "cron_failing"
1326
1502
  ];
1327
- var OpsSignalKindSchema = z8.enum(OPS_SIGNAL_KINDS);
1328
- var OpsSignalSchema = z8.object({
1503
+ var OpsSignalKindSchema = z9.enum(OPS_SIGNAL_KINDS);
1504
+ var OpsSignalSchema = z9.object({
1329
1505
  kind: OpsSignalKindSchema,
1330
1506
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1331
- key: z8.string().min(1),
1507
+ key: z9.string().min(1),
1332
1508
  /** One line a reader can act on. */
1333
- summary: z8.string().min(1),
1509
+ summary: z9.string().min(1),
1334
1510
  /** The counter or timer value behind the signal. */
1335
- value: z8.number(),
1336
- threshold: z8.number().optional(),
1511
+ value: z9.number(),
1512
+ threshold: z9.number().optional(),
1337
1513
  project: NameSchema.optional(),
1338
1514
  agent: NameSchema.optional(),
1339
1515
  role: NameSchema.optional(),
@@ -1341,8 +1517,8 @@ var OpsSignalSchema = z8.object({
1341
1517
  });
1342
1518
 
1343
1519
  // packages/shared/dist/metrics.js
1344
- import { z as z9 } from "zod";
1345
- var MetricsWindowSchema = z9.enum(["24h", "7d", "all"]);
1520
+ import { z as z10 } from "zod";
1521
+ var MetricsWindowSchema = z10.enum(["24h", "7d", "all"]);
1346
1522
  var METRICS_WINDOW_MS = {
1347
1523
  "24h": 24 * 36e5,
1348
1524
  "7d": 7 * 24 * 36e5,
@@ -1353,9 +1529,9 @@ function isBoardAction(tool) {
1353
1529
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1354
1530
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1355
1531
  }
1356
- var CountSchema = z9.number().int().nonnegative();
1357
- var MsSchema = z9.number().nonnegative();
1358
- var MetricsSchema = z9.object({
1532
+ var CountSchema = z10.number().int().nonnegative();
1533
+ var MsSchema = z10.number().nonnegative();
1534
+ var MetricsSchema = z10.object({
1359
1535
  window: MetricsWindowSchema,
1360
1536
  since: IsoDateTimeSchema.nullable(),
1361
1537
  until: IsoDateTimeSchema,
@@ -1363,12 +1539,12 @@ var MetricsSchema = z9.object({
1363
1539
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1364
1540
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1365
1541
  */
1366
- idle: z9.object({
1542
+ idle: z10.object({
1367
1543
  turns: CountSchema,
1368
1544
  idle: CountSchema,
1369
1545
  unknown: CountSchema,
1370
- byTrigger: z9.array(z9.object({ trigger: z9.string(), turns: CountSchema, idle: CountSchema })),
1371
- byAgent: z9.array(z9.object({
1546
+ byTrigger: z10.array(z10.object({ trigger: z10.string(), turns: CountSchema, idle: CountSchema })),
1547
+ byAgent: z10.array(z10.object({
1372
1548
  agent: NameSchema,
1373
1549
  role: NameSchema.nullable(),
1374
1550
  turns: CountSchema,
@@ -1376,33 +1552,33 @@ var MetricsSchema = z9.object({
1376
1552
  }))
1377
1553
  }),
1378
1554
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1379
- sentBack: z9.object({
1555
+ sentBack: z10.object({
1380
1556
  finished: CountSchema,
1381
1557
  sendBacks: CountSchema,
1382
1558
  tasksSentBack: CountSchema,
1383
- byStage: z9.array(z9.object({ project: NameSchema, stage: z9.string(), count: CountSchema })),
1384
- bySender: z9.array(z9.object({ agent: NameSchema, count: CountSchema }))
1559
+ byStage: z10.array(z10.object({ project: NameSchema, stage: z10.string(), count: CountSchema })),
1560
+ bySender: z10.array(z10.object({ agent: NameSchema, count: CountSchema }))
1385
1561
  }),
1386
1562
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1387
- messages: z9.object({
1563
+ messages: z10.object({
1388
1564
  finished: CountSchema,
1389
1565
  messages: CountSchema,
1390
- byProject: z9.array(z9.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1391
- busiest: z9.array(z9.object({
1566
+ byProject: z10.array(z10.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1567
+ busiest: z10.array(z10.object({
1392
1568
  taskId: UlidSchema,
1393
- title: z9.string(),
1569
+ title: z10.string(),
1394
1570
  project: NameSchema,
1395
1571
  messages: CountSchema
1396
1572
  }))
1397
1573
  }),
1398
1574
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1399
- latency: z9.object({
1575
+ latency: z10.object({
1400
1576
  mentions: CountSchema,
1401
1577
  /** Mentions no turn has followed yet. */
1402
1578
  unanswered: CountSchema,
1403
1579
  medianMs: MsSchema.nullable(),
1404
1580
  slowestMs: MsSchema.nullable(),
1405
- byAgent: z9.array(z9.object({
1581
+ byAgent: z10.array(z10.object({
1406
1582
  agent: NameSchema,
1407
1583
  mentions: CountSchema,
1408
1584
  medianMs: MsSchema,
@@ -1410,81 +1586,81 @@ var MetricsSchema = z9.object({
1410
1586
  }))
1411
1587
  }),
1412
1588
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1413
- decisions: z9.object({
1589
+ decisions: z10.object({
1414
1590
  total: CountSchema,
1415
- byDay: z9.array(z9.object({
1416
- day: z9.string(),
1591
+ byDay: z10.array(z10.object({
1592
+ day: z10.string(),
1417
1593
  proposals: CountSchema,
1418
1594
  answers: CountSchema,
1419
1595
  stages: CountSchema
1420
1596
  }))
1421
1597
  }),
1422
1598
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1423
- blocked: z9.array(z9.object({
1599
+ blocked: z10.array(z10.object({
1424
1600
  taskId: UlidSchema,
1425
- title: z9.string().nullable(),
1601
+ title: z10.string().nullable(),
1426
1602
  project: NameSchema.nullable(),
1427
- summary: z9.string(),
1603
+ summary: z10.string(),
1428
1604
  firstAt: IsoDateTimeSchema,
1429
1605
  lastAt: IsoDateTimeSchema,
1430
- holds: z9.boolean()
1606
+ holds: z10.boolean()
1431
1607
  }))
1432
1608
  });
1433
1609
 
1434
1610
  // packages/shared/dist/config.js
1435
- import { z as z10 } from "zod";
1436
- var LogLevelSchema = z10.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1437
- var GithubSignInSchema = z10.object({
1438
- clientId: z10.string().min(1),
1439
- clientSecret: z10.string().min(1),
1611
+ import { z as z11 } from "zod";
1612
+ var LogLevelSchema = z11.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1613
+ var GithubSignInSchema = z11.object({
1614
+ clientId: z11.string().min(1),
1615
+ clientSecret: z11.string().min(1),
1440
1616
  /** Logins, lowercased; GitHub logins are case-insensitive. */
1441
- users: z10.array(z10.string().min(1)).min(1)
1617
+ users: z11.array(z11.string().min(1)).min(1)
1442
1618
  });
1443
- var ServerConfigSchema = z10.object({
1444
- dataDir: z10.string().min(1),
1445
- host: z10.string().min(1),
1446
- port: z10.number().int().positive().max(65535),
1619
+ var ServerConfigSchema = z11.object({
1620
+ dataDir: z11.string().min(1),
1621
+ host: z11.string().min(1),
1622
+ port: z11.number().int().positive().max(65535),
1447
1623
  /**
1448
1624
  * An address every runner's agents reach this server at, for example https://board.example, or
1449
1625
  * null for each runner to use the address it reaches the server at itself.
1450
1626
  */
1451
- publicUrl: z10.string().min(1).nullable(),
1627
+ publicUrl: z11.string().min(1).nullable(),
1452
1628
  logLevel: LogLevelSchema,
1453
1629
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
1454
- concurrency: z10.number().int().positive().nullable(),
1630
+ concurrency: z11.number().int().positive().nullable(),
1455
1631
  /** How long one turn may run before it is stopped, or null for no limit. */
1456
- turnTimeoutMs: z10.number().int().positive().nullable(),
1632
+ turnTimeoutMs: z11.number().int().positive().nullable(),
1457
1633
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1458
- toolRounds: z10.number().int().positive().nullable(),
1634
+ toolRounds: z11.number().int().positive().nullable(),
1459
1635
  /** How long a resident session stays warm after its last turn. */
1460
- residentIdleMs: z10.number().int().positive(),
1636
+ residentIdleMs: z11.number().int().positive(),
1461
1637
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1462
1638
  github: GithubSignInSchema.nullable(),
1463
1639
  /**
1464
1640
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1465
1641
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1466
1642
  */
1467
- trustedProxies: z10.number().int().min(0).max(16)
1643
+ trustedProxies: z11.number().int().min(0).max(16)
1468
1644
  });
1469
- var RunnerConfigSchema = z10.object({
1645
+ var RunnerConfigSchema = z11.object({
1470
1646
  /** The board server, for example http://127.0.0.1:4700. */
1471
- serverUrl: z10.string().url(),
1647
+ serverUrl: z11.string().url(),
1472
1648
  /**
1473
1649
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1474
1650
  * to enroll it.
1475
1651
  */
1476
- token: z10.string().min(1).nullable(),
1652
+ token: z11.string().min(1).nullable(),
1477
1653
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1478
- dataDir: z10.string().min(1),
1654
+ dataDir: z11.string().min(1),
1479
1655
  logLevel: LogLevelSchema,
1480
1656
  /** Turns this machine runs at once, or null for no limit. */
1481
- slots: z10.number().int().positive().nullable(),
1482
- clis: z10.array(CliKindSchema).min(1),
1483
- capabilities: z10.array(z10.string())
1657
+ slots: z11.number().int().positive().nullable(),
1658
+ clis: z11.array(CliKindSchema).min(1),
1659
+ capabilities: z11.array(z11.string())
1484
1660
  });
1485
1661
 
1486
1662
  // packages/shared/dist/version.js
1487
- var STELLARIS_VERSION = "0.3.13";
1663
+ var STELLARIS_VERSION = "0.3.14";
1488
1664
 
1489
1665
  // packages/board-core/dist/errors.js
1490
1666
  var BoardError = class extends Error {
@@ -2029,32 +2205,32 @@ function filesOf(lines, status) {
2029
2205
  }
2030
2206
 
2031
2207
  // packages/board-core/dist/metrics.js
2032
- import { z as z11 } from "zod";
2033
- var TurnPayload = z11.object({
2034
- turnId: z11.string().optional(),
2035
- project: z11.string(),
2036
- thread: z11.string().optional(),
2037
- channel: z11.string().optional(),
2038
- trigger: z11.string()
2039
- });
2040
- var TaskPayload = z11.object({
2041
- taskId: z11.string(),
2042
- project: z11.string(),
2043
- from: z11.string().optional()
2044
- });
2045
- var MessagePayload = z11.object({
2046
- id: z11.string().optional(),
2047
- channel: z11.string(),
2048
- thread: z11.string().nullable().optional(),
2049
- mentions: z11.array(z11.string()).default([])
2050
- });
2051
- var SteeredPayload = z11.object({ messages: z11.array(z11.string()) });
2052
- var SignalPayload = z11.object({
2053
- kind: z11.string(),
2054
- key: z11.string(),
2055
- summary: z11.string(),
2056
- taskId: z11.string().optional(),
2057
- project: z11.string().optional()
2208
+ import { z as z12 } from "zod";
2209
+ var TurnPayload = z12.object({
2210
+ turnId: z12.string().optional(),
2211
+ project: z12.string(),
2212
+ thread: z12.string().optional(),
2213
+ channel: z12.string().optional(),
2214
+ trigger: z12.string()
2215
+ });
2216
+ var TaskPayload = z12.object({
2217
+ taskId: z12.string(),
2218
+ project: z12.string(),
2219
+ from: z12.string().optional()
2220
+ });
2221
+ var MessagePayload = z12.object({
2222
+ id: z12.string().optional(),
2223
+ channel: z12.string(),
2224
+ thread: z12.string().nullable().optional(),
2225
+ mentions: z12.array(z12.string()).default([])
2226
+ });
2227
+ var SteeredPayload = z12.object({ messages: z12.array(z12.string()) });
2228
+ var SignalPayload = z12.object({
2229
+ kind: z12.string(),
2230
+ key: z12.string(),
2231
+ summary: z12.string(),
2232
+ taskId: z12.string().optional(),
2233
+ project: z12.string().optional()
2058
2234
  });
2059
2235
  function median(values) {
2060
2236
  const sorted = values.toSorted((a, b) => a - b);
@@ -2423,6 +2599,13 @@ var BoardPaths = class {
2423
2599
  members() {
2424
2600
  return path5.join(this.society, "members");
2425
2601
  }
2602
+ /** Crons, one file each with its note as the body; ended ones stay. */
2603
+ crons() {
2604
+ return path5.join(this.society, "crons");
2605
+ }
2606
+ cron(id) {
2607
+ return path5.join(this.crons(), `${id}.md`);
2608
+ }
2426
2609
  member(name) {
2427
2610
  return path5.join(this.members(), `${name}.md`);
2428
2611
  }
@@ -2569,41 +2752,41 @@ function namesList(names) {
2569
2752
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
2570
2753
  }
2571
2754
  var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
2572
- var RunnerTokensSchema = z12.object({
2573
- runners: z12.record(z12.string(), z12.object({ tokenHash: z12.string().min(1) }))
2755
+ var RunnerTokensSchema = z13.object({
2756
+ runners: z13.record(z13.string(), z13.object({ tokenHash: z13.string().min(1) }))
2574
2757
  });
2575
- var SignInsSchema = z12.object({
2576
- signIns: z12.array(z12.object({
2577
- tokenHash: z12.string().min(1),
2578
- login: z12.string().min(1),
2758
+ var SignInsSchema = z13.object({
2759
+ signIns: z13.array(z13.object({
2760
+ tokenHash: z13.string().min(1),
2761
+ login: z13.string().min(1),
2579
2762
  /** The account's picture at the identity provider; sign-ins from before it have none. */
2580
- avatarUrl: z12.url().optional(),
2763
+ avatarUrl: z13.url().optional(),
2581
2764
  createdAt: IsoDateTimeSchema,
2582
2765
  expiresAt: IsoDateTimeSchema
2583
2766
  })).default([])
2584
2767
  });
2585
- var TurnHistoryPayloadSchema = z12.object({
2768
+ var TurnHistoryPayloadSchema = z13.object({
2586
2769
  project: NameSchema,
2587
2770
  thread: UlidSchema.optional(),
2588
2771
  channel: NameSchema.optional(),
2589
- trigger: z12.string().default("manual"),
2590
- exitReason: z12.string().nullable().default(null),
2591
- costUsd: z12.number().default(0),
2772
+ trigger: z13.string().default("manual"),
2773
+ exitReason: z13.string().nullable().default(null),
2774
+ costUsd: z13.number().default(0),
2592
2775
  usage: UsageSchema.optional(),
2593
- model: z12.string().nullable().default(null),
2594
- summary: z12.string().nullable().default(null),
2595
- error: z12.string().nullable().default(null),
2596
- toolCalls: z12.number().int().nonnegative().optional(),
2776
+ model: z13.string().nullable().default(null),
2777
+ summary: z13.string().nullable().default(null),
2778
+ error: z13.string().nullable().default(null),
2779
+ toolCalls: z13.number().int().nonnegative().optional(),
2597
2780
  turnId: UlidSchema.optional()
2598
2781
  });
2599
- var TurnStartPayloadSchema = z12.object({ project: NameSchema, turnId: UlidSchema.optional() });
2600
- var CursorsSchema = z12.object({
2601
- digest: z12.string().nullable(),
2602
- scopes: z12.record(z12.string(), z12.string()).default({}),
2603
- threadsFrom: z12.record(z12.string(), z12.string().nullable()).default({}),
2604
- channelsFrom: z12.record(z12.string(), z12.string().nullable()).default({}),
2782
+ var TurnStartPayloadSchema = z13.object({ project: NameSchema, turnId: UlidSchema.optional() });
2783
+ var CursorsSchema = z13.object({
2784
+ digest: z13.string().nullable(),
2785
+ scopes: z13.record(z13.string(), z13.string()).default({}),
2786
+ threadsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
2787
+ channelsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
2605
2788
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
2606
- news: z12.string().optional()
2789
+ news: z13.string().optional()
2607
2790
  });
2608
2791
  var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
2609
2792
  function conversationKey(scope, part) {
@@ -2642,7 +2825,7 @@ function byId(a, b) {
2642
2825
  function turnTime(record) {
2643
2826
  return record.endedAt ?? record.startedAt;
2644
2827
  }
2645
- var PausedSchema = z12.object({ paused: z12.boolean() });
2828
+ var PausedSchema = z13.object({ paused: z13.boolean() });
2646
2829
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
2647
2830
  var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
2648
2831
  function withoutMentions(text) {
@@ -2654,7 +2837,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
2654
2837
  var FRONT_DESK_TRIGGER = "user_post";
2655
2838
  var OPS_CHANNELS = ["governance"];
2656
2839
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
2657
- var AlignmentsSchema = z12.object({ applied: z12.array(z12.string()).default([]) });
2840
+ var AlignmentsSchema = z13.object({ applied: z13.array(z13.string()).default([]) });
2658
2841
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
2659
2842
  var THREAD_RECORDS = "threads-as-records";
2660
2843
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -2666,6 +2849,8 @@ var ASKS_CHANNEL_OPENED = "asks-channel-opened";
2666
2849
  var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
2667
2850
  var CHANNEL_CONVERSATIONS = "channel-conversations";
2668
2851
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
2852
+ var CRON_VERBS = "cron-verbs-granted";
2853
+ var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
2669
2854
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
2670
2855
  var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
2671
2856
  var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
@@ -2867,7 +3052,8 @@ var Board = class _Board {
2867
3052
  ASKS_CHANNEL_OPENED,
2868
3053
  KNOWLEDGE_REMOVAL,
2869
3054
  CHANNEL_CONVERSATIONS,
2870
- DASHBOARD_UPDATES
3055
+ DASHBOARD_UPDATES,
3056
+ CRON_VERBS
2871
3057
  ].filter((name) => !applied.includes(name));
2872
3058
  if (pending.length === 0) {
2873
3059
  return;
@@ -2906,6 +3092,9 @@ var Board = class _Board {
2906
3092
  await this.grantBesideKnowledge("update_dashboard");
2907
3093
  await this.rewordDashboards();
2908
3094
  }
3095
+ if (pending.includes(CRON_VERBS)) {
3096
+ await this.grantToEveryRole(CRON_VERB_NAMES);
3097
+ }
2909
3098
  await writeJson(file, { applied: [...applied, ...pending] });
2910
3099
  }
2911
3100
  /**
@@ -2919,6 +3108,15 @@ var Board = class _Board {
2919
3108
  }
2920
3109
  }
2921
3110
  }
3111
+ /** Grants verbs once to every charter but the user's, which holds every verb already. */
3112
+ async grantToEveryRole(verbs) {
3113
+ for (const charter of await this.listRoles()) {
3114
+ const missing = verbs.filter((verb) => !charter.verbs.includes(verb));
3115
+ if (charter.name !== USER_ROLE && missing.length > 0) {
3116
+ await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, ...missing] }, { verbsAdded: missing });
3117
+ }
3118
+ }
3119
+ }
2922
3120
  /** A dashboard still saying agents may edit its file says how it changes now. */
2923
3121
  async rewordDashboards() {
2924
3122
  for (const slug of await listDirs(this.paths.projects())) {
@@ -3063,7 +3261,7 @@ var Board = class _Board {
3063
3261
  }
3064
3262
  }
3065
3263
  for (const file of await listFiles(this.paths.tasks(slug))) {
3066
- const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z12.record(z12.string(), z12.unknown()));
3264
+ const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z13.record(z13.string(), z13.unknown()));
3067
3265
  const legacy = raw.data["thread"];
3068
3266
  if (legacy === void 0) {
3069
3267
  continue;
@@ -3141,7 +3339,7 @@ var Board = class _Board {
3141
3339
  * turns, so it follows nothing now.
3142
3340
  */
3143
3341
  async renameInboxCursors() {
3144
- const Legacy = z12.object({ inbox: z12.string().nullable() });
3342
+ const Legacy = z13.object({ inbox: z13.string().nullable() });
3145
3343
  for (const agent2 of await this.listAgents()) {
3146
3344
  const file = this.paths.agentCursors(agent2.name);
3147
3345
  if (await exists(file)) {
@@ -3187,7 +3385,7 @@ ${charter.purpose}
3187
3385
  */
3188
3386
  async alignSeedRole(seed) {
3189
3387
  const file = this.paths.role(seed.name);
3190
- const raw = await readMarkdown(file, z12.record(z12.string(), z12.unknown()));
3388
+ const raw = await readMarkdown(file, z13.record(z13.string(), z13.unknown()));
3191
3389
  const current = RoleCharterSchema.parse(raw.data);
3192
3390
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
3193
3391
  const fieldsAligned = RoleCharterSchema.keyof().options.filter((key) => key === "verbs" || seed[key] === void 0 ? false : seed.name === USER_ROLE ? JSON.stringify(seed[key]) !== JSON.stringify(current[key]) : !(key in raw.data));
@@ -3500,14 +3698,14 @@ ${charter.purpose}
3500
3698
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
3501
3699
  async readSocietyNorms() {
3502
3700
  const file = this.paths.societyKnowledgeFile("norms");
3503
- return await exists(file) ? (await readMarkdown(file, z12.record(z12.string(), z12.unknown()))).body : "";
3701
+ return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
3504
3702
  }
3505
3703
  /** Knowledge topics of a project, or of the society when the project is null. */
3506
3704
  async listKnowledge(project2) {
3507
3705
  const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
3508
3706
  const topics = [];
3509
3707
  for (const file of await listFiles(dir)) {
3510
- const doc = await readMarkdown(path6.join(dir, file), z12.record(z12.string(), z12.unknown()));
3708
+ const doc = await readMarkdown(path6.join(dir, file), z13.record(z13.string(), z13.unknown()));
3511
3709
  const parsed = KnowledgeSchema.safeParse({
3512
3710
  ...doc.data,
3513
3711
  project: project2,
@@ -3907,6 +4105,21 @@ ${DASHBOARD_LINE}
3907
4105
  await this.events.append("paused.changed", actor.name, { paused });
3908
4106
  });
3909
4107
  }
4108
+ /** Sets the society's time zone, the user's alone: crons that name none are read in it. */
4109
+ async setTimezone(actor, zone) {
4110
+ this.assertUser(actor, "only the user sets the society's time zone");
4111
+ const timezone = TimeZoneSchema.parse(zone);
4112
+ return this.mutex.run(async () => {
4113
+ const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
4114
+ const society = { ...doc.data, timezone };
4115
+ await writeMarkdown(this.paths.societyFile(), society, doc.body);
4116
+ await this.events.append("society.configured", actor.name, {
4117
+ timezone,
4118
+ previousTimezone: doc.data.timezone
4119
+ });
4120
+ return society;
4121
+ });
4122
+ }
3910
4123
  /** Releases every claim whose lease has expired. Called by the scheduler on its tick. */
3911
4124
  async expireLeases() {
3912
4125
  return this.mutex.run(async () => {
@@ -4329,6 +4542,241 @@ ${current.body}`);
4329
4542
  return updated;
4330
4543
  });
4331
4544
  }
4545
+ /** Every cron, ended ones included, oldest first. */
4546
+ async listCrons() {
4547
+ const crons = [];
4548
+ for (const file of await listFiles(this.paths.crons())) {
4549
+ crons.push(await this.readCronFile(path6.join(this.paths.crons(), file)));
4550
+ }
4551
+ return crons;
4552
+ }
4553
+ async readCron(id) {
4554
+ const file = this.paths.cron(id);
4555
+ if (!await exists(file)) {
4556
+ throw new BoardError("NOT_FOUND", `cron ${id} not found`);
4557
+ }
4558
+ return this.readCronFile(file);
4559
+ }
4560
+ /**
4561
+ * Sets a cron: one that wakes its setter, or, set by a planning role, any citizen. Without a
4562
+ * place named it fires in the conversation of the turn that set it.
4563
+ */
4564
+ async createCron(actor, input) {
4565
+ const args = VerbInputs.create_cron.parse(input);
4566
+ await this.authorize(actor, "create_cron");
4567
+ const target = args.agent ?? actor.name;
4568
+ this.assertMayCron(actor, target, "set");
4569
+ if (args.cron === void 0 === (args.at === void 0)) {
4570
+ throw new BoardError("VALIDATION", "a cron needs either cron, a five-field expression, or at, one time");
4571
+ }
4572
+ return this.mutex.run(async () => {
4573
+ const agent2 = await this.readAgent(target);
4574
+ if (agent2.status !== "active" || agent2.cli === null) {
4575
+ throw new BoardError("INVALID_STATE", `${target} takes no turns, so no cron can wake it`);
4576
+ }
4577
+ const where = await this.cronConversation(actor, agent2, args);
4578
+ const now = this.now();
4579
+ const schedule = await this.cronSchedule(args, void 0, now);
4580
+ if (schedule === void 0) {
4581
+ throw new BoardError("VALIDATION", "a cron needs a schedule");
4582
+ }
4583
+ const cron2 = await this.writeCron({
4584
+ id: this.newId(),
4585
+ title: args.title,
4586
+ agent: agent2.name,
4587
+ ...where,
4588
+ schedule,
4589
+ createdBy: actor.name,
4590
+ createdAt: now.toISOString(),
4591
+ since: now.toISOString(),
4592
+ failures: 0,
4593
+ note: args.note
4594
+ });
4595
+ await this.events.append("cron.created", actor.name, {
4596
+ ...this.cronPayload(cron2),
4597
+ title: cron2.title,
4598
+ schedule: cron2.schedule
4599
+ });
4600
+ return cron2;
4601
+ });
4602
+ }
4603
+ /** Pauses, resumes, retitles, rewrites, or reschedules a cron; resuming or rescheduling starts it afresh. */
4604
+ async updateCron(actor, input) {
4605
+ const args = VerbInputs.update_cron.parse(input);
4606
+ await this.authorize(actor, "update_cron");
4607
+ if (args.cron !== void 0 && args.at !== void 0) {
4608
+ throw new BoardError("VALIDATION", "a cron fires on an expression or at one time, not both");
4609
+ }
4610
+ return this.mutex.run(async () => {
4611
+ const current = await this.readCron(args.cron_id);
4612
+ this.assertMayCron(actor, current.agent, "change");
4613
+ if (current.ended !== void 0) {
4614
+ throw new BoardError("INVALID_STATE", `cron ${current.id} has ended (${current.ended.reason})`);
4615
+ }
4616
+ const now = this.now();
4617
+ const schedule = await this.cronSchedule(args, current.schedule, now);
4618
+ const resumed = args.paused === false && current.paused !== void 0;
4619
+ const changed = [
4620
+ ...args.title === void 0 ? [] : ["title"],
4621
+ ...args.note === void 0 ? [] : ["note"],
4622
+ ...schedule === void 0 ? [] : ["schedule"],
4623
+ ...args.paused === true && current.paused === void 0 ? ["paused"] : [],
4624
+ ...resumed ? ["resumed"] : []
4625
+ ];
4626
+ if (changed.length === 0 && args.paused === void 0) {
4627
+ throw new BoardError("VALIDATION", "update_cron needs paused, title, note, cron, timezone, or at");
4628
+ }
4629
+ const ts = now.toISOString();
4630
+ const { paused: wasPaused, ...rest } = current;
4631
+ const paused = args.paused === void 0 ? wasPaused : args.paused ? wasPaused ?? { at: ts, by: actor.name } : void 0;
4632
+ const cron2 = await this.writeCron({
4633
+ ...rest,
4634
+ ...args.title === void 0 ? {} : { title: args.title },
4635
+ ...args.note === void 0 ? {} : { note: args.note },
4636
+ ...schedule === void 0 ? {} : { schedule },
4637
+ ...schedule !== void 0 || resumed ? { since: ts } : {},
4638
+ ...paused === void 0 ? {} : { paused }
4639
+ });
4640
+ if (changed.length > 0) {
4641
+ await this.events.append("cron.updated", actor.name, {
4642
+ ...this.cronPayload(cron2),
4643
+ changed,
4644
+ ...schedule === void 0 ? {} : { schedule }
4645
+ });
4646
+ }
4647
+ return cron2;
4648
+ });
4649
+ }
4650
+ /** Ends a cron for good: its setter's, or anyone's for a planning role. */
4651
+ async removeCron(actor, input) {
4652
+ const args = VerbInputs.remove_cron.parse(input);
4653
+ await this.authorize(actor, "remove_cron");
4654
+ return this.mutex.run(async () => {
4655
+ const current = await this.readCron(args.cron_id);
4656
+ this.assertMayCron(actor, current.agent, "end");
4657
+ if (current.ended !== void 0) {
4658
+ throw new BoardError("INVALID_STATE", `cron ${current.id} has ended already (${current.ended.reason})`);
4659
+ }
4660
+ return this.endCronUnlocked(current, actor.name, args.reason);
4661
+ });
4662
+ }
4663
+ /** A cron wakes its setter; one that wakes another citizen is the planning roles' to set, change, and end. */
4664
+ assertMayCron(actor, agent2, what) {
4665
+ if (agent2 !== actor.name && !PLANNING_ROLES.includes(actor.role)) {
4666
+ throw new BoardError("FORBIDDEN", `only the user, the steward, and the concierge ${what} a cron that wakes another citizen`);
4667
+ }
4668
+ }
4669
+ /**
4670
+ * Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
4671
+ * the conversation of the turn that set it when it wakes its setter, else its home outside projects.
4672
+ */
4673
+ async cronConversation(actor, agent2, named) {
4674
+ const own = actor.name === agent2.name;
4675
+ if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
4676
+ return own && actor.scope !== void 0 ? this.cronConversation(actor, agent2, {
4677
+ project: actor.scope,
4678
+ thread_id: actor.thread,
4679
+ channel: actor.channel
4680
+ }) : { scope: SOCIETY_SCOPE };
4681
+ }
4682
+ if (named.thread_id !== void 0) {
4683
+ const { thread: thread2 } = await this.findThread(named.thread_id);
4684
+ if (thread2.state !== "open") {
4685
+ throw new BoardError("INVALID_STATE", `thread ${thread2.id} is closed`);
4686
+ }
4687
+ const scope2 = wakeScope(agent2, parseChannelRef(thread2.channel).project);
4688
+ if (named.project !== void 0 && named.project !== scope2) {
4689
+ throw new BoardError("VALIDATION", `thread ${thread2.id} is on ${thread2.channel}, so ${agent2.name}'s conversation there is in ${scope2}, not ${named.project}`);
4690
+ }
4691
+ return { scope: scope2, thread: thread2.id };
4692
+ }
4693
+ const scope = named.project ?? (own ? actor.scope : void 0) ?? SOCIETY_SCOPE;
4694
+ if (scope !== SOCIETY_SCOPE) {
4695
+ await this.readActiveProject(scope);
4696
+ if (!agent2.memberships.includes(scope)) {
4697
+ throw new BoardError("VALIDATION", `${agent2.name} is not a member of ${scope}`);
4698
+ }
4699
+ }
4700
+ if (named.channel === void 0 || named.channel === "general") {
4701
+ return { scope };
4702
+ }
4703
+ await this.assertChannelOpen(channelRef(scope === SOCIETY_SCOPE ? null : scope, named.channel));
4704
+ return { scope, channel: named.channel };
4705
+ }
4706
+ /**
4707
+ * The schedule a cron verb asks for, or undefined when it asks for none: an expression, read in
4708
+ * its zone or the society's, which must name a time to come and no two fires closer than the
4709
+ * floor, or one time to come. A zone alone keeps the expression of `existing`.
4710
+ */
4711
+ async cronSchedule(args, existing, now) {
4712
+ if (args.at !== void 0) {
4713
+ if (args.timezone !== void 0) {
4714
+ throw new BoardError("VALIDATION", "at carries its own offset; timezone is for a cron expression");
4715
+ }
4716
+ const at = new Date(args.at);
4717
+ if (at <= now) {
4718
+ throw new BoardError("VALIDATION", `${args.at} has passed; a cron fires at a time to come`);
4719
+ }
4720
+ return { at: at.toISOString() };
4721
+ }
4722
+ if (args.cron === void 0 && args.timezone === void 0) {
4723
+ return void 0;
4724
+ }
4725
+ const expression = args.cron ?? (existing === void 0 || isOneTime(existing) ? void 0 : existing.cron);
4726
+ if (expression === void 0) {
4727
+ throw new BoardError("VALIDATION", "timezone is for a cron expression, which this cron has not");
4728
+ }
4729
+ const kept = args.timezone === void 0 && existing !== void 0 && !isOneTime(existing) ? existing.timezone : args.timezone ?? void 0;
4730
+ const schedule = {
4731
+ cron: expression,
4732
+ ...kept === void 0 ? {} : { timezone: kept }
4733
+ };
4734
+ const { timezone: societyZone } = await this.society();
4735
+ if (fireTimes(schedule, societyZone, now, 1).length === 0) {
4736
+ throw new BoardError("VALIDATION", `${expression} names no time to come`);
4737
+ }
4738
+ const gap = shortestGapMs(schedule, societyZone, now);
4739
+ if (gap !== null && gap < CRON_FLOOR_MS) {
4740
+ throw new BoardError("VALIDATION", `${expression} fires ${Math.round(gap / 6e4)} minute(s) apart at its closest; a cron fires at most every ${CRON_FLOOR_MS / 6e4} minutes, and every fire is a turn`);
4741
+ }
4742
+ return schedule;
4743
+ }
4744
+ /** A cron's events name its citizen and the conversation it fires in, as turn events do. */
4745
+ cronPayload(cron2) {
4746
+ return {
4747
+ cronId: cron2.id,
4748
+ agent: cron2.agent,
4749
+ project: cron2.scope,
4750
+ ...cron2.thread === void 0 ? {} : { thread: cron2.thread },
4751
+ ...cron2.channel === void 0 ? {} : { channel: cron2.channel }
4752
+ };
4753
+ }
4754
+ async readCronFile(file) {
4755
+ const doc = await readMarkdown(file, CronFrontmatterSchema);
4756
+ return { ...doc.data, note: doc.body.trimEnd() };
4757
+ }
4758
+ async writeCron(cron2) {
4759
+ const { note, ...frontmatter } = cron2;
4760
+ const data = CronFrontmatterSchema.parse(Object.fromEntries(Object.entries(frontmatter).filter(([, value]) => value !== void 0)));
4761
+ await writeMarkdown(this.paths.cron(cron2.id), data, note);
4762
+ return { ...data, note };
4763
+ }
4764
+ async endCronUnlocked(cron2, by, reason) {
4765
+ const ended = await this.writeCron({
4766
+ ...cron2,
4767
+ ended: { at: this.now().toISOString(), by, reason }
4768
+ });
4769
+ await this.events.append("cron.ended", by, { ...this.cronPayload(ended), reason });
4770
+ return ended;
4771
+ }
4772
+ /** Ends every running cron `ends` picks, in the transaction that ends the conversation it fires in. */
4773
+ async endCronsUnlocked(by, reason, ends) {
4774
+ for (const cron2 of await this.listCrons()) {
4775
+ if (cron2.ended === void 0 && ends(cron2)) {
4776
+ await this.endCronUnlocked(cron2, by, reason);
4777
+ }
4778
+ }
4779
+ }
4332
4780
  async openThread(actor, input) {
4333
4781
  const args = VerbInputs.open_thread.parse(input);
4334
4782
  await this.authorize(actor, "open_thread");
@@ -4545,6 +4993,7 @@ ${args.summary}`);
4545
4993
  threadId: thread2.id,
4546
4994
  channel: thread2.channel
4547
4995
  });
4996
+ await this.endCronsUnlocked(actor.name, "its thread closed", (cron2) => cron2.thread === thread2.id);
4548
4997
  return summary;
4549
4998
  });
4550
4999
  }
@@ -5224,6 +5673,7 @@ ${rationale}`}`);
5224
5673
  members: project2.members.filter((member) => member !== target)
5225
5674
  }));
5226
5675
  await this.refreshMember(target);
5676
+ await this.endCronsUnlocked(actor.name, `${target} left ${args.project}`, (cron2) => cron2.agent === target && cron2.scope === args.project);
5227
5677
  await this.events.append("agent.left", actor.name, {
5228
5678
  name: target,
5229
5679
  project: args.project,
@@ -5292,6 +5742,12 @@ ${rationale}`}`);
5292
5742
  return this.archiveChannel(actor, VerbInputs.archive_channel.parse(input));
5293
5743
  case "update_dashboard":
5294
5744
  return this.updateDashboard(actor, VerbInputs.update_dashboard.parse(input));
5745
+ case "create_cron":
5746
+ return this.createCron(actor, VerbInputs.create_cron.parse(input));
5747
+ case "update_cron":
5748
+ return this.updateCron(actor, VerbInputs.update_cron.parse(input));
5749
+ case "remove_cron":
5750
+ return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
5295
5751
  default:
5296
5752
  throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
5297
5753
  }
@@ -5338,7 +5794,7 @@ ${rationale}`}`);
5338
5794
  }
5339
5795
  async readAgentRoleBody(agent2) {
5340
5796
  const file = this.paths.agentRole(agent2);
5341
- return await exists(file) ? (await readMarkdown(file, z12.record(z12.string(), z12.unknown()))).body : "";
5797
+ return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
5342
5798
  }
5343
5799
  /** The steps of one finished turn, as the runner handed them over when it ended. */
5344
5800
  /**
@@ -5516,6 +5972,9 @@ ${rationale}`}`);
5516
5972
  }
5517
5973
  await this.writeTurnRecord(parsed);
5518
5974
  await this.refreshMember(parsed.agent);
5975
+ for (const id of parsed.crons ?? []) {
5976
+ await this.recordCronRun(id, parsed, failed);
5977
+ }
5519
5978
  if (parsed.exitReason === "stopped") {
5520
5979
  await this.events.append("turn.stopped", parsed.stoppedBy ?? USER_NAME, {
5521
5980
  ...parsed.id === void 0 ? {} : { turnId: parsed.id },
@@ -5597,6 +6056,45 @@ ${rationale}`}`);
5597
6056
  return true;
5598
6057
  });
5599
6058
  }
6059
+ /**
6060
+ * Records a cron's fire, which the scheduler made; a one-time cron ends with it. Null, recording
6061
+ * nothing, for a cron ended or paused since the scheduler read it, which does not fire.
6062
+ */
6063
+ async cronFired(id, at) {
6064
+ return this.mutex.run(async () => {
6065
+ const current = await this.readCron(id);
6066
+ if (current.ended !== void 0 || current.paused !== void 0) {
6067
+ return null;
6068
+ }
6069
+ const cron2 = await this.writeCron({ ...current, lastFiredAt: at });
6070
+ await this.events.append("cron.fired", SYSTEM_ACTOR.name, this.cronPayload(cron2));
6071
+ return isOneTime(cron2.schedule) ? this.endCronUnlocked(cron2, SYSTEM_ACTOR.name, "fired once, as set") : cron2;
6072
+ });
6073
+ }
6074
+ /** Records a time a cron passed because the turn of its previous fire was still queued or running. */
6075
+ async cronSkipped(id, at) {
6076
+ return this.mutex.run(async () => {
6077
+ const cron2 = await this.writeCron({ ...await this.readCron(id), lastSkippedAt: at });
6078
+ await this.events.append("cron.skipped", SYSTEM_ACTOR.name, this.cronPayload(cron2));
6079
+ return cron2;
6080
+ });
6081
+ }
6082
+ /**
6083
+ * How the turn a cron's fire started ended. A failure counts toward `cron_failing`, a completed
6084
+ * turn clears the count, and a stop, the user's decision, leaves it.
6085
+ */
6086
+ async recordCronRun(id, record, failed) {
6087
+ if (record.id === void 0 || !await exists(this.paths.cron(id))) {
6088
+ return;
6089
+ }
6090
+ const cron2 = await this.readCron(id);
6091
+ const outcome = record.exitReason === "stopped" ? "stopped" : failed ? "failed" : "completed";
6092
+ await this.writeCron({
6093
+ ...cron2,
6094
+ lastRun: { turnId: record.id, at: record.endedAt ?? this.now().toISOString(), outcome },
6095
+ failures: outcome === "failed" ? cron2.failures + 1 : outcome === "completed" ? 0 : cron2.failures
6096
+ });
6097
+ }
5600
6098
  /**
5601
6099
  * Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
5602
6100
  * (for one from before tasks had threads), or a channel archived.
@@ -5696,7 +6194,7 @@ ${rationale}`}`);
5696
6194
  return { project: slug, revision: 0, body: "" };
5697
6195
  }
5698
6196
  const { data, body } = await readLooseMarkdown(file);
5699
- const revision = z12.number().int().min(0).safeParse(data["revision"]);
6197
+ const revision = z13.number().int().min(0).safeParse(data["revision"]);
5700
6198
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
5701
6199
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
5702
6200
  return {
@@ -5987,7 +6485,7 @@ ${seed}
5987
6485
  continue;
5988
6486
  }
5989
6487
  const file = this.paths.agentRole(agent2.name);
5990
- const body = await exists(file) ? (await readMarkdown(file, z12.record(z12.string(), z12.unknown()))).body : "";
6488
+ const body = await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
5991
6489
  const at = body.indexOf(`
5992
6490
  ${SEED_INSTRUCTIONS_HEADING}
5993
6491
  `);
@@ -6431,6 +6929,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
6431
6929
  archivedChannels: [...current.archivedChannels, archived]
6432
6930
  }));
6433
6931
  }
6932
+ await this.endCronsUnlocked(by, `${ref} was archived`, (cron2) => cron2.scope === scope && cron2.channel === channel || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
6434
6933
  await this.events.append("channel.archived", by, {
6435
6934
  channel: ref,
6436
6935
  reason,
@@ -6535,6 +7034,7 @@ ${charter.purpose}
6535
7034
  });
6536
7035
  threadsClosed.push(doc.data.id);
6537
7036
  }
7037
+ await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
6538
7038
  const project2 = await this.updateProject(slug, (p) => ({
6539
7039
  ...p,
6540
7040
  members: [],
@@ -6599,6 +7099,7 @@ ${charter.purpose}
6599
7099
  }
6600
7100
  this.tokenIndex.delete(current.tokenHash);
6601
7101
  await this.refreshMember(name);
7102
+ await this.endCronsUnlocked(by, `${name} was retired`, (cron2) => cron2.agent === name);
6602
7103
  await this.events.append("agent.retired", by, {
6603
7104
  name,
6604
7105
  role: agent2.role,
@@ -6910,6 +7411,7 @@ ${charter.purpose}
6910
7411
  channel: found.thread.channel,
6911
7412
  ended
6912
7413
  });
7414
+ await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron2) => cron2.thread === subjectId);
6913
7415
  }
6914
7416
  async readMessagesIn(dir, since) {
6915
7417
  const messages = [];
@@ -7345,6 +7847,66 @@ program.command("resume").description("Allow wakeups again").action(async () =>
7345
7847
  await board.setPaused(board.userActor(), false);
7346
7848
  print({ paused: false }, () => "Society resumed");
7347
7849
  });
7850
+ program.command("society").description("The society's settings").command("timezone [zone]").description("Show the society's time zone, or set it: crons that name none are read in it").action(async (zone) => {
7851
+ const board = await open();
7852
+ const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
7853
+ print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
7854
+ });
7855
+ function describeCron(cron2, timeZone) {
7856
+ const where = [
7857
+ cron2.scope,
7858
+ cron2.thread,
7859
+ cron2.channel === void 0 ? void 0 : `#${cron2.channel}`
7860
+ ].filter((part) => part !== void 0).join("/");
7861
+ const next = nextFire(cron2, timeZone);
7862
+ const state = cron2.ended !== void 0 ? `ended (${cron2.ended.reason})` : cron2.paused !== void 0 ? "paused" : next === null ? "no time to come" : `next ${next.toISOString()}`;
7863
+ return `${cron2.id} ${cron2.agent} ${where} ${describeSchedule(cron2.schedule, timeZone)} ${state} ${cron2.title}`;
7864
+ }
7865
+ var cron = program.command("cron").description("Crons that wake citizens on a clock");
7866
+ cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
7867
+ const board = await open();
7868
+ const { timezone } = await board.society();
7869
+ const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
7870
+ print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
7871
+ });
7872
+ cron.command("add <agent> <title>").description("Set a cron that wakes a citizen, with a note on what to do then").requiredOption("--note <text>", "what the citizen does when it fires").option("--cron <expression>", "five fields: minute hour day-of-month month day-of-week").option("--timezone <zone>", "the zone the expression is read in; the society's otherwise").option("--at <time>", "one time to fire, with its offset, instead of --cron").option("--project <slug>", "where it fires: a project the citizen is in, or society").option("--thread <id>", "an open thread there whose conversation it fires in").option("--channel <name>", "an open channel there, other than general, whose conversation it fires in").option("--as <agent>", "act as this agent instead of the user").action(async (agentName, title, opts) => {
7873
+ const board = await open();
7874
+ const created = await board.createCron(await actorFor(board, opts.as), {
7875
+ title,
7876
+ note: opts.note,
7877
+ agent: agentName,
7878
+ ...opts.cron === void 0 ? {} : { cron: opts.cron },
7879
+ ...opts.timezone === void 0 ? {} : { timezone: opts.timezone },
7880
+ ...opts.at === void 0 ? {} : { at: opts.at },
7881
+ ...opts.project === void 0 ? {} : { project: opts.project },
7882
+ ...opts.thread === void 0 ? {} : { thread_id: opts.thread },
7883
+ ...opts.channel === void 0 ? {} : { channel: opts.channel }
7884
+ });
7885
+ const { timezone } = await board.society();
7886
+ print(created, () => `Cron set: ${describeCron(created, timezone)}`);
7887
+ });
7888
+ for (const [name, paused] of [
7889
+ ["pause", true],
7890
+ ["resume", false]
7891
+ ]) {
7892
+ cron.command(`${name} <id>`).description(paused ? "Pause a cron" : "Resume a paused cron from its next time").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
7893
+ const board = await open();
7894
+ const updated = await board.updateCron(await actorFor(board, opts.as), {
7895
+ cron_id: id,
7896
+ paused
7897
+ });
7898
+ const { timezone } = await board.society();
7899
+ print(updated, () => describeCron(updated, timezone));
7900
+ });
7901
+ }
7902
+ cron.command("remove <id>").description("End a cron for good").requiredOption("--reason <text>", "why it ends").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
7903
+ const board = await open();
7904
+ const removed = await board.removeCron(await actorFor(board, opts.as), {
7905
+ cron_id: id,
7906
+ reason: opts.reason
7907
+ });
7908
+ print(removed, () => `Cron ${removed.id} ended`);
7909
+ });
7348
7910
  var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
7349
7911
  knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
7350
7912
  const board = await open();