@pikaa-ai/pikaa 0.3.18 → 0.3.20
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/dist/cli.js +448 -239
- package/dist/index.js +405 -199
- package/package.json +1 -1
- package/skills/frontend-design/SKILL.md +1 -0
- package/skills/guardian-rails/SKILL.md +53 -0
- package/skills/verification-before-completion/SKILL.md +2 -1
- package/templates/base/groupy_prompt.md +15 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
5
|
import { resolve as resolve21 } from "path";
|
|
6
|
-
import { existsSync as
|
|
6
|
+
import { existsSync as existsSync23 } from "fs";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
|
|
9
9
|
// src/auth/store.ts
|
|
@@ -235,12 +235,15 @@ class DefaultModelClientSession {
|
|
|
235
235
|
};
|
|
236
236
|
return;
|
|
237
237
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
const enablePromptCache = params.enablePromptCache ?? this.config.enablePromptCache ?? true;
|
|
239
|
+
const systemMessage = {
|
|
240
|
+
role: "system",
|
|
241
|
+
content: params.systemPrompt
|
|
242
|
+
};
|
|
243
|
+
if (enablePromptCache) {
|
|
244
|
+
systemMessage.cache_control = { type: "ephemeral" };
|
|
245
|
+
}
|
|
246
|
+
const messages = [systemMessage];
|
|
244
247
|
for (let i = 0;i < params.history.length; i++) {
|
|
245
248
|
const item = params.history[i];
|
|
246
249
|
if (item.type === "user_message") {
|
|
@@ -324,14 +327,27 @@ class DefaultModelClientSession {
|
|
|
324
327
|
temperature: params.temperature ?? 0.2
|
|
325
328
|
};
|
|
326
329
|
if (toolsPayload && toolsPayload.length > 0) {
|
|
327
|
-
|
|
330
|
+
if (enablePromptCache && toolsPayload.length > 0) {
|
|
331
|
+
const clonedTools = [...toolsPayload];
|
|
332
|
+
const lastIdx = clonedTools.length - 1;
|
|
333
|
+
if (clonedTools[lastIdx] && typeof clonedTools[lastIdx] === "object") {
|
|
334
|
+
clonedTools[lastIdx] = {
|
|
335
|
+
...clonedTools[lastIdx],
|
|
336
|
+
cache_control: { type: "ephemeral" }
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
body.tools = clonedTools;
|
|
340
|
+
} else {
|
|
341
|
+
body.tools = toolsPayload;
|
|
342
|
+
}
|
|
328
343
|
body.tool_choice = "auto";
|
|
329
344
|
}
|
|
330
345
|
const maxRetries = params.maxRetries ?? this.config.maxRetries ?? 10;
|
|
331
346
|
let attempt = 0;
|
|
332
347
|
let response = null;
|
|
333
348
|
const headers = {
|
|
334
|
-
"Content-Type": "application/json"
|
|
349
|
+
"Content-Type": "application/json",
|
|
350
|
+
"anthropic-beta": "prompt-caching-2024-07-25"
|
|
335
351
|
};
|
|
336
352
|
if (apiKey) {
|
|
337
353
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
@@ -441,7 +457,8 @@ class DefaultModelClientSession {
|
|
|
441
457
|
type: "done",
|
|
442
458
|
inputTokens: usageMetrics.inputTokens,
|
|
443
459
|
outputTokens: usageMetrics.outputTokens,
|
|
444
|
-
totalTokens: usageMetrics.totalTokens
|
|
460
|
+
totalTokens: usageMetrics.totalTokens,
|
|
461
|
+
cachedTokens: usageMetrics.cachedTokens
|
|
445
462
|
};
|
|
446
463
|
return;
|
|
447
464
|
}
|
|
@@ -452,10 +469,12 @@ class DefaultModelClientSession {
|
|
|
452
469
|
continue;
|
|
453
470
|
}
|
|
454
471
|
if (parsed.usage) {
|
|
472
|
+
const cached = parsed.usage.prompt_tokens_details?.cached_tokens ?? parsed.usage.cache_read_input_tokens ?? parsed.usage.cached_content_token_count ?? 0;
|
|
455
473
|
usageMetrics = {
|
|
456
474
|
inputTokens: parsed.usage.prompt_tokens,
|
|
457
475
|
outputTokens: parsed.usage.completion_tokens,
|
|
458
|
-
totalTokens: parsed.usage.total_tokens
|
|
476
|
+
totalTokens: parsed.usage.total_tokens,
|
|
477
|
+
cachedTokens: cached > 0 ? cached : undefined
|
|
459
478
|
};
|
|
460
479
|
}
|
|
461
480
|
const choice = parsed.choices?.[0];
|
|
@@ -673,6 +692,8 @@ class TurnAbortedError extends GroupyError {
|
|
|
673
692
|
}
|
|
674
693
|
|
|
675
694
|
// src/context/compactor.ts
|
|
695
|
+
var DEFAULT_MAX_CONTEXT_TOKENS = 256000;
|
|
696
|
+
var DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS = 180000;
|
|
676
697
|
function estimateItemTokens(item) {
|
|
677
698
|
const text = item.type === "user_message" ? item.content : item.type === "agent_message" ? item.content : item.type === "reasoning" ? item.content : item.type === "function_call" ? JSON.stringify(item.arguments) : item.type === "function_call_output" ? item.output : "";
|
|
678
699
|
return Math.ceil(text.length / 4) + 4;
|
|
@@ -742,9 +763,11 @@ async function captureWorldState(cwd) {
|
|
|
742
763
|
};
|
|
743
764
|
}
|
|
744
765
|
function formatWorldStatePrompt(state) {
|
|
766
|
+
const isWindows = process.platform === "win32";
|
|
767
|
+
const platformNote = isWindows ? `Platform: Windows (${state.os}). Shell is cmd.exe / PowerShell. POSIX commands (grep, find, cat, sed, awk) are NOT supported in shell. ALWAYS use native grep_search, find_files, and read_file tools.` : `Platform: ${state.os}`;
|
|
745
768
|
const parts = [
|
|
746
769
|
`Current Working Directory: ${state.cwd}`,
|
|
747
|
-
|
|
770
|
+
platformNote
|
|
748
771
|
];
|
|
749
772
|
if (state.gitBranch) {
|
|
750
773
|
parts.push(`Git Branch: ${state.gitBranch}`);
|
|
@@ -884,80 +907,205 @@ class AgentsMdLoader {
|
|
|
884
907
|
var globalAgentsMdLoader = new AgentsMdLoader;
|
|
885
908
|
|
|
886
909
|
// src/context/instructions.ts
|
|
887
|
-
function
|
|
888
|
-
const
|
|
910
|
+
function wrapXmlTag(tag, content, attrs = {}) {
|
|
911
|
+
const attrStr = Object.entries(attrs).filter(([_, v]) => Boolean(v)).map(([k, v]) => ` ${k}="${v}"`).join("");
|
|
912
|
+
return `<${tag}${attrStr}>
|
|
913
|
+
${content.trim()}
|
|
914
|
+
</${tag}>`;
|
|
915
|
+
}
|
|
916
|
+
function buildStructuredSystemPrompt(params) {
|
|
889
917
|
const cwd = params.cwd || process.cwd();
|
|
890
918
|
const mode = params.collaborationMode || "default";
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
919
|
+
const blocks = [];
|
|
920
|
+
let baseContent = params.basePrompt;
|
|
921
|
+
if (!baseContent) {
|
|
894
922
|
const templateName = params.basePromptTemplate || "base/groupy_prompt.md";
|
|
895
|
-
|
|
896
|
-
if (baseContent) {
|
|
897
|
-
sections.push(baseContent.trim());
|
|
898
|
-
} else {
|
|
899
|
-
sections.push("You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
900
|
-
}
|
|
923
|
+
baseContent = globalPromptLoader.loadTemplate(templateName, {}, cwd) || "You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.";
|
|
901
924
|
}
|
|
925
|
+
blocks.push({
|
|
926
|
+
tag: "system_identity",
|
|
927
|
+
content: wrapXmlTag("system_identity", baseContent),
|
|
928
|
+
cacheable: true
|
|
929
|
+
});
|
|
902
930
|
if (params.personality) {
|
|
903
931
|
const personalityContent = globalPromptLoader.loadTemplate(`personalities/${params.personality}.md`, {}, cwd);
|
|
904
932
|
if (personalityContent) {
|
|
905
|
-
|
|
933
|
+
blocks.push({
|
|
934
|
+
tag: "personality",
|
|
935
|
+
content: wrapXmlTag("personality", personalityContent, { kind: params.personality }),
|
|
936
|
+
cacheable: true
|
|
937
|
+
});
|
|
906
938
|
}
|
|
907
939
|
}
|
|
908
940
|
if (params.isOrchestrator) {
|
|
909
941
|
const orchestratorContent = globalPromptLoader.loadTemplate("agents/orchestrator.md", {}, cwd);
|
|
910
942
|
if (orchestratorContent) {
|
|
911
|
-
|
|
943
|
+
blocks.push({
|
|
944
|
+
tag: "orchestrator_guidelines",
|
|
945
|
+
content: wrapXmlTag("orchestrator_guidelines", orchestratorContent),
|
|
946
|
+
cacheable: true
|
|
947
|
+
});
|
|
912
948
|
}
|
|
913
949
|
}
|
|
914
|
-
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, {
|
|
915
|
-
KNOWN_MODE_NAMES: "default, plan, review"
|
|
916
|
-
}, cwd);
|
|
950
|
+
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, { KNOWN_MODE_NAMES: "default, plan, review" }, cwd);
|
|
917
951
|
if (modeTemplate) {
|
|
918
|
-
|
|
952
|
+
blocks.push({
|
|
953
|
+
tag: "collaboration_mode",
|
|
954
|
+
content: wrapXmlTag("collaboration_mode", modeTemplate, { name: mode }),
|
|
955
|
+
cacheable: true
|
|
956
|
+
});
|
|
919
957
|
}
|
|
920
958
|
if (params.sandboxMode) {
|
|
921
|
-
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, {
|
|
922
|
-
network_access: params.networkAccess ? "enabled" : "disabled"
|
|
923
|
-
}, cwd);
|
|
959
|
+
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, { network_access: params.networkAccess ? "enabled" : "disabled" }, cwd);
|
|
924
960
|
if (sandboxTemplate) {
|
|
925
|
-
|
|
961
|
+
blocks.push({
|
|
962
|
+
tag: "sandbox_policy",
|
|
963
|
+
content: wrapXmlTag("sandbox_policy", sandboxTemplate, { mode: params.sandboxMode }),
|
|
964
|
+
cacheable: true
|
|
965
|
+
});
|
|
926
966
|
}
|
|
927
967
|
}
|
|
928
968
|
if (params.approvalPolicy) {
|
|
929
969
|
const approvalTemplate = globalPromptLoader.loadTemplate(`permissions/approval_policy/${params.approvalPolicy}.md`, {}, cwd);
|
|
930
970
|
if (approvalTemplate) {
|
|
931
|
-
|
|
971
|
+
blocks.push({
|
|
972
|
+
tag: "approval_policy",
|
|
973
|
+
content: wrapXmlTag("approval_policy", approvalTemplate, { policy: params.approvalPolicy }),
|
|
974
|
+
cacheable: true
|
|
975
|
+
});
|
|
932
976
|
}
|
|
933
977
|
}
|
|
934
978
|
const projectInstructions = globalAgentsMdLoader.loadProjectInstructions(cwd);
|
|
935
979
|
if (projectInstructions) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
980
|
+
blocks.push({
|
|
981
|
+
tag: "project_instructions",
|
|
982
|
+
content: wrapXmlTag("project_instructions", projectInstructions.content, { source: "AGENTS.md" }),
|
|
983
|
+
cacheable: true
|
|
984
|
+
});
|
|
939
985
|
}
|
|
940
986
|
if (params.memoriesPrompt) {
|
|
941
|
-
|
|
987
|
+
blocks.push({
|
|
988
|
+
tag: "persistent_memories",
|
|
989
|
+
content: wrapXmlTag("persistent_memories", params.memoriesPrompt),
|
|
990
|
+
cacheable: true
|
|
991
|
+
});
|
|
942
992
|
}
|
|
943
993
|
if (params.skillsPrompt) {
|
|
944
|
-
|
|
994
|
+
blocks.push({
|
|
995
|
+
tag: "domain_skills",
|
|
996
|
+
content: wrapXmlTag("domain_skills", params.skillsPrompt),
|
|
997
|
+
cacheable: true
|
|
998
|
+
});
|
|
945
999
|
}
|
|
946
1000
|
if (params.mcpPrompt) {
|
|
947
|
-
|
|
1001
|
+
blocks.push({
|
|
1002
|
+
tag: "mcp_servers",
|
|
1003
|
+
content: wrapXmlTag("mcp_servers", params.mcpPrompt),
|
|
1004
|
+
cacheable: true
|
|
1005
|
+
});
|
|
948
1006
|
}
|
|
949
1007
|
if (params.developerInstructions) {
|
|
950
|
-
|
|
951
|
-
|
|
1008
|
+
blocks.push({
|
|
1009
|
+
tag: "developer_instructions",
|
|
1010
|
+
content: wrapXmlTag("developer_instructions", params.developerInstructions),
|
|
1011
|
+
cacheable: true
|
|
1012
|
+
});
|
|
952
1013
|
}
|
|
1014
|
+
const dynamicBlocks = [];
|
|
953
1015
|
if (params.worldStatePrompt) {
|
|
954
|
-
|
|
955
|
-
|
|
1016
|
+
dynamicBlocks.push({
|
|
1017
|
+
tag: "runtime_environment",
|
|
1018
|
+
content: wrapXmlTag("runtime_environment", params.worldStatePrompt),
|
|
1019
|
+
cacheable: false
|
|
1020
|
+
});
|
|
956
1021
|
}
|
|
957
|
-
|
|
1022
|
+
const staticPrefix = blocks.map((b) => b.content).join(`
|
|
958
1023
|
|
|
959
1024
|
`);
|
|
1025
|
+
const dynamicSuffix = dynamicBlocks.map((b) => b.content).join(`
|
|
1026
|
+
|
|
1027
|
+
`);
|
|
1028
|
+
const allBlocks = [...blocks, ...dynamicBlocks];
|
|
1029
|
+
const text = allBlocks.map((b) => b.content).join(`
|
|
1030
|
+
|
|
1031
|
+
`);
|
|
1032
|
+
return {
|
|
1033
|
+
text,
|
|
1034
|
+
staticPrefix,
|
|
1035
|
+
dynamicSuffix,
|
|
1036
|
+
blocks: allBlocks
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function buildSystemPrompt(params) {
|
|
1040
|
+
return buildStructuredSystemPrompt(params).text;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/workspace/ephemeral.ts
|
|
1044
|
+
import { mkdirSync as mkdirSync3, rmSync, existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1045
|
+
import { join as join4 } from "path";
|
|
1046
|
+
import { tmpdir } from "os";
|
|
1047
|
+
|
|
1048
|
+
class EphemeralWorkspaceManager {
|
|
1049
|
+
activeScratchpads = new Set;
|
|
1050
|
+
createScratchpad(turnId) {
|
|
1051
|
+
const uniqueId = `groupy_scratch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1052
|
+
const baseDir = join4(tmpdir(), "groupy-ephemeral", turnId ? `turn_${turnId}` : "general");
|
|
1053
|
+
const scratchPath = join4(baseDir, uniqueId);
|
|
1054
|
+
mkdirSync3(scratchPath, { recursive: true });
|
|
1055
|
+
this.activeScratchpads.add(scratchPath);
|
|
1056
|
+
return scratchPath;
|
|
1057
|
+
}
|
|
1058
|
+
cleanup(scratchPath) {
|
|
1059
|
+
if (!scratchPath || !this.activeScratchpads.has(scratchPath))
|
|
1060
|
+
return;
|
|
1061
|
+
try {
|
|
1062
|
+
if (existsSync5(scratchPath)) {
|
|
1063
|
+
rmSync(scratchPath, { recursive: true, force: true });
|
|
1064
|
+
}
|
|
1065
|
+
} catch {} finally {
|
|
1066
|
+
this.activeScratchpads.delete(scratchPath);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
cleanupTurn(turnId) {
|
|
1070
|
+
const turnDir = join4(tmpdir(), "groupy-ephemeral", `turn_${turnId}`);
|
|
1071
|
+
try {
|
|
1072
|
+
if (existsSync5(turnDir)) {
|
|
1073
|
+
rmSync(turnDir, { recursive: true, force: true });
|
|
1074
|
+
}
|
|
1075
|
+
} catch {}
|
|
1076
|
+
}
|
|
1077
|
+
cleanRootResidue(cwd) {
|
|
1078
|
+
const cleaned = [];
|
|
1079
|
+
try {
|
|
1080
|
+
const files = readdirSync2(cwd);
|
|
1081
|
+
const tempPatterns = [
|
|
1082
|
+
/^tmp_/i,
|
|
1083
|
+
/^draft_/i,
|
|
1084
|
+
/^scratch_/i,
|
|
1085
|
+
/^temp_/i,
|
|
1086
|
+
/^preview_.*\.html$/i,
|
|
1087
|
+
/\.tmp$/i,
|
|
1088
|
+
/\.bak$/i,
|
|
1089
|
+
/~$/i
|
|
1090
|
+
];
|
|
1091
|
+
for (const file of files) {
|
|
1092
|
+
if (tempPatterns.some((p) => p.test(file))) {
|
|
1093
|
+
const fullPath = join4(cwd, file);
|
|
1094
|
+
const stat = statSync2(fullPath);
|
|
1095
|
+
if (stat.isFile()) {
|
|
1096
|
+
rmSync(fullPath, { force: true });
|
|
1097
|
+
cleaned.push(file);
|
|
1098
|
+
} else if (stat.isDirectory() && (file.startsWith("tmp_") || file.startsWith("scratch_") || file.startsWith("temp_"))) {
|
|
1099
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
1100
|
+
cleaned.push(file);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
} catch {}
|
|
1105
|
+
return cleaned;
|
|
1106
|
+
}
|
|
960
1107
|
}
|
|
1108
|
+
var globalEphemeralWorkspace = new EphemeralWorkspaceManager;
|
|
961
1109
|
|
|
962
1110
|
// src/session/turn.ts
|
|
963
1111
|
async function runTurn(session, turnContext, input) {
|
|
@@ -968,7 +1116,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
968
1116
|
});
|
|
969
1117
|
const currentHistory = session.getHistory();
|
|
970
1118
|
const estimatedTokens = estimateTotalTokens(currentHistory);
|
|
971
|
-
const maxTokenLimit =
|
|
1119
|
+
const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
|
|
972
1120
|
if (estimatedTokens > maxTokenLimit) {
|
|
973
1121
|
const compacted = compactHistory(currentHistory);
|
|
974
1122
|
session.setHistory(compacted);
|
|
@@ -1007,6 +1155,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
1007
1155
|
let iteration = 0;
|
|
1008
1156
|
let accumulatedInputTokens = 0;
|
|
1009
1157
|
let accumulatedOutputTokens = 0;
|
|
1158
|
+
let accumulatedCachedTokens = 0;
|
|
1010
1159
|
const clientSession = session.modelClient.newSession();
|
|
1011
1160
|
try {
|
|
1012
1161
|
while (iteration < turnContext.maxIterations) {
|
|
@@ -1023,7 +1172,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1023
1172
|
systemPrompt: effectiveSystemPrompt,
|
|
1024
1173
|
history: session.getHistory(),
|
|
1025
1174
|
tools: turnContext.tools,
|
|
1026
|
-
signal
|
|
1175
|
+
signal,
|
|
1176
|
+
enablePromptCache: true
|
|
1027
1177
|
});
|
|
1028
1178
|
for await (const chunk of stream) {
|
|
1029
1179
|
if (signal.aborted) {
|
|
@@ -1049,6 +1199,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1049
1199
|
iterInputTokens = chunk.inputTokens;
|
|
1050
1200
|
if (chunk.outputTokens !== undefined)
|
|
1051
1201
|
iterOutputTokens = chunk.outputTokens;
|
|
1202
|
+
if (chunk.cachedTokens !== undefined)
|
|
1203
|
+
accumulatedCachedTokens += chunk.cachedTokens;
|
|
1052
1204
|
} else if (chunk.type === "error") {
|
|
1053
1205
|
throw chunk.error;
|
|
1054
1206
|
}
|
|
@@ -1160,7 +1312,10 @@ async function runTurn(session, turnContext, input) {
|
|
|
1160
1312
|
session.addHistoryItem({
|
|
1161
1313
|
id: `msg_nudge_${Date.now()}`,
|
|
1162
1314
|
type: "user_message",
|
|
1163
|
-
content:
|
|
1315
|
+
content: `[Systematic ReAct Nudge]: No tool actions or answers were produced in this iteration.
|
|
1316
|
+
1. Review the user's objective and determine the immediate next action.
|
|
1317
|
+
2. If more context is required, invoke an exploration tool ('read_file', 'list_dir', 'grep_search', 'find_files').
|
|
1318
|
+
3. If ready to answer or implement, call the required mutating tool or deliver your full, concrete response now.`,
|
|
1164
1319
|
createdAt: Date.now()
|
|
1165
1320
|
});
|
|
1166
1321
|
continue;
|
|
@@ -1169,13 +1324,14 @@ async function runTurn(session, turnContext, input) {
|
|
|
1169
1324
|
break;
|
|
1170
1325
|
}
|
|
1171
1326
|
const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
|
|
1172
|
-
const maxContextTokens =
|
|
1327
|
+
const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
1173
1328
|
session.emitEvent({
|
|
1174
1329
|
type: "TurnCompleted",
|
|
1175
1330
|
turnId,
|
|
1176
1331
|
inputTokens: accumulatedInputTokens,
|
|
1177
1332
|
outputTokens: accumulatedOutputTokens,
|
|
1178
1333
|
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
1334
|
+
cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
|
|
1179
1335
|
contextTokens: totalContextTokens,
|
|
1180
1336
|
maxContextTokens
|
|
1181
1337
|
});
|
|
@@ -1190,6 +1346,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1190
1346
|
});
|
|
1191
1347
|
} finally {
|
|
1192
1348
|
session.clearActiveTurn(turnId);
|
|
1349
|
+
globalEphemeralWorkspace.cleanupTurn(turnId);
|
|
1350
|
+
globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
|
|
1193
1351
|
}
|
|
1194
1352
|
}
|
|
1195
1353
|
|
|
@@ -1289,9 +1447,9 @@ class ExecPolicy {
|
|
|
1289
1447
|
shouldPromptFileEdit(filePath) {
|
|
1290
1448
|
if (this.mode === "plan") {
|
|
1291
1449
|
return {
|
|
1292
|
-
prompt:
|
|
1450
|
+
prompt: true,
|
|
1293
1451
|
isPlanBlocked: true,
|
|
1294
|
-
reason:
|
|
1452
|
+
reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
|
|
1295
1453
|
};
|
|
1296
1454
|
}
|
|
1297
1455
|
if (this.mode === "manual") {
|
|
@@ -1305,11 +1463,14 @@ class ExecPolicy {
|
|
|
1305
1463
|
evaluate(command) {
|
|
1306
1464
|
const trimmed = command.trim();
|
|
1307
1465
|
if (this.mode === "plan") {
|
|
1308
|
-
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1466
|
+
const isReadOnly = /^(git\s+(status|log|diff|branch|show|rev-parse)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1309
1467
|
if (isReadOnly) {
|
|
1310
1468
|
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1311
1469
|
}
|
|
1312
|
-
return {
|
|
1470
|
+
return {
|
|
1471
|
+
decision: "prompt",
|
|
1472
|
+
reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
|
|
1473
|
+
};
|
|
1313
1474
|
}
|
|
1314
1475
|
if (this.mode === "manual") {
|
|
1315
1476
|
return {
|
|
@@ -1580,9 +1741,9 @@ class Session {
|
|
|
1580
1741
|
}
|
|
1581
1742
|
}
|
|
1582
1743
|
// src/tools/handlers/apply-patch.ts
|
|
1583
|
-
import { existsSync as
|
|
1744
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
1584
1745
|
import { resolve as resolve5, dirname as dirname3 } from "path";
|
|
1585
|
-
import { mkdirSync as
|
|
1746
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1586
1747
|
var applyPatchTool = {
|
|
1587
1748
|
name: "apply_patch",
|
|
1588
1749
|
description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
|
|
@@ -1614,34 +1775,40 @@ var applyPatchTool = {
|
|
|
1614
1775
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1615
1776
|
if (ctx.execPolicy) {
|
|
1616
1777
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1617
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1618
|
-
return {
|
|
1619
|
-
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1620
|
-
isError: true
|
|
1621
|
-
};
|
|
1622
|
-
}
|
|
1623
1778
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
1624
|
-
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1779
|
+
const approval = await ctx.requestApproval(evalResult.reason || `Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1625
1780
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1626
1781
|
if (!allowed) {
|
|
1627
|
-
return {
|
|
1782
|
+
return {
|
|
1783
|
+
output: `[Plan Mode Gate]: File modification declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
|
|
1784
|
+
isError: true
|
|
1785
|
+
};
|
|
1628
1786
|
}
|
|
1787
|
+
} else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1788
|
+
return {
|
|
1789
|
+
output: `[Plan Mode Gate]: Cannot mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
|
|
1790
|
+
isError: true
|
|
1791
|
+
};
|
|
1629
1792
|
}
|
|
1630
1793
|
}
|
|
1631
|
-
if (!
|
|
1794
|
+
if (!existsSync6(filePath)) {
|
|
1632
1795
|
if (targetContent) {
|
|
1633
1796
|
return {
|
|
1634
|
-
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided
|
|
1797
|
+
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.
|
|
1798
|
+
[Systematic Error Recovery Checklist]:
|
|
1799
|
+
1. Root Cause: Trying to patch a non-existent file with targetContent.
|
|
1800
|
+
2. Fix: For creating new files, leave 'targetContent' empty and provide full contents in 'replacementContent'.
|
|
1801
|
+
3. Alternatively, check if the file path '${rawPath}' was mistyped.`,
|
|
1635
1802
|
isError: true
|
|
1636
1803
|
};
|
|
1637
1804
|
}
|
|
1638
1805
|
try {
|
|
1639
|
-
|
|
1806
|
+
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
1640
1807
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1641
1808
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1642
1809
|
} catch (err) {
|
|
1643
1810
|
return {
|
|
1644
|
-
output: `Failed to create file: ${err instanceof Error ? err.message : String(err)}`,
|
|
1811
|
+
output: `Failed to create file '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
1645
1812
|
isError: true
|
|
1646
1813
|
};
|
|
1647
1814
|
}
|
|
@@ -1650,21 +1817,33 @@ var applyPatchTool = {
|
|
|
1650
1817
|
const originalFileContent = readFileSync4(filePath, "utf8");
|
|
1651
1818
|
if (!targetContent) {
|
|
1652
1819
|
return {
|
|
1653
|
-
output: `Error: File '${rawPath}' already exists
|
|
1820
|
+
output: `Error: File '${rawPath}' already exists, but targetContent was empty.
|
|
1821
|
+
[Systematic Error Recovery Checklist]:
|
|
1822
|
+
1. Root Cause: An existing file requires targetContent to specify which lines to replace.
|
|
1823
|
+
2. Fix: Call 'read_file' on '${rawPath}', extract the exact target lines, and provide them in 'targetContent'.
|
|
1824
|
+
3. To overwrite the whole file, use the 'write_file' tool instead.`,
|
|
1654
1825
|
isError: true
|
|
1655
1826
|
};
|
|
1656
1827
|
}
|
|
1657
1828
|
const firstIndex = originalFileContent.indexOf(targetContent);
|
|
1658
1829
|
if (firstIndex === -1) {
|
|
1659
1830
|
return {
|
|
1660
|
-
output: `Error: targetContent was not found in '${rawPath}'.
|
|
1831
|
+
output: `Error: targetContent was not found in '${rawPath}'.
|
|
1832
|
+
[Systematic Error Recovery Checklist]:
|
|
1833
|
+
1. Root Cause: The snippet in targetContent does not match the actual file content (differences in whitespace, indentation, line endings, or prior edits).
|
|
1834
|
+
2. Action: Call 'read_file' on '${rawPath}' to inspect current exact lines and indentation.
|
|
1835
|
+
3. Fix: Provide the exact matching lines (including leading spaces) or wider context, then retry 'apply_patch'.`,
|
|
1661
1836
|
isError: true
|
|
1662
1837
|
};
|
|
1663
1838
|
}
|
|
1664
1839
|
const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
|
|
1665
1840
|
if (secondIndex !== -1) {
|
|
1666
1841
|
return {
|
|
1667
|
-
output: `Error: targetContent matched multiple locations in '${rawPath}'.
|
|
1842
|
+
output: `Error: targetContent matched multiple locations in '${rawPath}'.
|
|
1843
|
+
[Systematic Error Recovery Checklist]:
|
|
1844
|
+
1. Root Cause: targetContent is ambiguous and occurs multiple times in the file.
|
|
1845
|
+
2. Action: Include 2-3 additional surrounding lines (before or after the target block) to make the target snippet uniquely identifiable.
|
|
1846
|
+
3. Fix: Re-run 'apply_patch' with the extended unique block.`,
|
|
1668
1847
|
isError: true
|
|
1669
1848
|
};
|
|
1670
1849
|
}
|
|
@@ -1675,7 +1854,7 @@ var applyPatchTool = {
|
|
|
1675
1854
|
};
|
|
1676
1855
|
} catch (err) {
|
|
1677
1856
|
return {
|
|
1678
|
-
output: `Failed to apply patch: ${err instanceof Error ? err.message : String(err)}`,
|
|
1857
|
+
output: `Failed to apply patch to '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
1679
1858
|
isError: true
|
|
1680
1859
|
};
|
|
1681
1860
|
}
|
|
@@ -1762,7 +1941,7 @@ class WindowsSandbox {
|
|
|
1762
1941
|
}
|
|
1763
1942
|
|
|
1764
1943
|
// src/security/kernel/linux.ts
|
|
1765
|
-
import { existsSync as
|
|
1944
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1766
1945
|
|
|
1767
1946
|
class LinuxSandbox {
|
|
1768
1947
|
hasBwrap = false;
|
|
@@ -1773,7 +1952,7 @@ class LinuxSandbox {
|
|
|
1773
1952
|
if (process.platform !== "linux") {
|
|
1774
1953
|
return;
|
|
1775
1954
|
}
|
|
1776
|
-
this.hasBwrap =
|
|
1955
|
+
this.hasBwrap = existsSync7("/usr/bin/bwrap") || existsSync7("/bin/bwrap") || existsSync7("/usr/local/bin/bwrap");
|
|
1777
1956
|
}
|
|
1778
1957
|
wrapCommand(cmd, profile) {
|
|
1779
1958
|
if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
|
|
@@ -1807,7 +1986,7 @@ class LinuxSandbox {
|
|
|
1807
1986
|
}
|
|
1808
1987
|
|
|
1809
1988
|
// src/security/kernel/macos.ts
|
|
1810
|
-
import { existsSync as
|
|
1989
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1811
1990
|
|
|
1812
1991
|
class MacOSSandbox {
|
|
1813
1992
|
hasSandboxExec = false;
|
|
@@ -1818,7 +1997,7 @@ class MacOSSandbox {
|
|
|
1818
1997
|
if (process.platform !== "darwin") {
|
|
1819
1998
|
return;
|
|
1820
1999
|
}
|
|
1821
|
-
this.hasSandboxExec =
|
|
2000
|
+
this.hasSandboxExec = existsSync8("/usr/bin/sandbox-exec");
|
|
1822
2001
|
}
|
|
1823
2002
|
generateProfile(profile) {
|
|
1824
2003
|
const rules = [
|
|
@@ -1928,7 +2107,7 @@ var globalKernelSandbox = new KernelSandboxManager;
|
|
|
1928
2107
|
|
|
1929
2108
|
// src/storage/prefix-rules-store.ts
|
|
1930
2109
|
import { Database } from "bun:sqlite";
|
|
1931
|
-
import { existsSync as
|
|
2110
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
1932
2111
|
import { dirname as dirname4, resolve as resolve7 } from "path";
|
|
1933
2112
|
class PrefixRulesStore {
|
|
1934
2113
|
db;
|
|
@@ -1939,8 +2118,8 @@ class PrefixRulesStore {
|
|
|
1939
2118
|
const effectivePath = dbOrPath || getPrefixRulesDbPath();
|
|
1940
2119
|
if (effectivePath !== ":memory:") {
|
|
1941
2120
|
const dir = dirname4(effectivePath);
|
|
1942
|
-
if (!
|
|
1943
|
-
|
|
2121
|
+
if (!existsSync9(dir)) {
|
|
2122
|
+
mkdirSync5(dir, { recursive: true });
|
|
1944
2123
|
}
|
|
1945
2124
|
}
|
|
1946
2125
|
this.db = new Database(effectivePath);
|
|
@@ -2129,6 +2308,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2129
2308
|
const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : 30000;
|
|
2130
2309
|
const isWindows = process.platform === "win32";
|
|
2131
2310
|
const baseCmd = isWindows ? ["cmd.exe", "/d", "/s", "/c", command] : ["/bin/sh", "-c", command];
|
|
2311
|
+
const ephemeralScratchpad = globalEphemeralWorkspace.createScratchpad(ctx.turnId);
|
|
2132
2312
|
const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd);
|
|
2133
2313
|
if (isEscalated) {
|
|
2134
2314
|
sandboxProfile.allowNetwork = true;
|
|
@@ -2139,6 +2319,10 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2139
2319
|
cwd: ctx.cwd,
|
|
2140
2320
|
env: {
|
|
2141
2321
|
...process.env,
|
|
2322
|
+
TMPDIR: ephemeralScratchpad,
|
|
2323
|
+
TEMP: ephemeralScratchpad,
|
|
2324
|
+
TMP: ephemeralScratchpad,
|
|
2325
|
+
GROUPY_SCRATCH_DIR: ephemeralScratchpad,
|
|
2142
2326
|
...ctx.proxyEnv
|
|
2143
2327
|
},
|
|
2144
2328
|
stdout: "pipe",
|
|
@@ -2175,9 +2359,14 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2175
2359
|
if (result.stderr)
|
|
2176
2360
|
outputParts.push(`STDERR:
|
|
2177
2361
|
${result.stderr.trim()}`);
|
|
2178
|
-
if (result.code !== 0)
|
|
2362
|
+
if (result.code !== 0) {
|
|
2179
2363
|
outputParts.push(`
|
|
2180
|
-
[Process exited with code ${result.code}]`);
|
|
2364
|
+
[Process exited with non-zero code ${result.code}]`);
|
|
2365
|
+
outputParts.push(`[Systematic Error Recovery Checklist]:
|
|
2366
|
+
1. Inspect STDERR above to pinpoint syntax errors, failed test assertions, or missing dependencies.
|
|
2367
|
+
2. If this is a test failure, trace the failure in source code and fix the root cause before re-running.
|
|
2368
|
+
3. If this is a missing command/module, install or configure the prerequisite.`);
|
|
2369
|
+
}
|
|
2181
2370
|
const output = outputParts.join(`
|
|
2182
2371
|
`) || "[Command completed with no output]";
|
|
2183
2372
|
return {
|
|
@@ -2186,16 +2375,21 @@ ${result.stderr.trim()}`);
|
|
|
2186
2375
|
};
|
|
2187
2376
|
} catch (err) {
|
|
2188
2377
|
return {
|
|
2189
|
-
output: `Execution error: ${err instanceof Error ? err.message : String(err)}
|
|
2378
|
+
output: `Execution error: ${err instanceof Error ? err.message : String(err)}
|
|
2379
|
+
[Systematic Error Recovery Checklist]:
|
|
2380
|
+
1. Verify command syntax, arguments, and executable availability in PATH.
|
|
2381
|
+
2. Check if the current working directory ('${ctx.cwd}') is valid.`,
|
|
2190
2382
|
isError: true
|
|
2191
2383
|
};
|
|
2384
|
+
} finally {
|
|
2385
|
+
globalEphemeralWorkspace.cleanup(ephemeralScratchpad);
|
|
2192
2386
|
}
|
|
2193
2387
|
}
|
|
2194
2388
|
};
|
|
2195
2389
|
}
|
|
2196
2390
|
var shellTool = createShellTool();
|
|
2197
2391
|
// src/tools/handlers/file-ops.ts
|
|
2198
|
-
import { readdirSync as
|
|
2392
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync10, statSync as statSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
2199
2393
|
import { resolve as resolve8, dirname as dirname5 } from "path";
|
|
2200
2394
|
var readFileTool = {
|
|
2201
2395
|
name: "read_file",
|
|
@@ -2209,7 +2403,7 @@ var readFileTool = {
|
|
|
2209
2403
|
},
|
|
2210
2404
|
async execute(args, ctx) {
|
|
2211
2405
|
const filePath = resolve8(ctx.cwd, String(args.path || ""));
|
|
2212
|
-
if (!
|
|
2406
|
+
if (!existsSync10(filePath)) {
|
|
2213
2407
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2214
2408
|
}
|
|
2215
2409
|
try {
|
|
@@ -2231,14 +2425,14 @@ var listDirTool = {
|
|
|
2231
2425
|
},
|
|
2232
2426
|
async execute(args, ctx) {
|
|
2233
2427
|
const dirPath = resolve8(ctx.cwd, String(args.path || "."));
|
|
2234
|
-
if (!
|
|
2428
|
+
if (!existsSync10(dirPath)) {
|
|
2235
2429
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2236
2430
|
}
|
|
2237
2431
|
try {
|
|
2238
|
-
const entries =
|
|
2432
|
+
const entries = readdirSync3(dirPath);
|
|
2239
2433
|
const formatted = entries.map((entry) => {
|
|
2240
2434
|
const full = resolve8(dirPath, entry);
|
|
2241
|
-
const isDir =
|
|
2435
|
+
const isDir = statSync3(full).isDirectory();
|
|
2242
2436
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2243
2437
|
});
|
|
2244
2438
|
return { output: formatted.join(`
|
|
@@ -2264,26 +2458,35 @@ var writeFileTool = {
|
|
|
2264
2458
|
const filePath = resolve8(ctx.cwd, rawPath);
|
|
2265
2459
|
if (ctx.execPolicy) {
|
|
2266
2460
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2267
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2268
|
-
return {
|
|
2269
|
-
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2270
|
-
isError: true
|
|
2271
|
-
};
|
|
2272
|
-
}
|
|
2273
2461
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
2274
|
-
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2462
|
+
const approval = await ctx.requestApproval(evalResult.reason || `Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2275
2463
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2276
2464
|
if (!allowed) {
|
|
2277
|
-
return {
|
|
2465
|
+
return {
|
|
2466
|
+
output: `[Plan Mode Gate]: File creation declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
|
|
2467
|
+
isError: true
|
|
2468
|
+
};
|
|
2278
2469
|
}
|
|
2470
|
+
} else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2471
|
+
return {
|
|
2472
|
+
output: `[Plan Mode Gate]: Cannot write or mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
|
|
2473
|
+
isError: true
|
|
2474
|
+
};
|
|
2279
2475
|
}
|
|
2280
2476
|
}
|
|
2281
2477
|
try {
|
|
2282
|
-
|
|
2478
|
+
mkdirSync6(dirname5(filePath), { recursive: true });
|
|
2283
2479
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2284
2480
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2285
2481
|
} catch (err) {
|
|
2286
|
-
return {
|
|
2482
|
+
return {
|
|
2483
|
+
output: `Failed to write file '${rawPath}': ${err instanceof Error ? err.message : String(err)}
|
|
2484
|
+
[Systematic Error Recovery Checklist]:
|
|
2485
|
+
1. Check directory permissions and ensure the path is valid within the workspace.
|
|
2486
|
+
2. If the path contains non-existent nested folders, they should be auto-created.
|
|
2487
|
+
3. Verify that the file is not locked by another active process.`,
|
|
2488
|
+
isError: true
|
|
2489
|
+
};
|
|
2287
2490
|
}
|
|
2288
2491
|
}
|
|
2289
2492
|
};
|
|
@@ -2402,8 +2605,8 @@ var updatePlanTool = {
|
|
|
2402
2605
|
}
|
|
2403
2606
|
};
|
|
2404
2607
|
// src/search/engine.ts
|
|
2405
|
-
import { readdirSync as
|
|
2406
|
-
import { resolve as resolve9, relative, join as
|
|
2608
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4, existsSync as existsSync11 } from "fs";
|
|
2609
|
+
import { resolve as resolve9, relative, join as join5, extname } from "path";
|
|
2407
2610
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2408
2611
|
".git",
|
|
2409
2612
|
"node_modules",
|
|
@@ -2449,7 +2652,7 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2449
2652
|
class FileSearchEngine {
|
|
2450
2653
|
grep(cwd, options) {
|
|
2451
2654
|
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2452
|
-
if (!
|
|
2655
|
+
if (!existsSync11(searchRoot)) {
|
|
2453
2656
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2454
2657
|
}
|
|
2455
2658
|
const maxResults = options.maxResults || 50;
|
|
@@ -2499,7 +2702,7 @@ class FileSearchEngine {
|
|
|
2499
2702
|
}
|
|
2500
2703
|
findFiles(cwd, options) {
|
|
2501
2704
|
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2502
|
-
if (!
|
|
2705
|
+
if (!existsSync11(searchRoot))
|
|
2503
2706
|
return [];
|
|
2504
2707
|
const maxResults = options.maxResults || 100;
|
|
2505
2708
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2538,8 +2741,8 @@ class FileSearchEngine {
|
|
|
2538
2741
|
}
|
|
2539
2742
|
loadGitignoreRules(root) {
|
|
2540
2743
|
const rules = new Set;
|
|
2541
|
-
const gitignorePath =
|
|
2542
|
-
if (
|
|
2744
|
+
const gitignorePath = join5(root, ".gitignore");
|
|
2745
|
+
if (existsSync11(gitignorePath)) {
|
|
2543
2746
|
try {
|
|
2544
2747
|
const lines = readFileSync6(gitignorePath, "utf8").split(`
|
|
2545
2748
|
`);
|
|
@@ -2556,7 +2759,7 @@ class FileSearchEngine {
|
|
|
2556
2759
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2557
2760
|
const results = [];
|
|
2558
2761
|
try {
|
|
2559
|
-
const stat =
|
|
2762
|
+
const stat = statSync4(dir);
|
|
2560
2763
|
if (!stat.isDirectory()) {
|
|
2561
2764
|
if (!this.isBinary(dir)) {
|
|
2562
2765
|
results.push(dir);
|
|
@@ -2570,9 +2773,9 @@ class FileSearchEngine {
|
|
|
2570
2773
|
while (queue.length > 0) {
|
|
2571
2774
|
const currentDir = queue.shift();
|
|
2572
2775
|
try {
|
|
2573
|
-
const entries =
|
|
2776
|
+
const entries = readdirSync4(currentDir, { withFileTypes: true });
|
|
2574
2777
|
for (const entry of entries) {
|
|
2575
|
-
const fullPath =
|
|
2778
|
+
const fullPath = join5(currentDir, entry.name);
|
|
2576
2779
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2577
2780
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2578
2781
|
continue;
|
|
@@ -3271,8 +3474,8 @@ function createDefaultTools(options = {}) {
|
|
|
3271
3474
|
}
|
|
3272
3475
|
|
|
3273
3476
|
// src/agents/roles.ts
|
|
3274
|
-
import { existsSync as
|
|
3275
|
-
import { resolve as resolve10, join as
|
|
3477
|
+
import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "fs";
|
|
3478
|
+
import { resolve as resolve10, join as join6 } from "path";
|
|
3276
3479
|
|
|
3277
3480
|
class AgentRoleRegistry {
|
|
3278
3481
|
roles = new Map;
|
|
@@ -3357,13 +3560,13 @@ class AgentRoleRegistry {
|
|
|
3357
3560
|
}
|
|
3358
3561
|
loadRolesFromDir(dirPath) {
|
|
3359
3562
|
const fullPath = resolve10(dirPath);
|
|
3360
|
-
if (!
|
|
3563
|
+
if (!existsSync12(fullPath))
|
|
3361
3564
|
return;
|
|
3362
|
-
const entries =
|
|
3565
|
+
const entries = readdirSync5(fullPath);
|
|
3363
3566
|
for (const entry of entries) {
|
|
3364
3567
|
if (entry.endsWith(".json")) {
|
|
3365
3568
|
try {
|
|
3366
|
-
const content = readFileSync7(
|
|
3569
|
+
const content = readFileSync7(join6(fullPath, entry), "utf8");
|
|
3367
3570
|
const parsed = JSON.parse(content);
|
|
3368
3571
|
if (parsed.name && parsed.systemPrompt) {
|
|
3369
3572
|
this.registerRole(parsed);
|
|
@@ -3416,7 +3619,7 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
|
|
|
3416
3619
|
// src/agents/graph-store.ts
|
|
3417
3620
|
import { Database as Database2 } from "bun:sqlite";
|
|
3418
3621
|
import { resolve as resolve11 } from "path";
|
|
3419
|
-
import { existsSync as
|
|
3622
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
|
|
3420
3623
|
class AgentGraphStore {
|
|
3421
3624
|
db;
|
|
3422
3625
|
constructor(dbPathOrDb) {
|
|
@@ -3426,8 +3629,8 @@ class AgentGraphStore {
|
|
|
3426
3629
|
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
3427
3630
|
if (dbPath !== ":memory:") {
|
|
3428
3631
|
const dir = resolve11(dbPath, "..");
|
|
3429
|
-
if (!
|
|
3430
|
-
|
|
3632
|
+
if (!existsSync13(dir)) {
|
|
3633
|
+
mkdirSync7(dir, { recursive: true });
|
|
3431
3634
|
}
|
|
3432
3635
|
}
|
|
3433
3636
|
this.db = new Database2(dbPath);
|
|
@@ -3851,8 +4054,8 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
3851
4054
|
}
|
|
3852
4055
|
|
|
3853
4056
|
// src/mcp/manager.ts
|
|
3854
|
-
import { existsSync as
|
|
3855
|
-
import { resolve as resolve12, dirname as dirname6, join as
|
|
4057
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync8 } from "fs";
|
|
4058
|
+
import { resolve as resolve12, dirname as dirname6, join as join7 } from "path";
|
|
3856
4059
|
|
|
3857
4060
|
// src/mcp/client.ts
|
|
3858
4061
|
class McpClient {
|
|
@@ -4468,7 +4671,7 @@ class McpManager {
|
|
|
4468
4671
|
}
|
|
4469
4672
|
async loadConfigFile(filePath) {
|
|
4470
4673
|
const fullPath = resolve12(filePath);
|
|
4471
|
-
if (!
|
|
4674
|
+
if (!existsSync14(fullPath))
|
|
4472
4675
|
return;
|
|
4473
4676
|
this.loadedConfigFiles.add(fullPath);
|
|
4474
4677
|
try {
|
|
@@ -4723,11 +4926,11 @@ class McpManager {
|
|
|
4723
4926
|
saveServerToConfigFile(filePath, name, config) {
|
|
4724
4927
|
const fullPath = resolve12(filePath);
|
|
4725
4928
|
const dir = dirname6(fullPath);
|
|
4726
|
-
if (!
|
|
4727
|
-
|
|
4929
|
+
if (!existsSync14(dir)) {
|
|
4930
|
+
mkdirSync8(dir, { recursive: true });
|
|
4728
4931
|
}
|
|
4729
4932
|
let existing = { mcpServers: {} };
|
|
4730
|
-
if (
|
|
4933
|
+
if (existsSync14(fullPath)) {
|
|
4731
4934
|
try {
|
|
4732
4935
|
const content = readFileSync8(fullPath, "utf8");
|
|
4733
4936
|
existing = JSON.parse(content);
|
|
@@ -4742,7 +4945,7 @@ class McpManager {
|
|
|
4742
4945
|
}
|
|
4743
4946
|
removeServerFromConfigFile(filePath, name) {
|
|
4744
4947
|
const fullPath = resolve12(filePath);
|
|
4745
|
-
if (!
|
|
4948
|
+
if (!existsSync14(fullPath))
|
|
4746
4949
|
return false;
|
|
4747
4950
|
try {
|
|
4748
4951
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -4772,11 +4975,11 @@ class McpManager {
|
|
|
4772
4975
|
}
|
|
4773
4976
|
}
|
|
4774
4977
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
4775
|
-
const workspaceConfig =
|
|
4776
|
-
if (
|
|
4978
|
+
const workspaceConfig = join7(cwd, ".mcp.json");
|
|
4979
|
+
if (existsSync14(workspaceConfig))
|
|
4777
4980
|
return workspaceConfig;
|
|
4778
|
-
const altConfig =
|
|
4779
|
-
if (
|
|
4981
|
+
const altConfig = join7(cwd, "mcp_config.json");
|
|
4982
|
+
if (existsSync14(altConfig))
|
|
4780
4983
|
return altConfig;
|
|
4781
4984
|
return workspaceConfig;
|
|
4782
4985
|
}
|
|
@@ -4797,7 +5000,7 @@ class McpManager {
|
|
|
4797
5000
|
|
|
4798
5001
|
// src/storage/sqlite-store.ts
|
|
4799
5002
|
import { Database as Database3 } from "bun:sqlite";
|
|
4800
|
-
import { existsSync as
|
|
5003
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
4801
5004
|
import { dirname as dirname7 } from "path";
|
|
4802
5005
|
class SqliteThreadStore {
|
|
4803
5006
|
db;
|
|
@@ -4805,8 +5008,8 @@ class SqliteThreadStore {
|
|
|
4805
5008
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
4806
5009
|
if (effectivePath !== ":memory:") {
|
|
4807
5010
|
const dir = dirname7(effectivePath);
|
|
4808
|
-
if (!
|
|
4809
|
-
|
|
5011
|
+
if (!existsSync15(dir)) {
|
|
5012
|
+
mkdirSync9(dir, { recursive: true });
|
|
4810
5013
|
}
|
|
4811
5014
|
}
|
|
4812
5015
|
this.db = new Database3(effectivePath);
|
|
@@ -5040,8 +5243,8 @@ class SessionPersistenceManager {
|
|
|
5040
5243
|
}
|
|
5041
5244
|
|
|
5042
5245
|
// src/skills/loader.ts
|
|
5043
|
-
import { existsSync as
|
|
5044
|
-
import { resolve as resolve13, join as
|
|
5246
|
+
import { existsSync as existsSync16, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
5247
|
+
import { resolve as resolve13, join as join8 } from "path";
|
|
5045
5248
|
import { homedir as homedir3 } from "os";
|
|
5046
5249
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
5047
5250
|
|
|
@@ -5112,7 +5315,7 @@ class SkillsLoader {
|
|
|
5112
5315
|
resolve13(cwd, "skills")
|
|
5113
5316
|
];
|
|
5114
5317
|
for (const cand of candidates) {
|
|
5115
|
-
if (
|
|
5318
|
+
if (existsSync16(cand) && !roots.includes(cand)) {
|
|
5116
5319
|
roots.push(cand);
|
|
5117
5320
|
}
|
|
5118
5321
|
}
|
|
@@ -5121,7 +5324,7 @@ class SkillsLoader {
|
|
|
5121
5324
|
roots.push(getGlobalSkillsDir(), resolve13(homedir3(), ".gemini", "config", "skills"));
|
|
5122
5325
|
}
|
|
5123
5326
|
roots.push(...this.customRoots.map((r) => resolve13(r)));
|
|
5124
|
-
return roots.filter((r) =>
|
|
5327
|
+
return roots.filter((r) => existsSync16(r));
|
|
5125
5328
|
}
|
|
5126
5329
|
discoverSkills(cwd, options) {
|
|
5127
5330
|
return this.listSkills(cwd, options);
|
|
@@ -5137,12 +5340,12 @@ class SkillsLoader {
|
|
|
5137
5340
|
const discovered = new Map;
|
|
5138
5341
|
for (const root of roots) {
|
|
5139
5342
|
try {
|
|
5140
|
-
const entries =
|
|
5343
|
+
const entries = readdirSync6(root, { withFileTypes: true });
|
|
5141
5344
|
for (const entry of entries) {
|
|
5142
5345
|
if (entry.isDirectory()) {
|
|
5143
|
-
const skillDir =
|
|
5144
|
-
const skillFilePath =
|
|
5145
|
-
if (
|
|
5346
|
+
const skillDir = join8(root, entry.name);
|
|
5347
|
+
const skillFilePath = join8(skillDir, "SKILL.md");
|
|
5348
|
+
if (existsSync16(skillFilePath)) {
|
|
5146
5349
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
5147
5350
|
if (meta && !discovered.has(meta.name)) {
|
|
5148
5351
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -5155,7 +5358,7 @@ class SkillsLoader {
|
|
|
5155
5358
|
}
|
|
5156
5359
|
} catch {}
|
|
5157
5360
|
}
|
|
5158
|
-
const result = Array.from(discovered.values());
|
|
5361
|
+
const result = Array.from(discovered.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
5159
5362
|
this.skillsCache.set(cacheKey, { timestamp: now, skills: result });
|
|
5160
5363
|
return result;
|
|
5161
5364
|
}
|
|
@@ -5244,10 +5447,10 @@ class SkillsLoader {
|
|
|
5244
5447
|
const skills = this.listSkills(cwd, { includeDisabled: false });
|
|
5245
5448
|
if (skills.length === 0)
|
|
5246
5449
|
return "";
|
|
5247
|
-
const workspaceSkills = skills.filter((s) => s.scope === "workspace");
|
|
5248
|
-
const builtInSkills = skills.filter((s) => s.scope === "built-in");
|
|
5249
|
-
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in");
|
|
5250
|
-
const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0,
|
|
5450
|
+
const workspaceSkills = skills.filter((s) => s.scope === "workspace").sort((a, b) => a.name.localeCompare(b.name));
|
|
5451
|
+
const builtInSkills = skills.filter((s) => s.scope === "built-in").sort((a, b) => a.name.localeCompare(b.name));
|
|
5452
|
+
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in").sort((a, b) => a.name.localeCompare(b.name));
|
|
5453
|
+
const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0, 300);
|
|
5251
5454
|
const lines = selectedSkills.map((s) => {
|
|
5252
5455
|
const desc = s.shortDescription || s.description;
|
|
5253
5456
|
return `- **${s.name}**: ${desc}`;
|
|
@@ -5265,8 +5468,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
5265
5468
|
}
|
|
5266
5469
|
|
|
5267
5470
|
// src/memories/store.ts
|
|
5268
|
-
import { existsSync as
|
|
5269
|
-
import { resolve as resolve14, join as
|
|
5471
|
+
import { existsSync as existsSync17, readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync10, readdirSync as readdirSync7 } from "fs";
|
|
5472
|
+
import { resolve as resolve14, join as join9, basename, dirname as dirname8 } from "path";
|
|
5270
5473
|
import { createHash } from "crypto";
|
|
5271
5474
|
class MemoryStore {
|
|
5272
5475
|
globalPath;
|
|
@@ -5278,7 +5481,7 @@ class MemoryStore {
|
|
|
5278
5481
|
findProjectRoot(cwd) {
|
|
5279
5482
|
let current = resolve14(cwd);
|
|
5280
5483
|
while (true) {
|
|
5281
|
-
if (
|
|
5484
|
+
if (existsSync17(join9(current, ".git"))) {
|
|
5282
5485
|
return current;
|
|
5283
5486
|
}
|
|
5284
5487
|
const parent = dirname8(current);
|
|
@@ -5297,24 +5500,24 @@ class MemoryStore {
|
|
|
5297
5500
|
getProjectMemoryDir(cwd) {
|
|
5298
5501
|
if (this.customWorkspacePath) {
|
|
5299
5502
|
const dir2 = resolve14(this.customWorkspacePath);
|
|
5300
|
-
if (!
|
|
5503
|
+
if (!existsSync17(dir2)) {
|
|
5301
5504
|
try {
|
|
5302
|
-
|
|
5505
|
+
mkdirSync10(dir2, { recursive: true });
|
|
5303
5506
|
} catch {}
|
|
5304
5507
|
}
|
|
5305
5508
|
return dir2;
|
|
5306
5509
|
}
|
|
5307
5510
|
const slug = this.getProjectSlug(cwd);
|
|
5308
|
-
const dir =
|
|
5309
|
-
if (!
|
|
5511
|
+
const dir = join9(getProjectsDir(), slug, "memory");
|
|
5512
|
+
if (!existsSync17(dir)) {
|
|
5310
5513
|
try {
|
|
5311
|
-
|
|
5514
|
+
mkdirSync10(dir, { recursive: true });
|
|
5312
5515
|
} catch {}
|
|
5313
5516
|
}
|
|
5314
5517
|
return dir;
|
|
5315
5518
|
}
|
|
5316
5519
|
getMemoryIndexPath(cwd) {
|
|
5317
|
-
return
|
|
5520
|
+
return join9(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
5318
5521
|
}
|
|
5319
5522
|
normalizeCategory(raw) {
|
|
5320
5523
|
const cat = raw.toLowerCase().trim();
|
|
@@ -5333,7 +5536,7 @@ class MemoryStore {
|
|
|
5333
5536
|
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
5334
5537
|
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
5335
5538
|
const fileName = `${type}_${sanitizedName}.md`;
|
|
5336
|
-
const filePath =
|
|
5539
|
+
const filePath = join9(memoryDir, fileName);
|
|
5337
5540
|
const nowIso = new Date().toISOString();
|
|
5338
5541
|
const cleanContent = params.content.trim();
|
|
5339
5542
|
const desc = (params.description || cleanContent.split(`
|
|
@@ -5368,17 +5571,17 @@ class MemoryStore {
|
|
|
5368
5571
|
}
|
|
5369
5572
|
readTopicMemory(topicNameOrFile, cwd) {
|
|
5370
5573
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5371
|
-
let targetPath =
|
|
5372
|
-
if (!
|
|
5574
|
+
let targetPath = join9(memoryDir, topicNameOrFile);
|
|
5575
|
+
if (!existsSync17(targetPath)) {
|
|
5373
5576
|
if (!topicNameOrFile.endsWith(".md")) {
|
|
5374
|
-
targetPath =
|
|
5577
|
+
targetPath = join9(memoryDir, `${topicNameOrFile}.md`);
|
|
5375
5578
|
}
|
|
5376
5579
|
}
|
|
5377
|
-
if (!
|
|
5378
|
-
const files =
|
|
5580
|
+
if (!existsSync17(targetPath)) {
|
|
5581
|
+
const files = readdirSync7(memoryDir);
|
|
5379
5582
|
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
5380
5583
|
if (match) {
|
|
5381
|
-
targetPath =
|
|
5584
|
+
targetPath = join9(memoryDir, match);
|
|
5382
5585
|
} else {
|
|
5383
5586
|
return null;
|
|
5384
5587
|
}
|
|
@@ -5439,12 +5642,12 @@ class MemoryStore {
|
|
|
5439
5642
|
}
|
|
5440
5643
|
syncMemoryIndex(cwd) {
|
|
5441
5644
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5442
|
-
const indexPath =
|
|
5443
|
-
const files =
|
|
5645
|
+
const indexPath = join9(memoryDir, "MEMORY.md");
|
|
5646
|
+
const files = existsSync17(memoryDir) ? readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
5444
5647
|
const items = [];
|
|
5445
5648
|
for (const f of files) {
|
|
5446
5649
|
try {
|
|
5447
|
-
const full =
|
|
5650
|
+
const full = join9(memoryDir, f);
|
|
5448
5651
|
const parsed = this.parseTopicFile(readFileSync10(full, "utf8"), full);
|
|
5449
5652
|
items.push({
|
|
5450
5653
|
type: parsed.type,
|
|
@@ -5471,7 +5674,7 @@ class MemoryStore {
|
|
|
5471
5674
|
}
|
|
5472
5675
|
loadMemoryIndex(cwd) {
|
|
5473
5676
|
const indexPath = this.getMemoryIndexPath(cwd);
|
|
5474
|
-
if (!
|
|
5677
|
+
if (!existsSync17(indexPath))
|
|
5475
5678
|
return "";
|
|
5476
5679
|
try {
|
|
5477
5680
|
const raw = readFileSync10(indexPath, "utf8");
|
|
@@ -5487,13 +5690,13 @@ class MemoryStore {
|
|
|
5487
5690
|
}
|
|
5488
5691
|
listProjectMemories(cwd) {
|
|
5489
5692
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5490
|
-
if (!
|
|
5693
|
+
if (!existsSync17(memoryDir))
|
|
5491
5694
|
return [];
|
|
5492
|
-
const files =
|
|
5695
|
+
const files = readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
5493
5696
|
const list = [];
|
|
5494
5697
|
for (const f of files) {
|
|
5495
5698
|
try {
|
|
5496
|
-
const full =
|
|
5699
|
+
const full = join9(memoryDir, f);
|
|
5497
5700
|
list.push(this.parseTopicFile(readFileSync10(full, "utf8"), full));
|
|
5498
5701
|
} catch {}
|
|
5499
5702
|
}
|
|
@@ -5545,8 +5748,8 @@ class MemoryStore {
|
|
|
5545
5748
|
}
|
|
5546
5749
|
|
|
5547
5750
|
// src/worktree/manager.ts
|
|
5548
|
-
import { resolve as resolve16, join as
|
|
5549
|
-
import { existsSync as
|
|
5751
|
+
import { resolve as resolve16, join as join10 } from "path";
|
|
5752
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6, readFileSync as readFileSync11 } from "fs";
|
|
5550
5753
|
|
|
5551
5754
|
// src/worktree/git.ts
|
|
5552
5755
|
import { resolve as resolve15 } from "path";
|
|
@@ -5696,15 +5899,15 @@ class WorktreeManager {
|
|
|
5696
5899
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
5697
5900
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve16(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve16(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
5698
5901
|
const worktreeParent = resolve16(targetDir, "..");
|
|
5699
|
-
if (!
|
|
5700
|
-
|
|
5902
|
+
if (!existsSync18(worktreeParent)) {
|
|
5903
|
+
mkdirSync11(worktreeParent, { recursive: true });
|
|
5701
5904
|
}
|
|
5702
5905
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
5703
5906
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
5704
5907
|
if (!result.success) {
|
|
5705
5908
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
5706
5909
|
}
|
|
5707
|
-
const metaPath =
|
|
5910
|
+
const metaPath = join10(targetDir, "groupy-thread.json");
|
|
5708
5911
|
try {
|
|
5709
5912
|
writeFileSync6(metaPath, JSON.stringify({
|
|
5710
5913
|
version: 1,
|
|
@@ -5730,8 +5933,8 @@ class WorktreeManager {
|
|
|
5730
5933
|
return [];
|
|
5731
5934
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
5732
5935
|
return worktrees.map((wt) => {
|
|
5733
|
-
const metaPath =
|
|
5734
|
-
if (
|
|
5936
|
+
const metaPath = join10(wt.path, "groupy-thread.json");
|
|
5937
|
+
if (existsSync18(metaPath)) {
|
|
5735
5938
|
try {
|
|
5736
5939
|
const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
|
|
5737
5940
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -6429,7 +6632,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
|
|
|
6429
6632
|
// package.json
|
|
6430
6633
|
var package_default = {
|
|
6431
6634
|
name: "@pikaa-ai/pikaa",
|
|
6432
|
-
version: "0.3.
|
|
6635
|
+
version: "0.3.20",
|
|
6433
6636
|
description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
|
|
6434
6637
|
main: "./dist/index.js",
|
|
6435
6638
|
module: "./dist/index.js",
|
|
@@ -6804,7 +7007,12 @@ ${preview}${more}`);
|
|
|
6804
7007
|
if (metrics.inputTokens !== undefined || metrics.outputTokens !== undefined) {
|
|
6805
7008
|
const inStr = metrics.inputTokens !== undefined ? formatTokens(metrics.inputTokens) : "0";
|
|
6806
7009
|
const outStr = metrics.outputTokens !== undefined ? formatTokens(metrics.outputTokens) : "0";
|
|
6807
|
-
|
|
7010
|
+
if (metrics.cachedTokens !== undefined && metrics.cachedTokens > 0) {
|
|
7011
|
+
const cachedStr = formatTokens(metrics.cachedTokens);
|
|
7012
|
+
parts.push(`${style.cyan(`${inStr} in`)} ${style.dim(`(${cachedStr} cached)`)} ${style.dim("/")} ${style.cyan(`${outStr} out`)}`);
|
|
7013
|
+
} else {
|
|
7014
|
+
parts.push(`${style.cyan(`${inStr} in`)} ${style.dim("/")} ${style.cyan(`${outStr} out`)}`);
|
|
7015
|
+
}
|
|
6808
7016
|
} else if (metrics.totalTokens !== undefined && metrics.totalTokens > 0) {
|
|
6809
7017
|
parts.push(`${style.dim(`${formatTokens(metrics.totalTokens)} tokens`)}`);
|
|
6810
7018
|
}
|
|
@@ -7921,8 +8129,8 @@ async function promptInteractiveList(config) {
|
|
|
7921
8129
|
}
|
|
7922
8130
|
|
|
7923
8131
|
// src/security/scanner.ts
|
|
7924
|
-
import { existsSync as
|
|
7925
|
-
import { join as
|
|
8132
|
+
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync5 } from "fs";
|
|
8133
|
+
import { join as join11, relative as relative2, resolve as resolve17 } from "path";
|
|
7926
8134
|
var SECURITY_RULES = [
|
|
7927
8135
|
{
|
|
7928
8136
|
id: "SEC-001",
|
|
@@ -8060,21 +8268,21 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
8060
8268
|
const findings = [];
|
|
8061
8269
|
let scannedCount = 0;
|
|
8062
8270
|
function walk(current) {
|
|
8063
|
-
if (scannedCount >= maxFiles || !
|
|
8271
|
+
if (scannedCount >= maxFiles || !existsSync19(current))
|
|
8064
8272
|
return;
|
|
8065
8273
|
let entries;
|
|
8066
8274
|
try {
|
|
8067
|
-
entries =
|
|
8275
|
+
entries = readdirSync8(current);
|
|
8068
8276
|
} catch {
|
|
8069
8277
|
return;
|
|
8070
8278
|
}
|
|
8071
8279
|
for (const entry of entries) {
|
|
8072
8280
|
if (scannedCount >= maxFiles)
|
|
8073
8281
|
break;
|
|
8074
|
-
const fullPath =
|
|
8282
|
+
const fullPath = join11(current, entry);
|
|
8075
8283
|
let stat;
|
|
8076
8284
|
try {
|
|
8077
|
-
stat =
|
|
8285
|
+
stat = statSync5(fullPath);
|
|
8078
8286
|
} catch {
|
|
8079
8287
|
continue;
|
|
8080
8288
|
}
|
|
@@ -8179,8 +8387,8 @@ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
|
8179
8387
|
var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
|
|
8180
8388
|
|
|
8181
8389
|
// src/init/project-analyzer.ts
|
|
8182
|
-
import { existsSync as
|
|
8183
|
-
import { join as
|
|
8390
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13, readdirSync as readdirSync9 } from "fs";
|
|
8391
|
+
import { join as join12, basename as basename3 } from "path";
|
|
8184
8392
|
|
|
8185
8393
|
class ProjectAnalyzer {
|
|
8186
8394
|
cwd;
|
|
@@ -8198,8 +8406,8 @@ class ProjectAnalyzer {
|
|
|
8198
8406
|
const architectureNotes = [];
|
|
8199
8407
|
const codeConventions = [];
|
|
8200
8408
|
let description = readmeInfo.description;
|
|
8201
|
-
const pkgPath =
|
|
8202
|
-
if (
|
|
8409
|
+
const pkgPath = join12(this.cwd, "package.json");
|
|
8410
|
+
if (existsSync20(pkgPath)) {
|
|
8203
8411
|
try {
|
|
8204
8412
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8205
8413
|
if (!description && pkg.description)
|
|
@@ -8272,8 +8480,8 @@ class ProjectAnalyzer {
|
|
|
8272
8480
|
}
|
|
8273
8481
|
} catch {}
|
|
8274
8482
|
}
|
|
8275
|
-
const tsconfigPath =
|
|
8276
|
-
if (
|
|
8483
|
+
const tsconfigPath = join12(this.cwd, "tsconfig.json");
|
|
8484
|
+
if (existsSync20(tsconfigPath)) {
|
|
8277
8485
|
try {
|
|
8278
8486
|
const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
|
|
8279
8487
|
if (tsconfig.compilerOptions?.strict) {
|
|
@@ -8284,8 +8492,8 @@ class ProjectAnalyzer {
|
|
|
8284
8492
|
}
|
|
8285
8493
|
} catch {}
|
|
8286
8494
|
}
|
|
8287
|
-
const cargoPath =
|
|
8288
|
-
if (
|
|
8495
|
+
const cargoPath = join12(this.cwd, "Cargo.toml");
|
|
8496
|
+
if (existsSync20(cargoPath)) {
|
|
8289
8497
|
try {
|
|
8290
8498
|
commands.dev = commands.dev || "cargo run";
|
|
8291
8499
|
commands.build = commands.build || "cargo build";
|
|
@@ -8294,8 +8502,8 @@ class ProjectAnalyzer {
|
|
|
8294
8502
|
frameworks.push("Rust Cargo");
|
|
8295
8503
|
} catch {}
|
|
8296
8504
|
}
|
|
8297
|
-
const goModPath =
|
|
8298
|
-
if (
|
|
8505
|
+
const goModPath = join12(this.cwd, "go.mod");
|
|
8506
|
+
if (existsSync20(goModPath)) {
|
|
8299
8507
|
try {
|
|
8300
8508
|
commands.dev = commands.dev || "go run .";
|
|
8301
8509
|
commands.build = commands.build || "go build ./...";
|
|
@@ -8304,37 +8512,37 @@ class ProjectAnalyzer {
|
|
|
8304
8512
|
frameworks.push("Go Modules");
|
|
8305
8513
|
} catch {}
|
|
8306
8514
|
}
|
|
8307
|
-
const pyprojectPath =
|
|
8308
|
-
const requirementsPath =
|
|
8309
|
-
if (
|
|
8515
|
+
const pyprojectPath = join12(this.cwd, "pyproject.toml");
|
|
8516
|
+
const requirementsPath = join12(this.cwd, "requirements.txt");
|
|
8517
|
+
if (existsSync20(pyprojectPath) || existsSync20(requirementsPath)) {
|
|
8310
8518
|
commands.test = commands.test || "pytest";
|
|
8311
8519
|
commands.lint = commands.lint || "ruff check .";
|
|
8312
|
-
if (
|
|
8520
|
+
if (existsSync20(join12(this.cwd, "uv.lock"))) {
|
|
8313
8521
|
frameworks.push("uv");
|
|
8314
8522
|
commands.test = "uv run pytest";
|
|
8315
|
-
} else if (
|
|
8523
|
+
} else if (existsSync20(join12(this.cwd, "poetry.lock"))) {
|
|
8316
8524
|
frameworks.push("Poetry");
|
|
8317
8525
|
commands.test = "poetry run pytest";
|
|
8318
8526
|
}
|
|
8319
8527
|
}
|
|
8320
|
-
if (
|
|
8528
|
+
if (existsSync20(join12(this.cwd, "Dockerfile"))) {
|
|
8321
8529
|
infrastructure.push("Docker");
|
|
8322
8530
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
8323
8531
|
commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
|
|
8324
8532
|
}
|
|
8325
|
-
if (
|
|
8533
|
+
if (existsSync20(join12(this.cwd, "nginx.conf"))) {
|
|
8326
8534
|
infrastructure.push("Nginx");
|
|
8327
8535
|
}
|
|
8328
|
-
if (
|
|
8536
|
+
if (existsSync20(join12(this.cwd, "src/api.ts")) || existsSync20(join12(this.cwd, "src/api"))) {
|
|
8329
8537
|
architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
|
|
8330
8538
|
}
|
|
8331
|
-
if (
|
|
8539
|
+
if (existsSync20(join12(this.cwd, "src/components"))) {
|
|
8332
8540
|
architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
|
|
8333
8541
|
}
|
|
8334
|
-
if (
|
|
8542
|
+
if (existsSync20(join12(this.cwd, "src/types.ts")) || existsSync20(join12(this.cwd, "src/types"))) {
|
|
8335
8543
|
architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
|
|
8336
8544
|
}
|
|
8337
|
-
if (
|
|
8545
|
+
if (existsSync20(join12(this.cwd, ".env.example"))) {
|
|
8338
8546
|
architectureNotes.push("Environment configuration template is in `.env.example`.");
|
|
8339
8547
|
}
|
|
8340
8548
|
if (commands.typecheck || commands.lint || commands.test) {
|
|
@@ -8351,7 +8559,7 @@ class ProjectAnalyzer {
|
|
|
8351
8559
|
let hasExistingInstructions = false;
|
|
8352
8560
|
let existingInstructionFile;
|
|
8353
8561
|
for (const f of instructionFiles) {
|
|
8354
|
-
if (
|
|
8562
|
+
if (existsSync20(join12(this.cwd, f))) {
|
|
8355
8563
|
hasExistingInstructions = true;
|
|
8356
8564
|
existingInstructionFile = f;
|
|
8357
8565
|
break;
|
|
@@ -8432,8 +8640,8 @@ class ProjectAnalyzer {
|
|
|
8432
8640
|
extractReadmeMetadata() {
|
|
8433
8641
|
const readmeFiles = ["README.md", "readme.md", "README.MD"];
|
|
8434
8642
|
for (const file of readmeFiles) {
|
|
8435
|
-
const fullPath =
|
|
8436
|
-
if (
|
|
8643
|
+
const fullPath = join12(this.cwd, file);
|
|
8644
|
+
if (existsSync20(fullPath)) {
|
|
8437
8645
|
try {
|
|
8438
8646
|
const content = readFileSync13(fullPath, "utf8");
|
|
8439
8647
|
const lines = content.split(`
|
|
@@ -8458,8 +8666,8 @@ class ProjectAnalyzer {
|
|
|
8458
8666
|
return {};
|
|
8459
8667
|
}
|
|
8460
8668
|
detectProjectName() {
|
|
8461
|
-
const pkgPath =
|
|
8462
|
-
if (
|
|
8669
|
+
const pkgPath = join12(this.cwd, "package.json");
|
|
8670
|
+
if (existsSync20(pkgPath)) {
|
|
8463
8671
|
try {
|
|
8464
8672
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8465
8673
|
if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
|
|
@@ -8467,16 +8675,16 @@ class ProjectAnalyzer {
|
|
|
8467
8675
|
}
|
|
8468
8676
|
} catch {}
|
|
8469
8677
|
}
|
|
8470
|
-
const cargoPath =
|
|
8471
|
-
if (
|
|
8678
|
+
const cargoPath = join12(this.cwd, "Cargo.toml");
|
|
8679
|
+
if (existsSync20(cargoPath)) {
|
|
8472
8680
|
try {
|
|
8473
8681
|
const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
|
|
8474
8682
|
if (match?.[1])
|
|
8475
8683
|
return match[1];
|
|
8476
8684
|
} catch {}
|
|
8477
8685
|
}
|
|
8478
|
-
const goModPath =
|
|
8479
|
-
if (
|
|
8686
|
+
const goModPath = join12(this.cwd, "go.mod");
|
|
8687
|
+
if (existsSync20(goModPath)) {
|
|
8480
8688
|
try {
|
|
8481
8689
|
const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
|
|
8482
8690
|
if (match?.[1])
|
|
@@ -8487,53 +8695,53 @@ class ProjectAnalyzer {
|
|
|
8487
8695
|
}
|
|
8488
8696
|
detectLanguages() {
|
|
8489
8697
|
const langs = new Set;
|
|
8490
|
-
if (
|
|
8698
|
+
if (existsSync20(join12(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
|
|
8491
8699
|
langs.add("TypeScript");
|
|
8492
8700
|
}
|
|
8493
|
-
if (
|
|
8701
|
+
if (existsSync20(join12(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
|
|
8494
8702
|
langs.add("JavaScript");
|
|
8495
8703
|
}
|
|
8496
|
-
if (
|
|
8704
|
+
if (existsSync20(join12(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
|
|
8497
8705
|
langs.add("Rust");
|
|
8498
8706
|
}
|
|
8499
|
-
if (
|
|
8707
|
+
if (existsSync20(join12(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
|
|
8500
8708
|
langs.add("Go");
|
|
8501
8709
|
}
|
|
8502
|
-
if (
|
|
8710
|
+
if (existsSync20(join12(this.cwd, "pyproject.toml")) || existsSync20(join12(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
|
|
8503
8711
|
langs.add("Python");
|
|
8504
8712
|
}
|
|
8505
|
-
if (
|
|
8713
|
+
if (existsSync20(join12(this.cwd, "pom.xml")) || existsSync20(join12(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
|
|
8506
8714
|
langs.add("Java");
|
|
8507
8715
|
}
|
|
8508
|
-
if (
|
|
8716
|
+
if (existsSync20(join12(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
|
|
8509
8717
|
langs.add("C/C++");
|
|
8510
8718
|
}
|
|
8511
8719
|
return Array.from(langs);
|
|
8512
8720
|
}
|
|
8513
8721
|
detectPackageManager() {
|
|
8514
|
-
if (
|
|
8722
|
+
if (existsSync20(join12(this.cwd, "bun.lockb")) || existsSync20(join12(this.cwd, "bun.lock")))
|
|
8515
8723
|
return "bun";
|
|
8516
|
-
if (
|
|
8724
|
+
if (existsSync20(join12(this.cwd, "pnpm-lock.yaml")))
|
|
8517
8725
|
return "pnpm";
|
|
8518
|
-
if (
|
|
8726
|
+
if (existsSync20(join12(this.cwd, "yarn.lock")))
|
|
8519
8727
|
return "yarn";
|
|
8520
|
-
if (
|
|
8728
|
+
if (existsSync20(join12(this.cwd, "package-lock.json")))
|
|
8521
8729
|
return "npm";
|
|
8522
|
-
if (
|
|
8730
|
+
if (existsSync20(join12(this.cwd, "Cargo.lock")) || existsSync20(join12(this.cwd, "Cargo.toml")))
|
|
8523
8731
|
return "cargo";
|
|
8524
|
-
if (
|
|
8732
|
+
if (existsSync20(join12(this.cwd, "uv.lock")))
|
|
8525
8733
|
return "uv";
|
|
8526
|
-
if (
|
|
8734
|
+
if (existsSync20(join12(this.cwd, "poetry.lock")))
|
|
8527
8735
|
return "poetry";
|
|
8528
|
-
if (
|
|
8736
|
+
if (existsSync20(join12(this.cwd, "go.sum")) || existsSync20(join12(this.cwd, "go.mod")))
|
|
8529
8737
|
return "go";
|
|
8530
|
-
if (
|
|
8738
|
+
if (existsSync20(join12(this.cwd, "package.json")))
|
|
8531
8739
|
return "npm";
|
|
8532
8740
|
return;
|
|
8533
8741
|
}
|
|
8534
8742
|
hasFileWithExtension(...exts) {
|
|
8535
8743
|
try {
|
|
8536
|
-
const entries =
|
|
8744
|
+
const entries = readdirSync9(this.cwd);
|
|
8537
8745
|
return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
|
|
8538
8746
|
} catch {
|
|
8539
8747
|
return false;
|
|
@@ -8541,16 +8749,16 @@ class ProjectAnalyzer {
|
|
|
8541
8749
|
}
|
|
8542
8750
|
}
|
|
8543
8751
|
// src/init/init-command.ts
|
|
8544
|
-
import { existsSync as
|
|
8545
|
-
import { join as
|
|
8752
|
+
import { existsSync as existsSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
8753
|
+
import { join as join13 } from "path";
|
|
8546
8754
|
function runProjectInit(options = {}) {
|
|
8547
8755
|
const cwd = options.cwd || process.cwd();
|
|
8548
8756
|
const filename = options.filename || "AGENTS.md";
|
|
8549
|
-
const targetPath =
|
|
8757
|
+
const targetPath = join13(cwd, filename);
|
|
8550
8758
|
const analyzer = new ProjectAnalyzer(cwd);
|
|
8551
8759
|
const analysis = analyzer.analyze();
|
|
8552
8760
|
const content = analyzer.generateAgentsMarkdown(analysis);
|
|
8553
|
-
const alreadyExists =
|
|
8761
|
+
const alreadyExists = existsSync21(targetPath);
|
|
8554
8762
|
writeFileSync7(targetPath, content, "utf8");
|
|
8555
8763
|
return {
|
|
8556
8764
|
success: true,
|
|
@@ -9324,7 +9532,7 @@ function printSessionStats(ctx) {
|
|
|
9324
9532
|
const turns = ctx.repl?.turnCount ?? 0;
|
|
9325
9533
|
const history = ctx.session.getHistory();
|
|
9326
9534
|
const historyTokens = estimateTotalTokens(history);
|
|
9327
|
-
const maxTokens =
|
|
9535
|
+
const maxTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
9328
9536
|
const contextPct = Math.round(historyTokens / maxTokens * 100);
|
|
9329
9537
|
const colorFn = contextPct < 50 ? style.green : contextPct < 80 ? style.yellow : style.red;
|
|
9330
9538
|
const agents = ctx.spawner?.listAgents() || [];
|
|
@@ -10015,9 +10223,9 @@ class MarkdownHighlighter {
|
|
|
10015
10223
|
}
|
|
10016
10224
|
|
|
10017
10225
|
// src/cli/update-checker.ts
|
|
10018
|
-
import { existsSync as
|
|
10226
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
10019
10227
|
import { homedir as homedir5 } from "os";
|
|
10020
|
-
import { join as
|
|
10228
|
+
import { join as join14 } from "path";
|
|
10021
10229
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
10022
10230
|
function parseSemver(v) {
|
|
10023
10231
|
const clean = v.replace(/^v/, "").trim();
|
|
@@ -10038,8 +10246,8 @@ function isNewerVersion(current, remote) {
|
|
|
10038
10246
|
return remPatch > curPatch;
|
|
10039
10247
|
}
|
|
10040
10248
|
function getUpdateCachePath() {
|
|
10041
|
-
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME ||
|
|
10042
|
-
return
|
|
10249
|
+
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join14(homedir5(), ".pikaa");
|
|
10250
|
+
return join14(baseDir, "update-cache.json");
|
|
10043
10251
|
}
|
|
10044
10252
|
async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
|
|
10045
10253
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
@@ -10072,7 +10280,7 @@ async function checkForUpdates(options = {}) {
|
|
|
10072
10280
|
const cachePath = options.cachePath || getUpdateCachePath();
|
|
10073
10281
|
const now = Date.now();
|
|
10074
10282
|
let cached = null;
|
|
10075
|
-
if (!options.force &&
|
|
10283
|
+
if (!options.force && existsSync22(cachePath)) {
|
|
10076
10284
|
try {
|
|
10077
10285
|
const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
|
|
10078
10286
|
if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
|
|
@@ -10102,9 +10310,9 @@ async function checkForUpdates(options = {}) {
|
|
|
10102
10310
|
return null;
|
|
10103
10311
|
}
|
|
10104
10312
|
try {
|
|
10105
|
-
const parentDir =
|
|
10106
|
-
if (!
|
|
10107
|
-
|
|
10313
|
+
const parentDir = join14(cachePath, "..");
|
|
10314
|
+
if (!existsSync22(parentDir)) {
|
|
10315
|
+
mkdirSync12(parentDir, { recursive: true });
|
|
10108
10316
|
}
|
|
10109
10317
|
const cacheData = {
|
|
10110
10318
|
lastChecked: now,
|
|
@@ -10268,6 +10476,7 @@ class CliRepl {
|
|
|
10268
10476
|
inputTokens: msg.inputTokens,
|
|
10269
10477
|
outputTokens: msg.outputTokens || (this.turnCharsOut > 0 ? Math.round(this.turnCharsOut / 3.8) : undefined),
|
|
10270
10478
|
totalTokens: msg.totalTokens,
|
|
10479
|
+
cachedTokens: msg.cachedTokens,
|
|
10271
10480
|
contextTokens: msg.contextTokens,
|
|
10272
10481
|
maxContextTokens: msg.maxContextTokens,
|
|
10273
10482
|
sessionUptimeMs,
|
|
@@ -10526,7 +10735,7 @@ async function main() {
|
|
|
10526
10735
|
resolve21(cwd, "mcp_config.json")
|
|
10527
10736
|
].filter(Boolean);
|
|
10528
10737
|
for (const cfg of candidateConfigs) {
|
|
10529
|
-
if (
|
|
10738
|
+
if (existsSync23(cfg)) {
|
|
10530
10739
|
try {
|
|
10531
10740
|
await mcpManager.loadConfigFile(cfg);
|
|
10532
10741
|
mcpManager.registerToolsIntoRouter(tools4);
|