@kubitnodes/stellaris 0.3.23 → 0.3.25
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 +543 -682
- package/dist/server/index.js +866 -735
- package/package.json +4 -4
- package/web/dist/assets/BackLink-UTiLF1J4.js +1 -0
- package/web/dist/assets/{Button-f4jgBZY1.js → Button-Cp4cNRUX.js} +1 -1
- package/web/dist/assets/ChannelForms-DD2B0tey.js +1 -0
- package/web/dist/assets/ChannelView-CY3oG9hZ.js +2 -0
- package/web/dist/assets/CitizenView-BNK5ro-6.js +2 -0
- package/web/dist/assets/CitizensView-WPQ6Jhl3.js +1 -0
- package/web/dist/assets/{CliIcon-CLHaA65A.js → CliIcon-C7bwChMP.js} +1 -1
- package/web/dist/assets/CronsView-_pdgwX1I.js +1 -0
- package/web/dist/assets/FileTree-jyDUSu7g.js +1 -0
- package/web/dist/assets/FileView-DcQvlc8V.js +4 -0
- package/web/dist/assets/KnowledgeView-DDl0HQPG.js +1 -0
- package/web/dist/assets/Markdown-DT3V-EVe.js +30 -0
- package/web/dist/assets/{MessageItem-Bk9eBp1U.js → MessageItem-agtEugfG.js} +1 -1
- package/web/dist/assets/MetricsView-DiXyYeWP.js +1 -0
- package/web/dist/assets/NeedsYouView-CKmuYan_.js +1 -0
- package/web/dist/assets/Overview-CKcTqsMM.js +1 -0
- package/web/dist/assets/{Pane-CJYRVkwh.js → Pane-BaM0TdE6.js} +1 -1
- package/web/dist/assets/{Picker-DoxCN6C7.js → Picker-C2ig-0dT.js} +1 -1
- package/web/dist/assets/ProjectView-T2OHTh5j.js +1 -0
- package/web/dist/assets/ProposalView-B5yjTAcn.js +1 -0
- package/web/dist/assets/ProposalsView-p4KjwV9p.js +1 -0
- package/web/dist/assets/Quota-CfFGIql9.js +1 -0
- package/web/dist/assets/RunnersView-DdvwoOur.js +1 -0
- package/web/dist/assets/SkillFileList-CgrCnuaC.js +2 -0
- package/web/dist/assets/SkillView-Di5Z9h2L.js +1 -0
- package/web/dist/assets/SocietyView-BDiVxUC5.js +1 -0
- package/web/dist/assets/StickyList-BaV_KZ8x.js +1 -0
- package/web/dist/assets/SubjectThread-DD_ZCCnr.js +1 -0
- package/web/dist/assets/TaskView-CKcs-MOC.js +3 -0
- package/web/dist/assets/TasksView-oZMjncOU.js +1 -0
- package/web/dist/assets/{ThreadForms-C846TvtS.js → ThreadForms-CG5Y926b.js} +1 -1
- package/web/dist/assets/ThreadItem-D-n2arFA.js +1 -0
- package/web/dist/assets/ThreadView-CT9vd0P4.js +1 -0
- package/web/dist/assets/citizen-BiHfph2A.js +1 -0
- package/web/dist/assets/{compose-BmqQmcZH.js → compose-DY1WL8zB.js} +1 -1
- package/web/dist/assets/format-CENiwkJC.js +55 -0
- package/web/dist/assets/governance-C6ez5MLh.js +1 -0
- package/web/dist/assets/index-CPXALUV6.js +23 -0
- package/web/dist/assets/index-DIa8Dlav.css +2 -0
- package/web/dist/assets/mentions-BEL5U6ra.js +3 -0
- package/web/dist/assets/{tasks-R3YgpYTe.js → tasks-CAI8Eci7.js} +1 -1
- package/web/dist/assets/useMatch-CINzyDWa.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-DGYs80bb.js +0 -1
- package/web/dist/assets/ChannelForms-q9zdj4p2.js +0 -1
- package/web/dist/assets/ChannelView-CGs_aWL3.js +0 -2
- package/web/dist/assets/CitizenView-gcIo_sKt.js +0 -2
- package/web/dist/assets/CitizensView-DZ7S8vnJ.js +0 -1
- package/web/dist/assets/CronsView-1wns6Uwv.js +0 -1
- package/web/dist/assets/KnowledgeView-Cxfi2-Pt.js +0 -1
- package/web/dist/assets/Markdown-BhtIFcWc.js +0 -30
- package/web/dist/assets/MetricsView-CTEH6rJn.js +0 -1
- package/web/dist/assets/NeedsYouView-2VNqH2Sb.js +0 -1
- package/web/dist/assets/Overview-CS9HCSmm.js +0 -1
- package/web/dist/assets/ProjectView-CJ3r4UdN.js +0 -1
- package/web/dist/assets/ProposalView-DLn9dNPp.js +0 -2
- package/web/dist/assets/ProposalsView-B-cOxTsQ.js +0 -1
- package/web/dist/assets/Quota-Bo7UdWTV.js +0 -1
- package/web/dist/assets/RunnersView-DlQruhwp.js +0 -1
- package/web/dist/assets/SocietyView-Bl4ErnV8.js +0 -1
- package/web/dist/assets/StickyList-MVP34ytV.js +0 -1
- package/web/dist/assets/SubjectThread-BFFnHxIF.js +0 -1
- package/web/dist/assets/TaskFileView-GZ0eQFUb.js +0 -4
- package/web/dist/assets/TaskView-DppFelLW.js +0 -3
- package/web/dist/assets/TasksView-DDNXzeG4.js +0 -1
- package/web/dist/assets/ThreadItem-C8cou6wD.js +0 -1
- package/web/dist/assets/ThreadView-DvSGDYoD.js +0 -1
- package/web/dist/assets/citizen-B2Dlbcpe.js +0 -1
- package/web/dist/assets/format-DT52SbbR.js +0 -55
- package/web/dist/assets/governance-Cr5OlQnw.js +0 -1
- package/web/dist/assets/index-BTVy_xFF.js +0 -23
- package/web/dist/assets/index-DzV6CCnG.css +0 -2
- package/web/dist/assets/mentions-D5hNgtg6.js +0 -3
- package/web/dist/assets/useMatch-pEF6RJ6Q.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
// apps/cli/dist/index.js
|
|
4
4
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import path8 from "node:path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
|
|
8
8
|
// packages/board-core/dist/board.js
|
|
9
|
-
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as
|
|
10
|
-
import
|
|
9
|
+
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir4, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
10
|
+
import path7 from "node:path";
|
|
11
11
|
import { decodeTime, monotonicFactory } from "ulid";
|
|
12
12
|
import { z as z16 } from "zod";
|
|
13
13
|
|
|
@@ -234,6 +234,7 @@ var CronFrontmatterSchema = z3.object({
|
|
|
234
234
|
scope: NameSchema,
|
|
235
235
|
thread: UlidSchema.optional(),
|
|
236
236
|
channel: NameSchema.optional(),
|
|
237
|
+
place: NameSchema.optional(),
|
|
237
238
|
schedule: CronScheduleSchema,
|
|
238
239
|
createdBy: NameSchema,
|
|
239
240
|
createdAt: IsoDateTimeSchema,
|
|
@@ -714,7 +715,6 @@ var ReallocationProposalSchema = z5.object({
|
|
|
714
715
|
changes: RoleChangesSchema,
|
|
715
716
|
description: z5.string().min(1)
|
|
716
717
|
});
|
|
717
|
-
var LegacyReallocationSchema = z5.object({ description: z5.string().min(1) });
|
|
718
718
|
function applyRoleChanges(role2, changes) {
|
|
719
719
|
return {
|
|
720
720
|
...role2,
|
|
@@ -759,8 +759,7 @@ var ArchiveProposalSchema = z5.object({
|
|
|
759
759
|
});
|
|
760
760
|
var SkillProposalSchema = z5.object({
|
|
761
761
|
name: NameSchema,
|
|
762
|
-
summary: z5.string().min(1).max(200)
|
|
763
|
-
body: z5.string().min(1).optional()
|
|
762
|
+
summary: z5.string().min(1).max(200)
|
|
764
763
|
});
|
|
765
764
|
var ProposalCharterSchemas = {
|
|
766
765
|
role: RoleCharterSchema,
|
|
@@ -837,14 +836,10 @@ function describeCharter(kind, charter) {
|
|
|
837
836
|
}
|
|
838
837
|
case "reallocation": {
|
|
839
838
|
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
840
|
-
if (parsed.success)
|
|
841
|
-
const { role: role2, changes, description } = parsed.data;
|
|
842
|
-
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
843
|
-
}
|
|
844
|
-
const legacy = LegacyReallocationSchema.safeParse(charter);
|
|
845
|
-
if (!legacy.success)
|
|
839
|
+
if (!parsed.success)
|
|
846
840
|
break;
|
|
847
|
-
|
|
841
|
+
const { role: role2, changes, description } = parsed.data;
|
|
842
|
+
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
848
843
|
}
|
|
849
844
|
case "skill": {
|
|
850
845
|
const parsed = SkillProposalSchema.safeParse(charter);
|
|
@@ -939,7 +934,8 @@ var DashboardSchema = z5.object({
|
|
|
939
934
|
var ProposalOriginSchema = z5.object({
|
|
940
935
|
scope: NameSchema,
|
|
941
936
|
thread: UlidSchema.optional(),
|
|
942
|
-
channel: NameSchema.optional()
|
|
937
|
+
channel: NameSchema.optional(),
|
|
938
|
+
place: NameSchema.optional()
|
|
943
939
|
});
|
|
944
940
|
var SKILL_FILES_LIMIT = 100;
|
|
945
941
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
@@ -1048,12 +1044,10 @@ var VerbInputs = {
|
|
|
1048
1044
|
channel: ChannelRefSchema.optional(),
|
|
1049
1045
|
limit: z7.number().int().positive().max(100).default(20)
|
|
1050
1046
|
}),
|
|
1051
|
-
/** On a
|
|
1047
|
+
/** On a channel with a title; a task's and a proposal's open with them. */
|
|
1052
1048
|
open_thread: z7.object({
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
channel: ChannelRefSchema.optional(),
|
|
1056
|
-
title: z7.string().min(1).max(200).optional()
|
|
1049
|
+
channel: ChannelRefSchema,
|
|
1050
|
+
title: z7.string().min(1).max(200)
|
|
1057
1051
|
}),
|
|
1058
1052
|
close_thread: z7.object({ thread_id: UlidSchema, summary: z7.string().min(1) }),
|
|
1059
1053
|
create_task: z7.object({
|
|
@@ -1293,6 +1287,7 @@ var TurnExitReasonSchema = z8.enum([
|
|
|
1293
1287
|
"blocked",
|
|
1294
1288
|
"stopped"
|
|
1295
1289
|
]);
|
|
1290
|
+
var ResumeFailureSchema = z8.enum(["lost", "retry"]);
|
|
1296
1291
|
var AgentEventSchema = z8.discriminatedUnion("type", [
|
|
1297
1292
|
z8.object({
|
|
1298
1293
|
type: z8.literal("turn_started"),
|
|
@@ -1344,6 +1339,8 @@ var LiveTurnEventSchema = z8.object({
|
|
|
1344
1339
|
thread: UlidSchema.optional(),
|
|
1345
1340
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1346
1341
|
channel: NameSchema.optional(),
|
|
1342
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1343
|
+
place: NameSchema.optional(),
|
|
1347
1344
|
event: AgentEventSchema
|
|
1348
1345
|
});
|
|
1349
1346
|
|
|
@@ -1430,7 +1427,8 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1430
1427
|
z9.object({
|
|
1431
1428
|
kind: z9.literal("home"),
|
|
1432
1429
|
thread: UlidSchema.optional(),
|
|
1433
|
-
channel: NameSchema.optional()
|
|
1430
|
+
channel: NameSchema.optional(),
|
|
1431
|
+
place: NameSchema.optional()
|
|
1434
1432
|
}),
|
|
1435
1433
|
z9.object({
|
|
1436
1434
|
kind: z9.literal("project"),
|
|
@@ -1439,7 +1437,16 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1439
1437
|
thread: UlidSchema.optional(),
|
|
1440
1438
|
channel: NameSchema.optional()
|
|
1441
1439
|
}),
|
|
1442
|
-
z9.object({
|
|
1440
|
+
z9.object({
|
|
1441
|
+
kind: z9.literal("task"),
|
|
1442
|
+
repo: ProjectRepoSchema,
|
|
1443
|
+
taskId: UlidSchema,
|
|
1444
|
+
/**
|
|
1445
|
+
* The citizen holding the task's current stage, when someone does: a turn of anyone else
|
|
1446
|
+
* works detached at the branch's tip, so the holder's turns find the branch free.
|
|
1447
|
+
*/
|
|
1448
|
+
holder: NameSchema.optional()
|
|
1449
|
+
})
|
|
1443
1450
|
]);
|
|
1444
1451
|
var TurnJobSchema = z9.object({
|
|
1445
1452
|
turnId: UlidSchema,
|
|
@@ -1455,6 +1462,8 @@ var TurnJobSchema = z9.object({
|
|
|
1455
1462
|
thread: UlidSchema.optional(),
|
|
1456
1463
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1457
1464
|
channel: NameSchema.optional(),
|
|
1465
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1466
|
+
place: NameSchema.optional(),
|
|
1458
1467
|
/**
|
|
1459
1468
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1460
1469
|
* the outcome reports it.
|
|
@@ -1475,6 +1484,12 @@ var TurnJobSchema = z9.object({
|
|
|
1475
1484
|
workspace: TurnWorkspaceSchema
|
|
1476
1485
|
});
|
|
1477
1486
|
var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
|
|
1487
|
+
var StuckWorkSchema = z9.object({
|
|
1488
|
+
branch: z9.string().min(1),
|
|
1489
|
+
agent: NameSchema,
|
|
1490
|
+
commits: z9.array(z9.string()),
|
|
1491
|
+
total: z9.number().int().positive()
|
|
1492
|
+
});
|
|
1478
1493
|
var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
|
|
1479
1494
|
var TurnOutcomeSchema = z9.object({
|
|
1480
1495
|
exitReason: TurnExitReasonSchema,
|
|
@@ -1484,8 +1499,24 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1484
1499
|
costUsd: z9.number().nonnegative(),
|
|
1485
1500
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1486
1501
|
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1487
|
-
/**
|
|
1488
|
-
|
|
1502
|
+
/**
|
|
1503
|
+
* The session the CLI used, which differs from the job's for CLIs that assign their own ids. A
|
|
1504
|
+
* turn whose CLI never started, because it failed or was stopped first, reports the job's own,
|
|
1505
|
+
* null in a conversation's first turn, so a session never started is not recorded.
|
|
1506
|
+
*/
|
|
1507
|
+
session: z9.string().min(1).nullable(),
|
|
1508
|
+
/**
|
|
1509
|
+
* Set when the CLI could not resume the job's session, which failed the turn before any model
|
|
1510
|
+
* call; the server then drops a `lost` session, and one that failed `retry` too often in a row. A
|
|
1511
|
+
* runner from before never sends it.
|
|
1512
|
+
*/
|
|
1513
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1514
|
+
/**
|
|
1515
|
+
* Set when the model service turned the turn away for now, as when it was overloaded or failed
|
|
1516
|
+
* on its side, which the scheduler answers by sending the same wake again within minutes. A
|
|
1517
|
+
* runner from before never sends it.
|
|
1518
|
+
*/
|
|
1519
|
+
transient: z9.boolean().optional(),
|
|
1489
1520
|
/** The model the CLI reported, when it reported one. */
|
|
1490
1521
|
model: z9.string().nullable(),
|
|
1491
1522
|
work: TurnWorkSchema.nullable(),
|
|
@@ -1494,6 +1525,11 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1494
1525
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1495
1526
|
*/
|
|
1496
1527
|
leftovers: z9.boolean().default(false),
|
|
1528
|
+
/**
|
|
1529
|
+
* Commits the hand-back found its task's worktrees holding that no fast-forward of the task's
|
|
1530
|
+
* branch can take, each told once; a runner from before never sends it.
|
|
1531
|
+
*/
|
|
1532
|
+
stuck: z9.array(StuckWorkSchema).optional(),
|
|
1497
1533
|
/**
|
|
1498
1534
|
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1499
1535
|
* before never sends it.
|
|
@@ -1612,7 +1648,8 @@ var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
|
1612
1648
|
]);
|
|
1613
1649
|
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1614
1650
|
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1615
|
-
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema })
|
|
1651
|
+
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
1652
|
+
z9.object({ kind: z9.literal("place"), project: NameSchema })
|
|
1616
1653
|
]);
|
|
1617
1654
|
var HeldWorkspaceSchema = z9.object({
|
|
1618
1655
|
agent: NameSchema,
|
|
@@ -1718,6 +1755,7 @@ var TurnDispatchSchema = z10.object({
|
|
|
1718
1755
|
project: NameSchema,
|
|
1719
1756
|
thread: TurnThreadSchema.optional(),
|
|
1720
1757
|
channel: NameSchema.optional(),
|
|
1758
|
+
place: NameSchema.optional(),
|
|
1721
1759
|
trigger: TriggerSchema,
|
|
1722
1760
|
priority: z10.number().int().min(0).max(2),
|
|
1723
1761
|
onboarding: z10.boolean().default(false),
|
|
@@ -1725,7 +1763,13 @@ var TurnDispatchSchema = z10.object({
|
|
|
1725
1763
|
crons: z10.array(UlidSchema).optional()
|
|
1726
1764
|
});
|
|
1727
1765
|
function conversationPart(conversation) {
|
|
1728
|
-
|
|
1766
|
+
if (conversation.thread !== void 0) {
|
|
1767
|
+
return conversation.thread;
|
|
1768
|
+
}
|
|
1769
|
+
if (conversation.channel !== void 0) {
|
|
1770
|
+
return `#${conversation.channel}`;
|
|
1771
|
+
}
|
|
1772
|
+
return conversation.place === void 0 ? void 0 : `~${conversation.place}`;
|
|
1729
1773
|
}
|
|
1730
1774
|
function channelConversation(scope, channel) {
|
|
1731
1775
|
const at = channel.indexOf("/");
|
|
@@ -1733,6 +1777,10 @@ function channelConversation(scope, channel) {
|
|
|
1733
1777
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1734
1778
|
return place === scope && name !== "general" ? name : void 0;
|
|
1735
1779
|
}
|
|
1780
|
+
function placeConversation(scope, channel) {
|
|
1781
|
+
const at = channel.indexOf("/");
|
|
1782
|
+
return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
|
|
1783
|
+
}
|
|
1736
1784
|
var TurnRecordSchema = z10.object({
|
|
1737
1785
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1738
1786
|
id: UlidSchema.optional(),
|
|
@@ -1742,6 +1790,8 @@ var TurnRecordSchema = z10.object({
|
|
|
1742
1790
|
thread: UlidSchema.optional(),
|
|
1743
1791
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1744
1792
|
channel: NameSchema.optional(),
|
|
1793
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1794
|
+
place: NameSchema.optional(),
|
|
1745
1795
|
runner: NameSchema,
|
|
1746
1796
|
cli: CliKindSchema.nullable(),
|
|
1747
1797
|
session: z10.string().nullable(),
|
|
@@ -1765,6 +1815,14 @@ var TurnRecordSchema = z10.object({
|
|
|
1765
1815
|
work: TurnWorkSchema.optional(),
|
|
1766
1816
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1767
1817
|
stoppedBy: NameSchema.optional(),
|
|
1818
|
+
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
1819
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1820
|
+
/** Whether the model service turned the turn away for now: overloaded, or failing on its side. */
|
|
1821
|
+
transient: z10.boolean().optional(),
|
|
1822
|
+
/** How many turns in a row, this one included, could not resume the session for a `retry`. */
|
|
1823
|
+
resumeRetries: z10.number().int().positive().optional(),
|
|
1824
|
+
/** Whether the server dropped the conversation's session after this turn, which the next starts anew. */
|
|
1825
|
+
sessionDropped: z10.boolean().optional(),
|
|
1768
1826
|
/** The crons whose fire started the turn, which record how it ended. */
|
|
1769
1827
|
crons: z10.array(UlidSchema).optional()
|
|
1770
1828
|
});
|
|
@@ -1774,6 +1832,7 @@ var RunningTurnSchema = z10.object({
|
|
|
1774
1832
|
scope: NameSchema,
|
|
1775
1833
|
thread: UlidSchema.optional(),
|
|
1776
1834
|
channel: NameSchema.optional(),
|
|
1835
|
+
place: NameSchema.optional(),
|
|
1777
1836
|
cli: CliKindSchema,
|
|
1778
1837
|
askedIn: ChannelRefSchema.optional(),
|
|
1779
1838
|
steerable: z10.boolean(),
|
|
@@ -1804,6 +1863,8 @@ var TurnHistoryEntrySchema = z10.object({
|
|
|
1804
1863
|
thread: UlidSchema.optional(),
|
|
1805
1864
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1806
1865
|
channel: NameSchema.optional(),
|
|
1866
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1867
|
+
place: NameSchema.optional(),
|
|
1807
1868
|
trigger: z10.string(),
|
|
1808
1869
|
exitReason: z10.string().nullable(),
|
|
1809
1870
|
costUsd: z10.number(),
|
|
@@ -1832,9 +1893,19 @@ var OPS_SIGNAL_KINDS = [
|
|
|
1832
1893
|
"runner",
|
|
1833
1894
|
"home_conflict",
|
|
1834
1895
|
"stuck_workspace",
|
|
1835
|
-
"cron_failing"
|
|
1896
|
+
"cron_failing",
|
|
1897
|
+
"turn_refused"
|
|
1836
1898
|
];
|
|
1837
1899
|
var OpsSignalKindSchema = z11.enum(OPS_SIGNAL_KINDS);
|
|
1900
|
+
var WAKING_SIGNAL_KINDS = [
|
|
1901
|
+
"backlog",
|
|
1902
|
+
"role_gap",
|
|
1903
|
+
"churn",
|
|
1904
|
+
"stale_thread",
|
|
1905
|
+
"idle_member",
|
|
1906
|
+
"blocked_capability",
|
|
1907
|
+
"scaled"
|
|
1908
|
+
];
|
|
1838
1909
|
var OpsSignalSchema = z11.object({
|
|
1839
1910
|
kind: OpsSignalKindSchema,
|
|
1840
1911
|
/** Identifies the condition, for example `backlog:demo:engineer`, so a persisting one is not logged again every pass. */
|
|
@@ -1847,8 +1918,15 @@ var OpsSignalSchema = z11.object({
|
|
|
1847
1918
|
project: NameSchema.optional(),
|
|
1848
1919
|
agent: NameSchema.optional(),
|
|
1849
1920
|
role: NameSchema.optional(),
|
|
1850
|
-
taskId: UlidSchema.optional()
|
|
1921
|
+
taskId: UlidSchema.optional(),
|
|
1922
|
+
/**
|
|
1923
|
+
* Whether this log of the condition wakes the roles charted for `ops_event`, as its publisher
|
|
1924
|
+
* decided: the operations pass wakes them when a condition is new, and again only at the steps
|
|
1925
|
+
* it names, not at every repeat. Absent, the kind decides.
|
|
1926
|
+
*/
|
|
1927
|
+
wakes: z11.boolean().optional()
|
|
1851
1928
|
});
|
|
1929
|
+
var WAKING = new Set(WAKING_SIGNAL_KINDS);
|
|
1852
1930
|
|
|
1853
1931
|
// packages/shared/dist/metrics.js
|
|
1854
1932
|
import { z as z12 } from "zod";
|
|
@@ -2006,7 +2084,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2006
2084
|
});
|
|
2007
2085
|
|
|
2008
2086
|
// packages/shared/dist/version.js
|
|
2009
|
-
var STELLARIS_VERSION = "0.3.
|
|
2087
|
+
var STELLARIS_VERSION = "0.3.25";
|
|
2010
2088
|
|
|
2011
2089
|
// packages/shared/dist/mentions.js
|
|
2012
2090
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2387,6 +2465,7 @@ var HOME_GITIGNORE = [
|
|
|
2387
2465
|
"/projects/*/last-turn.json",
|
|
2388
2466
|
"/projects/*/threads/",
|
|
2389
2467
|
"/projects/*/channels/",
|
|
2468
|
+
"/projects/*/places/",
|
|
2390
2469
|
`/${HOME_SCRATCH}/`,
|
|
2391
2470
|
"*.tmp",
|
|
2392
2471
|
"node_modules/",
|
|
@@ -2404,7 +2483,7 @@ var HOME_GITIGNORE = [
|
|
|
2404
2483
|
var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
|
|
2405
2484
|
`;
|
|
2406
2485
|
var PRE_RECEIVE = `#!/bin/sh
|
|
2407
|
-
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/))'
|
|
2486
|
+
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/|places/))'
|
|
2408
2487
|
while read old new ref; do
|
|
2409
2488
|
if [ "$ref" != "refs/heads/main" ]; then
|
|
2410
2489
|
echo "a home takes pushes to main only" >&2
|
|
@@ -2730,6 +2809,7 @@ var TurnPayload = z14.object({
|
|
|
2730
2809
|
project: z14.string(),
|
|
2731
2810
|
thread: z14.string().optional(),
|
|
2732
2811
|
channel: z14.string().optional(),
|
|
2812
|
+
place: z14.string().optional(),
|
|
2733
2813
|
trigger: z14.string()
|
|
2734
2814
|
});
|
|
2735
2815
|
var TaskPayload = z14.object({
|
|
@@ -2775,11 +2855,16 @@ function answers(mention, turn2) {
|
|
|
2775
2855
|
if (!mention.scopes.includes(turn2.project)) {
|
|
2776
2856
|
return false;
|
|
2777
2857
|
}
|
|
2778
|
-
const
|
|
2858
|
+
const topLevel = mention.thread === null;
|
|
2859
|
+
const own = topLevel ? channelConversation(turn2.project, mention.channel) : void 0;
|
|
2860
|
+
const place = topLevel ? placeConversation(turn2.project, mention.channel) : void 0;
|
|
2779
2861
|
if (turn2.channel !== null) {
|
|
2780
|
-
return
|
|
2862
|
+
return topLevel && own === turn2.channel;
|
|
2863
|
+
}
|
|
2864
|
+
if (turn2.place !== null) {
|
|
2865
|
+
return topLevel && place === turn2.place;
|
|
2781
2866
|
}
|
|
2782
|
-
return
|
|
2867
|
+
return topLevel && own === void 0 && place === void 0 || turn2.trigger === "mention";
|
|
2783
2868
|
}
|
|
2784
2869
|
function computeMetrics(input) {
|
|
2785
2870
|
const { events, now, window } = input;
|
|
@@ -2845,6 +2930,7 @@ function computeMetrics(input) {
|
|
|
2845
2930
|
at,
|
|
2846
2931
|
thread: turn2.data.thread ?? null,
|
|
2847
2932
|
channel: turn2.data.channel ?? null,
|
|
2933
|
+
place: turn2.data.place ?? null,
|
|
2848
2934
|
project: turn2.data.project,
|
|
2849
2935
|
trigger: turn2.data.trigger
|
|
2850
2936
|
};
|
|
@@ -3044,6 +3130,7 @@ var TurnHistoryPayloadSchema = z15.object({
|
|
|
3044
3130
|
project: NameSchema,
|
|
3045
3131
|
thread: UlidSchema.optional(),
|
|
3046
3132
|
channel: NameSchema.optional(),
|
|
3133
|
+
place: NameSchema.optional(),
|
|
3047
3134
|
trigger: z15.string().default("manual"),
|
|
3048
3135
|
exitReason: z15.string().nullable().default(null),
|
|
3049
3136
|
costUsd: z15.number().default(0),
|
|
@@ -3282,11 +3369,14 @@ var BoardPaths = class {
|
|
|
3282
3369
|
return path5.join(this.agentProjects(name), slug);
|
|
3283
3370
|
}
|
|
3284
3371
|
/**
|
|
3285
|
-
* A thread's
|
|
3286
|
-
* conversation; `part` is a thread's id or
|
|
3372
|
+
* A thread's, a channel's, or, in the society scope, a project's conversation's session and last
|
|
3373
|
+
* turn, beside the scope's home conversation; `part` is a thread's id, `#channel`, or `~project`.
|
|
3287
3374
|
*/
|
|
3288
3375
|
agentConversation(name, slug, part) {
|
|
3289
|
-
|
|
3376
|
+
if (part.startsWith("#")) {
|
|
3377
|
+
return path5.join(this.agentProject(name, slug), "channels", part.slice(1));
|
|
3378
|
+
}
|
|
3379
|
+
return part.startsWith("~") ? path5.join(this.agentProject(name, slug), "places", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
|
|
3290
3380
|
}
|
|
3291
3381
|
agentCursors(name) {
|
|
3292
3382
|
return path5.join(this.agent(name), "cursors.json");
|
|
@@ -3320,6 +3410,111 @@ var BoardPaths = class {
|
|
|
3320
3410
|
}
|
|
3321
3411
|
};
|
|
3322
3412
|
|
|
3413
|
+
// packages/board-core/dist/skill-folder.js
|
|
3414
|
+
import { constants } from "node:fs";
|
|
3415
|
+
import { lstat, open as open2, readdir as readdir3, readlink, realpath } from "node:fs/promises";
|
|
3416
|
+
import path6 from "node:path";
|
|
3417
|
+
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3418
|
+
var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
|
|
3419
|
+
function skillFileText(bytes) {
|
|
3420
|
+
const text = bytes.includes(0) ? null : bytes.toString("utf8");
|
|
3421
|
+
return {
|
|
3422
|
+
text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
|
|
3423
|
+
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function inside(root, file) {
|
|
3427
|
+
const relative = path6.relative(root, file);
|
|
3428
|
+
return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
3429
|
+
}
|
|
3430
|
+
async function handleInside(root, handle) {
|
|
3431
|
+
if (process.platform !== "linux") {
|
|
3432
|
+
return true;
|
|
3433
|
+
}
|
|
3434
|
+
const file = await readlink(`/proc/self/fd/${handle.fd}`).catch(() => null);
|
|
3435
|
+
return file !== null && inside(root, file);
|
|
3436
|
+
}
|
|
3437
|
+
async function walk(root, prefix, found) {
|
|
3438
|
+
const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
|
|
3439
|
+
withFileTypes: true
|
|
3440
|
+
}).catch(() => []);
|
|
3441
|
+
for (const entry of entries.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
|
|
3442
|
+
if (found.length >= SKILL_FILES_LIMIT) {
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
3446
|
+
if (!isSafeRelativePath(relative)) {
|
|
3447
|
+
continue;
|
|
3448
|
+
}
|
|
3449
|
+
if (entry.isDirectory()) {
|
|
3450
|
+
await walk(root, relative, found);
|
|
3451
|
+
} else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
|
|
3452
|
+
found.push(relative);
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
async function readEntry(root, relative) {
|
|
3457
|
+
let real;
|
|
3458
|
+
try {
|
|
3459
|
+
real = await realpath(path6.join(root, ...relative.split("/")));
|
|
3460
|
+
} catch {
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
if (!inside(root, real)) {
|
|
3464
|
+
return null;
|
|
3465
|
+
}
|
|
3466
|
+
const handle = await open2(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
|
|
3467
|
+
if (handle === null) {
|
|
3468
|
+
return null;
|
|
3469
|
+
}
|
|
3470
|
+
try {
|
|
3471
|
+
if (!await handleInside(root, handle)) {
|
|
3472
|
+
return null;
|
|
3473
|
+
}
|
|
3474
|
+
const stats = await handle.stat();
|
|
3475
|
+
if (!stats.isFile()) {
|
|
3476
|
+
return null;
|
|
3477
|
+
}
|
|
3478
|
+
const buffer = Buffer.alloc(Math.min(stats.size, SKILL_TEXT_LIMIT_BYTES));
|
|
3479
|
+
let size = 0;
|
|
3480
|
+
while (size < buffer.length) {
|
|
3481
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
|
|
3482
|
+
if (bytesRead === 0) {
|
|
3483
|
+
break;
|
|
3484
|
+
}
|
|
3485
|
+
size += bytesRead;
|
|
3486
|
+
}
|
|
3487
|
+
return {
|
|
3488
|
+
path: relative,
|
|
3489
|
+
size: stats.size,
|
|
3490
|
+
executable: (stats.mode & 73) !== 0,
|
|
3491
|
+
...skillFileText(buffer.subarray(0, size))
|
|
3492
|
+
};
|
|
3493
|
+
} finally {
|
|
3494
|
+
await handle.close();
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
async function readSkillFolder(dir) {
|
|
3498
|
+
const info = await lstat(dir).catch(() => null);
|
|
3499
|
+
if (info === null || !info.isDirectory()) {
|
|
3500
|
+
return null;
|
|
3501
|
+
}
|
|
3502
|
+
const root = await realpath(dir).catch(() => null);
|
|
3503
|
+
if (root === null) {
|
|
3504
|
+
return null;
|
|
3505
|
+
}
|
|
3506
|
+
const found = [];
|
|
3507
|
+
await walk(root, "", found);
|
|
3508
|
+
const files = [];
|
|
3509
|
+
for (const relative of found) {
|
|
3510
|
+
const file = await readEntry(root, relative);
|
|
3511
|
+
if (file !== null) {
|
|
3512
|
+
files.push(file);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
return files.some((file) => file.path === "SKILL.md") ? files : null;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3323
3518
|
// packages/board-core/dist/tokens.js
|
|
3324
3519
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
3325
3520
|
function mintToken() {
|
|
@@ -3333,7 +3528,7 @@ function hashToken(token) {
|
|
|
3333
3528
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3334
3529
|
var CLOSING_ATTEMPTS = 3;
|
|
3335
3530
|
function ownTurn(actor, turn2) {
|
|
3336
|
-
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel;
|
|
3531
|
+
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel && turn2.place === actor.place;
|
|
3337
3532
|
}
|
|
3338
3533
|
function namesList(names) {
|
|
3339
3534
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
@@ -3346,37 +3541,45 @@ var SignInsSchema = z16.object({
|
|
|
3346
3541
|
signIns: z16.array(z16.object({
|
|
3347
3542
|
tokenHash: z16.string().min(1),
|
|
3348
3543
|
login: z16.string().min(1),
|
|
3349
|
-
/** The account's picture
|
|
3544
|
+
/** The account's picture, when GitHub gave one on its own avatar host. */
|
|
3350
3545
|
avatarUrl: z16.url().optional(),
|
|
3351
3546
|
createdAt: IsoDateTimeSchema,
|
|
3352
3547
|
expiresAt: IsoDateTimeSchema
|
|
3353
3548
|
})).default([])
|
|
3354
3549
|
});
|
|
3355
|
-
var TurnStartPayloadSchema = z16.object({
|
|
3550
|
+
var TurnStartPayloadSchema = z16.object({ turnId: UlidSchema });
|
|
3356
3551
|
var CursorsSchema = z16.object({
|
|
3357
3552
|
digest: z16.string().nullable(),
|
|
3358
3553
|
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3359
|
-
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3360
3554
|
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3555
|
+
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3361
3556
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3362
3557
|
news: z16.string().optional()
|
|
3363
3558
|
});
|
|
3364
|
-
var NO_CURSORS = {
|
|
3559
|
+
var NO_CURSORS = {
|
|
3560
|
+
digest: null,
|
|
3561
|
+
scopes: {},
|
|
3562
|
+
channelsFrom: {},
|
|
3563
|
+
placesFrom: {}
|
|
3564
|
+
};
|
|
3365
3565
|
function conversationKey(scope, part) {
|
|
3366
3566
|
return part === null ? scope : `${scope}/${part}`;
|
|
3367
3567
|
}
|
|
3368
3568
|
function channelOfPart(part) {
|
|
3369
3569
|
return part?.startsWith("#") === true ? part.slice(1) : void 0;
|
|
3370
3570
|
}
|
|
3571
|
+
function placeOfPart(part) {
|
|
3572
|
+
return part?.startsWith("~") === true ? part.slice(1) : void 0;
|
|
3573
|
+
}
|
|
3371
3574
|
function threadOfPart(part) {
|
|
3372
|
-
return part === null || part === void 0 || part.startsWith("#") ? void 0 : part;
|
|
3575
|
+
return part === null || part === void 0 || part.startsWith("#") || part.startsWith("~") ? void 0 : part;
|
|
3373
3576
|
}
|
|
3374
3577
|
function channelStart(cursors, scope) {
|
|
3375
3578
|
const from = cursors.channelsFrom[scope];
|
|
3376
3579
|
return from === void 0 ? cursors.digest : from;
|
|
3377
3580
|
}
|
|
3378
|
-
function
|
|
3379
|
-
const from = cursors.
|
|
3581
|
+
function placeStart(cursors, scope) {
|
|
3582
|
+
const from = cursors.placesFrom[scope];
|
|
3380
3583
|
return from === void 0 ? cursors.digest : from;
|
|
3381
3584
|
}
|
|
3382
3585
|
function newsStart(cursors) {
|
|
@@ -3390,7 +3593,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3390
3593
|
if (part === null) {
|
|
3391
3594
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3392
3595
|
}
|
|
3393
|
-
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) :
|
|
3596
|
+
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : cursors.digest);
|
|
3394
3597
|
}
|
|
3395
3598
|
function byId(a, b) {
|
|
3396
3599
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3398,21 +3601,23 @@ function byId(a, b) {
|
|
|
3398
3601
|
function turnTime(record) {
|
|
3399
3602
|
return record.endedAt ?? record.startedAt;
|
|
3400
3603
|
}
|
|
3604
|
+
function turnConversation(record) {
|
|
3605
|
+
return record.thread !== void 0 ? ` in thread ${record.thread}` : record.channel !== void 0 ? ` in #${record.channel}` : record.place !== void 0 ? ` in ${record.place}'s channels` : "";
|
|
3606
|
+
}
|
|
3401
3607
|
var PausedSchema = z16.object({ paused: z16.boolean() });
|
|
3402
3608
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3403
3609
|
function withoutMentions(text) {
|
|
3404
3610
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3405
3611
|
}
|
|
3406
|
-
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3407
3612
|
function skillFolderOf(proposal2) {
|
|
3408
3613
|
if (proposal2.kind !== "skill") {
|
|
3409
3614
|
return null;
|
|
3410
3615
|
}
|
|
3411
3616
|
const parsed = SkillProposalSchema.safeParse(proposal2.charter);
|
|
3412
|
-
return parsed.success
|
|
3617
|
+
return parsed.success ? parsed.data.name : null;
|
|
3413
3618
|
}
|
|
3414
3619
|
function proposedFrom(origin, where) {
|
|
3415
|
-
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
|
|
3620
|
+
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
3416
3621
|
}
|
|
3417
3622
|
function megabytes(bytes) {
|
|
3418
3623
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
@@ -3424,27 +3629,6 @@ var FRONT_DESK_TRIGGER = "user_post";
|
|
|
3424
3629
|
var OPS_CHANNELS = ["governance"];
|
|
3425
3630
|
var FRONT_DESK_CHANNELS = [ASK_CHANNEL];
|
|
3426
3631
|
var AlignmentsSchema = z16.object({ applied: z16.array(z16.string()).default([]) });
|
|
3427
|
-
var FOLLOW_DECISIONS = "ops-readers-follow-decisions";
|
|
3428
|
-
var THREAD_RECORDS = "threads-as-records";
|
|
3429
|
-
var DIGEST_CURSORS = "digest-replaces-inbox";
|
|
3430
|
-
var TASK_RETURNS = "task-returns-recorded";
|
|
3431
|
-
var OPS_CHANNEL_RETIRED = "ops-channel-retired";
|
|
3432
|
-
var DECISIONS_CHANNEL_RETIRED = "decisions-channel-retired";
|
|
3433
|
-
var THREAD_CONVERSATIONS = "thread-conversations";
|
|
3434
|
-
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3435
|
-
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3436
|
-
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3437
|
-
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3438
|
-
var CRON_VERBS = "cron-verbs-granted";
|
|
3439
|
-
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
3440
|
-
var IMAGE_UPLOADS = "image-uploads-granted";
|
|
3441
|
-
var KnowledgeEventSchema = z16.object({
|
|
3442
|
-
topic: NameSchema,
|
|
3443
|
-
project: NameSchema.nullable(),
|
|
3444
|
-
replaced: z16.boolean().optional()
|
|
3445
|
-
});
|
|
3446
|
-
var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
|
|
3447
|
-
var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
|
|
3448
3632
|
var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
|
|
3449
3633
|
var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
3450
3634
|
var REALLOCATING_ROLES = [USER_ROLE, "steward", "concierge"];
|
|
@@ -3464,9 +3648,9 @@ function sentence(summary) {
|
|
|
3464
3648
|
return summary.trim().replace(/\.+$/, "");
|
|
3465
3649
|
}
|
|
3466
3650
|
function skillSummary(data, body) {
|
|
3467
|
-
const
|
|
3468
|
-
if (typeof
|
|
3469
|
-
return
|
|
3651
|
+
const description = data["description"];
|
|
3652
|
+
if (typeof description === "string" && description.trim().length > 0) {
|
|
3653
|
+
return description.trim();
|
|
3470
3654
|
}
|
|
3471
3655
|
return body.split("\n").map((line) => line.trim()).find((line) => line.length > 0 && !line.startsWith("#")) ?? "";
|
|
3472
3656
|
}
|
|
@@ -3508,8 +3692,8 @@ function restage(existing, edit) {
|
|
|
3508
3692
|
};
|
|
3509
3693
|
}
|
|
3510
3694
|
function reopenStage(stage) {
|
|
3511
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3512
|
-
return
|
|
3695
|
+
const { completedBy: _by, completedAt: _at, ...open4 } = stage;
|
|
3696
|
+
return open4;
|
|
3513
3697
|
}
|
|
3514
3698
|
function gateChanges(before, after) {
|
|
3515
3699
|
const changes = [];
|
|
@@ -3590,7 +3774,7 @@ var Board = class _Board {
|
|
|
3590
3774
|
homes;
|
|
3591
3775
|
constructor(dataDir, options) {
|
|
3592
3776
|
this.paths = new BoardPaths(dataDir);
|
|
3593
|
-
this.homes = new HomeRepos(
|
|
3777
|
+
this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
|
|
3594
3778
|
this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
|
|
3595
3779
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3596
3780
|
this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
|
|
@@ -3619,368 +3803,22 @@ var Board = class _Board {
|
|
|
3619
3803
|
return board;
|
|
3620
3804
|
}
|
|
3621
3805
|
/**
|
|
3622
|
-
* Applies each one-time alignment an existing society has not had yet
|
|
3623
|
-
*
|
|
3806
|
+
* Applies each one-time alignment an existing society has not had yet, and remembers it, so a
|
|
3807
|
+
* member that later chooses otherwise keeps its choice. A new alignment is a name and a method in
|
|
3808
|
+
* this list; once every board has applied it, both are deleted. None is pending.
|
|
3624
3809
|
*/
|
|
3625
3810
|
async applyAlignments() {
|
|
3626
|
-
const
|
|
3811
|
+
const alignments = [];
|
|
3812
|
+
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3627
3813
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3628
|
-
const pending =
|
|
3629
|
-
FOLLOW_DECISIONS,
|
|
3630
|
-
THREAD_RECORDS,
|
|
3631
|
-
DIGEST_CURSORS,
|
|
3632
|
-
TASK_RETURNS,
|
|
3633
|
-
OPS_CHANNEL_RETIRED,
|
|
3634
|
-
DECISIONS_CHANNEL_RETIRED,
|
|
3635
|
-
THREAD_CONVERSATIONS,
|
|
3636
|
-
ASKS_CHANNEL_OPENED,
|
|
3637
|
-
KNOWLEDGE_REMOVAL,
|
|
3638
|
-
CHANNEL_CONVERSATIONS,
|
|
3639
|
-
DASHBOARD_UPDATES,
|
|
3640
|
-
CRON_VERBS,
|
|
3641
|
-
KNOWLEDGE_NOTICES,
|
|
3642
|
-
IMAGE_UPLOADS
|
|
3643
|
-
].filter((name) => !applied.includes(name));
|
|
3814
|
+
const pending = alignments.filter(({ name }) => !applied.includes(name));
|
|
3644
3815
|
if (pending.length === 0) {
|
|
3645
3816
|
return;
|
|
3646
3817
|
}
|
|
3647
|
-
|
|
3648
|
-
await
|
|
3649
|
-
}
|
|
3650
|
-
if (pending.includes(THREAD_RECORDS)) {
|
|
3651
|
-
await this.recordTaskThreads();
|
|
3652
|
-
}
|
|
3653
|
-
if (pending.includes(DIGEST_CURSORS)) {
|
|
3654
|
-
await this.renameInboxCursors();
|
|
3655
|
-
}
|
|
3656
|
-
if (pending.includes(TASK_RETURNS)) {
|
|
3657
|
-
await this.recordTaskReturns();
|
|
3818
|
+
for (const { apply } of pending) {
|
|
3819
|
+
await apply();
|
|
3658
3820
|
}
|
|
3659
|
-
|
|
3660
|
-
await this.retireSocietyChannel("ops");
|
|
3661
|
-
}
|
|
3662
|
-
if (pending.includes(DECISIONS_CHANNEL_RETIRED)) {
|
|
3663
|
-
await this.retireSocietyChannel("decisions");
|
|
3664
|
-
}
|
|
3665
|
-
if (pending.includes(THREAD_CONVERSATIONS)) {
|
|
3666
|
-
await this.startThreadCursors();
|
|
3667
|
-
}
|
|
3668
|
-
if (pending.includes(ASKS_CHANNEL_OPENED)) {
|
|
3669
|
-
await this.openAsksChannel();
|
|
3670
|
-
}
|
|
3671
|
-
if (pending.includes(KNOWLEDGE_REMOVAL)) {
|
|
3672
|
-
await this.grantBeside("write_knowledge", "remove_knowledge");
|
|
3673
|
-
}
|
|
3674
|
-
if (pending.includes(CHANNEL_CONVERSATIONS)) {
|
|
3675
|
-
await this.startChannelCursors();
|
|
3676
|
-
}
|
|
3677
|
-
if (pending.includes(DASHBOARD_UPDATES)) {
|
|
3678
|
-
await this.grantBeside("write_knowledge", "update_dashboard");
|
|
3679
|
-
await this.rewordDashboards();
|
|
3680
|
-
}
|
|
3681
|
-
if (pending.includes(CRON_VERBS)) {
|
|
3682
|
-
await this.grantToEveryRole(CRON_VERB_NAMES);
|
|
3683
|
-
}
|
|
3684
|
-
if (pending.includes(KNOWLEDGE_NOTICES)) {
|
|
3685
|
-
await this.noticeKnowledgeNotes();
|
|
3686
|
-
}
|
|
3687
|
-
if (pending.includes(IMAGE_UPLOADS)) {
|
|
3688
|
-
await this.grantBeside("post_message", "upload_image");
|
|
3689
|
-
}
|
|
3690
|
-
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3691
|
-
}
|
|
3692
|
-
/**
|
|
3693
|
-
* Gives each knowledge note from before notes carried their notice the notice it would carry
|
|
3694
|
-
* now. The event log, not the note's words, says which post is one: a knowledge verb appends
|
|
3695
|
-
* its event and then its note under the mutex, so a note is the post right after such an event,
|
|
3696
|
-
* by the same actor, at the top of the general channel of the topic's place. A post written to
|
|
3697
|
-
* look like a note follows no such event and is left as it is.
|
|
3698
|
-
*/
|
|
3699
|
-
async noticeKnowledgeNotes() {
|
|
3700
|
-
let before;
|
|
3701
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
3702
|
-
const verb = before;
|
|
3703
|
-
before = event;
|
|
3704
|
-
if (event.type !== "message.posted" || verb === void 0 || verb.actor !== event.actor || verb.type !== "knowledge.written" && verb.type !== "knowledge.removed") {
|
|
3705
|
-
continue;
|
|
3706
|
-
}
|
|
3707
|
-
const knowledge2 = KnowledgeEventSchema.safeParse(verb.payload);
|
|
3708
|
-
const id = UlidSchema.safeParse(event.payload["id"]);
|
|
3709
|
-
if (!knowledge2.success || !id.success || (event.payload["thread"] ?? null) !== null || event.payload["channel"] !== channelRef(knowledge2.data.project, "general")) {
|
|
3710
|
-
continue;
|
|
3711
|
-
}
|
|
3712
|
-
const { topic, project: project2, replaced } = knowledge2.data;
|
|
3713
|
-
const file = this.paths.messageFile(this.paths.channelDir(channelRef(project2, "general")), id.data, event.actor);
|
|
3714
|
-
if (!await exists(file)) {
|
|
3715
|
-
continue;
|
|
3716
|
-
}
|
|
3717
|
-
const { data, body, error } = await readLooseMarkdown(file);
|
|
3718
|
-
if (error !== null || data["notice"] !== void 0) {
|
|
3719
|
-
continue;
|
|
3720
|
-
}
|
|
3721
|
-
const notice = {
|
|
3722
|
-
kind: "knowledge",
|
|
3723
|
-
action: verb.type === "knowledge.removed" ? "removed" : replaced === true ? "updated" : "written",
|
|
3724
|
-
topic,
|
|
3725
|
-
project: project2
|
|
3726
|
-
};
|
|
3727
|
-
await writeMarkdown(file, { ...data, notice }, body);
|
|
3728
|
-
}
|
|
3729
|
-
}
|
|
3730
|
-
/**
|
|
3731
|
-
* Grants `verb` once to every charter granting `beside`, the work roles' charters in practice;
|
|
3732
|
-
* seed roles have it from their seed.
|
|
3733
|
-
*/
|
|
3734
|
-
async grantBeside(beside, verb) {
|
|
3735
|
-
for (const charter of await this.listRoles()) {
|
|
3736
|
-
if (charter.verbs.includes(beside) && !charter.verbs.includes(verb)) {
|
|
3737
|
-
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, verb] }, { verbsAdded: [verb] });
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
/** Grants verbs once to every charter but the user's, which holds every verb already. */
|
|
3742
|
-
async grantToEveryRole(verbs) {
|
|
3743
|
-
for (const charter of await this.listRoles()) {
|
|
3744
|
-
const missing = verbs.filter((verb) => !charter.verbs.includes(verb));
|
|
3745
|
-
if (charter.name !== USER_ROLE && missing.length > 0) {
|
|
3746
|
-
await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, ...missing] }, { verbsAdded: missing });
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
}
|
|
3750
|
-
/** A dashboard still saying agents may edit its file says how it changes now. */
|
|
3751
|
-
async rewordDashboards() {
|
|
3752
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
3753
|
-
const file = this.paths.dashboard(slug);
|
|
3754
|
-
if (!await exists(file)) {
|
|
3755
|
-
continue;
|
|
3756
|
-
}
|
|
3757
|
-
const { data, body } = await readLooseMarkdown(file);
|
|
3758
|
-
if (body.includes(OLD_DASHBOARD_LINE)) {
|
|
3759
|
-
await writeMarkdown(file, data, body.replace(OLD_DASHBOARD_LINE, DASHBOARD_LINE));
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
}
|
|
3763
|
-
/**
|
|
3764
|
-
* A task sent back before tasks recorded it kept no trace outside the event log. A task still
|
|
3765
|
-
* short of the stage its latest `task.moved` came from is in that rework, and records it.
|
|
3766
|
-
*/
|
|
3767
|
-
async recordTaskReturns() {
|
|
3768
|
-
const moves = /* @__PURE__ */ new Map();
|
|
3769
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
3770
|
-
const taskId = event.payload["taskId"];
|
|
3771
|
-
if (event.type === "task.moved" && typeof taskId === "string") {
|
|
3772
|
-
moves.set(taskId, event);
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
3776
|
-
for (const task2 of await this.listTasks(slug)) {
|
|
3777
|
-
const move = moves.get(task2.id);
|
|
3778
|
-
const from = move?.payload["from"];
|
|
3779
|
-
if (move === void 0 || typeof from !== "string" || task2.returned !== void 0 || task2.completing || task2.status !== "open" && task2.status !== "claimed") {
|
|
3780
|
-
continue;
|
|
3781
|
-
}
|
|
3782
|
-
const back = stageIndex(task2, from);
|
|
3783
|
-
if (back > stageIndex(task2, task2.stage)) {
|
|
3784
|
-
await this.writeTask(slug, {
|
|
3785
|
-
...task2,
|
|
3786
|
-
returned: { from, by: move.actor, at: move.ts }
|
|
3787
|
-
});
|
|
3788
|
-
}
|
|
3789
|
-
}
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
/**
|
|
3793
|
-
* A society channel the board no longer uses leaves the society's list and every member's
|
|
3794
|
-
* subscriptions; its old posts stay on disk. `ops` carried operations signals, which are only
|
|
3795
|
-
* logged now; `decisions` carried proposals' outcomes, which are posts in their threads now, and
|
|
3796
|
-
* requests to the user, which are mentions of the user wherever the question belongs.
|
|
3797
|
-
*/
|
|
3798
|
-
async retireSocietyChannel(name) {
|
|
3799
|
-
const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
|
|
3800
|
-
if (doc.data.channels.includes(name)) {
|
|
3801
|
-
await writeMarkdown(this.paths.societyFile(), { ...doc.data, channels: doc.data.channels.filter((channel) => channel !== name) }, doc.body);
|
|
3802
|
-
}
|
|
3803
|
-
for (const agent2 of await this.listAgents()) {
|
|
3804
|
-
if (agent2.subscriptions.includes(name)) {
|
|
3805
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
3806
|
-
...a,
|
|
3807
|
-
subscriptions: a.subscriptions.filter((ref) => ref !== name)
|
|
3808
|
-
}));
|
|
3809
|
-
await this.refreshMember(agent2.name);
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
/**
|
|
3814
|
-
* Asks once opened their threads in general, where closing one posted its summary to every
|
|
3815
|
-
* citizen and woke the steward's heartbeat for it. The society gains the asks channel, and the
|
|
3816
|
-
* front desk follows it.
|
|
3817
|
-
*/
|
|
3818
|
-
async openAsksChannel() {
|
|
3819
|
-
const doc = await readMarkdown(this.paths.societyFile(), SocietySchema);
|
|
3820
|
-
if (!doc.data.channels.includes(ASK_CHANNEL)) {
|
|
3821
|
-
await writeMarkdown(this.paths.societyFile(), { ...doc.data, channels: [...doc.data.channels, ASK_CHANNEL] }, doc.body);
|
|
3822
|
-
}
|
|
3823
|
-
await ensureDir(this.paths.societyChannel(ASK_CHANNEL));
|
|
3824
|
-
for (const agent2 of await this.listAgents()) {
|
|
3825
|
-
if (agent2.status !== "active" || agent2.subscriptions.includes(ASK_CHANNEL)) {
|
|
3826
|
-
continue;
|
|
3827
|
-
}
|
|
3828
|
-
if (!(await this.readRole(agent2.role)).wakeTriggers.includes(FRONT_DESK_TRIGGER)) {
|
|
3829
|
-
continue;
|
|
3830
|
-
}
|
|
3831
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
3832
|
-
...a,
|
|
3833
|
-
subscriptions: [...a.subscriptions, ASK_CHANNEL]
|
|
3834
|
-
}));
|
|
3835
|
-
await this.refreshMember(agent2.name);
|
|
3836
|
-
await this.events.append("subscription.changed", agent2.name, {
|
|
3837
|
-
channel: ASK_CHANNEL,
|
|
3838
|
-
subscribed: true,
|
|
3839
|
-
aligned: true
|
|
3840
|
-
});
|
|
3841
|
-
}
|
|
3842
|
-
}
|
|
3843
|
-
/** Members that predate `decisions` among the ops channels read proposals without their outcome. */
|
|
3844
|
-
async followDecisions() {
|
|
3845
|
-
if (!(await this.society()).channels.includes("decisions")) {
|
|
3846
|
-
return;
|
|
3847
|
-
}
|
|
3848
|
-
for (const agent2 of await this.listAgents()) {
|
|
3849
|
-
if (agent2.status !== "active" || agent2.subscriptions.includes("decisions")) {
|
|
3850
|
-
continue;
|
|
3851
|
-
}
|
|
3852
|
-
if (!(await this.readRole(agent2.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER)) {
|
|
3853
|
-
continue;
|
|
3854
|
-
}
|
|
3855
|
-
await this.updateAgent(agent2.name, (a) => ({
|
|
3856
|
-
...a,
|
|
3857
|
-
subscriptions: [...a.subscriptions, "decisions"]
|
|
3858
|
-
}));
|
|
3859
|
-
await this.refreshMember(agent2.name);
|
|
3860
|
-
await this.events.append("subscription.changed", agent2.name, {
|
|
3861
|
-
channel: "decisions",
|
|
3862
|
-
subscribed: true,
|
|
3863
|
-
aligned: true
|
|
3864
|
-
});
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
/**
|
|
3868
|
-
* Threads were once a state field on their task. Each becomes a record under the task's project,
|
|
3869
|
-
* on its general channel, closed if its task has ended, and the field leaves the task file.
|
|
3870
|
-
*/
|
|
3871
|
-
async recordTaskThreads() {
|
|
3872
|
-
const opened = /* @__PURE__ */ new Map();
|
|
3873
|
-
const closed = /* @__PURE__ */ new Map();
|
|
3874
|
-
for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
|
|
3875
|
-
const taskId = event.payload["taskId"];
|
|
3876
|
-
if (typeof taskId !== "string") {
|
|
3877
|
-
continue;
|
|
3878
|
-
}
|
|
3879
|
-
if (event.type === "thread.opened") {
|
|
3880
|
-
opened.set(taskId, event);
|
|
3881
|
-
} else if (event.type === "thread.closed") {
|
|
3882
|
-
closed.set(taskId, event);
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
3885
|
-
for (const slug of await listDirs(this.paths.projects())) {
|
|
3886
|
-
const threads = this.paths.threads(slug);
|
|
3887
|
-
const summaries = /* @__PURE__ */ new Map();
|
|
3888
|
-
for (const message of await this.readMessagesIn(this.paths.projectChannel(slug, "general"), null)) {
|
|
3889
|
-
if (message.task !== void 0) {
|
|
3890
|
-
summaries.set(message.task, message.body.replace(/^Thread closed[^\n]*\n\n/, ""));
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3894
|
-
const raw = await readMarkdown(path6.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
3895
|
-
const legacy = raw.data["thread"];
|
|
3896
|
-
if (legacy === void 0) {
|
|
3897
|
-
continue;
|
|
3898
|
-
}
|
|
3899
|
-
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3900
|
-
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
3901
|
-
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
3902
|
-
const open3 = legacy === "open" && !ended;
|
|
3903
|
-
const openedBy = opened.get(task2.id);
|
|
3904
|
-
const closedBy = closed.get(task2.id);
|
|
3905
|
-
await this.writeThread(threads, {
|
|
3906
|
-
id: task2.id,
|
|
3907
|
-
channel: channelRef(slug, "general"),
|
|
3908
|
-
title: task2.title,
|
|
3909
|
-
subject: { kind: "task", id: task2.id },
|
|
3910
|
-
state: open3 ? "open" : "closed",
|
|
3911
|
-
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
3912
|
-
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
3913
|
-
...open3 ? {} : {
|
|
3914
|
-
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
3915
|
-
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
3916
|
-
},
|
|
3917
|
-
body: summaries.get(task2.id) ?? ""
|
|
3918
|
-
});
|
|
3919
|
-
}
|
|
3920
|
-
await this.writeTask(slug, task2);
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
/**
|
|
3925
|
-
* Every channel of a scope was once read in its home conversation, so a member had read each up
|
|
3926
|
-
* to its scope's cursor. Each channel beside general starts from that cursor now, as each takes a
|
|
3927
|
-
* conversation of its own.
|
|
3928
|
-
*/
|
|
3929
|
-
async startChannelCursors() {
|
|
3930
|
-
for (const agent2 of await this.listAgents()) {
|
|
3931
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
3932
|
-
if (!await exists(file)) {
|
|
3933
|
-
continue;
|
|
3934
|
-
}
|
|
3935
|
-
const cursors = await readJson(file, CursorsSchema);
|
|
3936
|
-
const scopes = /* @__PURE__ */ new Set([
|
|
3937
|
-
...Object.keys(cursors.scopes).filter((key) => !key.includes("/")),
|
|
3938
|
-
...agent2.memberships,
|
|
3939
|
-
SOCIETY_SCOPE
|
|
3940
|
-
]);
|
|
3941
|
-
const channelsFrom = Object.fromEntries([...scopes].map((scope) => [scope, cursorOf(cursors, scope)]));
|
|
3942
|
-
await writeJson(file, { ...cursors, channelsFrom });
|
|
3943
|
-
}
|
|
3944
|
-
}
|
|
3945
|
-
/**
|
|
3946
|
-
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
3947
|
-
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
3948
|
-
* conversation takes a cursor of its own.
|
|
3949
|
-
*/
|
|
3950
|
-
async startThreadCursors() {
|
|
3951
|
-
for (const agent2 of await this.listAgents()) {
|
|
3952
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
3953
|
-
if (!await exists(file)) {
|
|
3954
|
-
continue;
|
|
3955
|
-
}
|
|
3956
|
-
const cursors = await readJson(file, CursorsSchema);
|
|
3957
|
-
const scopes = /* @__PURE__ */ new Set([
|
|
3958
|
-
...Object.keys(cursors.scopes).filter((key) => !key.includes("/")),
|
|
3959
|
-
...agent2.memberships,
|
|
3960
|
-
SOCIETY_SCOPE
|
|
3961
|
-
]);
|
|
3962
|
-
const threadsFrom = Object.fromEntries([...scopes].map((scope) => [scope, cursorOf(cursors, scope)]));
|
|
3963
|
-
await writeJson(file, { ...cursors, threadsFrom });
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
/**
|
|
3967
|
-
* The digest was once called the inbox: each cursor file keyed its cursor `inbox`, and the user
|
|
3968
|
-
* followed every society channel and project general channel to read one. The user takes no
|
|
3969
|
-
* turns, so it follows nothing now.
|
|
3970
|
-
*/
|
|
3971
|
-
async renameInboxCursors() {
|
|
3972
|
-
const Legacy = z16.object({ inbox: z16.string().nullable() });
|
|
3973
|
-
for (const agent2 of await this.listAgents()) {
|
|
3974
|
-
const file = this.paths.agentCursors(agent2.name);
|
|
3975
|
-
if (await exists(file)) {
|
|
3976
|
-
const legacy = Legacy.safeParse(JSON.parse(await readFile4(file, "utf8")));
|
|
3977
|
-
if (legacy.success) {
|
|
3978
|
-
await writeJson(file, { digest: legacy.data.inbox });
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
await this.updateAgent(USER_NAME, (user2) => ({ ...user2, subscriptions: [] }));
|
|
3983
|
-
await this.refreshMember(USER_NAME);
|
|
3821
|
+
await writeJson(file, { applied: [...applied, ...pending.map(({ name }) => name)] });
|
|
3984
3822
|
}
|
|
3985
3823
|
/** Writes every missing seed charter and aligns the existing ones with their seed. */
|
|
3986
3824
|
async ensureSeedRoles() {
|
|
@@ -4082,13 +3920,15 @@ ${charter.purpose}
|
|
|
4082
3920
|
const token = mintToken();
|
|
4083
3921
|
const thread2 = threadOfPart(part);
|
|
4084
3922
|
const channel = channelOfPart(part);
|
|
3923
|
+
const place = placeOfPart(part);
|
|
4085
3924
|
this.turnTokens.set(hashToken(token), {
|
|
4086
3925
|
actor: {
|
|
4087
3926
|
name: agent2,
|
|
4088
3927
|
role: role2,
|
|
4089
3928
|
...scope === void 0 ? {} : { scope },
|
|
4090
3929
|
...thread2 === void 0 ? {} : { thread: thread2 },
|
|
4091
|
-
...channel === void 0 ? {} : { channel }
|
|
3930
|
+
...channel === void 0 ? {} : { channel },
|
|
3931
|
+
...place === void 0 ? {} : { place }
|
|
4092
3932
|
},
|
|
4093
3933
|
expiresAt: this.now().getTime() + ttlMs
|
|
4094
3934
|
});
|
|
@@ -4177,7 +4017,7 @@ ${charter.purpose}
|
|
|
4177
4017
|
});
|
|
4178
4018
|
}
|
|
4179
4019
|
signInsFile() {
|
|
4180
|
-
return
|
|
4020
|
+
return path7.join(this.paths.state(), "sign-ins.json");
|
|
4181
4021
|
}
|
|
4182
4022
|
async readSignIns() {
|
|
4183
4023
|
const file = this.signInsFile();
|
|
@@ -4274,7 +4114,7 @@ ${charter.purpose}
|
|
|
4274
4114
|
const files = await listFiles(this.paths.tasks(project2));
|
|
4275
4115
|
const tasks = [];
|
|
4276
4116
|
for (const file of files) {
|
|
4277
|
-
const doc = await readMarkdown(
|
|
4117
|
+
const doc = await readMarkdown(path7.join(this.paths.tasks(project2), file), TaskFrontmatterSchema);
|
|
4278
4118
|
tasks.push({ ...doc.data, body: doc.body });
|
|
4279
4119
|
}
|
|
4280
4120
|
return tasks;
|
|
@@ -4293,7 +4133,7 @@ ${charter.purpose}
|
|
|
4293
4133
|
const files = await listFiles(this.paths.proposals());
|
|
4294
4134
|
const proposals = [];
|
|
4295
4135
|
for (const file of files) {
|
|
4296
|
-
const doc = await readMarkdown(
|
|
4136
|
+
const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
|
|
4297
4137
|
proposals.push({ ...doc.data, body: doc.body });
|
|
4298
4138
|
}
|
|
4299
4139
|
return proposals;
|
|
@@ -4320,7 +4160,7 @@ ${charter.purpose}
|
|
|
4320
4160
|
async listMembers() {
|
|
4321
4161
|
const members = [];
|
|
4322
4162
|
for (const file of await listFiles(this.paths.members())) {
|
|
4323
|
-
const doc = await readMarkdown(
|
|
4163
|
+
const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
|
|
4324
4164
|
members.push({ ...doc.data, profile: doc.body });
|
|
4325
4165
|
}
|
|
4326
4166
|
return members;
|
|
@@ -4339,7 +4179,7 @@ ${charter.purpose}
|
|
|
4339
4179
|
const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
|
|
4340
4180
|
const topics = [];
|
|
4341
4181
|
for (const file of await listFiles(dir)) {
|
|
4342
|
-
topics.push(await this.knowledgeAt(
|
|
4182
|
+
topics.push(await this.knowledgeAt(path7.join(dir, file), project2, file.replace(/\.md$/, "")));
|
|
4343
4183
|
}
|
|
4344
4184
|
return topics;
|
|
4345
4185
|
}
|
|
@@ -4369,7 +4209,7 @@ ${charter.purpose}
|
|
|
4369
4209
|
const aside = this.paths.removedKnowledge(project2 ?? SOCIETY_SCOPE);
|
|
4370
4210
|
let highest = 0;
|
|
4371
4211
|
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
4372
|
-
const copy = await this.knowledgeAt(
|
|
4212
|
+
const copy = await this.knowledgeAt(path7.join(aside, file), project2, topic);
|
|
4373
4213
|
highest = Math.max(highest, copy.revision);
|
|
4374
4214
|
}
|
|
4375
4215
|
return highest;
|
|
@@ -4385,7 +4225,7 @@ ${charter.purpose}
|
|
|
4385
4225
|
async listSkillsIn(dir, scope) {
|
|
4386
4226
|
const skills = [];
|
|
4387
4227
|
for (const entry of await listDirs(dir)) {
|
|
4388
|
-
const file =
|
|
4228
|
+
const file = path7.join(dir, entry, "SKILL.md");
|
|
4389
4229
|
if (!await exists(file)) {
|
|
4390
4230
|
continue;
|
|
4391
4231
|
}
|
|
@@ -4475,7 +4315,7 @@ ${charter.purpose}
|
|
|
4475
4315
|
if (!wanted.delete(file.slice(0, 26))) {
|
|
4476
4316
|
continue;
|
|
4477
4317
|
}
|
|
4478
|
-
const { data } = await readMarkdown(
|
|
4318
|
+
const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
4479
4319
|
found.push(MessageRefSchema.parse(data));
|
|
4480
4320
|
}
|
|
4481
4321
|
if (wanted.size === 0) {
|
|
@@ -4507,7 +4347,7 @@ ${charter.purpose}
|
|
|
4507
4347
|
if (!file.endsWith(".md")) {
|
|
4508
4348
|
continue;
|
|
4509
4349
|
}
|
|
4510
|
-
const doc = await readMarkdown(
|
|
4350
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4511
4351
|
if (doc.data.state !== "open") {
|
|
4512
4352
|
continue;
|
|
4513
4353
|
}
|
|
@@ -4534,7 +4374,7 @@ ${charter.purpose}
|
|
|
4534
4374
|
const all = [];
|
|
4535
4375
|
for (const threads of await this.threadScopes()) {
|
|
4536
4376
|
for (const file of await listFiles(threads)) {
|
|
4537
|
-
const doc = await readMarkdown(
|
|
4377
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4538
4378
|
const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
|
|
4539
4379
|
const last = messages.at(-1);
|
|
4540
4380
|
all.push({
|
|
@@ -4782,7 +4622,7 @@ ${DASHBOARD_LINE}
|
|
|
4782
4622
|
if (event.type === "turn.started") {
|
|
4783
4623
|
const start = TurnStartPayloadSchema.safeParse(event.payload);
|
|
4784
4624
|
if (start.success)
|
|
4785
|
-
starts.set(start.data.turnId
|
|
4625
|
+
starts.set(start.data.turnId, event.ts);
|
|
4786
4626
|
continue;
|
|
4787
4627
|
}
|
|
4788
4628
|
if (event.type !== "turn.completed" && event.type !== "turn.failed") {
|
|
@@ -4790,9 +4630,11 @@ ${DASHBOARD_LINE}
|
|
|
4790
4630
|
}
|
|
4791
4631
|
const parsed = TurnHistoryPayloadSchema.safeParse(event.payload);
|
|
4792
4632
|
if (parsed.success) {
|
|
4793
|
-
const
|
|
4794
|
-
const startedAt = starts.get(
|
|
4795
|
-
|
|
4633
|
+
const { turnId } = parsed.data;
|
|
4634
|
+
const startedAt = turnId === void 0 ? void 0 : starts.get(turnId);
|
|
4635
|
+
if (turnId !== void 0) {
|
|
4636
|
+
starts.delete(turnId);
|
|
4637
|
+
}
|
|
4796
4638
|
entries.push({
|
|
4797
4639
|
id: event.id,
|
|
4798
4640
|
ts: event.ts,
|
|
@@ -4901,16 +4743,20 @@ ${DASHBOARD_LINE}
|
|
|
4901
4743
|
}
|
|
4902
4744
|
/**
|
|
4903
4745
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
4904
|
-
* general channel, one of its other channels,
|
|
4746
|
+
* general channel, one of its other channels, one of its threads, or, in the society scope, a
|
|
4747
|
+
* project's channels. The heartbeat asks, so that it wakes a member where its unread
|
|
4905
4748
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
4906
4749
|
* scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
|
|
4907
4750
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
4908
4751
|
* whoever it hands work to, and the board's own, operations signals and landing announcements,
|
|
4909
|
-
* whose signals that call for action wake their readers directly.
|
|
4752
|
+
* whose signals that call for action wake their readers directly. A closed thread counts for
|
|
4753
|
+
* nothing: its conversation has ended, and a turn there could only read its posts and be refused
|
|
4754
|
+
* a reply, while a wake queued before it closed, as for a mention, still runs there.
|
|
4910
4755
|
*/
|
|
4911
4756
|
async unreadByConversation(actor) {
|
|
4912
4757
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4913
4758
|
const counts = /* @__PURE__ */ new Map();
|
|
4759
|
+
const closed = /* @__PURE__ */ new Map();
|
|
4914
4760
|
const unscoped = { name: actor.name, role: actor.role };
|
|
4915
4761
|
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4916
4762
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
@@ -4920,11 +4766,23 @@ ${DASHBOARD_LINE}
|
|
|
4920
4766
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4921
4767
|
continue;
|
|
4922
4768
|
}
|
|
4769
|
+
const thread2 = threadOfPart(part);
|
|
4770
|
+
if (thread2 !== void 0) {
|
|
4771
|
+
let ended = closed.get(thread2);
|
|
4772
|
+
if (ended === void 0) {
|
|
4773
|
+
ended = (await this.tryFindThread(thread2))?.thread.state === "closed";
|
|
4774
|
+
closed.set(thread2, ended);
|
|
4775
|
+
}
|
|
4776
|
+
if (ended) {
|
|
4777
|
+
continue;
|
|
4778
|
+
}
|
|
4779
|
+
}
|
|
4923
4780
|
const key = conversationKey(scope, part);
|
|
4924
4781
|
const entry = counts.get(key) ?? {
|
|
4925
4782
|
scope,
|
|
4926
|
-
thread:
|
|
4783
|
+
thread: thread2 ?? null,
|
|
4927
4784
|
channel: channelOfPart(part) ?? null,
|
|
4785
|
+
place: placeOfPart(part) ?? null,
|
|
4928
4786
|
count: 0
|
|
4929
4787
|
};
|
|
4930
4788
|
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
@@ -4982,8 +4840,9 @@ ${DASHBOARD_LINE}
|
|
|
4982
4840
|
* thread it takes part in, and for the front desk, which every post by the user wakes, every post
|
|
4983
4841
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4984
4842
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4985
|
-
* top-level messages; for a
|
|
4986
|
-
*
|
|
4843
|
+
* top-level messages; for a project's in the society scope, the top-level messages of every
|
|
4844
|
+
* channel of a project the member is not in; for a scope's home, general's, since each thread,
|
|
4845
|
+
* each other channel, and each such project is a conversation of its own. Nothing newer than
|
|
4987
4846
|
* `upto` counts (see `digestSnapshot`).
|
|
4988
4847
|
*/
|
|
4989
4848
|
async digestSince(actor, cursors, upto, conversation) {
|
|
@@ -5003,11 +4862,17 @@ ${DASHBOARD_LINE}
|
|
|
5003
4862
|
const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
|
|
5004
4863
|
const threadParticipation = /* @__PURE__ */ new Map();
|
|
5005
4864
|
const openChannels = /* @__PURE__ */ new Map();
|
|
4865
|
+
const liveProjects = /* @__PURE__ */ new Map();
|
|
5006
4866
|
const collected = [];
|
|
5007
4867
|
const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
|
|
5008
4868
|
const scopes = conversation !== void 0 ? [conversation.scope] : [...agent2.memberships, SOCIETY_SCOPE];
|
|
5009
4869
|
const starts = [
|
|
5010
|
-
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
4870
|
+
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
4871
|
+
cursorOf(cursors, each),
|
|
4872
|
+
cursors.digest,
|
|
4873
|
+
channelStart(cursors, each),
|
|
4874
|
+
placeStart(cursors, each)
|
|
4875
|
+
] : [cursorOf(cursors, each, conversation.part)]),
|
|
5011
4876
|
...newsFrom === void 0 ? [] : [newsFrom]
|
|
5012
4877
|
];
|
|
5013
4878
|
const known = starts.filter((start) => start !== null);
|
|
@@ -5017,7 +4882,7 @@ ${DASHBOARD_LINE}
|
|
|
5017
4882
|
const thread2 = message.thread ?? null;
|
|
5018
4883
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
5019
4884
|
if (newsFrom !== void 0 && thread2 === null && filedIn === SOCIETY_SCOPE && !asked) {
|
|
5020
|
-
if ((newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
4885
|
+
if ((conversation === void 0 || conversation.part === null) && (newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
5021
4886
|
collected.push({
|
|
5022
4887
|
scope: conversation?.scope ?? SOCIETY_SCOPE,
|
|
5023
4888
|
part: null,
|
|
@@ -5028,12 +4893,18 @@ ${DASHBOARD_LINE}
|
|
|
5028
4893
|
continue;
|
|
5029
4894
|
}
|
|
5030
4895
|
const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
5031
|
-
let
|
|
5032
|
-
if (named !== void 0 &&
|
|
5033
|
-
|
|
5034
|
-
openChannels.set(message.channel,
|
|
5035
|
-
}
|
|
5036
|
-
const
|
|
4896
|
+
let open4 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
4897
|
+
if (named !== void 0 && open4 === void 0) {
|
|
4898
|
+
open4 = await this.channelOpen(message.channel);
|
|
4899
|
+
openChannels.set(message.channel, open4);
|
|
4900
|
+
}
|
|
4901
|
+
const project2 = thread2 === null ? placeConversation(filedIn, message.channel) : void 0;
|
|
4902
|
+
let live = project2 === void 0 ? void 0 : liveProjects.get(project2);
|
|
4903
|
+
if (project2 !== void 0 && live === void 0) {
|
|
4904
|
+
live = await this.projectLive(project2);
|
|
4905
|
+
liveProjects.set(project2, live);
|
|
4906
|
+
}
|
|
4907
|
+
const part = thread2 ?? (open4 === true ? `#${named}` : live === true ? `~${project2}` : null);
|
|
5037
4908
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
5038
4909
|
continue;
|
|
5039
4910
|
}
|
|
@@ -5098,7 +4969,7 @@ ${task2.body}`;
|
|
|
5098
4969
|
}
|
|
5099
4970
|
}
|
|
5100
4971
|
for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
|
|
5101
|
-
const content = await readFile4(
|
|
4972
|
+
const content = await readFile4(path7.join(this.paths.projectKnowledge(project2), file), "utf8");
|
|
5102
4973
|
if (content.toLowerCase().includes(needle)) {
|
|
5103
4974
|
hits.push({
|
|
5104
4975
|
kind: "knowledge",
|
|
@@ -5119,7 +4990,7 @@ ${task2.body}`;
|
|
|
5119
4990
|
shared.push({
|
|
5120
4991
|
kind: "knowledge",
|
|
5121
4992
|
ref: `society/knowledge/${file}`,
|
|
5122
|
-
file:
|
|
4993
|
+
file: path7.join(this.paths.societyKnowledge(), file)
|
|
5123
4994
|
});
|
|
5124
4995
|
}
|
|
5125
4996
|
for (const skill2 of await this.listSocietySkills()) {
|
|
@@ -5132,7 +5003,7 @@ ${task2.body}`;
|
|
|
5132
5003
|
shared.push({
|
|
5133
5004
|
kind: "memory",
|
|
5134
5005
|
ref: `memory/${file}`,
|
|
5135
|
-
file:
|
|
5006
|
+
file: path7.join(this.paths.agentMemory(actor.name), file)
|
|
5136
5007
|
});
|
|
5137
5008
|
}
|
|
5138
5009
|
for (const entry of shared) {
|
|
@@ -5222,7 +5093,7 @@ ${current.body}`);
|
|
|
5222
5093
|
});
|
|
5223
5094
|
const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
|
|
5224
5095
|
await mkdir4(aside, { recursive: true });
|
|
5225
|
-
await rename3(file,
|
|
5096
|
+
await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
|
|
5226
5097
|
await this.events.append("knowledge.removed", actor.name, {
|
|
5227
5098
|
topic: args.topic,
|
|
5228
5099
|
project: args.project
|
|
@@ -5270,7 +5141,7 @@ ${current.body}`);
|
|
|
5270
5141
|
});
|
|
5271
5142
|
const aside = this.paths.removedSkills();
|
|
5272
5143
|
await mkdir4(aside, { recursive: true });
|
|
5273
|
-
await rename3(this.paths.societySkill(args.name),
|
|
5144
|
+
await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
|
|
5274
5145
|
await this.events.append("skill.removed", actor.name, {
|
|
5275
5146
|
name: args.name,
|
|
5276
5147
|
reason: args.reason
|
|
@@ -5347,7 +5218,7 @@ ${current.body}`);
|
|
|
5347
5218
|
type,
|
|
5348
5219
|
size: bytes.byteLength
|
|
5349
5220
|
});
|
|
5350
|
-
const name =
|
|
5221
|
+
const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
|
|
5351
5222
|
return {
|
|
5352
5223
|
id,
|
|
5353
5224
|
type,
|
|
@@ -5373,7 +5244,7 @@ ${current.body}`);
|
|
|
5373
5244
|
async listCrons() {
|
|
5374
5245
|
const crons = [];
|
|
5375
5246
|
for (const file of await listFiles(this.paths.crons())) {
|
|
5376
|
-
crons.push(await this.readCronFile(
|
|
5247
|
+
crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
|
|
5377
5248
|
}
|
|
5378
5249
|
return crons;
|
|
5379
5250
|
}
|
|
@@ -5495,11 +5366,16 @@ ${current.body}`);
|
|
|
5495
5366
|
}
|
|
5496
5367
|
/**
|
|
5497
5368
|
* Where a cron fires for its citizen: the thread, the channel, or the scope's home it names, else
|
|
5498
|
-
* the conversation of the turn that set it when it wakes its setter,
|
|
5369
|
+
* the conversation of the turn that set it when it wakes its setter, a project's channels' in
|
|
5370
|
+
* the society scope included, else its home outside projects.
|
|
5499
5371
|
*/
|
|
5500
5372
|
async cronConversation(actor, agent2, named) {
|
|
5501
5373
|
const own = actor.name === agent2.name;
|
|
5502
5374
|
if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
|
|
5375
|
+
if (own && actor.scope === SOCIETY_SCOPE && actor.place !== void 0) {
|
|
5376
|
+
await this.readActiveProject(actor.place);
|
|
5377
|
+
return { scope: SOCIETY_SCOPE, place: actor.place };
|
|
5378
|
+
}
|
|
5503
5379
|
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent2, {
|
|
5504
5380
|
project: actor.scope,
|
|
5505
5381
|
thread_id: actor.thread,
|
|
@@ -5575,7 +5451,8 @@ ${current.body}`);
|
|
|
5575
5451
|
agent: cron2.agent,
|
|
5576
5452
|
project: cron2.scope,
|
|
5577
5453
|
...cron2.thread === void 0 ? {} : { thread: cron2.thread },
|
|
5578
|
-
...cron2.channel === void 0 ? {} : { channel: cron2.channel }
|
|
5454
|
+
...cron2.channel === void 0 ? {} : { channel: cron2.channel },
|
|
5455
|
+
...cron2.place === void 0 ? {} : { place: cron2.place }
|
|
5579
5456
|
};
|
|
5580
5457
|
}
|
|
5581
5458
|
async readCronFile(file) {
|
|
@@ -5608,11 +5485,12 @@ ${current.body}`);
|
|
|
5608
5485
|
const args = VerbInputs.open_thread.parse(input);
|
|
5609
5486
|
await this.authorize(actor, "open_thread");
|
|
5610
5487
|
return this.mutex.run(async () => {
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5488
|
+
await this.assertChannelOpen(args.channel);
|
|
5489
|
+
return this.openThreadUnlocked(actor.name, {
|
|
5490
|
+
id: this.newId(),
|
|
5491
|
+
channel: args.channel,
|
|
5492
|
+
title: args.title
|
|
5493
|
+
});
|
|
5616
5494
|
});
|
|
5617
5495
|
}
|
|
5618
5496
|
/** Writes a new open thread and its event. Callers hold the mutex and chose where it hangs. */
|
|
@@ -5647,28 +5525,13 @@ ${current.body}`);
|
|
|
5647
5525
|
subject: { kind: "proposal", id: proposal2.id }
|
|
5648
5526
|
};
|
|
5649
5527
|
}
|
|
5650
|
-
/**
|
|
5651
|
-
|
|
5652
|
-
* threads gets one now, opened as its proposer, and a thread closed before the decision opens
|
|
5653
|
-
* again.
|
|
5654
|
-
*/
|
|
5655
|
-
async proposalThreadUnlocked(by, proposal2) {
|
|
5528
|
+
/** The open thread of a proposal being decided, which `propose` opened with it. */
|
|
5529
|
+
async proposalThreadUnlocked(proposal2) {
|
|
5656
5530
|
const found = await this.tryFindThread(proposal2.id);
|
|
5657
|
-
if (found?.thread.state
|
|
5658
|
-
|
|
5659
|
-
}
|
|
5660
|
-
if (found === null) {
|
|
5661
|
-
return this.openThreadUnlocked(proposal2.proposedBy, this.proposalThreadOpening(proposal2));
|
|
5531
|
+
if (found?.thread.state !== "open") {
|
|
5532
|
+
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} has no open thread`);
|
|
5662
5533
|
}
|
|
5663
|
-
|
|
5664
|
-
const reopened = await this.writeThread(found.threads, { ...kept, state: "open" });
|
|
5665
|
-
await this.events.append("thread.opened", by, {
|
|
5666
|
-
threadId: proposal2.id,
|
|
5667
|
-
channel: reopened.channel,
|
|
5668
|
-
subject: reopened.subject ?? null,
|
|
5669
|
-
reopened: true
|
|
5670
|
-
});
|
|
5671
|
-
return reopened;
|
|
5534
|
+
return found.thread;
|
|
5672
5535
|
}
|
|
5673
5536
|
/** Where a task's thread hangs, what it is called, and what it is about. */
|
|
5674
5537
|
taskThreadOpening(task2) {
|
|
@@ -5680,79 +5543,21 @@ ${current.body}`);
|
|
|
5680
5543
|
};
|
|
5681
5544
|
}
|
|
5682
5545
|
/**
|
|
5683
|
-
* The open thread of a task in play, for a verb about to post into it
|
|
5684
|
-
*
|
|
5685
|
-
* again; an ended task's thread stays closed.
|
|
5546
|
+
* The open thread of a task in play, for a verb about to post into it, which `createTask` opened
|
|
5547
|
+
* with the task; an ended task's thread closed with it.
|
|
5686
5548
|
*/
|
|
5687
|
-
async taskThreadUnlocked(
|
|
5549
|
+
async taskThreadUnlocked(task2) {
|
|
5688
5550
|
const found = await this.tryFindThread(task2.id);
|
|
5689
5551
|
if (found?.thread.state === "open") {
|
|
5690
5552
|
return found.thread;
|
|
5691
5553
|
}
|
|
5692
|
-
|
|
5693
|
-
throw new BoardError("INVALID_STATE", `task ${task2.id} is ${task2.status} and its thread closed with it`);
|
|
5694
|
-
}
|
|
5695
|
-
if (found === null) {
|
|
5696
|
-
return this.openThreadUnlocked(by, this.taskThreadOpening(task2));
|
|
5697
|
-
}
|
|
5698
|
-
const { closedBy: _by, closedAt: _at, ...kept } = found.thread;
|
|
5699
|
-
const reopened = await this.writeThread(found.threads, { ...kept, state: "open" });
|
|
5700
|
-
await this.events.append("thread.opened", by, {
|
|
5701
|
-
threadId: task2.id,
|
|
5702
|
-
channel: reopened.channel,
|
|
5703
|
-
subject: reopened.subject ?? null,
|
|
5704
|
-
reopened: true
|
|
5705
|
-
});
|
|
5706
|
-
return reopened;
|
|
5554
|
+
throw new BoardError("INVALID_STATE", task2.status === "done" || task2.status === "abandoned" ? `task ${task2.id} is ${task2.status} and its thread closed with it` : `task ${task2.id} has no open thread`);
|
|
5707
5555
|
}
|
|
5708
5556
|
/** Posts what a task verb was told to say into the task's thread, as its author, with the step it recorded. */
|
|
5709
5557
|
async postTaskNote(author, task2, body, step) {
|
|
5710
|
-
const thread2 = await this.taskThreadUnlocked(
|
|
5558
|
+
const thread2 = await this.taskThreadUnlocked(task2);
|
|
5711
5559
|
return this.appendThreadMessage(author, thread2.id, body, step === void 0 ? {} : { step });
|
|
5712
5560
|
}
|
|
5713
|
-
/** Where a new thread hangs, what it is called, and what it is about. */
|
|
5714
|
-
async threadToOpen(args) {
|
|
5715
|
-
if (args.task_id !== void 0 && args.proposal_id !== void 0) {
|
|
5716
|
-
throw new BoardError("VALIDATION", "a thread is about one task or one proposal, not both");
|
|
5717
|
-
}
|
|
5718
|
-
if (args.task_id !== void 0) {
|
|
5719
|
-
const { task: task2, project: project2 } = await this.findTask(args.task_id);
|
|
5720
|
-
if (task2.status === "done" || task2.status === "abandoned") {
|
|
5721
|
-
throw new BoardError("INVALID_STATE", `task ${task2.id} is ${task2.status}`);
|
|
5722
|
-
}
|
|
5723
|
-
const channel = args.channel ?? channelRef(project2, task2.channel);
|
|
5724
|
-
if (parseChannelRef(channel).project !== project2) {
|
|
5725
|
-
throw new BoardError("VALIDATION", `a task's thread hangs off a channel of its project, ${project2}`);
|
|
5726
|
-
}
|
|
5727
|
-
await this.assertChannelOpen(channel);
|
|
5728
|
-
return {
|
|
5729
|
-
id: task2.id,
|
|
5730
|
-
channel,
|
|
5731
|
-
title: args.title ?? task2.title,
|
|
5732
|
-
subject: { kind: "task", id: task2.id }
|
|
5733
|
-
};
|
|
5734
|
-
}
|
|
5735
|
-
if (args.proposal_id !== void 0) {
|
|
5736
|
-
const proposal2 = await this.readProposal(args.proposal_id);
|
|
5737
|
-
if (proposal2.status !== "proposed") {
|
|
5738
|
-
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} is already ${proposal2.status}`);
|
|
5739
|
-
}
|
|
5740
|
-
const channel = args.channel ?? "governance";
|
|
5741
|
-
await this.assertChannelOpen(channel);
|
|
5742
|
-
const described = `${proposal2.kind} proposal: ${describeCharter(proposal2.kind, proposal2.charter)}`;
|
|
5743
|
-
return {
|
|
5744
|
-
id: proposal2.id,
|
|
5745
|
-
channel,
|
|
5746
|
-
title: args.title ?? described.slice(0, 200),
|
|
5747
|
-
subject: { kind: "proposal", id: proposal2.id }
|
|
5748
|
-
};
|
|
5749
|
-
}
|
|
5750
|
-
if (args.channel === void 0 || args.title === void 0) {
|
|
5751
|
-
throw new BoardError("VALIDATION", "a thread needs a task_id, a proposal_id, or a channel and a title");
|
|
5752
|
-
}
|
|
5753
|
-
await this.assertChannelOpen(args.channel);
|
|
5754
|
-
return { id: this.newId(), channel: args.channel, title: args.title };
|
|
5755
|
-
}
|
|
5756
5561
|
/**
|
|
5757
5562
|
* Where the board learns which turns are in flight, from the server that hosts them, so it
|
|
5758
5563
|
* refuses to end a conversation under someone else's turn.
|
|
@@ -5786,17 +5591,20 @@ ${current.body}`);
|
|
|
5786
5591
|
async openThreadIds(dir, keep) {
|
|
5787
5592
|
const ids = /* @__PURE__ */ new Set();
|
|
5788
5593
|
for (const file of await listFiles(dir)) {
|
|
5789
|
-
const doc = await readMarkdown(
|
|
5594
|
+
const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
|
|
5790
5595
|
if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
|
|
5791
5596
|
ids.add(doc.data.id);
|
|
5792
5597
|
}
|
|
5793
5598
|
}
|
|
5794
5599
|
return ids;
|
|
5795
5600
|
}
|
|
5796
|
-
/**
|
|
5601
|
+
/**
|
|
5602
|
+
* Refuses to archive a project while a turn runs in it, in a citizen's conversation of its
|
|
5603
|
+
* channels in the society scope, or in a thread its archive closes.
|
|
5604
|
+
*/
|
|
5797
5605
|
async assertProjectQuiet(actor, slug) {
|
|
5798
5606
|
const threads = await this.openThreadIds(this.paths.threads(slug), () => true);
|
|
5799
|
-
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
|
|
5607
|
+
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.place === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
|
|
5800
5608
|
}
|
|
5801
5609
|
async closeThread(actor, input) {
|
|
5802
5610
|
const args = VerbInputs.close_thread.parse(input);
|
|
@@ -6337,7 +6145,8 @@ ${args.summary}`);
|
|
|
6337
6145
|
filedFrom: {
|
|
6338
6146
|
scope: actor.scope,
|
|
6339
6147
|
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
6340
|
-
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
6148
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel },
|
|
6149
|
+
...actor.place === void 0 ? {} : { place: actor.place }
|
|
6341
6150
|
}
|
|
6342
6151
|
},
|
|
6343
6152
|
charter: parsed.data
|
|
@@ -6749,19 +6558,20 @@ ${rationale}`}`);
|
|
|
6749
6558
|
project: input.project,
|
|
6750
6559
|
...input.thread === void 0 ? {} : { thread: input.thread },
|
|
6751
6560
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
6561
|
+
...input.place === void 0 ? {} : { place: input.place },
|
|
6752
6562
|
messages: [...input.messages]
|
|
6753
6563
|
});
|
|
6754
6564
|
});
|
|
6755
6565
|
}
|
|
6756
6566
|
/**
|
|
6757
6567
|
* Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
|
|
6758
|
-
* thread's or a
|
|
6568
|
+
* thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
|
|
6759
6569
|
*/
|
|
6760
6570
|
sessionDir(agent2, scope, part) {
|
|
6761
6571
|
return part === void 0 ? this.paths.agentProject(agent2, scope) : this.paths.agentConversation(agent2, scope, part);
|
|
6762
6572
|
}
|
|
6763
6573
|
async readSessions(agent2, project2, part) {
|
|
6764
|
-
const file =
|
|
6574
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
6765
6575
|
return await exists(file) ? readJson(file, SessionsFileSchema) : {};
|
|
6766
6576
|
}
|
|
6767
6577
|
/**
|
|
@@ -6772,15 +6582,34 @@ ${rationale}`}`);
|
|
|
6772
6582
|
await this.mutex.run(async () => {
|
|
6773
6583
|
const dir = this.sessionDir(agent2, project2, part);
|
|
6774
6584
|
await ensureDir(dir);
|
|
6775
|
-
const file =
|
|
6585
|
+
const file = path7.join(dir, "sessions.json");
|
|
6776
6586
|
const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
|
|
6777
6587
|
const kept = current.runner === void 0 || current.runner === runner2 ? current : {};
|
|
6778
6588
|
await writeJson(file, { ...kept, [cli]: sessionId, runner: runner2 });
|
|
6779
6589
|
});
|
|
6780
6590
|
}
|
|
6781
|
-
/**
|
|
6591
|
+
/**
|
|
6592
|
+
* Drops a conversation's session for a CLI, so its next turn starts a new one, when the record
|
|
6593
|
+
* still names `sessionId`; returns whether it did.
|
|
6594
|
+
*/
|
|
6595
|
+
async dropSession(agent2, project2, cli, sessionId, part) {
|
|
6596
|
+
return this.mutex.run(async () => {
|
|
6597
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
6598
|
+
if (!await exists(file)) {
|
|
6599
|
+
return false;
|
|
6600
|
+
}
|
|
6601
|
+
const current = await readJson(file, SessionsFileSchema);
|
|
6602
|
+
if (current[cli] !== sessionId) {
|
|
6603
|
+
return false;
|
|
6604
|
+
}
|
|
6605
|
+
const { [cli]: _dropped, ...kept } = current;
|
|
6606
|
+
await writeJson(file, kept);
|
|
6607
|
+
return true;
|
|
6608
|
+
});
|
|
6609
|
+
}
|
|
6610
|
+
/** The last turn of one conversation: the scope's home, a channel's, a project's, or a thread's. */
|
|
6782
6611
|
async readLastTurn(agent2, project2, part) {
|
|
6783
|
-
const file =
|
|
6612
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "last-turn.json");
|
|
6784
6613
|
return await exists(file) ? readJson(file, TurnRecordSchema) : null;
|
|
6785
6614
|
}
|
|
6786
6615
|
/** The latest turn a citizen took in a scope, in any of its conversations there. */
|
|
@@ -6788,8 +6617,9 @@ ${rationale}`}`);
|
|
|
6788
6617
|
let latest = await this.readLastTurn(agent2, project2);
|
|
6789
6618
|
const place = this.paths.agentProject(agent2, project2);
|
|
6790
6619
|
const parts = [
|
|
6791
|
-
...await listDirs(
|
|
6792
|
-
...(await listDirs(
|
|
6620
|
+
...await listDirs(path7.join(place, "threads")),
|
|
6621
|
+
...(await listDirs(path7.join(place, "channels"))).map((name) => `#${name}`),
|
|
6622
|
+
...(await listDirs(path7.join(place, "places"))).map((name) => `~${name}`)
|
|
6793
6623
|
];
|
|
6794
6624
|
for (const part of parts) {
|
|
6795
6625
|
const turn2 = await this.readLastTurn(agent2, project2, part);
|
|
@@ -6810,6 +6640,7 @@ ${rationale}`}`);
|
|
|
6810
6640
|
project: parsed.project,
|
|
6811
6641
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6812
6642
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
6643
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6813
6644
|
trigger: parsed.trigger.kind,
|
|
6814
6645
|
session: parsed.session,
|
|
6815
6646
|
runner: parsed.runner
|
|
@@ -6839,7 +6670,8 @@ ${rationale}`}`);
|
|
|
6839
6670
|
agent: parsed.agent,
|
|
6840
6671
|
project: parsed.project,
|
|
6841
6672
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6842
|
-
...parsed.channel === void 0 ? {} : { channel: parsed.channel }
|
|
6673
|
+
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
6674
|
+
...parsed.place === void 0 ? {} : { place: parsed.place }
|
|
6843
6675
|
});
|
|
6844
6676
|
}
|
|
6845
6677
|
await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
|
|
@@ -6847,6 +6679,7 @@ ${rationale}`}`);
|
|
|
6847
6679
|
project: parsed.project,
|
|
6848
6680
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6849
6681
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
6682
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6850
6683
|
trigger: parsed.trigger.kind,
|
|
6851
6684
|
session: parsed.session,
|
|
6852
6685
|
exitReason: parsed.exitReason,
|
|
@@ -6856,7 +6689,10 @@ ${rationale}`}`);
|
|
|
6856
6689
|
model: parsed.model,
|
|
6857
6690
|
summary: parsed.status?.summary ?? null,
|
|
6858
6691
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
6859
|
-
error: parsed.error
|
|
6692
|
+
error: parsed.error,
|
|
6693
|
+
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
6694
|
+
...parsed.transient === true ? { transient: true } : {},
|
|
6695
|
+
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
6860
6696
|
});
|
|
6861
6697
|
});
|
|
6862
6698
|
}
|
|
@@ -6893,6 +6729,20 @@ ${rationale}`}`);
|
|
|
6893
6729
|
return task2;
|
|
6894
6730
|
});
|
|
6895
6731
|
}
|
|
6732
|
+
/**
|
|
6733
|
+
* Posts the board's word on a task's work into the task's thread, such as commits a runner
|
|
6734
|
+
* could not put on its branch: as the board, with every `@name` written as a plain name, so it
|
|
6735
|
+
* wakes nobody and counts toward no heartbeat. Null, posting nothing, once the thread is closed.
|
|
6736
|
+
*/
|
|
6737
|
+
async postTaskNotice(taskId, body) {
|
|
6738
|
+
return this.mutex.run(async () => {
|
|
6739
|
+
const found = await this.tryFindThread(taskId);
|
|
6740
|
+
if (found === null || found.thread.state !== "open") {
|
|
6741
|
+
return null;
|
|
6742
|
+
}
|
|
6743
|
+
return this.appendThreadMessage(SYSTEM_ACTOR.name, taskId, withoutMentions(body));
|
|
6744
|
+
});
|
|
6745
|
+
}
|
|
6896
6746
|
/**
|
|
6897
6747
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
6898
6748
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -6903,7 +6753,7 @@ ${rationale}`}`);
|
|
|
6903
6753
|
const payload = {
|
|
6904
6754
|
agent: input.agent,
|
|
6905
6755
|
project: input.scope,
|
|
6906
|
-
...conversation.kind === "thread" ? { thread: conversation.id } : { channel: conversation.name }
|
|
6756
|
+
...conversation.kind === "thread" ? { thread: conversation.id } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
|
|
6907
6757
|
};
|
|
6908
6758
|
return this.mutex.run(async () => {
|
|
6909
6759
|
const earlier = (await this.events.readSince(null, Number.MAX_SAFE_INTEGER)).filter((event) => event.type === "workspace.leftovers" && Object.entries(payload).every(([key, value]) => event.payload[key] === value)).length;
|
|
@@ -6955,9 +6805,13 @@ ${rationale}`}`);
|
|
|
6955
6805
|
}
|
|
6956
6806
|
/**
|
|
6957
6807
|
* Whether a conversation with a workspace of its own has ended: a thread closed, a task ended
|
|
6958
|
-
* (for one from before tasks had threads), or a
|
|
6808
|
+
* (for one from before tasks had threads), a channel archived, or a project archived, which ends
|
|
6809
|
+
* every citizen's conversation of its channels in the society scope.
|
|
6959
6810
|
*/
|
|
6960
6811
|
async conversationEnded(conversation) {
|
|
6812
|
+
if (conversation.kind === "place") {
|
|
6813
|
+
return !await this.projectLive(conversation.project);
|
|
6814
|
+
}
|
|
6961
6815
|
if (conversation.kind === "channel") {
|
|
6962
6816
|
const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
|
|
6963
6817
|
return !await this.channelOpen(channelRef(place, conversation.name));
|
|
@@ -7031,11 +6885,11 @@ ${rationale}`}`);
|
|
|
7031
6885
|
}
|
|
7032
6886
|
/** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
|
|
7033
6887
|
async readState(name, schema, fallback) {
|
|
7034
|
-
const file =
|
|
6888
|
+
const file = path7.join(this.paths.state(), `${name}.json`);
|
|
7035
6889
|
return await exists(file) ? readJson(file, schema) : fallback;
|
|
7036
6890
|
}
|
|
7037
6891
|
async writeState(name, value) {
|
|
7038
|
-
await this.mutex.run(() => writeJson(
|
|
6892
|
+
await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
|
|
7039
6893
|
}
|
|
7040
6894
|
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
7041
6895
|
async readDashboard(slug) {
|
|
@@ -7066,7 +6920,7 @@ ${rationale}`}`);
|
|
|
7066
6920
|
async listRunners() {
|
|
7067
6921
|
const runners = [];
|
|
7068
6922
|
for (const file of await listFiles(this.paths.runners())) {
|
|
7069
|
-
runners.push((await readMarkdown(
|
|
6923
|
+
runners.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
|
|
7070
6924
|
}
|
|
7071
6925
|
return runners;
|
|
7072
6926
|
}
|
|
@@ -7118,7 +6972,7 @@ ${rationale}`}`);
|
|
|
7118
6972
|
return this.runnerTokens.get(hashToken(token)) ?? null;
|
|
7119
6973
|
}
|
|
7120
6974
|
runnerTokensFile() {
|
|
7121
|
-
return
|
|
6975
|
+
return path7.join(this.paths.state(), "runners.json");
|
|
7122
6976
|
}
|
|
7123
6977
|
async readRunnerTokens() {
|
|
7124
6978
|
const file = this.runnerTokensFile();
|
|
@@ -7167,26 +7021,26 @@ ${rationale}`}`);
|
|
|
7167
7021
|
async listHomeConflicts(agent2) {
|
|
7168
7022
|
const home = this.paths.agent(agent2);
|
|
7169
7023
|
const found = [];
|
|
7170
|
-
const
|
|
7171
|
-
for (const entry of await
|
|
7024
|
+
const walk2 = async (dir, prefix) => {
|
|
7025
|
+
for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
|
|
7172
7026
|
if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
|
|
7173
7027
|
continue;
|
|
7174
7028
|
}
|
|
7175
7029
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
7176
7030
|
if (entry.isDirectory()) {
|
|
7177
|
-
await
|
|
7031
|
+
await walk2(path7.join(dir, entry.name), relative);
|
|
7178
7032
|
continue;
|
|
7179
7033
|
}
|
|
7180
7034
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
7181
7035
|
if (file !== null) {
|
|
7182
|
-
const since = await this.homes.changedAt(home, relative) ?? (await stat3(
|
|
7036
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path7.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
7183
7037
|
if (since !== void 0) {
|
|
7184
7038
|
found.push({ path: relative, file, since });
|
|
7185
7039
|
}
|
|
7186
7040
|
}
|
|
7187
7041
|
}
|
|
7188
7042
|
};
|
|
7189
|
-
await
|
|
7043
|
+
await walk2(home, "");
|
|
7190
7044
|
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
7191
7045
|
}
|
|
7192
7046
|
/** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
|
|
@@ -7263,7 +7117,7 @@ ${rationale}`}`);
|
|
|
7263
7117
|
async writeTurnRecord(record) {
|
|
7264
7118
|
const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
|
|
7265
7119
|
await ensureDir(dir);
|
|
7266
|
-
await writeJson(
|
|
7120
|
+
await writeJson(path7.join(dir, "last-turn.json"), record);
|
|
7267
7121
|
}
|
|
7268
7122
|
// ---------------------------------------------------------------------------------------------
|
|
7269
7123
|
// Internals
|
|
@@ -7382,8 +7236,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7382
7236
|
async ensureAgentProject(name, slug) {
|
|
7383
7237
|
const dir = this.paths.agentProject(name, slug);
|
|
7384
7238
|
await ensureDir(dir);
|
|
7385
|
-
if (!await exists(
|
|
7386
|
-
await writeJson(
|
|
7239
|
+
if (!await exists(path7.join(dir, "sessions.json"))) {
|
|
7240
|
+
await writeJson(path7.join(dir, "sessions.json"), {});
|
|
7387
7241
|
}
|
|
7388
7242
|
}
|
|
7389
7243
|
/**
|
|
@@ -7415,7 +7269,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7415
7269
|
const at = turn2?.endedAt ?? turn2?.startedAt;
|
|
7416
7270
|
if (turn2 !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
|
|
7417
7271
|
lastTurnAt = at;
|
|
7418
|
-
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7272
|
+
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}${turnConversation(turn2)}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7419
7273
|
lastModel = turn2.model ?? void 0;
|
|
7420
7274
|
}
|
|
7421
7275
|
}
|
|
@@ -7545,15 +7399,9 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7545
7399
|
case "archive":
|
|
7546
7400
|
await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
|
|
7547
7401
|
return;
|
|
7548
|
-
case "reallocation":
|
|
7549
|
-
|
|
7550
|
-
if (parsed.success) {
|
|
7551
|
-
await this.reallocatedRole(parsed.data);
|
|
7552
|
-
} else {
|
|
7553
|
-
LegacyReallocationSchema.parse(charter);
|
|
7554
|
-
}
|
|
7402
|
+
case "reallocation":
|
|
7403
|
+
await this.reallocatedRole(ReallocationProposalSchema.parse(charter));
|
|
7555
7404
|
return;
|
|
7556
|
-
}
|
|
7557
7405
|
default:
|
|
7558
7406
|
return;
|
|
7559
7407
|
}
|
|
@@ -7613,11 +7461,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7613
7461
|
return { project: input.project, members };
|
|
7614
7462
|
}
|
|
7615
7463
|
case "reallocation": {
|
|
7616
|
-
const
|
|
7617
|
-
if (!parsed.success) {
|
|
7618
|
-
return void 0;
|
|
7619
|
-
}
|
|
7620
|
-
const { next, changed } = await this.reallocatedRole(parsed.data);
|
|
7464
|
+
const { next, changed } = await this.reallocatedRole(ReallocationProposalSchema.parse(proposal2.charter));
|
|
7621
7465
|
await this.writeRoleUnlocked(by, next, meta);
|
|
7622
7466
|
return { role: next.name, changed };
|
|
7623
7467
|
}
|
|
@@ -7627,25 +7471,21 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7627
7471
|
}
|
|
7628
7472
|
/**
|
|
7629
7473
|
* Publishes a promoted skill under the society's skills, where every citizen's skills index lists
|
|
7630
|
-
* it: the folder the board took from its proposer's home
|
|
7631
|
-
*
|
|
7474
|
+
* it: the folder the board took from its proposer's home. A replacement is the whole skill, so
|
|
7475
|
+
* files the new version dropped go with the old one.
|
|
7632
7476
|
*/
|
|
7633
7477
|
async promoteSkillUnlocked(by, proposal2, skill2, meta) {
|
|
7634
7478
|
const dir = this.paths.societySkill(skill2.name);
|
|
7635
7479
|
const file = this.paths.societySkillFile(skill2.name);
|
|
7636
7480
|
const replaced = await exists(file);
|
|
7637
7481
|
const taken = proposal2.skillFiles;
|
|
7638
|
-
const staged =
|
|
7639
|
-
if (
|
|
7482
|
+
const staged = path7.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
|
|
7483
|
+
if (taken === void 0 || !await exists(staged)) {
|
|
7640
7484
|
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
|
|
7641
7485
|
}
|
|
7642
7486
|
await rm2(dir, { recursive: true, force: true });
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
} else {
|
|
7646
|
-
await ensureDir(dir);
|
|
7647
|
-
}
|
|
7648
|
-
const body = skill2.body ?? (await readLooseMarkdown(file)).body;
|
|
7487
|
+
await cp(this.paths.proposalFiles(proposal2.id), dir, { recursive: true });
|
|
7488
|
+
const { body } = await readLooseMarkdown(file);
|
|
7649
7489
|
await writeMarkdown(file, {
|
|
7650
7490
|
name: skill2.name,
|
|
7651
7491
|
description: skill2.summary,
|
|
@@ -7653,7 +7493,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7653
7493
|
proposedBy: proposal2.proposedBy,
|
|
7654
7494
|
promotedBy: by,
|
|
7655
7495
|
promotedAt: this.now().toISOString(),
|
|
7656
|
-
|
|
7496
|
+
commit: taken.commit
|
|
7657
7497
|
}
|
|
7658
7498
|
}, body);
|
|
7659
7499
|
await this.events.append("skill.promoted", by, {
|
|
@@ -7661,7 +7501,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7661
7501
|
summary: skill2.summary,
|
|
7662
7502
|
proposedBy: proposal2.proposedBy,
|
|
7663
7503
|
replaced,
|
|
7664
|
-
|
|
7504
|
+
files: taken.files.length,
|
|
7665
7505
|
...meta
|
|
7666
7506
|
});
|
|
7667
7507
|
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${sentence(skill2.summary)}. Every citizen's skills index now lists it.`);
|
|
@@ -7698,8 +7538,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7698
7538
|
const dir = this.paths.proposalFiles(proposal2.id);
|
|
7699
7539
|
await rm2(dir, { recursive: true, force: true });
|
|
7700
7540
|
for (const entry of taken.files) {
|
|
7701
|
-
const target =
|
|
7702
|
-
await ensureDir(
|
|
7541
|
+
const target = path7.join(dir, ...entry.path.split("/"));
|
|
7542
|
+
await ensureDir(path7.dirname(target));
|
|
7703
7543
|
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7704
7544
|
await chmod2(target, entry.executable ? 493 : 420);
|
|
7705
7545
|
}
|
|
@@ -7757,7 +7597,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7757
7597
|
const where = {
|
|
7758
7598
|
scope: turn2.project,
|
|
7759
7599
|
...turn2.thread === void 0 ? {} : { thread: turn2.thread },
|
|
7760
|
-
...turn2.channel === void 0 ? {} : { channel: turn2.channel }
|
|
7600
|
+
...turn2.channel === void 0 ? {} : { channel: turn2.channel },
|
|
7601
|
+
...turn2.place === void 0 ? {} : { place: turn2.place }
|
|
7761
7602
|
};
|
|
7762
7603
|
await this.mutex.run(async () => {
|
|
7763
7604
|
for (const proposal2 of await this.listProposals()) {
|
|
@@ -7789,16 +7630,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7789
7630
|
if (!isSafeRelativePath(entry.path)) {
|
|
7790
7631
|
continue;
|
|
7791
7632
|
}
|
|
7792
|
-
const bytes = await readFile4(
|
|
7793
|
-
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
7633
|
+
const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7794
7634
|
files.push({
|
|
7795
7635
|
...entry,
|
|
7796
|
-
|
|
7797
|
-
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
7636
|
+
...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
|
|
7798
7637
|
});
|
|
7799
7638
|
}
|
|
7800
7639
|
return files;
|
|
7801
7640
|
}
|
|
7641
|
+
/**
|
|
7642
|
+
* A society skill's folder as it is published, with each file's text, for the skill's view; an
|
|
7643
|
+
* unknown skill, or a folder without a SKILL.md, is not found.
|
|
7644
|
+
*/
|
|
7645
|
+
async readSocietySkillFiles(name) {
|
|
7646
|
+
const files = await readSkillFolder(this.paths.societySkill(NameSchema.parse(name)));
|
|
7647
|
+
if (files === null) {
|
|
7648
|
+
throw new BoardError("NOT_FOUND", `the society has no skill ${name}`);
|
|
7649
|
+
}
|
|
7650
|
+
return files;
|
|
7651
|
+
}
|
|
7802
7652
|
async validateAddAgent(input) {
|
|
7803
7653
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
7804
7654
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -7929,7 +7779,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7929
7779
|
this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn2) => turn2.channel === channel && turn2.scope === scope || turn2.thread !== void 0 && closing.has(turn2.thread));
|
|
7930
7780
|
const threadsClosed = [];
|
|
7931
7781
|
for (const file of await listFiles(threads)) {
|
|
7932
|
-
const doc = await readMarkdown(
|
|
7782
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
7933
7783
|
if (doc.data.channel !== ref || doc.data.state !== "open") {
|
|
7934
7784
|
continue;
|
|
7935
7785
|
}
|
|
@@ -8035,7 +7885,8 @@ ${charter.purpose}
|
|
|
8035
7885
|
}
|
|
8036
7886
|
}
|
|
8037
7887
|
/**
|
|
8038
|
-
* Every member leaves, every open thread on its channels closes,
|
|
7888
|
+
* Every member leaves, every open thread on its channels closes, every citizen's conversation of
|
|
7889
|
+
* its channels in the society scope ends with the crons there, and the project is marked; its
|
|
8039
7890
|
* files, repository, and history stay. The notice is the board's, since the reason is free text
|
|
8040
7891
|
* a proposer wrote and a board-authored mention would wake whoever it names.
|
|
8041
7892
|
*/
|
|
@@ -8060,7 +7911,7 @@ ${charter.purpose}
|
|
|
8060
7911
|
const threads = this.paths.threads(slug);
|
|
8061
7912
|
const threadsClosed = [];
|
|
8062
7913
|
for (const file of await listFiles(threads)) {
|
|
8063
|
-
const doc = await readMarkdown(
|
|
7914
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
8064
7915
|
if (doc.data.state !== "open") {
|
|
8065
7916
|
continue;
|
|
8066
7917
|
}
|
|
@@ -8078,7 +7929,7 @@ ${charter.purpose}
|
|
|
8078
7929
|
});
|
|
8079
7930
|
threadsClosed.push(doc.data.id);
|
|
8080
7931
|
}
|
|
8081
|
-
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
7932
|
+
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.place === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
8082
7933
|
const project2 = await this.updateProject(slug, (p) => ({
|
|
8083
7934
|
...p,
|
|
8084
7935
|
members: [],
|
|
@@ -8135,10 +7986,10 @@ ${charter.purpose}
|
|
|
8135
7986
|
...project2,
|
|
8136
7987
|
members: project2.members.filter((member) => member !== name)
|
|
8137
7988
|
}));
|
|
8138
|
-
const sessions =
|
|
7989
|
+
const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
|
|
8139
7990
|
if (await exists(sessions)) {
|
|
8140
7991
|
const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
|
|
8141
|
-
await rename3(sessions,
|
|
7992
|
+
await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
|
|
8142
7993
|
}
|
|
8143
7994
|
}
|
|
8144
7995
|
this.tokenIndex.delete(current.tokenHash);
|
|
@@ -8218,6 +8069,17 @@ ${charter.purpose}
|
|
|
8218
8069
|
return false;
|
|
8219
8070
|
}
|
|
8220
8071
|
}
|
|
8072
|
+
/**
|
|
8073
|
+
* Whether a project exists and is not archived, so citizens' conversations of its channels in
|
|
8074
|
+
* the society scope are still going.
|
|
8075
|
+
*/
|
|
8076
|
+
async projectLive(slug) {
|
|
8077
|
+
try {
|
|
8078
|
+
return (await this.readProject(slug)).archived === void 0;
|
|
8079
|
+
} catch {
|
|
8080
|
+
return false;
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
8221
8083
|
/**
|
|
8222
8084
|
* A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
|
|
8223
8085
|
* and its project is not archived.
|
|
@@ -8328,6 +8190,7 @@ ${charter.purpose}
|
|
|
8328
8190
|
await this.validateProvision(filed.kind, filed.charter);
|
|
8329
8191
|
}
|
|
8330
8192
|
this.assertNoTurnCut(actor, `proposal ${proposalId} cannot be decided`, (turn2) => turn2.thread === proposalId);
|
|
8193
|
+
const thread2 = await this.proposalThreadUnlocked(filed);
|
|
8331
8194
|
if (outcome === "approved" && filed.kind === "archive") {
|
|
8332
8195
|
await this.assertProjectQuiet(actor, ArchiveProposalSchema.parse(filed.charter).project);
|
|
8333
8196
|
}
|
|
@@ -8369,7 +8232,6 @@ ${charter.purpose}
|
|
|
8369
8232
|
const verdict = outcome === "approved" ? "Approved" : "Rejected";
|
|
8370
8233
|
const why = reason === void 0 ? "" : ` Reason: ${reason}.`;
|
|
8371
8234
|
const result = provision === void 0 ? "" : ` Provisioned: ${Object.entries(provision).map(([key, value]) => `${key} ${plain(value)}`).join(", ")}.`;
|
|
8372
|
-
const thread2 = await this.proposalThreadUnlocked(actor.name, proposal2);
|
|
8373
8235
|
const post = `${verdict}: ${describeCharter(proposal2.kind, proposal2.charter)}.${why}${result}`;
|
|
8374
8236
|
await this.appendThreadMessage(actor.name, thread2.id, actor.name === SYSTEM_ACTOR.name ? withoutMentions(post) : post, { step: { action: outcome } });
|
|
8375
8237
|
await this.endThreadOf(proposalId, actor.name, outcome);
|
|
@@ -8468,7 +8330,7 @@ ${charter.purpose}
|
|
|
8468
8330
|
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8469
8331
|
continue;
|
|
8470
8332
|
}
|
|
8471
|
-
const doc = await readMarkdown(
|
|
8333
|
+
const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
8472
8334
|
messages.push({ ...doc.data, body: doc.body });
|
|
8473
8335
|
}
|
|
8474
8336
|
return messages;
|
|
@@ -8525,7 +8387,7 @@ function globals() {
|
|
|
8525
8387
|
function print(value, human) {
|
|
8526
8388
|
console.log(globals().json ? JSON.stringify(value, null, 2) : human());
|
|
8527
8389
|
}
|
|
8528
|
-
async function
|
|
8390
|
+
async function open3() {
|
|
8529
8391
|
return Board.open(globals().data);
|
|
8530
8392
|
}
|
|
8531
8393
|
async function actorFor(board, as) {
|
|
@@ -8571,7 +8433,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8571
8433
|
if (!opts.rotate) {
|
|
8572
8434
|
throw new BoardError("VALIDATION", "the board keeps only a hash of the user token, so it cannot show it again; `user token --rotate` issues a new one");
|
|
8573
8435
|
}
|
|
8574
|
-
const board = await
|
|
8436
|
+
const board = await open3();
|
|
8575
8437
|
const userToken = await board.rotateUserToken();
|
|
8576
8438
|
print({ userToken }, () => [
|
|
8577
8439
|
`User token (shown once): ${userToken}`,
|
|
@@ -8580,7 +8442,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8580
8442
|
});
|
|
8581
8443
|
var project = program.command("project").description("Manage projects");
|
|
8582
8444
|
project.command("add <slug>").description("Add a project with its default channels").option("--name <name>", "display name").option("--repo <url>", "git remote").option("--on-done <effect>", "what finishing a task does: none, or merge its branch").action(async (slug, opts) => {
|
|
8583
|
-
const board = await
|
|
8445
|
+
const board = await open3();
|
|
8584
8446
|
const added = await board.addProject(board.userActor(), {
|
|
8585
8447
|
slug,
|
|
8586
8448
|
name: opts.name,
|
|
@@ -8590,7 +8452,7 @@ project.command("add <slug>").description("Add a project with its default channe
|
|
|
8590
8452
|
print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
|
|
8591
8453
|
});
|
|
8592
8454
|
project.command("configure <slug>").description("Set a project's completion effect").requiredOption("--on-done <effect>", "none, or merge").option("--as <agent>", "act as this agent instead of the user").action(async (slug, opts) => {
|
|
8593
|
-
const board = await
|
|
8455
|
+
const board = await open3();
|
|
8594
8456
|
const configured = await board.configureProject(await actorFor(board, opts.as), {
|
|
8595
8457
|
project: slug,
|
|
8596
8458
|
on_done: CompletionEffectSchema.parse(opts.onDone)
|
|
@@ -8598,7 +8460,7 @@ project.command("configure <slug>").description("Set a project's completion effe
|
|
|
8598
8460
|
print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
|
|
8599
8461
|
});
|
|
8600
8462
|
project.command("archive <slug>").description("Archive a project with no task in play: its members leave and its threads close").requiredOption("--reason <text>", "why the project is archived").action(async (slug, opts) => {
|
|
8601
|
-
const board = await
|
|
8463
|
+
const board = await open3();
|
|
8602
8464
|
const archived = await board.archiveProject(board.userActor(), {
|
|
8603
8465
|
project: slug,
|
|
8604
8466
|
reason: opts.reason
|
|
@@ -8606,13 +8468,13 @@ project.command("archive <slug>").description("Archive a project with no task in
|
|
|
8606
8468
|
print(archived, () => `Project ${archived.slug} archived`);
|
|
8607
8469
|
});
|
|
8608
8470
|
project.command("list").description("List projects").action(async () => {
|
|
8609
|
-
const board = await
|
|
8471
|
+
const board = await open3();
|
|
8610
8472
|
const projects = await board.listProjects();
|
|
8611
8473
|
print(projects, () => projects.map((p) => p.archived === void 0 ? `${p.slug} ${p.name} members: ${p.members.join(", ") || "none"}` : `${p.slug} ${p.name} archived ${p.archived.at} by ${p.archived.by}`).join("\n"));
|
|
8612
8474
|
});
|
|
8613
8475
|
var runner = program.command("runner").description("Manage runners");
|
|
8614
8476
|
runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
|
|
8615
|
-
const board = await
|
|
8477
|
+
const board = await open3();
|
|
8616
8478
|
const { runner: added, token } = await board.addRunner(board.userActor(), name);
|
|
8617
8479
|
print({ runner: added, token }, () => [
|
|
8618
8480
|
`Runner ${added.name} added`,
|
|
@@ -8621,13 +8483,13 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
8621
8483
|
].join("\n"));
|
|
8622
8484
|
});
|
|
8623
8485
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
8624
|
-
const board = await
|
|
8486
|
+
const board = await open3();
|
|
8625
8487
|
const runners = await board.listRunners();
|
|
8626
8488
|
print(runners, () => runners.length === 0 ? "No runners yet; add one with `runner add <name>`." : runners.map((each) => `${each.name} ${each.status} ${each.os}${each.version === void 0 ? "" : ` v${each.version}`} ${each.clis.join(",") || "no CLIs yet"}${each.capabilities.length === 0 ? "" : ` ${each.capabilities.join(",")}`}`).join("\n"));
|
|
8627
8489
|
});
|
|
8628
8490
|
var agent = program.command("agent").description("Manage agents");
|
|
8629
8491
|
agent.command("add <name>").description("Add an agent and print its board token once").requiredOption("--role <role>", "role charter name").option("--cli <cli>", "claude or codex").option("--model <model>", "model to run the CLI with; the CLI's own default otherwise").option("--runner <name>", "runner preferred for its turns; any with its CLI otherwise").option("-p, --project <slug...>", "project memberships").action(async (name, opts) => {
|
|
8630
|
-
const board = await
|
|
8492
|
+
const board = await open3();
|
|
8631
8493
|
const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
|
|
8632
8494
|
const { agent: added, token } = await board.addAgent(board.userActor(), {
|
|
8633
8495
|
name,
|
|
@@ -8640,7 +8502,7 @@ agent.command("add <name>").description("Add an agent and print its board token
|
|
|
8640
8502
|
print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
|
|
8641
8503
|
});
|
|
8642
8504
|
agent.command("runner <name> [runner]").description("Show or set the runner a citizen's work outside any project runs on; `none` pins it again on its next turn").action(async (name, chosen) => {
|
|
8643
|
-
const board = await
|
|
8505
|
+
const board = await open3();
|
|
8644
8506
|
if (chosen === void 0) {
|
|
8645
8507
|
const current = await board.readAgent(name);
|
|
8646
8508
|
print({ agent: name, homeRunner: current.homeRunner ?? null }, () => current.homeRunner === void 0 ? `${name} is not pinned; its next turn outside a project pins it` : `${name} runs on ${current.homeRunner}`);
|
|
@@ -8650,7 +8512,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
|
|
|
8650
8512
|
print({ agent: name, homeRunner: updated.homeRunner ?? null }, () => updated.homeRunner === void 0 ? `${name} will be pinned on its next turn` : `${name} runs on ${updated.homeRunner} from its next turn`);
|
|
8651
8513
|
});
|
|
8652
8514
|
agent.command("list").description("List agents").action(async () => {
|
|
8653
|
-
const board = await
|
|
8515
|
+
const board = await open3();
|
|
8654
8516
|
const agents = await board.listAgents();
|
|
8655
8517
|
const members = await board.listMembers();
|
|
8656
8518
|
print(agents, () => agents.map((a) => {
|
|
@@ -8659,28 +8521,28 @@ agent.command("list").description("List agents").action(async () => {
|
|
|
8659
8521
|
}).join("\n"));
|
|
8660
8522
|
});
|
|
8661
8523
|
agent.command("model <name> [model]").description("Set the model a member's turns run with, from its next turn; no model means the CLI's default").action(async (name, model) => {
|
|
8662
|
-
const board = await
|
|
8524
|
+
const board = await open3();
|
|
8663
8525
|
const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
|
|
8664
8526
|
print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
|
|
8665
8527
|
});
|
|
8666
8528
|
agent.command("effort <name> [effort]").description("Set the reasoning effort a member's turns run with, from its next turn, such as high; no effort means the model's default").action(async (name, effort) => {
|
|
8667
|
-
const board = await
|
|
8529
|
+
const board = await open3();
|
|
8668
8530
|
const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
|
|
8669
8531
|
print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
|
|
8670
8532
|
});
|
|
8671
8533
|
agent.command("retire <name>").description("Retire a member: no more wakes, claims released, token revoked, sessions archived").requiredOption("--reason <text>", "why").action(async (name, opts) => {
|
|
8672
|
-
const board = await
|
|
8534
|
+
const board = await open3();
|
|
8673
8535
|
const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
|
|
8674
8536
|
print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
|
|
8675
8537
|
});
|
|
8676
8538
|
var proposal = program.command("proposal").description("Governance proposals");
|
|
8677
8539
|
proposal.command("list").description("List proposals").action(async () => {
|
|
8678
|
-
const board = await
|
|
8540
|
+
const board = await open3();
|
|
8679
8541
|
const proposals = await board.listProposals();
|
|
8680
8542
|
print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
|
|
8681
8543
|
});
|
|
8682
8544
|
proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
|
|
8683
|
-
const board = await
|
|
8545
|
+
const board = await open3();
|
|
8684
8546
|
const found = await board.readProposal(id);
|
|
8685
8547
|
print(found, () => [
|
|
8686
8548
|
`${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
|
|
@@ -8690,7 +8552,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
|
|
|
8690
8552
|
].filter((line) => line.length > 0).join("\n"));
|
|
8691
8553
|
});
|
|
8692
8554
|
proposal.command("create").description("Propose a member, role, channel, reallocation, or retirement").requiredOption("--kind <kind>", "member, role, channel, reallocation, or retirement").requiredOption("--charter <json>", "the charter as a JSON object").option("--rationale <text>", "why", "").option("--as <agent>", "act as this agent instead of the user").action(async (opts) => {
|
|
8693
|
-
const board = await
|
|
8555
|
+
const board = await open3();
|
|
8694
8556
|
const created = await board.propose(await actorFor(board, opts.as), {
|
|
8695
8557
|
kind: ProposalKindSchema.parse(opts.kind),
|
|
8696
8558
|
charter: parseCharter(opts.charter),
|
|
@@ -8699,7 +8561,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
|
|
|
8699
8561
|
print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
|
|
8700
8562
|
});
|
|
8701
8563
|
proposal.command("approve <id>").description("Approve a proposal; the board provisions it").option("--reason <text>", "a note for the record").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8702
|
-
const board = await
|
|
8564
|
+
const board = await open3();
|
|
8703
8565
|
const decision = await board.approve(await actorFor(board, opts.as), {
|
|
8704
8566
|
proposal_id: id,
|
|
8705
8567
|
...opts.reason === void 0 ? {} : { reason: opts.reason }
|
|
@@ -8708,7 +8570,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
|
|
|
8708
8570
|
print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
|
|
8709
8571
|
});
|
|
8710
8572
|
proposal.command("reject <id>").description("Reject a proposal with a reason").requiredOption("--reason <text>", "why").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8711
|
-
const board = await
|
|
8573
|
+
const board = await open3();
|
|
8712
8574
|
const decision = await board.reject(await actorFor(board, opts.as), {
|
|
8713
8575
|
proposal_id: id,
|
|
8714
8576
|
reason: opts.reason
|
|
@@ -8717,14 +8579,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
|
|
|
8717
8579
|
});
|
|
8718
8580
|
var role = program.command("role").description("Role charters");
|
|
8719
8581
|
role.command("list").description("List role charters").action(async () => {
|
|
8720
|
-
const board = await
|
|
8582
|
+
const board = await open3();
|
|
8721
8583
|
const roles = await board.listRoles();
|
|
8722
8584
|
print(roles, () => roles.map((r) => `${r.name} replicas up to ${r.maxReplicas} backlog threshold ${r.backlogThreshold} wakes on ${r.wakeTriggers.join(", ") || "direct wakes only"}`).join("\n"));
|
|
8723
8585
|
});
|
|
8724
8586
|
role.command("add <name>").description("Write a new role charter directly as the user").requiredOption("--purpose <text>", "what the role is for; its members read this every turn").option("--verbs <verbs...>", "board verbs granted; the member verbs by default").option("--triggers <triggers...>", "wakes it opts into: user_post, ops_event, heartbeat", [
|
|
8725
8587
|
"heartbeat"
|
|
8726
8588
|
]).action(async (name, opts) => {
|
|
8727
|
-
const board = await
|
|
8589
|
+
const board = await open3();
|
|
8728
8590
|
const added = await board.setRoleCharter(board.userActor(), {
|
|
8729
8591
|
name,
|
|
8730
8592
|
purpose: opts.purpose,
|
|
@@ -8734,7 +8596,7 @@ role.command("add <name>").description("Write a new role charter directly as the
|
|
|
8734
8596
|
print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
|
|
8735
8597
|
});
|
|
8736
8598
|
role.command("set <name>").description("Change a charter directly as the user: scaling cap, threshold, or purpose").option("--max-replicas <n>", "active members of the role per project the scheduler may reach").option("--backlog-threshold <n>", "load per member that adds a replica").option("--purpose <text>", "the charter's purpose").action(async (name, opts) => {
|
|
8737
|
-
const board = await
|
|
8599
|
+
const board = await open3();
|
|
8738
8600
|
const current = await board.readRole(name);
|
|
8739
8601
|
const updated = await board.setRoleCharter(board.userActor(), {
|
|
8740
8602
|
...current,
|
|
@@ -8746,7 +8608,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
|
|
|
8746
8608
|
});
|
|
8747
8609
|
var channelCommand = program.command("channel").description("Channels");
|
|
8748
8610
|
channelCommand.command("add <ref>").description("Open a channel: <name> for the society or <project>/<name>").requiredOption("--purpose <text>", "what the channel is for").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
|
|
8749
|
-
const board = await
|
|
8611
|
+
const board = await open3();
|
|
8750
8612
|
const parsed = parseChannelRef(ref);
|
|
8751
8613
|
const added = await board.addChannel(await actorFor(board, opts.as), {
|
|
8752
8614
|
project: parsed.project,
|
|
@@ -8756,7 +8618,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
|
|
|
8756
8618
|
print({ channel: added }, () => `Channel ${added} opened`);
|
|
8757
8619
|
});
|
|
8758
8620
|
channelCommand.command("archive <ref>").description("Archive a channel whose workstream is done, once no task filed there is in play").requiredOption("--reason <text>", "why its work is done").option("--as <agent>", "act as this agent instead of the user").action(async (ref, opts) => {
|
|
8759
|
-
const board = await
|
|
8621
|
+
const board = await open3();
|
|
8760
8622
|
const archived = await board.archiveChannel(await actorFor(board, opts.as), {
|
|
8761
8623
|
channel: ref,
|
|
8762
8624
|
reason: opts.reason
|
|
@@ -8764,12 +8626,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
|
|
|
8764
8626
|
print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
|
|
8765
8627
|
});
|
|
8766
8628
|
program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
|
|
8767
|
-
const board = await
|
|
8629
|
+
const board = await open3();
|
|
8768
8630
|
const signals = await board.listSignals(Number(opts.limit));
|
|
8769
8631
|
print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
|
|
8770
8632
|
});
|
|
8771
8633
|
program.command("post <channel> <body>").description("Post a message to a channel, or into one of its threads").option("--as <agent>", "act as this agent instead of the user").option("--thread <threadId>", "post into the thread, which must hang off the channel").action(async (channel, body, opts) => {
|
|
8772
|
-
const board = await
|
|
8634
|
+
const board = await open3();
|
|
8773
8635
|
const who = await actorFor(board, opts.as);
|
|
8774
8636
|
const message = await board.postMessage(who, {
|
|
8775
8637
|
channel,
|
|
@@ -8779,13 +8641,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
|
|
|
8779
8641
|
print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
|
|
8780
8642
|
});
|
|
8781
8643
|
program.command("ask <text>").description("Post to the society's general channel as the user; the front desk routes it").action(async (text) => {
|
|
8782
|
-
const board = await
|
|
8644
|
+
const board = await open3();
|
|
8783
8645
|
const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
|
|
8784
8646
|
print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
|
|
8785
8647
|
});
|
|
8786
8648
|
var task = program.command("task").description("Manage tasks");
|
|
8787
8649
|
task.command("create <project> <title>").description("Create a task with a plan, or with one stage anyone in the project may take").option("--body <text>", "task body", "").option("--parent <id>", "parent task id").option("--plan <json>", `stages, for example '[{"name":"analysis","role":"analyst"}]'`).option("--channel <name>", "the project's channel to file it in, such as a release's", "general").option("--as <agent>", "act as this agent instead of the user").action(async (projectSlug, title, opts) => {
|
|
8788
|
-
const board = await
|
|
8650
|
+
const board = await open3();
|
|
8789
8651
|
const who = await actorFor(board, opts.as);
|
|
8790
8652
|
const created = await board.createTask(who, {
|
|
8791
8653
|
project: projectSlug,
|
|
@@ -8801,17 +8663,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
|
|
|
8801
8663
|
].join("\n"));
|
|
8802
8664
|
});
|
|
8803
8665
|
task.command("claim <id>").description("Hold the task's current stage").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8804
|
-
const board = await
|
|
8666
|
+
const board = await open3();
|
|
8805
8667
|
const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
|
|
8806
8668
|
print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
|
|
8807
8669
|
});
|
|
8808
8670
|
task.command("release <id>").description("Let go of the stage held on a task").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8809
|
-
const board = await
|
|
8671
|
+
const board = await open3();
|
|
8810
8672
|
const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
|
|
8811
8673
|
print(released, () => `Stage ${released.stage} of task ${released.id} released`);
|
|
8812
8674
|
});
|
|
8813
8675
|
task.command("advance <id>").description("Finish the current stage; past the last one the task is done").option("--note <text>", "what was done, posted to the task's thread").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8814
|
-
const board = await
|
|
8676
|
+
const board = await open3();
|
|
8815
8677
|
const advanced = await board.advanceTask(await actorFor(board, opts.as), {
|
|
8816
8678
|
task_id: id,
|
|
8817
8679
|
...opts.note === void 0 ? {} : { note: opts.note }
|
|
@@ -8820,7 +8682,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
|
|
|
8820
8682
|
${describePlan(advanced)}`);
|
|
8821
8683
|
});
|
|
8822
8684
|
task.command("plan <id> <stages>").description("Reshape the stages ahead: pass existing stages with their id to keep them, new ones without").option("--on-done <effect>", "override the completion effect for this task").option("--as <agent>", "act as this agent instead of the user").action(async (id, stages, opts) => {
|
|
8823
|
-
const board = await
|
|
8685
|
+
const board = await open3();
|
|
8824
8686
|
const planned = await board.planTask(await actorFor(board, opts.as), {
|
|
8825
8687
|
task_id: id,
|
|
8826
8688
|
stages: PlanEditSchema.parse(JSON.parse(stages)),
|
|
@@ -8829,7 +8691,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
|
|
|
8829
8691
|
print(planned, () => describePlan(planned));
|
|
8830
8692
|
});
|
|
8831
8693
|
task.command("update <id>").description("Send a task back to an earlier stage, abandon it, or set blockers, with a note for its thread").option("--stage <stageId>", "move back to this earlier stage").option("--abandon", "abandon the task", false).option("--note <text>", "a note, posted to the task's thread").option("--blocked-by <ids...>", "task ids this task waits on").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8832
|
-
const board = await
|
|
8694
|
+
const board = await open3();
|
|
8833
8695
|
const updated = await board.updateTask(await actorFor(board, opts.as), {
|
|
8834
8696
|
task_id: id,
|
|
8835
8697
|
...opts.stage === void 0 ? {} : { stage: opts.stage },
|
|
@@ -8840,7 +8702,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
|
|
|
8840
8702
|
print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
|
|
8841
8703
|
});
|
|
8842
8704
|
task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
|
|
8843
|
-
const board = await
|
|
8705
|
+
const board = await open3();
|
|
8844
8706
|
const found = await board.getTask(board.userActor(), { task_id: id });
|
|
8845
8707
|
const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
|
|
8846
8708
|
const said = found.messages.map((message) => {
|
|
@@ -8860,7 +8722,7 @@ ${message.body.trim()}
|
|
|
8860
8722
|
].join("\n"));
|
|
8861
8723
|
});
|
|
8862
8724
|
task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
|
|
8863
|
-
const board = await
|
|
8725
|
+
const board = await open3();
|
|
8864
8726
|
const tasks = await board.listTasks(projectSlug);
|
|
8865
8727
|
print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
|
|
8866
8728
|
const stage = t.stages[stageIndex(t, t.stage)];
|
|
@@ -8868,18 +8730,16 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
8868
8730
|
}).join("\n"));
|
|
8869
8731
|
});
|
|
8870
8732
|
var thread = program.command("thread").description("Manage threads");
|
|
8871
|
-
thread.command("open
|
|
8872
|
-
const board = await
|
|
8733
|
+
thread.command("open").description("Open a thread on a channel with a title; a task's or a proposal's opens with it").requiredOption("--channel <ref>", "the channel it hangs off").requiredOption("--title <text>", "the thread's title").option("--as <agent>", "act as this agent instead of the user").action(async (opts) => {
|
|
8734
|
+
const board = await open3();
|
|
8873
8735
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
...opts.channel === void 0 ? {} : { channel: opts.channel },
|
|
8877
|
-
...opts.title === void 0 ? {} : { title: opts.title }
|
|
8736
|
+
channel: opts.channel,
|
|
8737
|
+
title: opts.title
|
|
8878
8738
|
});
|
|
8879
8739
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
8880
8740
|
});
|
|
8881
8741
|
thread.command("close <threadId>").description("Close a thread with a summary posted to its channel").requiredOption("--summary <text>", "closure summary").option("--as <agent>", "act as this agent instead of the user").action(async (threadId, opts) => {
|
|
8882
|
-
const board = await
|
|
8742
|
+
const board = await open3();
|
|
8883
8743
|
const summary = await board.closeThread(await actorFor(board, opts.as), {
|
|
8884
8744
|
thread_id: threadId,
|
|
8885
8745
|
summary: opts.summary
|
|
@@ -8887,7 +8747,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
|
|
|
8887
8747
|
print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
|
|
8888
8748
|
});
|
|
8889
8749
|
thread.command("list").description("List threads, the society's first, then each project's").option("--open", "only open threads", false).action(async (opts) => {
|
|
8890
|
-
const board = await
|
|
8750
|
+
const board = await open3();
|
|
8891
8751
|
const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
|
|
8892
8752
|
print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
|
|
8893
8753
|
const about = t.subject === void 0 ? "topic" : t.subject.kind;
|
|
@@ -8895,7 +8755,7 @@ thread.command("list").description("List threads, the society's first, then each
|
|
|
8895
8755
|
}).join("\n"));
|
|
8896
8756
|
});
|
|
8897
8757
|
thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
|
|
8898
|
-
const board = await
|
|
8758
|
+
const board = await open3();
|
|
8899
8759
|
const found = await board.readThread(threadId);
|
|
8900
8760
|
const messages = await board.listThread(threadId);
|
|
8901
8761
|
print({ thread: found, messages }, () => [
|
|
@@ -8906,17 +8766,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
|
|
|
8906
8766
|
].join("\n"));
|
|
8907
8767
|
});
|
|
8908
8768
|
program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
|
|
8909
|
-
const board = await
|
|
8769
|
+
const board = await open3();
|
|
8910
8770
|
await board.setPaused(board.userActor(), true);
|
|
8911
8771
|
print({ paused: true }, () => "Society paused");
|
|
8912
8772
|
});
|
|
8913
8773
|
program.command("resume").description("Allow wakeups again").action(async () => {
|
|
8914
|
-
const board = await
|
|
8774
|
+
const board = await open3();
|
|
8915
8775
|
await board.setPaused(board.userActor(), false);
|
|
8916
8776
|
print({ paused: false }, () => "Society resumed");
|
|
8917
8777
|
});
|
|
8918
8778
|
program.command("society").description("The society's settings").command("timezone [zone]").description("Show the society's time zone, or set it: crons that name none are read in it").action(async (zone) => {
|
|
8919
|
-
const board = await
|
|
8779
|
+
const board = await open3();
|
|
8920
8780
|
const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
|
|
8921
8781
|
print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
|
|
8922
8782
|
});
|
|
@@ -8924,7 +8784,8 @@ function describeCron(cron2, timeZone) {
|
|
|
8924
8784
|
const where = [
|
|
8925
8785
|
cron2.scope,
|
|
8926
8786
|
cron2.thread,
|
|
8927
|
-
cron2.channel === void 0 ? void 0 : `#${cron2.channel}
|
|
8787
|
+
cron2.channel === void 0 ? void 0 : `#${cron2.channel}`,
|
|
8788
|
+
cron2.place === void 0 ? void 0 : `~${cron2.place}`
|
|
8928
8789
|
].filter((part) => part !== void 0).join("/");
|
|
8929
8790
|
const next = nextFire(cron2, timeZone);
|
|
8930
8791
|
const state = cron2.ended !== void 0 ? `ended (${cron2.ended.reason})` : cron2.paused !== void 0 ? "paused" : next === null ? "no time to come" : `next ${next.toISOString()}`;
|
|
@@ -8932,13 +8793,13 @@ function describeCron(cron2, timeZone) {
|
|
|
8932
8793
|
}
|
|
8933
8794
|
var cron = program.command("cron").description("Crons that wake citizens on a clock");
|
|
8934
8795
|
cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
|
|
8935
|
-
const board = await
|
|
8796
|
+
const board = await open3();
|
|
8936
8797
|
const { timezone } = await board.society();
|
|
8937
8798
|
const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
|
|
8938
8799
|
print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
|
|
8939
8800
|
});
|
|
8940
8801
|
cron.command("add <agent> <title>").description("Set a cron that wakes a citizen, with a note on what to do then").requiredOption("--note <text>", "what the citizen does when it fires").option("--cron <expression>", "five fields: minute hour day-of-month month day-of-week").option("--timezone <zone>", "the zone the expression is read in; the society's otherwise").option("--at <time>", "one time to fire, with its offset, instead of --cron").option("--project <slug>", "where it fires: a project the citizen is in, or society").option("--thread <id>", "an open thread there whose conversation it fires in").option("--channel <name>", "an open channel there, other than general, whose conversation it fires in").option("--as <agent>", "act as this agent instead of the user").action(async (agentName, title, opts) => {
|
|
8941
|
-
const board = await
|
|
8802
|
+
const board = await open3();
|
|
8942
8803
|
const created = await board.createCron(await actorFor(board, opts.as), {
|
|
8943
8804
|
title,
|
|
8944
8805
|
note: opts.note,
|
|
@@ -8958,7 +8819,7 @@ for (const [name, paused] of [
|
|
|
8958
8819
|
["resume", false]
|
|
8959
8820
|
]) {
|
|
8960
8821
|
cron.command(`${name} <id>`).description(paused ? "Pause a cron" : "Resume a paused cron from its next time").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8961
|
-
const board = await
|
|
8822
|
+
const board = await open3();
|
|
8962
8823
|
const updated = await board.updateCron(await actorFor(board, opts.as), {
|
|
8963
8824
|
cron_id: id,
|
|
8964
8825
|
paused
|
|
@@ -8968,7 +8829,7 @@ for (const [name, paused] of [
|
|
|
8968
8829
|
});
|
|
8969
8830
|
}
|
|
8970
8831
|
cron.command("remove <id>").description("End a cron for good").requiredOption("--reason <text>", "why it ends").option("--as <agent>", "act as this agent instead of the user").action(async (id, opts) => {
|
|
8971
|
-
const board = await
|
|
8832
|
+
const board = await open3();
|
|
8972
8833
|
const removed = await board.removeCron(await actorFor(board, opts.as), {
|
|
8973
8834
|
cron_id: id,
|
|
8974
8835
|
reason: opts.reason
|
|
@@ -8977,12 +8838,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
|
|
|
8977
8838
|
});
|
|
8978
8839
|
var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
|
|
8979
8840
|
knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
|
|
8980
|
-
const board = await
|
|
8841
|
+
const board = await open3();
|
|
8981
8842
|
const topics = await board.listKnowledge(projectSlug ?? null);
|
|
8982
8843
|
print(topics, () => topics.length === 0 ? "No knowledge yet." : topics.map((t) => `${t.topic} revision ${t.revision}, updated by ${t.updatedBy} at ${t.updatedAt}`).join("\n"));
|
|
8983
8844
|
});
|
|
8984
8845
|
knowledge.command("show <topic>").description("Print a knowledge topic").option("--project <slug>", "the project's topic instead of the society's").action(async (topic, opts) => {
|
|
8985
|
-
const board = await
|
|
8846
|
+
const board = await open3();
|
|
8986
8847
|
const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
|
|
8987
8848
|
if (found === void 0) {
|
|
8988
8849
|
throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
|
|
@@ -8990,8 +8851,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
|
|
|
8990
8851
|
print(found, () => found.body);
|
|
8991
8852
|
});
|
|
8992
8853
|
knowledge.command("write <topic>").description("Write a knowledge topic as the user, from a file or standard input").requiredOption("--revision <n>", "the revision it replaces, as knowledge list shows it; 0 for a new topic").option("--project <slug>", "the project's topic instead of the society's").option("--file <path>", "read the body from this file instead of standard input").option("--as <agent>", "act as this agent instead of the user").action(async (topic, opts) => {
|
|
8993
|
-
const board = await
|
|
8994
|
-
const body = opts.file === void 0 ? await readStdin() : await readFile5(
|
|
8854
|
+
const board = await open3();
|
|
8855
|
+
const body = opts.file === void 0 ? await readStdin() : await readFile5(path8.resolve(opts.file), "utf8");
|
|
8995
8856
|
const written = await board.writeKnowledge(await actorFor(board, opts.as), {
|
|
8996
8857
|
project: opts.project ?? null,
|
|
8997
8858
|
topic,
|
|
@@ -9002,7 +8863,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
|
|
|
9002
8863
|
});
|
|
9003
8864
|
var skill = program.command("skill").description("Skills promoted to the society");
|
|
9004
8865
|
skill.command("list").description("List the society's skills, and each member's own").action(async () => {
|
|
9005
|
-
const board = await
|
|
8866
|
+
const board = await open3();
|
|
9006
8867
|
const society = await board.listSocietySkills();
|
|
9007
8868
|
const own = [];
|
|
9008
8869
|
for (const member of await board.listAgents()) {
|
|
@@ -9017,7 +8878,7 @@ skill.command("list").description("List the society's skills, and each member's
|
|
|
9017
8878
|
].join("\n") || "No skills yet.");
|
|
9018
8879
|
});
|
|
9019
8880
|
skill.command("remove <name>").description("Remove a skill from the society's skills; its text is set aside, not destroyed").requiredOption("--reason <text>", "why it is wrong, superseded, or no longer used").option("--as <agent>", "act as this agent instead of the user").action(async (name, opts) => {
|
|
9020
|
-
const board = await
|
|
8881
|
+
const board = await open3();
|
|
9021
8882
|
const removed = await board.removeSkill(await actorFor(board, opts.as), {
|
|
9022
8883
|
name,
|
|
9023
8884
|
reason: opts.reason
|
|
@@ -9026,13 +8887,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
|
|
|
9026
8887
|
});
|
|
9027
8888
|
var turn = program.command("turn").description("Turn operations");
|
|
9028
8889
|
turn.command("digest <agent>").description("Show the unread messages the agent's next turn opens with, in one scope or all; the cursors stay put").option("--limit <n>", "maximum messages", "50").option("--project <slug>", "the scope whose turn to show: a project, or society").action(async (agentName, opts) => {
|
|
9029
|
-
const board = await
|
|
8890
|
+
const board = await open3();
|
|
9030
8891
|
const actor = await board.actorFor(agentName);
|
|
9031
8892
|
const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
|
|
9032
8893
|
print(digest, () => digest.messages.length === 0 ? "No unread messages." : digest.messages.map((m) => `[${m.ts}] ${m.channel}${m.thread === void 0 ? "" : ` (thread ${m.thread})`} @${m.author}: ${m.body.trim()}`).join("\n"));
|
|
9033
8894
|
});
|
|
9034
8895
|
turn.command("run <agent>").description("Enqueue a manual wake for an agent on a project, or a reflection turn").requiredOption("--project <slug>", "project to act on, or society for a society-scope turn").option("--reason <text>", "why", "manual wake from the admin CLI").option("--reflect", "a reflection turn instead of a working turn", false).action(async (agentName, opts) => {
|
|
9035
|
-
const board = await
|
|
8896
|
+
const board = await open3();
|
|
9036
8897
|
const event = await board.requestWake(board.userActor(), {
|
|
9037
8898
|
agent: agentName,
|
|
9038
8899
|
project: opts.project,
|