@pikaa-ai/pikaa 0.3.19 → 0.3.21
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 +457 -240
- 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/index.js
CHANGED
|
@@ -1347,12 +1347,15 @@ class DefaultModelClientSession {
|
|
|
1347
1347
|
};
|
|
1348
1348
|
return;
|
|
1349
1349
|
}
|
|
1350
|
-
const
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1350
|
+
const enablePromptCache = params.enablePromptCache ?? this.config.enablePromptCache ?? true;
|
|
1351
|
+
const systemMessage = {
|
|
1352
|
+
role: "system",
|
|
1353
|
+
content: params.systemPrompt
|
|
1354
|
+
};
|
|
1355
|
+
if (enablePromptCache) {
|
|
1356
|
+
systemMessage.cache_control = { type: "ephemeral" };
|
|
1357
|
+
}
|
|
1358
|
+
const messages = [systemMessage];
|
|
1356
1359
|
for (let i = 0;i < params.history.length; i++) {
|
|
1357
1360
|
const item = params.history[i];
|
|
1358
1361
|
if (item.type === "user_message") {
|
|
@@ -1436,14 +1439,27 @@ class DefaultModelClientSession {
|
|
|
1436
1439
|
temperature: params.temperature ?? 0.2
|
|
1437
1440
|
};
|
|
1438
1441
|
if (toolsPayload && toolsPayload.length > 0) {
|
|
1439
|
-
|
|
1442
|
+
if (enablePromptCache && toolsPayload.length > 0) {
|
|
1443
|
+
const clonedTools = [...toolsPayload];
|
|
1444
|
+
const lastIdx = clonedTools.length - 1;
|
|
1445
|
+
if (clonedTools[lastIdx] && typeof clonedTools[lastIdx] === "object") {
|
|
1446
|
+
clonedTools[lastIdx] = {
|
|
1447
|
+
...clonedTools[lastIdx],
|
|
1448
|
+
cache_control: { type: "ephemeral" }
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
body.tools = clonedTools;
|
|
1452
|
+
} else {
|
|
1453
|
+
body.tools = toolsPayload;
|
|
1454
|
+
}
|
|
1440
1455
|
body.tool_choice = "auto";
|
|
1441
1456
|
}
|
|
1442
1457
|
const maxRetries = params.maxRetries ?? this.config.maxRetries ?? 10;
|
|
1443
1458
|
let attempt = 0;
|
|
1444
1459
|
let response = null;
|
|
1445
1460
|
const headers = {
|
|
1446
|
-
"Content-Type": "application/json"
|
|
1461
|
+
"Content-Type": "application/json",
|
|
1462
|
+
"anthropic-beta": "prompt-caching-2024-07-25"
|
|
1447
1463
|
};
|
|
1448
1464
|
if (apiKey) {
|
|
1449
1465
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
@@ -1553,7 +1569,8 @@ class DefaultModelClientSession {
|
|
|
1553
1569
|
type: "done",
|
|
1554
1570
|
inputTokens: usageMetrics.inputTokens,
|
|
1555
1571
|
outputTokens: usageMetrics.outputTokens,
|
|
1556
|
-
totalTokens: usageMetrics.totalTokens
|
|
1572
|
+
totalTokens: usageMetrics.totalTokens,
|
|
1573
|
+
cachedTokens: usageMetrics.cachedTokens
|
|
1557
1574
|
};
|
|
1558
1575
|
return;
|
|
1559
1576
|
}
|
|
@@ -1564,10 +1581,12 @@ class DefaultModelClientSession {
|
|
|
1564
1581
|
continue;
|
|
1565
1582
|
}
|
|
1566
1583
|
if (parsed.usage) {
|
|
1584
|
+
const cached = parsed.usage.prompt_tokens_details?.cached_tokens ?? parsed.usage.cache_read_input_tokens ?? parsed.usage.cached_content_token_count ?? 0;
|
|
1567
1585
|
usageMetrics = {
|
|
1568
1586
|
inputTokens: parsed.usage.prompt_tokens,
|
|
1569
1587
|
outputTokens: parsed.usage.completion_tokens,
|
|
1570
|
-
totalTokens: parsed.usage.total_tokens
|
|
1588
|
+
totalTokens: parsed.usage.total_tokens,
|
|
1589
|
+
cachedTokens: cached > 0 ? cached : undefined
|
|
1571
1590
|
};
|
|
1572
1591
|
}
|
|
1573
1592
|
const choice = parsed.choices?.[0];
|
|
@@ -1772,24 +1791,30 @@ var applyPatchTool = {
|
|
|
1772
1791
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1773
1792
|
if (ctx.execPolicy) {
|
|
1774
1793
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
1775
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1776
|
-
return {
|
|
1777
|
-
output: "Error: Cannot mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
1778
|
-
isError: true
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
1794
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
1782
|
-
const approval = await ctx.requestApproval(`Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1795
|
+
const approval = await ctx.requestApproval(evalResult.reason || `Apply patch to: ${rawPath}`, `apply_patch ${rawPath}`);
|
|
1783
1796
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
1784
1797
|
if (!allowed) {
|
|
1785
|
-
return {
|
|
1798
|
+
return {
|
|
1799
|
+
output: `[Plan Mode Gate]: File modification declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
|
|
1800
|
+
isError: true
|
|
1801
|
+
};
|
|
1786
1802
|
}
|
|
1803
|
+
} else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
1804
|
+
return {
|
|
1805
|
+
output: `[Plan Mode Gate]: Cannot mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
|
|
1806
|
+
isError: true
|
|
1807
|
+
};
|
|
1787
1808
|
}
|
|
1788
1809
|
}
|
|
1789
1810
|
if (!existsSync3(filePath)) {
|
|
1790
1811
|
if (targetContent) {
|
|
1791
1812
|
return {
|
|
1792
|
-
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided
|
|
1813
|
+
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.
|
|
1814
|
+
[Systematic Error Recovery Checklist]:
|
|
1815
|
+
1. Root Cause: Trying to patch a non-existent file with targetContent.
|
|
1816
|
+
2. Fix: For creating new files, leave 'targetContent' empty and provide full contents in 'replacementContent'.
|
|
1817
|
+
3. Alternatively, check if the file path '${rawPath}' was mistyped.`,
|
|
1793
1818
|
isError: true
|
|
1794
1819
|
};
|
|
1795
1820
|
}
|
|
@@ -1799,7 +1824,7 @@ var applyPatchTool = {
|
|
|
1799
1824
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1800
1825
|
} catch (err) {
|
|
1801
1826
|
return {
|
|
1802
|
-
output: `Failed to create file: ${err instanceof Error ? err.message : String(err)}`,
|
|
1827
|
+
output: `Failed to create file '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
1803
1828
|
isError: true
|
|
1804
1829
|
};
|
|
1805
1830
|
}
|
|
@@ -1808,21 +1833,33 @@ var applyPatchTool = {
|
|
|
1808
1833
|
const originalFileContent = readFileSync2(filePath, "utf8");
|
|
1809
1834
|
if (!targetContent) {
|
|
1810
1835
|
return {
|
|
1811
|
-
output: `Error: File '${rawPath}' already exists
|
|
1836
|
+
output: `Error: File '${rawPath}' already exists, but targetContent was empty.
|
|
1837
|
+
[Systematic Error Recovery Checklist]:
|
|
1838
|
+
1. Root Cause: An existing file requires targetContent to specify which lines to replace.
|
|
1839
|
+
2. Fix: Call 'read_file' on '${rawPath}', extract the exact target lines, and provide them in 'targetContent'.
|
|
1840
|
+
3. To overwrite the whole file, use the 'write_file' tool instead.`,
|
|
1812
1841
|
isError: true
|
|
1813
1842
|
};
|
|
1814
1843
|
}
|
|
1815
1844
|
const firstIndex = originalFileContent.indexOf(targetContent);
|
|
1816
1845
|
if (firstIndex === -1) {
|
|
1817
1846
|
return {
|
|
1818
|
-
output: `Error: targetContent was not found in '${rawPath}'.
|
|
1847
|
+
output: `Error: targetContent was not found in '${rawPath}'.
|
|
1848
|
+
[Systematic Error Recovery Checklist]:
|
|
1849
|
+
1. Root Cause: The snippet in targetContent does not match the actual file content (differences in whitespace, indentation, line endings, or prior edits).
|
|
1850
|
+
2. Action: Call 'read_file' on '${rawPath}' to inspect current exact lines and indentation.
|
|
1851
|
+
3. Fix: Provide the exact matching lines (including leading spaces) or wider context, then retry 'apply_patch'.`,
|
|
1819
1852
|
isError: true
|
|
1820
1853
|
};
|
|
1821
1854
|
}
|
|
1822
1855
|
const secondIndex = originalFileContent.indexOf(targetContent, firstIndex + 1);
|
|
1823
1856
|
if (secondIndex !== -1) {
|
|
1824
1857
|
return {
|
|
1825
|
-
output: `Error: targetContent matched multiple locations in '${rawPath}'.
|
|
1858
|
+
output: `Error: targetContent matched multiple locations in '${rawPath}'.
|
|
1859
|
+
[Systematic Error Recovery Checklist]:
|
|
1860
|
+
1. Root Cause: targetContent is ambiguous and occurs multiple times in the file.
|
|
1861
|
+
2. Action: Include 2-3 additional surrounding lines (before or after the target block) to make the target snippet uniquely identifiable.
|
|
1862
|
+
3. Fix: Re-run 'apply_patch' with the extended unique block.`,
|
|
1826
1863
|
isError: true
|
|
1827
1864
|
};
|
|
1828
1865
|
}
|
|
@@ -1833,7 +1870,7 @@ var applyPatchTool = {
|
|
|
1833
1870
|
};
|
|
1834
1871
|
} catch (err) {
|
|
1835
1872
|
return {
|
|
1836
|
-
output: `Failed to apply patch: ${err instanceof Error ? err.message : String(err)}`,
|
|
1873
|
+
output: `Failed to apply patch to '${rawPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
1837
1874
|
isError: true
|
|
1838
1875
|
};
|
|
1839
1876
|
}
|
|
@@ -1867,9 +1904,9 @@ class ExecPolicy {
|
|
|
1867
1904
|
shouldPromptFileEdit(filePath) {
|
|
1868
1905
|
if (this.mode === "plan") {
|
|
1869
1906
|
return {
|
|
1870
|
-
prompt:
|
|
1907
|
+
prompt: true,
|
|
1871
1908
|
isPlanBlocked: true,
|
|
1872
|
-
reason:
|
|
1909
|
+
reason: `[Plan Mode Gate] Approval required to mutate '${filePath || "file"}' and proceed with implementation.`
|
|
1873
1910
|
};
|
|
1874
1911
|
}
|
|
1875
1912
|
if (this.mode === "manual") {
|
|
@@ -1883,11 +1920,14 @@ class ExecPolicy {
|
|
|
1883
1920
|
evaluate(command) {
|
|
1884
1921
|
const trimmed = command.trim();
|
|
1885
1922
|
if (this.mode === "plan") {
|
|
1886
|
-
const isReadOnly = /^(git\s+(status|log|diff|branch|show)|ls|dir|cat|type|grep|rg|find|pwd|which|where)\b/i.test(trimmed);
|
|
1923
|
+
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);
|
|
1887
1924
|
if (isReadOnly) {
|
|
1888
1925
|
return { decision: "allow", reason: "Read-only inspection allowed in Plan mode" };
|
|
1889
1926
|
}
|
|
1890
|
-
return {
|
|
1927
|
+
return {
|
|
1928
|
+
decision: "prompt",
|
|
1929
|
+
reason: `[Plan Mode Gate] Approval required to execute shell command '${trimmed}' in Plan Mode`
|
|
1930
|
+
};
|
|
1891
1931
|
}
|
|
1892
1932
|
if (this.mode === "manual") {
|
|
1893
1933
|
return {
|
|
@@ -2283,6 +2323,73 @@ class PrefixRulesStore {
|
|
|
2283
2323
|
}
|
|
2284
2324
|
var globalPrefixRulesStore = new PrefixRulesStore;
|
|
2285
2325
|
|
|
2326
|
+
// src/workspace/ephemeral.ts
|
|
2327
|
+
import { mkdirSync as mkdirSync5, rmSync, existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
2328
|
+
import { join as join2 } from "path";
|
|
2329
|
+
import { tmpdir } from "os";
|
|
2330
|
+
|
|
2331
|
+
class EphemeralWorkspaceManager {
|
|
2332
|
+
activeScratchpads = new Set;
|
|
2333
|
+
createScratchpad(turnId) {
|
|
2334
|
+
const uniqueId = `groupy_scratch_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2335
|
+
const baseDir = join2(tmpdir(), "groupy-ephemeral", turnId ? `turn_${turnId}` : "general");
|
|
2336
|
+
const scratchPath = join2(baseDir, uniqueId);
|
|
2337
|
+
mkdirSync5(scratchPath, { recursive: true });
|
|
2338
|
+
this.activeScratchpads.add(scratchPath);
|
|
2339
|
+
return scratchPath;
|
|
2340
|
+
}
|
|
2341
|
+
cleanup(scratchPath) {
|
|
2342
|
+
if (!scratchPath || !this.activeScratchpads.has(scratchPath))
|
|
2343
|
+
return;
|
|
2344
|
+
try {
|
|
2345
|
+
if (existsSync7(scratchPath)) {
|
|
2346
|
+
rmSync(scratchPath, { recursive: true, force: true });
|
|
2347
|
+
}
|
|
2348
|
+
} catch {} finally {
|
|
2349
|
+
this.activeScratchpads.delete(scratchPath);
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
cleanupTurn(turnId) {
|
|
2353
|
+
const turnDir = join2(tmpdir(), "groupy-ephemeral", `turn_${turnId}`);
|
|
2354
|
+
try {
|
|
2355
|
+
if (existsSync7(turnDir)) {
|
|
2356
|
+
rmSync(turnDir, { recursive: true, force: true });
|
|
2357
|
+
}
|
|
2358
|
+
} catch {}
|
|
2359
|
+
}
|
|
2360
|
+
cleanRootResidue(cwd) {
|
|
2361
|
+
const cleaned = [];
|
|
2362
|
+
try {
|
|
2363
|
+
const files = readdirSync2(cwd);
|
|
2364
|
+
const tempPatterns = [
|
|
2365
|
+
/^tmp_/i,
|
|
2366
|
+
/^draft_/i,
|
|
2367
|
+
/^scratch_/i,
|
|
2368
|
+
/^temp_/i,
|
|
2369
|
+
/^preview_.*\.html$/i,
|
|
2370
|
+
/\.tmp$/i,
|
|
2371
|
+
/\.bak$/i,
|
|
2372
|
+
/~$/i
|
|
2373
|
+
];
|
|
2374
|
+
for (const file of files) {
|
|
2375
|
+
if (tempPatterns.some((p) => p.test(file))) {
|
|
2376
|
+
const fullPath = join2(cwd, file);
|
|
2377
|
+
const stat = statSync2(fullPath);
|
|
2378
|
+
if (stat.isFile()) {
|
|
2379
|
+
rmSync(fullPath, { force: true });
|
|
2380
|
+
cleaned.push(file);
|
|
2381
|
+
} else if (stat.isDirectory() && (file.startsWith("tmp_") || file.startsWith("scratch_") || file.startsWith("temp_"))) {
|
|
2382
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
2383
|
+
cleaned.push(file);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
} catch {}
|
|
2388
|
+
return cleaned;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
var globalEphemeralWorkspace = new EphemeralWorkspaceManager;
|
|
2392
|
+
|
|
2286
2393
|
// src/tools/handlers/shell.ts
|
|
2287
2394
|
function createShellTool(policy = new ExecPolicy) {
|
|
2288
2395
|
return {
|
|
@@ -2368,6 +2475,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2368
2475
|
const timeoutMs = typeof args.timeoutMs === "number" ? args.timeoutMs : 30000;
|
|
2369
2476
|
const isWindows = process.platform === "win32";
|
|
2370
2477
|
const baseCmd = isWindows ? ["cmd.exe", "/d", "/s", "/c", command] : ["/bin/sh", "-c", command];
|
|
2478
|
+
const ephemeralScratchpad = globalEphemeralWorkspace.createScratchpad(ctx.turnId);
|
|
2371
2479
|
const sandboxProfile = globalKernelSandbox.buildDefaultProfile(ctx.cwd);
|
|
2372
2480
|
if (isEscalated) {
|
|
2373
2481
|
sandboxProfile.allowNetwork = true;
|
|
@@ -2378,6 +2486,10 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2378
2486
|
cwd: ctx.cwd,
|
|
2379
2487
|
env: {
|
|
2380
2488
|
...process.env,
|
|
2489
|
+
TMPDIR: ephemeralScratchpad,
|
|
2490
|
+
TEMP: ephemeralScratchpad,
|
|
2491
|
+
TMP: ephemeralScratchpad,
|
|
2492
|
+
GROUPY_SCRATCH_DIR: ephemeralScratchpad,
|
|
2381
2493
|
...ctx.proxyEnv
|
|
2382
2494
|
},
|
|
2383
2495
|
stdout: "pipe",
|
|
@@ -2414,9 +2526,14 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2414
2526
|
if (result.stderr)
|
|
2415
2527
|
outputParts.push(`STDERR:
|
|
2416
2528
|
${result.stderr.trim()}`);
|
|
2417
|
-
if (result.code !== 0)
|
|
2529
|
+
if (result.code !== 0) {
|
|
2418
2530
|
outputParts.push(`
|
|
2419
|
-
[Process exited with code ${result.code}]`);
|
|
2531
|
+
[Process exited with non-zero code ${result.code}]`);
|
|
2532
|
+
outputParts.push(`[Systematic Error Recovery Checklist]:
|
|
2533
|
+
1. Inspect STDERR above to pinpoint syntax errors, failed test assertions, or missing dependencies.
|
|
2534
|
+
2. If this is a test failure, trace the failure in source code and fix the root cause before re-running.
|
|
2535
|
+
3. If this is a missing command/module, install or configure the prerequisite.`);
|
|
2536
|
+
}
|
|
2420
2537
|
const output = outputParts.join(`
|
|
2421
2538
|
`) || "[Command completed with no output]";
|
|
2422
2539
|
return {
|
|
@@ -2425,16 +2542,21 @@ ${result.stderr.trim()}`);
|
|
|
2425
2542
|
};
|
|
2426
2543
|
} catch (err) {
|
|
2427
2544
|
return {
|
|
2428
|
-
output: `Execution error: ${err instanceof Error ? err.message : String(err)}
|
|
2545
|
+
output: `Execution error: ${err instanceof Error ? err.message : String(err)}
|
|
2546
|
+
[Systematic Error Recovery Checklist]:
|
|
2547
|
+
1. Verify command syntax, arguments, and executable availability in PATH.
|
|
2548
|
+
2. Check if the current working directory ('${ctx.cwd}') is valid.`,
|
|
2429
2549
|
isError: true
|
|
2430
2550
|
};
|
|
2551
|
+
} finally {
|
|
2552
|
+
globalEphemeralWorkspace.cleanup(ephemeralScratchpad);
|
|
2431
2553
|
}
|
|
2432
2554
|
}
|
|
2433
2555
|
};
|
|
2434
2556
|
}
|
|
2435
2557
|
var shellTool = createShellTool();
|
|
2436
2558
|
// src/tools/handlers/file-ops.ts
|
|
2437
|
-
import { readdirSync as
|
|
2559
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync8, statSync as statSync3, mkdirSync as mkdirSync6 } from "fs";
|
|
2438
2560
|
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
2439
2561
|
var readFileTool = {
|
|
2440
2562
|
name: "read_file",
|
|
@@ -2448,7 +2570,7 @@ var readFileTool = {
|
|
|
2448
2570
|
},
|
|
2449
2571
|
async execute(args, ctx) {
|
|
2450
2572
|
const filePath = resolve6(ctx.cwd, String(args.path || ""));
|
|
2451
|
-
if (!
|
|
2573
|
+
if (!existsSync8(filePath)) {
|
|
2452
2574
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2453
2575
|
}
|
|
2454
2576
|
try {
|
|
@@ -2470,14 +2592,14 @@ var listDirTool = {
|
|
|
2470
2592
|
},
|
|
2471
2593
|
async execute(args, ctx) {
|
|
2472
2594
|
const dirPath = resolve6(ctx.cwd, String(args.path || "."));
|
|
2473
|
-
if (!
|
|
2595
|
+
if (!existsSync8(dirPath)) {
|
|
2474
2596
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2475
2597
|
}
|
|
2476
2598
|
try {
|
|
2477
|
-
const entries =
|
|
2599
|
+
const entries = readdirSync3(dirPath);
|
|
2478
2600
|
const formatted = entries.map((entry) => {
|
|
2479
2601
|
const full = resolve6(dirPath, entry);
|
|
2480
|
-
const isDir =
|
|
2602
|
+
const isDir = statSync3(full).isDirectory();
|
|
2481
2603
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2482
2604
|
});
|
|
2483
2605
|
return { output: formatted.join(`
|
|
@@ -2503,26 +2625,35 @@ var writeFileTool = {
|
|
|
2503
2625
|
const filePath = resolve6(ctx.cwd, rawPath);
|
|
2504
2626
|
if (ctx.execPolicy) {
|
|
2505
2627
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2506
|
-
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2507
|
-
return {
|
|
2508
|
-
output: "Error: Cannot write or mutate files while in Plan Mode. Please present the implementation plan first.",
|
|
2509
|
-
isError: true
|
|
2510
|
-
};
|
|
2511
|
-
}
|
|
2512
2628
|
if (evalResult.prompt && ctx.requestApproval) {
|
|
2513
|
-
const approval = await ctx.requestApproval(`Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2629
|
+
const approval = await ctx.requestApproval(evalResult.reason || `Write file: ${rawPath}`, `write_file ${rawPath}`);
|
|
2514
2630
|
const allowed = typeof approval === "object" ? approval.allowed : Boolean(approval);
|
|
2515
2631
|
if (!allowed) {
|
|
2516
|
-
return {
|
|
2632
|
+
return {
|
|
2633
|
+
output: `[Plan Mode Gate]: File creation declined by user for '${rawPath}'. Please refine your implementation plan or ask the user for guidance.`,
|
|
2634
|
+
isError: true
|
|
2635
|
+
};
|
|
2517
2636
|
}
|
|
2637
|
+
} else if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
2638
|
+
return {
|
|
2639
|
+
output: `[Plan Mode Gate]: Cannot write or mutate '${rawPath}' while in Plan Mode without user approval. Please present your implementation plan first.`,
|
|
2640
|
+
isError: true
|
|
2641
|
+
};
|
|
2518
2642
|
}
|
|
2519
2643
|
}
|
|
2520
2644
|
try {
|
|
2521
|
-
|
|
2645
|
+
mkdirSync6(dirname4(filePath), { recursive: true });
|
|
2522
2646
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2523
2647
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2524
2648
|
} catch (err) {
|
|
2525
|
-
return {
|
|
2649
|
+
return {
|
|
2650
|
+
output: `Failed to write file '${rawPath}': ${err instanceof Error ? err.message : String(err)}
|
|
2651
|
+
[Systematic Error Recovery Checklist]:
|
|
2652
|
+
1. Check directory permissions and ensure the path is valid within the workspace.
|
|
2653
|
+
2. If the path contains non-existent nested folders, they should be auto-created.
|
|
2654
|
+
3. Verify that the file is not locked by another active process.`,
|
|
2655
|
+
isError: true
|
|
2656
|
+
};
|
|
2526
2657
|
}
|
|
2527
2658
|
}
|
|
2528
2659
|
};
|
|
@@ -2641,8 +2772,8 @@ var updatePlanTool = {
|
|
|
2641
2772
|
}
|
|
2642
2773
|
};
|
|
2643
2774
|
// src/search/engine.ts
|
|
2644
|
-
import { readdirSync as
|
|
2645
|
-
import { resolve as resolve7, relative, join as
|
|
2775
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync4, existsSync as existsSync9 } from "fs";
|
|
2776
|
+
import { resolve as resolve7, relative, join as join3, extname } from "path";
|
|
2646
2777
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2647
2778
|
".git",
|
|
2648
2779
|
"node_modules",
|
|
@@ -2688,7 +2819,7 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2688
2819
|
class FileSearchEngine {
|
|
2689
2820
|
grep(cwd, options) {
|
|
2690
2821
|
const searchRoot = resolve7(cwd, options.path || ".");
|
|
2691
|
-
if (!
|
|
2822
|
+
if (!existsSync9(searchRoot)) {
|
|
2692
2823
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2693
2824
|
}
|
|
2694
2825
|
const maxResults = options.maxResults || 50;
|
|
@@ -2738,7 +2869,7 @@ class FileSearchEngine {
|
|
|
2738
2869
|
}
|
|
2739
2870
|
findFiles(cwd, options) {
|
|
2740
2871
|
const searchRoot = resolve7(cwd, options.path || ".");
|
|
2741
|
-
if (!
|
|
2872
|
+
if (!existsSync9(searchRoot))
|
|
2742
2873
|
return [];
|
|
2743
2874
|
const maxResults = options.maxResults || 100;
|
|
2744
2875
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2777,8 +2908,8 @@ class FileSearchEngine {
|
|
|
2777
2908
|
}
|
|
2778
2909
|
loadGitignoreRules(root) {
|
|
2779
2910
|
const rules = new Set;
|
|
2780
|
-
const gitignorePath =
|
|
2781
|
-
if (
|
|
2911
|
+
const gitignorePath = join3(root, ".gitignore");
|
|
2912
|
+
if (existsSync9(gitignorePath)) {
|
|
2782
2913
|
try {
|
|
2783
2914
|
const lines = readFileSync4(gitignorePath, "utf8").split(`
|
|
2784
2915
|
`);
|
|
@@ -2795,7 +2926,7 @@ class FileSearchEngine {
|
|
|
2795
2926
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2796
2927
|
const results = [];
|
|
2797
2928
|
try {
|
|
2798
|
-
const stat =
|
|
2929
|
+
const stat = statSync4(dir);
|
|
2799
2930
|
if (!stat.isDirectory()) {
|
|
2800
2931
|
if (!this.isBinary(dir)) {
|
|
2801
2932
|
results.push(dir);
|
|
@@ -2809,9 +2940,9 @@ class FileSearchEngine {
|
|
|
2809
2940
|
while (queue.length > 0) {
|
|
2810
2941
|
const currentDir = queue.shift();
|
|
2811
2942
|
try {
|
|
2812
|
-
const entries =
|
|
2943
|
+
const entries = readdirSync4(currentDir, { withFileTypes: true });
|
|
2813
2944
|
for (const entry of entries) {
|
|
2814
|
-
const fullPath =
|
|
2945
|
+
const fullPath = join3(currentDir, entry.name);
|
|
2815
2946
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2816
2947
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2817
2948
|
continue;
|
|
@@ -3531,8 +3662,8 @@ class PathSandbox {
|
|
|
3531
3662
|
}
|
|
3532
3663
|
}
|
|
3533
3664
|
// src/security/scanner.ts
|
|
3534
|
-
import { existsSync as
|
|
3535
|
-
import { join as
|
|
3665
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync5 } from "fs";
|
|
3666
|
+
import { join as join4, relative as relative3, resolve as resolve9 } from "path";
|
|
3536
3667
|
var SECURITY_RULES = [
|
|
3537
3668
|
{
|
|
3538
3669
|
id: "SEC-001",
|
|
@@ -3670,21 +3801,21 @@ async function runSecurityScan(targetDir, options = {}) {
|
|
|
3670
3801
|
const findings = [];
|
|
3671
3802
|
let scannedCount = 0;
|
|
3672
3803
|
function walk(current) {
|
|
3673
|
-
if (scannedCount >= maxFiles || !
|
|
3804
|
+
if (scannedCount >= maxFiles || !existsSync10(current))
|
|
3674
3805
|
return;
|
|
3675
3806
|
let entries;
|
|
3676
3807
|
try {
|
|
3677
|
-
entries =
|
|
3808
|
+
entries = readdirSync5(current);
|
|
3678
3809
|
} catch {
|
|
3679
3810
|
return;
|
|
3680
3811
|
}
|
|
3681
3812
|
for (const entry of entries) {
|
|
3682
3813
|
if (scannedCount >= maxFiles)
|
|
3683
3814
|
break;
|
|
3684
|
-
const fullPath =
|
|
3815
|
+
const fullPath = join4(current, entry);
|
|
3685
3816
|
let stat;
|
|
3686
3817
|
try {
|
|
3687
|
-
stat =
|
|
3818
|
+
stat = statSync5(fullPath);
|
|
3688
3819
|
} catch {
|
|
3689
3820
|
continue;
|
|
3690
3821
|
}
|
|
@@ -3807,13 +3938,13 @@ function formatWorldStatePrompt(state) {
|
|
|
3807
3938
|
`);
|
|
3808
3939
|
}
|
|
3809
3940
|
// src/prompts/loader.ts
|
|
3810
|
-
import { existsSync as
|
|
3811
|
-
import { resolve as resolve10, join as
|
|
3941
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6 } from "fs";
|
|
3942
|
+
import { resolve as resolve10, join as join5 } from "path";
|
|
3812
3943
|
import { homedir as homedir2 } from "os";
|
|
3813
3944
|
class PromptTemplateLoader {
|
|
3814
3945
|
builtInTemplatesDir;
|
|
3815
3946
|
constructor(builtInDir) {
|
|
3816
|
-
this.builtInTemplatesDir = builtInDir || resolve10(
|
|
3947
|
+
this.builtInTemplatesDir = builtInDir || resolve10(join5(import.meta.dir, "..", "..", "templates"));
|
|
3817
3948
|
}
|
|
3818
3949
|
loadTemplate(relativePath, variables = {}, cwd) {
|
|
3819
3950
|
const rawContent = this.resolveTemplateContent(relativePath, cwd);
|
|
@@ -3822,27 +3953,27 @@ class PromptTemplateLoader {
|
|
|
3822
3953
|
resolveTemplateContent(relativePath, cwd) {
|
|
3823
3954
|
const normalizedRel = relativePath.replace(/^\/+/, "");
|
|
3824
3955
|
if (cwd) {
|
|
3825
|
-
const workspacePath =
|
|
3826
|
-
if (
|
|
3956
|
+
const workspacePath = join5(cwd, ".agents", "templates", normalizedRel);
|
|
3957
|
+
if (existsSync11(workspacePath)) {
|
|
3827
3958
|
try {
|
|
3828
3959
|
return readFileSync6(workspacePath, "utf-8");
|
|
3829
3960
|
} catch {}
|
|
3830
3961
|
}
|
|
3831
3962
|
}
|
|
3832
|
-
const globalPath =
|
|
3833
|
-
if (
|
|
3963
|
+
const globalPath = join5(getGlobalTemplatesDir(), normalizedRel);
|
|
3964
|
+
if (existsSync11(globalPath)) {
|
|
3834
3965
|
try {
|
|
3835
3966
|
return readFileSync6(globalPath, "utf-8");
|
|
3836
3967
|
} catch {}
|
|
3837
3968
|
}
|
|
3838
|
-
const legacyGlobalPath =
|
|
3839
|
-
if (
|
|
3969
|
+
const legacyGlobalPath = join5(homedir2(), ".groupy", "templates", normalizedRel);
|
|
3970
|
+
if (existsSync11(legacyGlobalPath)) {
|
|
3840
3971
|
try {
|
|
3841
3972
|
return readFileSync6(legacyGlobalPath, "utf-8");
|
|
3842
3973
|
} catch {}
|
|
3843
3974
|
}
|
|
3844
|
-
const builtInPath =
|
|
3845
|
-
if (
|
|
3975
|
+
const builtInPath = join5(this.builtInTemplatesDir, normalizedRel);
|
|
3976
|
+
if (existsSync11(builtInPath)) {
|
|
3846
3977
|
try {
|
|
3847
3978
|
return readFileSync6(builtInPath, "utf-8");
|
|
3848
3979
|
} catch {}
|
|
@@ -3859,8 +3990,8 @@ class PromptTemplateLoader {
|
|
|
3859
3990
|
var globalPromptLoader = new PromptTemplateLoader;
|
|
3860
3991
|
|
|
3861
3992
|
// src/prompts/agents-md.ts
|
|
3862
|
-
import { existsSync as
|
|
3863
|
-
import { resolve as resolve11, join as
|
|
3993
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
3994
|
+
import { resolve as resolve11, join as join6, dirname as dirname5 } from "path";
|
|
3864
3995
|
var DEFAULT_AGENTS_MD_FILENAMES = [
|
|
3865
3996
|
"AGENTS.override.md",
|
|
3866
3997
|
"AGENTS.md",
|
|
@@ -3877,7 +4008,7 @@ class AgentsMdLoader {
|
|
|
3877
4008
|
findProjectRoot(startDir) {
|
|
3878
4009
|
let current = resolve11(startDir);
|
|
3879
4010
|
while (true) {
|
|
3880
|
-
if (
|
|
4011
|
+
if (existsSync12(join6(current, ".git"))) {
|
|
3881
4012
|
return current;
|
|
3882
4013
|
}
|
|
3883
4014
|
const parent = dirname5(current);
|
|
@@ -3911,8 +4042,8 @@ class AgentsMdLoader {
|
|
|
3911
4042
|
const sourcePaths = [];
|
|
3912
4043
|
for (const dir of dirHierarchy) {
|
|
3913
4044
|
for (const filename of fallbackFilenames) {
|
|
3914
|
-
const filePath =
|
|
3915
|
-
if (
|
|
4045
|
+
const filePath = join6(dir, filename);
|
|
4046
|
+
if (existsSync12(filePath)) {
|
|
3916
4047
|
try {
|
|
3917
4048
|
const content = readFileSync7(filePath, "utf-8").trim();
|
|
3918
4049
|
if (content) {
|
|
@@ -3936,81 +4067,141 @@ class AgentsMdLoader {
|
|
|
3936
4067
|
var globalAgentsMdLoader = new AgentsMdLoader;
|
|
3937
4068
|
|
|
3938
4069
|
// src/context/instructions.ts
|
|
3939
|
-
function
|
|
3940
|
-
const
|
|
4070
|
+
function wrapXmlTag(tag, content, attrs = {}) {
|
|
4071
|
+
const attrStr = Object.entries(attrs).filter(([_, v]) => Boolean(v)).map(([k, v]) => ` ${k}="${v}"`).join("");
|
|
4072
|
+
return `<${tag}${attrStr}>
|
|
4073
|
+
${content.trim()}
|
|
4074
|
+
</${tag}>`;
|
|
4075
|
+
}
|
|
4076
|
+
function buildStructuredSystemPrompt(params) {
|
|
3941
4077
|
const cwd = params.cwd || process.cwd();
|
|
3942
4078
|
const mode = params.collaborationMode || "default";
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
4079
|
+
const blocks = [];
|
|
4080
|
+
let baseContent = params.basePrompt;
|
|
4081
|
+
if (!baseContent) {
|
|
3946
4082
|
const templateName = params.basePromptTemplate || "base/groupy_prompt.md";
|
|
3947
|
-
|
|
3948
|
-
if (baseContent) {
|
|
3949
|
-
sections.push(baseContent.trim());
|
|
3950
|
-
} else {
|
|
3951
|
-
sections.push("You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
3952
|
-
}
|
|
4083
|
+
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.";
|
|
3953
4084
|
}
|
|
4085
|
+
blocks.push({
|
|
4086
|
+
tag: "system_identity",
|
|
4087
|
+
content: wrapXmlTag("system_identity", baseContent),
|
|
4088
|
+
cacheable: true
|
|
4089
|
+
});
|
|
3954
4090
|
if (params.personality) {
|
|
3955
4091
|
const personalityContent = globalPromptLoader.loadTemplate(`personalities/${params.personality}.md`, {}, cwd);
|
|
3956
4092
|
if (personalityContent) {
|
|
3957
|
-
|
|
4093
|
+
blocks.push({
|
|
4094
|
+
tag: "personality",
|
|
4095
|
+
content: wrapXmlTag("personality", personalityContent, { kind: params.personality }),
|
|
4096
|
+
cacheable: true
|
|
4097
|
+
});
|
|
3958
4098
|
}
|
|
3959
4099
|
}
|
|
3960
4100
|
if (params.isOrchestrator) {
|
|
3961
4101
|
const orchestratorContent = globalPromptLoader.loadTemplate("agents/orchestrator.md", {}, cwd);
|
|
3962
4102
|
if (orchestratorContent) {
|
|
3963
|
-
|
|
4103
|
+
blocks.push({
|
|
4104
|
+
tag: "orchestrator_guidelines",
|
|
4105
|
+
content: wrapXmlTag("orchestrator_guidelines", orchestratorContent),
|
|
4106
|
+
cacheable: true
|
|
4107
|
+
});
|
|
3964
4108
|
}
|
|
3965
4109
|
}
|
|
3966
|
-
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, {
|
|
3967
|
-
KNOWN_MODE_NAMES: "default, plan, review"
|
|
3968
|
-
}, cwd);
|
|
4110
|
+
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, { KNOWN_MODE_NAMES: "default, plan, review" }, cwd);
|
|
3969
4111
|
if (modeTemplate) {
|
|
3970
|
-
|
|
4112
|
+
blocks.push({
|
|
4113
|
+
tag: "collaboration_mode",
|
|
4114
|
+
content: wrapXmlTag("collaboration_mode", modeTemplate, { name: mode }),
|
|
4115
|
+
cacheable: true
|
|
4116
|
+
});
|
|
3971
4117
|
}
|
|
3972
4118
|
if (params.sandboxMode) {
|
|
3973
|
-
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, {
|
|
3974
|
-
network_access: params.networkAccess ? "enabled" : "disabled"
|
|
3975
|
-
}, cwd);
|
|
4119
|
+
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, { network_access: params.networkAccess ? "enabled" : "disabled" }, cwd);
|
|
3976
4120
|
if (sandboxTemplate) {
|
|
3977
|
-
|
|
4121
|
+
blocks.push({
|
|
4122
|
+
tag: "sandbox_policy",
|
|
4123
|
+
content: wrapXmlTag("sandbox_policy", sandboxTemplate, { mode: params.sandboxMode }),
|
|
4124
|
+
cacheable: true
|
|
4125
|
+
});
|
|
3978
4126
|
}
|
|
3979
4127
|
}
|
|
3980
4128
|
if (params.approvalPolicy) {
|
|
3981
4129
|
const approvalTemplate = globalPromptLoader.loadTemplate(`permissions/approval_policy/${params.approvalPolicy}.md`, {}, cwd);
|
|
3982
4130
|
if (approvalTemplate) {
|
|
3983
|
-
|
|
4131
|
+
blocks.push({
|
|
4132
|
+
tag: "approval_policy",
|
|
4133
|
+
content: wrapXmlTag("approval_policy", approvalTemplate, { policy: params.approvalPolicy }),
|
|
4134
|
+
cacheable: true
|
|
4135
|
+
});
|
|
3984
4136
|
}
|
|
3985
4137
|
}
|
|
3986
4138
|
const projectInstructions = globalAgentsMdLoader.loadProjectInstructions(cwd);
|
|
3987
4139
|
if (projectInstructions) {
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
4140
|
+
blocks.push({
|
|
4141
|
+
tag: "project_instructions",
|
|
4142
|
+
content: wrapXmlTag("project_instructions", projectInstructions.content, { source: "AGENTS.md" }),
|
|
4143
|
+
cacheable: true
|
|
4144
|
+
});
|
|
3991
4145
|
}
|
|
3992
4146
|
if (params.memoriesPrompt) {
|
|
3993
|
-
|
|
4147
|
+
blocks.push({
|
|
4148
|
+
tag: "persistent_memories",
|
|
4149
|
+
content: wrapXmlTag("persistent_memories", params.memoriesPrompt),
|
|
4150
|
+
cacheable: true
|
|
4151
|
+
});
|
|
3994
4152
|
}
|
|
3995
4153
|
if (params.skillsPrompt) {
|
|
3996
|
-
|
|
4154
|
+
blocks.push({
|
|
4155
|
+
tag: "domain_skills",
|
|
4156
|
+
content: wrapXmlTag("domain_skills", params.skillsPrompt),
|
|
4157
|
+
cacheable: true
|
|
4158
|
+
});
|
|
3997
4159
|
}
|
|
3998
4160
|
if (params.mcpPrompt) {
|
|
3999
|
-
|
|
4161
|
+
blocks.push({
|
|
4162
|
+
tag: "mcp_servers",
|
|
4163
|
+
content: wrapXmlTag("mcp_servers", params.mcpPrompt),
|
|
4164
|
+
cacheable: true
|
|
4165
|
+
});
|
|
4000
4166
|
}
|
|
4001
4167
|
if (params.developerInstructions) {
|
|
4002
|
-
|
|
4003
|
-
|
|
4168
|
+
blocks.push({
|
|
4169
|
+
tag: "developer_instructions",
|
|
4170
|
+
content: wrapXmlTag("developer_instructions", params.developerInstructions),
|
|
4171
|
+
cacheable: true
|
|
4172
|
+
});
|
|
4004
4173
|
}
|
|
4174
|
+
const dynamicBlocks = [];
|
|
4005
4175
|
if (params.worldStatePrompt) {
|
|
4006
|
-
|
|
4007
|
-
|
|
4176
|
+
dynamicBlocks.push({
|
|
4177
|
+
tag: "runtime_environment",
|
|
4178
|
+
content: wrapXmlTag("runtime_environment", params.worldStatePrompt),
|
|
4179
|
+
cacheable: false
|
|
4180
|
+
});
|
|
4008
4181
|
}
|
|
4009
|
-
|
|
4182
|
+
const staticPrefix = blocks.map((b) => b.content).join(`
|
|
4183
|
+
|
|
4184
|
+
`);
|
|
4185
|
+
const dynamicSuffix = dynamicBlocks.map((b) => b.content).join(`
|
|
4010
4186
|
|
|
4011
4187
|
`);
|
|
4188
|
+
const allBlocks = [...blocks, ...dynamicBlocks];
|
|
4189
|
+
const text = allBlocks.map((b) => b.content).join(`
|
|
4190
|
+
|
|
4191
|
+
`);
|
|
4192
|
+
return {
|
|
4193
|
+
text,
|
|
4194
|
+
staticPrefix,
|
|
4195
|
+
dynamicSuffix,
|
|
4196
|
+
blocks: allBlocks
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
function buildSystemPrompt(params) {
|
|
4200
|
+
return buildStructuredSystemPrompt(params).text;
|
|
4012
4201
|
}
|
|
4013
4202
|
// src/context/compactor.ts
|
|
4203
|
+
var DEFAULT_MAX_CONTEXT_TOKENS = 256000;
|
|
4204
|
+
var DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS = 180000;
|
|
4014
4205
|
function estimateItemTokens(item) {
|
|
4015
4206
|
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 : "";
|
|
4016
4207
|
return Math.ceil(text.length / 4) + 4;
|
|
@@ -4078,7 +4269,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
4078
4269
|
});
|
|
4079
4270
|
const currentHistory = session.getHistory();
|
|
4080
4271
|
const estimatedTokens = estimateTotalTokens(currentHistory);
|
|
4081
|
-
const maxTokenLimit =
|
|
4272
|
+
const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
|
|
4082
4273
|
if (estimatedTokens > maxTokenLimit) {
|
|
4083
4274
|
const compacted = compactHistory(currentHistory);
|
|
4084
4275
|
session.setHistory(compacted);
|
|
@@ -4117,6 +4308,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
4117
4308
|
let iteration = 0;
|
|
4118
4309
|
let accumulatedInputTokens = 0;
|
|
4119
4310
|
let accumulatedOutputTokens = 0;
|
|
4311
|
+
let accumulatedCachedTokens = 0;
|
|
4120
4312
|
const clientSession = session.modelClient.newSession();
|
|
4121
4313
|
try {
|
|
4122
4314
|
while (iteration < turnContext.maxIterations) {
|
|
@@ -4133,7 +4325,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4133
4325
|
systemPrompt: effectiveSystemPrompt,
|
|
4134
4326
|
history: session.getHistory(),
|
|
4135
4327
|
tools: turnContext.tools,
|
|
4136
|
-
signal
|
|
4328
|
+
signal,
|
|
4329
|
+
enablePromptCache: true
|
|
4137
4330
|
});
|
|
4138
4331
|
for await (const chunk of stream) {
|
|
4139
4332
|
if (signal.aborted) {
|
|
@@ -4159,6 +4352,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4159
4352
|
iterInputTokens = chunk.inputTokens;
|
|
4160
4353
|
if (chunk.outputTokens !== undefined)
|
|
4161
4354
|
iterOutputTokens = chunk.outputTokens;
|
|
4355
|
+
if (chunk.cachedTokens !== undefined)
|
|
4356
|
+
accumulatedCachedTokens += chunk.cachedTokens;
|
|
4162
4357
|
} else if (chunk.type === "error") {
|
|
4163
4358
|
throw chunk.error;
|
|
4164
4359
|
}
|
|
@@ -4270,7 +4465,10 @@ async function runTurn(session, turnContext, input) {
|
|
|
4270
4465
|
session.addHistoryItem({
|
|
4271
4466
|
id: `msg_nudge_${Date.now()}`,
|
|
4272
4467
|
type: "user_message",
|
|
4273
|
-
content:
|
|
4468
|
+
content: `[Systematic ReAct Nudge]: No tool actions or answers were produced in this iteration.
|
|
4469
|
+
1. Review the user's objective and determine the immediate next action.
|
|
4470
|
+
2. If more context is required, invoke an exploration tool ('read_file', 'list_dir', 'grep_search', 'find_files').
|
|
4471
|
+
3. If ready to answer or implement, call the required mutating tool or deliver your full, concrete response now.`,
|
|
4274
4472
|
createdAt: Date.now()
|
|
4275
4473
|
});
|
|
4276
4474
|
continue;
|
|
@@ -4279,13 +4477,14 @@ async function runTurn(session, turnContext, input) {
|
|
|
4279
4477
|
break;
|
|
4280
4478
|
}
|
|
4281
4479
|
const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
|
|
4282
|
-
const maxContextTokens =
|
|
4480
|
+
const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
4283
4481
|
session.emitEvent({
|
|
4284
4482
|
type: "TurnCompleted",
|
|
4285
4483
|
turnId,
|
|
4286
4484
|
inputTokens: accumulatedInputTokens,
|
|
4287
4485
|
outputTokens: accumulatedOutputTokens,
|
|
4288
4486
|
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
4487
|
+
cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
|
|
4289
4488
|
contextTokens: totalContextTokens,
|
|
4290
4489
|
maxContextTokens
|
|
4291
4490
|
});
|
|
@@ -4300,6 +4499,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4300
4499
|
});
|
|
4301
4500
|
} finally {
|
|
4302
4501
|
session.clearActiveTurn(turnId);
|
|
4502
|
+
globalEphemeralWorkspace.cleanupTurn(turnId);
|
|
4503
|
+
globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
|
|
4303
4504
|
}
|
|
4304
4505
|
}
|
|
4305
4506
|
|
|
@@ -5240,8 +5441,8 @@ class McpClient {
|
|
|
5240
5441
|
}
|
|
5241
5442
|
}
|
|
5242
5443
|
// src/mcp/manager.ts
|
|
5243
|
-
import { existsSync as
|
|
5244
|
-
import { resolve as resolve12, dirname as dirname6, join as
|
|
5444
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
5445
|
+
import { resolve as resolve12, dirname as dirname6, join as join7 } from "path";
|
|
5245
5446
|
class McpManager {
|
|
5246
5447
|
clients = new Map;
|
|
5247
5448
|
serverConfigs = new Map;
|
|
@@ -5276,7 +5477,7 @@ class McpManager {
|
|
|
5276
5477
|
}
|
|
5277
5478
|
async loadConfigFile(filePath) {
|
|
5278
5479
|
const fullPath = resolve12(filePath);
|
|
5279
|
-
if (!
|
|
5480
|
+
if (!existsSync13(fullPath))
|
|
5280
5481
|
return;
|
|
5281
5482
|
this.loadedConfigFiles.add(fullPath);
|
|
5282
5483
|
try {
|
|
@@ -5531,11 +5732,11 @@ class McpManager {
|
|
|
5531
5732
|
saveServerToConfigFile(filePath, name, config) {
|
|
5532
5733
|
const fullPath = resolve12(filePath);
|
|
5533
5734
|
const dir = dirname6(fullPath);
|
|
5534
|
-
if (!
|
|
5535
|
-
|
|
5735
|
+
if (!existsSync13(dir)) {
|
|
5736
|
+
mkdirSync7(dir, { recursive: true });
|
|
5536
5737
|
}
|
|
5537
5738
|
let existing = { mcpServers: {} };
|
|
5538
|
-
if (
|
|
5739
|
+
if (existsSync13(fullPath)) {
|
|
5539
5740
|
try {
|
|
5540
5741
|
const content = readFileSync8(fullPath, "utf8");
|
|
5541
5742
|
existing = JSON.parse(content);
|
|
@@ -5550,7 +5751,7 @@ class McpManager {
|
|
|
5550
5751
|
}
|
|
5551
5752
|
removeServerFromConfigFile(filePath, name) {
|
|
5552
5753
|
const fullPath = resolve12(filePath);
|
|
5553
|
-
if (!
|
|
5754
|
+
if (!existsSync13(fullPath))
|
|
5554
5755
|
return false;
|
|
5555
5756
|
try {
|
|
5556
5757
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -5580,11 +5781,11 @@ class McpManager {
|
|
|
5580
5781
|
}
|
|
5581
5782
|
}
|
|
5582
5783
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
5583
|
-
const workspaceConfig =
|
|
5584
|
-
if (
|
|
5784
|
+
const workspaceConfig = join7(cwd, ".mcp.json");
|
|
5785
|
+
if (existsSync13(workspaceConfig))
|
|
5585
5786
|
return workspaceConfig;
|
|
5586
|
-
const altConfig =
|
|
5587
|
-
if (
|
|
5787
|
+
const altConfig = join7(cwd, "mcp_config.json");
|
|
5788
|
+
if (existsSync13(altConfig))
|
|
5588
5789
|
return altConfig;
|
|
5589
5790
|
return workspaceConfig;
|
|
5590
5791
|
}
|
|
@@ -5606,30 +5807,30 @@ class McpManager {
|
|
|
5606
5807
|
import { resolve as resolve14 } from "path";
|
|
5607
5808
|
|
|
5608
5809
|
// src/mcp/servers/chrome-devtools/launcher.ts
|
|
5609
|
-
import { existsSync as
|
|
5610
|
-
import { join as
|
|
5611
|
-
import { tmpdir } from "os";
|
|
5810
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, rmSync as rmSync2 } from "fs";
|
|
5811
|
+
import { join as join8 } from "path";
|
|
5812
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
5612
5813
|
class BrowserLauncher {
|
|
5613
5814
|
proc = null;
|
|
5614
5815
|
tempUserDataDir = null;
|
|
5615
5816
|
wsDebuggerUrl = null;
|
|
5616
5817
|
port = 0;
|
|
5617
5818
|
static findBrowserExecutable() {
|
|
5618
|
-
if (process.env.CHROME_PATH &&
|
|
5819
|
+
if (process.env.CHROME_PATH && existsSync14(process.env.CHROME_PATH)) {
|
|
5619
5820
|
return process.env.CHROME_PATH;
|
|
5620
5821
|
}
|
|
5621
5822
|
if (process.platform === "win32") {
|
|
5622
5823
|
const candidates = [
|
|
5623
5824
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
5624
5825
|
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
5625
|
-
|
|
5826
|
+
join8(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
|
|
5626
5827
|
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5627
5828
|
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5628
5829
|
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
5629
|
-
|
|
5830
|
+
join8(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
|
|
5630
5831
|
];
|
|
5631
5832
|
for (const path of candidates) {
|
|
5632
|
-
if (path &&
|
|
5833
|
+
if (path && existsSync14(path))
|
|
5633
5834
|
return path;
|
|
5634
5835
|
}
|
|
5635
5836
|
} else if (process.platform === "darwin") {
|
|
@@ -5640,7 +5841,7 @@ class BrowserLauncher {
|
|
|
5640
5841
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
5641
5842
|
];
|
|
5642
5843
|
for (const path of candidates) {
|
|
5643
|
-
if (
|
|
5844
|
+
if (existsSync14(path))
|
|
5644
5845
|
return path;
|
|
5645
5846
|
}
|
|
5646
5847
|
} else {
|
|
@@ -5653,7 +5854,7 @@ class BrowserLauncher {
|
|
|
5653
5854
|
"/usr/bin/microsoft-edge"
|
|
5654
5855
|
];
|
|
5655
5856
|
for (const path of candidates) {
|
|
5656
|
-
if (
|
|
5857
|
+
if (existsSync14(path))
|
|
5657
5858
|
return path;
|
|
5658
5859
|
}
|
|
5659
5860
|
}
|
|
@@ -5665,8 +5866,8 @@ class BrowserLauncher {
|
|
|
5665
5866
|
throw new Error("No supported browser (Google Chrome, Chromium, MS Edge, Brave) found on this machine. Please install Chrome or specify CHROME_PATH.");
|
|
5666
5867
|
}
|
|
5667
5868
|
this.port = options.port || 9200 + Math.floor(Math.random() * 500);
|
|
5668
|
-
this.tempUserDataDir = options.userDataDir ||
|
|
5669
|
-
|
|
5869
|
+
this.tempUserDataDir = options.userDataDir || join8(tmpdir2(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
5870
|
+
mkdirSync8(this.tempUserDataDir, { recursive: true });
|
|
5670
5871
|
const isHeadless = options.headless ?? true;
|
|
5671
5872
|
const launchArgs = [
|
|
5672
5873
|
executable,
|
|
@@ -5743,9 +5944,9 @@ class BrowserLauncher {
|
|
|
5743
5944
|
}
|
|
5744
5945
|
this.proc = null;
|
|
5745
5946
|
}
|
|
5746
|
-
if (this.tempUserDataDir &&
|
|
5947
|
+
if (this.tempUserDataDir && existsSync14(this.tempUserDataDir)) {
|
|
5747
5948
|
try {
|
|
5748
|
-
|
|
5949
|
+
rmSync2(this.tempUserDataDir, { recursive: true, force: true });
|
|
5749
5950
|
} catch {}
|
|
5750
5951
|
this.tempUserDataDir = null;
|
|
5751
5952
|
}
|
|
@@ -6104,7 +6305,7 @@ class DomSnapshotEngine {
|
|
|
6104
6305
|
}
|
|
6105
6306
|
}
|
|
6106
6307
|
// src/mcp/servers/chrome-devtools/controller.ts
|
|
6107
|
-
import { writeFileSync as writeFileSync5, mkdirSync as
|
|
6308
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync9 } from "fs";
|
|
6108
6309
|
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
6109
6310
|
class ChromeDevToolsController {
|
|
6110
6311
|
launcher = new BrowserLauncher;
|
|
@@ -6255,7 +6456,7 @@ class ChromeDevToolsController {
|
|
|
6255
6456
|
const snapshot = await DomSnapshotEngine.captureSnapshot(cdp, params.verbose);
|
|
6256
6457
|
if (params.filePath) {
|
|
6257
6458
|
const fullPath = resolve13(params.filePath);
|
|
6258
|
-
|
|
6459
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6259
6460
|
writeFileSync5(fullPath, snapshot.textSnapshot, "utf8");
|
|
6260
6461
|
return `Snapshot saved to ${params.filePath} (${snapshot.elementsCount} indexed elements)`;
|
|
6261
6462
|
}
|
|
@@ -6284,7 +6485,7 @@ class ChromeDevToolsController {
|
|
|
6284
6485
|
const base64Data = res.data;
|
|
6285
6486
|
if (params.filePath) {
|
|
6286
6487
|
const fullPath = resolve13(params.filePath);
|
|
6287
|
-
|
|
6488
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6288
6489
|
writeFileSync5(fullPath, Buffer.from(base64Data, "base64"));
|
|
6289
6490
|
return { format, filePath: params.filePath };
|
|
6290
6491
|
}
|
|
@@ -6429,7 +6630,7 @@ class ChromeDevToolsController {
|
|
|
6429
6630
|
const value = res.result?.value;
|
|
6430
6631
|
if (params.filePath) {
|
|
6431
6632
|
const fullPath = resolve13(params.filePath);
|
|
6432
|
-
|
|
6633
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6433
6634
|
writeFileSync5(fullPath, JSON.stringify(value, null, 2), "utf8");
|
|
6434
6635
|
return `Script output saved to ${params.filePath}`;
|
|
6435
6636
|
}
|
|
@@ -7583,7 +7784,7 @@ import { resolve as resolve17 } from "path";
|
|
|
7583
7784
|
// src/mcp/servers/sqlite/db-engine.ts
|
|
7584
7785
|
import { Database as Database2 } from "bun:sqlite";
|
|
7585
7786
|
import { resolve as resolve16, isAbsolute } from "path";
|
|
7586
|
-
import { readdirSync as
|
|
7787
|
+
import { readdirSync as readdirSync6 } from "fs";
|
|
7587
7788
|
|
|
7588
7789
|
class SqliteEngine {
|
|
7589
7790
|
connections = new Map;
|
|
@@ -7613,7 +7814,7 @@ class SqliteEngine {
|
|
|
7613
7814
|
}
|
|
7614
7815
|
autoDiscoverDatabase() {
|
|
7615
7816
|
try {
|
|
7616
|
-
const files =
|
|
7817
|
+
const files = readdirSync6(process.cwd());
|
|
7617
7818
|
const dbFile = files.find((f) => f.endsWith(".sqlite") || f.endsWith(".sqlite3") || f.endsWith(".db"));
|
|
7618
7819
|
return dbFile ? resolve16(process.cwd(), dbFile) : null;
|
|
7619
7820
|
} catch {
|
|
@@ -8117,8 +8318,8 @@ function verifyTaskAction(assertion, payload) {
|
|
|
8117
8318
|
}
|
|
8118
8319
|
}
|
|
8119
8320
|
// src/agents/roles.ts
|
|
8120
|
-
import { existsSync as
|
|
8121
|
-
import { resolve as resolve18, join as
|
|
8321
|
+
import { existsSync as existsSync16, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "fs";
|
|
8322
|
+
import { resolve as resolve18, join as join9 } from "path";
|
|
8122
8323
|
|
|
8123
8324
|
class AgentRoleRegistry {
|
|
8124
8325
|
roles = new Map;
|
|
@@ -8203,13 +8404,13 @@ class AgentRoleRegistry {
|
|
|
8203
8404
|
}
|
|
8204
8405
|
loadRolesFromDir(dirPath) {
|
|
8205
8406
|
const fullPath = resolve18(dirPath);
|
|
8206
|
-
if (!
|
|
8407
|
+
if (!existsSync16(fullPath))
|
|
8207
8408
|
return;
|
|
8208
|
-
const entries =
|
|
8409
|
+
const entries = readdirSync7(fullPath);
|
|
8209
8410
|
for (const entry of entries) {
|
|
8210
8411
|
if (entry.endsWith(".json")) {
|
|
8211
8412
|
try {
|
|
8212
|
-
const content = readFileSync9(
|
|
8413
|
+
const content = readFileSync9(join9(fullPath, entry), "utf8");
|
|
8213
8414
|
const parsed = JSON.parse(content);
|
|
8214
8415
|
if (parsed.name && parsed.systemPrompt) {
|
|
8215
8416
|
this.registerRole(parsed);
|
|
@@ -8239,7 +8440,7 @@ class AgentRoleRegistry {
|
|
|
8239
8440
|
// src/agents/graph-store.ts
|
|
8240
8441
|
import { Database as Database3 } from "bun:sqlite";
|
|
8241
8442
|
import { resolve as resolve19 } from "path";
|
|
8242
|
-
import { existsSync as
|
|
8443
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10 } from "fs";
|
|
8243
8444
|
class AgentGraphStore {
|
|
8244
8445
|
db;
|
|
8245
8446
|
constructor(dbPathOrDb) {
|
|
@@ -8249,8 +8450,8 @@ class AgentGraphStore {
|
|
|
8249
8450
|
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
8250
8451
|
if (dbPath !== ":memory:") {
|
|
8251
8452
|
const dir = resolve19(dbPath, "..");
|
|
8252
|
-
if (!
|
|
8253
|
-
|
|
8453
|
+
if (!existsSync17(dir)) {
|
|
8454
|
+
mkdirSync10(dir, { recursive: true });
|
|
8254
8455
|
}
|
|
8255
8456
|
}
|
|
8256
8457
|
this.db = new Database3(dbPath);
|
|
@@ -8672,7 +8873,7 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
8672
8873
|
}
|
|
8673
8874
|
// src/storage/sqlite-store.ts
|
|
8674
8875
|
import { Database as Database4 } from "bun:sqlite";
|
|
8675
|
-
import { existsSync as
|
|
8876
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11 } from "fs";
|
|
8676
8877
|
import { dirname as dirname8 } from "path";
|
|
8677
8878
|
class SqliteThreadStore {
|
|
8678
8879
|
db;
|
|
@@ -8680,8 +8881,8 @@ class SqliteThreadStore {
|
|
|
8680
8881
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
8681
8882
|
if (effectivePath !== ":memory:") {
|
|
8682
8883
|
const dir = dirname8(effectivePath);
|
|
8683
|
-
if (!
|
|
8684
|
-
|
|
8884
|
+
if (!existsSync18(dir)) {
|
|
8885
|
+
mkdirSync11(dir, { recursive: true });
|
|
8685
8886
|
}
|
|
8686
8887
|
}
|
|
8687
8888
|
this.db = new Database4(effectivePath);
|
|
@@ -8913,8 +9114,8 @@ class SessionPersistenceManager {
|
|
|
8913
9114
|
}
|
|
8914
9115
|
}
|
|
8915
9116
|
// src/skills/loader.ts
|
|
8916
|
-
import { existsSync as
|
|
8917
|
-
import { resolve as resolve20, join as
|
|
9117
|
+
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "fs";
|
|
9118
|
+
import { resolve as resolve20, join as join10 } from "path";
|
|
8918
9119
|
import { homedir as homedir3 } from "os";
|
|
8919
9120
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
8920
9121
|
|
|
@@ -8985,7 +9186,7 @@ class SkillsLoader {
|
|
|
8985
9186
|
resolve20(cwd, "skills")
|
|
8986
9187
|
];
|
|
8987
9188
|
for (const cand of candidates) {
|
|
8988
|
-
if (
|
|
9189
|
+
if (existsSync19(cand) && !roots.includes(cand)) {
|
|
8989
9190
|
roots.push(cand);
|
|
8990
9191
|
}
|
|
8991
9192
|
}
|
|
@@ -8994,7 +9195,7 @@ class SkillsLoader {
|
|
|
8994
9195
|
roots.push(getGlobalSkillsDir(), resolve20(homedir3(), ".gemini", "config", "skills"));
|
|
8995
9196
|
}
|
|
8996
9197
|
roots.push(...this.customRoots.map((r) => resolve20(r)));
|
|
8997
|
-
return roots.filter((r) =>
|
|
9198
|
+
return roots.filter((r) => existsSync19(r));
|
|
8998
9199
|
}
|
|
8999
9200
|
discoverSkills(cwd, options) {
|
|
9000
9201
|
return this.listSkills(cwd, options);
|
|
@@ -9010,12 +9211,12 @@ class SkillsLoader {
|
|
|
9010
9211
|
const discovered = new Map;
|
|
9011
9212
|
for (const root of roots) {
|
|
9012
9213
|
try {
|
|
9013
|
-
const entries =
|
|
9214
|
+
const entries = readdirSync8(root, { withFileTypes: true });
|
|
9014
9215
|
for (const entry of entries) {
|
|
9015
9216
|
if (entry.isDirectory()) {
|
|
9016
|
-
const skillDir =
|
|
9017
|
-
const skillFilePath =
|
|
9018
|
-
if (
|
|
9217
|
+
const skillDir = join10(root, entry.name);
|
|
9218
|
+
const skillFilePath = join10(skillDir, "SKILL.md");
|
|
9219
|
+
if (existsSync19(skillFilePath)) {
|
|
9019
9220
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
9020
9221
|
if (meta && !discovered.has(meta.name)) {
|
|
9021
9222
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -9028,7 +9229,7 @@ class SkillsLoader {
|
|
|
9028
9229
|
}
|
|
9029
9230
|
} catch {}
|
|
9030
9231
|
}
|
|
9031
|
-
const result = Array.from(discovered.values());
|
|
9232
|
+
const result = Array.from(discovered.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
9032
9233
|
this.skillsCache.set(cacheKey, { timestamp: now, skills: result });
|
|
9033
9234
|
return result;
|
|
9034
9235
|
}
|
|
@@ -9117,10 +9318,10 @@ class SkillsLoader {
|
|
|
9117
9318
|
const skills = this.listSkills(cwd, { includeDisabled: false });
|
|
9118
9319
|
if (skills.length === 0)
|
|
9119
9320
|
return "";
|
|
9120
|
-
const workspaceSkills = skills.filter((s) => s.scope === "workspace");
|
|
9121
|
-
const builtInSkills = skills.filter((s) => s.scope === "built-in");
|
|
9122
|
-
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in");
|
|
9123
|
-
const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0,
|
|
9321
|
+
const workspaceSkills = skills.filter((s) => s.scope === "workspace").sort((a, b) => a.name.localeCompare(b.name));
|
|
9322
|
+
const builtInSkills = skills.filter((s) => s.scope === "built-in").sort((a, b) => a.name.localeCompare(b.name));
|
|
9323
|
+
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in").sort((a, b) => a.name.localeCompare(b.name));
|
|
9324
|
+
const selectedSkills = [...workspaceSkills, ...builtInSkills, ...otherSkills].slice(0, 300);
|
|
9124
9325
|
const lines = selectedSkills.map((s) => {
|
|
9125
9326
|
const desc = s.shortDescription || s.description;
|
|
9126
9327
|
return `- **${s.name}**: ${desc}`;
|
|
@@ -9137,8 +9338,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
9137
9338
|
}
|
|
9138
9339
|
}
|
|
9139
9340
|
// src/memories/store.ts
|
|
9140
|
-
import { existsSync as
|
|
9141
|
-
import { resolve as resolve21, join as
|
|
9341
|
+
import { existsSync as existsSync20, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync12, readdirSync as readdirSync9 } from "fs";
|
|
9342
|
+
import { resolve as resolve21, join as join11, basename, dirname as dirname9 } from "path";
|
|
9142
9343
|
import { createHash } from "crypto";
|
|
9143
9344
|
class MemoryStore {
|
|
9144
9345
|
globalPath;
|
|
@@ -9150,7 +9351,7 @@ class MemoryStore {
|
|
|
9150
9351
|
findProjectRoot(cwd) {
|
|
9151
9352
|
let current = resolve21(cwd);
|
|
9152
9353
|
while (true) {
|
|
9153
|
-
if (
|
|
9354
|
+
if (existsSync20(join11(current, ".git"))) {
|
|
9154
9355
|
return current;
|
|
9155
9356
|
}
|
|
9156
9357
|
const parent = dirname9(current);
|
|
@@ -9169,24 +9370,24 @@ class MemoryStore {
|
|
|
9169
9370
|
getProjectMemoryDir(cwd) {
|
|
9170
9371
|
if (this.customWorkspacePath) {
|
|
9171
9372
|
const dir2 = resolve21(this.customWorkspacePath);
|
|
9172
|
-
if (!
|
|
9373
|
+
if (!existsSync20(dir2)) {
|
|
9173
9374
|
try {
|
|
9174
|
-
|
|
9375
|
+
mkdirSync12(dir2, { recursive: true });
|
|
9175
9376
|
} catch {}
|
|
9176
9377
|
}
|
|
9177
9378
|
return dir2;
|
|
9178
9379
|
}
|
|
9179
9380
|
const slug = this.getProjectSlug(cwd);
|
|
9180
|
-
const dir =
|
|
9181
|
-
if (!
|
|
9381
|
+
const dir = join11(getProjectsDir(), slug, "memory");
|
|
9382
|
+
if (!existsSync20(dir)) {
|
|
9182
9383
|
try {
|
|
9183
|
-
|
|
9384
|
+
mkdirSync12(dir, { recursive: true });
|
|
9184
9385
|
} catch {}
|
|
9185
9386
|
}
|
|
9186
9387
|
return dir;
|
|
9187
9388
|
}
|
|
9188
9389
|
getMemoryIndexPath(cwd) {
|
|
9189
|
-
return
|
|
9390
|
+
return join11(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
9190
9391
|
}
|
|
9191
9392
|
normalizeCategory(raw) {
|
|
9192
9393
|
const cat = raw.toLowerCase().trim();
|
|
@@ -9205,7 +9406,7 @@ class MemoryStore {
|
|
|
9205
9406
|
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
9206
9407
|
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
9207
9408
|
const fileName = `${type}_${sanitizedName}.md`;
|
|
9208
|
-
const filePath =
|
|
9409
|
+
const filePath = join11(memoryDir, fileName);
|
|
9209
9410
|
const nowIso = new Date().toISOString();
|
|
9210
9411
|
const cleanContent = params.content.trim();
|
|
9211
9412
|
const desc = (params.description || cleanContent.split(`
|
|
@@ -9240,17 +9441,17 @@ class MemoryStore {
|
|
|
9240
9441
|
}
|
|
9241
9442
|
readTopicMemory(topicNameOrFile, cwd) {
|
|
9242
9443
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9243
|
-
let targetPath =
|
|
9244
|
-
if (!
|
|
9444
|
+
let targetPath = join11(memoryDir, topicNameOrFile);
|
|
9445
|
+
if (!existsSync20(targetPath)) {
|
|
9245
9446
|
if (!topicNameOrFile.endsWith(".md")) {
|
|
9246
|
-
targetPath =
|
|
9447
|
+
targetPath = join11(memoryDir, `${topicNameOrFile}.md`);
|
|
9247
9448
|
}
|
|
9248
9449
|
}
|
|
9249
|
-
if (!
|
|
9250
|
-
const files =
|
|
9450
|
+
if (!existsSync20(targetPath)) {
|
|
9451
|
+
const files = readdirSync9(memoryDir);
|
|
9251
9452
|
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
9252
9453
|
if (match) {
|
|
9253
|
-
targetPath =
|
|
9454
|
+
targetPath = join11(memoryDir, match);
|
|
9254
9455
|
} else {
|
|
9255
9456
|
return null;
|
|
9256
9457
|
}
|
|
@@ -9311,12 +9512,12 @@ class MemoryStore {
|
|
|
9311
9512
|
}
|
|
9312
9513
|
syncMemoryIndex(cwd) {
|
|
9313
9514
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9314
|
-
const indexPath =
|
|
9315
|
-
const files =
|
|
9515
|
+
const indexPath = join11(memoryDir, "MEMORY.md");
|
|
9516
|
+
const files = existsSync20(memoryDir) ? readdirSync9(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
9316
9517
|
const items2 = [];
|
|
9317
9518
|
for (const f of files) {
|
|
9318
9519
|
try {
|
|
9319
|
-
const full =
|
|
9520
|
+
const full = join11(memoryDir, f);
|
|
9320
9521
|
const parsed = this.parseTopicFile(readFileSync11(full, "utf8"), full);
|
|
9321
9522
|
items2.push({
|
|
9322
9523
|
type: parsed.type,
|
|
@@ -9343,7 +9544,7 @@ class MemoryStore {
|
|
|
9343
9544
|
}
|
|
9344
9545
|
loadMemoryIndex(cwd) {
|
|
9345
9546
|
const indexPath = this.getMemoryIndexPath(cwd);
|
|
9346
|
-
if (!
|
|
9547
|
+
if (!existsSync20(indexPath))
|
|
9347
9548
|
return "";
|
|
9348
9549
|
try {
|
|
9349
9550
|
const raw = readFileSync11(indexPath, "utf8");
|
|
@@ -9359,13 +9560,13 @@ class MemoryStore {
|
|
|
9359
9560
|
}
|
|
9360
9561
|
listProjectMemories(cwd) {
|
|
9361
9562
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9362
|
-
if (!
|
|
9563
|
+
if (!existsSync20(memoryDir))
|
|
9363
9564
|
return [];
|
|
9364
|
-
const files =
|
|
9565
|
+
const files = readdirSync9(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
9365
9566
|
const list = [];
|
|
9366
9567
|
for (const f of files) {
|
|
9367
9568
|
try {
|
|
9368
|
-
const full =
|
|
9569
|
+
const full = join11(memoryDir, f);
|
|
9369
9570
|
list.push(this.parseTopicFile(readFileSync11(full, "utf8"), full));
|
|
9370
9571
|
} catch {}
|
|
9371
9572
|
}
|
|
@@ -9543,8 +9744,8 @@ async function removeWorktreeGit(repoRoot, worktreePath, deleteBranch = false) {
|
|
|
9543
9744
|
return { success: true };
|
|
9544
9745
|
}
|
|
9545
9746
|
// src/worktree/manager.ts
|
|
9546
|
-
import { resolve as resolve23, join as
|
|
9547
|
-
import { existsSync as
|
|
9747
|
+
import { resolve as resolve23, join as join12 } from "path";
|
|
9748
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync13, writeFileSync as writeFileSync7, readFileSync as readFileSync12 } from "fs";
|
|
9548
9749
|
var DEFAULT_WORKTREE_KEEP_COUNT = 15;
|
|
9549
9750
|
|
|
9550
9751
|
class WorktreeManager {
|
|
@@ -9568,15 +9769,15 @@ class WorktreeManager {
|
|
|
9568
9769
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
9569
9770
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve23(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve23(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
9570
9771
|
const worktreeParent = resolve23(targetDir, "..");
|
|
9571
|
-
if (!
|
|
9572
|
-
|
|
9772
|
+
if (!existsSync21(worktreeParent)) {
|
|
9773
|
+
mkdirSync13(worktreeParent, { recursive: true });
|
|
9573
9774
|
}
|
|
9574
9775
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
9575
9776
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
9576
9777
|
if (!result.success) {
|
|
9577
9778
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
9578
9779
|
}
|
|
9579
|
-
const metaPath =
|
|
9780
|
+
const metaPath = join12(targetDir, "groupy-thread.json");
|
|
9580
9781
|
try {
|
|
9581
9782
|
writeFileSync7(metaPath, JSON.stringify({
|
|
9582
9783
|
version: 1,
|
|
@@ -9602,8 +9803,8 @@ class WorktreeManager {
|
|
|
9602
9803
|
return [];
|
|
9603
9804
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
9604
9805
|
return worktrees.map((wt) => {
|
|
9605
|
-
const metaPath =
|
|
9606
|
-
if (
|
|
9806
|
+
const metaPath = join12(wt.path, "groupy-thread.json");
|
|
9807
|
+
if (existsSync21(metaPath)) {
|
|
9607
9808
|
try {
|
|
9608
9809
|
const raw = JSON.parse(readFileSync12(metaPath, "utf8"));
|
|
9609
9810
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -11333,6 +11534,8 @@ export {
|
|
|
11333
11534
|
CodeModeRuntime,
|
|
11334
11535
|
ContextWindowExceededError,
|
|
11335
11536
|
CredentialsStore,
|
|
11537
|
+
DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS,
|
|
11538
|
+
DEFAULT_MAX_CONTEXT_TOKENS,
|
|
11336
11539
|
DEFAULT_WORKTREE_KEEP_COUNT,
|
|
11337
11540
|
DefaultModelClientSession,
|
|
11338
11541
|
DomSnapshotEngine,
|
|
@@ -11380,6 +11583,7 @@ export {
|
|
|
11380
11583
|
WorktreeManager,
|
|
11381
11584
|
applyPatchTool,
|
|
11382
11585
|
askQuestionTool,
|
|
11586
|
+
buildStructuredSystemPrompt,
|
|
11383
11587
|
buildSystemPrompt,
|
|
11384
11588
|
captureWorldState,
|
|
11385
11589
|
compactHistory,
|