@kubitnodes/stellaris 0.4.1 → 0.4.2

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 (63) hide show
  1. package/dist/cli/index.js +603 -18
  2. package/dist/server/index.js +744 -60
  3. package/package.json +1 -1
  4. package/web/dist/assets/BackLink-DNHWkHmh.js +1 -0
  5. package/web/dist/assets/{Button-DxGzriWQ.js → Button-B6EN7Lh7.js} +1 -1
  6. package/web/dist/assets/ChannelForms-BrcWrkej.js +1 -0
  7. package/web/dist/assets/{ChannelView-DvtoxLi5.js → ChannelView-D7bT7d9q.js} +2 -2
  8. package/web/dist/assets/CitizenView-D6d1KnS6.js +2 -0
  9. package/web/dist/assets/{CitizensView-BOZHABbN.js → CitizensView-C55zOiYE.js} +1 -1
  10. package/web/dist/assets/{CliIcon-D3GddnP4.js → CliIcon-BRq1tnhu.js} +1 -1
  11. package/web/dist/assets/CronsView-BTGiqn10.js +1 -0
  12. package/web/dist/assets/{FileTree-DSFN4sd8.js → FileTree-BDhtTIzT.js} +1 -1
  13. package/web/dist/assets/{FileView-DyUYLXRC.js → FileView-BkZPGEbO.js} +2 -2
  14. package/web/dist/assets/{KnowledgeView-CMdXaTvS.js → KnowledgeView-BiTS-lyE.js} +1 -1
  15. package/web/dist/assets/{Markdown-BSeWHBBA.js → Markdown-DYk-gVwe.js} +5 -5
  16. package/web/dist/assets/{MessageItem-qIxS96uJ.js → MessageItem-WBBIzv48.js} +1 -1
  17. package/web/dist/assets/MetricsView-CYmnG6aQ.js +1 -0
  18. package/web/dist/assets/NeedsYouView-B-5olhZL.js +1 -0
  19. package/web/dist/assets/Overview-BO_lR1cu.js +1 -0
  20. package/web/dist/assets/{Pane-pZUzNQIW.js → Pane-DFJdAhv9.js} +1 -1
  21. package/web/dist/assets/{Picker-CDf6os_x.js → Picker-CAEREZF9.js} +1 -1
  22. package/web/dist/assets/ProjectView-LU59V2vh.js +1 -0
  23. package/web/dist/assets/ProposalView-DHemTlkc.js +1 -0
  24. package/web/dist/assets/{ProposalsView-DBGpcAPO.js → ProposalsView-DBIOJAul.js} +1 -1
  25. package/web/dist/assets/{Quota-Brd4ddB4.js → Quota-D6_yyawu.js} +1 -1
  26. package/web/dist/assets/{RunnersView-45OXtalD.js → RunnersView-BNKgYuNw.js} +1 -1
  27. package/web/dist/assets/{SkillFileList-BUImALv9.js → SkillFileList-Ntyv8noL.js} +1 -1
  28. package/web/dist/assets/SkillView-Dez1fvqU.js +1 -0
  29. package/web/dist/assets/SocietyView-CbmbQXHi.js +1 -0
  30. package/web/dist/assets/{StickyList-D_ED_-LW.js → StickyList-Q0KEWBUl.js} +1 -1
  31. package/web/dist/assets/{SubjectThread-B5zvoRSd.js → SubjectThread-1PnxxtZc.js} +1 -1
  32. package/web/dist/assets/{TaskView-B5_QTI3p.js → TaskView-CcIlNHBh.js} +2 -2
  33. package/web/dist/assets/TasksView-BkTU43PD.js +1 -0
  34. package/web/dist/assets/{ThreadForms-CbFClyd0.js → ThreadForms-BlOEvY5E.js} +1 -1
  35. package/web/dist/assets/ThreadItem-D5N_Fw1K.js +1 -0
  36. package/web/dist/assets/{ThreadView-CHFs0zaS.js → ThreadView-BaYV77mQ.js} +1 -1
  37. package/web/dist/assets/{citizen-B8p56JRh.js → citizen-BRg9UIgD.js} +1 -1
  38. package/web/dist/assets/{compose-CV4o6TMb.js → compose-BnOoMY1c.js} +1 -1
  39. package/web/dist/assets/format-hyYD8qd8.js +55 -0
  40. package/web/dist/assets/governance-D4hlG-xw.js +1 -0
  41. package/web/dist/assets/index-5CbC_NpF.js +24 -0
  42. package/web/dist/assets/{index-CA_njyMe.css → index-vxBI4BYp.css} +1 -1
  43. package/web/dist/assets/{mentions-BH0RZXfc.js → mentions-Ckktc7nR.js} +1 -1
  44. package/web/dist/assets/{tasks-D2Es55TQ.js → tasks-CFVtN9TK.js} +1 -1
  45. package/web/dist/assets/{useMatch-DeOpK57G.js → useMatch-BCaBBHc2.js} +1 -1
  46. package/web/dist/favicon.svg +89 -4
  47. package/web/dist/index.html +9 -9
  48. package/web/dist/assets/BackLink-fxPmFUg8.js +0 -1
  49. package/web/dist/assets/ChannelForms-C3m4IPnZ.js +0 -1
  50. package/web/dist/assets/CitizenView-Dq64VsZi.js +0 -2
  51. package/web/dist/assets/CronsView-CI947lZS.js +0 -1
  52. package/web/dist/assets/MetricsView-BhjdwkX1.js +0 -1
  53. package/web/dist/assets/NeedsYouView-7uZW9dY8.js +0 -1
  54. package/web/dist/assets/Overview-D6kiK8sE.js +0 -1
  55. package/web/dist/assets/ProjectView-B6OmxiT9.js +0 -1
  56. package/web/dist/assets/ProposalView-B1X8Jlo0.js +0 -1
  57. package/web/dist/assets/SkillView-B1knYZV8.js +0 -1
  58. package/web/dist/assets/SocietyView-F7bzrNv5.js +0 -1
  59. package/web/dist/assets/TasksView-DZLNqU2V.js +0 -1
  60. package/web/dist/assets/ThreadItem-kFn5M3Cx.js +0 -1
  61. package/web/dist/assets/format-BYF63sQU.js +0 -55
  62. package/web/dist/assets/governance-BjIg0eok.js +0 -1
  63. package/web/dist/assets/index-CyROMh25.js +0 -24
