@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.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 (143) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/dist/cli.js +3616 -3676
  3. package/dist/types/advisor/advise-tool.d.ts +3 -0
  4. package/dist/types/advisor/runtime.d.ts +7 -1
  5. package/dist/types/async/job-manager.d.ts +2 -0
  6. package/dist/types/config/model-registry.d.ts +7 -0
  7. package/dist/types/config/model-resolver.d.ts +8 -0
  8. package/dist/types/config/models-config-schema.d.ts +10 -0
  9. package/dist/types/config/models-config.d.ts +6 -0
  10. package/dist/types/dap/client.d.ts +10 -0
  11. package/dist/types/dap/types.d.ts +6 -5
  12. package/dist/types/exec/bash-executor.d.ts +1 -0
  13. package/dist/types/extensibility/extensions/load-errors.d.ts +3 -0
  14. package/dist/types/extensibility/extensions/runner.d.ts +4 -2
  15. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
  16. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
  17. package/dist/types/launch/spawn-options.d.ts +10 -0
  18. package/dist/types/launch/spawn-options.test.d.ts +1 -0
  19. package/dist/types/modes/components/status-line/component.d.ts +10 -3
  20. package/dist/types/modes/components/status-line/types.d.ts +3 -1
  21. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  22. package/dist/types/modes/components/transcript-container.d.ts +4 -3
  23. package/dist/types/modes/components/tree-selector.d.ts +6 -2
  24. package/dist/types/modes/components/usage-row.d.ts +1 -1
  25. package/dist/types/modes/interactive-mode.d.ts +2 -0
  26. package/dist/types/modes/print-mode.d.ts +4 -0
  27. package/dist/types/modes/rpc/rpc-client.d.ts +1 -1
  28. package/dist/types/modes/rpc/rpc-frame.d.ts +9 -0
  29. package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
  30. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
  31. package/dist/types/modes/setup-wizard/index.d.ts +1 -1
  32. package/dist/types/modes/setup-wizard/scenes/model.d.ts +3 -0
  33. package/dist/types/modes/types.d.ts +2 -0
  34. package/dist/types/sdk.d.ts +2 -0
  35. package/dist/types/session/agent-session.d.ts +42 -6
  36. package/dist/types/session/messages.d.ts +6 -0
  37. package/dist/types/session/session-paths.d.ts +21 -4
  38. package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
  39. package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
  40. package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
  41. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  42. package/dist/types/tools/gh.d.ts +5 -3
  43. package/dist/types/tools/hub/index.d.ts +1 -1
  44. package/dist/types/tools/hub/jobs.d.ts +1 -0
  45. package/dist/types/tools/hub/types.d.ts +2 -0
  46. package/dist/types/tools/tool-timeouts.d.ts +1 -1
  47. package/dist/types/tools/xdev.d.ts +5 -3
  48. package/dist/types/utils/git.d.ts +33 -0
  49. package/dist/types/vibe/__tests__/token-rate.test.d.ts +1 -0
  50. package/dist/types/vibe/runtime.d.ts +23 -0
  51. package/dist/types/web/search/providers/codex.d.ts +1 -1
  52. package/package.json +12 -12
  53. package/src/advisor/__tests__/advisor.test.ts +150 -0
  54. package/src/advisor/advise-tool.ts +4 -0
  55. package/src/advisor/runtime.ts +38 -14
  56. package/src/async/job-manager.ts +3 -0
  57. package/src/cli/bench-cli.ts +8 -2
  58. package/src/cli/dry-balance-cli.ts +1 -0
  59. package/src/config/model-registry.ts +89 -8
  60. package/src/config/model-resolver.ts +78 -14
  61. package/src/config/models-config-schema.ts +1 -0
  62. package/src/config/settings.ts +3 -1
  63. package/src/dap/client.ts +168 -1
  64. package/src/dap/config.ts +51 -1
  65. package/src/dap/session.ts +575 -234
  66. package/src/dap/types.ts +6 -5
  67. package/src/discovery/agents.ts +2 -2
  68. package/src/exec/bash-executor.ts +68 -8
  69. package/src/extensibility/extensions/load-errors.ts +13 -0
  70. package/src/extensibility/extensions/runner.ts +6 -4
  71. package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
  72. package/src/extensibility/plugins/legacy-pi-compat.ts +41 -6
  73. package/src/launch/broker.ts +34 -31
  74. package/src/launch/client.ts +7 -1
  75. package/src/launch/spawn-options.test.ts +31 -0
  76. package/src/launch/spawn-options.ts +17 -0
  77. package/src/lsp/types.ts +5 -1
  78. package/src/main.ts +25 -4
  79. package/src/mcp/transports/stdio.test.ts +9 -1
  80. package/src/modes/components/ask-dialog.ts +137 -73
  81. package/src/modes/components/chat-transcript-builder.ts +10 -1
  82. package/src/modes/components/status-line/component.ts +57 -3
  83. package/src/modes/components/status-line/segments.ts +20 -3
  84. package/src/modes/components/status-line/types.ts +3 -1
  85. package/src/modes/components/tool-execution.ts +5 -0
  86. package/src/modes/components/transcript-container.ts +23 -122
  87. package/src/modes/components/tree-selector.ts +10 -4
  88. package/src/modes/components/usage-row.ts +17 -1
  89. package/src/modes/controllers/command-controller.ts +41 -1
  90. package/src/modes/controllers/event-controller.ts +7 -3
  91. package/src/modes/controllers/input-controller.ts +1 -1
  92. package/src/modes/controllers/selector-controller.ts +29 -8
  93. package/src/modes/interactive-mode.ts +102 -60
  94. package/src/modes/noninteractive-dispose.test.ts +14 -1
  95. package/src/modes/print-mode.ts +81 -9
  96. package/src/modes/rpc/rpc-client.ts +76 -35
  97. package/src/modes/rpc/rpc-frame.ts +156 -0
  98. package/src/modes/rpc/rpc-input.ts +38 -0
  99. package/src/modes/rpc/rpc-mode.ts +11 -4
  100. package/src/modes/setup-wizard/index.ts +2 -0
  101. package/src/modes/setup-wizard/scenes/model.ts +134 -0
  102. package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
  103. package/src/modes/types.ts +2 -0
  104. package/src/modes/utils/ui-helpers.ts +9 -3
  105. package/src/prompts/system/workflow-notice.md +89 -74
  106. package/src/prompts/tools/browser.md +3 -2
  107. package/src/prompts/tools/debug.md +2 -7
  108. package/src/prompts/tools/github.md +6 -1
  109. package/src/prompts/tools/hub.md +4 -2
  110. package/src/prompts/tools/task-async-contract.md +1 -0
  111. package/src/prompts/tools/task.md +9 -2
  112. package/src/sdk.ts +38 -6
  113. package/src/session/agent-session.ts +484 -188
  114. package/src/session/messages.test.ts +91 -0
  115. package/src/session/messages.ts +248 -110
  116. package/src/session/session-manager.ts +34 -3
  117. package/src/session/session-paths.ts +38 -9
  118. package/src/slash-commands/builtin-registry.ts +1 -0
  119. package/src/task/executor.ts +104 -33
  120. package/src/task/index.ts +46 -9
  121. package/src/task/worktree.ts +10 -0
  122. package/src/tools/browser/aria/aria-snapshot.ts +36 -8
  123. package/src/tools/browser/cmux/cmux-tab.ts +2 -1
  124. package/src/tools/browser/cmux/socket-client.ts +139 -3
  125. package/src/tools/browser/run-cancellation.ts +4 -0
  126. package/src/tools/browser/tab-protocol.ts +2 -0
  127. package/src/tools/browser/tab-supervisor.ts +21 -11
  128. package/src/tools/browser/tab-worker.ts +199 -33
  129. package/src/tools/debug.ts +3 -0
  130. package/src/tools/gh.ts +40 -2
  131. package/src/tools/hub/index.ts +4 -1
  132. package/src/tools/hub/jobs.ts +42 -1
  133. package/src/tools/hub/types.ts +2 -0
  134. package/src/tools/tool-timeouts.ts +1 -1
  135. package/src/tools/xdev.ts +11 -4
  136. package/src/tools/yield.ts +4 -1
  137. package/src/utils/git.ts +237 -0
  138. package/src/{modes/components/status-line → utils}/token-rate.ts +6 -0
  139. package/src/vibe/__tests__/token-rate.test.ts +96 -0
  140. package/src/vibe/runtime.ts +50 -0
  141. package/src/web/search/index.ts +9 -5
  142. package/src/web/search/providers/codex.ts +195 -99
  143. /package/dist/types/{modes/components/status-line → utils}/token-rate.d.ts +0 -0
