@kubitnodes/stellaris 0.3.16 → 0.3.18

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 (55) hide show
  1. package/dist/cli/index.js +136 -28
  2. package/dist/server/index.js +108 -8
  3. package/package.json +1 -1
  4. package/web/dist/assets/BackLink-DUOMS3fv.js +1 -0
  5. package/web/dist/assets/{ChannelForms-mdOuU6eZ.js → ChannelForms-DfG4vYvk.js} +1 -1
  6. package/web/dist/assets/ChannelView-DTLhLXy4.js +2 -0
  7. package/web/dist/assets/{CitizenView-B_AL8Fnt.js → CitizenView-q24CExsI.js} +2 -2
  8. package/web/dist/assets/{CitizensView--LjujB8k.js → CitizensView-bKbUaCab.js} +1 -1
  9. package/web/dist/assets/{CliIcon-Cyv7ef-2.js → CliIcon-DTVk0pGU.js} +1 -1
  10. package/web/dist/assets/CronsView-DaG1xhQu.js +1 -0
  11. package/web/dist/assets/{KnowledgeView-CMYgeq6t.js → KnowledgeView-Dt7wE3FT.js} +1 -1
  12. package/web/dist/assets/{Markdown-CI8CWgHq.js → Markdown-DeF9PKNm.js} +4 -4
  13. package/web/dist/assets/{MessageItem-Dtpufq_V.js → MessageItem-DDs7m3-4.js} +1 -1
  14. package/web/dist/assets/MetricsView-Bw7nL6Y9.js +1 -0
  15. package/web/dist/assets/NeedsYouView-DbESNtQT.js +1 -0
  16. package/web/dist/assets/Overview-Dv7SS4yW.js +1 -0
  17. package/web/dist/assets/{Pane-L4GJKBiw.js → Pane-v044FaP5.js} +1 -1
  18. package/web/dist/assets/{Picker-h6akABv6.js → Picker-wWsO86nu.js} +1 -1
  19. package/web/dist/assets/ProjectView--nzlqSDA.js +1 -0
  20. package/web/dist/assets/ProposalView-Cdw_xzG1.js +1 -0
  21. package/web/dist/assets/{ProposalsView-ffQA37Wa.js → ProposalsView-dVA50yG1.js} +1 -1
  22. package/web/dist/assets/{RunnersView-BD0XRmGY.js → RunnersView-C-l5QYIa.js} +1 -1
  23. package/web/dist/assets/SocietyView--mWRXRj_.js +1 -0
  24. package/web/dist/assets/{SubjectThread-DnQJDgcK.js → SubjectThread-DvvsxHN9.js} +1 -1
  25. package/web/dist/assets/TaskFileView-DYuPrCdk.js +4 -0
  26. package/web/dist/assets/{TaskView-CkMhrkxY.js → TaskView-BgJcOmGj.js} +2 -2
  27. package/web/dist/assets/{TasksView-BuAqdPIa.js → TasksView-f8KIsif_.js} +1 -1
  28. package/web/dist/assets/ThreadCard-BRLvjdz3.js +1 -0
  29. package/web/dist/assets/ThreadForms-CXsunS-N.js +1 -0
  30. package/web/dist/assets/{ThreadView-CpdtYhHa.js → ThreadView-DzpaXkik.js} +1 -1
  31. package/web/dist/assets/{citizen-Cyd1haJO.js → citizen-VtLNxjtf.js} +1 -1
  32. package/web/dist/assets/compose-D-SO-HE-.js +1 -0
  33. package/web/dist/assets/{format-qPQgH1PF.js → format-CkySAdH1.js} +1 -1
  34. package/web/dist/assets/{governance-D5tD2NOI.js → governance-1UeTD2K_.js} +1 -1
  35. package/web/dist/assets/index-CwkK8XtJ.js +23 -0
  36. package/web/dist/assets/{index-BoTts74M.css → index-DaES0xVj.css} +1 -1
  37. package/web/dist/assets/{link-DF__7B1f.js → link-BN04dcBX.js} +1 -1
  38. package/web/dist/assets/{tasks-DcovWYAU.js → tasks-CD70eybR.js} +1 -1
  39. package/web/dist/assets/{useMatch-AToHy-wD.js → useMatch-i33QT_GI.js} +1 -1
  40. package/web/dist/assets/{useStickyScroll-BFNyJYps.js → useStickyScroll-WSVpAMrA.js} +1 -1
  41. package/web/dist/index.html +9 -9
  42. package/web/dist/assets/BackLink-B5n6YQVF.js +0 -1
  43. package/web/dist/assets/ChannelView-CeoMScnt.js +0 -2
  44. package/web/dist/assets/CronsView-DGGDT8e-.js +0 -1
  45. package/web/dist/assets/MetricsView-CDtVttH_.js +0 -1
  46. package/web/dist/assets/NeedsYouView-DuVBOLn4.js +0 -1
  47. package/web/dist/assets/Overview-BMatmD0p.js +0 -1
  48. package/web/dist/assets/ProjectView-t3cOK3AT.js +0 -1
  49. package/web/dist/assets/ProposalView-BRr85RPY.js +0 -1
  50. package/web/dist/assets/SocietyView-CsbeXgkb.js +0 -1
  51. package/web/dist/assets/TaskFileView-uJ9xM8Y0.js +0 -4
  52. package/web/dist/assets/ThreadCard-CkqMOMDh.js +0 -1
  53. package/web/dist/assets/ThreadForms-Bciw71n3.js +0 -1
  54. package/web/dist/assets/compose-BCqmtrsZ.js +0 -1
  55. package/web/dist/assets/index-BfZnvrmA.js +0 -23
package/dist/cli/index.js CHANGED
@@ -63,7 +63,8 @@ var VERB_NAMES = [
63
63
  "update_dashboard",
64
64
  "create_cron",
65
65
  "update_cron",
66
- "remove_cron"
66
+ "remove_cron",
67
+ "remove_skill"
67
68
  ];
68
69
  var VerbNameSchema = z2.enum(VERB_NAMES);
69
70
  var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
@@ -123,6 +124,7 @@ var PLANNING_VERBS = [
123
124
  "archive_channel"
124
125
  ];
125
126
  var ARCHIVE_VERBS = ["archive_project"];
