@kubitnodes/stellaris 0.3.19 → 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.
Files changed (59) hide show
  1. package/dist/cli/index.js +213 -105
  2. package/dist/server/index.js +201 -63
  3. package/package.json +1 -1
  4. package/web/dist/assets/BackLink-BjfX2KZl.js +1 -0
  5. package/web/dist/assets/{ChannelForms-BjD5Lp_u.js → ChannelForms-BYrcUldk.js} +1 -1
  6. package/web/dist/assets/ChannelView-GJ_DusLQ.js +2 -0
  7. package/web/dist/assets/{CitizenView-tvC6dNUB.js → CitizenView-Cx7IYSRr.js} +2 -2
  8. package/web/dist/assets/{CitizensView-DsUhIYtl.js → CitizensView-IKH7_yNQ.js} +1 -1
  9. package/web/dist/assets/{CliIcon-TlyIqh7V.js → CliIcon-vSuwLv2K.js} +1 -1
  10. package/web/dist/assets/CronsView-DJbi8Qhx.js +1 -0
  11. package/web/dist/assets/KnowledgeView-CoYOG9tI.js +1 -0
  12. package/web/dist/assets/{Markdown-CePsfcUS.js → Markdown-YC-fbHiE.js} +4 -4
  13. package/web/dist/assets/MessageItem-D7MUCA_T.js +1 -0
  14. package/web/dist/assets/MetricsView-CE94VW0I.js +1 -0
  15. package/web/dist/assets/NeedsYouView-Otnm3lcG.js +1 -0
  16. package/web/dist/assets/Overview-B8f6rLb2.js +1 -0
  17. package/web/dist/assets/{Pane-DGtHsANK.js → Pane-_D7n5uKH.js} +1 -1
  18. package/web/dist/assets/{Picker-Dq5Vvxod.js → Picker-DUiiQMFN.js} +1 -1
  19. package/web/dist/assets/ProjectView-D_ACOmEd.js +1 -0
  20. package/web/dist/assets/ProposalView-4d0U3nEb.js +2 -0
  21. package/web/dist/assets/{ProposalsView-BjEfHBtx.js → ProposalsView-BovF6CMD.js} +1 -1
  22. package/web/dist/assets/{RunnersView-CM-q_AX9.js → RunnersView-B2_uCVHC.js} +1 -1
  23. package/web/dist/assets/SocietyView-Bf0iX8AY.js +1 -0
  24. package/web/dist/assets/SubjectThread-B_Vmgs3d.js +1 -0
  25. package/web/dist/assets/{TaskFileView-BMAkbh-e.js → TaskFileView-BzO0gjE6.js} +2 -2
  26. package/web/dist/assets/{TaskView-HDwqXuYy.js → TaskView-DB_Nji99.js} +2 -2
  27. package/web/dist/assets/{TasksView-Dt8M_z96.js → TasksView-Bz36YYWq.js} +1 -1
  28. package/web/dist/assets/{ThreadForms-DbV3kPCi.js → ThreadForms-1mbMZsbv.js} +1 -1
  29. package/web/dist/assets/ThreadItem-QJSA92C4.js +1 -0
  30. package/web/dist/assets/ThreadView-BECLqt5B.js +1 -0
  31. package/web/dist/assets/{citizen-DQ0Bmk-r.js → citizen-DVFr5T6R.js} +1 -1
  32. package/web/dist/assets/{compose-Cx9c9k90.js → compose-_6feqP1t.js} +1 -1
  33. package/web/dist/assets/{format-SP8GnzAX.js → format-DxXtbdNM.js} +1 -1
  34. package/web/dist/assets/{governance-DX2ph-qr.js → governance-C9xH4QTm.js} +1 -1
  35. package/web/dist/assets/index-BEVCPm4C.js +23 -0
  36. package/web/dist/assets/index-BkOq4PSU.css +2 -0
  37. package/web/dist/assets/{link-DxSeFzFf.js → link-CTjFMNoY.js} +1 -1
  38. package/web/dist/assets/{tasks-BeWF0Xy2.js → tasks-B6I9DJ8O.js} +1 -1
  39. package/web/dist/assets/useMatch-B3RzAe8y.js +1 -0
  40. package/web/dist/assets/useStickyScroll-Ca7sWbGH.js +1 -0
  41. package/web/dist/index.html +9 -9
  42. package/web/dist/assets/BackLink-CDrsO9Ls.js +0 -1
  43. package/web/dist/assets/ChannelView-dI5EHpof.js +0 -2
  44. package/web/dist/assets/CronsView-BPlSyoAe.js +0 -1
  45. package/web/dist/assets/KnowledgeView-CgRGZrOK.js +0 -1
  46. package/web/dist/assets/MessageItem-kg5zWMDe.js +0 -1
  47. package/web/dist/assets/MetricsView-cJeDTfBE.js +0 -1
  48. package/web/dist/assets/NeedsYouView-Cd1TnY7W.js +0 -1
  49. package/web/dist/assets/Overview-B6aBmove.js +0 -1
  50. package/web/dist/assets/ProjectView-uskZHP8l.js +0 -1
  51. package/web/dist/assets/ProposalView-DmjIL53_.js +0 -2
  52. package/web/dist/assets/SocietyView-DZuGEqIl.js +0 -1
  53. package/web/dist/assets/SubjectThread-pCRDn42X.js +0 -1
  54. package/web/dist/assets/ThreadItem-BVlz2w6q.js +0 -1
  55. package/web/dist/assets/ThreadView-CpBytSH7.js +0 -1
  56. package/web/dist/assets/index-BDKI0Rje.js +0 -23
  57. package/web/dist/assets/index-BPVeX6tV.css +0 -2
  58. package/web/dist/assets/useMatch-HNekgJrF.js +0 -1
  59. 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 readFile6 } from "node:fs/promises";
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 readFile5, rename as rename3, rm as rm2, stat as stat2, writeFile as writeFile4 } from "node:fs/promises";
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({
@@ -1733,7 +1740,7 @@ var RunnerConfigSchema = z11.object({
1733
1740
  });
1734
1741
 
1735
1742
  // packages/shared/dist/version.js
1736
- var STELLARIS_VERSION = "0.3.19";
1743
+ var STELLARIS_VERSION = "0.3.20";
1737
1744
 
1738
1745
  // packages/board-core/dist/errors.js
1739
1746
  var BoardError = class extends Error {
@@ -1746,7 +1753,7 @@ var BoardError = class extends Error {
1746
1753
  };
1747
1754
 
1748
1755
  // packages/board-core/dist/events.js
1749
- import { appendFile, readFile as readFile2 } from "node:fs/promises";
1756
+ import { appendFile, open, stat } from "node:fs/promises";
1750
1757
  import path2 from "node:path";
1751
1758
 
1752
1759
  // packages/board-core/dist/fs.js
@@ -1831,10 +1838,19 @@ async function listDirs(dir) {
1831
1838
 
1832
1839
  // packages/board-core/dist/events.js
1833
1840
  var LEADING_ID = /^\{"id":"([0-9A-HJKMNP-TV-Z]{26})"/;
1841
+ var ID_PREFIX_BYTES = 64;
1842
+ var NEWLINE = 10;
1834
1843
  var EventLog = class {
1835
1844
  file;
1836
1845
  newId;
1837
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();
1838
1854
  constructor(file, newId, now) {
1839
1855
  this.file = file;
1840
1856
  this.newId = newId;
@@ -1851,48 +1867,97 @@ var EventLog = class {
1851
1867
  await ensureDir(path2.dirname(this.file));
1852
1868
  await appendFile(this.file, `${JSON.stringify(event)}
1853
1869
  `, "utf8");
1870
+ for (const listener of this.listeners) {
1871
+ try {
1872
+ listener(event);
1873
+ } catch {
1874
+ }
1875
+ }
1854
1876
  return event;
1855
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
+ }
1856
1888
  /** Events with an id greater than `since`, oldest first. `null` reads from the beginning. */
1857
1889
  async readSince(since, limit = 1e3) {
1858
- if (!await exists(this.file)) {
1859
- return [];
1860
- }
1861
- const raw = await readFile2(this.file, "utf8");
1862
- const events = [];
1863
- for (const line of raw.split("\n")) {
1864
- if (line.trim().length === 0) {
1890
+ await this.refresh();
1891
+ const chosen = [];
1892
+ for (const line of this.lines) {
1893
+ if (since !== null && line.id <= since) {
1865
1894
  continue;
1866
1895
  }
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) {
1873
- continue;
1874
- }
1875
- events.push(event);
1876
- if (events.length >= limit) {
1896
+ chosen.push(line);
1897
+ if (chosen.length >= limit) {
1877
1898
  break;
1878
1899
  }
1879
1900
  }
1880
- return events;
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))));
1881
1908
  }
1882
1909
  /** The id of the newest event, or null for an empty log. */
1883
1910
  async lastId() {
1884
- if (!await exists(this.file)) {
1885
- return null;
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();
1886
1954
  }
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
1955
  }
1891
1956
  };
1892
1957
 
1893
1958
  // packages/board-core/dist/files.js
1894
1959
  import { createHash } from "node:crypto";
1895
- import { mkdir as mkdir2, readdir as readdir2, readFile as readFile3, rename as rename2, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
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";
1896
1961
  import path3 from "node:path";
1897
1962
  var MAX_FILE_BYTES = 8 * 1024 * 1024;
1898
1963
  function isMissing2(error) {
@@ -1914,9 +1979,9 @@ var FileTree = class {
1914
1979
  }
1915
1980
  try {
1916
1981
  const absolute = path3.join(root, ...relative.split("/"));
1917
- const info = await stat(absolute);
1982
+ const info = await stat2(absolute);
1918
1983
  if (info.isFile() && info.size <= MAX_FILE_BYTES) {
1919
- files[relative] = (await readFile3(absolute)).toString("base64");
1984
+ files[relative] = (await readFile2(absolute)).toString("base64");
1920
1985
  }
1921
1986
  } catch (error) {
1922
1987
  if (!isMissing2(error)) {
@@ -1972,7 +2037,7 @@ var FileTree = class {
1972
2037
  }
1973
2038
  async hash(absolute) {
1974
2039
  try {
1975
- const info = await stat(absolute);
2040
+ const info = await stat2(absolute);
1976
2041
  if (info.size > MAX_FILE_BYTES) {
1977
2042
  return null;
1978
2043
  }
@@ -1981,7 +2046,7 @@ var FileTree = class {
1981
2046
  if (known !== void 0 && known.mtimeMs === info.mtimeMs && known.size === info.size) {
1982
2047
  return fileVersion(known.sha, executable);
1983
2048
  }
1984
- const sha = createHash("sha256").update(await readFile3(absolute)).digest("hex");
2049
+ const sha = createHash("sha256").update(await readFile2(absolute)).digest("hex");
1985
2050
  this.hashes.set(absolute, { mtimeMs: info.mtimeMs, size: info.size, sha });
1986
2051
  return fileVersion(sha, executable);
1987
2052
  } catch (error) {
@@ -1994,7 +2059,7 @@ var FileTree = class {
1994
2059
  };
1995
2060
 
1996
2061
  // packages/board-core/dist/homes.js
1997
- import { chmod, mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
2062
+ import { chmod, mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
1998
2063
  import path4 from "node:path";
1999
2064
  import { execa } from "execa";
2000
2065
  var HOME_GITIGNORE = [
@@ -2138,7 +2203,7 @@ var HomeRepos = class {
2138
2203
  await writeFile3(ignore, IGNORE_FILE, "utf8");
2139
2204
  await git(["add", "--all"], home);
2140
2205
  await git(["commit", "--quiet", "--allow-empty", "-m", "home: created by the board"], home);
2141
- } else if (await readFile4(ignore, "utf8").catch(() => "") !== IGNORE_FILE) {
2206
+ } else if (await readFile3(ignore, "utf8").catch(() => "") !== IGNORE_FILE) {
2142
2207
  await writeFile3(ignore, IGNORE_FILE, "utf8");
2143
2208
  await git(["add", ".gitignore"], home);
2144
2209
  await git(["commit", "--quiet", "-m", "home: the board's ignore file"], home);
@@ -3057,8 +3122,8 @@ function restage(existing, edit) {
3057
3122
  };
3058
3123
  }
3059
3124
  function reopenStage(stage) {
3060
- const { completedBy: _by, completedAt: _at, ...open2 } = stage;
3061
- return open2;
3125
+ const { completedBy: _by, completedAt: _at, ...open3 } = stage;
3126
+ return open3;
3062
3127
  }
3063
3128
  function gateChanges(before, after) {
3064
3129
  const changes = [];
@@ -3453,7 +3518,7 @@ var Board = class _Board {
3453
3518
  const task2 = { ...TaskFrontmatterSchema.parse(raw.data), body: raw.body };
3454
3519
  if ((legacy === "open" || legacy === "closed") && !await exists(this.paths.threadFile(threads, task2.id))) {
3455
3520
  const ended = task2.status === "done" || task2.status === "abandoned";
3456
- const open2 = legacy === "open" && !ended;
3521
+ const open3 = legacy === "open" && !ended;
3457
3522
  const openedBy = opened.get(task2.id);
3458
3523
  const closedBy = closed.get(task2.id);
3459
3524
  await this.writeThread(threads, {
@@ -3461,10 +3526,10 @@ var Board = class _Board {
3461
3526
  channel: channelRef(slug, "general"),
3462
3527
  title: task2.title,
3463
3528
  subject: { kind: "task", id: task2.id },
3464
- state: open2 ? "open" : "closed",
3529
+ state: open3 ? "open" : "closed",
3465
3530
  openedBy: openedBy?.actor ?? task2.createdBy,
3466
3531
  openedAt: openedBy?.ts ?? task2.createdAt,
3467
- ...open2 ? {} : {
3532
+ ...open3 ? {} : {
3468
3533
  closedBy: closedBy?.actor ?? SYSTEM_ACTOR.name,
3469
3534
  closedAt: closedBy?.ts ?? this.now().toISOString()
3470
3535
  },
@@ -3527,7 +3592,7 @@ var Board = class _Board {
3527
3592
  for (const agent2 of await this.listAgents()) {
3528
3593
  const file = this.paths.agentCursors(agent2.name);
3529
3594
  if (await exists(file)) {
3530
- const legacy = Legacy.safeParse(JSON.parse(await readFile5(file, "utf8")));
3595
+ const legacy = Legacy.safeParse(JSON.parse(await readFile4(file, "utf8")));
3531
3596
  if (legacy.success) {
3532
3597
  await writeJson(file, { digest: legacy.data.inbox });
3533
3598
  }
@@ -3866,6 +3931,10 @@ ${charter.purpose}
3866
3931
  async latestEventId() {
3867
3932
  return this.events.lastId();
3868
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
+ }
3869
3938
  /** The roster as projected: every citizen with identity, reach, availability, and profile. */
3870
3939
  async listMembers() {
3871
3940
  const members = [];
@@ -3910,7 +3979,7 @@ ${charter.purpose}
3910
3979
  project: project2,
3911
3980
  revision: revision.success ? revision.data : 0,
3912
3981
  updatedBy: updatedBy.success ? updatedBy.data : SYSTEM_ACTOR.name,
3913
- updatedAt: updatedAt.success ? updatedAt.data : (await stat2(file)).mtime.toISOString(),
3982
+ updatedAt: updatedAt.success ? updatedAt.data : (await stat3(file)).mtime.toISOString(),
3914
3983
  body
3915
3984
  };
3916
3985
  }
@@ -4012,6 +4081,28 @@ ${charter.purpose}
4012
4081
  async readThread(id) {
4013
4082
  return (await this.findThread(id)).thread;
4014
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
+ }
4015
4106
  /**
4016
4107
  * What citizens have asked the user and the user has not answered, oldest first: every message
4017
4108
  * by someone else that mentions the user, in an open thread or a channel the society or an
@@ -4513,12 +4604,12 @@ ${DASHBOARD_LINE}
4513
4604
  continue;
4514
4605
  }
4515
4606
  const named = thread2 === null ? channelConversation(filedIn, message.channel) : void 0;
4516
- let open2 = named === void 0 ? void 0 : openChannels.get(message.channel);
4517
- if (named !== void 0 && open2 === void 0) {
4518
- open2 = await this.channelOpen(message.channel);
4519
- openChannels.set(message.channel, open2);
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);
4520
4611
  }
4521
- const part = thread2 ?? (open2 === true ? `#${named}` : null);
4612
+ const part = thread2 ?? (open3 === true ? `#${named}` : null);
4522
4613
  if (conversation !== void 0 && (filedIn !== conversation.scope || part !== conversation.part)) {
4523
4614
  continue;
4524
4615
  }
@@ -4583,7 +4674,7 @@ ${task2.body}`;
4583
4674
  }
4584
4675
  }
4585
4676
  for (const file of await listFiles(this.paths.projectKnowledge(project2))) {
4586
- const content = await readFile5(path6.join(this.paths.projectKnowledge(project2), file), "utf8");
4677
+ const content = await readFile4(path6.join(this.paths.projectKnowledge(project2), file), "utf8");
4587
4678
  if (content.toLowerCase().includes(needle)) {
4588
4679
  hits.push({
4589
4680
  kind: "knowledge",
@@ -4621,7 +4712,7 @@ ${task2.body}`;
4621
4712
  });
4622
4713
  }
4623
4714
  for (const entry of shared) {
4624
- const content = await readFile5(entry.file, "utf8");
4715
+ const content = await readFile4(entry.file, "utf8");
4625
4716
  if (content.toLowerCase().includes(needle)) {
4626
4717
  hits.push({
4627
4718
  kind: entry.kind,
@@ -6110,7 +6201,7 @@ ${rationale}`}`);
6110
6201
  throw new BoardError("NOT_FOUND", `no transcript of turn ${turnId} for ${agent2}`);
6111
6202
  }
6112
6203
  const entries = [];
6113
- for (const line of (await readFile5(file, "utf8")).split("\n")) {
6204
+ for (const line of (await readFile4(file, "utf8")).split("\n")) {
6114
6205
  if (line.trim() === "")
6115
6206
  continue;
6116
6207
  const parsed = TranscriptEntrySchema.safeParse(JSON.parse(line));
@@ -6589,7 +6680,7 @@ ${rationale}`}`);
6589
6680
  }
6590
6681
  const file = entry.isFile() ? conflictCopyOf(relative) : null;
6591
6682
  if (file !== null) {
6592
- const since = await this.homes.changedAt(home, relative) ?? (await stat2(path6.join(home, relative)).catch(() => null))?.mtime.toISOString();
6683
+ const since = await this.homes.changedAt(home, relative) ?? (await stat3(path6.join(home, relative)).catch(() => null))?.mtime.toISOString();
6593
6684
  if (since !== void 0) {
6594
6685
  found.push({ path: relative, file, since });
6595
6686
  }
@@ -7167,7 +7258,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
7167
7258
  if (!isSafeRelativePath(entry.path)) {
7168
7259
  continue;
7169
7260
  }
7170
- const bytes = await readFile5(path6.join(dir, ...entry.path.split("/"))).catch(() => null);
7261
+ const bytes = await readFile4(path6.join(dir, ...entry.path.split("/"))).catch(() => null);
7171
7262
  const text = bytes === null || bytes.includes(0) ? null : bytes.toString("utf8");
7172
7263
  files.push({
7173
7264
  ...entry,
@@ -7866,6 +7957,23 @@ ${charter.purpose}
7866
7957
  yield* this.threadMessagesIn(this.paths.threads(slug), since);
7867
7958
  }
7868
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
+ }
7869
7977
  async *threadMessagesIn(threads, since) {
7870
7978
  for (const id of await listDirs(threads)) {
7871
7979
  yield* await this.readMessagesIn(this.paths.threadMessages(threads, id), since);
@@ -7882,7 +7990,7 @@ function globals() {
7882
7990
  function print(value, human) {
7883
7991
  console.log(globals().json ? JSON.stringify(value, null, 2) : human());
7884
7992
  }
7885
- async function open() {
7993
+ async function open2() {
7886
7994
  return Board.open(globals().data);
7887
7995
  }
7888
7996
  async function actorFor(board, as) {
@@ -7928,7 +8036,7 @@ user.command("token").description("Issue the user a new token and print it once;
7928
8036
  if (!opts.rotate) {
7929
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");
7930
8038
  }
7931
- const board = await open();
8039
+ const board = await open2();
7932
8040
  const userToken = await board.rotateUserToken();
7933
8041
  print({ userToken }, () => [
7934
8042
  `User token (shown once): ${userToken}`,
@@ -7937,7 +8045,7 @@ user.command("token").description("Issue the user a new token and print it once;
7937
8045
  });
7938
8046
  var project = program.command("project").description("Manage projects");
7939
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) => {
7940
- const board = await open();
8048
+ const board = await open2();
7941
8049
  const added = await board.addProject(board.userActor(), {
7942
8050
  slug,
7943
8051
  name: opts.name,
@@ -7947,7 +8055,7 @@ project.command("add <slug>").description("Add a project with its default channe
7947
8055
  print(added, () => `Project ${added.slug} added with channels ${added.channels.join(", ")}`);
7948
8056
  });
7949
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) => {
7950
- const board = await open();
8058
+ const board = await open2();
7951
8059
  const configured = await board.configureProject(await actorFor(board, opts.as), {
7952
8060
  project: slug,
7953
8061
  on_done: CompletionEffectSchema.parse(opts.onDone)
@@ -7955,7 +8063,7 @@ project.command("configure <slug>").description("Set a project's completion effe
7955
8063
  print(configured, () => `Project ${configured.slug}: on done ${configured.onDone}`);
7956
8064
  });
7957
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) => {
7958
- const board = await open();
8066
+ const board = await open2();
7959
8067
  const archived = await board.archiveProject(board.userActor(), {
7960
8068
  project: slug,
7961
8069
  reason: opts.reason
@@ -7963,13 +8071,13 @@ project.command("archive <slug>").description("Archive a project with no task in
7963
8071
  print(archived, () => `Project ${archived.slug} archived`);
7964
8072
  });
7965
8073
  project.command("list").description("List projects").action(async () => {
7966
- const board = await open();
8074
+ const board = await open2();
7967
8075
  const projects = await board.listProjects();
7968
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"));
7969
8077
  });
7970
8078
  var runner = program.command("runner").description("Manage runners");
7971
8079
  runner.command("add <name>").description("Register a runner and print its token once").action(async (name) => {
7972
- const board = await open();
8080
+ const board = await open2();
7973
8081
  const { runner: added, token } = await board.addRunner(board.userActor(), name);
7974
8082
  print({ runner: added, token }, () => [
7975
8083
  `Runner ${added.name} added`,
@@ -7978,13 +8086,13 @@ runner.command("add <name>").description("Register a runner and print its token
7978
8086
  ].join("\n"));
7979
8087
  });
7980
8088
  runner.command("list").description("List runners and what they offer").action(async () => {
7981
- const board = await open();
8089
+ const board = await open2();
7982
8090
  const runners = await board.listRunners();
7983
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"));
7984
8092
  });
7985
8093
  var agent = program.command("agent").description("Manage agents");
7986
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) => {
7987
- const board = await open();
8095
+ const board = await open2();
7988
8096
  const cli = opts.cli === void 0 ? null : CliKindSchema.parse(opts.cli);
7989
8097
  const { agent: added, token } = await board.addAgent(board.userActor(), {
7990
8098
  name,
@@ -7997,7 +8105,7 @@ agent.command("add <name>").description("Add an agent and print its board token
7997
8105
  print({ agent: added, token }, () => [`Agent ${added.name} added as ${added.role}`, `Token (shown once): ${token}`].join("\n"));
7998
8106
  });
7999
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) => {
8000
- const board = await open();
8108
+ const board = await open2();
8001
8109
  if (chosen === void 0) {
8002
8110
  const current = await board.readAgent(name);
8003
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}`);
@@ -8007,7 +8115,7 @@ agent.command("runner <name> [runner]").description("Show or set the runner a ci
8007
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`);
8008
8116
  });
8009
8117
  agent.command("list").description("List agents").action(async () => {
8010
- const board = await open();
8118
+ const board = await open2();
8011
8119
  const agents = await board.listAgents();
8012
8120
  const members = await board.listMembers();
8013
8121
  print(agents, () => agents.map((a) => {
@@ -8016,28 +8124,28 @@ agent.command("list").description("List agents").action(async () => {
8016
8124
  }).join("\n"));
8017
8125
  });
8018
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) => {
8019
- const board = await open();
8127
+ const board = await open2();
8020
8128
  const updated = await board.setAgentModel(board.userActor(), name, model ?? null);
8021
8129
  print(updated, () => `${updated.name} runs with ${updated.model ?? "its CLI's default model"} from its next turn`);
8022
8130
  });
8023
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) => {
8024
- const board = await open();
8132
+ const board = await open2();
8025
8133
  const updated = await board.setAgentEffort(board.userActor(), name, effort ?? null);
8026
8134
  print(updated, () => `${updated.name} runs with ${updated.effort === void 0 ? "its model's default effort" : `${updated.effort} effort`} from its next turn`);
8027
8135
  });
8028
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) => {
8029
- const board = await open();
8137
+ const board = await open2();
8030
8138
  const retired = await board.retireAgent(board.userActor(), { name, reason: opts.reason });
8031
8139
  print(retired, () => `Agent ${retired.name} retired: ${opts.reason}`);
8032
8140
  });
8033
8141
  var proposal = program.command("proposal").description("Governance proposals");
8034
8142
  proposal.command("list").description("List proposals").action(async () => {
8035
- const board = await open();
8143
+ const board = await open2();
8036
8144
  const proposals = await board.listProposals();
8037
8145
  print(proposals, () => proposals.length === 0 ? "No proposals." : proposals.map((p) => `${p.id} ${p.kind} ${p.status} by ${p.proposedBy}`).join("\n"));
8038
8146
  });
8039
8147
  proposal.command("show <id>").description("Show a proposal with its charter and rationale").action(async (id) => {
8040
- const board = await open();
8148
+ const board = await open2();
8041
8149
  const found = await board.readProposal(id);
8042
8150
  print(found, () => [
8043
8151
  `${found.id} ${found.kind} ${found.status} by ${found.proposedBy}`,
@@ -8047,7 +8155,7 @@ proposal.command("show <id>").description("Show a proposal with its charter and
8047
8155
  ].filter((line) => line.length > 0).join("\n"));
8048
8156
  });
8049
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) => {
8050
- const board = await open();
8158
+ const board = await open2();
8051
8159
  const created = await board.propose(await actorFor(board, opts.as), {
8052
8160
  kind: ProposalKindSchema.parse(opts.kind),
8053
8161
  charter: parseCharter(opts.charter),
@@ -8056,7 +8164,7 @@ proposal.command("create").description("Propose a member, role, channel, realloc
8056
8164
  print(created, () => `Proposal ${created.id} (${created.kind}) is ${created.status}`);
8057
8165
  });
8058
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) => {
8059
- const board = await open();
8167
+ const board = await open2();
8060
8168
  const decision = await board.approve(await actorFor(board, opts.as), {
8061
8169
  proposal_id: id,
8062
8170
  ...opts.reason === void 0 ? {} : { reason: opts.reason }
@@ -8065,7 +8173,7 @@ proposal.command("approve <id>").description("Approve a proposal; the board prov
8065
8173
  print({ decision, proposal: after }, () => `Proposal ${id} approved; it is now ${after.status}`);
8066
8174
  });
8067
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) => {
8068
- const board = await open();
8176
+ const board = await open2();
8069
8177
  const decision = await board.reject(await actorFor(board, opts.as), {
8070
8178
  proposal_id: id,
8071
8179
  reason: opts.reason
@@ -8074,14 +8182,14 @@ proposal.command("reject <id>").description("Reject a proposal with a reason").r
8074
8182
  });
8075
8183
  var role = program.command("role").description("Role charters");
8076
8184
  role.command("list").description("List role charters").action(async () => {
8077
- const board = await open();
8185
+ const board = await open2();
8078
8186
  const roles = await board.listRoles();
8079
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"));
8080
8188
  });
8081
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", [
8082
8190
  "heartbeat"
8083
8191
  ]).action(async (name, opts) => {
8084
- const board = await open();
8192
+ const board = await open2();
8085
8193
  const added = await board.setRoleCharter(board.userActor(), {
8086
8194
  name,
8087
8195
  purpose: opts.purpose,
@@ -8091,7 +8199,7 @@ role.command("add <name>").description("Write a new role charter directly as the
8091
8199
  print(added, () => `Role ${added.name} written with ${added.verbs.length} verbs`);
8092
8200
  });
8093
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) => {
8094
- const board = await open();
8202
+ const board = await open2();
8095
8203
  const current = await board.readRole(name);
8096
8204
  const updated = await board.setRoleCharter(board.userActor(), {
8097
8205
  ...current,
@@ -8103,7 +8211,7 @@ role.command("set <name>").description("Change a charter directly as the user: s
8103
8211
  });
8104
8212
  var channelCommand = program.command("channel").description("Channels");
8105
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) => {
8106
- const board = await open();
8214
+ const board = await open2();
8107
8215
  const parsed = parseChannelRef(ref);
8108
8216
  const added = await board.addChannel(await actorFor(board, opts.as), {
8109
8217
  project: parsed.project,
@@ -8113,7 +8221,7 @@ channelCommand.command("add <ref>").description("Open a channel: <name> for the
8113
8221
  print({ channel: added }, () => `Channel ${added} opened`);
8114
8222
  });
8115
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) => {
8116
- const board = await open();
8224
+ const board = await open2();
8117
8225
  const archived = await board.archiveChannel(await actorFor(board, opts.as), {
8118
8226
  channel: ref,
8119
8227
  reason: opts.reason
@@ -8121,12 +8229,12 @@ channelCommand.command("archive <ref>").description("Archive a channel whose wor
8121
8229
  print(archived, () => `Channel ${ref} archived: ${archived.reason}`);
8122
8230
  });
8123
8231
  program.command("signals").description("Recent operations signals, oldest first").option("--limit <n>", "how many", "50").action(async (opts) => {
8124
- const board = await open();
8232
+ const board = await open2();
8125
8233
  const signals = await board.listSignals(Number(opts.limit));
8126
8234
  print(signals, () => signals.length === 0 ? "No signals yet." : signals.map((s) => `[${s.ts}] ${s.signal.kind} ${s.signal.summary}`).join("\n"));
8127
8235
  });
8128
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) => {
8129
- const board = await open();
8237
+ const board = await open2();
8130
8238
  const who = await actorFor(board, opts.as);
8131
8239
  const message = await board.postMessage(who, {
8132
8240
  channel,
@@ -8136,13 +8244,13 @@ program.command("post <channel> <body>").description("Post a message to a channe
8136
8244
  print(message, () => `Posted ${message.id} to ${message.channel}${message.thread === void 0 ? "" : ` (thread ${message.thread})`}`);
8137
8245
  });
8138
8246
  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 open();
8247
+ const board = await open2();
8140
8248
  const message = await board.postMessage(board.userActor(), { channel: "general", body: text });
8141
8249
  print(message, () => `Posted ${message.id} to general; the concierge wakes on it`);
8142
8250
  });
8143
8251
  var task = program.command("task").description("Manage tasks");
8144
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) => {
8145
- const board = await open();
8253
+ const board = await open2();
8146
8254
  const who = await actorFor(board, opts.as);
8147
8255
  const created = await board.createTask(who, {
8148
8256
  project: projectSlug,
@@ -8158,17 +8266,17 @@ task.command("create <project> <title>").description("Create a task with a plan,
8158
8266
  ].join("\n"));
8159
8267
  });
8160
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) => {
8161
- const board = await open();
8269
+ const board = await open2();
8162
8270
  const claimed = await board.claimTask(await actorFor(board, opts.as), { task_id: id });
8163
8271
  print(claimed, () => `Stage ${claimed.stage} of task ${claimed.id} held by ${claimed.claimedBy ?? "?"} until ${claimed.leaseExpiresAt ?? "?"}`);
8164
8272
  });
8165
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) => {
8166
- const board = await open();
8274
+ const board = await open2();
8167
8275
  const released = await board.releaseTask(await actorFor(board, opts.as), { task_id: id });
8168
8276
  print(released, () => `Stage ${released.stage} of task ${released.id} released`);
8169
8277
  });
8170
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) => {
8171
- const board = await open();
8279
+ const board = await open2();
8172
8280
  const advanced = await board.advanceTask(await actorFor(board, opts.as), {
8173
8281
  task_id: id,
8174
8282
  ...opts.note === void 0 ? {} : { note: opts.note }
@@ -8177,7 +8285,7 @@ task.command("advance <id>").description("Finish the current stage; past the las
8177
8285
  ${describePlan(advanced)}`);
8178
8286
  });
8179
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) => {
8180
- const board = await open();
8288
+ const board = await open2();
8181
8289
  const planned = await board.planTask(await actorFor(board, opts.as), {
8182
8290
  task_id: id,
8183
8291
  stages: PlanEditSchema.parse(JSON.parse(stages)),
@@ -8186,7 +8294,7 @@ task.command("plan <id> <stages>").description("Reshape the stages ahead: pass e
8186
8294
  print(planned, () => describePlan(planned));
8187
8295
  });
8188
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) => {
8189
- const board = await open();
8297
+ const board = await open2();
8190
8298
  const updated = await board.updateTask(await actorFor(board, opts.as), {
8191
8299
  task_id: id,
8192
8300
  ...opts.stage === void 0 ? {} : { stage: opts.stage },
@@ -8197,7 +8305,7 @@ task.command("update <id>").description("Send a task back to an earlier stage, a
8197
8305
  print(updated, () => `Task ${updated.id} is ${updated.status} at ${updated.stage}`);
8198
8306
  });
8199
8307
  task.command("show <id>").description("Show a task with its plan and its thread").action(async (id) => {
8200
- const board = await open();
8308
+ const board = await open2();
8201
8309
  const found = await board.getTask(board.userActor(), { task_id: id });
8202
8310
  const threadState = await board.readThread(id).then((t) => t.state).catch(() => "none");
8203
8311
  const said = found.messages.map((message) => {
@@ -8217,7 +8325,7 @@ ${message.body.trim()}
8217
8325
  ].join("\n"));
8218
8326
  });
8219
8327
  task.command("list <project>").description("List a project's tasks with their current stage").action(async (projectSlug) => {
8220
- const board = await open();
8328
+ const board = await open2();
8221
8329
  const tasks = await board.listTasks(projectSlug);
8222
8330
  print(tasks, () => tasks.length === 0 ? "No tasks." : tasks.map((t) => {
8223
8331
  const stage = t.stages[stageIndex(t, t.stage)];
@@ -8226,7 +8334,7 @@ task.command("list <project>").description("List a project's tasks with their cu
8226
8334
  });
8227
8335
  var thread = program.command("thread").description("Manage threads");
8228
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) => {
8229
- const board = await open();
8337
+ const board = await open2();
8230
8338
  const opened = await board.openThread(await actorFor(board, opts.as), {
8231
8339
  ...taskId === void 0 ? {} : { task_id: taskId },
8232
8340
  ...opts.proposal === void 0 ? {} : { proposal_id: opts.proposal },
@@ -8236,7 +8344,7 @@ thread.command("open [taskId]").description("Open a thread on a task, on a propo
8236
8344
  print(opened, () => `Thread ${opened.id} opened on ${opened.channel}: ${opened.title}`);
8237
8345
  });
8238
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) => {
8239
- const board = await open();
8347
+ const board = await open2();
8240
8348
  const summary = await board.closeThread(await actorFor(board, opts.as), {
8241
8349
  thread_id: threadId,
8242
8350
  summary: opts.summary
@@ -8244,7 +8352,7 @@ thread.command("close <threadId>").description("Close a thread with a summary po
8244
8352
  print(summary, () => `Thread closed; summary posted as ${summary.id} to ${summary.channel}`);
8245
8353
  });
8246
8354
  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 open();
8355
+ const board = await open2();
8248
8356
  const threads = (await board.listThreads()).filter((t) => !opts.open || t.state === "open");
8249
8357
  print(threads, () => threads.length === 0 ? "No threads." : threads.map((t) => {
8250
8358
  const about = t.subject === void 0 ? "topic" : t.subject.kind;
@@ -8252,7 +8360,7 @@ thread.command("list").description("List threads, the society's first, then each
8252
8360
  }).join("\n"));
8253
8361
  });
8254
8362
  thread.command("show <threadId>").description("Show a thread and its messages").action(async (threadId) => {
8255
- const board = await open();
8363
+ const board = await open2();
8256
8364
  const found = await board.readThread(threadId);
8257
8365
  const messages = await board.listThread(threadId);
8258
8366
  print({ thread: found, messages }, () => [
@@ -8263,17 +8371,17 @@ thread.command("show <threadId>").description("Show a thread and its messages").
8263
8371
  ].join("\n"));
8264
8372
  });
8265
8373
  program.command("pause").description("Stop all wakeups; turns in flight finish").action(async () => {
8266
- const board = await open();
8374
+ const board = await open2();
8267
8375
  await board.setPaused(board.userActor(), true);
8268
8376
  print({ paused: true }, () => "Society paused");
8269
8377
  });
8270
8378
  program.command("resume").description("Allow wakeups again").action(async () => {
8271
- const board = await open();
8379
+ const board = await open2();
8272
8380
  await board.setPaused(board.userActor(), false);
8273
8381
  print({ paused: false }, () => "Society resumed");
8274
8382
  });
8275
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) => {
8276
- const board = await open();
8384
+ const board = await open2();
8277
8385
  const society = zone === void 0 ? await board.society() : await board.setTimezone(board.userActor(), zone);
8278
8386
  print({ timezone: society.timezone }, () => `Time zone: ${society.timezone}`);
8279
8387
  });
@@ -8289,13 +8397,13 @@ function describeCron(cron2, timeZone) {
8289
8397
  }
8290
8398
  var cron = program.command("cron").description("Crons that wake citizens on a clock");
8291
8399
  cron.command("list").description("List crons, ended ones too with --all").option("--all", "include ended crons", false).action(async (opts) => {
8292
- const board = await open();
8400
+ const board = await open2();
8293
8401
  const { timezone } = await board.society();
8294
8402
  const crons = (await board.listCrons()).filter((each) => opts.all || each.ended === void 0);
8295
8403
  print(crons, () => crons.length === 0 ? "No crons." : crons.map((each) => describeCron(each, timezone)).join("\n"));
8296
8404
  });
8297
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) => {
8298
- const board = await open();
8406
+ const board = await open2();
8299
8407
  const created = await board.createCron(await actorFor(board, opts.as), {
8300
8408
  title,
8301
8409
  note: opts.note,
@@ -8315,7 +8423,7 @@ for (const [name, paused] of [
8315
8423
  ["resume", false]
8316
8424
  ]) {
8317
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) => {
8318
- const board = await open();
8426
+ const board = await open2();
8319
8427
  const updated = await board.updateCron(await actorFor(board, opts.as), {
8320
8428
  cron_id: id,
8321
8429
  paused
@@ -8325,7 +8433,7 @@ for (const [name, paused] of [
8325
8433
  });
8326
8434
  }
8327
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) => {
8328
- const board = await open();
8436
+ const board = await open2();
8329
8437
  const removed = await board.removeCron(await actorFor(board, opts.as), {
8330
8438
  cron_id: id,
8331
8439
  reason: opts.reason
@@ -8334,12 +8442,12 @@ cron.command("remove <id>").description("End a cron for good").requiredOption("-
8334
8442
  });
8335
8443
  var knowledge = program.command("knowledge").description("Shared knowledge, by project or society");
8336
8444
  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 open();
8445
+ const board = await open2();
8338
8446
  const topics = await board.listKnowledge(projectSlug ?? null);
8339
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"));
8340
8448
  });
8341
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) => {
8342
- const board = await open();
8450
+ const board = await open2();
8343
8451
  const found = (await board.listKnowledge(opts.project ?? null)).find((t) => t.topic === topic);
8344
8452
  if (found === void 0) {
8345
8453
  throw new BoardError("NOT_FOUND", `no knowledge topic ${topic}`);
@@ -8347,8 +8455,8 @@ knowledge.command("show <topic>").description("Print a knowledge topic").option(
8347
8455
  print(found, () => found.body);
8348
8456
  });
8349
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) => {
8350
- const board = await open();
8351
- const body = opts.file === void 0 ? await readStdin() : await readFile6(path7.resolve(opts.file), "utf8");
8458
+ const board = await open2();
8459
+ const body = opts.file === void 0 ? await readStdin() : await readFile5(path7.resolve(opts.file), "utf8");
8352
8460
  const written = await board.writeKnowledge(await actorFor(board, opts.as), {
8353
8461
  project: opts.project ?? null,
8354
8462
  topic,
@@ -8359,7 +8467,7 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
8359
8467
  });
8360
8468
  var skill = program.command("skill").description("Skills promoted to the society");
8361
8469
  skill.command("list").description("List the society's skills, and each member's own").action(async () => {
8362
- const board = await open();
8470
+ const board = await open2();
8363
8471
  const society = await board.listSocietySkills();
8364
8472
  const own = [];
8365
8473
  for (const member of await board.listAgents()) {
@@ -8374,7 +8482,7 @@ skill.command("list").description("List the society's skills, and each member's
8374
8482
  ].join("\n") || "No skills yet.");
8375
8483
  });
8376
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) => {
8377
- const board = await open();
8485
+ const board = await open2();
8378
8486
  const removed = await board.removeSkill(await actorFor(board, opts.as), {
8379
8487
  name,
8380
8488
  reason: opts.reason
@@ -8383,13 +8491,13 @@ skill.command("remove <name>").description("Remove a skill from the society's sk
8383
8491
  });
8384
8492
  var turn = program.command("turn").description("Turn operations");
8385
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) => {
8386
- const board = await open();
8494
+ const board = await open2();
8387
8495
  const actor = await board.actorFor(agentName);
8388
8496
  const digest = await board.readDigest(opts.project === void 0 ? actor : { ...actor, scope: opts.project }, { advance: false, limit: Number(opts.limit) });
8389
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"));
8390
8498
  });
8391
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) => {
8392
- const board = await open();
8500
+ const board = await open2();
8393
8501
  const event = await board.requestWake(board.userActor(), {
8394
8502
  agent: agentName,
8395
8503
  project: opts.project,