@kubitnodes/stellaris 0.3.22 → 0.3.24

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 (67) hide show
  1. package/README.md +4 -4
  2. package/dist/cli/index.js +1029 -571
  3. package/dist/server/index.js +1256 -656
  4. package/package.json +1 -1
  5. package/web/dist/assets/BackLink-DKMw_rHu.js +1 -0
  6. package/web/dist/assets/{Button-Yoe21NQ2.js → Button-BYJGqZ3Q.js} +1 -1
  7. package/web/dist/assets/{ChannelForms-55otlg-m.js → ChannelForms-CkYupEL2.js} +1 -1
  8. package/web/dist/assets/ChannelView-BIXouQMX.js +2 -0
  9. package/web/dist/assets/CitizenView-C3AMFixP.js +2 -0
  10. package/web/dist/assets/CitizensView-CYFS3f9U.js +1 -0
  11. package/web/dist/assets/{CliIcon-DbMYJdlU.js → CliIcon-DVX5iLhG.js} +1 -1
  12. package/web/dist/assets/CronsView-BGCcO7bH.js +1 -0
  13. package/web/dist/assets/KnowledgeView-DLLYjd1G.js +1 -0
  14. package/web/dist/assets/Markdown--whKJBYV.js +30 -0
  15. package/web/dist/assets/MessageItem-BvDOeJci.js +1 -0
  16. package/web/dist/assets/MetricsView-B-KUNOZm.js +1 -0
  17. package/web/dist/assets/NeedsYouView-Dee_bOKj.js +1 -0
  18. package/web/dist/assets/{Overview-BveU1yJj.js → Overview-DFiHDFnA.js} +1 -1
  19. package/web/dist/assets/{Pane-CLjdfBMM.js → Pane-DTmByqgA.js} +1 -1
  20. package/web/dist/assets/{Picker-CXOe5Xdu.js → Picker-DsHm4f7s.js} +1 -1
  21. package/web/dist/assets/{ProjectView-oa4Z46ED.js → ProjectView-DsRXW-qk.js} +1 -1
  22. package/web/dist/assets/ProposalView-B-4P1S-s.js +1 -0
  23. package/web/dist/assets/{ProposalsView-BZHivSEm.js → ProposalsView-uEuj-ooV.js} +1 -1
  24. package/web/dist/assets/Quota-D8WDWp5H.js +1 -0
  25. package/web/dist/assets/{RunnersView-BrJF-aLU.js → RunnersView-BB3q65rG.js} +1 -1
  26. package/web/dist/assets/SkillFileList-DhdEcFQA.js +2 -0
  27. package/web/dist/assets/SkillView-vAv7zvtJ.js +1 -0
  28. package/web/dist/assets/SocietyView-Dv4phSkz.js +1 -0
  29. package/web/dist/assets/{StickyList-7tAlUDCN.js → StickyList-C4fC_KJv.js} +1 -1
  30. package/web/dist/assets/SubjectThread-BEXt-tub.js +1 -0
  31. package/web/dist/assets/{TaskFileView-BEXQJQQ6.js → TaskFileView-B9sex0tS.js} +2 -2
  32. package/web/dist/assets/{TaskView-Cfhb7NuO.js → TaskView-BCT388kb.js} +2 -2
  33. package/web/dist/assets/{TasksView-3XljXcoi.js → TasksView-CuPay34-.js} +1 -1
  34. package/web/dist/assets/{ThreadForms-D2y5dXSC.js → ThreadForms-DzoRDeBh.js} +1 -1
  35. package/web/dist/assets/ThreadItem-CDVppcOZ.js +1 -0
  36. package/web/dist/assets/ThreadView-B4F10_j1.js +1 -0
  37. package/web/dist/assets/citizen-BPIJzKdn.js +1 -0
  38. package/web/dist/assets/{compose-DNc3_c5x.js → compose-BUhbVoI-.js} +1 -1
  39. package/web/dist/assets/format-CvfmDNoY.js +55 -0
  40. package/web/dist/assets/{governance-RBQ4Lam0.js → governance-Bba0OHvJ.js} +1 -1
  41. package/web/dist/assets/index-Bj8MlyAn.css +2 -0
  42. package/web/dist/assets/index-DD3J11PT.js +23 -0
  43. package/web/dist/assets/{mentions-B2Qchd_6.js → mentions-D838wlfQ.js} +1 -1
  44. package/web/dist/assets/{tasks-DCUj5w_d.js → tasks-CTaxXtGL.js} +1 -1
  45. package/web/dist/assets/useMatch-B8j0hm1q.js +1 -0
  46. package/web/dist/index.html +9 -9
  47. package/web/dist/assets/BackLink-CD-iuy-h.js +0 -1
  48. package/web/dist/assets/ChannelView-DJvLzg0N.js +0 -2
  49. package/web/dist/assets/CitizenView-m1K2aTvK.js +0 -2
  50. package/web/dist/assets/CitizensView-BeTgmrKE.js +0 -1
  51. package/web/dist/assets/CronsView-B4kXk5Ld.js +0 -1
  52. package/web/dist/assets/KnowledgeView-aSGxDDKy.js +0 -1
  53. package/web/dist/assets/Markdown-c9bCCLHg.js +0 -30
  54. package/web/dist/assets/MessageItem-DKJKMqs-.js +0 -1
  55. package/web/dist/assets/MetricsView-D2TTry5O.js +0 -1
  56. package/web/dist/assets/NeedsYouView-C3celRei.js +0 -1
  57. package/web/dist/assets/ProposalView-B4T-EoX9.js +0 -2
  58. package/web/dist/assets/Quota-BC7dpnwd.js +0 -1
  59. package/web/dist/assets/SocietyView-C1yosPuf.js +0 -1
  60. package/web/dist/assets/SubjectThread-CCq6ZAnR.js +0 -1
  61. package/web/dist/assets/ThreadItem-DeIhhmyu.js +0 -1
  62. package/web/dist/assets/ThreadView-CbUnt9WL.js +0 -1
  63. package/web/dist/assets/citizen--bqux6i_.js +0 -1
  64. package/web/dist/assets/format-B6fjC0_a.js +0 -55
  65. package/web/dist/assets/index-CyCty7a2.js +0 -23
  66. package/web/dist/assets/index-GRqxKN35.css +0 -2
  67. package/web/dist/assets/useMatch-CfbBQirn.js +0 -1
package/dist/cli/index.js CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  // apps/cli/dist/index.js
4
4
  import { readFile as readFile5 } from "node:fs/promises";
