@arbiterforge/ca-pi 0.6.3 → 0.10.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.
Files changed (67) hide show
  1. package/README.md +41 -98
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +145 -0
  4. package/plugins/ca-pi/COMMANDS.md +138 -68
  5. package/plugins/ca-pi/SKILLS.md +137 -30
  6. package/plugins/ca-pi/agents/INDEX.md +3 -2
  7. package/plugins/ca-pi/agents/checkpoint-aggregator.md +8 -7
  8. package/plugins/ca-pi/agents/design-quality-reviewer.md +1 -1
  9. package/plugins/ca-pi/agents/finding-triage.md +31 -14
  10. package/plugins/ca-pi/agents/verdict-aggregator.md +64 -0
  11. package/plugins/ca-pi/{ORCHESTRATOR.md → arbiter.md} +37 -36
  12. package/plugins/ca-pi/extensions/codearbiter.js +844 -19
  13. package/plugins/ca-pi/generated/command-catalog.json +386 -196
  14. package/plugins/ca-pi/generated/roles.json +9 -0
  15. package/plugins/ca-pi/hooks/_arbiterstatelib.py +59 -11
  16. package/plugins/ca-pi/hooks/_bashguardlib.py +30 -12
  17. package/plugins/ca-pi/hooks/_gitexec.py +23 -0
  18. package/plugins/ca-pi/hooks/_githooks.py +50 -23
  19. package/plugins/ca-pi/hooks/_hooklib.py +148 -20
  20. package/plugins/ca-pi/hooks/_host.py +9 -1
  21. package/plugins/ca-pi/hooks/_metricslib.py +20 -0
  22. package/plugins/ca-pi/hooks/_modelib.py +762 -0
  23. package/plugins/ca-pi/hooks/_protectedlib.py +13 -4
  24. package/plugins/ca-pi/hooks/_prunelib.py +51 -12
  25. package/plugins/ca-pi/hooks/_prunepolicy.py +33 -7
  26. package/plugins/ca-pi/hooks/_readinjectlib.py +10 -4
  27. package/plugins/ca-pi/hooks/_releaselib.py +278 -48
  28. package/plugins/ca-pi/hooks/_updatelib.py +230 -50
  29. package/plugins/ca-pi/hooks/doctor.py +58 -9
  30. package/plugins/ca-pi/hooks/git-enforce.py +10 -3
  31. package/plugins/ca-pi/hooks/hostapi.py +220 -22
  32. package/plugins/ca-pi/hooks/pi-bridge.py +10 -4
  33. package/plugins/ca-pi/hooks/prompt-submit.py +486 -0
  34. package/plugins/ca-pi/hooks/prune-transcript.py +23 -3
  35. package/plugins/ca-pi/hooks/session-start.py +529 -435
  36. package/plugins/ca-pi/hooks/statusline.py +28 -10
  37. package/plugins/ca-pi/hooks/wire-statusline.py +13 -8
  38. package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +1 -1
  39. package/plugins/ca-pi/includes/command-compatibility.md +16 -0
  40. package/plugins/ca-pi/includes/dangerous-mode.md +57 -0
  41. package/plugins/ca-pi/includes/ops-mode.md +96 -0
  42. package/plugins/ca-pi/includes/pi-host-notes.md +10 -1
  43. package/plugins/ca-pi/includes/redirect.md +12 -1
  44. package/plugins/ca-pi/includes/routing-table.md +14 -5
  45. package/plugins/ca-pi/includes/safety-core.md +86 -0
  46. package/plugins/ca-pi/includes/smarts/core.md +1 -1
  47. package/plugins/ca-pi/routines/INDEX.md +1 -1
  48. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +55 -3
  49. package/plugins/ca-pi/routines/decision-lifecycle/references/adr-template.md +9 -1
  50. package/plugins/ca-pi/routines/decompose/SKILL.md +1 -1
  51. package/plugins/ca-pi/routines/dispatching-parallel-agents/SKILL.md +4 -4
  52. package/plugins/ca-pi/routines/release/SKILL.md +1 -1
  53. package/plugins/ca-pi/skills/ca-checkpoint/SKILL.md +5 -4
  54. package/plugins/ca-pi/skills/ca-cleanup/SKILL.md +6 -0
  55. package/plugins/ca-pi/skills/ca-context-check/SKILL.md +6 -0
  56. package/plugins/ca-pi/skills/ca-create-context/SKILL.md +6 -0
  57. package/plugins/ca-pi/skills/ca-decompose/SKILL.md +6 -0
  58. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +4 -0
  59. package/plugins/ca-pi/skills/ca-init/SKILL.md +18 -1
  60. package/plugins/ca-pi/skills/ca-pr/SKILL.md +17 -1
  61. package/plugins/ca-pi/skills/ca-review/SKILL.md +3 -4
  62. package/plugins/ca-pi/skills/ca-spike/SKILL.md +15 -8
  63. package/plugins/ca-pi/skills/ca-status/SKILL.md +13 -1
  64. package/plugins/ca-pi/skills/ca-watch/SKILL.md +6 -0
  65. package/plugins/ca-pi/includes/dev-mode.md +0 -30
  66. package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +0 -36
  67. package/plugins/ca-pi/skills/ca-dev/SKILL.md +0 -42
@@ -291,7 +291,7 @@ var PI_STATUS_RESULT_KEYS = /* @__PURE__ */ new Set([
291
291
  "questions",
292
292
  "overrides",
293
293
  "sprint",
294
- "dev",
294
+ "mode",
295
295
  "prune"
296
296
  ]);
