@kubitnodes/stellaris 0.3.27 → 0.4.1
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 +448 -66
- package/dist/server/index.js +882 -156
- package/package.json +2 -2
- package/web/dist/assets/BackLink-fxPmFUg8.js +1 -0
- package/web/dist/assets/Button-DxGzriWQ.js +1 -0
- package/web/dist/assets/ChannelForms-C3m4IPnZ.js +1 -0
- package/web/dist/assets/ChannelView-DvtoxLi5.js +2 -0
- package/web/dist/assets/CitizenView-Dq64VsZi.js +2 -0
- package/web/dist/assets/{CitizensView-Bgw6PRSo.js → CitizensView-BOZHABbN.js} +1 -1
- package/web/dist/assets/{CliIcon-BB2NdLxN.js → CliIcon-D3GddnP4.js} +1 -1
- package/web/dist/assets/CronsView-CI947lZS.js +1 -0
- package/web/dist/assets/{FileTree-CHDmrOhY.js → FileTree-DSFN4sd8.js} +1 -1
- package/web/dist/assets/{FileView-DUMfKYRW.js → FileView-DyUYLXRC.js} +2 -2
- package/web/dist/assets/KnowledgeView-CMdXaTvS.js +1 -0
- package/web/dist/assets/{Markdown-C7RbUEZE.js → Markdown-BSeWHBBA.js} +5 -5
- package/web/dist/assets/{MessageItem-DgWh5pJF.js → MessageItem-qIxS96uJ.js} +1 -1
- package/web/dist/assets/MetricsView-BhjdwkX1.js +1 -0
- package/web/dist/assets/{NeedsYouView-CrmHXZAn.js → NeedsYouView-7uZW9dY8.js} +1 -1
- package/web/dist/assets/{Overview-xatORJK6.js → Overview-D6kiK8sE.js} +1 -1
- package/web/dist/assets/{Pane-BlIRwUtQ.js → Pane-pZUzNQIW.js} +1 -1
- package/web/dist/assets/{Picker-BSOB2JM_.js → Picker-CDf6os_x.js} +1 -1
- package/web/dist/assets/{ProjectView-CVt28SoI.js → ProjectView-B6OmxiT9.js} +1 -1
- package/web/dist/assets/ProposalView-B1X8Jlo0.js +1 -0
- package/web/dist/assets/{ProposalsView-C0r1ya8_.js → ProposalsView-DBGpcAPO.js} +1 -1
- package/web/dist/assets/{Quota-59QJZrPa.js → Quota-Brd4ddB4.js} +1 -1
- package/web/dist/assets/RunnersView-45OXtalD.js +1 -0
- package/web/dist/assets/{SkillFileList-Cs_f3RPo.js → SkillFileList-BUImALv9.js} +1 -1
- package/web/dist/assets/{SkillView-BvVx3LSL.js → SkillView-B1knYZV8.js} +1 -1
- package/web/dist/assets/SocietyView-F7bzrNv5.js +1 -0
- package/web/dist/assets/{StickyList-BbGq82Jj.js → StickyList-D_ED_-LW.js} +1 -1
- package/web/dist/assets/{SubjectThread-DdkyhVqK.js → SubjectThread-B5zvoRSd.js} +1 -1
- package/web/dist/assets/{TaskView-CyEWpCvS.js → TaskView-B5_QTI3p.js} +2 -2
- package/web/dist/assets/{TasksView-D10XP1uk.js → TasksView-DZLNqU2V.js} +1 -1
- package/web/dist/assets/{ThreadForms-D94Jj0ES.js → ThreadForms-CbFClyd0.js} +1 -1
- package/web/dist/assets/{ThreadItem-CYmdCPOf.js → ThreadItem-kFn5M3Cx.js} +1 -1
- package/web/dist/assets/{ThreadView-D79G2zgF.js → ThreadView-CHFs0zaS.js} +1 -1
- package/web/dist/assets/citizen-B8p56JRh.js +1 -0
- package/web/dist/assets/compose-CV4o6TMb.js +1 -0
- package/web/dist/assets/format-BYF63sQU.js +55 -0
- package/web/dist/assets/{governance-YialJVYK.js → governance-BjIg0eok.js} +1 -1
- package/web/dist/assets/index-CA_njyMe.css +2 -0
- package/web/dist/assets/index-CyROMh25.js +24 -0
- package/web/dist/assets/{mentions-Buc5vs-j.js → mentions-BH0RZXfc.js} +1 -1
- package/web/dist/assets/{tasks-DLTeaHys.js → tasks-D2Es55TQ.js} +1 -1
- package/web/dist/assets/useMatch-DeOpK57G.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-qSxOMeEG.js +0 -1
- package/web/dist/assets/Button-CxjWy598.js +0 -1
- package/web/dist/assets/ChannelForms-BVidWMId.js +0 -1
- package/web/dist/assets/ChannelView-rqfk1Tyu.js +0 -2
- package/web/dist/assets/CitizenView-xU99R8hS.js +0 -2
- package/web/dist/assets/CronsView-BrXrcfC6.js +0 -1
- package/web/dist/assets/KnowledgeView-Qog6c92V.js +0 -1
- package/web/dist/assets/MetricsView-DuOyISk1.js +0 -1
- package/web/dist/assets/ProposalView-DJ0aJm7J.js +0 -1
- package/web/dist/assets/RunnersView-CcAAfw6y.js +0 -1
- package/web/dist/assets/SocietyView-CJRrypjI.js +0 -1
- package/web/dist/assets/citizen-CgA4vJjy.js +0 -1
- package/web/dist/assets/compose-3ksQaHF6.js +0 -1
- package/web/dist/assets/format-DpQyO6r1.js +0 -55
- package/web/dist/assets/index-DIa8Dlav.css +0 -2
- package/web/dist/assets/index-DhuCp-VF.js +0 -23
- package/web/dist/assets/useMatch-DPEg8ohe.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,8 @@ var VERB_NAMES = [
|
|
|
65
65
|
"update_cron",
|
|
66
66
|
"remove_cron",
|
|
67
67
|
"remove_skill",
|
|
68
|
-
"upload_image"
|
|
68
|
+
"upload_image",
|
|
69
|
+
"read_image"
|
|
69
70
|
];
|
|
70
71
|
var VerbNameSchema = z2.enum(VERB_NAMES);
|
|
71
72
|
var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
|
|
@@ -95,6 +96,7 @@ var USER_NAME = "user";
|
|
|
95
96
|
var MEMBER_VERBS = [
|
|
96
97
|
"post_message",
|
|
97
98
|
"upload_image",
|
|
99
|
+
"read_image",
|
|
98
100
|
"read_inbox",
|
|
99
101
|
"search",
|
|
100
102
|
"open_thread",
|
|
@@ -225,7 +227,12 @@ var CronScheduleSchema = z3.union([
|
|
|
225
227
|
var CronRunSchema = z3.object({
|
|
226
228
|
turnId: UlidSchema,
|
|
227
229
|
at: IsoDateTimeSchema,
|
|
228
|
-
outcome: z3.enum(["completed", "failed", "stopped"])
|
|
230
|
+
outcome: z3.enum(["completed", "failed", "stopped"]),
|
|
231
|
+
/**
|
|
232
|
+
* For a failed one, the first turn of the cron's citizen in its conversation that was shown the
|
|
233
|
+
* failure and completed, and when it ended.
|
|
234
|
+
*/
|
|
235
|
+
caughtUp: z3.object({ turnId: UlidSchema, at: IsoDateTimeSchema }).optional()
|
|
229
236
|
});
|
|
230
237
|
var CronFrontmatterSchema = z3.object({
|
|
231
238
|
id: UlidSchema,
|
|
@@ -272,6 +279,16 @@ function shortestGapMs(schedule, societyZone, after) {
|
|
|
272
279
|
}
|
|
273
280
|
return shortest;
|
|
274
281
|
}
|
|
282
|
+
function firesInTask(cron2, task2) {
|
|
283
|
+
return cron2.scope === task2.project && cron2.thread === task2.id;
|
|
284
|
+
}
|
|
285
|
+
function cronFailure(cron2) {
|
|
286
|
+
return cron2.lastRun?.outcome === "failed" && cron2.lastRun.caughtUp === void 0 ? cron2.lastRun : null;
|
|
287
|
+
}
|
|
288
|
+
function cronStoppedAt(cron2) {
|
|
289
|
+
const times = [cron2.paused?.at, cron2.ended?.at, cronFailure(cron2)?.at].filter((at) => at !== void 0);
|
|
290
|
+
return times.reduce((first, at) => first === null || Date.parse(at) < Date.parse(first) ? at : first, null);
|
|
291
|
+
}
|
|
275
292
|
function nextFire(cron2, societyZone, floorMs = CRON_FLOOR_MS) {
|
|
276
293
|
if (cron2.ended !== void 0 || cron2.paused !== void 0) {
|
|
277
294
|
return null;
|
|
@@ -353,6 +370,18 @@ function mergeQuota(previous, next) {
|
|
|
353
370
|
windows: [...windows.values()]
|
|
354
371
|
};
|
|
355
372
|
}
|
|
373
|
+
var UNKNOWN_LIMIT_HOLD_MS = 30 * 6e4;
|
|
374
|
+
function stopsTurns(quota) {
|
|
375
|
+
return quota.status === "limited" && !(quota.source === "read" && quota.extraUsage?.enabled === true);
|
|
376
|
+
}
|
|
377
|
+
function limitHold(quota, now) {
|
|
378
|
+
if (!stopsTurns(quota)) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
const known = quota.limitedUntil !== null;
|
|
382
|
+
const until = quota.limitedUntil ?? new Date(Date.parse(quota.observedAt) + UNKNOWN_LIMIT_HOLD_MS).toISOString();
|
|
383
|
+
return Date.parse(until) > now.getTime() ? { until, known } : null;
|
|
384
|
+
}
|
|
356
385
|
function readings(quota) {
|
|
357
386
|
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();
|
|
358
387
|
return windows.join(",");
|
|
@@ -651,6 +680,7 @@ var MemberSchema = z5.object({
|
|
|
651
680
|
});
|
|
652
681
|
var RunnerOsSchema = z5.enum(["linux", "windows", "darwin"]);
|
|
653
682
|
var GitIdentityPartSchema = z5.enum(["name", "email"]);
|
|
683
|
+
var RunnerPauseSchema = z5.object({ at: IsoDateTimeSchema, by: NameSchema });
|
|
654
684
|
var RunnerSchema = z5.object({
|
|
655
685
|
name: NameSchema,
|
|
656
686
|
os: RunnerOsSchema,
|
|
@@ -669,8 +699,29 @@ var RunnerSchema = z5.object({
|
|
|
669
699
|
* Each CLI's plan quota as its login on this runner last reported it, from a turn's stream or a
|
|
670
700
|
* read; absent until one has.
|
|
671
701
|
*/
|
|
672
|
-
quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional()
|
|
702
|
+
quotas: z5.partialRecord(CliKindSchema, CliQuotaSchema).optional(),
|
|
703
|
+
/** The user's pause of the whole runner: no turn starts on it until the user resumes it. */
|
|
704
|
+
paused: RunnerPauseSchema.optional(),
|
|
705
|
+
/**
|
|
706
|
+
* The user's pauses of single CLIs on the runner, each apart from the runner's own: no turn of
|
|
707
|
+
* that CLI starts on it until the user resumes it.
|
|
708
|
+
*/
|
|
709
|
+
pausedClis: z5.partialRecord(CliKindSchema, RunnerPauseSchema).optional()
|
|
673
710
|
});
|
|
711
|
+
function runnerPause(runner2, cli) {
|
|
712
|
+
return runner2.paused ?? runner2.pausedClis?.[cli];
|
|
713
|
+
}
|
|
714
|
+
function runnerLimitHold(runner2, cli, now) {
|
|
715
|
+
const quota = runner2.quotas?.[cli];
|
|
716
|
+
return quota === void 0 ? null : limitHold(quota, now);
|
|
717
|
+
}
|
|
718
|
+
function heldOn(runners, runner2, cli, now) {
|
|
719
|
+
if (runner2 === void 0 || cli === null) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
const record = runners.find((each) => each.name === runner2);
|
|
723
|
+
return record !== void 0 && (runnerPause(record, cli) !== void 0 || runnerLimitHold(record, cli, now) !== null);
|
|
724
|
+
}
|
|
674
725
|
var ProposalKindSchema = z5.enum([
|
|
675
726
|
"role",
|
|
676
727
|
"member",
|
|
@@ -1014,7 +1065,21 @@ function imageTypeOf(bytes) {
|
|
|
1014
1065
|
return null;
|
|
1015
1066
|
}
|
|
1016
1067
|
var ImagePathSchema = z6.string().min(1).max(4096).refine((value) => !value.includes("\0"), { message: "a path with no NUL character" });
|
|
1068
|
+
var ImageNameSchema = z6.string().max(1024);
|
|
1069
|
+
function imageDescription(name) {
|
|
1070
|
+
return name.replaceAll(/[[\]\\\r\n`]/g, "");
|
|
1071
|
+
}
|
|
1017
1072
|
var IMAGE_SCHEME = "image:";
|
|
1073
|
+
var ImageRefSchema = z6.string().regex(/^(?:image:)?[0-9A-HJKMNP-TV-Z]{26}$/, "expected an image's id or image:<id>");
|
|
1074
|
+
function imageIdOf(ref) {
|
|
1075
|
+
return ref.startsWith(IMAGE_SCHEME) ? ref.slice(IMAGE_SCHEME.length) : ref;
|
|
1076
|
+
}
|
|
1077
|
+
var ImageFileSchema = z6.object({
|
|
1078
|
+
id: UlidSchema,
|
|
1079
|
+
type: ImageTypeSchema,
|
|
1080
|
+
size: z6.number().int().positive(),
|
|
1081
|
+
path: ImagePathSchema
|
|
1082
|
+
});
|
|
1018
1083
|
var UploadedImageSchema = z6.object({
|
|
1019
1084
|
id: UlidSchema,
|
|
1020
1085
|
type: ImageTypeSchema,
|
|
@@ -1179,6 +1244,10 @@ var VerbInputs = {
|
|
|
1179
1244
|
upload_image: z7.object({
|
|
1180
1245
|
/** The image file on the machine your turn runs on: absolute, or relative to your working directory. */
|
|
1181
1246
|
path: ImagePathSchema
|
|
1247
|
+
}),
|
|
1248
|
+
read_image: z7.object({
|
|
1249
|
+
/** The image's id, or its `image:<id>` address as a post shows it. */
|
|
1250
|
+
image_id: ImageRefSchema
|
|
1182
1251
|
})
|
|
1183
1252
|
};
|
|
1184
1253
|
|
|
@@ -1230,6 +1299,8 @@ var BOARD_EVENT_TYPES = [
|
|
|
1230
1299
|
"user.token_rotated",
|
|
1231
1300
|
"runner.changed",
|
|
1232
1301
|
"runner.quota",
|
|
1302
|
+
"runner.paused",
|
|
1303
|
+
"runner.resumed",
|
|
1233
1304
|
"ops.signal",
|
|
1234
1305
|
"paused.changed",
|
|
1235
1306
|
"wake.requested",
|
|
@@ -1371,6 +1442,11 @@ var RunnerHelloSchema = z9.object({
|
|
|
1371
1442
|
quotaReads: z9.boolean().default(false),
|
|
1372
1443
|
/** Whether the runner reads an image a turn running there uploads; one from before never does. */
|
|
1373
1444
|
imageReads: z9.boolean().default(false),
|
|
1445
|
+
/**
|
|
1446
|
+
* Whether the runner writes an image the board sends for a turn running there to open, for
|
|
1447
|
+
* `read_image`; one from before never does.
|
|
1448
|
+
*/
|
|
1449
|
+
imageWrites: z9.boolean().default(false),
|
|
1374
1450
|
/**
|
|
1375
1451
|
* Whether the runner carries a project's repository out to another runner and in from one, and
|
|
1376
1452
|
* removes its copy once the project has moved; one from before never does.
|
|
@@ -1484,6 +1560,12 @@ var TurnJobSchema = z9.object({
|
|
|
1484
1560
|
usageSoFar: UsageSchema.optional(),
|
|
1485
1561
|
/** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
|
|
1486
1562
|
instructions: z9.string(),
|
|
1563
|
+
/**
|
|
1564
|
+
* The hash of `instructions` without the first turn's section (`instructionsHash` in
|
|
1565
|
+
* `turn-host`): a warm process started on other instructions starts afresh, so a compaction in
|
|
1566
|
+
* it puts back the current ones. Absent from a server from before, when the text is compared.
|
|
1567
|
+
*/
|
|
1568
|
+
instructionsHash: z9.string().optional(),
|
|
1487
1569
|
prompt: z9.string(),
|
|
1488
1570
|
mcp: z9.object({ url: z9.string().min(1), token: z9.string().min(1) }),
|
|
1489
1571
|
limits: z9.object({
|
|
@@ -1668,6 +1750,13 @@ var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
|
1668
1750
|
}),
|
|
1669
1751
|
z9.object({ kind: z9.literal("refused"), why: ImageRefusalSchema, detail: z9.string() })
|
|
1670
1752
|
]);
|
|
1753
|
+
var TurnImageWriteSchema = z9.object({
|
|
1754
|
+
turnId: UlidSchema,
|
|
1755
|
+
id: UlidSchema,
|
|
1756
|
+
type: ImageTypeSchema,
|
|
1757
|
+
content: z9.string()
|
|
1758
|
+
});
|
|
1759
|
+
var TurnImageFileSchema = z9.object({ path: ImagePathSchema });
|
|
1671
1760
|
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1672
1761
|
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1673
1762
|
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
@@ -1681,6 +1770,7 @@ var HeldWorkspaceSchema = z9.object({
|
|
|
1681
1770
|
var HeldWorkspacesSchema = z9.object({ workspaces: z9.array(HeldWorkspaceSchema) });
|
|
1682
1771
|
var SweepResultSchema = z9.object({ kept: z9.array(HeldWorkspaceSchema) });
|
|
1683
1772
|
var MOVE_PACK_LIMIT_BYTES = 256 * 1024 * 1024;
|
|
1773
|
+
var MOVE_FLOOR_BYTES_PER_SECOND = 16 * 1024;
|
|
1684
1774
|
var RepoRefsSchema = z9.record(z9.string().regex(/^refs\/(heads|tags)\/\S+$/), z9.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/));
|
|
1685
1775
|
var ProjectExportSchema = z9.object({ move: UlidSchema, repo: ProjectRepoSchema });
|
|
1686
1776
|
var HeldWorkSchema = z9.object({
|
|
@@ -1704,7 +1794,9 @@ var ProjectExportedSchema = z9.discriminatedUnion("ok", [
|
|
|
1704
1794
|
var ProjectImportSchema = z9.object({
|
|
1705
1795
|
move: UlidSchema,
|
|
1706
1796
|
repo: ProjectRepoSchema,
|
|
1707
|
-
pullRequests: z9.boolean()
|
|
1797
|
+
pullRequests: z9.boolean(),
|
|
1798
|
+
/** The packs' size the old runner measured, which the clone's clock is sized from; the cap if unsaid. */
|
|
1799
|
+
bytes: z9.number().int().nonnegative().optional()
|
|
1708
1800
|
});
|
|
1709
1801
|
var ProjectImportedSchema = z9.object({ refs: RepoRefsSchema });
|
|
1710
1802
|
var ProjectReleaseSchema = z9.object({ slug: NameSchema });
|
|
@@ -1744,6 +1836,15 @@ var RunnerMessageSchema = z9.discriminatedUnion("type", [
|
|
|
1744
1836
|
* whose hello says `imageReads`.
|
|
1745
1837
|
*/
|
|
1746
1838
|
z9.object({ type: z9.literal("image"), request: z9.string().min(1), read: TurnImageReadSchema }),
|
|
1839
|
+
/**
|
|
1840
|
+
* Answered with a `TurnImageFile`, or null when the turn is not running there. Sent only to
|
|
1841
|
+
* runners whose hello says `imageWrites`.
|
|
1842
|
+
*/
|
|
1843
|
+
z9.object({
|
|
1844
|
+
type: z9.literal("image_write"),
|
|
1845
|
+
request: z9.string().min(1),
|
|
1846
|
+
write: TurnImageWriteSchema
|
|
1847
|
+
}),
|
|
1747
1848
|
/** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
|
|
1748
1849
|
z9.object({
|
|
1749
1850
|
type: z9.literal("steer"),
|
|
@@ -1809,6 +1910,7 @@ var TriggerKindSchema = z10.enum([
|
|
|
1809
1910
|
"closing",
|
|
1810
1911
|
"cron"
|
|
1811
1912
|
]);
|
|
1913
|
+
var HeartbeatCauseSchema = z10.enum(["unread", "held", "waiting"]);
|
|
1812
1914
|
var TriggerSchema = z10.object({
|
|
1813
1915
|
kind: TriggerKindSchema,
|
|
1814
1916
|
from: NameSchema.optional(),
|
|
@@ -1819,7 +1921,9 @@ var TriggerSchema = z10.object({
|
|
|
1819
1921
|
/** The channel the waking message was posted in. */
|
|
1820
1922
|
channel: ChannelRefSchema.optional(),
|
|
1821
1923
|
/** The cron that came due, for a `cron` wake. */
|
|
1822
|
-
cronId: UlidSchema.optional()
|
|
1924
|
+
cronId: UlidSchema.optional(),
|
|
1925
|
+
/** What a `heartbeat` woke for; absent on records from before the board kept it. */
|
|
1926
|
+
heartbeat: HeartbeatCauseSchema.optional()
|
|
1823
1927
|
});
|
|
1824
1928
|
var TurnThreadSchema = z10.object({ id: UlidSchema, task: z10.boolean() });
|
|
1825
1929
|
var TurnDispatchSchema = z10.object({
|
|
@@ -1885,6 +1989,12 @@ var TurnRecordSchema = z10.object({
|
|
|
1885
1989
|
* one. A resumed session's next turn measures its own usage from here.
|
|
1886
1990
|
*/
|
|
1887
1991
|
sessionUsage: UsageSchema.optional(),
|
|
1992
|
+
/**
|
|
1993
|
+
* The SHA-256 of the instructions the session holds after this turn, the first turn's section
|
|
1994
|
+
* left out (`instructionsHash` in `turn-host`): those it began with, or the latest a prompt
|
|
1995
|
+
* carried. Absent when the board does not know them, as for records from before it kept them.
|
|
1996
|
+
*/
|
|
1997
|
+
sessionInstructionsHash: z10.string().optional(),
|
|
1888
1998
|
/**
|
|
1889
1999
|
* The whole input of the turn's latest model call in the session's own thread, as the runner
|
|
1890
2000
|
* reported it: how large the session had grown. Absent for a turn that made no call.
|
|
@@ -1909,7 +2019,13 @@ var TurnRecordSchema = z10.object({
|
|
|
1909
2019
|
*/
|
|
1910
2020
|
sessionDropped: z10.boolean().optional(),
|
|
1911
2021
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1912
|
-
crons: z10.array(UlidSchema).optional()
|
|
2022
|
+
crons: z10.array(UlidSchema).optional(),
|
|
2023
|
+
/**
|
|
2024
|
+
* The failed fires its prompt named: each of its citizen's crons in its conversation whose latest
|
|
2025
|
+
* fire's turn failed with nothing catching it up (`cronFailure`), but those it carries, with that
|
|
2026
|
+
* turn. Completing it catches each up.
|
|
2027
|
+
*/
|
|
2028
|
+
cronFailures: z10.array(z10.object({ cron: UlidSchema, turnId: UlidSchema })).optional()
|
|
1913
2029
|
});
|
|
1914
2030
|
var RunningTurnSchema = z10.object({
|
|
1915
2031
|
turnId: UlidSchema,
|
|
@@ -1951,6 +2067,8 @@ var TurnHistoryEntrySchema = z10.object({
|
|
|
1951
2067
|
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1952
2068
|
place: NameSchema.optional(),
|
|
1953
2069
|
trigger: z10.string(),
|
|
2070
|
+
/** What a `heartbeat` woke for; absent on turns logged before the board kept it. */
|
|
2071
|
+
heartbeat: HeartbeatCauseSchema.optional(),
|
|
1954
2072
|
exitReason: z10.string().nullable(),
|
|
1955
2073
|
costUsd: z10.number(),
|
|
1956
2074
|
/** The tokens the turn used; absent for turns logged before turns recorded them. */
|
|
@@ -2021,7 +2139,7 @@ var METRICS_WINDOW_MS = {
|
|
|
2021
2139
|
"7d": 7 * 24 * 36e5,
|
|
2022
2140
|
all: null
|
|
2023
2141
|
};
|
|
2024
|
-
var READ_VERBS = ["read_inbox", "search", "get_task"];
|
|
2142
|
+
var READ_VERBS = ["read_inbox", "search", "get_task", "read_image"];
|
|
2025
2143
|
function isBoardAction(tool) {
|
|
2026
2144
|
const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
|
|
2027
2145
|
return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
|
|
@@ -2035,12 +2153,19 @@ var MetricsSchema = z12.object({
|
|
|
2035
2153
|
/**
|
|
2036
2154
|
* Finished turns that changed nothing on the board. Reflections are left out, since they write
|
|
2037
2155
|
* memory, not the board; turns without a transcript are counted apart, their actions unknown.
|
|
2156
|
+
* Heartbeats are split by what each woke for, and those recorded before the board kept it are a
|
|
2157
|
+
* row without one.
|
|
2038
2158
|
*/
|
|
2039
2159
|
idle: z12.object({
|
|
2040
2160
|
turns: CountSchema,
|
|
2041
2161
|
idle: CountSchema,
|
|
2042
2162
|
unknown: CountSchema,
|
|
2043
|
-
byTrigger: z12.array(z12.object({
|
|
2163
|
+
byTrigger: z12.array(z12.object({
|
|
2164
|
+
trigger: z12.string(),
|
|
2165
|
+
heartbeat: HeartbeatCauseSchema.optional(),
|
|
2166
|
+
turns: CountSchema,
|
|
2167
|
+
idle: CountSchema
|
|
2168
|
+
})),
|
|
2044
2169
|
byAgent: z12.array(z12.object({
|
|
2045
2170
|
agent: NameSchema,
|
|
2046
2171
|
role: NameSchema.nullable(),
|
|
@@ -2175,7 +2300,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2175
2300
|
});
|
|
2176
2301
|
|
|
2177
2302
|
// packages/shared/dist/version.js
|
|
2178
|
-
var STELLARIS_VERSION = "0.
|
|
2303
|
+
var STELLARIS_VERSION = "0.4.1";
|
|
2179
2304
|
|
|
2180
2305
|
// packages/shared/dist/mentions.js
|
|
2181
2306
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2936,7 +3061,8 @@ var TurnPayload = z14.object({
|
|
|
2936
3061
|
thread: z14.string().optional(),
|
|
2937
3062
|
channel: z14.string().optional(),
|
|
2938
3063
|
place: z14.string().optional(),
|
|
2939
|
-
trigger: z14.string()
|
|
3064
|
+
trigger: z14.string(),
|
|
3065
|
+
heartbeat: HeartbeatCauseSchema.optional().catch(void 0)
|
|
2940
3066
|
});
|
|
2941
3067
|
var TaskPayload = z14.object({
|
|
2942
3068
|
taskId: z14.string(),
|
|
@@ -3036,13 +3162,12 @@ function computeMetrics(input) {
|
|
|
3036
3162
|
const nothing = actions === 0 ? 1 : 0;
|
|
3037
3163
|
turns += 1;
|
|
3038
3164
|
idle += nothing;
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
}
|
|
3165
|
+
const { trigger, heartbeat } = turn2.data;
|
|
3166
|
+
const key = heartbeat === void 0 ? trigger : `${trigger}\0${heartbeat}`;
|
|
3167
|
+
const row = turnRows.get(key) ?? { trigger, heartbeat, turns: 0, idle: 0 };
|
|
3168
|
+
turnRows.set(key, { ...row, turns: row.turns + 1, idle: row.idle + nothing });
|
|
3169
|
+
const own = agentRows.get(event.actor) ?? { turns: 0, idle: 0 };
|
|
3170
|
+
agentRows.set(event.actor, { turns: own.turns + 1, idle: own.idle + nothing });
|
|
3046
3171
|
break;
|
|
3047
3172
|
}
|
|
3048
3173
|
case "turn.started": {
|
|
@@ -3198,7 +3323,10 @@ function computeMetrics(input) {
|
|
|
3198
3323
|
turns,
|
|
3199
3324
|
idle,
|
|
3200
3325
|
unknown,
|
|
3201
|
-
byTrigger: [...turnRows].map((
|
|
3326
|
+
byTrigger: [...turnRows.values()].map(({ heartbeat, ...row }) => ({
|
|
3327
|
+
...row,
|
|
3328
|
+
...heartbeat === void 0 ? {} : { heartbeat }
|
|
3329
|
+
})).toSorted((a, b) => b.idle - a.idle || b.turns - a.turns),
|
|
3202
3330
|
byAgent: [...agentRows].map(([agent2, row]) => ({ agent: agent2, role: citizens.get(agent2)?.role ?? null, ...row })).toSorted((a, b) => b.idle - a.idle || b.turns - a.turns)
|
|
3203
3331
|
},
|
|
3204
3332
|
sentBack: {
|
|
@@ -3258,6 +3386,8 @@ var TurnHistoryPayloadSchema = z15.object({
|
|
|
3258
3386
|
channel: NameSchema.optional(),
|
|
3259
3387
|
place: NameSchema.optional(),
|
|
3260
3388
|
trigger: z15.string().default("manual"),
|
|
3389
|
+
// One a later board added reads as none, so the turn stays in the history.
|
|
3390
|
+
heartbeat: HeartbeatCauseSchema.optional().catch(void 0),
|
|
3261
3391
|
exitReason: z15.string().nullable().default(null),
|
|
3262
3392
|
costUsd: z15.number().default(0),
|
|
3263
3393
|
usage: UsageSchema.optional(),
|
|
@@ -3683,7 +3813,10 @@ var SignInsSchema = z16.object({
|
|
|
3683
3813
|
expiresAt: IsoDateTimeSchema
|
|
3684
3814
|
})).default([])
|
|
3685
3815
|
});
|
|
3686
|
-
var TurnStartPayloadSchema = z16.object({
|
|
3816
|
+
var TurnStartPayloadSchema = z16.object({
|
|
3817
|
+
turnId: UlidSchema,
|
|
3818
|
+
heartbeat: TurnHistoryPayloadSchema.shape.heartbeat
|
|
3819
|
+
});
|
|
3687
3820
|
var CursorsSchema = z16.object({
|
|
3688
3821
|
digest: z16.string().nullable(),
|
|
3689
3822
|
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
@@ -3755,6 +3888,9 @@ function skillFolderOf(proposal2) {
|
|
|
3755
3888
|
function proposedFrom(origin, where) {
|
|
3756
3889
|
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
3757
3890
|
}
|
|
3891
|
+
function latestLease(a, b) {
|
|
3892
|
+
return a === null ? b : b === null || a >= b ? a : b;
|
|
3893
|
+
}
|
|
3758
3894
|
function megabytes(bytes) {
|
|
3759
3895
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
3760
3896
|
}
|
|
@@ -3780,8 +3916,10 @@ function plain(value) {
|
|
|
3780
3916
|
return "";
|
|
3781
3917
|
return JSON.stringify(value);
|
|
3782
3918
|
}
|
|
3783
|
-
|
|
3784
|
-
|
|
3919
|
+
var SENTENCE_END = /[.…?!]["'”’»)\]}*_]*$/u;
|
|
3920
|
+
function asSentence(text) {
|
|
3921
|
+
const trimmed = text.trim();
|
|
3922
|
+
return SENTENCE_END.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
3785
3923
|
}
|
|
3786
3924
|
function skillSummary(data, body) {
|
|
3787
3925
|
const description = data["description"];
|
|
@@ -3894,6 +4032,7 @@ var Board = class _Board {
|
|
|
3894
4032
|
/** The turns in flight, which the server hosting them supplies; none until it does. */
|
|
3895
4033
|
turnsInFlight = () => [];
|
|
3896
4034
|
imageSource = null;
|
|
4035
|
+
imageSink = null;
|
|
3897
4036
|
newId = monotonicFactory();
|
|
3898
4037
|
events;
|
|
3899
4038
|
actionCounts = /* @__PURE__ */ new Map();
|
|
@@ -3944,10 +4083,13 @@ var Board = class _Board {
|
|
|
3944
4083
|
/**
|
|
3945
4084
|
* Applies each one-time alignment an existing society has not had yet, and remembers it, so a
|
|
3946
4085
|
* member that later chooses otherwise keeps its choice. A new alignment is a name and a method in
|
|
3947
|
-
* this list; once every board has applied it, both are deleted.
|
|
4086
|
+
* this list; once every board has applied it, both are deleted.
|
|
3948
4087
|
*/
|
|
3949
4088
|
async applyAlignments() {
|
|
3950
|
-
const alignments = [
|
|
4089
|
+
const alignments = [
|
|
4090
|
+
// Whoever may show an image in a post may open one a post shows.
|
|
4091
|
+
{ name: "image-reads-granted", apply: () => this.grantBeside("upload_image", "read_image") }
|
|
4092
|
+
];
|
|
3951
4093
|
const file = path8.join(this.paths.state(), "alignments.json");
|
|
3952
4094
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3953
4095
|
const pending = alignments.filter(({ name }) => !applied.includes(name));
|
|
@@ -3959,6 +4101,17 @@ var Board = class _Board {
|
|
|
3959
4101
|
}
|
|
3960
4102
|
await writeJson(file, { applied: [...applied, ...pending.map(({ name }) => name)] });
|
|
3961
4103
|
}
|
|
4104
|
+
/**
|
|
4105
|
+
* Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
|
|
4106
|
+
* seed roles have it from their seed.
|
|
4107
|
+
*/
|
|
4108
|
+
async grantBeside(beside, verb) {
|
|
4109
|
+
for (const charter of await this.listRoles()) {
|
|
4110
|
+
if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
|
|
4111
|
+
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
3962
4115
|
/** Writes every missing seed charter and aligns the existing ones with their seed. */
|
|
3963
4116
|
async ensureSeedRoles() {
|
|
3964
4117
|
await ensureDir(this.paths.roles());
|
|
@@ -4726,6 +4879,46 @@ ${DASHBOARD_LINE}
|
|
|
4726
4879
|
return next;
|
|
4727
4880
|
});
|
|
4728
4881
|
}
|
|
4882
|
+
/**
|
|
4883
|
+
* Pauses or resumes a runner, the user's alone: the whole runner, or with `cli` that CLI on it
|
|
4884
|
+
* alone, each apart from the other. While either holds, no turn it covers starts there; their
|
|
4885
|
+
* wakes wait in the scheduler's queue, and a turn under way finishes. Pausing what is paused, or
|
|
4886
|
+
* resuming what is not, changes nothing and logs nothing.
|
|
4887
|
+
*/
|
|
4888
|
+
async setRunnerPaused(actor, name, change) {
|
|
4889
|
+
this.assertUser(actor, "only the user may pause or resume a runner");
|
|
4890
|
+
const cli = change.cli === void 0 ? void 0 : CliKindSchema.parse(change.cli);
|
|
4891
|
+
return this.mutex.run(async () => {
|
|
4892
|
+
const file = this.paths.runner(name);
|
|
4893
|
+
if (!await exists(file)) {
|
|
4894
|
+
throw new BoardError("NOT_FOUND", `runner ${name} not found`);
|
|
4895
|
+
}
|
|
4896
|
+
const doc = await readMarkdown(file, RunnerSchema);
|
|
4897
|
+
const { paused, pausedClis, ...rest } = doc.data;
|
|
4898
|
+
if (cli !== void 0 && change.paused && !doc.data.clis.includes(cli)) {
|
|
4899
|
+
throw new BoardError("INVALID_STATE", `runner ${name} has no ${cli} to pause`);
|
|
4900
|
+
}
|
|
4901
|
+
const current = cli === void 0 ? paused : pausedClis?.[cli];
|
|
4902
|
+
if (current !== void 0 === change.paused) {
|
|
4903
|
+
return doc.data;
|
|
4904
|
+
}
|
|
4905
|
+
const pause = change.paused ? { at: this.now().toISOString(), by: actor.name } : void 0;
|
|
4906
|
+
const clis = cli === void 0 ? { ...pausedClis } : { ...pausedClis, [cli]: pause };
|
|
4907
|
+
const kept = Object.entries(clis).filter(([, each]) => each !== void 0);
|
|
4908
|
+
const whole = cli === void 0 ? pause : paused;
|
|
4909
|
+
const next = RunnerSchema.parse({
|
|
4910
|
+
...rest,
|
|
4911
|
+
...whole === void 0 ? {} : { paused: whole },
|
|
4912
|
+
...kept.length === 0 ? {} : { pausedClis: Object.fromEntries(kept) }
|
|
4913
|
+
});
|
|
4914
|
+
await writeMarkdown(file, next, doc.body);
|
|
4915
|
+
await this.events.append(change.paused ? "runner.paused" : "runner.resumed", actor.name, {
|
|
4916
|
+
name,
|
|
4917
|
+
...cli === void 0 ? {} : { cli }
|
|
4918
|
+
});
|
|
4919
|
+
return next;
|
|
4920
|
+
});
|
|
4921
|
+
}
|
|
4729
4922
|
/**
|
|
4730
4923
|
* Logs an operations signal as an `ops.signal` event. No channel carries it: the scheduler wakes
|
|
4731
4924
|
* its readers from the event, the runner lists it in their prompts, and the interface's log shows
|
|
@@ -4760,8 +4953,9 @@ ${DASHBOARD_LINE}
|
|
|
4760
4953
|
}
|
|
4761
4954
|
if (event.type === "turn.started") {
|
|
4762
4955
|
const start = TurnStartPayloadSchema.safeParse(event.payload);
|
|
4763
|
-
if (start.success)
|
|
4764
|
-
starts.set(start.data.turnId, event.ts);
|
|
4956
|
+
if (start.success) {
|
|
4957
|
+
starts.set(start.data.turnId, { ts: event.ts, heartbeat: start.data.heartbeat });
|
|
4958
|
+
}
|
|
4765
4959
|
continue;
|
|
4766
4960
|
}
|
|
4767
4961
|
if (event.type !== "turn.completed" && event.type !== "turn.failed") {
|
|
@@ -4769,17 +4963,19 @@ ${DASHBOARD_LINE}
|
|
|
4769
4963
|
}
|
|
4770
4964
|
const parsed = TurnHistoryPayloadSchema.safeParse(event.payload);
|
|
4771
4965
|
if (parsed.success) {
|
|
4772
|
-
const {
|
|
4773
|
-
const
|
|
4774
|
-
if (turnId !== void 0) {
|
|
4775
|
-
starts.delete(turnId);
|
|
4966
|
+
const { heartbeat: ended, ...turn2 } = parsed.data;
|
|
4967
|
+
const start = turn2.turnId === void 0 ? void 0 : starts.get(turn2.turnId);
|
|
4968
|
+
if (turn2.turnId !== void 0) {
|
|
4969
|
+
starts.delete(turn2.turnId);
|
|
4776
4970
|
}
|
|
4971
|
+
const heartbeat = ended ?? start?.heartbeat;
|
|
4777
4972
|
entries.push({
|
|
4778
4973
|
id: event.id,
|
|
4779
4974
|
ts: event.ts,
|
|
4780
4975
|
outcome: event.type === "turn.failed" ? "failed" : "completed",
|
|
4781
|
-
...
|
|
4782
|
-
...
|
|
4976
|
+
...turn2,
|
|
4977
|
+
...heartbeat === void 0 ? {} : { heartbeat },
|
|
4978
|
+
...start === void 0 ? {} : { startedAt: start.ts }
|
|
4783
4979
|
});
|
|
4784
4980
|
}
|
|
4785
4981
|
}
|
|
@@ -4809,14 +5005,31 @@ ${DASHBOARD_LINE}
|
|
|
4809
5005
|
return society;
|
|
4810
5006
|
});
|
|
4811
5007
|
}
|
|
4812
|
-
/**
|
|
5008
|
+
/**
|
|
5009
|
+
* Releases every claim whose lease has expired, but for a holder's that its own cron in the
|
|
5010
|
+
* task's thread keeps (`keptLease`). Called by the scheduler on its tick.
|
|
5011
|
+
*/
|
|
4813
5012
|
async expireLeases() {
|
|
4814
5013
|
return this.mutex.run(async () => {
|
|
4815
5014
|
const expired = [];
|
|
4816
5015
|
const now = this.now();
|
|
5016
|
+
let crons;
|
|
5017
|
+
let runners;
|
|
4817
5018
|
for (const project2 of await listDirs(this.paths.projects())) {
|
|
5019
|
+
let place;
|
|
4818
5020
|
for (const task2 of await this.listTasks(project2)) {
|
|
4819
|
-
if (task2.status
|
|
5021
|
+
if (task2.status !== "claimed") {
|
|
5022
|
+
continue;
|
|
5023
|
+
}
|
|
5024
|
+
crons ??= await this.listCrons();
|
|
5025
|
+
runners ??= await this.listRunners();
|
|
5026
|
+
place ??= await this.readProject(project2);
|
|
5027
|
+
const kept = latestLease(this.keptLease(task2, crons, now), await this.pausedLease(task2, runners, place.runner, now));
|
|
5028
|
+
if (kept !== null) {
|
|
5029
|
+
await this.writeTask(project2, { ...task2, leaseExpiresAt: kept });
|
|
5030
|
+
continue;
|
|
5031
|
+
}
|
|
5032
|
+
if (this.leaseExpired(task2, now)) {
|
|
4820
5033
|
const released = await this.writeTask(project2, {
|
|
4821
5034
|
...task2,
|
|
4822
5035
|
status: "open",
|
|
@@ -4885,20 +5098,24 @@ ${DASHBOARD_LINE}
|
|
|
4885
5098
|
* general channel, one of its other channels, one of its threads, or, in the society scope, a
|
|
4886
5099
|
* project's channels. The heartbeat asks, so that it wakes a member where its unread
|
|
4887
5100
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
4888
|
-
* scope; a project member reads them at its next turn.
|
|
5101
|
+
* scope; a project member reads them at its next turn. Some posts are in the digest but not
|
|
4889
5102
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
4890
|
-
* whoever it hands work to
|
|
4891
|
-
*
|
|
4892
|
-
* nothing
|
|
4893
|
-
*
|
|
5103
|
+
* whoever it hands work to; the board's own, operations signals and landing announcements, whose
|
|
5104
|
+
* signals that call for action wake their readers directly; and a knowledge topic's notice, which
|
|
5105
|
+
* asks nothing of anyone. In a task's thread a post counts only for whoever the current stage is
|
|
5106
|
+
* for, its holder or, while nobody holds it, a member who may take it, and for the task's creator
|
|
5107
|
+
* when it mentions the user, so the creator can put a wait on the user in; a post that asks the
|
|
5108
|
+
* reader counts wherever it is. A closed thread counts for nothing: its conversation has ended,
|
|
5109
|
+
* and a turn there could only read its posts and be refused a reply, while a wake queued before
|
|
5110
|
+
* it closed, as for a mention, still runs there.
|
|
4894
5111
|
*/
|
|
4895
5112
|
async unreadByConversation(actor) {
|
|
4896
5113
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4897
5114
|
const counts = /* @__PURE__ */ new Map();
|
|
4898
|
-
const
|
|
5115
|
+
const threadCounts = /* @__PURE__ */ new Map();
|
|
4899
5116
|
const unscoped = { name: actor.name, role: actor.role };
|
|
4900
|
-
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4901
|
-
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
5117
|
+
for (const { scope, part, message, news, asked } of await this.digestSince(unscoped, cursors, upto)) {
|
|
5118
|
+
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name || message.notice?.kind === "knowledge") {
|
|
4902
5119
|
continue;
|
|
4903
5120
|
}
|
|
4904
5121
|
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
@@ -4907,12 +5124,13 @@ ${DASHBOARD_LINE}
|
|
|
4907
5124
|
}
|
|
4908
5125
|
const thread2 = threadOfPart(part);
|
|
4909
5126
|
if (thread2 !== void 0) {
|
|
4910
|
-
let
|
|
4911
|
-
if (
|
|
4912
|
-
|
|
4913
|
-
|
|
5127
|
+
let counted = threadCounts.get(thread2);
|
|
5128
|
+
if (counted === void 0) {
|
|
5129
|
+
counted = await this.threadCount(actor, thread2);
|
|
5130
|
+
threadCounts.set(thread2, counted);
|
|
4914
5131
|
}
|
|
4915
|
-
|
|
5132
|
+
const forReader = counted === "every" || counted !== "none" && asked === true || counted === "asks-user" && message.mentions.includes(USER_NAME);
|
|
5133
|
+
if (!forReader) {
|
|
4916
5134
|
continue;
|
|
4917
5135
|
}
|
|
4918
5136
|
}
|
|
@@ -5065,7 +5283,7 @@ ${DASHBOARD_LINE}
|
|
|
5065
5283
|
include = participates;
|
|
5066
5284
|
}
|
|
5067
5285
|
if (include && message.author !== actor.name) {
|
|
5068
|
-
collected.push({ scope: filedIn, part, message });
|
|
5286
|
+
collected.push({ scope: filedIn, part, message, ...asked ? { asked: true } : {} });
|
|
5069
5287
|
}
|
|
5070
5288
|
}
|
|
5071
5289
|
return collected.toSorted(byId);
|
|
@@ -5285,7 +5503,7 @@ ${current.body}`);
|
|
|
5285
5503
|
name: args.name,
|
|
5286
5504
|
reason: args.reason
|
|
5287
5505
|
});
|
|
5288
|
-
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${args.name} removed from the society by ${actor.name}: ${
|
|
5506
|
+
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${args.name} removed from the society by ${actor.name}: ${asSentence(withoutMentions(args.reason))} Every citizen's skills index stops listing it from its next turn.`);
|
|
5289
5507
|
return removed;
|
|
5290
5508
|
});
|
|
5291
5509
|
}
|
|
@@ -5342,12 +5560,30 @@ ${current.body}`);
|
|
|
5342
5560
|
throw new BoardError("NOT_FOUND", "this board has no runners to read an image from");
|
|
5343
5561
|
}
|
|
5344
5562
|
const bytes = await this.imageSource(actor, args.path);
|
|
5563
|
+
return this.keepImage(actor, bytes, args.path, path8.basename(args.path));
|
|
5564
|
+
}
|
|
5565
|
+
/**
|
|
5566
|
+
* Keeps an image the user posts from a composer, as its bytes, under the same checks as an image
|
|
5567
|
+
* a turn uploads. `name`, the file's, describes it in the markdown answered; without one the
|
|
5568
|
+
* description is empty, for the user to write.
|
|
5569
|
+
*/
|
|
5570
|
+
async postImage(actor, bytes, name) {
|
|
5571
|
+
this.assertUser(actor, "only the user posts an image's bytes; a turn uploads one with upload_image");
|
|
5572
|
+
const given = name === void 0 ? "" : ImageNameSchema.parse(name);
|
|
5573
|
+
return this.keepImage(actor, bytes, given === "" ? "the image" : given, given);
|
|
5574
|
+
}
|
|
5575
|
+
/**
|
|
5576
|
+
* Keeps `bytes` under a new id when they are a PNG, JPEG, GIF, or WebP of at most
|
|
5577
|
+
* `IMAGE_LIMIT_BYTES`, and answers the markdown that shows them, described by `name`. `what`
|
|
5578
|
+
* names the image in a refusal.
|
|
5579
|
+
*/
|
|
5580
|
+
async keepImage(actor, bytes, what, name) {
|
|
5345
5581
|
const type = imageTypeOf(bytes);
|
|
5346
5582
|
if (type === null) {
|
|
5347
|
-
throw new BoardError("VALIDATION", `${
|
|
5583
|
+
throw new BoardError("VALIDATION", `${what} is not a PNG, JPEG, GIF, or WebP image by its first bytes`);
|
|
5348
5584
|
}
|
|
5349
5585
|
if (bytes.byteLength > IMAGE_LIMIT_BYTES) {
|
|
5350
|
-
throw new BoardError("VALIDATION", `${
|
|
5586
|
+
throw new BoardError("VALIDATION", `${what} is ${bytes.byteLength} bytes, over the ${IMAGE_LIMIT_BYTES} an image may be`);
|
|
5351
5587
|
}
|
|
5352
5588
|
return this.mutex.run(async () => {
|
|
5353
5589
|
const id = this.newId();
|
|
@@ -5357,15 +5593,37 @@ ${current.body}`);
|
|
|
5357
5593
|
type,
|
|
5358
5594
|
size: bytes.byteLength
|
|
5359
5595
|
});
|
|
5360
|
-
const
|
|
5596
|
+
const description = imageDescription(name);
|
|
5361
5597
|
return {
|
|
5362
5598
|
id,
|
|
5363
5599
|
type,
|
|
5364
5600
|
size: bytes.byteLength,
|
|
5365
|
-
markdown: ``
|
|
5366
5602
|
};
|
|
5367
5603
|
});
|
|
5368
5604
|
}
|
|
5605
|
+
/**
|
|
5606
|
+
* Opens an image the board keeps for the calling turn, by its id or its `image:<id>` address:
|
|
5607
|
+
* the runner the turn runs on writes the bytes to a file in a folder of the turn's own in the
|
|
5608
|
+
* system temp folder, removed when the turn ends, and the board answers its path, for the
|
|
5609
|
+
* citizen to open with its own file tools, since a post reaches a turn as markdown and an image
|
|
5610
|
+
* in it as its address. Any image the board keeps may be opened: every post, brief, topic, and
|
|
5611
|
+
* dashboard that could show one is in the projection every turn reads.
|
|
5612
|
+
*/
|
|
5613
|
+
async openImage(actor, input) {
|
|
5614
|
+
const args = VerbInputs.read_image.parse(input);
|
|
5615
|
+
await this.authorize(actor, "read_image");
|
|
5616
|
+
if (actor.scope === void 0) {
|
|
5617
|
+
throw new BoardError("INVALID_STATE", "read_image writes the image on the machine a turn runs on, so only a turn can call it");
|
|
5618
|
+
}
|
|
5619
|
+
if (this.imageSink === null) {
|
|
5620
|
+
throw new BoardError("NOT_FOUND", "this board has no runners to write an image to");
|
|
5621
|
+
}
|
|
5622
|
+
const id = imageIdOf(args.image_id);
|
|
5623
|
+
const { type, bytes } = await this.readImage(id);
|
|
5624
|
+
const file = await this.imageSink(actor, { id, type, bytes });
|
|
5625
|
+
return { id, type, size: bytes.byteLength, path: file };
|
|
5626
|
+
}
|
|
5369
5627
|
/** An uploaded image's bytes and kind, which its first bytes tell. */
|
|
5370
5628
|
async readImage(id) {
|
|
5371
5629
|
const file = this.paths.image(UlidSchema.parse(id));
|
|
@@ -5711,6 +5969,13 @@ ${current.body}`);
|
|
|
5711
5969
|
readImagesWith(source) {
|
|
5712
5970
|
this.imageSource = source;
|
|
5713
5971
|
}
|
|
5972
|
+
/**
|
|
5973
|
+
* Where `read_image` writes an image to: the runner the calling turn runs on, through the server
|
|
5974
|
+
* that hosts the runners. Without one, as in the admin CLI, nothing can be opened.
|
|
5975
|
+
*/
|
|
5976
|
+
openImagesWith(sink) {
|
|
5977
|
+
this.imageSink = sink;
|
|
5978
|
+
}
|
|
5714
5979
|
/**
|
|
5715
5980
|
* Refuses to end conversations while another citizen's turn is in one of them: `cut` picks the
|
|
5716
5981
|
* turns that would lose theirs. The caller's own turn is exempt, since ending a conversation
|
|
@@ -6308,7 +6573,7 @@ ${args.summary}`);
|
|
|
6308
6573
|
});
|
|
6309
6574
|
const thread2 = await this.openThreadUnlocked(actor.name, this.proposalThreadOpening(proposal2));
|
|
6310
6575
|
const rationale = args.rationale.trim();
|
|
6311
|
-
await this.appendThreadMessage(actor.name, thread2.id, `Proposal ${proposal2.id}: ${describeCharter(proposal2.kind, proposal2.charter)}
|
|
6576
|
+
await this.appendThreadMessage(actor.name, thread2.id, `Proposal ${proposal2.id}: ${asSentence(describeCharter(proposal2.kind, proposal2.charter))}${rationale.length === 0 ? "" : `
|
|
6312
6577
|
|
|
6313
6578
|
${rationale}`}`);
|
|
6314
6579
|
return proposal2;
|
|
@@ -6546,6 +6811,8 @@ ${rationale}`}`);
|
|
|
6546
6811
|
return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
|
|
6547
6812
|
case "upload_image":
|
|
6548
6813
|
return this.uploadImage(actor, VerbInputs.upload_image.parse(input));
|
|
6814
|
+
case "read_image":
|
|
6815
|
+
return this.openImage(actor, VerbInputs.read_image.parse(input));
|
|
6549
6816
|
default:
|
|
6550
6817
|
throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
|
|
6551
6818
|
}
|
|
@@ -6781,6 +7048,7 @@ ${rationale}`}`);
|
|
|
6781
7048
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
6782
7049
|
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6783
7050
|
trigger: parsed.trigger.kind,
|
|
7051
|
+
...parsed.trigger.heartbeat === void 0 ? {} : { heartbeat: parsed.trigger.heartbeat },
|
|
6784
7052
|
session: parsed.session,
|
|
6785
7053
|
runner: parsed.runner
|
|
6786
7054
|
});
|
|
@@ -6803,6 +7071,11 @@ ${rationale}`}`);
|
|
|
6803
7071
|
for (const id of parsed.crons ?? []) {
|
|
6804
7072
|
await this.recordCronRun(id, parsed, failed);
|
|
6805
7073
|
}
|
|
7074
|
+
if (parsed.exitReason === "completed" || parsed.exitReason === "blocked") {
|
|
7075
|
+
for (const shown of parsed.cronFailures ?? []) {
|
|
7076
|
+
await this.catchUpCron(shown, parsed);
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
6806
7079
|
if (parsed.exitReason === "stopped") {
|
|
6807
7080
|
await this.events.append("turn.stopped", parsed.stoppedBy ?? USER_NAME, {
|
|
6808
7081
|
...parsed.id === void 0 ? {} : { turnId: parsed.id },
|
|
@@ -6820,6 +7093,7 @@ ${rationale}`}`);
|
|
|
6820
7093
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
6821
7094
|
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6822
7095
|
trigger: parsed.trigger.kind,
|
|
7096
|
+
...parsed.trigger.heartbeat === void 0 ? {} : { heartbeat: parsed.trigger.heartbeat },
|
|
6823
7097
|
session: parsed.session,
|
|
6824
7098
|
exitReason: parsed.exitReason,
|
|
6825
7099
|
costUsd: parsed.costUsd,
|
|
@@ -6943,6 +7217,27 @@ ${rationale}`}`);
|
|
|
6943
7217
|
failures: outcome === "failed" ? cron2.failures + 1 : outcome === "completed" ? 0 : cron2.failures
|
|
6944
7218
|
});
|
|
6945
7219
|
}
|
|
7220
|
+
/**
|
|
7221
|
+
* Marks a cron's failed fire caught up by a completed turn whose prompt named it, while that
|
|
7222
|
+
* failure is still its latest run. Its schedule and its run of failures stay as they are.
|
|
7223
|
+
*/
|
|
7224
|
+
async catchUpCron(shown, record) {
|
|
7225
|
+
if (record.id === void 0 || !await exists(this.paths.cron(shown.cron))) {
|
|
7226
|
+
return;
|
|
7227
|
+
}
|
|
7228
|
+
const cron2 = await this.readCron(shown.cron);
|
|
7229
|
+
const failure = cronFailure(cron2);
|
|
7230
|
+
if (failure === null || failure.turnId !== shown.turnId) {
|
|
7231
|
+
return;
|
|
7232
|
+
}
|
|
7233
|
+
await this.writeCron({
|
|
7234
|
+
...cron2,
|
|
7235
|
+
lastRun: {
|
|
7236
|
+
...failure,
|
|
7237
|
+
caughtUp: { turnId: record.id, at: record.endedAt ?? this.now().toISOString() }
|
|
7238
|
+
}
|
|
7239
|
+
});
|
|
7240
|
+
}
|
|
6946
7241
|
/**
|
|
6947
7242
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
6948
7243
|
* (for one from before tasks had threads), a channel archived, or a project archived, which ends
|
|
@@ -6982,7 +7277,7 @@ ${rationale}`}`);
|
|
|
6982
7277
|
const ts = this.now().toISOString();
|
|
6983
7278
|
const landed = current.onDone !== "none";
|
|
6984
7279
|
if (landed) {
|
|
6985
|
-
await this.postTaskNote(actor.name, current, input.ok ?
|
|
7280
|
+
await this.postTaskNote(actor.name, current, input.ok ? asSentence(input.detail) : `Landing failed: ${asSentence(input.detail)} The task waits at its last stage for its participants to reshape the plan.`, input.ok ? { action: "landed", stage: current.stage, to: null } : { action: "reopened", stage: current.stage, to: current.stage });
|
|
6986
7281
|
}
|
|
6987
7282
|
if (input.ok) {
|
|
6988
7283
|
const task3 = await this.writeEndedTask(actor.name, location.project, {
|
|
@@ -7002,7 +7297,7 @@ ${rationale}`}`);
|
|
|
7002
7297
|
await this.refreshMember(name);
|
|
7003
7298
|
}
|
|
7004
7299
|
if (landed) {
|
|
7005
|
-
await this.appendMessage(actor.name, channelRef(location.project, task3.channel), `Task ${task3.id} "${task3.title}" is done: ${
|
|
7300
|
+
await this.appendMessage(actor.name, channelRef(location.project, task3.channel), `Task ${task3.id} "${task3.title}" is done: ${asSentence(input.detail)}`);
|
|
7006
7301
|
}
|
|
7007
7302
|
return task3;
|
|
7008
7303
|
}
|
|
@@ -7713,7 +8008,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7713
8008
|
files: taken.files.length,
|
|
7714
8009
|
...meta
|
|
7715
8010
|
});
|
|
7716
|
-
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${
|
|
8011
|
+
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${asSentence(skill2.summary)} Every citizen's skills index now lists it.`);
|
|
7717
8012
|
return replaced;
|
|
7718
8013
|
}
|
|
7719
8014
|
/**
|
|
@@ -7960,7 +8255,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7960
8255
|
});
|
|
7961
8256
|
const purpose = withoutMentions(input.purpose);
|
|
7962
8257
|
await this.appendMessage(SYSTEM_ACTOR.name, ref, `Channel ${ref} opened: ${purpose}`);
|
|
7963
|
-
await this.appendMessage(SYSTEM_ACTOR.name, channelRef(input.project, "general"), `Channel ${ref} opened by ${by}: ${purpose}
|
|
8258
|
+
await this.appendMessage(SYSTEM_ACTOR.name, channelRef(input.project, "general"), `Channel ${ref} opened by ${by}: ${asSentence(purpose)} Subscribe to it to follow its posts; its tasks and mentions reach whoever they concern either way.`);
|
|
7964
8259
|
return ref;
|
|
7965
8260
|
}
|
|
7966
8261
|
/**
|
|
@@ -8312,7 +8607,7 @@ ${charter.purpose}
|
|
|
8312
8607
|
}
|
|
8313
8608
|
assertMayGate(actor, what) {
|
|
8314
8609
|
if (!PLANNING_ROLES.includes(actor.role)) {
|
|
8315
|
-
throw new BoardError("FORBIDDEN", `only the user, the steward, and the concierge may ${what}; ask one of them in the task's thread`);
|
|
8610
|
+
throw new BoardError("FORBIDDEN", `only the user, the steward, and the concierge may ${what}; ask one of them in the task's thread, with a mention`);
|
|
8316
8611
|
}
|
|
8317
8612
|
}
|
|
8318
8613
|
/** The user holds anything; everyone else must be a project member the current stage admits. */
|
|
@@ -8354,6 +8649,52 @@ ${charter.purpose}
|
|
|
8354
8649
|
leaseEnd(now) {
|
|
8355
8650
|
return new Date(now.getTime() + this.leaseMs).toISOString();
|
|
8356
8651
|
}
|
|
8652
|
+
/**
|
|
8653
|
+
* The lease a holder's own crons in the task's thread keep its claim to, when later than the one
|
|
8654
|
+
* it has, else null. While one of them is pending, the lease is renewed for a full lease once less
|
|
8655
|
+
* than half is left, so it never lapses between fires that may be hours apart; once every one is
|
|
8656
|
+
* paused, ended, or failed at its latest fire with nothing catching it up (`cronStoppedAt`), it
|
|
8657
|
+
* runs a full lease from when the last of them stopped.
|
|
8658
|
+
*/
|
|
8659
|
+
keptLease(task2, crons, now) {
|
|
8660
|
+
if (task2.leaseExpiresAt === void 0) {
|
|
8661
|
+
return null;
|
|
8662
|
+
}
|
|
8663
|
+
const lease = Date.parse(task2.leaseExpiresAt);
|
|
8664
|
+
let pending = false;
|
|
8665
|
+
let stopped = null;
|
|
8666
|
+
for (const cron2 of crons) {
|
|
8667
|
+
if (cron2.agent !== task2.claimedBy || !firesInTask(cron2, task2)) {
|
|
8668
|
+
continue;
|
|
8669
|
+
}
|
|
8670
|
+
const at = cronStoppedAt(cron2);
|
|
8671
|
+
if (at === null) {
|
|
8672
|
+
pending = true;
|
|
8673
|
+
} else {
|
|
8674
|
+
stopped = Math.max(stopped ?? 0, Date.parse(at));
|
|
8675
|
+
}
|
|
8676
|
+
}
|
|
8677
|
+
const kept = pending ? lease - now.getTime() < this.leaseMs / 2 ? now.getTime() + this.leaseMs : lease : stopped === null ? lease : stopped + this.leaseMs;
|
|
8678
|
+
return kept > lease ? new Date(kept).toISOString() : null;
|
|
8679
|
+
}
|
|
8680
|
+
/**
|
|
8681
|
+
* The lease a holder keeps while the user's pause, or its CLI's limit, holds its turns in the
|
|
8682
|
+
* task's project (`heldOn`), when later than the one it has, else null. It is renewed for a full
|
|
8683
|
+
* lease once less than half is left, as for a pending cron, so it never lapses while the holder
|
|
8684
|
+
* cannot take a turn there, and after the resume or the reset the holder has at least half a
|
|
8685
|
+
* lease to come back to it.
|
|
8686
|
+
*/
|
|
8687
|
+
async pausedLease(task2, runners, runner2, now) {
|
|
8688
|
+
if (task2.claimedBy === void 0 || task2.leaseExpiresAt === void 0) {
|
|
8689
|
+
return null;
|
|
8690
|
+
}
|
|
8691
|
+
const holder = await this.readAgent(task2.claimedBy).catch(() => null);
|
|
8692
|
+
if (holder === null || !heldOn(runners, runner2, holder.cli, now)) {
|
|
8693
|
+
return null;
|
|
8694
|
+
}
|
|
8695
|
+
const lease = Date.parse(task2.leaseExpiresAt);
|
|
8696
|
+
return lease - now.getTime() < this.leaseMs / 2 ? this.leaseEnd(now) : null;
|
|
8697
|
+
}
|
|
8357
8698
|
leaseExpired(task2, now) {
|
|
8358
8699
|
return task2.leaseExpiresAt !== void 0 && new Date(task2.leaseExpiresAt).getTime() <= now.getTime();
|
|
8359
8700
|
}
|
|
@@ -8439,9 +8780,9 @@ ${charter.purpose}
|
|
|
8439
8780
|
});
|
|
8440
8781
|
}
|
|
8441
8782
|
const verdict = outcome === "approved" ? "Approved" : "Rejected";
|
|
8442
|
-
const why = reason === void 0 ? "" : ` Reason: ${reason}
|
|
8783
|
+
const why = reason === void 0 ? "" : ` Reason: ${asSentence(reason)}`;
|
|
8443
8784
|
const result = provision === void 0 ? "" : ` Provisioned: ${Object.entries(provision).map(([key, value]) => `${key} ${plain(value)}`).join(", ")}.`;
|
|
8444
|
-
const post = `${verdict}: ${describeCharter(proposal2.kind, proposal2.charter)}
|
|
8785
|
+
const post = `${verdict}: ${asSentence(describeCharter(proposal2.kind, proposal2.charter))}${why}${result}`;
|
|
8445
8786
|
await this.appendThreadMessage(actor.name, thread2.id, actor.name === SYSTEM_ACTOR.name ? withoutMentions(post) : post, { step: { action: outcome } });
|
|
8446
8787
|
await this.endThreadOf(proposalId, actor.name, outcome);
|
|
8447
8788
|
return decision;
|
|
@@ -8466,11 +8807,7 @@ ${charter.purpose}
|
|
|
8466
8807
|
if (task2.claimedBy === actor.name || task2.createdBy === actor.name || task2.stages.some((stage) => stage.agent === actor.name || stage.holders.includes(actor.name))) {
|
|
8467
8808
|
return true;
|
|
8468
8809
|
}
|
|
8469
|
-
|
|
8470
|
-
return false;
|
|
8471
|
-
}
|
|
8472
|
-
const agent2 = await this.readAgent(actor.name);
|
|
8473
|
-
return agent2.memberships.includes(task2.project) && mayHoldStage(actor, task2);
|
|
8810
|
+
return await this.mayTakeStage(actor, task2);
|
|
8474
8811
|
}
|
|
8475
8812
|
if (subject?.kind === "proposal") {
|
|
8476
8813
|
const proposal2 = await this.readProposal(subject.id);
|
|
@@ -8484,6 +8821,34 @@ ${charter.purpose}
|
|
|
8484
8821
|
}
|
|
8485
8822
|
return false;
|
|
8486
8823
|
}
|
|
8824
|
+
/** Whether a task's current stage waits for a holder and the member may take it. */
|
|
8825
|
+
async mayTakeStage(actor, task2) {
|
|
8826
|
+
if (task2.status !== "open" || task2.completing) {
|
|
8827
|
+
return false;
|
|
8828
|
+
}
|
|
8829
|
+
const agent2 = await this.readAgent(actor.name);
|
|
8830
|
+
return agent2.memberships.includes(task2.project) && mayHoldStage(actor, task2);
|
|
8831
|
+
}
|
|
8832
|
+
/**
|
|
8833
|
+
* Which of a thread's posts count toward a reader's heartbeat (see `unreadByConversation`). A
|
|
8834
|
+
* thread, or a task, the board cannot find counts as an open topic's would.
|
|
8835
|
+
*/
|
|
8836
|
+
async threadCount(actor, id) {
|
|
8837
|
+
const found = await this.tryFindThread(id);
|
|
8838
|
+
if (found?.thread.state === "closed") {
|
|
8839
|
+
return "none";
|
|
8840
|
+
}
|
|
8841
|
+
const subject = found?.thread.subject;
|
|
8842
|
+
const task2 = subject?.kind === "task" ? await this.findTask(subject.id).then((location) => location.task, () => null) : null;
|
|
8843
|
+
if (task2 === null) {
|
|
8844
|
+
return "every";
|
|
8845
|
+
}
|
|
8846
|
+
const stageIsTheirs = task2.status === "claimed" ? task2.claimedBy === actor.name : await this.mayTakeStage(actor, task2);
|
|
8847
|
+
if (stageIsTheirs) {
|
|
8848
|
+
return "every";
|
|
8849
|
+
}
|
|
8850
|
+
return task2.createdBy === actor.name ? "asks-user" : "asked";
|
|
8851
|
+
}
|
|
8487
8852
|
async threadScopes() {
|
|
8488
8853
|
const projects = await listDirs(this.paths.projects());
|
|
8489
8854
|
return [this.paths.societyThreads(), ...projects.map((slug) => this.paths.threads(slug))];
|
|
@@ -8694,8 +9059,25 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
8694
9059
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
8695
9060
|
const board = await open3();
|
|
8696
9061
|
const runners = await board.listRunners();
|
|
8697
|
-
|
|
9062
|
+
const now = /* @__PURE__ */ new Date();
|
|
9063
|
+
print(runners, () => runners.length === 0 ? "No runners yet; add one with `runner add <name>`." : runners.map((each) => `${each.name} ${each.status}${each.paused === void 0 ? "" : " paused"} ${each.os}${each.version === void 0 ? "" : ` v${each.version}`} ${each.clis.join(",") || "no CLIs yet"}${each.capabilities.length === 0 ? "" : ` ${each.capabilities.join(",")}`}${Object.keys(each.pausedClis ?? {}).map((cli) => ` ${cli} paused`).join("")}${each.clis.map((cli) => {
|
|
9064
|
+
const hold = runnerLimitHold(each, cli, now);
|
|
9065
|
+
return hold === null ? "" : ` ${cli} held until ${hold.until}`;
|
|
9066
|
+
}).join("")}`).join("\n"));
|
|
8698
9067
|
});
|
|
9068
|
+
for (const [verb, paused] of [
|
|
9069
|
+
["pause", true],
|
|
9070
|
+
["resume", false]
|
|
9071
|
+
]) {
|
|
9072
|
+
runner.command(`${verb} <name> [cli]`).description(paused ? "Start no more turns on a runner, or with one of its CLIs, until resumed; turns in flight finish" : "Let a paused runner, or a paused CLI on it, take turns again").action(async (name, cli) => {
|
|
9073
|
+
const board = await open3();
|
|
9074
|
+
const updated = await board.setRunnerPaused(board.userActor(), name, {
|
|
9075
|
+
paused,
|
|
9076
|
+
...cli === void 0 ? {} : { cli: CliKindSchema.parse(cli) }
|
|
9077
|
+
});
|
|
9078
|
+
print(updated, () => `${cli === void 0 ? name : `${cli} on ${name}`} ${verb}d`);
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
8699
9081
|
var agent = program.command("agent").description("Manage agents");
|
|
8700
9082
|
agent.command("add <name>").description("Add an agent and print its board token once").requiredOption("--role <role>", "role charter name").option("--cli <cli>", "claude or codex").option("--model <model>", "model to run the CLI with; the CLI's own default otherwise").option("--runner <name>", "runner preferred for its turns; any with its CLI otherwise").option("-p, --project <slug...>", "project memberships").action(async (name, opts) => {
|
|
8701
9083
|
const board = await open3();
|