@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.11

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 (281) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
  3. package/dist/cli.js +10208 -12596
  4. package/dist/types/capability/mcp.d.ts +11 -0
  5. package/dist/types/capability/skill.d.ts +6 -0
  6. package/dist/types/cli/args.d.ts +1 -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/model-registry.d.ts +4 -0
  14. package/dist/types/config/model-resolver.d.ts +13 -0
  15. package/dist/types/config/settings-schema.d.ts +1 -34
  16. package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
  17. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  18. package/dist/types/discovery/contained-path.d.ts +34 -0
  19. package/dist/types/discovery/index.d.ts +1 -0
  20. package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
  21. package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
  22. package/dist/types/extensibility/extensions/loader.d.ts +9 -2
  23. package/dist/types/extensibility/extensions/types.d.ts +45 -8
  24. package/dist/types/extensibility/hooks/types.d.ts +5 -5
  25. package/dist/types/extensibility/shared-events.d.ts +3 -2
  26. package/dist/types/extensibility/skills.d.ts +5 -0
  27. package/dist/types/index.d.ts +2 -2
  28. package/dist/types/main.d.ts +1 -1
  29. package/dist/types/mcp/transports/header-policy.d.ts +45 -0
  30. package/dist/types/mcp/types.d.ts +15 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  32. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  33. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
  34. package/dist/types/modes/acp/acp-mode.d.ts +1 -1
  35. package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
  36. package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
  37. package/dist/types/modes/components/agent-hub.d.ts +14 -5
  38. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  39. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  40. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  41. package/dist/types/modes/components/model-browser.d.ts +9 -1
  42. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  43. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  44. package/dist/types/modes/interactive-mode.d.ts +8 -1
  45. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  46. package/dist/types/registry/agent-registry.d.ts +47 -0
  47. package/dist/types/registry/persisted-agents.d.ts +3 -1
  48. package/dist/types/secrets/index.d.ts +23 -1
  49. package/dist/types/session/agent-session-events.d.ts +2 -2
  50. package/dist/types/session/agent-session.d.ts +4 -2
  51. package/dist/types/session/messages.d.ts +1 -0
  52. package/dist/types/session/prewalk.d.ts +3 -1
  53. package/dist/types/session/session-entries.d.ts +10 -0
  54. package/dist/types/session/session-loader.d.ts +11 -1
  55. package/dist/types/session/session-manager.d.ts +9 -1
  56. package/dist/types/session/session-tools.d.ts +18 -1
  57. package/dist/types/session/turn-recovery.d.ts +7 -2
  58. package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
  59. package/dist/types/slash-commands/available-commands.d.ts +1 -1
  60. package/dist/types/task/executor.d.ts +6 -0
  61. package/dist/types/task/index.d.ts +4 -3
  62. package/dist/types/task/read-only-policy.d.ts +3 -0
  63. package/dist/types/task/structured-subagent.d.ts +2 -0
  64. package/dist/types/task/types.d.ts +4 -0
  65. package/dist/types/tools/bash.d.ts +3 -3
  66. package/dist/types/tools/browser/launch.d.ts +2 -1
  67. package/dist/types/tools/browser/tab-worker.d.ts +2 -2
  68. package/dist/types/tools/path-utils.d.ts +1 -0
  69. package/dist/types/tools/read.d.ts +1 -5
  70. package/dist/types/tools/run-scope.d.ts +27 -1
  71. package/dist/types/tools/shell-tokenize.d.ts +16 -0
  72. package/dist/types/tools/terminal-output.d.ts +1 -1
  73. package/dist/types/utils/turndown.d.ts +1 -1
  74. package/dist/types/vibe/runtime.d.ts +2 -3
  75. package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
  76. package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
  77. package/examples/custom-tools/README.md +1 -1
  78. package/examples/extensions/README.md +1 -1
  79. package/examples/extensions/api-demo.ts +5 -6
  80. package/examples/extensions/chalk-logger.ts +1 -1
  81. package/examples/extensions/hello.ts +2 -2
  82. package/examples/extensions/reload-runtime.ts +1 -1
  83. package/examples/extensions/tools.ts +2 -2
  84. package/examples/extensions/with-deps/index.ts +1 -1
  85. package/examples/sdk/06-extensions.ts +1 -1
  86. package/package.json +15 -31
  87. package/scripts/bundle-dist.ts +1 -11
  88. package/scripts/legacy-pi-virtual-module.ts +4 -1
  89. package/src/advisor/runtime.ts +54 -0
  90. package/src/capability/mcp.ts +11 -0
  91. package/src/capability/skill.ts +6 -0
  92. package/src/cli/agents-cli.ts +1 -1
  93. package/src/cli/args.ts +24 -2
  94. package/src/cli/auth-broker-cli.ts +1 -1
  95. package/src/cli/auth-gateway-cli.ts +1 -1
  96. package/src/cli/bench-cli.ts +1 -1
  97. package/src/cli/claude-trace-cli.ts +1 -1
  98. package/src/cli/command-help.ts +4 -0
  99. package/src/cli/config-cli.ts +1 -1
  100. package/src/cli/dry-balance-cli.ts +1 -1
  101. package/src/cli/file-processor.ts +1 -1
  102. package/src/cli/flag-tables.ts +4 -0
  103. package/src/cli/gallery-cli.ts +1 -1
  104. package/src/cli/grep-cli.ts +1 -1
  105. package/src/cli/grievances-cli.ts +1 -1
  106. package/src/cli/help-extra.ts +1 -1
  107. package/src/cli/models-cli.ts +1 -1
  108. package/src/cli/plugin-cli.ts +1 -1
  109. package/src/cli/read-cli.ts +1 -1
  110. package/src/cli/setup-cli.ts +1 -1
  111. package/src/cli/shell-cli.ts +1 -1
  112. package/src/cli/ssh-cli.ts +1 -1
  113. package/src/cli/stats-cli.ts +1 -1
  114. package/src/cli/tiny-models-cli.ts +1 -1
  115. package/src/cli/ttsr-cli.ts +1 -1
  116. package/src/cli/update-cli.ts +1 -1
  117. package/src/cli/usage-cli.ts +1 -1
  118. package/src/cli/web-search-cli.ts +1 -1
  119. package/src/cli/worktree-cli.ts +1 -1
  120. package/src/cli-commands.ts +5 -0
  121. package/src/commands/commit.ts +16 -3
  122. package/src/commands/say.ts +1 -1
  123. package/src/commands/share.ts +71 -0
  124. package/src/commands/token.ts +1 -1
  125. package/src/commit/agentic/agent.ts +1 -1
  126. package/src/commit/agentic/index.ts +39 -21
  127. package/src/commit/cli.ts +1 -1
  128. package/src/commit/execute.ts +56 -0
  129. package/src/commit/index.ts +2 -1
  130. package/src/commit/pipeline.ts +20 -7
  131. package/src/config/model-discovery.ts +1 -1
  132. package/src/config/model-registry.ts +21 -1
  133. package/src/config/model-resolver.ts +54 -7
  134. package/src/config/settings-schema.ts +2 -33
  135. package/src/config/settings.ts +46 -0
  136. package/src/discovery/agent-plugin-format.ts +551 -0
  137. package/src/discovery/agent-plugins.ts +341 -0
  138. package/src/discovery/agents.ts +4 -2
  139. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
  140. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
  141. package/src/discovery/claude-plugins.ts +21 -5
  142. package/src/discovery/contained-path.ts +78 -0
  143. package/src/discovery/helpers.ts +23 -9
  144. package/src/discovery/index.ts +1 -0
  145. package/src/discovery/omp-plugins.ts +20 -7
  146. package/src/eval/py/runner.py +38 -1
  147. package/src/exec/non-interactive-env.ts +1 -0
  148. package/src/extensibility/custom-commands/loader.ts +4 -4
  149. package/src/extensibility/custom-commands/types.ts +5 -5
  150. package/src/extensibility/custom-tools/loader.ts +4 -4
  151. package/src/extensibility/custom-tools/types.ts +3 -3
  152. package/src/extensibility/extensions/loader.ts +85 -20
  153. package/src/extensibility/extensions/runner.ts +6 -0
  154. package/src/extensibility/extensions/types.ts +51 -8
  155. package/src/extensibility/hooks/loader.ts +4 -5
  156. package/src/extensibility/hooks/types.ts +5 -5
  157. package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
  158. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  159. package/src/extensibility/shared-events.ts +3 -2
  160. package/src/extensibility/skills.ts +9 -0
  161. package/src/index.ts +2 -2
  162. package/src/internal-urls/memory-protocol.ts +5 -1
  163. package/src/internal-urls/skill-protocol.ts +14 -0
  164. package/src/internal-urls/vault-protocol.ts +2 -2
  165. package/src/launch/client.ts +11 -1
  166. package/src/launch/protocol.ts +7 -2
  167. package/src/launch/terminal-output.ts +1 -1
  168. package/src/main.ts +88 -23
  169. package/src/markit/converters/docx.ts +1 -1
  170. package/src/markit/converters/epub.ts +1 -1
  171. package/src/markit/converters/pptx.ts +1 -1
  172. package/src/markit/converters/xlsx.ts +1 -1
  173. package/src/mcp/config.ts +3 -0
  174. package/src/mcp/manager.ts +12 -9
  175. package/src/mcp/transports/header-policy.ts +95 -0
  176. package/src/mcp/transports/http.ts +35 -52
  177. package/src/mcp/transports/sse.ts +20 -27
  178. package/src/mcp/types.ts +15 -0
  179. package/src/modes/acp/acp-agent.ts +20 -9
  180. package/src/modes/acp/acp-client-bridge.ts +1 -1
  181. package/src/modes/acp/acp-event-mapper.ts +1 -1
  182. package/src/modes/acp/acp-mode.ts +1 -1
  183. package/src/modes/components/agent-dashboard.ts +2 -0
  184. package/src/modes/components/agent-hub-projection.ts +248 -0
  185. package/src/modes/components/agent-hub-renderer.ts +194 -0
  186. package/src/modes/components/agent-hub.ts +674 -207
  187. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  188. package/src/modes/components/assistant-message.ts +1 -1
  189. package/src/modes/components/chat-transcript-builder.ts +3 -0
  190. package/src/modes/components/custom-editor.ts +0 -9
  191. package/src/modes/components/extensions/inspector-panel.ts +11 -6
  192. package/src/modes/components/footer.ts +1 -3
  193. package/src/modes/components/model-browser.ts +11 -3
  194. package/src/modes/components/model-hub.ts +4 -1
  195. package/src/modes/components/status-line/component.ts +1 -0
  196. package/src/modes/components/status-line/segments.ts +10 -7
  197. package/src/modes/components/status-line/types.ts +2 -0
  198. package/src/modes/components/tool-execution.ts +13 -16
  199. package/src/modes/components/tree-selector.ts +62 -3
  200. package/src/modes/controllers/command-controller.ts +4 -1
  201. package/src/modes/controllers/event-controller.ts +44 -20
  202. package/src/modes/controllers/extension-ui-controller.ts +2 -2
  203. package/src/modes/controllers/input-controller.ts +20 -2
  204. package/src/modes/controllers/mcp-command-controller.ts +81 -20
  205. package/src/modes/controllers/selector-controller.ts +41 -49
  206. package/src/modes/controllers/streaming-reveal.ts +1 -1
  207. package/src/modes/interactive-mode.ts +13 -2
  208. package/src/modes/rpc/rpc-mode.ts +2 -2
  209. package/src/modes/runtime-init.ts +1 -1
  210. package/src/modes/theme/theme.ts +2 -2
  211. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  212. package/src/modes/utils/ui-helpers.ts +1 -0
  213. package/src/prompts/tools/computer.md +1 -1
  214. package/src/registry/agent-lifecycle.ts +14 -7
  215. package/src/registry/agent-registry.ts +65 -2
  216. package/src/registry/persisted-agents.ts +341 -16
  217. package/src/sdk.ts +6 -49
  218. package/src/secrets/index.ts +52 -1
  219. package/src/session/agent-session-events.ts +2 -2
  220. package/src/session/agent-session.ts +54 -16
  221. package/src/session/messages.ts +1 -0
  222. package/src/session/prewalk.ts +57 -17
  223. package/src/session/session-context.ts +3 -5
  224. package/src/session/session-entries.ts +10 -0
  225. package/src/session/session-handoff.ts +5 -1
  226. package/src/session/session-listing.ts +1 -1
  227. package/src/session/session-loader.ts +74 -8
  228. package/src/session/session-manager.ts +22 -2
  229. package/src/session/session-paths.ts +57 -9
  230. package/src/session/session-tools.ts +65 -4
  231. package/src/session/todo-tracker.ts +11 -1
  232. package/src/session/turn-recovery.ts +96 -74
  233. package/src/slash-commands/acp-builtins.ts +1 -1
  234. package/src/slash-commands/available-commands.ts +1 -1
  235. package/src/slash-commands/builtin-registry.ts +12 -8
  236. package/src/task/executor.ts +70 -20
  237. package/src/task/index.ts +30 -34
  238. package/src/task/isolation-runner.ts +24 -11
  239. package/src/task/persisted-revive.ts +12 -5
  240. package/src/task/read-only-policy.ts +27 -0
  241. package/src/task/structured-subagent.ts +14 -4
  242. package/src/task/types.ts +4 -0
  243. package/src/tools/auto-generated-guard.ts +1 -1
  244. package/src/tools/bash-interactive.ts +4 -4
  245. package/src/tools/bash-skill-urls.ts +15 -0
  246. package/src/tools/bash.ts +16 -17
  247. package/src/tools/browser/cmux/cmux-tab.ts +66 -18
  248. package/src/tools/browser/launch.ts +41 -12
  249. package/src/tools/browser/readable.ts +9 -9
  250. package/src/tools/browser/tab-supervisor.ts +12 -3
  251. package/src/tools/browser/tab-worker-entry.ts +1 -1
  252. package/src/tools/browser/tab-worker.ts +101 -12
  253. package/src/tools/debug.ts +1 -1
  254. package/src/tools/fetch.ts +1 -1
  255. package/src/tools/jtd-to-json-schema.ts +123 -21
  256. package/src/tools/path-utils.ts +9 -2
  257. package/src/tools/read.ts +21 -9
  258. package/src/tools/run-scope.ts +290 -4
  259. package/src/tools/shell-tokenize.ts +98 -0
  260. package/src/tools/terminal-output.ts +1 -1
  261. package/src/tools/todo.ts +1 -1
  262. package/src/utils/clipboard.ts +10 -2
  263. package/src/utils/external-editor.ts +4 -2
  264. package/src/utils/jj.ts +1 -1
  265. package/src/utils/turndown.ts +1 -2
  266. package/src/vibe/runtime.ts +5 -5
  267. package/src/web/scrapers/arxiv.ts +1 -1
  268. package/src/web/scrapers/go-pkg.ts +1 -1
  269. package/src/web/scrapers/iacr.ts +1 -1
  270. package/src/web/scrapers/readthedocs.ts +2 -1
  271. package/src/web/scrapers/twitter.ts +2 -2
  272. package/src/web/scrapers/types.ts +1 -1
  273. package/src/web/scrapers/wikipedia.ts +1 -1
  274. package/src/web/search/providers/browser-headers.ts +12 -39
  275. package/src/web/search/providers/codex.ts +3 -26
  276. package/src/web/search/providers/ecosia.ts +1 -1
  277. package/src/web/search/providers/exa.ts +1 -1
  278. package/src/web/search/providers/google.ts +1 -1
  279. package/src/web/search/providers/mojeek.ts +1 -1
  280. package/src/web/search/providers/startpage.ts +1 -1
  281. package/src/markit/converters/mammoth.d.ts +0 -24
