@oh-my-pi/pi-coding-agent 15.4.3 → 15.5.1
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.
- package/CHANGELOG.md +81 -5
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-broker-cli.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +8 -0
- package/dist/types/config/settings-schema.d.ts +42 -1
- package/dist/types/edit/index.d.ts +2 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -2
- package/dist/types/extensibility/hooks/types.d.ts +4 -0
- package/dist/types/hashline/executor.d.ts +6 -3
- package/dist/types/lsp/index.d.ts +9 -1
- package/dist/types/mcp/client.d.ts +2 -1
- package/dist/types/mcp/oauth-discovery.d.ts +4 -3
- package/dist/types/mcp/timeout.d.ts +9 -0
- package/dist/types/mcp/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/streaming-output.d.ts +1 -1
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/approval.d.ts +46 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/ast-edit.d.ts +2 -0
- package/dist/types/tools/ast-grep.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +11 -1
- package/dist/types/tools/browser.d.ts +2 -0
- package/dist/types/tools/calculator.d.ts +1 -0
- package/dist/types/tools/checkpoint.d.ts +2 -0
- package/dist/types/tools/debug.d.ts +9 -1
- package/dist/types/tools/eval.d.ts +2 -0
- package/dist/types/tools/find.d.ts +10 -0
- package/dist/types/tools/gh.d.ts +2 -1
- package/dist/types/tools/hindsight-recall.d.ts +1 -0
- package/dist/types/tools/hindsight-reflect.d.ts +1 -0
- package/dist/types/tools/hindsight-retain.d.ts +1 -0
- package/dist/types/tools/inspect-image.d.ts +1 -0
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/read.d.ts +1 -0
- package/dist/types/tools/recipe/index.d.ts +1 -0
- package/dist/types/tools/render-mermaid.d.ts +1 -0
- package/dist/types/tools/resolve.d.ts +1 -0
- package/dist/types/tools/search-tool-bm25.d.ts +1 -0
- package/dist/types/tools/search.d.ts +1 -0
- package/dist/types/tools/ssh.d.ts +2 -0
- package/dist/types/tools/todo-write.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tools/yield.d.ts +1 -0
- package/dist/types/web/search/index.d.ts +1 -0
- package/package.json +7 -7
- package/src/cli/args.ts +14 -0
- package/src/cli/auth-broker-cli.ts +171 -22
- package/src/commands/auth-broker.ts +3 -0
- package/src/commands/launch.ts +16 -0
- package/src/config/mcp-schema.json +2 -2
- package/src/config/model-registry.ts +19 -4
- package/src/config/prompt-templates.ts +0 -125
- package/src/config/settings-schema.ts +59 -1
- package/src/config/settings.ts +2 -1
- package/src/dap/session.ts +35 -2
- package/src/discovery/builtin.ts +2 -2
- package/src/discovery/mcp-json.ts +1 -1
- package/src/edit/index.ts +26 -0
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/streaming.ts +12 -2
- package/src/exec/bash-executor.ts +6 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +18 -14
- package/src/extensibility/custom-tools/types.ts +16 -2
- package/src/extensibility/extensions/wrapper.ts +36 -1
- package/src/extensibility/hooks/types.ts +8 -1
- package/src/hashline/apply.ts +47 -2
- package/src/hashline/executor.ts +46 -24
- package/src/internal-urls/docs-index.generated.ts +8 -7
- package/src/lsp/edits.ts +82 -29
- package/src/lsp/index.ts +38 -1
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +6 -0
- package/src/mcp/client.ts +8 -6
- package/src/mcp/oauth-discovery.ts +120 -32
- package/src/mcp/oauth-flow.ts +34 -6
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/transports/http.ts +42 -44
- package/src/mcp/transports/stdio.ts +8 -5
- package/src/mcp/types.ts +1 -1
- package/src/modes/components/hook-editor.ts +11 -3
- package/src/modes/components/mcp-add-wizard.ts +6 -2
- package/src/modes/components/model-selector.ts +33 -11
- package/src/modes/controllers/command-controller.ts +6 -4
- package/src/modes/controllers/mcp-command-controller.ts +8 -4
- package/src/prompts/review-custom-request.md +22 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +2 -3
- package/src/prompts/system/project-prompt.md +4 -0
- package/src/prompts/tools/debug.md +1 -0
- package/src/prompts/tools/find.md +4 -2
- package/src/prompts/tools/hashline.md +43 -93
- package/src/sdk.ts +47 -73
- package/src/session/agent-session.ts +93 -27
- package/src/session/streaming-output.ts +1 -1
- package/src/slash-commands/helpers/usage-report.ts +3 -1
- package/src/task/executor.ts +11 -0
- package/src/task/index.ts +19 -0
- package/src/task/render.ts +12 -2
- package/src/task/types.ts +4 -0
- package/src/tools/approval.ts +185 -0
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-edit.ts +25 -1
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +69 -1
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser.ts +15 -0
- package/src/tools/calculator.ts +1 -0
- package/src/tools/checkpoint.ts +2 -0
- package/src/tools/debug.ts +38 -0
- package/src/tools/eval.ts +15 -0
- package/src/tools/find.ts +17 -8
- package/src/tools/gh.ts +21 -1
- package/src/tools/hindsight-recall.ts +1 -0
- package/src/tools/hindsight-reflect.ts +1 -0
- package/src/tools/hindsight-retain.ts +1 -0
- package/src/tools/image-gen.ts +1 -0
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +1 -0
- package/src/tools/job.ts +1 -0
- package/src/tools/path-utils.ts +14 -1
- package/src/tools/read.ts +1 -0
- package/src/tools/recipe/index.ts +1 -0
- package/src/tools/render-mermaid.ts +1 -0
- package/src/tools/report-tool-issue.ts +1 -0
- package/src/tools/resolve.ts +1 -0
- package/src/tools/review.ts +1 -0
- package/src/tools/search-tool-bm25.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/ssh.ts +8 -0
- package/src/tools/todo-write.ts +1 -0
- package/src/tools/write.ts +12 -1
- package/src/tools/yield.ts +1 -0
- package/src/web/search/index.ts +2 -0
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
MCPTransport,
|
|
17
17
|
} from "../../mcp/types";
|
|
18
18
|
import { toJsonRpcError } from "../../mcp/types";
|
|
19
|
+
import { createMCPTimeout, getNeverAbortSignal, resolveMCPTimeoutMs } from "../timeout";
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* HTTP transport for MCP servers.
|
|
@@ -180,23 +181,18 @@ export class HttpTransport implements MCPTransport {
|
|
|
180
181
|
headers["Mcp-Session-Id"] = this.#sessionId;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
const abortController = new AbortController();
|
|
186
|
-
const timeoutId = setTimeout(() => abortController.abort(), timeout);
|
|
187
|
-
const operationSignal = options?.signal
|
|
188
|
-
? AbortSignal.any([options.signal, abortController.signal])
|
|
189
|
-
: abortController.signal;
|
|
184
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
185
|
+
const operation = createMCPTimeout(timeout, options?.signal);
|
|
190
186
|
|
|
191
187
|
try {
|
|
192
188
|
const response = await fetch(this.config.url, {
|
|
193
189
|
method: "POST",
|
|
194
190
|
headers,
|
|
195
191
|
body: JSON.stringify(body),
|
|
196
|
-
signal:
|
|
192
|
+
signal: operation.signal,
|
|
197
193
|
});
|
|
198
194
|
|
|
199
|
-
|
|
195
|
+
operation.clear();
|
|
200
196
|
|
|
201
197
|
// Check for session ID in response
|
|
202
198
|
const newSessionId = response.headers.get("Mcp-Session-Id");
|
|
@@ -234,11 +230,8 @@ export class HttpTransport implements MCPTransport {
|
|
|
234
230
|
|
|
235
231
|
return result.result as T;
|
|
236
232
|
} catch (error) {
|
|
237
|
-
|
|
238
|
-
if (error
|
|
239
|
-
if (options?.signal?.aborted) {
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
233
|
+
operation.clear();
|
|
234
|
+
if (operation.isTimeoutAbort(error)) {
|
|
242
235
|
throw new Error(`Request timeout after ${timeout}ms`);
|
|
243
236
|
}
|
|
244
237
|
throw error;
|
|
@@ -250,12 +243,9 @@ export class HttpTransport implements MCPTransport {
|
|
|
250
243
|
throw new Error("No response body");
|
|
251
244
|
}
|
|
252
245
|
|
|
253
|
-
const timeout = this.config.timeout
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
const operationSignal = options?.signal
|
|
257
|
-
? AbortSignal.any([options.signal, abortController.signal])
|
|
258
|
-
: abortController.signal;
|
|
246
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
247
|
+
const operation = createMCPTimeout(timeout, options?.signal);
|
|
248
|
+
const signal = operation.signal ?? getNeverAbortSignal();
|
|
259
249
|
|
|
260
250
|
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
261
251
|
let captured = false;
|
|
@@ -268,7 +258,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
268
258
|
// controller", so we must not exit the loop early.
|
|
269
259
|
const drain = async (): Promise<void> => {
|
|
270
260
|
try {
|
|
271
|
-
for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(response.body!,
|
|
261
|
+
for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(response.body!, signal)) {
|
|
272
262
|
const messages = Array.isArray(raw) ? raw : [raw];
|
|
273
263
|
for (const message of messages) {
|
|
274
264
|
if (
|
|
@@ -278,7 +268,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
278
268
|
("result" in message || "error" in message)
|
|
279
269
|
) {
|
|
280
270
|
captured = true;
|
|
281
|
-
|
|
271
|
+
operation.clear();
|
|
282
272
|
if (message.error) {
|
|
283
273
|
reject(new Error(`MCP error ${message.error.code}: ${message.error.message}`));
|
|
284
274
|
} else {
|
|
@@ -295,17 +285,13 @@ export class HttpTransport implements MCPTransport {
|
|
|
295
285
|
}
|
|
296
286
|
} catch (error) {
|
|
297
287
|
if (captured) return;
|
|
298
|
-
if (error
|
|
299
|
-
|
|
300
|
-
reject(error);
|
|
301
|
-
} else {
|
|
302
|
-
reject(new Error(`SSE response timeout after ${timeout}ms`));
|
|
303
|
-
}
|
|
288
|
+
if (operation.isTimeoutAbort(error)) {
|
|
289
|
+
reject(new Error(`SSE response timeout after ${timeout}ms`));
|
|
304
290
|
} else {
|
|
305
291
|
reject(error as Error);
|
|
306
292
|
}
|
|
307
293
|
} finally {
|
|
308
|
-
|
|
294
|
+
operation.clear();
|
|
309
295
|
}
|
|
310
296
|
};
|
|
311
297
|
|
|
@@ -340,13 +326,16 @@ export class HttpTransport implements MCPTransport {
|
|
|
340
326
|
if (this.#sessionId) {
|
|
341
327
|
headers["Mcp-Session-Id"] = this.#sessionId;
|
|
342
328
|
}
|
|
329
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
330
|
+
let operation = createMCPTimeout(timeout);
|
|
343
331
|
try {
|
|
344
332
|
const resp = await fetch(this.config.url, {
|
|
345
333
|
method: "POST",
|
|
346
334
|
headers,
|
|
347
335
|
body: JSON.stringify(body),
|
|
348
|
-
signal:
|
|
336
|
+
signal: operation.signal,
|
|
349
337
|
});
|
|
338
|
+
operation.clear();
|
|
350
339
|
// Retry once on auth failure if onAuthError is wired
|
|
351
340
|
if (this.onAuthError && (resp.status === 401 || resp.status === 403)) {
|
|
352
341
|
await resp.body?.cancel();
|
|
@@ -355,18 +344,21 @@ export class HttpTransport implements MCPTransport {
|
|
|
355
344
|
this.config.headers ??= {};
|
|
356
345
|
Object.assign(this.config.headers, newHeaders);
|
|
357
346
|
Object.assign(headers, newHeaders);
|
|
347
|
+
operation = createMCPTimeout(timeout);
|
|
358
348
|
const retry = await fetch(this.config.url, {
|
|
359
349
|
method: "POST",
|
|
360
350
|
headers,
|
|
361
351
|
body: JSON.stringify(body),
|
|
362
|
-
signal:
|
|
352
|
+
signal: operation.signal,
|
|
363
353
|
});
|
|
354
|
+
operation.clear();
|
|
364
355
|
await retry.body?.cancel();
|
|
365
356
|
return;
|
|
366
357
|
}
|
|
367
358
|
}
|
|
368
359
|
await resp.body?.cancel();
|
|
369
360
|
} catch {
|
|
361
|
+
operation.clear();
|
|
370
362
|
// Best-effort response delivery — server may have disconnected
|
|
371
363
|
}
|
|
372
364
|
}
|
|
@@ -392,20 +384,18 @@ export class HttpTransport implements MCPTransport {
|
|
|
392
384
|
headers["Mcp-Session-Id"] = this.#sessionId;
|
|
393
385
|
}
|
|
394
386
|
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
const abortController = new AbortController();
|
|
398
|
-
const timeoutId = setTimeout(() => abortController.abort(), timeout);
|
|
387
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
388
|
+
const operation = createMCPTimeout(timeout);
|
|
399
389
|
|
|
400
390
|
try {
|
|
401
391
|
const response = await fetch(this.config.url, {
|
|
402
392
|
method: "POST",
|
|
403
393
|
headers,
|
|
404
394
|
body: JSON.stringify(body),
|
|
405
|
-
signal:
|
|
395
|
+
signal: operation.signal,
|
|
406
396
|
});
|
|
407
397
|
|
|
408
|
-
|
|
398
|
+
operation.clear();
|
|
409
399
|
|
|
410
400
|
// 202 Accepted is success for notifications
|
|
411
401
|
if (!response.ok && response.status !== 202) {
|
|
@@ -417,15 +407,20 @@ export class HttpTransport implements MCPTransport {
|
|
|
417
407
|
// on the notification response (MCP Streamable HTTP spec). Read them.
|
|
418
408
|
const contentType = response.headers.get("Content-Type") ?? "";
|
|
419
409
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
420
|
-
// Use the SSE connection's signal if available
|
|
421
|
-
|
|
422
|
-
|
|
410
|
+
// Use the SSE connection's signal if available; otherwise keep the existing finite read timeout.
|
|
411
|
+
if (this.#sseConnection) {
|
|
412
|
+
void this.#readSSEStream(response.body, this.#sseConnection.signal);
|
|
413
|
+
} else {
|
|
414
|
+
const readOperation = createMCPTimeout(timeout);
|
|
415
|
+
const signal = readOperation.signal ?? getNeverAbortSignal();
|
|
416
|
+
void this.#readSSEStream(response.body, signal).finally(() => readOperation.clear());
|
|
417
|
+
}
|
|
423
418
|
} else {
|
|
424
419
|
await response.body?.cancel();
|
|
425
420
|
}
|
|
426
421
|
} catch (error) {
|
|
427
|
-
|
|
428
|
-
if (error
|
|
422
|
+
operation.clear();
|
|
423
|
+
if (operation.isTimeoutAbort(error)) {
|
|
429
424
|
throw new Error(`Notify timeout after ${timeout}ms`);
|
|
430
425
|
}
|
|
431
426
|
throw error;
|
|
@@ -444,8 +439,9 @@ export class HttpTransport implements MCPTransport {
|
|
|
444
439
|
|
|
445
440
|
// Send session termination if we have a session
|
|
446
441
|
if (this.#sessionId) {
|
|
442
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
443
|
+
const operation = createMCPTimeout(timeout);
|
|
447
444
|
try {
|
|
448
|
-
const timeout = this.config.timeout ?? 30000;
|
|
449
445
|
const headers: Record<string, string> = {
|
|
450
446
|
...this.config.headers,
|
|
451
447
|
"Mcp-Session-Id": this.#sessionId,
|
|
@@ -454,9 +450,11 @@ export class HttpTransport implements MCPTransport {
|
|
|
454
450
|
await fetch(this.config.url, {
|
|
455
451
|
method: "DELETE",
|
|
456
452
|
headers,
|
|
457
|
-
signal:
|
|
453
|
+
signal: operation.signal,
|
|
458
454
|
});
|
|
455
|
+
operation.clear();
|
|
459
456
|
} catch {
|
|
457
|
+
operation.clear();
|
|
460
458
|
// Ignore termination errors
|
|
461
459
|
}
|
|
462
460
|
this.#sessionId = null;
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
MCPTransport,
|
|
18
18
|
} from "../../mcp/types";
|
|
19
19
|
import { toJsonRpcError } from "../../mcp/types";
|
|
20
|
+
import { isMCPTimeoutEnabled, resolveMCPTimeoutMs } from "../timeout";
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Stdio transport for MCP servers.
|
|
@@ -208,7 +209,7 @@ export class StdioTransport implements MCPTransport {
|
|
|
208
209
|
params: params ?? {},
|
|
209
210
|
};
|
|
210
211
|
|
|
211
|
-
const timeout = this.config.timeout
|
|
212
|
+
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
212
213
|
const signal = options?.signal;
|
|
213
214
|
|
|
214
215
|
if (signal?.aborted) {
|
|
@@ -254,10 +255,12 @@ export class StdioTransport implements MCPTransport {
|
|
|
254
255
|
},
|
|
255
256
|
});
|
|
256
257
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
if (isMCPTimeoutEnabled(timeout)) {
|
|
259
|
+
timer = setTimeout(() => {
|
|
260
|
+
cleanup();
|
|
261
|
+
reject(new Error(`Request timeout after ${timeout}ms`));
|
|
262
|
+
}, timeout);
|
|
263
|
+
}
|
|
261
264
|
|
|
262
265
|
const message = `${JSON.stringify(request)}\n`;
|
|
263
266
|
try {
|
package/src/mcp/types.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface MCPAuthConfig {
|
|
|
61
61
|
interface MCPServerConfigBase {
|
|
62
62
|
/** Whether this server is enabled (default: true) */
|
|
63
63
|
enabled?: boolean;
|
|
64
|
-
/**
|
|
64
|
+
/** MCP request timeout in milliseconds (default: 30000, 0 to disable) */
|
|
65
65
|
timeout?: number;
|
|
66
66
|
/** Authentication configuration (optional) */
|
|
67
67
|
auth?: MCPAuthConfig;
|
|
@@ -17,6 +17,10 @@ export interface HookEditorOptions {
|
|
|
17
17
|
promptStyle?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function isCtrlEnterSubmit(keyData: string): boolean {
|
|
21
|
+
return matchesKey(keyData, "ctrl+enter") || (keyData.charCodeAt(0) === 10 && keyData.length > 1);
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
export class HookEditorComponent extends Container {
|
|
21
25
|
#editor: Editor;
|
|
22
26
|
#onSubmitCallback: (value: string) => void;
|
|
@@ -78,6 +82,10 @@ export class HookEditorComponent extends Container {
|
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
85
|
+
#submitCurrentText(): void {
|
|
86
|
+
this.#onSubmitCallback(this.#editor.getExpandedText());
|
|
87
|
+
}
|
|
88
|
+
|
|
81
89
|
/** Prompt-style: raw Enter submits; Editor owns newline-producing sequences. */
|
|
82
90
|
#handlePromptStyleInput(keyData: string): void {
|
|
83
91
|
// Prompt-style keeps Escape as an explicit cancel key and also honors app.interrupt remaps.
|
|
@@ -94,7 +102,7 @@ export class HookEditorComponent extends Container {
|
|
|
94
102
|
|
|
95
103
|
// Submit on any plain Enter encoding, including terminals that report unmodified Enter as LF.
|
|
96
104
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return")) {
|
|
97
|
-
this.#
|
|
105
|
+
this.#submitCurrentText();
|
|
98
106
|
return;
|
|
99
107
|
}
|
|
100
108
|
|
|
@@ -105,8 +113,8 @@ export class HookEditorComponent extends Container {
|
|
|
105
113
|
/** Hook-style: Enter=newline, Ctrl+Enter=submit (original behavior) */
|
|
106
114
|
#handleHookStyleInput(keyData: string): void {
|
|
107
115
|
// Ctrl+Enter to submit. Use key matching so lock-key and keypad Enter variants work.
|
|
108
|
-
if (
|
|
109
|
-
this.#
|
|
116
|
+
if (isCtrlEnterSubmit(keyData)) {
|
|
117
|
+
this.#submitCurrentText();
|
|
110
118
|
return;
|
|
111
119
|
}
|
|
112
120
|
|
|
@@ -965,14 +965,18 @@ export class MCPAddWizard extends Container {
|
|
|
965
965
|
}, 1000);
|
|
966
966
|
} catch (error) {
|
|
967
967
|
// Connection failed - check if it's an auth error
|
|
968
|
-
const authResult = analyzeAuthError(error as Error);
|
|
968
|
+
const authResult = analyzeAuthError(error as Error, this.#state.url);
|
|
969
969
|
|
|
970
970
|
if (authResult.requiresAuth) {
|
|
971
971
|
// Prefer OAuth first: use error metadata, then well-known discovery fallback.
|
|
972
972
|
let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
|
|
973
973
|
if (!oauth && this.#state.transport !== "stdio" && this.#state.url) {
|
|
974
974
|
try {
|
|
975
|
-
oauth = await discoverOAuthEndpoints(
|
|
975
|
+
oauth = await discoverOAuthEndpoints(
|
|
976
|
+
this.#state.url,
|
|
977
|
+
authResult.authServerUrl,
|
|
978
|
+
authResult.resourceMetadataUrl,
|
|
979
|
+
);
|
|
976
980
|
} catch {
|
|
977
981
|
// Ignore discovery failures and fallback to manual auth.
|
|
978
982
|
}
|
|
@@ -277,7 +277,12 @@ export class ModelSelectorComponent extends Container {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
/**
|
|
281
|
+
* @param skipRoleRank When a search query is narrowing the list, role assignments
|
|
282
|
+
* should NOT promote a weakly-matching default model above a perfect text
|
|
283
|
+
* match — defer to MRU/version instead so user affinity drives the order.
|
|
284
|
+
*/
|
|
285
|
+
#sortModels(models: ModelItem[], { skipRoleRank = false }: { skipRoleRank?: boolean } = {}): void {
|
|
281
286
|
// Sort: tagged models (default/smol/slow/plan) first, then MRU, then alphabetical
|
|
282
287
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
283
288
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
@@ -291,9 +296,11 @@ export class ModelSelectorComponent extends Container {
|
|
|
291
296
|
const aKey = a.selector;
|
|
292
297
|
const bKey = b.selector;
|
|
293
298
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
if (!skipRoleRank) {
|
|
300
|
+
const aRank = modelRank(a);
|
|
301
|
+
const bRank = modelRank(b);
|
|
302
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
303
|
+
}
|
|
297
304
|
|
|
298
305
|
// Then MRU order (models in mruIndex come before those not in it)
|
|
299
306
|
const aMru = mruIndex.get(aKey) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -340,16 +347,18 @@ export class ModelSelectorComponent extends Container {
|
|
|
340
347
|
});
|
|
341
348
|
}
|
|
342
349
|
|
|
343
|
-
#sortCanonicalModels(models: CanonicalModelItem[]): void {
|
|
350
|
+
#sortCanonicalModels(models: CanonicalModelItem[], { skipRoleRank = false }: { skipRoleRank?: boolean } = {}): void {
|
|
344
351
|
const mruOrder = this.#settings.getStorage()?.getModelUsageOrder() ?? [];
|
|
345
352
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
346
353
|
|
|
347
354
|
const modelRank = (item: CanonicalModelItem) => computeModelRank(item.model, this.#roles);
|
|
348
355
|
|
|
349
356
|
models.sort((a, b) => {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
if (!skipRoleRank) {
|
|
358
|
+
const aRank = modelRank(a);
|
|
359
|
+
const bRank = modelRank(b);
|
|
360
|
+
if (aRank !== bRank) return aRank - bRank;
|
|
361
|
+
}
|
|
353
362
|
|
|
354
363
|
const aMru = mruIndex.get(`${a.model.provider}/${a.model.id}`) ?? Number.MAX_SAFE_INTEGER;
|
|
355
364
|
const bMru = mruIndex.get(`${b.model.provider}/${b.model.id}`) ?? Number.MAX_SAFE_INTEGER;
|
|
@@ -558,12 +567,25 @@ export class ModelSelectorComponent extends Container {
|
|
|
558
567
|
: alphaFiltered.length > 0
|
|
559
568
|
? alphaFiltered
|
|
560
569
|
: baseCanonicalModels;
|
|
570
|
+
// Fuzzy provides the candidate set, but `${provider}/${id}` scoring
|
|
571
|
+
// is biased by provider-prefix length (e.g. `openai/X` beats
|
|
572
|
+
// `openai-codex/X` purely because the prefix is shorter). Re-sort by
|
|
573
|
+
// affinity — MRU then version — so the user's actually-used model
|
|
574
|
+
// wins. Role rank is skipped: when narrowing by query, a weakly
|
|
575
|
+
// matching default model should not be promoted above a stronger
|
|
576
|
+
// non-default match.
|
|
561
577
|
const fuzzyMatches = fuzzyFilter(fuzzySource, query, ({ searchText }) => searchText);
|
|
562
|
-
this.#sortCanonicalModels(fuzzyMatches);
|
|
578
|
+
this.#sortCanonicalModels(fuzzyMatches, { skipRoleRank: true });
|
|
563
579
|
this.#filteredCanonicalModels = fuzzyMatches;
|
|
564
580
|
} else {
|
|
565
|
-
|
|
566
|
-
|
|
581
|
+
// Match against the displayed "provider/id" string so the user can
|
|
582
|
+
// type what they see: bare names (`mimo`, `kimi`), provider prefixes
|
|
583
|
+
// (`openrouter`), or scoped queries (`openrouter/mimo`) all flow
|
|
584
|
+
// through the same fuzzy matcher. The score is biased by provider-
|
|
585
|
+
// prefix length, so re-sort by MRU/version afterwards; skip role
|
|
586
|
+
// rank so a weakly matching default doesn't trump a stronger match.
|
|
587
|
+
const fuzzyMatches = fuzzyFilter(baseModels, query, ({ id, provider }) => `${provider}/${id}`);
|
|
588
|
+
this.#sortModels(fuzzyMatches, { skipRoleRank: true });
|
|
567
589
|
this.#filteredModels = fuzzyMatches;
|
|
568
590
|
}
|
|
569
591
|
} else {
|
|
@@ -1368,10 +1368,12 @@ function formatUnlimitedReportLabel(report: UsageReport, index: number): string
|
|
|
1368
1368
|
}
|
|
1369
1369
|
|
|
1370
1370
|
function formatResetShort(limit: UsageLimit, nowMs: number): string | undefined {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1371
|
+
const resetsAt = limit.window?.resetsAt;
|
|
1372
|
+
if (resetsAt === undefined) return undefined;
|
|
1373
|
+
// Codex returns the prior window's reset_at until a new request opens a fresh window —
|
|
1374
|
+
// rendering a negative delta is meaningless, so drop the suffix in that case.
|
|
1375
|
+
if (resetsAt <= nowMs) return undefined;
|
|
1376
|
+
return formatDuration(resetsAt - nowMs);
|
|
1375
1377
|
}
|
|
1376
1378
|
|
|
1377
1379
|
function formatAccountHeaderRow(
|
|
@@ -401,12 +401,16 @@ export class MCPCommandController {
|
|
|
401
401
|
);
|
|
402
402
|
return;
|
|
403
403
|
}
|
|
404
|
-
const authResult = analyzeAuthError(error as Error);
|
|
404
|
+
const authResult = analyzeAuthError(error as Error, finalConfig.url);
|
|
405
405
|
if (authResult.requiresAuth) {
|
|
406
406
|
let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
|
|
407
407
|
if (!oauth && finalConfig.url) {
|
|
408
408
|
try {
|
|
409
|
-
oauth = await discoverOAuthEndpoints(
|
|
409
|
+
oauth = await discoverOAuthEndpoints(
|
|
410
|
+
finalConfig.url,
|
|
411
|
+
authResult.authServerUrl,
|
|
412
|
+
authResult.resourceMetadataUrl,
|
|
413
|
+
);
|
|
410
414
|
} catch {
|
|
411
415
|
// Ignore discovery error and handle below.
|
|
412
416
|
}
|
|
@@ -742,11 +746,11 @@ export class MCPCommandController {
|
|
|
742
746
|
}
|
|
743
747
|
|
|
744
748
|
// Analyze the connection error to extract OAuth endpoints
|
|
745
|
-
const authResult = analyzeAuthError(connectionError
|
|
749
|
+
const authResult = analyzeAuthError(connectionError!, "url" in config ? config.url : undefined);
|
|
746
750
|
let oauth = authResult.authType === "oauth" ? (authResult.oauth ?? null) : null;
|
|
747
751
|
|
|
748
752
|
if (!oauth && (config.type === "http" || config.type === "sse") && config.url) {
|
|
749
|
-
oauth = await discoverOAuthEndpoints(config.url, authResult.authServerUrl);
|
|
753
|
+
oauth = await discoverOAuthEndpoints(config.url, authResult.authServerUrl, authResult.resourceMetadataUrl);
|
|
750
754
|
}
|
|
751
755
|
|
|
752
756
|
if (!oauth) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
## Code Review Request
|
|
2
|
+
|
|
3
|
+
### Mode
|
|
4
|
+
|
|
5
|
+
Custom review instructions
|
|
6
|
+
|
|
7
|
+
### Distribution Guidelines
|
|
8
|
+
|
|
9
|
+
Use the `task` tool with `agent: "reviewer"` and a `tasks` array.
|
|
10
|
+
Create exactly **1 reviewer task**. Its assignment must include the custom instructions below.
|
|
11
|
+
|
|
12
|
+
### Reviewer Instructions
|
|
13
|
+
|
|
14
|
+
Reviewer MUST:
|
|
15
|
+
1. Follow the custom instructions below
|
|
16
|
+
2. Read the referenced files or workspace context needed to evaluate them
|
|
17
|
+
3. Call `report_finding` per issue
|
|
18
|
+
4. Call `yield` with verdict when done
|
|
19
|
+
|
|
20
|
+
### Custom Instructions
|
|
21
|
+
|
|
22
|
+
{{instructions}}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## Code Review Request
|
|
2
|
+
|
|
3
|
+
### Mode
|
|
4
|
+
|
|
5
|
+
Headless review request
|
|
6
|
+
|
|
7
|
+
### Distribution Guidelines
|
|
8
|
+
|
|
9
|
+
Use the `task` tool with `agent: "reviewer"` and a `tasks` array.
|
|
10
|
+
Create exactly **1 reviewer task** for recent code changes.
|
|
11
|
+
|
|
12
|
+
{{#if focus}}
|
|
13
|
+
### Focus
|
|
14
|
+
|
|
15
|
+
{{focus}}
|
|
16
|
+
{{/if}}
|
|
@@ -23,14 +23,13 @@ _No files to review._
|
|
|
23
23
|
|
|
24
24
|
### Distribution Guidelines
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Use the `task` tool with `agent: "reviewer"` and a `tasks` array.
|
|
27
|
+
{{#when agentCount "==" 1}}Create exactly **1 reviewer task**.{{else}}Spawn **{{agentCount}} reviewer agents** in parallel.{{/when}}
|
|
27
28
|
{{#if multiAgent}}
|
|
28
29
|
Group files by locality, e.g.:
|
|
29
30
|
- Same directory/module → same agent
|
|
30
31
|
- Related functionality → same agent
|
|
31
32
|
- Tests with their implementation files → same agent
|
|
32
|
-
|
|
33
|
-
You MUST use Task tool with `agent: "reviewer"` and `tasks` array.
|
|
34
33
|
{{/if}}
|
|
35
34
|
|
|
36
35
|
### Reviewer Instructions
|
|
@@ -22,6 +22,10 @@ MUST read before making changes within:
|
|
|
22
22
|
</dir-context>
|
|
23
23
|
{{/if}}
|
|
24
24
|
|
|
25
|
+
{{#ifAny contextFiles.length agentsMdSearch.files.length}}
|
|
26
|
+
The context files above are loaded automatically. You NEVER `search`/`find` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise.
|
|
27
|
+
{{/ifAny}}
|
|
28
|
+
|
|
25
29
|
{{#if workspaceTree.rendered}}
|
|
26
30
|
<workspace-tree>
|
|
27
31
|
Working directory layout (sorted by mtime, recent first; depth ≤ 3):
|
|
@@ -17,6 +17,7 @@ Use for launching or attaching debuggers, setting breakpoints, stepping through
|
|
|
17
17
|
- Some adapters require a launched session to receive `configurationDone` before the target actually runs; if the tool says configuration is pending, set breakpoints and then call `continue`.
|
|
18
18
|
- Adapter availability depends on local binaries. Common built-ins: `gdb`, `lldb-dap`, `python -m debugpy.adapter`, `dlv dap`.
|
|
19
19
|
- `program` must be an executable file or debug target, not a directory or interpreter name that resolves to a workspace directory.
|
|
20
|
+
- Python debugging requires `debugpy`; install with `pip install debugpy` if the adapter is unavailable.
|
|
20
21
|
</caution>
|
|
21
22
|
|
|
22
23
|
<examples>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Finds files using fast pattern matching that works with any codebase size.
|
|
1
|
+
Finds files and directories using fast pattern matching that works with any codebase size.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
4
|
- `paths` is required and accepts an array of globs, files, or directories
|
|
@@ -10,7 +10,7 @@ Finds files using fast pattern matching that works with any codebase size.
|
|
|
10
10
|
</instruction>
|
|
11
11
|
|
|
12
12
|
<output>
|
|
13
|
-
Matching file paths sorted by modification time (most recent first). Truncated at 1000 entries or 50KB (configurable via `limit`).
|
|
13
|
+
Matching file and directory paths sorted by modification time (most recent first). Directories are suffixed with `/`. Truncated at 1000 entries or 50KB (configurable via `limit`).
|
|
14
14
|
</output>
|
|
15
15
|
|
|
16
16
|
<examples>
|
|
@@ -20,6 +20,8 @@ Matching file paths sorted by modification time (most recent first). Truncated a
|
|
|
20
20
|
`{"paths": ["src/**/*.ts", "test/**/*.ts"]}`
|
|
21
21
|
# Find gitignored files like .env
|
|
22
22
|
`{"paths": [".env*"], "gitignore": false}`
|
|
23
|
+
# Find directories matching a name (returns both files and dirs; directories are suffixed with `/`)
|
|
24
|
+
`{"paths": ["**/tests"]}`
|
|
23
25
|
# Long-running search on a slow volume
|
|
24
26
|
`{"paths": ["/Volumes/Storage/**/*.py"], "timeout": 30}`
|
|
25
27
|
</examples>
|