@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12

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 (247) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
  3. package/dist/cli.js +10775 -10762
  4. package/dist/types/advisor/runtime.d.ts +1 -1
  5. package/dist/types/capability/mcp.d.ts +11 -0
  6. package/dist/types/capability/skill.d.ts +6 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/custom-models.d.ts +31 -0
  14. package/dist/types/config/model-config-values.d.ts +19 -0
  15. package/dist/types/config/model-patch.d.ts +93 -0
  16. package/dist/types/config/model-provider-discovery.d.ts +51 -0
  17. package/dist/types/config/model-registry.d.ts +9 -52
  18. package/dist/types/config/settings-schema.d.ts +1 -34
  19. package/dist/types/config/settings.d.ts +5 -3
  20. package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
  21. package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
  22. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  23. package/dist/types/discovery/contained-path.d.ts +33 -0
  24. package/dist/types/discovery/index.d.ts +1 -0
  25. package/dist/types/eval/executor-base.d.ts +8 -2
  26. package/dist/types/eval/kernel-session-registry.d.ts +60 -0
  27. package/dist/types/export/share.d.ts +1 -1
  28. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  29. package/dist/types/extensibility/extensions/loader.d.ts +7 -0
  30. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -2
  32. package/dist/types/extensibility/skills.d.ts +5 -0
  33. package/dist/types/lsp/diagnostics.d.ts +96 -0
  34. package/dist/types/lsp/index.d.ts +7 -128
  35. package/dist/types/lsp/servers.d.ts +72 -0
  36. package/dist/types/lsp/tool.d.ts +42 -0
  37. package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
  38. package/dist/types/lsp/writethrough.d.ts +33 -0
  39. package/dist/types/mcp/transports/header-policy.d.ts +46 -0
  40. package/dist/types/mcp/types.d.ts +15 -0
  41. package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
  42. package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
  43. package/dist/types/modes/components/agent-hub.d.ts +2 -0
  44. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  45. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  46. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  47. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  48. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  49. package/dist/types/modes/theme/color.d.ts +19 -0
  50. package/dist/types/modes/theme/loader.d.ts +19 -0
  51. package/dist/types/modes/theme/schema.d.ts +175 -0
  52. package/dist/types/modes/theme/symbols.d.ts +28 -0
  53. package/dist/types/modes/theme/theme-class.d.ts +244 -0
  54. package/dist/types/modes/theme/theme.d.ts +9 -280
  55. package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
  56. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  57. package/dist/types/registry/agent-registry.d.ts +1 -3
  58. package/dist/types/secrets/index.d.ts +26 -2
  59. package/dist/types/secrets/message-transform.d.ts +40 -0
  60. package/dist/types/secrets/obfuscator.d.ts +0 -108
  61. package/dist/types/secrets/placeholder-scan.d.ts +95 -0
  62. package/dist/types/secrets/placeholder.d.ts +94 -0
  63. package/dist/types/secrets/replacement.d.ts +82 -0
  64. package/dist/types/session/agent-session-events.d.ts +2 -2
  65. package/dist/types/session/agent-session-types.d.ts +6 -0
  66. package/dist/types/session/agent-session.d.ts +2 -2
  67. package/dist/types/session/messages.d.ts +1 -0
  68. package/dist/types/session/prewalk.d.ts +3 -1
  69. package/dist/types/session/session-handoff.d.ts +3 -3
  70. package/dist/types/session/session-manager.d.ts +32 -0
  71. package/dist/types/session/session-provider-boundary.d.ts +1 -1
  72. package/dist/types/session/session-tools.d.ts +8 -0
  73. package/dist/types/session/turn-recovery.d.ts +9 -4
  74. package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
  75. package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
  76. package/dist/types/slash-commands/builtin-control.d.ts +2 -0
  77. package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
  78. package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
  79. package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
  80. package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
  81. package/dist/types/slash-commands/builtin-session.d.ts +2 -0
  82. package/dist/types/task/index.d.ts +2 -0
  83. package/dist/types/tools/gh-common.d.ts +69 -0
  84. package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
  85. package/dist/types/tools/gh-pr-diff.d.ts +102 -0
  86. package/dist/types/tools/gh-run-watch.d.ts +78 -0
  87. package/dist/types/tools/gh-search.d.ts +61 -0
  88. package/dist/types/tools/gh-types.d.ts +340 -0
  89. package/dist/types/tools/gh-view.d.ts +88 -0
  90. package/dist/types/tools/gh.d.ts +4 -201
  91. package/dist/types/tools/read-archive.d.ts +16 -0
  92. package/dist/types/tools/read-format.d.ts +104 -0
  93. package/dist/types/tools/read-path-resolution.d.ts +19 -0
  94. package/dist/types/tools/read-pdf-images.d.ts +12 -0
  95. package/dist/types/tools/read-renderer.d.ts +24 -0
  96. package/dist/types/tools/read-selector.d.ts +27 -0
  97. package/dist/types/tools/read-sqlite.d.ts +16 -0
  98. package/dist/types/tools/read-summary.d.ts +19 -0
  99. package/dist/types/tools/read.d.ts +1 -23
  100. package/dist/types/tools/shell-tokenize.d.ts +18 -1
  101. package/dist/types/utils/changelog.d.ts +0 -5
  102. package/package.json +13 -13
  103. package/scripts/legacy-pi-virtual-module.ts +4 -1
  104. package/src/advisor/runtime.ts +12 -7
  105. package/src/capability/mcp.ts +11 -0
  106. package/src/capability/skill.ts +6 -0
  107. package/src/cli/command-help.ts +4 -0
  108. package/src/cli/gallery-cli.ts +1 -1
  109. package/src/cli-commands.ts +5 -0
  110. package/src/commands/commit.ts +16 -3
  111. package/src/commands/share.ts +71 -0
  112. package/src/commit/agentic/index.ts +39 -21
  113. package/src/commit/execute.ts +56 -0
  114. package/src/commit/index.ts +2 -1
  115. package/src/commit/pipeline.ts +20 -7
  116. package/src/config/custom-models.ts +188 -0
  117. package/src/config/model-config-values.ts +128 -0
  118. package/src/config/model-discovery.ts +1 -1
  119. package/src/config/model-patch.ts +252 -0
  120. package/src/config/model-provider-discovery.ts +132 -0
  121. package/src/config/model-registry.ts +84 -704
  122. package/src/config/settings-schema.ts +2 -33
  123. package/src/config/settings.ts +53 -3
  124. package/src/debug/raw-sse-buffer.ts +20 -0
  125. package/src/discovery/agent-plugin-format.ts +551 -0
  126. package/src/discovery/agent-plugins.ts +341 -0
  127. package/src/discovery/claude-plugins.ts +21 -5
  128. package/src/discovery/contained-path.ts +75 -0
  129. package/src/discovery/helpers.ts +23 -9
  130. package/src/discovery/index.ts +1 -0
  131. package/src/discovery/omp-plugins.ts +20 -7
  132. package/src/edit/hashline/diff.ts +5 -1
  133. package/src/eval/executor-base.ts +39 -6
  134. package/src/eval/jl/executor.ts +82 -371
  135. package/src/eval/kernel-session-registry.ts +398 -0
  136. package/src/eval/py/executor.ts +53 -261
  137. package/src/eval/rb/executor.ts +36 -279
  138. package/src/exec/non-interactive-env.ts +1 -0
  139. package/src/export/share.ts +2 -1
  140. package/src/extensibility/custom-tools/types.ts +2 -2
  141. package/src/extensibility/extensions/loader.ts +78 -14
  142. package/src/extensibility/extensions/runner.ts +6 -0
  143. package/src/extensibility/extensions/types.ts +12 -0
  144. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  145. package/src/extensibility/shared-events.ts +3 -2
  146. package/src/extensibility/skills.ts +9 -0
  147. package/src/internal-urls/skill-protocol.ts +14 -0
  148. package/src/internal-urls/vault-protocol.ts +2 -2
  149. package/src/launch/client.ts +11 -1
  150. package/src/launch/protocol.ts +7 -2
  151. package/src/lsp/diagnostics.ts +516 -0
  152. package/src/lsp/index.ts +20 -2819
  153. package/src/lsp/servers.ts +296 -0
  154. package/src/lsp/tool.ts +1352 -0
  155. package/src/lsp/workspace-diagnostics.ts +170 -0
  156. package/src/lsp/writethrough.ts +561 -0
  157. package/src/main.ts +3 -2
  158. package/src/mcp/config.ts +3 -0
  159. package/src/mcp/manager.ts +12 -9
  160. package/src/mcp/transports/header-policy.ts +95 -0
  161. package/src/mcp/transports/http.ts +35 -52
  162. package/src/mcp/transports/sse.ts +20 -27
  163. package/src/mcp/types.ts +15 -0
  164. package/src/modes/acp/acp-agent.ts +15 -4
  165. package/src/modes/components/agent-dashboard.ts +2 -0
  166. package/src/modes/components/agent-hub-projection.ts +2 -2
  167. package/src/modes/components/agent-hub-renderer.ts +3 -7
  168. package/src/modes/components/agent-hub.ts +5 -0
  169. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  170. package/src/modes/components/chat-transcript-builder.ts +3 -0
  171. package/src/modes/components/custom-editor.ts +0 -9
  172. package/src/modes/components/status-line/component.ts +1 -0
  173. package/src/modes/components/status-line/segments.ts +6 -4
  174. package/src/modes/components/status-line/types.ts +2 -0
  175. package/src/modes/components/tool-execution.ts +13 -16
  176. package/src/modes/components/tree-selector.ts +62 -3
  177. package/src/modes/controllers/command-controller.ts +8 -2
  178. package/src/modes/controllers/event-controller.ts +15 -15
  179. package/src/modes/controllers/input-controller.ts +20 -2
  180. package/src/modes/controllers/selector-controller.ts +1 -0
  181. package/src/modes/theme/color.ts +133 -0
  182. package/src/modes/theme/loader.ts +178 -0
  183. package/src/modes/theme/schema.ts +263 -0
  184. package/src/modes/theme/symbols.ts +1000 -0
  185. package/src/modes/theme/theme-class.ts +611 -0
  186. package/src/modes/theme/theme.ts +27 -2453
  187. package/src/modes/theme/tui-adapters.ts +279 -0
  188. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  189. package/src/modes/utils/ui-helpers.ts +1 -0
  190. package/src/registry/agent-registry.ts +2 -2
  191. package/src/sdk.ts +6 -49
  192. package/src/secrets/index.ts +51 -6
  193. package/src/secrets/message-transform.ts +287 -0
  194. package/src/secrets/obfuscator.ts +39 -1303
  195. package/src/secrets/placeholder-scan.ts +506 -0
  196. package/src/secrets/placeholder.ts +309 -0
  197. package/src/secrets/replacement.ts +216 -0
  198. package/src/session/agent-session-events.ts +2 -2
  199. package/src/session/agent-session-types.ts +6 -0
  200. package/src/session/agent-session.ts +146 -21
  201. package/src/session/indexed-session-storage.ts +7 -2
  202. package/src/session/messages.ts +1 -0
  203. package/src/session/prewalk.ts +57 -17
  204. package/src/session/session-advisors.ts +32 -34
  205. package/src/session/session-context.ts +3 -5
  206. package/src/session/session-handoff.ts +39 -16
  207. package/src/session/session-manager.ts +67 -3
  208. package/src/session/session-provider-boundary.ts +3 -6
  209. package/src/session/session-tools.ts +37 -3
  210. package/src/session/todo-tracker.ts +11 -1
  211. package/src/session/turn-recovery.ts +115 -84
  212. package/src/slash-commands/builtin-collaboration.ts +504 -0
  213. package/src/slash-commands/builtin-completions.ts +291 -0
  214. package/src/slash-commands/builtin-control.ts +78 -0
  215. package/src/slash-commands/builtin-lifecycle.ts +506 -0
  216. package/src/slash-commands/builtin-marketplace.ts +567 -0
  217. package/src/slash-commands/builtin-modes.ts +518 -0
  218. package/src/slash-commands/builtin-registry.ts +21 -2996
  219. package/src/slash-commands/builtin-session.ts +592 -0
  220. package/src/task/executor.ts +17 -14
  221. package/src/task/index.ts +21 -6
  222. package/src/tools/bash-skill-urls.ts +15 -0
  223. package/src/tools/bash.ts +10 -11
  224. package/src/tools/debug.ts +1 -1
  225. package/src/tools/gh-common.ts +288 -0
  226. package/src/tools/gh-pr-checkout.ts +679 -0
  227. package/src/tools/gh-pr-diff.ts +473 -0
  228. package/src/tools/gh-run-watch.ts +1015 -0
  229. package/src/tools/gh-search.ts +500 -0
  230. package/src/tools/gh-types.ts +379 -0
  231. package/src/tools/gh-view.ts +612 -0
  232. package/src/tools/gh.ts +109 -3821
  233. package/src/tools/jtd-to-json-schema.ts +123 -21
  234. package/src/tools/read-archive.ts +209 -0
  235. package/src/tools/read-format.ts +593 -0
  236. package/src/tools/read-path-resolution.ts +36 -0
  237. package/src/tools/read-pdf-images.ts +250 -0
  238. package/src/tools/read-renderer.ts +289 -0
  239. package/src/tools/read-selector.ts +84 -0
  240. package/src/tools/read-sqlite.ts +215 -0
  241. package/src/tools/read-summary.ts +199 -0
  242. package/src/tools/read.ts +82 -1824
  243. package/src/tools/shell-tokenize.ts +99 -1
  244. package/src/utils/changelog.ts +1 -1
  245. package/src/utils/title-generator.ts +3 -1
  246. package/src/web/search/providers/exa.ts +1 -1
  247. package/src/web/search/providers/zai.ts +12 -3