5
- import path7 from "node:path";
5
+ import path8 from "node:path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // packages/board-core/dist/board.js
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
- import path6 from "node:path";
9
+ import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir4, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
10
+ import path7 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",
@@ -232,6 +234,7 @@ var CronFrontmatterSchema = z3.object({
232
234
  scope: NameSchema,
233
235
  thread: UlidSchema.optional(),
234
236
  channel: NameSchema.optional(),
237
+ place: NameSchema.optional(),
235
238
  schedule: CronScheduleSchema,
236
239
  createdBy: NameSchema,
237
240
  createdAt: IsoDateTimeSchema,
@@ -937,7 +940,8 @@ var DashboardSchema = z5.object({
937
940
  var ProposalOriginSchema = z5.object({
938
941
  scope: NameSchema,
939
942
  thread: UlidSchema.optional(),
940
- channel: NameSchema.optional()
943
+ channel: NameSchema.optional(),
944
+ place: NameSchema.optional()
941
945
  });
942
946
  var SKILL_FILES_LIMIT = 100;
943
947
  var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
@@ -985,130 +989,169 @@ var DecisionSchema = z5.object({
985
989
  });
986
990
 
987
991
  // packages/shared/dist/verbs.js
992
+ import { z as z7 } from "zod";
993
+
994
+ // packages/shared/dist/images.js
988
995
  import { z as z6 } from "zod";
989
- var MomentSchema = z6.iso.datetime({ offset: true });
990
- var CronTitleSchema = z6.string().trim().min(1).max(120);
991
- var CronNoteSchema = z6.string().trim().min(1).max(4e3);
996
+ var IMAGE_LIMIT_BYTES = 8 * 1024 * 1024;
997
+ var ImageTypeSchema = z6.enum(["png", "jpeg", "gif", "webp"]);
998
+ function startsWith(bytes, prefix, at = 0) {
999
+ for (let index = 0; index < prefix.length; index += 1) {
1000
+ if (bytes[at + index] !== prefix[index]) {
1001
+ return false;
1002
+ }
1003
+ }
1004
+ return true;
1005
+ }
1006
+ var ascii = (text) => new TextEncoder().encode(text);
1007
+ function imageTypeOf(bytes) {
1008
+ if (startsWith(bytes, [137, 80, 78, 71, 13, 10, 26, 10])) {
1009
+ return "png";
1010
+ }
1011
+ if (startsWith(bytes, [255, 216, 255])) {
1012
+ return "jpeg";
1013
+ }
1014
+ if (startsWith(bytes, ascii("GIF87a")) || startsWith(bytes, ascii("GIF89a"))) {
1015
+ return "gif";
1016
+ }
1017
+ if (startsWith(bytes, ascii("RIFF")) && startsWith(bytes, ascii("WEBP"), 8)) {
1018
+ return "webp";
1019
+ }
1020
+ return null;
1021
+ }
1022
+ var ImagePathSchema = z6.string().min(1).max(4096).refine((value) => !value.includes("\0"), { message: "a path with no NUL character" });
1023
+ var IMAGE_SCHEME = "image:";
1024
+ var UploadedImageSchema = z6.object({
1025
+ id: UlidSchema,
1026
+ type: ImageTypeSchema,
1027
+ size: z6.number().int().positive(),
1028
+ markdown: z6.string()
1029
+ });
1030
+
1031
+ // packages/shared/dist/verbs.js
1032
+ var MomentSchema = z7.iso.datetime({ offset: true });
1033
+ var CronTitleSchema = z7.string().trim().min(1).max(120);
1034
+ var CronNoteSchema = z7.string().trim().min(1).max(4e3);
992
1035
  var VerbInputs = {
993
- post_message: z6.object({
1036
+ post_message: z7.object({
994
1037
  /** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
995
1038
  channel: ChannelRefSchema.optional(),
996
- body: z6.string().min(1),
1039
+ body: z7.string().min(1),
997
1040
  thread_id: UlidSchema.optional()
998
1041
  }),
999
- read_inbox: z6.object({
1042
+ read_inbox: z7.object({
1000
1043
  since_cursor: UlidSchema.nullable().optional(),
1001
- limit: z6.number().int().positive().max(200).default(50),
1002
- advance: z6.boolean().default(true)
1044
+ limit: z7.number().int().positive().max(200).default(50),
1045
+ advance: z7.boolean().default(true)
1003
1046
  }),
1004
- search: z6.object({
1005
- query: z6.string().min(1),
1047
+ search: z7.object({
1048
+ query: z7.string().min(1),
1006
1049
  project: NameSchema.optional(),
1007
1050
  channel: ChannelRefSchema.optional(),
1008
- limit: z6.number().int().positive().max(100).default(20)
1051
+ limit: z7.number().int().positive().max(100).default(20)
1009
1052
  }),
1010
1053
  /** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
1011
- open_thread: z6.object({
1054
+ open_thread: z7.object({
1012
1055
  task_id: UlidSchema.optional(),
1013
1056
  proposal_id: UlidSchema.optional(),
1014
1057
  channel: ChannelRefSchema.optional(),
1015
- title: z6.string().min(1).max(200).optional()
1058
+ title: z7.string().min(1).max(200).optional()
1016
1059
  }),
1017
- close_thread: z6.object({ thread_id: UlidSchema, summary: z6.string().min(1) }),
1018
- create_task: z6.object({
1060
+ close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
1061
+ create_task: z7.object({
1019
1062
  project: NameSchema,
1020
- title: z6.string().min(1).max(200),
1021
- body: z6.string().default(""),
1063
+ title: z7.string().min(1).max(200),
1064
+ body: z7.string().default(""),
1022
1065
  parent_id: UlidSchema.optional(),
1023
- required_capabilities: z6.array(z6.string()).default([]),
1066
+ required_capabilities: z7.array(z7.string()).default([]),
1024
1067
  /** The plan; without it the task gets one stage, work, that anyone in the project may take. */
1025
- stages: z6.array(PlanStageSchema).min(1).optional(),
1068
+ stages: z7.array(PlanStageSchema).min(1).optional(),
1026
1069
  /** A channel of the project the task belongs to, such as a release's; its thread opens there. */
1027
1070
  channel: NameSchema.default("general")
1028
1071
  }),
1029
- claim_task: z6.object({ task_id: UlidSchema }),
1030
- release_task: z6.object({ task_id: UlidSchema }),
1031
- update_task: z6.object({
1072
+ claim_task: z7.object({ task_id: UlidSchema }),
1073
+ release_task: z7.object({ task_id: UlidSchema }),
1074
+ update_task: z7.object({
1032
1075
  task_id: UlidSchema,
1033
1076
  /** Move the task back to this earlier stage. */
1034
1077
  stage: StageIdSchema.optional(),
1035
- status: z6.enum(["abandoned"]).optional(),
1078
+ status: z7.enum(["abandoned"]).optional(),
1036
1079
  /** Posted to the task's thread as the caller, marked with the step when the task moved. */
1037
- note: z6.string().min(1).optional(),
1038
- blocked_by: z6.array(UlidSchema).optional(),
1080
+ note: z7.string().min(1).optional(),
1081
+ blocked_by: z7.array(UlidSchema).optional(),
1039
1082
  /**
1040
1083
  * The pull request a `ghpr` task lands through, on its project's repository, with the merge
1041
1084
  * commit's subject and body; null unlinks it.
1042
1085
  */
1043
- pull_request: z6.object({
1086
+ pull_request: z7.object({
1044
1087
  url: PullRequestUrlSchema,
1045
1088
  merge_subject: MergeSubjectSchema.optional(),
1046
- merge_body: z6.string().max(2e4).optional()
1089
+ merge_body: z7.string().max(2e4).optional()
1047
1090
  }).nullable().optional()
1048
1091
  }),
1049
- get_task: z6.object({ task_id: UlidSchema }),
1050
- subscribe: z6.object({ channel: ChannelRefSchema }),
1051
- unsubscribe: z6.object({ channel: ChannelRefSchema }),
1052
- propose: z6.object({
1092
+ get_task: z7.object({ task_id: UlidSchema }),
1093
+ subscribe: z7.object({ channel: ChannelRefSchema }),
1094
+ unsubscribe: z7.object({ channel: ChannelRefSchema }),
1095
+ propose: z7.object({
1053
1096
  kind: ProposalKindSchema,
1054
- charter: z6.record(z6.string(), z6.unknown()),
1055
- rationale: z6.string().default("")
1097
+ charter: z7.record(z7.string(), z7.unknown()),
1098
+ rationale: z7.string().default("")
1056
1099
  }),
1057
- approve: z6.object({ proposal_id: UlidSchema, reason: z6.string().optional() }),
1058
- reject: z6.object({ proposal_id: UlidSchema, reason: z6.string().min(1) }),
1059
- create_project: z6.object({
1100
+ approve: z7.object({ proposal_id: UlidSchema, reason: z7.string().optional() }),
1101
+ reject: z7.object({ proposal_id: UlidSchema, reason: z7.string().min(1) }),
1102
+ create_project: z7.object({
1060
1103
  slug: NameSchema,
1061
- name: z6.string().min(1).optional(),
1062
- repo: z6.string().min(1).nullable().default(null),
1104
+ name: z7.string().min(1).optional(),
1105
+ repo: z7.string().min(1).nullable().default(null),
1063
1106
  default_branch: DefaultBranchSchema.default("main"),
1064
1107
  on_done: CompletionEffectSchema.optional()
1065
1108
  }),
1066
- join_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1067
- leave_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
1068
- write_knowledge: z6.object({
1109
+ join_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
1110
+ leave_project: z7.object({ project: NameSchema, agent: NameSchema.optional() }),
1111
+ write_knowledge: z7.object({
1069
1112
  /** A project you belong to, or null for society knowledge, which the steward and the user curate. */
1070
1113
  project: NameSchema.nullable().default(null),
1071
1114
  topic: NameSchema,
1072
- body: z6.string().min(1),
1115
+ body: z7.string().min(1),
1073
1116
  /** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
1074
- revision: z6.number().int().min(0)
1117
+ revision: z7.number().int().min(0)
1075
1118
  }),
1076
- remove_knowledge: z6.object({
1119
+ remove_knowledge: z7.object({
1077
1120
  /** As for write_knowledge: a project you belong to, or null for society knowledge. */
1078
1121
  project: NameSchema.nullable().default(null),
1079
1122
  topic: NameSchema
1080
1123
  }),
1081
- plan_task: z6.object({
1124
+ plan_task: z7.object({
1082
1125
  task_id: UlidSchema,
1083
- stages: z6.array(PlanEditStageSchema),
1126
+ stages: z7.array(PlanEditStageSchema),
1084
1127
  on_done: CompletionEffectSchema.optional()
1085
1128
  }),
1086
1129
  /** The note is posted to the task's thread as the caller, marked with the step. */
1087
- advance_task: z6.object({ task_id: UlidSchema, note: z6.string().min(1).optional() }),
1088
- configure_project: z6.object({
1130
+ advance_task: z7.object({ task_id: UlidSchema, note: z7.string().min(1).optional() }),
1131
+ configure_project: z7.object({
1089
1132
  project: NameSchema,
1090
1133
  on_done: CompletionEffectSchema.optional(),
1091
1134
  /** A new display name; the slug, which everything refers to the project by, never changes. */
1092
- name: z6.string().trim().min(1).max(120).optional(),
1135
+ name: z7.string().trim().min(1).max(120).optional(),
1093
1136
  /** The branch tasks start from and land on from now; tasks in play keep their branches. */
1094
1137
  default_branch: DefaultBranchSchema.optional()
1095
1138
  }),
1096
- archive_project: z6.object({ project: NameSchema, reason: z6.string().min(1) }),
1097
- create_channel: z6.object({
1139
+ archive_project: z7.object({ project: NameSchema, reason: z7.string().min(1) }),
1140
+ create_channel: z7.object({
1098
1141
  /** A project, or null for a channel of the society. */
1099
1142
  project: NameSchema.nullable().default(null),
1100
1143
  name: NameSchema,
1101
- purpose: z6.string().min(1)
1144
+ purpose: z7.string().min(1)
1102
1145
  }),
1103
- archive_channel: z6.object({ channel: ChannelRefSchema, reason: z6.string().min(1) }),
1104
- update_dashboard: z6.object({
1146
+ archive_channel: z7.object({ channel: ChannelRefSchema, reason: z7.string().min(1) }),
1147
+ update_dashboard: z7.object({
1105
1148
  project: NameSchema,
1106
1149
  /** The whole dashboard as markdown; the board keeps its frontmatter. */
1107
- body: z6.string().min(1),
1150
+ body: z7.string().min(1),
1108
1151
  /** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
1109
- revision: z6.number().int().min(0)
1152
+ revision: z7.number().int().min(0)
1110
1153
  }),
1111
- create_cron: z6.object({
1154
+ create_cron: z7.object({
1112
1155
  title: CronTitleSchema,
1113
1156
  /** What to do when it fires; quoted in the prompt of every turn it starts. */
1114
1157
  note: CronNoteSchema,
@@ -1127,10 +1170,10 @@ var VerbInputs = {
1127
1170
  /** An open channel of that place other than general, whose conversation it fires in. */
1128
1171
  channel: NameSchema.optional()
1129
1172
  }),
1130
- update_cron: z6.object({
1173
+ update_cron: z7.object({
1131
1174
  cron_id: UlidSchema,
1132
1175
  /** Pause it, or resume it, which starts its schedule afresh from now. */
1133
- paused: z6.boolean().optional(),
1176
+ paused: z7.boolean().optional(),
1134
1177
  title: CronTitleSchema.optional(),
1135
1178
  note: CronNoteSchema.optional(),
1136
1179
  /** A new expression, or `at` for one time; either starts the schedule afresh. */
@@ -1139,12 +1182,16 @@ var VerbInputs = {
1139
1182
  timezone: TimeZoneSchema.nullable().optional(),
1140
1183
  at: MomentSchema.optional()
1141
1184
  }),
1142
- remove_cron: z6.object({ cron_id: UlidSchema, reason: z6.string().trim().min(1) }),
1143
- remove_skill: z6.object({ name: NameSchema, reason: z6.string().trim().min(1) })
1185
+ remove_cron: z7.object({ cron_id: UlidSchema, reason: z7.string().trim().min(1) }),
1186
+ remove_skill: z7.object({ name: NameSchema, reason: z7.string().trim().min(1) }),
1187
+ upload_image: z7.object({
1188
+ /** The image file on the machine your turn runs on: absolute, or relative to your working directory. */
1189
+ path: ImagePathSchema
1190
+ })
1144
1191
  };
1145
1192
 
1146
1193
  // packages/shared/dist/events.js
1147
- import { z as z7 } from "zod";
1194
+ import { z as z8 } from "zod";
1148
1195
  var BOARD_EVENT_TYPES = [
1149
1196
  "society.initialized",
1150
1197
  "project.added",
@@ -1208,21 +1255,22 @@ var BOARD_EVENT_TYPES = [
1208
1255
  "cron.updated",
1209
1256
  "cron.fired",
1210
1257
  "cron.skipped",
1211
- "cron.ended"
1258
+ "cron.ended",
1259
+ "image.uploaded"
1212
1260
  ];
1213
- var BoardEventTypeSchema = z7.enum(BOARD_EVENT_TYPES);
1214
- var BoardEventSchema = z7.object({
1261
+ var BoardEventTypeSchema = z8.enum(BOARD_EVENT_TYPES);
1262
+ var BoardEventSchema = z8.object({
1215
1263
  id: UlidSchema,
1216
1264
  ts: IsoDateTimeSchema,
1217
1265
  type: BoardEventTypeSchema,
1218
1266
  actor: NameSchema,
1219
- payload: z7.record(z7.string(), z7.unknown())
1267
+ payload: z8.record(z8.string(), z8.unknown())
1220
1268
  });
1221
- var UsageSchema = z7.object({
1222
- inputTokens: z7.number().int().nonnegative(),
1223
- outputTokens: z7.number().int().nonnegative(),
1224
- cacheReadTokens: z7.number().int().nonnegative().default(0),
1225
- cacheWriteTokens: z7.number().int().nonnegative().default(0)
1269
+ var UsageSchema = z8.object({
1270
+ inputTokens: z8.number().int().nonnegative(),
1271
+ outputTokens: z8.number().int().nonnegative(),
1272
+ cacheReadTokens: z8.number().int().nonnegative().default(0),
1273
+ cacheWriteTokens: z8.number().int().nonnegative().default(0)
1226
1274
  });
1227
1275
  function addUsage(a, b) {
1228
1276
  return {
@@ -1232,14 +1280,14 @@ function addUsage(a, b) {
1232
1280
  cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
1233
1281
  };
1234
1282
  }
1235
- var TurnStatusSchema = z7.object({
1236
- summary: z7.string().min(1),
1237
- claimsHeld: z7.array(UlidSchema).default([]),
1238
- blockedOn: z7.array(z7.string()).default([]),
1239
- needsUserDecision: z7.boolean().default(false),
1240
- memoryUpdated: z7.boolean().default(false)
1241
- });
1242
- var TurnExitReasonSchema = z7.enum([
1283
+ var TurnStatusSchema = z8.object({
1284
+ summary: z8.string().min(1),
1285
+ claimsHeld: z8.array(UlidSchema).default([]),
1286
+ blockedOn: z8.array(z8.string()).default([]),
1287
+ needsUserDecision: z8.boolean().default(false),
1288
+ memoryUpdated: z8.boolean().default(false)
1289
+ });
1290
+ var TurnExitReasonSchema = z8.enum([
1243
1291
  "completed",
1244
1292
  "timeout",
1245
1293
  "error",
@@ -1247,50 +1295,51 @@ var TurnExitReasonSchema = z7.enum([
1247
1295
  "blocked",
1248
1296
  "stopped"
1249
1297
  ]);
1250
- var AgentEventSchema = z7.discriminatedUnion("type", [
1251
- z7.object({
1252
- type: z7.literal("turn_started"),
1298
+ var ResumeFailureSchema = z8.enum(["lost", "retry"]);
1299
+ var AgentEventSchema = z8.discriminatedUnion("type", [
1300
+ z8.object({
1301
+ type: z8.literal("turn_started"),
1253
1302
  agent: NameSchema,
1254
- session: z7.string(),
1303
+ session: z8.string(),
1255
1304
  runner: NameSchema,
1256
1305
  /** The model the CLI reports running, when it reports one. */
1257
- model: z7.string().optional()
1306
+ model: z8.string().optional()
1258
1307
  }),
1259
1308
  /**
1260
1309
  * The CLI took a steer into the running turn. Adapters name the steer; the server adds the
1261
1310
  * messages it delivered and the text the CLI was given.
1262
1311
  */
1263
- z7.object({
1264
- type: z7.literal("steered"),
1265
- steer: z7.string().min(1),
1266
- messages: z7.array(UlidSchema).optional(),
1267
- text: z7.string().optional()
1312
+ z8.object({
1313
+ type: z8.literal("steered"),
1314
+ steer: z8.string().min(1),
1315
+ messages: z8.array(UlidSchema).optional(),
1316
+ text: z8.string().optional()
1268
1317
  }),
1269
- z7.object({ type: z7.literal("text"), delta: z7.string() }),
1270
- z7.object({ type: z7.literal("tool_call"), name: z7.string(), input: z7.unknown() }),
1271
- z7.object({
1272
- type: z7.literal("tool_result"),
1273
- name: z7.string(),
1274
- ok: z7.boolean(),
1318
+ z8.object({ type: z8.literal("text"), delta: z8.string() }),
1319
+ z8.object({ type: z8.literal("tool_call"), name: z8.string(), input: z8.unknown() }),
1320
+ z8.object({
1321
+ type: z8.literal("tool_result"),
1322
+ name: z8.string(),
1323
+ ok: z8.boolean(),
1275
1324
  /** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
1276
- output: z7.string().optional()
1325
+ output: z8.string().optional()
1277
1326
  }),
1278
- z7.object({ type: z7.literal("approval_requested"), kind: z7.string(), detail: z7.unknown() }),
1279
- z7.object({
1280
- type: z7.literal("turn_completed"),
1327
+ z8.object({ type: z8.literal("approval_requested"), kind: z8.string(), detail: z8.unknown() }),
1328
+ z8.object({
1329
+ type: z8.literal("turn_completed"),
1281
1330
  usage: UsageSchema,
1282
- costUsd: z7.number().nonnegative(),
1331
+ costUsd: z8.number().nonnegative(),
1283
1332
  status: TurnStatusSchema.nullable(),
1284
1333
  exitReason: TurnExitReasonSchema
1285
1334
  }),
1286
- z7.object({ type: z7.literal("error"), message: z7.string() })
1335
+ z8.object({ type: z8.literal("error"), message: z8.string() })
1287
1336
  ]);
1288
- var TranscriptEntrySchema = z7.object({
1337
+ var TranscriptEntrySchema = z8.object({
1289
1338
  ts: IsoDateTimeSchema,
1290
1339
  event: AgentEventSchema
1291
1340
  });
1292
- var LiveTurnEventSchema = z7.object({
1293
- seq: z7.number().int().positive(),
1341
+ var LiveTurnEventSchema = z8.object({
1342
+ seq: z8.number().int().positive(),
1294
1343
  ts: IsoDateTimeSchema,
1295
1344
  agent: NameSchema,
1296
1345
  project: NameSchema,
@@ -1298,243 +1347,280 @@ var LiveTurnEventSchema = z7.object({
1298
1347
  thread: UlidSchema.optional(),
1299
1348
  /** The channel, never general, whose conversation the turn is in. */
1300
1349
  channel: NameSchema.optional(),
1350
+ /** The project whose channels' conversation the turn is in, for a turn in the society scope. */
1351
+ place: NameSchema.optional(),
1301
1352
  event: AgentEventSchema
1302
1353
  });
1303
1354
 
1304
1355
  // packages/shared/dist/turns.js
1305
- import { z as z9 } from "zod";
1356
+ import { z as z10 } from "zod";
1306
1357
 
1307
1358
  // packages/shared/dist/runner.js
1308
- import { z as z8 } from "zod";
1359
+ import { z as z9 } from "zod";
1309
1360
  var HOME_SCRATCH = "scratch";
1310
1361
  var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1311
- var RunnerHelloSchema = z8.object({
1312
- protocol: z8.number().int().positive(),
1313
- version: z8.string().min(1),
1362
+ var RunnerHelloSchema = z9.object({
1363
+ protocol: z9.number().int().positive(),
1364
+ version: z9.string().min(1),
1314
1365
  os: RunnerOsSchema,
1315
- clis: z8.array(CliKindSchema),
1366
+ clis: z9.array(CliKindSchema),
1316
1367
  /** The CLIs whose adapter can keep a session warm between turns. */
1317
- residentClis: z8.array(CliKindSchema).default([]),
1368
+ residentClis: z9.array(CliKindSchema).default([]),
1318
1369
  /** The CLIs whose adapter can take input into a running turn; the server steers only these. */
1319
- steerableClis: z8.array(CliKindSchema).default([]),
1370
+ steerableClis: z9.array(CliKindSchema).default([]),
1320
1371
  /** The CLIs whose adapter can stop a running turn; the server stops only these. */
1321
- stoppableClis: z8.array(CliKindSchema).default([]),
1372
+ stoppableClis: z9.array(CliKindSchema).default([]),
1322
1373
  /** Whether the runner answers file reads on its projects' branches; one from before never does. */
1323
- branchReads: z8.boolean().default(false),
1374
+ branchReads: z9.boolean().default(false),
1324
1375
  /** Whether the runner lists what a branch changed; one from before never does. */
1325
- branchChanges: z8.boolean().default(false),
1376
+ branchChanges: z9.boolean().default(false),
1326
1377
  /** Whether the runner answers a read of a CLI's quota; one from before never does. */
1327
- quotaReads: z8.boolean().default(false),
1328
- capabilities: z8.array(z8.string()).default([]),
1378
+ quotaReads: z9.boolean().default(false),
1379
+ /** Whether the runner reads an image a turn running there uploads; one from before never does. */
1380
+ imageReads: z9.boolean().default(false),
1381
+ capabilities: z9.array(z9.string()).default([]),
1329
1382
  /**
1330
1383
  * What the runner's account lacks of the git identity citizens' own commits carry, checked
1331
1384
  * before each hello outside any repository; empty when it has both. Absent from a runner from
1332
1385
  * before the check, which no default fills in, so the board marks only what a runner reported.
1333
1386
  */
1334
- gitIdentityMissing: z8.array(GitIdentityPartSchema).optional(),
1387
+ gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
1335
1388
  /** Turns the runner runs at once, or null for no limit. */
1336
- slots: z8.number().int().positive().nullable(),
1389
+ slots: z9.number().int().positive().nullable(),
1337
1390
  /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1338
- turns: z8.array(UlidSchema).default([])
1391
+ turns: z9.array(UlidSchema).default([])
1339
1392
  });
1340
- var RunnerWelcomeSchema = z8.object({ name: NameSchema, version: z8.string() });
1341
- var RunnerEnrollRequestSchema = z8.object({
1342
- protocol: z8.number().int().positive(),
1343
- version: z8.string().min(1).max(64),
1344
- hostname: z8.string().min(1).max(253),
1393
+ var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
1394
+ var RunnerEnrollRequestSchema = z9.object({
1395
+ protocol: z9.number().int().positive(),
1396
+ version: z9.string().min(1).max(64),
1397
+ hostname: z9.string().min(1).max(253),
1345
1398
  os: RunnerOsSchema,
1346
- clis: z8.array(CliKindSchema).max(8),
1347
- capabilities: z8.array(z8.string().min(1).max(64)).max(32).default([])
1399
+ clis: z9.array(CliKindSchema).max(8),
1400
+ capabilities: z9.array(z9.string().min(1).max(64)).max(32).default([])
1348
1401
  });
1349
- var RunnerEnrollmentSchema = z8.object({
1350
- deviceCode: z8.string().min(1),
1351
- userCode: z8.string().min(1),
1402
+ var RunnerEnrollmentSchema = z9.object({
1403
+ deviceCode: z9.string().min(1),
1404
+ userCode: z9.string().min(1),
1352
1405
  expiresAt: IsoDateTimeSchema,
1353
- intervalMs: z8.number().int().positive()
1406
+ intervalMs: z9.number().int().positive()
1354
1407
  });
1355
- var RunnerEnrollPollSchema = z8.object({ deviceCode: z8.string().min(1) });
1356
- var RunnerEnrollStatusSchema = z8.discriminatedUnion("status", [
1357
- z8.object({ status: z8.literal("pending") }),
1358
- z8.object({ status: z8.literal("approved"), name: NameSchema, token: z8.string().min(1) }),
1359
- z8.object({ status: z8.literal("denied") })
1408
+ var RunnerEnrollPollSchema = z9.object({ deviceCode: z9.string().min(1) });
1409
+ var RunnerEnrollStatusSchema = z9.discriminatedUnion("status", [
1410
+ z9.object({ status: z9.literal("pending") }),
1411
+ z9.object({ status: z9.literal("approved"), name: NameSchema, token: z9.string().min(1) }),
1412
+ z9.object({ status: z9.literal("denied") })
1360
1413
  ]);
1361
- var PendingEnrollmentSchema = z8.object({
1362
- userCode: z8.string().min(1),
1363
- hostname: z8.string(),
1414
+ var PendingEnrollmentSchema = z9.object({
1415
+ userCode: z9.string().min(1),
1416
+ hostname: z9.string(),
1364
1417
  os: RunnerOsSchema,
1365
- clis: z8.array(CliKindSchema),
1366
- capabilities: z8.array(z8.string()),
1367
- version: z8.string(),
1418
+ clis: z9.array(CliKindSchema),
1419
+ capabilities: z9.array(z9.string()),
1420
+ version: z9.string(),
1368
1421
  requestedAt: IsoDateTimeSchema,
1369
1422
  expiresAt: IsoDateTimeSchema
1370
1423
  });
1371
- var ProjectRepoSchema = z8.object({
1424
+ var ProjectRepoSchema = z9.object({
1372
1425
  slug: NameSchema,
1373
- origin: z8.string().nullable(),
1374
- defaultBranch: z8.string().min(1),
1426
+ origin: z9.string().nullable(),
1427
+ defaultBranch: z9.string().min(1),
1375
1428
  /**
1376
1429
  * The board lands finished tasks on the default branch, itself or through their pull requests,
1377
1430
  * so the runner lets nothing else move it.
1378
1431
  */
1379
- boardLands: z8.boolean()
1432
+ boardLands: z9.boolean()
1380
1433
  });
1381
- var TurnWorkspaceSchema = z8.discriminatedUnion("kind", [
1382
- z8.object({
1383
- kind: z8.literal("home"),
1434
+ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
1435
+ z9.object({
1436
+ kind: z9.literal("home"),
1384
1437
  thread: UlidSchema.optional(),
1385
- channel: NameSchema.optional()
1438
+ channel: NameSchema.optional(),
1439
+ place: NameSchema.optional()
1386
1440
  }),
1387
- z8.object({
1388
- kind: z8.literal("project"),
1441
+ z9.object({
1442
+ kind: z9.literal("project"),
1389
1443
  repo: ProjectRepoSchema,
1390
- branches: z8.array(z8.string().min(1)),
1444
+ branches: z9.array(z9.string().min(1)),
1391
1445
  thread: UlidSchema.optional(),
1392
1446
  channel: NameSchema.optional()
1393
1447
  }),
1394
- z8.object({ kind: z8.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
1448
+ z9.object({
1449
+ kind: z9.literal("task"),
1450
+ repo: ProjectRepoSchema,
1451
+ taskId: UlidSchema,
1452
+ /**
1453
+ * The citizen holding the task's current stage, when someone does: a turn of anyone else
1454
+ * works detached at the branch's tip, so the holder's turns find the branch free.
1455
+ */
1456
+ holder: NameSchema.optional()
1457
+ })
1395
1458
  ]);
1396
- var TurnJobSchema = z8.object({
1459
+ var TurnJobSchema = z9.object({
1397
1460
  turnId: UlidSchema,
1398
1461
  agent: NameSchema,
1399
1462
  role: NameSchema,
1400
1463
  cli: CliKindSchema,
1401
- model: z8.string().optional(),
1464
+ model: z9.string().optional(),
1402
1465
  /** The reasoning effort set on the agent; absent for the model's own default. */
1403
- effort: z8.string().optional(),
1466
+ effort: z9.string().optional(),
1404
1467
  /** A project slug, or the society scope. */
1405
1468
  scope: NameSchema,
1406
1469
  /** The thread whose conversation the turn is in; absent for the home conversation. */
1407
1470
  thread: UlidSchema.optional(),
1408
1471
  /** The channel, never general, whose conversation the turn is in. */
1409
1472
  channel: NameSchema.optional(),
1473
+ /** The project whose channels' conversation the turn is in, for a turn in the society scope. */
1474
+ place: NameSchema.optional(),
1410
1475
  /**
1411
1476
  * The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
1412
1477
  * the outcome reports it.
1413
1478
  */
1414
- session: z8.string().min(1).nullable(),
1479
+ session: z9.string().min(1).nullable(),
1415
1480
  /** The running total the session reported after its last turn, which a resumed session starts from. */
1416
- costSoFarUsd: z8.number().nonnegative(),
1481
+ costSoFarUsd: z9.number().nonnegative(),
1417
1482
  /** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
1418
- instructions: z8.string(),
1419
- prompt: z8.string(),
1420
- mcp: z8.object({ url: z8.string().min(1), token: z8.string().min(1) }),
1421
- limits: z8.object({
1422
- timeoutMs: z8.number().int().positive().nullable(),
1423
- maxTurns: z8.number().int().positive().nullable()
1483
+ instructions: z9.string(),
1484
+ prompt: z9.string(),
1485
+ mcp: z9.object({ url: z9.string().min(1), token: z9.string().min(1) }),
1486
+ limits: z9.object({
1487
+ timeoutMs: z9.number().int().positive().nullable(),
1488
+ maxTurns: z9.number().int().positive().nullable()
1424
1489
  }),
1425
1490
  /** Present when the runner keeps the conversation's session warm between turns, under `key`. */
1426
- resident: z8.object({ key: z8.string().min(1), idleMs: z8.number().int().positive() }).optional(),
1491
+ resident: z9.object({ key: z9.string().min(1), idleMs: z9.number().int().positive() }).optional(),
1427
1492
  workspace: TurnWorkspaceSchema
1428
1493
  });
1429
- var TurnWorkSchema = z8.object({ branch: z8.string().min(1), head: z8.string().min(1) });
1430
- var TurnEventsSchema = z8.object({ entries: z8.array(TranscriptEntrySchema) });
1431
- var TurnOutcomeSchema = z8.object({
1494
+ var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
1495
+ var StuckWorkSchema = z9.object({
1496
+ branch: z9.string().min(1),
1497
+ agent: NameSchema,
1498
+ commits: z9.array(z9.string()),
1499
+ total: z9.number().int().positive()
1500
+ });
1501
+ var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
1502
+ var TurnOutcomeSchema = z9.object({
1432
1503
  exitReason: TurnExitReasonSchema,
1433
1504
  status: TurnStatusSchema.nullable(),
1434
- error: z8.string().nullable(),
1505
+ error: z9.string().nullable(),
1435
1506
  usage: UsageSchema,
1436
- costUsd: z8.number().nonnegative(),
1507
+ costUsd: z9.number().nonnegative(),
1437
1508
  /** The session's running total after this turn, for CLIs that report one. */
1438
- sessionCostUsd: z8.number().nonnegative().optional(),
1439
- /** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
1440
- session: z8.string().min(1),
1509
+ sessionCostUsd: z9.number().nonnegative().optional(),
1510
+ /**
1511
+ * The session the CLI used, which differs from the job's for CLIs that assign their own ids. A
1512
+ * turn whose CLI never started, because it failed or was stopped first, reports the job's own,
1513
+ * null in a conversation's first turn, so a session never started is not recorded.
1514
+ */
1515
+ session: z9.string().min(1).nullable(),
1516
+ /**
1517
+ * Set when the CLI could not resume the job's session, which failed the turn before any model
1518
+ * call; the server then drops a `lost` session, and one that failed `retry` too often in a row. A
1519
+ * runner from before never sends it.
1520
+ */
1521
+ resumeFailed: ResumeFailureSchema.optional(),
1441
1522
  /** The model the CLI reported, when it reported one. */
1442
- model: z8.string().nullable(),
1523
+ model: z9.string().nullable(),
1443
1524
  work: TurnWorkSchema.nullable(),
1444
1525
  /**
1445
1526
  * Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
1446
1527
  * branch after the hand-back: uncommitted changes, or commits no branch contains.
1447
1528
  */
1448
- leftovers: z8.boolean().default(false),
1529
+ leftovers: z9.boolean().default(false),
1530
+ /**
1531
+ * Commits the hand-back found its task's worktrees holding that no fast-forward of the task's
1532
+ * branch can take, each told once; a runner from before never sends it.
1533
+ */
1534
+ stuck: z9.array(StuckWorkSchema).optional(),
1449
1535
  /**
1450
1536
  * The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
1451
1537
  * before never sends it.
1452
1538
  */
1453
1539
  quota: CliQuotaSchema.optional()
1454
1540
  });
1455
- var TurnAckSchema = z8.object({ dropWorkspace: z8.boolean() });
1456
- var LandRequestSchema = z8.object({
1541
+ var TurnAckSchema = z9.object({ dropWorkspace: z9.boolean() });
1542
+ var LandRequestSchema = z9.object({
1457
1543
  repo: ProjectRepoSchema,
1458
- branch: z8.string().min(1),
1544
+ branch: z9.string().min(1),
1459
1545
  /**
1460
1546
  * For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
1461
1547
  * branch's files, and the merge commit's subject and body, the pull request's title and an
1462
1548
  * empty body when left out.
1463
1549
  */
1464
- pullRequest: z8.object({
1465
- url: z8.string().min(1),
1466
- subject: z8.string().min(1).optional(),
1467
- body: z8.string().optional()
1550
+ pullRequest: z9.object({
1551
+ url: z9.string().min(1),
1552
+ subject: z9.string().min(1).optional(),
1553
+ body: z9.string().optional()
1468
1554
  }).optional()
1469
1555
  });
1470
- var CloseRequestSchema = z8.object({
1556
+ var CloseRequestSchema = z9.object({
1471
1557
  repo: ProjectRepoSchema,
1472
- url: z8.string().min(1),
1473
- comment: z8.string().min(1)
1558
+ url: z9.string().min(1),
1559
+ comment: z9.string().min(1)
1474
1560
  });
1475
- var MergeOutcomeSchema = z8.object({ ok: z8.boolean(), detail: z8.string() });
1476
- var ModelListSchema = z8.array(ModelOptionSchema);
1561
+ var MergeOutcomeSchema = z9.object({ ok: z9.boolean(), detail: z9.string() });
1562
+ var ModelListSchema = z9.array(ModelOptionSchema);
1477
1563
  var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
1478
- var BranchPathSchema = z8.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
1479
- var BranchNameSchema = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1480
- var BranchReadSchema = z8.object({
1564
+ 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" });
1565
+ var BranchNameSchema = z9.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
1566
+ var BranchReadSchema = z9.object({
1481
1567
  repo: ProjectRepoSchema,
1482
1568
  branch: BranchNameSchema,
1483
1569
  path: BranchPathSchema
1484
1570
  });
1485
- var BranchCommitSchema = z8.object({
1486
- id: z8.string().min(1),
1571
+ var BranchCommitSchema = z9.object({
1572
+ id: z9.string().min(1),
1487
1573
  at: IsoDateTimeSchema,
1488
- author: z8.string()
1574
+ author: z9.string()
1489
1575
  });
1490
- var BranchFileSchema = z8.discriminatedUnion("kind", [
1491
- z8.object({
1492
- kind: z8.literal("file"),
1576
+ var BranchFileSchema = z9.discriminatedUnion("kind", [
1577
+ z9.object({
1578
+ kind: z9.literal("file"),
1493
1579
  path: BranchPathSchema,
1494
1580
  commit: BranchCommitSchema,
1495
- size: z8.number().int().nonnegative(),
1496
- content: z8.string().nullable()
1581
+ size: z9.number().int().nonnegative(),
1582
+ content: z9.string().nullable()
1497
1583
  }),
1498
- z8.object({
1499
- kind: z8.literal("dir"),
1584
+ z9.object({
1585
+ kind: z9.literal("dir"),
1500
1586
  path: BranchPathSchema,
1501
1587
  commit: BranchCommitSchema,
1502
- entries: z8.array(z8.object({
1503
- name: z8.string().min(1),
1504
- kind: z8.enum(["file", "dir"]),
1505
- size: z8.number().int().nonnegative().nullable()
1588
+ entries: z9.array(z9.object({
1589
+ name: z9.string().min(1),
1590
+ kind: z9.enum(["file", "dir"]),
1591
+ size: z9.number().int().nonnegative().nullable()
1506
1592
  }))
1507
1593
  })
1508
1594
  ]);
1509
- var BranchChangesReadSchema = z8.object({
1595
+ var BranchChangesReadSchema = z9.object({
1510
1596
  repo: ProjectRepoSchema,
1511
1597
  branch: BranchNameSchema
1512
1598
  });
1513
- var BranchFileChangeSchema = z8.object({
1514
- path: z8.string().min(1),
1515
- status: z8.enum(["added", "modified", "deleted"]),
1599
+ var BranchFileChangeSchema = z9.object({
1600
+ path: z9.string().min(1),
1601
+ status: z9.enum(["added", "modified", "deleted"]),
1516
1602
  /** Lines added and removed, or null for a binary file. */
1517
- added: z8.number().int().nonnegative().nullable(),
1518
- removed: z8.number().int().nonnegative().nullable(),
1603
+ added: z9.number().int().nonnegative().nullable(),
1604
+ removed: z9.number().int().nonnegative().nullable(),
1519
1605
  /** Absent when the file changed only through a merge of another branch. */
1520
- lastChange: z8.object({ author: z8.string(), at: IsoDateTimeSchema }).optional()
1606
+ lastChange: z9.object({ author: z9.string(), at: IsoDateTimeSchema }).optional()
1521
1607
  });
1522
- var BranchChangesSchema = z8.object({
1608
+ var BranchChangesSchema = z9.object({
1523
1609
  head: BranchCommitSchema,
1524
- files: z8.array(BranchFileChangeSchema),
1525
- total: z8.number().int().nonnegative()
1610
+ files: z9.array(BranchFileChangeSchema),
1611
+ total: z9.number().int().nonnegative()
1526
1612
  });
1527
- var BranchDiffReadSchema = z8.object({
1613
+ var BranchDiffReadSchema = z9.object({
1528
1614
  repo: ProjectRepoSchema,
1529
1615
  branch: BranchNameSchema,
1530
1616
  path: BranchPathSchema.min(1)
1531
1617
  });
1532
- var BranchFileDiffSchema = z8.object({
1618
+ var BranchFileDiffSchema = z9.object({
1533
1619
  head: BranchCommitSchema,
1534
1620
  path: BranchPathSchema,
1535
1621
  status: BranchFileChangeSchema.shape.status,
1536
- patch: z8.string().nullable(),
1537
- truncated: z8.boolean()
1622
+ patch: z9.string().nullable(),
1623
+ truncated: z9.boolean()
1538
1624
  });
1539
1625
  function patchHunks(patch, limit) {
1540
1626
  if (/^Binary files .* differ$/m.test(patch)) {
@@ -1548,69 +1634,89 @@ function patchHunks(patch, limit) {
1548
1634
  const cut = hunks.lastIndexOf("\n", limit);
1549
1635
  return { patch: hunks.slice(0, cut === -1 ? limit : cut), truncated: true };
1550
1636
  }
1551
- var WorkspaceConversationSchema = z8.discriminatedUnion("kind", [
1552
- z8.object({ kind: z8.literal("thread"), id: UlidSchema }),
1553
- z8.object({ kind: z8.literal("channel"), scope: NameSchema, name: NameSchema })
1637
+ var TurnImageReadSchema = z9.object({
1638
+ turnId: UlidSchema,
1639
+ path: ImagePathSchema
1640
+ });
1641
+ var ImageRefusalSchema = z9.enum(["missing", "outside", "not_image", "too_big"]);
1642
+ var TurnImageSchema = z9.discriminatedUnion("kind", [
1643
+ z9.object({
1644
+ kind: z9.literal("image"),
1645
+ type: ImageTypeSchema,
1646
+ size: z9.number().int().positive(),
1647
+ content: z9.string()
1648
+ }),
1649
+ z9.object({ kind: z9.literal("refused"), why: ImageRefusalSchema, detail: z9.string() })
1554
1650
  ]);
1555
- var HeldWorkspaceSchema = z8.object({
1651
+ var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
1652
+ z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
1653
+ z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
1654
+ z9.object({ kind: z9.literal("place"), project: NameSchema })
1655
+ ]);
1656
+ var HeldWorkspaceSchema = z9.object({
1556
1657
  agent: NameSchema,
1557
1658
  scope: NameSchema,
1558
1659
  conversation: WorkspaceConversationSchema
1559
1660
  });
1560
- var HeldWorkspacesSchema = z8.object({ workspaces: z8.array(HeldWorkspaceSchema) });
1561
- var SweepResultSchema = z8.object({ kept: z8.array(HeldWorkspaceSchema) });
1562
- var TurnSteerSchema = z8.object({ id: z8.uuid(), text: z8.string().min(1) });
1563
- var RunnerMessageSchema = z8.discriminatedUnion("type", [
1564
- z8.object({ type: z8.literal("turn"), job: TurnJobSchema }),
1565
- z8.object({ type: z8.literal("land"), request: z8.string().min(1), land: LandRequestSchema }),
1661
+ var HeldWorkspacesSchema = z9.object({ workspaces: z9.array(HeldWorkspaceSchema) });
1662
+ var SweepResultSchema = z9.object({ kept: z9.array(HeldWorkspaceSchema) });
1663
+ var TurnSteerSchema = z9.object({ id: z9.uuid(), text: z9.string().min(1) });
1664
+ var RunnerMessageSchema = z9.discriminatedUnion("type", [
1665
+ z9.object({ type: z9.literal("turn"), job: TurnJobSchema }),
1666
+ z9.object({ type: z9.literal("land"), request: z9.string().min(1), land: LandRequestSchema }),
1566
1667
  /** Answered with a `MergeOutcome`. */
1567
- z8.object({ type: z8.literal("close"), request: z8.string().min(1), close: CloseRequestSchema }),
1568
- z8.object({ type: z8.literal("models"), request: z8.string().min(1), cli: CliKindSchema }),
1668
+ z9.object({ type: z9.literal("close"), request: z9.string().min(1), close: CloseRequestSchema }),
1669
+ z9.object({ type: z9.literal("models"), request: z9.string().min(1), cli: CliKindSchema }),
1569
1670
  /**
1570
1671
  * Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
1571
1672
  * read one. Sent only to runners whose hello says `quotaReads`.
1572
1673
  */
1573
- z8.object({ type: z8.literal("quota"), request: z8.string().min(1), cli: CliKindSchema }),
1674
+ z9.object({ type: z9.literal("quota"), request: z9.string().min(1), cli: CliKindSchema }),
1574
1675
  /** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
1575
- z8.object({ type: z8.literal("file"), request: z8.string().min(1), read: BranchReadSchema }),
1676
+ z9.object({ type: z9.literal("file"), request: z9.string().min(1), read: BranchReadSchema }),
1576
1677
  /** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
1577
- z8.object({
1578
- type: z8.literal("changes"),
1579
- request: z8.string().min(1),
1678
+ z9.object({
1679
+ type: z9.literal("changes"),
1680
+ request: z9.string().min(1),
1580
1681
  read: BranchChangesReadSchema
1581
1682
  }),
1582
1683
  /** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
1583
- z8.object({ type: z8.literal("diff"), request: z8.string().min(1), read: BranchDiffReadSchema }),
1684
+ z9.object({ type: z9.literal("diff"), request: z9.string().min(1), read: BranchDiffReadSchema }),
1685
+ /**
1686
+ * Answered with a `TurnImage`, or null when the turn is not running there. Sent only to runners
1687
+ * whose hello says `imageReads`.
1688
+ */
1689
+ z9.object({ type: z9.literal("image"), request: z9.string().min(1), read: TurnImageReadSchema }),
1584
1690
  /** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
1585
- z8.object({
1586
- type: z8.literal("steer"),
1587
- request: z8.string().min(1),
1691
+ z9.object({
1692
+ type: z9.literal("steer"),
1693
+ request: z9.string().min(1),
1588
1694
  turnId: UlidSchema,
1589
1695
  steer: TurnSteerSchema
1590
1696
  }),
1591
1697
  /** Answered with whether the turn was there to stop; it ends `stopped`. */
1592
- z8.object({ type: z8.literal("stop"), request: z8.string().min(1), turnId: UlidSchema }),
1698
+ z9.object({ type: z9.literal("stop"), request: z9.string().min(1), turnId: UlidSchema }),
1593
1699
  /**
1594
1700
  * A conversation ended: the runner removes every citizen's workspace for it but those a turn
1595
1701
  * still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
1596
1702
  */
1597
- z8.object({
1598
- type: z8.literal("sweep"),
1599
- request: z8.string().min(1),
1703
+ z9.object({
1704
+ type: z9.literal("sweep"),
1705
+ request: z9.string().min(1),
1600
1706
  conversation: WorkspaceConversationSchema
1601
1707
  })
1602
1708
  ]);
1603
- var RunnerAnswerSchema = z8.discriminatedUnion("ok", [
1604
- z8.object({ ok: z8.literal(true), value: z8.unknown() }),
1605
- z8.object({ ok: z8.literal(false), error: z8.string() })
1709
+ var RunnerAnswerSchema = z9.discriminatedUnion("ok", [
1710
+ z9.object({ ok: z9.literal(true), value: z9.unknown() }),
1711
+ z9.object({ ok: z9.literal(false), error: z9.string() })
1606
1712
  ]);
1607
- var WarmSessionsSchema = z8.object({ warm: z8.array(z8.string()) });
1608
- var FileManifestSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1713
+ var WarmSessionsSchema = z9.object({ warm: z9.array(z9.string()) });
1714
+ var FileManifestSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1609
1715
  function fileVersion(sha, executable) {
1610
1716
  return executable ? `${sha}+x` : sha;
1611
1717
  }
1612
- var FileReadSchema = z8.object({ paths: z8.array(z8.string()) });
1613
- var FileContentsSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
1718
+ var FileReadSchema = z9.object({ paths: z9.array(z9.string()) });
1719
+ var FileContentsSchema = z9.object({ files: z9.record(z9.string(), z9.string()) });
1614
1720
  function isSafeRelativePath(file) {
1615
1721
  if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
1616
1722
  return false;
@@ -1619,7 +1725,7 @@ function isSafeRelativePath(file) {
1619
1725
  }
1620
1726
 
1621
1727
  // packages/shared/dist/turns.js
1622
- var TriggerKindSchema = z9.enum([
1728
+ var TriggerKindSchema = z10.enum([
1623
1729
  "mention",
1624
1730
  "stage",
1625
1731
  "task_done",
@@ -1633,11 +1739,11 @@ var TriggerKindSchema = z9.enum([
1633
1739
  "closing",
1634
1740
  "cron"
1635
1741
  ]);
1636
- var TriggerSchema = z9.object({
1742
+ var TriggerSchema = z10.object({
1637
1743
  kind: TriggerKindSchema,
1638
1744
  from: NameSchema.optional(),
1639
- fromUser: z9.boolean().default(false),
1640
- reason: z9.string().default(""),
1745
+ fromUser: z10.boolean().default(false),
1746
+ reason: z10.string().default(""),
1641
1747
  taskId: UlidSchema.optional(),
1642
1748
  messageId: UlidSchema.optional(),
1643
1749
  /** The channel the waking message was posted in. */
@@ -1645,20 +1751,27 @@ var TriggerSchema = z9.object({
1645
1751
  /** The cron that came due, for a `cron` wake. */
1646
1752
  cronId: UlidSchema.optional()
1647
1753
  });
1648
- var TurnThreadSchema = z9.object({ id: UlidSchema, task: z9.boolean() });
1649
- var TurnDispatchSchema = z9.object({
1754
+ var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
1755
+ var TurnDispatchSchema = z10.object({
1650
1756
  agent: NameSchema,
1651
1757
  project: NameSchema,
1652
1758
  thread: TurnThreadSchema.optional(),
1653
1759
  channel: NameSchema.optional(),
1760
+ place: NameSchema.optional(),
1654
1761
  trigger: TriggerSchema,
1655
- priority: z9.number().int().min(0).max(2),
1656
- onboarding: z9.boolean().default(false),
1762
+ priority: z10.number().int().min(0).max(2),
1763
+ onboarding: z10.boolean().default(false),
1657
1764
  /** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
1658
- crons: z9.array(UlidSchema).optional()
1765
+ crons: z10.array(UlidSchema).optional()
1659
1766
  });
1660
1767
  function conversationPart(conversation) {
1661
- return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
1768
+ if (conversation.thread !== void 0) {
1769
+ return conversation.thread;
1770
+ }
1771
+ if (conversation.channel !== void 0) {
1772
+ return `#${conversation.channel}`;
1773
+ }
1774
+ return conversation.place === void 0 ? void 0 : `~${conversation.place}`;
1662
1775
  }
1663
1776
  function channelConversation(scope, channel) {
1664
1777
  const at = channel.indexOf("/");
@@ -1666,7 +1779,11 @@ function channelConversation(scope, channel) {
1666
1779
  const name = at < 0 ? channel : channel.slice(at + 1);
1667
1780
  return place === scope && name !== "general" ? name : void 0;
1668
1781
  }
1669
- var TurnRecordSchema = z9.object({
1782
+ function placeConversation(scope, channel) {
1783
+ const at = channel.indexOf("/");
1784
+ return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
1785
+ }
1786
+ var TurnRecordSchema = z10.object({
1670
1787
  /** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
1671
1788
  id: UlidSchema.optional(),
1672
1789
  agent: NameSchema,
@@ -1675,83 +1792,94 @@ var TurnRecordSchema = z9.object({
1675
1792
  thread: UlidSchema.optional(),
1676
1793
  /** The channel, never general, whose conversation the turn was in. */
1677
1794
  channel: NameSchema.optional(),
1795
+ /** The project whose channels' conversation the turn was in, for a turn in the society scope. */
1796
+ place: NameSchema.optional(),
1678
1797
  runner: NameSchema,
1679
1798
  cli: CliKindSchema.nullable(),
1680
- session: z9.string().nullable(),
1799
+ session: z10.string().nullable(),
1681
1800
  trigger: TriggerSchema,
1682
1801
  startedAt: IsoDateTimeSchema,
1683
1802
  endedAt: IsoDateTimeSchema.nullable(),
1684
1803
  exitReason: TurnExitReasonSchema.nullable(),
1685
1804
  status: TurnStatusSchema.nullable(),
1686
- error: z9.string().nullable(),
1805
+ error: z10.string().nullable(),
1687
1806
  usage: UsageSchema.nullable(),
1688
- costUsd: z9.number().nonnegative().default(0),
1807
+ costUsd: z10.number().nonnegative().default(0),
1689
1808
  /**
1690
1809
  * The running total the CLI reported for the session after this turn, when it reports one. A
1691
1810
  * resumed session starts from it, so the next turn's own cost is measured from here.
1692
1811
  */
1693
- sessionCostUsd: z9.number().nonnegative().optional(),
1694
- toolCalls: z9.number().int().nonnegative().default(0),
1812
+ sessionCostUsd: z10.number().nonnegative().optional(),
1813
+ toolCalls: z10.number().int().nonnegative().default(0),
1695
1814
  /** The model the CLI reported for this turn, else the model configured for the agent, else null. */
1696
- model: z9.string().nullable().default(null),
1815
+ model: z10.string().nullable().default(null),
1697
1816
  /** The task branch the turn left work on, with its commit, as the runner reported it. */
1698
1817
  work: TurnWorkSchema.optional(),
1699
1818
  /** Who stopped the turn, for one that ended `stopped`. */
1700
1819
  stoppedBy: NameSchema.optional(),
1820
+ /** Why the CLI could not resume the turn's session, for a turn that failed so. */
1821
+ resumeFailed: ResumeFailureSchema.optional(),
1822
+ /** How many turns in a row, this one included, could not resume the session for a `retry`. */
1823
+ resumeRetries: z10.number().int().positive().optional(),
1824
+ /** Whether the server dropped the conversation's session after this turn, which the next starts anew. */
1825
+ sessionDropped: z10.boolean().optional(),
1701
1826
  /** The crons whose fire started the turn, which record how it ended. */
1702
- crons: z9.array(UlidSchema).optional()
1827
+ crons: z10.array(UlidSchema).optional()
1703
1828
  });
1704
- var RunningTurnSchema = z9.object({
1829
+ var RunningTurnSchema = z10.object({
1705
1830
  turnId: UlidSchema,
1706
1831
  agent: NameSchema,
1707
1832
  scope: NameSchema,
1708
1833
  thread: UlidSchema.optional(),
1709
1834
  channel: NameSchema.optional(),
1835
+ place: NameSchema.optional(),
1710
1836
  cli: CliKindSchema,
1711
1837
  askedIn: ChannelRefSchema.optional(),
1712
- steerable: z9.boolean(),
1713
- stoppable: z9.boolean()
1838
+ steerable: z10.boolean(),
1839
+ stoppable: z10.boolean()
1714
1840
  });
1715
- var SessionsFileSchema = z9.object({
1716
- claude: z9.string().optional(),
1717
- codex: z9.string().optional(),
1841
+ var SessionsFileSchema = z10.object({
1842
+ claude: z10.string().optional(),
1843
+ codex: z10.string().optional(),
1718
1844
  runner: NameSchema.optional()
1719
1845
  });
1720
- var WakeRequestSchema = z9.object({
1846
+ var WakeRequestSchema = z10.object({
1721
1847
  agent: NameSchema,
1722
1848
  project: NameSchema,
1723
- reason: z9.string().default("manual wake"),
1849
+ reason: z10.string().default("manual wake"),
1724
1850
  /** A manual wake, or a reflection turn requested ahead of the cadence. */
1725
- kind: z9.enum(["manual", "reflection"]).default("manual")
1851
+ kind: z10.enum(["manual", "reflection"]).default("manual")
1726
1852
  });
1727
- var TurnHistoryEntrySchema = z9.object({
1853
+ var TurnHistoryEntrySchema = z10.object({
1728
1854
  /** The id of the event that ended the turn. */
1729
1855
  id: UlidSchema,
1730
1856
  /** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
1731
1857
  turnId: UlidSchema.optional(),
1732
1858
  ts: IsoDateTimeSchema,
1733
- outcome: z9.enum(["completed", "failed"]),
1859
+ outcome: z10.enum(["completed", "failed"]),
1734
1860
  /** A project slug, or the society scope. */
1735
1861
  project: NameSchema,
1736
1862
  /** The thread whose conversation the turn was in; absent for the home conversation. */
1737
1863
  thread: UlidSchema.optional(),
1738
1864
  /** The channel, never general, whose conversation the turn was in. */
1739
1865
  channel: NameSchema.optional(),
1740
- trigger: z9.string(),
1741
- exitReason: z9.string().nullable(),
1742
- costUsd: z9.number(),
1866
+ /** The project whose channels' conversation the turn was in, for a turn in the society scope. */
1867
+ place: NameSchema.optional(),
1868
+ trigger: z10.string(),
1869
+ exitReason: z10.string().nullable(),
1870
+ costUsd: z10.number(),
1743
1871
  /** The tokens the turn used; absent for turns logged before turns recorded them. */
1744
1872
  usage: UsageSchema.optional(),
1745
- model: z9.string().nullable(),
1746
- summary: z9.string().nullable(),
1747
- error: z9.string().nullable(),
1873
+ model: z10.string().nullable(),
1874
+ summary: z10.string().nullable(),
1875
+ error: z10.string().nullable(),
1748
1876
  /** When the log recorded the turn's start; absent when the start is missing from the log. */
1749
1877
  startedAt: IsoDateTimeSchema.optional(),
1750
- toolCalls: z9.number().int().nonnegative().optional()
1878
+ toolCalls: z10.number().int().nonnegative().optional()
1751
1879
  });
1752
1880
 
1753
1881
  // packages/shared/dist/ops.js
1754
- import { z as z10 } from "zod";
1882
+ import { z as z11 } from "zod";
1755
1883
  var OPS_SIGNAL_KINDS = [
1756
1884
  "waiting_stage",
1757
1885
  "backlog",
@@ -1767,16 +1895,16 @@ var OPS_SIGNAL_KINDS = [
1767
1895
  "stuck_workspace",
1768
1896
  "cron_failing"
1769
1897
  ];
1770
- var OpsSignalKindSchema = z10.enum(OPS_SIGNAL_KINDS);
1771
- var OpsSignalSchema = z10.object({
1898
+ var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
1899
+ var OpsSignalSchema = z11.object({
1772
1900
  kind: OpsSignalKindSchema,
1773
1901
  /** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
1774
- key: z10.string().min(1),
1902
+ key: z11.string().min(1),
1775
1903
  /** One line a reader can act on. */
1776
- summary: z10.string().min(1),
1904
+ summary: z11.string().min(1),
1777
1905
  /** The counter or timer value behind the signal. */
1778
- value: z10.number(),
1779
- threshold: z10.number().optional(),
1906
+ value: z11.number(),
1907
+ threshold: z11.number().optional(),
1780
1908
  project: NameSchema.optional(),
1781
1909
  agent: NameSchema.optional(),
1782
1910
  role: NameSchema.optional(),
@@ -1784,8 +1912,8 @@ var OpsSignalSchema = z10.object({
1784
1912
  });
1785
1913
 
1786
1914
  // packages/shared/dist/metrics.js
1787
- import { z as z11 } from "zod";
1788
- var MetricsWindowSchema = z11.enum(["24h", "7d", "all"]);
1915
+ import { z as z12 } from "zod";
1916
+ var MetricsWindowSchema = z12.enum(["24h", "7d", "all"]);
1789
1917
  var METRICS_WINDOW_MS = {
1790
1918
  "24h": 24 * 36e5,
1791
1919
  "7d": 7 * 24 * 36e5,
@@ -1796,9 +1924,9 @@ function isBoardAction(tool) {
1796
1924
  const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
1797
1925
  return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
1798
1926
  }
1799
- var CountSchema = z11.number().int().nonnegative();
1800
- var MsSchema = z11.number().nonnegative();
1801
- var MetricsSchema = z11.object({
1927
+ var CountSchema = z12.number().int().nonnegative();
1928
+ var MsSchema = z12.number().nonnegative();
1929
+ var MetricsSchema = z12.object({
1802
1930
  window: MetricsWindowSchema,
1803
1931
  since: IsoDateTimeSchema.nullable(),
1804
1932
  until: IsoDateTimeSchema,
@@ -1806,12 +1934,12 @@ var MetricsSchema = z11.object({
1806
1934
  * Finished turns that changed nothing on the board. Reflections are left out, since they write
1807
1935
  * memory, not the board; turns without a transcript are counted apart, their actions unknown.
1808
1936
  */
1809
- idle: z11.object({
1937
+ idle: z12.object({
1810
1938
  turns: CountSchema,
1811
1939
  idle: CountSchema,
1812
1940
  unknown: CountSchema,
1813
- byTrigger: z11.array(z11.object({ trigger: z11.string(), turns: CountSchema, idle: CountSchema })),
1814
- byAgent: z11.array(z11.object({
1941
+ byTrigger: z12.array(z12.object({ trigger: z12.string(), turns: CountSchema, idle: CountSchema })),
1942
+ byAgent: z12.array(z12.object({
1815
1943
  agent: NameSchema,
1816
1944
  role: NameSchema.nullable(),
1817
1945
  turns: CountSchema,
@@ -1819,33 +1947,33 @@ var MetricsSchema = z11.object({
1819
1947
  }))
1820
1948
  }),
1821
1949
  /** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
1822
- sentBack: z11.object({
1950
+ sentBack: z12.object({
1823
1951
  finished: CountSchema,
1824
1952
  sendBacks: CountSchema,
1825
1953
  tasksSentBack: CountSchema,
1826
- byStage: z11.array(z11.object({ project: NameSchema, stage: z11.string(), count: CountSchema })),
1827
- bySender: z11.array(z11.object({ agent: NameSchema, count: CountSchema }))
1954
+ byStage: z12.array(z12.object({ project: NameSchema, stage: z12.string(), count: CountSchema })),
1955
+ bySender: z12.array(z12.object({ agent: NameSchema, count: CountSchema }))
1828
1956
  }),
1829
1957
  /** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
1830
- messages: z11.object({
1958
+ messages: z12.object({
1831
1959
  finished: CountSchema,
1832
1960
  messages: CountSchema,
1833
- byProject: z11.array(z11.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1834
- busiest: z11.array(z11.object({
1961
+ byProject: z12.array(z12.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
1962
+ busiest: z12.array(z12.object({
1835
1963
  taskId: UlidSchema,
1836
- title: z11.string(),
1964
+ title: z12.string(),
1837
1965
  project: NameSchema,
1838
1966
  messages: CountSchema
1839
1967
  }))
1840
1968
  }),
1841
1969
  /** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
1842
- latency: z11.object({
1970
+ latency: z12.object({
1843
1971
  mentions: CountSchema,
1844
1972
  /** Mentions no turn has followed yet. */
1845
1973
  unanswered: CountSchema,
1846
1974
  medianMs: MsSchema.nullable(),
1847
1975
  slowestMs: MsSchema.nullable(),
1848
- byAgent: z11.array(z11.object({
1976
+ byAgent: z12.array(z12.object({
1849
1977
  agent: NameSchema,
1850
1978
  mentions: CountSchema,
1851
1979
  medianMs: MsSchema,
@@ -1853,93 +1981,93 @@ var MetricsSchema = z11.object({
1853
1981
  }))
1854
1982
  }),
1855
1983
  /** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
1856
- decisions: z11.object({
1984
+ decisions: z12.object({
1857
1985
  total: CountSchema,
1858
- byDay: z11.array(z11.object({
1859
- day: z11.string(),
1986
+ byDay: z12.array(z12.object({
1987
+ day: z12.string(),
1860
1988
  proposals: CountSchema,
1861
1989
  answers: CountSchema,
1862
1990
  stages: CountSchema
1863
1991
  }))
1864
1992
  }),
1865
1993
  /** Tasks that needed a capability no connected runner offered, and whether they still do. */
1866
- blocked: z11.array(z11.object({
1994
+ blocked: z12.array(z12.object({
1867
1995
  taskId: UlidSchema,
1868
- title: z11.string().nullable(),
1996
+ title: z12.string().nullable(),
1869
1997
  project: NameSchema.nullable(),
1870
- summary: z11.string(),
1998
+ summary: z12.string(),
1871
1999
  firstAt: IsoDateTimeSchema,
1872
2000
  lastAt: IsoDateTimeSchema,
1873
- holds: z11.boolean()
2001
+ holds: z12.boolean()
1874
2002
  }))
1875
2003
  });
1876
- var CitizenTokensSchema = z11.object({
2004
+ var CitizenTokensSchema = z12.object({
1877
2005
  agent: NameSchema,
1878
2006
  turns: CountSchema,
1879
2007
  withUsage: CountSchema,
1880
2008
  usage: UsageSchema.nullable()
1881
2009
  });
1882
- var TokenUseSchema = z11.object({
2010
+ var TokenUseSchema = z12.object({
1883
2011
  window: MetricsWindowSchema,
1884
2012
  since: IsoDateTimeSchema.nullable(),
1885
2013
  until: IsoDateTimeSchema,
1886
- citizens: z11.array(CitizenTokensSchema)
2014
+ citizens: z12.array(CitizenTokensSchema)
1887
2015
  });
1888
2016
 
1889
2017
  // packages/shared/dist/config.js
1890
- import { z as z12 } from "zod";
1891
- var LogLevelSchema = z12.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
1892
- var GithubSignInSchema = z12.object({
1893
- clientId: z12.string().min(1),
1894
- clientSecret: z12.string().min(1),
2018
+ import { z as z13 } from "zod";
2019
+ var LogLevelSchema = z13.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
2020
+ var GithubSignInSchema = z13.object({
2021
+ clientId: z13.string().min(1),
2022
+ clientSecret: z13.string().min(1),
1895
2023
  /** Logins, lowercased; GitHub logins are case-insensitive. */
1896
- users: z12.array(z12.string().min(1)).min(1)
2024
+ users: z13.array(z13.string().min(1)).min(1)
1897
2025
  });
1898
- var ServerConfigSchema = z12.object({
1899
- dataDir: z12.string().min(1),
1900
- host: z12.string().min(1),
1901
- port: z12.number().int().positive().max(65535),
2026
+ var ServerConfigSchema = z13.object({
2027
+ dataDir: z13.string().min(1),
2028
+ host: z13.string().min(1),
2029
+ port: z13.number().int().positive().max(65535),
1902
2030
  /**
1903
2031
  * An address every runner's agents reach this server at, for example https://board.example, or
1904
2032
  * null for each runner to use the address it reaches the server at itself.
1905
2033
  */
1906
- publicUrl: z12.string().min(1).nullable(),
2034
+ publicUrl: z13.string().min(1).nullable(),
1907
2035
  logLevel: LogLevelSchema,
1908
2036
  /** Turns running at once across every runner, or null for no limit beyond each runner's own. */
1909
- concurrency: z12.number().int().positive().nullable(),
2037
+ concurrency: z13.number().int().positive().nullable(),
1910
2038
  /** How long one turn may run before it is stopped, or null for no limit. */
1911
- turnTimeoutMs: z12.number().int().positive().nullable(),
2039
+ turnTimeoutMs: z13.number().int().positive().nullable(),
1912
2040
  /** Rounds of tool calls one Claude turn may take, or null for no limit. */
1913
- toolRounds: z12.number().int().positive().nullable(),
2041
+ toolRounds: z13.number().int().positive().nullable(),
1914
2042
  /** How long a resident session stays warm after its last turn. */
1915
- residentIdleMs: z12.number().int().positive(),
2043
+ residentIdleMs: z13.number().int().positive(),
1916
2044
  /** Signing in to the board with GitHub, or null when only a token lets anyone in. */
1917
2045
  github: GithubSignInSchema.nullable(),
1918
2046
  /**
1919
2047
  * Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
1920
2048
  * rate limits on what anyone may ask without a token: 0 when clients connect directly.
1921
2049
  */
1922
- trustedProxies: z12.number().int().min(0).max(16)
2050
+ trustedProxies: z13.number().int().min(0).max(16)
1923
2051
  });
1924
- var RunnerConfigSchema = z12.object({
2052
+ var RunnerConfigSchema = z13.object({
1925
2053
  /** The board server, for example http://127.0.0.1:4700. */
1926
- serverUrl: z12.string().url(),
2054
+ serverUrl: z13.string().url(),
1927
2055
  /**
1928
2056
  * The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
1929
2057
  * to enroll it.
1930
2058
  */
1931
- token: z12.string().min(1).nullable(),
2059
+ token: z13.string().min(1).nullable(),
1932
2060
  /** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
1933
- dataDir: z12.string().min(1),
2061
+ dataDir: z13.string().min(1),
1934
2062
  logLevel: LogLevelSchema,
1935
2063
  /** Turns this machine runs at once, or null for no limit. */
1936
- slots: z12.number().int().positive().nullable(),
1937
- clis: z12.array(CliKindSchema).min(1),
1938
- capabilities: z12.array(z12.string())
2064
+ slots: z13.number().int().positive().nullable(),
2065
+ clis: z13.array(CliKindSchema).min(1),
2066
+ capabilities: z13.array(z13.string())
1939
2067
  });
1940
2068
 
1941
2069
  // packages/shared/dist/version.js
1942
- var STELLARIS_VERSION = "0.3.22";
2070
+ var STELLARIS_VERSION = "0.3.24";
1943
2071
 
1944
2072
  // packages/shared/dist/mentions.js
1945
2073
  import { parse, postprocess, preprocess } from "micromark";
@@ -2320,6 +2448,7 @@ var HOME_GITIGNORE = [
2320
2448
  "/projects/*/last-turn.json",
2321
2449
  "/projects/*/threads/",
2322
2450
  "/projects/*/channels/",
2451
+ "/projects/*/places/",
2323
2452
  `/${HOME_SCRATCH}/`,
2324
2453
  "*.tmp",
2325
2454
  "node_modules/",
@@ -2337,7 +2466,7 @@ var HOME_GITIGNORE = [
2337
2466
  var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
2338
2467
  `;
2339
2468
  var PRE_RECEIVE = `#!/bin/sh
2340
- forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/))'
2469
+ forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/|places/))'
2341
2470
  while read old new ref; do
2342
2471
  if [ "$ref" != "refs/heads/main" ]; then
2343
2472
  echo "a home takes pushes to main only" >&2
@@ -2657,32 +2786,33 @@ function postMentions(body) {
2657
2786
  }
2658
2787
 
2659
2788
  // packages/board-core/dist/metrics.js
2660
- import { z as z13 } from "zod";
2661
- var TurnPayload = z13.object({
2662
- turnId: z13.string().optional(),
2663
- project: z13.string(),
2664
- thread: z13.string().optional(),
2665
- channel: z13.string().optional(),
2666
- trigger: z13.string()
2667
- });
2668
- var TaskPayload = z13.object({
2669
- taskId: z13.string(),
2670
- project: z13.string(),
2671
- from: z13.string().optional()
2672
- });
2673
- var MessagePayload = z13.object({
2674
- id: z13.string().optional(),
2675
- channel: z13.string(),
2676
- thread: z13.string().nullable().optional(),
2677
- mentions: z13.array(z13.string()).default([])
2678
- });
2679
- var SteeredPayload = z13.object({ messages: z13.array(z13.string()) });
2680
- var SignalPayload = z13.object({
2681
- kind: z13.string(),
2682
- key: z13.string(),
2683
- summary: z13.string(),
2684
- taskId: z13.string().optional(),
2685
- project: z13.string().optional()
2789
+ import { z as z14 } from "zod";
2790
+ var TurnPayload = z14.object({
2791
+ turnId: z14.string().optional(),
2792
+ project: z14.string(),
2793
+ thread: z14.string().optional(),
2794
+ channel: z14.string().optional(),
2795
+ place: z14.string().optional(),
2796
+ trigger: z14.string()
2797
+ });
2798
+ var TaskPayload = z14.object({
2799
+ taskId: z14.string(),
2800
+ project: z14.string(),
2801
+ from: z14.string().optional()
2802
+ });
2803
+ var MessagePayload = z14.object({
2804
+ id: z14.string().optional(),
2805
+ channel: z14.string(),
2806
+ thread: z14.string().nullable().optional(),
2807
+ mentions: z14.array(z14.string()).default([])
2808
+ });
2809
+ var SteeredPayload = z14.object({ messages: z14.array(z14.string()) });
2810
+ var SignalPayload = z14.object({
2811
+ kind: z14.string(),
2812
+ key: z14.string(),
2813
+ summary: z14.string(),
2814
+ taskId: z14.string().optional(),
2815
+ project: z14.string().optional()
2686
2816
  });
2687
2817
  function median(values) {
2688
2818
  const sorted = values.toSorted((a, b) => a - b);
@@ -2708,11 +2838,16 @@ function answers(mention, turn2) {
2708
2838
  if (!mention.scopes.includes(turn2.project)) {
2709
2839
  return false;
2710
2840
  }
2711
- const own = mention.thread === null ? channelConversation(turn2.project, mention.channel) : void 0;
2841
+ const topLevel = mention.thread === null;
2842
+ const own = topLevel ? channelConversation(turn2.project, mention.channel) : void 0;
2843
+ const place = topLevel ? placeConversation(turn2.project, mention.channel) : void 0;
2712
2844
  if (turn2.channel !== null) {
2713
- return mention.thread === null && own === turn2.channel;
2845
+ return topLevel && own === turn2.channel;
2714
2846
  }
2715
- return mention.thread === null && own === void 0 || turn2.trigger === "mention";
2847
+ if (turn2.place !== null) {
2848
+ return topLevel && place === turn2.place;
2849
+ }
2850
+ return topLevel && own === void 0 && place === void 0 || turn2.trigger === "mention";
2716
2851
  }
2717
2852
  function computeMetrics(input) {
2718
2853
  const { events, now, window } = input;
@@ -2778,6 +2913,7 @@ function computeMetrics(input) {
2778
2913
  at,
2779
2914
  thread: turn2.data.thread ?? null,
2780
2915
  channel: turn2.data.channel ?? null,
2916
+ place: turn2.data.place ?? null,
2781
2917
  project: turn2.data.project,
2782
2918
  trigger: turn2.data.trigger
2783
2919
  };
@@ -2972,19 +3108,20 @@ function computeMetrics(input) {
2972
3108
  }
2973
3109
 
2974
3110
  // packages/board-core/dist/turn-history.js
2975
- import { z as z14 } from "zod";
2976
- var TurnHistoryPayloadSchema = z14.object({
3111
+ import { z as z15 } from "zod";
3112
+ var TurnHistoryPayloadSchema = z15.object({
2977
3113
  project: NameSchema,
2978
3114
  thread: UlidSchema.optional(),
2979
3115
  channel: NameSchema.optional(),
2980
- trigger: z14.string().default("manual"),
2981
- exitReason: z14.string().nullable().default(null),
2982
- costUsd: z14.number().default(0),
3116
+ place: NameSchema.optional(),
3117
+ trigger: z15.string().default("manual"),
3118
+ exitReason: z15.string().nullable().default(null),
3119
+ costUsd: z15.number().default(0),
2983
3120
  usage: UsageSchema.optional(),
2984
- model: z14.string().nullable().default(null),
2985
- summary: z14.string().nullable().default(null),
2986
- error: z14.string().nullable().default(null),
2987
- toolCalls: z14.number().int().nonnegative().optional(),
3121
+ model: z15.string().nullable().default(null),
3122
+ summary: z15.string().nullable().default(null),
3123
+ error: z15.string().nullable().default(null),
3124
+ toolCalls: z15.number().int().nonnegative().optional(),
2988
3125
  turnId: UlidSchema.optional()
2989
3126
  });
2990
3127
  function tokenUse(events, window, now) {
@@ -3215,11 +3352,14 @@ var BoardPaths = class {
3215
3352
  return path5.join(this.agentProjects(name), slug);
3216
3353
  }
3217
3354
  /**
3218
- * A thread's or a channel's conversation's session and last turn, beside the scope's home
3219
- * conversation; `part` is a thread's id or `#channel`.
3355
+ * A thread's, a channel's, or, in the society scope, a project's conversation's session and last
3356
+ * turn, beside the scope's home conversation; `part` is a thread's id, `#channel`, or `~project`.
3220
3357
  */
3221
3358
  agentConversation(name, slug, part) {
3222
- return part.startsWith("#") ? path5.join(this.agentProject(name, slug), "channels", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
3359
+ if (part.startsWith("#")) {
3360
+ return path5.join(this.agentProject(name, slug), "channels", part.slice(1));
3361
+ }
3362
+ return part.startsWith("~") ? path5.join(this.agentProject(name, slug), "places", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
3223
3363
  }
3224
3364
  agentCursors(name) {
3225
3365
  return path5.join(this.agent(name), "cursors.json");
@@ -3230,6 +3370,13 @@ var BoardPaths = class {
3230
3370
  eventLog() {
3231
3371
  return path5.join(this.events(), "log.jsonl");
3232
3372
  }
3373
+ /** Images citizens uploaded, by id: outside the projection, which every runner mirrors. */
3374
+ images() {
3375
+ return path5.join(this.dataDir, "images");
3376
+ }
3377
+ image(id) {
3378
+ return path5.join(this.images(), id);
3379
+ }
3233
3380
  state() {
3234
3381
  return path5.join(this.dataDir, "state");
3235
3382
  }
@@ -3246,6 +3393,101 @@ var BoardPaths = class {
3246
3393
  }
3247
3394
  };
3248
3395
 
3396
+ // packages/board-core/dist/skill-folder.js
3397
+ import { constants } from "node:fs";
3398
+ import { lstat, open as open2, readdir as readdir3, realpath } from "node:fs/promises";
3399
+ import path6 from "node:path";
3400
+ var SKILL_TEXT_LIMIT_CHARS = 1e5;
3401
+ var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
3402
+ function skillFileText(bytes) {
3403
+ const text = bytes.includes(0) ? null : bytes.toString("utf8");
3404
+ return {
3405
+ text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
3406
+ truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
3407
+ };
3408
+ }
3409
+ function inside(root, file) {
3410
+ const relative = path6.relative(root, file);
3411
+ return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
3412
+ }
3413
+ async function walk(root, prefix, found) {
3414
+ const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
3415
+ withFileTypes: true
3416
+ }).catch(() => []);
3417
+ for (const entry of entries.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
3418
+ if (found.length >= SKILL_FILES_LIMIT) {
3419
+ return;
3420
+ }
3421
+ const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
3422
+ if (!isSafeRelativePath(relative)) {
3423
+ continue;
3424
+ }
3425
+ if (entry.isDirectory()) {
3426
+ await walk(root, relative, found);
3427
+ } else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
3428
+ found.push(relative);
3429
+ }
3430
+ }
3431
+ }
3432
+ async function readEntry(root, relative) {
3433
+ let real;
3434
+ try {
3435
+ real = await realpath(path6.join(root, ...relative.split("/")));
3436
+ } catch {
3437
+ return null;
3438
+ }
3439
+ if (!inside(root, real)) {
3440
+ return null;
3441
+ }
3442
+ const handle = await open2(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
3443
+ if (handle === null) {
3444
+ return null;
3445
+ }
3446
+ try {
3447
+ const stats = await handle.stat();
3448
+ if (!stats.isFile()) {
3449
+ return null;
3450
+ }
3451
+ const buffer = Buffer.alloc(Math.min(stats.size, SKILL_TEXT_LIMIT_BYTES));
3452
+ let size = 0;
3453
+ while (size < buffer.length) {
3454
+ const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
3455
+ if (bytesRead === 0) {
3456
+ break;
3457
+ }
3458
+ size += bytesRead;
3459
+ }
3460
+ return {
3461
+ path: relative,
3462
+ size: stats.size,
3463
+ executable: (stats.mode & 73) !== 0,
3464
+ ...skillFileText(buffer.subarray(0, size))
3465
+ };
3466
+ } finally {
3467
+ await handle.close();
3468
+ }
3469
+ }
3470
+ async function readSkillFolder(dir) {
3471
+ const info = await lstat(dir).catch(() => null);
3472
+ if (info === null || !info.isDirectory()) {
3473
+ return null;
3474
+ }
3475
+ const root = await realpath(dir).catch(() => null);
3476
+ if (root === null) {
3477
+ return null;
3478
+ }
3479
+ const found = [];
3480
+ await walk(root, "", found);
3481
+ const files = [];
3482
+ for (const relative of found) {
3483
+ const file = await readEntry(root, relative);
3484
+ if (file !== null) {
3485
+ files.push(file);
3486
+ }
3487
+ }
3488
+ return files.some((file) => file.path === "SKILL.md") ? files : null;
3489
+ }
3490
+
3249
3491
  // packages/board-core/dist/tokens.js
3250
3492
  import { createHash as createHash2, randomBytes } from "node:crypto";
3251
3493
  function mintToken() {
@@ -3259,48 +3501,62 @@ function hashToken(token) {
3259
3501
  var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
3260
3502
  var CLOSING_ATTEMPTS = 3;
3261
3503
  function ownTurn(actor, turn2) {
3262
- return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel;
3504
+ return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel && turn2.place === actor.place;
3263
3505
  }
3264
3506
  function namesList(names) {
3265
3507
  return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
3266
3508
  }
3267
3509
  var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
3268
- var RunnerTokensSchema = z15.object({
3269
- runners: z15.record(z15.string(), z15.object({ tokenHash: z15.string().min(1) }))
3510
+ var RunnerTokensSchema = z16.object({
3511
+ runners: z16.record(z16.string(), z16.object({ tokenHash: z16.string().min(1) }))
3270
3512
  });
3271
- var SignInsSchema = z15.object({
3272
- signIns: z15.array(z15.object({
3273
- tokenHash: z15.string().min(1),
3274
- login: z15.string().min(1),
3513
+ var SignInsSchema = z16.object({
3514
+ signIns: z16.array(z16.object({
3515
+ tokenHash: z16.string().min(1),
3516
+ login: z16.string().min(1),
3275
3517
  /** The account's picture at the identity provider; sign-ins from before it have none. */
3276
- avatarUrl: z15.url().optional(),
3518
+ avatarUrl: z16.url().optional(),
3277
3519
  createdAt: IsoDateTimeSchema,
3278
3520
  expiresAt: IsoDateTimeSchema
3279
3521
  })).default([])
3280
3522
  });
3281
- var TurnStartPayloadSchema = z15.object({ project: NameSchema, turnId: UlidSchema.optional() });
3282
- var CursorsSchema = z15.object({
3283
- digest: z15.string().nullable(),
3284
- scopes: z15.record(z15.string(), z15.string()).default({}),
3285
- threadsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3286
- channelsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
3523
+ var TurnStartPayloadSchema = z16.object({ project: NameSchema, turnId: UlidSchema.optional() });
3524
+ var CursorsSchema = z16.object({
3525
+ digest: z16.string().nullable(),
3526
+ scopes: z16.record(z16.string(), z16.string()).default({}),
3527
+ threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
3528
+ channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
3529
+ placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
3287
3530
  /** How far a work role has read news, which goes to whichever of its general conversations reads first. */
3288
- news: z15.string().optional()
3289
- });
3290
- var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
3531
+ news: z16.string().optional()
3532
+ });
3533
+ var NO_CURSORS = {
3534
+ digest: null,
3535
+ scopes: {},
3536
+ threadsFrom: {},
3537
+ channelsFrom: {},
3538
+ placesFrom: {}
3539
+ };
3291
3540
  function conversationKey(scope, part) {
3292
3541
  return part === null ? scope : `${scope}/${part}`;
3293
3542
  }
3294
3543
  function channelOfPart(part) {
3295
3544
  return part?.startsWith("#") === true ? part.slice(1) : void 0;
3296
3545
  }
3546
+ function placeOfPart(part) {
3547
+ return part?.startsWith("~") === true ? part.slice(1) : void 0;
3548
+ }
3297
3549
  function threadOfPart(part) {
3298
- return part === null || part === void 0 || part.startsWith("#") ? void 0 : part;
3550
+ return part === null || part === void 0 || part.startsWith("#") || part.startsWith("~") ? void 0 : part;
3299
3551
  }
3300
3552
  function channelStart(cursors, scope) {
3301
3553
  const from = cursors.channelsFrom[scope];
3302
3554
  return from === void 0 ? cursors.digest : from;
3303
3555
  }
3556
+ function placeStart(cursors, scope) {
3557
+ const from = cursors.placesFrom[scope];
3558
+ return from === void 0 ? cursors.digest : from;
3559
+ }
3304
3560
  function threadStart(cursors, scope) {
3305
3561
  const from = cursors.threadsFrom[scope];
3306
3562
  return from === void 0 ? cursors.digest : from;
@@ -3316,7 +3572,7 @@ function cursorOf(cursors, scope, part = null) {
3316
3572
  if (part === null) {
3317
3573
  return cursors.scopes[scope] ?? cursors.digest;
3318
3574
  }
3319
- return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : threadStart(cursors, scope));
3575
+ return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : threadStart(cursors, scope));
3320
3576
  }
3321
3577
  function byId(a, b) {
3322
3578
  return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
@@ -3324,12 +3580,14 @@ function byId(a, b) {
3324
3580
  function turnTime(record) {
3325
3581
  return record.endedAt ?? record.startedAt;
3326
3582
  }
3327
- var PausedSchema = z15.object({ paused: z15.boolean() });
3583
+ function turnConversation(record) {
3584
+ return record.thread !== void 0 ? ` in thread ${record.thread}` : record.channel !== void 0 ? ` in #${record.channel}` : record.place !== void 0 ? ` in ${record.place}'s channels` : "";
3585
+ }
3586
+ var PausedSchema = z16.object({ paused: z16.boolean() });
3328
3587
  var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
3329
3588
  function withoutMentions(text) {
3330
3589
  return text.replaceAll(MENTION_PATTERN, "$1$2");
3331
3590
  }
3332
- var SKILL_TEXT_LIMIT_CHARS = 1e5;
3333
3591
  function skillFolderOf(proposal2) {
3334
3592
  if (proposal2.kind !== "skill") {
3335
3593
  return null;
@@ -3338,7 +3596,7 @@ function skillFolderOf(proposal2) {
3338
3596
  return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
3339
3597
  }
3340
3598
  function proposedFrom(origin, where) {
3341
- return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
3599
+ return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
3342
3600
  }
3343
3601
  function megabytes(bytes) {
3344
3602
  return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
@@ -3349,7 +3607,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
3349
3607
  var FRONT_DESK_TRIGGER = "user_post";
3350
3608
  var OPS_CHANNELS = ["governance"];
3351
3609
  var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
3352
- var AlignmentsSchema = z15.object({ applied: z15.array(z15.string()).default([]) });
3610
+ var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
3353
3611
  var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
3354
3612
  var THREAD_RECORDS = "threads-as-records";
3355
3613
  var DIGEST_CURSORS = "digest-replaces-inbox";
@@ -3360,13 +3618,15 @@ var THREAD_CONVERSATIONS = "thread-conversations";
3360
3618
  var ASKS_CHANNEL_OPENED = "asks-channel-opened";
3361
3619
  var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
3362
3620
  var CHANNEL_CONVERSATIONS = "channel-conversations";
3621
+ var PLACE_CONVERSATIONS = "place-conversations";
3363
3622
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3364
3623
  var CRON_VERBS = "cron-verbs-granted";
3365
3624
  var KNOWLEDGE_NOTICES = "knowledge-notices";
3366
- var KnowledgeEventSchema = z15.object({
3625
+ var IMAGE_UPLOADS = "image-uploads-granted";
3626
+ var KnowledgeEventSchema = z16.object({
3367
3627
  topic: NameSchema,
3368
3628
  project: NameSchema.nullable(),
3369
- replaced: z15.boolean().optional()
3629
+ replaced: z16.boolean().optional()
3370
3630
  });
3371
3631
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3372
3632
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
@@ -3433,8 +3693,8 @@ function restage(existing, edit) {
3433
3693
  };
3434
3694
  }
3435
3695
  function reopenStage(stage) {
3436
- const { completedBy: _by, completedAt: _at, ...open3 } = stage;
3437
- return open3;
3696
+ const { completedBy: _by, completedAt: _at, ...open4 } = stage;
3697
+ return open4;
3438
3698
  }
3439
3699
  function gateChanges(before, after) {
3440
3700
  const changes = [];
@@ -3498,6 +3758,7 @@ var Board = class _Board {
3498
3758
  mutex = new Mutex();
3499
3759
  /** The turns in flight, which the server hosting them supplies; none until it does. */
3500
3760
  turnsInFlight = () => [];
3761
+ imageSource = null;
3501
3762
  newId = monotonicFactory();
3502
3763
  events;
3503
3764
  actionCounts = /* @__PURE__ */ new Map();
@@ -3514,7 +3775,7 @@ var Board = class _Board {
3514
3775
  homes;
3515
3776
  constructor(dataDir, options) {
3516
3777
  this.paths = new BoardPaths(dataDir);
3517
- this.homes = new HomeRepos(path6.join(this.paths.state(), "home-hooks"));
3778
+ this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
3518
3779
  this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
3519
3780
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
3520
3781
  this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
@@ -3547,7 +3808,7 @@ var Board = class _Board {
3547
3808
  * repeated, so a member that later chooses otherwise keeps its choice.
3548
3809
  */
3549
3810
  async applyAlignments() {
3550
- const file = path6.join(this.paths.state(), "alignments.json");
3811
+ const file = path7.join(this.paths.state(), "alignments.json");
3551
3812
  const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
3552
3813
  const pending = [
3553
3814
  FOLLOW_DECISIONS,
@@ -3562,7 +3823,9 @@ var Board = class _Board {
3562
3823
  CHANNEL_CONVERSATIONS,
3563
3824
  DASHBOARD_UPDATES,
3564
3825
  CRON_VERBS,
3565
- KNOWLEDGE_NOTICES
3826
+ KNOWLEDGE_NOTICES,
3827
+ IMAGE_UPLOADS,
3828
+ PLACE_CONVERSATIONS
3566
3829
  ].filter((name) => !applied.includes(name));
3567
3830
  if (pending.length === 0) {
3568
3831
  return;
@@ -3592,13 +3855,13 @@ var Board = class _Board {
3592
3855
  await this.openAsksChannel();
3593
3856
  }
3594
3857
  if (pending.includes(KNOWLEDGE_REMOVAL)) {
3595
- await this.grantBesideKnowledge("remove_knowledge");
3858
+ await this.grantBeside("write_knowledge", "remove_knowledge");
3596
3859
  }
3597
3860
  if (pending.includes(CHANNEL_CONVERSATIONS)) {
3598
3861
  await this.startChannelCursors();
3599
3862
  }
3600
3863
  if (pending.includes(DASHBOARD_UPDATES)) {
3601
- await this.grantBesideKnowledge("update_dashboard");
3864
+ await this.grantBeside("write_knowledge", "update_dashboard");
3602
3865
  await this.rewordDashboards();
3603
3866
  }
3604
3867
  if (pending.includes(CRON_VERBS)) {
@@ -3607,6 +3870,12 @@ var Board = class _Board {
3607
3870
  if (pending.includes(KNOWLEDGE_NOTICES)) {
3608
3871
  await this.noticeKnowledgeNotes();
3609
3872
  }
3873
+ if (pending.includes(IMAGE_UPLOADS)) {
3874
+ await this.grantBeside("post_message", "upload_image");
3875
+ }
3876
+ if (pending.includes(PLACE_CONVERSATIONS)) {
3877
+ await this.startPlaceCursors();
3878
+ }
3610
3879
  await writeJson(file, { applied: [...applied, ...pending] });
3611
3880
  }
3612
3881
  /**
@@ -3648,12 +3917,12 @@ var Board = class _Board {
3648
3917
  }
3649
3918
  }
3650
3919
  /**
3651
- * Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
3652
- * practice; seed roles have it from their seed.
3920
+ * Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
3921
+ * seed roles have it from their seed.
3653
3922
  */
3654
- async grantBesideKnowledge(verb) {
3923
+ async grantBeside(beside, verb) {
3655
3924
  for (const charter of await this.listRoles()) {
3656
- if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes(verb)) {
3925
+ if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
3657
3926
  await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
3658
3927
  }
3659
3928
  }
@@ -3811,7 +4080,7 @@ var Board = class _Board {
3811
4080
  }
3812
4081
  }
3813
4082
  for (const file of await listFiles(this.paths.tasks(slug))) {
3814
- const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z15.record(z15.string(), z15.unknown()));
4083
+ const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
3815
4084
  const legacy = raw.data["thread"];
3816
4085
  if (legacy === void 0) {
3817
4086
  continue;
@@ -3819,7 +4088,7 @@ var Board = class _Board {
3819
4088
  const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
3820
4089
  if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
3821
4090
  const ended = task2.status === "done" || task2.status === "abandoned";
3822
- const open3 = legacy === "open" && !ended;
4091
+ const open4 = legacy === "open" && !ended;
3823
4092
  const openedBy = opened.get(task2.id);
3824
4093
  const closedBy = closed.get(task2.id);
3825
4094
  await this.writeThread(threads, {
@@ -3827,10 +4096,10 @@ var Board = class _Board {
3827
4096
  channel: channelRef(slug, "general"),
3828
4097
  title: task2.title,
3829
4098
  subject: { kind: "task", id: task2.id },
3830
- state: open3 ? "open" : "closed",
4099
+ state: open4 ? "open" : "closed",
3831
4100
  openedBy: openedBy?.actor ?? task2.createdBy,
3832
4101
  openedAt: openedBy?.ts ?? task2.createdAt,
3833
- ...open3 ? {} : {
4102
+ ...open4 ? {} : {
3834
4103
  closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
3835
4104
  closedAt: closedBy?.ts ?? this.now().toISOString()
3836
4105
  },
@@ -3862,6 +4131,24 @@ var Board = class _Board {
3862
4131
  await writeJson(file, { ...cursors, channelsFrom });
3863
4132
  }
3864
4133
  }
4134
+ /**
4135
+ * A project's channels were once read, from the society scope, in the society's home
4136
+ * conversation, so a member had read each up to that cursor. Each project's conversation in the
4137
+ * society scope starts from it now, as each takes a conversation of its own.
4138
+ */
4139
+ async startPlaceCursors() {
4140
+ for (const agent2 of await this.listAgents()) {
4141
+ const file = this.paths.agentCursors(agent2.name);
4142
+ if (!await exists(file)) {
4143
+ continue;
4144
+ }
4145
+ const cursors = await readJson(file, CursorsSchema);
4146
+ await writeJson(file, {
4147
+ ...cursors,
4148
+ placesFrom: { [SOCIETY_SCOPE]: cursorOf(cursors, SOCIETY_SCOPE) }
4149
+ });
4150
+ }
4151
+ }
3865
4152
  /**
3866
4153
  * Threads were once read in the scope's single conversation, so a member had read every thread
3867
4154
  * up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
@@ -3889,7 +4176,7 @@ var Board = class _Board {
3889
4176
  * turns, so it follows nothing now.
3890
4177
  */
3891
4178
  async renameInboxCursors() {
3892
- const Legacy = z15.object({ inbox: z15.string().nullable() });
4179
+ const Legacy = z16.object({ inbox: z16.string().nullable() });
3893
4180
  for (const agent2 of await this.listAgents()) {
3894
4181
  const file = this.paths.agentCursors(agent2.name);
3895
4182
  if (await exists(file)) {
@@ -3935,7 +4222,7 @@ ${charter.purpose}
3935
4222
  */
3936
4223
  async alignSeedRole(seed) {
3937
4224
  const file = this.paths.role(seed.name);
3938
- const raw = await readMarkdown(file, z15.record(z15.string(), z15.unknown()));
4225
+ const raw = await readMarkdown(file, z16.record(z16.string(), z16.unknown()));
3939
4226
  const current = RoleCharterSchema.parse(raw.data);
3940
4227
  const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
3941
4228
  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));
@@ -4002,13 +4289,15 @@ ${charter.purpose}
4002
4289
  const token = mintToken();
4003
4290
  const thread2 = threadOfPart(part);
4004
4291
  const channel = channelOfPart(part);
4292
+ const place = placeOfPart(part);
4005
4293
  this.turnTokens.set(hashToken(token), {
4006
4294
  actor: {
4007
4295
  name: agent2,
4008
4296
  role: role2,
4009
4297
  ...scope === void 0 ? {} : { scope },
4010
4298
  ...thread2 === void 0 ? {} : { thread: thread2 },
4011
- ...channel === void 0 ? {} : { channel }
4299
+ ...channel === void 0 ? {} : { channel },
4300
+ ...place === void 0 ? {} : { place }
4012
4301
  },
4013
4302
  expiresAt: this.now().getTime() + ttlMs
4014
4303
  });
@@ -4097,7 +4386,7 @@ ${charter.purpose}
4097
4386
  });
4098
4387
  }
4099
4388
  signInsFile() {
4100
- return path6.join(this.paths.state(), "sign-ins.json");
4389
+ return path7.join(this.paths.state(), "sign-ins.json");
4101
4390
  }
4102
4391
  async readSignIns() {
4103
4392
  const file = this.signInsFile();
@@ -4194,7 +4483,7 @@ ${charter.purpose}
4194
4483
  const files = await listFiles(this.paths.tasks(project2));
4195
4484
  const tasks = [];
4196
4485
  for (const file of files) {
4197
- const doc = await readMarkdown(path6.join(this.paths.tasks(project2), file), TaskFrontmatterSchema);
4486
+ const doc = await readMarkdown(path7.join(this.paths.tasks(project2), file), TaskFrontmatterSchema);
4198
4487
  tasks.push({ ...doc.data, body: doc.body });
4199
4488
  }
4200
4489
  return tasks;
@@ -4213,7 +4502,7 @@ ${charter.purpose}
4213
4502
  const files = await listFiles(this.paths.proposals());
4214
4503
  const proposals = [];
4215
4504
  for (const file of files) {
4216
- const doc = await readMarkdown(path6.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
4505
+ const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
4217
4506
  proposals.push({ ...doc.data, body: doc.body });
4218
4507
  }
4219
4508
  return proposals;
@@ -4240,7 +4529,7 @@ ${charter.purpose}
4240
4529
  async listMembers() {
4241
4530
  const members = [];
4242
4531
  for (const file of await listFiles(this.paths.members())) {
4243
- const doc = await readMarkdown(path6.join(this.paths.members(), file), MemberSchema);
4532
+ const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
4244
4533
  members.push({ ...doc.data, profile: doc.body });
4245
4534
  }
4246
4535
  return members;
@@ -4252,14 +4541,14 @@ ${charter.purpose}
4252
4541
  /** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
4253
4542
  async readSocietyNorms() {
4254
4543
  const file = this.paths.societyKnowledgeFile("norms");
4255
- return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
4544
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
4256
4545
  }
4257
4546
  /** Knowledge topics of a project, or of the society when the project is null. */
4258
4547
  async listKnowledge(project2) {
4259
4548
  const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
4260
4549
  const topics = [];
4261
4550
  for (const file of await listFiles(dir)) {
4262
- topics.push(await this.knowledgeAt(path6.join(dir, file), project2, file.replace(/\.md$/, "")));
4551
+ topics.push(await this.knowledgeAt(path7.join(dir, file), project2, file.replace(/\.md$/, "")));
4263
4552
  }
4264
4553
  return topics;
4265
4554
  }
@@ -4272,7 +4561,7 @@ ${charter.purpose}
4272
4561
  */
4273
4562
  async knowledgeAt(file, project2, topic) {
4274
4563
  const { data, body } = await readLooseMarkdown(file);
4275
- const revision = z15.number().int().min(0).safeParse(data["revision"]);
4564
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
4276
4565
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
4277
4566
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
4278
4567
  return {
@@ -4289,7 +4578,7 @@ ${charter.purpose}
4289
4578
  const aside = this.paths.removedKnowledge(project2 ?? SOCIETY_SCOPE);
4290
4579
  let highest = 0;
4291
4580
  for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
4292
- const copy = await this.knowledgeAt(path6.join(aside, file), project2, topic);
4581
+ const copy = await this.knowledgeAt(path7.join(aside, file), project2, topic);
4293
4582
  highest = Math.max(highest, copy.revision);
4294
4583
  }
4295
4584
  return highest;
@@ -4305,7 +4594,7 @@ ${charter.purpose}
4305
4594
  async listSkillsIn(dir, scope) {
4306
4595
  const skills = [];
4307
4596
  for (const entry of await listDirs(dir)) {
4308
- const file = path6.join(dir, entry, "SKILL.md");
4597
+ const file = path7.join(dir, entry, "SKILL.md");
4309
4598
  if (!await exists(file)) {
4310
4599
  continue;
4311
4600
  }
@@ -4395,7 +4684,7 @@ ${charter.purpose}
4395
4684
  if (!wanted.delete(file.slice(0, 26))) {
4396
4685
  continue;
4397
4686
  }
4398
- const { data } = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
4687
+ const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
4399
4688
  found.push(MessageRefSchema.parse(data));
4400
4689
  }
4401
4690
  if (wanted.size === 0) {
@@ -4427,7 +4716,7 @@ ${charter.purpose}
4427
4716
  if (!file.endsWith(".md")) {
4428
4717
  continue;
4429
4718
  }
4430
- const doc = await readMarkdown(path6.join(threads, file), ThreadFrontmatterSchema);
4719
+ const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
4431
4720
  if (doc.data.state !== "open") {
4432
4721
  continue;
4433
4722
  }
@@ -4454,7 +4743,7 @@ ${charter.purpose}
4454
4743
  const all = [];
4455
4744
  for (const threads of await this.threadScopes()) {
4456
4745
  for (const file of await listFiles(threads)) {
4457
- const doc = await readMarkdown(path6.join(threads, file), ThreadFrontmatterSchema);
4746
+ const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
4458
4747
  const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
4459
4748
  const last = messages.at(-1);
4460
4749
  all.push({
@@ -4821,16 +5110,20 @@ ${DASHBOARD_LINE}
4821
5110
  }
4822
5111
  /**
4823
5112
  * How many unread digest messages a member has in each conversation: a scope's home, for its
4824
- * general channel, one of its other channels, or one of its threads. The heartbeat asks, so that it wakes a member where its unread
5113
+ * general channel, one of its other channels, one of its threads, or, in the society scope, a
5114
+ * project's channels. The heartbeat asks, so that it wakes a member where its unread
4825
5115
  * messages are and nowhere else. Society channels count only for a member woken in the society
4826
5116
  * scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
4827
5117
  * counted, and are read when the member wakes for something else: a task step's, which wakes
4828
5118
  * whoever it hands work to, and the board's own, operations signals and landing announcements,
4829
- * whose signals that call for action wake their readers directly.
5119
+ * whose signals that call for action wake their readers directly. A closed thread counts for
5120
+ * nothing: its conversation has ended, and a turn there could only read its posts and be refused
5121
+ * a reply, while a wake queued before it closed, as for a mention, still runs there.
4830
5122
  */
4831
5123
  async unreadByConversation(actor) {
4832
5124
  const { cursors, upto } = await this.digestSnapshot(actor.name);
4833
5125
  const counts = /* @__PURE__ */ new Map();
5126
+ const closed = /* @__PURE__ */ new Map();
4834
5127
  const unscoped = { name: actor.name, role: actor.role };
4835
5128
  for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
4836
5129
  if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
@@ -4840,11 +5133,23 @@ ${DASHBOARD_LINE}
4840
5133
  if (posted !== scope && scope !== SOCIETY_SCOPE) {
4841
5134
  continue;
4842
5135
  }
5136
+ const thread2 = threadOfPart(part);
5137
+ if (thread2 !== void 0) {
5138
+ let ended = closed.get(thread2);
5139
+ if (ended === void 0) {
5140
+ ended = (await this.tryFindThread(thread2))?.thread.state === "closed";
5141
+ closed.set(thread2, ended);
5142
+ }
5143
+ if (ended) {
5144
+ continue;
5145
+ }
5146
+ }
4843
5147
  const key = conversationKey(scope, part);
4844
5148
  const entry = counts.get(key) ?? {
4845
5149
  scope,
4846
- thread: threadOfPart(part) ?? null,
5150
+ thread: thread2 ?? null,
4847
5151
  channel: channelOfPart(part) ?? null,
5152
+ place: placeOfPart(part) ?? null,
4848
5153
  count: 0
4849
5154
  };
4850
5155
  counts.set(key, { ...entry, count: entry.count + 1 });
@@ -4902,8 +5207,9 @@ ${DASHBOARD_LINE}
4902
5207
  * thread it takes part in, and for the front desk, which every post by the user wakes, every post
4903
5208
  * by the user wherever it is. With a conversation, only what is filed there: for a thread, every
4904
5209
  * message of the thread, since the conversation is about it; for a channel's, that channel's
4905
- * top-level messages; for a scope's home, general's and those of a project the member is not in,
4906
- * since each thread and each other channel is a conversation of its own. Nothing newer than
5210
+ * top-level messages; for a project's in the society scope, the top-level messages of every
5211
+ * channel of a project the member is not in; for a scope's home, general's, since each thread,
5212
+ * each other channel, and each such project is a conversation of its own. Nothing newer than
4907
5213
  * `upto` counts (see `digestSnapshot`).
4908
5214
  */
4909
5215
  async digestSince(actor, cursors, upto, conversation) {
@@ -4923,11 +5229,17 @@ ${DASHBOARD_LINE}
4923
5229
  const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
4924
5230
  const threadParticipation = /* @__PURE__ */ new Map();
4925
5231
  const openChannels = /* @__PURE__ */ new Map();
5232
+ const liveProjects = /* @__PURE__ */ new Map();
4926
5233
  const collected = [];
4927
5234
  const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
4928
5235
  const scopes = conversation !== void 0 ? [conversation.scope] : [...agent2.memberships, SOCIETY_SCOPE];
4929
5236
  const starts = [
4930
- ...scopes.flatMap((each) => conversation === void 0 ? [cursorOf(cursors, each), threadStart(cursors, each), channelStart(cursors, each)] : [cursorOf(cursors, each, conversation.part)]),
5237
+ ...scopes.flatMap((each) => conversation === void 0 ? [
5238
+ cursorOf(cursors, each),
5239
+ threadStart(cursors, each),
5240
+ channelStart(cursors, each),
5241
+ placeStart(cursors, each)
5242
+ ] : [cursorOf(cursors, each, conversation.part)]),
4931
5243
  ...newsFrom === void 0 ? [] : [newsFrom]
4932
5244
  ];
4933
5245
  const known = starts.filter((start) => start !== null);
@@ -4937,7 +5249,7 @@ ${DASHBOARD_LINE}
4937
5249
  const thread2 = message.thread ?? null;
4938
5250
  const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
4939
5251
  if (newsFrom !== void 0 && thread2 === null && filedIn === SOCIETY_SCOPE && !asked) {
4940
- if ((newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
5252
+ if ((conversation === void 0 || conversation.part === null) && (newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
4941
5253
  collected.push({
4942
5254
  scope: conversation?.scope ?? SOCIETY_SCOPE,
4943
5255
  part: null,
@@ -4948,12 +5260,18 @@ ${DASHBOARD_LINE}
4948
5260
  continue;
4949
5261
  }
4950
5262
  const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
4951
- let open3 = named === void 0 ? void 0 : openChannels.get(message.channel);
4952
- if (named !== void 0 && open3 === void 0) {
4953
- open3 = await this.channelOpen(message.channel);
4954
- openChannels.set(message.channel, open3);
4955
- }
4956
- const part = thread2 ?? (open3 === true ? `#${named}` : null);
5263
+ let open4 = named === void 0 ? void 0 : openChannels.get(message.channel);
5264
+ if (named !== void 0 && open4 === void 0) {
5265
+ open4 = await this.channelOpen(message.channel);
5266
+ openChannels.set(message.channel, open4);
5267
+ }
5268
+ const project2 = thread2 === null ? placeConversation(filedIn, message.channel) : void 0;
5269
+ let live = project2 === void 0 ? void 0 : liveProjects.get(project2);
5270
+ if (project2 !== void 0 && live === void 0) {
5271
+ live = await this.projectLive(project2);
5272
+ liveProjects.set(project2, live);
5273
+ }
5274
+ const part = thread2 ?? (open4 === true ? `#${named}` : live === true ? `~${project2}` : null);
4957
5275
  if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
4958
5276
  continue;
4959
5277
  }
@@ -5018,7 +5336,7 @@ ${task2.body}`;
5018
5336
  }
5019
5337
  }
5020
5338
  for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
5021
- const content = await readFile4(path6.join(this.paths.projectKnowledge(project2), file), "utf8");
5339
+ const content = await readFile4(path7.join(this.paths.projectKnowledge(project2), file), "utf8");
5022
5340
  if (content.toLowerCase().includes(needle)) {
5023
5341
  hits.push({
5024
5342
  kind: "knowledge",
@@ -5039,7 +5357,7 @@ ${task2.body}`;
5039
5357
  shared.push({
5040
5358
  kind: "knowledge",
5041
5359
  ref: `society/knowledge/${file}`,
5042
- file: path6.join(this.paths.societyKnowledge(), file)
5360
+ file: path7.join(this.paths.societyKnowledge(), file)
5043
5361
  });
5044
5362
  }
5045
5363
  for (const skill2 of await this.listSocietySkills()) {
@@ -5052,7 +5370,7 @@ ${task2.body}`;
5052
5370
  shared.push({
5053
5371
  kind: "memory",
5054
5372
  ref: `memory/${file}`,
5055
- file: path6.join(this.paths.agentMemory(actor.name), file)
5373
+ file: path7.join(this.paths.agentMemory(actor.name), file)
5056
5374
  });
5057
5375
  }
5058
5376
  for (const entry of shared) {
@@ -5142,7 +5460,7 @@ ${current.body}`);
5142
5460
  });
5143
5461
  const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
5144
5462
  await mkdir4(aside, { recursive: true });
5145
- await rename3(file, path6.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
5463
+ await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
5146
5464
  await this.events.append("knowledge.removed", actor.name, {
5147
5465
  topic: args.topic,
5148
5466
  project: args.project
@@ -5190,7 +5508,7 @@ ${current.body}`);
5190
5508
  });
5191
5509
  const aside = this.paths.removedSkills();
5192
5510
  await mkdir4(aside, { recursive: true });
5193
- await rename3(this.paths.societySkill(args.name), path6.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
5511
+ await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
5194
5512
  await this.events.append("skill.removed", actor.name, {
5195
5513
  name: args.name,
5196
5514
  reason: args.reason
@@ -5235,11 +5553,65 @@ ${current.body}`);
5235
5553
  return updated;
5236
5554
  });
5237
5555
  }
5556
+ /**
5557
+ * Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
5558
+ * refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
5559
+ * Only the bytes of a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` are kept, checked
5560
+ * here again whatever the runner said, under a new id outside the projection, so no runner
5561
+ * mirrors them and no prompt carries them: a post shows the image by its `image:` address.
5562
+ */
5563
+ async uploadImage(actor, input) {
5564
+ const args = VerbInputs.upload_image.parse(input);
5565
+ await this.authorize(actor, "upload_image");
5566
+ if (actor.scope === void 0) {
5567
+ throw new BoardError("INVALID_STATE", "upload_image reads the file on the machine a turn runs on, so only a turn can call it");
5568
+ }
5569
+ if (this.imageSource === null) {
5570
+ throw new BoardError("NOT_FOUND", "this board has no runners to read an image from");
5571
+ }
5572
+ const bytes = await this.imageSource(actor, args.path);
5573
+ const type = imageTypeOf(bytes);
5574
+ if (type === null) {
5575
+ throw new BoardError("VALIDATION", `${args.path} is not a PNG, JPEG, GIF, or WebP image by its first bytes`);
5576
+ }
5577
+ if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
5578
+ throw new BoardError("VALIDATION", `${args.path} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
5579
+ }
5580
+ return this.mutex.run(async () => {
5581
+ const id = this.newId();
5582
+ await writeFileAtomic(this.paths.image(id), bytes);
5583
+ await this.events.append("image.uploaded", actor.name, {
5584
+ id,
5585
+ type,
5586
+ size: bytes.byteLength
5587
+ });
5588
+ const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
5589
+ return {
5590
+ id,
5591
+ type,
5592
+ size: bytes.byteLength,
5593
+ markdown: `![${name}](${IMAGE_SCHEME}${id})`
5594
+ };
5595
+ });
5596
+ }
5597
+ /** An uploaded image's bytes and kind, which its first bytes tell. */
5598
+ async readImage(id) {
5599
+ const file = this.paths.image(UlidSchema.parse(id));
5600
+ if (!await exists(file)) {
5601
+ throw new BoardError("NOT_FOUND", `image ${id} not found`);
5602
+ }
5603
+ const bytes = await readFile4(file);
5604
+ const type = imageTypeOf(bytes);
5605
+ if (type === null) {
5606
+ throw new BoardError("NOT_FOUND", `image ${id} is not an image the board shows`);
5607
+ }
5608
+ return { type, bytes };
5609
+ }
5238
5610
  /** Every cron, ended ones included, oldest first. */
5239
5611
  async listCrons() {
5240
5612
  const crons = [];
5241
5613
  for (const file of await listFiles(this.paths.crons())) {
5242
- crons.push(await this.readCronFile(path6.join(this.paths.crons(), file)));
5614
+ crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
5243
5615
  }
5244
5616
  return crons;
5245
5617
  }
@@ -5361,11 +5733,16 @@ ${current.body}`);
5361
5733
  }
5362
5734
  /**
5363
5735
  * Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
5364
- * the conversation of the turn that set it when it wakes its setter, else its home outside projects.
5736
+ * the conversation of the turn that set it when it wakes its setter, a project's channels' in
5737
+ * the society scope included, else its home outside projects.
5365
5738
  */
5366
5739
  async cronConversation(actor, agent2, named) {
5367
5740
  const own = actor.name === agent2.name;
5368
5741
  if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
5742
+ if (own && actor.scope === SOCIETY_SCOPE && actor.place !== void 0) {
5743
+ await this.readActiveProject(actor.place);
5744
+ return { scope: SOCIETY_SCOPE, place: actor.place };
5745
+ }
5369
5746
  return own && actor.scope !== void 0 ? this.cronConversation(actor, agent2, {
5370
5747
  project: actor.scope,
5371
5748
  thread_id: actor.thread,
@@ -5441,7 +5818,8 @@ ${current.body}`);
5441
5818
  agent: cron2.agent,
5442
5819
  project: cron2.scope,
5443
5820
  ...cron2.thread === void 0 ? {} : { thread: cron2.thread },
5444
- ...cron2.channel === void 0 ? {} : { channel: cron2.channel }
5821
+ ...cron2.channel === void 0 ? {} : { channel: cron2.channel },
5822
+ ...cron2.place === void 0 ? {} : { place: cron2.place }
5445
5823
  };
5446
5824
  }
5447
5825
  async readCronFile(file) {
@@ -5626,6 +6004,13 @@ ${current.body}`);
5626
6004
  watchTurns(source) {
5627
6005
  this.turnsInFlight = source;
5628
6006
  }
6007
+ /**
6008
+ * Where `upload_image` reads a file from: the runner the calling turn runs on, through the server
6009
+ * that hosts the runners. Without one, as in the admin CLI, nothing can be uploaded.
6010
+ */
6011
+ readImagesWith(source) {
6012
+ this.imageSource = source;
6013
+ }
5629
6014
  /**
5630
6015
  * Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
5631
6016
  * turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
@@ -5645,17 +6030,20 @@ ${current.body}`);
5645
6030
  async openThreadIds(dir, keep) {
5646
6031
  const ids = /* @__PURE__ */ new Set();
5647
6032
  for (const file of await listFiles(dir)) {
5648
- const doc = await readMarkdown(path6.join(dir, file), ThreadFrontmatterSchema);
6033
+ const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
5649
6034
  if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
5650
6035
  ids.add(doc.data.id);
5651
6036
  }
5652
6037
  }
5653
6038
  return ids;
5654
6039
  }
5655
- /** Refuses to archive a project while a turn runs in it or in a thread its archive closes. */
6040
+ /**
6041
+ * Refuses to archive a project while a turn runs in it, in a citizen's conversation of its
6042
+ * channels in the society scope, or in a thread its archive closes.
6043
+ */
5656
6044
  async assertProjectQuiet(actor, slug) {
5657
6045
  const threads = await this.openThreadIds(this.paths.threads(slug), () => true);
5658
- this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
6046
+ this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.place === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
5659
6047
  }
5660
6048
  async closeThread(actor, input) {
5661
6049
  const args = VerbInputs.close_thread.parse(input);
@@ -6196,7 +6584,8 @@ ${args.summary}`);
6196
6584
  filedFrom: {
6197
6585
  scope: actor.scope,
6198
6586
  ...actor.thread === void 0 ? {} : { thread: actor.thread },
6199
- ...actor.channel === void 0 ? {} : { channel: actor.channel }
6587
+ ...actor.channel === void 0 ? {} : { channel: actor.channel },
6588
+ ...actor.place === void 0 ? {} : { place: actor.place }
6200
6589
  }
6201
6590
  },
6202
6591
  charter: parsed.data
@@ -6455,6 +6844,8 @@ ${rationale}`}`);
6455
6844
  return this.updateCron(actor, VerbInputs.update_cron.parse(input));
6456
6845
  case "remove_cron":
6457
6846
  return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
6847
+ case "upload_image":
6848
+ return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
6458
6849
  default:
6459
6850
  throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
6460
6851
  }
@@ -6501,7 +6892,7 @@ ${rationale}`}`);
6501
6892
  }
6502
6893
  async readAgentRoleBody(agent2) {
6503
6894
  const file = this.paths.agentRole(agent2);
6504
- return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
6895
+ return await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
6505
6896
  }
6506
6897
  /** The steps of one finished turn, as the runner handed them over when it ended. */
6507
6898
  /**
@@ -6606,19 +6997,20 @@ ${rationale}`}`);
6606
6997
  project: input.project,
6607
6998
  ...input.thread === void 0 ? {} : { thread: input.thread },
6608
6999
  ...input.channel === void 0 ? {} : { channel: input.channel },
7000
+ ...input.place === void 0 ? {} : { place: input.place },
6609
7001
  messages: [...input.messages]
6610
7002
  });
6611
7003
  });
6612
7004
  }
6613
7005
  /**
6614
7006
  * Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
6615
- * thread's or a channel's, named by `part` as `conversationPart` gives it.
7007
+ * thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
6616
7008
  */
6617
7009
  sessionDir(agent2, scope, part) {
6618
7010
  return part === void 0 ? this.paths.agentProject(agent2, scope) : this.paths.agentConversation(agent2, scope, part);
6619
7011
  }
6620
7012
  async readSessions(agent2, project2, part) {
6621
- const file = path6.join(this.sessionDir(agent2, project2, part), "sessions.json");
7013
+ const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
6622
7014
  return await exists(file) ? readJson(file, SessionsFileSchema) : {};
6623
7015
  }
6624
7016
  /**
@@ -6629,15 +7021,34 @@ ${rationale}`}`);
6629
7021
  await this.mutex.run(async () => {
6630
7022
  const dir = this.sessionDir(agent2, project2, part);
6631
7023
  await ensureDir(dir);
6632
- const file = path6.join(dir, "sessions.json");
7024
+ const file = path7.join(dir, "sessions.json");
6633
7025
  const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
6634
7026
  const kept = current.runner === void 0 || current.runner === runner2 ? current : {};
6635
7027
  await writeJson(file, { ...kept, [cli]: sessionId, runner: runner2 });
6636
7028
  });
6637
7029
  }
6638
- /** The last turn of one conversation: the scope's home, a channel's, or a thread's. */
7030
+ /**
7031
+ * Drops a conversation's session for a CLI, so its next turn starts a new one, when the record
7032
+ * still names `sessionId`; returns whether it did.
7033
+ */
7034
+ async dropSession(agent2, project2, cli, sessionId, part) {
7035
+ return this.mutex.run(async () => {
7036
+ const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
7037
+ if (!await exists(file)) {
7038
+ return false;
7039
+ }
7040
+ const current = await readJson(file, SessionsFileSchema);
7041
+ if (current[cli] !== sessionId) {
7042
+ return false;
7043
+ }
7044
+ const { [cli]: _dropped, ...kept } = current;
7045
+ await writeJson(file, kept);
7046
+ return true;
7047
+ });
7048
+ }
7049
+ /** The last turn of one conversation: the scope's home, a channel's, a project's, or a thread's. */
6639
7050
  async readLastTurn(agent2, project2, part) {
6640
- const file = path6.join(this.sessionDir(agent2, project2, part), "last-turn.json");
7051
+ const file = path7.join(this.sessionDir(agent2, project2, part), "last-turn.json");
6641
7052
  return await exists(file) ? readJson(file, TurnRecordSchema) : null;
6642
7053
  }
6643
7054
  /** The latest turn a citizen took in a scope, in any of its conversations there. */
@@ -6645,8 +7056,9 @@ ${rationale}`}`);
6645
7056
  let latest = await this.readLastTurn(agent2, project2);
6646
7057
  const place = this.paths.agentProject(agent2, project2);
6647
7058
  const parts = [
6648
- ...await listDirs(path6.join(place, "threads")),
6649
- ...(await listDirs(path6.join(place, "channels"))).map((name) => `#${name}`)
7059
+ ...await listDirs(path7.join(place, "threads")),
7060
+ ...(await listDirs(path7.join(place, "channels"))).map((name) => `#${name}`),
7061
+ ...(await listDirs(path7.join(place, "places"))).map((name) => `~${name}`)
6650
7062
  ];
6651
7063
  for (const part of parts) {
6652
7064
  const turn2 = await this.readLastTurn(agent2, project2, part);
@@ -6667,6 +7079,7 @@ ${rationale}`}`);
6667
7079
  project: parsed.project,
6668
7080
  ...parsed.thread === void 0 ? {} : { thread: parsed.thread },
6669
7081
  ...parsed.channel === void 0 ? {} : { channel: parsed.channel },
7082
+ ...parsed.place === void 0 ? {} : { place: parsed.place },
6670
7083
  trigger: parsed.trigger.kind,
6671
7084
  session: parsed.session,
6672
7085
  runner: parsed.runner
@@ -6696,7 +7109,8 @@ ${rationale}`}`);
6696
7109
  agent: parsed.agent,
6697
7110
  project: parsed.project,
6698
7111
  ...parsed.thread === void 0 ? {} : { thread: parsed.thread },
6699
- ...parsed.channel === void 0 ? {} : { channel: parsed.channel }
7112
+ ...parsed.channel === void 0 ? {} : { channel: parsed.channel },
7113
+ ...parsed.place === void 0 ? {} : { place: parsed.place }
6700
7114
  });
6701
7115
  }
6702
7116
  await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
@@ -6704,6 +7118,7 @@ ${rationale}`}`);
6704
7118
  project: parsed.project,
6705
7119
  ...parsed.thread === void 0 ? {} : { thread: parsed.thread },
6706
7120
  ...parsed.channel === void 0 ? {} : { channel: parsed.channel },
7121
+ ...parsed.place === void 0 ? {} : { place: parsed.place },
6707
7122
  trigger: parsed.trigger.kind,
6708
7123
  session: parsed.session,
6709
7124
  exitReason: parsed.exitReason,
@@ -6713,7 +7128,9 @@ ${rationale}`}`);
6713
7128
  model: parsed.model,
6714
7129
  summary: parsed.status?.summary ?? null,
6715
7130
  needsUserDecision: parsed.status?.needsUserDecision ?? false,
6716
- error: parsed.error
7131
+ error: parsed.error,
7132
+ ...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
7133
+ ...parsed.sessionDropped === true ? { sessionDropped: true } : {}
6717
7134
  });
6718
7135
  });
6719
7136
  }
@@ -6750,6 +7167,20 @@ ${rationale}`}`);
6750
7167
  return task2;
6751
7168
  });
6752
7169
  }
7170
+ /**
7171
+ * Posts the board's word on a task's work into the task's thread, such as commits a runner
7172
+ * could not put on its branch: as the board, with every `@name` written as a plain name, so it
7173
+ * wakes nobody and counts toward no heartbeat. Null, posting nothing, once the thread is closed.
7174
+ */
7175
+ async postTaskNotice(taskId, body) {
7176
+ return this.mutex.run(async () => {
7177
+ const found = await this.tryFindThread(taskId);
7178
+ if (found === null || found.thread.state !== "open") {
7179
+ return null;
7180
+ }
7181
+ return this.appendThreadMessage(SYSTEM_ACTOR.name, taskId, withoutMentions(body));
7182
+ });
7183
+ }
6753
7184
  /**
6754
7185
  * Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
6755
7186
  * the scheduler gives the citizen a closing turn there to decide about it. False, recording
@@ -6760,7 +7191,7 @@ ${rationale}`}`);
6760
7191
  const payload = {
6761
7192
  agent: input.agent,
6762
7193
  project: input.scope,
6763
- ...conversation.kind === "thread" ? { thread: conversation.id } : { channel: conversation.name }
7194
+ ...conversation.kind === "thread" ? { thread: conversation.id } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
6764
7195
  };
6765
7196
  return this.mutex.run(async () => {
6766
7197
  const earlier = (await this.events.readSince(null, Number.MAX_SAFE_INTEGER)).filter((event) => event.type === "workspace.leftovers" && Object.entries(payload).every(([key, value]) => event.payload[key] === value)).length;
@@ -6812,9 +7243,13 @@ ${rationale}`}`);
6812
7243
  }
6813
7244
  /**
6814
7245
  * Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
6815
- * (for one from before tasks had threads), or a channel archived.
7246
+ * (for one from before tasks had threads), a channel archived, or a project archived, which ends
7247
+ * every citizen's conversation of its channels in the society scope.
6816
7248
  */
6817
7249
  async conversationEnded(conversation) {
7250
+ if (conversation.kind === "place") {
7251
+ return !await this.projectLive(conversation.project);
7252
+ }
6818
7253
  if (conversation.kind === "channel") {
6819
7254
  const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
6820
7255
  return !await this.channelOpen(channelRef(place, conversation.name));
@@ -6888,11 +7323,11 @@ ${rationale}`}`);
6888
7323
  }
6889
7324
  /** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
6890
7325
  async readState(name, schema, fallback) {
6891
- const file = path6.join(this.paths.state(), `${name}.json`);
7326
+ const file = path7.join(this.paths.state(), `${name}.json`);
6892
7327
  return await exists(file) ? readJson(file, schema) : fallback;
6893
7328
  }
6894
7329
  async writeState(name, value) {
6895
- await this.mutex.run(() => writeJson(path6.join(this.paths.state(), `${name}.json`), value));
7330
+ await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
6896
7331
  }
6897
7332
  /** The project's dashboard, empty at revision 0 when it has none. */
6898
7333
  async readDashboard(slug) {
@@ -6909,7 +7344,7 @@ ${rationale}`}`);
6909
7344
  return { project: slug, revision: 0, body: "" };
6910
7345
  }
6911
7346
  const { data, body } = await readLooseMarkdown(file);
6912
- const revision = z15.number().int().min(0).safeParse(data["revision"]);
7347
+ const revision = z16.number().int().min(0).safeParse(data["revision"]);
6913
7348
  const updatedBy = NameSchema.safeParse(data["updatedBy"]);
6914
7349
  const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
6915
7350
  return {
@@ -6923,7 +7358,7 @@ ${rationale}`}`);
6923
7358
  async listRunners() {
6924
7359
  const runners = [];
6925
7360
  for (const file of await listFiles(this.paths.runners())) {
6926
- runners.push((await readMarkdown(path6.join(this.paths.runners(), file), RunnerSchema)).data);
7361
+ runners.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
6927
7362
  }
6928
7363
  return runners;
6929
7364
  }
@@ -6975,7 +7410,7 @@ ${rationale}`}`);
6975
7410
  return this.runnerTokens.get(hashToken(token)) ?? null;
6976
7411
  }
6977
7412
  runnerTokensFile() {
6978
- return path6.join(this.paths.state(), "runners.json");
7413
+ return path7.join(this.paths.state(), "runners.json");
6979
7414
  }
6980
7415
  async readRunnerTokens() {
6981
7416
  const file = this.runnerTokensFile();
@@ -7024,26 +7459,26 @@ ${rationale}`}`);
7024
7459
  async listHomeConflicts(agent2) {
7025
7460
  const home = this.paths.agent(agent2);
7026
7461
  const found = [];
7027
- const walk = async (dir, prefix) => {
7028
- for (const entry of await readdir3(dir, { withFileTypes: true }).catch(() => [])) {
7462
+ const walk2 = async (dir, prefix) => {
7463
+ for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
7029
7464
  if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
7030
7465
  continue;
7031
7466
  }
7032
7467
  const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
7033
7468
  if (entry.isDirectory()) {
7034
- await walk(path6.join(dir, entry.name), relative);
7469
+ await walk2(path7.join(dir, entry.name), relative);
7035
7470
  continue;
7036
7471
  }
7037
7472
  const file = entry.isFile() ? conflictCopyOf(relative) : null;
7038
7473
  if (file !== null) {
7039
- const since = await this.homes.changedAt(home, relative) ?? (await stat3(path6.join(home, relative)).catch(() => null))?.mtime.toISOString();
7474
+ const since = await this.homes.changedAt(home, relative) ?? (await stat3(path7.join(home, relative)).catch(() => null))?.mtime.toISOString();
7040
7475
  if (since !== void 0) {
7041
7476
  found.push({ path: relative, file, since });
7042
7477
  }
7043
7478
  }
7044
7479
  }
7045
7480
  };
7046
- await walk(home, "");
7481
+ await walk2(home, "");
7047
7482
  return found.toSorted((a, b) => a.path.localeCompare(b.path));
7048
7483
  }
7049
7484
  /** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
@@ -7120,7 +7555,7 @@ ${rationale}`}`);
7120
7555
  async writeTurnRecord(record) {
7121
7556
  const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
7122
7557
  await ensureDir(dir);
7123
- await writeJson(path6.join(dir, "last-turn.json"), record);
7558
+ await writeJson(path7.join(dir, "last-turn.json"), record);
7124
7559
  }
7125
7560
  // ---------------------------------------------------------------------------------------------
7126
7561
  // Internals
@@ -7183,7 +7618,7 @@ ${charter.purpose}
7183
7618
 
7184
7619
  ${charter.purpose}
7185
7620
 
7186
- 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.
7621
+ 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.
7187
7622
  ` + (seed.length === 0 ? "" : `
7188
7623
  ${SEED_INSTRUCTIONS_HEADING}
7189
7624
 
@@ -7200,7 +7635,7 @@ ${seed}
7200
7635
  continue;
7201
7636
  }
7202
7637
  const file = this.paths.agentRole(agent2.name);
7203
- const body = await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
7638
+ const body = await exists(file) ? (await readMarkdown(file, z16.record(z16.string(), z16.unknown()))).body : "";
7204
7639
  const at = body.indexOf(`
7205
7640
  ${SEED_INSTRUCTIONS_HEADING}
7206
7641
  `);
@@ -7239,8 +7674,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7239
7674
  async ensureAgentProject(name, slug) {
7240
7675
  const dir = this.paths.agentProject(name, slug);
7241
7676
  await ensureDir(dir);
7242
- if (!await exists(path6.join(dir, "sessions.json"))) {
7243
- await writeJson(path6.join(dir, "sessions.json"), {});
7677
+ if (!await exists(path7.join(dir, "sessions.json"))) {
7678
+ await writeJson(path7.join(dir, "sessions.json"), {});
7244
7679
  }
7245
7680
  }
7246
7681
  /**
@@ -7272,7 +7707,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7272
7707
  const at = turn2?.endedAt ?? turn2?.startedAt;
7273
7708
  if (turn2 !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
7274
7709
  lastTurnAt = at;
7275
- lastTurnOutcome = `${turn2.trigger.kind} on ${scope}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
7710
+ lastTurnOutcome = `${turn2.trigger.kind} on ${scope}${turnConversation(turn2)}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
7276
7711
  lastModel = turn2.model ?? void 0;
7277
7712
  }
7278
7713
  }
@@ -7492,7 +7927,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7492
7927
  const file = this.paths.societySkillFile(skill2.name);
7493
7928
  const replaced = await exists(file);
7494
7929
  const taken = proposal2.skillFiles;
7495
- const staged = path6.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
7930
+ const staged = path7.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
7496
7931
  if (skill2.body === void 0 && (taken === void 0 || !await exists(staged))) {
7497
7932
  throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
7498
7933
  }
@@ -7555,8 +7990,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7555
7990
  const dir = this.paths.proposalFiles(proposal2.id);
7556
7991
  await rm2(dir, { recursive: true, force: true });
7557
7992
  for (const entry of taken.files) {
7558
- const target = path6.join(dir, ...entry.path.split("/"));
7559
- await ensureDir(path6.dirname(target));
7993
+ const target = path7.join(dir, ...entry.path.split("/"));
7994
+ await ensureDir(path7.dirname(target));
7560
7995
  await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
7561
7996
  await chmod2(target, entry.executable ? 493 : 420);
7562
7997
  }
@@ -7614,7 +8049,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7614
8049
  const where = {
7615
8050
  scope: turn2.project,
7616
8051
  ...turn2.thread === void 0 ? {} : { thread: turn2.thread },
7617
- ...turn2.channel === void 0 ? {} : { channel: turn2.channel }
8052
+ ...turn2.channel === void 0 ? {} : { channel: turn2.channel },
8053
+ ...turn2.place === void 0 ? {} : { place: turn2.place }
7618
8054
  };
7619
8055
  await this.mutex.run(async () => {
7620
8056
  for (const proposal2 of await this.listProposals()) {
@@ -7646,16 +8082,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7646
8082
  if (!isSafeRelativePath(entry.path)) {
7647
8083
  continue;
7648
8084
  }
7649
- const bytes = await readFile4(path6.join(dir, ...entry.path.split("/"))).catch(() => null);
7650
- const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
8085
+ const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
7651
8086
  files.push({
7652
8087
  ...entry,
7653
- text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
7654
- truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
8088
+ ...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
7655
8089
  });
7656
8090
  }
7657
8091
  return files;
7658
8092
  }
8093
+ /**
8094
+ * A society skill's folder as it is published, with each file's text, for the skill's view; an
8095
+ * unknown skill, or a folder without a SKILL.md, is not found.
8096
+ */
8097
+ async readSocietySkillFiles(name) {
8098
+ const files = await readSkillFolder(this.paths.societySkill(NameSchema.parse(name)));
8099
+ if (files === null) {
8100
+ throw new BoardError("NOT_FOUND", `the society has no skill ${name}`);
8101
+ }
8102
+ return files;
8103
+ }
7659
8104
  async validateAddAgent(input) {
7660
8105
  if (await exists(this.paths.agentFile(input.name))) {
7661
8106
  throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
@@ -7786,7 +8231,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7786
8231
  this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn2) => turn2.channel === channel && turn2.scope === scope || turn2.thread !== void 0 && closing.has(turn2.thread));
7787
8232
  const threadsClosed = [];
7788
8233
  for (const file of await listFiles(threads)) {
7789
- const doc = await readMarkdown(path6.join(threads, file), ThreadFrontmatterSchema);
8234
+ const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
7790
8235
  if (doc.data.channel !== ref || doc.data.state !== "open") {
7791
8236
  continue;
7792
8237
  }
@@ -7892,7 +8337,8 @@ ${charter.purpose}
7892
8337
  }
7893
8338
  }
7894
8339
  /**
7895
- * Every member leaves, every open thread on its channels closes, and the project is marked; its
8340
+ * Every member leaves, every open thread on its channels closes, every citizen's conversation of
8341
+ * its channels in the society scope ends with the crons there, and the project is marked; its
7896
8342
  * files, repository, and history stay. The notice is the board's, since the reason is free text
7897
8343
  * a proposer wrote and a board-authored mention would wake whoever it names.
7898
8344
  */
@@ -7917,7 +8363,7 @@ ${charter.purpose}
7917
8363
  const threads = this.paths.threads(slug);
7918
8364
  const threadsClosed = [];
7919
8365
  for (const file of await listFiles(threads)) {
7920
- const doc = await readMarkdown(path6.join(threads, file), ThreadFrontmatterSchema);
8366
+ const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
7921
8367
  if (doc.data.state !== "open") {
7922
8368
  continue;
7923
8369
  }
@@ -7935,7 +8381,7 @@ ${charter.purpose}
7935
8381
  });
7936
8382
  threadsClosed.push(doc.data.id);
7937
8383
  }
7938
- await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
8384
+ await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.place === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
7939
8385
  const project2 = await this.updateProject(slug, (p) => ({
7940
8386
  ...p,
7941
8387
  members: [],
@@ -7992,10 +8438,10 @@ ${charter.purpose}
7992
8438
  ...project2,
7993
8439
  members: project2.members.filter((member) => member !== name)
7994
8440
  }));
7995
- const sessions = path6.join(this.paths.agentProject(name, slug), "sessions.json");
8441
+ const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
7996
8442
  if (await exists(sessions)) {
7997
8443
  const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
7998
- await rename3(sessions, path6.join(this.paths.agentProject(name, slug), archived));
8444
+ await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
7999
8445
  }
8000
8446
  }
8001
8447
  this.tokenIndex.delete(current.tokenHash);
@@ -8075,6 +8521,17 @@ ${charter.purpose}
8075
8521
  return false;
8076
8522
  }
8077
8523
  }
8524
+ /**
8525
+ * Whether a project exists and is not archived, so citizens' conversations of its channels in
8526
+ * the society scope are still going.
8527
+ */
8528
+ async projectLive(slug) {
8529
+ try {
8530
+ return (await this.readProject(slug)).archived === void 0;
8531
+ } catch {
8532
+ return false;
8533
+ }
8534
+ }
8078
8535
  /**
8079
8536
  * A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
8080
8537
  * and its project is not archived.
@@ -8325,7 +8782,7 @@ ${charter.purpose}
8325
8782
  if (since !== null && id <= since || upto !== void 0 && id > upto) {
8326
8783
  continue;
8327
8784
  }
8328
- const doc = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
8785
+ const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
8329
8786
  messages.push({ ...doc.data, body: doc.body });
8330
8787
  }
8331
8788
  return messages;
@@ -8382,7 +8839,7 @@ function globals() {
8382
8839
  function print(value, human) {
8383
8840
  console.log(globals().json ? JSON.stringify(value, null, 2) : human());
8384
8841
  }
8385
- async function open2() {
8842
+ async function open3() {
8386
8843
  return Board.open(globals().data);
8387
8844
  }
8388
8845
  async function actorFor(board, as) {
@@ -8428,7 +8885,7 @@ user.command("token").description("Issue the user a new token and print it once;
8428
8885
  if (!opts.rotate) {
8429
8886
  throw new BoardError("VALIDATION", "the board keeps only a hash of the user token, so it cannot show it again; `user token --rotate` issues a new one");
8430
8887
  }
8431
- const board = await open2();
8888
+ const board = await open3();
8432
8889
  const userToken = await board.rotateUserToken();
8433
8890
  print({ userToken }, () => [
8434
8891
  `User token (shown once): ${userToken}`,
@@ -8437,7 +8894,7 @@ user.command("token").description("Issue the user a new token and print it once;
8437
8894
  });
8438
8895
  var project = program.command("project").description("Manage projects");
8439
8896
  project.command("add <slug>").description("Add a project with its default channels").option("--name <name>", "display name").option("--repo <url>", "git remote").option("--on-done <effect>", "what finishing a task does: none, or merge its branch").action(async (slug, opts) => {
8440
- const board = await open2();
8897
+ const board = await open3();
8441
8898
  const added = await board.addProject(board.userActor(), {
8442
8899
  slug,
8443
8900
  name: opts.name,
@@ -8447,7 +8904,7 @@ project.command("add <slug>").description("Add a project with its default channe
8447
8904
  print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
8448
8905
  });
8449
8906
  project.command("configure <slug>").description("Set a project's completion effect").requiredOption("--on-done <effect>", "none, or merge").option("--as <agent>", "act as this agent instead of the user").action(async (slug, opts) => {
8450
- const board = await open2();
8907
+ const board = await open3();
8451
8908
  const configured = await board.configureProject(await actorFor(board, opts.as), {
8452
8909
  project: slug,
8453
8910
  on_done: CompletionEffectSchema.parse(opts.onDone)
@@ -8455,7 +8912,7 @@ project.command("configure <slug>").description("Set a project's completion effe
8455
8912
  print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
8456
8913
  });
8457
8914
  project.command("archive <slug>").description("Archive a project with no task in play: its members leave and its threads close").requiredOption("--reason <text>", "why the project is archived").action(async (slug, opts) => {
8458
- const board = await open2();
8915
+ const board = await open3();
8459
8916
  const archived = await board.archiveProject(board.userActor(), {
8460
8917
  project: slug,
8461
8918
  reason: opts.reason
@@ -8463,13 +8920,13 @@ project.command("archive <slug>").description("Archive a project with no task in
8463
8920
  print(archived, () => `Project ${archived.slug} archived`);
8464
8921
  });
8465
8922
  project.command("list").description("List projects").action(async () => {
8466
- const board = await open2();
8923
+ const board = await open3();
8467
8924
  const projects = await board.listProjects();
8468
8925
  print(projects, () => projects.map((p) => p.archived === void 0 ? `${p.slug} ${p.name} members: ${p.members.join(", ") || "none"}` : `${p.slug} ${p.name} archived ${p.archived.at} by ${p.archived.by}`).join("\n"));
8469
8926
  });
8470
8927
  var runner = program.command("runner").description("Manage runners");
8471
8928
  runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
8472
- const board = await open2();
8929
+ const board = await open3();
8473
8930
  const { runner: added, token } = await board.addRunner(board.userActor(), name);
8474
8931
  print({ runner: added, token }, () => [
8475
8932
  `Runner ${added.name} added`,
@@ -8478,13 +8935,13 @@ runner.command("add <name>").description("Register a runner and print its token
8478
8935
  ].join("\n"));
8479
8936
  });
8480
8937
  runner.command("list").description("List runners and what they offer").action(async () => {
8481
- const board = await open2();
8938
+ const board = await open3();
8482
8939
  const runners = await board.listRunners();
8483
8940
  print(runners, () => runners.length === 0 ? "No runners yet; add one with `runner add <name>`." : runners.map((each) => `${each.name} ${each.status} ${each.os}${each.version === void 0 ? "" : ` v${each.version}`} ${each.clis.join(",") || "no CLIs yet"}${each.capabilities.length === 0 ? "" : ` ${each.capabilities.join(",")}`}`).join("\n"));
8484
8941
  });
8485
8942
  var agent = program.command("agent").description("Manage agents");
8486
8943
  agent.command("add <name>").description("Add an agent and print its board token once").requiredOption("--role <role>", "role charter name").option("--cli <cli>", "claude or codex").option("--model <model>", "model to run the CLI with; the CLI's own default otherwise").option("--runner <name>", "runner preferred for its turns; any with its CLI otherwise").option("-p, --project <slug...>", "project memberships").action(async (name, opts) => {
8487
- const board = await open2();
8944
+ const board = await open3();
8488
8945
  const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
8489
8946
  const { agent: added, token } = await board.addAgent(board.userActor(), {
8490
8947
  name,
@@ -8497,7 +8954,7 @@ agent.command("add <name>").description("Add an agent and print its board token
8497
8954
  print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
8498
8955
  });
8499
8956
  agent.command("runner <name> [runner]").description("Show or set the runner a citizen's work outside any project runs on; `none` pins it again on its next turn").action(async (name, chosen) => {
8500
- const board = await open2();
8957
+ const board = await open3();
8501
8958
  if (chosen === void 0) {
8502
8959
  const current = await board.readAgent(name);
8503
8960
  print({ agent: name, homeRunner: current.homeRunner ?? null }, () => current.homeRunner === void 0 ? `${name} is not pinned; its next turn outside a project pins it` : `${name} runs on ${current.homeRunner}`);
@@ -8507,7 +8964,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
8507
8964
  print({ agent: name, homeRunner: updated.homeRunner ?? null }, () => updated.homeRunner === void 0 ? `${name} will be pinned on its next turn` : `${name} runs on ${updated.homeRunner} from its next turn`);
8508
8965
  });
8509
8966
  agent.command("list").description("List agents").action(async () => {
8510
- const board = await open2();
8967
+ const board = await open3();
8511
8968
  const agents = await board.listAgents();
8512
8969
  const members = await board.listMembers();
8513
8970
  print(agents, () => agents.map((a) => {
@@ -8516,28 +8973,28 @@ agent.command("list").description("List agents").action(async () => {
8516
8973
  }).join("\n"));
8517
8974
  });
8518
8975
  agent.command("model <name> [model]").description("Set the model a member's turns run with, from its next turn; no model means the CLI's default").action(async (name, model) => {
8519
- const board = await open2();
8976
+ const board = await open3();
8520
8977
  const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
8521
8978
  print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
8522
8979
  });
8523
8980
  agent.command("effort <name> [effort]").description("Set the reasoning effort a member's turns run with, from its next turn, such as high; no effort means the model's default").action(async (name, effort) => {
8524
- const board = await open2();
8981
+ const board = await open3();
8525
8982
  const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
8526
8983
  print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
8527
8984
  });
8528
8985
  agent.command("retire <name>").description("Retire a member: no more wakes, claims released, token revoked, sessions archived").requiredOption("--reason <text>", "why").action(async (name, opts) => {
8529
- const board = await open2();
8986
+ const board = await open3();
8530
8987
  const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
8531
8988
  print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
8532
8989
  });
8533
8990
  var proposal = program.command("proposal").description("Governance proposals");
8534
8991
  proposal.command("list").description("List proposals").action(async () => {
8535
- const board = await open2();
8992
+ const board = await open3();
8536
8993
  const proposals = await board.listProposals();
8537
8994
  print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
8538
8995
  });
8539
8996
  proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
8540
- const board = await open2();
8997
+ const board = await open3();
8541
8998
  const found = await board.readProposal(id);
8542
8999
  print(found, () => [
8543
9000
  `${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
@@ -8547,7 +9004,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
8547
9004
  ].filter((line) => line.length > 0).join("\n"));
8548
9005
  });
8549
9006
  proposal.command("create").description("Propose a member, role, channel, reallocation, or retirement").requiredOption("--kind <kind>", "member, role, channel, reallocation, or retirement").requiredOption("--charter <json>", "the charter as a JSON object").option("--rationale <text>", "why", "").option("--as <agent>", "act as this agent instead of the user").action(async (opts) => {
8550
- const board = await open2();
9007
+ const board = await open3();
8551
9008
  const created = await board.propose(await actorFor(board, opts.as), {
8552
9009
  kind: ProposalKindSchema.parse(opts.kind),
8553
9010
  charter: parseCharter(opts.charter),
@@ -8556,7 +9013,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
8556
9013
  print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
8557
9014
  });
8558
9015
  proposal.command("approve <id>").description("Approve a proposal; the board provisions it").option("--reason <text>", "a note for the record").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8559
- const board = await open2();
9016
+ const board = await open3();
8560
9017
  const decision = await board.approve(await actorFor(board, opts.as), {
8561
9018
  proposal_id: id,
8562
9019
  ...opts.reason === void 0 ? {} : { reason: opts.reason }
@@ -8565,7 +9022,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
8565
9022
  print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
8566
9023
  });
8567
9024
  proposal.command("reject <id>").description("Reject a proposal with a reason").requiredOption("--reason <text>", "why").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8568
- const board = await open2();
9025
+ const board = await open3();
8569
9026
  const decision = await board.reject(await actorFor(board, opts.as), {
8570
9027
  proposal_id: id,
8571
9028
  reason: opts.reason
@@ -8574,14 +9031,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
8574
9031
  });
8575
9032
  var role = program.command("role").description("Role charters");
8576
9033
  role.command("list").description("List role charters").action(async () => {
8577
- const board = await open2();
9034
+ const board = await open3();
8578
9035
  const roles = await board.listRoles();
8579
9036
  print(roles, () => roles.map((r) => `${r.name} replicas up to ${r.maxReplicas} backlog threshold ${r.backlogThreshold} wakes on ${r.wakeTriggers.join(", ") || "direct wakes only"}`).join("\n"));
8580
9037
  });
8581
9038
  role.command("add <name>").description("Write a new role charter directly as the user").requiredOption("--purpose <text>", "what the role is for; its members read this every turn").option("--verbs <verbs...>", "board verbs granted; the member verbs by default").option("--triggers <triggers...>", "wakes it opts into: user_post, ops_event, heartbeat", [
8582
9039
  "heartbeat"
8583
9040
  ]).action(async (name, opts) => {
8584
- const board = await open2();
9041
+ const board = await open3();
8585
9042
  const added = await board.setRoleCharter(board.userActor(), {
8586
9043
  name,
8587
9044
  purpose: opts.purpose,
@@ -8591,7 +9048,7 @@ role.command("add <name>").description("Write a new role charter directly as the
8591
9048
  print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
8592
9049
  });
8593
9050
  role.command("set <name>").description("Change a charter directly as the user: scaling cap, threshold, or purpose").option("--max-replicas <n>", "active members of the role per project the scheduler may reach").option("--backlog-threshold <n>", "load per member that adds a replica").option("--purpose <text>", "the charter's purpose").action(async (name, opts) => {
8594
- const board = await open2();
9051
+ const board = await open3();
8595
9052
  const current = await board.readRole(name);
8596
9053
  const updated = await board.setRoleCharter(board.userActor(), {
8597
9054
  ...current,
@@ -8603,7 +9060,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
8603
9060
  });
8604
9061
  var channelCommand = program.command("channel").description("Channels");
8605
9062
  channelCommand.command("add <ref>").description("Open a channel: <name> for the society or <project>/<name>").requiredOption("--purpose <text>", "what the channel is for").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
8606
- const board = await open2();
9063
+ const board = await open3();
8607
9064
  const parsed = parseChannelRef(ref);
8608
9065
  const added = await board.addChannel(await actorFor(board, opts.as), {
8609
9066
  project: parsed.project,
@@ -8613,7 +9070,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
8613
9070
  print({ channel: added }, () => `Channel ${added} opened`);
8614
9071
  });
8615
9072
  channelCommand.command("archive <ref>").description("Archive a channel whose workstream is done, once no task filed there is in play").requiredOption("--reason <text>", "why its work is done").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
8616
- const board = await open2();
9073
+ const board = await open3();
8617
9074
  const archived = await board.archiveChannel(await actorFor(board, opts.as), {
8618
9075
  channel: ref,
8619
9076
  reason: opts.reason
@@ -8621,12 +9078,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
8621
9078
  print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
8622
9079
  });
8623
9080
  program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
8624
- const board = await open2();
9081
+ const board = await open3();
8625
9082
  const signals = await board.listSignals(Number(opts.limit));
8626
9083
  print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
8627
9084
  });
8628
9085
  program.command("post <channel> <body>").description("Post a message to a channel, or into one of its threads").option("--as <agent>", "act as this agent instead of the user").option("--thread <threadId>", "post into the thread, which must hang off the channel").action(async (channel, body, opts) => {
8629
- const board = await open2();
9086
+ const board = await open3();
8630
9087
  const who = await actorFor(board, opts.as);
8631
9088
  const message = await board.postMessage(who, {
8632
9089
  channel,
@@ -8636,13 +9093,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
8636
9093
  print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
8637
9094
  });
8638
9095
  program.command("ask <text>").description("Post to the society's general channel as the user; the front desk routes it").action(async (text) => {
8639
- const board = await open2();
9096
+ const board = await open3();
8640
9097
  const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
8641
9098
  print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
8642
9099
  });
8643
9100
  var task = program.command("task").description("Manage tasks");
8644
9101
  task.command("create <project> <title>").description("Create a task with a plan, or with one stage anyone in the project may take").option("--body <text>", "task body", "").option("--parent <id>", "parent task id").option("--plan <json>", `stages, for example '[{"name":"analysis","role":"analyst"}]'`).option("--channel <name>", "the project's channel to file it in, such as a release's", "general").option("--as <agent>", "act as this agent instead of the user").action(async (projectSlug, title, opts) => {
8645
- const board = await open2();
9102
+ const board = await open3();
8646
9103
  const who = await actorFor(board, opts.as);
8647
9104
  const created = await board.createTask(who, {
8648
9105
  project: projectSlug,
@@ -8658,17 +9115,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
8658
9115
  ].join("\n"));
8659
9116
  });
8660
9117
  task.command("claim <id>").description("Hold the task's current stage").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8661
- const board = await open2();
9118
+ const board = await open3();
8662
9119
  const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
8663
9120
  print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
8664
9121
  });
8665
9122
  task.command("release <id>").description("Let go of the stage held on a task").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8666
- const board = await open2();
9123
+ const board = await open3();
8667
9124
  const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
8668
9125
  print(released, () => `Stage ${released.stage} of task ${released.id} released`);
8669
9126
  });
8670
9127
  task.command("advance <id>").description("Finish the current stage; past the last one the task is done").option("--note <text>", "what was done, posted to the task's thread").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8671
- const board = await open2();
9128
+ const board = await open3();
8672
9129
  const advanced = await board.advanceTask(await actorFor(board, opts.as), {
8673
9130
  task_id: id,
8674
9131
  ...opts.note === void 0 ? {} : { note: opts.note }
@@ -8677,7 +9134,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
8677
9134
  ${describePlan(advanced)}`);
8678
9135
  });
8679
9136
  task.command("plan <id> <stages>").description("Reshape the stages ahead: pass existing stages with their id to keep them, new ones without").option("--on-done <effect>", "override the completion effect for this task").option("--as <agent>", "act as this agent instead of the user").action(async (id, stages, opts) => {
8680
- const board = await open2();
9137
+ const board = await open3();
8681
9138
  const planned = await board.planTask(await actorFor(board, opts.as), {
8682
9139
  task_id: id,
8683
9140
  stages: PlanEditSchema.parse(JSON.parse(stages)),
@@ -8686,7 +9143,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
8686
9143
  print(planned, () => describePlan(planned));
8687
9144
  });
8688
9145
  task.command("update <id>").description("Send a task back to an earlier stage, abandon it, or set blockers, with a note for its thread").option("--stage <stageId>", "move back to this earlier stage").option("--abandon", "abandon the task", false).option("--note <text>", "a note, posted to the task's thread").option("--blocked-by <ids...>", "task ids this task waits on").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8689
- const board = await open2();
9146
+ const board = await open3();
8690
9147
  const updated = await board.updateTask(await actorFor(board, opts.as), {
8691
9148
  task_id: id,
8692
9149
  ...opts.stage === void 0 ? {} : { stage: opts.stage },
@@ -8697,7 +9154,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
8697
9154
  print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
8698
9155
  });
8699
9156
  task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
8700
- const board = await open2();
9157
+ const board = await open3();
8701
9158
  const found = await board.getTask(board.userActor(), { task_id: id });
8702
9159
  const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
8703
9160
  const said = found.messages.map((message) => {
@@ -8717,7 +9174,7 @@ ${message.body.trim()}
8717
9174
  ].join("\n"));
8718
9175
  });
8719
9176
  task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
8720
- const board = await open2();
9177
+ const board = await open3();
8721
9178
  const tasks = await board.listTasks(projectSlug);
8722
9179
  print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
8723
9180
  const stage = t.stages[stageIndex(t, t.stage)];
@@ -8726,7 +9183,7 @@ task.command("list <project>").description("List a project's tasks with their cu
8726
9183
  });
8727
9184
  var thread = program.command("thread").description("Manage threads");
8728
9185
  thread.command("open [taskId]").description("Open a thread on a task, on a proposal, or on a channel with a title").option("--proposal <id>", "open the proposal's thread").option("--channel <ref>", "the channel it hangs off; required for a topic").option("--title <text>", "the thread's title; required for a topic").option("--as <agent>", "act as this agent instead of the user").action(async (taskId, opts) => {
8729
- const board = await open2();
9186
+ const board = await open3();
8730
9187
  const opened = await board.openThread(await actorFor(board, opts.as), {
8731
9188
  ...taskId === void 0 ? {} : { task_id: taskId },
8732
9189
  ...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
@@ -8736,7 +9193,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
8736
9193
  print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
8737
9194
  });
8738
9195
  thread.command("close <threadId>").description("Close a thread with a summary posted to its channel").requiredOption("--summary <text>", "closure summary").option("--as <agent>", "act as this agent instead of the user").action(async (threadId, opts) => {
8739
- const board = await open2();
9196
+ const board = await open3();
8740
9197
  const summary = await board.closeThread(await actorFor(board, opts.as), {
8741
9198
  thread_id: threadId,
8742
9199
  summary: opts.summary
@@ -8744,7 +9201,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
8744
9201
  print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
8745
9202
  });
8746
9203
  thread.command("list").description("List threads, the society's first, then each project's").option("--open", "only open threads", false).action(async (opts) => {
8747
- const board = await open2();
9204
+ const board = await open3();
8748
9205
  const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
8749
9206
  print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
8750
9207
  const about = t.subject === void 0 ? "topic" : t.subject.kind;
@@ -8752,7 +9209,7 @@ thread.command("list").description("List threads, the society's first, then each
8752
9209
  }).join("\n"));
8753
9210
  });
8754
9211
  thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
8755
- const board = await open2();
9212
+ const board = await open3();
8756
9213
  const found = await board.readThread(threadId);
8757
9214
  const messages = await board.listThread(threadId);
8758
9215
  print({ thread: found, messages }, () => [
@@ -8763,17 +9220,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
8763
9220
  ].join("\n"));
8764
9221
  });
8765
9222
  program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
8766
- const board = await open2();
9223
+ const board = await open3();
8767
9224
  await board.setPaused(board.userActor(), true);
8768
9225
  print({ paused: true }, () => "Society paused");
8769
9226
  });
8770
9227
  program.command("resume").description("Allow wakeups again").action(async () => {
8771
- const board = await open2();
9228
+ const board = await open3();
8772
9229
  await board.setPaused(board.userActor(), false);
8773
9230
  print({ paused: false }, () => "Society resumed");
8774
9231
  });
8775
9232
  program.command("society").description("The society's settings").command("timezone [zone]").description("Show the society's time zone, or set it: crons that name none are read in it").action(async (zone) => {
8776
- const board = await open2();
9233
+ const board = await open3();
8777
9234
  const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
8778
9235
  print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
8779
9236
  });
@@ -8781,7 +9238,8 @@ function describeCron(cron2, timeZone) {
8781
9238
  const where = [
8782
9239
  cron2.scope,
8783
9240
  cron2.thread,
8784
- cron2.channel === void 0 ? void 0 : `#${cron2.channel}`
9241
+ cron2.channel === void 0 ? void 0 : `#${cron2.channel}`,
9242
+ cron2.place === void 0 ? void 0 : `~${cron2.place}`
8785
9243
  ].filter((part) => part !== void 0).join("/");
8786
9244
  const next = nextFire(cron2, timeZone);
8787
9245
  const state = cron2.ended !== void 0 ? `ended (${cron2.ended.reason})` : cron2.paused !== void 0 ? "paused" : next === null ? "no time to come" : `next ${next.toISOString()}`;
@@ -8789,13 +9247,13 @@ function describeCron(cron2, timeZone) {
8789
9247
  }
8790
9248
  var cron = program.command("cron").description("Crons that wake citizens on a clock");
8791
9249
  cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
8792
- const board = await open2();
9250
+ const board = await open3();
8793
9251
  const { timezone } = await board.society();
8794
9252
  const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
8795
9253
  print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
8796
9254
  });
8797
9255
  cron.command("add <agent> <title>").description("Set a cron that wakes a citizen, with a note on what to do then").requiredOption("--note <text>", "what the citizen does when it fires").option("--cron <expression>", "five fields: minute hour day-of-month month day-of-week").option("--timezone <zone>", "the zone the expression is read in; the society's otherwise").option("--at <time>", "one time to fire, with its offset, instead of --cron").option("--project <slug>", "where it fires: a project the citizen is in, or society").option("--thread <id>", "an open thread there whose conversation it fires in").option("--channel <name>", "an open channel there, other than general, whose conversation it fires in").option("--as <agent>", "act as this agent instead of the user").action(async (agentName, title, opts) => {
8798
- const board = await open2();
9256
+ const board = await open3();
8799
9257
  const created = await board.createCron(await actorFor(board, opts.as), {
8800
9258
  title,
8801
9259
  note: opts.note,
@@ -8815,7 +9273,7 @@ for (const [name, paused] of [
8815
9273
  ["resume", false]
8816
9274
  ]) {
8817
9275
  cron.command(`${name} <id>`).description(paused ? "Pause a cron" : "Resume a paused cron from its next time").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8818
- const board = await open2();
9276
+ const board = await open3();
8819
9277
  const updated = await board.updateCron(await actorFor(board, opts.as), {
8820
9278
  cron_id: id,
8821
9279
  paused
@@ -8825,7 +9283,7 @@ for (const [name, paused] of [
8825
9283
  });
8826
9284
  }
8827
9285
  cron.command("remove <id>").description("End a cron for good").requiredOption("--reason <text>", "why it ends").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
8828
- const board = await open2();
9286
+ const board = await open3();
8829
9287
  const removed = await board.removeCron(await actorFor(board, opts.as), {
8830
9288
  cron_id: id,
8831
9289
  reason: opts.reason
@@ -8834,12 +9292,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
8834
9292
  });
8835
9293
  var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
8836
9294
  knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
8837
- const board = await open2();
9295
+ const board = await open3();
8838
9296
  const topics = await board.listKnowledge(projectSlug ?? null);
8839
9297
  print(topics, () => topics.length === 0 ? "No knowledge yet." : topics.map((t) => `${t.topic} revision ${t.revision}, updated by ${t.updatedBy} at ${t.updatedAt}`).join("\n"));
8840
9298
  });
8841
9299
  knowledge.command("show <topic>").description("Print a knowledge topic").option("--project <slug>", "the project's topic instead of the society's").action(async (topic, opts) => {
8842
- const board = await open2();
9300
+ const board = await open3();
8843
9301
  const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
8844
9302
  if (found === void 0) {
8845
9303
  throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
@@ -8847,8 +9305,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
8847
9305
  print(found, () => found.body);
8848
9306
  });
8849
9307
  knowledge.command("write <topic>").description("Write a knowledge topic as the user, from a file or standard input").requiredOption("--revision <n>", "the revision it replaces, as knowledge list shows it; 0 for a new topic").option("--project <slug>", "the project's topic instead of the society's").option("--file <path>", "read the body from this file instead of standard input").option("--as <agent>", "act as this agent instead of the user").action(async (topic, opts) => {
8850
- const board = await open2();
8851
- const body = opts.file === void 0 ? await readStdin() : await readFile5(path7.resolve(opts.file), "utf8");
9308
+ const board = await open3();
9309
+ const body = opts.file === void 0 ? await readStdin() : await readFile5(path8.resolve(opts.file), "utf8");
8852
9310
  const written = await board.writeKnowledge(await actorFor(board, opts.as), {
8853
9311
  project: opts.project ?? null,
8854
9312
  topic,
@@ -8859,7 +9317,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
8859
9317
  });
8860
9318
  var skill = program.command("skill").description("Skills promoted to the society");
8861
9319
  skill.command("list").description("List the society's skills, and each member's own").action(async () => {
8862
- const board = await open2();
9320
+ const board = await open3();
8863
9321
  const society = await board.listSocietySkills();
8864
9322
  const own = [];
8865
9323
  for (const member of await board.listAgents()) {
@@ -8874,7 +9332,7 @@ skill.command("list").description("List the society's skills, and each member's
8874
9332
  ].join("\n") || "No skills yet.");
8875
9333
  });
8876
9334
  skill.command("remove <name>").description("Remove a skill from the society's skills; its text is set aside, not destroyed").requiredOption("--reason <text>", "why it is wrong, superseded, or no longer used").option("--as <agent>", "act as this agent instead of the user").action(async (name, opts) => {
8877
- const board = await open2();
9335
+ const board = await open3();
8878
9336
  const removed = await board.removeSkill(await actorFor(board, opts.as), {
8879
9337
  name,
8880
9338
  reason: opts.reason
@@ -8883,13 +9341,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
8883
9341
  });
8884
9342
  var turn = program.command("turn").description("Turn operations");
8885
9343
  turn.command("digest <agent>").description("Show the unread messages the agent's next turn opens with, in one scope or all; the cursors stay put").option("--limit <n>", "maximum messages", "50").option("--project <slug>", "the scope whose turn to show: a project, or society").action(async (agentName, opts) => {
8886
- const board = await open2();
9344
+ const board = await open3();
8887
9345
  const actor = await board.actorFor(agentName);
8888
9346
  const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
8889
9347
  print(digest, () => digest.messages.length === 0 ? "No unread messages." : digest.messages.map((m) => `[${m.ts}] ${m.channel}${m.thread === void 0 ? "" : ` (thread ${m.thread})`} @${m.author}: ${m.body.trim()}`).join("\n"));
8890
9348
  });
8891
9349
  turn.command("run <agent>").description("Enqueue a manual wake for an agent on a project, or a reflection turn").requiredOption("--project <slug>", "project to act on, or society for a society-scope turn").option("--reason <text>", "why", "manual wake from the admin CLI").option("--reflect", "a reflection turn instead of a working turn", false).action(async (agentName, opts) => {
8892
- const board = await open2();
9350
+ const board = await open3();
8893
9351
  const event = await board.requestWake(board.userActor(), {
8894
9352
  agent: agentName,
8895
9353
  project: opts.project,