@oh-my-pi/pi-coding-agent 12.13.0 → 12.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,45 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [12.14.0] - 2026-02-19
6
+
7
+ ### Added
8
+
9
+ - Support for `docs://` internal URL protocol to access embedded documentation files (e.g., `docs://sdk.md`)
10
+ - Added `generate-docs-index` npm script to automatically index and embed documentation files at build time
11
+ - Support for executable tool files (.ts, .js, .sh, .bash, .py) in custom tools discovery alongside markdown files
12
+ - Display streamed tool intent in working message during agent execution
13
+ - Added `tools.intentTracing` setting to enable intent tracing, which asks the agent to describe the intent of each tool call before executing it
14
+ - Support for file deletion in hashline edit mode via `delete: true` parameter
15
+ - Support for file renaming/moving in hashline edit mode via `rename` parameter
16
+ - Optional content-replace edit variant in hashline mode (enabled via `PI_HL_REPLACETXT=1` environment variable)
17
+ - Support for grepping internal URLs (artifact://) by resolving them to their backing files
18
+
19
+ ### Changed
20
+
21
+ - System prompt now identifies agent as operating inside Oh My Pi harness and instructs reading docs:// URLs for omp/pi topics
22
+ - Tool discovery now accepts executable script extensions (.ts, .js, .sh, .bash, .py) in addition to .json and .md files
23
+ - Updated bash and read tool documentation to reference `docs://` URL support
24
+ - Hashline format separator changed from pipe (`|`) to colon (`:`) for improved readability (e.g., `LINE#ID:content` instead of `LINE#ID|content`)
25
+ - Hashline hash representation changed from 4-character base36 to 2-character hexadecimal for more compact line references
26
+ - Hashline edit API: renamed `delete` parameter to `rm` for consistency with standard file operations
27
+ - Hashline edit API: renamed `rename` parameter to `mv` for consistency with standard file operations
28
+ - Hashline edit API: content-replace operations now require explicit `op: "replaceText"` field to distinguish from other edit types
29
+ - Hashline documentation terminology updated: references to 'anchors' replaced with 'tags' for clearer semantics
30
+ - Intent tracing now uses `_intent` field name in tool schemas
31
+ - Hashline edit API: renamed `set` operation to `target`/`new_content` for clearer semantics
32
+ - Hashline edit API: renamed `set_range` operation to `first`/`last`/`new_content`
33
+ - Hashline edit API: renamed `insert` operation fields from `body` to `inserted_lines` and made `inserted_lines` required non-empty
34
+ - Hashline edit API: flattened `replace` operation to top-level fields (`old_text`, `new_text`, `all`) when enabled
35
+ - Hashline edit validation now provides more specific error messages indicating which variant is expected
36
+
37
+ ### Fixed
38
+
39
+ - Grep tool now properly handles internal URL resolution when searching artifact paths
40
+ - Working message intent updates now fall back to tool execution events when streamed tool arguments omit the intent field
41
+
5
42
  ## [12.13.0] - 2026-02-19
43
+
6
44
  ### Breaking Changes
7
45
 
8
46
  - Removed automatic line relocation when hash references become stale; edits with mismatched line hashes now fail with an error instead of silently relocating to matching lines elsewhere in the file
@@ -171,6 +209,7 @@
171
209
  - Refactored session directory naming to use single-dash format for home-relative paths and double-dash format for absolute paths, with automatic migration of legacy session directories on first access
172
210
 
173
211
  ## [12.8.2] - 2026-02-17
212
+
174
213
  ### Changed
175
214
 
176
215
  - Changed system environment context to use built-in `os` values for distro, kernel, and CPU model instead of native system-info data
@@ -183,11 +222,14 @@
183
222
  ## [12.8.0] - 2026-02-16
184
223
 
185
224
  ### Changed
225
+
186
226
  - Improved `/changelog` performance by displaying only the most recent 3 versions by default, with a `--full` flag for the complete history ([#85](https://github.com/can1357/oh-my-pi/pull/85) by [@tctev](https://github.com/tctev))
187
227
  - Centralized builtin slash command definitions and handlers into a shared registry, replacing the large input-controller if-chain dispatch
188
228
 
189
229
  ## [12.7.0] - 2026-02-16
230
+
190
231
  ### Added
232
+
191
233
  - Added abort signal support to LSP file operations (`ensureFileOpen`, `refreshFile`) for cancellable file synchronization
192
234
  - Added abort signal propagation through LSP request handlers (definition, references, hover, symbols, rename) enabling operation cancellation
193
235
  - Added `shouldBypassAutocompleteOnEscape` callback to custom editor for context-aware escape key handling during active operations
@@ -202,7 +244,9 @@
202
244
  - Added secret obfuscation: env vars matching secret patterns and `secrets.json` entries are replaced with placeholders before sending to LLM providers, deobfuscated in tool call arguments
203
245
  - Added `secrets.enabled` setting to toggle secret obfuscation
204
246
  - Added full regex literal support for `secrets.json` entries (`"/pattern/flags"` syntax with escaped `/` handling, automatic `g` flag enforcement)
247
+
205
248
  ### Changed
249
+
206
250
  - Changed context promotion to trigger on context overflow instead of a configurable threshold, promoting to a larger model before attempting compaction
207
251
  - Changed context promotion behavior to retry immediately on the promoted model without compacting, providing faster recovery from context limits
208
252
  - Changed default grep context lines from 1 before/3 after to 0 before/0 after for more focused search results
@@ -217,20 +261,25 @@
217
261
  - Updated web search provider priority order to include Brave (Exa → Brave → Jina → Perplexity → Anthropic → Gemini → Codex → Z.AI)
218
262
  - Extended recency filter support to Brave provider alongside Perplexity
219
263
  - Changed GitHub issue comment fetching to use paginated API requests with 100 comments per page instead of single request with 50-comment limit
264
+
220
265
  ### Removed
221
266
 
222
267
  - Removed `contextPromotion.thresholdPercent` setting as context promotion now triggers only on overflow
223
268
 
224
269
  ### Fixed
270
+
225
271
  - Fixed LSP operations to properly respect abort signals and throw `ToolAbortError` when cancelled
226
272
  - Fixed workspace diagnostics process cleanup to remove abort event listeners in finally block
227
273
  - Fixed PTY-backed bash execution to enforce timeout completion when detached child processes keep the PTY stream open ([#88](https://github.com/can1357/oh-my-pi/issues/88))
274
+
228
275
  ## [12.5.1] - 2026-02-15
276
+
229
277
  ### Added
230
278
 
231
279
  - Added `repeatToolDescriptions` setting to render full tool descriptions in the system prompt instead of a tool name list
232
280
 
233
281
  ## [12.5.0] - 2026-02-15
282
+
234
283
  ### Breaking Changes
235
284
 
236
285
  - Replaced `theme` setting with `theme.dark` and `theme.light` (auto-migrated)
@@ -281,6 +330,7 @@
281
330
  - Sanitized debug log display to strip control codes, normalize tabs, and trim width
282
331
 
283
332
  ## [12.4.0] - 2026-02-14
333
+
284
334
  ### Changed
285
335
 
286
336
  - Moved `sanitizeText` function from `@oh-my-pi/pi-utils` to `@oh-my-pi/pi-natives` for better code organization
@@ -296,6 +346,7 @@
296
346
  - Fixed Cloudflare returning corrupted bytes when compression is negotiated in web scraper requests
297
347
 
298
348
  ## [12.3.0] - 2026-02-14
349
+
299
350
  ### Added
300
351
 
301
352
  - Added autonomous memory extraction and consolidation system with configurable settings
@@ -341,6 +392,7 @@
341
392
  - Fixed fetch tool to preserve actual response metadata (finalUrl, contentType) instead of defaults when requests fail
342
393
 
343
394
  ||||||| parent of a70a34c8b (fix(coding-agent/debug): Sanitized debug log rendering)
395
+
344
396
  ## [12.1.0] - 2026-02-13
345
397
 
346
398
  ### Added
@@ -392,6 +444,7 @@
392
444
  - Removed @types/jsdom dependency
393
445
 
394
446
  ## [11.14.1] - 2026-02-12
447
+
395
448
  ### Changed
396
449
 
397
450
  - Improved Bun binary detection to check `Bun.env.PI_COMPILED` environment variable
@@ -403,6 +456,7 @@
403
456
  - Fixed Bun update process to properly handle version pinning and report installation mismatches
404
457
 
405
458
  ## [11.14.0] - 2026-02-12
459
+
406
460
  ### Added
407
461
 
408
462
  - Added SwiftLint linter client with JSON reporter support for Swift file linting
@@ -455,6 +509,7 @@
455
509
  - Refactored browser/file opening across multiple modules to use unified `openPath` utility for improved maintainability
456
510
 
457
511
  ## [11.12.0] - 2026-02-11
512
+
458
513
  ### Added
459
514
 
460
515
  - Added `resolveFileDisplayMode` utility to centralize file display mode resolution across tools (read, grep, file mentions)
@@ -543,6 +598,7 @@
543
598
  - Refactored hash line formatting to use async `streamHashLinesFromLines` for better performance
544
599
 
545
600
  ## [11.10.3] - 2026-02-10
601
+
546
602
  ### Added
547
603
 
548
604
  - Exported `./patch/*` subpath for direct access to patch utilities
@@ -568,6 +624,7 @@
568
624
  - Removed AggregateError unwrapping from console.warn in CLI initialization
569
625
 
570
626
  ## [11.10.1] - 2026-02-10
627
+
571
628
  ### Changed
572
629
 
573
630
  - Migrated CLI framework from oclif to lightweight pi-utils CLI runner
@@ -582,6 +639,7 @@
582
639
  - Removed custom oclif help renderer (oclif-help.ts)
583
640
 
584
641
  ## [11.10.0] - 2026-02-10
642
+
585
643
  ### Breaking Changes
586
644
 
587
645
  - Changed `HashlineEdit.src` from string format (e.g., `"5:ab"`, `"5:ab..9:ef"`) to structured `SrcSpec` object with discriminated union types (`{ kind: "single", ref: "..." }`, `{ kind: "range", start: "...", end: "..." }`, etc.)
@@ -732,6 +790,7 @@
732
790
  - Improved bash tool output draining after foreground completion to reduce tail output truncation
733
791
 
734
792
  ## [11.8.0] - 2026-02-10
793
+
735
794
  ### Added
736
795
 
737
796
  - Added `ctx.reload()` method to extension command context to reload extensions, skills, prompts, and themes from disk
@@ -754,6 +813,7 @@
754
813
  - Fixed archive extraction error handling to provide clear error messages on failure
755
814
 
756
815
  ## [11.7.0] - 2026-02-07
816
+
757
817
  ### Changed
758
818
 
759
819
  - Enhanced error messages for failed Python cells to include full combined output context instead of just the error message
@@ -765,6 +825,7 @@
765
825
  - Fixed tab character rendering in Python tool output display to properly format whitespace in cell output and status events
766
826
 
767
827
  ## [11.6.1] - 2026-02-07
828
+
768
829
  ### Fixed
769
830
 
770
831
  - Fixed potential crash when rendering results with undefined details.results
@@ -813,6 +874,7 @@
813
874
  - Removed ability to save screenshots to custom paths or artifacts directory
814
875
 
815
876
  ## [11.4.1] - 2026-02-06
877
+
816
878
  ### Fixed
817
879
 
818
880
  - Fixed tab character display in error messages and bash tool output by properly replacing tabs with spaces
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "12.13.0",
3
+ "version": "12.14.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -78,18 +78,19 @@
78
78
  "scripts": {
79
79
  "check": "tsgo -p tsconfig.json",
80
80
  "format-prompts": "bun scripts/format-prompts.ts",
81
+ "generate-docs-index": "bun scripts/generate-docs-index.ts",
81
82
  "build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
82
83
  "generate-template": "bun scripts/generate-template.ts",
83
84
  "test": "bun test"
84
85
  },
85
86
  "dependencies": {
86
87
  "@mozilla/readability": "0.6.0",
87
- "@oh-my-pi/omp-stats": "12.13.0",
88
- "@oh-my-pi/pi-agent-core": "12.13.0",
89
- "@oh-my-pi/pi-ai": "12.13.0",
90
- "@oh-my-pi/pi-natives": "12.13.0",
91
- "@oh-my-pi/pi-tui": "12.13.0",
92
- "@oh-my-pi/pi-utils": "12.13.0",
88
+ "@oh-my-pi/omp-stats": "12.14.0",
89
+ "@oh-my-pi/pi-agent-core": "12.14.0",
90
+ "@oh-my-pi/pi-ai": "12.14.0",
91
+ "@oh-my-pi/pi-natives": "12.14.0",
92
+ "@oh-my-pi/pi-tui": "12.14.0",
93
+ "@oh-my-pi/pi-utils": "12.14.0",
93
94
  "@sinclair/typebox": "^0.34.48",
94
95
  "@xterm/headless": "^6.0.0",
95
96
  "ajv": "^8.18.0",
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Glob } from "bun";
4
+ import * as path from "node:path";
5
+
6
+ const docsDir = new URL("../../../docs/", import.meta.url).pathname;
7
+ const outputPath = new URL("../src/internal-urls/docs-index.generated.ts", import.meta.url).pathname;
8
+ const importBase = "../../../../docs";
9
+
10
+ function toIdentifier(relativePath: string): string {
11
+ const withoutExt = relativePath.replace(/\.md$/i, "");
12
+ const parts = withoutExt
13
+ .split(/[^a-zA-Z0-9]+/)
14
+ .filter(Boolean)
15
+ .map((part, index) => {
16
+ if (index === 0) {
17
+ return part.toLowerCase();
18
+ }
19
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
20
+ });
21
+
22
+ const base = parts.length > 0 ? parts.join("") : "doc";
23
+ const safeBase = /^[0-9]/.test(base) ? `doc${base}` : base;
24
+ return `${safeBase}Md`;
25
+ }
26
+
27
+ const glob = new Glob("**/*.md");
28
+ const entries: string[] = [];
29
+ for await (const relativePath of glob.scan(docsDir)) {
30
+ entries.push(relativePath.split(path.sep).join("/"));
31
+ }
32
+ entries.sort();
33
+
34
+ const usedIdentifiers = new Set<string>();
35
+ const docs = entries.map((relativePath) => {
36
+ let identifier = toIdentifier(relativePath);
37
+ let suffix = 2;
38
+ while (usedIdentifiers.has(identifier)) {
39
+ identifier = `${toIdentifier(relativePath)}${suffix}`;
40
+ suffix++;
41
+ }
42
+ usedIdentifiers.add(identifier);
43
+ return { relativePath, identifier };
44
+ });
45
+ docs.sort((a, b) => a.identifier.localeCompare(b.identifier) || a.relativePath.localeCompare(b.relativePath));
46
+
47
+ const imports = docs
48
+ .map(({ relativePath, identifier }) => `import ${identifier} from "${importBase}/${relativePath}" with { type: "text" };`)
49
+ .join("\n");
50
+
51
+ const mapEntries = docs.map(({ relativePath, identifier }) => `\t"${relativePath}": ${identifier},`).join("\n");
52
+
53
+ const output = `// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT\n\n${imports}\n\nexport const EMBEDDED_DOCS: Readonly<Record<string, string>> = {\n${mapEntries}\n};\n\nexport const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();\n`;
54
+
55
+ await Bun.write(outputPath, output);
56
+ console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${docs.length} docs)`);
@@ -247,11 +247,11 @@ handlebars.registerHelper("hlineref", (lineNum: unknown, content: unknown): stri
247
247
 
248
248
  /**
249
249
  * {{hlinefull lineNum "content"}} — format a full read-style line with prefix.
250
- * Returns `"lineNum#hash|content"`.
250
+ * Returns `"lineNum#hash:content"`.
251
251
  */
252
252
  handlebars.registerHelper("hlinefull", (lineNum: unknown, content: unknown): string => {
253
253
  const { ref, text } = formatHashlineRef(lineNum, content);
254
- return `${ref}|${text}`;
254
+ return `${ref}:${text}`;
255
255
  });
256
256
 
257
257
  export function renderPromptTemplate(template: string, context: TemplateContext = {}): string {
@@ -287,7 +287,7 @@ export const SETTINGS_SCHEMA = {
287
287
  ui: {
288
288
  tab: "config",
289
289
  label: "Read hash lines",
290
- description: "Include line hashes in read output for hashline edit mode (LINE#ID|content)",
290
+ description: "Include line hashes in read output for hashline edit mode (LINE#ID:content)",
291
291
  },
292
292
  },
293
293
  showHardwareCursor: {
@@ -502,6 +502,15 @@ export const SETTINGS_SCHEMA = {
502
502
  description: "Launch browser in headless mode (disable to show browser UI)",
503
503
  },
504
504
  },
505
+ "tools.intentTracing": {
506
+ type: "boolean",
507
+ default: false,
508
+ ui: {
509
+ tab: "tools",
510
+ label: "Intent tracing",
511
+ description: "Ask the agent to describe the intent of each tool call before executing it",
512
+ },
513
+ },
505
514
 
506
515
  // ─────────────────────────────────────────────────────────────────────────
507
516
  // Task tool settings
@@ -656,7 +656,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
656
656
 
657
657
  fileLoadPromises.push(
658
658
  loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, level, {
659
- extensions: ["json", "md"],
659
+ extensions: ["json", "md", "ts", "js", "sh", "bash", "py"],
660
660
  transform: (name, content, path, source) => {
661
661
  if (name.endsWith(".json")) {
662
662
  const data = parseJSON<{ name?: string; description?: string }>(content);
@@ -668,11 +668,21 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
668
668
  _source: source,
669
669
  };
670
670
  }
671
- const { frontmatter } = parseFrontmatter(content, { source: path });
671
+ if (name.endsWith(".md")) {
672
+ const { frontmatter } = parseFrontmatter(content, { source: path });
673
+ return {
674
+ name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
675
+ path,
676
+ description: frontmatter.description as string | undefined,
677
+ level,
678
+ _source: source,
679
+ };
680
+ }
681
+ // Executable tool files (.ts, .js, .sh, .bash, .py)
682
+ const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
672
683
  return {
673
- name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
684
+ name: toolName,
674
685
  path,
675
- description: frontmatter.description as string | undefined,
676
686
  level,
677
687
  _source: source,
678
688
  };
@@ -503,6 +503,7 @@ export interface ToolExecutionStartEvent {
503
503
  toolCallId: string;
504
504
  toolName: string;
505
505
  args: unknown;
506
+ intent?: string;
506
507
  }
507
508
 
508
509
  /** Fired during tool execution with partial/streaming output */
@@ -0,0 +1,101 @@
1
+ // Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
2
+
3
+ import bashToolRuntimeMd from "../../../../docs/bash-tool-runtime.md" with { type: "text" };
4
+ import blobArtifactArchitectureMd from "../../../../docs/blob-artifact-architecture.md" with { type: "text" };
5
+ import compactionMd from "../../../../docs/compaction.md" with { type: "text" };
6
+ import configUsageMd from "../../../../docs/config-usage.md" with { type: "text" };
7
+ import customToolsMd from "../../../../docs/custom-tools.md" with { type: "text" };
8
+ import environmentVariablesMd from "../../../../docs/environment-variables.md" with { type: "text" };
9
+ import extensionLoadingMd from "../../../../docs/extension-loading.md" with { type: "text" };
10
+ import extensionsMd from "../../../../docs/extensions.md" with { type: "text" };
11
+ import fsScanCacheArchitectureMd from "../../../../docs/fs-scan-cache-architecture.md" with { type: "text" };
12
+ import geminiManifestExtensionsMd from "../../../../docs/gemini-manifest-extensions.md" with { type: "text" };
13
+ import handoffGenerationPipelineMd from "../../../../docs/handoff-generation-pipeline.md" with { type: "text" };
14
+ import hooksMd from "../../../../docs/hooks.md" with { type: "text" };
15
+ import mcpProtocolTransportsMd from "../../../../docs/mcp-protocol-transports.md" with { type: "text" };
16
+ import mcpRuntimeLifecycleMd from "../../../../docs/mcp-runtime-lifecycle.md" with { type: "text" };
17
+ import mcpServerToolAuthoringMd from "../../../../docs/mcp-server-tool-authoring.md" with { type: "text" };
18
+ import modelsMd from "../../../../docs/models.md" with { type: "text" };
19
+ import nativesAddonLoaderRuntimeMd from "../../../../docs/natives-addon-loader-runtime.md" with { type: "text" };
20
+ import nativesArchitectureMd from "../../../../docs/natives-architecture.md" with { type: "text" };
21
+ import nativesBindingContractMd from "../../../../docs/natives-binding-contract.md" with { type: "text" };
22
+ import nativesBuildReleaseDebuggingMd from "../../../../docs/natives-build-release-debugging.md" with { type: "text" };
23
+ import nativesMediaSystemUtilsMd from "../../../../docs/natives-media-system-utils.md" with { type: "text" };
24
+ import nativesRustTaskCancellationMd from "../../../../docs/natives-rust-task-cancellation.md" with { type: "text" };
25
+ import nativesShellPtyProcessMd from "../../../../docs/natives-shell-pty-process.md" with { type: "text" };
26
+ import nativesTextSearchPipelineMd from "../../../../docs/natives-text-search-pipeline.md" with { type: "text" };
27
+ import nonCompactionRetryPolicyMd from "../../../../docs/non-compaction-retry-policy.md" with { type: "text" };
28
+ import notebookToolRuntimeMd from "../../../../docs/notebook-tool-runtime.md" with { type: "text" };
29
+ import pluginManagerInstallerPlumbingMd from "../../../../docs/plugin-manager-installer-plumbing.md" with { type: "text" };
30
+ import portingFromPiMonoMd from "../../../../docs/porting-from-pi-mono.md" with { type: "text" };
31
+ import portingToNativesMd from "../../../../docs/porting-to-natives.md" with { type: "text" };
32
+ import providerStreamingInternalsMd from "../../../../docs/provider-streaming-internals.md" with { type: "text" };
33
+ import pythonReplMd from "../../../../docs/python-repl.md" with { type: "text" };
34
+ import rpcMd from "../../../../docs/rpc.md" with { type: "text" };
35
+ import rulebookMatchingPipelineMd from "../../../../docs/rulebook-matching-pipeline.md" with { type: "text" };
36
+ import sdkMd from "../../../../docs/sdk.md" with { type: "text" };
37
+ import secretsMd from "../../../../docs/secrets.md" with { type: "text" };
38
+ import sessionMd from "../../../../docs/session.md" with { type: "text" };
39
+ import sessionOperationsExportShareForkResumeMd from "../../../../docs/session-operations-export-share-fork-resume.md" with { type: "text" };
40
+ import sessionSwitchingAndRecentListingMd from "../../../../docs/session-switching-and-recent-listing.md" with { type: "text" };
41
+ import sessionTreePlanMd from "../../../../docs/session-tree-plan.md" with { type: "text" };
42
+ import skillsMd from "../../../../docs/skills.md" with { type: "text" };
43
+ import slashCommandInternalsMd from "../../../../docs/slash-command-internals.md" with { type: "text" };
44
+ import taskAgentDiscoveryMd from "../../../../docs/task-agent-discovery.md" with { type: "text" };
45
+ import themeMd from "../../../../docs/theme.md" with { type: "text" };
46
+ import treeMd from "../../../../docs/tree.md" with { type: "text" };
47
+ import ttsrInjectionLifecycleMd from "../../../../docs/ttsr-injection-lifecycle.md" with { type: "text" };
48
+ import tuiMd from "../../../../docs/tui.md" with { type: "text" };
49
+ import tuiRuntimeInternalsMd from "../../../../docs/tui-runtime-internals.md" with { type: "text" };
50
+
51
+ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
52
+ "bash-tool-runtime.md": bashToolRuntimeMd,
53
+ "blob-artifact-architecture.md": blobArtifactArchitectureMd,
54
+ "compaction.md": compactionMd,
55
+ "config-usage.md": configUsageMd,
56
+ "custom-tools.md": customToolsMd,
57
+ "environment-variables.md": environmentVariablesMd,
58
+ "extension-loading.md": extensionLoadingMd,
59
+ "extensions.md": extensionsMd,
60
+ "fs-scan-cache-architecture.md": fsScanCacheArchitectureMd,
61
+ "gemini-manifest-extensions.md": geminiManifestExtensionsMd,
62
+ "handoff-generation-pipeline.md": handoffGenerationPipelineMd,
63
+ "hooks.md": hooksMd,
64
+ "mcp-protocol-transports.md": mcpProtocolTransportsMd,
65
+ "mcp-runtime-lifecycle.md": mcpRuntimeLifecycleMd,
66
+ "mcp-server-tool-authoring.md": mcpServerToolAuthoringMd,
67
+ "models.md": modelsMd,
68
+ "natives-addon-loader-runtime.md": nativesAddonLoaderRuntimeMd,
69
+ "natives-architecture.md": nativesArchitectureMd,
70
+ "natives-binding-contract.md": nativesBindingContractMd,
71
+ "natives-build-release-debugging.md": nativesBuildReleaseDebuggingMd,
72
+ "natives-media-system-utils.md": nativesMediaSystemUtilsMd,
73
+ "natives-rust-task-cancellation.md": nativesRustTaskCancellationMd,
74
+ "natives-shell-pty-process.md": nativesShellPtyProcessMd,
75
+ "natives-text-search-pipeline.md": nativesTextSearchPipelineMd,
76
+ "non-compaction-retry-policy.md": nonCompactionRetryPolicyMd,
77
+ "notebook-tool-runtime.md": notebookToolRuntimeMd,
78
+ "plugin-manager-installer-plumbing.md": pluginManagerInstallerPlumbingMd,
79
+ "porting-from-pi-mono.md": portingFromPiMonoMd,
80
+ "porting-to-natives.md": portingToNativesMd,
81
+ "provider-streaming-internals.md": providerStreamingInternalsMd,
82
+ "python-repl.md": pythonReplMd,
83
+ "rpc.md": rpcMd,
84
+ "rulebook-matching-pipeline.md": rulebookMatchingPipelineMd,
85
+ "sdk.md": sdkMd,
86
+ "secrets.md": secretsMd,
87
+ "session.md": sessionMd,
88
+ "session-operations-export-share-fork-resume.md": sessionOperationsExportShareForkResumeMd,
89
+ "session-switching-and-recent-listing.md": sessionSwitchingAndRecentListingMd,
90
+ "session-tree-plan.md": sessionTreePlanMd,
91
+ "skills.md": skillsMd,
92
+ "slash-command-internals.md": slashCommandInternalsMd,
93
+ "task-agent-discovery.md": taskAgentDiscoveryMd,
94
+ "theme.md": themeMd,
95
+ "tree.md": treeMd,
96
+ "ttsr-injection-lifecycle.md": ttsrInjectionLifecycleMd,
97
+ "tui.md": tuiMd,
98
+ "tui-runtime-internals.md": tuiRuntimeInternalsMd,
99
+ };
100
+
101
+ export const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Protocol handler for docs:// URLs.
3
+ *
4
+ * Serves statically embedded documentation files bundled at build time.
5
+ *
6
+ * URL forms:
7
+ * - docs:// - Lists all available documentation files
8
+ * - docs://<file>.md - Reads a specific documentation file
9
+ */
10
+ import * as path from "node:path";
11
+ import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
12
+ import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
13
+
14
+ /**
15
+ * Handler for docs:// URLs.
16
+ *
17
+ * Resolves documentation file names to their content, or lists available docs.
18
+ */
19
+ export class DocsProtocolHandler implements ProtocolHandler {
20
+ readonly scheme = "docs";
21
+
22
+ async resolve(url: InternalUrl): Promise<InternalResource> {
23
+ // Extract filename from host + path
24
+ const host = url.rawHost || url.hostname;
25
+ const pathname = url.rawPathname ?? url.pathname;
26
+ const filename = host ? (pathname && pathname !== "/" ? host + pathname : host) : "";
27
+
28
+ if (!filename) {
29
+ return this.#listDocs(url);
30
+ }
31
+
32
+ return this.#readDoc(filename, url);
33
+ }
34
+
35
+ async #listDocs(url: InternalUrl): Promise<InternalResource> {
36
+ if (EMBEDDED_DOC_FILENAMES.length === 0) {
37
+ throw new Error("No documentation files found");
38
+ }
39
+
40
+ const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](docs://${f})`).join("\n");
41
+ const content = `# Documentation\n\n${EMBEDDED_DOC_FILENAMES.length} files available:\n\n${listing}\n`;
42
+
43
+ return {
44
+ url: url.href,
45
+ content,
46
+ contentType: "text/markdown",
47
+ size: Buffer.byteLength(content, "utf-8"),
48
+ sourcePath: "docs://",
49
+ };
50
+ }
51
+
52
+ async #readDoc(filename: string, url: InternalUrl): Promise<InternalResource> {
53
+ // Validate: no traversal, no absolute paths
54
+ if (path.isAbsolute(filename)) {
55
+ throw new Error("Absolute paths are not allowed in docs:// URLs");
56
+ }
57
+
58
+ const normalized = path.posix.normalize(filename.replaceAll("\\", "/"));
59
+ if (normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
60
+ throw new Error("Path traversal (..) is not allowed in docs:// URLs");
61
+ }
62
+
63
+ const content = EMBEDDED_DOCS[normalized];
64
+ if (content === undefined) {
65
+ const lookup = normalized.replace(/\.md$/, "");
66
+ const suggestions = EMBEDDED_DOC_FILENAMES.filter(
67
+ f => f.includes(lookup) || lookup.includes(f.replace(/\.md$/, "")),
68
+ ).slice(0, 5);
69
+ const suffix =
70
+ suggestions.length > 0
71
+ ? `\nDid you mean: ${suggestions.join(", ")}`
72
+ : "\nUse docs:// to list available files.";
73
+ throw new Error(`Documentation file not found: ${filename}${suffix}`);
74
+ }
75
+
76
+ return {
77
+ url: url.href,
78
+ content,
79
+ contentType: "text/markdown",
80
+ size: Buffer.byteLength(content, "utf-8"),
81
+ sourcePath: `docs://${normalized}`,
82
+ };
83
+ }
84
+ }
@@ -22,6 +22,7 @@
22
22
 
