@oh-my-pi/pi-coding-agent 16.3.4 → 16.3.6

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 (88) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cli.js +3522 -3485
  3. package/dist/types/edit/file-snapshot-store.d.ts +4 -2
  4. package/dist/types/edit/renderer.d.ts +0 -1
  5. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  6. package/dist/types/extensibility/shared-events.d.ts +8 -1
  7. package/dist/types/hindsight/content.d.ts +4 -0
  8. package/dist/types/internal-urls/artifact-protocol.d.ts +8 -0
  9. package/dist/types/internal-urls/types.d.ts +10 -0
  10. package/dist/types/lsp/config.d.ts +4 -0
  11. package/dist/types/lsp/edits.d.ts +2 -0
  12. package/dist/types/mcp/oauth-discovery.d.ts +30 -0
  13. package/dist/types/modes/components/assistant-message.d.ts +15 -10
  14. package/dist/types/modes/components/bash-execution.d.ts +6 -0
  15. package/dist/types/modes/components/eval-execution.d.ts +6 -0
  16. package/dist/types/modes/components/login-dialog.d.ts +10 -2
  17. package/dist/types/modes/components/tool-execution.d.ts +3 -13
  18. package/dist/types/modes/components/transcript-container.d.ts +26 -29
  19. package/dist/types/modes/controllers/mcp-command-controller.d.ts +20 -3
  20. package/dist/types/modes/rpc/rpc-client.d.ts +7 -3
  21. package/dist/types/modes/rpc/rpc-types.d.ts +6 -0
  22. package/dist/types/modes/utils/transcript-render-helpers.d.ts +15 -6
  23. package/dist/types/session/agent-session.d.ts +2 -0
  24. package/dist/types/subprocess/worker-runtime.d.ts +11 -0
  25. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  26. package/dist/types/tools/bash.d.ts +0 -2
  27. package/dist/types/tools/browser/tab-supervisor.d.ts +10 -0
  28. package/dist/types/tools/eval-render.d.ts +0 -2
  29. package/dist/types/tools/renderers.d.ts +0 -20
  30. package/dist/types/tools/ssh.d.ts +0 -2
  31. package/dist/types/tools/write.d.ts +1 -1
  32. package/dist/types/utils/git.d.ts +16 -0
  33. package/package.json +12 -12
  34. package/src/cli/auth-broker-cli.ts +13 -2
  35. package/src/cli/tiny-models-cli.ts +12 -5
  36. package/src/edit/file-snapshot-store.ts +5 -2
  37. package/src/edit/renderer.ts +0 -5
  38. package/src/extensibility/custom-tools/types.ts +2 -0
  39. package/src/extensibility/shared-events.ts +9 -1
  40. package/src/hindsight/content.ts +31 -0
  41. package/src/internal-urls/artifact-protocol.ts +97 -53
  42. package/src/internal-urls/types.ts +10 -0
  43. package/src/lsp/config.ts +15 -0
  44. package/src/lsp/edits.ts +28 -7
  45. package/src/lsp/index.ts +46 -4
  46. package/src/mcp/oauth-discovery.ts +88 -18
  47. package/src/mnemopi/state.ts +26 -2
  48. package/src/modes/components/assistant-message.ts +134 -82
  49. package/src/modes/components/bash-execution.ts +9 -0
  50. package/src/modes/components/chat-transcript-builder.ts +8 -4
  51. package/src/modes/components/eval-execution.ts +9 -0
  52. package/src/modes/components/login-dialog.ts +16 -2
  53. package/src/modes/components/mcp-add-wizard.ts +9 -1
  54. package/src/modes/components/tool-execution.ts +4 -50
  55. package/src/modes/components/transcript-container.ts +82 -458
  56. package/src/modes/components/tree-selector.ts +9 -3
  57. package/src/modes/controllers/command-controller.ts +0 -3
  58. package/src/modes/controllers/event-controller.ts +74 -14
  59. package/src/modes/controllers/extension-ui-controller.ts +0 -1
  60. package/src/modes/controllers/input-controller.ts +4 -10
  61. package/src/modes/controllers/mcp-command-controller.ts +106 -29
  62. package/src/modes/controllers/selector-controller.ts +9 -1
  63. package/src/modes/interactive-mode.ts +12 -8
  64. package/src/modes/rpc/rpc-client.ts +8 -4
  65. package/src/modes/rpc/rpc-mode.ts +1 -0
  66. package/src/modes/rpc/rpc-types.ts +13 -1
  67. package/src/modes/setup-wizard/scenes/sign-in.ts +18 -0
  68. package/src/modes/utils/transcript-render-helpers.ts +40 -13
  69. package/src/modes/utils/ui-helpers.ts +12 -7
  70. package/src/prompts/tools/read.md +1 -1
  71. package/src/sdk.ts +1 -0
  72. package/src/session/agent-session.ts +148 -1
  73. package/src/session/session-context.ts +7 -0
  74. package/src/subprocess/worker-runtime.ts +219 -2
  75. package/src/task/worktree.ts +28 -6
  76. package/src/tiny/worker.ts +14 -4
  77. package/src/tools/bash-skill-urls.ts +3 -3
  78. package/src/tools/bash.ts +0 -4
  79. package/src/tools/browser/tab-supervisor.ts +47 -3
  80. package/src/tools/eval-render.ts +0 -20
  81. package/src/tools/grep.ts +19 -2
  82. package/src/tools/path-utils.ts +4 -0
  83. package/src/tools/read.ts +261 -21
  84. package/src/tools/renderers.ts +0 -20
  85. package/src/tools/ssh.ts +0 -16
  86. package/src/tools/write.ts +13 -6
  87. package/src/utils/git.ts +20 -0
  88. package/src/utils/open.ts +51 -6
