@kubitnodes/stellaris 0.3.18 → 0.3.20
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 +602 -196
- package/dist/server/index.js +606 -156
- package/package.json +1 -1
- package/web/dist/assets/{BackLink-DUOMS3fv.js → BackLink-BjfX2KZl.js} +1 -1
- package/web/dist/assets/{ChannelForms-DfG4vYvk.js → ChannelForms-BYrcUldk.js} +1 -1
- package/web/dist/assets/ChannelView-GJ_DusLQ.js +2 -0
- package/web/dist/assets/CitizenView-Cx7IYSRr.js +2 -0
- package/web/dist/assets/{CitizensView-bKbUaCab.js → CitizensView-IKH7_yNQ.js} +1 -1
- package/web/dist/assets/{CliIcon-DTVk0pGU.js → CliIcon-vSuwLv2K.js} +1 -1
- package/web/dist/assets/CronsView-DJbi8Qhx.js +1 -0
- package/web/dist/assets/{KnowledgeView-Dt7wE3FT.js → KnowledgeView-CoYOG9tI.js} +1 -1
- package/web/dist/assets/{Markdown-DeF9PKNm.js → Markdown-YC-fbHiE.js} +4 -4
- package/web/dist/assets/MessageItem-D7MUCA_T.js +1 -0
- package/web/dist/assets/{MetricsView-Bw7nL6Y9.js → MetricsView-CE94VW0I.js} +1 -1
- package/web/dist/assets/{NeedsYouView-DbESNtQT.js → NeedsYouView-Otnm3lcG.js} +1 -1
- package/web/dist/assets/{Overview-Dv7SS4yW.js → Overview-B8f6rLb2.js} +1 -1
- package/web/dist/assets/{Pane-v044FaP5.js → Pane-_D7n5uKH.js} +1 -1
- package/web/dist/assets/{Picker-wWsO86nu.js → Picker-DUiiQMFN.js} +1 -1
- package/web/dist/assets/ProjectView-D_ACOmEd.js +1 -0
- package/web/dist/assets/ProposalView-4d0U3nEb.js +2 -0
- package/web/dist/assets/{ProposalsView-dVA50yG1.js → ProposalsView-BovF6CMD.js} +1 -1
- package/web/dist/assets/{RunnersView-C-l5QYIa.js → RunnersView-B2_uCVHC.js} +1 -1
- package/web/dist/assets/{SocietyView--mWRXRj_.js → SocietyView-Bf0iX8AY.js} +1 -1
- package/web/dist/assets/SubjectThread-B_Vmgs3d.js +1 -0
- package/web/dist/assets/TaskFileView-BzO0gjE6.js +4 -0
- package/web/dist/assets/{TaskView-BgJcOmGj.js → TaskView-DB_Nji99.js} +2 -2
- package/web/dist/assets/{TasksView-f8KIsif_.js → TasksView-Bz36YYWq.js} +1 -1
- package/web/dist/assets/{ThreadForms-CXsunS-N.js → ThreadForms-1mbMZsbv.js} +1 -1
- package/web/dist/assets/ThreadItem-QJSA92C4.js +1 -0
- package/web/dist/assets/ThreadView-BECLqt5B.js +1 -0
- package/web/dist/assets/{citizen-VtLNxjtf.js → citizen-DVFr5T6R.js} +1 -1
- package/web/dist/assets/{compose-D-SO-HE-.js → compose-_6feqP1t.js} +1 -1
- package/web/dist/assets/{format-CkySAdH1.js → format-DxXtbdNM.js} +1 -1
- package/web/dist/assets/governance-C9xH4QTm.js +1 -0
- package/web/dist/assets/index-BEVCPm4C.js +23 -0
- package/web/dist/assets/index-BkOq4PSU.css +2 -0
- package/web/dist/assets/link-CTjFMNoY.js +1 -0
- package/web/dist/assets/{tasks-CD70eybR.js → tasks-B6I9DJ8O.js} +1 -1
- package/web/dist/assets/useMatch-B3RzAe8y.js +1 -0
- package/web/dist/assets/useStickyScroll-Ca7sWbGH.js +1 -0
- package/web/dist/index.html +9 -9
- package/web/dist/assets/ChannelView-DTLhLXy4.js +0 -2
- package/web/dist/assets/CitizenView-q24CExsI.js +0 -2
- package/web/dist/assets/CronsView-DaG1xhQu.js +0 -1
- package/web/dist/assets/MessageItem-DDs7m3-4.js +0 -1
- package/web/dist/assets/ProjectView--nzlqSDA.js +0 -1
- package/web/dist/assets/ProposalView-Cdw_xzG1.js +0 -1
- package/web/dist/assets/SubjectThread-DvvsxHN9.js +0 -1
- package/web/dist/assets/TaskFileView-DYuPrCdk.js +0 -4
- package/web/dist/assets/ThreadCard-BRLvjdz3.js +0 -1
- package/web/dist/assets/ThreadView-DzpaXkik.js +0 -1
- package/web/dist/assets/governance-1UeTD2K_.js +0 -1
- package/web/dist/assets/index-CwkK8XtJ.js +0 -23
- package/web/dist/assets/index-DaES0xVj.css +0 -2
- package/web/dist/assets/link-BN04dcBX.js +0 -1
- package/web/dist/assets/useMatch-i33QT_GI.js +0 -1
- package/web/dist/assets/useStickyScroll-WSVpAMrA.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 { 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";
|
|
@@ -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({
|
|
@@ -622,7 +629,7 @@ var ArchiveProposalSchema = z4.object({
|
|
|
622
629
|
var SkillProposalSchema = z4.object({
|
|
623
630
|
name: NameSchema,
|
|
624
631
|
summary: z4.string().min(1).max(200),
|
|
625
|
-
body: z4.string().min(1)
|
|
632
|
+
body: z4.string().min(1).optional()
|
|
626
633
|
});
|
|
627
634
|
var ProposalCharterSchemas = {
|
|
628
635
|
role: RoleCharterSchema,
|
|
@@ -749,6 +756,7 @@ var HomeFileDiffSchema = HomeFileChangeSchema.pick({ path: true, status: true })
|
|
|
749
756
|
var KnowledgeSchema = z4.object({
|
|
750
757
|
topic: NameSchema,
|
|
751
758
|
project: NameSchema.nullable(),
|
|
759
|
+
revision: z4.number().int().min(0),
|
|
752
760
|
updatedBy: NameSchema,
|
|
753
761
|
updatedAt: IsoDateTimeSchema
|
|
754
762
|
});
|
|
@@ -771,6 +779,24 @@ var ProposalOriginSchema = z4.object({
|
|
|
771
779
|
thread: UlidSchema.optional(),
|
|
772
780
|
channel: NameSchema.optional()
|
|
773
781
|
});
|
|
782
|
+
var SKILL_FILES_LIMIT = 100;
|
|
783
|
+
var SKILL_BYTES_LIMIT = 10 * 1024 * 1024;
|
|
784
|
+
var SkillFileSchema = z4.object({
|
|
785
|
+
path: z4.string().min(1),
|
|
786
|
+
size: z4.number().int().nonnegative(),
|
|
787
|
+
executable: z4.boolean()
|
|
788
|
+
});
|
|
789
|
+
var SkillFilesSchema = z4.object({
|
|
790
|
+
/** The commit of the proposer's home the files came from. */
|
|
791
|
+
commit: CommitIdSchema,
|
|
792
|
+
takenAt: IsoDateTimeSchema,
|
|
793
|
+
files: z4.array(SkillFileSchema)
|
|
794
|
+
});
|
|
795
|
+
var SkillFileContentSchema = SkillFileSchema.extend({
|
|
796
|
+
text: z4.string().nullable(),
|
|
797
|
+
/** The text was cut at its size limit. */
|
|
798
|
+
truncated: z4.boolean()
|
|
799
|
+
});
|
|
774
800
|
var ProposalFrontmatterSchema = z4.object({
|
|
775
801
|
id: UlidSchema,
|
|
776
802
|
kind: ProposalKindSchema,
|
|
@@ -779,6 +805,8 @@ var ProposalFrontmatterSchema = z4.object({
|
|
|
779
805
|
createdAt: IsoDateTimeSchema,
|
|
780
806
|
/** Absent for a proposal filed outside a turn, or before proposals recorded it. */
|
|
781
807
|
filedFrom: ProposalOriginSchema.optional(),
|
|
808
|
+
/** A skill proposal's folder, once taken; absent for one proposed as text alone. */
|
|
809
|
+
skillFiles: SkillFilesSchema.optional(),
|
|
782
810
|
decidedBy: NameSchema.optional(),
|
|
783
811
|
decidedAt: IsoDateTimeSchema.optional(),
|
|
784
812
|
reason: z4.string().optional(),
|
|
@@ -881,7 +909,9 @@ var VerbInputs = {
|
|
|
881
909
|
/** A project you belong to, or null for society knowledge, which the steward and the user curate. */
|
|
882
910
|
project: NameSchema.nullable().default(null),
|
|
883
911
|
topic: NameSchema,
|
|
884
|
-
body: z5.string().min(1)
|
|
912
|
+
body: z5.string().min(1),
|
|
913
|
+
/** The `revision` in the frontmatter of the topic you read, 0 when it names none or for a new topic. */
|
|
914
|
+
revision: z5.number().int().min(0)
|
|
885
915
|
}),
|
|
886
916
|
remove_knowledge: z5.object({
|
|
887
917
|
/** As for write_knowledge: a project you belong to, or null for society knowledge. */
|
|
@@ -982,6 +1012,7 @@ var BOARD_EVENT_TYPES = [
|
|
|
982
1012
|
"proposal.created",
|
|
983
1013
|
"proposal.decided",
|
|
984
1014
|
"proposal.provisioned",
|
|
1015
|
+
"proposal.files_taken",
|
|
985
1016
|
"channel.added",
|
|
986
1017
|
"channel.archived",
|
|
987
1018
|
"role.added",
|
|
@@ -1388,6 +1419,9 @@ var RunnerAnswerSchema = z7.discriminatedUnion("ok", [
|
|
|
1388
1419
|
]);
|
|
1389
1420
|
var WarmSessionsSchema = z7.object({ warm: z7.array(z7.string()) });
|
|
1390
1421
|
var FileManifestSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
|
|
1422
|
+
function fileVersion(sha, executable) {
|
|
1423
|
+
return executable ? `${sha}+x` : sha;
|
|
1424
|
+
}
|
|
1391
1425
|
var FileReadSchema = z7.object({ paths: z7.array(z7.string()) });
|
|
1392
1426
|
var FileContentsSchema = z7.object({ files: z7.record(z7.string(), z7.string()) });
|
|
1393
1427
|
function isSafeRelativePath(file) {
|
|
@@ -1706,7 +1740,7 @@ var RunnerConfigSchema = z11.object({
|
|
|
1706
1740
|
});
|
|
1707
1741
|
|
|
1708
1742
|
// packages/shared/dist/version.js
|
|
1709
|
-
var STELLARIS_VERSION = "0.3.
|
|
1743
|
+
var STELLARIS_VERSION = "0.3.20";
|
|
1710
1744
|
|
|
1711
1745
|
// packages/board-core/dist/errors.js
|
|
1712
1746
|
var BoardError = class extends Error {
|
|
@@ -1719,7 +1753,7 @@ var BoardError = class extends Error {
|
|
|
1719
1753
|
};
|
|
1720
1754
|
|
|
1721
1755
|
// packages/board-core/dist/events.js
|
|
1722
|
-
import { appendFile,
|
|
1756
|
+
import { appendFile, open, stat } from "node:fs/promises";
|
|
1723
1757
|
import path2 from "node:path";
|
|
1724
1758
|
|
|
1725
1759
|
// packages/board-core/dist/fs.js
|
|
@@ -1760,7 +1794,9 @@ async function readMarkdown(file, schema) {
|
|
|
1760
1794
|
return { data: schema.parse(parsed.data), body: parsed.content.replace(/^\n/, "") };
|
|
1761
1795
|
}
|
|
1762
1796
|
async function readLooseMarkdown(file) {
|
|
1763
|
-
|
|
1797
|
+
return parseLooseMarkdown(await readFile(file, "utf8"));
|
|
1798
|
+
}
|
|
1799
|
+
function parseLooseMarkdown(raw) {
|
|
1764
1800
|
try {
|
|
1765
1801
|
const parsed = parseMatter(raw);
|
|
1766
1802
|
return { data: parsed.data, body: parsed.content.replace(/^\n/, ""), error: null };
|
|
@@ -1802,10 +1838,19 @@ async function listDirs(dir) {
|
|
|
1802
1838
|
|
|
1803
1839
|
// packages/board-core/dist/events.js
|
|
1804
1840
|
var LEADING_ID = /^\{"id":"([0-9A-HJKMNP-TV-Z]{26})"/;
|
|
1841
|
+
var ID_PREFIX_BYTES = 64;
|
|
1842
|
+
var NEWLINE = 10;
|
|
1805
1843
|
var EventLog = class {
|
|
1806
1844
|
file;
|
|
1807
1845
|
newId;
|
|
1808
1846
|
now;
|
|
1847
|
+
lines = [];
|
|
1848
|
+
/** Bytes the index covers: always the end of a complete line. */
|
|
1849
|
+
indexed = 0;
|
|
1850
|
+
/** The indexed file, to tell a replaced log from a grown one. */
|
|
1851
|
+
inode = null;
|
|
1852
|
+
scanning = Promise.resolve();
|
|
1853
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1809
1854
|
constructor(file, newId, now) {
|
|
1810
1855
|
this.file = file;
|
|
1811
1856
|
this.newId = newId;
|
|
@@ -1822,48 +1867,97 @@ var EventLog = class {
|
|
|
1822
1867
|
await ensureDir(path2.dirname(this.file));
|
|
1823
1868
|
await appendFile(this.file, `${JSON.stringify(event)}
|
|
1824
1869
|
`, "utf8");
|
|
1870
|
+
for (const listener of this.listeners) {
|
|
1871
|
+
try {
|
|
1872
|
+
listener(event);
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1825
1876
|
return event;
|
|
1826
1877
|
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Calls `listener` after each event this process appends, until the returned function is
|
|
1880
|
+
* called. Another process's appends, such as the admin CLI's, are seen only by the next read.
|
|
1881
|
+
*/
|
|
1882
|
+
onAppend(listener) {
|
|
1883
|
+
this.listeners.add(listener);
|
|
1884
|
+
return () => {
|
|
1885
|
+
this.listeners.delete(listener);
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1827
1888
|
/** Events with an id greater than `since`, oldest first. `null` reads from the beginning. */
|
|
1828
1889
|
async readSince(since, limit = 1e3) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
const events = [];
|
|
1834
|
-
for (const line of raw.split("\n")) {
|
|
1835
|
-
if (line.trim().length === 0) {
|
|
1836
|
-
continue;
|
|
1837
|
-
}
|
|
1838
|
-
const leading = LEADING_ID.exec(line)?.[1];
|
|
1839
|
-
if (since !== null && leading !== void 0 && leading <= since) {
|
|
1840
|
-
continue;
|
|
1841
|
-
}
|
|
1842
|
-
const event = BoardEventSchema.parse(JSON.parse(line));
|
|
1843
|
-
if (since !== null && event.id <= since) {
|
|
1890
|
+
await this.refresh();
|
|
1891
|
+
const chosen = [];
|
|
1892
|
+
for (const line of this.lines) {
|
|
1893
|
+
if (since !== null && line.id <= since) {
|
|
1844
1894
|
continue;
|
|
1845
1895
|
}
|
|
1846
|
-
|
|
1847
|
-
if (
|
|
1896
|
+
chosen.push(line);
|
|
1897
|
+
if (chosen.length >= limit) {
|
|
1848
1898
|
break;
|
|
1849
1899
|
}
|
|
1850
1900
|
}
|
|
1851
|
-
|
|
1901
|
+
const first = chosen[0];
|
|
1902
|
+
const last = chosen.at(-1);
|
|
1903
|
+
if (first === void 0 || last === void 0) {
|
|
1904
|
+
return [];
|
|
1905
|
+
}
|
|
1906
|
+
const span = await this.readBytes(first.start, last.end);
|
|
1907
|
+
return chosen.map((line) => BoardEventSchema.parse(JSON.parse(span.toString("utf8", line.start - first.start, line.end - first.start))));
|
|
1852
1908
|
}
|
|
1853
1909
|
/** The id of the newest event, or null for an empty log. */
|
|
1854
1910
|
async lastId() {
|
|
1855
|
-
|
|
1856
|
-
|
|
1911
|
+
await this.refresh();
|
|
1912
|
+
return this.lines.at(-1)?.id ?? null;
|
|
1913
|
+
}
|
|
1914
|
+
/** Brings the index up to the file's end, one scan at a time. */
|
|
1915
|
+
refresh() {
|
|
1916
|
+
this.scanning = this.scanning.then(() => this.scan(), () => this.scan());
|
|
1917
|
+
return this.scanning;
|
|
1918
|
+
}
|
|
1919
|
+
async scan() {
|
|
1920
|
+
const info = await stat(this.file).catch((error) => {
|
|
1921
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
throw error;
|
|
1925
|
+
});
|
|
1926
|
+
if (info === null || info.ino !== this.inode || info.size < this.indexed) {
|
|
1927
|
+
this.lines = [];
|
|
1928
|
+
this.indexed = 0;
|
|
1929
|
+
this.inode = info?.ino ?? null;
|
|
1930
|
+
}
|
|
1931
|
+
if (info === null || info.size === this.indexed) {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
const added = await this.readBytes(this.indexed, info.size);
|
|
1935
|
+
let start = 0;
|
|
1936
|
+
for (let at = added.indexOf(NEWLINE); at >= 0; at = added.indexOf(NEWLINE, start)) {
|
|
1937
|
+
const prefix = added.toString("utf8", start, Math.min(at, start + ID_PREFIX_BYTES));
|
|
1938
|
+
if (prefix.trim().length > 0) {
|
|
1939
|
+
const id = LEADING_ID.exec(prefix)?.[1] ?? BoardEventSchema.parse(JSON.parse(added.toString("utf8", start, at))).id;
|
|
1940
|
+
this.lines.push({ id, start: this.indexed + start, end: this.indexed + at });
|
|
1941
|
+
}
|
|
1942
|
+
start = at + 1;
|
|
1943
|
+
}
|
|
1944
|
+
this.indexed += start;
|
|
1945
|
+
}
|
|
1946
|
+
async readBytes(from, to) {
|
|
1947
|
+
const handle = await open(this.file, "r");
|
|
1948
|
+
try {
|
|
1949
|
+
const buffer = Buffer.alloc(to - from);
|
|
1950
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, from);
|
|
1951
|
+
return buffer.subarray(0, bytesRead);
|
|
1952
|
+
} finally {
|
|
1953
|
+
await handle.close();
|
|
1857
1954
|
}
|
|
1858
|
-
const lines = (await readFile2(this.file, "utf8")).trimEnd().split("\n");
|
|
1859
|
-
const last = lines.at(-1) ?? "";
|
|
1860
|
-
return last.length === 0 ? null : BoardEventSchema.parse(JSON.parse(last)).id;
|
|
1861
1955
|
}
|
|
1862
1956
|
};
|
|
1863
1957
|
|
|
1864
1958
|
// packages/board-core/dist/files.js
|
|
1865
1959
|
import { createHash } from "node:crypto";
|
|
1866
|
-
import { mkdir as mkdir2, readdir as readdir2, readFile as
|
|
1960
|
+
import { mkdir as mkdir2, readdir as readdir2, readFile as readFile2, rename as rename2, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1867
1961
|
import path3 from "node:path";
|
|
1868
1962
|
var MAX_FILE_BYTES = 8 * 1024 * 1024;
|
|
1869
1963
|
function isMissing2(error) {
|
|
@@ -1885,9 +1979,9 @@ var FileTree = class {
|
|
|
1885
1979
|
}
|
|
1886
1980
|
try {
|
|
1887
1981
|
const absolute = path3.join(root, ...relative.split("/"));
|
|
1888
|
-
const info = await
|
|
1982
|
+
const info = await stat2(absolute);
|
|
1889
1983
|
if (info.isFile() && info.size <= MAX_FILE_BYTES) {
|
|
1890
|
-
files[relative] = (await
|
|
1984
|
+
files[relative] = (await readFile2(absolute)).toString("base64");
|
|
1891
1985
|
}
|
|
1892
1986
|
} catch (error) {
|
|
1893
1987
|
if (!isMissing2(error)) {
|
|
@@ -1943,17 +2037,18 @@ var FileTree = class {
|
|
|
1943
2037
|
}
|
|
1944
2038
|
async hash(absolute) {
|
|
1945
2039
|
try {
|
|
1946
|
-
const info = await
|
|
2040
|
+
const info = await stat2(absolute);
|
|
1947
2041
|
if (info.size > MAX_FILE_BYTES) {
|
|
1948
2042
|
return null;
|
|
1949
2043
|
}
|
|
2044
|
+
const executable = (info.mode & 73) !== 0;
|
|
1950
2045
|
const known = this.hashes.get(absolute);
|
|
1951
2046
|
if (known !== void 0 && known.mtimeMs === info.mtimeMs && known.size === info.size) {
|
|
1952
|
-
return known.sha;
|
|
2047
|
+
return fileVersion(known.sha, executable);
|
|
1953
2048
|
}
|
|
1954
|
-
const sha = createHash("sha256").update(await
|
|
2049
|
+
const sha = createHash("sha256").update(await readFile2(absolute)).digest("hex");
|
|
1955
2050
|
this.hashes.set(absolute, { mtimeMs: info.mtimeMs, size: info.size, sha });
|
|
1956
|
-
return sha;
|
|
2051
|
+
return fileVersion(sha, executable);
|
|
1957
2052
|
} catch (error) {
|
|
1958
2053
|
if (isMissing2(error)) {
|
|
1959
2054
|
return null;
|
|
@@ -1964,7 +2059,7 @@ var FileTree = class {
|
|
|
1964
2059
|
};
|
|
1965
2060
|
|
|
1966
2061
|
// packages/board-core/dist/homes.js
|
|
1967
|
-
import { chmod, mkdir as mkdir3, readFile as
|
|
2062
|
+
import { chmod, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
1968
2063
|
import path4 from "node:path";
|
|
1969
2064
|
import { execa } from "execa";
|
|
1970
2065
|
var HOME_GITIGNORE = [
|
|
@@ -2108,7 +2203,7 @@ var HomeRepos = class {
|
|
|
2108
2203
|
await writeFile3(ignore, IGNORE_FILE, "utf8");
|
|
2109
2204
|
await git(["add", "--all"], home);
|
|
2110
2205
|
await git(["commit", "--quiet", "--allow-empty", "-m", "home: created by the board"], home);
|
|
2111
|
-
} else if (await
|
|
2206
|
+
} else if (await readFile3(ignore, "utf8").catch(() => "") !== IGNORE_FILE) {
|
|
2112
2207
|
await writeFile3(ignore, IGNORE_FILE, "utf8");
|
|
2113
2208
|
await git(["add", ".gitignore"], home);
|
|
2114
2209
|
await git(["commit", "--quiet", "-m", "home: the board's ignore file"], home);
|
|
@@ -2204,6 +2299,55 @@ var HomeRepos = class {
|
|
|
2204
2299
|
}
|
|
2205
2300
|
return files;
|
|
2206
2301
|
}
|
|
2302
|
+
/**
|
|
2303
|
+
* The files of a folder in a home's newest commit, by path within the folder, with the commit;
|
|
2304
|
+
* conflict copies and anything but a regular file are left out. Null when the home is not a
|
|
2305
|
+
* repository yet.
|
|
2306
|
+
*/
|
|
2307
|
+
async folder(home, folder) {
|
|
2308
|
+
if (!await exists(path4.join(home, ".git"))) {
|
|
2309
|
+
return null;
|
|
2310
|
+
}
|
|
2311
|
+
const commit = (await read(home, ["rev-parse", "--verify", "HEAD^{commit}"])).trim();
|
|
2312
|
+
const prefix = `${folder}/`;
|
|
2313
|
+
const listed = await read(home, [
|
|
2314
|
+
"ls-tree",
|
|
2315
|
+
"-r",
|
|
2316
|
+
"-z",
|
|
2317
|
+
"--long",
|
|
2318
|
+
"--full-tree",
|
|
2319
|
+
commit,
|
|
2320
|
+
prefix
|
|
2321
|
+
]);
|
|
2322
|
+
const files = [];
|
|
2323
|
+
for (const entry of listed.split("\0")) {
|
|
2324
|
+
const match = /^(100644|100755) blob ([0-9a-f]+) +(\d+)\t(.+)$/.exec(entry);
|
|
2325
|
+
const [, mode = "", object = "", size = "0", file = ""] = match ?? [];
|
|
2326
|
+
if (match === null || !file.startsWith(prefix) || !isSafeRelativePath(file) || conflictCopyOf(file) !== null) {
|
|
2327
|
+
continue;
|
|
2328
|
+
}
|
|
2329
|
+
files.push({
|
|
2330
|
+
path: file.slice(prefix.length),
|
|
2331
|
+
object,
|
|
2332
|
+
size: Number.parseInt(size, 10),
|
|
2333
|
+
executable: mode === "100755"
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
return { commit, files };
|
|
2337
|
+
}
|
|
2338
|
+
/** A blob of a home's repository, as `folder` names it. */
|
|
2339
|
+
async blob(home, object) {
|
|
2340
|
+
const result = await execa("git", ["cat-file", "blob", object], {
|
|
2341
|
+
cwd: home,
|
|
2342
|
+
encoding: "buffer",
|
|
2343
|
+
stripFinalNewline: false,
|
|
2344
|
+
reject: false
|
|
2345
|
+
});
|
|
2346
|
+
if (result.exitCode !== 0) {
|
|
2347
|
+
throw new Error(`git cat-file failed: ${Buffer.from(result.stderr).toString().trim()}`);
|
|
2348
|
+
}
|
|
2349
|
+
return Buffer.from(result.stdout);
|
|
2350
|
+
}
|
|
2207
2351
|
/** The conflict copies a merge added, against its first parent. */
|
|
2208
2352
|
async keptCopies(home, parent, commit) {
|
|
2209
2353
|
const added = await read(home, [
|
|
@@ -2619,6 +2763,10 @@ var BoardPaths = class {
|
|
|
2619
2763
|
proposal(id) {
|
|
2620
2764
|
return path5.join(this.proposals(), `${id}.md`);
|
|
2621
2765
|
}
|
|
2766
|
+
/** A skill proposal's folder, as the board took it from its proposer's home. */
|
|
2767
|
+
proposalFiles(id) {
|
|
2768
|
+
return path5.join(this.proposals(), id);
|
|
2769
|
+
}
|
|
2622
2770
|
decisions() {
|
|
2623
2771
|
return path5.join(this.society, "decisions");
|
|
2624
2772
|
}
|
|
@@ -2870,6 +3018,20 @@ var MENTION_PATTERN = /(^|[^\w@])@([a-z0-9][a-z0-9-]{0,31})(?![\w-])/g;
|
|
|
2870
3018
|
function withoutMentions(text) {
|
|
2871
3019
|
return text.replaceAll(MENTION_PATTERN, "$1$2");
|
|
2872
3020
|
}
|
|
3021
|
+
var SKILL_TEXT_LIMIT_CHARS = 1e5;
|
|
3022
|
+
function skillFolderOf(proposal2) {
|
|
3023
|
+
if (proposal2.kind !== "skill") {
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
const parsed = SkillProposalSchema.safeParse(proposal2.charter);
|
|
3027
|
+
return parsed.success && parsed.data.body === void 0 ? parsed.data.name : null;
|
|
3028
|
+
}
|
|
3029
|
+
function proposedFrom(origin, where) {
|
|
3030
|
+
return origin !== void 0 && origin.scope === where.scope && origin.thread === where.thread && origin.channel === where.channel;
|
|
3031
|
+
}
|
|
3032
|
+
function megabytes(bytes) {
|
|
3033
|
+
return (bytes / (1024 * 1024)).toFixed(1).replace(/\.0$/, "");
|
|
3034
|
+
}
|
|
2873
3035
|
var CURATING_ROLES = [USER_ROLE, "steward"];
|
|
2874
3036
|
var SEED_INSTRUCTIONS_HEADING = "## Seed instructions";
|
|
2875
3037
|
var OPS_WAKE_TRIGGER = "ops_event";
|
|
@@ -2960,8 +3122,8 @@ function restage(existing, edit) {
|
|
|
2960
3122
|
};
|
|
2961
3123
|
}
|
|
2962
3124
|
function reopenStage(stage) {
|
|
2963
|
-
const { completedBy: _by, completedAt: _at, ...
|
|
2964
|
-
return
|
|
3125
|
+
const { completedBy: _by, completedAt: _at, ...open3 } = stage;
|
|
3126
|
+
return open3;
|
|
2965
3127
|
}
|
|
2966
3128
|
function gateChanges(before, after) {
|
|
2967
3129
|
const changes = [];
|
|
@@ -3356,7 +3518,7 @@ var Board = class _Board {
|
|
|
3356
3518
|
const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
|
|
3357
3519
|
if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
|
|
3358
3520
|
const ended = task2.status === "done" || task2.status === "abandoned";
|
|
3359
|
-
const
|
|
3521
|
+
const open3 = legacy === "open" && !ended;
|
|
3360
3522
|
const openedBy = opened.get(task2.id);
|
|
3361
3523
|
const closedBy = closed.get(task2.id);
|
|
3362
3524
|
await this.writeThread(threads, {
|
|
@@ -3364,10 +3526,10 @@ var Board = class _Board {
|
|
|
3364
3526
|
channel: channelRef(slug, "general"),
|
|
3365
3527
|
title: task2.title,
|
|
3366
3528
|
subject: { kind: "task", id: task2.id },
|
|
3367
|
-
state:
|
|
3529
|
+
state: open3 ? "open" : "closed",
|
|
3368
3530
|
openedBy: openedBy?.actor ?? task2.createdBy,
|
|
3369
3531
|
openedAt: openedBy?.ts ?? task2.createdAt,
|
|
3370
|
-
...
|
|
3532
|
+
...open3 ? {} : {
|
|
3371
3533
|
closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
|
|
3372
3534
|
closedAt: closedBy?.ts ?? this.now().toISOString()
|
|
3373
3535
|
},
|
|
@@ -3430,7 +3592,7 @@ var Board = class _Board {
|
|
|
3430
3592
|
for (const agent2 of await this.listAgents()) {
|
|
3431
3593
|
const file = this.paths.agentCursors(agent2.name);
|
|
3432
3594
|
if (await exists(file)) {
|
|
3433
|
-
const legacy = Legacy.safeParse(JSON.parse(await
|
|
3595
|
+
const legacy = Legacy.safeParse(JSON.parse(await readFile4(file, "utf8")));
|
|
3434
3596
|
if (legacy.success) {
|
|
3435
3597
|
await writeJson(file, { digest: legacy.data.inbox });
|
|
3436
3598
|
}
|
|
@@ -3769,6 +3931,10 @@ ${charter.purpose}
|
|
|
3769
3931
|
async latestEventId() {
|
|
3770
3932
|
return this.events.lastId();
|
|
3771
3933
|
}
|
|
3934
|
+
/** Calls `listener` after each event the board appends, until the returned function is called. */
|
|
3935
|
+
onEventAppended(listener) {
|
|
3936
|
+
return this.events.onAppend(listener);
|
|
3937
|
+
}
|
|
3772
3938
|
/** The roster as projected: every citizen with identity, reach, availability, and profile. */
|
|
3773
3939
|
async listMembers() {
|
|
3774
3940
|
const members = [];
|
|
@@ -3792,22 +3958,41 @@ ${charter.purpose}
|
|
|
3792
3958
|
const dir = project2 === null ? this.paths.societyKnowledge() : this.paths.projectKnowledge(project2);
|
|
3793
3959
|
const topics = [];
|
|
3794
3960
|
for (const file of await listFiles(dir)) {
|
|
3795
|
-
|
|
3796
|
-
const parsed = KnowledgeSchema.safeParse({
|
|
3797
|
-
...doc.data,
|
|
3798
|
-
project: project2,
|
|
3799
|
-
topic: file.replace(/\.md$/, "")
|
|
3800
|
-
});
|
|
3801
|
-
const data = parsed.success ? parsed.data : {
|
|
3802
|
-
topic: file.replace(/\.md$/, ""),
|
|
3803
|
-
project: project2,
|
|
3804
|
-
updatedBy: SYSTEM_ACTOR.name,
|
|
3805
|
-
updatedAt: (await stat2(path6.join(dir, file))).mtime.toISOString()
|
|
3806
|
-
};
|
|
3807
|
-
topics.push({ ...data, body: doc.body });
|
|
3961
|
+
topics.push(await this.knowledgeAt(path6.join(dir, file), project2, file.replace(/\.md$/, "")));
|
|
3808
3962
|
}
|
|
3809
3963
|
return topics;
|
|
3810
3964
|
}
|
|
3965
|
+
knowledgeFile(project2, topic) {
|
|
3966
|
+
return project2 === null ? this.paths.societyKnowledgeFile(topic) : this.paths.projectKnowledgeFile(project2, topic);
|
|
3967
|
+
}
|
|
3968
|
+
/**
|
|
3969
|
+
* Read loosely, as a dashboard is: a topic written before revisions counts as revision 0, and an
|
|
3970
|
+
* author or a time that does not parse is the board's at the file's mtime.
|
|
3971
|
+
*/
|
|
3972
|
+
async knowledgeAt(file, project2, topic) {
|
|
3973
|
+
const { data, body } = await readLooseMarkdown(file);
|
|
3974
|
+
const revision = z13.number().int().min(0).safeParse(data["revision"]);
|
|
3975
|
+
const updatedBy = NameSchema.safeParse(data["updatedBy"]);
|
|
3976
|
+
const updatedAt = IsoDateTimeSchema.safeParse(data["updatedAt"]);
|
|
3977
|
+
return {
|
|
3978
|
+
topic,
|
|
3979
|
+
project: project2,
|
|
3980
|
+
revision: revision.success ? revision.data : 0,
|
|
3981
|
+
updatedBy: updatedBy.success ? updatedBy.data : SYSTEM_ACTOR.name,
|
|
3982
|
+
updatedAt: updatedAt.success ? updatedAt.data : (await stat3(file)).mtime.toISOString(),
|
|
3983
|
+
body
|
|
3984
|
+
};
|
|
3985
|
+
}
|
|
3986
|
+
/** The highest revision among a topic's removed copies, 0 when it has none. */
|
|
3987
|
+
async removedRevision(project2, topic) {
|
|
3988
|
+
const aside = this.paths.removedKnowledge(project2 ?? SOCIETY_SCOPE);
|
|
3989
|
+
let highest = 0;
|
|
3990
|
+
for (const file of (await listFiles(aside)).filter((name) => name.startsWith(`${topic}.`))) {
|
|
3991
|
+
const copy = await this.knowledgeAt(path6.join(aside, file), project2, topic);
|
|
3992
|
+
highest = Math.max(highest, copy.revision);
|
|
3993
|
+
}
|
|
3994
|
+
return highest;
|
|
3995
|
+
}
|
|
3811
3996
|
/** Skills promoted to the society: every citizen may read them. */
|
|
3812
3997
|
async listSocietySkills() {
|
|
3813
3998
|
return this.listSkillsIn(this.paths.societySkills(), "society");
|
|
@@ -3896,6 +4081,28 @@ ${charter.purpose}
|
|
|
3896
4081
|
async readThread(id) {
|
|
3897
4082
|
return (await this.findThread(id)).thread;
|
|
3898
4083
|
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Where each of the messages named is and who wrote it, for text that names them by id; an id
|
|
4086
|
+
* that names no message is left out. Message files start with their ids, so only the files
|
|
4087
|
+
* asked for are read.
|
|
4088
|
+
*/
|
|
4089
|
+
async findMessages(ids) {
|
|
4090
|
+
const wanted = new Set(ids);
|
|
4091
|
+
const found = [];
|
|
4092
|
+
for await (const dir of this.messageDirs()) {
|
|
4093
|
+
for (const file of await listFiles(dir)) {
|
|
4094
|
+
if (!wanted.delete(file.slice(0, 26))) {
|
|
4095
|
+
continue;
|
|
4096
|
+
}
|
|
4097
|
+
const { data } = await readMarkdown(path6.join(dir, file), MessageFrontmatterSchema);
|
|
4098
|
+
found.push(MessageRefSchema.parse(data));
|
|
4099
|
+
}
|
|
4100
|
+
if (wanted.size === 0) {
|
|
4101
|
+
break;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
return found;
|
|
4105
|
+
}
|
|
3899
4106
|
/**
|
|
3900
4107
|
* What citizens have asked the user and the user has not answered, oldest first: every message
|
|
3901
4108
|
* by someone else that mentions the user, in an open thread or a channel the society or an
|
|
@@ -4397,12 +4604,12 @@ ${DASHBOARD_LINE}
|
|
|
4397
4604
|
continue;
|
|
4398
4605
|
}
|
|
4399
4606
|
const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
|
|
4400
|
-
let
|
|
4401
|
-
if (named !== void 0 &&
|
|
4402
|
-
|
|
4403
|
-
openChannels.set(message.channel,
|
|
4607
|
+
let open3 = named === void 0 ? void 0 : openChannels.get(message.channel);
|
|
4608
|
+
if (named !== void 0 && open3 === void 0) {
|
|
4609
|
+
open3 = await this.channelOpen(message.channel);
|
|
4610
|
+
openChannels.set(message.channel, open3);
|
|
4404
4611
|
}
|
|
4405
|
-
const part = thread2 ?? (
|
|
4612
|
+
const part = thread2 ?? (open3 === true ? `#${named}` : null);
|
|
4406
4613
|
if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
|
|
4407
4614
|
continue;
|
|
4408
4615
|
}
|
|
@@ -4467,7 +4674,7 @@ ${task2.body}`;
|
|
|
4467
4674
|
}
|
|
4468
4675
|
}
|
|
4469
4676
|
for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
|
|
4470
|
-
const content = await
|
|
4677
|
+
const content = await readFile4(path6.join(this.paths.projectKnowledge(project2), file), "utf8");
|
|
4471
4678
|
if (content.toLowerCase().includes(needle)) {
|
|
4472
4679
|
hits.push({
|
|
4473
4680
|
kind: "knowledge",
|
|
@@ -4505,7 +4712,7 @@ ${task2.body}`;
|
|
|
4505
4712
|
});
|
|
4506
4713
|
}
|
|
4507
4714
|
for (const entry of shared) {
|
|
4508
|
-
const content = await
|
|
4715
|
+
const content = await readFile4(entry.file, "utf8");
|
|
4509
4716
|
if (content.toLowerCase().includes(needle)) {
|
|
4510
4717
|
hits.push({
|
|
4511
4718
|
kind: entry.kind,
|
|
@@ -4521,26 +4728,42 @@ ${task2.body}`;
|
|
|
4521
4728
|
}
|
|
4522
4729
|
/**
|
|
4523
4730
|
* Writes a topic: a project's by its members, the society's by the curating roles. The note in
|
|
4524
|
-
* the matching general channel carries no mention, so nobody is woken.
|
|
4731
|
+
* the matching general channel carries no mention, so nobody is woken. A turn reads a topic from
|
|
4732
|
+
* the copy of the projection pulled when it started, so a revision other than the current one, 0
|
|
4733
|
+
* for a topic not there, means someone wrote or removed it since; the refusal carries the current
|
|
4734
|
+
* text, which that copy lacks.
|
|
4525
4735
|
*/
|
|
4526
4736
|
async writeKnowledge(actor, input) {
|
|
4527
4737
|
const args = VerbInputs.write_knowledge.parse(input);
|
|
4528
4738
|
await this.authorize(actor, "write_knowledge");
|
|
4529
4739
|
return this.mutex.run(async () => {
|
|
4530
4740
|
await this.assertMayCurate(actor, args.project);
|
|
4741
|
+
const file = this.knowledgeFile(args.project, args.topic);
|
|
4742
|
+
const current = await exists(file) ? await this.knowledgeAt(file, args.project, args.topic) : null;
|
|
4743
|
+
const place = args.project ?? "the society";
|
|
4744
|
+
if (current === null && args.revision !== 0) {
|
|
4745
|
+
throw new BoardError("INVALID_STATE", `${place} has no knowledge topic ${args.topic}, so it has no revision ${args.revision}: it was removed since you read it, or never written. Write it with revision 0 to start it anew.`);
|
|
4746
|
+
}
|
|
4747
|
+
if (current !== null && args.revision !== current.revision) {
|
|
4748
|
+
throw new BoardError("INVALID_STATE", `the knowledge topic ${args.topic} of ${place} is at revision ${current.revision}, updated by ${current.updatedBy} at ${current.updatedAt}, not ${args.revision}. Merge your changes into its current text, below, and write again with revision ${current.revision}.
|
|
4749
|
+
|
|
4750
|
+
${current.body}`);
|
|
4751
|
+
}
|
|
4752
|
+
const base = current?.revision ?? await this.removedRevision(args.project, args.topic);
|
|
4531
4753
|
const data = KnowledgeSchema.parse({
|
|
4532
4754
|
topic: args.topic,
|
|
4533
4755
|
project: args.project,
|
|
4756
|
+
revision: base + 1,
|
|
4534
4757
|
updatedBy: actor.name,
|
|
4535
4758
|
updatedAt: this.now().toISOString()
|
|
4536
4759
|
});
|
|
4537
|
-
const
|
|
4538
|
-
const replaced = await exists(file);
|
|
4760
|
+
const replaced = current !== null;
|
|
4539
4761
|
await writeMarkdown(file, data, args.body);
|
|
4540
4762
|
await this.events.append("knowledge.written", actor.name, {
|
|
4541
4763
|
topic: args.topic,
|
|
4542
4764
|
project: args.project,
|
|
4543
|
-
replaced
|
|
4765
|
+
replaced,
|
|
4766
|
+
revision: data.revision
|
|
4544
4767
|
});
|
|
4545
4768
|
const where = args.project === null ? "general" : channelRef(args.project, "general");
|
|
4546
4769
|
await this.appendMessage(actor.name, where, `Knowledge ${replaced ? "updated" : "written"}: ${args.topic}. It is under knowledge/${args.topic}.md${args.project === null ? " of the society" : ""}.`, {
|
|
@@ -4563,7 +4786,7 @@ ${task2.body}`;
|
|
|
4563
4786
|
await this.authorize(actor, "remove_knowledge");
|
|
4564
4787
|
return this.mutex.run(async () => {
|
|
4565
4788
|
await this.assertMayCurate(actor, args.project);
|
|
4566
|
-
const file =
|
|
4789
|
+
const file = this.knowledgeFile(args.project, args.topic);
|
|
4567
4790
|
if (!await exists(file)) {
|
|
4568
4791
|
throw new BoardError("NOT_FOUND", `${args.project ?? "the society"} has no knowledge topic ${args.topic}`);
|
|
4569
4792
|
}
|
|
@@ -5614,7 +5837,7 @@ ${args.summary}`);
|
|
|
5614
5837
|
throw new BoardError("VALIDATION", `invalid ${args.kind} charter: ${parsed.error.message}`);
|
|
5615
5838
|
}
|
|
5616
5839
|
return this.mutex.run(async () => {
|
|
5617
|
-
|
|
5840
|
+
let proposal2 = {
|
|
5618
5841
|
...ProposalFrontmatterSchema.parse({
|
|
5619
5842
|
id: this.newId(),
|
|
5620
5843
|
kind: args.kind,
|
|
@@ -5633,6 +5856,14 @@ ${args.summary}`);
|
|
|
5633
5856
|
body: args.rationale
|
|
5634
5857
|
};
|
|
5635
5858
|
await this.validateProvision(proposal2.kind, proposal2.charter);
|
|
5859
|
+
const skill2 = skillFolderOf(proposal2);
|
|
5860
|
+
if (skill2 !== null && proposal2.filedFrom === void 0) {
|
|
5861
|
+
const taken = await this.takeSkillFolderUnlocked(proposal2, skill2);
|
|
5862
|
+
if (typeof taken === "string") {
|
|
5863
|
+
throw new BoardError("VALIDATION", taken);
|
|
5864
|
+
}
|
|
5865
|
+
proposal2 = { ...proposal2, skillFiles: taken };
|
|
5866
|
+
}
|
|
5636
5867
|
await this.writeProposal(proposal2);
|
|
5637
5868
|
await this.events.append("proposal.created", actor.name, {
|
|
5638
5869
|
proposalId: proposal2.id,
|
|
@@ -5970,7 +6201,7 @@ ${rationale}`}`);
|
|
|
5970
6201
|
throw new BoardError("NOT_FOUND", `no transcript of turn ${turnId} for ${agent2}`);
|
|
5971
6202
|
}
|
|
5972
6203
|
const entries = [];
|
|
5973
|
-
for (const line of (await
|
|
6204
|
+
for (const line of (await readFile4(file, "utf8")).split("\n")) {
|
|
5974
6205
|
if (line.trim() === "")
|
|
5975
6206
|
continue;
|
|
5976
6207
|
const parsed = TranscriptEntrySchema.safeParse(JSON.parse(line));
|
|
@@ -6449,7 +6680,7 @@ ${rationale}`}`);
|
|
|
6449
6680
|
}
|
|
6450
6681
|
const file = entry.isFile() ? conflictCopyOf(relative) : null;
|
|
6451
6682
|
if (file !== null) {
|
|
6452
|
-
const since = await this.homes.changedAt(home, relative) ?? (await
|
|
6683
|
+
const since = await this.homes.changedAt(home, relative) ?? (await stat3(path6.join(home, relative)).catch(() => null))?.mtime.toISOString();
|
|
6453
6684
|
if (since !== void 0) {
|
|
6454
6685
|
found.push({ path: relative, file, since });
|
|
6455
6686
|
}
|
|
@@ -6849,7 +7080,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
6849
7080
|
}
|
|
6850
7081
|
case "skill": {
|
|
6851
7082
|
const skill2 = SkillProposalSchema.parse(proposal2.charter);
|
|
6852
|
-
const replaced = await this.promoteSkillUnlocked(by, proposal2
|
|
7083
|
+
const replaced = await this.promoteSkillUnlocked(by, proposal2, skill2, meta);
|
|
6853
7084
|
return { skill: skill2.name, replaced };
|
|
6854
7085
|
}
|
|
6855
7086
|
case "archive": {
|
|
@@ -6863,26 +7094,180 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
|
|
|
6863
7094
|
return void 0;
|
|
6864
7095
|
}
|
|
6865
7096
|
}
|
|
6866
|
-
/**
|
|
6867
|
-
|
|
7097
|
+
/**
|
|
7098
|
+
* Publishes a promoted skill under the society's skills, where every citizen's skills index lists
|
|
7099
|
+
* it: the folder the board took from its proposer's home, or the text proposed alone. A
|
|
7100
|
+
* replacement is the whole skill, so files the new version dropped go with the old one.
|
|
7101
|
+
*/
|
|
7102
|
+
async promoteSkillUnlocked(by, proposal2, skill2, meta) {
|
|
7103
|
+
const dir = this.paths.societySkill(skill2.name);
|
|
6868
7104
|
const file = this.paths.societySkillFile(skill2.name);
|
|
6869
7105
|
const replaced = await exists(file);
|
|
6870
|
-
|
|
7106
|
+
const taken = proposal2.skillFiles;
|
|
7107
|
+
const staged = path6.join(this.paths.proposalFiles(proposal2.id), "SKILL.md");
|
|
7108
|
+
if (skill2.body === void 0 && (taken === void 0 || !await exists(staged))) {
|
|
7109
|
+
throw new BoardError("INVALID_STATE", `proposal ${proposal2.id} holds no skill files`);
|
|
7110
|
+
}
|
|
7111
|
+
await rm2(dir, { recursive: true, force: true });
|
|
7112
|
+
if (skill2.body === void 0) {
|
|
7113
|
+
await cp(this.paths.proposalFiles(proposal2.id), dir, { recursive: true });
|
|
7114
|
+
} else {
|
|
7115
|
+
await ensureDir(dir);
|
|
7116
|
+
}
|
|
7117
|
+
const body = skill2.body ?? (await readLooseMarkdown(file)).body;
|
|
6871
7118
|
await writeMarkdown(file, {
|
|
6872
7119
|
name: skill2.name,
|
|
6873
7120
|
description: skill2.summary,
|
|
6874
|
-
metadata: {
|
|
6875
|
-
|
|
7121
|
+
metadata: {
|
|
7122
|
+
proposedBy: proposal2.proposedBy,
|
|
7123
|
+
promotedBy: by,
|
|
7124
|
+
promotedAt: this.now().toISOString(),
|
|
7125
|
+
...taken === void 0 ? {} : { commit: taken.commit }
|
|
7126
|
+
}
|
|
7127
|
+
}, body);
|
|
6876
7128
|
await this.events.append("skill.promoted", by, {
|
|
6877
7129
|
name: skill2.name,
|
|
6878
7130
|
summary: skill2.summary,
|
|
6879
|
-
proposedBy,
|
|
7131
|
+
proposedBy: proposal2.proposedBy,
|
|
6880
7132
|
replaced,
|
|
7133
|
+
...taken === void 0 ? {} : { files: taken.files.length },
|
|
6881
7134
|
...meta
|
|
6882
7135
|
});
|
|
6883
7136
|
await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${sentence(skill2.summary)}. Every citizen's skills index now lists it.`);
|
|
6884
7137
|
return replaced;
|
|
6885
7138
|
}
|
|
7139
|
+
/**
|
|
7140
|
+
* Copies a skill's folder from its proposer's home, as the home's newest commit holds it, into the
|
|
7141
|
+
* proposal's folder in the projection, where its deciders read it and from where approval
|
|
7142
|
+
* publishes it. Returns what was taken, or why the home could not give it.
|
|
7143
|
+
*/
|
|
7144
|
+
async takeSkillFolderUnlocked(proposal2, name) {
|
|
7145
|
+
const home = this.paths.agent(proposal2.proposedBy);
|
|
7146
|
+
const folder = `skills/${name}`;
|
|
7147
|
+
const where = `${folder} in ${proposal2.proposedBy}'s home`;
|
|
7148
|
+
const taken = await this.homes.folder(home, folder);
|
|
7149
|
+
if (taken === null || !taken.files.some((entry) => entry.path === "SKILL.md")) {
|
|
7150
|
+
return `no ${folder}/SKILL.md in ${proposal2.proposedBy}'s home`;
|
|
7151
|
+
}
|
|
7152
|
+
if (taken.files.length > SKILL_FILES_LIMIT) {
|
|
7153
|
+
return `${where} holds ${taken.files.length} files, more than the ${SKILL_FILES_LIMIT} a society skill may`;
|
|
7154
|
+
}
|
|
7155
|
+
const bytes = taken.files.reduce((sum, entry) => sum + entry.size, 0);
|
|
7156
|
+
if (bytes > SKILL_BYTES_LIMIT) {
|
|
7157
|
+
return `${where} holds ${megabytes(bytes)} MB, more than the ${megabytes(SKILL_BYTES_LIMIT)} MB a society skill may`;
|
|
7158
|
+
}
|
|
7159
|
+
const contents = /* @__PURE__ */ new Map();
|
|
7160
|
+
for (const entry of taken.files) {
|
|
7161
|
+
contents.set(entry.path, await this.homes.blob(home, entry.object));
|
|
7162
|
+
}
|
|
7163
|
+
const manual = parseLooseMarkdown(contents.get("SKILL.md")?.toString("utf8") ?? "");
|
|
7164
|
+
if (manual.error !== null) {
|
|
7165
|
+
return `the frontmatter of ${folder}/SKILL.md in ${proposal2.proposedBy}'s home does not parse (${manual.error})`;
|
|
7166
|
+
}
|
|
7167
|
+
const dir = this.paths.proposalFiles(proposal2.id);
|
|
7168
|
+
await rm2(dir, { recursive: true, force: true });
|
|
7169
|
+
for (const entry of taken.files) {
|
|
7170
|
+
const target = path6.join(dir, ...entry.path.split("/"));
|
|
7171
|
+
await ensureDir(path6.dirname(target));
|
|
7172
|
+
await writeFile4(target, contents.get(entry.path) ?? Buffer.alloc(0));
|
|
7173
|
+
await chmod2(target, entry.executable ? 493 : 420);
|
|
7174
|
+
}
|
|
7175
|
+
return {
|
|
7176
|
+
commit: taken.commit,
|
|
7177
|
+
takenAt: this.now().toISOString(),
|
|
7178
|
+
files: taken.files.map((entry) => ({
|
|
7179
|
+
path: entry.path,
|
|
7180
|
+
size: entry.size,
|
|
7181
|
+
executable: entry.executable
|
|
7182
|
+
}))
|
|
7183
|
+
};
|
|
7184
|
+
}
|
|
7185
|
+
/** Keeps what was taken of a skill proposal's folder on the proposal, and logs it. */
|
|
7186
|
+
async recordSkillFiles(proposal2, taken) {
|
|
7187
|
+
const next = { ...proposal2, skillFiles: taken };
|
|
7188
|
+
await this.writeProposal(next);
|
|
7189
|
+
await this.events.append("proposal.files_taken", SYSTEM_ACTOR.name, {
|
|
7190
|
+
proposalId: proposal2.id,
|
|
7191
|
+
kind: proposal2.kind,
|
|
7192
|
+
proposedBy: proposal2.proposedBy,
|
|
7193
|
+
commit: taken.commit,
|
|
7194
|
+
files: taken.files.length
|
|
7195
|
+
});
|
|
7196
|
+
return next;
|
|
7197
|
+
}
|
|
7198
|
+
/**
|
|
7199
|
+
* A skill proposal with its folder, for approval: taken now when the turn that filed it ended
|
|
7200
|
+
* without it, as when the server restarted before the turn's end was recorded, and refused while
|
|
7201
|
+
* that turn is still in flight, since the folder reaches the board only when it ends.
|
|
7202
|
+
*/
|
|
7203
|
+
async skillFilesForApproval(proposal2) {
|
|
7204
|
+
const name = skillFolderOf(proposal2);
|
|
7205
|
+
if (name === null || proposal2.skillFiles !== void 0) {
|
|
7206
|
+
return proposal2;
|
|
7207
|
+
}
|
|
7208
|
+
const cannot = `proposal ${proposal2.id} cannot be approved`;
|
|
7209
|
+
const filing = this.turnsInFlight().some((turn2) => turn2.agent === proposal2.proposedBy && proposedFrom(proposal2.filedFrom, turn2));
|
|
7210
|
+
if (filing) {
|
|
7211
|
+
throw new BoardError("INVALID_STATE", `${cannot} until ${proposal2.proposedBy}'s turn that proposed it ends, when the board takes the skill's folder from its home`);
|
|
7212
|
+
}
|
|
7213
|
+
const taken = await this.takeSkillFolderUnlocked(proposal2, name);
|
|
7214
|
+
if (typeof taken === "string") {
|
|
7215
|
+
throw new BoardError("INVALID_STATE", `${cannot}: ${taken}; reject it with that reason`);
|
|
7216
|
+
}
|
|
7217
|
+
return this.recordSkillFiles(proposal2, taken);
|
|
7218
|
+
}
|
|
7219
|
+
/**
|
|
7220
|
+
* Takes the folders of the skills a citizen proposed from a conversation, once the turn there has
|
|
7221
|
+
* ended and its runner has pushed the citizen's home. A proposal whose folder the home does not
|
|
7222
|
+
* give is rejected by the board with the reason, which wakes the proposer to put it right; while
|
|
7223
|
+
* someone is in a turn in the proposal's thread, it stays as it is, and approval meets the reason.
|
|
7224
|
+
*/
|
|
7225
|
+
async takeSkillFiles(turn2) {
|
|
7226
|
+
const where = {
|
|
7227
|
+
scope: turn2.project,
|
|
7228
|
+
...turn2.thread === void 0 ? {} : { thread: turn2.thread },
|
|
7229
|
+
...turn2.channel === void 0 ? {} : { channel: turn2.channel }
|
|
7230
|
+
};
|
|
7231
|
+
await this.mutex.run(async () => {
|
|
7232
|
+
for (const proposal2 of await this.listProposals()) {
|
|
7233
|
+
const name = skillFolderOf(proposal2);
|
|
7234
|
+
if (name === null || proposal2.status !== "proposed" || proposal2.skillFiles !== void 0 || proposal2.proposedBy !== turn2.agent || !proposedFrom(proposal2.filedFrom, where)) {
|
|
7235
|
+
continue;
|
|
7236
|
+
}
|
|
7237
|
+
const taken = await this.takeSkillFolderUnlocked(proposal2, name);
|
|
7238
|
+
if (typeof taken !== "string") {
|
|
7239
|
+
await this.recordSkillFiles(proposal2, taken);
|
|
7240
|
+
continue;
|
|
7241
|
+
}
|
|
7242
|
+
try {
|
|
7243
|
+
await this.decideUnlocked(SYSTEM_ACTOR, proposal2.id, "rejected", `${taken} when the turn that proposed it ended; propose it again once the folder is there`);
|
|
7244
|
+
} catch (error) {
|
|
7245
|
+
if (!(error instanceof BoardError)) {
|
|
7246
|
+
throw error;
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
});
|
|
7251
|
+
}
|
|
7252
|
+
/** A skill proposal's files with their text, as the board took them; none before then. */
|
|
7253
|
+
async readSkillFiles(proposalId) {
|
|
7254
|
+
const proposal2 = await this.readProposal(proposalId);
|
|
7255
|
+
const dir = this.paths.proposalFiles(proposal2.id);
|
|
7256
|
+
const files = [];
|
|
7257
|
+
for (const entry of proposal2.skillFiles?.files ?? []) {
|
|
7258
|
+
if (!isSafeRelativePath(entry.path)) {
|
|
7259
|
+
continue;
|
|
7260
|
+
}
|
|
7261
|
+
const bytes = await readFile4(path6.join(dir, ...entry.path.split("/"))).catch(() => null);
|
|
7262
|
+
const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
|
|
7263
|
+
files.push({
|
|
7264
|
+
...entry,
|
|
7265
|
+
text: text === null ? null : text.slice(0, SKILL_TEXT_LIMIT_CHARS),
|
|
7266
|
+
truncated: text !== null && text.length > SKILL_TEXT_LIMIT_CHARS
|
|
7267
|
+
});
|
|
7268
|
+
}
|
|
7269
|
+
return files;
|
|
7270
|
+
}
|
|
6886
7271
|
async validateAddAgent(input) {
|
|
6887
7272
|
if (await exists(this.paths.agentFile(input.name))) {
|
|
6888
7273
|
throw new BoardError("ALREADY_EXISTS", `agent ${input.name} already exists`);
|
|
@@ -7394,67 +7779,70 @@ ${charter.purpose}
|
|
|
7394
7779
|
* retired. Validation runs before any write, so an impossible provision fails the decision.
|
|
7395
7780
|
*/
|
|
7396
7781
|
async decide(actor, proposalId, outcome, reason) {
|
|
7397
|
-
return this.mutex.run(
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
this.
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
|
|
7420
|
-
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7782
|
+
return this.mutex.run(() => this.decideUnlocked(actor, proposalId, outcome, reason));
|
|
7783
|
+
}
|
|
7784
|
+
async decideUnlocked(actor, proposalId, outcome, reason) {
|
|
7785
|
+
const filed = await this.readProposal(proposalId);
|
|
7786
|
+
if (filed.status !== "proposed") {
|
|
7787
|
+
throw new BoardError("INVALID_STATE", `proposal ${proposalId} is already ${filed.status}`);
|
|
7788
|
+
}
|
|
7789
|
+
if (filed.proposedBy === actor.name) {
|
|
7790
|
+
throw new BoardError("FORBIDDEN", "a proposer never decides its own proposal");
|
|
7791
|
+
}
|
|
7792
|
+
const approvers = ROLE_KIND_APPROVERS[filed.kind];
|
|
7793
|
+
if (!approvers.includes(actor.role)) {
|
|
7794
|
+
throw new BoardError("FORBIDDEN", `a ${filed.kind} proposal requires one of: ${approvers.join(", ")}`);
|
|
7795
|
+
}
|
|
7796
|
+
if (outcome === "approved") {
|
|
7797
|
+
await this.validateProvision(filed.kind, filed.charter);
|
|
7798
|
+
}
|
|
7799
|
+
this.assertNoTurnCut(actor, `proposal ${proposalId} cannot be decided`, (turn2) => turn2.thread === proposalId);
|
|
7800
|
+
if (outcome === "approved" && filed.kind === "archive") {
|
|
7801
|
+
await this.assertProjectQuiet(actor, ArchiveProposalSchema.parse(filed.charter).project);
|
|
7802
|
+
}
|
|
7803
|
+
const proposal2 = outcome === "approved" ? await this.skillFilesForApproval(filed) : filed;
|
|
7804
|
+
const ts = this.now().toISOString();
|
|
7805
|
+
const decision = DecisionSchema.parse({
|
|
7806
|
+
id: this.newId(),
|
|
7807
|
+
proposalId,
|
|
7808
|
+
decidedBy: actor.name,
|
|
7809
|
+
outcome,
|
|
7810
|
+
...reason === void 0 ? {} : { reason },
|
|
7811
|
+
ts
|
|
7812
|
+
});
|
|
7813
|
+
await writeMarkdown(this.paths.decision(decision.id), decision, reason ?? "");
|
|
7814
|
+
const provision = outcome === "approved" ? await this.executeProvision(actor.name, proposal2) : void 0;
|
|
7815
|
+
const status = outcome === "rejected" ? "rejected" : provision === void 0 ? "approved" : "provisioned";
|
|
7816
|
+
await this.writeProposal({
|
|
7817
|
+
...proposal2,
|
|
7818
|
+
status,
|
|
7819
|
+
decidedBy: actor.name,
|
|
7820
|
+
decidedAt: ts,
|
|
7821
|
+
...reason === void 0 ? {} : { reason },
|
|
7822
|
+
...provision === void 0 ? {} : { provisionedAt: ts, provision }
|
|
7823
|
+
});
|
|
7824
|
+
await this.events.append("proposal.decided", actor.name, {
|
|
7825
|
+
proposalId,
|
|
7826
|
+
outcome,
|
|
7827
|
+
kind: proposal2.kind,
|
|
7828
|
+
proposedBy: proposal2.proposedBy,
|
|
7829
|
+
...proposal2.filedFrom === void 0 ? {} : { filedFrom: proposal2.filedFrom }
|
|
7830
|
+
});
|
|
7831
|
+
if (provision !== void 0) {
|
|
7832
|
+
await this.events.append("proposal.provisioned", actor.name, {
|
|
7437
7833
|
proposalId,
|
|
7438
|
-
outcome,
|
|
7439
7834
|
kind: proposal2.kind,
|
|
7440
|
-
|
|
7441
|
-
...proposal2.filedFrom === void 0 ? {} : { filedFrom: proposal2.filedFrom }
|
|
7835
|
+
...provision
|
|
7442
7836
|
});
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
const result = provision === void 0 ? "" : ` Provisioned: ${Object.entries(provision).map(([key, value]) => `${key} ${plain(value)}`).join(", ")}.`;
|
|
7453
|
-
const thread2 = await this.proposalThreadUnlocked(actor.name, proposal2);
|
|
7454
|
-
await this.appendThreadMessage(actor.name, thread2.id, `${verdict}: ${describeCharter(proposal2.kind, proposal2.charter)}.${why}${result}`, { step: { action: outcome } });
|
|
7455
|
-
await this.endThreadOf(proposalId, actor.name, outcome);
|
|
7456
|
-
return decision;
|
|
7457
|
-
});
|
|
7837
|
+
}
|
|
7838
|
+
const verdict = outcome === "approved" ? "Approved" : "Rejected";
|
|
7839
|
+
const why = reason === void 0 ? "" : ` Reason: ${reason}.`;
|
|
7840
|
+
const result = provision === void 0 ? "" : ` Provisioned: ${Object.entries(provision).map(([key, value]) => `${key} ${plain(value)}`).join(", ")}.`;
|
|
7841
|
+
const thread2 = await this.proposalThreadUnlocked(actor.name, proposal2);
|
|
7842
|
+
const post = `${verdict}: ${describeCharter(proposal2.kind, proposal2.charter)}.${why}${result}`;
|
|
7843
|
+
await this.appendThreadMessage(actor.name, thread2.id, actor.name === SYSTEM_ACTOR.name ? withoutMentions(post) : post, { step: { action: outcome } });
|
|
7844
|
+
await this.endThreadOf(proposalId, actor.name, outcome);
|
|
7845
|
+
return decision;
|
|
7458
7846
|
}
|
|
7459
7847
|
/** The opener, the people of its subject, and anyone who posted in it; mentions reach the rest. */
|
|
7460
7848
|
async participatesInThread(actor, thread2, threads) {
|
|
@@ -7569,6 +7957,23 @@ ${charter.purpose}
|
|
|
7569
7957
|
yield* this.threadMessagesIn(this.paths.threads(slug), since);
|
|
7570
7958
|
}
|
|
7571
7959
|
}
|
|
7960
|
+
/** Every folder that holds messages: each channel's and each thread's, in the society and every project. */
|
|
7961
|
+
async *messageDirs() {
|
|
7962
|
+
for (const channel of await listDirs(this.paths.societyChannels())) {
|
|
7963
|
+
yield this.paths.societyChannel(channel);
|
|
7964
|
+
}
|
|
7965
|
+
for (const id of await listDirs(this.paths.societyThreads())) {
|
|
7966
|
+
yield this.paths.threadMessages(this.paths.societyThreads(), id);
|
|
7967
|
+
}
|
|
7968
|
+
for (const slug of await listDirs(this.paths.projects())) {
|
|
7969
|
+
for (const channel of await listDirs(this.paths.projectChannels(slug))) {
|
|
7970
|
+
yield this.paths.projectChannel(slug, channel);
|
|
7971
|
+
}
|
|
7972
|
+
for (const id of await listDirs(this.paths.threads(slug))) {
|
|
7973
|
+
yield this.paths.threadMessages(this.paths.threads(slug), id);
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7572
7977
|
async *threadMessagesIn(threads, since) {
|
|
7573
7978
|
for (const id of await listDirs(threads)) {
|
|
7574
7979
|
yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
|
|
@@ -7585,7 +7990,7 @@ function globals() {
|
|
|
7585
7990
|
function print(value, human) {
|
|
7586
7991
|
console.log(globals().json ? JSON.stringify(value, null, 2) : human());
|
|
7587
7992
|
}
|
|
7588
|
-
async function
|
|
7993
|
+
async function open2() {
|
|
7589
7994
|
return Board.open(globals().data);
|
|
7590
7995
|
}
|
|
7591
7996
|
async function actorFor(board, as) {
|
|
@@ -7631,7 +8036,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
7631
8036
|
if (!opts.rotate) {
|
|
7632
8037
|
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");
|
|
7633
8038
|
}
|
|
7634
|
-
const board = await
|
|
8039
|
+
const board = await open2();
|
|
7635
8040
|
const userToken = await board.rotateUserToken();
|
|
7636
8041
|
print({ userToken }, () => [
|
|
7637
8042
|
`User token (shown once): ${userToken}`,
|
|
@@ -7640,7 +8045,7 @@ user.command("token").description("Issue the user a new token and print it once;
|
|
|
7640
8045
|
});
|
|
7641
8046
|
var project = program.command("project").description("Manage projects");
|
|
7642
8047
|
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) => {
|
|
7643
|
-
const board = await
|
|
8048
|
+
const board = await open2();
|
|
7644
8049
|
const added = await board.addProject(board.userActor(), {
|
|
7645
8050
|
slug,
|
|
7646
8051
|
name: opts.name,
|
|
@@ -7650,7 +8055,7 @@ project.command("add <slug>").description("Add a project with its default channe
|
|
|
7650
8055
|
print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
|
|
7651
8056
|
});
|
|
7652
8057
|
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) => {
|
|
7653
|
-
const board = await
|
|
8058
|
+
const board = await open2();
|
|
7654
8059
|
const configured = await board.configureProject(await actorFor(board, opts.as), {
|
|
7655
8060
|
project: slug,
|
|
7656
8061
|
on_done: CompletionEffectSchema.parse(opts.onDone)
|
|
@@ -7658,7 +8063,7 @@ project.command("configure <slug>").description("Set a project's completion effe
|
|
|
7658
8063
|
print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
|
|
7659
8064
|
});
|
|
7660
8065
|
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) => {
|
|
7661
|
-
const board = await
|
|
8066
|
+
const board = await open2();
|
|
7662
8067
|
const archived = await board.archiveProject(board.userActor(), {
|
|
7663
8068
|
project: slug,
|
|
7664
8069
|
reason: opts.reason
|
|
@@ -7666,13 +8071,13 @@ project.command("archive <slug>").description("Archive a project with no task in
|
|
|
7666
8071
|
print(archived, () => `Project ${archived.slug} archived`);
|
|
7667
8072
|
});
|
|
7668
8073
|
project.command("list").description("List projects").action(async () => {
|
|
7669
|
-
const board = await
|
|
8074
|
+
const board = await open2();
|
|
7670
8075
|
const projects = await board.listProjects();
|
|
7671
8076
|
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"));
|
|
7672
8077
|
});
|
|
7673
8078
|
var runner = program.command("runner").description("Manage runners");
|
|
7674
8079
|
runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
|
|
7675
|
-
const board = await
|
|
8080
|
+
const board = await open2();
|
|
7676
8081
|
const { runner: added, token } = await board.addRunner(board.userActor(), name);
|
|
7677
8082
|
print({ runner: added, token }, () => [
|
|
7678
8083
|
`Runner ${added.name} added`,
|
|
@@ -7681,13 +8086,13 @@ runner.command("add <name>").description("Register a runner and print its token
|
|
|
7681
8086
|
].join("\n"));
|
|
7682
8087
|
});
|
|
7683
8088
|
runner.command("list").description("List runners and what they offer").action(async () => {
|
|
7684
|
-
const board = await
|
|
8089
|
+
const board = await open2();
|
|
7685
8090
|
const runners = await board.listRunners();
|
|
7686
8091
|
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"));
|
|
7687
8092
|
});
|
|
7688
8093
|
var agent = program.command("agent").description("Manage agents");
|
|
7689
8094
|
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) => {
|
|
7690
|
-
const board = await
|
|
8095
|
+
const board = await open2();
|
|
7691
8096
|
const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
|
|
7692
8097
|
const { agent: added, token } = await board.addAgent(board.userActor(), {
|
|
7693
8098
|
name,
|
|
@@ -7700,7 +8105,7 @@ agent.command("add <name>").description("Add an agent and print its board token
|
|
|
7700
8105
|
print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
|
|
7701
8106
|
});
|
|
7702
8107
|
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) => {
|
|
7703
|
-
const board = await
|
|
8108
|
+
const board = await open2();
|
|
7704
8109
|
if (chosen === void 0) {
|
|
7705
8110
|
const current = await board.readAgent(name);
|
|
7706
8111
|
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}`);
|
|
@@ -7710,7 +8115,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
|
|
|
7710
8115
|
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`);
|
|
7711
8116
|
});
|
|
7712
8117
|
agent.command("list").description("List agents").action(async () => {
|
|
7713
|
-
const board = await
|
|
8118
|
+
const board = await open2();
|
|
7714
8119
|
const agents = await board.listAgents();
|
|
7715
8120
|
const members = await board.listMembers();
|
|
7716
8121
|
print(agents, () => agents.map((a) => {
|
|
@@ -7719,28 +8124,28 @@ agent.command("list").description("List agents").action(async () => {
|
|
|
7719
8124
|
}).join("\n"));
|
|
7720
8125
|
});
|
|
7721
8126
|
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) => {
|
|
7722
|
-
const board = await
|
|
8127
|
+
const board = await open2();
|
|
7723
8128
|
const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
|
|
7724
8129
|
print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
|
|
7725
8130
|
});
|
|
7726
8131
|
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) => {
|
|
7727
|
-
const board = await
|
|
8132
|
+
const board = await open2();
|
|
7728
8133
|
const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
|
|
7729
8134
|
print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
|
|
7730
8135
|
});
|
|
7731
8136
|
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) => {
|
|
7732
|
-
const board = await
|
|
8137
|
+
const board = await open2();
|
|
7733
8138
|
const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
|
|
7734
8139
|
print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
|
|
7735
8140
|
});
|
|
7736
8141
|
var proposal = program.command("proposal").description("Governance proposals");
|
|
7737
8142
|
proposal.command("list").description("List proposals").action(async () => {
|
|
7738
|
-
const board = await
|
|
8143
|
+
const board = await open2();
|
|
7739
8144
|
const proposals = await board.listProposals();
|
|
7740
8145
|
print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
|
|
7741
8146
|
});
|
|
7742
8147
|
proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
|
|
7743
|
-
const board = await
|
|
8148
|
+
const board = await open2();
|
|
7744
8149
|
const found = await board.readProposal(id);
|
|
7745
8150
|
print(found, () => [
|
|
7746
8151
|
`${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
|
|
@@ -7750,7 +8155,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
|
|
|
7750
8155
|
].filter((line) => line.length > 0).join("\n"));
|
|
7751
8156
|
});
|
|
7752
8157
|
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) => {
|
|
7753
|
-
const board = await
|
|
8158
|
+
const board = await open2();
|
|
7754
8159
|
const created = await board.propose(await actorFor(board, opts.as), {
|
|
7755
8160
|
kind: ProposalKindSchema.parse(opts.kind),
|
|
7756
8161
|
charter: parseCharter(opts.charter),
|
|
@@ -7759,7 +8164,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
|
|
|
7759
8164
|
print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
|
|
7760
8165
|
});
|
|
7761
8166
|
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) => {
|
|
7762
|
-
const board = await
|
|
8167
|
+
const board = await open2();
|
|
7763
8168
|
const decision = await board.approve(await actorFor(board, opts.as), {
|
|
7764
8169
|
proposal_id: id,
|
|
7765
8170
|
...opts.reason === void 0 ? {} : { reason: opts.reason }
|
|
@@ -7768,7 +8173,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
|
|
|
7768
8173
|
print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
|
|
7769
8174
|
});
|
|
7770
8175
|
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) => {
|
|
7771
|
-
const board = await
|
|
8176
|
+
const board = await open2();
|
|
7772
8177
|
const decision = await board.reject(await actorFor(board, opts.as), {
|
|
7773
8178
|
proposal_id: id,
|
|
7774
8179
|
reason: opts.reason
|
|
@@ -7777,14 +8182,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
|
|
|
7777
8182
|
});
|
|
7778
8183
|
var role = program.command("role").description("Role charters");
|
|
7779
8184
|
role.command("list").description("List role charters").action(async () => {
|
|
7780
|
-
const board = await
|
|
8185
|
+
const board = await open2();
|
|
7781
8186
|
const roles = await board.listRoles();
|
|
7782
8187
|
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"));
|
|
7783
8188
|
});
|
|
7784
8189
|
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", [
|
|
7785
8190
|
"heartbeat"
|
|
7786
8191
|
]).action(async (name, opts) => {
|
|
7787
|
-
const board = await
|
|
8192
|
+
const board = await open2();
|
|
7788
8193
|
const added = await board.setRoleCharter(board.userActor(), {
|
|
7789
8194
|
name,
|
|
7790
8195
|
purpose: opts.purpose,
|
|
@@ -7794,7 +8199,7 @@ role.command("add <name>").description("Write a new role charter directly as the
|
|
|
7794
8199
|
print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
|
|
7795
8200
|
});
|
|
7796
8201
|
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) => {
|
|
7797
|
-
const board = await
|
|
8202
|
+
const board = await open2();
|
|
7798
8203
|
const current = await board.readRole(name);
|
|
7799
8204
|
const updated = await board.setRoleCharter(board.userActor(), {
|
|
7800
8205
|
...current,
|
|
@@ -7806,7 +8211,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
|
|
|
7806
8211
|
});
|
|
7807
8212
|
var channelCommand = program.command("channel").description("Channels");
|
|
7808
8213
|
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) => {
|
|
7809
|
-
const board = await
|
|
8214
|
+
const board = await open2();
|
|
7810
8215
|
const parsed = parseChannelRef(ref);
|
|
7811
8216
|
const added = await board.addChannel(await actorFor(board, opts.as), {
|
|
7812
8217
|
project: parsed.project,
|
|
@@ -7816,7 +8221,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
|
|
|
7816
8221
|
print({ channel: added }, () => `Channel ${added} opened`);
|
|
7817
8222
|
});
|
|
7818
8223
|
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) => {
|
|
7819
|
-
const board = await
|
|
8224
|
+
const board = await open2();
|
|
7820
8225
|
const archived = await board.archiveChannel(await actorFor(board, opts.as), {
|
|
7821
8226
|
channel: ref,
|
|
7822
8227
|
reason: opts.reason
|
|
@@ -7824,12 +8229,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
|
|
|
7824
8229
|
print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
|
|
7825
8230
|
});
|
|
7826
8231
|
program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
|
|
7827
|
-
const board = await
|
|
8232
|
+
const board = await open2();
|
|
7828
8233
|
const signals = await board.listSignals(Number(opts.limit));
|
|
7829
8234
|
print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
|
|
7830
8235
|
});
|
|
7831
8236
|
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) => {
|
|
7832
|
-
const board = await
|
|
8237
|
+
const board = await open2();
|
|
7833
8238
|
const who = await actorFor(board, opts.as);
|
|
7834
8239
|
const message = await board.postMessage(who, {
|
|
7835
8240
|
channel,
|
|
@@ -7839,13 +8244,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
|
|
|
7839
8244
|
print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
|
|
7840
8245
|
});
|
|
7841
8246
|
program.command("ask <text>").description("Post to the society's general channel as the user; the front desk routes it").action(async (text) => {
|
|
7842
|
-
const board = await
|
|
8247
|
+
const board = await open2();
|
|
7843
8248
|
const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
|
|
7844
8249
|
print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
|
|
7845
8250
|
});
|
|
7846
8251
|
var task = program.command("task").description("Manage tasks");
|
|
7847
8252
|
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) => {
|
|
7848
|
-
const board = await
|
|
8253
|
+
const board = await open2();
|
|
7849
8254
|
const who = await actorFor(board, opts.as);
|
|
7850
8255
|
const created = await board.createTask(who, {
|
|
7851
8256
|
project: projectSlug,
|
|
@@ -7861,17 +8266,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
|
|
|
7861
8266
|
].join("\n"));
|
|
7862
8267
|
});
|
|
7863
8268
|
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) => {
|
|
7864
|
-
const board = await
|
|
8269
|
+
const board = await open2();
|
|
7865
8270
|
const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
|
|
7866
8271
|
print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
|
|
7867
8272
|
});
|
|
7868
8273
|
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) => {
|
|
7869
|
-
const board = await
|
|
8274
|
+
const board = await open2();
|
|
7870
8275
|
const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
|
|
7871
8276
|
print(released, () => `Stage ${released.stage} of task ${released.id} released`);
|
|
7872
8277
|
});
|
|
7873
8278
|
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) => {
|
|
7874
|
-
const board = await
|
|
8279
|
+
const board = await open2();
|
|
7875
8280
|
const advanced = await board.advanceTask(await actorFor(board, opts.as), {
|
|
7876
8281
|
task_id: id,
|
|
7877
8282
|
...opts.note === void 0 ? {} : { note: opts.note }
|
|
@@ -7880,7 +8285,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
|
|
|
7880
8285
|
${describePlan(advanced)}`);
|
|
7881
8286
|
});
|
|
7882
8287
|
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) => {
|
|
7883
|
-
const board = await
|
|
8288
|
+
const board = await open2();
|
|
7884
8289
|
const planned = await board.planTask(await actorFor(board, opts.as), {
|
|
7885
8290
|
task_id: id,
|
|
7886
8291
|
stages: PlanEditSchema.parse(JSON.parse(stages)),
|
|
@@ -7889,7 +8294,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
|
|
|
7889
8294
|
print(planned, () => describePlan(planned));
|
|
7890
8295
|
});
|
|
7891
8296
|
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) => {
|
|
7892
|
-
const board = await
|
|
8297
|
+
const board = await open2();
|
|
7893
8298
|
const updated = await board.updateTask(await actorFor(board, opts.as), {
|
|
7894
8299
|
task_id: id,
|
|
7895
8300
|
...opts.stage === void 0 ? {} : { stage: opts.stage },
|
|
@@ -7900,7 +8305,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
|
|
|
7900
8305
|
print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
|
|
7901
8306
|
});
|
|
7902
8307
|
task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
|
|
7903
|
-
const board = await
|
|
8308
|
+
const board = await open2();
|
|
7904
8309
|
const found = await board.getTask(board.userActor(), { task_id: id });
|
|
7905
8310
|
const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
|
|
7906
8311
|
const said = found.messages.map((message) => {
|
|
@@ -7920,7 +8325,7 @@ ${message.body.trim()}
|
|
|
7920
8325
|
].join("\n"));
|
|
7921
8326
|
});
|
|
7922
8327
|
task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
|
|
7923
|
-
const board = await
|
|
8328
|
+
const board = await open2();
|
|
7924
8329
|
const tasks = await board.listTasks(projectSlug);
|
|
7925
8330
|
print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
|
|
7926
8331
|
const stage = t.stages[stageIndex(t, t.stage)];
|
|
@@ -7929,7 +8334,7 @@ task.command("list <project>").description("List a project's tasks with their cu
|
|
|
7929
8334
|
});
|
|
7930
8335
|
var thread = program.command("thread").description("Manage threads");
|
|
7931
8336
|
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) => {
|
|
7932
|
-
const board = await
|
|
8337
|
+
const board = await open2();
|
|
7933
8338
|
const opened = await board.openThread(await actorFor(board, opts.as), {
|
|
7934
8339
|
...taskId === void 0 ? {} : { task_id: taskId },
|
|
7935
8340
|
...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
|
|
@@ -7939,7 +8344,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
|
|
|
7939
8344
|
print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
|
|
7940
8345
|
});
|
|
7941
8346
|
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) => {
|
|
7942
|
-
const board = await
|
|
8347
|
+
const board = await open2();
|
|
7943
8348
|
const summary = await board.closeThread(await actorFor(board, opts.as), {
|
|
7944
8349
|
thread_id: threadId,
|
|
7945
8350
|
summary: opts.summary
|
|
@@ -7947,7 +8352,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
|
|
|
7947
8352
|
print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
|
|
7948
8353
|
});
|
|
7949
8354
|
thread.command("list").description("List threads, the society's first, then each project's").option("--open", "only open threads", false).action(async (opts) => {
|
|
7950
|
-
const board = await
|
|
8355
|
+
const board = await open2();
|
|
7951
8356
|
const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
|
|
7952
8357
|
print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
|
|
7953
8358
|
const about = t.subject === void 0 ? "topic" : t.subject.kind;
|
|
@@ -7955,7 +8360,7 @@ thread.command("list").description("List threads, the society's first, then each
|
|
|
7955
8360
|
}).join("\n"));
|
|
7956
8361
|
});
|
|
7957
8362
|
thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
|
|
7958
|
-
const board = await
|
|
8363
|
+
const board = await open2();
|
|
7959
8364
|
const found = await board.readThread(threadId);
|
|
7960
8365
|
const messages = await board.listThread(threadId);
|
|
7961
8366
|
print({ thread: found, messages }, () => [
|
|
@@ -7966,17 +8371,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
|
|
|
7966
8371
|
].join("\n"));
|
|
7967
8372
|
});
|
|
7968
8373
|
program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
|
|
7969
|
-
const board = await
|
|
8374
|
+
const board = await open2();
|
|
7970
8375
|
await board.setPaused(board.userActor(), true);
|
|
7971
8376
|
print({ paused: true }, () => "Society paused");
|
|
7972
8377
|
});
|
|
7973
8378
|
program.command("resume").description("Allow wakeups again").action(async () => {
|
|
7974
|
-
const board = await
|
|
8379
|
+
const board = await open2();
|
|
7975
8380
|
await board.setPaused(board.userActor(), false);
|
|
7976
8381
|
print({ paused: false }, () => "Society resumed");
|
|
7977
8382
|
});
|
|
7978
8383
|
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) => {
|
|
7979
|
-
const board = await
|
|
8384
|
+
const board = await open2();
|
|
7980
8385
|
const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
|
|
7981
8386
|
print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
|
|
7982
8387
|
});
|
|
@@ -7992,13 +8397,13 @@ function describeCron(cron2, timeZone) {
|
|
|
7992
8397
|
}
|
|
7993
8398
|
var cron = program.command("cron").description("Crons that wake citizens on a clock");
|
|
7994
8399
|
cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
|
|
7995
|
-
const board = await
|
|
8400
|
+
const board = await open2();
|
|
7996
8401
|
const { timezone } = await board.society();
|
|
7997
8402
|
const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
|
|
7998
8403
|
print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
|
|
7999
8404
|
});
|
|
8000
8405
|
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) => {
|
|
8001
|
-
const board = await
|
|
8406
|
+
const board = await open2();
|
|
8002
8407
|
const created = await board.createCron(await actorFor(board, opts.as), {
|
|
8003
8408
|
title,
|
|
8004
8409
|
note: opts.note,
|
|
@@ -8018,7 +8423,7 @@ for (const [name, paused] of [
|
|
|
8018
8423
|
["resume", false]
|
|
8019
8424
|
]) {
|
|
8020
8425
|
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) => {
|
|
8021
|
-
const board = await
|
|
8426
|
+
const board = await open2();
|
|
8022
8427
|
const updated = await board.updateCron(await actorFor(board, opts.as), {
|
|
8023
8428
|
cron_id: id,
|
|
8024
8429
|
paused
|
|
@@ -8028,7 +8433,7 @@ for (const [name, paused] of [
|
|
|
8028
8433
|
});
|
|
8029
8434
|
}
|
|
8030
8435
|
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) => {
|
|
8031
|
-
const board = await
|
|
8436
|
+
const board = await open2();
|
|
8032
8437
|
const removed = await board.removeCron(await actorFor(board, opts.as), {
|
|
8033
8438
|
cron_id: id,
|
|
8034
8439
|
reason: opts.reason
|
|
@@ -8037,31 +8442,32 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
|
|
|
8037
8442
|
});
|
|
8038
8443
|
var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
|
|
8039
8444
|
knowledge.command("list [project]").description("List knowledge topics of a project, or of the society when no project is given").action(async (projectSlug) => {
|
|
8040
|
-
const board = await
|
|
8445
|
+
const board = await open2();
|
|
8041
8446
|
const topics = await board.listKnowledge(projectSlug ?? null);
|
|
8042
|
-
print(topics, () => topics.length === 0 ? "No knowledge yet." : topics.map((t) => `${t.topic} updated by ${t.updatedBy} at ${t.updatedAt}`).join("\n"));
|
|
8447
|
+
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"));
|
|
8043
8448
|
});
|
|
8044
8449
|
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) => {
|
|
8045
|
-
const board = await
|
|
8450
|
+
const board = await open2();
|
|
8046
8451
|
const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
|
|
8047
8452
|
if (found === void 0) {
|
|
8048
8453
|
throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
|
|
8049
8454
|
}
|
|
8050
8455
|
print(found, () => found.body);
|
|
8051
8456
|
});
|
|
8052
|
-
knowledge.command("write <topic>").description("Write a knowledge topic as the user, from a file or standard input").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) => {
|
|
8053
|
-
const board = await
|
|
8054
|
-
const body = opts.file === void 0 ? await readStdin() : await
|
|
8457
|
+
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) => {
|
|
8458
|
+
const board = await open2();
|
|
8459
|
+
const body = opts.file === void 0 ? await readStdin() : await readFile5(path7.resolve(opts.file), "utf8");
|
|
8055
8460
|
const written = await board.writeKnowledge(await actorFor(board, opts.as), {
|
|
8056
8461
|
project: opts.project ?? null,
|
|
8057
8462
|
topic,
|
|
8058
|
-
body
|
|
8463
|
+
body,
|
|
8464
|
+
revision: Number(opts.revision)
|
|
8059
8465
|
});
|
|
8060
|
-
print(written, () => `Wrote ${written.topic} for ${written.project ?? "the society"}`);
|
|
8466
|
+
print(written, () => `Wrote ${written.topic} for ${written.project ?? "the society"} at revision ${written.revision}`);
|
|
8061
8467
|
});
|
|
8062
8468
|
var skill = program.command("skill").description("Skills promoted to the society");
|
|
8063
8469
|
skill.command("list").description("List the society's skills, and each member's own").action(async () => {
|
|
8064
|
-
const board = await
|
|
8470
|
+
const board = await open2();
|
|
8065
8471
|
const society = await board.listSocietySkills();
|
|
8066
8472
|
const own = [];
|
|
8067
8473
|
for (const member of await board.listAgents()) {
|
|
@@ -8076,7 +8482,7 @@ skill.command("list").description("List the society's skills, and each member's
|
|
|
8076
8482
|
].join("\n") || "No skills yet.");
|
|
8077
8483
|
});
|
|
8078
8484
|
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) => {
|
|
8079
|
-
const board = await
|
|
8485
|
+
const board = await open2();
|
|
8080
8486
|
const removed = await board.removeSkill(await actorFor(board, opts.as), {
|
|
8081
8487
|
name,
|
|
8082
8488
|
reason: opts.reason
|
|
@@ -8085,13 +8491,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
|
|
|
8085
8491
|
});
|
|
8086
8492
|
var turn = program.command("turn").description("Turn operations");
|
|
8087
8493
|
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) => {
|
|
8088
|
-
const board = await
|
|
8494
|
+
const board = await open2();
|
|
8089
8495
|
const actor = await board.actorFor(agentName);
|
|
8090
8496
|
const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
|
|
8091
8497
|
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"));
|
|
8092
8498
|
});
|
|
8093
8499
|
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) => {
|
|
8094
|
-
const board = await
|
|
8500
|
+
const board = await open2();
|
|
8095
8501
|
const event = await board.requestWake(board.userActor(), {
|
|
8096
8502
|
agent: agentName,
|
|
8097
8503
|
project: opts.project,
|