@kubitnodes/stellaris 0.3.21 → 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 (76) hide show
  1. package/dist/cli/index.js +854 -588
  2. package/dist/server/index.js +1039 -651
  3. package/package.json +3 -1
  4. package/web/dist/assets/BackLink-CD-iuy-h.js +1 -0
  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-BOSygXeI.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-DI05jbrI.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-BendOaje.js → Pane-CLjdfBMM.js} +1 -1
  19. package/web/dist/assets/{Picker-BuVnakvi.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-CyXIxoAy.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/BackLink-23Q1JvmZ.js +0 -1
  45. package/web/dist/assets/ChannelForms-0Cwh59E7.js +0 -1
  46. package/web/dist/assets/ChannelView-CqWV1zza.js +0 -2
  47. package/web/dist/assets/CitizenView-s5OdjXmB.js +0 -2
  48. package/web/dist/assets/CitizensView-r6SZLnVl.js +0 -1
  49. package/web/dist/assets/CronsView-Y-bAE0l2.js +0 -1
  50. package/web/dist/assets/KnowledgeView-CI4PJWwm.js +0 -1
  51. package/web/dist/assets/Markdown-DyckB74r.js +0 -32
  52. package/web/dist/assets/MetricsView-DKh-lEW5.js +0 -1
  53. package/web/dist/assets/NeedsYouView-C6FfiTsg.js +0 -1
  54. package/web/dist/assets/Overview-D5RvWBg5.js +0 -1
  55. package/web/dist/assets/ProjectView-blBEh70W.js +0 -1
  56. package/web/dist/assets/ProposalView-CMntpKTc.js +0 -2
  57. package/web/dist/assets/ProposalsView-CVUAV2Mm.js +0 -1
  58. package/web/dist/assets/RunnersView-B-wuWVgB.js +0 -1
  59. package/web/dist/assets/SocietyView-CaVbfJWz.js +0 -1
  60. package/web/dist/assets/SubjectThread-Bd_-sDhJ.js +0 -1
  61. package/web/dist/assets/TaskFileView-CO4Y97OT.js +0 -4
  62. package/web/dist/assets/TaskView-DRZoHALo.js +0 -3
  63. package/web/dist/assets/TasksView-CQVQo3zF.js +0 -1
  64. package/web/dist/assets/ThreadForms-CUSeWu0c.js +0 -1
  65. package/web/dist/assets/ThreadItem-BPYYXh3V.js +0 -1
  66. package/web/dist/assets/ThreadView-BcI2i6FI.js +0 -1
  67. package/web/dist/assets/citizen-Eff1h9rA.js +0 -1
  68. package/web/dist/assets/compose-D73Agshx.js +0 -1
  69. package/web/dist/assets/format-fw0G_Vff.js +0 -55
  70. package/web/dist/assets/governance-sRsfOw52.js +0 -1
  71. package/web/dist/assets/index-BkOq4PSU.css +0 -2
  72. package/web/dist/assets/index-CYiok0pT.js +0 -23
  73. package/web/dist/assets/link-Cl7vKbOz.js +0 -1
  74. package/web/dist/assets/mentions-BCdV0bMK.js +0 -1
  75. package/web/dist/assets/useMatch-D-tEHQEw.js +0 -1
  76. package/web/dist/assets/useStickyScroll-Bn4YyEXL.js +0 -1
@@ -9,7 +9,7 @@ import { serve } from "@hono/node-server";
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";
@@ -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";
@@ -303,6 +303,115 @@ function localTime(date, timeZone) {
303
303
  return `${part("weekday")} ${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}`;
304
304
  }
305
305
 
306
+ // packages/shared/dist/quota.js
307
+ import { z as z4 } from "zod";
308
+ var QUOTA_WARNING_PERCENT = 80;
309
+ var QuotaWindowSchema = z4.object({
310
+ /**
311
+ * The CLI's own name for the window: Claude's `five_hour`, `seven_day`, `seven_day_opus`, and
312
+ * `model:<label>` for a model's weekly window; Codex's `primary` and `secondary`.
313
+ */
314
+ key: z4.string().min(1).max(80),
315
+ /** What a scoped window counts, as the CLI labels it, such as a model's name. */
316
+ label: z4.string().min(1).max(80).optional(),
317
+ /** The window's length in minutes, when known. */
318
+ minutes: z4.number().int().positive().nullable().default(null),
319
+ /** How much of the window is used, in percent, or null when the CLI did not say. */
320
+ usedPercent: z4.number().nonnegative().nullable().default(null),
321
+ /** When the window starts afresh, when known. */
322
+ resetsAt: IsoDateTimeSchema.nullable().default(null),
323
+ /** When the CLI last reported this window. */
324
+ observedAt: IsoDateTimeSchema
325
+ });
326
+ var QuotaStatusSchema = z4.enum(["allowed", "warning", "limited", "none"]);
327
+ var QuotaCreditsSchema = z4.object({
328
+ hasCredits: z4.boolean(),
329
+ unlimited: z4.boolean(),
330
+ balance: z4.string().max(40).nullable()
331
+ });
332
+ var QuotaExtraUsageSchema = z4.object({
333
+ enabled: z4.boolean(),
334
+ monthlyLimit: z4.number().nonnegative().nullable(),
335
+ usedCredits: z4.number().nonnegative().nullable(),
336
+ currency: z4.string().min(1).max(8).nullable()
337
+ });
338
+ var CliQuotaSchema = z4.object({
339
+ /** When the CLI last reported anything here. */
340
+ observedAt: IsoDateTimeSchema,
341
+ /** Whether it came from a turn's stream or from asking the CLI. */
342
+ source: z4.enum(["turn", "read"]),
343
+ status: QuotaStatusSchema,
344
+ /** While `limited`: when work can go on, when known, and the window that stopped it. */
345
+ limitedUntil: IsoDateTimeSchema.nullable().default(null),
346
+ limitedBy: z4.string().min(1).max(80).nullable().default(null),
347
+ /** The plan's name as the CLI gives it, such as `max` or `pro`. */
348
+ plan: z4.string().min(1).max(40).nullable().default(null),
349
+ windows: z4.array(QuotaWindowSchema).max(24).default([]),
350
+ credits: QuotaCreditsSchema.nullable().default(null),
351
+ extraUsage: QuotaExtraUsageSchema.nullable().default(null)
352
+ });
353
+ function mergeQuota(previous, next) {
354
+ if (previous === void 0) {
355
+ return next;
356
+ }
357
+ const windows = new Map(previous.windows.map((window) => [window.key, window]));
358
+ for (const window of next.windows) {
359
+ windows.set(window.key, window);
360
+ }
361
+ return {
362
+ ...next,
363
+ plan: next.plan ?? previous.plan,
364
+ credits: next.credits ?? previous.credits,
365
+ extraUsage: next.extraUsage ?? previous.extraUsage,
366
+ windows: [...windows.values()]
367
+ };
368
+ }
369
+ function windowCurrent(window, now) {
370
+ return window.resetsAt === null || Date.parse(window.resetsAt) > now.getTime();
371
+ }
372
+ function quotaLevel(quota, now) {
373
+ if (quota.status === "none") {
374
+ return "none";
375
+ }
376
+ if (quota.status === "limited" && (quota.limitedUntil === null || Date.parse(quota.limitedUntil) > now.getTime())) {
377
+ return "limited";
378
+ }
379
+ const high = quota.windows.some((window) => windowCurrent(window, now) && window.usedPercent !== null && window.usedPercent >= QUOTA_WARNING_PERCENT);
380
+ return quota.status === "warning" || high ? "warning" : "ok";
381
+ }
382
+ var WINDOW_NAMES = {
383
+ five_hour: "5h",
384
+ seven_day: "7d",
385
+ seven_day_opus: "7d Opus",
386
+ seven_day_sonnet: "7d Sonnet",
387
+ seven_day_oauth_apps: "7d apps",
388
+ seven_day_overage_included: "7d extra",
389
+ overage: "extra usage"
390
+ };
391
+ function windowName(window) {
392
+ if (window.label !== void 0) {
393
+ return window.minutes === 10080 ? `7d ${window.label}` : window.label;
394
+ }
395
+ const named = WINDOW_NAMES[window.key];
396
+ if (named !== void 0) {
397
+ return named;
398
+ }
399
+ if (window.minutes !== null) {
400
+ return window.minutes % 1440 === 0 ? `${window.minutes / 1440}d` : window.minutes % 60 === 0 ? `${window.minutes / 60}h` : `${window.minutes}m`;
401
+ }
402
+ return window.key;
403
+ }
404
+ function readings(quota) {
405
+ 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();
406
+ return windows.join(",");
407
+ }
408
+ function quotaChanged(previous, next) {
409
+ if (previous === void 0) {
410
+ return true;
411
+ }
412
+ 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);
413
+ }
414
+
306
415
  // packages/shared/dist/board.js
307
416
  var ASK_CHANNEL = "asks";
308
417
  var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
@@ -315,24 +424,24 @@ var SOCIETY_SCOPE = "society";
315
424
  function wakeScope(member, project) {
316
425
  return project !== null && member.memberships.includes(project) ? project : SOCIETY_SCOPE;
317
426
  }
