@oh-my-pi/pi-coding-agent 17.2.14 → 17.2.15

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 (232) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/{CHANGELOG-za420td8.md → CHANGELOG-fk29zh06.md} +24 -0
  3. package/dist/cli.js +4696 -5008
  4. package/dist/types/cleanse/agent.d.ts +3 -0
  5. package/dist/types/cleanse/checkers.d.ts +21 -1
  6. package/dist/types/cleanse/index.d.ts +4 -0
  7. package/dist/types/cleanse/parsers.d.ts +3 -1
  8. package/dist/types/cli/args.d.ts +1 -0
  9. package/dist/types/cli/cleanse-picker.d.ts +17 -0
  10. package/dist/types/cli/command-help.d.ts +3 -0
  11. package/dist/types/cli/progress-reporter.d.ts +19 -0
  12. package/dist/types/commands/cleanse.d.ts +11 -0
  13. package/dist/types/commands/compress.d.ts +37 -0
  14. package/dist/types/commands/launch-help.d.ts +3 -0
  15. package/dist/types/commands/launch.d.ts +3 -0
  16. package/dist/types/compress/index.d.ts +27 -0
  17. package/dist/types/compress/protocol.d.ts +49 -0
  18. package/dist/types/compress/session.d.ts +15 -0
  19. package/dist/types/compress/types.d.ts +54 -0
  20. package/dist/types/config/settings-schema.d.ts +1 -1
  21. package/dist/types/internal-urls/local-protocol.d.ts +8 -0
  22. package/dist/types/mcp/transports/header-policy.d.ts +8 -0
  23. package/dist/types/mcp/transports/http.d.ts +2 -0
  24. package/dist/types/mcp/types.d.ts +19 -0
  25. package/dist/types/modes/components/late-diagnostics-message.d.ts +2 -0
  26. package/dist/types/modes/components/todo-reminder.d.ts +2 -0
  27. package/dist/types/modes/components/tool-activity.d.ts +17 -0
  28. package/dist/types/modes/components/transcript-container.d.ts +2 -0
  29. package/dist/types/modes/components/ttsr-notification.d.ts +2 -0
  30. package/dist/types/modes/interactive-mode.d.ts +3 -1
  31. package/dist/types/modes/types.d.ts +3 -1
  32. package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -1
  33. package/dist/types/modes/utils/ui-helpers.d.ts +3 -1
  34. package/dist/types/session/session-tools.d.ts +2 -0
  35. package/dist/types/tools/think.d.ts +6 -3
  36. package/dist/types/utils/zip.d.ts +19 -9
  37. package/package.json +13 -13
  38. package/src/cleanse/agent.ts +67 -2
  39. package/src/cleanse/checkers.ts +252 -21
  40. package/src/cleanse/index.ts +90 -36
  41. package/src/cleanse/parsers.ts +243 -20
  42. package/src/cleanse/prompts/discovery.md +74 -0
  43. package/src/cli/args.ts +3 -0
  44. package/src/cli/cleanse-picker.ts +86 -0
  45. package/src/cli/command-help.ts +4 -0
  46. package/src/cli/flag-tables.ts +1 -0
  47. package/src/cli/gallery-fixtures/agentic.ts +3 -3
  48. package/src/{cleanse/progress.ts → cli/progress-reporter.ts} +12 -7
  49. package/src/cli-commands.ts +5 -0
  50. package/src/commands/cleanse.ts +19 -4
  51. package/src/commands/compress.ts +45 -0
  52. package/src/commands/launch-help.ts +3 -0
  53. package/src/commit/agentic/prompts/analyze-file.md +6 -6
  54. package/src/commit/agentic/prompts/session-user.md +4 -4
  55. package/src/compress/index.ts +318 -0
  56. package/src/compress/prompts/request.md +11 -0
  57. package/src/compress/prompts/review.md +17 -0
  58. package/src/compress/prompts/system.md +81 -0
  59. package/src/compress/protocol.ts +210 -0
  60. package/src/compress/session.ts +72 -0
  61. package/src/compress/types.ts +59 -0
  62. package/src/config/settings-schema.ts +1 -1
  63. package/src/discovery/builtin-rules/go-add-cleanup.md +8 -8
  64. package/src/discovery/builtin-rules/go-exp-promoted.md +8 -8
  65. package/src/discovery/builtin-rules/go-ioutil.md +12 -12
  66. package/src/discovery/builtin-rules/go-new-expr.md +8 -8
  67. package/src/discovery/builtin-rules/go-range-int.md +5 -5
  68. package/src/discovery/builtin-rules/rs-box-leak.md +7 -7
  69. package/src/discovery/builtin-rules/rs-future-prelude.md +4 -4
  70. package/src/discovery/builtin-rules/rs-parking-lot.md +6 -6
  71. package/src/discovery/builtin-rules/ts-no-any.md +5 -5
  72. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +8 -8
  73. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +13 -14
  74. package/src/discovery/builtin-rules/ts-no-local-is-record.md +7 -7
  75. package/src/discovery/builtin-rules/ts-no-test-timers.md +3 -9
  76. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +7 -7
  77. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +2 -2
  78. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +3 -9
  79. package/src/discovery/builtin-rules/ts-set-map.md +2 -4
  80. package/src/internal-urls/local-protocol.ts +42 -0
  81. package/src/live/prompts/live-instructions.md +10 -10
  82. package/src/main.ts +4 -0
  83. package/src/mcp/client.ts +7 -3
  84. package/src/mcp/transports/header-policy.ts +28 -0
  85. package/src/mcp/transports/http.ts +200 -35
  86. package/src/mcp/types.ts +21 -0
  87. package/src/modes/components/chat-transcript-builder.ts +17 -5
  88. package/src/modes/components/late-diagnostics-message.ts +12 -1
  89. package/src/modes/components/todo-reminder.ts +12 -0
  90. package/src/modes/components/tool-activity.ts +45 -0
  91. package/src/modes/components/transcript-container.ts +16 -0
  92. package/src/modes/components/ttsr-notification.ts +12 -0
  93. package/src/modes/controllers/event-controller.ts +3 -6
  94. package/src/modes/controllers/input-controller.ts +6 -6
  95. package/src/modes/controllers/selector-controller.ts +3 -6
  96. package/src/modes/interactive-mode.ts +5 -40
  97. package/src/modes/print-mode.ts +13 -54
  98. package/src/modes/types.ts +1 -1
  99. package/src/modes/utils/transcript-render-helpers.ts +3 -2
  100. package/src/modes/utils/ui-helpers.ts +17 -7
  101. package/src/prompts/advisor/active-repo-watchdog.md +3 -4
  102. package/src/prompts/advisor/advise-tool.md +3 -3
  103. package/src/prompts/advisor/context-files.md +1 -1
  104. package/src/prompts/advisor/system.md +45 -65
  105. package/src/prompts/agents/designer.md +44 -44
  106. package/src/prompts/agents/init.md +18 -18
  107. package/src/prompts/agents/librarian.md +31 -31
  108. package/src/prompts/agents/reviewer.md +34 -39
  109. package/src/prompts/agents/security-reviewer.md +3 -5
  110. package/src/prompts/agents/task.md +11 -12
  111. package/src/prompts/bench.md +2 -5
  112. package/src/prompts/ci-green-request.md +13 -15
  113. package/src/prompts/dry-balance-bench.md +5 -5
  114. package/src/prompts/goals/goal-budget-limit.md +4 -5
  115. package/src/prompts/goals/goal-continuation.md +11 -11
  116. package/src/prompts/goals/goal-mode-active.md +7 -7
  117. package/src/prompts/goals/goal-todo-context.md +2 -2
  118. package/src/prompts/goals/guided-goal-interview.md +18 -24
  119. package/src/prompts/memories/read-path.md +9 -9
  120. package/src/prompts/memories/stage_one_system.md +9 -11
  121. package/src/prompts/review-custom-request.md +8 -11
  122. package/src/prompts/review-headless-request.md +3 -10
  123. package/src/prompts/security/scan-coordinator.md +6 -5
  124. package/src/prompts/security/validate-request.md +4 -7
  125. package/src/prompts/skills/user-invocation.md +2 -2
  126. package/src/prompts/steering/parent-irc.md +1 -1
  127. package/src/prompts/steering/user-interjection.md +1 -3
  128. package/src/prompts/system/active-repo-context.md +4 -2
  129. package/src/prompts/system/agent-creation-architect.md +22 -37
  130. package/src/prompts/system/agent-creation-user.md +3 -3
  131. package/src/prompts/system/auto-continue.md +1 -1
  132. package/src/prompts/system/auto-thinking-difficulty-local.md +5 -7
  133. package/src/prompts/system/auto-thinking-difficulty.md +8 -10
  134. package/src/prompts/system/autolearn-guidance-learn.md +2 -1
  135. package/src/prompts/system/autolearn-guidance.md +5 -4
  136. package/src/prompts/system/autolearn-nudge-autocontinue.md +3 -3
  137. package/src/prompts/system/background-tan-dispatch.md +3 -3
  138. package/src/prompts/system/btw-user.md +2 -2
  139. package/src/prompts/system/commit-message-system.md +8 -6
  140. package/src/prompts/system/eager-task.md +3 -3
  141. package/src/prompts/system/empty-stop-retry.md +1 -1
  142. package/src/prompts/system/gemini-tool-call-reminder.md +5 -5
  143. package/src/prompts/system/interrupted-thinking.md +3 -3
  144. package/src/prompts/system/irc-autoreply.md +1 -1
  145. package/src/prompts/system/irc-incoming.md +3 -3
  146. package/src/prompts/system/manual-continue.md +3 -3
  147. package/src/prompts/system/mcp-xdev-guidance.md +2 -2
  148. package/src/prompts/system/memory-consolidation-system.md +2 -2
  149. package/src/prompts/system/mid-run-todo-nudge.md +1 -1
  150. package/src/prompts/system/orchestrate-notice.md +25 -25
  151. package/src/prompts/system/personalities/default.md +9 -9
  152. package/src/prompts/system/personalities/friendly.md +11 -11
  153. package/src/prompts/system/personalities/pragmatic.md +8 -8
  154. package/src/prompts/system/plan-mode-active.md +61 -64
  155. package/src/prompts/system/plan-mode-approved.md +10 -11
  156. package/src/prompts/system/plan-mode-compact-instructions.md +11 -11
  157. package/src/prompts/system/plan-mode-reference.md +5 -5
  158. package/src/prompts/system/plan-yolo-handoff.md +2 -2
  159. package/src/prompts/system/prewalk-checklist.md +5 -5
  160. package/src/prompts/system/prewalk-continue.md +1 -1
  161. package/src/prompts/system/prewalk-plan.md +8 -9
  162. package/src/prompts/system/project-prompt.md +11 -12
  163. package/src/prompts/system/recap-user.md +1 -1
  164. package/src/prompts/system/resolve-device-reminder.md +1 -1
  165. package/src/prompts/system/rewind-report.md +3 -3
  166. package/src/prompts/system/side-channel-no-tools.md +3 -1
  167. package/src/prompts/system/snapcompact-context-stub.md +1 -1
  168. package/src/prompts/system/snapcompact-system-frames-note.md +1 -1
  169. package/src/prompts/system/snapcompact-system-stub.md +1 -1
  170. package/src/prompts/system/snapcompact-toolresult-note.md +1 -1
  171. package/src/prompts/system/speech-rewrite.md +11 -13
  172. package/src/prompts/system/subagent-async-pending.md +5 -5
  173. package/src/prompts/system/subagent-system-prompt.md +5 -15
  174. package/src/prompts/system/subagent-user-prompt.md +1 -1
  175. package/src/prompts/system/subagent-yield-reminder.md +12 -12
  176. package/src/prompts/system/system-prompt.md +108 -143
  177. package/src/prompts/system/tan-context-switch.md +8 -14
  178. package/src/prompts/system/task-label.md +3 -3
  179. package/src/prompts/system/thinking-loop-redirect.md +6 -6
  180. package/src/prompts/system/title-marker-instruction.md +2 -1
  181. package/src/prompts/system/title-system.md +3 -5
  182. package/src/prompts/system/ttsr-interrupt.md +3 -3
  183. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  184. package/src/prompts/system/ultrathink-notice.md +1 -1
  185. package/src/prompts/system/unexpected-stop-classifier.md +3 -3
  186. package/src/prompts/system/vibe-mode-active.md +15 -15
  187. package/src/prompts/system/web-search.md +16 -16
  188. package/src/prompts/system/workflow-notice.md +35 -33
  189. package/src/prompts/system/xdev-mount-notice.md +4 -4
  190. package/src/prompts/tools/apply-patch.md +22 -23
  191. package/src/prompts/tools/approve.md +5 -0
  192. package/src/prompts/tools/ask.md +10 -10
  193. package/src/prompts/tools/checkpoint.md +7 -7
  194. package/src/prompts/tools/computer.md +16 -16
  195. package/src/prompts/tools/github.md +12 -12
  196. package/src/prompts/tools/goal.md +8 -9
  197. package/src/prompts/tools/grep.md +5 -5
  198. package/src/prompts/tools/image-attachment-describe-system.md +6 -6
  199. package/src/prompts/tools/image-attachment-describe.md +3 -8
  200. package/src/prompts/tools/image-gen.md +4 -4
  201. package/src/prompts/tools/inspect-image-system.md +10 -10
  202. package/src/prompts/tools/inspect-image.md +10 -13
  203. package/src/prompts/tools/learn.md +4 -4
  204. package/src/prompts/tools/manage-skill.md +9 -6
  205. package/src/prompts/tools/memory-edit.md +8 -8
  206. package/src/prompts/tools/recall.md +4 -4
  207. package/src/prompts/tools/reflect.md +2 -2
  208. package/src/prompts/tools/replace.md +13 -11
  209. package/src/prompts/tools/retain.md +3 -4
  210. package/src/prompts/tools/rewind.md +7 -8
  211. package/src/prompts/tools/rewrite.md +12 -0
  212. package/src/prompts/tools/security-publish.md +5 -1
  213. package/src/prompts/tools/security-scan.md +10 -1
  214. package/src/prompts/tools/task-async-contract.md +7 -1
  215. package/src/prompts/tools/todo.md +27 -25
  216. package/src/prompts/tools/vibe-kill.md +2 -2
  217. package/src/prompts/tools/vibe-list.md +2 -2
  218. package/src/prompts/tools/vibe-send.md +6 -7
  219. package/src/prompts/tools/vibe-spawn.md +8 -6
  220. package/src/prompts/tools/web-search.md +5 -5
  221. package/src/sdk.ts +4 -1
  222. package/src/session/agent-session.ts +7 -4
  223. package/src/session/session-handoff.ts +21 -0
  224. package/src/session/session-tools.ts +12 -0
  225. package/src/tools/index.ts +6 -4
  226. package/src/tools/read-format.ts +8 -3
  227. package/src/tools/read.ts +18 -7
  228. package/src/tools/think.ts +28 -6
  229. package/src/tools/write.ts +1 -1
  230. package/src/utils/file-mentions.ts +8 -2
  231. package/src/utils/zip.ts +858 -94
  232. package/dist/types/cleanse/progress.d.ts +0 -14