127
+ var CURATING_VERBS = ["remove_skill"];
126
128
  var SEED_ROLES = [
127
129
  {
128
130
  name: USER_ROLE,
@@ -132,7 +134,8 @@ var SEED_ROLES = [
132
134
  ...GOVERNANCE_VERBS,
133
135
  ...FRONT_DESK_VERBS,
134
136
  ...PLANNING_VERBS,
135
- ...ARCHIVE_VERBS
137
+ ...ARCHIVE_VERBS,
138
+ ...CURATING_VERBS
136
139
  ],
137
140
  wakeTriggers: [],
138
141
  maxReplicas: 1,
@@ -144,7 +147,7 @@ var SEED_ROLES = [
144
147
  {
145
148
  name: "steward",
146
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.",
147
- verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS],
150
+ verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
148
151
  wakeTriggers: ["ops_event", "heartbeat"],
149
152
  maxReplicas: 1,
150
153
  backlogThreshold: 3,
@@ -697,6 +700,12 @@ var SkillSchema = z4.object({
697
700
  scope: z4.enum(["own", "society"]),
698
701
  path: z4.string().min(1)
699
702
  });
703
+ var RemovedSkillSchema = z4.object({
704
+ name: NameSchema,
705
+ reason: z4.string().min(1),
706
+ removedBy: NameSchema,
707
+ removedAt: IsoDateTimeSchema
708
+ });
700
709
  var HomeConflictSchema = z4.object({
701
710
  /** The copy, relative to the home. */
702
711
  path: z4.string().min(1),
@@ -940,7 +949,8 @@ var VerbInputs = {
940
949
  timezone: TimeZoneSchema.nullable().optional(),
941
950
  at: MomentSchema.optional()
942
951
  }),
943
- remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) })
952
+ remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) }),
953
+ remove_skill: z5.object({ name: NameSchema, reason: z5.string().trim().min(1) })
944
954
  };
945
955
 
946
956
  // packages/shared/dist/events.js
