autohand-cli 0.6.4 → 0.6.7
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/SkillsRegistry-LXDK73BL.cjs +9 -0
- package/dist/SkillsRegistry-SP5MX7OA.js +9 -0
- package/dist/agents-FH47ZMOI.cjs +10 -0
- package/dist/{agents-OJWYZN6X.js → agents-NB5VQN6H.js} +2 -2
- package/dist/{agents-new-WQLJOXSS.js → agents-new-M325HGWT.js} +3 -2
- package/dist/agents-new-XLEU26YI.cjs +11 -0
- package/dist/{chunk-DD2YPHP5.cjs → chunk-2OBNJCG6.cjs} +15 -13
- package/dist/{chunk-NWXYG5PQ.js → chunk-37NUB5KX.js} +1 -1
- package/dist/{chunk-AL4Z4WKG.cjs → chunk-3DPDLZYY.cjs} +9 -7
- package/dist/chunk-3HPUOQJN.cjs +23 -0
- package/dist/{chunk-G7SYGATA.cjs → chunk-3ZUWWML7.cjs} +2 -2
- package/dist/{chunk-3Y6G5DUX.cjs → chunk-53YDUYNS.cjs} +10 -4
- package/dist/{chunk-PRZTK2FX.js → chunk-5WKR4HIB.js} +7 -5
- package/dist/{chunk-ZTA2ASFW.cjs → chunk-6ZGNSZRG.cjs} +1 -1
- package/dist/chunk-7BYSXAKS.js +23 -0
- package/dist/{chunk-6FEZ6JAQ.js → chunk-7HB7GSQF.js} +1 -1
- package/dist/{chunk-UBGEAEKS.js → chunk-AY2XV7TH.js} +1 -1
- package/dist/{chunk-7RRX7H2X.cjs → chunk-B5N5UAMO.cjs} +20 -12
- package/dist/{chunk-KZ2UXXLH.js → chunk-BAHUKJJR.js} +7 -5
- package/dist/{chunk-KT55HW6V.js → chunk-CHQMK2ZG.js} +1 -1
- package/dist/chunk-CVYEUA3D.cjs +528 -0
- package/dist/{chunk-MRQV5HMC.js → chunk-DE7YC5MB.js} +8 -6
- package/dist/{chunk-AD4O67ZA.cjs → chunk-EJ77L3KT.cjs} +10 -10
- package/dist/{chunk-737A24RB.js → chunk-FUEL6BK7.js} +9 -1
- package/dist/{chunk-6MCXWSR3.js → chunk-H4RPZD6H.js} +1 -1
- package/dist/chunk-JHGIWNHL.cjs +46 -0
- package/dist/{chunk-27JNK5TE.cjs → chunk-LUKMRIKJ.cjs} +40 -61
- package/dist/{chunk-4H3B46YX.js → chunk-MWLAHCU7.js} +9 -3
- package/dist/{chunk-QCMC2WOC.cjs → chunk-N6ZOJI2M.cjs} +4 -4
- package/dist/{chunk-2TPGTNNY.js → chunk-NGSLABLS.js} +37 -58
- package/dist/chunk-QHPFA6OE.js +46 -0
- package/dist/{chunk-M4LKQQHU.cjs → chunk-REPKBECD.cjs} +2 -2
- package/dist/chunk-SKU4M27Z.js +528 -0
- package/dist/chunk-W4XTDUGT.js +267 -0
- package/dist/{chunk-RDEROLKA.cjs → chunk-XAM7SFVB.cjs} +8 -6
- package/dist/chunk-XF4EQ3IV.cjs +267 -0
- package/dist/{chunk-IHJDYAYJ.cjs → chunk-XTHHDIBG.cjs} +9 -1
- package/dist/{chunk-5N3QP5LJ.js → chunk-YDH2BMEN.js} +19 -11
- package/dist/constants-ICQLSGZN.cjs +19 -0
- package/dist/constants-N3I2FHCM.js +19 -0
- package/dist/feedback-TOGESBX7.cjs +11 -0
- package/dist/{feedback-3THCLEBE.js → feedback-YGSYBQEW.js} +3 -2
- package/dist/index.cjs +1463 -1146
- package/dist/index.js +987 -670
- package/dist/login-QVBS7KBK.cjs +13 -0
- package/dist/login-XUVEFKCR.js +13 -0
- package/dist/logout-75YLPOBK.js +13 -0
- package/dist/logout-FYYR5KCP.cjs +13 -0
- package/dist/{permissions-CYW62ZK3.js → permissions-3GS4ZWVA.js} +2 -1
- package/dist/permissions-E3MTIE7D.cjs +10 -0
- package/dist/{skills-HF4SAF5O.js → skills-3M26KASS.js} +3 -1
- package/dist/skills-BOFY5RQN.cjs +13 -0
- package/dist/skills-install-WJ2LDRQG.js +680 -0
- package/dist/skills-install-Z3W5PQWQ.cjs +680 -0
- package/dist/skills-new-KIBUN63X.js +12 -0
- package/dist/skills-new-XDYS24XW.cjs +12 -0
- package/dist/{status-SLYYTKXD.js → status-6FY6RKIS.js} +1 -1
- package/dist/status-DJHDT6QH.cjs +9 -0
- package/dist/theme-2UK74UWR.cjs +13 -0
- package/dist/{theme-THMQ5AIN.js → theme-XNZ2X6HE.js} +3 -3
- package/package.json +1 -1
- package/dist/agents-EHLYBJLK.cjs +0 -10
- package/dist/agents-new-7VPASCBV.cjs +0 -10
- package/dist/chunk-NEAJ2UWG.js +0 -191
- package/dist/chunk-Z6SGIQWH.cjs +0 -191
- package/dist/feedback-PATTKRH5.cjs +0 -10
- package/dist/login-QJROML5I.js +0 -12
- package/dist/login-X66DSV75.cjs +0 -12
- package/dist/logout-3Z7R3F7J.cjs +0 -12
- package/dist/logout-RJ5OAXRI.js +0 -12
- package/dist/permissions-NOC5DMOH.cjs +0 -9
- package/dist/skills-U6J6DFLK.cjs +0 -11
- package/dist/skills-new-QDTNEG3R.js +0 -10
- package/dist/skills-new-UPVBHIF2.cjs +0 -10
- package/dist/status-GR73LEEN.cjs +0 -9
- package/dist/theme-YDANJLZR.cjs +0 -13
package/dist/index.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
installMetadata,
|
|
4
|
+
metadata as metadata22
|
|
5
|
+
} from "./chunk-W4XTDUGT.js";
|
|
6
|
+
import {
|
|
7
|
+
metadata as metadata23
|
|
8
|
+
} from "./chunk-NGSLABLS.js";
|
|
2
9
|
import {
|
|
3
10
|
metadata as metadata24
|
|
4
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-37NUB5KX.js";
|
|
12
|
+
import {
|
|
13
|
+
SkillsRegistry
|
|
14
|
+
} from "./chunk-SKU4M27Z.js";
|
|
15
|
+
import "./chunk-QHPFA6OE.js";
|
|
16
|
+
import {
|
|
17
|
+
metadata as metadata15
|
|
18
|
+
} from "./chunk-2NUX2RAI.js";
|
|
5
19
|
import {
|
|
6
20
|
metadata as metadata16
|
|
7
21
|
} from "./chunk-YWKZF2SA.js";
|
|
@@ -11,16 +25,16 @@ import {
|
|
|
11
25
|
import {
|
|
12
26
|
metadata as metadata18,
|
|
13
27
|
package_default
|
|
14
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-H4RPZD6H.js";
|
|
15
29
|
import {
|
|
16
30
|
metadata as metadata19
|
|
17
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-DE7YC5MB.js";
|
|
18
32
|
import {
|
|
19
33
|
metadata as metadata20
|
|
20
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-BAHUKJJR.js";
|
|
21
35
|
import {
|
|
22
36
|
getAuthClient
|
|
23
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-CHQMK2ZG.js";
|
|
24
38
|
import {
|
|
25
39
|
ThemeProvider,
|
|
26
40
|
getProviderConfig,
|
|
@@ -31,33 +45,24 @@ import {
|
|
|
31
45
|
resolveWorkspaceRoot,
|
|
32
46
|
saveConfig,
|
|
33
47
|
useTheme
|
|
34
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-AY2XV7TH.js";
|
|
35
49
|
import {
|
|
36
50
|
metadata as metadata21
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import {
|
|
39
|
-
metadata as metadata22
|
|
40
|
-
} from "./chunk-NEAJ2UWG.js";
|
|
51
|
+
} from "./chunk-YDH2BMEN.js";
|
|
41
52
|
import {
|
|
42
|
-
metadata as
|
|
43
|
-
|
|
44
|
-
} from "./chunk-2TPGTNNY.js";
|
|
53
|
+
metadata as metadata7
|
|
54
|
+
} from "./chunk-SVLBJMYO.js";
|
|
45
55
|
import {
|
|
46
56
|
AgentRegistry,
|
|
47
57
|
metadata as metadata8
|
|
48
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-7HB7GSQF.js";
|
|
49
59
|
import {
|
|
50
60
|
metadata as metadata9
|
|
51
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-5WKR4HIB.js";
|
|
52
62
|
import {
|
|
53
63
|
metadata as metadata10
|
|
54
|
-
} from "./chunk-
|
|
55
|
-
import
|
|
56
|
-
AUTOHAND_FILES,
|
|
57
|
-
AUTOHAND_HOME,
|
|
58
|
-
AUTOHAND_PATHS,
|
|
59
|
-
PROJECT_DIR_NAME
|
|
60
|
-
} from "./chunk-737A24RB.js";
|
|
64
|
+
} from "./chunk-MWLAHCU7.js";
|
|
65
|
+
import "./chunk-7BYSXAKS.js";
|
|
61
66
|
import {
|
|
62
67
|
metadata as metadata11
|
|
63
68
|
} from "./chunk-I4HVBWYF.js";
|
|
@@ -72,8 +77,11 @@ import {
|
|
|
72
77
|
metadata as metadata14
|
|
73
78
|
} from "./chunk-XDVG3NM4.js";
|
|
74
79
|
import {
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
AUTOHAND_FILES,
|
|
81
|
+
AUTOHAND_HOME,
|
|
82
|
+
AUTOHAND_PATHS,
|
|
83
|
+
PROJECT_DIR_NAME
|
|
84
|
+
} from "./chunk-FUEL6BK7.js";
|
|
77
85
|
import {
|
|
78
86
|
metadata
|
|
79
87
|
} from "./chunk-KZ7VMQTC.js";
|
|
@@ -93,9 +101,6 @@ import {
|
|
|
93
101
|
import {
|
|
94
102
|
metadata as metadata6
|
|
95
103
|
} from "./chunk-QJ53OSGF.js";
|
|
96
|
-
import {
|
|
97
|
-
metadata as metadata7
|
|
98
|
-
} from "./chunk-SVLBJMYO.js";
|
|
99
104
|
import {
|
|
100
105
|
__commonJS,
|
|
101
106
|
__toESM
|
|
@@ -2087,6 +2092,18 @@ var GitIgnoreParser = class {
|
|
|
2087
2092
|
};
|
|
2088
2093
|
|
|
2089
2094
|
// src/actions/filesystem.ts
|
|
2095
|
+
var FILE_LIMITS = {
|
|
2096
|
+
/** Maximum file size for read operations (10MB) */
|
|
2097
|
+
MAX_READ_SIZE: 10 * 1024 * 1024,
|
|
2098
|
+
/** Maximum file size for write operations (50MB) */
|
|
2099
|
+
MAX_WRITE_SIZE: 50 * 1024 * 1024,
|
|
2100
|
+
/** Maximum number of files in a single directory listing */
|
|
2101
|
+
MAX_DIR_ENTRIES: 1e4,
|
|
2102
|
+
/** Maximum search results to return */
|
|
2103
|
+
MAX_SEARCH_RESULTS: 1e3,
|
|
2104
|
+
/** Maximum undo stack size */
|
|
2105
|
+
MAX_UNDO_STACK: 100
|
|
2106
|
+
};
|
|
2090
2107
|
var FileActionManager = class {
|
|
2091
2108
|
constructor(workspaceRoot) {
|
|
2092
2109
|
this.undoStack = [];
|
|
@@ -2101,12 +2118,27 @@ var FileActionManager = class {
|
|
|
2101
2118
|
if (!exists) {
|
|
2102
2119
|
throw new Error(`File ${target} not found in workspace.`);
|
|
2103
2120
|
}
|
|
2121
|
+
const stats = await fs3.stat(filePath);
|
|
2122
|
+
if (stats.size > FILE_LIMITS.MAX_READ_SIZE) {
|
|
2123
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
2124
|
+
const limitMB = (FILE_LIMITS.MAX_READ_SIZE / 1024 / 1024).toFixed(0);
|
|
2125
|
+
throw new Error(`File ${target} is too large (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
|
|
2126
|
+
}
|
|
2104
2127
|
return fs3.readFile(filePath, "utf8");
|
|
2105
2128
|
}
|
|
2106
2129
|
async writeFile(target, contents) {
|
|
2130
|
+
const contentSize = Buffer.byteLength(contents, "utf8");
|
|
2131
|
+
if (contentSize > FILE_LIMITS.MAX_WRITE_SIZE) {
|
|
2132
|
+
const sizeMB = (contentSize / 1024 / 1024).toFixed(2);
|
|
2133
|
+
const limitMB = (FILE_LIMITS.MAX_WRITE_SIZE / 1024 / 1024).toFixed(0);
|
|
2134
|
+
throw new Error(`Content too large to write (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
|
|
2135
|
+
}
|
|
2107
2136
|
const filePath = this.resolvePath(target);
|
|
2108
2137
|
await fs3.ensureDir(path2.dirname(filePath));
|
|
2109
2138
|
const previous = await fs3.pathExists(filePath) ? await fs3.readFile(filePath, "utf8") : "";
|
|
2139
|
+
if (this.undoStack.length >= FILE_LIMITS.MAX_UNDO_STACK) {
|
|
2140
|
+
this.undoStack.shift();
|
|
2141
|
+
}
|
|
2110
2142
|
this.undoStack.push({ absolutePath: filePath, previousContents: previous });
|
|
2111
2143
|
await fs3.writeFile(filePath, contents, "utf8");
|
|
2112
2144
|
}
|
|
@@ -2139,6 +2171,9 @@ var FileActionManager = class {
|
|
|
2139
2171
|
"never",
|
|
2140
2172
|
"--no-binary",
|
|
2141
2173
|
// Skip binary files
|
|
2174
|
+
"--max-count",
|
|
2175
|
+
String(FILE_LIMITS.MAX_SEARCH_RESULTS),
|
|
2176
|
+
// Enforce result limit
|
|
2142
2177
|
"--glob",
|
|
2143
2178
|
"!*.{exe,dll,so,dylib,bin,o,a,lib,pyc,pyo,class,jar,war,ear}",
|
|
2144
2179
|
"--glob",
|
|
@@ -2168,7 +2203,7 @@ var FileActionManager = class {
|
|
|
2168
2203
|
encoding: "utf8"
|
|
2169
2204
|
});
|
|
2170
2205
|
if (rgResult.status === 0 && rgResult.stdout) {
|
|
2171
|
-
|
|
2206
|
+
const results = rgResult.stdout.trim().split("\n").filter(Boolean).slice(0, FILE_LIMITS.MAX_SEARCH_RESULTS).map((line) => {
|
|
2172
2207
|
const [file, lineNo, ...rest] = line.split(":");
|
|
2173
2208
|
return {
|
|
2174
2209
|
file: path2.relative(this.workspaceRoot, path2.join(searchDir, file)),
|
|
@@ -2176,6 +2211,7 @@ var FileActionManager = class {
|
|
|
2176
2211
|
text: rest.join(":")
|
|
2177
2212
|
};
|
|
2178
2213
|
});
|
|
2214
|
+
return results;
|
|
2179
2215
|
}
|
|
2180
2216
|
return this.walkFallback(query, searchDir);
|
|
2181
2217
|
}
|
|
@@ -2300,13 +2336,37 @@ var FileActionManager = class {
|
|
|
2300
2336
|
if (!await fs3.pathExists(filePath)) {
|
|
2301
2337
|
return "";
|
|
2302
2338
|
}
|
|
2339
|
+
const stats = await fs3.stat(filePath);
|
|
2340
|
+
if (stats.size > FILE_LIMITS.MAX_READ_SIZE) {
|
|
2341
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
2342
|
+
const limitMB = (FILE_LIMITS.MAX_READ_SIZE / 1024 / 1024).toFixed(0);
|
|
2343
|
+
throw new Error(`File ${target} is too large (${sizeMB}MB). Maximum allowed: ${limitMB}MB`);
|
|
2344
|
+
}
|
|
2303
2345
|
return fs3.readFile(filePath, "utf8");
|
|
2304
2346
|
}
|
|
2305
2347
|
resolvePath(target) {
|
|
2306
2348
|
const normalized = path2.isAbsolute(target) ? target : path2.join(this.workspaceRoot, target);
|
|
2307
2349
|
const resolved = path2.resolve(normalized);
|
|
2308
|
-
|
|
2309
|
-
|
|
2350
|
+
let realPath;
|
|
2351
|
+
try {
|
|
2352
|
+
realPath = fs3.realpathSync(resolved);
|
|
2353
|
+
} catch {
|
|
2354
|
+
const parentDir = path2.dirname(resolved);
|
|
2355
|
+
try {
|
|
2356
|
+
const realParent = fs3.realpathSync(parentDir);
|
|
2357
|
+
realPath = path2.join(realParent, path2.basename(resolved));
|
|
2358
|
+
} catch {
|
|
2359
|
+
realPath = resolved;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
let realWorkspaceRoot;
|
|
2363
|
+
try {
|
|
2364
|
+
realWorkspaceRoot = fs3.realpathSync(this.workspaceRoot);
|
|
2365
|
+
} catch {
|
|
2366
|
+
realWorkspaceRoot = this.workspaceRoot;
|
|
2367
|
+
}
|
|
2368
|
+
const rootWithSep = realWorkspaceRoot.endsWith(path2.sep) ? realWorkspaceRoot : `${realWorkspaceRoot}${path2.sep}`;
|
|
2369
|
+
if (realPath !== realWorkspaceRoot && !realPath.startsWith(rootWithSep)) {
|
|
2310
2370
|
throw new Error(`Path ${target} escapes the workspace root ${this.workspaceRoot}`);
|
|
2311
2371
|
}
|
|
2312
2372
|
return resolved;
|
|
@@ -2314,7 +2374,7 @@ var FileActionManager = class {
|
|
|
2314
2374
|
walkFallback(query, baseDir) {
|
|
2315
2375
|
const hits = [];
|
|
2316
2376
|
const stack = [baseDir];
|
|
2317
|
-
while (stack.length) {
|
|
2377
|
+
while (stack.length && hits.length < FILE_LIMITS.MAX_SEARCH_RESULTS) {
|
|
2318
2378
|
const current = stack.pop();
|
|
2319
2379
|
if (!current) {
|
|
2320
2380
|
continue;
|
|
@@ -2336,7 +2396,8 @@ var FileActionManager = class {
|
|
|
2336
2396
|
} else if (stats.isFile()) {
|
|
2337
2397
|
const contents = fs3.readFileSync(current, "utf8");
|
|
2338
2398
|
const lines = contents.split(/\r?\n/);
|
|
2339
|
-
lines.
|
|
2399
|
+
for (let idx = 0; idx < lines.length && hits.length < FILE_LIMITS.MAX_SEARCH_RESULTS; idx++) {
|
|
2400
|
+
const line = lines[idx];
|
|
2340
2401
|
if (line.includes(query)) {
|
|
2341
2402
|
hits.push({
|
|
2342
2403
|
file: path2.relative(this.workspaceRoot, current),
|
|
@@ -2344,7 +2405,7 @@ var FileActionManager = class {
|
|
|
2344
2405
|
text: line.trim()
|
|
2345
2406
|
});
|
|
2346
2407
|
}
|
|
2347
|
-
}
|
|
2408
|
+
}
|
|
2348
2409
|
}
|
|
2349
2410
|
} catch {
|
|
2350
2411
|
continue;
|
|
@@ -3113,8 +3174,8 @@ var ProviderFactory = class {
|
|
|
3113
3174
|
|
|
3114
3175
|
// src/core/agent.ts
|
|
3115
3176
|
import chalk13 from "chalk";
|
|
3116
|
-
import
|
|
3117
|
-
import
|
|
3177
|
+
import fs20 from "fs-extra";
|
|
3178
|
+
import path19 from "path";
|
|
3118
3179
|
import { randomUUID } from "crypto";
|
|
3119
3180
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
3120
3181
|
import ora from "ora";
|
|
@@ -3334,12 +3395,12 @@ var MentionPreview = class {
|
|
|
3334
3395
|
const dir = parts.length ? parts.join("/") + "/" : "";
|
|
3335
3396
|
if (isSelected) {
|
|
3336
3397
|
const highlighted = chalk2.cyan(filename);
|
|
3337
|
-
const
|
|
3338
|
-
return `${pointer} ${
|
|
3398
|
+
const path22 = dir ? chalk2.gray(dir) : "";
|
|
3399
|
+
return `${pointer} ${path22}${highlighted}`;
|
|
3339
3400
|
}
|
|
3340
3401
|
const dimmedFilename = chalk2.white(filename);
|
|
3341
|
-
const
|
|
3342
|
-
return `${pointer} ${
|
|
3402
|
+
const path21 = dir ? chalk2.gray(dir) : "";
|
|
3403
|
+
return `${pointer} ${path21}${dimmedFilename}`;
|
|
3343
3404
|
}
|
|
3344
3405
|
const text = isSelected ? chalk2.cyan(entry) : entry;
|
|
3345
3406
|
return `${pointer} ${text}`;
|
|
@@ -3549,6 +3610,25 @@ function parseBase64DataUrl(dataUrl) {
|
|
|
3549
3610
|
return void 0;
|
|
3550
3611
|
}
|
|
3551
3612
|
}
|
|
3613
|
+
var VISION_MODELS = [
|
|
3614
|
+
"claude-3-opus",
|
|
3615
|
+
"claude-3-sonnet",
|
|
3616
|
+
"claude-3-haiku",
|
|
3617
|
+
"claude-3.5-sonnet",
|
|
3618
|
+
"claude-3.5-haiku",
|
|
3619
|
+
"claude-4",
|
|
3620
|
+
"gpt-4-vision",
|
|
3621
|
+
"gpt-4o",
|
|
3622
|
+
"gpt-4o-mini",
|
|
3623
|
+
"gemini-pro-vision",
|
|
3624
|
+
"gemini-1.5-pro",
|
|
3625
|
+
"gemini-1.5-flash",
|
|
3626
|
+
"gemini-2.0"
|
|
3627
|
+
];
|
|
3628
|
+
function supportsVision(model) {
|
|
3629
|
+
const lowerModel = model.toLowerCase();
|
|
3630
|
+
return VISION_MODELS.some((v) => lowerModel.includes(v.toLowerCase()));
|
|
3631
|
+
}
|
|
3552
3632
|
|
|
3553
3633
|
// src/ui/box.ts
|
|
3554
3634
|
import chalk3 from "chalk";
|
|
@@ -4041,6 +4121,25 @@ function calculateContextUsage(messages, tools, model) {
|
|
|
4041
4121
|
|
|
4042
4122
|
// src/actions/git.ts
|
|
4043
4123
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
4124
|
+
var GIT_SAFETY = {
|
|
4125
|
+
/** Branches where force push is blocked */
|
|
4126
|
+
PROTECTED_BRANCHES: ["main", "master", "develop", "production", "staging"],
|
|
4127
|
+
/** Maximum commits to push at once (to prevent accidental mass pushes) */
|
|
4128
|
+
MAX_COMMITS_PER_PUSH: 50
|
|
4129
|
+
};
|
|
4130
|
+
function getCurrentBranch(cwd) {
|
|
4131
|
+
const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf8" });
|
|
4132
|
+
return result.stdout?.trim() || "";
|
|
4133
|
+
}
|
|
4134
|
+
function getCommitsAhead(cwd, remote = "origin", branch) {
|
|
4135
|
+
const currentBranch = branch || getCurrentBranch(cwd);
|
|
4136
|
+
if (!currentBranch) return 0;
|
|
4137
|
+
const result = spawnSync3("git", ["rev-list", "--count", `${remote}/${currentBranch}..HEAD`], {
|
|
4138
|
+
cwd,
|
|
4139
|
+
encoding: "utf8"
|
|
4140
|
+
});
|
|
4141
|
+
return parseInt(result.stdout?.trim() || "0", 10) || 0;
|
|
4142
|
+
}
|
|
4044
4143
|
function applyGitPatch(cwd, patch) {
|
|
4045
4144
|
const result = spawnSync3("git", ["apply", "-"], {
|
|
4046
4145
|
cwd,
|
|
@@ -4239,6 +4338,12 @@ function gitCherryPickContinue(cwd) {
|
|
|
4239
4338
|
return "Cherry-pick continued";
|
|
4240
4339
|
}
|
|
4241
4340
|
function gitRebase(cwd, upstream, options = {}) {
|
|
4341
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
4342
|
+
if (GIT_SAFETY.PROTECTED_BRANCHES.includes(currentBranch)) {
|
|
4343
|
+
throw new Error(
|
|
4344
|
+
`Rebasing protected branch "${currentBranch}" is blocked. Protected branches should not be rebased as it rewrites history. Use merge instead, or switch to a feature branch first.`
|
|
4345
|
+
);
|
|
4346
|
+
}
|
|
4242
4347
|
const args = ["rebase"];
|
|
4243
4348
|
if (options.onto) {
|
|
4244
4349
|
args.push("--onto", options.onto);
|
|
@@ -4275,6 +4380,17 @@ function gitRebaseSkip(cwd) {
|
|
|
4275
4380
|
return "Skipped commit and continued rebase";
|
|
4276
4381
|
}
|
|
4277
4382
|
function gitMerge(cwd, branch, options = {}) {
|
|
4383
|
+
const localBranches = spawnSync3("git", ["branch", "--list"], { cwd, encoding: "utf8" });
|
|
4384
|
+
const remoteBranches = spawnSync3("git", ["branch", "-r", "--list"], { cwd, encoding: "utf8" });
|
|
4385
|
+
const allBranches = (localBranches.stdout || "") + (remoteBranches.stdout || "");
|
|
4386
|
+
const branchExists = allBranches.split("\n").some(
|
|
4387
|
+
(b) => b.trim().replace("* ", "") === branch || b.trim() === `origin/${branch}` || b.trim() === branch
|
|
4388
|
+
);
|
|
4389
|
+
if (!branchExists) {
|
|
4390
|
+
throw new Error(
|
|
4391
|
+
`Branch "${branch}" not found locally or in remotes. For security, only existing branches can be merged. Run 'git fetch' first if the branch exists remotely.`
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4278
4394
|
const args = ["merge"];
|
|
4279
4395
|
if (options.noCommit) {
|
|
4280
4396
|
args.push("--no-commit");
|
|
@@ -4325,6 +4441,16 @@ function gitAdd(cwd, paths) {
|
|
|
4325
4441
|
return result.stdout || `Staged ${paths.join(", ")}`;
|
|
4326
4442
|
}
|
|
4327
4443
|
function gitReset(cwd, mode = "mixed", ref) {
|
|
4444
|
+
if (mode === "hard") {
|
|
4445
|
+
const currentBranch = getCurrentBranch(cwd);
|
|
4446
|
+
if (GIT_SAFETY.PROTECTED_BRANCHES.includes(currentBranch)) {
|
|
4447
|
+
throw new Error(
|
|
4448
|
+
`Hard reset on protected branch "${currentBranch}" is blocked for safety.
|
|
4449
|
+
Protected branches: ${GIT_SAFETY.PROTECTED_BRANCHES.join(", ")}
|
|
4450
|
+
Use soft or mixed reset instead, or switch to a feature branch.`
|
|
4451
|
+
);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4328
4454
|
const args = ["reset", `--${mode}`];
|
|
4329
4455
|
if (ref) {
|
|
4330
4456
|
args.push(ref);
|
|
@@ -4519,9 +4645,22 @@ function gitPull(cwd, remote, branch) {
|
|
|
4519
4645
|
return result.stdout || "Pulled";
|
|
4520
4646
|
}
|
|
4521
4647
|
function gitPush(cwd, remote, branch, options = {}) {
|
|
4648
|
+
const targetRemote = remote || "origin";
|
|
4649
|
+
const targetBranch = branch || getCurrentBranch(cwd);
|
|
4650
|
+
if (options.force && GIT_SAFETY.PROTECTED_BRANCHES.includes(targetBranch)) {
|
|
4651
|
+
throw new Error(
|
|
4652
|
+
`Force push to protected branch "${targetBranch}" is blocked. Protected branches: ${GIT_SAFETY.PROTECTED_BRANCHES.join(", ")}`
|
|
4653
|
+
);
|
|
4654
|
+
}
|
|
4655
|
+
const commitsAhead = getCommitsAhead(cwd, targetRemote, targetBranch);
|
|
4656
|
+
if (commitsAhead > GIT_SAFETY.MAX_COMMITS_PER_PUSH) {
|
|
4657
|
+
throw new Error(
|
|
4658
|
+
`Too many commits to push (${commitsAhead}). Maximum allowed: ${GIT_SAFETY.MAX_COMMITS_PER_PUSH}. This limit exists to prevent accidental large pushes. Push manually if intentional.`
|
|
4659
|
+
);
|
|
4660
|
+
}
|
|
4522
4661
|
const args = ["push"];
|
|
4523
4662
|
if (options.force) {
|
|
4524
|
-
args.push("--force");
|
|
4663
|
+
args.push("--force-with-lease");
|
|
4525
4664
|
}
|
|
4526
4665
|
if (options.setUpstream) {
|
|
4527
4666
|
args.push("--set-upstream");
|
|
@@ -4895,6 +5034,7 @@ var SLASH_COMMANDS = [
|
|
|
4895
5034
|
metadata20,
|
|
4896
5035
|
metadata21,
|
|
4897
5036
|
metadata22,
|
|
5037
|
+
installMetadata,
|
|
4898
5038
|
metadata23,
|
|
4899
5039
|
metadata24
|
|
4900
5040
|
];
|
|
@@ -5944,25 +6084,30 @@ var ToolManager = class _ToolManager {
|
|
|
5944
6084
|
const requiresApproval = this.toolFilter.requiresApproval(call.tool, definition?.requiresApproval);
|
|
5945
6085
|
if (requiresApproval) {
|
|
5946
6086
|
let message = definition?.approvalMessage ?? `Allow tool ${call.tool}?`;
|
|
6087
|
+
let permContext = { tool: call.tool };
|
|
5947
6088
|
if (call.tool === "run_command" && call.args) {
|
|
5948
|
-
const cmd = call.args.command || "";
|
|
6089
|
+
const cmd = String(call.args.command || "");
|
|
5949
6090
|
const args = Array.isArray(call.args.args) ? call.args.args.join(" ") : "";
|
|
5950
6091
|
const fullCommand = args ? `${cmd} ${args}` : cmd;
|
|
5951
6092
|
const dir = call.args.directory ? ` (in ${call.args.directory})` : "";
|
|
5952
6093
|
message = `Run this command${dir}?
|
|
5953
6094
|
$ ${fullCommand}`;
|
|
6095
|
+
permContext.command = fullCommand;
|
|
5954
6096
|
} else if (call.tool === "delete_path" && call.args?.path) {
|
|
5955
6097
|
message = `Delete this path?
|
|
5956
6098
|
${call.args.path}`;
|
|
6099
|
+
permContext.path = String(call.args.path);
|
|
5957
6100
|
} else if (call.tool === "write_file" && call.args?.path) {
|
|
5958
6101
|
message = `Write to this file?
|
|
5959
6102
|
${call.args.path}`;
|
|
6103
|
+
permContext.path = String(call.args.path);
|
|
5960
6104
|
} else if (call.tool === "multi_file_edit" && call.args?.file_path) {
|
|
5961
6105
|
const editCount = Array.isArray(call.args.edits) ? call.args.edits.length : 0;
|
|
5962
6106
|
message = `Edit this file (${editCount} change${editCount === 1 ? "" : "s"})?
|
|
5963
6107
|
${call.args.file_path}`;
|
|
6108
|
+
permContext.path = String(call.args.file_path);
|
|
5964
6109
|
}
|
|
5965
|
-
const confirmed = await this.confirmApproval(message);
|
|
6110
|
+
const confirmed = await this.confirmApproval(message, permContext);
|
|
5966
6111
|
if (!confirmed) {
|
|
5967
6112
|
results.push({
|
|
5968
6113
|
tool: call.tool,
|
|
@@ -7600,6 +7745,96 @@ function mergePermissions(globalSettings, localSettings) {
|
|
|
7600
7745
|
}
|
|
7601
7746
|
|
|
7602
7747
|
// src/permissions/PermissionManager.ts
|
|
7748
|
+
var DEFAULT_SECURITY_BLACKLIST = [
|
|
7749
|
+
// === Sensitive Files (read/write blocked) ===
|
|
7750
|
+
// Environment files with secrets
|
|
7751
|
+
"read_file:.env",
|
|
7752
|
+
"read_file:.env.*",
|
|
7753
|
+
"read_file:*.env",
|
|
7754
|
+
"write_file:.env",
|
|
7755
|
+
"write_file:.env.*",
|
|
7756
|
+
"write_file:*.env",
|
|
7757
|
+
// Git credentials and config
|
|
7758
|
+
"read_file:.git/config",
|
|
7759
|
+
"write_file:.git/config",
|
|
7760
|
+
"read_file:.git/credentials",
|
|
7761
|
+
"write_file:.git/credentials",
|
|
7762
|
+
"read_file:.gitconfig",
|
|
7763
|
+
"write_file:.gitconfig",
|
|
7764
|
+
// SSH keys
|
|
7765
|
+
"read_file:*/.ssh/*",
|
|
7766
|
+
"write_file:*/.ssh/*",
|
|
7767
|
+
"read_file:*/id_rsa*",
|
|
7768
|
+
"read_file:*/id_ed25519*",
|
|
7769
|
+
"read_file:*/id_ecdsa*",
|
|
7770
|
+
"write_file:*/id_rsa*",
|
|
7771
|
+
"write_file:*/id_ed25519*",
|
|
7772
|
+
// Cloud credentials
|
|
7773
|
+
"read_file:*/.aws/credentials",
|
|
7774
|
+
"read_file:*/.aws/config",
|
|
7775
|
+
"write_file:*/.aws/*",
|
|
7776
|
+
"read_file:*/.azure/*",
|
|
7777
|
+
"write_file:*/.azure/*",
|
|
7778
|
+
"read_file:*/.gcloud/*",
|
|
7779
|
+
"write_file:*/.gcloud/*",
|
|
7780
|
+
// Private keys and certificates
|
|
7781
|
+
"read_file:*.pem",
|
|
7782
|
+
"read_file:*.key",
|
|
7783
|
+
"read_file:*.p12",
|
|
7784
|
+
"read_file:*.pfx",
|
|
7785
|
+
"write_file:*.pem",
|
|
7786
|
+
"write_file:*.key",
|
|
7787
|
+
// GPG keys
|
|
7788
|
+
"read_file:*/.gnupg/*",
|
|
7789
|
+
"write_file:*/.gnupg/*",
|
|
7790
|
+
// NPM tokens
|
|
7791
|
+
"read_file:.npmrc",
|
|
7792
|
+
"write_file:.npmrc",
|
|
7793
|
+
// Docker credentials
|
|
7794
|
+
"read_file:*/.docker/config.json",
|
|
7795
|
+
"write_file:*/.docker/config.json",
|
|
7796
|
+
// Kubernetes credentials
|
|
7797
|
+
"read_file:*/.kube/config",
|
|
7798
|
+
"write_file:*/.kube/config",
|
|
7799
|
+
// === Dangerous Commands ===
|
|
7800
|
+
// Environment exposure
|
|
7801
|
+
"run_command:printenv",
|
|
7802
|
+
"run_command:printenv *",
|
|
7803
|
+
"run_command:env",
|
|
7804
|
+
"run_command:export",
|
|
7805
|
+
"run_command:set",
|
|
7806
|
+
// System information
|
|
7807
|
+
"run_command:cat /etc/passwd",
|
|
7808
|
+
"run_command:cat /etc/shadow",
|
|
7809
|
+
"run_command:cat /etc/sudoers",
|
|
7810
|
+
// Privilege escalation
|
|
7811
|
+
"run_command:sudo *",
|
|
7812
|
+
"run_command:su *",
|
|
7813
|
+
"run_command:doas *",
|
|
7814
|
+
// Destructive operations
|
|
7815
|
+
"run_command:rm -rf /",
|
|
7816
|
+
"run_command:rm -rf /*",
|
|
7817
|
+
"run_command:rm -rf ~",
|
|
7818
|
+
"run_command:rm -rf ~/*",
|
|
7819
|
+
"run_command:dd if=* of=/dev/*",
|
|
7820
|
+
"run_command:mkfs*",
|
|
7821
|
+
"run_command:wipefs*",
|
|
7822
|
+
"run_command:shred*",
|
|
7823
|
+
// Remote code execution
|
|
7824
|
+
"run_command:curl * | *sh",
|
|
7825
|
+
"run_command:wget * | *sh",
|
|
7826
|
+
"run_command:curl *|*sh",
|
|
7827
|
+
"run_command:wget *|*sh",
|
|
7828
|
+
// Network tools that can exfiltrate
|
|
7829
|
+
"run_command:nc -e*",
|
|
7830
|
+
"run_command:ncat -e*",
|
|
7831
|
+
"run_command:netcat -e*",
|
|
7832
|
+
// Credential theft
|
|
7833
|
+
"run_command:cat */.ssh/*",
|
|
7834
|
+
"run_command:cat */.aws/*",
|
|
7835
|
+
"run_command:cat *.pem",
|
|
7836
|
+
"run_command:cat *.key"
|
|
7837
|
+
];
|
|
7603
7838
|
var PermissionManager = class {
|
|
7604
7839
|
constructor(options = {}) {
|
|
7605
7840
|
this.sessionCache = /* @__PURE__ */ new Map();
|
|
@@ -7608,10 +7843,11 @@ var PermissionManager = class {
|
|
|
7608
7843
|
const settings = isOptions ? options.settings ?? {} : options;
|
|
7609
7844
|
this.onPersist = isOptions ? options.onPersist : void 0;
|
|
7610
7845
|
this.workspaceRoot = isOptions ? options.workspaceRoot : void 0;
|
|
7846
|
+
const userBlacklist = settings.blacklist ?? [];
|
|
7611
7847
|
this.settings = {
|
|
7612
7848
|
mode: "interactive",
|
|
7613
7849
|
whitelist: [],
|
|
7614
|
-
blacklist:
|
|
7850
|
+
blacklist: userBlacklist,
|
|
7615
7851
|
rules: [],
|
|
7616
7852
|
rememberSession: true,
|
|
7617
7853
|
...settings
|
|
@@ -7655,6 +7891,9 @@ var PermissionManager = class {
|
|
|
7655
7891
|
* Check if an action should be allowed, denied, or prompted
|
|
7656
7892
|
*/
|
|
7657
7893
|
checkPermission(context) {
|
|
7894
|
+
if (this.isSecurityBlacklisted(context)) {
|
|
7895
|
+
return { allowed: false, reason: "blacklisted" };
|
|
7896
|
+
}
|
|
7658
7897
|
const cacheKey = this.getCacheKey(context);
|
|
7659
7898
|
if (this.settings.rememberSession && this.sessionCache.has(cacheKey)) {
|
|
7660
7899
|
return {
|
|
@@ -7733,12 +7972,19 @@ var PermissionManager = class {
|
|
|
7733
7972
|
return `${tool}:${value}`;
|
|
7734
7973
|
}
|
|
7735
7974
|
/**
|
|
7736
|
-
* Check if context matches
|
|
7975
|
+
* Check if context matches the immutable security blacklist
|
|
7976
|
+
* This check CANNOT be bypassed by any mode, whitelist, or user setting
|
|
7977
|
+
*/
|
|
7978
|
+
isSecurityBlacklisted(context) {
|
|
7979
|
+
return DEFAULT_SECURITY_BLACKLIST.some((pattern) => this.matchesPattern(context, pattern));
|
|
7980
|
+
}
|
|
7981
|
+
/**
|
|
7982
|
+
* Check if context matches user blacklist (can be modified by user)
|
|
7737
7983
|
*/
|
|
7738
7984
|
isBlacklisted(context) {
|
|
7739
7985
|
const merged = this.getMergedSettings();
|
|
7740
|
-
const
|
|
7741
|
-
return
|
|
7986
|
+
const userBlacklist = merged.blacklist || [];
|
|
7987
|
+
return userBlacklist.some((pattern) => this.matchesPattern(context, pattern));
|
|
7742
7988
|
}
|
|
7743
7989
|
/**
|
|
7744
7990
|
* Check if context matches whitelist (uses merged global + local settings)
|
|
@@ -8332,7 +8578,7 @@ var SecurityScanner = class {
|
|
|
8332
8578
|
|
|
8333
8579
|
// src/core/actionExecutor.ts
|
|
8334
8580
|
import { execSync as execSync2 } from "child_process";
|
|
8335
|
-
var ActionExecutor = class {
|
|
8581
|
+
var ActionExecutor = class _ActionExecutor {
|
|
8336
8582
|
constructor(deps) {
|
|
8337
8583
|
this.deps = deps;
|
|
8338
8584
|
this.searchCache = /* @__PURE__ */ new Map();
|
|
@@ -8420,8 +8666,8 @@ var ActionExecutor = class {
|
|
|
8420
8666
|
throw new Error(`write_file requires a "path" argument. Received arguments: [${receivedKeys}]`);
|
|
8421
8667
|
}
|
|
8422
8668
|
const filePath = this.resolveWorkspacePath(action.path);
|
|
8423
|
-
const
|
|
8424
|
-
const exists = this.files.root && await
|
|
8669
|
+
const fs22 = await import("fs-extra");
|
|
8670
|
+
const exists = this.files.root && await fs22.pathExists(filePath);
|
|
8425
8671
|
const oldContent = exists ? await this.files.readFile(action.path) : "";
|
|
8426
8672
|
const newContent = this.pickText(action.contents, action.content) ?? "";
|
|
8427
8673
|
if (!exists) {
|
|
@@ -8442,7 +8688,8 @@ var ActionExecutor = class {
|
|
|
8442
8688
|
const preview = newContent.length > 500 ? newContent.substring(0, 500) + "\n... (truncated)" : newContent;
|
|
8443
8689
|
console.log(chalk6.gray(preview));
|
|
8444
8690
|
const confirmed = await this.confirmDangerousAction(
|
|
8445
|
-
`Create new file ${action.path}
|
|
8691
|
+
`Create new file ${action.path}?`,
|
|
8692
|
+
{ tool: "write_file", path: action.path }
|
|
8446
8693
|
);
|
|
8447
8694
|
await this.permissionManager.recordDecision(permContext, confirmed);
|
|
8448
8695
|
if (!confirmed) {
|
|
@@ -8546,7 +8793,10 @@ ${hit.snippet}`).join("\n\n");
|
|
|
8546
8793
|
if (!action.path) {
|
|
8547
8794
|
throw new Error('delete_path requires a "path" argument.');
|
|
8548
8795
|
}
|
|
8549
|
-
const confirmed = await this.confirmDangerousAction(
|
|
8796
|
+
const confirmed = await this.confirmDangerousAction(
|
|
8797
|
+
`Delete ${action.path}?`,
|
|
8798
|
+
{ tool: "delete_path", path: action.path }
|
|
8799
|
+
);
|
|
8550
8800
|
if (!confirmed) {
|
|
8551
8801
|
return `Skipped deleting ${action.path}`;
|
|
8552
8802
|
}
|
|
@@ -8801,8 +9051,8 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
|
|
|
8801
9051
|
const lines = [chalk6.cyan("\u{1F504} Worktree Sync Results:"), ""];
|
|
8802
9052
|
if (result.synced.length > 0) {
|
|
8803
9053
|
lines.push(chalk6.green(`Synced (${result.synced.length}):`));
|
|
8804
|
-
for (const
|
|
8805
|
-
lines.push(` \u2713 ${
|
|
9054
|
+
for (const path21 of result.synced) {
|
|
9055
|
+
lines.push(` \u2713 ${path21}`);
|
|
8806
9056
|
}
|
|
8807
9057
|
lines.push("");
|
|
8808
9058
|
}
|
|
@@ -9141,10 +9391,37 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
|
|
|
9141
9391
|
if (builtInNames.includes(action.name)) {
|
|
9142
9392
|
throw new Error(`Cannot create meta-tool "${action.name}": conflicts with built-in tool`);
|
|
9143
9393
|
}
|
|
9144
|
-
const dangerousPatterns = [
|
|
9145
|
-
|
|
9146
|
-
|
|
9147
|
-
|
|
9394
|
+
const dangerousPatterns = [
|
|
9395
|
+
// Destructive file operations
|
|
9396
|
+
{ pattern: /rm\s+(-[rf]+\s+)*\/(?!\w)/i, description: "rm with root path" },
|
|
9397
|
+
{ pattern: /rm\s+.*--no-preserve-root/i, description: "rm --no-preserve-root" },
|
|
9398
|
+
{ pattern: /dd\s+.*(?:of|if)=\/dev\/[sh]d/i, description: "dd to disk device" },
|
|
9399
|
+
{ pattern: /mkfs\./i, description: "filesystem format" },
|
|
9400
|
+
{ pattern: /wipefs/i, description: "disk wipe" },
|
|
9401
|
+
// Privilege escalation
|
|
9402
|
+
{ pattern: /\bsudo\s/i, description: "sudo command" },
|
|
9403
|
+
{ pattern: /\bsu\s+-?\s*\w/i, description: "su command" },
|
|
9404
|
+
{ pattern: /chmod\s+[0-7]*7[0-7]*/i, description: "world-writable chmod" },
|
|
9405
|
+
{ pattern: /chown\s+root/i, description: "chown to root" },
|
|
9406
|
+
// Remote code execution
|
|
9407
|
+
{ pattern: /curl\s+.*\|\s*(ba)?sh/i, description: "curl | bash" },
|
|
9408
|
+
{ pattern: /wget\s+.*\|\s*(ba)?sh/i, description: "wget | sh" },
|
|
9409
|
+
{ pattern: /\beval\s+[`$]/i, description: "eval with expansion" },
|
|
9410
|
+
// Fork bomb and resource exhaustion
|
|
9411
|
+
{ pattern: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/i, description: "fork bomb" },
|
|
9412
|
+
{ pattern: /while\s+true.*do.*done/i, description: "infinite loop" },
|
|
9413
|
+
// Reverse shell indicators
|
|
9414
|
+
{ pattern: /nc\s+.*-e\s*\/bin/i, description: "netcat reverse shell" },
|
|
9415
|
+
{ pattern: /ncat\s+.*-e\s*\/bin/i, description: "ncat reverse shell" },
|
|
9416
|
+
{ pattern: /bash\s+-i\s+>&?\s*\/dev\/tcp/i, description: "bash reverse shell" },
|
|
9417
|
+
// Dangerous network operations
|
|
9418
|
+
{ pattern: /iptables\s+-F/i, description: "flush firewall rules" },
|
|
9419
|
+
// Crypto operations that could lock out user
|
|
9420
|
+
{ pattern: /gpg\s+.*--encrypt.*-r\s+\S+\s+\//i, description: "gpg encrypt root" }
|
|
9421
|
+
];
|
|
9422
|
+
for (const { pattern, description } of dangerousPatterns) {
|
|
9423
|
+
if (pattern.test(action.handler)) {
|
|
9424
|
+
throw new Error(`Handler contains dangerous pattern: ${description}`);
|
|
9148
9425
|
}
|
|
9149
9426
|
}
|
|
9150
9427
|
const saved = await this.toolsRegistry.saveMetaTool({
|
|
@@ -9337,7 +9614,10 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
|
|
|
9337
9614
|
if (this.isDestructiveCommand(definition.command)) {
|
|
9338
9615
|
console.log(chalk6.red("Warning: command may be destructive."));
|
|
9339
9616
|
}
|
|
9340
|
-
const answer = await this.confirmDangerousAction(
|
|
9617
|
+
const answer = await this.confirmDangerousAction(
|
|
9618
|
+
"Add and run this custom command?",
|
|
9619
|
+
{ tool: "run_command", command: definition.command }
|
|
9620
|
+
);
|
|
9341
9621
|
if (!answer) {
|
|
9342
9622
|
return "Custom command rejected by user.";
|
|
9343
9623
|
}
|
|
@@ -9350,6 +9630,19 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
|
|
|
9350
9630
|
const lowered = command.toLowerCase();
|
|
9351
9631
|
return lowered.includes("rm ") || lowered.includes("sudo ") || lowered.includes("dd ");
|
|
9352
9632
|
}
|
|
9633
|
+
static {
|
|
9634
|
+
/**
|
|
9635
|
+
* Shell metacharacters that could enable command injection
|
|
9636
|
+
*/
|
|
9637
|
+
this.SHELL_METACHARACTERS = /[|;&$`><(){}[\]!#*?~'"\\]/;
|
|
9638
|
+
}
|
|
9639
|
+
/**
|
|
9640
|
+
* Safely escape a value for shell interpolation
|
|
9641
|
+
* Uses single quotes which prevent all shell expansion except for single quotes themselves
|
|
9642
|
+
*/
|
|
9643
|
+
shellEscape(value) {
|
|
9644
|
+
return "'" + value.replace(/'/g, `'"'"'`) + "'";
|
|
9645
|
+
}
|
|
9353
9646
|
/**
|
|
9354
9647
|
* Execute a dynamic meta-tool by substituting {{param}} placeholders
|
|
9355
9648
|
*/
|
|
@@ -9363,18 +9656,39 @@ ${result.removed.map((p) => ` - ${p}`).join("\n")}`;
|
|
|
9363
9656
|
if (value === void 0 || value === null) {
|
|
9364
9657
|
throw new Error(`Missing required parameter "${paramName}" for meta-tool "${metaTool.name}"`);
|
|
9365
9658
|
}
|
|
9366
|
-
const
|
|
9367
|
-
|
|
9659
|
+
const stringValue = String(value);
|
|
9660
|
+
let safeValue;
|
|
9661
|
+
if (_ActionExecutor.SHELL_METACHARACTERS.test(stringValue)) {
|
|
9662
|
+
safeValue = this.shellEscape(stringValue);
|
|
9663
|
+
console.log(chalk6.yellow(` \u26A0 Parameter "${paramName}" contains shell metacharacters, escaped for safety`));
|
|
9664
|
+
} else {
|
|
9665
|
+
safeValue = stringValue;
|
|
9666
|
+
}
|
|
9667
|
+
command = command.replace(new RegExp(`\\{\\{${paramName}\\}\\}`, "g"), safeValue);
|
|
9368
9668
|
}
|
|
9369
9669
|
console.log(chalk6.cyan(`
|
|
9370
9670
|
\u{1F527} Running meta-tool: ${metaTool.name}`));
|
|
9371
9671
|
console.log(chalk6.gray(` $ ${command}`));
|
|
9372
|
-
const result = await runCommand(command, [], this.runtime.workspaceRoot);
|
|
9672
|
+
const result = await runCommand(command, [], this.runtime.workspaceRoot, { shell: true });
|
|
9373
9673
|
return [`$ ${command}`, result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
9374
9674
|
}
|
|
9375
9675
|
applySearchReplaceBlocks(content, blocks) {
|
|
9376
|
-
const MARKERS = { search: "<<<<<<< SEARCH", div: "=======", replace: ">>>>>>> REPLACE" };
|
|
9377
9676
|
let result = content;
|
|
9677
|
+
if (blocks.includes("SEARCH:") && blocks.includes("REPLACE:") && !blocks.includes("<<<<<<< SEARCH")) {
|
|
9678
|
+
const searchIdx = blocks.indexOf("SEARCH:");
|
|
9679
|
+
const replaceIdx = blocks.indexOf("REPLACE:");
|
|
9680
|
+
if (searchIdx !== -1 && replaceIdx !== -1 && replaceIdx > searchIdx) {
|
|
9681
|
+
const searchText = blocks.slice(searchIdx + 7, replaceIdx).trim();
|
|
9682
|
+
const replaceText = blocks.slice(replaceIdx + 8).trim();
|
|
9683
|
+
const idx = result.indexOf(searchText);
|
|
9684
|
+
if (idx === -1) {
|
|
9685
|
+
throw new Error(`SEARCH text not found: "${searchText.slice(0, 50)}..."`);
|
|
9686
|
+
}
|
|
9687
|
+
result = result.slice(0, idx) + replaceText + result.slice(idx + searchText.length);
|
|
9688
|
+
return result;
|
|
9689
|
+
}
|
|
9690
|
+
}
|
|
9691
|
+
const MARKERS = { search: "<<<<<<< SEARCH", div: "=======", replace: ">>>>>>> REPLACE" };
|
|
9378
9692
|
let remaining = blocks;
|
|
9379
9693
|
while (remaining.includes(MARKERS.search)) {
|
|
9380
9694
|
const searchStart = remaining.indexOf(MARKERS.search);
|
|
@@ -9688,6 +10002,12 @@ var SlashCommandHandler = class {
|
|
|
9688
10002
|
this.commandMap = /* @__PURE__ */ new Map();
|
|
9689
10003
|
commands.forEach((cmd) => this.commandMap.set(cmd.command, cmd));
|
|
9690
10004
|
}
|
|
10005
|
+
/**
|
|
10006
|
+
* Check if a command is supported (exists in the command map)
|
|
10007
|
+
*/
|
|
10008
|
+
isCommandSupported(command) {
|
|
10009
|
+
return this.commandMap.has(command);
|
|
10010
|
+
}
|
|
9691
10011
|
async handle(command, args = []) {
|
|
9692
10012
|
const meta = this.commandMap.get(command);
|
|
9693
10013
|
if (!meta) {
|
|
@@ -9718,7 +10038,7 @@ var SlashCommandHandler = class {
|
|
|
9718
10038
|
return help();
|
|
9719
10039
|
}
|
|
9720
10040
|
case "/agents": {
|
|
9721
|
-
const { handler } = await import("./agents-
|
|
10041
|
+
const { handler } = await import("./agents-NB5VQN6H.js");
|
|
9722
10042
|
const output = await handler();
|
|
9723
10043
|
if (output) {
|
|
9724
10044
|
console.log(output);
|
|
@@ -9727,11 +10047,11 @@ var SlashCommandHandler = class {
|
|
|
9727
10047
|
}
|
|
9728
10048
|
case "/agents new":
|
|
9729
10049
|
case "/agents-new": {
|
|
9730
|
-
const { createAgent } = await import("./agents-new-
|
|
10050
|
+
const { createAgent } = await import("./agents-new-M325HGWT.js");
|
|
9731
10051
|
return createAgent(this.ctx);
|
|
9732
10052
|
}
|
|
9733
10053
|
case "/feedback": {
|
|
9734
|
-
const { feedback } = await import("./feedback-
|
|
10054
|
+
const { feedback } = await import("./feedback-YGSYBQEW.js");
|
|
9735
10055
|
return feedback(this.ctx);
|
|
9736
10056
|
}
|
|
9737
10057
|
case "/resume": {
|
|
@@ -9794,40 +10114,56 @@ var SlashCommandHandler = class {
|
|
|
9794
10114
|
return null;
|
|
9795
10115
|
}
|
|
9796
10116
|
case "/status": {
|
|
9797
|
-
const { status } = await import("./status-
|
|
10117
|
+
const { status } = await import("./status-6FY6RKIS.js");
|
|
9798
10118
|
return status(this.ctx);
|
|
9799
10119
|
}
|
|
9800
10120
|
case "/login": {
|
|
9801
|
-
const { login } = await import("./login-
|
|
10121
|
+
const { login } = await import("./login-XUVEFKCR.js");
|
|
9802
10122
|
return login({ config: this.ctx.config });
|
|
9803
10123
|
}
|
|
9804
10124
|
case "/logout": {
|
|
9805
|
-
const { logout } = await import("./logout-
|
|
10125
|
+
const { logout } = await import("./logout-75YLPOBK.js");
|
|
9806
10126
|
return logout({ config: this.ctx.config });
|
|
9807
10127
|
}
|
|
9808
10128
|
case "/permissions": {
|
|
9809
|
-
const { permissions } = await import("./permissions-
|
|
10129
|
+
const { permissions } = await import("./permissions-3GS4ZWVA.js");
|
|
9810
10130
|
return permissions({ permissionManager: this.ctx.permissionManager });
|
|
9811
10131
|
}
|
|
9812
10132
|
case "/skills": {
|
|
9813
|
-
const { skills } = await import("./skills-
|
|
10133
|
+
const { skills } = await import("./skills-3M26KASS.js");
|
|
9814
10134
|
if (!this.ctx.skillsRegistry) {
|
|
9815
|
-
|
|
9816
|
-
return null;
|
|
10135
|
+
return "Skills registry not available.";
|
|
9817
10136
|
}
|
|
9818
|
-
return skills({
|
|
10137
|
+
return skills({
|
|
10138
|
+
skillsRegistry: this.ctx.skillsRegistry,
|
|
10139
|
+
workspaceRoot: this.ctx.workspaceRoot
|
|
10140
|
+
}, args);
|
|
10141
|
+
}
|
|
10142
|
+
case "/skills install": {
|
|
10143
|
+
const { skillsInstall } = await import("./skills-install-WJ2LDRQG.js");
|
|
10144
|
+
if (!this.ctx.skillsRegistry) {
|
|
10145
|
+
return "Skills registry not available.";
|
|
10146
|
+
}
|
|
10147
|
+
const skillName = args.join(" ").trim() || void 0;
|
|
10148
|
+
return skillsInstall({
|
|
10149
|
+
skillsRegistry: this.ctx.skillsRegistry,
|
|
10150
|
+
workspaceRoot: this.ctx.workspaceRoot
|
|
10151
|
+
}, skillName);
|
|
9819
10152
|
}
|
|
9820
10153
|
case "/skills new":
|
|
9821
10154
|
case "/skills-new": {
|
|
9822
|
-
const { createSkill } = await import("./skills-new-
|
|
10155
|
+
const { createSkill } = await import("./skills-new-KIBUN63X.js");
|
|
9823
10156
|
if (!this.ctx.skillsRegistry) {
|
|
9824
|
-
|
|
9825
|
-
return null;
|
|
10157
|
+
return "Skills registry not available.";
|
|
9826
10158
|
}
|
|
9827
|
-
return createSkill({
|
|
10159
|
+
return createSkill({
|
|
10160
|
+
llm: this.ctx.llm,
|
|
10161
|
+
skillsRegistry: this.ctx.skillsRegistry,
|
|
10162
|
+
workspaceRoot: this.ctx.workspaceRoot
|
|
10163
|
+
});
|
|
9828
10164
|
}
|
|
9829
10165
|
case "/theme": {
|
|
9830
|
-
const { theme } = await import("./theme-
|
|
10166
|
+
const { theme } = await import("./theme-XNZ2X6HE.js");
|
|
9831
10167
|
if (!this.ctx.config) {
|
|
9832
10168
|
console.log(chalk7.yellow("Config not available for theme selection."));
|
|
9833
10169
|
return null;
|
|
@@ -11270,8 +11606,18 @@ var FeedbackManager = class {
|
|
|
11270
11606
|
console.log(chalk10.gray(" Help us improve Autohand (takes 10 seconds)"));
|
|
11271
11607
|
console.log(chalk10.cyan("\u2501".repeat(50)));
|
|
11272
11608
|
console.log();
|
|
11609
|
+
const safePrompt = async (config) => {
|
|
11610
|
+
try {
|
|
11611
|
+
return await Enquirer.prompt(config);
|
|
11612
|
+
} catch (error) {
|
|
11613
|
+
if (error?.code === "ERR_USE_AFTER_CLOSE") {
|
|
11614
|
+
return null;
|
|
11615
|
+
}
|
|
11616
|
+
throw error;
|
|
11617
|
+
}
|
|
11618
|
+
};
|
|
11273
11619
|
try {
|
|
11274
|
-
const npsResult = await
|
|
11620
|
+
const npsResult = await safePrompt({
|
|
11275
11621
|
type: "select",
|
|
11276
11622
|
name: "score",
|
|
11277
11623
|
message: "How would you rate your experience?",
|
|
@@ -11284,6 +11630,11 @@ var FeedbackManager = class {
|
|
|
11284
11630
|
{ name: "skip", message: `${chalk10.gray("Skip")}` }
|
|
11285
11631
|
]
|
|
11286
11632
|
});
|
|
11633
|
+
if (!npsResult) {
|
|
11634
|
+
this.state.dismissed++;
|
|
11635
|
+
this.saveState();
|
|
11636
|
+
return false;
|
|
11637
|
+
}
|
|
11287
11638
|
if (npsResult.score === "skip") {
|
|
11288
11639
|
this.state.dismissed++;
|
|
11289
11640
|
this.saveState();
|
|
@@ -11295,26 +11646,28 @@ var FeedbackManager = class {
|
|
|
11295
11646
|
let improvement;
|
|
11296
11647
|
let recommend;
|
|
11297
11648
|
if (npsScore >= 4) {
|
|
11298
|
-
const followUp = await
|
|
11649
|
+
const followUp = await safePrompt({
|
|
11299
11650
|
type: "input",
|
|
11300
11651
|
name: "reason",
|
|
11301
11652
|
message: "What do you like most about Autohand? (optional, press Enter to skip)"
|
|
11302
11653
|
});
|
|
11303
|
-
reason = followUp
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11654
|
+
reason = followUp?.reason || void 0;
|
|
11655
|
+
if (followUp) {
|
|
11656
|
+
const recResult = await safePrompt({
|
|
11657
|
+
type: "confirm",
|
|
11658
|
+
name: "recommend",
|
|
11659
|
+
message: "Would you recommend Autohand to a colleague?",
|
|
11660
|
+
initial: true
|
|
11661
|
+
});
|
|
11662
|
+
recommend = recResult?.recommend;
|
|
11663
|
+
}
|
|
11311
11664
|
} else {
|
|
11312
|
-
const followUp = await
|
|
11665
|
+
const followUp = await safePrompt({
|
|
11313
11666
|
type: "input",
|
|
11314
11667
|
name: "improvement",
|
|
11315
11668
|
message: "What could we do better? (optional, press Enter to skip)"
|
|
11316
11669
|
});
|
|
11317
|
-
improvement = followUp
|
|
11670
|
+
improvement = followUp?.improvement || void 0;
|
|
11318
11671
|
}
|
|
11319
11672
|
const response = {
|
|
11320
11673
|
npsScore,
|
|
@@ -11341,6 +11694,9 @@ var FeedbackManager = class {
|
|
|
11341
11694
|
console.log();
|
|
11342
11695
|
return true;
|
|
11343
11696
|
} catch (error) {
|
|
11697
|
+
if (error?.code === "ERR_USE_AFTER_CLOSE") {
|
|
11698
|
+
return false;
|
|
11699
|
+
}
|
|
11344
11700
|
this.state.dismissed++;
|
|
11345
11701
|
this.saveState();
|
|
11346
11702
|
console.log(chalk10.gray("\nFeedback skipped.\n"));
|
|
@@ -11840,6 +12196,30 @@ var TelemetryManager = class {
|
|
|
11840
12196
|
context: data.context
|
|
11841
12197
|
});
|
|
11842
12198
|
}
|
|
12199
|
+
/**
|
|
12200
|
+
* Track a session failure as a bug report with detailed context.
|
|
12201
|
+
* Prefixes the error type with "BUG:" for easy identification.
|
|
12202
|
+
*/
|
|
12203
|
+
async trackSessionFailureBug(data) {
|
|
12204
|
+
this.errorsCount++;
|
|
12205
|
+
const sanitizedStack = data.error.stack?.replace(/\/Users\/[^/]+/g, "/Users/***").replace(/\/home\/[^/]+/g, "/home/***").replace(/C:\\Users\\[^\\]+/g, "C:\\Users\\***");
|
|
12206
|
+
const bugData = {
|
|
12207
|
+
type: "BUG:session_failure",
|
|
12208
|
+
errorMessage: data.error.message,
|
|
12209
|
+
errorName: data.error.name,
|
|
12210
|
+
stack: sanitizedStack,
|
|
12211
|
+
retryAttempt: data.retryAttempt,
|
|
12212
|
+
maxRetries: data.maxRetries,
|
|
12213
|
+
conversationLength: data.conversationLength,
|
|
12214
|
+
lastToolCalls: data.lastToolCalls,
|
|
12215
|
+
iterationCount: data.iterationCount,
|
|
12216
|
+
contextUsage: data.contextUsage,
|
|
12217
|
+
model: data.model || this.currentModel || void 0,
|
|
12218
|
+
provider: data.provider || this.currentProvider || void 0,
|
|
12219
|
+
isRetrying: data.retryAttempt < data.maxRetries
|
|
12220
|
+
};
|
|
12221
|
+
await this.trackEvent("session_failure_bug", bugData);
|
|
12222
|
+
}
|
|
11843
12223
|
/**
|
|
11844
12224
|
* Track slash command usage
|
|
11845
12225
|
*/
|
|
@@ -11959,462 +12339,9 @@ var TelemetryManager = class {
|
|
|
11959
12339
|
}
|
|
11960
12340
|
};
|
|
11961
12341
|
|
|
11962
|
-
// src/skills/SkillsRegistry.ts
|
|
11963
|
-
import fs18 from "fs-extra";
|
|
11964
|
-
import path17 from "path";
|
|
11965
|
-
|
|
11966
|
-
// src/skills/SkillParser.ts
|
|
11967
|
-
import fs17 from "fs-extra";
|
|
11968
|
-
import YAML from "yaml";
|
|
11969
|
-
var SkillParser = class {
|
|
11970
|
-
/**
|
|
11971
|
-
* Parse a SKILL.md file from disk
|
|
11972
|
-
*/
|
|
11973
|
-
async parseFile(filePath, source) {
|
|
11974
|
-
try {
|
|
11975
|
-
const content = await fs17.readFile(filePath, "utf-8");
|
|
11976
|
-
return this.parseContent(content, filePath, source);
|
|
11977
|
-
} catch (error) {
|
|
11978
|
-
return {
|
|
11979
|
-
success: false,
|
|
11980
|
-
error: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`
|
|
11981
|
-
};
|
|
11982
|
-
}
|
|
11983
|
-
}
|
|
11984
|
-
/**
|
|
11985
|
-
* Parse SKILL.md content directly from a string
|
|
11986
|
-
*/
|
|
11987
|
-
parseContent(content, filePath, source) {
|
|
11988
|
-
const extraction = this.extractFrontmatter(content);
|
|
11989
|
-
if (!extraction) {
|
|
11990
|
-
return {
|
|
11991
|
-
success: false,
|
|
11992
|
-
error: "No valid YAML frontmatter found. SKILL.md must start with --- delimited frontmatter."
|
|
11993
|
-
};
|
|
11994
|
-
}
|
|
11995
|
-
try {
|
|
11996
|
-
const parsed = YAML.parse(extraction.frontmatter);
|
|
11997
|
-
const validation = validateSkillFrontmatter(parsed);
|
|
11998
|
-
if (!validation.valid) {
|
|
11999
|
-
return {
|
|
12000
|
-
success: false,
|
|
12001
|
-
error: `Invalid skill frontmatter: ${validation.errors.join("; ")}`
|
|
12002
|
-
};
|
|
12003
|
-
}
|
|
12004
|
-
const skill = {
|
|
12005
|
-
name: parsed.name,
|
|
12006
|
-
description: parsed.description,
|
|
12007
|
-
license: parsed.license,
|
|
12008
|
-
compatibility: parsed.compatibility,
|
|
12009
|
-
metadata: parsed.metadata,
|
|
12010
|
-
"allowed-tools": parsed["allowed-tools"],
|
|
12011
|
-
body: extraction.body,
|
|
12012
|
-
path: filePath,
|
|
12013
|
-
source,
|
|
12014
|
-
isActive: false
|
|
12015
|
-
};
|
|
12016
|
-
return { success: true, skill };
|
|
12017
|
-
} catch (error) {
|
|
12018
|
-
return {
|
|
12019
|
-
success: false,
|
|
12020
|
-
error: `Failed to parse YAML frontmatter: ${error instanceof Error ? error.message : String(error)}`
|
|
12021
|
-
};
|
|
12022
|
-
}
|
|
12023
|
-
}
|
|
12024
|
-
/**
|
|
12025
|
-
* Extract YAML frontmatter and body from content
|
|
12026
|
-
* Frontmatter must be delimited by --- at start and end
|
|
12027
|
-
*/
|
|
12028
|
-
extractFrontmatter(content) {
|
|
12029
|
-
if (!content.startsWith("---")) {
|
|
12030
|
-
return null;
|
|
12031
|
-
}
|
|
12032
|
-
const lines = content.split("\n");
|
|
12033
|
-
let closingIndex = -1;
|
|
12034
|
-
for (let i = 1; i < lines.length; i++) {
|
|
12035
|
-
if (lines[i].trim() === "---") {
|
|
12036
|
-
closingIndex = i;
|
|
12037
|
-
break;
|
|
12038
|
-
}
|
|
12039
|
-
}
|
|
12040
|
-
if (closingIndex === -1) {
|
|
12041
|
-
return null;
|
|
12042
|
-
}
|
|
12043
|
-
const frontmatter = lines.slice(1, closingIndex).join("\n");
|
|
12044
|
-
const body = lines.slice(closingIndex + 1).join("\n").trim();
|
|
12045
|
-
return { frontmatter, body };
|
|
12046
|
-
}
|
|
12047
|
-
};
|
|
12048
|
-
|
|
12049
|
-
// src/skills/SkillsRegistry.ts
|
|
12050
|
-
var SIMILARITY_THRESHOLD2 = 0.3;
|
|
12051
|
-
var VENDOR_SOURCES = ["codex-user", "claude-user", "codex-project", "claude-project"];
|
|
12052
|
-
var SkillsRegistry = class {
|
|
12053
|
-
constructor(userSkillsDir, defaultSource = "autohand-user") {
|
|
12054
|
-
this.userSkillsDir = userSkillsDir;
|
|
12055
|
-
this.skills = /* @__PURE__ */ new Map();
|
|
12056
|
-
this.parser = new SkillParser();
|
|
12057
|
-
this.workspaceRoot = null;
|
|
12058
|
-
this.telemetryManager = null;
|
|
12059
|
-
this.communityClient = null;
|
|
12060
|
-
this.defaultSource = defaultSource;
|
|
12061
|
-
}
|
|
12062
|
-
/**
|
|
12063
|
-
* Set the telemetry manager for tracking skill events
|
|
12064
|
-
*/
|
|
12065
|
-
setTelemetryManager(telemetryManager) {
|
|
12066
|
-
this.telemetryManager = telemetryManager;
|
|
12067
|
-
}
|
|
12068
|
-
/**
|
|
12069
|
-
* Set the community skills client for backup/sync operations
|
|
12070
|
-
*/
|
|
12071
|
-
setCommunityClient(client) {
|
|
12072
|
-
this.communityClient = client;
|
|
12073
|
-
}
|
|
12074
|
-
/**
|
|
12075
|
-
* Get the community skills client
|
|
12076
|
-
*/
|
|
12077
|
-
getCommunityClient() {
|
|
12078
|
-
return this.communityClient;
|
|
12079
|
-
}
|
|
12080
|
-
/**
|
|
12081
|
-
* Check if any vendor skills (codex/claude) are loaded
|
|
12082
|
-
*/
|
|
12083
|
-
hasVendorSkills() {
|
|
12084
|
-
for (const skill of this.skills.values()) {
|
|
12085
|
-
if (VENDOR_SOURCES.includes(skill.source)) {
|
|
12086
|
-
return true;
|
|
12087
|
-
}
|
|
12088
|
-
}
|
|
12089
|
-
return false;
|
|
12090
|
-
}
|
|
12091
|
-
/**
|
|
12092
|
-
* Get all vendor skills (from codex/claude sources)
|
|
12093
|
-
*/
|
|
12094
|
-
getVendorSkills() {
|
|
12095
|
-
return Array.from(this.skills.values()).filter(
|
|
12096
|
-
(skill) => VENDOR_SOURCES.includes(skill.source)
|
|
12097
|
-
);
|
|
12098
|
-
}
|
|
12099
|
-
/**
|
|
12100
|
-
* Import a community skill package and save to disk
|
|
12101
|
-
*/
|
|
12102
|
-
async importCommunitySkill(pkg, targetDir) {
|
|
12103
|
-
if (!pkg.name || !pkg.body) {
|
|
12104
|
-
return { success: false, error: "Invalid skill package: missing name or body" };
|
|
12105
|
-
}
|
|
12106
|
-
const skillDir = path17.join(targetDir, pkg.name);
|
|
12107
|
-
const skillPath = path17.join(skillDir, "SKILL.md");
|
|
12108
|
-
if (await fs18.pathExists(skillPath)) {
|
|
12109
|
-
return { success: false, skipped: true, error: "Skill already exists" };
|
|
12110
|
-
}
|
|
12111
|
-
try {
|
|
12112
|
-
await fs18.ensureDir(skillDir);
|
|
12113
|
-
await fs18.writeFile(skillPath, pkg.body, "utf-8");
|
|
12114
|
-
const result = await this.parser.parseFile(skillPath, "community");
|
|
12115
|
-
if (result.success && result.skill) {
|
|
12116
|
-
this.skills.set(result.skill.name, result.skill);
|
|
12117
|
-
return { success: true, path: skillPath };
|
|
12118
|
-
}
|
|
12119
|
-
return { success: false, error: "Failed to parse imported skill" };
|
|
12120
|
-
} catch (err) {
|
|
12121
|
-
const error = err instanceof Error ? err.message : "Unknown error";
|
|
12122
|
-
return { success: false, error };
|
|
12123
|
-
}
|
|
12124
|
-
}
|
|
12125
|
-
/**
|
|
12126
|
-
* Initialize the registry by loading skills from the user directory
|
|
12127
|
-
*/
|
|
12128
|
-
async initialize() {
|
|
12129
|
-
await this.loadFromDirectory(this.userSkillsDir, this.defaultSource, true);
|
|
12130
|
-
}
|
|
12131
|
-
/**
|
|
12132
|
-
* Set the workspace root and load project-level skills
|
|
12133
|
-
*/
|
|
12134
|
-
async setWorkspace(workspaceRoot) {
|
|
12135
|
-
this.workspaceRoot = workspaceRoot;
|
|
12136
|
-
const claudeProjectSkillsDir = path17.join(workspaceRoot, ".claude", "skills");
|
|
12137
|
-
await this.loadFromDirectory(claudeProjectSkillsDir, "claude-project", false);
|
|
12138
|
-
const autohandProjectSkillsDir = path17.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
|
|
12139
|
-
await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
|
|
12140
|
-
}
|
|
12141
|
-
/**
|
|
12142
|
-
* Add an additional skill location to search
|
|
12143
|
-
*/
|
|
12144
|
-
async addLocation(directory, source, recursive = true) {
|
|
12145
|
-
await this.loadFromDirectory(directory, source, recursive);
|
|
12146
|
-
}
|
|
12147
|
-
/**
|
|
12148
|
-
* Add a skill location with auto-copy to autohand location
|
|
12149
|
-
* Copies discovered skills to the target autohand directory, preserving structure
|
|
12150
|
-
*/
|
|
12151
|
-
async addLocationWithAutoCopy(sourceDirectory, source, targetAutohandDir, recursive = true) {
|
|
12152
|
-
const result = {
|
|
12153
|
-
copiedCount: 0,
|
|
12154
|
-
skippedCount: 0,
|
|
12155
|
-
errorCount: 0,
|
|
12156
|
-
copiedSkills: [],
|
|
12157
|
-
skippedSkills: []
|
|
12158
|
-
};
|
|
12159
|
-
if (!await fs18.pathExists(sourceDirectory)) {
|
|
12160
|
-
return result;
|
|
12161
|
-
}
|
|
12162
|
-
const skillFiles = await this.findSkillFiles(sourceDirectory, recursive);
|
|
12163
|
-
for (const skillPath of skillFiles) {
|
|
12164
|
-
const parseResult = await this.parser.parseFile(skillPath, source);
|
|
12165
|
-
if (!parseResult.success || !parseResult.skill) {
|
|
12166
|
-
continue;
|
|
12167
|
-
}
|
|
12168
|
-
const skill = parseResult.skill;
|
|
12169
|
-
const skillName = skill.name;
|
|
12170
|
-
const skillDir = path17.dirname(skillPath);
|
|
12171
|
-
const relativePath = path17.relative(sourceDirectory, skillDir);
|
|
12172
|
-
const targetSkillDir = path17.join(targetAutohandDir, relativePath);
|
|
12173
|
-
const targetSkillPath = path17.join(targetSkillDir, "SKILL.md");
|
|
12174
|
-
if (await fs18.pathExists(targetSkillPath)) {
|
|
12175
|
-
result.skippedCount++;
|
|
12176
|
-
result.skippedSkills.push(skillName);
|
|
12177
|
-
} else {
|
|
12178
|
-
try {
|
|
12179
|
-
await fs18.ensureDir(targetSkillDir);
|
|
12180
|
-
await fs18.copyFile(skillPath, targetSkillPath);
|
|
12181
|
-
result.copiedCount++;
|
|
12182
|
-
result.copiedSkills.push(skillName);
|
|
12183
|
-
} catch {
|
|
12184
|
-
result.errorCount++;
|
|
12185
|
-
}
|
|
12186
|
-
}
|
|
12187
|
-
this.skills.set(skill.name, skill);
|
|
12188
|
-
}
|
|
12189
|
-
return result;
|
|
12190
|
-
}
|
|
12191
|
-
/**
|
|
12192
|
-
* Set workspace with auto-copy from claude-project to autohand-project
|
|
12193
|
-
*/
|
|
12194
|
-
async setWorkspaceWithAutoCopy(workspaceRoot) {
|
|
12195
|
-
this.workspaceRoot = workspaceRoot;
|
|
12196
|
-
const claudeProjectSkillsDir = path17.join(workspaceRoot, ".claude", "skills");
|
|
12197
|
-
const autohandProjectSkillsDir = path17.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
|
|
12198
|
-
await this.addLocationWithAutoCopy(
|
|
12199
|
-
claudeProjectSkillsDir,
|
|
12200
|
-
"claude-project",
|
|
12201
|
-
autohandProjectSkillsDir,
|
|
12202
|
-
false
|
|
12203
|
-
// Claude project is not recursive
|
|
12204
|
-
);
|
|
12205
|
-
await this.loadFromDirectory(autohandProjectSkillsDir, "autohand-project", true);
|
|
12206
|
-
}
|
|
12207
|
-
/**
|
|
12208
|
-
* Add a skill location with auto-copy AND backup to community API
|
|
12209
|
-
* Enhanced version that also backs up vendor skills to the API
|
|
12210
|
-
*/
|
|
12211
|
-
async addLocationWithAutoCopyAndBackup(sourceDirectory, source, targetAutohandDir, recursive = true) {
|
|
12212
|
-
const result = await this.addLocationWithAutoCopy(
|
|
12213
|
-
sourceDirectory,
|
|
12214
|
-
source,
|
|
12215
|
-
targetAutohandDir,
|
|
12216
|
-
recursive
|
|
12217
|
-
);
|
|
12218
|
-
if (this.communityClient && result.copiedCount > 0) {
|
|
12219
|
-
const backupPayloads = [];
|
|
12220
|
-
for (const skillName of result.copiedSkills) {
|
|
12221
|
-
const skill = this.skills.get(skillName);
|
|
12222
|
-
if (skill) {
|
|
12223
|
-
backupPayloads.push({
|
|
12224
|
-
name: skill.name,
|
|
12225
|
-
description: skill.description,
|
|
12226
|
-
body: skill.body,
|
|
12227
|
-
allowedTools: skill["allowed-tools"],
|
|
12228
|
-
originalSource: source,
|
|
12229
|
-
originalPath: skill.path
|
|
12230
|
-
});
|
|
12231
|
-
}
|
|
12232
|
-
}
|
|
12233
|
-
if (backupPayloads.length > 0) {
|
|
12234
|
-
await this.communityClient.backupAllSkills(backupPayloads);
|
|
12235
|
-
}
|
|
12236
|
-
}
|
|
12237
|
-
return result;
|
|
12238
|
-
}
|
|
12239
|
-
/**
|
|
12240
|
-
* Backup all vendor skills to community API
|
|
12241
|
-
*/
|
|
12242
|
-
async backupAllVendorSkills() {
|
|
12243
|
-
if (!this.communityClient) {
|
|
12244
|
-
return { backed: 0, failed: 0 };
|
|
12245
|
-
}
|
|
12246
|
-
const vendorSkills = this.getVendorSkills();
|
|
12247
|
-
if (vendorSkills.length === 0) {
|
|
12248
|
-
return { backed: 0, failed: 0 };
|
|
12249
|
-
}
|
|
12250
|
-
const payloads = vendorSkills.map((skill) => ({
|
|
12251
|
-
name: skill.name,
|
|
12252
|
-
description: skill.description,
|
|
12253
|
-
body: skill.body,
|
|
12254
|
-
allowedTools: skill["allowed-tools"],
|
|
12255
|
-
originalSource: skill.source,
|
|
12256
|
-
originalPath: skill.path
|
|
12257
|
-
}));
|
|
12258
|
-
return this.communityClient.backupAllSkills(payloads);
|
|
12259
|
-
}
|
|
12260
|
-
/**
|
|
12261
|
-
* Load skills from a directory
|
|
12262
|
-
*/
|
|
12263
|
-
async loadFromDirectory(directory, source, recursive) {
|
|
12264
|
-
if (!await fs18.pathExists(directory)) {
|
|
12265
|
-
return;
|
|
12266
|
-
}
|
|
12267
|
-
const skillFiles = await this.findSkillFiles(directory, recursive);
|
|
12268
|
-
for (const skillPath of skillFiles) {
|
|
12269
|
-
const result = await this.parser.parseFile(skillPath, source);
|
|
12270
|
-
if (result.success && result.skill) {
|
|
12271
|
-
this.skills.set(result.skill.name, result.skill);
|
|
12272
|
-
}
|
|
12273
|
-
}
|
|
12274
|
-
}
|
|
12275
|
-
/**
|
|
12276
|
-
* Find all SKILL.md files in a directory
|
|
12277
|
-
*/
|
|
12278
|
-
async findSkillFiles(directory, recursive) {
|
|
12279
|
-
const results = [];
|
|
12280
|
-
if (!await fs18.pathExists(directory)) {
|
|
12281
|
-
return results;
|
|
12282
|
-
}
|
|
12283
|
-
const entries = await fs18.readdir(directory, { withFileTypes: true });
|
|
12284
|
-
for (const entry of entries) {
|
|
12285
|
-
const fullPath = path17.join(directory, entry.name);
|
|
12286
|
-
if (entry.isDirectory()) {
|
|
12287
|
-
const skillPath = path17.join(fullPath, "SKILL.md");
|
|
12288
|
-
if (await fs18.pathExists(skillPath)) {
|
|
12289
|
-
results.push(skillPath);
|
|
12290
|
-
}
|
|
12291
|
-
if (recursive) {
|
|
12292
|
-
const nested = await this.findSkillFiles(fullPath, true);
|
|
12293
|
-
results.push(...nested);
|
|
12294
|
-
}
|
|
12295
|
-
}
|
|
12296
|
-
}
|
|
12297
|
-
return results;
|
|
12298
|
-
}
|
|
12299
|
-
/**
|
|
12300
|
-
* List all available skills
|
|
12301
|
-
*/
|
|
12302
|
-
listSkills() {
|
|
12303
|
-
return Array.from(this.skills.values());
|
|
12304
|
-
}
|
|
12305
|
-
/**
|
|
12306
|
-
* Get a specific skill by name
|
|
12307
|
-
*/
|
|
12308
|
-
getSkill(name) {
|
|
12309
|
-
return this.skills.get(name) ?? null;
|
|
12310
|
-
}
|
|
12311
|
-
/**
|
|
12312
|
-
* Activate a skill by name
|
|
12313
|
-
*/
|
|
12314
|
-
activateSkill(name) {
|
|
12315
|
-
const skill = this.skills.get(name);
|
|
12316
|
-
if (!skill) {
|
|
12317
|
-
return false;
|
|
12318
|
-
}
|
|
12319
|
-
skill.isActive = true;
|
|
12320
|
-
return true;
|
|
12321
|
-
}
|
|
12322
|
-
/**
|
|
12323
|
-
* Deactivate a skill by name
|
|
12324
|
-
*/
|
|
12325
|
-
deactivateSkill(name) {
|
|
12326
|
-
const skill = this.skills.get(name);
|
|
12327
|
-
if (!skill) {
|
|
12328
|
-
return false;
|
|
12329
|
-
}
|
|
12330
|
-
skill.isActive = false;
|
|
12331
|
-
return true;
|
|
12332
|
-
}
|
|
12333
|
-
/**
|
|
12334
|
-
* Deactivate all active skills
|
|
12335
|
-
*/
|
|
12336
|
-
deactivateAll() {
|
|
12337
|
-
for (const skill of this.skills.values()) {
|
|
12338
|
-
skill.isActive = false;
|
|
12339
|
-
}
|
|
12340
|
-
}
|
|
12341
|
-
/**
|
|
12342
|
-
* Get all currently active skills
|
|
12343
|
-
*/
|
|
12344
|
-
getActiveSkills() {
|
|
12345
|
-
return Array.from(this.skills.values()).filter((s) => s.isActive);
|
|
12346
|
-
}
|
|
12347
|
-
/**
|
|
12348
|
-
* Find skills similar to a given query using Jaccard similarity
|
|
12349
|
-
*/
|
|
12350
|
-
findSimilar(query, threshold = SIMILARITY_THRESHOLD2) {
|
|
12351
|
-
const queryTokens = this.tokenize(query);
|
|
12352
|
-
const matches = [];
|
|
12353
|
-
for (const skill of this.skills.values()) {
|
|
12354
|
-
const skillText = `${skill.name} ${skill.description}`;
|
|
12355
|
-
const skillTokens = this.tokenize(skillText);
|
|
12356
|
-
const score = this.calculateJaccard(queryTokens, skillTokens);
|
|
12357
|
-
if (score >= threshold) {
|
|
12358
|
-
matches.push({ skill, score });
|
|
12359
|
-
}
|
|
12360
|
-
}
|
|
12361
|
-
return matches.sort((a, b) => b.score - a.score);
|
|
12362
|
-
}
|
|
12363
|
-
/**
|
|
12364
|
-
* Calculate Jaccard similarity between two token sets
|
|
12365
|
-
*/
|
|
12366
|
-
calculateJaccard(a, b) {
|
|
12367
|
-
if (a.size === 0 || b.size === 0) {
|
|
12368
|
-
return 0;
|
|
12369
|
-
}
|
|
12370
|
-
const intersection = new Set([...a].filter((x) => b.has(x)));
|
|
12371
|
-
const union = /* @__PURE__ */ new Set([...a, ...b]);
|
|
12372
|
-
return intersection.size / union.size;
|
|
12373
|
-
}
|
|
12374
|
-
/**
|
|
12375
|
-
* Tokenize text into a set of lowercase words
|
|
12376
|
-
*/
|
|
12377
|
-
tokenize(text) {
|
|
12378
|
-
return new Set(
|
|
12379
|
-
text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
|
|
12380
|
-
);
|
|
12381
|
-
}
|
|
12382
|
-
/**
|
|
12383
|
-
* Get the number of loaded skills
|
|
12384
|
-
*/
|
|
12385
|
-
get size() {
|
|
12386
|
-
return this.skills.size;
|
|
12387
|
-
}
|
|
12388
|
-
/**
|
|
12389
|
-
* Check if a skill exists by name
|
|
12390
|
-
*/
|
|
12391
|
-
hasSkill(name) {
|
|
12392
|
-
return this.skills.has(name);
|
|
12393
|
-
}
|
|
12394
|
-
/**
|
|
12395
|
-
* Save a new skill to the user skills directory
|
|
12396
|
-
*/
|
|
12397
|
-
async saveSkill(name, content) {
|
|
12398
|
-
const skillDir = path17.join(this.userSkillsDir, name);
|
|
12399
|
-
const skillPath = path17.join(skillDir, "SKILL.md");
|
|
12400
|
-
try {
|
|
12401
|
-
await fs18.ensureDir(skillDir);
|
|
12402
|
-
await fs18.writeFile(skillPath, content, "utf-8");
|
|
12403
|
-
const result = await this.parser.parseFile(skillPath, "autohand-user");
|
|
12404
|
-
if (result.success && result.skill) {
|
|
12405
|
-
this.skills.set(result.skill.name, result.skill);
|
|
12406
|
-
return true;
|
|
12407
|
-
}
|
|
12408
|
-
return false;
|
|
12409
|
-
} catch {
|
|
12410
|
-
return false;
|
|
12411
|
-
}
|
|
12412
|
-
}
|
|
12413
|
-
};
|
|
12414
|
-
|
|
12415
12342
|
// src/skills/CommunitySkillsClient.ts
|
|
12416
|
-
import
|
|
12417
|
-
import
|
|
12343
|
+
import fs17 from "fs-extra";
|
|
12344
|
+
import path17 from "path";
|
|
12418
12345
|
var CommunitySkillsClient = class {
|
|
12419
12346
|
constructor(config) {
|
|
12420
12347
|
this.queue = [];
|
|
@@ -12422,7 +12349,7 @@ var CommunitySkillsClient = class {
|
|
|
12422
12349
|
this.apiBaseUrl = config.apiBaseUrl.replace(/\/$/, "");
|
|
12423
12350
|
this.enabled = config.enabled;
|
|
12424
12351
|
this.deviceId = config.deviceId || this.loadDeviceId();
|
|
12425
|
-
this.queueDir = config.queueDir ||
|
|
12352
|
+
this.queueDir = config.queueDir || path17.join(AUTOHAND_HOME, "community-skills");
|
|
12426
12353
|
this.timeout = config.timeout || 1e4;
|
|
12427
12354
|
this.maxRetries = config.maxRetries || 3;
|
|
12428
12355
|
}
|
|
@@ -12430,10 +12357,10 @@ var CommunitySkillsClient = class {
|
|
|
12430
12357
|
* Load device ID from filesystem
|
|
12431
12358
|
*/
|
|
12432
12359
|
loadDeviceId() {
|
|
12433
|
-
const deviceIdPath =
|
|
12360
|
+
const deviceIdPath = path17.join(AUTOHAND_HOME, "device-id");
|
|
12434
12361
|
try {
|
|
12435
|
-
if (
|
|
12436
|
-
return
|
|
12362
|
+
if (fs17.existsSync(deviceIdPath)) {
|
|
12363
|
+
return fs17.readFileSync(deviceIdPath, "utf-8").trim();
|
|
12437
12364
|
}
|
|
12438
12365
|
} catch {
|
|
12439
12366
|
}
|
|
@@ -12445,10 +12372,10 @@ var CommunitySkillsClient = class {
|
|
|
12445
12372
|
loadQueue() {
|
|
12446
12373
|
if (this.queueLoaded) return;
|
|
12447
12374
|
this.queueLoaded = true;
|
|
12448
|
-
const queuePath =
|
|
12375
|
+
const queuePath = path17.join(this.queueDir, "queue.json");
|
|
12449
12376
|
try {
|
|
12450
|
-
if (
|
|
12451
|
-
this.queue =
|
|
12377
|
+
if (fs17.existsSync(queuePath)) {
|
|
12378
|
+
this.queue = fs17.readJsonSync(queuePath);
|
|
12452
12379
|
}
|
|
12453
12380
|
} catch {
|
|
12454
12381
|
this.queue = [];
|
|
@@ -12459,8 +12386,8 @@ var CommunitySkillsClient = class {
|
|
|
12459
12386
|
*/
|
|
12460
12387
|
saveQueue() {
|
|
12461
12388
|
try {
|
|
12462
|
-
|
|
12463
|
-
|
|
12389
|
+
fs17.ensureDirSync(this.queueDir);
|
|
12390
|
+
fs17.writeJsonSync(path17.join(this.queueDir, "queue.json"), this.queue);
|
|
12464
12391
|
} catch {
|
|
12465
12392
|
}
|
|
12466
12393
|
}
|
|
@@ -13137,7 +13064,7 @@ function createPersistentInput(options) {
|
|
|
13137
13064
|
}
|
|
13138
13065
|
|
|
13139
13066
|
// src/ui/toolOutput.ts
|
|
13140
|
-
import * as
|
|
13067
|
+
import * as path18 from "path";
|
|
13141
13068
|
var FILE_SUMMARY_TOOLS = /* @__PURE__ */ new Set([
|
|
13142
13069
|
"read_file",
|
|
13143
13070
|
"write_file",
|
|
@@ -13177,9 +13104,9 @@ ${truncatedContent}` : outputLines > 0 ? `
|
|
|
13177
13104
|
};
|
|
13178
13105
|
}
|
|
13179
13106
|
if (FILE_SUMMARY_TOOLS.has(tool) && filePath) {
|
|
13180
|
-
const fileName =
|
|
13181
|
-
const dirName =
|
|
13182
|
-
const displayPath = dirName === "." ? fileName : `${
|
|
13107
|
+
const fileName = path18.basename(filePath);
|
|
13108
|
+
const dirName = path18.dirname(filePath);
|
|
13109
|
+
const displayPath = dirName === "." ? fileName : `${path18.basename(dirName)}/${fileName}`;
|
|
13183
13110
|
const lines = countLines(content);
|
|
13184
13111
|
const size = formatFileSize(Buffer.byteLength(content, "utf8"));
|
|
13185
13112
|
return {
|
|
@@ -14081,7 +14008,7 @@ var IntentDetector = class {
|
|
|
14081
14008
|
};
|
|
14082
14009
|
|
|
14083
14010
|
// src/core/EnvironmentBootstrap.ts
|
|
14084
|
-
import
|
|
14011
|
+
import fs18 from "fs-extra";
|
|
14085
14012
|
import { join as join2 } from "path";
|
|
14086
14013
|
import { exec } from "child_process";
|
|
14087
14014
|
import { promisify } from "util";
|
|
@@ -14177,14 +14104,14 @@ var EnvironmentBootstrap = class {
|
|
|
14177
14104
|
*/
|
|
14178
14105
|
async detectPackageManager(root) {
|
|
14179
14106
|
for (const { file, pm } of this.lockfileChecks) {
|
|
14180
|
-
if (await
|
|
14107
|
+
if (await fs18.pathExists(join2(root, file))) {
|
|
14181
14108
|
return pm;
|
|
14182
14109
|
}
|
|
14183
14110
|
}
|
|
14184
14111
|
const pkgPath = join2(root, "package.json");
|
|
14185
|
-
if (await
|
|
14112
|
+
if (await fs18.pathExists(pkgPath)) {
|
|
14186
14113
|
try {
|
|
14187
|
-
const pkg = await
|
|
14114
|
+
const pkg = await fs18.readJson(pkgPath);
|
|
14188
14115
|
if (pkg.packageManager) {
|
|
14189
14116
|
const match = pkg.packageManager.match(/^(bun|pnpm|yarn|npm)@/);
|
|
14190
14117
|
if (match) {
|
|
@@ -14215,7 +14142,7 @@ var EnvironmentBootstrap = class {
|
|
|
14215
14142
|
const step = { name: "Git Sync", status: "running" };
|
|
14216
14143
|
const start = Date.now();
|
|
14217
14144
|
try {
|
|
14218
|
-
const isGit = await
|
|
14145
|
+
const isGit = await fs18.pathExists(join2(root, ".git"));
|
|
14219
14146
|
if (!isGit) {
|
|
14220
14147
|
return {
|
|
14221
14148
|
...step,
|
|
@@ -14296,18 +14223,18 @@ var EnvironmentBootstrap = class {
|
|
|
14296
14223
|
const details = [];
|
|
14297
14224
|
try {
|
|
14298
14225
|
const nvmrcPath = join2(root, ".nvmrc");
|
|
14299
|
-
const nvmrcExists = await
|
|
14226
|
+
const nvmrcExists = await fs18.pathExists(nvmrcPath);
|
|
14300
14227
|
const nodeVersionPath = join2(root, ".node-version");
|
|
14301
|
-
const nodeVersionExists = await
|
|
14228
|
+
const nodeVersionExists = await fs18.pathExists(nodeVersionPath);
|
|
14302
14229
|
if (nvmrcExists || nodeVersionExists) {
|
|
14303
14230
|
details.push("Node version file found");
|
|
14304
14231
|
}
|
|
14305
14232
|
const toolVersionsPath = join2(root, ".tool-versions");
|
|
14306
|
-
if (await
|
|
14233
|
+
if (await fs18.pathExists(toolVersionsPath)) {
|
|
14307
14234
|
details.push(".tool-versions found");
|
|
14308
14235
|
}
|
|
14309
14236
|
const rustToolchainPath = join2(root, "rust-toolchain.toml");
|
|
14310
|
-
if (await
|
|
14237
|
+
if (await fs18.pathExists(rustToolchainPath)) {
|
|
14311
14238
|
details.push("Rust toolchain found");
|
|
14312
14239
|
}
|
|
14313
14240
|
step.status = "success";
|
|
@@ -14384,7 +14311,7 @@ var EnvironmentBootstrap = class {
|
|
|
14384
14311
|
};
|
|
14385
14312
|
|
|
14386
14313
|
// src/core/CodeQualityPipeline.ts
|
|
14387
|
-
import
|
|
14314
|
+
import fs19 from "fs-extra";
|
|
14388
14315
|
import { join as join3 } from "path";
|
|
14389
14316
|
import { exec as exec2 } from "child_process";
|
|
14390
14317
|
import { promisify as promisify2 } from "util";
|
|
@@ -14454,11 +14381,11 @@ var CodeQualityPipeline = class {
|
|
|
14454
14381
|
*/
|
|
14455
14382
|
async detectScripts(root) {
|
|
14456
14383
|
const pkgPath = join3(root, "package.json");
|
|
14457
|
-
if (!await
|
|
14384
|
+
if (!await fs19.pathExists(pkgPath)) {
|
|
14458
14385
|
return {};
|
|
14459
14386
|
}
|
|
14460
14387
|
try {
|
|
14461
|
-
const pkg = await
|
|
14388
|
+
const pkg = await fs19.readJson(pkgPath);
|
|
14462
14389
|
const scripts = pkg.scripts || {};
|
|
14463
14390
|
return {
|
|
14464
14391
|
lint: this.findScript(scripts, this.scriptPatterns.lint),
|
|
@@ -14644,6 +14571,7 @@ var AutohandAgent = class {
|
|
|
14644
14571
|
this.lastIntent = "diagnostic";
|
|
14645
14572
|
this.filesModifiedThisSession = false;
|
|
14646
14573
|
this.searchQueries = [];
|
|
14574
|
+
this.sessionRetryCount = 0;
|
|
14647
14575
|
const initialProvider = runtime.config.provider ?? "openrouter";
|
|
14648
14576
|
const providerSettings = getProviderConfig(runtime.config, initialProvider);
|
|
14649
14577
|
const model = runtime.options.model ?? providerSettings?.model ?? "unconfigured";
|
|
@@ -14670,7 +14598,7 @@ var AutohandAgent = class {
|
|
|
14670
14598
|
runtime,
|
|
14671
14599
|
files,
|
|
14672
14600
|
resolveWorkspacePath: (relativePath) => this.resolveWorkspacePath(relativePath),
|
|
14673
|
-
confirmDangerousAction: (message) => this.confirmDangerousAction(message),
|
|
14601
|
+
confirmDangerousAction: (message, context) => this.confirmDangerousAction(message, context),
|
|
14674
14602
|
onExploration: (entry) => this.recordExploration(entry),
|
|
14675
14603
|
onToolOutput: (chunk) => this.handleToolOutput(chunk),
|
|
14676
14604
|
toolsRegistry: this.toolsRegistry,
|
|
@@ -14762,7 +14690,7 @@ var AutohandAgent = class {
|
|
|
14762
14690
|
throw error;
|
|
14763
14691
|
}
|
|
14764
14692
|
},
|
|
14765
|
-
confirmApproval: (message) => this.confirmDangerousAction(message),
|
|
14693
|
+
confirmApproval: (message, context) => this.confirmDangerousAction(message, context),
|
|
14766
14694
|
definitions: [...DEFAULT_TOOL_DEFINITIONS, ...delegationTools],
|
|
14767
14695
|
clientContext
|
|
14768
14696
|
});
|
|
@@ -15111,7 +15039,16 @@ var AutohandAgent = class {
|
|
|
15111
15039
|
return false;
|
|
15112
15040
|
};
|
|
15113
15041
|
if (normalized.startsWith("/") && !looksLikeFilePath(normalized)) {
|
|
15114
|
-
const
|
|
15042
|
+
const parts = normalized.split(/\s+/);
|
|
15043
|
+
let command = parts[0];
|
|
15044
|
+
let args = parts.slice(1);
|
|
15045
|
+
const twoWordCommands = ["/skills new", "/agents new"];
|
|
15046
|
+
const potentialTwoWord = `${parts[0]} ${parts[1] || ""}`.trim();
|
|
15047
|
+
if (twoWordCommands.includes(potentialTwoWord)) {
|
|
15048
|
+
command = potentialTwoWord;
|
|
15049
|
+
args = parts.slice(2);
|
|
15050
|
+
}
|
|
15051
|
+
const handled = await this.slashHandler.handle(command, args);
|
|
15115
15052
|
if (handled === null) {
|
|
15116
15053
|
return null;
|
|
15117
15054
|
}
|
|
@@ -15171,7 +15108,7 @@ var AutohandAgent = class {
|
|
|
15171
15108
|
}
|
|
15172
15109
|
}
|
|
15173
15110
|
async listWorkspaceFiles() {
|
|
15174
|
-
const entries = await
|
|
15111
|
+
const entries = await fs20.readdir(this.runtime.workspaceRoot);
|
|
15175
15112
|
const sorted = entries.sort((a, b) => a.localeCompare(b));
|
|
15176
15113
|
console.log("\n" + chalk13.cyan("Workspace files:"));
|
|
15177
15114
|
console.log(sorted.map((entry) => ` - ${entry}`).join("\n"));
|
|
@@ -15198,18 +15135,18 @@ var AutohandAgent = class {
|
|
|
15198
15135
|
async walkWorkspace(current, acc) {
|
|
15199
15136
|
let entries;
|
|
15200
15137
|
try {
|
|
15201
|
-
entries = await
|
|
15138
|
+
entries = await fs20.readdir(current);
|
|
15202
15139
|
} catch {
|
|
15203
15140
|
return;
|
|
15204
15141
|
}
|
|
15205
15142
|
for (const entry of entries) {
|
|
15206
|
-
const full =
|
|
15207
|
-
const rel =
|
|
15143
|
+
const full = path19.join(current, entry);
|
|
15144
|
+
const rel = path19.relative(this.runtime.workspaceRoot, full);
|
|
15208
15145
|
if (rel === "" || this.shouldSkipPath(rel) || this.ignoreFilter.isIgnored(rel)) {
|
|
15209
15146
|
continue;
|
|
15210
15147
|
}
|
|
15211
15148
|
try {
|
|
15212
|
-
const stats = await
|
|
15149
|
+
const stats = await fs20.stat(full);
|
|
15213
15150
|
if (stats.isDirectory()) {
|
|
15214
15151
|
await this.walkWorkspace(full, acc);
|
|
15215
15152
|
} else if (stats.isFile()) {
|
|
@@ -15579,8 +15516,8 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
15579
15516
|
);
|
|
15580
15517
|
}
|
|
15581
15518
|
async createAgentsFile() {
|
|
15582
|
-
const target =
|
|
15583
|
-
if (await
|
|
15519
|
+
const target = path19.join(this.runtime.workspaceRoot, "AGENTS.md");
|
|
15520
|
+
if (await fs20.pathExists(target)) {
|
|
15584
15521
|
console.log(chalk13.gray("AGENTS.md already exists in this workspace."));
|
|
15585
15522
|
return;
|
|
15586
15523
|
}
|
|
@@ -15588,7 +15525,7 @@ ${selectedProvider} is not configured yet. Let's set it up!
|
|
|
15588
15525
|
|
|
15589
15526
|
Describe how Autohand should work in this repo. Include framework commands, testing requirements, and any constraints.
|
|
15590
15527
|
`;
|
|
15591
|
-
await
|
|
15528
|
+
await fs20.writeFile(target, template, "utf8");
|
|
15592
15529
|
console.log(chalk13.green("Created AGENTS.md template. Customize it to guide the agent."));
|
|
15593
15530
|
}
|
|
15594
15531
|
/**
|
|
@@ -15700,6 +15637,33 @@ No provider is configured yet. Let's set one up!
|
|
|
15700
15637
|
await this.promptModelSelection();
|
|
15701
15638
|
return this.runInstruction(instruction);
|
|
15702
15639
|
}
|
|
15640
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
15641
|
+
const maxRetries = this.runtime.config.agent?.sessionRetryLimit ?? 3;
|
|
15642
|
+
const baseDelay = this.runtime.config.agent?.sessionRetryDelay ?? 1e3;
|
|
15643
|
+
if (this.isRetryableSessionError(err) && this.sessionRetryCount < maxRetries) {
|
|
15644
|
+
this.sessionRetryCount++;
|
|
15645
|
+
await this.submitSessionFailureBugReport(err, this.sessionRetryCount, maxRetries);
|
|
15646
|
+
console.log(chalk13.yellow(`
|
|
15647
|
+
\u26A0 Session encountered an error: ${err.message}`));
|
|
15648
|
+
console.log(chalk13.cyan(` Attempting recovery (${this.sessionRetryCount}/${maxRetries})...`));
|
|
15649
|
+
const delay = baseDelay * Math.pow(1.5, this.sessionRetryCount - 1);
|
|
15650
|
+
await this.sleep(delay);
|
|
15651
|
+
this.injectContinuationMessage(err, this.sessionRetryCount);
|
|
15652
|
+
try {
|
|
15653
|
+
this.setUIStatus("Recovering session...");
|
|
15654
|
+
await this.runReactLoop(abortController);
|
|
15655
|
+
this.sessionRetryCount = 0;
|
|
15656
|
+
success = true;
|
|
15657
|
+
return success;
|
|
15658
|
+
} catch (retryError) {
|
|
15659
|
+
if (this.sessionRetryCount >= maxRetries) {
|
|
15660
|
+
this.sessionRetryCount = 0;
|
|
15661
|
+
} else {
|
|
15662
|
+
throw retryError;
|
|
15663
|
+
}
|
|
15664
|
+
}
|
|
15665
|
+
}
|
|
15666
|
+
this.sessionRetryCount = 0;
|
|
15703
15667
|
this.stopUI(true, "Session failed");
|
|
15704
15668
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
15705
15669
|
this.emitOutput({ type: "error", content: errorMessage });
|
|
@@ -16298,7 +16262,8 @@ ${summary}`
|
|
|
16298
16262
|
const needApproval = Boolean(args.need_user_approve);
|
|
16299
16263
|
if (needApproval) {
|
|
16300
16264
|
const approved = await this.confirmDangerousAction(
|
|
16301
|
-
`Crop ${direction} ${Math.floor(amount)} message(s) from the conversation
|
|
16265
|
+
`Crop ${direction} ${Math.floor(amount)} message(s) from the conversation?`,
|
|
16266
|
+
{ tool: "smart_context_cropper" }
|
|
16302
16267
|
);
|
|
16303
16268
|
if (!approved) {
|
|
16304
16269
|
return "smart_context_cropper canceled by user.";
|
|
@@ -16672,16 +16637,16 @@ ${toolSignatures}
|
|
|
16672
16637
|
return normalizedSeed || null;
|
|
16673
16638
|
}
|
|
16674
16639
|
async fileExists(relativePath) {
|
|
16675
|
-
const fullPath =
|
|
16640
|
+
const fullPath = path19.resolve(this.runtime.workspaceRoot, relativePath);
|
|
16676
16641
|
if (!fullPath.startsWith(this.runtime.workspaceRoot)) {
|
|
16677
16642
|
return false;
|
|
16678
16643
|
}
|
|
16679
|
-
const exists = await
|
|
16644
|
+
const exists = await fs20.pathExists(fullPath);
|
|
16680
16645
|
if (!exists) {
|
|
16681
16646
|
return false;
|
|
16682
16647
|
}
|
|
16683
16648
|
try {
|
|
16684
|
-
const stats = await
|
|
16649
|
+
const stats = await fs20.stat(fullPath);
|
|
16685
16650
|
return stats.isFile();
|
|
16686
16651
|
} catch {
|
|
16687
16652
|
return false;
|
|
@@ -16967,7 +16932,7 @@ ${ctx.contents}`).join("\n\n");
|
|
|
16967
16932
|
encoding: "utf8"
|
|
16968
16933
|
});
|
|
16969
16934
|
const gitStatus2 = git.status === 0 ? git.stdout.trim() : void 0;
|
|
16970
|
-
const entries = await
|
|
16935
|
+
const entries = await fs20.readdir(this.runtime.workspaceRoot);
|
|
16971
16936
|
const recentFiles = entries.filter((entry) => !this.ignoreFilter.isIgnored(entry)).slice(0, 20);
|
|
16972
16937
|
return {
|
|
16973
16938
|
workspaceRoot: this.runtime.workspaceRoot,
|
|
@@ -16978,17 +16943,17 @@ ${ctx.contents}`).join("\n\n");
|
|
|
16978
16943
|
async loadInstructionFiles() {
|
|
16979
16944
|
const instructions = [];
|
|
16980
16945
|
const workspace = this.runtime.workspaceRoot;
|
|
16981
|
-
const agentsPath =
|
|
16982
|
-
if (await
|
|
16983
|
-
const content = await
|
|
16946
|
+
const agentsPath = path19.join(workspace, "AGENTS.md");
|
|
16947
|
+
if (await fs20.pathExists(agentsPath)) {
|
|
16948
|
+
const content = await fs20.readFile(agentsPath, "utf-8");
|
|
16984
16949
|
instructions.push(`## Project Instructions (AGENTS.md)
|
|
16985
16950
|
${content}`);
|
|
16986
16951
|
}
|
|
16987
16952
|
const providerFile = this.activeProvider.includes("anthropic") || this.activeProvider === "openrouter" ? "CLAUDE.md" : this.activeProvider.includes("google") ? "GEMINI.md" : null;
|
|
16988
16953
|
if (providerFile) {
|
|
16989
|
-
const providerPath =
|
|
16990
|
-
if (await
|
|
16991
|
-
const content = await
|
|
16954
|
+
const providerPath = path19.join(workspace, providerFile);
|
|
16955
|
+
if (await fs20.pathExists(providerPath)) {
|
|
16956
|
+
const content = await fs20.readFile(providerPath, "utf-8");
|
|
16992
16957
|
instructions.push(`## Provider Instructions (${providerFile})
|
|
16993
16958
|
${content}`);
|
|
16994
16959
|
}
|
|
@@ -17139,9 +17104,100 @@ ${parts.join("\n")}`
|
|
|
17139
17104
|
return `${tokens} tokens`;
|
|
17140
17105
|
}
|
|
17141
17106
|
/**
|
|
17142
|
-
*
|
|
17107
|
+
* Sleep helper for retry delays
|
|
17108
|
+
*/
|
|
17109
|
+
sleep(ms) {
|
|
17110
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17111
|
+
}
|
|
17112
|
+
/**
|
|
17113
|
+
* Categorize errors to determine retry behavior.
|
|
17114
|
+
* Returns true if the error is retryable.
|
|
17115
|
+
*/
|
|
17116
|
+
isRetryableSessionError(error) {
|
|
17117
|
+
const message = error.message.toLowerCase();
|
|
17118
|
+
if (message.includes("authentication") || message.includes("api key") || message.includes("unauthorized") || message.includes("forbidden") || message.includes("access denied")) {
|
|
17119
|
+
return false;
|
|
17120
|
+
}
|
|
17121
|
+
if (message.includes("payment required") || message.includes("billing") || message.includes("quota exceeded")) {
|
|
17122
|
+
return false;
|
|
17123
|
+
}
|
|
17124
|
+
if (message.includes("model not found") || message.includes("model does not exist")) {
|
|
17125
|
+
return false;
|
|
17126
|
+
}
|
|
17127
|
+
if (message.includes("cancelled") || message.includes("canceled") || message.includes("aborted") || message.includes("user force closed")) {
|
|
17128
|
+
return false;
|
|
17129
|
+
}
|
|
17130
|
+
if (message.includes("payload too large") || message.includes("context too long")) {
|
|
17131
|
+
return false;
|
|
17132
|
+
}
|
|
17133
|
+
if (message.includes("network") || message.includes("connection") || message.includes("econnreset") || message.includes("enotfound") || message.includes("etimedout")) {
|
|
17134
|
+
return true;
|
|
17135
|
+
}
|
|
17136
|
+
if (message.includes("internal error") || message.includes("service unavailable") || message.includes("bad gateway") || message.includes("gateway timeout") || message.includes("overloaded")) {
|
|
17137
|
+
return true;
|
|
17138
|
+
}
|
|
17139
|
+
if (message.includes("rate limit") || message.includes("too many requests")) {
|
|
17140
|
+
return true;
|
|
17141
|
+
}
|
|
17142
|
+
if (message.includes("timed out") || message.includes("timeout")) {
|
|
17143
|
+
return true;
|
|
17144
|
+
}
|
|
17145
|
+
if (message.includes("json") || message.includes("parse") || message.includes("unexpected token")) {
|
|
17146
|
+
return true;
|
|
17147
|
+
}
|
|
17148
|
+
return true;
|
|
17149
|
+
}
|
|
17150
|
+
/**
|
|
17151
|
+
* Inject a continuation message into the conversation to help the LLM
|
|
17152
|
+
* recover from a failure and continue the task.
|
|
17153
|
+
*/
|
|
17154
|
+
injectContinuationMessage(error, retryAttempt) {
|
|
17155
|
+
const continuationPrompts = [
|
|
17156
|
+
// First retry: gentle continuation
|
|
17157
|
+
`[System Recovery] An error occurred (${error.message}). Please continue from where you left off. Review the conversation context and proceed with the next logical step. If you were in the middle of a tool call, retry it. If you completed tools, provide your response.`,
|
|
17158
|
+
// Second retry: more explicit
|
|
17159
|
+
`[System Recovery - Attempt ${retryAttempt + 1}] The previous operation encountered an error. Please analyze the current state and continue. Focus on completing the user's original request. If needed, you can re-read files or re-execute commands to verify the current state.`,
|
|
17160
|
+
// Third retry: most explicit with safety
|
|
17161
|
+
`[System Recovery - Final Attempt] Multiple errors have occurred. Please provide a status update to the user. If the task cannot be completed, explain what was accomplished and what remains. Do not attempt complex operations - focus on providing a helpful response.`
|
|
17162
|
+
];
|
|
17163
|
+
const promptIndex = Math.min(retryAttempt, continuationPrompts.length - 1);
|
|
17164
|
+
const continuationMessage = continuationPrompts[promptIndex];
|
|
17165
|
+
this.conversation.addSystemNote(continuationMessage);
|
|
17166
|
+
}
|
|
17167
|
+
/**
|
|
17168
|
+
* Submit a detailed bug report when a session failure occurs.
|
|
17169
|
+
*/
|
|
17170
|
+
async submitSessionFailureBugReport(error, retryAttempt, maxRetries) {
|
|
17171
|
+
try {
|
|
17172
|
+
const history = this.conversation.history();
|
|
17173
|
+
const recentToolCalls = history.filter((m) => m.role === "assistant" && m.tool_calls).slice(-3).flatMap((m) => m.tool_calls?.map((tc) => tc.function?.name) || []).filter(Boolean);
|
|
17174
|
+
const model = this.runtime.options.model ?? getProviderConfig(this.runtime.config, this.activeProvider)?.model;
|
|
17175
|
+
await this.telemetryManager.trackSessionFailureBug({
|
|
17176
|
+
error,
|
|
17177
|
+
retryAttempt,
|
|
17178
|
+
maxRetries,
|
|
17179
|
+
conversationLength: history.length,
|
|
17180
|
+
lastToolCalls: recentToolCalls,
|
|
17181
|
+
iterationCount: this.__currentIteration ?? 0,
|
|
17182
|
+
contextUsage: this.contextPercentLeft,
|
|
17183
|
+
model,
|
|
17184
|
+
provider: this.activeProvider
|
|
17185
|
+
});
|
|
17186
|
+
await this.errorLogger.log(error, {
|
|
17187
|
+
context: `Session failure (retry ${retryAttempt + 1}/${maxRetries})`,
|
|
17188
|
+
workspace: this.runtime.workspaceRoot
|
|
17189
|
+
});
|
|
17190
|
+
} catch (reportError) {
|
|
17191
|
+
console.error(chalk13.gray(`[Debug] Failed to submit bug report: ${reportError.message}`));
|
|
17192
|
+
}
|
|
17193
|
+
}
|
|
17194
|
+
/**
|
|
17195
|
+
* Display the detected intent mode to the user (only in debug mode)
|
|
17143
17196
|
*/
|
|
17144
17197
|
displayIntentMode(result) {
|
|
17198
|
+
if (process.env.AUTOHAND_DEBUG !== "1") {
|
|
17199
|
+
return;
|
|
17200
|
+
}
|
|
17145
17201
|
if (result.intent === "diagnostic") {
|
|
17146
17202
|
console.log(chalk13.blue("[DIAG] Mode: Diagnostic (read-only analysis)"));
|
|
17147
17203
|
if (result.keywords.length > 0) {
|
|
@@ -17161,18 +17217,23 @@ ${parts.join("\n")}`
|
|
|
17161
17217
|
* Run environment bootstrap before implementation
|
|
17162
17218
|
*/
|
|
17163
17219
|
async runEnvironmentBootstrap() {
|
|
17164
|
-
|
|
17220
|
+
const isDebug = process.env.AUTOHAND_DEBUG === "1";
|
|
17221
|
+
if (isDebug) {
|
|
17222
|
+
console.log(chalk13.cyan("[BOOTSTRAP] Running environment setup..."));
|
|
17223
|
+
}
|
|
17165
17224
|
const result = await this.environmentBootstrap.run(this.runtime.workspaceRoot);
|
|
17166
17225
|
for (const step of result.steps) {
|
|
17167
17226
|
const status = step.status === "success" ? chalk13.green("[OK]") : step.status === "failed" ? chalk13.red("[FAIL]") : step.status === "skipped" ? chalk13.gray("[SKIP]") : chalk13.gray("[...]");
|
|
17168
17227
|
const duration = step.duration ? chalk13.gray(`(${(step.duration / 1e3).toFixed(1)}s)`) : "";
|
|
17169
17228
|
const detail = step.detail ? chalk13.gray(` ${step.detail}`) : "";
|
|
17170
|
-
|
|
17229
|
+
if (step.status === "failed" || isDebug) {
|
|
17230
|
+
console.log(` ${status} ${step.name.padEnd(14)} ${duration}${detail}`);
|
|
17231
|
+
}
|
|
17171
17232
|
if (step.error) {
|
|
17172
17233
|
console.log(chalk13.red(` Error: ${step.error}`));
|
|
17173
17234
|
}
|
|
17174
17235
|
}
|
|
17175
|
-
if (result.success) {
|
|
17236
|
+
if (result.success && isDebug) {
|
|
17176
17237
|
console.log(chalk13.green(`
|
|
17177
17238
|
[READY] Environment ready (${(result.duration / 1e3).toFixed(1)}s)
|
|
17178
17239
|
`));
|
|
@@ -17218,6 +17279,45 @@ ${parts.join("\n")}`
|
|
|
17218
17279
|
getImageManager() {
|
|
17219
17280
|
return this.imageManager;
|
|
17220
17281
|
}
|
|
17282
|
+
/**
|
|
17283
|
+
* Handle a slash command (e.g., /skills, /skills install, /model)
|
|
17284
|
+
* Returns the command output or null if the command doesn't exist
|
|
17285
|
+
*/
|
|
17286
|
+
async handleSlashCommand(command, args = []) {
|
|
17287
|
+
return this.slashHandler.handle(command, args);
|
|
17288
|
+
}
|
|
17289
|
+
/**
|
|
17290
|
+
* Check if a string is a slash command
|
|
17291
|
+
*/
|
|
17292
|
+
isSlashCommand(input) {
|
|
17293
|
+
return input.trim().startsWith("/");
|
|
17294
|
+
}
|
|
17295
|
+
/**
|
|
17296
|
+
* Check if a slash command is supported (exists in the command map)
|
|
17297
|
+
*/
|
|
17298
|
+
isSlashCommandSupported(command) {
|
|
17299
|
+
return this.slashHandler.isCommandSupported(command);
|
|
17300
|
+
}
|
|
17301
|
+
/**
|
|
17302
|
+
* Parse a slash command string into command and args
|
|
17303
|
+
* e.g., "/skills install myskill" -> { command: "/skills install", args: ["myskill"] }
|
|
17304
|
+
*/
|
|
17305
|
+
parseSlashCommand(input) {
|
|
17306
|
+
const trimmed = input.trim();
|
|
17307
|
+
const parts = trimmed.split(/\s+/);
|
|
17308
|
+
const twoWordCommands = ["/skills install", "/skills new", "/agents new"];
|
|
17309
|
+
const potentialTwoWord = parts.slice(0, 2).join(" ");
|
|
17310
|
+
if (twoWordCommands.includes(potentialTwoWord)) {
|
|
17311
|
+
return {
|
|
17312
|
+
command: potentialTwoWord,
|
|
17313
|
+
args: parts.slice(2)
|
|
17314
|
+
};
|
|
17315
|
+
}
|
|
17316
|
+
return {
|
|
17317
|
+
command: parts[0],
|
|
17318
|
+
args: parts.slice(1)
|
|
17319
|
+
};
|
|
17320
|
+
}
|
|
17221
17321
|
/**
|
|
17222
17322
|
* Get messages with images included for the LLM API call.
|
|
17223
17323
|
* Modifies the last user message to include any images from the session.
|
|
@@ -17375,10 +17475,13 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
|
|
|
17375
17475
|
});
|
|
17376
17476
|
this.activeProvider = provider;
|
|
17377
17477
|
}
|
|
17378
|
-
async confirmDangerousAction(message) {
|
|
17478
|
+
async confirmDangerousAction(message, context) {
|
|
17379
17479
|
if (this.runtime.options.yes || this.runtime.config.ui?.autoConfirm) {
|
|
17380
17480
|
return true;
|
|
17381
17481
|
}
|
|
17482
|
+
if (this.confirmationCallback) {
|
|
17483
|
+
return this.confirmationCallback(message, context);
|
|
17484
|
+
}
|
|
17382
17485
|
if (isExternalCallbackEnabled()) {
|
|
17383
17486
|
return confirm(message);
|
|
17384
17487
|
}
|
|
@@ -17409,7 +17512,7 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
|
|
|
17409
17512
|
}
|
|
17410
17513
|
}
|
|
17411
17514
|
resolveWorkspacePath(relativePath) {
|
|
17412
|
-
const resolved =
|
|
17515
|
+
const resolved = path19.resolve(this.runtime.workspaceRoot, relativePath);
|
|
17413
17516
|
if (!resolved.startsWith(this.runtime.workspaceRoot)) {
|
|
17414
17517
|
throw new Error(`Path ${relativePath} escapes workspace root.`);
|
|
17415
17518
|
}
|
|
@@ -17426,6 +17529,13 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
|
|
|
17426
17529
|
setOutputListener(listener) {
|
|
17427
17530
|
this.outputListener = listener;
|
|
17428
17531
|
}
|
|
17532
|
+
/**
|
|
17533
|
+
* Set a callback for confirmation prompts (used by RPC mode)
|
|
17534
|
+
* When set, this callback is used instead of the default enquirer prompt
|
|
17535
|
+
*/
|
|
17536
|
+
setConfirmationCallback(callback) {
|
|
17537
|
+
this.confirmationCallback = callback;
|
|
17538
|
+
}
|
|
17429
17539
|
emitOutput(event) {
|
|
17430
17540
|
if (this.outputListener) {
|
|
17431
17541
|
this.outputListener(event);
|
|
@@ -17441,15 +17551,16 @@ ${chalk13.gray("\u203A")} ${inputPreview}${chalk13.gray("\u258B")}`;
|
|
|
17441
17551
|
return {
|
|
17442
17552
|
model: this.runtime.options.model ?? providerSettings?.model ?? "unconfigured",
|
|
17443
17553
|
workspace: this.runtime.workspaceRoot,
|
|
17444
|
-
contextPercent: this.contextPercentLeft
|
|
17554
|
+
contextPercent: this.contextPercentLeft,
|
|
17555
|
+
tokensUsed: this.totalTokensUsed
|
|
17445
17556
|
};
|
|
17446
17557
|
}
|
|
17447
17558
|
};
|
|
17448
17559
|
|
|
17449
17560
|
// src/skills/autoSkill.ts
|
|
17450
|
-
import
|
|
17561
|
+
import fs21 from "fs-extra";
|
|
17451
17562
|
import os6 from "os";
|
|
17452
|
-
import
|
|
17563
|
+
import path20 from "path";
|
|
17453
17564
|
import chalk14 from "chalk";
|
|
17454
17565
|
var AVAILABLE_TOOLS = {
|
|
17455
17566
|
file: [
|
|
@@ -17536,7 +17647,7 @@ var ProjectAnalyzer = class {
|
|
|
17536
17647
|
*/
|
|
17537
17648
|
async analyze() {
|
|
17538
17649
|
const analysis = {
|
|
17539
|
-
projectName:
|
|
17650
|
+
projectName: path20.basename(this.projectRoot),
|
|
17540
17651
|
languages: [],
|
|
17541
17652
|
frameworks: [],
|
|
17542
17653
|
patterns: [],
|
|
@@ -17548,8 +17659,8 @@ var ProjectAnalyzer = class {
|
|
|
17548
17659
|
hasCI: false,
|
|
17549
17660
|
packageManager: null
|
|
17550
17661
|
};
|
|
17551
|
-
analysis.hasGit = await
|
|
17552
|
-
analysis.hasCI = await
|
|
17662
|
+
analysis.hasGit = await fs21.pathExists(path20.join(this.projectRoot, ".git"));
|
|
17663
|
+
analysis.hasCI = await fs21.pathExists(path20.join(this.projectRoot, ".github", "workflows")) || await fs21.pathExists(path20.join(this.projectRoot, ".gitlab-ci.yml")) || await fs21.pathExists(path20.join(this.projectRoot, ".circleci"));
|
|
17553
17664
|
await this.analyzePackageJson(analysis);
|
|
17554
17665
|
await this.analyzePython(analysis);
|
|
17555
17666
|
await this.analyzeRust(analysis);
|
|
@@ -17571,12 +17682,12 @@ var ProjectAnalyzer = class {
|
|
|
17571
17682
|
* Analyze package.json for Node.js projects
|
|
17572
17683
|
*/
|
|
17573
17684
|
async analyzePackageJson(analysis) {
|
|
17574
|
-
const packageJsonPath =
|
|
17575
|
-
if (!await
|
|
17685
|
+
const packageJsonPath = path20.join(this.projectRoot, "package.json");
|
|
17686
|
+
if (!await fs21.pathExists(packageJsonPath)) {
|
|
17576
17687
|
return;
|
|
17577
17688
|
}
|
|
17578
17689
|
try {
|
|
17579
|
-
const pkg = await
|
|
17690
|
+
const pkg = await fs21.readJson(packageJsonPath);
|
|
17580
17691
|
analysis.projectName = pkg.name || analysis.projectName;
|
|
17581
17692
|
const allDeps = {
|
|
17582
17693
|
...pkg.dependencies,
|
|
@@ -17584,13 +17695,13 @@ var ProjectAnalyzer = class {
|
|
|
17584
17695
|
};
|
|
17585
17696
|
const depNames = Object.keys(allDeps);
|
|
17586
17697
|
analysis.dependencies.push(...depNames);
|
|
17587
|
-
if (await
|
|
17698
|
+
if (await fs21.pathExists(path20.join(this.projectRoot, "bun.lockb"))) {
|
|
17588
17699
|
analysis.packageManager = "bun";
|
|
17589
|
-
} else if (await
|
|
17700
|
+
} else if (await fs21.pathExists(path20.join(this.projectRoot, "pnpm-lock.yaml"))) {
|
|
17590
17701
|
analysis.packageManager = "pnpm";
|
|
17591
|
-
} else if (await
|
|
17702
|
+
} else if (await fs21.pathExists(path20.join(this.projectRoot, "yarn.lock"))) {
|
|
17592
17703
|
analysis.packageManager = "yarn";
|
|
17593
|
-
} else if (await
|
|
17704
|
+
} else if (await fs21.pathExists(path20.join(this.projectRoot, "package-lock.json"))) {
|
|
17594
17705
|
analysis.packageManager = "npm";
|
|
17595
17706
|
}
|
|
17596
17707
|
if (allDeps.typescript) {
|
|
@@ -17631,14 +17742,14 @@ var ProjectAnalyzer = class {
|
|
|
17631
17742
|
* Analyze Python project files
|
|
17632
17743
|
*/
|
|
17633
17744
|
async analyzePython(analysis) {
|
|
17634
|
-
const requirementsPath =
|
|
17635
|
-
const pyprojectPath =
|
|
17745
|
+
const requirementsPath = path20.join(this.projectRoot, "requirements.txt");
|
|
17746
|
+
const pyprojectPath = path20.join(this.projectRoot, "pyproject.toml");
|
|
17636
17747
|
let hasPython = false;
|
|
17637
|
-
if (await
|
|
17748
|
+
if (await fs21.pathExists(requirementsPath)) {
|
|
17638
17749
|
hasPython = true;
|
|
17639
17750
|
analysis.packageManager = "pip";
|
|
17640
17751
|
try {
|
|
17641
|
-
const content = await
|
|
17752
|
+
const content = await fs21.readFile(requirementsPath, "utf-8");
|
|
17642
17753
|
const lines = content.toLowerCase();
|
|
17643
17754
|
for (const [framework, deps] of Object.entries(PYTHON_FRAMEWORKS)) {
|
|
17644
17755
|
if (deps.some((d) => lines.includes(d))) {
|
|
@@ -17652,7 +17763,7 @@ var ProjectAnalyzer = class {
|
|
|
17652
17763
|
} catch {
|
|
17653
17764
|
}
|
|
17654
17765
|
}
|
|
17655
|
-
if (await
|
|
17766
|
+
if (await fs21.pathExists(pyprojectPath)) {
|
|
17656
17767
|
hasPython = true;
|
|
17657
17768
|
}
|
|
17658
17769
|
if (hasPython) {
|
|
@@ -17663,8 +17774,8 @@ var ProjectAnalyzer = class {
|
|
|
17663
17774
|
* Analyze Rust project
|
|
17664
17775
|
*/
|
|
17665
17776
|
async analyzeRust(analysis) {
|
|
17666
|
-
const cargoPath =
|
|
17667
|
-
if (await
|
|
17777
|
+
const cargoPath = path20.join(this.projectRoot, "Cargo.toml");
|
|
17778
|
+
if (await fs21.pathExists(cargoPath)) {
|
|
17668
17779
|
analysis.languages.push("rust");
|
|
17669
17780
|
analysis.packageManager = "cargo";
|
|
17670
17781
|
}
|
|
@@ -17673,8 +17784,8 @@ var ProjectAnalyzer = class {
|
|
|
17673
17784
|
* Analyze Go project
|
|
17674
17785
|
*/
|
|
17675
17786
|
async analyzeGo(analysis) {
|
|
17676
|
-
const goModPath =
|
|
17677
|
-
if (await
|
|
17787
|
+
const goModPath = path20.join(this.projectRoot, "go.mod");
|
|
17788
|
+
if (await fs21.pathExists(goModPath)) {
|
|
17678
17789
|
analysis.languages.push("go");
|
|
17679
17790
|
analysis.packageManager = "go";
|
|
17680
17791
|
}
|
|
@@ -17683,7 +17794,7 @@ var ProjectAnalyzer = class {
|
|
|
17683
17794
|
* Detect additional project patterns
|
|
17684
17795
|
*/
|
|
17685
17796
|
async detectPatterns(analysis) {
|
|
17686
|
-
if (await
|
|
17797
|
+
if (await fs21.pathExists(path20.join(this.projectRoot, "Dockerfile"))) {
|
|
17687
17798
|
analysis.patterns.push("docker");
|
|
17688
17799
|
}
|
|
17689
17800
|
const dbFiles = await this.findFiles("**/migrations/**", 2);
|
|
@@ -17702,13 +17813,13 @@ var ProjectAnalyzer = class {
|
|
|
17702
17813
|
async function walk(dir, depth) {
|
|
17703
17814
|
if (depth > maxDepth) return;
|
|
17704
17815
|
try {
|
|
17705
|
-
const entries = await
|
|
17816
|
+
const entries = await fs21.readdir(dir, { withFileTypes: true });
|
|
17706
17817
|
for (const entry of entries) {
|
|
17707
|
-
const fullPath =
|
|
17818
|
+
const fullPath = path20.join(dir, entry.name);
|
|
17708
17819
|
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
17709
17820
|
await walk(fullPath, depth + 1);
|
|
17710
17821
|
} else if (entry.isFile()) {
|
|
17711
|
-
const ext =
|
|
17822
|
+
const ext = path20.extname(entry.name);
|
|
17712
17823
|
if (pattern === "*.js" && ext === ".js") {
|
|
17713
17824
|
results.push(fullPath);
|
|
17714
17825
|
} else if (pattern === "*.ts" && ext === ".ts") {
|
|
@@ -17853,10 +17964,10 @@ async function generateAutoSkills(analysis, llm) {
|
|
|
17853
17964
|
}
|
|
17854
17965
|
}
|
|
17855
17966
|
async function saveSkill(skill, skillsDir) {
|
|
17856
|
-
const skillDir =
|
|
17857
|
-
const skillPath =
|
|
17967
|
+
const skillDir = path20.join(skillsDir, skill.name);
|
|
17968
|
+
const skillPath = path20.join(skillDir, "SKILL.md");
|
|
17858
17969
|
try {
|
|
17859
|
-
await
|
|
17970
|
+
await fs21.ensureDir(skillDir);
|
|
17860
17971
|
let frontmatter = `---
|
|
17861
17972
|
name: ${skill.name}
|
|
17862
17973
|
description: ${skill.description}`;
|
|
@@ -17869,7 +17980,7 @@ allowed-tools: ${skill.allowedTools.join(" ")}`;
|
|
|
17869
17980
|
|
|
17870
17981
|
`;
|
|
17871
17982
|
const content = frontmatter + skill.body + "\n";
|
|
17872
|
-
await
|
|
17983
|
+
await fs21.writeFile(skillPath, content, "utf-8");
|
|
17873
17984
|
return true;
|
|
17874
17985
|
} catch {
|
|
17875
17986
|
return false;
|
|
@@ -17905,7 +18016,7 @@ async function runAutoSkillGeneration(workspaceRoot, llm) {
|
|
|
17905
18016
|
result.error = "No skills generated";
|
|
17906
18017
|
return result;
|
|
17907
18018
|
}
|
|
17908
|
-
const skillsDir =
|
|
18019
|
+
const skillsDir = path20.join(workspaceRoot, PROJECT_DIR_NAME, "skills");
|
|
17909
18020
|
for (const skill of skills) {
|
|
17910
18021
|
const saved = await saveSkill(skill, skillsDir);
|
|
17911
18022
|
if (saved) {
|
|
@@ -17948,7 +18059,8 @@ var RPC_METHODS = {
|
|
|
17948
18059
|
RESET: "autohand.reset",
|
|
17949
18060
|
GET_STATE: "autohand.getState",
|
|
17950
18061
|
GET_MESSAGES: "autohand.getMessages",
|
|
17951
|
-
PERMISSION_RESPONSE: "autohand.permissionResponse"
|
|
18062
|
+
PERMISSION_RESPONSE: "autohand.permissionResponse",
|
|
18063
|
+
PERMISSION_ACKNOWLEDGED: "autohand.permissionAcknowledged"
|
|
17952
18064
|
};
|
|
17953
18065
|
var RPC_NOTIFICATIONS = {
|
|
17954
18066
|
AGENT_START: "autohand.agentStart",
|
|
@@ -18212,6 +18324,7 @@ var RPCAdapter = class {
|
|
|
18212
18324
|
this.imageManager = null;
|
|
18213
18325
|
this.sessionId = null;
|
|
18214
18326
|
this.currentTurnId = null;
|
|
18327
|
+
this.turnStartTime = null;
|
|
18215
18328
|
this.currentMessageId = null;
|
|
18216
18329
|
this.currentMessageContent = "";
|
|
18217
18330
|
this.pendingPermissions = /* @__PURE__ */ new Map();
|
|
@@ -18286,16 +18399,37 @@ var RPCAdapter = class {
|
|
|
18286
18399
|
this.status = "processing";
|
|
18287
18400
|
this.abortController = new AbortController();
|
|
18288
18401
|
this.currentTurnId = generateId("turn");
|
|
18402
|
+
this.turnStartTime = Date.now();
|
|
18289
18403
|
writeNotification(RPC_NOTIFICATIONS.TURN_START, {
|
|
18290
18404
|
turnId: this.currentTurnId,
|
|
18291
18405
|
timestamp: createTimestamp()
|
|
18292
18406
|
});
|
|
18293
18407
|
try {
|
|
18294
18408
|
const imagePlaceholders = [];
|
|
18295
|
-
|
|
18409
|
+
process.stderr.write(`[RPC] handlePrompt: images=${params.images?.length || 0}, hasImageManager=${!!this.imageManager}, model=${this.model}
|
|
18410
|
+
`);
|
|
18411
|
+
if (params.images && params.images.length > 0) {
|
|
18412
|
+
if (!supportsVision(this.model)) {
|
|
18413
|
+
process.stderr.write(`[RPC] WARNING: Model '${this.model}' does not support vision. Images will not be processed.
|
|
18414
|
+
`);
|
|
18415
|
+
writeNotification(RPC_NOTIFICATIONS.ERROR, {
|
|
18416
|
+
code: -32e3,
|
|
18417
|
+
message: `Model '${this.model}' does not support image inputs. Please use a vision-capable model like claude-3.5-sonnet, gpt-4o, or gemini-1.5-pro.`,
|
|
18418
|
+
recoverable: true,
|
|
18419
|
+
timestamp: createTimestamp()
|
|
18420
|
+
});
|
|
18421
|
+
}
|
|
18422
|
+
}
|
|
18423
|
+
if (params.images && params.images.length > 0 && this.imageManager && supportsVision(this.model)) {
|
|
18424
|
+
process.stderr.write(`[RPC] Processing ${params.images.length} images
|
|
18425
|
+
`);
|
|
18296
18426
|
for (const img of params.images) {
|
|
18297
18427
|
try {
|
|
18428
|
+
process.stderr.write(`[RPC] Image: mimeType=${img.mimeType}, dataLength=${img.data?.length || 0}
|
|
18429
|
+
`);
|
|
18298
18430
|
if (!isValidImageMimeType(img.mimeType)) {
|
|
18431
|
+
process.stderr.write(`[RPC] Invalid MIME type: ${img.mimeType}
|
|
18432
|
+
`);
|
|
18299
18433
|
writeNotification(RPC_NOTIFICATIONS.ERROR, {
|
|
18300
18434
|
code: -32602,
|
|
18301
18435
|
// Invalid params
|
|
@@ -18306,6 +18440,8 @@ var RPCAdapter = class {
|
|
|
18306
18440
|
continue;
|
|
18307
18441
|
}
|
|
18308
18442
|
const data = Buffer.from(img.data, "base64");
|
|
18443
|
+
process.stderr.write(`[RPC] Image decoded: ${data.length} bytes
|
|
18444
|
+
`);
|
|
18309
18445
|
if (data.length > MAX_IMAGE_SIZE) {
|
|
18310
18446
|
writeNotification(RPC_NOTIFICATIONS.ERROR, {
|
|
18311
18447
|
code: -32602,
|
|
@@ -18331,9 +18467,14 @@ var RPCAdapter = class {
|
|
|
18331
18467
|
}
|
|
18332
18468
|
let instruction = params.message;
|
|
18333
18469
|
if (imagePlaceholders.length > 0) {
|
|
18470
|
+
process.stderr.write(`[RPC] Image placeholders: ${imagePlaceholders.join(", ")}
|
|
18471
|
+
`);
|
|
18334
18472
|
instruction = `${imagePlaceholders.join(" ")}
|
|
18335
18473
|
|
|
18336
18474
|
${instruction}`;
|
|
18475
|
+
} else if (params.images && params.images.length > 0) {
|
|
18476
|
+
process.stderr.write(`[RPC] WARNING: Images provided but no placeholders generated!
|
|
18477
|
+
`);
|
|
18337
18478
|
}
|
|
18338
18479
|
if (params.context?.selection) {
|
|
18339
18480
|
const sel = params.context.selection;
|
|
@@ -18355,7 +18496,40 @@ ${sel.text}
|
|
|
18355
18496
|
try {
|
|
18356
18497
|
process.stderr.write(`[RPC DEBUG] Executing instruction: ${instruction.substring(0, 100)}
|
|
18357
18498
|
`);
|
|
18358
|
-
|
|
18499
|
+
if (this.agent.isSlashCommand(instruction)) {
|
|
18500
|
+
const { command, args } = this.agent.parseSlashCommand(instruction);
|
|
18501
|
+
process.stderr.write(`[RPC DEBUG] Handling slash command: ${command}, args: ${JSON.stringify(args)}
|
|
18502
|
+
`);
|
|
18503
|
+
if (this.agent.isSlashCommandSupported(command)) {
|
|
18504
|
+
const result = await this.agent.handleSlashCommand(command, args);
|
|
18505
|
+
if (result !== null) {
|
|
18506
|
+
this.currentMessageContent = result;
|
|
18507
|
+
writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
|
|
18508
|
+
messageId: this.currentMessageId,
|
|
18509
|
+
delta: result,
|
|
18510
|
+
timestamp: createTimestamp()
|
|
18511
|
+
});
|
|
18512
|
+
} else {
|
|
18513
|
+
this.currentMessageContent = `Command ${command} executed.`;
|
|
18514
|
+
writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
|
|
18515
|
+
messageId: this.currentMessageId,
|
|
18516
|
+
delta: this.currentMessageContent,
|
|
18517
|
+
timestamp: createTimestamp()
|
|
18518
|
+
});
|
|
18519
|
+
}
|
|
18520
|
+
success = true;
|
|
18521
|
+
} else {
|
|
18522
|
+
this.currentMessageContent = `Unknown command: ${command}. Type /help for available commands.`;
|
|
18523
|
+
writeNotification(RPC_NOTIFICATIONS.MESSAGE_UPDATE, {
|
|
18524
|
+
messageId: this.currentMessageId,
|
|
18525
|
+
delta: this.currentMessageContent,
|
|
18526
|
+
timestamp: createTimestamp()
|
|
18527
|
+
});
|
|
18528
|
+
success = false;
|
|
18529
|
+
}
|
|
18530
|
+
} else {
|
|
18531
|
+
success = await this.agent.runInstruction(instruction);
|
|
18532
|
+
}
|
|
18359
18533
|
process.stderr.write(`[RPC DEBUG] Instruction completed, success=${success}, content length=${this.currentMessageContent.length}
|
|
18360
18534
|
`);
|
|
18361
18535
|
} catch (err) {
|
|
@@ -18378,24 +18552,34 @@ ${sel.text}
|
|
|
18378
18552
|
content: this.currentMessageContent,
|
|
18379
18553
|
timestamp: createTimestamp()
|
|
18380
18554
|
});
|
|
18555
|
+
const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
|
|
18556
|
+
const snapshot = this.agent?.getStatusSnapshot();
|
|
18381
18557
|
writeNotification(RPC_NOTIFICATIONS.TURN_END, {
|
|
18382
18558
|
turnId: this.currentTurnId,
|
|
18383
18559
|
timestamp: createTimestamp(),
|
|
18384
|
-
contextPercent: this.contextPercent
|
|
18560
|
+
contextPercent: this.contextPercent,
|
|
18561
|
+
tokensUsed: snapshot?.tokensUsed,
|
|
18562
|
+
durationMs
|
|
18385
18563
|
});
|
|
18386
18564
|
this.status = "idle";
|
|
18387
18565
|
this.currentTurnId = null;
|
|
18566
|
+
this.turnStartTime = null;
|
|
18388
18567
|
this.currentMessageId = null;
|
|
18389
18568
|
this.abortController = null;
|
|
18390
18569
|
return { success };
|
|
18391
18570
|
} catch (error) {
|
|
18571
|
+
const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
|
|
18572
|
+
const snapshot = this.agent?.getStatusSnapshot();
|
|
18392
18573
|
writeNotification(RPC_NOTIFICATIONS.TURN_END, {
|
|
18393
18574
|
turnId: this.currentTurnId,
|
|
18394
18575
|
timestamp: createTimestamp(),
|
|
18395
|
-
contextPercent: this.contextPercent
|
|
18576
|
+
contextPercent: this.contextPercent,
|
|
18577
|
+
tokensUsed: snapshot?.tokensUsed,
|
|
18578
|
+
durationMs
|
|
18396
18579
|
});
|
|
18397
18580
|
this.status = "idle";
|
|
18398
18581
|
this.currentTurnId = null;
|
|
18582
|
+
this.turnStartTime = null;
|
|
18399
18583
|
this.currentMessageId = null;
|
|
18400
18584
|
this.abortController = null;
|
|
18401
18585
|
throw error;
|
|
@@ -18405,24 +18589,41 @@ ${sel.text}
|
|
|
18405
18589
|
* Handle abort request
|
|
18406
18590
|
*/
|
|
18407
18591
|
handleAbort(requestId) {
|
|
18592
|
+
process.stderr.write(`[RPC] handleAbort called, abortController=${!!this.abortController}
|
|
18593
|
+
`);
|
|
18594
|
+
for (const [permId, pending] of this.pendingPermissions) {
|
|
18595
|
+
process.stderr.write(`[RPC] Clearing pending permission ${permId} due to abort
|
|
18596
|
+
`);
|
|
18597
|
+
if (pending.ackTimeout) clearTimeout(pending.ackTimeout);
|
|
18598
|
+
if (pending.responseTimeout) clearTimeout(pending.responseTimeout);
|
|
18599
|
+
pending.resolve(false);
|
|
18600
|
+
}
|
|
18601
|
+
this.pendingPermissions.clear();
|
|
18408
18602
|
if (this.abortController) {
|
|
18409
18603
|
this.abortController.abort();
|
|
18410
18604
|
this.status = "idle";
|
|
18411
18605
|
if (this.currentMessageId) {
|
|
18412
18606
|
writeNotification(RPC_NOTIFICATIONS.MESSAGE_END, {
|
|
18413
18607
|
messageId: this.currentMessageId,
|
|
18414
|
-
content: this.currentMessageContent
|
|
18608
|
+
content: this.currentMessageContent,
|
|
18609
|
+
// No marker - UI handles display
|
|
18610
|
+
aborted: true,
|
|
18415
18611
|
timestamp: createTimestamp()
|
|
18416
18612
|
});
|
|
18417
18613
|
}
|
|
18418
18614
|
if (this.currentTurnId) {
|
|
18615
|
+
const durationMs = this.turnStartTime ? Date.now() - this.turnStartTime : void 0;
|
|
18616
|
+
const snapshot = this.agent?.getStatusSnapshot();
|
|
18419
18617
|
writeNotification(RPC_NOTIFICATIONS.TURN_END, {
|
|
18420
18618
|
turnId: this.currentTurnId,
|
|
18421
18619
|
timestamp: createTimestamp(),
|
|
18422
|
-
contextPercent: this.contextPercent
|
|
18620
|
+
contextPercent: this.contextPercent,
|
|
18621
|
+
tokensUsed: snapshot?.tokensUsed,
|
|
18622
|
+
durationMs
|
|
18423
18623
|
});
|
|
18424
18624
|
}
|
|
18425
18625
|
this.currentTurnId = null;
|
|
18626
|
+
this.turnStartTime = null;
|
|
18426
18627
|
this.currentMessageId = null;
|
|
18427
18628
|
this.currentMessageContent = "";
|
|
18428
18629
|
this.abortController = null;
|
|
@@ -18470,22 +18671,40 @@ ${sel.text}
|
|
|
18470
18671
|
* Handle permission response from client
|
|
18471
18672
|
*/
|
|
18472
18673
|
handlePermissionResponse(requestId, permRequestId, allowed) {
|
|
18674
|
+
process.stderr.write(`[RPC] handlePermissionResponse called: permRequestId=${permRequestId}, allowed=${allowed}, pending keys=${Array.from(this.pendingPermissions.keys()).join(",")}
|
|
18675
|
+
`);
|
|
18473
18676
|
const pending = this.pendingPermissions.get(permRequestId);
|
|
18474
18677
|
if (pending) {
|
|
18475
|
-
|
|
18678
|
+
process.stderr.write(`[RPC] Found pending permission, resolving with allowed=${allowed}
|
|
18679
|
+
`);
|
|
18680
|
+
if (pending.ackTimeout) {
|
|
18681
|
+
clearTimeout(pending.ackTimeout);
|
|
18682
|
+
}
|
|
18683
|
+
if (pending.responseTimeout) {
|
|
18684
|
+
clearTimeout(pending.responseTimeout);
|
|
18685
|
+
}
|
|
18476
18686
|
this.pendingPermissions.delete(permRequestId);
|
|
18477
18687
|
pending.resolve(allowed);
|
|
18478
18688
|
this.status = "processing";
|
|
18689
|
+
process.stderr.write(`[RPC] Permission resolved, status set to processing
|
|
18690
|
+
`);
|
|
18479
18691
|
return { success: true };
|
|
18480
18692
|
}
|
|
18693
|
+
process.stderr.write(`[RPC] Permission response for unknown request ${permRequestId}
|
|
18694
|
+
`);
|
|
18481
18695
|
return { success: false };
|
|
18482
18696
|
}
|
|
18483
18697
|
/**
|
|
18484
18698
|
* Request permission from client (called from agent's confirmDangerousAction)
|
|
18699
|
+
* Uses two-phase timeout:
|
|
18700
|
+
* - Phase 1: 30s to receive acknowledgment from extension
|
|
18701
|
+
* - Phase 2: 1 hour for user to respond after ack received
|
|
18485
18702
|
*/
|
|
18486
18703
|
async requestPermission(tool, description, context) {
|
|
18487
18704
|
const permRequestId = generateId("perm");
|
|
18488
18705
|
this.status = "waiting_permission";
|
|
18706
|
+
process.stderr.write(`[RPC] requestPermission: tool=${tool}, permRequestId=${permRequestId}
|
|
18707
|
+
`);
|
|
18489
18708
|
writeNotification(RPC_NOTIFICATIONS.PERMISSION_REQUEST, {
|
|
18490
18709
|
requestId: permRequestId,
|
|
18491
18710
|
tool,
|
|
@@ -18494,19 +18713,53 @@ ${sel.text}
|
|
|
18494
18713
|
timestamp: createTimestamp()
|
|
18495
18714
|
});
|
|
18496
18715
|
return new Promise((resolve, reject) => {
|
|
18497
|
-
const
|
|
18716
|
+
const ackTimeout = setTimeout(() => {
|
|
18498
18717
|
this.pendingPermissions.delete(permRequestId);
|
|
18499
18718
|
this.status = "processing";
|
|
18719
|
+
process.stderr.write(`[RPC] Permission ack timeout for ${permRequestId}
|
|
18720
|
+
`);
|
|
18500
18721
|
resolve(false);
|
|
18501
|
-
},
|
|
18722
|
+
}, 3e4);
|
|
18502
18723
|
this.pendingPermissions.set(permRequestId, {
|
|
18503
18724
|
requestId: permRequestId,
|
|
18504
18725
|
resolve,
|
|
18505
18726
|
reject,
|
|
18506
|
-
|
|
18727
|
+
ackTimeout,
|
|
18728
|
+
responseTimeout: null,
|
|
18729
|
+
acknowledged: false
|
|
18507
18730
|
});
|
|
18508
18731
|
});
|
|
18509
18732
|
}
|
|
18733
|
+
/**
|
|
18734
|
+
* Handle acknowledgment from client that permission UI is shown
|
|
18735
|
+
* Extends timeout since we know extension is alive and user is deciding
|
|
18736
|
+
*/
|
|
18737
|
+
handlePermissionAcknowledged(permRequestId) {
|
|
18738
|
+
const pending = this.pendingPermissions.get(permRequestId);
|
|
18739
|
+
if (!pending) {
|
|
18740
|
+
process.stderr.write(`[RPC] Permission ack for unknown request ${permRequestId}
|
|
18741
|
+
`);
|
|
18742
|
+
return { success: false };
|
|
18743
|
+
}
|
|
18744
|
+
if (pending.acknowledged) {
|
|
18745
|
+
return { success: true };
|
|
18746
|
+
}
|
|
18747
|
+
if (pending.ackTimeout) {
|
|
18748
|
+
clearTimeout(pending.ackTimeout);
|
|
18749
|
+
pending.ackTimeout = null;
|
|
18750
|
+
}
|
|
18751
|
+
pending.acknowledged = true;
|
|
18752
|
+
pending.responseTimeout = setTimeout(() => {
|
|
18753
|
+
this.pendingPermissions.delete(permRequestId);
|
|
18754
|
+
this.status = "processing";
|
|
18755
|
+
process.stderr.write(`[RPC] Permission response timeout for ${permRequestId} (1 hour)
|
|
18756
|
+
`);
|
|
18757
|
+
pending.resolve(false);
|
|
18758
|
+
}, 36e5);
|
|
18759
|
+
process.stderr.write(`[RPC] Permission acknowledged for ${permRequestId}
|
|
18760
|
+
`);
|
|
18761
|
+
return { success: true };
|
|
18762
|
+
}
|
|
18510
18763
|
/**
|
|
18511
18764
|
* Emit tool execution start notification
|
|
18512
18765
|
*/
|
|
@@ -18561,7 +18814,12 @@ ${sel.text}
|
|
|
18561
18814
|
*/
|
|
18562
18815
|
shutdown(reason = "completed") {
|
|
18563
18816
|
for (const [, pending] of this.pendingPermissions) {
|
|
18564
|
-
|
|
18817
|
+
if (pending.ackTimeout) {
|
|
18818
|
+
clearTimeout(pending.ackTimeout);
|
|
18819
|
+
}
|
|
18820
|
+
if (pending.responseTimeout) {
|
|
18821
|
+
clearTimeout(pending.responseTimeout);
|
|
18822
|
+
}
|
|
18565
18823
|
pending.reject(new Error("Adapter shutdown"));
|
|
18566
18824
|
}
|
|
18567
18825
|
this.pendingPermissions.clear();
|
|
@@ -18736,9 +18994,8 @@ async function runRpcMode(options) {
|
|
|
18736
18994
|
config,
|
|
18737
18995
|
workspaceRoot,
|
|
18738
18996
|
options: {
|
|
18739
|
-
...options
|
|
18740
|
-
//
|
|
18741
|
-
yes: true
|
|
18997
|
+
...options
|
|
18998
|
+
// Do NOT set yes: true - permissions are handled via RPC
|
|
18742
18999
|
}
|
|
18743
19000
|
};
|
|
18744
19001
|
const provider = ProviderFactory.create(config);
|
|
@@ -18756,6 +19013,17 @@ async function runRpcMode(options) {
|
|
|
18756
19013
|
options.model ?? config.openrouter?.model ?? "unknown",
|
|
18757
19014
|
workspaceRoot
|
|
18758
19015
|
);
|
|
19016
|
+
agent.setConfirmationCallback(async (message, context) => {
|
|
19017
|
+
if (!adapter) {
|
|
19018
|
+
throw new Error("RPC adapter not initialized");
|
|
19019
|
+
}
|
|
19020
|
+
const tool = context?.tool ?? "action";
|
|
19021
|
+
const description = message;
|
|
19022
|
+
const permContext = {};
|
|
19023
|
+
if (context?.command) permContext.command = context.command;
|
|
19024
|
+
if (context?.path) permContext.path = context.path;
|
|
19025
|
+
return adapter.requestPermission(tool, description, permContext);
|
|
19026
|
+
});
|
|
18759
19027
|
const reader = new LineReader(process.stdin);
|
|
18760
19028
|
while (true) {
|
|
18761
19029
|
try {
|
|
@@ -18813,8 +19081,21 @@ async function handleSingleRequest(request, adapter) {
|
|
|
18813
19081
|
}
|
|
18814
19082
|
return null;
|
|
18815
19083
|
}
|
|
18816
|
-
|
|
18817
|
-
|
|
19084
|
+
adapter.handlePrompt(id, promptParams).then((promptResult) => {
|
|
19085
|
+
if (shouldRespond) {
|
|
19086
|
+
process.stdout.write(JSON.stringify(createResponse(id, promptResult)) + "\n");
|
|
19087
|
+
}
|
|
19088
|
+
}).catch((error) => {
|
|
19089
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19090
|
+
if (shouldRespond) {
|
|
19091
|
+
process.stdout.write(JSON.stringify(createErrorResponse(
|
|
19092
|
+
id,
|
|
19093
|
+
JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
19094
|
+
message
|
|
19095
|
+
)) + "\n");
|
|
19096
|
+
}
|
|
19097
|
+
});
|
|
19098
|
+
return null;
|
|
18818
19099
|
}
|
|
18819
19100
|
case RPC_METHODS.ABORT: {
|
|
18820
19101
|
result = adapter.handleAbort(id);
|
|
@@ -18848,6 +19129,21 @@ async function handleSingleRequest(request, adapter) {
|
|
|
18848
19129
|
result = adapter.handlePermissionResponse(id, permParams.requestId, permParams.allowed);
|
|
18849
19130
|
break;
|
|
18850
19131
|
}
|
|
19132
|
+
case RPC_METHODS.PERMISSION_ACKNOWLEDGED: {
|
|
19133
|
+
const ackParams = params;
|
|
19134
|
+
if (!ackParams?.requestId) {
|
|
19135
|
+
if (shouldRespond) {
|
|
19136
|
+
return createErrorResponse(
|
|
19137
|
+
id,
|
|
19138
|
+
JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
19139
|
+
"Missing required parameter: requestId"
|
|
19140
|
+
);
|
|
19141
|
+
}
|
|
19142
|
+
return null;
|
|
19143
|
+
}
|
|
19144
|
+
result = adapter.handlePermissionAcknowledged(ackParams.requestId);
|
|
19145
|
+
break;
|
|
19146
|
+
}
|
|
18851
19147
|
default: {
|
|
18852
19148
|
if (shouldRespond) {
|
|
18853
19149
|
return createErrorResponse(
|
|
@@ -18879,6 +19175,9 @@ async function handleSingleRequest(request, adapter) {
|
|
|
18879
19175
|
// src/index.ts
|
|
18880
19176
|
process.title = "autohand";
|
|
18881
19177
|
function getGitCommit() {
|
|
19178
|
+
if (true) {
|
|
19179
|
+
return "056d8fb";
|
|
19180
|
+
}
|
|
18882
19181
|
try {
|
|
18883
19182
|
return execSync3("git rev-parse --short HEAD", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
18884
19183
|
} catch {
|
|
@@ -18929,6 +19228,9 @@ process.on("uncaughtException", (err) => {
|
|
|
18929
19228
|
process.exit(1);
|
|
18930
19229
|
});
|
|
18931
19230
|
process.on("unhandledRejection", (reason, promise) => {
|
|
19231
|
+
if (reason && typeof reason === "object" && reason.code === "ERR_USE_AFTER_CLOSE") {
|
|
19232
|
+
return;
|
|
19233
|
+
}
|
|
18932
19234
|
globalThis.__autohandLastError = reason;
|
|
18933
19235
|
console.error("[DEBUG] Unhandled Rejection at:", promise, "reason:", reason);
|
|
18934
19236
|
});
|
|
@@ -18943,7 +19245,11 @@ var ASCII_FRIEND = [
|
|
|
18943
19245
|
"\u2808\u28BF\u28E6\u28E4\u28F4\u287F\u2803\u2800\u2819\u28B7\u28E6\u28E4\u28F6\u287F\u2801\u2808\u283B\u28F7\u28E4\u28E4\u287E\u281B\u2800\u2808\u28BF\u28E6\u28E4\u28E4\u2834\u2801"
|
|
18944
19246
|
].join("\n");
|
|
18945
19247
|
var program = new Command();
|
|
18946
|
-
program.name("autohand").description("Autonomous LLM-powered coding agent CLI").version(getVersionString(), "-v, --version", "output the current version").option("-p, --prompt <text>", "Run a single instruction in command mode").option("--path <path>", "Workspace path to operate in").option("-y, --yes", "Auto-confirm risky actions", false).option("--dry-run", "Preview actions without applying mutations", false).option("--model <model>", "Override the configured LLM model").option("--config <path>", "Path to config file (default ~/.autohand/config.json)").option("--temperature <value>", "Sampling temperature", parseFloat).option("-c, --auto-commit", "Auto-commit changes after completing tasks", false).option("--unrestricted", "Run without any approval prompts (use with caution)", false).option("--restricted", "Deny all dangerous operations automatically", false).option("--auto-skill", "Auto-generate skills based on project analysis", false).option("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").action(async (opts) => {
|
|
19248
|
+
program.name("autohand").description("Autonomous LLM-powered coding agent CLI").version(getVersionString(), "-v, --version", "output the current version").option("-p, --prompt <text>", "Run a single instruction in command mode").option("--path <path>", "Workspace path to operate in").option("-y, --yes", "Auto-confirm risky actions", false).option("--dry-run", "Preview actions without applying mutations", false).option("--model <model>", "Override the configured LLM model").option("--config <path>", "Path to config file (default ~/.autohand/config.json)").option("--temperature <value>", "Sampling temperature", parseFloat).option("-c, --auto-commit", "Auto-commit changes after completing tasks", false).option("--unrestricted", "Run without any approval prompts (use with caution)", false).option("--restricted", "Deny all dangerous operations automatically", false).option("--auto-skill", "Auto-generate skills based on project analysis", false).option("--skill-install [skill-name]", "Install a community skill (opens browser if no name)").option("--project", "Install skill to project level (with --skill-install)", false).option("--mode <mode>", "Run mode: interactive (default) or rpc", "interactive").action(async (opts) => {
|
|
19249
|
+
if (opts.skillInstall !== void 0) {
|
|
19250
|
+
await runSkillInstall(opts);
|
|
19251
|
+
return;
|
|
19252
|
+
}
|
|
18947
19253
|
if (opts.mode === "rpc") {
|
|
18948
19254
|
await runRpcMode(opts);
|
|
18949
19255
|
} else {
|
|
@@ -18967,17 +19273,27 @@ async function runCLI(options) {
|
|
|
18967
19273
|
}
|
|
18968
19274
|
console.log(chalk15.yellow(`No ${providerName} API key configured.
|
|
18969
19275
|
`));
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
19276
|
+
let apiKey;
|
|
19277
|
+
try {
|
|
19278
|
+
const result = await enquirer4.prompt({
|
|
19279
|
+
type: "password",
|
|
19280
|
+
name: "apiKey",
|
|
19281
|
+
message: `Enter your ${providerName === "openrouter" ? "OpenRouter" : providerName} API key`,
|
|
19282
|
+
validate: (val) => {
|
|
19283
|
+
if (typeof val !== "string" || !val.trim()) {
|
|
19284
|
+
return "API key is required";
|
|
19285
|
+
}
|
|
19286
|
+
return true;
|
|
18977
19287
|
}
|
|
18978
|
-
|
|
19288
|
+
});
|
|
19289
|
+
apiKey = result.apiKey;
|
|
19290
|
+
} catch (error) {
|
|
19291
|
+
if (error?.code === "ERR_USE_AFTER_CLOSE" || error?.message?.includes("cancelled")) {
|
|
19292
|
+
console.log(chalk15.gray("\nSetup cancelled."));
|
|
19293
|
+
process.exit(0);
|
|
18979
19294
|
}
|
|
18980
|
-
|
|
19295
|
+
throw error;
|
|
19296
|
+
}
|
|
18981
19297
|
if (providerName === "openrouter") {
|
|
18982
19298
|
config.openrouter = {
|
|
18983
19299
|
...config.openrouter,
|
|
@@ -19079,6 +19395,21 @@ function printWelcome(runtime, authUser) {
|
|
|
19079
19395
|
}
|
|
19080
19396
|
console.log();
|
|
19081
19397
|
}
|
|
19398
|
+
async function runSkillInstall(opts) {
|
|
19399
|
+
const config = await loadConfig(opts.config);
|
|
19400
|
+
const workspaceRoot = resolveWorkspaceRoot(config, opts.path);
|
|
19401
|
+
const { SkillsRegistry: SkillsRegistry2 } = await import("./SkillsRegistry-SP5MX7OA.js");
|
|
19402
|
+
const { AUTOHAND_PATHS: AUTOHAND_PATHS2 } = await import("./constants-N3I2FHCM.js");
|
|
19403
|
+
const { skillsInstall } = await import("./skills-install-WJ2LDRQG.js");
|
|
19404
|
+
const skillsRegistry = new SkillsRegistry2(AUTOHAND_PATHS2.skills);
|
|
19405
|
+
await skillsRegistry.initialize();
|
|
19406
|
+
await skillsRegistry.setWorkspace(workspaceRoot);
|
|
19407
|
+
const skillName = typeof opts.skillInstall === "string" ? opts.skillInstall : void 0;
|
|
19408
|
+
await skillsInstall({
|
|
19409
|
+
skillsRegistry,
|
|
19410
|
+
workspaceRoot
|
|
19411
|
+
}, skillName);
|
|
19412
|
+
}
|
|
19082
19413
|
program.parseAsync();
|
|
19083
19414
|
/**
|
|
19084
19415
|
* @license
|
|
@@ -19157,20 +19488,6 @@ program.parseAsync();
|
|
|
19157
19488
|
* TelemetryManager - High-level telemetry tracking
|
|
19158
19489
|
* @license Apache-2.0
|
|
19159
19490
|
*/
|
|
19160
|
-
/**
|
|
19161
|
-
* @license
|
|
19162
|
-
* Copyright 2025 Autohand AI LLC
|
|
19163
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
19164
|
-
*
|
|
19165
|
-
* SkillParser - Parses SKILL.md files with YAML frontmatter
|
|
19166
|
-
*/
|
|
19167
|
-
/**
|
|
19168
|
-
* @license
|
|
19169
|
-
* Copyright 2025 Autohand AI LLC
|
|
19170
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
19171
|
-
*
|
|
19172
|
-
* SkillsRegistry - Manages skill discovery, loading, and activation
|
|
19173
|
-
*/
|
|
19174
19491
|
/**
|
|
19175
19492
|
* @license
|
|
19176
19493
|
* Copyright 2025 Autohand AI LLC
|