@@ -468,6 +468,8 @@ export class SessionManager {
468
468
 
469
469
  /** The single open append writer; the manager only ever writes one file at a time. */
470
470
  #writer: SessionStorageWriter | undefined;
471
+ /** Sealed by {@link releaseRetainedEntries}: every later append/title/rewrite is a dropped no-op. */
472
+ #released = false;
471
473
  /** Serializes async disk work (flush/close/atomic rewrite). Appends are synchronous and bypass it. */
472
474
  #diskTail: Promise<void> = Promise.resolve();
473
475
  #diskFailure: Error | undefined;
@@ -641,6 +643,16 @@ export class SessionManager {
641
643
  }
642
644
 
643
645
  async #authoritativelyRewriteCurrentStateLocked(operationError: Error): Promise<void> {
646
+ if (this.#released) {
647
+ // Terminal seal: repair would reset the disk tail (escaping the
648
+ // close() serialization) and atomically publish #fileBody() — after
649
+ // release that truncates, and a revival may already own the file.
650
+ // The original operation error still propagates to the caller.
651
+ logger.warn("Skipped authoritative session repair after terminal release", {
652
+ error: String(operationError),
653
+ });
654
+ return;
655
+ }
644
656
  if (!this.#persist || !this.#sessionFile) return;
645
657
  const previousDiskTail = this.#diskTail;
646
658
  const writer = this.#writer;
@@ -687,7 +699,7 @@ export class SessionManager {
687
699
  const body = this.#fileBody();
688
700
  try {
689
701
  await this.#storage.writeTextAtomic(sessionFile, body, {
690
- commitGuard: () => this.#diskEpoch === epoch,
702
+ commitGuard: () => !this.#released && this.#diskEpoch === epoch,
691
703
  });
692
704
  } catch (error) {
693
705
  const recoveryErrors = [toError(error)];
@@ -791,6 +803,7 @@ export class SessionManager {
791
803
  * concurrent completed entries are durable without recreating a vacated source.
792
804
  */
793
805
  #rewriteSynchronously(): void {
806
+ if (this.#released) return;
794
807
  if (!this.#persist || !this.#shouldHaveSessionFile()) return;
795
808
  const targetPath = this.#liveRelocationWritePath() ?? this.#sessionFile;
796
809
  if (!targetPath) return;
@@ -833,6 +846,7 @@ export class SessionManager {
833
846
  */
834
847
  async #rewriteAtomically(): Promise<void> {
835
848
  if (!this.#persist || !this.#sessionFile) return;
849
+ if (this.#released) return;
836
850
 
837
851
  const startEpoch = this.#diskEpoch;
838
852
  await this.#scheduleDiskWork(
@@ -858,6 +872,7 @@ export class SessionManager {
858
872
  * their post-publish state updates.
859
873
  */
860
874
  async #runFencedAtomicRewrite(epoch: number): Promise<boolean> {
875
+ if (this.#released) return false;
861
876
  this.#atomicRewriteFenceEpoch = epoch;
862
877
  try {
863
878
  do {
@@ -867,7 +882,7 @@ export class SessionManager {
867
882
  if (!sessionFile) return false;
868
883
  if (this.#diskEpoch !== epoch) return false;
869
884
  await this.#storage.writeTextAtomic(sessionFile, this.#fileBody(), {
870
- commitGuard: () => this.#diskEpoch === epoch,
885
+ commitGuard: () => !this.#released && this.#diskEpoch === epoch,
871
886
  });
872
887
  if (this.#diskEpoch !== epoch) return false;
873
888
  } while (this.#atomicRewriteDirty);
@@ -884,7 +899,7 @@ export class SessionManager {
884
899
  }
885
900
 
886
901
  #appendToSessionFile(entry: SessionEntry): void {
887
- if (!this.#persist || !this.#sessionFile) return;
902
+ if (this.#released || !this.#persist || !this.#sessionFile) return;
888
903
  if (this.#atomicEntryBatch) {
889
904
  this.#fileIsCurrent = false;
890
905
  this.#rewriteRequired = true;
@@ -979,6 +994,7 @@ export class SessionManager {
979
994
  const line = this.#lineFor(entry);
980
995
  await this.#scheduleDiskWork(
981
996
  async () => {
997
+ if (this.#released) return;
982
998
  const sessionFile = this.#sessionFile;
983
999
  if (!sessionFile) return;
984
1000
  try {
@@ -1093,6 +1109,10 @@ export class SessionManager {
1093
1109
  }
1094
1110
 
1095
1111
  #recordEntry(entry: SessionEntry): void {
1112
+ if (this.#released) {
1113
+ logger.warn("Dropped session entry appended after terminal release", { type: entry.type });
1114
+ return;
1115
+ }
1096
1116
  this.#entries.push(entry);
1097
1117
  this.#index.insert(entry);
1098
1118
  const batch = this.#atomicEntryBatch;
@@ -1707,6 +1727,49 @@ export class SessionManager {
1707
1727
  if (this.#diskFailure) throw this.#diskFailure;
1708
1728
  }
1709
1729
 
1730
+ /**
1731
+ * Raise the terminal write barrier ahead of the final {@link close}. Once
1732
+ * sealed:
1733
+ * - every later append, title change, and rewrite is a dropped no-op —
1734
+ * including work an event handler tries to enqueue while dispose is
1735
+ * awaiting `close()` on the disk tail;
1736
+ * - the disk epoch is bumped, so queued-but-unexecuted tail work is
1737
+ * superseded and an ALREADY-RUNNING fenced/repair rewrite (awaiting the
1738
+ * tail, drain, writer close, or the atomic stage) fails its commit guard
1739
+ * at the rename fence instead of publishing over a revived file.
1740
+ * The final `close()` itself is scheduled after the bump and still runs;
1741
+ * pre-seal hot-path appends are already in the page cache. Idempotent;
1742
+ * terminal.
1743
+ */
1744
+ seal(): void {
1745
+ if (this.#released) return;
1746
+ this.#released = true;
1747
+ this.#diskEpoch++;
1748
+ }
1749
+
1750
+ /**
1751
+ * Terminal release: drop the in-memory transcript and complete the
1752
+ * {@link seal}. The entry journal and its index mirror the agent's message
1753
+ * array (tool results, file contents, base64 frame images); on a disposed
1754
+ * session — e.g. a parked subagent still referenced by the lifecycle
1755
+ * adoption record — they would otherwise stay pinned for the process
1756
+ * lifetime.
1757
+ *
1758
+ * Closes the append writer; with the seal up, nothing can reopen it. A
1759
+ * revival may reopen the same JSONL through a NEW manager the moment
1760
+ * dispose returns; a late event handler resuming on THIS manager must
1761
+ * never race that writer — and a post-release rewrite would persist the
1762
+ * now-empty entry list, truncating the transcript. Reads after this point
1763
+ * reopen from disk (revival, `history://`). Only call from session
1764
+ * dispose, after the final `close()`; idempotent.
1765
+ */
1766
+ releaseRetainedEntries(): void {
1767
+ this.seal();
1768
+ this.#entries = [];
1769
+ this.#index.clear();
1770
+ this.#closeWriterEventually();
1771
+ }
1772
+
1710
1773
  getCwd(): string {
1711
1774
  return this.#cwd;
1712
1775
  }
@@ -1924,6 +1987,7 @@ export class SessionManager {
1924
1987
  * Auto titles are ignored once the user has set a name.
1925
1988
  */
1926
1989
  async setSessionName(name: string, source: SessionTitleSource = "auto", trigger?: string): Promise<boolean> {
1990
+ if (this.#released) return false;
1927
1991
  if (this.#titleSource === "user" && source === "auto") return false;
1928
1992
 
1929
1993
  const title = SessionManager.#cleanTitle(name);
@@ -10,12 +10,9 @@ import { formatModelString } from "../config/model-resolver";
10
10
  import type { Settings } from "../config/settings";
11
11
  import { validateProviderMaxInFlightRequests } from "../config/settings";
12
12
  import type { LocalProtocolOptions } from "../internal-urls";
13
- import {
14
- deobfuscateSessionContext,
15
- obfuscateMessages,
16
- type SecretObfuscator,
17
- stripPendingSecretPlaceholderSuffix,
18
- } from "../secrets/obfuscator";
13
+ import { deobfuscateSessionContext, obfuscateMessages } from "../secrets/message-transform";
14
+ import type { SecretObfuscator } from "../secrets/obfuscator";
15
+ import { stripPendingSecretPlaceholderSuffix } from "../secrets/placeholder";
19
16
  import { normalizeModelContextImages } from "../utils/image-loading";
20
17
  import { describeAttachedImagesForTextModel } from "../utils/image-vision-fallback";
21
18
  import { type CustomMessage, convertToLlm } from "./messages";
@@ -198,6 +198,15 @@ export class SessionTools {
198
198
  #presentationPinnedToolNames: ReadonlySet<string> | undefined;
199
199
  #runtimeSelectedToolNames: ReadonlySet<string> | undefined;
200
200
  #baseSystemPrompt: string[];
201
+ /**
202
+ * Per-turn system prompt returned by a `before_agent_start` extension hook
203
+ * ("replace the system prompt for this turn"). While set, base-prompt
204
+ * rebuilds keep this override on the agent instead of the rebuilt base, so a
205
+ * rebuild landing in the prompt window (compaction/promotion, memory
206
+ * promotion, MCP/RPC tool refresh, hindsight MM-TTL refresh) cannot silently
207
+ * drop it before the request. Cleared when the turn ends.
208
+ */
209
+ #turnSystemPromptOverride: string[] | undefined;
201
210
  #lastAppliedToolSignature: string | undefined;
202
211
  /**
203
212
  * `xd://` device names the current base system prompt renders in its catalog
@@ -262,6 +271,31 @@ export class SessionTools {
262
271
  this.#baseSystemPrompt = prompt;
263
272
  }
264
273
 
274
+ /**
275
+ * Pushes `base` to the agent as the effective system prompt, unless an active
276
+ * per-turn {@link #turnSystemPromptOverride} takes precedence. Every base
277
+ * rebuild applies its result through here so a mid-turn rebuild preserves the
278
+ * override.
279
+ */
280
+ #applyAgentSystemPrompt(base: string[]): void {
281
+ this.#host.agent.setSystemPrompt(this.#turnSystemPromptOverride ?? base);
282
+ }
283
+
284
+ /**
285
+ * Registers the per-turn `before_agent_start` system-prompt override and
286
+ * applies it to the agent. Base rebuilds during the turn preserve it until
287
+ * {@link clearTurnSystemPromptOverride}.
288
+ */
289
+ setTurnSystemPromptOverride(prompt: string[]): void {
290
+ this.#turnSystemPromptOverride = prompt;
291
+ this.#host.agent.setSystemPrompt(prompt);
292
+ }
293
+
294
+ /** Drops the active per-turn override; later rebuilds fall back to the base prompt. */
295
+ clearTurnSystemPromptOverride(): void {
296
+ this.#turnSystemPromptOverride = undefined;
297
+ }
298
+
265
299
  /** Skills currently rendered into the system prompt. */
266
300
  get skills(): Skill[] {
267
301
  return this.#skills;
@@ -690,7 +724,7 @@ export class SessionTools {
690
724
  if (this.#lastAppliedToolSignature !== undefined) this.#host.clearInheritedProviderPromptCacheKey();
691
725
  this.#baseSystemPrompt = rebuiltSystemPrompt;
692
726
  this.#host.clearMemoryPromotionSnapshot();
693
- this.#host.agent.setSystemPrompt(this.#baseSystemPrompt);
727
+ this.#applyAgentSystemPrompt(this.#baseSystemPrompt);
694
728
  this.#lastAppliedToolSignature = rebuiltSignature;
695
729
  this.#promptModelKey = this.#currentPromptModelKey();
696
730
  this.#basePromptXdevNames = new Set(rebuiltXdevCatalogNames);
@@ -1130,7 +1164,7 @@ export class SessionTools {
1130
1164
  ) {
1131
1165
  this.#host.clearInheritedProviderPromptCacheKey();
1132
1166
  }
1133
- this.#host.agent.setSystemPrompt(this.#baseSystemPrompt);
1167
+ this.#applyAgentSystemPrompt(this.#baseSystemPrompt);
1134
1168
  this.#promptModelKey = this.#currentPromptModelKey();
1135
1169
  // Refresh the cached signature so a subsequent `applyActiveToolsByName` with
1136
1170
  // the same tool set does not re-rebuild on top of the explicit refresh we
@@ -1170,7 +1204,7 @@ export class SessionTools {
1170
1204
  this.#host.captureMemoryPromotionSnapshot(previousBaseSystemPrompt);
1171
1205
  const stablePrompt = [...previousBaseSystemPrompt, injected];
1172
1206
  this.#baseSystemPrompt = stablePrompt;
1173
- this.#host.agent.setSystemPrompt(stablePrompt);
1207
+ this.#applyAgentSystemPrompt(stablePrompt);
1174
1208
  return stablePrompt;
1175
1209
  } catch (err) {
1176
1210
  logger.debug("Memory backend beforeAgentStartPrompt failed", {
@@ -27,6 +27,15 @@ const QUESTION_PROMPT_RE =
27
27
  const USER_DIRECTED_PROMPT_RE = /\b(?:you|your|we|our)\b/i;
28
28
  const USER_RESPONSE_CUE_RE =
29
29
  /^(?:please\s+)?(?:confirm|reply|choose|pick|decide|advise)\b|^(?:please\s+)?answer\b|^(?:please\s+)?(?:let\s+me\s+know|tell\s+me)\b/i;
30
+ /**
31
+ * A trailing question mark is the universal signal that a line is a question, but
32
+ * the English word/pronoun gates above exist to filter incidental "?" out of prose
33
+ * (e.g. a TypeScript `foo?: string` tail). Non-English text has no cheap word list,
34
+ * yet any non-ASCII character in a "?"/"?"-terminated line reliably marks it as
35
+ * genuine prose — CJK/Japanese/Korean, Spanish `¿…?`, accented Latin — so treat it
36
+ * as a real user-directed question. Fixes non-Latin prompts going undetected (#7803).
37
+ */
38
+ const NON_ASCII_TEXT_RE = /[^\x00-\x7F]/;
30
39
 
31
40
  interface PromptLine {
32
41
  text: string;
@@ -361,7 +370,8 @@ function isQuestionPromptLine(line: string): boolean {
361
370
  return (
362
371
  candidate.hadPromptLabel ||
363
372
  QUESTION_PROMPT_RE.test(candidate.text) ||
364
- USER_DIRECTED_PROMPT_RE.test(candidate.text)
373
+ USER_DIRECTED_PROMPT_RE.test(candidate.text) ||
374
+ NON_ASCII_TEXT_RE.test(candidate.text)
365
375
  );
366
376
  }
367
377