297
297
  function minimalEnvironment(identities, userHome) {
@@ -577,14 +577,14 @@ function statusResult(response) {
577
577
  const patch = record(response.resultPatch);
578
578
  if (patch === void 0 || !exactKeys(patch, /* @__PURE__ */ new Set(["footerStatus"]))) return void 0;
579
579
  const value = record(patch.footerStatus);
580
- if (value === void 0 || !exactKeys(value, PI_STATUS_RESULT_KEYS) || value.status !== "ok" || typeof value.stage !== "string" || value.stage.length < 1 || value.stage.length > 128 || CONTROL_RE.test(value.stage) || boundedStatusCount(value.tasks) === void 0 || boundedStatusCount(value.questions) === void 0 || boundedStatusCount(value.overrides) === void 0 || typeof value.sprint !== "boolean" || typeof value.dev !== "boolean" || value.prune !== null && (typeof value.prune !== "string" || value.prune.length > 256 || CONTROL_RE.test(value.prune))) return void 0;
580
+ if (value === void 0 || !exactKeys(value, PI_STATUS_RESULT_KEYS) || value.status !== "ok" || typeof value.stage !== "string" || value.stage.length < 1 || value.stage.length > 128 || CONTROL_RE.test(value.stage) || boundedStatusCount(value.tasks) === void 0 || boundedStatusCount(value.questions) === void 0 || boundedStatusCount(value.overrides) === void 0 || typeof value.sprint !== "boolean" || value.mode !== "arbiter" && value.mode !== "dangerous" && value.mode !== "ops" || value.prune !== null && (typeof value.prune !== "string" || value.prune.length > 256 || CONTROL_RE.test(value.prune))) return void 0;
581
581
  return {
582
582
  stage: value.stage,
583
583
  tasks: value.tasks,
584
584
  questions: value.questions,
585
585
  overrides: value.overrides,
586
586
  sprint: value.sprint,
587
- dev: value.dev,
587
+ mode: value.mode,
588
588
  prune: value.prune === null ? void 0 : value.prune
589
589
  };
590
590
  }
@@ -1224,7 +1224,9 @@ import { posix as posix3, win32 as win324 } from "node:path";
1224
1224
  // src/activity.ts
1225
1225
  import { types as utilTypes } from "node:util";
1226
1226
  var ACTIVITY_POLICY = Object.freeze({
1227
- maxActive: 8,
1227
+ // 16 active rows feed the sidebar subagents panel (spec pi-sidebar-panel);
1228
+ // the footer keeps its own tighter display bound.
1229
+ maxActive: 16,
1228
1230
  maxRecent: 8,
1229
1231
  activeTtlMs: 2 * 60 * 60 * 1e3,
1230
1232
  recentTtlMs: 5 * 60 * 1e3,
@@ -3556,10 +3558,10 @@ function parsePlanLedger(markdown) {
3556
3558
  if (lines.length > MAX_LEDGER_LINES) return void 0;
3557
3559
  let found;
3558
3560
  for (let lineIndex = 0; lineIndex < lines.length - 1; lineIndex += 1) {
3559
- const header = splitTableRow(lines[lineIndex]);
3561
+ const header2 = splitTableRow(lines[lineIndex]);
3560
3562
  const separator = splitTableRow(lines[lineIndex + 1]);
3561
- if (header === void 0 || separator === void 0 || !isSeparator(separator, header.length)) continue;
3562
- const normalizedHeaders = header.map((cell) => cell.toLowerCase().replace(/\s+/gu, " "));
3563
+ if (header2 === void 0 || separator === void 0 || !isSeparator(separator, header2.length)) continue;
3564
+ const normalizedHeaders = header2.map((cell) => cell.toLowerCase().replace(/\s+/gu, " "));
3563
3565
  const taskColumns = normalizedHeaders.flatMap((cell, index) => TASK_HEADERS.has(cell) ? [index] : []);
3564
3566
  const statusColumns = normalizedHeaders.flatMap((cell, index) => cell === "status" ? [index] : []);
3565
3567
  if (taskColumns.length !== 1 || statusColumns.length !== 1) {
@@ -3572,7 +3574,7 @@ function parsePlanLedger(markdown) {
3572
3574
  for (let rowIndex = lineIndex + 2; rowIndex < lines.length; rowIndex += 1) {
3573
3575
  const cells = splitTableRow(lines[rowIndex]);
3574
3576
  if (cells === void 0) break;
3575
- if (cells.length !== header.length) return void 0;
3577
+ if (cells.length !== header2.length) return void 0;
3576
3578
  const id = cells[taskColumns[0]];
3577
3579
  const status = taskStatus(cells[statusColumns[0]]);
3578
3580
  if (!TASK_ID.test(id) || CONTROL.test(id) || status === void 0 || ids.has(id)) return void 0;
@@ -4479,7 +4481,7 @@ function adaptPiFooterState(source) {
4479
4481
  const entriesValue = callMember2(manager, "getEntries");
4480
4482
  const snapshotSession = normalizeSnapshotSession(source.usageSnapshot?.session);
4481
4483
  const usage = snapshotSession ?? aggregateSessionUsage(entriesValue);
4482
- const sparkline = sparklineSeries(entriesValue);
4484
+ const sparkline2 = sparklineSeries(entriesValue);
4483
4485
  const ageSeconds = sessionAge(callMember2(manager, "getHeader"), now);
4484
4486
  const session = usage ? { ...usage, ...ageSeconds === void 0 ? {} : { ageSeconds } } : void 0;
4485
4487
  const rawContext = object(callMember2(source.context, "getContextUsage"));
@@ -4507,7 +4509,7 @@ function adaptPiFooterState(source) {
4507
4509
  ...daily ? { daily } : {},
4508
4510
  ...updateVersion ? { update: { version: updateVersion } } : {},
4509
4511
  ...activity ? { activity } : {},
4510
- ...sparkline ? { sparkline } : {}
4512
+ ...sparkline2 ? { sparkline: sparkline2 } : {}
4511
4513
  };
4512
4514
  }
4513
4515
 
@@ -4650,9 +4652,10 @@ function gitSegment(git, compact) {
4650
4652
  return `${colored(COLORS.normal, lead)}${divider}${colored(branchColor, `${branch}${git.dirty ? "*" : ""}`)}`;
4651
4653
  }
4652
4654
  function governanceSegment(governance) {
4653
- const badge = governance.dev ? " [DEV]" : governance.sprint ? " [SPRINT]" : "";
4655
+ const modeBadge = governance.mode === "dangerous" ? " [DANGEROUS]" : governance.mode === "ops" ? " [OPS]" : "";
4656
+ const badge = modeBadge || (governance.sprint ? " [SPRINT]" : "");
4654
4657
  const prune = governance.prune ? sanitize2(governance.prune, "") : "";
4655
- return `${colored(COLORS.ok, "\u25CF")} ${colored(COLORS.normal, `stage:${sanitize2(governance.stage)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(COLORS.normal, `tasks:${boundedCount(governance.tasks)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(governance.questions > 0 ? COLORS.danger : COLORS.muted, `q:${boundedCount(governance.questions)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(governance.overrides > 0 ? COLORS.danger : COLORS.muted, `over:${boundedCount(governance.overrides)}`)}` + (prune ? ` ${colored(COLORS.deep, "\xB7")} ${colored(COLORS.muted, `prune:${prune}`)}` : "") + (badge ? colored(governance.dev ? COLORS.danger : COLORS.bright, badge) : "");
4658
+ return `${colored(COLORS.ok, "\u25CF")} ${colored(COLORS.normal, `stage:${sanitize2(governance.stage)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(COLORS.normal, `tasks:${boundedCount(governance.tasks)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(governance.questions > 0 ? COLORS.danger : COLORS.muted, `q:${boundedCount(governance.questions)}`)} ${colored(COLORS.deep, "\xB7")} ${colored(governance.overrides > 0 ? COLORS.danger : COLORS.muted, `over:${boundedCount(governance.overrides)}`)}` + (prune ? ` ${colored(COLORS.deep, "\xB7")} ${colored(COLORS.muted, `prune:${prune}`)}` : "") + (badge ? colored(governance.mode === "dangerous" ? COLORS.danger : COLORS.bright, badge) : "");
4656
4659
  }
4657
4660
  function usageRow(label, usage) {
4658
4661
  return `${colored(COLORS.muted, label.padEnd(7))} ${colored(COLORS.deep, "\u2502")} ${colored(COLORS.primary, "\u2193")} ${colored(COLORS.normal, formatTokens(usage.inputTokens).padStart(6))} ${colored(COLORS.primary, "\u2191")} ${colored(COLORS.normal, formatTokens(usage.outputTokens).padStart(6))} ${colored(COLORS.deep, "\u2502")} ${colored(COLORS.ok, formatUsd(usage.costUsd))}`;
@@ -4693,8 +4696,8 @@ var SPARK_GLYPHS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586",
4693
4696
  function activityRows(activity) {
4694
4697
  const rows = activity.slice(0, ACTIVITY_MAX_ROWS).map((item) => {
4695
4698
  const glyph = item.state === "active" ? colored(COLORS.ok, "\u25CF") : colored(COLORS.muted, "\u2713");
4696
- const age = item.ageSeconds === void 0 ? "" : ` ${formatDuration(item.ageSeconds)}`;
4697
- return `${glyph} ${sanitize2(item.kind, "job")}:${sanitize2(item.label)}${age}`;
4699
+ const age2 = item.ageSeconds === void 0 ? "" : ` ${formatDuration(item.ageSeconds)}`;
4700
+ return `${glyph} ${sanitize2(item.kind, "job")}:${sanitize2(item.label)}${age2}`;
4698
4701
  });
4699
4702
  if (rows.length === 0) return rows;
4700
4703
  rows[0] = `${colored(COLORS.bright, "activity")} ${rows[0]}`;
@@ -4767,8 +4770,8 @@ function renderCompact(input, box) {
4767
4770
  if (readValue !== void 0 || writeValue !== void 0) {
4768
4771
  bits.push(`cache ${read + write > 0 ? Math.round(read / (read + write) * 100) : 0}%`);
4769
4772
  }
4770
- const age = usage.ageSeconds;
4771
- if (age !== void 0) bits.push(`age ${formatDuration(age)}`);
4773
+ const age2 = usage.ageSeconds;
4774
+ if (age2 !== void 0) bits.push(`age ${formatDuration(age2)}`);
4772
4775
  return bits.join(" \xB7 ");
4773
4776
  }, "");
4774
4777
  const daily = guard(() => {
@@ -4893,6 +4896,7 @@ var PiFooterLifecycle = class {
4893
4896
  governance;
4894
4897
  updateVersion;
4895
4898
  gitFacts;
4899
+ lastInput;
4896
4900
  activationEnabled = false;
4897
4901
  expected = false;
4898
4902
  installed = false;
@@ -4900,6 +4904,16 @@ var PiFooterLifecycle = class {
4900
4904
  requestActivityRender() {
4901
4905
  requestRender(this.tui);
4902
4906
  }
4907
+ /** The live tui handle captured by the footer factory; the sidebar
4908
+ * compositor probes it for the undocumented doRender hook. */
4909
+ currentTui() {
4910
+ return this.tui;
4911
+ }
4912
+ /** The most recently rendered footer input — the sidebar's session,
4913
+ * subagents and workspace panels reuse these already-gathered facts. */
4914
+ lastRenderedInput() {
4915
+ return this.lastInput;
4916
+ }
4903
4917
  health() {
4904
4918
  return Object.freeze({
4905
4919
  expected: this.expected,
@@ -4982,10 +4996,11 @@ var PiFooterLifecycle = class {
4982
4996
  questions: this.governance.questions,
4983
4997
  overrides: this.governance.overrides,
4984
4998
  sprint: this.governance.sprint,
4985
- dev: this.governance.dev,
4999
+ mode: this.governance.mode,
4986
5000
  ...this.governance.prune === void 0 ? {} : { prune: this.governance.prune }
4987
5001
  }
4988
5002
  };
5003
+ this.lastInput = enriched;
4989
5004
  const rendered = renderFooter(enriched, {
4990
5005
  width,
4991
5006
  noColor: Object.prototype.hasOwnProperty.call(process.env, "NO_COLOR")
@@ -5063,6 +5078,7 @@ var PiFooterLifecycle = class {
5063
5078
  this.governance = void 0;
5064
5079
  this.updateVersion = void 0;
5065
5080
  this.gitFacts = void 0;
5081
+ this.lastInput = void 0;
5066
5082
  this.activationEnabled = false;
5067
5083
  this.expected = false;
5068
5084
  this.refreshQueue = Promise.resolve();
@@ -5079,6 +5095,675 @@ var PiFooterLifecycle = class {
5079
5095
  }
5080
5096
  };
5081
5097
 
5098
+ // src/sidebar.ts
5099
+ var SIDEBAR_MAX_AGENT_ROWS = 16;
5100
+ var MAX_TODO_ROWS = 12;
5101
+ var MAX_MCP_ROWS = 8;
5102
+ var MAX_LIST_ITEMS = 256;
5103
+ var MAX_TEXT_POINTS3 = 512;
5104
+ var SPARK_GLYPHS2 = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
5105
+ var ESC2 = "\x1B";
5106
+ var RESET2 = `${ESC2}[0m`;
5107
+ var HEADER_COLOR = `${ESC2}[38;2;178;102;255m`;
5108
+ var MUTED = `${ESC2}[38;2;150;150;162m`;
5109
+ var OK = `${ESC2}[38;2;120;220;150m`;
5110
+ var WARN = `${ESC2}[38;2;255;184;76m`;
5111
+ var OSC_RE2 = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/gu;
5112
+ var CSI_RE2 = /\x1b\[[0-?]*[ -/]*[@-~]?/gu;
5113
+ var ESCAPE_RE2 = /\x1b[@-_]/gu;
5114
+ var CONTROL_RE3 = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2060-\u206f\ufeff]/gu;
5115
+ function sanitize3(value) {
5116
+ if (typeof value !== "string") return "";
5117
+ return value.replace(OSC_RE2, "").replace(CSI_RE2, "").replace(ESCAPE_RE2, "").replace(CONTROL_RE3, "").slice(0, MAX_TEXT_POINTS3);
5118
+ }
5119
+ function boundedNumber2(value, maximum) {
5120
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return 0;
5121
+ return Math.min(value, maximum);
5122
+ }
5123
+ var COLOR_PALETTE = { header: HEADER_COLOR, muted: MUTED, ok: OK, warn: WARN, reset: RESET2 };
5124
+ var PLAIN_PALETTE = { header: "", muted: "", ok: "", warn: "", reset: "" };
5125
+ function fit(text2, context) {
5126
+ const { width, metrics, palette } = context;
5127
+ let value = text2;
5128
+ let visible;
5129
+ try {
5130
+ visible = metrics.visibleWidth(value);
5131
+ } catch {
5132
+ value = "";
5133
+ visible = 0;
5134
+ }
5135
+ if (visible > width) {
5136
+ try {
5137
+ value = metrics.truncateToWidth(value, width, "\u2026");
5138
+ visible = metrics.visibleWidth(value);
5139
+ } catch {
5140
+ value = "";
5141
+ visible = 0;
5142
+ }
5143
+ }
5144
+ if (visible > width) {
5145
+ value = "";
5146
+ visible = 0;
5147
+ }
5148
+ const padded = value + " ".repeat(width - visible);
5149
+ return palette.reset === "" ? padded : padded + palette.reset;
5150
+ }
5151
+ function header(title, context) {
5152
+ const { width, palette } = context;
5153
+ const label = ` ${title} `;
5154
+ const room = Math.max(0, width - label.length - 2);
5155
+ const bar = "\u2500";
5156
+ const text2 = `${bar}${label}${bar.repeat(room)}`.slice(0, width);
5157
+ return fit(`${palette.header}${text2}`, context);
5158
+ }
5159
+ function tokensLabel(input, output) {
5160
+ const compact = (value) => value >= 1e3 ? `${(value / 1e3).toFixed(1)}k` : String(value);
5161
+ return `${compact(input)}\u2191 ${compact(output)}\u2193`;
5162
+ }
5163
+ function sparkline(series) {
5164
+ const bounded = series.slice(-16).map((value) => boundedNumber2(value, Number.MAX_SAFE_INTEGER));
5165
+ if (bounded.length === 0) return "";
5166
+ const maximum = Math.max(...bounded);
5167
+ return bounded.map((value) => {
5168
+ const level = maximum > 0 ? Math.min(8, Math.max(1, Math.ceil(value / maximum * 8))) : 1;
5169
+ return SPARK_GLYPHS2[level - 1];
5170
+ }).join("");
5171
+ }
5172
+ function sessionPanel(session, context) {
5173
+ const { palette } = context;
5174
+ const lines = [header("session", context)];
5175
+ const model = sanitize3(session.model);
5176
+ const thinking = sanitize3(session.thinkingLevel);
5177
+ if (model !== "") lines.push(fit(` ${model}${thinking === "" ? "" : `${palette.muted} \xB7 ${thinking}`}`, context));
5178
+ const percent = boundedNumber2(session.contextPercent, 999);
5179
+ if (session.contextPercent !== void 0) {
5180
+ const cells = 10;
5181
+ const filled = Math.min(cells, Math.round(Math.min(percent, 100) / 100 * cells));
5182
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(cells - filled);
5183
+ const tone = percent >= 80 ? palette.warn : palette.ok;
5184
+ lines.push(fit(` ctx ${tone}${bar}${palette.reset === "" ? "" : palette.reset} ${Math.round(percent)}%`, context));
5185
+ }
5186
+ const input = boundedNumber2(session.inputTokens, Number.MAX_SAFE_INTEGER);
5187
+ const output = boundedNumber2(session.outputTokens, Number.MAX_SAFE_INTEGER);
5188
+ const cost = boundedNumber2(session.costUsd, 1e9);
5189
+ if (session.inputTokens !== void 0 || session.outputTokens !== void 0 || session.costUsd !== void 0) {
5190
+ lines.push(fit(` ${tokensLabel(input, output)}${palette.muted} \xB7 $${cost.toFixed(cost >= 100 ? 0 : 2)}`, context));
5191
+ }
5192
+ if (session.burn !== void 0 && session.burn.length > 0) {
5193
+ lines.push(fit(` burn ${sparkline(session.burn)}`, context));
5194
+ }
5195
+ return lines;
5196
+ }
5197
+ function agentGlyph(row, palette) {
5198
+ if (row.state === "active") return `${palette.ok}\u25CF${palette.reset === "" ? "" : palette.reset}`;
5199
+ return `${palette.muted}\u25CB${palette.reset === "" ? "" : palette.reset}`;
5200
+ }
5201
+ function age(seconds) {
5202
+ const bounded = boundedNumber2(seconds, 3650 * 86400);
5203
+ if (bounded >= 3600) return `${Math.floor(bounded / 3600)}h`;
5204
+ if (bounded >= 60) return `${Math.floor(bounded / 60)}m`;
5205
+ return `${Math.floor(bounded)}s`;
5206
+ }
5207
+ function agentsPanel(rows, context) {
5208
+ const { palette } = context;
5209
+ const lines = [header("agents", context)];
5210
+ const bounded = rows.slice(0, MAX_LIST_ITEMS);
5211
+ for (const row of bounded.slice(0, SIDEBAR_MAX_AGENT_ROWS)) {
5212
+ const label = sanitize3(row.label);
5213
+ const kind = row.kind === "job" ? "job" : "child";
5214
+ const suffix = row.ageSeconds === void 0 ? "" : ` ${age(row.ageSeconds)}`;
5215
+ lines.push(fit(` ${agentGlyph(row, palette)} ${label}${palette.muted} \xB7 ${kind}${suffix}`, context));
5216
+ }
5217
+ if (bounded.length > SIDEBAR_MAX_AGENT_ROWS) {
5218
+ lines.push(fit(` ${palette.muted}+${bounded.length - SIDEBAR_MAX_AGENT_ROWS} more`, context));
5219
+ }
5220
+ return lines;
5221
+ }
5222
+ function workspacePanel(workspace, context) {
5223
+ const { palette } = context;
5224
+ const lines = [header("workspace", context)];
5225
+ const repository = sanitize3(workspace.repository);
5226
+ if (repository !== "") {
5227
+ const dirty = workspace.dirty === true ? `${palette.warn} \u25CF` : "";
5228
+ lines.push(fit(` ${repository}${dirty}`, context));
5229
+ }
5230
+ const cwd = sanitize3(workspace.cwd);
5231
+ if (cwd !== "") lines.push(fit(` ${palette.muted}${cwd}`, context));
5232
+ return lines;
5233
+ }
5234
+ var TODO_GLYPHS = {
5235
+ done: "\u2713",
5236
+ active: "\u25B8",
5237
+ open: "\xB7"
5238
+ };
5239
+ function todosPanel(rows, context) {
5240
+ const { palette } = context;
5241
+ const lines = [header("todos", context)];
5242
+ const bounded = rows.slice(0, MAX_LIST_ITEMS);
5243
+ for (const row of bounded.slice(0, MAX_TODO_ROWS)) {
5244
+ const glyph = TODO_GLYPHS[row.state] ?? TODO_GLYPHS.open;
5245
+ const tone = row.state === "done" ? palette.muted : row.state === "active" ? palette.ok : "";
5246
+ lines.push(fit(` ${tone}${glyph} ${sanitize3(row.text)}`, context));
5247
+ }
5248
+ if (bounded.length > MAX_TODO_ROWS) {
5249
+ lines.push(fit(` ${palette.muted}+${bounded.length - MAX_TODO_ROWS} more`, context));
5250
+ }
5251
+ return lines;
5252
+ }
5253
+ function mcpPanel(rows, context) {
5254
+ const { palette } = context;
5255
+ const lines = [header("mcp", context)];
5256
+ const bounded = rows.slice(0, MAX_LIST_ITEMS);
5257
+ for (const row of bounded.slice(0, MAX_MCP_ROWS)) {
5258
+ const state = sanitize3(row.state);
5259
+ const tone = state === "connected" ? palette.ok : palette.muted;
5260
+ lines.push(fit(` ${tone}\u25CF${palette.reset === "" ? "" : palette.reset} ${sanitize3(row.name)}${palette.muted} \xB7 ${state}`, context));
5261
+ }
5262
+ return lines;
5263
+ }
5264
+ function renderSidebar(input, width, metrics, options) {
5265
+ const safeWidth = Number.isSafeInteger(width) && width > 0 ? Math.min(width, 160) : 40;
5266
+ const palette = options?.noColor === true ? PLAIN_PALETTE : COLOR_PALETTE;
5267
+ const context = { width: safeWidth, metrics, palette };
5268
+ const lines = [];
5269
+ const panels = [
5270
+ ["session", () => {
5271
+ const session = input.session;
5272
+ return session === void 0 ? void 0 : sessionPanel(session, context);
5273
+ }],
5274
+ ["agents", () => {
5275
+ const rows = input.subagents;
5276
+ return rows === void 0 || rows.length === 0 ? void 0 : agentsPanel(rows, context);
5277
+ }],
5278
+ ["workspace", () => {
5279
+ const workspace = input.workspace;
5280
+ return workspace === void 0 ? void 0 : workspacePanel(workspace, context);
5281
+ }],
5282
+ ["todos", () => {
5283
+ const rows = input.todos;
5284
+ return rows === void 0 || rows.length === 0 ? void 0 : todosPanel(rows, context);
5285
+ }],
5286
+ ["mcp", () => {
5287
+ const rows = input.mcp;
5288
+ return rows === void 0 || rows.length === 0 ? void 0 : mcpPanel(rows, context);
5289
+ }]
5290
+ ];
5291
+ for (const [title, render] of panels) {
5292
+ let rendered;
5293
+ try {
5294
+ rendered = render();
5295
+ } catch {
5296
+ rendered = [header(title, context), fit(` ${palette.muted}(unavailable)`, context)];
5297
+ }
5298
+ if (rendered === void 0 || rendered.length === 0) continue;
5299
+ if (lines.length > 0) lines.push(fit("", context));
5300
+ lines.push(...rendered);
5301
+ }
5302
+ return Object.freeze(lines);
5303
+ }
5304
+
5305
+ // src/sidebar-compositor.ts
5306
+ var SIDEBAR_DEFAULT_WIDTH = 40;
5307
+ var SIDEBAR_MIN_WIDTH = 24;
5308
+ var SIDEBAR_MAX_WIDTH = 60;
5309
+ var SIDEBAR_MAIN_COLUMN_FLOOR = 60;
5310
+ var GEOMETRY_FAILURE_LIMIT = 3;
5311
+ var ESC3 = "\x1B";
5312
+ var SYNC_START = `${ESC3}[?2026h`;
5313
+ var SYNC_END = `${ESC3}[?2026l`;
5314
+ var CURSOR_SAVE = `${ESC3}7`;
5315
+ var CURSOR_RESTORE = `${ESC3}8`;
5316
+ var WRAP_OFF = `${ESC3}[?7l`;
5317
+ var WRAP_ON = `${ESC3}[?7h`;
5318
+ var RESET3 = `${ESC3}[0m`;
5319
+ function unavailable(reason) {
5320
+ return Object.freeze({
5321
+ installed: false,
5322
+ reason,
5323
+ setWidth: () => void 0,
5324
+ dispose: () => void 0
5325
+ });
5326
+ }
5327
+ function columnsDescriptor(terminal) {
5328
+ let target = terminal;
5329
+ while (target !== null) {
5330
+ const descriptor = Object.getOwnPropertyDescriptor(target, "columns");
5331
+ if (descriptor !== void 0) return descriptor;
5332
+ target = Object.getPrototypeOf(target);
5333
+ }
5334
+ return void 0;
5335
+ }
5336
+ function probeSidebarSupport(tui, terminal, metrics) {
5337
+ if (typeof metrics?.visibleWidth !== "function" || typeof metrics?.truncateToWidth !== "function") {
5338
+ return { ok: false, reason: "no-metrics" };
5339
+ }
5340
+ if (typeof tui?.doRender !== "function") return { ok: false, reason: "no-dorender" };
5341
+ const descriptor = columnsDescriptor(terminal);
5342
+ if (descriptor === void 0 || descriptor.configurable !== true) {
5343
+ return { ok: false, reason: "columns-not-configurable" };
5344
+ }
5345
+ return { ok: true };
5346
+ }
5347
+ function clampWidth(width) {
5348
+ if (typeof width !== "number" || !Number.isFinite(width)) return SIDEBAR_DEFAULT_WIDTH;
5349
+ return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, Math.floor(width)));
5350
+ }
5351
+ var Compositor = class {
5352
+ constructor(ports, width) {
5353
+ this.ports = ports;
5354
+ this.width = width;
5355
+ this.hadOwnColumns = Object.getOwnPropertyDescriptor(ports.terminal, "columns") !== void 0;
5356
+ this.hadOwnDoRender = Object.getOwnPropertyDescriptor(ports.tui, "doRender") !== void 0;
5357
+ this.originalDescriptor = columnsDescriptor(ports.terminal);
5358
+ this.liveRawColumns = this.originalDescriptor.value;
5359
+ this.originalDoRender = ports.tui.doRender;
5360
+ this.defineNarrowedColumns();
5361
+ this.paintAfterRender = (...args) => {
5362
+ const result3 = this.originalDoRender.apply(ports.tui, args);
5363
+ this.paint();
5364
+ return result3;
5365
+ };
5366
+ ports.tui.doRender = this.paintAfterRender;
5367
+ }
5368
+ ports;
5369
+ active = true;
5370
+ width;
5371
+ geometryFailures = 0;
5372
+ warned = false;
5373
+ narrowedGetter;
5374
+ /** Live raw width for data-descriptor terminals: the host's resize handler
5375
+ * assigns `terminal.columns = N`, which our accessor's setter records here so
5376
+ * neither the host nor the paint path ever sees a stale install-time width. */
5377
+ liveRawColumns;
5378
+ hadOwnColumns;
5379
+ hadOwnDoRender;
5380
+ originalDescriptor;
5381
+ originalDoRender;
5382
+ paintAfterRender;
5383
+ get installed() {
5384
+ return this.active;
5385
+ }
5386
+ setWidth(width) {
5387
+ if (!this.active) return;
5388
+ this.width = clampWidth(width);
5389
+ this.defineNarrowedColumns();
5390
+ try {
5391
+ this.ports.tui.requestRender();
5392
+ } catch {
5393
+ }
5394
+ }
5395
+ dispose() {
5396
+ if (!this.active) return;
5397
+ this.active = false;
5398
+ try {
5399
+ const own = Object.getOwnPropertyDescriptor(this.ports.terminal, "columns");
5400
+ if (own?.get === this.narrowedGetter) {
5401
+ if (!this.hadOwnColumns) {
5402
+ delete this.ports.terminal.columns;
5403
+ } else if (this.originalDescriptor.get !== void 0) {
5404
+ Object.defineProperty(this.ports.terminal, "columns", this.originalDescriptor);
5405
+ } else {
5406
+ Object.defineProperty(this.ports.terminal, "columns", {
5407
+ ...this.originalDescriptor,
5408
+ value: this.liveRawColumns
5409
+ });
5410
+ }
5411
+ }
5412
+ } catch {
5413
+ }
5414
+ try {
5415
+ if (this.ports.tui.doRender === this.paintAfterRender) {
5416
+ if (this.hadOwnDoRender) this.ports.tui.doRender = this.originalDoRender;
5417
+ else delete this.ports.tui.doRender;
5418
+ }
5419
+ } catch {
5420
+ }
5421
+ try {
5422
+ this.ports.tui.requestRender();
5423
+ } catch {
5424
+ }
5425
+ }
5426
+ readRawColumns() {
5427
+ return this.originalDescriptor.get !== void 0 ? this.originalDescriptor.get.call(this.ports.terminal) : this.liveRawColumns;
5428
+ }
5429
+ /** "too-narrow" is a recoverable skip — the hooks stay installed and the
5430
+ * next paint re-checks; `undefined` means the surface is no longer
5431
+ * understood and counts toward disposal. */
5432
+ rawGeometry() {
5433
+ const own = Object.getOwnPropertyDescriptor(this.ports.terminal, "columns");
5434
+ if (own?.get !== this.narrowedGetter) return void 0;
5435
+ let rawColumns2;
5436
+ try {
5437
+ rawColumns2 = this.readRawColumns();
5438
+ } catch {
5439
+ return void 0;
5440
+ }
5441
+ const rows = this.ports.terminal.rows;
5442
+ if (typeof rawColumns2 !== "number" || !Number.isFinite(rawColumns2)) return void 0;
5443
+ if (typeof rows !== "number" || !Number.isFinite(rows) || rows <= 0) return void 0;
5444
+ if (rawColumns2 < this.width + SIDEBAR_MAIN_COLUMN_FLOOR) return "too-narrow";
5445
+ return { rawColumns: Math.floor(rawColumns2), rows: Math.floor(rows) };
5446
+ }
5447
+ defineNarrowedColumns() {
5448
+ const narrowed = () => {
5449
+ const geometry = this.rawGeometry();
5450
+ if (geometry === void 0 || geometry === "too-narrow") {
5451
+ try {
5452
+ return this.readRawColumns();
5453
+ } catch {
5454
+ return this.width + SIDEBAR_MAIN_COLUMN_FLOOR;
5455
+ }
5456
+ }
5457
+ return geometry.rawColumns - (this.width + 1);
5458
+ };
5459
+ this.narrowedGetter = narrowed;
5460
+ Object.defineProperty(this.ports.terminal, "columns", {
5461
+ configurable: true,
5462
+ enumerable: true,
5463
+ get: narrowed,
5464
+ // The host's resize handler assigns `columns`; route the assignment to
5465
+ // the original setter, or record it as the live raw width.
5466
+ set: (value) => {
5467
+ if (this.originalDescriptor.set !== void 0) {
5468
+ try {
5469
+ this.originalDescriptor.set.call(this.ports.terminal, value);
5470
+ } catch {
5471
+ }
5472
+ } else {
5473
+ this.liveRawColumns = value;
5474
+ }
5475
+ }
5476
+ });
5477
+ }
5478
+ /** AC-3: paint inside a synchronized-output envelope; every cycle
5479
+ * re-validates raw geometry first and a persistent failure disposes. */
5480
+ paint() {
5481
+ if (!this.active) return;
5482
+ try {
5483
+ const geometry = this.rawGeometry();
5484
+ if (geometry === "too-narrow") return;
5485
+ if (geometry === void 0) {
5486
+ this.geometryFailures += 1;
5487
+ if (this.geometryFailures >= GEOMETRY_FAILURE_LIMIT) this.dispose();
5488
+ return;
5489
+ }
5490
+ this.geometryFailures = 0;
5491
+ const lines = renderSidebar(this.ports.dataSource(), this.width, this.ports.metrics, {
5492
+ noColor: this.ports.noColor
5493
+ });
5494
+ const separatorColumn = geometry.rawColumns - this.width;
5495
+ const parts = [SYNC_START, CURSOR_SAVE, WRAP_OFF];
5496
+ for (let row = 1; row <= geometry.rows; row += 1) {
5497
+ const content = row - 1 < lines.length ? lines[row - 1] : " ".repeat(this.width);
5498
+ parts.push(`${ESC3}[${row};${separatorColumn}H${RESET3}\u2502${content}`);
5499
+ }
5500
+ parts.push(WRAP_ON, CURSOR_RESTORE, SYNC_END);
5501
+ this.ports.writeOut(parts.join(""));
5502
+ } catch {
5503
+ this.dispose();
5504
+ if (!this.warned) {
5505
+ this.warned = true;
5506
+ try {
5507
+ this.ports.notify("codeArbiter sidebar disabled after a paint failure; run /ca-doctor.");
5508
+ } catch {
5509
+ }
5510
+ }
5511
+ }
5512
+ }
5513
+ };
5514
+ function installSidebar(ports, options) {
5515
+ const probe = probeSidebarSupport(ports.tui, ports.terminal, ports.metrics);
5516
+ if (!probe.ok) return unavailable(probe.reason);
5517
+ const width = clampWidth(options.width);
5518
+ const descriptor = columnsDescriptor(ports.terminal);
5519
+ let rawColumns2;
5520
+ try {
5521
+ rawColumns2 = descriptor.get !== void 0 ? descriptor.get.call(ports.terminal) : descriptor.value;
5522
+ } catch {
5523
+ return unavailable("geometry-unreadable");
5524
+ }
5525
+ if (typeof rawColumns2 !== "number" || !Number.isFinite(rawColumns2) || rawColumns2 < width + SIDEBAR_MAIN_COLUMN_FLOOR) {
5526
+ return unavailable("terminal-too-narrow");
5527
+ }
5528
+ const hadOwnColumns = Object.getOwnPropertyDescriptor(ports.terminal, "columns") !== void 0;
5529
+ try {
5530
+ return new Compositor(ports, width);
5531
+ } catch {
5532
+ try {
5533
+ if (hadOwnColumns) Object.defineProperty(ports.terminal, "columns", descriptor);
5534
+ else delete ports.terminal.columns;
5535
+ } catch {
5536
+ }
5537
+ return unavailable("install-failed");
5538
+ }
5539
+ }
5540
+
5541
+ // src/sidebar-manager.ts
5542
+ var SIDEBAR_AUTO_ON_MIN_COLUMNS = 120;
5543
+ var SIDEBAR_SYNTAX = "Usage: /ca-sidebar on|off|toggle|width N (N clamped 24..60)";
5544
+ var DEGRADED_REASONS = Object.freeze(/* @__PURE__ */ new Set([
5545
+ "no-dorender",
5546
+ "columns-not-configurable",
5547
+ "no-metrics",
5548
+ "geometry-unreadable",
5549
+ "install-failed"
5550
+ ]));
5551
+ function clampWidth2(width) {
5552
+ return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, Math.floor(width)));
5553
+ }
5554
+ function rawColumns(terminal) {
5555
+ try {
5556
+ const value = terminal?.columns;
5557
+ return typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : void 0;
5558
+ } catch {
5559
+ return void 0;
5560
+ }
5561
+ }
5562
+ var Manager = class {
5563
+ constructor(ports) {
5564
+ this.ports = ports;
5565
+ }
5566
+ ports;
5567
+ registered = false;
5568
+ expected = false;
5569
+ compositor;
5570
+ width = SIDEBAR_DEFAULT_WIDTH;
5571
+ userDisabled = false;
5572
+ autoDecided = false;
5573
+ lastReason;
5574
+ metrics;
5575
+ register(context) {
5576
+ if (!interactiveParent(context)) return false;
5577
+ this.expected = true;
5578
+ if (!this.registered) {
5579
+ this.ports.pi.registerCommand("ca-sidebar", {
5580
+ description: "Toggle or resize the codeArbiter sidebar (on|off|toggle|width N).",
5581
+ handler: async (args, commandContext) => {
5582
+ await this.handle(args, commandContext);
5583
+ }
5584
+ });
5585
+ this.registered = true;
5586
+ }
5587
+ return true;
5588
+ }
5589
+ async autoInstall(context) {
5590
+ if (!interactiveParent(context)) return;
5591
+ if (this.autoDecided || this.userDisabled || this.compositor?.installed === true) return;
5592
+ if (this.ports.currentTui() === void 0) return;
5593
+ const columns = rawColumns(this.ports.terminal());
5594
+ if (columns === void 0) return;
5595
+ this.autoDecided = true;
5596
+ if (columns < SIDEBAR_AUTO_ON_MIN_COLUMNS) {
5597
+ this.lastReason = "auto-off-narrow";
5598
+ return;
5599
+ }
5600
+ await this.install(context);
5601
+ }
5602
+ dispose() {
5603
+ try {
5604
+ this.compositor?.dispose();
5605
+ } catch {
5606
+ }
5607
+ this.compositor = void 0;
5608
+ this.expected = false;
5609
+ this.userDisabled = false;
5610
+ this.autoDecided = false;
5611
+ this.lastReason = void 0;
5612
+ this.width = SIDEBAR_DEFAULT_WIDTH;
5613
+ }
5614
+ health() {
5615
+ const installed = this.compositor?.installed === true;
5616
+ const reason = installed ? void 0 : this.compositor?.reason ?? this.lastReason;
5617
+ return Object.freeze({
5618
+ expected: this.expected,
5619
+ installed,
5620
+ degraded: !installed && reason !== void 0 && DEGRADED_REASONS.has(reason),
5621
+ ...reason === void 0 ? {} : { reason }
5622
+ });
5623
+ }
5624
+ async install(context) {
5625
+ const tui = this.ports.currentTui();
5626
+ const terminal = this.ports.terminal();
5627
+ if (tui === void 0 || terminal === void 0) {
5628
+ this.lastReason = tui === void 0 ? "no-tui" : "no-terminal";
5629
+ return false;
5630
+ }
5631
+ if (this.metrics === void 0) {
5632
+ try {
5633
+ this.metrics = await this.ports.loadMetrics?.();
5634
+ } catch {
5635
+ this.metrics = void 0;
5636
+ }
5637
+ if (this.metrics === void 0) {
5638
+ this.lastReason = "no-metrics";
5639
+ return false;
5640
+ }
5641
+ }
5642
+ const notify = (message) => {
5643
+ try {
5644
+ context.ui.notify(message, "warning");
5645
+ } catch {
5646
+ }
5647
+ };
5648
+ const compositor = installSidebar({
5649
+ tui,
5650
+ terminal,
5651
+ metrics: this.metrics,
5652
+ writeOut: this.ports.writeOut,
5653
+ notify,
5654
+ dataSource: this.ports.dataSource,
5655
+ noColor: this.ports.noColor()
5656
+ }, { width: this.width });
5657
+ this.compositor = compositor;
5658
+ this.lastReason = compositor.installed ? void 0 : compositor.reason;
5659
+ return compositor.installed;
5660
+ }
5661
+ notifyInfo(context, message, level = "info") {
5662
+ try {
5663
+ context.ui.notify(message, level);
5664
+ } catch {
5665
+ }
5666
+ }
5667
+ async turnOn(context) {
5668
+ this.userDisabled = false;
5669
+ this.autoDecided = true;
5670
+ if (this.compositor?.installed === true) {
5671
+ this.notifyInfo(context, "codeArbiter sidebar is already on.");
5672
+ return;
5673
+ }
5674
+ const installed = await this.install(context);
5675
+ if (!installed) {
5676
+ this.notifyInfo(context, `codeArbiter sidebar unavailable (${this.lastReason ?? "unknown"}); native rendering is untouched.`, "warning");
5677
+ }
5678
+ }
5679
+ turnOff(context) {
5680
+ this.userDisabled = true;
5681
+ this.autoDecided = true;
5682
+ try {
5683
+ this.compositor?.dispose();
5684
+ } catch {
5685
+ }
5686
+ this.compositor = void 0;
5687
+ this.lastReason = "off";
5688
+ this.notifyInfo(context, "codeArbiter sidebar off.");
5689
+ }
5690
+ async handle(rawArgs, context) {
5691
+ if (!interactiveParent(context)) return;
5692
+ const args = typeof rawArgs === "string" ? rawArgs.trim().split(/\s+/u).filter(Boolean) : [];
5693
+ if (args.length === 0 || args.length === 1 && args[0] === "toggle") {
5694
+ if (this.compositor?.installed === true) this.turnOff(context);
5695
+ else await this.turnOn(context);
5696
+ return;
5697
+ }
5698
+ if (args.length === 1 && args[0] === "on") {
5699
+ await this.turnOn(context);
5700
+ return;
5701
+ }
5702
+ if (args.length === 1 && args[0] === "off") {
5703
+ this.turnOff(context);
5704
+ return;
5705
+ }
5706
+ if (args.length === 2 && args[0] === "width" && /^\d{1,4}$/u.test(args[1])) {
5707
+ this.width = clampWidth2(Number(args[1]));
5708
+ if (this.compositor?.installed === true) this.compositor.setWidth(this.width);
5709
+ else this.notifyInfo(context, `codeArbiter sidebar width set to ${this.width}; the sidebar is off.`);
5710
+ return;
5711
+ }
5712
+ this.notifyInfo(context, SIDEBAR_SYNTAX, "warning");
5713
+ }
5714
+ };
5715
+ function createSidebarManager(ports) {
5716
+ return new Manager(ports);
5717
+ }
5718
+
5719
+ // src/sidebar-data.ts
5720
+ function sessionFromFooter(footer) {
5721
+ const context = footer.context;
5722
+ const contextPercent = context !== void 0 && typeof context.windowTokens === "number" && Number.isFinite(context.windowTokens) && context.windowTokens > 0 && typeof context.usedTokens === "number" && Number.isFinite(context.usedTokens) ? context.usedTokens / context.windowTokens * 100 : void 0;
5723
+ const session = {
5724
+ ...footer.model?.name === void 0 ? {} : { model: footer.model.name },
5725
+ ...footer.model?.thinking === void 0 ? {} : { thinkingLevel: footer.model.thinking },
5726
+ ...contextPercent === void 0 ? {} : { contextPercent },
5727
+ ...footer.session === void 0 ? {} : {
5728
+ inputTokens: footer.session.inputTokens,
5729
+ outputTokens: footer.session.outputTokens,
5730
+ costUsd: footer.session.costUsd
5731
+ },
5732
+ ...footer.sparkline === void 0 || footer.sparkline.length === 0 ? {} : { burn: footer.sparkline }
5733
+ };
5734
+ return Object.keys(session).length === 0 ? void 0 : session;
5735
+ }
5736
+ function sidebarInputFromFooter(footer) {
5737
+ if (footer === void 0) return {};
5738
+ const session = sessionFromFooter(footer);
5739
+ const subagents = footer.activity !== void 0 && footer.activity.length > 0 ? footer.activity.map((item) => ({
5740
+ kind: item.kind,
5741
+ label: item.label,
5742
+ state: item.state,
5743
+ ...item.ageSeconds === void 0 ? {} : { ageSeconds: item.ageSeconds }
5744
+ })) : void 0;
5745
+ const workspace = footer.git === void 0 ? void 0 : {
5746
+ cwd: footer.folder,
5747
+ ...footer.git.repository === void 0 ? {} : { repository: footer.git.repository },
5748
+ ...footer.git.dirty === void 0 ? {} : { dirty: footer.git.dirty }
5749
+ };
5750
+ return {
5751
+ ...session === void 0 ? {} : { session },
5752
+ ...subagents === void 0 ? {} : { subagents },
5753
+ ...workspace === void 0 ? {} : { workspace }
5754
+ };
5755
+ }
5756
+ var TODO_STATE = Object.freeze({
5757
+ PENDING: "open",
5758
+ IN_PROGRESS: "active",
5759
+ ACCEPTED: "done"
5760
+ });
5761
+ function sidebarTodosFromPlan(state) {
5762
+ const tasks = state?.activePlan.tasks;
5763
+ if (tasks === void 0 || tasks.length === 0) return void 0;
5764
+ return tasks.map((task) => ({ text: task.id, state: TODO_STATE[task.status] }));
5765
+ }
5766
+
5082
5767
  // src/tool-guard.ts
5083
5768
  import { createHash as createHash5, randomUUID as randomUUID3 } from "node:crypto";
5084
5769
  import { realpathSync as realpathSync6 } from "node:fs";
@@ -6127,6 +6812,12 @@ async function collectPiDoctorInput(dependencies) {
6127
6812
  expected: dependencies.footerExpected === true,
6128
6813
  initialized: dependencies.footerInitialized === true
6129
6814
  },
6815
+ sidebar: {
6816
+ expected: dependencies.sidebarExpected === true,
6817
+ installed: dependencies.sidebarInstalled === true,
6818
+ degraded: dependencies.sidebarDegraded === true,
6819
+ ...dependencies.sidebarReason === void 0 ? {} : { reason: dependencies.sidebarReason }
6820
+ },
6130
6821
  background: {
6131
6822
  expected: dependencies.backgroundExpected === true,
6132
6823
  initialized: dependencies.backgroundInitialized === true,
@@ -6163,6 +6854,7 @@ var REMEDIATION = {
6163
6854
  "module-identity": "Reinstall the active Pi CLI and ca-pi from their approved origins, then restart Pi.",
6164
6855
  "final-arguments": "Reinstall ca-pi, remove competing mutating tool definitions, and run /ca-doctor again.",
6165
6856
  footer: "Restart Pi in an interactive parent session; if the rich footer still fails, reinstall ca-pi and run /ca-doctor again.",
6857
+ sidebar: "Run /ca-sidebar on in a wide terminal; if the probe still reports unavailable, this Pi build changed its render surface \u2014 keep the footer and file an issue.",
6166
6858
  background: "Stop active work, restart Pi, and run /ca-doctor before launching another background job.",
6167
6859
  "active-dispatch": "Require passing supported-version real-host promotion/CI evidence before closing PI-AC-28."
6168
6860
  };
@@ -6261,6 +6953,12 @@ function diagnosePi(input) {
6261
6953
  input.footer.expected ? "The rich footer initialized in the current interactive parent session." : "The rich footer is intentionally absent outside an active interactive parent session.",
6262
6954
  input.footer.expected ? "The rich footer did not initialize in the current interactive parent session." : "The rich footer initialized outside an active interactive parent session; isolation is breached."
6263
6955
  ),
6956
+ diagnosis(
6957
+ "sidebar",
6958
+ input.sidebar.expected ? !input.sidebar.degraded : !input.sidebar.installed && !input.sidebar.degraded,
6959
+ input.sidebar.expected ? input.sidebar.installed ? "The probe-gated sidebar compositor is installed." : `The sidebar is not installed (${input.sidebar.reason ?? "off"}); native rendering is untouched.` : "The sidebar is intentionally absent outside an interactive parent session.",
6960
+ !input.sidebar.expected && input.sidebar.installed ? "The sidebar compositor installed outside an interactive parent session; isolation is breached." : `The sidebar hook probe failed (${input.sidebar.reason ?? "unknown"}); this Pi build changed its render surface and native rendering is untouched.`
6961
+ ),
6264
6962
  diagnosis(
6265
6963
  "background",
6266
6964
  input.background.expected ? input.background.initialized && input.background.healthy : !input.background.initialized,
@@ -9024,6 +9722,88 @@ async function appendPiCompactionAudit(record2) {
9024
9722
  }
9025
9723
 
9026
9724
  // src/extension.ts
9725
+ var COMMAND_VISIBILITY_ORDER = ["core", "advanced", "alias", "internal", "deprecated"];
9726
+ var COMMAND_WORKFLOW_ORDER = [
9727
+ "evaluate",
9728
+ "initialize",
9729
+ "change",
9730
+ "review",
9731
+ "decide",
9732
+ "ship",
9733
+ "operate",
9734
+ "extend",
9735
+ "help"
9736
+ ];
9737
+ function plainRecord2(value) {
9738
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
9739
+ }
9740
+ function sameStrings2(actual, expected) {
9741
+ return actual.length === expected.length && actual.every((item, index) => item === expected[index]);
9742
+ }
9743
+ function validLegacyRoutes(value) {
9744
+ return Array.isArray(value) && value.every((item) => typeof item === "string" && /^[a-z][a-z0-9-]*$/u.test(item)) && new Set(value).size === value.length && sameStrings2(value, [...value].sort());
9745
+ }
9746
+ function commandCatalogEntries(value) {
9747
+ const envelopeKeys = ["commands", "compatibility", "schemaVersion", "visibilityOrder", "workflowOrder"];
9748
+ if (!plainRecord2(value) || !sameStrings2(Object.keys(value).sort(), envelopeKeys) || value.schemaVersion !== 1 || !Array.isArray(value.visibilityOrder) || !sameStrings2(value.visibilityOrder, COMMAND_VISIBILITY_ORDER) || !Array.isArray(value.workflowOrder) || !sameStrings2(value.workflowOrder, COMMAND_WORKFLOW_ORDER) || !plainRecord2(value.compatibility) || !plainRecord2(value.commands)) {
9749
+ throw new Error("codeArbiter Pi command catalog envelope is invalid; run /ca-doctor.");
9750
+ }
9751
+ const commands = value.commands;
9752
+ const names = Object.keys(commands);
9753
+ if (!sameStrings2(names, [...names].sort()) || names.length === 0) {
9754
+ throw new Error("codeArbiter Pi command catalog envelope has no sorted commands; run /ca-doctor.");
9755
+ }
9756
+ const entries = [];
9757
+ for (const name of names) {
9758
+ const fail2 = () => {
9759
+ throw new Error(`codeArbiter Pi command catalog entry ${name} is invalid; run /ca-doctor.`);
9760
+ };
9761
+ const value2 = commands[name];
9762
+ if (!plainRecord2(value2)) {
9763
+ throw new Error(`codeArbiter Pi command catalog entry ${name} is invalid; run /ca-doctor.`);
9764
+ }
9765
+ const raw = value2;
9766
+ if (raw.name !== name || typeof raw.description !== "string" || raw.description === "" || raw.skillPath !== `skills/ca-${name}/SKILL.md` || !COMMAND_VISIBILITY_ORDER.includes(raw.visibility) || !COMMAND_WORKFLOW_ORDER.includes(raw.workflow)) {
9767
+ fail2();
9768
+ }
9769
+ const visibility = raw.visibility;
9770
+ const commonKeys = ["description", "name", "skillPath", "visibility", "workflow"];
9771
+ let expectedKeys;
9772
+ if (visibility === "core" || visibility === "advanced" || visibility === "internal") {
9773
+ expectedKeys = [...commonKeys, "canonical", "legacyRoutes"].sort();
9774
+ if (raw.canonical !== name || !validLegacyRoutes(raw.legacyRoutes)) fail2();
9775
+ } else if (visibility === "alias") {
9776
+ expectedKeys = [...commonKeys, "canonical", "replacement"].sort();
9777
+ if (typeof raw.canonical !== "string" || !/^[a-z][a-z0-9-]*$/u.test(raw.canonical) || typeof raw.replacement !== "string" || !raw.replacement.startsWith(`${raw.canonical} `)) fail2();
9778
+ } else {
9779
+ expectedKeys = [...commonKeys, "replacement"].sort();
9780
+ if (typeof raw.replacement !== "string" || raw.replacement === "") fail2();
9781
+ }
9782
+ if (!sameStrings2(Object.keys(raw).sort(), expectedKeys)) fail2();
9783
+ entries.push(Object.freeze({ ...raw }));
9784
+ }
9785
+ const entriesByName = new Map(entries.map((entry) => [entry.name, entry]));
9786
+ const expectedLegacyRoutes = /* @__PURE__ */ new Map();
9787
+ for (const entry of entries) {
9788
+ if (entry.visibility === "alias") {
9789
+ const target = entriesByName.get(entry.canonical);
9790
+ if (target === void 0 || target.visibility === "alias" || target.visibility === "deprecated") {
9791
+ throw new Error(`codeArbiter Pi command catalog entry ${entry.name} has an invalid alias target; run /ca-doctor.`);
9792
+ }
9793
+ const routes = expectedLegacyRoutes.get(entry.canonical) ?? [];
9794
+ routes.push(entry.name);
9795
+ expectedLegacyRoutes.set(entry.canonical, routes);
9796
+ }
9797
+ }
9798
+ for (const entry of entries) {
9799
+ if (entry.visibility === "alias" || entry.visibility === "deprecated") continue;
9800
+ const expected = (expectedLegacyRoutes.get(entry.name) ?? []).sort();
9801
+ if (!sameStrings2(entry.legacyRoutes ?? [], expected)) {
9802
+ throw new Error(`codeArbiter Pi command catalog entry ${entry.name} has broken legacy-route closure; run /ca-doctor.`);
9803
+ }
9804
+ }
9805
+ return Object.freeze(entries);
9806
+ }
9027
9807
  var PI_TRUST_REQUIRED_STATUS = "codeArbiter host: pi waiting for project trust - run /trust in Pi, approve this project, then start a new session";
9028
9808
  function hasAffirmativeProjectTrust(context) {
9029
9809
  try {
@@ -9167,6 +9947,19 @@ function ownershipStatus(pi, dependencies, nativePlanRegistered = false, nativeJ
9167
9947
  }
9168
9948
  return `codeArbiter host: pi degraded - ${native.length + jobs.length} native command ownership conflict(s); operations blocked`;
9169
9949
  }
9950
+ var PERSONA_SENTINEL = "<!-- codearbiter:persona-sentinel -->";
9951
+ async function loadPersonaFrom(packageRoot) {
9952
+ const [safetyCore, body] = await Promise.all([
9953
+ readFile6(resolve15(packageRoot, "includes", "safety-core.md"), "utf8"),
9954
+ readFile6(resolve15(packageRoot, "arbiter.md"), "utf8")
9955
+ ]);
9956
+ return `${safetyCore.replace(/\n+$/, "")}
9957
+
9958
+ ${body.replace(/\n+$/, "")}
9959
+
9960
+ ${PERSONA_SENTINEL}
9961
+ `;
9962
+ }
9170
9963
  function installParent(pi, dependencies) {
9171
9964
  let enabled = false;
9172
9965
  let persona = "";
@@ -9199,6 +9992,21 @@ function installParent(pi, dependencies) {
9199
9992
  currentActivity2,
9200
9993
  dependencies.collectGitFacts ?? collectGitFacts
9201
9994
  );
9995
+ const sidebar = createSidebarManager({
9996
+ pi,
9997
+ currentTui: () => footer.currentTui(),
9998
+ terminal: () => process.stdout,
9999
+ ...loadFooterMetrics === void 0 ? {} : { loadMetrics: loadFooterMetrics },
10000
+ dataSource: () => {
10001
+ const input = sidebarInputFromFooter(footer.lastRenderedInput());
10002
+ const todos = sidebarTodosFromPlan(plan?.status());
10003
+ return todos === void 0 ? input : { ...input, todos };
10004
+ },
10005
+ writeOut: (text2) => {
10006
+ process.stdout.write(text2);
10007
+ },
10008
+ noColor: () => Object.prototype.hasOwnProperty.call(process.env, "NO_COLOR")
10009
+ });
9202
10010
  const readActivation = dependencies.readActivation ?? isEnabled;
9203
10011
  dependencies.installDispatch?.(() => readyLifecycle, currentActivity2);
9204
10012
  dependencies.installCompaction?.(() => readyLifecycle);
@@ -9233,6 +10041,7 @@ function installParent(pi, dependencies) {
9233
10041
  }
9234
10042
  return Object.freeze({
9235
10043
  footer: footerHealth,
10044
+ sidebar: sidebar.health(),
9236
10045
  background: Object.freeze({
9237
10046
  expected: backgroundExpected,
9238
10047
  initialized: backgroundInitialized,
@@ -9253,6 +10062,7 @@ function installParent(pi, dependencies) {
9253
10062
  activeLifecycle = void 0;
9254
10063
  readyLifecycle = void 0;
9255
10064
  if (background !== void 0) await background.stop("session-switch");
10065
+ sidebar.dispose();
9256
10066
  activity?.dispose();
9257
10067
  activity = void 0;
9258
10068
  dependencies.enforcementReadiness?.deactivate();
@@ -9275,6 +10085,9 @@ function installParent(pi, dependencies) {
9275
10085
  readUpdateVersion: dependencies.readFooterUpdateVersion
9276
10086
  });
9277
10087
  if (!isCurrent()) return;
10088
+ sidebar.register(context);
10089
+ await sidebar.autoInstall(context);
10090
+ if (!isCurrent()) return;
9278
10091
  if (!markerEnabled) {
9279
10092
  activeLifecycle = void 0;
9280
10093
  dependencies.enforcementReadiness?.deactivate();
@@ -9370,13 +10183,16 @@ function installParent(pi, dependencies) {
9370
10183
  if (enabled) publishStatus(context, degradedStatus() ?? "codeArbiter host: pi governed");
9371
10184
  });
9372
10185
  pi.on("agent_settled", async (_event, context) => {
10186
+ const sequence = lifecycleSequence;
9373
10187
  await footer.refresh(context, { activation: { enabled: footerActivationEnabled } });
10188
+ if (sequence === lifecycleSequence) await sidebar.autoInstall(context);
9374
10189
  if (enabled) publishStatus(context, degradedStatus());
9375
10190
  });
9376
10191
  const stopSession = async (reason, context) => {
9377
10192
  readyLifecycle = void 0;
9378
10193
  activeLifecycle = void 0;
9379
10194
  await background?.stop(reason);
10195
+ sidebar.dispose();
9380
10196
  activity?.dispose();
9381
10197
  activity = void 0;
9382
10198
  footer.dispose();
@@ -9523,7 +10339,9 @@ async function codeArbiterPi(pi) {
9523
10339
  if (parent === packageRoot) throw new Error("codeArbiter could not locate the ca-pi package; run /ca-doctor.");
9524
10340
  packageRoot = parent;
9525
10341
  }
9526
- const catalog = JSON.parse(await readFile6(resolve15(packageRoot, "generated", "command-catalog.json"), "utf8"));
10342
+ const catalog = commandCatalogEntries(
10343
+ JSON.parse(await readFile6(resolve15(packageRoot, "generated", "command-catalog.json"), "utf8"))
10344
+ );
9527
10345
  const toolClasses = loadPiToolClasses(define_CODEARBITER_PI_TOOL_CLASSES_default);
9528
10346
  const rawPermissionSurfaces = define_CODEARBITER_PI_PERMISSION_POLICY_SURFACES_default;
9529
10347
  if (rawPermissionSurfaces === null || typeof rawPermissionSurfaces !== "object" || Array.isArray(rawPermissionSurfaces)) {
@@ -9601,7 +10419,7 @@ async function codeArbiterPi(pi) {
9601
10419
  pi.appendEntry(customType, data);
9602
10420
  },
9603
10421
  enforcementReadiness: enforcement,
9604
- loadPersona: async () => await readFile6(resolve15(packageRoot, "ORCHESTRATOR.md"), "utf8"),
10422
+ loadPersona: async () => await loadPersonaFrom(packageRoot),
9605
10423
  resetBridge,
9606
10424
  prepareFooterBridge: (cwd) => {
9607
10425
  prepareBridgeIdentity(cwd);
@@ -9720,6 +10538,10 @@ async function codeArbiterPi(pi) {
9720
10538
  bridgePrepared: enabledForDoctor && trustedForDoctor && pythonResolutionAttempted,
9721
10539
  footerExpected: health.footer.expected,
9722
10540
  footerInitialized: health.footer.initialized,
10541
+ sidebarExpected: health.sidebar.expected,
10542
+ sidebarInstalled: health.sidebar.installed,
10543
+ sidebarDegraded: health.sidebar.degraded,
10544
+ ...health.sidebar.reason === void 0 ? {} : { sidebarReason: health.sidebar.reason },
9723
10545
  backgroundExpected: health.background.expected,
9724
10546
  backgroundInitialized: health.background.initialized,
9725
10547
  backgroundHealthy: health.background.healthy,
@@ -9791,8 +10613,10 @@ async function codeArbiterPi(pi) {
9791
10613
  });
9792
10614
  }
9793
10615
  export {
10616
+ PERSONA_SENTINEL,
9794
10617
  PI_RUNTIME_DIAGNOSIS,
9795
10618
  boundedPiEnvironment,
10619
+ commandCatalogEntries,
9796
10620
  compatibilityDirection,
9797
10621
  createCodeArbiterPi,
9798
10622
  createPiFooterMetricsLoader,
@@ -9802,6 +10626,7 @@ export {
9802
10626
  installParent,
9803
10627
  installPiDispatch,
9804
10628
  installPiFarmPreview,
10629
+ loadPersonaFrom,
9805
10630
  renderPiDoctorReportBlock,
9806
10631
  resolvePiBackgroundShell,
9807
10632
  resolvePiRuntime,