@kubitnodes/stellaris 0.3.5 → 0.3.7

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 (56) hide show
  1. package/dist/cli/index.js +139 -15
  2. package/dist/server/index.js +210 -31
  3. package/package.json +1 -1
  4. package/web/dist/assets/BackLink-BW0sb-M0.js +1 -0
  5. package/web/dist/assets/ChannelView-DmTE9aUA.js +1 -0
  6. package/web/dist/assets/CitizenView-lrA6vZDK.js +2 -0
  7. package/web/dist/assets/CitizensView-MRVffBnU.js +1 -0
  8. package/web/dist/assets/KnowledgeView-C2N-x5yS.js +1 -0
  9. package/web/dist/assets/{Markdown-MZnGPfvk.js → Markdown-CS3NCze-.js} +4 -4
  10. package/web/dist/assets/MessageItem-CL3XDi7l.js +1 -0
  11. package/web/dist/assets/MetricsView-CR5u5k5n.js +1 -0
  12. package/web/dist/assets/NeedsYouView-BwUvwyVt.js +1 -0
  13. package/web/dist/assets/Overview-DxFOFviB.js +1 -0
  14. package/web/dist/assets/{Pane-B-pz52UC.js → Pane-voa6GU0F.js} +1 -1
  15. package/web/dist/assets/ProjectView-BkNG_qdh.js +1 -0
  16. package/web/dist/assets/ProposalView-G89UmLF5.js +1 -0
  17. package/web/dist/assets/ProposalsView-CzpL-POB.js +1 -0
  18. package/web/dist/assets/RunnersView-SKNIDsjR.js +1 -0
  19. package/web/dist/assets/{SocietyView-DA_65uyo.js → SocietyView-CCFXEYZW.js} +1 -1
  20. package/web/dist/assets/SubjectThread-JQG2FxkA.js +1 -0
  21. package/web/dist/assets/TaskFileView-BKoGmHGz.js +4 -0
  22. package/web/dist/assets/TaskView-BIYqIFU4.js +1 -0
  23. package/web/dist/assets/TasksView-n2ssgQOL.js +1 -0
  24. package/web/dist/assets/ThreadCard-CzQXJXSd.js +1 -0
  25. package/web/dist/assets/{ThreadForms-BipMby65.js → ThreadForms-0LpUc2jJ.js} +1 -1
  26. package/web/dist/assets/ThreadView-_LgGISSH.js +1 -0
  27. package/web/dist/assets/citizen-DvJr6Ibu.js +1 -0
  28. package/web/dist/assets/format-C7bqWt6N.js +55 -0
  29. package/web/dist/assets/index-D4oCtkuZ.css +2 -0
  30. package/web/dist/assets/index-DM00hlle.js +23 -0
  31. package/web/dist/assets/{tasks-DnJj3psB.js → tasks-B4bx7Gg3.js} +1 -1
  32. package/web/dist/assets/{useStickyScroll-D_7q2RKi.js → useStickyScroll-DCsF4GYJ.js} +1 -1
  33. package/web/dist/index.html +4 -4
  34. package/web/dist/assets/BackLink-DivaHiZ7.js +0 -1
  35. package/web/dist/assets/ChannelView-DgkkiZuY.js +0 -1
  36. package/web/dist/assets/CitizenView-BzhH1ycF.js +0 -2
  37. package/web/dist/assets/CitizensView-BzGNyC2f.js +0 -1
  38. package/web/dist/assets/KnowledgeView-DedLlEZd.js +0 -1
  39. package/web/dist/assets/MessageItem-Deq-ntT8.js +0 -1
  40. package/web/dist/assets/MetricsView-C-2j71rI.js +0 -1
  41. package/web/dist/assets/NeedsYouView-D4xqHNDU.js +0 -1
  42. package/web/dist/assets/Overview-DxCYcW_A.js +0 -1
  43. package/web/dist/assets/ProjectView-BhZYFv7f.js +0 -1
  44. package/web/dist/assets/ProposalView-DAc9lEHB.js +0 -1
  45. package/web/dist/assets/ProposalsView-CmwwJuop.js +0 -1
  46. package/web/dist/assets/RunnersView-Dqo_BEoK.js +0 -1
  47. package/web/dist/assets/SubjectThread-M4F1paqV.js +0 -1
  48. package/web/dist/assets/TaskFileView-CCNVrooP.js +0 -4
  49. package/web/dist/assets/TaskView-BTSDk1yL.js +0 -1
  50. package/web/dist/assets/TasksView-0IQm6u-_.js +0 -1
  51. package/web/dist/assets/ThreadCard-BTGP4fOT.js +0 -1
  52. package/web/dist/assets/ThreadView-D96DZAOM.js +0 -1
  53. package/web/dist/assets/citizen-CYBzbWt-.js +0 -1
  54. package/web/dist/assets/format-11lojpK1.js +0 -55
  55. package/web/dist/assets/index-BXq4t0NA.js +0 -23
  56. package/web/dist/assets/index-CKjqsZME.css +0 -2
@@ -6,7 +6,7 @@ import path9 from "node:path";
6
6
  import { serve } from "@hono/node-server";
7
7
 
8
8
  // packages/board-core/dist/board.js
9
- import { readdir as readdir3, readFile as readFile5, rename as rename3, stat as stat2 } from "node:fs/promises";
9
+ import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rename as rename3, stat as stat2 } from "node:fs/promises";
10
10
  import path6 from "node:path";
11
11
  import { decodeTime, monotonicFactory } from "ulid";
12
12
  import { z as z12 } from "zod";
@@ -56,7 +56,8 @@ var VERB_NAMES = [
56
56
  "plan_task",
57
57
  "advance_task",
58
58
  "configure_project",
59
- "archive_project"
59
+ "archive_project",
60
+ "remove_knowledge"
60
61
  ];
61
62
  var VerbNameSchema = z2.enum(VERB_NAMES);
62
63
  var CharterTriggerSchema = z2.enum(["user_post", "ops_event", "heartbeat"]);
@@ -100,6 +101,7 @@ var MEMBER_VERBS = [
100
101
  "join_project",
101
102
  "leave_project",
102
103
  "write_knowledge",
104
+ "remove_knowledge",
103
105
  "plan_task",
104
106
  "advance_task"
105
107
  ];
@@ -301,11 +303,23 @@ function mayHoldStage(member, task) {
301
303
  return !(stage.gate && task.stages.slice(0, index).some((s) => s.holders.includes(member.name)));
302
304
  }
303
305
  var ModelNameSchema = z3.string().trim().min(1).max(120).regex(/^[\w.:/[\]-]+$/, "a model name is one word of letters, digits, and . _ : / - [ ]");
306
+ var EffortSchema = z3.string().trim().min(1).max(32).regex(/^[a-z][a-z0-9_-]*$/, "an effort is one lowercase word, such as high");
307
+ var EffortOptionSchema = z3.object({
308
+ id: EffortSchema,
309
+ description: z3.string().default("")
310
+ });
304
311
  var ModelOptionSchema = z3.object({
305
312
  id: ModelNameSchema,
306
313
  name: z3.string(),
307
314
  description: z3.string().default(""),
308
- isDefault: z3.boolean().default(false)
315
+ isDefault: z3.boolean().default(false),
316
+ efforts: z3.array(EffortOptionSchema).default([]),
317
+ defaultEffort: EffortSchema.optional()
318
+ });
319
+ var RunnerModelsSchema = z3.object({
320
+ runner: NameSchema,
321
+ cli: CliKindSchema,
322
+ models: z3.array(ModelOptionSchema)
309
323
  });
310
324
  var AgentStatusSchema = z3.enum(["active", "retired"]);