package/dist/cli/index.js CHANGED
@@ -1040,6 +1040,12 @@ import { z as z7 } from "zod";
1040
1040
  import { z as z6 } from "zod";
1041
1041
  var IMAGE_LIMIT_BYTES = 8 * 1024 * 1024;
1042
1042
  var ImageTypeSchema = z6.enum(["png", "jpeg", "gif", "webp"]);
1043
+ var IMAGE_KIND_NAMES = {
1044
+ png: "PNG",
1045
+ jpeg: "JPEG",
1046
+ gif: "GIF",
1047
+ webp: "WebP"
1048
+ };
1043
1049
  function startsWith(bytes, prefix, at = 0) {
1044
1050
  for (let index = 0; index < prefix.length; index += 1) {
1045
1051
  if (bytes[at + index] !== prefix[index]) {
@@ -1461,7 +1467,11 @@ var RunnerHelloSchema = z9.object({
1461
1467
  gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
1462
1468
  /** Turns the runner runs at once, or null for no limit. */
1463
1469
  slots: z9.number().int().positive().nullable(),
1464
- /** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
1470
+ /**
1471
+ * Turns the runner is still running, when it registers again: the server fails the rest of its
1472
+ * turns, and has any of these it has ended stopped once the stream opens, when the runner lists
1473
+ * a CLI it can stop.
1474
+ */
1465
1475
  turns: z9.array(UlidSchema).default([])
1466
1476
  });
1467
1477
  var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
@@ -1616,9 +1626,9 @@ var TurnOutcomeSchema = z9.object({
1616
1626
  */
1617
1627
  resumeFailed: ResumeFailureSchema.optional(),
1618
1628
  /**
1619
- * Set when the model service turned the turn away for now, as when it was overloaded or failed
1620
- * on its side, which the scheduler answers by sending the same wake again within minutes. A
1621
- * runner from before never sends it.
1629
+ * Set when the turn failed for now: the model service turned it away or cut it off, or the runner
1630
+ * could not reach the board before the CLI started. The scheduler answers it by sending the same
1631
+ * wake again within minutes. A runner from before never sends it.
1622
1632
  */
1623
1633
  transient: z9.boolean().optional(),
1624
1634
  /** The model the CLI reported, when it reported one. */
@@ -2009,13 +2019,18 @@ var TurnRecordSchema = z10.object({
2009
2019
  stoppedBy: NameSchema.optional(),
2010
2020
  /** Why the CLI could not resume the turn's session, for a turn that failed so. */
2011
2021
  resumeFailed: ResumeFailureSchema.optional(),
2012
- /** Whether the model service turned the turn away for now: overloaded, or failing on its side. */
2022
+ /**
2023
+ * Whether the turn failed for now, which the same wake may get past in a minute: the model service
2024
+ * turned it away or cut it off, the runner could not reach the board before the CLI started, or
2025
+ * the job never reached the runner.
2026
+ */
2013
2027
  transient: z10.boolean().optional(),
2014
2028
  /** How many turns in a row, this one included, could not resume the session for a `retry`. */
2015
2029
  resumeRetries: z10.number().int().positive().optional(),
2016
2030
  /**
2017
2031
  * Whether the server dropped the conversation's session after this turn, which the next starts
2018
- * anew: one its CLI could not resume, or a society role's home after a reflection completed.
2032
+ * anew: one its CLI could not resume, or a society role's or a project member's home after a
2033
+ * reflection completed.
2019
2034
  */
2020
2035
  sessionDropped: z10.boolean().optional(),
2021
2036
  /** The crons whose fire started the turn, which record how it ended. */
@@ -2260,6 +2275,11 @@ var ServerConfigSchema = z13.object({
2260
2275
  dataDir: z13.string().min(1),
2261
2276
  host: z13.string().min(1),
2262
2277
  port: z13.number().int().positive().max(65535),
2278
+ /**
2279
+ * A second port, on the same host, that serves Prometheus's counters at `/metrics` and nothing
2280
+ * else, or null for none: the main port's `/metrics` is the interface's page.
2281
+ */
2282
+ metricsPort: z13.number().int().positive().max(65535).nullable(),
2263
2283
  /**
2264
2284
  * An address every runner's agents reach this server at, for example https://board.example, or
2265
2285
  * null for each runner to use the address it reaches the server at itself.
@@ -2300,7 +2320,7 @@ var RunnerConfigSchema = z13.object({
2300
2320
  });
2301
2321
 
2302
2322
  // packages/shared/dist/version.js
2303
- var STELLARIS_VERSION = "0.4.1";
2323
+ var STELLARIS_VERSION = "0.4.2";
2304
2324
 
2305
2325
  // packages/shared/dist/mentions.js
2306
2326
  import { parse, postprocess, preprocess } from "micromark";
@@ -2992,6 +3012,451 @@ function filesOf(lines, status) {
2992
3012
  return [...files.values()];
2993
3013
  }
2994
3014
 
3015
+ // packages/shared/dist/image-metadata.js
3016
+ function withoutMetadata(bytes) {
3017
+ switch (imageTypeOf(bytes)) {
3018
+ case "jpeg":
3019
+ return jpegWithoutMetadata(bytes);
3020
+ case "png":
3021
+ return pngWithoutMetadata(bytes);
3022
+ case "gif":
3023
+ return gifWithoutMetadata(bytes);
3024
+ case "webp":
3025
+ return webpWithoutMetadata(bytes);
3026
+ default:
3027
+ return null;
3028
+ }
3029
+ }
3030
+ function exifOrientation(block) {
3031
+ const tiff = startsWith2(block, EXIF_HEADER) ? block.subarray(EXIF_HEADER.length) : block;
3032
+ let little;
3033
+ if (tiff[0] === 73 && tiff[1] === 73) {
3034
+ little = true;
3035
+ } else if (tiff[0] === 77 && tiff[1] === 77) {
3036
+ little = false;
3037
+ } else {
3038
+ return 1;
3039
+ }
3040
+ const number = (offset, size) => offset >= 0 && size <= tiff.length && offset <= tiff.length - size ? numberAt(tiff, offset, size, little) : null;
3041
+ const directory = number(4, 4);
3042
+ const count = directory === null ? null : number(directory, 2);
3043
+ if (number(2, 2) !== 42 || directory === null || count === null) {
3044
+ return 1;
3045
+ }
3046
+ for (let entry = directory + 2; entry < directory + 2 + count * 12; entry += 12) {
3047
+ const tag = number(entry, 2);
3048
+ if (tag === null) {
3049
+ return 1;
3050
+ }
3051
+ if (tag === 274) {
3052
+ const value = number(entry + 8, 2);
3053
+ if (number(entry + 2, 2) !== 3 || number(entry + 4, 4) !== 1 || value === null || value < 1 || value > 8) {
3054
+ return 1;
3055
+ }
3056
+ return value;
3057
+ }
3058
+ }
3059
+ return 1;
3060
+ }
3061
+ function orientationOnly(orientation) {
3062
+ const block = new Uint8Array([
3063
+ 77,
3064
+ 77,
3065
+ 0,
3066
+ 42,
3067
+ 0,
3068
+ 0,
3069
+ 0,
3070
+ 8,
3071
+ 0,
3072
+ 1,
3073
+ 1,
3074
+ 18,
3075
+ 0,
3076
+ 3,
3077
+ 0,
3078
+ 0,
3079
+ 0,
3080
+ 1,
3081
+ 0,
3082
+ 0,
3083
+ 0,
3084
+ 0,
3085
+ 0,
3086
+ 0,
3087
+ 0,
3088
+ 0
3089
+ ]);
3090
+ block[19] = orientation;
3091
+ return block;
3092
+ }
3093
+ function crc32(bytes) {
3094
+ let crc = 4294967295;
3095
+ for (const byte of bytes) {
3096
+ crc = (crcTable()[(crc ^ byte) & 255] ?? 0) ^ crc >>> 8;
3097
+ }
3098
+ return (crc ^ 4294967295) >>> 0;
3099
+ }
3100
+ var crcEntries;
3101
+ function crcTable() {
3102
+ if (crcEntries === void 0) {
3103
+ crcEntries = new Uint32Array(256);
3104
+ for (let index = 0; index < 256; index += 1) {
3105
+ let crc = index;
3106
+ for (let bit = 0; bit < 8; bit += 1) {
3107
+ crc = crc & 1 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
3108
+ }
3109
+ crcEntries[index] = crc >>> 0;
3110
+ }
3111
+ }
3112
+ return crcEntries;
3113
+ }
3114
+ var ascii2 = (text) => new TextEncoder().encode(text);
3115
+ var EXIF_HEADER = Uint8Array.of(69, 120, 105, 102, 0, 0);
3116
+ function startsWith2(bytes, prefix, at = 0) {
3117
+ if (bytes.length - at < prefix.length) {
3118
+ return false;
3119
+ }
3120
+ for (let index = 0; index < prefix.length; index += 1) {
3121
+ if (bytes[at + index] !== prefix[index]) {
3122
+ return false;
3123
+ }
3124
+ }
3125
+ return true;
3126
+ }
3127
+ function typeAt(bytes, at) {
3128
+ return String.fromCharCode(bytes[at] ?? 0, bytes[at + 1] ?? 0, bytes[at + 2] ?? 0, bytes[at + 3] ?? 0);
3129
+ }
3130
+ function numberAt(bytes, at, size, little = false) {
3131
+ let value = 0;
3132
+ for (let index = 0; index < size; index += 1) {
3133
+ value = value * 256 + (bytes[at + (little ? size - 1 - index : index)] ?? 0);
3134
+ }
3135
+ return value;
3136
+ }
3137
+ function bigEndian(value, size) {
3138
+ return Array.from({ length: size }, (_, index) => value >>> (size - 1 - index) * 8 & 255);
3139
+ }
3140
+ function littleEndian(value, size) {
3141
+ return Array.from({ length: size }, (_, index) => value >>> index * 8 & 255);
3142
+ }
3143
+ var Kept = class {
3144
+ buffer;
3145
+ length = 0;
3146
+ constructor(capacity) {
3147
+ this.buffer = new Uint8Array(Math.max(capacity, 64));
3148
+ }
3149
+ add(piece) {
3150
+ if (this.length + piece.length > this.buffer.length) {
3151
+ const grown = new Uint8Array(Math.max(this.buffer.length * 2, this.length + piece.length));
3152
+ grown.set(this.buffer.subarray(0, this.length));
3153
+ this.buffer = grown;
3154
+ }
3155
+ this.buffer.set(piece, this.length);
3156
+ this.length += piece.length;
3157
+ }
3158
+ /** Writes `piece` over what was kept from `at` on, as a size once what it measures is kept. */
3159
+ set(at, piece) {
3160
+ this.buffer.set(piece, at);
3161
+ }
3162
+ get bytes() {
3163
+ return this.buffer.subarray(0, this.length);
3164
+ }
3165
+ };
3166
+ function jpegWithoutMetadata(bytes) {
3167
+ if (bytes[0] !== 255 || bytes[1] !== 216) {
3168
+ return null;
3169
+ }
3170
+ const kept = new Kept(bytes.length);
3171
+ kept.add([255, 216]);
3172
+ let at = 2;
3173
+ let orientationKept = false;
3174
+ for (; ; ) {
3175
+ if (at + 1 >= bytes.length || bytes[at] !== 255) {
3176
+ return null;
3177
+ }
3178
+ const marker = bytes[at + 1] ?? 0;
3179
+ if (marker === 255) {
3180
+ at += 1;
3181
+ continue;
3182
+ }
3183
+ if (marker === 0 || marker >= 2 && marker <= 191 || marker === 216 || marker === 217) {
3184
+ return null;
3185
+ }
3186
+ if (marker === 1 || marker >= 208 && marker <= 215) {
3187
+ kept.add([255, marker]);
3188
+ at += 2;
3189
+ continue;
3190
+ }
3191
+ if (at + 4 > bytes.length) {
3192
+ return null;
3193
+ }
3194
+ const length = numberAt(bytes, at + 2, 2);
3195
+ const end = at + 2 + length;
3196
+ if (length < 2 || end > bytes.length) {
3197
+ return null;
3198
+ }
3199
+ const payload = bytes.subarray(at + 4, end);
3200
+ if (marker === 224) {
3201
+ if (startsWith2(payload, JFIF) && payload.length >= 14) {
3202
+ kept.add([255, 224, 0, 16]);
3203
+ kept.add(payload.subarray(0, 12));
3204
+ kept.add([0, 0]);
3205
+ }
3206
+ } else if (marker === 225) {
3207
+ if (startsWith2(payload, EXIF_HEADER) && !orientationKept) {
3208
+ orientationKept = true;
3209
+ const orientation = exifOrientation(payload.subarray(EXIF_HEADER.length));
3210
+ if (orientation !== 1) {
3211
+ const exif = orientationOnly(orientation);
3212
+ kept.add([255, 225, ...bigEndian(EXIF_HEADER.length + exif.length + 2, 2)]);
3213
+ kept.add(EXIF_HEADER);
3214
+ kept.add(exif);
3215
+ }
3216
+ }
3217
+ } else if (marker === 226 && startsWith2(payload, ICC_PROFILE) || marker === 238 && startsWith2(payload, ADOBE) || // A frame, its tables, a scan, and their kin, JPEG-LS's frame and parameters among them.
3218
+ marker >= 192 && marker <= 207 && marker !== 200 || marker >= 218 && marker <= 223 || marker === 247 || marker === 248) {
3219
+ kept.add(bytes.subarray(at, end));
3220
+ }
3221
+ at = end;
3222
+ if (marker !== 218) {
3223
+ continue;
3224
+ }
3225
+ const scan = at;
3226
+ for (; ; ) {
3227
+ const next = bytes.indexOf(255, at);
3228
+ if (next === -1 || next + 1 >= bytes.length) {
3229
+ return null;
3230
+ }
3231
+ at = next;
3232
+ const after = bytes[at + 1] ?? 0;
3233
+ if (after !== 0 && after !== 255 && !(after >= 208 && after <= 215)) {
3234
+ break;
3235
+ }
3236
+ at += 1;
3237
+ }
3238
+ kept.add(bytes.subarray(scan, at));
3239
+ if (bytes[at + 1] === 217) {
3240
+ kept.add([255, 217]);
3241
+ return kept.bytes;
3242
+ }
3243
+ }
3244
+ }
3245
+ var JFIF = Uint8Array.of(74, 70, 73, 70, 0);
3246
+ var ICC_PROFILE = ascii2("ICC_PROFILE\0");
3247
+ var ADOBE = ascii2("Adobe");
3248
+ var PNG_DRAWING_CHUNKS = /* @__PURE__ */ new Set([
3249
+ "IHDR",
3250
+ "PLTE",
3251
+ "IDAT",
3252
+ "IEND",
3253
+ "tRNS",
3254
+ "cHRM",
3255
+ "gAMA",
3256
+ "iCCP",
3257
+ "sBIT",
3258
+ "sRGB",
3259
+ "cICP",
3260
+ "mDCV",
3261
+ "cLLI",
3262
+ "bKGD",
3263
+ "hIST",
3264
+ "pHYs",
3265
+ "sPLT",
3266
+ "acTL",
3267
+ "fcTL",
3268
+ "fdAT"
3269
+ ]);
3270
+ function pngWithoutMetadata(bytes) {
3271
+ const kept = new Kept(bytes.length);
3272
+ kept.add(bytes.subarray(0, 8));
3273
+ let at = 8;
3274
+ let orientationKept = false;
3275
+ while (at + 12 <= bytes.length) {
3276
+ const length = numberAt(bytes, at, 4);
3277
+ const type = typeAt(bytes, at + 4);
3278
+ if (length > bytes.length - at - 12) {
3279
+ return null;
3280
+ }
3281
+ const end = at + 12 + length;
3282
+ if (PNG_DRAWING_CHUNKS.has(type)) {
3283
+ kept.add(bytes.subarray(at, end));
3284
+ } else if (type === "eXIf" && !orientationKept) {
3285
+ orientationKept = true;
3286
+ const orientation = exifOrientation(bytes.subarray(at + 8, at + 8 + length));
3287
+ if (orientation !== 1) {
3288
+ const data = new Uint8Array([...ascii2("eXIf"), ...orientationOnly(orientation)]);
3289
+ kept.add(bigEndian(data.length - 4, 4));
3290
+ kept.add(data);
3291
+ kept.add(bigEndian(crc32(data), 4));
3292
+ }
3293
+ }
3294
+ if (type === "IEND") {
3295
+ return kept.bytes;
3296
+ }
3297
+ at = end;
3298
+ }
3299
+ return null;
3300
+ }
3301
+ var GIF_PLAYING_APPLICATIONS = [
3302
+ "NETSCAPE2.0",
3303
+ "ANIMEXTS1.0",
3304
+ "ICCRGBG1012"
3305
+ ].map(ascii2);
3306
+ function gifColourTable(packed) {
3307
+ return packed & 128 ? 3 << (packed & 7) + 1 : 0;
3308
+ }
3309
+ function gifWithoutMetadata(bytes) {
3310
+ const subBlocksEnd = (start) => {
3311
+ let at2 = start;
3312
+ while (at2 < bytes.length) {
3313
+ const size = bytes[at2] ?? 0;
3314
+ if (size === 0) {
3315
+ return at2 + 1;
3316
+ }
3317
+ at2 += 1 + size;
3318
+ }
3319
+ return null;
3320
+ };
3321
+ if (bytes.length < 13) {
3322
+ return null;
3323
+ }
3324
+ let at = 13 + gifColourTable(bytes[10] ?? 0);
3325
+ if (at > bytes.length) {
3326
+ return null;
3327
+ }
3328
+ const kept = new Kept(bytes.length);
3329
+ kept.add(bytes.subarray(0, at));
3330
+ while (at < bytes.length) {
3331
+ switch (bytes[at]) {
3332
+ case 44: {
3333
+ if (at + 10 > bytes.length) {
3334
+ return null;
3335
+ }
3336
+ const data = at + 10 + gifColourTable(bytes[at + 9] ?? 0);
3337
+ const end = data < bytes.length ? subBlocksEnd(data + 1) : null;
3338
+ if (end === null) {
3339
+ return null;
3340
+ }
3341
+ kept.add(bytes.subarray(at, end));
3342
+ at = end;
3343
+ break;
3344
+ }
3345
+ case 33: {
3346
+ const end = at + 2 < bytes.length ? subBlocksEnd(at + 2) : null;
3347
+ if (end === null) {
3348
+ return null;
3349
+ }
3350
+ const label = bytes[at + 1];
3351
+ const plays = label === 255 && bytes[at + 2] === 11 && end >= at + 14 && GIF_PLAYING_APPLICATIONS.some((name) => startsWith2(bytes, name, at + 3));
3352
+ if (label === 249 || label === 1 || plays) {
3353
+ kept.add(bytes.subarray(at, end));
3354
+ }
3355
+ at = end;
3356
+ break;
3357
+ }
3358
+ case 59:
3359
+ kept.add([59]);
3360
+ return kept.bytes;
3361
+ default:
3362
+ return null;
3363
+ }
3364
+ }
3365
+ return null;
3366
+ }
3367
+ var WEBP_DRAWING_CHUNKS = /* @__PURE__ */ new Set([
3368
+ "VP8X",
3369
+ "ICCP",
3370
+ "ANIM",
3371
+ "ANMF",
3372
+ "ALPH",
3373
+ "VP8 ",
3374
+ "VP8L"
3375
+ ]);
3376
+ function webpWithoutMetadata(bytes) {
3377
+ if (bytes.length < 12) {
3378
+ return null;
3379
+ }
3380
+ const end = 8 + numberAt(bytes, 4, 4, true);
3381
+ const chunks = end >= 12 && end <= bytes.length ? webpChunks(bytes.subarray(12, end)) : null;
3382
+ if (chunks === null || chunks.length === 0) {
3383
+ return null;
3384
+ }
3385
+ const kept = new Kept(chunks.length + 12);
3386
+ kept.add(ascii2("RIFF"));
3387
+ kept.add(littleEndian(chunks.length + 4, 4));
3388
+ kept.add(ascii2("WEBP"));
3389
+ kept.add(chunks);
3390
+ const image = kept.bytes;
3391
+ if (image.length > 20 && typeAt(image, 12) === "VP8X" && numberAt(image, 16, 4, true) > 0) {
3392
+ const exif = webpChunkTypes(image.subarray(12)).includes("EXIF");
3393
+ image[20] = (image[20] ?? 0) & ~12 | (exif ? 8 : 0);
3394
+ }
3395
+ return image;
3396
+ }
3397
+ function webpChunks(chunks) {
3398
+ const kept = new Kept(chunks.length);
3399
+ const frames = [];
3400
+ let end = chunks.length;
3401
+ let at = 0;
3402
+ let orientationKept = false;
3403
+ for (; ; ) {
3404
+ if (at >= end) {
3405
+ const frame = frames.pop();
3406
+ if (frame === void 0) {
3407
+ return kept.bytes;
3408
+ }
3409
+ kept.set(frame.size, littleEndian(kept.length - frame.size - 4, 4));
3410
+ at = frame.resume;
3411
+ end = frames.at(-1)?.end ?? chunks.length;
3412
+ continue;
3413
+ }
3414
+ if (end - at < 8) {
3415
+ return null;
3416
+ }
3417
+ const type = typeAt(chunks, at);
3418
+ const size = numberAt(chunks, at + 4, 4, true);
3419
+ if (size > end - at - 8) {
3420
+ return null;
3421
+ }
3422
+ const data = at + 8;
3423
+ at = Math.min(data + size + size % 2, end);
3424
+ if (type === "ANMF") {
3425
+ if (size < 16) {
3426
+ return null;
3427
+ }
3428
+ frames.push({ end: data + size, resume: at, size: kept.length + 4 });
3429
+ kept.add(chunks.subarray(data - 8, data + 16));
3430
+ at = data + 16;
3431
+ end = data + size;
3432
+ } else if (WEBP_DRAWING_CHUNKS.has(type)) {
3433
+ kept.add(chunks.subarray(data - 8, data + size));
3434
+ if (size % 2 === 1) {
3435
+ kept.add([0]);
3436
+ }
3437
+ } else if (type === "EXIF" && frames.length === 0 && !orientationKept) {
3438
+ orientationKept = true;
3439
+ const turned = exifOrientation(chunks.subarray(data, data + size));
3440
+ if (turned !== 1) {
3441
+ const exif = orientationOnly(turned);
3442
+ kept.add(ascii2(type));
3443
+ kept.add(littleEndian(exif.length, 4));
3444
+ kept.add(exif);
3445
+ }
3446
+ }
3447
+ }
3448
+ }
3449
+ function webpChunkTypes(chunks) {
3450
+ const types = [];
3451
+ let at = 0;
3452
+ while (chunks.length - at >= 8) {
3453
+ types.push(typeAt(chunks, at));
3454
+ const size = numberAt(chunks, at + 4, 4, true);
3455
+ at += 8 + size + size % 2;
3456
+ }
3457
+ return types;
3458
+ }
3459
+
2995
3460
  // packages/board-core/dist/moves.js
2996
3461
  import { mkdir as mkdir4, rm as rm2 } from "node:fs/promises";
2997
3462
  import path5 from "node:path";
@@ -3434,6 +3899,72 @@ function tokenUse(events, window, now) {
3434
3899
  };
3435
3900
  }
3436
3901
 
3902
+ // packages/board-core/dist/turn-counters.js
3903
+ var TOKEN_KINDS = ["input", "output", "cache_read", "cache_write"];
3904
+ var USAGE_FIELD = {
3905
+ input: "inputTokens",
3906
+ output: "outputTokens",
3907
+ cache_read: "cacheReadTokens",
3908
+ cache_write: "cacheWriteTokens"
3909
+ };
3910
+ var UNKNOWN_LABEL = "unknown";
3911
+ function byKey(entries) {
3912
+ return [...entries].toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, series]) => series);
3913
+ }
3914
+ var TurnCounters = class {
3915
+ cliOf;
3916
+ tokens = /* @__PURE__ */ new Map();
3917
+ turns = /* @__PURE__ */ new Map();
3918
+ /**
3919
+ * `cliOf` names a citizen's CLI for an end logged before ends named theirs: a turn runs on the
3920
+ * CLI its citizen's record names, which nothing changes after the citizen is added.
3921
+ */
3922
+ constructor(cliOf) {
3923
+ this.cliOf = cliOf;
3924
+ }
3925
+ /** Counts a turn's end, `turn.completed` or `turn.failed`, and passes over any other event. */
3926
+ add(event) {
3927
+ if (event.type !== "turn.completed" && event.type !== "turn.failed") {
3928
+ return;
3929
+ }
3930
+ const turn2 = TurnHistoryPayloadSchema.safeParse(event.payload);
3931
+ if (!turn2.success) {
3932
+ return;
3933
+ }
3934
+ const agent2 = event.actor;
3935
+ const outcome = event.type === "turn.failed" ? "failed" : "completed";
3936
+ const trigger = turn2.data.trigger;
3937
+ const counted = JSON.stringify([agent2, outcome, trigger]);
3938
+ this.turns.set(counted, {
3939
+ agent: agent2,
3940
+ outcome,
3941
+ trigger,
3942
+ turns: (this.turns.get(counted)?.turns ?? 0) + 1
3943
+ });
3944
+ const usage = turn2.data.usage;
3945
+ if (usage === void 0) {
3946
+ return;
3947
+ }
3948
+ const named = CliKindSchema.safeParse(event.payload["cli"]);
3949
+ const cli = (named.success ? named.data : this.cliOf(agent2)) ?? UNKNOWN_LABEL;
3950
+ const model = turn2.data.model ?? UNKNOWN_LABEL;
3951
+ for (const kind of TOKEN_KINDS) {
3952
+ const key = JSON.stringify([agent2, cli, model, kind]);
3953
+ this.tokens.set(key, {
3954
+ agent: agent2,
3955
+ cli,
3956
+ model,
3957
+ kind,
3958
+ tokens: (this.tokens.get(key)?.tokens ?? 0) + usage[USAGE_FIELD[kind]]
3959
+ });
3960
+ }
3961
+ }
3962
+ /** The counts so far, in an order that holds from one read to the next. */
3963
+ read() {
3964
+ return { tokens: byKey(this.tokens), turns: byKey(this.turns) };
3965
+ }
3966
+ };
3967
+
3437
3968
  // packages/board-core/dist/mutex.js
3438
3969
  var Mutex = class {
3439
3970
  tail = Promise.resolve();
@@ -3793,6 +4324,7 @@ function hashToken(token) {
3793
4324
  // packages/board-core/dist/board.js
3794
4325
  var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
3795
4326
  var CLOSING_ATTEMPTS = 3;
4327
+ var CHANNEL_POST_WAIT_MS = 24 * 36e5;
3796
4328
  function ownTurn(actor, turn2) {
3797
4329
  return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel && turn2.place === actor.place;
3798
4330
  }
@@ -5107,10 +5639,13 @@ ${DASHBOARD_LINE}
5107
5639
  * when it mentions the user, so the creator can put a wait on the user in; a post that asks the
5108
5640
  * reader counts wherever it is. A closed thread counts for nothing: its conversation has ended,
5109
5641
  * and a turn there could only read its posts and be refused a reply, while a wake queued before
5110
- * it closed, as for a mention, still runs there.
5642
+ * it closed, as for a mention, still runs there. A top-level post in a project's channel, general
5643
+ * or a workstream's, counts only for a reader it asks until it is `channelPostWaitMs` old, and
5644
+ * then for every reader who has not read it, so a turn there for anything else reads it sooner.
5111
5645
  */
5112
- async unreadByConversation(actor) {
5646
+ async unreadByConversation(actor, options = {}) {
5113
5647
  const { cursors, upto } = await this.digestSnapshot(actor.name);
5648
+ const waited = this.now().getTime() - (options.channelPostWaitMs ?? CHANNEL_POST_WAIT_MS);
5114
5649
  const counts = /* @__PURE__ */ new Map();
5115
5650
  const threadCounts = /* @__PURE__ */ new Map();
5116
5651
  const unscoped = { name: actor.name, role: actor.role };
@@ -5118,11 +5653,15 @@ ${DASHBOARD_LINE}
5118
5653
  if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name || message.notice?.kind === "knowledge") {
5119
5654
  continue;
5120
5655
  }
5121
- const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
5656
+ const project2 = parseChannelRef(message.channel).project;
5657
+ const posted = project2 ?? SOCIETY_SCOPE;
5122
5658
  if (posted !== scope && scope !== SOCIETY_SCOPE) {
5123
5659
  continue;
5124
5660
  }
5125
5661
  const thread2 = threadOfPart(part);
5662
+ if (thread2 === void 0 && project2 !== null && asked !== true && Date.parse(message.ts) > waited) {
5663
+ continue;
5664
+ }
5126
5665
  if (thread2 !== void 0) {
5127
5666
  let counted = threadCounts.get(thread2);
5128
5667
  if (counted === void 0) {
@@ -5546,9 +6085,9 @@ ${current.body}`);
5546
6085
  /**
5547
6086
  * Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
5548
6087
  * refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
5549
- * Only the bytes of a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` are kept, checked
5550
- * here again whatever the runner said, under a new id outside the projection, so no runner
5551
- * mirrors them and no prompt carries them: a post shows the image by its `image:` address.
6088
+ * Only a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` is kept, checked here again
6089
+ * whatever the runner said, without its metadata, under a new id outside the projection, so no
6090
+ * runner mirrors it and no prompt carries it: a post shows the image by its `image:` address.
5552
6091
  */
5553
6092
  async uploadImage(actor, input) {
5554
6093
  const args = VerbInputs.upload_image.parse(input);
@@ -5574,8 +6113,13 @@ ${current.body}`);
5574
6113
  }
5575
6114
  /**
5576
6115
  * Keeps `bytes` under a new id when they are a PNG, JPEG, GIF, or WebP of at most
5577
- * `IMAGE_LIMIT_BYTES`, and answers the markdown that shows them, described by `name`. `what`
5578
- * names the image in a refusal.
6116
+ * `IMAGE_LIMIT_BYTES` that reads to its end, without its metadata (`withoutMetadata`), and
6117
+ * answers the markdown that shows it, described by `name`. `what` names the image in a refusal.
6118
+ * The limit is for the bytes as they came, as the route's body cap measures them, and bounds
6119
+ * what is read; what is kept is checked against it too, since an orientation written smaller
6120
+ * than the board writes it grows by a few bytes. The runner, the web's composer, and the app
6121
+ * strip an image before they send it, and stripping it again changes nothing, but an older
6122
+ * runner, page, or app sends one as it was.
5579
6123
  */
5580
6124
  async keepImage(actor, bytes, what, name) {
5581
6125
  const type = imageTypeOf(bytes);
@@ -5585,19 +6129,26 @@ ${current.body}`);
5585
6129
  if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
5586
6130
  throw new BoardError("VALIDATION", `${what} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
5587
6131
  }
6132
+ const kept = withoutMetadata(bytes);
6133
+ if (kept === null) {
6134
+ throw new BoardError("VALIDATION", `${what} does not read to its end as a ${IMAGE_KIND_NAMES[type]}, so its metadata cannot be told from its picture; save it again and send that copy`);
6135
+ }
6136
+ if (kept.byteLength > IMAGE_LIMIT_BYTES) {
6137
+ throw new BoardError("VALIDATION", `${what} is ${kept.byteLength} bytes without its metadata, over the ${IMAGE_LIMIT_BYTES} an image may be`);
6138
+ }
5588
6139
  return this.mutex.run(async () => {
5589
6140
  const id = this.newId();
5590
- await writeFileAtomic(this.paths.image(id), bytes);
6141
+ await writeFileAtomic(this.paths.image(id), kept);
5591
6142
  await this.events.append("image.uploaded", actor.name, {
5592
6143
  id,
5593
6144
  type,
5594
- size: bytes.byteLength
6145
+ size: kept.byteLength
5595
6146
  });
5596
6147
  const description = imageDescription(name);
5597
6148
  return {
5598
6149
  id,
5599
6150
  type,
5600
- size: bytes.byteLength,
6151
+ size: kept.byteLength,
5601
6152
  markdown: `![${description}](${IMAGE_SCHEME}${id})`
5602
6153
  };
5603
6154
  });
@@ -6898,6 +7449,39 @@ ${rationale}`}`);
6898
7449
  const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
6899
7450
  return tokenUse(events, window, this.now());
6900
7451
  }
7452
+ /**
7453
+ * Every finished turn counted for Prometheus: seeded once from the whole event log, then moved
7454
+ * by each turn's end this process appends, so reading the counts never reads the log. The server
7455
+ * logs every turn's end itself; one another process appended would count from the next seed.
7456
+ */
7457
+ async countTurns() {
7458
+ const clis = /* @__PURE__ */ new Map();
7459
+ const counters = new TurnCounters((agent2) => clis.get(agent2) ?? null);
7460
+ let waiting = [];
7461
+ this.events.onAppend((event) => {
7462
+ if (waiting === null) {
7463
+ counters.add(event);
7464
+ } else {
7465
+ waiting.push(event);
7466
+ }
7467
+ });
7468
+ const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
7469
+ for (const agent2 of await this.listAgents()) {
7470
+ clis.set(agent2.name, agent2.cli);
7471
+ }
7472
+ const seen = /* @__PURE__ */ new Set();
7473
+ for (const event of events) {
7474
+ seen.add(event.id);
7475
+ counters.add(event);
7476
+ }
7477
+ for (const event of waiting) {
7478
+ if (!seen.has(event.id)) {
7479
+ counters.add(event);
7480
+ }
7481
+ }
7482
+ waiting = null;
7483
+ return counters;
7484
+ }
6901
7485
  /** The board actions a finished turn took, from its transcript; null when it kept none. */
6902
7486
  async turnActions(agent2, turnId) {
6903
7487
  const known = this.actionCounts.get(turnId);
@@ -7095,6 +7679,7 @@ ${rationale}`}`);
7095
7679
  trigger: parsed.trigger.kind,
7096
7680
  ...parsed.trigger.heartbeat === void 0 ? {} : { heartbeat: parsed.trigger.heartbeat },
7097
7681
  session: parsed.session,
7682
+ cli: parsed.cli,
7098
7683
  exitReason: parsed.exitReason,
7099
7684
  costUsd: parsed.costUsd,
7100
7685
  ...parsed.usage === null ? {} : { usage: parsed.usage },