318
- var CliKindSchema = z4.enum(["claude", "codex"]);
319
- var ArchivedChannelSchema = z4.object({
427
+ var CliKindSchema = z5.enum(["claude", "codex"]);
428
+ var ArchivedChannelSchema = z5.object({
320
429
  name: NameSchema,
321
430
  at: IsoDateTimeSchema,
322
431
  by: NameSchema,
323
- reason: z4.string().min(1)
432
+ reason: z5.string().min(1)
324
433
  });
325
- var SocietySchema = z4.object({
326
- name: z4.string().min(1),
327
- version: z4.literal(1),
434
+ var SocietySchema = z5.object({
435
+ name: z5.string().min(1),
436
+ version: z5.literal(1),
328
437
  createdAt: IsoDateTimeSchema,
329
438
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
330
- channels: z4.array(NameSchema),
331
- archivedChannels: z4.array(ArchivedChannelSchema).default([]),
439
+ channels: z5.array(NameSchema),
440
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
332
441
  /** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
333
442
  timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
334
443
  });
335
- var CompletionEffectSchema = z4.enum(["none", "merge", "ghpr"]);
444
+ var CompletionEffectSchema = z5.enum(["none", "merge", "ghpr"]);
336
445
  var GITHUB_REMOTE = [
337
446
  /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
338
447
  /^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
@@ -348,7 +457,7 @@ function githubRepository(remote) {
348
457
  return null;
349
458
  }
350
459
  var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
351
- var PullRequestUrlSchema = z4.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
460
+ var PullRequestUrlSchema = z5.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
352
461
  function parsePullRequestUrl(url) {
353
462
  const match = PULL_REQUEST_URL.exec(url.trim());
354
463
  return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
@@ -356,72 +465,72 @@ function parsePullRequestUrl(url) {
356
465
  function sameRepository(a, b) {
357
466
  return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
358
467
  }
359
- var MergeSubjectSchema = z4.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
360
- var PullRequestLinkSchema = z4.object({
468
+ var MergeSubjectSchema = z5.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
469
+ var PullRequestLinkSchema = z5.object({
361
470
  url: PullRequestUrlSchema,
362
- number: z4.number().int().positive(),
471
+ number: z5.number().int().positive(),
363
472
  subject: MergeSubjectSchema.optional(),
364
- body: z4.string().max(2e4).optional(),
473
+ body: z5.string().max(2e4).optional(),
365
474
  linkedBy: NameSchema,
366
475
  linkedAt: IsoDateTimeSchema,
367
476
  /**
368
477
  * Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
369
478
  * leaves it open, for the reason `detail` gives.
370
479
  */
371
- closed: z4.object({ at: IsoDateTimeSchema, ok: z4.boolean(), detail: z4.string() }).optional()
480
+ closed: z5.object({ at: IsoDateTimeSchema, ok: z5.boolean(), detail: z5.string() }).optional()
372
481
  });
373
- 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");
374
- var StageIdSchema = z4.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
482
+ 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");
483
+ var StageIdSchema = z5.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
375
484
  var stageFields = {
376
- name: z4.string().trim().min(1).max(120),
485
+ name: z5.string().trim().min(1).max(120),
377
486
  role: NameSchema.optional(),
378
487
  agent: NameSchema.optional(),
379
- gate: z4.boolean().default(false)
488
+ gate: z5.boolean().default(false)
380
489
  };
381
490
  function oneAssignee(stage) {
382
491
  return stage.role === void 0 || stage.agent === void 0;
383
492
  }
384
493
  var ONE_ASSIGNEE = { message: "a stage names a role or an agent, not both" };
385
- var PlanStageSchema = z4.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
386
- var PlanEditStageSchema = z4.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
387
- var PlanSchema = z4.array(PlanStageSchema);
388
- var PlanEditSchema = z4.array(PlanEditStageSchema);
389
- var ProjectSchema = z4.object({
494
+ var PlanStageSchema = z5.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
495
+ var PlanEditStageSchema = z5.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
496
+ var PlanSchema = z5.array(PlanStageSchema);
497
+ var PlanEditSchema = z5.array(PlanEditStageSchema);
498
+ var ProjectSchema = z5.object({
390
499
  slug: NameSchema,
391
- name: z4.string().min(1),
392
- repo: z4.string().nullable(),
393
- defaultBranch: z4.string().min(1),
500
+ name: z5.string().min(1),
501
+ repo: z5.string().nullable(),
502
+ defaultBranch: z5.string().min(1),
394
503
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
395
- channels: z4.array(NameSchema),
396
- archivedChannels: z4.array(ArchivedChannelSchema).default([]),
397
- members: z4.array(NameSchema),
398
- approvers: z4.array(NameSchema),
399
- requiredCapabilities: z4.array(z4.string()),
504
+ channels: z5.array(NameSchema),
505
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
506
+ members: z5.array(NameSchema),
507
+ approvers: z5.array(NameSchema),
508
+ requiredCapabilities: z5.array(z5.string()),
400
509
  createdAt: IsoDateTimeSchema,
401
510
  onDone: CompletionEffectSchema.default("none"),
402
511
  /** The runner the project lives on, which holds its repository; set on the project's first turn. */
403
512
  runner: NameSchema.optional(),
404
513
  /** Set once the project is archived: nobody belongs to it, and nothing more is posted, filed, or joined there. */
405
- archived: z4.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z4.string().min(1) }).optional()
514
+ archived: z5.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z5.string().min(1) }).optional()
406
515
  });
407
- var TaskStepSchema = z4.object({
408
- action: z4.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
516
+ var TaskStepSchema = z5.object({
517
+ action: z5.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
409
518
  stage: StageIdSchema,
410
519
  to: StageIdSchema.nullable()
411
520
  });
412
- var ProposalStepSchema = z4.object({ action: z4.enum(["approved", "rejected"]) });
413
- var MessageStepSchema = z4.union([TaskStepSchema, ProposalStepSchema]);
521
+ var ProposalStepSchema = z5.object({ action: z5.enum(["approved", "rejected"]) });
522
+ var MessageStepSchema = z5.union([TaskStepSchema, ProposalStepSchema]);
414
523
  function isTaskStep(step) {
415
524
  return "stage" in step;
416
525
  }
417
- var KnowledgeNoticeSchema = z4.object({
418
- kind: z4.literal("knowledge"),
419
- action: z4.enum(["written", "updated", "removed"]),
526
+ var KnowledgeNoticeSchema = z5.object({
527
+ kind: z5.literal("knowledge"),
528
+ action: z5.enum(["written", "updated", "removed"]),
420
529
  topic: NameSchema,
421
530
  project: NameSchema.nullable()
422
531
  });
423
- var MessageNoticeSchema = z4.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
424
- var MessageFrontmatterSchema = z4.object({
532
+ var MessageNoticeSchema = z5.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
533
+ var MessageFrontmatterSchema = z5.object({
425
534
  id: UlidSchema,
426
535
  author: NameSchema,
427
536
  channel: ChannelRefSchema,
@@ -435,7 +544,7 @@ var MessageFrontmatterSchema = z4.object({
435
544
  /** On a post a verb made about its work, such as a knowledge topic's note: what it records. */
436
545
  notice: MessageNoticeSchema.optional(),
437
546
  ts: IsoDateTimeSchema,
438
- mentions: z4.array(NameSchema)
547
+ mentions: z5.array(NameSchema)
439
548
  });
440
549
  var MessageRefSchema = MessageFrontmatterSchema.pick({
441
550
  id: true,
@@ -445,16 +554,16 @@ var MessageRefSchema = MessageFrontmatterSchema.pick({
445
554
  ts: true
446
555
  });
447
556
  var MESSAGE_LOOKUP_LIMIT = 50;
448
- var TaskStatusSchema = z4.enum(["open", "claimed", "done", "abandoned"]);
449
- var ThreadStateSchema = z4.enum(["open", "closed"]);
450
- var ThreadSubjectSchema = z4.object({
451
- kind: z4.enum(["task", "proposal"]),
557
+ var TaskStatusSchema = z5.enum(["open", "claimed", "done", "abandoned"]);
558
+ var ThreadStateSchema = z5.enum(["open", "closed"]);
559
+ var ThreadSubjectSchema = z5.object({
560
+ kind: z5.enum(["task", "proposal"]),
452
561
  id: UlidSchema
453
562
  });
454
- var ThreadFrontmatterSchema = z4.object({
563
+ var ThreadFrontmatterSchema = z5.object({
455
564
  id: UlidSchema,
456
565
  channel: ChannelRefSchema,
457
- title: z4.string().min(1).max(200),
566
+ title: z5.string().min(1).max(200),
458
567
  subject: ThreadSubjectSchema.optional(),
459
568
  state: ThreadStateSchema,
460
569
  openedBy: NameSchema,
@@ -462,28 +571,28 @@ var ThreadFrontmatterSchema = z4.object({
462
571
  closedBy: NameSchema.optional(),
463
572
  closedAt: IsoDateTimeSchema.optional()
464
573
  });
465
- var StageSchema = z4.object({
574
+ var StageSchema = z5.object({
466
575
  id: StageIdSchema,
467
- name: z4.string().min(1),
576
+ name: z5.string().min(1),
468
577
  role: NameSchema.optional(),
469
578
  agent: NameSchema.optional(),
470
- gate: z4.boolean().default(false),
579
+ gate: z5.boolean().default(false),
471
580
  /** Everyone who has held the stage, oldest first. */
472
- holders: z4.array(NameSchema).default([]),
581
+ holders: z5.array(NameSchema).default([]),
473
582
  completedBy: NameSchema.optional(),
474
583
  completedAt: IsoDateTimeSchema.optional()
475
584
  });
476
- var TaskReturnSchema = z4.object({
585
+ var TaskReturnSchema = z5.object({
477
586
  from: StageIdSchema,
478
587
  by: NameSchema,
479
588
  at: IsoDateTimeSchema
480
589
  });
481
- var TaskFrontmatterSchema = z4.object({
590
+ var TaskFrontmatterSchema = z5.object({
482
591
  id: UlidSchema,
483
592
  project: NameSchema,
484
593
  /** The channel of the project the task is filed in, where its thread hangs. */
485
594
  channel: NameSchema.default("general"),
486
- title: z4.string().min(1),
595
+ title: z5.string().min(1),
487
596
  status: TaskStatusSchema,
488
597
  createdBy: NameSchema,
489
598
  createdAt: IsoDateTimeSchema,
@@ -492,19 +601,19 @@ var TaskFrontmatterSchema = z4.object({
492
601
  claimedBy: NameSchema.optional(),
493
602
  leaseExpiresAt: IsoDateTimeSchema.optional(),
494
603
  parentId: UlidSchema.optional(),
495
- blockedBy: z4.array(UlidSchema),
496
- requiredCapabilities: z4.array(z4.string()),
497
- stages: z4.array(StageSchema).min(1),
604
+ blockedBy: z5.array(UlidSchema),
605
+ requiredCapabilities: z5.array(z5.string()),
606
+ stages: z5.array(StageSchema).min(1),
498
607
  stage: StageIdSchema,
499
608
  /** When the current stage last became current or lost its holder: the waiting clock. */
500
609
  stageSince: IsoDateTimeSchema,
501
610
  /** The highest stage number issued, so a removed stage's id is never reused. */
502
- stageSeq: z4.number().int().positive(),
611
+ stageSeq: z5.number().int().positive(),
503
612
  onDone: CompletionEffectSchema.default("none"),
504
613
  /** The pull request a `ghpr` task lands through. */
505
614
  pullRequest: PullRequestLinkSchema.optional(),
506
615
  /** The last stage is complete and the completion effect is running. */
507
- completing: z4.boolean().default(false),
616
+ completing: z5.boolean().default(false),
508
617
  /** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
509
618
  returned: TaskReturnSchema.optional()
510
619
  });
@@ -528,79 +637,90 @@ function mayHoldStage(member, task) {
528
637
  }
529
638
  return !(stage.gate && task.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
530
639
  }
531
- var ModelNameSchema = z4.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
532
- var EffortSchema = z4.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
533
- var EffortOptionSchema = z4.object({
640
+ var ModelNameSchema = z5.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
641
+ var EffortSchema = z5.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
642
+ var EffortOptionSchema = z5.object({
534
643
  id: EffortSchema,
535
- description: z4.string().default("")
644
+ description: z5.string().default("")
536
645
  });
537
- var ModelOptionSchema = z4.object({
646
+ var ModelOptionSchema = z5.object({
538
647
  id: ModelNameSchema,
539
- name: z4.string(),
540
- description: z4.string().default(""),
541
- isDefault: z4.boolean().default(false),
542
- efforts: z4.array(EffortOptionSchema).default([]),
648
+ name: z5.string(),
649
+ description: z5.string().default(""),
650
+ isDefault: z5.boolean().default(false),
651
+ efforts: z5.array(EffortOptionSchema).default([]),
543
652
  defaultEffort: EffortSchema.optional()
544
653
  });
545
- var RunnerModelsSchema = z4.object({
654
+ var RunnerModelsSchema = z5.object({
546
655
  runner: NameSchema,
547
656
  cli: CliKindSchema,
548
- models: z4.array(ModelOptionSchema)
657
+ models: z5.array(ModelOptionSchema)
549
658
  });
550
- var AgentStatusSchema = z4.enum(["active", "retired"]);
551
- var AgentSchema = z4.object({
659
+ var AgentStatusSchema = z5.enum(["active", "retired"]);
660
+ var AgentSchema = z5.object({
552
661
  name: NameSchema,
553
662
  role: NameSchema,
554
663
  cli: CliKindSchema.nullable(),
555
- model: z4.string().optional(),
664
+ model: z5.string().optional(),
556
665
  /** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
557
- effort: z4.string().optional(),
666
+ effort: z5.string().optional(),
558
667
  /** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
559
668
  homeRunner: NameSchema.optional(),
560
- memberships: z4.array(NameSchema),
561
- subscriptions: z4.array(ChannelRefSchema),
669
+ memberships: z5.array(NameSchema),
670
+ subscriptions: z5.array(ChannelRefSchema),
562
671
  status: AgentStatusSchema,
563
672
  createdAt: IsoDateTimeSchema,
564
- tokenHash: z4.string().min(1),
673
+ tokenHash: z5.string().min(1),
565
674
  retiredAt: IsoDateTimeSchema.optional(),
566
- retiredReason: z4.string().optional()
675
+ retiredReason: z5.string().optional()
567
676
  });
568
- var MemberSchema = z4.object({
677
+ var MemberSchema = z5.object({
569
678
  name: NameSchema,
570
679
  role: NameSchema,
571
680
  cli: CliKindSchema.nullable(),
572
681
  homeRunner: NameSchema.optional(),
573
682
  status: AgentStatusSchema,
574
- resident: z4.boolean().default(false),
683
+ resident: z5.boolean().default(false),
575
684
  /** The model configured on the record, if any; the CLI's own default applies otherwise. */
576
- model: z4.string().optional(),
685
+ model: z5.string().optional(),
577
686
  /** The model the CLI reported on the citizen's last turn. */
578
- lastModel: z4.string().optional(),
687
+ lastModel: z5.string().optional(),
579
688
  /** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
580
- effort: z4.string().optional(),
689
+ effort: z5.string().optional(),
581
690
  /** The citizen's own skills, by name. */
582
- skills: z4.array(NameSchema).default([]),
583
- memberships: z4.array(NameSchema),
584
- subscriptions: z4.array(ChannelRefSchema),
585
- claimsHeld: z4.number().int().nonnegative().default(0),
586
- tasksDone: z4.number().int().nonnegative().default(0),
691
+ skills: z5.array(NameSchema).default([]),
692
+ memberships: z5.array(NameSchema),
693
+ subscriptions: z5.array(ChannelRefSchema),
694
+ claimsHeld: z5.number().int().nonnegative().default(0),
695
+ tasksDone: z5.number().int().nonnegative().default(0),
587
696
  lastTurnAt: IsoDateTimeSchema.optional(),
588
- lastTurnOutcome: z4.string().optional(),
697
+ lastTurnOutcome: z5.string().optional(),
589
698
  createdAt: IsoDateTimeSchema,
590
699
  retiredAt: IsoDateTimeSchema.optional()
591
700
  });
592
- var RunnerOsSchema = z4.enum(["linux", "windows", "darwin"]);
593
- var RunnerSchema = z4.object({
701
+ var RunnerOsSchema = z5.enum(["linux", "windows", "darwin"]);
702
+ var GitIdentityPartSchema = z5.enum(["name", "email"]);
703
+ var RunnerSchema = z5.object({
594
704
  name: NameSchema,
595
705
  os: RunnerOsSchema,
596
- clis: z4.array(CliKindSchema),
597
- capabilities: z4.array(z4.string()),
706
+ clis: z5.array(CliKindSchema),
707
+ capabilities: z5.array(z5.string()),
598
708
  /** The release the runner reported when it last registered; absent until it has. */
599
- version: z4.string().optional(),
600
- status: z4.enum(["connected", "disconnected"]),
601
- lastSeen: IsoDateTimeSchema.optional()
709
+ version: z5.string().optional(),
710
+ /**
711
+ * What the runner's account lacked of a git identity when it last registered, empty when it had
712
+ * both; absent while the runner has not said, as one from before runners checked never does.
713
+ */
714
+ gitIdentityMissing: z5.array(GitIdentityPartSchema).optional(),
715
+ status: z5.enum(["connected", "disconnected"]),
716
+ lastSeen: IsoDateTimeSchema.optional(),
717
+ /**
718
+ * Each CLI's plan quota as its login on this runner last reported it, from a turn's stream or a
719
+ * read; absent until one has.
720
+ */
721
+ quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional()
602
722
  });
603
- var ProposalKindSchema = z4.enum([
723
+ var ProposalKindSchema = z5.enum([
604
724
  "role",
605
725
  "member",
606
726
  "channel",
@@ -609,42 +729,42 @@ var ProposalKindSchema = z4.enum([
609
729
  "skill",
610
730
  "archive"
611
731
  ]);
612
- var ProposalStatusSchema = z4.enum([
732
+ var ProposalStatusSchema = z5.enum([
613
733
  "proposed",
614
734
  "approved",
615
735
  "rejected",
616
736
  "provisioned",
617
737
  "retired"
618
738
  ]);
619
- var MemberProposalSchema = z4.object({
739
+ var MemberProposalSchema = z5.object({
620
740
  name: NameSchema,
621
741
  role: NameSchema,
622
742
  cli: CliKindSchema,
623
- model: z4.string().optional(),
743
+ model: z5.string().optional(),
624
744
  homeRunner: NameSchema.optional(),
625
- memberships: z4.array(NameSchema).default([]),
626
- subscriptions: z4.array(ChannelRefSchema).default([]),
627
- seedInstructions: z4.string().optional()
745
+ memberships: z5.array(NameSchema).default([]),
746
+ subscriptions: z5.array(ChannelRefSchema).default([]),
747
+ seedInstructions: z5.string().optional()
628
748
  });
629
- var ChannelProposalSchema = z4.object({
749
+ var ChannelProposalSchema = z5.object({
630
750
  project: NameSchema.nullable().default(null),
631
751
  name: NameSchema,
632
- purpose: z4.string().min(1)
752
+ purpose: z5.string().min(1)
633
753
  });
634
- var RoleChangesSchema = z4.strictObject({
635
- maxReplicas: z4.number().int().min(1).optional(),
636
- backlogThreshold: z4.number().positive().optional(),
637
- wakeTriggers: z4.array(CharterTriggerSchema).optional(),
638
- verbs: z4.array(VerbNameSchema).optional()
754
+ var RoleChangesSchema = z5.strictObject({
755
+ maxReplicas: z5.number().int().min(1).optional(),
756
+ backlogThreshold: z5.number().positive().optional(),
757
+ wakeTriggers: z5.array(CharterTriggerSchema).optional(),
758
+ verbs: z5.array(VerbNameSchema).optional()
639
759
  }).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
640
760
  message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
641
761
  });
642
- var ReallocationProposalSchema = z4.object({
762
+ var ReallocationProposalSchema = z5.object({
643
763
  role: NameSchema,
644
764
  changes: RoleChangesSchema,
645
- description: z4.string().min(1)
765
+ description: z5.string().min(1)
646
766
  });
647
- var LegacyReallocationSchema = z4.object({ description: z4.string().min(1) });
767
+ var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
648
768
  function applyRoleChanges(role, changes) {
649
769
  return {
650
770
  ...role,
@@ -679,18 +799,18 @@ function roleChangeLines(role, changes) {
679
799
  }
680
800
  return lines;
681
801
  }
682
- var RetirementProposalSchema = z4.object({
802
+ var RetirementProposalSchema = z5.object({
683
803
  agent: NameSchema,
684
- reason: z4.string().min(1)
804
+ reason: z5.string().min(1)
685
805
  });
686
- var ArchiveProposalSchema = z4.object({
806
+ var ArchiveProposalSchema = z5.object({
687
807
  project: NameSchema,
688
- reason: z4.string().min(1)
808
+ reason: z5.string().min(1)
689
809
  });
690
- var SkillProposalSchema = z4.object({
810
+ var SkillProposalSchema = z5.object({
691
811
  name: NameSchema,
692
- summary: z4.string().min(1).max(200),
693
- body: z4.string().min(1).optional()
812
+ summary: z5.string().min(1).max(200),
813
+ body: z5.string().min(1).optional()
694
814
  });
695
815
  var ProposalCharterSchemas = {
696
816
  role: RoleCharterSchema,
@@ -793,23 +913,23 @@ function describeCharter(kind, charter) {
793
913
  }
794
914
  return `${kind} ${JSON.stringify(charter)}`;
795
915
  }
796
- var SkillSchema = z4.object({
916
+ var SkillSchema = z5.object({
797
917
  name: NameSchema,
798
- summary: z4.string().default(""),
799
- scope: z4.enum(["own", "society"]),
800
- path: z4.string().min(1)
918
+ summary: z5.string().default(""),
919
+ scope: z5.enum(["own", "society"]),
920
+ path: z5.string().min(1)
801
921
  });
802
- var RemovedSkillSchema = z4.object({
922
+ var RemovedSkillSchema = z5.object({
803
923
  name: NameSchema,
804
- reason: z4.string().min(1),
924
+ reason: z5.string().min(1),
805
925
  removedBy: NameSchema,
806
926
  removedAt: IsoDateTimeSchema
807
927
  });
808
- var HomeConflictSchema = z4.object({
928
+ var HomeConflictSchema = z5.object({
809
929
  /** The copy, relative to the home. */
810
- path: z4.string().min(1),
930
+ path: z5.string().min(1),
811
931
  /** The file beside it, whose version the board kept. */
812
- file: z4.string().min(1),
932
+ file: z5.string().min(1),
813
933
  /** When the copy reached the board. */
814
934
  since: IsoDateTimeSchema
815
935
  });
@@ -817,79 +937,79 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
817
937
  function conflictCopyOf(copy) {
818
938
  return CONFLICT_COPY.exec(copy)?.[1] ?? null;
819
939
  }
820
- var CommitIdSchema = z4.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
821
- var HomeFileChangeSchema = z4.object({
822
- path: z4.string().min(1),
823
- status: z4.enum(["added", "modified", "deleted"]),
940
+ var CommitIdSchema = z5.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
941
+ var HomeFileChangeSchema = z5.object({
942
+ path: z5.string().min(1),
943
+ status: z5.enum(["added", "modified", "deleted"]),
824
944
  /** Lines added and removed, or null for a binary file. */
825
- added: z4.number().int().nonnegative().nullable(),
826
- removed: z4.number().int().nonnegative().nullable()
945
+ added: z5.number().int().nonnegative().nullable(),
946
+ removed: z5.number().int().nonnegative().nullable()
827
947
  });
828
- var HomeChangeSchema = z4.object({
948
+ var HomeChangeSchema = z5.object({
829
949
  commit: CommitIdSchema,
830
950
  at: IsoDateTimeSchema,
831
- kind: z4.enum(["turn", "board", "merge"]),
832
- author: z4.string(),
951
+ kind: z5.enum(["turn", "board", "merge"]),
952
+ author: z5.string(),
833
953
  /** The turn that made it, from the runner's commit message. */
834
954
  turnId: UlidSchema.optional(),
835
- subject: z4.string(),
836
- files: z4.array(HomeFileChangeSchema)
955
+ subject: z5.string(),
956
+ files: z5.array(HomeFileChangeSchema)
837
957
  });
838
- var HomeHistorySchema = z4.object({
839
- changes: z4.array(HomeChangeSchema),
958
+ var HomeHistorySchema = z5.object({
959
+ changes: z5.array(HomeChangeSchema),
840
960
  /** Whether older commits remain past the ones read. */
841
- more: z4.boolean()
961
+ more: z5.boolean()
842
962
  });
843
963
  var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
844
- patch: z4.string().nullable(),
964
+ patch: z5.string().nullable(),
845
965
  /** The patch was cut at its size limit. */
846
- truncated: z4.boolean()
966
+ truncated: z5.boolean()
847
967
  });
848
- var KnowledgeSchema = z4.object({
968
+ var KnowledgeSchema = z5.object({
849
969
  topic: NameSchema,
850
970
  project: NameSchema.nullable(),
851
- revision: z4.number().int().min(0),
971
+ revision: z5.number().int().min(0),
852
972
  updatedBy: NameSchema,
853
973
  updatedAt: IsoDateTimeSchema
854
974
  });
855
- var RemovedKnowledgeSchema = z4.object({
975
+ var RemovedKnowledgeSchema = z5.object({
856
976
  topic: NameSchema,
857
977
  project: NameSchema.nullable(),
858
978
  removedBy: NameSchema,
859
979
  removedAt: IsoDateTimeSchema
860
980
  });
861
- var DashboardSchema = z4.object({
981
+ var DashboardSchema = z5.object({
862
982
  project: NameSchema,
863
- revision: z4.number().int().min(0),
983
+ revision: z5.number().int().min(0),
864
984
  /** Absent until someone updates the dashboard the project began with. */
865
985
  updatedBy: NameSchema.optional(),
866
986
  updatedAt: IsoDateTimeSchema.optional(),
867
- body: z4.string()
987
+ body: z5.string()
868
988
  });
869
- var ProposalOriginSchema = z4.object({
989
+ var ProposalOriginSchema = z5.object({
870
990
  scope: NameSchema,
871
991
  thread: UlidSchema.optional(),
872
992
  channel: NameSchema.optional()
873
993
  });
874
994
  var SKILL_FILES_LIMIT = 100;
875
995
  var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
876
- var SkillFileSchema = z4.object({
877
- path: z4.string().min(1),
878
- size: z4.number().int().nonnegative(),
879
- executable: z4.boolean()
996
+ var SkillFileSchema = z5.object({
997
+ path: z5.string().min(1),
998
+ size: z5.number().int().nonnegative(),
999
+ executable: z5.boolean()
880
1000
  });
881
- var SkillFilesSchema = z4.object({
1001
+ var SkillFilesSchema = z5.object({
882
1002
  /** The commit of the proposer's home the files came from. */
883
1003
  commit: CommitIdSchema,
884
1004
  takenAt: IsoDateTimeSchema,
885
- files: z4.array(SkillFileSchema)
1005
+ files: z5.array(SkillFileSchema)
886
1006
  });
887
1007
  var SkillFileContentSchema = SkillFileSchema.extend({
888
- text: z4.string().nullable(),
1008
+ text: z5.string().nullable(),
889
1009
  /** The text was cut at its size limit. */
890
- truncated: z4.boolean()
1010
+ truncated: z5.boolean()
891
1011
  });
892
- var ProposalFrontmatterSchema = z4.object({
1012
+ var ProposalFrontmatterSchema = z5.object({
893
1013
  id: UlidSchema,
894
1014
  kind: ProposalKindSchema,
895
1015
  proposedBy: NameSchema,
@@ -901,146 +1021,146 @@ var ProposalFrontmatterSchema = z4.object({
901
1021
  skillFiles: SkillFilesSchema.optional(),
902
1022
  decidedBy: NameSchema.optional(),
903
1023
  decidedAt: IsoDateTimeSchema.optional(),
904
- reason: z4.string().optional(),
905
- charter: z4.record(z4.string(), z4.unknown()),
1024
+ reason: z5.string().optional(),
1025
+ charter: z5.record(z5.string(), z5.unknown()),
906
1026
  /** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
907
1027
  provisionedAt: IsoDateTimeSchema.optional(),
908
- provision: z4.record(z4.string(), z4.unknown()).optional()
1028
+ provision: z5.record(z5.string(), z5.unknown()).optional()
909
1029
  });
910
- var DecisionSchema = z4.object({
1030
+ var DecisionSchema = z5.object({
911
1031
  id: UlidSchema,
912
1032
  proposalId: UlidSchema,
913
1033
  decidedBy: NameSchema,
914
- outcome: z4.enum(["approved", "rejected"]),
915
- reason: z4.string().optional(),
1034
+ outcome: z5.enum(["approved", "rejected"]),
1035
+ reason: z5.string().optional(),
916
1036
  ts: IsoDateTimeSchema
917
1037
  });
918
1038
 
919
1039
  // packages/shared/dist/verbs.js
920
- import { z as z5 } from "zod";
921
- var MomentSchema = z5.iso.datetime({ offset: true });
922
- var CronTitleSchema = z5.string().trim().min(1).max(120);
923
- var CronNoteSchema = z5.string().trim().min(1).max(4e3);
1040
+ import { z as z6 } from "zod";
1041
+ var MomentSchema = z6.iso.datetime({ offset: true });
1042
+ var CronTitleSchema = z6.string().trim().min(1).max(120);
1043
+ var CronNoteSchema = z6.string().trim().min(1).max(4e3);
924
1044
  var VerbInputs = {
925
- post_message: z5.object({
1045
+ post_message: z6.object({
926
1046
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
927
1047
  channel: ChannelRefSchema.optional(),
928
- body: z5.string().min(1),
1048
+ body: z6.string().min(1),
929
1049
  thread_id: UlidSchema.optional()
930
1050
  }),
931
- read_inbox: z5.object({
1051
+ read_inbox: z6.object({
932
1052
  since_cursor: UlidSchema.nullable().optional(),
933
- limit: z5.number().int().positive().max(200).default(50),
934
- advance: z5.boolean().default(true)
1053
+ limit: z6.number().int().positive().max(200).default(50),
1054
+ advance: z6.boolean().default(true)
935
1055
  }),
936
- search: z5.object({
937
- query: z5.string().min(1),
1056
+ search: z6.object({
1057
+ query: z6.string().min(1),
938
1058
  project: NameSchema.optional(),
939
1059
  channel: ChannelRefSchema.optional(),
940
- limit: z5.number().int().positive().max(100).default(20)
1060
+ limit: z6.number().int().positive().max(100).default(20)
941
1061
  }),
942
1062
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
943
- open_thread: z5.object({
1063
+ open_thread: z6.object({
944
1064
  task_id: UlidSchema.optional(),
945
1065
  proposal_id: UlidSchema.optional(),
946
1066
  channel: ChannelRefSchema.optional(),
947
- title: z5.string().min(1).max(200).optional()
1067
+ title: z6.string().min(1).max(200).optional()
948
1068
  }),
949
- close_thread: z5.object({ thread_id: UlidSchema, summary: z5.string().min(1) }),
950
- create_task: z5.object({
1069
+ close_thread: z6.object({ thread_id: UlidSchema, summary: z6.string().min(1) }),
1070
+ create_task: z6.object({
951
1071
  project: NameSchema,
952
- title: z5.string().min(1).max(200),
953
- body: z5.string().default(""),
1072
+ title: z6.string().min(1).max(200),
1073
+ body: z6.string().default(""),
954
1074
  parent_id: UlidSchema.optional(),
955
- required_capabilities: z5.array(z5.string()).default([]),
1075
+ required_capabilities: z6.array(z6.string()).default([]),
956
1076
  /** The plan; without it the task gets one stage, work, that anyone in the project may take. */
957
- stages: z5.array(PlanStageSchema).min(1).optional(),
1077
+ stages: z6.array(PlanStageSchema).min(1).optional(),
958
1078
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
959
1079
  channel: NameSchema.default("general")
960
1080
  }),
961
- claim_task: z5.object({ task_id: UlidSchema }),
962
- release_task: z5.object({ task_id: UlidSchema }),
963
- update_task: z5.object({
1081
+ claim_task: z6.object({ task_id: UlidSchema }),
1082
+ release_task: z6.object({ task_id: UlidSchema }),
1083
+ update_task: z6.object({
964
1084
  task_id: UlidSchema,
965
1085
  /** Move the task back to this earlier stage. */
966
1086
  stage: StageIdSchema.optional(),
967
- status: z5.enum(["abandoned"]).optional(),
1087
+ status: z6.enum(["abandoned"]).optional(),
968
1088
  /** Posted to the task's thread as the caller, marked with the step when the task moved. */
969
- note: z5.string().min(1).optional(),
970
- blocked_by: z5.array(UlidSchema).optional(),
1089
+ note: z6.string().min(1).optional(),
1090
+ blocked_by: z6.array(UlidSchema).optional(),
971
1091
  /**
972
1092
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
973
1093
  * commit's subject and body; null unlinks it.
974
1094
  */
975
- pull_request: z5.object({
1095
+ pull_request: z6.object({
976
1096
  url: PullRequestUrlSchema,
977
1097
  merge_subject: MergeSubjectSchema.optional(),
978
- merge_body: z5.string().max(2e4).optional()
1098
+ merge_body: z6.string().max(2e4).optional()
979
1099
  }).nullable().optional()
980
1100
  }),
981
- get_task: z5.object({ task_id: UlidSchema }),
982
- subscribe: z5.object({ channel: ChannelRefSchema }),
983
- unsubscribe: z5.object({ channel: ChannelRefSchema }),
984
- propose: z5.object({
1101
+ get_task: z6.object({ task_id: UlidSchema }),
1102
+ subscribe: z6.object({ channel: ChannelRefSchema }),
1103
+ unsubscribe: z6.object({ channel: ChannelRefSchema }),
1104
+ propose: z6.object({
985
1105
  kind: ProposalKindSchema,
986
- charter: z5.record(z5.string(), z5.unknown()),
987
- rationale: z5.string().default("")
1106
+ charter: z6.record(z6.string(), z6.unknown()),
1107
+ rationale: z6.string().default("")
988
1108
  }),
989
- approve: z5.object({ proposal_id: UlidSchema, reason: z5.string().optional() }),
990
- reject: z5.object({ proposal_id: UlidSchema, reason: z5.string().min(1) }),
991
- create_project: z5.object({
1109
+ approve: z6.object({ proposal_id: UlidSchema, reason: z6.string().optional() }),
1110
+ reject: z6.object({ proposal_id: UlidSchema, reason: z6.string().min(1) }),
1111
+ create_project: z6.object({
992
1112
  slug: NameSchema,
993
- name: z5.string().min(1).optional(),
994
- repo: z5.string().min(1).nullable().default(null),
1113
+ name: z6.string().min(1).optional(),
1114
+ repo: z6.string().min(1).nullable().default(null),
995
1115
  default_branch: DefaultBranchSchema.default("main"),
996
1116
  on_done: CompletionEffectSchema.optional()
997
1117
  }),
998
- join_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
999
- leave_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
1000
- write_knowledge: z5.object({
1118
+ join_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1119
+ leave_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1120
+ write_knowledge: z6.object({
1001
1121
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
1002
1122
  project: NameSchema.nullable().default(null),
1003
1123
  topic: NameSchema,
1004
- body: z5.string().min(1),
1124
+ body: z6.string().min(1),
1005
1125
  /** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
1006
- revision: z5.number().int().min(0)
1126
+ revision: z6.number().int().min(0)
1007
1127
  }),
1008
- remove_knowledge: z5.object({
1128
+ remove_knowledge: z6.object({
1009
1129
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
1010
1130
  project: NameSchema.nullable().default(null),
1011
1131
  topic: NameSchema
1012
1132
  }),
1013
- plan_task: z5.object({
1133
+ plan_task: z6.object({
1014
1134
  task_id: UlidSchema,
1015
- stages: z5.array(PlanEditStageSchema),
1135
+ stages: z6.array(PlanEditStageSchema),
1016
1136
  on_done: CompletionEffectSchema.optional()
1017
1137
  }),
1018
1138
  /** The note is posted to the task's thread as the caller, marked with the step. */
1019
- advance_task: z5.object({ task_id: UlidSchema, note: z5.string().min(1).optional() }),
1020
- configure_project: z5.object({
1139
+ advance_task: z6.object({ task_id: UlidSchema, note: z6.string().min(1).optional() }),
1140
+ configure_project: z6.object({
1021
1141
  project: NameSchema,
1022
1142
  on_done: CompletionEffectSchema.optional(),
1023
1143
  /** A new display name; the slug, which everything refers to the project by, never changes. */
1024
- name: z5.string().trim().min(1).max(120).optional(),
1144
+ name: z6.string().trim().min(1).max(120).optional(),
1025
1145
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
1026
1146
  default_branch: DefaultBranchSchema.optional()
1027
1147
  }),
1028
- archive_project: z5.object({ project: NameSchema, reason: z5.string().min(1) }),
1029
- create_channel: z5.object({
1148
+ archive_project: z6.object({ project: NameSchema, reason: z6.string().min(1) }),
1149
+ create_channel: z6.object({
1030
1150
  /** A project, or null for a channel of the society. */
1031
1151
  project: NameSchema.nullable().default(null),
1032
1152
  name: NameSchema,
1033
- purpose: z5.string().min(1)
1153
+ purpose: z6.string().min(1)
1034
1154
  }),
1035
- archive_channel: z5.object({ channel: ChannelRefSchema, reason: z5.string().min(1) }),
1036
- update_dashboard: z5.object({
1155
+ archive_channel: z6.object({ channel: ChannelRefSchema, reason: z6.string().min(1) }),
1156
+ update_dashboard: z6.object({
1037
1157
  project: NameSchema,
1038
1158
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
1039
- body: z5.string().min(1),
1159
+ body: z6.string().min(1),
1040
1160
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
1041
- revision: z5.number().int().min(0)
1161
+ revision: z6.number().int().min(0)
1042
1162
  }),
1043
- create_cron: z5.object({
1163
+ create_cron: z6.object({
1044
1164
  title: CronTitleSchema,
1045
1165
  /** What to do when it fires; quoted in the prompt of every turn it starts. */
1046
1166
  note: CronNoteSchema,
@@ -1059,10 +1179,10 @@ var VerbInputs = {
1059
1179
  /** An open channel of that place other than general, whose conversation it fires in. */
1060
1180
  channel: NameSchema.optional()
1061
1181
  }),
1062
- update_cron: z5.object({
1182
+ update_cron: z6.object({
1063
1183
  cron_id: UlidSchema,
1064
1184
  /** Pause it, or resume it, which starts its schedule afresh from now. */
1065
- paused: z5.boolean().optional(),
1185
+ paused: z6.boolean().optional(),
1066
1186
  title: CronTitleSchema.optional(),
1067
1187
  note: CronNoteSchema.optional(),
1068
1188
  /** A new expression, or `at` for one time; either starts the schedule afresh. */
@@ -1071,8 +1191,8 @@ var VerbInputs = {
1071
1191
  timezone: TimeZoneSchema.nullable().optional(),
1072
1192
  at: MomentSchema.optional()
1073
1193
  }),
1074
- remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) }),
1075
- remove_skill: z5.object({ name: NameSchema, reason: z5.string().trim().min(1) })
1194
+ remove_cron: z6.object({ cron_id: UlidSchema, reason: z6.string().trim().min(1) }),
1195
+ remove_skill: z6.object({ name: NameSchema, reason: z6.string().trim().min(1) })
1076
1196
  };
1077
1197
  var VERB_DESCRIPTIONS = {
1078
1198
  post_message: "Post a markdown message to a channel, or into a thread when thread_id is given; a thread's message goes to the thread's channel.",
@@ -1109,7 +1229,7 @@ var VERB_DESCRIPTIONS = {
1109
1229
  };
1110
1230
 
1111
1231
  // packages/shared/dist/events.js
1112
- import { z as z6 } from "zod";
1232
+ import { z as z7 } from "zod";
1113
1233
  var BOARD_EVENT_TYPES = [
1114
1234
  "society.initialized",
1115
1235
  "project.added",
@@ -1154,6 +1274,7 @@ var BOARD_EVENT_TYPES = [
1154
1274
  "user.signed_out",
1155
1275
  "user.token_rotated",
1156
1276
  "runner.changed",
1277
+ "runner.quota",
1157
1278
  "ops.signal",
1158
1279
  "paused.changed",
1159
1280
  "wake.requested",
@@ -1174,28 +1295,36 @@ var BOARD_EVENT_TYPES = [
1174
1295
  "cron.skipped",
1175
1296
  "cron.ended"
1176
1297
  ];
1177
- var BoardEventTypeSchema = z6.enum(BOARD_EVENT_TYPES);
1178
- var BoardEventSchema = z6.object({
1298
+ var BoardEventTypeSchema = z7.enum(BOARD_EVENT_TYPES);
1299
+ var BoardEventSchema = z7.object({
1179
1300
  id: UlidSchema,
1180
1301
  ts: IsoDateTimeSchema,
1181
1302
  type: BoardEventTypeSchema,
1182
1303
  actor: NameSchema,
1183
- payload: z6.record(z6.string(), z6.unknown())
1304
+ payload: z7.record(z7.string(), z7.unknown())
1184
1305
  });
1185
- var UsageSchema = z6.object({
1186
- inputTokens: z6.number().int().nonnegative(),
1187
- outputTokens: z6.number().int().nonnegative(),
1188
- cacheReadTokens: z6.number().int().nonnegative().default(0),
1189
- cacheWriteTokens: z6.number().int().nonnegative().default(0)
1306
+ var UsageSchema = z7.object({
1307
+ inputTokens: z7.number().int().nonnegative(),
1308
+ outputTokens: z7.number().int().nonnegative(),
1309
+ cacheReadTokens: z7.number().int().nonnegative().default(0),
1310
+ cacheWriteTokens: z7.number().int().nonnegative().default(0)
1190
1311
  });
1191
- var TurnStatusSchema = z6.object({
1192
- summary: z6.string().min(1),
1193
- claimsHeld: z6.array(UlidSchema).default([]),
1194
- blockedOn: z6.array(z6.string()).default([]),
1195
- needsUserDecision: z6.boolean().default(false),
1196
- memoryUpdated: z6.boolean().default(false)
1312
+ function addUsage(a, b) {
1313
+ return {
1314
+ inputTokens: a.inputTokens + b.inputTokens,
1315
+ outputTokens: a.outputTokens + b.outputTokens,
1316
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
1317
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
1318
+ };
1319
+ }
1320
+ var TurnStatusSchema = z7.object({
1321
+ summary: z7.string().min(1),
1322
+ claimsHeld: z7.array(UlidSchema).default([]),
1323
+ blockedOn: z7.array(z7.string()).default([]),
1324
+ needsUserDecision: z7.boolean().default(false),
1325
+ memoryUpdated: z7.boolean().default(false)
1197
1326
  });
1198
- var TurnExitReasonSchema = z6.enum([
1327
+ var TurnExitReasonSchema = z7.enum([
1199
1328
  "completed",
1200
1329
  "timeout",
1201
1330
  "error",
@@ -1203,50 +1332,50 @@ var TurnExitReasonSchema = z6.enum([
1203
1332
  "blocked",
1204
1333
  "stopped"
1205
1334
  ]);
1206
- var AgentEventSchema = z6.discriminatedUnion("type", [
1207
- z6.object({
1208
- type: z6.literal("turn_started"),
1335
+ var AgentEventSchema = z7.discriminatedUnion("type", [
1336
+ z7.object({
1337
+ type: z7.literal("turn_started"),
1209
1338
  agent: NameSchema,
1210
- session: z6.string(),
1339
+ session: z7.string(),
1211
1340
  runner: NameSchema,
1212
1341
  /** The model the CLI reports running, when it reports one. */
1213
- model: z6.string().optional()
1342
+ model: z7.string().optional()
1214
1343
  }),
1215
1344
  /**
1216
1345
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
1217
1346
  * messages it delivered and the text the CLI was given.
1218
1347
  */
1219
- z6.object({
1220
- type: z6.literal("steered"),
1221
- steer: z6.string().min(1),
1222
- messages: z6.array(UlidSchema).optional(),
1223
- text: z6.string().optional()
1348
+ z7.object({
1349
+ type: z7.literal("steered"),
1350
+ steer: z7.string().min(1),
1351
+ messages: z7.array(UlidSchema).optional(),
1352
+ text: z7.string().optional()
1224
1353
  }),
1225
- z6.object({ type: z6.literal("text"), delta: z6.string() }),
1226
- z6.object({ type: z6.literal("tool_call"), name: z6.string(), input: z6.unknown() }),
1227
- z6.object({
1228
- type: z6.literal("tool_result"),
1229
- name: z6.string(),
1230
- ok: z6.boolean(),
1354
+ z7.object({ type: z7.literal("text"), delta: z7.string() }),
1355
+ z7.object({ type: z7.literal("tool_call"), name: z7.string(), input: z7.unknown() }),
1356
+ z7.object({
1357
+ type: z7.literal("tool_result"),
1358
+ name: z7.string(),
1359
+ ok: z7.boolean(),
1231
1360
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
1232
- output: z6.string().optional()
1361
+ output: z7.string().optional()
1233
1362
  }),
1234
- z6.object({ type: z6.literal("approval_requested"), kind: z6.string(), detail: z6.unknown() }),
1235
- z6.object({
1236
- type: z6.literal("turn_completed"),
1363
+ z7.object({ type: z7.literal("approval_requested"), kind: z7.string(), detail: z7.unknown() }),
1364
+ z7.object({
1365
+ type: z7.literal("turn_completed"),
1237
1366
  usage: UsageSchema,
1238
- costUsd: z6.number().nonnegative(),
1367
+ costUsd: z7.number().nonnegative(),
1239
1368
  status: TurnStatusSchema.nullable(),
1240
1369
  exitReason: TurnExitReasonSchema
1241
1370
  }),
1242
- z6.object({ type: z6.literal("error"), message: z6.string() })
1371
+ z7.object({ type: z7.literal("error"), message: z7.string() })
1243
1372
  ]);
1244
- var TranscriptEntrySchema = z6.object({
1373
+ var TranscriptEntrySchema = z7.object({
1245
1374
  ts: IsoDateTimeSchema,
1246
1375
  event: AgentEventSchema
1247
1376
  });
1248
- var LiveTurnEventSchema = z6.object({
1249
- seq: z6.number().int().positive(),
1377
+ var LiveTurnEventSchema = z7.object({
1378
+ seq: z7.number().int().positive(),
1250
1379
  ts: IsoDateTimeSchema,
1251
1380
  agent: NameSchema,
1252
1381
  project: NameSchema,
@@ -1258,10 +1387,10 @@ var LiveTurnEventSchema = z6.object({
1258
1387
  });
1259
1388
 
1260
1389
  // packages/shared/dist/turns.js
1261
- import { z as z8 } from "zod";
1390
+ import { z as z9 } from "zod";
1262
1391
 
1263
1392
  // packages/shared/dist/runner.js
1264
- import { z as z7 } from "zod";
1393
+ import { z as z8 } from "zod";
1265
1394
  var RUNNER_PROTOCOL = 1;
1266
1395
  var PATH_TOKENS = {
1267
1396
  home: "{{stellaris:home}}",
@@ -1272,67 +1401,75 @@ var SERVER_TOKEN = "{{stellaris:server}}";
1272
1401
  var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
1273
1402
  var HOME_SCRATCH = "scratch";
1274
1403
  var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1275
- var RunnerHelloSchema = z7.object({
1276
- protocol: z7.number().int().positive(),
1277
- version: z7.string().min(1),
1404
+ var RunnerHelloSchema = z8.object({
1405
+ protocol: z8.number().int().positive(),
1406
+ version: z8.string().min(1),
1278
1407
  os: RunnerOsSchema,
1279
- clis: z7.array(CliKindSchema),
1408
+ clis: z8.array(CliKindSchema),
1280
1409
  /** The CLIs whose adapter can keep a session warm between turns. */
1281
- residentClis: z7.array(CliKindSchema).default([]),
1410
+ residentClis: z8.array(CliKindSchema).default([]),
1282
1411
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
1283
- steerableClis: z7.array(CliKindSchema).default([]),
1412
+ steerableClis: z8.array(CliKindSchema).default([]),
1284
1413
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
1285
- stoppableClis: z7.array(CliKindSchema).default([]),
1414
+ stoppableClis: z8.array(CliKindSchema).default([]),
1286
1415
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
1287
- branchReads: z7.boolean().default(false),
1416
+ branchReads: z8.boolean().default(false),
1288
1417
  /** Whether the runner lists what a branch changed; one from before never does. */
1289
- branchChanges: z7.boolean().default(false),
1290
- capabilities: z7.array(z7.string()).default([]),
1418
+ branchChanges: z8.boolean().default(false),
1419
+ /** Whether the runner answers a read of a CLI's quota; one from before never does. */
1420
+ quotaReads: z8.boolean().default(false),
1421
+ capabilities: z8.array(z8.string()).default([]),
1422
+ /**
1423
+ * What the runner's account lacks of the git identity citizens' own commits carry, checked
1424
+ * before each hello outside any repository; empty when it has both. Absent from a runner from
1425
+ * before the check, which no default fills in, so the board marks only what a runner reported.
1426
+ */
1427
+ gitIdentityMissing: z8.array(GitIdentityPartSchema).optional(),
1291
1428
  /** Turns the runner runs at once, or null for no limit. */
1292
- slots: z7.number().int().positive().nullable(),
1429
+ slots: z8.number().int().positive().nullable(),
1293
1430
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1294
- turns: z7.array(UlidSchema).default([])
1431
+ turns: z8.array(UlidSchema).default([])
1295
1432
  });
1296
- var RunnerWelcomeSchema = z7.object({ name: NameSchema, version: z7.string() });
1297
- var RunnerEnrollRequestSchema = z7.object({
1298
- protocol: z7.number().int().positive(),
1299
- version: z7.string().min(1).max(64),
1300
- hostname: z7.string().min(1).max(253),
1433
+ var RunnerWelcomeSchema = z8.object({ name: NameSchema, version: z8.string() });
1434
+ var RunnerEnrollRequestSchema = z8.object({
1435
+ protocol: z8.number().int().positive(),
1436
+ version: z8.string().min(1).max(64),
1437
+ hostname: z8.string().min(1).max(253),
1301
1438
  os: RunnerOsSchema,
1302
- clis: z7.array(CliKindSchema).max(8),
1303
- capabilities: z7.array(z7.string().min(1).max(64)).max(32).default([])
1439
+ clis: z8.array(CliKindSchema).max(8),
1440
+ capabilities: z8.array(z8.string().min(1).max(64)).max(32).default([])
1304
1441
  });
1305
- var RunnerEnrollmentSchema = z7.object({
1306
- deviceCode: z7.string().min(1),
1307
- userCode: z7.string().min(1),
1442
+ var RunnerEnrollmentSchema = z8.object({
1443
+ deviceCode: z8.string().min(1),
1444
+ userCode: z8.string().min(1),
1308
1445
  expiresAt: IsoDateTimeSchema,
1309
- intervalMs: z7.number().int().positive()
1446
+ intervalMs: z8.number().int().positive()
1310
1447
  });
1311
- var RunnerEnrollPollSchema = z7.object({ deviceCode: z7.string().min(1) });
1312
- var RunnerEnrollStatusSchema = z7.discriminatedUnion("status", [
1313
- z7.object({ status: z7.literal("pending") }),
1314
- z7.object({ status: z7.literal("approved"), name: NameSchema, token: z7.string().min(1) }),
1315
- z7.object({ status: z7.literal("denied") })
1448
+ var RunnerEnrollPollSchema = z8.object({ deviceCode: z8.string().min(1) });
1449
+ var RunnerEnrollStatusSchema = z8.discriminatedUnion("status", [
1450
+ z8.object({ status: z8.literal("pending") }),
1451
+ z8.object({ status: z8.literal("approved"), name: NameSchema, token: z8.string().min(1) }),
1452
+ z8.object({ status: z8.literal("denied") })
1316
1453
  ]);
1317
- var PendingEnrollmentSchema = z7.object({
1318
- userCode: z7.string().min(1),
1319
- hostname: z7.string(),
1454
+ var PendingEnrollmentSchema = z8.object({
1455
+ userCode: z8.string().min(1),
1456
+ hostname: z8.string(),
1320
1457
  os: RunnerOsSchema,
1321
- clis: z7.array(CliKindSchema),
1322
- capabilities: z7.array(z7.string()),
1323
- version: z7.string(),
1458
+ clis: z8.array(CliKindSchema),
1459
+ capabilities: z8.array(z8.string()),
1460
+ version: z8.string(),
1324
1461
  requestedAt: IsoDateTimeSchema,
1325
1462
  expiresAt: IsoDateTimeSchema
1326
1463
  });
1327
- var ProjectRepoSchema = z7.object({
1464
+ var ProjectRepoSchema = z8.object({
1328
1465
  slug: NameSchema,
1329
- origin: z7.string().nullable(),
1330
- defaultBranch: z7.string().min(1),
1466
+ origin: z8.string().nullable(),
1467
+ defaultBranch: z8.string().min(1),
1331
1468
  /**
1332
1469
  * The board lands finished tasks on the default branch, itself or through their pull requests,
1333
1470
  * so the runner lets nothing else move it.
1334
1471
  */
1335
- boardLands: z7.boolean()
1472
+ boardLands: z8.boolean()
1336
1473
  });
1337
1474
  function projectRepo(project) {
1338
1475
  return {
@@ -1342,29 +1479,29 @@ function projectRepo(project) {
1342
1479
  boardLands: project.onDone !== "none"
1343
1480
  };
1344
1481
  }
1345
- var TurnWorkspaceSchema = z7.discriminatedUnion("kind", [
1346
- z7.object({
1347
- kind: z7.literal("home"),
1482
+ var TurnWorkspaceSchema = z8.discriminatedUnion("kind", [
1483
+ z8.object({
1484
+ kind: z8.literal("home"),
1348
1485
  thread: UlidSchema.optional(),
1349
1486
  channel: NameSchema.optional()
1350
1487
  }),
1351
- z7.object({
1352
- kind: z7.literal("project"),
1488
+ z8.object({
1489
+ kind: z8.literal("project"),
1353
1490
  repo: ProjectRepoSchema,
1354
- branches: z7.array(z7.string().min(1)),
1491
+ branches: z8.array(z8.string().min(1)),
1355
1492
  thread: UlidSchema.optional(),
1356
1493
  channel: NameSchema.optional()
1357
1494
  }),
1358
- z7.object({ kind: z7.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1495
+ z8.object({ kind: z8.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1359
1496
  ]);
1360
- var TurnJobSchema = z7.object({
1497
+ var TurnJobSchema = z8.object({
1361
1498
  turnId: UlidSchema,
1362
1499
  agent: NameSchema,
1363
1500
  role: NameSchema,
1364
1501
  cli: CliKindSchema,
1365
- model: z7.string().optional(),
1502
+ model: z8.string().optional(),
1366
1503
  /** The reasoning effort set on the agent; absent for the model's own default. */
1367
- effort: z7.string().optional(),
1504
+ effort: z8.string().optional(),
1368
1505
  /** A project slug, or the society scope. */
1369
1506
  scope: NameSchema,
1370
1507
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1375,125 +1512,130 @@ var TurnJobSchema = z7.object({
1375
1512
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1376
1513
  * the outcome reports it.
1377
1514
  */
1378
- session: z7.string().min(1).nullable(),
1515
+ session: z8.string().min(1).nullable(),
1379
1516
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1380
- costSoFarUsd: z7.number().nonnegative(),
1517
+ costSoFarUsd: z8.number().nonnegative(),
1381
1518
  /** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
1382
- instructions: z7.string(),
1383
- prompt: z7.string(),
1384
- mcp: z7.object({ url: z7.string().min(1), token: z7.string().min(1) }),
1385
- limits: z7.object({
1386
- timeoutMs: z7.number().int().positive().nullable(),
1387
- maxTurns: z7.number().int().positive().nullable()
1519
+ instructions: z8.string(),
1520
+ prompt: z8.string(),
1521
+ mcp: z8.object({ url: z8.string().min(1), token: z8.string().min(1) }),
1522
+ limits: z8.object({
1523
+ timeoutMs: z8.number().int().positive().nullable(),
1524
+ maxTurns: z8.number().int().positive().nullable()
1388
1525
  }),
1389
1526
  /** Present when the runner keeps the conversation's session warm between turns, under `key`. */
1390
- resident: z7.object({ key: z7.string().min(1), idleMs: z7.number().int().positive() }).optional(),
1527
+ resident: z8.object({ key: z8.string().min(1), idleMs: z8.number().int().positive() }).optional(),
1391
1528
  workspace: TurnWorkspaceSchema
1392
1529
  });
1393
- var TurnWorkSchema = z7.object({ branch: z7.string().min(1), head: z7.string().min(1) });
1394
- var TurnEventsSchema = z7.object({ entries: z7.array(TranscriptEntrySchema) });
1395
- var TurnOutcomeSchema = z7.object({
1530
+ var TurnWorkSchema = z8.object({ branch: z8.string().min(1), head: z8.string().min(1) });
1531
+ var TurnEventsSchema = z8.object({ entries: z8.array(TranscriptEntrySchema) });
1532
+ var TurnOutcomeSchema = z8.object({
1396
1533
  exitReason: TurnExitReasonSchema,
1397
1534
  status: TurnStatusSchema.nullable(),
1398
- error: z7.string().nullable(),
1535
+ error: z8.string().nullable(),
1399
1536
  usage: UsageSchema,
1400
- costUsd: z7.number().nonnegative(),
1537
+ costUsd: z8.number().nonnegative(),
1401
1538
  /** The session's running total after this turn, for CLIs that report one. */
1402
- sessionCostUsd: z7.number().nonnegative().optional(),
1539
+ sessionCostUsd: z8.number().nonnegative().optional(),
1403
1540
  /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1404
- session: z7.string().min(1),
1541
+ session: z8.string().min(1),
1405
1542
  /** The model the CLI reported, when it reported one. */
1406
- model: z7.string().nullable(),
1543
+ model: z8.string().nullable(),
1407
1544
  work: TurnWorkSchema.nullable(),
1408
1545
  /**
1409
1546
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1410
1547
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1411
1548
  */
1412
- leftovers: z7.boolean().default(false)
1549
+ leftovers: z8.boolean().default(false),
1550
+ /**
1551
+ * The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
1552
+ * before never sends it.
1553
+ */
1554
+ quota: CliQuotaSchema.optional()
1413
1555
  });
1414
- var TurnAckSchema = z7.object({ dropWorkspace: z7.boolean() });
1415
- var LandRequestSchema = z7.object({
1556
+ var TurnAckSchema = z8.object({ dropWorkspace: z8.boolean() });
1557
+ var LandRequestSchema = z8.object({
1416
1558
  repo: ProjectRepoSchema,
1417
- branch: z7.string().min(1),
1559
+ branch: z8.string().min(1),
1418
1560
  /**
1419
1561
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1420
1562
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1421
1563
  * empty body when left out.
1422
1564
  */
1423
- pullRequest: z7.object({
1424
- url: z7.string().min(1),
1425
- subject: z7.string().min(1).optional(),
1426
- body: z7.string().optional()
1565
+ pullRequest: z8.object({
1566
+ url: z8.string().min(1),
1567
+ subject: z8.string().min(1).optional(),
1568
+ body: z8.string().optional()
1427
1569
  }).optional()
1428
1570
  });
1429
- var CloseRequestSchema = z7.object({
1571
+ var CloseRequestSchema = z8.object({
1430
1572
  repo: ProjectRepoSchema,
1431
- url: z7.string().min(1),
1432
- comment: z7.string().min(1)
1573
+ url: z8.string().min(1),
1574
+ comment: z8.string().min(1)
1433
1575
  });
1434
- var MergeOutcomeSchema = z7.object({ ok: z7.boolean(), detail: z7.string() });
1435
- var ModelListSchema = z7.array(ModelOptionSchema);
1576
+ var MergeOutcomeSchema = z8.object({ ok: z8.boolean(), detail: z8.string() });
1577
+ var ModelListSchema = z8.array(ModelOptionSchema);
1436
1578
  var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1437
- 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" });
1438
- var BranchNameSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1439
- var BranchReadSchema = z7.object({
1579
+ 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" });
1580
+ var BranchNameSchema = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1581
+ var BranchReadSchema = z8.object({
1440
1582
  repo: ProjectRepoSchema,
1441
1583
  branch: BranchNameSchema,
1442
1584
  path: BranchPathSchema
1443
1585
  });
1444
- var BranchCommitSchema = z7.object({
1445
- id: z7.string().min(1),
1586
+ var BranchCommitSchema = z8.object({
1587
+ id: z8.string().min(1),
1446
1588
  at: IsoDateTimeSchema,
1447
- author: z7.string()
1589
+ author: z8.string()
1448
1590
  });
1449
- var BranchFileSchema = z7.discriminatedUnion("kind", [
1450
- z7.object({
1451
- kind: z7.literal("file"),
1591
+ var BranchFileSchema = z8.discriminatedUnion("kind", [
1592
+ z8.object({
1593
+ kind: z8.literal("file"),
1452
1594
  path: BranchPathSchema,
1453
1595
  commit: BranchCommitSchema,
1454
- size: z7.number().int().nonnegative(),
1455
- content: z7.string().nullable()
1596
+ size: z8.number().int().nonnegative(),
1597
+ content: z8.string().nullable()
1456
1598
  }),
1457
- z7.object({
1458
- kind: z7.literal("dir"),
1599
+ z8.object({
1600
+ kind: z8.literal("dir"),
1459
1601
  path: BranchPathSchema,
1460
1602
  commit: BranchCommitSchema,
1461
- entries: z7.array(z7.object({
1462
- name: z7.string().min(1),
1463
- kind: z7.enum(["file", "dir"]),
1464
- size: z7.number().int().nonnegative().nullable()
1603
+ entries: z8.array(z8.object({
1604
+ name: z8.string().min(1),
1605
+ kind: z8.enum(["file", "dir"]),
1606
+ size: z8.number().int().nonnegative().nullable()
1465
1607
  }))
1466
1608
  })
1467
1609
  ]);
1468
- var BranchChangesReadSchema = z7.object({
1610
+ var BranchChangesReadSchema = z8.object({
1469
1611
  repo: ProjectRepoSchema,
1470
1612
  branch: BranchNameSchema
1471
1613
  });
1472
- var BranchFileChangeSchema = z7.object({
1473
- path: z7.string().min(1),
1474
- status: z7.enum(["added", "modified", "deleted"]),
1614
+ var BranchFileChangeSchema = z8.object({
1615
+ path: z8.string().min(1),
1616
+ status: z8.enum(["added", "modified", "deleted"]),
1475
1617
  /** Lines added and removed, or null for a binary file. */
1476
- added: z7.number().int().nonnegative().nullable(),
1477
- removed: z7.number().int().nonnegative().nullable(),
1618
+ added: z8.number().int().nonnegative().nullable(),
1619
+ removed: z8.number().int().nonnegative().nullable(),
1478
1620
  /** Absent when the file changed only through a merge of another branch. */
1479
- lastChange: z7.object({ author: z7.string(), at: IsoDateTimeSchema }).optional()
1621
+ lastChange: z8.object({ author: z8.string(), at: IsoDateTimeSchema }).optional()
1480
1622
  });
1481
- var BranchChangesSchema = z7.object({
1623
+ var BranchChangesSchema = z8.object({
1482
1624
  head: BranchCommitSchema,
1483
- files: z7.array(BranchFileChangeSchema),
1484
- total: z7.number().int().nonnegative()
1625
+ files: z8.array(BranchFileChangeSchema),
1626
+ total: z8.number().int().nonnegative()
1485
1627
  });
1486
- var BranchDiffReadSchema = z7.object({
1628
+ var BranchDiffReadSchema = z8.object({
1487
1629
  repo: ProjectRepoSchema,
1488
1630
  branch: BranchNameSchema,
1489
1631
  path: BranchPathSchema.min(1)
1490
1632
  });
1491
- var BranchFileDiffSchema = z7.object({
1633
+ var BranchFileDiffSchema = z8.object({
1492
1634
  head: BranchCommitSchema,
1493
1635
  path: BranchPathSchema,
1494
1636
  status: BranchFileChangeSchema.shape.status,
1495
- patch: z7.string().nullable(),
1496
- truncated: z7.boolean()
1637
+ patch: z8.string().nullable(),
1638
+ truncated: z8.boolean()
1497
1639
  });
1498
1640
  function patchHunks(patch, limit2) {
1499
1641
  if (/^Binary files .* differ$/m.test(patch)) {
@@ -1507,9 +1649,9 @@ function patchHunks(patch, limit2) {
1507
1649
  const cut = hunks.lastIndexOf("\n", limit2);
1508
1650
  return { patch: hunks.slice(0, cut === -1 ? limit2 : cut), truncated: true };
1509
1651
  }
1510
- var WorkspaceConversationSchema = z7.discriminatedUnion("kind", [
1511
- z7.object({ kind: z7.literal("thread"), id: UlidSchema }),
1512
- z7.object({ kind: z7.literal("channel"), scope: NameSchema, name: NameSchema })
1652
+ var WorkspaceConversationSchema = z8.discriminatedUnion("kind", [
1653
+ z8.object({ kind: z8.literal("thread"), id: UlidSchema }),
1654
+ z8.object({ kind: z8.literal("channel"), scope: NameSchema, name: NameSchema })
1513
1655
  ]);
1514
1656
  function workspaceConversationOf(turn) {
1515
1657
  if (turn.thread !== void 0) {
@@ -1517,60 +1659,65 @@ function workspaceConversationOf(turn) {
1517
1659
  }
1518
1660
  return turn.channel === void 0 ? null : { kind: "channel", scope: turn.scope, name: turn.channel };
1519
1661
  }
1520
- var HeldWorkspaceSchema = z7.object({
1662
+ var HeldWorkspaceSchema = z8.object({
1521
1663
  agent: NameSchema,
1522
1664
  scope: NameSchema,
1523
1665
  conversation: WorkspaceConversationSchema
1524
1666
  });
1525
- var HeldWorkspacesSchema = z7.object({ workspaces: z7.array(HeldWorkspaceSchema) });
1526
- var SweepResultSchema = z7.object({ kept: z7.array(HeldWorkspaceSchema) });
1527
- var TurnSteerSchema = z7.object({ id: z7.uuid(), text: z7.string().min(1) });
1528
- var RunnerMessageSchema = z7.discriminatedUnion("type", [
1529
- z7.object({ type: z7.literal("turn"), job: TurnJobSchema }),
1530
- z7.object({ type: z7.literal("land"), request: z7.string().min(1), land: LandRequestSchema }),
1667
+ var HeldWorkspacesSchema = z8.object({ workspaces: z8.array(HeldWorkspaceSchema) });
1668
+ var SweepResultSchema = z8.object({ kept: z8.array(HeldWorkspaceSchema) });
1669
+ var TurnSteerSchema = z8.object({ id: z8.uuid(), text: z8.string().min(1) });
1670
+ var RunnerMessageSchema = z8.discriminatedUnion("type", [
1671
+ z8.object({ type: z8.literal("turn"), job: TurnJobSchema }),
1672
+ z8.object({ type: z8.literal("land"), request: z8.string().min(1), land: LandRequestSchema }),
1531
1673
  /** Answered with a `MergeOutcome`. */
1532
- z7.object({ type: z7.literal("close"), request: z7.string().min(1), close: CloseRequestSchema }),
1533
- z7.object({ type: z7.literal("models"), request: z7.string().min(1), cli: CliKindSchema }),
1674
+ z8.object({ type: z8.literal("close"), request: z8.string().min(1), close: CloseRequestSchema }),
1675
+ z8.object({ type: z8.literal("models"), request: z8.string().min(1), cli: CliKindSchema }),
1676
+ /**
1677
+ * Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
1678
+ * read one. Sent only to runners whose hello says `quotaReads`.
1679
+ */
1680
+ z8.object({ type: z8.literal("quota"), request: z8.string().min(1), cli: CliKindSchema }),
1534
1681
  /** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
1535
- z7.object({ type: z7.literal("file"), request: z7.string().min(1), read: BranchReadSchema }),
1682
+ z8.object({ type: z8.literal("file"), request: z8.string().min(1), read: BranchReadSchema }),
1536
1683
  /** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
1537
- z7.object({
1538
- type: z7.literal("changes"),
1539
- request: z7.string().min(1),
1684
+ z8.object({
1685
+ type: z8.literal("changes"),
1686
+ request: z8.string().min(1),
1540
1687
  read: BranchChangesReadSchema
1541
1688
  }),
1542
1689
  /** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
1543
- z7.object({ type: z7.literal("diff"), request: z7.string().min(1), read: BranchDiffReadSchema }),
1690
+ z8.object({ type: z8.literal("diff"), request: z8.string().min(1), read: BranchDiffReadSchema }),
1544
1691
  /** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
1545
- z7.object({
1546
- type: z7.literal("steer"),
1547
- request: z7.string().min(1),
1692
+ z8.object({
1693
+ type: z8.literal("steer"),
1694
+ request: z8.string().min(1),
1548
1695
  turnId: UlidSchema,
1549
1696
  steer: TurnSteerSchema
1550
1697
  }),
1551
1698
  /** Answered with whether the turn was there to stop; it ends `stopped`. */
1552
- z7.object({ type: z7.literal("stop"), request: z7.string().min(1), turnId: UlidSchema }),
1699
+ z8.object({ type: z8.literal("stop"), request: z8.string().min(1), turnId: UlidSchema }),
1553
1700
  /**
1554
1701
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1555
1702
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1556
1703
  */
1557
- z7.object({
1558
- type: z7.literal("sweep"),
1559
- request: z7.string().min(1),
1704
+ z8.object({
1705
+ type: z8.literal("sweep"),
1706
+ request: z8.string().min(1),
1560
1707
  conversation: WorkspaceConversationSchema
1561
1708
  })
1562
1709
  ]);
1563
- var RunnerAnswerSchema = z7.discriminatedUnion("ok", [
1564
- z7.object({ ok: z7.literal(true), value: z7.unknown() }),
1565
- z7.object({ ok: z7.literal(false), error: z7.string() })
1710
+ var RunnerAnswerSchema = z8.discriminatedUnion("ok", [
1711
+ z8.object({ ok: z8.literal(true), value: z8.unknown() }),
1712
+ z8.object({ ok: z8.literal(false), error: z8.string() })
1566
1713
  ]);
1567
- var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
1568
- var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1714
+ var WarmSessionsSchema = z8.object({ warm: z8.array(z8.string()) });
1715
+ var FileManifestSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1569
1716
  function fileVersion(sha, executable) {
1570
1717
  return executable ? `${sha}+x` : sha;
1571
1718
  }
1572
- var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
1573
- var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1719
+ var FileReadSchema = z8.object({ paths: z8.array(z8.string()) });
1720
+ var FileContentsSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1574
1721
  function isSafeRelativePath(file) {
1575
1722
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1576
1723
  return false;
@@ -1579,7 +1726,7 @@ function isSafeRelativePath(file) {
1579
1726
  }
1580
1727
 
1581
1728
  // packages/shared/dist/turns.js
1582
- var TriggerKindSchema = z8.enum([
1729
+ var TriggerKindSchema = z9.enum([
1583
1730
  "mention",
1584
1731
  "stage",
1585
1732
  "task_done",
@@ -1593,11 +1740,11 @@ var TriggerKindSchema = z8.enum([
1593
1740
  "closing",
1594
1741
  "cron"
1595
1742
  ]);
1596
- var TriggerSchema = z8.object({
1743
+ var TriggerSchema = z9.object({
1597
1744
  kind: TriggerKindSchema,
1598
1745
  from: NameSchema.optional(),
1599
- fromUser: z8.boolean().default(false),
1600
- reason: z8.string().default(""),
1746
+ fromUser: z9.boolean().default(false),
1747
+ reason: z9.string().default(""),
1601
1748
  taskId: UlidSchema.optional(),
1602
1749
  messageId: UlidSchema.optional(),
1603
1750
  /** The channel the waking message was posted in. */
@@ -1605,17 +1752,17 @@ var TriggerSchema = z8.object({
1605
1752
  /** The cron that came due, for a `cron` wake. */
1606
1753
  cronId: UlidSchema.optional()
1607
1754
  });
1608
- var TurnThreadSchema = z8.object({ id: UlidSchema, task: z8.boolean() });
1609
- var TurnDispatchSchema = z8.object({
1755
+ var TurnThreadSchema = z9.object({ id: UlidSchema, task: z9.boolean() });
1756
+ var TurnDispatchSchema = z9.object({
1610
1757
  agent: NameSchema,
1611
1758
  project: NameSchema,
1612
1759
  thread: TurnThreadSchema.optional(),
1613
1760
  channel: NameSchema.optional(),
1614
1761
  trigger: TriggerSchema,
1615
- priority: z8.number().int().min(0).max(2),
1616
- onboarding: z8.boolean().default(false),
1762
+ priority: z9.number().int().min(0).max(2),
1763
+ onboarding: z9.boolean().default(false),
1617
1764
  /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1618
- crons: z8.array(UlidSchema).optional()
1765
+ crons: z9.array(UlidSchema).optional()
1619
1766
  });
1620
1767
  function conversationPart(conversation) {
1621
1768
  return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
@@ -1639,7 +1786,7 @@ function channelConversation(scope, channel) {
1639
1786
  const name = at < 0 ? channel : channel.slice(at + 1);
1640
1787
  return place === scope && name !== "general" ? name : void 0;
1641
1788
  }
1642
- var TurnRecordSchema = z8.object({
1789
+ var TurnRecordSchema = z9.object({
1643
1790
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1644
1791
  id: UlidSchema.optional(),
1645
1792
  agent: NameSchema,
@@ -1650,31 +1797,31 @@ var TurnRecordSchema = z8.object({
1650
1797
  channel: NameSchema.optional(),
1651
1798
  runner: NameSchema,
1652
1799
  cli: CliKindSchema.nullable(),
1653
- session: z8.string().nullable(),
1800
+ session: z9.string().nullable(),
1654
1801
  trigger: TriggerSchema,
1655
1802
  startedAt: IsoDateTimeSchema,
1656
1803
  endedAt: IsoDateTimeSchema.nullable(),
1657
1804
  exitReason: TurnExitReasonSchema.nullable(),
1658
1805
  status: TurnStatusSchema.nullable(),
1659
- error: z8.string().nullable(),
1806
+ error: z9.string().nullable(),
1660
1807
  usage: UsageSchema.nullable(),
1661
- costUsd: z8.number().nonnegative().default(0),
1808
+ costUsd: z9.number().nonnegative().default(0),
1662
1809
  /**
1663
1810
  * The running total the CLI reported for the session after this turn, when it reports one. A
1664
1811
  * resumed session starts from it, so the next turn's own cost is measured from here.
1665
1812
  */
1666
- sessionCostUsd: z8.number().nonnegative().optional(),
1667
- toolCalls: z8.number().int().nonnegative().default(0),
1813
+ sessionCostUsd: z9.number().nonnegative().optional(),
1814
+ toolCalls: z9.number().int().nonnegative().default(0),
1668
1815
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1669
- model: z8.string().nullable().default(null),
1816
+ model: z9.string().nullable().default(null),
1670
1817
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1671
1818
  work: TurnWorkSchema.optional(),
1672
1819
  /** Who stopped the turn, for one that ended `stopped`. */
1673
1820
  stoppedBy: NameSchema.optional(),
1674
1821
  /** The crons whose fire started the turn, which record how it ended. */
1675
- crons: z8.array(UlidSchema).optional()
1822
+ crons: z9.array(UlidSchema).optional()
1676
1823
  });
1677
- var RunningTurnSchema = z8.object({
1824
+ var RunningTurnSchema = z9.object({
1678
1825
  turnId: UlidSchema,
1679
1826
  agent: NameSchema,
1680
1827
  scope: NameSchema,
@@ -1682,49 +1829,49 @@ var RunningTurnSchema = z8.object({
1682
1829
  channel: NameSchema.optional(),
1683
1830
  cli: CliKindSchema,
1684
1831
  askedIn: ChannelRefSchema.optional(),
1685
- steerable: z8.boolean(),
1686
- stoppable: z8.boolean()
1832
+ steerable: z9.boolean(),
1833
+ stoppable: z9.boolean()
1687
1834
  });
1688
- var SessionsFileSchema = z8.object({
1689
- claude: z8.string().optional(),
1690
- codex: z8.string().optional(),
1835
+ var SessionsFileSchema = z9.object({
1836
+ claude: z9.string().optional(),
1837
+ codex: z9.string().optional(),
1691
1838
  runner: NameSchema.optional()
1692
1839
  });
1693
- var WakeRequestSchema = z8.object({
1840
+ var WakeRequestSchema = z9.object({
1694
1841
  agent: NameSchema,
1695
1842
  project: NameSchema,
1696
- reason: z8.string().default("manual wake"),
1843
+ reason: z9.string().default("manual wake"),
1697
1844
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1698
- kind: z8.enum(["manual", "reflection"]).default("manual")
1845
+ kind: z9.enum(["manual", "reflection"]).default("manual")
1699
1846
  });
1700
- var TurnHistoryEntrySchema = z8.object({
1847
+ var TurnHistoryEntrySchema = z9.object({
1701
1848
  /** The id of the event that ended the turn. */
1702
1849
  id: UlidSchema,
1703
1850
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1704
1851
  turnId: UlidSchema.optional(),
1705
1852
  ts: IsoDateTimeSchema,
1706
- outcome: z8.enum(["completed", "failed"]),
1853
+ outcome: z9.enum(["completed", "failed"]),
1707
1854
  /** A project slug, or the society scope. */
1708
1855
  project: NameSchema,
1709
1856
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1710
1857
  thread: UlidSchema.optional(),
1711
1858
  /** The channel, never general, whose conversation the turn was in. */
1712
1859
  channel: NameSchema.optional(),
1713
- trigger: z8.string(),
1714
- exitReason: z8.string().nullable(),
1715
- costUsd: z8.number(),
1860
+ trigger: z9.string(),
1861
+ exitReason: z9.string().nullable(),
1862
+ costUsd: z9.number(),
1716
1863
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1717
1864
  usage: UsageSchema.optional(),
1718
- model: z8.string().nullable(),
1719
- summary: z8.string().nullable(),
1720
- error: z8.string().nullable(),
1865
+ model: z9.string().nullable(),
1866
+ summary: z9.string().nullable(),
1867
+ error: z9.string().nullable(),
1721
1868
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1722
1869
  startedAt: IsoDateTimeSchema.optional(),
1723
- toolCalls: z8.number().int().nonnegative().optional()
1870
+ toolCalls: z9.number().int().nonnegative().optional()
1724
1871
  });
1725
1872
 
1726
1873
  // packages/shared/dist/ops.js
1727
- import { z as z9 } from "zod";
1874
+ import { z as z10 } from "zod";
1728
1875
  var OPS_SIGNAL_KINDS = [
1729
1876
  "waiting_stage",
1730
1877
  "backlog",
@@ -1740,7 +1887,7 @@ var OPS_SIGNAL_KINDS = [
1740
1887
  "stuck_workspace",
1741
1888
  "cron_failing"
1742
1889
  ];
1743
- var OpsSignalKindSchema = z9.enum(OPS_SIGNAL_KINDS);
1890
+ var OpsSignalKindSchema = z10.enum(OPS_SIGNAL_KINDS);
1744
1891
  var WAKING_SIGNAL_KINDS = [
1745
1892
  "backlog",
1746
1893
  "role_gap",
@@ -1750,15 +1897,15 @@ var WAKING_SIGNAL_KINDS = [
1750
1897
  "blocked_capability",
1751
1898
  "scaled"
1752
1899
  ];
1753
- var OpsSignalSchema = z9.object({
1900
+ var OpsSignalSchema = z10.object({
1754
1901
  kind: OpsSignalKindSchema,
1755
1902
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1756
- key: z9.string().min(1),
1903
+ key: z10.string().min(1),
1757
1904
  /** One line a reader can act on. */
1758
- summary: z9.string().min(1),
1905
+ summary: z10.string().min(1),
1759
1906
  /** The counter or timer value behind the signal. */
1760
- value: z9.number(),
1761
- threshold: z9.number().optional(),
1907
+ value: z10.number(),
1908
+ threshold: z10.number().optional(),
1762
1909
  project: NameSchema.optional(),
1763
1910
  agent: NameSchema.optional(),
1764
1911
  role: NameSchema.optional(),
@@ -1766,8 +1913,8 @@ var OpsSignalSchema = z9.object({
1766
1913
  });
1767
1914
 
1768
1915
  // packages/shared/dist/metrics.js
1769
- import { z as z10 } from "zod";
1770
- var MetricsWindowSchema = z10.enum(["24h", "7d", "all"]);
1916
+ import { z as z11 } from "zod";
1917
+ var MetricsWindowSchema = z11.enum(["24h", "7d", "all"]);
1771
1918
  var METRICS_WINDOW_MS = {
1772
1919
  "24h": 24 * 36e5,
1773
1920
  "7d": 7 * 24 * 36e5,
@@ -1778,9 +1925,9 @@ function isBoardAction(tool) {
1778
1925
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1779
1926
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1780
1927
  }
1781
- var CountSchema = z10.number().int().nonnegative();
1782
- var MsSchema = z10.number().nonnegative();
1783
- var MetricsSchema = z10.object({
1928
+ var CountSchema = z11.number().int().nonnegative();
1929
+ var MsSchema = z11.number().nonnegative();
1930
+ var MetricsSchema = z11.object({
1784
1931
  window: MetricsWindowSchema,
1785
1932
  since: IsoDateTimeSchema.nullable(),
1786
1933
  until: IsoDateTimeSchema,
@@ -1788,12 +1935,12 @@ var MetricsSchema = z10.object({
1788
1935
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1789
1936
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1790
1937
  */
1791
- idle: z10.object({
1938
+ idle: z11.object({
1792
1939
  turns: CountSchema,
1793
1940
  idle: CountSchema,
1794
1941
  unknown: CountSchema,
1795
- byTrigger: z10.array(z10.object({ trigger: z10.string(), turns: CountSchema, idle: CountSchema })),
1796
- byAgent: z10.array(z10.object({
1942
+ byTrigger: z11.array(z11.object({ trigger: z11.string(), turns: CountSchema, idle: CountSchema })),
1943
+ byAgent: z11.array(z11.object({
1797
1944
  agent: NameSchema,
1798
1945
  role: NameSchema.nullable(),
1799
1946
  turns: CountSchema,
@@ -1801,33 +1948,33 @@ var MetricsSchema = z10.object({
1801
1948
  }))
1802
1949
  }),
1803
1950
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1804
- sentBack: z10.object({
1951
+ sentBack: z11.object({
1805
1952
  finished: CountSchema,
1806
1953
  sendBacks: CountSchema,
1807
1954
  tasksSentBack: CountSchema,
1808
- byStage: z10.array(z10.object({ project: NameSchema, stage: z10.string(), count: CountSchema })),
1809
- bySender: z10.array(z10.object({ agent: NameSchema, count: CountSchema }))
1955
+ byStage: z11.array(z11.object({ project: NameSchema, stage: z11.string(), count: CountSchema })),
1956
+ bySender: z11.array(z11.object({ agent: NameSchema, count: CountSchema }))
1810
1957
  }),
1811
1958
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1812
- messages: z10.object({
1959
+ messages: z11.object({
1813
1960
  finished: CountSchema,
1814
1961
  messages: CountSchema,
1815
- byProject: z10.array(z10.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1816
- busiest: z10.array(z10.object({
1962
+ byProject: z11.array(z11.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1963
+ busiest: z11.array(z11.object({
1817
1964
  taskId: UlidSchema,
1818
- title: z10.string(),
1965
+ title: z11.string(),
1819
1966
  project: NameSchema,
1820
1967
  messages: CountSchema
1821
1968
  }))
1822
1969
  }),
1823
1970
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1824
- latency: z10.object({
1971
+ latency: z11.object({
1825
1972
  mentions: CountSchema,
1826
1973
  /** Mentions no turn has followed yet. */
1827
1974
  unanswered: CountSchema,
1828
1975
  medianMs: MsSchema.nullable(),
1829
1976
  slowestMs: MsSchema.nullable(),
1830
- byAgent: z10.array(z10.object({
1977
+ byAgent: z11.array(z11.object({
1831
1978
  agent: NameSchema,
1832
1979
  mentions: CountSchema,
1833
1980
  medianMs: MsSchema,
@@ -1835,61 +1982,73 @@ var MetricsSchema = z10.object({
1835
1982
  }))
1836
1983
  }),
1837
1984
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1838
- decisions: z10.object({
1985
+ decisions: z11.object({
1839
1986
  total: CountSchema,
1840
- byDay: z10.array(z10.object({
1841
- day: z10.string(),
1987
+ byDay: z11.array(z11.object({
1988
+ day: z11.string(),
1842
1989
  proposals: CountSchema,
1843
1990
  answers: CountSchema,
1844
1991
  stages: CountSchema
1845
1992
  }))
1846
1993
  }),
1847
1994
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1848
- blocked: z10.array(z10.object({
1995
+ blocked: z11.array(z11.object({
1849
1996
  taskId: UlidSchema,
1850
- title: z10.string().nullable(),
1997
+ title: z11.string().nullable(),
1851
1998
  project: NameSchema.nullable(),
1852
- summary: z10.string(),
1999
+ summary: z11.string(),
1853
2000
  firstAt: IsoDateTimeSchema,
1854
2001
  lastAt: IsoDateTimeSchema,
1855
- holds: z10.boolean()
2002
+ holds: z11.boolean()
1856
2003
  }))
1857
2004
  });
2005
+ var CitizenTokensSchema = z11.object({
2006
+ agent: NameSchema,
2007
+ turns: CountSchema,
2008
+ withUsage: CountSchema,
2009
+ usage: UsageSchema.nullable()
2010
+ });
2011
+ var TokenUseSchema = z11.object({
2012
+ window: MetricsWindowSchema,
2013
+ since: IsoDateTimeSchema.nullable(),
2014
+ until: IsoDateTimeSchema,
2015
+ citizens: z11.array(CitizenTokensSchema)
2016
+ });
1858
2017
 
1859
2018
  // packages/shared/dist/config.js
1860
- import { z as z11 } from "zod";
1861
- var LogLevelSchema = z11.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1862
- var GithubSignInSchema = z11.object({
1863
- clientId: z11.string().min(1),
1864
- clientSecret: z11.string().min(1),
2019
+ import { z as z12 } from "zod";
2020
+ var LogLevelSchema = z12.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
2021
+ var GithubSignInSchema = z12.object({
2022
+ clientId: z12.string().min(1),
2023
+ clientSecret: z12.string().min(1),
1865
2024
  /** Logins, lowercased; GitHub logins are case-insensitive. */
1866
- users: z11.array(z11.string().min(1)).min(1)
2025
+ users: z12.array(z12.string().min(1)).min(1)
1867
2026
  });
1868
- var ServerConfigSchema = z11.object({
1869
- dataDir: z11.string().min(1),
1870
- host: z11.string().min(1),
1871
- port: z11.number().int().positive().max(65535),
2027
+ var ServerConfigSchema = z12.object({
2028
+ dataDir: z12.string().min(1),
2029
+ host: z12.string().min(1),
2030
+ port: z12.number().int().positive().max(65535),
1872
2031
  /**
1873
2032
  * An address every runner's agents reach this server at, for example https://board.example, or
1874
2033
  * null for each runner to use the address it reaches the server at itself.
1875
2034
  */
1876
- publicUrl: z11.string().min(1).nullable(),
2035
+ publicUrl: z12.string().min(1).nullable(),
1877
2036
  logLevel: LogLevelSchema,
1878
2037
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
1879
- concurrency: z11.number().int().positive().nullable(),
2038
+ concurrency: z12.number().int().positive().nullable(),
1880
2039
  /** How long one turn may run before it is stopped, or null for no limit. */
1881
- turnTimeoutMs: z11.number().int().positive().nullable(),
2040
+ turnTimeoutMs: z12.number().int().positive().nullable(),
1882
2041
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1883
- toolRounds: z11.number().int().positive().nullable(),
2042
+ toolRounds: z12.number().int().positive().nullable(),
1884
2043
  /** How long a resident session stays warm after its last turn. */
1885
- residentIdleMs: z11.number().int().positive(),
2044
+ residentIdleMs: z12.number().int().positive(),
1886
2045
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1887
2046
  github: GithubSignInSchema.nullable(),
1888
2047
  /**
1889
2048
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1890
2049
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1891
2050
  */
1892
- trustedProxies: z11.number().int().min(0).max(16)
2051
+ trustedProxies: z12.number().int().min(0).max(16)
1893
2052
  });
1894
2053
  function limit(value, fallback) {
1895
2054
  if (value === void 0 || value.trim() === "") {
@@ -1927,25 +2086,73 @@ function loadServerConfig(env) {
1927
2086
  trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
1928
2087
  });
1929
2088
  }
1930
- var RunnerConfigSchema = z11.object({
2089
+ var RunnerConfigSchema = z12.object({
1931
2090
  /** The board server, for example http://127.0.0.1:4700. */
1932
- serverUrl: z11.string().url(),
2091
+ serverUrl: z12.string().url(),
1933
2092
  /**
1934
2093
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1935
2094
  * to enroll it.
1936
2095
  */
1937
- token: z11.string().min(1).nullable(),
2096
+ token: z12.string().min(1).nullable(),
1938
2097
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1939
- dataDir: z11.string().min(1),
2098
+ dataDir: z12.string().min(1),
1940
2099
  logLevel: LogLevelSchema,
1941
2100
  /** Turns this machine runs at once, or null for no limit. */
1942
- slots: z11.number().int().positive().nullable(),
1943
- clis: z11.array(CliKindSchema).min(1),
1944
- capabilities: z11.array(z11.string())
2101
+ slots: z12.number().int().positive().nullable(),
2102
+ clis: z12.array(CliKindSchema).min(1),
2103
+ capabilities: z12.array(z12.string())
1945
2104
  });
1946
2105
 
1947
2106
  // packages/shared/dist/version.js
1948
- var STELLARIS_VERSION = "0.3.21";
2107
+ var STELLARIS_VERSION = "0.3.22";
2108
+
2109
+ // packages/shared/dist/mentions.js
2110
+ import { parse, postprocess, preprocess } from "micromark";
2111
+ import { gfm } from "micromark-extension-gfm";
2112
+ var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
2113
+ var CODE = /* @__PURE__ */ new Set(["codeText", "codeFenced", "codeIndented"]);
2114
+ var SLOW = {
2115
+ disable: {
2116
+ null: [
2117
+ "wwwAutolink",
2118
+ "protocolAutolink",
2119
+ "emailAutolink",
2120
+ "gfmFootnoteCall",
2121
+ "gfmPotentialFootnoteCall"
2122
+ ]
2123
+ }
2124
+ };
2125
+ function codeRanges(markdown) {
2126
+ const shift = markdown.startsWith("\uFEFF") ? 1 : 0;
2127
+ const events = postprocess(parse({ extensions: [gfm(), SLOW] }).document().write(preprocess()(markdown, void 0, true)));
2128
+ return events.flatMap(([kind, token]) => kind === "enter" && CODE.has(token.type) ? [[token.start.offset + shift, token.end.offset + shift]] : []);
2129
+ }
2130
+ function addMentions(found, text) {
2131
+ for (const match of text.matchAll(MENTION_PATTERN)) {
2132
+ const name = match[2];
2133
+ if (name !== void 0) {
2134
+ found.add(name);
2135
+ }
2136
+ }
2137
+ }
2138
+ function mentionsIn(markdown) {
2139
+ if (markdown.search(MENTION_PATTERN) === -1) {
2140
+ return [];
2141
+ }
2142
+ const found = /* @__PURE__ */ new Set();
2143
+ let from = 0;
2144
+ for (const [start, end] of codeRanges(markdown)) {
2145
+ addMentions(found, markdown.slice(from, start));
2146
+ from = end;
2147
+ }
2148
+ addMentions(found, markdown.slice(from));
2149
+ return [...found].toSorted();
2150
+ }
2151
+ function mentionsAnywhere(text) {
2152
+ const found = /* @__PURE__ */ new Set();
2153
+ addMentions(found, text);
2154
+ return [...found].toSorted();
2155
+ }
1949
2156
 
1950
2157
  // packages/board-core/dist/errors.js
1951
2158
  var BoardError = class extends Error {
@@ -2591,33 +2798,59 @@ function filesOf(lines, status) {
2591
2798
  return [...files.values()];
2592
2799
  }
2593
2800
 
2801
+ // packages/board-core/dist/mentions.js
2802
+ import vm from "node:vm";
2803
+ var MENTION_PARSE_MS = 500;
2804
+ var context = vm.createContext({});
2805
+ var call = new vm.Script("work()");
2806
+ function within(work, ms) {
2807
+ let result;
2808
+ context["work"] = () => {
2809
+ result = work();
2810
+ };
2811
+ try {
2812
+ call.runInContext(context, { timeout: ms });
2813
+ } catch (error) {
2814
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
2815
+ return void 0;
2816
+ }
2817
+ throw error;
2818
+ } finally {
2819
+ context["work"] = void 0;
2820
+ }
2821
+ return result;
2822
+ }
2823
+ function postMentions(body) {
2824
+ return within(() => mentionsIn(body), MENTION_PARSE_MS) ?? mentionsAnywhere(body);
2825
+ }
2826
+
2594
2827
  // packages/board-core/dist/metrics.js
2595
- import { z as z12 } from "zod";
2596
- var TurnPayload = z12.object({
2597
- turnId: z12.string().optional(),
2598
- project: z12.string(),
2599
- thread: z12.string().optional(),
2600
- channel: z12.string().optional(),
2601
- trigger: z12.string()
2828
+ import { z as z13 } from "zod";
2829
+ var TurnPayload = z13.object({
2830
+ turnId: z13.string().optional(),
2831
+ project: z13.string(),
2832
+ thread: z13.string().optional(),
2833
+ channel: z13.string().optional(),
2834
+ trigger: z13.string()
2602
2835
  });
2603
- var TaskPayload = z12.object({
2604
- taskId: z12.string(),
2605
- project: z12.string(),
2606
- from: z12.string().optional()
2836
+ var TaskPayload = z13.object({
2837
+ taskId: z13.string(),
2838
+ project: z13.string(),
2839
+ from: z13.string().optional()
2607
2840
  });
2608
- var MessagePayload = z12.object({
2609
- id: z12.string().optional(),
2610
- channel: z12.string(),
2611
- thread: z12.string().nullable().optional(),
2612
- mentions: z12.array(z12.string()).default([])
2841
+ var MessagePayload = z13.object({
2842
+ id: z13.string().optional(),
2843
+ channel: z13.string(),
2844
+ thread: z13.string().nullable().optional(),
2845
+ mentions: z13.array(z13.string()).default([])
2613
2846
  });
2614
- var SteeredPayload = z12.object({ messages: z12.array(z12.string()) });
2615
- var SignalPayload = z12.object({
2616
- kind: z12.string(),
2617
- key: z12.string(),
2618
- summary: z12.string(),
2619
- taskId: z12.string().optional(),
2620
- project: z12.string().optional()
2847
+ var SteeredPayload = z13.object({ messages: z13.array(z13.string()) });
2848
+ var SignalPayload = z13.object({
2849
+ kind: z13.string(),
2850
+ key: z13.string(),
2851
+ summary: z13.string(),
2852
+ taskId: z13.string().optional(),
2853
+ project: z13.string().optional()
2621
2854
  });
2622
2855
  function median(values) {
2623
2856
  const sorted = values.toSorted((a, b) => a - b);
@@ -2906,6 +3139,59 @@ function computeMetrics(input) {
2906
3139
  };
2907
3140
  }
2908
3141
 
3142
+ // packages/board-core/dist/turn-history.js
3143
+ import { z as z14 } from "zod";
3144
+ var TurnHistoryPayloadSchema = z14.object({
3145
+ project: NameSchema,
3146
+ thread: UlidSchema.optional(),
3147
+ channel: NameSchema.optional(),
3148
+ trigger: z14.string().default("manual"),
3149
+ exitReason: z14.string().nullable().default(null),
3150
+ costUsd: z14.number().default(0),
3151
+ usage: UsageSchema.optional(),
3152
+ model: z14.string().nullable().default(null),
3153
+ summary: z14.string().nullable().default(null),
3154
+ error: z14.string().nullable().default(null),
3155
+ toolCalls: z14.number().int().nonnegative().optional(),
3156
+ turnId: UlidSchema.optional()
3157
+ });
3158
+ function tokenUse(events, window, now) {
3159
+ const span = METRICS_WINDOW_MS[window];
3160
+ const since = span === null ? null : now.getTime() - span;
3161
+ const rows = /* @__PURE__ */ new Map();
3162
+ for (const event of events) {
3163
+ if (event.type !== "turn.completed" && event.type !== "turn.failed") {
3164
+ continue;
3165
+ }
3166
+ if (since !== null && Date.parse(event.ts) < since) {
3167
+ continue;
3168
+ }
3169
+ const turn = TurnHistoryPayloadSchema.safeParse(event.payload);
3170
+ if (!turn.success) {
3171
+ continue;
3172
+ }
3173
+ const row = rows.get(event.actor) ?? {
3174
+ agent: event.actor,
3175
+ turns: 0,
3176
+ withUsage: 0,
3177
+ usage: null
3178
+ };
3179
+ const usage = turn.data.usage;
3180
+ rows.set(event.actor, {
3181
+ agent: event.actor,
3182
+ turns: row.turns + 1,
3183
+ withUsage: row.withUsage + (usage === void 0 ? 0 : 1),
3184
+ usage: usage === void 0 ? row.usage : row.usage === null ? usage : addUsage(row.usage, usage)
3185
+ });
3186
+ }
3187
+ return {
3188
+ window,
3189
+ since: since === null ? null : new Date(since).toISOString(),
3190
+ until: now.toISOString(),
3191
+ citizens: [...rows.values()].toSorted((a, b) => a.agent.localeCompare(b.agent))
3192
+ };
3193
+ }
3194
+
2909
3195
  // packages/board-core/dist/mutex.js
2910
3196
  var Mutex = class {
2911
3197
  tail = Promise.resolve();
@@ -3147,41 +3433,27 @@ function namesList(names) {
3147
3433
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
3148
3434
  }
3149
3435
  var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
3150
- var RunnerTokensSchema = z13.object({
3151
- runners: z13.record(z13.string(), z13.object({ tokenHash: z13.string().min(1) }))
3436
+ var RunnerTokensSchema = z15.object({
3437
+ runners: z15.record(z15.string(), z15.object({ tokenHash: z15.string().min(1) }))
3152
3438
  });
3153
- var SignInsSchema = z13.object({
3154
- signIns: z13.array(z13.object({
3155
- tokenHash: z13.string().min(1),
3156
- login: z13.string().min(1),
3439
+ var SignInsSchema = z15.object({
3440
+ signIns: z15.array(z15.object({
3441
+ tokenHash: z15.string().min(1),
3442
+ login: z15.string().min(1),
3157
3443
  /** The account's picture at the identity provider; sign-ins from before it have none. */
3158
- avatarUrl: z13.url().optional(),
3444
+ avatarUrl: z15.url().optional(),
3159
3445
  createdAt: IsoDateTimeSchema,
3160
3446
  expiresAt: IsoDateTimeSchema
3161
3447
  })).default([])
3162
3448
  });
3163
- var TurnHistoryPayloadSchema = z13.object({
3164
- project: NameSchema,
3165
- thread: UlidSchema.optional(),
3166
- channel: NameSchema.optional(),
3167
- trigger: z13.string().default("manual"),
3168
- exitReason: z13.string().nullable().default(null),
3169
- costUsd: z13.number().default(0),
3170
- usage: UsageSchema.optional(),
3171
- model: z13.string().nullable().default(null),
3172
- summary: z13.string().nullable().default(null),
3173
- error: z13.string().nullable().default(null),
3174
- toolCalls: z13.number().int().nonnegative().optional(),
3175
- turnId: UlidSchema.optional()
3176
- });
3177
- var TurnStartPayloadSchema = z13.object({ project: NameSchema, turnId: UlidSchema.optional() });
3178
- var CursorsSchema = z13.object({
3179
- digest: z13.string().nullable(),
3180
- scopes: z13.record(z13.string(), z13.string()).default({}),
3181
- threadsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
3182
- channelsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
3449
+ var TurnStartPayloadSchema = z15.object({ project: NameSchema, turnId: UlidSchema.optional() });
3450
+ var CursorsSchema = z15.object({
3451
+ digest: z15.string().nullable(),
3452
+ scopes: z15.record(z15.string(), z15.string()).default({}),
3453
+ threadsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3454
+ channelsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3183
3455
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
3184
- news: z13.string().optional()
3456
+ news: z15.string().optional()
3185
3457
  });
3186
3458
  var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
3187
3459
  function conversationKey(scope, part) {
@@ -3220,9 +3492,8 @@ function byId(a, b) {
3220
3492
  function turnTime(record) {
3221
3493
  return record.endedAt ?? record.startedAt;
3222
3494
  }
3223
- var PausedSchema = z13.object({ paused: z13.boolean() });
3495
+ var PausedSchema = z15.object({ paused: z15.boolean() });
3224
3496
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
3225
- var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
3226
3497
  function withoutMentions(text) {
3227
3498
  return text.replaceAll(MENTION_PATTERN, "$1$2");
3228
3499
  }
@@ -3246,7 +3517,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
3246
3517
  var FRONT_DESK_TRIGGER = "user_post";
3247
3518
  var OPS_CHANNELS = ["governance"];
3248
3519
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
3249
- var AlignmentsSchema = z13.object({ applied: z13.array(z13.string()).default([]) });
3520
+ var AlignmentsSchema = z15.object({ applied: z15.array(z15.string()).default([]) });
3250
3521
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
3251
3522
  var THREAD_RECORDS = "threads-as-records";
3252
3523
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -3260,10 +3531,10 @@ var CHANNEL_CONVERSATIONS = "channel-conversations";
3260
3531
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3261
3532
  var CRON_VERBS = "cron-verbs-granted";
3262
3533
  var KNOWLEDGE_NOTICES = "knowledge-notices";
3263
- var KnowledgeEventSchema = z13.object({
3534
+ var KnowledgeEventSchema = z15.object({
3264
3535
  topic: NameSchema,
3265
3536
  project: NameSchema.nullable(),
3266
- replaced: z13.boolean().optional()
3537
+ replaced: z15.boolean().optional()
3267
3538
  });
3268
3539
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3269
3540
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
@@ -3381,16 +3652,6 @@ function unanswered(messages) {
3381
3652
  }
3382
3653
  return waiting;
3383
3654
  }
3384
- function extractMentions(body) {
3385
- const found = /* @__PURE__ */ new Set();
3386
- for (const match of body.matchAll(MENTION_PATTERN)) {
3387
- const name = match[2];
3388
- if (name !== void 0) {
3389
- found.add(name);
3390
- }
3391
- }
3392
- return [...found].toSorted();
3393
- }
3394
3655
  function snippetAround(text, query, radius = 80) {
3395
3656
  const index = text.toLowerCase().indexOf(query.toLowerCase());
3396
3657
  if (index === -1) {
@@ -3718,7 +3979,7 @@ var Board = class _Board {
3718
3979
  }
3719
3980
  }
3720
3981
  for (const file of await listFiles(this.paths.tasks(slug))) {
3721
- const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z13.record(z13.string(), z13.unknown()));
3982
+ const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z15.record(z15.string(), z15.unknown()));
3722
3983
  const legacy = raw.data["thread"];
3723
3984
  if (legacy === void 0) {
3724
3985
  continue;
@@ -3796,7 +4057,7 @@ var Board = class _Board {
3796
4057
  * turns, so it follows nothing now.
3797
4058
  */
3798
4059
  async renameInboxCursors() {
3799
- const Legacy = z13.object({ inbox: z13.string().nullable() });
4060
+ const Legacy = z15.object({ inbox: z15.string().nullable() });
3800
4061
  for (const agent of await this.listAgents()) {
3801
4062
  const file = this.paths.agentCursors(agent.name);
3802
4063
  if (await exists(file)) {
@@ -3842,7 +4103,7 @@ ${charter.purpose}
3842
4103
  */
3843
4104
  async alignSeedRole(seed) {
3844
4105
  const file = this.paths.role(seed.name);
3845
- const raw = await readMarkdown(file, z13.record(z13.string(), z13.unknown()));
4106
+ const raw = await readMarkdown(file, z15.record(z15.string(), z15.unknown()));
3846
4107
  const current = RoleCharterSchema.parse(raw.data);
3847
4108
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
3848
4109
  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));
@@ -4159,7 +4420,7 @@ ${charter.purpose}
4159
4420
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
4160
4421
  async readSocietyNorms() {
4161
4422
  const file = this.paths.societyKnowledgeFile("norms");
4162
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
4423
+ return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
4163
4424
  }
4164
4425
  /** Knowledge topics of a project, or of the society when the project is null. */
4165
4426
  async listKnowledge(project) {
@@ -4179,7 +4440,7 @@ ${charter.purpose}
4179
4440
  */
4180
4441
  async knowledgeAt(file, project, topic) {
4181
4442
  const { data, body } = await readLooseMarkdown(file);
4182
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
4443
+ const revision = z15.number().int().min(0).safeParse(data["revision"]);
4183
4444
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
4184
4445
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
4185
4446
  return {
@@ -4512,8 +4773,11 @@ ${DASHBOARD_LINE}
4512
4773
  throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4513
4774
  }
4514
4775
  const doc = await readMarkdown(file, RunnerSchema);
4776
+ const { gitIdentityMissing: reported, ...rest } = doc.data;
4777
+ const gitIdentityMissing = patch.gitIdentityMissing === void 0 ? reported : patch.gitIdentityMissing ?? void 0;
4515
4778
  const next = RunnerSchema.parse({
4516
- ...doc.data,
4779
+ ...rest,
4780
+ ...gitIdentityMissing === void 0 ? {} : { gitIdentityMissing: [...gitIdentityMissing] },
4517
4781
  ...patch.status === void 0 ? {} : { status: patch.status },
4518
4782
  ...patch.os === void 0 ? {} : { os: patch.os },
4519
4783
  ...patch.clis === void 0 ? {} : { clis: [...patch.clis] },
@@ -4541,6 +4805,36 @@ ${DASHBOARD_LINE}
4541
4805
  return next;
4542
4806
  });
4543
4807
  }
4808
+ /**
4809
+ * Records a CLI's plan quota as a runner reported it, from a turn's stream or a read, merged over
4810
+ * what the record held. The record is written on every report, so it says when the numbers were
4811
+ * last seen; `runner.quota` is appended only when what a reader would see changes, since every
4812
+ * turn reports.
4813
+ */
4814
+ async recordQuota(name, cli, quota) {
4815
+ return this.mutex.run(async () => {
4816
+ const file = this.paths.runner(name);
4817
+ if (!await exists(file)) {
4818
+ throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4819
+ }
4820
+ const doc = await readMarkdown(file, RunnerSchema);
4821
+ const previous = doc.data.quotas?.[cli];
4822
+ const merged = mergeQuota(previous, quota);
4823
+ const next = RunnerSchema.parse({
4824
+ ...doc.data,
4825
+ quotas: { ...doc.data.quotas, [cli]: merged }
4826
+ });
4827
+ await writeMarkdown(file, next, doc.body);
4828
+ if (quotaChanged(previous, merged)) {
4829
+ await this.events.append("runner.quota", SYSTEM_ACTOR.name, {
4830
+ name,
4831
+ cli,
4832
+ status: merged.status
4833
+ });
4834
+ }
4835
+ return next;
4836
+ });
4837
+ }
4544
4838
  /**
4545
4839
  * Logs an operations signal as an `ops.signal` event. No channel carries it: the scheduler wakes
4546
4840
  * its readers from the event, the runner lists it in their prompts, and the interface's log shows
@@ -6375,7 +6669,7 @@ ${rationale}`}`);
6375
6669
  }
6376
6670
  async readAgentRoleBody(agent) {
6377
6671
  const file = this.paths.agentRole(agent);
6378
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
6672
+ return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
6379
6673
  }
6380
6674
  /** The steps of one finished turn, as the runner handed them over when it ended. */
6381
6675
  /**
@@ -6406,6 +6700,14 @@ ${rationale}`}`);
6406
6700
  }
6407
6701
  return computeMetrics({ events, tasks, citizens, actions, activeSignals, window, now });
6408
6702
  }
6703
+ /**
6704
+ * Each citizen's finished turns over a window and the tokens of those that counted them, from
6705
+ * the event log alone, which holds every turn's end whatever runner it ran on.
6706
+ */
6707
+ async tokenUse(window) {
6708
+ const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
6709
+ return tokenUse(events, window, this.now());
6710
+ }
6409
6711
  /** The board actions a finished turn took, from its transcript; null when it kept none. */
6410
6712
  async turnActions(agent, turnId) {
6411
6713
  const known = this.actionCounts.get(turnId);
@@ -6775,7 +7077,7 @@ ${rationale}`}`);
6775
7077
  return { project: slug, revision: 0, body: "" };
6776
7078
  }
6777
7079
  const { data, body } = await readLooseMarkdown(file);
6778
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
7080
+ const revision = z15.number().int().min(0).safeParse(data["revision"]);
6779
7081
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
6780
7082
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
6781
7083
  return {
@@ -7066,7 +7368,7 @@ ${seed}
7066
7368
  continue;
7067
7369
  }
7068
7370
  const file = this.paths.agentRole(agent.name);
7069
- const body = await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
7371
+ const body = await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
7070
7372
  const at = body.indexOf(`
7071
7373
  ${SEED_INSTRUCTIONS_HEADING}
7072
7374
  `);
@@ -7904,7 +8206,7 @@ ${charter.purpose}
7904
8206
  id: this.newId(),
7905
8207
  ...head,
7906
8208
  ts: this.now().toISOString(),
7907
- mentions: extractMentions(body)
8209
+ mentions: postMentions(body)
7908
8210
  });
7909
8211
  const { author } = head;
7910
8212
  await writeMarkdown(this.paths.messageFile(dir, frontmatter.id, author), frontmatter, body);
@@ -8282,7 +8584,7 @@ function decideWake(input) {
8282
8584
  }
8283
8585
 
8284
8586
  // packages/scheduler/dist/scheduler.js
8285
- import { z as z14 } from "zod";
8587
+ import { z as z16 } from "zod";
8286
8588
  function besideFields(beside) {
8287
8589
  if (beside === void 0) {
8288
8590
  return {};
@@ -8309,21 +8611,21 @@ var DEFAULT_TIMINGS = Object.freeze({
8309
8611
  scaleCooldownMs: 36e5,
8310
8612
  reflectionMs: 24 * 36e5
8311
8613
  });
8312
- var SchedulerTimingsSchema = z14.object({
8313
- pollMs: z14.number().positive(),
8314
- debounceMs: z14.number().nonnegative(),
8315
- userDebounceMs: z14.number().nonnegative(),
8316
- heartbeatMs: z14.number().positive(),
8317
- waitingStageMs: z14.number().positive(),
8318
- leaseSweepMs: z14.number().positive(),
8319
- opsIntervalMs: z14.number().positive(),
8320
- signalRepeatMs: z14.number().positive(),
8321
- costReportMs: z14.number().positive(),
8322
- staleThreadMs: z14.number().positive(),
8323
- idleMemberMs: z14.number().positive(),
8324
- homeConflictMs: z14.number().positive(),
8325
- scaleCooldownMs: z14.number().positive(),
8326
- reflectionMs: z14.number().positive()
8614
+ var SchedulerTimingsSchema = z16.object({
8615
+ pollMs: z16.number().positive(),
8616
+ debounceMs: z16.number().nonnegative(),
8617
+ userDebounceMs: z16.number().nonnegative(),
8618
+ heartbeatMs: z16.number().positive(),
8619
+ waitingStageMs: z16.number().positive(),
8620
+ leaseSweepMs: z16.number().positive(),
8621
+ opsIntervalMs: z16.number().positive(),
8622
+ signalRepeatMs: z16.number().positive(),
8623
+ costReportMs: z16.number().positive(),
8624
+ staleThreadMs: z16.number().positive(),
8625
+ idleMemberMs: z16.number().positive(),
8626
+ homeConflictMs: z16.number().positive(),
8627
+ scaleCooldownMs: z16.number().positive(),
8628
+ reflectionMs: z16.number().positive()
8327
8629
  }).partial();
8328
8630
  function parseTimings(input) {
8329
8631
  const parsed = SchedulerTimingsSchema.parse(input);
@@ -8335,32 +8637,32 @@ function parseTimings(input) {
8335
8637
  }
8336
8638
  return overrides;
8337
8639
  }
8338
- var PendingSchema = z14.object({
8640
+ var PendingSchema = z16.object({
8339
8641
  dispatch: TurnDispatchSchema,
8340
- readyAt: z14.number(),
8341
- stageWakes: z14.array(TriggerSchema).optional(),
8342
- holder: z14.boolean().optional(),
8642
+ readyAt: z16.number(),
8643
+ stageWakes: z16.array(TriggerSchema).optional(),
8644
+ holder: z16.boolean().optional(),
8343
8645
  newestMessage: UlidSchema.optional(),
8344
- onlyMessages: z14.boolean().optional(),
8646
+ onlyMessages: z16.boolean().optional(),
8345
8647
  steeredThrough: UlidSchema.optional(),
8346
- onlyCrons: z14.boolean().optional()
8648
+ onlyCrons: z16.boolean().optional()
8347
8649
  });
8348
- var StateSchema = z14.object({
8349
- cursor: z14.string().nullable(),
8350
- lastHeartbeat: z14.record(z14.string(), z14.string()),
8650
+ var StateSchema = z16.object({
8651
+ cursor: z16.string().nullable(),
8652
+ lastHeartbeat: z16.record(z16.string(), z16.string()),
8351
8653
  /** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
8352
- waitingSeen: z14.record(z14.string(), z14.string()).default({}),
8654
+ waitingSeen: z16.record(z16.string(), z16.string()).default({}),
8353
8655
  /** Queued dispatches survive a restart; the cursor has already moved past the events that made them. */
8354
- pending: z14.record(z14.string(), PendingSchema).default({}),
8355
- opsReported: z14.record(z14.string(), z14.string()).default({}),
8356
- releaseCounts: z14.record(z14.string(), z14.number().int().nonnegative()).default({}),
8357
- lastOps: z14.string().nullable().default(null),
8358
- lastScaled: z14.record(z14.string(), z14.string()).default({}),
8359
- lastCostReport: z14.string().nullable().default(null),
8360
- costSinceReport: z14.number().nonnegative().default(0),
8361
- turnsSinceReport: z14.number().int().nonnegative().default(0),
8656
+ pending: z16.record(z16.string(), PendingSchema).default({}),
8657
+ opsReported: z16.record(z16.string(), z16.string()).default({}),
8658
+ releaseCounts: z16.record(z16.string(), z16.number().int().nonnegative()).default({}),
8659
+ lastOps: z16.string().nullable().default(null),
8660
+ lastScaled: z16.record(z16.string(), z16.string()).default({}),
8661
+ lastCostReport: z16.string().nullable().default(null),
8662
+ costSinceReport: z16.number().nonnegative().default(0),
8663
+ turnsSinceReport: z16.number().int().nonnegative().default(0),
8362
8664
  /** When each member last reflected, or was first seen; the cadence counts from here. */
8363
- lastReflection: z14.record(z14.string(), z14.string()).default({})
8665
+ lastReflection: z16.record(z16.string(), z16.string()).default({})
8364
8666
  });
8365
8667
  function messageOf(trigger) {
8366
8668
  return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
@@ -9933,7 +10235,7 @@ var TURN_CONTRACT = [
9933
10235
  "- Posts that would wake this conversation may arrive while you work, delivered into the turn under a heading that says so and formatted like the digest. They are part of this turn: act on them now, after the step in hand, or not at all, as with the digest. The user may also stop a turn; the next turn is told.",
9934
10236
  "- Silence is allowed. If the digest needs no reply, post nothing.",
9935
10237
  "- To come back to something later, set a cron with create_cron: a note on what to do then, and a five-field cron expression or one time with at. It wakes you in this conversation unless you name another, at most every fifteen minutes, and every fire is a paid turn, so end a cron with remove_cron once it has done its job. Never wait by holding a stage, sleeping, or leaving a process running: a turn's board token ends with the turn, so nothing it leaves behind can act on the board. The society's crons are under society/crons/ in the board projection.",
9936
- "- An @mention wakes the citizen named, and every wake costs a turn. Address someone with @ only when you need them to act; when you merely refer to citizens, write their names plainly. @user wakes nobody, since the user takes no turns; it is what puts a question before the user, so use it only when you need the user to answer or decide.",
10238
+ "- An @mention wakes the citizen named, and every wake costs a turn. Address someone with @ only when you need them to act; when you merely refer to citizens, write their names plainly. A name in a code span or a code block is no mention, so code keeps its @ as written. @user wakes nobody, since the user takes no turns; it is what puts a question before the user, so use it only when you need the user to answer or decide.",
9937
10239
  "- A thread is a conversation off a channel that reaches only its participants and whoever is mentioned in it. Every task has one, opened with it under the task's id on the channel of its project it was filed in, general unless it was filed in another: talk about a task there, with post_message and its thread_id, and the notes you give advance_task and update_task are posted there as you. Its participants are the task's creator, its stage holders and assignees, whoever may take the stage that waits, and anyone who posted; it closes when the task ends. Every proposal has one too, in governance, opened with its pitch and closed by its decision. Open other threads with open_thread on a channel with a title, for any other topic; close_thread posts your summary to its channel.",
9938
10240
  "- To show an image in a post, such as a screenshot or a chart, commit the file to a task's branch and link it by its path in the task's worktree, as `![what it shows](<path>)`: the board shows a file on a task's branch in place once it is committed, at the latest when your turn ends. An image at an https address shows too. Never put an image or any other file into a message as base64 or a data: URL: the board does not show it, and every reader of the conversation gets it in full in their prompt.",
9939
10241
  "- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; a durable fact about a project's codebase or process goes through `write_knowledge` on that project; something the whole project should know now, post it to the project channel, and anything about one task, in the task's thread.",
@@ -9967,15 +10269,15 @@ var GOVERNANCE = [
9967
10269
  "- Prefer scaling an existing role over inventing one, with a reallocation of its replicas, backlog threshold, wakes, or verbs; a new role is justified by work a project needs that no existing role covers. A role that needs a tool the board lacks is an engineering task, not a hiring request.",
9968
10270
  "- Operations signals are counters and timers the board logs, listed in the prompt of a role that reads them: stages waiting for a holder, backlog per role, stages waiting on a role nobody fills, churn, stale threads, idle members, missing capabilities, replicas added, spend. They are not posts in any channel; interpreting them is your judgment."
9969
10271
  ].join("\n");
9970
- function renderOnboardingPreamble(context) {
10272
+ function renderOnboardingPreamble(context2) {
9971
10273
  return [
9972
- `This is your first turn as ${context.agentName}.`,
10274
+ `This is your first turn as ${context2.agentName}.`,
9973
10275
  "",
9974
- `Your role in one paragraph: ${context.roleSummary}`,
10276
+ `Your role in one paragraph: ${context2.roleSummary}`,
9975
10277
  "",
9976
- `You are working on project "${context.project}" in the worktree at ${context.worktree}.`,
9977
- ...(context.otherChannels ?? []).length === 0 ? [] : [
9978
- `You follow its general channel. It also has ${(context.otherChannels ?? []).map((ref) => `#${ref}`).join(", ")}, each a workstream such as a release: subscribe to one when your work there needs its posts. Its tasks and mentions reach you either way.`
10278
+ `You are working on project "${context2.project}" in the worktree at ${context2.worktree}.`,
10279
+ ...(context2.otherChannels ?? []).length === 0 ? [] : [
10280
+ `You follow its general channel. It also has ${(context2.otherChannels ?? []).map((ref) => `#${ref}`).join(", ")}, each a workstream such as a release: subscribe to one when your work there needs its posts. Its tasks and mentions reach you either way.`
9979
10281
  ],
9980
10282
  "Your memory is empty. Write durable lessons about yourself, your craft, or the user to memory/core.md in your home directory.",
9981
10283
  "Write profile.md in your home directory: one paragraph on what you do well and what to send your way. The roster the front desk routes with is built from it.",
@@ -10061,10 +10363,25 @@ function signalLine(ts, signal) {
10061
10363
  ].filter((fact) => fact.length > 0);
10062
10364
  return `- [${ts}] ${signal.kind}: ${signal.summary} (${facts.join("; ")})`;
10063
10365
  }
10064
- function runnerLine(runner, projects) {
10366
+ function quotaPhrase(cli, quota, now) {
10367
+ const level = quotaLevel(quota, now);
10368
+ if (level === "none") {
10369
+ return `${cli} reports no plan limits`;
10370
+ }
10371
+ if (level === "limited") {
10372
+ return `${cli} limited until ${quota.limitedUntil ?? "an unknown time"}`;
10373
+ }
10374
+ const windows = quota.windows.map((window) => !windowCurrent(window, now) ? `${windowName(window)} reset since` : `${windowName(window)} ${window.usedPercent === null ? "?" : Math.floor(window.usedPercent)}%`);
10375
+ return `${cli} ${windows.length === 0 ? "allowed" : windows.join(", ")}${level === "warning" ? " (near a limit)" : ""} as of ${quota.observedAt}`;
10376
+ }
10377
+ function runnerLine(runner, projects, now) {
10065
10378
  const living = projects.filter((project) => project.runner === runner.name).map((project) => project.slug);
10066
10379
  const state = runner.status === "connected" ? "connected" : `away${runner.lastSeen === void 0 ? "" : ` since ${runner.lastSeen}`}`;
10067
- return `- ${runner.name}: ${state}; ${runner.os}; CLIs ${runner.clis.length === 0 ? "none" : runner.clis.join(", ")}; offers ${runner.capabilities.length === 0 ? "nothing beyond its CLIs" : runner.capabilities.join(", ")}; ${living.length === 0 ? "no projects" : `projects ${living.join(", ")}`}`;
10380
+ const quotas = runner.clis.flatMap((cli) => {
10381
+ const quota = runner.quotas?.[cli];
10382
+ return quota === void 0 ? [] : [quotaPhrase(cli, quota, now)];
10383
+ });
10384
+ return `- ${runner.name}: ${state}; ${runner.os}; CLIs ${runner.clis.length === 0 ? "none" : runner.clis.join(", ")}; offers ${runner.capabilities.length === 0 ? "nothing beyond its CLIs" : runner.capabilities.join(", ")}; ${living.length === 0 ? "no projects" : `projects ${living.join(", ")}`}${quotas.length === 0 ? "" : `; quota ${quotas.join("; ")}`}`;
10068
10385
  }
10069
10386
  function profileLine(profile) {
10070
10387
  const line = profile.split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
@@ -10214,12 +10531,12 @@ function buildTurnPrompt(input) {
10214
10531
  }
10215
10532
  const machines = input.runners;
10216
10533
  if (machines !== void 0 && machines !== null) {
10217
- lines.push("", "## Runners", "", "The machines turns run on. A project lives on the runner that takes its first turn, one that offers every capability the project and that turn's task require, and never moves. A task's required_capabilities must name capabilities exactly as a runner below offers them; a task needing one its project's runner lacks waits.", "");
10534
+ lines.push("", "## Runners", "", "The machines turns run on. A project lives on the runner that takes its first turn, one that offers every capability the project and that turn's task require, and never moves. A task's required_capabilities must name capabilities exactly as a runner below offers them; a task needing one its project's runner lacks waits. A runner's quota is what each CLI's login there last reported of its plan, in percent of each window; a CLI limited until a time fails its turns there until then.", "");
10218
10535
  if (machines.runners.length === 0) {
10219
10536
  lines.push("None registered yet.");
10220
10537
  }
10221
10538
  for (const runner of machines.runners) {
10222
- lines.push(runnerLine(runner, machines.projects));
10539
+ lines.push(runnerLine(runner, machines.projects, input.clock?.now ?? /* @__PURE__ */ new Date()));
10223
10540
  }
10224
10541
  }
10225
10542
  const knowledge = input.knowledge;
@@ -10752,6 +11069,11 @@ var TurnHost = class {
10752
11069
  }
10753
11070
  }
10754
11071
  });
11072
+ if (outcome.quota !== void 0) {
11073
+ await this.board.recordQuota(turn.runner, job.cli, outcome.quota).catch((error) => {
11074
+ this.log.warn({ runner: turn.runner, cli: job.cli, error: String(error) }, "could not record a quota");
11075
+ });
11076
+ }
10755
11077
  if (recorded) {
10756
11078
  this.log.info({
10757
11079
  agent: job.agent,
@@ -11081,7 +11403,8 @@ var RunnerHub = class {
11081
11403
  os: hello.os,
11082
11404
  clis: hello.clis,
11083
11405
  capabilities: hello.capabilities,
11084
- version: hello.version
11406
+ version: hello.version,
11407
+ gitIdentityMissing: hello.gitIdentityMissing ?? null
11085
11408
  });
11086
11409
  this.runnerChanged(name);
11087
11410
  this.log.info({ runner: name, version: hello.version, clis: hello.clis, slots: hello.slots }, "runner registered");
@@ -11523,6 +11846,26 @@ var RunnerHub = class {
11523
11846
  }
11524
11847
  return ModelListSchema.parse(await this.request(seat, (request) => ({ type: "models", request, cli })));
11525
11848
  }
11849
+ /**
11850
+ * A CLI's plan quota, read on a runner without a turn and recorded on the runner's record.
11851
+ * Refused for a runner not connected with the CLI, and for one from before quota reads, which
11852
+ * would skip the request and leave the reader waiting.
11853
+ */
11854
+ async quota(runner, cli) {
11855
+ const seat = this.seats.get(runner);
11856
+ if (seat === void 0 || seat.send === null || !seat.hello.clis.includes(cli)) {
11857
+ throw new RunnerAwayError(`runner ${runner} is not connected with ${cli}`);
11858
+ }
11859
+ if (!seat.hello.quotaReads) {
11860
+ throw new BoardError("INVALID_STATE", `runner ${runner} cannot read quotas yet; upgrade it`);
11861
+ }
11862
+ const quota = CliQuotaSchema.nullable().parse(await this.request(seat, (request) => ({ type: "quota", request, cli }), FILE_TIMEOUT_MS));
11863
+ if (quota === null) {
11864
+ throw new BoardError("NOT_FOUND", `${cli} on runner ${runner} cannot read its quota`);
11865
+ }
11866
+ await this.board.recordQuota(runner, cli, quota);
11867
+ return quota;
11868
+ }
11526
11869
  /** Calls `listener` whenever a runner registers, connects, or goes away. */
11527
11870
  onRunnerChange(listener) {
11528
11871
  this.runnerListeners.add(listener);
@@ -11755,20 +12098,20 @@ async function handleMcpRequest(board2, actor, request, version) {
11755
12098
  import { Hono as Hono3 } from "hono";
11756
12099
  import { compress } from "hono/compress";
11757
12100
  import { streamSSE as streamSSE2 } from "hono/streaming";
11758
- import { z as z16, ZodError } from "zod";
12101
+ import { z as z18, ZodError } from "zod";
11759
12102
 
11760
12103
  // apps/server/dist/github-auth.js
11761
12104
  import { randomBytes as randomBytes2 } from "node:crypto";
11762
12105
  import { Hono } from "hono";
11763
- import { z as z15 } from "zod";
12106
+ import { z as z17 } from "zod";
11764
12107
  var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
11765
12108
  var STATE_TTL_MS = 10 * 6e4;
11766
12109
  var MAX_STATES = 1e3;
11767
- var TokenAnswerSchema = z15.object({ access_token: z15.string().min(1) });
11768
- var GithubUserSchema = z15.object({
11769
- login: z15.string().min(1),
11770
- id: z15.number().int(),
11771
- avatar_url: z15.string().optional()
12110
+ var TokenAnswerSchema = z17.object({ access_token: z17.string().min(1) });
12111
+ var GithubUserSchema = z17.object({
12112
+ login: z17.string().min(1),
12113
+ id: z17.number().int(),
12114
+ avatar_url: z17.string().optional()
11772
12115
  });
11773
12116
  function githubAvatar(url) {
11774
12117
  try {
@@ -12191,7 +12534,7 @@ async function isFile(file) {
12191
12534
  return false;
12192
12535
  }
12193
12536
  }
12194
- function within(root, pathname) {
12537
+ function within2(root, pathname) {
12195
12538
  let decoded;
12196
12539
  try {
12197
12540
  decoded = decodeURIComponent(pathname);
@@ -12208,7 +12551,7 @@ function spaHandler(staticDir) {
12208
12551
  if (pathname.startsWith("/api/")) {
12209
12552
  return c.json({ error: "NOT_FOUND", message: `no route ${pathname}` }, 404);
12210
12553
  }
12211
- const file = within(root, pathname);
12554
+ const file = within2(root, pathname);
12212
12555
  const headers = { "x-content-type-options": "nosniff" };
12213
12556
  if (file !== null && await isFile(file)) {
12214
12557
  return c.body(await readFile5(file), 200, {
@@ -12243,19 +12586,19 @@ var ERROR_STATUS = {
12243
12586
  INVALID_STATE: 409
12244
12587
  };
12245
12588
  var EVENT_PAGE = 100;
12246
- var RetireBodySchema = z16.object({ reason: z16.string().min(1) });
12247
- var RunnerBodySchema = z16.object({ name: NameSchema });
12248
- var EnrollmentApprovalSchema = z16.object({ name: NameSchema });
12249
- var HomeRunnerBodySchema = z16.object({ runner: NameSchema.nullable() });
12250
- var ModelBodySchema = z16.object({ model: ModelNameSchema.nullable() });
12251
- var EffortBodySchema = z16.object({ effort: EffortSchema.nullable() });
12252
- var HistoryLimitSchema = z16.coerce.number().int().min(1).max(500);
12253
- var MessageIdsSchema = z16.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z16.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
12254
- var TimezoneBodySchema = z16.object({ timezone: TimeZoneSchema });
12255
- var ChannelBodySchema = z16.object({
12589
+ var RetireBodySchema = z18.object({ reason: z18.string().min(1) });
12590
+ var RunnerBodySchema = z18.object({ name: NameSchema });
12591
+ var EnrollmentApprovalSchema = z18.object({ name: NameSchema });
12592
+ var HomeRunnerBodySchema = z18.object({ runner: NameSchema.nullable() });
12593
+ var ModelBodySchema = z18.object({ model: ModelNameSchema.nullable() });
12594
+ var EffortBodySchema = z18.object({ effort: EffortSchema.nullable() });
12595
+ var HistoryLimitSchema = z18.coerce.number().int().min(1).max(500);
12596
+ var MessageIdsSchema = z18.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z18.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
12597
+ var TimezoneBodySchema = z18.object({ timezone: TimeZoneSchema });
12598
+ var ChannelBodySchema = z18.object({
12256
12599
  project: NameSchema.nullable().default(null),
12257
12600
  name: NameSchema,
12258
- purpose: z16.string().min(1)
12601
+ purpose: z18.string().min(1)
12259
12602
  });
12260
12603
  function bearer2(c) {
12261
12604
  const header = c.req.header("authorization") ?? "";
@@ -12263,7 +12606,7 @@ function bearer2(c) {
12263
12606
  return match?.[1]?.trim() ?? null;
12264
12607
  }
12265
12608
  function createApp(deps) {
12266
- const { board: board2, version, turns: turns2, scheduler: scheduler2, models } = deps;
12609
+ const { board: board2, version, turns: turns2, scheduler: scheduler2, models, quotas } = deps;
12267
12610
  const eventStreamPingMs = deps.eventStreamPingMs ?? 15e3;
12268
12611
  const app2 = new Hono3();
12269
12612
  app2.get("/health", (c) => c.json({ ok: true, version }));
@@ -12333,6 +12676,17 @@ function createApp(deps) {
12333
12676
  api.get("/agents/:name/history/:commit", async (c) => c.json(await board2.homeChange(c.req.param("name"), c.req.param("commit"))));
12334
12677
  api.get("/roles", async (c) => c.json(await board2.listRoles()));
12335
12678
  api.get("/runners", async (c) => c.json(await board2.listRunners()));
12679
+ api.post("/runners/:name/quota/:cli", async (c) => {
12680
+ if (c.get("actor").role !== USER_ROLE) {
12681
+ throw new BoardError("FORBIDDEN", "only the user reads a runner's quota");
12682
+ }
12683
+ const runner = NameSchema.parse(c.req.param("name"));
12684
+ const cli = CliKindSchema.parse(c.req.param("cli"));
12685
+ if (quotas === void 0) {
12686
+ throw new RunnerAwayError("this server asks no runner for quotas");
12687
+ }
12688
+ return c.json(await quotas.read(runner, cli));
12689
+ });
12336
12690
  api.post("/runners", async (c) => {
12337
12691
  const body = RunnerBodySchema.parse(await c.req.json());
12338
12692
  return c.json(await board2.addRunner(c.get("actor"), body.name));
@@ -12369,6 +12723,10 @@ function createApp(deps) {
12369
12723
  const window = MetricsWindowSchema.parse(c.req.query("window") ?? "7d");
12370
12724
  return c.json(await board2.metrics(window, new Set(scheduler2?.activeSignals ?? [])));
12371
12725
  });
12726
+ api.get("/tokens", async (c) => {
12727
+ const window = MetricsWindowSchema.parse(c.req.query("window") ?? "24h");
12728
+ return c.json(await board2.tokenUse(window));
12729
+ });
12372
12730
  api.get("/society/knowledge", async (c) => c.json(await board2.listKnowledge(null)));
12373
12731
  api.get("/skills", async (c) => c.json(await board2.listSocietySkills()));
12374
12732
  api.post("/agents/:name/retire", async (c) => {
@@ -12412,7 +12770,7 @@ function createApp(deps) {
12412
12770
  });
12413
12771
  api.put("/roles/:name", async (c) => {
12414
12772
  const charter = RoleCharterSchema.parse({
12415
- ...z16.record(z16.string(), z16.unknown()).parse(await c.req.json()),
12773
+ ...z18.record(z18.string(), z18.unknown()).parse(await c.req.json()),
12416
12774
  name: c.req.param("name")
12417
12775
  });
12418
12776
  return c.json(await board2.setRoleCharter(c.get("actor"), charter));
@@ -12762,6 +13120,35 @@ var ModelCatalog = class {
12762
13120
  }
12763
13121
  };
12764
13122
 
13123
+ // apps/server/dist/quotas.js
13124
+ var MINUTE = 6e4;
13125
+ var QuotaReads = class {
13126
+ runners;
13127
+ floorMs;
13128
+ now;
13129
+ kept = /* @__PURE__ */ new Map();
13130
+ constructor(runners2, floorMs = MINUTE, now = Date.now) {
13131
+ this.runners = runners2;
13132
+ this.floorMs = floorMs;
13133
+ this.now = now;
13134
+ }
13135
+ read(runner, cli) {
13136
+ const key = `${runner}/${cli}`;
13137
+ const kept = this.kept.get(key);
13138
+ if (kept !== void 0 && this.now() - kept.at < this.floorMs) {
13139
+ return kept.quota;
13140
+ }
13141
+ const quota = this.runners.quota(runner, cli);
13142
+ this.kept.set(key, { at: this.now(), quota });
13143
+ quota.catch(() => {
13144
+ if (this.kept.get(key)?.quota === quota) {
13145
+ this.kept.delete(key);
13146
+ }
13147
+ });
13148
+ return quota;
13149
+ }
13150
+ };
13151
+
12765
13152
  // apps/server/dist/turn-hub.js
12766
13153
  var TurnHub = class {
12767
13154
  capacity;
@@ -12869,6 +13256,7 @@ var app = createApp({
12869
13256
  turns,
12870
13257
  scheduler: view,
12871
13258
  models: new ModelCatalog(runners),
13259
+ quotas: new QuotaReads(runners),
12872
13260
  runners,
12873
13261
  webDir,
12874
13262
  signIn: { github: config.github, log },