@kubitnodes/stellaris 0.3.20 → 0.3.22

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 (75) hide show
  1. package/dist/cli/index.js +1029 -637
  2. package/dist/server/index.js +1313 -729
  3. package/package.json +3 -1
  4. package/web/dist/assets/{BackLink-BjfX2KZl.js → BackLink-CD-iuy-h.js} +1 -1
  5. package/web/dist/assets/Button-Yoe21NQ2.js +1 -0
  6. package/web/dist/assets/ChannelForms-55otlg-m.js +1 -0
  7. package/web/dist/assets/ChannelView-DJvLzg0N.js +2 -0
  8. package/web/dist/assets/CitizenView-m1K2aTvK.js +2 -0
  9. package/web/dist/assets/CitizensView-BeTgmrKE.js +1 -0
  10. package/web/dist/assets/{CliIcon-vSuwLv2K.js → CliIcon-DbMYJdlU.js} +1 -1
  11. package/web/dist/assets/CronsView-B4kXk5Ld.js +1 -0
  12. package/web/dist/assets/KnowledgeView-aSGxDDKy.js +1 -0
  13. package/web/dist/assets/Markdown-c9bCCLHg.js +30 -0
  14. package/web/dist/assets/{MessageItem-D7MUCA_T.js → MessageItem-DKJKMqs-.js} +1 -1
  15. package/web/dist/assets/MetricsView-D2TTry5O.js +1 -0
  16. package/web/dist/assets/NeedsYouView-C3celRei.js +1 -0
  17. package/web/dist/assets/Overview-BveU1yJj.js +1 -0
  18. package/web/dist/assets/{Pane-_D7n5uKH.js → Pane-CLjdfBMM.js} +1 -1
  19. package/web/dist/assets/{Picker-DUiiQMFN.js → Picker-CXOe5Xdu.js} +1 -1
  20. package/web/dist/assets/ProjectView-oa4Z46ED.js +1 -0
  21. package/web/dist/assets/ProposalView-B4T-EoX9.js +2 -0
  22. package/web/dist/assets/ProposalsView-BZHivSEm.js +1 -0
  23. package/web/dist/assets/Quota-BC7dpnwd.js +1 -0
  24. package/web/dist/assets/RunnersView-BrJF-aLU.js +1 -0
  25. package/web/dist/assets/SocietyView-C1yosPuf.js +1 -0
  26. package/web/dist/assets/StickyList-7tAlUDCN.js +1 -0
  27. package/web/dist/assets/SubjectThread-CCq6ZAnR.js +1 -0
  28. package/web/dist/assets/TaskFileView-BEXQJQQ6.js +4 -0
  29. package/web/dist/assets/TaskView-Cfhb7NuO.js +3 -0
  30. package/web/dist/assets/TasksView-3XljXcoi.js +1 -0
  31. package/web/dist/assets/ThreadForms-D2y5dXSC.js +1 -0
  32. package/web/dist/assets/ThreadItem-DeIhhmyu.js +1 -0
  33. package/web/dist/assets/ThreadView-CbUnt9WL.js +1 -0
  34. package/web/dist/assets/citizen--bqux6i_.js +1 -0
  35. package/web/dist/assets/compose-DNc3_c5x.js +1 -0
  36. package/web/dist/assets/format-B6fjC0_a.js +55 -0
  37. package/web/dist/assets/governance-RBQ4Lam0.js +1 -0
  38. package/web/dist/assets/index-CyCty7a2.js +23 -0
  39. package/web/dist/assets/index-GRqxKN35.css +2 -0
  40. package/web/dist/assets/mentions-B2Qchd_6.js +3 -0
  41. package/web/dist/assets/{tasks-B6I9DJ8O.js → tasks-DCUj5w_d.js} +1 -1
  42. package/web/dist/assets/useMatch-CfbBQirn.js +1 -0
  43. package/web/dist/index.html +9 -10
  44. package/web/dist/assets/ChannelForms-BYrcUldk.js +0 -1
  45. package/web/dist/assets/ChannelView-GJ_DusLQ.js +0 -2
  46. package/web/dist/assets/CitizenView-Cx7IYSRr.js +0 -2
  47. package/web/dist/assets/CitizensView-IKH7_yNQ.js +0 -1
  48. package/web/dist/assets/CronsView-DJbi8Qhx.js +0 -1
  49. package/web/dist/assets/KnowledgeView-CoYOG9tI.js +0 -1
  50. package/web/dist/assets/Markdown-YC-fbHiE.js +0 -32
  51. package/web/dist/assets/MetricsView-CE94VW0I.js +0 -1
  52. package/web/dist/assets/NeedsYouView-Otnm3lcG.js +0 -1
  53. package/web/dist/assets/Overview-B8f6rLb2.js +0 -1
  54. package/web/dist/assets/ProjectView-D_ACOmEd.js +0 -1
  55. package/web/dist/assets/ProposalView-4d0U3nEb.js +0 -2
  56. package/web/dist/assets/ProposalsView-BovF6CMD.js +0 -1
  57. package/web/dist/assets/RunnersView-B2_uCVHC.js +0 -1
  58. package/web/dist/assets/SocietyView-Bf0iX8AY.js +0 -1
  59. package/web/dist/assets/SubjectThread-B_Vmgs3d.js +0 -1
  60. package/web/dist/assets/TaskFileView-BzO0gjE6.js +0 -4
  61. package/web/dist/assets/TaskView-DB_Nji99.js +0 -3
  62. package/web/dist/assets/TasksView-Bz36YYWq.js +0 -1
  63. package/web/dist/assets/ThreadForms-1mbMZsbv.js +0 -1
  64. package/web/dist/assets/ThreadItem-QJSA92C4.js +0 -1
  65. package/web/dist/assets/ThreadView-BECLqt5B.js +0 -1
  66. package/web/dist/assets/citizen-DVFr5T6R.js +0 -1
  67. package/web/dist/assets/compose-_6feqP1t.js +0 -1
  68. package/web/dist/assets/format-DxXtbdNM.js +0 -55
  69. package/web/dist/assets/governance-C9xH4QTm.js +0 -1
  70. package/web/dist/assets/index-BEVCPm4C.js +0 -23
  71. package/web/dist/assets/index-BkOq4PSU.css +0 -2
  72. package/web/dist/assets/link-CTjFMNoY.js +0 -1
  73. package/web/dist/assets/mentions-BCdV0bMK.js +0 -1
  74. package/web/dist/assets/useMatch-B3RzAe8y.js +0 -1
  75. package/web/dist/assets/useStickyScroll-Ca7sWbGH.js +0 -1
package/dist/cli/index.js CHANGED
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
  import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
10
10
  import path6 from "node:path";
11
11
  import { decodeTime, monotonicFactory } from "ulid";
12
- import { z as z13 } from "zod";
12
+ import { z as z15 } from "zod";
13
13
 
14
14
  // packages/shared/dist/ids.js
15
15
  import { z } from "zod";
@@ -146,7 +146,7 @@ var SEED_ROLES = [
146
146
  },