@@ -982,6 +992,7 @@ var BOARD_EVENT_TYPES = [
982
992
  "knowledge.removed",
983
993
  "dashboard.updated",
984
994
  "skill.promoted",
995
+ "skill.removed",
985
996
  "runner.added",
986
997
  "user.signed_in",
987
998
  "user.signed_out",
@@ -1695,7 +1706,7 @@ var RunnerConfigSchema = z11.object({
1695
1706
  });
1696
1707
 
1697
1708
  // packages/shared/dist/version.js
1698
- var STELLARIS_VERSION = "0.3.16";
1709
+ var STELLARIS_VERSION = "0.3.18";
1699
1710
 
1700
1711
  // packages/board-core/dist/errors.js
1701
1712
  var BoardError = class extends Error {
@@ -2717,11 +2728,11 @@ var BoardPaths = class {
2717
2728
  agentSkills(name) {
2718
2729
  return path5.join(this.agent(name), "skills");
2719
2730
  }
2720
- agentSkill(name, skill) {
2721
- return path5.join(this.agentSkills(name), skill);
2731
+ agentSkill(name, skill2) {
2732
+ return path5.join(this.agentSkills(name), skill2);
2722
2733
  }
2723
- agentSkillFile(name, skill) {
2724
- return path5.join(this.agentSkill(name, skill), "SKILL.md");
2734
+ agentSkillFile(name, skill2) {
2735
+ return path5.join(this.agentSkill(name, skill2), "SKILL.md");
2725
2736
  }
2726
2737
  agentProjects(name) {
2727
2738
  return path5.join(this.agent(name), "projects");
@@ -2755,6 +2766,10 @@ var BoardPaths = class {
2755
2766
  removedKnowledge(scope) {
2756
2767
  return path5.join(this.state(), "removed-knowledge", scope);
2757
2768
  }
2769
+ /** Skills removed from the society, set aside outside the projection, one directory per removal. */
2770
+ removedSkills() {
2771
+ return path5.join(this.state(), "removed-skills");
2772
+ }
2758
2773
  };
2759
2774
 
2760
2775
  // packages/board-core/dist/tokens.js
@@ -2874,6 +2889,12 @@ var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
2874
2889
  var CHANNEL_CONVERSATIONS = "channel-conversations";
2875
2890
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
2876
2891
  var CRON_VERBS = "cron-verbs-granted";
2892
+ var KNOWLEDGE_NOTICES = "knowledge-notices";
2893
+ var KnowledgeEventSchema = z13.object({
2894
+ topic: NameSchema,
2895
+ project: NameSchema.nullable(),
2896
+ replaced: z13.boolean().optional()
2897
+ });
2877
2898
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
2878
2899
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
2879
2900
  var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
@@ -3077,7 +3098,8 @@ var Board = class _Board {
3077
3098
  KNOWLEDGE_REMOVAL,
3078
3099
  CHANNEL_CONVERSATIONS,
3079
3100
  DASHBOARD_UPDATES,
3080
- CRON_VERBS
3101
+ CRON_VERBS,
3102
+ KNOWLEDGE_NOTICES
3081
3103
  ].filter((name) => !applied.includes(name));
3082
3104
  if (pending.length === 0) {
3083
3105
  return;
@@ -3119,8 +3141,49 @@ var Board = class _Board {
3119
3141
  if (pending.includes(CRON_VERBS)) {
3120
3142
  await this.grantToEveryRole(CRON_VERB_NAMES);
3121
3143
  }
3144
+ if (pending.includes(KNOWLEDGE_NOTICES)) {
3145
+ await this.noticeKnowledgeNotes();
3146
+ }
3122
3147
  await writeJson(file, { applied: [...applied, ...pending] });
3123
3148
  }
3149
+ /**
3150
+ * Gives each knowledge note from before notes carried their notice the notice it would carry
3151
+ * now. The event log, not the note's words, says which post is one: a knowledge verb appends
3152
+ * its event and then its note under the mutex, so a note is the post right after such an event,
3153
+ * by the same actor, at the top of the general channel of the topic's place. A post written to
3154
+ * look like a note follows no such event and is left as it is.
3155
+ */
3156
+ async noticeKnowledgeNotes() {
3157
+ let before;
3158
+ for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
3159
+ const verb = before;
3160
+ before = event;
3161
+ if (event.type !== "message.posted" || verb === void 0 || verb.actor !== event.actor || verb.type !== "knowledge.written" && verb.type !== "knowledge.removed") {
3162
+ continue;
3163
+ }
3164
+ const knowledge2 = KnowledgeEventSchema.safeParse(verb.payload);
3165
+ const id = UlidSchema.safeParse(event.payload["id"]);
3166
+ if (!knowledge2.success || !id.success || (event.payload["thread"] ?? null) !== null || event.payload["channel"] !== channelRef(knowledge2.data.project, "general")) {
3167
+ continue;
3168
+ }
3169
+ const { topic, project: project2, replaced } = knowledge2.data;
3170
+ const file = this.paths.messageFile(this.paths.channelDir(channelRef(project2, "general")), id.data, event.actor);
3171
+ if (!await exists(file)) {
3172
+ continue;
3173
+ }
3174
+ const { data, body, error } = await readLooseMarkdown(file);
3175
+ if (error !== null || data["notice"] !== void 0) {
3176
+ continue;
3177
+ }
3178
+ const notice = {
3179
+ kind: "knowledge",
3180
+ action: verb.type === "knowledge.removed" ? "removed" : replaced === true ? "updated" : "written",
3181
+ topic,
3182
+ project: project2
3183
+ };
3184
+ await writeMarkdown(file, { ...data, notice }, body);
3185
+ }
3186
+ }
3124
3187
  /**
3125
3188
  * Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
3126
3189
  * practice; seed roles have it from their seed.
@@ -4428,11 +4491,11 @@ ${task2.body}`;
4428
4491
  file: path6.join(this.paths.societyKnowledge(), file)
4429
4492
  });
4430
4493
  }
4431
- for (const skill of await this.listSocietySkills()) {
4432
- shared.push({ kind: "skill", ref: `society/skills/${skill.name}`, file: skill.path });
4494
+ for (const skill2 of await this.listSocietySkills()) {
4495
+ shared.push({ kind: "skill", ref: `society/skills/${skill2.name}`, file: skill2.path });
4433
4496
  }
4434
- for (const skill of await this.listAgentSkills(actor.name)) {
4435
- shared.push({ kind: "skill", ref: `skills/${skill.name}`, file: skill.path });
4497
+ for (const skill2 of await this.listAgentSkills(actor.name)) {
4498
+ shared.push({ kind: "skill", ref: `skills/${skill2.name}`, file: skill2.path });
4436
4499
  }
4437
4500
  for (const file of await listFiles(this.paths.agentMemory(actor.name))) {
4438
4501
  shared.push({
@@ -4535,6 +4598,40 @@ ${task2.body}`;
4535
4598
  throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${project2}`);
4536
4599
  }
4537
4600
  }
4601
+ /**
4602
+ * Takes a skill out of the society's skills, by the steward or the user. Its directory leaves
4603
+ * the projection, so no skills index or search lists it from the next turn, and is set aside
4604
+ * under `state/removed-skills` for the user to restore by hand. The notice is the board's, as a
4605
+ * promotion's is, so a removal by the user wakes no front desk, and quotes the reason without
4606
+ * its mentions. The proposer's own copy in its home is not touched.
4607
+ */
4608
+ async removeSkill(actor, input) {
4609
+ const args = VerbInputs.remove_skill.parse(input);
4610
+ await this.authorize(actor, "remove_skill");
4611
+ if (!CURATING_ROLES.includes(actor.role)) {
4612
+ throw new BoardError("FORBIDDEN", "the society's skills are curated by the steward and the user");
4613
+ }
4614
+ return this.mutex.run(async () => {
4615
+ if (!await exists(this.paths.societySkillFile(args.name))) {
4616
+ throw new BoardError("NOT_FOUND", `the society has no skill ${args.name}`);
4617
+ }
4618
+ const removed = RemovedSkillSchema.parse({
4619
+ name: args.name,
4620
+ reason: args.reason,
4621
+ removedBy: actor.name,
4622
+ removedAt: this.now().toISOString()
4623
+ });
4624
+ const aside = this.paths.removedSkills();
4625
+ await mkdir4(aside, { recursive: true });
4626
+ await rename3(this.paths.societySkill(args.name), path6.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
4627
+ await this.events.append("skill.removed", actor.name, {
4628
+ name: args.name,
4629
+ reason: args.reason
4630
+ });
4631
+ await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${args.name} removed from the society by ${actor.name}: ${sentence(withoutMentions(args.reason))}. Every citizen's skills index stops listing it from its next turn.`);
4632
+ return removed;
4633
+ });
4634
+ }
4538
4635
  /**
4539
4636
  * Replaces a project's dashboard, by a member or the user. A turn reads the dashboard from the
4540
4637
  * copy of the projection pulled when it started, so a revision other than the current one means
@@ -5757,6 +5854,8 @@ ${rationale}`}`);
5757
5854
  return this.writeKnowledge(actor, VerbInputs.write_knowledge.parse(input));
5758
5855
  case "remove_knowledge":
5759
5856
  return this.removeKnowledge(actor, VerbInputs.remove_knowledge.parse(input));
5857
+ case "remove_skill":
5858
+ return this.removeSkill(actor, VerbInputs.remove_skill.parse(input));
5760
5859
  case "plan_task":
5761
5860
  return this.planTask(actor, VerbInputs.plan_task.parse(input));
5762
5861
  case "advance_task":
@@ -6600,7 +6699,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
6600
6699
  ...agent2.model === void 0 ? {} : { model: agent2.model },
6601
6700
  ...lastModel === void 0 ? {} : { lastModel },
6602
6701
  ...agent2.effort === void 0 ? {} : { effort: agent2.effort },
6603
- skills: (await this.listAgentSkills(name)).map((skill) => skill.name),
6702
+ skills: (await this.listAgentSkills(name)).map((skill2) => skill2.name),
6604
6703
  memberships: agent2.memberships,
6605
6704
  subscriptions: agent2.subscriptions,
6606
6705
  claimsHeld,
@@ -6749,9 +6848,9 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
6749
6848
  return { agent: agent2.name, releasedTasks };
6750
6849
  }
6751
6850
  case "skill": {
6752
- const skill = SkillProposalSchema.parse(proposal2.charter);
6753
- const replaced = await this.promoteSkillUnlocked(by, proposal2.proposedBy, skill, meta);
6754
- return { skill: skill.name, replaced };
6851
+ const skill2 = SkillProposalSchema.parse(proposal2.charter);
6852
+ const replaced = await this.promoteSkillUnlocked(by, proposal2.proposedBy, skill2, meta);
6853
+ return { skill: skill2.name, replaced };
6755
6854
  }
6756
6855
  case "archive": {
6757
6856
  const input = ArchiveProposalSchema.parse(proposal2.charter);
@@ -6765,23 +6864,23 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
6765
6864
  }
6766
6865
  }
6767
6866
  /** Writes a promoted skill under the society's skills, where every citizen's skills index lists it. */
6768
- async promoteSkillUnlocked(by, proposedBy, skill, meta) {
6769
- const file = this.paths.societySkillFile(skill.name);
6867
+ async promoteSkillUnlocked(by, proposedBy, skill2, meta) {
6868
+ const file = this.paths.societySkillFile(skill2.name);
6770
6869
  const replaced = await exists(file);
6771
- await ensureDir(this.paths.societySkill(skill.name));
6870
+ await ensureDir(this.paths.societySkill(skill2.name));
6772
6871
  await writeMarkdown(file, {
6773
- name: skill.name,
6774
- description: skill.summary,
6872
+ name: skill2.name,
6873
+ description: skill2.summary,
6775
6874
  metadata: { proposedBy, promotedBy: by, promotedAt: this.now().toISOString() }
6776
- }, skill.body);
6875
+ }, skill2.body);
6777
6876
  await this.events.append("skill.promoted", by, {
6778
- name: skill.name,
6779
- summary: skill.summary,
6877
+ name: skill2.name,
6878
+ summary: skill2.summary,
6780
6879
  proposedBy,
6781
6880
  replaced,
6782
6881
  ...meta
6783
6882
  });
6784
- await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill.name} ${replaced ? "updated in" : "promoted to"} the society: ${sentence(skill.summary)}. Every citizen's skills index now lists it.`);
6883
+ await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${skill2.name} ${replaced ? "updated in" : "promoted to"} the society: ${sentence(skill2.summary)}. Every citizen's skills index now lists it.`);
6785
6884
  return replaced;
6786
6885
  }
6787
6886
  async validateAddAgent(input) {
@@ -7960,7 +8059,8 @@ knowledge.command("write <topic>").description("Write a knowledge topic as the u
7960
8059
  });
7961
8060
  print(written, () => `Wrote ${written.topic} for ${written.project ?? "the society"}`);
7962
8061
  });
7963
- program.command("skill").description("Skills promoted to the society").command("list").description("List the society's skills, and each member's own").action(async () => {
8062
+ var skill = program.command("skill").description("Skills promoted to the society");
8063
+ skill.command("list").description("List the society's skills, and each member's own").action(async () => {
7964
8064
  const board = await open();
7965
8065
  const society = await board.listSocietySkills();
7966
8066
  const own = [];
@@ -7975,6 +8075,14 @@ program.command("skill").description("Skills promoted to the society").command("
7975
8075
  ...own.flatMap(({ agent: name, skills }) => skills.map((s) => `${name} ${s.name} ${s.summary}`))
7976
8076
  ].join("\n") || "No skills yet.");
7977
8077
  });
8078
+ skill.command("remove <name>").description("Remove a skill from the society's skills; its text is set aside, not destroyed").requiredOption("--reason <text>", "why it is wrong, superseded, or no longer used").option("--as <agent>", "act as this agent instead of the user").action(async (name, opts) => {
8079
+ const board = await open();
8080
+ const removed = await board.removeSkill(await actorFor(board, opts.as), {
8081
+ name,
8082
+ reason: opts.reason
8083
+ });
8084
+ print(removed, () => `Removed ${removed.name} from the society's skills: ${removed.reason}`);
8085
+ });
7978
8086
  var turn = program.command("turn").description("Turn operations");
7979
8087
  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) => {
7980
8088
  const board = await open();
@@ -63,7 +63,8 @@ var VERB_NAMES = [
63
63
  "update_dashboard",
64
64
  "create_cron",
65
65
  "update_cron",
66
- "remove_cron"
66
+ "remove_cron",
67
+ "remove_skill"
67
68
  ];
68
69
  var VerbNameSchema = z2.enum(VERB_NAMES);
69
70
  var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
@@ -123,6 +124,7 @@ var PLANNING_VERBS = [
123
124
  "archive_channel"
124
125
  ];
125
126
  var ARCHIVE_VERBS = ["archive_project"];
127
+ var CURATING_VERBS = ["remove_skill"];
126
128
  var SEED_ROLES = [
127
129
  {
128
130
  name: USER_ROLE,
@@ -132,7 +134,8 @@ var SEED_ROLES = [
132
134
  ...GOVERNANCE_VERBS,
133
135
  ...FRONT_DESK_VERBS,
134
136
  ...PLANNING_VERBS,
135
- ...ARCHIVE_VERBS
137
+ ...ARCHIVE_VERBS,
138
+ ...CURATING_VERBS
136
139
  ],
137
140
  wakeTriggers: [],
138
141
  maxReplicas: 1,
@@ -144,7 +147,7 @@ var SEED_ROLES = [
144
147
  {
145
148
  name: "steward",
146
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.",
147
- verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS],
150
+ verbs: [...MEMBER_VERBS, ...GOVERNANCE_VERBS, ...PLANNING_VERBS, ...CURATING_VERBS],
148
151
  wakeTriggers: ["ops_event", "heartbeat"],
149
152
  maxReplicas: 1,
150
153
  backlogThreshold: 3,
@@ -712,6 +715,12 @@ var SkillSchema = z4.object({
712
715
  scope: z4.enum(["own", "society"]),
713
716
  path: z4.string().min(1)
714
717
  });
718
+ var RemovedSkillSchema = z4.object({
719
+ name: NameSchema,
720
+ reason: z4.string().min(1),
721
+ removedBy: NameSchema,
722
+ removedAt: IsoDateTimeSchema
723
+ });
715
724
  var HomeConflictSchema = z4.object({
716
725
  /** The copy, relative to the home. */
717
726
  path: z4.string().min(1),
@@ -955,7 +964,8 @@ var VerbInputs = {
955
964
  timezone: TimeZoneSchema.nullable().optional(),
956
965
  at: MomentSchema.optional()
957
966
  }),
958
- remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) })
967
+ remove_cron: z5.object({ cron_id: UlidSchema, reason: z5.string().trim().min(1) }),
968
+ remove_skill: z5.object({ name: NameSchema, reason: z5.string().trim().min(1) })
959
969
  };
960
970
  var VERB_DESCRIPTIONS = {
961
971
  post_message: "Post a markdown message to a channel, or into a thread when thread_id is given; a thread's message goes to the thread's channel.",
@@ -987,7 +997,8 @@ var VERB_DESCRIPTIONS = {
987
997
  update_dashboard: "Replace the dashboard of a project you are a member of, the status page the user reads on its view, with a whole markdown body. Pass the revision from the frontmatter of the dashboard you read; if it was updated since, the call is refused with the current text to merge yours into. dashboard.md in the board projection is a copy, so editing it changes nothing.",
988
998
  create_cron: "Set a cron that wakes you later: a title, a note on what to do then, and either cron, a five-field expression (minute hour day-of-month month day-of-week) read in timezone or the society's, or at, one time. It fires in this turn's conversation unless you name a project, thread_id, or channel, at most every 15 minutes, and every fire is a turn. Only the user, the steward, and the concierge set one that wakes another citizen (agent).",
989
999
  update_cron: "Pause or resume a cron, or change its title, note, or schedule; resuming or rescheduling starts it afresh from now. Yours, or anyone's for the user, the steward, and the concierge.",
990
- remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires."
1000
+ remove_cron: "End a cron for good, with a reason. Yours, or anyone's for the user, the steward, and the concierge. A cron also ends with its conversation, and a one-time cron once it fires.",
1001
+ remove_skill: "Remove a skill from the society's skills, with a reason, when it is wrong, superseded, or no longer used: no skills index lists it from the next turn, and a notice in general says so with the reason. Its proposer's own copy stays in its home. Steward and user only; the board keeps its text aside for the user to restore."
991
1002
  };
992
1003
 
993
1004
  // packages/shared/dist/events.js
@@ -1029,6 +1040,7 @@ var BOARD_EVENT_TYPES = [
1029
1040
  "knowledge.removed",
1030
1041
  "dashboard.updated",
1031
1042
  "skill.promoted",
1043
+ "skill.removed",
1032
1044
  "runner.added",
1033
1045
  "user.signed_in",
1034
1046
  "user.signed_out",
@@ -1822,7 +1834,7 @@ var RunnerConfigSchema = z11.object({
1822
1834
  });
1823
1835
 
1824
1836
  // packages/shared/dist/version.js
1825
- var STELLARIS_VERSION = "0.3.16";
1837
+ var STELLARIS_VERSION = "0.3.18";
1826
1838
 
1827
1839
  // packages/board-core/dist/errors.js
1828
1840
  var BoardError = class extends Error {
@@ -2885,6 +2897,10 @@ var BoardPaths = class {
2885
2897
  removedKnowledge(scope) {
2886
2898
  return path5.join(this.state(), "removed-knowledge", scope);
2887
2899
  }
2900
+ /** Skills removed from the society, set aside outside the projection, one directory per removal. */
2901
+ removedSkills() {
2902
+ return path5.join(this.state(), "removed-skills");
2903
+ }
2888
2904
  };
2889
2905
 
2890
2906
  // packages/board-core/dist/tokens.js
@@ -3004,6 +3020,12 @@ var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
3004
3020
  var CHANNEL_CONVERSATIONS = "channel-conversations";
3005
3021
  var DASHBOARD_UPDATES = "dashboard-updates-granted";
3006
3022
  var CRON_VERBS = "cron-verbs-granted";
3023
+ var KNOWLEDGE_NOTICES = "knowledge-notices";
3024
+ var KnowledgeEventSchema = z13.object({
3025
+ topic: NameSchema,
3026
+ project: NameSchema.nullable(),
3027
+ replaced: z13.boolean().optional()
3028
+ });
3007
3029
  var CRON_VERB_NAMES = ["create_cron", "update_cron", "remove_cron"];
3008
3030
  var OLD_DASHBOARD_LINE = "Agents may edit this file. It is rendered by the board UI.";
3009
3031
  var DASHBOARD_LINE = "The project's members keep this page current with update_dashboard.";
@@ -3207,7 +3229,8 @@ var Board = class _Board {
3207
3229
  KNOWLEDGE_REMOVAL,
3208
3230
  CHANNEL_CONVERSATIONS,
3209
3231
  DASHBOARD_UPDATES,
3210
- CRON_VERBS
3232
+ CRON_VERBS,
3233
+ KNOWLEDGE_NOTICES
3211
3234
  ].filter((name) => !applied.includes(name));
3212
3235
  if (pending.length === 0) {
3213
3236
  return;
@@ -3249,8 +3272,49 @@ var Board = class _Board {
3249
3272
  if (pending.includes(CRON_VERBS)) {
3250
3273
  await this.grantToEveryRole(CRON_VERB_NAMES);
3251
3274
  }
3275
+ if (pending.includes(KNOWLEDGE_NOTICES)) {
3276
+ await this.noticeKnowledgeNotes();
3277
+ }
3252
3278
  await writeJson(file, { applied: [...applied, ...pending] });
3253
3279
  }
3280
+ /**
3281
+ * Gives each knowledge note from before notes carried their notice the notice it would carry
3282
+ * now. The event log, not the note's words, says which post is one: a knowledge verb appends
3283
+ * its event and then its note under the mutex, so a note is the post right after such an event,
3284
+ * by the same actor, at the top of the general channel of the topic's place. A post written to
3285
+ * look like a note follows no such event and is left as it is.
3286
+ */
3287
+ async noticeKnowledgeNotes() {
3288
+ let before;
3289
+ for (const event of await this.events.readSince(null, Number.POSITIVE_INFINITY)) {
3290
+ const verb = before;
3291
+ before = event;
3292
+ if (event.type !== "message.posted" || verb === void 0 || verb.actor !== event.actor || verb.type !== "knowledge.written" && verb.type !== "knowledge.removed") {
3293
+ continue;
3294
+ }
3295
+ const knowledge = KnowledgeEventSchema.safeParse(verb.payload);
3296
+ const id = UlidSchema.safeParse(event.payload["id"]);
3297
+ if (!knowledge.success || !id.success || (event.payload["thread"] ?? null) !== null || event.payload["channel"] !== channelRef(knowledge.data.project, "general")) {
3298
+ continue;
3299
+ }
3300
+ const { topic, project, replaced } = knowledge.data;
3301
+ const file = this.paths.messageFile(this.paths.channelDir(channelRef(project, "general")), id.data, event.actor);
3302
+ if (!await exists(file)) {
3303
+ continue;
3304
+ }
3305
+ const { data, body, error } = await readLooseMarkdown(file);
3306
+ if (error !== null || data["notice"] !== void 0) {
3307
+ continue;
3308
+ }
3309
+ const notice = {
3310
+ kind: "knowledge",
3311
+ action: verb.type === "knowledge.removed" ? "removed" : replaced === true ? "updated" : "written",
3312
+ topic,
3313
+ project
3314
+ };
3315
+ await writeMarkdown(file, { ...data, notice }, body);
3316
+ }
3317
+ }
3254
3318
  /**
3255
3319
  * Grants `verb` once to every charter granting `write_knowledge`, the work roles' charters in
3256
3320
  * practice; seed roles have it from their seed.
@@ -4665,6 +4729,40 @@ ${task.body}`;
4665
4729
  throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${project}`);
4666
4730
  }
4667
4731
  }
4732
+ /**
4733
+ * Takes a skill out of the society's skills, by the steward or the user. Its directory leaves
4734
+ * the projection, so no skills index or search lists it from the next turn, and is set aside
4735
+ * under `state/removed-skills` for the user to restore by hand. The notice is the board's, as a
4736
+ * promotion's is, so a removal by the user wakes no front desk, and quotes the reason without
4737
+ * its mentions. The proposer's own copy in its home is not touched.
4738
+ */
4739
+ async removeSkill(actor, input) {
4740
+ const args = VerbInputs.remove_skill.parse(input);
4741
+ await this.authorize(actor, "remove_skill");
4742
+ if (!CURATING_ROLES.includes(actor.role)) {
4743
+ throw new BoardError("FORBIDDEN", "the society's skills are curated by the steward and the user");
4744
+ }
4745
+ return this.mutex.run(async () => {
4746
+ if (!await exists(this.paths.societySkillFile(args.name))) {
4747
+ throw new BoardError("NOT_FOUND", `the society has no skill ${args.name}`);
4748
+ }
4749
+ const removed = RemovedSkillSchema.parse({
4750
+ name: args.name,
4751
+ reason: args.reason,
4752
+ removedBy: actor.name,
4753
+ removedAt: this.now().toISOString()
4754
+ });
4755
+ const aside = this.paths.removedSkills();
4756
+ await mkdir4(aside, { recursive: true });
4757
+ await rename3(this.paths.societySkill(args.name), path6.join(aside, `${args.name}.${removed.removedAt.replaceAll(":", "-")}`));
4758
+ await this.events.append("skill.removed", actor.name, {
4759
+ name: args.name,
4760
+ reason: args.reason
4761
+ });
4762
+ await this.appendMessage(SYSTEM_ACTOR.name, "general", `Skill ${args.name} removed from the society by ${actor.name}: ${sentence(withoutMentions(args.reason))}. Every citizen's skills index stops listing it from its next turn.`);
4763
+ return removed;
4764
+ });
4765
+ }
4668
4766
  /**
4669
4767
  * Replaces a project's dashboard, by a member or the user. A turn reads the dashboard from the
4670
4768
  * copy of the projection pulled when it started, so a revision other than the current one means
@@ -5887,6 +5985,8 @@ ${rationale}`}`);
5887
5985
  return this.writeKnowledge(actor, VerbInputs.write_knowledge.parse(input));
5888
5986
  case "remove_knowledge":
5889
5987
  return this.removeKnowledge(actor, VerbInputs.remove_knowledge.parse(input));
5988
+ case "remove_skill":
5989
+ return this.removeSkill(actor, VerbInputs.remove_skill.parse(input));
5890
5990
  case "plan_task":
5891
5991
  return this.planTask(actor, VerbInputs.plan_task.parse(input));
5892
5992
  case "advance_task":
@@ -9259,7 +9359,7 @@ var GOVERNANCE = [
9259
9359
  ' - retirement: {"agent", "reason"}',
9260
9360
  ' - archive: {"project", "reason"}: approval archives the project: its members leave, its open threads close, and nothing more is posted, filed, or joined there, while its files and history stay; refused while a task there is in play',
9261
9361
  ' - reallocation: {"description"}',
9262
- ` - skill: {"name", "summary", "body"}: the SKILL.md text; approval publishes it under society/skills, where every citizen's skills index lists it`,
9362
+ ' - skill: {"name", "summary", "body"}: the SKILL.md text; approval publishes it under society/skills, where every citizen\'s skills index lists it until the steward or the user removes it with `remove_skill`; ask the steward to remove one that is wrong or superseded',
9263
9363
  "- Projects and membership: a project is a lasting area of work, not one request; `create_project` opens one with its general channel (front desk and user), and `configure_project` sets its completion effect, its display name, or its default branch, never its slug (user, steward, concierge). A workstream within a project or the society, such as a release, gets a channel of its own: `create_channel` opens it, which its opener follows, and announces it in the place's general, where members subscribe when it concerns them, `create_task` with `channel` files the workstream's tasks there, and `archive_channel` ends it once none of its tasks is in play, leaving its history readable (user, steward, concierge; others propose a channel). A project whose work is finished or has moved to another project is archived by proposal, once no task there is in play. `join_project` and `leave_project` move yourself, or another citizen when you are the concierge, the steward, or the user; joining gives the pair a worktree and an onboarding turn. A society role, one that keeps watch over the whole society as the concierge and the steward do, is never in a project: it moves others, never itself.",
9264
9364
  "- Prefer scaling an existing role over inventing one; a new role is justified by work a project needs that no existing role covers. A role that needs a tool the board lacks is an engineering task, not a hiring request.",
9265
9365
  "- Operations signals are counters and timers the board logs, listed in the prompt of a role that reads them: stages waiting for a holder, backlog per role, stages waiting on a role nobody fills, churn, stale threads, idle members, missing capabilities, replicas added, spend. They are not posts in any channel; interpreting them is your judgment."
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "codex",
9
9
  "multi-agent"
10
10
  ],
11
- "version": "0.3.16",
11
+ "version": "0.3.18",
12
12
  "license": "MIT",
13
13
  "homepage": "https://github.com/kr4t0n/stellaris",
14
14
  "repository": {
@@ -0,0 +1 @@
1
+ import{Pt as e,x as t,yn as n}from"./format-CkySAdH1.js";import{D as r,n as i,r as a,t as o,v as s}from"./index-CwkK8XtJ.js";var c=n(),l=`grid size-7 shrink-0 place-items-center rounded-lg text-fg-tertiary hover:bg-surface-2/70 hover:text-fg-primary`;function u({fallback:n}){let u=e(),d=r({select:e=>e.location.state.__TSR_index}),f=a(),p=s(),m=t(),h=i(f,d);if(h===null)return n;let g=o(h,{entity:e=>p.get(e)?.title,project:e=>m.data?.find(t=>t.slug===e)?.name});return(0,c.jsx)(`button`,{type:`button`,onClick:()=>u.history.back(),"aria-label":`Back to ${g}`,title:`Back to ${g}`,className:l,children:`←`})}export{u as t};
@@ -1 +1 @@
1
- import{Cn as e,Dn as t,Sn as n,k as r,t as i,wn as a}from"./format-qPQgH1PF.js";import{c as o,l as s,s as c}from"./compose-BCqmtrsZ.js";import{t as l}from"./link-DF__7B1f.js";import{n as u,r as d}from"./ThreadForms-Bciw71n3.js";var f=t(a(),1),p=e();function m({project:e,onDone:t}){let{api:i}=r(),a=n(),l=o(),[m,h]=(0,f.useState)(``),[g,_]=(0,f.useState)(``),v=(0,f.useRef)(null);(0,f.useEffect)(()=>v.current?.focus(),[]);let y=s({mutationFn:()=>i.createChannel({project:e,name:m.trim(),purpose:g.trim()}),onSuccess:async e=>{await a.invalidateQueries({queryKey:[`channels`]}),t(),await l({to:`/c/$`,params:{_splat:e}})}});return(0,p.jsxs)(`form`,{"aria-label":`New channel`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),y.mutate()},children:[(0,p.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,p.jsx)(`div`,{className:`w-40 shrink-0`,children:(0,p.jsx)(`input`,{ref:v,value:m,maxLength:64,spellCheck:!1,"aria-label":`Channel name`,placeholder:`release-0-4`,onChange:e=>h(e.target.value),className:`${u} font-mono`})}),(0,p.jsx)(`input`,{value:g,"aria-label":`Purpose`,placeholder:`What the channel is for`,onChange:e=>_(e.target.value),className:`${u} min-w-0 flex-1`}),(0,p.jsx)(c,{onClick:t,children:`Cancel`}),(0,p.jsx)(c,{variant:`primary`,type:`submit`,disabled:y.isPending||m.trim()===``||g.trim()===``,children:y.isPending?`Opening…`:`Open`})]}),(0,p.jsx)(`p`,{className:`text-meta`,children:e===null?`Announced in the society's general, and followed by whoever subscribes to it; you read every channel without following one.`:`Announced in ${e}'s general, where its members choose to follow it; you read every channel without following one. Tasks filed in it hang their threads here, and it is archived once its tasks are done.`}),(0,p.jsx)(d,{error:y.error})]})}function h({channel:e,onDone:t}){let{api:i}=r(),a=n(),[o,l]=(0,f.useState)(``),m=s({mutationFn:()=>i.archiveChannel({channel:e,reason:o.trim()}),onSuccess:async()=>{await a.invalidateQueries({queryKey:[`channels`]}),t()}});return(0,p.jsxs)(`form`,{"aria-label":`Archive the channel`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),m.mutate()},children:[(0,p.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,p.jsx)(`input`,{value:o,"aria-label":`Reason`,placeholder:`Why its work is done`,onChange:e=>l(e.target.value),className:`${u} min-w-0 flex-1`}),(0,p.jsx)(c,{onClick:t,children:`Cancel`}),(0,p.jsx)(c,{variant:`primary`,type:`submit`,disabled:m.isPending||o.trim()===``,children:m.isPending?`Archiving…`:`Archive channel`})]}),(0,p.jsx)(`p`,{className:`text-meta`,children:`Its open threads close, nobody follows it, and nothing more is posted or filed here; its posts, threads, and tasks stay readable. The board refuses while a task filed here is in play.`}),(0,p.jsx)(d,{error:m.error})]})}function g({channels:e,now:t}){return e.length===0?(0,p.jsx)(`p`,{className:`text-meta`,children:`No channels.`}):(0,p.jsx)(`ul`,{className:`space-y-1`,children:e.map(e=>(0,p.jsxs)(`li`,{children:[(0,p.jsxs)(l,{to:`/c/$`,params:{_splat:e.ref},className:`text-sm hover:text-fg-primary ${e.archived===void 0?`text-fg-secondary`:`text-fg-tertiary`}`,children:[`# `,e.name]}),(0,p.jsxs)(`span`,{className:`text-meta`,children:[` `,`· `,e.messages,` messages`,e.archived===void 0?``:` · archived ${i(e.archived.at,t)}`]})]},e.ref))})}export{g as n,m as r,h as t};
1
+ import{Cn as e,bn as t,k as n,t as r,vn as i,yn as a}from"./format-CkySAdH1.js";import{a as o,o as s,s as c}from"./compose-D-SO-HE-.js";import{t as l}from"./link-BN04dcBX.js";import{n as u,r as d}from"./ThreadForms-CXsunS-N.js";var f=e(t(),1),p=a();function m({project:e,onDone:t}){let{api:r}=n(),a=i(),l=s(),[m,h]=(0,f.useState)(``),[g,_]=(0,f.useState)(``),v=(0,f.useRef)(null);(0,f.useEffect)(()=>v.current?.focus(),[]);let y=c({mutationFn:()=>r.createChannel({project:e,name:m.trim(),purpose:g.trim()}),onSuccess:async e=>{await a.invalidateQueries({queryKey:[`channels`]}),t(),await l({to:`/c/$`,params:{_splat:e}})}});return(0,p.jsxs)(`form`,{"aria-label":`New channel`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),y.mutate()},children:[(0,p.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,p.jsx)(`div`,{className:`w-40 shrink-0`,children:(0,p.jsx)(`input`,{ref:v,value:m,maxLength:64,spellCheck:!1,"aria-label":`Channel name`,placeholder:`release-0-4`,onChange:e=>h(e.target.value),className:`${u} font-mono`})}),(0,p.jsx)(`input`,{value:g,"aria-label":`Purpose`,placeholder:`What the channel is for`,onChange:e=>_(e.target.value),className:`${u} min-w-0 flex-1`}),(0,p.jsx)(o,{onClick:t,children:`Cancel`}),(0,p.jsx)(o,{variant:`primary`,type:`submit`,disabled:y.isPending||m.trim()===``||g.trim()===``,children:y.isPending?`Opening…`:`Open`})]}),(0,p.jsx)(`p`,{className:`text-meta`,children:e===null?`Announced in the society's general, and followed by whoever subscribes to it; you read every channel without following one.`:`Announced in ${e}'s general, where its members choose to follow it; you read every channel without following one. Tasks filed in it hang their threads here, and it is archived once its tasks are done.`}),(0,p.jsx)(d,{error:y.error})]})}function h({channel:e,onDone:t}){let{api:r}=n(),a=i(),[s,l]=(0,f.useState)(``),m=c({mutationFn:()=>r.archiveChannel({channel:e,reason:s.trim()}),onSuccess:async()=>{await a.invalidateQueries({queryKey:[`channels`]}),t()}});return(0,p.jsxs)(`form`,{"aria-label":`Archive the channel`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),m.mutate()},children:[(0,p.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,p.jsx)(`input`,{value:s,"aria-label":`Reason`,placeholder:`Why its work is done`,onChange:e=>l(e.target.value),className:`${u} min-w-0 flex-1`}),(0,p.jsx)(o,{onClick:t,children:`Cancel`}),(0,p.jsx)(o,{variant:`primary`,type:`submit`,disabled:m.isPending||s.trim()===``,children:m.isPending?`Archiving…`:`Archive channel`})]}),(0,p.jsx)(`p`,{className:`text-meta`,children:`Its open threads close, nobody follows it, and nothing more is posted or filed here; its posts, threads, and tasks stay readable. The board refuses while a task filed here is in play.`}),(0,p.jsx)(d,{error:m.error})]})}function g({channels:e,now:t}){return e.length===0?(0,p.jsx)(`p`,{className:`text-meta`,children:`No channels.`}):(0,p.jsx)(`ul`,{className:`space-y-1`,children:e.map(e=>(0,p.jsxs)(`li`,{children:[(0,p.jsxs)(l,{to:`/c/$`,params:{_splat:e.ref},className:`text-sm hover:text-fg-primary ${e.archived===void 0?`text-fg-secondary`:`text-fg-tertiary`}`,children:[`# `,e.name]}),(0,p.jsxs)(`span`,{className:`text-meta`,children:[` `,`· `,e.messages,` messages`,e.archived===void 0?``:` · archived ${r(e.archived.at,t)}`]})]},e.ref))})}export{g as n,m as r,h as t};
@@ -0,0 +1,2 @@
1
+ import{Cn as e,Qt as t,R as n,V as r,_ as i,b as a,bn as o,c as s,k as c,mt as l,t as u,x as d,yn as f}from"./format-CkySAdH1.js";import{a as p}from"./compose-D-SO-HE-.js";import{t as m}from"./link-BN04dcBX.js";import{S as h,i as g,k as _,s as v}from"./index-CwkK8XtJ.js";import{i as y}from"./ThreadForms-CXsunS-N.js";import{t as b}from"./ChannelForms-DfG4vYvk.js";import{t as x}from"./MessageItem-DDs7m3-4.js";import{n as S,t as C}from"./Pane-v044FaP5.js";import{n as w}from"./ThreadCard-BRLvjdz3.js";import{t as T}from"./useStickyScroll-WSVpAMrA.js";var E=e(o(),1);function D(e,t){let n=e.notices.at(-1);return n!==void 0&&n.kind===`knowledge`&&t.kind===`knowledge`&&n.action!==`removed`&&t.action===`updated`&&n.topic===t.topic&&n.project===t.project}function O(e,t){let n=[],r=null;for(let i of e){let e=t(i),a=e?.notice;e===void 0||a===void 0?(n.push({kind:`item`,item:i}),r=null):r!==null&&D(r,a)?(r.messages.push(e),r.notices.push(a)):(r={messages:[e],notices:[a]},n.push({kind:`notices`,run:r}))}return n}function k(e){let[t]=e,n=e.length-1;return t===void 0||t.action===`removed`?{did:`removed`,times:null}:t.action===`written`?{did:`wrote`,times:n===0?null:`then updated it ${n===1?`once`:`${n} times`}`}:{did:`updated`,times:n===0?null:`${e.length} times`}}function A(e){let t=[...new Set(e)];return t.length<=2?t.join(` and `):`${t.slice(0,-1).join(`, `)}, and ${t.at(-1)??``}`}var j=f();function M(){return(0,j.jsx)(`svg`,{viewBox:`0 0 16 16`,"aria-hidden":`true`,className:`size-3.5`,children:(0,j.jsx)(`path`,{d:`M8 4.2C6.4 3 4.4 2.8 2.2 3.4v9.2c2.2-.6 4.2-.4 5.8.8m0-9.2c1.6-1.2 3.6-1.4 5.8-.8v9.2c-2.2-.6-4.2-.4-5.8.8m0-9.2v9.2`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.2`,strokeLinejoin:`round`})})}function N({notices:e}){let[t]=e;if(t===void 0)return null;let{did:n,times:r}=k(e);return(0,j.jsxs)(j.Fragment,{children:[n,` knowledge`,` `,t.action===`removed`?(0,j.jsx)(`span`,{className:`text-fg-secondary`,children:t.topic}):(0,j.jsx)(m,{to:`/knowledge/$scope/$topic`,params:{scope:t.project??`society`,topic:t.topic},className:`text-fg-secondary hover:text-fg-primary`,children:t.topic}),r===null?null:` · ${r}`]})}function P(e){let t=e.notices.filter(e=>e.kind===`knowledge`);return{mark:(0,j.jsx)(M,{}),text:(0,j.jsx)(N,{notices:t})}}function F({run:e,now:t}){let n=v(),r=e.messages.at(-1);if(r===void 0)return null;let{mark:i,text:a}=P(e);return(0,j.jsxs)(`div`,{className:`flex items-center gap-3 px-4 py-1 text-meta`,children:[(0,j.jsx)(`span`,{className:`grid w-7 shrink-0 place-items-center text-fg-muted`,children:i}),(0,j.jsxs)(`p`,{className:`min-w-0 flex-1 truncate`,children:[A(e.messages.map(e=>n(e.author))),` `,a]}),(0,j.jsx)(`time`,{dateTime:r.ts,title:e.messages.map(e=>`${n(e.author)} ${e.ts}`).join(`
2
+ `),className:`shrink-0`,children:u(r.ts,t)})]})}function I(){let{_splat:e=`general`}=_({from:`/c/$`});return(0,j.jsx)(L,{channel:e},e)}function L({channel:e}){let{api:o}=c(),f=t({queryKey:[`channel`,e],queryFn:()=>o.channel(e)}),m=n(),_=i(),v=d(),D=s(),k=a(3e4),[A,M]=(0,E.useState)(!1),[N,P]=(0,E.useState)(!1),I=(0,E.useMemo)(()=>{let t=(f.data??[]).map(e=>({kind:`message`,at:e.ts,id:e.id,message:e})),n=(m.data??[]).filter(t=>t.channel===e).map(e=>({kind:`thread`,at:e.openedAt,id:e.id,thread:e}));return[...t,...n].toSorted((e,t)=>e.at.localeCompare(t.at)||e.id.localeCompare(t.id))},[f.data,m.data,e]),L=(0,E.useMemo)(()=>O(I,e=>e.kind===`message`?e.message:void 0),[I]),R=f.data?.at(-1)?.id??null;(0,E.useEffect)(()=>h(e,R),[e,R]);let{ref:z,onScroll:B}=T(),[V,H]=e.split(`/`),U=H??V??e,W=H===void 0?void 0:v.data?.find(e=>e.slug===V),G=H===void 0?`society`:W?.name??V,K=D.data?.find(t=>t.ref===e)?.archived,q=W?.archived!==void 0||K!==void 0,J=!q&&!l(H===void 0?null:V??null,U),Y=I.filter(e=>e.kind===`thread`&&e.thread.state===`open`);return(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(C,{title:`# ${U}`,subtitle:`${G} · ${f.data?.length??0} messages${Y.length>0?` · ${Y.length} open threads`:``}`,trailing:A||N||q?null:(0,j.jsxs)(j.Fragment,{children:[J?(0,j.jsx)(p,{onClick:()=>P(!0),children:`Archive…`}):null,(0,j.jsx)(p,{onClick:()=>M(!0),children:`New thread`})]})}),A?(0,j.jsx)(y,{channel:e,onDone:()=>M(!1)}):null,N?(0,j.jsx)(b,{channel:e,onDone:()=>P(!1)}):null,(0,j.jsx)(`div`,{ref:z,onScroll:B,className:`flex-1 overflow-y-auto py-2`,children:f.error instanceof r&&f.error.status===404?(0,j.jsxs)(S,{children:[`There is no channel `,e,`.`]}):f.data===void 0?(0,j.jsx)(S,{children:`Reading the channel…`}):I.length===0?(0,j.jsx)(S,{children:`Nothing has been said here yet.`}):L.map(e=>{if(e.kind===`notices`){let t=e.run.messages[0]?.id??``;return(0,j.jsx)(F,{run:e.run,now:k},t)}let{item:t}=e;return t.kind===`message`?(0,j.jsx)(x,{message:t.message,members:_.data,now:k},t.id):(0,j.jsx)(w,{thread:t.thread,now:k},t.id)})}),f.data===void 0?null:K===void 0?q?(0,j.jsxs)(`p`,{className:`border-t border-line px-4 py-3 text-meta`,children:[G,` is archived, so its channels are read-only.`]}):(0,j.jsx)(g,{target:{channel:e},placeholder:`Message #${U}`}):(0,j.jsxs)(`p`,{className:`border-t border-line px-4 py-3 text-meta`,children:[`Archived `,u(K.at,k),` by `,K.by,`: `,K.reason,`. Its posts, threads, and tasks stay readable; nothing more is posted or filed here.`]})]})}export{I as ChannelView};