@kubitnodes/stellaris 0.3.21 → 0.3.23

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 (77) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +1007 -598
  3. package/dist/server/index.js +1252 -671
  4. package/package.json +3 -1
  5. package/web/dist/assets/{BackLink-23Q1JvmZ.js → BackLink-DGYs80bb.js} +1 -1
  6. package/web/dist/assets/Button-f4jgBZY1.js +1 -0
  7. package/web/dist/assets/ChannelForms-q9zdj4p2.js +1 -0
  8. package/web/dist/assets/ChannelView-CGs_aWL3.js +2 -0
  9. package/web/dist/assets/CitizenView-gcIo_sKt.js +2 -0
  10. package/web/dist/assets/CitizensView-DZ7S8vnJ.js +1 -0
  11. package/web/dist/assets/{CliIcon-BOSygXeI.js → CliIcon-CLHaA65A.js} +1 -1
  12. package/web/dist/assets/CronsView-1wns6Uwv.js +1 -0
  13. package/web/dist/assets/KnowledgeView-Cxfi2-Pt.js +1 -0
  14. package/web/dist/assets/Markdown-BhtIFcWc.js +30 -0
  15. package/web/dist/assets/MessageItem-Bk9eBp1U.js +1 -0
  16. package/web/dist/assets/MetricsView-CTEH6rJn.js +1 -0
  17. package/web/dist/assets/NeedsYouView-2VNqH2Sb.js +1 -0
  18. package/web/dist/assets/Overview-CS9HCSmm.js +1 -0
  19. package/web/dist/assets/{Pane-BendOaje.js → Pane-CJYRVkwh.js} +1 -1
  20. package/web/dist/assets/{Picker-BuVnakvi.js → Picker-DoxCN6C7.js} +1 -1
  21. package/web/dist/assets/ProjectView-CJ3r4UdN.js +1 -0
  22. package/web/dist/assets/ProposalView-DLn9dNPp.js +2 -0
  23. package/web/dist/assets/ProposalsView-B-cOxTsQ.js +1 -0
  24. package/web/dist/assets/Quota-Bo7UdWTV.js +1 -0
  25. package/web/dist/assets/RunnersView-DlQruhwp.js +1 -0
  26. package/web/dist/assets/SocietyView-Bl4ErnV8.js +1 -0
  27. package/web/dist/assets/StickyList-MVP34ytV.js +1 -0
  28. package/web/dist/assets/SubjectThread-BFFnHxIF.js +1 -0
  29. package/web/dist/assets/TaskFileView-GZ0eQFUb.js +4 -0
  30. package/web/dist/assets/TaskView-DppFelLW.js +3 -0
  31. package/web/dist/assets/TasksView-DDNXzeG4.js +1 -0
  32. package/web/dist/assets/ThreadForms-C846TvtS.js +1 -0
  33. package/web/dist/assets/ThreadItem-C8cou6wD.js +1 -0
  34. package/web/dist/assets/ThreadView-DvSGDYoD.js +1 -0
  35. package/web/dist/assets/citizen-B2Dlbcpe.js +1 -0
  36. package/web/dist/assets/compose-BmqQmcZH.js +1 -0
  37. package/web/dist/assets/format-DT52SbbR.js +55 -0
  38. package/web/dist/assets/governance-Cr5OlQnw.js +1 -0
  39. package/web/dist/assets/index-BTVy_xFF.js +23 -0
  40. package/web/dist/assets/index-DzV6CCnG.css +2 -0
  41. package/web/dist/assets/mentions-D5hNgtg6.js +3 -0
  42. package/web/dist/assets/{tasks-CyXIxoAy.js → tasks-R3YgpYTe.js} +1 -1
  43. package/web/dist/assets/useMatch-pEF6RJ6Q.js +1 -0
  44. package/web/dist/index.html +9 -10
  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/MessageItem-DI05jbrI.js +0 -1
  53. package/web/dist/assets/MetricsView-DKh-lEW5.js +0 -1
  54. package/web/dist/assets/NeedsYouView-C6FfiTsg.js +0 -1
  55. package/web/dist/assets/Overview-D5RvWBg5.js +0 -1
  56. package/web/dist/assets/ProjectView-blBEh70W.js +0 -1
  57. package/web/dist/assets/ProposalView-CMntpKTc.js +0 -2
  58. package/web/dist/assets/ProposalsView-CVUAV2Mm.js +0 -1
  59. package/web/dist/assets/RunnersView-B-wuWVgB.js +0 -1
  60. package/web/dist/assets/SocietyView-CaVbfJWz.js +0 -1
  61. package/web/dist/assets/SubjectThread-Bd_-sDhJ.js +0 -1
  62. package/web/dist/assets/TaskFileView-CO4Y97OT.js +0 -4
  63. package/web/dist/assets/TaskView-DRZoHALo.js +0 -3
  64. package/web/dist/assets/TasksView-CQVQo3zF.js +0 -1
  65. package/web/dist/assets/ThreadForms-CUSeWu0c.js +0 -1
  66. package/web/dist/assets/ThreadItem-BPYYXh3V.js +0 -1
  67. package/web/dist/assets/ThreadView-BcI2i6FI.js +0 -1
  68. package/web/dist/assets/citizen-Eff1h9rA.js +0 -1
  69. package/web/dist/assets/compose-D73Agshx.js +0 -1
  70. package/web/dist/assets/format-fw0G_Vff.js +0 -55
  71. package/web/dist/assets/governance-sRsfOw52.js +0 -1
  72. package/web/dist/assets/index-BkOq4PSU.css +0 -2
  73. package/web/dist/assets/index-CYiok0pT.js +0 -23
  74. package/web/dist/assets/link-Cl7vKbOz.js +0 -1
  75. package/web/dist/assets/mentions-BCdV0bMK.js +0 -1
  76. package/web/dist/assets/useMatch-D-tEHQEw.js +0 -1
  77. 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 z16 } from "zod";
13
13
 
14
14
  // packages/shared/dist/ids.js
15
15
  import { z } from "zod";
@@ -64,7 +64,8 @@ var VERB_NAMES = [
64
64
  "create_cron",
65
65
  "update_cron",
66
66
  "remove_cron",
67
- "remove_skill"
67
+ "remove_skill",
68
+ "upload_image"
68
69
  ];
69
70
  var VerbNameSchema = z2.enum(VERB_NAMES);
70
71
  var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
@@ -93,6 +94,7 @@ var USER_ROLE = "user";
93
94
  var USER_NAME = "user";
94
95
  var MEMBER_VERBS = [
95
96
  "post_message",
97
+ "upload_image",
96
98
  "read_inbox",
97
99
  "search",
98
100
  "open_thread",
@@ -169,7 +171,7 @@ var SEED_ROLES = [
169
171
  ];
170
172
 
171
173
  // packages/shared/dist/board.js
172
- import { z as z4 } from "zod";
174
+ import { z as z5 } from "zod";
173
175
 
174
176
  // packages/shared/dist/crons.js
175
177
  import { Cron as Pattern } from "croner";
@@ -303,6 +305,115 @@ function localTime(date, timeZone) {
303
305
  return `${part("weekday")} ${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}`;
304
306
  }
305
307
 
308
+ // packages/shared/dist/quota.js
309
+ import { z as z4 } from "zod";
310
+ var QUOTA_WARNING_PERCENT = 80;
311
+ var QuotaWindowSchema = z4.object({
312
+ /**
313
+ * The CLI's own name for the window: Claude's `five_hour`, `seven_day`, `seven_day_opus`, and
314
+ * `model:<label>` for a model's weekly window; Codex's `primary` and `secondary`.
315
+ */
316
+ key: z4.string().min(1).max(80),
317
+ /** What a scoped window counts, as the CLI labels it, such as a model's name. */
318
+ label: z4.string().min(1).max(80).optional(),
319
+ /** The window's length in minutes, when known. */
320
+ minutes: z4.number().int().positive().nullable().default(null),
321
+ /** How much of the window is used, in percent, or null when the CLI did not say. */
322
+ usedPercent: z4.number().nonnegative().nullable().default(null),
323
+ /** When the window starts afresh, when known. */
324
+ resetsAt: IsoDateTimeSchema.nullable().default(null),
325
+ /** When the CLI last reported this window. */
326
+ observedAt: IsoDateTimeSchema
327
+ });
328
+ var QuotaStatusSchema = z4.enum(["allowed", "warning", "limited", "none"]);
329
+ var QuotaCreditsSchema = z4.object({
330
+ hasCredits: z4.boolean(),
331
+ unlimited: z4.boolean(),
332
+ balance: z4.string().max(40).nullable()
333
+ });
334
+ var QuotaExtraUsageSchema = z4.object({
335
+ enabled: z4.boolean(),
336
+ monthlyLimit: z4.number().nonnegative().nullable(),
337
+ usedCredits: z4.number().nonnegative().nullable(),
338
+ currency: z4.string().min(1).max(8).nullable()
339
+ });
340
+ var CliQuotaSchema = z4.object({
341
+ /** When the CLI last reported anything here. */
342
+ observedAt: IsoDateTimeSchema,
343
+ /** Whether it came from a turn's stream or from asking the CLI. */
344
+ source: z4.enum(["turn", "read"]),
345
+ status: QuotaStatusSchema,
346
+ /** While `limited`: when work can go on, when known, and the window that stopped it. */
347
+ limitedUntil: IsoDateTimeSchema.nullable().default(null),
348
+ limitedBy: z4.string().min(1).max(80).nullable().default(null),
349
+ /** The plan's name as the CLI gives it, such as `max` or `pro`. */
350
+ plan: z4.string().min(1).max(40).nullable().default(null),
351
+ windows: z4.array(QuotaWindowSchema).max(24).default([]),
352
+ credits: QuotaCreditsSchema.nullable().default(null),
353
+ extraUsage: QuotaExtraUsageSchema.nullable().default(null)
354
+ });
355
+ function mergeQuota(previous, next) {
356
+ if (previous === void 0) {
357
+ return next;
358
+ }
359
+ const windows = new Map(previous.windows.map((window) => [window.key, window]));
360
+ for (const window of next.windows) {
361
+ windows.set(window.key, window);
362
+ }
363
+ return {
364
+ ...next,
365
+ plan: next.plan ?? previous.plan,
366
+ credits: next.credits ?? previous.credits,
367
+ extraUsage: next.extraUsage ?? previous.extraUsage,
368
+ windows: [...windows.values()]
369
+ };
370
+ }
371
+ function windowCurrent(window, now) {
372
+ return window.resetsAt === null || Date.parse(window.resetsAt) > now.getTime();
373
+ }
374
+ function quotaLevel(quota, now) {
375
+ if (quota.status === "none") {
376
+ return "none";
377
+ }
378
+ if (quota.status === "limited" && (quota.limitedUntil === null || Date.parse(quota.limitedUntil) > now.getTime())) {
379
+ return "limited";
380
+ }
381
+ const high = quota.windows.some((window) => windowCurrent(window, now) && window.usedPercent !== null && window.usedPercent >= QUOTA_WARNING_PERCENT);
382
+ return quota.status === "warning" || high ? "warning" : "ok";
383
+ }
384
+ var WINDOW_NAMES = {
385
+ five_hour: "5h",
386
+ seven_day: "7d",
387
+ seven_day_opus: "7d Opus",
388
+ seven_day_sonnet: "7d Sonnet",
389
+ seven_day_oauth_apps: "7d apps",
390
+ seven_day_overage_included: "7d extra",
391
+ overage: "extra usage"
392
+ };
393
+ function windowName(window) {
394
+ if (window.label !== void 0) {
395
+ return window.minutes === 10080 ? `7d ${window.label}` : window.label;
396
+ }
397
+ const named = WINDOW_NAMES[window.key];
398
+ if (named !== void 0) {
399
+ return named;
400
+ }
401
+ if (window.minutes !== null) {
402
+ return window.minutes % 1440 === 0 ? `${window.minutes / 1440}d` : window.minutes % 60 === 0 ? `${window.minutes / 60}h` : `${window.minutes}m`;
403
+ }
404
+ return window.key;
405
+ }
406
+ function readings(quota) {
407
+ 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();
408
+ return windows.join(",");
409
+ }
410
+ function quotaChanged(previous, next) {
411
+ if (previous === void 0) {
412
+ return true;
413
+ }
414
+ 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);
415
+ }
416
+
306
417
  // packages/shared/dist/board.js
307
418
  var ASK_CHANNEL = "asks";
308
419
  var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
@@ -315,24 +426,24 @@ var SOCIETY_SCOPE = "society";
315
426
  function wakeScope(member, project) {
316
427
  return project !== null && member.memberships.includes(project) ? project : SOCIETY_SCOPE;
317
428
  }