@@ -62,7 +62,7 @@ import { wrapToolWithMetaNotice } from "./output-meta";
62
62
  import { ReadTool } from "./read";
63
63
  import type { PlanProposalHandler } from "./resolve";
64
64
  import { SecurityScanTool } from "./security-scan";
65
- import { ThinkTool } from "./think";
65
+ import { supportsExternalThinking, ThinkTool } from "./think";
66
66
  import { type TodoPhase, TodoTool } from "./todo";
67
67
  import { WriteTool } from "./write";
68
68
  import { isMountableUnderXdev, type XdevState } from "./xdev";
@@ -467,6 +467,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
467
467
  : undefined;
468
468
  const goalEnabled = session.settings.get("goal.enabled");
469
469
  const goalModeActive = !restrictToolNames && goalEnabled && session.getGoalModeState?.()?.enabled === true;
470
+ const externalThinkingActive =
471
+ session.settings.get("externalThinking") && supportsExternalThinking(session.getActiveModel?.());
470
472
  if (goalModeActive && requestedTools && !requestedTools.includes("goal")) {
471
473
  requestedTools.push("goal");
472
474
  }
@@ -565,7 +567,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
565
567
  if (session.settings.get("memory.backend") === "mnemopi" && !requestedTools.includes("memory_edit")) {
566
568
  requestedTools.push("memory_edit");
567
569
  }
