chapterhouse 0.6.0 → 0.7.0

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.
@@ -382,7 +382,10 @@ async function loadOrchestratorModule(t, overrides = {}) {
382
382
  });
383
383
  t.mock.module("./mcp-config.js", {
384
384
  namedExports: {
385
- loadMcpConfig: () => ({ filesystem: { command: "filesystem" } }),
385
+ loadMcpConfig: () => ({
386
+ filesystem: { command: "filesystem" },
387
+ truenas: { command: "truenas" },
388
+ }),
386
389
  },
387
390
  });
388
391
  t.mock.module("./skills.js", {
@@ -513,6 +516,13 @@ async function loadOrchestratorModule(t, overrides = {}) {
513
516
  return "@coder @designer";
514
517
  },
515
518
  composeAgentSystemMessage: (agent) => agent.systemMessage ?? `You are ${agent.slug}.`,
519
+ filterMcpServersForAgent: (agent, mcpServers) => {
520
+ if (agent.mcpServers && agent.mcpServers.length > 0) {
521
+ const allowed = new Set(agent.mcpServers);
522
+ return Object.fromEntries(Object.entries(mcpServers).filter(([name]) => allowed.has(name)));
523
+ }
524
+ return mcpServers;
525
+ },
516
526
  filterToolsForAgent: (_agent, tools) => tools,
517
527
  bindToolsToAgent: (_agentSlug, tools) => tools,
518
528
  withToolTaskContext: (_taskId, fn) => fn(),
@@ -728,6 +738,28 @@ test("initOrchestrator prewarms persistent agent sessions with scoped hot-tier c
728
738
  assert.equal(persistentCall.systemMessage.content.includes("scope=\"infra\""), true);
729
739
  assert.deepEqual(state.dbWrites.filter((write) => write.sql.includes("copilot_sessions")), []);
730
740
  });
741
+ test("initOrchestrator prewarms persistent agent sessions with only allowed MCP servers", async (t) => {
742
+ const { orchestrator, state, client } = await loadOrchestratorModule(t, {
743
+ registry: [
744
+ { slug: "coder", name: "Kaylee", model: "claude-sonnet-4.6", systemMessage: "You are Kaylee." },
745
+ {
746
+ slug: "bellonda",
747
+ name: "Bellonda",
748
+ model: "claude-sonnet-4.6",
749
+ systemMessage: "You are Bellonda.",
750
+ persistent: true,
751
+ scope: "infra",
752
+ mcpServers: ["truenas"],
753
+ },
754
+ ],
755
+ });
756
+ await orchestrator.initOrchestrator(client);
757
+ const persistentCall = state.createSessionCalls.find((call) => String(call.systemMessage?.content ?? "").includes("Bellonda"));
758
+ assert.ok(persistentCall, "expected a prewarmed Bellonda session");
759
+ assert.deepEqual(persistentCall.mcpServers, {
760
+ truenas: { command: "truenas" },
761
+ });
762
+ });
731
763
  test("sendToOrchestrator routes agent session keys directly to persistent agent sessions", async (t) => {
732
764
  const { orchestrator, state, client } = await loadOrchestratorModule(t, {
733
765
  config: {
@@ -1517,6 +1549,100 @@ test("cancelCurrentMessage aborts the active request and agent helpers expose ru
1517
1549
  await orchestrator.shutdownAgents();
1518
1550
  assert.equal(state.clearActiveTasksCalls, 1);
1519
1551
  });
1552
+ test("persistent agent helpers report session state and reload idle sessions", async (t) => {
1553
+ const { orchestrator, state, client } = await loadOrchestratorModule(t, {
1554
+ config: {
1555
+ copilotModel: "claude-sonnet-4.6",
1556
+ selfEditEnabled: true,
1557
+ },
1558
+ registry: [
1559
+ {
1560
+ slug: "bellonda",
1561
+ name: "Bellonda",
1562
+ model: "claude-sonnet-4.6",
1563
+ systemMessage: "You are Bellonda.",
1564
+ persistent: true,
1565
+ scope: "infra",
1566
+ },
1567
+ ],
1568
+ });
1569
+ await orchestrator.initOrchestrator(client);
1570
+ const createCallsBeforeReload = state.createSessionCalls.length;
1571
+ assert.equal(orchestrator.getPersistentAgentSessionState("bellonda"), "idle");
1572
+ const reloaded = await orchestrator.reloadPersistentAgent("bellonda");
1573
+ assert.equal(reloaded, "reloaded");
1574
+ assert.equal(orchestrator.getPersistentAgentSessionState("bellonda"), "idle");
1575
+ assert.equal(state.disconnectCalls, 1);
1576
+ assert.equal(state.createSessionCalls.length, createCallsBeforeReload + 1);
1577
+ });
1578
+ test("persistent agent helpers detect in-flight turns", async (t) => {
1579
+ const { orchestrator, state, client } = await loadOrchestratorModule(t, {
1580
+ config: {
1581
+ copilotModel: "claude-sonnet-4.6",
1582
+ selfEditEnabled: true,
1583
+ },
1584
+ registry: [
1585
+ {
1586
+ slug: "bellonda",
1587
+ name: "Bellonda",
1588
+ model: "claude-sonnet-4.6",
1589
+ systemMessage: "You are Bellonda.",
1590
+ persistent: true,
1591
+ scope: "infra",
1592
+ },
1593
+ ],
1594
+ sendResult: "__PENDING__",
1595
+ });
1596
+ await orchestrator.initOrchestrator(client);
1597
+ orchestrator.sendToOrchestrator("check deploy health", { type: "background", sessionKey: "agent:bellonda" }, () => { });
1598
+ await new Promise((resolve) => setTimeout(resolve, 10));
1599
+ assert.equal(orchestrator.getPersistentAgentSessionState("bellonda"), "in_flight");
1600
+ state.pendingReject?.(new Error("test teardown"));
1601
+ await new Promise((resolve) => setTimeout(resolve, 0));
1602
+ });
1603
+ test("reloadPersistentAgent defers reloads until the in-flight turn finishes and preserves queued turns", async (t) => {
1604
+ const { orchestrator, state, client } = await loadOrchestratorModule(t, {
1605
+ config: {
1606
+ copilotModel: "claude-sonnet-4.6",
1607
+ selfEditEnabled: true,
1608
+ },
1609
+ registry: [
1610
+ {
1611
+ slug: "bellonda",
1612
+ name: "Bellonda",
1613
+ model: "claude-sonnet-4.6",
1614
+ systemMessage: "You are Bellonda.",
1615
+ persistent: true,
1616
+ scope: "infra",
1617
+ },
1618
+ ],
1619
+ sendResult: "__PENDING__",
1620
+ });
1621
+ await orchestrator.initOrchestrator(client);
1622
+ const createCallsBeforeReload = state.createSessionCalls.length;
1623
+ const reloadEvents = [];
1624
+ orchestrator.sendToOrchestrator("check deploy health", { type: "background", sessionKey: "agent:bellonda" }, () => { });
1625
+ await new Promise((resolve) => setTimeout(resolve, 10));
1626
+ const queuedTurn = new Promise((resolve) => {
1627
+ orchestrator.sendToOrchestrator("summarize the queued follow-up", { type: "background", sessionKey: "agent:bellonda" }, (text, done) => {
1628
+ if (done)
1629
+ resolve(text);
1630
+ });
1631
+ });
1632
+ await new Promise((resolve) => setTimeout(resolve, 10));
1633
+ const reloadResult = await orchestrator.reloadPersistentAgent("bellonda", () => {
1634
+ reloadEvents.push("reloaded");
1635
+ });
1636
+ assert.equal(reloadResult, "scheduled");
1637
+ assert.equal(state.disconnectCalls, 0, "deferred reload should wait for the active turn to finish");
1638
+ state.sendResult = "Queued follow-up complete";
1639
+ state.pendingReject?.(new Error("forced restart"));
1640
+ assert.equal(await queuedTurn, "Queued follow-up complete");
1641
+ await new Promise((resolve) => setTimeout(resolve, 0));
1642
+ assert.deepEqual(reloadEvents, ["reloaded"]);
1643
+ assert.equal(state.disconnectCalls, 1, "deferred reload should restart the session once the turn finishes");
1644
+ assert.equal(state.createSessionCalls.length, createCallsBeforeReload + 1);
1645
+ });
1520
1646
  // ---------------------------------------------------------------------------
1521
1647
  // REGRESSION: #35 — per-session isolation
1522
1648
  // This test would have caught the original bug. With a global shared queue,
@@ -60,6 +60,9 @@ export class SessionManager {
60
60
  * never-evict-mid-turn invariant. */
61
61
  _pendingClose = false;
62
62
  _onPendingCloseEvict;
63
+ /** Set when a persistent session should be recreated before the next queued turn runs. */
64
+ _pendingReload = false;
65
+ _onPendingReload = [];
63
66
  constructor(sessionKey, worker, sessionFactory) {
64
67
  this.worker = worker;
65
68
  this.sessionFactory = sessionFactory;
@@ -178,6 +181,19 @@ export class SessionManager {
178
181
  item.reject(err);
179
182
  }
180
183
  this._lastActivityAt = Date.now();
184
+ if (this._pendingReload) {
185
+ await this.restartSession();
186
+ const callbacks = this._onPendingReload.splice(0);
187
+ this._pendingReload = false;
188
+ for (const callback of callbacks) {
189
+ try {
190
+ callback();
191
+ }
192
+ catch (err) {
193
+ log.warn({ sessionKey: this.sessionKey, err: err instanceof Error ? err.message : String(err) }, "session.reload.callback.failed");
194
+ }
195
+ }
196
+ }
181
197
  }
182
198
  this._currentTurnId = undefined;
183
199
  this._processing = false;
@@ -213,6 +229,24 @@ export class SessionManager {
213
229
  this._session = undefined;
214
230
  this._sessionCreatePromise = undefined;
215
231
  }
232
+ async restartSession() {
233
+ if (this._session) {
234
+ try {
235
+ await this._session.disconnect();
236
+ }
237
+ catch {
238
+ // best effort
239
+ }
240
+ }
241
+ this.invalidateSession();
242
+ await this.ensureSession();
243
+ }
244
+ requestSessionReload(onReloaded) {
245
+ this._pendingReload = true;
246
+ if (onReloaded) {
247
+ this._onPendingReload.push(onReloaded);
248
+ }
249
+ }
216
250
  /** Reject all queued messages without evicting the session. Returns count drained. */
217
251
  cancelQueued() {
218
252
  const count = this._queue.length;
@@ -20,7 +20,8 @@ const RUNTIME_OVERRIDE_ENV_VARS = [
20
20
  "CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST",
21
21
  "CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS",
22
22
  ];
23
- for (const name of RUNTIME_OVERRIDE_ENV_VARS) {
23
+ const AUTH_ENV_VARS = ["COPILOT_TOKEN", "GITHUB_TOKEN"];
24
+ for (const name of [...RUNTIME_OVERRIDE_ENV_VARS, ...AUTH_ENV_VARS]) {
24
25
  delete process.env[name];
25
26
  }
26
27
  process.env.CHAPTERHOUSE_DISABLE_DOTENV = "1";
@@ -8,8 +8,10 @@ const RUNTIME_OVERRIDE_ENV_VARS = [
8
8
  "CHAPTERHOUSE_SSE_BUFFER_CAPACITY",
9
9
  "CHAPTERHOUSE_SSE_REPLAY_LIMIT",
10
10
  ];
11
+ const AUTH_ENV_VARS = ["COPILOT_TOKEN", "GITHUB_TOKEN"];
11
12
  test("setup-env clears ambient Chapterhouse runtime overrides", async () => {
12
- const originalValues = new Map(["CHAPTERHOUSE_DISABLE_DOTENV", ...RUNTIME_OVERRIDE_ENV_VARS].map((name) => [name, process.env[name]]));
13
+ const originalValues = new Map(["CHAPTERHOUSE_DISABLE_DOTENV", ...RUNTIME_OVERRIDE_ENV_VARS, ...AUTH_ENV_VARS]
14
+ .map((name) => [name, process.env[name]]));
13
15
  try {
14
16
  process.env.CHAPTERHOUSE_DISABLE_DOTENV = "0";
15
17
  process.env.CHAPTERHOUSE_MODE = "team";
@@ -18,11 +20,16 @@ test("setup-env clears ambient Chapterhouse runtime overrides", async () => {
18
20
  process.env.CHAPTERHOUSE_CHAT_SSE = "0";
19
21
  process.env.CHAPTERHOUSE_SSE_BUFFER_CAPACITY = "3";
20
22
  process.env.CHAPTERHOUSE_SSE_REPLAY_LIMIT = "9";
23
+ process.env.COPILOT_TOKEN = "ambient-copilot-token";
24
+ process.env.GITHUB_TOKEN = "ambient-github-token";
21
25
  await import(`./setup-env.js?cache-bust=${Date.now()}`);
22
26
  assert.equal(process.env.CHAPTERHOUSE_DISABLE_DOTENV, "1");
23
27
  for (const name of RUNTIME_OVERRIDE_ENV_VARS) {
24
28
  assert.equal(process.env[name], undefined, `${name} should be cleared by setup-env`);
25
29
  }
30
+ for (const name of AUTH_ENV_VARS) {
31
+ assert.equal(process.env[name], undefined, `${name} should be cleared by setup-env`);
32
+ }
26
33
  }
27
34
  finally {
28
35
  for (const [name, value] of originalValues) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chapterhouse",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Chapterhouse — a team-level AI assistant for engineering teams, built on the GitHub Copilot SDK. Web UI only.",
5
5
  "bin": {
6
6
  "chapterhouse": "dist/cli.js"
@@ -62,6 +62,7 @@
62
62
  "dotenv": "^17.3.1",
63
63
  "express": "^5.2.1",
64
64
  "helmet": "^8.1.0",
65
+ "js-yaml": "^4.1.1",
65
66
  "jsonwebtoken": "^9.0.3",
66
67
  "jwks-rsa": "^4.0.1",
67
68
  "pino": "^10.3.1",
@@ -73,6 +74,7 @@
73
74
  "@types/better-sqlite3": "^7.6.13",
74
75
  "@types/cors": "^2.8.19",
75
76
  "@types/express": "^5.0.6",
77
+ "@types/js-yaml": "^4.0.9",
76
78
  "@types/jsonwebtoken": "^9.0.10",
77
79
  "@types/node": "^25.6.0",
78
80
  "husky": "^9.1.7",
@@ -0,0 +1,10 @@
1
+ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
2
+ Theme: GitHub Dark
3
+ Description: Dark theme as seen on github.com
4
+ Author: github.com
5
+ Maintainer: @Hirse
6
+ Updated: 2021-05-15
7
+
8
+ Outdated base version: https://github.com/primer/github-syntax-dark
9
+ Current colors taken from GitHub's CSS
10
+ */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}:root{color-scheme:dark;--bg: #0e1116;--bg-elev: #161b22;--bg-elev-2: #21262d;--fg: #e6edf3;--fg-dim: #8b949e;--border: #30363d;--accent: #3b82f6;--accent-fg: #ffffff;--danger: #f87171;--user-bubble: #1e293b}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--fg);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;font-size:14px;line-height:1.5}a,.link{color:var(--accent);text-decoration:none}a:hover,.link:hover{text-decoration:underline}button,input,textarea,select{font:inherit}button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.9em;background:var(--bg-elev-2);padding:1px 5px;border-radius:4px}.dim{color:var(--fg-dim)}.small{font-size:12px}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:absolute;left:16px;top:-48px;z-index:10;padding:10px 14px;border-radius:8px;background:var(--accent);color:var(--accent-fg)}.skip-link:focus{top:16px}.layout{display:grid;grid-template-columns:220px 1fr;height:100%}.sidebar{background:var(--bg-elev);border-right:1px solid var(--border);padding:16px 0;display:flex;flex-direction:column}.sidebar-brand{display:flex;align-items:center;gap:10px;padding:0 18px 18px;font-weight:600;font-size:16px;border-bottom:1px solid var(--border);margin-bottom:12px}.sidebar nav{display:flex;flex-direction:column}.channel-rail{border-bottom:1px solid var(--border);margin-bottom:12px;padding:0 0 12px}.sidebar-section-label{padding:0 18px 6px;color:var(--fg-dim);font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.channel-link{display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;background:transparent;border:0;border-left:2px solid transparent;color:var(--fg);cursor:pointer;padding:7px 18px;text-align:left}.channel-link:hover{background:var(--bg-elev-2)}.channel-link.active{background:color-mix(in srgb,var(--accent) 14%,var(--bg-elev-2));border-left-color:var(--accent)}.channel-scope{color:var(--fg-dim);font-size:11px}.sidebar-error{color:var(--danger);padding:4px 18px 0}.nav-link{padding:9px 18px;color:var(--fg);border-left:2px solid transparent}.nav-link:hover{background:var(--bg-elev-2);text-decoration:none}.nav-link.active{background:var(--bg-elev-2);border-left-color:var(--accent);color:var(--fg)}.nav-group{display:flex;flex-direction:column}.nav-group-row{display:flex;align-items:stretch}.nav-group-label{flex:1}.nav-group-toggle{background:none;border:none;cursor:pointer;padding:0 14px 0 4px;color:var(--fg-muted, var(--fg));display:flex;align-items:center;justify-content:center;border-left:2px solid transparent}.nav-group-toggle:hover{background:var(--bg-elev-2)}.nav-chevron{display:inline-block;font-size:18px;line-height:1;transition:transform .18s ease;transform:rotate(0)}.nav-chevron-open{transform:rotate(90deg)}.nav-recents{list-style:none;margin:0;padding:0}.nav-recent-link{display:flex;align-items:baseline;justify-content:space-between;gap:6px;width:100%;background:none;border:none;border-left:2px solid transparent;padding:6px 18px 6px 28px;cursor:pointer;color:var(--fg);text-align:left;font-size:13px}.nav-recent-link:hover{background:var(--bg-elev-2);text-decoration:none;border-left-color:var(--accent)}.nav-recent-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.nav-recent-hint{font-size:11px;flex-shrink:0;opacity:.6}.nav-recents-empty{padding:4px 28px 6px;font-size:12px;margin:0}.main{overflow:hidden;display:flex;flex-direction:column}.app-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-elev)}.app-header-title{font-size:16px;font-weight:600;margin:0}.app-header-title-row{display:inline-flex;align-items:center;gap:10px}.app-header-user{color:var(--fg-dim);font-size:13px}.mode-badge{display:inline-flex;align-items:center;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.mode-standalone{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg-dim)}.mode-team{background:color-mix(in srgb,var(--accent) 14%,transparent);border-color:color-mix(in srgb,var(--accent) 35%,var(--border));color:var(--accent)}.sse-badge{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:3px 10px;font-size:12px;font-weight:600;line-height:1;border:1px solid transparent}.sse-badge__dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.sse-badge--connected{background:color-mix(in srgb,#22c55e 12%,transparent);border-color:color-mix(in srgb,#22c55e 35%,var(--border));color:#15803d}.sse-badge--connected .sse-badge__dot{background:#22c55e}.sse-badge--idle{background:color-mix(in srgb,var(--panel) 65%,transparent);border-color:var(--border);color:var(--text-muted)}.sse-badge--idle .sse-badge__dot{background:var(--text-muted)}.sse-badge--reconnecting{background:color-mix(in srgb,#f59e0b 12%,transparent);border-color:color-mix(in srgb,#f59e0b 35%,var(--border));color:#b45309}.sse-badge--reconnecting .sse-badge__dot{background:#f59e0b;animation:sse-pulse 1.2s ease-in-out infinite}.sse-badge--disconnected{background:color-mix(in srgb,#ef4444 12%,transparent);border-color:color-mix(in srgb,#ef4444 35%,var(--border));color:#b91c1c}.sse-badge--disconnected .sse-badge__dot{background:#ef4444}.sse-badge__reconnect-btn{background:none;border:none;padding:0;margin-left:4px;font-size:12px;font-weight:600;color:inherit;cursor:pointer;text-decoration:underline;text-underline-offset:2px}.sse-badge__reconnect-btn:hover{opacity:.8}@keyframes sse-pulse{0%,to{opacity:1}50%{opacity:.35}}.connection-banner{border:1px solid transparent;border-radius:10px;font-size:13px;font-weight:600;margin-bottom:16px;padding:10px 14px}.connection-banner--reconnecting{background:color-mix(in srgb,#f59e0b 14%,var(--bg-elev));border-color:color-mix(in srgb,#f59e0b 35%,var(--border));color:#fbbf24}.connection-banner--disconnected{background:color-mix(in srgb,#ef4444 14%,var(--bg-elev));border-color:color-mix(in srgb,#ef4444 35%,var(--border));color:#fca5a5}.page-shell{max-width:760px;margin:0 auto;padding:32px}.loading,.empty-state{padding:32px;color:var(--fg-dim)}.empty-state h2{color:var(--fg);margin-top:0;margin-bottom:8px}.empty-state p{margin:0 0 12px}.empty-state-icon{font-size:28px;margin-bottom:8px;line-height:1}.empty-state-action{margin-top:4px}.auth-screen{min-height:100%;display:grid;place-items:center;padding:32px}.auth-card{width:min(420px,100%);background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:24px}.auth-card h1{margin-top:0;margin-bottom:8px}.auth-card p{margin-top:0;margin-bottom:20px;color:var(--fg-dim)}.page{padding:24px 32px;overflow:auto;flex:1;min-width:0}.page-header{margin-bottom:16px}.page-header h1{margin:0 0 4px;font-size:22px}.projects-table-wrap{overflow-x:auto}.projects-table{width:100%;border-collapse:collapse;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.projects-table th,.projects-table td{padding:12px 14px;border-bottom:1px solid var(--border);text-align:left;vertical-align:top}.projects-table thead th{background:var(--bg-elev-2);font-size:12px;text-transform:uppercase;letter-spacing:.04em;color:var(--fg-dim)}.projects-table tbody tr:last-child th,.projects-table tbody tr:last-child td{border-bottom:0}.agents-sort-button{width:100%;display:inline-flex;align-items:center;justify-content:space-between;gap:8px;padding:0;border:0;background:transparent;color:inherit;font:inherit;text-transform:inherit;letter-spacing:inherit;cursor:pointer}.agents-sort-indicator,.tag-builtin{color:var(--fg-dim)}.tag-custom{color:var(--accent)}.project-detail-backlink{display:inline-flex;align-items:center;gap:6px;margin-bottom:12px;font-size:13px}.project-detail-summary{margin:0;color:var(--fg-dim)}.agent-detail-header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.agent-detail-summary{margin:0;color:var(--fg-dim)}.agent-detail-banner{margin-bottom:16px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;background:var(--bg-elev);color:var(--fg-dim)}.project-detail-layout{display:grid;gap:16px}.project-detail-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:18px}.project-detail-card h2{margin:0 0 14px;font-size:16px}.project-detail-meta{display:grid;grid-template-columns:minmax(120px,180px) 1fr;gap:10px 16px;margin:0}.project-detail-meta dt{color:var(--fg-dim);font-weight:600}.project-detail-meta dd{margin:0}.project-detail-actions{display:flex;justify-content:flex-end;margin-top:16px}.project-hard-rules{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin:0}.project-hard-rule{background:var(--bg-elev-2);border:1px solid var(--border);border-radius:8px;padding:12px 14px}.project-hard-rule dt{color:var(--fg-dim);font-size:12px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.project-hard-rule dd{margin:6px 0 0;font-size:14px}.project-hard-rules-form{display:grid;gap:14px}.project-hard-rule-input{display:grid;gap:10px;align-content:start}.project-hard-rule-label{color:var(--fg-dim);font-size:12px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.project-hard-rule input[type=checkbox]{width:16px;height:16px}.project-hard-rule-text-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;padding:6px 10px;width:100%}.project-hard-rule-text-input:focus{outline:none;border-color:var(--accent)}.project-soft-rules-form{display:grid;gap:12px}.project-soft-rules-grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));align-items:start}.project-soft-rules-editor,.project-soft-rules-preview-card{min-width:0}.project-soft-rules-label{color:var(--fg-dim);font-size:12px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.project-soft-rules-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;line-height:1.5;min-height:140px;padding:10px 12px;resize:vertical;width:100%}.project-soft-rules-input:focus{outline:none;border-color:var(--accent)}.project-soft-rules-hint{color:var(--fg-dim);font-size:12px;margin:0}.project-soft-rules-preview-card{background:var(--bg-elev-2);border:1px solid var(--border);border-radius:8px;padding:12px 14px}.project-soft-rules-preview-title{margin:0 0 10px;color:var(--fg-dim);font-size:12px;font-weight:600;letter-spacing:.03em;text-transform:uppercase}.project-soft-rules-preview{min-height:140px}.project-soft-rules-preview .md>:first-child{margin-top:0}.project-soft-rules-preview .md>:last-child{margin-bottom:0}.agent-detail-markdown .md>:first-child{margin-top:0}.agent-detail-markdown .md>:last-child{margin-bottom:0}.project-save-feedback{margin:0;font-size:12px}.project-save-feedback-success{color:#4ade80}.project-save-feedback-error{color:var(--danger)}.project-detail-empty{padding:0}.projects-count{width:120px}.error-notice{background:#f871711a;border:1px solid var(--danger);color:var(--danger);padding:12px 14px;border-radius:8px;margin-bottom:16px}.error-notice.inline{margin-bottom:12px}.error-notice-title{margin:0 0 4px;font-size:16px}.error-notice-message{margin:0}.error-notice-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.error-details{background:var(--bg-elev-2);padding:12px;border-radius:8px;overflow:auto}.loading-state{display:flex;align-items:flex-start;gap:12px;padding:16px 0;color:var(--fg-dim)}.loading-state.inline{padding:10px 0}.loading-state.centered{justify-content:center;padding:48px 32px}.loading-spinner{width:18px;height:18px;border:2px solid rgba(59,130,246,.25);border-top-color:var(--accent);border-radius:999px;flex:none;margin-top:2px;animation:spin .9s linear infinite}.loading-state-label{color:var(--fg);font-weight:500}.loading-state-detail{margin-top:2px}.skeleton-state{display:flex;flex-direction:column;gap:14px;padding:12px 0}.skeleton-chat-row{display:flex}.skeleton-chat-row--user{justify-content:flex-end}.skeleton-chat-row--assistant{justify-content:flex-start}.skeleton-chat-bubble,.skeleton-wiki-sheet{background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:14px}.skeleton-chat-bubble{display:flex;flex-direction:column;gap:10px;max-width:min(100%,42rem);width:min(100%,42rem)}.skeleton-chat-bubble--user{background:color-mix(in srgb,var(--user-bubble) 80%,var(--bg-elev))}.skeleton-chat-bubble--assistant{background:color-mix(in srgb,var(--bg-elev) 70%,var(--bg-elev-2))}.skeleton-chat-bubble--narrow{width:min(100%,30rem)}.skeleton-wiki-sheet{display:flex;flex-direction:column;gap:12px;max-width:76ch;margin:24px 22px 32px}.skeleton-line{display:block;height:12px;width:100%;border-radius:999px;background:linear-gradient(90deg,color-mix(in srgb,var(--bg-elev-2) 88%,transparent) 25%,color-mix(in srgb,var(--fg-dim) 20%,var(--bg-elev-2)),color-mix(in srgb,var(--bg-elev-2) 88%,transparent) 75%);background-size:220% 100%;animation:skeleton-shimmer 1.2s ease-in-out infinite}.skeleton-line--title{height:18px}.skeleton-line--25{width:25%}.skeleton-line--30{width:30%}.skeleton-line--35{width:35%}.skeleton-line--45{width:45%}.skeleton-line--55{width:55%}.skeleton-line--60{width:60%}.skeleton-line--70{width:70%}.skeleton-line--80{width:80%}.skeleton-line--85{width:85%}.skeleton-line--90{width:90%}.skeleton-line--95{width:95%}.skeleton-line--100{width:100%}.btn{background:var(--bg-elev-2);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 14px;font-size:13px;cursor:pointer}.btn:hover{background:var(--bg-elev)}.btn.primary{background:var(--accent);color:var(--accent-fg);border-color:var(--accent)}.btn.primary:hover{filter:brightness(1.1)}.btn.danger{border-color:var(--danger);color:var(--danger)}.btn.cancel{background:var(--danger);border-color:var(--danger);color:var(--accent-fg)}.chat{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.channel-header{display:flex;align-items:center;justify-content:space-between;gap:16px;border-bottom:1px solid var(--border);background:#0e1116b8;padding:14px 32px}.channel-header-title{font-size:16px;font-weight:700}.channel-header-description{color:var(--fg-dim);font-size:12px}.channel-header-scope{border:1px solid color-mix(in srgb,var(--accent) 35%,var(--border));border-radius:999px;color:var(--fg-dim);font-size:12px;padding:3px 9px;white-space:nowrap}.chat-scroll{flex:1;min-height:0;overflow:auto;padding:24px 32px 0}.chat-log{display:flex;flex-direction:column}.turn-wrapper{margin-bottom:18px}.turn-wrapper+.turn-wrapper:not(.has-separator) .bubble{border-top:1px solid var(--border);padding-top:14px}.turn-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:12px}.turn-header--user{justify-content:flex-end}.turn-actor-badge{display:inline-flex;align-items:center;gap:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:11px;font-weight:500;-webkit-user-select:none;user-select:none}.turn-header--agent .turn-actor-badge{background:color-mix(in srgb,var(--accent) 10%,var(--bg-elev));border-color:color-mix(in srgb,var(--accent) 35%,var(--border))}.turn-actor-icon{font-size:11px;line-height:1}.turn-agent-slug{color:var(--accent);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:10px;font-weight:600}.turn-ts{font-size:11px;color:var(--fg-dim);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;cursor:default;-webkit-user-select:none;user-select:none}.time-separator{display:flex;align-items:center;gap:10px;margin:16px 0 10px;color:var(--fg-dim);font-size:11px;font-style:italic;-webkit-user-select:none;user-select:none}.time-separator-line{flex:1;height:1px;background:var(--border);opacity:.5}.time-separator-label{white-space:nowrap;opacity:.7;letter-spacing:.02em}.bubble{max-width:800px}.bubble.user{margin-left:auto;text-align:right}.bubble--agent-activity{opacity:.88;border-left:2px solid var(--accent, #6366f1);padding-left:10px}.bubble.user .user-text{display:inline-block;background:var(--user-bubble);border:1px solid var(--border);padding:8px 14px;border-radius:14px;white-space:pre-wrap;text-align:left;margin:0}.route-tag{font-size:11px;color:var(--fg-dim);margin-top:4px}.assistant-turn-status{display:inline-flex;align-items:center;gap:8px;margin:0 0 8px;color:var(--fg-dim);font-size:12px;line-height:1.4}.assistant-turn-status-dots{display:inline-flex;align-items:center;gap:4px}.assistant-turn-status-dot{width:6px;height:6px;border-radius:999px;background:currentColor;opacity:.35;animation:pulse 1.1s ease-in-out infinite}.assistant-turn-status-dot:nth-child(2){animation-delay:.15s}.assistant-turn-status-dot:nth-child(3){animation-delay:.3s}@media (prefers-reduced-motion: reduce){.assistant-turn-status-dot{animation:none;opacity:.7}}.copy-btn-wrap{position:relative}.copy-btn{position:absolute;top:6px;right:6px;display:flex;align-items:center;justify-content:center;padding:4px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg-dim);cursor:pointer;z-index:1;line-height:0;transition:color .15s,background .15s}.copy-btn:hover{background:var(--bg-elev-2);color:var(--fg)}.copy-btn--copied{color:#4ade80;border-color:#4ade80}@media (hover: hover){.copy-btn{opacity:0;pointer-events:none;transition:opacity .15s,color .15s,background .15s}.copy-btn-wrap:hover .copy-btn,.copy-btn-wrap:focus-within .copy-btn{opacity:1;pointer-events:auto}}.copy-btn--code{top:8px;right:8px}.activity-heartbeat{display:flex;align-items:center;gap:6px;padding:4px 8px;margin:0 0 6px;border-radius:6px;border:1px solid rgba(59,130,246,.35);background:var(--bg-elev-2);font-size:12px;color:var(--fg);transition:opacity .3s}.activity-heartbeat.stale{opacity:.5;border-color:var(--border);color:var(--fg-dim)}.heartbeat-spinner{font-family:ui-monospace,monospace;font-size:11px;color:var(--accent);display:inline-block;animation:spin 1.4s linear infinite}.activity-heartbeat.stale .heartbeat-spinner{animation:none;color:var(--fg-dim)}.heartbeat-agent{font-weight:600}.heartbeat-sep{color:var(--fg-dim)}.heartbeat-action{color:var(--fg-dim);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.heartbeat-elapsed{font-family:ui-monospace,monospace;font-size:11px;color:var(--fg-dim);white-space:nowrap}.activity-strip{margin:0 0 8px;font-size:12px}.activity-summary{display:flex;flex-wrap:wrap;gap:6px}.activity-pill{display:inline-flex;align-items:center;gap:6px;background:var(--bg-elev);border:1px solid var(--border);color:var(--fg-dim);padding:3px 10px;border-radius:999px;cursor:pointer;font-size:12px}.activity-pill:hover{background:var(--bg-elev-2)}.activity-pill.running{color:var(--accent);border-color:#3b82f673}.activity-pill .glyph{font-family:ui-monospace,monospace;font-size:11px}.activity-pill.running .glyph{display:inline-block;animation:spin 1s linear infinite}.activity-pill .caret{color:var(--fg-dim);font-size:10px}.activity-headlines{display:flex;flex-direction:column;gap:2px;margin-top:6px}.activity-headline{display:inline-flex;align-items:center;gap:6px;padding:2px 4px;color:var(--fg-dim)}.activity-headline.status-running{color:var(--accent)}.activity-headline.status-failed{color:var(--danger)}.activity-headline .glyph{font-family:ui-monospace,monospace;font-size:11px;width:12px;text-align:center}.activity-headline.status-running .glyph{animation:spin 1s linear infinite}.agent-tag{font-size:10px;text-transform:lowercase;background:#3b82f629;color:#93c5fd;border:1px solid rgba(59,130,246,.35);padding:1px 6px;border-radius:4px;letter-spacing:.02em}.activity-thinking,.activity-details{margin-top:8px;padding:10px 12px;background:var(--bg-elev);border:1px solid var(--border);border-radius:6px}.activity-details{display:flex;flex-direction:column;gap:6px}.thinking-block{margin:0;padding:8px;background:var(--bg-elev-2);border-radius:4px;white-space:pre-wrap;font-size:12px;line-height:1.5;max-height:280px;overflow:auto}.activity-row{border:1px solid var(--border);border-radius:6px;background:var(--bg-elev-2)}.activity-row.status-running{border-color:#3b82f673}.activity-row.status-failed{border-color:var(--danger)}.activity-row-head{width:100%;display:flex;align-items:center;gap:8px;background:transparent;border:0;color:var(--fg);text-align:left;padding:6px 10px;cursor:pointer;font-size:12px}.activity-row.status-running .activity-row-head .glyph{animation:spin 1s linear infinite;color:var(--accent)}.activity-row.status-failed .activity-row-head .glyph{color:var(--danger)}.activity-row .glyph{font-family:ui-monospace,monospace;width:12px;text-align:center}.activity-row .caret{margin-left:auto;color:var(--fg-dim)}.activity-row-body{padding:0 10px 10px;display:flex;flex-direction:column;gap:6px}.row-label{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--fg-dim)}.composer{border-top:1px solid var(--border);background:var(--bg-elev);padding:14px 32px;display:flex;flex-direction:column;gap:8px}.composer textarea{width:100%;background:var(--bg);border:1px solid var(--border);border-radius:8px;color:var(--fg);padding:10px;resize:vertical;transition:background .14s ease,opacity .14s ease,border-color .14s ease}.composer-textarea--disabled,.composer textarea:disabled{background:color-mix(in srgb,var(--bg-elev-2) 88%,black);border-color:color-mix(in srgb,var(--border) 80%,black);cursor:not-allowed;opacity:.5}.composer-help{margin-top:-2px}.composer-status{border:1px solid transparent;border-radius:8px;font-size:12px;font-weight:600;padding:8px 10px}.composer-status--reconnecting{background:color-mix(in srgb,#f59e0b 14%,transparent);border-color:color-mix(in srgb,#f59e0b 35%,var(--border));color:#fbbf24}.composer-status--disconnected{background:color-mix(in srgb,#ef4444 12%,transparent);border-color:color-mix(in srgb,#ef4444 35%,var(--border));color:#fca5a5}.composer-queued{align-items:center;border:1px solid transparent;border-radius:10px;color:var(--fg-dim);display:flex;font-size:12px;gap:10px;justify-content:space-between;padding:8px 10px;transition:background .18s ease,border-color .18s ease,box-shadow .18s ease}.composer-queued--highlight{background:color-mix(in srgb,#f59e0b 14%,transparent);border-color:color-mix(in srgb,#f59e0b 35%,var(--border));box-shadow:0 0 0 1px color-mix(in srgb,#f59e0b 22%,transparent)}.composer-queued-link{flex:0 0 auto;text-underline-offset:2px}.dreaming-indicator{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-dim);padding:4px 0;animation:pulse 2s ease-in-out infinite}.dreaming-indicator-glyph{color:#c4b5fd}.composer-actions{display:flex;justify-content:flex-end;gap:6px}.composer-actions .btn:disabled{cursor:not-allowed;opacity:.55}.md{line-height:1.55}.md p:first-child{margin-top:0}.md p:last-child{margin-bottom:0}.md pre{background:var(--bg-elev-2);border-radius:6px;padding:12px;overflow:auto}.md pre code{background:transparent;padding:0}.md table{border-collapse:collapse;margin:1em 0}.md th,.md td{border:1px solid var(--border);padding:6px 10px}.workers-layout{display:grid;grid-template-columns:320px 1fr;gap:18px;align-items:start}.workers-list{display:flex;flex-direction:column;gap:6px}.worker-row{text-align:left;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px 12px;cursor:pointer;color:var(--fg)}.worker-row.selected,.worker-row:hover{background:var(--bg-elev-2)}.worker-row-head{display:flex;justify-content:space-between;align-items:center}.worker-status{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.04em}.worker-status--running{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent)}.worker-status--completed{background:color-mix(in srgb,#4caf50 15%,transparent);color:#4caf50}.worker-status--error{background:color-mix(in srgb,#f44336 15%,transparent);color:#f44336}.worker-row-desc{margin-top:4px;font-size:13px;color:var(--fg)}.workers-detail{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.worker-detail-description{margin:4px 0 8px;font-size:15px}.worker-detail-slug,.worker-detail-taskid{font-size:.75em;font-family:var(--font-mono, monospace)}.worker-detail-meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-bottom:8px}.worker-events{display:flex;flex-direction:column;gap:4px;max-height:320px;overflow-y:auto;background:var(--bg-elev-2);border-radius:6px;padding:10px 12px;margin-bottom:12px;font-size:12px;font-family:var(--font-mono, monospace)}.worker-event{display:flex;gap:8px;align-items:baseline;line-height:1.5}.worker-event-ts{color:var(--text-dim, #888);flex-shrink:0;font-size:11px}.worker-event-body{display:flex;gap:4px;align-items:baseline;flex-wrap:wrap;overflow:hidden}.worker-event-icon{flex-shrink:0}.worker-event--tool_complete .worker-event-icon{opacity:.7}.msg-queued-indicator{font-size:.8em;opacity:.6;vertical-align:middle;-webkit-user-select:none;user-select:none}.msg-queued-backend-indicator{display:inline-block;font-size:.78em;opacity:.75;margin-top:4px;color:var(--fg-dim);-webkit-user-select:none;user-select:none}.output{background:var(--bg-elev-2);padding:12px;border-radius:6px;overflow:auto;white-space:pre-wrap;font-size:13px}.projects-toolbar{margin-bottom:16px}.projects-register-form{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.projects-path-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--fg);font-size:13px;padding:6px 10px;width:380px;max-width:100%}.projects-path-input:focus{outline:none;border-color:var(--accent)}.projects-register-error{font-size:12px}.projects-disabled{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:18px}.projects-empty{padding:24px 0}.projects-list{display:flex;flex-direction:column;gap:8px}.project-row{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between;gap:12px}.project-row-info{display:flex;flex-direction:column;gap:4px;min-width:0}.project-root{font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-meta{display:flex;gap:12px;font-size:12px;flex-wrap:wrap}.project-badge{background:var(--bg-elev-2);border:1px solid var(--border);border-radius:10px;padding:1px 8px;font-size:11px;color:var(--fg)}.project-row-actions{display:flex;gap:6px;flex-shrink:0}.project-chat-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:8px 16px;background:color-mix(in srgb,var(--accent) 8%,var(--bg-elev));border-bottom:1px solid color-mix(in srgb,var(--accent) 20%,var(--border));flex-shrink:0}.project-chat-header-identity{display:flex;align-items:center;gap:8px;min-width:0;overflow:hidden}.project-chat-icon{font-size:16px;flex-shrink:0}.project-chat-title{font-size:14px;white-space:nowrap;flex-shrink:0}.project-chat-path{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}.wiki{display:flex;flex-direction:column;min-height:100%}.wiki-layout{display:grid;grid-template-columns:minmax(320px,360px) minmax(0,1fr);gap:20px;flex:1;min-height:0}.wiki-mobile-nav-bar{display:none}.wiki-sidebar,.wiki-main{min-height:0}.wiki-sidebar{display:flex;flex-direction:column;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-sidebar-header{position:sticky;top:0;z-index:1;display:flex;flex-direction:column;gap:14px;padding:16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-sidebar-header-row{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.wiki-sidebar-header-row h2{margin:0 0 4px;font-size:16px}.wiki-sidebar-header-row p{margin:0}.wiki-search{display:flex;flex-direction:column;gap:12px}.wiki-search-field input,.wiki-filter select{width:100%;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:9px 10px;border-radius:8px}.wiki-filter{display:flex;flex-direction:column;gap:6px;font-size:12px;color:var(--fg-dim)}.wiki-search-meta,.wiki-shortcuts,.wiki-scope-legend{color:var(--fg-dim)}.wiki-scope-header-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.wiki-scope-header-row h1{margin:0}.wiki-shortcuts{border-top:1px solid var(--border);padding-top:12px}.wiki-scope-legend{display:flex;flex-wrap:wrap;gap:8px}.wiki-scope-legend>span{display:inline-flex;align-items:center;gap:4px}.wiki-sidebar-body{flex:1;min-height:0;overflow:auto;padding:12px}.wiki-tree,.wiki-tree-children{list-style:none;margin:0;padding:0}.wiki-tree-children{margin-top:4px}.wiki-node{margin:2px 0}.wiki-node-button{width:100%;display:flex;align-items:center;gap:8px;padding:7px 10px;background:transparent;border:1px solid transparent;border-radius:8px;color:var(--fg);text-align:left;cursor:pointer}.wiki-node-folder-button{color:var(--fg-dim)}.wiki-node-folder-button:hover,.wiki-node-folder-button.expanded,.wiki-node-page-button:hover{background:var(--bg-elev-2);border-color:var(--border);color:var(--fg)}.wiki-node-page-button{align-items:flex-start}.wiki-node-page-button.selected{background:#3b82f61f;border-color:#3b82f659;box-shadow:inset 2px 0 0 var(--accent)}.wiki-node-icon{width:14px;flex:none;text-align:center;color:var(--fg-dim)}.wiki-node-page-button.selected .wiki-node-icon{color:#93c5fd}.wiki-node-page-button.selected .wiki-node-scope-icon-personal{color:#ddd6fe}.wiki-node-page-button.selected .wiki-node-scope-icon-team{color:#a7f3d0}.wiki-node-content{min-width:0;display:flex;flex:1;flex-direction:column;gap:4px}.wiki-node-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wiki-node-meta{display:flex;flex-wrap:wrap;gap:6px;font-size:11px}.wiki-node-count{margin-left:auto;border:1px solid var(--border);border-radius:999px;padding:0 6px;font-size:11px;color:var(--fg-dim)}.wiki-main{min-width:0;display:flex;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;overflow:hidden}.wiki-main>.wiki-empty-state{width:100%}.wiki-document{width:100%;min-height:0;display:flex;flex-direction:column}.wiki-page-header{position:sticky;top:0;z-index:1;padding:18px 22px 16px;border-bottom:1px solid var(--border);background:linear-gradient(180deg,var(--bg-elev) 0%,rgba(22,27,34,.98) 100%)}.wiki-page-header-main{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.wiki-page-title-block{min-width:0}.wiki-page-title-block h2{margin:0;font-size:28px;line-height:1.2}.wiki-page-summary{margin:8px 0 0;max-width:72ch;color:var(--fg-dim)}.wiki-page-actions{display:flex;gap:8px;flex:none}.wiki-breadcrumbs ol{display:flex;flex-wrap:wrap;gap:8px;list-style:none;margin:0 0 12px;padding:0}.wiki-breadcrumbs li{display:flex;align-items:center;min-width:0}.wiki-breadcrumbs li+li:before{content:"/";margin-right:8px;color:var(--fg-dim)}.wiki-breadcrumb-button{display:inline-block;max-width:100%;overflow:hidden;padding:0;border:0;background:transparent;color:var(--fg-dim);text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.wiki-breadcrumbs [aria-current=page]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wiki-breadcrumb-button:hover{color:var(--fg);text-decoration:underline}.wiki-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:14px;font-size:12px;color:var(--fg-dim)}.wiki-meta>*{max-width:100%}.wiki-badge,.wiki-tag,.wiki-meta-item{display:inline-flex;align-items:center;border:1px solid var(--border);border-radius:999px;padding:3px 8px;background:var(--bg-elev-2)}.wiki-badge{color:#93c5fd;border-color:#3b82f659}.wiki-scope-badge{display:inline-flex;align-items:center;gap:4px}.wiki-scope-badge-personal{color:#c4b5fd;border-color:#c4b5fd59;background:#c4b5fd14}.wiki-scope-badge-team{color:#6ee7b7;border-color:#6ee7b759;background:#6ee7b714}.wiki-node-scope-icon-personal{color:#c4b5fd}.wiki-node-scope-icon-team{color:#6ee7b7}.wiki-tag{color:var(--fg)}.wiki-meta-path{max-width:100%;overflow:auto;white-space:nowrap}.wiki-document-body{flex:1;min-height:0;overflow:auto}.wiki-article{box-sizing:border-box;width:100%;max-width:min(76ch,100%);padding:24px 22px 32px}.wiki-article .md{overflow-wrap:anywhere}.wiki-article .md img{max-width:100%;height:auto}.wiki-article .md table{display:block;width:max-content;max-width:100%;overflow-x:auto}.wiki-empty-state{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:12px;margin:auto;max-width:56ch;padding:32px}.wiki-empty-state.compact{margin:0;max-width:none;padding:20px 12px}.wiki-empty-state h2{margin:0;font-size:20px}.wiki-empty-state p{margin:0;color:var(--fg-dim)}.wiki-empty-state-actions{display:flex;flex-wrap:wrap;gap:8px}.wiki-mobile-sidebar-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:30;display:flex;justify-content:flex-start}.wiki-mobile-sidebar-backdrop{flex:1;border:0;background:#0a0f19b8}.wiki-mobile-sidebar-panel{position:relative;z-index:1;display:flex;flex-direction:column;gap:12px;width:min(100%,24rem);min-width:0;padding:16px 12px 12px;background:#0a0f19fa;border-right:1px solid var(--border);box-shadow:0 20px 48px #00000059}.wiki-mobile-sidebar-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px}.wiki-mobile-sidebar-title{font-size:14px;font-weight:600}.wiki-mobile-sidebar-panel .wiki-sidebar{flex:1;min-height:0}@media (max-width: 960px){.wiki-layout{grid-template-columns:1fr}.wiki-sidebar{max-height:50vh}.wiki-page-header-main,.wiki-sidebar-header-row,.wiki-scope-legend{flex-direction:column}.wiki-page-actions{width:100%}.wiki-page-actions .btn{flex:1}}@media (max-width: 767px){.wiki{gap:16px}.wiki-mobile-nav-bar{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--bg-elev);border:1px solid var(--border);border-radius:12px}.wiki-mobile-nav-bar .btn{flex:none}.wiki-mobile-nav-label{min-width:0;margin-left:auto;overflow:hidden;color:var(--fg-dim);text-align:right;text-overflow:ellipsis;white-space:nowrap}.wiki-main{min-height:0}.wiki-page-header{padding:16px}.wiki-page-title-block h2{font-size:24px}.wiki-meta{align-items:flex-start}.wiki-article{padding:18px 16px 24px}.wiki-mobile-sidebar-toolbar,.wiki-page-actions{flex-wrap:wrap}.wiki-page-actions .btn,.wiki-mobile-sidebar-toolbar .btn{flex:1 1 160px}}.wiki-edit .row{display:flex;gap:12px;margin-bottom:12px}.wiki-edit input[type=text]{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--fg);padding:8px;border-radius:6px}.wiki-edit label{display:block;width:100%;font-size:12px;color:var(--fg-dim)}.wiki-editor{margin-bottom:16px}.agent-editor-form{display:flex;flex-direction:column;gap:16px}.agent-editor-card{display:flex;flex-direction:column;gap:12px;padding:16px;border:1px solid var(--border);border-radius:12px;background:var(--bg-elev)}.agent-editor-grid{display:grid;gap:16px}.agent-editor-meta-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin:0}.agent-editor-meta-list div{display:flex;flex-direction:column;gap:4px}.agent-editor-meta-list dt{font-size:12px;color:var(--fg-dim)}.agent-editor-meta-list dd{margin:0}.agent-editor-skills{display:flex;flex-direction:column;gap:10px}.agent-editor-skill-option{display:flex;align-items:center;gap:8px}.agent-editor-prompt{min-height:240px;font-family:var(--font-mono, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace)}.agent-editor-toast{margin-bottom:12px}.skill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}.skill-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px}.skill-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.tag{font-size:10px;text-transform:uppercase;padding:2px 6px;border-radius:4px;letter-spacing:.05em;background:var(--bg-elev-2);color:var(--fg-dim)}.tag-bundled{color:#93c5fd}.tag-local{color:#86efac}.tag-global{color:#fcd34d}.settings section{margin-bottom:28px}.settings-field{display:flex;flex-direction:column;gap:6px}.settings-field-label{font-size:12px;color:var(--fg-dim)}.settings select{background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:6px;padding:6px 10px}.row{display:flex;align-items:center;gap:8px}.settings-row{align-items:flex-end}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes skeleton-shimmer{0%{background-position:100% 50%}to{background-position:0 50%}}@keyframes pulse{0%,to{opacity:.4}50%{opacity:1}}.ralph{max-width:760px}.ralph-section{margin-top:24px}.ralph-section h2{font-size:15px;font-weight:600;margin-bottom:10px;color:var(--fg)}.ralph-status-card{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px 16px;display:flex;flex-direction:column;gap:8px}.ralph-status-row{display:flex;align-items:center;gap:10px;font-size:14px}.ralph-status-label{min-width:90px;color:var(--text-dim, #888);font-size:12px;text-transform:uppercase;letter-spacing:.04em}.ralph-badge{font-size:11px;font-weight:600;padding:2px 8px;border-radius:10px;text-transform:uppercase;letter-spacing:.04em}.ralph-badge--running{background:color-mix(in srgb,var(--accent) 15%,transparent);color:var(--accent)}.ralph-badge--stopped{background:color-mix(in srgb,#888 15%,transparent);color:#888}.ralph-mono{font-family:var(--font-mono, monospace);font-size:13px}.ralph-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:400px}.ralph-controls{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:14px 16px;display:flex;flex-direction:column;gap:12px}.ralph-form-row{display:flex;align-items:center;gap:10px;font-size:14px}.ralph-form-row label{min-width:140px;color:var(--text-dim, #888);font-size:12px;text-transform:uppercase;letter-spacing:.04em}.ralph-select,.ralph-input{background:var(--bg-elev-2, var(--bg-elev));border:1px solid var(--border);border-radius:5px;color:var(--fg);font-size:13px;padding:5px 8px;min-width:240px}.ralph-input--narrow{min-width:80px;width:80px}.ralph-select:focus,.ralph-input:focus{outline:2px solid var(--accent);outline-offset:1px}.btn{border:none;border-radius:6px;font-size:13px;font-weight:600;padding:6px 14px;cursor:pointer;transition:opacity .1s;align-self:flex-start}.btn:disabled{opacity:.5;cursor:not-allowed}.btn-primary{background:var(--accent);color:#fff}.btn-primary:hover:not(:disabled){opacity:.85}.btn-danger{background:#f44336;color:#fff}.btn-danger:hover:not(:disabled){opacity:.85}.ralph-queue-count{font-weight:400;color:var(--text-dim, #888);font-size:13px}.ralph-queue-source{margin-bottom:8px}.ralph-queue{display:flex;flex-direction:column;gap:6px}.ralph-issue{display:block;text-decoration:none;color:inherit;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px 12px;transition:background .1s}.ralph-issue:hover{background:var(--bg-elev-2)}.ralph-issue-head{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}.ralph-issue-number{font-family:var(--font-mono, monospace);font-size:12px;color:var(--text-dim, #888);flex-shrink:0}.ralph-issue-title{font-size:14px;font-weight:500}.ralph-issue-meta{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.ralph-labels{display:flex;flex-wrap:wrap;gap:4px}.ralph-label{font-size:11px;padding:1px 6px;border-radius:8px;background:color-mix(in srgb,#888 12%,transparent);color:var(--fg)}.ralph-label--agent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent)}.ralph-label--triage{background:color-mix(in srgb,#9c27b0 12%,transparent);color:#9c27b0}.ralph-label--urgent{background:color-mix(in srgb,#f44336 12%,transparent);color:#f44336}.link-btn{background:none;border:none;color:var(--accent);font-size:inherit;cursor:pointer;padding:0;text-decoration:underline}