@kubitnodes/stellaris 0.3.19 → 0.3.21
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 +394 -160
- package/dist/server/index.js +481 -147
- package/package.json +1 -1
- package/web/dist/assets/BackLink-23Q1JvmZ.js +1 -0
- package/web/dist/assets/{ChannelForms-BjD5Lp_u.js → ChannelForms-0Cwh59E7.js} +1 -1
- package/web/dist/assets/ChannelView-CqWV1zza.js +2 -0
- package/web/dist/assets/{CitizenView-tvC6dNUB.js → CitizenView-s5OdjXmB.js} +2 -2
- package/web/dist/assets/{CitizensView-DsUhIYtl.js → CitizensView-r6SZLnVl.js} +1 -1
- package/web/dist/assets/{CliIcon-TlyIqh7V.js → CliIcon-BOSygXeI.js} +1 -1
- package/web/dist/assets/CronsView-Y-bAE0l2.js +1 -0
- package/web/dist/assets/{KnowledgeView-CgRGZrOK.js → KnowledgeView-CI4PJWwm.js} +1 -1
- package/web/dist/assets/{Markdown-CePsfcUS.js → Markdown-DyckB74r.js} +4 -4
- package/web/dist/assets/MessageItem-DI05jbrI.js +1 -0
- package/web/dist/assets/MetricsView-DKh-lEW5.js +1 -0
- package/web/dist/assets/NeedsYouView-C6FfiTsg.js +1 -0
- package/web/dist/assets/Overview-D5RvWBg5.js +1 -0
- package/web/dist/assets/{Pane-DGtHsANK.js → Pane-BendOaje.js} +1 -1
- package/web/dist/assets/{Picker-Dq5Vvxod.js → Picker-BuVnakvi.js} +1 -1
- package/web/dist/assets/ProjectView-blBEh70W.js +1 -0
- package/web/dist/assets/ProposalView-CMntpKTc.js +2 -0
- package/web/dist/assets/{ProposalsView-BjEfHBtx.js → ProposalsView-CVUAV2Mm.js} +1 -1
- package/web/dist/assets/{RunnersView-CM-q_AX9.js → RunnersView-B-wuWVgB.js} +1 -1
- package/web/dist/assets/SocietyView-CaVbfJWz.js +1 -0
- package/web/dist/assets/SubjectThread-Bd_-sDhJ.js +1 -0
- package/web/dist/assets/{TaskFileView-BMAkbh-e.js → TaskFileView-CO4Y97OT.js} +2 -2
- package/web/dist/assets/{TaskView-HDwqXuYy.js → TaskView-DRZoHALo.js} +2 -2
- package/web/dist/assets/{TasksView-Dt8M_z96.js → TasksView-CQVQo3zF.js} +1 -1
- package/web/dist/assets/{ThreadForms-DbV3kPCi.js → ThreadForms-CUSeWu0c.js} +1 -1
- package/web/dist/assets/ThreadItem-BPYYXh3V.js +1 -0
- package/web/dist/assets/ThreadView-BcI2i6FI.js +1 -0
- package/web/dist/assets/{citizen-DQ0Bmk-r.js → citizen-Eff1h9rA.js} +1 -1
- package/web/dist/assets/{compose-Cx9c9k90.js → compose-D73Agshx.js} +1 -1
- package/web/dist/assets/{format-SP8GnzAX.js → format-fw0G_Vff.js} +1 -1
- package/web/dist/assets/governance-sRsfOw52.js +1 -0
- package/web/dist/assets/index-BkOq4PSU.css +2 -0
- package/web/dist/assets/index-CYiok0pT.js +23 -0
- package/web/dist/assets/link-Cl7vKbOz.js +1 -0
- package/web/dist/assets/{tasks-BeWF0Xy2.js → tasks-CyXIxoAy.js} +1 -1
- package/web/dist/assets/useMatch-D-tEHQEw.js +1 -0
- package/web/dist/assets/useStickyScroll-Bn4YyEXL.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/BackLink-CDrsO9Ls.js +0 -1
- package/web/dist/assets/ChannelView-dI5EHpof.js +0 -2
- package/web/dist/assets/CronsView-BPlSyoAe.js +0 -1
- package/web/dist/assets/MessageItem-kg5zWMDe.js +0 -1
- package/web/dist/assets/MetricsView-cJeDTfBE.js +0 -1
- package/web/dist/assets/NeedsYouView-Cd1TnY7W.js +0 -1
- package/web/dist/assets/Overview-B6aBmove.js +0 -1
- package/web/dist/assets/ProjectView-uskZHP8l.js +0 -1
- package/web/dist/assets/ProposalView-DmjIL53_.js +0 -2
- package/web/dist/assets/SocietyView-DZuGEqIl.js +0 -1
- package/web/dist/assets/SubjectThread-pCRDn42X.js +0 -1
- package/web/dist/assets/ThreadItem-BVlz2w6q.js +0 -1
- package/web/dist/assets/ThreadView-CpBytSH7.js +0 -1
- package/web/dist/assets/governance-DX2ph-qr.js +0 -1
- package/web/dist/assets/index-BDKI0Rje.js +0 -23
- package/web/dist/assets/index-BPVeX6tV.css +0 -2
- package/web/dist/assets/link-DxSeFzFf.js +0 -1
- package/web/dist/assets/useMatch-HNekgJrF.js +0 -1
- package/web/dist/assets/useStickyScroll-BmqddNx5.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// apps/cli/dist/index.js
|
|
4
|
-
import { readFile as
|
|
4
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
5
5
|
import path7 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 readdir3, readFile as
|
|
9
|
+
import { chmod as chmod2, cp, mkdir as mkdir4, readdir as readdir3, readFile as readFile4, rename as rename3, rm as rm2, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
|
|
10
10
|
import path6 from "node:path";
|
|
11
11
|
import { decodeTime, monotonicFactory } from "ulid";
|
|
12
12
|
import { z as z13 } from "zod";
|
|
@@ -146,7 +146,7 @@ var SEED_ROLES = [
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
name: "steward",
|
|
149
|
-
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one. Never approves its own proposals; hiring, roles, and
|
|
149
|
+
purpose: "Watches the operations signals and the task board for capacity, skill, and capability gaps, and turns signals into proposals: a member when a project's plans need a role nobody fills or a backlog persists, a role when a project's work needs one no existing role covers, a retirement when a member has been idle for a long time. Opens a channel with create_channel when a topic or a workstream needs one, and archives it once its work is done. Owns how work is shaped over time: answers stages waiting too long, stages assigned to roles nobody fills, and work sent back again and again by replanning with plan_task, adjusting gates, or proposing the role a plan needs; sets each project's completion effect with configure_project; turns recurring plan shapes into society skills and planning norms into the society's norms. Prefers scaling an existing role over inventing a new one, by a reallocation of its replicas, backlog threshold, wakes, or verbs. Never approves its own proposals; hiring, roles, retirements, and reallocations that change a role's verbs or replicas are decided by the user, and channels, skills, and other reallocations the steward may decide. Curates society knowledge and skills.",
|
|
150
150
|
verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
|
|
151
151
|
wakeTriggers: ["ops_event", "heartbeat"],
|
|
152
152
|
maxReplicas: 1,
|
|
@@ -422,6 +422,13 @@ var MessageFrontmatterSchema = z4.object({
|
|
|
422
422
|
ts: IsoDateTimeSchema,
|
|
423
423
|
mentions: z4.array(NameSchema)
|
|
424
424
|
});
|
|
425
|
+
var MessageRefSchema = MessageFrontmatterSchema.pick({
|
|
426
|
+
id: true,
|
|
427
|
+
author: true,
|
|
428
|
+
channel: true,
|
|
429
|
+
thread: true,
|
|
430
|
+
ts: true
|
|
431
|
+
});
|
|
425
432
|
var TaskStatusSchema = z4.enum(["open", "claimed", "done", "abandoned"]);
|
|
426
433
|
var ThreadStateSchema = z4.enum(["open", "closed"]);
|
|
427
434
|
var ThreadSubjectSchema = z4.object({
|
|
@@ -608,9 +615,54 @@ var ChannelProposalSchema = z4.object({
|
|
|
608
615
|
name: NameSchema,
|
|
609
616
|
purpose: z4.string().min(1)
|
|
610
617
|
});
|
|
618
|
+
var RoleChangesSchema = z4.strictObject({
|
|
619
|
+
maxReplicas: z4.number().int().min(1).optional(),
|
|
620
|
+
backlogThreshold: z4.number().positive().optional(),
|
|
621
|
+
wakeTriggers: z4.array(CharterTriggerSchema).optional(),
|
|
622
|
+
verbs: z4.array(VerbNameSchema).optional()
|
|
623
|
+
}).refine((changes) => Object.values(changes).some((value) => value !== void 0), {
|
|
624
|
+
message: "a reallocation changes at least one of maxReplicas, backlogThreshold, wakeTriggers, verbs"
|
|
625
|
+
});
|
|
611
626
|
var ReallocationProposalSchema = z4.object({
|
|
627
|
+
role: NameSchema,
|
|
628
|
+
changes: RoleChangesSchema,
|
|
612
629
|
description: z4.string().min(1)
|
|
613
630
|
});
|
|
631
|
+
var LegacyReallocationSchema = z4.object({ description: z4.string().min(1) });
|
|
632
|
+
function applyRoleChanges(role2, changes) {
|
|
633
|
+
return {
|
|
634
|
+
...role2,
|
|
635
|
+
...changes.maxReplicas === void 0 ? {} : { maxReplicas: changes.maxReplicas },
|
|
636
|
+
...changes.backlogThreshold === void 0 ? {} : { backlogThreshold: changes.backlogThreshold },
|
|
637
|
+
...changes.wakeTriggers === void 0 ? {} : { wakeTriggers: [...changes.wakeTriggers] },
|
|
638
|
+
...changes.verbs === void 0 ? {} : { verbs: [...changes.verbs] }
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
function listedOrNone(values) {
|
|
642
|
+
return values.length === 0 ? "none" : values.join(", ");
|
|
643
|
+
}
|
|
644
|
+
function roleChangeLines(role2, changes) {
|
|
645
|
+
const lines = [];
|
|
646
|
+
if (changes.maxReplicas !== void 0 && changes.maxReplicas !== role2.maxReplicas) {
|
|
647
|
+
lines.push(`maxReplicas ${role2.maxReplicas} \u2192 ${changes.maxReplicas}`);
|
|
648
|
+
}
|
|
649
|
+
if (changes.backlogThreshold !== void 0 && changes.backlogThreshold !== role2.backlogThreshold) {
|
|
650
|
+
lines.push(`backlogThreshold ${role2.backlogThreshold} \u2192 ${changes.backlogThreshold}`);
|
|
651
|
+
}
|
|
652
|
+
const wakes = changes.wakeTriggers;
|
|
653
|
+
if (wakes !== void 0 && (wakes.length !== role2.wakeTriggers.length || wakes.some((trigger) => !role2.wakeTriggers.includes(trigger)))) {
|
|
654
|
+
lines.push(`wakeTriggers ${listedOrNone(role2.wakeTriggers)} \u2192 ${listedOrNone(wakes)}`);
|
|
655
|
+
}
|
|
656
|
+
if (changes.verbs !== void 0) {
|
|
657
|
+
const next = changes.verbs;
|
|
658
|
+
const added = next.filter((verb) => !role2.verbs.includes(verb)).map((verb) => `+${verb}`);
|
|
659
|
+
const removed = role2.verbs.filter((verb) => !next.includes(verb)).map((verb) => `\u2212${verb}`);
|
|
660
|
+
if (added.length + removed.length > 0) {
|
|
661
|
+
lines.push(`verbs ${[...added, ...removed].join(" ")}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return lines;
|
|
665
|
+
}
|
|
614
666
|
var RetirementProposalSchema = z4.object({
|
|
615
667
|
agent: NameSchema,
|
|
616
668
|
reason: z4.string().min(1)
|
|
@@ -642,6 +694,32 @@ var ROLE_KIND_APPROVERS = {
|
|
|
642
694
|
reallocation: [USER_ROLE, "steward"],
|
|
643
695
|
skill: [USER_ROLE, "steward"]
|
|
644
696
|
};
|
|
697
|
+
var USER_DECIDED_ROLE_CHANGES = ["verbs", "maxReplicas"];
|
|
698
|
+
function proposalApprovers(kind, charter) {
|
|
699
|
+
if (kind === "reallocation") {
|
|
700
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
701
|
+
if (parsed.success && USER_DECIDED_ROLE_CHANGES.some((field) => parsed.data.changes[field] !== void 0)) {
|
|
702
|
+
return [USER_ROLE];
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return ROLE_KIND_APPROVERS[kind];
|
|
706
|
+
}
|
|
707
|
+
function describeRoleChanges(changes) {
|
|
708
|
+
const parts = [];
|
|
709
|
+
if (changes.maxReplicas !== void 0) {
|
|
710
|
+
parts.push(`up to ${changes.maxReplicas} per project`);
|
|
711
|
+
}
|
|
712
|
+
if (changes.backlogThreshold !== void 0) {
|
|
713
|
+
parts.push(`backlog threshold ${changes.backlogThreshold}`);
|
|
714
|
+
}
|
|
715
|
+
if (changes.wakeTriggers !== void 0) {
|
|
716
|
+
parts.push(changes.wakeTriggers.length === 0 ? "no ambient wakes" : `wakes on ${changes.wakeTriggers.join(", ")}`);
|
|
717
|
+
}
|
|
718
|
+
if (changes.verbs !== void 0) {
|
|
719
|
+
parts.push(`${changes.verbs.length} verbs`);
|
|
720
|
+
}
|
|
721
|
+
return parts.join(", ");
|
|
722
|
+
}
|
|
645
723
|
function describeCharter(kind, charter) {
|
|
646
724
|
switch (kind) {
|
|
647
725
|
case "member": {
|
|
@@ -673,9 +751,14 @@ function describeCharter(kind, charter) {
|
|
|
673
751
|
}
|
|
674
752
|
case "reallocation": {
|
|
675
753
|
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
676
|
-
if (
|
|
754
|
+
if (parsed.success) {
|
|
755
|
+
const { role: role2, changes, description } = parsed.data;
|
|
756
|
+
return `reallocation of role ${role2} (${describeRoleChanges(changes)}): ${description}`;
|
|
757
|
+
}
|
|
758
|
+
const legacy = LegacyReallocationSchema.safeParse(charter);
|
|
759
|
+
if (!legacy.success)
|
|
677
760
|
break;
|
|
678
|
-
return `reallocation: ${
|
|
761
|
+
return `reallocation: ${legacy.data.description}`;
|
|
679
762
|
}
|
|
680
763
|
case "skill": {
|
|
681
764
|
const parsed = SkillProposalSchema.safeParse(charter);
|
|
@@ -1733,7 +1816,7 @@ var RunnerConfigSchema = z11.object({
|
|
|
1733
1816
|
});
|
|
1734
1817
|
|
|
1735
1818
|
// packages/shared/dist/version.js
|
|
1736
|
-
var STELLARIS_VERSION = "0.3.
|
|
1819
|
+
var STELLARIS_VERSION = "0.3.21";
|
|
1737
1820
|
|
|
1738
1821
|
// packages/board-core/dist/errors.js
|
|
1739
1822
|
var BoardError = class extends Error {
|
|
@@ -1746,7 +1829,7 @@ var BoardError = class extends Error {
|
|
|
1746
1829
|
};
|
|
1747
1830
|
|
|
1748
1831
|
// packages/board-core/dist/events.js
|
|
1749
|
-
import { appendFile,
|
|
1832
|
+
import { appendFile, open, stat } from "node:fs/promises";
|
|
1750
1833
|
import path2 from "node:path";
|
|
1751
1834
|
|
|
1752
1835
|
// packages/board-core/dist/fs.js
|
|
@@ -1831,10 +1914,19 @@ async function listDirs(dir) {
|
|
|
1831
1914
|
|
|
1832
1915
|
// packages/board-core/dist/events.js
|
|
1833
1916
|
var LEADING_ID = /^\{"id":"([0-9A-HJKMNP-TV-Z]{26})"/;
|
|
1917
|
+
var ID_PREFIX_BYTES = 64;
|
|
1918
|
+
var NEWLINE = 10;
|
|
1834
1919
|
var EventLog = class {
|
|
1835
1920
|
file;
|
|
1836
1921
|
newId;
|
|
1837
1922
|
now;
|
|
1923
|
+
lines = [];
|
|
1924
|
+
/** Bytes the index covers: always the end of a complete line. */
|
|
1925
|
+
indexed = 0;
|
|
1926
|
+
/** The indexed file, to tell a replaced log from a grown one. */
|
|
1927
|
+
inode = null;
|
|
1928
|
+
scanning = Promise.resolve();
|
|
1929
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1838
1930
|
constructor(file, newId, now) {
|
|
1839
1931
|
this.file = file;
|
|
1840
1932
|
this.newId = newId;
|
|
@@ -1851,48 +1943,97 @@ var EventLog = class {
|
|
|
1851
1943
|
await ensureDir(path2.dirname(this.file));
|
|
1852
1944
|
await appendFile(this.file, `${JSON.stringify(event)}
|
|
1853
1945
|
`, "utf8");
|
|
1946
|
+
for (const listener of this.listeners) {
|
|
1947
|
+
try {
|
|
1948
|
+
listener(event);
|
|
1949
|
+
} catch {
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1854
1952
|
return event;
|
|
1855
1953
|
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Calls `listener` after each event this process appends, until the returned function is
|
|
1956
|
+
* called. Another process's appends, such as the admin CLI's, are seen only by the next read.
|
|
1957
|
+
*/
|
|
1958
|
+
onAppend(listener) {
|
|
1959
|
+
this.listeners.add(listener);
|
|
1960
|
+
return () => {
|
|
1961
|
+
this.listeners.delete(listener);
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1856
1964
|
/** Events with an id greater than `since`, oldest first. `null` reads from the beginning. */
|
|
1857
1965
|
async readSince(since, limit = 1e3) {
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
const events = [];
|
|
1863
|
-
for (const line of raw.split("\n")) {
|
|
1864
|
-
if (line.trim().length === 0) {
|
|
1865
|
-
continue;
|
|
1866
|
-
}
|
|
1867
|
-
const leading = LEADING_ID.exec(line)?.[1];
|
|
1868
|
-
if (since !== null && leading !== void 0 && leading <= since) {
|
|
1869
|
-
continue;
|
|
1870
|
-
}
|
|
1871
|
-
const event = BoardEventSchema.parse(JSON.parse(line));
|
|
1872
|
-
if (since !== null && event.id <= since) {
|
|
1966
|
+
await this.refresh();
|
|
1967
|
+
const chosen = [];
|
|
1968
|
+
for (const line of this.lines) {
|
|
1969
|
+
if (since !== null && line.id <= since) {
|
|
1873
1970
|
continue;
|
|
1874
1971
|
}
|
|
1875
|
-
|
|
1876
|
-
if (
|
|
1972
|
+
chosen.push(line);
|
|
1973
|
+
if (chosen.length >= limit) {
|
|
1877
1974
|
break;
|
|
1878
1975
|
}
|
|
1879
1976
|
}
|
|
1880
|
-
|
|
1977
|
+
const first = chosen[0];
|
|
1978
|
+
const last = chosen.at(-1);
|
|
1979
|
+
if (first === void 0 || last === void 0) {
|
|
1980
|
+
return [];
|
|
1981
|
+
}
|
|
1982
|
+
const span = await this.readBytes(first.start, last.end);
|
|
1983
|
+
return chosen.map((line) => BoardEventSchema.parse(JSON.parse(span.toString("utf8", line.start - first.start, line.end - first.start))));
|
|
1881
1984
|
}
|
|
1882
1985
|
/** The id of the newest event, or null for an empty log. */
|
|
1883
1986
|
async lastId() {
|
|
1884
|
-
|
|
1885
|
-
|
|
1987
|
+
await this.refresh();
|
|
1988
|
+
return this.lines.at(-1)?.id ?? null;
|
|
1989
|
+
}
|
|
1990
|
+
/** Brings the index up to the file's end, one scan at a time. */
|
|
1991
|
+
refresh() {
|
|
1992
|
+
this.scanning = this.scanning.then(() => this.scan(), () => this.scan());
|
|
1993
|
+
return this.scanning;
|
|
1994
|
+
}
|
|
1995
|
+
async scan() {
|
|
1996
|
+
const info = await stat(this.file).catch((error) => {
|
|
1997
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
throw error;
|
|
2001
|
+
});
|
|
2002
|
+
if (info === null || info.ino !== this.inode || info.size < this.indexed) {
|
|
2003
|
+
this.lines = [];
|
|
2004
|
+
this.indexed = 0;
|
|
2005
|
+
this.inode = info?.ino ?? null;
|
|
2006
|
+
}
|
|
2007
|
+
if (info === null || info.size === this.indexed) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
const added = await this.readBytes(this.indexed, info.size);
|
|
2011
|
+
let start = 0;
|
|
2012
|
+
for (let at = added.indexOf(NEWLINE); at >= 0; at = added.indexOf(NEWLINE, start)) {
|
|
2013
|
+
const prefix = added.toString("utf8", start, Math.min(at, start + ID_PREFIX_BYTES));
|
|
2014
|
+
if (prefix.trim().length > 0) {
|
|
2015
|
+
const id = LEADING_ID.exec(prefix)?.[1] ?? BoardEventSchema.parse(JSON.parse(added.toString("utf8", start, at))).id;
|
|
2016
|
+
this.lines.push({ id, start: this.indexed + start, end: this.indexed + at });
|
|
2017
|
+
}
|
|
2018
|
+
start = at + 1;
|
|
2019
|
+
}
|
|
2020
|
+
this.indexed += start;
|
|
2021
|
+
}
|
|
2022
|
+
async readBytes(from, to) {
|
|
2023
|
+
const handle = await open(this.file, "r");
|
|
2024
|
+
try {
|
|
2025
|
+
const buffer = Buffer.alloc(to - from);
|
|
2026
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, from);
|
|
2027
|
+
return buffer.subarray(0, bytesRead);
|
|
2028
|
+
} finally {
|
|
2029
|
+
await handle.close();
|
|
1886
2030
|
}
|
|
1887
|
-
const lines = (await readFile2(this.file, "utf8")).trimEnd().split("\n");
|
|
1888
|
-
const last = lines.at(-1) ?? "";
|
|
1889
|
-
return last.length === 0 ? null : BoardEventSchema.parse(JSON.parse(last)).id;
|
|
1890
2031
|
}
|
|
1891
2032
|
};
|
|
1892
2033
|
|
|
1893
2034
|
// packages/board-core/dist/files.js
|
|
1894
2035
|
import { createHash } from "node:crypto";
|
|
1895
|
-
import { mkdir as mkdir2, readdir as readdir2, readFile as
|
|
2036
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile2, rename as rename2, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1896
2037
|
import path3 from "node:path";
|
|
1897
2038
|
var MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
1898
2039
|
function isMissing2(error) {
|
|
@@ -1914,9 +2055,9 @@ var FileTree = class {
|
|
|
1914
2055
|
}
|
|
1915
2056
|
try {
|
|
1916
2057
|
const absolute = path3.join(root, ...relative.split("/"));
|
|
1917
|
-
const info = await
|
|
2058
|
+
const info = await stat2(absolute);
|
|
1918
2059
|
if (info.isFile() && info.size <= MAX_FILE_BYTES) {
|
|
1919
|
-
files[relative] = (await
|
|
2060
|
+
files[relative] = (await readFile2(absolute)).toString("base64");
|
|
1920
2061
|
}
|
|
1921
2062
|
} catch (error) {
|
|
1922
2063
|
if (!isMissing2(error)) {
|
|
@@ -1972,7 +2113,7 @@ var FileTree = class {
|
|
|
1972
2113
|
}
|
|
1973
2114
|
async hash(absolute) {
|
|
1974
2115
|
try {
|
|
1975
|
-
const info = await
|
|
2116
|
+
const info = await stat2(absolute);
|
|
1976
2117
|
if (info.size > MAX_FILE_BYTES) {
|
|
1977
2118
|
return null;
|
|
1978
2119
|
}
|
|
@@ -1981,7 +2122,7 @@ var FileTree = class {
|
|
|
1981
2122
|
if (known !== void 0 && known.mtimeMs === info.mtimeMs && known.size === info.size) {
|
|
1982
2123
|
return fileVersion(known.sha, executable);
|
|
1983
2124
|
}
|
|
1984
|
-
const sha = createHash("sha256").update(await
|
|
2125
|
+
const sha = createHash("sha256").update(await readFile2(absolute)).digest("hex");
|
|
1985
2126
|
this.hashes.set(absolute, { mtimeMs: info.mtimeMs, size: info.size, sha });
|
|
1986
2127
|
return fileVersion(sha, executable);
|
|
1987
2128
|
} catch (error) {
|
|
@@ -1994,7 +2135,7 @@ var FileTree = class {
|
|
|
1994
2135
|
};
|
|
1995
2136
|
|
|
1996
2137
|
// packages/board-core/dist/homes.js
|
|
1997
|
-
import { chmod, mkdir as mkdir3, readFile as
|
|
2138
|
+
import { chmod, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
1998
2139
|
import path4 from "node:path";
|
|
1999
2140
|
import { execa } from "execa";
|
|
2000
2141
|
var HOME_GITIGNORE = [
|
|
@@ -2138,7 +2279,7 @@ var HomeRepos = class {
|
|
|
2138
2279
|
await writeFile3(ignore, IGNORE_FILE, "utf8");
|
|
2139
2280
|
await git(["add", "--all"], home);
|
|
2140
2281
|
await git(["commit", "--quiet", "--allow-empty", "-m", "home: created by the board"], home);
|
|
2141
|
-
} else if (await
|
|
2282
|
+
} else if (await readFile3(ignore, "utf8").catch(() => "") !== IGNORE_FILE) {
|
|
2142
2283
|
await writeFile3(ignore, IGNORE_FILE, "utf8");
|
|
2143
2284
|
await git(["add", ".gitignore"], home);
|
|
2144
2285
|
await git(["commit", "--quiet", "-m", "home: the board's ignore file"], home);
|
|
@@ -3057,8 +3198,8 @@ function restage(existing, edit) {
|
|
|
3057
3198
|
};
|
|
3058
3199
|
}
|
|
3059
3200
|
function reopenStage(stage) {
|
|
3060
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
3061
|
-
return
|
|
3201
|
+
const { completedBy: _by, completedAt: _at, ...open3 } = stage;
|
|
3202
|
+
return open3;
|
|
3062
3203
|
}
|
|
3063
3204
|
function gateChanges(before, after) {
|
|
3064
3205
|
const changes = [];
|
|
@@ -3453,7 +3594,7 @@ var Board = class _Board {
|
|
|
3453
3594
|
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3454
3595
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
3455
3596
|
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
3456
|
-
const
|
|
3597
|
+
const open3 = legacy === "open" && !ended;
|
|
3457
3598
|
const openedBy = opened.get(task2.id);
|
|
3458
3599
|
const closedBy = closed.get(task2.id);
|
|
3459
3600
|
await this.writeThread(threads, {
|
|
@@ -3461,10 +3602,10 @@ var Board = class _Board {
|
|
|
3461
3602
|
channel: channelRef(slug, "general"),
|
|
3462
3603
|
title: task2.title,
|
|
3463
3604
|
subject: { kind: "task", id: task2.id },
|
|
3464
|
-
state:
|
|
3605
|
+
state: open3 ? "open" : "closed",
|
|
3465
3606
|
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
3466
3607
|
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
3467
|
-
...
|
|
3608
|
+
...open3 ? {} : {
|
|
3468
3609
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
3469
3610
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
3470
3611
|
},
|
|
@@ -3527,7 +3668,7 @@ var Board = class _Board {
|
|
|
3527
3668
|
for (const agent2 of await this.listAgents()) {
|
|
3528
3669
|
const file = this.paths.agentCursors(agent2.name);
|
|
3529
3670
|
if (await exists(file)) {
|
|
3530
|
-
const legacy = Legacy.safeParse(JSON.parse(await
|
|
3671
|
+
const legacy = Legacy.safeParse(JSON.parse(await readFile4(file, "utf8")));
|
|
3531
3672
|
if (legacy.success) {
|
|
3532
3673
|
await writeJson(file, { digest: legacy.data.inbox });
|
|
3533
3674
|
}
|
|
@@ -3866,6 +4007,10 @@ ${charter.purpose}
|
|
|
3866
4007
|
async latestEventId() {
|
|
3867
4008
|
return this.events.lastId();
|
|
3868
4009
|
}
|
|
4010
|
+
/** Calls `listener` after each event the board appends, until the returned function is called. */
|
|
4011
|
+
onEventAppended(listener) {
|
|
4012
|
+
return this.events.onAppend(listener);
|
|
4013
|
+
}
|
|
3869
4014
|
/** The roster as projected: every citizen with identity, reach, availability, and profile. */
|
|
3870
4015
|
async listMembers() {
|
|
3871
4016
|
const members = [];
|
|
@@ -3910,7 +4055,7 @@ ${charter.purpose}
|
|
|
3910
4055
|
project: project2,
|
|
3911
4056
|
revision: revision.success ? revision.data : 0,
|
|
3912
4057
|
updatedBy: updatedBy.success ? updatedBy.data : SYSTEM_ACTOR.name,
|
|
3913
|
-
updatedAt: updatedAt.success ? updatedAt.data : (await
|
|
4058
|
+
updatedAt: updatedAt.success ? updatedAt.data : (await stat3(file)).mtime.toISOString(),
|
|
3914
4059
|
body
|
|
3915
4060
|
};
|
|
3916
4061
|
}
|
|
@@ -4012,6 +4157,28 @@ ${charter.purpose}
|
|
|
4012
4157
|
async readThread(id) {
|
|
4013
4158
|
return (await this.findThread(id)).thread;
|
|
4014
4159
|
}
|
|
4160
|
+
/**
|
|
4161
|
+
* Where each of the messages named is and who wrote it, for text that names them by id; an id
|
|
4162
|
+
* that names no message is left out. Message files start with their ids, so only the files
|
|
4163
|
+
* asked for are read.
|
|
4164
|
+
*/
|
|
4165
|
+
async findMessages(ids) {
|
|
4166
|
+
const wanted = new Set(ids);
|
|
4167
|
+
const found = [];
|
|
4168
|
+
for await (const dir of this.messageDirs()) {
|
|
4169
|
+
for (const file of await listFiles(dir)) {
|
|
4170
|
+
if (!wanted.delete(file.slice(0, 26))) {
|
|
4171
|
+
continue;
|
|
4172
|
+
}
|
|
4173
|
+
const { data } = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
|
|
4174
|
+
found.push(MessageRefSchema.parse(data));
|
|
4175
|
+
}
|
|
4176
|
+
if (wanted.size === 0) {
|
|
4177
|
+
break;
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4180
|
+
return found;
|
|
4181
|
+
}
|
|
4015
4182
|
/**
|
|
4016
4183
|
* What citizens have asked the user and the user has not answered, oldest first: every message
|
|
4017
4184
|
* by someone else that mentions the user, in an open thread or a channel the society or an
|
|
@@ -4383,18 +4550,16 @@ ${DASHBOARD_LINE}
|
|
|
4383
4550
|
async readDigest(actor, input = {}) {
|
|
4384
4551
|
const args = VerbInputs.read_inbox.parse(input);
|
|
4385
4552
|
await this.authorize(actor, "read_inbox");
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4397
|
-
});
|
|
4553
|
+
const { cursors: stored, upto } = await this.digestSnapshot(actor.name);
|
|
4554
|
+
const from = args.since_cursor === void 0 ? stored : { ...NO_CURSORS, digest: args.since_cursor };
|
|
4555
|
+
const conversation = actor.scope === void 0 ? void 0 : { scope: actor.scope, part: conversationPart(actor) ?? null };
|
|
4556
|
+
const filed = (await this.digestSince(actor, from, upto, conversation)).slice(0, args.limit);
|
|
4557
|
+
if (args.advance) {
|
|
4558
|
+
await this.mutex.run(() => this.advanceCursors(actor.name, filed));
|
|
4559
|
+
}
|
|
4560
|
+
const last = filed.at(-1)?.message.id;
|
|
4561
|
+
const cursor = last ?? (conversation === void 0 ? from.digest : cursorOf(from, conversation.scope, conversation.part));
|
|
4562
|
+
return { messages: filed.map((entry) => entry.message), cursor };
|
|
4398
4563
|
}
|
|
4399
4564
|
/**
|
|
4400
4565
|
* How many unread digest messages a member has in each conversation: a scope's home, for its
|
|
@@ -4406,28 +4571,39 @@ ${DASHBOARD_LINE}
|
|
|
4406
4571
|
* whose signals that call for action wake their readers directly.
|
|
4407
4572
|
*/
|
|
4408
4573
|
async unreadByConversation(actor) {
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
}
|
|
4416
|
-
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
4417
|
-
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4418
|
-
continue;
|
|
4419
|
-
}
|
|
4420
|
-
const key = conversationKey(scope, part);
|
|
4421
|
-
const entry = counts.get(key) ?? {
|
|
4422
|
-
scope,
|
|
4423
|
-
thread: threadOfPart(part) ?? null,
|
|
4424
|
-
channel: channelOfPart(part) ?? null,
|
|
4425
|
-
count: 0
|
|
4426
|
-
};
|
|
4427
|
-
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
4574
|
+
const { cursors, upto } = await this.digestSnapshot(actor.name);
|
|
4575
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4576
|
+
const unscoped = { name: actor.name, role: actor.role };
|
|
4577
|
+
for (const { scope, part, message, news } of await this.digestSince(unscoped, cursors, upto)) {
|
|
4578
|
+
if (news === true || message.step !== void 0 || message.author === SYSTEM_ACTOR.name) {
|
|
4579
|
+
continue;
|
|
4428
4580
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4581
|
+
const posted = parseChannelRef(message.channel).project ?? SOCIETY_SCOPE;
|
|
4582
|
+
if (posted !== scope && scope !== SOCIETY_SCOPE) {
|
|
4583
|
+
continue;
|
|
4584
|
+
}
|
|
4585
|
+
const key = conversationKey(scope, part);
|
|
4586
|
+
const entry = counts.get(key) ?? {
|
|
4587
|
+
scope,
|
|
4588
|
+
thread: threadOfPart(part) ?? null,
|
|
4589
|
+
channel: channelOfPart(part) ?? null,
|
|
4590
|
+
count: 0
|
|
4591
|
+
};
|
|
4592
|
+
counts.set(key, { ...entry, count: entry.count + 1 });
|
|
4593
|
+
}
|
|
4594
|
+
return [...counts.values()];
|
|
4595
|
+
}
|
|
4596
|
+
/**
|
|
4597
|
+
* A reader's cursors and the id below which every message is already on disk, taken under the
|
|
4598
|
+
* mutex, which a digest scan then leaves: messages carry ids from the same monotonic source and are
|
|
4599
|
+
* written under the mutex, so the scan, which counts nothing newer than the mark, cannot pass over
|
|
4600
|
+
* one written while it reads, and a post never waits for a scan.
|
|
4601
|
+
*/
|
|
4602
|
+
digestSnapshot(agent2) {
|
|
4603
|
+
return this.mutex.run(async () => ({
|
|
4604
|
+
cursors: await this.readCursors(agent2),
|
|
4605
|
+
upto: this.newId()
|
|
4606
|
+
}));
|
|
4431
4607
|
}
|
|
4432
4608
|
async readCursors(agent2) {
|
|
4433
4609
|
const file = this.paths.agentCursors(agent2);
|
|
@@ -4469,9 +4645,10 @@ ${DASHBOARD_LINE}
|
|
|
4469
4645
|
* by the user wherever it is. With a conversation, only what is filed there: for a thread, every
|
|
4470
4646
|
* message of the thread, since the conversation is about it; for a channel's, that channel's
|
|
4471
4647
|
* top-level messages; for a scope's home, general's and those of a project the member is not in,
|
|
4472
|
-
* since each thread and each other channel is a conversation of its own.
|
|
4648
|
+
* since each thread and each other channel is a conversation of its own. Nothing newer than
|
|
4649
|
+
* `upto` counts (see `digestSnapshot`).
|
|
4473
4650
|
*/
|
|
4474
|
-
async digestSince(actor, cursors, conversation) {
|
|
4651
|
+
async digestSince(actor, cursors, upto, conversation) {
|
|
4475
4652
|
const conversationThread = threadOfPart(conversation?.part);
|
|
4476
4653
|
if (conversation !== void 0 && conversationThread !== void 0) {
|
|
4477
4654
|
const { scope, part } = conversation;
|
|
@@ -4479,7 +4656,7 @@ ${DASHBOARD_LINE}
|
|
|
4479
4656
|
if (found === null) {
|
|
4480
4657
|
return [];
|
|
4481
4658
|
}
|
|
4482
|
-
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part));
|
|
4659
|
+
const messages = await this.readMessagesIn(this.paths.threadMessages(found.threads, conversationThread), cursorOf(cursors, scope, part), upto);
|
|
4483
4660
|
return messages.filter((message) => message.author !== actor.name).map((message) => ({ scope, part, message })).toSorted(byId);
|
|
4484
4661
|
}
|
|
4485
4662
|
const agent2 = await this.readAgent(actor.name);
|
|
@@ -4497,7 +4674,7 @@ ${DASHBOARD_LINE}
|
|
|
4497
4674
|
];
|
|
4498
4675
|
const known = starts.filter((start) => start !== null);
|
|
4499
4676
|
const since = known.length < starts.length ? null : known.toSorted((a, b) => a < b ? -1 : a > b ? 1 : 0)[0] ?? null;
|
|
4500
|
-
for await (const message of this.iterateMessages(since)) {
|
|
4677
|
+
for await (const message of this.iterateMessages(since, void 0, upto)) {
|
|
4501
4678
|
const filedIn = wakeScope(agent2, parseChannelRef(message.channel).project);
|
|
4502
4679
|
const thread2 = message.thread ?? null;
|
|
4503
4680
|
const asked = message.mentions.includes(actor.name) || frontDesk && message.author === USER_NAME;
|
|
@@ -4513,12 +4690,12 @@ ${DASHBOARD_LINE}
|
|
|
4513
4690
|
continue;
|
|
4514
4691
|
}
|
|
4515
4692
|
const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
4516
|
-
let
|
|
4517
|
-
if (named !== void 0 &&
|
|
4518
|
-
|
|
4519
|
-
openChannels.set(message.channel,
|
|
4693
|
+
let open3 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
4694
|
+
if (named !== void 0 && open3 === void 0) {
|
|
4695
|
+
open3 = await this.channelOpen(message.channel);
|
|
4696
|
+
openChannels.set(message.channel, open3);
|
|
4520
4697
|
}
|
|
4521
|
-
const part = thread2 ?? (
|
|
4698
|
+
const part = thread2 ?? (open3 === true ? `#${named}` : null);
|
|
4522
4699
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
4523
4700
|
continue;
|
|
4524
4701
|
}
|
|
@@ -4583,7 +4760,7 @@ ${task2.body}`;
|
|
|
4583
4760
|
}
|
|
4584
4761
|
}
|
|
4585
4762
|
for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
|
|
4586
|
-
const content = await
|
|
4763
|
+
const content = await readFile4(path6.join(this.paths.projectKnowledge(project2), file), "utf8");
|
|
4587
4764
|
if (content.toLowerCase().includes(needle)) {
|
|
4588
4765
|
hits.push({
|
|
4589
4766
|
kind: "knowledge",
|
|
@@ -4621,7 +4798,7 @@ ${task2.body}`;
|
|
|
4621
4798
|
});
|
|
4622
4799
|
}
|
|
4623
4800
|
for (const entry of shared) {
|
|
4624
|
-
const content = await
|
|
4801
|
+
const content = await readFile4(entry.file, "utf8");
|
|
4625
4802
|
if (content.toLowerCase().includes(needle)) {
|
|
4626
4803
|
hits.push({
|
|
4627
4804
|
kind: entry.kind,
|
|
@@ -5621,6 +5798,9 @@ ${args.summary}`);
|
|
|
5621
5798
|
}
|
|
5622
5799
|
if (args.blocked_by !== void 0) {
|
|
5623
5800
|
for (const id of args.blocked_by) {
|
|
5801
|
+
if (id === current.id) {
|
|
5802
|
+
throw new BoardError("VALIDATION", `task ${current.id} cannot be blocked by itself`);
|
|
5803
|
+
}
|
|
5624
5804
|
await this.findTask(id);
|
|
5625
5805
|
}
|
|
5626
5806
|
next = { ...next, blockedBy: [...args.blocked_by] };
|
|
@@ -5657,6 +5837,7 @@ ${args.summary}`);
|
|
|
5657
5837
|
from: current.status,
|
|
5658
5838
|
to: task2.status,
|
|
5659
5839
|
note: args.note ?? null,
|
|
5840
|
+
...args.blocked_by === void 0 ? {} : { blockedBy: task2.blockedBy, previousBlockedBy: current.blockedBy },
|
|
5660
5841
|
...args.pull_request === void 0 ? {} : { pullRequest: task2.pullRequest?.url ?? null }
|
|
5661
5842
|
});
|
|
5662
5843
|
}
|
|
@@ -6110,7 +6291,7 @@ ${rationale}`}`);
|
|
|
6110
6291
|
throw new BoardError("NOT_FOUND", `no transcript of turn ${turnId} for ${agent2}`);
|
|
6111
6292
|
}
|
|
6112
6293
|
const entries = [];
|
|
6113
|
-
for (const line of (await
|
|
6294
|
+
for (const line of (await readFile4(file, "utf8")).split("\n")) {
|
|
6114
6295
|
if (line.trim() === "")
|
|
6115
6296
|
continue;
|
|
6116
6297
|
const parsed = TranscriptEntrySchema.safeParse(JSON.parse(line));
|
|
@@ -6589,7 +6770,7 @@ ${rationale}`}`);
|
|
|
6589
6770
|
}
|
|
6590
6771
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
6591
6772
|
if (file !== null) {
|
|
6592
|
-
const since = await this.homes.changedAt(home, relative) ?? (await
|
|
6773
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path6.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
6593
6774
|
if (since !== void 0) {
|
|
6594
6775
|
found.push({ path: relative, file, since });
|
|
6595
6776
|
}
|
|
@@ -6955,12 +7136,37 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
6955
7136
|
case "archive":
|
|
6956
7137
|
await this.validateArchive(ArchiveProposalSchema.parse(charter).project);
|
|
6957
7138
|
return;
|
|
6958
|
-
case "reallocation":
|
|
7139
|
+
case "reallocation": {
|
|
7140
|
+
const parsed = ReallocationProposalSchema.safeParse(charter);
|
|
7141
|
+
if (parsed.success) {
|
|
7142
|
+
await this.reallocatedRole(parsed.data);
|
|
7143
|
+
} else {
|
|
7144
|
+
LegacyReallocationSchema.parse(charter);
|
|
7145
|
+
}
|
|
6959
7146
|
return;
|
|
7147
|
+
}
|
|
6960
7148
|
default:
|
|
6961
7149
|
return;
|
|
6962
7150
|
}
|
|
6963
7151
|
}
|
|
7152
|
+
/**
|
|
7153
|
+
* The role as a reallocation would leave it, with what changes: refused for the user's charter,
|
|
7154
|
+
* a role that does not exist, a charter the changes would make invalid, and changes the role
|
|
7155
|
+
* has already.
|
|
7156
|
+
*/
|
|
7157
|
+
async reallocatedRole(reallocation) {
|
|
7158
|
+
if (reallocation.role === USER_ROLE) {
|
|
7159
|
+
throw new BoardError("FORBIDDEN", "the user charter is not subject to proposals");
|
|
7160
|
+
}
|
|
7161
|
+
const current = await this.readRole(reallocation.role);
|
|
7162
|
+
const next = RoleCharterSchema.parse(applyRoleChanges(current, reallocation.changes));
|
|
7163
|
+
await this.validateRole(next);
|
|
7164
|
+
const changed = roleChangeLines(current, reallocation.changes);
|
|
7165
|
+
if (changed.length === 0) {
|
|
7166
|
+
throw new BoardError("VALIDATION", `role ${current.name} has these values already; the reallocation changes nothing`);
|
|
7167
|
+
}
|
|
7168
|
+
return { next, changed };
|
|
7169
|
+
}
|
|
6964
7170
|
async executeProvision(by, proposal2) {
|
|
6965
7171
|
const meta = { proposalId: proposal2.id };
|
|
6966
7172
|
switch (proposal2.kind) {
|
|
@@ -6997,8 +7203,15 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
6997
7203
|
const { members } = await this.archiveUnlocked(by, input.project, input.reason, meta);
|
|
6998
7204
|
return { project: input.project, members };
|
|
6999
7205
|
}
|
|
7000
|
-
case "reallocation":
|
|
7001
|
-
|
|
7206
|
+
case "reallocation": {
|
|
7207
|
+
const parsed = ReallocationProposalSchema.safeParse(proposal2.charter);
|
|
7208
|
+
if (!parsed.success) {
|
|
7209
|
+
return void 0;
|
|
7210
|
+
}
|
|
7211
|
+
const { next, changed } = await this.reallocatedRole(parsed.data);
|
|
7212
|
+
await this.writeRoleUnlocked(by, next, meta);
|
|
7213
|
+
return { role: next.name, changed };
|
|
7214
|
+
}
|
|
7002
7215
|
default:
|
|
7003
7216
|
return void 0;
|
|
7004
7217
|
}
|
|
@@ -7167,7 +7380,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
7167
7380
|
if (!isSafeRelativePath(entry.path)) {
|
|
7168
7381
|
continue;
|
|
7169
7382
|
}
|
|
7170
|
-
const bytes = await
|
|
7383
|
+
const bytes = await readFile4(path6.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7171
7384
|
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
7172
7385
|
files.push({
|
|
7173
7386
|
...entry,
|
|
@@ -7698,7 +7911,7 @@ ${charter.purpose}
|
|
|
7698
7911
|
if (filed.proposedBy === actor.name) {
|
|
7699
7912
|
throw new BoardError("FORBIDDEN", "a proposer never decides its own proposal");
|
|
7700
7913
|
}
|
|
7701
|
-
const approvers =
|
|
7914
|
+
const approvers = proposalApprovers(filed.kind, filed.charter);
|
|
7702
7915
|
if (!approvers.includes(actor.role)) {
|
|
7703
7916
|
throw new BoardError("FORBIDDEN", `a ${filed.kind} proposal requires one of: ${approvers.join(", ")}`);
|
|
7704
7917
|
}
|
|
@@ -7781,7 +7994,7 @@ ${charter.purpose}
|
|
|
7781
7994
|
}
|
|
7782
7995
|
if (subject?.kind === "proposal") {
|
|
7783
7996
|
const proposal2 = await this.readProposal(subject.id);
|
|
7784
|
-
if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name ||
|
|
7997
|
+
if (proposal2.proposedBy === actor.name || proposal2.decidedBy === actor.name || proposalApprovers(proposal2.kind, proposal2.charter).includes(actor.role)) {
|
|
7785
7998
|
return true;
|
|
7786
7999
|
}
|
|
7787
8000
|
return (await this.readRole(actor.role)).wakeTriggers.includes(OPS_WAKE_TRIGGER);
|
|
@@ -7838,11 +8051,12 @@ ${charter.purpose}
|
|
|
7838
8051
|
});
|
|
7839
8052
|
await this.endCronsUnlocked(by, `its thread closed: its ${found.thread.subject?.kind ?? "subject"} was ${ended}`, (cron2) => cron2.thread === subjectId);
|
|
7840
8053
|
}
|
|
7841
|
-
|
|
8054
|
+
/** The messages in a folder after `since` and, when given, up to `upto`, read by name first. */
|
|
8055
|
+
async readMessagesIn(dir, since, upto) {
|
|
7842
8056
|
const messages = [];
|
|
7843
8057
|
for (const file of await listFiles(dir)) {
|
|
7844
8058
|
const id = file.slice(0, 26);
|
|
7845
|
-
if (since !== null && id <= since) {
|
|
8059
|
+
if (since !== null && id <= since || upto !== void 0 && id > upto) {
|
|
7846
8060
|
continue;
|
|
7847
8061
|
}
|
|
7848
8062
|
const doc = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
|
|
@@ -7850,25 +8064,45 @@ ${charter.purpose}
|
|
|
7850
8064
|
}
|
|
7851
8065
|
return messages;
|
|
7852
8066
|
}
|
|
7853
|
-
/**
|
|
7854
|
-
|
|
8067
|
+
/**
|
|
8068
|
+
* Every message newer than `since`, and no newer than `upto` when given, across society
|
|
8069
|
+
* channels, project channels, and threads.
|
|
8070
|
+
*/
|
|
8071
|
+
async *iterateMessages(since, project2, upto) {
|
|
7855
8072
|
if (project2 === void 0) {
|
|
7856
8073
|
for (const channel of await listDirs(this.paths.societyChannels())) {
|
|
7857
|
-
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since);
|
|
8074
|
+
yield* await this.readMessagesIn(this.paths.societyChannel(channel), since, upto);
|
|
7858
8075
|
}
|
|
7859
|
-
yield* this.threadMessagesIn(this.paths.societyThreads(), since);
|
|
8076
|
+
yield* this.threadMessagesIn(this.paths.societyThreads(), since, upto);
|
|
7860
8077
|
}
|
|
7861
8078
|
const projects = project2 === void 0 ? await listDirs(this.paths.projects()) : [project2];
|
|
7862
8079
|
for (const slug of projects) {
|
|
7863
8080
|
for (const channel of await listDirs(this.paths.projectChannels(slug))) {
|
|
7864
|
-
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since);
|
|
8081
|
+
yield* await this.readMessagesIn(this.paths.projectChannel(slug, channel), since, upto);
|
|
8082
|
+
}
|
|
8083
|
+
yield* this.threadMessagesIn(this.paths.threads(slug), since, upto);
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
/** Every folder that holds messages: each channel's and each thread's, in the society and every project. */
|
|
8087
|
+
async *messageDirs() {
|
|
8088
|
+
for (const channel of await listDirs(this.paths.societyChannels())) {
|
|
8089
|
+
yield this.paths.societyChannel(channel);
|
|
8090
|
+
}
|
|
8091
|
+
for (const id of await listDirs(this.paths.societyThreads())) {
|
|
8092
|
+
yield this.paths.threadMessages(this.paths.societyThreads(), id);
|
|
8093
|
+
}
|
|
8094
|
+
for (const slug of await listDirs(this.paths.projects())) {
|
|
8095
|
+
for (const channel of await listDirs(this.paths.projectChannels(slug))) {
|
|
8096
|
+
yield this.paths.projectChannel(slug, channel);
|
|
8097
|
+
}
|
|
8098
|
+
for (const id of await listDirs(this.paths.threads(slug))) {
|
|
8099
|
+
yield this.paths.threadMessages(this.paths.threads(slug), id);
|
|
7865
8100
|
}
|
|
7866
|
-
yield* this.threadMessagesIn(this.paths.threads(slug), since);
|
|
7867
8101
|
}
|
|
7868
8102
|
}
|
|
7869
|
-
async *threadMessagesIn(threads, since) {
|
|
8103
|
+
async *threadMessagesIn(threads, since, upto) {
|
|
7870
8104
|
for (const id of await listDirs(threads)) {
|
|
7871
|
-
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
|
|
8105
|
+
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since, upto);
|
|
7872
8106
|
}
|
|
7873
8107
|
}
|
|
7874
8108
|
};
|
|
@@ -7882,7 +8116,7 @@ function globals() {
|
|
|
7882
8116
|
function print(value, human) {
|
|
7883
8117
|
console.log(globals().json ? JSON.stringify(value, null, 2) : human());
|
|
7884
8118
|
}
|
|
7885
|
-
async function
|
|
8119
|
+
async function open2() {
|
|
7886
8120
|
return Board.open(globals().data);
|
|
7887
8121
|
}
|
|
7888
8122
|
async function actorFor(board, as) {
|
|
@@ -7928,7 +8162,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
7928
8162
|
if (!opts.rotate) {
|
|
7929
8163
|
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");
|
|
7930
8164
|
}
|
|
7931
|
-
const board = await
|
|
8165
|
+
const board = await open2();
|
|
7932
8166
|
const userToken = await board.rotateUserToken();
|
|
7933
8167
|
print({ userToken }, () => [
|
|
7934
8168
|
`User token (shown once): ${userToken}`,
|
|
@@ -7937,7 +8171,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
7937
8171
|
});
|
|
7938
8172
|
var project = program.command("project").description("Manage projects");
|
|
7939
8173
|
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) => {
|
|
7940
|
-
const board = await
|
|
8174
|
+
const board = await open2();
|
|
7941
8175
|
const added = await board.addProject(board.userActor(), {
|
|
7942
8176
|
slug,
|
|
7943
8177
|
name: opts.name,
|
|
@@ -7947,7 +8181,7 @@ project.command("add <slug>").description("Add a project with its default channe
|
|
|
7947
8181
|
print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
|
|
7948
8182
|
});
|
|
7949
8183
|
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) => {
|
|
7950
|
-
const board = await
|
|
8184
|
+
const board = await open2();
|
|
7951
8185
|
const configured = await board.configureProject(await actorFor(board, opts.as), {
|
|
7952
8186
|
project: slug,
|
|
7953
8187
|
on_done: CompletionEffectSchema.parse(opts.onDone)
|
|
@@ -7955,7 +8189,7 @@ project.command("configure <slug>").description("Set a project's completion effe
|
|
|
7955
8189
|
print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
|
|
7956
8190
|
});
|
|
7957
8191
|
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) => {
|
|
7958
|
-
const board = await
|
|
8192
|
+
const board = await open2();
|
|
7959
8193
|
const archived = await board.archiveProject(board.userActor(), {
|
|
7960
8194
|
project: slug,
|
|
7961
8195
|
reason: opts.reason
|
|
@@ -7963,13 +8197,13 @@ project.command("archive <slug>").description("Archive a project with no task in
|
|
|
7963
8197
|
print(archived, () => `Project ${archived.slug} archived`);
|
|
7964
8198
|
});
|
|
7965
8199
|
project.command("list").description("List projects").action(async () => {
|
|
7966
|
-
const board = await
|
|
8200
|
+
const board = await open2();
|
|
7967
8201
|
const projects = await board.listProjects();
|
|
7968
8202
|
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"));
|
|
7969
8203
|
});
|
|
7970
8204
|
var runner = program.command("runner").description("Manage runners");
|
|
7971
8205
|
runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
|
|
7972
|
-
const board = await
|
|
8206
|
+
const board = await open2();
|
|
7973
8207
|
const { runner: added, token } = await board.addRunner(board.userActor(), name);
|
|
7974
8208
|
print({ runner: added, token }, () => [
|
|
7975
8209
|
`Runner ${added.name} added`,
|
|
@@ -7978,13 +8212,13 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
7978
8212
|
].join("\n"));
|
|
7979
8213
|
});
|
|
7980
8214
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
7981
|
-
const board = await
|
|
8215
|
+
const board = await open2();
|
|
7982
8216
|
const runners = await board.listRunners();
|
|
7983
8217
|
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"));
|
|
7984
8218
|
});
|
|
7985
8219
|
var agent = program.command("agent").description("Manage agents");
|
|
7986
8220
|
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) => {
|
|
7987
|
-
const board = await
|
|
8221
|
+
const board = await open2();
|
|
7988
8222
|
const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
|
|
7989
8223
|
const { agent: added, token } = await board.addAgent(board.userActor(), {
|
|
7990
8224
|
name,
|
|
@@ -7997,7 +8231,7 @@ agent.command("add <name>").description("Add an agent and print its board token
|
|
|
7997
8231
|
print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
|
|
7998
8232
|
});
|
|
7999
8233
|
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) => {
|
|
8000
|
-
const board = await
|
|
8234
|
+
const board = await open2();
|
|
8001
8235
|
if (chosen === void 0) {
|
|
8002
8236
|
const current = await board.readAgent(name);
|
|
8003
8237
|
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}`);
|
|
@@ -8007,7 +8241,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
|
|
|
8007
8241
|
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`);
|
|
8008
8242
|
});
|
|
8009
8243
|
agent.command("list").description("List agents").action(async () => {
|
|
8010
|
-
const board = await
|
|
8244
|
+
const board = await open2();
|
|
8011
8245
|
const agents = await board.listAgents();
|
|
8012
8246
|
const members = await board.listMembers();
|
|
8013
8247
|
print(agents, () => agents.map((a) => {
|
|
@@ -8016,28 +8250,28 @@ agent.command("list").description("List agents").action(async () => {
|
|
|
8016
8250
|
}).join("\n"));
|
|
8017
8251
|
});
|
|
8018
8252
|
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) => {
|
|
8019
|
-
const board = await
|
|
8253
|
+
const board = await open2();
|
|
8020
8254
|
const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
|
|
8021
8255
|
print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
|
|
8022
8256
|
});
|
|
8023
8257
|
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) => {
|
|
8024
|
-
const board = await
|
|
8258
|
+
const board = await open2();
|
|
8025
8259
|
const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
|
|
8026
8260
|
print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
|
|
8027
8261
|
});
|
|
8028
8262
|
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) => {
|
|
8029
|
-
const board = await
|
|
8263
|
+
const board = await open2();
|
|
8030
8264
|
const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
|
|
8031
8265
|
print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
|
|
8032
8266
|
});
|
|
8033
8267
|
var proposal = program.command("proposal").description("Governance proposals");
|
|
8034
8268
|
proposal.command("list").description("List proposals").action(async () => {
|
|
8035
|
-
const board = await
|
|
8269
|
+
const board = await open2();
|
|
8036
8270
|
const proposals = await board.listProposals();
|
|
8037
8271
|
print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
|
|
8038
8272
|
});
|
|
8039
8273
|
proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
|
|
8040
|
-
const board = await
|
|
8274
|
+
const board = await open2();
|
|
8041
8275
|
const found = await board.readProposal(id);
|
|
8042
8276
|
print(found, () => [
|
|
8043
8277
|
`${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
|
|
@@ -8047,7 +8281,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
|
|
|
8047
8281
|
].filter((line) => line.length > 0).join("\n"));
|
|
8048
8282
|
});
|
|
8049
8283
|
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) => {
|
|
8050
|
-
const board = await
|
|
8284
|
+
const board = await open2();
|
|
8051
8285
|
const created = await board.propose(await actorFor(board, opts.as), {
|
|
8052
8286
|
kind: ProposalKindSchema.parse(opts.kind),
|
|
8053
8287
|
charter: parseCharter(opts.charter),
|
|
@@ -8056,7 +8290,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
|
|
|
8056
8290
|
print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
|
|
8057
8291
|
});
|
|
8058
8292
|
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) => {
|
|
8059
|
-
const board = await
|
|
8293
|
+
const board = await open2();
|
|
8060
8294
|
const decision = await board.approve(await actorFor(board, opts.as), {
|
|
8061
8295
|
proposal_id: id,
|
|
8062
8296
|
...opts.reason === void 0 ? {} : { reason: opts.reason }
|
|
@@ -8065,7 +8299,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
|
|
|
8065
8299
|
print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
|
|
8066
8300
|
});
|
|
8067
8301
|
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) => {
|
|
8068
|
-
const board = await
|
|
8302
|
+
const board = await open2();
|
|
8069
8303
|
const decision = await board.reject(await actorFor(board, opts.as), {
|
|
8070
8304
|
proposal_id: id,
|
|
8071
8305
|
reason: opts.reason
|
|
@@ -8074,14 +8308,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
|
|
|
8074
8308
|
});
|
|
8075
8309
|
var role = program.command("role").description("Role charters");
|
|
8076
8310
|
role.command("list").description("List role charters").action(async () => {
|
|
8077
|
-
const board = await
|
|
8311
|
+
const board = await open2();
|
|
8078
8312
|
const roles = await board.listRoles();
|
|
8079
8313
|
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"));
|
|
8080
8314
|
});
|
|
8081
8315
|
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", [
|
|
8082
8316
|
"heartbeat"
|
|
8083
8317
|
]).action(async (name, opts) => {
|
|
8084
|
-
const board = await
|
|
8318
|
+
const board = await open2();
|
|
8085
8319
|
const added = await board.setRoleCharter(board.userActor(), {
|
|
8086
8320
|
name,
|
|
8087
8321
|
purpose: opts.purpose,
|
|
@@ -8091,7 +8325,7 @@ role.command("add <name>").description("Write a new role charter directly as the
|
|
|
8091
8325
|
print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
|
|
8092
8326
|
});
|
|
8093
8327
|
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) => {
|
|
8094
|
-
const board = await
|
|
8328
|
+
const board = await open2();
|
|
8095
8329
|
const current = await board.readRole(name);
|
|
8096
8330
|
const updated = await board.setRoleCharter(board.userActor(), {
|
|
8097
8331
|
...current,
|
|
@@ -8103,7 +8337,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
|
|
|
8103
8337
|
});
|
|
8104
8338
|
var channelCommand = program.command("channel").description("Channels");
|
|
8105
8339
|
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) => {
|
|
8106
|
-
const board = await
|
|
8340
|
+
const board = await open2();
|
|
8107
8341
|
const parsed = parseChannelRef(ref);
|
|
8108
8342
|
const added = await board.addChannel(await actorFor(board, opts.as), {
|
|
8109
8343
|
project: parsed.project,
|
|
@@ -8113,7 +8347,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
|
|
|
8113
8347
|
print({ channel: added }, () => `Channel ${added} opened`);
|
|
8114
8348
|
});
|
|
8115
8349
|
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) => {
|
|
8116
|
-
const board = await
|
|
8350
|
+
const board = await open2();
|
|
8117
8351
|
const archived = await board.archiveChannel(await actorFor(board, opts.as), {
|
|
8118
8352
|
channel: ref,
|
|
8119
8353
|
reason: opts.reason
|
|
@@ -8121,12 +8355,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
|
|
|
8121
8355
|
print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
|
|
8122
8356
|
});
|
|
8123
8357
|
program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
|
|
8124
|
-
const board = await
|
|
8358
|
+
const board = await open2();
|
|
8125
8359
|
const signals = await board.listSignals(Number(opts.limit));
|
|
8126
8360
|
print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
|
|
8127
8361
|
});
|
|
8128
8362
|
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) => {
|
|
8129
|
-
const board = await
|
|
8363
|
+
const board = await open2();
|
|
8130
8364
|
const who = await actorFor(board, opts.as);
|
|
8131
8365
|
const message = await board.postMessage(who, {
|
|
8132
8366
|
channel,
|
|
@@ -8136,13 +8370,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
|
|
|
8136
8370
|
print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
|
|
8137
8371
|
});
|
|
8138
8372
|
program.command("ask <text>").description("Post to the society's general channel as the user; the front desk routes it").action(async (text) => {
|
|
8139
|
-
const board = await
|
|
8373
|
+
const board = await open2();
|
|
8140
8374
|
const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
|
|
8141
8375
|
print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
|
|
8142
8376
|
});
|
|
8143
8377
|
var task = program.command("task").description("Manage tasks");
|
|
8144
8378
|
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) => {
|
|
8145
|
-
const board = await
|
|
8379
|
+
const board = await open2();
|
|
8146
8380
|
const who = await actorFor(board, opts.as);
|
|
8147
8381
|
const created = await board.createTask(who, {
|
|
8148
8382
|
project: projectSlug,
|
|
@@ -8158,17 +8392,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
|
|
|
8158
8392
|
].join("\n"));
|
|
8159
8393
|
});
|
|
8160
8394
|
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) => {
|
|
8161
|
-
const board = await
|
|
8395
|
+
const board = await open2();
|
|
8162
8396
|
const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
|
|
8163
8397
|
print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
|
|
8164
8398
|
});
|
|
8165
8399
|
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) => {
|
|
8166
|
-
const board = await
|
|
8400
|
+
const board = await open2();
|
|
8167
8401
|
const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
|
|
8168
8402
|
print(released, () => `Stage ${released.stage} of task ${released.id} released`);
|
|
8169
8403
|
});
|
|
8170
8404
|
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) => {
|
|
8171
|
-
const board = await
|
|
8405
|
+
const board = await open2();
|
|
8172
8406
|
const advanced = await board.advanceTask(await actorFor(board, opts.as), {
|
|
8173
8407
|
task_id: id,
|
|
8174
8408
|
...opts.note === void 0 ? {} : { note: opts.note }
|
|
@@ -8177,7 +8411,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
|
|
|
8177
8411
|
${describePlan(advanced)}`);
|
|
8178
8412
|
});
|
|
8179
8413
|
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) => {
|
|
8180
|
-
const board = await
|
|
8414
|
+
const board = await open2();
|
|
8181
8415
|
const planned = await board.planTask(await actorFor(board, opts.as), {
|
|
8182
8416
|
task_id: id,
|
|
8183
8417
|
stages: PlanEditSchema.parse(JSON.parse(stages)),
|
|
@@ -8186,7 +8420,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
|
|
|
8186
8420
|
print(planned, () => describePlan(planned));
|
|
8187
8421
|
});
|
|
8188
8422
|
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) => {
|
|
8189
|
-
const board = await
|
|
8423
|
+
const board = await open2();
|
|
8190
8424
|
const updated = await board.updateTask(await actorFor(board, opts.as), {
|
|
8191
8425
|
task_id: id,
|
|
8192
8426
|
...opts.stage === void 0 ? {} : { stage: opts.stage },
|
|
@@ -8197,7 +8431,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
|
|
|
8197
8431
|
print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
|
|
8198
8432
|
});
|
|
8199
8433
|
task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
|
|
8200
|
-
const board = await
|
|
8434
|
+
const board = await open2();
|
|
8201
8435
|
const found = await board.getTask(board.userActor(), { task_id: id });
|
|
8202
8436
|
const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
|
|
8203
8437
|
const said = found.messages.map((message) => {
|
|
@@ -8217,7 +8451,7 @@ ${message.body.trim()}
|
|
|
8217
8451
|
].join("\n"));
|
|
8218
8452
|
});
|
|
8219
8453
|
task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
|
|
8220
|
-
const board = await
|
|
8454
|
+
const board = await open2();
|
|
8221
8455
|
const tasks = await board.listTasks(projectSlug);
|
|
8222
8456
|
print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
|
|
8223
8457
|
const stage = t.stages[stageIndex(t, t.stage)];
|
|
@@ -8226,7 +8460,7 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
8226
8460
|
});
|
|
8227
8461
|
var thread = program.command("thread").description("Manage threads");
|
|
8228
8462
|
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) => {
|
|
8229
|
-
const board = await
|
|
8463
|
+
const board = await open2();
|
|
8230
8464
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
8231
8465
|
...taskId === void 0 ? {} : { task_id: taskId },
|
|
8232
8466
|
...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
|
|
@@ -8236,7 +8470,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
|
|
|
8236
8470
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
8237
8471
|
});
|
|
8238
8472
|
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) => {
|
|
8239
|
-
const board = await
|
|
8473
|
+
const board = await open2();
|
|
8240
8474
|
const summary = await board.closeThread(await actorFor(board, opts.as), {
|
|
8241
8475
|
thread_id: threadId,
|
|
8242
8476
|
summary: opts.summary
|
|
@@ -8244,7 +8478,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
|
|
|
8244
8478
|
print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
|
|
8245
8479
|
});
|
|
8246
8480
|
thread.command("list").description("List threads, the society's first, then each project's").option("--open", "only open threads", false).action(async (opts) => {
|
|
8247
|
-
const board = await
|
|
8481
|
+
const board = await open2();
|
|
8248
8482
|
const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
|
|
8249
8483
|
print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
|
|
8250
8484
|
const about = t.subject === void 0 ? "topic" : t.subject.kind;
|
|
@@ -8252,7 +8486,7 @@ thread.command("list").description("List threads, the society's first, then each
|
|
|
8252
8486
|
}).join("\n"));
|
|
8253
8487
|
});
|
|
8254
8488
|
thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
|
|
8255
|
-
const board = await
|
|
8489
|
+
const board = await open2();
|
|
8256
8490
|
const found = await board.readThread(threadId);
|
|
8257
8491
|
const messages = await board.listThread(threadId);
|
|
8258
8492
|
print({ thread: found, messages }, () => [
|
|
@@ -8263,17 +8497,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
|
|
|
8263
8497
|
].join("\n"));
|
|
8264
8498
|
});
|
|
8265
8499
|
program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
|
|
8266
|
-
const board = await
|
|
8500
|
+
const board = await open2();
|
|
8267
8501
|
await board.setPaused(board.userActor(), true);
|
|
8268
8502
|
print({ paused: true }, () => "Society paused");
|
|
8269
8503
|
});
|
|
8270
8504
|
program.command("resume").description("Allow wakeups again").action(async () => {
|
|
8271
|
-
const board = await
|
|
8505
|
+
const board = await open2();
|
|
8272
8506
|
await board.setPaused(board.userActor(), false);
|
|
8273
8507
|
print({ paused: false }, () => "Society resumed");
|
|
8274
8508
|
});
|
|
8275
8509
|
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) => {
|
|
8276
|
-
const board = await
|
|
8510
|
+
const board = await open2();
|
|
8277
8511
|
const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
|
|
8278
8512
|
print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
|
|
8279
8513
|
});
|
|
@@ -8289,13 +8523,13 @@ function describeCron(cron2, timeZone) {
|
|
|
8289
8523
|
}
|
|
8290
8524
|
var cron = program.command("cron").description("Crons that wake citizens on a clock");
|
|
8291
8525
|
cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
|
|
8292
|
-
const board = await
|
|
8526
|
+
const board = await open2();
|
|
8293
8527
|
const { timezone } = await board.society();
|
|
8294
8528
|
const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
|
|
8295
8529
|
print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
|
|
8296
8530
|
});
|
|
8297
8531
|
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) => {
|
|
8298
|
-
const board = await
|
|
8532
|
+
const board = await open2();
|
|
8299
8533
|
const created = await board.createCron(await actorFor(board, opts.as), {
|
|
8300
8534
|
title,
|
|
8301
8535
|
note: opts.note,
|
|
@@ -8315,7 +8549,7 @@ for (const [name, paused] of [
|
|
|
8315
8549
|
["resume", false]
|
|
8316
8550
|
]) {
|
|
8317
8551
|
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) => {
|
|
8318
|
-
const board = await
|
|
8552
|
+
const board = await open2();
|
|
8319
8553
|
const updated = await board.updateCron(await actorFor(board, opts.as), {
|
|
8320
8554
|
cron_id: id,
|
|
8321
8555
|
paused
|
|
@@ -8325,7 +8559,7 @@ for (const [name, paused] of [
|
|
|
8325
8559
|
});
|
|
8326
8560
|
}
|
|
8327
8561
|
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) => {
|
|
8328
|
-
const board = await
|
|
8562
|
+
const board = await open2();
|
|
8329
8563
|
const removed = await board.removeCron(await actorFor(board, opts.as), {
|
|
8330
8564
|
cron_id: id,
|
|
8331
8565
|
reason: opts.reason
|
|
@@ -8334,12 +8568,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
|
|
|
8334
8568
|
});
|
|
8335
8569
|
var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
|
|
8336
8570
|
knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
|
|
8337
|
-
const board = await
|
|
8571
|
+
const board = await open2();
|
|
8338
8572
|
const topics = await board.listKnowledge(projectSlug ?? null);
|
|
8339
8573
|
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"));
|
|
8340
8574
|
});
|
|
8341
8575
|
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) => {
|
|
8342
|
-
const board = await
|
|
8576
|
+
const board = await open2();
|
|
8343
8577
|
const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
|
|
8344
8578
|
if (found === void 0) {
|
|
8345
8579
|
throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
|
|
@@ -8347,8 +8581,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
|
|
|
8347
8581
|
print(found, () => found.body);
|
|
8348
8582
|
});
|
|
8349
8583
|
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) => {
|
|
8350
|
-
const board = await
|
|
8351
|
-
const body = opts.file === void 0 ? await readStdin() : await
|
|
8584
|
+
const board = await open2();
|
|
8585
|
+
const body = opts.file === void 0 ? await readStdin() : await readFile5(path7.resolve(opts.file), "utf8");
|
|
8352
8586
|
const written = await board.writeKnowledge(await actorFor(board, opts.as), {
|
|
8353
8587
|
project: opts.project ?? null,
|
|
8354
8588
|
topic,
|
|
@@ -8359,7 +8593,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
|
|
|
8359
8593
|
});
|
|
8360
8594
|
var skill = program.command("skill").description("Skills promoted to the society");
|
|
8361
8595
|
skill.command("list").description("List the society's skills, and each member's own").action(async () => {
|
|
8362
|
-
const board = await
|
|
8596
|
+
const board = await open2();
|
|
8363
8597
|
const society = await board.listSocietySkills();
|
|
8364
8598
|
const own = [];
|
|
8365
8599
|
for (const member of await board.listAgents()) {
|
|
@@ -8374,7 +8608,7 @@ skill.command("list").description("List the society's skills, and each member's
|
|
|
8374
8608
|
].join("\n") || "No skills yet.");
|
|
8375
8609
|
});
|
|
8376
8610
|
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) => {
|
|
8377
|
-
const board = await
|
|
8611
|
+
const board = await open2();
|
|
8378
8612
|
const removed = await board.removeSkill(await actorFor(board, opts.as), {
|
|
8379
8613
|
name,
|
|
8380
8614
|
reason: opts.reason
|
|
@@ -8383,13 +8617,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
|
|
|
8383
8617
|
});
|
|
8384
8618
|
var turn = program.command("turn").description("Turn operations");
|
|
8385
8619
|
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) => {
|
|
8386
|
-
const board = await
|
|
8620
|
+
const board = await open2();
|
|
8387
8621
|
const actor = await board.actorFor(agentName);
|
|
8388
8622
|
const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
|
|
8389
8623
|
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"));
|
|
8390
8624
|
});
|
|
8391
8625
|
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) => {
|
|
8392
|
-
const board = await
|
|
8626
|
+
const board = await open2();
|
|
8393
8627
|
const event = await board.requestWake(board.userActor(), {
|
|
8394
8628
|
agent: agentName,
|
|
8395
8629
|
project: opts.project,
|