@kubitnodes/stellaris 0.3.8 → 0.3.10
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 +42 -20
- package/dist/server/index.js +117 -80
- package/package.json +1 -1
- package/web/dist/assets/{BackLink-DrYyD7II.js → BackLink-B3tz8OFm.js} +1 -1
- package/web/dist/assets/{ChannelView-B2sufhRs.js → ChannelView-Dkzr1zkF.js} +1 -1
- package/web/dist/assets/{CitizenView-B9u0X7ZY.js → CitizenView-BLgyEvYz.js} +1 -1
- package/web/dist/assets/{CitizensView-B60Z7gmH.js → CitizensView-DttP5FqR.js} +1 -1
- package/web/dist/assets/{KnowledgeView-Cppc2D7_.js → KnowledgeView-DFAC95aF.js} +1 -1
- package/web/dist/assets/{Markdown-B390nfRS.js → Markdown-4W4xjIq7.js} +1 -1
- package/web/dist/assets/{MessageItem-D54BS9OS.js → MessageItem-Bfpve1_Y.js} +1 -1
- package/web/dist/assets/{MetricsView-CsoRuj4g.js → MetricsView-Bp486U0v.js} +1 -1
- package/web/dist/assets/{NeedsYouView-BPjjyZRx.js → NeedsYouView-CJ4COcNn.js} +1 -1
- package/web/dist/assets/{Overview-BWEXLQaq.js → Overview-BnO1K52h.js} +1 -1
- package/web/dist/assets/{Pane-DvocjNie.js → Pane-xzIpwnBD.js} +1 -1
- package/web/dist/assets/ProjectView-w0mcF0KW.js +1 -0
- package/web/dist/assets/{ProposalView-cPi-EUXR.js → ProposalView-CyJCwUYK.js} +1 -1
- package/web/dist/assets/{ProposalsView-Bq7iCMtk.js → ProposalsView-XrraDhwt.js} +1 -1
- package/web/dist/assets/{RunnersView-BiJmbsoG.js → RunnersView-H3kCdSQB.js} +1 -1
- package/web/dist/assets/{SocietyView-6iPyxKze.js → SocietyView-Cf8ENDAw.js} +1 -1
- package/web/dist/assets/{SubjectThread-D2BHnecl.js → SubjectThread-ClMmgVVd.js} +1 -1
- package/web/dist/assets/{TaskFileView-DDs6OHWY.js → TaskFileView-CRwwL5K8.js} +1 -1
- package/web/dist/assets/{TaskView-3_2vs2fy.js → TaskView-A9IgTxQ0.js} +1 -1
- package/web/dist/assets/{TasksView-B7a0sWKu.js → TasksView-CnhTIldG.js} +1 -1
- package/web/dist/assets/{ThreadCard-9h03aKCB.js → ThreadCard-fvf8cY_l.js} +1 -1
- package/web/dist/assets/{ThreadForms-uORhDqAo.js → ThreadForms-D09Ykgj9.js} +1 -1
- package/web/dist/assets/{ThreadView-DVSe1HGK.js → ThreadView-BqNSssCf.js} +1 -1
- package/web/dist/assets/{citizen-CDTy3g7f.js → citizen-C27Wvimj.js} +1 -1
- package/web/dist/assets/{format-C6tBTRKZ.js → format-lz-5NQWT.js} +1 -1
- package/web/dist/assets/{index-CugUsWCm.js → index-CoKyPk79.js} +3 -3
- package/web/dist/assets/index-t2fXHB5Y.css +2 -0
- package/web/dist/assets/{tasks-Bh3GaY7V.js → tasks-DK1cnXqs.js} +1 -1
- package/web/dist/assets/{useStickyScroll-C6bu36lV.js → useStickyScroll-CfP3Yl_4.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/dist/assets/ProjectView-BIC7kE4L.js +0 -1
- package/web/dist/assets/index-D4oCtkuZ.css +0 -2
package/dist/cli/index.js
CHANGED
|
@@ -168,6 +168,7 @@ var SocietySchema = z3.object({
|
|
|
168
168
|
channels: z3.array(NameSchema)
|
|
169
169
|
});
|
|
170
170
|
var CompletionEffectSchema = z3.enum(["none", "merge"]);
|
|
171
|
+
var DefaultBranchSchema = z3.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");
|
|
171
172
|
var StageIdSchema = z3.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
|
|
172
173
|
var stageFields = {
|
|
173
174
|
name: z3.string().trim().min(1).max(120),
|
|
@@ -632,7 +633,7 @@ var VerbInputs = {
|
|
|
632
633
|
slug: NameSchema,
|
|
633
634
|
name: z4.string().min(1).optional(),
|
|
634
635
|
repo: z4.string().min(1).nullable().default(null),
|
|
635
|
-
default_branch:
|
|
636
|
+
default_branch: DefaultBranchSchema.default("main"),
|
|
636
637
|
on_done: CompletionEffectSchema.optional()
|
|
637
638
|
}),
|
|
638
639
|
join_project: z4.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
@@ -659,7 +660,9 @@ var VerbInputs = {
|
|
|
659
660
|
project: NameSchema,
|
|
660
661
|
on_done: CompletionEffectSchema.optional(),
|
|
661
662
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
662
|
-
name: z4.string().trim().min(1).max(120).optional()
|
|
663
|
+
name: z4.string().trim().min(1).max(120).optional(),
|
|
664
|
+
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
665
|
+
default_branch: DefaultBranchSchema.optional()
|
|
663
666
|
}),
|
|
664
667
|
archive_project: z4.object({ project: NameSchema, reason: z4.string().min(1) })
|
|
665
668
|
};
|
|
@@ -863,11 +866,12 @@ var ProjectRepoSchema = z6.object({
|
|
|
863
866
|
defaultBranch: z6.string().min(1)
|
|
864
867
|
});
|
|
865
868
|
var TurnWorkspaceSchema = z6.discriminatedUnion("kind", [
|
|
866
|
-
z6.object({ kind: z6.literal("home") }),
|
|
869
|
+
z6.object({ kind: z6.literal("home"), thread: UlidSchema.optional() }),
|
|
867
870
|
z6.object({
|
|
868
871
|
kind: z6.literal("project"),
|
|
869
872
|
repo: ProjectRepoSchema,
|
|
870
|
-
branches: z6.array(z6.string().min(1))
|
|
873
|
+
branches: z6.array(z6.string().min(1)),
|
|
874
|
+
thread: UlidSchema.optional()
|
|
871
875
|
}),
|
|
872
876
|
z6.object({ kind: z6.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
|
|
873
877
|
]);
|
|
@@ -918,7 +922,7 @@ var TurnOutcomeSchema = z6.object({
|
|
|
918
922
|
model: z6.string().nullable(),
|
|
919
923
|
work: TurnWorkSchema.nullable()
|
|
920
924
|
});
|
|
921
|
-
var TurnAckSchema = z6.object({
|
|
925
|
+
var TurnAckSchema = z6.object({ dropWorkspace: z6.boolean() });
|
|
922
926
|
var LandRequestSchema = z6.object({
|
|
923
927
|
repo: ProjectRepoSchema,
|
|
924
928
|
branch: z6.string().min(1)
|
|
@@ -1297,7 +1301,7 @@ var RunnerConfigSchema = z10.object({
|
|
|
1297
1301
|
});
|
|
1298
1302
|
|
|
1299
1303
|
// packages/shared/dist/version.js
|
|
1300
|
-
var STELLARIS_VERSION = "0.3.
|
|
1304
|
+
var STELLARIS_VERSION = "0.3.10";
|
|
1301
1305
|
|
|
1302
1306
|
// packages/board-core/dist/errors.js
|
|
1303
1307
|
var BoardError = class extends Error {
|
|
@@ -1342,15 +1346,28 @@ async function writeJson(file, value) {
|
|
|
1342
1346
|
await writeFileAtomic(file, `${JSON.stringify(value, null, 2)}
|
|
1343
1347
|
`);
|
|
1344
1348
|
}
|
|
1349
|
+
function parseMatter(raw) {
|
|
1350
|
+
return matter(raw, {});
|
|
1351
|
+
}
|
|
1345
1352
|
async function readMarkdown(file, schema) {
|
|
1346
1353
|
const raw = await readFile(file, "utf8");
|
|
1347
|
-
const parsed =
|
|
1354
|
+
const parsed = parseMatter(raw);
|
|
1348
1355
|
return { data: schema.parse(parsed.data), body: parsed.content.replace(/^\n/, "") };
|
|
1349
1356
|
}
|
|
1357
|
+
async function readLooseMarkdown(file) {
|
|
1358
|
+
const raw = await readFile(file, "utf8");
|
|
1359
|
+
try {
|
|
1360
|
+
const parsed = parseMatter(raw);
|
|
1361
|
+
return { data: parsed.data, body: parsed.content.replace(/^\n/, ""), error: null };
|
|
1362
|
+
} catch (error) {
|
|
1363
|
+
const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
1364
|
+
return { data: {}, body: raw, error: reason.replace(/:$/, "") };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1350
1367
|
async function writeMarkdown(file, data, body) {
|
|
1351
1368
|
const normalized = body.endsWith("\n") || body.length === 0 ? body : `${body}
|
|
1352
1369
|
`;
|
|
1353
|
-
await writeFileAtomic(file, matter.stringify(normalized, data));
|
|
1370
|
+
await writeFileAtomic(file, matter.stringify({ content: normalized }, data, {}));
|
|
1354
1371
|
}
|
|
1355
1372
|
function isMissing(error) {
|
|
1356
1373
|
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
@@ -3202,7 +3219,7 @@ ${charter.purpose}
|
|
|
3202
3219
|
}
|
|
3203
3220
|
async readProfile(name) {
|
|
3204
3221
|
const file = this.paths.agentProfile(name);
|
|
3205
|
-
return await exists(file) ? (await
|
|
3222
|
+
return await exists(file) ? (await readLooseMarkdown(file)).body : "";
|
|
3206
3223
|
}
|
|
3207
3224
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
3208
3225
|
async readSocietyNorms() {
|
|
@@ -3249,10 +3266,11 @@ ${charter.purpose}
|
|
|
3249
3266
|
if (!name.success) {
|
|
3250
3267
|
continue;
|
|
3251
3268
|
}
|
|
3252
|
-
const doc = await
|
|
3269
|
+
const doc = await readLooseMarkdown(file);
|
|
3253
3270
|
skills.push({
|
|
3254
3271
|
name: name.data,
|
|
3255
|
-
|
|
3272
|
+
// The index is where its writer sees that the file needs fixing.
|
|
3273
|
+
summary: doc.error === null ? skillSummary(doc.data, doc.body) : `its frontmatter does not parse (${doc.error}), so fix the file`,
|
|
3256
3274
|
scope,
|
|
3257
3275
|
path: file
|
|
3258
3276
|
});
|
|
@@ -4628,29 +4646,32 @@ ${rationale}`}`);
|
|
|
4628
4646
|
...args.on_done === void 0 ? {} : { onDone: args.on_done }
|
|
4629
4647
|
}));
|
|
4630
4648
|
}
|
|
4631
|
-
/** Sets a project's completion effect. */
|
|
4632
4649
|
/**
|
|
4633
|
-
* Sets a project's completion effect,
|
|
4634
|
-
* channel references, branches, and runners' directories all carry it.
|
|
4650
|
+
* Sets a project's completion effect, display name, or default branch, any of them at once. The
|
|
4651
|
+
* slug stays: addresses, channel references, branches, and runners' directories all carry it. A
|
|
4652
|
+
* new default branch the project's repository lacks is made by its runner before the next turn.
|
|
4635
4653
|
*/
|
|
4636
4654
|
async configureProject(actor, input) {
|
|
4637
4655
|
const args = VerbInputs.configure_project.parse(input);
|
|
4638
4656
|
await this.authorize(actor, "configure_project");
|
|
4639
|
-
if (args.on_done === void 0 && args.name === void 0) {
|
|
4640
|
-
throw new BoardError("VALIDATION", "configure_project needs on_done, name, or
|
|
4657
|
+
if (args.on_done === void 0 && args.name === void 0 && args.default_branch === void 0) {
|
|
4658
|
+
throw new BoardError("VALIDATION", "configure_project needs on_done, name, or default_branch");
|
|
4641
4659
|
}
|
|
4642
4660
|
return this.mutex.run(async () => {
|
|
4643
4661
|
const previous = await this.readActiveProject(args.project);
|
|
4644
4662
|
const project2 = await this.updateProject(args.project, (current) => ({
|
|
4645
4663
|
...current,
|
|
4646
4664
|
...args.on_done === void 0 ? {} : { onDone: args.on_done },
|
|
4647
|
-
...args.name === void 0 ? {} : { name: args.name }
|
|
4665
|
+
...args.name === void 0 ? {} : { name: args.name },
|
|
4666
|
+
...args.default_branch === void 0 ? {} : { defaultBranch: args.default_branch }
|
|
4648
4667
|
}));
|
|
4649
4668
|
await this.events.append("project.configured", actor.name, {
|
|
4650
4669
|
slug: project2.slug,
|
|
4651
4670
|
onDone: project2.onDone,
|
|
4652
4671
|
name: project2.name,
|
|
4653
|
-
|
|
4672
|
+
defaultBranch: project2.defaultBranch,
|
|
4673
|
+
...project2.name === previous.name ? {} : { previousName: previous.name },
|
|
4674
|
+
...project2.defaultBranch === previous.defaultBranch ? {} : { previousDefaultBranch: previous.defaultBranch }
|
|
4654
4675
|
});
|
|
4655
4676
|
return project2;
|
|
4656
4677
|
});
|
|
@@ -4911,7 +4932,7 @@ ${rationale}`}`);
|
|
|
4911
4932
|
}
|
|
4912
4933
|
async readMemoryCore(agent2) {
|
|
4913
4934
|
const file = this.paths.agentMemoryCore(agent2);
|
|
4914
|
-
return await exists(file) ? (await
|
|
4935
|
+
return await exists(file) ? (await readLooseMarkdown(file)).body : "";
|
|
4915
4936
|
}
|
|
4916
4937
|
/**
|
|
4917
4938
|
* Moves a conversation's cursor forward to `cursor` once a turn there has delivered up to it. It
|
|
@@ -5126,7 +5147,8 @@ ${rationale}`}`);
|
|
|
5126
5147
|
if (!await exists(file)) {
|
|
5127
5148
|
return { data: { project: slug }, body: "" };
|
|
5128
5149
|
}
|
|
5129
|
-
|
|
5150
|
+
const { data, body } = await readLooseMarkdown(file);
|
|
5151
|
+
return { data, body };
|
|
5130
5152
|
}
|
|
5131
5153
|
async listRunners() {
|
|
5132
5154
|
const runners = [];
|
package/dist/server/index.js
CHANGED
|
@@ -168,6 +168,7 @@ var SocietySchema = z3.object({
|
|
|
168
168
|
channels: z3.array(NameSchema)
|
|
169
169
|
});
|
|
170
170
|
var CompletionEffectSchema = z3.enum(["none", "merge"]);
|
|
171
|
+
var DefaultBranchSchema = z3.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");
|
|
171
172
|
var StageIdSchema = z3.string().regex(/^s[1-9][0-9]*$/, "expected a stage id such as s1");
|
|
172
173
|
var stageFields = {
|
|
173
174
|
name: z3.string().trim().min(1).max(120),
|
|
@@ -632,7 +633,7 @@ var VerbInputs = {
|
|
|
632
633
|
slug: NameSchema,
|
|
633
634
|
name: z4.string().min(1).optional(),
|
|
634
635
|
repo: z4.string().min(1).nullable().default(null),
|
|
635
|
-
default_branch:
|
|
636
|
+
default_branch: DefaultBranchSchema.default("main"),
|
|
636
637
|
on_done: CompletionEffectSchema.optional()
|
|
637
638
|
}),
|
|
638
639
|
join_project: z4.object({ project: NameSchema, agent: NameSchema.optional() }),
|
|
@@ -659,7 +660,9 @@ var VerbInputs = {
|
|
|
659
660
|
project: NameSchema,
|
|
660
661
|
on_done: CompletionEffectSchema.optional(),
|
|
661
662
|
/** A new display name; the slug, which everything refers to the project by, never changes. */
|
|
662
|
-
name: z4.string().trim().min(1).max(120).optional()
|
|
663
|
+
name: z4.string().trim().min(1).max(120).optional(),
|
|
664
|
+
/** The branch tasks start from and land on from now; tasks in play keep their branches. */
|
|
665
|
+
default_branch: DefaultBranchSchema.optional()
|
|
663
666
|
}),
|
|
664
667
|
archive_project: z4.object({ project: NameSchema, reason: z4.string().min(1) })
|
|
665
668
|
};
|
|
@@ -686,7 +689,7 @@ var VERB_DESCRIPTIONS = {
|
|
|
686
689
|
remove_knowledge: "Remove a knowledge topic that is wrong, superseded, or folded into another, so no turn or search finds it again. Whoever may write the topic may remove it; the board keeps its text aside for the user to restore.",
|
|
687
690
|
plan_task: "Reshape a task's plan from the current stage onward, or after it while someone holds it: keep a stage by passing its id, drop it by leaving it out, add one without an id. Gated stages and on_done: user, steward, concierge.",
|
|
688
691
|
advance_task: "Finish the stage you hold, with a note for the task's thread on what you did. The next stage becomes current; past the last one the task is done.",
|
|
689
|
-
configure_project: "Set a project's completion effect (none, or merge to land each finished task's branch on the default branch), its display name, or
|
|
692
|
+
configure_project: "Set a project's completion effect (none, or merge to land each finished task's branch on the default branch), its display name, or its default branch, any of them at once. The slug never changes.",
|
|
690
693
|
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."
|
|
691
694
|
};
|
|
692
695
|
|
|
@@ -896,11 +899,12 @@ var ProjectRepoSchema = z6.object({
|
|
|
896
899
|
defaultBranch: z6.string().min(1)
|
|
897
900
|
});
|
|
898
901
|
var TurnWorkspaceSchema = z6.discriminatedUnion("kind", [
|
|
899
|
-
z6.object({ kind: z6.literal("home") }),
|
|
902
|
+
z6.object({ kind: z6.literal("home"), thread: UlidSchema.optional() }),
|
|
900
903
|
z6.object({
|
|
901
904
|
kind: z6.literal("project"),
|
|
902
905
|
repo: ProjectRepoSchema,
|
|
903
|
-
branches: z6.array(z6.string().min(1))
|
|
906
|
+
branches: z6.array(z6.string().min(1)),
|
|
907
|
+
thread: UlidSchema.optional()
|
|
904
908
|
}),
|
|
905
909
|
z6.object({ kind: z6.literal("task"), repo: ProjectRepoSchema, taskId: UlidSchema })
|
|
906
910
|
]);
|
|
@@ -951,7 +955,7 @@ var TurnOutcomeSchema = z6.object({
|
|
|
951
955
|
model: z6.string().nullable(),
|
|
952
956
|
work: TurnWorkSchema.nullable()
|
|
953
957
|
});
|
|
954
|
-
var TurnAckSchema = z6.object({
|
|
958
|
+
var TurnAckSchema = z6.object({ dropWorkspace: z6.boolean() });
|
|
955
959
|
var LandRequestSchema = z6.object({
|
|
956
960
|
repo: ProjectRepoSchema,
|
|
957
961
|
branch: z6.string().min(1)
|
|
@@ -1378,7 +1382,7 @@ var RunnerConfigSchema = z10.object({
|
|
|
1378
1382
|
});
|
|
1379
1383
|
|
|
1380
1384
|
// packages/shared/dist/version.js
|
|
1381
|
-
var STELLARIS_VERSION = "0.3.
|
|
1385
|
+
var STELLARIS_VERSION = "0.3.10";
|
|
1382
1386
|
|
|
1383
1387
|
// packages/board-core/dist/errors.js
|
|
1384
1388
|
var BoardError = class extends Error {
|
|
@@ -1426,15 +1430,28 @@ async function writeJson(file, value) {
|
|
|
1426
1430
|
await writeFileAtomic(file, `${JSON.stringify(value, null, 2)}
|
|
1427
1431
|
`);
|
|
1428
1432
|
}
|
|
1433
|
+
function parseMatter(raw) {
|
|
1434
|
+
return matter(raw, {});
|
|
1435
|
+
}
|
|
1429
1436
|
async function readMarkdown(file, schema) {
|
|
1430
1437
|
const raw = await readFile(file, "utf8");
|
|
1431
|
-
const parsed =
|
|
1438
|
+
const parsed = parseMatter(raw);
|
|
1432
1439
|
return { data: schema.parse(parsed.data), body: parsed.content.replace(/^\n/, "") };
|
|
1433
1440
|
}
|
|
1441
|
+
async function readLooseMarkdown(file) {
|
|
1442
|
+
const raw = await readFile(file, "utf8");
|
|
1443
|
+
try {
|
|
1444
|
+
const parsed = parseMatter(raw);
|
|
1445
|
+
return { data: parsed.data, body: parsed.content.replace(/^\n/, ""), error: null };
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
const reason = (error instanceof Error ? error.message : String(error)).split("\n")[0] ?? "";
|
|
1448
|
+
return { data: {}, body: raw, error: reason.replace(/:$/, "") };
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1434
1451
|
async function writeMarkdown(file, data, body) {
|
|
1435
1452
|
const normalized2 = body.endsWith("\n") || body.length === 0 ? body : `${body}
|
|
1436
1453
|
`;
|
|
1437
|
-
await writeFileAtomic(file, matter.stringify(normalized2, data));
|
|
1454
|
+
await writeFileAtomic(file, matter.stringify({ content: normalized2 }, data, {}));
|
|
1438
1455
|
}
|
|
1439
1456
|
function isMissing(error) {
|
|
1440
1457
|
return typeof error === "object" && error !== null && error.code === "ENOENT";
|
|
@@ -3286,7 +3303,7 @@ ${charter.purpose}
|
|
|
3286
3303
|
}
|
|
3287
3304
|
async readProfile(name) {
|
|
3288
3305
|
const file = this.paths.agentProfile(name);
|
|
3289
|
-
return await exists(file) ? (await
|
|
3306
|
+
return await exists(file) ? (await readLooseMarkdown(file)).body : "";
|
|
3290
3307
|
}
|
|
3291
3308
|
/** The society's norms, the one knowledge topic every turn loads. Empty until the steward writes it. */
|
|
3292
3309
|
async readSocietyNorms() {
|
|
@@ -3333,10 +3350,11 @@ ${charter.purpose}
|
|
|
3333
3350
|
if (!name.success) {
|
|
3334
3351
|
continue;
|
|
3335
3352
|
}
|
|
3336
|
-
const doc = await
|
|
3353
|
+
const doc = await readLooseMarkdown(file);
|
|
3337
3354
|
skills.push({
|
|
3338
3355
|
name: name.data,
|
|
3339
|
-
|
|
3356
|
+
// The index is where its writer sees that the file needs fixing.
|
|
3357
|
+
summary: doc.error === null ? skillSummary(doc.data, doc.body) : `its frontmatter does not parse (${doc.error}), so fix the file`,
|
|
3340
3358
|
scope,
|
|
3341
3359
|
path: file
|
|
3342
3360
|
});
|
|
@@ -4712,29 +4730,32 @@ ${rationale}`}`);
|
|
|
4712
4730
|
...args.on_done === void 0 ? {} : { onDone: args.on_done }
|
|
4713
4731
|
}));
|
|
4714
4732
|
}
|
|
4715
|
-
/** Sets a project's completion effect. */
|
|
4716
4733
|
/**
|
|
4717
|
-
* Sets a project's completion effect,
|
|
4718
|
-
* channel references, branches, and runners' directories all carry it.
|
|
4734
|
+
* Sets a project's completion effect, display name, or default branch, any of them at once. The
|
|
4735
|
+
* slug stays: addresses, channel references, branches, and runners' directories all carry it. A
|
|
4736
|
+
* new default branch the project's repository lacks is made by its runner before the next turn.
|
|
4719
4737
|
*/
|
|
4720
4738
|
async configureProject(actor, input) {
|
|
4721
4739
|
const args = VerbInputs.configure_project.parse(input);
|
|
4722
4740
|
await this.authorize(actor, "configure_project");
|
|
4723
|
-
if (args.on_done === void 0 && args.name === void 0) {
|
|
4724
|
-
throw new BoardError("VALIDATION", "configure_project needs on_done, name, or
|
|
4741
|
+
if (args.on_done === void 0 && args.name === void 0 && args.default_branch === void 0) {
|
|
4742
|
+
throw new BoardError("VALIDATION", "configure_project needs on_done, name, or default_branch");
|
|
4725
4743
|
}
|
|
4726
4744
|
return this.mutex.run(async () => {
|
|
4727
4745
|
const previous = await this.readActiveProject(args.project);
|
|
4728
4746
|
const project = await this.updateProject(args.project, (current) => ({
|
|
4729
4747
|
...current,
|
|
4730
4748
|
...args.on_done === void 0 ? {} : { onDone: args.on_done },
|
|
4731
|
-
...args.name === void 0 ? {} : { name: args.name }
|
|
4749
|
+
...args.name === void 0 ? {} : { name: args.name },
|
|
4750
|
+
...args.default_branch === void 0 ? {} : { defaultBranch: args.default_branch }
|
|
4732
4751
|
}));
|
|
4733
4752
|
await this.events.append("project.configured", actor.name, {
|
|
4734
4753
|
slug: project.slug,
|
|
4735
4754
|
onDone: project.onDone,
|
|
4736
4755
|
name: project.name,
|
|
4737
|
-
|
|
4756
|
+
defaultBranch: project.defaultBranch,
|
|
4757
|
+
...project.name === previous.name ? {} : { previousName: previous.name },
|
|
4758
|
+
...project.defaultBranch === previous.defaultBranch ? {} : { previousDefaultBranch: previous.defaultBranch }
|
|
4738
4759
|
});
|
|
4739
4760
|
return project;
|
|
4740
4761
|
});
|
|
@@ -4995,7 +5016,7 @@ ${rationale}`}`);
|
|
|
4995
5016
|
}
|
|
4996
5017
|
async readMemoryCore(agent) {
|
|
4997
5018
|
const file = this.paths.agentMemoryCore(agent);
|
|
4998
|
-
return await exists(file) ? (await
|
|
5019
|
+
return await exists(file) ? (await readLooseMarkdown(file)).body : "";
|
|
4999
5020
|
}
|
|
5000
5021
|
/**
|
|
5001
5022
|
* Moves a conversation's cursor forward to `cursor` once a turn there has delivered up to it. It
|
|
@@ -5210,7 +5231,8 @@ ${rationale}`}`);
|
|
|
5210
5231
|
if (!await exists(file)) {
|
|
5211
5232
|
return { data: { project: slug }, body: "" };
|
|
5212
5233
|
}
|
|
5213
|
-
|
|
5234
|
+
const { data, body } = await readLooseMarkdown(file);
|
|
5235
|
+
return { data, body };
|
|
5214
5236
|
}
|
|
5215
5237
|
async listRunners() {
|
|
5216
5238
|
const runners2 = [];
|
|
@@ -6497,7 +6519,10 @@ var Scheduler = class {
|
|
|
6497
6519
|
now;
|
|
6498
6520
|
log;
|
|
6499
6521
|
pending = /* @__PURE__ */ new Map();
|
|
6500
|
-
/**
|
|
6522
|
+
/**
|
|
6523
|
+
* Turns in flight by session key. A conversation runs one turn at a time, and different
|
|
6524
|
+
* conversations side by side, since each thread's works in a place of its own.
|
|
6525
|
+
*/
|
|
6501
6526
|
running = /* @__PURE__ */ new Map();
|
|
6502
6527
|
/** Steers under way, by session key; a pass does not wait for a runner to answer one. */
|
|
6503
6528
|
steering = /* @__PURE__ */ new Map();
|
|
@@ -6554,7 +6579,7 @@ var Scheduler = class {
|
|
|
6554
6579
|
}
|
|
6555
6580
|
await this.passing?.catch(() => void 0);
|
|
6556
6581
|
await Promise.allSettled([
|
|
6557
|
-
...
|
|
6582
|
+
...this.running.values(),
|
|
6558
6583
|
...this.completing,
|
|
6559
6584
|
...this.steering.values()
|
|
6560
6585
|
]);
|
|
@@ -6604,7 +6629,7 @@ var Scheduler = class {
|
|
|
6604
6629
|
async drain() {
|
|
6605
6630
|
await Promise.allSettled([
|
|
6606
6631
|
...this.steering.values(),
|
|
6607
|
-
...
|
|
6632
|
+
...this.running.values(),
|
|
6608
6633
|
...this.completing
|
|
6609
6634
|
]);
|
|
6610
6635
|
}
|
|
@@ -7222,7 +7247,7 @@ var Scheduler = class {
|
|
|
7222
7247
|
continue;
|
|
7223
7248
|
}
|
|
7224
7249
|
const key = sessionKey(agent.name, latest.scope);
|
|
7225
|
-
if (this.pending.has(key) || this.
|
|
7250
|
+
if (this.pending.has(key) || this.running.has(key)) {
|
|
7226
7251
|
continue;
|
|
7227
7252
|
}
|
|
7228
7253
|
this.state.lastReflection[agent.name] = iso(now);
|
|
@@ -7520,22 +7545,6 @@ var Scheduler = class {
|
|
|
7520
7545
|
this.state.costSinceReport = 0;
|
|
7521
7546
|
this.state.turnsSinceReport = 0;
|
|
7522
7547
|
}
|
|
7523
|
-
/**
|
|
7524
|
-
* The lane a turn runs in: a task's conversation has its own, with a worktree of its own; a
|
|
7525
|
-
* member's home conversation and its proposal and topic threads in a scope share the scope's
|
|
7526
|
-
* worktree, and so one lane, one turn at a time.
|
|
7527
|
-
*/
|
|
7528
|
-
laneOf(dispatch) {
|
|
7529
|
-
return dispatch.thread?.task === true ? sessionKey(dispatch.agent, dispatch.project, dispatch.thread.id) : sessionKey(dispatch.agent, dispatch.project);
|
|
7530
|
-
}
|
|
7531
|
-
laneBusy(lane) {
|
|
7532
|
-
for (const turn of this.running.values()) {
|
|
7533
|
-
if (turn.lane === lane) {
|
|
7534
|
-
return true;
|
|
7535
|
-
}
|
|
7536
|
-
}
|
|
7537
|
-
return false;
|
|
7538
|
-
}
|
|
7539
7548
|
/**
|
|
7540
7549
|
* Delivers ready message wakes into the turn already running in their conversation, rather than
|
|
7541
7550
|
* keeping them for a turn of their own. Each stays queued until that turn ends and is checked
|
|
@@ -7586,10 +7595,6 @@ var Scheduler = class {
|
|
|
7586
7595
|
if (this.running.size >= this.concurrency) {
|
|
7587
7596
|
break;
|
|
7588
7597
|
}
|
|
7589
|
-
const lane = this.laneOf(item.dispatch);
|
|
7590
|
-
if (this.laneBusy(lane)) {
|
|
7591
|
-
continue;
|
|
7592
|
-
}
|
|
7593
7598
|
this.pending.delete(key);
|
|
7594
7599
|
if (await this.alreadyRead(item)) {
|
|
7595
7600
|
this.log.info({ agent: item.dispatch.agent, project: item.dispatch.project }, "dropping message wake, its conversation has read the message");
|
|
@@ -7630,7 +7635,7 @@ var Scheduler = class {
|
|
|
7630
7635
|
this.running.delete(key);
|
|
7631
7636
|
}
|
|
7632
7637
|
})();
|
|
7633
|
-
this.running.set(key,
|
|
7638
|
+
this.running.set(key, promise);
|
|
7634
7639
|
}
|
|
7635
7640
|
}
|
|
7636
7641
|
/** Starts each queued completion once the turn that finished the task's last stage has ended. */
|
|
@@ -7663,7 +7668,7 @@ import { randomUUID } from "node:crypto";
|
|
|
7663
7668
|
var TURN_CONTRACT = [
|
|
7664
7669
|
"- Every turn belongs to one conversation, named at the top of your prompt: your home conversation in a project or the society, for its channels and for work tied to no thread, or the conversation of one thread you take part in, a task's, a proposal's, or a topic's. Each has its own session and its own turns, which may run while this one does. The prompt carries this conversation only: in a thread, its new messages, or the whole thread when the conversation is new, and in a task's thread the task and its stage; at home, the channel messages that concern you. Read anything else you need with get_task, search, and the board's files, and leave other threads' and other projects' work to their own turns.",
|
|
7665
7670
|
"- Act through the board tools (the `board` MCP server). Holding a stage is a lease; every turn that touches the task renews it.",
|
|
7666
|
-
"- 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.",
|
|
7671
|
+
"- 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. In a proposal's or a topic's conversation it is a worktree of its own, detached at your own branch's tip, which goes when the thread closes, so work that should last goes on a task's branch.",
|
|
7667
7672
|
"- 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.",
|
|
7668
7673
|
"- Silence is allowed. If the digest needs no reply, post nothing.",
|
|
7669
7674
|
"- 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.",
|
|
@@ -7692,7 +7697,7 @@ var GOVERNANCE = [
|
|
|
7692
7697
|
' - archive: {"project", "reason"}: approval archives the project: its members leave, its open threads close, and nothing more is posted, filed, or joined there, while its files and history stay; refused while a task there is in play',
|
|
7693
7698
|
' - reallocation: {"description"}',
|
|
7694
7699
|
` - skill: {"name", "summary", "body"}: the SKILL.md text; approval publishes it under society/skills, where every citizen's skills index lists it`,
|
|
7695
|
-
"- Projects and membership: a project is a lasting area of work, not one request; `create_project` opens one with its general channel (front desk and user), and `configure_project` sets its completion effect
|
|
7700
|
+
"- Projects and membership: a project is a lasting area of work, not one request; `create_project` opens one with its general channel (front desk and user), and `configure_project` sets its completion effect, its display name, or its default branch, never its slug (user, steward, concierge). A project whose work is finished or has moved to another project is archived by proposal, once no task there is in play. `join_project` and `leave_project` move yourself, or another citizen when you are the concierge, the steward, or the user; joining gives the pair a worktree and an onboarding turn. A society role, one that keeps watch over the whole society as the concierge and the steward do, is never in a project: it moves others, never itself.",
|
|
7696
7701
|
"- Prefer scaling an existing role over inventing one; a new role is justified by work a project needs that no existing role covers. A role that needs a tool the board lacks is an engineering task, not a hiring request.",
|
|
7697
7702
|
"- Operations signals are counters and timers the board logs, listed in the prompt of a role that reads them: stages waiting for a holder, backlog per role, stages waiting on a role nobody fills, churn, stale threads, idle members, missing capabilities, replicas added, spend. They are not posts in any channel; interpreting them is your judgment."
|
|
7698
7703
|
].join("\n");
|
|
@@ -7728,7 +7733,7 @@ function renderInstructions(input) {
|
|
|
7728
7733
|
if (norms.length > 0) {
|
|
7729
7734
|
sections.push("", "## Society norms", "", norms);
|
|
7730
7735
|
}
|
|
7731
|
-
sections.push("", "## Core memory", "", memory.length === 0 ? "(empty)" : memory, "", "## Skills", "", ...renderSkillsIndex(input.skills ?? []), "", "## Where things are", "", `- Your home directory: ${input.homeDir}. memory/core.md is loaded every turn; memory/<topic>.md is your archive; skills/<name>/SKILL.md are your skills; projects/<slug>/notes.md are your notes. You may read and write all of it, and the board's search covers your archive and skills for you alone. The runner keeps it in git and shares it with the board after every turn, so whatever machine your next turn runs on has it; leave git there to the runner. Files over ${HOME_FILE_LIMIT_BYTES / (1024 * 1024)} MB are not kept.`, `- ${HOME_SCRATCH}/ in your home is your working folder in turns outside any project: it is
|
|
7736
|
+
sections.push("", "## Core memory", "", memory.length === 0 ? "(empty)" : memory, "", "## Skills", "", ...renderSkillsIndex(input.skills ?? []), "", "## Where things are", "", `- Your home directory: ${input.homeDir}. memory/core.md is loaded every turn; memory/<topic>.md is your archive; skills/<name>/SKILL.md are your skills; projects/<slug>/notes.md are your notes. You may read and write all of it, and the board's search covers your archive and skills for you alone. The runner keeps it in git and shares it with the board after every turn, so whatever machine your next turn runs on has it; leave git there to the runner. Files over ${HOME_FILE_LIMIT_BYTES / (1024 * 1024)} MB are not kept.`, `- ${HOME_SCRATCH}/ in your home is your working folder in turns outside any project, and a thread's conversation there works in a folder of its own inside it, which goes when the thread closes: none of it is kept or shared, so put downloads, environments, and other working files there, and keep the rest of your home for what you mean to remember.`, `- The board projection: ${input.boardDir} (read-only markdown: society and project channels, tasks, threads; shared knowledge under projects/<slug>/knowledge/ and society/knowledge/; the society's skills under society/skills/). Search it with your file tools; act through the board tools.`, "", "## Turn contract", "", TURN_CONTRACT, "", "## Planning", "", PLANNING, "", "## Governance", "", GOVERNANCE);
|
|
7732
7737
|
if (input.onboarding !== void 0) {
|
|
7733
7738
|
sections.push("", "## First turn", "", renderOnboardingPreamble(input.onboarding));
|
|
7734
7739
|
}
|
|
@@ -8060,7 +8065,7 @@ var TurnHost = class {
|
|
|
8060
8065
|
const societyScope = dispatch.project === SOCIETY_SCOPE;
|
|
8061
8066
|
const taskId = thread?.task === true ? thread.id : void 0;
|
|
8062
8067
|
const project = societyScope ? null : await this.board.readProject(dispatch.project);
|
|
8063
|
-
const workspace = await this.workspaceOf(project,
|
|
8068
|
+
const workspace = await this.workspaceOf(project, thread);
|
|
8064
8069
|
const sessions = await this.board.readSessions(agent.name, dispatch.project, thread?.id);
|
|
8065
8070
|
const recorded = sessions.runner === runner.name ? sessions[agent.cli] : void 0;
|
|
8066
8071
|
const newSession = recorded === void 0;
|
|
@@ -8172,7 +8177,8 @@ var TurnHost = class {
|
|
|
8172
8177
|
void this.board.heldClaims(agent.name).then((claims) => this.renewLeases(actor, claims.filter((each) => each.project === dispatch.project).map((each) => each.id))).catch(() => void 0);
|
|
8173
8178
|
}, LEASE_RENEW_MS);
|
|
8174
8179
|
keepLeases.unref?.();
|
|
8175
|
-
const { promise: ended, resolve: done } = Promise.withResolvers();
|
|
8180
|
+
const { promise: ended, resolve: done, reject: fail } = Promise.withResolvers();
|
|
8181
|
+
ended.catch(() => void 0);
|
|
8176
8182
|
this.open.set(record.id, {
|
|
8177
8183
|
job,
|
|
8178
8184
|
runner: runner.name,
|
|
@@ -8183,14 +8189,14 @@ var TurnHost = class {
|
|
|
8183
8189
|
outstanding: null,
|
|
8184
8190
|
stopRequestedBy: null,
|
|
8185
8191
|
held: held.map((each) => each.id),
|
|
8186
|
-
taskId,
|
|
8187
8192
|
costSoFarUsd,
|
|
8188
8193
|
configuredModel: agent.model ?? null,
|
|
8189
8194
|
transcript: [],
|
|
8190
8195
|
coldToken: residentKey === null ? token : null,
|
|
8191
8196
|
residentKey: residentKey === null ? null : `${runner.name}|${residentKey}`,
|
|
8192
8197
|
keepLeases,
|
|
8193
|
-
done
|
|
8198
|
+
done,
|
|
8199
|
+
fail
|
|
8194
8200
|
});
|
|
8195
8201
|
this.log.info({
|
|
8196
8202
|
agent: agent.name,
|
|
@@ -8329,7 +8335,7 @@ var TurnHost = class {
|
|
|
8329
8335
|
}
|
|
8330
8336
|
/**
|
|
8331
8337
|
* Records a turn's end from its outcome. Returns null for a turn the host does not know, else
|
|
8332
|
-
* whether the task the turn worked
|
|
8338
|
+
* whether the task or thread the turn worked in has ended, so the runner may drop its workspace.
|
|
8333
8339
|
*/
|
|
8334
8340
|
async closeTurn(turnId, outcome) {
|
|
8335
8341
|
const turn = this.open.get(turnId);
|
|
@@ -8342,9 +8348,6 @@ var TurnHost = class {
|
|
|
8342
8348
|
this.board.revokeTurnToken(turn.coldToken);
|
|
8343
8349
|
}
|
|
8344
8350
|
const { job } = turn;
|
|
8345
|
-
if (outcome.session !== job.session) {
|
|
8346
|
-
await this.board.writeSession(job.agent, job.scope, job.cli, outcome.session, turn.runner, job.thread);
|
|
8347
|
-
}
|
|
8348
8351
|
const finished = {
|
|
8349
8352
|
...turn.record,
|
|
8350
8353
|
session: outcome.session,
|
|
@@ -8360,17 +8363,29 @@ var TurnHost = class {
|
|
|
8360
8363
|
...outcome.work === null ? {} : { work: outcome.work },
|
|
8361
8364
|
...outcome.exitReason === "stopped" ? { stoppedBy: turn.stopRequestedBy ?? USER_NAME } : {}
|
|
8362
8365
|
};
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8366
|
+
const recorded = await this.recorded(turn, async () => {
|
|
8367
|
+
if (outcome.session !== job.session) {
|
|
8368
|
+
await this.board.writeSession(job.agent, job.scope, job.cli, outcome.session, turn.runner, job.thread);
|
|
8369
|
+
}
|
|
8370
|
+
if (outcome.exitReason === "completed" || outcome.exitReason === "blocked" || outcome.exitReason === "stopped") {
|
|
8371
|
+
await this.board.setDigestCursor(job.agent, job.scope, turn.delivered, job.thread);
|
|
8372
|
+
await this.renewLeases(turn.actor, turn.held);
|
|
8373
|
+
if (outcome.status?.needsUserDecision === true) {
|
|
8374
|
+
await this.askUser(turn.actor, job.scope, job.thread, turn.record.startedAt, outcome.status.summary);
|
|
8375
|
+
}
|
|
8368
8376
|
}
|
|
8377
|
+
await this.board.finishTurn(finished, turn.transcript);
|
|
8378
|
+
});
|
|
8379
|
+
if (recorded) {
|
|
8380
|
+
this.log.info({
|
|
8381
|
+
agent: job.agent,
|
|
8382
|
+
project: job.scope,
|
|
8383
|
+
thread: job.thread,
|
|
8384
|
+
exitReason: finished.exitReason
|
|
8385
|
+
}, "turn finished");
|
|
8386
|
+
turn.done(finished);
|
|
8369
8387
|
}
|
|
8370
|
-
await this.
|
|
8371
|
-
this.log.info({ agent: job.agent, project: job.scope, thread: job.thread, exitReason: finished.exitReason }, "turn finished");
|
|
8372
|
-
turn.done(finished);
|
|
8373
|
-
return { dropWorktree: await this.taskEnded(turn.taskId) };
|
|
8388
|
+
return { dropWorkspace: await this.conversationEnded(job) };
|
|
8374
8389
|
}
|
|
8375
8390
|
/** Ends a turn whose outcome will never come, as when its runner went away, as failed. */
|
|
8376
8391
|
async abortTurn(turnId, reason) {
|
|
@@ -8390,9 +8405,26 @@ var TurnHost = class {
|
|
|
8390
8405
|
error: reason,
|
|
8391
8406
|
toolCalls: turn.transcript.filter((entry) => entry.event.type === "tool_call").length
|
|
8392
8407
|
};
|
|
8393
|
-
await this.board.finishTurn(finished, turn.transcript)
|
|
8394
|
-
|
|
8395
|
-
|
|
8408
|
+
if (await this.recorded(turn, () => this.board.finishTurn(finished, turn.transcript))) {
|
|
8409
|
+
this.log.warn({ agent: turn.job.agent, project: turn.job.scope, reason }, "turn aborted");
|
|
8410
|
+
turn.done(finished);
|
|
8411
|
+
}
|
|
8412
|
+
}
|
|
8413
|
+
/**
|
|
8414
|
+
* Records the end of a turn already taken out of `open`, and says whether it could. The scheduler
|
|
8415
|
+
* holds the turn's conversation until the turn ends, and nothing would end it once it has left
|
|
8416
|
+
* `open`, so a turn whose end could not be recorded ends with the error rather than holding its
|
|
8417
|
+
* conversation for good.
|
|
8418
|
+
*/
|
|
8419
|
+
async recorded(turn, record) {
|
|
8420
|
+
try {
|
|
8421
|
+
await record();
|
|
8422
|
+
return true;
|
|
8423
|
+
} catch (error) {
|
|
8424
|
+
this.log.error({ agent: turn.job.agent, project: turn.job.scope, error: String(error) }, "could not record the end of a turn");
|
|
8425
|
+
turn.fail(error);
|
|
8426
|
+
return false;
|
|
8427
|
+
}
|
|
8396
8428
|
}
|
|
8397
8429
|
/** Records a turn that could never start, as when no runner could take its agent's CLI. */
|
|
8398
8430
|
async refuseTurn(dispatch, runner, reason) {
|
|
@@ -8445,29 +8477,34 @@ var TurnHost = class {
|
|
|
8445
8477
|
this.residentTokens.set(slot, token);
|
|
8446
8478
|
return token;
|
|
8447
8479
|
}
|
|
8448
|
-
|
|
8480
|
+
/** Where a conversation works: a task's on its branch, any other thread's in a place of its own. */
|
|
8481
|
+
async workspaceOf(project, thread) {
|
|
8482
|
+
const own = thread === void 0 || thread.task ? {} : { thread: thread.id };
|
|
8449
8483
|
if (project === null) {
|
|
8450
|
-
return { kind: "home" };
|
|
8484
|
+
return { kind: "home", ...own };
|
|
8451
8485
|
}
|
|
8452
8486
|
const repo = {
|
|
8453
8487
|
slug: project.slug,
|
|
8454
8488
|
origin: project.repo,
|
|
8455
8489
|
defaultBranch: project.defaultBranch
|
|
8456
8490
|
};
|
|
8457
|
-
if (
|
|
8458
|
-
return { kind: "task", repo, taskId };
|
|
8491
|
+
if (thread?.task === true) {
|
|
8492
|
+
return { kind: "task", repo, taskId: thread.id };
|
|
8459
8493
|
}
|
|
8460
8494
|
const branches = (await this.board.listTasks(project.slug)).filter((task) => task.status === "open" || task.status === "claimed").map((task) => `task/${task.id}`);
|
|
8461
|
-
return { kind: "project", repo, branches };
|
|
8495
|
+
return { kind: "project", repo, branches, ...own };
|
|
8462
8496
|
}
|
|
8463
|
-
/** Whether a
|
|
8464
|
-
async
|
|
8465
|
-
if (
|
|
8497
|
+
/** Whether a thread conversation's task or thread has ended, which frees its workspace. */
|
|
8498
|
+
async conversationEnded(job) {
|
|
8499
|
+
if (job.thread === void 0) {
|
|
8466
8500
|
return false;
|
|
8467
8501
|
}
|
|
8468
8502
|
try {
|
|
8469
|
-
|
|
8470
|
-
|
|
8503
|
+
if (job.workspace.kind === "task") {
|
|
8504
|
+
const { task } = await this.board.findTask(job.thread);
|
|
8505
|
+
return task.status === "done" || task.status === "abandoned";
|
|
8506
|
+
}
|
|
8507
|
+
return (await this.board.readThread(job.thread)).state === "closed";
|
|
8471
8508
|
} catch {
|
|
8472
8509
|
return false;
|
|
8473
8510
|
}
|
|
@@ -8670,7 +8707,7 @@ var RunnerHub = class {
|
|
|
8670
8707
|
async turnOutcome(runner, turnId, outcome) {
|
|
8671
8708
|
this.assertOwns(runner, turnId);
|
|
8672
8709
|
const ack = await this.host.closeTurn(turnId, outcome);
|
|
8673
|
-
return ack ?? {
|
|
8710
|
+
return ack ?? { dropWorkspace: false };
|
|
8674
8711
|
}
|
|
8675
8712
|
answer(runner, request, answer) {
|
|
8676
8713
|
const pending = this.pending.get(request);
|