@zenodinh/pi-render 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +110 -2
- package/index.ts +279 -0
- package/package.json +66 -5
- package/src/commands/canvas.test.ts +150 -0
- package/src/commands/canvas.ts +57 -0
- package/src/core/code-theme.test.ts +266 -0
- package/src/core/code-theme.ts +275 -0
- package/src/core/log.test.ts +127 -0
- package/src/core/log.ts +57 -0
- package/src/core/paint.test.ts +322 -0
- package/src/core/paint.ts +64 -0
- package/src/core/registry.test.ts +307 -0
- package/src/core/registry.ts +135 -0
- package/src/core/settings.test.ts +183 -0
- package/src/core/settings.ts +119 -0
- package/src/core/types/code-theme.ts +24 -0
- package/src/core/types/host.ts +160 -0
- package/src/core/types/log.ts +28 -0
- package/src/core/types/paint.ts +54 -0
- package/src/core/types/registry.ts +44 -0
- package/src/core/types/settings.ts +31 -0
- package/src/core/types.ts +34 -0
- package/src/renderers/content/artifacts/artifacts.test.ts +199 -0
- package/src/renderers/content/artifacts/cache.ts +234 -0
- package/src/renderers/content/artifacts/cards.test.ts +216 -0
- package/src/renderers/content/artifacts/cards.ts +136 -0
- package/src/renderers/content/artifacts/engines-extra.test.ts +556 -0
- package/src/renderers/content/artifacts/engines.ts +396 -0
- package/src/renderers/content/artifacts/local-binary.test.ts +207 -0
- package/src/renderers/content/artifacts/local-binary.ts +80 -0
- package/src/renderers/content/artifacts/prereqs.ts +128 -0
- package/src/renderers/content/artifacts/server.ts +181 -0
- package/src/renderers/content/code-panel.ts +161 -0
- package/src/renderers/content/image-card.test.ts +170 -0
- package/src/renderers/content/image-card.ts +252 -0
- package/src/renderers/content/index.ts +79 -0
- package/src/renderers/content/json-panel.ts +116 -0
- package/src/renderers/content/panels.test.ts +188 -0
- package/src/renderers/content/table.test.ts +209 -0
- package/src/renderers/content/table.ts +174 -0
- package/src/renderers/content/transformer.test.ts +254 -0
- package/src/renderers/content/types.ts +20 -0
- package/src/renderers/tool/index.ts +113 -0
- package/src/renderers/tool/resolver.test.ts +257 -0
- package/src/renderers/tool/runtime.test.ts +313 -0
- package/src/renderers/tool/runtime.ts +267 -0
- package/src/renderers/tool/specs/bash.test.ts +110 -0
- package/src/renderers/tool/specs/bash.ts +168 -0
- package/src/renderers/tool/specs/codemode.test.ts +212 -0
- package/src/renderers/tool/specs/codemode.ts +248 -0
- package/src/renderers/tool/specs/edit.test.ts +260 -0
- package/src/renderers/tool/specs/edit.ts +213 -0
- package/src/renderers/tool/specs/ls.test.ts +173 -0
- package/src/renderers/tool/specs/ls.ts +136 -0
- package/src/renderers/tool/specs/read.test.ts +340 -0
- package/src/renderers/tool/specs/read.ts +296 -0
- package/src/renderers/tool/specs/search.test.ts +197 -0
- package/src/renderers/tool/specs/search.ts +325 -0
- package/src/renderers/tool/specs/write.test.ts +145 -0
- package/src/renderers/tool/specs/write.ts +142 -0
- package/src/renderers/tool/types.ts +45 -0
- package/themes/dracula-soft.json +81 -0
- package/themes/one-dark.json +80 -0
- package/themes/themes.test.ts +251 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// ported from pi-pretty-tui/src/core/state.ts — survives because: its tmp+rename write and its
|
|
2
|
+
// fill-defaults read are exactly the mechanics that keep one document uncorrupted and cheap to read.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The one persisted settings document (SA §3), at `~/.pi/agent/pi-render/settings.json`.
|
|
6
|
+
*
|
|
7
|
+
* Why cached: the predecessor re-read its document inside the render path (P render.ts:62), so every
|
|
8
|
+
* row paid a disk read. Here only the first load reads; save() refreshes the cache.
|
|
9
|
+
*
|
|
10
|
+
* Why tmp+rename: a reader opening the final path mid-write must see the old document or the new one
|
|
11
|
+
* complete — never a torn file.
|
|
12
|
+
*
|
|
13
|
+
* shape: closure returning an object literal — trigger #4, this module's one runtime unit is
|
|
14
|
+
* createSettingsStore(): a cached document plus two methods, so no class, subclassing, or instanceof.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { dirname, join } from "node:path";
|
|
19
|
+
import type { Logger, ModuleSettings, SettingsDoc, SettingsStore } from "./types.ts";
|
|
20
|
+
|
|
21
|
+
const DOC_VERSION = 1;
|
|
22
|
+
|
|
23
|
+
/** Stands in when no logger is injected, so every factory call needs no logger guard. */
|
|
24
|
+
const NOOP_LOGGER: Logger = {
|
|
25
|
+
logLine: () => {},
|
|
26
|
+
logOnce: () => {},
|
|
27
|
+
drain: () => [],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** shape: none — one env lookup; PI_CODING_AGENT_DIR mirrors how the host resolves its agent dir. */
|
|
31
|
+
function defaultSettingsPath(): string {
|
|
32
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR || join(process.env.HOME ?? "", ".pi/agent");
|
|
33
|
+
return join(agentDir, "pi-render", "settings.json");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** shape: none — one type predicate over untrusted JSON. */
|
|
37
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
38
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** shape: none — one code check on an unknown catch value. */
|
|
42
|
+
function isEnoent(error: unknown): boolean {
|
|
43
|
+
return isRecord(error) && error.code === "ENOENT";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** shape: none — one field shape per key; a bad entry reads as the module's defaults. */
|
|
47
|
+
function normalizeModule(entry: unknown): ModuleSettings {
|
|
48
|
+
if (!isRecord(entry)) return { enabled: true };
|
|
49
|
+
const enabled = typeof entry.enabled === "boolean" ? entry.enabled : true;
|
|
50
|
+
return isRecord(entry.settings) ? { enabled, settings: entry.settings } : { enabled };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** shape: none — one field shape per key; a schema library does not earn a dependency for two keys. */
|
|
54
|
+
function normalize(raw: Record<string, unknown>): SettingsDoc {
|
|
55
|
+
const version = typeof raw.version === "number" ? raw.version : DOC_VERSION;
|
|
56
|
+
const source = isRecord(raw.modules) ? raw.modules : {};
|
|
57
|
+
// Object.fromEntries writes own data properties, so a "__proto__" key cannot set the prototype.
|
|
58
|
+
const modules = Object.fromEntries(
|
|
59
|
+
Object.entries(source).map(([key, entry]): [string, ModuleSettings] => [key, normalizeModule(entry)]),
|
|
60
|
+
);
|
|
61
|
+
return { version, modules };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Cached load plus atomic save. `path` and `logger` are injectable so tests never touch the real
|
|
66
|
+
* agent directory and can read the malformed-doc line back.
|
|
67
|
+
*/
|
|
68
|
+
export function createSettingsStore(opts?: { path?: string; logger?: Logger }): SettingsStore {
|
|
69
|
+
const path = opts?.path ?? defaultSettingsPath();
|
|
70
|
+
const logger = opts?.logger ?? NOOP_LOGGER;
|
|
71
|
+
let cache: SettingsDoc | undefined;
|
|
72
|
+
|
|
73
|
+
const defaults = (): SettingsDoc => ({ version: DOC_VERSION, modules: {} });
|
|
74
|
+
|
|
75
|
+
const malformed = (why: string): SettingsDoc => {
|
|
76
|
+
logger.logOnce(`settings:${path}`, "settings", `${why} at ${path} — using defaults`);
|
|
77
|
+
return defaults();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const readFromDisk = (): SettingsDoc => {
|
|
81
|
+
let text: string;
|
|
82
|
+
try {
|
|
83
|
+
text = readFileSync(path, "utf8");
|
|
84
|
+
} catch (error) {
|
|
85
|
+
// A missing file is the normal first run, not a fault worth a line.
|
|
86
|
+
return isEnoent(error) ? defaults() : malformed("settings unreadable");
|
|
87
|
+
}
|
|
88
|
+
let raw: unknown;
|
|
89
|
+
try {
|
|
90
|
+
raw = JSON.parse(text);
|
|
91
|
+
} catch {
|
|
92
|
+
return malformed("settings malformed");
|
|
93
|
+
}
|
|
94
|
+
if (!isRecord(raw)) return malformed("settings malformed");
|
|
95
|
+
if (raw.modules !== undefined && !isRecord(raw.modules)) return malformed("settings malformed");
|
|
96
|
+
return normalize(raw);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
load(): SettingsDoc {
|
|
101
|
+
const current = cache ?? readFromDisk();
|
|
102
|
+
cache = current;
|
|
103
|
+
return current;
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
save(doc: SettingsDoc): void {
|
|
107
|
+
try {
|
|
108
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
109
|
+
const temporary = `${path}.tmp`;
|
|
110
|
+
writeFileSync(temporary, `${JSON.stringify(doc, null, 2)}\n`);
|
|
111
|
+
renameSync(temporary, path);
|
|
112
|
+
} catch {
|
|
113
|
+
// A write failure degrades to in-memory: the panel keeps working, the disk stays old.
|
|
114
|
+
logger.logOnce(`settings:save:${path}`, "settings", `settings write failed at ${path} — kept in memory`);
|
|
115
|
+
}
|
|
116
|
+
cache = doc;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code-theme.ts — the highlighting contract core/code-theme (T-10) implements.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: the highlighter is injected — the shiki adapter in production, a counting double in
|
|
5
|
+
* tests — so no renderer imports shiki and no test needs the wasm engine.
|
|
6
|
+
*
|
|
7
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The injectable highlighter: code plus a language id in, one escape-wrapped line per array entry out. */
|
|
11
|
+
export type HighlightEngine = (
|
|
12
|
+
/** Source text to highlight; may be multi-line. Required. */
|
|
13
|
+
code: string,
|
|
14
|
+
/** Language id, e.g. "ts" or "bash". Required. */
|
|
15
|
+
lang: string,
|
|
16
|
+
) => string[];
|
|
17
|
+
|
|
18
|
+
/** Cached highlighting: an oversize input and an engine failure both yield plain, uncoloured lines. */
|
|
19
|
+
export interface CodeTheme {
|
|
20
|
+
/** Highlights one block, waiting for the engine. Required; never rejects — failures degrade. */
|
|
21
|
+
highlight(code: string, lang: string): Promise<string[]>;
|
|
22
|
+
/** Highlights one block from cache or synchronous engine. Required; the render-path entry. */
|
|
23
|
+
highlightSync(code: string, lang: string): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host.ts — the host shapes pi-render reads, composes against, and registers through.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: these are STRUCTURAL declarations of shapes the pi host also declares. No host
|
|
5
|
+
* package is imported here, so a plain object literal satisfies them and a unit test needs no pi
|
|
6
|
+
* runtime; the composition root (index.ts) passes the real host in as a parameter.
|
|
7
|
+
*
|
|
8
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
9
|
+
*
|
|
10
|
+
* Ported from pi-pretty-tui/src/types.ts — survives because: host-verified structural shapes keep
|
|
11
|
+
* every renderer decoupled from a host import, so unit tests need no pi runtime. The predecessor's
|
|
12
|
+
* `*Like` suffix meant "structural declaration of the host shape"; that meaning lives in these docs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The host theme slice paint reads. Structural host shape: any object with these two members qualifies. */
|
|
16
|
+
export interface HostTheme {
|
|
17
|
+
/** Maps a theme token and text to escape-wrapped text, e.g. fg("accent", "ok"). Required. */
|
|
18
|
+
fg(key: string, text: string): string;
|
|
19
|
+
/** Wraps text in the theme's bold escape sequence. Required. */
|
|
20
|
+
bold(text: string): string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Live markdown-theme closures the content painter wraps. Structural host shape — no host import. */
|
|
24
|
+
export interface MarkdownTheme {
|
|
25
|
+
/** Horizontal-rule escape wrapper. Optional: a host without this token degrades to plain text. */
|
|
26
|
+
hr?(text: string): string;
|
|
27
|
+
/** Blockquote body wrapper. Optional. */
|
|
28
|
+
quote?(text: string): string;
|
|
29
|
+
/** Blockquote left-border wrapper. Optional. */
|
|
30
|
+
quoteBorder?(text: string): string;
|
|
31
|
+
/** Inline-code wrapper. Optional. */
|
|
32
|
+
code?(text: string): string;
|
|
33
|
+
/** Fenced-code-block border wrapper. Optional. */
|
|
34
|
+
codeBlockBorder?(text: string): string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The pi-tui component slice renderers return and receive. Structural host shape — no host import. */
|
|
38
|
+
export interface UiComponent {
|
|
39
|
+
/** Draws the current frame as lines; `width` is the terminal width in columns. Required. */
|
|
40
|
+
render(width: number): string[];
|
|
41
|
+
/** Marks the cached frame stale so the next render() redraws. Required. */
|
|
42
|
+
invalidate(): void;
|
|
43
|
+
/** Replaces the component's text. Optional: base components the runtime delegates to do not have it. */
|
|
44
|
+
setText?(v: string): void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The text-component slice the runtime reads back. Structural host shape — no host import. */
|
|
48
|
+
export interface TextComponent {
|
|
49
|
+
/** Replaces the displayed text. Required. */
|
|
50
|
+
setText(v: string): void;
|
|
51
|
+
/** Reads the displayed text back. Optional: the host's own component may not expose it. */
|
|
52
|
+
getText?(): string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** What renderCall / renderResult receive. Structural host shape — no host import; a plain object is accepted. */
|
|
56
|
+
export interface RenderContext {
|
|
57
|
+
/** ONE state bag per row, shared across that row's invocations; the parse-once memo lives here. Required. */
|
|
58
|
+
state: Record<string, unknown>;
|
|
59
|
+
/** Tool arguments as the host passes them; unreadable until argsComplete is true. Optional. */
|
|
60
|
+
args?: Record<string, unknown>;
|
|
61
|
+
/** Stable id of the tool call this row draws, used as the logOnce key. Optional. */
|
|
62
|
+
toolCallId?: string;
|
|
63
|
+
/** Asks the host to redraw this row, e.g. after an async decorate() resolved. Optional. */
|
|
64
|
+
invalidate?: () => void;
|
|
65
|
+
/** The component returned on the previous invocation of the same row. Optional. */
|
|
66
|
+
lastComponent?: UiComponent;
|
|
67
|
+
/** Working directory the tool ran in, when the host reports one. Optional. */
|
|
68
|
+
cwd?: string;
|
|
69
|
+
/** True once the host has delivered the full argument set — the gate for argument-dependent output. Optional. */
|
|
70
|
+
argsComplete?: boolean;
|
|
71
|
+
/** True while the row is drawn expanded (ctrl+o or click). Optional. */
|
|
72
|
+
expanded?: boolean;
|
|
73
|
+
/** True when the tool call failed; the row collapses to an error summary. Optional. */
|
|
74
|
+
isError?: boolean;
|
|
75
|
+
/** True while the result is still streaming; the row must stay cheap. Optional. */
|
|
76
|
+
isPartial?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One result content block; only text blocks are rendered. Structural host shape. */
|
|
80
|
+
export interface ToolResultContent {
|
|
81
|
+
/** Block kind as the host labels it, e.g. "text" or "image". Required. */
|
|
82
|
+
type: string;
|
|
83
|
+
/** The block's text; absent for non-text blocks. Optional. */
|
|
84
|
+
text?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** The result slice renderers read. Structural host shape — the shape is narrowed, never assumed. */
|
|
88
|
+
export interface ToolResult {
|
|
89
|
+
/** Result blocks in host order. Optional: a result may carry details only. */
|
|
90
|
+
content?: ToolResultContent[];
|
|
91
|
+
/** Host-supplied structured payload; opaque here and never rendered directly. Optional. */
|
|
92
|
+
details?: unknown;
|
|
93
|
+
/** True when the tool call failed. Optional. */
|
|
94
|
+
isError?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Renderer options exactly as the host passes them — one bag per render call, no defaults invented. */
|
|
98
|
+
export interface RenderResultOptions {
|
|
99
|
+
/** True while the row is drawn expanded. Required. */
|
|
100
|
+
expanded: boolean;
|
|
101
|
+
/** True while the result is still streaming. Required. */
|
|
102
|
+
isPartial: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The renderer triple the resolver returns or delegates to; renderShell stays host-owned. */
|
|
106
|
+
export interface ToolRenderers {
|
|
107
|
+
/** Asks the host for its own shell frame instead of drawing one. Optional: exactly "default" or "self". */
|
|
108
|
+
renderShell?: "default" | "self";
|
|
109
|
+
/** Draws the call line. Optional: omitting it delegates this row to the next resolver. */
|
|
110
|
+
renderCall?(args: Record<string, unknown>, theme: HostTheme, ctx: RenderContext): UiComponent;
|
|
111
|
+
/** Draws the result. Optional: omitting it delegates this row to the next resolver. */
|
|
112
|
+
renderResult?(result: ToolResult, options: RenderResultOptions, theme: HostTheme, ctx: RenderContext): UiComponent;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The host resolver chain signature. Structural host shape — the host calls it, pi-render returns it. */
|
|
116
|
+
export type ToolRendererResolver = (
|
|
117
|
+
/** Name of the tool being rendered, exactly as the host spells it, e.g. "bash". Required. */
|
|
118
|
+
toolName: string,
|
|
119
|
+
/** Returns what the remaining chain would draw, or undefined when the chain is exhausted. Required. */
|
|
120
|
+
next: () => ToolRenderers | undefined,
|
|
121
|
+
) => ToolRenderers | undefined;
|
|
122
|
+
|
|
123
|
+
/** The host markdown-transform context slice surfaces read. Structural host shape — no host import. */
|
|
124
|
+
export interface TransformContext {
|
|
125
|
+
/** Which transcript stream the text belongs to. Required: "user" | "assistant" | "assistant-thinking". */
|
|
126
|
+
messageType: "user" | "assistant" | "assistant-thinking";
|
|
127
|
+
/** True while the message is still streaming in; a streaming message is not rewritten. Required. */
|
|
128
|
+
isStreaming: boolean;
|
|
129
|
+
/** Columns available to the message, in the host's character-cell units. Required; > 0. */
|
|
130
|
+
availableWidth: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The command-handler context slice /canvas reads. Structural host shape — no host import. */
|
|
134
|
+
export interface CommandContext {
|
|
135
|
+
/** Host output mode. Required: "tui" | "rpc" | "json" | "print". */
|
|
136
|
+
mode: "tui" | "rpc" | "json" | "print";
|
|
137
|
+
/** Host notification seam for user-facing feedback. Required. */
|
|
138
|
+
ui: {
|
|
139
|
+
/** Shows one message to the user, e.g. notify("Canvas closed"). Required. */
|
|
140
|
+
notify(message: string): void;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The host surface the extension registers through. Structural host shape: the host arrives as the
|
|
146
|
+
* composition root's parameter, never as a module import, so this file stays pi-free.
|
|
147
|
+
*/
|
|
148
|
+
export interface ExtensionApi {
|
|
149
|
+
/** Adds one resolver to the tool-render chain. Required; called exactly once, in index.ts. */
|
|
150
|
+
registerToolRenderer(resolver: ToolRendererResolver): void;
|
|
151
|
+
/** Adds one markdown rewrite stage. Required; called exactly once, in index.ts. */
|
|
152
|
+
registerMarkdownTransformer(transformer: (markdown: string, ctx: TransformContext) => string): void;
|
|
153
|
+
/** Registers one slash command, e.g. name "canvas". Required; one call per command. */
|
|
154
|
+
registerCommand(
|
|
155
|
+
name: string,
|
|
156
|
+
options: { description?: string; handler: (args: string, ctx: CommandContext) => Promise<void> },
|
|
157
|
+
): void;
|
|
158
|
+
/** Collapses every tool row to one line at boot. Required; the render-only default is false. */
|
|
159
|
+
setToolsExpanded(expanded: boolean): void;
|
|
160
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* log.ts — the diagnostics contract core/log (T-06) implements.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: renderers call these from a render path, so the contract forbids throwing and forbids
|
|
5
|
+
* writing to stdout/stderr — pi owns both for the TUI.
|
|
6
|
+
*
|
|
7
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** One captured diagnostic entry: the memory record a test or the /render panel reads back. */
|
|
11
|
+
export interface LogEntry {
|
|
12
|
+
/** Emitting module scope, e.g. "tool.bash". Required. */
|
|
13
|
+
scope: string;
|
|
14
|
+
/** One-line diagnostic text, never multi-line. Required. */
|
|
15
|
+
message: string;
|
|
16
|
+
/** Capture time in epoch milliseconds (Date.now()). Required. */
|
|
17
|
+
time: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Keyed in-memory diagnostics. Structural core shape: any object with these three methods qualifies. */
|
|
21
|
+
export interface Logger {
|
|
22
|
+
/** Appends one entry. Required; repeats freely, so keep it off per-frame paths. */
|
|
23
|
+
logLine(scope: string, message: string): void;
|
|
24
|
+
/** Appends at most one entry per key, e.g. key "tool:call-42". Required — the per-frame default. */
|
|
25
|
+
logOnce(key: string, scope: string, message: string): void;
|
|
26
|
+
/** Returns every captured entry and empties the buffer. Required; tests and the panel consume this. */
|
|
27
|
+
drain(): LogEntry[];
|
|
28
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* paint.ts — the painting contract: the two role sets every renderer draws through.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: region 1 is the tool row, region 2 is markdown content. Renderers call role methods
|
|
5
|
+
* and never emit an escape sequence themselves (AGENTS: escapes originate in core/paint.ts).
|
|
6
|
+
*
|
|
7
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { HostTheme } from "./host.ts";
|
|
11
|
+
|
|
12
|
+
/** Region 1 — the tool-row roles. Text in, theme-derived escape-wrapped text out. */
|
|
13
|
+
export interface RowPaint {
|
|
14
|
+
/** The tool name or call title, e.g. title("bash"). Required. */
|
|
15
|
+
title(text: string): string;
|
|
16
|
+
/** Tool output / result text. Required. */
|
|
17
|
+
output(text: string): string;
|
|
18
|
+
/** De-emphasized metadata such as durations and counts. Required. */
|
|
19
|
+
muted(text: string): string;
|
|
20
|
+
/** Accent for the active or highlighted element. Required. */
|
|
21
|
+
accent(text: string): string;
|
|
22
|
+
/** Error text on a failed call. Required. */
|
|
23
|
+
error(text: string): string;
|
|
24
|
+
/** Warning text for a degraded or partial result. Required. */
|
|
25
|
+
warning(text: string): string;
|
|
26
|
+
/** The row's left gutter marker. Required. */
|
|
27
|
+
gutter(text: string): string;
|
|
28
|
+
/** A matched substring highlighted inside a result line. Required. */
|
|
29
|
+
match(text: string): string;
|
|
30
|
+
/** Diff line added (+), from the host toolDiffAdded token. Required. */
|
|
31
|
+
diffAdded(text: string): string;
|
|
32
|
+
/** Diff line removed (−), from the host toolDiffRemoved token. Required. */
|
|
33
|
+
diffRemoved(text: string): string;
|
|
34
|
+
/** Diff context line, from the host toolDiffContext token. Required. */
|
|
35
|
+
diffContext(text: string): string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Region 1 factory: the theme arrives per render call and is read live, so a theme switch repaints. */
|
|
39
|
+
export type RowPaintFactory = (
|
|
40
|
+
/** The host theme as delivered on this render call. Required. */
|
|
41
|
+
theme: HostTheme,
|
|
42
|
+
) => RowPaint;
|
|
43
|
+
|
|
44
|
+
/** Region 2 — the markdown-content roles. */
|
|
45
|
+
export interface ContentPaint {
|
|
46
|
+
/** Horizontal rule; falls back hr → quoteBorder → plain rather than throw. Required. */
|
|
47
|
+
rule(text: string): string;
|
|
48
|
+
/** Blockquote body. Required. */
|
|
49
|
+
quote(text: string): string;
|
|
50
|
+
/** Inline code. Required. */
|
|
51
|
+
code(text: string): string;
|
|
52
|
+
/** Fenced-code-block border. Required. */
|
|
53
|
+
codeBlockBorder(text: string): string;
|
|
54
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry.ts — the module-registry contract core/registry (T-09) implements.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: index.ts is the only registration site; renderers read the registry per invocation and
|
|
5
|
+
* never write it.
|
|
6
|
+
*
|
|
7
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** One declared setting: the /render panel renders an editor from this spec. */
|
|
11
|
+
export interface SettingSpec {
|
|
12
|
+
/** Setting name inside the module's slice, e.g. "collapseAt". Required; unique per module. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Editor kind. Required: "enum" | "toggle" | "number". */
|
|
15
|
+
kind: "enum" | "toggle" | "number";
|
|
16
|
+
/** Allowed values — required when kind is "enum", ignored otherwise. Optional. */
|
|
17
|
+
values?: string[];
|
|
18
|
+
/** Value used when the document omits the key. Required; must read as `unknown` before use. */
|
|
19
|
+
default: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** One pluggable module's registration. Duplicate keys keep the first and log once. */
|
|
23
|
+
export interface ModuleDescriptor {
|
|
24
|
+
/** Registry key, unique module id, e.g. "row.bash". Required. */
|
|
25
|
+
key: string;
|
|
26
|
+
/** Human-readable name shown in the /render panel, e.g. "Bash row". Required. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Initial enabled state before any settings write. Required. */
|
|
29
|
+
defaultEnabled: boolean;
|
|
30
|
+
/** Settings this module exposes. Required; use [] when it exposes none. */
|
|
31
|
+
settings: SettingSpec[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Consumers poll per invocation; a settings write lands on the next read and pushes no redraw. */
|
|
35
|
+
export interface Registry {
|
|
36
|
+
/** Declares one module. Required; a repeat key keeps the first definition. */
|
|
37
|
+
defineModule(d: ModuleDescriptor): void;
|
|
38
|
+
/** Reads a module's enabled flag by key, e.g. "row.bash". Required; an unknown key reads false. */
|
|
39
|
+
isEnabled(key: string): boolean;
|
|
40
|
+
/** Reads a module's effective settings (defaults merged with stored values). Required; unknown key = {}. */
|
|
41
|
+
getSettings(key: string): Record<string, unknown>;
|
|
42
|
+
/** Merges a patch into a module's stored settings. Required; takes effect on the next read, not now. */
|
|
43
|
+
setSettings(key: string, patch: object): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* settings.ts — the persisted-settings contract core/settings (T-07) implements.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: only the store touches the disk; a render path reads the cached document it returns.
|
|
5
|
+
*
|
|
6
|
+
* shape: none — declaration-only module (no runtime unit), so no DSG-1 shape trigger applies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** One module's persisted slice, e.g. { enabled: false, settings: { collapseAt: 3 } }. */
|
|
10
|
+
export interface ModuleSettings {
|
|
11
|
+
/** Whether the module's row/surface is active. Required; a missing module key reads as enabled. */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** The module's own key/value bag, validated by the module that owns it. Optional; default {}. */
|
|
14
|
+
settings?: Record<string, unknown>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The persisted document: one file for every module. A malformed value is replaced by all defaults. */
|
|
18
|
+
export interface SettingsDoc {
|
|
19
|
+
/** Document schema revision, currently 1 — bumped by a migration, never by a module. Required. */
|
|
20
|
+
version: number;
|
|
21
|
+
/** Module slices keyed by registry key, e.g. "row.bash". Required; absent key = that module's defaults. */
|
|
22
|
+
modules: Record<string, ModuleSettings>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Cached load plus atomic save. A render path never touches the disk after the first load. */
|
|
26
|
+
export interface SettingsStore {
|
|
27
|
+
/** Returns the cached document, reading the file only on first call. Required. */
|
|
28
|
+
load(): SettingsDoc;
|
|
29
|
+
/** Writes the document atomically (temp file + rename). Required; a failure degrades to in-memory. */
|
|
30
|
+
save(doc: SettingsDoc): void;
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* types.ts — the seam catalog barrel: every cross-lane contract, in one import path.
|
|
3
|
+
*
|
|
4
|
+
* Boundary: cross-lane contracts are declared per scope under `src/core/types/` and per lane under
|
|
5
|
+
* `src/renderers/{tool,content}/types.ts`; this file re-exports them so `core/types.ts` stays the
|
|
6
|
+
* stable import path every module and ticket names. Add a type to its scope file, never here.
|
|
7
|
+
* AC-2 scope: "a later ticket finds a missing interface" means a missing cross-lane interface in
|
|
8
|
+
* one of those files, never any interface a ticket happens to mention.
|
|
9
|
+
*
|
|
10
|
+
* shape: none — re-export barrel (no runtime unit), so no DSG-1 shape trigger applies.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type { Surface, SurfaceKey } from "../renderers/content/types.ts";
|
|
14
|
+
export type { CallModel, RowSpec, RowView } from "../renderers/tool/types.ts";
|
|
15
|
+
export type { CodeTheme, HighlightEngine } from "./types/code-theme.ts";
|
|
16
|
+
export type {
|
|
17
|
+
CommandContext,
|
|
18
|
+
ExtensionApi,
|
|
19
|
+
HostTheme,
|
|
20
|
+
MarkdownTheme,
|
|
21
|
+
RenderContext,
|
|
22
|
+
RenderResultOptions,
|
|
23
|
+
TextComponent,
|
|
24
|
+
ToolRendererResolver,
|
|
25
|
+
ToolRenderers,
|
|
26
|
+
ToolResult,
|
|
27
|
+
ToolResultContent,
|
|
28
|
+
TransformContext,
|
|
29
|
+
UiComponent,
|
|
30
|
+
} from "./types/host.ts";
|
|
31
|
+
export type { LogEntry, Logger } from "./types/log.ts";
|
|
32
|
+
export type { ContentPaint, RowPaint, RowPaintFactory } from "./types/paint.ts";
|
|
33
|
+
export type { ModuleDescriptor, Registry, SettingSpec } from "./types/registry.ts";
|
|
34
|
+
export type { ModuleSettings, SettingsDoc, SettingsStore } from "./types/settings.ts";
|