f1ceeb4363
Audit produced five concrete improvements:
1) New shared/ module (zero-dep pure utilities)
- shared/ansi.ts: hexToRgb (throws on malformed input instead of
silently producing NaN), fgFromHex, stripAnsi, visibleWidth,
ANSI_RESET_FG / ANSI_RESET_ALL constants.
- shared/format.ts: formatTokens, formatElapsed.
- shared/ctx.ts: safely() and safelyAsync() helpers for dealing with
pi's "stale after session replacement or reload" ExtensionRunner
semantics.
Removes duplicate helpers from mechanicus-footer, markdown-body-color,
dark-mechanicus-indicator.
2) ai-server: non-blocking startup + short-race timeout
- Factory registers STATIC_MODELS immediately so pi startup isn't
blocked on the HTTPS round-trip.
- Races discoverModels() against a 300ms timeout. On LAN (~40ms) the
live list wins and pi --list-models sees the real models. Past the
timeout, fallback remains and background discovery updates the
provider later.
- listModelsCached() with 5s TTL for tab completions (was firing a
round-trip on every keystroke).
- loadModel/unloadModel invalidate the cache.
3) dark-mechanicus-indicator: stale-ctx guard
- Wrap the setInterval ticker body in safely() so a race between
session_shutdown and the ticker can't crash node. Same pattern as
the earlier footer fix.
4) Safer monkey-patches in markdown-body-color and mechanicus-thinking-label
- Feature-detect Markdown/Editor/AssistantMessageComponent's target
method before patching. Warn-and-skip rather than silently create
a broken prototype if a pi-tui upgrade renames the internal method.
5) Minor
- Replaced five `as any` casts with typed Record<string, unknown>
access in the monkey-patch sites.
- ai-server debug log only fires when actual discovery succeeds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
/**
|
|
* markdown-body-color — force a theme-aware color on text paths that pi-tui
|
|
* emits uncoloured, so they stop inheriting the terminal profile's default
|
|
* foreground color (green on many themes).
|
|
*
|
|
* Two patches:
|
|
*
|
|
* 1. Markdown.prototype.render — pi-tui's Markdown component only wraps
|
|
* paragraph text when the caller passes a `defaultTextStyle.color` fn.
|
|
* pi-coding-agent does this for thinking blocks but not for regular
|
|
* assistant content. We install a fallback defaultTextStyle before the
|
|
* original render runs.
|
|
*
|
|
* 2. Editor.prototype.render — the Editor emits typed text and the cursor
|
|
* (via \x1b[7m reverse-video) with no color wrapping at all. We wrap the
|
|
* whole line in our body color and re-open the color after any `\x1b[0m`
|
|
* (cursor's full-reset) or `\x1b[39m` (default-fg reset, used by nested
|
|
* theme.fg wrappers) so the color persists across those breaks.
|
|
*
|
|
* Body color defaults to the dark-mechanicus "inkPurple" (#d4c5e8). Override
|
|
* via PI_MARKDOWN_BODY_COLOR env var.
|
|
*/
|
|
|
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
import { Editor, Markdown } from "@mariozechner/pi-tui";
|
|
import { ANSI_RESET_FG, fgFromHex } from "./shared/ansi.js";
|
|
|
|
const FALLBACK_HEX = process.env.PI_MARKDOWN_BODY_COLOR ?? "#d4c5e8";
|
|
const OPEN = fgFromHex(FALLBACK_HEX);
|
|
const CLOSE = ANSI_RESET_FG;
|
|
|
|
// Re-open the body color after any ANSI reset inside a line. Cursor inverse
|
|
// video uses \x1b[0m (full reset) and theme.fg helpers close with \x1b[39m.
|
|
function reopenAfterResets(s: string): string {
|
|
return s.replace(/\x1b\[0m/g, `\x1b[0m${OPEN}`).replace(/\x1b\[39m/g, `\x1b[39m${OPEN}`);
|
|
}
|
|
|
|
function wrap(s: string): string {
|
|
return OPEN + reopenAfterResets(s) + CLOSE;
|
|
}
|
|
|
|
let patched = false;
|
|
|
|
export default function (_pi: ExtensionAPI) {
|
|
if (patched) return;
|
|
patched = true;
|
|
|
|
// ─ 1. Markdown component ────────────────────────────────────────────
|
|
const mdProto = Markdown.prototype as Record<string, unknown>;
|
|
const origMdRender = mdProto.render as ((width: number) => string[]) | undefined;
|
|
if (typeof origMdRender !== "function") {
|
|
console.warn(
|
|
"[markdown-body-color] pi-tui Markdown.prototype.render is missing; skipping patch. (pi-tui upgrade?)",
|
|
);
|
|
return;
|
|
}
|
|
mdProto.render = function (this: any, width: number) {
|
|
if (!this.defaultTextStyle) {
|
|
this.defaultTextStyle = { color: wrap };
|
|
if (typeof this.invalidate === "function") this.invalidate();
|
|
}
|
|
return origMdRender.call(this, width);
|
|
};
|
|
|
|
// ─ 2. Editor component ──────────────────────────────────────────────
|
|
const edProto = Editor.prototype as Record<string, unknown>;
|
|
const origEdRender = edProto.render as ((width: number) => string[]) | undefined;
|
|
if (typeof origEdRender !== "function") {
|
|
console.warn(
|
|
"[markdown-body-color] pi-tui Editor.prototype.render is missing; skipping Editor patch.",
|
|
);
|
|
return;
|
|
}
|
|
edProto.render = function (this: any, width: number) {
|
|
const lines: string[] = origEdRender.call(this, width);
|
|
return lines.map(wrap);
|
|
};
|
|
|
|
if (process.env.PI_DEBUG) {
|
|
console.log(
|
|
`[markdown-body-color] Body color = ${FALLBACK_HEX} (Markdown + Editor)`,
|
|
);
|
|
}
|
|
}
|