@overmux/pi 0.0.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 (70) hide show
  1. package/README.md +118 -0
  2. package/dist/cli.d.ts +1 -0
  3. package/dist/cli.js +40 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config-2jNv4tol.d.ts +34 -0
  6. package/dist/config-2jNv4tol.d.ts.map +1 -0
  7. package/dist/config.d.ts +2 -0
  8. package/dist/config.js +47 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/extension.d.ts +19 -0
  11. package/dist/extension.d.ts.map +1 -0
  12. package/dist/extension.js +265 -0
  13. package/dist/extension.js.map +1 -0
  14. package/dist/index.d.ts +10 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +16 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/jsonl-tail.d.ts +13 -0
  19. package/dist/jsonl-tail.d.ts.map +1 -0
  20. package/dist/jsonl-tail.js +83 -0
  21. package/dist/jsonl-tail.js.map +1 -0
  22. package/dist/live-events-ClhFOMGW.js +497 -0
  23. package/dist/live-events-ClhFOMGW.js.map +1 -0
  24. package/dist/live-events-DAmf6RRx.d.ts +107 -0
  25. package/dist/live-events-DAmf6RRx.d.ts.map +1 -0
  26. package/dist/live-events.d.ts +2 -0
  27. package/dist/live-events.js +2 -0
  28. package/dist/notification-DzOd9cRc.d.ts +20 -0
  29. package/dist/notification-DzOd9cRc.d.ts.map +1 -0
  30. package/dist/notification.d.ts +2 -0
  31. package/dist/notification.js +54 -0
  32. package/dist/notification.js.map +1 -0
  33. package/dist/plugin.d.ts +90 -0
  34. package/dist/plugin.d.ts.map +1 -0
  35. package/dist/plugin.js +213 -0
  36. package/dist/plugin.js.map +1 -0
  37. package/dist/projection.d.ts +101 -0
  38. package/dist/projection.d.ts.map +1 -0
  39. package/dist/projection.js +550 -0
  40. package/dist/projection.js.map +1 -0
  41. package/dist/protocol-CsrnSPOv.d.ts +115 -0
  42. package/dist/protocol-CsrnSPOv.d.ts.map +1 -0
  43. package/dist/protocol.d.ts +2 -0
  44. package/dist/protocol.js +365 -0
  45. package/dist/protocol.js.map +1 -0
  46. package/dist/react.d.ts +139 -0
  47. package/dist/react.d.ts.map +1 -0
  48. package/dist/react.js +796 -0
  49. package/dist/react.js.map +1 -0
  50. package/dist/server.d.ts +5 -0
  51. package/dist/server.js +4 -0
  52. package/dist/session-status-CZLTo8Km.d.ts +71 -0
  53. package/dist/session-status-CZLTo8Km.d.ts.map +1 -0
  54. package/dist/styles.css +619 -0
  55. package/docs/index.md +10 -0
  56. package/package.json +115 -0
  57. package/src/cli.ts +59 -0
  58. package/src/config.ts +87 -0
  59. package/src/extension.ts +486 -0
  60. package/src/index.ts +14 -0
  61. package/src/jsonl-tail.ts +110 -0
  62. package/src/live-events.ts +578 -0
  63. package/src/notification.ts +109 -0
  64. package/src/plugin.ts +369 -0
  65. package/src/projection.ts +995 -0
  66. package/src/protocol.ts +805 -0
  67. package/src/react.tsx +1293 -0
  68. package/src/server.ts +15 -0
  69. package/src/session-status.ts +379 -0
  70. package/src/styles.css +619 -0
