@kubitnodes/stellaris 0.3.22 → 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 (54) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +556 -413
  3. package/dist/server/index.js +666 -473
  4. package/package.json +1 -1
  5. package/web/dist/assets/BackLink-DGYs80bb.js +1 -0
  6. package/web/dist/assets/{Button-Yoe21NQ2.js → Button-f4jgBZY1.js} +1 -1
  7. package/web/dist/assets/{ChannelForms-55otlg-m.js → ChannelForms-q9zdj4p2.js} +1 -1
  8. package/web/dist/assets/{ChannelView-DJvLzg0N.js → ChannelView-CGs_aWL3.js} +2 -2
  9. package/web/dist/assets/{CitizenView-m1K2aTvK.js → CitizenView-gcIo_sKt.js} +2 -2
  10. package/web/dist/assets/CitizensView-DZ7S8vnJ.js +1 -0
  11. package/web/dist/assets/{CliIcon-DbMYJdlU.js → CliIcon-CLHaA65A.js} +1 -1
  12. package/web/dist/assets/{CronsView-B4kXk5Ld.js → CronsView-1wns6Uwv.js} +1 -1
  13. package/web/dist/assets/{KnowledgeView-aSGxDDKy.js → KnowledgeView-Cxfi2-Pt.js} +1 -1
  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-BveU1yJj.js → Overview-CS9HCSmm.js} +1 -1
  19. package/web/dist/assets/{Pane-CLjdfBMM.js → Pane-CJYRVkwh.js} +1 -1
  20. package/web/dist/assets/{Picker-CXOe5Xdu.js → Picker-DoxCN6C7.js} +1 -1
  21. package/web/dist/assets/{ProjectView-oa4Z46ED.js → ProjectView-CJ3r4UdN.js} +1 -1
  22. package/web/dist/assets/ProposalView-DLn9dNPp.js +2 -0
  23. package/web/dist/assets/{ProposalsView-BZHivSEm.js → ProposalsView-B-cOxTsQ.js} +1 -1
  24. package/web/dist/assets/{Quota-BC7dpnwd.js → Quota-Bo7UdWTV.js} +1 -1
  25. package/web/dist/assets/{RunnersView-BrJF-aLU.js → RunnersView-DlQruhwp.js} +1 -1
  26. package/web/dist/assets/{SocietyView-C1yosPuf.js → SocietyView-Bl4ErnV8.js} +1 -1
  27. package/web/dist/assets/{StickyList-7tAlUDCN.js → StickyList-MVP34ytV.js} +1 -1
  28. package/web/dist/assets/{SubjectThread-CCq6ZAnR.js → SubjectThread-BFFnHxIF.js} +1 -1
  29. package/web/dist/assets/{TaskFileView-BEXQJQQ6.js → TaskFileView-GZ0eQFUb.js} +2 -2
  30. package/web/dist/assets/{TaskView-Cfhb7NuO.js → TaskView-DppFelLW.js} +2 -2
  31. package/web/dist/assets/{TasksView-3XljXcoi.js → TasksView-DDNXzeG4.js} +1 -1
  32. package/web/dist/assets/{ThreadForms-D2y5dXSC.js → ThreadForms-C846TvtS.js} +1 -1
  33. package/web/dist/assets/{ThreadItem-DeIhhmyu.js → ThreadItem-C8cou6wD.js} +1 -1
  34. package/web/dist/assets/{ThreadView-CbUnt9WL.js → ThreadView-DvSGDYoD.js} +1 -1
  35. package/web/dist/assets/{citizen--bqux6i_.js → citizen-B2Dlbcpe.js} +1 -1
  36. package/web/dist/assets/{compose-DNc3_c5x.js → compose-BmqQmcZH.js} +1 -1
  37. package/web/dist/assets/{format-B6fjC0_a.js → format-DT52SbbR.js} +1 -1
  38. package/web/dist/assets/{governance-RBQ4Lam0.js → governance-Cr5OlQnw.js} +1 -1
  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-B2Qchd_6.js → mentions-D5hNgtg6.js} +1 -1
  42. package/web/dist/assets/{tasks-DCUj5w_d.js → tasks-R3YgpYTe.js} +1 -1
  43. package/web/dist/assets/useMatch-pEF6RJ6Q.js +1 -0
  44. package/web/dist/index.html +9 -9
  45. package/web/dist/assets/BackLink-CD-iuy-h.js +0 -1
  46. package/web/dist/assets/CitizensView-BeTgmrKE.js +0 -1
  47. package/web/dist/assets/Markdown-c9bCCLHg.js +0 -30
  48. package/web/dist/assets/MessageItem-DKJKMqs-.js +0 -1
  49. package/web/dist/assets/MetricsView-D2TTry5O.js +0 -1
  50. package/web/dist/assets/NeedsYouView-C3celRei.js +0 -1
  51. package/web/dist/assets/ProposalView-B4T-EoX9.js +0 -2
  52. package/web/dist/assets/index-CyCty7a2.js +0 -23
  53. package/web/dist/assets/index-GRqxKN35.css +0 -2
  54. package/web/dist/assets/useMatch-CfbBQirn.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 z15 } 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",
@@ -1037,130 +1039,175 @@ var DecisionSchema = z5.object({
1037
1039
  });
1038
1040
 
1039
1041
  // packages/shared/dist/verbs.js
1042
+ import { z as z7 } from "zod";
1043
+
1044
+ // packages/shared/dist/images.js
1040
1045
  import { z as z6 } from "zod";