package/src/dap/client.ts CHANGED
@@ -55,6 +55,8 @@ export class DapClient {
55
55
  readonly adapter: DapResolvedAdapter;
56
56
  readonly cwd: string;
57
57
  readonly proc: DapClientState["proc"];
58
+ /** TCP server port reused by child DAP sessions. */
59
+ readonly port?: number;
58
60
  /** ReadableStream of DAP bytes — from proc.stdout (stdio) or a socket (socket mode). */
59
61
  readonly #readable: ReadableStream<Uint8Array>;
60
62
  /** Write sink — proc.stdin (stdio) or a socket (socket mode). */
@@ -78,7 +80,12 @@ export class DapClient {
78
80
  adapter: DapResolvedAdapter,
79
81
  cwd: string,
80
82
  proc: DapClientState["proc"],
81
- options?: { readable?: ReadableStream<Uint8Array>; writeSink?: DapWriteSink; socket?: { end(): void } },
83
+ options?: {
84
+ readable?: ReadableStream<Uint8Array>;
85
+ writeSink?: DapWriteSink;
86
+ socket?: { end(): void };
87
+ port?: number;
88
+ },
82
89
  ) {
83
90
  this.adapter = adapter;
84
91
  this.cwd = cwd;
@@ -86,6 +93,7 @@ export class DapClient {
86
93
  this.#readable = options?.readable ?? (proc.stdout as ReadableStream<Uint8Array>);
87
94
  this.#writeSink = options?.writeSink ?? proc.stdin;
88
95
  this.#socket = options?.socket;
96
+ this.port = options?.port;
89
97
  this.proc.exited.then(
90
98
  () => this.#rejectPendingWritesForExit(),
91
99
  () => this.#rejectPendingWritesForExit(),
@@ -96,6 +104,9 @@ export class DapClient {
96
104
  if (adapter.connectMode === "socket") {
97
105
  return DapClient.#spawnSocket({ adapter, cwd, socketReadyTimeoutMs });
98
106
  }
107
+ if (adapter.connectMode === "tcp") {
108
+ return DapClient.#spawnTcp({ adapter, cwd, socketReadyTimeoutMs });
109
+ }
99
110
  // Merge non-interactive env and start in a new session (detached → setsid)
100
111
  // so the adapter process tree has no controlling terminal. Without this,
101
112
  // debuggee children can reach /dev/tty and trigger SIGTTIN, suspending
@@ -118,6 +129,85 @@ export class DapClient {
118
129
  return client;
119
130
  }
120
131
 
132
+ /** Connect to another session on an existing TCP DAP server. */
133
+ static async connect({
134
+ adapter,
135
+ cwd,
136
+ host,
137
+ port,
138
+ }: {
139
+ adapter: DapResolvedAdapter;
140
+ cwd: string;
141
+ host: string;
142
+ port: number;
143
+ }): Promise<DapClient> {
144
+ const exited = Promise.withResolvers<void>();
145
+ const { readable, writeSink, socket } = await connectTcpSocket(host, port, () => exited.resolve());
146
+ const proc = {
147
+ exited: exited.promise,
148
+ exitCode: null,
149
+ stdin: { write: () => 0, flush: () => undefined },
150
+ stdout: new ReadableStream<Uint8Array>(),
151
+ stderr: new ReadableStream<Uint8Array>(),
152
+ peekStderr: () => "",
153
+ kill: () => {
154
+ exited.resolve();
155
+ return true;
156
+ },
157
+ } as unknown as DapClientState["proc"];
158
+ const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket, port });
159
+ exited.promise.then(() => client.#handleProcessExit());
160
+ void client.#startMessageReader();
161
+ return client;
162
+ }
163
+
164
+ /** Spawn an adapter that listens on a caller-selected TCP port. */
165
+ static async #spawnTcp({ adapter, cwd, socketReadyTimeoutMs }: DapSpawnOptions): Promise<DapClient> {
166
+ const host = "127.0.0.1";
167
+ const reservation = Bun.listen({
168
+ hostname: host,
169
+ port: 0,
170
+ socket: {
171
+ open() {},
172
+ data() {},
173
+ close() {},
174
+ error() {},
175
+ },
176
+ });
177
+ const port = reservation.port;
178
+ reservation.stop(true);
179
+ const args = adapter.args.map(arg => arg.replaceAll("$" + "{port}", String(port)));
180
+ const proc = ptree.spawn([adapter.resolvedCommand, ...args], {
181
+ cwd,
182
+ stdin: "pipe",
183
+ env: {
184
+ ...Bun.env,
185
+ ...NON_INTERACTIVE_ENV,
186
+ },
187
+ detached: true,
188
+ });
189
+
190
+ try {
191
+ const { readable, writeSink, socket } = await waitForTcpTransport(
192
+ host,
193
+ port,
194
+ socketReadyTimeoutMs ?? SOCKET_READY_TIMEOUT_MS,
195
+ proc,
196
+ );
197
+ const client = new DapClient(adapter, cwd, proc, { readable, writeSink, socket, port });
198
+ proc.exited.then(() => client.#handleProcessExit());
199
+ void client.#startMessageReader();
200
+ return client;
201
+ } catch (error) {
202
+ try {
203
+ proc.kill();
204
+ } catch {
205
+ /* proc may already be dead */
206
+ }
207
+ throw error;
208
+ }
209
+ }
210
+
121
211
  /**
122
212
  * Spawn a socket-mode adapter (e.g. dlv).
123
213
  * Linux: connect to a unix domain socket via --listen=unix:<path>
@@ -659,6 +749,83 @@ async function waitForCondition(
659
749
  throw new Error(`Socket not ready after ${timeoutMs}ms`);
660
750
  }
661
751
 
752
+ /** Connect once to a TCP DAP server. */
753
+ async function connectTcpSocket(host: string, port: number, onClose?: () => void): Promise<SocketTransport> {
754
+ const { promise, resolve, reject } = Promise.withResolvers<SocketTransport>();
755
+ let streamController: ReadableStreamDefaultController<Uint8Array>;
756
+ let opened = false;
757
+ const readable = new ReadableStream<Uint8Array>({
758
+ start(controller) {
759
+ streamController = controller;
760
+ },
761
+ });
762
+
763
+ void Bun.connect({
764
+ hostname: host,
765
+ port,
766
+ socket: {
767
+ open(socket) {
768
+ opened = true;
769
+ resolve({
770
+ readable,
771
+ writeSink: socketToSink(socket),
772
+ socket,
773
+ });
774
+ },
775
+ data(_socket, data) {
776
+ streamController.enqueue(new Uint8Array(data));
777
+ },
778
+ close() {
779
+ onClose?.();
780
+ if (!opened) {
781
+ reject(new Error(`Connection to TCP port ${host}:${port} closed before opening`));
782
+ }
783
+ try {
784
+ streamController.close();
785
+ } catch {
786
+ /* already closed */
787
+ }
788
+ },
789
+ error(_socket, error) {
790
+ onClose?.();
791
+ if (!opened) {
792
+ reject(error);
793
+ }
794
+ try {
795
+ streamController.error(error);
796
+ } catch {
797
+ /* already closed */
798
+ }
799
+ },
800
+ },
801
+ }).catch(error => {
802
+ onClose?.();
803
+ reject(error);
804
+ });
805
+ return promise;
806
+ }
807
+
808
+ /** Wait for a TCP DAP server and retain the first successful connection. */
809
+ async function waitForTcpTransport(
810
+ host: string,
811
+ port: number,
812
+ timeoutMs: number,
813
+ proc: { exitCode: number | null },
814
+ ): Promise<SocketTransport> {
815
+ const deadline = Date.now() + timeoutMs;
816
+ while (Date.now() < deadline) {
817
+ if (proc.exitCode !== null) {
818
+ throw new Error(`Adapter process exited before TCP port ${host}:${port} was ready`);
819
+ }
820
+ try {
821
+ return await connectTcpSocket(host, port);
822
+ } catch {
823
+ await Bun.sleep(50);
824
+ }
825
+ }
826
+ throw new Error(`TCP port ${host}:${port} was not ready after ${timeoutMs}ms`);
827
+ }
828
+
662
829
  interface SocketTransport {
663
830
  readable: ReadableStream<Uint8Array>;
664
831
  writeSink: DapWriteSink;
package/src/dap/config.ts CHANGED
@@ -10,6 +10,8 @@ import DEFAULTS from "./defaults.json" with { type: "json" };
10
10
  import type { DapAdapterConfig, DapResolvedAdapter } from "./types";
11
11
 
12
12
  const EXTENSIONLESS_DEBUGGER_ORDER: readonly string[] = ["gdb", "lldb-dap"];
13
+ const JS_DEBUG_SERVER_ENV = "JS_DEBUG_DAP_SERVER";
14
+ const DAP_PORT_ARGUMENT = "$" + "{port}";
13
15
 
14
16
  interface NormalizedConfig {
15
17
  adapters: Record<string, unknown>;
@@ -45,7 +47,7 @@ function normalizeObject(value: unknown): Record<string, unknown> {
45
47
  function normalizeAdapterConfig(config: unknown): DapAdapterConfig | null {
46
48
  if (!isRecord(config)) return null;
47
49
  if (typeof config.command !== "string" || config.command.length === 0) return null;
48
- const connectMode = config.connectMode === "socket" ? ("socket" as const) : undefined;
50
+ const connectMode = config.connectMode === "socket" || config.connectMode === "tcp" ? config.connectMode : undefined;
49
51
  return {
50
52
  command: config.command,
51
53
  args: normalizeStringArray(config.args),
@@ -184,6 +186,52 @@ function normalizeCommandForCwd(command: string, cwd: string): string {
184
186
  return command;
185
187
  }
186
188
 
189
+ function resolveJsDebugServerPath(cwd: string): string | null {
190
+ const configured = process.env[JS_DEBUG_SERVER_ENV];
191
+ const dataHome = process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
192
+ const candidates = [
193
+ ...(configured ? [path.resolve(cwd, configured)] : []),
194
+ path.join(dataHome, "nvim", "mason", "packages", "js-debug-adapter", "js-debug", "src", "dapDebugServer.js"),
195
+ path.join(os.homedir(), ".local", "opt", "js-debug", "src", "dapDebugServer.js"),
196
+ ];
197
+ for (const candidate of candidates) {
198
+ if (fs.existsSync(candidate)) return candidate;
199
+ }
200
+ return null;
201
+ }
202
+
203
+ function resolveDefaultJsDebugAdapter(
204
+ adapterName: string,
205
+ config: DapAdapterConfig,
206
+ cwd: string,
207
+ localRoots?: readonly string[],
208
+ ): DapResolvedAdapter | null | undefined {
209
+ if (adapterName !== "js-debug-adapter" || config.command !== "js-debug-adapter") {
210
+ return undefined;
211
+ }
212
+ const serverPath = resolveJsDebugServerPath(cwd);
213
+ if (!serverPath) return null;
214
+ const nodeCommand = resolveCommand("node", cwd, {
215
+ cache: WhichCachePolicy.Fresh,
216
+ PATH: process.env.PATH,
217
+ localRoots,
218
+ });
219
+ const resolvedCommand = nodeCommand ?? process.execPath;
220
+ return {
221
+ name: adapterName,
222
+ command: nodeCommand ? "node" : "bun",
223
+ args: [serverPath, DAP_PORT_ARGUMENT, "127.0.0.1"],
224
+ resolvedCommand,
225
+ languages: config.languages ?? [],
226
+ fileTypes: config.fileTypes ?? [],
227
+ rootMarkers: config.rootMarkers ?? [],
228
+ launchDefaults: config.launchDefaults ?? {},
229
+ attachDefaults: config.attachDefaults ?? {},
230
+ connectMode: "tcp",
231
+ acceptsDirectoryProgram: config.acceptsDirectoryProgram === true,
232
+ };
233
+ }
234
+
187
235
  function resolveAdapterFromConfig(
188
236
  adapterName: string,
189
237
  configs: Record<string, DapAdapterConfig>,
@@ -192,6 +240,8 @@ function resolveAdapterFromConfig(
192
240
  ): DapResolvedAdapter | null {
193
241
  const config = configs[adapterName];
194
242
  if (!config) return null;
243
+ const jsDebugAdapter = resolveDefaultJsDebugAdapter(adapterName, config, cwd, localRoots);
244
+ if (jsDebugAdapter !== undefined) return jsDebugAdapter;
195
245
  const normalizedCommand = normalizeCommandForCwd(config.command, cwd);
196
246
  const commandIsBare =
197
247
  !path.isAbsolute(config.command) && !config.command.includes("/") && !config.command.includes("\\");