@pikaa-ai/pikaa 0.3.14 → 0.3.16
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 +801 -387
- package/dist/index.js +698 -319
- package/package.json +1 -1
- package/templates/base/groupy_prompt.md +14 -0
package/dist/index.js
CHANGED
|
@@ -1119,17 +1119,139 @@ class ExecutionPolicyError extends GroupyError {
|
|
|
1119
1119
|
}
|
|
1120
1120
|
}
|
|
1121
1121
|
// src/auth/store.ts
|
|
1122
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
1123
|
-
import { resolve } from "path";
|
|
1122
|
+
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
1123
|
+
import { resolve as resolve2 } from "path";
|
|
1124
|
+
|
|
1125
|
+
// src/config/paths.ts
|
|
1126
|
+
import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
|
|
1127
|
+
import { resolve, join } from "path";
|
|
1124
1128
|
import { homedir } from "os";
|
|
1129
|
+
function getPikaaHomeDir() {
|
|
1130
|
+
const envDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME;
|
|
1131
|
+
if (envDir) {
|
|
1132
|
+
return resolve(envDir);
|
|
1133
|
+
}
|
|
1134
|
+
return resolve(homedir(), ".pikaa");
|
|
1135
|
+
}
|
|
1136
|
+
function getLegacyGroupyHomeDir() {
|
|
1137
|
+
if (process.env.GROUPY_HOME) {
|
|
1138
|
+
return resolve(process.env.GROUPY_HOME);
|
|
1139
|
+
}
|
|
1140
|
+
return resolve(homedir(), ".groupy");
|
|
1141
|
+
}
|
|
1142
|
+
var hasMigrated = false;
|
|
1143
|
+
function copyDirRecursiveSync(src, dest) {
|
|
1144
|
+
if (!existsSync(src))
|
|
1145
|
+
return;
|
|
1146
|
+
if (!existsSync(dest)) {
|
|
1147
|
+
mkdirSync(dest, { recursive: true });
|
|
1148
|
+
}
|
|
1149
|
+
const entries = readdirSync(src);
|
|
1150
|
+
for (const entry of entries) {
|
|
1151
|
+
const srcPath = join(src, entry);
|
|
1152
|
+
const destPath = join(dest, entry);
|
|
1153
|
+
const stat = statSync(srcPath);
|
|
1154
|
+
if (stat.isDirectory()) {
|
|
1155
|
+
copyDirRecursiveSync(srcPath, destPath);
|
|
1156
|
+
} else if (!existsSync(destPath)) {
|
|
1157
|
+
try {
|
|
1158
|
+
copyFileSync(srcPath, destPath);
|
|
1159
|
+
} catch {}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function ensurePikaaHomeMigrated(force = false) {
|
|
1164
|
+
const pikaaHome = getPikaaHomeDir();
|
|
1165
|
+
const legacyHome = getLegacyGroupyHomeDir();
|
|
1166
|
+
try {
|
|
1167
|
+
if (!existsSync(pikaaHome)) {
|
|
1168
|
+
mkdirSync(pikaaHome, { recursive: true });
|
|
1169
|
+
}
|
|
1170
|
+
} catch {}
|
|
1171
|
+
if (hasMigrated && !force) {
|
|
1172
|
+
return pikaaHome;
|
|
1173
|
+
}
|
|
1174
|
+
hasMigrated = true;
|
|
1175
|
+
try {
|
|
1176
|
+
if (existsSync(legacyHome) && legacyHome !== pikaaHome) {
|
|
1177
|
+
const legacyCreds = join(legacyHome, "credentials.json");
|
|
1178
|
+
const pikaaCreds = join(pikaaHome, "credentials.json");
|
|
1179
|
+
if (existsSync(legacyCreds) && !existsSync(pikaaCreds)) {
|
|
1180
|
+
copyFileSync(legacyCreds, pikaaCreds);
|
|
1181
|
+
}
|
|
1182
|
+
const legacyThreads = join(legacyHome, "groupy_threads.db");
|
|
1183
|
+
const pikaaThreads = join(pikaaHome, "pikaa_threads.db");
|
|
1184
|
+
if (existsSync(legacyThreads) && !existsSync(pikaaThreads)) {
|
|
1185
|
+
copyFileSync(legacyThreads, pikaaThreads);
|
|
1186
|
+
}
|
|
1187
|
+
const legacyRules = join(legacyHome, "groupy_rules.db");
|
|
1188
|
+
const pikaaRules = join(pikaaHome, "pikaa_rules.db");
|
|
1189
|
+
if (existsSync(legacyRules) && !existsSync(pikaaRules)) {
|
|
1190
|
+
copyFileSync(legacyRules, pikaaRules);
|
|
1191
|
+
}
|
|
1192
|
+
const legacyGraph = join(legacyHome, "agent_graph.db");
|
|
1193
|
+
const pikaaGraph = join(pikaaHome, "agent_graph.db");
|
|
1194
|
+
if (existsSync(legacyGraph) && !existsSync(pikaaGraph)) {
|
|
1195
|
+
copyFileSync(legacyGraph, pikaaGraph);
|
|
1196
|
+
}
|
|
1197
|
+
const legacyMemories = join(legacyHome, "memories.md");
|
|
1198
|
+
const pikaaMemories = join(pikaaHome, "memories.md");
|
|
1199
|
+
if (existsSync(legacyMemories) && !existsSync(pikaaMemories)) {
|
|
1200
|
+
copyFileSync(legacyMemories, pikaaMemories);
|
|
1201
|
+
}
|
|
1202
|
+
copyDirRecursiveSync(join(legacyHome, "skills"), join(pikaaHome, "skills"));
|
|
1203
|
+
copyDirRecursiveSync(join(legacyHome, "templates"), join(pikaaHome, "templates"));
|
|
1204
|
+
}
|
|
1205
|
+
} catch {}
|
|
1206
|
+
return pikaaHome;
|
|
1207
|
+
}
|
|
1208
|
+
function getCredentialsPath() {
|
|
1209
|
+
ensurePikaaHomeMigrated();
|
|
1210
|
+
return join(getPikaaHomeDir(), "credentials.json");
|
|
1211
|
+
}
|
|
1212
|
+
function getThreadsDbPath() {
|
|
1213
|
+
ensurePikaaHomeMigrated();
|
|
1214
|
+
return join(getPikaaHomeDir(), "pikaa_threads.db");
|
|
1215
|
+
}
|
|
1216
|
+
function getPrefixRulesDbPath() {
|
|
1217
|
+
ensurePikaaHomeMigrated();
|
|
1218
|
+
return join(getPikaaHomeDir(), "pikaa_rules.db");
|
|
1219
|
+
}
|
|
1220
|
+
function getAgentGraphDbPath() {
|
|
1221
|
+
ensurePikaaHomeMigrated();
|
|
1222
|
+
return join(getPikaaHomeDir(), "agent_graph.db");
|
|
1223
|
+
}
|
|
1224
|
+
function getGlobalSkillsDir() {
|
|
1225
|
+
ensurePikaaHomeMigrated();
|
|
1226
|
+
return join(getPikaaHomeDir(), "skills");
|
|
1227
|
+
}
|
|
1228
|
+
function getGlobalTemplatesDir() {
|
|
1229
|
+
ensurePikaaHomeMigrated();
|
|
1230
|
+
return join(getPikaaHomeDir(), "templates");
|
|
1231
|
+
}
|
|
1232
|
+
function getGlobalMemoriesPath() {
|
|
1233
|
+
ensurePikaaHomeMigrated();
|
|
1234
|
+
return join(getPikaaHomeDir(), "memories.md");
|
|
1235
|
+
}
|
|
1236
|
+
function getProjectsDir() {
|
|
1237
|
+
ensurePikaaHomeMigrated();
|
|
1238
|
+
const dir = join(getPikaaHomeDir(), "projects");
|
|
1239
|
+
if (!existsSync(dir)) {
|
|
1240
|
+
try {
|
|
1241
|
+
mkdirSync(dir, { recursive: true });
|
|
1242
|
+
} catch {}
|
|
1243
|
+
}
|
|
1244
|
+
return dir;
|
|
1245
|
+
}
|
|
1125
1246
|
|
|
1247
|
+
// src/auth/store.ts
|
|
1126
1248
|
class CredentialsStore {
|
|
1127
1249
|
filePath;
|
|
1128
1250
|
constructor(customPath) {
|
|
1129
|
-
this.filePath = customPath ||
|
|
1251
|
+
this.filePath = customPath || getCredentialsPath();
|
|
1130
1252
|
}
|
|
1131
1253
|
load() {
|
|
1132
|
-
if (!
|
|
1254
|
+
if (!existsSync2(this.filePath))
|
|
1133
1255
|
return null;
|
|
1134
1256
|
try {
|
|
1135
1257
|
const raw = readFileSync(this.filePath, "utf8");
|
|
@@ -1160,14 +1282,14 @@ class CredentialsStore {
|
|
|
1160
1282
|
return creds?.user;
|
|
1161
1283
|
}
|
|
1162
1284
|
save(credentials) {
|
|
1163
|
-
const dir =
|
|
1164
|
-
if (!
|
|
1165
|
-
|
|
1285
|
+
const dir = resolve2(this.filePath, "..");
|
|
1286
|
+
if (!existsSync2(dir)) {
|
|
1287
|
+
mkdirSync2(dir, { recursive: true });
|
|
1166
1288
|
}
|
|
1167
1289
|
writeFileSync(this.filePath, JSON.stringify(credentials, null, 2), "utf8");
|
|
1168
1290
|
}
|
|
1169
1291
|
clear() {
|
|
1170
|
-
if (
|
|
1292
|
+
if (existsSync2(this.filePath)) {
|
|
1171
1293
|
try {
|
|
1172
1294
|
unlinkSync(this.filePath);
|
|
1173
1295
|
return true;
|
|
@@ -1616,9 +1738,9 @@ class ToolRouter {
|
|
|
1616
1738
|
}
|
|
1617
1739
|
}
|
|
1618
1740
|
// src/tools/handlers/apply-patch.ts
|
|
1619
|
-
import { existsSync as
|
|
1620
|
-
import { resolve as
|
|
1621
|
-
import { mkdirSync as
|
|
1741
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1742
|
+
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
1743
|
+
import { mkdirSync as mkdirSync3 } from "fs";
|
|
1622
1744
|
var applyPatchTool = {
|
|
1623
1745
|
name: "apply_patch",
|
|
1624
1746
|
description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
|
|
@@ -1645,7 +1767,7 @@ var applyPatchTool = {
|
|
|
1645
1767
|
if (!rawPath) {
|
|
1646
1768
|
return { output: "Error: 'path' parameter is required", isError: true };
|
|
1647
1769
|
}
|
|
1648
|
-
const filePath =
|
|
1770
|
+
const filePath = resolve3(ctx.cwd, rawPath);
|
|
1649
1771
|
const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
|
|
1650
1772
|
const replacementContent = String(args.replacementContent ?? "");
|
|
1651
1773
|
if (ctx.execPolicy) {
|
|
@@ -1664,7 +1786,7 @@ var applyPatchTool = {
|
|
|
1664
1786
|
}
|
|
1665
1787
|
}
|
|
1666
1788
|
}
|
|
1667
|
-
if (!
|
|
1789
|
+
if (!existsSync3(filePath)) {
|
|
1668
1790
|
if (targetContent) {
|
|
1669
1791
|
return {
|
|
1670
1792
|
output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.`,
|
|
@@ -1672,7 +1794,7 @@ var applyPatchTool = {
|
|
|
1672
1794
|
};
|
|
1673
1795
|
}
|
|
1674
1796
|
try {
|
|
1675
|
-
|
|
1797
|
+
mkdirSync3(dirname2(filePath), { recursive: true });
|
|
1676
1798
|
writeFileSync2(filePath, replacementContent, "utf8");
|
|
1677
1799
|
return { output: `Successfully created new file '${rawPath}'` };
|
|
1678
1800
|
} catch (err) {
|
|
@@ -1879,7 +2001,7 @@ class WindowsSandbox {
|
|
|
1879
2001
|
}
|
|
1880
2002
|
|
|
1881
2003
|
// src/security/kernel/linux.ts
|
|
1882
|
-
import { existsSync as
|
|
2004
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1883
2005
|
|
|
1884
2006
|
class LinuxSandbox {
|
|
1885
2007
|
hasBwrap = false;
|
|
@@ -1890,7 +2012,7 @@ class LinuxSandbox {
|
|
|
1890
2012
|
if (process.platform !== "linux") {
|
|
1891
2013
|
return;
|
|
1892
2014
|
}
|
|
1893
|
-
this.hasBwrap =
|
|
2015
|
+
this.hasBwrap = existsSync4("/usr/bin/bwrap") || existsSync4("/bin/bwrap") || existsSync4("/usr/local/bin/bwrap");
|
|
1894
2016
|
}
|
|
1895
2017
|
wrapCommand(cmd, profile) {
|
|
1896
2018
|
if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
|
|
@@ -1924,7 +2046,7 @@ class LinuxSandbox {
|
|
|
1924
2046
|
}
|
|
1925
2047
|
|
|
1926
2048
|
// src/security/kernel/macos.ts
|
|
1927
|
-
import { existsSync as
|
|
2049
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1928
2050
|
|
|
1929
2051
|
class MacOSSandbox {
|
|
1930
2052
|
hasSandboxExec = false;
|
|
@@ -1935,7 +2057,7 @@ class MacOSSandbox {
|
|
|
1935
2057
|
if (process.platform !== "darwin") {
|
|
1936
2058
|
return;
|
|
1937
2059
|
}
|
|
1938
|
-
this.hasSandboxExec =
|
|
2060
|
+
this.hasSandboxExec = existsSync5("/usr/bin/sandbox-exec");
|
|
1939
2061
|
}
|
|
1940
2062
|
generateProfile(profile) {
|
|
1941
2063
|
const rules = [
|
|
@@ -1974,7 +2096,7 @@ class MacOSSandbox {
|
|
|
1974
2096
|
}
|
|
1975
2097
|
|
|
1976
2098
|
// src/security/kernel/manager.ts
|
|
1977
|
-
import { resolve as
|
|
2099
|
+
import { resolve as resolve4, normalize } from "path";
|
|
1978
2100
|
|
|
1979
2101
|
class KernelSandboxManager {
|
|
1980
2102
|
windowsSandbox;
|
|
@@ -1996,10 +2118,10 @@ class KernelSandboxManager {
|
|
|
1996
2118
|
};
|
|
1997
2119
|
}
|
|
1998
2120
|
buildDefaultProfile(cwd, allowNetwork = true) {
|
|
1999
|
-
const normCwd = normalize(
|
|
2121
|
+
const normCwd = normalize(resolve4(cwd));
|
|
2000
2122
|
return {
|
|
2001
2123
|
kind: "workspace-write",
|
|
2002
|
-
readableRoots: [normCwd,
|
|
2124
|
+
readableRoots: [normCwd, resolve4(process.cwd())],
|
|
2003
2125
|
writableRoots: [normCwd],
|
|
2004
2126
|
allowNetwork,
|
|
2005
2127
|
limits: {
|
|
@@ -2045,21 +2167,19 @@ var globalKernelSandbox = new KernelSandboxManager;
|
|
|
2045
2167
|
|
|
2046
2168
|
// src/storage/prefix-rules-store.ts
|
|
2047
2169
|
import { Database } from "bun:sqlite";
|
|
2048
|
-
import { existsSync as
|
|
2049
|
-
import { dirname as
|
|
2050
|
-
import { homedir as homedir2 } from "os";
|
|
2051
|
-
|
|
2170
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2171
|
+
import { dirname as dirname3, resolve as resolve5 } from "path";
|
|
2052
2172
|
class PrefixRulesStore {
|
|
2053
2173
|
db;
|
|
2054
2174
|
constructor(dbOrPath) {
|
|
2055
2175
|
if (dbOrPath instanceof Database) {
|
|
2056
2176
|
this.db = dbOrPath;
|
|
2057
2177
|
} else {
|
|
2058
|
-
const effectivePath = dbOrPath ||
|
|
2178
|
+
const effectivePath = dbOrPath || getPrefixRulesDbPath();
|
|
2059
2179
|
if (effectivePath !== ":memory:") {
|
|
2060
|
-
const dir =
|
|
2061
|
-
if (!
|
|
2062
|
-
|
|
2180
|
+
const dir = dirname3(effectivePath);
|
|
2181
|
+
if (!existsSync6(dir)) {
|
|
2182
|
+
mkdirSync4(dir, { recursive: true });
|
|
2063
2183
|
}
|
|
2064
2184
|
}
|
|
2065
2185
|
this.db = new Database(effectivePath);
|
|
@@ -2082,7 +2202,7 @@ class PrefixRulesStore {
|
|
|
2082
2202
|
addRule(workspacePath, prefixTokens) {
|
|
2083
2203
|
if (!prefixTokens || prefixTokens.length === 0)
|
|
2084
2204
|
return;
|
|
2085
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2205
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
|
|
2086
2206
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
2087
2207
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
2088
2208
|
const query = this.db.prepare(`
|
|
@@ -2099,7 +2219,7 @@ class PrefixRulesStore {
|
|
|
2099
2219
|
isApproved(workspacePath, commandTokens) {
|
|
2100
2220
|
if (!commandTokens || commandTokens.length === 0)
|
|
2101
2221
|
return false;
|
|
2102
|
-
const normalizedWs =
|
|
2222
|
+
const normalizedWs = resolve5(workspacePath);
|
|
2103
2223
|
const query = this.db.prepare(`
|
|
2104
2224
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
2105
2225
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -2118,7 +2238,7 @@ class PrefixRulesStore {
|
|
|
2118
2238
|
listRules(workspacePath) {
|
|
2119
2239
|
let rows;
|
|
2120
2240
|
if (workspacePath) {
|
|
2121
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2241
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
|
|
2122
2242
|
const query = this.db.prepare(`
|
|
2123
2243
|
SELECT prefix_tokens FROM approved_prefix_rules
|
|
2124
2244
|
WHERE workspace_path = $ws OR workspace_path = '*'
|
|
@@ -2137,7 +2257,7 @@ class PrefixRulesStore {
|
|
|
2137
2257
|
}).filter((r) => r.length > 0);
|
|
2138
2258
|
}
|
|
2139
2259
|
removeRule(workspacePath, prefixTokens) {
|
|
2140
|
-
const normalizedWs = workspacePath === "*" ? "*" :
|
|
2260
|
+
const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
|
|
2141
2261
|
const tokensJson = JSON.stringify(prefixTokens);
|
|
2142
2262
|
const id = `${normalizedWs}:${tokensJson}`;
|
|
2143
2263
|
const query = this.db.prepare(`
|
|
@@ -2270,7 +2390,7 @@ function createShellTool(policy = new ExecPolicy) {
|
|
|
2270
2390
|
} catch {}
|
|
2271
2391
|
});
|
|
2272
2392
|
}
|
|
2273
|
-
const timeoutPromise = new Promise((
|
|
2393
|
+
const timeoutPromise = new Promise((resolve6) => setTimeout(() => resolve6({ isTimeout: true }), timeoutMs));
|
|
2274
2394
|
const result = await Promise.race([
|
|
2275
2395
|
proc.exited.then(async (code) => {
|
|
2276
2396
|
const stdout = await new Response(proc.stdout).text();
|
|
@@ -2314,8 +2434,8 @@ ${result.stderr.trim()}`);
|
|
|
2314
2434
|
}
|
|
2315
2435
|
var shellTool = createShellTool();
|
|
2316
2436
|
// src/tools/handlers/file-ops.ts
|
|
2317
|
-
import { readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as
|
|
2318
|
-
import { resolve as
|
|
2437
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync7, statSync as statSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
2438
|
+
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
2319
2439
|
var readFileTool = {
|
|
2320
2440
|
name: "read_file",
|
|
2321
2441
|
description: "Read the full text content of a file.",
|
|
@@ -2327,8 +2447,8 @@ var readFileTool = {
|
|
|
2327
2447
|
required: ["path"]
|
|
2328
2448
|
},
|
|
2329
2449
|
async execute(args, ctx) {
|
|
2330
|
-
const filePath =
|
|
2331
|
-
if (!
|
|
2450
|
+
const filePath = resolve6(ctx.cwd, String(args.path || ""));
|
|
2451
|
+
if (!existsSync7(filePath)) {
|
|
2332
2452
|
return { output: `Error: File not found: '${args.path}'`, isError: true };
|
|
2333
2453
|
}
|
|
2334
2454
|
try {
|
|
@@ -2349,15 +2469,15 @@ var listDirTool = {
|
|
|
2349
2469
|
}
|
|
2350
2470
|
},
|
|
2351
2471
|
async execute(args, ctx) {
|
|
2352
|
-
const dirPath =
|
|
2353
|
-
if (!
|
|
2472
|
+
const dirPath = resolve6(ctx.cwd, String(args.path || "."));
|
|
2473
|
+
if (!existsSync7(dirPath)) {
|
|
2354
2474
|
return { output: `Error: Directory not found: '${args.path}'`, isError: true };
|
|
2355
2475
|
}
|
|
2356
2476
|
try {
|
|
2357
|
-
const entries =
|
|
2477
|
+
const entries = readdirSync2(dirPath);
|
|
2358
2478
|
const formatted = entries.map((entry) => {
|
|
2359
|
-
const full =
|
|
2360
|
-
const isDir =
|
|
2479
|
+
const full = resolve6(dirPath, entry);
|
|
2480
|
+
const isDir = statSync2(full).isDirectory();
|
|
2361
2481
|
return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
|
|
2362
2482
|
});
|
|
2363
2483
|
return { output: formatted.join(`
|
|
@@ -2380,7 +2500,7 @@ var writeFileTool = {
|
|
|
2380
2500
|
},
|
|
2381
2501
|
async execute(args, ctx) {
|
|
2382
2502
|
const rawPath = String(args.path || "");
|
|
2383
|
-
const filePath =
|
|
2503
|
+
const filePath = resolve6(ctx.cwd, rawPath);
|
|
2384
2504
|
if (ctx.execPolicy) {
|
|
2385
2505
|
const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
|
|
2386
2506
|
if (evalResult.isPlanBlocked || ctx.mode === "plan") {
|
|
@@ -2398,7 +2518,7 @@ var writeFileTool = {
|
|
|
2398
2518
|
}
|
|
2399
2519
|
}
|
|
2400
2520
|
try {
|
|
2401
|
-
|
|
2521
|
+
mkdirSync5(dirname4(filePath), { recursive: true });
|
|
2402
2522
|
writeFileSync3(filePath, String(args.content ?? ""), "utf8");
|
|
2403
2523
|
return { output: `Successfully wrote to '${args.path}'` };
|
|
2404
2524
|
} catch (err) {
|
|
@@ -2521,8 +2641,8 @@ var updatePlanTool = {
|
|
|
2521
2641
|
}
|
|
2522
2642
|
};
|
|
2523
2643
|
// src/search/engine.ts
|
|
2524
|
-
import { readdirSync as
|
|
2525
|
-
import { resolve as
|
|
2644
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3, existsSync as existsSync8 } from "fs";
|
|
2645
|
+
import { resolve as resolve7, relative, join as join2, extname } from "path";
|
|
2526
2646
|
var DEFAULT_IGNORE_DIRS = new Set([
|
|
2527
2647
|
".git",
|
|
2528
2648
|
"node_modules",
|
|
@@ -2567,8 +2687,8 @@ var BINARY_EXTENSIONS = new Set([
|
|
|
2567
2687
|
|
|
2568
2688
|
class FileSearchEngine {
|
|
2569
2689
|
grep(cwd, options) {
|
|
2570
|
-
const searchRoot =
|
|
2571
|
-
if (!
|
|
2690
|
+
const searchRoot = resolve7(cwd, options.path || ".");
|
|
2691
|
+
if (!existsSync8(searchRoot)) {
|
|
2572
2692
|
return { matches: [], totalMatches: 0, truncated: false };
|
|
2573
2693
|
}
|
|
2574
2694
|
const maxResults = options.maxResults || 50;
|
|
@@ -2617,8 +2737,8 @@ class FileSearchEngine {
|
|
|
2617
2737
|
return { matches, totalMatches, truncated };
|
|
2618
2738
|
}
|
|
2619
2739
|
findFiles(cwd, options) {
|
|
2620
|
-
const searchRoot =
|
|
2621
|
-
if (!
|
|
2740
|
+
const searchRoot = resolve7(cwd, options.path || ".");
|
|
2741
|
+
if (!existsSync8(searchRoot))
|
|
2622
2742
|
return [];
|
|
2623
2743
|
const maxResults = options.maxResults || 100;
|
|
2624
2744
|
const gitignoreRules = this.loadGitignoreRules(searchRoot);
|
|
@@ -2657,8 +2777,8 @@ class FileSearchEngine {
|
|
|
2657
2777
|
}
|
|
2658
2778
|
loadGitignoreRules(root) {
|
|
2659
2779
|
const rules = new Set;
|
|
2660
|
-
const gitignorePath =
|
|
2661
|
-
if (
|
|
2780
|
+
const gitignorePath = join2(root, ".gitignore");
|
|
2781
|
+
if (existsSync8(gitignorePath)) {
|
|
2662
2782
|
try {
|
|
2663
2783
|
const lines = readFileSync4(gitignorePath, "utf8").split(`
|
|
2664
2784
|
`);
|
|
@@ -2675,7 +2795,7 @@ class FileSearchEngine {
|
|
|
2675
2795
|
collectFiles(dir, root, gitignoreRules, includePattern) {
|
|
2676
2796
|
const results = [];
|
|
2677
2797
|
try {
|
|
2678
|
-
const stat =
|
|
2798
|
+
const stat = statSync3(dir);
|
|
2679
2799
|
if (!stat.isDirectory()) {
|
|
2680
2800
|
if (!this.isBinary(dir)) {
|
|
2681
2801
|
results.push(dir);
|
|
@@ -2689,9 +2809,9 @@ class FileSearchEngine {
|
|
|
2689
2809
|
while (queue.length > 0) {
|
|
2690
2810
|
const currentDir = queue.shift();
|
|
2691
2811
|
try {
|
|
2692
|
-
const entries =
|
|
2812
|
+
const entries = readdirSync3(currentDir, { withFileTypes: true });
|
|
2693
2813
|
for (const entry of entries) {
|
|
2694
|
-
const fullPath =
|
|
2814
|
+
const fullPath = join2(currentDir, entry.name);
|
|
2695
2815
|
const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
|
|
2696
2816
|
if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
|
|
2697
2817
|
continue;
|
|
@@ -3088,6 +3208,111 @@ ${loaded.instructions}`
|
|
|
3088
3208
|
}
|
|
3089
3209
|
|
|
3090
3210
|
// src/memories/tool.ts
|
|
3211
|
+
function createSaveMemoryTool(store) {
|
|
3212
|
+
return {
|
|
3213
|
+
name: "save_memory",
|
|
3214
|
+
description: "Save a persistent memory note to the project's Auto-Memory bank. Categories: 'user' (role, workflow style, tooling preferences), 'feedback' (user corrections, guidelines), 'project' (external context, environments, deadlines), 'reference' (links, issue trackers, dashboards). Do NOT save facts easily discovered in code or git history.",
|
|
3215
|
+
parameters: {
|
|
3216
|
+
type: "object",
|
|
3217
|
+
properties: {
|
|
3218
|
+
category: {
|
|
3219
|
+
type: "string",
|
|
3220
|
+
description: "Category of memory: 'user', 'feedback', 'project', or 'reference'.",
|
|
3221
|
+
enum: ["user", "feedback", "project", "reference"]
|
|
3222
|
+
},
|
|
3223
|
+
name: {
|
|
3224
|
+
type: "string",
|
|
3225
|
+
description: "Short, descriptive snake_case identifier for this memory topic (e.g. 'testing_strategy', 'preferred_framework', 'staging_api')."
|
|
3226
|
+
},
|
|
3227
|
+
description: {
|
|
3228
|
+
type: "string",
|
|
3229
|
+
description: "One-line summary to display in the MEMORY.md index (e.g. 'Prefers Vitest without database mocks')."
|
|
3230
|
+
},
|
|
3231
|
+
content: {
|
|
3232
|
+
type: "string",
|
|
3233
|
+
description: "Detailed description of the fact, preference, or learned correction."
|
|
3234
|
+
}
|
|
3235
|
+
},
|
|
3236
|
+
required: ["category", "name", "content"]
|
|
3237
|
+
},
|
|
3238
|
+
async execute(args, context) {
|
|
3239
|
+
const category = args.category || "project";
|
|
3240
|
+
const name = String(args.name || `topic_${Date.now()}`);
|
|
3241
|
+
const content = String(args.content || "").trim();
|
|
3242
|
+
const description = args.description ? String(args.description).trim() : undefined;
|
|
3243
|
+
if (!content) {
|
|
3244
|
+
return { output: "Error: memory content cannot be empty", isError: true };
|
|
3245
|
+
}
|
|
3246
|
+
const entry = store.saveTopicMemory({
|
|
3247
|
+
category,
|
|
3248
|
+
name,
|
|
3249
|
+
description,
|
|
3250
|
+
content,
|
|
3251
|
+
cwd: context.cwd
|
|
3252
|
+
});
|
|
3253
|
+
return {
|
|
3254
|
+
output: `\u2713 Saved Auto-Memory topic: [${entry.category}] "${entry.name}" -> ${entry.filePath}`
|
|
3255
|
+
};
|
|
3256
|
+
}
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3259
|
+
function createReadMemoryTool(store) {
|
|
3260
|
+
return {
|
|
3261
|
+
name: "read_memory",
|
|
3262
|
+
description: "Read the full details of a specific Auto-Memory topic file recorded in the project's memory index.",
|
|
3263
|
+
parameters: {
|
|
3264
|
+
type: "object",
|
|
3265
|
+
properties: {
|
|
3266
|
+
topic: {
|
|
3267
|
+
type: "string",
|
|
3268
|
+
description: "Name or filename of the memory topic to read (e.g. 'testing_strategy' or 'feedback_testing.md')."
|
|
3269
|
+
}
|
|
3270
|
+
},
|
|
3271
|
+
required: ["topic"]
|
|
3272
|
+
},
|
|
3273
|
+
async execute(args, context) {
|
|
3274
|
+
const topic = String(args.topic || "").trim();
|
|
3275
|
+
if (!topic) {
|
|
3276
|
+
return { output: "Error: topic name is required", isError: true };
|
|
3277
|
+
}
|
|
3278
|
+
const memory = store.readTopicMemory(topic, context.cwd);
|
|
3279
|
+
if (!memory) {
|
|
3280
|
+
return {
|
|
3281
|
+
output: `No memory topic found matching '${topic}' in this project.`,
|
|
3282
|
+
isError: true
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
return {
|
|
3286
|
+
output: `# Topic: ${memory.name} (${memory.type})
|
|
3287
|
+
Modified: ${memory.modified}
|
|
3288
|
+
|
|
3289
|
+
${memory.content}`
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
3294
|
+
function createListMemoriesTool(store) {
|
|
3295
|
+
return {
|
|
3296
|
+
name: "list_memories",
|
|
3297
|
+
description: "List all persistent memory topics and index for the current project repository.",
|
|
3298
|
+
parameters: {
|
|
3299
|
+
type: "object",
|
|
3300
|
+
properties: {}
|
|
3301
|
+
},
|
|
3302
|
+
async execute(_args, context) {
|
|
3303
|
+
const topics = store.listProjectMemories(context.cwd);
|
|
3304
|
+
if (topics.length === 0) {
|
|
3305
|
+
return { output: "No persistent Auto-Memories have been recorded for this project yet." };
|
|
3306
|
+
}
|
|
3307
|
+
const lines = topics.map((t) => `\u2022 [${t.type}] **${t.name}**: ${t.description || t.content.split(`
|
|
3308
|
+
`)[0]} (file: ${t.filePath})`);
|
|
3309
|
+
return { output: `Project Auto-Memories (${topics.length} topics):
|
|
3310
|
+
|
|
3311
|
+
${lines.join(`
|
|
3312
|
+
`)}` };
|
|
3313
|
+
}
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3091
3316
|
function createRememberTool(store) {
|
|
3092
3317
|
return {
|
|
3093
3318
|
name: "remember",
|
|
@@ -3098,16 +3323,15 @@ function createRememberTool(store) {
|
|
|
3098
3323
|
category: {
|
|
3099
3324
|
type: "string",
|
|
3100
3325
|
description: "Category of the memory.",
|
|
3101
|
-
enum: ["preference", "guideline", "architecture", "note"]
|
|
3326
|
+
enum: ["preference", "guideline", "architecture", "note", "user", "feedback", "project", "reference"]
|
|
3102
3327
|
},
|
|
3103
3328
|
content: {
|
|
3104
3329
|
type: "string",
|
|
3105
3330
|
description: "The concise rule, preference, or fact to remember permanently."
|
|
3106
3331
|
},
|
|
3107
|
-
|
|
3332
|
+
name: {
|
|
3108
3333
|
type: "string",
|
|
3109
|
-
description: "
|
|
3110
|
-
enum: ["global", "workspace"]
|
|
3334
|
+
description: "Optional topic name."
|
|
3111
3335
|
}
|
|
3112
3336
|
},
|
|
3113
3337
|
required: ["category", "content"]
|
|
@@ -3115,22 +3339,30 @@ function createRememberTool(store) {
|
|
|
3115
3339
|
async execute(args, context) {
|
|
3116
3340
|
const category = args.category || "preference";
|
|
3117
3341
|
const content = String(args.content || "");
|
|
3118
|
-
const
|
|
3342
|
+
const name = args.name ? String(args.name) : undefined;
|
|
3119
3343
|
if (!content) {
|
|
3120
3344
|
return { output: "Error: memory content cannot be empty", isError: true };
|
|
3121
3345
|
}
|
|
3122
3346
|
const entry = store.addMemory({
|
|
3123
3347
|
category,
|
|
3124
3348
|
content,
|
|
3125
|
-
|
|
3349
|
+
name,
|
|
3126
3350
|
cwd: context.cwd
|
|
3127
3351
|
});
|
|
3128
3352
|
return {
|
|
3129
|
-
output: `Successfully saved to
|
|
3353
|
+
output: `Successfully saved to Auto-Memory bank: [${entry.category}] "${entry.name || entry.content}"`
|
|
3130
3354
|
};
|
|
3131
3355
|
}
|
|
3132
3356
|
};
|
|
3133
3357
|
}
|
|
3358
|
+
function createAutoMemoryTools(store) {
|
|
3359
|
+
return [
|
|
3360
|
+
createSaveMemoryTool(store),
|
|
3361
|
+
createReadMemoryTool(store),
|
|
3362
|
+
createListMemoriesTool(store),
|
|
3363
|
+
createRememberTool(store)
|
|
3364
|
+
];
|
|
3365
|
+
}
|
|
3134
3366
|
|
|
3135
3367
|
// src/worktree/tools.ts
|
|
3136
3368
|
function createWorktreeTools(manager) {
|
|
@@ -3265,7 +3497,9 @@ function createDefaultTools(options = {}) {
|
|
|
3265
3497
|
router2.register(createSkillTool(options.skillsLoader));
|
|
3266
3498
|
}
|
|
3267
3499
|
if (options.memoryStore) {
|
|
3268
|
-
|
|
3500
|
+
for (const tool of createAutoMemoryTools(options.memoryStore)) {
|
|
3501
|
+
router2.register(tool);
|
|
3502
|
+
}
|
|
3269
3503
|
}
|
|
3270
3504
|
if (options.worktreeManager) {
|
|
3271
3505
|
for (const tool of createWorktreeTools(options.worktreeManager)) {
|
|
@@ -3275,21 +3509,21 @@ function createDefaultTools(options = {}) {
|
|
|
3275
3509
|
return router2;
|
|
3276
3510
|
}
|
|
3277
3511
|
// src/security/sandbox.ts
|
|
3278
|
-
import { resolve as
|
|
3512
|
+
import { resolve as resolve8, normalize as normalize2, relative as relative2 } from "path";
|
|
3279
3513
|
class PathSandbox {
|
|
3280
3514
|
allowedRoots;
|
|
3281
3515
|
constructor(workspaceRoots) {
|
|
3282
|
-
this.allowedRoots = workspaceRoots.map((root) => normalize2(
|
|
3516
|
+
this.allowedRoots = workspaceRoots.map((root) => normalize2(resolve8(root)));
|
|
3283
3517
|
}
|
|
3284
3518
|
isPathAllowed(targetPath) {
|
|
3285
|
-
const normalized = normalize2(
|
|
3519
|
+
const normalized = normalize2(resolve8(targetPath));
|
|
3286
3520
|
return this.allowedRoots.some((root) => {
|
|
3287
3521
|
const rel = relative2(root, normalized);
|
|
3288
|
-
return !rel.startsWith("..") && !
|
|
3522
|
+
return !rel.startsWith("..") && !resolve8(root, rel).startsWith("..");
|
|
3289
3523
|
});
|
|
3290
3524
|
}
|
|
3291
3525
|
assertPathAllowed(targetPath) {
|
|
3292
|
-
const normalized = normalize2(
|
|
3526
|
+
const normalized = normalize2(resolve8(targetPath));
|
|
3293
3527
|
if (!this.isPathAllowed(normalized)) {
|
|
3294
3528
|
throw new ExecutionPolicyError(`Path access denied: '${targetPath}' is outside the allowed workspace boundaries (${this.allowedRoots.join(", ")})`);
|
|
3295
3529
|
}
|
|
@@ -3297,8 +3531,8 @@ class PathSandbox {
|
|
|
3297
3531
|
}
|
|
3298
3532
|
}
|
|
3299
3533
|
// src/security/scanner.ts
|
|
3300
|
-
import { existsSync as
|
|
3301
|
-
import { join as
|
|
3534
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
|
|
3535
|
+
import { join as join3, relative as relative3, resolve as resolve9 } from "path";
|
|
3302
3536
|
var SECURITY_RULES = [
|
|
3303
3537
|
{
|
|
3304
3538
|
id: "SEC-001",
|
|
@@ -3430,27 +3664,27 @@ var IGNORED_FILES = new Set([
|
|
|
3430
3664
|
]);
|
|
3431
3665
|
async function runSecurityScan(targetDir, options = {}) {
|
|
3432
3666
|
const startTime = performance.now();
|
|
3433
|
-
const root =
|
|
3667
|
+
const root = resolve9(targetDir);
|
|
3434
3668
|
const isDirectTestDir = targetDir.includes("test") || Boolean(options.includeTests);
|
|
3435
3669
|
const maxFiles = options.maxFiles || 2000;
|
|
3436
3670
|
const findings = [];
|
|
3437
3671
|
let scannedCount = 0;
|
|
3438
3672
|
function walk(current) {
|
|
3439
|
-
if (scannedCount >= maxFiles || !
|
|
3673
|
+
if (scannedCount >= maxFiles || !existsSync9(current))
|
|
3440
3674
|
return;
|
|
3441
3675
|
let entries;
|
|
3442
3676
|
try {
|
|
3443
|
-
entries =
|
|
3677
|
+
entries = readdirSync4(current);
|
|
3444
3678
|
} catch {
|
|
3445
3679
|
return;
|
|
3446
3680
|
}
|
|
3447
3681
|
for (const entry of entries) {
|
|
3448
3682
|
if (scannedCount >= maxFiles)
|
|
3449
3683
|
break;
|
|
3450
|
-
const fullPath =
|
|
3684
|
+
const fullPath = join3(current, entry);
|
|
3451
3685
|
let stat;
|
|
3452
3686
|
try {
|
|
3453
|
-
stat =
|
|
3687
|
+
stat = statSync4(fullPath);
|
|
3454
3688
|
} catch {
|
|
3455
3689
|
continue;
|
|
3456
3690
|
}
|
|
@@ -3571,14 +3805,13 @@ function formatWorldStatePrompt(state) {
|
|
|
3571
3805
|
`);
|
|
3572
3806
|
}
|
|
3573
3807
|
// src/prompts/loader.ts
|
|
3574
|
-
import { existsSync as
|
|
3575
|
-
import { resolve as
|
|
3576
|
-
import { homedir as
|
|
3577
|
-
|
|
3808
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
3809
|
+
import { resolve as resolve10, join as join4 } from "path";
|
|
3810
|
+
import { homedir as homedir2 } from "os";
|
|
3578
3811
|
class PromptTemplateLoader {
|
|
3579
3812
|
builtInTemplatesDir;
|
|
3580
3813
|
constructor(builtInDir) {
|
|
3581
|
-
this.builtInTemplatesDir = builtInDir ||
|
|
3814
|
+
this.builtInTemplatesDir = builtInDir || resolve10(join4(import.meta.dir, "..", "..", "templates"));
|
|
3582
3815
|
}
|
|
3583
3816
|
loadTemplate(relativePath, variables = {}, cwd) {
|
|
3584
3817
|
const rawContent = this.resolveTemplateContent(relativePath, cwd);
|
|
@@ -3587,21 +3820,27 @@ class PromptTemplateLoader {
|
|
|
3587
3820
|
resolveTemplateContent(relativePath, cwd) {
|
|
3588
3821
|
const normalizedRel = relativePath.replace(/^\/+/, "");
|
|
3589
3822
|
if (cwd) {
|
|
3590
|
-
const workspacePath =
|
|
3591
|
-
if (
|
|
3823
|
+
const workspacePath = join4(cwd, ".agents", "templates", normalizedRel);
|
|
3824
|
+
if (existsSync10(workspacePath)) {
|
|
3592
3825
|
try {
|
|
3593
3826
|
return readFileSync6(workspacePath, "utf-8");
|
|
3594
3827
|
} catch {}
|
|
3595
3828
|
}
|
|
3596
3829
|
}
|
|
3597
|
-
const globalPath =
|
|
3598
|
-
if (
|
|
3830
|
+
const globalPath = join4(getGlobalTemplatesDir(), normalizedRel);
|
|
3831
|
+
if (existsSync10(globalPath)) {
|
|
3599
3832
|
try {
|
|
3600
3833
|
return readFileSync6(globalPath, "utf-8");
|
|
3601
3834
|
} catch {}
|
|
3602
3835
|
}
|
|
3603
|
-
const
|
|
3604
|
-
if (
|
|
3836
|
+
const legacyGlobalPath = join4(homedir2(), ".groupy", "templates", normalizedRel);
|
|
3837
|
+
if (existsSync10(legacyGlobalPath)) {
|
|
3838
|
+
try {
|
|
3839
|
+
return readFileSync6(legacyGlobalPath, "utf-8");
|
|
3840
|
+
} catch {}
|
|
3841
|
+
}
|
|
3842
|
+
const builtInPath = join4(this.builtInTemplatesDir, normalizedRel);
|
|
3843
|
+
if (existsSync10(builtInPath)) {
|
|
3605
3844
|
try {
|
|
3606
3845
|
return readFileSync6(builtInPath, "utf-8");
|
|
3607
3846
|
} catch {}
|
|
@@ -3618,8 +3857,8 @@ class PromptTemplateLoader {
|
|
|
3618
3857
|
var globalPromptLoader = new PromptTemplateLoader;
|
|
3619
3858
|
|
|
3620
3859
|
// src/prompts/agents-md.ts
|
|
3621
|
-
import { existsSync as
|
|
3622
|
-
import { resolve as
|
|
3860
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
3861
|
+
import { resolve as resolve11, join as join5, dirname as dirname5 } from "path";
|
|
3623
3862
|
var DEFAULT_AGENTS_MD_FILENAMES = [
|
|
3624
3863
|
"AGENTS.override.md",
|
|
3625
3864
|
"AGENTS.md",
|
|
@@ -3634,28 +3873,28 @@ var AGENTS_MD_SEPARATOR = `
|
|
|
3634
3873
|
|
|
3635
3874
|
class AgentsMdLoader {
|
|
3636
3875
|
findProjectRoot(startDir) {
|
|
3637
|
-
let current =
|
|
3876
|
+
let current = resolve11(startDir);
|
|
3638
3877
|
while (true) {
|
|
3639
|
-
if (
|
|
3878
|
+
if (existsSync11(join5(current, ".git"))) {
|
|
3640
3879
|
return current;
|
|
3641
3880
|
}
|
|
3642
|
-
const parent =
|
|
3881
|
+
const parent = dirname5(current);
|
|
3643
3882
|
if (parent === current) {
|
|
3644
|
-
return
|
|
3883
|
+
return resolve11(startDir);
|
|
3645
3884
|
}
|
|
3646
3885
|
current = parent;
|
|
3647
3886
|
}
|
|
3648
3887
|
}
|
|
3649
3888
|
collectDirectoryHierarchy(targetDir, rootDir) {
|
|
3650
3889
|
const hierarchy = [];
|
|
3651
|
-
let current =
|
|
3652
|
-
const normalizedRoot =
|
|
3890
|
+
let current = resolve11(targetDir);
|
|
3891
|
+
const normalizedRoot = resolve11(rootDir);
|
|
3653
3892
|
while (true) {
|
|
3654
3893
|
hierarchy.unshift(current);
|
|
3655
3894
|
if (current === normalizedRoot) {
|
|
3656
3895
|
break;
|
|
3657
3896
|
}
|
|
3658
|
-
const parent =
|
|
3897
|
+
const parent = dirname5(current);
|
|
3659
3898
|
if (parent === current) {
|
|
3660
3899
|
break;
|
|
3661
3900
|
}
|
|
@@ -3670,8 +3909,8 @@ class AgentsMdLoader {
|
|
|
3670
3909
|
const sourcePaths = [];
|
|
3671
3910
|
for (const dir of dirHierarchy) {
|
|
3672
3911
|
for (const filename of fallbackFilenames) {
|
|
3673
|
-
const filePath =
|
|
3674
|
-
if (
|
|
3912
|
+
const filePath = join5(dir, filename);
|
|
3913
|
+
if (existsSync11(filePath)) {
|
|
3675
3914
|
try {
|
|
3676
3915
|
const content = readFileSync7(filePath, "utf-8").trim();
|
|
3677
3916
|
if (content) {
|
|
@@ -4260,13 +4499,13 @@ class Session {
|
|
|
4260
4499
|
type: "StatusChanged",
|
|
4261
4500
|
status: "waiting_approval"
|
|
4262
4501
|
});
|
|
4263
|
-
return new Promise((
|
|
4502
|
+
return new Promise((resolve12) => {
|
|
4264
4503
|
this.pendingApprovals.set(params.approvalId, (approved) => {
|
|
4265
4504
|
this.emitEvent({
|
|
4266
4505
|
type: "StatusChanged",
|
|
4267
4506
|
status: "running"
|
|
4268
4507
|
});
|
|
4269
|
-
|
|
4508
|
+
resolve12(approved);
|
|
4270
4509
|
});
|
|
4271
4510
|
});
|
|
4272
4511
|
}
|
|
@@ -4289,13 +4528,13 @@ class Session {
|
|
|
4289
4528
|
type: "StatusChanged",
|
|
4290
4529
|
status: "waiting_user_input"
|
|
4291
4530
|
});
|
|
4292
|
-
return new Promise((
|
|
4531
|
+
return new Promise((resolve12) => {
|
|
4293
4532
|
this.pendingUserQuestions.set(params.questionId, (answer) => {
|
|
4294
4533
|
this.emitEvent({
|
|
4295
4534
|
type: "StatusChanged",
|
|
4296
4535
|
status: "running"
|
|
4297
4536
|
});
|
|
4298
|
-
|
|
4537
|
+
resolve12(answer);
|
|
4299
4538
|
});
|
|
4300
4539
|
});
|
|
4301
4540
|
}
|
|
@@ -4325,7 +4564,7 @@ class Session {
|
|
|
4325
4564
|
return handleTurnInput(this, { text, images });
|
|
4326
4565
|
}
|
|
4327
4566
|
async promptAndWait(text, images, timeoutMs = 30000) {
|
|
4328
|
-
return new Promise((
|
|
4567
|
+
return new Promise((resolve12, reject) => {
|
|
4329
4568
|
const timer = setTimeout(() => {
|
|
4330
4569
|
unsub();
|
|
4331
4570
|
reject(new Error(`Turn timed out after ${timeoutMs}ms`));
|
|
@@ -4334,7 +4573,7 @@ class Session {
|
|
|
4334
4573
|
if (event.msg.type === "TurnCompleted") {
|
|
4335
4574
|
clearTimeout(timer);
|
|
4336
4575
|
unsub();
|
|
4337
|
-
|
|
4576
|
+
resolve12();
|
|
4338
4577
|
} else if (event.msg.type === "Error") {
|
|
4339
4578
|
clearTimeout(timer);
|
|
4340
4579
|
unsub();
|
|
@@ -4353,8 +4592,8 @@ class Session {
|
|
|
4353
4592
|
if (this.submissionQueue.length > 0) {
|
|
4354
4593
|
yield this.submissionQueue.shift();
|
|
4355
4594
|
} else {
|
|
4356
|
-
const nextSub = await new Promise((
|
|
4357
|
-
this.submissionResolvers.push(
|
|
4595
|
+
const nextSub = await new Promise((resolve12) => {
|
|
4596
|
+
this.submissionResolvers.push(resolve12);
|
|
4358
4597
|
});
|
|
4359
4598
|
yield nextSub;
|
|
4360
4599
|
}
|
|
@@ -4618,13 +4857,13 @@ class StdioTransport {
|
|
|
4618
4857
|
if (this.isClosed || !this.proc || !this.proc.stdin) {
|
|
4619
4858
|
throw new GroupyError("MCP Stdio transport is closed");
|
|
4620
4859
|
}
|
|
4621
|
-
return new Promise((
|
|
4860
|
+
return new Promise((resolve12, reject) => {
|
|
4622
4861
|
const timeoutMs = 30000;
|
|
4623
4862
|
const timer = setTimeout(() => {
|
|
4624
4863
|
this.pendingRequests.delete(request.id);
|
|
4625
4864
|
reject(new GroupyError(`MCP request timed out after ${timeoutMs}ms (method: ${request.method})`));
|
|
4626
4865
|
}, timeoutMs);
|
|
4627
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
4866
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4628
4867
|
try {
|
|
4629
4868
|
const payload = JSON.stringify(request) + `
|
|
4630
4869
|
`;
|
|
@@ -4757,12 +4996,12 @@ class SseTransport {
|
|
|
4757
4996
|
if (!this.messageUrl) {
|
|
4758
4997
|
this.messageUrl = this.endpointUrl;
|
|
4759
4998
|
}
|
|
4760
|
-
return new Promise((
|
|
4999
|
+
return new Promise((resolve12, reject) => {
|
|
4761
5000
|
const timer = setTimeout(() => {
|
|
4762
5001
|
this.pendingRequests.delete(request.id);
|
|
4763
5002
|
reject(new GroupyError(`MCP SSE request timed out (method: ${request.method})`));
|
|
4764
5003
|
}, 30000);
|
|
4765
|
-
this.pendingRequests.set(request.id, { resolve:
|
|
5004
|
+
this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
|
|
4766
5005
|
fetch(this.messageUrl, {
|
|
4767
5006
|
method: "POST",
|
|
4768
5007
|
headers: {
|
|
@@ -4999,8 +5238,8 @@ class McpClient {
|
|
|
4999
5238
|
}
|
|
5000
5239
|
}
|
|
5001
5240
|
// src/mcp/manager.ts
|
|
5002
|
-
import { existsSync as
|
|
5003
|
-
import { resolve as
|
|
5241
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
|
|
5242
|
+
import { resolve as resolve12, dirname as dirname6, join as join6 } from "path";
|
|
5004
5243
|
class McpManager {
|
|
5005
5244
|
clients = new Map;
|
|
5006
5245
|
serverConfigs = new Map;
|
|
@@ -5034,8 +5273,8 @@ class McpManager {
|
|
|
5034
5273
|
}
|
|
5035
5274
|
}
|
|
5036
5275
|
async loadConfigFile(filePath) {
|
|
5037
|
-
const fullPath =
|
|
5038
|
-
if (!
|
|
5276
|
+
const fullPath = resolve12(filePath);
|
|
5277
|
+
if (!existsSync12(fullPath))
|
|
5039
5278
|
return;
|
|
5040
5279
|
this.loadedConfigFiles.add(fullPath);
|
|
5041
5280
|
try {
|
|
@@ -5288,13 +5527,13 @@ class McpManager {
|
|
|
5288
5527
|
`);
|
|
5289
5528
|
}
|
|
5290
5529
|
saveServerToConfigFile(filePath, name, config) {
|
|
5291
|
-
const fullPath =
|
|
5292
|
-
const dir =
|
|
5293
|
-
if (!
|
|
5294
|
-
|
|
5530
|
+
const fullPath = resolve12(filePath);
|
|
5531
|
+
const dir = dirname6(fullPath);
|
|
5532
|
+
if (!existsSync12(dir)) {
|
|
5533
|
+
mkdirSync6(dir, { recursive: true });
|
|
5295
5534
|
}
|
|
5296
5535
|
let existing = { mcpServers: {} };
|
|
5297
|
-
if (
|
|
5536
|
+
if (existsSync12(fullPath)) {
|
|
5298
5537
|
try {
|
|
5299
5538
|
const content = readFileSync8(fullPath, "utf8");
|
|
5300
5539
|
existing = JSON.parse(content);
|
|
@@ -5308,8 +5547,8 @@ class McpManager {
|
|
|
5308
5547
|
this.loadedConfigFiles.add(fullPath);
|
|
5309
5548
|
}
|
|
5310
5549
|
removeServerFromConfigFile(filePath, name) {
|
|
5311
|
-
const fullPath =
|
|
5312
|
-
if (!
|
|
5550
|
+
const fullPath = resolve12(filePath);
|
|
5551
|
+
if (!existsSync12(fullPath))
|
|
5313
5552
|
return false;
|
|
5314
5553
|
try {
|
|
5315
5554
|
const content = readFileSync8(fullPath, "utf8");
|
|
@@ -5339,11 +5578,11 @@ class McpManager {
|
|
|
5339
5578
|
}
|
|
5340
5579
|
}
|
|
5341
5580
|
getDefaultConfigFile(cwd = process.cwd()) {
|
|
5342
|
-
const workspaceConfig =
|
|
5343
|
-
if (
|
|
5581
|
+
const workspaceConfig = join6(cwd, ".mcp.json");
|
|
5582
|
+
if (existsSync12(workspaceConfig))
|
|
5344
5583
|
return workspaceConfig;
|
|
5345
|
-
const altConfig =
|
|
5346
|
-
if (
|
|
5584
|
+
const altConfig = join6(cwd, "mcp_config.json");
|
|
5585
|
+
if (existsSync12(altConfig))
|
|
5347
5586
|
return altConfig;
|
|
5348
5587
|
return workspaceConfig;
|
|
5349
5588
|
}
|
|
@@ -5362,11 +5601,11 @@ class McpManager {
|
|
|
5362
5601
|
}
|
|
5363
5602
|
}
|
|
5364
5603
|
// src/mcp/servers/chrome-devtools/index.ts
|
|
5365
|
-
import { resolve as
|
|
5604
|
+
import { resolve as resolve14 } from "path";
|
|
5366
5605
|
|
|
5367
5606
|
// src/mcp/servers/chrome-devtools/launcher.ts
|
|
5368
|
-
import { existsSync as
|
|
5369
|
-
import { join as
|
|
5607
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync7, rmSync } from "fs";
|
|
5608
|
+
import { join as join7 } from "path";
|
|
5370
5609
|
import { tmpdir } from "os";
|
|
5371
5610
|
class BrowserLauncher {
|
|
5372
5611
|
proc = null;
|
|
@@ -5374,21 +5613,21 @@ class BrowserLauncher {
|
|
|
5374
5613
|
wsDebuggerUrl = null;
|
|
5375
5614
|
port = 0;
|
|
5376
5615
|
static findBrowserExecutable() {
|
|
5377
|
-
if (process.env.CHROME_PATH &&
|
|
5616
|
+
if (process.env.CHROME_PATH && existsSync13(process.env.CHROME_PATH)) {
|
|
5378
5617
|
return process.env.CHROME_PATH;
|
|
5379
5618
|
}
|
|
5380
5619
|
if (process.platform === "win32") {
|
|
5381
5620
|
const candidates = [
|
|
5382
5621
|
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
|
|
5383
5622
|
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
|
|
5384
|
-
|
|
5623
|
+
join7(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
|
|
5385
5624
|
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5386
5625
|
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
5387
5626
|
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
5388
|
-
|
|
5627
|
+
join7(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
|
|
5389
5628
|
];
|
|
5390
5629
|
for (const path of candidates) {
|
|
5391
|
-
if (path &&
|
|
5630
|
+
if (path && existsSync13(path))
|
|
5392
5631
|
return path;
|
|
5393
5632
|
}
|
|
5394
5633
|
} else if (process.platform === "darwin") {
|
|
@@ -5399,7 +5638,7 @@ class BrowserLauncher {
|
|
|
5399
5638
|
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
5400
5639
|
];
|
|
5401
5640
|
for (const path of candidates) {
|
|
5402
|
-
if (
|
|
5641
|
+
if (existsSync13(path))
|
|
5403
5642
|
return path;
|
|
5404
5643
|
}
|
|
5405
5644
|
} else {
|
|
@@ -5412,7 +5651,7 @@ class BrowserLauncher {
|
|
|
5412
5651
|
"/usr/bin/microsoft-edge"
|
|
5413
5652
|
];
|
|
5414
5653
|
for (const path of candidates) {
|
|
5415
|
-
if (
|
|
5654
|
+
if (existsSync13(path))
|
|
5416
5655
|
return path;
|
|
5417
5656
|
}
|
|
5418
5657
|
}
|
|
@@ -5424,8 +5663,8 @@ class BrowserLauncher {
|
|
|
5424
5663
|
throw new Error("No supported browser (Google Chrome, Chromium, MS Edge, Brave) found on this machine. Please install Chrome or specify CHROME_PATH.");
|
|
5425
5664
|
}
|
|
5426
5665
|
this.port = options.port || 9200 + Math.floor(Math.random() * 500);
|
|
5427
|
-
this.tempUserDataDir = options.userDataDir ||
|
|
5428
|
-
|
|
5666
|
+
this.tempUserDataDir = options.userDataDir || join7(tmpdir(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
5667
|
+
mkdirSync7(this.tempUserDataDir, { recursive: true });
|
|
5429
5668
|
const isHeadless = options.headless ?? true;
|
|
5430
5669
|
const launchArgs = [
|
|
5431
5670
|
executable,
|
|
@@ -5502,7 +5741,7 @@ class BrowserLauncher {
|
|
|
5502
5741
|
}
|
|
5503
5742
|
this.proc = null;
|
|
5504
5743
|
}
|
|
5505
|
-
if (this.tempUserDataDir &&
|
|
5744
|
+
if (this.tempUserDataDir && existsSync13(this.tempUserDataDir)) {
|
|
5506
5745
|
try {
|
|
5507
5746
|
rmSync(this.tempUserDataDir, { recursive: true, force: true });
|
|
5508
5747
|
} catch {}
|
|
@@ -5524,7 +5763,7 @@ class CdpSession {
|
|
|
5524
5763
|
this.wsUrl = wsUrl;
|
|
5525
5764
|
}
|
|
5526
5765
|
async connect() {
|
|
5527
|
-
return new Promise((
|
|
5766
|
+
return new Promise((resolve13, reject) => {
|
|
5528
5767
|
try {
|
|
5529
5768
|
this.ws = new WebSocket(this.wsUrl);
|
|
5530
5769
|
const onOpen = async () => {
|
|
@@ -5540,7 +5779,7 @@ class CdpSession {
|
|
|
5540
5779
|
await this.send("Network.enable").catch(() => {});
|
|
5541
5780
|
await this.send("Page.setLifecycleEventsEnabled", { enabled: true }).catch(() => {});
|
|
5542
5781
|
this.setupEventHandlers();
|
|
5543
|
-
|
|
5782
|
+
resolve13();
|
|
5544
5783
|
} catch (err) {
|
|
5545
5784
|
reject(err);
|
|
5546
5785
|
}
|
|
@@ -5662,13 +5901,13 @@ class CdpSession {
|
|
|
5662
5901
|
throw new Error("CDP WebSocket is not connected");
|
|
5663
5902
|
}
|
|
5664
5903
|
const id = this.nextId++;
|
|
5665
|
-
return new Promise((
|
|
5904
|
+
return new Promise((resolve13, reject) => {
|
|
5666
5905
|
const timeoutMs = 30000;
|
|
5667
5906
|
const timer = setTimeout(() => {
|
|
5668
5907
|
this.pending.delete(id);
|
|
5669
5908
|
reject(new Error(`CDP command '${method}' timed out after ${timeoutMs}ms`));
|
|
5670
5909
|
}, timeoutMs);
|
|
5671
|
-
this.pending.set(id, { resolve:
|
|
5910
|
+
this.pending.set(id, { resolve: resolve13, reject, timer });
|
|
5672
5911
|
try {
|
|
5673
5912
|
this.ws?.send(JSON.stringify({ id, method, params }));
|
|
5674
5913
|
} catch (err) {
|
|
@@ -5863,8 +6102,8 @@ class DomSnapshotEngine {
|
|
|
5863
6102
|
}
|
|
5864
6103
|
}
|
|
5865
6104
|
// src/mcp/servers/chrome-devtools/controller.ts
|
|
5866
|
-
import { writeFileSync as writeFileSync5, mkdirSync as
|
|
5867
|
-
import { dirname as
|
|
6105
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
|
|
6106
|
+
import { dirname as dirname7, resolve as resolve13 } from "path";
|
|
5868
6107
|
class ChromeDevToolsController {
|
|
5869
6108
|
launcher = new BrowserLauncher;
|
|
5870
6109
|
sessions = new Map;
|
|
@@ -5992,12 +6231,12 @@ class ChromeDevToolsController {
|
|
|
5992
6231
|
} else if (navType === "reload") {
|
|
5993
6232
|
await cdp.send("Page.reload", { ignoreCache: params.ignoreCache });
|
|
5994
6233
|
}
|
|
5995
|
-
await new Promise((
|
|
5996
|
-
const timer = setTimeout(
|
|
6234
|
+
await new Promise((resolve14) => {
|
|
6235
|
+
const timer = setTimeout(resolve14, params.timeout || 3000);
|
|
5997
6236
|
const unsub = cdp.on("Page.loadEventFired", () => {
|
|
5998
6237
|
clearTimeout(timer);
|
|
5999
6238
|
unsub();
|
|
6000
|
-
|
|
6239
|
+
resolve14();
|
|
6001
6240
|
});
|
|
6002
6241
|
});
|
|
6003
6242
|
const evalRes = await cdp.send("Runtime.evaluate", {
|
|
@@ -6013,8 +6252,8 @@ class ChromeDevToolsController {
|
|
|
6013
6252
|
const { cdp } = this.getSession(params.pageId);
|
|
6014
6253
|
const snapshot = await DomSnapshotEngine.captureSnapshot(cdp, params.verbose);
|
|
6015
6254
|
if (params.filePath) {
|
|
6016
|
-
const fullPath =
|
|
6017
|
-
|
|
6255
|
+
const fullPath = resolve13(params.filePath);
|
|
6256
|
+
mkdirSync8(dirname7(fullPath), { recursive: true });
|
|
6018
6257
|
writeFileSync5(fullPath, snapshot.textSnapshot, "utf8");
|
|
6019
6258
|
return `Snapshot saved to ${params.filePath} (${snapshot.elementsCount} indexed elements)`;
|
|
6020
6259
|
}
|
|
@@ -6042,8 +6281,8 @@ class ChromeDevToolsController {
|
|
|
6042
6281
|
});
|
|
6043
6282
|
const base64Data = res.data;
|
|
6044
6283
|
if (params.filePath) {
|
|
6045
|
-
const fullPath =
|
|
6046
|
-
|
|
6284
|
+
const fullPath = resolve13(params.filePath);
|
|
6285
|
+
mkdirSync8(dirname7(fullPath), { recursive: true });
|
|
6047
6286
|
writeFileSync5(fullPath, Buffer.from(base64Data, "base64"));
|
|
6048
6287
|
return { format, filePath: params.filePath };
|
|
6049
6288
|
}
|
|
@@ -6187,8 +6426,8 @@ class ChromeDevToolsController {
|
|
|
6187
6426
|
}
|
|
6188
6427
|
const value = res.result?.value;
|
|
6189
6428
|
if (params.filePath) {
|
|
6190
|
-
const fullPath =
|
|
6191
|
-
|
|
6429
|
+
const fullPath = resolve13(params.filePath);
|
|
6430
|
+
mkdirSync8(dirname7(fullPath), { recursive: true });
|
|
6192
6431
|
writeFileSync5(fullPath, JSON.stringify(value, null, 2), "utf8");
|
|
6193
6432
|
return `Script output saved to ${params.filePath}`;
|
|
6194
6433
|
}
|
|
@@ -6681,9 +6920,9 @@ if (false) {}
|
|
|
6681
6920
|
|
|
6682
6921
|
// src/mcp/servers/chrome-devtools/index.ts
|
|
6683
6922
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
|
|
6684
|
-
var CHROME_DEVTOOLS_MCP_SERVER_PATH =
|
|
6923
|
+
var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve14(__dirname, "server.ts");
|
|
6685
6924
|
// src/mcp/servers/web-search/index.ts
|
|
6686
|
-
import { resolve as
|
|
6925
|
+
import { resolve as resolve15 } from "path";
|
|
6687
6926
|
|
|
6688
6927
|
// src/mcp/servers/web-search/html-to-markdown.ts
|
|
6689
6928
|
class HtmlToMarkdownConverter {
|
|
@@ -7335,14 +7574,14 @@ if (false) {}
|
|
|
7335
7574
|
|
|
7336
7575
|
// src/mcp/servers/web-search/index.ts
|
|
7337
7576
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
|
|
7338
|
-
var WEB_SEARCH_MCP_SERVER_PATH =
|
|
7577
|
+
var WEB_SEARCH_MCP_SERVER_PATH = resolve15(__dirname, "server.ts");
|
|
7339
7578
|
// src/mcp/servers/sqlite/index.ts
|
|
7340
|
-
import { resolve as
|
|
7579
|
+
import { resolve as resolve17 } from "path";
|
|
7341
7580
|
|
|
7342
7581
|
// src/mcp/servers/sqlite/db-engine.ts
|
|
7343
7582
|
import { Database as Database2 } from "bun:sqlite";
|
|
7344
|
-
import { resolve as
|
|
7345
|
-
import { readdirSync as
|
|
7583
|
+
import { resolve as resolve16, isAbsolute } from "path";
|
|
7584
|
+
import { readdirSync as readdirSync5 } from "fs";
|
|
7346
7585
|
|
|
7347
7586
|
class SqliteEngine {
|
|
7348
7587
|
connections = new Map;
|
|
@@ -7364,17 +7603,17 @@ class SqliteEngine {
|
|
|
7364
7603
|
this.defaultDbPath = discovered;
|
|
7365
7604
|
return discovered;
|
|
7366
7605
|
}
|
|
7367
|
-
const fallback =
|
|
7606
|
+
const fallback = resolve16(process.cwd(), "dev.sqlite");
|
|
7368
7607
|
this.defaultDbPath = fallback;
|
|
7369
7608
|
return fallback;
|
|
7370
7609
|
}
|
|
7371
|
-
return isAbsolute(dbPath) ? dbPath :
|
|
7610
|
+
return isAbsolute(dbPath) ? dbPath : resolve16(process.cwd(), dbPath);
|
|
7372
7611
|
}
|
|
7373
7612
|
autoDiscoverDatabase() {
|
|
7374
7613
|
try {
|
|
7375
|
-
const files =
|
|
7614
|
+
const files = readdirSync5(process.cwd());
|
|
7376
7615
|
const dbFile = files.find((f) => f.endsWith(".sqlite") || f.endsWith(".sqlite3") || f.endsWith(".db"));
|
|
7377
|
-
return dbFile ?
|
|
7616
|
+
return dbFile ? resolve16(process.cwd(), dbFile) : null;
|
|
7378
7617
|
} catch {
|
|
7379
7618
|
return null;
|
|
7380
7619
|
}
|
|
@@ -7833,7 +8072,7 @@ if (false) {}
|
|
|
7833
8072
|
|
|
7834
8073
|
// src/mcp/servers/sqlite/index.ts
|
|
7835
8074
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
|
|
7836
|
-
var SQLITE_MCP_SERVER_PATH =
|
|
8075
|
+
var SQLITE_MCP_SERVER_PATH = resolve17(__dirname, "server.ts");
|
|
7837
8076
|
// src/agents/identity.ts
|
|
7838
8077
|
import { generateKeyPairSync, sign, verify } from "crypto";
|
|
7839
8078
|
function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
|
|
@@ -7876,8 +8115,8 @@ function verifyTaskAction(assertion, payload) {
|
|
|
7876
8115
|
}
|
|
7877
8116
|
}
|
|
7878
8117
|
// src/agents/roles.ts
|
|
7879
|
-
import { existsSync as
|
|
7880
|
-
import { resolve as
|
|
8118
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
|
|
8119
|
+
import { resolve as resolve18, join as join8 } from "path";
|
|
7881
8120
|
|
|
7882
8121
|
class AgentRoleRegistry {
|
|
7883
8122
|
roles = new Map;
|
|
@@ -7961,14 +8200,14 @@ class AgentRoleRegistry {
|
|
|
7961
8200
|
return cycle === 0 ? base : `${base}_${cycle + 1}`;
|
|
7962
8201
|
}
|
|
7963
8202
|
loadRolesFromDir(dirPath) {
|
|
7964
|
-
const fullPath =
|
|
7965
|
-
if (!
|
|
8203
|
+
const fullPath = resolve18(dirPath);
|
|
8204
|
+
if (!existsSync15(fullPath))
|
|
7966
8205
|
return;
|
|
7967
|
-
const entries =
|
|
8206
|
+
const entries = readdirSync6(fullPath);
|
|
7968
8207
|
for (const entry of entries) {
|
|
7969
8208
|
if (entry.endsWith(".json")) {
|
|
7970
8209
|
try {
|
|
7971
|
-
const content = readFileSync9(
|
|
8210
|
+
const content = readFileSync9(join8(fullPath, entry), "utf8");
|
|
7972
8211
|
const parsed = JSON.parse(content);
|
|
7973
8212
|
if (parsed.name && parsed.systemPrompt) {
|
|
7974
8213
|
this.registerRole(parsed);
|
|
@@ -7997,21 +8236,19 @@ class AgentRoleRegistry {
|
|
|
7997
8236
|
}
|
|
7998
8237
|
// src/agents/graph-store.ts
|
|
7999
8238
|
import { Database as Database3 } from "bun:sqlite";
|
|
8000
|
-
import { resolve as
|
|
8001
|
-
import { existsSync as
|
|
8002
|
-
import { homedir as homedir4 } from "os";
|
|
8003
|
-
|
|
8239
|
+
import { resolve as resolve19 } from "path";
|
|
8240
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
|
|
8004
8241
|
class AgentGraphStore {
|
|
8005
8242
|
db;
|
|
8006
8243
|
constructor(dbPathOrDb) {
|
|
8007
8244
|
if (dbPathOrDb instanceof Database3) {
|
|
8008
8245
|
this.db = dbPathOrDb;
|
|
8009
8246
|
} else {
|
|
8010
|
-
const dbPath = dbPathOrDb ||
|
|
8247
|
+
const dbPath = dbPathOrDb || getAgentGraphDbPath();
|
|
8011
8248
|
if (dbPath !== ":memory:") {
|
|
8012
|
-
const dir =
|
|
8013
|
-
if (!
|
|
8014
|
-
|
|
8249
|
+
const dir = resolve19(dbPath, "..");
|
|
8250
|
+
if (!existsSync16(dir)) {
|
|
8251
|
+
mkdirSync9(dir, { recursive: true });
|
|
8015
8252
|
}
|
|
8016
8253
|
}
|
|
8017
8254
|
this.db = new Database3(dbPath);
|
|
@@ -8133,8 +8370,8 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
|
|
|
8133
8370
|
});
|
|
8134
8371
|
let resolvePromise;
|
|
8135
8372
|
let rejectPromise;
|
|
8136
|
-
const taskPromise = new Promise((
|
|
8137
|
-
resolvePromise =
|
|
8373
|
+
const taskPromise = new Promise((resolve20, reject) => {
|
|
8374
|
+
resolvePromise = resolve20;
|
|
8138
8375
|
rejectPromise = reject;
|
|
8139
8376
|
});
|
|
8140
8377
|
const handle = {
|
|
@@ -8433,17 +8670,16 @@ function registerMultiAgentTools(router2, spawner) {
|
|
|
8433
8670
|
}
|
|
8434
8671
|
// src/storage/sqlite-store.ts
|
|
8435
8672
|
import { Database as Database4 } from "bun:sqlite";
|
|
8436
|
-
import { existsSync as
|
|
8437
|
-
import { dirname as
|
|
8438
|
-
import { homedir as homedir5 } from "os";
|
|
8673
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10 } from "fs";
|
|
8674
|
+
import { dirname as dirname8 } from "path";
|
|
8439
8675
|
class SqliteThreadStore {
|
|
8440
8676
|
db;
|
|
8441
8677
|
constructor(dbPath) {
|
|
8442
8678
|
const effectivePath = dbPath || this.getDefaultDbPath();
|
|
8443
8679
|
if (effectivePath !== ":memory:") {
|
|
8444
|
-
const dir =
|
|
8445
|
-
if (!
|
|
8446
|
-
|
|
8680
|
+
const dir = dirname8(effectivePath);
|
|
8681
|
+
if (!existsSync17(dir)) {
|
|
8682
|
+
mkdirSync10(dir, { recursive: true });
|
|
8447
8683
|
}
|
|
8448
8684
|
}
|
|
8449
8685
|
this.db = new Database4(effectivePath);
|
|
@@ -8452,7 +8688,7 @@ class SqliteThreadStore {
|
|
|
8452
8688
|
this.initSchema();
|
|
8453
8689
|
}
|
|
8454
8690
|
getDefaultDbPath() {
|
|
8455
|
-
return
|
|
8691
|
+
return getThreadsDbPath();
|
|
8456
8692
|
}
|
|
8457
8693
|
initSchema() {
|
|
8458
8694
|
this.db.exec(`
|
|
@@ -8675,9 +8911,9 @@ class SessionPersistenceManager {
|
|
|
8675
8911
|
}
|
|
8676
8912
|
}
|
|
8677
8913
|
// src/skills/loader.ts
|
|
8678
|
-
import { existsSync as
|
|
8679
|
-
import { resolve as resolve20, join as
|
|
8680
|
-
import { homedir as
|
|
8914
|
+
import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
|
|
8915
|
+
import { resolve as resolve20, join as join9 } from "path";
|
|
8916
|
+
import { homedir as homedir3 } from "os";
|
|
8681
8917
|
var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
|
|
8682
8918
|
|
|
8683
8919
|
class SkillsLoader {
|
|
@@ -8747,16 +8983,16 @@ class SkillsLoader {
|
|
|
8747
8983
|
resolve20(cwd, "skills")
|
|
8748
8984
|
];
|
|
8749
8985
|
for (const cand of candidates) {
|
|
8750
|
-
if (
|
|
8986
|
+
if (existsSync18(cand) && !roots.includes(cand)) {
|
|
8751
8987
|
roots.push(cand);
|
|
8752
8988
|
}
|
|
8753
8989
|
}
|
|
8754
8990
|
}
|
|
8755
8991
|
if (this.includeGlobal) {
|
|
8756
|
-
roots.push(
|
|
8992
|
+
roots.push(getGlobalSkillsDir(), resolve20(homedir3(), ".gemini", "config", "skills"));
|
|
8757
8993
|
}
|
|
8758
8994
|
roots.push(...this.customRoots.map((r) => resolve20(r)));
|
|
8759
|
-
return roots.filter((r) =>
|
|
8995
|
+
return roots.filter((r) => existsSync18(r));
|
|
8760
8996
|
}
|
|
8761
8997
|
discoverSkills(cwd, options) {
|
|
8762
8998
|
return this.listSkills(cwd, options);
|
|
@@ -8772,12 +9008,12 @@ class SkillsLoader {
|
|
|
8772
9008
|
const discovered = new Map;
|
|
8773
9009
|
for (const root of roots) {
|
|
8774
9010
|
try {
|
|
8775
|
-
const entries =
|
|
9011
|
+
const entries = readdirSync7(root, { withFileTypes: true });
|
|
8776
9012
|
for (const entry of entries) {
|
|
8777
9013
|
if (entry.isDirectory()) {
|
|
8778
|
-
const skillDir =
|
|
8779
|
-
const skillFilePath =
|
|
8780
|
-
if (
|
|
9014
|
+
const skillDir = join9(root, entry.name);
|
|
9015
|
+
const skillFilePath = join9(skillDir, "SKILL.md");
|
|
9016
|
+
if (existsSync18(skillFilePath)) {
|
|
8781
9017
|
const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
|
|
8782
9018
|
if (meta && !discovered.has(meta.name)) {
|
|
8783
9019
|
meta.enabled = !this.isSkillDisabled(meta.name);
|
|
@@ -8899,143 +9135,282 @@ When tackling complex specialized tasks that match any of these skills, autonomo
|
|
|
8899
9135
|
}
|
|
8900
9136
|
}
|
|
8901
9137
|
// src/memories/store.ts
|
|
8902
|
-
import { existsSync as
|
|
8903
|
-
import { resolve as resolve21, dirname as
|
|
8904
|
-
import {
|
|
8905
|
-
|
|
9138
|
+
import { existsSync as existsSync19, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync11, readdirSync as readdirSync8 } from "fs";
|
|
9139
|
+
import { resolve as resolve21, join as join10, basename, dirname as dirname9 } from "path";
|
|
9140
|
+
import { createHash } from "crypto";
|
|
8906
9141
|
class MemoryStore {
|
|
8907
9142
|
globalPath;
|
|
8908
9143
|
customWorkspacePath;
|
|
8909
9144
|
constructor(options = {}) {
|
|
8910
|
-
this.globalPath = options.globalPath ||
|
|
9145
|
+
this.globalPath = options.globalPath || getGlobalMemoriesPath();
|
|
8911
9146
|
this.customWorkspacePath = options.workspacePath;
|
|
8912
9147
|
}
|
|
8913
|
-
|
|
8914
|
-
|
|
9148
|
+
findProjectRoot(cwd) {
|
|
9149
|
+
let current = resolve21(cwd);
|
|
9150
|
+
while (true) {
|
|
9151
|
+
if (existsSync19(join10(current, ".git"))) {
|
|
9152
|
+
return current;
|
|
9153
|
+
}
|
|
9154
|
+
const parent = dirname9(current);
|
|
9155
|
+
if (parent === current) {
|
|
9156
|
+
return resolve21(cwd);
|
|
9157
|
+
}
|
|
9158
|
+
current = parent;
|
|
9159
|
+
}
|
|
8915
9160
|
}
|
|
8916
|
-
|
|
8917
|
-
const
|
|
8918
|
-
const
|
|
8919
|
-
const
|
|
8920
|
-
|
|
8921
|
-
mkdirSync10(dir, { recursive: true });
|
|
8922
|
-
}
|
|
8923
|
-
const existingEntries = this.readMemoryFile(targetFile, scope);
|
|
8924
|
-
const normalized = params.content.trim();
|
|
8925
|
-
const duplicate = existingEntries.find((e) => e.category === params.category && e.content.toLowerCase() === normalized.toLowerCase());
|
|
8926
|
-
if (duplicate) {
|
|
8927
|
-
return duplicate;
|
|
8928
|
-
}
|
|
8929
|
-
const newEntry = {
|
|
8930
|
-
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
|
8931
|
-
category: params.category,
|
|
8932
|
-
content: normalized,
|
|
8933
|
-
scope,
|
|
8934
|
-
createdAt: Date.now()
|
|
8935
|
-
};
|
|
8936
|
-
existingEntries.push(newEntry);
|
|
8937
|
-
this.writeMemoryFile(targetFile, existingEntries);
|
|
8938
|
-
return newEntry;
|
|
9161
|
+
getProjectSlug(cwd) {
|
|
9162
|
+
const root = this.findProjectRoot(cwd);
|
|
9163
|
+
const folderName = basename(root).toLowerCase().replace(/[^a-z0-9_-]/g, "-") || "project";
|
|
9164
|
+
const hash = createHash("sha256").update(resolve21(root)).digest("hex").slice(0, 6);
|
|
9165
|
+
return `${folderName}-${hash}`;
|
|
8939
9166
|
}
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
9167
|
+
getProjectMemoryDir(cwd) {
|
|
9168
|
+
if (this.customWorkspacePath) {
|
|
9169
|
+
const dir2 = resolve21(this.customWorkspacePath);
|
|
9170
|
+
if (!existsSync19(dir2)) {
|
|
9171
|
+
try {
|
|
9172
|
+
mkdirSync11(dir2, { recursive: true });
|
|
9173
|
+
} catch {}
|
|
9174
|
+
}
|
|
9175
|
+
return dir2;
|
|
9176
|
+
}
|
|
9177
|
+
const slug = this.getProjectSlug(cwd);
|
|
9178
|
+
const dir = join10(getProjectsDir(), slug, "memory");
|
|
9179
|
+
if (!existsSync19(dir)) {
|
|
9180
|
+
try {
|
|
9181
|
+
mkdirSync11(dir, { recursive: true });
|
|
9182
|
+
} catch {}
|
|
9183
|
+
}
|
|
9184
|
+
return dir;
|
|
9185
|
+
}
|
|
9186
|
+
getMemoryIndexPath(cwd) {
|
|
9187
|
+
return join10(this.getProjectMemoryDir(cwd), "MEMORY.md");
|
|
9188
|
+
}
|
|
9189
|
+
normalizeCategory(raw) {
|
|
9190
|
+
const cat = raw.toLowerCase().trim();
|
|
9191
|
+
if (cat === "user" || cat === "preference")
|
|
9192
|
+
return "user";
|
|
9193
|
+
if (cat === "feedback" || cat === "guideline")
|
|
9194
|
+
return "feedback";
|
|
9195
|
+
if (cat === "project" || cat === "architecture")
|
|
9196
|
+
return "project";
|
|
9197
|
+
if (cat === "reference" || cat === "note")
|
|
9198
|
+
return "reference";
|
|
9199
|
+
return "project";
|
|
9200
|
+
}
|
|
9201
|
+
saveTopicMemory(params) {
|
|
9202
|
+
const type = this.normalizeCategory(params.category);
|
|
9203
|
+
const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
|
|
9204
|
+
const memoryDir = this.getProjectMemoryDir(params.cwd);
|
|
9205
|
+
const fileName = `${type}_${sanitizedName}.md`;
|
|
9206
|
+
const filePath = join10(memoryDir, fileName);
|
|
9207
|
+
const nowIso = new Date().toISOString();
|
|
9208
|
+
const cleanContent = params.content.trim();
|
|
9209
|
+
const desc = (params.description || cleanContent.split(`
|
|
9210
|
+
`)[0] || sanitizedName).replace(/[\r\n]+/g, " ");
|
|
9211
|
+
const frontmatter = [
|
|
9212
|
+
"---",
|
|
9213
|
+
`type: ${type}`,
|
|
9214
|
+
`name: ${sanitizedName}`,
|
|
9215
|
+
`description: ${desc}`,
|
|
9216
|
+
`modified: ${nowIso}`,
|
|
9217
|
+
"---",
|
|
9218
|
+
"",
|
|
9219
|
+
`# ${sanitizedName.replace(/_/g, " ").toUpperCase()}`,
|
|
9220
|
+
"",
|
|
9221
|
+
cleanContent,
|
|
9222
|
+
""
|
|
9223
|
+
].join(`
|
|
9224
|
+
`);
|
|
9225
|
+
writeFileSync6(filePath, frontmatter, "utf8");
|
|
9226
|
+
this.syncMemoryIndex(params.cwd);
|
|
9227
|
+
return {
|
|
9228
|
+
id: `mem_${sanitizedName}`,
|
|
9229
|
+
category: type,
|
|
9230
|
+
name: sanitizedName,
|
|
9231
|
+
description: desc,
|
|
9232
|
+
content: cleanContent,
|
|
9233
|
+
scope: "project",
|
|
9234
|
+
createdAt: Date.now(),
|
|
9235
|
+
modifiedAt: Date.now(),
|
|
9236
|
+
filePath
|
|
9237
|
+
};
|
|
8945
9238
|
}
|
|
8946
|
-
|
|
8947
|
-
|
|
8948
|
-
|
|
9239
|
+
readTopicMemory(topicNameOrFile, cwd) {
|
|
9240
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9241
|
+
let targetPath = join10(memoryDir, topicNameOrFile);
|
|
9242
|
+
if (!existsSync19(targetPath)) {
|
|
9243
|
+
if (!topicNameOrFile.endsWith(".md")) {
|
|
9244
|
+
targetPath = join10(memoryDir, `${topicNameOrFile}.md`);
|
|
9245
|
+
}
|
|
9246
|
+
}
|
|
9247
|
+
if (!existsSync19(targetPath)) {
|
|
9248
|
+
const files = readdirSync8(memoryDir);
|
|
9249
|
+
const match = files.find((f) => f.includes(topicNameOrFile));
|
|
9250
|
+
if (match) {
|
|
9251
|
+
targetPath = join10(memoryDir, match);
|
|
9252
|
+
} else {
|
|
9253
|
+
return null;
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
8949
9256
|
try {
|
|
8950
|
-
const
|
|
8951
|
-
|
|
9257
|
+
const raw = readFileSync11(targetPath, "utf8");
|
|
9258
|
+
return this.parseTopicFile(raw, targetPath);
|
|
9259
|
+
} catch {
|
|
9260
|
+
return null;
|
|
9261
|
+
}
|
|
9262
|
+
}
|
|
9263
|
+
parseTopicFile(raw, filePath) {
|
|
9264
|
+
const lines = raw.split(`
|
|
8952
9265
|
`);
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
8969
|
-
id: `mem_${entries.length + 1}`,
|
|
8970
|
-
category: currentCategory,
|
|
8971
|
-
content: itemText,
|
|
8972
|
-
scope,
|
|
8973
|
-
createdAt: Date.now()
|
|
8974
|
-
});
|
|
8975
|
-
}
|
|
9266
|
+
let inFm = false;
|
|
9267
|
+
let type = "project";
|
|
9268
|
+
let name = basename(filePath, ".md");
|
|
9269
|
+
let description;
|
|
9270
|
+
let modified = new Date().toISOString();
|
|
9271
|
+
const bodyLines = [];
|
|
9272
|
+
for (let i = 0;i < lines.length; i++) {
|
|
9273
|
+
const line = lines[i];
|
|
9274
|
+
if (i === 0 && line.trim() === "---") {
|
|
9275
|
+
inFm = true;
|
|
9276
|
+
continue;
|
|
9277
|
+
}
|
|
9278
|
+
if (inFm) {
|
|
9279
|
+
if (line.trim() === "---") {
|
|
9280
|
+
inFm = false;
|
|
9281
|
+
continue;
|
|
8976
9282
|
}
|
|
9283
|
+
const colonIdx = line.indexOf(":");
|
|
9284
|
+
if (colonIdx !== -1) {
|
|
9285
|
+
const key = line.slice(0, colonIdx).trim();
|
|
9286
|
+
const val = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
9287
|
+
if (key === "type")
|
|
9288
|
+
type = this.normalizeCategory(val);
|
|
9289
|
+
else if (key === "name")
|
|
9290
|
+
name = val;
|
|
9291
|
+
else if (key === "description")
|
|
9292
|
+
description = val;
|
|
9293
|
+
else if (key === "modified")
|
|
9294
|
+
modified = val;
|
|
9295
|
+
}
|
|
9296
|
+
} else {
|
|
9297
|
+
bodyLines.push(line);
|
|
8977
9298
|
}
|
|
8978
|
-
return entries;
|
|
8979
|
-
} catch {
|
|
8980
|
-
return [];
|
|
8981
9299
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
9300
|
+
return {
|
|
9301
|
+
type,
|
|
9302
|
+
name,
|
|
9303
|
+
description,
|
|
9304
|
+
modified,
|
|
9305
|
+
content: bodyLines.join(`
|
|
9306
|
+
`).trim(),
|
|
9307
|
+
filePath
|
|
8989
9308
|
};
|
|
8990
|
-
|
|
8991
|
-
|
|
9309
|
+
}
|
|
9310
|
+
syncMemoryIndex(cwd) {
|
|
9311
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9312
|
+
const indexPath = join10(memoryDir, "MEMORY.md");
|
|
9313
|
+
const files = existsSync19(memoryDir) ? readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
|
|
9314
|
+
const items2 = [];
|
|
9315
|
+
for (const f of files) {
|
|
9316
|
+
try {
|
|
9317
|
+
const full = join10(memoryDir, f);
|
|
9318
|
+
const parsed = this.parseTopicFile(readFileSync11(full, "utf8"), full);
|
|
9319
|
+
items2.push({
|
|
9320
|
+
type: parsed.type,
|
|
9321
|
+
name: parsed.name,
|
|
9322
|
+
desc: parsed.description || parsed.content.split(`
|
|
9323
|
+
`)[0] || parsed.name,
|
|
9324
|
+
file: f
|
|
9325
|
+
});
|
|
9326
|
+
} catch {}
|
|
8992
9327
|
}
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
`;
|
|
9328
|
+
const indexLines = [
|
|
9329
|
+
"# Project Auto-Memory Index",
|
|
9330
|
+
"",
|
|
9331
|
+
"This index is loaded at session startup. Detailed topics can be retrieved via read_memory tool.",
|
|
9332
|
+
""
|
|
9333
|
+
];
|
|
9334
|
+
for (const item of items2) {
|
|
9335
|
+
indexLines.push(`- [${item.type}] **${item.name}**: ${item.desc} (topic: ${item.file})`);
|
|
9002
9336
|
}
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9337
|
+
const boundedLines = indexLines.slice(0, 200);
|
|
9338
|
+
writeFileSync6(indexPath, boundedLines.join(`
|
|
9339
|
+
`) + `
|
|
9340
|
+
`, "utf8");
|
|
9341
|
+
}
|
|
9342
|
+
loadMemoryIndex(cwd) {
|
|
9343
|
+
const indexPath = this.getMemoryIndexPath(cwd);
|
|
9344
|
+
if (!existsSync19(indexPath))
|
|
9345
|
+
return "";
|
|
9346
|
+
try {
|
|
9347
|
+
const raw = readFileSync11(indexPath, "utf8");
|
|
9348
|
+
const byteLimit = 25 * 1024;
|
|
9349
|
+
const sliced = raw.length > byteLimit ? raw.slice(0, byteLimit) : raw;
|
|
9350
|
+
const lines = sliced.split(`
|
|
9351
|
+
`).slice(0, 200);
|
|
9352
|
+
return lines.join(`
|
|
9353
|
+
`).trim();
|
|
9354
|
+
} catch {
|
|
9355
|
+
return "";
|
|
9009
9356
|
}
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9357
|
+
}
|
|
9358
|
+
listProjectMemories(cwd) {
|
|
9359
|
+
const memoryDir = this.getProjectMemoryDir(cwd);
|
|
9360
|
+
if (!existsSync19(memoryDir))
|
|
9361
|
+
return [];
|
|
9362
|
+
const files = readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
9363
|
+
const list = [];
|
|
9364
|
+
for (const f of files) {
|
|
9365
|
+
try {
|
|
9366
|
+
const full = join10(memoryDir, f);
|
|
9367
|
+
list.push(this.parseTopicFile(readFileSync11(full, "utf8"), full));
|
|
9368
|
+
} catch {}
|
|
9016
9369
|
}
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
`;
|
|
9370
|
+
return list;
|
|
9371
|
+
}
|
|
9372
|
+
addMemory(params) {
|
|
9373
|
+
const cwd = params.cwd || process.cwd();
|
|
9374
|
+
const type = this.normalizeCategory(params.category);
|
|
9375
|
+
const name = params.name || `${type}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
9376
|
+
const entry = this.saveTopicMemory({
|
|
9377
|
+
category: type,
|
|
9378
|
+
name,
|
|
9379
|
+
content: params.content,
|
|
9380
|
+
cwd
|
|
9381
|
+
});
|
|
9382
|
+
if (params.scope) {
|
|
9383
|
+
entry.scope = params.scope;
|
|
9023
9384
|
}
|
|
9024
|
-
|
|
9025
|
-
|
|
9385
|
+
return entry;
|
|
9386
|
+
}
|
|
9387
|
+
getAllMemories(cwd) {
|
|
9388
|
+
const projectTopics = this.listProjectMemories(cwd).map((t) => ({
|
|
9389
|
+
id: `mem_${t.name}`,
|
|
9390
|
+
category: t.type,
|
|
9391
|
+
name: t.name,
|
|
9392
|
+
description: t.description,
|
|
9393
|
+
content: t.content,
|
|
9394
|
+
scope: t.type === "user" ? "global" : "workspace",
|
|
9395
|
+
createdAt: new Date(t.modified).getTime() || Date.now(),
|
|
9396
|
+
filePath: t.filePath
|
|
9397
|
+
}));
|
|
9398
|
+
return projectTopics;
|
|
9026
9399
|
}
|
|
9027
9400
|
formatMemoriesPrompt(cwd) {
|
|
9028
|
-
const
|
|
9029
|
-
if (
|
|
9401
|
+
const indexContent = this.loadMemoryIndex(cwd);
|
|
9402
|
+
if (!indexContent)
|
|
9030
9403
|
return "";
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
##
|
|
9034
|
-
<
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9404
|
+
return [
|
|
9405
|
+
"",
|
|
9406
|
+
"## Project Auto-Memory (Persistent Learnings)",
|
|
9407
|
+
"<auto_memory>",
|
|
9408
|
+
indexContent,
|
|
9409
|
+
"</auto_memory>",
|
|
9410
|
+
"Apply these persistent project learnings, user preferences, and feedback across all tasks.",
|
|
9411
|
+
"If more context is needed for a specific topic, retrieve it using the `read_memory` tool."
|
|
9412
|
+
].join(`
|
|
9413
|
+
`);
|
|
9039
9414
|
}
|
|
9040
9415
|
}
|
|
9041
9416
|
// src/worktree/git.ts
|
|
@@ -9166,8 +9541,8 @@ async function removeWorktreeGit(repoRoot, worktreePath, deleteBranch = false) {
|
|
|
9166
9541
|
return { success: true };
|
|
9167
9542
|
}
|
|
9168
9543
|
// src/worktree/manager.ts
|
|
9169
|
-
import { resolve as resolve23, join as
|
|
9170
|
-
import { existsSync as
|
|
9544
|
+
import { resolve as resolve23, join as join11 } from "path";
|
|
9545
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, readFileSync as readFileSync12 } from "fs";
|
|
9171
9546
|
var DEFAULT_WORKTREE_KEEP_COUNT = 15;
|
|
9172
9547
|
|
|
9173
9548
|
class WorktreeManager {
|
|
@@ -9191,15 +9566,15 @@ class WorktreeManager {
|
|
|
9191
9566
|
const branchName = options.branch || `groupy/${taskId}`;
|
|
9192
9567
|
const targetDir = options.worktreePath || (this.baseStorageDir ? resolve23(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve23(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
|
|
9193
9568
|
const worktreeParent = resolve23(targetDir, "..");
|
|
9194
|
-
if (!
|
|
9195
|
-
|
|
9569
|
+
if (!existsSync20(worktreeParent)) {
|
|
9570
|
+
mkdirSync12(worktreeParent, { recursive: true });
|
|
9196
9571
|
}
|
|
9197
9572
|
const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
|
|
9198
9573
|
const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
|
|
9199
9574
|
if (!result.success) {
|
|
9200
9575
|
throw new Error(`Failed to create git worktree: ${result.error}`);
|
|
9201
9576
|
}
|
|
9202
|
-
const metaPath =
|
|
9577
|
+
const metaPath = join11(targetDir, "groupy-thread.json");
|
|
9203
9578
|
try {
|
|
9204
9579
|
writeFileSync7(metaPath, JSON.stringify({
|
|
9205
9580
|
version: 1,
|
|
@@ -9225,8 +9600,8 @@ class WorktreeManager {
|
|
|
9225
9600
|
return [];
|
|
9226
9601
|
const worktrees = await listWorktreesGit(repoRoot);
|
|
9227
9602
|
return worktrees.map((wt) => {
|
|
9228
|
-
const metaPath =
|
|
9229
|
-
if (
|
|
9603
|
+
const metaPath = join11(wt.path, "groupy-thread.json");
|
|
9604
|
+
if (existsSync20(metaPath)) {
|
|
9230
9605
|
try {
|
|
9231
9606
|
const raw = JSON.parse(readFileSync12(metaPath, "utf8"));
|
|
9232
9607
|
return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
|
|
@@ -9304,7 +9679,7 @@ class WorktreeManager {
|
|
|
9304
9679
|
}
|
|
9305
9680
|
}
|
|
9306
9681
|
// src/auth/oauth.ts
|
|
9307
|
-
import { randomBytes, createHash } from "crypto";
|
|
9682
|
+
import { randomBytes, createHash as createHash2 } from "crypto";
|
|
9308
9683
|
import { exec } from "child_process";
|
|
9309
9684
|
class AuthClient {
|
|
9310
9685
|
store;
|
|
@@ -9457,7 +9832,7 @@ class AuthClient {
|
|
|
9457
9832
|
return randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9]/g, "").slice(0, 64);
|
|
9458
9833
|
}
|
|
9459
9834
|
generateCodeChallenge(verifier) {
|
|
9460
|
-
return
|
|
9835
|
+
return createHash2("sha256").update(verifier).digest("base64url");
|
|
9461
9836
|
}
|
|
9462
9837
|
}
|
|
9463
9838
|
// src/ui/components/claude/claude-header.tsx
|
|
@@ -11007,11 +11382,15 @@ export {
|
|
|
11007
11382
|
captureWorldState,
|
|
11008
11383
|
compactHistory,
|
|
11009
11384
|
createAgentIdentity,
|
|
11385
|
+
createAutoMemoryTools,
|
|
11010
11386
|
createCodeModeTools,
|
|
11011
11387
|
createDefaultTools,
|
|
11012
11388
|
createFileSearchTools,
|
|
11389
|
+
createListMemoriesTool,
|
|
11013
11390
|
createMultiAgentTools,
|
|
11391
|
+
createReadMemoryTool,
|
|
11014
11392
|
createRememberTool,
|
|
11393
|
+
createSaveMemoryTool,
|
|
11015
11394
|
createShellTool,
|
|
11016
11395
|
createSkillTool,
|
|
11017
11396
|
createWorktreeGit,
|