@oh-my-pi/pi-coding-agent 1.341.0 → 2.1.1337

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 (158) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +10 -9
  5. package/src/bun-imports.d.ts +16 -0
  6. package/src/cli/args.ts +5 -6
  7. package/src/cli/file-processor.ts +3 -3
  8. package/src/cli/list-models.ts +2 -2
  9. package/src/cli/plugin-cli.ts +1 -1
  10. package/src/cli/session-picker.ts +2 -2
  11. package/src/cli/update-cli.ts +273 -0
  12. package/src/cli.ts +1 -1
  13. package/src/config.ts +23 -75
  14. package/src/core/agent-session.ts +158 -16
  15. package/src/core/auth-storage.ts +2 -3
  16. package/src/core/bash-executor.ts +50 -10
  17. package/src/core/compaction/branch-summarization.ts +5 -5
  18. package/src/core/compaction/compaction.ts +3 -3
  19. package/src/core/compaction/index.ts +3 -3
  20. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  21. package/src/core/custom-commands/index.ts +15 -0
  22. package/src/core/custom-commands/loader.ts +232 -0
  23. package/src/core/custom-commands/types.ts +112 -0
  24. package/src/core/custom-tools/index.ts +3 -3
  25. package/src/core/custom-tools/loader.ts +10 -8
  26. package/src/core/custom-tools/types.ts +11 -6
  27. package/src/core/custom-tools/wrapper.ts +2 -1
  28. package/src/core/exec.ts +22 -12
  29. package/src/core/export-html/index.ts +38 -123
  30. package/src/core/export-html/template.css +0 -7
  31. package/src/core/export-html/template.html +3 -4
  32. package/src/core/export-html/template.macro.ts +24 -0
  33. package/src/core/file-mentions.ts +54 -0
  34. package/src/core/hooks/index.ts +5 -5
  35. package/src/core/hooks/loader.ts +21 -16
  36. package/src/core/hooks/runner.ts +6 -6
  37. package/src/core/hooks/tool-wrapper.ts +2 -2
  38. package/src/core/hooks/types.ts +12 -15
  39. package/src/core/index.ts +6 -6
  40. package/src/core/logger.ts +112 -0
  41. package/src/core/mcp/client.ts +3 -3
  42. package/src/core/mcp/config.ts +1 -1
  43. package/src/core/mcp/index.ts +12 -12
  44. package/src/core/mcp/loader.ts +2 -2
  45. package/src/core/mcp/manager.ts +6 -6
  46. package/src/core/mcp/tool-bridge.ts +3 -3
  47. package/src/core/mcp/transports/http.ts +1 -1
  48. package/src/core/mcp/transports/index.ts +2 -2
  49. package/src/core/mcp/transports/stdio.ts +1 -1
  50. package/src/core/messages.ts +22 -0
  51. package/src/core/model-registry.ts +2 -2
  52. package/src/core/model-resolver.ts +2 -2
  53. package/src/core/plugins/doctor.ts +1 -1
  54. package/src/core/plugins/index.ts +6 -6
  55. package/src/core/plugins/installer.ts +4 -4
  56. package/src/core/plugins/loader.ts +4 -9
  57. package/src/core/plugins/manager.ts +5 -5
  58. package/src/core/plugins/paths.ts +3 -3
  59. package/src/core/sdk.ts +77 -35
  60. package/src/core/session-manager.ts +6 -6
  61. package/src/core/settings-manager.ts +16 -3
  62. package/src/core/skills.ts +5 -5
  63. package/src/core/slash-commands.ts +60 -45
  64. package/src/core/system-prompt.ts +6 -6
  65. package/src/core/title-generator.ts +2 -2
  66. package/src/core/tools/bash.ts +32 -155
  67. package/src/core/tools/context.ts +2 -2
  68. package/src/core/tools/edit-diff.ts +3 -3
  69. package/src/core/tools/edit.ts +18 -5
  70. package/src/core/tools/exa/company.ts +3 -3
  71. package/src/core/tools/exa/index.ts +16 -17
  72. package/src/core/tools/exa/linkedin.ts +3 -3
  73. package/src/core/tools/exa/mcp-client.ts +9 -9
  74. package/src/core/tools/exa/render.ts +5 -5
  75. package/src/core/tools/exa/researcher.ts +3 -3
  76. package/src/core/tools/exa/search.ts +6 -5
  77. package/src/core/tools/exa/types.ts +5 -6
  78. package/src/core/tools/exa/websets.ts +3 -3
  79. package/src/core/tools/find.ts +3 -3
  80. package/src/core/tools/grep.ts +3 -3
  81. package/src/core/tools/index.ts +48 -34
  82. package/src/core/tools/ls.ts +4 -4
  83. package/src/core/tools/lsp/client.ts +161 -90
  84. package/src/core/tools/lsp/config.ts +1 -1
  85. package/src/core/tools/lsp/edits.ts +2 -2
  86. package/src/core/tools/lsp/index.ts +15 -13
  87. package/src/core/tools/lsp/render.ts +2 -2
  88. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  89. package/src/core/tools/lsp/utils.ts +1 -1
  90. package/src/core/tools/notebook.ts +1 -1
  91. package/src/core/tools/output.ts +175 -0
  92. package/src/core/tools/read.ts +7 -7
  93. package/src/core/tools/renderers.ts +92 -13
  94. package/src/core/tools/review.ts +268 -0
  95. package/src/core/tools/task/agents.ts +22 -38
  96. package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
  97. package/src/core/tools/task/commands.ts +31 -10
  98. package/src/core/tools/task/discovery.ts +2 -2
  99. package/src/core/tools/task/executor.ts +145 -28
  100. package/src/core/tools/task/index.ts +78 -30
  101. package/src/core/tools/task/model-resolver.ts +30 -20
  102. package/src/core/tools/task/parallel.ts +1 -1
  103. package/src/core/tools/task/render.ts +219 -30
  104. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  105. package/src/core/tools/task/types.ts +36 -2
  106. package/src/core/tools/web-fetch.ts +5 -3
  107. package/src/core/tools/web-search/auth.ts +1 -1
  108. package/src/core/tools/web-search/index.ts +17 -15
  109. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  110. package/src/core/tools/web-search/providers/exa.ts +3 -5
  111. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  112. package/src/core/tools/web-search/render.ts +3 -3
  113. package/src/core/tools/write.ts +4 -4
  114. package/src/index.ts +29 -18
  115. package/src/main.ts +50 -33
  116. package/src/migrations.ts +3 -3
  117. package/src/modes/index.ts +5 -5
  118. package/src/modes/interactive/components/armin.ts +1 -1
  119. package/src/modes/interactive/components/assistant-message.ts +1 -1
  120. package/src/modes/interactive/components/bash-execution.ts +4 -4
  121. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  122. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  123. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  124. package/src/modes/interactive/components/diff.ts +1 -1
  125. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  126. package/src/modes/interactive/components/footer.ts +5 -5
  127. package/src/modes/interactive/components/hook-editor.ts +2 -2
  128. package/src/modes/interactive/components/hook-input.ts +2 -2
  129. package/src/modes/interactive/components/hook-message.ts +3 -3
  130. package/src/modes/interactive/components/hook-selector.ts +2 -2
  131. package/src/modes/interactive/components/model-selector.ts +281 -59
  132. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  133. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  134. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  135. package/src/modes/interactive/components/session-selector.ts +4 -4
  136. package/src/modes/interactive/components/settings-defs.ts +1 -1
  137. package/src/modes/interactive/components/settings-selector.ts +5 -5
  138. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  139. package/src/modes/interactive/components/theme-selector.ts +2 -2
  140. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  141. package/src/modes/interactive/components/tool-execution.ts +26 -8
  142. package/src/modes/interactive/components/tree-selector.ts +3 -3
  143. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  144. package/src/modes/interactive/components/user-message.ts +1 -1
  145. package/src/modes/interactive/components/welcome.ts +2 -2
  146. package/src/modes/interactive/interactive-mode.ts +86 -42
  147. package/src/modes/interactive/theme/theme.ts +15 -17
  148. package/src/modes/print-mode.ts +4 -3
  149. package/src/modes/rpc/rpc-client.ts +4 -4
  150. package/src/modes/rpc/rpc-mode.ts +22 -12
  151. package/src/modes/rpc/rpc-types.ts +3 -3
  152. package/src/utils/changelog.ts +2 -2
  153. package/src/utils/clipboard.ts +1 -1
  154. package/src/utils/shell-snapshot.ts +218 -0
  155. package/src/utils/shell.ts +93 -13
  156. package/src/utils/tools-manager.ts +1 -1
  157. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  158. package/src/core/tools/exa/logger.ts +0 -56
