@pikaa-ai/pikaa 0.3.18 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +448 -239
- package/dist/index.js +405 -199
- package/package.json +1 -1
- package/skills/frontend-design/SKILL.md +1 -0
- package/skills/guardian-rails/SKILL.md +53 -0
- package/skills/verification-before-completion/SKILL.md +2 -1
- package/templates/base/groupy_prompt.md +15 -1
package/dist/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
|
}
|
|
@@ -3793,9 +3924,11 @@ async function captureWorldState(cwd) {
|
|
|
3793
3924
|
};
|
|
3794
3925
|
}
|
|
3795
3926
|
function formatWorldStatePrompt(state) {
|
|
3927
|
+
const isWindows = process.platform === "win32";
|
|
3928
|
+
const platformNote = isWindows ? `Platform: Windows (${state.os}). Shell is cmd.exe / PowerShell. POSIX commands (grep, find, cat, sed, awk) are NOT supported in shell. ALWAYS use native grep_search, find_files, and read_file tools.` : `Platform: ${state.os}`;
|
|
3796
3929
|
const parts = [
|
|
3797
3930
|
`Current Working Directory: ${state.cwd}`,
|
|
3798
|
-
|
|
3931
|
+
platformNote
|
|
3799
3932
|
];
|
|
3800
3933
|
if (state.gitBranch) {
|
|
3801
3934
|
parts.push(`Git Branch: ${state.gitBranch}`);
|
|
@@ -3805,13 +3938,13 @@ function formatWorldStatePrompt(state) {
|
|
|
3805
3938
|
`);
|
|
3806
3939
|
}
|
|
3807
3940
|
// src/prompts/loader.ts
|
|
3808
|
-
import { existsSync as
|
|
3809
|
-
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";
|
|
3810
3943
|
import { homedir as homedir2 } from "os";
|
|
3811
3944
|
class PromptTemplateLoader {
|
|
3812
3945
|
builtInTemplatesDir;
|
|
3813
3946
|
constructor(builtInDir) {
|
|
3814
|
-
this.builtInTemplatesDir = builtInDir || resolve10(
|
|
3947
|
+
this.builtInTemplatesDir = builtInDir || resolve10(join5(import.meta.dir, "..", "..", "templates"));
|
|
3815
3948
|
}
|
|
3816
3949
|
loadTemplate(relativePath, variables = {}, cwd) {
|
|
3817
3950
|
const rawContent = this.resolveTemplateContent(relativePath, cwd);
|
|
@@ -3820,27 +3953,27 @@ class PromptTemplateLoader {
|
|
|
3820
3953
|
resolveTemplateContent(relativePath, cwd) {
|
|
3821
3954
|
const normalizedRel = relativePath.replace(/^\/+/, "");
|
|
3822
3955
|
if (cwd) {
|
|
3823
|
-
const workspacePath =
|
|
3824
|
-
if (
|
|
3956
|
+
const workspacePath = join5(cwd, ".agents", "templates", normalizedRel);
|
|
3957
|
+
if (existsSync11(workspacePath)) {
|
|
3825
3958
|
try {
|
|
3826
3959
|
return readFileSync6(workspacePath, "utf-8");
|
|
3827
3960
|
} catch {}
|
|
3828
3961
|
}
|
|
3829
3962
|
}
|
|
3830
|
-
const globalPath =
|
|
3831
|
-
if (
|
|
3963
|
+
const globalPath = join5(getGlobalTemplatesDir(), normalizedRel);
|
|
3964
|
+
if (existsSync11(globalPath)) {
|
|
3832
3965
|
try {
|
|
3833
3966
|
return readFileSync6(globalPath, "utf-8");
|
|
3834
3967
|
} catch {}
|
|
3835
3968
|
}
|
|
3836
|
-
const legacyGlobalPath =
|
|
3837
|
-
if (
|
|
3969
|
+
const legacyGlobalPath = join5(homedir2(), ".groupy", "templates", normalizedRel);
|
|
3970
|
+
if (existsSync11(legacyGlobalPath)) {
|
|
3838
3971
|
try {
|
|
3839
3972
|
return readFileSync6(legacyGlobalPath, "utf-8");
|
|
3840
3973
|
} catch {}
|
|
3841
3974
|
}
|
|
3842
|
-
const builtInPath =
|
|
3843
|
-
if (
|
|
3975
|
+
const builtInPath = join5(this.builtInTemplatesDir, normalizedRel);
|
|
3976
|
+
if (existsSync11(builtInPath)) {
|
|
3844
3977
|
try {
|
|
3845
3978
|
return readFileSync6(builtInPath, "utf-8");
|
|
3846
3979
|
} catch {}
|
|
@@ -3857,8 +3990,8 @@ class PromptTemplateLoader {
|
|
|
3857
3990
|
var globalPromptLoader = new PromptTemplateLoader;
|
|
3858
3991
|
|
|
3859
3992
|
// src/prompts/agents-md.ts
|
|
3860
|
-
import { existsSync as
|
|
3861
|
-
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";
|
|
3862
3995
|
var DEFAULT_AGENTS_MD_FILENAMES = [
|
|
3863
3996
|
"AGENTS.override.md",
|
|
3864
3997
|
"AGENTS.md",
|
|
@@ -3875,7 +4008,7 @@ class AgentsMdLoader {
|
|
|
3875
4008
|
findProjectRoot(startDir) {
|
|
3876
4009
|
let current = resolve11(startDir);
|
|
3877
4010
|
while (true) {
|
|
3878
|
-
if (
|
|
4011
|
+
if (existsSync12(join6(current, ".git"))) {
|
|
3879
4012
|
return current;
|
|
3880
4013
|
}
|
|
3881
4014
|
const parent = dirname5(current);
|
|
@@ -3909,8 +4042,8 @@ class AgentsMdLoader {
|
|
|
3909
4042
|
const sourcePaths = [];
|
|
3910
4043
|
for (const dir of dirHierarchy) {
|
|
3911
4044
|
for (const filename of fallbackFilenames) {
|
|
3912
|
-
const filePath =
|
|
3913
|
-
if (
|
|
4045
|
+
const filePath = join6(dir, filename);
|
|
4046
|
+
if (existsSync12(filePath)) {
|
|
3914
4047
|
try {
|
|
3915
4048
|
const content = readFileSync7(filePath, "utf-8").trim();
|
|
3916
4049
|
if (content) {
|
|
@@ -3934,81 +4067,141 @@ class AgentsMdLoader {
|
|
|
3934
4067
|
var globalAgentsMdLoader = new AgentsMdLoader;
|
|
3935
4068
|
|
|
3936
4069
|
// src/context/instructions.ts
|
|
3937
|
-
function
|
|
3938
|
-
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) {
|
|
3939
4077
|
const cwd = params.cwd || process.cwd();
|
|
3940
4078
|
const mode = params.collaborationMode || "default";
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
4079
|
+
const blocks = [];
|
|
4080
|
+
let baseContent = params.basePrompt;
|
|
4081
|
+
if (!baseContent) {
|
|
3944
4082
|
const templateName = params.basePromptTemplate || "base/groupy_prompt.md";
|
|
3945
|
-
|
|
3946
|
-
if (baseContent) {
|
|
3947
|
-
sections.push(baseContent.trim());
|
|
3948
|
-
} else {
|
|
3949
|
-
sections.push("You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
3950
|
-
}
|
|
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.";
|
|
3951
4084
|
}
|
|
4085
|
+
blocks.push({
|
|
4086
|
+
tag: "system_identity",
|
|
4087
|
+
content: wrapXmlTag("system_identity", baseContent),
|
|
4088
|
+
cacheable: true
|
|
4089
|
+
});
|
|
3952
4090
|
if (params.personality) {
|
|
3953
4091
|
const personalityContent = globalPromptLoader.loadTemplate(`personalities/${params.personality}.md`, {}, cwd);
|
|
3954
4092
|
if (personalityContent) {
|
|
3955
|
-
|
|
4093
|
+
blocks.push({
|
|
4094
|
+
tag: "personality",
|
|
4095
|
+
content: wrapXmlTag("personality", personalityContent, { kind: params.personality }),
|
|
4096
|
+
cacheable: true
|
|
4097
|
+
});
|
|
3956
4098
|
}
|
|
3957
4099
|
}
|
|
3958
4100
|
if (params.isOrchestrator) {
|
|
3959
4101
|
const orchestratorContent = globalPromptLoader.loadTemplate("agents/orchestrator.md", {}, cwd);
|
|
3960
4102
|
if (orchestratorContent) {
|
|
3961
|
-
|
|
4103
|
+
blocks.push({
|
|
4104
|
+
tag: "orchestrator_guidelines",
|
|
4105
|
+
content: wrapXmlTag("orchestrator_guidelines", orchestratorContent),
|
|
4106
|
+
cacheable: true
|
|
4107
|
+
});
|
|
3962
4108
|
}
|
|
3963
4109
|
}
|
|
3964
|
-
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, {
|
|
3965
|
-
KNOWN_MODE_NAMES: "default, plan, review"
|
|
3966
|
-
}, cwd);
|
|
4110
|
+
const modeTemplate = globalPromptLoader.loadTemplate(`modes/${mode}.md`, { KNOWN_MODE_NAMES: "default, plan, review" }, cwd);
|
|
3967
4111
|
if (modeTemplate) {
|
|
3968
|
-
|
|
4112
|
+
blocks.push({
|
|
4113
|
+
tag: "collaboration_mode",
|
|
4114
|
+
content: wrapXmlTag("collaboration_mode", modeTemplate, { name: mode }),
|
|
4115
|
+
cacheable: true
|
|
4116
|
+
});
|
|
3969
4117
|
}
|
|
3970
4118
|
if (params.sandboxMode) {
|
|
3971
|
-
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, {
|
|
3972
|
-
network_access: params.networkAccess ? "enabled" : "disabled"
|
|
3973
|
-
}, cwd);
|
|
4119
|
+
const sandboxTemplate = globalPromptLoader.loadTemplate(`permissions/sandbox_mode/${params.sandboxMode}.md`, { network_access: params.networkAccess ? "enabled" : "disabled" }, cwd);
|
|
3974
4120
|
if (sandboxTemplate) {
|
|
3975
|
-
|
|
4121
|
+
blocks.push({
|
|
4122
|
+
tag: "sandbox_policy",
|
|
4123
|
+
content: wrapXmlTag("sandbox_policy", sandboxTemplate, { mode: params.sandboxMode }),
|
|
4124
|
+
cacheable: true
|
|
4125
|
+
});
|
|
3976
4126
|
}
|
|
3977
4127
|
}
|
|
3978
4128
|
if (params.approvalPolicy) {
|
|
3979
4129
|
const approvalTemplate = globalPromptLoader.loadTemplate(`permissions/approval_policy/${params.approvalPolicy}.md`, {}, cwd);
|
|
3980
4130
|
if (approvalTemplate) {
|
|
3981
|
-
|
|
4131
|
+
blocks.push({
|
|
4132
|
+
tag: "approval_policy",
|
|
4133
|
+
content: wrapXmlTag("approval_policy", approvalTemplate, { policy: params.approvalPolicy }),
|
|
4134
|
+
cacheable: true
|
|
4135
|
+
});
|
|
3982
4136
|
}
|
|
3983
4137
|
}
|
|
3984
4138
|
const projectInstructions = globalAgentsMdLoader.loadProjectInstructions(cwd);
|
|
3985
4139
|
if (projectInstructions) {
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
4140
|
+
blocks.push({
|
|
4141
|
+
tag: "project_instructions",
|
|
4142
|
+
content: wrapXmlTag("project_instructions", projectInstructions.content, { source: "AGENTS.md" }),
|
|
4143
|
+
cacheable: true
|
|
4144
|
+
});
|
|
3989
4145
|
}
|
|
3990
4146
|
if (params.memoriesPrompt) {
|
|
3991
|
-
|
|
4147
|
+
blocks.push({
|
|
4148
|
+
tag: "persistent_memories",
|
|
4149
|
+
content: wrapXmlTag("persistent_memories", params.memoriesPrompt),
|
|
4150
|
+
cacheable: true
|
|
4151
|
+
});
|
|
3992
4152
|
}
|
|
3993
4153
|
if (params.skillsPrompt) {
|
|
3994
|
-
|
|
4154
|
+
blocks.push({
|
|
4155
|
+
tag: "domain_skills",
|
|
4156
|
+
content: wrapXmlTag("domain_skills", params.skillsPrompt),
|
|
4157
|
+
cacheable: true
|
|
4158
|
+
});
|
|
3995
4159
|
}
|
|
3996
4160
|
if (params.mcpPrompt) {
|
|
3997
|
-
|
|
4161
|
+
blocks.push({
|
|
4162
|
+
tag: "mcp_servers",
|
|
4163
|
+
content: wrapXmlTag("mcp_servers", params.mcpPrompt),
|
|
4164
|
+
cacheable: true
|
|
4165
|
+
});
|
|
3998
4166
|
}
|
|
3999
4167
|
if (params.developerInstructions) {
|
|
4000
|
-
|
|
4001
|
-
|
|
4168
|
+
blocks.push({
|
|
4169
|
+
tag: "developer_instructions",
|
|
4170
|
+
content: wrapXmlTag("developer_instructions", params.developerInstructions),
|
|
4171
|
+
cacheable: true
|
|
4172
|
+
});
|
|
4002
4173
|
}
|
|
4174
|
+
const dynamicBlocks = [];
|
|
4003
4175
|
if (params.worldStatePrompt) {
|
|
4004
|
-
|
|
4005
|
-
|
|
4176
|
+
dynamicBlocks.push({
|
|
4177
|
+
tag: "runtime_environment",
|
|
4178
|
+
content: wrapXmlTag("runtime_environment", params.worldStatePrompt),
|
|
4179
|
+
cacheable: false
|
|
4180
|
+
});
|
|
4006
4181
|
}
|
|
4007
|
-
|
|
4182
|
+
const staticPrefix = blocks.map((b) => b.content).join(`
|
|
4183
|
+
|
|
4184
|
+
`);
|
|
4185
|
+
const dynamicSuffix = dynamicBlocks.map((b) => b.content).join(`
|
|
4008
4186
|
|
|
4009
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;
|
|
4010
4201
|
}
|
|
4011
4202
|
// src/context/compactor.ts
|
|
4203
|
+
var DEFAULT_MAX_CONTEXT_TOKENS = 256000;
|
|
4204
|
+
var DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS = 180000;
|
|
4012
4205
|
function estimateItemTokens(item) {
|
|
4013
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 : "";
|
|
4014
4207
|
return Math.ceil(text.length / 4) + 4;
|
|
@@ -4076,7 +4269,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
4076
4269
|
});
|
|
4077
4270
|
const currentHistory = session.getHistory();
|
|
4078
4271
|
const estimatedTokens = estimateTotalTokens(currentHistory);
|
|
4079
|
-
const maxTokenLimit =
|
|
4272
|
+
const maxTokenLimit = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS;
|
|
4080
4273
|
if (estimatedTokens > maxTokenLimit) {
|
|
4081
4274
|
const compacted = compactHistory(currentHistory);
|
|
4082
4275
|
session.setHistory(compacted);
|
|
@@ -4115,6 +4308,7 @@ async function runTurn(session, turnContext, input) {
|
|
|
4115
4308
|
let iteration = 0;
|
|
4116
4309
|
let accumulatedInputTokens = 0;
|
|
4117
4310
|
let accumulatedOutputTokens = 0;
|
|
4311
|
+
let accumulatedCachedTokens = 0;
|
|
4118
4312
|
const clientSession = session.modelClient.newSession();
|
|
4119
4313
|
try {
|
|
4120
4314
|
while (iteration < turnContext.maxIterations) {
|
|
@@ -4131,7 +4325,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4131
4325
|
systemPrompt: effectiveSystemPrompt,
|
|
4132
4326
|
history: session.getHistory(),
|
|
4133
4327
|
tools: turnContext.tools,
|
|
4134
|
-
signal
|
|
4328
|
+
signal,
|
|
4329
|
+
enablePromptCache: true
|
|
4135
4330
|
});
|
|
4136
4331
|
for await (const chunk of stream) {
|
|
4137
4332
|
if (signal.aborted) {
|
|
@@ -4157,6 +4352,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4157
4352
|
iterInputTokens = chunk.inputTokens;
|
|
4158
4353
|
if (chunk.outputTokens !== undefined)
|
|
4159
4354
|
iterOutputTokens = chunk.outputTokens;
|
|
4355
|
+
if (chunk.cachedTokens !== undefined)
|
|
4356
|
+
accumulatedCachedTokens += chunk.cachedTokens;
|
|
4160
4357
|
} else if (chunk.type === "error") {
|
|
4161
4358
|
throw chunk.error;
|
|
4162
4359
|
}
|
|
@@ -4268,7 +4465,10 @@ async function runTurn(session, turnContext, input) {
|
|
|
4268
4465
|
session.addHistoryItem({
|
|
4269
4466
|
id: `msg_nudge_${Date.now()}`,
|
|
4270
4467
|
type: "user_message",
|
|
4271
|
-
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.`,
|
|
4272
4472
|
createdAt: Date.now()
|
|
4273
4473
|
});
|
|
4274
4474
|
continue;
|
|
@@ -4277,13 +4477,14 @@ async function runTurn(session, turnContext, input) {
|
|
|
4277
4477
|
break;
|
|
4278
4478
|
}
|
|
4279
4479
|
const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
|
|
4280
|
-
const maxContextTokens =
|
|
4480
|
+
const maxContextTokens = DEFAULT_MAX_CONTEXT_TOKENS;
|
|
4281
4481
|
session.emitEvent({
|
|
4282
4482
|
type: "TurnCompleted",
|
|
4283
4483
|
turnId,
|
|
4284
4484
|
inputTokens: accumulatedInputTokens,
|
|
4285
4485
|
outputTokens: accumulatedOutputTokens,
|
|
4286
4486
|
totalTokens: accumulatedInputTokens + accumulatedOutputTokens,
|
|
4487
|
+
cachedTokens: accumulatedCachedTokens > 0 ? accumulatedCachedTokens : undefined,
|
|
4287
4488
|
contextTokens: totalContextTokens,
|
|
4288
4489
|
maxContextTokens
|
|
4289
4490
|
});
|
|
@@ -4298,6 +4499,8 @@ async function runTurn(session, turnContext, input) {
|
|
|
4298
4499
|
});
|
|
4299
4500
|
} finally {
|
|
4300
4501
|
session.clearActiveTurn(turnId);
|
|
4502
|
+
globalEphemeralWorkspace.cleanupTurn(turnId);
|
|
4503
|
+
globalEphemeralWorkspace.cleanRootResidue(turnContext.environment.cwd);
|
|
4301
4504
|
}
|
|
4302
4505
|
}
|
|
4303
4506
|
|
|
@@ -5238,8 +5441,8 @@ class McpClient {
|
|
|
5238
5441
|
}
|
|
5239
5442
|
}
|
|
5240
5443
|
// src/mcp/manager.ts
|
|
5241
|
-
import { existsSync as
|
|
5242
|
-
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";
|
|
5243
5446
|
class McpManager {
|
|
5244
5447
|
clients = new Map;
|
|
5245
5448
|
serverConfigs = new Map;
|
|
@@ -5274,7 +5477,7 @@ class McpManager {
|
|
|
5274
5477
|
}
|
|
5275
5478
|
async loadConfigFile(filePath) {
|
|
5276
5479
|
const fullPath = resolve12(filePath);
|
|
5277
|
-
if (!
|
|
5480
|
+
if (!existsSync13(fullPath))
|
|
5278
5481
|
return;
|
|
5279
5482
|
this.loadedConfigFiles.add(fullPath);
|
|
5280
5483
|
try {
|
|
@@ -5529,11 +5732,11 @@ class McpManager {
|
|
|
5529
5732
|
saveServerToConfigFile(filePath, name, config) {
|
|
5530
5733
|
const fullPath = resolve12(filePath);
|
|
5531
5734
|
const dir = dirname6(fullPath);
|
|
5532
|
-
if (!
|
|
5533
|
-
|
|
5735
|
+
if (!existsSync13(dir)) {
|
|
5736
|
+
mkdirSync7(dir, { recursive: true });
|
|
5534
5737
|
}
|
|
5535
5738
|
let existing = { mcpServers: {} };
|
|
5536
|
-
if (
|
|
5739
|
+
if (existsSync13(fullPath)) {
|
|
5537
5740
|
try {
|
|
5538
5741
|
const content = readFileSync8(fullPath, "utf8");
|
|
5539
5742
|
existing = JSON.parse(content);
|
|
@@ -5548,7 +5751,7 @@ class McpManager {
|
|
|
5548
5751
|
}
|
|
5549
5752
|
removeServerFromConfigFile(filePath, name) {
|
|
5550
5753
|
const fullPath = resolve12(filePath);
|
|
5551
|
-
if (!
|
|
5754
|
+
if (!existsSync13(fullPath))
|
|
5552
5755
|
return false;
|
|
5553
5756
|
try {
|
|
5554
5757
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -5578,11 +5781,11 @@ class McpManager {
|
|
|
5578
5781
|
}
|
|
5579
5782
|
}
|
|
5580
5783
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
5581
|
-
const workspaceConfig =
|
|
5582
|
-
if (
|
|
5784
|
+
const workspaceConfig = join7(cwd, ".mcp.json");
|
|
5785
|
+
if (existsSync13(workspaceConfig))
|
|
5583
5786
|
return workspaceConfig;
|
|
5584
|
-
const altConfig =
|
|
5585
|
-
if (
|
|
5787
|
+
const altConfig = join7(cwd, "mcp_config.json");
|
|
5788
|
+
if (existsSync13(altConfig))
|
|
5586
5789
|
return altConfig;
|
|
5587
5790
|
return workspaceConfig;
|
|
5588
5791
|
}
|
|
@@ -5604,30 +5807,30 @@ class McpManager {
|
|
|
5604
5807
|
import { resolve as resolve14 } from "path";
|
|
5605
5808
|
|
|
5606
5809
|
// src/mcp/servers/chrome-devtools/launcher.ts
|
|
5607
|
-
import { existsSync as
|
|
5608
|
-
import { join as
|
|
5609
|
-
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";
|
|
5610
5813
|
class BrowserLauncher {
|
|
5611
5814
|
proc = null;
|
|
5612
5815
|
tempUserDataDir = null;
|
|
5613
5816
|
wsDebuggerUrl = null;
|
|
5614
5817
|
port = 0;
|
|
5615
5818
|
static findBrowserExecutable() {
|
|
5616
|
-
if (process.env.CHROME_PATH &&
|
|
5819
|
+
if (process.env.CHROME_PATH && existsSync14(process.env.CHROME_PATH)) {
|
|
5617
5820
|
return process.env.CHROME_PATH;
|
|
5618
5821
|
}
|
|
5619
5822
|
if (process.platform === "win32") {
|
|
5620
5823
|
const candidates = [
|
|
5621
5824
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
5622
5825
|
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
5623
|
-
|
|
5826
|
+
join8(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
|
|
5624
5827
|
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5625
5828
|
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5626
5829
|
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
5627
|
-
|
|
5830
|
+
join8(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
|
|
5628
5831
|
];
|
|
5629
5832
|
for (const path of candidates) {
|
|
5630
|
-
if (path &&
|
|
5833
|
+
if (path && existsSync14(path))
|
|
5631
5834
|
return path;
|
|
5632
5835
|
}
|
|
5633
5836
|
} else if (process.platform === "darwin") {
|
|
@@ -5638,7 +5841,7 @@ class BrowserLauncher {
|
|
|
5638
5841
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
5639
5842
|
];
|
|
5640
5843
|
for (const path of candidates) {
|
|
5641
|
-
if (
|
|
5844
|
+
if (existsSync14(path))
|
|
5642
5845
|
return path;
|
|
5643
5846
|
}
|
|
5644
5847
|
} else {
|
|
@@ -5651,7 +5854,7 @@ class BrowserLauncher {
|
|
|
5651
5854
|
"/usr/bin/microsoft-edge"
|
|
5652
5855
|
];
|
|
5653
5856
|
for (const path of candidates) {
|
|
5654
|
-
if (
|
|
5857
|
+
if (existsSync14(path))
|
|
5655
5858
|
return path;
|
|
5656
5859
|
}
|
|
5657
5860
|
}
|
|
@@ -5663,8 +5866,8 @@ class BrowserLauncher {
|
|
|
5663
5866
|
throw new Error("No supported browser (Google Chrome, Chromium, MS Edge, Brave) found on this machine. Please install Chrome or specify CHROME_PATH.");
|
|
5664
5867
|
}
|
|
5665
5868
|
this.port = options.port || 9200 + Math.floor(Math.random() * 500);
|
|
5666
|
-
this.tempUserDataDir = options.userDataDir ||
|
|
5667
|
-
|
|
5869
|
+
this.tempUserDataDir = options.userDataDir || join8(tmpdir2(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
5870
|
+
mkdirSync8(this.tempUserDataDir, { recursive: true });
|
|
5668
5871
|
const isHeadless = options.headless ?? true;
|
|
5669
5872
|
const launchArgs = [
|
|
5670
5873
|
executable,
|
|
@@ -5741,9 +5944,9 @@ class BrowserLauncher {
|
|
|
5741
5944
|
}
|
|
5742
5945
|
this.proc = null;
|
|
5743
5946
|
}
|
|
5744
|
-
if (this.tempUserDataDir &&
|
|
5947
|
+
if (this.tempUserDataDir && existsSync14(this.tempUserDataDir)) {
|
|
5745
5948
|
try {
|
|
5746
|
-
|
|
5949
|
+
rmSync2(this.tempUserDataDir, { recursive: true, force: true });
|
|
5747
5950
|
} catch {}
|
|
5748
5951
|
this.tempUserDataDir = null;
|
|
5749
5952
|
}
|
|
@@ -6102,7 +6305,7 @@ class DomSnapshotEngine {
|
|
|
6102
6305
|
}
|
|
6103
6306
|
}
|
|
6104
6307
|
// src/mcp/servers/chrome-devtools/controller.ts
|
|
6105
|
-
import { writeFileSync as writeFileSync5, mkdirSync as
|
|
6308
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync9 } from "fs";
|
|
6106
6309
|
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
6107
6310
|
class ChromeDevToolsController {
|
|
6108
6311
|
launcher = new BrowserLauncher;
|
|
@@ -6253,7 +6456,7 @@ class ChromeDevToolsController {
|
|
|
6253
6456
|
const snapshot = await DomSnapshotEngine.captureSnapshot(cdp, params.verbose);
|
|
6254
6457
|
if (params.filePath) {
|
|
6255
6458
|
const fullPath = resolve13(params.filePath);
|
|
6256
|
-
|
|
6459
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6257
6460
|
writeFileSync5(fullPath, snapshot.textSnapshot, "utf8");
|
|
6258
6461
|
return `Snapshot saved to ${params.filePath} (${snapshot.elementsCount} indexed elements)`;
|
|
6259
6462
|
}
|
|
@@ -6282,7 +6485,7 @@ class ChromeDevToolsController {
|
|
|
6282
6485
|
const base64Data = res.data;
|
|
6283
6486
|
if (params.filePath) {
|
|
6284
6487
|
const fullPath = resolve13(params.filePath);
|
|
6285
|
-
|
|
6488
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6286
6489
|
writeFileSync5(fullPath, Buffer.from(base64Data, "base64"));
|
|
6287
6490
|
return { format, filePath: params.filePath };
|
|
6288
6491
|
}
|
|
@@ -6427,7 +6630,7 @@ class ChromeDevToolsController {
|
|
|
6427
6630
|
const value = res.result?.value;
|
|
6428
6631
|
if (params.filePath) {
|
|
6429
6632
|
const fullPath = resolve13(params.filePath);
|
|
6430
|
-
|
|
6633
|
+
mkdirSync9(dirname7(fullPath), { recursive: true });
|
|
6431
6634
|
writeFileSync5(fullPath, JSON.stringify(value, null, 2), "utf8");
|
|
6432
6635
|
return `Script output saved to ${params.filePath}`;
|
|
6433
6636
|
}
|
|
@@ -7581,7 +7784,7 @@ import { resolve as resolve17 } from "path";
|
|
|
7581
7784
|
// src/mcp/servers/sqlite/db-engine.ts
|
|
7582
7785
|
import { Database as Database2 } from "bun:sqlite";
|
|
7583
7786
|
import { resolve as resolve16, isAbsolute } from "path";
|
|
7584
|
-
import { readdirSync as
|
|
7787
|
+
import { readdirSync as readdirSync6 } from "fs";
|
|
7585
7788
|
|
|
7586
7789
|
class SqliteEngine {
|
|
7587
7790
|
connections = new Map;
|
|
@@ -7611,7 +7814,7 @@ class SqliteEngine {
|
|
|
7611
7814
|
}
|
|
7612
7815
|
autoDiscoverDatabase() {
|
|
7613
7816
|
try {
|
|
7614
|
-
const files =
|
|
7817
|
+
const files = readdirSync6(process.cwd());
|
|
7615
7818
|
const dbFile = files.find((f) => f.endsWith(".sqlite") || f.endsWith(".sqlite3") || f.endsWith(".db"));
|
|
7616
7819
|
return dbFile ? resolve16(process.cwd(), dbFile) : null;
|
|
7617
7820
|
} catch {
|
|
@@ -8115,8 +8318,8 @@ function verifyTaskAction(assertion, payload) {
|
|
|
8115
8318
|
}
|
|
8116
8319
|
}
|
|
8117
8320
|
// src/agents/roles.ts
|
|
8118
|
-
import { existsSync as
|
|
8119
|
-
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";
|
|
8120
8323
|
|
|
8121
8324
|
class AgentRoleRegistry {
|
|
8122
8325
|
roles = new Map;
|
|
@@ -8201,13 +8404,13 @@ class AgentRoleRegistry {
|
|
|
8201
8404
|
}
|
|
8202
8405
|
loadRolesFromDir(dirPath) {
|
|
8203
8406
|
const fullPath = resolve18(dirPath);
|
|
8204
|
-
if (!
|
|
8407
|
+
if (!existsSync16(fullPath))
|
|
8205
8408
|
return;
|
|
8206
|
-
const entries =
|
|
8409
|
+
const entries = readdirSync7(fullPath);
|
|
8207
8410
|
for (const entry of entries) {
|
|
8208
8411
|
if (entry.endsWith(".json")) {
|
|
8209
8412
|
try {
|
|
8210
|
-
const content = readFileSync9(
|
|
8413
|
+
const content = readFileSync9(join9(fullPath, entry), "utf8");
|
|
8211
8414
|
const parsed = JSON.parse(content);
|
|
8212
8415
|
if (parsed.name && parsed.systemPrompt) {
|
|
8213
8416
|
this.registerRole(parsed);
|
|
@@ -8237,7 +8440,7 @@ class AgentRoleRegistry {
|
|
|
8237
8440
|
// src/agents/graph-store.ts
|
|
8238
8441
|
import { Database as Database3 } from "bun:sqlite";
|
|
8239
8442
|
import { resolve as resolve19 } from "path";
|
|
8240
|
-
import { existsSync as
|
|
8443
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10 } from "fs";
|
|
8241
8444
|
class AgentGraphStore {
|
|
8242
8445
|
db;
|
|
8243
8446
|
constructor(dbPathOrDb) {
|
|
@@ -8247,8 +8450,8 @@ class AgentGraphStore {
|
|
|
8247
8450
|
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
8248
8451
|
if (dbPath !== ":memory:") {
|
|
8249
8452
|
const dir = resolve19(dbPath, "..");
|
|
8250
|
-
if (!
|
|
8251
|
-
|
|
8453
|
+
if (!existsSync17(dir)) {
|
|
8454
|
+
mkdirSync10(dir, { recursive: true });
|
|
8252
8455
|
}
|
|
8253
8456
|
}
|
|
8254
8457
|
this.db = new Database3(dbPath);
|
|
@@ -8670,7 +8873,7 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
8670
8873
|
}
|
|
8671
8874
|
// src/storage/sqlite-store.ts
|
|
8672
8875
|
import { Database as Database4 } from "bun:sqlite";
|
|
8673
|
-
import { existsSync as
|
|
8876
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11 } from "fs";
|
|
8674
8877
|
import { dirname as dirname8 } from "path";
|
|
8675
8878
|
class SqliteThreadStore {
|
|
8676
8879
|
db;
|
|
@@ -8678,8 +8881,8 @@ class SqliteThreadStore {
|
|
|
8678
8881
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
8679
8882
|
if (effectivePath !== ":memory:") {
|
|
8680
8883
|
const dir = dirname8(effectivePath);
|
|
8681
|
-
if (!
|
|
8682
|
-
|
|
8884
|
+
if (!existsSync18(dir)) {
|
|
8885
|
+
mkdirSync11(dir, { recursive: true });
|
|
8683
8886
|
}
|
|
8684
8887
|
}
|
|
8685
8888
|
this.db = new Database4(effectivePath);
|
|
@@ -8911,8 +9114,8 @@ class SessionPersistenceManager {
|
|
|
8911
9114
|
}
|
|
8912
9115
|
}
|
|
8913
9116
|
// src/skills/loader.ts
|
|
8914
|
-
import { existsSync as
|
|
8915
|
-
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";
|
|
8916
9119
|
import { homedir as homedir3 } from "os";
|
|
8917
9120
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
8918
9121
|
|
|
@@ -8983,7 +9186,7 @@ class SkillsLoader {
|
|
|
8983
9186
|
resolve20(cwd, "skills")
|
|
8984
9187
|
];
|
|
8985
9188
|
for (const cand of candidates) {
|
|
8986
|
-
if (
|
|
9189
|
+
if (existsSync19(cand) && !roots.includes(cand)) {
|
|
8987
9190
|
roots.push(cand);
|
|
8988
9191
|
}
|
|
8989
9192
|
}
|
|
@@ -8992,7 +9195,7 @@ class SkillsLoader {
|
|
|
8992
9195
|
roots.push(getGlobalSkillsDir(), resolve20(homedir3(), ".gemini", "config", "skills"));
|
|
8993
9196
|
}
|
|
8994
9197
|
roots.push(...this.customRoots.map((r) => resolve20(r)));
|
|
8995
|
-
return roots.filter((r) =>
|
|
9198
|
+
return roots.filter((r) => existsSync19(r));
|
|
8996
9199
|
}
|
|
8997
9200
|
discoverSkills(cwd, options) {
|
|
8998
9201
|
return this.listSkills(cwd, options);
|
|
@@ -9008,12 +9211,12 @@ class SkillsLoader {
|
|
|
9008
9211
|
const discovered = new Map;
|
|
9009
9212
|
for (const root of roots) {
|
|
9010
9213
|
try {
|
|
9011
|
-
const entries =
|
|
9214
|
+
const entries = readdirSync8(root, { withFileTypes: true });
|
|
9012
9215
|
for (const entry of entries) {
|
|
9013
9216
|
if (entry.isDirectory()) {
|
|
9014
|
-
const skillDir =
|
|
9015
|
-
const skillFilePath =
|
|
9016
|
-
if (
|
|
9217
|
+
const skillDir = join10(root, entry.name);
|
|
9218
|
+
const skillFilePath = join10(skillDir, "SKILL.md");
|
|
9219
|
+
if (existsSync19(skillFilePath)) {
|
|
9017
9220
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
9018
9221
|
if (meta && !discovered.has(meta.name)) {
|
|
9019
9222
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -9026,7 +9229,7 @@ class SkillsLoader {
|
|
|
9026
9229
|
}
|
|
9027
9230
|
} catch {}
|
|
9028
9231
|
}
|
|
9029
|
-
const result = Array.from(discovered.values());
|
|
9232
|
+
const result = Array.from(discovered.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
9030
9233
|
this.skillsCache.set(cacheKey, { timestamp: now, skills: result });
|
|
9031
9234
|
return result;
|
|
9032
9235
|
}
|
|
@@ -9115,10 +9318,10 @@ class SkillsLoader {
|
|
|
9115
9318
|
const skills = this.listSkills(cwd, { includeDisabled: false });
|
|
9116
9319
|
if (skills.length === 0)
|
|
9117
9320
|
return "";
|
|
9118
|
-
const workspaceSkills = skills.filter((s) => s.scope === "workspace");
|
|
9119
|
-
const builtInSkills = skills.filter((s) => s.scope === "built-in");
|
|
9120
|
-
const otherSkills = skills.filter((s) => s.scope !== "workspace" && s.scope !== "built-in");
|
|
9121
|
-
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);
|
|
9122
9325
|
const lines = selectedSkills.map((s) => {
|
|
9123
9326
|
const desc = s.shortDescription || s.description;
|
|
9124
9327
|
return `- **${s.name}**: ${desc}`;
|
|
@@ -9135,8 +9338,8 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
9135
9338
|
}
|
|
9136
9339
|
}
|
|
9137
9340
|
// src/memories/store.ts
|
|
9138
|
-
import { existsSync as
|
|
9139
|
-
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";
|
|
9140
9343
|
import { createHash } from "crypto";
|
|
9141
9344
|
class MemoryStore {
|
|
9142
9345
|
globalPath;
|
|
@@ -9148,7 +9351,7 @@ class MemoryStore {
|
|
|
9148
9351
|
findProjectRoot(cwd) {
|
|
9149
9352
|
let current = resolve21(cwd);
|
|
9150
9353
|
while (true) {
|
|
9151
|
-
if (
|
|
9354
|
+
if (existsSync20(join11(current, ".git"))) {
|
|
9152
9355
|
return current;
|
|
9153
9356
|
}
|
|
9154
9357
|
const parent = dirname9(current);
|
|
@@ -9167,24 +9370,24 @@ class MemoryStore {
|
|
|
9167
9370
|
getProjectMemoryDir(cwd) {
|
|
9168
9371
|
if (this.customWorkspacePath) {
|
|
9169
9372
|
const dir2 = resolve21(this.customWorkspacePath);
|
|
9170
|
-
if (!
|
|
9373
|
+
if (!existsSync20(dir2)) {
|
|
9171
9374
|
try {
|
|
9172
|
-
|
|
9375
|
+
mkdirSync12(dir2, { recursive: true });
|
|
9173
9376
|
} catch {}
|
|
9174
9377
|
}
|
|
9175
9378
|
return dir2;
|
|
9176
9379
|
}
|
|
9177
9380
|
const slug = this.getProjectSlug(cwd);
|
|
9178
|
-
const dir =
|
|
9179
|
-
if (!
|
|
9381
|
+
const dir = join11(getProjectsDir(), slug, "memory");
|
|
9382
|
+
if (!existsSync20(dir)) {
|
|
9180
9383
|
try {
|
|
9181
|
-
|
|
9384
|
+
mkdirSync12(dir, { recursive: true });
|
|
9182
9385
|
} catch {}
|
|
9183
9386
|
}
|
|
9184
9387
|
return dir;
|
|
9185
9388
|
}
|
|
9186
9389
|
getMemoryIndexPath(cwd) {
|
|
9187
|
-
return
|
|
9390
|
+
return join11(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
9188
9391
|
}
|
|
9189
9392
|
normalizeCategory(raw) {
|
|
9190
9393
|
const cat = raw.toLowerCase().trim();
|
|
@@ -9203,7 +9406,7 @@ class MemoryStore {
|
|
|
9203
9406
|
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
9204
9407
|
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
9205
9408
|
const fileName = `${type}_${sanitizedName}.md`;
|
|
9206
|
-
const filePath =
|
|
9409
|
+
const filePath = join11(memoryDir, fileName);
|
|
9207
9410
|
const nowIso = new Date().toISOString();
|
|
9208
9411
|
const cleanContent = params.content.trim();
|
|
9209
9412
|
const desc = (params.description || cleanContent.split(`
|
|
@@ -9238,17 +9441,17 @@ class MemoryStore {
|
|
|
9238
9441
|
}
|
|
9239
9442
|
readTopicMemory(topicNameOrFile, cwd) {
|
|
9240
9443
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9241
|
-
let targetPath =
|
|
9242
|
-
if (!
|
|
9444
|
+
let targetPath = join11(memoryDir, topicNameOrFile);
|
|
9445
|
+
if (!existsSync20(targetPath)) {
|
|
9243
9446
|
if (!topicNameOrFile.endsWith(".md")) {
|
|
9244
|
-
targetPath =
|
|
9447
|
+
targetPath = join11(memoryDir, `${topicNameOrFile}.md`);
|
|
9245
9448
|
}
|
|
9246
9449
|
}
|
|
9247
|
-
if (!
|
|
9248
|
-
const files =
|
|
9450
|
+
if (!existsSync20(targetPath)) {
|
|
9451
|
+
const files = readdirSync9(memoryDir);
|
|
9249
9452
|
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
9250
9453
|
if (match) {
|
|
9251
|
-
targetPath =
|
|
9454
|
+
targetPath = join11(memoryDir, match);
|
|
9252
9455
|
} else {
|
|
9253
9456
|
return null;
|
|
9254
9457
|
}
|
|
@@ -9309,12 +9512,12 @@ class MemoryStore {
|
|
|
9309
9512
|
}
|
|
9310
9513
|
syncMemoryIndex(cwd) {
|
|
9311
9514
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9312
|
-
const indexPath =
|
|
9313
|
-
const files =
|
|
9515
|
+
const indexPath = join11(memoryDir, "MEMORY.md");
|
|
9516
|
+
const files = existsSync20(memoryDir) ? readdirSync9(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
9314
9517
|
const items2 = [];
|
|
9315
9518
|
for (const f of files) {
|
|
9316
9519
|
try {
|
|
9317
|
-
const full =
|
|
9520
|
+
const full = join11(memoryDir, f);
|
|
9318
9521
|
const parsed = this.parseTopicFile(readFileSync11(full, "utf8"), full);
|
|
9319
9522
|
items2.push({
|
|
9320
9523
|
type: parsed.type,
|
|
@@ -9341,7 +9544,7 @@ class MemoryStore {
|
|
|
9341
9544
|
}
|
|
9342
9545
|
loadMemoryIndex(cwd) {
|
|
9343
9546
|
const indexPath = this.getMemoryIndexPath(cwd);
|
|
9344
|
-
if (!
|
|
9547
|
+
if (!existsSync20(indexPath))
|
|
9345
9548
|
return "";
|
|
9346
9549
|
try {
|
|
9347
9550
|
const raw = readFileSync11(indexPath, "utf8");
|
|
@@ -9357,13 +9560,13 @@ class MemoryStore {
|
|
|
9357
9560
|
}
|
|
9358
9561
|
listProjectMemories(cwd) {
|
|
9359
9562
|
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9360
|
-
if (!
|
|
9563
|
+
if (!existsSync20(memoryDir))
|
|
9361
9564
|
return [];
|
|
9362
|
-
const files =
|
|
9565
|
+
const files = readdirSync9(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
9363
9566
|
const list = [];
|
|
9364
9567
|
for (const f of files) {
|
|
9365
9568
|
try {
|
|
9366
|
-
const full =
|
|
9569
|
+
const full = join11(memoryDir, f);
|
|
9367
9570
|
list.push(this.parseTopicFile(readFileSync11(full, "utf8"), full));
|
|
9368
9571
|
} catch {}
|
|
9369
9572
|
}
|
|
@@ -9541,8 +9744,8 @@ async function removeWorktreeGit(repoRoot, worktreePath, deleteBranch = false) {
|
|
|
9541
9744
|
return { success: true };
|
|
9542
9745
|
}
|
|
9543
9746
|
// src/worktree/manager.ts
|
|
9544
|
-
import { resolve as resolve23, join as
|
|
9545
|
-
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";
|
|
9546
9749
|
var DEFAULT_WORKTREE_KEEP_COUNT = 15;
|
|
9547
9750
|
|
|
9548
9751
|
class WorktreeManager {
|
|
@@ -9566,15 +9769,15 @@ class WorktreeManager {
|
|
|
9566
9769
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
9567
9770
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve23(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve23(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
9568
9771
|
const worktreeParent = resolve23(targetDir, "..");
|
|
9569
|
-
if (!
|
|
9570
|
-
|
|
9772
|
+
if (!existsSync21(worktreeParent)) {
|
|
9773
|
+
mkdirSync13(worktreeParent, { recursive: true });
|
|
9571
9774
|
}
|
|
9572
9775
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
9573
9776
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
9574
9777
|
if (!result.success) {
|
|
9575
9778
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
9576
9779
|
}
|
|
9577
|
-
const metaPath =
|
|
9780
|
+
const metaPath = join12(targetDir, "groupy-thread.json");
|
|
9578
9781
|
try {
|
|
9579
9782
|
writeFileSync7(metaPath, JSON.stringify({
|
|
9580
9783
|
version: 1,
|
|
@@ -9600,8 +9803,8 @@ class WorktreeManager {
|
|
|
9600
9803
|
return [];
|
|
9601
9804
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
9602
9805
|
return worktrees.map((wt) => {
|
|
9603
|
-
const metaPath =
|
|
9604
|
-
if (
|
|
9806
|
+
const metaPath = join12(wt.path, "groupy-thread.json");
|
|
9807
|
+
if (existsSync21(metaPath)) {
|
|
9605
9808
|
try {
|
|
9606
9809
|
const raw = JSON.parse(readFileSync12(metaPath, "utf8"));
|
|
9607
9810
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -11331,6 +11534,8 @@ export {
|
|
|
11331
11534
|
CodeModeRuntime,
|
|
11332
11535
|
ContextWindowExceededError,
|
|
11333
11536
|
CredentialsStore,
|
|
11537
|
+
DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS,
|
|
11538
|
+
DEFAULT_MAX_CONTEXT_TOKENS,
|
|
11334
11539
|
DEFAULT_WORKTREE_KEEP_COUNT,
|
|
11335
11540
|
DefaultModelClientSession,
|
|
11336
11541
|
DomSnapshotEngine,
|
|
@@ -11378,6 +11583,7 @@ export {
|
|
|
11378
11583
|
WorktreeManager,
|
|
11379
11584
|
applyPatchTool,
|
|
11380
11585
|
askQuestionTool,
|
|
11586
|
+
buildStructuredSystemPrompt,
|
|
11381
11587
|
buildSystemPrompt,
|
|
11382
11588
|
captureWorldState,
|
|
11383
11589
|
compactHistory,
|