568
- if (session.settings.get("externalThinking") && !requestedTools.includes("think")) {
570
+ if (externalThinkingActive && !requestedTools.includes("think")) {
569
571
  requestedTools.push("think");
570
572
  }
571
573
  // Auto-learn tools are gated by `autolearn.enabled` but, like the memory
@@ -610,7 +612,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
610
612
  if (name === "inspect_image") return isInspectImageToolActive(session);
611
613
  if (name === "web_search") return session.settings.get("web_search.enabled");
612
614
  if (name === "security_scan") return session.settings.get("security.enabled");
613
- if (name === "think") return session.settings.get("externalThinking");
615
+ if (name === "think") return externalThinkingActive;
614
616
  if (name === "ask") return session.settings.get("ask.enabled");
615
617
  if (name === "browser") return session.settings.get("browser.enabled");
616
618
  if (name === "computer") return session.settings.get("computer.enabled");
@@ -657,7 +659,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
657
659
  ...Object.entries(BUILTIN_TOOLS)
658
660
  .filter(([name]) => isToolAllowed(name))
659
661
  .map(([name, factory]) => [name, factory] as const),
660
- ...(session.settings.get("externalThinking") ? ([["think", HIDDEN_TOOLS.think]] as const) : []),
662
+ ...(externalThinkingActive ? ([["think", HIDDEN_TOOLS.think]] as const) : []),
661
663
  ...(includeYield ? ([["yield", HIDDEN_TOOLS.yield]] as const) : []),
662
664
  ...(goalModeActive ? ([["goal", HIDDEN_TOOLS.goal]] as const) : []),
663
665
  ];