package/dist/react.js ADDED
@@ -0,0 +1,796 @@
1
+ import "./styles.css";
2
+ import { PierrePatchDiff } from "@overmux/git/react";
3
+ import { ChevronRight, Send, Square } from "lucide-react";
4
+ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
5
+ import ReactMarkdown from "react-markdown";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/react.tsx
8
+ const contextWindowOverrides = /* @__PURE__ */ new Map([
9
+ ["openai-codex/gpt-5.6-luna", 372e3],
10
+ ["openai-codex/gpt-5.6-sol", 372e3],
11
+ ["openai-codex/gpt-5.6-terra", 372e3]
12
+ ]);
13
+ const formatTokenCount = (tokens) => tokens < 1e3 ? `${tokens}` : `${(tokens / 1e3).toFixed(1)}k`;
14
+ const contextUsageLabel = ({ contextUsage, model }) => {
15
+ if (!contextUsage) return;
16
+ const contextWindow = contextWindowOverrides.get(`${model?.provider}/${model?.id}`) ?? contextUsage.contextWindow;
17
+ return `ctx ${formatTokenCount(contextUsage.tokens)}/${formatTokenCount(contextWindow)} ${(contextUsage.tokens / contextWindow * 100).toFixed(1)}%`;
18
+ };
19
+ const thinkingLevels = [
20
+ "off",
21
+ "minimal",
22
+ "low",
23
+ "medium",
24
+ "high",
25
+ "xhigh",
26
+ "max"
27
+ ];
28
+ const PiConversationMetadata = ({ busy, metadata, setModel, setThinkingLevel }) => {
29
+ const [changing, setChanging] = useState(false);
30
+ const [error, setError] = useState("");
31
+ const update = async (action) => {
32
+ setChanging(true);
33
+ try {
34
+ await action();
35
+ setError("");
36
+ } catch (cause) {
37
+ setError(cause instanceof Error ? cause.message : String(cause));
38
+ } finally {
39
+ setChanging(false);
40
+ }
41
+ };
42
+ const disabled = busy || changing;
43
+ const currentModel = metadata.model;
44
+ return /* @__PURE__ */ jsxs("div", {
45
+ className: "om-pi-conversation-metadata om-pi-muted",
46
+ children: [
47
+ currentModel && metadata.modelOptions?.length && setModel ? /* @__PURE__ */ jsx("select", {
48
+ "aria-label": "Pi model",
49
+ className: "om-pi-metadata-select",
50
+ disabled,
51
+ onChange: (event) => void update(() => setModel(JSON.parse(event.target.value))),
52
+ value: JSON.stringify({
53
+ provider: currentModel.provider,
54
+ id: currentModel.id
55
+ }),
56
+ children: metadata.modelOptions.map((model) => /* @__PURE__ */ jsx("option", {
57
+ value: JSON.stringify({
58
+ provider: model.provider,
59
+ id: model.id
60
+ }),
61
+ children: model.id
62
+ }, `${model.provider}\0${model.id}`))
63
+ }) : /* @__PURE__ */ jsx("span", { children: currentModel?.id ?? "no-model" }),
64
+ /* @__PURE__ */ jsx("span", {
65
+ "aria-hidden": "true",
66
+ children: " · "
67
+ }),
68
+ setThinkingLevel ? /* @__PURE__ */ jsx("select", {
69
+ "aria-label": "Pi thinking level",
70
+ className: "om-pi-metadata-select",
71
+ disabled,
72
+ onChange: (event) => void update(() => setThinkingLevel(event.target.value)),
73
+ value: metadata.thinkingLevel,
74
+ children: thinkingLevels.map((level) => /* @__PURE__ */ jsx("option", {
75
+ value: level,
76
+ children: level
77
+ }, level))
78
+ }) : /* @__PURE__ */ jsx("span", { children: metadata.thinkingLevel }),
79
+ contextUsageLabel(metadata) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
80
+ "aria-hidden": "true",
81
+ children: " · "
82
+ }), /* @__PURE__ */ jsx("span", { children: contextUsageLabel(metadata) })] }) : null,
83
+ error ? /* @__PURE__ */ jsx("span", {
84
+ className: "om-pi-metadata-error",
85
+ role: "alert",
86
+ children: error
87
+ }) : null
88
+ ]
89
+ });
90
+ };
91
+ const languageLoaders = {
92
+ bash: () => import("@shikijs/langs/bash"),
93
+ css: () => import("@shikijs/langs/css"),
94
+ diff: () => import("@shikijs/langs/diff"),
95
+ html: () => import("@shikijs/langs/html"),
96
+ javascript: () => import("@shikijs/langs/javascript"),
97
+ json: () => import("@shikijs/langs/json"),
98
+ markdown: () => import("@shikijs/langs/markdown"),
99
+ nix: () => import("@shikijs/langs/nix"),
100
+ typescript: () => import("@shikijs/langs/typescript"),
101
+ tsx: () => import("@shikijs/langs/tsx"),
102
+ yaml: () => import("@shikijs/langs/yaml")
103
+ };
104
+ const languageAliases = {
105
+ js: "javascript",
106
+ md: "markdown",
107
+ sh: "bash",
108
+ shell: "bash",
109
+ ts: "typescript",
110
+ yml: "yaml"
111
+ };
112
+ const maxHighlightedCodeEntries = 64;
113
+ let highlighterPromise;
114
+ const highlightedCode = /* @__PURE__ */ new Map();
115
+ const loadHighlighter = () => {
116
+ if (!highlighterPromise) highlighterPromise = Promise.all([
117
+ import("shiki/core"),
118
+ import("shiki/engine/javascript"),
119
+ import("@shikijs/themes/tokyo-night")
120
+ ]).then(async ([core, engine, theme]) => core.createHighlighterCore({
121
+ engine: engine.createJavaScriptRegexEngine(),
122
+ langs: await Promise.all(Object.values(languageLoaders).map(async (loadLanguage) => loadLanguage())),
123
+ themes: [theme.default]
124
+ }));
125
+ return highlighterPromise;
126
+ };
127
+ loadHighlighter();
128
+ const highlighted = (code, language) => {
129
+ const key = `${language}\u0000${code}`;
130
+ const cached = highlightedCode.get(key);
131
+ if (cached) return cached;
132
+ const next = loadHighlighter().then((highlighter) => highlighter.codeToTokens(code, {
133
+ lang: language,
134
+ theme: "tokyo-night"
135
+ }).tokens);
136
+ highlightedCode.set(key, next);
137
+ if (highlightedCode.size > maxHighlightedCodeEntries) {
138
+ const oldest = highlightedCode.keys().next().value;
139
+ if (oldest) highlightedCode.delete(oldest);
140
+ }
141
+ return next;
142
+ };
143
+ const textOf = (block) => block.text ?? block.thinking ?? (typeof block.data === "string" ? block.data : "");
144
+ const formatted = (value) => {
145
+ if (typeof value === "string") try {
146
+ return JSON.stringify(JSON.parse(value), null, 2);
147
+ } catch {
148
+ return value;
149
+ }
150
+ try {
151
+ return JSON.stringify(value, null, 2) ?? "";
152
+ } catch {
153
+ return String(value);
154
+ }
155
+ };
156
+ const codeLanguage = (className) => {
157
+ const language = /language-([^\s]+)/.exec(className ?? "")?.[1]?.toLowerCase();
158
+ if (!language) return;
159
+ const normalized = languageAliases[language] ?? language;
160
+ return Object.hasOwn(languageLoaders, normalized) ? normalized : void 0;
161
+ };
162
+ const FencedCode = ({ children, className, node: _node, ...props }) => {
163
+ const code = String(children).replace(/\n$/, "");
164
+ const language = codeLanguage(className);
165
+ const highlightKey = language ? `${language}\u0000${code}` : void 0;
166
+ const [highlightedState, setHighlightedState] = useState();
167
+ const lines = highlightedState && highlightedState.key === highlightKey ? highlightedState.lines : void 0;
168
+ useEffect(() => {
169
+ if (!language || !highlightKey) return;
170
+ let active = true;
171
+ highlighted(code, language).then((value) => {
172
+ if (active) setHighlightedState({
173
+ key: highlightKey,
174
+ lines: value
175
+ });
176
+ });
177
+ return () => {
178
+ active = false;
179
+ };
180
+ }, [
181
+ code,
182
+ highlightKey,
183
+ language
184
+ ]);
185
+ if (!language || !lines) return /* @__PURE__ */ jsx("code", {
186
+ className,
187
+ ...props,
188
+ children
189
+ });
190
+ return /* @__PURE__ */ jsx("code", {
191
+ className: "om-pi-code-highlighted",
192
+ ...props,
193
+ children: lines.map((line, index) => /* @__PURE__ */ jsxs("span", {
194
+ className: "om-pi-code-line",
195
+ children: [line.map((token, tokenIndex) => /* @__PURE__ */ jsx("span", {
196
+ style: { color: token.color },
197
+ children: token.content
198
+ }, tokenIndex)), index < lines.length - 1 ? "\n" : null]
199
+ }, index))
200
+ });
201
+ };
202
+ const markdownComponents = { code: FencedCode };
203
+ const MarkdownPart = ({ block, subdued = false }) => /* @__PURE__ */ jsx("div", {
204
+ className: ["om-pi-markdown", subdued && "om-pi-muted"].filter(Boolean).join(" "),
205
+ children: /* @__PURE__ */ jsx(ReactMarkdown, {
206
+ components: markdownComponents,
207
+ children: textOf(block)
208
+ })
209
+ });
210
+ const preview = (value, maxLength = 120) => value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
211
+ const bounded = (value, maxLines = 200, maxLength = 2e4) => {
212
+ const lines = value.split("\n").slice(0, maxLines).join("\n");
213
+ return lines.length > maxLength ? `${lines.slice(0, maxLength - 1)}…` : lines;
214
+ };
215
+ const detailsRecord = (details) => typeof details === "object" && details !== null ? details : void 0;
216
+ const resultText = (result) => result?.content.map((block) => block.text ?? block.thinking ?? block.data ?? "").filter(Boolean).join("\n") ?? "";
217
+ const truncationSummary = (details) => {
218
+ const truncation = detailsRecord(details)?.truncation;
219
+ const record = detailsRecord(truncation);
220
+ if (!record?.truncated) return;
221
+ const totalLines = typeof record.totalLines === "number" ? record.totalLines : void 0;
222
+ const outputLines = typeof record.outputLines === "number" ? record.outputLines : void 0;
223
+ return totalLines && outputLines ? `${totalLines - outputLines} lines omitted` : "Output truncated";
224
+ };
225
+ const ToolDisclosure = ({ children, collapsible = true, indicator, label, status }) => {
226
+ const [open, setOpen] = useState(status !== "success");
227
+ const explicit = useRef(false);
228
+ useEffect(() => {
229
+ if (!explicit.current) setOpen(status !== "success");
230
+ }, [status]);
231
+ const header = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
232
+ className: `om-pi-tool-status${indicator ? " om-pi-tool-status-label" : ""}`,
233
+ "aria-label": `Tool ${status}`,
234
+ children: indicator ?? (status === "success" ? "✓" : status === "error" ? "×" : "●")
235
+ }), label] });
236
+ if (!collapsible) return /* @__PURE__ */ jsxs("div", {
237
+ className: "om-pi-tool-details",
238
+ "data-om-tool-status": status,
239
+ children: [/* @__PURE__ */ jsx("div", {
240
+ className: "om-pi-tool-summary",
241
+ children: header
242
+ }), children ? /* @__PURE__ */ jsx("div", {
243
+ className: "om-pi-tool-body",
244
+ children
245
+ }) : null]
246
+ });
247
+ return /* @__PURE__ */ jsxs("details", {
248
+ className: "om-pi-tool-details",
249
+ "data-om-tool-status": status,
250
+ onToggle: (event) => setOpen(event.currentTarget.open),
251
+ open,
252
+ children: [/* @__PURE__ */ jsx("summary", {
253
+ onClick: () => {
254
+ explicit.current = true;
255
+ },
256
+ children: header
257
+ }), children ? /* @__PURE__ */ jsx("div", {
258
+ className: "om-pi-tool-body",
259
+ children
260
+ }) : null]
261
+ });
262
+ };
263
+ const ThinkingPart = ({ block, status }) => {
264
+ const thinking = textOf(block);
265
+ const [open, setOpen] = useState(status === "pending");
266
+ const explicit = useRef(false);
267
+ useEffect(() => {
268
+ if (!explicit.current) setOpen(status === "pending");
269
+ }, [status]);
270
+ return /* @__PURE__ */ jsxs("details", {
271
+ className: "om-pi-thinking",
272
+ onToggle: (event) => setOpen(event.currentTarget.open),
273
+ open,
274
+ children: [/* @__PURE__ */ jsx("summary", {
275
+ onClick: () => {
276
+ explicit.current = true;
277
+ },
278
+ children: status === "pending" && thinking ? preview(thinking) : "Thinking…"
279
+ }), /* @__PURE__ */ jsx("div", {
280
+ className: "om-pi-content-block",
281
+ children: /* @__PURE__ */ jsx(MarkdownPart, {
282
+ block,
283
+ subdued: true
284
+ })
285
+ })]
286
+ });
287
+ };
288
+ const ImagePart = ({ block }) => /* @__PURE__ */ jsxs("p", {
289
+ className: "om-pi-muted",
290
+ children: ["Image attachment ", block.mimeType ?? ""]
291
+ });
292
+ const toolStatus = (tool) => tool.result?.isError ? "error" : tool.result ? "success" : tool.status;
293
+ const argumentsRecord = (arguments_) => typeof arguments_ === "object" && arguments_ !== null ? arguments_ : void 0;
294
+ const stringArgument = (arguments_, name) => {
295
+ const value = argumentsRecord(arguments_)?.[name];
296
+ return typeof value === "string" ? value : void 0;
297
+ };
298
+ const numberArgument = (arguments_, name) => {
299
+ const value = argumentsRecord(arguments_)?.[name];
300
+ return typeof value === "number" ? value : void 0;
301
+ };
302
+ const Output = ({ result }) => {
303
+ const content = resultText(result);
304
+ if (!content && !result?.isError) return null;
305
+ return /* @__PURE__ */ jsx("pre", {
306
+ className: result?.isError ? "om-pi-tool-error" : "om-pi-tool-output",
307
+ children: bounded(content || formatted(result?.details))
308
+ });
309
+ };
310
+ const toolFrame = ({ result, status }, label, children) => /* @__PURE__ */ jsxs(ToolDisclosure, {
311
+ label,
312
+ status,
313
+ children: [children, /* @__PURE__ */ jsx(Output, { result })]
314
+ });
315
+ const ToolSection = ({ children, defaultOpen, label, status }) => {
316
+ const shouldOpen = defaultOpen ?? status !== "success";
317
+ const [open, setOpen] = useState(shouldOpen);
318
+ const explicit = useRef(false);
319
+ useEffect(() => {
320
+ if (!explicit.current) setOpen(shouldOpen);
321
+ }, [shouldOpen]);
322
+ return /* @__PURE__ */ jsxs("details", {
323
+ className: "om-pi-tool-section",
324
+ "data-om-tool-status": status,
325
+ onToggle: (event) => setOpen(event.currentTarget.open),
326
+ open,
327
+ children: [/* @__PURE__ */ jsxs("summary", {
328
+ onClick: () => {
329
+ explicit.current = true;
330
+ },
331
+ children: [/* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }), /* @__PURE__ */ jsx("span", { children: label })]
332
+ }), children ? /* @__PURE__ */ jsx("div", {
333
+ className: "om-pi-tool-body",
334
+ children
335
+ }) : null]
336
+ });
337
+ };
338
+ const BashToolRenderer = (input) => {
339
+ const command = stringArgument(input.arguments, "command");
340
+ const truncation = truncationSummary(input.result?.details);
341
+ return /* @__PURE__ */ jsxs("div", {
342
+ className: "om-pi-tool-details",
343
+ "data-om-tool-status": input.status,
344
+ children: [
345
+ /* @__PURE__ */ jsx("div", {
346
+ className: "om-pi-tool-kind",
347
+ children: /* @__PURE__ */ jsx("span", {
348
+ className: "om-pi-tool-status om-pi-tool-status-label",
349
+ "aria-label": `Tool ${input.status}`,
350
+ children: "bash"
351
+ })
352
+ }),
353
+ /* @__PURE__ */ jsx("pre", {
354
+ className: "om-pi-tool-command",
355
+ children: /* @__PURE__ */ jsxs("code", { children: ["$ ", command ?? "bash"] })
356
+ }),
357
+ /* @__PURE__ */ jsxs(ToolSection, {
358
+ label: "Logs",
359
+ status: input.status,
360
+ children: [truncation ? /* @__PURE__ */ jsx("p", {
361
+ className: "om-pi-tool-field",
362
+ children: truncation
363
+ }) : null, /* @__PURE__ */ jsx(Output, { result: input.result })]
364
+ })
365
+ ]
366
+ });
367
+ };
368
+ const BashProcessToolRenderer = (input) => {
369
+ const action = stringArgument(input.arguments, "action") ?? "bash_process";
370
+ const pgid = numberArgument(input.arguments, "pgid") ?? stringArgument(input.arguments, "pgid");
371
+ return toolFrame(input, `${action}${pgid === void 0 ? "" : ` · ${pgid}`}`);
372
+ };
373
+ const languageForPath = (path) => {
374
+ const extension = path?.split(".").at(-1)?.toLowerCase();
375
+ return extension ? languageAliases[extension] ?? extension : "";
376
+ };
377
+ const ReadToolRenderer = (input) => {
378
+ const path = stringArgument(input.arguments, "path");
379
+ const offset = numberArgument(input.arguments, "offset") ?? 1;
380
+ const limit = numberArgument(input.arguments, "limit");
381
+ const range = limit === void 0 ? `:${offset}` : `:${offset}-${offset + limit - 1}`;
382
+ const content = resultText(input.result);
383
+ const language = languageForPath(path);
384
+ return /* @__PURE__ */ jsxs(ToolDisclosure, {
385
+ indicator: "read",
386
+ label: `${path ?? "read"}${range}`,
387
+ status: input.status,
388
+ children: [input.result?.isError ? /* @__PURE__ */ jsx(Output, { result: input.result }) : null, content && !input.result?.isError ? /* @__PURE__ */ jsx(MarkdownPart, {
389
+ block: {
390
+ text: `\`\`\`${language}\n${bounded(content)}\n\`\`\``,
391
+ type: "text"
392
+ },
393
+ subdued: true
394
+ }) : null]
395
+ });
396
+ };
397
+ const editResultDetails = (details) => {
398
+ const record = detailsRecord(details);
399
+ return typeof record?.diff === "string" ? {
400
+ diff: record.diff,
401
+ patch: typeof record.patch === "string" ? record.patch : void 0
402
+ } : void 0;
403
+ };
404
+ const EditToolRenderer = (input) => {
405
+ const details = editResultDetails(input.result?.details);
406
+ const diff = details?.diff;
407
+ const label = /* @__PURE__ */ jsx("code", {
408
+ className: "om-pi-tool-path",
409
+ children: stringArgument(input.arguments, "path") ?? "edit"
410
+ });
411
+ return /* @__PURE__ */ jsxs(ToolDisclosure, {
412
+ collapsible: false,
413
+ indicator: "edit",
414
+ label,
415
+ status: input.status,
416
+ children: [diff ? /* @__PURE__ */ jsx("div", {
417
+ className: "om-pi-tool-diff",
418
+ children: details?.patch ? /* @__PURE__ */ jsx(PierrePatchDiff, {
419
+ options: { hunkSeparators: "simple" },
420
+ patch: details.patch
421
+ }) : /* @__PURE__ */ jsx("pre", { children: bounded(diff) })
422
+ }) : null, input.result?.isError ? /* @__PURE__ */ jsx(ToolSection, {
423
+ defaultOpen: false,
424
+ label: "Error",
425
+ status: input.status,
426
+ children: /* @__PURE__ */ jsx(Output, { result: input.result })
427
+ }) : null]
428
+ });
429
+ };
430
+ const newFilePatch = (path, content) => {
431
+ const safePath = path.replace(/[\r\n]/g, "_");
432
+ const displayed = bounded(content);
433
+ const hasTrailingNewline = displayed.endsWith("\n");
434
+ const lines = displayed ? displayed.split("\n") : [];
435
+ const additions = hasTrailingNewline ? lines.slice(0, -1) : lines;
436
+ return `diff --git a/${safePath} b/${safePath}\nnew file mode 100644\n--- /dev/null\n+++ b/${safePath}\n${additions.length ? `@@ -0,0 +1,${additions.length} @@\n${additions.map((line) => `+${line}`).join("\n")}${hasTrailingNewline ? "\n" : ""}` : ""}`;
437
+ };
438
+ const WriteToolRenderer = (input) => {
439
+ const path = stringArgument(input.arguments, "path") ?? "write";
440
+ const content = stringArgument(input.arguments, "content");
441
+ return /* @__PURE__ */ jsxs(ToolDisclosure, {
442
+ collapsible: false,
443
+ indicator: "write",
444
+ label: /* @__PURE__ */ jsx("code", {
445
+ className: "om-pi-tool-path",
446
+ children: path
447
+ }),
448
+ status: input.status,
449
+ children: [content === void 0 ? null : /* @__PURE__ */ jsx("div", {
450
+ className: "om-pi-tool-diff",
451
+ "data-patch": newFilePatch(path, content),
452
+ children: /* @__PURE__ */ jsx(PierrePatchDiff, {
453
+ options: { hunkSeparators: "simple" },
454
+ patch: newFilePatch(path, content)
455
+ })
456
+ }), input.result?.isError ? /* @__PURE__ */ jsx(ToolSection, {
457
+ defaultOpen: false,
458
+ label: "Error",
459
+ status: input.status,
460
+ children: /* @__PURE__ */ jsx(Output, { result: input.result })
461
+ }) : null]
462
+ });
463
+ };
464
+ const GenericToolRenderer = (input) => toolFrame(input, input.toolName, input.arguments === void 0 ? null : /* @__PURE__ */ jsx("pre", {
465
+ className: "om-pi-tool-input",
466
+ children: formatted(input.arguments)
467
+ }));
468
+ const defaultToolRenderers = {
469
+ bash: BashToolRenderer,
470
+ bash_process: BashProcessToolRenderer,
471
+ edit: EditToolRenderer,
472
+ read: ReadToolRenderer,
473
+ write: WriteToolRenderer
474
+ };
475
+ const ToolPart = ({ block, entry, renderers }) => {
476
+ const knownToolName = block.name ?? entry.toolName;
477
+ const tool = block.tool ?? { status: "pending" };
478
+ if (!knownToolName && tool.status === "pending") return /* @__PURE__ */ jsx("p", {
479
+ className: "om-pi-muted",
480
+ role: "status",
481
+ children: "Running tool…"
482
+ });
483
+ const toolName = knownToolName ?? "Tool call";
484
+ return /* @__PURE__ */ jsx(renderers[toolName] ?? GenericToolRenderer, {
485
+ arguments: block.arguments,
486
+ result: tool.result,
487
+ status: toolStatus(tool),
488
+ toolName
489
+ });
490
+ };
491
+ const MessagePart = ({ block, entry, renderers }) => {
492
+ if (block.type === "thinking") return /* @__PURE__ */ jsx(ThinkingPart, {
493
+ block,
494
+ status: entry.status
495
+ });
496
+ if (block.type === "toolCall") return /* @__PURE__ */ jsx(ToolPart, {
497
+ block,
498
+ entry,
499
+ renderers
500
+ });
501
+ if (block.type === "image") return /* @__PURE__ */ jsx(ImagePart, { block });
502
+ return /* @__PURE__ */ jsx(MarkdownPart, { block });
503
+ };
504
+ const roleName = (entry) => {
505
+ if (entry.role === "toolResult") return entry.toolName ? `${entry.toolName} result` : "Tool result";
506
+ return entry.role === "user" ? "You" : "Pi";
507
+ };
508
+ const PiConversationMessage = ({ className, entry, renderBlock, renderers }) => /* @__PURE__ */ jsxs("article", {
509
+ "aria-label": `${roleName(entry)} message`,
510
+ className: ["om-pi-conversation-entry", className].filter(Boolean).join(" "),
511
+ "data-om-role": entry.role,
512
+ "data-om-source": entry.source,
513
+ "data-om-status": entry.status,
514
+ children: [
515
+ entry.role === "user" || entry.status !== "complete" ? /* @__PURE__ */ jsxs("header", {
516
+ className: "om-pi-muted",
517
+ children: [
518
+ entry.role === "user" ? /* @__PURE__ */ jsx("span", { children: "You" }) : null,
519
+ entry.status === "pending" ? /* @__PURE__ */ jsx("span", {
520
+ role: "status",
521
+ children: "Streaming…"
522
+ }) : null,
523
+ entry.status === "error" ? /* @__PURE__ */ jsx("span", {
524
+ className: "om-pi-tool-error",
525
+ children: "Error"
526
+ }) : null
527
+ ]
528
+ }) : null,
529
+ /* @__PURE__ */ jsxs("div", {
530
+ className: "om-pi-content-block",
531
+ children: [entry.content.map((block, index) => /* @__PURE__ */ jsx("div", { children: renderBlock?.({
532
+ block,
533
+ entry,
534
+ index
535
+ }) ?? /* @__PURE__ */ jsx(MessagePart, {
536
+ block,
537
+ entry,
538
+ renderers
539
+ }) }, `${block.type}:${block.id ?? index}`)), !entry.content.length && entry.status === "pending" ? /* @__PURE__ */ jsx("p", {
540
+ className: "om-pi-muted",
541
+ role: "status",
542
+ children: "Waiting for output…"
543
+ }) : null]
544
+ }),
545
+ entry.errorMessage ? /* @__PURE__ */ jsx("p", {
546
+ className: "om-pi-conversation-error",
547
+ role: "alert",
548
+ children: entry.errorMessage
549
+ }) : null
550
+ ]
551
+ });
552
+ const statusLabel = (status) => {
553
+ if (status === "busy") return "Pi is working";
554
+ if (status === "degraded") return "Pi updates are degraded";
555
+ if (status === "idle") return "Pi is ready";
556
+ return "Pi is offline";
557
+ };
558
+ const PiMessageComposer = ({ autoFocus = false, available, busy = false, className, sendMessage, stop, style }) => {
559
+ const messageId = useId();
560
+ const textareaRef = useRef(null);
561
+ const [draft, setDraft] = useState("");
562
+ const [error, setError] = useState("");
563
+ const [focused, setFocused] = useState(false);
564
+ const [sending, setSending] = useState();
565
+ const [stopping, setStopping] = useState(false);
566
+ const actionsVisible = focused || Boolean(draft);
567
+ useLayoutEffect(() => {
568
+ const textarea = textareaRef.current;
569
+ if (!textarea) return;
570
+ textarea.style.height = "auto";
571
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 192)}px`;
572
+ }, [draft]);
573
+ useLayoutEffect(() => {
574
+ if (autoFocus && available) textareaRef.current?.focus();
575
+ }, [autoFocus, available]);
576
+ const submit = async (deliverAs) => {
577
+ const message = draft.trim();
578
+ if (!message || !available || sending || stopping) return;
579
+ setSending(deliverAs);
580
+ try {
581
+ await sendMessage({
582
+ deliverAs,
583
+ message
584
+ });
585
+ setDraft("");
586
+ setError("");
587
+ } catch (cause) {
588
+ setError(cause instanceof Error ? cause.message : String(cause));
589
+ } finally {
590
+ setSending(void 0);
591
+ }
592
+ };
593
+ const abort = async () => {
594
+ if (!available || !busy || !stop || sending || stopping) return;
595
+ setStopping(true);
596
+ try {
597
+ await stop();
598
+ setError("");
599
+ } catch (cause) {
600
+ setError(cause instanceof Error ? cause.message : String(cause));
601
+ } finally {
602
+ setStopping(false);
603
+ }
604
+ };
605
+ const onSubmit = (event) => {
606
+ event.preventDefault();
607
+ submit("steer");
608
+ };
609
+ const onKeyDown = (event) => {
610
+ if (event.key !== "Enter" || !event.metaKey && !event.ctrlKey) return;
611
+ event.preventDefault();
612
+ submit(busy && event.shiftKey ? "followUp" : "steer");
613
+ };
614
+ return /* @__PURE__ */ jsxs("form", {
615
+ className: ["om-pi-composer", className].filter(Boolean).join(" "),
616
+ "data-om-pi-composer": true,
617
+ onSubmit,
618
+ style,
619
+ children: [/* @__PURE__ */ jsxs("div", {
620
+ className: "om-pi-composer-box",
621
+ children: [
622
+ /* @__PURE__ */ jsx("label", {
623
+ className: "om-pi-visually-hidden",
624
+ htmlFor: messageId,
625
+ children: "Message Pi"
626
+ }),
627
+ /* @__PURE__ */ jsx("textarea", {
628
+ className: ["om-pi-composer-textarea", busy && !actionsVisible ? "om-pi-composer-textarea-with-stop" : void 0].filter(Boolean).join(" "),
629
+ disabled: !available || Boolean(sending) || stopping,
630
+ id: messageId,
631
+ onBlur: () => setFocused(false),
632
+ onChange: (event) => setDraft(event.target.value),
633
+ onFocus: () => setFocused(true),
634
+ onKeyDown,
635
+ placeholder: available ? "Message Pi…" : "Pi is offline",
636
+ ref: textareaRef,
637
+ rows: 1,
638
+ value: draft
639
+ }),
640
+ busy && !actionsVisible ? /* @__PURE__ */ jsx("button", {
641
+ "aria-label": stopping ? "Stopping Pi" : "Stop Pi",
642
+ className: "om-pi-composer-stop om-pi-composer-stop-inline",
643
+ disabled: !available || !stop || Boolean(sending) || stopping,
644
+ onClick: () => void abort(),
645
+ title: "Stop Pi",
646
+ type: "button",
647
+ children: /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
648
+ }) : null,
649
+ /* @__PURE__ */ jsxs("div", {
650
+ className: "om-pi-composer-actions",
651
+ children: [
652
+ /* @__PURE__ */ jsx("span", {
653
+ className: "om-pi-muted",
654
+ children: "Ctrl/⌘ Enter · Shift queues"
655
+ }),
656
+ busy ? /* @__PURE__ */ jsxs(Fragment, { children: [actionsVisible ? /* @__PURE__ */ jsx("button", {
657
+ "aria-label": stopping ? "Stopping Pi" : "Stop Pi",
658
+ className: "om-pi-composer-stop",
659
+ disabled: !available || !stop || Boolean(sending) || stopping,
660
+ onClick: () => void abort(),
661
+ title: "Stop Pi",
662
+ type: "button",
663
+ children: /* @__PURE__ */ jsx(Square, { "aria-hidden": "true" })
664
+ }) : null, /* @__PURE__ */ jsx("button", {
665
+ "aria-label": "Follow up after the current turn",
666
+ className: "om-pi-composer-follow-up",
667
+ disabled: !available || !draft.trim() || Boolean(sending) || stopping,
668
+ onClick: () => void submit("followUp"),
669
+ type: "button",
670
+ children: sending === "followUp" ? "Queueing…" : "Follow up"
671
+ })] }) : null,
672
+ /* @__PURE__ */ jsxs("button", {
673
+ className: "om-pi-composer-send",
674
+ disabled: !available || !draft.trim() || Boolean(sending) || stopping,
675
+ type: "submit",
676
+ children: [/* @__PURE__ */ jsx(Send, { "aria-hidden": "true" }), sending === "steer" ? "Sending…" : "Send"]
677
+ })
678
+ ]
679
+ })
680
+ ]
681
+ }), error ? /* @__PURE__ */ jsx("p", {
682
+ className: "om-pi-conversation-error",
683
+ role: "alert",
684
+ children: error
685
+ }) : null]
686
+ });
687
+ };
688
+ const PiConversation = ({ autoFocus = false, conversation, className, classNames = {}, emptyState, renderBlock, renderers, sendMessage, setModel, setThinkingLevel, stop, style }) => {
689
+ const [snapshot, setSnapshot] = useState();
690
+ useLayoutEffect(() => conversation.subscribe(setSnapshot), [conversation]);
691
+ const error = conversation.error?.message;
692
+ const status = snapshot?.status ?? "offline";
693
+ const scrollerRef = useRef(null);
694
+ const followingRef = useRef(true);
695
+ const [following, setFollowing] = useState(true);
696
+ const toolRenderers = {
697
+ ...defaultToolRenderers,
698
+ ...renderers
699
+ };
700
+ const scrollToLatest = useCallback(() => {
701
+ const scroller = scrollerRef.current;
702
+ if (!scroller) return;
703
+ followingRef.current = true;
704
+ setFollowing(true);
705
+ scroller.scrollTop = scroller.scrollHeight;
706
+ }, []);
707
+ const updateFollowing = useCallback(() => {
708
+ const scroller = scrollerRef.current;
709
+ if (!scroller) return;
710
+ const next = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < 48;
711
+ followingRef.current = next;
712
+ setFollowing(next);
713
+ }, []);
714
+ useLayoutEffect(() => {
715
+ if (followingRef.current) scrollToLatest();
716
+ }, [scrollToLatest, snapshot]);
717
+ return /* @__PURE__ */ jsxs("section", {
718
+ "aria-label": "Pi conversation",
719
+ className: [
720
+ "om-pi-conversation",
721
+ className,
722
+ classNames.root
723
+ ].filter(Boolean).join(" "),
724
+ "data-om-agent-status": status,
725
+ "data-om-pi-conversation": true,
726
+ style,
727
+ children: [
728
+ /* @__PURE__ */ jsx("header", {
729
+ className: ["om-pi-conversation-status", classNames.status].filter(Boolean).join(" "),
730
+ children: /* @__PURE__ */ jsxs("div", {
731
+ className: "om-pi-conversation-status-row",
732
+ children: [/* @__PURE__ */ jsx("strong", { children: "Pi conversation" }), /* @__PURE__ */ jsx("span", {
733
+ "aria-live": "polite",
734
+ className: "om-pi-muted",
735
+ role: "status",
736
+ children: !snapshot && !error ? "Connecting to Pi…" : statusLabel(status)
737
+ })]
738
+ })
739
+ }),
740
+ error ? /* @__PURE__ */ jsx("p", {
741
+ className: "om-pi-conversation-error",
742
+ role: "alert",
743
+ children: error
744
+ }) : null,
745
+ /* @__PURE__ */ jsx("div", {
746
+ className: ["om-pi-conversation-scroller", classNames.scroller].filter(Boolean).join(" "),
747
+ onScroll: updateFollowing,
748
+ ref: scrollerRef,
749
+ tabIndex: 0,
750
+ children: /* @__PURE__ */ jsxs("div", {
751
+ className: ["om-pi-conversation-content", classNames.content].filter(Boolean).join(" "),
752
+ children: [
753
+ snapshot?.entries.map((entry) => /* @__PURE__ */ jsx(PiConversationMessage, {
754
+ className: ["om-pi-conversation-entry", classNames.entry].filter(Boolean).join(" "),
755
+ entry,
756
+ renderBlock,
757
+ renderers: toolRenderers
758
+ }, entry.id)),
759
+ snapshot && !snapshot.entries.length ? emptyState ?? /* @__PURE__ */ jsx("p", {
760
+ className: "om-pi-muted",
761
+ children: "No conversation yet."
762
+ }) : null,
763
+ !snapshot && error ? /* @__PURE__ */ jsx("p", {
764
+ className: "om-pi-muted",
765
+ children: "Conversation unavailable while Pi is offline."
766
+ }) : null
767
+ ]
768
+ })
769
+ }),
770
+ !following ? /* @__PURE__ */ jsx("button", {
771
+ className: "om-pi-conversation-jump",
772
+ onClick: scrollToLatest,
773
+ type: "button",
774
+ children: "Jump to latest"
775
+ }) : null,
776
+ /* @__PURE__ */ jsx(PiMessageComposer, {
777
+ autoFocus,
778
+ available: snapshot?.agentAvailable === true,
779
+ busy: status === "busy",
780
+ className: classNames.composer,
781
+ sendMessage,
782
+ stop
783
+ }),
784
+ snapshot?.sessionMetadata ? /* @__PURE__ */ jsx(PiConversationMetadata, {
785
+ busy: status === "busy",
786
+ metadata: snapshot.sessionMetadata,
787
+ setModel,
788
+ setThinkingLevel
789
+ }) : null
790
+ ]
791
+ });
792
+ };
793
+ //#endregion
794
+ export { PiConversation, PiMessageComposer, defaultToolRenderers };
795
+
796
+ //# sourceMappingURL=react.js.map