@@ -1,44 +1,19 @@
1
+ import { existsSync, writeFileSync } from "node:fs";
2
+ import { basename } from "node:path";
1
3
  import type { AgentState } from "@oh-my-pi/pi-agent-core";
2
- import { existsSync, readFileSync, writeFileSync } from "fs";
3
- import { basename, join } from "path";
4
- import { APP_NAME, getExportTemplateDir } from "../../config.js";
5
- import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
6
- import { SessionManager } from "../session-manager.js";
7
-
8
- // Cached minified assets (populated on first use)
9
- let cachedTemplate: string | null = null;
10
- let cachedJs: string | null = null;
11
-
12
- /** Minify CSS by removing comments, unnecessary whitespace, and newlines. */
13
- function minifyCss(css: string): string {
14
- return css
15
- .replace(/\/\*[\s\S]*?\*\//g, "") // Remove comments
16
- .replace(/\s+/g, " ") // Collapse whitespace
17
- .replace(/\s*([{}:;,>+~])\s*/g, "$1") // Remove space around punctuation
18
- .replace(/;}/g, "}") // Remove trailing semicolons
19
- .trim();
20
- }
21
-
22
- /** Minify JS using Bun's transpiler. */
23
- function minifyJs(js: string): string {
24
- const transpiler = new Bun.Transpiler({ loader: "js", minifyWhitespace: true });
25
- return transpiler.transformSync(js);
26
- }
4
+ import { APP_NAME } from "../../config";
5
+ import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme";
6
+ import { SessionManager } from "../session-manager";
27
7
 
