@arbiterforge/ca-pi 0.6.2 → 0.8.1

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 (39) hide show
  1. package/README.md +21 -17
  2. package/package.json +1 -1
  3. package/plugins/ca-pi/CHANGELOG.md +74 -0
  4. package/plugins/ca-pi/COMMANDS.md +0 -7
  5. package/plugins/ca-pi/SKILLS.md +0 -2
  6. package/plugins/ca-pi/agents/design-quality-reviewer.md +1 -1
  7. package/plugins/ca-pi/{ORCHESTRATOR.md → arbiter.md} +28 -36
  8. package/plugins/ca-pi/extensions/codearbiter-child.js +2 -2
  9. package/plugins/ca-pi/extensions/codearbiter.js +766 -26
  10. package/plugins/ca-pi/generated/command-catalog.json +0 -10
  11. package/plugins/ca-pi/hooks/_arbiterstatelib.py +59 -11
  12. package/plugins/ca-pi/hooks/_bashguardlib.py +12 -1
  13. package/plugins/ca-pi/hooks/_hooklib.py +58 -17
  14. package/plugins/ca-pi/hooks/_metricslib.py +20 -0
  15. package/plugins/ca-pi/hooks/_modelib.py +644 -0
  16. package/plugins/ca-pi/hooks/_prunelib.py +51 -12
  17. package/plugins/ca-pi/hooks/_prunepolicy.py +33 -7
  18. package/plugins/ca-pi/hooks/_readinjectlib.py +10 -4
  19. package/plugins/ca-pi/hooks/doctor.py +2 -1
  20. package/plugins/ca-pi/hooks/pi-bridge.py +10 -4
  21. package/plugins/ca-pi/hooks/prompt-submit.py +486 -0
  22. package/plugins/ca-pi/hooks/prune-transcript.py +23 -3
  23. package/plugins/ca-pi/hooks/session-start.py +526 -434
  24. package/plugins/ca-pi/hooks/statusline.py +27 -9
  25. package/plugins/ca-pi/includes/anti-slop-design/INDEX.md +1 -1
  26. package/plugins/ca-pi/includes/dangerous-mode.md +57 -0
  27. package/plugins/ca-pi/includes/ops-mode.md +96 -0
  28. package/plugins/ca-pi/includes/pi-host-notes.md +14 -3
  29. package/plugins/ca-pi/includes/redirect.md +12 -1
  30. package/plugins/ca-pi/includes/routing-table.md +1 -0
  31. package/plugins/ca-pi/includes/safety-core.md +86 -0
  32. package/plugins/ca-pi/includes/smarts/core.md +1 -1
  33. package/plugins/ca-pi/routines/decision-lifecycle/SKILL.md +1 -1
  34. package/plugins/ca-pi/routines/decompose/SKILL.md +1 -1
  35. package/plugins/ca-pi/skills/ca-doctor/SKILL.md +1 -1
  36. package/plugins/ca-pi/skills/ca-spike/SKILL.md +15 -8
  37. package/plugins/ca-pi/includes/dev-mode.md +0 -30
  38. package/plugins/ca-pi/skills/ca-arbiter/SKILL.md +0 -36
  39. package/plugins/ca-pi/skills/ca-dev/SKILL.md +0 -42
@@ -2,7 +2,7 @@
2
2
  var define_CODEARBITER_PI_PERMISSION_POLICY_SURFACES_default = { "ca-plan": "planning-write", codearbiter_background_bash: "background-launch" };
3
3
 
4
4
  // <define:__CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS__>
5
- var define_CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS_default = { "0.80.5": "12632f365440b07d5183cff871d889b796a3c711b6b49df20f95d9bc198d6c51", "0.80.10": "12632f365440b07d5183cff871d889b796a3c711b6b49df20f95d9bc198d6c51" };
5
+ var define_CODEARBITER_PI_SKILL_EXPANSION_FINGERPRINTS_default = { "0.80.5": "12632f365440b07d5183cff871d889b796a3c711b6b49df20f95d9bc198d6c51", "0.84.1": "12632f365440b07d5183cff871d889b796a3c711b6b49df20f95d9bc198d6c51" };
6
6
 
7
7
  // <define:__CODEARBITER_PI_TOOL_CLASSES__>
