@kubitnodes/stellaris 0.3.23 → 0.3.24
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 +479 -164
- package/dist/server/index.js +598 -191
- package/package.json +1 -1
- package/web/dist/assets/{BackLink-DGYs80bb.js → BackLink-DKMw_rHu.js} +1 -1
- package/web/dist/assets/{Button-f4jgBZY1.js → Button-BYJGqZ3Q.js} +1 -1
- package/web/dist/assets/{ChannelForms-q9zdj4p2.js → ChannelForms-CkYupEL2.js} +1 -1
- package/web/dist/assets/ChannelView-BIXouQMX.js +2 -0
- package/web/dist/assets/CitizenView-C3AMFixP.js +2 -0
- package/web/dist/assets/CitizensView-CYFS3f9U.js +1 -0
- package/web/dist/assets/{CliIcon-CLHaA65A.js → CliIcon-DVX5iLhG.js} +1 -1
- package/web/dist/assets/CronsView-BGCcO7bH.js +1 -0
- package/web/dist/assets/{KnowledgeView-Cxfi2-Pt.js → KnowledgeView-DLLYjd1G.js} +1 -1
- package/web/dist/assets/Markdown--whKJBYV.js +30 -0
- package/web/dist/assets/{MessageItem-Bk9eBp1U.js → MessageItem-BvDOeJci.js} +1 -1
- package/web/dist/assets/{MetricsView-CTEH6rJn.js → MetricsView-B-KUNOZm.js} +1 -1
- package/web/dist/assets/NeedsYouView-Dee_bOKj.js +1 -0
- package/web/dist/assets/{Overview-CS9HCSmm.js → Overview-DFiHDFnA.js} +1 -1
- package/web/dist/assets/{Pane-CJYRVkwh.js → Pane-DTmByqgA.js} +1 -1
- package/web/dist/assets/{Picker-DoxCN6C7.js → Picker-DsHm4f7s.js} +1 -1
- package/web/dist/assets/{ProjectView-CJ3r4UdN.js → ProjectView-DsRXW-qk.js} +1 -1
- package/web/dist/assets/ProposalView-B-4P1S-s.js +1 -0
- package/web/dist/assets/{ProposalsView-B-cOxTsQ.js → ProposalsView-uEuj-ooV.js} +1 -1
- package/web/dist/assets/Quota-D8WDWp5H.js +1 -0
- package/web/dist/assets/{RunnersView-DlQruhwp.js → RunnersView-BB3q65rG.js} +1 -1
- package/web/dist/assets/SkillFileList-DhdEcFQA.js +2 -0
- package/web/dist/assets/SkillView-vAv7zvtJ.js +1 -0
- package/web/dist/assets/SocietyView-Dv4phSkz.js +1 -0
- package/web/dist/assets/{StickyList-MVP34ytV.js → StickyList-C4fC_KJv.js} +1 -1
- package/web/dist/assets/SubjectThread-BEXt-tub.js +1 -0
- package/web/dist/assets/{TaskFileView-GZ0eQFUb.js → TaskFileView-B9sex0tS.js} +2 -2
- package/web/dist/assets/{TaskView-DppFelLW.js → TaskView-BCT388kb.js} +2 -2
- package/web/dist/assets/{TasksView-DDNXzeG4.js → TasksView-CuPay34-.js} +1 -1
- package/web/dist/assets/{ThreadForms-C846TvtS.js → ThreadForms-DzoRDeBh.js} +1 -1
- package/web/dist/assets/ThreadItem-CDVppcOZ.js +1 -0
- package/web/dist/assets/ThreadView-B4F10_j1.js +1 -0
- package/web/dist/assets/citizen-BPIJzKdn.js +1 -0
- package/web/dist/assets/{compose-BmqQmcZH.js → compose-BUhbVoI-.js} +1 -1
- package/web/dist/assets/format-CvfmDNoY.js +55 -0
- package/web/dist/assets/{governance-Cr5OlQnw.js → governance-Bba0OHvJ.js} +1 -1
- package/web/dist/assets/index-Bj8MlyAn.css +2 -0
- package/web/dist/assets/index-DD3J11PT.js +23 -0
- package/web/dist/assets/{mentions-D5hNgtg6.js → mentions-D838wlfQ.js} +1 -1
- package/web/dist/assets/{tasks-R3YgpYTe.js → tasks-CTaxXtGL.js} +1 -1
- package/web/dist/assets/useMatch-B8j0hm1q.js +1 -0
- package/web/dist/index.html +9 -9
- 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/Markdown-BhtIFcWc.js +0 -30
- package/web/dist/assets/NeedsYouView-2VNqH2Sb.js +0 -1
- package/web/dist/assets/ProposalView-DLn9dNPp.js +0 -2
- package/web/dist/assets/Quota-Bo7UdWTV.js +0 -1
- package/web/dist/assets/SocietyView-Bl4ErnV8.js +0 -1
- package/web/dist/assets/SubjectThread-BFFnHxIF.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/index-BTVy_xFF.js +0 -23
- package/web/dist/assets/index-DzV6CCnG.css +0 -2
- 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,
|
|
@@ -939,7 +940,8 @@ var DashboardSchema = z5.object({
|
|
|
939
940
|
var ProposalOriginSchema = z5.object({
|
|
940
941
|
scope: NameSchema,
|
|
941
942
|
thread: UlidSchema.optional(),
|
|
942
|
-
channel: NameSchema.optional()
|
|
943
|
+
channel: NameSchema.optional(),
|
|
944
|
+
place: NameSchema.optional()
|
|
943
945
|
});
|
|
944
946
|
var SKILL_FILES_LIMIT = 100;
|
|
945
947
|
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
@@ -1293,6 +1295,7 @@ var TurnExitReasonSchema = z8.enum([
|
|
|
1293
1295
|
"blocked",
|
|
1294
1296
|
"stopped"
|
|
1295
1297
|
]);
|
|
1298
|
+
var ResumeFailureSchema = z8.enum(["lost", "retry"]);
|
|
1296
1299
|
var AgentEventSchema = z8.discriminatedUnion("type", [
|
|
1297
1300
|
z8.object({
|
|
1298
1301
|
type: z8.literal("turn_started"),
|
|
@@ -1344,6 +1347,8 @@ var LiveTurnEventSchema = z8.object({
|
|
|
1344
1347
|
thread: UlidSchema.optional(),
|
|
1345
1348
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1346
1349
|
channel: NameSchema.optional(),
|
|
1350
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1351
|
+
place: NameSchema.optional(),
|
|
1347
1352
|
event: AgentEventSchema
|
|
1348
1353
|
});
|
|
1349
1354
|
|
|
@@ -1430,7 +1435,8 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1430
1435
|
z9.object({
|
|
1431
1436
|
kind: z9.literal("home"),
|
|
1432
1437
|
thread: UlidSchema.optional(),
|
|
1433
|
-
channel: NameSchema.optional()
|
|
1438
|
+
channel: NameSchema.optional(),
|
|
1439
|
+
place: NameSchema.optional()
|
|
1434
1440
|
}),
|
|
1435
1441
|
z9.object({
|
|
1436
1442
|
kind: z9.literal("project"),
|
|
@@ -1439,7 +1445,16 @@ var TurnWorkspaceSchema = z9.discriminatedUnion("kind", [
|
|
|
1439
1445
|
thread: UlidSchema.optional(),
|
|
1440
1446
|
channel: NameSchema.optional()
|
|
1441
1447
|
}),
|
|
1442
|
-
z9.object({
|
|
1448
|
+
z9.object({
|
|
1449
|
+
kind: z9.literal("task"),
|
|
1450
|
+
repo: ProjectRepoSchema,
|
|
1451
|
+
taskId: UlidSchema,
|
|
1452
|
+
/**
|
|
1453
|
+
* The citizen holding the task's current stage, when someone does: a turn of anyone else
|
|
1454
|
+
* works detached at the branch's tip, so the holder's turns find the branch free.
|
|
1455
|
+
*/
|
|
1456
|
+
holder: NameSchema.optional()
|
|
1457
|
+
})
|
|
1443
1458
|
]);
|
|
1444
1459
|
var TurnJobSchema = z9.object({
|
|
1445
1460
|
turnId: UlidSchema,
|
|
@@ -1455,6 +1470,8 @@ var TurnJobSchema = z9.object({
|
|
|
1455
1470
|
thread: UlidSchema.optional(),
|
|
1456
1471
|
/** The channel, never general, whose conversation the turn is in. */
|
|
1457
1472
|
channel: NameSchema.optional(),
|
|
1473
|
+
/** The project whose channels' conversation the turn is in, for a turn in the society scope. */
|
|
1474
|
+
place: NameSchema.optional(),
|
|
1458
1475
|
/**
|
|
1459
1476
|
* The session to resume, or null to start one: the CLI's adapter picks a fresh session's id, and
|
|
1460
1477
|
* the outcome reports it.
|
|
@@ -1475,6 +1492,12 @@ var TurnJobSchema = z9.object({
|
|
|
1475
1492
|
workspace: TurnWorkspaceSchema
|
|
1476
1493
|
});
|
|
1477
1494
|
var TurnWorkSchema = z9.object({ branch: z9.string().min(1), head: z9.string().min(1) });
|
|
1495
|
+
var StuckWorkSchema = z9.object({
|
|
1496
|
+
branch: z9.string().min(1),
|
|
1497
|
+
agent: NameSchema,
|
|
1498
|
+
commits: z9.array(z9.string()),
|
|
1499
|
+
total: z9.number().int().positive()
|
|
1500
|
+
});
|
|
1478
1501
|
var TurnEventsSchema = z9.object({ entries: z9.array(TranscriptEntrySchema) });
|
|
1479
1502
|
var TurnOutcomeSchema = z9.object({
|
|
1480
1503
|
exitReason: TurnExitReasonSchema,
|
|
@@ -1484,8 +1507,18 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1484
1507
|
costUsd: z9.number().nonnegative(),
|
|
1485
1508
|
/** The session's running total after this turn, for CLIs that report one. */
|
|
1486
1509
|
sessionCostUsd: z9.number().nonnegative().optional(),
|
|
1487
|
-
/**
|
|
1488
|
-
|
|
1510
|
+
/**
|
|
1511
|
+
* The session the CLI used, which differs from the job's for CLIs that assign their own ids. A
|
|
1512
|
+
* turn whose CLI never started, because it failed or was stopped first, reports the job's own,
|
|
1513
|
+
* null in a conversation's first turn, so a session never started is not recorded.
|
|
1514
|
+
*/
|
|
1515
|
+
session: z9.string().min(1).nullable(),
|
|
1516
|
+
/**
|
|
1517
|
+
* Set when the CLI could not resume the job's session, which failed the turn before any model
|
|
1518
|
+
* call; the server then drops a `lost` session, and one that failed `retry` too often in a row. A
|
|
1519
|
+
* runner from before never sends it.
|
|
1520
|
+
*/
|
|
1521
|
+
resumeFailed: ResumeFailureSchema.optional(),
|
|
1489
1522
|
/** The model the CLI reported, when it reported one. */
|
|
1490
1523
|
model: z9.string().nullable(),
|
|
1491
1524
|
work: TurnWorkSchema.nullable(),
|
|
@@ -1494,6 +1527,11 @@ var TurnOutcomeSchema = z9.object({
|
|
|
1494
1527
|
* branch after the hand-back: uncommitted changes, or commits no branch contains.
|
|
1495
1528
|
*/
|
|
1496
1529
|
leftovers: z9.boolean().default(false),
|
|
1530
|
+
/**
|
|
1531
|
+
* Commits the hand-back found its task's worktrees holding that no fast-forward of the task's
|
|
1532
|
+
* branch can take, each told once; a runner from before never sends it.
|
|
1533
|
+
*/
|
|
1534
|
+
stuck: z9.array(StuckWorkSchema).optional(),
|
|
1497
1535
|
/**
|
|
1498
1536
|
* The CLI's plan quota as the turn's stream last reported it, when it reported any; a runner from
|
|
1499
1537
|
* before never sends it.
|
|
@@ -1612,7 +1650,8 @@ var TurnImageSchema = z9.discriminatedUnion("kind", [
|
|
|
1612
1650
|
]);
|
|
1613
1651
|
var WorkspaceConversationSchema = z9.discriminatedUnion("kind", [
|
|
1614
1652
|
z9.object({ kind: z9.literal("thread"), id: UlidSchema }),
|
|
1615
|
-
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema })
|
|
1653
|
+
z9.object({ kind: z9.literal("channel"), scope: NameSchema, name: NameSchema }),
|
|
1654
|
+
z9.object({ kind: z9.literal("place"), project: NameSchema })
|
|
1616
1655
|
]);
|
|
1617
1656
|
var HeldWorkspaceSchema = z9.object({
|
|
1618
1657
|
agent: NameSchema,
|
|
@@ -1718,6 +1757,7 @@ var TurnDispatchSchema = z10.object({
|
|
|
1718
1757
|
project: NameSchema,
|
|
1719
1758
|
thread: TurnThreadSchema.optional(),
|
|
1720
1759
|
channel: NameSchema.optional(),
|
|
1760
|
+
place: NameSchema.optional(),
|
|
1721
1761
|
trigger: TriggerSchema,
|
|
1722
1762
|
priority: z10.number().int().min(0).max(2),
|
|
1723
1763
|
onboarding: z10.boolean().default(false),
|
|
@@ -1725,7 +1765,13 @@ var TurnDispatchSchema = z10.object({
|
|
|
1725
1765
|
crons: z10.array(UlidSchema).optional()
|
|
1726
1766
|
});
|
|
1727
1767
|
function conversationPart(conversation) {
|
|
1728
|
-
|
|
1768
|
+
if (conversation.thread !== void 0) {
|
|
1769
|
+
return conversation.thread;
|
|
1770
|
+
}
|
|
1771
|
+
if (conversation.channel !== void 0) {
|
|
1772
|
+
return `#${conversation.channel}`;
|
|
1773
|
+
}
|
|
1774
|
+
return conversation.place === void 0 ? void 0 : `~${conversation.place}`;
|
|
1729
1775
|
}
|
|
1730
1776
|
function channelConversation(scope, channel) {
|
|
1731
1777
|
const at = channel.indexOf("/");
|
|
@@ -1733,6 +1779,10 @@ function channelConversation(scope, channel) {
|
|
|
1733
1779
|
const name = at < 0 ? channel : channel.slice(at + 1);
|
|
1734
1780
|
return place === scope && name !== "general" ? name : void 0;
|
|
1735
1781
|
}
|
|
1782
|
+
function placeConversation(scope, channel) {
|
|
1783
|
+
const at = channel.indexOf("/");
|
|
1784
|
+
return scope === SOCIETY_SCOPE && at >= 0 ? channel.slice(0, at) : void 0;
|
|
1785
|
+
}
|
|
1736
1786
|
var TurnRecordSchema = z10.object({
|
|
1737
1787
|
/** Assigned by the board when the turn begins; the turn's transcript is filed under it. */
|
|
1738
1788
|
id: UlidSchema.optional(),
|
|
@@ -1742,6 +1792,8 @@ var TurnRecordSchema = z10.object({
|
|
|
1742
1792
|
thread: UlidSchema.optional(),
|
|
1743
1793
|
/** The channel, never general, whose conversation the turn was in. */
|
|
1744
1794
|
channel: NameSchema.optional(),
|
|
1795
|
+
/** The project whose channels' conversation the turn was in, for a turn in the society scope. */
|
|
1796
|
+
place: NameSchema.optional(),
|
|
1745
1797
|
runner: NameSchema,
|
|
1746
1798
|
cli: CliKindSchema.nullable(),
|
|
1747
1799
|
session: z10.string().nullable(),
|
|
@@ -1765,6 +1817,12 @@ var TurnRecordSchema = z10.object({
|
|
|
1765
1817
|
work: TurnWorkSchema.optional(),
|
|
1766
1818
|
/** Who stopped the turn, for one that ended `stopped`. */
|
|
1767
1819
|
stoppedBy: NameSchema.optional(),
|
|
1820
|
+
/** Why the CLI could not resume the turn's session, for a turn that failed so. */
|
|
1821
|
+
resumeFailed: ResumeFailureSchema.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(),
|
|
@@ -2006,7 +2067,7 @@ var RunnerConfigSchema = z13.object({
|
|
|
2006
2067
|
});
|
|
2007
2068
|
|
|
2008
2069
|
// packages/shared/dist/version.js
|
|
2009
|
-
var STELLARIS_VERSION = "0.3.
|
|
2070
|
+
var STELLARIS_VERSION = "0.3.24";
|
|
2010
2071
|
|
|
2011
2072
|
// packages/shared/dist/mentions.js
|
|
2012
2073
|
import { parse, postprocess, preprocess } from "micromark";
|
|
@@ -2387,6 +2448,7 @@ var HOME_GITIGNORE = [
|
|
|
2387
2448
|
"/projects/*/last-turn.json",
|
|
2388
2449
|
"/projects/*/threads/",
|
|
2389
2450
|
"/projects/*/channels/",
|
|
2451
|
+
"/projects/*/places/",
|
|
2390
2452
|
`/${HOME_SCRATCH}/`,
|
|
2391
2453
|
"*.tmp",
|
|
2392
2454
|
"node_modules/",
|
|
@@ -2404,7 +2466,7 @@ var HOME_GITIGNORE = [
|
|
|
2404
2466
|
var IGNORE_FILE = `${HOME_GITIGNORE.join("\n")}
|
|
2405
2467
|
`;
|
|
2406
2468
|
var PRE_RECEIVE = `#!/bin/sh
|
|
2407
|
-
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/))'
|
|
2469
|
+
forbidden='^(agent\\.json|cursors\\.json|role\\.md|turns/|\\.claude/|\\.codex/|projects/[^/]+/(sessions[^/]*\\.json|last-turn\\.json|threads/|channels/|places/))'
|
|
2408
2470
|
while read old new ref; do
|
|
2409
2471
|
if [ "$ref" != "refs/heads/main" ]; then
|
|
2410
2472
|
echo "a home takes pushes to main only" >&2
|
|
@@ -2730,6 +2792,7 @@ var TurnPayload = z14.object({
|
|
|
2730
2792
|
project: z14.string(),
|
|
2731
2793
|
thread: z14.string().optional(),
|
|
2732
2794
|
channel: z14.string().optional(),
|
|
2795
|
+
place: z14.string().optional(),
|
|
2733
2796
|
trigger: z14.string()
|
|
2734
2797
|
});
|
|
2735
2798
|
var TaskPayload = z14.object({
|
|
@@ -2775,11 +2838,16 @@ function answers(mention, turn2) {
|
|
|
2775
2838
|
if (!mention.scopes.includes(turn2.project)) {
|
|
2776
2839
|
return false;
|
|
2777
2840
|
}
|
|
2778
|
-
const
|
|
2841
|
+
const topLevel = mention.thread === null;
|
|
2842
|
+
const own = topLevel ? channelConversation(turn2.project, mention.channel) : void 0;
|
|
2843
|
+
const place = topLevel ? placeConversation(turn2.project, mention.channel) : void 0;
|
|
2779
2844
|
if (turn2.channel !== null) {
|
|
2780
|
-
return
|
|
2845
|
+
return topLevel && own === turn2.channel;
|
|
2846
|
+
}
|
|
2847
|
+
if (turn2.place !== null) {
|
|
2848
|
+
return topLevel && place === turn2.place;
|
|
2781
2849
|
}
|
|
2782
|
-
return
|
|
2850
|
+
return topLevel && own === void 0 && place === void 0 || turn2.trigger === "mention";
|
|
2783
2851
|
}
|
|
2784
2852
|
function computeMetrics(input) {
|
|
2785
2853
|
const { events, now, window } = input;
|
|
@@ -2845,6 +2913,7 @@ function computeMetrics(input) {
|
|
|
2845
2913
|
at,
|
|
2846
2914
|
thread: turn2.data.thread ?? null,
|
|
2847
2915
|
channel: turn2.data.channel ?? null,
|
|
2916
|
+
place: turn2.data.place ?? null,
|
|
2848
2917
|
project: turn2.data.project,
|
|
2849
2918
|
trigger: turn2.data.trigger
|
|
2850
2919
|
};
|
|
@@ -3044,6 +3113,7 @@ var TurnHistoryPayloadSchema = z15.object({
|
|
|
3044
3113
|
project: NameSchema,
|
|
3045
3114
|
thread: UlidSchema.optional(),
|
|
3046
3115
|
channel: NameSchema.optional(),
|
|
3116
|
+
place: NameSchema.optional(),
|
|
3047
3117
|
trigger: z15.string().default("manual"),
|
|
3048
3118
|
exitReason: z15.string().nullable().default(null),
|
|
3049
3119
|
costUsd: z15.number().default(0),
|
|
@@ -3282,11 +3352,14 @@ var BoardPaths = class {
|
|
|
3282
3352
|
return path5.join(this.agentProjects(name), slug);
|
|
3283
3353
|
}
|
|
3284
3354
|
/**
|
|
3285
|
-
* A thread's
|
|
3286
|
-
* conversation; `part` is a thread's id or
|
|
3355
|
+
* A thread's, a channel's, or, in the society scope, a project's conversation's session and last
|
|
3356
|
+
* turn, beside the scope's home conversation; `part` is a thread's id, `#channel`, or `~project`.
|
|
3287
3357
|
*/
|
|
3288
3358
|
agentConversation(name, slug, part) {
|
|
3289
|
-
|
|
3359
|
+
if (part.startsWith("#")) {
|
|
3360
|
+
return path5.join(this.agentProject(name, slug), "channels", part.slice(1));
|
|
3361
|
+
}
|
|
3362
|
+
return part.startsWith("~") ? path5.join(this.agentProject(name, slug), "places", part.slice(1)) : path5.join(this.agentProject(name, slug), "threads", part);
|
|
3290
3363
|
}
|
|
3291
3364
|
agentCursors(name) {
|
|
3292
3365
|
return path5.join(this.agent(name), "cursors.json");
|
|
@@ -3320,6 +3393,101 @@ var BoardPaths = class {
|
|
|
3320
3393
|
}
|
|
3321
3394
|
};
|
|
3322
3395
|
|
|
3396
|
+
// packages/board-core/dist/skill-folder.js
|
|
3397
|
+
import { constants } from "node:fs";
|
|
3398
|
+
import { lstat, open as open2, readdir as readdir3, realpath } from "node:fs/promises";
|
|
3399
|
+
import path6 from "node:path";
|
|
3400
|
+
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3401
|
+
var SKILL_TEXT_LIMIT_BYTES = SKILL_TEXT_LIMIT_CHARS * 4;
|
|
3402
|
+
function skillFileText(bytes) {
|
|
3403
|
+
const text = bytes.includes(0) ? null : bytes.toString("utf8");
|
|
3404
|
+
return {
|
|
3405
|
+
text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
|
|
3406
|
+
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
function inside(root, file) {
|
|
3410
|
+
const relative = path6.relative(root, file);
|
|
3411
|
+
return relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
3412
|
+
}
|
|
3413
|
+
async function walk(root, prefix, found) {
|
|
3414
|
+
const entries = await readdir3(path6.join(root, ...prefix === "" ? [] : prefix.split("/")), {
|
|
3415
|
+
withFileTypes: true
|
|
3416
|
+
}).catch(() => []);
|
|
3417
|
+
for (const entry of entries.toSorted((a, b) => a.name < b.name ? -1 : 1)) {
|
|
3418
|
+
if (found.length >= SKILL_FILES_LIMIT) {
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
3422
|
+
if (!isSafeRelativePath(relative)) {
|
|
3423
|
+
continue;
|
|
3424
|
+
}
|
|
3425
|
+
if (entry.isDirectory()) {
|
|
3426
|
+
await walk(root, relative, found);
|
|
3427
|
+
} else if (entry.isFile() && !entry.name.endsWith(".tmp")) {
|
|
3428
|
+
found.push(relative);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
async function readEntry(root, relative) {
|
|
3433
|
+
let real;
|
|
3434
|
+
try {
|
|
3435
|
+
real = await realpath(path6.join(root, ...relative.split("/")));
|
|
3436
|
+
} catch {
|
|
3437
|
+
return null;
|
|
3438
|
+
}
|
|
3439
|
+
if (!inside(root, real)) {
|
|
3440
|
+
return null;
|
|
3441
|
+
}
|
|
3442
|
+
const handle = await open2(real, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK).catch(() => null);
|
|
3443
|
+
if (handle === null) {
|
|
3444
|
+
return null;
|
|
3445
|
+
}
|
|
3446
|
+
try {
|
|
3447
|
+
const stats = await handle.stat();
|
|
3448
|
+
if (!stats.isFile()) {
|
|
3449
|
+
return null;
|
|
3450
|
+
}
|
|
3451
|
+
const buffer = Buffer.alloc(Math.min(stats.size, SKILL_TEXT_LIMIT_BYTES));
|
|
3452
|
+
let size = 0;
|
|
3453
|
+
while (size < buffer.length) {
|
|
3454
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, size);
|
|
3455
|
+
if (bytesRead === 0) {
|
|
3456
|
+
break;
|
|
3457
|
+
}
|
|
3458
|
+
size += bytesRead;
|
|
3459
|
+
}
|
|
3460
|
+
return {
|
|
3461
|
+
path: relative,
|
|
3462
|
+
size: stats.size,
|
|
3463
|
+
executable: (stats.mode & 73) !== 0,
|
|
3464
|
+
...skillFileText(buffer.subarray(0, size))
|
|
3465
|
+
};
|
|
3466
|
+
} finally {
|
|
3467
|
+
await handle.close();
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
async function readSkillFolder(dir) {
|
|
3471
|
+
const info = await lstat(dir).catch(() => null);
|
|
3472
|
+
if (info === null || !info.isDirectory()) {
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
const root = await realpath(dir).catch(() => null);
|
|
3476
|
+
if (root === null) {
|
|
3477
|
+
return null;
|
|
3478
|
+
}
|
|
3479
|
+
const found = [];
|
|
3480
|
+
await walk(root, "", found);
|
|
3481
|
+
const files = [];
|
|
3482
|
+
for (const relative of found) {
|
|
3483
|
+
const file = await readEntry(root, relative);
|
|
3484
|
+
if (file !== null) {
|
|
3485
|
+
files.push(file);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return files.some((file) => file.path === "SKILL.md") ? files : null;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3323
3491
|
// packages/board-core/dist/tokens.js
|
|
3324
3492
|
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
3325
3493
|
function mintToken() {
|
|
@@ -3333,7 +3501,7 @@ function hashToken(token) {
|
|
|
3333
3501
|
var SYSTEM_ACTOR = { name: "board", role: USER_ROLE };
|
|
3334
3502
|
var CLOSING_ATTEMPTS = 3;
|
|
3335
3503
|
function ownTurn(actor, turn2) {
|
|
3336
|
-
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel;
|
|
3504
|
+
return turn2.agent === actor.name && turn2.scope === actor.scope && turn2.thread === actor.thread && turn2.channel === actor.channel && turn2.place === actor.place;
|
|
3337
3505
|
}
|
|
3338
3506
|
function namesList(names) {
|
|
3339
3507
|
return names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names.at(-1) ?? ""}`;
|
|
@@ -3358,23 +3526,37 @@ var CursorsSchema = z16.object({
|
|
|
3358
3526
|
scopes: z16.record(z16.string(), z16.string()).default({}),
|
|
3359
3527
|
threadsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3360
3528
|
channelsFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3529
|
+
placesFrom: z16.record(z16.string(), z16.string().nullable()).default({}),
|
|
3361
3530
|
/** How far a work role has read news, which goes to whichever of its general conversations reads first. */
|
|
3362
3531
|
news: z16.string().optional()
|
|
3363
3532
|
});
|
|
3364
|
-
var NO_CURSORS = {
|
|
3533
|
+
var NO_CURSORS = {
|
|
3534
|
+
digest: null,
|
|
3535
|
+
scopes: {},
|
|
3536
|
+
threadsFrom: {},
|
|
3537
|
+
channelsFrom: {},
|
|
3538
|
+
placesFrom: {}
|
|
3539
|
+
};
|
|
3365
3540
|
function conversationKey(scope, part) {
|
|
3366
3541
|
return part === null ? scope : `${scope}/${part}`;
|
|
3367
3542
|
}
|
|
3368
3543
|
function channelOfPart(part) {
|
|
3369
3544
|
return part?.startsWith("#") === true ? part.slice(1) : void 0;
|
|
3370
3545
|
}
|
|
3546
|
+
function placeOfPart(part) {
|
|
3547
|
+
return part?.startsWith("~") === true ? part.slice(1) : void 0;
|
|
3548
|
+
}
|
|
3371
3549
|
function threadOfPart(part) {
|
|
3372
|
-
return part === null || part === void 0 || part.startsWith("#") ? void 0 : part;
|
|
3550
|
+
return part === null || part === void 0 || part.startsWith("#") || part.startsWith("~") ? void 0 : part;
|
|
3373
3551
|
}
|
|
3374
3552
|
function channelStart(cursors, scope) {
|
|
3375
3553
|
const from = cursors.channelsFrom[scope];
|
|
3376
3554
|
return from === void 0 ? cursors.digest : from;
|
|
3377
3555
|
}
|
|
3556
|
+
function placeStart(cursors, scope) {
|
|
3557
|
+
const from = cursors.placesFrom[scope];
|
|
3558
|
+
return from === void 0 ? cursors.digest : from;
|
|
3559
|
+
}
|
|
3378
3560
|
function threadStart(cursors, scope) {
|
|
3379
3561
|
const from = cursors.threadsFrom[scope];
|
|
3380
3562
|
return from === void 0 ? cursors.digest : from;
|
|
@@ -3390,7 +3572,7 @@ function cursorOf(cursors, scope, part = null) {
|
|
|
3390
3572
|
if (part === null) {
|
|
3391
3573
|
return cursors.scopes[scope] ?? cursors.digest;
|
|
3392
3574
|
}
|
|
3393
|
-
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : threadStart(cursors, scope));
|
|
3575
|
+
return cursors.scopes[conversationKey(scope, part)] ?? (part.startsWith("#") ? channelStart(cursors, scope) : part.startsWith("~") ? placeStart(cursors, scope) : threadStart(cursors, scope));
|
|
3394
3576
|
}
|
|
3395
3577
|
function byId(a, b) {
|
|
3396
3578
|
return a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0;
|
|
@@ -3398,12 +3580,14 @@ function byId(a, b) {
|
|
|
3398
3580
|
function turnTime(record) {
|
|
3399
3581
|
return record.endedAt ?? record.startedAt;
|
|
3400
3582
|
}
|
|
3583
|
+
function turnConversation(record) {
|
|
3584
|
+
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` : "";
|
|
3585
|
+
}
|
|
3401
3586
|
var PausedSchema = z16.object({ paused: z16.boolean() });
|
|
3402
3587
|
var DEFAULT_LEASE_MS = 30 * 60 * 1e3;
|
|
3403
3588
|
function withoutMentions(text) {
|
|
3404
3589
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
3405
3590
|
}
|
|
3406
|
-
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3407
3591
|
function skillFolderOf(proposal2) {
|
|
3408
3592
|
if (proposal2.kind !== "skill") {
|
|
3409
3593
|
return null;
|
|
@@ -3412,7 +3596,7 @@ function skillFolderOf(proposal2) {
|
|
|
3412
3596
|
return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
|
|
3413
3597
|
}
|
|
3414
3598
|
function proposedFrom(origin, where) {
|
|
3415
|
-
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
|
|
3599
|
+
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel && origin.place === where.place;
|
|
3416
3600
|
}
|
|
3417
3601
|
function megabytes(bytes) {
|
|
3418
3602
|
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
@@ -3434,6 +3618,7 @@ var THREAD_CONVERSATIONS = "thread-conversations";
|
|
|
3434
3618
|
var ASKS_CHANNEL_OPENED = "asks-channel-opened";
|
|
3435
3619
|
var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
|
|
3436
3620
|
var CHANNEL_CONVERSATIONS = "channel-conversations";
|
|
3621
|
+
var PLACE_CONVERSATIONS = "place-conversations";
|
|
3437
3622
|
var DASHBOARD_UPDATES = "dashboard-updates-granted";
|
|
3438
3623
|
var CRON_VERBS = "cron-verbs-granted";
|
|
3439
3624
|
var KNOWLEDGE_NOTICES = "knowledge-notices";
|
|
@@ -3508,8 +3693,8 @@ function restage(existing, edit) {
|
|
|
3508
3693
|
};
|
|
3509
3694
|
}
|
|
3510
3695
|
function reopenStage(stage) {
|
|
3511
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3512
|
-
return
|
|
3696
|
+
const { completedBy: _by, completedAt: _at, ...open4 } = stage;
|
|
3697
|
+
return open4;
|
|
3513
3698
|
}
|
|
3514
3699
|
function gateChanges(before, after) {
|
|
3515
3700
|
const changes = [];
|
|
@@ -3590,7 +3775,7 @@ var Board = class _Board {
|
|
|
3590
3775
|
homes;
|
|
3591
3776
|
constructor(dataDir, options) {
|
|
3592
3777
|
this.paths = new BoardPaths(dataDir);
|
|
3593
|
-
this.homes = new HomeRepos(
|
|
3778
|
+
this.homes = new HomeRepos(path7.join(this.paths.state(), "home-hooks"));
|
|
3594
3779
|
this.leaseMs = options.leaseMs ?? DEFAULT_LEASE_MS;
|
|
3595
3780
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
3596
3781
|
this.events = new EventLog(this.paths.eventLog(), () => this.newId(), this.now);
|
|
@@ -3623,7 +3808,7 @@ var Board = class _Board {
|
|
|
3623
3808
|
* repeated, so a member that later chooses otherwise keeps its choice.
|
|
3624
3809
|
*/
|
|
3625
3810
|
async applyAlignments() {
|
|
3626
|
-
const file =
|
|
3811
|
+
const file = path7.join(this.paths.state(), "alignments.json");
|
|
3627
3812
|
const { applied } = await exists(file) ? await readJson(file, AlignmentsSchema) : { applied: [] };
|
|
3628
3813
|
const pending = [
|
|
3629
3814
|
FOLLOW_DECISIONS,
|
|
@@ -3639,7 +3824,8 @@ var Board = class _Board {
|
|
|
3639
3824
|
DASHBOARD_UPDATES,
|
|
3640
3825
|
CRON_VERBS,
|
|
3641
3826
|
KNOWLEDGE_NOTICES,
|
|
3642
|
-
IMAGE_UPLOADS
|
|
3827
|
+
IMAGE_UPLOADS,
|
|
3828
|
+
PLACE_CONVERSATIONS
|
|
3643
3829
|
].filter((name) => !applied.includes(name));
|
|
3644
3830
|
if (pending.length === 0) {
|
|
3645
3831
|
return;
|
|
@@ -3687,6 +3873,9 @@ var Board = class _Board {
|
|
|
3687
3873
|
if (pending.includes(IMAGE_UPLOADS)) {
|
|
3688
3874
|
await this.grantBeside("post_message", "upload_image");
|
|
3689
3875
|
}
|
|
3876
|
+
if (pending.includes(PLACE_CONVERSATIONS)) {
|
|
3877
|
+
await this.startPlaceCursors();
|
|
3878
|
+
}
|
|
3690
3879
|
await writeJson(file, { applied: [...applied, ...pending] });
|
|
3691
3880
|
}
|
|
3692
3881
|
/**
|
|
@@ -3891,7 +4080,7 @@ var Board = class _Board {
|
|
|
3891
4080
|
}
|
|
3892
4081
|
}
|
|
3893
4082
|
for (const file of await listFiles(this.paths.tasks(slug))) {
|
|
3894
|
-
const raw = await readMarkdown(
|
|
4083
|
+
const raw = await readMarkdown(path7.join(this.paths.tasks(slug), file), z16.record(z16.string(), z16.unknown()));
|
|
3895
4084
|
const legacy = raw.data["thread"];
|
|
3896
4085
|
if (legacy === void 0) {
|
|
3897
4086
|
continue;
|
|
@@ -3899,7 +4088,7 @@ var Board = class _Board {
|
|
|
3899
4088
|
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3900
4089
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
3901
4090
|
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
3902
|
-
const
|
|
4091
|
+
const open4 = legacy === "open" && !ended;
|
|
3903
4092
|
const openedBy = opened.get(task2.id);
|
|
3904
4093
|
const closedBy = closed.get(task2.id);
|
|
3905
4094
|
await this.writeThread(threads, {
|
|
@@ -3907,10 +4096,10 @@ var Board = class _Board {
|
|
|
3907
4096
|
channel: channelRef(slug, "general"),
|
|
3908
4097
|
title: task2.title,
|
|
3909
4098
|
subject: { kind: "task", id: task2.id },
|
|
3910
|
-
state:
|
|
4099
|
+
state: open4 ? "open" : "closed",
|
|
3911
4100
|
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
3912
4101
|
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
3913
|
-
...
|
|
4102
|
+
...open4 ? {} : {
|
|
3914
4103
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
3915
4104
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
3916
4105
|
},
|
|
@@ -3942,6 +4131,24 @@ var Board = class _Board {
|
|
|
3942
4131
|
await writeJson(file, { ...cursors, channelsFrom });
|
|
3943
4132
|
}
|
|
3944
4133
|
}
|
|
4134
|
+
/**
|
|
4135
|
+
* A project's channels were once read, from the society scope, in the society's home
|
|
4136
|
+
* conversation, so a member had read each up to that cursor. Each project's conversation in the
|
|
4137
|
+
* society scope starts from it now, as each takes a conversation of its own.
|
|
4138
|
+
*/
|
|
4139
|
+
async startPlaceCursors() {
|
|
4140
|
+
for (const agent2 of await this.listAgents()) {
|
|
4141
|
+
const file = this.paths.agentCursors(agent2.name);
|
|
4142
|
+
if (!await exists(file)) {
|
|
4143
|
+
continue;
|
|
4144
|
+
}
|
|
4145
|
+
const cursors = await readJson(file, CursorsSchema);
|
|
4146
|
+
await writeJson(file, {
|
|
4147
|
+
...cursors,
|
|
4148
|
+
placesFrom: { [SOCIETY_SCOPE]: cursorOf(cursors, SOCIETY_SCOPE) }
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
3945
4152
|
/**
|
|
3946
4153
|
* Threads were once read in the scope's single conversation, so a member had read every thread
|
|
3947
4154
|
* up to its scope's cursor. Each scope's threads start from that cursor now, as each thread's
|
|
@@ -4082,13 +4289,15 @@ ${charter.purpose}
|
|
|
4082
4289
|
const token = mintToken();
|
|
4083
4290
|
const thread2 = threadOfPart(part);
|
|
4084
4291
|
const channel = channelOfPart(part);
|
|
4292
|
+
const place = placeOfPart(part);
|
|
4085
4293
|
this.turnTokens.set(hashToken(token), {
|
|
4086
4294
|
actor: {
|
|
4087
4295
|
name: agent2,
|
|
4088
4296
|
role: role2,
|
|
4089
4297
|
...scope === void 0 ? {} : { scope },
|
|
4090
4298
|
...thread2 === void 0 ? {} : { thread: thread2 },
|
|
4091
|
-
...channel === void 0 ? {} : { channel }
|
|
4299
|
+
...channel === void 0 ? {} : { channel },
|
|
4300
|
+
...place === void 0 ? {} : { place }
|
|
4092
4301
|
},
|
|
4093
4302
|
expiresAt: this.now().getTime() + ttlMs
|
|
4094
4303
|
});
|
|
@@ -4177,7 +4386,7 @@ ${charter.purpose}
|
|
|
4177
4386
|
});
|
|
4178
4387
|
}
|
|
4179
4388
|
signInsFile() {
|
|
4180
|
-
return
|
|
4389
|
+
return path7.join(this.paths.state(), "sign-ins.json");
|
|
4181
4390
|
}
|
|
4182
4391
|
async readSignIns() {
|
|
4183
4392
|
const file = this.signInsFile();
|
|
@@ -4274,7 +4483,7 @@ ${charter.purpose}
|
|
|
4274
4483
|
const files = await listFiles(this.paths.tasks(project2));
|
|
4275
4484
|
const tasks = [];
|
|
4276
4485
|
for (const file of files) {
|
|
4277
|
-
const doc = await readMarkdown(
|
|
4486
|
+
const doc = await readMarkdown(path7.join(this.paths.tasks(project2), file), TaskFrontmatterSchema);
|
|
4278
4487
|
tasks.push({ ...doc.data, body: doc.body });
|
|
4279
4488
|
}
|
|
4280
4489
|
return tasks;
|
|
@@ -4293,7 +4502,7 @@ ${charter.purpose}
|
|
|
4293
4502
|
const files = await listFiles(this.paths.proposals());
|
|
4294
4503
|
const proposals = [];
|
|
4295
4504
|
for (const file of files) {
|
|
4296
|
-
const doc = await readMarkdown(
|
|
4505
|
+
const doc = await readMarkdown(path7.join(this.paths.proposals(), file), ProposalFrontmatterSchema);
|
|
4297
4506
|
proposals.push({ ...doc.data, body: doc.body });
|
|
4298
4507
|
}
|
|
4299
4508
|
return proposals;
|
|
@@ -4320,7 +4529,7 @@ ${charter.purpose}
|
|
|
4320
4529
|
async listMembers() {
|
|
4321
4530
|
const members = [];
|
|
4322
4531
|
for (const file of await listFiles(this.paths.members())) {
|
|
4323
|
-
const doc = await readMarkdown(
|
|
4532
|
+
const doc = await readMarkdown(path7.join(this.paths.members(), file), MemberSchema);
|
|
4324
4533
|
members.push({ ...doc.data, profile: doc.body });
|
|
4325
4534
|
}
|
|
4326
4535
|
return members;
|
|
@@ -4339,7 +4548,7 @@ ${charter.purpose}
|
|
|
4339
4548
|
const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
|
|
4340
4549
|
const topics = [];
|
|
4341
4550
|
for (const file of await listFiles(dir)) {
|
|
4342
|
-
topics.push(await this.knowledgeAt(
|
|
4551
|
+
topics.push(await this.knowledgeAt(path7.join(dir, file), project2, file.replace(/\.md$/, "")));
|
|
4343
4552
|
}
|
|
4344
4553
|
return topics;
|
|
4345
4554
|
}
|
|
@@ -4369,7 +4578,7 @@ ${charter.purpose}
|
|
|
4369
4578
|
const aside = this.paths.removedKnowledge(project2 ?? SOCIETY_SCOPE);
|
|
4370
4579
|
let highest = 0;
|
|
4371
4580
|
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
4372
|
-
const copy = await this.knowledgeAt(
|
|
4581
|
+
const copy = await this.knowledgeAt(path7.join(aside, file), project2, topic);
|
|
4373
4582
|
highest = Math.max(highest, copy.revision);
|
|
4374
4583
|
}
|
|
4375
4584
|
return highest;
|
|
@@ -4385,7 +4594,7 @@ ${charter.purpose}
|
|
|
4385
4594
|
async listSkillsIn(dir, scope) {
|
|
4386
4595
|
const skills = [];
|
|
4387
4596
|
for (const entry of await listDirs(dir)) {
|
|
4388
|
-
const file =
|
|
4597
|
+
const file = path7.join(dir, entry, "SKILL.md");
|
|
4389
4598
|
if (!await exists(file)) {
|
|
4390
4599
|
continue;
|
|
4391
4600
|
}
|
|
@@ -4475,7 +4684,7 @@ ${charter.purpose}
|
|
|
4475
4684
|
if (!wanted.delete(file.slice(0, 26))) {
|
|
4476
4685
|
continue;
|
|
4477
4686
|
}
|
|
4478
|
-
const { data } = await readMarkdown(
|
|
4687
|
+
const { data } = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
4479
4688
|
found.push(MessageRefSchema.parse(data));
|
|
4480
4689
|
}
|
|
4481
4690
|
if (wanted.size === 0) {
|
|
@@ -4507,7 +4716,7 @@ ${charter.purpose}
|
|
|
4507
4716
|
if (!file.endsWith(".md")) {
|
|
4508
4717
|
continue;
|
|
4509
4718
|
}
|
|
4510
|
-
const doc = await readMarkdown(
|
|
4719
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4511
4720
|
if (doc.data.state !== "open") {
|
|
4512
4721
|
continue;
|
|
4513
4722
|
}
|
|
@@ -4534,7 +4743,7 @@ ${charter.purpose}
|
|
|
4534
4743
|
const all = [];
|
|
4535
4744
|
for (const threads of await this.threadScopes()) {
|
|
4536
4745
|
for (const file of await listFiles(threads)) {
|
|
4537
|
-
const doc = await readMarkdown(
|
|
4746
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
4538
4747
|
const messages = await listFiles(this.paths.threadMessages(threads, doc.data.id));
|
|
4539
4748
|
const last = messages.at(-1);
|
|
4540
4749
|
all.push({
|
|
@@ -4901,16 +5110,20 @@ ${DASHBOARD_LINE}
|
|
|
4901
5110
|
}
|
|
4902
5111
|
/**
|
|
4903
5112
|
* 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,
|
|
5113
|
+
* general channel, one of its other channels, one of its threads, or, in the society scope, a
|
|
5114
|
+
* project's channels. The heartbeat asks, so that it wakes a member where its unread
|
|
4905
5115
|
* messages are and nowhere else. Society channels count only for a member woken in the society
|
|
4906
5116
|
* scope; a project member reads them at its next turn. Two kinds of post are in the digest but not
|
|
4907
5117
|
* counted, and are read when the member wakes for something else: a task step's, which wakes
|
|
4908
5118
|
* 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.
|
|
5119
|
+
* whose signals that call for action wake their readers directly. A closed thread counts for
|
|
5120
|
+
* nothing: its conversation has ended, and a turn there could only read its posts and be refused
|
|
5121
|
+
* a reply, while a wake queued before it closed, as for a mention, still runs there.
|
|
4910
5122
|
*/
|
|
4911
5123
|
async unreadByConversation(actor) {
|
|
4912
5124
|
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4913
5125
|
const counts = /* @__PURE__ */ new Map();
|
|
5126
|
+
const closed = /* @__PURE__ */ new Map();
|
|
4914
5127
|
const unscoped = { name: actor.name, role: actor.role };
|
|
4915
5128
|
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4916
5129
|
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
@@ -4920,11 +5133,23 @@ ${DASHBOARD_LINE}
|
|
|
4920
5133
|
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4921
5134
|
continue;
|
|
4922
5135
|
}
|
|
5136
|
+
const thread2 = threadOfPart(part);
|
|
5137
|
+
if (thread2 !== void 0) {
|
|
5138
|
+
let ended = closed.get(thread2);
|
|
5139
|
+
if (ended === void 0) {
|
|
5140
|
+
ended = (await this.tryFindThread(thread2))?.thread.state === "closed";
|
|
5141
|
+
closed.set(thread2, ended);
|
|
5142
|
+
}
|
|
5143
|
+
if (ended) {
|
|
5144
|
+
continue;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
4923
5147
|
const key = conversationKey(scope, part);
|
|
4924
5148
|
const entry = counts.get(key) ?? {
|
|
4925
5149
|
scope,
|
|
4926
|
-
thread:
|
|
5150
|
+
thread: thread2 ?? null,
|
|
4927
5151
|
channel: channelOfPart(part) ?? null,
|
|
5152
|
+
place: placeOfPart(part) ?? null,
|
|
4928
5153
|
count: 0
|
|
4929
5154
|
};
|
|
4930
5155
|
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
@@ -4982,8 +5207,9 @@ ${DASHBOARD_LINE}
|
|
|
4982
5207
|
* thread it takes part in, and for the front desk, which every post by the user wakes, every post
|
|
4983
5208
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4984
5209
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4985
|
-
* top-level messages; for a
|
|
4986
|
-
*
|
|
5210
|
+
* top-level messages; for a project's in the society scope, the top-level messages of every
|
|
5211
|
+
* channel of a project the member is not in; for a scope's home, general's, since each thread,
|
|
5212
|
+
* each other channel, and each such project is a conversation of its own. Nothing newer than
|
|
4987
5213
|
* `upto` counts (see `digestSnapshot`).
|
|
4988
5214
|
*/
|
|
4989
5215
|
async digestSince(actor, cursors, upto, conversation) {
|
|
@@ -5003,11 +5229,17 @@ ${DASHBOARD_LINE}
|
|
|
5003
5229
|
const frontDesk = charter.wakeTriggers.includes(FRONT_DESK_TRIGGER);
|
|
5004
5230
|
const threadParticipation = /* @__PURE__ */ new Map();
|
|
5005
5231
|
const openChannels = /* @__PURE__ */ new Map();
|
|
5232
|
+
const liveProjects = /* @__PURE__ */ new Map();
|
|
5006
5233
|
const collected = [];
|
|
5007
5234
|
const newsFrom = charter.societyScope ? void 0 : newsStart(cursors);
|
|
5008
5235
|
const scopes = conversation !== void 0 ? [conversation.scope] : [...agent2.memberships, SOCIETY_SCOPE];
|
|
5009
5236
|
const starts = [
|
|
5010
|
-
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5237
|
+
...scopes.flatMap((each) => conversation === void 0 ? [
|
|
5238
|
+
cursorOf(cursors, each),
|
|
5239
|
+
threadStart(cursors, each),
|
|
5240
|
+
channelStart(cursors, each),
|
|
5241
|
+
placeStart(cursors, each)
|
|
5242
|
+
] : [cursorOf(cursors, each, conversation.part)]),
|
|
5011
5243
|
...newsFrom === void 0 ? [] : [newsFrom]
|
|
5012
5244
|
];
|
|
5013
5245
|
const known = starts.filter((start) => start !== null);
|
|
@@ -5017,7 +5249,7 @@ ${DASHBOARD_LINE}
|
|
|
5017
5249
|
const thread2 = message.thread ?? null;
|
|
5018
5250
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
5019
5251
|
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) {
|
|
5252
|
+
if ((conversation === void 0 || conversation.part === null) && (newsFrom === null || message.id > newsFrom) && subscribed.has(message.channel) && message.author !== actor.name) {
|
|
5021
5253
|
collected.push({
|
|
5022
5254
|
scope: conversation?.scope ?? SOCIETY_SCOPE,
|
|
5023
5255
|
part: null,
|
|
@@ -5028,12 +5260,18 @@ ${DASHBOARD_LINE}
|
|
|
5028
5260
|
continue;
|
|
5029
5261
|
}
|
|
5030
5262
|
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
|
|
5263
|
+
let open4 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
5264
|
+
if (named !== void 0 && open4 === void 0) {
|
|
5265
|
+
open4 = await this.channelOpen(message.channel);
|
|
5266
|
+
openChannels.set(message.channel, open4);
|
|
5267
|
+
}
|
|
5268
|
+
const project2 = thread2 === null ? placeConversation(filedIn, message.channel) : void 0;
|
|
5269
|
+
let live = project2 === void 0 ? void 0 : liveProjects.get(project2);
|
|
5270
|
+
if (project2 !== void 0 && live === void 0) {
|
|
5271
|
+
live = await this.projectLive(project2);
|
|
5272
|
+
liveProjects.set(project2, live);
|
|
5273
|
+
}
|
|
5274
|
+
const part = thread2 ?? (open4 === true ? `#${named}` : live === true ? `~${project2}` : null);
|
|
5037
5275
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
5038
5276
|
continue;
|
|
5039
5277
|
}
|
|
@@ -5098,7 +5336,7 @@ ${task2.body}`;
|
|
|
5098
5336
|
}
|
|
5099
5337
|
}
|
|
5100
5338
|
for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
|
|
5101
|
-
const content = await readFile4(
|
|
5339
|
+
const content = await readFile4(path7.join(this.paths.projectKnowledge(project2), file), "utf8");
|
|
5102
5340
|
if (content.toLowerCase().includes(needle)) {
|
|
5103
5341
|
hits.push({
|
|
5104
5342
|
kind: "knowledge",
|
|
@@ -5119,7 +5357,7 @@ ${task2.body}`;
|
|
|
5119
5357
|
shared.push({
|
|
5120
5358
|
kind: "knowledge",
|
|
5121
5359
|
ref: `society/knowledge/${file}`,
|
|
5122
|
-
file:
|
|
5360
|
+
file: path7.join(this.paths.societyKnowledge(), file)
|
|
5123
5361
|
});
|
|
5124
5362
|
}
|
|
5125
5363
|
for (const skill2 of await this.listSocietySkills()) {
|
|
@@ -5132,7 +5370,7 @@ ${task2.body}`;
|
|
|
5132
5370
|
shared.push({
|
|
5133
5371
|
kind: "memory",
|
|
5134
5372
|
ref: `memory/${file}`,
|
|
5135
|
-
file:
|
|
5373
|
+
file: path7.join(this.paths.agentMemory(actor.name), file)
|
|
5136
5374
|
});
|
|
5137
5375
|
}
|
|
5138
5376
|
for (const entry of shared) {
|
|
@@ -5222,7 +5460,7 @@ ${current.body}`);
|
|
|
5222
5460
|
});
|
|
5223
5461
|
const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
|
|
5224
5462
|
await mkdir4(aside, { recursive: true });
|
|
5225
|
-
await rename3(file,
|
|
5463
|
+
await rename3(file, path7.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
|
|
5226
5464
|
await this.events.append("knowledge.removed", actor.name, {
|
|
5227
5465
|
topic: args.topic,
|
|
5228
5466
|
project: args.project
|
|
@@ -5270,7 +5508,7 @@ ${current.body}`);
|
|
|
5270
5508
|
});
|
|
5271
5509
|
const aside = this.paths.removedSkills();
|
|
5272
5510
|
await mkdir4(aside, { recursive: true });
|
|
5273
|
-
await rename3(this.paths.societySkill(args.name),
|
|
5511
|
+
await rename3(this.paths.societySkill(args.name), path7.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
|
|
5274
5512
|
await this.events.append("skill.removed", actor.name, {
|
|
5275
5513
|
name: args.name,
|
|
5276
5514
|
reason: args.reason
|
|
@@ -5347,7 +5585,7 @@ ${current.body}`);
|
|
|
5347
5585
|
type,
|
|
5348
5586
|
size: bytes.byteLength
|
|
5349
5587
|
});
|
|
5350
|
-
const name =
|
|
5588
|
+
const name = path7.basename(args.path).replaceAll(/[[\]\\\r\n]/g, "");
|
|
5351
5589
|
return {
|
|
5352
5590
|
id,
|
|
5353
5591
|
type,
|
|
@@ -5373,7 +5611,7 @@ ${current.body}`);
|
|
|
5373
5611
|
async listCrons() {
|
|
5374
5612
|
const crons = [];
|
|
5375
5613
|
for (const file of await listFiles(this.paths.crons())) {
|
|
5376
|
-
crons.push(await this.readCronFile(
|
|
5614
|
+
crons.push(await this.readCronFile(path7.join(this.paths.crons(), file)));
|
|
5377
5615
|
}
|
|
5378
5616
|
return crons;
|
|
5379
5617
|
}
|
|
@@ -5495,11 +5733,16 @@ ${current.body}`);
|
|
|
5495
5733
|
}
|
|
5496
5734
|
/**
|
|
5497
5735
|
* 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,
|
|
5736
|
+
* the conversation of the turn that set it when it wakes its setter, a project's channels' in
|
|
5737
|
+
* the society scope included, else its home outside projects.
|
|
5499
5738
|
*/
|
|
5500
5739
|
async cronConversation(actor, agent2, named) {
|
|
5501
5740
|
const own = actor.name === agent2.name;
|
|
5502
5741
|
if (named.project === void 0 && named.thread_id === void 0 && named.channel === void 0) {
|
|
5742
|
+
if (own && actor.scope === SOCIETY_SCOPE && actor.place !== void 0) {
|
|
5743
|
+
await this.readActiveProject(actor.place);
|
|
5744
|
+
return { scope: SOCIETY_SCOPE, place: actor.place };
|
|
5745
|
+
}
|
|
5503
5746
|
return own && actor.scope !== void 0 ? this.cronConversation(actor, agent2, {
|
|
5504
5747
|
project: actor.scope,
|
|
5505
5748
|
thread_id: actor.thread,
|
|
@@ -5575,7 +5818,8 @@ ${current.body}`);
|
|
|
5575
5818
|
agent: cron2.agent,
|
|
5576
5819
|
project: cron2.scope,
|
|
5577
5820
|
...cron2.thread === void 0 ? {} : { thread: cron2.thread },
|
|
5578
|
-
...cron2.channel === void 0 ? {} : { channel: cron2.channel }
|
|
5821
|
+
...cron2.channel === void 0 ? {} : { channel: cron2.channel },
|
|
5822
|
+
...cron2.place === void 0 ? {} : { place: cron2.place }
|
|
5579
5823
|
};
|
|
5580
5824
|
}
|
|
5581
5825
|
async readCronFile(file) {
|
|
@@ -5786,17 +6030,20 @@ ${current.body}`);
|
|
|
5786
6030
|
async openThreadIds(dir, keep) {
|
|
5787
6031
|
const ids = /* @__PURE__ */ new Set();
|
|
5788
6032
|
for (const file of await listFiles(dir)) {
|
|
5789
|
-
const doc = await readMarkdown(
|
|
6033
|
+
const doc = await readMarkdown(path7.join(dir, file), ThreadFrontmatterSchema);
|
|
5790
6034
|
if (doc.data.state === "open" && keep({ ...doc.data, body: doc.body })) {
|
|
5791
6035
|
ids.add(doc.data.id);
|
|
5792
6036
|
}
|
|
5793
6037
|
}
|
|
5794
6038
|
return ids;
|
|
5795
6039
|
}
|
|
5796
|
-
/**
|
|
6040
|
+
/**
|
|
6041
|
+
* Refuses to archive a project while a turn runs in it, in a citizen's conversation of its
|
|
6042
|
+
* channels in the society scope, or in a thread its archive closes.
|
|
6043
|
+
*/
|
|
5797
6044
|
async assertProjectQuiet(actor, slug) {
|
|
5798
6045
|
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));
|
|
6046
|
+
this.assertNoTurnCut(actor, `project ${slug} cannot be archived`, (turn2) => turn2.scope === slug || turn2.place === slug || turn2.thread !== void 0 && threads.has(turn2.thread));
|
|
5800
6047
|
}
|
|
5801
6048
|
async closeThread(actor, input) {
|
|
5802
6049
|
const args = VerbInputs.close_thread.parse(input);
|
|
@@ -6337,7 +6584,8 @@ ${args.summary}`);
|
|
|
6337
6584
|
filedFrom: {
|
|
6338
6585
|
scope: actor.scope,
|
|
6339
6586
|
...actor.thread === void 0 ? {} : { thread: actor.thread },
|
|
6340
|
-
...actor.channel === void 0 ? {} : { channel: actor.channel }
|
|
6587
|
+
...actor.channel === void 0 ? {} : { channel: actor.channel },
|
|
6588
|
+
...actor.place === void 0 ? {} : { place: actor.place }
|
|
6341
6589
|
}
|
|
6342
6590
|
},
|
|
6343
6591
|
charter: parsed.data
|
|
@@ -6749,19 +6997,20 @@ ${rationale}`}`);
|
|
|
6749
6997
|
project: input.project,
|
|
6750
6998
|
...input.thread === void 0 ? {} : { thread: input.thread },
|
|
6751
6999
|
...input.channel === void 0 ? {} : { channel: input.channel },
|
|
7000
|
+
...input.place === void 0 ? {} : { place: input.place },
|
|
6752
7001
|
messages: [...input.messages]
|
|
6753
7002
|
});
|
|
6754
7003
|
});
|
|
6755
7004
|
}
|
|
6756
7005
|
/**
|
|
6757
7006
|
* Where a conversation's session ids and last turn are kept: the scope's home, or beside it a
|
|
6758
|
-
* thread's or a
|
|
7007
|
+
* thread's, a channel's, or a project's, named by `part` as `conversationPart` gives it.
|
|
6759
7008
|
*/
|
|
6760
7009
|
sessionDir(agent2, scope, part) {
|
|
6761
7010
|
return part === void 0 ? this.paths.agentProject(agent2, scope) : this.paths.agentConversation(agent2, scope, part);
|
|
6762
7011
|
}
|
|
6763
7012
|
async readSessions(agent2, project2, part) {
|
|
6764
|
-
const file =
|
|
7013
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
6765
7014
|
return await exists(file) ? readJson(file, SessionsFileSchema) : {};
|
|
6766
7015
|
}
|
|
6767
7016
|
/**
|
|
@@ -6772,15 +7021,34 @@ ${rationale}`}`);
|
|
|
6772
7021
|
await this.mutex.run(async () => {
|
|
6773
7022
|
const dir = this.sessionDir(agent2, project2, part);
|
|
6774
7023
|
await ensureDir(dir);
|
|
6775
|
-
const file =
|
|
7024
|
+
const file = path7.join(dir, "sessions.json");
|
|
6776
7025
|
const current = await exists(file) ? await readJson(file, SessionsFileSchema) : {};
|
|
6777
7026
|
const kept = current.runner === void 0 || current.runner === runner2 ? current : {};
|
|
6778
7027
|
await writeJson(file, { ...kept, [cli]: sessionId, runner: runner2 });
|
|
6779
7028
|
});
|
|
6780
7029
|
}
|
|
6781
|
-
/**
|
|
7030
|
+
/**
|
|
7031
|
+
* Drops a conversation's session for a CLI, so its next turn starts a new one, when the record
|
|
7032
|
+
* still names `sessionId`; returns whether it did.
|
|
7033
|
+
*/
|
|
7034
|
+
async dropSession(agent2, project2, cli, sessionId, part) {
|
|
7035
|
+
return this.mutex.run(async () => {
|
|
7036
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "sessions.json");
|
|
7037
|
+
if (!await exists(file)) {
|
|
7038
|
+
return false;
|
|
7039
|
+
}
|
|
7040
|
+
const current = await readJson(file, SessionsFileSchema);
|
|
7041
|
+
if (current[cli] !== sessionId) {
|
|
7042
|
+
return false;
|
|
7043
|
+
}
|
|
7044
|
+
const { [cli]: _dropped, ...kept } = current;
|
|
7045
|
+
await writeJson(file, kept);
|
|
7046
|
+
return true;
|
|
7047
|
+
});
|
|
7048
|
+
}
|
|
7049
|
+
/** The last turn of one conversation: the scope's home, a channel's, a project's, or a thread's. */
|
|
6782
7050
|
async readLastTurn(agent2, project2, part) {
|
|
6783
|
-
const file =
|
|
7051
|
+
const file = path7.join(this.sessionDir(agent2, project2, part), "last-turn.json");
|
|
6784
7052
|
return await exists(file) ? readJson(file, TurnRecordSchema) : null;
|
|
6785
7053
|
}
|
|
6786
7054
|
/** The latest turn a citizen took in a scope, in any of its conversations there. */
|
|
@@ -6788,8 +7056,9 @@ ${rationale}`}`);
|
|
|
6788
7056
|
let latest = await this.readLastTurn(agent2, project2);
|
|
6789
7057
|
const place = this.paths.agentProject(agent2, project2);
|
|
6790
7058
|
const parts = [
|
|
6791
|
-
...await listDirs(
|
|
6792
|
-
...(await listDirs(
|
|
7059
|
+
...await listDirs(path7.join(place, "threads")),
|
|
7060
|
+
...(await listDirs(path7.join(place, "channels"))).map((name) => `#${name}`),
|
|
7061
|
+
...(await listDirs(path7.join(place, "places"))).map((name) => `~${name}`)
|
|
6793
7062
|
];
|
|
6794
7063
|
for (const part of parts) {
|
|
6795
7064
|
const turn2 = await this.readLastTurn(agent2, project2, part);
|
|
@@ -6810,6 +7079,7 @@ ${rationale}`}`);
|
|
|
6810
7079
|
project: parsed.project,
|
|
6811
7080
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6812
7081
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7082
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6813
7083
|
trigger: parsed.trigger.kind,
|
|
6814
7084
|
session: parsed.session,
|
|
6815
7085
|
runner: parsed.runner
|
|
@@ -6839,7 +7109,8 @@ ${rationale}`}`);
|
|
|
6839
7109
|
agent: parsed.agent,
|
|
6840
7110
|
project: parsed.project,
|
|
6841
7111
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6842
|
-
...parsed.channel === void 0 ? {} : { channel: parsed.channel }
|
|
7112
|
+
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7113
|
+
...parsed.place === void 0 ? {} : { place: parsed.place }
|
|
6843
7114
|
});
|
|
6844
7115
|
}
|
|
6845
7116
|
await this.events.append(failed ? "turn.failed" : "turn.completed", parsed.agent, {
|
|
@@ -6847,6 +7118,7 @@ ${rationale}`}`);
|
|
|
6847
7118
|
project: parsed.project,
|
|
6848
7119
|
...parsed.thread === void 0 ? {} : { thread: parsed.thread },
|
|
6849
7120
|
...parsed.channel === void 0 ? {} : { channel: parsed.channel },
|
|
7121
|
+
...parsed.place === void 0 ? {} : { place: parsed.place },
|
|
6850
7122
|
trigger: parsed.trigger.kind,
|
|
6851
7123
|
session: parsed.session,
|
|
6852
7124
|
exitReason: parsed.exitReason,
|
|
@@ -6856,7 +7128,9 @@ ${rationale}`}`);
|
|
|
6856
7128
|
model: parsed.model,
|
|
6857
7129
|
summary: parsed.status?.summary ?? null,
|
|
6858
7130
|
needsUserDecision: parsed.status?.needsUserDecision ?? false,
|
|
6859
|
-
error: parsed.error
|
|
7131
|
+
error: parsed.error,
|
|
7132
|
+
...parsed.resumeFailed === void 0 ? {} : { resumeFailed: parsed.resumeFailed },
|
|
7133
|
+
...parsed.sessionDropped === true ? { sessionDropped: true } : {}
|
|
6860
7134
|
});
|
|
6861
7135
|
});
|
|
6862
7136
|
}
|
|
@@ -6893,6 +7167,20 @@ ${rationale}`}`);
|
|
|
6893
7167
|
return task2;
|
|
6894
7168
|
});
|
|
6895
7169
|
}
|
|
7170
|
+
/**
|
|
7171
|
+
* Posts the board's word on a task's work into the task's thread, such as commits a runner
|
|
7172
|
+
* could not put on its branch: as the board, with every `@name` written as a plain name, so it
|
|
7173
|
+
* wakes nobody and counts toward no heartbeat. Null, posting nothing, once the thread is closed.
|
|
7174
|
+
*/
|
|
7175
|
+
async postTaskNotice(taskId, body) {
|
|
7176
|
+
return this.mutex.run(async () => {
|
|
7177
|
+
const found = await this.tryFindThread(taskId);
|
|
7178
|
+
if (found === null || found.thread.state !== "open") {
|
|
7179
|
+
return null;
|
|
7180
|
+
}
|
|
7181
|
+
return this.appendThreadMessage(SYSTEM_ACTOR.name, taskId, withoutMentions(body));
|
|
7182
|
+
});
|
|
7183
|
+
}
|
|
6896
7184
|
/**
|
|
6897
7185
|
* Records that a citizen's workspace in a conversation that has ended holds work on no branch, so
|
|
6898
7186
|
* the scheduler gives the citizen a closing turn there to decide about it. False, recording
|
|
@@ -6903,7 +7191,7 @@ ${rationale}`}`);
|
|
|
6903
7191
|
const payload = {
|
|
6904
7192
|
agent: input.agent,
|
|
6905
7193
|
project: input.scope,
|
|
6906
|
-
...conversation.kind === "thread" ? { thread: conversation.id } : { channel: conversation.name }
|
|
7194
|
+
...conversation.kind === "thread" ? { thread: conversation.id } : conversation.kind === "channel" ? { channel: conversation.name } : { place: conversation.project }
|
|
6907
7195
|
};
|
|
6908
7196
|
return this.mutex.run(async () => {
|
|
6909
7197
|
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 +7243,13 @@ ${rationale}`}`);
|
|
|
6955
7243
|
}
|
|
6956
7244
|
/**
|
|
6957
7245
|
* 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
|
|
7246
|
+
* (for one from before tasks had threads), a channel archived, or a project archived, which ends
|
|
7247
|
+
* every citizen's conversation of its channels in the society scope.
|
|
6959
7248
|
*/
|
|
6960
7249
|
async conversationEnded(conversation) {
|
|
7250
|
+
if (conversation.kind === "place") {
|
|
7251
|
+
return !await this.projectLive(conversation.project);
|
|
7252
|
+
}
|
|
6961
7253
|
if (conversation.kind === "channel") {
|
|
6962
7254
|
const place = conversation.scope === SOCIETY_SCOPE ? null : conversation.scope;
|
|
6963
7255
|
return !await this.channelOpen(channelRef(place, conversation.name));
|
|
@@ -7031,11 +7323,11 @@ ${rationale}`}`);
|
|
|
7031
7323
|
}
|
|
7032
7324
|
/** Small named state files under data/state, for cursors the scheduler must keep across restarts. */
|
|
7033
7325
|
async readState(name, schema, fallback) {
|
|
7034
|
-
const file =
|
|
7326
|
+
const file = path7.join(this.paths.state(), `${name}.json`);
|
|
7035
7327
|
return await exists(file) ? readJson(file, schema) : fallback;
|
|
7036
7328
|
}
|
|
7037
7329
|
async writeState(name, value) {
|
|
7038
|
-
await this.mutex.run(() => writeJson(
|
|
7330
|
+
await this.mutex.run(() => writeJson(path7.join(this.paths.state(), `${name}.json`), value));
|
|
7039
7331
|
}
|
|
7040
7332
|
/** The project's dashboard, empty at revision 0 when it has none. */
|
|
7041
7333
|
async readDashboard(slug) {
|
|
@@ -7066,7 +7358,7 @@ ${rationale}`}`);
|
|
|
7066
7358
|
async listRunners() {
|
|
7067
7359
|
const runners = [];
|
|
7068
7360
|
for (const file of await listFiles(this.paths.runners())) {
|
|
7069
|
-
runners.push((await readMarkdown(
|
|
7361
|
+
runners.push((await readMarkdown(path7.join(this.paths.runners(), file), RunnerSchema)).data);
|
|
7070
7362
|
}
|
|
7071
7363
|
return runners;
|
|
7072
7364
|
}
|
|
@@ -7118,7 +7410,7 @@ ${rationale}`}`);
|
|
|
7118
7410
|
return this.runnerTokens.get(hashToken(token)) ?? null;
|
|
7119
7411
|
}
|
|
7120
7412
|
runnerTokensFile() {
|
|
7121
|
-
return
|
|
7413
|
+
return path7.join(this.paths.state(), "runners.json");
|
|
7122
7414
|
}
|
|
7123
7415
|
async readRunnerTokens() {
|
|
7124
7416
|
const file = this.runnerTokensFile();
|
|
@@ -7167,26 +7459,26 @@ ${rationale}`}`);
|
|
|
7167
7459
|
async listHomeConflicts(agent2) {
|
|
7168
7460
|
const home = this.paths.agent(agent2);
|
|
7169
7461
|
const found = [];
|
|
7170
|
-
const
|
|
7171
|
-
for (const entry of await
|
|
7462
|
+
const walk2 = async (dir, prefix) => {
|
|
7463
|
+
for (const entry of await readdir4(dir, { withFileTypes: true }).catch(() => [])) {
|
|
7172
7464
|
if (prefix === "" && SKIPPED_IN_HOME.has(entry.name)) {
|
|
7173
7465
|
continue;
|
|
7174
7466
|
}
|
|
7175
7467
|
const relative = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
7176
7468
|
if (entry.isDirectory()) {
|
|
7177
|
-
await
|
|
7469
|
+
await walk2(path7.join(dir, entry.name), relative);
|
|
7178
7470
|
continue;
|
|
7179
7471
|
}
|
|
7180
7472
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
7181
7473
|
if (file !== null) {
|
|
7182
|
-
const since = await this.homes.changedAt(home, relative) ?? (await stat3(
|
|
7474
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path7.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
7183
7475
|
if (since !== void 0) {
|
|
7184
7476
|
found.push({ path: relative, file, since });
|
|
7185
7477
|
}
|
|
7186
7478
|
}
|
|
7187
7479
|
}
|
|
7188
7480
|
};
|
|
7189
|
-
await
|
|
7481
|
+
await walk2(home, "");
|
|
7190
7482
|
return found.toSorted((a, b) => a.path.localeCompare(b.path));
|
|
7191
7483
|
}
|
|
7192
7484
|
/** The newest `limit` changes to an agent's home, newest first; empty while it is no repository. */
|
|
@@ -7263,7 +7555,7 @@ ${rationale}`}`);
|
|
|
7263
7555
|
async writeTurnRecord(record) {
|
|
7264
7556
|
const dir = this.sessionDir(record.agent, record.project, conversationPart(record));
|
|
7265
7557
|
await ensureDir(dir);
|
|
7266
|
-
await writeJson(
|
|
7558
|
+
await writeJson(path7.join(dir, "last-turn.json"), record);
|
|
7267
7559
|
}
|
|
7268
7560
|
// ---------------------------------------------------------------------------------------------
|
|
7269
7561
|
// Internals
|
|
@@ -7382,8 +7674,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7382
7674
|
async ensureAgentProject(name, slug) {
|
|
7383
7675
|
const dir = this.paths.agentProject(name, slug);
|
|
7384
7676
|
await ensureDir(dir);
|
|
7385
|
-
if (!await exists(
|
|
7386
|
-
await writeJson(
|
|
7677
|
+
if (!await exists(path7.join(dir, "sessions.json"))) {
|
|
7678
|
+
await writeJson(path7.join(dir, "sessions.json"), {});
|
|
7387
7679
|
}
|
|
7388
7680
|
}
|
|
7389
7681
|
/**
|
|
@@ -7415,7 +7707,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7415
7707
|
const at = turn2?.endedAt ?? turn2?.startedAt;
|
|
7416
7708
|
if (turn2 !== null && at !== void 0 && (lastTurnAt === void 0 || at > lastTurnAt)) {
|
|
7417
7709
|
lastTurnAt = at;
|
|
7418
|
-
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7710
|
+
lastTurnOutcome = `${turn2.trigger.kind} on ${scope}${turnConversation(turn2)}: ${turn2.exitReason ?? "running"}${turn2.status === null ? "" : `, ${turn2.status.summary.slice(0, 160)}`}`;
|
|
7419
7711
|
lastModel = turn2.model ?? void 0;
|
|
7420
7712
|
}
|
|
7421
7713
|
}
|
|
@@ -7635,7 +7927,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7635
7927
|
const file = this.paths.societySkillFile(skill2.name);
|
|
7636
7928
|
const replaced = await exists(file);
|
|
7637
7929
|
const taken = proposal2.skillFiles;
|
|
7638
|
-
const staged =
|
|
7930
|
+
const staged = path7.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
|
|
7639
7931
|
if (skill2.body === void 0 && (taken === void 0 || !await exists(staged))) {
|
|
7640
7932
|
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
|
|
7641
7933
|
}
|
|
@@ -7698,8 +7990,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7698
7990
|
const dir = this.paths.proposalFiles(proposal2.id);
|
|
7699
7991
|
await rm2(dir, { recursive: true, force: true });
|
|
7700
7992
|
for (const entry of taken.files) {
|
|
7701
|
-
const target =
|
|
7702
|
-
await ensureDir(
|
|
7993
|
+
const target = path7.join(dir, ...entry.path.split("/"));
|
|
7994
|
+
await ensureDir(path7.dirname(target));
|
|
7703
7995
|
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7704
7996
|
await chmod2(target, entry.executable ? 493 : 420);
|
|
7705
7997
|
}
|
|
@@ -7757,7 +8049,8 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7757
8049
|
const where = {
|
|
7758
8050
|
scope: turn2.project,
|
|
7759
8051
|
...turn2.thread === void 0 ? {} : { thread: turn2.thread },
|
|
7760
|
-
...turn2.channel === void 0 ? {} : { channel: turn2.channel }
|
|
8052
|
+
...turn2.channel === void 0 ? {} : { channel: turn2.channel },
|
|
8053
|
+
...turn2.place === void 0 ? {} : { place: turn2.place }
|
|
7761
8054
|
};
|
|
7762
8055
|
await this.mutex.run(async () => {
|
|
7763
8056
|
for (const proposal2 of await this.listProposals()) {
|
|
@@ -7789,16 +8082,25 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7789
8082
|
if (!isSafeRelativePath(entry.path)) {
|
|
7790
8083
|
continue;
|
|
7791
8084
|
}
|
|
7792
|
-
const bytes = await readFile4(
|
|
7793
|
-
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
8085
|
+
const bytes = await readFile4(path7.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7794
8086
|
files.push({
|
|
7795
8087
|
...entry,
|
|
7796
|
-
|
|
7797
|
-
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
8088
|
+
...bytes === null ? { text: null, truncated: false } : skillFileText(bytes)
|
|
7798
8089
|
});
|
|
7799
8090
|
}
|
|
7800
8091
|
return files;
|
|
7801
8092
|
}
|
|
8093
|
+
/**
|
|
8094
|
+
* A society skill's folder as it is published, with each file's text, for the skill's view; an
|
|
8095
|
+
* unknown skill, or a folder without a SKILL.md, is not found.
|
|
8096
|
+
*/
|
|
8097
|
+
async readSocietySkillFiles(name) {
|
|
8098
|
+
const files = await readSkillFolder(this.paths.societySkill(NameSchema.parse(name)));
|
|
8099
|
+
if (files === null) {
|
|
8100
|
+
throw new BoardError("NOT_FOUND", `the society has no skill ${name}`);
|
|
8101
|
+
}
|
|
8102
|
+
return files;
|
|
8103
|
+
}
|
|
7802
8104
|
async validateAddAgent(input) {
|
|
7803
8105
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
7804
8106
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -7929,7 +8231,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7929
8231
|
this.assertNoTurnCut(actor, `${ref} cannot be archived`, (turn2) => turn2.channel === channel && turn2.scope === scope || turn2.thread !== void 0 && closing.has(turn2.thread));
|
|
7930
8232
|
const threadsClosed = [];
|
|
7931
8233
|
for (const file of await listFiles(threads)) {
|
|
7932
|
-
const doc = await readMarkdown(
|
|
8234
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
7933
8235
|
if (doc.data.channel !== ref || doc.data.state !== "open") {
|
|
7934
8236
|
continue;
|
|
7935
8237
|
}
|
|
@@ -8035,7 +8337,8 @@ ${charter.purpose}
|
|
|
8035
8337
|
}
|
|
8036
8338
|
}
|
|
8037
8339
|
/**
|
|
8038
|
-
* Every member leaves, every open thread on its channels closes,
|
|
8340
|
+
* Every member leaves, every open thread on its channels closes, every citizen's conversation of
|
|
8341
|
+
* its channels in the society scope ends with the crons there, and the project is marked; its
|
|
8039
8342
|
* files, repository, and history stay. The notice is the board's, since the reason is free text
|
|
8040
8343
|
* a proposer wrote and a board-authored mention would wake whoever it names.
|
|
8041
8344
|
*/
|
|
@@ -8060,7 +8363,7 @@ ${charter.purpose}
|
|
|
8060
8363
|
const threads = this.paths.threads(slug);
|
|
8061
8364
|
const threadsClosed = [];
|
|
8062
8365
|
for (const file of await listFiles(threads)) {
|
|
8063
|
-
const doc = await readMarkdown(
|
|
8366
|
+
const doc = await readMarkdown(path7.join(threads, file), ThreadFrontmatterSchema);
|
|
8064
8367
|
if (doc.data.state !== "open") {
|
|
8065
8368
|
continue;
|
|
8066
8369
|
}
|
|
@@ -8078,7 +8381,7 @@ ${charter.purpose}
|
|
|
8078
8381
|
});
|
|
8079
8382
|
threadsClosed.push(doc.data.id);
|
|
8080
8383
|
}
|
|
8081
|
-
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
8384
|
+
await this.endCronsUnlocked(by, `project ${slug} was archived`, (cron2) => cron2.scope === slug || cron2.place === slug || cron2.thread !== void 0 && threadsClosed.includes(cron2.thread));
|
|
8082
8385
|
const project2 = await this.updateProject(slug, (p) => ({
|
|
8083
8386
|
...p,
|
|
8084
8387
|
members: [],
|
|
@@ -8135,10 +8438,10 @@ ${charter.purpose}
|
|
|
8135
8438
|
...project2,
|
|
8136
8439
|
members: project2.members.filter((member) => member !== name)
|
|
8137
8440
|
}));
|
|
8138
|
-
const sessions =
|
|
8441
|
+
const sessions = path7.join(this.paths.agentProject(name, slug), "sessions.json");
|
|
8139
8442
|
if (await exists(sessions)) {
|
|
8140
8443
|
const archived = `sessions.archived.${ts.replace(/[:.]/g, "-")}.json`;
|
|
8141
|
-
await rename3(sessions,
|
|
8444
|
+
await rename3(sessions, path7.join(this.paths.agentProject(name, slug), archived));
|
|
8142
8445
|
}
|
|
8143
8446
|
}
|
|
8144
8447
|
this.tokenIndex.delete(current.tokenHash);
|
|
@@ -8218,6 +8521,17 @@ ${charter.purpose}
|
|
|
8218
8521
|
return false;
|
|
8219
8522
|
}
|
|
8220
8523
|
}
|
|
8524
|
+
/**
|
|
8525
|
+
* Whether a project exists and is not archived, so citizens' conversations of its channels in
|
|
8526
|
+
* the society scope are still going.
|
|
8527
|
+
*/
|
|
8528
|
+
async projectLive(slug) {
|
|
8529
|
+
try {
|
|
8530
|
+
return (await this.readProject(slug)).archived === void 0;
|
|
8531
|
+
} catch {
|
|
8532
|
+
return false;
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8221
8535
|
/**
|
|
8222
8536
|
* A channel that takes new posts, threads, tasks, and followers: it exists, it is not archived,
|
|
8223
8537
|
* and its project is not archived.
|
|
@@ -8468,7 +8782,7 @@ ${charter.purpose}
|
|
|
8468
8782
|
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
8469
8783
|
continue;
|
|
8470
8784
|
}
|
|
8471
|
-
const doc = await readMarkdown(
|
|
8785
|
+
const doc = await readMarkdown(path7.join(dir, file), MessageFrontmatterSchema);
|
|
8472
8786
|
messages.push({ ...doc.data, body: doc.body });
|
|
8473
8787
|
}
|
|
8474
8788
|
return messages;
|
|
@@ -8525,7 +8839,7 @@ function globals() {
|
|
|
8525
8839
|
function print(value, human) {
|
|
8526
8840
|
console.log(globals().json ? JSON.stringify(value, null, 2) : human());
|
|
8527
8841
|
}
|
|
8528
|
-
async function
|
|
8842
|
+
async function open3() {
|
|
8529
8843
|
return Board.open(globals().data);
|
|
8530
8844
|
}
|
|
8531
8845
|
async function actorFor(board, as) {
|
|
@@ -8571,7 +8885,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8571
8885
|
if (!opts.rotate) {
|
|
8572
8886
|
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
8887
|
}
|
|
8574
|
-
const board = await
|
|
8888
|
+
const board = await open3();
|
|
8575
8889
|
const userToken = await board.rotateUserToken();
|
|
8576
8890
|
print({ userToken }, () => [
|
|
8577
8891
|
`User token (shown once): ${userToken}`,
|
|
@@ -8580,7 +8894,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
8580
8894
|
});
|
|
8581
8895
|
var project = program.command("project").description("Manage projects");
|
|
8582
8896
|
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
|
|
8897
|
+
const board = await open3();
|
|
8584
8898
|
const added = await board.addProject(board.userActor(), {
|
|
8585
8899
|
slug,
|
|
8586
8900
|
name: opts.name,
|
|
@@ -8590,7 +8904,7 @@ project.command("add <slug>").description("Add a project with its default channe
|
|
|
8590
8904
|
print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
|
|
8591
8905
|
});
|
|
8592
8906
|
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
|
|
8907
|
+
const board = await open3();
|
|
8594
8908
|
const configured = await board.configureProject(await actorFor(board, opts.as), {
|
|
8595
8909
|
project: slug,
|
|
8596
8910
|
on_done: CompletionEffectSchema.parse(opts.onDone)
|
|
@@ -8598,7 +8912,7 @@ project.command("configure <slug>").description("Set a project's completion effe
|
|
|
8598
8912
|
print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
|
|
8599
8913
|
});
|
|
8600
8914
|
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
|
|
8915
|
+
const board = await open3();
|
|
8602
8916
|
const archived = await board.archiveProject(board.userActor(), {
|
|
8603
8917
|
project: slug,
|
|
8604
8918
|
reason: opts.reason
|
|
@@ -8606,13 +8920,13 @@ project.command("archive <slug>").description("Archive a project with no task in
|
|
|
8606
8920
|
print(archived, () => `Project ${archived.slug} archived`);
|
|
8607
8921
|
});
|
|
8608
8922
|
project.command("list").description("List projects").action(async () => {
|
|
8609
|
-
const board = await
|
|
8923
|
+
const board = await open3();
|
|
8610
8924
|
const projects = await board.listProjects();
|
|
8611
8925
|
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
8926
|
});
|
|
8613
8927
|
var runner = program.command("runner").description("Manage runners");
|
|
8614
8928
|
runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
|
|
8615
|
-
const board = await
|
|
8929
|
+
const board = await open3();
|
|
8616
8930
|
const { runner: added, token } = await board.addRunner(board.userActor(), name);
|
|
8617
8931
|
print({ runner: added, token }, () => [
|
|
8618
8932
|
`Runner ${added.name} added`,
|
|
@@ -8621,13 +8935,13 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
8621
8935
|
].join("\n"));
|
|
8622
8936
|
});
|
|
8623
8937
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
8624
|
-
const board = await
|
|
8938
|
+
const board = await open3();
|
|
8625
8939
|
const runners = await board.listRunners();
|
|
8626
8940
|
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
8941
|
});
|
|
8628
8942
|
var agent = program.command("agent").description("Manage agents");
|
|
8629
8943
|
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
|
|
8944
|
+
const board = await open3();
|
|
8631
8945
|
const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
|
|
8632
8946
|
const { agent: added, token } = await board.addAgent(board.userActor(), {
|
|
8633
8947
|
name,
|
|
@@ -8640,7 +8954,7 @@ agent.command("add <name>").description("Add an agent and print its board token
|
|
|
8640
8954
|
print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
|
|
8641
8955
|
});
|
|
8642
8956
|
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
|
|
8957
|
+
const board = await open3();
|
|
8644
8958
|
if (chosen === void 0) {
|
|
8645
8959
|
const current = await board.readAgent(name);
|
|
8646
8960
|
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 +8964,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
|
|
|
8650
8964
|
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
8965
|
});
|
|
8652
8966
|
agent.command("list").description("List agents").action(async () => {
|
|
8653
|
-
const board = await
|
|
8967
|
+
const board = await open3();
|
|
8654
8968
|
const agents = await board.listAgents();
|
|
8655
8969
|
const members = await board.listMembers();
|
|
8656
8970
|
print(agents, () => agents.map((a) => {
|
|
@@ -8659,28 +8973,28 @@ agent.command("list").description("List agents").action(async () => {
|
|
|
8659
8973
|
}).join("\n"));
|
|
8660
8974
|
});
|
|
8661
8975
|
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
|
|
8976
|
+
const board = await open3();
|
|
8663
8977
|
const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
|
|
8664
8978
|
print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
|
|
8665
8979
|
});
|
|
8666
8980
|
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
|
|
8981
|
+
const board = await open3();
|
|
8668
8982
|
const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
|
|
8669
8983
|
print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
|
|
8670
8984
|
});
|
|
8671
8985
|
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
|
|
8986
|
+
const board = await open3();
|
|
8673
8987
|
const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
|
|
8674
8988
|
print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
|
|
8675
8989
|
});
|
|
8676
8990
|
var proposal = program.command("proposal").description("Governance proposals");
|
|
8677
8991
|
proposal.command("list").description("List proposals").action(async () => {
|
|
8678
|
-
const board = await
|
|
8992
|
+
const board = await open3();
|
|
8679
8993
|
const proposals = await board.listProposals();
|
|
8680
8994
|
print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
|
|
8681
8995
|
});
|
|
8682
8996
|
proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
|
|
8683
|
-
const board = await
|
|
8997
|
+
const board = await open3();
|
|
8684
8998
|
const found = await board.readProposal(id);
|
|
8685
8999
|
print(found, () => [
|
|
8686
9000
|
`${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
|
|
@@ -8690,7 +9004,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
|
|
|
8690
9004
|
].filter((line) => line.length > 0).join("\n"));
|
|
8691
9005
|
});
|
|
8692
9006
|
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
|
|
9007
|
+
const board = await open3();
|
|
8694
9008
|
const created = await board.propose(await actorFor(board, opts.as), {
|
|
8695
9009
|
kind: ProposalKindSchema.parse(opts.kind),
|
|
8696
9010
|
charter: parseCharter(opts.charter),
|
|
@@ -8699,7 +9013,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
|
|
|
8699
9013
|
print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
|
|
8700
9014
|
});
|
|
8701
9015
|
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
|
|
9016
|
+
const board = await open3();
|
|
8703
9017
|
const decision = await board.approve(await actorFor(board, opts.as), {
|
|
8704
9018
|
proposal_id: id,
|
|
8705
9019
|
...opts.reason === void 0 ? {} : { reason: opts.reason }
|
|
@@ -8708,7 +9022,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
|
|
|
8708
9022
|
print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
|
|
8709
9023
|
});
|
|
8710
9024
|
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
|
|
9025
|
+
const board = await open3();
|
|
8712
9026
|
const decision = await board.reject(await actorFor(board, opts.as), {
|
|
8713
9027
|
proposal_id: id,
|
|
8714
9028
|
reason: opts.reason
|
|
@@ -8717,14 +9031,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
|
|
|
8717
9031
|
});
|
|
8718
9032
|
var role = program.command("role").description("Role charters");
|
|
8719
9033
|
role.command("list").description("List role charters").action(async () => {
|
|
8720
|
-
const board = await
|
|
9034
|
+
const board = await open3();
|
|
8721
9035
|
const roles = await board.listRoles();
|
|
8722
9036
|
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
9037
|
});
|
|
8724
9038
|
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
9039
|
"heartbeat"
|
|
8726
9040
|
]).action(async (name, opts) => {
|
|
8727
|
-
const board = await
|
|
9041
|
+
const board = await open3();
|
|
8728
9042
|
const added = await board.setRoleCharter(board.userActor(), {
|
|
8729
9043
|
name,
|
|
8730
9044
|
purpose: opts.purpose,
|
|
@@ -8734,7 +9048,7 @@ role.command("add <name>").description("Write a new role charter directly as the
|
|
|
8734
9048
|
print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
|
|
8735
9049
|
});
|
|
8736
9050
|
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
|
|
9051
|
+
const board = await open3();
|
|
8738
9052
|
const current = await board.readRole(name);
|
|
8739
9053
|
const updated = await board.setRoleCharter(board.userActor(), {
|
|
8740
9054
|
...current,
|
|
@@ -8746,7 +9060,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
|
|
|
8746
9060
|
});
|
|
8747
9061
|
var channelCommand = program.command("channel").description("Channels");
|
|
8748
9062
|
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
|
|
9063
|
+
const board = await open3();
|
|
8750
9064
|
const parsed = parseChannelRef(ref);
|
|
8751
9065
|
const added = await board.addChannel(await actorFor(board, opts.as), {
|
|
8752
9066
|
project: parsed.project,
|
|
@@ -8756,7 +9070,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
|
|
|
8756
9070
|
print({ channel: added }, () => `Channel ${added} opened`);
|
|
8757
9071
|
});
|
|
8758
9072
|
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
|
|
9073
|
+
const board = await open3();
|
|
8760
9074
|
const archived = await board.archiveChannel(await actorFor(board, opts.as), {
|
|
8761
9075
|
channel: ref,
|
|
8762
9076
|
reason: opts.reason
|
|
@@ -8764,12 +9078,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
|
|
|
8764
9078
|
print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
|
|
8765
9079
|
});
|
|
8766
9080
|
program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
|
|
8767
|
-
const board = await
|
|
9081
|
+
const board = await open3();
|
|
8768
9082
|
const signals = await board.listSignals(Number(opts.limit));
|
|
8769
9083
|
print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
|
|
8770
9084
|
});
|
|
8771
9085
|
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
|
|
9086
|
+
const board = await open3();
|
|
8773
9087
|
const who = await actorFor(board, opts.as);
|
|
8774
9088
|
const message = await board.postMessage(who, {
|
|
8775
9089
|
channel,
|
|
@@ -8779,13 +9093,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
|
|
|
8779
9093
|
print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
|
|
8780
9094
|
});
|
|
8781
9095
|
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
|
|
9096
|
+
const board = await open3();
|
|
8783
9097
|
const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
|
|
8784
9098
|
print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
|
|
8785
9099
|
});
|
|
8786
9100
|
var task = program.command("task").description("Manage tasks");
|
|
8787
9101
|
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
|
|
9102
|
+
const board = await open3();
|
|
8789
9103
|
const who = await actorFor(board, opts.as);
|
|
8790
9104
|
const created = await board.createTask(who, {
|
|
8791
9105
|
project: projectSlug,
|
|
@@ -8801,17 +9115,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
|
|
|
8801
9115
|
].join("\n"));
|
|
8802
9116
|
});
|
|
8803
9117
|
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
|
|
9118
|
+
const board = await open3();
|
|
8805
9119
|
const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
|
|
8806
9120
|
print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
|
|
8807
9121
|
});
|
|
8808
9122
|
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
|
|
9123
|
+
const board = await open3();
|
|
8810
9124
|
const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
|
|
8811
9125
|
print(released, () => `Stage ${released.stage} of task ${released.id} released`);
|
|
8812
9126
|
});
|
|
8813
9127
|
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
|
|
9128
|
+
const board = await open3();
|
|
8815
9129
|
const advanced = await board.advanceTask(await actorFor(board, opts.as), {
|
|
8816
9130
|
task_id: id,
|
|
8817
9131
|
...opts.note === void 0 ? {} : { note: opts.note }
|
|
@@ -8820,7 +9134,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
|
|
|
8820
9134
|
${describePlan(advanced)}`);
|
|
8821
9135
|
});
|
|
8822
9136
|
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
|
|
9137
|
+
const board = await open3();
|
|
8824
9138
|
const planned = await board.planTask(await actorFor(board, opts.as), {
|
|
8825
9139
|
task_id: id,
|
|
8826
9140
|
stages: PlanEditSchema.parse(JSON.parse(stages)),
|
|
@@ -8829,7 +9143,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
|
|
|
8829
9143
|
print(planned, () => describePlan(planned));
|
|
8830
9144
|
});
|
|
8831
9145
|
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
|
|
9146
|
+
const board = await open3();
|
|
8833
9147
|
const updated = await board.updateTask(await actorFor(board, opts.as), {
|
|
8834
9148
|
task_id: id,
|
|
8835
9149
|
...opts.stage === void 0 ? {} : { stage: opts.stage },
|
|
@@ -8840,7 +9154,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
|
|
|
8840
9154
|
print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
|
|
8841
9155
|
});
|
|
8842
9156
|
task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
|
|
8843
|
-
const board = await
|
|
9157
|
+
const board = await open3();
|
|
8844
9158
|
const found = await board.getTask(board.userActor(), { task_id: id });
|
|
8845
9159
|
const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
|
|
8846
9160
|
const said = found.messages.map((message) => {
|
|
@@ -8860,7 +9174,7 @@ ${message.body.trim()}
|
|
|
8860
9174
|
].join("\n"));
|
|
8861
9175
|
});
|
|
8862
9176
|
task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
|
|
8863
|
-
const board = await
|
|
9177
|
+
const board = await open3();
|
|
8864
9178
|
const tasks = await board.listTasks(projectSlug);
|
|
8865
9179
|
print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
|
|
8866
9180
|
const stage = t.stages[stageIndex(t, t.stage)];
|
|
@@ -8869,7 +9183,7 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
8869
9183
|
});
|
|
8870
9184
|
var thread = program.command("thread").description("Manage threads");
|
|
8871
9185
|
thread.command("open [taskId]").description("Open a thread on a task, on a proposal, or on a channel with a title").option("--proposal <id>", "open the proposal's thread").option("--channel <ref>", "the channel it hangs off; required for a topic").option("--title <text>", "the thread's title; required for a topic").option("--as <agent>", "act as this agent instead of the user").action(async (taskId, opts) => {
|
|
8872
|
-
const board = await
|
|
9186
|
+
const board = await open3();
|
|
8873
9187
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
8874
9188
|
...taskId === void 0 ? {} : { task_id: taskId },
|
|
8875
9189
|
...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
|
|
@@ -8879,7 +9193,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
|
|
|
8879
9193
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
8880
9194
|
});
|
|
8881
9195
|
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
|
|
9196
|
+
const board = await open3();
|
|
8883
9197
|
const summary = await board.closeThread(await actorFor(board, opts.as), {
|
|
8884
9198
|
thread_id: threadId,
|
|
8885
9199
|
summary: opts.summary
|
|
@@ -8887,7 +9201,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
|
|
|
8887
9201
|
print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
|
|
8888
9202
|
});
|
|
8889
9203
|
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
|
|
9204
|
+
const board = await open3();
|
|
8891
9205
|
const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
|
|
8892
9206
|
print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
|
|
8893
9207
|
const about = t.subject === void 0 ? "topic" : t.subject.kind;
|
|
@@ -8895,7 +9209,7 @@ thread.command("list").description("List threads, the society's first, then each
|
|
|
8895
9209
|
}).join("\n"));
|
|
8896
9210
|
});
|
|
8897
9211
|
thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
|
|
8898
|
-
const board = await
|
|
9212
|
+
const board = await open3();
|
|
8899
9213
|
const found = await board.readThread(threadId);
|
|
8900
9214
|
const messages = await board.listThread(threadId);
|
|
8901
9215
|
print({ thread: found, messages }, () => [
|
|
@@ -8906,17 +9220,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
|
|
|
8906
9220
|
].join("\n"));
|
|
8907
9221
|
});
|
|
8908
9222
|
program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
|
|
8909
|
-
const board = await
|
|
9223
|
+
const board = await open3();
|
|
8910
9224
|
await board.setPaused(board.userActor(), true);
|
|
8911
9225
|
print({ paused: true }, () => "Society paused");
|
|
8912
9226
|
});
|
|
8913
9227
|
program.command("resume").description("Allow wakeups again").action(async () => {
|
|
8914
|
-
const board = await
|
|
9228
|
+
const board = await open3();
|
|
8915
9229
|
await board.setPaused(board.userActor(), false);
|
|
8916
9230
|
print({ paused: false }, () => "Society resumed");
|
|
8917
9231
|
});
|
|
8918
9232
|
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
|
|
9233
|
+
const board = await open3();
|
|
8920
9234
|
const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
|
|
8921
9235
|
print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
|
|
8922
9236
|
});
|
|
@@ -8924,7 +9238,8 @@ function describeCron(cron2, timeZone) {
|
|
|
8924
9238
|
const where = [
|
|
8925
9239
|
cron2.scope,
|
|
8926
9240
|
cron2.thread,
|
|
8927
|
-
cron2.channel === void 0 ? void 0 : `#${cron2.channel}
|
|
9241
|
+
cron2.channel === void 0 ? void 0 : `#${cron2.channel}`,
|
|
9242
|
+
cron2.place === void 0 ? void 0 : `~${cron2.place}`
|
|
8928
9243
|
].filter((part) => part !== void 0).join("/");
|
|
8929
9244
|
const next = nextFire(cron2, timeZone);
|
|
8930
9245
|
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 +9247,13 @@ function describeCron(cron2, timeZone) {
|
|
|
8932
9247
|
}
|
|
8933
9248
|
var cron = program.command("cron").description("Crons that wake citizens on a clock");
|
|
8934
9249
|
cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
|
|
8935
|
-
const board = await
|
|
9250
|
+
const board = await open3();
|
|
8936
9251
|
const { timezone } = await board.society();
|
|
8937
9252
|
const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
|
|
8938
9253
|
print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
|
|
8939
9254
|
});
|
|
8940
9255
|
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
|
|
9256
|
+
const board = await open3();
|
|
8942
9257
|
const created = await board.createCron(await actorFor(board, opts.as), {
|
|
8943
9258
|
title,
|
|
8944
9259
|
note: opts.note,
|
|
@@ -8958,7 +9273,7 @@ for (const [name, paused] of [
|
|
|
8958
9273
|
["resume", false]
|
|
8959
9274
|
]) {
|
|
8960
9275
|
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
|
|
9276
|
+
const board = await open3();
|
|
8962
9277
|
const updated = await board.updateCron(await actorFor(board, opts.as), {
|
|
8963
9278
|
cron_id: id,
|
|
8964
9279
|
paused
|
|
@@ -8968,7 +9283,7 @@ for (const [name, paused] of [
|
|
|
8968
9283
|
});
|
|
8969
9284
|
}
|
|
8970
9285
|
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
|
|
9286
|
+
const board = await open3();
|
|
8972
9287
|
const removed = await board.removeCron(await actorFor(board, opts.as), {
|
|
8973
9288
|
cron_id: id,
|
|
8974
9289
|
reason: opts.reason
|
|
@@ -8977,12 +9292,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
|
|
|
8977
9292
|
});
|
|
8978
9293
|
var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
|
|
8979
9294
|
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
|
|
9295
|
+
const board = await open3();
|
|
8981
9296
|
const topics = await board.listKnowledge(projectSlug ?? null);
|
|
8982
9297
|
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
9298
|
});
|
|
8984
9299
|
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
|
|
9300
|
+
const board = await open3();
|
|
8986
9301
|
const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
|
|
8987
9302
|
if (found === void 0) {
|
|
8988
9303
|
throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
|
|
@@ -8990,8 +9305,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
|
|
|
8990
9305
|
print(found, () => found.body);
|
|
8991
9306
|
});
|
|
8992
9307
|
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(
|
|
9308
|
+
const board = await open3();
|
|
9309
|
+
const body = opts.file === void 0 ? await readStdin() : await readFile5(path8.resolve(opts.file), "utf8");
|
|
8995
9310
|
const written = await board.writeKnowledge(await actorFor(board, opts.as), {
|
|
8996
9311
|
project: opts.project ?? null,
|
|
8997
9312
|
topic,
|
|
@@ -9002,7 +9317,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
|
|
|
9002
9317
|
});
|
|
9003
9318
|
var skill = program.command("skill").description("Skills promoted to the society");
|
|
9004
9319
|
skill.command("list").description("List the society's skills, and each member's own").action(async () => {
|
|
9005
|
-
const board = await
|
|
9320
|
+
const board = await open3();
|
|
9006
9321
|
const society = await board.listSocietySkills();
|
|
9007
9322
|
const own = [];
|
|
9008
9323
|
for (const member of await board.listAgents()) {
|
|
@@ -9017,7 +9332,7 @@ skill.command("list").description("List the society's skills, and each member's
|
|
|
9017
9332
|
].join("\n") || "No skills yet.");
|
|
9018
9333
|
});
|
|
9019
9334
|
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
|
|
9335
|
+
const board = await open3();
|
|
9021
9336
|
const removed = await board.removeSkill(await actorFor(board, opts.as), {
|
|
9022
9337
|
name,
|
|
9023
9338
|
reason: opts.reason
|
|
@@ -9026,13 +9341,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
|
|
|
9026
9341
|
});
|
|
9027
9342
|
var turn = program.command("turn").description("Turn operations");
|
|
9028
9343
|
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
|
|
9344
|
+
const board = await open3();
|
|
9030
9345
|
const actor = await board.actorFor(agentName);
|
|
9031
9346
|
const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
|
|
9032
9347
|
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
9348
|
});
|
|
9034
9349
|
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
|
|
9350
|
+
const board = await open3();
|
|
9036
9351
|
const event = await board.requestWake(board.userActor(), {
|
|
9037
9352
|
agent: agentName,
|
|
9038
9353
|
project: opts.project,
|