@oh-my-pi/pi-coding-agent 13.5.7 → 13.6.0
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 +30 -1
- package/package.json +7 -7
- package/src/cli/args.ts +7 -0
- package/src/cli/stats-cli.ts +5 -0
- package/src/config/model-registry.ts +99 -9
- package/src/config/settings-schema.ts +22 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/internal-urls/index.ts +2 -1
- package/src/internal-urls/mcp-protocol.ts +156 -0
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +3 -3
- package/src/ipy/prelude.py +1 -0
- package/src/mcp/client.ts +235 -2
- package/src/mcp/index.ts +1 -1
- package/src/mcp/manager.ts +399 -5
- package/src/mcp/oauth-flow.ts +26 -1
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +455 -0
- package/src/mcp/types.ts +140 -0
- package/src/modes/components/footer.ts +10 -4
- package/src/modes/components/settings-defs.ts +15 -1
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/presets.ts +6 -6
- package/src/modes/components/status-line/segments.ts +27 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +109 -5
- package/src/modes/controllers/command-controller.ts +12 -2
- package/src/modes/controllers/extension-ui-controller.ts +12 -21
- package/src/modes/controllers/mcp-command-controller.ts +577 -14
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/theme/theme.ts +6 -0
- package/src/prompts/tools/hashline.md +4 -3
- package/src/sdk.ts +115 -3
- package/src/session/agent-session.ts +19 -4
- package/src/session/session-manager.ts +17 -5
- package/src/slash-commands/builtin-registry.ts +10 -0
- package/src/task/executor.ts +37 -3
- package/src/task/index.ts +37 -5
- package/src/task/isolation-backend.ts +72 -0
- package/src/task/render.ts +6 -1
- package/src/task/types.ts +1 -0
- package/src/task/worktree.ts +67 -5
- package/src/tools/index.ts +1 -1
- package/src/tools/path-utils.ts +2 -1
- package/src/tools/read.ts +3 -7
- package/src/utils/open.ts +1 -1
|
@@ -122,17 +122,18 @@ Range — add `end`:
|
|
|
122
122
|
{{hlinefull 62 " return null;"}}
|
|
123
123
|
{{hlinefull 63 " }"}}
|
|
124
124
|
```
|
|
125
|
-
|
|
125
|
+
Include the closing `}` in the replaced range — stopping one line short orphans the brace or duplicates it.
|
|
126
126
|
```
|
|
127
127
|
{
|
|
128
128
|
path: "…",
|
|
129
129
|
edits: [{
|
|
130
130
|
op: "replace",
|
|
131
131
|
pos: {{hlinejsonref 61 " console.error(err);"}},
|
|
132
|
-
end: {{hlinejsonref
|
|
132
|
+
end: {{hlinejsonref 63 " }"}},
|
|
133
133
|
lines: [
|
|
134
134
|
" if (isEnoent(err)) return null;",
|
|
135
|
-
" throw err;"
|
|
135
|
+
" throw err;",
|
|
136
|
+
" }"
|
|
136
137
|
]
|
|
137
138
|
}]
|
|
138
139
|
}
|
package/src/sdk.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { initializeWithSettings } from "./discovery";
|
|
|
29
29
|
import { TtsrManager } from "./export/ttsr";
|
|
30
30
|
import {
|
|
31
31
|
type CustomCommandsLoadResult,
|
|
32
|
+
type LoadedCustomCommand,
|
|
32
33
|
loadCustomCommands as loadCustomCommandsInternal,
|
|
33
34
|
} from "./extensibility/custom-commands";
|
|
34
35
|
import { discoverAndLoadCustomTools } from "./extensibility/custom-tools";
|
|
@@ -55,6 +56,7 @@ import {
|
|
|
55
56
|
InternalUrlRouter,
|
|
56
57
|
JobsProtocolHandler,
|
|
57
58
|
LocalProtocolHandler,
|
|
59
|
+
McpProtocolHandler,
|
|
58
60
|
MemoryProtocolHandler,
|
|
59
61
|
PiProtocolHandler,
|
|
60
62
|
RuleProtocolHandler,
|
|
@@ -516,6 +518,54 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
516
518
|
|
|
517
519
|
// Factory
|
|
518
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Build LoadedCustomCommand entries for all MCP prompts across connected servers.
|
|
523
|
+
* These are re-created whenever prompts change (setOnPromptsChanged callback).
|
|
524
|
+
*/
|
|
525
|
+
function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
|
526
|
+
const commands: LoadedCustomCommand[] = [];
|
|
527
|
+
for (const serverName of manager.getConnectedServers()) {
|
|
528
|
+
const prompts = manager.getServerPrompts(serverName);
|
|
529
|
+
if (!prompts?.length) continue;
|
|
530
|
+
for (const prompt of prompts) {
|
|
531
|
+
const commandName = `${serverName}:${prompt.name}`;
|
|
532
|
+
commands.push({
|
|
533
|
+
path: `mcp:${commandName}`,
|
|
534
|
+
resolvedPath: `mcp:${commandName}`,
|
|
535
|
+
source: "bundled",
|
|
536
|
+
command: {
|
|
537
|
+
name: commandName,
|
|
538
|
+
description: prompt.description ?? `MCP prompt from ${serverName}`,
|
|
539
|
+
async execute(args: string[]) {
|
|
540
|
+
const promptArgs: Record<string, string> = {};
|
|
541
|
+
for (const arg of args) {
|
|
542
|
+
const eqIdx = arg.indexOf("=");
|
|
543
|
+
if (eqIdx > 0) {
|
|
544
|
+
promptArgs[arg.slice(0, eqIdx)] = arg.slice(eqIdx + 1);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
const result = await manager.executePrompt(serverName, prompt.name, promptArgs);
|
|
548
|
+
if (!result) return "";
|
|
549
|
+
const parts: string[] = [];
|
|
550
|
+
for (const msg of result.messages) {
|
|
551
|
+
const contentItems = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
552
|
+
for (const item of contentItems) {
|
|
553
|
+
if (item.type === "text") {
|
|
554
|
+
parts.push(item.text);
|
|
555
|
+
} else if (item.type === "resource") {
|
|
556
|
+
const resource = item.resource;
|
|
557
|
+
if (resource.text) parts.push(resource.text);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
return parts.join("\n\n");
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return commands;
|
|
568
|
+
}
|
|
519
569
|
/**
|
|
520
570
|
* Create an AgentSession with the specified options.
|
|
521
571
|
*
|
|
@@ -823,7 +873,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
823
873
|
pendingActionStore,
|
|
824
874
|
};
|
|
825
875
|
|
|
826
|
-
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, local://)
|
|
876
|
+
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, mcp://, local://)
|
|
827
877
|
const internalRouter = new InternalUrlRouter();
|
|
828
878
|
const getArtifactsDir = () => sessionManager.getArtifactsDir();
|
|
829
879
|
internalRouter.register(new AgentProtocolHandler({ getArtifactsDir }));
|
|
@@ -851,6 +901,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
851
901
|
);
|
|
852
902
|
internalRouter.register(new PiProtocolHandler());
|
|
853
903
|
internalRouter.register(new JobsProtocolHandler({ getAsyncJobManager: () => asyncJobManager }));
|
|
904
|
+
internalRouter.register(new McpProtocolHandler({ getMcpManager: () => mcpManager }));
|
|
854
905
|
toolSession.internalRouter = internalRouter;
|
|
855
906
|
toolSession.getArtifactsDir = getArtifactsDir;
|
|
856
907
|
toolSession.agentOutputManager = new AgentOutputManager(
|
|
@@ -885,6 +936,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
885
936
|
mcpManager = mcpResult.manager;
|
|
886
937
|
toolSession.mcpManager = mcpManager;
|
|
887
938
|
|
|
939
|
+
if (settings.get("mcp.notifications")) {
|
|
940
|
+
mcpManager.setNotificationsEnabled(true);
|
|
941
|
+
}
|
|
888
942
|
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
889
943
|
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
890
944
|
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
@@ -1139,6 +1193,26 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1139
1193
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1140
1194
|
toolContextStore.setToolNames(toolNames);
|
|
1141
1195
|
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1196
|
+
|
|
1197
|
+
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1198
|
+
const serverInstructions = mcpManager?.getServerInstructions();
|
|
1199
|
+
let appendPrompt: string | undefined = memoryInstructions ?? undefined;
|
|
1200
|
+
if (serverInstructions && serverInstructions.size > 0) {
|
|
1201
|
+
const MAX_INSTRUCTIONS_LENGTH = 4000;
|
|
1202
|
+
const parts: string[] = [];
|
|
1203
|
+
if (appendPrompt) parts.push(appendPrompt);
|
|
1204
|
+
parts.push(
|
|
1205
|
+
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
1206
|
+
);
|
|
1207
|
+
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
1208
|
+
const truncated =
|
|
1209
|
+
srvInstructions.length > MAX_INSTRUCTIONS_LENGTH
|
|
1210
|
+
? `${srvInstructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
1211
|
+
: srvInstructions;
|
|
1212
|
+
parts.push(`### ${srvName}\n${truncated}`);
|
|
1213
|
+
}
|
|
1214
|
+
appendPrompt = parts.join("\n\n");
|
|
1215
|
+
}
|
|
1142
1216
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
1143
1217
|
cwd,
|
|
1144
1218
|
skills,
|
|
@@ -1147,7 +1221,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1147
1221
|
toolNames,
|
|
1148
1222
|
rules: rulebookRules,
|
|
1149
1223
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1150
|
-
appendSystemPrompt:
|
|
1224
|
+
appendSystemPrompt: appendPrompt,
|
|
1151
1225
|
repeatToolDescriptions,
|
|
1152
1226
|
eagerTasks,
|
|
1153
1227
|
intentField,
|
|
@@ -1166,7 +1240,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1166
1240
|
rules: rulebookRules,
|
|
1167
1241
|
skillsSettings: settings.getGroup("skills") as SkillsSettings,
|
|
1168
1242
|
customPrompt: options.systemPrompt,
|
|
1169
|
-
appendSystemPrompt:
|
|
1243
|
+
appendSystemPrompt: appendPrompt,
|
|
1170
1244
|
repeatToolDescriptions,
|
|
1171
1245
|
eagerTasks,
|
|
1172
1246
|
intentField,
|
|
@@ -1410,6 +1484,44 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1410
1484
|
taskDepth,
|
|
1411
1485
|
});
|
|
1412
1486
|
|
|
1487
|
+
// Wire MCP manager callbacks to session for reactive tool updates
|
|
1488
|
+
if (mcpManager) {
|
|
1489
|
+
mcpManager.setOnToolsChanged(tools => {
|
|
1490
|
+
void session.refreshMCPTools(tools);
|
|
1491
|
+
});
|
|
1492
|
+
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
1493
|
+
mcpManager.setOnPromptsChanged(serverName => {
|
|
1494
|
+
const promptCommands = buildMCPPromptCommands(mcpManager);
|
|
1495
|
+
session.setMCPPromptCommands(promptCommands);
|
|
1496
|
+
logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
|
|
1497
|
+
});
|
|
1498
|
+
const notificationDebounceTimers = new Map<string, Timer>();
|
|
1499
|
+
const clearDebounceTimers = () => {
|
|
1500
|
+
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
1501
|
+
notificationDebounceTimers.clear();
|
|
1502
|
+
};
|
|
1503
|
+
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
1504
|
+
mcpManager.setOnResourcesChanged((serverName, uri) => {
|
|
1505
|
+
logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
|
|
1506
|
+
if (!settings.get("mcp.notifications")) return;
|
|
1507
|
+
const debounceMs = (settings.get("mcp.notificationDebounceMs") as number) ?? 500;
|
|
1508
|
+
const key = `${serverName}:${uri}`;
|
|
1509
|
+
const existing = notificationDebounceTimers.get(key);
|
|
1510
|
+
if (existing) clearTimeout(existing);
|
|
1511
|
+
notificationDebounceTimers.set(
|
|
1512
|
+
key,
|
|
1513
|
+
setTimeout(() => {
|
|
1514
|
+
notificationDebounceTimers.delete(key);
|
|
1515
|
+
// Re-check: user may have disabled notifications during the debounce window
|
|
1516
|
+
if (!settings.get("mcp.notifications")) return;
|
|
1517
|
+
void session.followUp(
|
|
1518
|
+
`[MCP notification] Server "${serverName}" reports resource \`${uri}\` was updated. Use read(path="mcp://${uri}") to inspect if relevant.`,
|
|
1519
|
+
);
|
|
1520
|
+
}, debounceMs),
|
|
1521
|
+
);
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1413
1525
|
return {
|
|
1414
1526
|
session,
|
|
1415
1527
|
extensionsResult,
|
|
@@ -237,6 +237,7 @@ export interface SessionStats {
|
|
|
237
237
|
cacheWrite: number;
|
|
238
238
|
total: number;
|
|
239
239
|
};
|
|
240
|
+
premiumRequests: number;
|
|
240
241
|
cost: number;
|
|
241
242
|
}
|
|
242
243
|
|
|
@@ -352,6 +353,8 @@ export class AgentSession {
|
|
|
352
353
|
|
|
353
354
|
// Custom commands (TypeScript slash commands)
|
|
354
355
|
#customCommands: LoadedCustomCommand[] = [];
|
|
356
|
+
/** MCP prompt commands (updated dynamically when prompts are loaded) */
|
|
357
|
+
#mcpPromptCommands: LoadedCustomCommand[] = [];
|
|
355
358
|
|
|
356
359
|
#skillsSettings: Required<SkillsSettings> | undefined;
|
|
357
360
|
|
|
@@ -1769,9 +1772,15 @@ export class AgentSession {
|
|
|
1769
1772
|
this.#slashCommands = [...slashCommands];
|
|
1770
1773
|
}
|
|
1771
1774
|
|
|
1772
|
-
/** Custom commands (TypeScript slash commands) */
|
|
1775
|
+
/** Custom commands (TypeScript slash commands and MCP prompts) */
|
|
1773
1776
|
get customCommands(): ReadonlyArray<LoadedCustomCommand> {
|
|
1774
|
-
return this.#customCommands;
|
|
1777
|
+
if (this.#mcpPromptCommands.length === 0) return this.#customCommands;
|
|
1778
|
+
return [...this.#customCommands, ...this.#mcpPromptCommands];
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
/** Update the MCP prompt commands list. Called when server prompts are (re)loaded. */
|
|
1782
|
+
setMCPPromptCommands(commands: LoadedCustomCommand[]): void {
|
|
1783
|
+
this.#mcpPromptCommands = commands;
|
|
1775
1784
|
}
|
|
1776
1785
|
|
|
1777
1786
|
// =========================================================================
|
|
@@ -2173,7 +2182,7 @@ export class AgentSession {
|
|
|
2173
2182
|
* If the command returns void, returns empty string to indicate it was handled.
|
|
2174
2183
|
*/
|
|
2175
2184
|
async #tryExecuteCustomCommand(text: string): Promise<string | null> {
|
|
2176
|
-
if (this.#customCommands.length === 0) return null;
|
|
2185
|
+
if (this.#customCommands.length === 0 && this.#mcpPromptCommands.length === 0) return null;
|
|
2177
2186
|
|
|
2178
2187
|
// Parse command name and args
|
|
2179
2188
|
const spaceIndex = text.indexOf(" ");
|
|
@@ -2181,7 +2190,9 @@ export class AgentSession {
|
|
|
2181
2190
|
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1);
|
|
2182
2191
|
|
|
2183
2192
|
// Find matching command
|
|
2184
|
-
const loaded =
|
|
2193
|
+
const loaded =
|
|
2194
|
+
this.#customCommands.find(c => c.command.name === commandName) ??
|
|
2195
|
+
this.#mcpPromptCommands.find(c => c.command.name === commandName);
|
|
2185
2196
|
if (!loaded) return null;
|
|
2186
2197
|
|
|
2187
2198
|
// Get command context from extension runner (includes session control methods)
|
|
@@ -4781,6 +4792,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4781
4792
|
let totalCacheWrite = 0;
|
|
4782
4793
|
let totalCost = 0;
|
|
4783
4794
|
|
|
4795
|
+
let totalPremiumRequests = 0;
|
|
4784
4796
|
const getTaskToolUsage = (details: unknown): Usage | undefined => {
|
|
4785
4797
|
if (!details || typeof details !== "object") return undefined;
|
|
4786
4798
|
const record = details as Record<string, unknown>;
|
|
@@ -4797,6 +4809,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4797
4809
|
totalOutput += assistantMsg.usage.output;
|
|
4798
4810
|
totalCacheRead += assistantMsg.usage.cacheRead;
|
|
4799
4811
|
totalCacheWrite += assistantMsg.usage.cacheWrite;
|
|
4812
|
+
totalPremiumRequests += assistantMsg.usage.premiumRequests ?? 0;
|
|
4800
4813
|
totalCost += assistantMsg.usage.cost.total;
|
|
4801
4814
|
}
|
|
4802
4815
|
|
|
@@ -4807,6 +4820,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4807
4820
|
totalOutput += usage.output;
|
|
4808
4821
|
totalCacheRead += usage.cacheRead;
|
|
4809
4822
|
totalCacheWrite += usage.cacheWrite;
|
|
4823
|
+
totalPremiumRequests += usage.premiumRequests ?? 0;
|
|
4810
4824
|
totalCost += usage.cost.total;
|
|
4811
4825
|
}
|
|
4812
4826
|
}
|
|
@@ -4828,6 +4842,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
4828
4842
|
total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
|
|
4829
4843
|
},
|
|
4830
4844
|
cost: totalCost,
|
|
4845
|
+
premiumRequests: totalPremiumRequests,
|
|
4831
4846
|
};
|
|
4832
4847
|
}
|
|
4833
4848
|
|
|
@@ -1049,6 +1049,7 @@ export interface UsageStatistics {
|
|
|
1049
1049
|
output: number;
|
|
1050
1050
|
cacheRead: number;
|
|
1051
1051
|
cacheWrite: number;
|
|
1052
|
+
premiumRequests: number;
|
|
1052
1053
|
cost: number;
|
|
1053
1054
|
}
|
|
1054
1055
|
|
|
@@ -1144,9 +1145,16 @@ export class SessionManager {
|
|
|
1144
1145
|
#fileEntries: FileEntry[] = [];
|
|
1145
1146
|
#byId: Map<string, SessionEntry> = new Map();
|
|
1146
1147
|
#labelsById: Map<string, string> = new Map();
|
|
1147
|
-
#leafId
|
|
1148
|
-
#usageStatistics
|
|
1149
|
-
|
|
1148
|
+
#leafId = null as string | null;
|
|
1149
|
+
#usageStatistics = {
|
|
1150
|
+
input: 0,
|
|
1151
|
+
output: 0,
|
|
1152
|
+
cacheRead: 0,
|
|
1153
|
+
cacheWrite: 0,
|
|
1154
|
+
premiumRequests: 0,
|
|
1155
|
+
cost: 0,
|
|
1156
|
+
} satisfies UsageStatistics;
|
|
1157
|
+
#persistWriter = undefined as NdjsonFileWriter | undefined;
|
|
1150
1158
|
#persistWriterPath: string | undefined;
|
|
1151
1159
|
#persistChain: Promise<void> = Promise.resolve();
|
|
1152
1160
|
#persistError: Error | undefined;
|
|
@@ -1373,7 +1381,7 @@ export class SessionManager {
|
|
|
1373
1381
|
this.#labelsById.clear();
|
|
1374
1382
|
this.#leafId = null;
|
|
1375
1383
|
this.#flushed = false;
|
|
1376
|
-
this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
1384
|
+
this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, premiumRequests: 0, cost: 0 };
|
|
1377
1385
|
|
|
1378
1386
|
if (this.persist) {
|
|
1379
1387
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
@@ -1387,7 +1395,7 @@ export class SessionManager {
|
|
|
1387
1395
|
this.#byId.clear();
|
|
1388
1396
|
this.#labelsById.clear();
|
|
1389
1397
|
this.#leafId = null;
|
|
1390
|
-
this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
1398
|
+
this.#usageStatistics = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, premiumRequests: 0, cost: 0 };
|
|
1391
1399
|
for (const entry of this.#fileEntries) {
|
|
1392
1400
|
if (entry.type === "session") continue;
|
|
1393
1401
|
this.#byId.set(entry.id, entry);
|
|
@@ -1405,6 +1413,7 @@ export class SessionManager {
|
|
|
1405
1413
|
this.#usageStatistics.output += usage.output;
|
|
1406
1414
|
this.#usageStatistics.cacheRead += usage.cacheRead;
|
|
1407
1415
|
this.#usageStatistics.cacheWrite += usage.cacheWrite;
|
|
1416
|
+
this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
|
|
1408
1417
|
this.#usageStatistics.cost += usage.cost.total;
|
|
1409
1418
|
}
|
|
1410
1419
|
|
|
@@ -1415,6 +1424,7 @@ export class SessionManager {
|
|
|
1415
1424
|
this.#usageStatistics.output += usage.output;
|
|
1416
1425
|
this.#usageStatistics.cacheRead += usage.cacheRead;
|
|
1417
1426
|
this.#usageStatistics.cacheWrite += usage.cacheWrite;
|
|
1427
|
+
this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
|
|
1418
1428
|
this.#usageStatistics.cost += usage.cost.total;
|
|
1419
1429
|
}
|
|
1420
1430
|
}
|
|
@@ -1679,6 +1689,7 @@ export class SessionManager {
|
|
|
1679
1689
|
this.#usageStatistics.output += usage.output;
|
|
1680
1690
|
this.#usageStatistics.cacheRead += usage.cacheRead;
|
|
1681
1691
|
this.#usageStatistics.cacheWrite += usage.cacheWrite;
|
|
1692
|
+
this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
|
|
1682
1693
|
this.#usageStatistics.cost += usage.cost.total;
|
|
1683
1694
|
}
|
|
1684
1695
|
|
|
@@ -1689,6 +1700,7 @@ export class SessionManager {
|
|
|
1689
1700
|
this.#usageStatistics.output += usage.output;
|
|
1690
1701
|
this.#usageStatistics.cacheRead += usage.cacheRead;
|
|
1691
1702
|
this.#usageStatistics.cacheWrite += usage.cacheWrite;
|
|
1703
|
+
this.#usageStatistics.premiumRequests += usage.premiumRequests ?? 0;
|
|
1692
1704
|
this.#usageStatistics.cost += usage.cost.total;
|
|
1693
1705
|
}
|
|
1694
1706
|
}
|
|
@@ -315,7 +315,17 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
315
315
|
{ name: "unauth", description: "Remove OAuth auth from a server", usage: "<name>" },
|
|
316
316
|
{ name: "enable", description: "Enable an MCP server", usage: "<name>" },
|
|
317
317
|
{ name: "disable", description: "Disable an MCP server", usage: "<name>" },
|
|
318
|
+
{
|
|
319
|
+
name: "smithery-search",
|
|
320
|
+
description: "Search Smithery registry and deploy an MCP server",
|
|
321
|
+
usage: "<keyword> [--scope project|user] [--limit <1-100>] [--semantic]",
|
|
322
|
+
},
|
|
323
|
+
{ name: "smithery-login", description: "Login to Smithery and cache API key" },
|
|
324
|
+
{ name: "smithery-logout", description: "Remove cached Smithery API key" },
|
|
318
325
|
{ name: "reload", description: "Force reload MCP runtime tools" },
|
|
326
|
+
{ name: "resources", description: "List available resources from connected servers" },
|
|
327
|
+
{ name: "prompts", description: "List available prompts from connected servers" },
|
|
328
|
+
{ name: "notifications", description: "Show notification capabilities and subscriptions" },
|
|
319
329
|
{ name: "help", description: "Show help message" },
|
|
320
330
|
],
|
|
321
331
|
allowArgs: true,
|
package/src/task/executor.ts
CHANGED
|
@@ -499,12 +499,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
499
499
|
description: options.description,
|
|
500
500
|
exitCode: 1,
|
|
501
501
|
output: "",
|
|
502
|
-
stderr: "
|
|
502
|
+
stderr: "Cancelled before start",
|
|
503
503
|
truncated: false,
|
|
504
504
|
durationMs: 0,
|
|
505
505
|
tokens: 0,
|
|
506
506
|
modelOverride,
|
|
507
|
-
error: "
|
|
507
|
+
error: "Cancelled before start",
|
|
508
|
+
aborted: true,
|
|
509
|
+
abortReason: "Cancelled before start",
|
|
508
510
|
};
|
|
509
511
|
}
|
|
510
512
|
|
|
@@ -612,6 +614,17 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
612
614
|
signal.addEventListener("abort", onAbort, { once: true, signal: listenerSignal });
|
|
613
615
|
}
|
|
614
616
|
|
|
617
|
+
const resolveSignalAbortReason = (): string => {
|
|
618
|
+
const reason = signal?.reason;
|
|
619
|
+
if (reason instanceof Error) {
|
|
620
|
+
const message = reason.message.trim();
|
|
621
|
+
if (message.length > 0) return message;
|
|
622
|
+
} else if (typeof reason === "string") {
|
|
623
|
+
const message = reason.trim();
|
|
624
|
+
if (message.length > 0) return message;
|
|
625
|
+
}
|
|
626
|
+
return "Cancelled by caller";
|
|
627
|
+
};
|
|
615
628
|
const PROGRESS_COALESCE_MS = 150;
|
|
616
629
|
let lastProgressEmitMs = 0;
|
|
617
630
|
let progressTimeoutId: NodeJS.Timeout | null = null;
|
|
@@ -898,16 +911,20 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
898
911
|
exitCode: number;
|
|
899
912
|
error?: string;
|
|
900
913
|
aborted?: boolean;
|
|
914
|
+
abortReason?: string;
|
|
901
915
|
durationMs: number;
|
|
902
916
|
}> => {
|
|
903
917
|
const sessionAbortController = new AbortController();
|
|
904
918
|
let exitCode = 0;
|
|
905
919
|
let error: string | undefined;
|
|
906
920
|
let aborted = false;
|
|
907
|
-
|
|
921
|
+
let abortReasonText: string | undefined;
|
|
908
922
|
const checkAbort = () => {
|
|
909
923
|
if (abortSignal.aborted) {
|
|
910
924
|
aborted = abortReason === "signal" || abortReason === undefined;
|
|
925
|
+
if (aborted) {
|
|
926
|
+
abortReasonText ??= resolveSignalAbortReason();
|
|
927
|
+
}
|
|
911
928
|
exitCode = 1;
|
|
912
929
|
throw new ToolAbortError();
|
|
913
930
|
}
|
|
@@ -1096,6 +1113,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1096
1113
|
if (!submitResultCalled && !abortSignal.aborted) {
|
|
1097
1114
|
aborted = true;
|
|
1098
1115
|
exitCode = 1;
|
|
1116
|
+
abortReasonText ??= SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
1099
1117
|
error ??= SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
|
|
1100
1118
|
}
|
|
1101
1119
|
|
|
@@ -1103,6 +1121,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1103
1121
|
if (lastAssistant) {
|
|
1104
1122
|
if (lastAssistant.stopReason === "aborted") {
|
|
1105
1123
|
aborted = abortReason === "signal" || abortReason === undefined;
|
|
1124
|
+
if (aborted) {
|
|
1125
|
+
abortReasonText ??= resolveSignalAbortReason();
|
|
1126
|
+
}
|
|
1106
1127
|
exitCode = 1;
|
|
1107
1128
|
} else if (lastAssistant.stopReason === "error") {
|
|
1108
1129
|
exitCode = 1;
|
|
@@ -1117,6 +1138,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1117
1138
|
} finally {
|
|
1118
1139
|
if (abortSignal.aborted) {
|
|
1119
1140
|
aborted = abortReason === "signal" || abortReason === undefined;
|
|
1141
|
+
if (aborted) {
|
|
1142
|
+
abortReasonText ??= resolveSignalAbortReason();
|
|
1143
|
+
}
|
|
1120
1144
|
if (exitCode === 0) exitCode = 1;
|
|
1121
1145
|
}
|
|
1122
1146
|
sessionAbortController.abort();
|
|
@@ -1143,6 +1167,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1143
1167
|
exitCode,
|
|
1144
1168
|
error,
|
|
1145
1169
|
aborted,
|
|
1170
|
+
abortReason: aborted ? abortReasonText : undefined,
|
|
1146
1171
|
durationMs: Date.now() - startTime,
|
|
1147
1172
|
};
|
|
1148
1173
|
};
|
|
@@ -1178,6 +1203,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1178
1203
|
rawOutput = finalized.rawOutput;
|
|
1179
1204
|
exitCode = finalized.exitCode;
|
|
1180
1205
|
stderr = finalized.stderr;
|
|
1206
|
+
const lastSubmitResult = submitResultItems?.[submitResultItems.length - 1];
|
|
1207
|
+
const submitResultAbortReason =
|
|
1208
|
+
lastSubmitResult?.status === "aborted" ? lastSubmitResult.error || "Subagent aborted task" : undefined;
|
|
1181
1209
|
const { abortedViaSubmitResult, hasSubmitResult } = finalized;
|
|
1182
1210
|
const { content: truncatedOutput, truncated } = truncateTail(rawOutput, {
|
|
1183
1211
|
maxBytes: MAX_OUTPUT_BYTES,
|
|
@@ -1203,6 +1231,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1203
1231
|
|
|
1204
1232
|
// Update final progress
|
|
1205
1233
|
const wasAborted = abortedViaSubmitResult || (!hasSubmitResult && (done.aborted || signal?.aborted || false));
|
|
1234
|
+
const finalAbortReason = wasAborted
|
|
1235
|
+
? abortedViaSubmitResult
|
|
1236
|
+
? submitResultAbortReason
|
|
1237
|
+
: (done.abortReason ?? (signal?.aborted ? resolveSignalAbortReason() : "Subagent aborted task"))
|
|
1238
|
+
: undefined;
|
|
1206
1239
|
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
|
1207
1240
|
scheduleProgress(true);
|
|
1208
1241
|
|
|
@@ -1223,6 +1256,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1223
1256
|
modelOverride,
|
|
1224
1257
|
error: exitCode !== 0 && stderr ? stderr : undefined,
|
|
1225
1258
|
aborted: wasAborted,
|
|
1259
|
+
abortReason: finalAbortReason,
|
|
1226
1260
|
usage: hasUsage ? accumulatedUsage : undefined,
|
|
1227
1261
|
outputPath,
|
|
1228
1262
|
extractedToolData: progress.extractedToolData,
|
package/src/task/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import "../tools/review";
|
|
|
32
32
|
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
33
33
|
import { discoverAgents, getAgent } from "./discovery";
|
|
34
34
|
import { runSubprocess } from "./executor";
|
|
35
|
+
import { resolveIsolationBackendForTaskExecution } from "./isolation-backend";
|
|
35
36
|
import { AgentOutputManager } from "./output-manager";
|
|
36
37
|
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
37
38
|
import { renderCall, renderResult } from "./render";
|
|
@@ -52,10 +53,12 @@ import {
|
|
|
52
53
|
captureBaseline,
|
|
53
54
|
captureDeltaPatch,
|
|
54
55
|
cleanupFuseOverlay,
|
|
56
|
+
cleanupProjfsOverlay,
|
|
55
57
|
cleanupTaskBranches,
|
|
56
58
|
cleanupWorktree,
|
|
57
59
|
commitToBranch,
|
|
58
60
|
ensureFuseOverlay,
|
|
61
|
+
ensureProjfsOverlay,
|
|
59
62
|
ensureWorktree,
|
|
60
63
|
getRepoRoot,
|
|
61
64
|
mergeTaskBranches,
|
|
@@ -442,7 +445,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
442
445
|
content: [
|
|
443
446
|
{
|
|
444
447
|
type: "text",
|
|
445
|
-
text: "Task isolation is disabled. Remove the isolated argument or set task.isolation.mode to 'worktree' or 'fuse-
|
|
448
|
+
text: "Task isolation is disabled. Remove the isolated argument or set task.isolation.mode to 'worktree', 'fuse-overlay', or 'fuse-projfs'.",
|
|
446
449
|
},
|
|
447
450
|
],
|
|
448
451
|
details: {
|
|
@@ -605,6 +608,29 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
605
608
|
}
|
|
606
609
|
}
|
|
607
610
|
|
|
611
|
+
let effectiveIsolationMode = isolationMode;
|
|
612
|
+
let isolationBackendWarning = "";
|
|
613
|
+
try {
|
|
614
|
+
const resolvedIsolation = await resolveIsolationBackendForTaskExecution(isolationMode, isIsolated, repoRoot);
|
|
615
|
+
effectiveIsolationMode = resolvedIsolation.effectiveIsolationMode;
|
|
616
|
+
isolationBackendWarning = resolvedIsolation.warning;
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
619
|
+
return {
|
|
620
|
+
content: [
|
|
621
|
+
{
|
|
622
|
+
type: "text",
|
|
623
|
+
text: message,
|
|
624
|
+
},
|
|
625
|
+
],
|
|
626
|
+
details: {
|
|
627
|
+
projectAgentsDir,
|
|
628
|
+
results: [],
|
|
629
|
+
totalDurationMs: Date.now() - startTime,
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
608
634
|
// Derive artifacts directory
|
|
609
635
|
const sessionFile = this.session.getSessionFile();
|
|
610
636
|
const artifactsDir = sessionFile ? sessionFile.slice(0, -6) : null;
|
|
@@ -761,8 +787,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
761
787
|
}
|
|
762
788
|
const taskBaseline = structuredClone(baseline);
|
|
763
789
|
|
|
764
|
-
if (
|
|
790
|
+
if (effectiveIsolationMode === "fuse-overlay") {
|
|
765
791
|
isolationDir = await ensureFuseOverlay(repoRoot, task.id);
|
|
792
|
+
} else if (effectiveIsolationMode === "fuse-projfs") {
|
|
793
|
+
isolationDir = await ensureProjfsOverlay(repoRoot, task.id);
|
|
766
794
|
} else {
|
|
767
795
|
isolationDir = await ensureWorktree(repoRoot, task.id);
|
|
768
796
|
await applyBaseline(isolationDir, taskBaseline);
|
|
@@ -871,8 +899,10 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
871
899
|
};
|
|
872
900
|
} finally {
|
|
873
901
|
if (isolationDir) {
|
|
874
|
-
if (
|
|
902
|
+
if (effectiveIsolationMode === "fuse-overlay") {
|
|
875
903
|
await cleanupFuseOverlay(isolationDir);
|
|
904
|
+
} else if (effectiveIsolationMode === "fuse-projfs") {
|
|
905
|
+
await cleanupProjfsOverlay(isolationDir);
|
|
876
906
|
} else {
|
|
877
907
|
await cleanupWorktree(isolationDir);
|
|
878
908
|
}
|
|
@@ -908,8 +938,9 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
908
938
|
durationMs: 0,
|
|
909
939
|
tokens: 0,
|
|
910
940
|
modelOverride,
|
|
911
|
-
error: "
|
|
941
|
+
error: "Cancelled before start",
|
|
912
942
|
aborted: true,
|
|
943
|
+
abortReason: "Cancelled before start",
|
|
913
944
|
};
|
|
914
945
|
});
|
|
915
946
|
|
|
@@ -1108,6 +1139,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
1108
1139
|
});
|
|
1109
1140
|
|
|
1110
1141
|
const outputIds = results.filter(r => !r.aborted || r.output.trim()).map(r => `agent://${r.id}`);
|
|
1142
|
+
const backendSummaryPrefix = isolationBackendWarning ? `\n\n${isolationBackendWarning}` : "";
|
|
1111
1143
|
const summary = renderPromptTemplate(taskSummaryTemplate, {
|
|
1112
1144
|
successCount,
|
|
1113
1145
|
totalCount: results.length,
|
|
@@ -1117,7 +1149,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
1117
1149
|
summaries,
|
|
1118
1150
|
outputIds,
|
|
1119
1151
|
agentName,
|
|
1120
|
-
mergeSummary
|
|
1152
|
+
mergeSummary: `${backendSummaryPrefix}${mergeSummary}`,
|
|
1121
1153
|
});
|
|
1122
1154
|
|
|
1123
1155
|
// Cleanup temp directory if used
|