8
8
  var define_CODEARBITER_PI_TOOL_CLASSES_default = { bash: "EXEC", codearbiter_background_bash: "EXEC", codearbiter_dispatch: "EXEC", codearbiter_farm_preview: "EXEC", write: "WRITE", edit: "EDIT", read: "READ" };
@@ -16,7 +16,7 @@ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from
16
16
  import { types as utilTypes9 } from "node:util";
17
17
 
18
18
  // src/compatibility.ts
19
- var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["0.80.5", "0.80.10"]);
19
+ var SUPPORTED_PI_VERSIONS = /* @__PURE__ */ new Set(["0.80.5", "0.84.1"]);
20
20
  var MINIMUM_NODE = [22, 19, 0];
21
21
  var SEMVER_PREFIX = /^(\d+)\.(\d+)\.(\d+)(?:$|[-+])/u;
22
22
  function atLeast(version, minimum) {
@@ -31,7 +31,7 @@ function atLeast(version, minimum) {
31
31
  }
32
32
  function compatibilityDirection(input) {
33
33
  if (!SUPPORTED_PI_VERSIONS.has(input.piVersion)) {
34
- return "codeArbiter requires Pi 0.80.5 or 0.80.10; install a supported Pi version and run /ca-doctor.";
34
+ return "codeArbiter requires Pi 0.80.5 or 0.84.1; install a supported Pi version and run /ca-doctor.";
35
35
  }
36
36
  if (!atLeast(input.nodeVersion, MINIMUM_NODE)) {
37
37
  return "codeArbiter requires Node >=22.19.0 for Pi; upgrade Node and run /ca-doctor.";
@@ -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,
@@ -6153,16 +6844,17 @@ async function collectPiDoctorInput(dependencies) {
6153
6844
  var REMEDIATION = {
6154
6845
  package: "Reinstall ca-pi from the approved pinned Git tag, then restart Pi.",
6155
6846
  trust: "Run /trust in Pi, inspect the project, grant trust only if you accept it, then start a new session.",
6156
- version: "Upgrade Pi to 0.80.5 or 0.80.10 and Node to >=22.19.0, then restart Pi.",
6847
+ version: "Upgrade Pi to 0.80.5 or 0.84.1 and Node to >=22.19.0, then restart Pi.",
6157
6848
  python: "Upgrade or install Python 3, then run /ca-doctor again.",
6158
6849
  core: "Reinstall ca-pi to restore the generated shared core, then run /ca-doctor again.",
6159
- commands: "Remove conflicting command owners or run Pi 0.80.5/0.80.10, then restart Pi and run /ca-doctor.",
6850
+ commands: "Remove conflicting command owners or run Pi 0.80.5/0.84.1, then restart Pi and run /ca-doctor.",
6160
6851
  bridge: "Reinstall ca-pi and Python 3, then run /ca-doctor again.",
6161
6852
  child: "Reinstall ca-pi if the hardened child artifact is missing or tampered, then run /ca-doctor again.",
6162
6853
  "ambient-marker": "Remove CODEARBITER_SUBAGENT from the parent environment and restart Pi.",
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
  };
@@ -6184,7 +6876,7 @@ function diagnosePi(input) {
6184
6876
  const packageHealthy = input.package.declared && input.package.name === "@arbiterforge/ca-pi" && existsSync(input.package.root) && existsSync(input.package.extensionPath) && samePath2(input.package.extensionPath, expectedExtension) && canonicallyInside(input.package.extensionPath, input.package.root);
6185
6877
  const trustHealthy = input.trust.inspected && (!input.trust.required || input.trust.projectTrusted);
6186
6878
  const waitingForTrust = input.trust.required && !input.trust.projectTrusted;
6187
- const versionHealthy = ["0.80.5", "0.80.10"].includes(input.runtime.piVersion) && atLeast(input.runtime.nodeVersion, [22, 19, 0]);
6879
+ const versionHealthy = ["0.80.5", "0.84.1"].includes(input.runtime.piVersion) && atLeast(input.runtime.nodeVersion, [22, 19, 0]);
6188
6880
  const piBelowMinimum = !atLeast(input.runtime.piVersion, [0, 80, 5]);
6189
6881
  const supportedExpansion = input.commands.expansionVerifiedVersions.includes(input.runtime.piVersion);
6190
6882
  const expectedDoctorSkill = resolve9(input.package.root, "skills", "ca-doctor", "SKILL.md");
@@ -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,
@@ -6299,7 +6997,7 @@ function diagnosePi(input) {
6299
6997
  {
6300
6998
  id: "active-dispatch",
6301
6999
  state: "degraded",
6302
- message: "Supported Pi 0.80.5/0.80.10 public extension APIs cannot submit this deterministic self-test through the active dispatcher; the wrapper self-test does not exercise active dispatch.",
7000
+ message: "Supported Pi 0.80.5/0.84.1 public extension APIs cannot submit this deterministic self-test through the active dispatcher; the wrapper self-test does not exercise active dispatch.",
6303
7001
  remediation: REMEDIATION["active-dispatch"]
6304
7002
  }
6305
7003
  ];
@@ -9167,6 +9865,19 @@ function ownershipStatus(pi, dependencies, nativePlanRegistered = false, nativeJ
9167
9865
  }
9168
9866
  return `codeArbiter host: pi degraded - ${native.length + jobs.length} native command ownership conflict(s); operations blocked`;
9169
9867
  }
9868
+ var PERSONA_SENTINEL = "<!-- codearbiter:persona-sentinel -->";
9869
+ async function loadPersonaFrom(packageRoot) {
9870
+ const [safetyCore, body] = await Promise.all([
9871
+ readFile6(resolve15(packageRoot, "includes", "safety-core.md"), "utf8"),
9872
+ readFile6(resolve15(packageRoot, "arbiter.md"), "utf8")
9873
+ ]);
9874
+ return `${safetyCore.replace(/\n+$/, "")}
9875
+
9876
+ ${body.replace(/\n+$/, "")}
9877
+
9878
+ ${PERSONA_SENTINEL}
9879
+ `;
9880
+ }
9170
9881
  function installParent(pi, dependencies) {
9171
9882
  let enabled = false;
9172
9883
  let persona = "";
@@ -9199,6 +9910,21 @@ function installParent(pi, dependencies) {
9199
9910
  currentActivity2,
9200
9911
  dependencies.collectGitFacts ?? collectGitFacts
9201
9912
  );
9913
+ const sidebar = createSidebarManager({
9914
+ pi,
9915
+ currentTui: () => footer.currentTui(),
9916
+ terminal: () => process.stdout,
9917
+ ...loadFooterMetrics === void 0 ? {} : { loadMetrics: loadFooterMetrics },
9918
+ dataSource: () => {
9919
+ const input = sidebarInputFromFooter(footer.lastRenderedInput());
9920
+ const todos = sidebarTodosFromPlan(plan?.status());
9921
+ return todos === void 0 ? input : { ...input, todos };
9922
+ },
9923
+ writeOut: (text2) => {
9924
+ process.stdout.write(text2);
9925
+ },
9926
+ noColor: () => Object.prototype.hasOwnProperty.call(process.env, "NO_COLOR")
9927
+ });
9202
9928
  const readActivation = dependencies.readActivation ?? isEnabled;
9203
9929
  dependencies.installDispatch?.(() => readyLifecycle, currentActivity2);
9204
9930
  dependencies.installCompaction?.(() => readyLifecycle);
@@ -9233,6 +9959,7 @@ function installParent(pi, dependencies) {
9233
9959
  }
9234
9960
  return Object.freeze({
9235
9961
  footer: footerHealth,
9962
+ sidebar: sidebar.health(),
9236
9963
  background: Object.freeze({
9237
9964
  expected: backgroundExpected,
9238
9965
  initialized: backgroundInitialized,
@@ -9253,6 +9980,7 @@ function installParent(pi, dependencies) {
9253
9980
  activeLifecycle = void 0;
9254
9981
  readyLifecycle = void 0;
9255
9982
  if (background !== void 0) await background.stop("session-switch");
9983
+ sidebar.dispose();
9256
9984
  activity?.dispose();
9257
9985
  activity = void 0;
9258
9986
  dependencies.enforcementReadiness?.deactivate();
@@ -9275,6 +10003,9 @@ function installParent(pi, dependencies) {
9275
10003
  readUpdateVersion: dependencies.readFooterUpdateVersion
9276
10004
  });
9277
10005
  if (!isCurrent()) return;
10006
+ sidebar.register(context);
10007
+ await sidebar.autoInstall(context);
10008
+ if (!isCurrent()) return;
9278
10009
  if (!markerEnabled) {
9279
10010
  activeLifecycle = void 0;
9280
10011
  dependencies.enforcementReadiness?.deactivate();
@@ -9370,13 +10101,16 @@ function installParent(pi, dependencies) {
9370
10101
  if (enabled) publishStatus(context, degradedStatus() ?? "codeArbiter host: pi governed");
9371
10102
  });
9372
10103
  pi.on("agent_settled", async (_event, context) => {
10104
+ const sequence = lifecycleSequence;
9373
10105
  await footer.refresh(context, { activation: { enabled: footerActivationEnabled } });
10106
+ if (sequence === lifecycleSequence) await sidebar.autoInstall(context);
9374
10107
  if (enabled) publishStatus(context, degradedStatus());
9375
10108
  });
9376
10109
  const stopSession = async (reason, context) => {
9377
10110
  readyLifecycle = void 0;
9378
10111
  activeLifecycle = void 0;
9379
10112
  await background?.stop(reason);
10113
+ sidebar.dispose();
9380
10114
  activity?.dispose();
9381
10115
  activity = void 0;
9382
10116
  footer.dispose();
@@ -9601,7 +10335,7 @@ async function codeArbiterPi(pi) {
9601
10335
  pi.appendEntry(customType, data);
9602
10336
  },
9603
10337
  enforcementReadiness: enforcement,
9604
- loadPersona: async () => await readFile6(resolve15(packageRoot, "ORCHESTRATOR.md"), "utf8"),
10338
+ loadPersona: async () => await loadPersonaFrom(packageRoot),
9605
10339
  resetBridge,
9606
10340
  prepareFooterBridge: (cwd) => {
9607
10341
  prepareBridgeIdentity(cwd);
@@ -9720,6 +10454,10 @@ async function codeArbiterPi(pi) {
9720
10454
  bridgePrepared: enabledForDoctor && trustedForDoctor && pythonResolutionAttempted,
9721
10455
  footerExpected: health.footer.expected,
9722
10456
  footerInitialized: health.footer.initialized,
10457
+ sidebarExpected: health.sidebar.expected,
10458
+ sidebarInstalled: health.sidebar.installed,
10459
+ sidebarDegraded: health.sidebar.degraded,
10460
+ ...health.sidebar.reason === void 0 ? {} : { sidebarReason: health.sidebar.reason },
9723
10461
  backgroundExpected: health.background.expected,
9724
10462
  backgroundInitialized: health.background.initialized,
9725
10463
  backgroundHealthy: health.background.healthy,
@@ -9729,7 +10467,7 @@ async function codeArbiterPi(pi) {
9729
10467
  activeTools: pi.getActiveTools(),
9730
10468
  allTools: pi.getAllTools(),
9731
10469
  expansionFingerprints,
9732
- childFingerprint: "8b2422b7b8daa66b19f140136f0d9a6ebb3681527ae8bad25b049349771011a8"
10470
+ childFingerprint: "c9573777b4c59abfb4adbe6879fc03de7f7962327d97c378c2fb71d300a4e22f"
9733
10471
  });
9734
10472
  const wrapperSelfTest = await runPiWrapperSelfTest({
9735
10473
  enabled: enabledForDoctor,
@@ -9791,6 +10529,7 @@ async function codeArbiterPi(pi) {
9791
10529
  });
9792
10530
  }
9793
10531
  export {
10532
+ PERSONA_SENTINEL,
9794
10533
  PI_RUNTIME_DIAGNOSIS,
9795
10534
  boundedPiEnvironment,
9796
10535
  compatibilityDirection,
@@ -9802,6 +10541,7 @@ export {
9802
10541
  installParent,
9803
10542
  installPiDispatch,
9804
10543
  installPiFarmPreview,
10544
+ loadPersonaFrom,
9805
10545
  renderPiDoctorReportBlock,
9806
10546
  resolvePiBackgroundShell,
9807
10547
  resolvePiRuntime,