311
325
  var AgentSchema = z3.object({
@@ -313,6 +327,8 @@ var AgentSchema = z3.object({
313
327
  role: NameSchema,
314
328
  cli: CliKindSchema.nullable(),
315
329
  model: z3.string().optional(),
330
+ /** The reasoning effort set for the agent's turns; the model's own default applies otherwise. */
331
+ effort: z3.string().optional(),
316
332
  /** The runner preferred for the agent's turns where nothing else decides; any runner with its CLI otherwise. */
317
333
  homeRunner: NameSchema.optional(),
318
334
  memberships: z3.array(NameSchema),
@@ -334,6 +350,8 @@ var MemberSchema = z3.object({
334
350
  model: z3.string().optional(),
335
351
  /** The model the CLI reported on the citizen's last turn. */
336
352
  lastModel: z3.string().optional(),
353
+ /** The reasoning effort configured on the record, if any; the model's own default applies otherwise. */
354
+ effort: z3.string().optional(),
337
355
  /** The citizen's own skills, by name. */
338
356
  skills: z3.array(NameSchema).default([]),
339
357
  memberships: z3.array(NameSchema),
@@ -523,6 +541,12 @@ var KnowledgeSchema = z3.object({
523
541
  updatedBy: NameSchema,
524
542
  updatedAt: IsoDateTimeSchema
525
543
  });
544
+ var RemovedKnowledgeSchema = z3.object({
545
+ topic: NameSchema,
546
+ project: NameSchema.nullable(),
547
+ removedBy: NameSchema,
548
+ removedAt: IsoDateTimeSchema
549
+ });
526
550
  var ProposalFrontmatterSchema = z3.object({
527
551
  id: UlidSchema,
528
552
  kind: ProposalKindSchema,
@@ -619,6 +643,11 @@ var VerbInputs = {
619
643
  topic: NameSchema,
620
644
  body: z4.string().min(1)
621
645
  }),
646
+ remove_knowledge: z4.object({
647
+ /** As for write_knowledge: a project you belong to, or null for society knowledge. */
648
+ project: NameSchema.nullable().default(null),
649
+ topic: NameSchema
650
+ }),
622
651
  plan_task: z4.object({
623
652
  task_id: UlidSchema,
624
653
  stages: z4.array(PlanEditStageSchema),
@@ -652,6 +681,7 @@ var VERB_DESCRIPTIONS = {
652
681
  join_project: "Join a project, or add another citizen to one when your role allows it. Membership gives the pair a worktree and an onboarding turn.",
653
682
  leave_project: "Leave a project, or remove another citizen from one when your role allows it.",
654
683
  write_knowledge: "Write or replace a knowledge topic: durable facts every member of a project should know, or with project null the society's shared knowledge (steward and user). Not a message; use post_message for those.",
684
+ remove_knowledge: "Remove a knowledge topic that is wrong, superseded, or folded into another, so no turn or search finds it again. Whoever may write the topic may remove it; the board keeps its text aside for the user to restore.",
655
685
  plan_task: "Reshape a task's plan from the current stage onward, or after it while someone holds it: keep a stage by passing its id, drop it by leaving it out, add one without an id. Gated stages and on_done: user, steward, concierge.",
656
686
  advance_task: "Finish the stage you hold, with a note for the task's thread on what you did. The next stage becomes current; past the last one the task is done.",
657
687
  configure_project: "Set a project's completion effect: none, or merge to land each finished task's branch on the default branch.",
@@ -693,6 +723,7 @@ var BOARD_EVENT_TYPES = [
693
723
  "agent.joined",
694
724
  "agent.left",
695
725
  "knowledge.written",
726
+ "knowledge.removed",
696
727
  "skill.promoted",
697
728
  "runner.added",
698
729
  "user.signed_in",
@@ -877,6 +908,8 @@ var TurnJobSchema = z6.object({
877
908
  role: NameSchema,
878
909
  cli: CliKindSchema,
879
910
  model: z6.string().optional(),
911
+ /** The reasoning effort set on the agent; absent for the model's own default. */
912
+ effort: z6.string().optional(),
880
913
  /** A project slug, or the society scope. */
881
914
  scope: NameSchema,
882
915
  /** The thread whose conversation the turn is in; absent for the home conversation. */
@@ -1113,6 +1146,8 @@ var TurnHistoryEntrySchema = z7.object({
1113
1146
  trigger: z7.string(),
1114
1147
  exitReason: z7.string().nullable(),
1115
1148
  costUsd: z7.number(),
1149
+ /** The tokens the turn used; absent for turns logged before turns recorded them. */
1150
+ usage: UsageSchema.optional(),
1116
1151
  model: z7.string().nullable(),
1117
1152
  summary: z7.string().nullable(),
1118
1153
  error: z7.string().nullable(),
@@ -1341,7 +1376,7 @@ var RunnerConfigSchema = z10.object({
1341
1376
  });
1342
1377
 
1343
1378
  // packages/shared/dist/version.js
1344
- var STELLARIS_VERSION = "0.3.5";
1379
+ var STELLARIS_VERSION = "0.3.7";
1345
1380
 
1346
1381
  // packages/board-core/dist/errors.js
1347
1382
  var BoardError = class extends Error {
@@ -2377,6 +2412,10 @@ var BoardPaths = class {
2377
2412
  pausedFile() {
2378
2413
  return path5.join(this.state(), "paused.json");
2379
2414
  }
2415
+ /** Removed knowledge topics, set aside outside the projection, by scope. */
2416
+ removedKnowledge(scope) {
2417
+ return path5.join(this.state(), "removed-knowledge", scope);
2418
+ }
2380
2419
  };
2381
2420
 
2382
2421
  // packages/board-core/dist/tokens.js
@@ -2410,6 +2449,7 @@ var TurnHistoryPayloadSchema = z12.object({
2410
2449
  trigger: z12.string().default("manual"),
2411
2450
  exitReason: z12.string().nullable().default(null),
2412
2451
  costUsd: z12.number().default(0),
2452
+ usage: UsageSchema.optional(),
2413
2453
  model: z12.string().nullable().default(null),
2414
2454
  summary: z12.string().nullable().default(null),
2415
2455
  error: z12.string().nullable().default(null),
@@ -2469,6 +2509,7 @@ var OPS_CHANNEL_RETIRED = "ops-channel-retired";
2469
2509
  var DECISIONS_CHANNEL_RETIRED = "decisions-channel-retired";
2470
2510
  var THREAD_CONVERSATIONS = "thread-conversations";
2471
2511
  var ASKS_CHANNEL_OPENED = "asks-channel-opened";
2512
+ var KNOWLEDGE_REMOVAL = "knowledge-removal-granted";
2472
2513
  var PLANNING_ROLES = [USER_ROLE, "steward", "concierge"];
2473
2514
  var REALLOCATING_ROLES = [USER_ROLE, "steward", "concierge"];
2474
2515
  var PROFILE_TEMPLATE = "# Profile\n\nOne short paragraph, kept current: what I do well, what I am working on, and what to send my way. The board projects this into the roster the front desk reads.\n";
@@ -2658,7 +2699,8 @@ var Board = class _Board {
2658
2699
  OPS_CHANNEL_RETIRED,
2659
2700
  DECISIONS_CHANNEL_RETIRED,
2660
2701
  THREAD_CONVERSATIONS,
2661
- ASKS_CHANNEL_OPENED
2702
+ ASKS_CHANNEL_OPENED,
2703
+ KNOWLEDGE_REMOVAL
2662
2704
  ].filter((name) => !applied.includes(name));
2663
2705
  if (pending.length === 0) {
2664
2706
  return;
@@ -2687,8 +2729,22 @@ var Board = class _Board {
2687
2729
  if (pending.includes(ASKS_CHANNEL_OPENED)) {
2688
2730
  await this.openAsksChannel();
2689
2731
  }
2732
+ if (pending.includes(KNOWLEDGE_REMOVAL)) {
2733
+ await this.grantKnowledgeRemoval();
2734
+ }
2690
2735
  await writeJson(file, { applied: [...applied, ...pending] });
2691
2736
  }
2737
+ /**
2738
+ * Whoever may write a scope's knowledge may remove it, so every charter granting
2739
+ * `write_knowledge` is granted `remove_knowledge` once; seed roles have it from their seed.
2740
+ */
2741
+ async grantKnowledgeRemoval() {
2742
+ for (const charter of await this.listRoles()) {
2743
+ if (charter.verbs.includes("write_knowledge") && !charter.verbs.includes("remove_knowledge")) {
2744
+ await this.writeRoleUnlocked(USER_NAME, { ...charter, verbs: [...charter.verbs, "remove_knowledge"] }, { verbsAdded: ["remove_knowledge"] });
2745
+ }
2746
+ }
2747
+ }
2692
2748
  /**
2693
2749
  * A task sent back before tasks recorded it kept no trace outside the event log. A task still
2694
2750
  * short of the stage its latest `task.moved` came from is in that rework, and records it.
@@ -3920,16 +3976,7 @@ ${task.body}`;
3920
3976
  const args = VerbInputs.write_knowledge.parse(input);
3921
3977
  await this.authorize(actor, "write_knowledge");
3922
3978
  return this.mutex.run(async () => {
3923
- if (args.project === null) {
3924
- if (!CURATING_ROLES.includes(actor.role)) {
3925
- throw new BoardError("FORBIDDEN", "society knowledge is curated by the steward and the user");
3926
- }
3927
- } else {
3928
- const project = await this.readActiveProject(args.project);
3929
- if (!CURATING_ROLES.includes(actor.role) && !project.members.includes(actor.name)) {
3930
- throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${args.project}`);
3931
- }
3932
- }
3979
+ await this.assertMayCurate(actor, args.project);
3933
3980
  const data = KnowledgeSchema.parse({
3934
3981
  topic: args.topic,
3935
3982
  project: args.project,
@@ -3949,6 +3996,52 @@ ${task.body}`;
3949
3996
  return { ...data, body: args.body };
3950
3997
  });
3951
3998
  }
3999
+ /**
4000
+ * Retires a topic, by whoever may write it. Its file leaves the knowledge directory, so no turn,
4001
+ * search, or digest finds it again, and is set aside under `state/removed-knowledge`, outside
4002
+ * the projection, for the user to restore by hand. The note in the matching general channel
4003
+ * carries no mention, as a write's does.
4004
+ */
4005
+ async removeKnowledge(actor, input) {
4006
+ const args = VerbInputs.remove_knowledge.parse(input);
4007
+ await this.authorize(actor, "remove_knowledge");
4008
+ return this.mutex.run(async () => {
4009
+ await this.assertMayCurate(actor, args.project);
4010
+ const file = args.project === null ? this.paths.societyKnowledgeFile(args.topic) : this.paths.projectKnowledgeFile(args.project, args.topic);
4011
+ if (!await exists(file)) {
4012
+ throw new BoardError("NOT_FOUND", `${args.project ?? "the society"} has no knowledge topic ${args.topic}`);
4013
+ }
4014
+ const removed = RemovedKnowledgeSchema.parse({
4015
+ topic: args.topic,
4016
+ project: args.project,
4017
+ removedBy: actor.name,
4018
+ removedAt: this.now().toISOString()
4019
+ });
4020
+ const aside = this.paths.removedKnowledge(args.project ?? SOCIETY_SCOPE);
4021
+ await mkdir4(aside, { recursive: true });
4022
+ await rename3(file, path6.join(aside, `${args.topic}.${removed.removedAt.replaceAll(":", "-")}.md`));
4023
+ await this.events.append("knowledge.removed", actor.name, {
4024
+ topic: args.topic,
4025
+ project: args.project
4026
+ });
4027
+ const where = args.project === null ? "general" : channelRef(args.project, "general");
4028
+ await this.appendMessage(actor.name, where, `Knowledge removed: ${args.topic}. It is no longer under knowledge/${args.topic}.md${args.project === null ? " of the society" : ""}.`);
4029
+ return removed;
4030
+ });
4031
+ }
4032
+ /** Society knowledge is the steward's and the user's; a project's, its members' too. */
4033
+ async assertMayCurate(actor, project) {
4034
+ if (project === null) {
4035
+ if (!CURATING_ROLES.includes(actor.role)) {
4036
+ throw new BoardError("FORBIDDEN", "society knowledge is curated by the steward and the user");
4037
+ }
4038
+ return;
4039
+ }
4040
+ const active = await this.readActiveProject(project);
4041
+ if (!CURATING_ROLES.includes(actor.role) && !active.members.includes(actor.name)) {
4042
+ throw new BoardError("FORBIDDEN", `${actor.name} is not a member of ${project}`);
4043
+ }
4044
+ }
3952
4045
  async openThread(actor, input) {
3953
4046
  const args = VerbInputs.open_thread.parse(input);
3954
4047
  await this.authorize(actor, "open_thread");
@@ -4775,6 +4868,8 @@ ${rationale}`}`);
4775
4868
  return this.leaveProject(actor, VerbInputs.leave_project.parse(input));
4776
4869
  case "write_knowledge":
4777
4870
  return this.writeKnowledge(actor, VerbInputs.write_knowledge.parse(input));
4871
+ case "remove_knowledge":
4872
+ return this.removeKnowledge(actor, VerbInputs.remove_knowledge.parse(input));
4778
4873
  case "plan_task":
4779
4874
  return this.planTask(actor, VerbInputs.plan_task.parse(input));
4780
4875
  case "advance_task":
@@ -5013,6 +5108,7 @@ ${rationale}`}`);
5013
5108
  session: parsed.session,
5014
5109
  exitReason: parsed.exitReason,
5015
5110
  costUsd: parsed.costUsd,
5111
+ ...parsed.usage === null ? {} : { usage: parsed.usage },
5016
5112
  toolCalls: parsed.toolCalls,
5017
5113
  model: parsed.model,
5018
5114
  summary: parsed.status?.summary ?? null,
@@ -5469,6 +5565,7 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
5469
5565
  resident: charter.resident,
5470
5566
  ...agent.model === void 0 ? {} : { model: agent.model },
5471
5567
  ...lastModel === void 0 ? {} : { lastModel },
5568
+ ...agent.effort === void 0 ? {} : { effort: agent.effort },
5472
5569
  skills: (await this.listAgentSkills(name)).map((skill) => skill.name),
5473
5570
  memberships: agent.memberships,
5474
5571
  subscriptions: agent.subscriptions,
@@ -5517,6 +5614,29 @@ Short, curated, loaded on every turn. Keep entries that change future behavior a
5517
5614
  return next;
5518
5615
  });
5519
5616
  }
5617
+ /**
5618
+ * Sets the reasoning effort a citizen's turns run with, or clears it for the model's own default.
5619
+ * It applies from the citizen's next turn; the runner starts a warm session afresh when it changes.
5620
+ */
5621
+ async setAgentEffort(actor, name, effort) {
5622
+ this.assertUser(actor, "only the user may change a citizen's reasoning effort");
5623
+ const chosen = effort === null ? null : EffortSchema.parse(effort);
5624
+ return this.mutex.run(async () => {
5625
+ const current = await this.readAgent(name);
5626
+ if (current.status !== "active" || current.cli === null) {
5627
+ throw new BoardError("INVALID_STATE", `${name} takes no turns, so it has no effort to set`);
5628
+ }
5629
+ const { effort: previous, ...rest } = current;
5630
+ const next = await this.updateAgent(name, () => chosen === null ? rest : { ...rest, effort: chosen });
5631
+ await this.refreshMember(name);
5632
+ await this.events.append("agent.configured", actor.name, {
5633
+ agent: name,
5634
+ effort: chosen,
5635
+ previous: previous ?? null
5636
+ });
5637
+ return next;
5638
+ });
5639
+ }
5520
5640
  async updateAgent(name, mutate) {
5521
5641
  const current = await this.readAgent(name);
5522
5642
  const next = AgentSchema.parse(mutate(current));
@@ -8024,6 +8144,7 @@ var TurnHost = class {
8024
8144
  role: agent.role,
8025
8145
  cli: agent.cli,
8026
8146
  ...agent.model === void 0 ? {} : { model: agent.model },
8147
+ ...agent.effort === void 0 ? {} : { effort: agent.effort },
8027
8148
  scope: dispatch.project,
8028
8149
  ...thread === void 0 ? {} : { thread: thread.id },
8029
8150
  session: recorded ?? null,
@@ -8431,6 +8552,7 @@ var RunnerHub = class {
8431
8552
  pending = /* @__PURE__ */ new Map();
8432
8553
  /** Disconnects being recorded, which `close` waits for. */
8433
8554
  detaching = /* @__PURE__ */ new Set();
8555
+ runnerListeners = /* @__PURE__ */ new Set();
8434
8556
  constructor(options) {
8435
8557
  this.board = options.board;
8436
8558
  this.host = options.host;
@@ -8486,6 +8608,7 @@ var RunnerHub = class {
8486
8608
  clis: hello.clis,
8487
8609
  capabilities: hello.capabilities
8488
8610
  });
8611
+ this.runnerChanged(name);
8489
8612
  this.log.info({ runner: name, clis: hello.clis, slots: hello.slots }, "runner registered");
8490
8613
  return { name, version: this.version };
8491
8614
  }
@@ -8502,6 +8625,7 @@ var RunnerHub = class {
8502
8625
  seat.grace = null;
8503
8626
  }
8504
8627
  await this.board.markRunner(name, { status: "connected" });
8628
+ this.runnerChanged(name);
8505
8629
  return async () => {
8506
8630
  if (seat.send !== send) {
8507
8631
  return;
@@ -8774,16 +8898,37 @@ var RunnerHub = class {
8774
8898
  return changes;
8775
8899
  }
8776
8900
  /** The models a CLI offers, asked of a connected runner that has it; none when no runner has it. */
8777
- async models(cli) {
8778
- const seat = this.byLoad().find((each) => each.send !== null && each.hello.clis.includes(cli));
8779
- if (seat === void 0) {
8780
- throw new Error(`no connected runner has ${cli}`);
8901
+ /**
8902
+ * The connected runner a model list for `cli` comes from: the first of `prefer` that has the CLI,
8903
+ * else the least busy that does; null when no connected runner has it.
8904
+ */
8905
+ modelRunner(cli, prefer = []) {
8906
+ const able = (seat) => seat !== void 0 && seat.send !== null && seat.hello.clis.includes(cli);
8907
+ const preferred = prefer.map((name) => this.seats.get(name)).find(able);
8908
+ return (preferred ?? this.byLoad().find(able))?.name ?? null;
8909
+ }
8910
+ /** The models `cli` lists on a runner, `modelRunner`'s choice when none is named. */
8911
+ async models(cli, runner) {
8912
+ const name = runner ?? this.modelRunner(cli);
8913
+ const seat = name === null ? void 0 : this.seats.get(name);
8914
+ if (seat === void 0 || seat.send === null || !seat.hello.clis.includes(cli)) {
8915
+ throw new RunnerAwayError(runner === void 0 ? `no connected runner has ${cli}` : `runner ${runner} is not connected with ${cli}`);
8781
8916
  }
8782
8917
  return ModelListSchema.parse(await this.request(seat, (request) => ({ type: "models", request, cli })));
8783
8918
  }
8919
+ /** Calls `listener` whenever a runner registers, connects, or goes away. */
8920
+ onRunnerChange(listener) {
8921
+ this.runnerListeners.add(listener);
8922
+ }
8923
+ runnerChanged(runner) {
8924
+ for (const listener of this.runnerListeners) {
8925
+ listener(runner);
8926
+ }
8927
+ }
8784
8928
  // -------------------------------------------------------------------------------------------
8785
8929
  async detach(seat) {
8786
8930
  seat.send = null;
8931
+ this.runnerChanged(seat.name);
8787
8932
  seat.disconnectedAt = this.now().getTime();
8788
8933
  for (const [id, pending] of this.pending) {
8789
8934
  if (pending.runner === seat.name) {
@@ -9453,6 +9598,7 @@ var RunnerBodySchema = z15.object({ name: NameSchema });
9453
9598
  var EnrollmentApprovalSchema = z15.object({ name: NameSchema });
9454
9599
  var HomeRunnerBodySchema = z15.object({ runner: NameSchema.nullable() });
9455
9600
  var ModelBodySchema = z15.object({ model: ModelNameSchema.nullable() });
9601
+ var EffortBodySchema = z15.object({ effort: EffortSchema.nullable() });
9456
9602
  var HistoryLimitSchema = z15.coerce.number().int().min(1).max(500);
9457
9603
  var ChannelBodySchema = z15.object({
9458
9604
  project: NameSchema.nullable().default(null),
@@ -9577,6 +9723,11 @@ function createApp(deps) {
9577
9723
  const { tokenHash: _hash, ...agent } = await board2.setAgentModel(c.get("actor"), c.req.param("name"), body.model);
9578
9724
  return c.json(agent);
9579
9725
  });
9726
+ api.put("/agents/:name/effort", async (c) => {
9727
+ const body = EffortBodySchema.parse(await c.req.json());
9728
+ const { tokenHash: _hash, ...agent } = await board2.setAgentEffort(c.get("actor"), c.req.param("name"), body.effort);
9729
+ return c.json(agent);
9730
+ });
9580
9731
  api.put("/agents/:name/runner", async (c) => {
9581
9732
  const body = HomeRunnerBodySchema.parse(await c.req.json());
9582
9733
  const { tokenHash: _hash, ...agent } = await board2.setAgentRunner(c.get("actor"), c.req.param("name"), body.runner);
@@ -9584,7 +9735,19 @@ function createApp(deps) {
9584
9735
  });
9585
9736
  api.get("/models/:cli", async (c) => {
9586
9737
  const cli = CliKindSchema.parse(c.req.param("cli"));
9587
- return c.json(models === void 0 ? [] : await models.list(cli).catch(() => []));
9738
+ return c.json(models === void 0 ? [] : await models.list(cli).then((listed) => listed.models, () => []));
9739
+ });
9740
+ api.get("/agents/:name/models", async (c) => {
9741
+ const agent = await board2.readAgent(c.req.param("name"));
9742
+ if (agent.cli === null) {
9743
+ throw new BoardError("INVALID_STATE", `${agent.name} takes no turns, so it has no models`);
9744
+ }
9745
+ if (models === void 0) {
9746
+ throw new RunnerAwayError("this server asks no runner for models");
9747
+ }
9748
+ const placed = new Map((await board2.listProjects()).map((project) => [project.slug, project.runner]));
9749
+ const prefer = [agent.homeRunner, ...agent.memberships.map((slug) => placed.get(slug))].filter((runner) => runner !== void 0);
9750
+ return c.json(await models.list(agent.cli, prefer));
9588
9751
  });
9589
9752
  api.put("/roles/:name", async (c) => {
9590
9753
  const charter = RoleCharterSchema.parse({
@@ -9867,26 +10030,42 @@ var EnrollmentDesk = class {
9867
10030
  // apps/server/dist/models.js
9868
10031
  var HOUR = 60 * 6e4;
9869
10032
  var ModelCatalog = class {
9870
- lister;
10033
+ runners;
9871
10034
  ttlMs;
9872
10035
  now;
9873
10036
  kept = /* @__PURE__ */ new Map();
9874
- constructor(lister, ttlMs = HOUR, now = Date.now) {
9875
- this.lister = lister;
10037
+ constructor(runners2, ttlMs = HOUR, now = Date.now) {
10038
+ this.runners = runners2;
9876
10039
  this.ttlMs = ttlMs;
9877
10040
  this.now = now;
10041
+ runners2.onRunnerChange((runner) => this.forget(runner));
9878
10042
  }
9879
- list(cli) {
9880
- const kept = this.kept.get(cli);
10043
+ async list(cli, prefer = []) {
10044
+ const runner = this.runners.modelRunner(cli, prefer);
10045
+ if (runner === null) {
10046
+ throw new RunnerAwayError(`no connected runner has ${cli}`);
10047
+ }
10048
+ const key = `${runner}/${cli}`;
10049
+ const kept = this.kept.get(key);
9881
10050
  if (kept !== void 0 && this.now() - kept.at < this.ttlMs) {
9882
- return kept.models;
10051
+ return { runner, cli, models: await kept.models };
9883
10052
  }
9884
- const models = this.lister(cli);
9885
- this.kept.set(cli, { at: this.now(), models });
10053
+ const models = this.runners.models(cli, runner);
10054
+ this.kept.set(key, { at: this.now(), models });
9886
10055
  models.catch(() => {
9887
- this.kept.delete(cli);
10056
+ if (this.kept.get(key)?.models === models) {
10057
+ this.kept.delete(key);
10058
+ }
9888
10059
  });
9889
- return models;
10060
+ return { runner, cli, models: await models };
10061
+ }
10062
+ /** Drops every list a runner gave. */
10063
+ forget(runner) {
10064
+ for (const key of this.kept.keys()) {
10065
+ if (key.startsWith(`${runner}/`)) {
10066
+ this.kept.delete(key);
10067
+ }
10068
+ }
9890
10069
  }
9891
10070
  };
9892
10071
 
@@ -9994,7 +10173,7 @@ var app = createApp({
9994
10173
  version: VERSION,
9995
10174
  turns,
9996
10175
  scheduler: view,
9997
- models: new ModelCatalog((cli) => runners.models(cli)),
10176
+ models: new ModelCatalog(runners),
9998
10177
  runners,
9999
10178
  webDir,
10000
10179
  signIn: { github: config.github, log },
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "codex",
9
9
  "multi-agent"
10
10
  ],
11
- "version": "0.3.5",
11
+ "version": "0.3.7",
12
12
  "license": "MIT",
13
13
  "homepage": "https://github.com/kr4t0n/stellaris",
14
14
  "repository": {
@@ -0,0 +1 @@
1
+ import{b as e,ln as t,wt as n}from"./format-C7bqWt6N.js";import{L as r,S as i,n as a,r as o,t as s}from"./index-DM00hlle.js";var c=t(),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:t}){let u=n(),d=r({select:e=>e.location.state.__TSR_index}),f=o(),p=i(),m=e(),h=a(f,d);if(h===null)return t;let g=s(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};
@@ -0,0 +1 @@
1
+ import{D as e,F as t,Ht as n,R as r,b as i,g as a,ln as o,pn as s,un as c,y as l}from"./format-C7bqWt6N.js";import{I as u,V as d,c as f,i as p}from"./index-DM00hlle.js";import{t as m}from"./MessageItem-CL3XDi7l.js";import{n as h,t as g}from"./Pane-voa6GU0F.js";import{n as _}from"./ThreadCard-CzQXJXSd.js";import{i as v}from"./ThreadForms-0LpUc2jJ.js";import{t as y}from"./useStickyScroll-DCsF4GYJ.js";var b=s(c(),1),x=o();function S(){let{_splat:e=`general`}=d({from:`/c/$`});return(0,x.jsx)(C,{channel:e},e)}function C({channel:o}){let{api:s}=e(),c=n({queryKey:[`channel`,o],queryFn:()=>s.channel(o)}),d=t(),S=a(),C=i(),w=l(3e4),[T,E]=(0,b.useState)(!1),D=(0,b.useMemo)(()=>{let e=(c.data??[]).map(e=>({kind:`message`,at:e.ts,id:e.id,message:e})),t=(d.data??[]).filter(e=>e.channel===o).map(e=>({kind:`thread`,at:e.openedAt,id:e.id,thread:e}));return[...e,...t].toSorted((e,t)=>e.at.localeCompare(t.at)||e.id.localeCompare(t.id))},[c.data,d.data,o]),O=c.data?.at(-1)?.id??null;(0,b.useEffect)(()=>u(o,O),[o,O]);let{ref:k,onScroll:A}=y(),[j,M]=o.split(`/`),N=M??j??o,P=M===void 0?void 0:C.data?.find(e=>e.slug===j),F=M===void 0?`society`:P?.name??j,I=P?.archived!==void 0,L=D.filter(e=>e.kind===`thread`&&e.thread.state===`open`);return(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(g,{title:`# ${N}`,subtitle:`${F} · ${c.data?.length??0} messages${L.length>0?` · ${L.length} open threads`:``}`,trailing:T||I?null:(0,x.jsx)(f,{onClick:()=>E(!0),children:`New thread`})}),T?(0,x.jsx)(v,{channel:o,onDone:()=>E(!1)}):null,(0,x.jsx)(`div`,{ref:k,onScroll:A,className:`flex-1 overflow-y-auto py-2`,children:c.error instanceof r&&c.error.status===404?(0,x.jsxs)(h,{children:[`There is no channel `,o,`.`]}):c.data===void 0?(0,x.jsx)(h,{children:`Reading the channel…`}):D.length===0?(0,x.jsx)(h,{children:`Nothing has been said here yet.`}):D.map(e=>e.kind===`message`?(0,x.jsx)(m,{message:e.message,members:S.data,now:w},e.id):(0,x.jsx)(_,{thread:e.thread,now:w},e.id))}),c.data===void 0?null:I?(0,x.jsxs)(`p`,{className:`border-t border-line px-4 py-3 text-meta`,children:[F,` is archived, so its channels are read-only.`]}):(0,x.jsx)(p,{target:{channel:o},placeholder:`Message #${N}`})]})}export{S as ChannelView};
@@ -0,0 +1,2 @@
1
+ import{D as e,E as t,H as n,I as r,L as i,P as a,R as o,T as s,_ as c,a as l,cn as u,ct as d,d as f,f as p,g as m,ln as h,o as g,p as _,pn as v,t as y,un as b,w as x,y as S}from"./format-C7bqWt6N.js";import{B as C,H as w,R as T,S as E,T as D,V as ee,_ as te,c as O,f as k,g as ne,h as re,i as ie,m as A,p as j,v as M,x as N,y as P}from"./index-DM00hlle.js";import{t as F}from"./Markdown-CS3NCze-.js";import{n as I,t as ae}from"./Pane-voa6GU0F.js";import{n as oe,r as L}from"./ThreadForms-0LpUc2jJ.js";import{t as se}from"./useStickyScroll-DCsF4GYJ.js";import{a as R,c as ce,i as z,l as B,n as le,o as V,s as ue,t as de}from"./citizen-DvJr6Ibu.js";var H=v(b(),1),U=h(),fe={completed:`completed`,timeout:`timed out`,error:`failed`,interrupted:`was interrupted`,blocked:`was blocked`,stopped:`was stopped by you`};function W(e){return new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1})}function G({label:e,tone:t,children:n}){return(0,U.jsxs)(`div`,{children:[(0,U.jsx)(`p`,{className:`px-0.5 pb-0.5 text-[10px] tracking-wider text-fg-muted uppercase`,children:e}),(0,U.jsx)(`pre`,{className:`max-h-72 overflow-auto rounded-md bg-surface-0/60 p-2 font-mono text-[11px] leading-relaxed whitespace-pre-wrap break-words ${t}`,children:n})]})}function pe({step:e,open:t}){if(e.kind===`say`)return(0,U.jsx)(`li`,{className:`py-1.5 text-sm`,title:W(e.at),children:(0,U.jsx)(F,{text:e.text})});if(e.kind===`error`)return(0,U.jsx)(`li`,{className:`py-1 font-mono text-xs break-words text-red-400`,title:W(e.at),children:e.message});if(e.kind===`steer`)return(0,U.jsx)(`li`,{children:(0,U.jsxs)(`details`,{open:t,className:`group rounded-md open:bg-surface-2/30`,children:[(0,U.jsxs)(`summary`,{className:`flex cursor-pointer list-none items-center gap-2 rounded-md px-1.5 py-1 text-xs hover:bg-surface-2/40`,children:[(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`grid w-3 shrink-0 place-items-center text-sky-300`,children:`↳`}),(0,U.jsxs)(`span`,{className:`shrink-0 text-fg-tertiary`,children:[`Took `,re(e.posts.length)]}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-fg-secondary`,children:e.from.length===0?``:`from ${e.from.join(`, `)}, delivered mid-turn`}),(0,U.jsx)(`span`,{className:`shrink-0 font-mono text-[10px] text-fg-muted`,children:W(e.at)})]}),(0,U.jsx)(`ul`,{className:`space-y-1.5 px-1.5 pb-1.5`,children:e.posts.map(e=>(0,U.jsxs)(`li`,{className:`rounded-md bg-surface-0/40 px-2.5 py-2`,children:[(0,U.jsxs)(`p`,{className:`flex gap-1.5 text-meta`,children:[(0,U.jsx)(`span`,{className:`text-fg-secondary`,children:e.author}),(0,U.jsxs)(`span`,{className:`min-w-0 truncate`,children:[`in `,e.where]}),e.at===``?null:(0,U.jsx)(`span`,{className:`ml-auto shrink-0 font-mono`,children:W(e.at)})]}),(0,U.jsx)(`div`,{className:`mt-1 text-sm`,children:(0,U.jsx)(F,{text:e.body})})]},`${e.at}-${e.author}`))})]})});let n=e.ok===null?(0,U.jsx)(`span`,{"aria-label":`running`,className:`size-1.5 animate-pulse rounded-full bg-amber-300`}):e.ok?(0,U.jsx)(`span`,{"aria-label":`succeeded`,className:`text-[11px] text-emerald-400`,children:`✓`}):(0,U.jsx)(`span`,{"aria-label":`failed`,className:`text-[11px] text-red-400`,children:`✕`});return(0,U.jsx)(`li`,{children:(0,U.jsxs)(`details`,{open:t,className:`group rounded-md open:bg-surface-2/30`,children:[(0,U.jsxs)(`summary`,{className:`flex cursor-pointer list-none items-center gap-2 rounded-md px-1.5 py-1 text-xs hover:bg-surface-2/40`,children:[(0,U.jsx)(`span`,{className:`grid w-3 shrink-0 place-items-center`,children:n}),(0,U.jsx)(`span`,{className:`shrink-0 font-mono text-fg-tertiary`,children:ne(e.name)}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate font-mono text-fg-secondary`,children:e.summary}),(0,U.jsx)(`span`,{className:`shrink-0 font-mono text-[10px] text-fg-muted`,children:W(e.at)})]}),(0,U.jsxs)(`div`,{className:`space-y-1.5 px-1.5 pb-1.5`,children:[(0,U.jsx)(G,{label:`Input`,tone:`text-fg-secondary`,children:e.detail}),e.output===null?null:(0,U.jsx)(G,{label:e.ok===!1?`Returned an error`:`Returned`,tone:e.ok===!1?`text-red-300`:`text-fg-tertiary`,children:e.output})]})]})})}function K({steps:e}){let[t,n]=(0,H.useState)(!1),r=e.filter(e=>e.kind===`tool`).length;return e.length===0?(0,U.jsx)(`p`,{className:`mt-2 text-meta`,children:`No steps yet.`}):(0,U.jsxs)(U.Fragment,{children:[r===0?null:(0,U.jsxs)(`p`,{className:`mt-2 flex items-center gap-2 text-meta`,children:[r===1?`1 tool call`:`${r} tool calls`,(0,U.jsx)(`button`,{type:`button`,onClick:()=>n(!t),className:`rounded-md px-1.5 py-0.5 text-fg-tertiary transition-colors hover:bg-surface-2/60 hover:text-fg-primary`,children:t?`Collapse all`:`Expand all`})]}),(0,U.jsx)(`ol`,{className:`mt-1 space-y-0.5`,children:e.map(e=>(0,U.jsx)(pe,{step:e,open:t},e.seq))})]})}function me({turn:e,running:t,now:n}){return e.end===null?t?(0,U.jsxs)(`p`,{className:`mt-3 flex items-center gap-2 text-meta`,"aria-live":`polite`,children:[(0,U.jsx)(`span`,{className:`size-1.5 animate-pulse rounded-full bg-emerald-400`}),`Working · last step `,y(e.lastAt,n)]}):(0,U.jsx)(`p`,{className:`mt-3 text-meta`,children:`The turn ended without a report.`}):(0,U.jsxs)(`section`,{className:`mt-3 rounded-xl bg-surface-2/40 px-3.5 py-3 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)]`,children:[(0,U.jsxs)(`p`,{className:`text-caps`,children:[`The turn `,fe[e.end.exitReason],` `,y(e.end.at,n),e.fromStart?` · ${j(e.startedAt,Date.parse(e.end.at))}`:``]}),e.end.summary===null?null:(0,U.jsx)(`p`,{className:`mt-1 text-sm text-fg-secondary`,children:e.end.summary})]})}var q=30,he={turn:`turn`,board:`board`,merge:`both kept`},ge={turn:`Changed by a turn`,board:`Written by the board`,merge:`Both versions kept`};function _e(e){return e.status===`added`?`new`:e.status===`deleted`?`deleted`:e.added===null||e.removed===null?`binary`:`+${e.added} −${e.removed}`}function ve(e){return e.files.map(e=>`${e.path} ${_e(e)}`).join(`, `)}function ye(e){return e.startsWith(`@@`)||e.startsWith(`\\`)?`text-fg-muted`:e.startsWith(`+`)?`text-emerald-300`:e.startsWith(`-`)?`text-red-300`:`text-fg-tertiary`}function be({file:e}){return(0,U.jsxs)(`div`,{children:[(0,U.jsxs)(`p`,{className:`text-xs text-fg-secondary`,children:[e.path,e.status===`modified`?``:(0,U.jsxs)(`span`,{className:`text-fg-muted`,children:[` · `,e.status]})]}),e.patch===null?(0,U.jsx)(`p`,{className:`mt-1 text-meta`,children:`A binary file; its contents are not shown.`}):e.patch===``?(0,U.jsx)(`p`,{className:`mt-1 text-meta`,children:`Empty.`}):(0,U.jsx)(`pre`,{className:`mt-1 max-h-96 overflow-auto rounded-md bg-surface-0/60 p-2 font-mono text-[11px] leading-relaxed`,children:e.patch.split(`
2
+ `).map((e,t)=>(0,U.jsx)(`span`,{className:`block whitespace-pre-wrap break-words ${ye(e)}`,children:e===``?` `:e},t))}),e.truncated?(0,U.jsx)(`p`,{className:`mt-1 text-meta`,children:`The rest of this file's change is cut.`}):null]})}function xe({name:e,change:t}){let n=f(e,t.commit,!0);return(0,U.jsxs)(`div`,{className:`space-y-3 px-4 pt-1 pb-3`,children:[(0,U.jsxs)(`p`,{className:`text-xs text-fg-secondary`,children:[t.kind===`merge`?`Two turns on different runners changed the same lines at once. The file kept one turn's version, and this copy holds the other's until the citizen reconciles them.`:t.kind===`board`?`Written by the board: ${t.subject.replace(/^home: /,``)}.`:t.turnId===void 0?t.subject:(0,U.jsx)(T,{to:`/citizen/$name`,params:{name:e},search:{tab:`turns`,turn:t.turnId},className:`text-fg-secondary underline decoration-line underline-offset-2 hover:text-fg-primary`,children:`Open the turn that made it`}),(0,U.jsxs)(`span`,{className:`text-fg-muted`,children:[` · `,W(t.at)]})]}),n.data===void 0?(0,U.jsx)(`p`,{className:`text-meta`,children:n.isError?`Could not read the change.`:`Reading the change…`}):n.data.map(e=>(0,U.jsx)(be,{file:e},e.path))]})}function Se({name:e,change:t,now:n}){let[r,i]=(0,H.useState)(!1),a=t.files.length===0?t.subject:ve(t);return(0,U.jsx)(`li`,{className:`border-b border-line/60 last:border-b-0`,children:(0,U.jsxs)(`details`,{className:`group`,onToggle:e=>i(e.currentTarget.open),children:[(0,U.jsx)(`summary`,{"aria-label":`${ge[t.kind]} ${y(t.at,n)}: ${a}`,className:`block cursor-pointer list-none px-4 py-2.5 transition-colors hover:bg-surface-2/30 group-open:bg-surface-2/20`,children:(0,U.jsxs)(`span`,{className:`flex items-center gap-2 text-xs`,children:[(0,U.jsx)(`span`,{className:`shrink-0 text-fg-muted`,children:he[t.kind]}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-fg-secondary`,children:a}),(0,U.jsx)(`span`,{className:`shrink-0 text-fg-muted`,children:y(t.at,n)}),(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`shrink-0 text-[10px] text-fg-muted transition-transform group-open:rotate-90`,children:`▸`})]})}),r?(0,U.jsx)(xe,{name:e,change:t}):null]})})}function Ce({name:e}){let[t,n]=(0,H.useState)(q),r=_(e,t),i=S(3e4);if(r.data===void 0)return(0,U.jsx)(I,{children:r.isError?`Could not read the home's history.`:`Reading the history…`});let{changes:a,more:o}=r.data;return a.length===0?(0,U.jsxs)(I,{children:[e,`'s home has no history yet; it starts with the citizen's first turn.`]}):(0,U.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[(0,U.jsxs)(`p`,{className:`px-4 pt-3 pb-1 text-meta`,children:[`What `,e,`'s turns changed in its memory, skills, and profile, newest first.`]}),(0,U.jsx)(`ol`,{children:a.map(t=>(0,U.jsx)(Se,{name:e,change:t,now:i},t.commit))}),o?(0,U.jsx)(`button`,{type:`button`,onClick:()=>n(t+q),className:`px-4 py-3 text-xs text-fg-tertiary hover:text-fg-primary`,children:`Show earlier changes`}):null]})}function J({title:e,children:t}){return(0,U.jsxs)(`section`,{className:`border-t border-line pt-4 first:border-t-0 first:pt-0`,children:[(0,U.jsx)(`h3`,{className:`text-section`,children:e}),(0,U.jsx)(`div`,{className:`mt-2`,children:t})]})}function we({member:e,charter:t}){let n=c(e.name),r=g(e.name),i=p(e.name),a=S(3e4);return(0,U.jsxs)(`div`,{className:`flex-1 space-y-5 overflow-y-auto px-4 py-4`,children:[i.data===void 0||i.data.length===0?null:(0,U.jsxs)(J,{title:`Edits to reconcile`,children:[(0,U.jsxs)(`p`,{className:`text-sm text-fg-secondary`,children:[`Two turns on different runners changed these files at once. Each file kept one version; the other waits beside it until `,e.name,` merges it in and deletes the copy.`]}),(0,U.jsx)(`ul`,{className:`mt-2 space-y-1.5`,children:i.data.map(e=>(0,U.jsxs)(`li`,{className:`text-sm`,children:[(0,U.jsx)(`span`,{className:`text-fg-primary`,children:e.file}),(0,U.jsxs)(`span`,{className:`text-meta`,children:[` `,`· `,e.path.slice(e.path.lastIndexOf(`/`)+1),` · left`,` `,y(e.since,a)]})]},e.path))})]}),(0,U.jsx)(J,{title:`Profile`,children:e.profile.trim()===``?(0,U.jsx)(`p`,{className:`text-meta`,children:`No profile yet. The citizen writes its own profile.md.`}):(0,U.jsx)(F,{text:B(e.profile,`Profile`)})}),(0,U.jsx)(J,{title:`Core memory`,children:n.data===void 0?(0,U.jsx)(`p`,{className:`text-meta`,children:`Reading…`}):n.data.body.trim()===``?(0,U.jsx)(`p`,{className:`text-meta`,children:`Empty. Every turn loads this file in full.`}):(0,U.jsx)(F,{text:B(n.data.body,`Core memory`)})}),(0,U.jsx)(J,{title:`Own skills`,children:r.data===void 0?(0,U.jsx)(`p`,{className:`text-meta`,children:`Reading…`}):r.data.length===0?(0,U.jsx)(`p`,{className:`text-meta`,children:`None yet.`}):(0,U.jsx)(`ul`,{className:`space-y-1.5`,children:r.data.map(e=>(0,U.jsxs)(`li`,{className:`text-sm`,children:[(0,U.jsx)(`span`,{className:`text-fg-primary`,children:e.name}),e.summary===``?null:(0,U.jsxs)(`span`,{className:`text-fg-secondary`,children:[` · `,e.summary]})]},e.name))})}),t===void 0?null:(0,U.jsxs)(J,{title:`Charter of ${t.name}`,children:[(0,U.jsx)(`p`,{className:`text-sm text-fg-secondary`,children:t.purpose}),(0,U.jsxs)(`p`,{className:`mt-2 text-meta`,children:[`Wakes on mentions, stages, and `,t.wakeTriggers.join(`, `)||`nothing else`,t.resident?` · keeps a warm session`:``,t.reflects?` · reflects`:``]}),(0,U.jsx)(`p`,{className:`mt-2 flex flex-wrap gap-1`,children:t.verbs.map(e=>(0,U.jsx)(`span`,{className:`rounded-md bg-surface-2/70 px-1.5 py-px font-mono text-[11px] text-fg-tertiary`,children:e},e))})]})]})}function Te(e,t){let n=r(e,t?.turnId??null);return{turn:(0,H.useMemo)(()=>t===void 0||n.data===void 0?void 0:te(n.data,e,t.project,t.thread),[t,n.data,e]),loading:t?.turnId!==void 0&&n.isPending,missing:t!==void 0&&(t.turnId===void 0||n.isError)}}var Y=50;function Ee({name:e,entry:t,now:n,opened:r}){let[i,a]=(0,H.useState)(r),o=(0,H.useRef)(null);(0,H.useEffect)(()=>{r&&o.current?.scrollIntoView({block:`start`})},[r]);let s=E(),c=ue(t),l=t.thread===void 0?null:s.get(t.thread)?.title??`a thread`,u=t.error??t.summary??``,d=t.error===null?`text-fg-secondary`:`text-red-300`;return(0,U.jsx)(`li`,{ref:o,className:`border-b border-line/60 last:border-b-0`,children:(0,U.jsxs)(`details`,{className:`group`,open:i,onToggle:e=>a(e.currentTarget.open),children:[(0,U.jsxs)(`summary`,{className:`block cursor-pointer list-none px-4 py-2.5 transition-colors hover:bg-surface-2/30 group-open:bg-surface-2/20`,children:[(0,U.jsxs)(`span`,{className:`flex items-center gap-2 text-xs`,children:[(0,U.jsxs)(`span`,{className:`min-w-0 truncate text-fg-secondary`,children:[z(t.project),l===null?``:` · ${l}`]}),(0,U.jsx)(`span`,{className:`text-fg-muted`,children:t.trigger}),(0,U.jsxs)(`span`,{className:`min-w-0 flex-1 truncate text-fg-muted`,children:[de(t),` `,y(t.ts,n)]}),(0,U.jsx)(`span`,{className:`shrink-0 font-mono text-[11px] text-fg-tertiary`,title:t.usage===void 0?void 0:R(t.usage),children:[c,t.toolCalls===void 0?null:`${t.toolCalls} tools`,t.usage===void 0?null:V(t.usage)].filter(e=>e!==null).join(` · `)}),(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`shrink-0 text-[10px] text-fg-muted transition-transform group-open:rotate-90`,children:`▸`})]}),u.trim()===``?null:(0,U.jsx)(`span`,{className:`mt-1 line-clamp-2 block text-xs leading-relaxed group-open:hidden ${d}`,children:(0,U.jsx)(N,{text:u,links:!1})})]}),i?(0,U.jsx)(De,{name:e,entry:t,text:u,tone:d}):null]})})}function De({name:e,entry:t,text:n,tone:r}){let i=Te(e,t);return(0,U.jsxs)(`div`,{className:`px-4 pt-1 pb-3`,children:[n.trim()===``?null:(0,U.jsx)(`p`,{className:`text-xs leading-relaxed whitespace-pre-wrap ${r}`,children:(0,U.jsx)(N,{text:n})}),(0,U.jsx)(`div`,{className:`mt-2`,children:t.turnId===void 0?(0,U.jsx)(`p`,{className:`text-meta`,children:`This turn ran before turns kept their steps.`}):i.loading?(0,U.jsx)(`p`,{className:`text-meta`,children:`Reading the steps…`}):i.turn===void 0?(0,U.jsx)(`p`,{className:`text-meta`,children:`No steps were kept for this turn.`}):(0,U.jsxs)(U.Fragment,{children:[t.startedAt===void 0?null:(0,U.jsxs)(`p`,{className:`text-caps`,children:[`Started `,W(t.startedAt)]}),(0,U.jsx)(K,{steps:i.turn.steps})]})})]})}function Oe({name:e,opened:t}){let[n,r]=(0,H.useState)(Y),a=i(e,n),o=S(3e4);if(a.data===void 0)return(0,U.jsx)(I,{children:`Reading the turns…`});let s=a.data.toReversed();if(s.length===0)return(0,U.jsxs)(I,{children:[e,` has not finished a turn yet.`]});let c=le(s),l=c.usage===null?``:` · ${V(c.usage)}${c.withUsage<c.turns?` over ${c.withUsage} of them`:``}`;return(0,U.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[(0,U.jsxs)(`p`,{className:`px-4 pt-3 pb-1 text-meta`,title:c.usage===null?void 0:R(c.usage),children:[c.turns===1?`1 turn`:`${c.turns} turns`,c.failed===0?``:`, ${c.failed} not completed`,l]}),t!==null&&!s.some(e=>e.turnId===t)?(0,U.jsx)(`p`,{className:`px-4 pb-1 text-meta`,children:a.data.length===n?`The turn this address opens is further back; show earlier turns to reach it.`:`The turn this address opens is not in this citizen's history.`}):null,(0,U.jsx)(`ol`,{children:s.map(n=>(0,U.jsx)(Ee,{name:e,entry:n,now:o,opened:t!==null&&n.turnId===t},n.id))}),a.data.length===n?(0,U.jsxs)(`button`,{type:`button`,onClick:()=>r(n+Y),className:`px-4 py-3 text-xs text-fg-tertiary hover:text-fg-primary`,children:[`Show `,Y,` earlier turns`]}):null]})}function ke(e){return e===void 0?``:`${e.name}${e.id===null?``:` · ${e.id}`}`}function X({label:e,choices:t,value:n,onChange:r,listClassName:i=`inset-x-0`}){let[a,o]=(0,H.useState)(!1),s=(0,H.useRef)(null),c=(0,H.useRef)(null),l=(0,H.useRef)([]),u=t.findIndex(e=>e.value===n);(0,H.useEffect)(()=>{if(!a)return;l.current[Math.max(u,0)]?.focus();let e=e=>{e.target instanceof Node&&s.current?.contains(e.target)===!0||o(!1)};return window.addEventListener(`pointerdown`,e),()=>window.removeEventListener(`pointerdown`,e)},[a,u]);let d=e=>{if(e.key===`Escape`){e.stopPropagation(),o(!1),c.current?.focus();return}if(e.key!==`ArrowDown`&&e.key!==`ArrowUp`)return;e.preventDefault();let n=(l.current.findIndex(e=>e===document.activeElement)+(e.key===`ArrowDown`?1:-1)+t.length)%t.length;l.current[n]?.focus()},f=t[u];return(0,U.jsxs)(`div`,{ref:s,className:`relative min-w-0 flex-1`,children:[(0,U.jsxs)(`button`,{ref:c,type:`button`,"aria-label":`${e}: ${ke(f)}`,"aria-expanded":a,onClick:()=>o(!a),onKeyDown:a?d:void 0,className:`flex h-7 w-full items-center gap-1.5 rounded-md bg-surface-2/60 px-2.5 text-xs text-fg-secondary transition-colors hover:bg-surface-2 focus-visible:ring-2 focus-visible:ring-fg-primary/30 focus-visible:outline-none`,children:[(0,U.jsxs)(`span`,{className:`min-w-0 flex-1 truncate text-left`,children:[f?.name,f?.id===null||f===void 0?null:(0,U.jsxs)(`span`,{className:`text-fg-tertiary`,children:[` · `,f.id]})]}),(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`text-[10px] text-fg-muted transition-transform ${a?`rotate-180`:``}`,children:`▾`})]}),a?(0,U.jsx)(`div`,{className:`absolute ${i} top-full z-20 mt-1 max-h-80 overflow-y-auto rounded-xl bg-surface-1/95 p-1 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08),0_16px_32px_-12px_rgba(0,0,0,0.8)] backdrop-blur-xl`,children:t.map((e,t)=>{let i=e.value===n;return(0,U.jsxs)(`button`,{ref:e=>{l.current[t]=e},type:`button`,"aria-current":i?`true`:void 0,onKeyDown:d,onClick:()=>{r(e.value),o(!1),c.current?.focus()},className:`flex w-full items-start gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors focus-visible:outline-none ${i?`bg-surface-2/80`:`hover:bg-surface-2/50 focus-visible:bg-surface-2/50`}`,children:[(0,U.jsxs)(`span`,{className:`min-w-0 flex-1`,children:[(0,U.jsxs)(`span`,{className:`block truncate text-xs text-fg-secondary`,children:[e.name,e.id===null?null:(0,U.jsxs)(`span`,{className:`text-fg-tertiary`,children:[` · `,e.id]})]}),e.description===``?null:(0,U.jsx)(`span`,{className:`block truncate text-[11px] text-fg-muted`,children:e.description})]}),(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`w-3 shrink-0 pt-px text-[11px] text-fg-primary`,children:i?`✓`:``})]},e.value)})}):null]})}var Z=``,Q=``;function Ae(e,t){return[{value:Z,name:`CLI default`,id:e.find(e=>e.isDefault)?.name??null,description:`Whatever the CLI runs when no model is set.`},...e.map(e=>({value:e.id,name:e.name,id:e.name===e.id?null:e.id,description:e.description})),...t===null?[]:[{value:t,name:t,id:null,description:`Not in the CLI's list.`}]]}function je(e,t){return[{value:Q,name:`Default`,id:e?.defaultEffort??null,description:`Whatever the model runs unset.`},...(e?.efforts??[]).map(e=>({value:e.id,name:e.id,id:null,description:e.description})),...t===null?[]:[{value:t,name:t,id:null,description:`Not in the model's list.`}]]}function Me({member:t,onDone:n}){let{api:r}=e(),i=u(),a=l(t.name),s=t.model??Z,c=t.effort??Q,[d,f]=(0,H.useState)(s),[p,m]=(0,H.useState)(c),h=w({mutationFn:async()=>{d!==s&&await r.setModel(t.name,d===Z?null:d),p!==c&&await r.setEffort(t.name,p===Q?null:p)},onSettled:()=>void i.invalidateQueries({queryKey:[`members`]}),onSuccess:n}),g=a.data?.models??[],_=t.model!==void 0&&!g.some(e=>e.id===t.model)?t.model:null,v=e=>g.find(t=>e===Z?t.isDefault:t.id===e),y=v(d)?.efforts??[],b=p!==Q&&!y.some(e=>e.id===p)?p:null,x=e=>{f(e);let t=v(e)?.efforts??[];p!==Q&&!t.some(e=>e.id===p)&&m(Q)},S=d!==s||p!==c;return(0,U.jsxs)(`form`,{"aria-label":`Model of ${t.name}`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),h.mutate()},children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[a.isPending?(0,U.jsxs)(`p`,{className:`min-w-0 flex-1 truncate text-meta`,children:[`Asking `,t.cli,` for its models…`]}):a.error===null?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(X,{label:`Model`,choices:Ae(g,_),value:d,onChange:x}),y.length===0&&b===null?null:(0,U.jsx)(`div`,{className:`flex w-32 shrink-0`,children:(0,U.jsx)(X,{label:`Effort`,choices:je(v(d),b),value:p,onChange:m,listClassName:`right-0 w-64`})})]}):(0,U.jsxs)(`p`,{role:`alert`,className:`min-w-0 flex-1 truncate text-xs text-red-400`,children:[t.cli,` did not list its models:`,` `,a.error instanceof o?a.error.message:`the board server did not answer`]}),(0,U.jsx)(O,{onClick:n,children:`Cancel`}),(0,U.jsx)(O,{variant:`primary`,type:`submit`,disabled:h.isPending||!S||a.error!==null,children:h.isPending?`Saving…`:`Save`})]}),a.data===void 0?null:(0,U.jsxs)(`p`,{className:`text-meta`,children:[`As `,a.data.cli,` on `,a.data.runner,` lists them, where this citizen's turns run.`]}),(0,U.jsx)(L,{error:h.error})]})}var $=``;function Ne(e,t){let n=t.cli;return[e.status,e.clis.length===0?`offers no CLI yet`:e.clis.join(`, `),n!==null&&e.clis.length>0&&!e.clis.includes(n)?`has no ${n}`:``,e.capabilities.length===0?``:e.capabilities.join(`, `)].filter(e=>e!==``).join(` · `)}function Pe(e,t){return[{value:$,name:`Any runner`,id:null,description:`Pinned to the least busy runner on its next turn outside a project.`},...e.map(e=>({value:e.name,name:e.name,id:e.os,description:Ne(e,t)}))]}function Fe({member:t,onDone:n}){let{api:r}=e(),i=u(),a=s(),o=t.homeRunner??$,[c,l]=(0,H.useState)(o),d=w({mutationFn:()=>r.setRunner(t.name,c===$?null:c),onSuccess:()=>{i.invalidateQueries({queryKey:[`members`]}),n()}});return(0,U.jsxs)(`form`,{"aria-label":`Runner of ${t.name}`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),d.mutate()},children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[a.data===void 0?(0,U.jsx)(`p`,{className:`min-w-0 flex-1 truncate text-meta`,children:`Reading the runners…`}):(0,U.jsx)(X,{label:`Runner`,choices:Pe(a.data,t),value:c,onChange:l}),(0,U.jsx)(O,{onClick:n,children:`Cancel`}),(0,U.jsx)(O,{variant:`primary`,type:`submit`,disabled:d.isPending||c===o,children:d.isPending?`Saving…`:`Run here`})]}),(0,U.jsx)(`p`,{className:`text-meta`,children:`Its conversations outside projects start afresh there from its next turn; its memory and skills follow it. Project work runs where each project lives.`}),(0,U.jsx)(L,{error:d.error})]})}function Ie(e){return e.thread===void 0?{channel:e.channel??(e.scope===`society`?`general`:`${e.scope}/general`)}:{threadId:e.thread}}function Le({name:t,turn:n}){let{api:r}=e(),i=u(),[a,o]=(0,H.useState)(!1),s=w({mutationFn:()=>r.stopTurn(n.turnId),onSuccess:()=>{o(!1),i.invalidateQueries({queryKey:[`scheduler`]})}});return(0,U.jsxs)(`div`,{className:`border-t border-line`,children:[(0,U.jsx)(ie,{target:Ie(n),initialText:`@${t} `,placeholder:n.steerable?`Message ${t} while it works`:`Message ${t} for its next turn`,className:`px-3 pt-3 pb-2`},n.turnId),n.stoppable?(0,U.jsxs)(`div`,{className:`flex items-center gap-2 px-3 pb-3`,children:[(0,U.jsx)(`p`,{className:`min-w-0 flex-1 text-[11px] leading-snug text-fg-muted`,children:a?`Ends the turn now. What it was shown counts as read; its stages stay held.`:n.steerable?`Posts reach the turn at its next step.`:`This runner delivers posts after the turn ends.`}),a?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(O,{onClick:()=>{o(!1),s.reset()},children:`Keep it running`}),(0,U.jsx)(O,{variant:`primary`,disabled:s.isPending,onClick:()=>{s.mutate()},children:s.isPending?`Stopping…`:`Stop the turn`})]}):(0,U.jsx)(O,{onClick:()=>o(!0),children:`Stop…`})]}):null,s.error===null?null:(0,U.jsx)(`div`,{className:`px-3 pb-3`,children:(0,U.jsx)(L,{error:s.error})})]})}function Re({member:t,scopes:n,preferred:r,running:i,paused:a,onDone:o}){let{api:s}=e(),c=u(),[l,d]=(0,H.useState)(`manual`),[f,p]=(0,H.useState)(r!==null&&n.includes(r)?r:n[0]??``),[m,h]=(0,H.useState)(``),g=(0,H.useRef)(null);(0,H.useEffect)(()=>g.current?.focus(),[]);let _=w({mutationFn:()=>s.wake({agent:t.name,project:f,kind:l,...m.trim()===``?{}:{reason:m.trim()}}),onSuccess:()=>{c.invalidateQueries({queryKey:[`scheduler`]}),o()}}),v=z(f),y=l===`reflection`?`Asks ${t.name} to reflect at ${v}: consolidate its memory, skills, and profile, and take no new work. It restarts the reflection clock.`:`Starts a turn for ${t.name} at ${v} as soon as the scheduler takes it.`,b=a?` The society is paused, so it waits until you resume.`:i.includes(f)?` ${t.name} is in a turn there now; this one follows it.`:``;return(0,U.jsxs)(`form`,{"aria-label":`Wake ${t.name}`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),_.mutate()},children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsxs)(`fieldset`,{className:`flex gap-1`,children:[(0,U.jsx)(`legend`,{className:`sr-only`,children:`Kind of turn`}),[`manual`,`reflection`].map(e=>(0,U.jsxs)(`label`,{className:`cursor-pointer rounded-md px-2.5 py-1 text-xs transition-colors has-focus-visible:ring-2 has-focus-visible:ring-fg-primary/30 ${l===e?`bg-surface-2 text-fg-primary`:`text-fg-tertiary hover:bg-surface-2/60 hover:text-fg-primary`}`,children:[(0,U.jsx)(`input`,{type:`radio`,name:`kind`,value:e,checked:l===e,onChange:()=>d(e),className:`sr-only`}),e===`manual`?`A turn`:`A reflection`]},e))]}),(0,U.jsxs)(`label`,{className:`ml-auto flex items-center gap-2 text-xs text-fg-tertiary`,children:[`at`,(0,U.jsx)(`select`,{value:f,onChange:e=>p(e.target.value),"aria-label":`Where`,className:`rounded-md bg-surface-2/60 px-2 py-1 text-xs text-fg-primary outline-none`,children:n.map(e=>(0,U.jsx)(`option`,{value:e,children:z(e)},e))})]})]}),(0,U.jsx)(`textarea`,{ref:g,value:m,rows:2,placeholder:l===`reflection`?`Anything to reflect on in particular? Optional.`:`What should it look at? It reads this as the reason it was woken.`,"aria-label":`Reason`,onChange:e=>h(e.target.value),className:`${oe} resize-none`}),(0,U.jsxs)(`p`,{className:`text-[11px] leading-relaxed text-fg-muted`,children:[y,b,` Each turn costs money.`]}),(0,U.jsxs)(`div`,{className:`flex items-center justify-end gap-2`,children:[(0,U.jsx)(O,{onClick:o,children:`Cancel`}),(0,U.jsx)(O,{variant:`primary`,type:`submit`,disabled:_.isPending||f===``,children:_.isPending?`Waking…`:l===`reflection`?`Ask for a reflection`:`Wake ${t.name}`})]}),(0,U.jsx)(L,{error:_.error})]})}var ze={working:`bg-emerald-500/15 text-emerald-300`,queued:`bg-amber-500/15 text-amber-300`,idle:`bg-surface-2 text-fg-tertiary`};function Be({project:e,name:t}){let n=(a(e).data??[]).filter(e=>e.status===`claimed`&&e.claimedBy===t);return n.length===0?null:(0,U.jsx)(`ul`,{className:`mt-1.5 space-y-1`,children:n.map(e=>(0,U.jsx)(`li`,{children:(0,U.jsxs)(T,{to:`/task/$taskId`,params:{taskId:e.id},className:`flex items-center gap-2 rounded-md text-xs text-fg-secondary hover:text-fg-primary`,children:[(0,U.jsx)(`span`,{"aria-hidden":`true`,className:`text-fg-muted`,children:`◇`}),(0,U.jsx)(`span`,{className:`min-w-0 flex-1 truncate`,children:e.title}),(0,U.jsx)(`span`,{className:`max-w-[40%] shrink-0 truncate font-mono text-[11px] text-fg-muted`,children:d(e)?.name??e.stage})]})},e.id))})}function Ve(){let{name:e}=ee({from:`/citizen/$name`}),{scope:t,tab:n,turn:r}=C({from:`/citizen/$name`});return(0,U.jsx)(Ue,{name:e,chosen:t??null,tab:n??`now`,turn:r??null},e)}var He=[{tab:`now`,label:`Now`},{tab:`turns`,label:`Turns`},{tab:`memory`,label:`Memory`},{tab:`history`,label:`History`}];function Ue({name:e,chosen:r,tab:i,turn:a}){let o=m(),s=x(),c=t(),l=P(),[u,d]=(0,H.useState)(null),f=o.data?.find(t=>t.name===e);if(f===void 0)return(0,U.jsx)(I,{children:o.data===void 0?`Reading the roster…`:`There is no citizen ${e}.`});let p=n(c.data?.running??[]).filter(t=>t.agent===e),h=[...new Set(p.map(e=>e.scope))],g=p.map(e=>A(e.agent,e.scope,e.thread)),_=n(c.data?.pending??[]).find(t=>t.agent===e),v=h.length>0?`working`:_===void 0?`idle`:`queued`,y=M(l,e)[0],b=y?.model??f.lastModel,S=b??f.model??`CLI default`,C=f.model!==void 0&&f.model!==b?f.model:null,w=s.data?.find(e=>e.name===f.role),E=ce(f),ee=f.status===`active`&&f.cli!==null;return(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(ae,{leading:f.cli===null?null:(0,U.jsx)(`span`,{className:`grid size-9 shrink-0 place-items-center rounded-lg bg-surface-2/70`,children:(0,U.jsx)(D,{cli:f.cli,size:20})}),title:e,subtitle:(0,U.jsxs)(U.Fragment,{children:[f.role,` · `,S,C===null?``:` · set to ${C}`,f.effort===void 0?``:` · ${f.effort} effort`,f.homeRunner===void 0?``:` · on ${f.homeRunner}`,f.status===`retired`?` · retired`:``]}),trailing:(0,U.jsxs)(U.Fragment,{children:[ee&&u===null?(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(O,{onClick:()=>d(`model`),children:`Model…`}),(0,U.jsx)(O,{onClick:()=>d(`runner`),children:`Runner…`}),(0,U.jsx)(O,{onClick:()=>d(`wake`),children:`Wake…`})]}):null,(0,U.jsx)(`span`,{className:`shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium ${ze[v]}`,children:v})]})}),u===`wake`?(0,U.jsx)(Re,{member:f,scopes:E,preferred:r?.split(`/`)[0]??y?.scope??null,running:h,paused:c.data?.paused??!1,onDone:()=>d(null)}):u===`model`?(0,U.jsx)(Me,{member:f,onDone:()=>d(null)}):u===`runner`?(0,U.jsx)(Fe,{member:f,onDone:()=>d(null)}):null,(0,U.jsx)(`nav`,{"aria-label":`Citizen views`,className:`flex gap-1 border-b border-line px-3 py-2`,children:He.map(t=>(0,U.jsx)(T,{to:`/citizen/$name`,params:{name:e},search:{...r===null?{}:{scope:r},...t.tab===`now`?{}:{tab:t.tab}},replace:!0,"aria-current":t.tab===i?`page`:void 0,className:`rounded-md px-2.5 py-1 text-xs transition-colors ${t.tab===i?`bg-surface-2 text-fg-primary`:`text-fg-tertiary hover:bg-surface-2/60 hover:text-fg-primary`}`,children:t.label},t.tab))}),i===`turns`?(0,U.jsx)(Oe,{name:e,opened:a},a):i===`memory`?(0,U.jsx)(we,{member:f,charter:w}):i===`history`?(0,U.jsx)(Ce,{name:e}):(0,U.jsx)(Ge,{name:e,chosen:r,lastTurnAt:f.lastTurnAt,state:v,runningScopes:h,runningKeys:g,runningTurns:(c.data?.turns??[]).filter(t=>t.agent===e),queuedScope:_?.scope??null})]})}function We(e){return e.toSorted((e,t)=>e.startedAt.localeCompare(t.startedAt))[0]}function Ge({name:e,chosen:t,lastTurnAt:n,state:r,runningScopes:a,runningKeys:o,runningTurns:s,queuedScope:c}){let l=P(),u=S(5e3),{ref:d,onScroll:f}=se(),p=M(l,e),m=i(e,1),h=Te(e,p.length===0?m.data?.at(-1):void 0),g=E(),_=e=>e.end===null&&o.includes(A(e.agent,e.scope,e.thread)),v=t?.split(`/`)[0]??null,b=(t?.includes(`/`)===!0?p.find(e=>k(e)===t):void 0)??We(p.filter(e=>e.scope===v&&_(e)))??p.find(e=>k(e)===t)??We(p.filter(_))??p[0]??h.turn,x=e=>e.thread===void 0?z(e.scope):`${z(e.scope)} · ${g.get(e.thread)?.title??`a thread`}`,C=b!==void 0&&_(b),w=b?.scope??a[0]??c,D=(b===void 0?void 0:s.find(e=>e.scope===b.scope&&e.thread===b.thread))??(C?void 0:s[0]);return(0,U.jsxs)(U.Fragment,{children:[(0,U.jsxs)(`section`,{className:`border-b border-line px-4 py-3 text-xs text-fg-tertiary`,children:[(0,U.jsx)(`p`,{children:C?`In a turn at ${z(b.scope)} for ${b.fromStart?``:`at least `}${j(b.startedAt,u)}`:a.length>0?`In a turn at ${a.map(z).join(` and `)}`:c===null?n===void 0?`No turns yet`:`Last turn ${y(n,u)}`:`Queued for a turn at ${z(c)}`}),w===null||w===`society`?null:(0,U.jsx)(Be,{project:w,name:e})]}),(0,U.jsx)(`div`,{ref:d,onScroll:f,className:`flex-1 overflow-y-auto px-4 py-3`,children:b===void 0?(0,U.jsx)(I,{children:r===`working`?`The turn has not reported a step yet.`:h.loading?`Reading the last turn…`:n===void 0?`${e} has not taken a turn yet.`:`${e}'s last turn kept no steps; Turns lists every finished one.`}):(0,U.jsxs)(U.Fragment,{children:[p.length>1?(0,U.jsx)(`div`,{className:`mb-3 flex flex-wrap gap-1.5`,"aria-label":`Turns by conversation`,children:p.toSorted((e,t)=>k(e).localeCompare(k(t))).map(t=>(0,U.jsxs)(T,{to:`/citizen/$name`,params:{name:e},search:{scope:k(t)},replace:!0,"aria-current":t===b?`true`:void 0,className:`inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-[11px] transition-colors ${t===b?`bg-surface-2 text-fg-primary`:`text-fg-tertiary hover:bg-surface-2/60 hover:text-fg-primary`}`,children:[_(t)?(0,U.jsx)(`span`,{className:`size-1.5 animate-pulse rounded-full bg-emerald-400`}):null,x(t)]},k(t)))}):null,(0,U.jsxs)(`p`,{className:`text-caps`,children:[C?`This turn`:`Last turn`,` · `,x(b),` · started`,` `,b.fromStart?W(b.startedAt):`before the earliest step shown`]}),b.fromStart?null:(0,U.jsxs)(`p`,{className:`mt-1 text-meta`,children:[`Earlier steps of this turn are no longer in the server's live buffer`,C?``:`; Turns has the whole turn`,`.`]}),(0,U.jsx)(K,{steps:b.steps}),(0,U.jsx)(me,{turn:b,running:C,now:u})]})}),D===void 0?null:(0,U.jsx)(Le,{name:e,turn:D})]})}export{Ve as CitizenView};
@@ -0,0 +1 @@
1
+ import{E as e,H as t,b as n,g as r,ln as i}from"./format-C7bqWt6N.js";import{R as a,T as o}from"./index-DM00hlle.js";import{n as s,t as c}from"./Pane-voa6GU0F.js";import{i as l,r as u}from"./citizen-DvJr6Ibu.js";var d=i();function f(e){let t=e.lastModel??e.model??`CLI default`,n=e.model!==void 0&&e.model!==t?`${t} · set to ${e.model}`:t;return e.effort===void 0?n:`${n} · ${e.effort}`}function p(e,t){return e.name.localeCompare(t.name)}function m(e,t){return e.role.localeCompare(t.role)||p(e,t)}function h(){let i=r(),h=e(),g=n();if(i.data===void 0)return(0,d.jsx)(s,{children:`Reading the roster…`});let _=i.data.filter(e=>e.cli!==null),v=_.filter(e=>e.status===`active`).toSorted(m),y=_.filter(e=>e.status!==`active`).toSorted(p),b=new Set(v.map(e=>e.role)).size,x=t(h.data?.running??[]),S=t(h.data?.pending??[]),C=new Map((g.data??[]).flatMap(e=>e.runner===void 0?[]:[[e.slug,e.runner]])),w=e=>{let t=[...new Set(x.filter(t=>t.agent===e.name).map(e=>e.scope))];return t.length>0?{label:`working`,detail:`in a turn at ${t.map(l).join(` and `)}`,tone:`text-emerald-300`}:S.some(t=>t.agent===e.name)?{label:`queued`,detail:`queued for a turn`,tone:`text-amber-300`}:{label:`resting`,detail:`resting`,tone:`text-fg-muted`}};return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(c,{title:`Citizens`,subtitle:`${v.length} active in ${b===1?`1 role`:`${b} roles`}${y.length===0?``:` · ${y.length} retired`}`}),(0,d.jsxs)(`div`,{className:`flex-1 overflow-y-auto px-2 py-2`,children:[v.length===0?(0,d.jsx)(`p`,{className:`px-2 py-2 text-meta`,children:`No citizens yet. Ask the concierge for one.`}):null,(0,d.jsx)(`ul`,{className:`grid grid-cols-[auto_auto_auto_minmax(0,max-content)_auto_minmax(3rem,1fr)_auto] gap-x-3`,children:v.map(e=>{let t=w(e),n=f(e),r=e.memberships.length===0?`no projects`:e.memberships.join(`, `),i=u(e,C);return(0,d.jsx)(`li`,{className:`col-span-full grid grid-cols-subgrid`,children:(0,d.jsxs)(a,{to:`/citizen/$name`,params:{name:e.name},className:`col-span-full grid grid-cols-subgrid items-center rounded-lg px-2 py-1.5 text-sm transition-colors hover:bg-surface-2/50`,children:[(0,d.jsx)(`span`,{className:`grid size-4 place-items-center`,children:e.cli===null?null:(0,d.jsx)(o,{cli:e.cli,size:13})}),(0,d.jsx)(`span`,{className:`text-fg-primary`,children:e.name}),(0,d.jsx)(`span`,{className:`text-fg-secondary`,children:e.role}),(0,d.jsx)(`span`,{className:`truncate text-xs text-fg-muted`,title:n,children:n}),(0,d.jsx)(`span`,{className:`text-xs text-fg-muted`,title:i.detail,children:i.label}),(0,d.jsx)(`span`,{className:`truncate text-xs text-fg-muted`,title:r,children:r}),(0,d.jsx)(`span`,{className:`text-right text-xs ${t.tone}`,title:t.detail,children:t.label})]})},e.name)})}),y.length===0?null:(0,d.jsxs)(`p`,{className:`mt-2 border-t border-line px-2 pt-2 text-meta`,children:[`Retired: `,y.map(e=>`${e.name} (${e.role})`).join(`, `)]})]})]})}export{h as CitizensView};
@@ -0,0 +1 @@
1
+ import{D as e,b as t,cn as n,ln as r,m as i,ot as a,pn as o,t as s,un as c,y as l}from"./format-C7bqWt6N.js";import{H as u,R as d,V as f,c as p,z as m}from"./index-DM00hlle.js";import{t as h}from"./Markdown-CS3NCze-.js";import{n as g,t as _}from"./Pane-voa6GU0F.js";import{r as v}from"./ThreadForms-0LpUc2jJ.js";import{t as y}from"./BackLink-BW0sb-M0.js";var b=o(c(),1),x=r(),S=`grid size-7 shrink-0 place-items-center rounded-lg text-fg-tertiary hover:bg-surface-2/70 hover:text-fg-primary`;function C(){let{scope:r,topic:o}=f({from:`/knowledge/$scope/$topic`}),{api:c}=e(),C=n(),w=m(),T=i(r),E=t(),D=l(3e4),[O,k]=(0,b.useState)(!1),A=r===a,j=A?`the society`:E.data?.find(e=>e.slug===r)?.name??r,M=T.data?.find(e=>e.topic===o),N=u({mutationFn:()=>c.removeKnowledge({project:A?null:r,topic:o}),onSuccess:async()=>{await C.invalidateQueries({queryKey:[`knowledge`,r]}),await w(A?{to:`/society`}:{to:`/p/$slug`,params:{slug:r}})}}),P=A?(0,x.jsx)(d,{to:`/society`,"aria-label":`Back to the society`,className:S,children:`←`}):(0,x.jsx)(d,{to:`/p/$slug`,params:{slug:r},"aria-label":`Back to ${j}`,className:S,children:`←`});return M===void 0?(0,x.jsx)(g,{children:T.data===void 0?`Reading…`:`${j} has no topic ${o}.`}):(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)(_,{leading:(0,x.jsx)(y,{fallback:P}),title:M.topic,subtitle:`Knowledge of ${j} · written by ${M.updatedBy} ${s(M.updatedAt,D)}`,trailing:O?null:(0,x.jsx)(p,{onClick:()=>k(!0),children:`Remove…`})}),O?(0,x.jsxs)(`form`,{"aria-label":`Remove the topic`,className:`space-y-2 border-b border-line bg-surface-2/20 p-3`,onSubmit:e=>{e.preventDefault(),N.mutate()},children:[(0,x.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,x.jsxs)(`p`,{className:`min-w-0 flex-1 text-meta`,children:[`No turn or search finds `,M.topic,` again, and a note in`,` `,A?`#general`:`#${r}/general`,` says so without waking anyone. The server keeps its text aside.`]}),(0,x.jsx)(p,{onClick:()=>k(!1),children:`Cancel`}),(0,x.jsx)(p,{variant:`primary`,type:`submit`,disabled:N.isPending,children:N.isPending?`Removing…`:`Remove topic`})]}),(0,x.jsx)(v,{error:N.error})]}):null,(0,x.jsx)(`div`,{className:`flex-1 overflow-y-auto px-4 py-4`,children:(0,x.jsx)(h,{text:M.body})})]})}export{C as KnowledgeView};