@@ -1,6 +1,285 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { untilAborted } from "@oh-my-pi/pi-utils/abortable";
3
+ import * as postmortem from "@oh-my-pi/pi-utils/postmortem";
2
4
  import { ToolError, throwIfAborted } from "./tool-errors";
3
5
 
6
+ const browserRunRejections = new WeakMap<object, object>();
7
+
8
+ /** Associates a browser operation failure with its owning evaluated run. */
9
+ export function markBrowserRunRejection<T>(reason: T, owner: object): T {
10
+ if (reason !== null && (typeof reason === "object" || typeof reason === "function")) {
11
+ browserRunRejections.set(reason, owner);
12
+ }
13
+ return reason;
14
+ }
15
+
16
+ /** Returns whether a rejection was marked for the specified evaluated run. */
17
+ export function isBrowserRunRejection(reason: unknown, owner: object): boolean {
18
+ return (
19
+ reason !== null &&
20
+ (typeof reason === "object" || typeof reason === "function") &&
21
+ browserRunRejections.get(reason) === owner
22
+ );
23
+ }
24
+
25
+ /** Returns whether a rejection belongs to the marked browser run or its evaluated source file. */
26
+ export function isBrowserRunOwnedRejection(reason: unknown, owner: object, filename: string): boolean {
27
+ if (isBrowserRunRejection(reason, owner)) return true;
28
+ return reason instanceof Error && typeof reason.stack === "string" && reason.stack.includes(filename);
29
+ }
30
+
31
+ type FloatingRejectionHandler = (reason: unknown) => void;
32
+
33
+ interface ObservedPromiseState {
34
+ handled: boolean;
35
+ userContinuationFailed: boolean;
36
+ }
37
+
38
+ const observedBrowserPromises = new WeakMap<Promise<unknown>, ObservedPromiseState>();
39
+ const observedPromiseConstructor = { [Symbol.species]: Promise };
40
+
41
+ type PromiseCombinatorName = "all" | "race";
42
+ type PromiseCombinator = (this: PromiseConstructor, values: Iterable<unknown>) => Promise<unknown>;
43
+
44
+ interface PromiseCombinatorTrackingContext {
45
+ owner: object;
46
+ onFloatingRejection: FloatingRejectionHandler;
47
+ }
48
+
49
+ const PROMISE_COMBINATORS: readonly PromiseCombinatorName[] = ["all", "race"];
50
+ const NativePromise = Promise;
51
+ const nativePromiseCombinators: Record<PromiseCombinatorName, PromiseCombinator> = {
52
+ all: Promise.all,
53
+ race: Promise.race,
54
+ };
55
+ const promiseCombinatorTracking = new AsyncLocalStorage<PromiseCombinatorTrackingContext>();
56
+ let previousPromiseDescriptor: PropertyDescriptor | undefined;
57
+ let promiseCombinatorTrackingScopes = 0;
58
+
59
+ /**
60
+ * Observes native promise-combinator results derived from browser promises for
61
+ * the duration of one evaluated run. Native `await` remains unchanged; dropped
62
+ * user continuations from `Promise.all` and `Promise.race` are routed to the
63
+ * owning run.
64
+ */
65
+ export async function withBrowserPromiseCombinatorTracking<T>(
66
+ owner: object,
67
+ onFloatingRejection: FloatingRejectionHandler,
68
+ run: () => Promise<T>,
69
+ ): Promise<T> {
70
+ installPromiseCombinatorTracking();
71
+ try {
72
+ return await promiseCombinatorTracking.run({ owner, onFloatingRejection }, run);
73
+ } finally {
74
+ restorePromiseCombinatorTracking();
75
+ }
76
+ }
77
+
78
+ function installPromiseCombinatorTracking(): void {
79
+ if (promiseCombinatorTrackingScopes > 0) {
80
+ promiseCombinatorTrackingScopes++;
81
+ return;
82
+ }
83
+ const descriptor = Object.getOwnPropertyDescriptor(globalThis, "Promise");
84
+ if (!descriptor) throw new Error("Global Promise descriptor is unavailable");
85
+ const trackedPromise = createTrackedPromiseConstructor();
86
+ Object.defineProperty(globalThis, "Promise", { ...descriptor, value: trackedPromise });
87
+ previousPromiseDescriptor = descriptor;
88
+ promiseCombinatorTrackingScopes = 1;
89
+ }
90
+
91
+ function restorePromiseCombinatorTracking(): void {
92
+ if (promiseCombinatorTrackingScopes > 1) {
93
+ promiseCombinatorTrackingScopes--;
94
+ return;
95
+ }
96
+ const descriptor = previousPromiseDescriptor;
97
+ try {
98
+ if (!descriptor) throw new Error("Global Promise tracking scope is not installed");
99
+ Object.defineProperty(globalThis, "Promise", descriptor);
100
+ } finally {
101
+ previousPromiseDescriptor = undefined;
102
+ promiseCombinatorTrackingScopes = 0;
103
+ }
104
+ }
105
+
106
+ function createTrackedPromiseConstructor(): PromiseConstructor {
107
+ class TrackedPromise<T> extends NativePromise<T> {}
108
+ for (const name of PROMISE_COMBINATORS) {
109
+ const original = nativePromiseCombinators[name];
110
+ Object.defineProperty(TrackedPromise, name, {
111
+ configurable: true,
112
+ writable: true,
113
+ value(this: PromiseConstructor, values: Iterable<unknown>): Promise<unknown> {
114
+ let hasObservedInput = false;
115
+ const result = Reflect.apply(original, this, [
116
+ tapObservedBrowserPromises(values, () => {
117
+ hasObservedInput = true;
118
+ }),
119
+ ]) as Promise<unknown>;
120
+ const context = promiseCombinatorTracking.getStore();
121
+ return hasObservedInput && context
122
+ ? observeBrowserRunPromise(result, context.owner, context.onFloatingRejection)
123
+ : result;
124
+ },
125
+ });
126
+ }
127
+ return TrackedPromise;
128
+ }
129
+
130
+ function* tapObservedBrowserPromises(
131
+ values: Iterable<unknown>,
132
+ onObserved: () => void,
133
+ ): Generator<unknown, void, undefined> {
134
+ for (const value of values) {
135
+ if (observedBrowserPromises.has(value as Promise<unknown>)) onObserved();
136
+ yield value;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Observes every explicit continuation of a browser promise without replacing
142
+ * the native promise. Browser failures remain contained; an unhandled error
143
+ * created by user continuation code is reported to the owning run.
144
+ */
145
+ export function observeBrowserRunPromise<T>(
146
+ promise: Promise<T>,
147
+ owner: object,
148
+ onFloatingRejection: FloatingRejectionHandler,
149
+ ): Promise<T> {
150
+ return observeBrowserRunPromiseWithState(promise, owner, onFloatingRejection, {
151
+ handled: false,
152
+ userContinuationFailed: false,
153
+ });
154
+ }
155
+
156
+ function observeBrowserRunPromiseWithState<T>(
157
+ promise: Promise<T>,
158
+ owner: object,
159
+ onFloatingRejection: FloatingRejectionHandler,
160
+ state: ObservedPromiseState,
161
+ ): Promise<T> {
162
+ if (observedBrowserPromises.has(promise)) return promise;
163
+ observedBrowserPromises.set(promise, state);
164
+ const originalThen = promise.then.bind(promise);
165
+ const originalFinally = promise.finally.bind(promise);
166
+ void originalThen(undefined, reason => {
167
+ setTimeout(() => {
168
+ if (!state.handled && (state.userContinuationFailed || !isBrowserRunRejection(reason, owner))) {
169
+ onFloatingRejection(reason);
170
+ }
171
+ }, 0);
172
+ });
173
+ Object.defineProperties(promise, {
174
+ constructor: { configurable: true, value: observedPromiseConstructor },
175
+ // biome-ignore lint/suspicious/noThenProperty: native Promise continuations must remain thenable.
176
+ then: {
177
+ configurable: true,
178
+ value: <TResult1 = T, TResult2 = never>(
179
+ onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
180
+ onRejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
181
+ ): Promise<TResult1 | TResult2> => {
182
+ state.handled = true;
183
+ const childState = createContinuationState();
184
+ return observeBrowserRunPromiseWithState(
185
+ originalThen(
186
+ recordContinuationFailure(onFulfilled, childState),
187
+ recordContinuationFailure(onRejected, childState),
188
+ ),
189
+ owner,
190
+ onFloatingRejection,
191
+ childState,
192
+ );
193
+ },
194
+ },
195
+ catch: {
196
+ configurable: true,
197
+ value: <TResult = never>(
198
+ onRejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
199
+ ): Promise<T | TResult> => {
200
+ state.handled = true;
201
+ const childState = createContinuationState();
202
+ return observeBrowserRunPromiseWithState(
203
+ originalThen(undefined, recordContinuationFailure(onRejected, childState)),
204
+ owner,
205
+ onFloatingRejection,
206
+ childState,
207
+ );
208
+ },
209
+ },
210
+ finally: {
211
+ configurable: true,
212
+ value: (onFinally?: (() => void) | null): Promise<T> => {
213
+ state.handled = true;
214
+ const childState = createContinuationState();
215
+ return observeBrowserRunPromiseWithState(
216
+ originalFinally(recordContinuationFailure(onFinally, childState)),
217
+ owner,
218
+ onFloatingRejection,
219
+ childState,
220
+ );
221
+ },
222
+ },
223
+ });
224
+ return promise;
225
+ }
226
+
227
+ function createContinuationState(): ObservedPromiseState {
228
+ return { handled: false, userContinuationFailed: false };
229
+ }
230
+
231
+ function recordContinuationFailure<TArgs extends unknown[], TResult>(
232
+ continuation: ((...args: TArgs) => TResult | PromiseLike<TResult>) | null | undefined,
233
+ state: ObservedPromiseState,
234
+ ): ((...args: TArgs) => TResult | PromiseLike<TResult>) | null | undefined {
235
+ if (!continuation) return continuation;
236
+ return (...args) => {
237
+ try {
238
+ const result = continuation(...args);
239
+ if (!isThenable(result)) return result;
240
+ return Promise.resolve(result).catch(reason => {
241
+ state.userContinuationFailed = true;
242
+ throw reason;
243
+ }) as PromiseLike<TResult>;
244
+ } catch (reason) {
245
+ state.userContinuationFailed = true;
246
+ throw reason;
247
+ }
248
+ };
249
+ }
250
+
251
+ function isThenable(value: unknown): value is PromiseLike<unknown> {
252
+ if (value === null || (typeof value !== "object" && typeof value !== "function")) return false;
253
+ return typeof Reflect.get(value, "then") === "function";
254
+ }
255
+
256
+ function trackBrowserRunPromise<T>(
257
+ promise: Promise<T>,
258
+ owner?: object,
259
+ onFloatingRejection?: FloatingRejectionHandler,
260
+ ): Promise<T> {
261
+ if (!owner) return markHandled(promise);
262
+ const tracked = promise.catch(error => {
263
+ throw markBrowserRunRejection(error, owner);
264
+ });
265
+ return onFloatingRejection ? observeBrowserRunPromise(tracked, owner, onFloatingRejection) : tracked;
266
+ }
267
+
268
+ /**
269
+ * Installs worker-realm rejection routing. Consumed browser-run failures stay in
270
+ * the worker; unrelated failures retain the default fatal worker behavior.
271
+ */
272
+ export function installBrowserWorkerRejectionGuard(consume: (reason: unknown) => boolean): () => void {
273
+ const onRejection = (reason: unknown): void => {
274
+ if (postmortem.isExpectedCleanupError(reason) || consume(reason)) return;
275
+ setTimeout(() => {
276
+ throw reason;
277
+ }, 0);
278
+ };
279
+ process.on("unhandledRejection", onRejection);
280
+ return () => process.off("unhandledRejection", onRejection);
281
+ }
282
+
4
283
  /**
5
284
  * Marks a run-scoped promise as observed without changing its behavior for awaited callers.
6
285
  *
@@ -83,11 +362,16 @@ export function waitForRun(
83
362
  await untilAborted(signal, async () => await Bun.sleep(interval));
84
363
  }
85
364
  })();
86
- return markHandled(promise);
365
+ return trackBrowserRunPromise(promise);
87
366
  }
88
367
 
89
368
  /** Binds a long-lived scope facade (page/tab/desktop objects) to one evaluated run's abort signal. */
90
- export function bindRunFacade<T extends object>(target: T, signal: AbortSignal): T {
369
+ export function bindRunFacade<T extends object>(
370
+ target: T,
371
+ signal: AbortSignal,
372
+ rejectionOwner?: object,
373
+ onFloatingRejection?: FloatingRejectionHandler,
374
+ ): T {
91
375
  const cache = new Map<PropertyKey, unknown>();
92
376
  return new Proxy(target, {
93
377
  get(current, prop) {
@@ -102,11 +386,13 @@ export function bindRunFacade<T extends object>(target: T, signal: AbortSignal):
102
386
  if (result && typeof result === "object") {
103
387
  const then = Reflect.get(result, "then");
104
388
  if (typeof then === "function") {
105
- return markHandled(
389
+ return trackBrowserRunPromise(
106
390
  Promise.resolve(result).then(resolved => {
107
391
  throwIfAborted(signal);
108
392
  return resolved;
109
393
  }),
394
+ rejectionOwner,
395
+ onFloatingRejection,
110
396
  );
111
397
  }
112
398
  }
@@ -121,7 +407,7 @@ export function bindRunFacade<T extends object>(target: T, signal: AbortSignal):
121
407
  // brand-check internal slots that a Proxy cannot forward, and reading a
122
408
  // signal needs no abort gating anyway.
123
409
  if (value instanceof AbortSignal) return value;
124
- const wrapped = bindRunFacade(value, signal);
410
+ const wrapped = bindRunFacade(value, signal, rejectionOwner, onFloatingRejection);
125
411
  cache.set(prop, wrapped);
126
412
  return wrapped;
127
413
  }
@@ -211,3 +211,101 @@ export function extractFlatShellCommandSegments(command: string): FlatShellComma
211
211
  pushSegment(command.length);
212
212
  return segments;
213
213
  }
214
+
215
+ /**
216
+ * Shell metacharacters that end an unquoted `cd` target token. A redirect,
217
+ * extra argument, or any operator in this set means the leading construct is
218
+ * more than a bare `cd <path>`, so extraction must bail.
219
+ */
220
+ const CD_TARGET_TERMINATORS: Record<string, true> = {
221
+ " ": true,
222
+ "\t": true,
223
+ "\n": true,
224
+ "\r": true,
225
+ "&": true,
226
+ "|": true,
227
+ ";": true,
228
+ "<": true,
229
+ ">": true,
230
+ "(": true,
231
+ ")": true,
232
+ };
233
+
234
+ /**
235
+ * Parses a leading `cd <path> && ...` prefix so the bash tool can route the
236
+ * target through its structured `cwd` parameter when the model omits it.
237
+ *
238
+ * Returns the single path token (quotes and backslash escapes resolved to their
239
+ * literal value) and the command remainder after the top-level `&&`, or `null`
240
+ * when the command does not begin with exactly `cd`, one path token, and a
241
+ * top-level `&&`. The scanner deliberately bails on anything else in the prefix
242
+ * — redirects (`cd /tmp 2>/dev/null && ...`), extra arguments, or paths needing
243
+ * shell expansion (`$`, backticks, `(`) — leaving the whole command for the
244
+ * shell instead of absorbing shell syntax into `cwd`.
245
+ */
246
+ export function extractLeadingCdTarget(command: string): { path: string; rest: string } | null {
247
+ const prefix = /^cd[ \t]+/.exec(command);
248
+ if (!prefix) return null;
249
+ let i = prefix[0].length;
250
+ let path = "";
251
+ let inSingle = false;
252
+ let inDouble = false;
253
+ for (; i < command.length; i++) {
254
+ const ch = command[i];
255
+ if (inSingle) {
256
+ if (ch === "'") {
257
+ inSingle = false;
258
+ continue;
259
+ }
260
+ path += ch;
261
+ continue;
262
+ }
263
+ if (inDouble) {
264
+ if (ch === "\\" && i + 1 < command.length) {
265
+ const next = command[i + 1];
266
+ // A line continuation crosses the first physical line. Leave it to
267
+ // the shell rather than turning the escaped newline into cwd text.
268
+ if (next === "\n" || next === "\r") return null;
269
+ if (next === '"' || next === "\\" || next === "$" || next === "`") {
270
+ path += next;
271
+ i++;
272
+ continue;
273
+ }
274
+ }
275
+ if (ch === '"') {
276
+ inDouble = false;
277
+ continue;
278
+ }
279
+ path += ch;
280
+ continue;
281
+ }
282
+ if (ch === "'") {
283
+ inSingle = true;
284
+ continue;
285
+ }
286
+ if (ch === '"') {
287
+ inDouble = true;
288
+ continue;
289
+ }
290
+ if (ch === "\\" && i + 1 < command.length) {
291
+ // Preserve shell line-continuation semantics by declining extraction.
292
+ if (command[i + 1] === "\n" || command[i + 1] === "\r") return null;
293
+ path += command[i + 1];
294
+ i++;
295
+ continue;
296
+ }
297
+ if (CD_TARGET_TERMINATORS[ch]) break;
298
+ path += ch;
299
+ }
300
+ // Unterminated quote or empty target: leave the command for the shell.
301
+ if (inSingle || inDouble || path.length === 0) return null;
302
+ // A path needing shell expansion can't be resolved literally through cwd.
303
+ if (/[$`(]/.test(path)) return null;
304
+ // Skip inter-token whitespace, then require a top-level `&&` (a single `&`,
305
+ // `||`, `;`, `|`, or a redirect all mean this is not a bare `cd <path>`).
306
+ while (command[i] === " " || command[i] === "\t") i++;
307
+ if (command[i] !== "&" || command[i + 1] !== "&") return null;
308
+ i += 2;
309
+ while (command[i] === " " || command[i] === "\t") i++;
310
+ return { path, rest: command.slice(i) };
311
+ }
@@ -1,5 +1,5 @@
1
1
  import { sanitizeText } from "@oh-my-pi/pi-utils";
2
- import type { Terminal as XtermTerminal } from "@xterm/headless";
2
+ import type { Terminal as XtermTerminal } from "@oh-my-pi/pi-utils/vterm";
3
3
 
4
4
  const RESET = "\x1b[0m";
5
5
  const SGR = /\x1b\[([0-9;]*)m/g;
package/src/tools/todo.ts CHANGED
@@ -4,7 +4,7 @@ import type { ToolExample } from "@oh-my-pi/pi-ai";
4
4
  import type { Component } from "@oh-my-pi/pi-tui";
5
5
  import { Text } from "@oh-my-pi/pi-tui";
6
6
  import { isRecord, prompt, sanitizeText } from "@oh-my-pi/pi-utils";
7
- import chalk from "chalk";
7
+ import chalk from "@oh-my-pi/pi-utils/chalk";
8
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
9
  import type { Theme } from "../modes/theme/theme";
10
10
  import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
@@ -259,6 +259,14 @@ async function readTextViaPowerShell(): Promise<string | null> {
259
259
  }
260
260
  }
261
261
 
262
+ async function readTextFromX11Clipboard(): Promise<string> {
263
+ try {
264
+ return await spawnCapture(["xclip", "-selection", "clipboard", "-o"]);
265
+ } catch {
266
+ return await spawnCapture(["xsel", "--clipboard", "--output"]);
267
+ }
268
+ }
269
+
262
270
  /**
263
271
  * Read an image from the system clipboard.
264
272
  *
@@ -340,11 +348,11 @@ export async function readTextFromClipboard(): Promise<string> {
340
348
  return await spawnCapture(["wl-paste", "--type", "text/plain", "--no-newline"]);
341
349
  } catch {
342
350
  if (hasX11Display) {
343
- return await spawnCapture(["xclip", "-selection", "clipboard", "-o"]);
351
+ return await readTextFromX11Clipboard();
344
352
  }
345
353
  }
346
354
  } else if (hasX11Display) {
347
- return await spawnCapture(["xclip", "-selection", "clipboard", "-o"]);
355
+ return await readTextFromX11Clipboard();
348
356
  }
349
357
  } catch (error) {
350
358
  logger.warn("clipboard: failed to read clipboard text", { error: String(error) });
@@ -53,8 +53,10 @@ export async function openInEditor(
53
53
 
54
54
  const [editor, ...editorArgs] = editorCmd.split(" ");
55
55
  const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
56
-
57
- const child = spawn(editor, [...editorArgs, tmpFile], { stdio, shell: process.platform === "win32" });
56
+ const child =
57
+ process.platform === "win32"
58
+ ? spawn(editor, [...editorArgs, tmpFile], { stdio, shell: true })
59
+ : spawn("/bin/sh", ["-c", `${editorCmd} "$1"`, "sh", tmpFile], { stdio });
58
60
  const { promise, reject, resolve } = Promise.withResolvers<number>();
59
61
  child.once("exit", (code, signal) => resolve(code ?? (signal ? -1 : 0)));
60
62
  child.once("error", error => reject(error));
package/src/utils/jj.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { $which } from "@oh-my-pi/pi-utils";
4
- import { LRUCache } from "lru-cache/raw";
4
+ import { LRUCache } from "@oh-my-pi/pi-utils/lru";
5
5
  import { withTimeoutSignal } from "./fetch-timeout";
6
6
  import * as git from "./git";
7
7
 
@@ -1,5 +1,4 @@
1
- import TurndownService from "turndown";
2
- import { gfm } from "turndown-plugin-gfm";
1
+ import TurndownService, { gfm } from "@oh-my-pi/pi-utils/turndown";
3
2
 
4
3
  type TurndownListParent = {
5
4
  nodeName: string;
@@ -25,7 +25,6 @@ import { MCPManager } from "../mcp/manager";
25
25
  import vibeTurnResultTemplate from "../prompts/tools/vibe-turn-result.md" with { type: "text" };
26
26
  import { AgentLifecycleManager } from "../registry/agent-lifecycle";
27
27
  import { type AgentRef, AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
28
- import type { SessionEntry } from "../session/session-entries";
29
28
  import { SessionManager, SessionPersistenceIndeterminateError } from "../session/session-manager";
30
29
  import { getBundledAgent } from "../task/agents";
31
30
  import { type ExecutorOptions, runSubagentFollowUpTurn, runSubprocess } from "../task/executor";
@@ -363,11 +362,12 @@ function parseLifecycleEvent(value: unknown): VibeLifecycleEvent | undefined {
363
362
  return undefined;
364
363
  }
365
364
 
366
- /** Child ids claimed by any valid Vibe spawn event, independent of current parent scope. */
367
- export function persistedVibeChildIds(entries: Iterable<SessionEntry>): Set<string> {
365
+ /** Child ids claimed by valid Vibe spawn records from untrusted persisted JSON. */
366
+ export function persistedVibeChildIds(entries: Iterable<unknown>): Set<string> {
368
367
  const ids = new Set<string>();
369
- for (const entry of entries) {
370
- if (entry.type !== "custom" || entry.customType !== VIBE_LIFECYCLE_CUSTOM_TYPE) continue;
368
+ for (const value of entries) {
369
+ const entry = objectRecord(value);
370
+ if (entry?.type !== "custom" || entry.customType !== VIBE_LIFECYCLE_CUSTOM_TYPE) continue;
371
371
  const event = parseLifecycleEvent(entry.data);
372
372
  if (
373
373
  event?.action === "spawn" &&
@@ -30,7 +30,7 @@ export const handleArxiv: SpecialHandler = async (
30
30
  if (!result.ok) return null;
31
31
 
32
32
  // Parse the Atom feed response
33
- const { parseHTML } = await import("linkedom");
33
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
34
34
  const doc = parseHTML(result.content).document;
35
35
  const entry = doc.querySelector("entry");
36
36
 
@@ -96,7 +96,7 @@ export const handleGoPkg: SpecialHandler = async (
96
96
  });
97
97
  }
98
98
 
99
- const { parseHTML } = await import("linkedom");
99
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
100
100
  const doc = parseHTML(pageResult.content).document;
101
101
 
102
102
  // Extract actual module path from breadcrumb or header
@@ -29,7 +29,7 @@ export const handleIacr: SpecialHandler = async (
29
29
 
30
30
  if (!result.ok) return null;
31
31
 
32
- const { parseHTML } = await import("linkedom");
32
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
33
33
  const doc = parseHTML(result.content).document;
34
34
 
35
35
  // Extract metadata from the page
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Read the Docs handler for web-fetch
3
3
  */
4
+ import type { Element } from "@oh-my-pi/pi-utils/dom";
4
5
  import { buildResult, htmlToBasicMarkdown, loadPage, type RenderResult, type SpecialHandler } from "./types";
5
6
 
6
7
  export const handleReadTheDocs: SpecialHandler = async (
@@ -38,7 +39,7 @@ export const handleReadTheDocs: SpecialHandler = async (
38
39
  }
39
40
 
40
41
  // Parse HTML
41
- const { parseHTML } = await import("linkedom");
42
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
42
43
  const root = parseHTML(result.content).document;
43
44
 
44
45
  // Extract main content from common Read the Docs selectors
@@ -1,4 +1,4 @@
1
- import type { HTMLElement } from "linkedom";
1
+ import type { HTMLElement } from "@oh-my-pi/pi-utils/dom";
2
2
  import { ToolAbortError } from "../../tools/tool-errors";
3
3
  import type { RenderResult, SpecialHandler } from "./types";
4
4
  import { buildResult, loadPage } from "./types";
@@ -33,7 +33,7 @@ export const handleTwitter: SpecialHandler = async (
33
33
 
34
34
  if (result.ok && result.content.length > 500) {
35
35
  // Parse the Nitter HTML
36
- const { parseHTML } = await import("linkedom");
36
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
37
37
  const doc = parseHTML(result.content).document;
38
38
 
39
39
  // Extract tweet content
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { scheduler } from "node:timers/promises";
5
5
  import { ptree } from "@oh-my-pi/pi-utils";
6
- import type TurndownService from "turndown";
6
+ import type TurndownService from "@oh-my-pi/pi-utils/turndown";
7
7
 
8
8
  import type { AgentStorage } from "../../session/agent-storage";
9
9
  import { ToolAbortError } from "../../tools/tool-errors";
@@ -44,7 +44,7 @@ export const handleWikipedia: SpecialHandler = async (
44
44
  const contentResult = await loadPage(contentUrl, { timeout, signal });
45
45
 
46
46
  if (contentResult.ok) {
47
- const { parseHTML } = await import("linkedom");
47
+ const { parseHTML } = await import("@oh-my-pi/pi-utils/dom");
48
48
  const doc = parseHTML(contentResult.content).document;
49
49
 
50
50
  // Extract main content sections