indusagi-coding-agent 0.2.8 → 0.2.9
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/package.json +2 -1
- package/src/_decls/entry.ts +18 -0
- package/src/_decls/guardrails.ts +35 -0
- package/src/_decls/index.ts +26 -0
- package/src/addons/contract.ts +236 -0
- package/src/addons/dispatch/event-dispatcher.ts +164 -0
- package/src/addons/dispatch/index.ts +25 -0
- package/src/addons/dispatch/tool-interceptor.ts +208 -0
- package/src/addons/host.ts +225 -0
- package/src/addons/index.ts +112 -0
- package/src/addons/manifest.ts +158 -0
- package/src/addons/sandbox.ts +170 -0
- package/src/addons/surface.ts +78 -0
- package/src/boot/auth-vault.ts +195 -0
- package/src/boot/boot.ts +138 -0
- package/src/boot/contract.ts +238 -0
- package/src/boot/heap.ts +59 -0
- package/src/boot/index.ts +28 -0
- package/src/boot/invocation.ts +93 -0
- package/src/boot/runners/addon-wiring.ts +153 -0
- package/src/boot/runners/checkpoint.ts +169 -0
- package/src/boot/runners/delegate-runner.ts +294 -0
- package/src/boot/runners/index.ts +13 -0
- package/src/boot/runners/link-runner.ts +45 -0
- package/src/boot/runners/memdir.ts +168 -0
- package/src/boot/runners/oneshot-runner.ts +58 -0
- package/src/boot/runners/read-state.ts +90 -0
- package/src/boot/runners/registry.ts +42 -0
- package/src/boot/runners/repl-runner.ts +143 -0
- package/src/boot/runners/server-mode.ts +121 -0
- package/src/boot/runners/session.ts +641 -0
- package/src/boot/server-token.ts +148 -0
- package/src/boot/stages.ts +167 -0
- package/src/boot/upgrade/apply.ts +94 -0
- package/src/boot/upgrade/index.ts +13 -0
- package/src/boot/upgrade/upgrades.ts +289 -0
- package/src/briefing/compose.ts +150 -0
- package/src/briefing/context-docs.ts +19 -0
- package/src/briefing/contract.ts +717 -0
- package/src/briefing/index.ts +31 -0
- package/src/briefing/macros.ts +97 -0
- package/src/briefing/skills.ts +47 -0
- package/src/capability-deck/bridge-ledger/index.ts +27 -0
- package/src/capability-deck/bridge-ledger/key.ts +67 -0
- package/src/capability-deck/bridge-ledger/ledger.ts +131 -0
- package/src/capability-deck/bridge-ledger/network.ts +117 -0
- package/src/capability-deck/builtin-bridge.ts +312 -0
- package/src/capability-deck/cards/bg-process-card.ts +335 -0
- package/src/capability-deck/cards/index.ts +115 -0
- package/src/capability-deck/cards/memory-card.ts +146 -0
- package/src/capability-deck/cards/plan-file.ts +97 -0
- package/src/capability-deck/cards/plan-tools.ts +185 -0
- package/src/capability-deck/cards/saas-card.ts +183 -0
- package/src/capability-deck/cards/task-card.ts +207 -0
- package/src/capability-deck/cards/todo-card.ts +168 -0
- package/src/capability-deck/cards/workflow-card.ts +247 -0
- package/src/capability-deck/contract.ts +388 -0
- package/src/capability-deck/index.ts +48 -0
- package/src/capability-deck/manifest.ts +109 -0
- package/src/capability-deck/provision.ts +169 -0
- package/src/channels/contract.ts +191 -0
- package/src/channels/framer.ts +50 -0
- package/src/channels/index.ts +101 -0
- package/src/channels/link/dialog.ts +129 -0
- package/src/channels/link/driver.ts +190 -0
- package/src/channels/link/index.ts +34 -0
- package/src/channels/link/server.ts +134 -0
- package/src/channels/oneshot.ts +90 -0
- package/src/channels/ops.ts +65 -0
- package/src/channels/session-ops.ts +81 -0
- package/src/conductor/bash-guard.ts +599 -0
- package/src/conductor/catalog/catalog.ts +116 -0
- package/src/conductor/catalog/index.ts +8 -0
- package/src/conductor/catalog/matcher.ts +134 -0
- package/src/conductor/conductor.ts +234 -0
- package/src/conductor/contract.ts +842 -0
- package/src/conductor/diagnostics.ts +227 -0
- package/src/conductor/index.ts +33 -0
- package/src/conductor/permissions.ts +588 -0
- package/src/conductor/quota-error.ts +49 -0
- package/src/conductor/signal-hub/hub.ts +46 -0
- package/src/conductor/signal-hub/index.ts +2 -0
- package/src/conductor/signal-hub/translate.test.ts +81 -0
- package/src/conductor/signal-hub/translate.ts +74 -0
- package/src/conductor/skill-parse/index.ts +2 -0
- package/src/conductor/skill-parse/parse.ts +108 -0
- package/src/conductor/transcript-store/index.ts +22 -0
- package/src/conductor/transcript-store/serialize.ts +116 -0
- package/src/conductor/transcript-store/store.ts +205 -0
- package/src/console/auth-status.ts +56 -0
- package/src/console/components/AgentsView.ts +165 -0
- package/src/console/components/BackgroundAgents.ts +155 -0
- package/src/console/components/Banner.ts +334 -0
- package/src/console/components/Composer.ts +94 -0
- package/src/console/components/StatusBar.ts +49 -0
- package/src/console/components/TerminalConsole.ts +1090 -0
- package/src/console/components/WorkingIndicator.ts +98 -0
- package/src/console/components/banner-sweep.ts +24 -0
- package/src/console/components/welcome.ts +74 -0
- package/src/console/contract.ts +630 -0
- package/src/console/index.ts +34 -0
- package/src/console/input/complete.ts +127 -0
- package/src/console/input/dir-reader.ts +34 -0
- package/src/console/input/index.ts +23 -0
- package/src/console/input/keymap.ts +159 -0
- package/src/console/input/paste.ts +104 -0
- package/src/console/mount.ts +56 -0
- package/src/console/overlays/approval-queue.ts +57 -0
- package/src/console/overlays/approval.ts +130 -0
- package/src/console/overlays/auth.ts +342 -0
- package/src/console/overlays/boards.ts +308 -0
- package/src/console/overlays/host.ts +36 -0
- package/src/console/overlays/index.ts +26 -0
- package/src/console/overlays/pickers.ts +258 -0
- package/src/console/overlays/sessions.ts +190 -0
- package/src/console/reducer.ts +182 -0
- package/src/console/slash/builtins.ts +81 -0
- package/src/console/slash/commands/dynamic.ts +83 -0
- package/src/console/slash/commands/integrations.ts +695 -0
- package/src/console/slash/commands/shared.ts +75 -0
- package/src/console/slash/commands/transcript.ts +263 -0
- package/src/console/slash/commands/workbench.ts +246 -0
- package/src/console/slash/index.ts +15 -0
- package/src/console/slash/registry.ts +70 -0
- package/src/console/slash/resolve.ts +63 -0
- package/src/console/startup.ts +209 -0
- package/src/console/theme/adapter.ts +45 -0
- package/src/console/theme/index.ts +7 -0
- package/src/console/theme/palette.ts +68 -0
- package/src/console/theme/resolve.ts +39 -0
- package/src/console/theme/tokens.ts +71 -0
- package/src/entry.ts +55 -0
- package/src/guardrails.ts +37 -0
- package/src/index.ts +18 -0
- package/src/insight/channel.ts +88 -0
- package/src/insight/contract.ts +185 -0
- package/src/insight/index.ts +110 -0
- package/src/insight/recorder.ts +213 -0
- package/src/insight/redaction.ts +157 -0
- package/src/insight/replay.ts +158 -0
- package/src/insight/sampling.ts +70 -0
- package/src/insight/serialize.ts +50 -0
- package/src/insight/sinks/console.ts +64 -0
- package/src/insight/sinks/file.ts +40 -0
- package/src/insight/sinks/index.ts +24 -0
- package/src/insight/sinks/stream.ts +54 -0
- package/src/integrations/sarvam/attach.ts +239 -0
- package/src/integrations/sarvam/config.ts +156 -0
- package/src/integrations/sarvam/index.ts +25 -0
- package/src/integrations/sarvam/sarvam.test.ts +60 -0
- package/src/integrations/sarvam/types.ts +27 -0
- package/src/integrations/zoho/attach.ts +342 -0
- package/src/integrations/zoho/config.ts +125 -0
- package/src/integrations/zoho/index.ts +27 -0
- package/src/integrations/zoho/types.ts +21 -0
- package/src/integrations/zoho/zoho.test.ts +50 -0
- package/src/kit/clipboard-image.ts +107 -0
- package/src/kit/external-editor.ts +48 -0
- package/src/kit/image.ts +59 -0
- package/src/kit/index.ts +51 -0
- package/src/kit/shell.ts +19 -0
- package/src/kit/tool-fetch.ts +85 -0
- package/src/launch/catalog.ts +148 -0
- package/src/launch/contract.ts +187 -0
- package/src/launch/credentials.ts +625 -0
- package/src/launch/index.ts +98 -0
- package/src/launch/invocation/attachments.ts +179 -0
- package/src/launch/invocation/flags.ts +196 -0
- package/src/launch/invocation/index.ts +25 -0
- package/src/launch/invocation/read.ts +260 -0
- package/src/launch/invocation/usage.ts +67 -0
- package/src/launch/login.ts +324 -0
- package/src/launch/oauth.test.ts +18 -0
- package/src/launch/oauth.ts +203 -0
- package/src/launch/packages.ts +194 -0
- package/src/launch/pickers.ts +189 -0
- package/src/runtime-bridge/bridges/_drive.ts +96 -0
- package/src/runtime-bridge/bridges/builtins.ts +68 -0
- package/src/runtime-bridge/bridges/claude-cli.ts +123 -0
- package/src/runtime-bridge/bridges/codex-cli.ts +142 -0
- package/src/runtime-bridge/bridges/index.ts +33 -0
- package/src/runtime-bridge/bridges/indusagi-cli.ts +155 -0
- package/src/runtime-bridge/broker.ts +227 -0
- package/src/runtime-bridge/contract.ts +122 -0
- package/src/runtime-bridge/index.ts +79 -0
- package/src/runtime-bridge/sink.ts +180 -0
- package/src/sessions/contract.ts +81 -0
- package/src/sessions/index.ts +13 -0
- package/src/sessions/library.ts +229 -0
- package/src/settings/contract.ts +114 -0
- package/src/settings/index.ts +32 -0
- package/src/settings/manager.ts +117 -0
- package/src/transcript-export/index.ts +45 -0
- package/src/transcript-export/publish.ts +260 -0
- package/src/transcript-export/sgr.ts +315 -0
- package/src/transcript-export/template.ts +272 -0
- package/src/transcript-export/theme-bridge.ts +150 -0
- package/src/window-budget/budget/estimate.ts +135 -0
- package/src/window-budget/budget/gate.ts +33 -0
- package/src/window-budget/budget/index.ts +16 -0
- package/src/window-budget/budget/slice.ts +56 -0
- package/src/window-budget/condenser.ts +58 -0
- package/src/window-budget/contract.ts +184 -0
- package/src/window-budget/index.ts +19 -0
- package/src/window-budget/microcompact.ts +95 -0
- package/src/window-budget/rehydrate.ts +136 -0
- package/src/window-budget/summarize/condense.ts +103 -0
- package/src/window-budget/summarize/index.ts +14 -0
- package/src/window-budget/summarize/prompt.ts +149 -0
- package/src/workflow-engine/agent-runner.ts +181 -0
- package/src/workflow-engine/display.ts +224 -0
- package/src/workflow-engine/engine.ts +294 -0
- package/src/workflow-engine/index.ts +23 -0
- package/src/workflow-engine/parse.ts +172 -0
- package/src/workflow-engine/structured-output.ts +35 -0
- package/src/workspace/brand.ts +29 -0
- package/src/workspace/index.ts +18 -0
- package/src/workspace/locator.ts +103 -0
- package/src/workspace/runtime-detect.ts +64 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* publishTranscript — render a session transcript to a standalone, self-contained
|
|
3
|
+
* HTML document.
|
|
4
|
+
*/
|
|
5
|
+
import hljs from "highlight.js";
|
|
6
|
+
import { marked } from "marked";
|
|
7
|
+
import { briefingFault, type ExportTheme } from "../briefing/contract.js";
|
|
8
|
+
import { paintSgr } from "./sgr.js";
|
|
9
|
+
import { CLIENT_SCRIPT, fill, PAGE_SHELL, PAGE_STYLES, SHELL_SLOTS } from "./template.js";
|
|
10
|
+
import { createThemeBridge, DefaultThemeBridge } from "./theme-bridge.js";
|
|
11
|
+
|
|
12
|
+
interface TextPart {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ImagePart {
|
|
18
|
+
type: "image";
|
|
19
|
+
data: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ThinkingPart {
|
|
24
|
+
type: "thinking";
|
|
25
|
+
thinking: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ToolCallPart {
|
|
29
|
+
type: "toolCall";
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
arguments: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type ContentPart = TextPart | ImagePart | ThinkingPart | ToolCallPart | { type: string };
|
|
36
|
+
|
|
37
|
+
export interface TranscriptEntry {
|
|
38
|
+
readonly role: "user" | "assistant" | "tool" | "system" | "condense" | "note";
|
|
39
|
+
readonly message: {
|
|
40
|
+
readonly content: string | readonly ContentPart[];
|
|
41
|
+
readonly toolCallId?: string;
|
|
42
|
+
readonly toolName?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TranscriptWidget {
|
|
47
|
+
readonly callId: string;
|
|
48
|
+
readonly html: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PublishOptions {
|
|
52
|
+
readonly theme?: ExportTheme;
|
|
53
|
+
readonly title?: string;
|
|
54
|
+
readonly widgets?: readonly TranscriptWidget[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const MARKED_LICENSE = [
|
|
58
|
+
"marked \u2014 https://github.com/markedjs/marked",
|
|
59
|
+
"Copyright (c) 2018+, MarkedJS",
|
|
60
|
+
"Copyright (c) 2011-2018, Christopher Jeffrey",
|
|
61
|
+
"Released under the MIT License.",
|
|
62
|
+
].join("\n");
|
|
63
|
+
|
|
64
|
+
const HIGHLIGHT_LICENSE = [
|
|
65
|
+
"highlight.js \u2014 https://github.com/highlightjs/highlight.js",
|
|
66
|
+
"Copyright (c) 2006, Ivan Sagalaev. All rights reserved.",
|
|
67
|
+
"Released under the BSD-3-Clause License.",
|
|
68
|
+
].join("\n");
|
|
69
|
+
|
|
70
|
+
const ROLE_LABEL: Readonly<Record<TranscriptEntry["role"], string>> = {
|
|
71
|
+
user: "You",
|
|
72
|
+
assistant: "Assistant",
|
|
73
|
+
tool: "Tool",
|
|
74
|
+
system: "System",
|
|
75
|
+
condense: "Summary",
|
|
76
|
+
note: "Note",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function escapeHtml2(text: string): string {
|
|
80
|
+
return text
|
|
81
|
+
.replace(/&/g, "&")
|
|
82
|
+
.replace(/</g, "<")
|
|
83
|
+
.replace(/>/g, ">")
|
|
84
|
+
.replace(/"/g, """)
|
|
85
|
+
.replace(/'/g, "'");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasAnsi(text: string): boolean {
|
|
89
|
+
return /\x1B\[/.test(text);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function highlightCode(code: string, language?: string): string {
|
|
93
|
+
try {
|
|
94
|
+
if (language && hljs.getLanguage(language)) {
|
|
95
|
+
return hljs.highlight(code, { language }).value;
|
|
96
|
+
}
|
|
97
|
+
return hljs.highlightAuto(code).value;
|
|
98
|
+
} catch {
|
|
99
|
+
return escapeHtml2(code);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function configuredMarked(): typeof marked {
|
|
104
|
+
marked.setOptions({ gfm: true, breaks: false, async: false });
|
|
105
|
+
marked.use({
|
|
106
|
+
renderer: {
|
|
107
|
+
code(token: string | { text: string; lang?: string }): string {
|
|
108
|
+
const text = typeof token === "string" ? token : token.text;
|
|
109
|
+
const lang =
|
|
110
|
+
typeof token === "string" ? undefined : (token.lang || "").trim() || undefined;
|
|
111
|
+
const highlighted = highlightCode(text, lang);
|
|
112
|
+
const cls = lang ? ` class="language-${escapeHtml2(lang)}"` : "";
|
|
113
|
+
return `<pre><code${cls}>${highlighted}</code></pre>`;
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return marked;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderMarkdown(md: typeof marked, source: string): string {
|
|
121
|
+
const out = md.parse(source, { async: false });
|
|
122
|
+
return typeof out === "string" ? out : "";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function renderImage(part: ImagePart): string {
|
|
126
|
+
const src = `data:${escapeHtml2(part.mimeType)};base64,${part.data}`;
|
|
127
|
+
return `<img alt="attached image" src="${src}" />`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderToolCall(name: string, args: unknown): string {
|
|
131
|
+
let payload: string;
|
|
132
|
+
try {
|
|
133
|
+
payload = JSON.stringify(args, null, 2);
|
|
134
|
+
} catch {
|
|
135
|
+
payload = String(args);
|
|
136
|
+
}
|
|
137
|
+
return `<div class="tool-frame"><strong>${escapeHtml2(name)}</strong>\n${escapeHtml2(payload)}</div>`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function renderToolText(text: string): string {
|
|
141
|
+
const inner = hasAnsi(text) ? paintSgr(text) : escapeHtml2(text);
|
|
142
|
+
return `<pre class="tool-frame">${inner}</pre>`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function partsOf(content: string | readonly ContentPart[]): readonly ContentPart[] {
|
|
146
|
+
if (typeof content === "string") {
|
|
147
|
+
return [{ type: "text", text: content }];
|
|
148
|
+
}
|
|
149
|
+
return content;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
interface RenderedTurn {
|
|
153
|
+
readonly kind: "turn" | "note";
|
|
154
|
+
readonly role: TranscriptEntry["role"];
|
|
155
|
+
readonly label: string;
|
|
156
|
+
readonly html: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function renderEntry(
|
|
160
|
+
entry: TranscriptEntry,
|
|
161
|
+
md: typeof marked,
|
|
162
|
+
widgets: Map<string, TranscriptWidget>,
|
|
163
|
+
): RenderedTurn {
|
|
164
|
+
const role = entry.role;
|
|
165
|
+
const label = ROLE_LABEL[role] ?? role;
|
|
166
|
+
if (role === "tool" && entry.message.toolCallId !== undefined) {
|
|
167
|
+
const widget = widgets.get(entry.message.toolCallId);
|
|
168
|
+
if (widget !== undefined) {
|
|
169
|
+
return { kind: "turn", role, label, html: widget.html };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const fragments: string[] = [];
|
|
173
|
+
for (const part of partsOf(entry.message.content)) {
|
|
174
|
+
switch (part.type) {
|
|
175
|
+
case "text":
|
|
176
|
+
if (role === "tool") {
|
|
177
|
+
fragments.push(renderToolText((part as TextPart).text));
|
|
178
|
+
} else {
|
|
179
|
+
fragments.push(renderMarkdown(md, (part as TextPart).text));
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
case "image":
|
|
183
|
+
fragments.push(renderImage(part as ImagePart));
|
|
184
|
+
break;
|
|
185
|
+
case "thinking":
|
|
186
|
+
fragments.push(
|
|
187
|
+
`<div class="aside-note">${escapeHtml2((part as ThinkingPart).thinking)}</div>`,
|
|
188
|
+
);
|
|
189
|
+
break;
|
|
190
|
+
case "toolCall": {
|
|
191
|
+
const tc = part as ToolCallPart;
|
|
192
|
+
fragments.push(renderToolCall(tc.name, tc.arguments));
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const html = fragments.join("\n");
|
|
200
|
+
const kind: "turn" | "note" = role === "system" || role === "condense" ? "note" : "turn";
|
|
201
|
+
return { kind, role, label, html };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function toBase64(text: string): string {
|
|
205
|
+
if (typeof Buffer !== "undefined") {
|
|
206
|
+
return Buffer.from(text, "utf-8").toString("base64");
|
|
207
|
+
}
|
|
208
|
+
const bytes = encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_m, h) =>
|
|
209
|
+
String.fromCharCode(parseInt(h, 16)),
|
|
210
|
+
);
|
|
211
|
+
return btoa(bytes);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function themeVarsBlock(bridge: DefaultThemeBridge): string {
|
|
215
|
+
const vars = bridge.toCssVars();
|
|
216
|
+
const hyphenate = (name: string): string =>
|
|
217
|
+
name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
218
|
+
return Object.entries(vars)
|
|
219
|
+
.map(([token, value]) => ` --x-${hyphenate(token)}: ${value};`)
|
|
220
|
+
.join("\n");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function publishTranscript(
|
|
224
|
+
entries: readonly TranscriptEntry[],
|
|
225
|
+
opts: PublishOptions = {},
|
|
226
|
+
): string {
|
|
227
|
+
try {
|
|
228
|
+
const bridge = opts.theme ? createThemeBridge(opts.theme) : createThemeBridge();
|
|
229
|
+
const md = configuredMarked();
|
|
230
|
+
const widgetIndex = new Map<string, TranscriptWidget>(
|
|
231
|
+
(opts.widgets ?? []).map((w) => [w.callId, w]),
|
|
232
|
+
);
|
|
233
|
+
const turns = entries.map((entry) => renderEntry(entry, md, widgetIndex));
|
|
234
|
+
const title = opts.title ?? "Session Transcript";
|
|
235
|
+
const payloadJson = JSON.stringify({
|
|
236
|
+
title,
|
|
237
|
+
turns: turns.map((t) => ({
|
|
238
|
+
kind: t.kind === "note" ? "note" : undefined,
|
|
239
|
+
role: t.role,
|
|
240
|
+
label: t.label,
|
|
241
|
+
html: t.html,
|
|
242
|
+
})),
|
|
243
|
+
});
|
|
244
|
+
const payload = toBase64(payloadJson);
|
|
245
|
+
const slots: Record<string, string> = {
|
|
246
|
+
[SHELL_SLOTS.themeVars]: themeVarsBlock(bridge),
|
|
247
|
+
[SHELL_SLOTS.pageSurface]: bridge.theme.pageSurface,
|
|
248
|
+
[SHELL_SLOTS.cardSurface]: bridge.theme.cardSurface,
|
|
249
|
+
[SHELL_SLOTS.noteSurface]: bridge.theme.noteSurface,
|
|
250
|
+
[SHELL_SLOTS.styles]: PAGE_STYLES,
|
|
251
|
+
[SHELL_SLOTS.script]: CLIENT_SCRIPT,
|
|
252
|
+
[SHELL_SLOTS.payload]: payload,
|
|
253
|
+
[SHELL_SLOTS.markedLib]: MARKED_LICENSE,
|
|
254
|
+
[SHELL_SLOTS.highlightLib]: HIGHLIGHT_LICENSE,
|
|
255
|
+
};
|
|
256
|
+
return fill(PAGE_SHELL, slots);
|
|
257
|
+
} catch (cause) {
|
|
258
|
+
throw briefingFault("publish", "Failed to render the HTML transcript.", cause);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SGR painter — a table-driven ANSI Select-Graphic-Rendition state machine that
|
|
3
|
+
* converts a terminal byte stream into a sequence of styled HTML spans.
|
|
4
|
+
*/
|
|
5
|
+
import type { SgrMutation, SgrState, SgrToken } from "../briefing/contract.js";
|
|
6
|
+
import { SGR_INITIAL_STATE } from "../briefing/contract.js";
|
|
7
|
+
|
|
8
|
+
const LINE_FEED = "\n";
|
|
9
|
+
const LINE_SEPARATOR = "\u2028";
|
|
10
|
+
const LINE_SEPARATOR_ESCAPE = "\\u2028";
|
|
11
|
+
const PARAGRAPH_SEPARATOR_ESCAPE = "\\u2029";
|
|
12
|
+
const SEPARATOR_PATTERN = /[\u2028\u2029]/g;
|
|
13
|
+
const TEXT_DECODER = new TextDecoder();
|
|
14
|
+
|
|
15
|
+
function clampByte(value: number): number {
|
|
16
|
+
if (value < 0) return 0;
|
|
17
|
+
if (value > 255) return 255;
|
|
18
|
+
return value | 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function hexPair(value: number): string {
|
|
22
|
+
return clampByte(value).toString(16).padStart(2, "0");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function rgbHex(r: number, g: number, b: number): string {
|
|
26
|
+
return `#${hexPair(r)}${hexPair(g)}${hexPair(b)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function color256(index: number): string {
|
|
30
|
+
const i = clampByte(index);
|
|
31
|
+
if (i < 16) {
|
|
32
|
+
return PALETTE_16[i] ?? "#000000";
|
|
33
|
+
}
|
|
34
|
+
if (i < 232) {
|
|
35
|
+
const c = i - 16;
|
|
36
|
+
const levels = [0, 95, 135, 175, 215, 255];
|
|
37
|
+
const r = levels[Math.floor(c / 36) % 6] ?? 0;
|
|
38
|
+
const g = levels[Math.floor(c / 6) % 6] ?? 0;
|
|
39
|
+
const b = levels[c % 6] ?? 0;
|
|
40
|
+
return rgbHex(r, g, b);
|
|
41
|
+
}
|
|
42
|
+
const step = 8 + (i - 232) * 10;
|
|
43
|
+
return rgbHex(step, step, step);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildCodeTable(): Map<number, SgrMutation> {
|
|
47
|
+
const table = new Map<number, SgrMutation>();
|
|
48
|
+
table.set(0, { ...SGR_INITIAL_STATE });
|
|
49
|
+
table.set(1, { bold: true });
|
|
50
|
+
table.set(2, { dim: true });
|
|
51
|
+
table.set(3, { italic: true });
|
|
52
|
+
table.set(4, { underline: true });
|
|
53
|
+
table.set(5, { blink: true });
|
|
54
|
+
table.set(6, { blink: true });
|
|
55
|
+
table.set(7, { inverse: true });
|
|
56
|
+
table.set(8, { hidden: true });
|
|
57
|
+
table.set(9, { strike: true });
|
|
58
|
+
table.set(21, { bold: false });
|
|
59
|
+
table.set(22, { bold: false, dim: false });
|
|
60
|
+
table.set(23, { italic: false });
|
|
61
|
+
table.set(24, { underline: false });
|
|
62
|
+
table.set(25, { blink: false });
|
|
63
|
+
table.set(27, { inverse: false });
|
|
64
|
+
table.set(28, { hidden: false });
|
|
65
|
+
table.set(29, { strike: false });
|
|
66
|
+
for (let n = 0; n < 8; n++) {
|
|
67
|
+
table.set(30 + n, { fg: BASE_PALETTE[n] ?? null });
|
|
68
|
+
table.set(90 + n, { fg: BRIGHT_PALETTE[n] ?? null });
|
|
69
|
+
}
|
|
70
|
+
table.set(39, { fg: null });
|
|
71
|
+
for (let n = 0; n < 8; n++) {
|
|
72
|
+
table.set(40 + n, { bg: BASE_PALETTE[n] ?? null });
|
|
73
|
+
table.set(100 + n, { bg: BRIGHT_PALETTE[n] ?? null });
|
|
74
|
+
}
|
|
75
|
+
table.set(49, { bg: null });
|
|
76
|
+
return table;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const BASE_PALETTE: readonly string[] = [
|
|
80
|
+
"#1c1c1c",
|
|
81
|
+
"#c14a4a",
|
|
82
|
+
"#5aa45a",
|
|
83
|
+
"#b5963c",
|
|
84
|
+
"#4a7fc1",
|
|
85
|
+
"#a05aa0",
|
|
86
|
+
"#42a0a0",
|
|
87
|
+
"#c9c9c9",
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const BRIGHT_PALETTE: readonly string[] = [
|
|
91
|
+
"#5c5c5c",
|
|
92
|
+
"#e06666",
|
|
93
|
+
"#86d186",
|
|
94
|
+
"#e6cc66",
|
|
95
|
+
"#6fa8e6",
|
|
96
|
+
"#c986c9",
|
|
97
|
+
"#6fcccc",
|
|
98
|
+
"#f5f5f5",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const PALETTE_16: readonly string[] = [...BASE_PALETTE, ...BRIGHT_PALETTE];
|
|
102
|
+
const CODE_TABLE = buildCodeTable();
|
|
103
|
+
|
|
104
|
+
export const SGR_CODE_TABLE: ReadonlyMap<number, SgrMutation> = CODE_TABLE;
|
|
105
|
+
|
|
106
|
+
function readExtendedColor(
|
|
107
|
+
params: readonly number[],
|
|
108
|
+
introducerIndex: number,
|
|
109
|
+
): { color: string | null; consumed: number } {
|
|
110
|
+
const selector = params[introducerIndex + 1];
|
|
111
|
+
if (selector === 5) {
|
|
112
|
+
const index = params[introducerIndex + 2];
|
|
113
|
+
if (index === undefined) return { color: null, consumed: 1 };
|
|
114
|
+
return { color: color256(index), consumed: 3 };
|
|
115
|
+
}
|
|
116
|
+
if (selector === 2) {
|
|
117
|
+
const r = params[introducerIndex + 2];
|
|
118
|
+
const g = params[introducerIndex + 3];
|
|
119
|
+
const b = params[introducerIndex + 4];
|
|
120
|
+
if (r === undefined || g === undefined || b === undefined) {
|
|
121
|
+
return { color: null, consumed: 1 };
|
|
122
|
+
}
|
|
123
|
+
return { color: rgbHex(r, g, b), consumed: 5 };
|
|
124
|
+
}
|
|
125
|
+
return { color: null, consumed: 1 };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function foldSgr(state: SgrState, params: readonly number[]): SgrState {
|
|
129
|
+
if (params.length === 0) {
|
|
130
|
+
return { ...SGR_INITIAL_STATE };
|
|
131
|
+
}
|
|
132
|
+
let next = state;
|
|
133
|
+
for (let i = 0; i < params.length; i++) {
|
|
134
|
+
const code = params[i];
|
|
135
|
+
if (code === undefined) continue;
|
|
136
|
+
if (code === 38 || code === 48) {
|
|
137
|
+
const { color, consumed } = readExtendedColor(params, i);
|
|
138
|
+
if (color !== null) {
|
|
139
|
+
next = code === 38 ? { ...next, fg: color } : { ...next, bg: color };
|
|
140
|
+
}
|
|
141
|
+
i += consumed - 1;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const mutation = CODE_TABLE.get(code);
|
|
145
|
+
if (mutation !== undefined) {
|
|
146
|
+
next = { ...next, ...mutation };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return next;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isFinalByte(ch: string): boolean {
|
|
153
|
+
const code = ch.charCodeAt(0);
|
|
154
|
+
return code >= 64 && code <= 126;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseParams(body: string): number[] {
|
|
158
|
+
if (body.length === 0) return [];
|
|
159
|
+
return body.split(";").map((field) => {
|
|
160
|
+
const value = parseInt(field, 10);
|
|
161
|
+
return Number.isNaN(value) ? 0 : value;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function tokenizeSgr(input: string): SgrToken[] {
|
|
166
|
+
const tokens: SgrToken[] = [];
|
|
167
|
+
let textStart = 0;
|
|
168
|
+
let i = 0;
|
|
169
|
+
const n = input.length;
|
|
170
|
+
const flushText = (end: number): void => {
|
|
171
|
+
if (end > textStart) {
|
|
172
|
+
tokens.push({ kind: "text", text: input.slice(textStart, end) });
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
while (i < n) {
|
|
176
|
+
if (input[i] !== ESC) {
|
|
177
|
+
i++;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
flushText(i);
|
|
181
|
+
if (input[i + 1] !== "[") {
|
|
182
|
+
i += input[i + 1] === undefined ? 1 : 2;
|
|
183
|
+
textStart = i;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let j = i + 2;
|
|
187
|
+
while (j < n && !isFinalByte(input[j])) {
|
|
188
|
+
j++;
|
|
189
|
+
}
|
|
190
|
+
const finalByte = input[j];
|
|
191
|
+
const body = input.slice(i + 2, j);
|
|
192
|
+
if (finalByte === "m") {
|
|
193
|
+
tokens.push({ kind: "sgr", params: parseParams(body) });
|
|
194
|
+
}
|
|
195
|
+
i = j + 1;
|
|
196
|
+
textStart = i;
|
|
197
|
+
}
|
|
198
|
+
flushText(n);
|
|
199
|
+
return tokens;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const ESC = "\x1B";
|
|
203
|
+
|
|
204
|
+
function escapeChar(ch: string): string | null {
|
|
205
|
+
switch (ch) {
|
|
206
|
+
case "&":
|
|
207
|
+
return "&";
|
|
208
|
+
case "<":
|
|
209
|
+
return "<";
|
|
210
|
+
case ">":
|
|
211
|
+
return ">";
|
|
212
|
+
case '"':
|
|
213
|
+
return """;
|
|
214
|
+
case "'":
|
|
215
|
+
return "'";
|
|
216
|
+
default:
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function escapeHtml(text: string): string {
|
|
222
|
+
let out = "";
|
|
223
|
+
for (const ch of text) {
|
|
224
|
+
out += escapeChar(ch) ?? ch;
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function classesFor(state: SgrState): string[] {
|
|
230
|
+
const classes: string[] = [];
|
|
231
|
+
for (const { key, suffix } of FLAG_CLASSES) {
|
|
232
|
+
if (state[key as keyof SgrState]) classes.push(`${CLASS_PREFIX}-${suffix}`);
|
|
233
|
+
}
|
|
234
|
+
if (state.inverse) classes.push(`${CLASS_PREFIX}-inverse`);
|
|
235
|
+
if (state.hidden) classes.push(`${CLASS_PREFIX}-hidden`);
|
|
236
|
+
return classes;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const CLASS_PREFIX = "sgr";
|
|
240
|
+
const FLAG_CLASSES: ReadonlyArray<{ key: keyof SgrState; suffix: string }> = [
|
|
241
|
+
{ key: "bold", suffix: "bold" },
|
|
242
|
+
{ key: "dim", suffix: "dim" },
|
|
243
|
+
{ key: "italic", suffix: "italic" },
|
|
244
|
+
{ key: "underline", suffix: "underline" },
|
|
245
|
+
{ key: "blink", suffix: "blink" },
|
|
246
|
+
{ key: "strike", suffix: "strike" },
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
function stylesFor(state: SgrState): string[] {
|
|
250
|
+
let fg = state.fg;
|
|
251
|
+
let bg = state.bg;
|
|
252
|
+
if (state.inverse) {
|
|
253
|
+
const swapped = fg;
|
|
254
|
+
fg = bg ?? "var(--sgr-default-bg, #13161c)";
|
|
255
|
+
bg = swapped ?? "var(--sgr-default-fg, #e6e8ee)";
|
|
256
|
+
}
|
|
257
|
+
if (state.hidden) {
|
|
258
|
+
fg = bg ?? "transparent";
|
|
259
|
+
}
|
|
260
|
+
const decls: string[] = [];
|
|
261
|
+
if (fg !== null) decls.push(`color:${fg}`);
|
|
262
|
+
if (bg !== null) decls.push(`background-color:${bg}`);
|
|
263
|
+
return decls;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isPlain(state: SgrState): boolean {
|
|
267
|
+
return (
|
|
268
|
+
state.fg === null &&
|
|
269
|
+
state.bg === null &&
|
|
270
|
+
!state.bold &&
|
|
271
|
+
!state.dim &&
|
|
272
|
+
!state.italic &&
|
|
273
|
+
!state.underline &&
|
|
274
|
+
!state.blink &&
|
|
275
|
+
!state.inverse &&
|
|
276
|
+
!state.hidden &&
|
|
277
|
+
!state.strike
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function renderRun(text: string, state: SgrState): string {
|
|
282
|
+
const safe = escapeHtml(text);
|
|
283
|
+
if (isPlain(state)) {
|
|
284
|
+
return safe;
|
|
285
|
+
}
|
|
286
|
+
const classes = classesFor(state);
|
|
287
|
+
const styles = stylesFor(state);
|
|
288
|
+
const attrs: string[] = [];
|
|
289
|
+
if (classes.length > 0) attrs.push(`class="${classes.join(" ")}"`);
|
|
290
|
+
if (styles.length > 0) attrs.push(`style="${styles.join(";")}"`);
|
|
291
|
+
return `<span${attrs.length > 0 ? " " + attrs.join(" ") : ""}>${safe}</span>`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function paintSgr(input: string): string {
|
|
295
|
+
if (input.length === 0) return "";
|
|
296
|
+
const tokens = tokenizeSgr(input);
|
|
297
|
+
let state: SgrState = { ...SGR_INITIAL_STATE };
|
|
298
|
+
let out = "";
|
|
299
|
+
for (const token of tokens) {
|
|
300
|
+
if (token.kind === "sgr") {
|
|
301
|
+
state = foldSgr(state, token.params);
|
|
302
|
+
} else {
|
|
303
|
+
out += renderRun(token.text, state);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Suppress unused warnings for symbols used only via shared state above.
|
|
310
|
+
void LINE_FEED;
|
|
311
|
+
void LINE_SEPARATOR;
|
|
312
|
+
void LINE_SEPARATOR_ESCAPE;
|
|
313
|
+
void PARAGRAPH_SEPARATOR_ESCAPE;
|
|
314
|
+
void SEPARATOR_PATTERN;
|
|
315
|
+
void TEXT_DECODER;
|