1041
- var MomentSchema = z6.iso.datetime({ offset: true });
1042
- var CronTitleSchema = z6.string().trim().min(1).max(120);
1043
- var CronNoteSchema = z6.string().trim().min(1).max(4e3);
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);
1044
1091
  var VerbInputs = {
1045
- post_message: z6.object({
1092
+ post_message: z7.object({
1046
1093
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
1047
1094
  channel: ChannelRefSchema.optional(),
1048
- body: z6.string().min(1),
1095
+ body: z7.string().min(1),
1049
1096
  thread_id: UlidSchema.optional()
1050
1097
  }),
1051
- read_inbox: z6.object({
1098
+ read_inbox: z7.object({
1052
1099
  since_cursor: UlidSchema.nullable().optional(),
1053
- limit: z6.number().int().positive().max(200).default(50),
1054
- advance: z6.boolean().default(true)
1100
+ limit: z7.number().int().positive().max(200).default(50),
1101
+ advance: z7.boolean().default(true)
1055
1102
  }),
1056
- search: z6.object({
1057
- query: z6.string().min(1),
1103
+ search: z7.object({
1104
+ query: z7.string().min(1),
1058
1105
  project: NameSchema.optional(),
1059
1106
  channel: ChannelRefSchema.optional(),
1060
- limit: z6.number().int().positive().max(100).default(20)
1107
+ limit: z7.number().int().positive().max(100).default(20)
1061
1108
  }),
1062
1109
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
1063
- open_thread: z6.object({
1110
+ open_thread: z7.object({
1064
1111
  task_id: UlidSchema.optional(),
1065
1112
  proposal_id: UlidSchema.optional(),
1066
1113
  channel: ChannelRefSchema.optional(),
1067
- title: z6.string().min(1).max(200).optional()
1114
+ title: z7.string().min(1).max(200).optional()
1068
1115
  }),
1069
- close_thread: z6.object({ thread_id: UlidSchema, summary: z6.string().min(1) }),
1070
- create_task: z6.object({
1116
+ close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
1117
+ create_task: z7.object({
1071
1118
  project: NameSchema,
1072
- title: z6.string().min(1).max(200),
1073
- body: z6.string().default(""),
1119
+ title: z7.string().min(1).max(200),
1120
+ body: z7.string().default(""),
1074
1121
  parent_id: UlidSchema.optional(),
1075
- required_capabilities: z6.array(z6.string()).default([]),
1122
+ required_capabilities: z7.array(z7.string()).default([]),
1076
1123
  /** The plan; without it the task gets one stage, work, that anyone in the project may take. */
1077
- stages: z6.array(PlanStageSchema).min(1).optional(),
1124
+ stages: z7.array(PlanStageSchema).min(1).optional(),
1078
1125
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
1079
1126
  channel: NameSchema.default("general")
1080
1127
  }),
1081
- claim_task: z6.object({ task_id: UlidSchema }),
1082
- release_task: z6.object({ task_id: UlidSchema }),
1083
- update_task: z6.object({
1128
+ claim_task: z7.object({ task_id: UlidSchema }),
1129
+ release_task: z7.object({ task_id: UlidSchema }),
1130
+ update_task: z7.object({
1084
1131
  task_id: UlidSchema,
1085
1132
  /** Move the task back to this earlier stage. */
1086
1133
  stage: StageIdSchema.optional(),
1087
- status: z6.enum(["abandoned"]).optional(),
1134
+ status: z7.enum(["abandoned"]).optional(),
1088
1135
  /** Posted to the task's thread as the caller, marked with the step when the task moved. */
1089
- note: z6.string().min(1).optional(),
1090
- blocked_by: z6.array(UlidSchema).optional(),
1136
+ note: z7.string().min(1).optional(),
1137
+ blocked_by: z7.array(UlidSchema).optional(),
1091
1138
  /**
1092
1139
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
1093
1140
  * commit's subject and body; null unlinks it.
1094
1141
  */
1095
- pull_request: z6.object({
1142
+ pull_request: z7.object({
1096
1143
  url: PullRequestUrlSchema,
1097
1144
  merge_subject: MergeSubjectSchema.optional(),
1098
- merge_body: z6.string().max(2e4).optional()
1145
+ merge_body: z7.string().max(2e4).optional()
1099
1146
  }).nullable().optional()
1100
1147
  }),
1101
- get_task: z6.object({ task_id: UlidSchema }),
1102
- subscribe: z6.object({ channel: ChannelRefSchema }),
1103
- unsubscribe: z6.object({ channel: ChannelRefSchema }),
1104
- propose: z6.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({
1105
1152
  kind: ProposalKindSchema,
1106
- charter: z6.record(z6.string(), z6.unknown()),
1107
- rationale: z6.string().default("")
1153
+ charter: z7.record(z7.string(), z7.unknown()),
1154
+ rationale: z7.string().default("")
1108
1155
  }),
1109
- approve: z6.object({ proposal_id: UlidSchema, reason: z6.string().optional() }),
1110
- reject: z6.object({ proposal_id: UlidSchema, reason: z6.string().min(1) }),
1111
- create_project: z6.object({
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({
1112
1159
  slug: NameSchema,
1113
- name: z6.string().min(1).optional(),
1114
- repo: z6.string().min(1).nullable().default(null),
1160
+ name: z7.string().min(1).optional(),
1161
+ repo: z7.string().min(1).nullable().default(null),
1115
1162
  default_branch: DefaultBranchSchema.default("main"),
1116
1163
  on_done: CompletionEffectSchema.optional()
1117
1164
  }),
1118
- join_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1119
- leave_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1120
- write_knowledge: z6.object({
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({
1121
1168
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
1122
1169
  project: NameSchema.nullable().default(null),
1123
1170
  topic: NameSchema,
1124
- body: z6.string().min(1),
1171
+ body: z7.string().min(1),
1125
1172
  /** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
1126
- revision: z6.number().int().min(0)
1173
+ revision: z7.number().int().min(0)
1127
1174
  }),
1128
- remove_knowledge: z6.object({
1175
+ remove_knowledge: z7.object({
1129
1176
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
1130
1177
  project: NameSchema.nullable().default(null),
1131
1178
  topic: NameSchema
1132
1179
  }),
1133
- plan_task: z6.object({
1180
+ plan_task: z7.object({
1134
1181
  task_id: UlidSchema,
1135
- stages: z6.array(PlanEditStageSchema),
1182
+ stages: z7.array(PlanEditStageSchema),
1136
1183
  on_done: CompletionEffectSchema.optional()
1137
1184
  }),
1138
1185
  /** The note is posted to the task's thread as the caller, marked with the step. */
1139
- advance_task: z6.object({ task_id: UlidSchema, note: z6.string().min(1).optional() }),
1140
- configure_project: z6.object({
1186
+ advance_task: z7.object({ task_id: UlidSchema, note: z7.string().min(1).optional() }),
1187
+ configure_project: z7.object({
1141
1188
  project: NameSchema,
1142
1189
  on_done: CompletionEffectSchema.optional(),
1143
1190
  /** A new display name; the slug, which everything refers to the project by, never changes. */
1144
- name: z6.string().trim().min(1).max(120).optional(),
1191
+ name: z7.string().trim().min(1).max(120).optional(),
1145
1192
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
1146
1193
  default_branch: DefaultBranchSchema.optional()
1147
1194
  }),
1148
- archive_project: z6.object({ project: NameSchema, reason: z6.string().min(1) }),
1149
- create_channel: z6.object({
1195
+ archive_project: z7.object({ project: NameSchema, reason: z7.string().min(1) }),
1196
+ create_channel: z7.object({
1150
1197
  /** A project, or null for a channel of the society. */
1151
1198
  project: NameSchema.nullable().default(null),
1152
1199
  name: NameSchema,
1153
- purpose: z6.string().min(1)
1200
+ purpose: z7.string().min(1)
1154
1201
  }),
1155
- archive_channel: z6.object({ channel: ChannelRefSchema, reason: z6.string().min(1) }),
1156
- update_dashboard: z6.object({
1202
+ archive_channel: z7.object({ channel: ChannelRefSchema, reason: z7.string().min(1) }),
1203
+ update_dashboard: z7.object({
1157
1204
  project: NameSchema,
1158
1205
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
1159
- body: z6.string().min(1),
1206
+ body: z7.string().min(1),
1160
1207
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
1161
- revision: z6.number().int().min(0)
1208
+ revision: z7.number().int().min(0)
1162
1209
  }),
1163
- create_cron: z6.object({
1210
+ create_cron: z7.object({
1164
1211
  title: CronTitleSchema,
1165
1212
  /** What to do when it fires; quoted in the prompt of every turn it starts. */
1166
1213
  note: CronNoteSchema,
@@ -1179,10 +1226,10 @@ var VerbInputs = {
1179
1226
  /** An open channel of that place other than general, whose conversation it fires in. */
1180
1227
  channel: NameSchema.optional()
1181
1228
  }),
1182
- update_cron: z6.object({
1229
+ update_cron: z7.object({
1183
1230
  cron_id: UlidSchema,
1184
1231
  /** Pause it, or resume it, which starts its schedule afresh from now. */
1185
- paused: z6.boolean().optional(),
1232
+ paused: z7.boolean().optional(),
1186
1233
  title: CronTitleSchema.optional(),
1187
1234
  note: CronNoteSchema.optional(),
1188
1235
  /** A new expression, or `at` for one time; either starts the schedule afresh. */
@@ -1191,8 +1238,12 @@ var VerbInputs = {
1191
1238
  timezone: TimeZoneSchema.nullable().optional(),
1192
1239
  at: MomentSchema.optional()
1193
1240
  }),
1194
- remove_cron: z6.object({ cron_id: UlidSchema, reason: z6.string().trim().min(1) }),
1195
- remove_skill: z6.object({ name: NameSchema, reason: z6.string().trim().min(1) })
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
+ })
1196
1247
  };
1197
1248
  var VERB_DESCRIPTIONS = {
1198
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.",
@@ -1203,7 +1254,7 @@ var VERB_DESCRIPTIONS = {
1203
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.",
1204
1255
  claim_task: "Hold the task's current stage. Claims are leases renewed by every turn that touches the task.",
1205
1256
  release_task: "Let go of the stage you hold so someone else can take it.",
1206
- 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.",
1207
1258
  get_task: "Read a task with its plan, its brief, and the messages of its thread.",
1208
1259
  subscribe: "Subscribe to a channel. Subscriptions feed your digest; they never wake you.",
1209
1260
  unsubscribe: "Unsubscribe from a channel.",
@@ -1225,11 +1276,12 @@ var VERB_DESCRIPTIONS = {
1225
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).",
1226
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.",
1227
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.",
1228
- 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."
1229
1281
  };
1230
1282
 
1231
1283
  // packages/shared/dist/events.js
1232
- import { z as z7 } from "zod";
1284
+ import { z as z8 } from "zod";
1233
1285
  var BOARD_EVENT_TYPES = [
1234
1286
  "society.initialized",
1235
1287
  "project.added",
@@ -1293,21 +1345,22 @@ var BOARD_EVENT_TYPES = [
1293
1345
  "cron.updated",
1294
1346
  "cron.fired",
1295
1347
  "cron.skipped",
1296
- "cron.ended"
1348
+ "cron.ended",
1349
+ "image.uploaded"
1297
1350
  ];
1298
- var BoardEventTypeSchema = z7.enum(BOARD_EVENT_TYPES);
1299
- var BoardEventSchema = z7.object({
1351
+ var BoardEventTypeSchema = z8.enum(BOARD_EVENT_TYPES);
1352
+ var BoardEventSchema = z8.object({
1300
1353
  id: UlidSchema,
1301
1354
  ts: IsoDateTimeSchema,
1302
1355
  type: BoardEventTypeSchema,
1303
1356
  actor: NameSchema,
1304
- payload: z7.record(z7.string(), z7.unknown())
1357
+ payload: z8.record(z8.string(), z8.unknown())
1305
1358
  });
1306
- var UsageSchema = z7.object({
1307
- inputTokens: z7.number().int().nonnegative(),
1308
- outputTokens: z7.number().int().nonnegative(),
1309
- cacheReadTokens: z7.number().int().nonnegative().default(0),
1310
- cacheWriteTokens: z7.number().int().nonnegative().default(0)
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)
1311
1364
  });
1312
1365
  function addUsage(a, b) {
1313
1366
  return {
@@ -1317,14 +1370,14 @@ function addUsage(a, b) {
1317
1370
  cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
1318
1371
  };
1319
1372
  }
1320
- var TurnStatusSchema = z7.object({
1321
- summary: z7.string().min(1),
1322
- claimsHeld: z7.array(UlidSchema).default([]),
1323
- blockedOn: z7.array(z7.string()).default([]),
1324
- needsUserDecision: z7.boolean().default(false),
1325
- memoryUpdated: z7.boolean().default(false)
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)
1326
1379
  });
1327
- var TurnExitReasonSchema = z7.enum([
1380
+ var TurnExitReasonSchema = z8.enum([
1328
1381
  "completed",
1329
1382
  "timeout",
1330
1383
  "error",
@@ -1332,50 +1385,50 @@ var TurnExitReasonSchema = z7.enum([
1332
1385
  "blocked",
1333
1386
  "stopped"
1334
1387
  ]);
1335
- var AgentEventSchema = z7.discriminatedUnion("type", [
1336
- z7.object({
1337
- type: z7.literal("turn_started"),
1388
+ var AgentEventSchema = z8.discriminatedUnion("type", [
1389
+ z8.object({
1390
+ type: z8.literal("turn_started"),
1338
1391
  agent: NameSchema,
1339
- session: z7.string(),
1392
+ session: z8.string(),
1340
1393
  runner: NameSchema,
1341
1394
  /** The model the CLI reports running, when it reports one. */
1342
- model: z7.string().optional()
1395
+ model: z8.string().optional()
1343
1396
  }),
1344
1397
  /**
1345
1398
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
1346
1399
  * messages it delivered and the text the CLI was given.
1347
1400
  */
1348
- z7.object({
1349
- type: z7.literal("steered"),
1350
- steer: z7.string().min(1),
1351
- messages: z7.array(UlidSchema).optional(),
1352
- text: z7.string().optional()
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()
1353
1406
  }),
1354
- z7.object({ type: z7.literal("text"), delta: z7.string() }),
1355
- z7.object({ type: z7.literal("tool_call"), name: z7.string(), input: z7.unknown() }),
1356
- z7.object({
1357
- type: z7.literal("tool_result"),
1358
- name: z7.string(),
1359
- ok: z7.boolean(),
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(),
1360
1413
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
1361
- output: z7.string().optional()
1414
+ output: z8.string().optional()
1362
1415
  }),
1363
- z7.object({ type: z7.literal("approval_requested"), kind: z7.string(), detail: z7.unknown() }),
1364
- z7.object({
1365
- type: z7.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"),
1366
1419
  usage: UsageSchema,
1367
- costUsd: z7.number().nonnegative(),
1420
+ costUsd: z8.number().nonnegative(),
1368
1421
  status: TurnStatusSchema.nullable(),
1369
1422
  exitReason: TurnExitReasonSchema
1370
1423
  }),
1371
- z7.object({ type: z7.literal("error"), message: z7.string() })
1424
+ z8.object({ type: z8.literal("error"), message: z8.string() })
1372
1425
  ]);
1373
- var TranscriptEntrySchema = z7.object({
1426
+ var TranscriptEntrySchema = z8.object({
1374
1427
  ts: IsoDateTimeSchema,
1375
1428
  event: AgentEventSchema
1376
1429
  });
1377
- var LiveTurnEventSchema = z7.object({
1378
- seq: z7.number().int().positive(),
1430
+ var LiveTurnEventSchema = z8.object({
1431
+ seq: z8.number().int().positive(),
1379
1432
  ts: IsoDateTimeSchema,
1380
1433
  agent: NameSchema,
1381
1434
  project: NameSchema,
@@ -1387,10 +1440,10 @@ var LiveTurnEventSchema = z7.object({
1387
1440
  });
1388
1441
 
1389
1442
  // packages/shared/dist/turns.js
1390
- import { z as z9 } from "zod";
1443
+ import { z as z10 } from "zod";
1391
1444
 
1392
1445
  // packages/shared/dist/runner.js
1393
- import { z as z8 } from "zod";
1446
+ import { z as z9 } from "zod";
1394
1447
  var RUNNER_PROTOCOL = 1;
1395
1448
  var PATH_TOKENS = {
1396
1449
  home: "{{stellaris:home}}",
@@ -1401,75 +1454,77 @@ var SERVER_TOKEN = "{{stellaris:server}}";
1401
1454
  var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
1402
1455
  var HOME_SCRATCH = "scratch";
1403
1456
  var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1404
- var RunnerHelloSchema = z8.object({
1405
- protocol: z8.number().int().positive(),
1406
- version: z8.string().min(1),
1457
+ var RunnerHelloSchema = z9.object({
1458
+ protocol: z9.number().int().positive(),
1459
+ version: z9.string().min(1),
1407
1460
  os: RunnerOsSchema,
1408
- clis: z8.array(CliKindSchema),
1461
+ clis: z9.array(CliKindSchema),
1409
1462
  /** The CLIs whose adapter can keep a session warm between turns. */
1410
- residentClis: z8.array(CliKindSchema).default([]),
1463
+ residentClis: z9.array(CliKindSchema).default([]),
1411
1464
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
1412
- steerableClis: z8.array(CliKindSchema).default([]),
1465
+ steerableClis: z9.array(CliKindSchema).default([]),
1413
1466
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
1414
- stoppableClis: z8.array(CliKindSchema).default([]),
1467
+ stoppableClis: z9.array(CliKindSchema).default([]),
1415
1468
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
1416
- branchReads: z8.boolean().default(false),
1469
+ branchReads: z9.boolean().default(false),
1417
1470
  /** Whether the runner lists what a branch changed; one from before never does. */
1418
- branchChanges: z8.boolean().default(false),
1471
+ branchChanges: z9.boolean().default(false),
1419
1472
  /** Whether the runner answers a read of a CLI's quota; one from before never does. */
1420
- quotaReads: z8.boolean().default(false),
1421
- capabilities: z8.array(z8.string()).default([]),
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([]),
1422
1477
  /**
1423
1478
  * What the runner's account lacks of the git identity citizens' own commits carry, checked
1424
1479
  * before each hello outside any repository; empty when it has both. Absent from a runner from
1425
1480
  * before the check, which no default fills in, so the board marks only what a runner reported.
1426
1481
  */
1427
- gitIdentityMissing: z8.array(GitIdentityPartSchema).optional(),
1482
+ gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
1428
1483
  /** Turns the runner runs at once, or null for no limit. */
1429
- slots: z8.number().int().positive().nullable(),
1484
+ slots: z9.number().int().positive().nullable(),
1430
1485
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1431
- turns: z8.array(UlidSchema).default([])
1486
+ turns: z9.array(UlidSchema).default([])
1432
1487
  });
1433
- var RunnerWelcomeSchema = z8.object({ name: NameSchema, version: z8.string() });
1434
- var RunnerEnrollRequestSchema = z8.object({
1435
- protocol: z8.number().int().positive(),
1436
- version: z8.string().min(1).max(64),
1437
- hostname: z8.string().min(1).max(253),
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),
1438
1493
  os: RunnerOsSchema,
1439
- clis: z8.array(CliKindSchema).max(8),
1440
- capabilities: z8.array(z8.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([])
1441
1496
  });
1442
- var RunnerEnrollmentSchema = z8.object({
1443
- deviceCode: z8.string().min(1),
1444
- userCode: z8.string().min(1),
1497
+ var RunnerEnrollmentSchema = z9.object({
1498
+ deviceCode: z9.string().min(1),
1499
+ userCode: z9.string().min(1),
1445
1500
  expiresAt: IsoDateTimeSchema,
1446
- intervalMs: z8.number().int().positive()
1501
+ intervalMs: z9.number().int().positive()
1447
1502
  });
1448
- var RunnerEnrollPollSchema = z8.object({ deviceCode: z8.string().min(1) });
1449
- var RunnerEnrollStatusSchema = z8.discriminatedUnion("status", [
1450
- z8.object({ status: z8.literal("pending") }),
1451
- z8.object({ status: z8.literal("approved"), name: NameSchema, token: z8.string().min(1) }),
1452
- z8.object({ status: z8.literal("denied") })
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") })
1453
1508
  ]);
1454
- var PendingEnrollmentSchema = z8.object({
1455
- userCode: z8.string().min(1),
1456
- hostname: z8.string(),
1509
+ var PendingEnrollmentSchema = z9.object({
1510
+ userCode: z9.string().min(1),
1511
+ hostname: z9.string(),
1457
1512
  os: RunnerOsSchema,
1458
- clis: z8.array(CliKindSchema),
1459
- capabilities: z8.array(z8.string()),
1460
- version: z8.string(),
1513
+ clis: z9.array(CliKindSchema),
1514
+ capabilities: z9.array(z9.string()),
1515
+ version: z9.string(),
1461
1516
  requestedAt: IsoDateTimeSchema,
1462
1517
  expiresAt: IsoDateTimeSchema
1463
1518
  });
1464
- var ProjectRepoSchema = z8.object({
1519
+ var ProjectRepoSchema = z9.object({
1465
1520
  slug: NameSchema,
1466
- origin: z8.string().nullable(),
1467
- defaultBranch: z8.string().min(1),
1521
+ origin: z9.string().nullable(),
1522
+ defaultBranch: z9.string().min(1),
1468
1523
  /**
1469
1524
  * The board lands finished tasks on the default branch, itself or through their pull requests,
1470
1525
  * so the runner lets nothing else move it.
1471
1526
  */
1472
- boardLands: z8.boolean()
1527
+ boardLands: z9.boolean()
1473
1528
  });
1474
1529
  function projectRepo(project) {
1475
1530
  return {
@@ -1479,29 +1534,29 @@ function projectRepo(project) {
1479
1534
  boardLands: project.onDone !== "none"
1480
1535
  };
1481
1536
  }
1482
- var TurnWorkspaceSchema = z8.discriminatedUnion("kind", [
1483
- z8.object({
1484
- kind: z8.literal("home"),
1537
+ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
1538
+ z9.object({
1539
+ kind: z9.literal("home"),
1485
1540
  thread: UlidSchema.optional(),
1486
1541
  channel: NameSchema.optional()
1487
1542
  }),
1488
- z8.object({
1489
- kind: z8.literal("project"),
1543
+ z9.object({
1544
+ kind: z9.literal("project"),
1490
1545
  repo: ProjectRepoSchema,
1491
- branches: z8.array(z8.string().min(1)),
1546
+ branches: z9.array(z9.string().min(1)),
1492
1547
  thread: UlidSchema.optional(),
1493
1548
  channel: NameSchema.optional()
1494
1549
  }),
1495
- z8.object({ kind: z8.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1550
+ z9.object({ kind: z9.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1496
1551
  ]);
1497
- var TurnJobSchema = z8.object({
1552
+ var TurnJobSchema = z9.object({
1498
1553
  turnId: UlidSchema,
1499
1554
  agent: NameSchema,
1500
1555
  role: NameSchema,
1501
1556
  cli: CliKindSchema,
1502
- model: z8.string().optional(),
1557
+ model: z9.string().optional(),
1503
1558
  /** The reasoning effort set on the agent; absent for the model's own default. */
1504
- effort: z8.string().optional(),
1559
+ effort: z9.string().optional(),
1505
1560
  /** A project slug, or the society scope. */
1506
1561
  scope: NameSchema,
1507
1562
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1512,130 +1567,130 @@ var TurnJobSchema = z8.object({
1512
1567
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1513
1568
  * the outcome reports it.
1514
1569
  */
1515
- session: z8.string().min(1).nullable(),
1570
+ session: z9.string().min(1).nullable(),
1516
1571
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1517
- costSoFarUsd: z8.number().nonnegative(),
1572
+ costSoFarUsd: z9.number().nonnegative(),
1518
1573
  /** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
1519
- instructions: z8.string(),
1520
- prompt: z8.string(),
1521
- mcp: z8.object({ url: z8.string().min(1), token: z8.string().min(1) }),
1522
- limits: z8.object({
1523
- timeoutMs: z8.number().int().positive().nullable(),
1524
- maxTurns: z8.number().int().positive().nullable()
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()
1525
1580
  }),
1526
1581
  /** Present when the runner keeps the conversation's session warm between turns, under `key`. */
1527
- resident: z8.object({ key: z8.string().min(1), idleMs: z8.number().int().positive() }).optional(),
1582
+ resident: z9.object({ key: z9.string().min(1), idleMs: z9.number().int().positive() }).optional(),
1528
1583
  workspace: TurnWorkspaceSchema
1529
1584
  });
1530
- var TurnWorkSchema = z8.object({ branch: z8.string().min(1), head: z8.string().min(1) });
1531
- var TurnEventsSchema = z8.object({ entries: z8.array(TranscriptEntrySchema) });
1532
- var TurnOutcomeSchema = z8.object({
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({
1533
1588
  exitReason: TurnExitReasonSchema,
1534
1589
  status: TurnStatusSchema.nullable(),
1535
- error: z8.string().nullable(),
1590
+ error: z9.string().nullable(),
1536
1591
  usage: UsageSchema,
1537
- costUsd: z8.number().nonnegative(),
1592
+ costUsd: z9.number().nonnegative(),
1538
1593
  /** The session's running total after this turn, for CLIs that report one. */
1539
- sessionCostUsd: z8.number().nonnegative().optional(),
1594
+ sessionCostUsd: z9.number().nonnegative().optional(),
1540
1595
  /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1541
- session: z8.string().min(1),
1596
+ session: z9.string().min(1),
1542
1597
  /** The model the CLI reported, when it reported one. */
1543
- model: z8.string().nullable(),
1598
+ model: z9.string().nullable(),
1544
1599
  work: TurnWorkSchema.nullable(),
1545
1600
  /**
1546
1601
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1547
1602
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1548
1603
  */
1549
- leftovers: z8.boolean().default(false),
1604
+ leftovers: z9.boolean().default(false),
1550
1605
  /**
1551
1606
  * The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
1552
1607
  * before never sends it.
1553
1608
  */
1554
1609
  quota: CliQuotaSchema.optional()
1555
1610
  });
1556
- var TurnAckSchema = z8.object({ dropWorkspace: z8.boolean() });
1557
- var LandRequestSchema = z8.object({
1611
+ var TurnAckSchema = z9.object({ dropWorkspace: z9.boolean() });
1612
+ var LandRequestSchema = z9.object({
1558
1613
  repo: ProjectRepoSchema,
1559
- branch: z8.string().min(1),
1614
+ branch: z9.string().min(1),
1560
1615
  /**
1561
1616
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1562
1617
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1563
1618
  * empty body when left out.
1564
1619
  */
1565
- pullRequest: z8.object({
1566
- url: z8.string().min(1),
1567
- subject: z8.string().min(1).optional(),
1568
- body: z8.string().optional()
1620
+ pullRequest: z9.object({
1621
+ url: z9.string().min(1),
1622
+ subject: z9.string().min(1).optional(),
1623
+ body: z9.string().optional()
1569
1624
  }).optional()
1570
1625
  });
1571
- var CloseRequestSchema = z8.object({
1626
+ var CloseRequestSchema = z9.object({
1572
1627
  repo: ProjectRepoSchema,
1573
- url: z8.string().min(1),
1574
- comment: z8.string().min(1)
1628
+ url: z9.string().min(1),
1629
+ comment: z9.string().min(1)
1575
1630
  });
1576
- var MergeOutcomeSchema = z8.object({ ok: z8.boolean(), detail: z8.string() });
1577
- var ModelListSchema = z8.array(ModelOptionSchema);
1631
+ var MergeOutcomeSchema = z9.object({ ok: z9.boolean(), detail: z9.string() });
1632
+ var ModelListSchema = z9.array(ModelOptionSchema);
1578
1633
  var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1579
- var BranchPathSchema = z8.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
1580
- var BranchNameSchema = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1581
- var BranchReadSchema = z8.object({
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({
1582
1637
  repo: ProjectRepoSchema,
1583
1638
  branch: BranchNameSchema,
1584
1639
  path: BranchPathSchema
1585
1640
  });
1586
- var BranchCommitSchema = z8.object({
1587
- id: z8.string().min(1),
1641
+ var BranchCommitSchema = z9.object({
1642
+ id: z9.string().min(1),
1588
1643
  at: IsoDateTimeSchema,
1589
- author: z8.string()
1644
+ author: z9.string()
1590
1645
  });
1591
- var BranchFileSchema = z8.discriminatedUnion("kind", [
1592
- z8.object({
1593
- kind: z8.literal("file"),
1646
+ var BranchFileSchema = z9.discriminatedUnion("kind", [
1647
+ z9.object({
1648
+ kind: z9.literal("file"),
1594
1649
  path: BranchPathSchema,
1595
1650
  commit: BranchCommitSchema,
1596
- size: z8.number().int().nonnegative(),
1597
- content: z8.string().nullable()
1651
+ size: z9.number().int().nonnegative(),
1652
+ content: z9.string().nullable()
1598
1653
  }),
1599
- z8.object({
1600
- kind: z8.literal("dir"),
1654
+ z9.object({
1655
+ kind: z9.literal("dir"),
1601
1656
  path: BranchPathSchema,
1602
1657
  commit: BranchCommitSchema,
1603
- entries: z8.array(z8.object({
1604
- name: z8.string().min(1),
1605
- kind: z8.enum(["file", "dir"]),
1606
- size: z8.number().int().nonnegative().nullable()
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()
1607
1662
  }))
1608
1663
  })
1609
1664
  ]);
1610
- var BranchChangesReadSchema = z8.object({
1665
+ var BranchChangesReadSchema = z9.object({
1611
1666
  repo: ProjectRepoSchema,
1612
1667
  branch: BranchNameSchema
1613
1668
  });
1614
- var BranchFileChangeSchema = z8.object({
1615
- path: z8.string().min(1),
1616
- status: z8.enum(["added", "modified", "deleted"]),
1669
+ var BranchFileChangeSchema = z9.object({
1670
+ path: z9.string().min(1),
1671
+ status: z9.enum(["added", "modified", "deleted"]),
1617
1672
  /** Lines added and removed, or null for a binary file. */
1618
- added: z8.number().int().nonnegative().nullable(),
1619
- removed: z8.number().int().nonnegative().nullable(),
1673
+ added: z9.number().int().nonnegative().nullable(),
1674
+ removed: z9.number().int().nonnegative().nullable(),
1620
1675
  /** Absent when the file changed only through a merge of another branch. */
1621
- lastChange: z8.object({ author: z8.string(), at: IsoDateTimeSchema }).optional()
1676
+ lastChange: z9.object({ author: z9.string(), at: IsoDateTimeSchema }).optional()
1622
1677
  });
1623
- var BranchChangesSchema = z8.object({
1678
+ var BranchChangesSchema = z9.object({
1624
1679
  head: BranchCommitSchema,
1625
- files: z8.array(BranchFileChangeSchema),
1626
- total: z8.number().int().nonnegative()
1680
+ files: z9.array(BranchFileChangeSchema),
1681
+ total: z9.number().int().nonnegative()
1627
1682
  });
1628
- var BranchDiffReadSchema = z8.object({
1683
+ var BranchDiffReadSchema = z9.object({
1629
1684
  repo: ProjectRepoSchema,
1630
1685
  branch: BranchNameSchema,
1631
1686
  path: BranchPathSchema.min(1)
1632
1687
  });
1633
- var BranchFileDiffSchema = z8.object({
1688
+ var BranchFileDiffSchema = z9.object({
1634
1689
  head: BranchCommitSchema,
1635
1690
  path: BranchPathSchema,
1636
1691
  status: BranchFileChangeSchema.shape.status,
1637
- patch: z8.string().nullable(),
1638
- truncated: z8.boolean()
1692
+ patch: z9.string().nullable(),
1693
+ truncated: z9.boolean()
1639
1694
  });
1640
1695
  function patchHunks(patch, limit2) {
1641
1696
  if (/^Binary files .* differ$/m.test(patch)) {
@@ -1649,9 +1704,23 @@ function patchHunks(patch, limit2) {
1649
1704
  const cut = hunks.lastIndexOf("\n", limit2);
1650
1705
  return { patch: hunks.slice(0, cut === -1 ? limit2 : cut), truncated: true };
1651
1706
  }
1652
- var WorkspaceConversationSchema = z8.discriminatedUnion("kind", [
1653
- z8.object({ kind: z8.literal("thread"), id: UlidSchema }),
1654
- z8.object({ kind: z8.literal("channel"), scope: NameSchema, name: NameSchema })
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 })
1655
1724
  ]);
1656
1725
  function workspaceConversationOf(turn) {
1657
1726
  if (turn.thread !== void 0) {
@@ -1659,65 +1728,70 @@ function workspaceConversationOf(turn) {
1659
1728
  }
1660
1729
  return turn.channel === void 0 ? null : { kind: "channel", scope: turn.scope, name: turn.channel };
1661
1730
  }
1662
- var HeldWorkspaceSchema = z8.object({
1731
+ var HeldWorkspaceSchema = z9.object({
1663
1732
  agent: NameSchema,
1664
1733
  scope: NameSchema,
1665
1734
  conversation: WorkspaceConversationSchema
1666
1735
  });
1667
- var HeldWorkspacesSchema = z8.object({ workspaces: z8.array(HeldWorkspaceSchema) });
1668
- var SweepResultSchema = z8.object({ kept: z8.array(HeldWorkspaceSchema) });
1669
- var TurnSteerSchema = z8.object({ id: z8.uuid(), text: z8.string().min(1) });
1670
- var RunnerMessageSchema = z8.discriminatedUnion("type", [
1671
- z8.object({ type: z8.literal("turn"), job: TurnJobSchema }),
1672
- z8.object({ type: z8.literal("land"), request: z8.string().min(1), land: LandRequestSchema }),
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 }),
1673
1742
  /** Answered with a `MergeOutcome`. */
1674
- z8.object({ type: z8.literal("close"), request: z8.string().min(1), close: CloseRequestSchema }),
1675
- z8.object({ type: z8.literal("models"), request: z8.string().min(1), cli: CliKindSchema }),
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 }),
1676
1745
  /**
1677
1746
  * Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
1678
1747
  * read one. Sent only to runners whose hello says `quotaReads`.
1679
1748
  */
1680
- z8.object({ type: z8.literal("quota"), request: z8.string().min(1), cli: CliKindSchema }),
1749
+ z9.object({ type: z9.literal("quota"), request: z9.string().min(1), cli: CliKindSchema }),
1681
1750
  /** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
1682
- z8.object({ type: z8.literal("file"), request: z8.string().min(1), read: BranchReadSchema }),
1751
+ z9.object({ type: z9.literal("file"), request: z9.string().min(1), read: BranchReadSchema }),
1683
1752
  /** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
1684
- z8.object({
1685
- type: z8.literal("changes"),
1686
- request: z8.string().min(1),
1753
+ z9.object({
1754
+ type: z9.literal("changes"),
1755
+ request: z9.string().min(1),
1687
1756
  read: BranchChangesReadSchema
1688
1757
  }),
1689
1758
  /** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
1690
- z8.object({ type: z8.literal("diff"), request: z8.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 }),
1691
1765
  /** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
1692
- z8.object({
1693
- type: z8.literal("steer"),
1694
- request: z8.string().min(1),
1766
+ z9.object({
1767
+ type: z9.literal("steer"),
1768
+ request: z9.string().min(1),
1695
1769
  turnId: UlidSchema,
1696
1770
  steer: TurnSteerSchema
1697
1771
  }),
1698
1772
  /** Answered with whether the turn was there to stop; it ends `stopped`. */
1699
- z8.object({ type: z8.literal("stop"), request: z8.string().min(1), turnId: UlidSchema }),
1773
+ z9.object({ type: z9.literal("stop"), request: z9.string().min(1), turnId: UlidSchema }),
1700
1774
  /**
1701
1775
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1702
1776
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1703
1777
  */
1704
- z8.object({
1705
- type: z8.literal("sweep"),
1706
- request: z8.string().min(1),
1778
+ z9.object({
1779
+ type: z9.literal("sweep"),
1780
+ request: z9.string().min(1),
1707
1781
  conversation: WorkspaceConversationSchema
1708
1782
  })
1709
1783
  ]);
1710
- var RunnerAnswerSchema = z8.discriminatedUnion("ok", [
1711
- z8.object({ ok: z8.literal(true), value: z8.unknown() }),
1712
- z8.object({ ok: z8.literal(false), error: z8.string() })
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() })
1713
1787
  ]);
1714
- var WarmSessionsSchema = z8.object({ warm: z8.array(z8.string()) });
1715
- var FileManifestSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1788
+ var WarmSessionsSchema = z9.object({ warm: z9.array(z9.string()) });
1789
+ var FileManifestSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1716
1790
  function fileVersion(sha, executable) {
1717
1791
  return executable ? `${sha}+x` : sha;
1718
1792
  }
1719
- var FileReadSchema = z8.object({ paths: z8.array(z8.string()) });
1720
- var FileContentsSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1793
+ var FileReadSchema = z9.object({ paths: z9.array(z9.string()) });
1794
+ var FileContentsSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1721
1795
  function isSafeRelativePath(file) {
1722
1796
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1723
1797
  return false;
@@ -1726,7 +1800,7 @@ function isSafeRelativePath(file) {
1726
1800
  }
1727
1801
 
1728
1802
  // packages/shared/dist/turns.js
1729
- var TriggerKindSchema = z9.enum([
1803
+ var TriggerKindSchema = z10.enum([
1730
1804
  "mention",
1731
1805
  "stage",
1732
1806
  "task_done",
@@ -1740,11 +1814,11 @@ var TriggerKindSchema = z9.enum([
1740
1814
  "closing",
1741
1815
  "cron"
1742
1816
  ]);
1743
- var TriggerSchema = z9.object({
1817
+ var TriggerSchema = z10.object({
1744
1818
  kind: TriggerKindSchema,
1745
1819
  from: NameSchema.optional(),
1746
- fromUser: z9.boolean().default(false),
1747
- reason: z9.string().default(""),
1820
+ fromUser: z10.boolean().default(false),
1821
+ reason: z10.string().default(""),
1748
1822
  taskId: UlidSchema.optional(),
1749
1823
  messageId: UlidSchema.optional(),
1750
1824
  /** The channel the waking message was posted in. */
@@ -1752,17 +1826,17 @@ var TriggerSchema = z9.object({
1752
1826
  /** The cron that came due, for a `cron` wake. */
1753
1827
  cronId: UlidSchema.optional()
1754
1828
  });
1755
- var TurnThreadSchema = z9.object({ id: UlidSchema, task: z9.boolean() });
1756
- var TurnDispatchSchema = z9.object({
1829
+ var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
1830
+ var TurnDispatchSchema = z10.object({
1757
1831
  agent: NameSchema,
1758
1832
  project: NameSchema,
1759
1833
  thread: TurnThreadSchema.optional(),
1760
1834
  channel: NameSchema.optional(),
1761
1835
  trigger: TriggerSchema,
1762
- priority: z9.number().int().min(0).max(2),
1763
- onboarding: z9.boolean().default(false),
1836
+ priority: z10.number().int().min(0).max(2),
1837
+ onboarding: z10.boolean().default(false),
1764
1838
  /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1765
- crons: z9.array(UlidSchema).optional()
1839
+ crons: z10.array(UlidSchema).optional()
1766
1840
  });
1767
1841
  function conversationPart(conversation) {
1768
1842
  return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
@@ -1786,7 +1860,7 @@ function channelConversation(scope, channel) {
1786
1860
  const name = at < 0 ? channel : channel.slice(at + 1);
1787
1861
  return place === scope && name !== "general" ? name : void 0;
1788
1862
  }
1789
- var TurnRecordSchema = z9.object({
1863
+ var TurnRecordSchema = z10.object({
1790
1864
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1791
1865
  id: UlidSchema.optional(),
1792
1866
  agent: NameSchema,
@@ -1797,31 +1871,31 @@ var TurnRecordSchema = z9.object({
1797
1871
  channel: NameSchema.optional(),
1798
1872
  runner: NameSchema,
1799
1873
  cli: CliKindSchema.nullable(),
1800
- session: z9.string().nullable(),
1874
+ session: z10.string().nullable(),
1801
1875
  trigger: TriggerSchema,
1802
1876
  startedAt: IsoDateTimeSchema,
1803
1877
  endedAt: IsoDateTimeSchema.nullable(),
1804
1878
  exitReason: TurnExitReasonSchema.nullable(),
1805
1879
  status: TurnStatusSchema.nullable(),
1806
- error: z9.string().nullable(),
1880
+ error: z10.string().nullable(),
1807
1881
  usage: UsageSchema.nullable(),
1808
- costUsd: z9.number().nonnegative().default(0),
1882
+ costUsd: z10.number().nonnegative().default(0),
1809
1883
  /**
1810
1884
  * The running total the CLI reported for the session after this turn, when it reports one. A
1811
1885
  * resumed session starts from it, so the next turn's own cost is measured from here.
1812
1886
  */
1813
- sessionCostUsd: z9.number().nonnegative().optional(),
1814
- toolCalls: z9.number().int().nonnegative().default(0),
1887
+ sessionCostUsd: z10.number().nonnegative().optional(),
1888
+ toolCalls: z10.number().int().nonnegative().default(0),
1815
1889
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1816
- model: z9.string().nullable().default(null),
1890
+ model: z10.string().nullable().default(null),
1817
1891
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1818
1892
  work: TurnWorkSchema.optional(),
1819
1893
  /** Who stopped the turn, for one that ended `stopped`. */
1820
1894
  stoppedBy: NameSchema.optional(),
1821
1895
  /** The crons whose fire started the turn, which record how it ended. */
1822
- crons: z9.array(UlidSchema).optional()
1896
+ crons: z10.array(UlidSchema).optional()
1823
1897
  });
1824
- var RunningTurnSchema = z9.object({
1898
+ var RunningTurnSchema = z10.object({
1825
1899
  turnId: UlidSchema,
1826
1900
  agent: NameSchema,
1827
1901
  scope: NameSchema,
@@ -1829,49 +1903,49 @@ var RunningTurnSchema = z9.object({
1829
1903
  channel: NameSchema.optional(),
1830
1904
  cli: CliKindSchema,
1831
1905
  askedIn: ChannelRefSchema.optional(),
1832
- steerable: z9.boolean(),
1833
- stoppable: z9.boolean()
1906
+ steerable: z10.boolean(),
1907
+ stoppable: z10.boolean()
1834
1908
  });
1835
- var SessionsFileSchema = z9.object({
1836
- claude: z9.string().optional(),
1837
- codex: z9.string().optional(),
1909
+ var SessionsFileSchema = z10.object({
1910
+ claude: z10.string().optional(),
1911
+ codex: z10.string().optional(),
1838
1912
  runner: NameSchema.optional()
1839
1913
  });
1840
- var WakeRequestSchema = z9.object({
1914
+ var WakeRequestSchema = z10.object({
1841
1915
  agent: NameSchema,
1842
1916
  project: NameSchema,
1843
- reason: z9.string().default("manual wake"),
1917
+ reason: z10.string().default("manual wake"),
1844
1918
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1845
- kind: z9.enum(["manual", "reflection"]).default("manual")
1919
+ kind: z10.enum(["manual", "reflection"]).default("manual")
1846
1920
  });
1847
- var TurnHistoryEntrySchema = z9.object({
1921
+ var TurnHistoryEntrySchema = z10.object({
1848
1922
  /** The id of the event that ended the turn. */
1849
1923
  id: UlidSchema,
1850
1924
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1851
1925
  turnId: UlidSchema.optional(),
1852
1926
  ts: IsoDateTimeSchema,
1853
- outcome: z9.enum(["completed", "failed"]),
1927
+ outcome: z10.enum(["completed", "failed"]),
1854
1928
  /** A project slug, or the society scope. */
1855
1929
  project: NameSchema,
1856
1930
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1857
1931
  thread: UlidSchema.optional(),
1858
1932
  /** The channel, never general, whose conversation the turn was in. */
1859
1933
  channel: NameSchema.optional(),
1860
- trigger: z9.string(),
1861
- exitReason: z9.string().nullable(),
1862
- costUsd: z9.number(),
1934
+ trigger: z10.string(),
1935
+ exitReason: z10.string().nullable(),
1936
+ costUsd: z10.number(),
1863
1937
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1864
1938
  usage: UsageSchema.optional(),
1865
- model: z9.string().nullable(),
1866
- summary: z9.string().nullable(),
1867
- error: z9.string().nullable(),
1939
+ model: z10.string().nullable(),
1940
+ summary: z10.string().nullable(),
1941
+ error: z10.string().nullable(),
1868
1942
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1869
1943
  startedAt: IsoDateTimeSchema.optional(),
1870
- toolCalls: z9.number().int().nonnegative().optional()
1944
+ toolCalls: z10.number().int().nonnegative().optional()
1871
1945
  });
1872
1946
 
1873
1947
  // packages/shared/dist/ops.js
1874
- import { z as z10 } from "zod";
1948
+ import { z as z11 } from "zod";
1875
1949
  var OPS_SIGNAL_KINDS = [
1876
1950
  "waiting_stage",
1877
1951
  "backlog",
@@ -1887,7 +1961,7 @@ var OPS_SIGNAL_KINDS = [
1887
1961
  "stuck_workspace",
1888
1962
  "cron_failing"
1889
1963
  ];
1890
- var OpsSignalKindSchema = z10.enum(OPS_SIGNAL_KINDS);
1964
+ var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
1891
1965
  var WAKING_SIGNAL_KINDS = [
1892
1966
  "backlog",
1893
1967
  "role_gap",
@@ -1897,15 +1971,15 @@ var WAKING_SIGNAL_KINDS = [
1897
1971
  "blocked_capability",
1898
1972
  "scaled"
1899
1973
  ];
1900
- var OpsSignalSchema = z10.object({
1974
+ var OpsSignalSchema = z11.object({
1901
1975
  kind: OpsSignalKindSchema,
1902
1976
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1903
- key: z10.string().min(1),
1977
+ key: z11.string().min(1),
1904
1978
  /** One line a reader can act on. */
1905
- summary: z10.string().min(1),
1979
+ summary: z11.string().min(1),
1906
1980
  /** The counter or timer value behind the signal. */
1907
- value: z10.number(),
1908
- threshold: z10.number().optional(),
1981
+ value: z11.number(),
1982
+ threshold: z11.number().optional(),
1909
1983
  project: NameSchema.optional(),
1910
1984
  agent: NameSchema.optional(),
1911
1985
  role: NameSchema.optional(),
@@ -1913,8 +1987,8 @@ var OpsSignalSchema = z10.object({
1913
1987
  });
1914
1988
 
1915
1989
  // packages/shared/dist/metrics.js
1916
- import { z as z11 } from "zod";
1917
- var MetricsWindowSchema = z11.enum(["24h", "7d", "all"]);
1990
+ import { z as z12 } from "zod";
1991
+ var MetricsWindowSchema = z12.enum(["24h", "7d", "all"]);
1918
1992
  var METRICS_WINDOW_MS = {
1919
1993
  "24h": 24 * 36e5,
1920
1994
  "7d": 7 * 24 * 36e5,
@@ -1925,9 +1999,9 @@ function isBoardAction(tool) {
1925
1999
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1926
2000
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1927
2001
  }
1928
- var CountSchema = z11.number().int().nonnegative();
1929
- var MsSchema = z11.number().nonnegative();
1930
- var MetricsSchema = z11.object({
2002
+ var CountSchema = z12.number().int().nonnegative();
2003
+ var MsSchema = z12.number().nonnegative();
2004
+ var MetricsSchema = z12.object({
1931
2005
  window: MetricsWindowSchema,
1932
2006
  since: IsoDateTimeSchema.nullable(),
1933
2007
  until: IsoDateTimeSchema,
@@ -1935,12 +2009,12 @@ var MetricsSchema = z11.object({
1935
2009
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1936
2010
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1937
2011
  */
1938
- idle: z11.object({
2012
+ idle: z12.object({
1939
2013
  turns: CountSchema,
1940
2014
  idle: CountSchema,
1941
2015
  unknown: CountSchema,
1942
- byTrigger: z11.array(z11.object({ trigger: z11.string(), turns: CountSchema, idle: CountSchema })),
1943
- byAgent: z11.array(z11.object({
2016
+ byTrigger: z12.array(z12.object({ trigger: z12.string(), turns: CountSchema, idle: CountSchema })),
2017
+ byAgent: z12.array(z12.object({
1944
2018
  agent: NameSchema,
1945
2019
  role: NameSchema.nullable(),
1946
2020
  turns: CountSchema,
@@ -1948,33 +2022,33 @@ var MetricsSchema = z11.object({
1948
2022
  }))
1949
2023
  }),
1950
2024
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1951
- sentBack: z11.object({
2025
+ sentBack: z12.object({
1952
2026
  finished: CountSchema,
1953
2027
  sendBacks: CountSchema,
1954
2028
  tasksSentBack: CountSchema,
1955
- byStage: z11.array(z11.object({ project: NameSchema, stage: z11.string(), count: CountSchema })),
1956
- bySender: z11.array(z11.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 }))
1957
2031
  }),
1958
2032
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1959
- messages: z11.object({
2033
+ messages: z12.object({
1960
2034
  finished: CountSchema,
1961
2035
  messages: CountSchema,
1962
- byProject: z11.array(z11.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1963
- busiest: z11.array(z11.object({
2036
+ byProject: z12.array(z12.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
2037
+ busiest: z12.array(z12.object({
1964
2038
  taskId: UlidSchema,
1965
- title: z11.string(),
2039
+ title: z12.string(),
1966
2040
  project: NameSchema,
1967
2041
  messages: CountSchema
1968
2042
  }))
1969
2043
  }),
1970
2044
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1971
- latency: z11.object({
2045
+ latency: z12.object({
1972
2046
  mentions: CountSchema,
1973
2047
  /** Mentions no turn has followed yet. */
1974
2048
  unanswered: CountSchema,
1975
2049
  medianMs: MsSchema.nullable(),
1976
2050
  slowestMs: MsSchema.nullable(),
1977
- byAgent: z11.array(z11.object({
2051
+ byAgent: z12.array(z12.object({
1978
2052
  agent: NameSchema,
1979
2053
  mentions: CountSchema,
1980
2054
  medianMs: MsSchema,
@@ -1982,73 +2056,73 @@ var MetricsSchema = z11.object({
1982
2056
  }))
1983
2057
  }),
1984
2058
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1985
- decisions: z11.object({
2059
+ decisions: z12.object({
1986
2060
  total: CountSchema,
1987
- byDay: z11.array(z11.object({
1988
- day: z11.string(),
2061
+ byDay: z12.array(z12.object({
2062
+ day: z12.string(),
1989
2063
  proposals: CountSchema,
1990
2064
  answers: CountSchema,
1991
2065
  stages: CountSchema
1992
2066
  }))
1993
2067
  }),
1994
2068
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1995
- blocked: z11.array(z11.object({
2069
+ blocked: z12.array(z12.object({
1996
2070
  taskId: UlidSchema,
1997
- title: z11.string().nullable(),
2071
+ title: z12.string().nullable(),
1998
2072
  project: NameSchema.nullable(),
1999
- summary: z11.string(),
2073
+ summary: z12.string(),
2000
2074
  firstAt: IsoDateTimeSchema,
2001
2075
  lastAt: IsoDateTimeSchema,
2002
- holds: z11.boolean()
2076
+ holds: z12.boolean()
2003
2077
  }))
2004
2078
  });
2005
- var CitizenTokensSchema = z11.object({
2079
+ var CitizenTokensSchema = z12.object({
2006
2080
  agent: NameSchema,
2007
2081
  turns: CountSchema,
2008
2082
  withUsage: CountSchema,
2009
2083
  usage: UsageSchema.nullable()
2010
2084
  });
2011
- var TokenUseSchema = z11.object({
2085
+ var TokenUseSchema = z12.object({
2012
2086
  window: MetricsWindowSchema,
2013
2087
  since: IsoDateTimeSchema.nullable(),
2014
2088
  until: IsoDateTimeSchema,
2015
- citizens: z11.array(CitizenTokensSchema)
2089
+ citizens: z12.array(CitizenTokensSchema)
2016
2090
  });
2017
2091
 
2018
2092
  // packages/shared/dist/config.js
2019
- import { z as z12 } from "zod";
2020
- var LogLevelSchema = z12.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
2021
- var GithubSignInSchema = z12.object({
2022
- clientId: z12.string().min(1),
2023
- clientSecret: z12.string().min(1),
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),
2024
2098
  /** Logins, lowercased; GitHub logins are case-insensitive. */
2025
- users: z12.array(z12.string().min(1)).min(1)
2099
+ users: z13.array(z13.string().min(1)).min(1)
2026
2100
  });
2027
- var ServerConfigSchema = z12.object({
2028
- dataDir: z12.string().min(1),
2029
- host: z12.string().min(1),
2030
- port: z12.number().int().positive().max(65535),
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),
2031
2105
  /**
2032
2106
  * An address every runner's agents reach this server at, for example https://board.example, or
2033
2107
  * null for each runner to use the address it reaches the server at itself.
2034
2108
  */
2035
- publicUrl: z12.string().min(1).nullable(),
2109
+ publicUrl: z13.string().min(1).nullable(),
2036
2110
  logLevel: LogLevelSchema,
2037
2111
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
2038
- concurrency: z12.number().int().positive().nullable(),
2112
+ concurrency: z13.number().int().positive().nullable(),
2039
2113
  /** How long one turn may run before it is stopped, or null for no limit. */
2040
- turnTimeoutMs: z12.number().int().positive().nullable(),
2114
+ turnTimeoutMs: z13.number().int().positive().nullable(),
2041
2115
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
2042
- toolRounds: z12.number().int().positive().nullable(),
2116
+ toolRounds: z13.number().int().positive().nullable(),
2043
2117
  /** How long a resident session stays warm after its last turn. */
2044
- residentIdleMs: z12.number().int().positive(),
2118
+ residentIdleMs: z13.number().int().positive(),
2045
2119
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
2046
2120
  github: GithubSignInSchema.nullable(),
2047
2121
  /**
2048
2122
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
2049
2123
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
2050
2124
  */
2051
- trustedProxies: z12.number().int().min(0).max(16)
2125
+ trustedProxies: z13.number().int().min(0).max(16)
2052
2126
  });
2053
2127
  function limit(value, fallback) {
2054
2128
  if (value === void 0 || value.trim() === "") {
@@ -2086,25 +2160,25 @@ function loadServerConfig(env) {
2086
2160
  trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
2087
2161
  });
2088
2162
  }
2089
- var RunnerConfigSchema = z12.object({
2163
+ var RunnerConfigSchema = z13.object({
2090
2164
  /** The board server, for example http://127.0.0.1:4700. */
2091
- serverUrl: z12.string().url(),
2165
+ serverUrl: z13.string().url(),
2092
2166
  /**
2093
2167
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
2094
2168
  * to enroll it.
2095
2169
  */
2096
- token: z12.string().min(1).nullable(),
2170
+ token: z13.string().min(1).nullable(),
2097
2171
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
2098
- dataDir: z12.string().min(1),
2172
+ dataDir: z13.string().min(1),
2099
2173
  logLevel: LogLevelSchema,
2100
2174
  /** Turns this machine runs at once, or null for no limit. */
2101
- slots: z12.number().int().positive().nullable(),
2102
- clis: z12.array(CliKindSchema).min(1),
2103
- capabilities: z12.array(z12.string())
2175
+ slots: z13.number().int().positive().nullable(),
2176
+ clis: z13.array(CliKindSchema).min(1),
2177
+ capabilities: z13.array(z13.string())
2104
2178
  });
2105
2179
 
2106
2180
  // packages/shared/dist/version.js
2107
- var STELLARIS_VERSION = "0.3.22";
2181
+ var STELLARIS_VERSION = "0.3.23";
2108
2182
 
2109
2183
  // packages/shared/dist/mentions.js
2110
2184
  import { parse, postprocess, preprocess } from "micromark";
@@ -2825,32 +2899,32 @@ function postMentions(body) {
2825
2899
  }
2826
2900
 
2827
2901
  // packages/board-core/dist/metrics.js
2828
- import { z as z13 } from "zod";
2829
- var TurnPayload = z13.object({
2830
- turnId: z13.string().optional(),
2831
- project: z13.string(),
2832
- thread: z13.string().optional(),
2833
- channel: z13.string().optional(),
2834
- trigger: z13.string()
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()
2835
2909
  });
2836
- var TaskPayload = z13.object({
2837
- taskId: z13.string(),
2838
- project: z13.string(),
2839
- from: z13.string().optional()
2910
+ var TaskPayload = z14.object({
2911
+ taskId: z14.string(),
2912
+ project: z14.string(),
2913
+ from: z14.string().optional()
2840
2914
  });
2841
- var MessagePayload = z13.object({
2842
- id: z13.string().optional(),
2843
- channel: z13.string(),
2844
- thread: z13.string().nullable().optional(),
2845
- mentions: z13.array(z13.string()).default([])
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([])
2846
2920
  });
2847
- var SteeredPayload = z13.object({ messages: z13.array(z13.string()) });
2848
- var SignalPayload = z13.object({
2849
- kind: z13.string(),
2850
- key: z13.string(),
2851
- summary: z13.string(),
2852
- taskId: z13.string().optional(),
2853
- project: z13.string().optional()
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()
2854
2928
  });
2855
2929
  function median(values) {
2856
2930
  const sorted = values.toSorted((a, b) => a - b);
@@ -3140,19 +3214,19 @@ function computeMetrics(input) {
3140
3214
  }
3141
3215
 
3142
3216
  // packages/board-core/dist/turn-history.js
3143
- import { z as z14 } from "zod";
3144
- var TurnHistoryPayloadSchema = z14.object({
3217
+ import { z as z15 } from "zod";
3218
+ var TurnHistoryPayloadSchema = z15.object({
3145
3219
  project: NameSchema,
3146
3220
  thread: UlidSchema.optional(),
3147
3221
  channel: NameSchema.optional(),
3148
- trigger: z14.string().default("manual"),
3149
- exitReason: z14.string().nullable().default(null),
3150
- costUsd: z14.number().default(0),
3222
+ trigger: z15.string().default("manual"),
3223
+ exitReason: z15.string().nullable().default(null),
3224
+ costUsd: z15.number().default(0),
3151
3225
  usage: UsageSchema.optional(),
3152
- model: z14.string().nullable().default(null),
3153
- summary: z14.string().nullable().default(null),
3154
- error: z14.string().nullable().default(null),
3155
- toolCalls: z14.number().int().nonnegative().optional(),
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(),
3156
3230
  turnId: UlidSchema.optional()
3157
3231
  });
3158
3232
  function tokenUse(events, window, now) {
@@ -3398,6 +3472,13 @@ var BoardPaths = class {
3398
3472
  eventLog() {
3399
3473
  return path5.join(this.events(), "log.jsonl");
3400
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
+ }
3401
3482
  state() {
3402
3483
  return path5.join(this.dataDir, "state");
3403
3484
  }
@@ -3433,27 +3514,27 @@ function namesList(names) {
3433
3514
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
3434
3515
  }
3435
3516
  var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
3436
- var RunnerTokensSchema = z15.object({
3437
- runners: z15.record(z15.string(), z15.object({ tokenHash: z15.string().min(1) }))
3517
+ var RunnerTokensSchema = z16.object({
3518
+ runners: z16.record(z16.string(), z16.object({ tokenHash: z16.string().min(1) }))
3438
3519
  });
3439
- var SignInsSchema = z15.object({
3440
- signIns: z15.array(z15.object({
3441
- tokenHash: z15.string().min(1),
3442
- login: z15.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),
3443
3524
  /** The account's picture at the identity provider; sign-ins from before it have none. */
3444
- avatarUrl: z15.url().optional(),
3525
+ avatarUrl: z16.url().optional(),
3445
3526
  createdAt: IsoDateTimeSchema,
3446
3527
  expiresAt: IsoDateTimeSchema
3447
3528
  })).default([])
3448
3529
  });
3449
- var TurnStartPayloadSchema = z15.object({ project: NameSchema, turnId: UlidSchema.optional() });
3450
- var CursorsSchema = z15.object({
3451
- digest: z15.string().nullable(),
3452
- scopes: z15.record(z15.string(), z15.string()).default({}),
3453
- threadsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3454
- channelsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
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({}),
3455
3536
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
3456
- news: z15.string().optional()
3537
+ news: z16.string().optional()
3457
3538
  });
3458
3539
  var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
3459
3540
  function conversationKey(scope, part) {
@@ -3492,7 +3573,7 @@ function byId(a, b) {
3492
3573
  function turnTime(record) {
3493
3574
  return record.endedAt ?? record.startedAt;
3494
3575
  }
3495
- var PausedSchema = z15.object({ paused: z15.boolean() });
3576
+ var PausedSchema = z16.object({ paused: z16.boolean() });
3496
3577
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
3497
3578
  function withoutMentions(text) {
3498
3579
  return text.replaceAll(MENTION_PATTERN, "$1$2");
@@ -3517,7 +3598,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
3517
3598
  var FRONT_DESK_TRIGGER = "user_post";
3518
3599
  var OPS_CHANNELS = ["governance"];
3519
3600
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
3520
- var AlignmentsSchema = z15.object({ applied: z15.array(z15.string()).default([]) });
3601
+ var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
3521
3602
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
3522
3603
  var THREAD_RECORDS = "threads-as-records";
3523
3604
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -3531,10 +3612,11 @@ var CHANNEL_CONVERSATIONS = "channel-conversations";
3531
3612
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3532
3613
  var CRON_VERBS = "cron-verbs-granted";
3533
3614
  var KNOWLEDGE_NOTICES = "knowledge-notices";
3534
- var KnowledgeEventSchema = z15.object({
3615
+ var IMAGE_UPLOADS = "image-uploads-granted";
3616
+ var KnowledgeEventSchema = z16.object({
3535
3617
  topic: NameSchema,
3536
3618
  project: NameSchema.nullable(),
3537
- replaced: z15.boolean().optional()
3619
+ replaced: z16.boolean().optional()
3538
3620
  });
3539
3621
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3540
3622
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
@@ -3666,6 +3748,7 @@ var Board = class _Board {
3666
3748
  mutex = new Mutex();
3667
3749
  /** The turns in flight, which the server hosting them supplies; none until it does. */
3668
3750
  turnsInFlight = () => [];
3751
+ imageSource = null;
3669
3752
  newId = monotonicFactory();
3670
3753
  events;
3671
3754
  actionCounts = /* @__PURE__ */ new Map();
@@ -3730,7 +3813,8 @@ var Board = class _Board {
3730
3813
  CHANNEL_CONVERSATIONS,
3731
3814
  DASHBOARD_UPDATES,
3732
3815
  CRON_VERBS,
3733
- KNOWLEDGE_NOTICES
3816
+ KNOWLEDGE_NOTICES,
3817
+ IMAGE_UPLOADS
3734
3818
  ].filter((name) => !applied.includes(name));
3735
3819
  if (pending.length === 0) {
3736
3820
  return;
@@ -3760,13 +3844,13 @@ var Board = class _Board {
3760
3844
  await this.openAsksChannel();
3761
3845
  }
3762
3846
  if (pending.includes(KNOWLEDGE_REMOVAL)) {
3763
- await this.grantBesideKnowledge("remove_knowledge");
3847
+ await this.grantBeside("write_knowledge", "remove_knowledge");
3764
3848
  }
3765
3849
  if (pending.includes(CHANNEL_CONVERSATIONS)) {
3766
3850
  await this.startChannelCursors();
3767
3851
  }
3768
3852
  if (pending.includes(DASHBOARD_UPDATES)) {
3769
- await this.grantBesideKnowledge("update_dashboard");
3853
+ await this.grantBeside("write_knowledge", "update_dashboard");
3770
3854
  await this.rewordDashboards();
3771
3855
  }
3772
3856
  if (pending.includes(CRON_VERBS)) {
@@ -3775,6 +3859,9 @@ var Board = class _Board {
3775
3859
  if (pending.includes(KNOWLEDGE_NOTICES)) {
3776
3860
  await this.noticeKnowledgeNotes();
3777
3861
  }
3862
+ if (pending.includes(IMAGE_UPLOADS)) {
3863
+ await this.grantBeside("post_message", "upload_image");
3864
+ }
3778
3865
  await writeJson(file, { applied: [...applied, ...pending] });
3779
3866
  }
3780
3867
  /**
@@ -3816,12 +3903,12 @@ var Board = class _Board {
3816
3903
  }
3817
3904
  }
3818
3905
  /**
3819
- * Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
3820
- * 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.
3821
3908
  */
3822
- async grantBesideKnowledge(verb) {
3909
+ async grantBeside(beside, verb) {
3823
3910
  for (const charter of await this.listRoles()) {
3824
- if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes(verb)) {
3911
+ if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
3825
3912
  await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
3826
3913
  }
3827
3914
  }
@@ -3979,7 +4066,7 @@ var Board = class _Board {
3979
4066
  }
3980
4067
  }
3981
4068
  for (const file of await listFiles(this.paths.tasks(slug))) {
3982
- const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z15.record(z15.string(), z15.unknown()));
4069
+ const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
3983
4070
  const legacy = raw.data["thread"];
3984
4071
  if (legacy === void 0) {
3985
4072
  continue;
@@ -4057,7 +4144,7 @@ var Board = class _Board {
4057
4144
  * turns, so it follows nothing now.
4058
4145
  */
4059
4146
  async renameInboxCursors() {
4060
- const Legacy = z15.object({ inbox: z15.string().nullable() });
4147
+ const Legacy = z16.object({ inbox: z16.string().nullable() });
4061
4148
  for (const agent of await this.listAgents()) {
4062
4149
  const file = this.paths.agentCursors(agent.name);
4063
4150
  if (await exists(file)) {
@@ -4103,7 +4190,7 @@ ${charter.purpose}
4103
4190
  */
4104
4191
  async alignSeedRole(seed) {
4105
4192
  const file = this.paths.role(seed.name);
4106
- const raw = await readMarkdown(file, z15.record(z15.string(), z15.unknown()));
4193
+ const raw = await readMarkdown(file, z16.record(z16.string(), z16.unknown()));
4107
4194
  const current = RoleCharterSchema.parse(raw.data);
4108
4195
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
4109
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));
@@ -4420,7 +4507,7 @@ ${charter.purpose}
4420
4507
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
4421
4508
  async readSocietyNorms() {
4422
4509
  const file = this.paths.societyKnowledgeFile("norms");
4423
- return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
4510
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
4424
4511
  }
4425
4512
  /** Knowledge topics of a project, or of the society when the project is null. */
4426
4513
  async listKnowledge(project) {
@@ -4440,7 +4527,7 @@ ${charter.purpose}
4440
4527
  */
4441
4528
  async knowledgeAt(file, project, topic) {
4442
4529
  const { data, body } = await readLooseMarkdown(file);
4443
- const revision = z15.number().int().min(0).safeParse(data["revision"]);
4530
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
4444
4531
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
4445
4532
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
4446
4533
  return {
@@ -5403,6 +5490,60 @@ ${current.body}`);
5403
5490
  return updated;
5404
5491
  });
5405
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
+ }
5406
5547
  /** Every cron, ended ones included, oldest first. */
5407
5548
  async listCrons() {
5408
5549
  const crons = [];
@@ -5794,6 +5935,13 @@ ${current.body}`);
5794
5935
  watchTurns(source) {
5795
5936
  this.turnsInFlight = source;
5796
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
+ }
5797
5945
  /**
5798
5946
  * Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
5799
5947
  * turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
@@ -6623,6 +6771,8 @@ ${rationale}`}`);
6623
6771
  return this.updateCron(actor, VerbInputs.update_cron.parse(input));
6624
6772
  case "remove_cron":
6625
6773
  return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
6774
+ case "upload_image":
6775
+ return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
6626
6776
  default:
6627
6777
  throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
6628
6778
  }
@@ -6669,7 +6819,7 @@ ${rationale}`}`);
6669
6819
  }
6670
6820
  async readAgentRoleBody(agent) {
6671
6821
  const file = this.paths.agentRole(agent);
6672
- return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
6822
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
6673
6823
  }
6674
6824
  /** The steps of one finished turn, as the runner handed them over when it ended. */
6675
6825
  /**
@@ -7077,7 +7227,7 @@ ${rationale}`}`);
7077
7227
  return { project: slug, revision: 0, body: "" };
7078
7228
  }
7079
7229
  const { data, body } = await readLooseMarkdown(file);
7080
- const revision = z15.number().int().min(0).safeParse(data["revision"]);
7230
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
7081
7231
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
7082
7232
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
7083
7233
  return {
@@ -7351,7 +7501,7 @@ ${charter.purpose}
7351
7501
 
7352
7502
  ${charter.purpose}
7353
7503
 
7354
- 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.
7355
7505
  ` + (seed.length === 0 ? "" : `
7356
7506
  ${SEED_INSTRUCTIONS_HEADING}
7357
7507
 
@@ -7368,7 +7518,7 @@ ${seed}
7368
7518
  continue;
7369
7519
  }
7370
7520
  const file = this.paths.agentRole(agent.name);
7371
- const body = await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
7521
+ const body = await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
7372
7522
  const at = body.indexOf(`
7373
7523
  ${SEED_INSTRUCTIONS_HEADING}
7374
7524
  `);
@@ -8584,7 +8734,7 @@ function decideWake(input) {
8584
8734
  }
8585
8735
 
8586
8736
  // packages/scheduler/dist/scheduler.js
8587
- import { z as z16 } from "zod";
8737
+ import { z as z17 } from "zod";
8588
8738
  function besideFields(beside) {
8589
8739
  if (beside === void 0) {
8590
8740
  return {};
@@ -8611,21 +8761,21 @@ var DEFAULT_TIMINGS = Object.freeze({
8611
8761
  scaleCooldownMs: 36e5,
8612
8762
  reflectionMs: 24 * 36e5
8613
8763
  });
8614
- var SchedulerTimingsSchema = z16.object({
8615
- pollMs: z16.number().positive(),
8616
- debounceMs: z16.number().nonnegative(),
8617
- userDebounceMs: z16.number().nonnegative(),
8618
- heartbeatMs: z16.number().positive(),
8619
- waitingStageMs: z16.number().positive(),
8620
- leaseSweepMs: z16.number().positive(),
8621
- opsIntervalMs: z16.number().positive(),
8622
- signalRepeatMs: z16.number().positive(),
8623
- costReportMs: z16.number().positive(),
8624
- staleThreadMs: z16.number().positive(),
8625
- idleMemberMs: z16.number().positive(),
8626
- homeConflictMs: z16.number().positive(),
8627
- scaleCooldownMs: z16.number().positive(),
8628
- reflectionMs: z16.number().positive()
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()
8629
8779
  }).partial();
8630
8780
  function parseTimings(input) {
8631
8781
  const parsed = SchedulerTimingsSchema.parse(input);
@@ -8637,32 +8787,32 @@ function parseTimings(input) {
8637
8787
  }
8638
8788
  return overrides;
8639
8789
  }
8640
- var PendingSchema = z16.object({
8790
+ var PendingSchema = z17.object({
8641
8791
  dispatch: TurnDispatchSchema,
8642
- readyAt: z16.number(),
8643
- stageWakes: z16.array(TriggerSchema).optional(),
8644
- holder: z16.boolean().optional(),
8792
+ readyAt: z17.number(),
8793
+ stageWakes: z17.array(TriggerSchema).optional(),
8794
+ holder: z17.boolean().optional(),
8645
8795
  newestMessage: UlidSchema.optional(),
8646
- onlyMessages: z16.boolean().optional(),
8796
+ onlyMessages: z17.boolean().optional(),
8647
8797
  steeredThrough: UlidSchema.optional(),
8648
- onlyCrons: z16.boolean().optional()
8798
+ onlyCrons: z17.boolean().optional()
8649
8799
  });
8650
- var StateSchema = z16.object({
8651
- cursor: z16.string().nullable(),
8652
- lastHeartbeat: z16.record(z16.string(), z16.string()),
8800
+ var StateSchema = z17.object({
8801
+ cursor: z17.string().nullable(),
8802
+ lastHeartbeat: z17.record(z17.string(), z17.string()),
8653
8803
  /** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
8654
- waitingSeen: z16.record(z16.string(), z16.string()).default({}),
8804
+ waitingSeen: z17.record(z17.string(), z17.string()).default({}),
8655
8805
  /** Queued dispatches survive a restart; the cursor has already moved past the events that made them. */
8656
- pending: z16.record(z16.string(), PendingSchema).default({}),
8657
- opsReported: z16.record(z16.string(), z16.string()).default({}),
8658
- releaseCounts: z16.record(z16.string(), z16.number().int().nonnegative()).default({}),
8659
- lastOps: z16.string().nullable().default(null),
8660
- lastScaled: z16.record(z16.string(), z16.string()).default({}),
8661
- lastCostReport: z16.string().nullable().default(null),
8662
- costSinceReport: z16.number().nonnegative().default(0),
8663
- turnsSinceReport: z16.number().int().nonnegative().default(0),
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),
8664
8814
  /** When each member last reflected, or was first seen; the cadence counts from here. */
8665
- lastReflection: z16.record(z16.string(), z16.string()).default({})
8815
+ lastReflection: z17.record(z17.string(), z17.string()).default({})
8666
8816
  });
8667
8817
  function messageOf(trigger) {
8668
8818
  return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
@@ -10237,9 +10387,9 @@ var TURN_CONTRACT = [
10237
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.",
10238
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.",
10239
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.",
10240
- "- To show an image in a post, such as a screenshot or a chart, commit the file to a task's branch and link it by its path in the task's worktree, as `![what it shows](<path>)`: the board shows a file on a task's branch in place once it is committed, at the latest when your turn ends. An image at an https address shows too. Never put an image or any other file into a message as base64 or a data: URL: the board does not show it, and every reader of the conversation gets it in full in their prompt.",
10241
- "- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; a durable fact about a project's codebase or process goes through `write_knowledge` on that project; something the whole project should know now, post it to the project channel, and anything about one task, in the task's thread.",
10242
- '- 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.',
10243
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.",
10244
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.",
10245
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.",
@@ -10250,7 +10400,7 @@ var PLANNING = [
10250
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.",
10251
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.",
10252
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.",
10253
- "- 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.",
10254
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.",
10255
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.",
10256
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."
@@ -10288,7 +10438,7 @@ function renderOnboardingPreamble(context2) {
10288
10438
  function renderSkillsIndex(skills) {
10289
10439
  if (skills.length === 0) {
10290
10440
  return [
10291
- "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."
10292
10442
  ];
10293
10443
  }
10294
10444
  return [
@@ -10317,9 +10467,9 @@ var REFLECTION_INTRO = "This turn is for your memory; take no new work, and answ
10317
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.";
10318
10468
  var REFLECTION_STEPS = [
10319
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.",
10320
- '- 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.',
10321
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.",
10322
- "- 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.",
10323
10473
  "- Report memoryUpdated: true in the status object when core.md or a skill changed."
10324
10474
  ];
10325
10475
  var MAX_BODY_CHARS = 1500;
@@ -10543,7 +10693,7 @@ function buildTurnPrompt(input) {
10543
10693
  if (knowledge !== void 0 && knowledge !== null) {
10544
10694
  lines.push("", `## Knowledge of ${dispatch.project}`, "");
10545
10695
  if (knowledge.topics.length === 0) {
10546
- 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.");
10547
10697
  } else {
10548
10698
  lines.push(`Topics under ${knowledge.dir}; open one with your file tools when it is relevant.`, "");
10549
10699
  for (const topic of knowledge.topics) {
@@ -11319,6 +11469,12 @@ var DEFAULT_GRACE_MS = 2 * 6e4;
11319
11469
  var DEFAULT_REQUEST_TIMEOUT_MS = 10 * 6e4;
11320
11470
  var CONTROL_TIMEOUT_MS = 15e3;
11321
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
+ };
11322
11478
  var SWEEP_TIMEOUT_MS = 2 * 6e4;
11323
11479
  var DEFAULT_ACK_TIMEOUT_MS = 3e4;
11324
11480
  var RunnerHub = class {
@@ -11827,6 +11983,33 @@ var RunnerHub = class {
11827
11983
  }
11828
11984
  return diff;
11829
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
+ }
11830
12013
  /** The models a CLI offers, asked of a connected runner that has it; none when no runner has it. */
11831
12014
  /**
11832
12015
  * The connected runner a model list for `cli` comes from: the first of `prefer` that has the CLI,
@@ -12098,20 +12281,20 @@ async function handleMcpRequest(board2, actor, request, version) {
12098
12281
  import { Hono as Hono3 } from "hono";
12099
12282
  import { compress } from "hono/compress";
12100
12283
  import { streamSSE as streamSSE2 } from "hono/streaming";
12101
- import { z as z18, ZodError } from "zod";
12284
+ import { z as z19, ZodError } from "zod";
12102
12285
 
12103
12286
  // apps/server/dist/github-auth.js
12104
12287
  import { randomBytes as randomBytes2 } from "node:crypto";
12105
12288
  import { Hono } from "hono";
12106
- import { z as z17 } from "zod";
12289
+ import { z as z18 } from "zod";
12107
12290
  var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
12108
12291
  var STATE_TTL_MS = 10 * 6e4;
12109
12292
  var MAX_STATES = 1e3;
12110
- var TokenAnswerSchema = z17.object({ access_token: z17.string().min(1) });
12111
- var GithubUserSchema = z17.object({
12112
- login: z17.string().min(1),
12113
- id: z17.number().int(),
12114
- avatar_url: z17.string().optional()
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()
12115
12298
  });
12116
12299
  function githubAvatar(url) {
12117
12300
  try {
@@ -12586,19 +12769,19 @@ var ERROR_STATUS = {
12586
12769
  INVALID_STATE: 409
12587
12770
  };
12588
12771
  var EVENT_PAGE = 100;
12589
- var RetireBodySchema = z18.object({ reason: z18.string().min(1) });
12590
- var RunnerBodySchema = z18.object({ name: NameSchema });
12591
- var EnrollmentApprovalSchema = z18.object({ name: NameSchema });
12592
- var HomeRunnerBodySchema = z18.object({ runner: NameSchema.nullable() });
12593
- var ModelBodySchema = z18.object({ model: ModelNameSchema.nullable() });
12594
- var EffortBodySchema = z18.object({ effort: EffortSchema.nullable() });
12595
- var HistoryLimitSchema = z18.coerce.number().int().min(1).max(500);
12596
- var MessageIdsSchema = z18.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z18.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
12597
- var TimezoneBodySchema = z18.object({ timezone: TimeZoneSchema });
12598
- var ChannelBodySchema = z18.object({
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({
12599
12782
  project: NameSchema.nullable().default(null),
12600
12783
  name: NameSchema,
12601
- purpose: z18.string().min(1)
12784
+ purpose: z19.string().min(1)
12602
12785
  });
12603
12786
  function bearer2(c) {
12604
12787
  const header = c.req.header("authorization") ?? "";
@@ -12770,7 +12953,7 @@ function createApp(deps) {
12770
12953
  });
12771
12954
  api.put("/roles/:name", async (c) => {
12772
12955
  const charter = RoleCharterSchema.parse({
12773
- ...z18.record(z18.string(), z18.unknown()).parse(await c.req.json()),
12956
+ ...z19.record(z19.string(), z19.unknown()).parse(await c.req.json()),
12774
12957
  name: c.req.param("name")
12775
12958
  });
12776
12959
  return c.json(await board2.setRoleCharter(c.get("actor"), charter));
@@ -12809,6 +12992,15 @@ function createApp(deps) {
12809
12992
  return c.json(await deps.runners.branchDiff(task.project, `task/${task.id}`, BranchPathSchema.parse(c.req.param("path"))));
12810
12993
  });
12811
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
+ });
12812
13004
  api.get("/threads", async (c) => c.json(await board2.listThreads()));
12813
13005
  api.get("/requests", async (c) => c.json(await board2.listRequests()));
12814
13006
  api.get("/threads/:id", async (c) => {
@@ -13231,6 +13423,7 @@ var host = new TurnHost({
13231
13423
  });
13232
13424
  var runners = new RunnerHub({ board, host, version: VERSION, log });
13233
13425
  board.watchTurns(() => host.inFlight());
13426
+ board.readImagesWith((actor, file) => runners.readTurnImage(actor, file));
13234
13427
  var concurrency = config.concurrency ?? Number.POSITIVE_INFINITY;
13235
13428
  var timings = parseTimings(JSON.parse(process.env["STELLARIS_TIMINGS"] ?? "{}"));
13236
13429
  var scheduler = new Scheduler({ board, runner: runners, log, concurrency, timings });