@pikaa-ai/pikaa 0.3.19 → 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 +445 -238
- package/dist/index.js +402 -198
- 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 +10 -0
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;
|
|
@@ -886,80 +907,205 @@ class AgentsMdLoader {
|
|
|
886
907
|
var globalAgentsMdLoader = new AgentsMdLoader;
|
|
887
908
|
|
|
888
909
|
// src/context/instructions.ts
|
|
889
|
-
function
|
|
890
|
-
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) {
|
|
891
917
|
const cwd = params.cwd || process.cwd();
|
|
892
918
|
const mode = params.collaborationMode || "default";
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
919
|
+
const blocks = [];
|
|
920
|
+
let baseContent = params.basePrompt;
|
|
921
|
+
if (!baseContent) {
|
|
896
922
|
const templateName = params.basePromptTemplate || "base/groupy_prompt.md";
|
|
897
|
-
|
|
898
|
-
if (baseContent) {
|
|
899
|
-
sections.push(baseContent.trim());
|
|
900
|
-
} else {
|
|
901
|
-
sections.push("You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
902
|
-
}
|
|
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.";
|
|
903
924
|
}
|
|
925
|
+
blocks.push({
|
|
926
|
+
tag: "system_identity",
|
|
927
|
+
content: wrapXmlTag("system_identity", baseContent),
|
|
928
|
+
cacheable: true
|
|
929
|
+
});
|
|
904
930
|
if (params.personality) {
|
|
905
931
|
const personalityContent = globalPromptLoader.loadTemplate(`personalities/${params.personality}.md`, {}, cwd);
|
|
906
932
|
if (personalityContent) {
|
|
907
|
-
|
|
933
|
+
blocks.push({
|
|
934
|
+
tag: "personality",
|
|
935
|
+
content: wrapXmlTag("personality", personalityContent, { kind: params.personality }),
|
|
936
|
+
cacheable: true
|
|
937
|
+
});
|
|
908
938
|
}
|
|
909
939
|
}
|
|
910
940
|
if (params.isOrchestrator) {
|
|
911
941
|
const orchestratorContent = globalPromptLoader.loadTemplate("agents/orchestrator.md", {}, cwd);
|
|
912
942
|
if (orchestratorContent) {
|
|
913
|
-
|
|
943
|
+
blocks.push({
|
|
944
|
+
tag: "orchestrator_guidelines",
|
|
945
|
+
content: wrapXmlTag("orchestrator_guidelines", orchestratorContent),
|
|
946
|
+
cacheable: true
|
|
947
|
+
});
|
|
914
948
|
}
|
|
915
949
|
}
|
|
916
|
-
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, {
|
|
917
|
-
KNOWN_MODE_NAMES: "default, plan, review"
|
|
918
|
-
}, cwd);
|
|
950
|
+
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, { KNOWN_MODE_NAMES: "default, plan, review" }, cwd);
|
|
919
951
|
if (modeTemplate) {
|
|
920
|
-
|
|
952
|
+
blocks.push({
|
|
953
|
+
tag: "collaboration_mode",
|
|
954
|
+
content: wrapXmlTag("collaboration_mode", modeTemplate, { name: mode }),
|
|
955
|
+
cacheable: true
|
|
956
|
+
});
|
|
921
957
|
}
|
|
922
958
|
if (params.sandboxMode) {
|
|
923
|
-
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, {
|
|
924
|
-
network_access: params.networkAccess ? "enabled" : "disabled"
|
|
925
|
-
}, cwd);
|
|
959
|
+
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, { network_access: params.networkAccess ? "enabled" : "disabled" }, cwd);
|
|
926
960
|
if (sandboxTemplate) {
|
|
927
|
-
|
|
961
|
+
blocks.push({
|
|
962
|
+
tag: "sandbox_policy",
|
|
963
|
+
content: wrapXmlTag("sandbox_policy", sandboxTemplate, { mode: params.sandboxMode }),
|
|
964
|
+
cacheable: true
|
|
965
|
+
});
|
|
928
966
|
}
|
|
929
967
|
}
|
|
930
968
|
if (params.approvalPolicy) {
|
|
931
969
|
const approvalTemplate = globalPromptLoader.loadTemplate(`permissions/approval_policy/${params.approvalPolicy}.md`, {}, cwd);
|
|
932
970
|
if (approvalTemplate) {
|
|
933
|
-
|
|
971
|
+
blocks.push({
|
|
972
|
+
tag: "approval_policy",
|
|
973
|
+
content: wrapXmlTag("approval_policy", approvalTemplate, { policy: params.approvalPolicy }),
|
|
974
|
+
cacheable: true
|
|
975
|
+
});
|
|
934
976
|
}
|
|
935
977
|
}
|
|
936
978
|
const projectInstructions = globalAgentsMdLoader.loadProjectInstructions(cwd);
|
|
937
979
|
if (projectInstructions) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
980
|
+
blocks.push({
|
|
981
|
+
tag: "project_instructions",
|
|
982
|
+
content: wrapXmlTag("project_instructions", projectInstructions.content, { source: "AGENTS.md" }),
|
|
983
|
+
cacheable: true
|
|
984
|
+
});
|
|
941
985
|
}
|
|
942
986
|
if (params.memoriesPrompt) {
|
|
943
|
-
|
|
987
|
+
blocks.push({
|
|
988
|
+
tag: "persistent_memories",
|
|
989
|
+
content: wrapXmlTag("persistent_memories", params.memoriesPrompt),
|
|
990
|
+
cacheable: true
|
|
991
|
+
});
|
|
944
992
|
}
|
|
945
993
|
if (params.skillsPrompt) {
|
|
946
|
-
|
|
994
|
+
blocks.push({
|
|
995
|
+
tag: "domain_skills",
|
|
996
|
+
content: wrapXmlTag("domain_skills", params.skillsPrompt),
|
|
997
|
+
cacheable: true
|
|
998
|
+
});
|
|
947
999
|
}
|
|
948
1000
|
if (params.mcpPrompt) {
|
|
949
|
-
|
|
1001
|
+
blocks.push({
|
|
1002
|
+
tag: "mcp_servers",
|
|
1003
|
+
content: wrapXmlTag("mcp_servers", params.mcpPrompt),
|
|
1004
|
+
cacheable: true
|
|
1005
|
+
});
|
|
950
1006
|
}
|
|
951
1007
|
if (params.developerInstructions) {
|
|
952
|
-
|
|
953
|
-
|
|
1008
|
+
blocks.push({
|
|
1009
|
+
tag: "developer_instructions",
|
|
1010
|
+
content: wrapXmlTag("developer_instructions", params.developerInstructions),
|
|
1011
|
+
cacheable: true
|
|
1012
|
+
});
|
|
954
1013
|
}
|
|
1014
|
+
const dynamicBlocks = [];
|
|
955
1015
|
if (params.worldStatePrompt) {
|
|
956
|
-
|
|
957
|
-
|
|
1016
|
+
dynamicBlocks.push({
|
|
1017
|
+
tag: "runtime_environment",
|
|
1018
|
+
content: wrapXmlTag("runtime_environment", params.worldStatePrompt),
|
|
1019
|
+
cacheable: false
|
|
1020
|
+
});
|
|
958
1021
|
}
|
|
959
|
-
|
|
1022
|
+
const staticPrefix = blocks.map((b) => b.content).join(`
|
|
960
1023
|
|
|
961
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
|
+
}
|
|
962
1107
|
}
|
|
1108
|
+
var globalEphemeralWorkspace = new EphemeralWorkspaceManager;
|
|
963
1109
|
|
|
964
1110
|
// src/session/turn.ts
|
|
965
1111
|
async function runTurn(session, turnContext, input) {
|
|
@@ -970,7 +1116,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
970
1116
|
});
|
|
971
1117
|
const currentHistory = session.getHistory();
|
|
972
1118
|
const estimatedTokens = estimateTotalTokens(currentHistory);
|
|
973
|
-
const maxTokenLimit =
|
|
1119
|
+
const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
|
|
974
1120
|
if (estimatedTokens > maxTokenLimit) {
|
|
975
1121
|
const compacted = compactHistory(currentHistory);
|
|
976
1122
|
session.setHistory(compacted);
|
|
@@ -1009,6 +1155,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
1009
1155
|
let iteration = 0;
|
|
1010
1156
|
let accumulatedInputTokens = 0;
|
|
1011
1157
|
let accumulatedOutputTokens = 0;
|
|
1158
|
+
let accumulatedCachedTokens = 0;
|
|
1012
1159
|
const clientSession = session.modelClient.newSession();
|
|
1013
1160
|
try {
|
|
1014
1161
|
while (iteration < turnContext.maxIterations) {
|
|
@@ -1025,7 +1172,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1025
1172
|
systemPrompt: effectiveSystemPrompt,
|
|
1026
1173
|
history: session.getHistory(),
|
|
1027
1174
|
tools: turnContext.tools,
|
|
1028
|
-
signal
|
|
1175
|
+
signal,
|
|
1176
|
+
enablePromptCache: true
|
|
1029
1177
|
});
|
|
1030
1178
|
for await (const chunk of stream) {
|
|
1031
1179
|
if (signal.aborted) {
|
|
@@ -1051,6 +1199,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1051
1199
|
iterInputTokens = chunk.inputTokens;
|
|
1052
1200
|
if (chunk.outputTokens !== undefined)
|
|
1053
1201
|
iterOutputTokens = chunk.outputTokens;
|
|
1202
|
+
if (chunk.cachedTokens !== undefined)
|
|
1203
|
+
accumulatedCachedTokens += chunk.cachedTokens;
|
|
1054
1204
|
} else if (chunk.type === "error") {
|
|
1055
1205
|
throw chunk.error;
|
|
1056
1206
|
}
|
|
@@ -1162,7 +1312,10 @@ async function runTurn(session, turnContext, input) {
|
|
|
1162
1312
|
session.addHistoryItem({
|
|
1163
1313
|
id: `msg_nudge_${Date.now()}`,
|
|
1164
1314
|
type: "user_message",
|
|
1165
|
-
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.`,
|
|
1166
1319
|
createdAt: Date.now()
|
|
1167
1320
|
});
|
|
1168
1321
|
continue;
|
|
@@ -1171,13 +1324,14 @@ async function runTurn(session, turnContext, input) {
|
|
|
1171
1324
|
break;
|
|
1172
1325
|
}
|
|
1173
1326
|
const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
|
|
1174
|
-
const maxContextTokens =
|
|
1327
|
+
const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
1175
1328
|
session.emitEvent({
|
|
1176
1329
|
type: "TurnCompleted",
|
|
1177
1330
|
turnId,
|
|
1178
1331
|
inputTokens: accumulatedInputTokens,
|
|
1179
1332
|
outputTokens: accumulatedOutputTokens,
|
|
1180
1333
|
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
1334
|
+
cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
|
|
1181
1335
|
contextTokens: totalContextTokens,
|
|
1182
1336
|
maxContextTokens
|
|
1183
1337
|
});
|
|
@@ -1192,6 +1346,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
1192
1346
|
});
|
|
1193
1347
|
} finally {
|
|
1194
1348
|
session.clearActiveTurn(turnId);
|
|
1349
|
+
globalEphemeralWorkspace.cleanupTurn(turnId);
|
|
1350
|
+
globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
|
|
1195
1351
|
}
|
|
1196
1352
|
}
|
|
1197
1353
|
|
|
@@ -1291,9 +1447,9 @@ class ExecPolicy {
|
|
|
1291
1447
|
shouldPromptFileEdit(filePath) {
|
|
1292
1448
|
if (this.mode === "plan") {
|
|
1293
1449
|
return {
|
|
1294
|
-
prompt:
|
|
1450
|
+
prompt: true,
|
|
1295
1451
|
isPlanBlocked: true,
|
|
1296
|
-
reason:
|
|
1452
|
+
reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
|
|
1297
1453
|
};
|
|
1298
1454
|
}
|
|
1299
1455
|
if (this.mode === "manual") {
|
|
@@ -1307,11 +1463,14 @@ class ExecPolicy {
|
|
|
1307
1463
|
evaluate(command) {
|
|
1308
1464
|
const trimmed = command.trim();
|
|
1309
1465
|
if (this.mode === "plan") {
|
|
1310
|
-
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);
|
|
1311
1467
|
if (isReadOnly) {
|
|
1312
1468
|
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1313
1469
|
}
|
|
1314
|
-
return {
|
|
1470
|
+
return {
|
|
1471
|
+
decision: "prompt",
|
|
1472
|
+
reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
|
|
1473
|
+
};
|
|
1315
1474
|
}
|
|
1316
1475
|
if (this.mode === "manual") {
|
|
1317
1476
|
return {
|
|
@@ -1582,9 +1741,9 @@ class Session {
|
|
|
1582
1741
|
}
|
|
1583
1742
|
}
|
|
1584
1743
|
// src/tools/handlers/apply-patch.ts
|
|
1585
|
-
import { existsSync as
|
|
1744
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
1586
1745
|
import { resolve as resolve5, dirname as dirname3 } from "path";
|
|
1587
|
-
import { mkdirSync as
|
|
1746
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
1588
1747
|
var applyPatchTool = {
|
|
1589
1748
|
name: "apply_patch",
|
|
1590
1749
|
description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
|
|
@@ -1616,34 +1775,40 @@ var applyPatchTool = {
|
|
|
1616
1775
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1617
1776
|
if (ctx.execPolicy) {
|
|
1618
1777
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1619
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1620
|
-
return {
|
|
1621
|
-
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1622
|
-
isError: true
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
1778
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
1626
|
-
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}`);
|
|
1627
1780
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1628
1781
|
if (!allowed) {
|
|
1629
|
-
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
|
+
};
|
|
1630
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
|
+
};
|
|
1631
1792
|
}
|
|
1632
1793
|
}
|
|
1633
|
-
if (!
|
|
1794
|
+
if (!existsSync6(filePath)) {
|
|
1634
1795
|
if (targetContent) {
|
|
1635
1796
|
return {
|
|
1636
|
-
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.`,
|
|
1637
1802
|
isError: true
|
|
1638
1803
|
};
|
|
1639
1804
|
}
|
|
1640
1805
|
try {
|
|
1641
|
-
|
|
1806
|
+
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
1642
1807
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1643
1808
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1644
1809
|
} catch (err) {
|
|
1645
1810
|
return {
|
|
1646
|
-
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)}`,
|
|
1647
1812
|
isError: true
|
|
1648
1813
|
};
|
|
1649
1814
|
}
|
|
@@ -1652,21 +1817,33 @@ var applyPatchTool = {
|
|
|
1652
1817
|
const originalFileContent = readFileSync4(filePath, "utf8");
|
|
1653
1818
|
if (!targetContent) {
|
|
1654
1819
|
return {
|
|
1655
|
-
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.`,
|
|
1656
1825
|
isError: true
|
|
1657
1826
|
};
|
|
1658
1827
|
}
|
|
1659
1828
|
const firstIndex = originalFileContent.indexOf(targetContent);
|
|
1660
1829
|
if (firstIndex === -1) {
|
|
1661
1830
|
return {
|
|
1662
|
-
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'.`,
|
|
1663
1836
|
isError: true
|
|
1664
1837
|
};
|
|
1665
1838
|
}
|
|
1666
1839
|
const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
|
|
1667
1840
|
if (secondIndex !== -1) {
|
|
1668
1841
|
return {
|
|
1669
|
-
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.`,
|
|
1670
1847
|
isError: true
|
|
1671
1848
|
};
|
|
1672
1849
|
}
|
|
@@ -1677,7 +1854,7 @@ var applyPatchTool = {
|
|
|
1677
1854
|
};
|
|
1678
1855
|
} catch (err) {
|
|
1679
1856
|
return {
|
|
1680
|
-
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)}`,
|
|
1681
1858
|
isError: true
|
|
1682
1859
|
};
|
|
1683
1860
|
}
|
|
@@ -1764,7 +1941,7 @@ class WindowsSandbox {
|
|
|
1764
1941
|
}
|
|
1765
1942
|
|
|
1766
1943
|
// src/security/kernel/linux.ts
|
|
1767
|
-
import { existsSync as
|
|
1944
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1768
1945
|
|
|
1769
1946
|
class LinuxSandbox {
|
|
1770
1947
|
hasBwrap = false;
|
|
@@ -1775,7 +1952,7 @@ class LinuxSandbox {
|
|
|
1775
1952
|
if (process.platform !== "linux") {
|
|
1776
1953
|
return;
|
|
1777
1954
|
}
|
|
1778
|
-
this.hasBwrap =
|
|
1955
|
+
this.hasBwrap = existsSync7("/usr/bin/bwrap") || existsSync7("/bin/bwrap") || existsSync7("/usr/local/bin/bwrap");
|
|
1779
1956
|
}
|
|
1780
1957
|
wrapCommand(cmd, profile) {
|
|
1781
1958
|
if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
|
|
@@ -1809,7 +1986,7 @@ class LinuxSandbox {
|
|
|
1809
1986
|
}
|
|
1810
1987
|
|
|
1811
1988
|
// src/security/kernel/macos.ts
|
|
1812
|
-
import { existsSync as
|
|
1989
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1813
1990
|
|
|
1814
1991
|
class MacOSSandbox {
|
|
1815
1992
|
hasSandboxExec = false;
|
|
@@ -1820,7 +1997,7 @@ class MacOSSandbox {
|
|
|
1820
1997
|
if (process.platform !== "darwin") {
|
|
1821
1998
|
return;
|
|
1822
1999
|
}
|
|
1823
|
-
this.hasSandboxExec =
|
|
2000
|
+
this.hasSandboxExec = existsSync8("/usr/bin/sandbox-exec");
|
|
1824
2001
|
}
|
|
1825
2002
|
generateProfile(profile) {
|
|
1826
2003
|
const rules = [
|
|
@@ -1930,7 +2107,7 @@ var globalKernelSandbox = new KernelSandboxManager;
|
|
|
1930
2107
|
|
|
1931
2108
|
// src/storage/prefix-rules-store.ts
|
|
1932
2109
|
import { Database } from "bun:sqlite";
|
|
1933
|
-
import { existsSync as
|
|
2110
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
|
|
1934
2111
|
import { dirname as dirname4, resolve as resolve7 } from "path";
|
|
1935
2112
|
class PrefixRulesStore {
|
|
1936
2113
|
db;
|
|
@@ -1941,8 +2118,8 @@ class PrefixRulesStore {
|
|
|
1941
2118
|
const effectivePath = dbOrPath || getPrefixRulesDbPath();
|
|
1942
2119
|
if (effectivePath !== ":memory:") {
|
|
1943
2120
|
const dir = dirname4(effectivePath);
|
|
1944
|
-
if (!
|
|
1945
|
-
|
|
2121
|
+
if (!existsSync9(dir)) {
|
|
2122
|
+
mkdirSync5(dir, { recursive: true });
|
|
1946
2123
|
}
|
|
1947
2124
|
}
|
|
1948
2125
|
this.db = new Database(effectivePath);
|
|
@@ -2131,6 +2308,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2131
2308
|
const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : 30000;
|
|
2132
2309
|
const isWindows = process.platform === "win32";
|
|
2133
2310
|
const baseCmd = isWindows ? ["cmd.exe", "/d", "/s", "/c", command] : ["/bin/sh", "-c", command];
|
|
2311
|
+
const ephemeralScratchpad = globalEphemeralWorkspace.createScratchpad(ctx.turnId);
|
|
2134
2312
|
const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd);
|
|
2135
2313
|
if (isEscalated) {
|
|
2136
2314
|
sandboxProfile.allowNetwork = true;
|
|
@@ -2141,6 +2319,10 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2141
2319
|
cwd: ctx.cwd,
|
|
2142
2320
|
env: {
|
|
2143
2321
|
...process.env,
|
|
2322
|
+
TMPDIR: ephemeralScratchpad,
|
|
2323
|
+
TEMP: ephemeralScratchpad,
|
|
2324
|
+
TMP: ephemeralScratchpad,
|
|
2325
|
+
GROUPY_SCRATCH_DIR: ephemeralScratchpad,
|
|
2144
2326
|
...ctx.proxyEnv
|
|
2145
2327
|
},
|
|
2146
2328
|
stdout: "pipe",
|
|
@@ -2177,9 +2359,14 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2177
2359
|
if (result.stderr)
|
|
2178
2360
|
outputParts.push(`STDERR:
|
|
2179
2361
|
${result.stderr.trim()}`);
|
|
2180
|
-
if (result.code !== 0)
|
|
2362
|
+
if (result.code !== 0) {
|
|
2181
2363
|
outputParts.push(`
|
|
2182
|
-
[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
|
+
}
|
|
2183
2370
|
const output = outputParts.join(`
|
|
2184
2371
|
`) || "[Command completed with no output]";
|
|
2185
2372
|
return {
|
|
@@ -2188,16 +2375,21 @@ ${result.stderr.trim()}`);
|
|
|
2188
2375
|
};
|
|
2189
2376
|
} catch (err) {
|
|
2190
2377
|
return {
|
|
2191
|
-
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.`,
|
|
2192
2382
|
isError: true
|
|
2193
2383
|
};
|
|
2384
|
+
} finally {
|
|
2385
|
+
globalEphemeralWorkspace.cleanup(ephemeralScratchpad);
|
|
2194
2386
|
}
|
|
2195
2387
|
}
|
|
2196
2388
|
};
|
|
2197
2389
|
}
|
|
2198
2390
|
var shellTool = createShellTool();
|
|
2199
2391
|
// src/tools/handlers/file-ops.ts
|
|
2200
|
-
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";
|
|
2201
2393
|
import { resolve as resolve8, dirname as dirname5 } from "path";
|
|
2202
2394
|
var readFileTool = {
|
|
2203
2395
|
name: "read_file",
|
|
@@ -2211,7 +2403,7 @@ var readFileTool = {
|
|
|
2211
2403
|
},
|
|
2212
2404
|
async execute(args, ctx) {
|
|
2213
2405
|
const filePath = resolve8(ctx.cwd, String(args.path || ""));
|
|
2214
|
-
if (!
|
|
2406
|
+
if (!existsSync10(filePath)) {
|
|
2215
2407
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2216
2408
|
}
|
|
2217
2409
|
try {
|
|
@@ -2233,14 +2425,14 @@ var listDirTool = {
|
|
|
2233
2425
|
},
|
|
2234
2426
|
async execute(args, ctx) {
|
|
2235
2427
|
const dirPath = resolve8(ctx.cwd, String(args.path || "."));
|
|
2236
|
-
if (!
|
|
2428
|
+
if (!existsSync10(dirPath)) {
|
|
2237
2429
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2238
2430
|
}
|
|
2239
2431
|
try {
|
|
2240
|
-
const entries =
|
|
2432
|
+
const entries = readdirSync3(dirPath);
|
|
2241
2433
|
const formatted = entries.map((entry) => {
|
|
2242
2434
|
const full = resolve8(dirPath, entry);
|
|
2243
|
-
const isDir =
|
|
2435
|
+
const isDir = statSync3(full).isDirectory();
|
|
2244
2436
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2245
2437
|
});
|
|
2246
2438
|
return { output: formatted.join(`
|
|
@@ -2266,26 +2458,35 @@ var writeFileTool = {
|
|
|
2266
2458
|
const filePath = resolve8(ctx.cwd, rawPath);
|
|
2267
2459
|
if (ctx.execPolicy) {
|
|
2268
2460
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2269
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2270
|
-
return {
|
|
2271
|
-
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2272
|
-
isError: true
|
|
2273
|
-
};
|
|
2274
|
-
}
|
|
2275
2461
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
2276
|
-
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}`);
|
|
2277
2463
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2278
2464
|
if (!allowed) {
|
|
2279
|
-
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
|
+
};
|
|
2280
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
|
+
};
|
|
2281
2475
|
}
|
|
2282
2476
|
}
|
|
2283
2477
|
try {
|
|
2284
|
-
|
|
2478
|
+
mkdirSync6(dirname5(filePath), { recursive: true });
|
|
2285
2479
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2286
2480
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2287
2481
|
} catch (err) {
|
|
2288
|
-
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
|
+
};
|
|
2289
2490
|
}
|
|
2290
2491
|
}
|
|
2291
2492
|
};
|
|
@@ -2404,8 +2605,8 @@ var updatePlanTool = {
|
|
|
2404
2605
|
}
|
|
2405
2606
|
};
|
|
2406
2607
|
// src/search/engine.ts
|
|
2407
|
-
import { readdirSync as
|
|
2408
|
-
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";
|
|
2409
2610
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2410
2611
|
".git",
|
|
2411
2612
|
"node_modules",
|
|
@@ -2451,7 +2652,7 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2451
2652
|
class FileSearchEngine {
|
|
2452
2653
|
grep(cwd, options) {
|
|
2453
2654
|
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2454
|
-
if (!
|
|
2655
|
+
if (!existsSync11(searchRoot)) {
|
|
2455
2656
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2456
2657
|
}
|
|
2457
2658
|
const maxResults = options.maxResults || 50;
|
|
@@ -2501,7 +2702,7 @@ class FileSearchEngine {
|
|
|
2501
2702
|
}
|
|
2502
2703
|
findFiles(cwd, options) {
|
|
2503
2704
|
const searchRoot = resolve9(cwd, options.path || ".");
|
|
2504
|
-
if (!
|
|
2705
|
+
if (!existsSync11(searchRoot))
|
|
2505
2706
|
return [];
|
|
2506
2707
|
const maxResults = options.maxResults || 100;
|
|
2507
2708
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2540,8 +2741,8 @@ class FileSearchEngine {
|
|
|
2540
2741
|
}
|
|
2541
2742
|
loadGitignoreRules(root) {
|
|
2542
2743
|
const rules = new Set;
|
|
2543
|
-
const gitignorePath =
|
|
2544
|
-
if (
|
|
2744
|
+
const gitignorePath = join5(root, ".gitignore");
|
|
2745
|
+
if (existsSync11(gitignorePath)) {
|
|
2545
2746
|
try {
|
|
2546
2747
|
const lines = readFileSync6(gitignorePath, "utf8").split(`
|
|
2547
2748
|
`);
|
|
@@ -2558,7 +2759,7 @@ class FileSearchEngine {
|
|
|
2558
2759
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2559
2760
|
const results = [];
|
|
2560
2761
|
try {
|
|
2561
|
-
const stat =
|
|
2762
|
+
const stat = statSync4(dir);
|
|
2562
2763
|
if (!stat.isDirectory()) {
|
|
2563
2764
|
if (!this.isBinary(dir)) {
|
|
2564
2765
|
results.push(dir);
|
|
@@ -2572,9 +2773,9 @@ class FileSearchEngine {
|
|
|
2572
2773
|
while (queue.length > 0) {
|
|
2573
2774
|
const currentDir = queue.shift();
|
|
2574
2775
|
try {
|
|
2575
|
-
const entries =
|
|
2776
|
+
const entries = readdirSync4(currentDir, { withFileTypes: true });
|
|
2576
2777
|
for (const entry of entries) {
|
|
2577
|
-
const fullPath =
|
|
2778
|
+
const fullPath = join5(currentDir, entry.name);
|
|
2578
2779
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2579
2780
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2580
2781
|
continue;
|
|
@@ -3273,8 +3474,8 @@ function createDefaultTools(options = {}) {
|
|
|
3273
3474
|
}
|
|
3274
3475
|
|
|
3275
3476
|
// src/agents/roles.ts
|
|
3276
|
-
import { existsSync as
|
|
3277
|
-
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";
|
|
3278
3479
|
|
|
3279
3480
|
class AgentRoleRegistry {
|
|
3280
3481
|
roles = new Map;
|
|
@@ -3359,13 +3560,13 @@ class AgentRoleRegistry {
|
|
|
3359
3560
|
}
|
|
3360
3561
|
loadRolesFromDir(dirPath) {
|
|
3361
3562
|
const fullPath = resolve10(dirPath);
|
|
3362
|
-
if (!
|
|
3563
|
+
if (!existsSync12(fullPath))
|
|
3363
3564
|
return;
|
|
3364
|
-
const entries =
|
|
3565
|
+
const entries = readdirSync5(fullPath);
|
|
3365
3566
|
for (const entry of entries) {
|
|
3366
3567
|
if (entry.endsWith(".json")) {
|
|
3367
3568
|
try {
|
|
3368
|
-
const content = readFileSync7(
|
|
3569
|
+
const content = readFileSync7(join6(fullPath, entry), "utf8");
|
|
3369
3570
|
const parsed = JSON.parse(content);
|
|
3370
3571
|
if (parsed.name && parsed.systemPrompt) {
|
|
3371
3572
|
this.registerRole(parsed);
|
|
@@ -3418,7 +3619,7 @@ function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
|
|
|
3418
3619
|
// src/agents/graph-store.ts
|
|
3419
3620
|
import { Database as Database2 } from "bun:sqlite";
|
|
3420
3621
|
import { resolve as resolve11 } from "path";
|
|
3421
|
-
import { existsSync as
|
|
3622
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
|
|
3422
3623
|
class AgentGraphStore {
|
|
3423
3624
|
db;
|
|
3424
3625
|
constructor(dbPathOrDb) {
|
|
@@ -3428,8 +3629,8 @@ class AgentGraphStore {
|
|
|
3428
3629
|
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
3429
3630
|
if (dbPath !== ":memory:") {
|
|
3430
3631
|
const dir = resolve11(dbPath, "..");
|
|
3431
|
-
if (!
|
|
3432
|
-
|
|
3632
|
+
if (!existsSync13(dir)) {
|
|
3633
|
+
mkdirSync7(dir, { recursive: true });
|
|
3433
3634
|
}
|
|
3434
3635
|
}
|
|
3435
3636
|
this.db = new Database2(dbPath);
|
|
@@ -3853,8 +4054,8 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
3853
4054
|
}
|
|
3854
4055
|
|
|
3855
4056
|
// src/mcp/manager.ts
|
|
3856
|
-
import { existsSync as
|
|
3857
|
-
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";
|
|
3858
4059
|
|
|
3859
4060
|
// src/mcp/client.ts
|
|
3860
4061
|
class McpClient {
|
|
@@ -4470,7 +4671,7 @@ class McpManager {
|
|
|
4470
4671
|
}
|
|
4471
4672
|
async loadConfigFile(filePath) {
|
|
4472
4673
|
const fullPath = resolve12(filePath);
|
|
4473
|
-
if (!
|
|
4674
|
+
if (!existsSync14(fullPath))
|
|
4474
4675
|
return;
|
|
4475
4676
|
this.loadedConfigFiles.add(fullPath);
|
|
4476
4677
|
try {
|
|
@@ -4725,11 +4926,11 @@ class McpManager {
|
|
|
4725
4926
|
saveServerToConfigFile(filePath, name, config) {
|
|
4726
4927
|
const fullPath = resolve12(filePath);
|
|
4727
4928
|
const dir = dirname6(fullPath);
|
|
4728
|
-
if (!
|
|
4729
|
-
|
|
4929
|
+
if (!existsSync14(dir)) {
|
|
4930
|
+
mkdirSync8(dir, { recursive: true });
|
|
4730
4931
|
}
|
|
4731
4932
|
let existing = { mcpServers: {} };
|
|
4732
|
-
if (
|
|
4933
|
+
if (existsSync14(fullPath)) {
|
|
4733
4934
|
try {
|
|
4734
4935
|
const content = readFileSync8(fullPath, "utf8");
|
|
4735
4936
|
existing = JSON.parse(content);
|
|
@@ -4744,7 +4945,7 @@ class McpManager {
|
|
|
4744
4945
|
}
|
|
4745
4946
|
removeServerFromConfigFile(filePath, name) {
|
|
4746
4947
|
const fullPath = resolve12(filePath);
|
|
4747
|
-
if (!
|
|
4948
|
+
if (!existsSync14(fullPath))
|
|
4748
4949
|
return false;
|
|
4749
4950
|
try {
|
|
4750
4951
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -4774,11 +4975,11 @@ class McpManager {
|
|
|
4774
4975
|
}
|
|
4775
4976
|
}
|
|
4776
4977
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
4777
|
-
const workspaceConfig =
|
|
4778
|
-
if (
|
|
4978
|
+
const workspaceConfig = join7(cwd, ".mcp.json");
|
|
4979
|
+
if (existsSync14(workspaceConfig))
|
|
4779
4980
|
return workspaceConfig;
|
|
4780
|
-
const altConfig =
|
|
4781
|
-
if (
|
|
4981
|
+
const altConfig = join7(cwd, "mcp_config.json");
|
|
4982
|
+
if (existsSync14(altConfig))
|
|
4782
4983
|
return altConfig;
|
|
4783
4984
|
return workspaceConfig;
|
|
4784
4985
|
}
|
|
@@ -4799,7 +5000,7 @@ class McpManager {
|
|
|
4799
5000
|
|
|
4800
5001
|
// src/storage/sqlite-store.ts
|
|
4801
5002
|
import { Database as Database3 } from "bun:sqlite";
|
|
4802
|
-
import { existsSync as
|
|
5003
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
|
|
4803
5004
|
import { dirname as dirname7 } from "path";
|
|
4804
5005
|
class SqliteThreadStore {
|
|
4805
5006
|
db;
|
|
@@ -4807,8 +5008,8 @@ class SqliteThreadStore {
|
|
|
4807
5008
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
4808
5009
|
if (effectivePath !== ":memory:") {
|
|
4809
5010
|
const dir = dirname7(effectivePath);
|
|
4810
|
-
if (!
|
|
4811
|
-
|
|
5011
|
+
if (!existsSync15(dir)) {
|
|
5012
|
+
mkdirSync9(dir, { recursive: true });
|
|
4812
5013
|
}
|
|
4813
5014
|
}
|
|
4814
5015
|
this.db = new Database3(effectivePath);
|
|
@@ -5042,8 +5243,8 @@ class SessionPersistenceManager {
|
|
|
5042
5243
|
}
|
|
5043
5244
|
|
|
5044
5245
|
// src/skills/loader.ts
|
|
5045
|
-
import { existsSync as
|
|
5046
|
-
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";
|
|
5047
5248
|
import { homedir as homedir3 } from "os";
|
|
5048
5249
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
5049
5250
|
|
|
@@ -5114,7 +5315,7 @@ class SkillsLoader {
|
|
|
5114
5315
|
resolve13(cwd, "skills")
|
|
5115
5316
|
];
|
|
5116
5317
|
for (const cand of candidates) {
|
|
5117
|
-
if (
|
|
5318
|
+
if (existsSync16(cand) && !roots.includes(cand)) {
|
|
5118
5319
|
roots.push(cand);
|
|
5119
5320
|
}
|
|
5120
5321
|
}
|
|
@@ -5123,7 +5324,7 @@ class SkillsLoader {
|
|
|
5123
5324
|
roots.push(getGlobalSkillsDir(), resolve13(homedir3(), ".gemini", "config", "skills"));
|
|
5124
5325
|
}
|
|
5125
5326
|
roots.push(...this.customRoots.map((r) => resolve13(r)));
|
|
5126
|
-
return roots.filter((r) =>
|
|
5327
|
+
return roots.filter((r) => existsSync16(r));
|
|
5127
5328
|
}
|
|
5128
5329
|
discoverSkills(cwd, options) {
|
|
5129
5330
|
return this.listSkills(cwd, options);
|
|
@@ -5139,12 +5340,12 @@ class SkillsLoader {
|
|
|
5139
5340
|
const discovered = new Map;
|
|
5140
5341
|
for (const root of roots) {
|
|
5141
5342
|
try {
|
|
5142
|
-
const entries =
|
|
5343
|
+
const entries = readdirSync6(root, { withFileTypes: true });
|
|
5143
5344
|
for (const entry of entries) {
|
|
5144
5345
|
if (entry.isDirectory()) {
|
|
5145
|
-
const skillDir =
|
|
5146
|
-
const skillFilePath =
|
|
5147
|
-
if (
|
|
5346
|
+
const skillDir = join8(root, entry.name);
|
|
5347
|
+
const skillFilePath = join8(skillDir, "SKILL.md");
|
|
5348
|
+
if (existsSync16(skillFilePath)) {
|
|
5148
5349
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
5149
5350
|
if (meta && !discovered.has(meta.name)) {
|
|
5150
5351
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -5157,7 +5358,7 @@ class SkillsLoader {
|
|
|
5157
5358
|
}
|
|
5158
5359
|
} catch {}
|
|
5159
5360
|
}
|
|
5160
|
-
const result = Array.from(discovered.values());
|
|
5361
|
+
const result = Array.from(discovered.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
5161
5362
|
this.skillsCache.set(cacheKey, { timestamp: now, skills: result });
|
|
5162
5363
|
return result;
|
|
5163
5364
|
}
|
|
@@ -5246,10 +5447,10 @@ class SkillsLoader {
|
|
|
5246
5447
|
const skills = this.listSkills(cwd, { includeDisabled: false });
|
|
5247
5448
|
if (skills.length === 0)
|
|
5248
5449
|
return "";
|
|
5249
|
-
const workspaceSkills = skills.filter((s) => s.scope === "workspace");
|
|
5250
|
-
const builtInSkills = skills.filter((s) => s.scope === "built-in");
|
|
5251
|
-
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in");
|
|
5252
|
-
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);
|
|
5253
5454
|
const lines = selectedSkills.map((s) => {
|
|
5254
5455
|
const desc = s.shortDescription || s.description;
|
|
5255
5456
|
return `- **${s.name}**: ${desc}`;
|
|
@@ -5267,8 +5468,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
5267
5468
|
}
|
|
5268
5469
|
|
|
5269
5470
|
// src/memories/store.ts
|
|
5270
|
-
import { existsSync as
|
|
5271
|
-
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";
|
|
5272
5473
|
import { createHash } from "crypto";
|
|
5273
5474
|
class MemoryStore {
|
|
5274
5475
|
globalPath;
|
|
@@ -5280,7 +5481,7 @@ class MemoryStore {
|
|
|
5280
5481
|
findProjectRoot(cwd) {
|
|
5281
5482
|
let current = resolve14(cwd);
|
|
5282
5483
|
while (true) {
|
|
5283
|
-
if (
|
|
5484
|
+
if (existsSync17(join9(current, ".git"))) {
|
|
5284
5485
|
return current;
|
|
5285
5486
|
}
|
|
5286
5487
|
const parent = dirname8(current);
|
|
@@ -5299,24 +5500,24 @@ class MemoryStore {
|
|
|
5299
5500
|
getProjectMemoryDir(cwd) {
|
|
5300
5501
|
if (this.customWorkspacePath) {
|
|
5301
5502
|
const dir2 = resolve14(this.customWorkspacePath);
|
|
5302
|
-
if (!
|
|
5503
|
+
if (!existsSync17(dir2)) {
|
|
5303
5504
|
try {
|
|
5304
|
-
|
|
5505
|
+
mkdirSync10(dir2, { recursive: true });
|
|
5305
5506
|
} catch {}
|
|
5306
5507
|
}
|
|
5307
5508
|
return dir2;
|
|
5308
5509
|
}
|
|
5309
5510
|
const slug = this.getProjectSlug(cwd);
|
|
5310
|
-
const dir =
|
|
5311
|
-
if (!
|
|
5511
|
+
const dir = join9(getProjectsDir(), slug, "memory");
|
|
5512
|
+
if (!existsSync17(dir)) {
|
|
5312
5513
|
try {
|
|
5313
|
-
|
|
5514
|
+
mkdirSync10(dir, { recursive: true });
|
|
5314
5515
|
} catch {}
|
|
5315
5516
|
}
|
|
5316
5517
|
return dir;
|
|
5317
5518
|
}
|
|
5318
5519
|
getMemoryIndexPath(cwd) {
|
|
5319
|
-
return
|
|
5520
|
+
return join9(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
5320
5521
|
}
|
|
5321
5522
|
normalizeCategory(raw) {
|
|
5322
5523
|
const cat = raw.toLowerCase().trim();
|
|
@@ -5335,7 +5536,7 @@ class MemoryStore {
|
|
|
5335
5536
|
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
5336
5537
|
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
5337
5538
|
const fileName = `${type}_${sanitizedName}.md`;
|
|
5338
|
-
const filePath =
|
|
5539
|
+
const filePath = join9(memoryDir, fileName);
|
|
5339
5540
|
const nowIso = new Date().toISOString();
|
|
5340
5541
|
const cleanContent = params.content.trim();
|
|
5341
5542
|
const desc = (params.description || cleanContent.split(`
|
|
@@ -5370,17 +5571,17 @@ class MemoryStore {
|
|
|
5370
5571
|
}
|
|
5371
5572
|
readTopicMemory(topicNameOrFile, cwd) {
|
|
5372
5573
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5373
|
-
let targetPath =
|
|
5374
|
-
if (!
|
|
5574
|
+
let targetPath = join9(memoryDir, topicNameOrFile);
|
|
5575
|
+
if (!existsSync17(targetPath)) {
|
|
5375
5576
|
if (!topicNameOrFile.endsWith(".md")) {
|
|
5376
|
-
targetPath =
|
|
5577
|
+
targetPath = join9(memoryDir, `${topicNameOrFile}.md`);
|
|
5377
5578
|
}
|
|
5378
5579
|
}
|
|
5379
|
-
if (!
|
|
5380
|
-
const files =
|
|
5580
|
+
if (!existsSync17(targetPath)) {
|
|
5581
|
+
const files = readdirSync7(memoryDir);
|
|
5381
5582
|
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
5382
5583
|
if (match) {
|
|
5383
|
-
targetPath =
|
|
5584
|
+
targetPath = join9(memoryDir, match);
|
|
5384
5585
|
} else {
|
|
5385
5586
|
return null;
|
|
5386
5587
|
}
|
|
@@ -5441,12 +5642,12 @@ class MemoryStore {
|
|
|
5441
5642
|
}
|
|
5442
5643
|
syncMemoryIndex(cwd) {
|
|
5443
5644
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5444
|
-
const indexPath =
|
|
5445
|
-
const files =
|
|
5645
|
+
const indexPath = join9(memoryDir, "MEMORY.md");
|
|
5646
|
+
const files = existsSync17(memoryDir) ? readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
5446
5647
|
const items = [];
|
|
5447
5648
|
for (const f of files) {
|
|
5448
5649
|
try {
|
|
5449
|
-
const full =
|
|
5650
|
+
const full = join9(memoryDir, f);
|
|
5450
5651
|
const parsed = this.parseTopicFile(readFileSync10(full, "utf8"), full);
|
|
5451
5652
|
items.push({
|
|
5452
5653
|
type: parsed.type,
|
|
@@ -5473,7 +5674,7 @@ class MemoryStore {
|
|
|
5473
5674
|
}
|
|
5474
5675
|
loadMemoryIndex(cwd) {
|
|
5475
5676
|
const indexPath = this.getMemoryIndexPath(cwd);
|
|
5476
|
-
if (!
|
|
5677
|
+
if (!existsSync17(indexPath))
|
|
5477
5678
|
return "";
|
|
5478
5679
|
try {
|
|
5479
5680
|
const raw = readFileSync10(indexPath, "utf8");
|
|
@@ -5489,13 +5690,13 @@ class MemoryStore {
|
|
|
5489
5690
|
}
|
|
5490
5691
|
listProjectMemories(cwd) {
|
|
5491
5692
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
5492
|
-
if (!
|
|
5693
|
+
if (!existsSync17(memoryDir))
|
|
5493
5694
|
return [];
|
|
5494
|
-
const files =
|
|
5695
|
+
const files = readdirSync7(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
5495
5696
|
const list = [];
|
|
5496
5697
|
for (const f of files) {
|
|
5497
5698
|
try {
|
|
5498
|
-
const full =
|
|
5699
|
+
const full = join9(memoryDir, f);
|
|
5499
5700
|
list.push(this.parseTopicFile(readFileSync10(full, "utf8"), full));
|
|
5500
5701
|
} catch {}
|
|
5501
5702
|
}
|
|
@@ -5547,8 +5748,8 @@ class MemoryStore {
|
|
|
5547
5748
|
}
|
|
5548
5749
|
|
|
5549
5750
|
// src/worktree/manager.ts
|
|
5550
|
-
import { resolve as resolve16, join as
|
|
5551
|
-
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";
|
|
5552
5753
|
|
|
5553
5754
|
// src/worktree/git.ts
|
|
5554
5755
|
import { resolve as resolve15 } from "path";
|
|
@@ -5698,15 +5899,15 @@ class WorktreeManager {
|
|
|
5698
5899
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
5699
5900
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve16(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve16(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
5700
5901
|
const worktreeParent = resolve16(targetDir, "..");
|
|
5701
|
-
if (!
|
|
5702
|
-
|
|
5902
|
+
if (!existsSync18(worktreeParent)) {
|
|
5903
|
+
mkdirSync11(worktreeParent, { recursive: true });
|
|
5703
5904
|
}
|
|
5704
5905
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
5705
5906
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
5706
5907
|
if (!result.success) {
|
|
5707
5908
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
5708
5909
|
}
|
|
5709
|
-
const metaPath =
|
|
5910
|
+
const metaPath = join10(targetDir, "groupy-thread.json");
|
|
5710
5911
|
try {
|
|
5711
5912
|
writeFileSync6(metaPath, JSON.stringify({
|
|
5712
5913
|
version: 1,
|
|
@@ -5732,8 +5933,8 @@ class WorktreeManager {
|
|
|
5732
5933
|
return [];
|
|
5733
5934
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
5734
5935
|
return worktrees.map((wt) => {
|
|
5735
|
-
const metaPath =
|
|
5736
|
-
if (
|
|
5936
|
+
const metaPath = join10(wt.path, "groupy-thread.json");
|
|
5937
|
+
if (existsSync18(metaPath)) {
|
|
5737
5938
|
try {
|
|
5738
5939
|
const raw = JSON.parse(readFileSync11(metaPath, "utf8"));
|
|
5739
5940
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -6431,7 +6632,7 @@ function parsePatch(oldSrc, newSrc, contextLines = 3) {
|
|
|
6431
6632
|
// package.json
|
|
6432
6633
|
var package_default = {
|
|
6433
6634
|
name: "@pikaa-ai/pikaa",
|
|
6434
|
-
version: "0.3.
|
|
6635
|
+
version: "0.3.20",
|
|
6435
6636
|
description: "PIKAA CLI - AI coding agent that runs locally in your terminal.",
|
|
6436
6637
|
main: "./dist/index.js",
|
|
6437
6638
|
module: "./dist/index.js",
|
|
@@ -6806,7 +7007,12 @@ ${preview}${more}`);
|
|
|
6806
7007
|
if (metrics.inputTokens !== undefined || metrics.outputTokens !== undefined) {
|
|
6807
7008
|
const inStr = metrics.inputTokens !== undefined ? formatTokens(metrics.inputTokens) : "0";
|
|
6808
7009
|
const outStr = metrics.outputTokens !== undefined ? formatTokens(metrics.outputTokens) : "0";
|
|
6809
|
-
|
|
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
|
+
}
|
|
6810
7016
|
} else if (metrics.totalTokens !== undefined && metrics.totalTokens > 0) {
|
|
6811
7017
|
parts.push(`${style.dim(`${formatTokens(metrics.totalTokens)} tokens`)}`);
|
|
6812
7018
|
}
|
|
@@ -7923,8 +8129,8 @@ async function promptInteractiveList(config) {
|
|
|
7923
8129
|
}
|
|
7924
8130
|
|
|
7925
8131
|
// src/security/scanner.ts
|
|
7926
|
-
import { existsSync as
|
|
7927
|
-
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";
|
|
7928
8134
|
var SECURITY_RULES = [
|
|
7929
8135
|
{
|
|
7930
8136
|
id: "SEC-001",
|
|
@@ -8062,21 +8268,21 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
8062
8268
|
const findings = [];
|
|
8063
8269
|
let scannedCount = 0;
|
|
8064
8270
|
function walk(current) {
|
|
8065
|
-
if (scannedCount >= maxFiles || !
|
|
8271
|
+
if (scannedCount >= maxFiles || !existsSync19(current))
|
|
8066
8272
|
return;
|
|
8067
8273
|
let entries;
|
|
8068
8274
|
try {
|
|
8069
|
-
entries =
|
|
8275
|
+
entries = readdirSync8(current);
|
|
8070
8276
|
} catch {
|
|
8071
8277
|
return;
|
|
8072
8278
|
}
|
|
8073
8279
|
for (const entry of entries) {
|
|
8074
8280
|
if (scannedCount >= maxFiles)
|
|
8075
8281
|
break;
|
|
8076
|
-
const fullPath =
|
|
8282
|
+
const fullPath = join11(current, entry);
|
|
8077
8283
|
let stat;
|
|
8078
8284
|
try {
|
|
8079
|
-
stat =
|
|
8285
|
+
stat = statSync5(fullPath);
|
|
8080
8286
|
} catch {
|
|
8081
8287
|
continue;
|
|
8082
8288
|
}
|
|
@@ -8181,8 +8387,8 @@ var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
|
8181
8387
|
var SQLITE_MCP_SERVER_PATH = resolve20(__dirname, "server.ts");
|
|
8182
8388
|
|
|
8183
8389
|
// src/init/project-analyzer.ts
|
|
8184
|
-
import { existsSync as
|
|
8185
|
-
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";
|
|
8186
8392
|
|
|
8187
8393
|
class ProjectAnalyzer {
|
|
8188
8394
|
cwd;
|
|
@@ -8200,8 +8406,8 @@ class ProjectAnalyzer {
|
|
|
8200
8406
|
const architectureNotes = [];
|
|
8201
8407
|
const codeConventions = [];
|
|
8202
8408
|
let description = readmeInfo.description;
|
|
8203
|
-
const pkgPath =
|
|
8204
|
-
if (
|
|
8409
|
+
const pkgPath = join12(this.cwd, "package.json");
|
|
8410
|
+
if (existsSync20(pkgPath)) {
|
|
8205
8411
|
try {
|
|
8206
8412
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8207
8413
|
if (!description && pkg.description)
|
|
@@ -8274,8 +8480,8 @@ class ProjectAnalyzer {
|
|
|
8274
8480
|
}
|
|
8275
8481
|
} catch {}
|
|
8276
8482
|
}
|
|
8277
|
-
const tsconfigPath =
|
|
8278
|
-
if (
|
|
8483
|
+
const tsconfigPath = join12(this.cwd, "tsconfig.json");
|
|
8484
|
+
if (existsSync20(tsconfigPath)) {
|
|
8279
8485
|
try {
|
|
8280
8486
|
const tsconfig = JSON.parse(readFileSync13(tsconfigPath, "utf8"));
|
|
8281
8487
|
if (tsconfig.compilerOptions?.strict) {
|
|
@@ -8286,8 +8492,8 @@ class ProjectAnalyzer {
|
|
|
8286
8492
|
}
|
|
8287
8493
|
} catch {}
|
|
8288
8494
|
}
|
|
8289
|
-
const cargoPath =
|
|
8290
|
-
if (
|
|
8495
|
+
const cargoPath = join12(this.cwd, "Cargo.toml");
|
|
8496
|
+
if (existsSync20(cargoPath)) {
|
|
8291
8497
|
try {
|
|
8292
8498
|
commands.dev = commands.dev || "cargo run";
|
|
8293
8499
|
commands.build = commands.build || "cargo build";
|
|
@@ -8296,8 +8502,8 @@ class ProjectAnalyzer {
|
|
|
8296
8502
|
frameworks.push("Rust Cargo");
|
|
8297
8503
|
} catch {}
|
|
8298
8504
|
}
|
|
8299
|
-
const goModPath =
|
|
8300
|
-
if (
|
|
8505
|
+
const goModPath = join12(this.cwd, "go.mod");
|
|
8506
|
+
if (existsSync20(goModPath)) {
|
|
8301
8507
|
try {
|
|
8302
8508
|
commands.dev = commands.dev || "go run .";
|
|
8303
8509
|
commands.build = commands.build || "go build ./...";
|
|
@@ -8306,37 +8512,37 @@ class ProjectAnalyzer {
|
|
|
8306
8512
|
frameworks.push("Go Modules");
|
|
8307
8513
|
} catch {}
|
|
8308
8514
|
}
|
|
8309
|
-
const pyprojectPath =
|
|
8310
|
-
const requirementsPath =
|
|
8311
|
-
if (
|
|
8515
|
+
const pyprojectPath = join12(this.cwd, "pyproject.toml");
|
|
8516
|
+
const requirementsPath = join12(this.cwd, "requirements.txt");
|
|
8517
|
+
if (existsSync20(pyprojectPath) || existsSync20(requirementsPath)) {
|
|
8312
8518
|
commands.test = commands.test || "pytest";
|
|
8313
8519
|
commands.lint = commands.lint || "ruff check .";
|
|
8314
|
-
if (
|
|
8520
|
+
if (existsSync20(join12(this.cwd, "uv.lock"))) {
|
|
8315
8521
|
frameworks.push("uv");
|
|
8316
8522
|
commands.test = "uv run pytest";
|
|
8317
|
-
} else if (
|
|
8523
|
+
} else if (existsSync20(join12(this.cwd, "poetry.lock"))) {
|
|
8318
8524
|
frameworks.push("Poetry");
|
|
8319
8525
|
commands.test = "poetry run pytest";
|
|
8320
8526
|
}
|
|
8321
8527
|
}
|
|
8322
|
-
if (
|
|
8528
|
+
if (existsSync20(join12(this.cwd, "Dockerfile"))) {
|
|
8323
8529
|
infrastructure.push("Docker");
|
|
8324
8530
|
const sanitizedName = projectName.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/^-+|-+$/g, "");
|
|
8325
8531
|
commands.dockerBuild = `docker build -t ${sanitizedName || "app"} .`;
|
|
8326
8532
|
}
|
|
8327
|
-
if (
|
|
8533
|
+
if (existsSync20(join12(this.cwd, "nginx.conf"))) {
|
|
8328
8534
|
infrastructure.push("Nginx");
|
|
8329
8535
|
}
|
|
8330
|
-
if (
|
|
8536
|
+
if (existsSync20(join12(this.cwd, "src/api.ts")) || existsSync20(join12(this.cwd, "src/api"))) {
|
|
8331
8537
|
architectureNotes.push("Backend API endpoints and network client logic are centralized in `src/api`.");
|
|
8332
8538
|
}
|
|
8333
|
-
if (
|
|
8539
|
+
if (existsSync20(join12(this.cwd, "src/components"))) {
|
|
8334
8540
|
architectureNotes.push("Reusable UI presentation components live in `src/components/`.");
|
|
8335
8541
|
}
|
|
8336
|
-
if (
|
|
8542
|
+
if (existsSync20(join12(this.cwd, "src/types.ts")) || existsSync20(join12(this.cwd, "src/types"))) {
|
|
8337
8543
|
architectureNotes.push("Shared TypeScript data models and interfaces are defined in `src/types`.");
|
|
8338
8544
|
}
|
|
8339
|
-
if (
|
|
8545
|
+
if (existsSync20(join12(this.cwd, ".env.example"))) {
|
|
8340
8546
|
architectureNotes.push("Environment configuration template is in `.env.example`.");
|
|
8341
8547
|
}
|
|
8342
8548
|
if (commands.typecheck || commands.lint || commands.test) {
|
|
@@ -8353,7 +8559,7 @@ class ProjectAnalyzer {
|
|
|
8353
8559
|
let hasExistingInstructions = false;
|
|
8354
8560
|
let existingInstructionFile;
|
|
8355
8561
|
for (const f of instructionFiles) {
|
|
8356
|
-
if (
|
|
8562
|
+
if (existsSync20(join12(this.cwd, f))) {
|
|
8357
8563
|
hasExistingInstructions = true;
|
|
8358
8564
|
existingInstructionFile = f;
|
|
8359
8565
|
break;
|
|
@@ -8434,8 +8640,8 @@ class ProjectAnalyzer {
|
|
|
8434
8640
|
extractReadmeMetadata() {
|
|
8435
8641
|
const readmeFiles = ["README.md", "readme.md", "README.MD"];
|
|
8436
8642
|
for (const file of readmeFiles) {
|
|
8437
|
-
const fullPath =
|
|
8438
|
-
if (
|
|
8643
|
+
const fullPath = join12(this.cwd, file);
|
|
8644
|
+
if (existsSync20(fullPath)) {
|
|
8439
8645
|
try {
|
|
8440
8646
|
const content = readFileSync13(fullPath, "utf8");
|
|
8441
8647
|
const lines = content.split(`
|
|
@@ -8460,8 +8666,8 @@ class ProjectAnalyzer {
|
|
|
8460
8666
|
return {};
|
|
8461
8667
|
}
|
|
8462
8668
|
detectProjectName() {
|
|
8463
|
-
const pkgPath =
|
|
8464
|
-
if (
|
|
8669
|
+
const pkgPath = join12(this.cwd, "package.json");
|
|
8670
|
+
if (existsSync20(pkgPath)) {
|
|
8465
8671
|
try {
|
|
8466
8672
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
8467
8673
|
if (pkg.name && pkg.name !== "frontend" && pkg.name !== "backend" && pkg.name !== "app") {
|
|
@@ -8469,16 +8675,16 @@ class ProjectAnalyzer {
|
|
|
8469
8675
|
}
|
|
8470
8676
|
} catch {}
|
|
8471
8677
|
}
|
|
8472
|
-
const cargoPath =
|
|
8473
|
-
if (
|
|
8678
|
+
const cargoPath = join12(this.cwd, "Cargo.toml");
|
|
8679
|
+
if (existsSync20(cargoPath)) {
|
|
8474
8680
|
try {
|
|
8475
8681
|
const match = readFileSync13(cargoPath, "utf8").match(/name\s*=\s*"([^"]+)"/);
|
|
8476
8682
|
if (match?.[1])
|
|
8477
8683
|
return match[1];
|
|
8478
8684
|
} catch {}
|
|
8479
8685
|
}
|
|
8480
|
-
const goModPath =
|
|
8481
|
-
if (
|
|
8686
|
+
const goModPath = join12(this.cwd, "go.mod");
|
|
8687
|
+
if (existsSync20(goModPath)) {
|
|
8482
8688
|
try {
|
|
8483
8689
|
const match = readFileSync13(goModPath, "utf8").match(/module\s+([^\s]+)/);
|
|
8484
8690
|
if (match?.[1])
|
|
@@ -8489,53 +8695,53 @@ class ProjectAnalyzer {
|
|
|
8489
8695
|
}
|
|
8490
8696
|
detectLanguages() {
|
|
8491
8697
|
const langs = new Set;
|
|
8492
|
-
if (
|
|
8698
|
+
if (existsSync20(join12(this.cwd, "tsconfig.json")) || this.hasFileWithExtension(".ts", ".tsx")) {
|
|
8493
8699
|
langs.add("TypeScript");
|
|
8494
8700
|
}
|
|
8495
|
-
if (
|
|
8701
|
+
if (existsSync20(join12(this.cwd, "package.json")) || this.hasFileWithExtension(".js", ".jsx", ".mjs")) {
|
|
8496
8702
|
langs.add("JavaScript");
|
|
8497
8703
|
}
|
|
8498
|
-
if (
|
|
8704
|
+
if (existsSync20(join12(this.cwd, "Cargo.toml")) || this.hasFileWithExtension(".rs")) {
|
|
8499
8705
|
langs.add("Rust");
|
|
8500
8706
|
}
|
|
8501
|
-
if (
|
|
8707
|
+
if (existsSync20(join12(this.cwd, "go.mod")) || this.hasFileWithExtension(".go")) {
|
|
8502
8708
|
langs.add("Go");
|
|
8503
8709
|
}
|
|
8504
|
-
if (
|
|
8710
|
+
if (existsSync20(join12(this.cwd, "pyproject.toml")) || existsSync20(join12(this.cwd, "requirements.txt")) || this.hasFileWithExtension(".py")) {
|
|
8505
8711
|
langs.add("Python");
|
|
8506
8712
|
}
|
|
8507
|
-
if (
|
|
8713
|
+
if (existsSync20(join12(this.cwd, "pom.xml")) || existsSync20(join12(this.cwd, "build.gradle")) || this.hasFileWithExtension(".java")) {
|
|
8508
8714
|
langs.add("Java");
|
|
8509
8715
|
}
|
|
8510
|
-
if (
|
|
8716
|
+
if (existsSync20(join12(this.cwd, "CMakeLists.txt")) || this.hasFileWithExtension(".cpp", ".c", ".h", ".hpp")) {
|
|
8511
8717
|
langs.add("C/C++");
|
|
8512
8718
|
}
|
|
8513
8719
|
return Array.from(langs);
|
|
8514
8720
|
}
|
|
8515
8721
|
detectPackageManager() {
|
|
8516
|
-
if (
|
|
8722
|
+
if (existsSync20(join12(this.cwd, "bun.lockb")) || existsSync20(join12(this.cwd, "bun.lock")))
|
|
8517
8723
|
return "bun";
|
|
8518
|
-
if (
|
|
8724
|
+
if (existsSync20(join12(this.cwd, "pnpm-lock.yaml")))
|
|
8519
8725
|
return "pnpm";
|
|
8520
|
-
if (
|
|
8726
|
+
if (existsSync20(join12(this.cwd, "yarn.lock")))
|
|
8521
8727
|
return "yarn";
|
|
8522
|
-
if (
|
|
8728
|
+
if (existsSync20(join12(this.cwd, "package-lock.json")))
|
|
8523
8729
|
return "npm";
|
|
8524
|
-
if (
|
|
8730
|
+
if (existsSync20(join12(this.cwd, "Cargo.lock")) || existsSync20(join12(this.cwd, "Cargo.toml")))
|
|
8525
8731
|
return "cargo";
|
|
8526
|
-
if (
|
|
8732
|
+
if (existsSync20(join12(this.cwd, "uv.lock")))
|
|
8527
8733
|
return "uv";
|
|
8528
|
-
if (
|
|
8734
|
+
if (existsSync20(join12(this.cwd, "poetry.lock")))
|
|
8529
8735
|
return "poetry";
|
|
8530
|
-
if (
|
|
8736
|
+
if (existsSync20(join12(this.cwd, "go.sum")) || existsSync20(join12(this.cwd, "go.mod")))
|
|
8531
8737
|
return "go";
|
|
8532
|
-
if (
|
|
8738
|
+
if (existsSync20(join12(this.cwd, "package.json")))
|
|
8533
8739
|
return "npm";
|
|
8534
8740
|
return;
|
|
8535
8741
|
}
|
|
8536
8742
|
hasFileWithExtension(...exts) {
|
|
8537
8743
|
try {
|
|
8538
|
-
const entries =
|
|
8744
|
+
const entries = readdirSync9(this.cwd);
|
|
8539
8745
|
return entries.some((e) => exts.some((ext) => e.endsWith(ext)));
|
|
8540
8746
|
} catch {
|
|
8541
8747
|
return false;
|
|
@@ -8543,16 +8749,16 @@ class ProjectAnalyzer {
|
|
|
8543
8749
|
}
|
|
8544
8750
|
}
|
|
8545
8751
|
// src/init/init-command.ts
|
|
8546
|
-
import { existsSync as
|
|
8547
|
-
import { join as
|
|
8752
|
+
import { existsSync as existsSync21, writeFileSync as writeFileSync7 } from "fs";
|
|
8753
|
+
import { join as join13 } from "path";
|
|
8548
8754
|
function runProjectInit(options = {}) {
|
|
8549
8755
|
const cwd = options.cwd || process.cwd();
|
|
8550
8756
|
const filename = options.filename || "AGENTS.md";
|
|
8551
|
-
const targetPath =
|
|
8757
|
+
const targetPath = join13(cwd, filename);
|
|
8552
8758
|
const analyzer = new ProjectAnalyzer(cwd);
|
|
8553
8759
|
const analysis = analyzer.analyze();
|
|
8554
8760
|
const content = analyzer.generateAgentsMarkdown(analysis);
|
|
8555
|
-
const alreadyExists =
|
|
8761
|
+
const alreadyExists = existsSync21(targetPath);
|
|
8556
8762
|
writeFileSync7(targetPath, content, "utf8");
|
|
8557
8763
|
return {
|
|
8558
8764
|
success: true,
|
|
@@ -9326,7 +9532,7 @@ function printSessionStats(ctx) {
|
|
|
9326
9532
|
const turns = ctx.repl?.turnCount ?? 0;
|
|
9327
9533
|
const history = ctx.session.getHistory();
|
|
9328
9534
|
const historyTokens = estimateTotalTokens(history);
|
|
9329
|
-
const maxTokens =
|
|
9535
|
+
const maxTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
9330
9536
|
const contextPct = Math.round(historyTokens / maxTokens * 100);
|
|
9331
9537
|
const colorFn = contextPct < 50 ? style.green : contextPct < 80 ? style.yellow : style.red;
|
|
9332
9538
|
const agents = ctx.spawner?.listAgents() || [];
|
|
@@ -10017,9 +10223,9 @@ class MarkdownHighlighter {
|
|
|
10017
10223
|
}
|
|
10018
10224
|
|
|
10019
10225
|
// src/cli/update-checker.ts
|
|
10020
|
-
import { existsSync as
|
|
10226
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "fs";
|
|
10021
10227
|
import { homedir as homedir5 } from "os";
|
|
10022
|
-
import { join as
|
|
10228
|
+
import { join as join14 } from "path";
|
|
10023
10229
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
10024
10230
|
function parseSemver(v) {
|
|
10025
10231
|
const clean = v.replace(/^v/, "").trim();
|
|
@@ -10040,8 +10246,8 @@ function isNewerVersion(current, remote) {
|
|
|
10040
10246
|
return remPatch > curPatch;
|
|
10041
10247
|
}
|
|
10042
10248
|
function getUpdateCachePath() {
|
|
10043
|
-
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME ||
|
|
10044
|
-
return
|
|
10249
|
+
const baseDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME || join14(homedir5(), ".pikaa");
|
|
10250
|
+
return join14(baseDir, "update-cache.json");
|
|
10045
10251
|
}
|
|
10046
10252
|
async function fetchLatestNpmVersion(packageName, timeoutMs = 1500) {
|
|
10047
10253
|
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
@@ -10074,7 +10280,7 @@ async function checkForUpdates(options = {}) {
|
|
|
10074
10280
|
const cachePath = options.cachePath || getUpdateCachePath();
|
|
10075
10281
|
const now = Date.now();
|
|
10076
10282
|
let cached = null;
|
|
10077
|
-
if (!options.force &&
|
|
10283
|
+
if (!options.force && existsSync22(cachePath)) {
|
|
10078
10284
|
try {
|
|
10079
10285
|
const raw = JSON.parse(readFileSync14(cachePath, "utf8"));
|
|
10080
10286
|
if (raw && typeof raw.lastChecked === "number" && typeof raw.latestVersion === "string") {
|
|
@@ -10104,9 +10310,9 @@ async function checkForUpdates(options = {}) {
|
|
|
10104
10310
|
return null;
|
|
10105
10311
|
}
|
|
10106
10312
|
try {
|
|
10107
|
-
const parentDir =
|
|
10108
|
-
if (!
|
|
10109
|
-
|
|
10313
|
+
const parentDir = join14(cachePath, "..");
|
|
10314
|
+
if (!existsSync22(parentDir)) {
|
|
10315
|
+
mkdirSync12(parentDir, { recursive: true });
|
|
10110
10316
|
}
|
|
10111
10317
|
const cacheData = {
|
|
10112
10318
|
lastChecked: now,
|
|
@@ -10270,6 +10476,7 @@ class CliRepl {
|
|
|
10270
10476
|
inputTokens: msg.inputTokens,
|
|
10271
10477
|
outputTokens: msg.outputTokens || (this.turnCharsOut > 0 ? Math.round(this.turnCharsOut / 3.8) : undefined),
|
|
10272
10478
|
totalTokens: msg.totalTokens,
|
|
10479
|
+
cachedTokens: msg.cachedTokens,
|
|
10273
10480
|
contextTokens: msg.contextTokens,
|
|
10274
10481
|
maxContextTokens: msg.maxContextTokens,
|
|
10275
10482
|
sessionUptimeMs,
|
|
@@ -10528,7 +10735,7 @@ async function main() {
|
|
|
10528
10735
|
resolve21(cwd, "mcp_config.json")
|
|
10529
10736
|
].filter(Boolean);
|
|
10530
10737
|
for (const cfg of candidateConfigs) {
|
|
10531
|
-
if (
|
|
10738
|
+
if (existsSync23(cfg)) {
|
|
10532
10739
|
try {
|
|
10533
10740
|
await mcpManager.loadConfigFile(cfg);
|
|
10534
10741
|
mcpManager.registerToolsIntoRouter(tools4);
|