318
- var CliKindSchema = z4.enum(["claude", "codex"]);
319
- var ArchivedChannelSchema = z4.object({
429
+ var CliKindSchema = z5.enum(["claude", "codex"]);
430
+ var ArchivedChannelSchema = z5.object({
320
431
  name: NameSchema,
321
432
  at: IsoDateTimeSchema,
322
433
  by: NameSchema,
323
- reason: z4.string().min(1)
434
+ reason: z5.string().min(1)
324
435
  });
325
- var SocietySchema = z4.object({
326
- name: z4.string().min(1),
327
- version: z4.literal(1),
436
+ var SocietySchema = z5.object({
437
+ name: z5.string().min(1),
438
+ version: z5.literal(1),
328
439
  createdAt: IsoDateTimeSchema,
329
440
  /** The channels that take posts; an archived one moves to `archivedChannels`. */
330
- channels: z4.array(NameSchema),
331
- archivedChannels: z4.array(ArchivedChannelSchema).default([]),
441
+ channels: z5.array(NameSchema),
442
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
332
443
  /** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
333
444
  timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
334
445
  });
335
- var CompletionEffectSchema = z4.enum(["none", "merge", "ghpr"]);
446
+ var CompletionEffectSchema = z5.enum(["none", "merge", "ghpr"]);
336
447
  var GITHUB_REMOTE = [
337
448
  /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
338
449
  /^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
@@ -348,7 +459,7 @@ function githubRepository(remote) {
348
459
  return null;
349
460
  }
350
461
  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>");
462
+ var PullRequestUrlSchema = z5.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
352
463
  function parsePullRequestUrl(url) {
353
464
  const match = PULL_REQUEST_URL.exec(url.trim());
354
465
  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 +467,72 @@ function parsePullRequestUrl(url) {
356
467
  function sameRepository(a, b) {
357
468
  return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
358
469
  }
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({
470
+ var MergeSubjectSchema = z5.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
471
+ var PullRequestLinkSchema = z5.object({
361
472
  url: PullRequestUrlSchema,
362
- number: z4.number().int().positive(),
473
+ number: z5.number().int().positive(),
363
474
  subject: MergeSubjectSchema.optional(),
364
- body: z4.string().max(2e4).optional(),
475
+ body: z5.string().max(2e4).optional(),
365
476
  linkedBy: NameSchema,
366
477
  linkedAt: IsoDateTimeSchema,
367
478
  /**
368
479
  * Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
369
480
  * leaves it open, for the reason `detail` gives.
370
481
  */
371
- closed: z4.object({ at: IsoDateTimeSchema, ok: z4.boolean(), detail: z4.string() }).optional()
482
+ closed: z5.object({ at: IsoDateTimeSchema, ok: z5.boolean(), detail: z5.string() }).optional()
372
483
  });
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");
484
+ 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");
485
+ var StageIdSchema = z5.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
375
486
  var stageFields = {
376
- name: z4.string().trim().min(1).max(120),
487
+ name: z5.string().trim().min(1).max(120),
377
488
  role: NameSchema.optional(),
378
489
  agent: NameSchema.optional(),
379
- gate: z4.boolean().default(false)
490
+ gate: z5.boolean().default(false)
380
491
  };
381
492
  function oneAssignee(stage) {
382
493
  return stage.role === void 0 || stage.agent === void 0;
383
494
  }
384
495
  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({
496
+ var PlanStageSchema = z5.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
497
+ var PlanEditStageSchema = z5.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
498
+ var PlanSchema = z5.array(PlanStageSchema);
499
+ var PlanEditSchema = z5.array(PlanEditStageSchema);
500
+ var ProjectSchema = z5.object({
390
501
  slug: NameSchema,
391
- name: z4.string().min(1),
392
- repo: z4.string().nullable(),
393
- defaultBranch: z4.string().min(1),
502
+ name: z5.string().min(1),
503
+ repo: z5.string().nullable(),
504
+ defaultBranch: z5.string().min(1),
394
505
  /** 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()),
506
+ channels: z5.array(NameSchema),
507
+ archivedChannels: z5.array(ArchivedChannelSchema).default([]),
508
+ members: z5.array(NameSchema),
509
+ approvers: z5.array(NameSchema),
510
+ requiredCapabilities: z5.array(z5.string()),
400
511
  createdAt: IsoDateTimeSchema,
401
512
  onDone: CompletionEffectSchema.default("none"),
402
513
  /** The runner the project lives on, which holds its repository; set on the project's first turn. */
403
514
  runner: NameSchema.optional(),
404
515
  /** 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()
516
+ archived: z5.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z5.string().min(1) }).optional()
406
517
  });
407
- var TaskStepSchema = z4.object({
408
- action: z4.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
518
+ var TaskStepSchema = z5.object({
519
+ action: z5.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
409
520
  stage: StageIdSchema,
410
521
  to: StageIdSchema.nullable()
411
522
  });
412
- var ProposalStepSchema = z4.object({ action: z4.enum(["approved", "rejected"]) });
413
- var MessageStepSchema = z4.union([TaskStepSchema, ProposalStepSchema]);
523
+ var ProposalStepSchema = z5.object({ action: z5.enum(["approved", "rejected"]) });
524
+ var MessageStepSchema = z5.union([TaskStepSchema, ProposalStepSchema]);
414
525
  function isTaskStep(step) {
415
526
  return "stage" in step;
416
527
  }
417
- var KnowledgeNoticeSchema = z4.object({
418
- kind: z4.literal("knowledge"),
419
- action: z4.enum(["written", "updated", "removed"]),
528
+ var KnowledgeNoticeSchema = z5.object({
529
+ kind: z5.literal("knowledge"),
530
+ action: z5.enum(["written", "updated", "removed"]),
420
531
  topic: NameSchema,
421
532
  project: NameSchema.nullable()
422
533
  });
423
- var MessageNoticeSchema = z4.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
424
- var MessageFrontmatterSchema = z4.object({
534
+ var MessageNoticeSchema = z5.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
535
+ var MessageFrontmatterSchema = z5.object({
425
536
  id: UlidSchema,
426
537
  author: NameSchema,
427
538
  channel: ChannelRefSchema,
@@ -435,7 +546,7 @@ var MessageFrontmatterSchema = z4.object({
435
546
  /** On a post a verb made about its work, such as a knowledge topic's note: what it records. */
436
547
  notice: MessageNoticeSchema.optional(),
437
548
  ts: IsoDateTimeSchema,
438
- mentions: z4.array(NameSchema)
549
+ mentions: z5.array(NameSchema)
439
550
  });
440
551
  var MessageRefSchema = MessageFrontmatterSchema.pick({
441
552
  id: true,
@@ -445,16 +556,16 @@ var MessageRefSchema = MessageFrontmatterSchema.pick({
445
556
  ts: true
446
557
  });
447
558
  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"]),
559
+ var TaskStatusSchema = z5.enum(["open", "claimed", "done", "abandoned"]);
560
+ var ThreadStateSchema = z5.enum(["open", "closed"]);
561
+ var ThreadSubjectSchema = z5.object({
562
+ kind: z5.enum(["task", "proposal"]),
452
563
  id: UlidSchema
453
564
  });
454
- var ThreadFrontmatterSchema = z4.object({
565
+ var ThreadFrontmatterSchema = z5.object({
455
566
  id: UlidSchema,
456
567
  channel: ChannelRefSchema,
457
- title: z4.string().min(1).max(200),
568
+ title: z5.string().min(1).max(200),
458
569
  subject: ThreadSubjectSchema.optional(),
459
570
  state: ThreadStateSchema,
460
571
  openedBy: NameSchema,
@@ -462,28 +573,28 @@ var ThreadFrontmatterSchema = z4.object({
462
573
  closedBy: NameSchema.optional(),
463
574
  closedAt: IsoDateTimeSchema.optional()
464
575
  });
465
- var StageSchema = z4.object({
576
+ var StageSchema = z5.object({
466
577
  id: StageIdSchema,
467
- name: z4.string().min(1),
578
+ name: z5.string().min(1),
468
579
  role: NameSchema.optional(),
469
580
  agent: NameSchema.optional(),
470
- gate: z4.boolean().default(false),
581
+ gate: z5.boolean().default(false),
471
582
  /** Everyone who has held the stage, oldest first. */
472
- holders: z4.array(NameSchema).default([]),
583
+ holders: z5.array(NameSchema).default([]),
473
584
  completedBy: NameSchema.optional(),
474
585
  completedAt: IsoDateTimeSchema.optional()
475
586
  });
476
- var TaskReturnSchema = z4.object({
587
+ var TaskReturnSchema = z5.object({
477
588
  from: StageIdSchema,
478
589
  by: NameSchema,
479
590
  at: IsoDateTimeSchema
480
591
  });
481
- var TaskFrontmatterSchema = z4.object({
592
+ var TaskFrontmatterSchema = z5.object({
482
593
  id: UlidSchema,
483
594
  project: NameSchema,
484
595
  /** The channel of the project the task is filed in, where its thread hangs. */
485
596
  channel: NameSchema.default("general"),
486
- title: z4.string().min(1),
597
+ title: z5.string().min(1),
487
598
  status: TaskStatusSchema,
488
599
  createdBy: NameSchema,
489
600
  createdAt: IsoDateTimeSchema,
@@ -492,19 +603,19 @@ var TaskFrontmatterSchema = z4.object({
492
603
  claimedBy: NameSchema.optional(),
493
604
  leaseExpiresAt: IsoDateTimeSchema.optional(),
494
605
  parentId: UlidSchema.optional(),
495
- blockedBy: z4.array(UlidSchema),
496
- requiredCapabilities: z4.array(z4.string()),
497
- stages: z4.array(StageSchema).min(1),
606
+ blockedBy: z5.array(UlidSchema),
607
+ requiredCapabilities: z5.array(z5.string()),
608
+ stages: z5.array(StageSchema).min(1),
498
609
  stage: StageIdSchema,
499
610
  /** When the current stage last became current or lost its holder: the waiting clock. */
500
611
  stageSince: IsoDateTimeSchema,
501
612
  /** The highest stage number issued, so a removed stage's id is never reused. */
502
- stageSeq: z4.number().int().positive(),
613
+ stageSeq: z5.number().int().positive(),
503
614
  onDone: CompletionEffectSchema.default("none"),
504
615
  /** The pull request a `ghpr` task lands through. */
505
616
  pullRequest: PullRequestLinkSchema.optional(),
506
617
  /** The last stage is complete and the completion effect is running. */
507
- completing: z4.boolean().default(false),
618
+ completing: z5.boolean().default(false),
508
619
  /** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
509
620
  returned: TaskReturnSchema.optional()
510
621
  });
@@ -528,79 +639,90 @@ function mayHoldStage(member, task) {
528
639
  }
529
640
  return !(stage.gate && task.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
530
641
  }
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({
642
+ var ModelNameSchema = z5.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
643
+ var EffortSchema = z5.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
644
+ var EffortOptionSchema = z5.object({
534
645
  id: EffortSchema,
535
- description: z4.string().default("")
646
+ description: z5.string().default("")
536
647
  });
537
- var ModelOptionSchema = z4.object({
648
+ var ModelOptionSchema = z5.object({
538
649
  id: ModelNameSchema,
539
- name: z4.string(),
540
- description: z4.string().default(""),
541
- isDefault: z4.boolean().default(false),
542
- efforts: z4.array(EffortOptionSchema).default([]),
650
+ name: z5.string(),
651
+ description: z5.string().default(""),
652
+ isDefault: z5.boolean().default(false),
653
+ efforts: z5.array(EffortOptionSchema).default([]),
543
654
  defaultEffort: EffortSchema.optional()
544
655
  });
545
- var RunnerModelsSchema = z4.object({
656
+ var RunnerModelsSchema = z5.object({
546
657
  runner: NameSchema,
547
658
  cli: CliKindSchema,
548
- models: z4.array(ModelOptionSchema)
659
+ models: z5.array(ModelOptionSchema)
549
660
  });
550
- var AgentStatusSchema = z4.enum(["active", "retired"]);
551
- var AgentSchema = z4.object({
661
+ var AgentStatusSchema = z5.enum(["active", "retired"]);
662
+ var AgentSchema = z5.object({
552
663
  name: NameSchema,
553
664
  role: NameSchema,
554
665
  cli: CliKindSchema.nullable(),
555
- model: z4.string().optional(),
666
+ model: z5.string().optional(),
556
667
  /** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
557
- effort: z4.string().optional(),
668
+ effort: z5.string().optional(),
558
669
  /** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
559
670
  homeRunner: NameSchema.optional(),
560
- memberships: z4.array(NameSchema),
561
- subscriptions: z4.array(ChannelRefSchema),
671
+ memberships: z5.array(NameSchema),
672
+ subscriptions: z5.array(ChannelRefSchema),
562
673
  status: AgentStatusSchema,
563
674
  createdAt: IsoDateTimeSchema,
564
- tokenHash: z4.string().min(1),
675
+ tokenHash: z5.string().min(1),
565
676
  retiredAt: IsoDateTimeSchema.optional(),
566
- retiredReason: z4.string().optional()
677
+ retiredReason: z5.string().optional()
567
678
  });
568
- var MemberSchema = z4.object({
679
+ var MemberSchema = z5.object({
569
680
  name: NameSchema,
570
681
  role: NameSchema,
571
682
  cli: CliKindSchema.nullable(),
572
683
  homeRunner: NameSchema.optional(),
573
684
  status: AgentStatusSchema,
574
- resident: z4.boolean().default(false),
685
+ resident: z5.boolean().default(false),
575
686
  /** The model configured on the record, if any; the CLI's own default applies otherwise. */
576
- model: z4.string().optional(),
687
+ model: z5.string().optional(),
577
688
  /** The model the CLI reported on the citizen's last turn. */
578
- lastModel: z4.string().optional(),
689
+ lastModel: z5.string().optional(),
579
690
  /** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
580
- effort: z4.string().optional(),
691
+ effort: z5.string().optional(),
581
692
  /** 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),
693
+ skills: z5.array(NameSchema).default([]),
694
+ memberships: z5.array(NameSchema),
695
+ subscriptions: z5.array(ChannelRefSchema),
696
+ claimsHeld: z5.number().int().nonnegative().default(0),
697
+ tasksDone: z5.number().int().nonnegative().default(0),
587
698
  lastTurnAt: IsoDateTimeSchema.optional(),
588
- lastTurnOutcome: z4.string().optional(),
699
+ lastTurnOutcome: z5.string().optional(),
589
700
  createdAt: IsoDateTimeSchema,
590
701
  retiredAt: IsoDateTimeSchema.optional()
591
702
  });
592
- var RunnerOsSchema = z4.enum(["linux", "windows", "darwin"]);
593
- var RunnerSchema = z4.object({
703
+ var RunnerOsSchema = z5.enum(["linux", "windows", "darwin"]);
704
+ var GitIdentityPartSchema = z5.enum(["name", "email"]);
705
+ var RunnerSchema = z5.object({
594
706
  name: NameSchema,
595
707
  os: RunnerOsSchema,
596
- clis: z4.array(CliKindSchema),
597
- capabilities: z4.array(z4.string()),
708
+ clis: z5.array(CliKindSchema),
709
+ capabilities: z5.array(z5.string()),
598
710
  /** 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()
711
+ version: z5.string().optional(),
712
+ /**
713
+ * What the runner's account lacked of a git identity when it last registered, empty when it had
714
+ * both; absent while the runner has not said, as one from before runners checked never does.
715
+ */
716
+ gitIdentityMissing: z5.array(GitIdentityPartSchema).optional(),
717
+ status: z5.enum(["connected", "disconnected"]),
718
+ lastSeen: IsoDateTimeSchema.optional(),
719
+ /**
720
+ * Each CLI's plan quota as its login on this runner last reported it, from a turn's stream or a
721
+ * read; absent until one has.
722
+ */
723
+ quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional()
602
724
  });
603
- var ProposalKindSchema = z4.enum([
725
+ var ProposalKindSchema = z5.enum([
604
726
  "role",
605
727
  "member",
606
728
  "channel",
@@ -609,42 +731,42 @@ var ProposalKindSchema = z4.enum([
609
731
  "skill",
610
732
  "archive"
611
733
  ]);
612
- var ProposalStatusSchema = z4.enum([
734
+ var ProposalStatusSchema = z5.enum([
613
735
  "proposed",
614
736
  "approved",
615
737
  "rejected",
616
738
  "provisioned",
617
739
  "retired"
618
740
  ]);
619
- var MemberProposalSchema = z4.object({
741
+ var MemberProposalSchema = z5.object({
620
742
  name: NameSchema,
621
743
  role: NameSchema,
622
744
  cli: CliKindSchema,
623
- model: z4.string().optional(),
745
+ model: z5.string().optional(),
624
746
  homeRunner: NameSchema.optional(),
625
- memberships: z4.array(NameSchema).default([]),
626
- subscriptions: z4.array(ChannelRefSchema).default([]),
627
- seedInstructions: z4.string().optional()
747
+ memberships: z5.array(NameSchema).default([]),
748
+ subscriptions: z5.array(ChannelRefSchema).default([]),
749
+ seedInstructions: z5.string().optional()
628
750
  });
629
- var ChannelProposalSchema = z4.object({
751
+ var ChannelProposalSchema = z5.object({
630
752
  project: NameSchema.nullable().default(null),
631
753
  name: NameSchema,
632
- purpose: z4.string().min(1)
754
+ purpose: z5.string().min(1)
633
755
  });
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()
756
+ var RoleChangesSchema = z5.strictObject({
757
+ maxReplicas: z5.number().int().min(1).optional(),
758
+ backlogThreshold: z5.number().positive().optional(),
759
+ wakeTriggers: z5.array(CharterTriggerSchema).optional(),
760
+ verbs: z5.array(VerbNameSchema).optional()
639
761
  }).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
640
762
  message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
641
763
  });
642
- var ReallocationProposalSchema = z4.object({
764
+ var ReallocationProposalSchema = z5.object({
643
765
  role: NameSchema,
644
766
  changes: RoleChangesSchema,
645
- description: z4.string().min(1)
767
+ description: z5.string().min(1)
646
768
  });
647
- var LegacyReallocationSchema = z4.object({ description: z4.string().min(1) });
769
+ var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
648
770
  function applyRoleChanges(role, changes) {
649
771
  return {
650
772
  ...role,
@@ -679,18 +801,18 @@ function roleChangeLines(role, changes) {
679
801
  }
680
802
  return lines;
681
803
  }
682
- var RetirementProposalSchema = z4.object({
804
+ var RetirementProposalSchema = z5.object({
683
805
  agent: NameSchema,
684
- reason: z4.string().min(1)
806
+ reason: z5.string().min(1)
685
807
  });
686
- var ArchiveProposalSchema = z4.object({
808
+ var ArchiveProposalSchema = z5.object({
687
809
  project: NameSchema,
688
- reason: z4.string().min(1)
810
+ reason: z5.string().min(1)
689
811
  });
690
- var SkillProposalSchema = z4.object({
812
+ var SkillProposalSchema = z5.object({
691
813
  name: NameSchema,
692
- summary: z4.string().min(1).max(200),
693
- body: z4.string().min(1).optional()
814
+ summary: z5.string().min(1).max(200),
815
+ body: z5.string().min(1).optional()
694
816
  });
695
817
  var ProposalCharterSchemas = {
696
818
  role: RoleCharterSchema,
@@ -793,23 +915,23 @@ function describeCharter(kind, charter) {
793
915
  }
794
916
  return `${kind} ${JSON.stringify(charter)}`;
795
917
  }
796
- var SkillSchema = z4.object({
918
+ var SkillSchema = z5.object({
797
919
  name: NameSchema,
798
- summary: z4.string().default(""),
799
- scope: z4.enum(["own", "society"]),
800
- path: z4.string().min(1)
920
+ summary: z5.string().default(""),
921
+ scope: z5.enum(["own", "society"]),
922
+ path: z5.string().min(1)
801
923
  });
802
- var RemovedSkillSchema = z4.object({
924
+ var RemovedSkillSchema = z5.object({
803
925
  name: NameSchema,
804
- reason: z4.string().min(1),
926
+ reason: z5.string().min(1),
805
927
  removedBy: NameSchema,
806
928
  removedAt: IsoDateTimeSchema
807
929
  });
808
- var HomeConflictSchema = z4.object({
930
+ var HomeConflictSchema = z5.object({
809
931
  /** The copy, relative to the home. */
810
- path: z4.string().min(1),
932
+ path: z5.string().min(1),
811
933
  /** The file beside it, whose version the board kept. */
812
- file: z4.string().min(1),
934
+ file: z5.string().min(1),
813
935
  /** When the copy reached the board. */
814
936
  since: IsoDateTimeSchema
815
937
  });
@@ -817,79 +939,79 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
817
939
  function conflictCopyOf(copy) {
818
940
  return CONFLICT_COPY.exec(copy)?.[1] ?? null;
819
941
  }
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"]),
942
+ var CommitIdSchema = z5.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
943
+ var HomeFileChangeSchema = z5.object({
944
+ path: z5.string().min(1),
945
+ status: z5.enum(["added", "modified", "deleted"]),
824
946
  /** Lines added and removed, or null for a binary file. */
825
- added: z4.number().int().nonnegative().nullable(),
826
- removed: z4.number().int().nonnegative().nullable()
947
+ added: z5.number().int().nonnegative().nullable(),
948
+ removed: z5.number().int().nonnegative().nullable()
827
949
  });
828
- var HomeChangeSchema = z4.object({
950
+ var HomeChangeSchema = z5.object({
829
951
  commit: CommitIdSchema,
830
952
  at: IsoDateTimeSchema,
831
- kind: z4.enum(["turn", "board", "merge"]),
832
- author: z4.string(),
953
+ kind: z5.enum(["turn", "board", "merge"]),
954
+ author: z5.string(),
833
955
  /** The turn that made it, from the runner's commit message. */
834
956
  turnId: UlidSchema.optional(),
835
- subject: z4.string(),
836
- files: z4.array(HomeFileChangeSchema)
957
+ subject: z5.string(),
958
+ files: z5.array(HomeFileChangeSchema)
837
959
  });
838
- var HomeHistorySchema = z4.object({
839
- changes: z4.array(HomeChangeSchema),
960
+ var HomeHistorySchema = z5.object({
961
+ changes: z5.array(HomeChangeSchema),
840
962
  /** Whether older commits remain past the ones read. */
841
- more: z4.boolean()
963
+ more: z5.boolean()
842
964
  });
843
965
  var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
844
- patch: z4.string().nullable(),
966
+ patch: z5.string().nullable(),
845
967
  /** The patch was cut at its size limit. */
846
- truncated: z4.boolean()
968
+ truncated: z5.boolean()
847
969
  });
848
- var KnowledgeSchema = z4.object({
970
+ var KnowledgeSchema = z5.object({
849
971
  topic: NameSchema,
850
972
  project: NameSchema.nullable(),
851
- revision: z4.number().int().min(0),
973
+ revision: z5.number().int().min(0),
852
974
  updatedBy: NameSchema,
853
975
  updatedAt: IsoDateTimeSchema
854
976
  });
855
- var RemovedKnowledgeSchema = z4.object({
977
+ var RemovedKnowledgeSchema = z5.object({
856
978
  topic: NameSchema,
857
979
  project: NameSchema.nullable(),
858
980
  removedBy: NameSchema,
859
981
  removedAt: IsoDateTimeSchema
860
982
  });
861
- var DashboardSchema = z4.object({
983
+ var DashboardSchema = z5.object({
862
984
  project: NameSchema,
863
- revision: z4.number().int().min(0),
985
+ revision: z5.number().int().min(0),
864
986
  /** Absent until someone updates the dashboard the project began with. */
865
987
  updatedBy: NameSchema.optional(),
866
988
  updatedAt: IsoDateTimeSchema.optional(),
867
- body: z4.string()
989
+ body: z5.string()
868
990
  });
869
- var ProposalOriginSchema = z4.object({
991
+ var ProposalOriginSchema = z5.object({
870
992
  scope: NameSchema,
871
993
  thread: UlidSchema.optional(),
872
994
  channel: NameSchema.optional()
873
995
  });
874
996
  var SKILL_FILES_LIMIT = 100;
875
997
  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()
998
+ var SkillFileSchema = z5.object({
999
+ path: z5.string().min(1),
1000
+ size: z5.number().int().nonnegative(),
1001
+ executable: z5.boolean()
880
1002
  });
881
- var SkillFilesSchema = z4.object({
1003
+ var SkillFilesSchema = z5.object({
882
1004
  /** The commit of the proposer's home the files came from. */
883
1005
  commit: CommitIdSchema,
884
1006
  takenAt: IsoDateTimeSchema,
885
- files: z4.array(SkillFileSchema)
1007
+ files: z5.array(SkillFileSchema)
886
1008
  });
887
1009
  var SkillFileContentSchema = SkillFileSchema.extend({
888
- text: z4.string().nullable(),
1010
+ text: z5.string().nullable(),
889
1011
  /** The text was cut at its size limit. */
890
- truncated: z4.boolean()
1012
+ truncated: z5.boolean()
891
1013
  });
892
- var ProposalFrontmatterSchema = z4.object({
1014
+ var ProposalFrontmatterSchema = z5.object({
893
1015
  id: UlidSchema,
894
1016
  kind: ProposalKindSchema,
895
1017
  proposedBy: NameSchema,
@@ -901,146 +1023,191 @@ var ProposalFrontmatterSchema = z4.object({
901
1023
  skillFiles: SkillFilesSchema.optional(),
902
1024
  decidedBy: NameSchema.optional(),
903
1025
  decidedAt: IsoDateTimeSchema.optional(),
904
- reason: z4.string().optional(),
905
- charter: z4.record(z4.string(), z4.unknown()),
1026
+ reason: z5.string().optional(),
1027
+ charter: z5.record(z5.string(), z5.unknown()),
906
1028
  /** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
907
1029
  provisionedAt: IsoDateTimeSchema.optional(),
908
- provision: z4.record(z4.string(), z4.unknown()).optional()
1030
+ provision: z5.record(z5.string(), z5.unknown()).optional()
909
1031
  });
910
- var DecisionSchema = z4.object({
1032
+ var DecisionSchema = z5.object({
911
1033
  id: UlidSchema,
912
1034
  proposalId: UlidSchema,
913
1035
  decidedBy: NameSchema,
914
- outcome: z4.enum(["approved", "rejected"]),
915
- reason: z4.string().optional(),
1036
+ outcome: z5.enum(["approved", "rejected"]),
1037
+ reason: z5.string().optional(),
916
1038
  ts: IsoDateTimeSchema
917
1039
  });
918
1040
 
919
1041
  // 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);
1042
+ import { z as z7 } from "zod";
1043
+
1044
+ // packages/shared/dist/images.js
1045
+ import { z as z6 } from "zod";
1046
+ var IMAGE_LIMIT_BYTES = 8 * 1024 * 1024;
1047
+ var ImageTypeSchema = z6.enum(["png", "jpeg", "gif", "webp"]);
1048
+ var IMAGE_MEDIA_TYPES = {
1049
+ png: "image/png",
1050
+ jpeg: "image/jpeg",
1051
+ gif: "image/gif",
1052
+ webp: "image/webp"
1053
+ };
1054
+ function startsWith(bytes, prefix, at = 0) {
1055
+ for (let index = 0; index < prefix.length; index += 1) {
1056
+ if (bytes[at + index] !== prefix[index]) {
1057
+ return false;
1058
+ }
1059
+ }
1060
+ return true;
1061
+ }
1062
+ var ascii = (text) => new TextEncoder().encode(text);
1063
+ function imageTypeOf(bytes) {
1064
+ if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
1065
+ return "png";
1066
+ }
1067
+ if (startsWith(bytes, [255, 216, 255])) {
1068
+ return "jpeg";
1069
+ }
1070
+ if (startsWith(bytes, ascii("GIF87a")) || startsWith(bytes, ascii("GIF89a"))) {
1071
+ return "gif";
1072
+ }
1073
+ if (startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)) {
1074
+ return "webp";
1075
+ }
1076
+ return null;
1077
+ }
1078
+ var ImagePathSchema = z6.string().min(1).max(4096).refine((value) => !value.includes("\0"), { message: "a path with no NUL character" });
1079
+ var IMAGE_SCHEME = "image:";
1080
+ var UploadedImageSchema = z6.object({
1081
+ id: UlidSchema,
1082
+ type: ImageTypeSchema,
1083
+ size: z6.number().int().positive(),
1084
+ markdown: z6.string()
1085
+ });
1086
+
1087
+ // packages/shared/dist/verbs.js
1088
+ var MomentSchema = z7.iso.datetime({ offset: true });
1089
+ var CronTitleSchema = z7.string().trim().min(1).max(120);
1090
+ var CronNoteSchema = z7.string().trim().min(1).max(4e3);
924
1091
  var VerbInputs = {
925
- post_message: z5.object({
1092
+ post_message: z7.object({
926
1093
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
927
1094
  channel: ChannelRefSchema.optional(),
928
- body: z5.string().min(1),
1095
+ body: z7.string().min(1),
929
1096
  thread_id: UlidSchema.optional()
930
1097
  }),
931
- read_inbox: z5.object({
1098
+ read_inbox: z7.object({
932
1099
  since_cursor: UlidSchema.nullable().optional(),
933
- limit: z5.number().int().positive().max(200).default(50),
934
- advance: z5.boolean().default(true)
1100
+ limit: z7.number().int().positive().max(200).default(50),
1101
+ advance: z7.boolean().default(true)
935
1102
  }),
936
- search: z5.object({
937
- query: z5.string().min(1),
1103
+ search: z7.object({
1104
+ query: z7.string().min(1),
938
1105
  project: NameSchema.optional(),
939
1106
  channel: ChannelRefSchema.optional(),
940
- limit: z5.number().int().positive().max(100).default(20)
1107
+ limit: z7.number().int().positive().max(100).default(20)
941
1108
  }),
942
1109
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
943
- open_thread: z5.object({
1110
+ open_thread: z7.object({
944
1111
  task_id: UlidSchema.optional(),
945
1112
  proposal_id: UlidSchema.optional(),
946
1113
  channel: ChannelRefSchema.optional(),
947
- title: z5.string().min(1).max(200).optional()
1114
+ title: z7.string().min(1).max(200).optional()
948
1115
  }),
949
- close_thread: z5.object({ thread_id: UlidSchema, summary: z5.string().min(1) }),
950
- create_task: z5.object({
1116
+ close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
1117
+ create_task: z7.object({
951
1118
  project: NameSchema,
952
- title: z5.string().min(1).max(200),
953
- body: z5.string().default(""),
1119
+ title: z7.string().min(1).max(200),
1120
+ body: z7.string().default(""),
954
1121
  parent_id: UlidSchema.optional(),
955
- required_capabilities: z5.array(z5.string()).default([]),
1122
+ required_capabilities: z7.array(z7.string()).default([]),
956
1123
  /** 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(),
1124
+ stages: z7.array(PlanStageSchema).min(1).optional(),
958
1125
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
959
1126
  channel: NameSchema.default("general")
960
1127
  }),
961
- claim_task: z5.object({ task_id: UlidSchema }),
962
- release_task: z5.object({ task_id: UlidSchema }),
963
- update_task: z5.object({
1128
+ claim_task: z7.object({ task_id: UlidSchema }),
1129
+ release_task: z7.object({ task_id: UlidSchema }),
1130
+ update_task: z7.object({
964
1131
  task_id: UlidSchema,
965
1132
  /** Move the task back to this earlier stage. */
966
1133
  stage: StageIdSchema.optional(),
967
- status: z5.enum(["abandoned"]).optional(),
1134
+ status: z7.enum(["abandoned"]).optional(),
968
1135
  /** 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(),
1136
+ note: z7.string().min(1).optional(),
1137
+ blocked_by: z7.array(UlidSchema).optional(),
971
1138
  /**
972
1139
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
973
1140
  * commit's subject and body; null unlinks it.
974
1141
  */
975
- pull_request: z5.object({
1142
+ pull_request: z7.object({
976
1143
  url: PullRequestUrlSchema,
977
1144
  merge_subject: MergeSubjectSchema.optional(),
978
- merge_body: z5.string().max(2e4).optional()
1145
+ merge_body: z7.string().max(2e4).optional()
979
1146
  }).nullable().optional()
980
1147
  }),
981
- get_task: z5.object({ task_id: UlidSchema }),
982
- subscribe: z5.object({ channel: ChannelRefSchema }),
983
- unsubscribe: z5.object({ channel: ChannelRefSchema }),
984
- propose: z5.object({
1148
+ get_task: z7.object({ task_id: UlidSchema }),
1149
+ subscribe: z7.object({ channel: ChannelRefSchema }),
1150
+ unsubscribe: z7.object({ channel: ChannelRefSchema }),
1151
+ propose: z7.object({
985
1152
  kind: ProposalKindSchema,
986
- charter: z5.record(z5.string(), z5.unknown()),
987
- rationale: z5.string().default("")
1153
+ charter: z7.record(z7.string(), z7.unknown()),
1154
+ rationale: z7.string().default("")
988
1155
  }),
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({
1156
+ approve: z7.object({ proposal_id: UlidSchema, reason: z7.string().optional() }),
1157
+ reject: z7.object({ proposal_id: UlidSchema, reason: z7.string().min(1) }),
1158
+ create_project: z7.object({
992
1159
  slug: NameSchema,
993
- name: z5.string().min(1).optional(),
994
- repo: z5.string().min(1).nullable().default(null),
1160
+ name: z7.string().min(1).optional(),
1161
+ repo: z7.string().min(1).nullable().default(null),
995
1162
  default_branch: DefaultBranchSchema.default("main"),
996
1163
  on_done: CompletionEffectSchema.optional()
997
1164
  }),
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({
1165
+ join_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
1166
+ leave_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
1167
+ write_knowledge: z7.object({
1001
1168
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
1002
1169
  project: NameSchema.nullable().default(null),
1003
1170
  topic: NameSchema,
1004
- body: z5.string().min(1),
1171
+ body: z7.string().min(1),
1005
1172
  /** 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)
1173
+ revision: z7.number().int().min(0)
1007
1174
  }),
1008
- remove_knowledge: z5.object({
1175
+ remove_knowledge: z7.object({
1009
1176
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
1010
1177
  project: NameSchema.nullable().default(null),
1011
1178
  topic: NameSchema
1012
1179
  }),
1013
- plan_task: z5.object({
1180
+ plan_task: z7.object({
1014
1181
  task_id: UlidSchema,
1015
- stages: z5.array(PlanEditStageSchema),
1182
+ stages: z7.array(PlanEditStageSchema),
1016
1183
  on_done: CompletionEffectSchema.optional()
1017
1184
  }),
1018
1185
  /** 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({
1186
+ advance_task: z7.object({ task_id: UlidSchema, note: z7.string().min(1).optional() }),
1187
+ configure_project: z7.object({
1021
1188
  project: NameSchema,
1022
1189
  on_done: CompletionEffectSchema.optional(),
1023
1190
  /** 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(),
1191
+ name: z7.string().trim().min(1).max(120).optional(),
1025
1192
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
1026
1193
  default_branch: DefaultBranchSchema.optional()
1027
1194
  }),
1028
- archive_project: z5.object({ project: NameSchema, reason: z5.string().min(1) }),
1029
- create_channel: z5.object({
1195
+ archive_project: z7.object({ project: NameSchema, reason: z7.string().min(1) }),
1196
+ create_channel: z7.object({
1030
1197
  /** A project, or null for a channel of the society. */
1031
1198
  project: NameSchema.nullable().default(null),
1032
1199
  name: NameSchema,
1033
- purpose: z5.string().min(1)
1200
+ purpose: z7.string().min(1)
1034
1201
  }),
1035
- archive_channel: z5.object({ channel: ChannelRefSchema, reason: z5.string().min(1) }),
1036
- update_dashboard: z5.object({
1202
+ archive_channel: z7.object({ channel: ChannelRefSchema, reason: z7.string().min(1) }),
1203
+ update_dashboard: z7.object({
1037
1204
  project: NameSchema,
1038
1205
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
1039
- body: z5.string().min(1),
1206
+ body: z7.string().min(1),
1040
1207
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
1041
- revision: z5.number().int().min(0)
1208
+ revision: z7.number().int().min(0)
1042
1209
  }),
1043
- create_cron: z5.object({
1210
+ create_cron: z7.object({
1044
1211
  title: CronTitleSchema,
1045
1212
  /** What to do when it fires; quoted in the prompt of every turn it starts. */
1046
1213
  note: CronNoteSchema,
@@ -1059,10 +1226,10 @@ var VerbInputs = {
1059
1226
  /** An open channel of that place other than general, whose conversation it fires in. */
1060
1227
  channel: NameSchema.optional()
1061
1228
  }),
1062
- update_cron: z5.object({
1229
+ update_cron: z7.object({
1063
1230
  cron_id: UlidSchema,
1064
1231
  /** Pause it, or resume it, which starts its schedule afresh from now. */
1065
- paused: z5.boolean().optional(),
1232
+ paused: z7.boolean().optional(),
1066
1233
  title: CronTitleSchema.optional(),
1067
1234
  note: CronNoteSchema.optional(),
1068
1235
  /** A new expression, or `at` for one time; either starts the schedule afresh. */
@@ -1071,8 +1238,12 @@ var VerbInputs = {
1071
1238
  timezone: TimeZoneSchema.nullable().optional(),
1072
1239
  at: MomentSchema.optional()
1073
1240
  }),
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) })
1241
+ remove_cron: z7.object({ cron_id: UlidSchema, reason: z7.string().trim().min(1) }),
1242
+ remove_skill: z7.object({ name: NameSchema, reason: z7.string().trim().min(1) }),
1243
+ upload_image: z7.object({
1244
+ /** The image file on the machine your turn runs on: absolute, or relative to your working directory. */
1245
+ path: ImagePathSchema
1246
+ })
1076
1247
  };
1077
1248
  var VERB_DESCRIPTIONS = {
1078
1249
  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.",
@@ -1083,7 +1254,7 @@ var VERB_DESCRIPTIONS = {
1083
1254
  create_task: "Create a task in a project with its plan: stages of {name, role or agent, gate}. Without stages it gets one stage, work, that anyone in the project may take. File it in a channel of the project, such as a release's, or general by default; its thread opens there with it, under the task's id, for everything said about the work.",
1084
1255
  claim_task: "Hold the task's current stage. Claims are leases renewed by every turn that touches the task.",
1085
1256
  release_task: "Let go of the stage you hold so someone else can take it.",
1086
- update_task: "Move a task back to an earlier stage, abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
1257
+ update_task: "Move a task back to an earlier stage (the current stage's holder, the user, the steward, or the concierge; anyone else asks the concierge in the task's thread), abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
1087
1258
  get_task: "Read a task with its plan, its brief, and the messages of its thread.",
1088
1259
  subscribe: "Subscribe to a channel. Subscriptions feed your digest; they never wake you.",
1089
1260
  unsubscribe: "Unsubscribe from a channel.",
@@ -1105,11 +1276,12 @@ var VERB_DESCRIPTIONS = {
1105
1276
  create_cron: "Set a cron that wakes you later: a title, a note on what to do then, and either cron, a five-field expression (minute hour day-of-month month day-of-week) read in timezone or the society's, or at, one time. It fires in this turn's conversation unless you name a project, thread_id, or channel, at most every 15 minutes, and every fire is a turn. Only the user, the steward, and the concierge set one that wakes another citizen (agent).",
1106
1277
  update_cron: "Pause or resume a cron, or change its title, note, or schedule; resuming or rescheduling starts it afresh from now. Yours, or anyone's for the user, the steward, and the concierge.",
1107
1278
  remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires.",
1108
- remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore."
1279
+ remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore.",
1280
+ upload_image: "Upload an image file, such as a screenshot or a chart, from the machine your turn runs on: a PNG, JPEG, GIF, or WebP of at most 8 MB, in your worktree, your home, or the system temp folder. Returns its id and the markdown that shows it in a post, `![what it shows](image:<id>)`; write what it shows as the alt text."
1109
1281
  };
1110
1282
 
1111
1283
  // packages/shared/dist/events.js
1112
- import { z as z6 } from "zod";
1284
+ import { z as z8 } from "zod";
1113
1285
  var BOARD_EVENT_TYPES = [
1114
1286
  "society.initialized",
1115
1287
  "project.added",
@@ -1154,6 +1326,7 @@ var BOARD_EVENT_TYPES = [
1154
1326
  "user.signed_out",
1155
1327
  "user.token_rotated",
1156
1328
  "runner.changed",
1329
+ "runner.quota",
1157
1330
  "ops.signal",
1158
1331
  "paused.changed",
1159
1332
  "wake.requested",
@@ -1172,30 +1345,39 @@ var BOARD_EVENT_TYPES = [
1172
1345
  "cron.updated",
1173
1346
  "cron.fired",
1174
1347
  "cron.skipped",
1175
- "cron.ended"
1348
+ "cron.ended",
1349
+ "image.uploaded"
1176
1350
  ];
1177
- var BoardEventTypeSchema = z6.enum(BOARD_EVENT_TYPES);
1178
- var BoardEventSchema = z6.object({
1351
+ var BoardEventTypeSchema = z8.enum(BOARD_EVENT_TYPES);
1352
+ var BoardEventSchema = z8.object({
1179
1353
  id: UlidSchema,
1180
1354
  ts: IsoDateTimeSchema,
1181
1355
  type: BoardEventTypeSchema,
1182
1356
  actor: NameSchema,
1183
- payload: z6.record(z6.string(), z6.unknown())
1357
+ payload: z8.record(z8.string(), z8.unknown())
1184
1358
  });
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)
1359
+ var UsageSchema = z8.object({
1360
+ inputTokens: z8.number().int().nonnegative(),
1361
+ outputTokens: z8.number().int().nonnegative(),
1362
+ cacheReadTokens: z8.number().int().nonnegative().default(0),
1363
+ cacheWriteTokens: z8.number().int().nonnegative().default(0)
1190
1364
  });
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)
1365
+ function addUsage(a, b) {
1366
+ return {
1367
+ inputTokens: a.inputTokens + b.inputTokens,
1368
+ outputTokens: a.outputTokens + b.outputTokens,
1369
+ cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
1370
+ cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
1371
+ };
1372
+ }
1373
+ var TurnStatusSchema = z8.object({
1374
+ summary: z8.string().min(1),
1375
+ claimsHeld: z8.array(UlidSchema).default([]),
1376
+ blockedOn: z8.array(z8.string()).default([]),
1377
+ needsUserDecision: z8.boolean().default(false),
1378
+ memoryUpdated: z8.boolean().default(false)
1197
1379
  });
1198
- var TurnExitReasonSchema = z6.enum([
1380
+ var TurnExitReasonSchema = z8.enum([
1199
1381
  "completed",
1200
1382
  "timeout",
1201
1383
  "error",
@@ -1203,50 +1385,50 @@ var TurnExitReasonSchema = z6.enum([
1203
1385
  "blocked",
1204
1386
  "stopped"
1205
1387
  ]);
1206
- var AgentEventSchema = z6.discriminatedUnion("type", [
1207
- z6.object({
1208
- type: z6.literal("turn_started"),
1388
+ var AgentEventSchema = z8.discriminatedUnion("type", [
1389
+ z8.object({
1390
+ type: z8.literal("turn_started"),
1209
1391
  agent: NameSchema,
1210
- session: z6.string(),
1392
+ session: z8.string(),
1211
1393
  runner: NameSchema,
1212
1394
  /** The model the CLI reports running, when it reports one. */
1213
- model: z6.string().optional()
1395
+ model: z8.string().optional()
1214
1396
  }),
1215
1397
  /**
1216
1398
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
1217
1399
  * messages it delivered and the text the CLI was given.
1218
1400
  */
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()
1401
+ z8.object({
1402
+ type: z8.literal("steered"),
1403
+ steer: z8.string().min(1),
1404
+ messages: z8.array(UlidSchema).optional(),
1405
+ text: z8.string().optional()
1224
1406
  }),
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(),
1407
+ z8.object({ type: z8.literal("text"), delta: z8.string() }),
1408
+ z8.object({ type: z8.literal("tool_call"), name: z8.string(), input: z8.unknown() }),
1409
+ z8.object({
1410
+ type: z8.literal("tool_result"),
1411
+ name: z8.string(),
1412
+ ok: z8.boolean(),
1231
1413
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
1232
- output: z6.string().optional()
1414
+ output: z8.string().optional()
1233
1415
  }),
1234
- z6.object({ type: z6.literal("approval_requested"), kind: z6.string(), detail: z6.unknown() }),
1235
- z6.object({
1236
- type: z6.literal("turn_completed"),
1416
+ z8.object({ type: z8.literal("approval_requested"), kind: z8.string(), detail: z8.unknown() }),
1417
+ z8.object({
1418
+ type: z8.literal("turn_completed"),
1237
1419
  usage: UsageSchema,
1238
- costUsd: z6.number().nonnegative(),
1420
+ costUsd: z8.number().nonnegative(),
1239
1421
  status: TurnStatusSchema.nullable(),
1240
1422
  exitReason: TurnExitReasonSchema
1241
1423
  }),
1242
- z6.object({ type: z6.literal("error"), message: z6.string() })
1424
+ z8.object({ type: z8.literal("error"), message: z8.string() })
1243
1425
  ]);
1244
- var TranscriptEntrySchema = z6.object({
1426
+ var TranscriptEntrySchema = z8.object({
1245
1427
  ts: IsoDateTimeSchema,
1246
1428
  event: AgentEventSchema
1247
1429
  });
1248
- var LiveTurnEventSchema = z6.object({
1249
- seq: z6.number().int().positive(),
1430
+ var LiveTurnEventSchema = z8.object({
1431
+ seq: z8.number().int().positive(),
1250
1432
  ts: IsoDateTimeSchema,
1251
1433
  agent: NameSchema,
1252
1434
  project: NameSchema,
@@ -1258,10 +1440,10 @@ var LiveTurnEventSchema = z6.object({
1258
1440
  });
1259
1441
 
1260
1442
  // packages/shared/dist/turns.js
1261
- import { z as z8 } from "zod";
1443
+ import { z as z10 } from "zod";
1262
1444
 
1263
1445
  // packages/shared/dist/runner.js
1264
- import { z as z7 } from "zod";
1446
+ import { z as z9 } from "zod";
1265
1447
  var RUNNER_PROTOCOL = 1;
1266
1448
  var PATH_TOKENS = {
1267
1449
  home: "{{stellaris:home}}",
@@ -1272,67 +1454,77 @@ var SERVER_TOKEN = "{{stellaris:server}}";
1272
1454
  var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
1273
1455
  var HOME_SCRATCH = "scratch";
1274
1456
  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),
1457
+ var RunnerHelloSchema = z9.object({
1458
+ protocol: z9.number().int().positive(),
1459
+ version: z9.string().min(1),
1278
1460
  os: RunnerOsSchema,
1279
- clis: z7.array(CliKindSchema),
1461
+ clis: z9.array(CliKindSchema),
1280
1462
  /** The CLIs whose adapter can keep a session warm between turns. */
1281
- residentClis: z7.array(CliKindSchema).default([]),
1463
+ residentClis: z9.array(CliKindSchema).default([]),
1282
1464
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
1283
- steerableClis: z7.array(CliKindSchema).default([]),
1465
+ steerableClis: z9.array(CliKindSchema).default([]),
1284
1466
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
1285
- stoppableClis: z7.array(CliKindSchema).default([]),
1467
+ stoppableClis: z9.array(CliKindSchema).default([]),
1286
1468
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
1287
- branchReads: z7.boolean().default(false),
1469
+ branchReads: z9.boolean().default(false),
1288
1470
  /** 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([]),
1471
+ branchChanges: z9.boolean().default(false),
1472
+ /** Whether the runner answers a read of a CLI's quota; one from before never does. */
1473
+ quotaReads: z9.boolean().default(false),
1474
+ /** Whether the runner reads an image a turn running there uploads; one from before never does. */
1475
+ imageReads: z9.boolean().default(false),
1476
+ capabilities: z9.array(z9.string()).default([]),
1477
+ /**
1478
+ * What the runner's account lacks of the git identity citizens' own commits carry, checked
1479
+ * before each hello outside any repository; empty when it has both. Absent from a runner from
1480
+ * before the check, which no default fills in, so the board marks only what a runner reported.
1481
+ */
1482
+ gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
1291
1483
  /** Turns the runner runs at once, or null for no limit. */
1292
- slots: z7.number().int().positive().nullable(),
1484
+ slots: z9.number().int().positive().nullable(),
1293
1485
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1294
- turns: z7.array(UlidSchema).default([])
1486
+ turns: z9.array(UlidSchema).default([])
1295
1487
  });
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),
1488
+ var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
1489
+ var RunnerEnrollRequestSchema = z9.object({
1490
+ protocol: z9.number().int().positive(),
1491
+ version: z9.string().min(1).max(64),
1492
+ hostname: z9.string().min(1).max(253),
1301
1493
  os: RunnerOsSchema,
1302
- clis: z7.array(CliKindSchema).max(8),
1303
- capabilities: z7.array(z7.string().min(1).max(64)).max(32).default([])
1494
+ clis: z9.array(CliKindSchema).max(8),
1495
+ capabilities: z9.array(z9.string().min(1).max(64)).max(32).default([])
1304
1496
  });
1305
- var RunnerEnrollmentSchema = z7.object({
1306
- deviceCode: z7.string().min(1),
1307
- userCode: z7.string().min(1),
1497
+ var RunnerEnrollmentSchema = z9.object({
1498
+ deviceCode: z9.string().min(1),
1499
+ userCode: z9.string().min(1),
1308
1500
  expiresAt: IsoDateTimeSchema,
1309
- intervalMs: z7.number().int().positive()
1501
+ intervalMs: z9.number().int().positive()
1310
1502
  });
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") })
1503
+ var RunnerEnrollPollSchema = z9.object({ deviceCode: z9.string().min(1) });
1504
+ var RunnerEnrollStatusSchema = z9.discriminatedUnion("status", [
1505
+ z9.object({ status: z9.literal("pending") }),
1506
+ z9.object({ status: z9.literal("approved"), name: NameSchema, token: z9.string().min(1) }),
1507
+ z9.object({ status: z9.literal("denied") })
1316
1508
  ]);
1317
- var PendingEnrollmentSchema = z7.object({
1318
- userCode: z7.string().min(1),
1319
- hostname: z7.string(),
1509
+ var PendingEnrollmentSchema = z9.object({
1510
+ userCode: z9.string().min(1),
1511
+ hostname: z9.string(),
1320
1512
  os: RunnerOsSchema,
1321
- clis: z7.array(CliKindSchema),
1322
- capabilities: z7.array(z7.string()),
1323
- version: z7.string(),
1513
+ clis: z9.array(CliKindSchema),
1514
+ capabilities: z9.array(z9.string()),
1515
+ version: z9.string(),
1324
1516
  requestedAt: IsoDateTimeSchema,
1325
1517
  expiresAt: IsoDateTimeSchema
1326
1518
  });
1327
- var ProjectRepoSchema = z7.object({
1519
+ var ProjectRepoSchema = z9.object({
1328
1520
  slug: NameSchema,
1329
- origin: z7.string().nullable(),
1330
- defaultBranch: z7.string().min(1),
1521
+ origin: z9.string().nullable(),
1522
+ defaultBranch: z9.string().min(1),
1331
1523
  /**
1332
1524
  * The board lands finished tasks on the default branch, itself or through their pull requests,
1333
1525
  * so the runner lets nothing else move it.
1334
1526
  */
1335
- boardLands: z7.boolean()
1527
+ boardLands: z9.boolean()
1336
1528
  });
1337
1529
  function projectRepo(project) {
1338
1530
  return {
@@ -1342,29 +1534,29 @@ function projectRepo(project) {
1342
1534
  boardLands: project.onDone !== "none"
1343
1535
  };
1344
1536
  }
1345
- var TurnWorkspaceSchema = z7.discriminatedUnion("kind", [
1346
- z7.object({
1347
- kind: z7.literal("home"),
1537
+ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
1538
+ z9.object({
1539
+ kind: z9.literal("home"),
1348
1540
  thread: UlidSchema.optional(),
1349
1541
  channel: NameSchema.optional()
1350
1542
  }),
1351
- z7.object({
1352
- kind: z7.literal("project"),
1543
+ z9.object({
1544
+ kind: z9.literal("project"),
1353
1545
  repo: ProjectRepoSchema,
1354
- branches: z7.array(z7.string().min(1)),
1546
+ branches: z9.array(z9.string().min(1)),
1355
1547
  thread: UlidSchema.optional(),
1356
1548
  channel: NameSchema.optional()
1357
1549
  }),
1358
- z7.object({ kind: z7.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1550
+ z9.object({ kind: z9.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1359
1551
  ]);
1360
- var TurnJobSchema = z7.object({
1552
+ var TurnJobSchema = z9.object({
1361
1553
  turnId: UlidSchema,
1362
1554
  agent: NameSchema,
1363
1555
  role: NameSchema,
1364
1556
  cli: CliKindSchema,
1365
- model: z7.string().optional(),
1557
+ model: z9.string().optional(),
1366
1558
  /** The reasoning effort set on the agent; absent for the model's own default. */
1367
- effort: z7.string().optional(),
1559
+ effort: z9.string().optional(),
1368
1560
  /** A project slug, or the society scope. */
1369
1561
  scope: NameSchema,
1370
1562
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1375,125 +1567,130 @@ var TurnJobSchema = z7.object({
1375
1567
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1376
1568
  * the outcome reports it.
1377
1569
  */
1378
- session: z7.string().min(1).nullable(),
1570
+ session: z9.string().min(1).nullable(),
1379
1571
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1380
- costSoFarUsd: z7.number().nonnegative(),
1572
+ costSoFarUsd: z9.number().nonnegative(),
1381
1573
  /** 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()
1574
+ instructions: z9.string(),
1575
+ prompt: z9.string(),
1576
+ mcp: z9.object({ url: z9.string().min(1), token: z9.string().min(1) }),
1577
+ limits: z9.object({
1578
+ timeoutMs: z9.number().int().positive().nullable(),
1579
+ maxTurns: z9.number().int().positive().nullable()
1388
1580
  }),
1389
1581
  /** 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(),
1582
+ resident: z9.object({ key: z9.string().min(1), idleMs: z9.number().int().positive() }).optional(),
1391
1583
  workspace: TurnWorkspaceSchema
1392
1584
  });
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({
1585
+ var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
1586
+ var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
1587
+ var TurnOutcomeSchema = z9.object({
1396
1588
  exitReason: TurnExitReasonSchema,
1397
1589
  status: TurnStatusSchema.nullable(),
1398
- error: z7.string().nullable(),
1590
+ error: z9.string().nullable(),
1399
1591
  usage: UsageSchema,
1400
- costUsd: z7.number().nonnegative(),
1592
+ costUsd: z9.number().nonnegative(),
1401
1593
  /** The session's running total after this turn, for CLIs that report one. */
1402
- sessionCostUsd: z7.number().nonnegative().optional(),
1594
+ sessionCostUsd: z9.number().nonnegative().optional(),
1403
1595
  /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1404
- session: z7.string().min(1),
1596
+ session: z9.string().min(1),
1405
1597
  /** The model the CLI reported, when it reported one. */
1406
- model: z7.string().nullable(),
1598
+ model: z9.string().nullable(),
1407
1599
  work: TurnWorkSchema.nullable(),
1408
1600
  /**
1409
1601
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1410
1602
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1411
1603
  */
1412
- leftovers: z7.boolean().default(false)
1604
+ leftovers: z9.boolean().default(false),
1605
+ /**
1606
+ * The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
1607
+ * before never sends it.
1608
+ */
1609
+ quota: CliQuotaSchema.optional()
1413
1610
  });
1414
- var TurnAckSchema = z7.object({ dropWorkspace: z7.boolean() });
1415
- var LandRequestSchema = z7.object({
1611
+ var TurnAckSchema = z9.object({ dropWorkspace: z9.boolean() });
1612
+ var LandRequestSchema = z9.object({
1416
1613
  repo: ProjectRepoSchema,
1417
- branch: z7.string().min(1),
1614
+ branch: z9.string().min(1),
1418
1615
  /**
1419
1616
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1420
1617
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1421
1618
  * empty body when left out.
1422
1619
  */
1423
- pullRequest: z7.object({
1424
- url: z7.string().min(1),
1425
- subject: z7.string().min(1).optional(),
1426
- body: z7.string().optional()
1620
+ pullRequest: z9.object({
1621
+ url: z9.string().min(1),
1622
+ subject: z9.string().min(1).optional(),
1623
+ body: z9.string().optional()
1427
1624
  }).optional()
1428
1625
  });
1429
- var CloseRequestSchema = z7.object({
1626
+ var CloseRequestSchema = z9.object({
1430
1627
  repo: ProjectRepoSchema,
1431
- url: z7.string().min(1),
1432
- comment: z7.string().min(1)
1628
+ url: z9.string().min(1),
1629
+ comment: z9.string().min(1)
1433
1630
  });
1434
- var MergeOutcomeSchema = z7.object({ ok: z7.boolean(), detail: z7.string() });
1435
- var ModelListSchema = z7.array(ModelOptionSchema);
1631
+ var MergeOutcomeSchema = z9.object({ ok: z9.boolean(), detail: z9.string() });
1632
+ var ModelListSchema = z9.array(ModelOptionSchema);
1436
1633
  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({
1634
+ var BranchPathSchema = z9.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" });
1635
+ var BranchNameSchema = z9.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1636
+ var BranchReadSchema = z9.object({
1440
1637
  repo: ProjectRepoSchema,
1441
1638
  branch: BranchNameSchema,
1442
1639
  path: BranchPathSchema
1443
1640
  });
1444
- var BranchCommitSchema = z7.object({
1445
- id: z7.string().min(1),
1641
+ var BranchCommitSchema = z9.object({
1642
+ id: z9.string().min(1),
1446
1643
  at: IsoDateTimeSchema,
1447
- author: z7.string()
1644
+ author: z9.string()
1448
1645
  });
1449
- var BranchFileSchema = z7.discriminatedUnion("kind", [
1450
- z7.object({
1451
- kind: z7.literal("file"),
1646
+ var BranchFileSchema = z9.discriminatedUnion("kind", [
1647
+ z9.object({
1648
+ kind: z9.literal("file"),
1452
1649
  path: BranchPathSchema,
1453
1650
  commit: BranchCommitSchema,
1454
- size: z7.number().int().nonnegative(),
1455
- content: z7.string().nullable()
1651
+ size: z9.number().int().nonnegative(),
1652
+ content: z9.string().nullable()
1456
1653
  }),
1457
- z7.object({
1458
- kind: z7.literal("dir"),
1654
+ z9.object({
1655
+ kind: z9.literal("dir"),
1459
1656
  path: BranchPathSchema,
1460
1657
  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()
1658
+ entries: z9.array(z9.object({
1659
+ name: z9.string().min(1),
1660
+ kind: z9.enum(["file", "dir"]),
1661
+ size: z9.number().int().nonnegative().nullable()
1465
1662
  }))
1466
1663
  })
1467
1664
  ]);
1468
- var BranchChangesReadSchema = z7.object({
1665
+ var BranchChangesReadSchema = z9.object({
1469
1666
  repo: ProjectRepoSchema,
1470
1667
  branch: BranchNameSchema
1471
1668
  });
1472
- var BranchFileChangeSchema = z7.object({
1473
- path: z7.string().min(1),
1474
- status: z7.enum(["added", "modified", "deleted"]),
1669
+ var BranchFileChangeSchema = z9.object({
1670
+ path: z9.string().min(1),
1671
+ status: z9.enum(["added", "modified", "deleted"]),
1475
1672
  /** Lines added and removed, or null for a binary file. */
1476
- added: z7.number().int().nonnegative().nullable(),
1477
- removed: z7.number().int().nonnegative().nullable(),
1673
+ added: z9.number().int().nonnegative().nullable(),
1674
+ removed: z9.number().int().nonnegative().nullable(),
1478
1675
  /** Absent when the file changed only through a merge of another branch. */
1479
- lastChange: z7.object({ author: z7.string(), at: IsoDateTimeSchema }).optional()
1676
+ lastChange: z9.object({ author: z9.string(), at: IsoDateTimeSchema }).optional()
1480
1677
  });
1481
- var BranchChangesSchema = z7.object({
1678
+ var BranchChangesSchema = z9.object({
1482
1679
  head: BranchCommitSchema,
1483
- files: z7.array(BranchFileChangeSchema),
1484
- total: z7.number().int().nonnegative()
1680
+ files: z9.array(BranchFileChangeSchema),
1681
+ total: z9.number().int().nonnegative()
1485
1682
  });
1486
- var BranchDiffReadSchema = z7.object({
1683
+ var BranchDiffReadSchema = z9.object({
1487
1684
  repo: ProjectRepoSchema,
1488
1685
  branch: BranchNameSchema,
1489
1686
  path: BranchPathSchema.min(1)
1490
1687
  });
1491
- var BranchFileDiffSchema = z7.object({
1688
+ var BranchFileDiffSchema = z9.object({
1492
1689
  head: BranchCommitSchema,
1493
1690
  path: BranchPathSchema,
1494
1691
  status: BranchFileChangeSchema.shape.status,
1495
- patch: z7.string().nullable(),
1496
- truncated: z7.boolean()
1692
+ patch: z9.string().nullable(),
1693
+ truncated: z9.boolean()
1497
1694
  });
1498
1695
  function patchHunks(patch, limit2) {
1499
1696
  if (/^Binary files .* differ$/m.test(patch)) {
@@ -1507,9 +1704,23 @@ function patchHunks(patch, limit2) {
1507
1704
  const cut = hunks.lastIndexOf("\n", limit2);
1508
1705
  return { patch: hunks.slice(0, cut === -1 ? limit2 : cut), truncated: true };
1509
1706
  }
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 })
1707
+ var TurnImageReadSchema = z9.object({
1708
+ turnId: UlidSchema,
1709
+ path: ImagePathSchema
1710
+ });
1711
+ var ImageRefusalSchema = z9.enum(["missing", "outside", "not_image", "too_big"]);
1712
+ var TurnImageSchema = z9.discriminatedUnion("kind", [
1713
+ z9.object({
1714
+ kind: z9.literal("image"),
1715
+ type: ImageTypeSchema,
1716
+ size: z9.number().int().positive(),
1717
+ content: z9.string()
1718
+ }),
1719
+ z9.object({ kind: z9.literal("refused"), why: ImageRefusalSchema, detail: z9.string() })
1720
+ ]);
1721
+ var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
1722
+ z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
1723
+ z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema })
1513
1724
  ]);
1514
1725
  function workspaceConversationOf(turn) {
1515
1726
  if (turn.thread !== void 0) {
@@ -1517,60 +1728,70 @@ function workspaceConversationOf(turn) {
1517
1728
  }
1518
1729
  return turn.channel === void 0 ? null : { kind: "channel", scope: turn.scope, name: turn.channel };
1519
1730
  }
1520
- var HeldWorkspaceSchema = z7.object({
1731
+ var HeldWorkspaceSchema = z9.object({
1521
1732
  agent: NameSchema,
1522
1733
  scope: NameSchema,
1523
1734
  conversation: WorkspaceConversationSchema
1524
1735
  });
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 }),
1736
+ var HeldWorkspacesSchema = z9.object({ workspaces: z9.array(HeldWorkspaceSchema) });
1737
+ var SweepResultSchema = z9.object({ kept: z9.array(HeldWorkspaceSchema) });
1738
+ var TurnSteerSchema = z9.object({ id: z9.uuid(), text: z9.string().min(1) });
1739
+ var RunnerMessageSchema = z9.discriminatedUnion("type", [
1740
+ z9.object({ type: z9.literal("turn"), job: TurnJobSchema }),
1741
+ z9.object({ type: z9.literal("land"), request: z9.string().min(1), land: LandRequestSchema }),
1531
1742
  /** 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 }),
1743
+ z9.object({ type: z9.literal("close"), request: z9.string().min(1), close: CloseRequestSchema }),
1744
+ z9.object({ type: z9.literal("models"), request: z9.string().min(1), cli: CliKindSchema }),
1745
+ /**
1746
+ * Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
1747
+ * read one. Sent only to runners whose hello says `quotaReads`.
1748
+ */
1749
+ z9.object({ type: z9.literal("quota"), request: z9.string().min(1), cli: CliKindSchema }),
1534
1750
  /** 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 }),
1751
+ z9.object({ type: z9.literal("file"), request: z9.string().min(1), read: BranchReadSchema }),
1536
1752
  /** 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),
1753
+ z9.object({
1754
+ type: z9.literal("changes"),
1755
+ request: z9.string().min(1),
1540
1756
  read: BranchChangesReadSchema
1541
1757
  }),
1542
1758
  /** 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 }),
1759
+ z9.object({ type: z9.literal("diff"), request: z9.string().min(1), read: BranchDiffReadSchema }),
1760
+ /**
1761
+ * Answered with a `TurnImage`, or null when the turn is not running there. Sent only to runners
1762
+ * whose hello says `imageReads`.
1763
+ */
1764
+ z9.object({ type: z9.literal("image"), request: z9.string().min(1), read: TurnImageReadSchema }),
1544
1765
  /** 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),
1766
+ z9.object({
1767
+ type: z9.literal("steer"),
1768
+ request: z9.string().min(1),
1548
1769
  turnId: UlidSchema,
1549
1770
  steer: TurnSteerSchema
1550
1771
  }),
1551
1772
  /** 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 }),
1773
+ z9.object({ type: z9.literal("stop"), request: z9.string().min(1), turnId: UlidSchema }),
1553
1774
  /**
1554
1775
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1555
1776
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1556
1777
  */
1557
- z7.object({
1558
- type: z7.literal("sweep"),
1559
- request: z7.string().min(1),
1778
+ z9.object({
1779
+ type: z9.literal("sweep"),
1780
+ request: z9.string().min(1),
1560
1781
  conversation: WorkspaceConversationSchema
1561
1782
  })
1562
1783
  ]);
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() })
1784
+ var RunnerAnswerSchema = z9.discriminatedUnion("ok", [
1785
+ z9.object({ ok: z9.literal(true), value: z9.unknown() }),
1786
+ z9.object({ ok: z9.literal(false), error: z9.string() })
1566
1787
  ]);
1567
- var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
1568
- var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1788
+ var WarmSessionsSchema = z9.object({ warm: z9.array(z9.string()) });
1789
+ var FileManifestSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1569
1790
  function fileVersion(sha, executable) {
1570
1791
  return executable ? `${sha}+x` : sha;
1571
1792
  }
1572
- var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
1573
- var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
1793
+ var FileReadSchema = z9.object({ paths: z9.array(z9.string()) });
1794
+ var FileContentsSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1574
1795
  function isSafeRelativePath(file) {
1575
1796
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1576
1797
  return false;
@@ -1579,7 +1800,7 @@ function isSafeRelativePath(file) {
1579
1800
  }
1580
1801
 
1581
1802
  // packages/shared/dist/turns.js
1582
- var TriggerKindSchema = z8.enum([
1803
+ var TriggerKindSchema = z10.enum([
1583
1804
  "mention",
1584
1805
  "stage",
1585
1806
  "task_done",
@@ -1593,11 +1814,11 @@ var TriggerKindSchema = z8.enum([
1593
1814
  "closing",
1594
1815
  "cron"
1595
1816
  ]);
1596
- var TriggerSchema = z8.object({
1817
+ var TriggerSchema = z10.object({
1597
1818
  kind: TriggerKindSchema,
1598
1819
  from: NameSchema.optional(),
1599
- fromUser: z8.boolean().default(false),
1600
- reason: z8.string().default(""),
1820
+ fromUser: z10.boolean().default(false),
1821
+ reason: z10.string().default(""),
1601
1822
  taskId: UlidSchema.optional(),
1602
1823
  messageId: UlidSchema.optional(),
1603
1824
  /** The channel the waking message was posted in. */
@@ -1605,17 +1826,17 @@ var TriggerSchema = z8.object({
1605
1826
  /** The cron that came due, for a `cron` wake. */
1606
1827
  cronId: UlidSchema.optional()
1607
1828
  });
1608
- var TurnThreadSchema = z8.object({ id: UlidSchema, task: z8.boolean() });
1609
- var TurnDispatchSchema = z8.object({
1829
+ var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
1830
+ var TurnDispatchSchema = z10.object({
1610
1831
  agent: NameSchema,
1611
1832
  project: NameSchema,
1612
1833
  thread: TurnThreadSchema.optional(),
1613
1834
  channel: NameSchema.optional(),
1614
1835
  trigger: TriggerSchema,
1615
- priority: z8.number().int().min(0).max(2),
1616
- onboarding: z8.boolean().default(false),
1836
+ priority: z10.number().int().min(0).max(2),
1837
+ onboarding: z10.boolean().default(false),
1617
1838
  /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1618
- crons: z8.array(UlidSchema).optional()
1839
+ crons: z10.array(UlidSchema).optional()
1619
1840
  });
1620
1841
  function conversationPart(conversation) {
1621
1842
  return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
@@ -1639,7 +1860,7 @@ function channelConversation(scope, channel) {
1639
1860
  const name = at < 0 ? channel : channel.slice(at + 1);
1640
1861
  return place === scope && name !== "general" ? name : void 0;
1641
1862
  }
1642
- var TurnRecordSchema = z8.object({
1863
+ var TurnRecordSchema = z10.object({
1643
1864
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1644
1865
  id: UlidSchema.optional(),
1645
1866
  agent: NameSchema,
@@ -1650,31 +1871,31 @@ var TurnRecordSchema = z8.object({
1650
1871
  channel: NameSchema.optional(),
1651
1872
  runner: NameSchema,
1652
1873
  cli: CliKindSchema.nullable(),
1653
- session: z8.string().nullable(),
1874
+ session: z10.string().nullable(),
1654
1875
  trigger: TriggerSchema,
1655
1876
  startedAt: IsoDateTimeSchema,
1656
1877
  endedAt: IsoDateTimeSchema.nullable(),
1657
1878
  exitReason: TurnExitReasonSchema.nullable(),
1658
1879
  status: TurnStatusSchema.nullable(),
1659
- error: z8.string().nullable(),
1880
+ error: z10.string().nullable(),
1660
1881
  usage: UsageSchema.nullable(),
1661
- costUsd: z8.number().nonnegative().default(0),
1882
+ costUsd: z10.number().nonnegative().default(0),
1662
1883
  /**
1663
1884
  * The running total the CLI reported for the session after this turn, when it reports one. A
1664
1885
  * resumed session starts from it, so the next turn's own cost is measured from here.
1665
1886
  */
1666
- sessionCostUsd: z8.number().nonnegative().optional(),
1667
- toolCalls: z8.number().int().nonnegative().default(0),
1887
+ sessionCostUsd: z10.number().nonnegative().optional(),
1888
+ toolCalls: z10.number().int().nonnegative().default(0),
1668
1889
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1669
- model: z8.string().nullable().default(null),
1890
+ model: z10.string().nullable().default(null),
1670
1891
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1671
1892
  work: TurnWorkSchema.optional(),
1672
1893
  /** Who stopped the turn, for one that ended `stopped`. */
1673
1894
  stoppedBy: NameSchema.optional(),
1674
1895
  /** The crons whose fire started the turn, which record how it ended. */
1675
- crons: z8.array(UlidSchema).optional()
1896
+ crons: z10.array(UlidSchema).optional()
1676
1897
  });
1677
- var RunningTurnSchema = z8.object({
1898
+ var RunningTurnSchema = z10.object({
1678
1899
  turnId: UlidSchema,
1679
1900
  agent: NameSchema,
1680
1901
  scope: NameSchema,
@@ -1682,49 +1903,49 @@ var RunningTurnSchema = z8.object({
1682
1903
  channel: NameSchema.optional(),
1683
1904
  cli: CliKindSchema,
1684
1905
  askedIn: ChannelRefSchema.optional(),
1685
- steerable: z8.boolean(),
1686
- stoppable: z8.boolean()
1906
+ steerable: z10.boolean(),
1907
+ stoppable: z10.boolean()
1687
1908
  });
1688
- var SessionsFileSchema = z8.object({
1689
- claude: z8.string().optional(),
1690
- codex: z8.string().optional(),
1909
+ var SessionsFileSchema = z10.object({
1910
+ claude: z10.string().optional(),
1911
+ codex: z10.string().optional(),
1691
1912
  runner: NameSchema.optional()
1692
1913
  });
1693
- var WakeRequestSchema = z8.object({
1914
+ var WakeRequestSchema = z10.object({
1694
1915
  agent: NameSchema,
1695
1916
  project: NameSchema,
1696
- reason: z8.string().default("manual wake"),
1917
+ reason: z10.string().default("manual wake"),
1697
1918
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1698
- kind: z8.enum(["manual", "reflection"]).default("manual")
1919
+ kind: z10.enum(["manual", "reflection"]).default("manual")
1699
1920
  });
1700
- var TurnHistoryEntrySchema = z8.object({
1921
+ var TurnHistoryEntrySchema = z10.object({
1701
1922
  /** The id of the event that ended the turn. */
1702
1923
  id: UlidSchema,
1703
1924
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1704
1925
  turnId: UlidSchema.optional(),
1705
1926
  ts: IsoDateTimeSchema,
1706
- outcome: z8.enum(["completed", "failed"]),
1927
+ outcome: z10.enum(["completed", "failed"]),
1707
1928
  /** A project slug, or the society scope. */
1708
1929
  project: NameSchema,
1709
1930
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1710
1931
  thread: UlidSchema.optional(),
1711
1932
  /** The channel, never general, whose conversation the turn was in. */
1712
1933
  channel: NameSchema.optional(),
1713
- trigger: z8.string(),
1714
- exitReason: z8.string().nullable(),
1715
- costUsd: z8.number(),
1934
+ trigger: z10.string(),
1935
+ exitReason: z10.string().nullable(),
1936
+ costUsd: z10.number(),
1716
1937
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1717
1938
  usage: UsageSchema.optional(),
1718
- model: z8.string().nullable(),
1719
- summary: z8.string().nullable(),
1720
- error: z8.string().nullable(),
1939
+ model: z10.string().nullable(),
1940
+ summary: z10.string().nullable(),
1941
+ error: z10.string().nullable(),
1721
1942
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1722
1943
  startedAt: IsoDateTimeSchema.optional(),
1723
- toolCalls: z8.number().int().nonnegative().optional()
1944
+ toolCalls: z10.number().int().nonnegative().optional()
1724
1945
  });
1725
1946
 
1726
1947
  // packages/shared/dist/ops.js
1727
- import { z as z9 } from "zod";
1948
+ import { z as z11 } from "zod";
1728
1949
  var OPS_SIGNAL_KINDS = [
1729
1950
  "waiting_stage",
1730
1951
  "backlog",
@@ -1740,7 +1961,7 @@ var OPS_SIGNAL_KINDS = [
1740
1961
  "stuck_workspace",
1741
1962
  "cron_failing"
1742
1963
  ];
1743
- var OpsSignalKindSchema = z9.enum(OPS_SIGNAL_KINDS);
1964
+ var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
1744
1965
  var WAKING_SIGNAL_KINDS = [
1745
1966
  "backlog",
1746
1967
  "role_gap",
@@ -1750,15 +1971,15 @@ var WAKING_SIGNAL_KINDS = [
1750
1971
  "blocked_capability",
1751
1972
  "scaled"
1752
1973
  ];
1753
- var OpsSignalSchema = z9.object({
1974
+ var OpsSignalSchema = z11.object({
1754
1975
  kind: OpsSignalKindSchema,
1755
1976
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1756
- key: z9.string().min(1),
1977
+ key: z11.string().min(1),
1757
1978
  /** One line a reader can act on. */
1758
- summary: z9.string().min(1),
1979
+ summary: z11.string().min(1),
1759
1980
  /** The counter or timer value behind the signal. */
1760
- value: z9.number(),
1761
- threshold: z9.number().optional(),
1981
+ value: z11.number(),
1982
+ threshold: z11.number().optional(),
1762
1983
  project: NameSchema.optional(),
1763
1984
  agent: NameSchema.optional(),
1764
1985
  role: NameSchema.optional(),
@@ -1766,8 +1987,8 @@ var OpsSignalSchema = z9.object({
1766
1987
  });
1767
1988
 
1768
1989
  // packages/shared/dist/metrics.js
1769
- import { z as z10 } from "zod";
1770
- var MetricsWindowSchema = z10.enum(["24h", "7d", "all"]);
1990
+ import { z as z12 } from "zod";
1991
+ var MetricsWindowSchema = z12.enum(["24h", "7d", "all"]);
1771
1992
  var METRICS_WINDOW_MS = {
1772
1993
  "24h": 24 * 36e5,
1773
1994
  "7d": 7 * 24 * 36e5,
@@ -1778,9 +1999,9 @@ function isBoardAction(tool) {
1778
1999
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1779
2000
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1780
2001
  }
1781
- var CountSchema = z10.number().int().nonnegative();
1782
- var MsSchema = z10.number().nonnegative();
1783
- var MetricsSchema = z10.object({
2002
+ var CountSchema = z12.number().int().nonnegative();
2003
+ var MsSchema = z12.number().nonnegative();
2004
+ var MetricsSchema = z12.object({
1784
2005
  window: MetricsWindowSchema,
1785
2006
  since: IsoDateTimeSchema.nullable(),
1786
2007
  until: IsoDateTimeSchema,
@@ -1788,12 +2009,12 @@ var MetricsSchema = z10.object({
1788
2009
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1789
2010
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1790
2011
  */
1791
- idle: z10.object({
2012
+ idle: z12.object({
1792
2013
  turns: CountSchema,
1793
2014
  idle: CountSchema,
1794
2015
  unknown: CountSchema,
1795
- byTrigger: z10.array(z10.object({ trigger: z10.string(), turns: CountSchema, idle: CountSchema })),
1796
- byAgent: z10.array(z10.object({
2016
+ byTrigger: z12.array(z12.object({ trigger: z12.string(), turns: CountSchema, idle: CountSchema })),
2017
+ byAgent: z12.array(z12.object({
1797
2018
  agent: NameSchema,
1798
2019
  role: NameSchema.nullable(),
1799
2020
  turns: CountSchema,
@@ -1801,33 +2022,33 @@ var MetricsSchema = z10.object({
1801
2022
  }))
1802
2023
  }),
1803
2024
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1804
- sentBack: z10.object({
2025
+ sentBack: z12.object({
1805
2026
  finished: CountSchema,
1806
2027
  sendBacks: CountSchema,
1807
2028
  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 }))
2029
+ byStage: z12.array(z12.object({ project: NameSchema, stage: z12.string(), count: CountSchema })),
2030
+ bySender: z12.array(z12.object({ agent: NameSchema, count: CountSchema }))
1810
2031
  }),
1811
2032
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1812
- messages: z10.object({
2033
+ messages: z12.object({
1813
2034
  finished: CountSchema,
1814
2035
  messages: CountSchema,
1815
- byProject: z10.array(z10.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1816
- busiest: z10.array(z10.object({
2036
+ byProject: z12.array(z12.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
2037
+ busiest: z12.array(z12.object({
1817
2038
  taskId: UlidSchema,
1818
- title: z10.string(),
2039
+ title: z12.string(),
1819
2040
  project: NameSchema,
1820
2041
  messages: CountSchema
1821
2042
  }))
1822
2043
  }),
1823
2044
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1824
- latency: z10.object({
2045
+ latency: z12.object({
1825
2046
  mentions: CountSchema,
1826
2047
  /** Mentions no turn has followed yet. */
1827
2048
  unanswered: CountSchema,
1828
2049
  medianMs: MsSchema.nullable(),
1829
2050
  slowestMs: MsSchema.nullable(),
1830
- byAgent: z10.array(z10.object({
2051
+ byAgent: z12.array(z12.object({
1831
2052
  agent: NameSchema,
1832
2053
  mentions: CountSchema,
1833
2054
  medianMs: MsSchema,
@@ -1835,61 +2056,73 @@ var MetricsSchema = z10.object({
1835
2056
  }))
1836
2057
  }),
1837
2058
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1838
- decisions: z10.object({
2059
+ decisions: z12.object({
1839
2060
  total: CountSchema,
1840
- byDay: z10.array(z10.object({
1841
- day: z10.string(),
2061
+ byDay: z12.array(z12.object({
2062
+ day: z12.string(),
1842
2063
  proposals: CountSchema,
1843
2064
  answers: CountSchema,
1844
2065
  stages: CountSchema
1845
2066
  }))
1846
2067
  }),
1847
2068
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1848
- blocked: z10.array(z10.object({
2069
+ blocked: z12.array(z12.object({
1849
2070
  taskId: UlidSchema,
1850
- title: z10.string().nullable(),
2071
+ title: z12.string().nullable(),
1851
2072
  project: NameSchema.nullable(),
1852
- summary: z10.string(),
2073
+ summary: z12.string(),
1853
2074
  firstAt: IsoDateTimeSchema,
1854
2075
  lastAt: IsoDateTimeSchema,
1855
- holds: z10.boolean()
2076
+ holds: z12.boolean()
1856
2077
  }))
1857
2078
  });
2079
+ var CitizenTokensSchema = z12.object({
2080
+ agent: NameSchema,
2081
+ turns: CountSchema,
2082
+ withUsage: CountSchema,
2083
+ usage: UsageSchema.nullable()
2084
+ });
2085
+ var TokenUseSchema = z12.object({
2086
+ window: MetricsWindowSchema,
2087
+ since: IsoDateTimeSchema.nullable(),
2088
+ until: IsoDateTimeSchema,
2089
+ citizens: z12.array(CitizenTokensSchema)
2090
+ });
1858
2091
 
1859
2092
  // 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),
2093
+ import { z as z13 } from "zod";
2094
+ var LogLevelSchema = z13.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
2095
+ var GithubSignInSchema = z13.object({
2096
+ clientId: z13.string().min(1),
2097
+ clientSecret: z13.string().min(1),
1865
2098
  /** Logins, lowercased; GitHub logins are case-insensitive. */
1866
- users: z11.array(z11.string().min(1)).min(1)
2099
+ users: z13.array(z13.string().min(1)).min(1)
1867
2100
  });
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),
2101
+ var ServerConfigSchema = z13.object({
2102
+ dataDir: z13.string().min(1),
2103
+ host: z13.string().min(1),
2104
+ port: z13.number().int().positive().max(65535),
1872
2105
  /**
1873
2106
  * An address every runner's agents reach this server at, for example https://board.example, or
1874
2107
  * null for each runner to use the address it reaches the server at itself.
1875
2108
  */
1876
- publicUrl: z11.string().min(1).nullable(),
2109
+ publicUrl: z13.string().min(1).nullable(),
1877
2110
  logLevel: LogLevelSchema,
1878
2111
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
1879
- concurrency: z11.number().int().positive().nullable(),
2112
+ concurrency: z13.number().int().positive().nullable(),
1880
2113
  /** How long one turn may run before it is stopped, or null for no limit. */
1881
- turnTimeoutMs: z11.number().int().positive().nullable(),
2114
+ turnTimeoutMs: z13.number().int().positive().nullable(),
1882
2115
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1883
- toolRounds: z11.number().int().positive().nullable(),
2116
+ toolRounds: z13.number().int().positive().nullable(),
1884
2117
  /** How long a resident session stays warm after its last turn. */
1885
- residentIdleMs: z11.number().int().positive(),
2118
+ residentIdleMs: z13.number().int().positive(),
1886
2119
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1887
2120
  github: GithubSignInSchema.nullable(),
1888
2121
  /**
1889
2122
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1890
2123
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1891
2124
  */
1892
- trustedProxies: z11.number().int().min(0).max(16)
2125
+ trustedProxies: z13.number().int().min(0).max(16)
1893
2126
  });
1894
2127
  function limit(value, fallback) {
1895
2128
  if (value === void 0 || value.trim() === "") {
@@ -1927,25 +2160,73 @@ function loadServerConfig(env) {
1927
2160
  trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
1928
2161
  });
1929
2162
  }
1930
- var RunnerConfigSchema = z11.object({
2163
+ var RunnerConfigSchema = z13.object({
1931
2164
  /** The board server, for example http://127.0.0.1:4700. */
1932
- serverUrl: z11.string().url(),
2165
+ serverUrl: z13.string().url(),
1933
2166
  /**
1934
2167
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1935
2168
  * to enroll it.
1936
2169
  */
1937
- token: z11.string().min(1).nullable(),
2170
+ token: z13.string().min(1).nullable(),
1938
2171
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1939
- dataDir: z11.string().min(1),
2172
+ dataDir: z13.string().min(1),
1940
2173
  logLevel: LogLevelSchema,
1941
2174
  /** 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())
2175
+ slots: z13.number().int().positive().nullable(),
2176
+ clis: z13.array(CliKindSchema).min(1),
2177
+ capabilities: z13.array(z13.string())
1945
2178
  });
1946
2179
 
1947
2180
  // packages/shared/dist/version.js
1948
- var STELLARIS_VERSION = "0.3.21";
2181
+ var STELLARIS_VERSION = "0.3.23";
2182
+
2183
+ // packages/shared/dist/mentions.js
2184
+ import { parse, postprocess, preprocess } from "micromark";
2185
+ import { gfm } from "micromark-extension-gfm";
2186
+ var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
2187
+ var CODE = /* @__PURE__ */ new Set(["codeText", "codeFenced", "codeIndented"]);
2188
+ var SLOW = {
2189
+ disable: {
2190
+ null: [
2191
+ "wwwAutolink",
2192
+ "protocolAutolink",
2193
+ "emailAutolink",
2194
+ "gfmFootnoteCall",
2195
+ "gfmPotentialFootnoteCall"
2196
+ ]
2197
+ }
2198
+ };
2199
+ function codeRanges(markdown) {
2200
+ const shift = markdown.startsWith("\uFEFF") ? 1 : 0;
2201
+ const events = postprocess(parse({ extensions: [gfm(), SLOW] }).document().write(preprocess()(markdown, void 0, true)));
2202
+ return events.flatMap(([kind, token]) => kind === "enter" && CODE.has(token.type) ? [[token.start.offset + shift, token.end.offset + shift]] : []);
2203
+ }
2204
+ function addMentions(found, text) {
2205
+ for (const match of text.matchAll(MENTION_PATTERN)) {
2206
+ const name = match[2];
2207
+ if (name !== void 0) {
2208
+ found.add(name);
2209
+ }
2210
+ }
2211
+ }
2212
+ function mentionsIn(markdown) {
2213
+ if (markdown.search(MENTION_PATTERN) === -1) {
2214
+ return [];
2215
+ }
2216
+ const found = /* @__PURE__ */ new Set();
2217
+ let from = 0;
2218
+ for (const [start, end] of codeRanges(markdown)) {
2219
+ addMentions(found, markdown.slice(from, start));
2220
+ from = end;
2221
+ }
2222
+ addMentions(found, markdown.slice(from));
2223
+ return [...found].toSorted();
2224
+ }
2225
+ function mentionsAnywhere(text) {
2226
+ const found = /* @__PURE__ */ new Set();
2227
+ addMentions(found, text);
2228
+ return [...found].toSorted();
2229
+ }
1949
2230
 
1950
2231
  // packages/board-core/dist/errors.js
1951
2232
  var BoardError = class extends Error {
@@ -2591,33 +2872,59 @@ function filesOf(lines, status) {
2591
2872
  return [...files.values()];
2592
2873
  }
2593
2874
 
2875
+ // packages/board-core/dist/mentions.js
2876
+ import vm from "node:vm";
2877
+ var MENTION_PARSE_MS = 500;
2878
+ var context = vm.createContext({});
2879
+ var call = new vm.Script("work()");
2880
+ function within(work, ms) {
2881
+ let result;
2882
+ context["work"] = () => {
2883
+ result = work();
2884
+ };
2885
+ try {
2886
+ call.runInContext(context, { timeout: ms });
2887
+ } catch (error) {
2888
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
2889
+ return void 0;
2890
+ }
2891
+ throw error;
2892
+ } finally {
2893
+ context["work"] = void 0;
2894
+ }
2895
+ return result;
2896
+ }
2897
+ function postMentions(body) {
2898
+ return within(() => mentionsIn(body), MENTION_PARSE_MS) ?? mentionsAnywhere(body);
2899
+ }
2900
+
2594
2901
  // 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()
2902
+ import { z as z14 } from "zod";
2903
+ var TurnPayload = z14.object({
2904
+ turnId: z14.string().optional(),
2905
+ project: z14.string(),
2906
+ thread: z14.string().optional(),
2907
+ channel: z14.string().optional(),
2908
+ trigger: z14.string()
2602
2909
  });
2603
- var TaskPayload = z12.object({
2604
- taskId: z12.string(),
2605
- project: z12.string(),
2606
- from: z12.string().optional()
2910
+ var TaskPayload = z14.object({
2911
+ taskId: z14.string(),
2912
+ project: z14.string(),
2913
+ from: z14.string().optional()
2607
2914
  });
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([])
2915
+ var MessagePayload = z14.object({
2916
+ id: z14.string().optional(),
2917
+ channel: z14.string(),
2918
+ thread: z14.string().nullable().optional(),
2919
+ mentions: z14.array(z14.string()).default([])
2613
2920
  });
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()
2921
+ var SteeredPayload = z14.object({ messages: z14.array(z14.string()) });
2922
+ var SignalPayload = z14.object({
2923
+ kind: z14.string(),
2924
+ key: z14.string(),
2925
+ summary: z14.string(),
2926
+ taskId: z14.string().optional(),
2927
+ project: z14.string().optional()
2621
2928
  });
2622
2929
  function median(values) {
2623
2930
  const sorted = values.toSorted((a, b) => a - b);
@@ -2906,6 +3213,59 @@ function computeMetrics(input) {
2906
3213
  };
2907
3214
  }
2908
3215
 
3216
+ // packages/board-core/dist/turn-history.js
3217
+ import { z as z15 } from "zod";
3218
+ var TurnHistoryPayloadSchema = z15.object({
3219
+ project: NameSchema,
3220
+ thread: UlidSchema.optional(),
3221
+ channel: NameSchema.optional(),
3222
+ trigger: z15.string().default("manual"),
3223
+ exitReason: z15.string().nullable().default(null),
3224
+ costUsd: z15.number().default(0),
3225
+ usage: UsageSchema.optional(),
3226
+ model: z15.string().nullable().default(null),
3227
+ summary: z15.string().nullable().default(null),
3228
+ error: z15.string().nullable().default(null),
3229
+ toolCalls: z15.number().int().nonnegative().optional(),
3230
+ turnId: UlidSchema.optional()
3231
+ });
3232
+ function tokenUse(events, window, now) {
3233
+ const span = METRICS_WINDOW_MS[window];
3234
+ const since = span === null ? null : now.getTime() - span;
3235
+ const rows = /* @__PURE__ */ new Map();
3236
+ for (const event of events) {
3237
+ if (event.type !== "turn.completed" && event.type !== "turn.failed") {
3238
+ continue;
3239
+ }
3240
+ if (since !== null && Date.parse(event.ts) < since) {
3241
+ continue;
3242
+ }
3243
+ const turn = TurnHistoryPayloadSchema.safeParse(event.payload);
3244
+ if (!turn.success) {
3245
+ continue;
3246
+ }
3247
+ const row = rows.get(event.actor) ?? {
3248
+ agent: event.actor,
3249
+ turns: 0,
3250
+ withUsage: 0,
3251
+ usage: null
3252
+ };
3253
+ const usage = turn.data.usage;
3254
+ rows.set(event.actor, {
3255
+ agent: event.actor,
3256
+ turns: row.turns + 1,
3257
+ withUsage: row.withUsage + (usage === void 0 ? 0 : 1),
3258
+ usage: usage === void 0 ? row.usage : row.usage === null ? usage : addUsage(row.usage, usage)
3259
+ });
3260
+ }
3261
+ return {
3262
+ window,
3263
+ since: since === null ? null : new Date(since).toISOString(),
3264
+ until: now.toISOString(),
3265
+ citizens: [...rows.values()].toSorted((a, b) => a.agent.localeCompare(b.agent))
3266
+ };
3267
+ }
3268
+
2909
3269
  // packages/board-core/dist/mutex.js
2910
3270
  var Mutex = class {
2911
3271
  tail = Promise.resolve();
@@ -3112,6 +3472,13 @@ var BoardPaths = class {
3112
3472
  eventLog() {
3113
3473
  return path5.join(this.events(), "log.jsonl");
3114
3474
  }
3475
+ /** Images citizens uploaded, by id: outside the projection, which every runner mirrors. */
3476
+ images() {
3477
+ return path5.join(this.dataDir, "images");
3478
+ }
3479
+ image(id) {
3480
+ return path5.join(this.images(), id);
3481
+ }
3115
3482
  state() {
3116
3483
  return path5.join(this.dataDir, "state");
3117
3484
  }
@@ -3147,41 +3514,27 @@ function namesList(names) {
3147
3514
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
3148
3515
  }
3149
3516
  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) }))
3517
+ var RunnerTokensSchema = z16.object({
3518
+ runners: z16.record(z16.string(), z16.object({ tokenHash: z16.string().min(1) }))
3152
3519
  });
3153
- var SignInsSchema = z13.object({
3154
- signIns: z13.array(z13.object({
3155
- tokenHash: z13.string().min(1),
3156
- login: z13.string().min(1),
3520
+ var SignInsSchema = z16.object({
3521
+ signIns: z16.array(z16.object({
3522
+ tokenHash: z16.string().min(1),
3523
+ login: z16.string().min(1),
3157
3524
  /** The account's picture at the identity provider; sign-ins from before it have none. */
3158
- avatarUrl: z13.url().optional(),
3525
+ avatarUrl: z16.url().optional(),
3159
3526
  createdAt: IsoDateTimeSchema,
3160
3527
  expiresAt: IsoDateTimeSchema
3161
3528
  })).default([])
3162
3529
  });
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({}),
3530
+ var TurnStartPayloadSchema = z16.object({ project: NameSchema, turnId: UlidSchema.optional() });
3531
+ var CursorsSchema = z16.object({
3532
+ digest: z16.string().nullable(),
3533
+ scopes: z16.record(z16.string(), z16.string()).default({}),
3534
+ threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
3535
+ channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
3183
3536
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
3184
- news: z13.string().optional()
3537
+ news: z16.string().optional()
3185
3538
  });
3186
3539
  var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
3187
3540
  function conversationKey(scope, part) {
@@ -3220,9 +3573,8 @@ function byId(a, b) {
3220
3573
  function turnTime(record) {
3221
3574
  return record.endedAt ?? record.startedAt;
3222
3575
  }
3223
- var PausedSchema = z13.object({ paused: z13.boolean() });
3576
+ var PausedSchema = z16.object({ paused: z16.boolean() });
3224
3577
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
3225
- var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
3226
3578
  function withoutMentions(text) {
3227
3579
  return text.replaceAll(MENTION_PATTERN, "$1$2");
3228
3580
  }
@@ -3246,7 +3598,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
3246
3598
  var FRONT_DESK_TRIGGER = "user_post";
3247
3599
  var OPS_CHANNELS = ["governance"];
3248
3600
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
3249
- var AlignmentsSchema = z13.object({ applied: z13.array(z13.string()).default([]) });
3601
+ var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
3250
3602
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
3251
3603
  var THREAD_RECORDS = "threads-as-records";
3252
3604
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -3260,10 +3612,11 @@ var CHANNEL_CONVERSATIONS = "channel-conversations";
3260
3612
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3261
3613
  var CRON_VERBS = "cron-verbs-granted";
3262
3614
  var KNOWLEDGE_NOTICES = "knowledge-notices";
3263
- var KnowledgeEventSchema = z13.object({
3615
+ var IMAGE_UPLOADS = "image-uploads-granted";
3616
+ var KnowledgeEventSchema = z16.object({
3264
3617
  topic: NameSchema,
3265
3618
  project: NameSchema.nullable(),
3266
- replaced: z13.boolean().optional()
3619
+ replaced: z16.boolean().optional()
3267
3620
  });
3268
3621
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3269
3622
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
@@ -3381,16 +3734,6 @@ function unanswered(messages) {
3381
3734
  }
3382
3735
  return waiting;
3383
3736
  }
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
3737
  function snippetAround(text, query, radius = 80) {
3395
3738
  const index = text.toLowerCase().indexOf(query.toLowerCase());
3396
3739
  if (index === -1) {
@@ -3405,6 +3748,7 @@ var Board = class _Board {
3405
3748
  mutex = new Mutex();
3406
3749
  /** The turns in flight, which the server hosting them supplies; none until it does. */
3407
3750
  turnsInFlight = () => [];
3751
+ imageSource = null;
3408
3752
  newId = monotonicFactory();
3409
3753
  events;
3410
3754
  actionCounts = /* @__PURE__ */ new Map();
@@ -3469,7 +3813,8 @@ var Board = class _Board {
3469
3813
  CHANNEL_CONVERSATIONS,
3470
3814
  DASHBOARD_UPDATES,
3471
3815
  CRON_VERBS,
3472
- KNOWLEDGE_NOTICES
3816
+ KNOWLEDGE_NOTICES,
3817
+ IMAGE_UPLOADS
3473
3818
  ].filter((name) => !applied.includes(name));
3474
3819
  if (pending.length === 0) {
3475
3820
  return;
@@ -3499,13 +3844,13 @@ var Board = class _Board {
3499
3844
  await this.openAsksChannel();
3500
3845
  }
3501
3846
  if (pending.includes(KNOWLEDGE_REMOVAL)) {
3502
- await this.grantBesideKnowledge("remove_knowledge");
3847
+ await this.grantBeside("write_knowledge", "remove_knowledge");
3503
3848
  }
3504
3849
  if (pending.includes(CHANNEL_CONVERSATIONS)) {
3505
3850
  await this.startChannelCursors();
3506
3851
  }
3507
3852
  if (pending.includes(DASHBOARD_UPDATES)) {
3508
- await this.grantBesideKnowledge("update_dashboard");
3853
+ await this.grantBeside("write_knowledge", "update_dashboard");
3509
3854
  await this.rewordDashboards();
3510
3855
  }
3511
3856
  if (pending.includes(CRON_VERBS)) {
@@ -3514,6 +3859,9 @@ var Board = class _Board {
3514
3859
  if (pending.includes(KNOWLEDGE_NOTICES)) {
3515
3860
  await this.noticeKnowledgeNotes();
3516
3861
  }
3862
+ if (pending.includes(IMAGE_UPLOADS)) {
3863
+ await this.grantBeside("post_message", "upload_image");
3864
+ }
3517
3865
  await writeJson(file, { applied: [...applied, ...pending] });
3518
3866
  }
3519
3867
  /**
@@ -3555,12 +3903,12 @@ var Board = class _Board {
3555
3903
  }
3556
3904
  }
3557
3905
  /**
3558
- * Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
3559
- * practice; seed roles have it from their seed.
3906
+ * Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
3907
+ * seed roles have it from their seed.
3560
3908
  */
3561
- async grantBesideKnowledge(verb) {
3909
+ async grantBeside(beside, verb) {
3562
3910
  for (const charter of await this.listRoles()) {
3563
- if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes(verb)) {
3911
+ if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
3564
3912
  await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
3565
3913
  }
3566
3914
  }
@@ -3718,7 +4066,7 @@ var Board = class _Board {
3718
4066
  }
3719
4067
  }
3720
4068
  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()));
4069
+ const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
3722
4070
  const legacy = raw.data["thread"];
3723
4071
  if (legacy === void 0) {
3724
4072
  continue;
@@ -3796,7 +4144,7 @@ var Board = class _Board {
3796
4144
  * turns, so it follows nothing now.
3797
4145
  */
3798
4146
  async renameInboxCursors() {
3799
- const Legacy = z13.object({ inbox: z13.string().nullable() });
4147
+ const Legacy = z16.object({ inbox: z16.string().nullable() });
3800
4148
  for (const agent of await this.listAgents()) {
3801
4149
  const file = this.paths.agentCursors(agent.name);
3802
4150
  if (await exists(file)) {
@@ -3842,7 +4190,7 @@ ${charter.purpose}
3842
4190
  */
3843
4191
  async alignSeedRole(seed) {
3844
4192
  const file = this.paths.role(seed.name);
3845
- const raw = await readMarkdown(file, z13.record(z13.string(), z13.unknown()));
4193
+ const raw = await readMarkdown(file, z16.record(z16.string(), z16.unknown()));
3846
4194
  const current = RoleCharterSchema.parse(raw.data);
3847
4195
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
3848
4196
  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 +4507,7 @@ ${charter.purpose}
4159
4507
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
4160
4508
  async readSocietyNorms() {
4161
4509
  const file = this.paths.societyKnowledgeFile("norms");
4162
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
4510
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
4163
4511
  }
4164
4512
  /** Knowledge topics of a project, or of the society when the project is null. */
4165
4513
  async listKnowledge(project) {
@@ -4179,7 +4527,7 @@ ${charter.purpose}
4179
4527
  */
4180
4528
  async knowledgeAt(file, project, topic) {
4181
4529
  const { data, body } = await readLooseMarkdown(file);
4182
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
4530
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
4183
4531
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
4184
4532
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
4185
4533
  return {
@@ -4512,8 +4860,11 @@ ${DASHBOARD_LINE}
4512
4860
  throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4513
4861
  }
4514
4862
  const doc = await readMarkdown(file, RunnerSchema);
4863
+ const { gitIdentityMissing: reported, ...rest } = doc.data;
4864
+ const gitIdentityMissing = patch.gitIdentityMissing === void 0 ? reported : patch.gitIdentityMissing ?? void 0;
4515
4865
  const next = RunnerSchema.parse({
4516
- ...doc.data,
4866
+ ...rest,
4867
+ ...gitIdentityMissing === void 0 ? {} : { gitIdentityMissing: [...gitIdentityMissing] },
4517
4868
  ...patch.status === void 0 ? {} : { status: patch.status },
4518
4869
  ...patch.os === void 0 ? {} : { os: patch.os },
4519
4870
  ...patch.clis === void 0 ? {} : { clis: [...patch.clis] },
@@ -4541,6 +4892,36 @@ ${DASHBOARD_LINE}
4541
4892
  return next;
4542
4893
  });
4543
4894
  }
4895
+ /**
4896
+ * Records a CLI's plan quota as a runner reported it, from a turn's stream or a read, merged over
4897
+ * what the record held. The record is written on every report, so it says when the numbers were
4898
+ * last seen; `runner.quota` is appended only when what a reader would see changes, since every
4899
+ * turn reports.
4900
+ */
4901
+ async recordQuota(name, cli, quota) {
4902
+ return this.mutex.run(async () => {
4903
+ const file = this.paths.runner(name);
4904
+ if (!await exists(file)) {
4905
+ throw new BoardError("NOT_FOUND", `runner ${name} not found`);
4906
+ }
4907
+ const doc = await readMarkdown(file, RunnerSchema);
4908
+ const previous = doc.data.quotas?.[cli];
4909
+ const merged = mergeQuota(previous, quota);
4910
+ const next = RunnerSchema.parse({
4911
+ ...doc.data,
4912
+ quotas: { ...doc.data.quotas, [cli]: merged }
4913
+ });
4914
+ await writeMarkdown(file, next, doc.body);
4915
+ if (quotaChanged(previous, merged)) {
4916
+ await this.events.append("runner.quota", SYSTEM_ACTOR.name, {
4917
+ name,
4918
+ cli,
4919
+ status: merged.status
4920
+ });
4921
+ }
4922
+ return next;
4923
+ });
4924
+ }
4544
4925
  /**
4545
4926
  * Logs an operations signal as an `ops.signal` event. No channel carries it: the scheduler wakes
4546
4927
  * its readers from the event, the runner lists it in their prompts, and the interface's log shows
@@ -5109,6 +5490,60 @@ ${current.body}`);
5109
5490
  return updated;
5110
5491
  });
5111
5492
  }
5493
+ /**
5494
+ * Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
5495
+ * refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
5496
+ * Only the bytes of a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` are kept, checked
5497
+ * here again whatever the runner said, under a new id outside the projection, so no runner
5498
+ * mirrors them and no prompt carries them: a post shows the image by its `image:` address.
5499
+ */
5500
+ async uploadImage(actor, input) {
5501
+ const args = VerbInputs.upload_image.parse(input);
5502
+ await this.authorize(actor, "upload_image");
5503
+ if (actor.scope === void 0) {
5504
+ throw new BoardError("INVALID_STATE", "upload_image reads the file on the machine a turn runs on, so only a turn can call it");
5505
+ }
5506
+ if (this.imageSource === null) {
5507
+ throw new BoardError("NOT_FOUND", "this board has no runners to read an image from");
5508
+ }
5509
+ const bytes = await this.imageSource(actor, args.path);
5510
+ const type = imageTypeOf(bytes);
5511
+ if (type === null) {
5512
+ throw new BoardError("VALIDATION", `${args.path} is not a PNG, JPEG, GIF, or WebP image by its first bytes`);
5513
+ }
5514
+ if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
5515
+ throw new BoardError("VALIDATION", `${args.path} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
5516
+ }
5517
+ return this.mutex.run(async () => {
5518
+ const id = this.newId();
5519
+ await writeFileAtomic(this.paths.image(id), bytes);
5520
+ await this.events.append("image.uploaded", actor.name, {
5521
+ id,
5522
+ type,
5523
+ size: bytes.byteLength
5524
+ });
5525
+ const name = path6.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
5526
+ return {
5527
+ id,
5528
+ type,
5529
+ size: bytes.byteLength,
5530
+ markdown: `![${name}](${IMAGE_SCHEME}${id})`
5531
+ };
5532
+ });
5533
+ }
5534
+ /** An uploaded image's bytes and kind, which its first bytes tell. */
5535
+ async readImage(id) {
5536
+ const file = this.paths.image(UlidSchema.parse(id));
5537
+ if (!await exists(file)) {
5538
+ throw new BoardError("NOT_FOUND", `image ${id} not found`);
5539
+ }
5540
+ const bytes = await readFile4(file);
5541
+ const type = imageTypeOf(bytes);
5542
+ if (type === null) {
5543
+ throw new BoardError("NOT_FOUND", `image ${id} is not an image the board shows`);
5544
+ }
5545
+ return { type, bytes };
5546
+ }
5112
5547
  /** Every cron, ended ones included, oldest first. */
5113
5548
  async listCrons() {
5114
5549
  const crons = [];
@@ -5500,6 +5935,13 @@ ${current.body}`);
5500
5935
  watchTurns(source) {
5501
5936
  this.turnsInFlight = source;
5502
5937
  }
5938
+ /**
5939
+ * Where `upload_image` reads a file from: the runner the calling turn runs on, through the server
5940
+ * that hosts the runners. Without one, as in the admin CLI, nothing can be uploaded.
5941
+ */
5942
+ readImagesWith(source) {
5943
+ this.imageSource = source;
5944
+ }
5503
5945
  /**
5504
5946
  * Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
5505
5947
  * turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
@@ -6329,6 +6771,8 @@ ${rationale}`}`);
6329
6771
  return this.updateCron(actor, VerbInputs.update_cron.parse(input));
6330
6772
  case "remove_cron":
6331
6773
  return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
6774
+ case "upload_image":
6775
+ return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
6332
6776
  default:
6333
6777
  throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
6334
6778
  }
@@ -6375,7 +6819,7 @@ ${rationale}`}`);
6375
6819
  }
6376
6820
  async readAgentRoleBody(agent) {
6377
6821
  const file = this.paths.agentRole(agent);
6378
- return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
6822
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
6379
6823
  }
6380
6824
  /** The steps of one finished turn, as the runner handed them over when it ended. */
6381
6825
  /**
@@ -6406,6 +6850,14 @@ ${rationale}`}`);
6406
6850
  }
6407
6851
  return computeMetrics({ events, tasks, citizens, actions, activeSignals, window, now });
6408
6852
  }
6853
+ /**
6854
+ * Each citizen's finished turns over a window and the tokens of those that counted them, from
6855
+ * the event log alone, which holds every turn's end whatever runner it ran on.
6856
+ */
6857
+ async tokenUse(window) {
6858
+ const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
6859
+ return tokenUse(events, window, this.now());
6860
+ }
6409
6861
  /** The board actions a finished turn took, from its transcript; null when it kept none. */
6410
6862
  async turnActions(agent, turnId) {
6411
6863
  const known = this.actionCounts.get(turnId);
@@ -6775,7 +7227,7 @@ ${rationale}`}`);
6775
7227
  return { project: slug, revision: 0, body: "" };
6776
7228
  }
6777
7229
  const { data, body } = await readLooseMarkdown(file);
6778
- const revision = z13.number().int().min(0).safeParse(data["revision"]);
7230
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
6779
7231
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
6780
7232
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
6781
7233
  return {
@@ -7049,7 +7501,7 @@ ${charter.purpose}
7049
7501
 
7050
7502
  ${charter.purpose}
7051
7503
 
7052
- Route every lesson with one question: about me, my craft, or the user, it goes in memory/core.md; about this codebase, it goes in the project's knowledge directory; something everyone should know, post it.
7504
+ Route every lesson with one question: about me, my craft, or the user, it goes in memory/core.md; about how this codebase behaves, it goes in the repo's AGENTS.md or README; about this project as a place (runners, access, flow, where results live), it goes in the project's knowledge; something everyone should know, post it.
7053
7505
  ` + (seed.length === 0 ? "" : `
7054
7506
  ${SEED_INSTRUCTIONS_HEADING}
7055
7507
 
@@ -7066,7 +7518,7 @@ ${seed}
7066
7518
  continue;
7067
7519
  }
7068
7520
  const file = this.paths.agentRole(agent.name);
7069
- const body = await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
7521
+ const body = await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
7070
7522
  const at = body.indexOf(`
7071
7523
  ${SEED_INSTRUCTIONS_HEADING}
7072
7524
  `);
@@ -7904,7 +8356,7 @@ ${charter.purpose}
7904
8356
  id: this.newId(),
7905
8357
  ...head,
7906
8358
  ts: this.now().toISOString(),
7907
- mentions: extractMentions(body)
8359
+ mentions: postMentions(body)
7908
8360
  });
7909
8361
  const { author } = head;
7910
8362
  await writeMarkdown(this.paths.messageFile(dir, frontmatter.id, author), frontmatter, body);
@@ -8282,7 +8734,7 @@ function decideWake(input) {
8282
8734
  }
8283
8735
 
8284
8736
  // packages/scheduler/dist/scheduler.js
8285
- import { z as z14 } from "zod";
8737
+ import { z as z17 } from "zod";
8286
8738
  function besideFields(beside) {
8287
8739
  if (beside === void 0) {
8288
8740
  return {};
@@ -8309,21 +8761,21 @@ var DEFAULT_TIMINGS = Object.freeze({
8309
8761
  scaleCooldownMs: 36e5,
8310
8762
  reflectionMs: 24 * 36e5
8311
8763
  });
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()
8764
+ var SchedulerTimingsSchema = z17.object({
8765
+ pollMs: z17.number().positive(),
8766
+ debounceMs: z17.number().nonnegative(),
8767
+ userDebounceMs: z17.number().nonnegative(),
8768
+ heartbeatMs: z17.number().positive(),
8769
+ waitingStageMs: z17.number().positive(),
8770
+ leaseSweepMs: z17.number().positive(),
8771
+ opsIntervalMs: z17.number().positive(),
8772
+ signalRepeatMs: z17.number().positive(),
8773
+ costReportMs: z17.number().positive(),
8774
+ staleThreadMs: z17.number().positive(),
8775
+ idleMemberMs: z17.number().positive(),
8776
+ homeConflictMs: z17.number().positive(),
8777
+ scaleCooldownMs: z17.number().positive(),
8778
+ reflectionMs: z17.number().positive()
8327
8779
  }).partial();
8328
8780
  function parseTimings(input) {
8329
8781
  const parsed = SchedulerTimingsSchema.parse(input);
@@ -8335,32 +8787,32 @@ function parseTimings(input) {
8335
8787
  }
8336
8788
  return overrides;
8337
8789
  }
8338
- var PendingSchema = z14.object({
8790
+ var PendingSchema = z17.object({
8339
8791
  dispatch: TurnDispatchSchema,
8340
- readyAt: z14.number(),
8341
- stageWakes: z14.array(TriggerSchema).optional(),
8342
- holder: z14.boolean().optional(),
8792
+ readyAt: z17.number(),
8793
+ stageWakes: z17.array(TriggerSchema).optional(),
8794
+ holder: z17.boolean().optional(),
8343
8795
  newestMessage: UlidSchema.optional(),
8344
- onlyMessages: z14.boolean().optional(),
8796
+ onlyMessages: z17.boolean().optional(),
8345
8797
  steeredThrough: UlidSchema.optional(),
8346
- onlyCrons: z14.boolean().optional()
8798
+ onlyCrons: z17.boolean().optional()
8347
8799
  });
8348
- var StateSchema = z14.object({
8349
- cursor: z14.string().nullable(),
8350
- lastHeartbeat: z14.record(z14.string(), z14.string()),
8800
+ var StateSchema = z17.object({
8801
+ cursor: z17.string().nullable(),
8802
+ lastHeartbeat: z17.record(z17.string(), z17.string()),
8351
8803
  /** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
8352
- waitingSeen: z14.record(z14.string(), z14.string()).default({}),
8804
+ waitingSeen: z17.record(z17.string(), z17.string()).default({}),
8353
8805
  /** 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),
8806
+ pending: z17.record(z17.string(), PendingSchema).default({}),
8807
+ opsReported: z17.record(z17.string(), z17.string()).default({}),
8808
+ releaseCounts: z17.record(z17.string(), z17.number().int().nonnegative()).default({}),
8809
+ lastOps: z17.string().nullable().default(null),
8810
+ lastScaled: z17.record(z17.string(), z17.string()).default({}),
8811
+ lastCostReport: z17.string().nullable().default(null),
8812
+ costSinceReport: z17.number().nonnegative().default(0),
8813
+ turnsSinceReport: z17.number().int().nonnegative().default(0),
8362
8814
  /** When each member last reflected, or was first seen; the cadence counts from here. */
8363
- lastReflection: z14.record(z14.string(), z14.string()).default({})
8815
+ lastReflection: z17.record(z17.string(), z17.string()).default({})
8364
8816
  });
8365
8817
  function messageOf(trigger) {
8366
8818
  return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
@@ -9933,11 +10385,11 @@ var TURN_CONTRACT = [
9933
10385
  "- 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
10386
  "- Silence is allowed. If the digest needs no reply, post nothing.",
9935
10387
  "- 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.",
10388
+ "- 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
10389
  "- 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
- "- 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
- "- 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.",
9940
- '- A procedure you have followed twice is a skill: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when the whole society would use it.',
10390
+ "- To show an image in a post, such as a screenshot or a chart, save it anywhere you can read it, such as a folder from `mktemp -d`, call upload_image with its path, and put the markdown it returns in the post, `![what it shows](image:<id>)`, with what the image shows as its alt text: the board keeps the image, so it shows in any conversation after the file is gone. It takes a PNG, JPEG, GIF, or WebP of at most 8 MB from your worktree, your home, or the system temp folder. An image committed on a task's branch, linked by its path in the task's worktree, still shows in place, and so does one at an https address. 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.",
10391
+ "- 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; how a codebase behaves (architecture, conventions, gotchas, how to build, test, and verify it) goes in its AGENTS.md or README, committed on a task's branch like any change to the code, or proposed in the task's thread or the project channel when it is outside the task at hand; a durable fact about a project as a place (runners, access, flow, where results live) 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.",
10392
+ '- A procedure you have followed twice is a candidate for a skill, which it is when it makes a citizen better at a kind of work on any project: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when more than one citizen would use it and nothing in it is tied to one place; a skill only you use stays yours, however many projects you use it on.',
9941
10393
  "- To take a published skill for yourself, install it in a temporary folder and copy it into your home: in a folder from `mktemp -d`, `npx -y skills add <source> --skill <name> -a universal --copy -y` writes `.agents/skills/<name>/` and a lock file there, and you copy that skill folder to skills/<name>/ in your home, named in lowercase letters, digits, and hyphens. Never install one inside a worktree, where it would be committed into the project, or with -g, which writes to the runner's own home, shared by every citizen on it and listed in no skills index.",
9942
10394
  "- Report memoryUpdated: true in the status object whenever you changed memory/core.md or a skill, so a warm session restarts with the new instructions.",
9943
10395
  "- Other turns of yours may be running at the same time and share your home. Change a file there with your edit tool, which refuses a file changed since you read it, and never rewrite one whole from an earlier read, with a shell redirect or a script, or you erase what another of your turns wrote.",
@@ -9948,7 +10400,7 @@ var PLANNING = [
9948
10400
  "- A task is a plan of stages between open and done. Hold the current stage with claim_task and finish it with advance_task; the next stage becomes current, and past the last one the task is done.",
9949
10401
  "- Name stages by what gets done. Plan the next few steps rather than everything, and reshape with plan_task as the work teaches you.",
9950
10402
  "- Assign a stage to a role when anyone in it could do it, to a citizen only when it must be them, and to nobody when anyone in the project could. A citizen named on a stage must be a member of the task's project; add them first. A stage wakes everyone it is open to when it comes up, so the narrower the assignee, the fewer turns it costs.",
9951
- "- Reshape rather than force: another round is an inserted stage, and a wait on something outside the society is a stage named for what it waits on. When a check finds work unfinished, send the task back to an earlier stage with update_task.",
10403
+ "- Reshape rather than force: another round is an inserted stage, and a wait on something outside the society is a stage named for what it waits on. When a check finds work unfinished, the holder of the current stage sends the task back to an earlier stage with update_task, and so may the user, the steward, and the concierge; anyone else asks the concierge in the task's thread.",
9952
10404
  "- A task that cannot start until other tasks finish names them with update_task blocked_by. While any of them is in play, its stage wakes nobody, neither when it comes up nor on a heartbeat; once the last has ended, done or abandoned, the stage wakes as if it had just come up, or wakes its holder. Nobody needs to clear the block or mention anyone when they end; change blocked_by only when the plan changes.",
9953
10405
  "- A gated stage is an independent check: nobody who held an earlier stage of the task may hold it, and only the user, the steward, and the concierge may add, remove, move, reassign, or ungate it. Ask them in the task's thread when a gate should change.",
9954
10406
  "- Commit your work on the task's branch before you advance, and say what you did in the advance note; it is the handover the next holder reads in the task's thread."
@@ -9967,15 +10419,15 @@ var GOVERNANCE = [
9967
10419
  "- 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
10420
  "- 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
10421
  ].join("\n");
9970
- function renderOnboardingPreamble(context) {
10422
+ function renderOnboardingPreamble(context2) {
9971
10423
  return [
9972
- `This is your first turn as ${context.agentName}.`,
10424
+ `This is your first turn as ${context2.agentName}.`,
9973
10425
  "",
9974
- `Your role in one paragraph: ${context.roleSummary}`,
10426
+ `Your role in one paragraph: ${context2.roleSummary}`,
9975
10427
  "",
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.`
10428
+ `You are working on project "${context2.project}" in the worktree at ${context2.worktree}.`,
10429
+ ...(context2.otherChannels ?? []).length === 0 ? [] : [
10430
+ `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
10431
  ],
9980
10432
  "Your memory is empty. Write durable lessons about yourself, your craft, or the user to memory/core.md in your home directory.",
9981
10433
  "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.",
@@ -9986,7 +10438,7 @@ function renderOnboardingPreamble(context) {
9986
10438
  function renderSkillsIndex(skills) {
9987
10439
  if (skills.length === 0) {
9988
10440
  return [
9989
- "None yet. Write the first one when you notice yourself doing something the same way twice."
10441
+ "None yet. Something you have done the same way twice is a candidate for the first; the turn contract says when it is one."
9990
10442
  ];
9991
10443
  }
9992
10444
  return [
@@ -10015,9 +10467,9 @@ var REFLECTION_INTRO = "This turn is for your memory; take no new work, and answ
10015
10467
  var RECONCILE = "- Reconcile first: merge each copy listed under Edits to reconcile in your home into the file beside it, then delete the copy.";
10016
10468
  var REFLECTION_STEPS = [
10017
10469
  "- Consolidate memory/core.md: keep it to lessons that still hold, about sixty lines at most, and move detail worth keeping to memory/<topic>.md in your home, which the board's search covers for you alone.",
10018
- '- Extract a skill: a procedure you have followed twice goes to skills/<name>/SKILL.md, frontmatter with `name` and a one-line `description` and then the steps, with any script or reference it uses in the same folder. Propose it with kind "skill" when other citizens would use it.',
10470
+ '- Extract a skill: a procedure you have followed twice is a candidate, and one that makes a citizen better at a kind of work on any project goes to skills/<name>/SKILL.md, frontmatter with `name` and a one-line `description` and then the steps, with any script or reference it uses in the same folder. Propose it with kind "skill" when more than one citizen would use it and nothing in it is tied to one place; one only you use stays yours.',
10019
10471
  "- Refresh profile.md: one paragraph on what you do well and what to send your way; the roster the front desk routes with is built from it.",
10020
- "- Share: a durable fact about this scope's codebase or process goes through write_knowledge; a norm the whole society should follow goes to the steward as a plain post in general.",
10472
+ "- Share: how a codebase behaves goes in its AGENTS.md or README through a task like any change to the code, so propose it in that task's thread or the project channel; a durable fact about this scope as a place (runners, access, flow, where results live) goes through write_knowledge; a norm the whole society should follow goes to the steward as a plain post in general.",
10021
10473
  "- Report memoryUpdated: true in the status object when core.md or a skill changed."
10022
10474
  ];
10023
10475
  var MAX_BODY_CHARS = 1500;
@@ -10061,10 +10513,25 @@ function signalLine(ts, signal) {
10061
10513
  ].filter((fact) => fact.length > 0);
10062
10514
  return `- [${ts}] ${signal.kind}: ${signal.summary} (${facts.join("; ")})`;
10063
10515
  }
10064
- function runnerLine(runner, projects) {
10516
+ function quotaPhrase(cli, quota, now) {
10517
+ const level = quotaLevel(quota, now);
10518
+ if (level === "none") {
10519
+ return `${cli} reports no plan limits`;
10520
+ }
10521
+ if (level === "limited") {
10522
+ return `${cli} limited until ${quota.limitedUntil ?? "an unknown time"}`;
10523
+ }
10524
+ const windows = quota.windows.map((window) => !windowCurrent(window, now) ? `${windowName(window)} reset since` : `${windowName(window)} ${window.usedPercent === null ? "?" : Math.floor(window.usedPercent)}%`);
10525
+ return `${cli} ${windows.length === 0 ? "allowed" : windows.join(", ")}${level === "warning" ? " (near a limit)" : ""} as of ${quota.observedAt}`;
10526
+ }
10527
+ function runnerLine(runner, projects, now) {
10065
10528
  const living = projects.filter((project) => project.runner === runner.name).map((project) => project.slug);
10066
10529
  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(", ")}`}`;
10530
+ const quotas = runner.clis.flatMap((cli) => {
10531
+ const quota = runner.quotas?.[cli];
10532
+ return quota === void 0 ? [] : [quotaPhrase(cli, quota, now)];
10533
+ });
10534
+ 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
10535
  }
10069
10536
  function profileLine(profile) {
10070
10537
  const line = profile.split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
@@ -10214,19 +10681,19 @@ function buildTurnPrompt(input) {
10214
10681
  }
10215
10682
  const machines = input.runners;
10216
10683
  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.", "");
10684
+ 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
10685
  if (machines.runners.length === 0) {
10219
10686
  lines.push("None registered yet.");
10220
10687
  }
10221
10688
  for (const runner of machines.runners) {
10222
- lines.push(runnerLine(runner, machines.projects));
10689
+ lines.push(runnerLine(runner, machines.projects, input.clock?.now ?? /* @__PURE__ */ new Date()));
10223
10690
  }
10224
10691
  }
10225
10692
  const knowledge = input.knowledge;
10226
10693
  if (knowledge !== void 0 && knowledge !== null) {
10227
10694
  lines.push("", `## Knowledge of ${dispatch.project}`, "");
10228
10695
  if (knowledge.topics.length === 0) {
10229
- lines.push("None yet. A durable fact about this scope's codebase or process is worth a write_knowledge call.");
10696
+ lines.push("None yet. A durable fact about this place, such as its runners, access, flow, or where results live, is worth a write_knowledge call.");
10230
10697
  } else {
10231
10698
  lines.push(`Topics under ${knowledge.dir}; open one with your file tools when it is relevant.`, "");
10232
10699
  for (const topic of knowledge.topics) {
@@ -10752,6 +11219,11 @@ var TurnHost = class {
10752
11219
  }
10753
11220
  }
10754
11221
  });
11222
+ if (outcome.quota !== void 0) {
11223
+ await this.board.recordQuota(turn.runner, job.cli, outcome.quota).catch((error) => {
11224
+ this.log.warn({ runner: turn.runner, cli: job.cli, error: String(error) }, "could not record a quota");
11225
+ });
11226
+ }
10755
11227
  if (recorded) {
10756
11228
  this.log.info({
10757
11229
  agent: job.agent,
@@ -10997,6 +11469,12 @@ var DEFAULT_GRACE_MS = 2 * 6e4;
10997
11469
  var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 6e4;
10998
11470
  var CONTROL_TIMEOUT_MS = 15e3;
10999
11471
  var FILE_TIMEOUT_MS = 6e4;
11472
+ var IMAGE_REFUSALS = {
11473
+ missing: "NOT_FOUND",
11474
+ outside: "FORBIDDEN",
11475
+ not_image: "VALIDATION",
11476
+ too_big: "VALIDATION"
11477
+ };
11000
11478
  var SWEEP_TIMEOUT_MS = 2 * 6e4;
11001
11479
  var DEFAULT_ACK_TIMEOUT_MS = 3e4;
11002
11480
  var RunnerHub = class {
@@ -11081,7 +11559,8 @@ var RunnerHub = class {
11081
11559
  os: hello.os,
11082
11560
  clis: hello.clis,
11083
11561
  capabilities: hello.capabilities,
11084
- version: hello.version
11562
+ version: hello.version,
11563
+ gitIdentityMissing: hello.gitIdentityMissing ?? null
11085
11564
  });
11086
11565
  this.runnerChanged(name);
11087
11566
  this.log.info({ runner: name, version: hello.version, clis: hello.clis, slots: hello.slots }, "runner registered");
@@ -11504,6 +11983,33 @@ var RunnerHub = class {
11504
11983
  }
11505
11984
  return diff;
11506
11985
  }
11986
+ /**
11987
+ * The image file a citizen's turn names for `upload_image`, read on the runner that turn runs on,
11988
+ * which refuses a file outside the turn's worktree, its citizen's home, and the system temp
11989
+ * folder, and anything but a PNG, JPEG, GIF, or WebP within the size limit; each refusal comes
11990
+ * back as a `BoardError` in the runner's words.
11991
+ */
11992
+ async readTurnImage(actor, file) {
11993
+ const turn = this.host.runningTurns().find((each) => each.agent === actor.name && each.scope === actor.scope && each.thread === actor.thread && each.channel === actor.channel);
11994
+ if (turn === void 0) {
11995
+ throw new BoardError("INVALID_STATE", `${actor.name} has no turn running in this conversation to read ${file} from`);
11996
+ }
11997
+ const seat = this.seats.get(turn.runner);
11998
+ if (seat === void 0 || seat.send === null) {
11999
+ throw new RunnerAwayError(`runner ${turn.runner}, where the turn runs, is not connected`);
12000
+ }
12001
+ if (!seat.hello.imageReads) {
12002
+ throw new BoardError("INVALID_STATE", `runner ${seat.name}, where the turn runs, cannot read images yet; upgrade it`);
12003
+ }
12004
+ const image = TurnImageSchema.nullable().parse(await this.request(seat, (request) => ({ type: "image", request, read: { turnId: turn.turnId, path: file } }), FILE_TIMEOUT_MS));
12005
+ if (image === null) {
12006
+ throw new BoardError("INVALID_STATE", `the turn is no longer running on runner ${seat.name}`);
12007
+ }
12008
+ if (image.kind === "refused") {
12009
+ throw new BoardError(IMAGE_REFUSALS[image.why], image.detail);
12010
+ }
12011
+ return Buffer.from(image.content, "base64");
12012
+ }
11507
12013
  /** The models a CLI offers, asked of a connected runner that has it; none when no runner has it. */
11508
12014
  /**
11509
12015
  * The connected runner a model list for `cli` comes from: the first of `prefer` that has the CLI,
@@ -11523,6 +12029,26 @@ var RunnerHub = class {
11523
12029
  }
11524
12030
  return ModelListSchema.parse(await this.request(seat, (request) => ({ type: "models", request, cli })));
11525
12031
  }
12032
+ /**
12033
+ * A CLI's plan quota, read on a runner without a turn and recorded on the runner's record.
12034
+ * Refused for a runner not connected with the CLI, and for one from before quota reads, which
12035
+ * would skip the request and leave the reader waiting.
12036
+ */
12037
+ async quota(runner, cli) {
12038
+ const seat = this.seats.get(runner);
12039
+ if (seat === void 0 || seat.send === null || !seat.hello.clis.includes(cli)) {
12040
+ throw new RunnerAwayError(`runner ${runner} is not connected with ${cli}`);
12041
+ }
12042
+ if (!seat.hello.quotaReads) {
12043
+ throw new BoardError("INVALID_STATE", `runner ${runner} cannot read quotas yet; upgrade it`);
12044
+ }
12045
+ const quota = CliQuotaSchema.nullable().parse(await this.request(seat, (request) => ({ type: "quota", request, cli }), FILE_TIMEOUT_MS));
12046
+ if (quota === null) {
12047
+ throw new BoardError("NOT_FOUND", `${cli} on runner ${runner} cannot read its quota`);
12048
+ }
12049
+ await this.board.recordQuota(runner, cli, quota);
12050
+ return quota;
12051
+ }
11526
12052
  /** Calls `listener` whenever a runner registers, connects, or goes away. */
11527
12053
  onRunnerChange(listener) {
11528
12054
  this.runnerListeners.add(listener);
@@ -11755,20 +12281,20 @@ async function handleMcpRequest(board2, actor, request, version) {
11755
12281
  import { Hono as Hono3 } from "hono";
11756
12282
  import { compress } from "hono/compress";
11757
12283
  import { streamSSE as streamSSE2 } from "hono/streaming";
11758
- import { z as z16, ZodError } from "zod";
12284
+ import { z as z19, ZodError } from "zod";
11759
12285
 
11760
12286
  // apps/server/dist/github-auth.js
11761
12287
  import { randomBytes as randomBytes2 } from "node:crypto";
11762
12288
  import { Hono } from "hono";
11763
- import { z as z15 } from "zod";
12289
+ import { z as z18 } from "zod";
11764
12290
  var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
11765
12291
  var STATE_TTL_MS = 10 * 6e4;
11766
12292
  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()
12293
+ var TokenAnswerSchema = z18.object({ access_token: z18.string().min(1) });
12294
+ var GithubUserSchema = z18.object({
12295
+ login: z18.string().min(1),
12296
+ id: z18.number().int(),
12297
+ avatar_url: z18.string().optional()
11772
12298
  });
11773
12299
  function githubAvatar(url) {
11774
12300
  try {
@@ -12191,7 +12717,7 @@ async function isFile(file) {
12191
12717
  return false;
12192
12718
  }
12193
12719
  }
12194
- function within(root, pathname) {
12720
+ function within2(root, pathname) {
12195
12721
  let decoded;
12196
12722
  try {
12197
12723
  decoded = decodeURIComponent(pathname);
@@ -12208,7 +12734,7 @@ function spaHandler(staticDir) {
12208
12734
  if (pathname.startsWith("/api/")) {
12209
12735
  return c.json({ error: "NOT_FOUND", message: `no route ${pathname}` }, 404);
12210
12736
  }
12211
- const file = within(root, pathname);
12737
+ const file = within2(root, pathname);
12212
12738
  const headers = { "x-content-type-options": "nosniff" };
12213
12739
  if (file !== null && await isFile(file)) {
12214
12740
  return c.body(await readFile5(file), 200, {
@@ -12243,19 +12769,19 @@ var ERROR_STATUS = {
12243
12769
  INVALID_STATE: 409
12244
12770
  };
12245
12771
  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({
12772
+ var RetireBodySchema = z19.object({ reason: z19.string().min(1) });
12773
+ var RunnerBodySchema = z19.object({ name: NameSchema });
12774
+ var EnrollmentApprovalSchema = z19.object({ name: NameSchema });
12775
+ var HomeRunnerBodySchema = z19.object({ runner: NameSchema.nullable() });
12776
+ var ModelBodySchema = z19.object({ model: ModelNameSchema.nullable() });
12777
+ var EffortBodySchema = z19.object({ effort: EffortSchema.nullable() });
12778
+ var HistoryLimitSchema = z19.coerce.number().int().min(1).max(500);
12779
+ var MessageIdsSchema = z19.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z19.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
12780
+ var TimezoneBodySchema = z19.object({ timezone: TimeZoneSchema });
12781
+ var ChannelBodySchema = z19.object({
12256
12782
  project: NameSchema.nullable().default(null),
12257
12783
  name: NameSchema,
12258
- purpose: z16.string().min(1)
12784
+ purpose: z19.string().min(1)
12259
12785
  });
12260
12786
  function bearer2(c) {
12261
12787
  const header = c.req.header("authorization") ?? "";
@@ -12263,7 +12789,7 @@ function bearer2(c) {
12263
12789
  return match?.[1]?.trim() ?? null;
12264
12790
  }
12265
12791
  function createApp(deps) {
12266
- const { board: board2, version, turns: turns2, scheduler: scheduler2, models } = deps;
12792
+ const { board: board2, version, turns: turns2, scheduler: scheduler2, models, quotas } = deps;
12267
12793
  const eventStreamPingMs = deps.eventStreamPingMs ?? 15e3;
12268
12794
  const app2 = new Hono3();
12269
12795
  app2.get("/health", (c) => c.json({ ok: true, version }));
@@ -12333,6 +12859,17 @@ function createApp(deps) {
12333
12859
  api.get("/agents/:name/history/:commit", async (c) => c.json(await board2.homeChange(c.req.param("name"), c.req.param("commit"))));
12334
12860
  api.get("/roles", async (c) => c.json(await board2.listRoles()));
12335
12861
  api.get("/runners", async (c) => c.json(await board2.listRunners()));
12862
+ api.post("/runners/:name/quota/:cli", async (c) => {
12863
+ if (c.get("actor").role !== USER_ROLE) {
12864
+ throw new BoardError("FORBIDDEN", "only the user reads a runner's quota");
12865
+ }
12866
+ const runner = NameSchema.parse(c.req.param("name"));
12867
+ const cli = CliKindSchema.parse(c.req.param("cli"));
12868
+ if (quotas === void 0) {
12869
+ throw new RunnerAwayError("this server asks no runner for quotas");
12870
+ }
12871
+ return c.json(await quotas.read(runner, cli));
12872
+ });
12336
12873
  api.post("/runners", async (c) => {
12337
12874
  const body = RunnerBodySchema.parse(await c.req.json());
12338
12875
  return c.json(await board2.addRunner(c.get("actor"), body.name));
@@ -12369,6 +12906,10 @@ function createApp(deps) {
12369
12906
  const window = MetricsWindowSchema.parse(c.req.query("window") ?? "7d");
12370
12907
  return c.json(await board2.metrics(window, new Set(scheduler2?.activeSignals ?? [])));
12371
12908
  });
12909
+ api.get("/tokens", async (c) => {
12910
+ const window = MetricsWindowSchema.parse(c.req.query("window") ?? "24h");
12911
+ return c.json(await board2.tokenUse(window));
12912
+ });
12372
12913
  api.get("/society/knowledge", async (c) => c.json(await board2.listKnowledge(null)));
12373
12914
  api.get("/skills", async (c) => c.json(await board2.listSocietySkills()));
12374
12915
  api.post("/agents/:name/retire", async (c) => {
@@ -12412,7 +12953,7 @@ function createApp(deps) {
12412
12953
  });
12413
12954
  api.put("/roles/:name", async (c) => {
12414
12955
  const charter = RoleCharterSchema.parse({
12415
- ...z16.record(z16.string(), z16.unknown()).parse(await c.req.json()),
12956
+ ...z19.record(z19.string(), z19.unknown()).parse(await c.req.json()),
12416
12957
  name: c.req.param("name")
12417
12958
  });
12418
12959
  return c.json(await board2.setRoleCharter(c.get("actor"), charter));
@@ -12451,6 +12992,15 @@ function createApp(deps) {
12451
12992
  return c.json(await deps.runners.branchDiff(task.project, `task/${task.id}`, BranchPathSchema.parse(c.req.param("path"))));
12452
12993
  });
12453
12994
  api.get("/tasks/:id/files/:path{.+}", (c) => taskFile(c, c.req.param("path")));
12995
+ api.get("/images/:id", async (c) => {
12996
+ const { type, bytes } = await board2.readImage(UlidSchema.parse(c.req.param("id")));
12997
+ return c.body(new Uint8Array(bytes), 200, {
12998
+ "content-type": IMAGE_MEDIA_TYPES[type],
12999
+ "cache-control": "private, max-age=31536000, immutable",
13000
+ "x-content-type-options": "nosniff",
13001
+ "content-security-policy": "default-src 'none'; sandbox"
13002
+ });
13003
+ });
12454
13004
  api.get("/threads", async (c) => c.json(await board2.listThreads()));
12455
13005
  api.get("/requests", async (c) => c.json(await board2.listRequests()));
12456
13006
  api.get("/threads/:id", async (c) => {
@@ -12762,6 +13312,35 @@ var ModelCatalog = class {
12762
13312
  }
12763
13313
  };
12764
13314
 
13315
+ // apps/server/dist/quotas.js
13316
+ var MINUTE = 6e4;
13317
+ var QuotaReads = class {
13318
+ runners;
13319
+ floorMs;
13320
+ now;
13321
+ kept = /* @__PURE__ */ new Map();
13322
+ constructor(runners2, floorMs = MINUTE, now = Date.now) {
13323
+ this.runners = runners2;
13324
+ this.floorMs = floorMs;
13325
+ this.now = now;
13326
+ }
13327
+ read(runner, cli) {
13328
+ const key = `${runner}/${cli}`;
13329
+ const kept = this.kept.get(key);
13330
+ if (kept !== void 0 && this.now() - kept.at < this.floorMs) {
13331
+ return kept.quota;
13332
+ }
13333
+ const quota = this.runners.quota(runner, cli);
13334
+ this.kept.set(key, { at: this.now(), quota });
13335
+ quota.catch(() => {
13336
+ if (this.kept.get(key)?.quota === quota) {
13337
+ this.kept.delete(key);
13338
+ }
13339
+ });
13340
+ return quota;
13341
+ }
13342
+ };
13343
+
12765
13344
  // apps/server/dist/turn-hub.js
12766
13345
  var TurnHub = class {
12767
13346
  capacity;
@@ -12844,6 +13423,7 @@ var host = new TurnHost({
12844
13423
  });
12845
13424
  var runners = new RunnerHub({ board, host, version: VERSION, log });
12846
13425
  board.watchTurns(() => host.inFlight());
13426
+ board.readImagesWith((actor, file) => runners.readTurnImage(actor, file));
12847
13427
  var concurrency = config.concurrency ?? Number.POSITIVE_INFINITY;
12848
13428
  var timings = parseTimings(JSON.parse(process.env["STELLARIS_TIMINGS"] ?? "{}"));
12849
13429
  var scheduler = new Scheduler({ board, runner: runners, log, concurrency, timings });
@@ -12869,6 +13449,7 @@ var app = createApp({
12869
13449
  turns,
12870
13450
  scheduler: view,
12871
13451
  models: new ModelCatalog(runners),
13452
+ quotas: new QuotaReads(runners),
12872
13453
  runners,
12873
13454
  webDir,
12874
13455
  signIn: { github: config.github, log },