@@ -855,18 +855,40 @@ export async function mergeTaskBranches(
855
855
  try {
856
856
  const target = baseSha ? `${baseSha}..${branchName}` : branchName;
857
857
  await git.cherryPick(repoRoot, target);
858
- } catch (err) {
858
+ } catch (initialErr) {
859
+ // Empty cherry-picks are not conflicts: a commit whose net
860
+ // effect is already on HEAD (redundant change, or 3-way
861
+ // merge auto-resolved to HEAD) leaves the sequencer stopped
862
+ // with a "The previous cherry-pick is now empty" message.
863
+ // Advance past every consecutive empty with `--skip` so the
864
+ // remaining non-redundant commits in the range still land.
865
+ // A genuine conflict (unmerged files, no "now empty"
866
+ // message) falls through to the abort path below.
867
+ let cursor: unknown = initialErr;
868
+ while (git.cherryPick.isEmptyError(cursor)) {
869
+ try {
870
+ await git.cherryPick.skip(repoRoot);
871
+ cursor = undefined;
872
+ break;
873
+ } catch (skipErr) {
874
+ cursor = skipErr;
875
+ }
876
+ }
877
+ if (cursor === undefined) {
878
+ merged.push(branchName);
879
+ continue;
880
+ }
859
881
  try {
860
882
  await git.cherryPick.abort(repoRoot);
861
883
  } catch {
862
884
  /* no state to abort */
863
885
  }
864
886
  const stderr =
865
- err instanceof git.GitCommandError
866
- ? err.result.stderr.trim()
867
- : err instanceof Error
868
- ? err.message
869
- : String(err);
887
+ cursor instanceof git.GitCommandError
888
+ ? cursor.result.stderr.trim()
889
+ : cursor instanceof Error
890
+ ? cursor.message
891
+ : String(cursor);
870
892
  failed.push(branchName);
871
893
  conflictResult = {
872
894
  merged,
@@ -10,12 +10,14 @@ import tinyTitleSystemPrompt from "../prompts/system/tiny-title-system.md" with
10
10
  import {
11
11
  errorMessage,
12
12
  errorText,
13
+ formatOnnxRuntimeCudaDiagnostics,
13
14
  getTransformersVersionSpec,
14
15
  loadTransformersRuntime,
15
16
  MemoizedRuntime,
16
17
  replayCachedReady,
17
18
  sendLog,
18
19
  sendProgress,
20
+ type TransformersRuntimeMetadata,
19
21
  } from "../subprocess/worker-runtime";
20
22
  import { resolveTinyModelDevicePreference, type TinyModelDevice, tinyModelDeviceLoadOrder } from "./device";
21
23
  import { resolveTinyModelDtypeOverride, type TinyModelDtype } from "./dtype";
@@ -39,7 +41,7 @@ const TINY_TITLE_SYSTEM_PROMPT = prompt.render(tinyTitleSystemPrompt);
39
41
  const tinyModelDevicePreference = resolveTinyModelDevicePreference();
40
42
  const tinyModelDtypeOverride = resolveTinyModelDtypeOverride();
41
43
 
42
- interface TransformersRuntime {
44
+ interface TransformersRuntime extends TransformersRuntimeMetadata {
43
45
  env: {
44
46
  cacheDir?: string;
45
47
  allowLocalModels?: boolean;
@@ -136,6 +138,7 @@ async function loadPipelineWithDeviceFallback(
136
138
  device: devices[0],
137
139
  });
138
140
  }
141
+ let cudaDiagnostics: string | null = null;
139
142
  for (let i = 0; i < devices.length; i += 1) {
140
143
  const device = devices[i]!;
141
144
  try {
@@ -144,15 +147,22 @@ async function loadPipelineWithDeviceFallback(
144
147
  device,
145
148
  };
146
149
  } catch (error) {
147
- if (i === devices.length - 1) throw error;
150
+ const deviceDiagnostics = await formatOnnxRuntimeCudaDiagnostics(transformers, device, error);
151
+ if (deviceDiagnostics) cudaDiagnostics = deviceDiagnostics;
152
+ if (i === devices.length - 1) {
153
+ if (cudaDiagnostics) throw new Error(`${errorText(error)}\n${cudaDiagnostics}`);
154
+ throw error;
155
+ }
148
156
  const fallbackDevice = devices[i + 1]!;
149
- sendLog(transport, "warn", "tiny-model: accelerated device failed; falling back", {
157
+ const meta: Record<string, unknown> = {
150
158
  modelKey,
151
159
  repo: spec.repo,
152
160
  device,
153
161
  fallbackDevice,
154
162
  error: errorMessage(error),
155
- });
163
+ };
164
+ if (deviceDiagnostics) meta.cudaDiagnostics = deviceDiagnostics;
165
+ sendLog(transport, "warn", "tiny-model: accelerated device failed; falling back", meta);
156
166
  }
157
167
  }
158
168
  throw new Error("No tiny model devices configured");
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import type { Skill } from "../extensibility/skills";
4
4
  import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
5
5
  import { validateRelativePath } from "../internal-urls/skill-protocol";
6
- import type { InternalResource } from "../internal-urls/types";
6
+ import type { InternalResource, ResolveContext } from "../internal-urls/types";
7
7
  import { normalizeLocalScheme } from "./path-utils";
8
8
  import { ToolError } from "./tool-errors";
9
9
 
@@ -19,7 +19,7 @@ type SupportedInternalScheme = (typeof SUPPORTED_INTERNAL_SCHEMES)[number];
19
19
 
20
20
  interface InternalUrlResolver {
21
21
  canHandle(input: string): boolean;
22
- resolve(input: string): Promise<InternalResource>;
22
+ resolve(input: string, context?: ResolveContext): Promise<InternalResource>;
23
23
  }
24
24
 
25
25
  export interface InternalUrlExpansionOptions {
@@ -184,7 +184,7 @@ async function resolveInternalUrlToPath(
184
184
 
185
185
  let resource: InternalResource;
186
186
  try {
187
- resource = await internalRouter.resolve(url);
187
+ resource = await internalRouter.resolve(url, { pathOnly: true });
188
188
  } catch (error) {
189
189
  const message = error instanceof Error ? error.message : String(error);
190
190
  throw new ToolError(`Failed to resolve ${scheme}:// URL in bash command: ${url}\n${message}`);
package/src/tools/bash.ts CHANGED
@@ -1409,10 +1409,6 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
1409
1409
  },
1410
1410
  mergeCallAndResult: true,
1411
1411
  inline: true,
1412
- // Collapsed pending preview caps the command to a viewport-sized tail
1413
- // window that shifts while args stream. Expanded output is top-anchored
1414
- // enough for the transcript to commit its settled prefix.
1415
- provisionalPendingPreview: "collapsed",
1416
1412
  };
1417
1413
  }
1418
1414
 
@@ -48,6 +48,16 @@ export interface PendingRun {
48
48
  session: ToolSession;
49
49
  signal?: AbortSignal;
50
50
  toolCalls: Map<string, AbortController>;
51
+ /**
52
+ * Fires when `releaseTab` closes the tab out from under an in-flight run
53
+ * (sibling `browser close --all`, session-scoped reap, etc.). Composed
54
+ * into the cmux run's signal so `wait(...)`, cmux socket calls, and the
55
+ * facade proxies unwind promptly instead of blocking to the run's
56
+ * timeout. `pending.reject` still fires first so the awaiting caller
57
+ * sees the tab-close error immediately; `closeAc` propagates the
58
+ * cancellation into the still-running `runCmuxCode` body (issue #4499).
59
+ */
60
+ closeAc?: AbortController;
51
61
  }
52
62
 
53
63
  interface TabSessionBase<TBrowser extends BrowserHandle = BrowserHandle> {
@@ -380,23 +390,47 @@ async function runInTabWithSnapshot(
380
390
  if (tab.pending.size > 0) throw new ToolError(`Tab ${JSON.stringify(name)} is busy`);
381
391
  const id = Snowflake.next();
382
392
  const { promise, resolve, reject } = Promise.withResolvers<RunResultOk>();
393
+ // `releaseTab` calls `pending.reject(closeError)` when the tab dies
394
+ // out from under an in-flight run (sibling `browser close --all`,
395
+ // session-scoped reap, etc.). Both backends below MUST end up awaiting
396
+ // this same `promise` so:
397
+ // 1. The caller sees `Tab ... was closed` immediately instead of
398
+ // blocking to the run's timeout, and
399
+ // 2. `reject(...)` always has an attached handler — a zero-consumer
400
+ // rejection would fire `unhandledRejection` and the CLI's
401
+ // top-level handler would tear the whole session down, killing
402
+ // every other tab and subagent sharing the process (issue #4499).
403
+ // The cmux branch also composes `closeAc.signal` into the run's abort
404
+ // signal so `wait(...)`, cmux socket calls, and the facade proxies
405
+ // unwind promptly when the tab is closed — otherwise a `wait(60_000)`
406
+ // with no in-flight socket request would keep `runCmuxCode` blocked
407
+ // until timeout even after the tab is gone.
408
+ const closeAc = new AbortController();
383
409
  const pending: PendingRun = {
384
410
  resolve,
385
411
  reject,
386
412
  session: opts.session ?? ({} as ToolSession),
387
413
  signal: opts.signal,
388
414
  toolCalls: new Map(),
415
+ closeAc,
389
416
  };
390
417
  tab.pending.set(id, pending);
391
418
  if (tab.backend === "cmux") {
419
+ const runSignal = opts.signal ? AbortSignal.any([opts.signal, closeAc.signal]) : closeAc.signal;
392
420
  try {
393
- return await runCmuxCode(tab.cmuxTab, {
421
+ // `runCmuxCode.then(resolve, reject)` publishes the run's real
422
+ // outcome to `promise`, but `releaseTab` may have already
423
+ // rejected it — `Promise.withResolvers` settles on the first
424
+ // call and later resolve/reject are no-ops, so the tab-close
425
+ // error still wins the race.
426
+ runCmuxCode(tab.cmuxTab, {
394
427
  code: opts.code,
395
428
  timeoutMs: opts.timeoutMs,
396
- signal: opts.signal,
429
+ signal: runSignal,
397
430
  session: pending.session,
398
431
  snapshot,
399
- });
432
+ }).then(resolve, reject);
433
+ return await promise;
400
434
  } finally {
401
435
  tab.pending.delete(id);
402
436
  }
@@ -460,6 +494,16 @@ export async function releaseTab(name: string, opts: ReleaseTabOptions = {}): Pr
460
494
  } catch {}
461
495
  }
462
496
  for (const ctrl of pending.toolCalls.values()) ctrl.abort(closeError);
497
+ // Propagate the closure into the cmux run's abort signal so
498
+ // `wait(...)`, in-flight cmux socket calls, and the facade proxies
499
+ // unwind promptly. Firing this BEFORE `pending.reject` means
500
+ // `runCmuxCode` finishes with `ToolAbortError` and its `.then(reject)`
501
+ // is a no-op — `promise` still settles with the tab-close error via
502
+ // the `reject` call below. Without it, a run that isn't currently
503
+ // making a socket request (e.g. `await wait(60_000)`) would keep
504
+ // `runCmuxCode` blocked until timeout even after `pending.reject`
505
+ // unblocked the caller (issue #4499 review feedback).
506
+ pending.closeAc?.abort(closeError);
463
507
  pending.reject(closeError);
464
508
  }
465
509
  tab.pending.clear();
@@ -773,24 +773,4 @@ export const evalToolRenderer = {
773
773
 
774
774
  mergeCallAndResult: true,
775
775
  inline: true,
776
- // Collapsed pending preview shows tail-window code cells; the result render
777
- // interleaves each cell's output under its code, re-laying-out every row
778
- // below the first cell. Expanded output is top-anchored enough for the
779
- // transcript to commit its settled prefix.
780
- provisionalPendingPreview: "collapsed",
781
- // Partial-result chrome is NOT byte-stable: `renderAgentProgressEvents`
782
- // inserts/removes each subagent's current-tool line as it starts/stops a
783
- // tool, and ticks status icon/stats/duration on already-rendered rows,
784
- // while `options.isPartial` holds for the whole eval() cell (agent
785
- // progress ticks never carry an `async` completed/failed state, so
786
- // `event-controller.ts` keeps `isPartial: true` throughout). If this
787
- // block were commit-stable during that churn, `deriveLiveCommitState`'s
788
- // stable-prefix ratchet would promote agent rows that keep mutating (a
789
- // "slow ticker", see transcript-container.ts) into native scrollback,
790
- // and the tui resync's "duplication, never loss" contract would
791
- // repeatedly re-show the frame tail under a heavy concurrent
792
- // `agent()`/`parallel()` fan-out — the overlapping/duplicated tree rows
793
- // seen when many subagents run at once. Once the cell settles
794
- // (`isPartial === false`) the block is commit-stable again.
795
- provisionalPartialResult: true,
796
776
  };
package/src/tools/grep.ts CHANGED
@@ -750,6 +750,11 @@ async function resolveInternalSearchInputs(opts: {
750
750
  localProtocolOptions: opts.localProtocolOptions,
751
751
  skills: opts.skills,
752
752
  skipDirectoryListing: true,
753
+ // Try path-only first so large artifacts (and any other handler that
754
+ // separates path from content) resolve without materializing bytes.
755
+ // Handlers that ignore the flag still return content, and virtual
756
+ // resources without a sourcePath fall through to a second resolve.
757
+ pathOnly: true,
753
758
  };
754
759
 
755
760
  for (let idx = 0; idx < paths.length; idx++) {
@@ -764,7 +769,7 @@ async function resolveInternalSearchInputs(opts: {
764
769
  if (hasGlobPathChars(globTarget)) {
765
770
  throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
766
771
  }
767
- const resource = await internalRouter.resolve(rawPath, context);
772
+ let resource = await internalRouter.resolve(rawPath, context);
768
773
  // A directory listing with no backing local path (e.g. a remote ssh:// dir)
769
774
  // has no real contents to grep — searching its listing text would be
770
775
  // misleading. Local/skill/vault dir resources set `sourcePath` and skip this.
@@ -781,8 +786,20 @@ async function resolveInternalSearchInputs(opts: {
781
786
  continue;
782
787
  }
783
788
 
789
+ // No sourcePath: this handler needs its content materialized so the
790
+ // virtual expansion can search it. Re-resolve without pathOnly.
791
+ if (context.pathOnly) {
792
+ resource = await internalRouter.resolve(rawPath, { ...context, pathOnly: false });
793
+ }
794
+
784
795
  const ranges = opts.pathSpecs[idx]?.ranges;
785
- const expanded = await expandVirtualInternalResource(rawPath, resource, internalRouter, context, ranges);
796
+ const expanded = await expandVirtualInternalResource(
797
+ rawPath,
798
+ resource,
799
+ internalRouter,
800
+ { ...context, pathOnly: false },
801
+ ranges,
802
+ );
786
803
  virtualInputIndexes.add(idx);
787
804
  for (const virtual of expanded) {
788
805
  virtualResources.push(virtual);
@@ -1201,6 +1201,10 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
1201
1201
  signal: opts.signal,
1202
1202
  localProtocolOptions: opts.localProtocolOptions,
1203
1203
  skills: opts.skills,
1204
+ // Tool-scope resolution only needs `sourcePath`; skip content
1205
+ // materialization so large artifacts (or any handler that separates
1206
+ // path from content) stay searchable without OOM risk.
1207
+ pathOnly: true,
1204
1208
  });
1205
1209
  if (!resource.sourcePath) {
1206
1210
  throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);