@oh-my-pi/pi-coding-agent 17.2.10 → 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.
- package/CHANGELOG.md +40 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-d9xenpn9.md} +40 -0
- package/dist/cli.js +3025 -3016
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +34 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/mcp/transports/header-policy.d.ts +45 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/secrets/index.d.ts +23 -1
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +7 -2
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/shell-tokenize.d.ts +16 -0
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-registry.ts +21 -1
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +46 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +78 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +4 -1
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +52 -1
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session.ts +33 -14
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +5 -1
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +94 -72
- package/src/slash-commands/builtin-registry.ts +12 -8
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read.ts +5 -4
- package/src/tools/shell-tokenize.ts +98 -0
- package/src/web/search/providers/exa.ts +1 -1
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
import { toJsonRpcError } from "../../mcp/types";
|
|
20
20
|
import { RequestIdAllocator } from "../request-id";
|
|
21
21
|
import { createMCPTimeout, getNeverAbortSignal, isMCPTimeoutEnabled, resolveMCPTimeoutMs } from "../timeout";
|
|
22
|
+
import { type MCPFetchInit, mcpFetch } from "./header-policy";
|
|
22
23
|
|
|
23
24
|
const HTTP_SSE_CONNECT_TIMEOUT_MS = 1_000;
|
|
24
25
|
/**
|
|
@@ -54,6 +55,16 @@ export class HttpTransport implements MCPTransport {
|
|
|
54
55
|
|
|
55
56
|
constructor(private config: MCPHttpServerConfig | MCPSseServerConfig) {}
|
|
56
57
|
|
|
58
|
+
/** Fetch the configured endpoint with header precedence and origin policy. */
|
|
59
|
+
#fetch(init: MCPFetchInit, generated: Record<string, string>): Promise<Response> {
|
|
60
|
+
return mcpFetch(
|
|
61
|
+
this.config.url,
|
|
62
|
+
init,
|
|
63
|
+
{ generated, configured: this.config.headers },
|
|
64
|
+
this.config.headerPolicy === "origin-locked",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
get connected(): boolean {
|
|
58
69
|
return this.#connected;
|
|
59
70
|
}
|
|
@@ -81,13 +92,12 @@ export class HttpTransport implements MCPTransport {
|
|
|
81
92
|
if (this.#sseConnection) return;
|
|
82
93
|
|
|
83
94
|
this.#sseConnection = new AbortController();
|
|
84
|
-
const
|
|
95
|
+
const generated: Record<string, string> = {
|
|
85
96
|
Accept: "text/event-stream",
|
|
86
|
-
...this.config.headers,
|
|
87
97
|
};
|
|
88
98
|
|
|
89
99
|
if (this.#sessionId) {
|
|
90
|
-
|
|
100
|
+
generated["Mcp-Session-Id"] = this.#sessionId;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
103
|
let response: Response | null;
|
|
@@ -95,11 +105,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
95
105
|
let startupFinished = false;
|
|
96
106
|
const connection = this.#sseConnection;
|
|
97
107
|
const startupTimeoutMs = resolveSSEConnectTimeoutMs(this.config.timeout);
|
|
98
|
-
const fetchPromise = fetch(
|
|
99
|
-
method: "GET",
|
|
100
|
-
headers,
|
|
101
|
-
signal: connection.signal,
|
|
102
|
-
});
|
|
108
|
+
const fetchPromise = this.#fetch({ method: "GET", signal: connection.signal }, generated);
|
|
103
109
|
const timeoutPromise =
|
|
104
110
|
startupTimeoutMs > 0
|
|
105
111
|
? new Promise<null>(resolve => {
|
|
@@ -219,26 +225,23 @@ export class HttpTransport implements MCPTransport {
|
|
|
219
225
|
params: params ?? {},
|
|
220
226
|
};
|
|
221
227
|
|
|
222
|
-
const
|
|
228
|
+
const generated: Record<string, string> = {
|
|
223
229
|
"Content-Type": "application/json",
|
|
224
230
|
Accept: "application/json, text/event-stream",
|
|
225
|
-
...this.config.headers,
|
|
226
231
|
};
|
|
227
232
|
|
|
228
233
|
if (this.#sessionId) {
|
|
229
|
-
|
|
234
|
+
generated["Mcp-Session-Id"] = this.#sessionId;
|
|
230
235
|
}
|
|
231
236
|
|
|
232
237
|
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
233
238
|
const operation = createMCPTimeout(timeout, options?.signal);
|
|
234
239
|
|
|
235
240
|
try {
|
|
236
|
-
const response = await fetch(
|
|
237
|
-
method: "POST",
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
signal: operation.signal,
|
|
241
|
-
});
|
|
241
|
+
const response = await this.#fetch(
|
|
242
|
+
{ method: "POST", body: JSON.stringify(body), signal: operation.signal },
|
|
243
|
+
generated,
|
|
244
|
+
);
|
|
242
245
|
|
|
243
246
|
// Check for session ID in response
|
|
244
247
|
const newSessionId = response.headers.get("Mcp-Session-Id");
|
|
@@ -365,23 +368,18 @@ export class HttpTransport implements MCPTransport {
|
|
|
365
368
|
const body = error
|
|
366
369
|
? { jsonrpc: "2.0" as const, id, error }
|
|
367
370
|
: { jsonrpc: "2.0" as const, id, result: result ?? {} };
|
|
368
|
-
const
|
|
371
|
+
const generated: Record<string, string> = {
|
|
369
372
|
"Content-Type": "application/json",
|
|
370
373
|
Accept: "application/json, text/event-stream",
|
|
371
|
-
...this.config.headers,
|
|
372
374
|
};
|
|
373
375
|
if (this.#sessionId) {
|
|
374
|
-
|
|
376
|
+
generated["Mcp-Session-Id"] = this.#sessionId;
|
|
375
377
|
}
|
|
378
|
+
const payload = JSON.stringify(body);
|
|
376
379
|
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
377
380
|
const operation = createMCPTimeout(timeout);
|
|
378
381
|
try {
|
|
379
|
-
const resp = await fetch(
|
|
380
|
-
method: "POST",
|
|
381
|
-
headers,
|
|
382
|
-
body: JSON.stringify(body),
|
|
383
|
-
signal: operation.signal,
|
|
384
|
-
});
|
|
382
|
+
const resp = await this.#fetch({ method: "POST", body: payload, signal: operation.signal }, generated);
|
|
385
383
|
// Retry once on auth failure if onAuthError is wired
|
|
386
384
|
if (this.onAuthError && (resp.status === 401 || resp.status === 403)) {
|
|
387
385
|
await resp.body?.cancel();
|
|
@@ -389,16 +387,13 @@ export class HttpTransport implements MCPTransport {
|
|
|
389
387
|
if (newHeaders) {
|
|
390
388
|
this.config.headers ??= {};
|
|
391
389
|
Object.assign(this.config.headers, newHeaders);
|
|
392
|
-
Object.assign(headers, newHeaders);
|
|
393
390
|
operation.clear();
|
|
394
391
|
const retryOperation = createMCPTimeout(timeout);
|
|
395
392
|
try {
|
|
396
|
-
const retry = await fetch(
|
|
397
|
-
method: "POST",
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
signal: retryOperation.signal,
|
|
401
|
-
});
|
|
393
|
+
const retry = await this.#fetch(
|
|
394
|
+
{ method: "POST", body: payload, signal: retryOperation.signal },
|
|
395
|
+
generated,
|
|
396
|
+
);
|
|
402
397
|
await retry.body?.cancel();
|
|
403
398
|
} finally {
|
|
404
399
|
retryOperation.clear();
|
|
@@ -425,26 +420,23 @@ export class HttpTransport implements MCPTransport {
|
|
|
425
420
|
params: params ?? {},
|
|
426
421
|
};
|
|
427
422
|
|
|
428
|
-
const
|
|
423
|
+
const generated: Record<string, string> = {
|
|
429
424
|
"Content-Type": "application/json",
|
|
430
425
|
Accept: "application/json, text/event-stream",
|
|
431
|
-
...this.config.headers,
|
|
432
426
|
};
|
|
433
427
|
|
|
434
428
|
if (this.#sessionId) {
|
|
435
|
-
|
|
429
|
+
generated["Mcp-Session-Id"] = this.#sessionId;
|
|
436
430
|
}
|
|
437
431
|
|
|
438
432
|
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
439
433
|
const operation = createMCPTimeout(timeout);
|
|
440
434
|
|
|
441
435
|
try {
|
|
442
|
-
const response = await fetch(
|
|
443
|
-
method: "POST",
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
signal: operation.signal,
|
|
447
|
-
});
|
|
436
|
+
const response = await this.#fetch(
|
|
437
|
+
{ method: "POST", body: JSON.stringify(body), signal: operation.signal },
|
|
438
|
+
generated,
|
|
439
|
+
);
|
|
448
440
|
|
|
449
441
|
// 202 Accepted is success for notifications
|
|
450
442
|
if (!response.ok && response.status !== 202) {
|
|
@@ -492,16 +484,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
492
484
|
const timeout = resolveMCPTimeoutMs(this.config.timeout);
|
|
493
485
|
const operation = createMCPTimeout(timeout);
|
|
494
486
|
try {
|
|
495
|
-
|
|
496
|
-
...this.config.headers,
|
|
497
|
-
"Mcp-Session-Id": this.#sessionId,
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
await fetch(this.config.url, {
|
|
501
|
-
method: "DELETE",
|
|
502
|
-
headers,
|
|
503
|
-
signal: operation.signal,
|
|
504
|
-
});
|
|
487
|
+
await this.#fetch({ method: "DELETE", signal: operation.signal }, { "Mcp-Session-Id": this.#sessionId });
|
|
505
488
|
operation.clear();
|
|
506
489
|
} catch {
|
|
507
490
|
operation.clear();
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import { toJsonRpcError } from "../../mcp/types";
|
|
13
13
|
import { RequestIdAllocator } from "../request-id";
|
|
14
14
|
import { createMCPTimeout, getNeverAbortSignal, resolveMCPTimeoutMs } from "../timeout";
|
|
15
|
+
import { type MCPFetchInit, mcpFetch } from "./header-policy";
|
|
15
16
|
|
|
16
17
|
interface MCPTimeoutOperation {
|
|
17
18
|
signal?: AbortSignal;
|
|
@@ -46,6 +47,16 @@ export class LegacySseTransport implements MCPTransport {
|
|
|
46
47
|
this.#config = config;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/** Fetch an endpoint with header precedence and origin policy. */
|
|
51
|
+
#fetch(url: string, init: MCPFetchInit, generated: Record<string, string>): Promise<Response> {
|
|
52
|
+
return mcpFetch(
|
|
53
|
+
url,
|
|
54
|
+
init,
|
|
55
|
+
{ generated, configured: this.#config.headers },
|
|
56
|
+
this.#config.headerPolicy === "origin-locked",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
get connected(): boolean {
|
|
50
61
|
return this.#connected;
|
|
51
62
|
}
|
|
@@ -65,14 +76,11 @@ export class LegacySseTransport implements MCPTransport {
|
|
|
65
76
|
this.#sseConnection = connection;
|
|
66
77
|
|
|
67
78
|
try {
|
|
68
|
-
const response = await fetch(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
},
|
|
74
|
-
signal: operation.signal,
|
|
75
|
-
});
|
|
79
|
+
const response = await this.#fetch(
|
|
80
|
+
this.#config.url,
|
|
81
|
+
{ method: "GET", signal: operation.signal },
|
|
82
|
+
{ Accept: "text/event-stream" },
|
|
83
|
+
);
|
|
76
84
|
|
|
77
85
|
if (!response.ok) {
|
|
78
86
|
const text = await response.text();
|
|
@@ -286,17 +294,12 @@ export class LegacySseTransport implements MCPTransport {
|
|
|
286
294
|
): Promise<Response> {
|
|
287
295
|
const endpointUrl = this.#endpointUrl;
|
|
288
296
|
if (!endpointUrl) throw new Error("Transport not connected");
|
|
289
|
-
|
|
297
|
+
const generated: Record<string, string> = {
|
|
290
298
|
"Content-Type": "application/json",
|
|
291
299
|
Accept: "application/json, text/event-stream",
|
|
292
|
-
...this.#config.headers,
|
|
293
300
|
};
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
headers,
|
|
297
|
-
body: JSON.stringify(body),
|
|
298
|
-
signal,
|
|
299
|
-
});
|
|
301
|
+
const payload = JSON.stringify(body);
|
|
302
|
+
let response = await this.#fetch(endpointUrl, { method: "POST", body: payload, signal }, generated);
|
|
300
303
|
const status = AIError.status(response);
|
|
301
304
|
if (!this.onAuthError || (status !== 401 && status !== 403)) return response;
|
|
302
305
|
|
|
@@ -304,17 +307,7 @@ export class LegacySseTransport implements MCPTransport {
|
|
|
304
307
|
if (!refreshedHeaders) return response;
|
|
305
308
|
await response.body?.cancel();
|
|
306
309
|
this.#config.headers = refreshedHeaders;
|
|
307
|
-
|
|
308
|
-
"Content-Type": "application/json",
|
|
309
|
-
Accept: "application/json, text/event-stream",
|
|
310
|
-
...this.#config.headers,
|
|
311
|
-
};
|
|
312
|
-
response = await fetch(endpointUrl, {
|
|
313
|
-
method: "POST",
|
|
314
|
-
headers,
|
|
315
|
-
body: JSON.stringify(body),
|
|
316
|
-
signal,
|
|
317
|
-
});
|
|
310
|
+
response = await this.#fetch(endpointUrl, { method: "POST", body: payload, signal }, generated);
|
|
318
311
|
return response;
|
|
319
312
|
}
|
|
320
313
|
|
package/src/mcp/types.ts
CHANGED
|
@@ -100,6 +100,13 @@ export interface MCPStdioServerConfig extends MCPServerConfigBase {
|
|
|
100
100
|
command: string;
|
|
101
101
|
args?: string[];
|
|
102
102
|
env?: Record<string, string>;
|
|
103
|
+
/**
|
|
104
|
+
* `literal`: env values are opaque plugin package data (Agent Plugins
|
|
105
|
+
* §§4.1/9.2) — no env-name lookup, no `!command` execution, no dropping of
|
|
106
|
+
* empty values. The provider already applied the only permitted expansion
|
|
107
|
+
* (`${PLUGIN_ROOT}`/`${PLUGIN_DATA}`).
|
|
108
|
+
*/
|
|
109
|
+
envPolicy?: "literal";
|
|
103
110
|
cwd?: string;
|
|
104
111
|
}
|
|
105
112
|
|
|
@@ -108,6 +115,12 @@ export interface MCPHttpServerConfig extends MCPServerConfigBase {
|
|
|
108
115
|
type: "http";
|
|
109
116
|
url: string;
|
|
110
117
|
headers?: Record<string, string>;
|
|
118
|
+
/**
|
|
119
|
+
* `origin-locked`: configured headers are literal package data pinned to the
|
|
120
|
+
* configured URL's origin (Agent Plugins §7.2.1) — never expanded, never
|
|
121
|
+
* forwarded cross-origin, and client-generated headers win case-insensitively.
|
|
122
|
+
*/
|
|
123
|
+
headerPolicy?: "origin-locked";
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
/** SSE server configuration (deprecated, use HTTP) */
|
|
@@ -115,6 +128,8 @@ export interface MCPSseServerConfig extends MCPServerConfigBase {
|
|
|
115
128
|
type: "sse";
|
|
116
129
|
url: string;
|
|
117
130
|
headers?: Record<string, string>;
|
|
131
|
+
/** See {@link MCPHttpServerConfig.headerPolicy}. */
|
|
132
|
+
headerPolicy?: "origin-locked";
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
export type MCPServerConfig = MCPStdioServerConfig | MCPHttpServerConfig | MCPSseServerConfig;
|
|
@@ -70,6 +70,7 @@ import { SessionManager } from "../../session/session-manager";
|
|
|
70
70
|
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
71
71
|
import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
|
|
72
72
|
import { DEFAULT_STT_MODEL_KEY, STT_MODEL_OPTIONS } from "../../stt/models";
|
|
73
|
+
import { refreshAgentDiscovery } from "../../task";
|
|
73
74
|
import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
|
|
74
75
|
import { normalizeLocalScheme } from "../../tools/path-utils";
|
|
75
76
|
import { ToolError } from "../../tools/tool-errors";
|
|
@@ -1930,14 +1931,15 @@ export class AcpAgent implements Agent {
|
|
|
1930
1931
|
/**
|
|
1931
1932
|
* Reload plugin/registry state for an ACP session. Mirrors the interactive
|
|
1932
1933
|
* `/reload-plugins` and `/move` flows: invalidates the plugin-roots cache,
|
|
1933
|
-
* resets the capability cache, refreshes the
|
|
1934
|
-
* then re-advertises commands so the client
|
|
1935
|
-
* plugins.
|
|
1934
|
+
* refreshes task agents, resets the capability cache, refreshes the
|
|
1935
|
+
* session's slash-command state, then re-advertises commands so the client
|
|
1936
|
+
* sees newly installed/disabled plugins.
|
|
1936
1937
|
*/
|
|
1937
1938
|
async #reloadPluginState(record: ManagedSessionRecord): Promise<void> {
|
|
1938
1939
|
const cwd = record.session.sessionManager.getCwd();
|
|
1939
1940
|
const projectPath = await resolveActiveProjectRegistryPath(cwd);
|
|
1940
1941
|
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
1942
|
+
await refreshAgentDiscovery(cwd);
|
|
1941
1943
|
resetCapabilities();
|
|
1942
1944
|
await record.session.refreshSkills();
|
|
1943
1945
|
const fileCommands = await loadSlashCommands({ cwd });
|
|
@@ -2019,7 +2021,16 @@ export class AcpAgent implements Agent {
|
|
|
2019
2021
|
|
|
2020
2022
|
async #findStoredSession(sessionId: string, cwd: string): Promise<StoredSessionInfo | undefined> {
|
|
2021
2023
|
const sessions = await this.#listStoredSessions(cwd);
|
|
2022
|
-
|
|
2024
|
+
const scoped = sessions.find(session => session.id === sessionId);
|
|
2025
|
+
if (scoped) {
|
|
2026
|
+
return scoped;
|
|
2027
|
+
}
|
|
2028
|
+
// The cwd-derived directory only covers sessions stored under the current
|
|
2029
|
+
// naming scheme. Sessions written under a legacy/hashed project directory
|
|
2030
|
+
// (the 17.2.5+ scheme reverted in #7656) live elsewhere, so fall back to a
|
|
2031
|
+
// global by-id scan: the session id is globally unique, and
|
|
2032
|
+
// #openStoredSession reopens the file with the request cwd. See #7779.
|
|
2033
|
+
return this.#findStoredSessionById(sessionId);
|
|
2023
2034
|
}
|
|
2024
2035
|
|
|
2025
2036
|
async #findStoredSessionById(sessionId: string): Promise<StoredSessionInfo | undefined> {
|
|
@@ -48,6 +48,7 @@ import { Settings } from "../../config/settings";
|
|
|
48
48
|
import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-architect.md" with { type: "text" };
|
|
49
49
|
import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
|
|
50
50
|
import { createAgentSession } from "../../sdk";
|
|
51
|
+
import { refreshAgentDiscovery } from "../../task";
|
|
51
52
|
import { discoverAgents } from "../../task/discovery";
|
|
52
53
|
import { resolveAgentPrewalkDefault } from "../../task/prewalk";
|
|
53
54
|
import type { AgentDefinition, AgentSource } from "../../task/types";
|
|
@@ -824,6 +825,7 @@ export class AgentDashboard extends Container {
|
|
|
824
825
|
).trimEnd();
|
|
825
826
|
const content = `---\n${frontmatter}\n---\n\n${spec.systemPrompt.trim()}\n`;
|
|
826
827
|
await Bun.write(filePath, content);
|
|
828
|
+
await refreshAgentDiscovery(this.cwd);
|
|
827
829
|
await this.#reloadData();
|
|
828
830
|
this.#clearCreateFlow();
|
|
829
831
|
this.#notice = `Created agent ${spec.identifier} at ${shortenPath(filePath)}`;
|
|
@@ -123,6 +123,8 @@ export interface AgentHubDeps {
|
|
|
123
123
|
ui?: TUI;
|
|
124
124
|
/** Tool lookup for transcript renderers (labels, custom render functions). */
|
|
125
125
|
getTool?: (name: string) => AgentTool | undefined;
|
|
126
|
+
/** Whether the active registry entry came from a built-in factory. */
|
|
127
|
+
isBuiltInTool?: (name: string) => boolean;
|
|
126
128
|
/** Extension message renderers for custom messages in the transcript. */
|
|
127
129
|
getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
|
|
128
130
|
/** Cwd used by tool renderers for path shortening; defaults to the project dir. */
|
|
@@ -206,6 +208,7 @@ export class AgentHubOverlayComponent extends Container implements SelectListMou
|
|
|
206
208
|
// Transcript-viewer launch deps (passed through to AgentTranscriptViewer).
|
|
207
209
|
#ui: TUI;
|
|
208
210
|
#getTool: ((name: string) => AgentTool | undefined) | undefined;
|
|
211
|
+
#isBuiltInTool: ((name: string) => boolean) | undefined;
|
|
209
212
|
#getMessageRenderer: ((customType: string) => MessageRenderer | undefined) | undefined;
|
|
210
213
|
#cwd: string;
|
|
211
214
|
#hideThinkingBlock: (() => boolean) | undefined;
|
|
@@ -238,6 +241,7 @@ export class AgentHubOverlayComponent extends Container implements SelectListMou
|
|
|
238
241
|
requestComponentRender: () => deps.requestRender(),
|
|
239
242
|
} as unknown as TUI);
|
|
240
243
|
this.#getTool = deps.getTool;
|
|
244
|
+
this.#isBuiltInTool = deps.isBuiltInTool;
|
|
241
245
|
this.#getMessageRenderer = deps.getMessageRenderer;
|
|
242
246
|
this.#cwd = deps.cwd ?? getProjectDir();
|
|
243
247
|
this.#hideThinkingBlock = deps.hideThinkingBlock;
|
|
@@ -364,6 +368,7 @@ export class AgentHubOverlayComponent extends Container implements SelectListMou
|
|
|
364
368
|
lifecycle: this.#remote ? undefined : this.#lifecycle,
|
|
365
369
|
ui: this.#ui,
|
|
366
370
|
getTool: this.#getTool,
|
|
371
|
+
isBuiltInTool: this.#isBuiltInTool,
|
|
367
372
|
getMessageRenderer: this.#getMessageRenderer,
|
|
368
373
|
cwd: this.#cwd,
|
|
369
374
|
hideThinkingBlock: this.#hideThinkingBlock,
|
|
@@ -43,6 +43,8 @@ export interface AgentTranscriptViewerDeps {
|
|
|
43
43
|
lifecycle?: () => AgentLifecycleManager;
|
|
44
44
|
ui: TUI;
|
|
45
45
|
getTool?: (name: string) => AgentTool | undefined;
|
|
46
|
+
/** Whether the active registry entry came from a built-in factory. */
|
|
47
|
+
isBuiltInTool?: (name: string) => boolean;
|
|
46
48
|
getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
|
|
47
49
|
cwd: string;
|
|
48
50
|
hideThinkingBlock?: () => boolean;
|
|
@@ -161,6 +163,7 @@ export class AgentTranscriptViewer implements Component {
|
|
|
161
163
|
this.#builder = new ChatTranscriptBuilder({
|
|
162
164
|
ui: deps.ui,
|
|
163
165
|
getTool: deps.getTool,
|
|
166
|
+
isBuiltInTool: deps.isBuiltInTool,
|
|
164
167
|
getMessageRenderer: deps.getMessageRenderer,
|
|
165
168
|
cwd: deps.cwd,
|
|
166
169
|
hideThinkingBlock: deps.hideThinkingBlock,
|
|
@@ -61,6 +61,8 @@ import { CollapsedSyntheticMessageComponent, UserMessageComponent } from "./user
|
|
|
61
61
|
export interface ChatTranscriptBuilderDeps {
|
|
62
62
|
ui: TUI;
|
|
63
63
|
getTool?: (name: string) => AgentTool | undefined;
|
|
64
|
+
/** Whether the active registry entry came from a built-in factory. */
|
|
65
|
+
isBuiltInTool?: (name: string) => boolean;
|
|
64
66
|
getMessageRenderer?: (customType: string) => MessageRenderer | undefined;
|
|
65
67
|
cwd: string;
|
|
66
68
|
hideThinkingBlock?: () => boolean;
|
|
@@ -392,6 +394,7 @@ export class ChatTranscriptBuilder {
|
|
|
392
394
|
content.name,
|
|
393
395
|
content.arguments,
|
|
394
396
|
{
|
|
397
|
+
useBuiltInRenderer: this.deps.isBuiltInTool?.(content.name) ?? true,
|
|
395
398
|
// Stable ids and Kitty placeholder cells keep images anchored
|
|
396
399
|
// while the transcript viewport scrolls and reflows.
|
|
397
400
|
showImages: settings.get("terminal.showImages"),
|
|
@@ -30,7 +30,6 @@ type ConfigurableEditorAction = Extract<
|
|
|
30
30
|
| "app.model.cycleBackward"
|
|
31
31
|
| "app.model.select"
|
|
32
32
|
| "app.model.selectTemporary"
|
|
33
|
-
| "app.tools.expand"
|
|
34
33
|
| "app.tools.toggleVisibility"
|
|
35
34
|
| "app.thinking.toggle"
|
|
36
35
|
| "app.editor.external"
|
|
@@ -53,7 +52,6 @@ const DEFAULT_ACTION_KEYS: Record<ConfigurableEditorAction, KeyId[]> = {
|
|
|
53
52
|
"app.model.cycleBackward": ["shift+ctrl+p"],
|
|
54
53
|
"app.model.select": ["alt+m"],
|
|
55
54
|
"app.model.selectTemporary": ["alt+p"],
|
|
56
|
-
"app.tools.expand": ["ctrl+o"],
|
|
57
55
|
"app.tools.toggleVisibility": ["ctrl+shift+o"],
|
|
58
56
|
"app.thinking.toggle": ["ctrl+t"],
|
|
59
57
|
"app.editor.external": ["ctrl+g"],
|
|
@@ -551,7 +549,6 @@ export class CustomEditor extends Editor {
|
|
|
551
549
|
onCycleModelForward?: () => void;
|
|
552
550
|
onCycleModelBackward?: () => void;
|
|
553
551
|
onSelectModel?: () => void;
|
|
554
|
-
onExpandTools?: () => void;
|
|
555
552
|
onToggleToolActivity?: () => void;
|
|
556
553
|
onToggleThinking?: () => void;
|
|
557
554
|
onExternalEditor?: () => void;
|
|
@@ -907,12 +904,6 @@ export class CustomEditor extends Editor {
|
|
|
907
904
|
return;
|
|
908
905
|
}
|
|
909
906
|
|
|
910
|
-
// Intercept configured tool output expansion shortcut
|
|
911
|
-
if (this.#matchesAction(canonical, "app.tools.expand") && this.onExpandTools) {
|
|
912
|
-
this.onExpandTools();
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
907
|
// Intercept configured tool activity visibility toggle
|
|
917
908
|
if (this.#matchesAction(canonical, "app.tools.toggleVisibility") && this.onToggleToolActivity) {
|
|
918
909
|
this.onToggleToolActivity();
|
|
@@ -1556,6 +1556,7 @@ export class StatusLineComponent implements Component {
|
|
|
1556
1556
|
return {
|
|
1557
1557
|
session: this.session,
|
|
1558
1558
|
focusedAgentId: this.#focusedAgentId,
|
|
1559
|
+
sessionAccent: this.#resolveSettings().sessionAccent !== false,
|
|
1559
1560
|
activeRepo: activeRepoCache.activeRepo,
|
|
1560
1561
|
width,
|
|
1561
1562
|
options: segmentOptions ?? {},
|
|
@@ -592,10 +592,12 @@ const sessionNameSegment: StatusLineSegment = {
|
|
|
592
592
|
const name = sessionManager?.getSessionName();
|
|
593
593
|
if (!name) return { content: "", visible: false };
|
|
594
594
|
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
595
|
+
const accentEnabled = ctx.sessionAccent !== false;
|
|
596
|
+
const ansi = accentEnabled
|
|
597
|
+
? (getSessionAccentAnsi(
|
|
598
|
+
getSessionAccentHex(name, theme.getMajorThemeColorHexes(), theme.accentSurfaceLuminance),
|
|
599
|
+
) ?? theme.getFgAnsi("accent"))
|
|
600
|
+
: theme.getFgAnsi("accent");
|
|
599
601
|
return { content: `${ansi}${sanitizeStatusText(name)}\x1b[39m`, visible: true };
|
|
600
602
|
},
|
|
601
603
|
};
|
|
@@ -52,6 +52,8 @@ export interface SegmentContext {
|
|
|
52
52
|
session: AgentSession;
|
|
53
53
|
/** Focused subagent id while the view is proxied at its session, undefined otherwise. */
|
|
54
54
|
focusedAgentId?: string | undefined;
|
|
55
|
+
/** Effective `statusLine.sessionAccent`; `false` disables hash-derived accent colors, while `true` or omission enables them. */
|
|
56
|
+
sessionAccent?: boolean;
|
|
55
57
|
activeRepo: ActiveRepoContext | null;
|
|
56
58
|
width: number;
|
|
57
59
|
options: StatusLineSegmentOptions;
|
|
@@ -23,7 +23,7 @@ import { formatDefaultToolExecution } from "../../tools/default-renderer";
|
|
|
23
23
|
import { EVAL_DEFAULT_PREVIEW_LINES } from "../../tools/eval";
|
|
24
24
|
import { isWaitingPollDetails } from "../../tools/hub";
|
|
25
25
|
import { formatStatusIcon, replaceTabs, resolveImageOptions } from "../../tools/render-utils";
|
|
26
|
-
import { type FirstResultViewportRepaint, toolRenderers } from "../../tools/renderers";
|
|
26
|
+
import { type FirstResultViewportRepaint, type ToolRenderer, toolRenderers } from "../../tools/renderers";
|
|
27
27
|
import { TODO_STRIKE_TOTAL_FRAMES, type TodoToolDetails } from "../../tools/todo";
|
|
28
28
|
import type { XdevState } from "../../tools/xdev";
|
|
29
29
|
import { isFramedBlockComponent, markFramedBlockComponent, renderStatusLine, WidthAwareText } from "../../tui";
|
|
@@ -226,6 +226,8 @@ export interface ToolExecutionOptions {
|
|
|
226
226
|
/** Session-persistent edit clipboard register, forked per preview frame. */
|
|
227
227
|
clipboard?: Clipboard;
|
|
228
228
|
showImages?: boolean; // default: true (only used if terminal supports images)
|
|
229
|
+
/** Allow the name-keyed renderer registry only when the active tool is the built-in implementation. */
|
|
230
|
+
useBuiltInRenderer?: boolean;
|
|
229
231
|
editFuzzyThreshold?: number;
|
|
230
232
|
editAllowFuzzy?: boolean;
|
|
231
233
|
/** Live-region probe used to settle detached task progress once the block
|
|
@@ -312,6 +314,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
312
314
|
// forcing the common image-free result to re-shape on every resize tick.
|
|
313
315
|
#renderedImageCount = 0;
|
|
314
316
|
#tool?: AgentTool;
|
|
317
|
+
#renderer?: ToolRenderer;
|
|
315
318
|
#ui: ToolExecutionUi;
|
|
316
319
|
#cwd: string;
|
|
317
320
|
#result?: {
|
|
@@ -398,6 +401,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
398
401
|
super();
|
|
399
402
|
this.#toolName = toolName;
|
|
400
403
|
this.#toolLabel = tool?.label ?? toolName;
|
|
404
|
+
this.#renderer = options.useBuiltInRenderer === false ? undefined : toolRenderers[toolName];
|
|
401
405
|
this.#showImages = options.showImages ?? true;
|
|
402
406
|
this.#editFuzzyThreshold = options.editFuzzyThreshold;
|
|
403
407
|
this.#editAllowFuzzy = options.editAllowFuzzy;
|
|
@@ -418,10 +422,9 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
418
422
|
this.#contentBox = new Box(0, 1);
|
|
419
423
|
this.#contentText = new WidthAwareText(contentWidth => this.#renderDefaultCard(contentWidth), 1, 1);
|
|
420
424
|
|
|
421
|
-
// Use Box for custom tools or built-in tools
|
|
422
|
-
const hasRenderer = toolName in toolRenderers;
|
|
425
|
+
// Use Box for custom tools or built-in tools with rich renderers.
|
|
423
426
|
const hasCustomRenderer = !!(tool?.renderCall || tool?.renderResult);
|
|
424
|
-
this.#usesContentBox = hasCustomRenderer ||
|
|
427
|
+
this.#usesContentBox = hasCustomRenderer || this.#renderer !== undefined;
|
|
425
428
|
if (this.#usesContentBox) {
|
|
426
429
|
this.addChild(this.#contentBox);
|
|
427
430
|
} else {
|
|
@@ -668,12 +671,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
668
671
|
const isStreamingArgs = !this.#argsComplete && (isEditLikeToolName(this.#toolName) || this.#toolName === "write");
|
|
669
672
|
const isBackgroundAsyncRunning =
|
|
670
673
|
(this.#result?.details as { async?: { state?: string } } | undefined)?.async?.state === "running";
|
|
671
|
-
const renderer =
|
|
672
|
-
| {
|
|
673
|
-
animatedPendingPreview?: boolean | ((args: unknown) => boolean);
|
|
674
|
-
animatedPartialResult?: boolean | ((args: unknown) => boolean);
|
|
675
|
-
}
|
|
676
|
-
| undefined;
|
|
674
|
+
const renderer = this.#renderer;
|
|
677
675
|
const pendingAnimation = renderer?.animatedPendingPreview;
|
|
678
676
|
const partialAnimation = renderer?.animatedPartialResult;
|
|
679
677
|
const pendingCallConsumesSpinner =
|
|
@@ -933,7 +931,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
933
931
|
|
|
934
932
|
#rendererFlag(name: "forceResultViewportRepaintOnSettle"): boolean {
|
|
935
933
|
const toolValue = (this.#tool as Record<string, unknown> | undefined)?.[name];
|
|
936
|
-
const rendererValue =
|
|
934
|
+
const rendererValue = this.#renderer?.[name];
|
|
937
935
|
return toolValue === true || (toolValue === undefined && rendererValue === true);
|
|
938
936
|
}
|
|
939
937
|
|
|
@@ -948,8 +946,7 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
948
946
|
if (this.#result !== undefined) return false;
|
|
949
947
|
const toolValue = (this.#tool as { forceFirstResultViewportRepaint?: FirstResultViewportRepaint } | undefined)
|
|
950
948
|
?.forceFirstResultViewportRepaint;
|
|
951
|
-
const value =
|
|
952
|
-
toolValue !== undefined ? toolValue : toolRenderers[this.#toolName]?.forceFirstResultViewportRepaint;
|
|
949
|
+
const value = toolValue !== undefined ? toolValue : this.#renderer?.forceFirstResultViewportRepaint;
|
|
953
950
|
if (typeof value === "function") return value(this.#args, this.#renderState);
|
|
954
951
|
return value === true;
|
|
955
952
|
}
|
|
@@ -1101,9 +1098,9 @@ export class ToolExecutionComponent extends Container implements NativeScrollbac
|
|
|
1101
1098
|
const customFramed = this.#contentBox.children.some(isFramedBlockComponent);
|
|
1102
1099
|
this.#contentBox.setPaddingX(customFramed ? 0 : 1);
|
|
1103
1100
|
this.#contentBox.setBgFn(customFramed ? undefined : stateBgFn);
|
|
1104
|
-
} else if (this.#
|
|
1105
|
-
//
|
|
1106
|
-
const renderer =
|
|
1101
|
+
} else if (this.#renderer) {
|
|
1102
|
+
// The active registry entry is a built-in tool with a rich renderer.
|
|
1103
|
+
const renderer = this.#renderer;
|
|
1107
1104
|
|
|
1108
1105
|
// Clean up previous multi-file boxes
|
|
1109
1106
|
for (const box of this.#multiFileBoxes) {
|