@kubitnodes/stellaris 0.3.20 → 0.3.22
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 +1029 -637
- package/dist/server/index.js +1313 -729
- package/package.json +3 -1
- package/web/dist/assets/{BackLink-BjfX2KZl.js → BackLink-CD-iuy-h.js} +1 -1
- package/web/dist/assets/Button-Yoe21NQ2.js +1 -0
- package/web/dist/assets/ChannelForms-55otlg-m.js +1 -0
- package/web/dist/assets/ChannelView-DJvLzg0N.js +2 -0
- package/web/dist/assets/CitizenView-m1K2aTvK.js +2 -0
- package/web/dist/assets/CitizensView-BeTgmrKE.js +1 -0
- package/web/dist/assets/{CliIcon-vSuwLv2K.js → CliIcon-DbMYJdlU.js} +1 -1
- package/web/dist/assets/CronsView-B4kXk5Ld.js +1 -0
- package/web/dist/assets/KnowledgeView-aSGxDDKy.js +1 -0
- package/web/dist/assets/Markdown-c9bCCLHg.js +30 -0
- package/web/dist/assets/{MessageItem-D7MUCA_T.js → MessageItem-DKJKMqs-.js} +1 -1
- package/web/dist/assets/MetricsView-D2TTry5O.js +1 -0
- package/web/dist/assets/NeedsYouView-C3celRei.js +1 -0
- package/web/dist/assets/Overview-BveU1yJj.js +1 -0
- package/web/dist/assets/{Pane-_D7n5uKH.js → Pane-CLjdfBMM.js} +1 -1
- package/web/dist/assets/{Picker-DUiiQMFN.js → Picker-CXOe5Xdu.js} +1 -1
- package/web/dist/assets/ProjectView-oa4Z46ED.js +1 -0
- package/web/dist/assets/ProposalView-B4T-EoX9.js +2 -0
- package/web/dist/assets/ProposalsView-BZHivSEm.js +1 -0
- package/web/dist/assets/Quota-BC7dpnwd.js +1 -0
- package/web/dist/assets/RunnersView-BrJF-aLU.js +1 -0
- package/web/dist/assets/SocietyView-C1yosPuf.js +1 -0
- package/web/dist/assets/StickyList-7tAlUDCN.js +1 -0
- package/web/dist/assets/SubjectThread-CCq6ZAnR.js +1 -0
- package/web/dist/assets/TaskFileView-BEXQJQQ6.js +4 -0
- package/web/dist/assets/TaskView-Cfhb7NuO.js +3 -0
- package/web/dist/assets/TasksView-3XljXcoi.js +1 -0
- package/web/dist/assets/ThreadForms-D2y5dXSC.js +1 -0
- package/web/dist/assets/ThreadItem-DeIhhmyu.js +1 -0
- package/web/dist/assets/ThreadView-CbUnt9WL.js +1 -0
- package/web/dist/assets/citizen--bqux6i_.js +1 -0
- package/web/dist/assets/compose-DNc3_c5x.js +1 -0
- package/web/dist/assets/format-B6fjC0_a.js +55 -0
- package/web/dist/assets/governance-RBQ4Lam0.js +1 -0
- package/web/dist/assets/index-CyCty7a2.js +23 -0
- package/web/dist/assets/index-GRqxKN35.css +2 -0
- package/web/dist/assets/mentions-B2Qchd_6.js +3 -0
- package/web/dist/assets/{tasks-B6I9DJ8O.js → tasks-DCUj5w_d.js} +1 -1
- package/web/dist/assets/useMatch-CfbBQirn.js +1 -0
- package/web/dist/index.html +9 -10
- package/web/dist/assets/ChannelForms-BYrcUldk.js +0 -1
- package/web/dist/assets/ChannelView-GJ_DusLQ.js +0 -2
- package/web/dist/assets/CitizenView-Cx7IYSRr.js +0 -2
- package/web/dist/assets/CitizensView-IKH7_yNQ.js +0 -1
- package/web/dist/assets/CronsView-DJbi8Qhx.js +0 -1
- package/web/dist/assets/KnowledgeView-CoYOG9tI.js +0 -1
- package/web/dist/assets/Markdown-YC-fbHiE.js +0 -32
- package/web/dist/assets/MetricsView-CE94VW0I.js +0 -1
- package/web/dist/assets/NeedsYouView-Otnm3lcG.js +0 -1
- package/web/dist/assets/Overview-B8f6rLb2.js +0 -1
- package/web/dist/assets/ProjectView-D_ACOmEd.js +0 -1
- package/web/dist/assets/ProposalView-4d0U3nEb.js +0 -2
- package/web/dist/assets/ProposalsView-BovF6CMD.js +0 -1
- package/web/dist/assets/RunnersView-B2_uCVHC.js +0 -1
- package/web/dist/assets/SocietyView-Bf0iX8AY.js +0 -1
- package/web/dist/assets/SubjectThread-B_Vmgs3d.js +0 -1
- package/web/dist/assets/TaskFileView-BzO0gjE6.js +0 -4
- package/web/dist/assets/TaskView-DB_Nji99.js +0 -3
- package/web/dist/assets/TasksView-Bz36YYWq.js +0 -1
- package/web/dist/assets/ThreadForms-1mbMZsbv.js +0 -1
- package/web/dist/assets/ThreadItem-QJSA92C4.js +0 -1
- package/web/dist/assets/ThreadView-BECLqt5B.js +0 -1
- package/web/dist/assets/citizen-DVFr5T6R.js +0 -1
- package/web/dist/assets/compose-_6feqP1t.js +0 -1
- package/web/dist/assets/format-DxXtbdNM.js +0 -55
- package/web/dist/assets/governance-C9xH4QTm.js +0 -1
- package/web/dist/assets/index-BEVCPm4C.js +0 -23
- package/web/dist/assets/index-BkOq4PSU.css +0 -2
- package/web/dist/assets/link-CTjFMNoY.js +0 -1
- package/web/dist/assets/mentions-BCdV0bMK.js +0 -1
- package/web/dist/assets/useMatch-B3RzAe8y.js +0 -1
- package/web/dist/assets/useStickyScroll-Ca7sWbGH.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { serve } from "@hono/node-server";
|
|
|
9
9
|
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
10
10
|
import path6 from "node:path";
|
|
11
11
|
import { decodeTime, monotonicFactory } from "ulid";
|
|
12
|
-
import { z as
|
|
12
|
+
import { z as z15 } from "zod";
|
|
13
13
|
|
|
14
14
|
// packages/shared/dist/ids.js
|
|
15
15
|
import { z } from "zod";
|
|
@@ -146,7 +146,7 @@ var SEED_ROLES = [
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
name: "steward",
|
|
149
|
-
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one. Never approves its own proposals; hiring, roles, and
|
|
149
|
+
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one, by a reallocation of its replicas, backlog threshold, wakes, or verbs. Never approves its own proposals; hiring, roles, retirements, and reallocations that change a role's verbs or replicas are decided by the user, and channels, skills, and other reallocations the steward may decide. Curates society knowledge and skills.",
|
|
150
150
|
verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
|
|
151
151
|
wakeTriggers: ["ops_event", "heartbeat"],
|
|
152
152
|
maxReplicas: 1,
|
|
@@ -169,7 +169,7 @@ var SEED_ROLES = [
|
|
|
169
169
|
];
|
|
170
170
|
|
|
171
171
|
// packages/shared/dist/board.js
|
|
172
|
-
import { z as
|
|
172
|
+
import { z as z5 } from "zod";
|
|
173
173
|
|
|
174
174
|
// packages/shared/dist/crons.js
|
|
175
175
|
import { Cron as Pattern } from "croner";
|
|
@@ -303,6 +303,115 @@ function localTime(date, timeZone) {
|
|
|
303
303
|
return `${part("weekday")} ${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}`;
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
// packages/shared/dist/quota.js
|
|
307
|
+
import { z as z4 } from "zod";
|
|
308
|
+
var QUOTA_WARNING_PERCENT = 80;
|
|
309
|
+
var QuotaWindowSchema = z4.object({
|
|
310
|
+
/**
|
|
311
|
+
* The CLI's own name for the window: Claude's `five_hour`, `seven_day`, `seven_day_opus`, and
|
|
312
|
+
* `model:<label>` for a model's weekly window; Codex's `primary` and `secondary`.
|
|
313
|
+
*/
|
|
314
|
+
key: z4.string().min(1).max(80),
|
|
315
|
+
/** What a scoped window counts, as the CLI labels it, such as a model's name. */
|
|
316
|
+
label: z4.string().min(1).max(80).optional(),
|
|
317
|
+
/** The window's length in minutes, when known. */
|
|
318
|
+
minutes: z4.number().int().positive().nullable().default(null),
|
|
319
|
+
/** How much of the window is used, in percent, or null when the CLI did not say. */
|
|
320
|
+
usedPercent: z4.number().nonnegative().nullable().default(null),
|
|
321
|
+
/** When the window starts afresh, when known. */
|
|
322
|
+
resetsAt: IsoDateTimeSchema.nullable().default(null),
|
|
323
|
+
/** When the CLI last reported this window. */
|
|
324
|
+
observedAt: IsoDateTimeSchema
|
|
325
|
+
});
|
|
326
|
+
var QuotaStatusSchema = z4.enum(["allowed", "warning", "limited", "none"]);
|
|
327
|
+
var QuotaCreditsSchema = z4.object({
|
|
328
|
+
hasCredits: z4.boolean(),
|
|
329
|
+
unlimited: z4.boolean(),
|
|
330
|
+
balance: z4.string().max(40).nullable()
|
|
331
|
+
});
|
|
332
|
+
var QuotaExtraUsageSchema = z4.object({
|
|
333
|
+
enabled: z4.boolean(),
|
|
334
|
+
monthlyLimit: z4.number().nonnegative().nullable(),
|
|
335
|
+
usedCredits: z4.number().nonnegative().nullable(),
|
|
336
|
+
currency: z4.string().min(1).max(8).nullable()
|
|
337
|
+
});
|
|
338
|
+
var CliQuotaSchema = z4.object({
|
|
339
|
+
/** When the CLI last reported anything here. */
|
|
340
|
+
observedAt: IsoDateTimeSchema,
|
|
341
|
+
/** Whether it came from a turn's stream or from asking the CLI. */
|
|
342
|
+
source: z4.enum(["turn", "read"]),
|
|
343
|
+
status: QuotaStatusSchema,
|
|
344
|
+
/** While `limited`: when work can go on, when known, and the window that stopped it. */
|
|
345
|
+
limitedUntil: IsoDateTimeSchema.nullable().default(null),
|
|
346
|
+
limitedBy: z4.string().min(1).max(80).nullable().default(null),
|
|
347
|
+
/** The plan's name as the CLI gives it, such as `max` or `pro`. */
|
|
348
|
+
plan: z4.string().min(1).max(40).nullable().default(null),
|
|
349
|
+
windows: z4.array(QuotaWindowSchema).max(24).default([]),
|
|
350
|
+
credits: QuotaCreditsSchema.nullable().default(null),
|
|
351
|
+
extraUsage: QuotaExtraUsageSchema.nullable().default(null)
|
|
352
|
+
});
|
|
353
|
+
function mergeQuota(previous, next) {
|
|
354
|
+
if (previous === void 0) {
|
|
355
|
+
return next;
|
|
356
|
+
}
|
|
357
|
+
const windows = new Map(previous.windows.map((window) => [window.key, window]));
|
|
358
|
+
for (const window of next.windows) {
|
|
359
|
+
windows.set(window.key, window);
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
...next,
|
|
363
|
+
plan: next.plan ?? previous.plan,
|
|
364
|
+
credits: next.credits ?? previous.credits,
|
|
365
|
+
extraUsage: next.extraUsage ?? previous.extraUsage,
|
|
366
|
+
windows: [...windows.values()]
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function windowCurrent(window, now) {
|
|
370
|
+
return window.resetsAt === null || Date.parse(window.resetsAt) > now.getTime();
|
|
371
|
+
}
|
|
372
|
+
function quotaLevel(quota, now) {
|
|
373
|
+
if (quota.status === "none") {
|
|
374
|
+
return "none";
|
|
375
|
+
}
|
|
376
|
+
if (quota.status === "limited" && (quota.limitedUntil === null || Date.parse(quota.limitedUntil) > now.getTime())) {
|
|
377
|
+
return "limited";
|
|
378
|
+
}
|
|
379
|
+
const high = quota.windows.some((window) => windowCurrent(window, now) && window.usedPercent !== null && window.usedPercent >= QUOTA_WARNING_PERCENT);
|
|
380
|
+
return quota.status === "warning" || high ? "warning" : "ok";
|
|
381
|
+
}
|
|
382
|
+
var WINDOW_NAMES = {
|
|
383
|
+
five_hour: "5h",
|
|
384
|
+
seven_day: "7d",
|
|
385
|
+
seven_day_opus: "7d Opus",
|
|
386
|
+
seven_day_sonnet: "7d Sonnet",
|
|
387
|
+
seven_day_oauth_apps: "7d apps",
|
|
388
|
+
seven_day_overage_included: "7d extra",
|
|
389
|
+
overage: "extra usage"
|
|
390
|
+
};
|
|
391
|
+
function windowName(window) {
|
|
392
|
+
if (window.label !== void 0) {
|
|
393
|
+
return window.minutes === 10080 ? `7d ${window.label}` : window.label;
|
|
394
|
+
}
|
|
395
|
+
const named = WINDOW_NAMES[window.key];
|
|
396
|
+
if (named !== void 0) {
|
|
397
|
+
return named;
|
|
398
|
+
}
|
|
399
|
+
if (window.minutes !== null) {
|
|
400
|
+
return window.minutes % 1440 === 0 ? `${window.minutes / 1440}d` : window.minutes % 60 === 0 ? `${window.minutes / 60}h` : `${window.minutes}m`;
|
|
401
|
+
}
|
|
402
|
+
return window.key;
|
|
403
|
+
}
|
|
404
|
+
function readings(quota) {
|
|
405
|
+
const windows = quota.windows.map((window) => `${window.key}=${window.usedPercent === null ? "?" : Math.round(window.usedPercent)}@${window.resetsAt === null ? "?" : Math.round(Date.parse(window.resetsAt) / 6e4)}`).toSorted();
|
|
406
|
+
return windows.join(",");
|
|
407
|
+
}
|
|
408
|
+
function quotaChanged(previous, next) {
|
|
409
|
+
if (previous === void 0) {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
return previous.status !== next.status || previous.limitedUntil !== next.limitedUntil || previous.plan !== next.plan || previous.credits?.balance !== next.credits?.balance || previous.extraUsage?.usedCredits !== next.extraUsage?.usedCredits || readings(previous) !== readings(next);
|
|
413
|
+
}
|
|
414
|
+
|
|
306
415
|
// packages/shared/dist/board.js
|
|
307
416
|
var ASK_CHANNEL = "asks";
|
|
308
417
|
var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
|
|
@@ -315,24 +424,24 @@ var SOCIETY_SCOPE = "society";
|
|
|
315
424
|
function wakeScope(member, project) {
|
|
316
425
|
return project !== null && member.memberships.includes(project) ? project : SOCIETY_SCOPE;
|
|
317
426
|
}
|
|
318
|
-
var CliKindSchema =
|
|
319
|
-
var ArchivedChannelSchema =
|
|
427
|
+
var CliKindSchema = z5.enum(["claude", "codex"]);
|
|
428
|
+
var ArchivedChannelSchema = z5.object({
|
|
320
429
|
name: NameSchema,
|
|
321
430
|
at: IsoDateTimeSchema,
|
|
322
431
|
by: NameSchema,
|
|
323
|
-
reason:
|
|
432
|
+
reason: z5.string().min(1)
|
|
324
433
|
});
|
|
325
|
-
var SocietySchema =
|
|
326
|
-
name:
|
|
327
|
-
version:
|
|
434
|
+
var SocietySchema = z5.object({
|
|
435
|
+
name: z5.string().min(1),
|
|
436
|
+
version: z5.literal(1),
|
|
328
437
|
createdAt: IsoDateTimeSchema,
|
|
329
438
|
/** The channels that take posts; an archived one moves to `archivedChannels`. */
|
|
330
|
-
channels:
|
|
331
|
-
archivedChannels:
|
|
439
|
+
channels: z5.array(NameSchema),
|
|
440
|
+
archivedChannels: z5.array(ArchivedChannelSchema).default([]),
|
|
332
441
|
/** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
|
|
333
442
|
timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
|
|
334
443
|
});
|
|
335
|
-
var CompletionEffectSchema =
|
|
444
|
+
var CompletionEffectSchema = z5.enum(["none", "merge", "ghpr"]);
|
|
336
445
|
var GITHUB_REMOTE = [
|
|
337
446
|
/^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
|
|
338
447
|
/^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
|
|
@@ -348,7 +457,7 @@ function githubRepository(remote) {
|
|
|
348
457
|
return null;
|
|
349
458
|
}
|
|
350
459
|
var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
|
|
351
|
-
var PullRequestUrlSchema =
|
|
460
|
+
var PullRequestUrlSchema = z5.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
|
|
352
461
|
function parsePullRequestUrl(url) {
|
|
353
462
|
const match = PULL_REQUEST_URL.exec(url.trim());
|
|
354
463
|
return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
|
|
@@ -356,72 +465,72 @@ function parsePullRequestUrl(url) {
|
|
|
356
465
|
function sameRepository(a, b) {
|
|
357
466
|
return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
|
|
358
467
|
}
|
|
359
|
-
var MergeSubjectSchema =
|
|
360
|
-
var PullRequestLinkSchema =
|
|
468
|
+
var MergeSubjectSchema = z5.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
|
|
469
|
+
var PullRequestLinkSchema = z5.object({
|
|
361
470
|
url: PullRequestUrlSchema,
|
|
362
|
-
number:
|
|
471
|
+
number: z5.number().int().positive(),
|
|
363
472
|
subject: MergeSubjectSchema.optional(),
|
|
364
|
-
body:
|
|
473
|
+
body: z5.string().max(2e4).optional(),
|
|
365
474
|
linkedBy: NameSchema,
|
|
366
475
|
linkedAt: IsoDateTimeSchema,
|
|
367
476
|
/**
|
|
368
477
|
* Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
|
|
369
478
|
* leaves it open, for the reason `detail` gives.
|
|
370
479
|
*/
|
|
371
|
-
closed:
|
|
480
|
+
closed: z5.object({ at: IsoDateTimeSchema, ok: z5.boolean(), detail: z5.string() }).optional()
|
|
372
481
|
});
|
|
373
|
-
var DefaultBranchSchema =
|
|
374
|
-
var StageIdSchema =
|
|
482
|
+
var DefaultBranchSchema = z5.string().trim().min(1).max(100).regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/, "a branch name is letters, digits, and . _ / -").refine((branch) => !branch.includes("..") && !branch.includes("//") && !branch.endsWith("/") && !branch.endsWith(".") && !branch.endsWith(".lock"), "not a branch name git takes").refine((branch) => !branch.startsWith("task/") && !branch.startsWith("agent/"), "task/ and agent/ hold the board's own branches");
|
|
483
|
+
var StageIdSchema = z5.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
|
|
375
484
|
var stageFields = {
|
|
376
|
-
name:
|
|
485
|
+
name: z5.string().trim().min(1).max(120),
|
|
377
486
|
role: NameSchema.optional(),
|
|
378
487
|
agent: NameSchema.optional(),
|
|
379
|
-
gate:
|
|
488
|
+
gate: z5.boolean().default(false)
|
|
380
489
|
};
|
|
381
490
|
function oneAssignee(stage) {
|
|
382
491
|
return stage.role === void 0 || stage.agent === void 0;
|
|
383
492
|
}
|
|
384
493
|
var ONE_ASSIGNEE = { message: "a stage names a role or an agent, not both" };
|
|
385
|
-
var PlanStageSchema =
|
|
386
|
-
var PlanEditStageSchema =
|
|
387
|
-
var PlanSchema =
|
|
388
|
-
var PlanEditSchema =
|
|
389
|
-
var ProjectSchema =
|
|
494
|
+
var PlanStageSchema = z5.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
|
|
495
|
+
var PlanEditStageSchema = z5.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
|
|
496
|
+
var PlanSchema = z5.array(PlanStageSchema);
|
|
497
|
+
var PlanEditSchema = z5.array(PlanEditStageSchema);
|
|
498
|
+
var ProjectSchema = z5.object({
|
|
390
499
|
slug: NameSchema,
|
|
391
|
-
name:
|
|
392
|
-
repo:
|
|
393
|
-
defaultBranch:
|
|
500
|
+
name: z5.string().min(1),
|
|
501
|
+
repo: z5.string().nullable(),
|
|
502
|
+
defaultBranch: z5.string().min(1),
|
|
394
503
|
/** The channels that take posts; an archived one moves to `archivedChannels`. */
|
|
395
|
-
channels:
|
|
396
|
-
archivedChannels:
|
|
397
|
-
members:
|
|
398
|
-
approvers:
|
|
399
|
-
requiredCapabilities:
|
|
504
|
+
channels: z5.array(NameSchema),
|
|
505
|
+
archivedChannels: z5.array(ArchivedChannelSchema).default([]),
|
|
506
|
+
members: z5.array(NameSchema),
|
|
507
|
+
approvers: z5.array(NameSchema),
|
|
508
|
+
requiredCapabilities: z5.array(z5.string()),
|
|
400
509
|
createdAt: IsoDateTimeSchema,
|
|
401
510
|
onDone: CompletionEffectSchema.default("none"),
|
|
402
511
|
/** The runner the project lives on, which holds its repository; set on the project's first turn. */
|
|
403
512
|
runner: NameSchema.optional(),
|
|
404
513
|
/** Set once the project is archived: nobody belongs to it, and nothing more is posted, filed, or joined there. */
|
|
405
|
-
archived:
|
|
514
|
+
archived: z5.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z5.string().min(1) }).optional()
|
|
406
515
|
});
|
|
407
|
-
var TaskStepSchema =
|
|
408
|
-
action:
|
|
516
|
+
var TaskStepSchema = z5.object({
|
|
517
|
+
action: z5.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
|
|
409
518
|
stage: StageIdSchema,
|
|
410
519
|
to: StageIdSchema.nullable()
|
|
411
520
|
});
|
|
412
|
-
var ProposalStepSchema =
|
|
413
|
-
var MessageStepSchema =
|
|
521
|
+
var ProposalStepSchema = z5.object({ action: z5.enum(["approved", "rejected"]) });
|
|
522
|
+
var MessageStepSchema = z5.union([TaskStepSchema, ProposalStepSchema]);
|
|
414
523
|
function isTaskStep(step) {
|
|
415
524
|
return "stage" in step;
|
|
416
525
|
}
|
|
417
|
-
var KnowledgeNoticeSchema =
|
|
418
|
-
kind:
|
|
419
|
-
action:
|
|
526
|
+
var KnowledgeNoticeSchema = z5.object({
|
|
527
|
+
kind: z5.literal("knowledge"),
|
|
528
|
+
action: z5.enum(["written", "updated", "removed"]),
|
|
420
529
|
topic: NameSchema,
|
|
421
530
|
project: NameSchema.nullable()
|
|
422
531
|
});
|
|
423
|
-
var MessageNoticeSchema =
|
|
424
|
-
var MessageFrontmatterSchema =
|
|
532
|
+
var MessageNoticeSchema = z5.discriminatedUnion("kind", [KnowledgeNoticeSchema]);
|
|
533
|
+
var MessageFrontmatterSchema = z5.object({
|
|
425
534
|
id: UlidSchema,
|
|
426
535
|
author: NameSchema,
|
|
427
536
|
channel: ChannelRefSchema,
|
|
@@ -435,7 +544,7 @@ var MessageFrontmatterSchema = z4.object({
|
|
|
435
544
|
/** On a post a verb made about its work, such as a knowledge topic's note: what it records. */
|
|
436
545
|
notice: MessageNoticeSchema.optional(),
|
|
437
546
|
ts: IsoDateTimeSchema,
|
|
438
|
-
mentions:
|
|
547
|
+
mentions: z5.array(NameSchema)
|
|
439
548
|
});
|
|
440
549
|
var MessageRefSchema = MessageFrontmatterSchema.pick({
|
|
441
550
|
id: true,
|
|
@@ -445,16 +554,16 @@ var MessageRefSchema = MessageFrontmatterSchema.pick({
|
|
|
445
554
|
ts: true
|
|
446
555
|
});
|
|
447
556
|
var MESSAGE_LOOKUP_LIMIT = 50;
|
|
448
|
-
var TaskStatusSchema =
|
|
449
|
-
var ThreadStateSchema =
|
|
450
|
-
var ThreadSubjectSchema =
|
|
451
|
-
kind:
|
|
557
|
+
var TaskStatusSchema = z5.enum(["open", "claimed", "done", "abandoned"]);
|
|
558
|
+
var ThreadStateSchema = z5.enum(["open", "closed"]);
|
|
559
|
+
var ThreadSubjectSchema = z5.object({
|
|
560
|
+
kind: z5.enum(["task", "proposal"]),
|
|
452
561
|
id: UlidSchema
|
|
453
562
|
});
|
|
454
|
-
var ThreadFrontmatterSchema =
|
|
563
|
+
var ThreadFrontmatterSchema = z5.object({
|
|
455
564
|
id: UlidSchema,
|
|
456
565
|
channel: ChannelRefSchema,
|
|
457
|
-
title:
|
|
566
|
+
title: z5.string().min(1).max(200),
|
|
458
567
|
subject: ThreadSubjectSchema.optional(),
|
|
459
568
|
state: ThreadStateSchema,
|
|
460
569
|
openedBy: NameSchema,
|
|
@@ -462,28 +571,28 @@ var ThreadFrontmatterSchema = z4.object({
|
|
|
462
571
|
closedBy: NameSchema.optional(),
|
|
463
572
|
closedAt: IsoDateTimeSchema.optional()
|
|
464
573
|
});
|
|
465
|
-
var StageSchema =
|
|
574
|
+
var StageSchema = z5.object({
|
|
466
575
|
id: StageIdSchema,
|
|
467
|
-
name:
|
|
576
|
+
name: z5.string().min(1),
|
|
468
577
|
role: NameSchema.optional(),
|
|
469
578
|
agent: NameSchema.optional(),
|
|
470
|
-
gate:
|
|
579
|
+
gate: z5.boolean().default(false),
|
|
471
580
|
/** Everyone who has held the stage, oldest first. */
|
|
472
|
-
holders:
|
|
581
|
+
holders: z5.array(NameSchema).default([]),
|
|
473
582
|
completedBy: NameSchema.optional(),
|
|
474
583
|
completedAt: IsoDateTimeSchema.optional()
|
|
475
584
|
});
|
|
476
|
-
var TaskReturnSchema =
|
|
585
|
+
var TaskReturnSchema = z5.object({
|
|
477
586
|
from: StageIdSchema,
|
|
478
587
|
by: NameSchema,
|
|
479
588
|
at: IsoDateTimeSchema
|
|
480
589
|
});
|
|
481
|
-
var TaskFrontmatterSchema =
|
|
590
|
+
var TaskFrontmatterSchema = z5.object({
|
|
482
591
|
id: UlidSchema,
|
|
483
592
|
project: NameSchema,
|
|
484
593
|
/** The channel of the project the task is filed in, where its thread hangs. */
|
|
485
594
|
channel: NameSchema.default("general"),
|
|
486
|
-
title:
|
|
595
|
+
title: z5.string().min(1),
|
|
487
596
|
status: TaskStatusSchema,
|
|
488
597
|
createdBy: NameSchema,
|
|
489
598
|
createdAt: IsoDateTimeSchema,
|
|
@@ -492,19 +601,19 @@ var TaskFrontmatterSchema = z4.object({
|
|
|
492
601
|
claimedBy: NameSchema.optional(),
|
|
493
602
|
leaseExpiresAt: IsoDateTimeSchema.optional(),
|
|
494
603
|
parentId: UlidSchema.optional(),
|
|
495
|
-
blockedBy:
|
|
496
|
-
requiredCapabilities:
|
|
497
|
-
stages:
|
|
604
|
+
blockedBy: z5.array(UlidSchema),
|
|
605
|
+
requiredCapabilities: z5.array(z5.string()),
|
|
606
|
+
stages: z5.array(StageSchema).min(1),
|
|
498
607
|
stage: StageIdSchema,
|
|
499
608
|
/** When the current stage last became current or lost its holder: the waiting clock. */
|
|
500
609
|
stageSince: IsoDateTimeSchema,
|
|
501
610
|
/** The highest stage number issued, so a removed stage's id is never reused. */
|
|
502
|
-
stageSeq:
|
|
611
|
+
stageSeq: z5.number().int().positive(),
|
|
503
612
|
onDone: CompletionEffectSchema.default("none"),
|
|
504
613
|
/** The pull request a `ghpr` task lands through. */
|
|
505
614
|
pullRequest: PullRequestLinkSchema.optional(),
|
|
506
615
|
/** The last stage is complete and the completion effect is running. */
|
|
507
|
-
completing:
|
|
616
|
+
completing: z5.boolean().default(false),
|
|
508
617
|
/** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
|
|
509
618
|
returned: TaskReturnSchema.optional()
|
|
510
619
|
});
|
|
@@ -528,79 +637,90 @@ function mayHoldStage(member, task) {
|
|
|
528
637
|
}
|
|
529
638
|
return !(stage.gate && task.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
|
|
530
639
|
}
|
|
531
|
-
var ModelNameSchema =
|
|
532
|
-
var EffortSchema =
|
|
533
|
-
var EffortOptionSchema =
|
|
640
|
+
var ModelNameSchema = z5.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
|
|
641
|
+
var EffortSchema = z5.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
|
|
642
|
+
var EffortOptionSchema = z5.object({
|
|
534
643
|
id: EffortSchema,
|
|
535
|
-
description:
|
|
644
|
+
description: z5.string().default("")
|
|
536
645
|
});
|
|
537
|
-
var ModelOptionSchema =
|
|
646
|
+
var ModelOptionSchema = z5.object({
|
|
538
647
|
id: ModelNameSchema,
|
|
539
|
-
name:
|
|
540
|
-
description:
|
|
541
|
-
isDefault:
|
|
542
|
-
efforts:
|
|
648
|
+
name: z5.string(),
|
|
649
|
+
description: z5.string().default(""),
|
|
650
|
+
isDefault: z5.boolean().default(false),
|
|
651
|
+
efforts: z5.array(EffortOptionSchema).default([]),
|
|
543
652
|
defaultEffort: EffortSchema.optional()
|
|
544
653
|
});
|
|
545
|
-
var RunnerModelsSchema =
|
|
654
|
+
var RunnerModelsSchema = z5.object({
|
|
546
655
|
runner: NameSchema,
|
|
547
656
|
cli: CliKindSchema,
|
|
548
|
-
models:
|
|
657
|
+
models: z5.array(ModelOptionSchema)
|
|
549
658
|
});
|
|
550
|
-
var AgentStatusSchema =
|
|
551
|
-
var AgentSchema =
|
|
659
|
+
var AgentStatusSchema = z5.enum(["active", "retired"]);
|
|
660
|
+
var AgentSchema = z5.object({
|
|
552
661
|
name: NameSchema,
|
|
553
662
|
role: NameSchema,
|
|
554
663
|
cli: CliKindSchema.nullable(),
|
|
555
|
-
model:
|
|
664
|
+
model: z5.string().optional(),
|
|
556
665
|
/** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
|
|
557
|
-
effort:
|
|
666
|
+
effort: z5.string().optional(),
|
|
558
667
|
/** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
|
|
559
668
|
homeRunner: NameSchema.optional(),
|
|
560
|
-
memberships:
|
|
561
|
-
subscriptions:
|
|
669
|
+
memberships: z5.array(NameSchema),
|
|
670
|
+
subscriptions: z5.array(ChannelRefSchema),
|
|
562
671
|
status: AgentStatusSchema,
|
|
563
672
|
createdAt: IsoDateTimeSchema,
|
|
564
|
-
tokenHash:
|
|
673
|
+
tokenHash: z5.string().min(1),
|
|
565
674
|
retiredAt: IsoDateTimeSchema.optional(),
|
|
566
|
-
retiredReason:
|
|
675
|
+
retiredReason: z5.string().optional()
|
|
567
676
|
});
|
|
568
|
-
var MemberSchema =
|
|
677
|
+
var MemberSchema = z5.object({
|
|
569
678
|
name: NameSchema,
|
|
570
679
|
role: NameSchema,
|
|
571
680
|
cli: CliKindSchema.nullable(),
|
|
572
681
|
homeRunner: NameSchema.optional(),
|
|
573
682
|
status: AgentStatusSchema,
|
|
574
|
-
resident:
|
|
683
|
+
resident: z5.boolean().default(false),
|
|
575
684
|
/** The model configured on the record, if any; the CLI's own default applies otherwise. */
|
|
576
|
-
model:
|
|
685
|
+
model: z5.string().optional(),
|
|
577
686
|
/** The model the CLI reported on the citizen's last turn. */
|
|
578
|
-
lastModel:
|
|
687
|
+
lastModel: z5.string().optional(),
|
|
579
688
|
/** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
|
|
580
|
-
effort:
|
|
689
|
+
effort: z5.string().optional(),
|
|
581
690
|
/** The citizen's own skills, by name. */
|
|
582
|
-
skills:
|
|
583
|
-
memberships:
|
|
584
|
-
subscriptions:
|
|
585
|
-
claimsHeld:
|
|
586
|
-
tasksDone:
|
|
691
|
+
skills: z5.array(NameSchema).default([]),
|
|
692
|
+
memberships: z5.array(NameSchema),
|
|
693
|
+
subscriptions: z5.array(ChannelRefSchema),
|
|
694
|
+
claimsHeld: z5.number().int().nonnegative().default(0),
|
|
695
|
+
tasksDone: z5.number().int().nonnegative().default(0),
|
|
587
696
|
lastTurnAt: IsoDateTimeSchema.optional(),
|
|
588
|
-
lastTurnOutcome:
|
|
697
|
+
lastTurnOutcome: z5.string().optional(),
|
|
589
698
|
createdAt: IsoDateTimeSchema,
|
|
590
699
|
retiredAt: IsoDateTimeSchema.optional()
|
|
591
700
|
});
|
|
592
|
-
var RunnerOsSchema =
|
|
593
|
-
var
|
|
701
|
+
var RunnerOsSchema = z5.enum(["linux", "windows", "darwin"]);
|
|
702
|
+
var GitIdentityPartSchema = z5.enum(["name", "email"]);
|
|
703
|
+
var RunnerSchema = z5.object({
|
|
594
704
|
name: NameSchema,
|
|
595
705
|
os: RunnerOsSchema,
|
|
596
|
-
clis:
|
|
597
|
-
capabilities:
|
|
706
|
+
clis: z5.array(CliKindSchema),
|
|
707
|
+
capabilities: z5.array(z5.string()),
|
|
598
708
|
/** The release the runner reported when it last registered; absent until it has. */
|
|
599
|
-
version:
|
|
600
|
-
|
|
601
|
-
|
|
709
|
+
version: z5.string().optional(),
|
|
710
|
+
/**
|
|
711
|
+
* What the runner's account lacked of a git identity when it last registered, empty when it had
|
|
712
|
+
* both; absent while the runner has not said, as one from before runners checked never does.
|
|
713
|
+
*/
|
|
714
|
+
gitIdentityMissing: z5.array(GitIdentityPartSchema).optional(),
|
|
715
|
+
status: z5.enum(["connected", "disconnected"]),
|
|
716
|
+
lastSeen: IsoDateTimeSchema.optional(),
|
|
717
|
+
/**
|
|
718
|
+
* Each CLI's plan quota as its login on this runner last reported it, from a turn's stream or a
|
|
719
|
+
* read; absent until one has.
|
|
720
|
+
*/
|
|
721
|
+
quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional()
|
|
602
722
|
});
|
|
603
|
-
var ProposalKindSchema =
|
|
723
|
+
var ProposalKindSchema = z5.enum([
|
|
604
724
|
"role",
|
|
605
725
|
"member",
|
|
606
726
|
"channel",
|
|
@@ -609,43 +729,88 @@ var ProposalKindSchema = z4.enum([
|
|
|
609
729
|
"skill",
|
|
610
730
|
"archive"
|
|
611
731
|
]);
|
|
612
|
-
var ProposalStatusSchema =
|
|
732
|
+
var ProposalStatusSchema = z5.enum([
|
|
613
733
|
"proposed",
|
|
614
734
|
"approved",
|
|
615
735
|
"rejected",
|
|
616
736
|
"provisioned",
|
|
617
737
|
"retired"
|
|
618
738
|
]);
|
|
619
|
-
var MemberProposalSchema =
|
|
739
|
+
var MemberProposalSchema = z5.object({
|
|
620
740
|
name: NameSchema,
|
|
621
741
|
role: NameSchema,
|
|
622
742
|
cli: CliKindSchema,
|
|
623
|
-
model:
|
|
743
|
+
model: z5.string().optional(),
|
|
624
744
|
homeRunner: NameSchema.optional(),
|
|
625
|
-
memberships:
|
|
626
|
-
subscriptions:
|
|
627
|
-
seedInstructions:
|
|
745
|
+
memberships: z5.array(NameSchema).default([]),
|
|
746
|
+
subscriptions: z5.array(ChannelRefSchema).default([]),
|
|
747
|
+
seedInstructions: z5.string().optional()
|
|
628
748
|
});
|
|
629
|
-
var ChannelProposalSchema =
|
|
749
|
+
var ChannelProposalSchema = z5.object({
|
|
630
750
|
project: NameSchema.nullable().default(null),
|
|
631
751
|
name: NameSchema,
|
|
632
|
-
purpose:
|
|
752
|
+
purpose: z5.string().min(1)
|
|
633
753
|
});
|
|
634
|
-
var
|
|
635
|
-
|
|
754
|
+
var RoleChangesSchema = z5.strictObject({
|
|
755
|
+
maxReplicas: z5.number().int().min(1).optional(),
|
|
756
|
+
backlogThreshold: z5.number().positive().optional(),
|
|
757
|
+
wakeTriggers: z5.array(CharterTriggerSchema).optional(),
|
|
758
|
+
verbs: z5.array(VerbNameSchema).optional()
|
|
759
|
+
}).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
|
|
760
|
+
message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
|
|
761
|
+
});
|
|
762
|
+
var ReallocationProposalSchema = z5.object({
|
|
763
|
+
role: NameSchema,
|
|
764
|
+
changes: RoleChangesSchema,
|
|
765
|
+
description: z5.string().min(1)
|
|
636
766
|
});
|
|
637
|
-
var
|
|
767
|
+
var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
|
|
768
|
+
function applyRoleChanges(role, changes) {
|
|
769
|
+
return {
|
|
770
|
+
...role,
|
|
771
|
+
...changes.maxReplicas === void 0 ? {} : { maxReplicas: changes.maxReplicas },
|
|
772
|
+
...changes.backlogThreshold === void 0 ? {} : { backlogThreshold: changes.backlogThreshold },
|
|
773
|
+
...changes.wakeTriggers === void 0 ? {} : { wakeTriggers: [...changes.wakeTriggers] },
|
|
774
|
+
...changes.verbs === void 0 ? {} : { verbs: [...changes.verbs] }
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function listedOrNone(values) {
|
|
778
|
+
return values.length === 0 ? "none" : values.join(", ");
|
|
779
|
+
}
|
|
780
|
+
function roleChangeLines(role, changes) {
|
|
781
|
+
const lines = [];
|
|
782
|
+
if (changes.maxReplicas !== void 0 && changes.maxReplicas !== role.maxReplicas) {
|
|
783
|
+
lines.push(`maxReplicas ${role.maxReplicas} \u2192 ${changes.maxReplicas}`);
|
|
784
|
+
}
|
|
785
|
+
if (changes.backlogThreshold !== void 0 && changes.backlogThreshold !== role.backlogThreshold) {
|
|
786
|
+
lines.push(`backlogThreshold ${role.backlogThreshold} \u2192 ${changes.backlogThreshold}`);
|
|
787
|
+
}
|
|
788
|
+
const wakes = changes.wakeTriggers;
|
|
789
|
+
if (wakes !== void 0 && (wakes.length !== role.wakeTriggers.length || wakes.some((trigger) => !role.wakeTriggers.includes(trigger)))) {
|
|
790
|
+
lines.push(`wakeTriggers ${listedOrNone(role.wakeTriggers)} \u2192 ${listedOrNone(wakes)}`);
|
|
791
|
+
}
|
|
792
|
+
if (changes.verbs !== void 0) {
|
|
793
|
+
const next = changes.verbs;
|
|
794
|
+
const added = next.filter((verb) => !role.verbs.includes(verb)).map((verb) => `+${verb}`);
|
|
795
|
+
const removed = role.verbs.filter((verb) => !next.includes(verb)).map((verb) => `\u2212${verb}`);
|
|
796
|
+
if (added.length + removed.length > 0) {
|
|
797
|
+
lines.push(`verbs ${[...added, ...removed].join(" ")}`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return lines;
|
|
801
|
+
}
|
|
802
|
+
var RetirementProposalSchema = z5.object({
|
|
638
803
|
agent: NameSchema,
|
|
639
|
-
reason:
|
|
804
|
+
reason: z5.string().min(1)
|
|
640
805
|
});
|
|
641
|
-
var ArchiveProposalSchema =
|
|
806
|
+
var ArchiveProposalSchema = z5.object({
|
|
642
807
|
project: NameSchema,
|
|
643
|
-
reason:
|
|
808
|
+
reason: z5.string().min(1)
|
|
644
809
|
});
|
|
645
|
-
var SkillProposalSchema =
|
|
810
|
+
var SkillProposalSchema = z5.object({
|
|
646
811
|
name: NameSchema,
|
|
647
|
-
summary:
|
|
648
|
-
body:
|
|
812
|
+
summary: z5.string().min(1).max(200),
|
|
813
|
+
body: z5.string().min(1).optional()
|
|
649
814
|
});
|
|
650
815
|
var ProposalCharterSchemas = {
|
|
651
816
|
role: RoleCharterSchema,
|
|
@@ -665,6 +830,32 @@ var ROLE_KIND_APPROVERS = {
|
|
|
665
830
|
reallocation: [USER_ROLE, "steward"],
|
|
666
831
|
skill: [USER_ROLE, "steward"]
|
|
667
832
|
};
|
|
833
|
+
var USER_DECIDED_ROLE_CHANGES = ["verbs", "maxReplicas"];
|
|
834
|
+
function proposalApprovers(kind, charter) {
|
|
835
|
+
if (kind === "reallocation") {
|
|
836
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
837
|
+
if (parsed.success && USER_DECIDED_ROLE_CHANGES.some((field) => parsed.data.changes[field] !== void 0)) {
|
|
838
|
+
return [USER_ROLE];
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return ROLE_KIND_APPROVERS[kind];
|
|
842
|
+
}
|
|
843
|
+
function describeRoleChanges(changes) {
|
|
844
|
+
const parts = [];
|
|
845
|
+
if (changes.maxReplicas !== void 0) {
|
|
846
|
+
parts.push(`up to ${changes.maxReplicas} per project`);
|
|
847
|
+
}
|
|
848
|
+
if (changes.backlogThreshold !== void 0) {
|
|
849
|
+
parts.push(`backlog threshold ${changes.backlogThreshold}`);
|
|
850
|
+
}
|
|
851
|
+
if (changes.wakeTriggers !== void 0) {
|
|
852
|
+
parts.push(changes.wakeTriggers.length === 0 ? "no ambient wakes" : `wakes on ${changes.wakeTriggers.join(", ")}`);
|
|
853
|
+
}
|
|
854
|
+
if (changes.verbs !== void 0) {
|
|
855
|
+
parts.push(`${changes.verbs.length} verbs`);
|
|
856
|
+
}
|
|
857
|
+
return parts.join(", ");
|
|
858
|
+
}
|
|
668
859
|
function describeCharter(kind, charter) {
|
|
669
860
|
switch (kind) {
|
|
670
861
|
case "member": {
|
|
@@ -696,9 +887,14 @@ function describeCharter(kind, charter) {
|
|
|
696
887
|
}
|
|
697
888
|
case "reallocation": {
|
|
698
889
|
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
699
|
-
if (
|
|
890
|
+
if (parsed.success) {
|
|
891
|
+
const { role, changes, description } = parsed.data;
|
|
892
|
+
return `reallocation of role ${role} (${describeRoleChanges(changes)}): ${description}`;
|
|
893
|
+
}
|
|
894
|
+
const legacy = LegacyReallocationSchema.safeParse(charter);
|
|
895
|
+
if (!legacy.success)
|
|
700
896
|
break;
|
|
701
|
-
return `reallocation: ${
|
|
897
|
+
return `reallocation: ${legacy.data.description}`;
|
|
702
898
|
}
|
|
703
899
|
case "skill": {
|
|
704
900
|
const parsed = SkillProposalSchema.safeParse(charter);
|
|
@@ -717,23 +913,23 @@ function describeCharter(kind, charter) {
|
|
|
717
913
|
}
|
|
718
914
|
return `${kind} ${JSON.stringify(charter)}`;
|
|
719
915
|
}
|
|
720
|
-
var SkillSchema =
|
|
916
|
+
var SkillSchema = z5.object({
|
|
721
917
|
name: NameSchema,
|
|
722
|
-
summary:
|
|
723
|
-
scope:
|
|
724
|
-
path:
|
|
918
|
+
summary: z5.string().default(""),
|
|
919
|
+
scope: z5.enum(["own", "society"]),
|
|
920
|
+
path: z5.string().min(1)
|
|
725
921
|
});
|
|
726
|
-
var RemovedSkillSchema =
|
|
922
|
+
var RemovedSkillSchema = z5.object({
|
|
727
923
|
name: NameSchema,
|
|
728
|
-
reason:
|
|
924
|
+
reason: z5.string().min(1),
|
|
729
925
|
removedBy: NameSchema,
|
|
730
926
|
removedAt: IsoDateTimeSchema
|
|
731
927
|
});
|
|
732
|
-
var HomeConflictSchema =
|
|
928
|
+
var HomeConflictSchema = z5.object({
|
|
733
929
|
/** The copy, relative to the home. */
|
|
734
|
-
path:
|
|
930
|
+
path: z5.string().min(1),
|
|
735
931
|
/** The file beside it, whose version the board kept. */
|
|
736
|
-
file:
|
|
932
|
+
file: z5.string().min(1),
|
|
737
933
|
/** When the copy reached the board. */
|
|
738
934
|
since: IsoDateTimeSchema
|
|
739
935
|
});
|
|
@@ -741,79 +937,79 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
|
|
|
741
937
|
function conflictCopyOf(copy) {
|
|
742
938
|
return CONFLICT_COPY.exec(copy)?.[1] ?? null;
|
|
743
939
|
}
|
|
744
|
-
var CommitIdSchema =
|
|
745
|
-
var HomeFileChangeSchema =
|
|
746
|
-
path:
|
|
747
|
-
status:
|
|
940
|
+
var CommitIdSchema = z5.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
|
|
941
|
+
var HomeFileChangeSchema = z5.object({
|
|
942
|
+
path: z5.string().min(1),
|
|
943
|
+
status: z5.enum(["added", "modified", "deleted"]),
|
|
748
944
|
/** Lines added and removed, or null for a binary file. */
|
|
749
|
-
added:
|
|
750
|
-
removed:
|
|
945
|
+
added: z5.number().int().nonnegative().nullable(),
|
|
946
|
+
removed: z5.number().int().nonnegative().nullable()
|
|
751
947
|
});
|
|
752
|
-
var HomeChangeSchema =
|
|
948
|
+
var HomeChangeSchema = z5.object({
|
|
753
949
|
commit: CommitIdSchema,
|
|
754
950
|
at: IsoDateTimeSchema,
|
|
755
|
-
kind:
|
|
756
|
-
author:
|
|
951
|
+
kind: z5.enum(["turn", "board", "merge"]),
|
|
952
|
+
author: z5.string(),
|
|
757
953
|
/** The turn that made it, from the runner's commit message. */
|
|
758
954
|
turnId: UlidSchema.optional(),
|
|
759
|
-
subject:
|
|
760
|
-
files:
|
|
955
|
+
subject: z5.string(),
|
|
956
|
+
files: z5.array(HomeFileChangeSchema)
|
|
761
957
|
});
|
|
762
|
-
var HomeHistorySchema =
|
|
763
|
-
changes:
|
|
958
|
+
var HomeHistorySchema = z5.object({
|
|
959
|
+
changes: z5.array(HomeChangeSchema),
|
|
764
960
|
/** Whether older commits remain past the ones read. */
|
|
765
|
-
more:
|
|
961
|
+
more: z5.boolean()
|
|
766
962
|
});
|
|
767
963
|
var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
|
|
768
|
-
patch:
|
|
964
|
+
patch: z5.string().nullable(),
|
|
769
965
|
/** The patch was cut at its size limit. */
|
|
770
|
-
truncated:
|
|
966
|
+
truncated: z5.boolean()
|
|
771
967
|
});
|
|
772
|
-
var KnowledgeSchema =
|
|
968
|
+
var KnowledgeSchema = z5.object({
|
|
773
969
|
topic: NameSchema,
|
|
774
970
|
project: NameSchema.nullable(),
|
|
775
|
-
revision:
|
|
971
|
+
revision: z5.number().int().min(0),
|
|
776
972
|
updatedBy: NameSchema,
|
|
777
973
|
updatedAt: IsoDateTimeSchema
|
|
778
974
|
});
|
|
779
|
-
var RemovedKnowledgeSchema =
|
|
975
|
+
var RemovedKnowledgeSchema = z5.object({
|
|
780
976
|
topic: NameSchema,
|
|
781
977
|
project: NameSchema.nullable(),
|
|
782
978
|
removedBy: NameSchema,
|
|
783
979
|
removedAt: IsoDateTimeSchema
|
|
784
980
|
});
|
|
785
|
-
var DashboardSchema =
|
|
981
|
+
var DashboardSchema = z5.object({
|
|
786
982
|
project: NameSchema,
|
|
787
|
-
revision:
|
|
983
|
+
revision: z5.number().int().min(0),
|
|
788
984
|
/** Absent until someone updates the dashboard the project began with. */
|
|
789
985
|
updatedBy: NameSchema.optional(),
|
|
790
986
|
updatedAt: IsoDateTimeSchema.optional(),
|
|
791
|
-
body:
|
|
987
|
+
body: z5.string()
|
|
792
988
|
});
|
|
793
|
-
var ProposalOriginSchema =
|
|
989
|
+
var ProposalOriginSchema = z5.object({
|
|
794
990
|
scope: NameSchema,
|
|
795
991
|
thread: UlidSchema.optional(),
|
|
796
992
|
channel: NameSchema.optional()
|
|
797
993
|
});
|
|
798
994
|
var SKILL_FILES_LIMIT = 100;
|
|
799
995
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
800
|
-
var SkillFileSchema =
|
|
801
|
-
path:
|
|
802
|
-
size:
|
|
803
|
-
executable:
|
|
996
|
+
var SkillFileSchema = z5.object({
|
|
997
|
+
path: z5.string().min(1),
|
|
998
|
+
size: z5.number().int().nonnegative(),
|
|
999
|
+
executable: z5.boolean()
|
|
804
1000
|
});
|
|
805
|
-
var SkillFilesSchema =
|
|
1001
|
+
var SkillFilesSchema = z5.object({
|
|
806
1002
|
/** The commit of the proposer's home the files came from. */
|
|
807
1003
|
commit: CommitIdSchema,
|
|
808
1004
|
takenAt: IsoDateTimeSchema,
|
|
809
|
-
files:
|
|
1005
|
+
files: z5.array(SkillFileSchema)
|
|
810
1006
|
});
|
|
811
1007
|
var SkillFileContentSchema = SkillFileSchema.extend({
|
|
812
|
-
text:
|
|
1008
|
+
text: z5.string().nullable(),
|
|
813
1009
|
/** The text was cut at its size limit. */
|
|
814
|
-
truncated:
|
|
1010
|
+
truncated: z5.boolean()
|
|
815
1011
|
});
|
|
816
|
-
var ProposalFrontmatterSchema =
|
|
1012
|
+
var ProposalFrontmatterSchema = z5.object({
|
|
817
1013
|
id: UlidSchema,
|
|
818
1014
|
kind: ProposalKindSchema,
|
|
819
1015
|
proposedBy: NameSchema,
|
|
@@ -825,146 +1021,146 @@ var ProposalFrontmatterSchema = z4.object({
|
|
|
825
1021
|
skillFiles: SkillFilesSchema.optional(),
|
|
826
1022
|
decidedBy: NameSchema.optional(),
|
|
827
1023
|
decidedAt: IsoDateTimeSchema.optional(),
|
|
828
|
-
reason:
|
|
829
|
-
charter:
|
|
1024
|
+
reason: z5.string().optional(),
|
|
1025
|
+
charter: z5.record(z5.string(), z5.unknown()),
|
|
830
1026
|
/** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
|
|
831
1027
|
provisionedAt: IsoDateTimeSchema.optional(),
|
|
832
|
-
provision:
|
|
1028
|
+
provision: z5.record(z5.string(), z5.unknown()).optional()
|
|
833
1029
|
});
|
|
834
|
-
var DecisionSchema =
|
|
1030
|
+
var DecisionSchema = z5.object({
|
|
835
1031
|
id: UlidSchema,
|
|
836
1032
|
proposalId: UlidSchema,
|
|
837
1033
|
decidedBy: NameSchema,
|
|
838
|
-
outcome:
|
|
839
|
-
reason:
|
|
1034
|
+
outcome: z5.enum(["approved", "rejected"]),
|
|
1035
|
+
reason: z5.string().optional(),
|
|
840
1036
|
ts: IsoDateTimeSchema
|
|
841
1037
|
});
|
|
842
1038
|
|
|
843
1039
|
// packages/shared/dist/verbs.js
|
|
844
|
-
import { z as
|
|
845
|
-
var MomentSchema =
|
|
846
|
-
var CronTitleSchema =
|
|
847
|
-
var CronNoteSchema =
|
|
1040
|
+
import { z as z6 } from "zod";
|
|
1041
|
+
var MomentSchema = z6.iso.datetime({ offset: true });
|
|
1042
|
+
var CronTitleSchema = z6.string().trim().min(1).max(120);
|
|
1043
|
+
var CronNoteSchema = z6.string().trim().min(1).max(4e3);
|
|
848
1044
|
var VerbInputs = {
|
|
849
|
-
post_message:
|
|
1045
|
+
post_message: z6.object({
|
|
850
1046
|
/** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
|
|
851
1047
|
channel: ChannelRefSchema.optional(),
|
|
852
|
-
body:
|
|
1048
|
+
body: z6.string().min(1),
|
|
853
1049
|
thread_id: UlidSchema.optional()
|
|
854
1050
|
}),
|
|
855
|
-
read_inbox:
|
|
1051
|
+
read_inbox: z6.object({
|
|
856
1052
|
since_cursor: UlidSchema.nullable().optional(),
|
|
857
|
-
limit:
|
|
858
|
-
advance:
|
|
1053
|
+
limit: z6.number().int().positive().max(200).default(50),
|
|
1054
|
+
advance: z6.boolean().default(true)
|
|
859
1055
|
}),
|
|
860
|
-
search:
|
|
861
|
-
query:
|
|
1056
|
+
search: z6.object({
|
|
1057
|
+
query: z6.string().min(1),
|
|
862
1058
|
project: NameSchema.optional(),
|
|
863
1059
|
channel: ChannelRefSchema.optional(),
|
|
864
|
-
limit:
|
|
1060
|
+
limit: z6.number().int().positive().max(100).default(20)
|
|
865
1061
|
}),
|
|
866
1062
|
/** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
|
|
867
|
-
open_thread:
|
|
1063
|
+
open_thread: z6.object({
|
|
868
1064
|
task_id: UlidSchema.optional(),
|
|
869
1065
|
proposal_id: UlidSchema.optional(),
|
|
870
1066
|
channel: ChannelRefSchema.optional(),
|
|
871
|
-
title:
|
|
1067
|
+
title: z6.string().min(1).max(200).optional()
|
|
872
1068
|
}),
|
|
873
|
-
close_thread:
|
|
874
|
-
create_task:
|
|
1069
|
+
close_thread: z6.object({ thread_id: UlidSchema, summary: z6.string().min(1) }),
|
|
1070
|
+
create_task: z6.object({
|
|
875
1071
|
project: NameSchema,
|
|
876
|
-
title:
|
|
877
|
-
body:
|
|
1072
|
+
title: z6.string().min(1).max(200),
|
|
1073
|
+
body: z6.string().default(""),
|
|
878
1074
|
parent_id: UlidSchema.optional(),
|
|
879
|
-
required_capabilities:
|
|
1075
|
+
required_capabilities: z6.array(z6.string()).default([]),
|
|
880
1076
|
/** The plan; without it the task gets one stage, work, that anyone in the project may take. */
|
|
881
|
-
stages:
|
|
1077
|
+
stages: z6.array(PlanStageSchema).min(1).optional(),
|
|
882
1078
|
/** A channel of the project the task belongs to, such as a release's; its thread opens there. */
|
|
883
1079
|
channel: NameSchema.default("general")
|
|
884
1080
|
}),
|
|
885
|
-
claim_task:
|
|
886
|
-
release_task:
|
|
887
|
-
update_task:
|
|
1081
|
+
claim_task: z6.object({ task_id: UlidSchema }),
|
|
1082
|
+
release_task: z6.object({ task_id: UlidSchema }),
|
|
1083
|
+
update_task: z6.object({
|
|
888
1084
|
task_id: UlidSchema,
|
|
889
1085
|
/** Move the task back to this earlier stage. */
|
|
890
1086
|
stage: StageIdSchema.optional(),
|
|
891
|
-
status:
|
|
1087
|
+
status: z6.enum(["abandoned"]).optional(),
|
|
892
1088
|
/** Posted to the task's thread as the caller, marked with the step when the task moved. */
|
|
893
|
-
note:
|
|
894
|
-
blocked_by:
|
|
1089
|
+
note: z6.string().min(1).optional(),
|
|
1090
|
+
blocked_by: z6.array(UlidSchema).optional(),
|
|
895
1091
|
/**
|
|
896
1092
|
* The pull request a `ghpr` task lands through, on its project's repository, with the merge
|
|
897
1093
|
* commit's subject and body; null unlinks it.
|
|
898
1094
|
*/
|
|
899
|
-
pull_request:
|
|
1095
|
+
pull_request: z6.object({
|
|
900
1096
|
url: PullRequestUrlSchema,
|
|
901
1097
|
merge_subject: MergeSubjectSchema.optional(),
|
|
902
|
-
merge_body:
|
|
1098
|
+
merge_body: z6.string().max(2e4).optional()
|
|
903
1099
|
}).nullable().optional()
|
|
904
1100
|
}),
|
|
905
|
-
get_task:
|
|
906
|
-
subscribe:
|
|
907
|
-
unsubscribe:
|
|
908
|
-
propose:
|
|
1101
|
+
get_task: z6.object({ task_id: UlidSchema }),
|
|
1102
|
+
subscribe: z6.object({ channel: ChannelRefSchema }),
|
|
1103
|
+
unsubscribe: z6.object({ channel: ChannelRefSchema }),
|
|
1104
|
+
propose: z6.object({
|
|
909
1105
|
kind: ProposalKindSchema,
|
|
910
|
-
charter:
|
|
911
|
-
rationale:
|
|
1106
|
+
charter: z6.record(z6.string(), z6.unknown()),
|
|
1107
|
+
rationale: z6.string().default("")
|
|
912
1108
|
}),
|
|
913
|
-
approve:
|
|
914
|
-
reject:
|
|
915
|
-
create_project:
|
|
1109
|
+
approve: z6.object({ proposal_id: UlidSchema, reason: z6.string().optional() }),
|
|
1110
|
+
reject: z6.object({ proposal_id: UlidSchema, reason: z6.string().min(1) }),
|
|
1111
|
+
create_project: z6.object({
|
|
916
1112
|
slug: NameSchema,
|
|
917
|
-
name:
|
|
918
|
-
repo:
|
|
1113
|
+
name: z6.string().min(1).optional(),
|
|
1114
|
+
repo: z6.string().min(1).nullable().default(null),
|
|
919
1115
|
default_branch: DefaultBranchSchema.default("main"),
|
|
920
1116
|
on_done: CompletionEffectSchema.optional()
|
|
921
1117
|
}),
|
|
922
|
-
join_project:
|
|
923
|
-
leave_project:
|
|
924
|
-
write_knowledge:
|
|
1118
|
+
join_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1119
|
+
leave_project: z6.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
1120
|
+
write_knowledge: z6.object({
|
|
925
1121
|
/** A project you belong to, or null for society knowledge, which the steward and the user curate. */
|
|
926
1122
|
project: NameSchema.nullable().default(null),
|
|
927
1123
|
topic: NameSchema,
|
|
928
|
-
body:
|
|
1124
|
+
body: z6.string().min(1),
|
|
929
1125
|
/** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
|
|
930
|
-
revision:
|
|
1126
|
+
revision: z6.number().int().min(0)
|
|
931
1127
|
}),
|
|
932
|
-
remove_knowledge:
|
|
1128
|
+
remove_knowledge: z6.object({
|
|
933
1129
|
/** As for write_knowledge: a project you belong to, or null for society knowledge. */
|
|
934
1130
|
project: NameSchema.nullable().default(null),
|
|
935
1131
|
topic: NameSchema
|
|
936
1132
|
}),
|
|
937
|
-
plan_task:
|
|
1133
|
+
plan_task: z6.object({
|
|
938
1134
|
task_id: UlidSchema,
|
|
939
|
-
stages:
|
|
1135
|
+
stages: z6.array(PlanEditStageSchema),
|
|
940
1136
|
on_done: CompletionEffectSchema.optional()
|
|
941
1137
|
}),
|
|
942
1138
|
/** The note is posted to the task's thread as the caller, marked with the step. */
|
|
943
|
-
advance_task:
|
|
944
|
-
configure_project:
|
|
1139
|
+
advance_task: z6.object({ task_id: UlidSchema, note: z6.string().min(1).optional() }),
|
|
1140
|
+
configure_project: z6.object({
|
|
945
1141
|
project: NameSchema,
|
|
946
1142
|
on_done: CompletionEffectSchema.optional(),
|
|
947
1143
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
948
|
-
name:
|
|
1144
|
+
name: z6.string().trim().min(1).max(120).optional(),
|
|
949
1145
|
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
950
1146
|
default_branch: DefaultBranchSchema.optional()
|
|
951
1147
|
}),
|
|
952
|
-
archive_project:
|
|
953
|
-
create_channel:
|
|
1148
|
+
archive_project: z6.object({ project: NameSchema, reason: z6.string().min(1) }),
|
|
1149
|
+
create_channel: z6.object({
|
|
954
1150
|
/** A project, or null for a channel of the society. */
|
|
955
1151
|
project: NameSchema.nullable().default(null),
|
|
956
1152
|
name: NameSchema,
|
|
957
|
-
purpose:
|
|
1153
|
+
purpose: z6.string().min(1)
|
|
958
1154
|
}),
|
|
959
|
-
archive_channel:
|
|
960
|
-
update_dashboard:
|
|
1155
|
+
archive_channel: z6.object({ channel: ChannelRefSchema, reason: z6.string().min(1) }),
|
|
1156
|
+
update_dashboard: z6.object({
|
|
961
1157
|
project: NameSchema,
|
|
962
1158
|
/** The whole dashboard as markdown; the board keeps its frontmatter. */
|
|
963
|
-
body:
|
|
1159
|
+
body: z6.string().min(1),
|
|
964
1160
|
/** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
|
|
965
|
-
revision:
|
|
1161
|
+
revision: z6.number().int().min(0)
|
|
966
1162
|
}),
|
|
967
|
-
create_cron:
|
|
1163
|
+
create_cron: z6.object({
|
|
968
1164
|
title: CronTitleSchema,
|
|
969
1165
|
/** What to do when it fires; quoted in the prompt of every turn it starts. */
|
|
970
1166
|
note: CronNoteSchema,
|
|
@@ -983,10 +1179,10 @@ var VerbInputs = {
|
|
|
983
1179
|
/** An open channel of that place other than general, whose conversation it fires in. */
|
|
984
1180
|
channel: NameSchema.optional()
|
|
985
1181
|
}),
|
|
986
|
-
update_cron:
|
|
1182
|
+
update_cron: z6.object({
|
|
987
1183
|
cron_id: UlidSchema,
|
|
988
1184
|
/** Pause it, or resume it, which starts its schedule afresh from now. */
|
|
989
|
-
paused:
|
|
1185
|
+
paused: z6.boolean().optional(),
|
|
990
1186
|
title: CronTitleSchema.optional(),
|
|
991
1187
|
note: CronNoteSchema.optional(),
|
|
992
1188
|
/** A new expression, or `at` for one time; either starts the schedule afresh. */
|
|
@@ -995,8 +1191,8 @@ var VerbInputs = {
|
|
|
995
1191
|
timezone: TimeZoneSchema.nullable().optional(),
|
|
996
1192
|
at: MomentSchema.optional()
|
|
997
1193
|
}),
|
|
998
|
-
remove_cron:
|
|
999
|
-
remove_skill:
|
|
1194
|
+
remove_cron: z6.object({ cron_id: UlidSchema, reason: z6.string().trim().min(1) }),
|
|
1195
|
+
remove_skill: z6.object({ name: NameSchema, reason: z6.string().trim().min(1) })
|
|
1000
1196
|
};
|
|
1001
1197
|
var VERB_DESCRIPTIONS = {
|
|
1002
1198
|
post_message: "Post a markdown message to a channel, or into a thread when thread_id is given; a thread's message goes to the thread's channel.",
|
|
@@ -1007,7 +1203,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
1007
1203
|
create_task: "Create a task in a project with its plan: stages of {name, role or agent, gate}. Without stages it gets one stage, work, that anyone in the project may take. File it in a channel of the project, such as a release's, or general by default; its thread opens there with it, under the task's id, for everything said about the work.",
|
|
1008
1204
|
claim_task: "Hold the task's current stage. Claims are leases renewed by every turn that touches the task.",
|
|
1009
1205
|
release_task: "Let go of the stage you hold so someone else can take it.",
|
|
1010
|
-
update_task: "Move a task back to an earlier stage, abandon it, set
|
|
1206
|
+
update_task: "Move a task back to an earlier stage, abandon it, set the tasks it is blocked by (blocked_by: its stage wakes nobody until they have all ended), or link its pull request (pull_request: url, with the merge commit's merge_subject and merge_body), which a ghpr task needs before its last stage completes. A note is posted to the task's thread.",
|
|
1011
1207
|
get_task: "Read a task with its plan, its brief, and the messages of its thread.",
|
|
1012
1208
|
subscribe: "Subscribe to a channel. Subscriptions feed your digest; they never wake you.",
|
|
1013
1209
|
unsubscribe: "Unsubscribe from a channel.",
|
|
@@ -1033,7 +1229,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
1033
1229
|
};
|
|
1034
1230
|
|
|
1035
1231
|
// packages/shared/dist/events.js
|
|
1036
|
-
import { z as
|
|
1232
|
+
import { z as z7 } from "zod";
|
|
1037
1233
|
var BOARD_EVENT_TYPES = [
|
|
1038
1234
|
"society.initialized",
|
|
1039
1235
|
"project.added",
|
|
@@ -1078,6 +1274,7 @@ var BOARD_EVENT_TYPES = [
|
|
|
1078
1274
|
"user.signed_out",
|
|
1079
1275
|
"user.token_rotated",
|
|
1080
1276
|
"runner.changed",
|
|
1277
|
+
"runner.quota",
|
|
1081
1278
|
"ops.signal",
|
|
1082
1279
|
"paused.changed",
|
|
1083
1280
|
"wake.requested",
|
|
@@ -1098,28 +1295,36 @@ var BOARD_EVENT_TYPES = [
|
|
|
1098
1295
|
"cron.skipped",
|
|
1099
1296
|
"cron.ended"
|
|
1100
1297
|
];
|
|
1101
|
-
var BoardEventTypeSchema =
|
|
1102
|
-
var BoardEventSchema =
|
|
1298
|
+
var BoardEventTypeSchema = z7.enum(BOARD_EVENT_TYPES);
|
|
1299
|
+
var BoardEventSchema = z7.object({
|
|
1103
1300
|
id: UlidSchema,
|
|
1104
1301
|
ts: IsoDateTimeSchema,
|
|
1105
1302
|
type: BoardEventTypeSchema,
|
|
1106
1303
|
actor: NameSchema,
|
|
1107
|
-
payload:
|
|
1304
|
+
payload: z7.record(z7.string(), z7.unknown())
|
|
1108
1305
|
});
|
|
1109
|
-
var UsageSchema =
|
|
1110
|
-
inputTokens:
|
|
1111
|
-
outputTokens:
|
|
1112
|
-
cacheReadTokens:
|
|
1113
|
-
cacheWriteTokens:
|
|
1306
|
+
var UsageSchema = z7.object({
|
|
1307
|
+
inputTokens: z7.number().int().nonnegative(),
|
|
1308
|
+
outputTokens: z7.number().int().nonnegative(),
|
|
1309
|
+
cacheReadTokens: z7.number().int().nonnegative().default(0),
|
|
1310
|
+
cacheWriteTokens: z7.number().int().nonnegative().default(0)
|
|
1114
1311
|
});
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1312
|
+
function addUsage(a, b) {
|
|
1313
|
+
return {
|
|
1314
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
1315
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
1316
|
+
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens,
|
|
1317
|
+
cacheWriteTokens: a.cacheWriteTokens + b.cacheWriteTokens
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
var TurnStatusSchema = z7.object({
|
|
1321
|
+
summary: z7.string().min(1),
|
|
1322
|
+
claimsHeld: z7.array(UlidSchema).default([]),
|
|
1323
|
+
blockedOn: z7.array(z7.string()).default([]),
|
|
1324
|
+
needsUserDecision: z7.boolean().default(false),
|
|
1325
|
+
memoryUpdated: z7.boolean().default(false)
|
|
1121
1326
|
});
|
|
1122
|
-
var TurnExitReasonSchema =
|
|
1327
|
+
var TurnExitReasonSchema = z7.enum([
|
|
1123
1328
|
"completed",
|
|
1124
1329
|
"timeout",
|
|
1125
1330
|
"error",
|
|
@@ -1127,50 +1332,50 @@ var TurnExitReasonSchema = z6.enum([
|
|
|
1127
1332
|
"blocked",
|
|
1128
1333
|
"stopped"
|
|
1129
1334
|
]);
|
|
1130
|
-
var AgentEventSchema =
|
|
1131
|
-
|
|
1132
|
-
type:
|
|
1335
|
+
var AgentEventSchema = z7.discriminatedUnion("type", [
|
|
1336
|
+
z7.object({
|
|
1337
|
+
type: z7.literal("turn_started"),
|
|
1133
1338
|
agent: NameSchema,
|
|
1134
|
-
session:
|
|
1339
|
+
session: z7.string(),
|
|
1135
1340
|
runner: NameSchema,
|
|
1136
1341
|
/** The model the CLI reports running, when it reports one. */
|
|
1137
|
-
model:
|
|
1342
|
+
model: z7.string().optional()
|
|
1138
1343
|
}),
|
|
1139
1344
|
/**
|
|
1140
1345
|
* The CLI took a steer into the running turn. Adapters name the steer; the server adds the
|
|
1141
1346
|
* messages it delivered and the text the CLI was given.
|
|
1142
1347
|
*/
|
|
1143
|
-
|
|
1144
|
-
type:
|
|
1145
|
-
steer:
|
|
1146
|
-
messages:
|
|
1147
|
-
text:
|
|
1348
|
+
z7.object({
|
|
1349
|
+
type: z7.literal("steered"),
|
|
1350
|
+
steer: z7.string().min(1),
|
|
1351
|
+
messages: z7.array(UlidSchema).optional(),
|
|
1352
|
+
text: z7.string().optional()
|
|
1148
1353
|
}),
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
type:
|
|
1153
|
-
name:
|
|
1154
|
-
ok:
|
|
1354
|
+
z7.object({ type: z7.literal("text"), delta: z7.string() }),
|
|
1355
|
+
z7.object({ type: z7.literal("tool_call"), name: z7.string(), input: z7.unknown() }),
|
|
1356
|
+
z7.object({
|
|
1357
|
+
type: z7.literal("tool_result"),
|
|
1358
|
+
name: z7.string(),
|
|
1359
|
+
ok: z7.boolean(),
|
|
1155
1360
|
/** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
|
|
1156
|
-
output:
|
|
1361
|
+
output: z7.string().optional()
|
|
1157
1362
|
}),
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
type:
|
|
1363
|
+
z7.object({ type: z7.literal("approval_requested"), kind: z7.string(), detail: z7.unknown() }),
|
|
1364
|
+
z7.object({
|
|
1365
|
+
type: z7.literal("turn_completed"),
|
|
1161
1366
|
usage: UsageSchema,
|
|
1162
|
-
costUsd:
|
|
1367
|
+
costUsd: z7.number().nonnegative(),
|
|
1163
1368
|
status: TurnStatusSchema.nullable(),
|
|
1164
1369
|
exitReason: TurnExitReasonSchema
|
|
1165
1370
|
}),
|
|
1166
|
-
|
|
1371
|
+
z7.object({ type: z7.literal("error"), message: z7.string() })
|
|
1167
1372
|
]);
|
|
1168
|
-
var TranscriptEntrySchema =
|
|
1373
|
+
var TranscriptEntrySchema = z7.object({
|
|
1169
1374
|
ts: IsoDateTimeSchema,
|
|
1170
1375
|
event: AgentEventSchema
|
|
1171
1376
|
});
|
|
1172
|
-
var LiveTurnEventSchema =
|
|
1173
|
-
seq:
|
|
1377
|
+
var LiveTurnEventSchema = z7.object({
|
|
1378
|
+
seq: z7.number().int().positive(),
|
|
1174
1379
|
ts: IsoDateTimeSchema,
|
|
1175
1380
|
agent: NameSchema,
|
|
1176
1381
|
project: NameSchema,
|
|
@@ -1182,10 +1387,10 @@ var LiveTurnEventSchema = z6.object({
|
|
|
1182
1387
|
});
|
|
1183
1388
|
|
|
1184
1389
|
// packages/shared/dist/turns.js
|
|
1185
|
-
import { z as
|
|
1390
|
+
import { z as z9 } from "zod";
|
|
1186
1391
|
|
|
1187
1392
|
// packages/shared/dist/runner.js
|
|
1188
|
-
import { z as
|
|
1393
|
+
import { z as z8 } from "zod";
|
|
1189
1394
|
var RUNNER_PROTOCOL = 1;
|
|
1190
1395
|
var PATH_TOKENS = {
|
|
1191
1396
|
home: "{{stellaris:home}}",
|
|
@@ -1196,67 +1401,75 @@ var SERVER_TOKEN = "{{stellaris:server}}";
|
|
|
1196
1401
|
var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
|
|
1197
1402
|
var HOME_SCRATCH = "scratch";
|
|
1198
1403
|
var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1199
|
-
var RunnerHelloSchema =
|
|
1200
|
-
protocol:
|
|
1201
|
-
version:
|
|
1404
|
+
var RunnerHelloSchema = z8.object({
|
|
1405
|
+
protocol: z8.number().int().positive(),
|
|
1406
|
+
version: z8.string().min(1),
|
|
1202
1407
|
os: RunnerOsSchema,
|
|
1203
|
-
clis:
|
|
1408
|
+
clis: z8.array(CliKindSchema),
|
|
1204
1409
|
/** The CLIs whose adapter can keep a session warm between turns. */
|
|
1205
|
-
residentClis:
|
|
1410
|
+
residentClis: z8.array(CliKindSchema).default([]),
|
|
1206
1411
|
/** The CLIs whose adapter can take input into a running turn; the server steers only these. */
|
|
1207
|
-
steerableClis:
|
|
1412
|
+
steerableClis: z8.array(CliKindSchema).default([]),
|
|
1208
1413
|
/** The CLIs whose adapter can stop a running turn; the server stops only these. */
|
|
1209
|
-
stoppableClis:
|
|
1414
|
+
stoppableClis: z8.array(CliKindSchema).default([]),
|
|
1210
1415
|
/** Whether the runner answers file reads on its projects' branches; one from before never does. */
|
|
1211
|
-
branchReads:
|
|
1416
|
+
branchReads: z8.boolean().default(false),
|
|
1212
1417
|
/** Whether the runner lists what a branch changed; one from before never does. */
|
|
1213
|
-
branchChanges:
|
|
1214
|
-
|
|
1418
|
+
branchChanges: z8.boolean().default(false),
|
|
1419
|
+
/** Whether the runner answers a read of a CLI's quota; one from before never does. */
|
|
1420
|
+
quotaReads: z8.boolean().default(false),
|
|
1421
|
+
capabilities: z8.array(z8.string()).default([]),
|
|
1422
|
+
/**
|
|
1423
|
+
* What the runner's account lacks of the git identity citizens' own commits carry, checked
|
|
1424
|
+
* before each hello outside any repository; empty when it has both. Absent from a runner from
|
|
1425
|
+
* before the check, which no default fills in, so the board marks only what a runner reported.
|
|
1426
|
+
*/
|
|
1427
|
+
gitIdentityMissing: z8.array(GitIdentityPartSchema).optional(),
|
|
1215
1428
|
/** Turns the runner runs at once, or null for no limit. */
|
|
1216
|
-
slots:
|
|
1429
|
+
slots: z8.number().int().positive().nullable(),
|
|
1217
1430
|
/** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
|
|
1218
|
-
turns:
|
|
1431
|
+
turns: z8.array(UlidSchema).default([])
|
|
1219
1432
|
});
|
|
1220
|
-
var RunnerWelcomeSchema =
|
|
1221
|
-
var RunnerEnrollRequestSchema =
|
|
1222
|
-
protocol:
|
|
1223
|
-
version:
|
|
1224
|
-
hostname:
|
|
1433
|
+
var RunnerWelcomeSchema = z8.object({ name: NameSchema, version: z8.string() });
|
|
1434
|
+
var RunnerEnrollRequestSchema = z8.object({
|
|
1435
|
+
protocol: z8.number().int().positive(),
|
|
1436
|
+
version: z8.string().min(1).max(64),
|
|
1437
|
+
hostname: z8.string().min(1).max(253),
|
|
1225
1438
|
os: RunnerOsSchema,
|
|
1226
|
-
clis:
|
|
1227
|
-
capabilities:
|
|
1439
|
+
clis: z8.array(CliKindSchema).max(8),
|
|
1440
|
+
capabilities: z8.array(z8.string().min(1).max(64)).max(32).default([])
|
|
1228
1441
|
});
|
|
1229
|
-
var RunnerEnrollmentSchema =
|
|
1230
|
-
deviceCode:
|
|
1231
|
-
userCode:
|
|
1442
|
+
var RunnerEnrollmentSchema = z8.object({
|
|
1443
|
+
deviceCode: z8.string().min(1),
|
|
1444
|
+
userCode: z8.string().min(1),
|
|
1232
1445
|
expiresAt: IsoDateTimeSchema,
|
|
1233
|
-
intervalMs:
|
|
1446
|
+
intervalMs: z8.number().int().positive()
|
|
1234
1447
|
});
|
|
1235
|
-
var RunnerEnrollPollSchema =
|
|
1236
|
-
var RunnerEnrollStatusSchema =
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1448
|
+
var RunnerEnrollPollSchema = z8.object({ deviceCode: z8.string().min(1) });
|
|
1449
|
+
var RunnerEnrollStatusSchema = z8.discriminatedUnion("status", [
|
|
1450
|
+
z8.object({ status: z8.literal("pending") }),
|
|
1451
|
+
z8.object({ status: z8.literal("approved"), name: NameSchema, token: z8.string().min(1) }),
|
|
1452
|
+
z8.object({ status: z8.literal("denied") })
|
|
1240
1453
|
]);
|
|
1241
|
-
var PendingEnrollmentSchema =
|
|
1242
|
-
userCode:
|
|
1243
|
-
hostname:
|
|
1454
|
+
var PendingEnrollmentSchema = z8.object({
|
|
1455
|
+
userCode: z8.string().min(1),
|
|
1456
|
+
hostname: z8.string(),
|
|
1244
1457
|
os: RunnerOsSchema,
|
|
1245
|
-
clis:
|
|
1246
|
-
capabilities:
|
|
1247
|
-
version:
|
|
1458
|
+
clis: z8.array(CliKindSchema),
|
|
1459
|
+
capabilities: z8.array(z8.string()),
|
|
1460
|
+
version: z8.string(),
|
|
1248
1461
|
requestedAt: IsoDateTimeSchema,
|
|
1249
1462
|
expiresAt: IsoDateTimeSchema
|
|
1250
1463
|
});
|
|
1251
|
-
var ProjectRepoSchema =
|
|
1464
|
+
var ProjectRepoSchema = z8.object({
|
|
1252
1465
|
slug: NameSchema,
|
|
1253
|
-
origin:
|
|
1254
|
-
defaultBranch:
|
|
1466
|
+
origin: z8.string().nullable(),
|
|
1467
|
+
defaultBranch: z8.string().min(1),
|
|
1255
1468
|
/**
|
|
1256
1469
|
* The board lands finished tasks on the default branch, itself or through their pull requests,
|
|
1257
1470
|
* so the runner lets nothing else move it.
|
|
1258
1471
|
*/
|
|
1259
|
-
boardLands:
|
|
1472
|
+
boardLands: z8.boolean()
|
|
1260
1473
|
});
|
|
1261
1474
|
function projectRepo(project) {
|
|
1262
1475
|
return {
|
|
@@ -1266,29 +1479,29 @@ function projectRepo(project) {
|
|
|
1266
1479
|
boardLands: project.onDone !== "none"
|
|
1267
1480
|
};
|
|
1268
1481
|
}
|
|
1269
|
-
var TurnWorkspaceSchema =
|
|
1270
|
-
|
|
1271
|
-
kind:
|
|
1482
|
+
var TurnWorkspaceSchema = z8.discriminatedUnion("kind", [
|
|
1483
|
+
z8.object({
|
|
1484
|
+
kind: z8.literal("home"),
|
|
1272
1485
|
thread: UlidSchema.optional(),
|
|
1273
1486
|
channel: NameSchema.optional()
|
|
1274
1487
|
}),
|
|
1275
|
-
|
|
1276
|
-
kind:
|
|
1488
|
+
z8.object({
|
|
1489
|
+
kind: z8.literal("project"),
|
|
1277
1490
|
repo: ProjectRepoSchema,
|
|
1278
|
-
branches:
|
|
1491
|
+
branches: z8.array(z8.string().min(1)),
|
|
1279
1492
|
thread: UlidSchema.optional(),
|
|
1280
1493
|
channel: NameSchema.optional()
|
|
1281
1494
|
}),
|
|
1282
|
-
|
|
1495
|
+
z8.object({ kind: z8.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
|
|
1283
1496
|
]);
|
|
1284
|
-
var TurnJobSchema =
|
|
1497
|
+
var TurnJobSchema = z8.object({
|
|
1285
1498
|
turnId: UlidSchema,
|
|
1286
1499
|
agent: NameSchema,
|
|
1287
1500
|
role: NameSchema,
|
|
1288
1501
|
cli: CliKindSchema,
|
|
1289
|
-
model:
|
|
1502
|
+
model: z8.string().optional(),
|
|
1290
1503
|
/** The reasoning effort set on the agent; absent for the model's own default. */
|
|
1291
|
-
effort:
|
|
1504
|
+
effort: z8.string().optional(),
|
|
1292
1505
|
/** A project slug, or the society scope. */
|
|
1293
1506
|
scope: NameSchema,
|
|
1294
1507
|
/** The thread whose conversation the turn is in; absent for the home conversation. */
|
|
@@ -1299,125 +1512,130 @@ var TurnJobSchema = z7.object({
|
|
|
1299
1512
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1300
1513
|
* the outcome reports it.
|
|
1301
1514
|
*/
|
|
1302
|
-
session:
|
|
1515
|
+
session: z8.string().min(1).nullable(),
|
|
1303
1516
|
/** The running total the session reported after its last turn, which a resumed session starts from. */
|
|
1304
|
-
costSoFarUsd:
|
|
1517
|
+
costSoFarUsd: z8.number().nonnegative(),
|
|
1305
1518
|
/** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
|
|
1306
|
-
instructions:
|
|
1307
|
-
prompt:
|
|
1308
|
-
mcp:
|
|
1309
|
-
limits:
|
|
1310
|
-
timeoutMs:
|
|
1311
|
-
maxTurns:
|
|
1519
|
+
instructions: z8.string(),
|
|
1520
|
+
prompt: z8.string(),
|
|
1521
|
+
mcp: z8.object({ url: z8.string().min(1), token: z8.string().min(1) }),
|
|
1522
|
+
limits: z8.object({
|
|
1523
|
+
timeoutMs: z8.number().int().positive().nullable(),
|
|
1524
|
+
maxTurns: z8.number().int().positive().nullable()
|
|
1312
1525
|
}),
|
|
1313
1526
|
/** Present when the runner keeps the conversation's session warm between turns, under `key`. */
|
|
1314
|
-
resident:
|
|
1527
|
+
resident: z8.object({ key: z8.string().min(1), idleMs: z8.number().int().positive() }).optional(),
|
|
1315
1528
|
workspace: TurnWorkspaceSchema
|
|
1316
1529
|
});
|
|
1317
|
-
var TurnWorkSchema =
|
|
1318
|
-
var TurnEventsSchema =
|
|
1319
|
-
var TurnOutcomeSchema =
|
|
1530
|
+
var TurnWorkSchema = z8.object({ branch: z8.string().min(1), head: z8.string().min(1) });
|
|
1531
|
+
var TurnEventsSchema = z8.object({ entries: z8.array(TranscriptEntrySchema) });
|
|
1532
|
+
var TurnOutcomeSchema = z8.object({
|
|
1320
1533
|
exitReason: TurnExitReasonSchema,
|
|
1321
1534
|
status: TurnStatusSchema.nullable(),
|
|
1322
|
-
error:
|
|
1535
|
+
error: z8.string().nullable(),
|
|
1323
1536
|
usage: UsageSchema,
|
|
1324
|
-
costUsd:
|
|
1537
|
+
costUsd: z8.number().nonnegative(),
|
|
1325
1538
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1326
|
-
sessionCostUsd:
|
|
1539
|
+
sessionCostUsd: z8.number().nonnegative().optional(),
|
|
1327
1540
|
/** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
|
|
1328
|
-
session:
|
|
1541
|
+
session: z8.string().min(1),
|
|
1329
1542
|
/** The model the CLI reported, when it reported one. */
|
|
1330
|
-
model:
|
|
1543
|
+
model: z8.string().nullable(),
|
|
1331
1544
|
work: TurnWorkSchema.nullable(),
|
|
1332
1545
|
/**
|
|
1333
1546
|
* Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
|
|
1334
1547
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1335
1548
|
*/
|
|
1336
|
-
leftovers:
|
|
1549
|
+
leftovers: z8.boolean().default(false),
|
|
1550
|
+
/**
|
|
1551
|
+
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1552
|
+
* before never sends it.
|
|
1553
|
+
*/
|
|
1554
|
+
quota: CliQuotaSchema.optional()
|
|
1337
1555
|
});
|
|
1338
|
-
var TurnAckSchema =
|
|
1339
|
-
var LandRequestSchema =
|
|
1556
|
+
var TurnAckSchema = z8.object({ dropWorkspace: z8.boolean() });
|
|
1557
|
+
var LandRequestSchema = z8.object({
|
|
1340
1558
|
repo: ProjectRepoSchema,
|
|
1341
|
-
branch:
|
|
1559
|
+
branch: z8.string().min(1),
|
|
1342
1560
|
/**
|
|
1343
1561
|
* For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
|
|
1344
1562
|
* branch's files, and the merge commit's subject and body, the pull request's title and an
|
|
1345
1563
|
* empty body when left out.
|
|
1346
1564
|
*/
|
|
1347
|
-
pullRequest:
|
|
1348
|
-
url:
|
|
1349
|
-
subject:
|
|
1350
|
-
body:
|
|
1565
|
+
pullRequest: z8.object({
|
|
1566
|
+
url: z8.string().min(1),
|
|
1567
|
+
subject: z8.string().min(1).optional(),
|
|
1568
|
+
body: z8.string().optional()
|
|
1351
1569
|
}).optional()
|
|
1352
1570
|
});
|
|
1353
|
-
var CloseRequestSchema =
|
|
1571
|
+
var CloseRequestSchema = z8.object({
|
|
1354
1572
|
repo: ProjectRepoSchema,
|
|
1355
|
-
url:
|
|
1356
|
-
comment:
|
|
1573
|
+
url: z8.string().min(1),
|
|
1574
|
+
comment: z8.string().min(1)
|
|
1357
1575
|
});
|
|
1358
|
-
var MergeOutcomeSchema =
|
|
1359
|
-
var ModelListSchema =
|
|
1576
|
+
var MergeOutcomeSchema = z8.object({ ok: z8.boolean(), detail: z8.string() });
|
|
1577
|
+
var ModelListSchema = z8.array(ModelOptionSchema);
|
|
1360
1578
|
var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1361
|
-
var BranchPathSchema =
|
|
1362
|
-
var BranchNameSchema =
|
|
1363
|
-
var BranchReadSchema =
|
|
1579
|
+
var BranchPathSchema = z8.string().max(4096).refine((value) => value === "" || value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== ".." && !segment.includes("\0")), { message: "a path inside the branch, with no empty, `.`, or `..` segment" });
|
|
1580
|
+
var BranchNameSchema = z8.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
|
|
1581
|
+
var BranchReadSchema = z8.object({
|
|
1364
1582
|
repo: ProjectRepoSchema,
|
|
1365
1583
|
branch: BranchNameSchema,
|
|
1366
1584
|
path: BranchPathSchema
|
|
1367
1585
|
});
|
|
1368
|
-
var BranchCommitSchema =
|
|
1369
|
-
id:
|
|
1586
|
+
var BranchCommitSchema = z8.object({
|
|
1587
|
+
id: z8.string().min(1),
|
|
1370
1588
|
at: IsoDateTimeSchema,
|
|
1371
|
-
author:
|
|
1589
|
+
author: z8.string()
|
|
1372
1590
|
});
|
|
1373
|
-
var BranchFileSchema =
|
|
1374
|
-
|
|
1375
|
-
kind:
|
|
1591
|
+
var BranchFileSchema = z8.discriminatedUnion("kind", [
|
|
1592
|
+
z8.object({
|
|
1593
|
+
kind: z8.literal("file"),
|
|
1376
1594
|
path: BranchPathSchema,
|
|
1377
1595
|
commit: BranchCommitSchema,
|
|
1378
|
-
size:
|
|
1379
|
-
content:
|
|
1596
|
+
size: z8.number().int().nonnegative(),
|
|
1597
|
+
content: z8.string().nullable()
|
|
1380
1598
|
}),
|
|
1381
|
-
|
|
1382
|
-
kind:
|
|
1599
|
+
z8.object({
|
|
1600
|
+
kind: z8.literal("dir"),
|
|
1383
1601
|
path: BranchPathSchema,
|
|
1384
1602
|
commit: BranchCommitSchema,
|
|
1385
|
-
entries:
|
|
1386
|
-
name:
|
|
1387
|
-
kind:
|
|
1388
|
-
size:
|
|
1603
|
+
entries: z8.array(z8.object({
|
|
1604
|
+
name: z8.string().min(1),
|
|
1605
|
+
kind: z8.enum(["file", "dir"]),
|
|
1606
|
+
size: z8.number().int().nonnegative().nullable()
|
|
1389
1607
|
}))
|
|
1390
1608
|
})
|
|
1391
1609
|
]);
|
|
1392
|
-
var BranchChangesReadSchema =
|
|
1610
|
+
var BranchChangesReadSchema = z8.object({
|
|
1393
1611
|
repo: ProjectRepoSchema,
|
|
1394
1612
|
branch: BranchNameSchema
|
|
1395
1613
|
});
|
|
1396
|
-
var BranchFileChangeSchema =
|
|
1397
|
-
path:
|
|
1398
|
-
status:
|
|
1614
|
+
var BranchFileChangeSchema = z8.object({
|
|
1615
|
+
path: z8.string().min(1),
|
|
1616
|
+
status: z8.enum(["added", "modified", "deleted"]),
|
|
1399
1617
|
/** Lines added and removed, or null for a binary file. */
|
|
1400
|
-
added:
|
|
1401
|
-
removed:
|
|
1618
|
+
added: z8.number().int().nonnegative().nullable(),
|
|
1619
|
+
removed: z8.number().int().nonnegative().nullable(),
|
|
1402
1620
|
/** Absent when the file changed only through a merge of another branch. */
|
|
1403
|
-
lastChange:
|
|
1621
|
+
lastChange: z8.object({ author: z8.string(), at: IsoDateTimeSchema }).optional()
|
|
1404
1622
|
});
|
|
1405
|
-
var BranchChangesSchema =
|
|
1623
|
+
var BranchChangesSchema = z8.object({
|
|
1406
1624
|
head: BranchCommitSchema,
|
|
1407
|
-
files:
|
|
1408
|
-
total:
|
|
1625
|
+
files: z8.array(BranchFileChangeSchema),
|
|
1626
|
+
total: z8.number().int().nonnegative()
|
|
1409
1627
|
});
|
|
1410
|
-
var BranchDiffReadSchema =
|
|
1628
|
+
var BranchDiffReadSchema = z8.object({
|
|
1411
1629
|
repo: ProjectRepoSchema,
|
|
1412
1630
|
branch: BranchNameSchema,
|
|
1413
1631
|
path: BranchPathSchema.min(1)
|
|
1414
1632
|
});
|
|
1415
|
-
var BranchFileDiffSchema =
|
|
1633
|
+
var BranchFileDiffSchema = z8.object({
|
|
1416
1634
|
head: BranchCommitSchema,
|
|
1417
1635
|
path: BranchPathSchema,
|
|
1418
1636
|
status: BranchFileChangeSchema.shape.status,
|
|
1419
|
-
patch:
|
|
1420
|
-
truncated:
|
|
1637
|
+
patch: z8.string().nullable(),
|
|
1638
|
+
truncated: z8.boolean()
|
|
1421
1639
|
});
|
|
1422
1640
|
function patchHunks(patch, limit2) {
|
|
1423
1641
|
if (/^Binary files .* differ$/m.test(patch)) {
|
|
@@ -1431,9 +1649,9 @@ function patchHunks(patch, limit2) {
|
|
|
1431
1649
|
const cut = hunks.lastIndexOf("\n", limit2);
|
|
1432
1650
|
return { patch: hunks.slice(0, cut === -1 ? limit2 : cut), truncated: true };
|
|
1433
1651
|
}
|
|
1434
|
-
var WorkspaceConversationSchema =
|
|
1435
|
-
|
|
1436
|
-
|
|
1652
|
+
var WorkspaceConversationSchema = z8.discriminatedUnion("kind", [
|
|
1653
|
+
z8.object({ kind: z8.literal("thread"), id: UlidSchema }),
|
|
1654
|
+
z8.object({ kind: z8.literal("channel"), scope: NameSchema, name: NameSchema })
|
|
1437
1655
|
]);
|
|
1438
1656
|
function workspaceConversationOf(turn) {
|
|
1439
1657
|
if (turn.thread !== void 0) {
|
|
@@ -1441,60 +1659,65 @@ function workspaceConversationOf(turn) {
|
|
|
1441
1659
|
}
|
|
1442
1660
|
return turn.channel === void 0 ? null : { kind: "channel", scope: turn.scope, name: turn.channel };
|
|
1443
1661
|
}
|
|
1444
|
-
var HeldWorkspaceSchema =
|
|
1662
|
+
var HeldWorkspaceSchema = z8.object({
|
|
1445
1663
|
agent: NameSchema,
|
|
1446
1664
|
scope: NameSchema,
|
|
1447
1665
|
conversation: WorkspaceConversationSchema
|
|
1448
1666
|
});
|
|
1449
|
-
var HeldWorkspacesSchema =
|
|
1450
|
-
var SweepResultSchema =
|
|
1451
|
-
var TurnSteerSchema =
|
|
1452
|
-
var RunnerMessageSchema =
|
|
1453
|
-
|
|
1454
|
-
|
|
1667
|
+
var HeldWorkspacesSchema = z8.object({ workspaces: z8.array(HeldWorkspaceSchema) });
|
|
1668
|
+
var SweepResultSchema = z8.object({ kept: z8.array(HeldWorkspaceSchema) });
|
|
1669
|
+
var TurnSteerSchema = z8.object({ id: z8.uuid(), text: z8.string().min(1) });
|
|
1670
|
+
var RunnerMessageSchema = z8.discriminatedUnion("type", [
|
|
1671
|
+
z8.object({ type: z8.literal("turn"), job: TurnJobSchema }),
|
|
1672
|
+
z8.object({ type: z8.literal("land"), request: z8.string().min(1), land: LandRequestSchema }),
|
|
1455
1673
|
/** Answered with a `MergeOutcome`. */
|
|
1456
|
-
|
|
1457
|
-
|
|
1674
|
+
z8.object({ type: z8.literal("close"), request: z8.string().min(1), close: CloseRequestSchema }),
|
|
1675
|
+
z8.object({ type: z8.literal("models"), request: z8.string().min(1), cli: CliKindSchema }),
|
|
1676
|
+
/**
|
|
1677
|
+
* Answered with a `CliQuota` read from the CLI without a turn, or null when its adapter cannot
|
|
1678
|
+
* read one. Sent only to runners whose hello says `quotaReads`.
|
|
1679
|
+
*/
|
|
1680
|
+
z8.object({ type: z8.literal("quota"), request: z8.string().min(1), cli: CliKindSchema }),
|
|
1458
1681
|
/** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
|
|
1459
|
-
|
|
1682
|
+
z8.object({ type: z8.literal("file"), request: z8.string().min(1), read: BranchReadSchema }),
|
|
1460
1683
|
/** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
|
|
1461
|
-
|
|
1462
|
-
type:
|
|
1463
|
-
request:
|
|
1684
|
+
z8.object({
|
|
1685
|
+
type: z8.literal("changes"),
|
|
1686
|
+
request: z8.string().min(1),
|
|
1464
1687
|
read: BranchChangesReadSchema
|
|
1465
1688
|
}),
|
|
1466
1689
|
/** Answered with a `BranchFileDiff`, or null when the branch does not exist or left the path alone. */
|
|
1467
|
-
|
|
1690
|
+
z8.object({ type: z8.literal("diff"), request: z8.string().min(1), read: BranchDiffReadSchema }),
|
|
1468
1691
|
/** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
|
|
1469
|
-
|
|
1470
|
-
type:
|
|
1471
|
-
request:
|
|
1692
|
+
z8.object({
|
|
1693
|
+
type: z8.literal("steer"),
|
|
1694
|
+
request: z8.string().min(1),
|
|
1472
1695
|
turnId: UlidSchema,
|
|
1473
1696
|
steer: TurnSteerSchema
|
|
1474
1697
|
}),
|
|
1475
1698
|
/** Answered with whether the turn was there to stop; it ends `stopped`. */
|
|
1476
|
-
|
|
1699
|
+
z8.object({ type: z8.literal("stop"), request: z8.string().min(1), turnId: UlidSchema }),
|
|
1477
1700
|
/**
|
|
1478
1701
|
* A conversation ended: the runner removes every citizen's workspace for it but those a turn
|
|
1479
1702
|
* still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
|
|
1480
1703
|
*/
|
|
1481
|
-
|
|
1482
|
-
type:
|
|
1483
|
-
request:
|
|
1704
|
+
z8.object({
|
|
1705
|
+
type: z8.literal("sweep"),
|
|
1706
|
+
request: z8.string().min(1),
|
|
1484
1707
|
conversation: WorkspaceConversationSchema
|
|
1485
1708
|
})
|
|
1486
1709
|
]);
|
|
1487
|
-
var RunnerAnswerSchema =
|
|
1488
|
-
|
|
1489
|
-
|
|
1710
|
+
var RunnerAnswerSchema = z8.discriminatedUnion("ok", [
|
|
1711
|
+
z8.object({ ok: z8.literal(true), value: z8.unknown() }),
|
|
1712
|
+
z8.object({ ok: z8.literal(false), error: z8.string() })
|
|
1490
1713
|
]);
|
|
1491
|
-
var WarmSessionsSchema =
|
|
1492
|
-
var FileManifestSchema =
|
|
1714
|
+
var WarmSessionsSchema = z8.object({ warm: z8.array(z8.string()) });
|
|
1715
|
+
var FileManifestSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
|
|
1493
1716
|
function fileVersion(sha, executable) {
|
|
1494
1717
|
return executable ? `${sha}+x` : sha;
|
|
1495
1718
|
}
|
|
1496
|
-
var FileReadSchema =
|
|
1497
|
-
var FileContentsSchema =
|
|
1719
|
+
var FileReadSchema = z8.object({ paths: z8.array(z8.string()) });
|
|
1720
|
+
var FileContentsSchema = z8.object({ files: z8.record(z8.string(), z8.string()) });
|
|
1498
1721
|
function isSafeRelativePath(file) {
|
|
1499
1722
|
if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
|
|
1500
1723
|
return false;
|
|
@@ -1503,7 +1726,7 @@ function isSafeRelativePath(file) {
|
|
|
1503
1726
|
}
|
|
1504
1727
|
|
|
1505
1728
|
// packages/shared/dist/turns.js
|
|
1506
|
-
var TriggerKindSchema =
|
|
1729
|
+
var TriggerKindSchema = z9.enum([
|
|
1507
1730
|
"mention",
|
|
1508
1731
|
"stage",
|
|
1509
1732
|
"task_done",
|
|
@@ -1517,11 +1740,11 @@ var TriggerKindSchema = z8.enum([
|
|
|
1517
1740
|
"closing",
|
|
1518
1741
|
"cron"
|
|
1519
1742
|
]);
|
|
1520
|
-
var TriggerSchema =
|
|
1743
|
+
var TriggerSchema = z9.object({
|
|
1521
1744
|
kind: TriggerKindSchema,
|
|
1522
1745
|
from: NameSchema.optional(),
|
|
1523
|
-
fromUser:
|
|
1524
|
-
reason:
|
|
1746
|
+
fromUser: z9.boolean().default(false),
|
|
1747
|
+
reason: z9.string().default(""),
|
|
1525
1748
|
taskId: UlidSchema.optional(),
|
|
1526
1749
|
messageId: UlidSchema.optional(),
|
|
1527
1750
|
/** The channel the waking message was posted in. */
|
|
@@ -1529,17 +1752,17 @@ var TriggerSchema = z8.object({
|
|
|
1529
1752
|
/** The cron that came due, for a `cron` wake. */
|
|
1530
1753
|
cronId: UlidSchema.optional()
|
|
1531
1754
|
});
|
|
1532
|
-
var TurnThreadSchema =
|
|
1533
|
-
var TurnDispatchSchema =
|
|
1755
|
+
var TurnThreadSchema = z9.object({ id: UlidSchema, task: z9.boolean() });
|
|
1756
|
+
var TurnDispatchSchema = z9.object({
|
|
1534
1757
|
agent: NameSchema,
|
|
1535
1758
|
project: NameSchema,
|
|
1536
1759
|
thread: TurnThreadSchema.optional(),
|
|
1537
1760
|
channel: NameSchema.optional(),
|
|
1538
1761
|
trigger: TriggerSchema,
|
|
1539
|
-
priority:
|
|
1540
|
-
onboarding:
|
|
1762
|
+
priority: z9.number().int().min(0).max(2),
|
|
1763
|
+
onboarding: z9.boolean().default(false),
|
|
1541
1764
|
/** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
|
|
1542
|
-
crons:
|
|
1765
|
+
crons: z9.array(UlidSchema).optional()
|
|
1543
1766
|
});
|
|
1544
1767
|
function conversationPart(conversation) {
|
|
1545
1768
|
return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
|
|
@@ -1563,7 +1786,7 @@ function channelConversation(scope, channel) {
|
|
|
1563
1786
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1564
1787
|
return place === scope && name !== "general" ? name : void 0;
|
|
1565
1788
|
}
|
|
1566
|
-
var TurnRecordSchema =
|
|
1789
|
+
var TurnRecordSchema = z9.object({
|
|
1567
1790
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1568
1791
|
id: UlidSchema.optional(),
|
|
1569
1792
|
agent: NameSchema,
|
|
@@ -1574,31 +1797,31 @@ var TurnRecordSchema = z8.object({
|
|
|
1574
1797
|
channel: NameSchema.optional(),
|
|
1575
1798
|
runner: NameSchema,
|
|
1576
1799
|
cli: CliKindSchema.nullable(),
|
|
1577
|
-
session:
|
|
1800
|
+
session: z9.string().nullable(),
|
|
1578
1801
|
trigger: TriggerSchema,
|
|
1579
1802
|
startedAt: IsoDateTimeSchema,
|
|
1580
1803
|
endedAt: IsoDateTimeSchema.nullable(),
|
|
1581
1804
|
exitReason: TurnExitReasonSchema.nullable(),
|
|
1582
1805
|
status: TurnStatusSchema.nullable(),
|
|
1583
|
-
error:
|
|
1806
|
+
error: z9.string().nullable(),
|
|
1584
1807
|
usage: UsageSchema.nullable(),
|
|
1585
|
-
costUsd:
|
|
1808
|
+
costUsd: z9.number().nonnegative().default(0),
|
|
1586
1809
|
/**
|
|
1587
1810
|
* The running total the CLI reported for the session after this turn, when it reports one. A
|
|
1588
1811
|
* resumed session starts from it, so the next turn's own cost is measured from here.
|
|
1589
1812
|
*/
|
|
1590
|
-
sessionCostUsd:
|
|
1591
|
-
toolCalls:
|
|
1813
|
+
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1814
|
+
toolCalls: z9.number().int().nonnegative().default(0),
|
|
1592
1815
|
/** The model the CLI reported for this turn, else the model configured for the agent, else null. */
|
|
1593
|
-
model:
|
|
1816
|
+
model: z9.string().nullable().default(null),
|
|
1594
1817
|
/** The task branch the turn left work on, with its commit, as the runner reported it. */
|
|
1595
1818
|
work: TurnWorkSchema.optional(),
|
|
1596
1819
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1597
1820
|
stoppedBy: NameSchema.optional(),
|
|
1598
1821
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1599
|
-
crons:
|
|
1822
|
+
crons: z9.array(UlidSchema).optional()
|
|
1600
1823
|
});
|
|
1601
|
-
var RunningTurnSchema =
|
|
1824
|
+
var RunningTurnSchema = z9.object({
|
|
1602
1825
|
turnId: UlidSchema,
|
|
1603
1826
|
agent: NameSchema,
|
|
1604
1827
|
scope: NameSchema,
|
|
@@ -1606,49 +1829,49 @@ var RunningTurnSchema = z8.object({
|
|
|
1606
1829
|
channel: NameSchema.optional(),
|
|
1607
1830
|
cli: CliKindSchema,
|
|
1608
1831
|
askedIn: ChannelRefSchema.optional(),
|
|
1609
|
-
steerable:
|
|
1610
|
-
stoppable:
|
|
1832
|
+
steerable: z9.boolean(),
|
|
1833
|
+
stoppable: z9.boolean()
|
|
1611
1834
|
});
|
|
1612
|
-
var SessionsFileSchema =
|
|
1613
|
-
claude:
|
|
1614
|
-
codex:
|
|
1835
|
+
var SessionsFileSchema = z9.object({
|
|
1836
|
+
claude: z9.string().optional(),
|
|
1837
|
+
codex: z9.string().optional(),
|
|
1615
1838
|
runner: NameSchema.optional()
|
|
1616
1839
|
});
|
|
1617
|
-
var WakeRequestSchema =
|
|
1840
|
+
var WakeRequestSchema = z9.object({
|
|
1618
1841
|
agent: NameSchema,
|
|
1619
1842
|
project: NameSchema,
|
|
1620
|
-
reason:
|
|
1843
|
+
reason: z9.string().default("manual wake"),
|
|
1621
1844
|
/** A manual wake, or a reflection turn requested ahead of the cadence. */
|
|
1622
|
-
kind:
|
|
1845
|
+
kind: z9.enum(["manual", "reflection"]).default("manual")
|
|
1623
1846
|
});
|
|
1624
|
-
var TurnHistoryEntrySchema =
|
|
1847
|
+
var TurnHistoryEntrySchema = z9.object({
|
|
1625
1848
|
/** The id of the event that ended the turn. */
|
|
1626
1849
|
id: UlidSchema,
|
|
1627
1850
|
/** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
|
|
1628
1851
|
turnId: UlidSchema.optional(),
|
|
1629
1852
|
ts: IsoDateTimeSchema,
|
|
1630
|
-
outcome:
|
|
1853
|
+
outcome: z9.enum(["completed", "failed"]),
|
|
1631
1854
|
/** A project slug, or the society scope. */
|
|
1632
1855
|
project: NameSchema,
|
|
1633
1856
|
/** The thread whose conversation the turn was in; absent for the home conversation. */
|
|
1634
1857
|
thread: UlidSchema.optional(),
|
|
1635
1858
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1636
1859
|
channel: NameSchema.optional(),
|
|
1637
|
-
trigger:
|
|
1638
|
-
exitReason:
|
|
1639
|
-
costUsd:
|
|
1860
|
+
trigger: z9.string(),
|
|
1861
|
+
exitReason: z9.string().nullable(),
|
|
1862
|
+
costUsd: z9.number(),
|
|
1640
1863
|
/** The tokens the turn used; absent for turns logged before turns recorded them. */
|
|
1641
1864
|
usage: UsageSchema.optional(),
|
|
1642
|
-
model:
|
|
1643
|
-
summary:
|
|
1644
|
-
error:
|
|
1865
|
+
model: z9.string().nullable(),
|
|
1866
|
+
summary: z9.string().nullable(),
|
|
1867
|
+
error: z9.string().nullable(),
|
|
1645
1868
|
/** When the log recorded the turn's start; absent when the start is missing from the log. */
|
|
1646
1869
|
startedAt: IsoDateTimeSchema.optional(),
|
|
1647
|
-
toolCalls:
|
|
1870
|
+
toolCalls: z9.number().int().nonnegative().optional()
|
|
1648
1871
|
});
|
|
1649
1872
|
|
|
1650
1873
|
// packages/shared/dist/ops.js
|
|
1651
|
-
import { z as
|
|
1874
|
+
import { z as z10 } from "zod";
|
|
1652
1875
|
var OPS_SIGNAL_KINDS = [
|
|
1653
1876
|
"waiting_stage",
|
|
1654
1877
|
"backlog",
|
|
@@ -1664,7 +1887,7 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1664
1887
|
"stuck_workspace",
|
|
1665
1888
|
"cron_failing"
|
|
1666
1889
|
];
|
|
1667
|
-
var OpsSignalKindSchema =
|
|
1890
|
+
var OpsSignalKindSchema = z10.enum(OPS_SIGNAL_KINDS);
|
|
1668
1891
|
var WAKING_SIGNAL_KINDS = [
|
|
1669
1892
|
"backlog",
|
|
1670
1893
|
"role_gap",
|
|
@@ -1674,15 +1897,15 @@ var WAKING_SIGNAL_KINDS = [
|
|
|
1674
1897
|
"blocked_capability",
|
|
1675
1898
|
"scaled"
|
|
1676
1899
|
];
|
|
1677
|
-
var OpsSignalSchema =
|
|
1900
|
+
var OpsSignalSchema = z10.object({
|
|
1678
1901
|
kind: OpsSignalKindSchema,
|
|
1679
1902
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
1680
|
-
key:
|
|
1903
|
+
key: z10.string().min(1),
|
|
1681
1904
|
/** One line a reader can act on. */
|
|
1682
|
-
summary:
|
|
1905
|
+
summary: z10.string().min(1),
|
|
1683
1906
|
/** The counter or timer value behind the signal. */
|
|
1684
|
-
value:
|
|
1685
|
-
threshold:
|
|
1907
|
+
value: z10.number(),
|
|
1908
|
+
threshold: z10.number().optional(),
|
|
1686
1909
|
project: NameSchema.optional(),
|
|
1687
1910
|
agent: NameSchema.optional(),
|
|
1688
1911
|
role: NameSchema.optional(),
|
|
@@ -1690,8 +1913,8 @@ var OpsSignalSchema = z9.object({
|
|
|
1690
1913
|
});
|
|
1691
1914
|
|
|
1692
1915
|
// packages/shared/dist/metrics.js
|
|
1693
|
-
import { z as
|
|
1694
|
-
var MetricsWindowSchema =
|
|
1916
|
+
import { z as z11 } from "zod";
|
|
1917
|
+
var MetricsWindowSchema = z11.enum(["24h", "7d", "all"]);
|
|
1695
1918
|
var METRICS_WINDOW_MS = {
|
|
1696
1919
|
"24h": 24 * 36e5,
|
|
1697
1920
|
"7d": 7 * 24 * 36e5,
|
|
@@ -1702,9 +1925,9 @@ function isBoardAction(tool) {
|
|
|
1702
1925
|
const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
|
|
1703
1926
|
return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
|
|
1704
1927
|
}
|
|
1705
|
-
var CountSchema =
|
|
1706
|
-
var MsSchema =
|
|
1707
|
-
var MetricsSchema =
|
|
1928
|
+
var CountSchema = z11.number().int().nonnegative();
|
|
1929
|
+
var MsSchema = z11.number().nonnegative();
|
|
1930
|
+
var MetricsSchema = z11.object({
|
|
1708
1931
|
window: MetricsWindowSchema,
|
|
1709
1932
|
since: IsoDateTimeSchema.nullable(),
|
|
1710
1933
|
until: IsoDateTimeSchema,
|
|
@@ -1712,12 +1935,12 @@ var MetricsSchema = z10.object({
|
|
|
1712
1935
|
* Finished turns that changed nothing on the board. Reflections are left out, since they write
|
|
1713
1936
|
* memory, not the board; turns without a transcript are counted apart, their actions unknown.
|
|
1714
1937
|
*/
|
|
1715
|
-
idle:
|
|
1938
|
+
idle: z11.object({
|
|
1716
1939
|
turns: CountSchema,
|
|
1717
1940
|
idle: CountSchema,
|
|
1718
1941
|
unknown: CountSchema,
|
|
1719
|
-
byTrigger:
|
|
1720
|
-
byAgent:
|
|
1942
|
+
byTrigger: z11.array(z11.object({ trigger: z11.string(), turns: CountSchema, idle: CountSchema })),
|
|
1943
|
+
byAgent: z11.array(z11.object({
|
|
1721
1944
|
agent: NameSchema,
|
|
1722
1945
|
role: NameSchema.nullable(),
|
|
1723
1946
|
turns: CountSchema,
|
|
@@ -1725,33 +1948,33 @@ var MetricsSchema = z10.object({
|
|
|
1725
1948
|
}))
|
|
1726
1949
|
}),
|
|
1727
1950
|
/** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
|
|
1728
|
-
sentBack:
|
|
1951
|
+
sentBack: z11.object({
|
|
1729
1952
|
finished: CountSchema,
|
|
1730
1953
|
sendBacks: CountSchema,
|
|
1731
1954
|
tasksSentBack: CountSchema,
|
|
1732
|
-
byStage:
|
|
1733
|
-
bySender:
|
|
1955
|
+
byStage: z11.array(z11.object({ project: NameSchema, stage: z11.string(), count: CountSchema })),
|
|
1956
|
+
bySender: z11.array(z11.object({ agent: NameSchema, count: CountSchema }))
|
|
1734
1957
|
}),
|
|
1735
1958
|
/** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
|
|
1736
|
-
messages:
|
|
1959
|
+
messages: z11.object({
|
|
1737
1960
|
finished: CountSchema,
|
|
1738
1961
|
messages: CountSchema,
|
|
1739
|
-
byProject:
|
|
1740
|
-
busiest:
|
|
1962
|
+
byProject: z11.array(z11.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
|
|
1963
|
+
busiest: z11.array(z11.object({
|
|
1741
1964
|
taskId: UlidSchema,
|
|
1742
|
-
title:
|
|
1965
|
+
title: z11.string(),
|
|
1743
1966
|
project: NameSchema,
|
|
1744
1967
|
messages: CountSchema
|
|
1745
1968
|
}))
|
|
1746
1969
|
}),
|
|
1747
1970
|
/** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
|
|
1748
|
-
latency:
|
|
1971
|
+
latency: z11.object({
|
|
1749
1972
|
mentions: CountSchema,
|
|
1750
1973
|
/** Mentions no turn has followed yet. */
|
|
1751
1974
|
unanswered: CountSchema,
|
|
1752
1975
|
medianMs: MsSchema.nullable(),
|
|
1753
1976
|
slowestMs: MsSchema.nullable(),
|
|
1754
|
-
byAgent:
|
|
1977
|
+
byAgent: z11.array(z11.object({
|
|
1755
1978
|
agent: NameSchema,
|
|
1756
1979
|
mentions: CountSchema,
|
|
1757
1980
|
medianMs: MsSchema,
|
|
@@ -1759,61 +1982,73 @@ var MetricsSchema = z10.object({
|
|
|
1759
1982
|
}))
|
|
1760
1983
|
}),
|
|
1761
1984
|
/** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
|
|
1762
|
-
decisions:
|
|
1985
|
+
decisions: z11.object({
|
|
1763
1986
|
total: CountSchema,
|
|
1764
|
-
byDay:
|
|
1765
|
-
day:
|
|
1987
|
+
byDay: z11.array(z11.object({
|
|
1988
|
+
day: z11.string(),
|
|
1766
1989
|
proposals: CountSchema,
|
|
1767
1990
|
answers: CountSchema,
|
|
1768
1991
|
stages: CountSchema
|
|
1769
1992
|
}))
|
|
1770
1993
|
}),
|
|
1771
1994
|
/** Tasks that needed a capability no connected runner offered, and whether they still do. */
|
|
1772
|
-
blocked:
|
|
1995
|
+
blocked: z11.array(z11.object({
|
|
1773
1996
|
taskId: UlidSchema,
|
|
1774
|
-
title:
|
|
1997
|
+
title: z11.string().nullable(),
|
|
1775
1998
|
project: NameSchema.nullable(),
|
|
1776
|
-
summary:
|
|
1999
|
+
summary: z11.string(),
|
|
1777
2000
|
firstAt: IsoDateTimeSchema,
|
|
1778
2001
|
lastAt: IsoDateTimeSchema,
|
|
1779
|
-
holds:
|
|
2002
|
+
holds: z11.boolean()
|
|
1780
2003
|
}))
|
|
1781
2004
|
});
|
|
2005
|
+
var CitizenTokensSchema = z11.object({
|
|
2006
|
+
agent: NameSchema,
|
|
2007
|
+
turns: CountSchema,
|
|
2008
|
+
withUsage: CountSchema,
|
|
2009
|
+
usage: UsageSchema.nullable()
|
|
2010
|
+
});
|
|
2011
|
+
var TokenUseSchema = z11.object({
|
|
2012
|
+
window: MetricsWindowSchema,
|
|
2013
|
+
since: IsoDateTimeSchema.nullable(),
|
|
2014
|
+
until: IsoDateTimeSchema,
|
|
2015
|
+
citizens: z11.array(CitizenTokensSchema)
|
|
2016
|
+
});
|
|
1782
2017
|
|
|
1783
2018
|
// packages/shared/dist/config.js
|
|
1784
|
-
import { z as
|
|
1785
|
-
var LogLevelSchema =
|
|
1786
|
-
var GithubSignInSchema =
|
|
1787
|
-
clientId:
|
|
1788
|
-
clientSecret:
|
|
2019
|
+
import { z as z12 } from "zod";
|
|
2020
|
+
var LogLevelSchema = z12.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
|
2021
|
+
var GithubSignInSchema = z12.object({
|
|
2022
|
+
clientId: z12.string().min(1),
|
|
2023
|
+
clientSecret: z12.string().min(1),
|
|
1789
2024
|
/** Logins, lowercased; GitHub logins are case-insensitive. */
|
|
1790
|
-
users:
|
|
2025
|
+
users: z12.array(z12.string().min(1)).min(1)
|
|
1791
2026
|
});
|
|
1792
|
-
var ServerConfigSchema =
|
|
1793
|
-
dataDir:
|
|
1794
|
-
host:
|
|
1795
|
-
port:
|
|
2027
|
+
var ServerConfigSchema = z12.object({
|
|
2028
|
+
dataDir: z12.string().min(1),
|
|
2029
|
+
host: z12.string().min(1),
|
|
2030
|
+
port: z12.number().int().positive().max(65535),
|
|
1796
2031
|
/**
|
|
1797
2032
|
* An address every runner's agents reach this server at, for example https://board.example, or
|
|
1798
2033
|
* null for each runner to use the address it reaches the server at itself.
|
|
1799
2034
|
*/
|
|
1800
|
-
publicUrl:
|
|
2035
|
+
publicUrl: z12.string().min(1).nullable(),
|
|
1801
2036
|
logLevel: LogLevelSchema,
|
|
1802
2037
|
/** Turns running at once across every runner, or null for no limit beyond each runner's own. */
|
|
1803
|
-
concurrency:
|
|
2038
|
+
concurrency: z12.number().int().positive().nullable(),
|
|
1804
2039
|
/** How long one turn may run before it is stopped, or null for no limit. */
|
|
1805
|
-
turnTimeoutMs:
|
|
2040
|
+
turnTimeoutMs: z12.number().int().positive().nullable(),
|
|
1806
2041
|
/** Rounds of tool calls one Claude turn may take, or null for no limit. */
|
|
1807
|
-
toolRounds:
|
|
2042
|
+
toolRounds: z12.number().int().positive().nullable(),
|
|
1808
2043
|
/** How long a resident session stays warm after its last turn. */
|
|
1809
|
-
residentIdleMs:
|
|
2044
|
+
residentIdleMs: z12.number().int().positive(),
|
|
1810
2045
|
/** Signing in to the board with GitHub, or null when only a token lets anyone in. */
|
|
1811
2046
|
github: GithubSignInSchema.nullable(),
|
|
1812
2047
|
/**
|
|
1813
2048
|
* Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
|
|
1814
2049
|
* rate limits on what anyone may ask without a token: 0 when clients connect directly.
|
|
1815
2050
|
*/
|
|
1816
|
-
trustedProxies:
|
|
2051
|
+
trustedProxies: z12.number().int().min(0).max(16)
|
|
1817
2052
|
});
|
|
1818
2053
|
function limit(value, fallback) {
|
|
1819
2054
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -1851,25 +2086,73 @@ function loadServerConfig(env) {
|
|
|
1851
2086
|
trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
|
|
1852
2087
|
});
|
|
1853
2088
|
}
|
|
1854
|
-
var RunnerConfigSchema =
|
|
2089
|
+
var RunnerConfigSchema = z12.object({
|
|
1855
2090
|
/** The board server, for example http://127.0.0.1:4700. */
|
|
1856
|
-
serverUrl:
|
|
2091
|
+
serverUrl: z12.string().url(),
|
|
1857
2092
|
/**
|
|
1858
2093
|
* The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
|
|
1859
2094
|
* to enroll it.
|
|
1860
2095
|
*/
|
|
1861
|
-
token:
|
|
2096
|
+
token: z12.string().min(1).nullable(),
|
|
1862
2097
|
/** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
|
|
1863
|
-
dataDir:
|
|
2098
|
+
dataDir: z12.string().min(1),
|
|
1864
2099
|
logLevel: LogLevelSchema,
|
|
1865
2100
|
/** Turns this machine runs at once, or null for no limit. */
|
|
1866
|
-
slots:
|
|
1867
|
-
clis:
|
|
1868
|
-
capabilities:
|
|
2101
|
+
slots: z12.number().int().positive().nullable(),
|
|
2102
|
+
clis: z12.array(CliKindSchema).min(1),
|
|
2103
|
+
capabilities: z12.array(z12.string())
|
|
1869
2104
|
});
|
|
1870
2105
|
|
|
1871
2106
|
// packages/shared/dist/version.js
|
|
1872
|
-
var STELLARIS_VERSION = "0.3.
|
|
2107
|
+
var STELLARIS_VERSION = "0.3.22";
|
|
2108
|
+
|
|
2109
|
+
// packages/shared/dist/mentions.js
|
|
2110
|
+
import { parse, postprocess, preprocess } from "micromark";
|
|
2111
|
+
import { gfm } from "micromark-extension-gfm";
|
|
2112
|
+
var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
|
|
2113
|
+
var CODE = /* @__PURE__ */ new Set(["codeText", "codeFenced", "codeIndented"]);
|
|
2114
|
+
var SLOW = {
|
|
2115
|
+
disable: {
|
|
2116
|
+
null: [
|
|
2117
|
+
"wwwAutolink",
|
|
2118
|
+
"protocolAutolink",
|
|
2119
|
+
"emailAutolink",
|
|
2120
|
+
"gfmFootnoteCall",
|
|
2121
|
+
"gfmPotentialFootnoteCall"
|
|
2122
|
+
]
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
function codeRanges(markdown) {
|
|
2126
|
+
const shift = markdown.startsWith("\uFEFF") ? 1 : 0;
|
|
2127
|
+
const events = postprocess(parse({ extensions: [gfm(), SLOW] }).document().write(preprocess()(markdown, void 0, true)));
|
|
2128
|
+
return events.flatMap(([kind, token]) => kind === "enter" && CODE.has(token.type) ? [[token.start.offset + shift, token.end.offset + shift]] : []);
|
|
2129
|
+
}
|
|
2130
|
+
function addMentions(found, text) {
|
|
2131
|
+
for (const match of text.matchAll(MENTION_PATTERN)) {
|
|
2132
|
+
const name = match[2];
|
|
2133
|
+
if (name !== void 0) {
|
|
2134
|
+
found.add(name);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
function mentionsIn(markdown) {
|
|
2139
|
+
if (markdown.search(MENTION_PATTERN) === -1) {
|
|
2140
|
+
return [];
|
|
2141
|
+
}
|
|
2142
|
+
const found = /* @__PURE__ */ new Set();
|
|
2143
|
+
let from = 0;
|
|
2144
|
+
for (const [start, end] of codeRanges(markdown)) {
|
|
2145
|
+
addMentions(found, markdown.slice(from, start));
|
|
2146
|
+
from = end;
|
|
2147
|
+
}
|
|
2148
|
+
addMentions(found, markdown.slice(from));
|
|
2149
|
+
return [...found].toSorted();
|
|
2150
|
+
}
|
|
2151
|
+
function mentionsAnywhere(text) {
|
|
2152
|
+
const found = /* @__PURE__ */ new Set();
|
|
2153
|
+
addMentions(found, text);
|
|
2154
|
+
return [...found].toSorted();
|
|
2155
|
+
}
|
|
1873
2156
|
|
|
1874
2157
|
// packages/board-core/dist/errors.js
|
|
1875
2158
|
var BoardError = class extends Error {
|
|
@@ -2515,33 +2798,59 @@ function filesOf(lines, status) {
|
|
|
2515
2798
|
return [...files.values()];
|
|
2516
2799
|
}
|
|
2517
2800
|
|
|
2801
|
+
// packages/board-core/dist/mentions.js
|
|
2802
|
+
import vm from "node:vm";
|
|
2803
|
+
var MENTION_PARSE_MS = 500;
|
|
2804
|
+
var context = vm.createContext({});
|
|
2805
|
+
var call = new vm.Script("work()");
|
|
2806
|
+
function within(work, ms) {
|
|
2807
|
+
let result;
|
|
2808
|
+
context["work"] = () => {
|
|
2809
|
+
result = work();
|
|
2810
|
+
};
|
|
2811
|
+
try {
|
|
2812
|
+
call.runInContext(context, { timeout: ms });
|
|
2813
|
+
} catch (error) {
|
|
2814
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
|
|
2815
|
+
return void 0;
|
|
2816
|
+
}
|
|
2817
|
+
throw error;
|
|
2818
|
+
} finally {
|
|
2819
|
+
context["work"] = void 0;
|
|
2820
|
+
}
|
|
2821
|
+
return result;
|
|
2822
|
+
}
|
|
2823
|
+
function postMentions(body) {
|
|
2824
|
+
return within(() => mentionsIn(body), MENTION_PARSE_MS) ?? mentionsAnywhere(body);
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2518
2827
|
// packages/board-core/dist/metrics.js
|
|
2519
|
-
import { z as
|
|
2520
|
-
var TurnPayload =
|
|
2521
|
-
turnId:
|
|
2522
|
-
project:
|
|
2523
|
-
thread:
|
|
2524
|
-
channel:
|
|
2525
|
-
trigger:
|
|
2828
|
+
import { z as z13 } from "zod";
|
|
2829
|
+
var TurnPayload = z13.object({
|
|
2830
|
+
turnId: z13.string().optional(),
|
|
2831
|
+
project: z13.string(),
|
|
2832
|
+
thread: z13.string().optional(),
|
|
2833
|
+
channel: z13.string().optional(),
|
|
2834
|
+
trigger: z13.string()
|
|
2526
2835
|
});
|
|
2527
|
-
var TaskPayload =
|
|
2528
|
-
taskId:
|
|
2529
|
-
project:
|
|
2530
|
-
from:
|
|
2836
|
+
var TaskPayload = z13.object({
|
|
2837
|
+
taskId: z13.string(),
|
|
2838
|
+
project: z13.string(),
|
|
2839
|
+
from: z13.string().optional()
|
|
2531
2840
|
});
|
|
2532
|
-
var MessagePayload =
|
|
2533
|
-
id:
|
|
2534
|
-
channel:
|
|
2535
|
-
thread:
|
|
2536
|
-
mentions:
|
|
2841
|
+
var MessagePayload = z13.object({
|
|
2842
|
+
id: z13.string().optional(),
|
|
2843
|
+
channel: z13.string(),
|
|
2844
|
+
thread: z13.string().nullable().optional(),
|
|
2845
|
+
mentions: z13.array(z13.string()).default([])
|
|
2537
2846
|
});
|
|
2538
|
-
var SteeredPayload =
|
|
2539
|
-
var SignalPayload =
|
|
2540
|
-
kind:
|
|
2541
|
-
key:
|
|
2542
|
-
summary:
|
|
2543
|
-
taskId:
|
|
2544
|
-
project:
|
|
2847
|
+
var SteeredPayload = z13.object({ messages: z13.array(z13.string()) });
|
|
2848
|
+
var SignalPayload = z13.object({
|
|
2849
|
+
kind: z13.string(),
|
|
2850
|
+
key: z13.string(),
|
|
2851
|
+
summary: z13.string(),
|
|
2852
|
+
taskId: z13.string().optional(),
|
|
2853
|
+
project: z13.string().optional()
|
|
2545
2854
|
});
|
|
2546
2855
|
function median(values) {
|
|
2547
2856
|
const sorted = values.toSorted((a, b) => a - b);
|
|
@@ -2830,6 +3139,59 @@ function computeMetrics(input) {
|
|
|
2830
3139
|
};
|
|
2831
3140
|
}
|
|
2832
3141
|
|
|
3142
|
+
// packages/board-core/dist/turn-history.js
|
|
3143
|
+
import { z as z14 } from "zod";
|
|
3144
|
+
var TurnHistoryPayloadSchema = z14.object({
|
|
3145
|
+
project: NameSchema,
|
|
3146
|
+
thread: UlidSchema.optional(),
|
|
3147
|
+
channel: NameSchema.optional(),
|
|
3148
|
+
trigger: z14.string().default("manual"),
|
|
3149
|
+
exitReason: z14.string().nullable().default(null),
|
|
3150
|
+
costUsd: z14.number().default(0),
|
|
3151
|
+
usage: UsageSchema.optional(),
|
|
3152
|
+
model: z14.string().nullable().default(null),
|
|
3153
|
+
summary: z14.string().nullable().default(null),
|
|
3154
|
+
error: z14.string().nullable().default(null),
|
|
3155
|
+
toolCalls: z14.number().int().nonnegative().optional(),
|
|
3156
|
+
turnId: UlidSchema.optional()
|
|
3157
|
+
});
|
|
3158
|
+
function tokenUse(events, window, now) {
|
|
3159
|
+
const span = METRICS_WINDOW_MS[window];
|
|
3160
|
+
const since = span === null ? null : now.getTime() - span;
|
|
3161
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3162
|
+
for (const event of events) {
|
|
3163
|
+
if (event.type !== "turn.completed" && event.type !== "turn.failed") {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
if (since !== null && Date.parse(event.ts) < since) {
|
|
3167
|
+
continue;
|
|
3168
|
+
}
|
|
3169
|
+
const turn = TurnHistoryPayloadSchema.safeParse(event.payload);
|
|
3170
|
+
if (!turn.success) {
|
|
3171
|
+
continue;
|
|
3172
|
+
}
|
|
3173
|
+
const row = rows.get(event.actor) ?? {
|
|
3174
|
+
agent: event.actor,
|
|
3175
|
+
turns: 0,
|
|
3176
|
+
withUsage: 0,
|
|
3177
|
+
usage: null
|
|
3178
|
+
};
|
|
3179
|
+
const usage = turn.data.usage;
|
|
3180
|
+
rows.set(event.actor, {
|
|
3181
|
+
agent: event.actor,
|
|
3182
|
+
turns: row.turns + 1,
|
|
3183
|
+
withUsage: row.withUsage + (usage === void 0 ? 0 : 1),
|
|
3184
|
+
usage: usage === void 0 ? row.usage : row.usage === null ? usage : addUsage(row.usage, usage)
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
window,
|
|
3189
|
+
since: since === null ? null : new Date(since).toISOString(),
|
|
3190
|
+
until: now.toISOString(),
|
|
3191
|
+
citizens: [...rows.values()].toSorted((a, b) => a.agent.localeCompare(b.agent))
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
|
|
2833
3195
|
// packages/board-core/dist/mutex.js
|
|
2834
3196
|
var Mutex = class {
|
|
2835
3197
|
tail = Promise.resolve();
|
|
@@ -3071,41 +3433,27 @@ function namesList(names) {
|
|
|
3071
3433
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
3072
3434
|
}
|
|
3073
3435
|
var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
|
|
3074
|
-
var RunnerTokensSchema =
|
|
3075
|
-
runners:
|
|
3436
|
+
var RunnerTokensSchema = z15.object({
|
|
3437
|
+
runners: z15.record(z15.string(), z15.object({ tokenHash: z15.string().min(1) }))
|
|
3076
3438
|
});
|
|
3077
|
-
var SignInsSchema =
|
|
3078
|
-
signIns:
|
|
3079
|
-
tokenHash:
|
|
3080
|
-
login:
|
|
3439
|
+
var SignInsSchema = z15.object({
|
|
3440
|
+
signIns: z15.array(z15.object({
|
|
3441
|
+
tokenHash: z15.string().min(1),
|
|
3442
|
+
login: z15.string().min(1),
|
|
3081
3443
|
/** The account's picture at the identity provider; sign-ins from before it have none. */
|
|
3082
|
-
avatarUrl:
|
|
3444
|
+
avatarUrl: z15.url().optional(),
|
|
3083
3445
|
createdAt: IsoDateTimeSchema,
|
|
3084
3446
|
expiresAt: IsoDateTimeSchema
|
|
3085
3447
|
})).default([])
|
|
3086
3448
|
});
|
|
3087
|
-
var
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
costUsd: z13.number().default(0),
|
|
3094
|
-
usage: UsageSchema.optional(),
|
|
3095
|
-
model: z13.string().nullable().default(null),
|
|
3096
|
-
summary: z13.string().nullable().default(null),
|
|
3097
|
-
error: z13.string().nullable().default(null),
|
|
3098
|
-
toolCalls: z13.number().int().nonnegative().optional(),
|
|
3099
|
-
turnId: UlidSchema.optional()
|
|
3100
|
-
});
|
|
3101
|
-
var TurnStartPayloadSchema = z13.object({ project: NameSchema, turnId: UlidSchema.optional() });
|
|
3102
|
-
var CursorsSchema = z13.object({
|
|
3103
|
-
digest: z13.string().nullable(),
|
|
3104
|
-
scopes: z13.record(z13.string(), z13.string()).default({}),
|
|
3105
|
-
threadsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
|
|
3106
|
-
channelsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
|
|
3449
|
+
var TurnStartPayloadSchema = z15.object({ project: NameSchema, turnId: UlidSchema.optional() });
|
|
3450
|
+
var CursorsSchema = z15.object({
|
|
3451
|
+
digest: z15.string().nullable(),
|
|
3452
|
+
scopes: z15.record(z15.string(), z15.string()).default({}),
|
|
3453
|
+
threadsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
|
|
3454
|
+
channelsFrom: z15.record(z15.string(), z15.string().nullable()).default({}),
|
|
3107
3455
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3108
|
-
news:
|
|
3456
|
+
news: z15.string().optional()
|
|
3109
3457
|
});
|
|
3110
3458
|
var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
|
|
3111
3459
|
function conversationKey(scope, part) {
|
|
@@ -3144,9 +3492,8 @@ function byId(a, b) {
|
|
|
3144
3492
|
function turnTime(record) {
|
|
3145
3493
|
return record.endedAt ?? record.startedAt;
|
|
3146
3494
|
}
|
|
3147
|
-
var PausedSchema =
|
|
3495
|
+
var PausedSchema = z15.object({ paused: z15.boolean() });
|
|
3148
3496
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3149
|
-
var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
|
|
3150
3497
|
function withoutMentions(text) {
|
|
3151
3498
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3152
3499
|
}
|
|
@@ -3170,7 +3517,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
|
|
|
3170
3517
|
var FRONT_DESK_TRIGGER = "user_post";
|
|
3171
3518
|
var OPS_CHANNELS = ["governance"];
|
|
3172
3519
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
3173
|
-
var AlignmentsSchema =
|
|
3520
|
+
var AlignmentsSchema = z15.object({ applied: z15.array(z15.string()).default([]) });
|
|
3174
3521
|
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
3175
3522
|
var THREAD_RECORDS = "threads-as-records";
|
|
3176
3523
|
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
@@ -3184,10 +3531,10 @@ var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
|
3184
3531
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3185
3532
|
var CRON_VERBS = "cron-verbs-granted";
|
|
3186
3533
|
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
3187
|
-
var KnowledgeEventSchema =
|
|
3534
|
+
var KnowledgeEventSchema = z15.object({
|
|
3188
3535
|
topic: NameSchema,
|
|
3189
3536
|
project: NameSchema.nullable(),
|
|
3190
|
-
replaced:
|
|
3537
|
+
replaced: z15.boolean().optional()
|
|
3191
3538
|
});
|
|
3192
3539
|
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
3193
3540
|
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
@@ -3305,16 +3652,6 @@ function unanswered(messages) {
|
|
|
3305
3652
|
}
|
|
3306
3653
|
return waiting;
|
|
3307
3654
|
}
|
|
3308
|
-
function extractMentions(body) {
|
|
3309
|
-
const found = /* @__PURE__ */ new Set();
|
|
3310
|
-
for (const match of body.matchAll(MENTION_PATTERN)) {
|
|
3311
|
-
const name = match[2];
|
|
3312
|
-
if (name !== void 0) {
|
|
3313
|
-
found.add(name);
|
|
3314
|
-
}
|
|
3315
|
-
}
|
|
3316
|
-
return [...found].toSorted();
|
|
3317
|
-
}
|
|
3318
3655
|
function snippetAround(text, query, radius = 80) {
|
|
3319
3656
|
const index = text.toLowerCase().indexOf(query.toLowerCase());
|
|
3320
3657
|
if (index === -1) {
|
|
@@ -3642,7 +3979,7 @@ var Board = class _Board {
|
|
|
3642
3979
|
}
|
|
3643
3980
|
}
|
|
3644
3981
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3645
|
-
const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file),
|
|
3982
|
+
const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z15.record(z15.string(), z15.unknown()));
|
|
3646
3983
|
const legacy = raw.data["thread"];
|
|
3647
3984
|
if (legacy === void 0) {
|
|
3648
3985
|
continue;
|
|
@@ -3720,7 +4057,7 @@ var Board = class _Board {
|
|
|
3720
4057
|
* turns, so it follows nothing now.
|
|
3721
4058
|
*/
|
|
3722
4059
|
async renameInboxCursors() {
|
|
3723
|
-
const Legacy =
|
|
4060
|
+
const Legacy = z15.object({ inbox: z15.string().nullable() });
|
|
3724
4061
|
for (const agent of await this.listAgents()) {
|
|
3725
4062
|
const file = this.paths.agentCursors(agent.name);
|
|
3726
4063
|
if (await exists(file)) {
|
|
@@ -3766,7 +4103,7 @@ ${charter.purpose}
|
|
|
3766
4103
|
*/
|
|
3767
4104
|
async alignSeedRole(seed) {
|
|
3768
4105
|
const file = this.paths.role(seed.name);
|
|
3769
|
-
const raw = await readMarkdown(file,
|
|
4106
|
+
const raw = await readMarkdown(file, z15.record(z15.string(), z15.unknown()));
|
|
3770
4107
|
const current = RoleCharterSchema.parse(raw.data);
|
|
3771
4108
|
const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
|
|
3772
4109
|
const fieldsAligned = RoleCharterSchema.keyof().options.filter((key) => key === "verbs" || seed[key] === void 0 ? false : seed.name === USER_ROLE ? JSON.stringify(seed[key]) !== JSON.stringify(current[key]) : !(key in raw.data));
|
|
@@ -4083,7 +4420,7 @@ ${charter.purpose}
|
|
|
4083
4420
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
4084
4421
|
async readSocietyNorms() {
|
|
4085
4422
|
const file = this.paths.societyKnowledgeFile("norms");
|
|
4086
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
4423
|
+
return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
|
|
4087
4424
|
}
|
|
4088
4425
|
/** Knowledge topics of a project, or of the society when the project is null. */
|
|
4089
4426
|
async listKnowledge(project) {
|
|
@@ -4103,7 +4440,7 @@ ${charter.purpose}
|
|
|
4103
4440
|
*/
|
|
4104
4441
|
async knowledgeAt(file, project, topic) {
|
|
4105
4442
|
const { data, body } = await readLooseMarkdown(file);
|
|
4106
|
-
const revision =
|
|
4443
|
+
const revision = z15.number().int().min(0).safeParse(data["revision"]);
|
|
4107
4444
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
4108
4445
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
4109
4446
|
return {
|
|
@@ -4436,8 +4773,11 @@ ${DASHBOARD_LINE}
|
|
|
4436
4773
|
throw new BoardError("NOT_FOUND", `runner ${name} not found`);
|
|
4437
4774
|
}
|
|
4438
4775
|
const doc = await readMarkdown(file, RunnerSchema);
|
|
4776
|
+
const { gitIdentityMissing: reported, ...rest } = doc.data;
|
|
4777
|
+
const gitIdentityMissing = patch.gitIdentityMissing === void 0 ? reported : patch.gitIdentityMissing ?? void 0;
|
|
4439
4778
|
const next = RunnerSchema.parse({
|
|
4440
|
-
...
|
|
4779
|
+
...rest,
|
|
4780
|
+
...gitIdentityMissing === void 0 ? {} : { gitIdentityMissing: [...gitIdentityMissing] },
|
|
4441
4781
|
...patch.status === void 0 ? {} : { status: patch.status },
|
|
4442
4782
|
...patch.os === void 0 ? {} : { os: patch.os },
|
|
4443
4783
|
...patch.clis === void 0 ? {} : { clis: [...patch.clis] },
|
|
@@ -4465,6 +4805,36 @@ ${DASHBOARD_LINE}
|
|
|
4465
4805
|
return next;
|
|
4466
4806
|
});
|
|
4467
4807
|
}
|
|
4808
|
+
/**
|
|
4809
|
+
* Records a CLI's plan quota as a runner reported it, from a turn's stream or a read, merged over
|
|
4810
|
+
* what the record held. The record is written on every report, so it says when the numbers were
|
|
4811
|
+
* last seen; `runner.quota` is appended only when what a reader would see changes, since every
|
|
4812
|
+
* turn reports.
|
|
4813
|
+
*/
|
|
4814
|
+
async recordQuota(name, cli, quota) {
|
|
4815
|
+
return this.mutex.run(async () => {
|
|
4816
|
+
const file = this.paths.runner(name);
|
|
4817
|
+
if (!await exists(file)) {
|
|
4818
|
+
throw new BoardError("NOT_FOUND", `runner ${name} not found`);
|
|
4819
|
+
}
|
|
4820
|
+
const doc = await readMarkdown(file, RunnerSchema);
|
|
4821
|
+
const previous = doc.data.quotas?.[cli];
|
|
4822
|
+
const merged = mergeQuota(previous, quota);
|
|
4823
|
+
const next = RunnerSchema.parse({
|
|
4824
|
+
...doc.data,
|
|
4825
|
+
quotas: { ...doc.data.quotas, [cli]: merged }
|
|
4826
|
+
});
|
|
4827
|
+
await writeMarkdown(file, next, doc.body);
|
|
4828
|
+
if (quotaChanged(previous, merged)) {
|
|
4829
|
+
await this.events.append("runner.quota", SYSTEM_ACTOR.name, {
|
|
4830
|
+
name,
|
|
4831
|
+
cli,
|
|
4832
|
+
status: merged.status
|
|
4833
|
+
});
|
|
4834
|
+
}
|
|
4835
|
+
return next;
|
|
4836
|
+
});
|
|
4837
|
+
}
|
|
4468
4838
|
/**
|
|
4469
4839
|
* Logs an operations signal as an `ops.signal` event. No channel carries it: the scheduler wakes
|
|
4470
4840
|
* its readers from the event, the runner lists it in their prompts, and the interface's log shows
|
|
@@ -4606,18 +4976,16 @@ ${DASHBOARD_LINE}
|
|
|
4606
4976
|
async readDigest(actor, input = {}) {
|
|
4607
4977
|
const args = VerbInputs.read_inbox.parse(input);
|
|
4608
4978
|
await this.authorize(actor, "read_inbox");
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4620
|
-
});
|
|
4979
|
+
const { cursors: stored, upto } = await this.digestSnapshot(actor.name);
|
|
4980
|
+
const from = args.since_cursor === void 0 ? stored : { ...NO_CURSORS, digest: args.since_cursor };
|
|
4981
|
+
const conversation = actor.scope === void 0 ? void 0 : { scope: actor.scope, part: conversationPart(actor) ?? null };
|
|
4982
|
+
const filed = (await this.digestSince(actor, from, upto, conversation)).slice(0, args.limit);
|
|
4983
|
+
if (args.advance) {
|
|
4984
|
+
await this.mutex.run(() => this.advanceCursors(actor.name, filed));
|
|
4985
|
+
}
|
|
4986
|
+
const last = filed.at(-1)?.message.id;
|
|
4987
|
+
const cursor = last ?? (conversation === void 0 ? from.digest : cursorOf(from, conversation.scope, conversation.part));
|
|
4988
|
+
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4621
4989
|
}
|
|
4622
4990
|
/**
|
|
4623
4991
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
@@ -4629,28 +4997,39 @@ ${DASHBOARD_LINE}
|
|
|
4629
4997
|
* whose signals that call for action wake their readers directly.
|
|
4630
4998
|
*/
|
|
4631
4999
|
async unreadByConversation(actor) {
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
}
|
|
4639
|
-
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
4640
|
-
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4641
|
-
continue;
|
|
4642
|
-
}
|
|
4643
|
-
const key = conversationKey(scope, part);
|
|
4644
|
-
const entry = counts.get(key) ?? {
|
|
4645
|
-
scope,
|
|
4646
|
-
thread: threadOfPart(part) ?? null,
|
|
4647
|
-
channel: channelOfPart(part) ?? null,
|
|
4648
|
-
count: 0
|
|
4649
|
-
};
|
|
4650
|
-
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
5000
|
+
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
5001
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5002
|
+
const unscoped = { name: actor.name, role: actor.role };
|
|
5003
|
+
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
5004
|
+
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
5005
|
+
continue;
|
|
4651
5006
|
}
|
|
4652
|
-
|
|
4653
|
-
|
|
5007
|
+
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
5008
|
+
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
5009
|
+
continue;
|
|
5010
|
+
}
|
|
5011
|
+
const key = conversationKey(scope, part);
|
|
5012
|
+
const entry = counts.get(key) ?? {
|
|
5013
|
+
scope,
|
|
5014
|
+
thread: threadOfPart(part) ?? null,
|
|
5015
|
+
channel: channelOfPart(part) ?? null,
|
|
5016
|
+
count: 0
|
|
5017
|
+
};
|
|
5018
|
+
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
5019
|
+
}
|
|
5020
|
+
return [...counts.values()];
|
|
5021
|
+
}
|
|
5022
|
+
/**
|
|
5023
|
+
* A reader's cursors and the id below which every message is already on disk, taken under the
|
|
5024
|
+
* mutex, which a digest scan then leaves: messages carry ids from the same monotonic source and are
|
|
5025
|
+
* written under the mutex, so the scan, which counts nothing newer than the mark, cannot pass over
|
|
5026
|
+
* one written while it reads, and a post never waits for a scan.
|
|
5027
|
+
*/
|
|
5028
|
+
digestSnapshot(agent) {
|
|
5029
|
+
return this.mutex.run(async () => ({
|
|
5030
|
+
cursors: await this.readCursors(agent),
|
|
5031
|
+
upto: this.newId()
|
|
5032
|
+
}));
|
|
4654
5033
|
}
|
|
4655
5034
|
async readCursors(agent) {
|
|
4656
5035
|
const file = this.paths.agentCursors(agent);
|
|
@@ -4692,9 +5071,10 @@ ${DASHBOARD_LINE}
|
|
|
4692
5071
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4693
5072
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4694
5073
|
* top-level messages; for a scope's home, general's and those of a project the member is not in,
|
|
4695
|
-
* since each thread and each other channel is a conversation of its own.
|
|
5074
|
+
* since each thread and each other channel is a conversation of its own. Nothing newer than
|
|
5075
|
+
* `upto` counts (see `digestSnapshot`).
|
|
4696
5076
|
*/
|
|
4697
|
-
async digestSince(actor, cursors, conversation) {
|
|
5077
|
+
async digestSince(actor, cursors, upto, conversation) {
|
|
4698
5078
|
const conversationThread = threadOfPart(conversation?.part);
|
|
4699
5079
|
if (conversation !== void 0 && conversationThread !== void 0) {
|
|
4700
5080
|
const { scope, part } = conversation;
|
|
@@ -4702,7 +5082,7 @@ ${DASHBOARD_LINE}
|
|
|
4702
5082
|
if (found === null) {
|
|
4703
5083
|
return [];
|
|
4704
5084
|
}
|
|
4705
|
-
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part));
|
|
5085
|
+
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part), upto);
|
|
4706
5086
|
return messages.filter((message) => message.author !== actor.name).map((message) => ({ scope, part, message })).toSorted(byId);
|
|
4707
5087
|
}
|
|
4708
5088
|
const agent = await this.readAgent(actor.name);
|
|
@@ -4720,7 +5100,7 @@ ${DASHBOARD_LINE}
|
|
|
4720
5100
|
];
|
|
4721
5101
|
const known = starts.filter((start) => start !== null);
|
|
4722
5102
|
const since = known.length < starts.length ? null : known.toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0)[0] ?? null;
|
|
4723
|
-
for await (const message of this.iterateMessages(since)) {
|
|
5103
|
+
for await (const message of this.iterateMessages(since, void 0, upto)) {
|
|
4724
5104
|
const filedIn = wakeScope(agent, parseChannelRef(message.channel).project);
|
|
4725
5105
|
const thread = message.thread ?? null;
|
|
4726
5106
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
@@ -5844,6 +6224,9 @@ ${args.summary}`);
|
|
|
5844
6224
|
}
|
|
5845
6225
|
if (args.blocked_by !== void 0) {
|
|
5846
6226
|
for (const id of args.blocked_by) {
|
|
6227
|
+
if (id === current.id) {
|
|
6228
|
+
throw new BoardError("VALIDATION", `task ${current.id} cannot be blocked by itself`);
|
|
6229
|
+
}
|
|
5847
6230
|
await this.findTask(id);
|
|
5848
6231
|
}
|
|
5849
6232
|
next = { ...next, blockedBy: [...args.blocked_by] };
|
|
@@ -5880,6 +6263,7 @@ ${args.summary}`);
|
|
|
5880
6263
|
from: current.status,
|
|
5881
6264
|
to: task.status,
|
|
5882
6265
|
note: args.note ?? null,
|
|
6266
|
+
...args.blocked_by === void 0 ? {} : { blockedBy: task.blockedBy, previousBlockedBy: current.blockedBy },
|
|
5883
6267
|
...args.pull_request === void 0 ? {} : { pullRequest: task.pullRequest?.url ?? null }
|
|
5884
6268
|
});
|
|
5885
6269
|
}
|
|
@@ -6285,7 +6669,7 @@ ${rationale}`}`);
|
|
|
6285
6669
|
}
|
|
6286
6670
|
async readAgentRoleBody(agent) {
|
|
6287
6671
|
const file = this.paths.agentRole(agent);
|
|
6288
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
6672
|
+
return await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
|
|
6289
6673
|
}
|
|
6290
6674
|
/** The steps of one finished turn, as the runner handed them over when it ended. */
|
|
6291
6675
|
/**
|
|
@@ -6316,6 +6700,14 @@ ${rationale}`}`);
|
|
|
6316
6700
|
}
|
|
6317
6701
|
return computeMetrics({ events, tasks, citizens, actions, activeSignals, window, now });
|
|
6318
6702
|
}
|
|
6703
|
+
/**
|
|
6704
|
+
* Each citizen's finished turns over a window and the tokens of those that counted them, from
|
|
6705
|
+
* the event log alone, which holds every turn's end whatever runner it ran on.
|
|
6706
|
+
*/
|
|
6707
|
+
async tokenUse(window) {
|
|
6708
|
+
const events = await this.events.readSince(null, Number.MAX_SAFE_INTEGER);
|
|
6709
|
+
return tokenUse(events, window, this.now());
|
|
6710
|
+
}
|
|
6319
6711
|
/** The board actions a finished turn took, from its transcript; null when it kept none. */
|
|
6320
6712
|
async turnActions(agent, turnId) {
|
|
6321
6713
|
const known = this.actionCounts.get(turnId);
|
|
@@ -6685,7 +7077,7 @@ ${rationale}`}`);
|
|
|
6685
7077
|
return { project: slug, revision: 0, body: "" };
|
|
6686
7078
|
}
|
|
6687
7079
|
const { data, body } = await readLooseMarkdown(file);
|
|
6688
|
-
const revision =
|
|
7080
|
+
const revision = z15.number().int().min(0).safeParse(data["revision"]);
|
|
6689
7081
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
6690
7082
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
6691
7083
|
return {
|
|
@@ -6976,7 +7368,7 @@ ${seed}
|
|
|
6976
7368
|
continue;
|
|
6977
7369
|
}
|
|
6978
7370
|
const file = this.paths.agentRole(agent.name);
|
|
6979
|
-
const body = await exists(file) ? (await readMarkdown(file,
|
|
7371
|
+
const body = await exists(file) ? (await readMarkdown(file, z15.record(z15.string(), z15.unknown()))).body : "";
|
|
6980
7372
|
const at = body.indexOf(`
|
|
6981
7373
|
${SEED_INSTRUCTIONS_HEADING}
|
|
6982
7374
|
`);
|
|
@@ -7178,12 +7570,37 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7178
7570
|
case "archive":
|
|
7179
7571
|
await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
|
|
7180
7572
|
return;
|
|
7181
|
-
case "reallocation":
|
|
7573
|
+
case "reallocation": {
|
|
7574
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
7575
|
+
if (parsed.success) {
|
|
7576
|
+
await this.reallocatedRole(parsed.data);
|
|
7577
|
+
} else {
|
|
7578
|
+
LegacyReallocationSchema.parse(charter);
|
|
7579
|
+
}
|
|
7182
7580
|
return;
|
|
7581
|
+
}
|
|
7183
7582
|
default:
|
|
7184
7583
|
return;
|
|
7185
7584
|
}
|
|
7186
7585
|
}
|
|
7586
|
+
/**
|
|
7587
|
+
* The role as a reallocation would leave it, with what changes: refused for the user's charter,
|
|
7588
|
+
* a role that does not exist, a charter the changes would make invalid, and changes the role
|
|
7589
|
+
* has already.
|
|
7590
|
+
*/
|
|
7591
|
+
async reallocatedRole(reallocation) {
|
|
7592
|
+
if (reallocation.role === USER_ROLE) {
|
|
7593
|
+
throw new BoardError("FORBIDDEN", "the user charter is not subject to proposals");
|
|
7594
|
+
}
|
|
7595
|
+
const current = await this.readRole(reallocation.role);
|
|
7596
|
+
const next = RoleCharterSchema.parse(applyRoleChanges(current, reallocation.changes));
|
|
7597
|
+
await this.validateRole(next);
|
|
7598
|
+
const changed = roleChangeLines(current, reallocation.changes);
|
|
7599
|
+
if (changed.length === 0) {
|
|
7600
|
+
throw new BoardError("VALIDATION", `role ${current.name} has these values already; the reallocation changes nothing`);
|
|
7601
|
+
}
|
|
7602
|
+
return { next, changed };
|
|
7603
|
+
}
|
|
7187
7604
|
async executeProvision(by, proposal) {
|
|
7188
7605
|
const meta = { proposalId: proposal.id };
|
|
7189
7606
|
switch (proposal.kind) {
|
|
@@ -7220,8 +7637,15 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7220
7637
|
const { members } = await this.archiveUnlocked(by, input.project, input.reason, meta);
|
|
7221
7638
|
return { project: input.project, members };
|
|
7222
7639
|
}
|
|
7223
|
-
case "reallocation":
|
|
7224
|
-
|
|
7640
|
+
case "reallocation": {
|
|
7641
|
+
const parsed = ReallocationProposalSchema.safeParse(proposal.charter);
|
|
7642
|
+
if (!parsed.success) {
|
|
7643
|
+
return void 0;
|
|
7644
|
+
}
|
|
7645
|
+
const { next, changed } = await this.reallocatedRole(parsed.data);
|
|
7646
|
+
await this.writeRoleUnlocked(by, next, meta);
|
|
7647
|
+
return { role: next.name, changed };
|
|
7648
|
+
}
|
|
7225
7649
|
default:
|
|
7226
7650
|
return void 0;
|
|
7227
7651
|
}
|
|
@@ -7782,7 +8206,7 @@ ${charter.purpose}
|
|
|
7782
8206
|
id: this.newId(),
|
|
7783
8207
|
...head,
|
|
7784
8208
|
ts: this.now().toISOString(),
|
|
7785
|
-
mentions:
|
|
8209
|
+
mentions: postMentions(body)
|
|
7786
8210
|
});
|
|
7787
8211
|
const { author } = head;
|
|
7788
8212
|
await writeMarkdown(this.paths.messageFile(dir, frontmatter.id, author), frontmatter, body);
|
|
@@ -7921,7 +8345,7 @@ ${charter.purpose}
|
|
|
7921
8345
|
if (filed.proposedBy === actor.name) {
|
|
7922
8346
|
throw new BoardError("FORBIDDEN", "a proposer never decides its own proposal");
|
|
7923
8347
|
}
|
|
7924
|
-
const approvers =
|
|
8348
|
+
const approvers = proposalApprovers(filed.kind, filed.charter);
|
|
7925
8349
|
if (!approvers.includes(actor.role)) {
|
|
7926
8350
|
throw new BoardError("FORBIDDEN", `a ${filed.kind} proposal requires one of: ${approvers.join(", ")}`);
|
|
7927
8351
|
}
|
|
@@ -8004,7 +8428,7 @@ ${charter.purpose}
|
|
|
8004
8428
|
}
|
|
8005
8429
|
if (subject?.kind === "proposal") {
|
|
8006
8430
|
const proposal = await this.readProposal(subject.id);
|
|
8007
|
-
if (proposal.proposedBy === actor.name || proposal.decidedBy === actor.name ||
|
|
8431
|
+
if (proposal.proposedBy === actor.name || proposal.decidedBy === actor.name || proposalApprovers(proposal.kind, proposal.charter).includes(actor.role)) {
|
|
8008
8432
|
return true;
|
|
8009
8433
|
}
|
|
8010
8434
|
return (await this.readRole(actor.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER);
|
|
@@ -8061,11 +8485,12 @@ ${charter.purpose}
|
|
|
8061
8485
|
});
|
|
8062
8486
|
await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron) => cron.thread === subjectId);
|
|
8063
8487
|
}
|
|
8064
|
-
|
|
8488
|
+
/** The messages in a folder after `since` and, when given, up to `upto`, read by name first. */
|
|
8489
|
+
async readMessagesIn(dir, since, upto) {
|
|
8065
8490
|
const messages = [];
|
|
8066
8491
|
for (const file of await listFiles(dir)) {
|
|
8067
8492
|
const id = file.slice(0, 26);
|
|
8068
|
-
if (since !== null && id <= since) {
|
|
8493
|
+
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8069
8494
|
continue;
|
|
8070
8495
|
}
|
|
8071
8496
|
const doc = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
|
|
@@ -8073,20 +8498,23 @@ ${charter.purpose}
|
|
|
8073
8498
|
}
|
|
8074
8499
|
return messages;
|
|
8075
8500
|
}
|
|
8076
|
-
/**
|
|
8077
|
-
|
|
8501
|
+
/**
|
|
8502
|
+
* Every message newer than `since`, and no newer than `upto` when given, across society
|
|
8503
|
+
* channels, project channels, and threads.
|
|
8504
|
+
*/
|
|
8505
|
+
async *iterateMessages(since, project, upto) {
|
|
8078
8506
|
if (project === void 0) {
|
|
8079
8507
|
for (const channel of await listDirs(this.paths.societyChannels())) {
|
|
8080
|
-
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since);
|
|
8508
|
+
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since, upto);
|
|
8081
8509
|
}
|
|
8082
|
-
yield* this.threadMessagesIn(this.paths.societyThreads(), since);
|
|
8510
|
+
yield* this.threadMessagesIn(this.paths.societyThreads(), since, upto);
|
|
8083
8511
|
}
|
|
8084
8512
|
const projects = project === void 0 ? await listDirs(this.paths.projects()) : [project];
|
|
8085
8513
|
for (const slug of projects) {
|
|
8086
8514
|
for (const channel of await listDirs(this.paths.projectChannels(slug))) {
|
|
8087
|
-
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since);
|
|
8515
|
+
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since, upto);
|
|
8088
8516
|
}
|
|
8089
|
-
yield* this.threadMessagesIn(this.paths.threads(slug), since);
|
|
8517
|
+
yield* this.threadMessagesIn(this.paths.threads(slug), since, upto);
|
|
8090
8518
|
}
|
|
8091
8519
|
}
|
|
8092
8520
|
/** Every folder that holds messages: each channel's and each thread's, in the society and every project. */
|
|
@@ -8106,9 +8534,9 @@ ${charter.purpose}
|
|
|
8106
8534
|
}
|
|
8107
8535
|
}
|
|
8108
8536
|
}
|
|
8109
|
-
async *threadMessagesIn(threads, since) {
|
|
8537
|
+
async *threadMessagesIn(threads, since, upto) {
|
|
8110
8538
|
for (const id of await listDirs(threads)) {
|
|
8111
|
-
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
|
|
8539
|
+
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since, upto);
|
|
8112
8540
|
}
|
|
8113
8541
|
}
|
|
8114
8542
|
};
|
|
@@ -8156,7 +8584,7 @@ function decideWake(input) {
|
|
|
8156
8584
|
}
|
|
8157
8585
|
|
|
8158
8586
|
// packages/scheduler/dist/scheduler.js
|
|
8159
|
-
import { z as
|
|
8587
|
+
import { z as z16 } from "zod";
|
|
8160
8588
|
function besideFields(beside) {
|
|
8161
8589
|
if (beside === void 0) {
|
|
8162
8590
|
return {};
|
|
@@ -8183,21 +8611,21 @@ var DEFAULT_TIMINGS = Object.freeze({
|
|
|
8183
8611
|
scaleCooldownMs: 36e5,
|
|
8184
8612
|
reflectionMs: 24 * 36e5
|
|
8185
8613
|
});
|
|
8186
|
-
var SchedulerTimingsSchema =
|
|
8187
|
-
pollMs:
|
|
8188
|
-
debounceMs:
|
|
8189
|
-
userDebounceMs:
|
|
8190
|
-
heartbeatMs:
|
|
8191
|
-
waitingStageMs:
|
|
8192
|
-
leaseSweepMs:
|
|
8193
|
-
opsIntervalMs:
|
|
8194
|
-
signalRepeatMs:
|
|
8195
|
-
costReportMs:
|
|
8196
|
-
staleThreadMs:
|
|
8197
|
-
idleMemberMs:
|
|
8198
|
-
homeConflictMs:
|
|
8199
|
-
scaleCooldownMs:
|
|
8200
|
-
reflectionMs:
|
|
8614
|
+
var SchedulerTimingsSchema = z16.object({
|
|
8615
|
+
pollMs: z16.number().positive(),
|
|
8616
|
+
debounceMs: z16.number().nonnegative(),
|
|
8617
|
+
userDebounceMs: z16.number().nonnegative(),
|
|
8618
|
+
heartbeatMs: z16.number().positive(),
|
|
8619
|
+
waitingStageMs: z16.number().positive(),
|
|
8620
|
+
leaseSweepMs: z16.number().positive(),
|
|
8621
|
+
opsIntervalMs: z16.number().positive(),
|
|
8622
|
+
signalRepeatMs: z16.number().positive(),
|
|
8623
|
+
costReportMs: z16.number().positive(),
|
|
8624
|
+
staleThreadMs: z16.number().positive(),
|
|
8625
|
+
idleMemberMs: z16.number().positive(),
|
|
8626
|
+
homeConflictMs: z16.number().positive(),
|
|
8627
|
+
scaleCooldownMs: z16.number().positive(),
|
|
8628
|
+
reflectionMs: z16.number().positive()
|
|
8201
8629
|
}).partial();
|
|
8202
8630
|
function parseTimings(input) {
|
|
8203
8631
|
const parsed = SchedulerTimingsSchema.parse(input);
|
|
@@ -8209,32 +8637,32 @@ function parseTimings(input) {
|
|
|
8209
8637
|
}
|
|
8210
8638
|
return overrides;
|
|
8211
8639
|
}
|
|
8212
|
-
var PendingSchema =
|
|
8640
|
+
var PendingSchema = z16.object({
|
|
8213
8641
|
dispatch: TurnDispatchSchema,
|
|
8214
|
-
readyAt:
|
|
8215
|
-
stageWakes:
|
|
8216
|
-
holder:
|
|
8642
|
+
readyAt: z16.number(),
|
|
8643
|
+
stageWakes: z16.array(TriggerSchema).optional(),
|
|
8644
|
+
holder: z16.boolean().optional(),
|
|
8217
8645
|
newestMessage: UlidSchema.optional(),
|
|
8218
|
-
onlyMessages:
|
|
8646
|
+
onlyMessages: z16.boolean().optional(),
|
|
8219
8647
|
steeredThrough: UlidSchema.optional(),
|
|
8220
|
-
onlyCrons:
|
|
8648
|
+
onlyCrons: z16.boolean().optional()
|
|
8221
8649
|
});
|
|
8222
|
-
var StateSchema =
|
|
8223
|
-
cursor:
|
|
8224
|
-
lastHeartbeat:
|
|
8650
|
+
var StateSchema = z16.object({
|
|
8651
|
+
cursor: z16.string().nullable(),
|
|
8652
|
+
lastHeartbeat: z16.record(z16.string(), z16.string()),
|
|
8225
8653
|
/** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
|
|
8226
|
-
waitingSeen:
|
|
8654
|
+
waitingSeen: z16.record(z16.string(), z16.string()).default({}),
|
|
8227
8655
|
/** Queued dispatches survive a restart; the cursor has already moved past the events that made them. */
|
|
8228
|
-
pending:
|
|
8229
|
-
opsReported:
|
|
8230
|
-
releaseCounts:
|
|
8231
|
-
lastOps:
|
|
8232
|
-
lastScaled:
|
|
8233
|
-
lastCostReport:
|
|
8234
|
-
costSinceReport:
|
|
8235
|
-
turnsSinceReport:
|
|
8656
|
+
pending: z16.record(z16.string(), PendingSchema).default({}),
|
|
8657
|
+
opsReported: z16.record(z16.string(), z16.string()).default({}),
|
|
8658
|
+
releaseCounts: z16.record(z16.string(), z16.number().int().nonnegative()).default({}),
|
|
8659
|
+
lastOps: z16.string().nullable().default(null),
|
|
8660
|
+
lastScaled: z16.record(z16.string(), z16.string()).default({}),
|
|
8661
|
+
lastCostReport: z16.string().nullable().default(null),
|
|
8662
|
+
costSinceReport: z16.number().nonnegative().default(0),
|
|
8663
|
+
turnsSinceReport: z16.number().int().nonnegative().default(0),
|
|
8236
8664
|
/** When each member last reflected, or was first seen; the cadence counts from here. */
|
|
8237
|
-
lastReflection:
|
|
8665
|
+
lastReflection: z16.record(z16.string(), z16.string()).default({})
|
|
8238
8666
|
});
|
|
8239
8667
|
function messageOf(trigger) {
|
|
8240
8668
|
return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
|
|
@@ -8492,7 +8920,7 @@ var Scheduler = class {
|
|
|
8492
8920
|
return;
|
|
8493
8921
|
}
|
|
8494
8922
|
if (stringOf(payload["thread"]) !== taskId) {
|
|
8495
|
-
await this.wakeHolder(taskId, event.actor, now);
|
|
8923
|
+
await this.wakeHolder(taskId, event.actor, event.actor, now);
|
|
8496
8924
|
}
|
|
8497
8925
|
await this.pruneStageWakes(taskId);
|
|
8498
8926
|
return;
|
|
@@ -8518,6 +8946,7 @@ var Scheduler = class {
|
|
|
8518
8946
|
if (creator !== null && creator !== event.actor) {
|
|
8519
8947
|
await this.wakeCreator(taskId, project, creator, event.actor, now);
|
|
8520
8948
|
}
|
|
8949
|
+
await this.wakeDependents(taskId, event.actor, now);
|
|
8521
8950
|
const threaded = await this.board.readThread(taskId).then(() => true, () => false);
|
|
8522
8951
|
if (!threaded) {
|
|
8523
8952
|
this.sweep({ kind: "thread", id: taskId });
|
|
@@ -8537,12 +8966,17 @@ var Scheduler = class {
|
|
|
8537
8966
|
}
|
|
8538
8967
|
case "task.updated": {
|
|
8539
8968
|
const taskId = stringOf(payload["taskId"]);
|
|
8540
|
-
if (taskId
|
|
8969
|
+
if (taskId === null) {
|
|
8970
|
+
return;
|
|
8971
|
+
}
|
|
8972
|
+
if (payload["to"] === "abandoned") {
|
|
8541
8973
|
delete this.state.releaseCounts[taskId];
|
|
8542
8974
|
await this.queueClosing(taskId);
|
|
8975
|
+
await this.wakeDependents(taskId, event.actor, now);
|
|
8543
8976
|
}
|
|
8544
|
-
|
|
8545
|
-
|
|
8977
|
+
await this.pruneStageWakes(taskId);
|
|
8978
|
+
if (Array.isArray(payload["blockedBy"]) && (await this.blockersInPlay(stringArray(payload["previousBlockedBy"]))).length > 0) {
|
|
8979
|
+
await this.wakeUnblocked(taskId, event.actor, now);
|
|
8546
8980
|
}
|
|
8547
8981
|
return;
|
|
8548
8982
|
}
|
|
@@ -8713,9 +9147,10 @@ var Scheduler = class {
|
|
|
8713
9147
|
}
|
|
8714
9148
|
/**
|
|
8715
9149
|
* Entering a stage wakes its named citizen, else its last holder when work returns to it, else
|
|
8716
|
-
* the project's members of its role, else every member of the project who may hold it.
|
|
9150
|
+
* the project's members of its role, else every member of the project who may hold it. A stage
|
|
9151
|
+
* of a blocked task wakes nobody until its last blocker ends (`wakeUnblocked`).
|
|
8717
9152
|
*/
|
|
8718
|
-
async wakeStage(taskId, from, now,
|
|
9153
|
+
async wakeStage(taskId, from, now, options = {}) {
|
|
8719
9154
|
let task;
|
|
8720
9155
|
try {
|
|
8721
9156
|
task = (await this.board.findTask(taskId)).task;
|
|
@@ -8726,6 +9161,10 @@ var Scheduler = class {
|
|
|
8726
9161
|
if (stage === void 0 || task.status !== "open" || task.completing) {
|
|
8727
9162
|
return;
|
|
8728
9163
|
}
|
|
9164
|
+
if ((await this.blockersInPlay(task.blockedBy)).length > 0) {
|
|
9165
|
+
return;
|
|
9166
|
+
}
|
|
9167
|
+
const { only, state = "is waiting for you" } = options;
|
|
8729
9168
|
const lastHolder = stage.holders.at(-1);
|
|
8730
9169
|
const targets = stage.agent !== void 0 ? [stage.agent] : lastHolder !== void 0 ? [lastHolder] : stage.role !== void 0 ? (await this.board.membersWithRole(task.project, stage.role)).map((a) => a.name) : (await this.board.projectMembers(task.project)).map((a) => a.name);
|
|
8731
9170
|
for (const name of targets) {
|
|
@@ -8736,7 +9175,7 @@ var Scheduler = class {
|
|
|
8736
9175
|
if (agent === null || !mayHoldStage(agent, task)) {
|
|
8737
9176
|
continue;
|
|
8738
9177
|
}
|
|
8739
|
-
await this.enqueueStage(agent, task, from, now,
|
|
9178
|
+
await this.enqueueStage(agent, task, from, now, state);
|
|
8740
9179
|
}
|
|
8741
9180
|
}
|
|
8742
9181
|
/**
|
|
@@ -8745,7 +9184,7 @@ var Scheduler = class {
|
|
|
8745
9184
|
*/
|
|
8746
9185
|
async wakeNewcomer(name, project, now) {
|
|
8747
9186
|
for (const task of await this.board.openTasks(project).catch(() => [])) {
|
|
8748
|
-
await this.wakeStage(task.id, name, now, name);
|
|
9187
|
+
await this.wakeStage(task.id, name, now, { only: name });
|
|
8749
9188
|
}
|
|
8750
9189
|
}
|
|
8751
9190
|
/**
|
|
@@ -8753,7 +9192,7 @@ var Scheduler = class {
|
|
|
8753
9192
|
* worked in that conversation, which nothing else would start before the next heartbeat. Turns
|
|
8754
9193
|
* of one conversation run one at a time, so the turn this queues is the one that sees the claim.
|
|
8755
9194
|
*/
|
|
8756
|
-
async wakeHolder(taskId, holder, now) {
|
|
9195
|
+
async wakeHolder(taskId, holder, from, now, state = "is yours; its work happens here") {
|
|
8757
9196
|
let task;
|
|
8758
9197
|
try {
|
|
8759
9198
|
task = (await this.board.findTask(taskId)).task;
|
|
@@ -8764,12 +9203,66 @@ var Scheduler = class {
|
|
|
8764
9203
|
if (agent === null || task.status !== "claimed" || task.claimedBy !== holder) {
|
|
8765
9204
|
return;
|
|
8766
9205
|
}
|
|
8767
|
-
const key = await this.enqueueStage(agent, task,
|
|
9206
|
+
const key = await this.enqueueStage(agent, task, from, now, state);
|
|
8768
9207
|
const item = key === null ? void 0 : this.pending.get(key);
|
|
8769
9208
|
if (item?.stageWakes !== void 0) {
|
|
8770
9209
|
item.holder = true;
|
|
8771
9210
|
}
|
|
8772
9211
|
}
|
|
9212
|
+
/**
|
|
9213
|
+
* The ids among these that name a task still open or claimed, a landing one included. A task
|
|
9214
|
+
* blocked by any of them waits for them rather than for a holder: its stage wakes nobody, by
|
|
9215
|
+
* coming up or on a heartbeat, until the last one ends.
|
|
9216
|
+
*/
|
|
9217
|
+
async blockersInPlay(ids) {
|
|
9218
|
+
const blocking = [];
|
|
9219
|
+
for (const id of ids) {
|
|
9220
|
+
try {
|
|
9221
|
+
const { task } = await this.board.findTask(id);
|
|
9222
|
+
if (task.status === "open" || task.status === "claimed") {
|
|
9223
|
+
blocking.push(id);
|
|
9224
|
+
}
|
|
9225
|
+
} catch {
|
|
9226
|
+
}
|
|
9227
|
+
}
|
|
9228
|
+
return blocking;
|
|
9229
|
+
}
|
|
9230
|
+
/**
|
|
9231
|
+
* A task's last blocker has ended, or its blockers were changed to none in play: its stage wakes
|
|
9232
|
+
* as on coming up, or its holder in the task's conversation, since nothing woke either for it
|
|
9233
|
+
* while it was blocked.
|
|
9234
|
+
*/
|
|
9235
|
+
async wakeUnblocked(taskId, from, now) {
|
|
9236
|
+
let task;
|
|
9237
|
+
try {
|
|
9238
|
+
task = (await this.board.findTask(taskId)).task;
|
|
9239
|
+
} catch {
|
|
9240
|
+
return;
|
|
9241
|
+
}
|
|
9242
|
+
if (task.completing || (await this.blockersInPlay(task.blockedBy)).length > 0) {
|
|
9243
|
+
return;
|
|
9244
|
+
}
|
|
9245
|
+
if (task.status === "open") {
|
|
9246
|
+
await this.wakeStage(task.id, from, now, {
|
|
9247
|
+
state: "is waiting for you, now that nothing blocks it"
|
|
9248
|
+
});
|
|
9249
|
+
} else if (task.status === "claimed" && task.claimedBy !== void 0) {
|
|
9250
|
+
await this.wakeHolder(task.id, task.claimedBy, from, now, "is yours, and nothing blocks it any more");
|
|
9251
|
+
}
|
|
9252
|
+
}
|
|
9253
|
+
/** A task that ended unblocks every task in play that names it, once nothing else blocks them. */
|
|
9254
|
+
async wakeDependents(taskId, from, now) {
|
|
9255
|
+
for (const project of await this.board.listProjects()) {
|
|
9256
|
+
if (project.archived !== void 0) {
|
|
9257
|
+
continue;
|
|
9258
|
+
}
|
|
9259
|
+
for (const task of await this.board.listTasks(project.slug)) {
|
|
9260
|
+
if (task.blockedBy.includes(taskId) && (task.status === "open" || task.status === "claimed")) {
|
|
9261
|
+
await this.wakeUnblocked(task.id, from, now);
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
}
|
|
9265
|
+
}
|
|
8773
9266
|
/** Queues a stage wake in the task's conversation and returns its key, or null for no wake. */
|
|
8774
9267
|
async enqueueStage(agent, task, from, now, state) {
|
|
8775
9268
|
if (agent.status !== "active" || agent.cli === null) {
|
|
@@ -8805,9 +9298,9 @@ var Scheduler = class {
|
|
|
8805
9298
|
}
|
|
8806
9299
|
}
|
|
8807
9300
|
/**
|
|
8808
|
-
* The first of these stage wakes still due: its task waits, unheld, on a stage the
|
|
8809
|
-
* hold, or, with `holder`, the agent holds it, having claimed it outside the task's
|
|
8810
|
-
* A wake for a stage the agent took in the turn the wake waited on is dropped.
|
|
9301
|
+
* The first of these stage wakes still due: its task waits, unheld and unblocked, on a stage the
|
|
9302
|
+
* agent may hold, or, with `holder`, the agent holds it, having claimed it outside the task's
|
|
9303
|
+
* conversation. A wake for a stage the agent took in the turn the wake waited on is dropped.
|
|
8811
9304
|
*/
|
|
8812
9305
|
async stillWaiting(name, wakes, holder) {
|
|
8813
9306
|
const agent = await this.tryReadAgent(name);
|
|
@@ -8823,7 +9316,7 @@ var Scheduler = class {
|
|
|
8823
9316
|
if (task.completing || currentStage(task) === void 0) {
|
|
8824
9317
|
continue;
|
|
8825
9318
|
}
|
|
8826
|
-
if (task.status === "open" && mayHoldStage(agent, task)) {
|
|
9319
|
+
if (task.status === "open" && mayHoldStage(agent, task) && (await this.blockersInPlay(task.blockedBy)).length === 0) {
|
|
8827
9320
|
return wake;
|
|
8828
9321
|
}
|
|
8829
9322
|
if (holder && task.status === "claimed" && task.claimedBy === agent.name) {
|
|
@@ -9046,9 +9539,9 @@ var Scheduler = class {
|
|
|
9046
9539
|
/**
|
|
9047
9540
|
* A heartbeat per member and scope on the cadence, which then looks at every conversation of the
|
|
9048
9541
|
* scope: the unread messages of its home, of each other channel, and of each thread, a stage the
|
|
9049
|
-
* member holds, and a stage
|
|
9050
|
-
*
|
|
9051
|
-
*
|
|
9542
|
+
* member holds, and a stage of an unblocked task waiting that it may take, each in its task's
|
|
9543
|
+
* thread. Each conversation with something wakes on its own, so a heartbeat never brings one
|
|
9544
|
+
* task's business into another's turn.
|
|
9052
9545
|
*/
|
|
9053
9546
|
async checkHeartbeats(now) {
|
|
9054
9547
|
for (const agent of await this.board.listAgents()) {
|
|
@@ -9100,7 +9593,7 @@ var Scheduler = class {
|
|
|
9100
9593
|
}
|
|
9101
9594
|
if (project !== SOCIETY_SCOPE) {
|
|
9102
9595
|
for (const task of await this.board.openTasks(project)) {
|
|
9103
|
-
if (mayHoldStage(agent, task)) {
|
|
9596
|
+
if (mayHoldStage(agent, task) && (await this.blockersInPlay(task.blockedBy)).length === 0) {
|
|
9104
9597
|
reasonsIn({ thread: { id: task.id, task: true } }).waiting += 1;
|
|
9105
9598
|
}
|
|
9106
9599
|
}
|
|
@@ -9252,9 +9745,10 @@ var Scheduler = class {
|
|
|
9252
9745
|
return latest;
|
|
9253
9746
|
}
|
|
9254
9747
|
/**
|
|
9255
|
-
* A current stage without a holder past the threshold is logged as a signal once while it waits
|
|
9256
|
-
* It wakes nobody by itself: its assignees see the stage on
|
|
9257
|
-
*
|
|
9748
|
+
* A current stage without a holder past the threshold is logged as a signal once while it waits,
|
|
9749
|
+
* naming the tasks that still block it. It wakes nobody by itself: its assignees see the stage on
|
|
9750
|
+
* their heartbeat, or a blocked one when its last blocker ends, and the steward reads the signal
|
|
9751
|
+
* at its next turn and decides whether to replan or to mention someone.
|
|
9258
9752
|
*/
|
|
9259
9753
|
async checkWaitingStages(now) {
|
|
9260
9754
|
const stillWaiting = /* @__PURE__ */ new Set();
|
|
@@ -9275,10 +9769,12 @@ var Scheduler = class {
|
|
|
9275
9769
|
}
|
|
9276
9770
|
this.state.waitingSeen[key] = iso(now);
|
|
9277
9771
|
const assignee2 = stage.agent ?? stage.role ?? "anyone in the project";
|
|
9772
|
+
const blockers = await this.blockersInPlay(task.blockedBy);
|
|
9773
|
+
const waited = blockers.length === 0 ? "for a holder" : `blocked by ${blockers.map((id) => `task ${id}`).join(", ")}`;
|
|
9278
9774
|
await this.board.publishSignal({
|
|
9279
9775
|
kind: "waiting_stage",
|
|
9280
9776
|
key: `waiting_stage:${key}`,
|
|
9281
|
-
summary: `stage "${stage.name}" of task ${task.id} "${task.title}" in ${project.slug}, for ${assignee2}, has waited ${describeDuration(age)}
|
|
9777
|
+
summary: `stage "${stage.name}" of task ${task.id} "${task.title}" in ${project.slug}, for ${assignee2}, has waited ${describeDuration(age)} ${waited}`,
|
|
9282
9778
|
value: age,
|
|
9283
9779
|
threshold: this.timings.waitingStageMs,
|
|
9284
9780
|
project: project.slug,
|
|
@@ -9739,8 +10235,9 @@ var TURN_CONTRACT = [
|
|
|
9739
10235
|
"- Posts that would wake this conversation may arrive while you work, delivered into the turn under a heading that says so and formatted like the digest. They are part of this turn: act on them now, after the step in hand, or not at all, as with the digest. The user may also stop a turn; the next turn is told.",
|
|
9740
10236
|
"- Silence is allowed. If the digest needs no reply, post nothing.",
|
|
9741
10237
|
"- To come back to something later, set a cron with create_cron: a note on what to do then, and a five-field cron expression or one time with at. It wakes you in this conversation unless you name another, at most every fifteen minutes, and every fire is a paid turn, so end a cron with remove_cron once it has done its job. Never wait by holding a stage, sleeping, or leaving a process running: a turn's board token ends with the turn, so nothing it leaves behind can act on the board. The society's crons are under society/crons/ in the board projection.",
|
|
9742
|
-
"- An @mention wakes the citizen named, and every wake costs a turn. Address someone with @ only when you need them to act; when you merely refer to citizens, write their names plainly. @user wakes nobody, since the user takes no turns; it is what puts a question before the user, so use it only when you need the user to answer or decide.",
|
|
10238
|
+
"- 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.",
|
|
9743
10239
|
"- A thread is a conversation off a channel that reaches only its participants and whoever is mentioned in it. Every task has one, opened with it under the task's id on the channel of its project it was filed in, general unless it was filed in another: talk about a task there, with post_message and its thread_id, and the notes you give advance_task and update_task are posted there as you. Its participants are the task's creator, its stage holders and assignees, whoever may take the stage that waits, and anyone who posted; it closes when the task ends. Every proposal has one too, in governance, opened with its pitch and closed by its decision. Open other threads with open_thread on a channel with a title, for any other topic; close_thread posts your summary to its channel.",
|
|
10240
|
+
"- To show an image in a post, such as a screenshot or a chart, commit the file to a task's branch and link it by its path in the task's worktree, as ``: the board shows a file on a task's branch in place once it is committed, at the latest when your turn ends. An image at an https address shows too. Never put an image or any other file into a message as base64 or a data: URL: the board does not show it, and every reader of the conversation gets it in full in their prompt.",
|
|
9744
10241
|
"- Route every lesson: about you, your craft, or the user, write it to memory/core.md in your home directory, and keep that file short by moving detail to memory/<topic>.md, your archive; a durable fact about a project's codebase or process goes through `write_knowledge` on that project; something the whole project should know now, post it to the project channel, and anything about one task, in the task's thread.",
|
|
9745
10242
|
'- A procedure you have followed twice is a skill: write it to skills/<name>/SKILL.md in your home, frontmatter with `name` and a one-line `description` and then the steps, and your skills index lists it from the next turn. Files the steps use, such as scripts/ and references/, go in the same folder, named in SKILL.md by paths relative to it. Propose it with kind "skill" when the whole society would use it.',
|
|
9746
10243
|
"- To take a published skill for yourself, install it in a temporary folder and copy it into your home: in a folder from `mktemp -d`, `npx -y skills add <source> --skill <name> -a universal --copy -y` writes `.agents/skills/<name>/` and a lock file there, and you copy that skill folder to skills/<name>/ in your home, named in lowercase letters, digits, and hyphens. Never install one inside a worktree, where it would be committed into the project, or with -g, which writes to the runner's own home, shared by every citizen on it and listed in no skills index.",
|
|
@@ -9754,32 +10251,33 @@ var PLANNING = [
|
|
|
9754
10251
|
"- Name stages by what gets done. Plan the next few steps rather than everything, and reshape with plan_task as the work teaches you.",
|
|
9755
10252
|
"- Assign a stage to a role when anyone in it could do it, to a citizen only when it must be them, and to nobody when anyone in the project could. A citizen named on a stage must be a member of the task's project; add them first. A stage wakes everyone it is open to when it comes up, so the narrower the assignee, the fewer turns it costs.",
|
|
9756
10253
|
"- Reshape rather than force: another round is an inserted stage, and a wait on something outside the society is a stage named for what it waits on. When a check finds work unfinished, send the task back to an earlier stage with update_task.",
|
|
10254
|
+
"- A task that cannot start until other tasks finish names them with update_task blocked_by. While any of them is in play, its stage wakes nobody, neither when it comes up nor on a heartbeat; once the last has ended, done or abandoned, the stage wakes as if it had just come up, or wakes its holder. Nobody needs to clear the block or mention anyone when they end; change blocked_by only when the plan changes.",
|
|
9757
10255
|
"- A gated stage is an independent check: nobody who held an earlier stage of the task may hold it, and only the user, the steward, and the concierge may add, remove, move, reassign, or ungate it. Ask them in the task's thread when a gate should change.",
|
|
9758
10256
|
"- Commit your work on the task's branch before you advance, and say what you did in the advance note; it is the handover the next holder reads in the task's thread."
|
|
9759
10257
|
].join("\n");
|
|
9760
10258
|
var GOVERNANCE = [
|
|
9761
|
-
"- Anything the society lacks is a proposal: call `propose` with a kind and a charter. It opens the proposal's thread in governance with your rationale, where it is discussed and where the decision is posted. Approval provisions it on the spot, and either decision wakes the proposer. The user decides members, roles, retirements, and
|
|
10259
|
+
"- Anything the society lacks is a proposal: call `propose` with a kind and a charter. It opens the proposal's thread in governance with your rationale, where it is discussed and where the decision is posted. Approval provisions it on the spot, and either decision wakes the proposer. The user decides members, roles, retirements, archives, and reallocations that change a role's verbs or replicas; the steward may also decide channels, skills, and other reallocations. Nobody decides their own proposal.",
|
|
9762
10260
|
"- Charter shapes, as JSON objects:",
|
|
9763
10261
|
' - member: {"name", "role", "cli": "claude" or "codex", "memberships": [project slugs], "model"?, "homeRunner"?, "subscriptions"?, "seedInstructions"?}',
|
|
9764
10262
|
' - role: {"name", "purpose", "verbs": [board verbs], "wakeTriggers"?: any of "user_post", "ops_event", "heartbeat" (heartbeat when omitted), "maxReplicas"?, "backlogThreshold"?, "societyScope"?: true for a society role, whose members are never in a project and keep watch over the whole society}. Mentions, stages that become yours, and finished tasks you created wake every role; a heartbeat wakes you when something is unread, held, or waiting for you.',
|
|
9765
10263
|
' - channel: {"project": slug or null, "name", "purpose"}',
|
|
9766
10264
|
' - retirement: {"agent", "reason"}',
|
|
9767
10265
|
' - archive: {"project", "reason"}: approval archives the project: its members leave, its open threads close, and nothing more is posted, filed, or joined there, while its files and history stay; refused while a task there is in play',
|
|
9768
|
-
|
|
9769
|
-
' - skill: {"name", "summary"}: the folder skills/<name>/ in your home, SKILL.md and every file beside it, which the board takes as it stands when this turn ends into society/proposals/<id>/, where whoever decides reads it; a proposal whose folder has no SKILL.md then is rejected by the board. Approval publishes it under society/skills/<name>/, where every citizen\'s skills index lists it until the steward or the user removes it with `remove_skill`; ask the steward to remove one that is wrong or superseded. A proposal from before folders carries "body", its SKILL.md text alone',
|
|
10266
|
+
` - reallocation: {"role", "changes": {"maxReplicas"?, "backlogThreshold"?, "wakeTriggers"?, "verbs"?}, "description"}: approval applies the changes to the existing role, each the field's new value and verbs and wakeTriggers whole lists, for its members from their next turn; refused for a role that does not exist or that has those values already. Only the user decides one that changes verbs or maxReplicas`,
|
|
10267
|
+
' - skill: {"name", "summary"}: the folder skills/<name>/ in your home, SKILL.md and every file beside it, which the board takes as it stands when this turn ends into society/proposals/<id>/, where whoever decides reads it; a proposal whose folder has no SKILL.md then is rejected by the board. Approval publishes it under society/skills/<name>/, where every citizen\'s skills index lists it until the steward or the user removes it with `remove_skill`; ask the steward to remove one that is wrong or superseded. Once yours is approved, delete skills/<name>/ from your home and report memoryUpdated: the board leaves your copy there, and your index would list it beside the published one and follow it as it drifts from what was reviewed. To revise a published skill, copy its folder home, edit it, and propose it again. A proposal from before folders carries "body", its SKILL.md text alone',
|
|
9770
10268
|
"- Projects and membership: a project is a lasting area of work, not one request; `create_project` opens one with its general channel (front desk and user), and `configure_project` sets its completion effect, its display name, or its default branch, never its slug (user, steward, concierge). A workstream within a project or the society, such as a release, gets a channel of its own: `create_channel` opens it, which its opener follows, and announces it in the place's general, where members subscribe when it concerns them, `create_task` with `channel` files the workstream's tasks there, and `archive_channel` ends it once none of its tasks is in play, leaving its history readable (user, steward, concierge; others propose a channel). A project whose work is finished or has moved to another project is archived by proposal, once no task there is in play. `join_project` and `leave_project` move yourself, or another citizen when you are the concierge, the steward, or the user; joining gives the pair a worktree and an onboarding turn. A society role, one that keeps watch over the whole society as the concierge and the steward do, is never in a project: it moves others, never itself.",
|
|
9771
|
-
"- Prefer scaling an existing role over inventing one; a new role is justified by work a project needs that no existing role covers. A role that needs a tool the board lacks is an engineering task, not a hiring request.",
|
|
10269
|
+
"- Prefer scaling an existing role over inventing one, with a reallocation of its replicas, backlog threshold, wakes, or verbs; a new role is justified by work a project needs that no existing role covers. A role that needs a tool the board lacks is an engineering task, not a hiring request.",
|
|
9772
10270
|
"- Operations signals are counters and timers the board logs, listed in the prompt of a role that reads them: stages waiting for a holder, backlog per role, stages waiting on a role nobody fills, churn, stale threads, idle members, missing capabilities, replicas added, spend. They are not posts in any channel; interpreting them is your judgment."
|
|
9773
10271
|
].join("\n");
|
|
9774
|
-
function renderOnboardingPreamble(
|
|
10272
|
+
function renderOnboardingPreamble(context2) {
|
|
9775
10273
|
return [
|
|
9776
|
-
`This is your first turn as ${
|
|
10274
|
+
`This is your first turn as ${context2.agentName}.`,
|
|
9777
10275
|
"",
|
|
9778
|
-
`Your role in one paragraph: ${
|
|
10276
|
+
`Your role in one paragraph: ${context2.roleSummary}`,
|
|
9779
10277
|
"",
|
|
9780
|
-
`You are working on project "${
|
|
9781
|
-
...(
|
|
9782
|
-
`You follow its general channel. It also has ${(
|
|
10278
|
+
`You are working on project "${context2.project}" in the worktree at ${context2.worktree}.`,
|
|
10279
|
+
...(context2.otherChannels ?? []).length === 0 ? [] : [
|
|
10280
|
+
`You follow its general channel. It also has ${(context2.otherChannels ?? []).map((ref) => `#${ref}`).join(", ")}, each a workstream such as a release: subscribe to one when your work there needs its posts. Its tasks and mentions reach you either way.`
|
|
9783
10281
|
],
|
|
9784
10282
|
"Your memory is empty. Write durable lessons about yourself, your craft, or the user to memory/core.md in your home directory.",
|
|
9785
10283
|
"Write profile.md in your home directory: one paragraph on what you do well and what to send your way. The roster the front desk routes with is built from it.",
|
|
@@ -9865,10 +10363,25 @@ function signalLine(ts, signal) {
|
|
|
9865
10363
|
].filter((fact) => fact.length > 0);
|
|
9866
10364
|
return `- [${ts}] ${signal.kind}: ${signal.summary} (${facts.join("; ")})`;
|
|
9867
10365
|
}
|
|
9868
|
-
function
|
|
10366
|
+
function quotaPhrase(cli, quota, now) {
|
|
10367
|
+
const level = quotaLevel(quota, now);
|
|
10368
|
+
if (level === "none") {
|
|
10369
|
+
return `${cli} reports no plan limits`;
|
|
10370
|
+
}
|
|
10371
|
+
if (level === "limited") {
|
|
10372
|
+
return `${cli} limited until ${quota.limitedUntil ?? "an unknown time"}`;
|
|
10373
|
+
}
|
|
10374
|
+
const windows = quota.windows.map((window) => !windowCurrent(window, now) ? `${windowName(window)} reset since` : `${windowName(window)} ${window.usedPercent === null ? "?" : Math.floor(window.usedPercent)}%`);
|
|
10375
|
+
return `${cli} ${windows.length === 0 ? "allowed" : windows.join(", ")}${level === "warning" ? " (near a limit)" : ""} as of ${quota.observedAt}`;
|
|
10376
|
+
}
|
|
10377
|
+
function runnerLine(runner, projects, now) {
|
|
9869
10378
|
const living = projects.filter((project) => project.runner === runner.name).map((project) => project.slug);
|
|
9870
10379
|
const state = runner.status === "connected" ? "connected" : `away${runner.lastSeen === void 0 ? "" : ` since ${runner.lastSeen}`}`;
|
|
9871
|
-
|
|
10380
|
+
const quotas = runner.clis.flatMap((cli) => {
|
|
10381
|
+
const quota = runner.quotas?.[cli];
|
|
10382
|
+
return quota === void 0 ? [] : [quotaPhrase(cli, quota, now)];
|
|
10383
|
+
});
|
|
10384
|
+
return `- ${runner.name}: ${state}; ${runner.os}; CLIs ${runner.clis.length === 0 ? "none" : runner.clis.join(", ")}; offers ${runner.capabilities.length === 0 ? "nothing beyond its CLIs" : runner.capabilities.join(", ")}; ${living.length === 0 ? "no projects" : `projects ${living.join(", ")}`}${quotas.length === 0 ? "" : `; quota ${quotas.join("; ")}`}`;
|
|
9872
10385
|
}
|
|
9873
10386
|
function profileLine(profile) {
|
|
9874
10387
|
const line = profile.split("\n").map((entry) => entry.trim()).find((entry) => entry.length > 0 && !entry.startsWith("#"));
|
|
@@ -9951,7 +10464,7 @@ function buildTurnPrompt(input) {
|
|
|
9951
10464
|
lines.push("A task you created is done. Tell whoever asked for it, if anyone did.");
|
|
9952
10465
|
}
|
|
9953
10466
|
if (dispatch.trigger.kind === "proposal_decided") {
|
|
9954
|
-
lines.push("A proposal you made was decided; the decision is the last post in the proposal's thread. A decision wakes you in the conversation you filed the proposal from, or at home once that has ended. Carry on with what an approval enables or a rejection asks, and stay silent if nothing follows.");
|
|
10467
|
+
lines.push("A proposal you made was decided; the decision is the last post in the proposal's thread. A decision wakes you in the conversation you filed the proposal from, or at home once that has ended. Carry on with what an approval enables or a rejection asks, and stay silent if nothing follows. An approved skill is published under society/skills/<name>/: delete your own skills/<name>/ from your home and report memoryUpdated, so your index lists the published copy alone.");
|
|
9955
10468
|
}
|
|
9956
10469
|
if (dispatch.trigger.kind === "cron") {
|
|
9957
10470
|
lines.push("A cron came due: what it asks is under Crons below.");
|
|
@@ -10018,12 +10531,12 @@ function buildTurnPrompt(input) {
|
|
|
10018
10531
|
}
|
|
10019
10532
|
const machines = input.runners;
|
|
10020
10533
|
if (machines !== void 0 && machines !== null) {
|
|
10021
|
-
lines.push("", "## Runners", "", "The machines turns run on. A project lives on the runner that takes its first turn, one that offers every capability the project and that turn's task require, and never moves. A task's required_capabilities must name capabilities exactly as a runner below offers them; a task needing one its project's runner lacks waits.", "");
|
|
10534
|
+
lines.push("", "## Runners", "", "The machines turns run on. A project lives on the runner that takes its first turn, one that offers every capability the project and that turn's task require, and never moves. A task's required_capabilities must name capabilities exactly as a runner below offers them; a task needing one its project's runner lacks waits. A runner's quota is what each CLI's login there last reported of its plan, in percent of each window; a CLI limited until a time fails its turns there until then.", "");
|
|
10022
10535
|
if (machines.runners.length === 0) {
|
|
10023
10536
|
lines.push("None registered yet.");
|
|
10024
10537
|
}
|
|
10025
10538
|
for (const runner of machines.runners) {
|
|
10026
|
-
lines.push(runnerLine(runner, machines.projects));
|
|
10539
|
+
lines.push(runnerLine(runner, machines.projects, input.clock?.now ?? /* @__PURE__ */ new Date()));
|
|
10027
10540
|
}
|
|
10028
10541
|
}
|
|
10029
10542
|
const knowledge = input.knowledge;
|
|
@@ -10556,6 +11069,11 @@ var TurnHost = class {
|
|
|
10556
11069
|
}
|
|
10557
11070
|
}
|
|
10558
11071
|
});
|
|
11072
|
+
if (outcome.quota !== void 0) {
|
|
11073
|
+
await this.board.recordQuota(turn.runner, job.cli, outcome.quota).catch((error) => {
|
|
11074
|
+
this.log.warn({ runner: turn.runner, cli: job.cli, error: String(error) }, "could not record a quota");
|
|
11075
|
+
});
|
|
11076
|
+
}
|
|
10559
11077
|
if (recorded) {
|
|
10560
11078
|
this.log.info({
|
|
10561
11079
|
agent: job.agent,
|
|
@@ -10737,7 +11255,7 @@ var TurnHost = class {
|
|
|
10737
11255
|
if (await this.board.hasMentioned(actor.name, USER_NAME, since)) {
|
|
10738
11256
|
return;
|
|
10739
11257
|
}
|
|
10740
|
-
const proposed = (await this.board.listProposals()).some((proposal) => proposal.proposedBy === actor.name && proposal.createdAt >= since &&
|
|
11258
|
+
const proposed = (await this.board.listProposals()).some((proposal) => proposal.proposedBy === actor.name && proposal.createdAt >= since && proposalApprovers(proposal.kind, proposal.charter).includes(USER_ROLE));
|
|
10741
11259
|
if (proposed) {
|
|
10742
11260
|
return;
|
|
10743
11261
|
}
|
|
@@ -10885,7 +11403,8 @@ var RunnerHub = class {
|
|
|
10885
11403
|
os: hello.os,
|
|
10886
11404
|
clis: hello.clis,
|
|
10887
11405
|
capabilities: hello.capabilities,
|
|
10888
|
-
version: hello.version
|
|
11406
|
+
version: hello.version,
|
|
11407
|
+
gitIdentityMissing: hello.gitIdentityMissing ?? null
|
|
10889
11408
|
});
|
|
10890
11409
|
this.runnerChanged(name);
|
|
10891
11410
|
this.log.info({ runner: name, version: hello.version, clis: hello.clis, slots: hello.slots }, "runner registered");
|
|
@@ -11327,6 +11846,26 @@ var RunnerHub = class {
|
|
|
11327
11846
|
}
|
|
11328
11847
|
return ModelListSchema.parse(await this.request(seat, (request) => ({ type: "models", request, cli })));
|
|
11329
11848
|
}
|
|
11849
|
+
/**
|
|
11850
|
+
* A CLI's plan quota, read on a runner without a turn and recorded on the runner's record.
|
|
11851
|
+
* Refused for a runner not connected with the CLI, and for one from before quota reads, which
|
|
11852
|
+
* would skip the request and leave the reader waiting.
|
|
11853
|
+
*/
|
|
11854
|
+
async quota(runner, cli) {
|
|
11855
|
+
const seat = this.seats.get(runner);
|
|
11856
|
+
if (seat === void 0 || seat.send === null || !seat.hello.clis.includes(cli)) {
|
|
11857
|
+
throw new RunnerAwayError(`runner ${runner} is not connected with ${cli}`);
|
|
11858
|
+
}
|
|
11859
|
+
if (!seat.hello.quotaReads) {
|
|
11860
|
+
throw new BoardError("INVALID_STATE", `runner ${runner} cannot read quotas yet; upgrade it`);
|
|
11861
|
+
}
|
|
11862
|
+
const quota = CliQuotaSchema.nullable().parse(await this.request(seat, (request) => ({ type: "quota", request, cli }), FILE_TIMEOUT_MS));
|
|
11863
|
+
if (quota === null) {
|
|
11864
|
+
throw new BoardError("NOT_FOUND", `${cli} on runner ${runner} cannot read its quota`);
|
|
11865
|
+
}
|
|
11866
|
+
await this.board.recordQuota(runner, cli, quota);
|
|
11867
|
+
return quota;
|
|
11868
|
+
}
|
|
11330
11869
|
/** Calls `listener` whenever a runner registers, connects, or goes away. */
|
|
11331
11870
|
onRunnerChange(listener) {
|
|
11332
11871
|
this.runnerListeners.add(listener);
|
|
@@ -11559,20 +12098,20 @@ async function handleMcpRequest(board2, actor, request, version) {
|
|
|
11559
12098
|
import { Hono as Hono3 } from "hono";
|
|
11560
12099
|
import { compress } from "hono/compress";
|
|
11561
12100
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
11562
|
-
import { z as
|
|
12101
|
+
import { z as z18, ZodError } from "zod";
|
|
11563
12102
|
|
|
11564
12103
|
// apps/server/dist/github-auth.js
|
|
11565
12104
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
11566
12105
|
import { Hono } from "hono";
|
|
11567
|
-
import { z as
|
|
12106
|
+
import { z as z17 } from "zod";
|
|
11568
12107
|
var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
|
|
11569
12108
|
var STATE_TTL_MS = 10 * 6e4;
|
|
11570
12109
|
var MAX_STATES = 1e3;
|
|
11571
|
-
var TokenAnswerSchema =
|
|
11572
|
-
var GithubUserSchema =
|
|
11573
|
-
login:
|
|
11574
|
-
id:
|
|
11575
|
-
avatar_url:
|
|
12110
|
+
var TokenAnswerSchema = z17.object({ access_token: z17.string().min(1) });
|
|
12111
|
+
var GithubUserSchema = z17.object({
|
|
12112
|
+
login: z17.string().min(1),
|
|
12113
|
+
id: z17.number().int(),
|
|
12114
|
+
avatar_url: z17.string().optional()
|
|
11576
12115
|
});
|
|
11577
12116
|
function githubAvatar(url) {
|
|
11578
12117
|
try {
|
|
@@ -11995,7 +12534,7 @@ async function isFile(file) {
|
|
|
11995
12534
|
return false;
|
|
11996
12535
|
}
|
|
11997
12536
|
}
|
|
11998
|
-
function
|
|
12537
|
+
function within2(root, pathname) {
|
|
11999
12538
|
let decoded;
|
|
12000
12539
|
try {
|
|
12001
12540
|
decoded = decodeURIComponent(pathname);
|
|
@@ -12012,7 +12551,7 @@ function spaHandler(staticDir) {
|
|
|
12012
12551
|
if (pathname.startsWith("/api/")) {
|
|
12013
12552
|
return c.json({ error: "NOT_FOUND", message: `no route ${pathname}` }, 404);
|
|
12014
12553
|
}
|
|
12015
|
-
const file =
|
|
12554
|
+
const file = within2(root, pathname);
|
|
12016
12555
|
const headers = { "x-content-type-options": "nosniff" };
|
|
12017
12556
|
if (file !== null && await isFile(file)) {
|
|
12018
12557
|
return c.body(await readFile5(file), 200, {
|
|
@@ -12047,19 +12586,19 @@ var ERROR_STATUS = {
|
|
|
12047
12586
|
INVALID_STATE: 409
|
|
12048
12587
|
};
|
|
12049
12588
|
var EVENT_PAGE = 100;
|
|
12050
|
-
var RetireBodySchema =
|
|
12051
|
-
var RunnerBodySchema =
|
|
12052
|
-
var EnrollmentApprovalSchema =
|
|
12053
|
-
var HomeRunnerBodySchema =
|
|
12054
|
-
var ModelBodySchema =
|
|
12055
|
-
var EffortBodySchema =
|
|
12056
|
-
var HistoryLimitSchema =
|
|
12057
|
-
var MessageIdsSchema =
|
|
12058
|
-
var TimezoneBodySchema =
|
|
12059
|
-
var ChannelBodySchema =
|
|
12589
|
+
var RetireBodySchema = z18.object({ reason: z18.string().min(1) });
|
|
12590
|
+
var RunnerBodySchema = z18.object({ name: NameSchema });
|
|
12591
|
+
var EnrollmentApprovalSchema = z18.object({ name: NameSchema });
|
|
12592
|
+
var HomeRunnerBodySchema = z18.object({ runner: NameSchema.nullable() });
|
|
12593
|
+
var ModelBodySchema = z18.object({ model: ModelNameSchema.nullable() });
|
|
12594
|
+
var EffortBodySchema = z18.object({ effort: EffortSchema.nullable() });
|
|
12595
|
+
var HistoryLimitSchema = z18.coerce.number().int().min(1).max(500);
|
|
12596
|
+
var MessageIdsSchema = z18.string().transform((ids) => ids.split(",").filter((id) => id !== "")).pipe(z18.array(UlidSchema).min(1).max(MESSAGE_LOOKUP_LIMIT));
|
|
12597
|
+
var TimezoneBodySchema = z18.object({ timezone: TimeZoneSchema });
|
|
12598
|
+
var ChannelBodySchema = z18.object({
|
|
12060
12599
|
project: NameSchema.nullable().default(null),
|
|
12061
12600
|
name: NameSchema,
|
|
12062
|
-
purpose:
|
|
12601
|
+
purpose: z18.string().min(1)
|
|
12063
12602
|
});
|
|
12064
12603
|
function bearer2(c) {
|
|
12065
12604
|
const header = c.req.header("authorization") ?? "";
|
|
@@ -12067,7 +12606,7 @@ function bearer2(c) {
|
|
|
12067
12606
|
return match?.[1]?.trim() ?? null;
|
|
12068
12607
|
}
|
|
12069
12608
|
function createApp(deps) {
|
|
12070
|
-
const { board: board2, version, turns: turns2, scheduler: scheduler2, models } = deps;
|
|
12609
|
+
const { board: board2, version, turns: turns2, scheduler: scheduler2, models, quotas } = deps;
|
|
12071
12610
|
const eventStreamPingMs = deps.eventStreamPingMs ?? 15e3;
|
|
12072
12611
|
const app2 = new Hono3();
|
|
12073
12612
|
app2.get("/health", (c) => c.json({ ok: true, version }));
|
|
@@ -12137,6 +12676,17 @@ function createApp(deps) {
|
|
|
12137
12676
|
api.get("/agents/:name/history/:commit", async (c) => c.json(await board2.homeChange(c.req.param("name"), c.req.param("commit"))));
|
|
12138
12677
|
api.get("/roles", async (c) => c.json(await board2.listRoles()));
|
|
12139
12678
|
api.get("/runners", async (c) => c.json(await board2.listRunners()));
|
|
12679
|
+
api.post("/runners/:name/quota/:cli", async (c) => {
|
|
12680
|
+
if (c.get("actor").role !== USER_ROLE) {
|
|
12681
|
+
throw new BoardError("FORBIDDEN", "only the user reads a runner's quota");
|
|
12682
|
+
}
|
|
12683
|
+
const runner = NameSchema.parse(c.req.param("name"));
|
|
12684
|
+
const cli = CliKindSchema.parse(c.req.param("cli"));
|
|
12685
|
+
if (quotas === void 0) {
|
|
12686
|
+
throw new RunnerAwayError("this server asks no runner for quotas");
|
|
12687
|
+
}
|
|
12688
|
+
return c.json(await quotas.read(runner, cli));
|
|
12689
|
+
});
|
|
12140
12690
|
api.post("/runners", async (c) => {
|
|
12141
12691
|
const body = RunnerBodySchema.parse(await c.req.json());
|
|
12142
12692
|
return c.json(await board2.addRunner(c.get("actor"), body.name));
|
|
@@ -12173,6 +12723,10 @@ function createApp(deps) {
|
|
|
12173
12723
|
const window = MetricsWindowSchema.parse(c.req.query("window") ?? "7d");
|
|
12174
12724
|
return c.json(await board2.metrics(window, new Set(scheduler2?.activeSignals ?? [])));
|
|
12175
12725
|
});
|
|
12726
|
+
api.get("/tokens", async (c) => {
|
|
12727
|
+
const window = MetricsWindowSchema.parse(c.req.query("window") ?? "24h");
|
|
12728
|
+
return c.json(await board2.tokenUse(window));
|
|
12729
|
+
});
|
|
12176
12730
|
api.get("/society/knowledge", async (c) => c.json(await board2.listKnowledge(null)));
|
|
12177
12731
|
api.get("/skills", async (c) => c.json(await board2.listSocietySkills()));
|
|
12178
12732
|
api.post("/agents/:name/retire", async (c) => {
|
|
@@ -12216,7 +12770,7 @@ function createApp(deps) {
|
|
|
12216
12770
|
});
|
|
12217
12771
|
api.put("/roles/:name", async (c) => {
|
|
12218
12772
|
const charter = RoleCharterSchema.parse({
|
|
12219
|
-
...
|
|
12773
|
+
...z18.record(z18.string(), z18.unknown()).parse(await c.req.json()),
|
|
12220
12774
|
name: c.req.param("name")
|
|
12221
12775
|
});
|
|
12222
12776
|
return c.json(await board2.setRoleCharter(c.get("actor"), charter));
|
|
@@ -12566,6 +13120,35 @@ var ModelCatalog = class {
|
|
|
12566
13120
|
}
|
|
12567
13121
|
};
|
|
12568
13122
|
|
|
13123
|
+
// apps/server/dist/quotas.js
|
|
13124
|
+
var MINUTE = 6e4;
|
|
13125
|
+
var QuotaReads = class {
|
|
13126
|
+
runners;
|
|
13127
|
+
floorMs;
|
|
13128
|
+
now;
|
|
13129
|
+
kept = /* @__PURE__ */ new Map();
|
|
13130
|
+
constructor(runners2, floorMs = MINUTE, now = Date.now) {
|
|
13131
|
+
this.runners = runners2;
|
|
13132
|
+
this.floorMs = floorMs;
|
|
13133
|
+
this.now = now;
|
|
13134
|
+
}
|
|
13135
|
+
read(runner, cli) {
|
|
13136
|
+
const key = `${runner}/${cli}`;
|
|
13137
|
+
const kept = this.kept.get(key);
|
|
13138
|
+
if (kept !== void 0 && this.now() - kept.at < this.floorMs) {
|
|
13139
|
+
return kept.quota;
|
|
13140
|
+
}
|
|
13141
|
+
const quota = this.runners.quota(runner, cli);
|
|
13142
|
+
this.kept.set(key, { at: this.now(), quota });
|
|
13143
|
+
quota.catch(() => {
|
|
13144
|
+
if (this.kept.get(key)?.quota === quota) {
|
|
13145
|
+
this.kept.delete(key);
|
|
13146
|
+
}
|
|
13147
|
+
});
|
|
13148
|
+
return quota;
|
|
13149
|
+
}
|
|
13150
|
+
};
|
|
13151
|
+
|
|
12569
13152
|
// apps/server/dist/turn-hub.js
|
|
12570
13153
|
var TurnHub = class {
|
|
12571
13154
|
capacity;
|
|
@@ -12673,6 +13256,7 @@ var app = createApp({
|
|
|
12673
13256
|
turns,
|
|
12674
13257
|
scheduler: view,
|
|
12675
13258
|
models: new ModelCatalog(runners),
|
|
13259
|
+
quotas: new QuotaReads(runners),
|
|
12676
13260
|
runners,
|
|
12677
13261
|
webDir,
|
|
12678
13262
|
signIn: { github: config.github, log },
|