147
147
  {
148
148
  name: "steward",
149
- purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one. Never approves its own proposals; hiring, roles, and retirements are decided by the user, and channels, reallocations, and skills the steward may decide. Curates society knowledge and skills.",
149
+ purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one, by a reallocation of its replicas, backlog threshold, wakes, or verbs. Never approves its own proposals; hiring, roles, retirements, and reallocations that change a role's verbs or replicas are decided by the user, and channels, skills, and other reallocations the steward may decide. Curates society knowledge and skills.",
150
150
  verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
151
151
  wakeTriggers: ["ops_event", "heartbeat"],
152
152
  maxReplicas: 1,
@@ -169,7 +169,7 @@ var SEED_ROLES = [
169
169
  ];
170
170
 
171
171
  // packages/shared/dist/board.js
172
- import { z as z4 } from "zod";
172
+ import { z as z5 } from "zod";
173
173
 
174
174
  // packages/shared/dist/crons.js
175
175
  import { Cron as Pattern } from "croner";
@@ -288,6 +288,79 @@ function describeSchedule(schedule, societyZone) {
288
288
  return schedule.timezone === void 0 ? `\`${schedule.cron}\` in the society's time zone, ${societyZone}` : `\`${schedule.cron}\` in ${schedule.timezone}`;
289
289
  }
290
290
 
291
+ // packages/shared/dist/quota.js
292
+ import { z as z4 } from "zod";
293
+ var QuotaWindowSchema = z4.object({
294
+ /**
295
+ * The CLI's own name for the window: Claude's `five_hour`, `seven_day`, `seven_day_opus`, and
296
+ * `model:<label>` for a model's weekly window; Codex's `primary` and `secondary`.
297
+ */
298
+ key: z4.string().min(1).max(80),
299
+ /** What a scoped window counts, as the CLI labels it, such as a model's name. */
300
+ label: z4.string().min(1).max(80).optional(),
301
+ /** The window's length in minutes, when known. */
302
+ minutes: z4.number().int().positive().nullable().default(null),
303
+ /** How much of the window is used, in percent, or null when the CLI did not say. */
304
+ usedPercent: z4.number().nonnegative().nullable().default(null),
305
+ /** When the window starts afresh, when known. */
306
+ resetsAt: IsoDateTimeSchema.nullable().default(null),
307
+ /** When the CLI last reported this window. */
308
+ observedAt: IsoDateTimeSchema
309
+ });
310
+ var QuotaStatusSchema = z4.enum(["allowed", "warning", "limited", "none"]);
311
+ var QuotaCreditsSchema = z4.object({
312
+ hasCredits: z4.boolean(),
313
+ unlimited: z4.boolean(),
314
+ balance: z4.string().max(40).nullable()
315
+ });
316
+ var QuotaExtraUsageSchema = z4.object({
317
+ enabled: z4.boolean(),
318
+ monthlyLimit: z4.number().nonnegative().nullable(),
319
+ usedCredits: z4.number().nonnegative().nullable(),
320
+ currency: z4.string().min(1).max(8).nullable()
321
+ });
322
+ var CliQuotaSchema = z4.object({
323
+ /** When the CLI last reported anything here. */
324
+ observedAt: IsoDateTimeSchema,
325
+ /** Whether it came from a turn's stream or from asking the CLI. */
326
+ source: z4.enum(["turn", "read"]),
327
+ status: QuotaStatusSchema,
328
+ /** While `limited`: when work can go on, when known, and the window that stopped it. */
329
+ limitedUntil: IsoDateTimeSchema.nullable().default(null),
330
+ limitedBy: z4.string().min(1).max(80).nullable().default(null),
331
+ /** The plan's name as the CLI gives it, such as `max` or `pro`. */
332
+ plan: z4.string().min(1).max(40).nullable().default(null),
333
+ windows: z4.array(QuotaWindowSchema).max(24).default([]),
334
+ credits: QuotaCreditsSchema.nullable().default(null),
335
+ extraUsage: QuotaExtraUsageSchema.nullable().default(null)
336
+ });
337
+ function mergeQuota(previous, next) {
338
+ if (previous === void 0) {
339
+ return next;
340
+ }
341
+ const windows = new Map(previous.windows.map((window) => [window.key, window]));
342
+ for (const window of next.windows) {
343
+ windows.set(window.key, window);
344
+ }
345
+ return {
346
+ ...next,
347
+ plan: next.plan ?? previous.plan,
348
+ credits: next.credits ?? previous.credits,
349
+ extraUsage: next.extraUsage ?? previous.extraUsage,
350
+ windows: [...windows.values()]
351
+ };
352
+ }
353
+ function readings(quota) {
354
+ const windows = quota.windows.map((window) => `${window.key}=${window.usedPercent === null ? "?" : Math.round(window.usedPercent)}@${window.resetsAt === null ? "?" : Math.round(Date.parse(window.resetsAt) / 6e4)}`).toSorted();
355
+ return windows.join(",");
356
+ }
357
+ function quotaChanged(previous, next) {
358
+ if (previous === void 0) {
359
+ return true;
360
+ }
361
+ return previous.status !== next.status || previous.limitedUntil !== next.limitedUntil || previous.plan !== next.plan || previous.credits?.balance !== next.credits?.balance || previous.extraUsage?.usedCredits !== next.extraUsage?.usedCredits || readings(previous) !== readings(next);
362
+ }
363
+
291
364
  // packages/shared/dist/board.js
292
365
  var ASK_CHANNEL = "asks";
293
366
  var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
@@ -300,24 +373,24 @@ var SOCIETY_SCOPE = "society";
300
373
  function wakeScope(member, project2) {
301
374
  return project2 !== null && member.memberships.includes(project2) ? project2 : SOCIETY_SCOPE;
302
375
  }
303
- var CliKindSchema = z4.enum(["claude", "codex"]);
304
- var ArchivedChannelSchema = z4.object({
376
+ var CliKindSchema = z5.enum(["claude", "codex"]);
377
+ var ArchivedChannelSchema = z5.object({
305
378
  name: NameSchema,
306
379
  at: IsoDateTimeSchema,
307
380
  by: NameSchema,
308
- reason: z4.string().min(1)
381
+ reason: z5.string().min(1)
309
382
  });
310
- var SocietySchema = z4.object({
311
- name: z4.string().min(1),
312
- version: z4.literal(1),
383
+ var SocietySchema = z5.object({
384
+ name: z5.string().min(1),
385
+ version: z5.literal(1),
313
386
  createdAt: IsoDateTimeSchema,
314
387
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
315
- channels: z4.array(NameSchema),
316
- archivedChannels: z4.array(ArchivedChannelSchema).default([]),
388
+ channels: z5.array(NameSchema),
389
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
317
390
  /** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
318
391
  timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
319
392
  });
320
- var CompletionEffectSchema = z4.enum(["none", "merge", "ghpr"]);
393
+ var CompletionEffectSchema = z5.enum(["none", "merge", "ghpr"]);
321
394
  var GITHUB_REMOTE = [
322
395
  /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
323
396
  /^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
@@ -333,7 +406,7 @@ function githubRepository(remote) {
333
406
  return null;
334
407
  }
335
408
  var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
336
- var PullRequestUrlSchema = z4.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
409
+ var PullRequestUrlSchema = z5.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
337
410
  function parsePullRequestUrl(url) {
338
411
  const match = PULL_REQUEST_URL.exec(url.trim());
339
412
  return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
@@ -341,72 +414,72 @@ function parsePullRequestUrl(url) {
341
414
  function sameRepository(a, b) {
342
415
  return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
343
416
  }
344
- var MergeSubjectSchema = z4.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
345
- var PullRequestLinkSchema = z4.object({
417
+ var MergeSubjectSchema = z5.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
418
+ var PullRequestLinkSchema = z5.object({
346
419
  url: PullRequestUrlSchema,
347
- number: z4.number().int().positive(),
420
+ number: z5.number().int().positive(),
348
421
  subject: MergeSubjectSchema.optional(),
349
- body: z4.string().max(2e4).optional(),
422
+ body: z5.string().max(2e4).optional(),
350
423
  linkedBy: NameSchema,
351
424
  linkedAt: IsoDateTimeSchema,
352
425
  /**
353
426
  * Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
354
427
  * leaves it open, for the reason `detail` gives.
355
428
  */
356
- closed: z4.object({ at: IsoDateTimeSchema, ok: z4.boolean(), detail: z4.string() }).optional()
429
+ closed: z5.object({ at: IsoDateTimeSchema, ok: z5.boolean(), detail: z5.string() }).optional()
357
430
  });
358
- 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");
359
- var StageIdSchema = z4.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
431
+ var DefaultBranchSchema = z5.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");
432
+ var StageIdSchema = z5.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
360
433
  var stageFields = {
361
- name: z4.string().trim().min(1).max(120),
434
+ name: z5.string().trim().min(1).max(120),
362
435
  role: NameSchema.optional(),
363
436
  agent: NameSchema.optional(),
364
- gate: z4.boolean().default(false)
437
+ gate: z5.boolean().default(false)
365
438
  };
366
439
  function oneAssignee(stage) {
367
440
  return stage.role === void 0 || stage.agent === void 0;
368
441
  }
369
442
  var ONE_ASSIGNEE = { message: "a stage names a role or an agent, not both" };
370
- var PlanStageSchema = z4.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
371
- var PlanEditStageSchema = z4.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
372
- var PlanSchema = z4.array(PlanStageSchema);
373
- var PlanEditSchema = z4.array(PlanEditStageSchema);
374
- var ProjectSchema = z4.object({
443
+ var PlanStageSchema = z5.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
444
+ var PlanEditStageSchema = z5.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
445
+ var PlanSchema = z5.array(PlanStageSchema);
446
+ var PlanEditSchema = z5.array(PlanEditStageSchema);
447
+ var ProjectSchema = z5.object({
375
448
  slug: NameSchema,
376
- name: z4.string().min(1),
377
- repo: z4.string().nullable(),
378
- defaultBranch: z4.string().min(1),
449
+ name: z5.string().min(1),
450
+ repo: z5.string().nullable(),
451
+ defaultBranch: z5.string().min(1),
379
452
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
380
- channels: z4.array(NameSchema),
381
- archivedChannels: z4.array(ArchivedChannelSchema).default([]),
382
- members: z4.array(NameSchema),
383
- approvers: z4.array(NameSchema),
384
- requiredCapabilities: z4.array(z4.string()),
453
+ channels: z5.array(NameSchema),
454
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
455
+ members: z5.array(NameSchema),
456
+ approvers: z5.array(NameSchema),
457
+ requiredCapabilities: z5.array(z5.string()),
385
458
  createdAt: IsoDateTimeSchema,
386
459
  onDone: CompletionEffectSchema.default("none"),
387
460
  /** The runner the project lives on, which holds its repository; set on the project's first turn. */
388
461
  runner: NameSchema.optional(),
389
462
  /** Set once the project is archived: nobody belongs to it, and nothing more is posted, filed, or joined there. */
390
- archived: z4.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z4.string().min(1) }).optional()
463
+ archived: z5.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z5.string().min(1) }).optional()
391
464
  });
392
- var TaskStepSchema = z4.object({
393
- action: z4.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
465
+ var TaskStepSchema = z5.object({
466
+ action: z5.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
394
467
  stage: StageIdSchema,
395
468
  to: StageIdSchema.nullable()
396
469
  });
397
- var ProposalStepSchema = z4.object({ action: z4.enum(["approved", "rejected"]) });
398
- var MessageStepSchema = z4.union([TaskStepSchema, ProposalStepSchema]);
470
+ var ProposalStepSchema = z5.object({ action: z5.enum(["approved", "rejected"]) });
471
+ var MessageStepSchema = z5.union([TaskStepSchema, ProposalStepSchema]);
399
472
  function isTaskStep(step) {
400
473
  return "stage" in step;
401
474
  }
402
- var KnowledgeNoticeSchema = z4.object({
403
- kind: z4.literal("knowledge"),
404
- action: z4.enum(["written", "updated", "removed"]),
475
+ var KnowledgeNoticeSchema = z5.object({
476
+ kind: z5.literal("knowledge"),
477
+ action: z5.enum(["written", "updated", "removed"]),
405
478
  topic: NameSchema,
406
479
  project: NameSchema.nullable()
407
480
  });
408
- var MessageNoticeSchema = z4.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
409
- var MessageFrontmatterSchema = z4.object({
481
+ var MessageNoticeSchema = z5.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
482
+ var MessageFrontmatterSchema = z5.object({
410
483
  id: UlidSchema,
411
484
  author: NameSchema,
412
485
  channel: ChannelRefSchema,
@@ -420,7 +493,7 @@ var MessageFrontmatterSchema = z4.object({
420
493
  /** On a post a verb made about its work, such as a knowledge topic's note: what it records. */
421
494
  notice: MessageNoticeSchema.optional(),
422
495
  ts: IsoDateTimeSchema,
423
- mentions: z4.array(NameSchema)
496
+ mentions: z5.array(NameSchema)
424
497
  });
425
498
  var MessageRefSchema = MessageFrontmatterSchema.pick({
426
499
  id: true,
@@ -429,16 +502,16 @@ var MessageRefSchema = MessageFrontmatterSchema.pick({
429
502
  thread: true,
430
503
  ts: true
431
504
  });
432
- var TaskStatusSchema = z4.enum(["open", "claimed", "done", "abandoned"]);
433
- var ThreadStateSchema = z4.enum(["open", "closed"]);
434
- var ThreadSubjectSchema = z4.object({
435
- kind: z4.enum(["task", "proposal"]),
505
+ var TaskStatusSchema = z5.enum(["open", "claimed", "done", "abandoned"]);
506
+ var ThreadStateSchema = z5.enum(["open", "closed"]);
507
+ var ThreadSubjectSchema = z5.object({
508
+ kind: z5.enum(["task", "proposal"]),
436
509
  id: UlidSchema
437
510
  });
438
- var ThreadFrontmatterSchema = z4.object({
511
+ var ThreadFrontmatterSchema = z5.object({
439
512
  id: UlidSchema,
440
513
  channel: ChannelRefSchema,
441
- title: z4.string().min(1).max(200),
514
+ title: z5.string().min(1).max(200),
442
515
  subject: ThreadSubjectSchema.optional(),
443
516
  state: ThreadStateSchema,
444
517
  openedBy: NameSchema,
@@ -446,28 +519,28 @@ var ThreadFrontmatterSchema = z4.object({
446
519
  closedBy: NameSchema.optional(),
447
520
  closedAt: IsoDateTimeSchema.optional()
448
521
  });
449
- var StageSchema = z4.object({
522
+ var StageSchema = z5.object({
450
523
  id: StageIdSchema,
451
- name: z4.string().min(1),
524
+ name: z5.string().min(1),
452
525
  role: NameSchema.optional(),
453
526
  agent: NameSchema.optional(),
454
- gate: z4.boolean().default(false),
527
+ gate: z5.boolean().default(false),
455
528
  /** Everyone who has held the stage, oldest first. */
456
- holders: z4.array(NameSchema).default([]),
529
+ holders: z5.array(NameSchema).default([]),
457
530
  completedBy: NameSchema.optional(),
458
531
  completedAt: IsoDateTimeSchema.optional()
459
532
  });
460
- var TaskReturnSchema = z4.object({
533
+ var TaskReturnSchema = z5.object({
461
534
  from: StageIdSchema,
462
535
  by: NameSchema,
463
536
  at: IsoDateTimeSchema
464
537
  });
465
- var TaskFrontmatterSchema = z4.object({
538
+ var TaskFrontmatterSchema = z5.object({
466
539
  id: UlidSchema,
467
540
  project: NameSchema,
468
541
  /** The channel of the project the task is filed in, where its thread hangs. */
469
542
  channel: NameSchema.default("general"),
470
- title: z4.string().min(1),
543
+ title: z5.string().min(1),
471
544
  status: TaskStatusSchema,
472
545
  createdBy: NameSchema,
473
546
  createdAt: IsoDateTimeSchema,
@@ -476,19 +549,19 @@ var TaskFrontmatterSchema = z4.object({
476
549
  claimedBy: NameSchema.optional(),
477
550
  leaseExpiresAt: IsoDateTimeSchema.optional(),
478
551
  parentId: UlidSchema.optional(),
479
- blockedBy: z4.array(UlidSchema),
480
- requiredCapabilities: z4.array(z4.string()),
481
- stages: z4.array(StageSchema).min(1),
552
+ blockedBy: z5.array(UlidSchema),
553
+ requiredCapabilities: z5.array(z5.string()),
554
+ stages: z5.array(StageSchema).min(1),
482
555
  stage: StageIdSchema,
483
556
  /** When the current stage last became current or lost its holder: the waiting clock. */
484
557
  stageSince: IsoDateTimeSchema,
485
558
  /** The highest stage number issued, so a removed stage's id is never reused. */
486
- stageSeq: z4.number().int().positive(),
559
+ stageSeq: z5.number().int().positive(),
487
560
  onDone: CompletionEffectSchema.default("none"),
488
561
  /** The pull request a `ghpr` task lands through. */
489
562
  pullRequest: PullRequestLinkSchema.optional(),
490
563
  /** The last stage is complete and the completion effect is running. */
491
- completing: z4.boolean().default(false),
564
+ completing: z5.boolean().default(false),
492
565
  /** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
493
566
  returned: TaskReturnSchema.optional()
494
567
  });
@@ -512,79 +585,90 @@ function mayHoldStage(member, task2) {
512
585
  }
513
586
  return !(stage.gate && task2.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
514
587
  }
515
- var ModelNameSchema = z4.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
516
- var EffortSchema = z4.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
517
- var EffortOptionSchema = z4.object({
588
+ var ModelNameSchema = z5.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
589
+ var EffortSchema = z5.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
590
+ var EffortOptionSchema = z5.object({
518
591
  id: EffortSchema,
519
- description: z4.string().default("")
592
+ description: z5.string().default("")
520
593
  });
521
- var ModelOptionSchema = z4.object({
594
+ var ModelOptionSchema = z5.object({
522
595
  id: ModelNameSchema,
523
- name: z4.string(),
524
- description: z4.string().default(""),
525
- isDefault: z4.boolean().default(false),
526
- efforts: z4.array(EffortOptionSchema).default([]),
596
+ name: z5.string(),
597
+ description: z5.string().default(""),
598
+ isDefault: z5.boolean().default(false),
599
+ efforts: z5.array(EffortOptionSchema).default([]),
527
600
  defaultEffort: EffortSchema.optional()
528
601
  });
529
- var RunnerModelsSchema = z4.object({
602
+ var RunnerModelsSchema = z5.object({
530
603
  runner: NameSchema,
531
604
  cli: CliKindSchema,
532
- models: z4.array(ModelOptionSchema)
605
+ models: z5.array(ModelOptionSchema)
533
606
  });
534
- var AgentStatusSchema = z4.enum(["active", "retired"]);
535
- var AgentSchema = z4.object({
607
+ var AgentStatusSchema = z5.enum(["active", "retired"]);
608
+ var AgentSchema = z5.object({
536
609
  name: NameSchema,
537
610
  role: NameSchema,
538
611
  cli: CliKindSchema.nullable(),
539
- model: z4.string().optional(),
612
+ model: z5.string().optional(),
540
613
  /** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
541
- effort: z4.string().optional(),
614
+ effort: z5.string().optional(),
542
615
  /** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
543
616
  homeRunner: NameSchema.optional(),
544
- memberships: z4.array(NameSchema),
545
- subscriptions: z4.array(ChannelRefSchema),
617
+ memberships: z5.array(NameSchema),
618
+ subscriptions: z5.array(ChannelRefSchema),
546
619
  status: AgentStatusSchema,
547
620
  createdAt: IsoDateTimeSchema,
548
- tokenHash: z4.string().min(1),
621
+ tokenHash: z5.string().min(1),
549
622
  retiredAt: IsoDateTimeSchema.optional(),
550
- retiredReason: z4.string().optional()
623
+ retiredReason: z5.string().optional()
551
624
  });
552
- var MemberSchema = z4.object({
625
+ var MemberSchema = z5.object({
553
626
  name: NameSchema,
554
627
  role: NameSchema,
555
628
  cli: CliKindSchema.nullable(),
556
629
  homeRunner: NameSchema.optional(),
557
630
  status: AgentStatusSchema,
558
- resident: z4.boolean().default(false),
631
+ resident: z5.boolean().default(false),
559
632
  /** The model configured on the record, if any; the CLI's own default applies otherwise. */
560
- model: z4.string().optional(),
633
+ model: z5.string().optional(),
561
634
  /** The model the CLI reported on the citizen's last turn. */
562
- lastModel: z4.string().optional(),
635
+ lastModel: z5.string().optional(),
563
636
  /** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
564
- effort: z4.string().optional(),
637
+ effort: z5.string().optional(),
565
638
  /** The citizen's own skills, by name. */
566
- skills: z4.array(NameSchema).default([]),
567
- memberships: z4.array(NameSchema),
568
- subscriptions: z4.array(ChannelRefSchema),
569
- claimsHeld: z4.number().int().nonnegative().default(0),
570
- tasksDone: z4.number().int().nonnegative().default(0),
639
+ skills: z5.array(NameSchema).default([]),
640
+ memberships: z5.array(NameSchema),
641
+ subscriptions: z5.array(ChannelRefSchema),
642
+ claimsHeld: z5.number().int().nonnegative().default(0),
643
+ tasksDone: z5.number().int().nonnegative().default(0),
571
644
  lastTurnAt: IsoDateTimeSchema.optional(),
572
- lastTurnOutcome: z4.string().optional(),
645
+ lastTurnOutcome: z5.string().optional(),
573
646
  createdAt: IsoDateTimeSchema,
574
647
  retiredAt: IsoDateTimeSchema.optional()
575
648
  });
576
- var RunnerOsSchema = z4.enum(["linux", "windows", "darwin"]);
577
- var RunnerSchema = z4.object({
649
+ var RunnerOsSchema = z5.enum(["linux", "windows", "darwin"]);
650
+ var GitIdentityPartSchema = z5.enum(["name", "email"]);
651
+ var RunnerSchema = z5.object({
578
652
  name: NameSchema,
579
653
  os: RunnerOsSchema,
580
- clis: z4.array(CliKindSchema),
581
- capabilities: z4.array(z4.string()),
654
+ clis: z5.array(CliKindSchema),
655
+ capabilities: z5.array(z5.string()),
582
656
  /** The release the runner reported when it last registered; absent until it has. */
583
- version: z4.string().optional(),
584
- status: z4.enum(["connected", "disconnected"]),
585
- lastSeen: IsoDateTimeSchema.optional()
657
+ version: z5.string().optional(),
658
+ /**
659
+ * What the runner's account lacked of a git identity when it last registered, empty when it had
660
+ * both; absent while the runner has not said, as one from before runners checked never does.
661
+ */
662
+ gitIdentityMissing: z5.array(GitIdentityPartSchema).optional(),
663
+ status: z5.enum(["connected", "disconnected"]),
664
+ lastSeen: IsoDateTimeSchema.optional(),
665
+ /**
666
+ * Each CLI's plan quota as its login on this runner last reported it, from a turn's stream or a
667
+ * read; absent until one has.
668
+ */
669
+ quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional()
586
670
  });
587
- var ProposalKindSchema = z4.enum([
671
+ var ProposalKindSchema = z5.enum([
588
672
  "role",
589
673
  "member",
590
674
  "channel",
@@ -593,43 +677,88 @@ var ProposalKindSchema = z4.enum([
593
677
  "skill",
594
678
  "archive"
595
679
  ]);
596
- var ProposalStatusSchema = z4.enum([
680
+ var ProposalStatusSchema = z5.enum([
597
681
  "proposed",
598
682
  "approved",
599
683
  "rejected",
600
684
  "provisioned",
601
685
  "retired"
602
686
  ]);
603
- var MemberProposalSchema = z4.object({
687
+ var MemberProposalSchema = z5.object({
604
688
  name: NameSchema,
605
689
  role: NameSchema,
606
690
  cli: CliKindSchema,
607
- model: z4.string().optional(),
691
+ model: z5.string().optional(),
608
692
  homeRunner: NameSchema.optional(),
609
- memberships: z4.array(NameSchema).default([]),
610
- subscriptions: z4.array(ChannelRefSchema).default([]),
611
- seedInstructions: z4.string().optional()
693
+ memberships: z5.array(NameSchema).default([]),
694
+ subscriptions: z5.array(ChannelRefSchema).default([]),
695
+ seedInstructions: z5.string().optional()
612
696
  });
613
- var ChannelProposalSchema = z4.object({
697
+ var ChannelProposalSchema = z5.object({
614
698
  project: NameSchema.nullable().default(null),
615
699
  name: NameSchema,
616
- purpose: z4.string().min(1)
700
+ purpose: z5.string().min(1)
617
701
  });
618
- var ReallocationProposalSchema = z4.object({
619
- description: z4.string().min(1)
702
+ var RoleChangesSchema = z5.strictObject({
703
+ maxReplicas: z5.number().int().min(1).optional(),
704
+ backlogThreshold: z5.number().positive().optional(),
705
+ wakeTriggers: z5.array(CharterTriggerSchema).optional(),
706
+ verbs: z5.array(VerbNameSchema).optional()
707
+ }).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
708
+ message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
620
709
  });
621
- var RetirementProposalSchema = z4.object({
710
+ var ReallocationProposalSchema = z5.object({
711
+ role: NameSchema,
712
+ changes: RoleChangesSchema,
713
+ description: z5.string().min(1)
714
+ });
715
+ var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
716
+ function applyRoleChanges(role2, changes) {
717
+ return {
718
+ ...role2,
719
+ ...changes.maxReplicas === void 0 ? {} : { maxReplicas: changes.maxReplicas },
720
+ ...changes.backlogThreshold === void 0 ? {} : { backlogThreshold: changes.backlogThreshold },
721
+ ...changes.wakeTriggers === void 0 ? {} : { wakeTriggers: [...changes.wakeTriggers] },
722
+ ...changes.verbs === void 0 ? {} : { verbs: [...changes.verbs] }
723
+ };
724
+ }
725
+ function listedOrNone(values) {
726
+ return values.length === 0 ? "none" : values.join(", ");
727
+ }
728
+ function roleChangeLines(role2, changes) {
729
+ const lines = [];
730
+ if (changes.maxReplicas !== void 0 && changes.maxReplicas !== role2.maxReplicas) {
731
+ lines.push(`maxReplicas ${role2.maxReplicas} \u2192 ${changes.maxReplicas}`);
732
+ }
733
+ if (changes.backlogThreshold !== void 0 && changes.backlogThreshold !== role2.backlogThreshold) {
734
+ lines.push(`backlogThreshold ${role2.backlogThreshold} \u2192 ${changes.backlogThreshold}`);
735
+ }
736
+ const wakes = changes.wakeTriggers;
737
+ if (wakes !== void 0 && (wakes.length !== role2.wakeTriggers.length || wakes.some((trigger) => !role2.wakeTriggers.includes(trigger)))) {
738
+ lines.push(`wakeTriggers ${listedOrNone(role2.wakeTriggers)} \u2192 ${listedOrNone(wakes)}`);
739
+ }
740
+ if (changes.verbs !== void 0) {
741
+ const next = changes.verbs;
742
+ const added = next.filter((verb) => !role2.verbs.includes(verb)).map((verb) => `+${verb}`);
743
+ const removed = role2.verbs.filter((verb) => !next.includes(verb)).map((verb) => `\u2212${verb}`);
744
+ if (added.length + removed.length > 0) {
745
+ lines.push(`verbs ${[...added, ...removed].join(" ")}`);
746
+ }
747
+ }
748
+ return lines;
749
+ }
750
+ var RetirementProposalSchema = z5.object({
622
751
  agent: NameSchema,
623
- reason: z4.string().min(1)
752
+ reason: z5.string().min(1)
624
753
  });
625
- var ArchiveProposalSchema = z4.object({
754
+ var ArchiveProposalSchema = z5.object({
626
755
  project: NameSchema,
627
- reason: z4.string().min(1)
756
+ reason: z5.string().min(1)
628
757
  });
629
- var SkillProposalSchema = z4.object({
758
+ var SkillProposalSchema = z5.object({
630
759
  name: NameSchema,
631
- summary: z4.string().min(1).max(200),
632
- body: z4.string().min(1).optional()
760
+ summary: z5.string().min(1).max(200),
761
+ body: z5.string().min(1).optional()
633
762
  });
634
763
  var ProposalCharterSchemas = {
635
764
  role: RoleCharterSchema,
@@ -649,6 +778,32 @@ var ROLE_KIND_APPROVERS = {
649
778
  reallocation: [USER_ROLE, "steward"],
650
779
  skill: [USER_ROLE, "steward"]
651
780
  };
781
+ var USER_DECIDED_ROLE_CHANGES = ["verbs", "maxReplicas"];
782
+ function proposalApprovers(kind, charter) {
783
+ if (kind === "reallocation") {
784
+ const parsed = ReallocationProposalSchema.safeParse(charter);
785
+ if (parsed.success && USER_DECIDED_ROLE_CHANGES.some((field) => parsed.data.changes[field] !== void 0)) {
786
+ return [USER_ROLE];
787
+ }
788
+ }
789
+ return ROLE_KIND_APPROVERS[kind];
790
+ }
791
+ function describeRoleChanges(changes) {
792
+ const parts = [];
793
+ if (changes.maxReplicas !== void 0) {
794
+ parts.push(`up to ${changes.maxReplicas} per project`);
795
+ }
796
+ if (changes.backlogThreshold !== void 0) {
797
+ parts.push(`backlog threshold ${changes.backlogThreshold}`);
798
+ }
799
+ if (changes.wakeTriggers !== void 0) {
800
+ parts.push(changes.wakeTriggers.length === 0 ? "no ambient wakes" : `wakes on ${changes.wakeTriggers.join(", ")}`);
801
+ }
802
+ if (changes.verbs !== void 0) {
803
+ parts.push(`${changes.verbs.length} verbs`);
804
+ }
805
+ return parts.join(", ");
806
+ }
652
807
  function describeCharter(kind, charter) {
653
808
  switch (kind) {
654
809
  case "member": {
@@ -680,9 +835,14 @@ function describeCharter(kind, charter) {
680
835
  }
681
836
  case "reallocation": {
682
837
  const parsed = ReallocationProposalSchema.safeParse(charter);
683
- if (!parsed.success)
838
+ if (parsed.success) {
839
+ const { role: role2, changes, description } = parsed.data;
840
+ return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
841
+ }
842
+ const legacy = LegacyReallocationSchema.safeParse(charter);
843
+ if (!legacy.success)
684
844
  break;
685
- return `reallocation: ${parsed.data.description}`;
845
+ return `reallocation: ${legacy.data.description}`;
686
846
  }
687
847
  case "skill": {
688
848
  const parsed = SkillProposalSchema.safeParse(charter);
@@ -701,23 +861,23 @@ function describeCharter(kind, charter) {
701
861
  }
702
862
  return `${kind} ${JSON.stringify(charter)}`;
703
863
  }
704
- var SkillSchema = z4.object({
864
+ var SkillSchema = z5.object({
705
865
  name: NameSchema,
706
- summary: z4.string().default(""),
707
- scope: z4.enum(["own", "society"]),
708
- path: z4.string().min(1)
866
+ summary: z5.string().default(""),
867
+ scope: z5.enum(["own", "society"]),
868
+ path: z5.string().min(1)
709
869
  });
710
- var RemovedSkillSchema = z4.object({
870
+ var RemovedSkillSchema = z5.object({
711
871
  name: NameSchema,
712
- reason: z4.string().min(1),
872
+ reason: z5.string().min(1),
713
873
  removedBy: NameSchema,
714
874
  removedAt: IsoDateTimeSchema
715
875
  });
716
- var HomeConflictSchema = z4.object({
876
+ var HomeConflictSchema = z5.object({
717
877
  /** The copy, relative to the home. */
718
- path: z4.string().min(1),
878
+ path: z5.string().min(1),
719
879
  /** The file beside it, whose version the board kept. */
720
- file: z4.string().min(1),
880
+ file: z5.string().min(1),
721
881
  /** When the copy reached the board. */
722
882
  since: IsoDateTimeSchema
723
883
  });
@@ -725,79 +885,79 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
725
885
  function conflictCopyOf(copy) {
726
886
  return CONFLICT_COPY.exec(copy)?.[1] ?? null;
727
887
  }
728
- var CommitIdSchema = z4.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
729
- var HomeFileChangeSchema = z4.object({
730
- path: z4.string().min(1),
731
- status: z4.enum(["added", "modified", "deleted"]),
888
+ var CommitIdSchema = z5.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
889
+ var HomeFileChangeSchema = z5.object({
890
+ path: z5.string().min(1),
891
+ status: z5.enum(["added", "modified", "deleted"]),
732
892
  /** Lines added and removed, or null for a binary file. */
733
- added: z4.number().int().nonnegative().nullable(),
734
- removed: z4.number().int().nonnegative().nullable()
893
+ added: z5.number().int().nonnegative().nullable(),
894
+ removed: z5.number().int().nonnegative().nullable()
735
895
  });
736
- var HomeChangeSchema = z4.object({
896
+ var HomeChangeSchema = z5.object({
737
897
  commit: CommitIdSchema,
738
898
  at: IsoDateTimeSchema,
739
- kind: z4.enum(["turn", "board", "merge"]),
740
- author: z4.string(),
899
+ kind: z5.enum(["turn", "board", "merge"]),
900
+ author: z5.string(),
741
901
  /** The turn that made it, from the runner's commit message. */
742
902
  turnId: UlidSchema.optional(),
743
- subject: z4.string(),
744
- files: z4.array(HomeFileChangeSchema)
903
+ subject: z5.string(),
904
+ files: z5.array(HomeFileChangeSchema)
745
905
  });
746
- var HomeHistorySchema = z4.object({
747
- changes: z4.array(HomeChangeSchema),
906
+ var HomeHistorySchema = z5.object({
907
+ changes: z5.array(HomeChangeSchema),
748
908
  /** Whether older commits remain past the ones read. */
749
- more: z4.boolean()
909
+ more: z5.boolean()
750
910
  });
751
911
  var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
752
- patch: z4.string().nullable(),
912
+ patch: z5.string().nullable(),
753
913
  /** The patch was cut at its size limit. */
754
- truncated: z4.boolean()
914
+ truncated: z5.boolean()
755
915
  });
756
- var KnowledgeSchema = z4.object({
916
+ var KnowledgeSchema = z5.object({
757
917
  topic: NameSchema,
758
918
  project: NameSchema.nullable(),
759
- revision: z4.number().int().min(0),
919
+ revision: z5.number().int().min(0),
760
920
  updatedBy: NameSchema,
761
921
  updatedAt: IsoDateTimeSchema
762
922
  });
763
- var RemovedKnowledgeSchema = z4.object({
923
+ var RemovedKnowledgeSchema = z5.object({
764
924
  topic: NameSchema,
765
925
  project: NameSchema.nullable(),
766
926
  removedBy: NameSchema,
767
927
  removedAt: IsoDateTimeSchema
768
928
  });
769
- var DashboardSchema = z4.object({
929
+ var DashboardSchema = z5.object({
770
930
  project: NameSchema,
771
- revision: z4.number().int().min(0),
931
+ revision: z5.number().int().min(0),
772
932
  /** Absent until someone updates the dashboard the project began with. */
773
933
  updatedBy: NameSchema.optional(),
774
934
  updatedAt: IsoDateTimeSchema.optional(),
775
- body: z4.string()
935
+ body: z5.string()
776
936
  });
777
- var ProposalOriginSchema = z4.object({
937
+ var ProposalOriginSchema = z5.object({
778
938
  scope: NameSchema,
779
939
  thread: UlidSchema.optional(),
780
940
  channel: NameSchema.optional()
781
941
  });
782
942
  var SKILL_FILES_LIMIT = 100;
783
943
  var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
784
- var SkillFileSchema = z4.object({
785
- path: z4.string().min(1),
786
- size: z4.number().int().nonnegative(),
787
- executable: z4.boolean()
944
+ var SkillFileSchema = z5.object({
945
+ path: z5.string().min(1),
946
+ size: z5.number().int().nonnegative(),
947
+ executable: z5.boolean()
788
948
  });
789
- var SkillFilesSchema = z4.object({
949
+ var SkillFilesSchema = z5.object({
790
950
  /** The commit of the proposer's home the files came from. */
791
951
  commit: CommitIdSchema,
792
952
  takenAt: IsoDateTimeSchema,
793
- files: z4.array(SkillFileSchema)
953
+ files: z5.array(SkillFileSchema)
794
954
  });
795
955
  var SkillFileContentSchema = SkillFileSchema.extend({
796
- text: z4.string().nullable(),
956
+ text: z5.string().nullable(),
797
957
  /** The text was cut at its size limit. */
798
- truncated: z4.boolean()
958
+ truncated: z5.boolean()
799
959
  });
800
- var ProposalFrontmatterSchema = z4.object({
960
+ var ProposalFrontmatterSchema = z5.object({
801
961
  id: UlidSchema,
802
962
  kind: ProposalKindSchema,
803
963
  proposedBy: NameSchema,
@@ -809,146 +969,146 @@ var ProposalFrontmatterSchema = z4.object({
809
969
  skillFiles: SkillFilesSchema.optional(),
810
970
  decidedBy: NameSchema.optional(),
811
971
  decidedAt: IsoDateTimeSchema.optional(),
812
- reason: z4.string().optional(),
813
- charter: z4.record(z4.string(), z4.unknown()),
972
+ reason: z5.string().optional(),
973
+ charter: z5.record(z5.string(), z5.unknown()),
814
974
  /** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
815
975
  provisionedAt: IsoDateTimeSchema.optional(),
816
- provision: z4.record(z4.string(), z4.unknown()).optional()
976
+ provision: z5.record(z5.string(), z5.unknown()).optional()
817
977
  });
818
- var DecisionSchema = z4.object({
978
+ var DecisionSchema = z5.object({
819
979
  id: UlidSchema,
820
980
  proposalId: UlidSchema,
821
981
  decidedBy: NameSchema,
822
- outcome: z4.enum(["approved", "rejected"]),
823
- reason: z4.string().optional(),
982
+ outcome: z5.enum(["approved", "rejected"]),
983
+ reason: z5.string().optional(),
824
984
  ts: IsoDateTimeSchema
825
985
  });
826
986
 
827
987
  // packages/shared/dist/verbs.js
828
- import { z as z5 } from "zod";
829
- var MomentSchema = z5.iso.datetime({ offset: true });
830
- var CronTitleSchema = z5.string().trim().min(1).max(120);
831
- var CronNoteSchema = z5.string().trim().min(1).max(4e3);
988
+ import { z as z6 } from "zod";
989
+ var MomentSchema = z6.iso.datetime({ offset: true });
990
+ var CronTitleSchema = z6.string().trim().min(1).max(120);
991
+ var CronNoteSchema = z6.string().trim().min(1).max(4e3);
832
992
  var VerbInputs = {
833
- post_message: z5.object({
993
+ post_message: z6.object({
834
994
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
835
995
  channel: ChannelRefSchema.optional(),
836
- body: z5.string().min(1),
996
+ body: z6.string().min(1),
837
997
  thread_id: UlidSchema.optional()
838
998
  }),
839
- read_inbox: z5.object({
999
+ read_inbox: z6.object({
840
1000
  since_cursor: UlidSchema.nullable().optional(),
841
- limit: z5.number().int().positive().max(200).default(50),
842
- advance: z5.boolean().default(true)
1001
+ limit: z6.number().int().positive().max(200).default(50),
1002
+ advance: z6.boolean().default(true)
843
1003
  }),
844
- search: z5.object({
845
- query: z5.string().min(1),
1004
+ search: z6.object({
1005
+ query: z6.string().min(1),
846
1006
  project: NameSchema.optional(),
847
1007
  channel: ChannelRefSchema.optional(),
848
- limit: z5.number().int().positive().max(100).default(20)
1008
+ limit: z6.number().int().positive().max(100).default(20)
849
1009
  }),
850
1010
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
851
- open_thread: z5.object({
1011
+ open_thread: z6.object({
852
1012
  task_id: UlidSchema.optional(),
853
1013
  proposal_id: UlidSchema.optional(),
854
1014
  channel: ChannelRefSchema.optional(),
855
- title: z5.string().min(1).max(200).optional()
1015
+ title: z6.string().min(1).max(200).optional()
856
1016
  }),
857
- close_thread: z5.object({ thread_id: UlidSchema, summary: z5.string().min(1) }),
858
- create_task: z5.object({
1017
+ close_thread: z6.object({ thread_id: UlidSchema, summary: z6.string().min(1) }),
1018
+ create_task: z6.object({
859
1019
  project: NameSchema,
860
- title: z5.string().min(1).max(200),
861
- body: z5.string().default(""),
1020
+ title: z6.string().min(1).max(200),
1021
+ body: z6.string().default(""),
862
1022
  parent_id: UlidSchema.optional(),
863
- required_capabilities: z5.array(z5.string()).default([]),
1023
+ required_capabilities: z6.array(z6.string()).default([]),
864
1024
  /** The plan; without it the task gets one stage, work, that anyone in the project may take. */
865
- stages: z5.array(PlanStageSchema).min(1).optional(),
1025
+ stages: z6.array(PlanStageSchema).min(1).optional(),
866
1026
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
867
1027
  channel: NameSchema.default("general")
868
1028
  }),
869
- claim_task: z5.object({ task_id: UlidSchema }),
870
- release_task: z5.object({ task_id: UlidSchema }),
871
- update_task: z5.object({
1029
+ claim_task: z6.object({ task_id: UlidSchema }),
1030
+ release_task: z6.object({ task_id: UlidSchema }),
1031
+ update_task: z6.object({
872
1032
  task_id: UlidSchema,
873
1033
  /** Move the task back to this earlier stage. */
874
1034
  stage: StageIdSchema.optional(),
875
- status: z5.enum(["abandoned"]).optional(),
1035
+ status: z6.enum(["abandoned"]).optional(),
876
1036
  /** Posted to the task's thread as the caller, marked with the step when the task moved. */
877
- note: z5.string().min(1).optional(),
878
- blocked_by: z5.array(UlidSchema).optional(),
1037
+ note: z6.string().min(1).optional(),
1038
+ blocked_by: z6.array(UlidSchema).optional(),
879
1039
  /**
880
1040
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
881
1041
  * commit's subject and body; null unlinks it.
882
1042
  */
883
- pull_request: z5.object({
1043
+ pull_request: z6.object({
884
1044
  url: PullRequestUrlSchema,
885
1045
  merge_subject: MergeSubjectSchema.optional(),
886
- merge_body: z5.string().max(2e4).optional()
1046
+ merge_body: z6.string().max(2e4).optional()
887
1047
  }).nullable().optional()
888
1048
  }),
889
- get_task: z5.object({ task_id: UlidSchema }),
890
- subscribe: z5.object({ channel: ChannelRefSchema }),
891
- unsubscribe: z5.object({ channel: ChannelRefSchema }),
892
- propose: z5.object({
1049
+ get_task: z6.object({ task_id: UlidSchema }),
1050
+ subscribe: z6.object({ channel: ChannelRefSchema }),
1051
+ unsubscribe: z6.object({ channel: ChannelRefSchema }),
1052
+ propose: z6.object({
893
1053
  kind: ProposalKindSchema,
894
- charter: z5.record(z5.string(), z5.unknown()),
895
- rationale: z5.string().default("")
1054
+ charter: z6.record(z6.string(), z6.unknown()),
1055
+ rationale: z6.string().default("")
896
1056
  }),
897
- approve: z5.object({ proposal_id: UlidSchema, reason: z5.string().optional() }),
898
- reject: z5.object({ proposal_id: UlidSchema, reason: z5.string().min(1) }),
899
- create_project: z5.object({
1057
+ approve: z6.object({ proposal_id: UlidSchema, reason: z6.string().optional() }),
1058
+ reject: z6.object({ proposal_id: UlidSchema, reason: z6.string().min(1) }),
1059
+ create_project: z6.object({
900
1060
  slug: NameSchema,
901
- name: z5.string().min(1).optional(),
902
- repo: z5.string().min(1).nullable().default(null),
1061
+ name: z6.string().min(1).optional(),
1062
+ repo: z6.string().min(1).nullable().default(null),
903
1063
  default_branch: DefaultBranchSchema.default("main"),
904
1064
  on_done: CompletionEffectSchema.optional()
905
1065
  }),
906
- join_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
907
- leave_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
908
- write_knowledge: z5.object({
1066
+ join_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1067
+ leave_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1068
+ write_knowledge: z6.object({
909
1069
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
910
1070
  project: NameSchema.nullable().default(null),
911
1071
  topic: NameSchema,
912
- body: z5.string().min(1),
1072
+ body: z6.string().min(1),
913
1073
  /** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
914
- revision: z5.number().int().min(0)
1074
+ revision: z6.number().int().min(0)
915
1075
  }),
916
- remove_knowledge: z5.object({
1076
+ remove_knowledge: z6.object({
917
1077
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
918
1078
  project: NameSchema.nullable().default(null),
919
1079
  topic: NameSchema
920
1080
  }),
921
- plan_task: z5.object({
1081
+ plan_task: z6.object({
922
1082
  task_id: UlidSchema,
923
- stages: z5.array(PlanEditStageSchema),
1083
+ stages: z6.array(PlanEditStageSchema),
924
1084
  on_done: CompletionEffectSchema.optional()
925
1085
  }),
926
1086
  /** The note is posted to the task's thread as the caller, marked with the step. */
927
- advance_task: z5.object({ task_id: UlidSchema, note: z5.string().min(1).optional() }),
928
- configure_project: z5.object({
1087
+ advance_task: z6.object({ task_id: UlidSchema, note: z6.string().min(1).optional() }),
1088
+ configure_project: z6.object({
929
1089
  project: NameSchema,
930
1090
  on_done: CompletionEffectSchema.optional(),
931
1091
  /** A new display name; the slug, which everything refers to the project by, never changes. */
932
- name: z5.string().trim().min(1).max(120).optional(),
1092
+ name: z6.string().trim().min(1).max(120).optional(),
933
1093
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
934
1094
  default_branch: DefaultBranchSchema.optional()
935
1095
  }),
936
- archive_project: z5.object({ project: NameSchema, reason: z5.string().min(1) }),
937
- create_channel: z5.object({
1096
+ archive_project: z6.object({ project: NameSchema, reason: z6.string().min(1) }),
1097
+ create_channel: z6.object({
938
1098
  /** A project, or null for a channel of the society. */
939
1099
  project: NameSchema.nullable().default(null),
940
1100
  name: NameSchema,
941
- purpose: z5.string().min(1)
1101
+ purpose: z6.string().min(1)
942
1102
  }),
943
- archive_channel: z5.object({ channel: ChannelRefSchema, reason: z5.string().min(1) }),
944
- update_dashboard: z5.object({
1103
+ archive_channel: z6.object({ channel: ChannelRefSchema, reason: z6.string().min(1) }),
1104
+ update_dashboard: z6.object({
945
1105
  project: NameSchema,
946
1106
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
947
- body: z5.string().min(1),
1107
+ body: z6.string().min(1),
948
1108
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
949
- revision: z5.number().int().min(0)
1109
+ revision: z6.number().int().min(0)
950
1110
  }),
951
- create_cron: z5.object({
1111
+ create_cron: z6.object({
952
1112
  title: CronTitleSchema,
953
1113
  /** What to do when it fires; quoted in the prompt of every turn it starts. */
954
1114
  note: CronNoteSchema,
@@ -967,10 +1127,10 @@ var VerbInputs = {
967
1127
  /** An open channel of that place other than general, whose conversation it fires in. */
968
1128
  channel: NameSchema.optional()
969
1129
  }),
970
- update_cron: z5.object({
1130
+ update_cron: z6.object({
971
1131
  cron_id: UlidSchema,
972
1132
  /** Pause it, or resume it, which starts its schedule afresh from now. */
973
- paused: z5.boolean().optional(),
1133
+ paused: z6.boolean().optional(),
974
1134
  title: CronTitleSchema.optional(),
975
1135
  note: CronNoteSchema.optional(),
976
1136
  /** A new expression, or `at` for one time; either starts the schedule afresh. */
@@ -979,12 +1139,12 @@ var VerbInputs = {
979
1139
  timezone: TimeZoneSchema.nullable().optional(),
980
1140
  at: MomentSchema.optional()
981
1141
  }),
982
- remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) }),
983
- remove_skill: z5.object({ name: NameSchema, reason: z5.string().trim().min(1) })
1142
+ remove_cron: z6.object({ cron_id: UlidSchema, reason: z6.string().trim().min(1) }),
1143
+ remove_skill: z6.object({ name: NameSchema, reason: z6.string().trim().min(1) })
984
1144
  };
985
1145
 
986
1146
  // packages/shared/dist/events.js
987
- import { z as z6 } from "zod";
1147
+ import { z as z7 } from "zod";
988
1148
  var BOARD_EVENT_TYPES = [
989
1149
  "society.initialized",
990
1150
  "project.added",
@@ -1029,6 +1189,7 @@ var BOARD_EVENT_TYPES = [
1029
1189
  "user.signed_out",
1030
1190
  "user.token_rotated",
1031
1191
  "runner.changed",
1192
+ "runner.quota",
1032
1193
  "ops.signal",
1033
1194
  "paused.changed",
1034
1195
  "wake.requested",
@@ -1049,28 +1210,36 @@ var BOARD_EVENT_TYPES = [
1049
1210
  "cron.skipped",
1050
1211
  "cron.ended"
1051
1212
  ];
1052
- var BoardEventTypeSchema = z6.enum(BOARD_EVENT_TYPES);
1053
- var BoardEventSchema = z6.object({
1213
+ var BoardEventTypeSchema = z7.enum(BOARD_EVENT_TYPES);
1214
+ var BoardEventSchema = z7.object({
1054
1215
  id: UlidSchema,
1055
1216
  ts: IsoDateTimeSchema,
1056
1217
  type: BoardEventTypeSchema,
1057
1218
  actor: NameSchema,
1058
- payload: z6.record(z6.string(), z6.unknown())
1059
- });
1060
- var UsageSchema = z6.object({
1061
- inputTokens: z6.number().int().nonnegative(),
1062
- outputTokens: z6.number().int().nonnegative(),
1063
- cacheReadTokens: z6.number().int().nonnegative().default(0),
1064
- cacheWriteTokens: z6.number().int().nonnegative().default(0)
1065
- });
1066
- var TurnStatusSchema = z6.object({
1067
- summary: z6.string().min(1),
1068
- claimsHeld: z6.array(UlidSchema).default([]),
1069
- blockedOn: z6.array(z6.string()).default([]),
1070
- needsUserDecision: z6.boolean().default(false),
1071
- memoryUpdated: z6.boolean().default(false)
1072
- });
1073
- var TurnExitReasonSchema = z6.enum([
1219
+ payload: z7.record(z7.string(), z7.unknown())
1220
+ });
1221
+ var UsageSchema = z7.object({
1222
+ inputTokens: z7.number().int().nonnegative(),
1223
+ outputTokens: z7.number().int().nonnegative(),
1224
+ cacheReadTokens: z7.number().int().nonnegative().default(0),
1225
+ cacheWriteTokens: z7.number().int().nonnegative().default(0)
1226
+ });
1227
+ function addUsage(a, b) {
1228
+ return {
1229
+ inputTokens: a.inputTokens + b.inputTokens,
1230
+ outputTokens: a.outputTokens + b.outputTokens,
1231
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
1232
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
1233
+ };
1234
+ }
1235
+ var TurnStatusSchema = z7.object({
1236
+ summary: z7.string().min(1),
1237
+ claimsHeld: z7.array(UlidSchema).default([]),
1238
+ blockedOn: z7.array(z7.string()).default([]),
1239
+ needsUserDecision: z7.boolean().default(false),
1240
+ memoryUpdated: z7.boolean().default(false)
1241
+ });
1242
+ var TurnExitReasonSchema = z7.enum([
1074
1243
  "completed",
1075
1244
  "timeout",
1076
1245
  "error",
@@ -1078,50 +1247,50 @@ var TurnExitReasonSchema = z6.enum([
1078
1247
  "blocked",
1079
1248
  "stopped"
1080
1249
  ]);
1081
- var AgentEventSchema = z6.discriminatedUnion("type", [
1082
- z6.object({
1083
- type: z6.literal("turn_started"),
1250
+ var AgentEventSchema = z7.discriminatedUnion("type", [
1251
+ z7.object({
1252
+ type: z7.literal("turn_started"),
1084
1253
  agent: NameSchema,
1085
- session: z6.string(),
1254
+ session: z7.string(),
1086
1255
  runner: NameSchema,
1087
1256
  /** The model the CLI reports running, when it reports one. */
1088
- model: z6.string().optional()
1257
+ model: z7.string().optional()
1089
1258
  }),
1090
1259
  /**
1091
1260
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
1092
1261
  * messages it delivered and the text the CLI was given.
1093
1262
  */
1094
- z6.object({
1095
- type: z6.literal("steered"),
1096
- steer: z6.string().min(1),
1097
- messages: z6.array(UlidSchema).optional(),
1098
- text: z6.string().optional()
1263
+ z7.object({
1264
+ type: z7.literal("steered"),
1265
+ steer: z7.string().min(1),
1266
+ messages: z7.array(UlidSchema).optional(),
1267
+ text: z7.string().optional()
1099
1268
  }),
1100
- z6.object({ type: z6.literal("text"), delta: z6.string() }),
1101
- z6.object({ type: z6.literal("tool_call"), name: z6.string(), input: z6.unknown() }),
1102
- z6.object({
1103
- type: z6.literal("tool_result"),
1104
- name: z6.string(),
1105
- ok: z6.boolean(),
1269
+ z7.object({ type: z7.literal("text"), delta: z7.string() }),
1270
+ z7.object({ type: z7.literal("tool_call"), name: z7.string(), input: z7.unknown() }),
1271
+ z7.object({
1272
+ type: z7.literal("tool_result"),
1273
+ name: z7.string(),
1274
+ ok: z7.boolean(),
1106
1275
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
1107
- output: z6.string().optional()
1276
+ output: z7.string().optional()
1108
1277
  }),
1109
- z6.object({ type: z6.literal("approval_requested"), kind: z6.string(), detail: z6.unknown() }),
1110
- z6.object({
1111
- type: z6.literal("turn_completed"),
1278
+ z7.object({ type: z7.literal("approval_requested"), kind: z7.string(), detail: z7.unknown() }),
1279
+ z7.object({
1280
+ type: z7.literal("turn_completed"),
1112
1281
  usage: UsageSchema,
1113
- costUsd: z6.number().nonnegative(),
1282
+ costUsd: z7.number().nonnegative(),
1114
1283
  status: TurnStatusSchema.nullable(),
1115
1284
  exitReason: TurnExitReasonSchema
1116
1285
  }),
1117
- z6.object({ type: z6.literal("error"), message: z6.string() })
1286
+ z7.object({ type: z7.literal("error"), message: z7.string() })
1118
1287
  ]);
1119
- var TranscriptEntrySchema = z6.object({
1288
+ var TranscriptEntrySchema = z7.object({
1120
1289
  ts: IsoDateTimeSchema,
1121
1290
  event: AgentEventSchema
1122
1291
  });
1123
- var LiveTurnEventSchema = z6.object({
1124
- seq: z6.number().int().positive(),
1292
+ var LiveTurnEventSchema = z7.object({
1293
+ seq: z7.number().int().positive(),
1125
1294
  ts: IsoDateTimeSchema,
1126
1295
  agent: NameSchema,
1127
1296
  project: NameSchema,
@@ -1133,97 +1302,105 @@ var LiveTurnEventSchema = z6.object({
1133
1302
  });
1134
1303
 
1135
1304
  // packages/shared/dist/turns.js
1136
- import { z as z8 } from "zod";
1305
+ import { z as z9 } from "zod";
1137
1306
 
1138
1307
  // packages/shared/dist/runner.js
1139
- import { z as z7 } from "zod";
1308
+ import { z as z8 } from "zod";
1140
1309
  var HOME_SCRATCH = "scratch";
1141
1310
  var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1142
- var RunnerHelloSchema = z7.object({
1143
- protocol: z7.number().int().positive(),
1144
- version: z7.string().min(1),
1311
+ var RunnerHelloSchema = z8.object({
1312
+ protocol: z8.number().int().positive(),
1313
+ version: z8.string().min(1),
1145
1314
  os: RunnerOsSchema,
1146
- clis: z7.array(CliKindSchema),
1315
+ clis: z8.array(CliKindSchema),
1147
1316
  /** The CLIs whose adapter can keep a session warm between turns. */
1148
- residentClis: z7.array(CliKindSchema).default([]),
1317
+ residentClis: z8.array(CliKindSchema).default([]),
1149
1318
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
1150
- steerableClis: z7.array(CliKindSchema).default([]),
1319
+ steerableClis: z8.array(CliKindSchema).default([]),
1151
1320
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
1152
- stoppableClis: z7.array(CliKindSchema).default([]),
1321
+ stoppableClis: z8.array(CliKindSchema).default([]),
1153
1322
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
1154
- branchReads: z7.boolean().default(false),
1323
+ branchReads: z8.boolean().default(false),
1155
1324
  /** Whether the runner lists what a branch changed; one from before never does. */
1156
- branchChanges: z7.boolean().default(false),
1157
- capabilities: z7.array(z7.string()).default([]),
1325
+ branchChanges: z8.boolean().default(false),
1326
+ /** Whether the runner answers a read of a CLI's quota; one from before never does. */
1327
+ quotaReads: z8.boolean().default(false),
1328
+ capabilities: z8.array(z8.string()).default([]),
1329
+ /**
1330
+ * What the runner's account lacks of the git identity citizens' own commits carry, checked
1331
+ * before each hello outside any repository; empty when it has both. Absent from a runner from
1332
+ * before the check, which no default fills in, so the board marks only what a runner reported.
1333
+ */
1334
+ gitIdentityMissing: z8.array(GitIdentityPartSchema).optional(),
1158
1335
  /** Turns the runner runs at once, or null for no limit. */
1159
- slots: z7.number().int().positive().nullable(),
1336
+ slots: z8.number().int().positive().nullable(),
1160
1337
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1161
- turns: z7.array(UlidSchema).default([])
1338
+ turns: z8.array(UlidSchema).default([])
1162
1339
  });
1163
- var RunnerWelcomeSchema = z7.object({ name: NameSchema, version: z7.string() });
1164
- var RunnerEnrollRequestSchema = z7.object({
1165
- protocol: z7.number().int().positive(),
1166
- version: z7.string().min(1).max(64),
1167
- hostname: z7.string().min(1).max(253),
1340
+ var RunnerWelcomeSchema = z8.object({ name: NameSchema, version: z8.string() });
1341
+ var RunnerEnrollRequestSchema = z8.object({
1342
+ protocol: z8.number().int().positive(),
1343
+ version: z8.string().min(1).max(64),
1344
+ hostname: z8.string().min(1).max(253),
1168
1345
  os: RunnerOsSchema,
1169
- clis: z7.array(CliKindSchema).max(8),
1170
- capabilities: z7.array(z7.string().min(1).max(64)).max(32).default([])
1346
+ clis: z8.array(CliKindSchema).max(8),
1347
+ capabilities: z8.array(z8.string().min(1).max(64)).max(32).default([])
1171
1348
  });
1172
- var RunnerEnrollmentSchema = z7.object({
1173
- deviceCode: z7.string().min(1),
1174
- userCode: z7.string().min(1),
1349
+ var RunnerEnrollmentSchema = z8.object({
1350
+ deviceCode: z8.string().min(1),
1351
+ userCode: z8.string().min(1),
1175
1352
  expiresAt: IsoDateTimeSchema,
1176
- intervalMs: z7.number().int().positive()
1353
+ intervalMs: z8.number().int().positive()
1177
1354
  });
1178
- var RunnerEnrollPollSchema = z7.object({ deviceCode: z7.string().min(1) });
1179
- var RunnerEnrollStatusSchema = z7.discriminatedUnion("status", [
1180
- z7.object({ status: z7.literal("pending") }),
1181
- z7.object({ status: z7.literal("approved"), name: NameSchema, token: z7.string().min(1) }),
1182
- z7.object({ status: z7.literal("denied") })
1355
+ var RunnerEnrollPollSchema = z8.object({ deviceCode: z8.string().min(1) });
1356
+ var RunnerEnrollStatusSchema = z8.discriminatedUnion("status", [
1357
+ z8.object({ status: z8.literal("pending") }),
1358
+ z8.object({ status: z8.literal("approved"), name: NameSchema, token: z8.string().min(1) }),
1359
+ z8.object({ status: z8.literal("denied") })
1183
1360
  ]);
1184
- var PendingEnrollmentSchema = z7.object({
1185
- userCode: z7.string().min(1),
1186
- hostname: z7.string(),
1361
+ var PendingEnrollmentSchema = z8.object({
1362
+ userCode: z8.string().min(1),
1363
+ hostname: z8.string(),
1187
1364
  os: RunnerOsSchema,
1188
- clis: z7.array(CliKindSchema),
1189
- capabilities: z7.array(z7.string()),
1190
- version: z7.string(),
1365
+ clis: z8.array(CliKindSchema),
1366
+ capabilities: z8.array(z8.string()),
1367
+ version: z8.string(),
1191
1368
  requestedAt: IsoDateTimeSchema,
1192
1369
  expiresAt: IsoDateTimeSchema
1193
1370
  });
1194
- var ProjectRepoSchema = z7.object({
1371
+ var ProjectRepoSchema = z8.object({
1195
1372
  slug: NameSchema,
1196
- origin: z7.string().nullable(),
1197
- defaultBranch: z7.string().min(1),
1373
+ origin: z8.string().nullable(),
1374
+ defaultBranch: z8.string().min(1),
1198
1375
  /**
1199
1376
  * The board lands finished tasks on the default branch, itself or through their pull requests,
1200
1377
  * so the runner lets nothing else move it.
1201
1378
  */
1202
- boardLands: z7.boolean()
1379
+ boardLands: z8.boolean()
1203
1380
  });
1204
- var TurnWorkspaceSchema = z7.discriminatedUnion("kind", [
1205
- z7.object({
1206
- kind: z7.literal("home"),
1381
+ var TurnWorkspaceSchema = z8.discriminatedUnion("kind", [
1382
+ z8.object({
1383
+ kind: z8.literal("home"),
1207
1384
  thread: UlidSchema.optional(),
1208
1385
  channel: NameSchema.optional()
1209
1386
  }),
1210
- z7.object({
1211
- kind: z7.literal("project"),
1387
+ z8.object({
1388
+ kind: z8.literal("project"),
1212
1389
  repo: ProjectRepoSchema,
1213
- branches: z7.array(z7.string().min(1)),
1390
+ branches: z8.array(z8.string().min(1)),
1214
1391
  thread: UlidSchema.optional(),
1215
1392
  channel: NameSchema.optional()
1216
1393
  }),
1217
- z7.object({ kind: z7.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1394
+ z8.object({ kind: z8.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1218
1395
  ]);
1219
- var TurnJobSchema = z7.object({
1396
+ var TurnJobSchema = z8.object({
1220
1397
  turnId: UlidSchema,
1221
1398
  agent: NameSchema,
1222
1399
  role: NameSchema,
1223
1400
  cli: CliKindSchema,
1224
- model: z7.string().optional(),
1401
+ model: z8.string().optional(),
1225
1402
  /** The reasoning effort set on the agent; absent for the model's own default. */
1226
- effort: z7.string().optional(),
1403
+ effort: z8.string().optional(),
1227
1404
  /** A project slug, or the society scope. */
1228
1405
  scope: NameSchema,
1229
1406
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1234,125 +1411,130 @@ var TurnJobSchema = z7.object({
1234
1411
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1235
1412
  * the outcome reports it.
1236
1413
  */
1237
- session: z7.string().min(1).nullable(),
1414
+ session: z8.string().min(1).nullable(),
1238
1415
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1239
- costSoFarUsd: z7.number().nonnegative(),
1416
+ costSoFarUsd: z8.number().nonnegative(),
1240
1417
  /** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
1241
- instructions: z7.string(),
1242
- prompt: z7.string(),
1243
- mcp: z7.object({ url: z7.string().min(1), token: z7.string().min(1) }),
1244
- limits: z7.object({
1245
- timeoutMs: z7.number().int().positive().nullable(),
1246
- maxTurns: z7.number().int().positive().nullable()
1418
+ instructions: z8.string(),
1419
+ prompt: z8.string(),
1420
+ mcp: z8.object({ url: z8.string().min(1), token: z8.string().min(1) }),
1421
+ limits: z8.object({
1422
+ timeoutMs: z8.number().int().positive().nullable(),
1423
+ maxTurns: z8.number().int().positive().nullable()
1247
1424
  }),
1248
1425
  /** Present when the runner keeps the conversation's session warm between turns, under `key`. */
1249
- resident: z7.object({ key: z7.string().min(1), idleMs: z7.number().int().positive() }).optional(),
1426
+ resident: z8.object({ key: z8.string().min(1), idleMs: z8.number().int().positive() }).optional(),
1250
1427
  workspace: TurnWorkspaceSchema
1251
1428
  });
1252
- var TurnWorkSchema = z7.object({ branch: z7.string().min(1), head: z7.string().min(1) });
1253
- var TurnEventsSchema = z7.object({ entries: z7.array(TranscriptEntrySchema) });
1254
- var TurnOutcomeSchema = z7.object({
1429
+ var TurnWorkSchema = z8.object({ branch: z8.string().min(1), head: z8.string().min(1) });
1430
+ var TurnEventsSchema = z8.object({ entries: z8.array(TranscriptEntrySchema) });
1431
+ var TurnOutcomeSchema = z8.object({
1255
1432
  exitReason: TurnExitReasonSchema,
1256
1433
  status: TurnStatusSchema.nullable(),
1257
- error: z7.string().nullable(),
1434
+ error: z8.string().nullable(),
1258
1435
  usage: UsageSchema,
1259
- costUsd: z7.number().nonnegative(),
1436
+ costUsd: z8.number().nonnegative(),
1260
1437
  /** The session's running total after this turn, for CLIs that report one. */
1261
- sessionCostUsd: z7.number().nonnegative().optional(),
1438
+ sessionCostUsd: z8.number().nonnegative().optional(),
1262
1439
  /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1263
- session: z7.string().min(1),
1440
+ session: z8.string().min(1),
1264
1441
  /** The model the CLI reported, when it reported one. */
1265
- model: z7.string().nullable(),
1442
+ model: z8.string().nullable(),
1266
1443
  work: TurnWorkSchema.nullable(),
1267
1444
  /**
1268
1445
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1269
1446
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1270
1447
  */
1271
- leftovers: z7.boolean().default(false)
1448
+ leftovers: z8.boolean().default(false),
1449
+ /**
1450
+ * The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
1451
+ * before never sends it.
1452
+ */
1453
+ quota: CliQuotaSchema.optional()
1272
1454
  });
1273
- var TurnAckSchema = z7.object({ dropWorkspace: z7.boolean() });
1274
- var LandRequestSchema = z7.object({
1455
+ var TurnAckSchema = z8.object({ dropWorkspace: z8.boolean() });
1456
+ var LandRequestSchema = z8.object({
1275
1457
  repo: ProjectRepoSchema,
1276
- branch: z7.string().min(1),
1458
+ branch: z8.string().min(1),
1277
1459
  /**
1278
1460
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1279
1461
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1280
1462
  * empty body when left out.
1281
1463
  */
1282
- pullRequest: z7.object({
1283
- url: z7.string().min(1),
1284
- subject: z7.string().min(1).optional(),
1285
- body: z7.string().optional()
1464
+ pullRequest: z8.object({
1465
+ url: z8.string().min(1),
1466
+ subject: z8.string().min(1).optional(),
1467
+ body: z8.string().optional()
1286
1468
  }).optional()
1287
1469
  });
1288
- var CloseRequestSchema = z7.object({
1470
+ var CloseRequestSchema = z8.object({
1289
1471
  repo: ProjectRepoSchema,
1290
- url: z7.string().min(1),
1291
- comment: z7.string().min(1)
1472
+ url: z8.string().min(1),
1473
+ comment: z8.string().min(1)
1292
1474
  });
1293
- var MergeOutcomeSchema = z7.object({ ok: z7.boolean(), detail: z7.string() });
1294
- var ModelListSchema = z7.array(ModelOptionSchema);
1475
+ var MergeOutcomeSchema = z8.object({ ok: z8.boolean(), detail: z8.string() });
1476
+ var ModelListSchema = z8.array(ModelOptionSchema);
1295
1477
  var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1296
- 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" });
1297
- var BranchNameSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1298
- var BranchReadSchema = z7.object({
1478
+ var BranchPathSchema = z8.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" });
1479
+ var BranchNameSchema = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1480
+ var BranchReadSchema = z8.object({
1299
1481
  repo: ProjectRepoSchema,
1300
1482
  branch: BranchNameSchema,
1301
1483
  path: BranchPathSchema
1302
1484
  });
1303
- var BranchCommitSchema = z7.object({
1304
- id: z7.string().min(1),
1485
+ var BranchCommitSchema = z8.object({
1486
+ id: z8.string().min(1),
1305
1487
  at: IsoDateTimeSchema,
1306
- author: z7.string()
1488
+ author: z8.string()
1307
1489
  });
1308
- var BranchFileSchema = z7.discriminatedUnion("kind", [
1309
- z7.object({
1310
- kind: z7.literal("file"),
1490
+ var BranchFileSchema = z8.discriminatedUnion("kind", [
1491
+ z8.object({
1492
+ kind: z8.literal("file"),
1311
1493
  path: BranchPathSchema,
1312
1494
  commit: BranchCommitSchema,
1313
- size: z7.number().int().nonnegative(),
1314
- content: z7.string().nullable()
1495
+ size: z8.number().int().nonnegative(),
1496
+ content: z8.string().nullable()
1315
1497
  }),
1316
- z7.object({
1317
- kind: z7.literal("dir"),
1498
+ z8.object({
1499
+ kind: z8.literal("dir"),
1318
1500
  path: BranchPathSchema,
1319
1501
  commit: BranchCommitSchema,
1320
- entries: z7.array(z7.object({
1321
- name: z7.string().min(1),
1322
- kind: z7.enum(["file", "dir"]),
1323
- size: z7.number().int().nonnegative().nullable()
1502
+ entries: z8.array(z8.object({
1503
+ name: z8.string().min(1),
1504
+ kind: z8.enum(["file", "dir"]),
1505
+ size: z8.number().int().nonnegative().nullable()
1324
1506
  }))
1325
1507
  })
1326
1508
  ]);
1327
- var BranchChangesReadSchema = z7.object({
1509
+ var BranchChangesReadSchema = z8.object({
1328
1510
  repo: ProjectRepoSchema,
1329
1511
  branch: BranchNameSchema
1330
1512
  });
1331
- var BranchFileChangeSchema = z7.object({
1332
- path: z7.string().min(1),
1333
- status: z7.enum(["added", "modified", "deleted"]),
1513
+ var BranchFileChangeSchema = z8.object({
1514
+ path: z8.string().min(1),
1515
+ status: z8.enum(["added", "modified", "deleted"]),
1334
1516
  /** Lines added and removed, or null for a binary file. */
1335
- added: z7.number().int().nonnegative().nullable(),
1336
- removed: z7.number().int().nonnegative().nullable(),
1517
+ added: z8.number().int().nonnegative().nullable(),
1518
+ removed: z8.number().int().nonnegative().nullable(),
1337
1519
  /** Absent when the file changed only through a merge of another branch. */
1338
- lastChange: z7.object({ author: z7.string(), at: IsoDateTimeSchema }).optional()
1520
+ lastChange: z8.object({ author: z8.string(), at: IsoDateTimeSchema }).optional()
1339
1521
  });
1340
- var BranchChangesSchema = z7.object({
1522
+ var BranchChangesSchema = z8.object({
1341
1523
  head: BranchCommitSchema,
1342
- files: z7.array(BranchFileChangeSchema),
1343
- total: z7.number().int().nonnegative()
1524
+ files: z8.array(BranchFileChangeSchema),
1525
+ total: z8.number().int().nonnegative()
1344
1526
  });
1345
- var BranchDiffReadSchema = z7.object({
1527
+ var BranchDiffReadSchema = z8.object({
1346
1528
  repo: ProjectRepoSchema,
1347
1529
  branch: BranchNameSchema,
1348
1530
  path: BranchPathSchema.min(1)
1349
1531
  });
1350
- var BranchFileDiffSchema = z7.object({
1532
+ var BranchFileDiffSchema = z8.object({
1351
1533
  head: BranchCommitSchema,
1352
1534
  path: BranchPathSchema,
1353
1535
  status: BranchFileChangeSchema.shape.status,
1354
- patch: z7.string().nullable(),
1355
- truncated: z7.boolean()
1536
+ patch: z8.string().nullable(),
1537
+ truncated: z8.boolean()
1356
1538
  });
1357
1539
  function patchHunks(patch, limit) {
1358
1540
  if (/^Binary files .* differ$/m.test(patch)) {
@@ -1366,64 +1548,69 @@ function patchHunks(patch, limit) {
1366
1548
  const cut = hunks.lastIndexOf("\n", limit);
1367
1549
  return { patch: hunks.slice(0, cut === -1 ? limit : cut), truncated: true };
1368
1550
  }
1369
- var WorkspaceConversationSchema = z7.discriminatedUnion("kind", [
1370
- z7.object({ kind: z7.literal("thread"), id: UlidSchema }),
1371
- z7.object({ kind: z7.literal("channel"), scope: NameSchema, name: NameSchema })
1551
+ var WorkspaceConversationSchema = z8.discriminatedUnion("kind", [
1552
+ z8.object({ kind: z8.literal("thread"), id: UlidSchema }),
1553
+ z8.object({ kind: z8.literal("channel"), scope: NameSchema, name: NameSchema })
1372
1554
  ]);
1373
- var HeldWorkspaceSchema = z7.object({
1555
+ var HeldWorkspaceSchema = z8.object({
1374
1556
  agent: NameSchema,
1375
1557
  scope: NameSchema,
1376
1558
  conversation: WorkspaceConversationSchema
1377
1559
  });
1378
- var HeldWorkspacesSchema = z7.object({ workspaces: z7.array(HeldWorkspaceSchema) });
1379
- var SweepResultSchema = z7.object({ kept: z7.array(HeldWorkspaceSchema) });
1380
- var TurnSteerSchema = z7.object({ id: z7.uuid(), text: z7.string().min(1) });
1381
- var RunnerMessageSchema = z7.discriminatedUnion("type", [
1382
- z7.object({ type: z7.literal("turn"), job: TurnJobSchema }),
1383
- z7.object({ type: z7.literal("land"), request: z7.string().min(1), land: LandRequestSchema }),
1560
+ var HeldWorkspacesSchema = z8.object({ workspaces: z8.array(HeldWorkspaceSchema) });
1561
+ var SweepResultSchema = z8.object({ kept: z8.array(HeldWorkspaceSchema) });
1562
+ var TurnSteerSchema = z8.object({ id: z8.uuid(), text: z8.string().min(1) });
1563
+ var RunnerMessageSchema = z8.discriminatedUnion("type", [
1564
+ z8.object({ type: z8.literal("turn"), job: TurnJobSchema }),
1565
+ z8.object({ type: z8.literal("land"), request: z8.string().min(1), land: LandRequestSchema }),
1384
1566
  /** Answered with a `MergeOutcome`. */
1385
- z7.object({ type: z7.literal("close"), request: z7.string().min(1), close: CloseRequestSchema }),
1386
- z7.object({ type: z7.literal("models"), request: z7.string().min(1), cli: CliKindSchema }),
1567
+ z8.object({ type: z8.literal("close"), request: z8.string().min(1), close: CloseRequestSchema }),
1568
+ z8.object({ type: z8.literal("models"), request: z8.string().min(1), cli: CliKindSchema }),
1569
+ /**
1570
+ * Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
1571
+ * read one. Sent only to runners whose hello says `quotaReads`.
1572
+ */
1573
+ z8.object({ type: z8.literal("quota"), request: z8.string().min(1), cli: CliKindSchema }),
1387
1574
  /** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
1388
- z7.object({ type: z7.literal("file"), request: z7.string().min(1), read: BranchReadSchema }),
1575
+ z8.object({ type: z8.literal("file"), request: z8.string().min(1), read: BranchReadSchema }),
1389
1576
  /** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
1390
- z7.object({
1391
- type: z7.literal("changes"),
1392
- request: z7.string().min(1),
1577
+ z8.object({
1578
+ type: z8.literal("changes"),
1579
+ request: z8.string().min(1),
1393
1580
  read: BranchChangesReadSchema
1394
1581
  }),
1395
1582
  /** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
1396
- z7.object({ type: z7.literal("diff"), request: z7.string().min(1), read: BranchDiffReadSchema }),
1583
+ z8.object({ type: z8.literal("diff"), request: z8.string().min(1), read: BranchDiffReadSchema }),
1397
1584
  /** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
1398
- z7.object({
1399
- type: z7.literal("steer"),
1400
- request: z7.string().min(1),
1585
+ z8.object({
1586
+ type: z8.literal("steer"),
1587
+ request: z8.string().min(1),
1401
1588
  turnId: UlidSchema,
1402
1589
  steer: TurnSteerSchema
1403
1590
  }),
1404
1591
  /** Answered with whether the turn was there to stop; it ends `stopped`. */
1405
- z7.object({ type: z7.literal("stop"), request: z7.string().min(1), turnId: UlidSchema }),
1592
+ z8.object({ type: z8.literal("stop"), request: z8.string().min(1), turnId: UlidSchema }),
1406
1593
  /**
1407
1594
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1408
1595
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1409
1596
  */
1410
- z7.object({
1411
- type: z7.literal("sweep"),
1412
- request: z7.string().min(1),
1597
+ z8.object({
1598
+ type: z8.literal("sweep"),
1599
+ request: z8.string().min(1),
1413
1600
  conversation: WorkspaceConversationSchema
1414
1601
  })
1415
1602
  ]);
1416
- var RunnerAnswerSchema = z7.discriminatedUnion("ok", [
1417
- z7.object({ ok: z7.literal(true), value: z7.unknown() }),
1418
- z7.object({ ok: z7.literal(false), error: z7.string() })
1603
+ var RunnerAnswerSchema = z8.discriminatedUnion("ok", [
1604
+ z8.object({ ok: z8.literal(true), value: z8.unknown() }),
1605
+ z8.object({ ok: z8.literal(false), error: z8.string() })
1419
1606
  ]);
1420
- var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
1421
- var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1607
+ var WarmSessionsSchema = z8.object({ warm: z8.array(z8.string()) });
1608
+ var FileManifestSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1422
1609
  function fileVersion(sha, executable) {
1423
1610
  return executable ? `${sha}+x` : sha;
1424
1611
  }
1425
- var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
1426
- var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1612
+ var FileReadSchema = z8.object({ paths: z8.array(z8.string()) });
1613
+ var FileContentsSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1427
1614
  function isSafeRelativePath(file) {
1428
1615
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1429
1616
  return false;
@@ -1432,7 +1619,7 @@ function isSafeRelativePath(file) {
1432
1619
  }
1433
1620
 
1434
1621
  // packages/shared/dist/turns.js
1435
- var TriggerKindSchema = z8.enum([
1622
+ var TriggerKindSchema = z9.enum([
1436
1623
  "mention",
1437
1624
  "stage",
1438
1625
  "task_done",
@@ -1446,11 +1633,11 @@ var TriggerKindSchema = z8.enum([
1446
1633
  "closing",
1447
1634
  "cron"
1448
1635
  ]);
1449
- var TriggerSchema = z8.object({
1636
+ var TriggerSchema = z9.object({
1450
1637
  kind: TriggerKindSchema,
1451
1638
  from: NameSchema.optional(),
1452
- fromUser: z8.boolean().default(false),
1453
- reason: z8.string().default(""),
1639
+ fromUser: z9.boolean().default(false),
1640
+ reason: z9.string().default(""),
1454
1641
  taskId: UlidSchema.optional(),
1455
1642
  messageId: UlidSchema.optional(),
1456
1643
  /** The channel the waking message was posted in. */
@@ -1458,17 +1645,17 @@ var TriggerSchema = z8.object({
1458
1645
  /** The cron that came due, for a `cron` wake. */
1459
1646
  cronId: UlidSchema.optional()
1460
1647
  });
1461
- var TurnThreadSchema = z8.object({ id: UlidSchema, task: z8.boolean() });
1462
- var TurnDispatchSchema = z8.object({
1648
+ var TurnThreadSchema = z9.object({ id: UlidSchema, task: z9.boolean() });
1649
+ var TurnDispatchSchema = z9.object({
1463
1650
  agent: NameSchema,
1464
1651
  project: NameSchema,
1465
1652
  thread: TurnThreadSchema.optional(),
1466
1653
  channel: NameSchema.optional(),
1467
1654
  trigger: TriggerSchema,
1468
- priority: z8.number().int().min(0).max(2),
1469
- onboarding: z8.boolean().default(false),
1655
+ priority: z9.number().int().min(0).max(2),
1656
+ onboarding: z9.boolean().default(false),
1470
1657
  /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1471
- crons: z8.array(UlidSchema).optional()
1658
+ crons: z9.array(UlidSchema).optional()
1472
1659
  });
1473
1660
  function conversationPart(conversation) {
1474
1661
  return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
@@ -1479,7 +1666,7 @@ function channelConversation(scope, channel) {
1479
1666
  const name = at < 0 ? channel : channel.slice(at + 1);
1480
1667
  return place === scope && name !== "general" ? name : void 0;
1481
1668
  }
1482
- var TurnRecordSchema = z8.object({
1669
+ var TurnRecordSchema = z9.object({
1483
1670
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1484
1671
  id: UlidSchema.optional(),
1485
1672
  agent: NameSchema,
@@ -1490,31 +1677,31 @@ var TurnRecordSchema = z8.object({
1490
1677
  channel: NameSchema.optional(),
1491
1678
  runner: NameSchema,
1492
1679
  cli: CliKindSchema.nullable(),
1493
- session: z8.string().nullable(),
1680
+ session: z9.string().nullable(),
1494
1681
  trigger: TriggerSchema,
1495
1682
  startedAt: IsoDateTimeSchema,
1496
1683
  endedAt: IsoDateTimeSchema.nullable(),
1497
1684
  exitReason: TurnExitReasonSchema.nullable(),
1498
1685
  status: TurnStatusSchema.nullable(),
1499
- error: z8.string().nullable(),
1686
+ error: z9.string().nullable(),
1500
1687
  usage: UsageSchema.nullable(),
1501
- costUsd: z8.number().nonnegative().default(0),
1688
+ costUsd: z9.number().nonnegative().default(0),
1502
1689
  /**
1503
1690
  * The running total the CLI reported for the session after this turn, when it reports one. A
1504
1691
  * resumed session starts from it, so the next turn's own cost is measured from here.
1505
1692
  */
1506
- sessionCostUsd: z8.number().nonnegative().optional(),
1507
- toolCalls: z8.number().int().nonnegative().default(0),
1693
+ sessionCostUsd: z9.number().nonnegative().optional(),
1694
+ toolCalls: z9.number().int().nonnegative().default(0),
1508
1695
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1509
- model: z8.string().nullable().default(null),
1696
+ model: z9.string().nullable().default(null),
1510
1697
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1511
1698
  work: TurnWorkSchema.optional(),
1512
1699
  /** Who stopped the turn, for one that ended `stopped`. */
1513
1700
  stoppedBy: NameSchema.optional(),
1514
1701
  /** The crons whose fire started the turn, which record how it ended. */
1515
- crons: z8.array(UlidSchema).optional()
1702
+ crons: z9.array(UlidSchema).optional()
1516
1703
  });
1517
- var RunningTurnSchema = z8.object({
1704
+ var RunningTurnSchema = z9.object({
1518
1705
  turnId: UlidSchema,
1519
1706
  agent: NameSchema,
1520
1707
  scope: NameSchema,
@@ -1522,49 +1709,49 @@ var RunningTurnSchema = z8.object({
1522
1709
  channel: NameSchema.optional(),
1523
1710
  cli: CliKindSchema,
1524
1711
  askedIn: ChannelRefSchema.optional(),
1525
- steerable: z8.boolean(),
1526
- stoppable: z8.boolean()
1712
+ steerable: z9.boolean(),
1713
+ stoppable: z9.boolean()
1527
1714
  });
1528
- var SessionsFileSchema = z8.object({
1529
- claude: z8.string().optional(),
1530
- codex: z8.string().optional(),
1715
+ var SessionsFileSchema = z9.object({
1716
+ claude: z9.string().optional(),
1717
+ codex: z9.string().optional(),
1531
1718
  runner: NameSchema.optional()
1532
1719
  });
1533
- var WakeRequestSchema = z8.object({
1720
+ var WakeRequestSchema = z9.object({
1534
1721
  agent: NameSchema,
1535
1722
  project: NameSchema,
1536
- reason: z8.string().default("manual wake"),
1723
+ reason: z9.string().default("manual wake"),
1537
1724
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1538
- kind: z8.enum(["manual", "reflection"]).default("manual")
1725
+ kind: z9.enum(["manual", "reflection"]).default("manual")
1539
1726
  });
1540
- var TurnHistoryEntrySchema = z8.object({
1727
+ var TurnHistoryEntrySchema = z9.object({
1541
1728
  /** The id of the event that ended the turn. */
1542
1729
  id: UlidSchema,
1543
1730
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1544
1731
  turnId: UlidSchema.optional(),
1545
1732
  ts: IsoDateTimeSchema,
1546
- outcome: z8.enum(["completed", "failed"]),
1733
+ outcome: z9.enum(["completed", "failed"]),
1547
1734
  /** A project slug, or the society scope. */
1548
1735
  project: NameSchema,
1549
1736
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1550
1737
  thread: UlidSchema.optional(),
1551
1738
  /** The channel, never general, whose conversation the turn was in. */
1552
1739
  channel: NameSchema.optional(),
1553
- trigger: z8.string(),
1554
- exitReason: z8.string().nullable(),
1555
- costUsd: z8.number(),
1740
+ trigger: z9.string(),
1741
+ exitReason: z9.string().nullable(),
1742
+ costUsd: z9.number(),
1556
1743
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1557
1744
  usage: UsageSchema.optional(),
1558
- model: z8.string().nullable(),
1559
- summary: z8.string().nullable(),
1560
- error: z8.string().nullable(),
1745
+ model: z9.string().nullable(),
1746
+ summary: z9.string().nullable(),
1747
+ error: z9.string().nullable(),
1561
1748
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1562
1749
  startedAt: IsoDateTimeSchema.optional(),
1563
- toolCalls: z8.number().int().nonnegative().optional()
1750
+ toolCalls: z9.number().int().nonnegative().optional()
1564
1751
  });
1565
1752
 
1566
1753
  // packages/shared/dist/ops.js
1567
- import { z as z9 } from "zod";
1754
+ import { z as z10 } from "zod";
1568
1755
  var OPS_SIGNAL_KINDS = [
1569
1756
  "waiting_stage",
1570
1757
  "backlog",
@@ -1580,16 +1767,16 @@ var OPS_SIGNAL_KINDS = [
1580
1767
  "stuck_workspace",
1581
1768
  "cron_failing"
1582
1769
  ];
1583
- var OpsSignalKindSchema = z9.enum(OPS_SIGNAL_KINDS);
1584
- var OpsSignalSchema = z9.object({
1770
+ var OpsSignalKindSchema = z10.enum(OPS_SIGNAL_KINDS);
1771
+ var OpsSignalSchema = z10.object({
1585
1772
  kind: OpsSignalKindSchema,
1586
1773
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1587
- key: z9.string().min(1),
1774
+ key: z10.string().min(1),
1588
1775
  /** One line a reader can act on. */
1589
- summary: z9.string().min(1),
1776
+ summary: z10.string().min(1),
1590
1777
  /** The counter or timer value behind the signal. */
1591
- value: z9.number(),
1592
- threshold: z9.number().optional(),
1778
+ value: z10.number(),
1779
+ threshold: z10.number().optional(),
1593
1780
  project: NameSchema.optional(),
1594
1781
  agent: NameSchema.optional(),
1595
1782
  role: NameSchema.optional(),
@@ -1597,8 +1784,8 @@ var OpsSignalSchema = z9.object({
1597
1784
  });
1598
1785
 
1599
1786
  // packages/shared/dist/metrics.js
1600
- import { z as z10 } from "zod";
1601
- var MetricsWindowSchema = z10.enum(["24h", "7d", "all"]);
1787
+ import { z as z11 } from "zod";
1788
+ var MetricsWindowSchema = z11.enum(["24h", "7d", "all"]);
1602
1789
  var METRICS_WINDOW_MS = {
1603
1790
  "24h": 24 * 36e5,
1604
1791
  "7d": 7 * 24 * 36e5,
@@ -1609,9 +1796,9 @@ function isBoardAction(tool) {
1609
1796
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1610
1797
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1611
1798
  }
1612
- var CountSchema = z10.number().int().nonnegative();
1613
- var MsSchema = z10.number().nonnegative();
1614
- var MetricsSchema = z10.object({
1799
+ var CountSchema = z11.number().int().nonnegative();
1800
+ var MsSchema = z11.number().nonnegative();
1801
+ var MetricsSchema = z11.object({
1615
1802
  window: MetricsWindowSchema,
1616
1803
  since: IsoDateTimeSchema.nullable(),
1617
1804
  until: IsoDateTimeSchema,
@@ -1619,12 +1806,12 @@ var MetricsSchema = z10.object({
1619
1806
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1620
1807
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1621
1808
  */
1622
- idle: z10.object({
1809
+ idle: z11.object({
1623
1810
  turns: CountSchema,
1624
1811
  idle: CountSchema,
1625
1812
  unknown: CountSchema,
1626
- byTrigger: z10.array(z10.object({ trigger: z10.string(), turns: CountSchema, idle: CountSchema })),
1627
- byAgent: z10.array(z10.object({
1813
+ byTrigger: z11.array(z11.object({ trigger: z11.string(), turns: CountSchema, idle: CountSchema })),
1814
+ byAgent: z11.array(z11.object({
1628
1815
  agent: NameSchema,
1629
1816
  role: NameSchema.nullable(),
1630
1817
  turns: CountSchema,
@@ -1632,33 +1819,33 @@ var MetricsSchema = z10.object({
1632
1819
  }))
1633
1820
  }),
1634
1821
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1635
- sentBack: z10.object({
1822
+ sentBack: z11.object({
1636
1823
  finished: CountSchema,
1637
1824
  sendBacks: CountSchema,
1638
1825
  tasksSentBack: CountSchema,
1639
- byStage: z10.array(z10.object({ project: NameSchema, stage: z10.string(), count: CountSchema })),
1640
- bySender: z10.array(z10.object({ agent: NameSchema, count: CountSchema }))
1826
+ byStage: z11.array(z11.object({ project: NameSchema, stage: z11.string(), count: CountSchema })),
1827
+ bySender: z11.array(z11.object({ agent: NameSchema, count: CountSchema }))
1641
1828
  }),
1642
1829
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1643
- messages: z10.object({
1830
+ messages: z11.object({
1644
1831
  finished: CountSchema,
1645
1832
  messages: CountSchema,
1646
- byProject: z10.array(z10.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1647
- busiest: z10.array(z10.object({
1833
+ byProject: z11.array(z11.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1834
+ busiest: z11.array(z11.object({
1648
1835
  taskId: UlidSchema,
1649
- title: z10.string(),
1836
+ title: z11.string(),
1650
1837
  project: NameSchema,
1651
1838
  messages: CountSchema
1652
1839
  }))
1653
1840
  }),
1654
1841
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1655
- latency: z10.object({
1842
+ latency: z11.object({
1656
1843
  mentions: CountSchema,
1657
1844
  /** Mentions no turn has followed yet. */
1658
1845
  unanswered: CountSchema,
1659
1846
  medianMs: MsSchema.nullable(),
1660
1847
  slowestMs: MsSchema.nullable(),
1661
- byAgent: z10.array(z10.object({
1848
+ byAgent: z11.array(z11.object({
1662
1849
  agent: NameSchema,
1663
1850
  mentions: CountSchema,
1664
1851
  medianMs: MsSchema,
@@ -1666,81 +1853,141 @@ var MetricsSchema = z10.object({
1666
1853
  }))
1667
1854
  }),
1668
1855
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1669
- decisions: z10.object({
1856
+ decisions: z11.object({
1670
1857
  total: CountSchema,
1671
- byDay: z10.array(z10.object({
1672
- day: z10.string(),
1858
+ byDay: z11.array(z11.object({
1859
+ day: z11.string(),
1673
1860
  proposals: CountSchema,
1674
1861
  answers: CountSchema,
1675
1862
  stages: CountSchema
1676
1863
  }))
1677
1864
  }),
1678
1865
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1679
- blocked: z10.array(z10.object({
1866
+ blocked: z11.array(z11.object({
1680
1867
  taskId: UlidSchema,
1681
- title: z10.string().nullable(),
1868
+ title: z11.string().nullable(),
1682
1869
  project: NameSchema.nullable(),
1683
- summary: z10.string(),
1870
+ summary: z11.string(),
1684
1871
  firstAt: IsoDateTimeSchema,
1685
1872
  lastAt: IsoDateTimeSchema,
1686
- holds: z10.boolean()
1873
+ holds: z11.boolean()
1687
1874
  }))
1688
1875
  });
1876
+ var CitizenTokensSchema = z11.object({
1877
+ agent: NameSchema,
1878
+ turns: CountSchema,
1879
+ withUsage: CountSchema,
1880
+ usage: UsageSchema.nullable()
1881
+ });
1882
+ var TokenUseSchema = z11.object({
1883
+ window: MetricsWindowSchema,
1884
+ since: IsoDateTimeSchema.nullable(),
1885
+ until: IsoDateTimeSchema,
1886
+ citizens: z11.array(CitizenTokensSchema)
1887
+ });
1689
1888
 
1690
1889
  // packages/shared/dist/config.js
1691
- import { z as z11 } from "zod";
1692
- var LogLevelSchema = z11.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1693
- var GithubSignInSchema = z11.object({
1694
- clientId: z11.string().min(1),
1695
- clientSecret: z11.string().min(1),
1890
+ import { z as z12 } from "zod";
1891
+ var LogLevelSchema = z12.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1892
+ var GithubSignInSchema = z12.object({
1893
+ clientId: z12.string().min(1),
1894
+ clientSecret: z12.string().min(1),
1696
1895
  /** Logins, lowercased; GitHub logins are case-insensitive. */
1697
- users: z11.array(z11.string().min(1)).min(1)
1896
+ users: z12.array(z12.string().min(1)).min(1)
1698
1897
  });
1699
- var ServerConfigSchema = z11.object({
1700
- dataDir: z11.string().min(1),
1701
- host: z11.string().min(1),
1702
- port: z11.number().int().positive().max(65535),
1898
+ var ServerConfigSchema = z12.object({
1899
+ dataDir: z12.string().min(1),
1900
+ host: z12.string().min(1),
1901
+ port: z12.number().int().positive().max(65535),
1703
1902
  /**
1704
1903
  * An address every runner's agents reach this server at, for example https://board.example, or
1705
1904
  * null for each runner to use the address it reaches the server at itself.
1706
1905
  */
1707
- publicUrl: z11.string().min(1).nullable(),
1906
+ publicUrl: z12.string().min(1).nullable(),
1708
1907
  logLevel: LogLevelSchema,
1709
1908
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
1710
- concurrency: z11.number().int().positive().nullable(),
1909
+ concurrency: z12.number().int().positive().nullable(),
1711
1910
  /** How long one turn may run before it is stopped, or null for no limit. */
1712
- turnTimeoutMs: z11.number().int().positive().nullable(),
1911
+ turnTimeoutMs: z12.number().int().positive().nullable(),
1713
1912
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1714
- toolRounds: z11.number().int().positive().nullable(),
1913
+ toolRounds: z12.number().int().positive().nullable(),
1715
1914
  /** How long a resident session stays warm after its last turn. */
1716
- residentIdleMs: z11.number().int().positive(),
1915
+ residentIdleMs: z12.number().int().positive(),
1717
1916
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1718
1917
  github: GithubSignInSchema.nullable(),
1719
1918
  /**
1720
1919
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1721
1920
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1722
1921
  */
1723
- trustedProxies: z11.number().int().min(0).max(16)
1922
+ trustedProxies: z12.number().int().min(0).max(16)
1724
1923
  });
1725
- var RunnerConfigSchema = z11.object({
1924
+ var RunnerConfigSchema = z12.object({
1726
1925
  /** The board server, for example http://127.0.0.1:4700. */
1727
- serverUrl: z11.string().url(),
1926
+ serverUrl: z12.string().url(),
1728
1927
  /**
1729
1928
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1730
1929
  * to enroll it.
1731
1930
  */
1732
- token: z11.string().min(1).nullable(),
1931
+ token: z12.string().min(1).nullable(),
1733
1932
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1734
- dataDir: z11.string().min(1),
1933
+ dataDir: z12.string().min(1),
1735
1934
  logLevel: LogLevelSchema,
1736
1935
  /** Turns this machine runs at once, or null for no limit. */
1737
- slots: z11.number().int().positive().nullable(),
1738
- clis: z11.array(CliKindSchema).min(1),
1739
- capabilities: z11.array(z11.string())
1936
+ slots: z12.number().int().positive().nullable(),
1937
+ clis: z12.array(CliKindSchema).min(1),
1938
+ capabilities: z12.array(z12.string())
1740
1939
  });
1741
1940
 
1742
1941
  // packages/shared/dist/version.js
1743
- var STELLARIS_VERSION = "0.3.20";
1942
+ var STELLARIS_VERSION = "0.3.22";
1943
+
1944
+ // packages/shared/dist/mentions.js
1945
+ import { parse, postprocess, preprocess } from "micromark";
1946
+ import { gfm } from "micromark-extension-gfm";
1947
+ var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
1948
+ var CODE = /* @__PURE__ */ new Set(["codeText", "codeFenced", "codeIndented"]);
1949
+ var SLOW = {
1950
+ disable: {
1951
+ null: [
1952
+ "wwwAutolink",
1953
+ "protocolAutolink",
1954
+ "emailAutolink",
1955
+ "gfmFootnoteCall",
1956
+ "gfmPotentialFootnoteCall"
1957
+ ]
1958
+ }
1959
+ };
1960
+ function codeRanges(markdown) {
1961
+ const shift = markdown.startsWith("\uFEFF") ? 1 : 0;
1962
+ const events = postprocess(parse({ extensions: [gfm(), SLOW] }).document().write(preprocess()(markdown, void 0, true)));
1963
+ return events.flatMap(([kind, token]) => kind === "enter" && CODE.has(token.type) ? [[token.start.offset + shift, token.end.offset + shift]] : []);
1964
+ }
1965
+ function addMentions(found, text) {
1966
+ for (const match of text.matchAll(MENTION_PATTERN)) {
1967
+ const name = match[2];
1968
+ if (name !== void 0) {
1969
+ found.add(name);
1970
+ }
1971
+ }
1972
+ }
1973
+ function mentionsIn(markdown) {
1974
+ if (markdown.search(MENTION_PATTERN) === -1) {
1975
+ return [];
1976
+ }
1977
+ const found = /* @__PURE__ */ new Set();
1978
+ let from = 0;
1979
+ for (const [start, end] of codeRanges(markdown)) {
1980
+ addMentions(found, markdown.slice(from, start));
1981
+ from = end;
1982
+ }
1983
+ addMentions(found, markdown.slice(from));
1984
+ return [...found].toSorted();
1985
+ }
1986
+ function mentionsAnywhere(text) {
1987
+ const found = /* @__PURE__ */ new Set();
1988
+ addMentions(found, text);
1989
+ return [...found].toSorted();
1990
+ }
1744
1991
 
1745
1992
  // packages/board-core/dist/errors.js
1746
1993
  var BoardError = class extends Error {
@@ -2383,33 +2630,59 @@ function filesOf(lines, status) {
2383
2630
  return [...files.values()];
2384
2631
  }
2385
2632
 
2633
+ // packages/board-core/dist/mentions.js
2634
+ import vm from "node:vm";
2635
+ var MENTION_PARSE_MS = 500;
2636
+ var context = vm.createContext({});
2637
+ var call = new vm.Script("work()");
2638
+ function within(work, ms) {
2639
+ let result;
2640
+ context["work"] = () => {
2641
+ result = work();
2642
+ };
2643
+ try {
2644
+ call.runInContext(context, { timeout: ms });
2645
+ } catch (error) {
2646
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
2647
+ return void 0;
2648
+ }
2649
+ throw error;
2650
+ } finally {
2651
+ context["work"] = void 0;
2652
+ }
2653
+ return result;
2654
+ }
2655
+ function postMentions(body) {
2656
+ return within(() => mentionsIn(body), MENTION_PARSE_MS) ?? mentionsAnywhere(body);
2657
+ }
2658
+
2386
2659
  // packages/board-core/dist/metrics.js
2387
- import { z as z12 } from "zod";
2388
- var TurnPayload = z12.object({
2389
- turnId: z12.string().optional(),
2390
- project: z12.string(),
2391
- thread: z12.string().optional(),
2392
- channel: z12.string().optional(),
2393
- trigger: z12.string()
2394
- });
2395
- var TaskPayload = z12.object({
2396
- taskId: z12.string(),
2397
- project: z12.string(),
2398
- from: z12.string().optional()
2399
- });
2400
- var MessagePayload = z12.object({
2401
- id: z12.string().optional(),
2402
- channel: z12.string(),
2403
- thread: z12.string().nullable().optional(),
2404
- mentions: z12.array(z12.string()).default([])
2405
- });
2406
- var SteeredPayload = z12.object({ messages: z12.array(z12.string()) });
2407
- var SignalPayload = z12.object({
2408
- kind: z12.string(),
2409
- key: z12.string(),
2410
- summary: z12.string(),
2411
- taskId: z12.string().optional(),
2412
- project: z12.string().optional()
2660
+ import { z as z13 } from "zod";
2661
+ var TurnPayload = z13.object({
2662
+ turnId: z13.string().optional(),
2663
+ project: z13.string(),
2664
+ thread: z13.string().optional(),
2665
+ channel: z13.string().optional(),
2666
+ trigger: z13.string()
2667
+ });
2668
+ var TaskPayload = z13.object({
2669
+ taskId: z13.string(),
2670
+ project: z13.string(),
2671
+ from: z13.string().optional()
2672
+ });
2673
+ var MessagePayload = z13.object({
2674
+ id: z13.string().optional(),
2675
+ channel: z13.string(),
2676
+ thread: z13.string().nullable().optional(),
2677
+ mentions: z13.array(z13.string()).default([])
2678
+ });
2679
+ var SteeredPayload = z13.object({ messages: z13.array(z13.string()) });
2680
+ var SignalPayload = z13.object({
2681
+ kind: z13.string(),
2682
+ key: z13.string(),
2683
+ summary: z13.string(),
2684
+ taskId: z13.string().optional(),
2685
+ project: z13.string().optional()
2413
2686
  });
2414
2687
  function median(values) {
2415
2688
  const sorted = values.toSorted((a, b) => a - b);
@@ -2698,6 +2971,59 @@ function computeMetrics(input) {
2698
2971
  };
2699
2972
  }
2700
2973
 
2974
+ // packages/board-core/dist/turn-history.js
2975
+ import { z as z14 } from "zod";
2976
+ var TurnHistoryPayloadSchema = z14.object({
2977
+ project: NameSchema,
2978
+ thread: UlidSchema.optional(),
2979
+ channel: NameSchema.optional(),
2980
+ trigger: z14.string().default("manual"),
2981
+ exitReason: z14.string().nullable().default(null),
2982
+ costUsd: z14.number().default(0),
2983
+ usage: UsageSchema.optional(),
2984
+ model: z14.string().nullable().default(null),
2985
+ summary: z14.string().nullable().default(null),
2986
+ error: z14.string().nullable().default(null),
2987
+ toolCalls: z14.number().int().nonnegative().optional(),
2988
+ turnId: UlidSchema.optional()
2989
+ });
2990
+ function tokenUse(events, window, now) {
2991
+ const span = METRICS_WINDOW_MS[window];
2992
+ const since = span === null ? null : now.getTime() - span;
2993
+ const rows = /* @__PURE__ */ new Map();
2994
+ for (const event of events) {
2995
+ if (event.type !== "turn.completed" && event.type !== "turn.failed") {
2996
+ continue;
2997
+ }
2998
+ if (since !== null && Date.parse(event.ts) < since) {
2999
+ continue;
3000
+ }
3001
+ const turn2 = TurnHistoryPayloadSchema.safeParse(event.payload);
3002
+ if (!turn2.success) {
3003
+ continue;
3004
+ }
3005
+ const row = rows.get(event.actor) ?? {
3006
+ agent: event.actor,
3007
+ turns: 0,
3008
+ withUsage: 0,
3009
+ usage: null
3010
+ };
3011
+ const usage = turn2.data.usage;
3012
+ rows.set(event.actor, {
3013
+ agent: event.actor,
3014
+ turns: row.turns + 1,
3015
+ withUsage: row.withUsage + (usage === void 0 ? 0 : 1),
3016
+ usage: usage === void 0 ? row.usage : row.usage === null ? usage : addUsage(row.usage, usage)
3017
+ });
3018
+ }
3019
+ return {
3020
+ window,
3021
+ since: since === null ? null : new Date(since).toISOString(),
3022
+ until: now.toISOString(),
3023
+ citizens: [...rows.values()].toSorted((a, b) => a.agent.localeCompare(b.agent))
3024
+ };
3025
+ }
3026
+
2701
3027
  // packages/board-core/dist/mutex.js
2702
3028
  var Mutex = class {
2703
3029
  tail = Promise.resolve();
@@ -2939,41 +3265,27 @@ function namesList(names) {
2939
3265
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
2940
3266
  }
2941
3267
  var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
2942
- var RunnerTokensSchema = z13.object({
2943
- runners: z13.record(z13.string(), z13.object({ tokenHash: z13.string().min(1) }))
3268
+ var RunnerTokensSchema = z15.object({
3269
+ runners: z15.record(z15.string(), z15.object({ tokenHash: z15.string().min(1) }))
2944
3270
  });
2945
- var SignInsSchema = z13.object({
2946
- signIns: z13.array(z13.object({
2947
- tokenHash: z13.string().min(1),
2948
- login: z13.string().min(1),
3271
+ var SignInsSchema = z15.object({
3272
+ signIns: z15.array(z15.object({
3273
+ tokenHash: z15.string().min(1),
3274
+ login: z15.string().min(1),
2949
3275
  /** The account's picture at the identity provider; sign-ins from before it have none. */
2950
- avatarUrl: z13.url().optional(),
3276
+ avatarUrl: z15.url().optional(),
2951
3277
  createdAt: IsoDateTimeSchema,
2952
3278
  expiresAt: IsoDateTimeSchema
2953
3279
  })).default([])
2954
3280
  });
2955
- var TurnHistoryPayloadSchema = z13.object({
2956
- project: NameSchema,
2957
- thread: UlidSchema.optional(),
2958
- channel: NameSchema.optional(),
2959
- trigger: z13.string().default("manual"),
2960
- exitReason: z13.string().nullable().default(null),
2961
- costUsd: z13.number().default(0),
2962
- usage: UsageSchema.optional(),
2963
- model: z13.string().nullable().default(null),
2964
- summary: z13.string().nullable().default(null),
2965
- error: z13.string().nullable().default(null),
2966
- toolCalls: z13.number().int().nonnegative().optional(),
2967
- turnId: UlidSchema.optional()
2968
- });
2969
- var TurnStartPayloadSchema = z13.object({ project: NameSchema, turnId: UlidSchema.optional() });
2970
- var CursorsSchema = z13.object({
2971
- digest: z13.string().nullable(),
2972
- scopes: z13.record(z13.string(), z13.string()).default({}),
2973
- threadsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
2974
- channelsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
3281
+ var TurnStartPayloadSchema = z15.object({ project: NameSchema, turnId: UlidSchema.optional() });
3282
+ var CursorsSchema = z15.object({
3283
+ digest: z15.string().nullable(),
3284
+ scopes: z15.record(z15.string(), z15.string()).default({}),
3285
+ threadsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3286
+ channelsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
2975
3287
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
2976
- news: z13.string().optional()
3288
+ news: z15.string().optional()
2977
3289
  });
2978
3290
  var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
2979
3291
  function conversationKey(scope, part) {
@@ -3012,9 +3324,8 @@ function byId(a, b) {
3012
3324
  function turnTime(record) {
3013
3325
  return record.endedAt ?? record.startedAt;
3014
3326
  }
3015
- var PausedSchema = z13.object({ paused: z13.boolean() });
3327
+ var PausedSchema = z15.object({ paused: z15.boolean() });
3016
3328
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
3017
- var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
3018
3329
  function withoutMentions(text) {
3019
3330
  return text.replaceAll(MENTION_PATTERN, "$1$2");
3020
3331
  }
@@ -3038,7 +3349,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
3038
3349
  var FRONT_DESK_TRIGGER = "user_post";
3039
3350
  var OPS_CHANNELS = ["governance"];
3040
3351
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
3041
- var AlignmentsSchema = z13.object({ applied: z13.array(z13.string()).default([]) });
3352
+ var AlignmentsSchema = z15.object({ applied: z15.array(z15.string()).default([]) });
3042
3353
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
3043
3354
  var THREAD_RECORDS = "threads-as-records";
3044
3355
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -3052,10 +3363,10 @@ var CHANNEL_CONVERSATIONS = "channel-conversations";
3052
3363
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3053
3364
  var CRON_VERBS = "cron-verbs-granted";
3054
3365
  var KNOWLEDGE_NOTICES = "knowledge-notices";
3055
- var KnowledgeEventSchema = z13.object({
3366
+ var KnowledgeEventSchema = z15.object({
3056
3367
  topic: NameSchema,
3057
3368
  project: NameSchema.nullable(),
3058
- replaced: z13.boolean().optional()
3369
+ replaced: z15.boolean().optional()
3059
3370
  });
3060
3371
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3061
3372
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
@@ -3173,16 +3484,6 @@ function unanswered(messages) {
3173
3484
  }
3174
3485
  return waiting;
3175
3486
  }
3176
- function extractMentions(body) {
3177
- const found = /* @__PURE__ */ new Set();
3178
- for (const match of body.matchAll(MENTION_PATTERN)) {
3179
- const name = match[2];
3180
- if (name !== void 0) {
3181
- found.add(name);
3182
- }
3183
- }
3184
- return [...found].toSorted();
3185
- }
3186
3487
  function snippetAround(text, query, radius = 80) {
3187
3488
  const index = text.toLowerCase().indexOf(query.toLowerCase());
3188
3489
  if (index === -1) {
@@ -3510,7 +3811,7 @@ var Board = class _Board {
3510
3811
  }
3511
3812
  }
3512
3813
  for (const file of await listFiles(this.paths.tasks(slug))) {
3513
- const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z13.record(z13.string(), z13.unknown()));
3814
+ const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z15.record(z15.string(), z15.unknown()));
3514
3815
  const legacy = raw.data["thread"];
3515
3816
  if (legacy === void 0) {
3516
3817
  continue;
@@ -3588,7 +3889,7 @@ var Board = class _Board {
3588
3889
  * turns, so it follows nothing now.
3589
3890
  */
3590
3891
  async renameInboxCursors() {
3591
- const Legacy = z13.object({ inbox: z13.string().nullable() });
3892
+ const Legacy = z15.object({ inbox: z15.string().nullable() });
3592
3893
  for (const agent2 of await this.listAgents()) {
3593
3894
  const file = this.paths.agentCursors(agent2.name);
3594
3895
  if (await exists(file)) {
@@ -3634,7 +3935,7 @@ ${charter.purpose}
3634
3935
  */
3635
3936
  async alignSeedRole(seed) {
3636
3937
  const file = this.paths.role(seed.name);
3637
- const raw = await readMarkdown(file, z13.record(z13.string(), z13.unknown()));
3938
+ const raw = await readMarkdown(file, z15.record(z15.string(), z15.unknown()));
3638
3939
  const current = RoleCharterSchema.parse(raw.data);
3639
3940
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
3640
3941
  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));
@@ -3951,7 +4252,7 @@ ${charter.purpose}
3951
4252
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
3952
4253
  async readSocietyNorms() {
3953
4254
  const file = this.paths.societyKnowledgeFile("norms");
3954
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
4255
+ return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
3955
4256
  }
3956
4257
  /** Knowledge topics of a project, or of the society when the project is null. */
3957
4258
  async listKnowledge(project2) {
@@ -3971,7 +4272,7 @@ ${charter.purpose}
3971
4272
  */
3972
4273
  async knowledgeAt(file, project2, topic) {
3973
4274
  const { data, body } = await readLooseMarkdown(file);
3974
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
4275
+ const revision = z15.number().int().min(0).safeParse(data["revision"]);
3975
4276
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
3976
4277
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
3977
4278
  return {
@@ -4304,8 +4605,11 @@ ${DASHBOARD_LINE}
4304
4605
  throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4305
4606
  }
4306
4607
  const doc = await readMarkdown(file, RunnerSchema);
4608
+ const { gitIdentityMissing: reported, ...rest } = doc.data;
4609
+ const gitIdentityMissing = patch.gitIdentityMissing === void 0 ? reported : patch.gitIdentityMissing ?? void 0;
4307
4610
  const next = RunnerSchema.parse({
4308
- ...doc.data,
4611
+ ...rest,
4612
+ ...gitIdentityMissing === void 0 ? {} : { gitIdentityMissing: [...gitIdentityMissing] },
4309
4613
  ...patch.status === void 0 ? {} : { status: patch.status },
4310
4614
  ...patch.os === void 0 ? {} : { os: patch.os },
4311
4615
  ...patch.clis === void 0 ? {} : { clis: [...patch.clis] },
@@ -4333,6 +4637,36 @@ ${DASHBOARD_LINE}
4333
4637
  return next;
4334
4638
  });
4335
4639
  }
4640
+ /**
4641
+ * Records a CLI's plan quota as a runner reported it, from a turn's stream or a read, merged over
4642
+ * what the record held. The record is written on every report, so it says when the numbers were
4643
+ * last seen; `runner.quota` is appended only when what a reader would see changes, since every
4644
+ * turn reports.
4645
+ */
4646
+ async recordQuota(name, cli, quota) {
4647
+ return this.mutex.run(async () => {
4648
+ const file = this.paths.runner(name);
4649
+ if (!await exists(file)) {
4650
+ throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4651
+ }
4652
+ const doc = await readMarkdown(file, RunnerSchema);
4653
+ const previous = doc.data.quotas?.[cli];
4654
+ const merged = mergeQuota(previous, quota);
4655
+ const next = RunnerSchema.parse({
4656
+ ...doc.data,
4657
+ quotas: { ...doc.data.quotas, [cli]: merged }
4658
+ });
4659
+ await writeMarkdown(file, next, doc.body);
4660
+ if (quotaChanged(previous, merged)) {
4661
+ await this.events.append("runner.quota", SYSTEM_ACTOR.name, {
4662
+ name,
4663
+ cli,
4664
+ status: merged.status
4665
+ });
4666
+ }
4667
+ return next;
4668
+ });
4669
+ }
4336
4670
  /**
4337
4671
  * Logs an operations signal as an `ops.signal` event. No channel carries it: the scheduler wakes
4338
4672
  * its readers from the event, the runner lists it in their prompts, and the interface's log shows
@@ -4474,18 +4808,16 @@ ${DASHBOARD_LINE}
4474
4808
  async readDigest(actor, input = {}) {
4475
4809
  const args = VerbInputs.read_inbox.parse(input);
4476
4810
  await this.authorize(actor, "read_inbox");
4477
- return this.mutex.run(async () => {
4478
- const stored = await this.readCursors(actor.name);
4479
- const from = args.since_cursor === void 0 ? stored : { ...NO_CURSORS, digest: args.since_cursor };
4480
- const conversation = actor.scope === void 0 ? void 0 : { scope: actor.scope, part: conversationPart(actor) ?? null };
4481
- const filed = (await this.digestSince(actor, from, conversation)).slice(0, args.limit);
4482
- if (args.advance) {
4483
- await this.advanceCursors(actor.name, filed);
4484
- }
4485
- const last = filed.at(-1)?.message.id;
4486
- const cursor = last ?? (conversation === void 0 ? from.digest : cursorOf(from, conversation.scope, conversation.part));
4487
- return { messages: filed.map((entry) => entry.message), cursor };
4488
- });
4811
+ const { cursors: stored, upto } = await this.digestSnapshot(actor.name);
4812
+ const from = args.since_cursor === void 0 ? stored : { ...NO_CURSORS, digest: args.since_cursor };
4813
+ const conversation = actor.scope === void 0 ? void 0 : { scope: actor.scope, part: conversationPart(actor) ?? null };
4814
+ const filed = (await this.digestSince(actor, from, upto, conversation)).slice(0, args.limit);
4815
+ if (args.advance) {
4816
+ await this.mutex.run(() => this.advanceCursors(actor.name, filed));
4817
+ }
4818
+ const last = filed.at(-1)?.message.id;
4819
+ const cursor = last ?? (conversation === void 0 ? from.digest : cursorOf(from, conversation.scope, conversation.part));
4820
+ return { messages: filed.map((entry) => entry.message), cursor };
4489
4821
  }
4490
4822
  /**
4491
4823
  * How many unread digest messages a member has in each conversation: a scope's home, for its
@@ -4497,28 +4829,39 @@ ${DASHBOARD_LINE}
4497
4829
  * whose signals that call for action wake their readers directly.
4498
4830
  */
4499
4831
  async unreadByConversation(actor) {
4500
- return this.mutex.run(async () => {
4501
- const counts = /* @__PURE__ */ new Map();
4502
- const unscoped = { name: actor.name, role: actor.role };
4503
- for (const { scope, part, message, news } of await this.digestSince(unscoped, await this.readCursors(actor.name))) {
4504
- if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
4505
- continue;
4506
- }
4507
- const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
4508
- if (posted !== scope && scope !== SOCIETY_SCOPE) {
4509
- continue;
4510
- }
4511
- const key = conversationKey(scope, part);
4512
- const entry = counts.get(key) ?? {
4513
- scope,
4514
- thread: threadOfPart(part) ?? null,
4515
- channel: channelOfPart(part) ?? null,
4516
- count: 0
4517
- };
4518
- counts.set(key, { ...entry, count: entry.count + 1 });
4832
+ const { cursors, upto } = await this.digestSnapshot(actor.name);
4833
+ const counts = /* @__PURE__ */ new Map();
4834
+ const unscoped = { name: actor.name, role: actor.role };
4835
+ for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
4836
+ if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
4837
+ continue;
4519
4838
  }
4520
- return [...counts.values()];
4521
- });
4839
+ const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
4840
+ if (posted !== scope && scope !== SOCIETY_SCOPE) {
4841
+ continue;
4842
+ }
4843
+ const key = conversationKey(scope, part);
4844
+ const entry = counts.get(key) ?? {
4845
+ scope,
4846
+ thread: threadOfPart(part) ?? null,
4847
+ channel: channelOfPart(part) ?? null,
4848
+ count: 0
4849
+ };
4850
+ counts.set(key, { ...entry, count: entry.count + 1 });
4851
+ }
4852
+ return [...counts.values()];
4853
+ }
4854
+ /**
4855
+ * A reader's cursors and the id below which every message is already on disk, taken under the
4856
+ * mutex, which a digest scan then leaves: messages carry ids from the same monotonic source and are
4857
+ * written under the mutex, so the scan, which counts nothing newer than the mark, cannot pass over
4858
+ * one written while it reads, and a post never waits for a scan.
4859
+ */
4860
+ digestSnapshot(agent2) {
4861
+ return this.mutex.run(async () => ({
4862
+ cursors: await this.readCursors(agent2),
4863
+ upto: this.newId()
4864
+ }));
4522
4865
  }
4523
4866
  async readCursors(agent2) {
4524
4867
  const file = this.paths.agentCursors(agent2);
@@ -4560,9 +4903,10 @@ ${DASHBOARD_LINE}
4560
4903
  * by the user wherever it is. With a conversation, only what is filed there: for a thread, every
4561
4904
  * message of the thread, since the conversation is about it; for a channel's, that channel's
4562
4905
  * top-level messages; for a scope's home, general's and those of a project the member is not in,
4563
- * since each thread and each other channel is a conversation of its own.
4906
+ * since each thread and each other channel is a conversation of its own. Nothing newer than
4907
+ * `upto` counts (see `digestSnapshot`).
4564
4908
  */
4565
- async digestSince(actor, cursors, conversation) {
4909
+ async digestSince(actor, cursors, upto, conversation) {
4566
4910
  const conversationThread = threadOfPart(conversation?.part);
4567
4911
  if (conversation !== void 0 && conversationThread !== void 0) {
4568
4912
  const { scope, part } = conversation;
@@ -4570,7 +4914,7 @@ ${DASHBOARD_LINE}
4570
4914
  if (found === null) {
4571
4915
  return [];
4572
4916
  }
4573
- const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part));
4917
+ const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part), upto);
4574
4918
  return messages.filter((message) => message.author !== actor.name).map((message) => ({ scope, part, message })).toSorted(byId);
4575
4919
  }
4576
4920
  const agent2 = await this.readAgent(actor.name);
@@ -4588,7 +4932,7 @@ ${DASHBOARD_LINE}
4588
4932
  ];
4589
4933
  const known = starts.filter((start) => start !== null);
4590
4934
  const since = known.length < starts.length ? null : known.toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0)[0] ?? null;
4591
- for await (const message of this.iterateMessages(since)) {
4935
+ for await (const message of this.iterateMessages(since, void 0, upto)) {
4592
4936
  const filedIn = wakeScope(agent2, parseChannelRef(message.channel).project);
4593
4937
  const thread2 = message.thread ?? null;
4594
4938
  const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
@@ -5712,6 +6056,9 @@ ${args.summary}`);
5712
6056
  }
5713
6057
  if (args.blocked_by !== void 0) {
5714
6058
  for (const id of args.blocked_by) {
6059
+ if (id === current.id) {
6060
+ throw new BoardError("VALIDATION", `task ${current.id} cannot be blocked by itself`);
6061
+ }
5715
6062
  await this.findTask(id);
5716
6063
  }
5717
6064
  next = { ...next, blockedBy: [...args.blocked_by] };
@@ -5748,6 +6095,7 @@ ${args.summary}`);
5748
6095
  from: current.status,
5749
6096
  to: task2.status,
5750
6097
  note: args.note ?? null,
6098
+ ...args.blocked_by === void 0 ? {} : { blockedBy: task2.blockedBy, previousBlockedBy: current.blockedBy },
5751
6099
  ...args.pull_request === void 0 ? {} : { pullRequest: task2.pullRequest?.url ?? null }
5752
6100
  });
5753
6101
  }
@@ -6153,7 +6501,7 @@ ${rationale}`}`);
6153
6501
  }
6154
6502
  async readAgentRoleBody(agent2) {
6155
6503
  const file = this.paths.agentRole(agent2);
6156
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
6504
+ return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
6157
6505
  }
6158
6506
  /** The steps of one finished turn, as the runner handed them over when it ended. */
6159
6507
  /**
@@ -6184,6 +6532,14 @@ ${rationale}`}`);
6184
6532
  }
6185
6533
  return computeMetrics({ events, tasks, citizens, actions, activeSignals, window, now });
6186
6534
  }
6535
+ /**
6536
+ * Each citizen's finished turns over a window and the tokens of those that counted them, from
6537
+ * the event log alone, which holds every turn's end whatever runner it ran on.
6538
+ */
6539
+ async tokenUse(window) {
6540
+ const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
6541
+ return tokenUse(events, window, this.now());
6542
+ }
6187
6543
  /** The board actions a finished turn took, from its transcript; null when it kept none. */
6188
6544
  async turnActions(agent2, turnId) {
6189
6545
  const known = this.actionCounts.get(turnId);
@@ -6553,7 +6909,7 @@ ${rationale}`}`);
6553
6909
  return { project: slug, revision: 0, body: "" };
6554
6910
  }
6555
6911
  const { data, body } = await readLooseMarkdown(file);
6556
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
6912
+ const revision = z15.number().int().min(0).safeParse(data["revision"]);
6557
6913
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
6558
6914
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
6559
6915
  return {
@@ -6844,7 +7200,7 @@ ${seed}
6844
7200
  continue;
6845
7201
  }
6846
7202
  const file = this.paths.agentRole(agent2.name);
6847
- const body = await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
7203
+ const body = await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
6848
7204
  const at = body.indexOf(`
6849
7205
  ${SEED_INSTRUCTIONS_HEADING}
6850
7206
  `);
@@ -7046,12 +7402,37 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7046
7402
  case "archive":
7047
7403
  await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
7048
7404
  return;
7049
- case "reallocation":
7405
+ case "reallocation": {
7406
+ const parsed = ReallocationProposalSchema.safeParse(charter);
7407
+ if (parsed.success) {
7408
+ await this.reallocatedRole(parsed.data);
7409
+ } else {
7410
+ LegacyReallocationSchema.parse(charter);
7411
+ }
7050
7412
  return;
7413
+ }
7051
7414
  default:
7052
7415
  return;
7053
7416
  }
7054
7417
  }
7418
+ /**
7419
+ * The role as a reallocation would leave it, with what changes: refused for the user's charter,
7420
+ * a role that does not exist, a charter the changes would make invalid, and changes the role
7421
+ * has already.
7422
+ */
7423
+ async reallocatedRole(reallocation) {
7424
+ if (reallocation.role === USER_ROLE) {
7425
+ throw new BoardError("FORBIDDEN", "the user charter is not subject to proposals");
7426
+ }
7427
+ const current = await this.readRole(reallocation.role);
7428
+ const next = RoleCharterSchema.parse(applyRoleChanges(current, reallocation.changes));
7429
+ await this.validateRole(next);
7430
+ const changed = roleChangeLines(current, reallocation.changes);
7431
+ if (changed.length === 0) {
7432
+ throw new BoardError("VALIDATION", `role ${current.name} has these values already; the reallocation changes nothing`);
7433
+ }
7434
+ return { next, changed };
7435
+ }
7055
7436
  async executeProvision(by, proposal2) {
7056
7437
  const meta = { proposalId: proposal2.id };
7057
7438
  switch (proposal2.kind) {
@@ -7088,8 +7469,15 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7088
7469
  const { members } = await this.archiveUnlocked(by, input.project, input.reason, meta);
7089
7470
  return { project: input.project, members };
7090
7471
  }
7091
- case "reallocation":
7092
- return void 0;
7472
+ case "reallocation": {
7473
+ const parsed = ReallocationProposalSchema.safeParse(proposal2.charter);
7474
+ if (!parsed.success) {
7475
+ return void 0;
7476
+ }
7477
+ const { next, changed } = await this.reallocatedRole(parsed.data);
7478
+ await this.writeRoleUnlocked(by, next, meta);
7479
+ return { role: next.name, changed };
7480
+ }
7093
7481
  default:
7094
7482
  return void 0;
7095
7483
  }
@@ -7650,7 +8038,7 @@ ${charter.purpose}
7650
8038
  id: this.newId(),
7651
8039
  ...head,
7652
8040
  ts: this.now().toISOString(),
7653
- mentions: extractMentions(body)
8041
+ mentions: postMentions(body)
7654
8042
  });
7655
8043
  const { author } = head;
7656
8044
  await writeMarkdown(this.paths.messageFile(dir, frontmatter.id, author), frontmatter, body);
@@ -7789,7 +8177,7 @@ ${charter.purpose}
7789
8177
  if (filed.proposedBy === actor.name) {
7790
8178
  throw new BoardError("FORBIDDEN", "a proposer never decides its own proposal");
7791
8179
  }
7792
- const approvers = ROLE_KIND_APPROVERS[filed.kind];
8180
+ const approvers = proposalApprovers(filed.kind, filed.charter);
7793
8181
  if (!approvers.includes(actor.role)) {
7794
8182
  throw new BoardError("FORBIDDEN", `a ${filed.kind} proposal requires one of: ${approvers.join(", ")}`);
7795
8183
  }
@@ -7872,7 +8260,7 @@ ${charter.purpose}
7872
8260
  }
7873
8261
  if (subject?.kind === "proposal") {
7874
8262
  const proposal2 = await this.readProposal(subject.id);
7875
- if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name || ROLE_KIND_APPROVERS[proposal2.kind].includes(actor.role)) {
8263
+ if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name || proposalApprovers(proposal2.kind, proposal2.charter).includes(actor.role)) {
7876
8264
  return true;
7877
8265
  }
7878
8266
  return (await this.readRole(actor.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER);
@@ -7929,11 +8317,12 @@ ${charter.purpose}
7929
8317
  });
7930
8318
  await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron2) => cron2.thread === subjectId);
7931
8319
  }
7932
- async readMessagesIn(dir, since) {
8320
+ /** The messages in a folder after `since` and, when given, up to `upto`, read by name first. */
8321
+ async readMessagesIn(dir, since, upto) {
7933
8322
  const messages = [];
7934
8323
  for (const file of await listFiles(dir)) {
7935
8324
  const id = file.slice(0, 26);
7936
- if (since !== null && id <= since) {
8325
+ if (since !== null && id <= since || upto !== void 0 && id > upto) {
7937
8326
  continue;
7938
8327
  }
7939
8328
  const doc = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
@@ -7941,20 +8330,23 @@ ${charter.purpose}
7941
8330
  }
7942
8331
  return messages;
7943
8332
  }
7944
- /** Every message newer than `since` across society channels, project channels, and threads. */
7945
- async *iterateMessages(since, project2) {
8333
+ /**
8334
+ * Every message newer than `since`, and no newer than `upto` when given, across society
8335
+ * channels, project channels, and threads.
8336
+ */
8337
+ async *iterateMessages(since, project2, upto) {
7946
8338
  if (project2 === void 0) {
7947
8339
  for (const channel of await listDirs(this.paths.societyChannels())) {
7948
- yield* await this.readMessagesIn(this.paths.societyChannel(channel), since);
8340
+ yield* await this.readMessagesIn(this.paths.societyChannel(channel), since, upto);
7949
8341
  }
7950
- yield* this.threadMessagesIn(this.paths.societyThreads(), since);
8342
+ yield* this.threadMessagesIn(this.paths.societyThreads(), since, upto);
7951
8343
  }
7952
8344
  const projects = project2 === void 0 ? await listDirs(this.paths.projects()) : [project2];
7953
8345
  for (const slug of projects) {
7954
8346
  for (const channel of await listDirs(this.paths.projectChannels(slug))) {
7955
- yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since);
8347
+ yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since, upto);
7956
8348
  }
7957
- yield* this.threadMessagesIn(this.paths.threads(slug), since);
8349
+ yield* this.threadMessagesIn(this.paths.threads(slug), since, upto);
7958
8350
  }
7959
8351
  }
7960
8352
  /** Every folder that holds messages: each channel's and each thread's, in the society and every project. */
@@ -7974,9 +8366,9 @@ ${charter.purpose}
7974
8366
  }
7975
8367
  }
7976
8368
  }
7977
- async *threadMessagesIn(threads, since) {
8369
+ async *threadMessagesIn(threads, since, upto) {
7978
8370
  for (const id of await listDirs(threads)) {
7979
- yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
8371
+ yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since, upto);
7980
8372
  }
7981
8373
  }
7982
8374
  };