23
23
  export { AgentProtocolHandler, type AgentProtocolOptions } from "./agent-protocol";
24
24
  export { ArtifactProtocolHandler, type ArtifactProtocolOptions } from "./artifact-protocol";
25
+ export { DocsProtocolHandler } from "./docs-protocol";
25
26
  export { applyQuery, parseQuery, pathToQuery } from "./json-query";
26
27
  export { MemoryProtocolHandler, type MemoryProtocolOptions, resolveMemoryUrlToPath } from "./memory-protocol";
27
28
  export { PlanProtocolHandler, type PlanProtocolOptions, resolvePlanUrlToPath } from "./plan-protocol";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://).
2
+ * Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://, docs://).
3
3
  */
4
4
  import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
5
5
 
@@ -1,3 +1,4 @@
1
+ import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
1
2
  import { Loader, TERMINAL, Text } from "@oh-my-pi/pi-tui";
2
3
  import { settings } from "../../config/settings";
3
4
  import { AssistantMessageComponent } from "../../modes/components/assistant-message";
@@ -14,6 +15,7 @@ export class EventController {
14
15
  #lastReadGroup: ReadToolGroupComponent | undefined = undefined;
15
16
  #lastThinkingCount = 0;
16
17
  #renderedCustomMessages = new Set<string>();
18
+ #lastIntent: string | undefined = undefined;
17
19
 
18
20
  constructor(private ctx: InteractiveModeContext) {}
19
21
 
@@ -32,6 +34,13 @@ export class EventController {
32
34
  return this.#lastReadGroup;
33
35
  }
34
36
 
37
+ #updateWorkingMessageFromIntent(intent: string | undefined): void {
38
+ const trimmed = intent?.trim();
39
+ if (!trimmed || trimmed === this.#lastIntent) return;
40
+ this.#lastIntent = trimmed;
41
+ this.ctx.setWorkingMessage(`${trimmed} (esc to interrupt)`);
42
+ }
43
+
35
44
  subscribeToAgent(): void {
36
45
  this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
37
46
  await this.handleEvent(event);
@@ -48,6 +57,7 @@ export class EventController {
48
57
 
49
58
  switch (event.type) {
50
59
  case "agent_start":
60
+ this.#lastIntent = undefined;
51
61
  if (this.ctx.retryEscapeHandler) {
52
62
  this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
53
63
  this.ctx.retryEscapeHandler = undefined;
@@ -155,6 +165,15 @@ export class EventController {
155
165
  }
156
166
  }
157
167
  }
168
+
169
+ // Update working message with intent from streamed tool arguments
170
+ for (const content of this.ctx.streamingMessage.content) {
171
+ if (content.type !== "toolCall") continue;
172
+ const args = content.arguments;
173
+ if (!args || typeof args !== "object" || !(INTENT_FIELD in args)) continue;
174
+ this.#updateWorkingMessageFromIntent(args[INTENT_FIELD] as string | undefined);
175
+ }
176
+
158
177
  this.ctx.ui.requestRender();
159
178
  }
160
179
  break;
@@ -196,6 +215,7 @@ export class EventController {
196
215
  break;
197
216
 
198
217
  case "tool_execution_start": {
218
+ this.#updateWorkingMessageFromIntent(event.intent);
199
219
  if (!this.ctx.pendingTools.has(event.toolCallId)) {
200
220
  if (event.toolName === "read") {
201
221
  const group = this.#getReadGroup();
package/src/patch/diff.ts CHANGED
@@ -8,8 +8,8 @@ import * as Diff from "diff";
8
8
  import { resolveToCwd } from "../tools/path-utils";
9
9
  import { previewPatch } from "./applicator";
10
10
  import { DEFAULT_FUZZY_THRESHOLD, findMatch } from "./fuzzy";
11
+ import type { HashlineEdit } from "./hashline";
11
12
  import { applyHashlineEdits } from "./hashline";
12
- import type { HashlineEdit } from "./index";
13
13
  import { adjustIndentation, normalizeToLF, stripBom } from "./normalize";
14
14
  import type { DiffError, DiffResult, PatchInput } from "./types";
15
15
  import { EditMatchError } from "./types";