28
- /** Minify HTML by collapsing whitespace outside of tags. */
29
- function minifyHtml(html: string): string {
30
- return html
31
- .replace(/>\s+</g, "><") // Remove whitespace between tags
32
- .replace(/\s{2,}/g, " ") // Collapse multiple spaces
33
- .trim();
34
- }
8
+ // Bun macro: bundles HTML+CSS+JS at compile time, evaluated at bundle time
9
+ import { getTemplate } from "./template.macro" with { type: "macro" };
35
10
 
36
11
  export interface ExportOptions {
37
12
  outputPath?: string;
38
13
  themeName?: string;
39
14
  }
40
15
 
41
- /** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */
16
+ /** Parse a color string to RGB values. */
42
17
  function parseColor(color: string): { r: number; g: number; b: number } | undefined {
43
18
  const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
44
19
  if (hexMatch) {
@@ -68,7 +43,7 @@ function getLuminance(r: number, g: number, b: number): number {
68
43
  return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
69
44
  }
70
45
 
71
- /** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */
46
+ /** Adjust color brightness. */
72
47
  function adjustBrightness(color: string, factor: number): string {
73
48
  const parsed = parseColor(color);
74
49
  if (!parsed) return color;
@@ -76,21 +51,15 @@ function adjustBrightness(color: string, factor: number): string {
76
51
  return `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;
77
52
  }
78
53
 
79
- /** Derive export background colors from a base color (e.g., userMessageBg). */
54
+ /** Derive export background colors from a base color. */
80
55
  function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {
81
56
  const parsed = parseColor(baseColor);
82
57
  if (!parsed) {
83
- return {
84
- pageBg: "rgb(24, 24, 30)",
85
- cardBg: "rgb(30, 30, 36)",
86
- infoBg: "rgb(60, 55, 40)",
87
- };
58
+ return { pageBg: "rgb(24, 24, 30)", cardBg: "rgb(30, 30, 36)", infoBg: "rgb(60, 55, 40)" };
88
59
  }
89
60
 
90
61
  const luminance = getLuminance(parsed.r, parsed.g, parsed.b);
91
- const isLight = luminance > 0.5;
92
-
93
- if (isLight) {
62
+ if (luminance > 0.5) {
94
63
  return {
95
64
  pageBg: adjustBrightness(baseColor, 0.96),
96
65
  cardBg: baseColor,
@@ -104,9 +73,7 @@ function deriveExportColors(baseColor: string): { pageBg: string; cardBg: string
104
73
  };
105
74
  }
106
75
 
107
- /**
108
- * Generate CSS custom property declarations from theme colors.
109
- */
76
+ /** Generate CSS custom properties for theme. */
110
77
  function generateThemeVars(themeName?: string): string {
111
78
  const colors = getResolvedThemeColors(themeName);
112
79
  const lines: string[] = [];
@@ -114,16 +81,15 @@ function generateThemeVars(themeName?: string): string {
114
81
  lines.push(`--${key}: ${value};`);
115
82
  }
116
83
 
117
- // Use explicit theme export colors if available, otherwise derive from userMessageBg
118
84
  const themeExport = getThemeExportColors(themeName);
119
85
  const userMessageBg = colors.userMessageBg || "#343541";
120
- const derivedColors = deriveExportColors(userMessageBg);
86
+ const derived = deriveExportColors(userMessageBg);
121
87
 
122
- lines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);
123
- lines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);
124
- lines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);
88
+ lines.push(`--body-bg: ${themeExport.pageBg ?? derived.pageBg};`);
89
+ lines.push(`--container-bg: ${themeExport.cardBg ?? derived.cardBg};`);
90
+ lines.push(`--info-bg: ${themeExport.infoBg ?? derived.infoBg};`);
125
91
 
126
- return lines.join("\n ");
92
+ return lines.join(" ");
127
93
  }
128
94
 
129
95
  interface SessionData {
@@ -134,61 +100,28 @@ interface SessionData {
134
100
  tools?: { name: string; description: string }[];
135
101
  }
136
102
 
137
- /**
138
- * Core HTML generation logic shared by both export functions.
139
- */
140
- function generateHtml(sessionData: SessionData, themeName?: string): string {
141
- const templateDir = getExportTemplateDir();
142
-
143
- // Load and minify assets on first use
144
- if (!cachedTemplate) {
145
- cachedTemplate = minifyHtml(readFileSync(join(templateDir, "template.html"), "utf-8"));
146
- }
147
- if (!cachedJs) {
148
- cachedJs = minifyJs(readFileSync(join(templateDir, "template.js"), "utf-8"));
149
- }
150
-
151
- const templateCss = readFileSync(join(templateDir, "template.css"), "utf-8");
152
-
103
+ /** Generate HTML from bundled template with runtime substitutions. */
104
+ async function generateHtml(sessionData: SessionData, themeName?: string): Promise<string> {
153
105
  const themeVars = generateThemeVars(themeName);
154
- const colors = getResolvedThemeColors(themeName);
155
- const exportColors = deriveExportColors(colors.userMessageBg || "#343541");
156
- const bodyBg = exportColors.pageBg;
157
- const containerBg = exportColors.cardBg;
158
- const infoBg = exportColors.infoBg;
159
-
160
- // Base64 encode session data to avoid escaping issues
161
106
  const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString("base64");
107
+ const template = await getTemplate();
162
108
 
163
- // Build and minify the CSS with theme variables injected
164
- const css = minifyCss(
165
- templateCss
166
- .replace("{{THEME_VARS}}", themeVars)
167
- .replace("{{BODY_BG}}", bodyBg)
168
- .replace("{{CONTAINER_BG}}", containerBg)
169
- .replace("{{INFO_BG}}", infoBg),
170
- );
171
-
172
- return cachedTemplate
173
- .replace("{{CSS}}", css)
174
- .replace("{{JS}}", cachedJs)
109
+ return template
110
+ .replace("<theme-vars/>", `<style>:root { ${themeVars} }</style>`)
175
111
  .replace("{{SESSION_DATA}}", sessionDataBase64);
176
112
  }
177
113
 
178
- /**
179
- * Export session to HTML using SessionManager and AgentState.
180
- * Used by TUI's /export command.
181
- */
182
- export function exportSessionToHtml(sm: SessionManager, state?: AgentState, options?: ExportOptions | string): string {
114
+ /** Export session to HTML using SessionManager and AgentState. */
115
+ export async function exportSessionToHtml(
116
+ sm: SessionManager,
117
+ state?: AgentState,
118
+ options?: ExportOptions | string,
119
+ ): Promise<string> {
183
120
  const opts: ExportOptions = typeof options === "string" ? { outputPath: options } : options || {};
184
121
 
185
122
  const sessionFile = sm.getSessionFile();
186
- if (!sessionFile) {
187
- throw new Error("Cannot export in-memory session to HTML");
188
- }
189
- if (!existsSync(sessionFile)) {
190
- throw new Error("Nothing to export yet - start a conversation first");
191
- }
123
+ if (!sessionFile) throw new Error("Cannot export in-memory session to HTML");
124
+ if (!existsSync(sessionFile)) throw new Error("Nothing to export yet - start a conversation first");
192
125
 
193
126
  const sessionData: SessionData = {
194
127
  header: sm.getHeader(),
@@ -198,46 +131,28 @@ export function exportSessionToHtml(sm: SessionManager, state?: AgentState, opti
198
131
  tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),
199
132
  };
200
133
 
201
- const html = generateHtml(sessionData, opts.themeName);
202
-
203
- let outputPath = opts.outputPath;
204
- if (!outputPath) {
205
- const sessionBasename = basename(sessionFile, ".jsonl");
206
- outputPath = `${APP_NAME}-session-${sessionBasename}.html`;
207
- }
134
+ const html = await generateHtml(sessionData, opts.themeName);
135
+ const outputPath = opts.outputPath || `${APP_NAME}-session-${basename(sessionFile, ".jsonl")}.html`;
208
136
 
209
137
  writeFileSync(outputPath, html, "utf8");
210
138
  return outputPath;
211
139
  }
212
140
 
213
- /**
214
- * Export session file to HTML (standalone, without AgentState).
215
- * Used by CLI for exporting arbitrary session files.
216
- */
217
- export function exportFromFile(inputPath: string, options?: ExportOptions | string): string {
141
+ /** Export session file to HTML (standalone). */
142
+ export async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {
218
143
  const opts: ExportOptions = typeof options === "string" ? { outputPath: options } : options || {};
219
144
 
220
- if (!existsSync(inputPath)) {
221
- throw new Error(`File not found: ${inputPath}`);
222
- }
145
+ if (!existsSync(inputPath)) throw new Error(`File not found: ${inputPath}`);
223
146
 
224
147
  const sm = SessionManager.open(inputPath);
225
-
226
148
  const sessionData: SessionData = {
227
149
  header: sm.getHeader(),
228
150
  entries: sm.getEntries(),
229
151
  leafId: sm.getLeafId(),
230
- systemPrompt: undefined,
231
- tools: undefined,
232
152
  };
233
153
 
234
- const html = generateHtml(sessionData, opts.themeName);
235
-
236
- let outputPath = opts.outputPath;
237
- if (!outputPath) {
238
- const inputBasename = basename(inputPath, ".jsonl");
239
- outputPath = `${APP_NAME}-session-${inputBasename}.html`;
240
- }
154
+ const html = await generateHtml(sessionData, opts.themeName);
155
+ const outputPath = opts.outputPath || `${APP_NAME}-session-${basename(inputPath, ".jsonl")}.html`;
241
156
 
242
157
  writeFileSync(outputPath, html, "utf8");
243
158
  return outputPath;
@@ -1,10 +1,3 @@
1
- :root {
2
- {{THEME_VARS}}
3
- --body-bg: {{BODY_BG}};
4
- --container-bg: {{CONTAINER_BG}};
5
- --info-bg: {{INFO_BG}};
6
- }
7
-
8
1
  * { margin: 0; padding: 0; box-sizing: border-box; }
9
2
 
10
3
  :root {
@@ -4,9 +4,8 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Session Export</title>
7
- <style>
8
- {{CSS}}
9
- </style>
7
+ <template-css/>
8
+ <theme-vars/>
10
9
  </head>
11
10
  <body>
12
11
  <button id="hamburger" title="Open sidebar"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" stroke="none"><circle cx="6" cy="6" r="2.5"/><circle cx="6" cy="18" r="2.5"/><circle cx="18" cy="12" r="2.5"/><rect x="5" y="6" width="2" height="12"/><path d="M6 12h10c1 0 2 0 2-2V8"/></svg></button>
@@ -41,6 +40,6 @@
41
40
  <script id="session-data" type="application/json">{{SESSION_DATA}}</script>
42
41
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.4/marked.min.js" integrity="sha512-VmLxPVdDGeR+F0DzUHVqzHwaR4ZSSh1g/7aYXwKT1PAGVxunOEcysta+4H5Utvmpr2xExEPybZ8q+iM9F1tGdw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
43
42
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js" integrity="sha512-D9gUyxqja7hBtkWpPWGt9wfbfaMGVt9gnyCvYa+jojwwPHLCzUm5i8rpk7vD7wNee9bA35eYIjobYPaQuKS1MQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
44
- <script>{{JS}}</script>
43
+ <template-js/>
45
44
  </body>
46
45
  </html>
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Bun macro that inlines HTML template with CSS/JS at compile time.
3
+ * This runs during `bun build` and embeds the result as a string.
4
+ */
5
+ export async function getTemplate(): Promise<string> {
6
+ const dir = new URL(".", import.meta.url).pathname;
7
+
8
+ // Read all files
9
+ const html = await Bun.file(`${dir}template.html`).text();
10
+ const css = await Bun.file(`${dir}template.css`).text();
11
+ const js = await Bun.file(`${dir}template.js`).text();
12
+
13
+ // Minify CSS
14
+ const minifiedCss = css
15
+ .replace(/\/\*[\s\S]*?\*\//g, "")
16
+ .replace(/\s+/g, " ")
17
+ .replace(/\s*([{}:;,])\s*/g, "$1")
18
+ .trim();
19
+
20
+ // Inline everything
21
+ return html
22
+ .replace("<template-css/>", `<style>${minifiedCss}</style>`)
23
+ .replace("<template-js/>", `<script>${js}</script>`);
24
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Auto-read file mentions from user prompts.
3
+ *
4
+ * When users reference files with @path syntax (e.g., "@src/foo.ts"),
5
+ * we automatically inject the file contents as a FileMentionMessage
6
+ * so the agent doesn't need to read them manually.
7
+ */
8
+
9
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
10
+ import type { FileMentionMessage } from "./messages";
11
+ import { createReadTool } from "./tools/read";
12
+
13
+ /** Regex to match @filepath patterns in text */
14
+ const FILE_MENTION_REGEX = /@((?:[^\s@]+\/)*[^\s@]+\.[a-zA-Z0-9]+)/g;
15
+
16
+ /** Extract all @filepath mentions from text */
17
+ export function extractFileMentions(text: string): string[] {
18
+ const matches = [...text.matchAll(FILE_MENTION_REGEX)];
19
+ return [...new Set(matches.map((m) => m[1]))];
20
+ }
21
+
22
+ /**
23
+ * Generate a FileMentionMessage containing the contents of mentioned files.
24
+ * Returns empty array if no files could be read.
25
+ */
26
+ export async function generateFileMentionMessages(filePaths: string[], cwd: string): Promise<AgentMessage[]> {
27
+ if (filePaths.length === 0) return [];
28
+
29
+ const readTool = createReadTool(cwd);
30
+ const files: FileMentionMessage["files"] = [];
31
+
32
+ for (const filePath of filePaths) {
33
+ try {
34
+ const result = await readTool.execute("auto-read", { path: filePath });
35
+ const textContent = result.content.find((c) => c.type === "text");
36
+ if (textContent && textContent.type === "text") {
37
+ const lineCount = textContent.text.split("\n").length;
38
+ files.push({ path: filePath, content: textContent.text, lineCount });
39
+ }
40
+ } catch {
41
+ // File doesn't exist or isn't readable - skip silently
42
+ }
43
+ }
44
+
45
+ if (files.length === 0) return [];
46
+
47
+ const message: FileMentionMessage = {
48
+ role: "fileMention",
49
+ files,
50
+ timestamp: Date.now(),
51
+ };
52
+
53
+ return [message];
54
+ }
@@ -9,8 +9,8 @@ export {
9
9
  type NavigateTreeHandler,
10
10
  type NewSessionHandler,
11
11
  type SendMessageHandler,
12
- } from "./loader.js";
13
- export { execCommand, HookRunner, type HookErrorListener } from "./runner.js";
14
- export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper.js";
15
- export * from "./types.js";
16
- export type { ReadonlySessionManager } from "../session-manager.js";
12
+ } from "./loader";
13
+ export { execCommand, HookRunner, type HookErrorListener } from "./runner";
14
+ export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper";
15
+ export * from "./types";
16
+ export type { ReadonlySessionManager } from "../session-manager";
@@ -6,13 +6,14 @@ import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import * as typebox from "@sinclair/typebox";
9
- import { getAgentDir } from "../../config.js";
10
- import * as piCodingAgent from "../../index.js";
11
- import type { HookMessage } from "../messages.js";
12
- import { getAllPluginHookPaths } from "../plugins/loader.js";
13
- import type { SessionManager } from "../session-manager.js";
14
- import { execCommand } from "./runner.js";
15
- import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types.js";
9
+ import { getAgentDir } from "../../config";
10
+ import * as piCodingAgent from "../../index";
11
+ import { logger } from "../logger";
12
+ import type { HookMessage } from "../messages";
13
+ import { getAllPluginHookPaths } from "../plugins/loader";
14
+ import type { SessionManager } from "../session-manager";
15
+ import { execCommand } from "./runner";
16
+ import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
16
17
 
17
18
  /**
18
19
  * Generic handler function type.
@@ -131,12 +132,8 @@ function createHookAPI(
131
132
  setSendMessageHandler: (handler: SendMessageHandler) => void;
132
133
  setAppendEntryHandler: (handler: AppendEntryHandler) => void;
133
134
  } {
134
- let sendMessageHandler: SendMessageHandler = () => {
135
- // Default no-op until mode sets the handler
136
- };
137
- let appendEntryHandler: AppendEntryHandler = () => {
138
- // Default no-op until mode sets the handler
139
- };
135
+ let sendMessageHandler: SendMessageHandler | null = null;
136
+ let appendEntryHandler: AppendEntryHandler | null = null;
140
137
  const messageRenderers = new Map<string, HookMessageRenderer>();
141
138
  const commands = new Map<string, RegisteredCommand>();
142
139
 
@@ -144,14 +141,21 @@ function createHookAPI(
144
141
  // but the interface has specific overloads for type safety in hooks
145
142
  const api = {
146
143
  on(event: string, handler: HandlerFn): void {
147
- const list = handlers.get(event) ?? [];
148
- list.push(handler);
149
- handlers.set(event, list);
144
+ if (!handlers.has(event)) {
145
+ handlers.set(event, []);
146
+ }
147
+ handlers.get(event)!.push(handler);
150
148
  },
151
149
  sendMessage<T = unknown>(message: HookMessage<T>, triggerTurn?: boolean): void {
150
+ if (!sendMessageHandler) {
151
+ throw new Error("sendMessage handler not initialized");
152
+ }
152
153
  sendMessageHandler(message, triggerTurn);
153
154
  },
154
155
  appendEntry<T = unknown>(customType: string, data?: T): void {
156
+ if (!appendEntryHandler) {
157
+ throw new Error("appendEntry handler not initialized");
158
+ }
155
159
  appendEntryHandler(customType, data);
156
160
  },
157
161
  registerMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void {
@@ -163,6 +167,7 @@ function createHookAPI(
163
167
  exec(command: string, args: string[], options?: ExecOptions) {
164
168
  return execCommand(command, args, options?.cwd ?? cwd, options);
165
169
  },
170
+ logger,
166
171
  typebox,
167
172
  pi: piCodingAgent,
168
173
  } as HookAPI;
@@ -4,9 +4,9 @@
4
4
 
5
5
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
6
  import type { Model } from "@oh-my-pi/pi-ai";
7
- import { theme } from "../../modes/interactive/theme/theme.js";
8
- import type { ModelRegistry } from "../model-registry.js";
9
- import type { SessionManager } from "../session-manager.js";
7
+ import { theme } from "../../modes/interactive/theme/theme";
8
+ import type { ModelRegistry } from "../model-registry";
9
+ import type { SessionManager } from "../session-manager";
10
10
  import type {
11
11
  AppendEntryHandler,
12
12
  BranchHandler,
@@ -14,7 +14,7 @@ import type {
14
14
  NavigateTreeHandler,
15
15
  NewSessionHandler,
16
16
  SendMessageHandler,
17
- } from "./loader.js";
17
+ } from "./loader";
18
18
  import type {
19
19
  BeforeAgentStartEvent,
20
20
  BeforeAgentStartEventResult,
@@ -32,7 +32,7 @@ import type {
32
32
  ToolCallEvent,
33
33
  ToolCallEventResult,
34
34
  ToolResultEventResult,
35
- } from "./types.js";
35
+ } from "./types";
36
36
 
37
37
  /**
38
38
  * Listener for hook errors.
@@ -40,7 +40,7 @@ import type {
40
40
  export type HookErrorListener = (error: HookError) => void;
41
41
 
42
42
  // Re-export execCommand for backward compatibility
43
- export { execCommand } from "../exec.js";
43
+ export { execCommand } from "../exec";
44
44
 
45
45
  /** No-op UI context used when no UI is available */
46
46
  const noOpUIContext: HookUIContext = {
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
6
- import type { HookRunner } from "./runner.js";
7
- import type { ToolCallEventResult, ToolResultEventResult } from "./types.js";
6
+ import type { HookRunner } from "./runner";
7
+ import type { ToolCallEventResult, ToolResultEventResult } from "./types";
8
8
 
9
9
  /**
10
10
  * Wrap a tool with hook callbacks.
@@ -8,30 +8,25 @@
8
8
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
9
  import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
10
10
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
11
- import type { Theme } from "../../modes/interactive/theme/theme.js";
12
- import type { CompactionPreparation, CompactionResult } from "../compaction/index.js";
13
- import type { ExecOptions, ExecResult } from "../exec.js";
14
- import type { HookMessage } from "../messages.js";
15
- import type { ModelRegistry } from "../model-registry.js";
11
+ import type { Theme } from "../../modes/interactive/theme/theme";
12
+ import type { CompactionPreparation, CompactionResult } from "../compaction/index";
13
+ import type { ExecOptions, ExecResult } from "../exec";
14
+ import type { Logger } from "../logger";
15
+ import type { HookMessage } from "../messages";
16
+ import type { ModelRegistry } from "../model-registry";
16
17
  import type {
17
18
  BranchSummaryEntry,
18
19
  CompactionEntry,
19
20
  ReadonlySessionManager,
20
21
  SessionEntry,
21
22
  SessionManager,
22
- } from "../session-manager.js";
23
+ } from "../session-manager";
23
24
 
24
- import type { EditToolDetails } from "../tools/edit.js";
25
- import type {
26
- BashToolDetails,
27
- FindToolDetails,
28
- GrepToolDetails,
29
- LsToolDetails,
30
- ReadToolDetails,
31
- } from "../tools/index.js";
25
+ import type { EditToolDetails } from "../tools/edit";
26
+ import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools/index";
32
27
 
33
28
  // Re-export for backward compatibility
34
- export type { ExecOptions, ExecResult } from "../exec.js";
29
+ export type { ExecOptions, ExecResult } from "../exec";
35
30
 
36
31
  /**
37
32
  * UI context for hooks to request interactive UI from the harness.
@@ -747,6 +742,8 @@ export interface HookAPI {
747
742
  */
748
743
  exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
749
744
 
745
+ /** File logger for error/warning/debug messages */
746
+ logger: Logger;
750
747
  /** Injected @sinclair/typebox module */
751
748
  typebox: typeof import("@sinclair/typebox");
752
749
  /** Injected pi-coding-agent exports */
package/src/core/index.ts CHANGED
@@ -10,9 +10,9 @@ export {
10
10
  type ModelCycleResult,
11
11
  type PromptOptions,
12
12
  type SessionStats,
13
- } from "./agent-session.js";
14
- export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor.js";
15
- export type { CompactionResult } from "./compaction/index.js";
13
+ } from "./agent-session";
14
+ export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor";
15
+ export type { CompactionResult } from "./compaction/index";
16
16
  export {
17
17
  type CustomTool,
18
18
  type CustomToolAPI,
@@ -24,7 +24,7 @@ export {
24
24
  type LoadedCustomTool,
25
25
  loadCustomTools,
26
26
  type RenderResultOptions,
27
- } from "./custom-tools/index.js";
27
+ } from "./custom-tools/index";
28
28
  export {
29
29
  type HookAPI,
30
30
  type HookContext,
@@ -34,7 +34,7 @@ export {
34
34
  HookRunner,
35
35
  type HookUIContext,
36
36
  loadHooks,
37
- } from "./hooks/index.js";
37
+ } from "./hooks/index";
38
38
  export {
39
39
  createMCPManager,
40
40
  discoverAndLoadMCPTools,
@@ -49,4 +49,4 @@ export {
49
49
  type MCPToolDetails,
50
50
  type MCPToolsLoadResult,
51
51
  type MCPTransport,
52
- } from "./mcp/index.js";
52
+ } from "./mcp/index";