@@ -1,5 +1,10 @@
1
1
  import * as path from "node:path";
2
- import { formatHashlineHeader, formatNumberedLine, formatNumberedLines } from "@oh-my-pi/hashline";
2
+ import {
3
+ formatHashlineHeader,
4
+ formatNumberedLine,
5
+ formatNumberedLines,
6
+ splitAddressableFileLines,
7
+ } from "@oh-my-pi/hashline";
3
8
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
4
9
  import { canonicalSnapshotKey, getFileSnapshotStore, recordSeenLines } from "../edit/file-snapshot-store";
5
10
  import { normalizeToLF } from "../edit/normalize";
@@ -290,7 +295,7 @@ export function buildInMemoryTextResult(
290
295
  ): AgentToolResult<ReadToolDetails> {
291
296
  const displayMode = resolveFileDisplayMode(session, { raw: options.raw, immutable: options.immutable });
292
297
  const details = options.details ?? {};
293
- const allLines = text.split("\n");
298
+ const allLines = options.raw === true ? text.split("\n") : splitAddressableFileLines(text);
294
299
  const totalLines = allLines.length;
295
300
  details.totalLines = totalLines;
296
301
  // User-requested 0-indexed range start. Lines BEFORE this are leading
@@ -487,7 +492,7 @@ export function buildInMemoryMultiRangeResult(
487
492
  ): AgentToolResult<ReadToolDetails> {
488
493
  const displayMode = resolveFileDisplayMode(session, { raw: options.raw, immutable: options.immutable });
489
494
  const details = options.details ?? {};
490
- const allLines = text.split("\n");
495
+ const allLines = options.raw === true ? text.split("\n") : splitAddressableFileLines(text);
491
496
  const totalLines = allLines.length;
492
497
  details.totalLines = totalLines;
493
498
  const shouldAddHashLines = displayMode.hashLines;
package/src/tools/read.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+ import { splitAddressableFileLines } from "@oh-my-pi/hashline";
3
4
  import { type } from "@oh-my-pi/omptype";
4
5
  import type {
5
6
  AgentTool,
@@ -119,12 +120,17 @@ const MAX_ARTIFACT_RAW_INLINE_BYTES = DEFAULT_MAX_BYTES;
119
120
  async function readBracketContextFullLines(absolutePath: string, fileSize: number): Promise<string[] | undefined> {
120
121
  if (fileSize > SNAPSHOT_MAX_BYTES) return undefined;
121
122
  try {
122
- return normalizeToLF(await Bun.file(absolutePath).text()).split("\n");
123
+ return splitAddressableFileLines(normalizeToLF(await Bun.file(absolutePath).text()));
123
124
  } catch {
124
125
  return undefined;
125
126
  }
126
127
  }
127
128
 
129
+ interface StreamFileLinesOptions {
130
+ includeTerminalNewline?: boolean;
131
+ stopScanAfterCollect?: boolean;
132
+ }
133
+
128
134
  async function streamLinesFromFile(
129
135
  filePath: string,
130
136
  startLine: number,
@@ -132,7 +138,7 @@ async function streamLinesFromFile(
132
138
  maxBytes: number,
133
139
  selectedLineLimit: number | null,
134
140
  signal?: AbortSignal,
135
- stopScanAfterCollect = false,
141
+ options: StreamFileLinesOptions = {},
136
142
  ): Promise<{
137
143
  lines: string[];
138
144
  totalFileLines: number;
@@ -141,9 +147,12 @@ async function streamLinesFromFile(
141
147
  firstLinePreview?: { text: string; bytes: number };
142
148
  firstLineByteLength?: number;
143
149
  selectedBytesTotal: number;
150
+ /** Whether the fully scanned source ended in a newline. */
151
+ hasTrailingNewline: boolean;
144
152
  /** False when `stopScanAfterCollect` cut the scan short — `totalFileLines` is then a lower bound. */
145
153
  reachedEof: boolean;
146
154
  }> {
155
+ const { includeTerminalNewline = false, stopScanAfterCollect = false } = options;
147
156
  const bufferChunk = Buffer.allocUnsafe(READ_CHUNK_SIZE);
148
157
  const collectedLines: string[] = [];
149
158
  let lineIndex = 0;
@@ -311,7 +320,7 @@ async function streamLinesFromFile(
311
320
  }
312
321
  }
313
322
 
314
- if (reachedEof && (endedWithNewline || currentLineLength > 0 || !sawAnyByte)) {
323
+ if (reachedEof && (currentLineLength > 0 || !sawAnyByte || (endedWithNewline && includeTerminalNewline))) {
315
324
  finalizeLine();
316
325
  }
317
326
 
@@ -330,6 +339,7 @@ async function streamLinesFromFile(
330
339
  firstLineByteLength,
331
340
  selectedBytesTotal,
332
341
  reachedEof,
342
+ hasTrailingNewline: reachedEof && endedWithNewline,
333
343
  };
334
344
  }
335
345
 
@@ -692,7 +702,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
692
702
  maxBytesForRead,
693
703
  maxLines,
694
704
  signal,
695
- fileSize > SNAPSHOT_MAX_BYTES, // giant file: collected ranges don't need an exact EOF line count
705
+ { includeTerminalNewline: rawSelector, stopScanAfterCollect: fileSize > SNAPSHOT_MAX_BYTES },
696
706
  );
697
707
  totalFileLines = streamResult.totalFileLines;
698
708
  collectedLines = streamResult.lines;
@@ -1256,7 +1266,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1256
1266
  maxBytesForRead,
1257
1267
  selectedLineLimit,
1258
1268
  undefined, // plain-file read: deterministic and fast, never abort mid-read
1259
- fileSize > SNAPSHOT_MAX_BYTES, // giant file: don't scan to EOF just for an exact line count
1269
+ { includeTerminalNewline: rawSelector, stopScanAfterCollect: fileSize > SNAPSHOT_MAX_BYTES },
1260
1270
  );
1261
1271
 
1262
1272
  const {
@@ -1267,6 +1277,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1267
1277
  firstLinePreview,
1268
1278
  firstLineByteLength,
1269
1279
  reachedEof,
1280
+ hasTrailingNewline,
1270
1281
  } = streamResult;
1271
1282
 
1272
1283
  // Check if offset is out of bounds - return graceful message instead of throwing
@@ -1347,7 +1358,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1347
1358
  const tag = isWholeFile
1348
1359
  ? getFileSnapshotStore(this.session).record(
1349
1360
  canonicalSnapshotKey(absolutePath),
1350
- normalizeToLF(collectedLines.join("\n")),
1361
+ normalizeToLF(`${collectedLines.join("\n")}${hasTrailingNewline ? "\n" : ""}`),
1351
1362
  )
1352
1363
  : await recordFileSnapshot(this.session, absolutePath);
1353
1364
  if (tag) {
@@ -1694,7 +1705,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
1694
1705
  maxBytesForRead,
1695
1706
  selectedLineLimit,
1696
1707
  signal,
1697
- artifact.size > SNAPSHOT_MAX_BYTES,
1708
+ { includeTerminalNewline: rawSelector, stopScanAfterCollect: artifact.size > SNAPSHOT_MAX_BYTES },
1698
1709
  );
1699
1710
  const {
1700
1711
  lines: collectedLines,
@@ -1,13 +1,36 @@
1
1
  import { type } from "@oh-my-pi/omptype";
2
2
  import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
3
+ import type { Model } from "@oh-my-pi/pi-ai";
3
4
  import { type Component, Markdown } from "@oh-my-pi/pi-tui";
4
5
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
5
6
  import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
6
7
 
8
+ /** Whether a model transport can suppress native reasoning while private scratchpad thoughts are active. */
9
+ export function supportsExternalThinking(model: Model | null | undefined): boolean {
10
+ if (!model) return false;
11
+ const requiresThinking =
12
+ model.api === "anthropic-messages" &&
13
+ model.compat !== undefined &&
14
+ "requiresThinkingEnabled" in model.compat &&
15
+ model.compat.requiresThinkingEnabled === true;
16
+ if (model.reasoning && (requiresThinking || (model.thinking?.requiresEffort && !model.thinking.suppressWhenOff))) {
17
+ return false;
18
+ }
19
+ if (model.api === "google-generative-ai" || model.api === "google-gemini-cli" || model.api === "google-vertex") {
20
+ return !model.reasoning || model.thinking?.mode === "budget" || model.thinking?.suppressWhenOff === true;
21
+ }
22
+ return (
23
+ model.api === "openai-responses" ||
24
+ model.api === "azure-openai-responses" ||
25
+ model.api === "openai-codex-responses" ||
26
+ model.api === "anthropic-messages"
27
+ );
28
+ }
29
+
7
30
  const thinkSchema = type({
8
- thoughts: type("string").describe("private scratchpad reasoning to retain before the next response"),
31
+ thoughts: type("string").describe("private scratchpad; not shown to user"),
9
32
  "+": "reject",
10
- }).describe("record private intermediate reasoning before answering");
33
+ }).describe("private scratchpad; not shown to user");
11
34
 
12
35
  type ThinkParams = typeof thinkSchema.infer;
13
36
 
@@ -36,14 +59,13 @@ interface ThinkToolDetails {
36
59
  recorded: true;
37
60
  }
38
61
 
39
- /** Records private intermediate reasoning while native GPT reasoning is disabled. */
62
+ /** Records private scratchpad thoughts while native model reasoning is disabled. */
40
63
  export class ThinkTool implements AgentTool<typeof thinkSchema, ThinkToolDetails> {
41
64
  readonly name = "think";
42
65
  readonly approval = "read" as const;
43
66
  readonly label = "Think";
44
- readonly summary = "Record private intermediate reasoning before answering";
45
- readonly description =
46
- "Use this private scratchpad to plan, derive, or check work before answering. Record only materially new reasoning. The user does not see this tool activity.";
67
+ readonly summary = "Record private scratchpad thoughts";
68
+ readonly description = "private scratchpad; not shown to user";
47
69
  readonly parameters = thinkSchema;
48
70
  readonly strict = true;
49
71
  readonly intent = "omit" as const;
@@ -659,7 +659,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
659
659
  const entries = new Map<string, ArchiveMemberContent>();
660
660
  if (resolvedArchivePath.exists) {
661
661
  try {
662
- const existing = await readArchiveEntries({ bytes: await Bun.file(finalPath).bytes(), format });
662
+ const existing = await readArchiveEntries({ path: finalPath, format });
663
663
  for (const [entryPath, data] of existing) {
664
664
  entries.set(entryPath, data);
665
665
  }
@@ -7,7 +7,12 @@
7
7
  */
8
8
  import * as fs from "node:fs/promises";
9
9
  import path from "node:path";
10
- import { formatHashlineHeader, formatNumberedLines, type SnapshotStore } from "@oh-my-pi/hashline";
10
+ import {
11
+ formatHashlineHeader,
12
+ formatNumberedLines,
13
+ type SnapshotStore,
14
+ splitAddressableFileLines,
15
+ } from "@oh-my-pi/hashline";
11
16
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
12
17
  import type { ImageContent } from "@oh-my-pi/pi-ai";
13
18
  import { formatAge, formatBytes, isProbablyBinary, readImageMetadata } from "@oh-my-pi/pi-utils";
@@ -270,7 +275,8 @@ export async function generateFileMentionMessages(
270
275
  const content = await Bun.file(absolutePath).text();
271
276
  const snapshotStore = options?.useHashLines ? options.snapshotStore : undefined;
272
277
  const normalized = snapshotStore ? normalizeToLF(content) : content;
273
- let { output, lineCount } = buildTextOutput(normalized);
278
+ const displayText = snapshotStore ? splitAddressableFileLines(normalized).join("\n") : normalized;
279
+ let { output, lineCount } = buildTextOutput(displayText);
274
280
  if (snapshotStore) {
275
281
  const tag = snapshotStore.record(canonicalSnapshotKey(absolutePath), normalized);
276
282
  output = `${formatHashlineHeader(resolvedPath, tag)}\n${formatNumberedLines(output)}`;