@kubitnodes/stellaris 0.3.12 → 0.3.14
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 +1149 -535
- package/dist/server/index.js +1426 -593
- package/package.json +2 -1
- package/web/dist/assets/{BackLink-BlJvN2RA.js → BackLink-giAdtos8.js} +1 -1
- package/web/dist/assets/{ChannelForms-CmFxBXNK.js → ChannelForms-b6eKogvP.js} +1 -1
- package/web/dist/assets/ChannelView-DXoJ5w3z.js +1 -0
- package/web/dist/assets/CitizenView-yOnQtNz4.js +2 -0
- package/web/dist/assets/{CitizensView-B8lVGSBD.js → CitizensView-Dbo47F82.js} +1 -1
- package/web/dist/assets/{CliIcon-CT5ljQDC.js → CliIcon-DxDvRRcX.js} +1 -1
- package/web/dist/assets/CronsView-DYx7dQcr.js +1 -0
- package/web/dist/assets/KnowledgeView-BXLj85OH.js +1 -0
- package/web/dist/assets/{Markdown-B5zEdYuO.js → Markdown-Dw04_ImA.js} +4 -4
- package/web/dist/assets/{MessageItem-DuxSZwn0.js → MessageItem-DnfMDxDB.js} +1 -1
- package/web/dist/assets/{MetricsView-D8D-y-sz.js → MetricsView-_h2TWeo8.js} +1 -1
- package/web/dist/assets/{NeedsYouView-Buwq8-_l.js → NeedsYouView-BI1-_bYI.js} +1 -1
- package/web/dist/assets/{Overview-CrQ8ybpq.js → Overview-BdZiHglw.js} +1 -1
- package/web/dist/assets/{Pane-CFoSKOo5.js → Pane-CODhA5zE.js} +1 -1
- package/web/dist/assets/ProjectView-CScK4F7F.js +1 -0
- package/web/dist/assets/ProposalView-BQ1rflqo.js +1 -0
- package/web/dist/assets/{ProposalsView-DjyZpZe0.js → ProposalsView-DSi2u6qO.js} +1 -1
- package/web/dist/assets/{RunnersView-2h5NWUuD.js → RunnersView-9E2qUSO5.js} +1 -1
- package/web/dist/assets/{SocietyView-BuaSb_3d.js → SocietyView-DEeUo4wb.js} +1 -1
- package/web/dist/assets/SubjectThread-DhGB5RPc.js +1 -0
- package/web/dist/assets/{TaskFileView-CeEzbSCL.js → TaskFileView-DQC_ZUdY.js} +2 -2
- package/web/dist/assets/TaskView-yVpUG83f.js +3 -0
- package/web/dist/assets/{TasksView-DCVU5oZn.js → TasksView-CJbJt5dM.js} +1 -1
- package/web/dist/assets/ThreadCard-DVAx3gGc.js +1 -0
- package/web/dist/assets/{ThreadForms-CTBaAJ57.js → ThreadForms-RnrV7V5X.js} +1 -1
- package/web/dist/assets/{ThreadView-zg8KqF3t.js → ThreadView-CMwiVZFq.js} +1 -1
- package/web/dist/assets/{citizen-CImM6WB6.js → citizen-aLkZ4owL.js} +1 -1
- package/web/dist/assets/{compose-BG_-Tp87.js → compose-D7DOz6kC.js} +1 -1
- package/web/dist/assets/format-BORdp4w-.js +55 -0
- package/web/dist/assets/{governance-cDTg2_iq.js → governance-WrDQawLN.js} +1 -1
- package/web/dist/assets/index-7uInG5D9.js +23 -0
- package/web/dist/assets/index-D02Wgo_C.css +2 -0
- package/web/dist/assets/{link-CFdt17Sp.js → link-1v5lugnf.js} +1 -1
- package/web/dist/assets/{tasks-C10zwkED.js → tasks-C-9dAYQV.js} +1 -1
- package/web/dist/assets/useMatch-CyeFfbcR.js +1 -0
- package/web/dist/assets/{useStickyScroll-BjqdEt_f.js → useStickyScroll-CE7dyz0Q.js} +1 -1
- package/web/dist/index.html +9 -9
- package/web/dist/assets/ChannelView-4s--IwKC.js +0 -1
- package/web/dist/assets/CitizenView-B5-LVVCG.js +0 -2
- package/web/dist/assets/KnowledgeView-DbI8DIZb.js +0 -1
- package/web/dist/assets/ProjectView-BBQg-ZxC.js +0 -1
- package/web/dist/assets/ProposalView-BjPHVxj4.js +0 -1
- package/web/dist/assets/SubjectThread-C1mHvHMI.js +0 -1
- package/web/dist/assets/TaskView-yL9hCM7G.js +0 -3
- package/web/dist/assets/ThreadCard-CXXf9qfA.js +0 -1
- package/web/dist/assets/format-BWr3zKWg.js +0 -55
- package/web/dist/assets/index-Cxqhrg2v.js +0 -23
- package/web/dist/assets/index-VVnGdAXc.css +0 -2
- package/web/dist/assets/useMatch-BpnmzT6s.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { serve } from "@hono/node-server";
|
|
|
9
9
|
import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rename as rename3, stat as stat2 } 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 z13 } from "zod";
|
|
13
13
|
|
|
14
14
|
// packages/shared/dist/ids.js
|
|
15
15
|
import { z } from "zod";
|
|
@@ -60,7 +60,10 @@ var VERB_NAMES = [
|
|
|
60
60
|
"remove_knowledge",
|
|
61
61
|
"create_channel",
|
|
62
62
|
"archive_channel",
|
|
63
|
-
"update_dashboard"
|
|
63
|
+
"update_dashboard",
|
|
64
|
+
"create_cron",
|
|
65
|
+
"update_cron",
|
|
66
|
+
"remove_cron"
|
|
64
67
|
];
|
|
65
68
|
var VerbNameSchema = z2.enum(VERB_NAMES);
|
|
66
69
|
var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
|
|
@@ -107,7 +110,10 @@ var MEMBER_VERBS = [
|
|
|
107
110
|
"remove_knowledge",
|
|
108
111
|
"plan_task",
|
|
109
112
|
"advance_task",
|
|
110
|
-
"update_dashboard"
|
|
113
|
+
"update_dashboard",
|
|
114
|
+
"create_cron",
|
|
115
|
+
"update_cron",
|
|
116
|
+
"remove_cron"
|
|
111
117
|
];
|
|
112
118
|
var GOVERNANCE_VERBS = ["approve", "reject"];
|
|
113
119
|
var FRONT_DESK_VERBS = ["create_project"];
|
|
@@ -160,7 +166,141 @@ var SEED_ROLES = [
|
|
|
160
166
|
];
|
|
161
167
|
|
|
162
168
|
// packages/shared/dist/board.js
|
|
169
|
+
import { z as z4 } from "zod";
|
|
170
|
+
|
|
171
|
+
// packages/shared/dist/crons.js
|
|
172
|
+
import { Cron as Pattern } from "croner";
|
|
163
173
|
import { z as z3 } from "zod";
|
|
174
|
+
var CRON_FLOOR_MS = 15 * 6e4;
|
|
175
|
+
var FIRE_SLACK_MS = 6e4;
|
|
176
|
+
var FLOOR_LOOKAHEAD = 200;
|
|
177
|
+
var CRON_FAILING_RUNS = 3;
|
|
178
|
+
var DEFAULT_TIME_ZONE = "UTC";
|
|
179
|
+
function canonicalTimeZone(zone) {
|
|
180
|
+
try {
|
|
181
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: zone }).resolvedOptions().timeZone;
|
|
182
|
+
} catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
var TimeZoneSchema = z3.string().trim().min(1).max(64).transform((zone, ctx) => {
|
|
187
|
+
const canonical = canonicalTimeZone(zone);
|
|
188
|
+
if (canonical === null) {
|
|
189
|
+
ctx.addIssue({
|
|
190
|
+
code: "custom",
|
|
191
|
+
message: `${zone} is not a time zone; name one as IANA does, such as Europe/Berlin, America/New_York, or UTC`
|
|
192
|
+
});
|
|
193
|
+
return z3.NEVER;
|
|
194
|
+
}
|
|
195
|
+
return canonical;
|
|
196
|
+
});
|
|
197
|
+
function pattern(expression, timeZone) {
|
|
198
|
+
return new Pattern(expression, { mode: "5-part", timezone: timeZone, paused: true });
|
|
199
|
+
}
|
|
200
|
+
function cronExpressionError(expression) {
|
|
201
|
+
try {
|
|
202
|
+
pattern(expression, DEFAULT_TIME_ZONE);
|
|
203
|
+
return null;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return error instanceof Error ? error.message : String(error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var CronExpressionSchema = z3.string().trim().min(1).max(120).superRefine((expression, ctx) => {
|
|
209
|
+
const error = cronExpressionError(expression);
|
|
210
|
+
if (error !== null) {
|
|
211
|
+
ctx.addIssue({
|
|
212
|
+
code: "custom",
|
|
213
|
+
message: `not a five-field cron expression (minute, hour, day of month, month, day of week): ${error}`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
var CronScheduleSchema = z3.union([
|
|
218
|
+
z3.object({ cron: CronExpressionSchema, timezone: TimeZoneSchema.optional() }),
|
|
219
|
+
z3.object({ at: IsoDateTimeSchema })
|
|
220
|
+
]);
|
|
221
|
+
var CronRunSchema = z3.object({
|
|
222
|
+
turnId: UlidSchema,
|
|
223
|
+
at: IsoDateTimeSchema,
|
|
224
|
+
outcome: z3.enum(["completed", "failed", "stopped"])
|
|
225
|
+
});
|
|
226
|
+
var CronFrontmatterSchema = z3.object({
|
|
227
|
+
id: UlidSchema,
|
|
228
|
+
title: z3.string().trim().min(1).max(120),
|
|
229
|
+
agent: NameSchema,
|
|
230
|
+
scope: NameSchema,
|
|
231
|
+
thread: UlidSchema.optional(),
|
|
232
|
+
channel: NameSchema.optional(),
|
|
233
|
+
schedule: CronScheduleSchema,
|
|
234
|
+
createdBy: NameSchema,
|
|
235
|
+
createdAt: IsoDateTimeSchema,
|
|
236
|
+
/** When the current schedule took effect: set, changed, or resumed. It fires only after this. */
|
|
237
|
+
since: IsoDateTimeSchema,
|
|
238
|
+
paused: z3.object({ at: IsoDateTimeSchema, by: NameSchema }).optional(),
|
|
239
|
+
lastFiredAt: IsoDateTimeSchema.optional(),
|
|
240
|
+
/** When a time came while its previous fire's turn was still queued or running, so it passed. */
|
|
241
|
+
lastSkippedAt: IsoDateTimeSchema.optional(),
|
|
242
|
+
/** How the turn of its latest fire ended. */
|
|
243
|
+
lastRun: CronRunSchema.optional(),
|
|
244
|
+
/** Its turns that failed in a row, up to the latest. */
|
|
245
|
+
failures: z3.number().int().nonnegative().default(0),
|
|
246
|
+
ended: z3.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z3.string().min(1) }).optional()
|
|
247
|
+
});
|
|
248
|
+
function isOneTime(schedule) {
|
|
249
|
+
return "at" in schedule;
|
|
250
|
+
}
|
|
251
|
+
function scheduleZone(schedule, societyZone) {
|
|
252
|
+
return isOneTime(schedule) ? societyZone : schedule.timezone ?? societyZone;
|
|
253
|
+
}
|
|
254
|
+
function fireTimes(schedule, societyZone, after, count) {
|
|
255
|
+
if (isOneTime(schedule)) {
|
|
256
|
+
const at = new Date(schedule.at);
|
|
257
|
+
return at > after && count > 0 ? [at] : [];
|
|
258
|
+
}
|
|
259
|
+
return pattern(schedule.cron, scheduleZone(schedule, societyZone)).nextRuns(count, after);
|
|
260
|
+
}
|
|
261
|
+
function shortestGapMs(schedule, societyZone, after) {
|
|
262
|
+
const times = fireTimes(schedule, societyZone, after, FLOOR_LOOKAHEAD);
|
|
263
|
+
let shortest = null;
|
|
264
|
+
for (let index = 1; index < times.length; index += 1) {
|
|
265
|
+
const gap = (times[index]?.getTime() ?? 0) - (times[index - 1]?.getTime() ?? 0);
|
|
266
|
+
shortest = shortest === null ? gap : Math.min(shortest, gap);
|
|
267
|
+
}
|
|
268
|
+
return shortest;
|
|
269
|
+
}
|
|
270
|
+
function nextFire(cron, societyZone, floorMs = CRON_FLOOR_MS) {
|
|
271
|
+
if (cron.ended !== void 0 || cron.paused !== void 0) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const fired = cron.lastFiredAt === void 0 ? null : Date.parse(cron.lastFiredAt);
|
|
275
|
+
const since = Date.parse(cron.since);
|
|
276
|
+
if (isOneTime(cron.schedule)) {
|
|
277
|
+
return fired !== null && fired >= since ? null : new Date(cron.schedule.at);
|
|
278
|
+
}
|
|
279
|
+
const from = Math.max(since, fired === null ? since : fired + floorMs - FIRE_SLACK_MS, cron.lastSkippedAt === void 0 ? since : Date.parse(cron.lastSkippedAt));
|
|
280
|
+
return fireTimes(cron.schedule, societyZone, new Date(from), 1)[0] ?? null;
|
|
281
|
+
}
|
|
282
|
+
function describeSchedule(schedule, societyZone) {
|
|
283
|
+
if (isOneTime(schedule)) {
|
|
284
|
+
return `once at ${schedule.at}`;
|
|
285
|
+
}
|
|
286
|
+
return schedule.timezone === void 0 ? `\`${schedule.cron}\` in the society's time zone, ${societyZone}` : `\`${schedule.cron}\` in ${schedule.timezone}`;
|
|
287
|
+
}
|
|
288
|
+
function localTime(date, timeZone) {
|
|
289
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
290
|
+
timeZone,
|
|
291
|
+
weekday: "long",
|
|
292
|
+
year: "numeric",
|
|
293
|
+
month: "2-digit",
|
|
294
|
+
day: "2-digit",
|
|
295
|
+
hour: "2-digit",
|
|
296
|
+
minute: "2-digit",
|
|
297
|
+
hourCycle: "h23"
|
|
298
|
+
}).formatToParts(date);
|
|
299
|
+
const part = (type) => parts.find((each) => each.type === type)?.value ?? "";
|
|
300
|
+
return `${part("weekday")} ${part("year")}-${part("month")}-${part("day")} ${part("hour")}:${part("minute")}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// packages/shared/dist/board.js
|
|
164
304
|
var ASK_CHANNEL = "asks";
|
|
165
305
|
var SOCIETY_CHANNELS = ["general", "governance", ASK_CHANNEL];
|
|
166
306
|
var PROJECT_DEFAULT_CHANNELS = ["general"];
|
|
@@ -172,30 +312,32 @@ var SOCIETY_SCOPE = "society";
|
|
|
172
312
|
function wakeScope(member, project) {
|
|
173
313
|
return project !== null && member.memberships.includes(project) ? project : SOCIETY_SCOPE;
|
|
174
314
|
}
|
|
175
|
-
var CliKindSchema =
|
|
176
|
-
var ArchivedChannelSchema =
|
|
315
|
+
var CliKindSchema = z4.enum(["claude", "codex"]);
|
|
316
|
+
var ArchivedChannelSchema = z4.object({
|
|
177
317
|
name: NameSchema,
|
|
178
318
|
at: IsoDateTimeSchema,
|
|
179
319
|
by: NameSchema,
|
|
180
|
-
reason:
|
|
320
|
+
reason: z4.string().min(1)
|
|
181
321
|
});
|
|
182
|
-
var SocietySchema =
|
|
183
|
-
name:
|
|
184
|
-
version:
|
|
322
|
+
var SocietySchema = z4.object({
|
|
323
|
+
name: z4.string().min(1),
|
|
324
|
+
version: z4.literal(1),
|
|
185
325
|
createdAt: IsoDateTimeSchema,
|
|
186
326
|
/** The channels that take posts; an archived one moves to `archivedChannels`. */
|
|
187
|
-
channels:
|
|
188
|
-
archivedChannels:
|
|
327
|
+
channels: z4.array(NameSchema),
|
|
328
|
+
archivedChannels: z4.array(ArchivedChannelSchema).default([]),
|
|
329
|
+
/** The time zone crons are read in when they name none, and every prompt tells the time in; the user's to set. */
|
|
330
|
+
timezone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
|
|
189
331
|
});
|
|
190
|
-
var CompletionEffectSchema =
|
|
332
|
+
var CompletionEffectSchema = z4.enum(["none", "merge", "ghpr"]);
|
|
191
333
|
var GITHUB_REMOTE = [
|
|
192
334
|
/^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/,
|
|
193
335
|
/^git@github\.com:([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/,
|
|
194
336
|
/^ssh:\/\/git@github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?\/?$/
|
|
195
337
|
];
|
|
196
338
|
function githubRepository(remote) {
|
|
197
|
-
for (const
|
|
198
|
-
const match = remote === null ? null :
|
|
339
|
+
for (const pattern2 of GITHUB_REMOTE) {
|
|
340
|
+
const match = remote === null ? null : pattern2.exec(remote);
|
|
199
341
|
if (match?.[1] !== void 0 && match[2] !== void 0) {
|
|
200
342
|
return { owner: match[1], name: match[2] };
|
|
201
343
|
}
|
|
@@ -203,7 +345,7 @@ function githubRepository(remote) {
|
|
|
203
345
|
return null;
|
|
204
346
|
}
|
|
205
347
|
var PULL_REQUEST_URL = /^https:\/\/github\.com\/([A-Za-z0-9-]+)\/([A-Za-z0-9._-]+)\/pull\/([1-9]\d*)\/?$/;
|
|
206
|
-
var PullRequestUrlSchema =
|
|
348
|
+
var PullRequestUrlSchema = z4.string().trim().regex(PULL_REQUEST_URL, "a pull request's address, https://github.com/<owner>/<repo>/pull/<number>");
|
|
207
349
|
function parsePullRequestUrl(url) {
|
|
208
350
|
const match = PULL_REQUEST_URL.exec(url.trim());
|
|
209
351
|
return match?.[1] === void 0 || match[2] === void 0 || match[3] === void 0 ? null : { owner: match[1], name: match[2], number: Number(match[3]) };
|
|
@@ -211,60 +353,65 @@ function parsePullRequestUrl(url) {
|
|
|
211
353
|
function sameRepository(a, b) {
|
|
212
354
|
return a.owner.toLowerCase() === b.owner.toLowerCase() && a.name.toLowerCase() === b.name.toLowerCase();
|
|
213
355
|
}
|
|
214
|
-
var MergeSubjectSchema =
|
|
215
|
-
var PullRequestLinkSchema =
|
|
356
|
+
var MergeSubjectSchema = z4.string().trim().min(1).max(300).regex(/^[^\n]+$/, "a merge commit's subject is one line");
|
|
357
|
+
var PullRequestLinkSchema = z4.object({
|
|
216
358
|
url: PullRequestUrlSchema,
|
|
217
|
-
number:
|
|
359
|
+
number: z4.number().int().positive(),
|
|
218
360
|
subject: MergeSubjectSchema.optional(),
|
|
219
|
-
body:
|
|
361
|
+
body: z4.string().max(2e4).optional(),
|
|
220
362
|
linkedBy: NameSchema,
|
|
221
|
-
linkedAt: IsoDateTimeSchema
|
|
363
|
+
linkedAt: IsoDateTimeSchema,
|
|
364
|
+
/**
|
|
365
|
+
* Once its `ghpr` task was abandoned, how the board's closing of it on GitHub went: `ok` false
|
|
366
|
+
* leaves it open, for the reason `detail` gives.
|
|
367
|
+
*/
|
|
368
|
+
closed: z4.object({ at: IsoDateTimeSchema, ok: z4.boolean(), detail: z4.string() }).optional()
|
|
222
369
|
});
|
|
223
|
-
var DefaultBranchSchema =
|
|
224
|
-
var StageIdSchema =
|
|
370
|
+
var DefaultBranchSchema = z4.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");
|
|
371
|
+
var StageIdSchema = z4.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
|
|
225
372
|
var stageFields = {
|
|
226
|
-
name:
|
|
373
|
+
name: z4.string().trim().min(1).max(120),
|
|
227
374
|
role: NameSchema.optional(),
|
|
228
375
|
agent: NameSchema.optional(),
|
|
229
|
-
gate:
|
|
376
|
+
gate: z4.boolean().default(false)
|
|
230
377
|
};
|
|
231
378
|
function oneAssignee(stage) {
|
|
232
379
|
return stage.role === void 0 || stage.agent === void 0;
|
|
233
380
|
}
|
|
234
381
|
var ONE_ASSIGNEE = { message: "a stage names a role or an agent, not both" };
|
|
235
|
-
var PlanStageSchema =
|
|
236
|
-
var PlanEditStageSchema =
|
|
237
|
-
var PlanSchema =
|
|
238
|
-
var PlanEditSchema =
|
|
239
|
-
var ProjectSchema =
|
|
382
|
+
var PlanStageSchema = z4.object(stageFields).refine(oneAssignee, ONE_ASSIGNEE);
|
|
383
|
+
var PlanEditStageSchema = z4.object({ id: StageIdSchema.optional(), ...stageFields }).refine(oneAssignee, ONE_ASSIGNEE);
|
|
384
|
+
var PlanSchema = z4.array(PlanStageSchema);
|
|
385
|
+
var PlanEditSchema = z4.array(PlanEditStageSchema);
|
|
386
|
+
var ProjectSchema = z4.object({
|
|
240
387
|
slug: NameSchema,
|
|
241
|
-
name:
|
|
242
|
-
repo:
|
|
243
|
-
defaultBranch:
|
|
388
|
+
name: z4.string().min(1),
|
|
389
|
+
repo: z4.string().nullable(),
|
|
390
|
+
defaultBranch: z4.string().min(1),
|
|
244
391
|
/** The channels that take posts; an archived one moves to `archivedChannels`. */
|
|
245
|
-
channels:
|
|
246
|
-
archivedChannels:
|
|
247
|
-
members:
|
|
248
|
-
approvers:
|
|
249
|
-
requiredCapabilities:
|
|
392
|
+
channels: z4.array(NameSchema),
|
|
393
|
+
archivedChannels: z4.array(ArchivedChannelSchema).default([]),
|
|
394
|
+
members: z4.array(NameSchema),
|
|
395
|
+
approvers: z4.array(NameSchema),
|
|
396
|
+
requiredCapabilities: z4.array(z4.string()),
|
|
250
397
|
createdAt: IsoDateTimeSchema,
|
|
251
398
|
onDone: CompletionEffectSchema.default("none"),
|
|
252
399
|
/** The runner the project lives on, which holds its repository; set on the project's first turn. */
|
|
253
400
|
runner: NameSchema.optional(),
|
|
254
401
|
/** Set once the project is archived: nobody belongs to it, and nothing more is posted, filed, or joined there. */
|
|
255
|
-
archived:
|
|
402
|
+
archived: z4.object({ at: IsoDateTimeSchema, by: NameSchema, reason: z4.string().min(1) }).optional()
|
|
256
403
|
});
|
|
257
|
-
var TaskStepSchema =
|
|
258
|
-
action:
|
|
404
|
+
var TaskStepSchema = z4.object({
|
|
405
|
+
action: z4.enum(["advanced", "returned", "abandoned", "landed", "reopened"]),
|
|
259
406
|
stage: StageIdSchema,
|
|
260
407
|
to: StageIdSchema.nullable()
|
|
261
408
|
});
|
|
262
|
-
var ProposalStepSchema =
|
|
263
|
-
var MessageStepSchema =
|
|
409
|
+
var ProposalStepSchema = z4.object({ action: z4.enum(["approved", "rejected"]) });
|
|
410
|
+
var MessageStepSchema = z4.union([TaskStepSchema, ProposalStepSchema]);
|
|
264
411
|
function isTaskStep(step) {
|
|
265
412
|
return "stage" in step;
|
|
266
413
|
}
|
|
267
|
-
var MessageFrontmatterSchema =
|
|
414
|
+
var MessageFrontmatterSchema = z4.object({
|
|
268
415
|
id: UlidSchema,
|
|
269
416
|
author: NameSchema,
|
|
270
417
|
channel: ChannelRefSchema,
|
|
@@ -276,18 +423,18 @@ var MessageFrontmatterSchema = z3.object({
|
|
|
276
423
|
/** On a post a task verb made in the task's thread, or a decision in a proposal's: the step it recorded. */
|
|
277
424
|
step: MessageStepSchema.optional(),
|
|
278
425
|
ts: IsoDateTimeSchema,
|
|
279
|
-
mentions:
|
|
426
|
+
mentions: z4.array(NameSchema)
|
|
280
427
|
});
|
|
281
|
-
var TaskStatusSchema =
|
|
282
|
-
var ThreadStateSchema =
|
|
283
|
-
var ThreadSubjectSchema =
|
|
284
|
-
kind:
|
|
428
|
+
var TaskStatusSchema = z4.enum(["open", "claimed", "done", "abandoned"]);
|
|
429
|
+
var ThreadStateSchema = z4.enum(["open", "closed"]);
|
|
430
|
+
var ThreadSubjectSchema = z4.object({
|
|
431
|
+
kind: z4.enum(["task", "proposal"]),
|
|
285
432
|
id: UlidSchema
|
|
286
433
|
});
|
|
287
|
-
var ThreadFrontmatterSchema =
|
|
434
|
+
var ThreadFrontmatterSchema = z4.object({
|
|
288
435
|
id: UlidSchema,
|
|
289
436
|
channel: ChannelRefSchema,
|
|
290
|
-
title:
|
|
437
|
+
title: z4.string().min(1).max(200),
|
|
291
438
|
subject: ThreadSubjectSchema.optional(),
|
|
292
439
|
state: ThreadStateSchema,
|
|
293
440
|
openedBy: NameSchema,
|
|
@@ -295,28 +442,28 @@ var ThreadFrontmatterSchema = z3.object({
|
|
|
295
442
|
closedBy: NameSchema.optional(),
|
|
296
443
|
closedAt: IsoDateTimeSchema.optional()
|
|
297
444
|
});
|
|
298
|
-
var StageSchema =
|
|
445
|
+
var StageSchema = z4.object({
|
|
299
446
|
id: StageIdSchema,
|
|
300
|
-
name:
|
|
447
|
+
name: z4.string().min(1),
|
|
301
448
|
role: NameSchema.optional(),
|
|
302
449
|
agent: NameSchema.optional(),
|
|
303
|
-
gate:
|
|
450
|
+
gate: z4.boolean().default(false),
|
|
304
451
|
/** Everyone who has held the stage, oldest first. */
|
|
305
|
-
holders:
|
|
452
|
+
holders: z4.array(NameSchema).default([]),
|
|
306
453
|
completedBy: NameSchema.optional(),
|
|
307
454
|
completedAt: IsoDateTimeSchema.optional()
|
|
308
455
|
});
|
|
309
|
-
var TaskReturnSchema =
|
|
456
|
+
var TaskReturnSchema = z4.object({
|
|
310
457
|
from: StageIdSchema,
|
|
311
458
|
by: NameSchema,
|
|
312
459
|
at: IsoDateTimeSchema
|
|
313
460
|
});
|
|
314
|
-
var TaskFrontmatterSchema =
|
|
461
|
+
var TaskFrontmatterSchema = z4.object({
|
|
315
462
|
id: UlidSchema,
|
|
316
463
|
project: NameSchema,
|
|
317
464
|
/** The channel of the project the task is filed in, where its thread hangs. */
|
|
318
465
|
channel: NameSchema.default("general"),
|
|
319
|
-
title:
|
|
466
|
+
title: z4.string().min(1),
|
|
320
467
|
status: TaskStatusSchema,
|
|
321
468
|
createdBy: NameSchema,
|
|
322
469
|
createdAt: IsoDateTimeSchema,
|
|
@@ -325,19 +472,19 @@ var TaskFrontmatterSchema = z3.object({
|
|
|
325
472
|
claimedBy: NameSchema.optional(),
|
|
326
473
|
leaseExpiresAt: IsoDateTimeSchema.optional(),
|
|
327
474
|
parentId: UlidSchema.optional(),
|
|
328
|
-
blockedBy:
|
|
329
|
-
requiredCapabilities:
|
|
330
|
-
stages:
|
|
475
|
+
blockedBy: z4.array(UlidSchema),
|
|
476
|
+
requiredCapabilities: z4.array(z4.string()),
|
|
477
|
+
stages: z4.array(StageSchema).min(1),
|
|
331
478
|
stage: StageIdSchema,
|
|
332
479
|
/** When the current stage last became current or lost its holder: the waiting clock. */
|
|
333
480
|
stageSince: IsoDateTimeSchema,
|
|
334
481
|
/** The highest stage number issued, so a removed stage's id is never reused. */
|
|
335
|
-
stageSeq:
|
|
482
|
+
stageSeq: z4.number().int().positive(),
|
|
336
483
|
onDone: CompletionEffectSchema.default("none"),
|
|
337
484
|
/** The pull request a `ghpr` task lands through. */
|
|
338
485
|
pullRequest: PullRequestLinkSchema.optional(),
|
|
339
486
|
/** The last stage is complete and the completion effect is running. */
|
|
340
|
-
completing:
|
|
487
|
+
completing: z4.boolean().default(false),
|
|
341
488
|
/** The latest send-back while its rework lasts: cleared once the task reaches that stage again. */
|
|
342
489
|
returned: TaskReturnSchema.optional()
|
|
343
490
|
});
|
|
@@ -361,79 +508,79 @@ function mayHoldStage(member, task) {
|
|
|
361
508
|
}
|
|
362
509
|
return !(stage.gate && task.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
|
|
363
510
|
}
|
|
364
|
-
var ModelNameSchema =
|
|
365
|
-
var EffortSchema =
|
|
366
|
-
var EffortOptionSchema =
|
|
511
|
+
var ModelNameSchema = z4.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
|
|
512
|
+
var EffortSchema = z4.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
|
|
513
|
+
var EffortOptionSchema = z4.object({
|
|
367
514
|
id: EffortSchema,
|
|
368
|
-
description:
|
|
515
|
+
description: z4.string().default("")
|
|
369
516
|
});
|
|
370
|
-
var ModelOptionSchema =
|
|
517
|
+
var ModelOptionSchema = z4.object({
|
|
371
518
|
id: ModelNameSchema,
|
|
372
|
-
name:
|
|
373
|
-
description:
|
|
374
|
-
isDefault:
|
|
375
|
-
efforts:
|
|
519
|
+
name: z4.string(),
|
|
520
|
+
description: z4.string().default(""),
|
|
521
|
+
isDefault: z4.boolean().default(false),
|
|
522
|
+
efforts: z4.array(EffortOptionSchema).default([]),
|
|
376
523
|
defaultEffort: EffortSchema.optional()
|
|
377
524
|
});
|
|
378
|
-
var RunnerModelsSchema =
|
|
525
|
+
var RunnerModelsSchema = z4.object({
|
|
379
526
|
runner: NameSchema,
|
|
380
527
|
cli: CliKindSchema,
|
|
381
|
-
models:
|
|
528
|
+
models: z4.array(ModelOptionSchema)
|
|
382
529
|
});
|
|
383
|
-
var AgentStatusSchema =
|
|
384
|
-
var AgentSchema =
|
|
530
|
+
var AgentStatusSchema = z4.enum(["active", "retired"]);
|
|
531
|
+
var AgentSchema = z4.object({
|
|
385
532
|
name: NameSchema,
|
|
386
533
|
role: NameSchema,
|
|
387
534
|
cli: CliKindSchema.nullable(),
|
|
388
|
-
model:
|
|
535
|
+
model: z4.string().optional(),
|
|
389
536
|
/** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
|
|
390
|
-
effort:
|
|
537
|
+
effort: z4.string().optional(),
|
|
391
538
|
/** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
|
|
392
539
|
homeRunner: NameSchema.optional(),
|
|
393
|
-
memberships:
|
|
394
|
-
subscriptions:
|
|
540
|
+
memberships: z4.array(NameSchema),
|
|
541
|
+
subscriptions: z4.array(ChannelRefSchema),
|
|
395
542
|
status: AgentStatusSchema,
|
|
396
543
|
createdAt: IsoDateTimeSchema,
|
|
397
|
-
tokenHash:
|
|
544
|
+
tokenHash: z4.string().min(1),
|
|
398
545
|
retiredAt: IsoDateTimeSchema.optional(),
|
|
399
|
-
retiredReason:
|
|
546
|
+
retiredReason: z4.string().optional()
|
|
400
547
|
});
|
|
401
|
-
var MemberSchema =
|
|
548
|
+
var MemberSchema = z4.object({
|
|
402
549
|
name: NameSchema,
|
|
403
550
|
role: NameSchema,
|
|
404
551
|
cli: CliKindSchema.nullable(),
|
|
405
552
|
homeRunner: NameSchema.optional(),
|
|
406
553
|
status: AgentStatusSchema,
|
|
407
|
-
resident:
|
|
554
|
+
resident: z4.boolean().default(false),
|
|
408
555
|
/** The model configured on the record, if any; the CLI's own default applies otherwise. */
|
|
409
|
-
model:
|
|
556
|
+
model: z4.string().optional(),
|
|
410
557
|
/** The model the CLI reported on the citizen's last turn. */
|
|
411
|
-
lastModel:
|
|
558
|
+
lastModel: z4.string().optional(),
|
|
412
559
|
/** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
|
|
413
|
-
effort:
|
|
560
|
+
effort: z4.string().optional(),
|
|
414
561
|
/** The citizen's own skills, by name. */
|
|
415
|
-
skills:
|
|
416
|
-
memberships:
|
|
417
|
-
subscriptions:
|
|
418
|
-
claimsHeld:
|
|
419
|
-
tasksDone:
|
|
562
|
+
skills: z4.array(NameSchema).default([]),
|
|
563
|
+
memberships: z4.array(NameSchema),
|
|
564
|
+
subscriptions: z4.array(ChannelRefSchema),
|
|
565
|
+
claimsHeld: z4.number().int().nonnegative().default(0),
|
|
566
|
+
tasksDone: z4.number().int().nonnegative().default(0),
|
|
420
567
|
lastTurnAt: IsoDateTimeSchema.optional(),
|
|
421
|
-
lastTurnOutcome:
|
|
568
|
+
lastTurnOutcome: z4.string().optional(),
|
|
422
569
|
createdAt: IsoDateTimeSchema,
|
|
423
570
|
retiredAt: IsoDateTimeSchema.optional()
|
|
424
571
|
});
|
|
425
|
-
var RunnerOsSchema =
|
|
426
|
-
var RunnerSchema =
|
|
572
|
+
var RunnerOsSchema = z4.enum(["linux", "windows", "darwin"]);
|
|
573
|
+
var RunnerSchema = z4.object({
|
|
427
574
|
name: NameSchema,
|
|
428
575
|
os: RunnerOsSchema,
|
|
429
|
-
clis:
|
|
430
|
-
capabilities:
|
|
576
|
+
clis: z4.array(CliKindSchema),
|
|
577
|
+
capabilities: z4.array(z4.string()),
|
|
431
578
|
/** The release the runner reported when it last registered; absent until it has. */
|
|
432
|
-
version:
|
|
433
|
-
status:
|
|
579
|
+
version: z4.string().optional(),
|
|
580
|
+
status: z4.enum(["connected", "disconnected"]),
|
|
434
581
|
lastSeen: IsoDateTimeSchema.optional()
|
|
435
582
|
});
|
|
436
|
-
var ProposalKindSchema =
|
|
583
|
+
var ProposalKindSchema = z4.enum([
|
|
437
584
|
"role",
|
|
438
585
|
"member",
|
|
439
586
|
"channel",
|
|
@@ -442,43 +589,43 @@ var ProposalKindSchema = z3.enum([
|
|
|
442
589
|
"skill",
|
|
443
590
|
"archive"
|
|
444
591
|
]);
|
|
445
|
-
var ProposalStatusSchema =
|
|
592
|
+
var ProposalStatusSchema = z4.enum([
|
|
446
593
|
"proposed",
|
|
447
594
|
"approved",
|
|
448
595
|
"rejected",
|
|
449
596
|
"provisioned",
|
|
450
597
|
"retired"
|
|
451
598
|
]);
|
|
452
|
-
var MemberProposalSchema =
|
|
599
|
+
var MemberProposalSchema = z4.object({
|
|
453
600
|
name: NameSchema,
|
|
454
601
|
role: NameSchema,
|
|
455
602
|
cli: CliKindSchema,
|
|
456
|
-
model:
|
|
603
|
+
model: z4.string().optional(),
|
|
457
604
|
homeRunner: NameSchema.optional(),
|
|
458
|
-
memberships:
|
|
459
|
-
subscriptions:
|
|
460
|
-
seedInstructions:
|
|
605
|
+
memberships: z4.array(NameSchema).default([]),
|
|
606
|
+
subscriptions: z4.array(ChannelRefSchema).default([]),
|
|
607
|
+
seedInstructions: z4.string().optional()
|
|
461
608
|
});
|
|
462
|
-
var ChannelProposalSchema =
|
|
609
|
+
var ChannelProposalSchema = z4.object({
|
|
463
610
|
project: NameSchema.nullable().default(null),
|
|
464
611
|
name: NameSchema,
|
|
465
|
-
purpose:
|
|
612
|
+
purpose: z4.string().min(1)
|
|
466
613
|
});
|
|
467
|
-
var ReallocationProposalSchema =
|
|
468
|
-
description:
|
|
614
|
+
var ReallocationProposalSchema = z4.object({
|
|
615
|
+
description: z4.string().min(1)
|
|
469
616
|
});
|
|
470
|
-
var RetirementProposalSchema =
|
|
617
|
+
var RetirementProposalSchema = z4.object({
|
|
471
618
|
agent: NameSchema,
|
|
472
|
-
reason:
|
|
619
|
+
reason: z4.string().min(1)
|
|
473
620
|
});
|
|
474
|
-
var ArchiveProposalSchema =
|
|
621
|
+
var ArchiveProposalSchema = z4.object({
|
|
475
622
|
project: NameSchema,
|
|
476
|
-
reason:
|
|
623
|
+
reason: z4.string().min(1)
|
|
477
624
|
});
|
|
478
|
-
var SkillProposalSchema =
|
|
625
|
+
var SkillProposalSchema = z4.object({
|
|
479
626
|
name: NameSchema,
|
|
480
|
-
summary:
|
|
481
|
-
body:
|
|
627
|
+
summary: z4.string().min(1).max(200),
|
|
628
|
+
body: z4.string().min(1)
|
|
482
629
|
});
|
|
483
630
|
var ProposalCharterSchemas = {
|
|
484
631
|
role: RoleCharterSchema,
|
|
@@ -550,17 +697,17 @@ function describeCharter(kind, charter) {
|
|
|
550
697
|
}
|
|
551
698
|
return `${kind} ${JSON.stringify(charter)}`;
|
|
552
699
|
}
|
|
553
|
-
var SkillSchema =
|
|
700
|
+
var SkillSchema = z4.object({
|
|
554
701
|
name: NameSchema,
|
|
555
|
-
summary:
|
|
556
|
-
scope:
|
|
557
|
-
path:
|
|
702
|
+
summary: z4.string().default(""),
|
|
703
|
+
scope: z4.enum(["own", "society"]),
|
|
704
|
+
path: z4.string().min(1)
|
|
558
705
|
});
|
|
559
|
-
var HomeConflictSchema =
|
|
706
|
+
var HomeConflictSchema = z4.object({
|
|
560
707
|
/** The copy, relative to the home. */
|
|
561
|
-
path:
|
|
708
|
+
path: z4.string().min(1),
|
|
562
709
|
/** The file beside it, whose version the board kept. */
|
|
563
|
-
file:
|
|
710
|
+
file: z4.string().min(1),
|
|
564
711
|
/** When the copy reached the board. */
|
|
565
712
|
since: IsoDateTimeSchema
|
|
566
713
|
});
|
|
@@ -568,196 +715,238 @@ var CONFLICT_COPY = /^(.+)\.conflict-[^/.]+$/;
|
|
|
568
715
|
function conflictCopyOf(copy) {
|
|
569
716
|
return CONFLICT_COPY.exec(copy)?.[1] ?? null;
|
|
570
717
|
}
|
|
571
|
-
var CommitIdSchema =
|
|
572
|
-
var HomeFileChangeSchema =
|
|
573
|
-
path:
|
|
574
|
-
status:
|
|
718
|
+
var CommitIdSchema = z4.string().regex(/^[0-9a-f]{40}([0-9a-f]{24})?$/);
|
|
719
|
+
var HomeFileChangeSchema = z4.object({
|
|
720
|
+
path: z4.string().min(1),
|
|
721
|
+
status: z4.enum(["added", "modified", "deleted"]),
|
|
575
722
|
/** Lines added and removed, or null for a binary file. */
|
|
576
|
-
added:
|
|
577
|
-
removed:
|
|
723
|
+
added: z4.number().int().nonnegative().nullable(),
|
|
724
|
+
removed: z4.number().int().nonnegative().nullable()
|
|
578
725
|
});
|
|
579
|
-
var HomeChangeSchema =
|
|
726
|
+
var HomeChangeSchema = z4.object({
|
|
580
727
|
commit: CommitIdSchema,
|
|
581
728
|
at: IsoDateTimeSchema,
|
|
582
|
-
kind:
|
|
583
|
-
author:
|
|
729
|
+
kind: z4.enum(["turn", "board", "merge"]),
|
|
730
|
+
author: z4.string(),
|
|
584
731
|
/** The turn that made it, from the runner's commit message. */
|
|
585
732
|
turnId: UlidSchema.optional(),
|
|
586
|
-
subject:
|
|
587
|
-
files:
|
|
733
|
+
subject: z4.string(),
|
|
734
|
+
files: z4.array(HomeFileChangeSchema)
|
|
588
735
|
});
|
|
589
|
-
var HomeHistorySchema =
|
|
590
|
-
changes:
|
|
736
|
+
var HomeHistorySchema = z4.object({
|
|
737
|
+
changes: z4.array(HomeChangeSchema),
|
|
591
738
|
/** Whether older commits remain past the ones read. */
|
|
592
|
-
more:
|
|
739
|
+
more: z4.boolean()
|
|
593
740
|
});
|
|
594
741
|
var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true }).extend({
|
|
595
|
-
patch:
|
|
742
|
+
patch: z4.string().nullable(),
|
|
596
743
|
/** The patch was cut at its size limit. */
|
|
597
|
-
truncated:
|
|
744
|
+
truncated: z4.boolean()
|
|
598
745
|
});
|
|
599
|
-
var KnowledgeSchema =
|
|
746
|
+
var KnowledgeSchema = z4.object({
|
|
600
747
|
topic: NameSchema,
|
|
601
748
|
project: NameSchema.nullable(),
|
|
602
749
|
updatedBy: NameSchema,
|
|
603
750
|
updatedAt: IsoDateTimeSchema
|
|
604
751
|
});
|
|
605
|
-
var RemovedKnowledgeSchema =
|
|
752
|
+
var RemovedKnowledgeSchema = z4.object({
|
|
606
753
|
topic: NameSchema,
|
|
607
754
|
project: NameSchema.nullable(),
|
|
608
755
|
removedBy: NameSchema,
|
|
609
756
|
removedAt: IsoDateTimeSchema
|
|
610
757
|
});
|
|
611
|
-
var DashboardSchema =
|
|
758
|
+
var DashboardSchema = z4.object({
|
|
612
759
|
project: NameSchema,
|
|
613
|
-
revision:
|
|
760
|
+
revision: z4.number().int().min(0),
|
|
614
761
|
/** Absent until someone updates the dashboard the project began with. */
|
|
615
762
|
updatedBy: NameSchema.optional(),
|
|
616
763
|
updatedAt: IsoDateTimeSchema.optional(),
|
|
617
|
-
body:
|
|
764
|
+
body: z4.string()
|
|
765
|
+
});
|
|
766
|
+
var ProposalOriginSchema = z4.object({
|
|
767
|
+
scope: NameSchema,
|
|
768
|
+
thread: UlidSchema.optional(),
|
|
769
|
+
channel: NameSchema.optional()
|
|
618
770
|
});
|
|
619
|
-
var ProposalFrontmatterSchema =
|
|
771
|
+
var ProposalFrontmatterSchema = z4.object({
|
|
620
772
|
id: UlidSchema,
|
|
621
773
|
kind: ProposalKindSchema,
|
|
622
774
|
proposedBy: NameSchema,
|
|
623
775
|
status: ProposalStatusSchema,
|
|
624
776
|
createdAt: IsoDateTimeSchema,
|
|
777
|
+
/** Absent for a proposal filed outside a turn, or before proposals recorded it. */
|
|
778
|
+
filedFrom: ProposalOriginSchema.optional(),
|
|
625
779
|
decidedBy: NameSchema.optional(),
|
|
626
780
|
decidedAt: IsoDateTimeSchema.optional(),
|
|
627
|
-
reason:
|
|
628
|
-
charter:
|
|
781
|
+
reason: z4.string().optional(),
|
|
782
|
+
charter: z4.record(z4.string(), z4.unknown()),
|
|
629
783
|
/** What approval created, for kinds the board provisions: the agent, channel, role, or retirement. */
|
|
630
784
|
provisionedAt: IsoDateTimeSchema.optional(),
|
|
631
|
-
provision:
|
|
785
|
+
provision: z4.record(z4.string(), z4.unknown()).optional()
|
|
632
786
|
});
|
|
633
|
-
var DecisionSchema =
|
|
787
|
+
var DecisionSchema = z4.object({
|
|
634
788
|
id: UlidSchema,
|
|
635
789
|
proposalId: UlidSchema,
|
|
636
790
|
decidedBy: NameSchema,
|
|
637
|
-
outcome:
|
|
638
|
-
reason:
|
|
791
|
+
outcome: z4.enum(["approved", "rejected"]),
|
|
792
|
+
reason: z4.string().optional(),
|
|
639
793
|
ts: IsoDateTimeSchema
|
|
640
794
|
});
|
|
641
795
|
|
|
642
796
|
// packages/shared/dist/verbs.js
|
|
643
|
-
import { z as
|
|
797
|
+
import { z as z5 } from "zod";
|
|
798
|
+
var MomentSchema = z5.iso.datetime({ offset: true });
|
|
799
|
+
var CronTitleSchema = z5.string().trim().min(1).max(120);
|
|
800
|
+
var CronNoteSchema = z5.string().trim().min(1).max(4e3);
|
|
644
801
|
var VerbInputs = {
|
|
645
|
-
post_message:
|
|
802
|
+
post_message: z5.object({
|
|
646
803
|
/** Required outside a thread; in one it may be left out, and must otherwise be the thread's. */
|
|
647
804
|
channel: ChannelRefSchema.optional(),
|
|
648
|
-
body:
|
|
805
|
+
body: z5.string().min(1),
|
|
649
806
|
thread_id: UlidSchema.optional()
|
|
650
807
|
}),
|
|
651
|
-
read_inbox:
|
|
808
|
+
read_inbox: z5.object({
|
|
652
809
|
since_cursor: UlidSchema.nullable().optional(),
|
|
653
|
-
limit:
|
|
654
|
-
advance:
|
|
810
|
+
limit: z5.number().int().positive().max(200).default(50),
|
|
811
|
+
advance: z5.boolean().default(true)
|
|
655
812
|
}),
|
|
656
|
-
search:
|
|
657
|
-
query:
|
|
813
|
+
search: z5.object({
|
|
814
|
+
query: z5.string().min(1),
|
|
658
815
|
project: NameSchema.optional(),
|
|
659
816
|
channel: ChannelRefSchema.optional(),
|
|
660
|
-
limit:
|
|
817
|
+
limit: z5.number().int().positive().max(100).default(20)
|
|
661
818
|
}),
|
|
662
819
|
/** On a task or a proposal, whose id the thread takes, or on a channel with a title. */
|
|
663
|
-
open_thread:
|
|
820
|
+
open_thread: z5.object({
|
|
664
821
|
task_id: UlidSchema.optional(),
|
|
665
822
|
proposal_id: UlidSchema.optional(),
|
|
666
823
|
channel: ChannelRefSchema.optional(),
|
|
667
|
-
title:
|
|
824
|
+
title: z5.string().min(1).max(200).optional()
|
|
668
825
|
}),
|
|
669
|
-
close_thread:
|
|
670
|
-
create_task:
|
|
826
|
+
close_thread: z5.object({ thread_id: UlidSchema, summary: z5.string().min(1) }),
|
|
827
|
+
create_task: z5.object({
|
|
671
828
|
project: NameSchema,
|
|
672
|
-
title:
|
|
673
|
-
body:
|
|
829
|
+
title: z5.string().min(1).max(200),
|
|
830
|
+
body: z5.string().default(""),
|
|
674
831
|
parent_id: UlidSchema.optional(),
|
|
675
|
-
required_capabilities:
|
|
832
|
+
required_capabilities: z5.array(z5.string()).default([]),
|
|
676
833
|
/** The plan; without it the task gets one stage, work, that anyone in the project may take. */
|
|
677
|
-
stages:
|
|
834
|
+
stages: z5.array(PlanStageSchema).min(1).optional(),
|
|
678
835
|
/** A channel of the project the task belongs to, such as a release's; its thread opens there. */
|
|
679
836
|
channel: NameSchema.default("general")
|
|
680
837
|
}),
|
|
681
|
-
claim_task:
|
|
682
|
-
release_task:
|
|
683
|
-
update_task:
|
|
838
|
+
claim_task: z5.object({ task_id: UlidSchema }),
|
|
839
|
+
release_task: z5.object({ task_id: UlidSchema }),
|
|
840
|
+
update_task: z5.object({
|
|
684
841
|
task_id: UlidSchema,
|
|
685
842
|
/** Move the task back to this earlier stage. */
|
|
686
843
|
stage: StageIdSchema.optional(),
|
|
687
|
-
status:
|
|
844
|
+
status: z5.enum(["abandoned"]).optional(),
|
|
688
845
|
/** Posted to the task's thread as the caller, marked with the step when the task moved. */
|
|
689
|
-
note:
|
|
690
|
-
blocked_by:
|
|
846
|
+
note: z5.string().min(1).optional(),
|
|
847
|
+
blocked_by: z5.array(UlidSchema).optional(),
|
|
691
848
|
/**
|
|
692
849
|
* The pull request a `ghpr` task lands through, on its project's repository, with the merge
|
|
693
850
|
* commit's subject and body; null unlinks it.
|
|
694
851
|
*/
|
|
695
|
-
pull_request:
|
|
852
|
+
pull_request: z5.object({
|
|
696
853
|
url: PullRequestUrlSchema,
|
|
697
854
|
merge_subject: MergeSubjectSchema.optional(),
|
|
698
|
-
merge_body:
|
|
855
|
+
merge_body: z5.string().max(2e4).optional()
|
|
699
856
|
}).nullable().optional()
|
|
700
857
|
}),
|
|
701
|
-
get_task:
|
|
702
|
-
subscribe:
|
|
703
|
-
unsubscribe:
|
|
704
|
-
propose:
|
|
858
|
+
get_task: z5.object({ task_id: UlidSchema }),
|
|
859
|
+
subscribe: z5.object({ channel: ChannelRefSchema }),
|
|
860
|
+
unsubscribe: z5.object({ channel: ChannelRefSchema }),
|
|
861
|
+
propose: z5.object({
|
|
705
862
|
kind: ProposalKindSchema,
|
|
706
|
-
charter:
|
|
707
|
-
rationale:
|
|
863
|
+
charter: z5.record(z5.string(), z5.unknown()),
|
|
864
|
+
rationale: z5.string().default("")
|
|
708
865
|
}),
|
|
709
|
-
approve:
|
|
710
|
-
reject:
|
|
711
|
-
create_project:
|
|
866
|
+
approve: z5.object({ proposal_id: UlidSchema, reason: z5.string().optional() }),
|
|
867
|
+
reject: z5.object({ proposal_id: UlidSchema, reason: z5.string().min(1) }),
|
|
868
|
+
create_project: z5.object({
|
|
712
869
|
slug: NameSchema,
|
|
713
|
-
name:
|
|
714
|
-
repo:
|
|
870
|
+
name: z5.string().min(1).optional(),
|
|
871
|
+
repo: z5.string().min(1).nullable().default(null),
|
|
715
872
|
default_branch: DefaultBranchSchema.default("main"),
|
|
716
873
|
on_done: CompletionEffectSchema.optional()
|
|
717
874
|
}),
|
|
718
|
-
join_project:
|
|
719
|
-
leave_project:
|
|
720
|
-
write_knowledge:
|
|
875
|
+
join_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
876
|
+
leave_project: z5.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
877
|
+
write_knowledge: z5.object({
|
|
721
878
|
/** A project you belong to, or null for society knowledge, which the steward and the user curate. */
|
|
722
879
|
project: NameSchema.nullable().default(null),
|
|
723
880
|
topic: NameSchema,
|
|
724
|
-
body:
|
|
881
|
+
body: z5.string().min(1)
|
|
725
882
|
}),
|
|
726
|
-
remove_knowledge:
|
|
883
|
+
remove_knowledge: z5.object({
|
|
727
884
|
/** As for write_knowledge: a project you belong to, or null for society knowledge. */
|
|
728
885
|
project: NameSchema.nullable().default(null),
|
|
729
886
|
topic: NameSchema
|
|
730
887
|
}),
|
|
731
|
-
plan_task:
|
|
888
|
+
plan_task: z5.object({
|
|
732
889
|
task_id: UlidSchema,
|
|
733
|
-
stages:
|
|
890
|
+
stages: z5.array(PlanEditStageSchema),
|
|
734
891
|
on_done: CompletionEffectSchema.optional()
|
|
735
892
|
}),
|
|
736
893
|
/** The note is posted to the task's thread as the caller, marked with the step. */
|
|
737
|
-
advance_task:
|
|
738
|
-
configure_project:
|
|
894
|
+
advance_task: z5.object({ task_id: UlidSchema, note: z5.string().min(1).optional() }),
|
|
895
|
+
configure_project: z5.object({
|
|
739
896
|
project: NameSchema,
|
|
740
897
|
on_done: CompletionEffectSchema.optional(),
|
|
741
898
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
742
|
-
name:
|
|
899
|
+
name: z5.string().trim().min(1).max(120).optional(),
|
|
743
900
|
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
744
901
|
default_branch: DefaultBranchSchema.optional()
|
|
745
902
|
}),
|
|
746
|
-
archive_project:
|
|
747
|
-
create_channel:
|
|
903
|
+
archive_project: z5.object({ project: NameSchema, reason: z5.string().min(1) }),
|
|
904
|
+
create_channel: z5.object({
|
|
748
905
|
/** A project, or null for a channel of the society. */
|
|
749
906
|
project: NameSchema.nullable().default(null),
|
|
750
907
|
name: NameSchema,
|
|
751
|
-
purpose:
|
|
908
|
+
purpose: z5.string().min(1)
|
|
752
909
|
}),
|
|
753
|
-
archive_channel:
|
|
754
|
-
update_dashboard:
|
|
910
|
+
archive_channel: z5.object({ channel: ChannelRefSchema, reason: z5.string().min(1) }),
|
|
911
|
+
update_dashboard: z5.object({
|
|
755
912
|
project: NameSchema,
|
|
756
913
|
/** The whole dashboard as markdown; the board keeps its frontmatter. */
|
|
757
|
-
body:
|
|
914
|
+
body: z5.string().min(1),
|
|
758
915
|
/** The `revision` in the frontmatter of the dashboard you read, 0 when it names none. */
|
|
759
|
-
revision:
|
|
760
|
-
})
|
|
916
|
+
revision: z5.number().int().min(0)
|
|
917
|
+
}),
|
|
918
|
+
create_cron: z5.object({
|
|
919
|
+
title: CronTitleSchema,
|
|
920
|
+
/** What to do when it fires; quoted in the prompt of every turn it starts. */
|
|
921
|
+
note: CronNoteSchema,
|
|
922
|
+
/** Minute, hour, day of month, month, day of week, such as `0 9 * * 1-5`; or `at` instead. */
|
|
923
|
+
cron: CronExpressionSchema.optional(),
|
|
924
|
+
/** The IANA time zone `cron` is read in; the society's when left out. */
|
|
925
|
+
timezone: TimeZoneSchema.optional(),
|
|
926
|
+
/** One time to fire, instead of `cron`; the cron ends once it has fired. */
|
|
927
|
+
at: MomentSchema.optional(),
|
|
928
|
+
/** The citizen it wakes: yourself when left out. */
|
|
929
|
+
agent: NameSchema.optional(),
|
|
930
|
+
/** Where it fires: a project the citizen belongs to, or `society`; this turn's scope when left out. */
|
|
931
|
+
project: NameSchema.optional(),
|
|
932
|
+
/** An open thread there, whose conversation it fires in. */
|
|
933
|
+
thread_id: UlidSchema.optional(),
|
|
934
|
+
/** An open channel of that place other than general, whose conversation it fires in. */
|
|
935
|
+
channel: NameSchema.optional()
|
|
936
|
+
}),
|
|
937
|
+
update_cron: z5.object({
|
|
938
|
+
cron_id: UlidSchema,
|
|
939
|
+
/** Pause it, or resume it, which starts its schedule afresh from now. */
|
|
940
|
+
paused: z5.boolean().optional(),
|
|
941
|
+
title: CronTitleSchema.optional(),
|
|
942
|
+
note: CronNoteSchema.optional(),
|
|
943
|
+
/** A new expression, or `at` for one time; either starts the schedule afresh. */
|
|
944
|
+
cron: CronExpressionSchema.optional(),
|
|
945
|
+
/** The zone `cron` is read in; null for the society's. */
|
|
946
|
+
timezone: TimeZoneSchema.nullable().optional(),
|
|
947
|
+
at: MomentSchema.optional()
|
|
948
|
+
}),
|
|
949
|
+
remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) })
|
|
761
950
|
};
|
|
762
951
|
var VERB_DESCRIPTIONS = {
|
|
763
952
|
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.",
|
|
@@ -786,11 +975,14 @@ var VERB_DESCRIPTIONS = {
|
|
|
786
975
|
archive_project: "Archive a project whose work is finished or has moved: 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.",
|
|
787
976
|
create_channel: "Open a channel in a project, or with project null in the society, for a workstream such as a release, whose tasks are filed there. You follow it; a notice in the place's general announces it, and members subscribe when it concerns them. User, steward, concierge.",
|
|
788
977
|
archive_channel: "Archive a channel whose workstream is done: its open threads close, its followers stop following it, and nothing more is posted or filed there, while its history stays readable. Refused while a task filed there is in play, and for the channels the board itself uses. User, steward, concierge.",
|
|
789
|
-
update_dashboard: "Replace the dashboard of a project you are a member of, the status page the user reads on its view, with a whole markdown body. Pass the revision from the frontmatter of the dashboard you read; if it was updated since, the call is refused with the current text to merge yours into. dashboard.md in the board projection is a copy, so editing it changes nothing."
|
|
978
|
+
update_dashboard: "Replace the dashboard of a project you are a member of, the status page the user reads on its view, with a whole markdown body. Pass the revision from the frontmatter of the dashboard you read; if it was updated since, the call is refused with the current text to merge yours into. dashboard.md in the board projection is a copy, so editing it changes nothing.",
|
|
979
|
+
create_cron: "Set a cron that wakes you later: a title, a note on what to do then, and either cron, a five-field expression (minute hour day-of-month month day-of-week) read in timezone or the society's, or at, one time. It fires in this turn's conversation unless you name a project, thread_id, or channel, at most every 15 minutes, and every fire is a turn. Only the user, the steward, and the concierge set one that wakes another citizen (agent).",
|
|
980
|
+
update_cron: "Pause or resume a cron, or change its title, note, or schedule; resuming or rescheduling starts it afresh from now. Yours, or anyone's for the user, the steward, and the concierge.",
|
|
981
|
+
remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires."
|
|
790
982
|
};
|
|
791
983
|
|
|
792
984
|
// packages/shared/dist/events.js
|
|
793
|
-
import { z as
|
|
985
|
+
import { z as z6 } from "zod";
|
|
794
986
|
var BOARD_EVENT_TYPES = [
|
|
795
987
|
"society.initialized",
|
|
796
988
|
"project.added",
|
|
@@ -843,30 +1035,38 @@ var BOARD_EVENT_TYPES = [
|
|
|
843
1035
|
"turn.failed",
|
|
844
1036
|
"merge.completed",
|
|
845
1037
|
"merge.failed",
|
|
846
|
-
"
|
|
1038
|
+
"pull_request.closed",
|
|
1039
|
+
"pull_request.close_failed",
|
|
1040
|
+
"workspace.leftovers",
|
|
1041
|
+
"society.configured",
|
|
1042
|
+
"cron.created",
|
|
1043
|
+
"cron.updated",
|
|
1044
|
+
"cron.fired",
|
|
1045
|
+
"cron.skipped",
|
|
1046
|
+
"cron.ended"
|
|
847
1047
|
];
|
|
848
|
-
var BoardEventTypeSchema =
|
|
849
|
-
var BoardEventSchema =
|
|
1048
|
+
var BoardEventTypeSchema = z6.enum(BOARD_EVENT_TYPES);
|
|
1049
|
+
var BoardEventSchema = z6.object({
|
|
850
1050
|
id: UlidSchema,
|
|
851
1051
|
ts: IsoDateTimeSchema,
|
|
852
1052
|
type: BoardEventTypeSchema,
|
|
853
1053
|
actor: NameSchema,
|
|
854
|
-
payload:
|
|
1054
|
+
payload: z6.record(z6.string(), z6.unknown())
|
|
855
1055
|
});
|
|
856
|
-
var UsageSchema =
|
|
857
|
-
inputTokens:
|
|
858
|
-
outputTokens:
|
|
859
|
-
cacheReadTokens:
|
|
860
|
-
cacheWriteTokens:
|
|
1056
|
+
var UsageSchema = z6.object({
|
|
1057
|
+
inputTokens: z6.number().int().nonnegative(),
|
|
1058
|
+
outputTokens: z6.number().int().nonnegative(),
|
|
1059
|
+
cacheReadTokens: z6.number().int().nonnegative().default(0),
|
|
1060
|
+
cacheWriteTokens: z6.number().int().nonnegative().default(0)
|
|
861
1061
|
});
|
|
862
|
-
var TurnStatusSchema =
|
|
863
|
-
summary:
|
|
864
|
-
claimsHeld:
|
|
865
|
-
blockedOn:
|
|
866
|
-
needsUserDecision:
|
|
867
|
-
memoryUpdated:
|
|
1062
|
+
var TurnStatusSchema = z6.object({
|
|
1063
|
+
summary: z6.string().min(1),
|
|
1064
|
+
claimsHeld: z6.array(UlidSchema).default([]),
|
|
1065
|
+
blockedOn: z6.array(z6.string()).default([]),
|
|
1066
|
+
needsUserDecision: z6.boolean().default(false),
|
|
1067
|
+
memoryUpdated: z6.boolean().default(false)
|
|
868
1068
|
});
|
|
869
|
-
var TurnExitReasonSchema =
|
|
1069
|
+
var TurnExitReasonSchema = z6.enum([
|
|
870
1070
|
"completed",
|
|
871
1071
|
"timeout",
|
|
872
1072
|
"error",
|
|
@@ -874,50 +1074,50 @@ var TurnExitReasonSchema = z5.enum([
|
|
|
874
1074
|
"blocked",
|
|
875
1075
|
"stopped"
|
|
876
1076
|
]);
|
|
877
|
-
var AgentEventSchema =
|
|
878
|
-
|
|
879
|
-
type:
|
|
1077
|
+
var AgentEventSchema = z6.discriminatedUnion("type", [
|
|
1078
|
+
z6.object({
|
|
1079
|
+
type: z6.literal("turn_started"),
|
|
880
1080
|
agent: NameSchema,
|
|
881
|
-
session:
|
|
1081
|
+
session: z6.string(),
|
|
882
1082
|
runner: NameSchema,
|
|
883
1083
|
/** The model the CLI reports running, when it reports one. */
|
|
884
|
-
model:
|
|
1084
|
+
model: z6.string().optional()
|
|
885
1085
|
}),
|
|
886
1086
|
/**
|
|
887
1087
|
* The CLI took a steer into the running turn. Adapters name the steer; the server adds the
|
|
888
1088
|
* messages it delivered and the text the CLI was given.
|
|
889
1089
|
*/
|
|
890
|
-
|
|
891
|
-
type:
|
|
892
|
-
steer:
|
|
893
|
-
messages:
|
|
894
|
-
text:
|
|
1090
|
+
z6.object({
|
|
1091
|
+
type: z6.literal("steered"),
|
|
1092
|
+
steer: z6.string().min(1),
|
|
1093
|
+
messages: z6.array(UlidSchema).optional(),
|
|
1094
|
+
text: z6.string().optional()
|
|
895
1095
|
}),
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
type:
|
|
900
|
-
name:
|
|
901
|
-
ok:
|
|
1096
|
+
z6.object({ type: z6.literal("text"), delta: z6.string() }),
|
|
1097
|
+
z6.object({ type: z6.literal("tool_call"), name: z6.string(), input: z6.unknown() }),
|
|
1098
|
+
z6.object({
|
|
1099
|
+
type: z6.literal("tool_result"),
|
|
1100
|
+
name: z6.string(),
|
|
1101
|
+
ok: z6.boolean(),
|
|
902
1102
|
/** What the tool returned, cut to `OUTPUT_LIMIT` characters by `capOutput`. */
|
|
903
|
-
output:
|
|
1103
|
+
output: z6.string().optional()
|
|
904
1104
|
}),
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
type:
|
|
1105
|
+
z6.object({ type: z6.literal("approval_requested"), kind: z6.string(), detail: z6.unknown() }),
|
|
1106
|
+
z6.object({
|
|
1107
|
+
type: z6.literal("turn_completed"),
|
|
908
1108
|
usage: UsageSchema,
|
|
909
|
-
costUsd:
|
|
1109
|
+
costUsd: z6.number().nonnegative(),
|
|
910
1110
|
status: TurnStatusSchema.nullable(),
|
|
911
1111
|
exitReason: TurnExitReasonSchema
|
|
912
1112
|
}),
|
|
913
|
-
|
|
1113
|
+
z6.object({ type: z6.literal("error"), message: z6.string() })
|
|
914
1114
|
]);
|
|
915
|
-
var TranscriptEntrySchema =
|
|
1115
|
+
var TranscriptEntrySchema = z6.object({
|
|
916
1116
|
ts: IsoDateTimeSchema,
|
|
917
1117
|
event: AgentEventSchema
|
|
918
1118
|
});
|
|
919
|
-
var LiveTurnEventSchema =
|
|
920
|
-
seq:
|
|
1119
|
+
var LiveTurnEventSchema = z6.object({
|
|
1120
|
+
seq: z6.number().int().positive(),
|
|
921
1121
|
ts: IsoDateTimeSchema,
|
|
922
1122
|
agent: NameSchema,
|
|
923
1123
|
project: NameSchema,
|
|
@@ -929,10 +1129,10 @@ var LiveTurnEventSchema = z5.object({
|
|
|
929
1129
|
});
|
|
930
1130
|
|
|
931
1131
|
// packages/shared/dist/turns.js
|
|
932
|
-
import { z as
|
|
1132
|
+
import { z as z8 } from "zod";
|
|
933
1133
|
|
|
934
1134
|
// packages/shared/dist/runner.js
|
|
935
|
-
import { z as
|
|
1135
|
+
import { z as z7 } from "zod";
|
|
936
1136
|
var RUNNER_PROTOCOL = 1;
|
|
937
1137
|
var PATH_TOKENS = {
|
|
938
1138
|
home: "{{stellaris:home}}",
|
|
@@ -943,67 +1143,67 @@ var SERVER_TOKEN = "{{stellaris:server}}";
|
|
|
943
1143
|
var WORKSPACE_TOKEN = "{{stellaris:workspace}}";
|
|
944
1144
|
var HOME_SCRATCH = "scratch";
|
|
945
1145
|
var HOME_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
946
|
-
var RunnerHelloSchema =
|
|
947
|
-
protocol:
|
|
948
|
-
version:
|
|
1146
|
+
var RunnerHelloSchema = z7.object({
|
|
1147
|
+
protocol: z7.number().int().positive(),
|
|
1148
|
+
version: z7.string().min(1),
|
|
949
1149
|
os: RunnerOsSchema,
|
|
950
|
-
clis:
|
|
1150
|
+
clis: z7.array(CliKindSchema),
|
|
951
1151
|
/** The CLIs whose adapter can keep a session warm between turns. */
|
|
952
|
-
residentClis:
|
|
1152
|
+
residentClis: z7.array(CliKindSchema).default([]),
|
|
953
1153
|
/** The CLIs whose adapter can take input into a running turn; the server steers only these. */
|
|
954
|
-
steerableClis:
|
|
1154
|
+
steerableClis: z7.array(CliKindSchema).default([]),
|
|
955
1155
|
/** The CLIs whose adapter can stop a running turn; the server stops only these. */
|
|
956
|
-
stoppableClis:
|
|
1156
|
+
stoppableClis: z7.array(CliKindSchema).default([]),
|
|
957
1157
|
/** Whether the runner answers file reads on its projects' branches; one from before never does. */
|
|
958
|
-
branchReads:
|
|
1158
|
+
branchReads: z7.boolean().default(false),
|
|
959
1159
|
/** Whether the runner lists what a branch changed; one from before never does. */
|
|
960
|
-
branchChanges:
|
|
961
|
-
capabilities:
|
|
1160
|
+
branchChanges: z7.boolean().default(false),
|
|
1161
|
+
capabilities: z7.array(z7.string()).default([]),
|
|
962
1162
|
/** Turns the runner runs at once, or null for no limit. */
|
|
963
|
-
slots:
|
|
1163
|
+
slots: z7.number().int().positive().nullable(),
|
|
964
1164
|
/** Turns the runner is still running, when it registers again; the server fails the rest of its turns. */
|
|
965
|
-
turns:
|
|
1165
|
+
turns: z7.array(UlidSchema).default([])
|
|
966
1166
|
});
|
|
967
|
-
var RunnerWelcomeSchema =
|
|
968
|
-
var RunnerEnrollRequestSchema =
|
|
969
|
-
protocol:
|
|
970
|
-
version:
|
|
971
|
-
hostname:
|
|
1167
|
+
var RunnerWelcomeSchema = z7.object({ name: NameSchema, version: z7.string() });
|
|
1168
|
+
var RunnerEnrollRequestSchema = z7.object({
|
|
1169
|
+
protocol: z7.number().int().positive(),
|
|
1170
|
+
version: z7.string().min(1).max(64),
|
|
1171
|
+
hostname: z7.string().min(1).max(253),
|
|
972
1172
|
os: RunnerOsSchema,
|
|
973
|
-
clis:
|
|
974
|
-
capabilities:
|
|
1173
|
+
clis: z7.array(CliKindSchema).max(8),
|
|
1174
|
+
capabilities: z7.array(z7.string().min(1).max(64)).max(32).default([])
|
|
975
1175
|
});
|
|
976
|
-
var RunnerEnrollmentSchema =
|
|
977
|
-
deviceCode:
|
|
978
|
-
userCode:
|
|
1176
|
+
var RunnerEnrollmentSchema = z7.object({
|
|
1177
|
+
deviceCode: z7.string().min(1),
|
|
1178
|
+
userCode: z7.string().min(1),
|
|
979
1179
|
expiresAt: IsoDateTimeSchema,
|
|
980
|
-
intervalMs:
|
|
1180
|
+
intervalMs: z7.number().int().positive()
|
|
981
1181
|
});
|
|
982
|
-
var RunnerEnrollPollSchema =
|
|
983
|
-
var RunnerEnrollStatusSchema =
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1182
|
+
var RunnerEnrollPollSchema = z7.object({ deviceCode: z7.string().min(1) });
|
|
1183
|
+
var RunnerEnrollStatusSchema = z7.discriminatedUnion("status", [
|
|
1184
|
+
z7.object({ status: z7.literal("pending") }),
|
|
1185
|
+
z7.object({ status: z7.literal("approved"), name: NameSchema, token: z7.string().min(1) }),
|
|
1186
|
+
z7.object({ status: z7.literal("denied") })
|
|
987
1187
|
]);
|
|
988
|
-
var PendingEnrollmentSchema =
|
|
989
|
-
userCode:
|
|
990
|
-
hostname:
|
|
1188
|
+
var PendingEnrollmentSchema = z7.object({
|
|
1189
|
+
userCode: z7.string().min(1),
|
|
1190
|
+
hostname: z7.string(),
|
|
991
1191
|
os: RunnerOsSchema,
|
|
992
|
-
clis:
|
|
993
|
-
capabilities:
|
|
994
|
-
version:
|
|
1192
|
+
clis: z7.array(CliKindSchema),
|
|
1193
|
+
capabilities: z7.array(z7.string()),
|
|
1194
|
+
version: z7.string(),
|
|
995
1195
|
requestedAt: IsoDateTimeSchema,
|
|
996
1196
|
expiresAt: IsoDateTimeSchema
|
|
997
1197
|
});
|
|
998
|
-
var ProjectRepoSchema =
|
|
1198
|
+
var ProjectRepoSchema = z7.object({
|
|
999
1199
|
slug: NameSchema,
|
|
1000
|
-
origin:
|
|
1001
|
-
defaultBranch:
|
|
1200
|
+
origin: z7.string().nullable(),
|
|
1201
|
+
defaultBranch: z7.string().min(1),
|
|
1002
1202
|
/**
|
|
1003
1203
|
* The board lands finished tasks on the default branch, itself or through their pull requests,
|
|
1004
1204
|
* so the runner lets nothing else move it.
|
|
1005
1205
|
*/
|
|
1006
|
-
boardLands:
|
|
1206
|
+
boardLands: z7.boolean()
|
|
1007
1207
|
});
|
|
1008
1208
|
function projectRepo(project) {
|
|
1009
1209
|
return {
|
|
@@ -1013,29 +1213,29 @@ function projectRepo(project) {
|
|
|
1013
1213
|
boardLands: project.onDone !== "none"
|
|
1014
1214
|
};
|
|
1015
1215
|
}
|
|
1016
|
-
var TurnWorkspaceSchema =
|
|
1017
|
-
|
|
1018
|
-
kind:
|
|
1216
|
+
var TurnWorkspaceSchema = z7.discriminatedUnion("kind", [
|
|
1217
|
+
z7.object({
|
|
1218
|
+
kind: z7.literal("home"),
|
|
1019
1219
|
thread: UlidSchema.optional(),
|
|
1020
1220
|
channel: NameSchema.optional()
|
|
1021
1221
|
}),
|
|
1022
|
-
|
|
1023
|
-
kind:
|
|
1222
|
+
z7.object({
|
|
1223
|
+
kind: z7.literal("project"),
|
|
1024
1224
|
repo: ProjectRepoSchema,
|
|
1025
|
-
branches:
|
|
1225
|
+
branches: z7.array(z7.string().min(1)),
|
|
1026
1226
|
thread: UlidSchema.optional(),
|
|
1027
1227
|
channel: NameSchema.optional()
|
|
1028
1228
|
}),
|
|
1029
|
-
|
|
1229
|
+
z7.object({ kind: z7.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
|
|
1030
1230
|
]);
|
|
1031
|
-
var TurnJobSchema =
|
|
1231
|
+
var TurnJobSchema = z7.object({
|
|
1032
1232
|
turnId: UlidSchema,
|
|
1033
1233
|
agent: NameSchema,
|
|
1034
1234
|
role: NameSchema,
|
|
1035
1235
|
cli: CliKindSchema,
|
|
1036
|
-
model:
|
|
1236
|
+
model: z7.string().optional(),
|
|
1037
1237
|
/** The reasoning effort set on the agent; absent for the model's own default. */
|
|
1038
|
-
effort:
|
|
1238
|
+
effort: z7.string().optional(),
|
|
1039
1239
|
/** A project slug, or the society scope. */
|
|
1040
1240
|
scope: NameSchema,
|
|
1041
1241
|
/** The thread whose conversation the turn is in; absent for the home conversation. */
|
|
@@ -1046,112 +1246,117 @@ var TurnJobSchema = z6.object({
|
|
|
1046
1246
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1047
1247
|
* the outcome reports it.
|
|
1048
1248
|
*/
|
|
1049
|
-
session:
|
|
1249
|
+
session: z7.string().min(1).nullable(),
|
|
1050
1250
|
/** The running total the session reported after its last turn, which a resumed session starts from. */
|
|
1051
|
-
costSoFarUsd:
|
|
1251
|
+
costSoFarUsd: z7.number().nonnegative(),
|
|
1052
1252
|
/** The rendered charter, norms, memory core, and skills index, with `PATH_TOKENS` for places. */
|
|
1053
|
-
instructions:
|
|
1054
|
-
prompt:
|
|
1055
|
-
mcp:
|
|
1056
|
-
limits:
|
|
1057
|
-
timeoutMs:
|
|
1058
|
-
maxTurns:
|
|
1253
|
+
instructions: z7.string(),
|
|
1254
|
+
prompt: z7.string(),
|
|
1255
|
+
mcp: z7.object({ url: z7.string().min(1), token: z7.string().min(1) }),
|
|
1256
|
+
limits: z7.object({
|
|
1257
|
+
timeoutMs: z7.number().int().positive().nullable(),
|
|
1258
|
+
maxTurns: z7.number().int().positive().nullable()
|
|
1059
1259
|
}),
|
|
1060
1260
|
/** Present when the runner keeps the conversation's session warm between turns, under `key`. */
|
|
1061
|
-
resident:
|
|
1261
|
+
resident: z7.object({ key: z7.string().min(1), idleMs: z7.number().int().positive() }).optional(),
|
|
1062
1262
|
workspace: TurnWorkspaceSchema
|
|
1063
1263
|
});
|
|
1064
|
-
var TurnWorkSchema =
|
|
1065
|
-
var TurnEventsSchema =
|
|
1066
|
-
var TurnOutcomeSchema =
|
|
1264
|
+
var TurnWorkSchema = z7.object({ branch: z7.string().min(1), head: z7.string().min(1) });
|
|
1265
|
+
var TurnEventsSchema = z7.object({ entries: z7.array(TranscriptEntrySchema) });
|
|
1266
|
+
var TurnOutcomeSchema = z7.object({
|
|
1067
1267
|
exitReason: TurnExitReasonSchema,
|
|
1068
1268
|
status: TurnStatusSchema.nullable(),
|
|
1069
|
-
error:
|
|
1269
|
+
error: z7.string().nullable(),
|
|
1070
1270
|
usage: UsageSchema,
|
|
1071
|
-
costUsd:
|
|
1271
|
+
costUsd: z7.number().nonnegative(),
|
|
1072
1272
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1073
|
-
sessionCostUsd:
|
|
1273
|
+
sessionCostUsd: z7.number().nonnegative().optional(),
|
|
1074
1274
|
/** The session the CLI used, which differs from the job's for CLIs that assign their own ids. */
|
|
1075
|
-
session:
|
|
1275
|
+
session: z7.string().min(1),
|
|
1076
1276
|
/** The model the CLI reported, when it reported one. */
|
|
1077
|
-
model:
|
|
1277
|
+
model: z7.string().nullable(),
|
|
1078
1278
|
work: TurnWorkSchema.nullable(),
|
|
1079
1279
|
/**
|
|
1080
1280
|
* Whether the turn's own workspace, a thread's, a channel's, or a task's, holds work on no
|
|
1081
1281
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1082
1282
|
*/
|
|
1083
|
-
leftovers:
|
|
1283
|
+
leftovers: z7.boolean().default(false)
|
|
1084
1284
|
});
|
|
1085
|
-
var TurnAckSchema =
|
|
1086
|
-
var LandRequestSchema =
|
|
1285
|
+
var TurnAckSchema = z7.object({ dropWorkspace: z7.boolean() });
|
|
1286
|
+
var LandRequestSchema = z7.object({
|
|
1087
1287
|
repo: ProjectRepoSchema,
|
|
1088
|
-
branch:
|
|
1288
|
+
branch: z7.string().min(1),
|
|
1089
1289
|
/**
|
|
1090
1290
|
* For a `ghpr` task, the pull request to merge in place of the branch, which must hold the
|
|
1091
1291
|
* branch's files, and the merge commit's subject and body, the pull request's title and an
|
|
1092
1292
|
* empty body when left out.
|
|
1093
1293
|
*/
|
|
1094
|
-
pullRequest:
|
|
1095
|
-
url:
|
|
1096
|
-
subject:
|
|
1097
|
-
body:
|
|
1294
|
+
pullRequest: z7.object({
|
|
1295
|
+
url: z7.string().min(1),
|
|
1296
|
+
subject: z7.string().min(1).optional(),
|
|
1297
|
+
body: z7.string().optional()
|
|
1098
1298
|
}).optional()
|
|
1099
1299
|
});
|
|
1100
|
-
var
|
|
1101
|
-
|
|
1300
|
+
var CloseRequestSchema = z7.object({
|
|
1301
|
+
repo: ProjectRepoSchema,
|
|
1302
|
+
url: z7.string().min(1),
|
|
1303
|
+
comment: z7.string().min(1)
|
|
1304
|
+
});
|
|
1305
|
+
var MergeOutcomeSchema = z7.object({ ok: z7.boolean(), detail: z7.string() });
|
|
1306
|
+
var ModelListSchema = z7.array(ModelOptionSchema);
|
|
1102
1307
|
var BRANCH_FILE_LIMIT_BYTES = 8 * 1024 * 1024;
|
|
1103
|
-
var BranchPathSchema =
|
|
1104
|
-
var BranchNameSchema =
|
|
1105
|
-
var BranchReadSchema =
|
|
1308
|
+
var BranchPathSchema = z7.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" });
|
|
1309
|
+
var BranchNameSchema = z7.string().regex(/^[A-Za-z0-9][A-Za-z0-9._/-]*$/);
|
|
1310
|
+
var BranchReadSchema = z7.object({
|
|
1106
1311
|
repo: ProjectRepoSchema,
|
|
1107
1312
|
branch: BranchNameSchema,
|
|
1108
1313
|
path: BranchPathSchema
|
|
1109
1314
|
});
|
|
1110
|
-
var BranchCommitSchema =
|
|
1111
|
-
id:
|
|
1315
|
+
var BranchCommitSchema = z7.object({
|
|
1316
|
+
id: z7.string().min(1),
|
|
1112
1317
|
at: IsoDateTimeSchema,
|
|
1113
|
-
author:
|
|
1318
|
+
author: z7.string()
|
|
1114
1319
|
});
|
|
1115
|
-
var BranchFileSchema =
|
|
1116
|
-
|
|
1117
|
-
kind:
|
|
1320
|
+
var BranchFileSchema = z7.discriminatedUnion("kind", [
|
|
1321
|
+
z7.object({
|
|
1322
|
+
kind: z7.literal("file"),
|
|
1118
1323
|
path: BranchPathSchema,
|
|
1119
1324
|
commit: BranchCommitSchema,
|
|
1120
|
-
size:
|
|
1121
|
-
content:
|
|
1325
|
+
size: z7.number().int().nonnegative(),
|
|
1326
|
+
content: z7.string().nullable()
|
|
1122
1327
|
}),
|
|
1123
|
-
|
|
1124
|
-
kind:
|
|
1328
|
+
z7.object({
|
|
1329
|
+
kind: z7.literal("dir"),
|
|
1125
1330
|
path: BranchPathSchema,
|
|
1126
1331
|
commit: BranchCommitSchema,
|
|
1127
|
-
entries:
|
|
1128
|
-
name:
|
|
1129
|
-
kind:
|
|
1130
|
-
size:
|
|
1332
|
+
entries: z7.array(z7.object({
|
|
1333
|
+
name: z7.string().min(1),
|
|
1334
|
+
kind: z7.enum(["file", "dir"]),
|
|
1335
|
+
size: z7.number().int().nonnegative().nullable()
|
|
1131
1336
|
}))
|
|
1132
1337
|
})
|
|
1133
1338
|
]);
|
|
1134
|
-
var BranchChangesReadSchema =
|
|
1339
|
+
var BranchChangesReadSchema = z7.object({
|
|
1135
1340
|
repo: ProjectRepoSchema,
|
|
1136
1341
|
branch: BranchNameSchema
|
|
1137
1342
|
});
|
|
1138
|
-
var BranchFileChangeSchema =
|
|
1139
|
-
path:
|
|
1140
|
-
status:
|
|
1343
|
+
var BranchFileChangeSchema = z7.object({
|
|
1344
|
+
path: z7.string().min(1),
|
|
1345
|
+
status: z7.enum(["added", "modified", "deleted"]),
|
|
1141
1346
|
/** Lines added and removed, or null for a binary file. */
|
|
1142
|
-
added:
|
|
1143
|
-
removed:
|
|
1347
|
+
added: z7.number().int().nonnegative().nullable(),
|
|
1348
|
+
removed: z7.number().int().nonnegative().nullable(),
|
|
1144
1349
|
/** Absent when the file changed only through a merge of another branch. */
|
|
1145
|
-
lastChange:
|
|
1350
|
+
lastChange: z7.object({ author: z7.string(), at: IsoDateTimeSchema }).optional()
|
|
1146
1351
|
});
|
|
1147
|
-
var BranchChangesSchema =
|
|
1352
|
+
var BranchChangesSchema = z7.object({
|
|
1148
1353
|
head: BranchCommitSchema,
|
|
1149
|
-
files:
|
|
1150
|
-
total:
|
|
1354
|
+
files: z7.array(BranchFileChangeSchema),
|
|
1355
|
+
total: z7.number().int().nonnegative()
|
|
1151
1356
|
});
|
|
1152
|
-
var WorkspaceConversationSchema =
|
|
1153
|
-
|
|
1154
|
-
|
|
1357
|
+
var WorkspaceConversationSchema = z7.discriminatedUnion("kind", [
|
|
1358
|
+
z7.object({ kind: z7.literal("thread"), id: UlidSchema }),
|
|
1359
|
+
z7.object({ kind: z7.literal("channel"), scope: NameSchema, name: NameSchema })
|
|
1155
1360
|
]);
|
|
1156
1361
|
function workspaceConversationOf(turn) {
|
|
1157
1362
|
if (turn.thread !== void 0) {
|
|
@@ -1159,53 +1364,55 @@ function workspaceConversationOf(turn) {
|
|
|
1159
1364
|
}
|
|
1160
1365
|
return turn.channel === void 0 ? null : { kind: "channel", scope: turn.scope, name: turn.channel };
|
|
1161
1366
|
}
|
|
1162
|
-
var HeldWorkspaceSchema =
|
|
1367
|
+
var HeldWorkspaceSchema = z7.object({
|
|
1163
1368
|
agent: NameSchema,
|
|
1164
1369
|
scope: NameSchema,
|
|
1165
1370
|
conversation: WorkspaceConversationSchema
|
|
1166
1371
|
});
|
|
1167
|
-
var HeldWorkspacesSchema =
|
|
1168
|
-
var SweepResultSchema =
|
|
1169
|
-
var TurnSteerSchema =
|
|
1170
|
-
var RunnerMessageSchema =
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1372
|
+
var HeldWorkspacesSchema = z7.object({ workspaces: z7.array(HeldWorkspaceSchema) });
|
|
1373
|
+
var SweepResultSchema = z7.object({ kept: z7.array(HeldWorkspaceSchema) });
|
|
1374
|
+
var TurnSteerSchema = z7.object({ id: z7.uuid(), text: z7.string().min(1) });
|
|
1375
|
+
var RunnerMessageSchema = z7.discriminatedUnion("type", [
|
|
1376
|
+
z7.object({ type: z7.literal("turn"), job: TurnJobSchema }),
|
|
1377
|
+
z7.object({ type: z7.literal("land"), request: z7.string().min(1), land: LandRequestSchema }),
|
|
1378
|
+
/** Answered with a `MergeOutcome`. */
|
|
1379
|
+
z7.object({ type: z7.literal("close"), request: z7.string().min(1), close: CloseRequestSchema }),
|
|
1380
|
+
z7.object({ type: z7.literal("models"), request: z7.string().min(1), cli: CliKindSchema }),
|
|
1174
1381
|
/** Answered with a `BranchFile`, or null when the branch or the path does not exist. */
|
|
1175
|
-
|
|
1382
|
+
z7.object({ type: z7.literal("file"), request: z7.string().min(1), read: BranchReadSchema }),
|
|
1176
1383
|
/** Answered with `BranchChanges`, or null when the repository or the branch does not exist. */
|
|
1177
|
-
|
|
1178
|
-
type:
|
|
1179
|
-
request:
|
|
1384
|
+
z7.object({
|
|
1385
|
+
type: z7.literal("changes"),
|
|
1386
|
+
request: z7.string().min(1),
|
|
1180
1387
|
read: BranchChangesReadSchema
|
|
1181
1388
|
}),
|
|
1182
1389
|
/** Answered with whether the CLI accepted it; that it took it comes as a `steered` event. */
|
|
1183
|
-
|
|
1184
|
-
type:
|
|
1185
|
-
request:
|
|
1390
|
+
z7.object({
|
|
1391
|
+
type: z7.literal("steer"),
|
|
1392
|
+
request: z7.string().min(1),
|
|
1186
1393
|
turnId: UlidSchema,
|
|
1187
1394
|
steer: TurnSteerSchema
|
|
1188
1395
|
}),
|
|
1189
1396
|
/** Answered with whether the turn was there to stop; it ends `stopped`. */
|
|
1190
|
-
|
|
1397
|
+
z7.object({ type: z7.literal("stop"), request: z7.string().min(1), turnId: UlidSchema }),
|
|
1191
1398
|
/**
|
|
1192
1399
|
* A conversation ended: the runner removes every citizen's workspace for it but those a turn
|
|
1193
1400
|
* still uses, and answers with a `SweepResult` naming those it kept for holding work on no branch.
|
|
1194
1401
|
*/
|
|
1195
|
-
|
|
1196
|
-
type:
|
|
1197
|
-
request:
|
|
1402
|
+
z7.object({
|
|
1403
|
+
type: z7.literal("sweep"),
|
|
1404
|
+
request: z7.string().min(1),
|
|
1198
1405
|
conversation: WorkspaceConversationSchema
|
|
1199
1406
|
})
|
|
1200
1407
|
]);
|
|
1201
|
-
var RunnerAnswerSchema =
|
|
1202
|
-
|
|
1203
|
-
|
|
1408
|
+
var RunnerAnswerSchema = z7.discriminatedUnion("ok", [
|
|
1409
|
+
z7.object({ ok: z7.literal(true), value: z7.unknown() }),
|
|
1410
|
+
z7.object({ ok: z7.literal(false), error: z7.string() })
|
|
1204
1411
|
]);
|
|
1205
|
-
var WarmSessionsSchema =
|
|
1206
|
-
var FileManifestSchema =
|
|
1207
|
-
var FileReadSchema =
|
|
1208
|
-
var FileContentsSchema =
|
|
1412
|
+
var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
|
|
1413
|
+
var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
|
|
1414
|
+
var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
|
|
1415
|
+
var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
|
|
1209
1416
|
function isSafeRelativePath(file) {
|
|
1210
1417
|
if (file.length === 0 || file.startsWith("/") || file.includes("\\") || file.includes("\0")) {
|
|
1211
1418
|
return false;
|
|
@@ -1214,7 +1421,7 @@ function isSafeRelativePath(file) {
|
|
|
1214
1421
|
}
|
|
1215
1422
|
|
|
1216
1423
|
// packages/shared/dist/turns.js
|
|
1217
|
-
var TriggerKindSchema =
|
|
1424
|
+
var TriggerKindSchema = z8.enum([
|
|
1218
1425
|
"mention",
|
|
1219
1426
|
"stage",
|
|
1220
1427
|
"task_done",
|
|
@@ -1225,27 +1432,32 @@ var TriggerKindSchema = z7.enum([
|
|
|
1225
1432
|
"manual",
|
|
1226
1433
|
"ops_event",
|
|
1227
1434
|
"user_post",
|
|
1228
|
-
"closing"
|
|
1435
|
+
"closing",
|
|
1436
|
+
"cron"
|
|
1229
1437
|
]);
|
|
1230
|
-
var TriggerSchema =
|
|
1438
|
+
var TriggerSchema = z8.object({
|
|
1231
1439
|
kind: TriggerKindSchema,
|
|
1232
1440
|
from: NameSchema.optional(),
|
|
1233
|
-
fromUser:
|
|
1234
|
-
reason:
|
|
1441
|
+
fromUser: z8.boolean().default(false),
|
|
1442
|
+
reason: z8.string().default(""),
|
|
1235
1443
|
taskId: UlidSchema.optional(),
|
|
1236
1444
|
messageId: UlidSchema.optional(),
|
|
1237
1445
|
/** The channel the waking message was posted in. */
|
|
1238
|
-
channel: ChannelRefSchema.optional()
|
|
1446
|
+
channel: ChannelRefSchema.optional(),
|
|
1447
|
+
/** The cron that came due, for a `cron` wake. */
|
|
1448
|
+
cronId: UlidSchema.optional()
|
|
1239
1449
|
});
|
|
1240
|
-
var TurnThreadSchema =
|
|
1241
|
-
var TurnDispatchSchema =
|
|
1450
|
+
var TurnThreadSchema = z8.object({ id: UlidSchema, task: z8.boolean() });
|
|
1451
|
+
var TurnDispatchSchema = z8.object({
|
|
1242
1452
|
agent: NameSchema,
|
|
1243
1453
|
project: NameSchema,
|
|
1244
1454
|
thread: TurnThreadSchema.optional(),
|
|
1245
1455
|
channel: NameSchema.optional(),
|
|
1246
1456
|
trigger: TriggerSchema,
|
|
1247
|
-
priority:
|
|
1248
|
-
onboarding:
|
|
1457
|
+
priority: z8.number().int().min(0).max(2),
|
|
1458
|
+
onboarding: z8.boolean().default(false),
|
|
1459
|
+
/** Every cron that came due for this turn, whatever wake it merged into; its prompt quotes each. */
|
|
1460
|
+
crons: z8.array(UlidSchema).optional()
|
|
1249
1461
|
});
|
|
1250
1462
|
function conversationPart(conversation) {
|
|
1251
1463
|
return conversation.thread ?? (conversation.channel === void 0 ? void 0 : `#${conversation.channel}`);
|
|
@@ -1269,7 +1481,7 @@ function channelConversation(scope, channel) {
|
|
|
1269
1481
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1270
1482
|
return place === scope && name !== "general" ? name : void 0;
|
|
1271
1483
|
}
|
|
1272
|
-
var TurnRecordSchema =
|
|
1484
|
+
var TurnRecordSchema = z8.object({
|
|
1273
1485
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1274
1486
|
id: UlidSchema.optional(),
|
|
1275
1487
|
agent: NameSchema,
|
|
@@ -1280,29 +1492,31 @@ var TurnRecordSchema = z7.object({
|
|
|
1280
1492
|
channel: NameSchema.optional(),
|
|
1281
1493
|
runner: NameSchema,
|
|
1282
1494
|
cli: CliKindSchema.nullable(),
|
|
1283
|
-
session:
|
|
1495
|
+
session: z8.string().nullable(),
|
|
1284
1496
|
trigger: TriggerSchema,
|
|
1285
1497
|
startedAt: IsoDateTimeSchema,
|
|
1286
1498
|
endedAt: IsoDateTimeSchema.nullable(),
|
|
1287
1499
|
exitReason: TurnExitReasonSchema.nullable(),
|
|
1288
1500
|
status: TurnStatusSchema.nullable(),
|
|
1289
|
-
error:
|
|
1501
|
+
error: z8.string().nullable(),
|
|
1290
1502
|
usage: UsageSchema.nullable(),
|
|
1291
|
-
costUsd:
|
|
1503
|
+
costUsd: z8.number().nonnegative().default(0),
|
|
1292
1504
|
/**
|
|
1293
1505
|
* The running total the CLI reported for the session after this turn, when it reports one. A
|
|
1294
1506
|
* resumed session starts from it, so the next turn's own cost is measured from here.
|
|
1295
1507
|
*/
|
|
1296
|
-
sessionCostUsd:
|
|
1297
|
-
toolCalls:
|
|
1508
|
+
sessionCostUsd: z8.number().nonnegative().optional(),
|
|
1509
|
+
toolCalls: z8.number().int().nonnegative().default(0),
|
|
1298
1510
|
/** The model the CLI reported for this turn, else the model configured for the agent, else null. */
|
|
1299
|
-
model:
|
|
1511
|
+
model: z8.string().nullable().default(null),
|
|
1300
1512
|
/** The task branch the turn left work on, with its commit, as the runner reported it. */
|
|
1301
1513
|
work: TurnWorkSchema.optional(),
|
|
1302
1514
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1303
|
-
stoppedBy: NameSchema.optional()
|
|
1515
|
+
stoppedBy: NameSchema.optional(),
|
|
1516
|
+
/** The crons whose fire started the turn, which record how it ended. */
|
|
1517
|
+
crons: z8.array(UlidSchema).optional()
|
|
1304
1518
|
});
|
|
1305
|
-
var RunningTurnSchema =
|
|
1519
|
+
var RunningTurnSchema = z8.object({
|
|
1306
1520
|
turnId: UlidSchema,
|
|
1307
1521
|
agent: NameSchema,
|
|
1308
1522
|
scope: NameSchema,
|
|
@@ -1310,49 +1524,49 @@ var RunningTurnSchema = z7.object({
|
|
|
1310
1524
|
channel: NameSchema.optional(),
|
|
1311
1525
|
cli: CliKindSchema,
|
|
1312
1526
|
askedIn: ChannelRefSchema.optional(),
|
|
1313
|
-
steerable:
|
|
1314
|
-
stoppable:
|
|
1527
|
+
steerable: z8.boolean(),
|
|
1528
|
+
stoppable: z8.boolean()
|
|
1315
1529
|
});
|
|
1316
|
-
var SessionsFileSchema =
|
|
1317
|
-
claude:
|
|
1318
|
-
codex:
|
|
1530
|
+
var SessionsFileSchema = z8.object({
|
|
1531
|
+
claude: z8.string().optional(),
|
|
1532
|
+
codex: z8.string().optional(),
|
|
1319
1533
|
runner: NameSchema.optional()
|
|
1320
1534
|
});
|
|
1321
|
-
var WakeRequestSchema =
|
|
1535
|
+
var WakeRequestSchema = z8.object({
|
|
1322
1536
|
agent: NameSchema,
|
|
1323
1537
|
project: NameSchema,
|
|
1324
|
-
reason:
|
|
1538
|
+
reason: z8.string().default("manual wake"),
|
|
1325
1539
|
/** A manual wake, or a reflection turn requested ahead of the cadence. */
|
|
1326
|
-
kind:
|
|
1540
|
+
kind: z8.enum(["manual", "reflection"]).default("manual")
|
|
1327
1541
|
});
|
|
1328
|
-
var TurnHistoryEntrySchema =
|
|
1542
|
+
var TurnHistoryEntrySchema = z8.object({
|
|
1329
1543
|
/** The id of the event that ended the turn. */
|
|
1330
1544
|
id: UlidSchema,
|
|
1331
1545
|
/** The turn's own id, when it has a transcript to open; turns before transcripts have none. */
|
|
1332
1546
|
turnId: UlidSchema.optional(),
|
|
1333
1547
|
ts: IsoDateTimeSchema,
|
|
1334
|
-
outcome:
|
|
1548
|
+
outcome: z8.enum(["completed", "failed"]),
|
|
1335
1549
|
/** A project slug, or the society scope. */
|
|
1336
1550
|
project: NameSchema,
|
|
1337
1551
|
/** The thread whose conversation the turn was in; absent for the home conversation. */
|
|
1338
1552
|
thread: UlidSchema.optional(),
|
|
1339
1553
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1340
1554
|
channel: NameSchema.optional(),
|
|
1341
|
-
trigger:
|
|
1342
|
-
exitReason:
|
|
1343
|
-
costUsd:
|
|
1555
|
+
trigger: z8.string(),
|
|
1556
|
+
exitReason: z8.string().nullable(),
|
|
1557
|
+
costUsd: z8.number(),
|
|
1344
1558
|
/** The tokens the turn used; absent for turns logged before turns recorded them. */
|
|
1345
1559
|
usage: UsageSchema.optional(),
|
|
1346
|
-
model:
|
|
1347
|
-
summary:
|
|
1348
|
-
error:
|
|
1560
|
+
model: z8.string().nullable(),
|
|
1561
|
+
summary: z8.string().nullable(),
|
|
1562
|
+
error: z8.string().nullable(),
|
|
1349
1563
|
/** When the log recorded the turn's start; absent when the start is missing from the log. */
|
|
1350
1564
|
startedAt: IsoDateTimeSchema.optional(),
|
|
1351
|
-
toolCalls:
|
|
1565
|
+
toolCalls: z8.number().int().nonnegative().optional()
|
|
1352
1566
|
});
|
|
1353
1567
|
|
|
1354
1568
|
// packages/shared/dist/ops.js
|
|
1355
|
-
import { z as
|
|
1569
|
+
import { z as z9 } from "zod";
|
|
1356
1570
|
var OPS_SIGNAL_KINDS = [
|
|
1357
1571
|
"waiting_stage",
|
|
1358
1572
|
"backlog",
|
|
@@ -1365,9 +1579,10 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1365
1579
|
"scaled",
|
|
1366
1580
|
"runner",
|
|
1367
1581
|
"home_conflict",
|
|
1368
|
-
"stuck_workspace"
|
|
1582
|
+
"stuck_workspace",
|
|
1583
|
+
"cron_failing"
|
|
1369
1584
|
];
|
|
1370
|
-
var OpsSignalKindSchema =
|
|
1585
|
+
var OpsSignalKindSchema = z9.enum(OPS_SIGNAL_KINDS);
|
|
1371
1586
|
var WAKING_SIGNAL_KINDS = [
|
|
1372
1587
|
"backlog",
|
|
1373
1588
|
"role_gap",
|
|
@@ -1377,15 +1592,15 @@ var WAKING_SIGNAL_KINDS = [
|
|
|
1377
1592
|
"blocked_capability",
|
|
1378
1593
|
"scaled"
|
|
1379
1594
|
];
|
|
1380
|
-
var OpsSignalSchema =
|
|
1595
|
+
var OpsSignalSchema = z9.object({
|
|
1381
1596
|
kind: OpsSignalKindSchema,
|
|
1382
1597
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
1383
|
-
key:
|
|
1598
|
+
key: z9.string().min(1),
|
|
1384
1599
|
/** One line a reader can act on. */
|
|
1385
|
-
summary:
|
|
1600
|
+
summary: z9.string().min(1),
|
|
1386
1601
|
/** The counter or timer value behind the signal. */
|
|
1387
|
-
value:
|
|
1388
|
-
threshold:
|
|
1602
|
+
value: z9.number(),
|
|
1603
|
+
threshold: z9.number().optional(),
|
|
1389
1604
|
project: NameSchema.optional(),
|
|
1390
1605
|
agent: NameSchema.optional(),
|
|
1391
1606
|
role: NameSchema.optional(),
|
|
@@ -1393,8 +1608,8 @@ var OpsSignalSchema = z8.object({
|
|
|
1393
1608
|
});
|
|
1394
1609
|
|
|
1395
1610
|
// packages/shared/dist/metrics.js
|
|
1396
|
-
import { z as
|
|
1397
|
-
var MetricsWindowSchema =
|
|
1611
|
+
import { z as z10 } from "zod";
|
|
1612
|
+
var MetricsWindowSchema = z10.enum(["24h", "7d", "all"]);
|
|
1398
1613
|
var METRICS_WINDOW_MS = {
|
|
1399
1614
|
"24h": 24 * 36e5,
|
|
1400
1615
|
"7d": 7 * 24 * 36e5,
|
|
@@ -1405,9 +1620,9 @@ function isBoardAction(tool) {
|
|
|
1405
1620
|
const verb = /^mcp__board__(.+)$/.exec(tool)?.[1];
|
|
1406
1621
|
return verb !== void 0 && !READ_VERBS.some((read2) => read2 === verb);
|
|
1407
1622
|
}
|
|
1408
|
-
var CountSchema =
|
|
1409
|
-
var MsSchema =
|
|
1410
|
-
var MetricsSchema =
|
|
1623
|
+
var CountSchema = z10.number().int().nonnegative();
|
|
1624
|
+
var MsSchema = z10.number().nonnegative();
|
|
1625
|
+
var MetricsSchema = z10.object({
|
|
1411
1626
|
window: MetricsWindowSchema,
|
|
1412
1627
|
since: IsoDateTimeSchema.nullable(),
|
|
1413
1628
|
until: IsoDateTimeSchema,
|
|
@@ -1415,12 +1630,12 @@ var MetricsSchema = z9.object({
|
|
|
1415
1630
|
* Finished turns that changed nothing on the board. Reflections are left out, since they write
|
|
1416
1631
|
* memory, not the board; turns without a transcript are counted apart, their actions unknown.
|
|
1417
1632
|
*/
|
|
1418
|
-
idle:
|
|
1633
|
+
idle: z10.object({
|
|
1419
1634
|
turns: CountSchema,
|
|
1420
1635
|
idle: CountSchema,
|
|
1421
1636
|
unknown: CountSchema,
|
|
1422
|
-
byTrigger:
|
|
1423
|
-
byAgent:
|
|
1637
|
+
byTrigger: z10.array(z10.object({ trigger: z10.string(), turns: CountSchema, idle: CountSchema })),
|
|
1638
|
+
byAgent: z10.array(z10.object({
|
|
1424
1639
|
agent: NameSchema,
|
|
1425
1640
|
role: NameSchema.nullable(),
|
|
1426
1641
|
turns: CountSchema,
|
|
@@ -1428,33 +1643,33 @@ var MetricsSchema = z9.object({
|
|
|
1428
1643
|
}))
|
|
1429
1644
|
}),
|
|
1430
1645
|
/** Work sent back to an earlier stage: over the tasks finished in the window, and by where. */
|
|
1431
|
-
sentBack:
|
|
1646
|
+
sentBack: z10.object({
|
|
1432
1647
|
finished: CountSchema,
|
|
1433
1648
|
sendBacks: CountSchema,
|
|
1434
1649
|
tasksSentBack: CountSchema,
|
|
1435
|
-
byStage:
|
|
1436
|
-
bySender:
|
|
1650
|
+
byStage: z10.array(z10.object({ project: NameSchema, stage: z10.string(), count: CountSchema })),
|
|
1651
|
+
bySender: z10.array(z10.object({ agent: NameSchema, count: CountSchema }))
|
|
1437
1652
|
}),
|
|
1438
1653
|
/** Posts in the threads of the tasks finished in the window, the board's own notices left out. */
|
|
1439
|
-
messages:
|
|
1654
|
+
messages: z10.object({
|
|
1440
1655
|
finished: CountSchema,
|
|
1441
1656
|
messages: CountSchema,
|
|
1442
|
-
byProject:
|
|
1443
|
-
busiest:
|
|
1657
|
+
byProject: z10.array(z10.object({ project: NameSchema, finished: CountSchema, messages: CountSchema })),
|
|
1658
|
+
busiest: z10.array(z10.object({
|
|
1444
1659
|
taskId: UlidSchema,
|
|
1445
|
-
title:
|
|
1660
|
+
title: z10.string(),
|
|
1446
1661
|
project: NameSchema,
|
|
1447
1662
|
messages: CountSchema
|
|
1448
1663
|
}))
|
|
1449
1664
|
}),
|
|
1450
1665
|
/** Time from a mention to the start of the turn it woke, in the conversation it was made in. */
|
|
1451
|
-
latency:
|
|
1666
|
+
latency: z10.object({
|
|
1452
1667
|
mentions: CountSchema,
|
|
1453
1668
|
/** Mentions no turn has followed yet. */
|
|
1454
1669
|
unanswered: CountSchema,
|
|
1455
1670
|
medianMs: MsSchema.nullable(),
|
|
1456
1671
|
slowestMs: MsSchema.nullable(),
|
|
1457
|
-
byAgent:
|
|
1672
|
+
byAgent: z10.array(z10.object({
|
|
1458
1673
|
agent: NameSchema,
|
|
1459
1674
|
mentions: CountSchema,
|
|
1460
1675
|
medianMs: MsSchema,
|
|
@@ -1462,61 +1677,61 @@ var MetricsSchema = z9.object({
|
|
|
1462
1677
|
}))
|
|
1463
1678
|
}),
|
|
1464
1679
|
/** The user's decisions: proposals decided, questions answered, and stages passed or sent back. */
|
|
1465
|
-
decisions:
|
|
1680
|
+
decisions: z10.object({
|
|
1466
1681
|
total: CountSchema,
|
|
1467
|
-
byDay:
|
|
1468
|
-
day:
|
|
1682
|
+
byDay: z10.array(z10.object({
|
|
1683
|
+
day: z10.string(),
|
|
1469
1684
|
proposals: CountSchema,
|
|
1470
1685
|
answers: CountSchema,
|
|
1471
1686
|
stages: CountSchema
|
|
1472
1687
|
}))
|
|
1473
1688
|
}),
|
|
1474
1689
|
/** Tasks that needed a capability no connected runner offered, and whether they still do. */
|
|
1475
|
-
blocked:
|
|
1690
|
+
blocked: z10.array(z10.object({
|
|
1476
1691
|
taskId: UlidSchema,
|
|
1477
|
-
title:
|
|
1692
|
+
title: z10.string().nullable(),
|
|
1478
1693
|
project: NameSchema.nullable(),
|
|
1479
|
-
summary:
|
|
1694
|
+
summary: z10.string(),
|
|
1480
1695
|
firstAt: IsoDateTimeSchema,
|
|
1481
1696
|
lastAt: IsoDateTimeSchema,
|
|
1482
|
-
holds:
|
|
1697
|
+
holds: z10.boolean()
|
|
1483
1698
|
}))
|
|
1484
1699
|
});
|
|
1485
1700
|
|
|
1486
1701
|
// packages/shared/dist/config.js
|
|
1487
|
-
import { z as
|
|
1488
|
-
var LogLevelSchema =
|
|
1489
|
-
var GithubSignInSchema =
|
|
1490
|
-
clientId:
|
|
1491
|
-
clientSecret:
|
|
1702
|
+
import { z as z11 } from "zod";
|
|
1703
|
+
var LogLevelSchema = z11.enum(["trace", "debug", "info", "warn", "error", "fatal"]);
|
|
1704
|
+
var GithubSignInSchema = z11.object({
|
|
1705
|
+
clientId: z11.string().min(1),
|
|
1706
|
+
clientSecret: z11.string().min(1),
|
|
1492
1707
|
/** Logins, lowercased; GitHub logins are case-insensitive. */
|
|
1493
|
-
users:
|
|
1708
|
+
users: z11.array(z11.string().min(1)).min(1)
|
|
1494
1709
|
});
|
|
1495
|
-
var ServerConfigSchema =
|
|
1496
|
-
dataDir:
|
|
1497
|
-
host:
|
|
1498
|
-
port:
|
|
1710
|
+
var ServerConfigSchema = z11.object({
|
|
1711
|
+
dataDir: z11.string().min(1),
|
|
1712
|
+
host: z11.string().min(1),
|
|
1713
|
+
port: z11.number().int().positive().max(65535),
|
|
1499
1714
|
/**
|
|
1500
1715
|
* An address every runner's agents reach this server at, for example https://board.example, or
|
|
1501
1716
|
* null for each runner to use the address it reaches the server at itself.
|
|
1502
1717
|
*/
|
|
1503
|
-
publicUrl:
|
|
1718
|
+
publicUrl: z11.string().min(1).nullable(),
|
|
1504
1719
|
logLevel: LogLevelSchema,
|
|
1505
1720
|
/** Turns running at once across every runner, or null for no limit beyond each runner's own. */
|
|
1506
|
-
concurrency:
|
|
1721
|
+
concurrency: z11.number().int().positive().nullable(),
|
|
1507
1722
|
/** How long one turn may run before it is stopped, or null for no limit. */
|
|
1508
|
-
turnTimeoutMs:
|
|
1723
|
+
turnTimeoutMs: z11.number().int().positive().nullable(),
|
|
1509
1724
|
/** Rounds of tool calls one Claude turn may take, or null for no limit. */
|
|
1510
|
-
toolRounds:
|
|
1725
|
+
toolRounds: z11.number().int().positive().nullable(),
|
|
1511
1726
|
/** How long a resident session stays warm after its last turn. */
|
|
1512
|
-
residentIdleMs:
|
|
1727
|
+
residentIdleMs: z11.number().int().positive(),
|
|
1513
1728
|
/** Signing in to the board with GitHub, or null when only a token lets anyone in. */
|
|
1514
1729
|
github: GithubSignInSchema.nullable(),
|
|
1515
1730
|
/**
|
|
1516
1731
|
* Reverse proxies in front of the server whose X-Forwarded-For entries name the client, for the
|
|
1517
1732
|
* rate limits on what anyone may ask without a token: 0 when clients connect directly.
|
|
1518
1733
|
*/
|
|
1519
|
-
trustedProxies:
|
|
1734
|
+
trustedProxies: z11.number().int().min(0).max(16)
|
|
1520
1735
|
});
|
|
1521
1736
|
function limit(value, fallback) {
|
|
1522
1737
|
if (value === void 0 || value.trim() === "") {
|
|
@@ -1554,25 +1769,25 @@ function loadServerConfig(env) {
|
|
|
1554
1769
|
trustedProxies: Number(env["STELLARIS_TRUSTED_PROXIES"] ?? "0")
|
|
1555
1770
|
});
|
|
1556
1771
|
}
|
|
1557
|
-
var RunnerConfigSchema =
|
|
1772
|
+
var RunnerConfigSchema = z11.object({
|
|
1558
1773
|
/** The board server, for example http://127.0.0.1:4700. */
|
|
1559
|
-
serverUrl:
|
|
1774
|
+
serverUrl: z11.string().url(),
|
|
1560
1775
|
/**
|
|
1561
1776
|
* The runner's token, from `runner add`, or null for the one it saved when it was enrolled, or
|
|
1562
1777
|
* to enroll it.
|
|
1563
1778
|
*/
|
|
1564
|
-
token:
|
|
1779
|
+
token: z11.string().min(1).nullable(),
|
|
1565
1780
|
/** The runner's own data directory: the board mirror, agent homes, repositories, worktrees. */
|
|
1566
|
-
dataDir:
|
|
1781
|
+
dataDir: z11.string().min(1),
|
|
1567
1782
|
logLevel: LogLevelSchema,
|
|
1568
1783
|
/** Turns this machine runs at once, or null for no limit. */
|
|
1569
|
-
slots:
|
|
1570
|
-
clis:
|
|
1571
|
-
capabilities:
|
|
1784
|
+
slots: z11.number().int().positive().nullable(),
|
|
1785
|
+
clis: z11.array(CliKindSchema).min(1),
|
|
1786
|
+
capabilities: z11.array(z11.string())
|
|
1572
1787
|
});
|
|
1573
1788
|
|
|
1574
1789
|
// packages/shared/dist/version.js
|
|
1575
|
-
var STELLARIS_VERSION = "0.3.
|
|
1790
|
+
var STELLARIS_VERSION = "0.3.14";
|
|
1576
1791
|
|
|
1577
1792
|
// packages/board-core/dist/errors.js
|
|
1578
1793
|
var BoardError = class extends Error {
|
|
@@ -2120,32 +2335,32 @@ function filesOf(lines, status) {
|
|
|
2120
2335
|
}
|
|
2121
2336
|
|
|
2122
2337
|
// packages/board-core/dist/metrics.js
|
|
2123
|
-
import { z as
|
|
2124
|
-
var TurnPayload =
|
|
2125
|
-
turnId:
|
|
2126
|
-
project:
|
|
2127
|
-
thread:
|
|
2128
|
-
channel:
|
|
2129
|
-
trigger:
|
|
2338
|
+
import { z as z12 } from "zod";
|
|
2339
|
+
var TurnPayload = z12.object({
|
|
2340
|
+
turnId: z12.string().optional(),
|
|
2341
|
+
project: z12.string(),
|
|
2342
|
+
thread: z12.string().optional(),
|
|
2343
|
+
channel: z12.string().optional(),
|
|
2344
|
+
trigger: z12.string()
|
|
2130
2345
|
});
|
|
2131
|
-
var TaskPayload =
|
|
2132
|
-
taskId:
|
|
2133
|
-
project:
|
|
2134
|
-
from:
|
|
2346
|
+
var TaskPayload = z12.object({
|
|
2347
|
+
taskId: z12.string(),
|
|
2348
|
+
project: z12.string(),
|
|
2349
|
+
from: z12.string().optional()
|
|
2135
2350
|
});
|
|
2136
|
-
var MessagePayload =
|
|
2137
|
-
id:
|
|
2138
|
-
channel:
|
|
2139
|
-
thread:
|
|
2140
|
-
mentions:
|
|
2351
|
+
var MessagePayload = z12.object({
|
|
2352
|
+
id: z12.string().optional(),
|
|
2353
|
+
channel: z12.string(),
|
|
2354
|
+
thread: z12.string().nullable().optional(),
|
|
2355
|
+
mentions: z12.array(z12.string()).default([])
|
|
2141
2356
|
});
|
|
2142
|
-
var SteeredPayload =
|
|
2143
|
-
var SignalPayload =
|
|
2144
|
-
kind:
|
|
2145
|
-
key:
|
|
2146
|
-
summary:
|
|
2147
|
-
taskId:
|
|
2148
|
-
project:
|
|
2357
|
+
var SteeredPayload = z12.object({ messages: z12.array(z12.string()) });
|
|
2358
|
+
var SignalPayload = z12.object({
|
|
2359
|
+
kind: z12.string(),
|
|
2360
|
+
key: z12.string(),
|
|
2361
|
+
summary: z12.string(),
|
|
2362
|
+
taskId: z12.string().optional(),
|
|
2363
|
+
project: z12.string().optional()
|
|
2149
2364
|
});
|
|
2150
2365
|
function median(values) {
|
|
2151
2366
|
const sorted = values.toSorted((a, b) => a - b);
|
|
@@ -2514,6 +2729,13 @@ var BoardPaths = class {
|
|
|
2514
2729
|
members() {
|
|
2515
2730
|
return path5.join(this.society, "members");
|
|
2516
2731
|
}
|
|
2732
|
+
/** Crons, one file each with its note as the body; ended ones stay. */
|
|
2733
|
+
crons() {
|
|
2734
|
+
return path5.join(this.society, "crons");
|
|
2735
|
+
}
|
|
2736
|
+
cron(id) {
|
|
2737
|
+
return path5.join(this.crons(), `${id}.md`);
|
|
2738
|
+
}
|
|
2517
2739
|
member(name) {
|
|
2518
2740
|
return path5.join(this.members(), `${name}.md`);
|
|
2519
2741
|
}
|
|
@@ -2660,41 +2882,41 @@ function namesList(names) {
|
|
|
2660
2882
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
2661
2883
|
}
|
|
2662
2884
|
var SKIPPED_IN_HOME = /* @__PURE__ */ new Set([".git", "turns", ".claude", ".codex"]);
|
|
2663
|
-
var RunnerTokensSchema =
|
|
2664
|
-
runners:
|
|
2885
|
+
var RunnerTokensSchema = z13.object({
|
|
2886
|
+
runners: z13.record(z13.string(), z13.object({ tokenHash: z13.string().min(1) }))
|
|
2665
2887
|
});
|
|
2666
|
-
var SignInsSchema =
|
|
2667
|
-
signIns:
|
|
2668
|
-
tokenHash:
|
|
2669
|
-
login:
|
|
2888
|
+
var SignInsSchema = z13.object({
|
|
2889
|
+
signIns: z13.array(z13.object({
|
|
2890
|
+
tokenHash: z13.string().min(1),
|
|
2891
|
+
login: z13.string().min(1),
|
|
2670
2892
|
/** The account's picture at the identity provider; sign-ins from before it have none. */
|
|
2671
|
-
avatarUrl:
|
|
2893
|
+
avatarUrl: z13.url().optional(),
|
|
2672
2894
|
createdAt: IsoDateTimeSchema,
|
|
2673
2895
|
expiresAt: IsoDateTimeSchema
|
|
2674
2896
|
})).default([])
|
|
2675
2897
|
});
|
|
2676
|
-
var TurnHistoryPayloadSchema =
|
|
2898
|
+
var TurnHistoryPayloadSchema = z13.object({
|
|
2677
2899
|
project: NameSchema,
|
|
2678
2900
|
thread: UlidSchema.optional(),
|
|
2679
2901
|
channel: NameSchema.optional(),
|
|
2680
|
-
trigger:
|
|
2681
|
-
exitReason:
|
|
2682
|
-
costUsd:
|
|
2902
|
+
trigger: z13.string().default("manual"),
|
|
2903
|
+
exitReason: z13.string().nullable().default(null),
|
|
2904
|
+
costUsd: z13.number().default(0),
|
|
2683
2905
|
usage: UsageSchema.optional(),
|
|
2684
|
-
model:
|
|
2685
|
-
summary:
|
|
2686
|
-
error:
|
|
2687
|
-
toolCalls:
|
|
2906
|
+
model: z13.string().nullable().default(null),
|
|
2907
|
+
summary: z13.string().nullable().default(null),
|
|
2908
|
+
error: z13.string().nullable().default(null),
|
|
2909
|
+
toolCalls: z13.number().int().nonnegative().optional(),
|
|
2688
2910
|
turnId: UlidSchema.optional()
|
|
2689
2911
|
});
|
|
2690
|
-
var TurnStartPayloadSchema =
|
|
2691
|
-
var CursorsSchema =
|
|
2692
|
-
digest:
|
|
2693
|
-
scopes:
|
|
2694
|
-
threadsFrom:
|
|
2695
|
-
channelsFrom:
|
|
2912
|
+
var TurnStartPayloadSchema = z13.object({ project: NameSchema, turnId: UlidSchema.optional() });
|
|
2913
|
+
var CursorsSchema = z13.object({
|
|
2914
|
+
digest: z13.string().nullable(),
|
|
2915
|
+
scopes: z13.record(z13.string(), z13.string()).default({}),
|
|
2916
|
+
threadsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
|
|
2917
|
+
channelsFrom: z13.record(z13.string(), z13.string().nullable()).default({}),
|
|
2696
2918
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
2697
|
-
news:
|
|
2919
|
+
news: z13.string().optional()
|
|
2698
2920
|
});
|
|
2699
2921
|
var NO_CURSORS = { digest: null, scopes: {}, threadsFrom: {}, channelsFrom: {} };
|
|
2700
2922
|
function conversationKey(scope, part) {
|
|
@@ -2733,7 +2955,7 @@ function byId(a, b) {
|
|
|
2733
2955
|
function turnTime(record) {
|
|
2734
2956
|
return record.endedAt ?? record.startedAt;
|
|
2735
2957
|
}
|
|
2736
|
-
var PausedSchema =
|
|
2958
|
+
var PausedSchema = z13.object({ paused: z13.boolean() });
|
|
2737
2959
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
2738
2960
|
var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
|
|
2739
2961
|
function withoutMentions(text) {
|
|
@@ -2745,7 +2967,7 @@ var OPS_WAKE_TRIGGER = "ops_event";
|
|
|
2745
2967
|
var FRONT_DESK_TRIGGER = "user_post";
|
|
2746
2968
|
var OPS_CHANNELS = ["governance"];
|
|
2747
2969
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
2748
|
-
var AlignmentsSchema =
|
|
2970
|
+
var AlignmentsSchema = z13.object({ applied: z13.array(z13.string()).default([]) });
|
|
2749
2971
|
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
2750
2972
|
var THREAD_RECORDS = "threads-as-records";
|
|
2751
2973
|
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
@@ -2757,6 +2979,8 @@ var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
|
2757
2979
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
2758
2980
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
2759
2981
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
2982
|
+
var CRON_VERBS = "cron-verbs-granted";
|
|
2983
|
+
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
2760
2984
|
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
2761
2985
|
var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
|
|
2762
2986
|
var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
@@ -2958,7 +3182,8 @@ var Board = class _Board {
|
|
|
2958
3182
|
ASKS_CHANNEL_OPENED,
|
|
2959
3183
|
KNOWLEDGE_REMOVAL,
|
|
2960
3184
|
CHANNEL_CONVERSATIONS,
|
|
2961
|
-
DASHBOARD_UPDATES
|
|
3185
|
+
DASHBOARD_UPDATES,
|
|
3186
|
+
CRON_VERBS
|
|
2962
3187
|
].filter((name) => !applied.includes(name));
|
|
2963
3188
|
if (pending.length === 0) {
|
|
2964
3189
|
return;
|
|
@@ -2997,6 +3222,9 @@ var Board = class _Board {
|
|
|
2997
3222
|
await this.grantBesideKnowledge("update_dashboard");
|
|
2998
3223
|
await this.rewordDashboards();
|
|
2999
3224
|
}
|
|
3225
|
+
if (pending.includes(CRON_VERBS)) {
|
|
3226
|
+
await this.grantToEveryRole(CRON_VERB_NAMES);
|
|
3227
|
+
}
|
|
3000
3228
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3001
3229
|
}
|
|
3002
3230
|
/**
|
|
@@ -3010,6 +3238,15 @@ var Board = class _Board {
|
|
|
3010
3238
|
}
|
|
3011
3239
|
}
|
|
3012
3240
|
}
|
|
3241
|
+
/** Grants verbs once to every charter but the user's, which holds every verb already. */
|
|
3242
|
+
async grantToEveryRole(verbs) {
|
|
3243
|
+
for (const charter of await this.listRoles()) {
|
|
3244
|
+
const missing = verbs.filter((verb) => !charter.verbs.includes(verb));
|
|
3245
|
+
if (charter.name !== USER_ROLE && missing.length > 0) {
|
|
3246
|
+
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, ...missing] }, { verbsAdded: missing });
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3013
3250
|
/** A dashboard still saying agents may edit its file says how it changes now. */
|
|
3014
3251
|
async rewordDashboards() {
|
|
3015
3252
|
for (const slug of await listDirs(this.paths.projects())) {
|
|
@@ -3154,7 +3391,7 @@ var Board = class _Board {
|
|
|
3154
3391
|
}
|
|
3155
3392
|
}
|
|
3156
3393
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3157
|
-
const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file),
|
|
3394
|
+
const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z13.record(z13.string(), z13.unknown()));
|
|
3158
3395
|
const legacy = raw.data["thread"];
|
|
3159
3396
|
if (legacy === void 0) {
|
|
3160
3397
|
continue;
|
|
@@ -3232,7 +3469,7 @@ var Board = class _Board {
|
|
|
3232
3469
|
* turns, so it follows nothing now.
|
|
3233
3470
|
*/
|
|
3234
3471
|
async renameInboxCursors() {
|
|
3235
|
-
const Legacy =
|
|
3472
|
+
const Legacy = z13.object({ inbox: z13.string().nullable() });
|
|
3236
3473
|
for (const agent of await this.listAgents()) {
|
|
3237
3474
|
const file = this.paths.agentCursors(agent.name);
|
|
3238
3475
|
if (await exists(file)) {
|
|
@@ -3278,7 +3515,7 @@ ${charter.purpose}
|
|
|
3278
3515
|
*/
|
|
3279
3516
|
async alignSeedRole(seed) {
|
|
3280
3517
|
const file = this.paths.role(seed.name);
|
|
3281
|
-
const raw = await readMarkdown(file,
|
|
3518
|
+
const raw = await readMarkdown(file, z13.record(z13.string(), z13.unknown()));
|
|
3282
3519
|
const current = RoleCharterSchema.parse(raw.data);
|
|
3283
3520
|
const verbsAdded = seed.verbs.filter((verb) => !current.verbs.includes(verb));
|
|
3284
3521
|
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));
|
|
@@ -3591,14 +3828,14 @@ ${charter.purpose}
|
|
|
3591
3828
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
3592
3829
|
async readSocietyNorms() {
|
|
3593
3830
|
const file = this.paths.societyKnowledgeFile("norms");
|
|
3594
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
3831
|
+
return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
|
|
3595
3832
|
}
|
|
3596
3833
|
/** Knowledge topics of a project, or of the society when the project is null. */
|
|
3597
3834
|
async listKnowledge(project) {
|
|
3598
3835
|
const dir = project === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project);
|
|
3599
3836
|
const topics = [];
|
|
3600
3837
|
for (const file of await listFiles(dir)) {
|
|
3601
|
-
const doc = await readMarkdown(path6.join(dir, file),
|
|
3838
|
+
const doc = await readMarkdown(path6.join(dir, file), z13.record(z13.string(), z13.unknown()));
|
|
3602
3839
|
const parsed = KnowledgeSchema.safeParse({
|
|
3603
3840
|
...doc.data,
|
|
3604
3841
|
project,
|
|
@@ -3998,6 +4235,21 @@ ${DASHBOARD_LINE}
|
|
|
3998
4235
|
await this.events.append("paused.changed", actor.name, { paused });
|
|
3999
4236
|
});
|
|
4000
4237
|
}
|
|
4238
|
+
/** Sets the society's time zone, the user's alone: crons that name none are read in it. */
|
|
4239
|
+
async setTimezone(actor, zone) {
|
|
4240
|
+
this.assertUser(actor, "only the user sets the society's time zone");
|
|
4241
|
+
const timezone = TimeZoneSchema.parse(zone);
|
|
4242
|
+
return this.mutex.run(async () => {
|
|
4243
|
+
const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
|
|
4244
|
+
const society = { ...doc.data, timezone };
|
|
4245
|
+
await writeMarkdown(this.paths.societyFile(), society, doc.body);
|
|
4246
|
+
await this.events.append("society.configured", actor.name, {
|
|
4247
|
+
timezone,
|
|
4248
|
+
previousTimezone: doc.data.timezone
|
|
4249
|
+
});
|
|
4250
|
+
return society;
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4001
4253
|
/** Releases every claim whose lease has expired. Called by the scheduler on its tick. */
|
|
4002
4254
|
async expireLeases() {
|
|
4003
4255
|
return this.mutex.run(async () => {
|
|
@@ -4420,6 +4672,241 @@ ${current.body}`);
|
|
|
4420
4672
|
return updated;
|
|
4421
4673
|
});
|
|
4422
4674
|
}
|
|
4675
|
+
/** Every cron, ended ones included, oldest first. */
|
|
4676
|
+
async listCrons() {
|
|
4677
|
+
const crons = [];
|
|
4678
|
+
for (const file of await listFiles(this.paths.crons())) {
|
|
4679
|
+
crons.push(await this.readCronFile(path6.join(this.paths.crons(), file)));
|
|
4680
|
+
}
|
|
4681
|
+
return crons;
|
|
4682
|
+
}
|
|
4683
|
+
async readCron(id) {
|
|
4684
|
+
const file = this.paths.cron(id);
|
|
4685
|
+
if (!await exists(file)) {
|
|
4686
|
+
throw new BoardError("NOT_FOUND", `cron ${id} not found`);
|
|
4687
|
+
}
|
|
4688
|
+
return this.readCronFile(file);
|
|
4689
|
+
}
|
|
4690
|
+
/**
|
|
4691
|
+
* Sets a cron: one that wakes its setter, or, set by a planning role, any citizen. Without a
|
|
4692
|
+
* place named it fires in the conversation of the turn that set it.
|
|
4693
|
+
*/
|
|
4694
|
+
async createCron(actor, input) {
|
|
4695
|
+
const args = VerbInputs.create_cron.parse(input);
|
|
4696
|
+
await this.authorize(actor, "create_cron");
|
|
4697
|
+
const target = args.agent ?? actor.name;
|
|
4698
|
+
this.assertMayCron(actor, target, "set");
|
|
4699
|
+
if (args.cron === void 0 === (args.at === void 0)) {
|
|
4700
|
+
throw new BoardError("VALIDATION", "a cron needs either cron, a five-field expression, or at, one time");
|
|
4701
|
+
}
|
|
4702
|
+
return this.mutex.run(async () => {
|
|
4703
|
+
const agent = await this.readAgent(target);
|
|
4704
|
+
if (agent.status !== "active" || agent.cli === null) {
|
|
4705
|
+
throw new BoardError("INVALID_STATE", `${target} takes no turns, so no cron can wake it`);
|
|
4706
|
+
}
|
|
4707
|
+
const where = await this.cronConversation(actor, agent, args);
|
|
4708
|
+
const now = this.now();
|
|
4709
|
+
const schedule = await this.cronSchedule(args, void 0, now);
|
|
4710
|
+
if (schedule === void 0) {
|
|
4711
|
+
throw new BoardError("VALIDATION", "a cron needs a schedule");
|
|
4712
|
+
}
|
|
4713
|
+
const cron = await this.writeCron({
|
|
4714
|
+
id: this.newId(),
|
|
4715
|
+
title: args.title,
|
|
4716
|
+
agent: agent.name,
|
|
4717
|
+
...where,
|
|
4718
|
+
schedule,
|
|
4719
|
+
createdBy: actor.name,
|
|
4720
|
+
createdAt: now.toISOString(),
|
|
4721
|
+
since: now.toISOString(),
|
|
4722
|
+
failures: 0,
|
|
4723
|
+
note: args.note
|
|
4724
|
+
});
|
|
4725
|
+
await this.events.append("cron.created", actor.name, {
|
|
4726
|
+
...this.cronPayload(cron),
|
|
4727
|
+
title: cron.title,
|
|
4728
|
+
schedule: cron.schedule
|
|
4729
|
+
});
|
|
4730
|
+
return cron;
|
|
4731
|
+
});
|
|
4732
|
+
}
|
|
4733
|
+
/** Pauses, resumes, retitles, rewrites, or reschedules a cron; resuming or rescheduling starts it afresh. */
|
|
4734
|
+
async updateCron(actor, input) {
|
|
4735
|
+
const args = VerbInputs.update_cron.parse(input);
|
|
4736
|
+
await this.authorize(actor, "update_cron");
|
|
4737
|
+
if (args.cron !== void 0 && args.at !== void 0) {
|
|
4738
|
+
throw new BoardError("VALIDATION", "a cron fires on an expression or at one time, not both");
|
|
4739
|
+
}
|
|
4740
|
+
return this.mutex.run(async () => {
|
|
4741
|
+
const current = await this.readCron(args.cron_id);
|
|
4742
|
+
this.assertMayCron(actor, current.agent, "change");
|
|
4743
|
+
if (current.ended !== void 0) {
|
|
4744
|
+
throw new BoardError("INVALID_STATE", `cron ${current.id} has ended (${current.ended.reason})`);
|
|
4745
|
+
}
|
|
4746
|
+
const now = this.now();
|
|
4747
|
+
const schedule = await this.cronSchedule(args, current.schedule, now);
|
|
4748
|
+
const resumed = args.paused === false && current.paused !== void 0;
|
|
4749
|
+
const changed = [
|
|
4750
|
+
...args.title === void 0 ? [] : ["title"],
|
|
4751
|
+
...args.note === void 0 ? [] : ["note"],
|
|
4752
|
+
...schedule === void 0 ? [] : ["schedule"],
|
|
4753
|
+
...args.paused === true && current.paused === void 0 ? ["paused"] : [],
|
|
4754
|
+
...resumed ? ["resumed"] : []
|
|
4755
|
+
];
|
|
4756
|
+
if (changed.length === 0 && args.paused === void 0) {
|
|
4757
|
+
throw new BoardError("VALIDATION", "update_cron needs paused, title, note, cron, timezone, or at");
|
|
4758
|
+
}
|
|
4759
|
+
const ts = now.toISOString();
|
|
4760
|
+
const { paused: wasPaused, ...rest } = current;
|
|
4761
|
+
const paused = args.paused === void 0 ? wasPaused : args.paused ? wasPaused ?? { at: ts, by: actor.name } : void 0;
|
|
4762
|
+
const cron = await this.writeCron({
|
|
4763
|
+
...rest,
|
|
4764
|
+
...args.title === void 0 ? {} : { title: args.title },
|
|
4765
|
+
...args.note === void 0 ? {} : { note: args.note },
|
|
4766
|
+
...schedule === void 0 ? {} : { schedule },
|
|
4767
|
+
...schedule !== void 0 || resumed ? { since: ts } : {},
|
|
4768
|
+
...paused === void 0 ? {} : { paused }
|
|
4769
|
+
});
|
|
4770
|
+
if (changed.length > 0) {
|
|
4771
|
+
await this.events.append("cron.updated", actor.name, {
|
|
4772
|
+
...this.cronPayload(cron),
|
|
4773
|
+
changed,
|
|
4774
|
+
...schedule === void 0 ? {} : { schedule }
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
return cron;
|
|
4778
|
+
});
|
|
4779
|
+
}
|
|
4780
|
+
/** Ends a cron for good: its setter's, or anyone's for a planning role. */
|
|
4781
|
+
async removeCron(actor, input) {
|
|
4782
|
+
const args = VerbInputs.remove_cron.parse(input);
|
|
4783
|
+
await this.authorize(actor, "remove_cron");
|
|
4784
|
+
return this.mutex.run(async () => {
|
|
4785
|
+
const current = await this.readCron(args.cron_id);
|
|
4786
|
+
this.assertMayCron(actor, current.agent, "end");
|
|
4787
|
+
if (current.ended !== void 0) {
|
|
4788
|
+
throw new BoardError("INVALID_STATE", `cron ${current.id} has ended already (${current.ended.reason})`);
|
|
4789
|
+
}
|
|
4790
|
+
return this.endCronUnlocked(current, actor.name, args.reason);
|
|
4791
|
+
});
|
|
4792
|
+
}
|
|
4793
|
+
/** A cron wakes its setter; one that wakes another citizen is the planning roles' to set, change, and end. */
|
|
4794
|
+
assertMayCron(actor, agent, what) {
|
|
4795
|
+
if (agent !== actor.name && !PLANNING_ROLES.includes(actor.role)) {
|
|
4796
|
+
throw new BoardError("FORBIDDEN", `only the user, the steward, and the concierge ${what} a cron that wakes another citizen`);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
/**
|
|
4800
|
+
* Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
|
|
4801
|
+
* the conversation of the turn that set it when it wakes its setter, else its home outside projects.
|
|
4802
|
+
*/
|
|
4803
|
+
async cronConversation(actor, agent, named) {
|
|
4804
|
+
const own = actor.name === agent.name;
|
|
4805
|
+
if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
|
|
4806
|
+
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent, {
|
|
4807
|
+
project: actor.scope,
|
|
4808
|
+
thread_id: actor.thread,
|
|
4809
|
+
channel: actor.channel
|
|
4810
|
+
}) : { scope: SOCIETY_SCOPE };
|
|
4811
|
+
}
|
|
4812
|
+
if (named.thread_id !== void 0) {
|
|
4813
|
+
const { thread } = await this.findThread(named.thread_id);
|
|
4814
|
+
if (thread.state !== "open") {
|
|
4815
|
+
throw new BoardError("INVALID_STATE", `thread ${thread.id} is closed`);
|
|
4816
|
+
}
|
|
4817
|
+
const scope2 = wakeScope(agent, parseChannelRef(thread.channel).project);
|
|
4818
|
+
if (named.project !== void 0 && named.project !== scope2) {
|
|
4819
|
+
throw new BoardError("VALIDATION", `thread ${thread.id} is on ${thread.channel}, so ${agent.name}'s conversation there is in ${scope2}, not ${named.project}`);
|
|
4820
|
+
}
|
|
4821
|
+
return { scope: scope2, thread: thread.id };
|
|
4822
|
+
}
|
|
4823
|
+
const scope = named.project ?? (own ? actor.scope : void 0) ?? SOCIETY_SCOPE;
|
|
4824
|
+
if (scope !== SOCIETY_SCOPE) {
|
|
4825
|
+
await this.readActiveProject(scope);
|
|
4826
|
+
if (!agent.memberships.includes(scope)) {
|
|
4827
|
+
throw new BoardError("VALIDATION", `${agent.name} is not a member of ${scope}`);
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
if (named.channel === void 0 || named.channel === "general") {
|
|
4831
|
+
return { scope };
|
|
4832
|
+
}
|
|
4833
|
+
await this.assertChannelOpen(channelRef(scope === SOCIETY_SCOPE ? null : scope, named.channel));
|
|
4834
|
+
return { scope, channel: named.channel };
|
|
4835
|
+
}
|
|
4836
|
+
/**
|
|
4837
|
+
* The schedule a cron verb asks for, or undefined when it asks for none: an expression, read in
|
|
4838
|
+
* its zone or the society's, which must name a time to come and no two fires closer than the
|
|
4839
|
+
* floor, or one time to come. A zone alone keeps the expression of `existing`.
|
|
4840
|
+
*/
|
|
4841
|
+
async cronSchedule(args, existing, now) {
|
|
4842
|
+
if (args.at !== void 0) {
|
|
4843
|
+
if (args.timezone !== void 0) {
|
|
4844
|
+
throw new BoardError("VALIDATION", "at carries its own offset; timezone is for a cron expression");
|
|
4845
|
+
}
|
|
4846
|
+
const at = new Date(args.at);
|
|
4847
|
+
if (at <= now) {
|
|
4848
|
+
throw new BoardError("VALIDATION", `${args.at} has passed; a cron fires at a time to come`);
|
|
4849
|
+
}
|
|
4850
|
+
return { at: at.toISOString() };
|
|
4851
|
+
}
|
|
4852
|
+
if (args.cron === void 0 && args.timezone === void 0) {
|
|
4853
|
+
return void 0;
|
|
4854
|
+
}
|
|
4855
|
+
const expression = args.cron ?? (existing === void 0 || isOneTime(existing) ? void 0 : existing.cron);
|
|
4856
|
+
if (expression === void 0) {
|
|
4857
|
+
throw new BoardError("VALIDATION", "timezone is for a cron expression, which this cron has not");
|
|
4858
|
+
}
|
|
4859
|
+
const kept = args.timezone === void 0 && existing !== void 0 && !isOneTime(existing) ? existing.timezone : args.timezone ?? void 0;
|
|
4860
|
+
const schedule = {
|
|
4861
|
+
cron: expression,
|
|
4862
|
+
...kept === void 0 ? {} : { timezone: kept }
|
|
4863
|
+
};
|
|
4864
|
+
const { timezone: societyZone } = await this.society();
|
|
4865
|
+
if (fireTimes(schedule, societyZone, now, 1).length === 0) {
|
|
4866
|
+
throw new BoardError("VALIDATION", `${expression} names no time to come`);
|
|
4867
|
+
}
|
|
4868
|
+
const gap = shortestGapMs(schedule, societyZone, now);
|
|
4869
|
+
if (gap !== null && gap < CRON_FLOOR_MS) {
|
|
4870
|
+
throw new BoardError("VALIDATION", `${expression} fires ${Math.round(gap / 6e4)} minute(s) apart at its closest; a cron fires at most every ${CRON_FLOOR_MS / 6e4} minutes, and every fire is a turn`);
|
|
4871
|
+
}
|
|
4872
|
+
return schedule;
|
|
4873
|
+
}
|
|
4874
|
+
/** A cron's events name its citizen and the conversation it fires in, as turn events do. */
|
|
4875
|
+
cronPayload(cron) {
|
|
4876
|
+
return {
|
|
4877
|
+
cronId: cron.id,
|
|
4878
|
+
agent: cron.agent,
|
|
4879
|
+
project: cron.scope,
|
|
4880
|
+
...cron.thread === void 0 ? {} : { thread: cron.thread },
|
|
4881
|
+
...cron.channel === void 0 ? {} : { channel: cron.channel }
|
|
4882
|
+
};
|
|
4883
|
+
}
|
|
4884
|
+
async readCronFile(file) {
|
|
4885
|
+
const doc = await readMarkdown(file, CronFrontmatterSchema);
|
|
4886
|
+
return { ...doc.data, note: doc.body.trimEnd() };
|
|
4887
|
+
}
|
|
4888
|
+
async writeCron(cron) {
|
|
4889
|
+
const { note, ...frontmatter } = cron;
|
|
4890
|
+
const data = CronFrontmatterSchema.parse(Object.fromEntries(Object.entries(frontmatter).filter(([, value]) => value !== void 0)));
|
|
4891
|
+
await writeMarkdown(this.paths.cron(cron.id), data, note);
|
|
4892
|
+
return { ...data, note };
|
|
4893
|
+
}
|
|
4894
|
+
async endCronUnlocked(cron, by, reason) {
|
|
4895
|
+
const ended = await this.writeCron({
|
|
4896
|
+
...cron,
|
|
4897
|
+
ended: { at: this.now().toISOString(), by, reason }
|
|
4898
|
+
});
|
|
4899
|
+
await this.events.append("cron.ended", by, { ...this.cronPayload(ended), reason });
|
|
4900
|
+
return ended;
|
|
4901
|
+
}
|
|
4902
|
+
/** Ends every running cron `ends` picks, in the transaction that ends the conversation it fires in. */
|
|
4903
|
+
async endCronsUnlocked(by, reason, ends) {
|
|
4904
|
+
for (const cron of await this.listCrons()) {
|
|
4905
|
+
if (cron.ended === void 0 && ends(cron)) {
|
|
4906
|
+
await this.endCronUnlocked(cron, by, reason);
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4423
4910
|
async openThread(actor, input) {
|
|
4424
4911
|
const args = VerbInputs.open_thread.parse(input);
|
|
4425
4912
|
await this.authorize(actor, "open_thread");
|
|
@@ -4636,6 +5123,7 @@ ${args.summary}`);
|
|
|
4636
5123
|
threadId: thread.id,
|
|
4637
5124
|
channel: thread.channel
|
|
4638
5125
|
});
|
|
5126
|
+
await this.endCronsUnlocked(actor.name, "its thread closed", (cron) => cron.thread === thread.id);
|
|
4639
5127
|
return summary;
|
|
4640
5128
|
});
|
|
4641
5129
|
}
|
|
@@ -5137,6 +5625,13 @@ ${args.summary}`);
|
|
|
5137
5625
|
proposedBy: actor.name,
|
|
5138
5626
|
status: "proposed",
|
|
5139
5627
|
createdAt: this.now().toISOString(),
|
|
5628
|
+
...actor.scope === void 0 ? {} : {
|
|
5629
|
+
filedFrom: {
|
|
5630
|
+
scope: actor.scope,
|
|
5631
|
+
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
5632
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
5633
|
+
}
|
|
5634
|
+
},
|
|
5140
5635
|
charter: parsed.data
|
|
5141
5636
|
}),
|
|
5142
5637
|
body: args.rationale
|
|
@@ -5308,6 +5803,7 @@ ${rationale}`}`);
|
|
|
5308
5803
|
members: project.members.filter((member) => member !== target)
|
|
5309
5804
|
}));
|
|
5310
5805
|
await this.refreshMember(target);
|
|
5806
|
+
await this.endCronsUnlocked(actor.name, `${target} left ${args.project}`, (cron) => cron.agent === target && cron.scope === args.project);
|
|
5311
5807
|
await this.events.append("agent.left", actor.name, {
|
|
5312
5808
|
name: target,
|
|
5313
5809
|
project: args.project,
|
|
@@ -5376,6 +5872,12 @@ ${rationale}`}`);
|
|
|
5376
5872
|
return this.archiveChannel(actor, VerbInputs.archive_channel.parse(input));
|
|
5377
5873
|
case "update_dashboard":
|
|
5378
5874
|
return this.updateDashboard(actor, VerbInputs.update_dashboard.parse(input));
|
|
5875
|
+
case "create_cron":
|
|
5876
|
+
return this.createCron(actor, VerbInputs.create_cron.parse(input));
|
|
5877
|
+
case "update_cron":
|
|
5878
|
+
return this.updateCron(actor, VerbInputs.update_cron.parse(input));
|
|
5879
|
+
case "remove_cron":
|
|
5880
|
+
return this.removeCron(actor, VerbInputs.remove_cron.parse(input));
|
|
5379
5881
|
default:
|
|
5380
5882
|
throw new BoardError("VALIDATION", `unknown verb ${String(verb)}`);
|
|
5381
5883
|
}
|
|
@@ -5422,7 +5924,7 @@ ${rationale}`}`);
|
|
|
5422
5924
|
}
|
|
5423
5925
|
async readAgentRoleBody(agent) {
|
|
5424
5926
|
const file = this.paths.agentRole(agent);
|
|
5425
|
-
return await exists(file) ? (await readMarkdown(file,
|
|
5927
|
+
return await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
|
|
5426
5928
|
}
|
|
5427
5929
|
/** The steps of one finished turn, as the runner handed them over when it ended. */
|
|
5428
5930
|
/**
|
|
@@ -5600,6 +6102,9 @@ ${rationale}`}`);
|
|
|
5600
6102
|
}
|
|
5601
6103
|
await this.writeTurnRecord(parsed);
|
|
5602
6104
|
await this.refreshMember(parsed.agent);
|
|
6105
|
+
for (const id of parsed.crons ?? []) {
|
|
6106
|
+
await this.recordCronRun(id, parsed, failed);
|
|
6107
|
+
}
|
|
5603
6108
|
if (parsed.exitReason === "stopped") {
|
|
5604
6109
|
await this.events.append("turn.stopped", parsed.stoppedBy ?? USER_NAME, {
|
|
5605
6110
|
...parsed.id === void 0 ? {} : { turnId: parsed.id },
|
|
@@ -5637,6 +6142,29 @@ ${rationale}`}`);
|
|
|
5637
6142
|
});
|
|
5638
6143
|
});
|
|
5639
6144
|
}
|
|
6145
|
+
/**
|
|
6146
|
+
* Records how closing an abandoned `ghpr` task's pull request on GitHub went, once: the task's
|
|
6147
|
+
* link keeps it, so a restart does not try again.
|
|
6148
|
+
*/
|
|
6149
|
+
async recordPullRequestClosed(actor, input) {
|
|
6150
|
+
return this.mutex.run(async () => {
|
|
6151
|
+
const location = await this.findTask(input.taskId);
|
|
6152
|
+
const current = location.task;
|
|
6153
|
+
const pull = current.pullRequest;
|
|
6154
|
+
if (pull === void 0 || pull.closed !== void 0) {
|
|
6155
|
+
return current;
|
|
6156
|
+
}
|
|
6157
|
+
const task = await this.writeTask(location.project, {
|
|
6158
|
+
...current,
|
|
6159
|
+
pullRequest: {
|
|
6160
|
+
...pull,
|
|
6161
|
+
closed: { at: this.now().toISOString(), ok: input.ok, detail: input.detail }
|
|
6162
|
+
}
|
|
6163
|
+
});
|
|
6164
|
+
await this.events.append(input.ok ? "pull_request.closed" : "pull_request.close_failed", actor.name, { project: location.project, taskId: task.id, url: pull.url, detail: input.detail });
|
|
6165
|
+
return task;
|
|
6166
|
+
});
|
|
6167
|
+
}
|
|
5640
6168
|
/**
|
|
5641
6169
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
5642
6170
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -5658,6 +6186,45 @@ ${rationale}`}`);
|
|
|
5658
6186
|
return true;
|
|
5659
6187
|
});
|
|
5660
6188
|
}
|
|
6189
|
+
/**
|
|
6190
|
+
* Records a cron's fire, which the scheduler made; a one-time cron ends with it. Null, recording
|
|
6191
|
+
* nothing, for a cron ended or paused since the scheduler read it, which does not fire.
|
|
6192
|
+
*/
|
|
6193
|
+
async cronFired(id, at) {
|
|
6194
|
+
return this.mutex.run(async () => {
|
|
6195
|
+
const current = await this.readCron(id);
|
|
6196
|
+
if (current.ended !== void 0 || current.paused !== void 0) {
|
|
6197
|
+
return null;
|
|
6198
|
+
}
|
|
6199
|
+
const cron = await this.writeCron({ ...current, lastFiredAt: at });
|
|
6200
|
+
await this.events.append("cron.fired", SYSTEM_ACTOR.name, this.cronPayload(cron));
|
|
6201
|
+
return isOneTime(cron.schedule) ? this.endCronUnlocked(cron, SYSTEM_ACTOR.name, "fired once, as set") : cron;
|
|
6202
|
+
});
|
|
6203
|
+
}
|
|
6204
|
+
/** Records a time a cron passed because the turn of its previous fire was still queued or running. */
|
|
6205
|
+
async cronSkipped(id, at) {
|
|
6206
|
+
return this.mutex.run(async () => {
|
|
6207
|
+
const cron = await this.writeCron({ ...await this.readCron(id), lastSkippedAt: at });
|
|
6208
|
+
await this.events.append("cron.skipped", SYSTEM_ACTOR.name, this.cronPayload(cron));
|
|
6209
|
+
return cron;
|
|
6210
|
+
});
|
|
6211
|
+
}
|
|
6212
|
+
/**
|
|
6213
|
+
* How the turn a cron's fire started ended. A failure counts toward `cron_failing`, a completed
|
|
6214
|
+
* turn clears the count, and a stop, the user's decision, leaves it.
|
|
6215
|
+
*/
|
|
6216
|
+
async recordCronRun(id, record, failed) {
|
|
6217
|
+
if (record.id === void 0 || !await exists(this.paths.cron(id))) {
|
|
6218
|
+
return;
|
|
6219
|
+
}
|
|
6220
|
+
const cron = await this.readCron(id);
|
|
6221
|
+
const outcome = record.exitReason === "stopped" ? "stopped" : failed ? "failed" : "completed";
|
|
6222
|
+
await this.writeCron({
|
|
6223
|
+
...cron,
|
|
6224
|
+
lastRun: { turnId: record.id, at: record.endedAt ?? this.now().toISOString(), outcome },
|
|
6225
|
+
failures: outcome === "failed" ? cron.failures + 1 : outcome === "completed" ? 0 : cron.failures
|
|
6226
|
+
});
|
|
6227
|
+
}
|
|
5661
6228
|
/**
|
|
5662
6229
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
5663
6230
|
* (for one from before tasks had threads), or a channel archived.
|
|
@@ -5757,7 +6324,7 @@ ${rationale}`}`);
|
|
|
5757
6324
|
return { project: slug, revision: 0, body: "" };
|
|
5758
6325
|
}
|
|
5759
6326
|
const { data, body } = await readLooseMarkdown(file);
|
|
5760
|
-
const revision =
|
|
6327
|
+
const revision = z13.number().int().min(0).safeParse(data["revision"]);
|
|
5761
6328
|
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
5762
6329
|
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
5763
6330
|
return {
|
|
@@ -6048,7 +6615,7 @@ ${seed}
|
|
|
6048
6615
|
continue;
|
|
6049
6616
|
}
|
|
6050
6617
|
const file = this.paths.agentRole(agent.name);
|
|
6051
|
-
const body = await exists(file) ? (await readMarkdown(file,
|
|
6618
|
+
const body = await exists(file) ? (await readMarkdown(file, z13.record(z13.string(), z13.unknown()))).body : "";
|
|
6052
6619
|
const at = body.indexOf(`
|
|
6053
6620
|
${SEED_INSTRUCTIONS_HEADING}
|
|
6054
6621
|
`);
|
|
@@ -6492,6 +7059,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
6492
7059
|
archivedChannels: [...current.archivedChannels, archived]
|
|
6493
7060
|
}));
|
|
6494
7061
|
}
|
|
7062
|
+
await this.endCronsUnlocked(by, `${ref} was archived`, (cron) => cron.scope === scope && cron.channel === channel || cron.thread !== void 0 && threadsClosed.includes(cron.thread));
|
|
6495
7063
|
await this.events.append("channel.archived", by, {
|
|
6496
7064
|
channel: ref,
|
|
6497
7065
|
reason,
|
|
@@ -6596,6 +7164,7 @@ ${charter.purpose}
|
|
|
6596
7164
|
});
|
|
6597
7165
|
threadsClosed.push(doc.data.id);
|
|
6598
7166
|
}
|
|
7167
|
+
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron) => cron.scope === slug || cron.thread !== void 0 && threadsClosed.includes(cron.thread));
|
|
6599
7168
|
const project = await this.updateProject(slug, (p) => ({
|
|
6600
7169
|
...p,
|
|
6601
7170
|
members: [],
|
|
@@ -6660,6 +7229,7 @@ ${charter.purpose}
|
|
|
6660
7229
|
}
|
|
6661
7230
|
this.tokenIndex.delete(current.tokenHash);
|
|
6662
7231
|
await this.refreshMember(name);
|
|
7232
|
+
await this.endCronsUnlocked(by, `${name} was retired`, (cron) => cron.agent === name);
|
|
6663
7233
|
await this.events.append("agent.retired", by, {
|
|
6664
7234
|
name,
|
|
6665
7235
|
role: agent.role,
|
|
@@ -6869,7 +7439,8 @@ ${charter.purpose}
|
|
|
6869
7439
|
proposalId,
|
|
6870
7440
|
outcome,
|
|
6871
7441
|
kind: proposal.kind,
|
|
6872
|
-
proposedBy: proposal.proposedBy
|
|
7442
|
+
proposedBy: proposal.proposedBy,
|
|
7443
|
+
...proposal.filedFrom === void 0 ? {} : { filedFrom: proposal.filedFrom }
|
|
6873
7444
|
});
|
|
6874
7445
|
if (provision !== void 0) {
|
|
6875
7446
|
await this.events.append("proposal.provisioned", actor.name, {
|
|
@@ -6970,6 +7541,7 @@ ${charter.purpose}
|
|
|
6970
7541
|
channel: found.thread.channel,
|
|
6971
7542
|
ended
|
|
6972
7543
|
});
|
|
7544
|
+
await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron) => cron.thread === subjectId);
|
|
6973
7545
|
}
|
|
6974
7546
|
async readMessagesIn(dir, since) {
|
|
6975
7547
|
const messages = [];
|
|
@@ -7033,6 +7605,8 @@ function decideWake(input) {
|
|
|
7033
7605
|
return { wake: true, reason: "user post", priority: 2 };
|
|
7034
7606
|
case "closing":
|
|
7035
7607
|
return { wake: true, reason: "an ended conversation left work on no branch", priority: 1 };
|
|
7608
|
+
case "cron":
|
|
7609
|
+
return { wake: true, reason: "a cron came due", priority: 1 };
|
|
7036
7610
|
case "heartbeat":
|
|
7037
7611
|
break;
|
|
7038
7612
|
}
|
|
@@ -7047,7 +7621,7 @@ function decideWake(input) {
|
|
|
7047
7621
|
}
|
|
7048
7622
|
|
|
7049
7623
|
// packages/scheduler/dist/scheduler.js
|
|
7050
|
-
import { z as
|
|
7624
|
+
import { z as z14 } from "zod";
|
|
7051
7625
|
function besideFields(beside) {
|
|
7052
7626
|
if (beside === void 0) {
|
|
7053
7627
|
return {};
|
|
@@ -7074,21 +7648,21 @@ var DEFAULT_TIMINGS = Object.freeze({
|
|
|
7074
7648
|
scaleCooldownMs: 36e5,
|
|
7075
7649
|
reflectionMs: 24 * 36e5
|
|
7076
7650
|
});
|
|
7077
|
-
var SchedulerTimingsSchema =
|
|
7078
|
-
pollMs:
|
|
7079
|
-
debounceMs:
|
|
7080
|
-
userDebounceMs:
|
|
7081
|
-
heartbeatMs:
|
|
7082
|
-
waitingStageMs:
|
|
7083
|
-
leaseSweepMs:
|
|
7084
|
-
opsIntervalMs:
|
|
7085
|
-
signalRepeatMs:
|
|
7086
|
-
costReportMs:
|
|
7087
|
-
staleThreadMs:
|
|
7088
|
-
idleMemberMs:
|
|
7089
|
-
homeConflictMs:
|
|
7090
|
-
scaleCooldownMs:
|
|
7091
|
-
reflectionMs:
|
|
7651
|
+
var SchedulerTimingsSchema = z14.object({
|
|
7652
|
+
pollMs: z14.number().positive(),
|
|
7653
|
+
debounceMs: z14.number().nonnegative(),
|
|
7654
|
+
userDebounceMs: z14.number().nonnegative(),
|
|
7655
|
+
heartbeatMs: z14.number().positive(),
|
|
7656
|
+
waitingStageMs: z14.number().positive(),
|
|
7657
|
+
leaseSweepMs: z14.number().positive(),
|
|
7658
|
+
opsIntervalMs: z14.number().positive(),
|
|
7659
|
+
signalRepeatMs: z14.number().positive(),
|
|
7660
|
+
costReportMs: z14.number().positive(),
|
|
7661
|
+
staleThreadMs: z14.number().positive(),
|
|
7662
|
+
idleMemberMs: z14.number().positive(),
|
|
7663
|
+
homeConflictMs: z14.number().positive(),
|
|
7664
|
+
scaleCooldownMs: z14.number().positive(),
|
|
7665
|
+
reflectionMs: z14.number().positive()
|
|
7092
7666
|
}).partial();
|
|
7093
7667
|
function parseTimings(input) {
|
|
7094
7668
|
const parsed = SchedulerTimingsSchema.parse(input);
|
|
@@ -7100,31 +7674,32 @@ function parseTimings(input) {
|
|
|
7100
7674
|
}
|
|
7101
7675
|
return overrides;
|
|
7102
7676
|
}
|
|
7103
|
-
var PendingSchema =
|
|
7677
|
+
var PendingSchema = z14.object({
|
|
7104
7678
|
dispatch: TurnDispatchSchema,
|
|
7105
|
-
readyAt:
|
|
7106
|
-
stageWakes:
|
|
7107
|
-
holder:
|
|
7679
|
+
readyAt: z14.number(),
|
|
7680
|
+
stageWakes: z14.array(TriggerSchema).optional(),
|
|
7681
|
+
holder: z14.boolean().optional(),
|
|
7108
7682
|
newestMessage: UlidSchema.optional(),
|
|
7109
|
-
onlyMessages:
|
|
7110
|
-
steeredThrough: UlidSchema.optional()
|
|
7683
|
+
onlyMessages: z14.boolean().optional(),
|
|
7684
|
+
steeredThrough: UlidSchema.optional(),
|
|
7685
|
+
onlyCrons: z14.boolean().optional()
|
|
7111
7686
|
});
|
|
7112
|
-
var StateSchema =
|
|
7113
|
-
cursor:
|
|
7114
|
-
lastHeartbeat:
|
|
7687
|
+
var StateSchema = z14.object({
|
|
7688
|
+
cursor: z14.string().nullable(),
|
|
7689
|
+
lastHeartbeat: z14.record(z14.string(), z14.string()),
|
|
7115
7690
|
/** Waiting stages already signalled, as `task:stage`, so each is logged once while it waits. */
|
|
7116
|
-
waitingSeen:
|
|
7691
|
+
waitingSeen: z14.record(z14.string(), z14.string()).default({}),
|
|
7117
7692
|
/** Queued dispatches survive a restart; the cursor has already moved past the events that made them. */
|
|
7118
|
-
pending:
|
|
7119
|
-
opsReported:
|
|
7120
|
-
releaseCounts:
|
|
7121
|
-
lastOps:
|
|
7122
|
-
lastScaled:
|
|
7123
|
-
lastCostReport:
|
|
7124
|
-
costSinceReport:
|
|
7125
|
-
turnsSinceReport:
|
|
7693
|
+
pending: z14.record(z14.string(), PendingSchema).default({}),
|
|
7694
|
+
opsReported: z14.record(z14.string(), z14.string()).default({}),
|
|
7695
|
+
releaseCounts: z14.record(z14.string(), z14.number().int().nonnegative()).default({}),
|
|
7696
|
+
lastOps: z14.string().nullable().default(null),
|
|
7697
|
+
lastScaled: z14.record(z14.string(), z14.string()).default({}),
|
|
7698
|
+
lastCostReport: z14.string().nullable().default(null),
|
|
7699
|
+
costSinceReport: z14.number().nonnegative().default(0),
|
|
7700
|
+
turnsSinceReport: z14.number().int().nonnegative().default(0),
|
|
7126
7701
|
/** When each member last reflected, or was first seen; the cadence counts from here. */
|
|
7127
|
-
lastReflection:
|
|
7702
|
+
lastReflection: z14.record(z14.string(), z14.string()).default({})
|
|
7128
7703
|
});
|
|
7129
7704
|
function messageOf(trigger) {
|
|
7130
7705
|
return trigger.kind === "mention" || trigger.kind === "user_post" ? trigger.messageId : void 0;
|
|
@@ -7150,6 +7725,9 @@ function stringArray(value) {
|
|
|
7150
7725
|
function stringOf(value) {
|
|
7151
7726
|
return typeof value === "string" ? value : null;
|
|
7152
7727
|
}
|
|
7728
|
+
function awaitsClosing(task) {
|
|
7729
|
+
return task.status === "abandoned" && task.onDone === "ghpr" && task.pullRequest !== void 0 && task.pullRequest.closed === void 0;
|
|
7730
|
+
}
|
|
7153
7731
|
function inPlay(task) {
|
|
7154
7732
|
return (task.status === "open" || task.status === "claimed") && !task.completing;
|
|
7155
7733
|
}
|
|
@@ -7182,6 +7760,9 @@ var Scheduler = class {
|
|
|
7182
7760
|
steering = /* @__PURE__ */ new Map();
|
|
7183
7761
|
/** Tasks whose completion effect waits for the turn that finished their last stage to end. */
|
|
7184
7762
|
completions = /* @__PURE__ */ new Map();
|
|
7763
|
+
/** Abandoned `ghpr` tasks whose pull request waits to be closed, by task id, with its project. */
|
|
7764
|
+
closings = /* @__PURE__ */ new Map();
|
|
7765
|
+
/** Completions and closings under way. */
|
|
7185
7766
|
completing = /* @__PURE__ */ new Set();
|
|
7186
7767
|
/** Running turns that must run alone among their citizen's, by session key: see `heldBack`. */
|
|
7187
7768
|
exclusive = /* @__PURE__ */ new Map();
|
|
@@ -7189,6 +7770,8 @@ var Scheduler = class {
|
|
|
7189
7770
|
landing = /* @__PURE__ */ new Set();
|
|
7190
7771
|
/** Sweeps of ended conversations' workspaces under way, which `drain` and `stop` wait for. */
|
|
7191
7772
|
sweeping = /* @__PURE__ */ new Set();
|
|
7773
|
+
/** Crons whose fire's turn is running; a cron's next time passes while one is. */
|
|
7774
|
+
cronsInFlight = /* @__PURE__ */ new Set();
|
|
7192
7775
|
state = StateSchema.parse({
|
|
7193
7776
|
cursor: null,
|
|
7194
7777
|
lastHeartbeat: {}
|
|
@@ -7268,6 +7851,7 @@ var Scheduler = class {
|
|
|
7268
7851
|
this.state.cursor = event.id;
|
|
7269
7852
|
}
|
|
7270
7853
|
if (!paused) {
|
|
7854
|
+
await this.checkCrons(now);
|
|
7271
7855
|
await this.checkHeartbeats(now);
|
|
7272
7856
|
await this.checkWaitingStages(now);
|
|
7273
7857
|
await this.checkReflections(now);
|
|
@@ -7281,6 +7865,7 @@ var Scheduler = class {
|
|
|
7281
7865
|
this.lastSweep = now;
|
|
7282
7866
|
}
|
|
7283
7867
|
this.runCompletions();
|
|
7868
|
+
this.runClosings();
|
|
7284
7869
|
if (!paused) {
|
|
7285
7870
|
await this.dispatchReady(now);
|
|
7286
7871
|
}
|
|
@@ -7308,6 +7893,9 @@ var Scheduler = class {
|
|
|
7308
7893
|
if (task.completing) {
|
|
7309
7894
|
this.completions.set(task.id, { project: project.slug, actor: USER_NAME });
|
|
7310
7895
|
}
|
|
7896
|
+
if (awaitsClosing(task)) {
|
|
7897
|
+
this.closings.set(task.id, project.slug);
|
|
7898
|
+
}
|
|
7311
7899
|
}
|
|
7312
7900
|
}
|
|
7313
7901
|
this.loaded = true;
|
|
@@ -7416,6 +8004,7 @@ var Scheduler = class {
|
|
|
7416
8004
|
const taskId = stringOf(payload["taskId"]);
|
|
7417
8005
|
if (taskId !== null && payload["to"] === "abandoned") {
|
|
7418
8006
|
delete this.state.releaseCounts[taskId];
|
|
8007
|
+
await this.queueClosing(taskId);
|
|
7419
8008
|
}
|
|
7420
8009
|
if (taskId !== null) {
|
|
7421
8010
|
await this.pruneStageWakes(taskId);
|
|
@@ -7520,6 +8109,13 @@ var Scheduler = class {
|
|
|
7520
8109
|
}
|
|
7521
8110
|
return;
|
|
7522
8111
|
}
|
|
8112
|
+
case "cron.ended": {
|
|
8113
|
+
const cronId = stringOf(payload["cronId"]);
|
|
8114
|
+
if (cronId !== null && event.actor !== SYSTEM_ACTOR.name) {
|
|
8115
|
+
this.dropCronWakes(cronId);
|
|
8116
|
+
}
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
7523
8119
|
case "proposal.decided": {
|
|
7524
8120
|
const proposalId = stringOf(payload["proposalId"]);
|
|
7525
8121
|
const proposer = stringOf(payload["proposedBy"]);
|
|
@@ -7755,13 +8351,16 @@ var Scheduler = class {
|
|
|
7755
8351
|
reason: "a conversation you took part in has ended, and its workspace holds work on no branch"
|
|
7756
8352
|
}, now, beside);
|
|
7757
8353
|
}
|
|
7758
|
-
/**
|
|
8354
|
+
/**
|
|
8355
|
+
* A decided proposal wakes the citizen that made it, to carry on with what it enables, in the
|
|
8356
|
+
* conversation it was filed from, where that work waits on the decision.
|
|
8357
|
+
*/
|
|
7759
8358
|
async wakeProposer(proposalId, proposer, event, now) {
|
|
7760
8359
|
const agent = await this.tryReadAgent(proposer);
|
|
7761
8360
|
if (agent === null) {
|
|
7762
8361
|
return;
|
|
7763
8362
|
}
|
|
7764
|
-
const scope = this.
|
|
8363
|
+
const { scope, beside } = await this.filedFrom(agent, event.payload["filedFrom"]);
|
|
7765
8364
|
const kind = stringOf(event.payload["kind"]) ?? "";
|
|
7766
8365
|
const outcome = stringOf(event.payload["outcome"]) ?? "decided";
|
|
7767
8366
|
this.enqueue(agent.name, scope, {
|
|
@@ -7769,7 +8368,27 @@ var Scheduler = class {
|
|
|
7769
8368
|
from: event.actor,
|
|
7770
8369
|
fromUser: event.actor === USER_NAME,
|
|
7771
8370
|
reason: `your ${kind} proposal ${proposalId} was ${outcome} by ${event.actor}`
|
|
7772
|
-
}, now);
|
|
8371
|
+
}, now, beside);
|
|
8372
|
+
}
|
|
8373
|
+
/**
|
|
8374
|
+
* The conversation a proposal was filed from while it is still open; its scope's home once it
|
|
8375
|
+
* has ended; and the home outside projects when the citizen has left that scope, or the proposal
|
|
8376
|
+
* names none, filed outside a turn or before proposals recorded it.
|
|
8377
|
+
*/
|
|
8378
|
+
async filedFrom(agent, recorded) {
|
|
8379
|
+
const origin = ProposalOriginSchema.safeParse(recorded);
|
|
8380
|
+
if (!origin.success || origin.data.scope !== SOCIETY_SCOPE && !agent.memberships.includes(origin.data.scope)) {
|
|
8381
|
+
return { scope: this.scopeForProject(agent, null) };
|
|
8382
|
+
}
|
|
8383
|
+
const scope = origin.data.scope;
|
|
8384
|
+
const conversation = workspaceConversationOf(origin.data);
|
|
8385
|
+
if (conversation === null || await this.board.conversationEnded(conversation)) {
|
|
8386
|
+
return { scope };
|
|
8387
|
+
}
|
|
8388
|
+
return {
|
|
8389
|
+
scope,
|
|
8390
|
+
beside: conversation.kind === "thread" ? { thread: await this.conversationOf(conversation.id, scope) } : { channel: conversation.name }
|
|
8391
|
+
};
|
|
7773
8392
|
}
|
|
7774
8393
|
/**
|
|
7775
8394
|
* A thread's conversation for a turn in `scope`, and whether it is a task's in that scope, whose
|
|
@@ -7828,6 +8447,7 @@ var Scheduler = class {
|
|
|
7828
8447
|
const readyAt = now + debounce;
|
|
7829
8448
|
const existing = this.pending.get(key);
|
|
7830
8449
|
const message = messageOf(trigger);
|
|
8450
|
+
const cron = trigger.kind === "cron" ? trigger.cronId : void 0;
|
|
7831
8451
|
if (existing === void 0) {
|
|
7832
8452
|
this.pending.set(key, {
|
|
7833
8453
|
dispatch: {
|
|
@@ -7836,24 +8456,30 @@ var Scheduler = class {
|
|
|
7836
8456
|
...besideFields(beside),
|
|
7837
8457
|
trigger,
|
|
7838
8458
|
priority: decision.priority,
|
|
7839
|
-
onboarding: trigger.kind === "onboarding"
|
|
8459
|
+
onboarding: trigger.kind === "onboarding",
|
|
8460
|
+
...cron === void 0 ? {} : { crons: [cron] }
|
|
7840
8461
|
},
|
|
7841
8462
|
readyAt,
|
|
7842
8463
|
...trigger.kind === "stage" ? { stageWakes: [trigger] } : {},
|
|
7843
8464
|
...message === void 0 ? {} : { newestMessage: message },
|
|
7844
|
-
onlyMessages: message !== void 0
|
|
8465
|
+
onlyMessages: message !== void 0,
|
|
8466
|
+
onlyCrons: cron !== void 0
|
|
7845
8467
|
});
|
|
7846
8468
|
return;
|
|
7847
8469
|
}
|
|
7848
8470
|
const newestMessage = later(existing.newestMessage, message);
|
|
7849
8471
|
const keepNew = decision.priority > existing.dispatch.priority;
|
|
7850
8472
|
const priority = keepNew ? decision.priority : existing.dispatch.priority;
|
|
8473
|
+
const crons = [
|
|
8474
|
+
.../* @__PURE__ */ new Set([...existing.dispatch.crons ?? [], ...cron === void 0 ? [] : [cron]])
|
|
8475
|
+
];
|
|
7851
8476
|
this.pending.set(key, {
|
|
7852
8477
|
dispatch: {
|
|
7853
8478
|
...existing.dispatch,
|
|
7854
8479
|
trigger: keepNew ? trigger : existing.dispatch.trigger,
|
|
7855
8480
|
priority,
|
|
7856
|
-
onboarding: existing.dispatch.onboarding || trigger.kind === "onboarding"
|
|
8481
|
+
onboarding: existing.dispatch.onboarding || trigger.kind === "onboarding",
|
|
8482
|
+
...crons.length === 0 ? {} : { crons }
|
|
7857
8483
|
},
|
|
7858
8484
|
readyAt: Math.min(existing.readyAt, readyAt),
|
|
7859
8485
|
...existing.stageWakes !== void 0 && trigger.kind === "stage" ? {
|
|
@@ -7862,7 +8488,8 @@ var Scheduler = class {
|
|
|
7862
8488
|
} : {},
|
|
7863
8489
|
...newestMessage === void 0 ? {} : { newestMessage },
|
|
7864
8490
|
onlyMessages: existing.onlyMessages === true && message !== void 0,
|
|
7865
|
-
...existing.steeredThrough === void 0 ? {} : { steeredThrough: existing.steeredThrough }
|
|
8491
|
+
...existing.steeredThrough === void 0 ? {} : { steeredThrough: existing.steeredThrough },
|
|
8492
|
+
onlyCrons: existing.onlyCrons === true && cron !== void 0
|
|
7866
8493
|
});
|
|
7867
8494
|
}
|
|
7868
8495
|
/** Debounce coalesces chatter: mentions, stage handoffs, and bursts of signals. Everything else runs at once. */
|
|
@@ -7972,6 +8599,71 @@ var Scheduler = class {
|
|
|
7972
8599
|
}
|
|
7973
8600
|
}
|
|
7974
8601
|
}
|
|
8602
|
+
/**
|
|
8603
|
+
* Each active cron whose next time has come wakes its citizen in its conversation, unless the turn
|
|
8604
|
+
* of its previous fire is still queued or running, in which case the time passes. The fire is
|
|
8605
|
+
* recorded on the board, so its next time counts from it and a restart does not fire it again.
|
|
8606
|
+
*/
|
|
8607
|
+
async checkCrons(now) {
|
|
8608
|
+
const crons = (await this.board.listCrons()).filter((cron) => cron.ended === void 0 && cron.paused === void 0);
|
|
8609
|
+
if (crons.length === 0) {
|
|
8610
|
+
return;
|
|
8611
|
+
}
|
|
8612
|
+
const { timezone } = await this.board.society();
|
|
8613
|
+
for (const cron of crons) {
|
|
8614
|
+
const due = nextFire(cron, timezone);
|
|
8615
|
+
if (due === null || due.getTime() > now) {
|
|
8616
|
+
continue;
|
|
8617
|
+
}
|
|
8618
|
+
if (this.cronQueuedOrRunning(cron.id)) {
|
|
8619
|
+
await this.board.cronSkipped(cron.id, iso(now));
|
|
8620
|
+
this.log.info({ cron: cron.id, agent: cron.agent }, "cron time passed, its previous turn has not ended");
|
|
8621
|
+
continue;
|
|
8622
|
+
}
|
|
8623
|
+
const agent = await this.tryReadAgent(cron.agent);
|
|
8624
|
+
if (agent === null) {
|
|
8625
|
+
continue;
|
|
8626
|
+
}
|
|
8627
|
+
if (await this.board.cronFired(cron.id, iso(now)) === null) {
|
|
8628
|
+
continue;
|
|
8629
|
+
}
|
|
8630
|
+
this.enqueue(agent.name, cron.scope, {
|
|
8631
|
+
kind: "cron",
|
|
8632
|
+
from: cron.createdBy,
|
|
8633
|
+
fromUser: cron.createdBy === USER_NAME,
|
|
8634
|
+
reason: `cron "${cron.title}" came due`,
|
|
8635
|
+
cronId: cron.id
|
|
8636
|
+
}, now, await this.cronConversation(cron));
|
|
8637
|
+
}
|
|
8638
|
+
}
|
|
8639
|
+
/** The conversation a cron fires in, beside its scope's home or none for the home. */
|
|
8640
|
+
async cronConversation(cron) {
|
|
8641
|
+
if (cron.thread !== void 0) {
|
|
8642
|
+
return { thread: await this.conversationOf(cron.thread, cron.scope) };
|
|
8643
|
+
}
|
|
8644
|
+
return cron.channel === void 0 ? void 0 : { channel: cron.channel };
|
|
8645
|
+
}
|
|
8646
|
+
/** Whether a cron's wake waits in the queue or its turn is running. */
|
|
8647
|
+
cronQueuedOrRunning(id) {
|
|
8648
|
+
return this.cronsInFlight.has(id) || [...this.pending.values()].some((item) => item.dispatch.crons?.includes(id) === true);
|
|
8649
|
+
}
|
|
8650
|
+
/** Takes an ended cron out of the queued wakes, and drops a wake left with nothing else to run. */
|
|
8651
|
+
dropCronWakes(id) {
|
|
8652
|
+
for (const [key, item] of this.pending) {
|
|
8653
|
+
const crons = item.dispatch.crons;
|
|
8654
|
+
if (crons?.includes(id) !== true) {
|
|
8655
|
+
continue;
|
|
8656
|
+
}
|
|
8657
|
+
const rest = crons.filter((each) => each !== id);
|
|
8658
|
+
if (rest.length === 0 && item.onlyCrons === true) {
|
|
8659
|
+
this.pending.delete(key);
|
|
8660
|
+
this.log.info({ agent: item.dispatch.agent, cron: id }, "dropping cron wake, the cron ended");
|
|
8661
|
+
continue;
|
|
8662
|
+
}
|
|
8663
|
+
const { crons: _crons, ...dispatch } = item.dispatch;
|
|
8664
|
+
item.dispatch = rest.length === 0 ? dispatch : { ...dispatch, crons: rest };
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
7975
8667
|
/**
|
|
7976
8668
|
* Every `reflectionMs`, a member whose charter reflects takes a reflection turn in the scope of
|
|
7977
8669
|
* its latest working turn, unless it has not worked since its last reflection.
|
|
@@ -8186,6 +8878,7 @@ var Scheduler = class {
|
|
|
8186
8878
|
});
|
|
8187
8879
|
}
|
|
8188
8880
|
}
|
|
8881
|
+
const crons = (await this.board.listCrons()).filter((cron) => cron.ended === void 0);
|
|
8189
8882
|
for (const agent of await this.board.listAgents()) {
|
|
8190
8883
|
if (agent.status !== "active" || agent.cli === null) {
|
|
8191
8884
|
continue;
|
|
@@ -8210,6 +8903,20 @@ var Scheduler = class {
|
|
|
8210
8903
|
role: agent.role
|
|
8211
8904
|
});
|
|
8212
8905
|
}
|
|
8906
|
+
for (const cron of crons.filter((each) => each.agent === agent.name)) {
|
|
8907
|
+
if (cron.failures >= CRON_FAILING_RUNS) {
|
|
8908
|
+
signals.push({
|
|
8909
|
+
kind: "cron_failing",
|
|
8910
|
+
key: `cron_failing:${cron.id}`,
|
|
8911
|
+
summary: `${agent.name}'s cron ${cron.id} "${cron.title}" has had ${cron.failures} failed turns in a row`,
|
|
8912
|
+
value: cron.failures,
|
|
8913
|
+
threshold: CRON_FAILING_RUNS,
|
|
8914
|
+
agent: agent.name,
|
|
8915
|
+
role: agent.role,
|
|
8916
|
+
...cron.scope === SOCIETY_SCOPE ? {} : { project: cron.scope }
|
|
8917
|
+
});
|
|
8918
|
+
}
|
|
8919
|
+
}
|
|
8213
8920
|
const left = (await this.board.listHomeConflicts(agent.name)).filter((conflict) => now - Date.parse(conflict.since) >= this.timings.homeConflictMs);
|
|
8214
8921
|
if (left.length > 0) {
|
|
8215
8922
|
const oldest = Math.max(...left.map((conflict) => now - Date.parse(conflict.since)));
|
|
@@ -8377,6 +9084,10 @@ var Scheduler = class {
|
|
|
8377
9084
|
channel: dispatch.channel,
|
|
8378
9085
|
trigger: dispatch.trigger.kind
|
|
8379
9086
|
}, "dispatching turn");
|
|
9087
|
+
const crons = dispatch.crons ?? [];
|
|
9088
|
+
for (const id of crons) {
|
|
9089
|
+
this.cronsInFlight.add(id);
|
|
9090
|
+
}
|
|
8380
9091
|
const promise = (async () => {
|
|
8381
9092
|
try {
|
|
8382
9093
|
const record = await this.runner.runTurn(dispatch, assignment);
|
|
@@ -8391,6 +9102,9 @@ var Scheduler = class {
|
|
|
8391
9102
|
} finally {
|
|
8392
9103
|
this.running.delete(key);
|
|
8393
9104
|
this.exclusive.delete(key);
|
|
9105
|
+
for (const id of crons) {
|
|
9106
|
+
this.cronsInFlight.delete(id);
|
|
9107
|
+
}
|
|
8394
9108
|
}
|
|
8395
9109
|
})();
|
|
8396
9110
|
this.running.set(key, promise);
|
|
@@ -8419,6 +9133,40 @@ var Scheduler = class {
|
|
|
8419
9133
|
}
|
|
8420
9134
|
return others.some((other) => this.exclusive.get(other) === "reflection");
|
|
8421
9135
|
}
|
|
9136
|
+
/** Queues closing an abandoned task's pull request, when it is a `ghpr` task with one open. */
|
|
9137
|
+
async queueClosing(taskId) {
|
|
9138
|
+
if (this.runner.closePullRequest === void 0) {
|
|
9139
|
+
return;
|
|
9140
|
+
}
|
|
9141
|
+
try {
|
|
9142
|
+
const { task } = await this.board.findTask(taskId);
|
|
9143
|
+
if (awaitsClosing(task)) {
|
|
9144
|
+
this.closings.set(taskId, task.project);
|
|
9145
|
+
}
|
|
9146
|
+
} catch {
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
/** Starts closing each queued pull request; one whose runner is away waits in the queue. */
|
|
9150
|
+
runClosings() {
|
|
9151
|
+
const close = this.runner.closePullRequest?.bind(this.runner);
|
|
9152
|
+
if (close === void 0) {
|
|
9153
|
+
return;
|
|
9154
|
+
}
|
|
9155
|
+
for (const [taskId, project] of this.closings) {
|
|
9156
|
+
this.closings.delete(taskId);
|
|
9157
|
+
const promise = close(project, taskId).then((outcome) => {
|
|
9158
|
+
if (outcome === "deferred" && !this.closings.has(taskId)) {
|
|
9159
|
+
this.closings.set(taskId, project);
|
|
9160
|
+
}
|
|
9161
|
+
return void 0;
|
|
9162
|
+
}).catch((error) => {
|
|
9163
|
+
this.log.error({ project, taskId, error: String(error) }, "closing a pull request failed");
|
|
9164
|
+
}).finally(() => {
|
|
9165
|
+
this.completing.delete(promise);
|
|
9166
|
+
});
|
|
9167
|
+
this.completing.add(promise);
|
|
9168
|
+
}
|
|
9169
|
+
}
|
|
8422
9170
|
/** Starts each queued completion once the turn that finished the task's last stage has ended. */
|
|
8423
9171
|
runCompletions() {
|
|
8424
9172
|
for (const [taskId, item] of this.completions) {
|
|
@@ -8455,6 +9203,7 @@ var TURN_CONTRACT = [
|
|
|
8455
9203
|
"- Work only inside your worktree. In a task's conversation it is a worktree of its own on the task's branch `task/<id>`: commit your work there. After each turn the runner commits what you left on the branch and lets it go for the next holder. Your home worktree is on your own branch, `agent/<name>`, for work tied to no task; nothing lands from it by itself. In a proposal's, a topic's, or a channel's conversation it is a worktree of its own, detached at the project's default branch, which goes when the thread closes or the channel is archived, so work that should last goes on a task's branch. Before each turn the runner fetches the project's remote and brings what holds no work of yours up to the default branch: your own branch when it has no commits of its own, a task's branch nobody has worked on, and a detached worktree with nothing uncommitted; what it leaves where it is, and why, is under Your workspace in the prompt. It never merges or commits for you.",
|
|
8456
9204
|
"- 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.",
|
|
8457
9205
|
"- Silence is allowed. If the digest needs no reply, post nothing.",
|
|
9206
|
+
"- 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.",
|
|
8458
9207
|
"- 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.",
|
|
8459
9208
|
"- 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.",
|
|
8460
9209
|
"- 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.",
|
|
@@ -8617,6 +9366,17 @@ function messageLines(messages, threads) {
|
|
|
8617
9366
|
}
|
|
8618
9367
|
return lines;
|
|
8619
9368
|
}
|
|
9369
|
+
function cronLines(cron, timeZone) {
|
|
9370
|
+
const previous = cron.lastRun === void 0 ? "" : ` The turn of its previous fire ${cron.lastRun.outcome === "stopped" ? "was stopped by the user" : cron.lastRun.outcome} at ${cron.lastRun.at}.`;
|
|
9371
|
+
const next = nextFire(cron, timeZone);
|
|
9372
|
+
const state = isOneTime(cron.schedule) ? " It fired once, as set, and has ended." : cron.ended !== void 0 ? ` ${cron.ended.by} has ended it since it fired (${cron.ended.reason}), so do what it asks only if it still matters.` : next === null ? "" : ` Next it fires at ${next.toISOString()}.`;
|
|
9373
|
+
return [
|
|
9374
|
+
`- "${cron.title}" (cron ${cron.id}), set by ${cron.createdBy} at ${cron.createdAt}, fires ${describeSchedule(cron.schedule, timeZone)}.${previous}${state}`,
|
|
9375
|
+
"",
|
|
9376
|
+
clip(cron.note).split("\n").map((line) => ` ${line}`).join("\n"),
|
|
9377
|
+
""
|
|
9378
|
+
];
|
|
9379
|
+
}
|
|
8620
9380
|
function buildSteerText(messages, threads) {
|
|
8621
9381
|
return `${[
|
|
8622
9382
|
`## New in this conversation while you work (${messages.length})`,
|
|
@@ -8637,6 +9397,10 @@ function buildTurnPrompt(input) {
|
|
|
8637
9397
|
if (dispatch.trigger.taskId !== void 0) {
|
|
8638
9398
|
lines.push(`Task in question: ${dispatch.trigger.taskId}`);
|
|
8639
9399
|
}
|
|
9400
|
+
if (input.clock !== void 0) {
|
|
9401
|
+
const { now, timeZone } = input.clock;
|
|
9402
|
+
lines.push(`It is ${now.toISOString()} now, ${localTime(now, timeZone)} in the society's time zone, ${timeZone}.`);
|
|
9403
|
+
}
|
|
8640
9404
|
const askedIn = dispatch.trigger.channel === void 0 ? null : parseChannelRef(dispatch.trigger.channel).project;
|
|
8641
9405
|
if (dispatch.project === SOCIETY_SCOPE && askedIn !== null) {
|
|
8642
9406
|
lines.push(`You were asked in ${dispatch.trigger.channel}, in project ${askedIn}, which you are not a member of, so this turn is outside any project. Answer there with post_message. If the work needs you in ${askedIn}, ask the concierge or @user to add you, or join it with join_project if your charter grants that verb.`);
|
|
@@ -8651,7 +9415,10 @@ function buildTurnPrompt(input) {
|
|
|
8651
9415
|
lines.push("A task you created is done. Tell whoever asked for it, if anyone did.");
|
|
8652
9416
|
}
|
|
8653
9417
|
if (dispatch.trigger.kind === "proposal_decided") {
|
|
8654
|
-
lines.push("A proposal you made was decided; the decision is the last post in
|
|
9418
|
+
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.");
|
|
9419
|
+
}
|
|
9420
|
+
if (dispatch.trigger.kind === "cron") {
|
|
9421
|
+
lines.push("A cron came due: what it asks is under Crons below.");
|
|
8655
9422
|
}
|
|
8656
9423
|
if (dispatch.trigger.kind === "user_post") {
|
|
8657
9424
|
lines.push("The user posted. Route it: answer where it was posted, in its thread when it came in one, or create what it needs: a task with a plan whose stages name who does them, a thread, or a project, adding citizens to a project first when they are not members. Stay silent when the user already addressed a citizen and nothing else is needed.");
|
|
@@ -8679,6 +9446,11 @@ function buildTurnPrompt(input) {
|
|
|
8679
9446
|
if (ending !== null) {
|
|
8680
9447
|
lines.push("", "## This conversation has ended", "", `${capitalized(ending.how)}. You are woken once more because your workspace here still holds work on no branch, listed under Your workspace, and it is removed when this turn ends, whatever it still holds. You may not be the one who ended the conversation, and the work may not be yours to finish, so decide for each piece:`, "- It is yours and it matters: land it through a task. File one, or use one you hold, switch this worktree to its branch task/<id> (git switch -c task/<id> for a task filed in this turn), and commit there.", `- It matters but is someone else's to finish: put it on a task's branch the same way, then hand that task to whoever should own it and ask them in ${ending.askIn} with a mention, since this conversation takes no more posts.`, "- It is disposable: leave it, and it goes with the workspace.");
|
|
8681
9448
|
}
|
|
9449
|
+
const crons = input.crons ?? [];
|
|
9450
|
+
if (crons.length > 0) {
|
|
9451
|
+
const timeZone = input.clock?.timeZone ?? DEFAULT_TIME_ZONE;
|
|
9452
|
+
lines.push("", "## Crons", "", "Each of these came due for this turn; its note says what to do. Change one with update_cron, or end it with remove_cron, by its id.", "", ...crons.flatMap((cron) => cronLines(cron, timeZone)));
|
|
9453
|
+
}
|
|
8682
9454
|
lines.push("", WORKSPACE_TOKEN);
|
|
8683
9455
|
const conflicts = input.conflicts ?? [];
|
|
8684
9456
|
if (dispatch.trigger.kind === "reflection") {
|
|
@@ -8737,7 +9509,7 @@ function buildTurnPrompt(input) {
|
|
|
8737
9509
|
}
|
|
8738
9510
|
if (project !== void 0 && project !== null && landing === "ghpr") {
|
|
8739
9511
|
const base = project.defaultBranch;
|
|
8740
|
-
lines.push("", `## Project ${project.slug}`, "", `A finished task lands by the board merging its pull request on GitHub into ${base}. Open the pull request yourself, from a branch named for what it does that holds the same files as task/<id>, as \`git push origin task/<id>:<branch>\` gives, and link it to the task with update_task pull_request before its last stage completes, with the merge commit's merge_subject and merge_body, which you may change by linking again until it lands. Never merge the pull request or ${base} yourself, nor push other work to ${base}: the board merges once the task's stages are done, and the runner refuses the pushes.`);
|
|
9512
|
+
lines.push("", `## Project ${project.slug}`, "", `A finished task lands by the board merging its pull request on GitHub into ${base}. Open the pull request yourself, from a branch named for what it does that holds the same files as task/<id>, as \`git push origin task/<id>:<branch>\` gives, and link it to the task with update_task pull_request before its last stage completes, with the merge commit's merge_subject and merge_body, which you may change by linking again until it lands. Never merge the pull request or ${base} yourself, nor push other work to ${base}: the board merges once the task's stages are done, and the runner refuses the pushes. Abandoning the task closes its linked pull request and deletes its branch.`);
|
|
8741
9513
|
}
|
|
8742
9514
|
if (conflicts.length > 0) {
|
|
8743
9515
|
lines.push("", "## Edits to reconcile in your home", "", "Two of your turns changed these files at once on different runners. The file in place kept the other turn's version; yours is the copy named here, beside it. Merge what still holds into the file, then delete the copy.", "", ...conflicts.map((conflict) => `- ${conflict.path}, beside ${conflict.file}, since ${conflict.since}`));
|
|
@@ -8861,7 +9633,8 @@ var TurnHost = class {
|
|
|
8861
9633
|
usage: null,
|
|
8862
9634
|
costUsd: 0,
|
|
8863
9635
|
toolCalls: 0,
|
|
8864
|
-
model: agent.model ?? null
|
|
9636
|
+
model: agent.model ?? null,
|
|
9637
|
+
...dispatch.crons === void 0 ? {} : { crons: dispatch.crons }
|
|
8865
9638
|
};
|
|
8866
9639
|
if (agent.status === "retired") {
|
|
8867
9640
|
return { failed: await this.fail(base, `${agent.name} is retired`) };
|
|
@@ -8948,8 +9721,18 @@ var TurnHost = class {
|
|
|
8948
9721
|
...threadRecord.state === "closed" ? { replyIn: await this.replyIn(threadRecord) } : {}
|
|
8949
9722
|
};
|
|
8950
9723
|
const ending = dispatch.trigger.kind === "closing" ? await this.endingOf(dispatch.project, threadRecord, task, channel) : null;
|
|
9724
|
+
const society = await this.board.society();
|
|
9725
|
+
const crons = [];
|
|
9726
|
+
for (const id of dispatch.crons ?? []) {
|
|
9727
|
+
const cron = await this.board.readCron(id).catch(() => null);
|
|
9728
|
+
if (cron !== null) {
|
|
9729
|
+
crons.push(cron);
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
8951
9732
|
const prompt = buildTurnPrompt({
|
|
8952
9733
|
dispatch,
|
|
9734
|
+
clock: { now: this.now(), timeZone: society.timezone },
|
|
9735
|
+
crons,
|
|
8953
9736
|
messages,
|
|
8954
9737
|
threads,
|
|
8955
9738
|
conversation,
|
|
@@ -9308,7 +10091,8 @@ var TurnHost = class {
|
|
|
9308
10091
|
usage: null,
|
|
9309
10092
|
costUsd: 0,
|
|
9310
10093
|
toolCalls: 0,
|
|
9311
|
-
model: agent?.model ?? null
|
|
10094
|
+
model: agent?.model ?? null,
|
|
10095
|
+
...dispatch.crons === void 0 ? {} : { crons: dispatch.crons }
|
|
9312
10096
|
}, reason);
|
|
9313
10097
|
}
|
|
9314
10098
|
/**
|
|
@@ -9895,6 +10679,49 @@ var RunnerHub = class {
|
|
|
9895
10679
|
});
|
|
9896
10680
|
return "done";
|
|
9897
10681
|
}
|
|
10682
|
+
/**
|
|
10683
|
+
* Closes the pull request of a `ghpr` task abandoned with one linked, on the runner its project
|
|
10684
|
+
* lives on, whose `gh` login reaches GitHub, and records how it went on the task; `deferred`
|
|
10685
|
+
* while that runner is away.
|
|
10686
|
+
*/
|
|
10687
|
+
async closePullRequest(project, taskId) {
|
|
10688
|
+
const { task } = await this.board.findTask(taskId);
|
|
10689
|
+
const pull = task.pullRequest;
|
|
10690
|
+
if (task.status !== "abandoned" || task.onDone !== "ghpr" || pull === void 0 || pull.closed !== void 0) {
|
|
10691
|
+
return "done";
|
|
10692
|
+
}
|
|
10693
|
+
const record = await this.board.readProject(project);
|
|
10694
|
+
let outcome = {
|
|
10695
|
+
ok: false,
|
|
10696
|
+
detail: `${project} lives on no runner yet, so none could close ${pull.url}`
|
|
10697
|
+
};
|
|
10698
|
+
if (record.runner !== void 0) {
|
|
10699
|
+
const seat = this.seats.get(record.runner);
|
|
10700
|
+
if (seat === void 0 || seat.send === null) {
|
|
10701
|
+
return "deferred";
|
|
10702
|
+
}
|
|
10703
|
+
try {
|
|
10704
|
+
outcome = MergeOutcomeSchema.parse(await this.request(seat, (request) => ({
|
|
10705
|
+
type: "close",
|
|
10706
|
+
request,
|
|
10707
|
+
close: {
|
|
10708
|
+
repo: projectRepo(record),
|
|
10709
|
+
url: pull.url,
|
|
10710
|
+
comment: `Closed by Stellaris: the task this pull request was opened for, "${task.title}", was abandoned.`
|
|
10711
|
+
}
|
|
10712
|
+
})));
|
|
10713
|
+
} catch (error) {
|
|
10714
|
+
this.log.warn({ project, taskId, error: String(error) }, "closing a pull request deferred");
|
|
10715
|
+
return "deferred";
|
|
10716
|
+
}
|
|
10717
|
+
}
|
|
10718
|
+
await this.board.recordPullRequestClosed(SYSTEM_ACTOR, {
|
|
10719
|
+
taskId,
|
|
10720
|
+
ok: outcome.ok,
|
|
10721
|
+
detail: outcome.detail
|
|
10722
|
+
});
|
|
10723
|
+
return "done";
|
|
10724
|
+
}
|
|
9898
10725
|
/**
|
|
9899
10726
|
* One path on a branch of a project's repository, read on the runner the project lives on.
|
|
9900
10727
|
* NOT_FOUND when the project has no runner yet or the branch or the path does not exist, and
|
|
@@ -10171,20 +10998,20 @@ async function handleMcpRequest(board2, actor, request, version) {
|
|
|
10171
10998
|
import { Hono as Hono3 } from "hono";
|
|
10172
10999
|
import { compress } from "hono/compress";
|
|
10173
11000
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
10174
|
-
import { z as
|
|
11001
|
+
import { z as z16, ZodError } from "zod";
|
|
10175
11002
|
|
|
10176
11003
|
// apps/server/dist/github-auth.js
|
|
10177
11004
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
10178
11005
|
import { Hono } from "hono";
|
|
10179
|
-
import { z as
|
|
11006
|
+
import { z as z15 } from "zod";
|
|
10180
11007
|
var SIGN_IN_TTL_MS = 30 * 24 * 36e5;
|
|
10181
11008
|
var STATE_TTL_MS = 10 * 6e4;
|
|
10182
11009
|
var MAX_STATES = 1e3;
|
|
10183
|
-
var TokenAnswerSchema =
|
|
10184
|
-
var GithubUserSchema =
|
|
10185
|
-
login:
|
|
10186
|
-
id:
|
|
10187
|
-
avatar_url:
|
|
11010
|
+
var TokenAnswerSchema = z15.object({ access_token: z15.string().min(1) });
|
|
11011
|
+
var GithubUserSchema = z15.object({
|
|
11012
|
+
login: z15.string().min(1),
|
|
11013
|
+
id: z15.number().int(),
|
|
11014
|
+
avatar_url: z15.string().optional()
|
|
10188
11015
|
});
|
|
10189
11016
|
function githubAvatar(url) {
|
|
10190
11017
|
try {
|
|
@@ -10658,17 +11485,18 @@ var ERROR_STATUS = {
|
|
|
10658
11485
|
INVALID_TRANSITION: 409,
|
|
10659
11486
|
INVALID_STATE: 409
|
|
10660
11487
|
};
|
|
10661
|
-
var RetireBodySchema =
|
|
10662
|
-
var RunnerBodySchema =
|
|
10663
|
-
var EnrollmentApprovalSchema =
|
|
10664
|
-
var HomeRunnerBodySchema =
|
|
10665
|
-
var ModelBodySchema =
|
|
10666
|
-
var EffortBodySchema =
|
|
10667
|
-
var HistoryLimitSchema =
|
|
10668
|
-
var
|
|
11488
|
+
var RetireBodySchema = z16.object({ reason: z16.string().min(1) });
|
|
11489
|
+
var RunnerBodySchema = z16.object({ name: NameSchema });
|
|
11490
|
+
var EnrollmentApprovalSchema = z16.object({ name: NameSchema });
|
|
11491
|
+
var HomeRunnerBodySchema = z16.object({ runner: NameSchema.nullable() });
|
|
11492
|
+
var ModelBodySchema = z16.object({ model: ModelNameSchema.nullable() });
|
|
11493
|
+
var EffortBodySchema = z16.object({ effort: EffortSchema.nullable() });
|
|
11494
|
+
var HistoryLimitSchema = z16.coerce.number().int().min(1).max(500);
|
|
11495
|
+
var TimezoneBodySchema = z16.object({ timezone: TimeZoneSchema });
|
|
11496
|
+
var ChannelBodySchema = z16.object({
|
|
10669
11497
|
project: NameSchema.nullable().default(null),
|
|
10670
11498
|
name: NameSchema,
|
|
10671
|
-
purpose:
|
|
11499
|
+
purpose: z16.string().min(1)
|
|
10672
11500
|
});
|
|
10673
11501
|
function bearer2(c) {
|
|
10674
11502
|
const header = c.req.header("authorization") ?? "";
|
|
@@ -10717,6 +11545,10 @@ function createApp(deps) {
|
|
|
10717
11545
|
});
|
|
10718
11546
|
api.delete("/sign-in", async (c) => c.json({ signedOut: await board2.signOut(bearer2(c) ?? "") }));
|
|
10719
11547
|
api.get("/society", async (c) => c.json(await board2.society()));
|
|
11548
|
+
api.put("/society/timezone", async (c) => {
|
|
11549
|
+
const body = TimezoneBodySchema.parse(await c.req.json());
|
|
11550
|
+
return c.json(await board2.setTimezone(c.get("actor"), body.timezone));
|
|
11551
|
+
});
|
|
10720
11552
|
api.get("/version", (c) => c.json({ version }));
|
|
10721
11553
|
api.get("/agents", async (c) => c.json((await board2.listAgents()).map(({ tokenHash: _hash, ...agent }) => agent)));
|
|
10722
11554
|
api.get("/members", async (c) => c.json(await board2.listMembers()));
|
|
@@ -10768,6 +11600,7 @@ function createApp(deps) {
|
|
|
10768
11600
|
});
|
|
10769
11601
|
}
|
|
10770
11602
|
api.get("/proposals", async (c) => c.json(await board2.listProposals()));
|
|
11603
|
+
api.get("/crons", async (c) => c.json(await board2.listCrons()));
|
|
10771
11604
|
api.get("/proposals/:id", async (c) => c.json(await board2.readProposal(c.req.param("id"))));
|
|
10772
11605
|
api.get("/signals", async (c) => c.json(await board2.listSignals(Number(c.req.query("limit") ?? "100"))));
|
|
10773
11606
|
api.get("/metrics", async (c) => {
|
|
@@ -10817,7 +11650,7 @@ function createApp(deps) {
|
|
|
10817
11650
|
});
|
|
10818
11651
|
api.put("/roles/:name", async (c) => {
|
|
10819
11652
|
const charter = RoleCharterSchema.parse({
|
|
10820
|
-
...
|
|
11653
|
+
...z16.record(z16.string(), z16.unknown()).parse(await c.req.json()),
|
|
10821
11654
|
name: c.req.param("name")
|
|
10822
11655
|
});
|
|
10823
11656
|
return c.json(await board2.setRoleCharter(c.get("actor"), charter));
|