@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.
- package/dist/cli/index.js +603 -18
- package/dist/server/index.js +744 -60
- package/package.json +1 -1
- package/web/dist/assets/BackLink-DNHWkHmh.js +1 -0
- package/web/dist/assets/{Button-DxGzriWQ.js → Button-B6EN7Lh7.js} +1 -1
- package/web/dist/assets/ChannelForms-BrcWrkej.js +1 -0
- package/web/dist/assets/{ChannelView-DvtoxLi5.js → ChannelView-D7bT7d9q.js} +2 -2
- package/web/dist/assets/CitizenView-D6d1KnS6.js +2 -0
- package/web/dist/assets/{CitizensView-BOZHABbN.js → CitizensView-C55zOiYE.js} +1 -1
- package/web/dist/assets/{CliIcon-D3GddnP4.js → CliIcon-BRq1tnhu.js} +1 -1
- package/web/dist/assets/CronsView-BTGiqn10.js +1 -0
- package/web/dist/assets/{FileTree-DSFN4sd8.js → FileTree-BDhtTIzT.js} +1 -1
- package/web/dist/assets/{FileView-DyUYLXRC.js → FileView-BkZPGEbO.js} +2 -2
- package/web/dist/assets/{KnowledgeView-CMdXaTvS.js → KnowledgeView-BiTS-lyE.js} +1 -1
- package/web/dist/assets/{Markdown-BSeWHBBA.js → Markdown-DYk-gVwe.js} +5 -5
- package/web/dist/assets/{MessageItem-qIxS96uJ.js → MessageItem-WBBIzv48.js} +1 -1
- package/web/dist/assets/MetricsView-CYmnG6aQ.js +1 -0
- package/web/dist/assets/NeedsYouView-B-5olhZL.js +1 -0
- package/web/dist/assets/Overview-BO_lR1cu.js +1 -0
- package/web/dist/assets/{Pane-pZUzNQIW.js → Pane-DFJdAhv9.js} +1 -1
- package/web/dist/assets/{Picker-CDf6os_x.js → Picker-CAEREZF9.js} +1 -1
- package/web/dist/assets/ProjectView-LU59V2vh.js +1 -0
- package/web/dist/assets/ProposalView-DHemTlkc.js +1 -0
- package/web/dist/assets/{ProposalsView-DBGpcAPO.js → ProposalsView-DBIOJAul.js} +1 -1
- package/web/dist/assets/{Quota-Brd4ddB4.js → Quota-D6_yyawu.js} +1 -1
- package/web/dist/assets/{RunnersView-45OXtalD.js → RunnersView-BNKgYuNw.js} +1 -1
- package/web/dist/assets/{SkillFileList-BUImALv9.js → SkillFileList-Ntyv8noL.js} +1 -1
- package/web/dist/assets/SkillView-Dez1fvqU.js +1 -0
- package/web/dist/assets/SocietyView-CbmbQXHi.js +1 -0
- package/web/dist/assets/{StickyList-D_ED_-LW.js → StickyList-Q0KEWBUl.js} +1 -1
- package/web/dist/assets/{SubjectThread-B5zvoRSd.js → SubjectThread-1PnxxtZc.js} +1 -1
- package/web/dist/assets/{TaskView-B5_QTI3p.js → TaskView-CcIlNHBh.js} +2 -2
- package/web/dist/assets/TasksView-BkTU43PD.js +1 -0
- package/web/dist/assets/{ThreadForms-CbFClyd0.js → ThreadForms-BlOEvY5E.js} +1 -1
- package/web/dist/assets/ThreadItem-D5N_Fw1K.js +1 -0
- package/web/dist/assets/{ThreadView-CHFs0zaS.js → ThreadView-BaYV77mQ.js} +1 -1
- package/web/dist/assets/{citizen-B8p56JRh.js → citizen-BRg9UIgD.js} +1 -1
- package/web/dist/assets/{compose-CV4o6TMb.js → compose-BnOoMY1c.js} +1 -1
- package/web/dist/assets/format-hyYD8qd8.js +55 -0
- package/web/dist/assets/governance-D4hlG-xw.js +1 -0
- package/web/dist/assets/index-5CbC_NpF.js +24 -0
- package/web/dist/assets/{index-CA_njyMe.css → index-vxBI4BYp.css} +1 -1
- package/web/dist/assets/{mentions-BH0RZXfc.js → mentions-Ckktc7nR.js} +1 -1
- package/web/dist/assets/{tasks-D2Es55TQ.js → tasks-CFVtN9TK.js} +1 -1
- package/web/dist/assets/{useMatch-DeOpK57G.js → useMatch-BCaBBHc2.js} +1 -1
- package/web/dist/favicon.svg +89 -4
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-fxPmFUg8.js +0 -1
- package/web/dist/assets/ChannelForms-C3m4IPnZ.js +0 -1
- package/web/dist/assets/CitizenView-Dq64VsZi.js +0 -2
- package/web/dist/assets/CronsView-CI947lZS.js +0 -1
- package/web/dist/assets/MetricsView-BhjdwkX1.js +0 -1
- package/web/dist/assets/NeedsYouView-7uZW9dY8.js +0 -1
- package/web/dist/assets/Overview-D6kiK8sE.js +0 -1
- package/web/dist/assets/ProjectView-B6OmxiT9.js +0 -1
- package/web/dist/assets/ProposalView-B1X8Jlo0.js +0 -1
- package/web/dist/assets/SkillView-B1knYZV8.js +0 -1
- package/web/dist/assets/SocietyView-F7bzrNv5.js +0 -1
- package/web/dist/assets/TasksView-DZLNqU2V.js +0 -1
- package/web/dist/assets/ThreadItem-kFn5M3Cx.js +0 -1
- package/web/dist/assets/format-BYF63sQU.js +0 -55
- package/web/dist/assets/governance-BjIg0eok.js +0 -1
- package/web/dist/assets/index-CyROMh25.js +0 -24
package/dist/server/index.js
CHANGED
|
@@ -1101,6 +1101,12 @@ var IMAGE_MEDIA_TYPES = {
|
|
|
1101
1101
|
gif: "image/gif",
|
|
1102
1102
|
webp: "image/webp"
|
|
1103
1103
|
};
|
|
1104
|
+
var IMAGE_KIND_NAMES = {
|
|
1105
|
+
png: "PNG",
|
|
1106
|
+
jpeg: "JPEG",
|
|
1107
|
+
gif: "GIF",
|
|
1108
|
+
webp: "WebP"
|
|
1109
|
+
};
|
|
1104
1110
|
function startsWith(bytes, prefix, at = 0) {
|
|
1105
1111
|
for (let index = 0; index < prefix.length; index += 1) {
|
|
1106
1112
|
if (bytes[at + index] !== prefix[index]) {
|
|
@@ -1322,7 +1328,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
1322
1328
|
release_task: "Let go of the stage you hold so someone else can take it.",
|
|
1323
1329
|
update_task: "Move a task back to an earlier stage (the current stage's holder, the user, the steward, or the concierge; anyone else asks the concierge in the task's thread, with a mention), abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
|
|
1324
1330
|
get_task: "Read a task with its plan, its brief, and the messages of its thread.",
|
|
1325
|
-
subscribe: "Subscribe to a channel.
|
|
1331
|
+
subscribe: "Subscribe to a channel. Its posts feed your digest, read at your next turn there; in a project's channel, one that asks nothing of you wakes you only once it is a day old.",
|
|
1326
1332
|
unsubscribe: "Unsubscribe from a channel.",
|
|
1327
1333
|
propose: "Propose a member, role, channel, reallocation, retirement, skill, or a project's archive. The charter shape per kind is in your instructions. Opens the proposal's thread in governance with your rationale; approval provisions it.",
|
|
1328
1334
|
approve: "Approve a proposal; the board then provisions it, posts the decision in the proposal's thread, and closes it. User and steward only, never on your own proposal.",
|
|
@@ -1343,7 +1349,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
1343
1349
|
update_cron: "Pause or resume a cron, or change its title, note, or schedule; resuming or rescheduling starts it afresh from now. Yours, or anyone's for the user, the steward, and the concierge.",
|
|
1344
1350
|
remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires.",
|
|
1345
1351
|
remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore.",
|
|
1346
|
-
upload_image: "Upload an image file, such as a screenshot or a chart, from the machine your turn runs on: a PNG, JPEG, GIF, or WebP of at most 8 MB, in your worktree, your home, or the system temp folder. Returns its id and the markdown that shows it in a post, ``; write what it shows as the alt text.",
|
|
1352
|
+
upload_image: "Upload an image file, such as a screenshot or a chart, from the machine your turn runs on: a PNG, JPEG, GIF, or WebP of at most 8 MB once its metadata is left out, in your worktree, your home, or the system temp folder. Returns its id and the markdown that shows it in a post, ``; write what it shows as the alt text.",
|
|
1347
1353
|
read_image: "Open an image the board keeps, as a post shows it, ``, by its id: the board writes it to a file of this turn's own on the machine it runs on, removed when the turn ends, and returns its path, type, and size. Open the path with your file tools to see the image."
|
|
1348
1354
|
};
|
|
1349
1355
|
|
|
@@ -1565,7 +1571,11 @@ var RunnerHelloSchema = z9.object({
|
|
|
1565
1571
|
gitIdentityMissing: z9.array(GitIdentityPartSchema).optional(),
|
|
1566
1572
|
/** Turns the runner runs at once, or null for no limit. */
|
|
1567
1573
|
slots: z9.number().int().positive().nullable(),
|
|
1568
|
-
/**
|
|
1574
|
+
/**
|
|
1575
|
+
* Turns the runner is still running, when it registers again: the server fails the rest of its
|
|
1576
|
+
* turns, and has any of these it has ended stopped once the stream opens, when the runner lists
|
|
1577
|
+
* a CLI it can stop.
|
|
1578
|
+
*/
|
|
1569
1579
|
turns: z9.array(UlidSchema).default([])
|
|
1570
1580
|
});
|
|
1571
1581
|
var RunnerWelcomeSchema = z9.object({ name: NameSchema, version: z9.string() });
|
|
@@ -1728,9 +1738,9 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1728
1738
|
*/
|
|
1729
1739
|
resumeFailed: ResumeFailureSchema.optional(),
|
|
1730
1740
|
/**
|
|
1731
|
-
* Set when the model service turned
|
|
1732
|
-
*
|
|
1733
|
-
* runner from before never sends it.
|
|
1741
|
+
* Set when the turn failed for now: the model service turned it away or cut it off, or the runner
|
|
1742
|
+
* could not reach the board before the CLI started. The scheduler answers it by sending the same
|
|
1743
|
+
* wake again within minutes. A runner from before never sends it.
|
|
1734
1744
|
*/
|
|
1735
1745
|
transient: z9.boolean().optional(),
|
|
1736
1746
|
/** The model the CLI reported, when it reported one. */
|
|
@@ -2149,13 +2159,18 @@ var TurnRecordSchema = z10.object({
|
|
|
2149
2159
|
stoppedBy: NameSchema.optional(),
|
|
2150
2160
|
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
2151
2161
|
resumeFailed: ResumeFailureSchema.optional(),
|
|
2152
|
-
/**
|
|
2162
|
+
/**
|
|
2163
|
+
* Whether the turn failed for now, which the same wake may get past in a minute: the model service
|
|
2164
|
+
* turned it away or cut it off, the runner could not reach the board before the CLI started, or
|
|
2165
|
+
* the job never reached the runner.
|
|
2166
|
+
*/
|
|
2153
2167
|
transient: z10.boolean().optional(),
|
|
2154
2168
|
/** How many turns in a row, this one included, could not resume the session for a `retry`. */
|
|
2155
2169
|
resumeRetries: z10.number().int().positive().optional(),
|
|
2156
2170
|
/**
|
|
2157
2171
|
* Whether the server dropped the conversation's session after this turn, which the next starts
|
|
2158
|
-
* anew: one its CLI could not resume, or a society role's home after a
|
|
2172
|
+
* anew: one its CLI could not resume, or a society role's or a project member's home after a
|
|
2173
|
+
* reflection completed.
|
|
2159
2174
|
*/
|
|
2160
2175
|
sessionDropped: z10.boolean().optional(),
|
|
2161
2176
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
@@ -2403,6 +2418,11 @@ var ServerConfigSchema = z13.object({
|
|
|
2403
2418
|
dataDir: z13.string().min(1),
|
|
2404
2419
|
host: z13.string().min(1),
|
|
2405
2420
|
port: z13.number().int().positive().max(65535),
|
|
2421
|
+
/**
|
|
2422
|
+
* A second port, on the same host, that serves Prometheus's counters at `/metrics` and nothing
|
|
2423
|
+
* else, or null for none: the main port's `/metrics` is the interface's page.
|
|
2424
|
+
*/
|
|
2425
|
+
metricsPort: z13.number().int().positive().max(65535).nullable(),
|
|
2406
2426
|
/**
|
|
2407
2427
|
* An address every runner's agents reach this server at, for example https://board.example, or
|
|
2408
2428
|
* null for each runner to use the address it reaches the server at itself.
|
|
@@ -2454,10 +2474,11 @@ function githubSignIn(env) {
|
|
|
2454
2474
|
return GithubSignInSchema.parse({ clientId, clientSecret, users, appleAppId });
|
|
2455
2475
|
}
|
|
2456
2476
|
function loadServerConfig(env) {
|
|
2457
|
-
|
|
2477
|
+
const config2 = ServerConfigSchema.parse({
|
|
2458
2478
|
dataDir: env["STELLARIS_DATA_DIR"] ?? "./data",
|
|
2459
2479
|
host: env["STELLARIS_HOST"] ?? "127.0.0.1",
|
|
2460
2480
|
port: Number(env["STELLARIS_PORT"] ?? "4700"),
|
|
2481
|
+
metricsPort: env["STELLARIS_METRICS_PORT"]?.trim() ? Number(env["STELLARIS_METRICS_PORT"]) : null,
|
|
2461
2482
|
publicUrl: env["STELLARIS_PUBLIC_URL"]?.replace(/\/+$/, "") ?? null,
|
|
2462
2483
|
logLevel: env["STELLARIS_LOG_LEVEL"] ?? "info",
|
|
2463
2484
|
concurrency: limit(env["STELLARIS_CONCURRENCY"], null),
|
|
@@ -2467,6 +2488,10 @@ function loadServerConfig(env) {
|
|
|
2467
2488
|
github: githubSignIn(env),
|
|
2468
2489
|
trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
|
|
2469
2490
|
});
|
|
2491
|
+
if (config2.metricsPort === config2.port) {
|
|
2492
|
+
throw new Error("STELLARIS_METRICS_PORT must differ from STELLARIS_PORT");
|
|
2493
|
+
}
|
|
2494
|
+
return config2;
|
|
2470
2495
|
}
|
|
2471
2496
|
var RunnerConfigSchema = z13.object({
|
|
2472
2497
|
/** The board server, for example http://127.0.0.1:4700. */
|
|
@@ -2486,7 +2511,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2486
2511
|
});
|
|
2487
2512
|
|
|
2488
2513
|
// packages/shared/dist/version.js
|
|
2489
|
-
var STELLARIS_VERSION = "0.4.
|
|
2514
|
+
var STELLARIS_VERSION = "0.4.2";
|
|
2490
2515
|
|
|
2491
2516
|
// packages/shared/dist/mentions.js
|
|
2492
2517
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -3181,6 +3206,451 @@ function filesOf(lines, status) {
|
|
|
3181
3206
|
return [...files.values()];
|
|
3182
3207
|
}
|
|
3183
3208
|
|
|
3209
|
+
// packages/shared/dist/image-metadata.js
|
|
3210
|
+
function withoutMetadata(bytes) {
|
|
3211
|
+
switch (imageTypeOf(bytes)) {
|
|
3212
|
+
case "jpeg":
|
|
3213
|
+
return jpegWithoutMetadata(bytes);
|
|
3214
|
+
case "png":
|
|
3215
|
+
return pngWithoutMetadata(bytes);
|
|
3216
|
+
case "gif":
|
|
3217
|
+
return gifWithoutMetadata(bytes);
|
|
3218
|
+
case "webp":
|
|
3219
|
+
return webpWithoutMetadata(bytes);
|
|
3220
|
+
default:
|
|
3221
|
+
return null;
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
function exifOrientation(block) {
|
|
3225
|
+
const tiff = startsWith2(block, EXIF_HEADER) ? block.subarray(EXIF_HEADER.length) : block;
|
|
3226
|
+
let little;
|
|
3227
|
+
if (tiff[0] === 73 && tiff[1] === 73) {
|
|
3228
|
+
little = true;
|
|
3229
|
+
} else if (tiff[0] === 77 && tiff[1] === 77) {
|
|
3230
|
+
little = false;
|
|
3231
|
+
} else {
|
|
3232
|
+
return 1;
|
|
3233
|
+
}
|
|
3234
|
+
const number = (offset, size) => offset >= 0 && size <= tiff.length && offset <= tiff.length - size ? numberAt(tiff, offset, size, little) : null;
|
|
3235
|
+
const directory = number(4, 4);
|
|
3236
|
+
const count = directory === null ? null : number(directory, 2);
|
|
3237
|
+
if (number(2, 2) !== 42 || directory === null || count === null) {
|
|
3238
|
+
return 1;
|
|
3239
|
+
}
|
|
3240
|
+
for (let entry = directory + 2; entry < directory + 2 + count * 12; entry += 12) {
|
|
3241
|
+
const tag = number(entry, 2);
|
|
3242
|
+
if (tag === null) {
|
|
3243
|
+
return 1;
|
|
3244
|
+
}
|
|
3245
|
+
if (tag === 274) {
|
|
3246
|
+
const value = number(entry + 8, 2);
|
|
3247
|
+
if (number(entry + 2, 2) !== 3 || number(entry + 4, 4) !== 1 || value === null || value < 1 || value > 8) {
|
|
3248
|
+
return 1;
|
|
3249
|
+
}
|
|
3250
|
+
return value;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
return 1;
|
|
3254
|
+
}
|
|
3255
|
+
function orientationOnly(orientation) {
|
|
3256
|
+
const block = new Uint8Array([
|
|
3257
|
+
77,
|
|
3258
|
+
77,
|
|
3259
|
+
0,
|
|
3260
|
+
42,
|
|
3261
|
+
0,
|
|
3262
|
+
0,
|
|
3263
|
+
0,
|
|
3264
|
+
8,
|
|
3265
|
+
0,
|
|
3266
|
+
1,
|
|
3267
|
+
1,
|
|
3268
|
+
18,
|
|
3269
|
+
0,
|
|
3270
|
+
3,
|
|
3271
|
+
0,
|
|
3272
|
+
0,
|
|
3273
|
+
0,
|
|
3274
|
+
1,
|
|
3275
|
+
0,
|
|
3276
|
+
0,
|
|
3277
|
+
0,
|
|
3278
|
+
0,
|
|
3279
|
+
0,
|
|
3280
|
+
0,
|
|
3281
|
+
0,
|
|
3282
|
+
0
|
|
3283
|
+
]);
|
|
3284
|
+
block[19] = orientation;
|
|
3285
|
+
return block;
|
|
3286
|
+
}
|
|
3287
|
+
function crc32(bytes) {
|
|
3288
|
+
let crc = 4294967295;
|
|
3289
|
+
for (const byte of bytes) {
|
|
3290
|
+
crc = (crcTable()[(crc ^ byte) & 255] ?? 0) ^ crc >>> 8;
|
|
3291
|
+
}
|
|
3292
|
+
return (crc ^ 4294967295) >>> 0;
|
|
3293
|
+
}
|
|
3294
|
+
var crcEntries;
|
|
3295
|
+
function crcTable() {
|
|
3296
|
+
if (crcEntries === void 0) {
|
|
3297
|
+
crcEntries = new Uint32Array(256);
|
|
3298
|
+
for (let index = 0; index < 256; index += 1) {
|
|
3299
|
+
let crc = index;
|
|
3300
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
3301
|
+
crc = crc & 1 ? 3988292384 ^ crc >>> 1 : crc >>> 1;
|
|
3302
|
+
}
|
|
3303
|
+
crcEntries[index] = crc >>> 0;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
return crcEntries;
|
|
3307
|
+
}
|
|
3308
|
+
var ascii2 = (text) => new TextEncoder().encode(text);
|
|
3309
|
+
var EXIF_HEADER = Uint8Array.of(69, 120, 105, 102, 0, 0);
|
|
3310
|
+
function startsWith2(bytes, prefix, at = 0) {
|
|
3311
|
+
if (bytes.length - at < prefix.length) {
|
|
3312
|
+
return false;
|
|
3313
|
+
}
|
|
3314
|
+
for (let index = 0; index < prefix.length; index += 1) {
|
|
3315
|
+
if (bytes[at + index] !== prefix[index]) {
|
|
3316
|
+
return false;
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
return true;
|
|
3320
|
+
}
|
|
3321
|
+
function typeAt(bytes, at) {
|
|
3322
|
+
return String.fromCharCode(bytes[at] ?? 0, bytes[at + 1] ?? 0, bytes[at + 2] ?? 0, bytes[at + 3] ?? 0);
|
|
3323
|
+
}
|
|
3324
|
+
function numberAt(bytes, at, size, little = false) {
|
|
3325
|
+
let value = 0;
|
|
3326
|
+
for (let index = 0; index < size; index += 1) {
|
|
3327
|
+
value = value * 256 + (bytes[at + (little ? size - 1 - index : index)] ?? 0);
|
|
3328
|
+
}
|
|
3329
|
+
return value;
|
|
3330
|
+
}
|
|
3331
|
+
function bigEndian(value, size) {
|
|
3332
|
+
return Array.from({ length: size }, (_, index) => value >>> (size - 1 - index) * 8 & 255);
|
|
3333
|
+
}
|
|
3334
|
+
function littleEndian(value, size) {
|
|
3335
|
+
return Array.from({ length: size }, (_, index) => value >>> index * 8 & 255);
|
|
3336
|
+
}
|
|
3337
|
+
var Kept = class {
|
|
3338
|
+
buffer;
|
|
3339
|
+
length = 0;
|
|
3340
|
+
constructor(capacity) {
|
|
3341
|
+
this.buffer = new Uint8Array(Math.max(capacity, 64));
|
|
3342
|
+
}
|
|
3343
|
+
add(piece) {
|
|
3344
|
+
if (this.length + piece.length > this.buffer.length) {
|
|
3345
|
+
const grown = new Uint8Array(Math.max(this.buffer.length * 2, this.length + piece.length));
|
|
3346
|
+
grown.set(this.buffer.subarray(0, this.length));
|
|
3347
|
+
this.buffer = grown;
|
|
3348
|
+
}
|
|
3349
|
+
this.buffer.set(piece, this.length);
|
|
3350
|
+
this.length += piece.length;
|
|
3351
|
+
}
|
|
3352
|
+
/** Writes `piece` over what was kept from `at` on, as a size once what it measures is kept. */
|
|
3353
|
+
set(at, piece) {
|
|
3354
|
+
this.buffer.set(piece, at);
|
|
3355
|
+
}
|
|
3356
|
+
get bytes() {
|
|
3357
|
+
return this.buffer.subarray(0, this.length);
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
function jpegWithoutMetadata(bytes) {
|
|
3361
|
+
if (bytes[0] !== 255 || bytes[1] !== 216) {
|
|
3362
|
+
return null;
|
|
3363
|
+
}
|
|
3364
|
+
const kept = new Kept(bytes.length);
|
|
3365
|
+
kept.add([255, 216]);
|
|
3366
|
+
let at = 2;
|
|
3367
|
+
let orientationKept = false;
|
|
3368
|
+
for (; ; ) {
|
|
3369
|
+
if (at + 1 >= bytes.length || bytes[at] !== 255) {
|
|
3370
|
+
return null;
|
|
3371
|
+
}
|
|
3372
|
+
const marker = bytes[at + 1] ?? 0;
|
|
3373
|
+
if (marker === 255) {
|
|
3374
|
+
at += 1;
|
|
3375
|
+
continue;
|
|
3376
|
+
}
|
|
3377
|
+
if (marker === 0 || marker >= 2 && marker <= 191 || marker === 216 || marker === 217) {
|
|
3378
|
+
return null;
|
|
3379
|
+
}
|
|
3380
|
+
if (marker === 1 || marker >= 208 && marker <= 215) {
|
|
3381
|
+
kept.add([255, marker]);
|
|
3382
|
+
at += 2;
|
|
3383
|
+
continue;
|
|
3384
|
+
}
|
|
3385
|
+
if (at + 4 > bytes.length) {
|
|
3386
|
+
return null;
|
|
3387
|
+
}
|
|
3388
|
+
const length = numberAt(bytes, at + 2, 2);
|
|
3389
|
+
const end = at + 2 + length;
|
|
3390
|
+
if (length < 2 || end > bytes.length) {
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
3393
|
+
const payload = bytes.subarray(at + 4, end);
|
|
3394
|
+
if (marker === 224) {
|
|
3395
|
+
if (startsWith2(payload, JFIF) && payload.length >= 14) {
|
|
3396
|
+
kept.add([255, 224, 0, 16]);
|
|
3397
|
+
kept.add(payload.subarray(0, 12));
|
|
3398
|
+
kept.add([0, 0]);
|
|
3399
|
+
}
|
|
3400
|
+
} else if (marker === 225) {
|
|
3401
|
+
if (startsWith2(payload, EXIF_HEADER) && !orientationKept) {
|
|
3402
|
+
orientationKept = true;
|
|
3403
|
+
const orientation = exifOrientation(payload.subarray(EXIF_HEADER.length));
|
|
3404
|
+
if (orientation !== 1) {
|
|
3405
|
+
const exif = orientationOnly(orientation);
|
|
3406
|
+
kept.add([255, 225, ...bigEndian(EXIF_HEADER.length + exif.length + 2, 2)]);
|
|
3407
|
+
kept.add(EXIF_HEADER);
|
|
3408
|
+
kept.add(exif);
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
} 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.
|
|
3412
|
+
marker >= 192 && marker <= 207 && marker !== 200 || marker >= 218 && marker <= 223 || marker === 247 || marker === 248) {
|
|
3413
|
+
kept.add(bytes.subarray(at, end));
|
|
3414
|
+
}
|
|
3415
|
+
at = end;
|
|
3416
|
+
if (marker !== 218) {
|
|
3417
|
+
continue;
|
|
3418
|
+
}
|
|
3419
|
+
const scan = at;
|
|
3420
|
+
for (; ; ) {
|
|
3421
|
+
const next = bytes.indexOf(255, at);
|
|
3422
|
+
if (next === -1 || next + 1 >= bytes.length) {
|
|
3423
|
+
return null;
|
|
3424
|
+
}
|
|
3425
|
+
at = next;
|
|
3426
|
+
const after = bytes[at + 1] ?? 0;
|
|
3427
|
+
if (after !== 0 && after !== 255 && !(after >= 208 && after <= 215)) {
|
|
3428
|
+
break;
|
|
3429
|
+
}
|
|
3430
|
+
at += 1;
|
|
3431
|
+
}
|
|
3432
|
+
kept.add(bytes.subarray(scan, at));
|
|
3433
|
+
if (bytes[at + 1] === 217) {
|
|
3434
|
+
kept.add([255, 217]);
|
|
3435
|
+
return kept.bytes;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
var JFIF = Uint8Array.of(74, 70, 73, 70, 0);
|
|
3440
|
+
var ICC_PROFILE = ascii2("ICC_PROFILE\0");
|
|
3441
|
+
var ADOBE = ascii2("Adobe");
|
|
3442
|
+
var PNG_DRAWING_CHUNKS = /* @__PURE__ */ new Set([
|
|
3443
|
+
"IHDR",
|
|
3444
|
+
"PLTE",
|
|
3445
|
+
"IDAT",
|
|
3446
|
+
"IEND",
|
|
3447
|
+
"tRNS",
|
|
3448
|
+
"cHRM",
|
|
3449
|
+
"gAMA",
|
|
3450
|
+
"iCCP",
|
|
3451
|
+
"sBIT",
|
|
3452
|
+
"sRGB",
|
|
3453
|
+
"cICP",
|
|
3454
|
+
"mDCV",
|
|
3455
|
+
"cLLI",
|
|
3456
|
+
"bKGD",
|
|
3457
|
+
"hIST",
|
|
3458
|
+
"pHYs",
|
|
3459
|
+
"sPLT",
|
|
3460
|
+
"acTL",
|
|
3461
|
+
"fcTL",
|
|
3462
|
+
"fdAT"
|
|
3463
|
+
]);
|
|
3464
|
+
function pngWithoutMetadata(bytes) {
|
|
3465
|
+
const kept = new Kept(bytes.length);
|
|
3466
|
+
kept.add(bytes.subarray(0, 8));
|
|
3467
|
+
let at = 8;
|
|
3468
|
+
let orientationKept = false;
|
|
3469
|
+
while (at + 12 <= bytes.length) {
|
|
3470
|
+
const length = numberAt(bytes, at, 4);
|
|
3471
|
+
const type = typeAt(bytes, at + 4);
|
|
3472
|
+
if (length > bytes.length - at - 12) {
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
const end = at + 12 + length;
|
|
3476
|
+
if (PNG_DRAWING_CHUNKS.has(type)) {
|
|
3477
|
+
kept.add(bytes.subarray(at, end));
|
|
3478
|
+
} else if (type === "eXIf" && !orientationKept) {
|
|
3479
|
+
orientationKept = true;
|
|
3480
|
+
const orientation = exifOrientation(bytes.subarray(at + 8, at + 8 + length));
|
|
3481
|
+
if (orientation !== 1) {
|
|
3482
|
+
const data = new Uint8Array([...ascii2("eXIf"), ...orientationOnly(orientation)]);
|
|
3483
|
+
kept.add(bigEndian(data.length - 4, 4));
|
|
3484
|
+
kept.add(data);
|
|
3485
|
+
kept.add(bigEndian(crc32(data), 4));
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
if (type === "IEND") {
|
|
3489
|
+
return kept.bytes;
|
|
3490
|
+
}
|
|
3491
|
+
at = end;
|
|
3492
|
+
}
|
|
3493
|
+
return null;
|
|
3494
|
+
}
|
|
3495
|
+
var GIF_PLAYING_APPLICATIONS = [
|
|
3496
|
+
"NETSCAPE2.0",
|
|
3497
|
+
"ANIMEXTS1.0",
|
|
3498
|
+
"ICCRGBG1012"
|
|
3499
|
+
].map(ascii2);
|
|
3500
|
+
function gifColourTable(packed) {
|
|
3501
|
+
return packed & 128 ? 3 << (packed & 7) + 1 : 0;
|
|
3502
|
+
}
|
|
3503
|
+
function gifWithoutMetadata(bytes) {
|
|
3504
|
+
const subBlocksEnd = (start) => {
|
|
3505
|
+
let at2 = start;
|
|
3506
|
+
while (at2 < bytes.length) {
|
|
3507
|
+
const size = bytes[at2] ?? 0;
|
|
3508
|
+
if (size === 0) {
|
|
3509
|
+
return at2 + 1;
|
|
3510
|
+
}
|
|
3511
|
+
at2 += 1 + size;
|
|
3512
|
+
}
|
|
3513
|
+
return null;
|
|
3514
|
+
};
|
|
3515
|
+
if (bytes.length < 13) {
|
|
3516
|
+
return null;
|
|
3517
|
+
}
|
|
3518
|
+
let at = 13 + gifColourTable(bytes[10] ?? 0);
|
|
3519
|
+
if (at > bytes.length) {
|
|
3520
|
+
return null;
|
|
3521
|
+
}
|
|
3522
|
+
const kept = new Kept(bytes.length);
|
|
3523
|
+
kept.add(bytes.subarray(0, at));
|
|
3524
|
+
while (at < bytes.length) {
|
|
3525
|
+
switch (bytes[at]) {
|
|
3526
|
+
case 44: {
|
|
3527
|
+
if (at + 10 > bytes.length) {
|
|
3528
|
+
return null;
|
|
3529
|
+
}
|
|
3530
|
+
const data = at + 10 + gifColourTable(bytes[at + 9] ?? 0);
|
|
3531
|
+
const end = data < bytes.length ? subBlocksEnd(data + 1) : null;
|
|
3532
|
+
if (end === null) {
|
|
3533
|
+
return null;
|
|
3534
|
+
}
|
|
3535
|
+
kept.add(bytes.subarray(at, end));
|
|
3536
|
+
at = end;
|
|
3537
|
+
break;
|
|
3538
|
+
}
|
|
3539
|
+
case 33: {
|
|
3540
|
+
const end = at + 2 < bytes.length ? subBlocksEnd(at + 2) : null;
|
|
3541
|
+
if (end === null) {
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
const label = bytes[at + 1];
|
|
3545
|
+
const plays = label === 255 && bytes[at + 2] === 11 && end >= at + 14 && GIF_PLAYING_APPLICATIONS.some((name) => startsWith2(bytes, name, at + 3));
|
|
3546
|
+
if (label === 249 || label === 1 || plays) {
|
|
3547
|
+
kept.add(bytes.subarray(at, end));
|
|
3548
|
+
}
|
|
3549
|
+
at = end;
|
|
3550
|
+
break;
|
|
3551
|
+
}
|
|
3552
|
+
case 59:
|
|
3553
|
+
kept.add([59]);
|
|
3554
|
+
return kept.bytes;
|
|
3555
|
+
default:
|
|
3556
|
+
return null;
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
return null;
|
|
3560
|
+
}
|
|
3561
|
+
var WEBP_DRAWING_CHUNKS = /* @__PURE__ */ new Set([
|
|
3562
|
+
"VP8X",
|
|
3563
|
+
"ICCP",
|
|
3564
|
+
"ANIM",
|
|
3565
|
+
"ANMF",
|
|
3566
|
+
"ALPH",
|
|
3567
|
+
"VP8 ",
|
|
3568
|
+
"VP8L"
|
|
3569
|
+
]);
|
|
3570
|
+
function webpWithoutMetadata(bytes) {
|
|
3571
|
+
if (bytes.length < 12) {
|
|
3572
|
+
return null;
|
|
3573
|
+
}
|
|
3574
|
+
const end = 8 + numberAt(bytes, 4, 4, true);
|
|
3575
|
+
const chunks = end >= 12 && end <= bytes.length ? webpChunks(bytes.subarray(12, end)) : null;
|
|
3576
|
+
if (chunks === null || chunks.length === 0) {
|
|
3577
|
+
return null;
|
|
3578
|
+
}
|
|
3579
|
+
const kept = new Kept(chunks.length + 12);
|
|
3580
|
+
kept.add(ascii2("RIFF"));
|
|
3581
|
+
kept.add(littleEndian(chunks.length + 4, 4));
|
|
3582
|
+
kept.add(ascii2("WEBP"));
|
|
3583
|
+
kept.add(chunks);
|
|
3584
|
+
const image = kept.bytes;
|
|
3585
|
+
if (image.length > 20 && typeAt(image, 12) === "VP8X" && numberAt(image, 16, 4, true) > 0) {
|
|
3586
|
+
const exif = webpChunkTypes(image.subarray(12)).includes("EXIF");
|
|
3587
|
+
image[20] = (image[20] ?? 0) & ~12 | (exif ? 8 : 0);
|
|
3588
|
+
}
|
|
3589
|
+
return image;
|
|
3590
|
+
}
|
|
3591
|
+
function webpChunks(chunks) {
|
|
3592
|
+
const kept = new Kept(chunks.length);
|
|
3593
|
+
const frames = [];
|
|
3594
|
+
let end = chunks.length;
|
|
3595
|
+
let at = 0;
|
|
3596
|
+
let orientationKept = false;
|
|
3597
|
+
for (; ; ) {
|
|
3598
|
+
if (at >= end) {
|
|
3599
|
+
const frame = frames.pop();
|
|
3600
|
+
if (frame === void 0) {
|
|
3601
|
+
return kept.bytes;
|
|
3602
|
+
}
|
|
3603
|
+
kept.set(frame.size, littleEndian(kept.length - frame.size - 4, 4));
|
|
3604
|
+
at = frame.resume;
|
|
3605
|
+
end = frames.at(-1)?.end ?? chunks.length;
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
if (end - at < 8) {
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
const type = typeAt(chunks, at);
|
|
3612
|
+
const size = numberAt(chunks, at + 4, 4, true);
|
|
3613
|
+
if (size > end - at - 8) {
|
|
3614
|
+
return null;
|
|
3615
|
+
}
|
|
3616
|
+
const data = at + 8;
|
|
3617
|
+
at = Math.min(data + size + size % 2, end);
|
|
3618
|
+
if (type === "ANMF") {
|
|
3619
|
+
if (size < 16) {
|
|
3620
|
+
return null;
|
|
3621
|
+
}
|
|
3622
|
+
frames.push({ end: data + size, resume: at, size: kept.length + 4 });
|
|
3623
|
+
kept.add(chunks.subarray(data - 8, data + 16));
|
|
3624
|
+
at = data + 16;
|
|
3625
|
+
end = data + size;
|
|
3626
|
+
} else if (WEBP_DRAWING_CHUNKS.has(type)) {
|
|
3627
|
+
kept.add(chunks.subarray(data - 8, data + size));
|
|
3628
|
+
if (size % 2 === 1) {
|
|
3629
|
+
kept.add([0]);
|
|
3630
|
+
}
|
|
3631
|
+
} else if (type === "EXIF" && frames.length === 0 && !orientationKept) {
|
|
3632
|
+
orientationKept = true;
|
|
3633
|
+
const turned = exifOrientation(chunks.subarray(data, data + size));
|
|
3634
|
+
if (turned !== 1) {
|
|
3635
|
+
const exif = orientationOnly(turned);
|
|
3636
|
+
kept.add(ascii2(type));
|
|
3637
|
+
kept.add(littleEndian(exif.length, 4));
|
|
3638
|
+
kept.add(exif);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3643
|
+
function webpChunkTypes(chunks) {
|
|
3644
|
+
const types = [];
|
|
3645
|
+
let at = 0;
|
|
3646
|
+
while (chunks.length - at >= 8) {
|
|
3647
|
+
types.push(typeAt(chunks, at));
|
|
3648
|
+
const size = numberAt(chunks, at + 4, 4, true);
|
|
3649
|
+
at += 8 + size + size % 2;
|
|
3650
|
+
}
|
|
3651
|
+
return types;
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3184
3654
|
// packages/board-core/dist/moves.js
|
|
3185
3655
|
import { mkdir as mkdir4, rm as rm2 } from "node:fs/promises";
|
|
3186
3656
|
import path5 from "node:path";
|
|
@@ -3623,6 +4093,72 @@ function tokenUse(events, window, now) {
|
|
|
3623
4093
|
};
|
|
3624
4094
|
}
|
|
3625
4095
|
|
|
4096
|
+
// packages/board-core/dist/turn-counters.js
|
|
4097
|
+
var TOKEN_KINDS = ["input", "output", "cache_read", "cache_write"];
|
|
4098
|
+
var USAGE_FIELD = {
|
|
4099
|
+
input: "inputTokens",
|
|
4100
|
+
output: "outputTokens",
|
|
4101
|
+
cache_read: "cacheReadTokens",
|
|
4102
|
+
cache_write: "cacheWriteTokens"
|
|
4103
|
+
};
|
|
4104
|
+
var UNKNOWN_LABEL = "unknown";
|
|
4105
|
+
function byKey(entries) {
|
|
4106
|
+
return [...entries].toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, series]) => series);
|
|
4107
|
+
}
|
|
4108
|
+
var TurnCounters = class {
|
|
4109
|
+
cliOf;
|
|
4110
|
+
tokens = /* @__PURE__ */ new Map();
|
|
4111
|
+
turns = /* @__PURE__ */ new Map();
|
|
4112
|
+
/**
|
|
4113
|
+
* `cliOf` names a citizen's CLI for an end logged before ends named theirs: a turn runs on the
|
|
4114
|
+
* CLI its citizen's record names, which nothing changes after the citizen is added.
|
|
4115
|
+
*/
|
|
4116
|
+
constructor(cliOf) {
|
|
4117
|
+
this.cliOf = cliOf;
|
|
4118
|
+
}
|
|
4119
|
+
/** Counts a turn's end, `turn.completed` or `turn.failed`, and passes over any other event. */
|
|
4120
|
+
add(event) {
|
|
4121
|
+
if (event.type !== "turn.completed" && event.type !== "turn.failed") {
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
4124
|
+
const turn = TurnHistoryPayloadSchema.safeParse(event.payload);
|
|
4125
|
+
if (!turn.success) {
|
|
4126
|
+
return;
|
|
4127
|
+
}
|
|
4128
|
+
const agent = event.actor;
|
|
4129
|
+
const outcome = event.type === "turn.failed" ? "failed" : "completed";
|
|
4130
|
+
const trigger = turn.data.trigger;
|
|
4131
|
+
const counted = JSON.stringify([agent, outcome, trigger]);
|
|
4132
|
+
this.turns.set(counted, {
|
|
4133
|
+
agent,
|
|
4134
|
+
outcome,
|
|
4135
|
+
trigger,
|
|
4136
|
+
turns: (this.turns.get(counted)?.turns ?? 0) + 1
|
|
4137
|
+
});
|
|
4138
|
+
const usage = turn.data.usage;
|
|
4139
|
+
if (usage === void 0) {
|
|
4140
|
+
return;
|
|
4141
|
+
}
|
|
4142
|
+
const named = CliKindSchema.safeParse(event.payload["cli"]);
|
|
4143
|
+
const cli = (named.success ? named.data : this.cliOf(agent)) ?? UNKNOWN_LABEL;
|
|
4144
|
+
const model = turn.data.model ?? UNKNOWN_LABEL;
|
|
4145
|
+
for (const kind of TOKEN_KINDS) {
|
|
4146
|
+
const key = JSON.stringify([agent, cli, model, kind]);
|
|
4147
|
+
this.tokens.set(key, {
|
|
4148
|
+
agent,
|
|
4149
|
+
cli,
|
|
4150
|
+
model,
|
|
4151
|
+
kind,
|
|
4152
|
+
tokens: (this.tokens.get(key)?.tokens ?? 0) + usage[USAGE_FIELD[kind]]
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
/** The counts so far, in an order that holds from one read to the next. */
|
|
4157
|
+
read() {
|
|
4158
|
+
return { tokens: byKey(this.tokens), turns: byKey(this.turns) };
|
|
4159
|
+
}
|
|
4160
|
+
};
|
|
4161
|
+
|
|
3626
4162
|
// packages/board-core/dist/mutex.js
|
|
3627
4163
|
var Mutex = class {
|
|
3628
4164
|
tail = Promise.resolve();
|
|
@@ -3982,6 +4518,7 @@ function hashToken(token) {
|
|
|
3982
4518
|
// packages/board-core/dist/board.js
|
|
3983
4519
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3984
4520
|
var CLOSING_ATTEMPTS = 3;
|
|
4521
|
+
var CHANNEL_POST_WAIT_MS = 24 * 36e5;
|
|
3985
4522
|
function ownTurn(actor, turn) {
|
|
3986
4523
|
return turn.agent === actor.name && turn.scope === actor.scope && turn.thread === actor.thread && turn.channel === actor.channel && turn.place === actor.place;
|
|
3987
4524
|
}
|
|
@@ -5296,10 +5833,13 @@ ${DASHBOARD_LINE}
|
|
|
5296
5833
|
* when it mentions the user, so the creator can put a wait on the user in; a post that asks the
|
|
5297
5834
|
* reader counts wherever it is. A closed thread counts for nothing: its conversation has ended,
|
|
5298
5835
|
* and a turn there could only read its posts and be refused a reply, while a wake queued before
|
|
5299
|
-
* it closed, as for a mention, still runs there.
|
|
5836
|
+
* it closed, as for a mention, still runs there. A top-level post in a project's channel, general
|
|
5837
|
+
* or a workstream's, counts only for a reader it asks until it is `channelPostWaitMs` old, and
|
|
5838
|
+
* then for every reader who has not read it, so a turn there for anything else reads it sooner.
|
|
5300
5839
|
*/
|
|
5301
|
-
async unreadByConversation(actor) {
|
|
5840
|
+
async unreadByConversation(actor, options = {}) {
|
|
5302
5841
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
5842
|
+
const waited = this.now().getTime() - (options.channelPostWaitMs ?? CHANNEL_POST_WAIT_MS);
|
|
5303
5843
|
const counts = /* @__PURE__ */ new Map();
|
|
5304
5844
|
const threadCounts = /* @__PURE__ */ new Map();
|
|
5305
5845
|
const unscoped = { name: actor.name, role: actor.role };
|
|
@@ -5307,11 +5847,15 @@ ${DASHBOARD_LINE}
|
|
|
5307
5847
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name || message.notice?.kind === "knowledge") {
|
|
5308
5848
|
continue;
|
|
5309
5849
|
}
|
|
5310
|
-
const
|
|
5850
|
+
const project = parseChannelRef(message.channel).project;
|
|
5851
|
+
const posted = project ?? SOCIETY_SCOPE;
|
|
5311
5852
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
5312
5853
|
continue;
|
|
5313
5854
|
}
|
|
5314
5855
|
const thread = threadOfPart(part);
|
|
5856
|
+
if (thread === void 0 && project !== null && asked !== true && Date.parse(message.ts) > waited) {
|
|
5857
|
+
continue;
|
|
5858
|
+
}
|
|
5315
5859
|
if (thread !== void 0) {
|
|
5316
5860
|
let counted = threadCounts.get(thread);
|
|
5317
5861
|
if (counted === void 0) {
|
|
@@ -5735,9 +6279,9 @@ ${current.body}`);
|
|
|
5735
6279
|
/**
|
|
5736
6280
|
* Keeps an image a citizen's turn names by its path, read on the runner the turn runs on, which
|
|
5737
6281
|
* refuses a file outside the turn's worktree, the citizen's home, and the system temp folder.
|
|
5738
|
-
* Only
|
|
5739
|
-
*
|
|
5740
|
-
* mirrors
|
|
6282
|
+
* Only a PNG, JPEG, GIF, or WebP of at most `IMAGE_LIMIT_BYTES` is kept, checked here again
|
|
6283
|
+
* whatever the runner said, without its metadata, under a new id outside the projection, so no
|
|
6284
|
+
* runner mirrors it and no prompt carries it: a post shows the image by its `image:` address.
|
|
5741
6285
|
*/
|
|
5742
6286
|
async uploadImage(actor, input) {
|
|
5743
6287
|
const args = VerbInputs.upload_image.parse(input);
|
|
@@ -5763,8 +6307,13 @@ ${current.body}`);
|
|
|
5763
6307
|
}
|
|
5764
6308
|
/**
|
|
5765
6309
|
* Keeps `bytes` under a new id when they are a PNG, JPEG, GIF, or WebP of at most
|
|
5766
|
-
* `IMAGE_LIMIT_BYTES
|
|
5767
|
-
* names the image in a refusal.
|
|
6310
|
+
* `IMAGE_LIMIT_BYTES` that reads to its end, without its metadata (`withoutMetadata`), and
|
|
6311
|
+
* answers the markdown that shows it, described by `name`. `what` names the image in a refusal.
|
|
6312
|
+
* The limit is for the bytes as they came, as the route's body cap measures them, and bounds
|
|
6313
|
+
* what is read; what is kept is checked against it too, since an orientation written smaller
|
|
6314
|
+
* than the board writes it grows by a few bytes. The runner, the web's composer, and the app
|
|
6315
|
+
* strip an image before they send it, and stripping it again changes nothing, but an older
|
|
6316
|
+
* runner, page, or app sends one as it was.
|
|
5768
6317
|
*/
|
|
5769
6318
|
async keepImage(actor, bytes, what, name) {
|
|
5770
6319
|
const type = imageTypeOf(bytes);
|
|
@@ -5774,19 +6323,26 @@ ${current.body}`);
|
|
|
5774
6323
|
if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
|
|
5775
6324
|
throw new BoardError("VALIDATION", `${what} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
|
|
5776
6325
|
}
|
|
6326
|
+
const kept = withoutMetadata(bytes);
|
|
6327
|
+
if (kept === null) {
|
|
6328
|
+
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`);
|
|
6329
|
+
}
|
|
6330
|
+
if (kept.byteLength > IMAGE_LIMIT_BYTES) {
|
|
6331
|
+
throw new BoardError("VALIDATION", `${what} is ${kept.byteLength} bytes without its metadata, over the ${IMAGE_LIMIT_BYTES} an image may be`);
|
|
6332
|
+
}
|
|
5777
6333
|
return this.mutex.run(async () => {
|
|
5778
6334
|
const id = this.newId();
|
|
5779
|
-
await writeFileAtomic(this.paths.image(id),
|
|
6335
|
+
await writeFileAtomic(this.paths.image(id), kept);
|
|
5780
6336
|
await this.events.append("image.uploaded", actor.name, {
|
|
5781
6337
|
id,
|
|
5782
6338
|
type,
|
|
5783
|
-
size:
|
|
6339
|
+
size: kept.byteLength
|
|
5784
6340
|
});
|
|
5785
6341
|
const description = imageDescription(name);
|
|
5786
6342
|
return {
|
|
5787
6343
|
id,
|
|
5788
6344
|
type,
|
|
5789
|
-
size:
|
|
6345
|
+
size: kept.byteLength,
|
|
5790
6346
|
markdown: ``
|
|
5791
6347
|
};
|
|
5792
6348
|
});
|
|
@@ -7087,6 +7643,39 @@ ${rationale}`}`);
|
|
|
7087
7643
|
const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
|
|
7088
7644
|
return tokenUse(events, window, this.now());
|
|
7089
7645
|
}
|
|
7646
|
+
/**
|
|
7647
|
+
* Every finished turn counted for Prometheus: seeded once from the whole event log, then moved
|
|
7648
|
+
* by each turn's end this process appends, so reading the counts never reads the log. The server
|
|
7649
|
+
* logs every turn's end itself; one another process appended would count from the next seed.
|
|
7650
|
+
*/
|
|
7651
|
+
async countTurns() {
|
|
7652
|
+
const clis = /* @__PURE__ */ new Map();
|
|
7653
|
+
const counters = new TurnCounters((agent) => clis.get(agent) ?? null);
|
|
7654
|
+
let waiting = [];
|
|
7655
|
+
this.events.onAppend((event) => {
|
|
7656
|
+
if (waiting === null) {
|
|
7657
|
+
counters.add(event);
|
|
7658
|
+
} else {
|
|
7659
|
+
waiting.push(event);
|
|
7660
|
+
}
|
|
7661
|
+
});
|
|
7662
|
+
const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
|
|
7663
|
+
for (const agent of await this.listAgents()) {
|
|
7664
|
+
clis.set(agent.name, agent.cli);
|
|
7665
|
+
}
|
|
7666
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7667
|
+
for (const event of events) {
|
|
7668
|
+
seen.add(event.id);
|
|
7669
|
+
counters.add(event);
|
|
7670
|
+
}
|
|
7671
|
+
for (const event of waiting) {
|
|
7672
|
+
if (!seen.has(event.id)) {
|
|
7673
|
+
counters.add(event);
|
|
7674
|
+
}
|
|
7675
|
+
}
|
|
7676
|
+
waiting = null;
|
|
7677
|
+
return counters;
|
|
7678
|
+
}
|
|
7090
7679
|
/** The board actions a finished turn took, from its transcript; null when it kept none. */
|
|
7091
7680
|
async turnActions(agent, turnId) {
|
|
7092
7681
|
const known = this.actionCounts.get(turnId);
|
|
@@ -7284,6 +7873,7 @@ ${rationale}`}`);
|
|
|
7284
7873
|
trigger: parsed.trigger.kind,
|
|
7285
7874
|
...parsed.trigger.heartbeat === void 0 ? {} : { heartbeat: parsed.trigger.heartbeat },
|
|
7286
7875
|
session: parsed.session,
|
|
7876
|
+
cli: parsed.cli,
|
|
7287
7877
|
exitReason: parsed.exitReason,
|
|
7288
7878
|
costUsd: parsed.costUsd,
|
|
7289
7879
|
...parsed.usage === null ? {} : { usage: parsed.usage },
|
|
@@ -9219,6 +9809,7 @@ var DEFAULT_TIMINGS = Object.freeze({
|
|
|
9219
9809
|
debounceMs: 3e4,
|
|
9220
9810
|
userDebounceMs: 5e3,
|
|
9221
9811
|
heartbeatMs: 15 * 6e4,
|
|
9812
|
+
channelPostWaitMs: CHANNEL_POST_WAIT_MS,
|
|
9222
9813
|
waitingStageMs: 10 * 6e4,
|
|
9223
9814
|
leaseSweepMs: 6e4,
|
|
9224
9815
|
opsIntervalMs: 5 * 6e4,
|
|
@@ -9238,6 +9829,7 @@ var SchedulerTimingsSchema = z17.object({
|
|
|
9238
9829
|
debounceMs: z17.number().nonnegative(),
|
|
9239
9830
|
userDebounceMs: z17.number().nonnegative(),
|
|
9240
9831
|
heartbeatMs: z17.number().positive(),
|
|
9832
|
+
channelPostWaitMs: z17.number().nonnegative(),
|
|
9241
9833
|
waitingStageMs: z17.number().positive(),
|
|
9242
9834
|
leaseSweepMs: z17.number().positive(),
|
|
9243
9835
|
opsIntervalMs: z17.number().positive(),
|
|
@@ -9306,10 +9898,14 @@ var StateSchema = z17.object({
|
|
|
9306
9898
|
/** When each member last reflected, or was first seen; the cadence counts from here. */
|
|
9307
9899
|
lastReflection: z17.record(z17.string(), z17.string()).default({}),
|
|
9308
9900
|
/**
|
|
9309
|
-
* The
|
|
9310
|
-
*
|
|
9901
|
+
* The session each home last asked for an early reflection for, once per session, by the home's
|
|
9902
|
+
* `sessionKey`, and whether that reflection still waits to be queued. A key of a member's name
|
|
9903
|
+
* alone, kept before a project's homes asked, is its society home.
|
|
9311
9904
|
*/
|
|
9312
|
-
earlyReflection: z17.record(z17.string(), z17.object({ session: z17.string(), due: z17.boolean() })).default({})
|
|
9905
|
+
earlyReflection: z17.record(z17.string(), z17.object({ session: z17.string(), due: z17.boolean() })).default({}).transform((early) => Object.fromEntries(Object.entries(early).map(([key, asked]) => [
|
|
9906
|
+
key.includes("/") ? key : sessionKey(key, SOCIETY_SCOPE),
|
|
9907
|
+
asked
|
|
9908
|
+
])))
|
|
9313
9909
|
});
|
|
9314
9910
|
function messageOf(trigger) {
|
|
9315
9911
|
return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
|
|
@@ -9398,7 +9994,7 @@ var Scheduler = class {
|
|
|
9398
9994
|
sweeping = /* @__PURE__ */ new Set();
|
|
9399
9995
|
/** Crons whose fire's turn is running; a cron's next time passes while one is. */
|
|
9400
9996
|
cronsInFlight = /* @__PURE__ */ new Set();
|
|
9401
|
-
/** Turns in a row
|
|
9997
|
+
/** Turns in a row that failed for now, by session key, which `retryRefused` counts. */
|
|
9402
9998
|
refused = /* @__PURE__ */ new Map();
|
|
9403
9999
|
state = StateSchema.parse({
|
|
9404
10000
|
cursor: null,
|
|
@@ -10258,7 +10854,9 @@ var Scheduler = class {
|
|
|
10258
10854
|
continue;
|
|
10259
10855
|
}
|
|
10260
10856
|
this.state.lastHeartbeat[cadence] = iso(now);
|
|
10261
|
-
unread ??= await this.board.unreadByConversation(actor
|
|
10857
|
+
unread ??= await this.board.unreadByConversation(actor, {
|
|
10858
|
+
channelPostWaitMs: this.timings.channelPostWaitMs
|
|
10859
|
+
});
|
|
10262
10860
|
held ??= await this.board.heldClaims(agent.name);
|
|
10263
10861
|
pendingCrons ??= (await this.board.listCrons()).filter((cron) => cronStoppedAt(cron) === null);
|
|
10264
10862
|
const crons = pendingCrons;
|
|
@@ -10399,18 +10997,20 @@ var Scheduler = class {
|
|
|
10399
10997
|
}
|
|
10400
10998
|
}
|
|
10401
10999
|
/**
|
|
10402
|
-
* A
|
|
10403
|
-
*
|
|
10404
|
-
*
|
|
11000
|
+
* A home session that a turn left at `sessionCapTokens` or more, by its latest model call, asks
|
|
11001
|
+
* for a reflection ahead of the cadence, which `checkReflections` queues in that home once none
|
|
11002
|
+
* of the member's turns is queued or running: a society role's home in the society scope, and
|
|
11003
|
+
* the home of a project's member whose role reflects. The reflection's end starts the home afresh
|
|
10405
11004
|
* (`TurnHost`), so its history goes only once a reflection saved what to keep. A session asks
|
|
10406
11005
|
* once, so a reflection that fails leaves the next to the cadence, and none asks while a
|
|
10407
|
-
* reflection is queued or running.
|
|
11006
|
+
* reflection is queued or running in its home.
|
|
10408
11007
|
*/
|
|
10409
11008
|
async checkSessionSize(event) {
|
|
10410
11009
|
const { payload } = event;
|
|
10411
11010
|
const tokens = payload["contextTokens"];
|
|
10412
11011
|
const session = stringOf(payload["session"]);
|
|
10413
|
-
|
|
11012
|
+
const project = stringOf(payload["project"]);
|
|
11013
|
+
if (typeof tokens !== "number" || tokens < this.timings.sessionCapTokens || session === null || project === null || payload["thread"] !== void 0 || payload["channel"] !== void 0 || payload["place"] !== void 0 || payload["trigger"] === "reflection" || payload["sessionDropped"] === true) {
|
|
10414
11014
|
return;
|
|
10415
11015
|
}
|
|
10416
11016
|
const agent = await this.tryReadAgent(event.actor);
|
|
@@ -10418,24 +11018,33 @@ var Scheduler = class {
|
|
|
10418
11018
|
return;
|
|
10419
11019
|
}
|
|
10420
11020
|
const charter = await this.board.readRole(agent.role).catch(() => null);
|
|
10421
|
-
if (charter === null || !charter.
|
|
11021
|
+
if (charter === null || !charter.reflects || !this.isHome(agent, charter, project)) {
|
|
10422
11022
|
return;
|
|
10423
11023
|
}
|
|
10424
|
-
|
|
11024
|
+
const home = sessionKey(agent.name, project);
|
|
11025
|
+
if (this.state.earlyReflection[home]?.session === session) {
|
|
10425
11026
|
return;
|
|
10426
11027
|
}
|
|
10427
|
-
const
|
|
10428
|
-
const reflecting = [...this.pending].some(([key, item]) => ofAgent(key) && item.dispatch.trigger.kind === "reflection") || [...this.exclusive].some(([key, kind]) => ofAgent(key) && kind === "reflection");
|
|
11028
|
+
const reflecting = this.pending.get(home)?.dispatch.trigger.kind === "reflection" || this.exclusive.get(home) === "reflection";
|
|
10429
11029
|
if (reflecting) {
|
|
10430
11030
|
return;
|
|
10431
11031
|
}
|
|
10432
|
-
this.state.earlyReflection[
|
|
10433
|
-
this.
|
|
11032
|
+
delete this.state.earlyReflection[home];
|
|
11033
|
+
this.state.earlyReflection[home] = { session, due: true };
|
|
11034
|
+
this.log.info({ agent: agent.name, project, contextTokens: tokens, cap: this.timings.sessionCapTokens }, "home session past the cap; a reflection is due");
|
|
11035
|
+
}
|
|
11036
|
+
/**
|
|
11037
|
+
* Whether a member's home in a scope starts afresh after a reflection, as `TurnHost` decides: a
|
|
11038
|
+
* society role's in the society scope, and its own in each project it belongs to.
|
|
11039
|
+
*/
|
|
11040
|
+
isHome(agent, charter, scope) {
|
|
11041
|
+
return scope === SOCIETY_SCOPE ? charter.societyScope : agent.memberships.includes(scope);
|
|
10434
11042
|
}
|
|
10435
11043
|
/**
|
|
10436
11044
|
* Every `reflectionMs`, a member whose charter reflects takes a reflection turn in the scope of
|
|
10437
|
-
* its latest working turn, unless it has not worked since its last reflection; a
|
|
10438
|
-
* whose
|
|
11045
|
+
* its latest working turn, unless it has not worked since its last reflection; a member one of
|
|
11046
|
+
* whose homes passed the cap takes one in that home ahead of the cadence, which restarts it, one
|
|
11047
|
+
* home at a time, in the order they asked.
|
|
10439
11048
|
*/
|
|
10440
11049
|
async checkReflections(now) {
|
|
10441
11050
|
for (const agent of await this.board.listAgents()) {
|
|
@@ -10446,12 +11055,18 @@ var Scheduler = class {
|
|
|
10446
11055
|
if (!charter.reflects) {
|
|
10447
11056
|
continue;
|
|
10448
11057
|
}
|
|
10449
|
-
const
|
|
10450
|
-
if (
|
|
11058
|
+
const due = Object.entries(this.state.earlyReflection).filter(([key, early]) => early.due && parseSessionKey(key)?.agent === agent.name);
|
|
11059
|
+
if (due.length > 0) {
|
|
10451
11060
|
if (!this.busy(agent.name)) {
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
|
|
11061
|
+
for (const [key, early] of due) {
|
|
11062
|
+
this.state.earlyReflection[key] = { ...early, due: false };
|
|
11063
|
+
const scope = parseSessionKey(key)?.scope;
|
|
11064
|
+
if (scope !== void 0 && this.isHome(agent, charter, scope)) {
|
|
11065
|
+
this.state.lastReflection[agent.name] = iso(now);
|
|
11066
|
+
this.enqueue(agent.name, scope, { kind: "reflection", reason: "home session past the cap" }, now);
|
|
11067
|
+
break;
|
|
11068
|
+
}
|
|
11069
|
+
}
|
|
10455
11070
|
}
|
|
10456
11071
|
continue;
|
|
10457
11072
|
}
|
|
@@ -10958,19 +11573,21 @@ var Scheduler = class {
|
|
|
10958
11573
|
this.exclusive.set(key, "onboarding");
|
|
10959
11574
|
} else if (dispatch.trigger.kind === "reflection") {
|
|
10960
11575
|
this.exclusive.set(key, "reflection");
|
|
10961
|
-
const early = this.state.earlyReflection[
|
|
11576
|
+
const early = this.state.earlyReflection[key];
|
|
10962
11577
|
if (early?.due === true) {
|
|
10963
|
-
this.state.earlyReflection[
|
|
11578
|
+
this.state.earlyReflection[key] = { ...early, due: false };
|
|
10964
11579
|
}
|
|
10965
11580
|
}
|
|
10966
11581
|
}
|
|
10967
11582
|
}
|
|
10968
11583
|
/**
|
|
10969
|
-
* A turn the model service turned away
|
|
10970
|
-
*
|
|
10971
|
-
*
|
|
10972
|
-
*
|
|
10973
|
-
*
|
|
11584
|
+
* A turn that failed for now is sent again: the model service turned it away or cut it off, the
|
|
11585
|
+
* runner could not reach the board before its CLI started, or its job never reached the runner.
|
|
11586
|
+
* The same wake goes in the same conversation, after the next delay of `transientRetryMs`, with
|
|
11587
|
+
* every check a queued wake gets at dispatch. A wake already queued there, as a heartbeat's or a
|
|
11588
|
+
* new post's, runs the conversation instead. Once the delays are spent the heartbeat takes over,
|
|
11589
|
+
* and a `turn_refused` signal, which wakes nobody, says so. A turn that ends any other way ends
|
|
11590
|
+
* the run.
|
|
10974
11591
|
*/
|
|
10975
11592
|
async retryRefused(key, item, record) {
|
|
10976
11593
|
if (record.transient !== true || record.exitReason !== "error") {
|
|
@@ -10987,7 +11604,7 @@ var Scheduler = class {
|
|
|
10987
11604
|
}
|
|
10988
11605
|
const { steeredThrough: _steered, ...retry } = item;
|
|
10989
11606
|
this.pending.set(key, { ...retry, readyAt: this.now().getTime() + delay });
|
|
10990
|
-
this.log.info({ agent: dispatch.agent, project: dispatch.project, refused, delayMs: delay }, "the
|
|
11607
|
+
this.log.info({ agent: dispatch.agent, project: dispatch.project, refused, delayMs: delay }, "the turn failed for now, sending its wake again");
|
|
10991
11608
|
return;
|
|
10992
11609
|
}
|
|
10993
11610
|
if (refused !== this.timings.transientRetryMs.length + 1) {
|
|
@@ -10998,7 +11615,7 @@ var Scheduler = class {
|
|
|
10998
11615
|
await this.board.publishSignal({
|
|
10999
11616
|
kind: "turn_refused",
|
|
11000
11617
|
key: `turn_refused:${key}`,
|
|
11001
|
-
summary: `${dispatch.agent}'s turns in ${where}
|
|
11618
|
+
summary: `${dispatch.agent}'s turns in ${where} failed for now ${refused} times in a row${record.model === null ? "" : ` on ${record.model}`}, the last with "${record.error ?? "no error"}"; its heartbeat takes over`,
|
|
11002
11619
|
value: refused,
|
|
11003
11620
|
threshold: refused,
|
|
11004
11621
|
agent: dispatch.agent,
|
|
@@ -11099,6 +11716,7 @@ var TURN_CONTRACT = [
|
|
|
11099
11716
|
"- To come back to something later, set a cron with create_cron: a note on what to do then, and a five-field cron expression or one time with at. It wakes you in this conversation unless you name another, at most every fifteen minutes, and every fire is a paid turn, so end a cron with remove_cron once it has done its job. Never wait by sleeping or leaving a process running: a turn's board token ends with the turn, so nothing it leaves behind can act on the board. While you check your own stage's work on a schedule, such as a running job, CI, or a deploy, keep holding the stage: the cron sets when you check, and while it is pending in the task's thread, neither ended nor paused, with its latest fire's turn not failed or a turn of yours there completed since, the board keeps your lease and the heartbeat leaves the stage to it, waking you there only for a post you have not read. Release a stage only to hand it on, or once its task's blocked_by names the tasks it waits on: the stage then wakes nobody until the last of them ends. Any other wait on someone else, such as the user or an outside party, is a stage of its own, not a held stage. The society's crons are under society/crons/ in the board projection.",
|
|
11100
11717
|
"- An @mention wakes the citizen named, and every wake costs a turn. Address someone with @ only when you need them to act; when you merely refer to citizens, write their names plainly. A name in a code span or a code block is no mention, so code keeps its @ as written. @user wakes nobody, since the user takes no turns; it is what puts a question before the user, so use it only when you need the user to answer or decide.",
|
|
11101
11718
|
"- A thread is a conversation off a channel that reaches only its participants and whoever is mentioned in it. Every task has one, opened with it under the task's id on the channel of its project it was filed in, general unless it was filed in another: talk about a task there, with post_message and its thread_id, and the notes you give advance_task and update_task are posted there as you. Its participants are the task's creator, its stage holders and assignees, whoever may take the stage that waits, and anyone who posted; it closes when the task ends. In a task's thread, only the current stage's holder, or while nobody holds it whoever may take it, wakes for your post, and a post that asks the user also wakes the task's creator; mention anyone else you need, including the concierge for a plan change. Every proposal has one too, in governance, opened with its pitch and closed by its decision. Open other threads with open_thread on a channel with a title, for any other topic; close_thread posts your summary to its channel.",
|
|
11719
|
+
"- A post at the top of a project's channel, general or another, wakes only whoever it mentions; everyone else reads it at their next turn there, at the latest about a day later. So mention whoever must act on it or know it soon.",
|
|
11102
11720
|
"- To show an image in a post, such as a screenshot or a chart, save it anywhere you can read it, such as a folder from `mktemp -d`, call upload_image with its path, and put the markdown it returns in the post, ``, with what the image shows as its alt text: the board keeps the image, so it shows in any conversation after the file is gone. It takes a PNG, JPEG, GIF, or WebP of at most 8 MB from your worktree, your home, or the system temp folder. An image committed on a task's branch, linked by its path in the task's worktree, still shows in place, and so does one at an https address. Never put an image or any other file into a message as base64 or a data: URL: the board does not show it, and every reader of the conversation gets it in full in their prompt. To see an image a post, a brief, or a topic shows as `image:<id>`, call read_image with the id: it returns the path of a file of this turn's own that holds the image, removed when the turn ends, which you open with the tool that shows you an image, Read in Claude Code or view_image in Codex.",
|
|
11103
11721
|
"- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; how a codebase behaves (architecture, conventions, gotchas, how to build, test, and verify it) goes in its AGENTS.md or README, committed on a task's branch like any change to the code, or proposed in the task's thread or the project channel when it is outside the task at hand; a durable fact about a project as a place (runners, access, flow, where results live) goes through `write_knowledge` on that project; something the whole project should know now, post it to the project channel, and anything about one task, in the task's thread.",
|
|
11104
11722
|
'- A procedure you have followed twice is a candidate for a skill, which it is when it makes a citizen better at a kind of work on any project: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when more than one citizen would use it and nothing in it is tied to one place; a skill only you use stays yours, however many projects you use it on.',
|
|
@@ -11652,7 +12270,7 @@ var TurnHost = class {
|
|
|
11652
12270
|
const costSoFarUsd = !newSession && lastTurn?.session === recorded ? lastTurn.sessionCostUsd ?? lastTurn.costUsd : 0;
|
|
11653
12271
|
const usageSoFar = !newSession && lastTurn?.session === recorded ? lastTurn.sessionUsage : void 0;
|
|
11654
12272
|
const resumeRetries = !newSession && lastTurn?.session === recorded && lastTurn.resumeFailed === "retry" ? lastTurn.resumeRetries ?? 1 : 0;
|
|
11655
|
-
const afreshAfterReflection =
|
|
12273
|
+
const afreshAfterReflection = agent.role !== USER_ROLE && part === void 0 && (societyScope ? charter.societyScope : charter.reflects);
|
|
11656
12274
|
const afterReflection = lastTurn?.trigger.kind === "reflection" && lastTurn.sessionDropped === true;
|
|
11657
12275
|
const digest = await this.board.readDigest({
|
|
11658
12276
|
...actor,
|
|
@@ -12088,8 +12706,11 @@ var TurnHost = class {
|
|
|
12088
12706
|
}
|
|
12089
12707
|
return { dropWorkspace: ended && (closing ? decided : !outcome.leftovers) };
|
|
12090
12708
|
}
|
|
12091
|
-
/**
|
|
12092
|
-
|
|
12709
|
+
/**
|
|
12710
|
+
* Ends a turn whose outcome will never come, as when its runner went away, as failed; `transient`
|
|
12711
|
+
* when its job never reached the runner, so the same wake may pass in a minute.
|
|
12712
|
+
*/
|
|
12713
|
+
async abortTurn(turnId, reason, options = {}) {
|
|
12093
12714
|
const turn = this.open.get(turnId);
|
|
12094
12715
|
if (turn === void 0) {
|
|
12095
12716
|
return;
|
|
@@ -12104,7 +12725,8 @@ var TurnHost = class {
|
|
|
12104
12725
|
endedAt: this.now().toISOString(),
|
|
12105
12726
|
exitReason: "error",
|
|
12106
12727
|
error: reason,
|
|
12107
|
-
toolCalls: turn.transcript.filter((entry) => entry.event.type === "tool_call").length
|
|
12728
|
+
toolCalls: turn.transcript.filter((entry) => entry.event.type === "tool_call").length,
|
|
12729
|
+
...options.transient === true ? { transient: true } : {}
|
|
12108
12730
|
};
|
|
12109
12731
|
if (await this.recorded(turn, () => this.board.finishTurn(finished, turn.transcript))) {
|
|
12110
12732
|
this.log.warn({ agent: turn.job.agent, project: turn.job.scope, reason }, "turn aborted");
|
|
@@ -12450,7 +13072,12 @@ var RunnerHub = class {
|
|
|
12450
13072
|
this.log.info({ runner: name, version: hello.version, clis: hello.clis, slots: hello.slots }, "runner registered");
|
|
12451
13073
|
return { name, version: this.version };
|
|
12452
13074
|
}
|
|
12453
|
-
/**
|
|
13075
|
+
/**
|
|
13076
|
+
* A registered runner's stream opened. Returns what to call when it closes. A turn its hello
|
|
13077
|
+
* listed that the board has ended, as one whose runner came back after the grace period, is told
|
|
13078
|
+
* to stop before anything else goes down the stream, since its CLI still works there; a runner
|
|
13079
|
+
* that lists no CLI it can stop, as one from before stops, is sent none.
|
|
13080
|
+
*/
|
|
12454
13081
|
async attach(name, send) {
|
|
12455
13082
|
const seat = this.seats.get(name);
|
|
12456
13083
|
if (seat === void 0) {
|
|
@@ -12462,6 +13089,13 @@ var RunnerHub = class {
|
|
|
12462
13089
|
clearTimeout(seat.grace);
|
|
12463
13090
|
seat.grace = null;
|
|
12464
13091
|
}
|
|
13092
|
+
if (seat.hello.stoppableClis.length > 0) {
|
|
13093
|
+
for (const turnId of seat.hello.turns) {
|
|
13094
|
+
if (this.host.runnerOf(turnId) !== name) {
|
|
13095
|
+
void this.stopEnded(seat, turnId);
|
|
13096
|
+
}
|
|
13097
|
+
}
|
|
13098
|
+
}
|
|
12465
13099
|
for (const { job } of seat.unacked.values()) {
|
|
12466
13100
|
if (send({ type: "turn", job })) {
|
|
12467
13101
|
this.expectAck(seat, job);
|
|
@@ -12745,7 +13379,7 @@ var RunnerHub = class {
|
|
|
12745
13379
|
return opened.failed;
|
|
12746
13380
|
}
|
|
12747
13381
|
if (seat.send === null || !seat.send({ type: "turn", job: opened.job })) {
|
|
12748
|
-
await this.host.abortTurn(opened.job.turnId, `runner ${seat.name} went away before the turn
|
|
13382
|
+
await this.host.abortTurn(opened.job.turnId, `runner ${seat.name} went away before the turn`, { transient: true });
|
|
12749
13383
|
} else {
|
|
12750
13384
|
this.expectAck(seat, opened.job);
|
|
12751
13385
|
}
|
|
@@ -13235,6 +13869,19 @@ var RunnerHub = class {
|
|
|
13235
13869
|
this.log.info({ runner: seat.name }, "runner is taking jobs again");
|
|
13236
13870
|
}
|
|
13237
13871
|
}
|
|
13872
|
+
/** Has a runner stop the CLI of a turn it still runs that the board has ended. */
|
|
13873
|
+
async stopEnded(seat, turnId) {
|
|
13874
|
+
try {
|
|
13875
|
+
const stopped = await this.request(seat, (request) => ({ type: "stop", request, turnId }), CONTROL_TIMEOUT_MS);
|
|
13876
|
+
if (stopped === true) {
|
|
13877
|
+
this.log.warn({ runner: seat.name, turnId }, "stopped a turn the board had ended, which its runner still ran");
|
|
13878
|
+
} else {
|
|
13879
|
+
this.log.info({ runner: seat.name, turnId }, "a turn the board had ended was no longer running on its runner");
|
|
13880
|
+
}
|
|
13881
|
+
} catch (error) {
|
|
13882
|
+
this.log.warn({ runner: seat.name, turnId, error: String(error) }, "could not stop a turn the board had ended");
|
|
13883
|
+
}
|
|
13884
|
+
}
|
|
13238
13885
|
async detach(seat) {
|
|
13239
13886
|
seat.send = null;
|
|
13240
13887
|
this.runnerChanged(seat.name);
|
|
@@ -13255,7 +13902,8 @@ var RunnerHub = class {
|
|
|
13255
13902
|
return;
|
|
13256
13903
|
}
|
|
13257
13904
|
for (const turnId of this.host.turnsOn(seat.name)) {
|
|
13258
|
-
|
|
13905
|
+
const unsent = seat.unacked.has(turnId);
|
|
13906
|
+
void this.host.abortTurn(turnId, unsent ? `the turn never reached runner ${seat.name}, which went away` : `runner ${seat.name} went away during the turn`, { transient: unsent });
|
|
13259
13907
|
}
|
|
13260
13908
|
}, this.graceMs);
|
|
13261
13909
|
seat.grace.unref?.();
|
|
@@ -14803,6 +15451,34 @@ var ModelCatalog = class {
|
|
|
14803
15451
|
}
|
|
14804
15452
|
};
|
|
14805
15453
|
|
|
15454
|
+
// apps/server/dist/prometheus.js
|
|
15455
|
+
import { Hono as Hono4 } from "hono";
|
|
15456
|
+
var PROMETHEUS_CONTENT_TYPE = "text/plain; version=0.0.4; charset=utf-8";
|
|
15457
|
+
function quoted(value) {
|
|
15458
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n")}"`;
|
|
15459
|
+
}
|
|
15460
|
+
function sample(name, labels, value) {
|
|
15461
|
+
const pairs = Object.entries(labels).map(([label, text]) => `${label}=${quoted(text)}`);
|
|
15462
|
+
return `${name}{${pairs.join(",")}} ${value}`;
|
|
15463
|
+
}
|
|
15464
|
+
function prometheusText(counts) {
|
|
15465
|
+
return [
|
|
15466
|
+
"# HELP stellaris_turn_tokens_total Tokens finished turns used, by citizen, CLI, model, and kind: input, output, cache_read, or cache_write.",
|
|
15467
|
+
"# TYPE stellaris_turn_tokens_total counter",
|
|
15468
|
+
...counts.tokens.map(({ agent, cli, model, kind, tokens }) => sample("stellaris_turn_tokens_total", { agent, cli, model, kind }, tokens)),
|
|
15469
|
+
"# HELP stellaris_turns_total Finished turns, by citizen, outcome (completed or failed), and the trigger that woke them.",
|
|
15470
|
+
"# TYPE stellaris_turns_total counter",
|
|
15471
|
+
...counts.turns.map(({ agent, outcome, trigger, turns: turns2 }) => sample("stellaris_turns_total", { agent, outcome, trigger }, turns2)),
|
|
15472
|
+
""
|
|
15473
|
+
].join("\n");
|
|
15474
|
+
}
|
|
15475
|
+
function createMetricsApp(counters) {
|
|
15476
|
+
const app2 = new Hono4();
|
|
15477
|
+
app2.get("/metrics", (c) => c.body(prometheusText(counters.read()), 200, { "content-type": PROMETHEUS_CONTENT_TYPE }));
|
|
15478
|
+
app2.notFound((c) => c.text("not found", 404));
|
|
15479
|
+
return app2;
|
|
15480
|
+
}
|
|
15481
|
+
|
|
14806
15482
|
// apps/server/dist/quotas.js
|
|
14807
15483
|
var MINUTE = 6e4;
|
|
14808
15484
|
var QuotaReads = class {
|
|
@@ -14966,6 +15642,13 @@ var server = serveBoard({ fetch: app.fetch, port: config.port, hostname: config.
|
|
|
14966
15642
|
trustedProxies: config.trustedProxies
|
|
14967
15643
|
}, "board server listening");
|
|
14968
15644
|
});
|
|
15645
|
+
var metrics = config.metricsPort === null ? null : serveBoard({
|
|
15646
|
+
fetch: createMetricsApp(await board.countTurns()).fetch,
|
|
15647
|
+
port: config.metricsPort,
|
|
15648
|
+
hostname: config.host
|
|
15649
|
+
}, (info) => {
|
|
15650
|
+
log.info({ host: info.address, port: info.port }, "metrics listening");
|
|
15651
|
+
});
|
|
14969
15652
|
await scheduler.start();
|
|
14970
15653
|
log.info({
|
|
14971
15654
|
concurrency: config.concurrency ?? "unlimited",
|
|
@@ -14977,6 +15660,7 @@ var shutdown = async (signal) => {
|
|
|
14977
15660
|
log.info({ signal }, "shutting down; waiting for running turns to report");
|
|
14978
15661
|
await scheduler.stop();
|
|
14979
15662
|
await runners.close();
|
|
15663
|
+
metrics?.close();
|
|
14980
15664
|
server.close();
|
|
14981
15665
|
process.exit(0);
|
|
14982
15666
|
};
|