@nanobpm/nano-workforce 0.168.0 → 0.168.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.
@@ -0,0 +1,260 @@
1
+ // @generated from app/agentic/cockpit/transcript-derive.ts by scripts/build-cockpit-browser.ts — DO NOT EDIT.
2
+ //
3
+ // Browser ESM derived (type-strip only) from the typed transcript core so pages/cockpit/mount.js
4
+ // renders the agentic transcript from ONE source of truth (#660). Regenerate with:
5
+ // node --experimental-strip-types scripts/build-cockpit-browser.ts
6
+
7
+ import { deriveViewFromChunks, optionKindAllows, } from "./transcript-events.js";
8
+ /**
9
+ * Derive the structured view of a fetched transcript page by folding its stored chunks through the ONE
10
+ * event parser + fold. Pure: the cockpit reads THIS instead of re-parsing raw frame bytes.
11
+ */
12
+ export function deriveTranscript(data) {
13
+ return deriveViewFromChunks(data.entries);
14
+ }
15
+ function el(doc, tag, className, text) {
16
+ const node = doc.createElement(tag);
17
+ if (className !== undefined)
18
+ node.className = className;
19
+ if (text !== undefined)
20
+ node.textContent = text;
21
+ return node;
22
+ }
23
+ /** Render an arbitrary derived value (tool args/result) as displayable text without re-parsing the log. */
24
+ function toText(value) {
25
+ if (typeof value === "string")
26
+ return value;
27
+ if (value === undefined)
28
+ return "";
29
+ return JSON.stringify(value, null, 2);
30
+ }
31
+ /** Read the first string-valued field among `keys` off an object, without an `as` cast. */
32
+ function pickString(obj, keys) {
33
+ for (const key of keys) {
34
+ const value = Reflect.get(obj, key);
35
+ if (typeof value === "string")
36
+ return value;
37
+ }
38
+ return undefined;
39
+ }
40
+ /** Classify one line of a unified diff (file/hunk headers are context, not add/del). */
41
+ function classifyUnifiedLine(line) {
42
+ if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("@@") || line.startsWith("diff "))
43
+ return "ctx";
44
+ if (line.startsWith("+"))
45
+ return "add";
46
+ if (line.startsWith("-"))
47
+ return "del";
48
+ return "ctx";
49
+ }
50
+ /** Heuristic: does this string look like a unified diff (a hunk header, or paired +/- content lines)? */
51
+ function looksLikeUnifiedDiff(text) {
52
+ if (text.length === 0)
53
+ return false;
54
+ let add = false;
55
+ let del = false;
56
+ let hunk = false;
57
+ for (const line of text.split("\n")) {
58
+ if (line.startsWith("@@") || line.startsWith("diff --git"))
59
+ hunk = true;
60
+ else if (line.startsWith("+++") || line.startsWith("---"))
61
+ continue;
62
+ else if (line.startsWith("+"))
63
+ add = true;
64
+ else if (line.startsWith("-"))
65
+ del = true;
66
+ }
67
+ return hunk || (add && del);
68
+ }
69
+ /** Split a unified-diff string into classified lines (dropping a single trailing empty line). */
70
+ function parseUnifiedDiff(text) {
71
+ const lines = text.split("\n");
72
+ if (lines.length > 0 && lines[lines.length - 1] === "")
73
+ lines.pop();
74
+ return lines.map((line) => ({ kind: classifyUnifiedLine(line), text: line }));
75
+ }
76
+ /** Split a block of text into lines, dropping a single trailing empty segment (text ending in "\n"). */
77
+ function splitTextLines(text) {
78
+ const lines = text.split("\n");
79
+ if (lines.length > 0 && lines[lines.length - 1] === "")
80
+ lines.pop();
81
+ return lines;
82
+ }
83
+ /** Synthesize a diff from structured edit args (`{ path?, oldText/old_string, newText/new_string }`). */
84
+ function structuredDiff(args) {
85
+ if (typeof args !== "object" || args === null)
86
+ return undefined;
87
+ const oldText = pickString(args, ["oldText", "old_string", "oldStr", "old", "before"]);
88
+ const newText = pickString(args, ["newText", "new_string", "newStr", "new", "after"]);
89
+ if (oldText === undefined && newText === undefined)
90
+ return undefined;
91
+ const lines = [];
92
+ const path = pickString(args, ["path", "file", "filePath", "fileName"]);
93
+ if (path !== undefined)
94
+ lines.push({ kind: "ctx", text: `diff --git a/${path} b/${path}` });
95
+ if (oldText !== undefined && oldText.length > 0) {
96
+ for (const line of splitTextLines(oldText))
97
+ lines.push({ kind: "del", text: `-${line}` });
98
+ }
99
+ if (newText !== undefined && newText.length > 0) {
100
+ for (const line of splitTextLines(newText))
101
+ lines.push({ kind: "add", text: `+${line}` });
102
+ }
103
+ return lines.length > 0 ? lines : undefined;
104
+ }
105
+ /** Detect diff-shaped content on a tool call/result — a unified-diff string or structured edit args. */
106
+ function detectDiff(tool) {
107
+ const content = tool.result?.content;
108
+ if (typeof content === "string" && looksLikeUnifiedDiff(content)) {
109
+ return { lines: parseUnifiedDiff(content), source: "result" };
110
+ }
111
+ if (typeof tool.args === "string" && looksLikeUnifiedDiff(tool.args)) {
112
+ return { lines: parseUnifiedDiff(tool.args), source: "args" };
113
+ }
114
+ const structured = structuredDiff(tool.args);
115
+ if (structured !== undefined)
116
+ return { lines: structured, source: "args" };
117
+ return undefined;
118
+ }
119
+ /** Render one tool card: name, status, args + result content, and a distinguishable diff block. */
120
+ function renderTool(doc, tool) {
121
+ const card = el(doc, "div", "cockpit-transcript-tool");
122
+ card.setAttribute("data-tool", tool.name);
123
+ card.setAttribute("data-offset", String(tool.offset));
124
+ card.setAttribute("data-status", tool.result === undefined ? "pending" : tool.result.ok ? "ok" : "error");
125
+ card.appendChild(el(doc, "div", "cockpit-transcript-tool-name", tool.name));
126
+ const diff = detectDiff(tool);
127
+ if (diff !== undefined)
128
+ card.setAttribute("data-tool-kind", "diff");
129
+ // Show the raw args unless the diff was synthesized FROM the args (then the diff block replaces it).
130
+ if (tool.args !== undefined && !(diff !== undefined && diff.source === "args")) {
131
+ const argsEl = el(doc, "pre", "cockpit-transcript-tool-args", toText(tool.args));
132
+ argsEl.setAttribute("data-tool-args", "true");
133
+ card.appendChild(argsEl);
134
+ }
135
+ if (diff !== undefined) {
136
+ const pre = el(doc, "pre", "cockpit-transcript-diff");
137
+ pre.setAttribute("data-diff", "true");
138
+ for (const line of diff.lines) {
139
+ // A <pre> may only contain phrasing content, so each diff line is a phrasing <span>
140
+ // (not a block <div>, which would be invalid markup) carrying a trailing "\n". The
141
+ // enclosing <pre> preserves that newline, so lines break onto their own line without
142
+ // depending on host CSS forcing display:block.
143
+ const row = el(doc, "span", "cockpit-transcript-diff-line", `${line.text}\n`);
144
+ row.setAttribute("data-diff-line", line.kind);
145
+ pre.appendChild(row);
146
+ }
147
+ card.appendChild(pre);
148
+ }
149
+ // Render the result content unless it was itself consumed as the diff source (source === "result").
150
+ if (typeof tool.result?.content === "string" && !(diff !== undefined && diff.source === "result")) {
151
+ const resEl = el(doc, "pre", "cockpit-transcript-tool-result", tool.result.content);
152
+ resEl.setAttribute("data-tool-result", "true");
153
+ card.appendChild(resEl);
154
+ }
155
+ return card;
156
+ }
157
+ /**
158
+ * Render one permission prompt card from a {@link DerivedPermission}:
159
+ * - a pending `escalate` request → interactive Allow/Deny buttons wired to `onPermissionResolve`;
160
+ * - a `yolo` request → informational only (yolo auto-allows, it never prompts a human);
161
+ * - a resolved permission → settled (`allowed`/`denied`), showing the chosen option, no live buttons.
162
+ */
163
+ function renderPermission(doc, perm, options) {
164
+ const card = el(doc, "div", "cockpit-transcript-permission");
165
+ card.setAttribute("data-permission", "request");
166
+ card.setAttribute("data-policy", perm.policy);
167
+ card.setAttribute("data-call-id", perm.callId);
168
+ card.setAttribute("data-offset", String(perm.offset));
169
+ if (perm.toolName !== undefined)
170
+ card.setAttribute("data-tool", perm.toolName);
171
+ if (perm.title !== undefined)
172
+ card.appendChild(el(doc, "div", "cockpit-transcript-permission-title", perm.title));
173
+ if (perm.reason !== undefined)
174
+ card.appendChild(el(doc, "div", "cockpit-transcript-permission-reason", perm.reason));
175
+ if (perm.resolved !== undefined) {
176
+ // Settled: show which option was chosen and no live buttons.
177
+ card.setAttribute("data-status", perm.resolved.allowed ? "allowed" : "denied");
178
+ const chosen = perm.options.find((option) => option.optionId === perm.resolved?.optionId);
179
+ const settled = el(doc, "div", "cockpit-transcript-permission-settled", chosen?.name ?? perm.resolved.optionId);
180
+ settled.setAttribute("data-chosen-option", perm.resolved.optionId);
181
+ if (perm.resolved.by !== undefined)
182
+ settled.setAttribute("data-by", perm.resolved.by);
183
+ card.appendChild(settled);
184
+ return card;
185
+ }
186
+ if (perm.policy === "yolo") {
187
+ // Informational: yolo auto-allows and never prompts a human, so no Allow/Deny buttons.
188
+ card.setAttribute("data-status", "auto");
189
+ card.appendChild(el(doc, "div", "cockpit-transcript-permission-note", "Auto-allowed (yolo) — no operator prompt."));
190
+ return card;
191
+ }
192
+ // Pending escalate: one interactive button per offered option, wired to the resolve seam.
193
+ card.setAttribute("data-status", "pending");
194
+ const actions = el(doc, "div", "cockpit-transcript-permission-actions");
195
+ for (const option of perm.options) {
196
+ const allowed = optionKindAllows(option.kind);
197
+ const button = el(doc, "button", "cockpit-transcript-permission-option", option.name);
198
+ button.setAttribute("type", "button");
199
+ button.setAttribute("data-option-id", option.optionId);
200
+ button.setAttribute("data-option-kind", option.kind);
201
+ button.setAttribute("data-allowed", String(allowed));
202
+ const onPermissionResolve = options.onPermissionResolve;
203
+ if (onPermissionResolve !== undefined) {
204
+ button.addEventListener("click", () => onPermissionResolve({ callId: perm.callId, optionId: option.optionId, allowed }));
205
+ }
206
+ actions.appendChild(button);
207
+ }
208
+ card.appendChild(actions);
209
+ return card;
210
+ }
211
+ /**
212
+ * Render the DERIVED structured view of a fetched transcript into `host`, replacing whatever was there.
213
+ * Draws per-turn sections with their derived messages, rich tool/diff cards and permission prompts, plus
214
+ * a raw-fidelity footer (retained bytes/chunks) so the operator sees the byte-replay is preserved
215
+ * alongside the structure. Idempotent — call again on each refresh. Everything it shows is a derivation
216
+ * of the one event log. `options.onPermissionResolve`, when provided, is invoked by a pending
217
+ * escalate-permission prompt's Allow/Deny buttons.
218
+ */
219
+ export function renderDerivedTranscript(host, doc, data, options = {}) {
220
+ const view = deriveTranscript(data);
221
+ host.replaceChildren();
222
+ const root = el(doc, "div", "cockpit-transcript-derived");
223
+ root.setAttribute("data-stream", data.stream);
224
+ root.setAttribute("data-lifecycle", view.lifecycle);
225
+ root.setAttribute("data-turn-count", String(view.turns.length));
226
+ root.setAttribute("data-message-count", String(view.messages.length));
227
+ root.setAttribute("data-tool-count", String(view.tools.length));
228
+ root.setAttribute("data-permission-count", String(view.permissions.length));
229
+ if (view.turns.length === 0) {
230
+ const empty = el(doc, "div", "cockpit-transcript-empty", "No structured events derived — raw replay only.");
231
+ empty.setAttribute("data-empty", "true");
232
+ root.appendChild(empty);
233
+ }
234
+ for (const turn of view.turns) {
235
+ const section = el(doc, "section", "cockpit-transcript-turn");
236
+ section.setAttribute("data-turn", String(turn.index));
237
+ section.setAttribute("data-steps", String(turn.steps));
238
+ section.appendChild(el(doc, "h3", "cockpit-transcript-turn-title", `Turn ${turn.index}`));
239
+ for (const msg of turn.messages) {
240
+ const row = el(doc, "div", "cockpit-transcript-message", msg.text);
241
+ row.setAttribute("data-role", msg.role);
242
+ row.setAttribute("data-offset", String(msg.offset));
243
+ section.appendChild(row);
244
+ }
245
+ for (const tool of turn.tools) {
246
+ section.appendChild(renderTool(doc, tool));
247
+ }
248
+ for (const perm of turn.permissions) {
249
+ section.appendChild(renderPermission(doc, perm, options));
250
+ }
251
+ root.appendChild(section);
252
+ }
253
+ const footer = el(doc, "footer", "cockpit-transcript-raw");
254
+ footer.setAttribute("data-raw-bytes", String(view.rawByteLength));
255
+ footer.setAttribute("data-raw-chunks", String(view.rawChunkCount));
256
+ footer.textContent = `${view.rawChunkCount} raw chunk(s) · ${view.rawByteLength} B retained for replay`;
257
+ root.appendChild(footer);
258
+ host.appendChild(root);
259
+ return { root };
260
+ }