@kubitnodes/stellaris 0.3.19 → 0.3.21

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