cliskill 1.0.4 → 1.0.6
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/README.md +66 -0
- package/dist/bootstrap/cli.js +1 -1
- package/dist/{chunk-UJMUL64T.js → chunk-S4ZZPUPF.js} +1595 -159
- package/dist/chunk-S4ZZPUPF.js.map +1 -0
- package/dist/index.d.ts +249 -63
- package/dist/index.js +46 -313
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-UJMUL64T.js.map +0 -1
|
@@ -90,6 +90,18 @@ var modelsSchema = z.object({
|
|
|
90
90
|
fallbackModel: z.string().default("glm-5.1"),
|
|
91
91
|
maxContextTokens: z.number().default(5e5)
|
|
92
92
|
});
|
|
93
|
+
var mcpServerSchema = z.object({
|
|
94
|
+
/** Unique name for this MCP server */
|
|
95
|
+
name: z.string().min(1),
|
|
96
|
+
/** Command to start the MCP server process */
|
|
97
|
+
command: z.string().min(1),
|
|
98
|
+
/** Arguments passed to the command */
|
|
99
|
+
args: z.array(z.string()).default([]),
|
|
100
|
+
/** Environment variables for the server process */
|
|
101
|
+
env: z.record(z.string()).optional(),
|
|
102
|
+
/** Transport type (currently only stdio supported) */
|
|
103
|
+
transport: z.enum(["stdio"]).default("stdio")
|
|
104
|
+
});
|
|
93
105
|
var appConfigSchema = z.object({
|
|
94
106
|
/** Default provider name to use */
|
|
95
107
|
defaultProvider: z.string().optional(),
|
|
@@ -136,7 +148,9 @@ var appConfigSchema = z.object({
|
|
|
136
148
|
maxHistoryAge: z.number().default(60),
|
|
137
149
|
maxMemorySize: z.number().default(50),
|
|
138
150
|
idleThreshold: z.number().default(3e5)
|
|
139
|
-
}).default({})
|
|
151
|
+
}).default({}),
|
|
152
|
+
/** MCP servers — external tool providers via Model Context Protocol */
|
|
153
|
+
mcpServers: z.array(mcpServerSchema).default([])
|
|
140
154
|
});
|
|
141
155
|
|
|
142
156
|
// src/config/constants.ts
|
|
@@ -417,7 +431,7 @@ var BaseAdapter = class {
|
|
|
417
431
|
if (attempt >= 5) throw err;
|
|
418
432
|
const delay = Math.min(1e3 * Math.pow(2, attempt - 1), 1e4);
|
|
419
433
|
log.warn(`Network error on ${url}: ${err.message}, retrying in ${delay}ms (attempt ${attempt})`);
|
|
420
|
-
await new Promise((
|
|
434
|
+
await new Promise((resolve10) => setTimeout(resolve10, delay));
|
|
421
435
|
continue;
|
|
422
436
|
}
|
|
423
437
|
if (response.ok) return response;
|
|
@@ -436,7 +450,7 @@ var BaseAdapter = class {
|
|
|
436
450
|
return response;
|
|
437
451
|
}
|
|
438
452
|
log.warn(`Retrying ${url} in ${Math.round(action.delay)}ms (attempt ${attempt})`);
|
|
439
|
-
await new Promise((
|
|
453
|
+
await new Promise((resolve10) => setTimeout(resolve10, action.delay));
|
|
440
454
|
}
|
|
441
455
|
}
|
|
442
456
|
};
|
|
@@ -584,7 +598,16 @@ var GenericCompatAdapter = class extends BaseAdapter {
|
|
|
584
598
|
let buffer = "";
|
|
585
599
|
try {
|
|
586
600
|
while (true) {
|
|
587
|
-
|
|
601
|
+
let done;
|
|
602
|
+
let value;
|
|
603
|
+
try {
|
|
604
|
+
const result = await reader.read();
|
|
605
|
+
done = result.done;
|
|
606
|
+
value = result.value;
|
|
607
|
+
} catch (err) {
|
|
608
|
+
if (isAbortOrNetworkError(err)) break;
|
|
609
|
+
throw err;
|
|
610
|
+
}
|
|
588
611
|
if (done) break;
|
|
589
612
|
buffer += decoder.decode(value, { stream: true });
|
|
590
613
|
const lines = buffer.split("\n");
|
|
@@ -649,6 +672,11 @@ var GenericCompatAdapter = class extends BaseAdapter {
|
|
|
649
672
|
}
|
|
650
673
|
}
|
|
651
674
|
};
|
|
675
|
+
function isAbortOrNetworkError(err) {
|
|
676
|
+
const name = err.name ?? "";
|
|
677
|
+
const msg = err.message ?? "";
|
|
678
|
+
return name === "AbortError" || msg.includes("abort") || msg.includes("Abort") || msg.includes("timeout") || msg.includes("Timeout") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("network");
|
|
679
|
+
}
|
|
652
680
|
|
|
653
681
|
// src/connect/glm-adapter.ts
|
|
654
682
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -808,7 +836,16 @@ var GLMAdapter = class extends BaseAdapter {
|
|
|
808
836
|
let buffer = "";
|
|
809
837
|
try {
|
|
810
838
|
while (true) {
|
|
811
|
-
|
|
839
|
+
let done;
|
|
840
|
+
let value;
|
|
841
|
+
try {
|
|
842
|
+
const result = await reader.read();
|
|
843
|
+
done = result.done;
|
|
844
|
+
value = result.value;
|
|
845
|
+
} catch (err) {
|
|
846
|
+
if (isAbortOrNetworkError2(err)) break;
|
|
847
|
+
throw err;
|
|
848
|
+
}
|
|
812
849
|
if (done) break;
|
|
813
850
|
buffer += decoder.decode(value, { stream: true });
|
|
814
851
|
const lines = buffer.split("\n");
|
|
@@ -880,6 +917,11 @@ var GLMAdapter = class extends BaseAdapter {
|
|
|
880
917
|
}
|
|
881
918
|
}
|
|
882
919
|
};
|
|
920
|
+
function isAbortOrNetworkError2(err) {
|
|
921
|
+
const name = err.name ?? "";
|
|
922
|
+
const msg = err.message ?? "";
|
|
923
|
+
return name === "AbortError" || msg.includes("abort") || msg.includes("Abort") || msg.includes("timeout") || msg.includes("Timeout") || msg.includes("fetch failed") || msg.includes("ECONNRESET") || msg.includes("network");
|
|
924
|
+
}
|
|
883
925
|
|
|
884
926
|
// src/connect/registry.ts
|
|
885
927
|
var AdapterRegistry = class {
|
|
@@ -921,6 +963,30 @@ var AdapterRegistry = class {
|
|
|
921
963
|
}
|
|
922
964
|
};
|
|
923
965
|
|
|
966
|
+
// src/infra/errors.ts
|
|
967
|
+
var AppError = class extends Error {
|
|
968
|
+
constructor(message, code, recoverable = true) {
|
|
969
|
+
super(message);
|
|
970
|
+
this.code = code;
|
|
971
|
+
this.recoverable = recoverable;
|
|
972
|
+
this.name = "AppError";
|
|
973
|
+
}
|
|
974
|
+
code;
|
|
975
|
+
recoverable;
|
|
976
|
+
};
|
|
977
|
+
var FallbackTriggeredError = class extends AppError {
|
|
978
|
+
constructor(message, originalError, fromModel, fallbackModel) {
|
|
979
|
+
super(message, "FALLBACK_TRIGGERED", true);
|
|
980
|
+
this.originalError = originalError;
|
|
981
|
+
this.fromModel = fromModel;
|
|
982
|
+
this.fallbackModel = fallbackModel;
|
|
983
|
+
this.name = "FallbackTriggeredError";
|
|
984
|
+
}
|
|
985
|
+
originalError;
|
|
986
|
+
fromModel;
|
|
987
|
+
fallbackModel;
|
|
988
|
+
};
|
|
989
|
+
|
|
924
990
|
// src/connect/model-router.ts
|
|
925
991
|
var TASK_KEYWORDS = {
|
|
926
992
|
reasoning: ["analyze", "reason", "think", "explain", "compare", "evaluate", "decide", "logic", "math", "prove"],
|
|
@@ -945,6 +1011,20 @@ var DEFAULT_PREFERENCES = {
|
|
|
945
1011
|
analysis: "balanced",
|
|
946
1012
|
default: "balanced"
|
|
947
1013
|
};
|
|
1014
|
+
var FALLBACK_TRIGGER_PATTERNS = ["529", "503", "Overloaded", "overloaded", "capacity", "rate_limit"];
|
|
1015
|
+
var FALLBACK_TRIGGER_CODES = /* @__PURE__ */ new Set([529, 503, 502]);
|
|
1016
|
+
function isFallbackTriggeredError(err) {
|
|
1017
|
+
if (err instanceof FallbackTriggeredError) return true;
|
|
1018
|
+
if (!(err instanceof Error)) return false;
|
|
1019
|
+
const message = err.message;
|
|
1020
|
+
for (const pattern of FALLBACK_TRIGGER_PATTERNS) {
|
|
1021
|
+
if (message.includes(pattern)) return true;
|
|
1022
|
+
}
|
|
1023
|
+
if ("statusCode" in err && typeof err.statusCode === "number") {
|
|
1024
|
+
return FALLBACK_TRIGGER_CODES.has(err.statusCode);
|
|
1025
|
+
}
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
948
1028
|
var ModelRouter = class {
|
|
949
1029
|
registry;
|
|
950
1030
|
modelAccess;
|
|
@@ -954,7 +1034,8 @@ var ModelRouter = class {
|
|
|
954
1034
|
this.modelAccess = modelAccess;
|
|
955
1035
|
this.config = {
|
|
956
1036
|
defaultModel: config?.defaultModel ?? modelAccess.getEffectiveModel(),
|
|
957
|
-
preferences: { ...DEFAULT_PREFERENCES, ...config?.preferences }
|
|
1037
|
+
preferences: { ...DEFAULT_PREFERENCES, ...config?.preferences },
|
|
1038
|
+
fallbackChain: config?.fallbackChain
|
|
958
1039
|
};
|
|
959
1040
|
}
|
|
960
1041
|
/** Resolve a model name or alias to a concrete adapter */
|
|
@@ -1012,6 +1093,116 @@ var ModelRouter = class {
|
|
|
1012
1093
|
getEffectiveModel(requestedModel) {
|
|
1013
1094
|
return this.modelAccess.getEffectiveModel(requestedModel);
|
|
1014
1095
|
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Resolve with fallback: if primary adapter fails (529/503/overloaded),
|
|
1098
|
+
* try the fallback model. Returns both adapters for retry logic.
|
|
1099
|
+
*/
|
|
1100
|
+
resolveWithFallback(primaryModel) {
|
|
1101
|
+
const primary = this.resolve(primaryModel);
|
|
1102
|
+
const fallbackModel = this.config.fallbackModel;
|
|
1103
|
+
const fallback = fallbackModel && fallbackModel !== primaryModel ? this.resolve(fallbackModel) : this.findAlternativeAdapter(primary);
|
|
1104
|
+
return { primary, fallback };
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Build the full fallback chain for a given primary model.
|
|
1108
|
+
* Returns adapters in priority order: [primary, ...fallbackChain].
|
|
1109
|
+
* Skips duplicates and null entries.
|
|
1110
|
+
*/
|
|
1111
|
+
resolveFallbackChain(primaryModel) {
|
|
1112
|
+
const primary = this.resolve(primaryModel);
|
|
1113
|
+
const chain = [];
|
|
1114
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1115
|
+
if (primary) {
|
|
1116
|
+
chain.push(primary);
|
|
1117
|
+
seen.add(primary.config.model);
|
|
1118
|
+
}
|
|
1119
|
+
const fallbackModels = this.config.fallbackChain ?? [];
|
|
1120
|
+
if (this.config.fallbackModel && !fallbackModels.includes(this.config.fallbackModel)) {
|
|
1121
|
+
fallbackModels.unshift(this.config.fallbackModel);
|
|
1122
|
+
}
|
|
1123
|
+
for (const modelOrAlias of fallbackModels) {
|
|
1124
|
+
const adapter = this.resolve(modelOrAlias);
|
|
1125
|
+
if (adapter && !seen.has(adapter.config.model)) {
|
|
1126
|
+
chain.push(adapter);
|
|
1127
|
+
seen.add(adapter.config.model);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
for (const adapter of this.registry.getAll()) {
|
|
1131
|
+
if (!seen.has(adapter.config.model)) {
|
|
1132
|
+
chain.push(adapter);
|
|
1133
|
+
seen.add(adapter.config.model);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return chain;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Stream with automatic fallback on overloaded errors.
|
|
1140
|
+
* Tries each adapter in the fallback chain until one succeeds.
|
|
1141
|
+
* Yields a mix of the primary response or fallback response events.
|
|
1142
|
+
*/
|
|
1143
|
+
async *streamWithFallback(request, primaryModel) {
|
|
1144
|
+
const chain = this.resolveFallbackChain(primaryModel);
|
|
1145
|
+
const errors = [];
|
|
1146
|
+
for (const adapter of chain) {
|
|
1147
|
+
try {
|
|
1148
|
+
yield* adapter.stream(request);
|
|
1149
|
+
return;
|
|
1150
|
+
} catch (err) {
|
|
1151
|
+
const error = err;
|
|
1152
|
+
errors.push(error);
|
|
1153
|
+
if (!isFallbackTriggeredError(error)) {
|
|
1154
|
+
throw error;
|
|
1155
|
+
}
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
const lastError = errors[errors.length - 1];
|
|
1160
|
+
if (chain.length > 1) {
|
|
1161
|
+
throw new FallbackTriggeredError(
|
|
1162
|
+
`All ${chain.length} models in fallback chain failed. Last error: ${lastError?.message ?? "unknown"}`,
|
|
1163
|
+
lastError ?? new Error("unknown"),
|
|
1164
|
+
chain[0]?.config.model ?? "unknown",
|
|
1165
|
+
chain[chain.length - 1]?.config.model ?? "unknown"
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
throw lastError ?? new Error("No adapters available");
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Complete with automatic fallback on overloaded errors.
|
|
1172
|
+
* Tries each adapter in the fallback chain until one succeeds.
|
|
1173
|
+
*/
|
|
1174
|
+
async completeWithFallback(request, primaryModel) {
|
|
1175
|
+
const chain = this.resolveFallbackChain(primaryModel);
|
|
1176
|
+
const errors = [];
|
|
1177
|
+
for (const adapter of chain) {
|
|
1178
|
+
try {
|
|
1179
|
+
const result = await adapter.complete(request);
|
|
1180
|
+
return { result, usedModel: adapter.config.model };
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
const error = err;
|
|
1183
|
+
errors.push(error);
|
|
1184
|
+
if (!isFallbackTriggeredError(error)) {
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const lastError = errors[errors.length - 1];
|
|
1191
|
+
if (chain.length > 1) {
|
|
1192
|
+
throw new FallbackTriggeredError(
|
|
1193
|
+
`All ${chain.length} models in fallback chain failed. Last error: ${lastError?.message ?? "unknown"}`,
|
|
1194
|
+
lastError ?? new Error("unknown"),
|
|
1195
|
+
chain[0]?.config.model ?? "unknown",
|
|
1196
|
+
chain[chain.length - 1]?.config.model ?? "unknown"
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
throw lastError ?? new Error("No adapters available");
|
|
1200
|
+
}
|
|
1201
|
+
/** Find an alternative adapter different from the excluded one */
|
|
1202
|
+
findAlternativeAdapter(exclude) {
|
|
1203
|
+
const adapters = this.registry.getAll();
|
|
1204
|
+
return adapters.find((a) => a !== exclude) ?? null;
|
|
1205
|
+
}
|
|
1015
1206
|
findAdapterForModel(modelName) {
|
|
1016
1207
|
const adapters = this.registry.getAll();
|
|
1017
1208
|
return adapters.find((a) => a.config.model === modelName) ?? adapters.find((a) => a.config.model.includes(modelName)) ?? null;
|
|
@@ -1096,16 +1287,24 @@ var ModelAccessManager = class {
|
|
|
1096
1287
|
// src/tools/registry.ts
|
|
1097
1288
|
var ToolRegistry = class {
|
|
1098
1289
|
tools = /* @__PURE__ */ new Map();
|
|
1099
|
-
|
|
1290
|
+
aliasMap = /* @__PURE__ */ new Map();
|
|
1291
|
+
/** Register a tool (with optional aliases) */
|
|
1100
1292
|
register(tool) {
|
|
1101
1293
|
if (this.tools.has(tool.name)) {
|
|
1102
1294
|
throw new Error(`Tool "${tool.name}" is already registered`);
|
|
1103
1295
|
}
|
|
1104
1296
|
this.tools.set(tool.name, tool);
|
|
1297
|
+
if (tool.aliases) {
|
|
1298
|
+
for (const alias of tool.aliases) {
|
|
1299
|
+
if (!this.aliasMap.has(alias)) {
|
|
1300
|
+
this.aliasMap.set(alias, tool);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1105
1304
|
}
|
|
1106
|
-
/** Get a tool by name */
|
|
1305
|
+
/** Get a tool by name or alias */
|
|
1107
1306
|
get(name) {
|
|
1108
|
-
return this.tools.get(name);
|
|
1307
|
+
return this.tools.get(name) ?? this.aliasMap.get(name);
|
|
1109
1308
|
}
|
|
1110
1309
|
/** Get all registered tools */
|
|
1111
1310
|
getAll() {
|
|
@@ -1115,17 +1314,33 @@ var ToolRegistry = class {
|
|
|
1115
1314
|
getToolDefinitions() {
|
|
1116
1315
|
return this.getAll().map((t) => t.toToolDefinition());
|
|
1117
1316
|
}
|
|
1118
|
-
/** Check if a tool exists */
|
|
1317
|
+
/** Check if a tool exists (by name or alias) */
|
|
1119
1318
|
has(name) {
|
|
1120
|
-
return this.tools.has(name);
|
|
1319
|
+
return this.tools.has(name) || this.aliasMap.has(name);
|
|
1320
|
+
}
|
|
1321
|
+
/** Search tools by keyword in name, description, or searchHint */
|
|
1322
|
+
search(keyword) {
|
|
1323
|
+
const lower = keyword.toLowerCase();
|
|
1324
|
+
return this.getAll().filter(
|
|
1325
|
+
(t) => t.name.toLowerCase().includes(lower) || t.description.toLowerCase().includes(lower) || (t.searchHint?.toLowerCase().includes(lower) ?? false)
|
|
1326
|
+
);
|
|
1121
1327
|
}
|
|
1122
|
-
/** Unregister a tool */
|
|
1328
|
+
/** Unregister a tool (and its aliases) */
|
|
1123
1329
|
unregister(name) {
|
|
1124
|
-
this.tools.
|
|
1330
|
+
const tool = this.tools.get(name);
|
|
1331
|
+
if (tool) {
|
|
1332
|
+
this.tools.delete(name);
|
|
1333
|
+
if (tool.aliases) {
|
|
1334
|
+
for (const alias of tool.aliases) {
|
|
1335
|
+
this.aliasMap.delete(alias);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1125
1339
|
}
|
|
1126
|
-
/** Clear all tools */
|
|
1340
|
+
/** Clear all tools and aliases */
|
|
1127
1341
|
clear() {
|
|
1128
1342
|
this.tools.clear();
|
|
1343
|
+
this.aliasMap.clear();
|
|
1129
1344
|
}
|
|
1130
1345
|
};
|
|
1131
1346
|
|
|
@@ -1308,7 +1523,7 @@ var BashTool = class extends BaseTool {
|
|
|
1308
1523
|
}
|
|
1309
1524
|
const timeoutMs = input.timeout ?? 3e4;
|
|
1310
1525
|
const command = process.platform === "win32" ? `chcp 65001 >nul 2>&1 && ${input.command}` : input.command;
|
|
1311
|
-
return new Promise((
|
|
1526
|
+
return new Promise((resolve10) => {
|
|
1312
1527
|
const child = exec(
|
|
1313
1528
|
command,
|
|
1314
1529
|
{
|
|
@@ -1324,12 +1539,12 @@ var BashTool = class extends BaseTool {
|
|
|
1324
1539
|
if (error) {
|
|
1325
1540
|
result += (result ? "\n" : "") + `Exit code: ${error.code ?? "unknown"}`;
|
|
1326
1541
|
}
|
|
1327
|
-
|
|
1542
|
+
resolve10(result || "(no output)");
|
|
1328
1543
|
}
|
|
1329
1544
|
);
|
|
1330
1545
|
context.abortSignal.addEventListener("abort", () => {
|
|
1331
1546
|
child.kill("SIGTERM");
|
|
1332
|
-
|
|
1547
|
+
resolve10("Error: Command was aborted by user");
|
|
1333
1548
|
}, { once: true });
|
|
1334
1549
|
});
|
|
1335
1550
|
}
|
|
@@ -1567,10 +1782,10 @@ var GrepTool = class extends BaseTool {
|
|
|
1567
1782
|
}
|
|
1568
1783
|
}
|
|
1569
1784
|
async isRgAvailable() {
|
|
1570
|
-
return new Promise((
|
|
1785
|
+
return new Promise((resolve10) => {
|
|
1571
1786
|
const cmd = process.platform === "win32" ? "rg.exe" : "rg";
|
|
1572
1787
|
execFile(cmd, ["--version"], (err) => {
|
|
1573
|
-
|
|
1788
|
+
resolve10(!err);
|
|
1574
1789
|
});
|
|
1575
1790
|
});
|
|
1576
1791
|
}
|
|
@@ -1596,23 +1811,23 @@ var GrepTool = class extends BaseTool {
|
|
|
1596
1811
|
args.push("--max-count", String(input.max_results));
|
|
1597
1812
|
args.push(input.pattern, searchPath);
|
|
1598
1813
|
const cmd = process.platform === "win32" ? "rg.exe" : "rg";
|
|
1599
|
-
return new Promise((
|
|
1814
|
+
return new Promise((resolve10) => {
|
|
1600
1815
|
execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
1601
1816
|
if (error && !stdout) {
|
|
1602
1817
|
if (error.code === "1") {
|
|
1603
|
-
|
|
1818
|
+
resolve10("No matches found.");
|
|
1604
1819
|
}
|
|
1605
|
-
|
|
1820
|
+
resolve10(`Error: ${stderr || error.message}`);
|
|
1606
1821
|
return;
|
|
1607
1822
|
}
|
|
1608
1823
|
const output = stdout.trim();
|
|
1609
1824
|
if (!output) {
|
|
1610
|
-
|
|
1825
|
+
resolve10("No matches found.");
|
|
1611
1826
|
return;
|
|
1612
1827
|
}
|
|
1613
1828
|
const lines = output.split("\n");
|
|
1614
1829
|
const maxLines = lines.slice(0, input.max_results);
|
|
1615
|
-
|
|
1830
|
+
resolve10(maxLines.join("\n"));
|
|
1616
1831
|
});
|
|
1617
1832
|
});
|
|
1618
1833
|
}
|
|
@@ -2584,6 +2799,7 @@ var LspTool = class extends BaseTool {
|
|
|
2584
2799
|
|
|
2585
2800
|
// src/tools/builtins/agent-tool.ts
|
|
2586
2801
|
import { z as z15 } from "zod";
|
|
2802
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2587
2803
|
|
|
2588
2804
|
// src/services/streaming-tool-executor.ts
|
|
2589
2805
|
var StreamingToolExecutor = class {
|
|
@@ -2592,6 +2808,9 @@ var StreamingToolExecutor = class {
|
|
|
2592
2808
|
bashErrorDescription = "";
|
|
2593
2809
|
siblingAbortController = new AbortController();
|
|
2594
2810
|
discarded = false;
|
|
2811
|
+
// P0.2: Progress events buffer
|
|
2812
|
+
pendingProgress = [];
|
|
2813
|
+
progressAvailableResolve = null;
|
|
2595
2814
|
// Backward-compatible fields
|
|
2596
2815
|
toolRegistry;
|
|
2597
2816
|
executorConfig;
|
|
@@ -2623,10 +2842,19 @@ var StreamingToolExecutor = class {
|
|
|
2623
2842
|
this.discarded = true;
|
|
2624
2843
|
this.siblingAbortController.abort("discarded");
|
|
2625
2844
|
}
|
|
2845
|
+
/** P0.2: Emit a progress event for a running tool */
|
|
2846
|
+
onToolProgress(toolName, toolId, message) {
|
|
2847
|
+
this.pendingProgress.push({ toolName, toolId, message });
|
|
2848
|
+
if (this.progressAvailableResolve) {
|
|
2849
|
+
this.progressAvailableResolve();
|
|
2850
|
+
this.progressAvailableResolve = null;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2626
2853
|
/** Add a tool to the execution queue. Starts immediately if conditions allow. */
|
|
2627
2854
|
addTool(toolCall) {
|
|
2628
2855
|
const tool = this.toolRegistry.get(toolCall.name);
|
|
2629
|
-
const
|
|
2856
|
+
const safeFlag = tool ? tool.concurrencySafe : false;
|
|
2857
|
+
const isConcurrencySafe = typeof safeFlag === "function" ? safeFlag(toolCall.input) : safeFlag;
|
|
2630
2858
|
this.tools.push({
|
|
2631
2859
|
id: toolCall.id,
|
|
2632
2860
|
name: toolCall.name,
|
|
@@ -2648,20 +2876,40 @@ var StreamingToolExecutor = class {
|
|
|
2648
2876
|
}
|
|
2649
2877
|
}
|
|
2650
2878
|
}
|
|
2651
|
-
/**
|
|
2879
|
+
/** Drain buffered progress events */
|
|
2880
|
+
*drainProgress() {
|
|
2881
|
+
while (this.pendingProgress.length > 0) {
|
|
2882
|
+
yield this.pendingProgress.shift();
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
/** Wait for all remaining tools and yield results + progress events as they arrive */
|
|
2652
2886
|
async *getRemainingResults() {
|
|
2653
2887
|
while (this.hasUnfinishedTools()) {
|
|
2654
2888
|
await this.processQueue();
|
|
2889
|
+
for (const progress of this.drainProgress()) {
|
|
2890
|
+
yield { type: "progress", progress };
|
|
2891
|
+
}
|
|
2655
2892
|
for (const result of this.getCompletedResults()) {
|
|
2656
2893
|
yield result;
|
|
2657
2894
|
}
|
|
2658
2895
|
if (this.hasExecutingTools() && !this.hasCompletedResults()) {
|
|
2659
2896
|
const executingPromises = this.tools.filter((t) => t.status === "executing" && t.promise).map((t) => t.promise);
|
|
2897
|
+
const progressPromise = new Promise((resolve10) => {
|
|
2898
|
+
this.progressAvailableResolve = resolve10;
|
|
2899
|
+
});
|
|
2660
2900
|
if (executingPromises.length > 0) {
|
|
2661
|
-
await Promise.race(executingPromises);
|
|
2901
|
+
await Promise.race([...executingPromises, progressPromise]);
|
|
2902
|
+
} else {
|
|
2903
|
+
await progressPromise;
|
|
2904
|
+
}
|
|
2905
|
+
for (const progress of this.drainProgress()) {
|
|
2906
|
+
yield { type: "progress", progress };
|
|
2662
2907
|
}
|
|
2663
2908
|
}
|
|
2664
2909
|
}
|
|
2910
|
+
for (const progress of this.drainProgress()) {
|
|
2911
|
+
yield { type: "progress", progress };
|
|
2912
|
+
}
|
|
2665
2913
|
for (const result of this.getCompletedResults()) {
|
|
2666
2914
|
yield result;
|
|
2667
2915
|
}
|
|
@@ -2720,6 +2968,8 @@ var StreamingToolExecutor = class {
|
|
|
2720
2968
|
this.bashErrorDescription = "";
|
|
2721
2969
|
this.siblingAbortController = new AbortController();
|
|
2722
2970
|
this.discarded = false;
|
|
2971
|
+
this.pendingProgress = [];
|
|
2972
|
+
this.progressAvailableResolve = null;
|
|
2723
2973
|
}
|
|
2724
2974
|
canExecuteTool(isConcurrencySafe) {
|
|
2725
2975
|
if (this.discarded) return false;
|
|
@@ -2750,12 +3000,29 @@ var StreamingToolExecutor = class {
|
|
|
2750
3000
|
return;
|
|
2751
3001
|
}
|
|
2752
3002
|
const startTime = performance.now();
|
|
3003
|
+
const externalSignal = context?.abortSignal ?? this.toolContext?.abortSignal;
|
|
3004
|
+
const linkedController = new AbortController();
|
|
3005
|
+
const onDiscard = () => linkedController.abort("discarded");
|
|
3006
|
+
this.siblingAbortController.signal.addEventListener("abort", onDiscard);
|
|
3007
|
+
if (externalSignal) {
|
|
3008
|
+
externalSignal.addEventListener("abort", onDiscard);
|
|
3009
|
+
if (externalSignal.aborted) linkedController.abort("external");
|
|
3010
|
+
}
|
|
3011
|
+
const progressCallback = (message) => {
|
|
3012
|
+
this.onToolProgress(tool.name, tool.id, message);
|
|
3013
|
+
};
|
|
2753
3014
|
try {
|
|
2754
3015
|
if (this.executeFn && this.autoMode && this.onPermissionRequest) {
|
|
3016
|
+
const baseCtx = context ?? this.toolContext;
|
|
3017
|
+
const ctxWithProgress = {
|
|
3018
|
+
...baseCtx,
|
|
3019
|
+
abortSignal: linkedController.signal,
|
|
3020
|
+
onProgress: progressCallback
|
|
3021
|
+
};
|
|
2755
3022
|
const result = await this.executeFn(
|
|
2756
3023
|
{ id: tool.id, name: tool.name, input: tool.input },
|
|
2757
3024
|
this.toolRegistry,
|
|
2758
|
-
|
|
3025
|
+
ctxWithProgress,
|
|
2759
3026
|
this.autoMode,
|
|
2760
3027
|
this.onPermissionRequest
|
|
2761
3028
|
);
|
|
@@ -2767,9 +3034,10 @@ var StreamingToolExecutor = class {
|
|
|
2767
3034
|
tool.status = "completed";
|
|
2768
3035
|
return;
|
|
2769
3036
|
}
|
|
2770
|
-
const ctx = context ?? { cwd: process.cwd(), abortSignal:
|
|
3037
|
+
const ctx = context ?? { cwd: process.cwd(), abortSignal: linkedController.signal, checkPermission: async () => true };
|
|
3038
|
+
const execCtx = { ...ctx, abortSignal: linkedController.signal, onProgress: progressCallback };
|
|
2771
3039
|
const validatedInput = registeredTool.inputSchema.parse(tool.input);
|
|
2772
|
-
const rawOutput = await registeredTool.execute(validatedInput,
|
|
3040
|
+
const rawOutput = await registeredTool.execute(validatedInput, execCtx);
|
|
2773
3041
|
const output = rawOutput.length > 5e4 ? rawOutput.slice(0, 5e4) + "\n\n... [truncated]" : rawOutput;
|
|
2774
3042
|
tool.result = {
|
|
2775
3043
|
toolResult: {
|
|
@@ -2814,6 +3082,11 @@ var StreamingToolExecutor = class {
|
|
|
2814
3082
|
isError: true
|
|
2815
3083
|
}
|
|
2816
3084
|
};
|
|
3085
|
+
} finally {
|
|
3086
|
+
this.siblingAbortController.signal.removeEventListener("abort", onDiscard);
|
|
3087
|
+
if (externalSignal) {
|
|
3088
|
+
externalSignal.removeEventListener("abort", onDiscard);
|
|
3089
|
+
}
|
|
2817
3090
|
}
|
|
2818
3091
|
tool.status = "completed";
|
|
2819
3092
|
void this.processQueue(context);
|
|
@@ -2913,8 +3186,20 @@ function countLatinChars(text) {
|
|
|
2913
3186
|
var DEFAULT_CONFIG = {
|
|
2914
3187
|
maxTokens: 128e3,
|
|
2915
3188
|
compactionThreshold: 0.8,
|
|
2916
|
-
keepRecentMessages:
|
|
3189
|
+
keepRecentMessages: 6
|
|
2917
3190
|
};
|
|
3191
|
+
var AUTOCOMPACT_BUFFER_TOKENS = 13e3;
|
|
3192
|
+
var MAX_CONSECUTIVE_FAILURES = 3;
|
|
3193
|
+
function calculateTokenWarningState(tokenUsage, maxTokens) {
|
|
3194
|
+
const usagePercent = tokenUsage / maxTokens;
|
|
3195
|
+
return {
|
|
3196
|
+
usagePercent,
|
|
3197
|
+
isNormal: usagePercent <= 0.8,
|
|
3198
|
+
isWarning: usagePercent > 0.8 && usagePercent <= 0.9,
|
|
3199
|
+
isError: usagePercent > 0.9 && usagePercent <= 0.95,
|
|
3200
|
+
shouldAutoCompact: usagePercent > 0.95
|
|
3201
|
+
};
|
|
3202
|
+
}
|
|
2918
3203
|
var SUMMARIZATION_PROMPT = `Summarize the following conversation history concisely, preserving:
|
|
2919
3204
|
- Key decisions and their rationale
|
|
2920
3205
|
- Important code changes made
|
|
@@ -2927,13 +3212,18 @@ Conversation to summarize:
|
|
|
2927
3212
|
var ContextCompactor = class {
|
|
2928
3213
|
config;
|
|
2929
3214
|
adapter;
|
|
3215
|
+
consecutiveFailures = 0;
|
|
2930
3216
|
constructor(config, adapter) {
|
|
2931
3217
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
2932
3218
|
this.adapter = adapter ?? null;
|
|
2933
3219
|
}
|
|
2934
3220
|
shouldCompact(messages) {
|
|
3221
|
+
if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
2935
3224
|
const totalTokens = this.estimateMessagesTokens(messages);
|
|
2936
|
-
const
|
|
3225
|
+
const effectiveMax = this.config.maxTokens - AUTOCOMPACT_BUFFER_TOKENS;
|
|
3226
|
+
const threshold = this.config.compactionThreshold * effectiveMax;
|
|
2937
3227
|
return totalTokens > threshold;
|
|
2938
3228
|
}
|
|
2939
3229
|
async compact(messages, systemPrompt) {
|
|
@@ -2960,11 +3250,22 @@ var ContextCompactor = class {
|
|
|
2960
3250
|
};
|
|
2961
3251
|
}
|
|
2962
3252
|
let summary;
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
3253
|
+
let succeeded = false;
|
|
3254
|
+
try {
|
|
3255
|
+
if (this.adapter) {
|
|
3256
|
+
summary = await this.generateSummary(oldMessages);
|
|
3257
|
+
succeeded = true;
|
|
3258
|
+
} else {
|
|
3259
|
+
summary = this.truncateMessages(oldMessages);
|
|
3260
|
+
succeeded = true;
|
|
3261
|
+
}
|
|
3262
|
+
} catch {
|
|
3263
|
+
this.consecutiveFailures++;
|
|
2966
3264
|
summary = this.truncateMessages(oldMessages);
|
|
2967
3265
|
}
|
|
3266
|
+
if (succeeded) {
|
|
3267
|
+
this.consecutiveFailures = 0;
|
|
3268
|
+
}
|
|
2968
3269
|
const summaryMessage = {
|
|
2969
3270
|
role: "user",
|
|
2970
3271
|
content: `[Previous conversation summary]
|
|
@@ -3018,30 +3319,96 @@ ${summary}`
|
|
|
3018
3319
|
|
|
3019
3320
|
// src/services/cost-tracker.ts
|
|
3020
3321
|
var DEFAULT_PRICES = {
|
|
3322
|
+
// OpenAI
|
|
3021
3323
|
"gpt-4o": { input: 2.5, output: 10, cacheRead: 1.25, cacheWrite: 2.5 },
|
|
3022
3324
|
"gpt-4o-mini": { input: 0.15, output: 0.6, cacheRead: 0.075, cacheWrite: 0.15 },
|
|
3023
3325
|
"gpt-4-turbo": { input: 10, output: 30, cacheRead: 5, cacheWrite: 10 },
|
|
3024
3326
|
"gpt-3.5-turbo": { input: 0.5, output: 1.5, cacheRead: 0.25, cacheWrite: 0.5 },
|
|
3327
|
+
"o1": { input: 15, output: 60, cacheRead: 7.5, cacheWrite: 15 },
|
|
3328
|
+
"o1-mini": { input: 3, output: 12, cacheRead: 1.5, cacheWrite: 3 },
|
|
3329
|
+
"o3-mini": { input: 1.1, output: 4.4, cacheRead: 0.55, cacheWrite: 1.1 },
|
|
3330
|
+
// Anthropic
|
|
3025
3331
|
"claude-3.5-sonnet": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
3026
3332
|
"claude-3-opus": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
3027
3333
|
"claude-3-haiku": { input: 0.25, output: 1.25, cacheRead: 0.03, cacheWrite: 0.3 },
|
|
3334
|
+
"claude-3.5-haiku": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
3335
|
+
// DeepSeek
|
|
3028
3336
|
"deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.014, cacheWrite: 0.14 },
|
|
3029
|
-
"deepseek-reasoner": { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0.55 }
|
|
3337
|
+
"deepseek-reasoner": { input: 0.55, output: 2.19, cacheRead: 0.14, cacheWrite: 0.55 },
|
|
3338
|
+
// Google
|
|
3339
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4, cacheRead: 0.025, cacheWrite: 0.1 },
|
|
3340
|
+
"gemini-1.5-pro": { input: 1.25, output: 5, cacheRead: 0.3125, cacheWrite: 1.25 },
|
|
3341
|
+
"gemini-1.5-flash": { input: 0.075, output: 0.3, cacheRead: 0.01875, cacheWrite: 0.075 },
|
|
3342
|
+
// GLM
|
|
3343
|
+
"glm-4": { input: 1, output: 3, cacheRead: 0.5, cacheWrite: 1 },
|
|
3344
|
+
"glm-4.5": { input: 1.5, output: 4.5, cacheRead: 0.75, cacheWrite: 1.5 },
|
|
3345
|
+
"glm-4.7": { input: 2, output: 6, cacheRead: 1, cacheWrite: 2 },
|
|
3346
|
+
"glm-5": { input: 2.5, output: 8, cacheRead: 1.25, cacheWrite: 2.5 },
|
|
3347
|
+
"glm-5.1": { input: 3, output: 10, cacheRead: 1.5, cacheWrite: 3 },
|
|
3348
|
+
"glm-5-turbo": { input: 0.5, output: 2, cacheRead: 0.25, cacheWrite: 0.5 },
|
|
3349
|
+
// Qwen
|
|
3350
|
+
"qwen-2.5-72b": { input: 0.4, output: 1.2, cacheRead: 0.2, cacheWrite: 0.4 },
|
|
3351
|
+
// Llama
|
|
3352
|
+
"llama-3.1-405b": { input: 3, output: 9, cacheRead: 1.5, cacheWrite: 3 }
|
|
3030
3353
|
};
|
|
3354
|
+
var MODEL_PATTERNS2 = [
|
|
3355
|
+
{ pattern: /gpt-4o-mini/i, baseModel: "gpt-4o-mini" },
|
|
3356
|
+
{ pattern: /gpt-4o/i, baseModel: "gpt-4o" },
|
|
3357
|
+
{ pattern: /gpt-4-turbo/i, baseModel: "gpt-4-turbo" },
|
|
3358
|
+
{ pattern: /gpt-3\.5/i, baseModel: "gpt-3.5-turbo" },
|
|
3359
|
+
{ pattern: /o1-mini/i, baseModel: "o1-mini" },
|
|
3360
|
+
{ pattern: /o3-mini/i, baseModel: "o3-mini" },
|
|
3361
|
+
{ pattern: /o1/i, baseModel: "o1" },
|
|
3362
|
+
{ pattern: /claude-3\.5-sonnet/i, baseModel: "claude-3.5-sonnet" },
|
|
3363
|
+
{ pattern: /claude-3\.5-haiku/i, baseModel: "claude-3.5-haiku" },
|
|
3364
|
+
{ pattern: /claude-3-opus/i, baseModel: "claude-3-opus" },
|
|
3365
|
+
{ pattern: /claude-3-haiku/i, baseModel: "claude-3-haiku" },
|
|
3366
|
+
{ pattern: /deepseek-reasoner/i, baseModel: "deepseek-reasoner" },
|
|
3367
|
+
{ pattern: /deepseek-chat/i, baseModel: "deepseek-chat" },
|
|
3368
|
+
{ pattern: /deepseek/i, baseModel: "deepseek-chat" },
|
|
3369
|
+
{ pattern: /gemini-2\.0-flash/i, baseModel: "gemini-2.0-flash" },
|
|
3370
|
+
{ pattern: /gemini-1\.5-pro/i, baseModel: "gemini-1.5-pro" },
|
|
3371
|
+
{ pattern: /gemini-1\.5-flash/i, baseModel: "gemini-1.5-flash" },
|
|
3372
|
+
{ pattern: /gemini/i, baseModel: "gemini-2.0-flash" },
|
|
3373
|
+
{ pattern: /glm-5-turbo/i, baseModel: "glm-5-turbo" },
|
|
3374
|
+
{ pattern: /glm-5\.1/i, baseModel: "glm-5.1" },
|
|
3375
|
+
{ pattern: /glm-5/i, baseModel: "glm-5" },
|
|
3376
|
+
{ pattern: /glm-4\.7/i, baseModel: "glm-4.7" },
|
|
3377
|
+
{ pattern: /glm-4\.5/i, baseModel: "glm-4.5" },
|
|
3378
|
+
{ pattern: /glm-4/i, baseModel: "glm-4" },
|
|
3379
|
+
{ pattern: /glm/i, baseModel: "glm-5.1" },
|
|
3380
|
+
{ pattern: /qwen.*72b/i, baseModel: "qwen-2.5-72b" },
|
|
3381
|
+
{ pattern: /qwen/i, baseModel: "qwen-2.5-72b" },
|
|
3382
|
+
{ pattern: /llama.*405b/i, baseModel: "llama-3.1-405b" },
|
|
3383
|
+
{ pattern: /llama/i, baseModel: "llama-3.1-405b" }
|
|
3384
|
+
];
|
|
3031
3385
|
var CostTracker = class {
|
|
3032
3386
|
entries = [];
|
|
3033
3387
|
config;
|
|
3034
3388
|
sessionStart;
|
|
3389
|
+
lastBudgetLevel = "normal";
|
|
3035
3390
|
constructor(config) {
|
|
3036
3391
|
this.config = {
|
|
3037
3392
|
prices: { ...DEFAULT_PRICES, ...config?.prices },
|
|
3038
|
-
budgetLimit: config?.budgetLimit
|
|
3393
|
+
budgetLimit: config?.budgetLimit,
|
|
3394
|
+
warningThreshold: config?.warningThreshold ?? 0.75,
|
|
3395
|
+
criticalThreshold: config?.criticalThreshold ?? 0.9
|
|
3039
3396
|
};
|
|
3040
3397
|
this.sessionStart = Date.now();
|
|
3041
3398
|
}
|
|
3399
|
+
/** Find prices for a model — exact match first, then fuzzy pattern matching */
|
|
3400
|
+
findPrices(model) {
|
|
3401
|
+
if (this.config.prices[model]) return this.config.prices[model];
|
|
3402
|
+
for (const { pattern, baseModel } of MODEL_PATTERNS2) {
|
|
3403
|
+
if (pattern.test(model)) {
|
|
3404
|
+
return this.config.prices[baseModel];
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
return void 0;
|
|
3408
|
+
}
|
|
3042
3409
|
/** Record token usage for a model */
|
|
3043
3410
|
recordUsage(model, usage) {
|
|
3044
|
-
const prices = this.
|
|
3411
|
+
const prices = this.findPrices(model);
|
|
3045
3412
|
let cost = 0;
|
|
3046
3413
|
if (prices) {
|
|
3047
3414
|
const inputCost = usage.inputTokens / 1e6 * prices.input;
|
|
@@ -3128,6 +3495,66 @@ var CostTracker = class {
|
|
|
3128
3495
|
percent
|
|
3129
3496
|
};
|
|
3130
3497
|
}
|
|
3498
|
+
/**
|
|
3499
|
+
* Get detailed budget status with warning levels.
|
|
3500
|
+
* Returns current budget level and whether it changed since last check.
|
|
3501
|
+
*/
|
|
3502
|
+
getBudgetStatus() {
|
|
3503
|
+
const used = this.getSessionTotal();
|
|
3504
|
+
const limit = this.config.budgetLimit;
|
|
3505
|
+
if (!limit) {
|
|
3506
|
+
return { level: "normal", used, percent: 0 };
|
|
3507
|
+
}
|
|
3508
|
+
const percent = used / limit * 100;
|
|
3509
|
+
const ratio = used / limit;
|
|
3510
|
+
let level = "normal";
|
|
3511
|
+
if (ratio >= 1) level = "exceeded";
|
|
3512
|
+
else if (ratio >= (this.config.criticalThreshold ?? 0.9)) level = "critical";
|
|
3513
|
+
else if (ratio >= (this.config.warningThreshold ?? 0.75)) level = "warning";
|
|
3514
|
+
return {
|
|
3515
|
+
level,
|
|
3516
|
+
used,
|
|
3517
|
+
limit,
|
|
3518
|
+
percent,
|
|
3519
|
+
remainingBudget: limit - used
|
|
3520
|
+
};
|
|
3521
|
+
}
|
|
3522
|
+
/**
|
|
3523
|
+
* Check if budget level changed since last call.
|
|
3524
|
+
* Returns the new status if it escalated, null if unchanged.
|
|
3525
|
+
* Use this to emit budget warnings to the user.
|
|
3526
|
+
*/
|
|
3527
|
+
checkBudgetWarning() {
|
|
3528
|
+
const status = this.getBudgetStatus();
|
|
3529
|
+
const levels = ["normal", "warning", "critical", "exceeded"];
|
|
3530
|
+
const currentIdx = levels.indexOf(status.level);
|
|
3531
|
+
const lastIdx = levels.indexOf(this.lastBudgetLevel);
|
|
3532
|
+
if (currentIdx > lastIdx) {
|
|
3533
|
+
this.lastBudgetLevel = status.level;
|
|
3534
|
+
return status;
|
|
3535
|
+
}
|
|
3536
|
+
this.lastBudgetLevel = status.level;
|
|
3537
|
+
return null;
|
|
3538
|
+
}
|
|
3539
|
+
/** Get cache efficiency metrics */
|
|
3540
|
+
getCacheMetrics() {
|
|
3541
|
+
let totalCacheReads = 0;
|
|
3542
|
+
let totalCacheWrites = 0;
|
|
3543
|
+
let savings = 0;
|
|
3544
|
+
for (const entry of this.entries) {
|
|
3545
|
+
totalCacheReads += entry.cacheReadTokens;
|
|
3546
|
+
totalCacheWrites += entry.cacheCreationTokens;
|
|
3547
|
+
const prices = this.findPrices(entry.model);
|
|
3548
|
+
if (prices) {
|
|
3549
|
+
const fullInputCost = entry.inputTokens / 1e6 * prices.input;
|
|
3550
|
+
const actualInputCost = (entry.inputTokens - entry.cacheReadTokens) / 1e6 * prices.input + entry.cacheReadTokens / 1e6 * prices.cacheRead;
|
|
3551
|
+
savings += fullInputCost - actualInputCost;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
const totalInputTokens = this.entries.reduce((sum, e) => sum + e.inputTokens, 0);
|
|
3555
|
+
const cacheHitRate = totalInputTokens > 0 ? totalCacheReads / totalInputTokens : 0;
|
|
3556
|
+
return { totalCacheReads, totalCacheWrites, cacheHitRate, savings };
|
|
3557
|
+
}
|
|
3131
3558
|
/** Get session duration in ms */
|
|
3132
3559
|
getSessionDuration() {
|
|
3133
3560
|
return Date.now() - this.sessionStart;
|
|
@@ -3136,6 +3563,7 @@ var CostTracker = class {
|
|
|
3136
3563
|
reset() {
|
|
3137
3564
|
this.entries = [];
|
|
3138
3565
|
this.sessionStart = Date.now();
|
|
3566
|
+
this.lastBudgetLevel = "normal";
|
|
3139
3567
|
}
|
|
3140
3568
|
};
|
|
3141
3569
|
|
|
@@ -3246,6 +3674,41 @@ function extractToolCalls(content) {
|
|
|
3246
3674
|
);
|
|
3247
3675
|
}
|
|
3248
3676
|
|
|
3677
|
+
// src/infra/abort-controller.ts
|
|
3678
|
+
import { setMaxListeners } from "events";
|
|
3679
|
+
var DEFAULT_MAX_LISTENERS = 50;
|
|
3680
|
+
function createAbortController(maxListeners = DEFAULT_MAX_LISTENERS) {
|
|
3681
|
+
const controller = new AbortController();
|
|
3682
|
+
setMaxListeners(maxListeners, controller.signal);
|
|
3683
|
+
return controller;
|
|
3684
|
+
}
|
|
3685
|
+
function createChildAbortController(parent, maxListeners) {
|
|
3686
|
+
const child = createAbortController(maxListeners);
|
|
3687
|
+
const parentSignal = parent instanceof AbortController ? parent.signal : parent.signal;
|
|
3688
|
+
if (parentSignal.aborted) {
|
|
3689
|
+
child.abort(parentSignal.reason);
|
|
3690
|
+
return child;
|
|
3691
|
+
}
|
|
3692
|
+
const weakChild = new WeakRef(child);
|
|
3693
|
+
const weakParentRef = new WeakRef(parent);
|
|
3694
|
+
const handler = () => {
|
|
3695
|
+
const p = weakParentRef.deref();
|
|
3696
|
+
const signal = p instanceof AbortController ? p.signal : p.signal;
|
|
3697
|
+
weakChild.deref()?.abort(signal.reason);
|
|
3698
|
+
};
|
|
3699
|
+
parentSignal.addEventListener("abort", handler, { once: true });
|
|
3700
|
+
child.signal.addEventListener(
|
|
3701
|
+
"abort",
|
|
3702
|
+
() => {
|
|
3703
|
+
const p = weakParentRef.deref();
|
|
3704
|
+
const signal = p instanceof AbortController ? p.signal : p.signal;
|
|
3705
|
+
signal.removeEventListener("abort", handler);
|
|
3706
|
+
},
|
|
3707
|
+
{ once: true }
|
|
3708
|
+
);
|
|
3709
|
+
return child;
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3249
3712
|
// src/core/query-engine.ts
|
|
3250
3713
|
var DEFAULT_CONFIG2 = {
|
|
3251
3714
|
maxTurns: 50,
|
|
@@ -3259,7 +3722,7 @@ var QueryEngine = class {
|
|
|
3259
3722
|
executor;
|
|
3260
3723
|
compactor;
|
|
3261
3724
|
costTracker;
|
|
3262
|
-
abortController =
|
|
3725
|
+
abortController = createAbortController();
|
|
3263
3726
|
constructor(config, adapter, tools) {
|
|
3264
3727
|
this.config = { ...DEFAULT_CONFIG2, ...config };
|
|
3265
3728
|
this.adapter = adapter;
|
|
@@ -3278,6 +3741,7 @@ var QueryEngine = class {
|
|
|
3278
3741
|
let totalInputTokens = 0;
|
|
3279
3742
|
let totalOutputTokens = 0;
|
|
3280
3743
|
let wasCompacted = false;
|
|
3744
|
+
const runAbortController = this.config.signal ? createChildAbortController({ signal: this.config.signal }) : this.abortController;
|
|
3281
3745
|
messages.push({
|
|
3282
3746
|
role: "user",
|
|
3283
3747
|
content: [{ type: "text", text: prompt }]
|
|
@@ -3285,11 +3749,11 @@ var QueryEngine = class {
|
|
|
3285
3749
|
const toolDefs = this.tools.getToolDefinitions();
|
|
3286
3750
|
const toolContext = {
|
|
3287
3751
|
cwd: process.cwd(),
|
|
3288
|
-
abortSignal:
|
|
3752
|
+
abortSignal: runAbortController.signal,
|
|
3289
3753
|
checkPermission: this.config.checkPermission ?? (async () => true)
|
|
3290
3754
|
};
|
|
3291
3755
|
let turn = 0;
|
|
3292
|
-
while (turn < this.config.maxTurns && !
|
|
3756
|
+
while (turn < this.config.maxTurns && !runAbortController.signal.aborted) {
|
|
3293
3757
|
turn++;
|
|
3294
3758
|
yield { type: "turn_start", turn };
|
|
3295
3759
|
try {
|
|
@@ -3326,11 +3790,11 @@ ${compactionResult.summary}` }]
|
|
|
3326
3790
|
systemPrompt: this.config.systemPrompt,
|
|
3327
3791
|
maxTokens: Math.max(this.adapter.config.maxTokens, 16384),
|
|
3328
3792
|
stream: true,
|
|
3329
|
-
signal:
|
|
3793
|
+
signal: runAbortController.signal
|
|
3330
3794
|
};
|
|
3331
3795
|
const { assistantContent, stopReason, usage, events } = await parseStreamToBlocks(
|
|
3332
3796
|
this.adapter.stream(request),
|
|
3333
|
-
|
|
3797
|
+
runAbortController.signal
|
|
3334
3798
|
);
|
|
3335
3799
|
for (const evt of events) {
|
|
3336
3800
|
if (evt.type === "text_delta") {
|
|
@@ -3357,11 +3821,12 @@ ${compactionResult.summary}` }]
|
|
|
3357
3821
|
if (toolCalls.length > 0) {
|
|
3358
3822
|
const executorRequests = toolCalls.map((tc) => {
|
|
3359
3823
|
const tool = this.tools.get(tc.name);
|
|
3824
|
+
const safeFlag = tool?.concurrencySafe ?? false;
|
|
3360
3825
|
return {
|
|
3361
3826
|
id: tc.id,
|
|
3362
3827
|
name: tc.name,
|
|
3363
3828
|
input: tc.input,
|
|
3364
|
-
concurrencySafe:
|
|
3829
|
+
concurrencySafe: typeof safeFlag === "function" ? safeFlag(tc.input) : safeFlag
|
|
3365
3830
|
};
|
|
3366
3831
|
});
|
|
3367
3832
|
for (const tc of toolCalls) {
|
|
@@ -3418,6 +3883,13 @@ function formatContentBlocks(blocks) {
|
|
|
3418
3883
|
}).join("\n");
|
|
3419
3884
|
}
|
|
3420
3885
|
|
|
3886
|
+
// src/infra/agent-context.ts
|
|
3887
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3888
|
+
var agentContextStorage = new AsyncLocalStorage();
|
|
3889
|
+
function runWithAgentContext(ctx, fn) {
|
|
3890
|
+
return agentContextStorage.run(ctx, fn);
|
|
3891
|
+
}
|
|
3892
|
+
|
|
3421
3893
|
// src/tools/builtins/agent-tool.ts
|
|
3422
3894
|
var agentInputSchema = z15.object({
|
|
3423
3895
|
task: z15.string().describe("Description of the task for the sub-agent"),
|
|
@@ -3492,10 +3964,12 @@ var AgentTool = class extends BaseTool {
|
|
|
3492
3964
|
let finalText = "";
|
|
3493
3965
|
let turnsUsed = 0;
|
|
3494
3966
|
try {
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3967
|
+
const eventStream = runWithAgentContext(
|
|
3968
|
+
{ agentId: randomUUID3(), agentType: "subagent", agentName: "agent" },
|
|
3969
|
+
() => engine.run(input.task)
|
|
3970
|
+
);
|
|
3971
|
+
for await (const event of eventStream) {
|
|
3972
|
+
if (subAgentController.signal.aborted) break;
|
|
3499
3973
|
switch (event.type) {
|
|
3500
3974
|
case "text_delta":
|
|
3501
3975
|
finalText += event.text;
|
|
@@ -3559,12 +4033,12 @@ import { execFile as execFile2 } from "child_process";
|
|
|
3559
4033
|
import { readFile as readFile5, unlink } from "fs/promises";
|
|
3560
4034
|
import { tmpdir } from "os";
|
|
3561
4035
|
import { join as join5 } from "path";
|
|
3562
|
-
import { randomUUID as
|
|
4036
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
3563
4037
|
function getPlatform() {
|
|
3564
4038
|
return process.platform;
|
|
3565
4039
|
}
|
|
3566
4040
|
function execCommand(cmd, args) {
|
|
3567
|
-
return new Promise((
|
|
4041
|
+
return new Promise((resolve10, reject) => {
|
|
3568
4042
|
execFile2(cmd, args, { timeout: 15e3, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
3569
4043
|
if (err) {
|
|
3570
4044
|
reject(new Error(`Command "${cmd} ${args.join(" ")}" failed: ${err.message}`));
|
|
@@ -3574,12 +4048,12 @@ function execCommand(cmd, args) {
|
|
|
3574
4048
|
reject(new Error(`Command stderr: ${stderr}`));
|
|
3575
4049
|
return;
|
|
3576
4050
|
}
|
|
3577
|
-
|
|
4051
|
+
resolve10(stdout.trim());
|
|
3578
4052
|
});
|
|
3579
4053
|
});
|
|
3580
4054
|
}
|
|
3581
4055
|
function execPowerShell(script) {
|
|
3582
|
-
return new Promise((
|
|
4056
|
+
return new Promise((resolve10, reject) => {
|
|
3583
4057
|
execFile2(
|
|
3584
4058
|
"powershell.exe",
|
|
3585
4059
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
@@ -3593,13 +4067,13 @@ function execPowerShell(script) {
|
|
|
3593
4067
|
reject(new Error(`PowerShell stderr: ${stderr}`));
|
|
3594
4068
|
return;
|
|
3595
4069
|
}
|
|
3596
|
-
|
|
4070
|
+
resolve10(stdout.trim());
|
|
3597
4071
|
}
|
|
3598
4072
|
);
|
|
3599
4073
|
});
|
|
3600
4074
|
}
|
|
3601
4075
|
async function tmpFile(ext) {
|
|
3602
|
-
return join5(tmpdir(), `cliskill-capture-${
|
|
4076
|
+
return join5(tmpdir(), `cliskill-capture-${randomUUID4()}.${ext}`);
|
|
3603
4077
|
}
|
|
3604
4078
|
async function readFileAsBase64(path) {
|
|
3605
4079
|
const buf = await readFile5(path);
|
|
@@ -3743,7 +4217,7 @@ function getPlatform2() {
|
|
|
3743
4217
|
return process.platform;
|
|
3744
4218
|
}
|
|
3745
4219
|
function execPowerShell2(script) {
|
|
3746
|
-
return new Promise((
|
|
4220
|
+
return new Promise((resolve10, reject) => {
|
|
3747
4221
|
execFile3(
|
|
3748
4222
|
"powershell.exe",
|
|
3749
4223
|
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
@@ -3757,13 +4231,13 @@ function execPowerShell2(script) {
|
|
|
3757
4231
|
reject(new Error(`PowerShell stderr: ${stderr}`));
|
|
3758
4232
|
return;
|
|
3759
4233
|
}
|
|
3760
|
-
|
|
4234
|
+
resolve10(stdout.trim());
|
|
3761
4235
|
}
|
|
3762
4236
|
);
|
|
3763
4237
|
});
|
|
3764
4238
|
}
|
|
3765
4239
|
function execCommand2(cmd, args) {
|
|
3766
|
-
return new Promise((
|
|
4240
|
+
return new Promise((resolve10, reject) => {
|
|
3767
4241
|
execFile3(cmd, args, { timeout: 1e4 }, (err, stdout, stderr) => {
|
|
3768
4242
|
if (err) {
|
|
3769
4243
|
reject(new Error(`Command "${cmd} ${args.join(" ")}" failed: ${err.message}`));
|
|
@@ -3773,7 +4247,7 @@ function execCommand2(cmd, args) {
|
|
|
3773
4247
|
reject(new Error(`Command stderr: ${stderr}`));
|
|
3774
4248
|
return;
|
|
3775
4249
|
}
|
|
3776
|
-
|
|
4250
|
+
resolve10(stdout.trim());
|
|
3777
4251
|
});
|
|
3778
4252
|
});
|
|
3779
4253
|
}
|
|
@@ -4327,7 +4801,7 @@ var ComputerUseTool = class extends BaseTool {
|
|
|
4327
4801
|
import { z as z17 } from "zod";
|
|
4328
4802
|
|
|
4329
4803
|
// src/remote/session-manager.ts
|
|
4330
|
-
import { randomUUID as
|
|
4804
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
4331
4805
|
import { readFile as readFile6 } from "fs/promises";
|
|
4332
4806
|
var DEFAULT_PORT = 22;
|
|
4333
4807
|
var CONNECTION_TIMEOUT = 3e4;
|
|
@@ -4343,7 +4817,7 @@ var RemoteSessionManager = class {
|
|
|
4343
4817
|
}
|
|
4344
4818
|
async connect(config) {
|
|
4345
4819
|
const ssh2 = await this.loadSsh2();
|
|
4346
|
-
const id =
|
|
4820
|
+
const id = randomUUID5();
|
|
4347
4821
|
const session = {
|
|
4348
4822
|
id,
|
|
4349
4823
|
config: { ...config, port: config.port || DEFAULT_PORT },
|
|
@@ -4366,7 +4840,7 @@ var RemoteSessionManager = class {
|
|
|
4366
4840
|
} else if (config.password) {
|
|
4367
4841
|
connectConfig.password = config.password;
|
|
4368
4842
|
}
|
|
4369
|
-
await new Promise((
|
|
4843
|
+
await new Promise((resolve10, reject) => {
|
|
4370
4844
|
const timeout = setTimeout(() => {
|
|
4371
4845
|
reject(new Error(`Connection timeout to ${config.host}:${config.port}`));
|
|
4372
4846
|
}, CONNECTION_TIMEOUT);
|
|
@@ -4375,7 +4849,7 @@ var RemoteSessionManager = class {
|
|
|
4375
4849
|
session.status = "connected";
|
|
4376
4850
|
session.connectedAt = /* @__PURE__ */ new Date();
|
|
4377
4851
|
log.info(`Remote session ${id} connected to ${config.host}`);
|
|
4378
|
-
|
|
4852
|
+
resolve10();
|
|
4379
4853
|
});
|
|
4380
4854
|
client.on("error", (err) => {
|
|
4381
4855
|
clearTimeout(timeout);
|
|
@@ -4406,7 +4880,7 @@ var RemoteSessionManager = class {
|
|
|
4406
4880
|
throw new Error(`Session ${sessionId} is not connected (status: ${pooled.session.status})`);
|
|
4407
4881
|
}
|
|
4408
4882
|
}
|
|
4409
|
-
return new Promise((
|
|
4883
|
+
return new Promise((resolve10, reject) => {
|
|
4410
4884
|
pooled.client.exec(command, (err, stream) => {
|
|
4411
4885
|
if (err) {
|
|
4412
4886
|
reject(err);
|
|
@@ -4419,7 +4893,7 @@ var RemoteSessionManager = class {
|
|
|
4419
4893
|
stream.stderr.on("data", (data) => stderrChunks.push(data));
|
|
4420
4894
|
stream.on("close", (code) => {
|
|
4421
4895
|
exitCode = code ?? 0;
|
|
4422
|
-
|
|
4896
|
+
resolve10({
|
|
4423
4897
|
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
4424
4898
|
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
4425
4899
|
exitCode
|
|
@@ -4431,20 +4905,20 @@ var RemoteSessionManager = class {
|
|
|
4431
4905
|
async downloadFile(sessionId, remotePath, localPath) {
|
|
4432
4906
|
const pooled = this.getSessionOrThrow(sessionId);
|
|
4433
4907
|
const sftp = await this.getSftp(pooled);
|
|
4434
|
-
return new Promise((
|
|
4908
|
+
return new Promise((resolve10, reject) => {
|
|
4435
4909
|
sftp.fastGet(remotePath, localPath, (err) => {
|
|
4436
4910
|
if (err) reject(err);
|
|
4437
|
-
else
|
|
4911
|
+
else resolve10();
|
|
4438
4912
|
});
|
|
4439
4913
|
});
|
|
4440
4914
|
}
|
|
4441
4915
|
async uploadFile(sessionId, localPath, remotePath) {
|
|
4442
4916
|
const pooled = this.getSessionOrThrow(sessionId);
|
|
4443
4917
|
const sftp = await this.getSftp(pooled);
|
|
4444
|
-
return new Promise((
|
|
4918
|
+
return new Promise((resolve10, reject) => {
|
|
4445
4919
|
sftp.fastPut(localPath, remotePath, (err) => {
|
|
4446
4920
|
if (err) reject(err);
|
|
4447
|
-
else
|
|
4921
|
+
else resolve10();
|
|
4448
4922
|
});
|
|
4449
4923
|
});
|
|
4450
4924
|
}
|
|
@@ -4479,12 +4953,12 @@ var RemoteSessionManager = class {
|
|
|
4479
4953
|
}
|
|
4480
4954
|
async getSftp(pooled) {
|
|
4481
4955
|
if (pooled.sftp) return pooled.sftp;
|
|
4482
|
-
return new Promise((
|
|
4956
|
+
return new Promise((resolve10, reject) => {
|
|
4483
4957
|
pooled.client.sftp((err, sftp) => {
|
|
4484
4958
|
if (err) reject(err);
|
|
4485
4959
|
else {
|
|
4486
4960
|
pooled.sftp = sftp;
|
|
4487
|
-
|
|
4961
|
+
resolve10(sftp);
|
|
4488
4962
|
}
|
|
4489
4963
|
});
|
|
4490
4964
|
});
|
|
@@ -4501,7 +4975,7 @@ var RemoteSessionManager = class {
|
|
|
4501
4975
|
return true;
|
|
4502
4976
|
} catch {
|
|
4503
4977
|
if (attempt < MAX_RECONNECT_ATTEMPTS) {
|
|
4504
|
-
await new Promise((
|
|
4978
|
+
await new Promise((resolve10) => setTimeout(resolve10, RECONNECT_DELAY_MS * attempt));
|
|
4505
4979
|
}
|
|
4506
4980
|
}
|
|
4507
4981
|
}
|
|
@@ -5140,6 +5614,196 @@ var MemoryTool = class extends BaseTool {
|
|
|
5140
5614
|
}
|
|
5141
5615
|
};
|
|
5142
5616
|
|
|
5617
|
+
// src/tools/builtins/tool-search-tool.ts
|
|
5618
|
+
import { z as z20 } from "zod";
|
|
5619
|
+
var inputSchema = z20.object({
|
|
5620
|
+
query: z20.string().describe("Search query \u2014 matches against tool name, aliases, description, and searchHint"),
|
|
5621
|
+
limit: z20.number().min(1).max(20).optional().describe("Maximum results to return (default: 10)")
|
|
5622
|
+
});
|
|
5623
|
+
var ToolSearchTool = class extends BaseTool {
|
|
5624
|
+
name = "tool_search";
|
|
5625
|
+
aliases = ["search-tools", "find-tool"];
|
|
5626
|
+
description = "Search available tools by keyword. Returns matching tools with name, description, and usage hints.";
|
|
5627
|
+
searchHint = "discover find lookup explore tools capabilities";
|
|
5628
|
+
inputSchema = inputSchema;
|
|
5629
|
+
riskLevel = "readonly";
|
|
5630
|
+
concurrencySafe = true;
|
|
5631
|
+
readOnly = true;
|
|
5632
|
+
getRegistry;
|
|
5633
|
+
constructor(registry) {
|
|
5634
|
+
super();
|
|
5635
|
+
this.getRegistry = registry;
|
|
5636
|
+
}
|
|
5637
|
+
async execute(input, _context) {
|
|
5638
|
+
const registry = this.getRegistry();
|
|
5639
|
+
const results = registry.search(input.query);
|
|
5640
|
+
const limit = input.limit ?? 10;
|
|
5641
|
+
const limited = results.slice(0, limit);
|
|
5642
|
+
if (limited.length === 0) {
|
|
5643
|
+
return `No tools found matching "${input.query}". Use 'list' to see all available tools.`;
|
|
5644
|
+
}
|
|
5645
|
+
const lines = limited.map((tool) => {
|
|
5646
|
+
const aliasStr = tool.aliases?.length ? ` (aliases: ${tool.aliases.join(", ")})` : "";
|
|
5647
|
+
const hintStr = tool.searchHint ? ` [${tool.searchHint}]` : "";
|
|
5648
|
+
return `\u2022 ${tool.name}${aliasStr}: ${tool.description}${hintStr}`;
|
|
5649
|
+
});
|
|
5650
|
+
return `Found ${results.length} tool(s) matching "${input.query}":
|
|
5651
|
+
${lines.join("\n")}`;
|
|
5652
|
+
}
|
|
5653
|
+
};
|
|
5654
|
+
|
|
5655
|
+
// src/tools/builtins/background-bash-tool.ts
|
|
5656
|
+
import { z as z21 } from "zod";
|
|
5657
|
+
var bashSchema = z21.object({
|
|
5658
|
+
command: z21.string().describe("Shell command to run in background"),
|
|
5659
|
+
cwd: z21.string().optional().describe("Working directory")
|
|
5660
|
+
});
|
|
5661
|
+
var BackgroundBashTool = class extends BaseTool {
|
|
5662
|
+
name = "background_bash";
|
|
5663
|
+
aliases = ["bg-bash", "async-bash"];
|
|
5664
|
+
description = "Run a shell command in the background. Returns a task ID for checking output later.";
|
|
5665
|
+
searchHint = "background async shell command run execute";
|
|
5666
|
+
inputSchema = bashSchema;
|
|
5667
|
+
riskLevel = "destructive";
|
|
5668
|
+
concurrencySafe = true;
|
|
5669
|
+
readOnly = false;
|
|
5670
|
+
getManager;
|
|
5671
|
+
constructor(manager) {
|
|
5672
|
+
super();
|
|
5673
|
+
this.getManager = manager;
|
|
5674
|
+
}
|
|
5675
|
+
async execute(input, _context) {
|
|
5676
|
+
const manager = this.getManager();
|
|
5677
|
+
const task = manager.startTask(input.command, [], input.cwd);
|
|
5678
|
+
return [
|
|
5679
|
+
`Background task started:`,
|
|
5680
|
+
` Task ID: ${task.id}`,
|
|
5681
|
+
` Command: ${input.command}`,
|
|
5682
|
+
` Status: ${task.status}`,
|
|
5683
|
+
"",
|
|
5684
|
+
`Use task_output with task_id "${task.id}" to check progress.`,
|
|
5685
|
+
`Use task_stop with task_id "${task.id}" to cancel.`
|
|
5686
|
+
].join("\n");
|
|
5687
|
+
}
|
|
5688
|
+
};
|
|
5689
|
+
|
|
5690
|
+
// src/tools/builtins/task-list-tool.ts
|
|
5691
|
+
import { z as z22 } from "zod";
|
|
5692
|
+
var listSchema = z22.object({
|
|
5693
|
+
status: z22.enum(["pending", "running", "completed", "failed", "cancelled"]).optional().describe("Filter by task status")
|
|
5694
|
+
});
|
|
5695
|
+
var TaskListTool = class extends BaseTool {
|
|
5696
|
+
name = "task_list";
|
|
5697
|
+
aliases = ["list-tasks", "tasks"];
|
|
5698
|
+
description = "List background tasks and their statuses.";
|
|
5699
|
+
searchHint = "list background tasks status";
|
|
5700
|
+
inputSchema = listSchema;
|
|
5701
|
+
riskLevel = "readonly";
|
|
5702
|
+
concurrencySafe = true;
|
|
5703
|
+
readOnly = true;
|
|
5704
|
+
getManager;
|
|
5705
|
+
constructor(manager) {
|
|
5706
|
+
super();
|
|
5707
|
+
this.getManager = manager;
|
|
5708
|
+
}
|
|
5709
|
+
async execute(input, _context) {
|
|
5710
|
+
const manager = this.getManager();
|
|
5711
|
+
const tasks = manager.listTasks(input.status);
|
|
5712
|
+
if (tasks.length === 0) {
|
|
5713
|
+
return "No tasks found.";
|
|
5714
|
+
}
|
|
5715
|
+
const lines = tasks.map((t) => {
|
|
5716
|
+
const age = t.completedAt ? `${Math.round((t.completedAt - t.createdAt) / 1e3)}s` : `${Math.round((Date.now() - t.createdAt) / 1e3)}s`;
|
|
5717
|
+
return `\u2022 ${t.id.slice(0, 8)}\u2026 [${t.status}] ${t.command} ${t.args.join(" ")} (${age})`;
|
|
5718
|
+
});
|
|
5719
|
+
return `Tasks (${tasks.length}):
|
|
5720
|
+
${lines.join("\n")}`;
|
|
5721
|
+
}
|
|
5722
|
+
};
|
|
5723
|
+
|
|
5724
|
+
// src/tools/builtins/task-output-tool.ts
|
|
5725
|
+
import { z as z23 } from "zod";
|
|
5726
|
+
var outputSchema = z23.object({
|
|
5727
|
+
task_id: z23.string().describe("ID of the background task")
|
|
5728
|
+
});
|
|
5729
|
+
var TaskOutputTool = class extends BaseTool {
|
|
5730
|
+
name = "task_output";
|
|
5731
|
+
aliases = ["get-task-output"];
|
|
5732
|
+
description = "Get the output of a background task. Returns current status and accumulated output.";
|
|
5733
|
+
searchHint = "background task result output status";
|
|
5734
|
+
inputSchema = outputSchema;
|
|
5735
|
+
riskLevel = "readonly";
|
|
5736
|
+
concurrencySafe = true;
|
|
5737
|
+
readOnly = true;
|
|
5738
|
+
getManager;
|
|
5739
|
+
constructor(manager) {
|
|
5740
|
+
super();
|
|
5741
|
+
this.getManager = manager;
|
|
5742
|
+
}
|
|
5743
|
+
async execute(input, _context) {
|
|
5744
|
+
const manager = this.getManager();
|
|
5745
|
+
const task = manager.getTask(input.task_id);
|
|
5746
|
+
if (!task) {
|
|
5747
|
+
return `Task not found: ${input.task_id}. Use task_list to see available tasks.`;
|
|
5748
|
+
}
|
|
5749
|
+
const lines = [
|
|
5750
|
+
`Task: ${task.id}`,
|
|
5751
|
+
`Command: ${task.command} ${task.args.join(" ")}`,
|
|
5752
|
+
`Status: ${task.status}`,
|
|
5753
|
+
`Exit Code: ${task.exitCode ?? "N/A"}`,
|
|
5754
|
+
`Created: ${new Date(task.createdAt).toISOString()}`
|
|
5755
|
+
];
|
|
5756
|
+
if (task.progress > 0) {
|
|
5757
|
+
lines.push(`Progress: ${task.progress}%`);
|
|
5758
|
+
}
|
|
5759
|
+
if (task.completedAt) {
|
|
5760
|
+
lines.push(`Completed: ${new Date(task.completedAt).toISOString()}`);
|
|
5761
|
+
}
|
|
5762
|
+
if (task.error) {
|
|
5763
|
+
lines.push("", "--- Error ---", task.error);
|
|
5764
|
+
}
|
|
5765
|
+
if (task.result) {
|
|
5766
|
+
lines.push("", "--- Result ---", task.result);
|
|
5767
|
+
}
|
|
5768
|
+
if (task.output) {
|
|
5769
|
+
lines.push("", "--- Output ---", task.output);
|
|
5770
|
+
}
|
|
5771
|
+
if (!task.output && !task.result && !task.error) {
|
|
5772
|
+
lines.push("", "(no output yet)");
|
|
5773
|
+
}
|
|
5774
|
+
return lines.join("\n");
|
|
5775
|
+
}
|
|
5776
|
+
};
|
|
5777
|
+
|
|
5778
|
+
// src/tools/builtins/task-stop-tool.ts
|
|
5779
|
+
import { z as z24 } from "zod";
|
|
5780
|
+
var stopSchema = z24.object({
|
|
5781
|
+
task_id: z24.string().describe("ID of the background task to stop")
|
|
5782
|
+
});
|
|
5783
|
+
var TaskStopTool = class extends BaseTool {
|
|
5784
|
+
name = "task_stop";
|
|
5785
|
+
aliases = ["stop-task", "cancel-task"];
|
|
5786
|
+
description = "Stop a running background task. Sends SIGTERM to the process.";
|
|
5787
|
+
searchHint = "stop cancel kill background task";
|
|
5788
|
+
inputSchema = stopSchema;
|
|
5789
|
+
riskLevel = "destructive";
|
|
5790
|
+
concurrencySafe = false;
|
|
5791
|
+
readOnly = false;
|
|
5792
|
+
getManager;
|
|
5793
|
+
constructor(manager) {
|
|
5794
|
+
super();
|
|
5795
|
+
this.getManager = manager;
|
|
5796
|
+
}
|
|
5797
|
+
async execute(input, _context) {
|
|
5798
|
+
const manager = this.getManager();
|
|
5799
|
+
const stopped = manager.stopTask(input.task_id);
|
|
5800
|
+
if (stopped) {
|
|
5801
|
+
return `Task ${input.task_id} stopped successfully.`;
|
|
5802
|
+
}
|
|
5803
|
+
return `Failed to stop task ${input.task_id}. Task may not exist or not be running.`;
|
|
5804
|
+
}
|
|
5805
|
+
};
|
|
5806
|
+
|
|
5143
5807
|
// src/services/auto-mode.ts
|
|
5144
5808
|
var DEFAULT_READONLY_TOOLS = [
|
|
5145
5809
|
"file_read",
|
|
@@ -5387,7 +6051,7 @@ var ContextWindowManager = class {
|
|
|
5387
6051
|
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
5388
6052
|
import { join as join8 } from "path";
|
|
5389
6053
|
import { tmpdir as tmpdir2 } from "os";
|
|
5390
|
-
import { randomUUID as
|
|
6054
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
5391
6055
|
var MAX_RESULT_SIZE_CHARS = 5e4;
|
|
5392
6056
|
var MAX_RESULTS_PER_MESSAGE_CHARS = 2e5;
|
|
5393
6057
|
var PREVIEW_LENGTH = 500;
|
|
@@ -5395,7 +6059,7 @@ var TOOL_RESULTS_SUBDIR = "cliskill-tool-results";
|
|
|
5395
6059
|
var sessionDir = null;
|
|
5396
6060
|
async function getSessionDir() {
|
|
5397
6061
|
if (!sessionDir) {
|
|
5398
|
-
sessionDir = join8(tmpdir2(), TOOL_RESULTS_SUBDIR,
|
|
6062
|
+
sessionDir = join8(tmpdir2(), TOOL_RESULTS_SUBDIR, randomUUID6());
|
|
5399
6063
|
await mkdir3(sessionDir, { recursive: true });
|
|
5400
6064
|
}
|
|
5401
6065
|
return sessionDir;
|
|
@@ -5415,7 +6079,7 @@ async function persistLargeResult(content, toolName) {
|
|
|
5415
6079
|
return { persisted: false, content };
|
|
5416
6080
|
}
|
|
5417
6081
|
const dir = await getSessionDir();
|
|
5418
|
-
const fileName = `${toolName}-${Date.now()}-${
|
|
6082
|
+
const fileName = `${toolName}-${Date.now()}-${randomUUID6().slice(0, 8)}.txt`;
|
|
5419
6083
|
const filePath = join8(dir, fileName);
|
|
5420
6084
|
await writeFile4(filePath, content, "utf-8");
|
|
5421
6085
|
const preview = content.slice(0, PREVIEW_LENGTH);
|
|
@@ -5463,6 +6127,249 @@ async function applyResultBudget(results) {
|
|
|
5463
6127
|
return output;
|
|
5464
6128
|
}
|
|
5465
6129
|
|
|
6130
|
+
// src/tasks/manager.ts
|
|
6131
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
6132
|
+
import { spawn } from "child_process";
|
|
6133
|
+
import { EventEmitter } from "events";
|
|
6134
|
+
var MAX_COMPLETED_TASKS = 50;
|
|
6135
|
+
var TaskManager = class extends EventEmitter {
|
|
6136
|
+
tasks = /* @__PURE__ */ new Map();
|
|
6137
|
+
completedOrder = [];
|
|
6138
|
+
/** Create a generic task entry (for agent/shell tasks via executor) */
|
|
6139
|
+
createTask(options) {
|
|
6140
|
+
const id = `task_${randomUUID7()}`;
|
|
6141
|
+
const task = {
|
|
6142
|
+
id,
|
|
6143
|
+
name: options.name,
|
|
6144
|
+
description: options.description,
|
|
6145
|
+
type: options.type,
|
|
6146
|
+
status: "pending",
|
|
6147
|
+
progress: 0,
|
|
6148
|
+
result: null,
|
|
6149
|
+
error: null,
|
|
6150
|
+
output: "",
|
|
6151
|
+
exitCode: null,
|
|
6152
|
+
createdAt: Date.now(),
|
|
6153
|
+
completedAt: null,
|
|
6154
|
+
command: options.name,
|
|
6155
|
+
args: []
|
|
6156
|
+
};
|
|
6157
|
+
this.tasks.set(id, task);
|
|
6158
|
+
return id;
|
|
6159
|
+
}
|
|
6160
|
+
/** Start a shell task with a spawned child process */
|
|
6161
|
+
startTask(command, args = [], cwd2) {
|
|
6162
|
+
const id = `task_${randomUUID7()}`;
|
|
6163
|
+
const task = {
|
|
6164
|
+
id,
|
|
6165
|
+
name: command,
|
|
6166
|
+
description: args.join(" "),
|
|
6167
|
+
type: "shell",
|
|
6168
|
+
status: "running",
|
|
6169
|
+
progress: 0,
|
|
6170
|
+
result: null,
|
|
6171
|
+
error: null,
|
|
6172
|
+
output: "",
|
|
6173
|
+
exitCode: null,
|
|
6174
|
+
createdAt: Date.now(),
|
|
6175
|
+
completedAt: null,
|
|
6176
|
+
command,
|
|
6177
|
+
args
|
|
6178
|
+
};
|
|
6179
|
+
const child = spawn(command, args, {
|
|
6180
|
+
cwd: cwd2 ?? process.cwd(),
|
|
6181
|
+
shell: true,
|
|
6182
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6183
|
+
});
|
|
6184
|
+
task.process = child;
|
|
6185
|
+
child.stdout?.on("data", (data) => {
|
|
6186
|
+
task.output += data.toString();
|
|
6187
|
+
});
|
|
6188
|
+
child.stderr?.on("data", (data) => {
|
|
6189
|
+
task.output += data.toString();
|
|
6190
|
+
});
|
|
6191
|
+
child.on("close", (code) => {
|
|
6192
|
+
task.status = code === 0 ? "completed" : "failed";
|
|
6193
|
+
task.exitCode = code;
|
|
6194
|
+
task.completedAt = Date.now();
|
|
6195
|
+
task.process = void 0;
|
|
6196
|
+
this.completedOrder.push(id);
|
|
6197
|
+
this.evictOldTasks();
|
|
6198
|
+
this.emit("onStatusChange", id, task.status);
|
|
6199
|
+
});
|
|
6200
|
+
child.on("error", (err) => {
|
|
6201
|
+
task.status = "failed";
|
|
6202
|
+
task.output += `
|
|
6203
|
+
Error: ${err.message}`;
|
|
6204
|
+
task.exitCode = -1;
|
|
6205
|
+
task.completedAt = Date.now();
|
|
6206
|
+
task.process = void 0;
|
|
6207
|
+
this.emit("onStatusChange", id, "failed");
|
|
6208
|
+
});
|
|
6209
|
+
this.tasks.set(id, task);
|
|
6210
|
+
this.emit("onStatusChange", id, "running");
|
|
6211
|
+
return task;
|
|
6212
|
+
}
|
|
6213
|
+
getTask(id) {
|
|
6214
|
+
return this.tasks.get(id);
|
|
6215
|
+
}
|
|
6216
|
+
stopTask(id) {
|
|
6217
|
+
const task = this.tasks.get(id);
|
|
6218
|
+
if (!task || task.status !== "running" || !task.process) return false;
|
|
6219
|
+
task.process.kill("SIGTERM");
|
|
6220
|
+
task.status = "cancelled";
|
|
6221
|
+
task.completedAt = Date.now();
|
|
6222
|
+
task.process = void 0;
|
|
6223
|
+
this.emit("onStatusChange", id, "cancelled");
|
|
6224
|
+
return true;
|
|
6225
|
+
}
|
|
6226
|
+
/** Update task status (for agent/shell tasks via executor) */
|
|
6227
|
+
updateStatus(id, status) {
|
|
6228
|
+
const task = this.tasks.get(id);
|
|
6229
|
+
if (!task) return;
|
|
6230
|
+
task.status = status;
|
|
6231
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
6232
|
+
task.completedAt = Date.now();
|
|
6233
|
+
}
|
|
6234
|
+
this.emit("onStatusChange", id, status);
|
|
6235
|
+
}
|
|
6236
|
+
/** Update task progress percentage (for agent tasks) */
|
|
6237
|
+
updateProgress(id, progress, message) {
|
|
6238
|
+
const task = this.tasks.get(id);
|
|
6239
|
+
if (!task) return;
|
|
6240
|
+
task.progress = Math.max(0, Math.min(100, progress));
|
|
6241
|
+
this.emit("onProgress", id, task.progress, message);
|
|
6242
|
+
}
|
|
6243
|
+
/** Cancel a task (for agent/shell tasks via executor) */
|
|
6244
|
+
cancelTask(id) {
|
|
6245
|
+
const task = this.tasks.get(id);
|
|
6246
|
+
if (!task) return;
|
|
6247
|
+
if (task.process) {
|
|
6248
|
+
task.process.kill("SIGTERM");
|
|
6249
|
+
task.process = void 0;
|
|
6250
|
+
}
|
|
6251
|
+
task.status = "cancelled";
|
|
6252
|
+
task.completedAt = Date.now();
|
|
6253
|
+
this.emit("onStatusChange", id, "cancelled");
|
|
6254
|
+
}
|
|
6255
|
+
/** Set error result on a task */
|
|
6256
|
+
setError(id, error) {
|
|
6257
|
+
const task = this.tasks.get(id);
|
|
6258
|
+
if (!task) return;
|
|
6259
|
+
task.error = error;
|
|
6260
|
+
task.status = "failed";
|
|
6261
|
+
task.completedAt = Date.now();
|
|
6262
|
+
this.emit("onStatusChange", id, "failed");
|
|
6263
|
+
this.emit("onError", id, error);
|
|
6264
|
+
}
|
|
6265
|
+
/** Set success result on a task */
|
|
6266
|
+
setResult(id, result) {
|
|
6267
|
+
const task = this.tasks.get(id);
|
|
6268
|
+
if (!task) return;
|
|
6269
|
+
task.result = result;
|
|
6270
|
+
task.status = "completed";
|
|
6271
|
+
task.completedAt = Date.now();
|
|
6272
|
+
this.completedOrder.push(id);
|
|
6273
|
+
this.evictOldTasks();
|
|
6274
|
+
this.emit("onStatusChange", id, "completed");
|
|
6275
|
+
this.emit("onComplete", id, result);
|
|
6276
|
+
}
|
|
6277
|
+
/** Remove a completed/failed/cancelled task */
|
|
6278
|
+
removeTask(id) {
|
|
6279
|
+
const task = this.tasks.get(id);
|
|
6280
|
+
if (!task) return;
|
|
6281
|
+
if (task.status === "running") {
|
|
6282
|
+
throw new Error("Cannot remove running task");
|
|
6283
|
+
}
|
|
6284
|
+
this.tasks.delete(id);
|
|
6285
|
+
const idx = this.completedOrder.indexOf(id);
|
|
6286
|
+
if (idx >= 0) this.completedOrder.splice(idx, 1);
|
|
6287
|
+
}
|
|
6288
|
+
/** Clear all completed/failed/cancelled tasks */
|
|
6289
|
+
clearCompleted() {
|
|
6290
|
+
for (const [id, task] of this.tasks) {
|
|
6291
|
+
if (task.status !== "running" && task.status !== "pending") {
|
|
6292
|
+
this.tasks.delete(id);
|
|
6293
|
+
}
|
|
6294
|
+
}
|
|
6295
|
+
this.completedOrder = this.completedOrder.filter((id) => this.tasks.has(id));
|
|
6296
|
+
}
|
|
6297
|
+
/** Count of active (pending + running) tasks */
|
|
6298
|
+
getActiveCount() {
|
|
6299
|
+
let count = 0;
|
|
6300
|
+
for (const task of this.tasks.values()) {
|
|
6301
|
+
if (task.status === "pending" || task.status === "running") count++;
|
|
6302
|
+
}
|
|
6303
|
+
return count;
|
|
6304
|
+
}
|
|
6305
|
+
listTasks(filter) {
|
|
6306
|
+
const all = Array.from(this.tasks.values());
|
|
6307
|
+
const status = typeof filter === "string" ? filter : filter?.status;
|
|
6308
|
+
if (status) return all.filter((t) => t.status === status);
|
|
6309
|
+
return all;
|
|
6310
|
+
}
|
|
6311
|
+
/** Type-safe event listener */
|
|
6312
|
+
on(event, listener) {
|
|
6313
|
+
return super.on(event, listener);
|
|
6314
|
+
}
|
|
6315
|
+
evictOldTasks() {
|
|
6316
|
+
while (this.completedOrder.length > MAX_COMPLETED_TASKS) {
|
|
6317
|
+
const oldestId = this.completedOrder.shift();
|
|
6318
|
+
if (oldestId) this.tasks.delete(oldestId);
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
};
|
|
6322
|
+
|
|
6323
|
+
// src/memory/project-scanner.ts
|
|
6324
|
+
import { readFile as readFile8, stat as stat3 } from "fs/promises";
|
|
6325
|
+
import { join as join9, resolve as resolve8, dirname as dirname2 } from "path";
|
|
6326
|
+
import { existsSync as existsSync4 } from "fs";
|
|
6327
|
+
var MEMORY_FILENAMES = [".cliskill.md", "CLAUDE.md", ".claude.md"];
|
|
6328
|
+
async function scanProjectMemory(cwd2) {
|
|
6329
|
+
const results = [];
|
|
6330
|
+
const visitedDirs = /* @__PURE__ */ new Set();
|
|
6331
|
+
let currentDir = resolve8(cwd2);
|
|
6332
|
+
for (let i = 0; i < 20; i++) {
|
|
6333
|
+
if (visitedDirs.has(currentDir)) break;
|
|
6334
|
+
visitedDirs.add(currentDir);
|
|
6335
|
+
for (const filename of MEMORY_FILENAMES) {
|
|
6336
|
+
const filePath = join9(currentDir, filename);
|
|
6337
|
+
if (existsSync4(filePath)) {
|
|
6338
|
+
try {
|
|
6339
|
+
const content = await readFile8(filePath, "utf-8");
|
|
6340
|
+
const fileStat = await stat3(filePath);
|
|
6341
|
+
results.push({
|
|
6342
|
+
content: content.trim(),
|
|
6343
|
+
filePath,
|
|
6344
|
+
modifiedAt: fileStat.mtimeMs
|
|
6345
|
+
});
|
|
6346
|
+
} catch {
|
|
6347
|
+
}
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6350
|
+
const parent = dirname2(currentDir);
|
|
6351
|
+
if (parent === currentDir) break;
|
|
6352
|
+
currentDir = parent;
|
|
6353
|
+
}
|
|
6354
|
+
return results;
|
|
6355
|
+
}
|
|
6356
|
+
async function loadProjectMemoryPrompt(cwd2) {
|
|
6357
|
+
const memories = await scanProjectMemory(cwd2);
|
|
6358
|
+
if (memories.length === 0) return "";
|
|
6359
|
+
const sections = memories.map((m) => {
|
|
6360
|
+
const relativePath = m.filePath.startsWith(cwd2) ? m.filePath.slice(cwd2.length + 1) : m.filePath;
|
|
6361
|
+
return `### ${relativePath}
|
|
6362
|
+
${m.content}`;
|
|
6363
|
+
});
|
|
6364
|
+
return [
|
|
6365
|
+
"<project-memory>",
|
|
6366
|
+
"The following project-level instructions were found in memory files:",
|
|
6367
|
+
"",
|
|
6368
|
+
...sections,
|
|
6369
|
+
"</project-memory>"
|
|
6370
|
+
].join("\n");
|
|
6371
|
+
}
|
|
6372
|
+
|
|
5466
6373
|
// src/core/loop.ts
|
|
5467
6374
|
var MAX_TURNS = 50;
|
|
5468
6375
|
var MAX_CONSECUTIVE_TOOL_ERRORS = 3;
|
|
@@ -5470,11 +6377,11 @@ var MAX_TOOL_OUTPUT_CHARS = 5e4;
|
|
|
5470
6377
|
var MIN_OUTPUT_TOKENS = 16384;
|
|
5471
6378
|
var MAX_OUTPUT_TOKENS_RECOVERY_LIMIT = 3;
|
|
5472
6379
|
var ESCALATED_MAX_TOKENS = 65536;
|
|
5473
|
-
function sanitizeToolOutput(output) {
|
|
6380
|
+
function sanitizeToolOutput(output, maxChars = MAX_TOOL_OUTPUT_CHARS) {
|
|
5474
6381
|
const sanitized = output.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
5475
|
-
if (sanitized.length <=
|
|
5476
|
-
const truncated = sanitized.slice(0,
|
|
5477
|
-
const omitted = sanitized.length -
|
|
6382
|
+
if (sanitized.length <= maxChars) return sanitized;
|
|
6383
|
+
const truncated = sanitized.slice(0, maxChars);
|
|
6384
|
+
const omitted = sanitized.length - maxChars;
|
|
5478
6385
|
return `${truncated}
|
|
5479
6386
|
|
|
5480
6387
|
... [truncated ${omitted} characters]`;
|
|
@@ -5524,7 +6431,8 @@ ${result.summary}` }]
|
|
|
5524
6431
|
content: [{ type: "text", text: userMessage }]
|
|
5525
6432
|
});
|
|
5526
6433
|
const toolDefs = toolRegistry.getToolDefinitions();
|
|
5527
|
-
const
|
|
6434
|
+
const projectMemory = await loadProjectMemoryPrompt(cwd2);
|
|
6435
|
+
const effectiveSystemPrompt = systemPrompt + fastMode.getSystemPromptModifier() + (projectMemory ? "\n\n" + projectMemory : "");
|
|
5528
6436
|
const toolContext = {
|
|
5529
6437
|
cwd: cwd2,
|
|
5530
6438
|
abortSignal,
|
|
@@ -5540,7 +6448,7 @@ ${result.summary}` }]
|
|
|
5540
6448
|
state.turnCount++;
|
|
5541
6449
|
const delay = fastMode.getInterTurnDelay();
|
|
5542
6450
|
if (delay > 0) {
|
|
5543
|
-
await new Promise((
|
|
6451
|
+
await new Promise((resolve10) => setTimeout(resolve10, delay));
|
|
5544
6452
|
}
|
|
5545
6453
|
if (state.messages.length > 10) {
|
|
5546
6454
|
const compactionInput = state.messages.map((m) => ({
|
|
@@ -5683,19 +6591,39 @@ ${result.summary}` }]
|
|
|
5683
6591
|
for (const toolCall of toolCalls) {
|
|
5684
6592
|
executor.addTool(toolCall);
|
|
5685
6593
|
}
|
|
5686
|
-
for await (const
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
6594
|
+
for await (const execEvent of executor.getRemainingResults()) {
|
|
6595
|
+
if ("toolResult" in execEvent) {
|
|
6596
|
+
toolResults.push(execEvent.toolResult);
|
|
6597
|
+
yield execEvent.event;
|
|
6598
|
+
if (execEvent.toolResult.isError) hadError = true;
|
|
6599
|
+
} else {
|
|
6600
|
+
const p = execEvent.progress;
|
|
6601
|
+
yield { type: "tool_progress", toolName: p.toolName, toolId: p.toolId, message: p.message };
|
|
6602
|
+
}
|
|
5690
6603
|
}
|
|
5691
6604
|
} else {
|
|
6605
|
+
const progressBuffer = [];
|
|
6606
|
+
const singleToolContext = {
|
|
6607
|
+
...toolContext,
|
|
6608
|
+
onProgress: (message) => {
|
|
6609
|
+
progressBuffer.push({
|
|
6610
|
+
type: "tool_progress",
|
|
6611
|
+
toolName: toolCalls[0].name,
|
|
6612
|
+
toolId: toolCalls[0].id,
|
|
6613
|
+
message
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
};
|
|
5692
6617
|
const result = await executeToolCall(
|
|
5693
6618
|
toolCalls[0],
|
|
5694
6619
|
toolRegistry,
|
|
5695
|
-
|
|
6620
|
+
singleToolContext,
|
|
5696
6621
|
autoMode,
|
|
5697
6622
|
onPermissionRequest
|
|
5698
6623
|
);
|
|
6624
|
+
for (const progressEvent of progressBuffer) {
|
|
6625
|
+
yield progressEvent;
|
|
6626
|
+
}
|
|
5699
6627
|
toolResults.push(result.toolResult);
|
|
5700
6628
|
yield result.event;
|
|
5701
6629
|
if (result.toolResult.isError) hadError = true;
|
|
@@ -5734,12 +6662,20 @@ ${result.summary}` }]
|
|
|
5734
6662
|
}
|
|
5735
6663
|
}
|
|
5736
6664
|
} catch (err) {
|
|
5737
|
-
const
|
|
5738
|
-
|
|
6665
|
+
const lastAssistantContent = state.messages.length > 0 && state.messages[state.messages.length - 1].role === "assistant" ? state.messages[state.messages.length - 1].content : [];
|
|
6666
|
+
const pendingToolCalls = extractToolCalls(lastAssistantContent);
|
|
6667
|
+
const pendingResults = createMissingToolResults(
|
|
6668
|
+
pendingToolCalls,
|
|
6669
|
+
`Request failed: ${err.message}`
|
|
6670
|
+
);
|
|
6671
|
+
if (pendingResults.length > 0) {
|
|
6672
|
+
state.messages.push({ role: "user", content: pendingResults });
|
|
6673
|
+
}
|
|
6674
|
+
if (isFallbackTriggeredError(err)) {
|
|
5739
6675
|
yield {
|
|
5740
6676
|
type: "error",
|
|
5741
6677
|
error: new Error(
|
|
5742
|
-
"Model is currently overloaded (529/503).
|
|
6678
|
+
"Model is currently overloaded (529/503). Fallback chain will be attempted on next request. If this persists, configure fallbackChain in config or try a different model."
|
|
5743
6679
|
)
|
|
5744
6680
|
};
|
|
5745
6681
|
} else {
|
|
@@ -5795,15 +6731,13 @@ function summarizeOldToolResults(messages, keepRecent) {
|
|
|
5795
6731
|
}
|
|
5796
6732
|
return compressed;
|
|
5797
6733
|
}
|
|
5798
|
-
var TOKEN_BUDGET_WARNING_THRESHOLD = 0.8;
|
|
5799
|
-
var TOKEN_BUDGET_CRITICAL_THRESHOLD = 0.95;
|
|
5800
6734
|
function checkTokenBudget(inputTokens, outputTokens, maxContextTokens) {
|
|
5801
6735
|
const totalTokens = inputTokens + outputTokens;
|
|
5802
|
-
const
|
|
6736
|
+
const state = calculateTokenWarningState(totalTokens, maxContextTokens);
|
|
5803
6737
|
return {
|
|
5804
|
-
usagePercent,
|
|
5805
|
-
shouldNudge:
|
|
5806
|
-
shouldBlock:
|
|
6738
|
+
usagePercent: state.usagePercent,
|
|
6739
|
+
shouldNudge: state.isWarning || state.isError,
|
|
6740
|
+
shouldBlock: state.shouldAutoCompact
|
|
5807
6741
|
};
|
|
5808
6742
|
}
|
|
5809
6743
|
function runStopHooks(assistantContent, consecutiveErrors, turnCount) {
|
|
@@ -5869,7 +6803,8 @@ async function executeToolCall(toolCall, toolRegistry, toolContext, autoMode, on
|
|
|
5869
6803
|
try {
|
|
5870
6804
|
const validatedInput = tool.inputSchema.parse(toolCall.input);
|
|
5871
6805
|
const rawResult = await tool.execute(validatedInput, toolContext);
|
|
5872
|
-
const
|
|
6806
|
+
const maxChars = tool.maxResultSizeChars ?? MAX_TOOL_OUTPUT_CHARS;
|
|
6807
|
+
const result = sanitizeToolOutput(rawResult, maxChars);
|
|
5873
6808
|
return {
|
|
5874
6809
|
toolResult: {
|
|
5875
6810
|
type: "tool_result",
|
|
@@ -5934,6 +6869,12 @@ function createDefaultToolRegistry(modelRouter) {
|
|
|
5934
6869
|
registry.register(new RemoteTool());
|
|
5935
6870
|
registry.register(new WorktreeTool());
|
|
5936
6871
|
registry.register(new MemoryTool());
|
|
6872
|
+
registry.register(new ToolSearchTool(() => registry));
|
|
6873
|
+
const taskManager = new TaskManager();
|
|
6874
|
+
registry.register(new BackgroundBashTool(() => taskManager));
|
|
6875
|
+
registry.register(new TaskListTool(() => taskManager));
|
|
6876
|
+
registry.register(new TaskOutputTool(() => taskManager));
|
|
6877
|
+
registry.register(new TaskStopTool(() => taskManager));
|
|
5937
6878
|
if (modelRouter) {
|
|
5938
6879
|
const agentTool = new AgentTool(
|
|
5939
6880
|
() => modelRouter,
|
|
@@ -5943,12 +6884,20 @@ function createDefaultToolRegistry(modelRouter) {
|
|
|
5943
6884
|
}
|
|
5944
6885
|
return registry;
|
|
5945
6886
|
}
|
|
6887
|
+
function createMissingToolResults(toolCalls, errorMessage) {
|
|
6888
|
+
return toolCalls.map((tc) => ({
|
|
6889
|
+
type: "tool_result",
|
|
6890
|
+
toolUseId: tc.id,
|
|
6891
|
+
content: `Error: ${errorMessage}`,
|
|
6892
|
+
isError: true
|
|
6893
|
+
}));
|
|
6894
|
+
}
|
|
5946
6895
|
|
|
5947
6896
|
// src/ui/ink-app.tsx
|
|
5948
6897
|
import { useState, useEffect, useCallback, useRef, useMemo, memo } from "react";
|
|
5949
6898
|
import { Box, Text, render, useInput, useApp, useStdout } from "ink";
|
|
5950
6899
|
import { readdirSync } from "fs";
|
|
5951
|
-
import { join as
|
|
6900
|
+
import { join as join10, basename as basename2, dirname as dirname3 } from "path";
|
|
5952
6901
|
import { exec as exec2, execSync as execSync2 } from "child_process";
|
|
5953
6902
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5954
6903
|
var C = {
|
|
@@ -6354,7 +7303,7 @@ var InputBar = memo(function InputBar2({ input, active }) {
|
|
|
6354
7303
|
active && /* @__PURE__ */ jsx(Text, { color: C.primary, children: "\u258E" })
|
|
6355
7304
|
] });
|
|
6356
7305
|
});
|
|
6357
|
-
function InkApp({ model, toolCount, onSubmit }) {
|
|
7306
|
+
function InkApp({ model, toolCount, onSubmit, onCancel }) {
|
|
6358
7307
|
const { exit } = useApp();
|
|
6359
7308
|
const { stdout } = useStdout();
|
|
6360
7309
|
const terminalHeight = stdout?.rows ?? 24;
|
|
@@ -6521,16 +7470,16 @@ function InkApp({ model, toolCount, onSubmit }) {
|
|
|
6521
7470
|
const selected = currentFiles[selectedIdx];
|
|
6522
7471
|
if (selected) {
|
|
6523
7472
|
if (selected.isDir) {
|
|
6524
|
-
setFileCwd(
|
|
7473
|
+
setFileCwd(join10(fileCwd, selected.name));
|
|
6525
7474
|
setSelectedIdx(0);
|
|
6526
7475
|
} else {
|
|
6527
|
-
openInEditor(
|
|
7476
|
+
openInEditor(join10(fileCwd, selected.name));
|
|
6528
7477
|
}
|
|
6529
7478
|
}
|
|
6530
7479
|
return;
|
|
6531
7480
|
}
|
|
6532
7481
|
if (key.backspace || key.delete) {
|
|
6533
|
-
const parent =
|
|
7482
|
+
const parent = dirname3(fileCwd);
|
|
6534
7483
|
if (parent !== fileCwd) {
|
|
6535
7484
|
setFileCwd(parent);
|
|
6536
7485
|
setSelectedIdx(0);
|
|
@@ -6544,6 +7493,10 @@ function InkApp({ model, toolCount, onSubmit }) {
|
|
|
6544
7493
|
return;
|
|
6545
7494
|
}
|
|
6546
7495
|
if (key.escape) {
|
|
7496
|
+
if (loading && onCancel) {
|
|
7497
|
+
onCancel();
|
|
7498
|
+
return;
|
|
7499
|
+
}
|
|
6547
7500
|
exit();
|
|
6548
7501
|
return;
|
|
6549
7502
|
}
|
|
@@ -6665,8 +7618,8 @@ function MessageList({ messages }) {
|
|
|
6665
7618
|
|
|
6666
7619
|
// src/ui/repl.ts
|
|
6667
7620
|
import { mkdir as mkdir4, appendFile } from "fs/promises";
|
|
6668
|
-
import { join as
|
|
6669
|
-
import { randomUUID as
|
|
7621
|
+
import { join as join12 } from "path";
|
|
7622
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
6670
7623
|
|
|
6671
7624
|
// src/prompts/system-prompt.ts
|
|
6672
7625
|
import { hostname } from "os";
|
|
@@ -6811,15 +7764,424 @@ function buildSystemPrompt() {
|
|
|
6811
7764
|
].join("\n\n");
|
|
6812
7765
|
}
|
|
6813
7766
|
|
|
7767
|
+
// src/mcp/client.ts
|
|
7768
|
+
import { spawn as spawn2 } from "child_process";
|
|
7769
|
+
import { createInterface } from "readline";
|
|
7770
|
+
var REQUEST_TIMEOUT = 3e4;
|
|
7771
|
+
var MCPClient = class {
|
|
7772
|
+
config;
|
|
7773
|
+
process = null;
|
|
7774
|
+
rl = null;
|
|
7775
|
+
connected = false;
|
|
7776
|
+
nextId = 1;
|
|
7777
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
7778
|
+
buffer = "";
|
|
7779
|
+
constructor(config) {
|
|
7780
|
+
this.config = config;
|
|
7781
|
+
}
|
|
7782
|
+
async connect() {
|
|
7783
|
+
if (this.connected) return;
|
|
7784
|
+
const env = {
|
|
7785
|
+
...process.env,
|
|
7786
|
+
...this.config.env
|
|
7787
|
+
};
|
|
7788
|
+
this.process = spawn2(this.config.command, this.config.args, {
|
|
7789
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
7790
|
+
env
|
|
7791
|
+
});
|
|
7792
|
+
this.process.on("error", (err) => {
|
|
7793
|
+
this.cleanup();
|
|
7794
|
+
throw err;
|
|
7795
|
+
});
|
|
7796
|
+
this.process.on("exit", () => {
|
|
7797
|
+
this.cleanup();
|
|
7798
|
+
});
|
|
7799
|
+
if (!this.process.stdout) {
|
|
7800
|
+
throw new Error("MCP server stdout is not available");
|
|
7801
|
+
}
|
|
7802
|
+
this.rl = createInterface({ input: this.process.stdout });
|
|
7803
|
+
this.rl.on("line", (line) => {
|
|
7804
|
+
this.handleLine(line);
|
|
7805
|
+
});
|
|
7806
|
+
if (this.process.stderr) {
|
|
7807
|
+
this.process.stderr.on("data", () => {
|
|
7808
|
+
});
|
|
7809
|
+
}
|
|
7810
|
+
await this.sendRequest("initialize", {
|
|
7811
|
+
protocolVersion: "2024-11-05",
|
|
7812
|
+
capabilities: {},
|
|
7813
|
+
clientInfo: { name: "cliskill", version: "1.0.0" }
|
|
7814
|
+
});
|
|
7815
|
+
this.sendNotification("notifications/initialized", {});
|
|
7816
|
+
this.connected = true;
|
|
7817
|
+
}
|
|
7818
|
+
async disconnect() {
|
|
7819
|
+
if (!this.process) return;
|
|
7820
|
+
for (const [, pending] of this.pendingRequests) {
|
|
7821
|
+
clearTimeout(pending.timer);
|
|
7822
|
+
pending.reject(new Error("Connection closed"));
|
|
7823
|
+
}
|
|
7824
|
+
this.pendingRequests.clear();
|
|
7825
|
+
this.process.kill("SIGTERM");
|
|
7826
|
+
this.cleanup();
|
|
7827
|
+
}
|
|
7828
|
+
async listTools() {
|
|
7829
|
+
const response = await this.sendRequest("tools/list", {});
|
|
7830
|
+
const result = response.result;
|
|
7831
|
+
return result?.tools ?? [];
|
|
7832
|
+
}
|
|
7833
|
+
async callTool(name, args) {
|
|
7834
|
+
const response = await this.sendRequest("tools/call", { name, arguments: args });
|
|
7835
|
+
return response.result;
|
|
7836
|
+
}
|
|
7837
|
+
async listResources() {
|
|
7838
|
+
const response = await this.sendRequest("resources/list", {});
|
|
7839
|
+
const result = response.result;
|
|
7840
|
+
return result?.resources ?? [];
|
|
7841
|
+
}
|
|
7842
|
+
async readResource(uri) {
|
|
7843
|
+
const response = await this.sendRequest("resources/read", { uri });
|
|
7844
|
+
return response.result;
|
|
7845
|
+
}
|
|
7846
|
+
async listPrompts() {
|
|
7847
|
+
const response = await this.sendRequest("prompts/list", {});
|
|
7848
|
+
const result = response.result;
|
|
7849
|
+
return result?.prompts ?? [];
|
|
7850
|
+
}
|
|
7851
|
+
isConnected() {
|
|
7852
|
+
return this.connected;
|
|
7853
|
+
}
|
|
7854
|
+
sendRequest(method, params) {
|
|
7855
|
+
return new Promise((resolve10, reject) => {
|
|
7856
|
+
if (!this.process?.stdin) {
|
|
7857
|
+
reject(new Error("MCP server not connected"));
|
|
7858
|
+
return;
|
|
7859
|
+
}
|
|
7860
|
+
const id = this.nextId++;
|
|
7861
|
+
const request = { jsonrpc: "2.0", id, method, params };
|
|
7862
|
+
const timer = setTimeout(() => {
|
|
7863
|
+
this.pendingRequests.delete(id);
|
|
7864
|
+
reject(new Error(`Request timeout: ${method} (id=${id})`));
|
|
7865
|
+
}, REQUEST_TIMEOUT);
|
|
7866
|
+
this.pendingRequests.set(id, { resolve: resolve10, reject, timer });
|
|
7867
|
+
const data = JSON.stringify(request) + "\n";
|
|
7868
|
+
this.process.stdin.write(data, (err) => {
|
|
7869
|
+
if (err) {
|
|
7870
|
+
clearTimeout(timer);
|
|
7871
|
+
this.pendingRequests.delete(id);
|
|
7872
|
+
reject(err);
|
|
7873
|
+
}
|
|
7874
|
+
});
|
|
7875
|
+
});
|
|
7876
|
+
}
|
|
7877
|
+
sendNotification(method, params) {
|
|
7878
|
+
if (!this.process?.stdin) return;
|
|
7879
|
+
const notification = { jsonrpc: "2.0", method, params };
|
|
7880
|
+
const data = JSON.stringify(notification) + "\n";
|
|
7881
|
+
this.process.stdin.write(data);
|
|
7882
|
+
}
|
|
7883
|
+
handleLine(line) {
|
|
7884
|
+
this.buffer += line;
|
|
7885
|
+
let response;
|
|
7886
|
+
try {
|
|
7887
|
+
response = JSON.parse(this.buffer);
|
|
7888
|
+
} catch {
|
|
7889
|
+
return;
|
|
7890
|
+
} finally {
|
|
7891
|
+
this.buffer = "";
|
|
7892
|
+
}
|
|
7893
|
+
if (response.id !== void 0 && response.id !== null) {
|
|
7894
|
+
const pending = this.pendingRequests.get(response.id);
|
|
7895
|
+
if (pending) {
|
|
7896
|
+
clearTimeout(pending.timer);
|
|
7897
|
+
this.pendingRequests.delete(response.id);
|
|
7898
|
+
pending.resolve(response);
|
|
7899
|
+
}
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
cleanup() {
|
|
7903
|
+
this.connected = false;
|
|
7904
|
+
this.rl?.close();
|
|
7905
|
+
this.rl = null;
|
|
7906
|
+
this.process = null;
|
|
7907
|
+
this.buffer = "";
|
|
7908
|
+
}
|
|
7909
|
+
};
|
|
7910
|
+
|
|
7911
|
+
// src/mcp/manager.ts
|
|
7912
|
+
var MCPConnectionManager = class {
|
|
7913
|
+
clients = /* @__PURE__ */ new Map();
|
|
7914
|
+
async addServer(config) {
|
|
7915
|
+
const client = new MCPClient(config);
|
|
7916
|
+
await client.connect();
|
|
7917
|
+
this.clients.set(config.name, client);
|
|
7918
|
+
}
|
|
7919
|
+
async removeServer(name) {
|
|
7920
|
+
const client = this.clients.get(name);
|
|
7921
|
+
if (client) {
|
|
7922
|
+
await client.disconnect();
|
|
7923
|
+
this.clients.delete(name);
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7926
|
+
getConnectedServers() {
|
|
7927
|
+
return Array.from(this.clients.entries()).filter(([, client]) => client.isConnected()).map(([name]) => name);
|
|
7928
|
+
}
|
|
7929
|
+
async getAllTools() {
|
|
7930
|
+
const allTools = [];
|
|
7931
|
+
for (const [, client] of this.clients) {
|
|
7932
|
+
if (!client.isConnected()) continue;
|
|
7933
|
+
try {
|
|
7934
|
+
const tools = await client.listTools();
|
|
7935
|
+
allTools.push(...tools);
|
|
7936
|
+
} catch {
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
return allTools;
|
|
7940
|
+
}
|
|
7941
|
+
async callTool(serverName, toolName, args) {
|
|
7942
|
+
const client = this.clients.get(serverName);
|
|
7943
|
+
if (!client) {
|
|
7944
|
+
throw new Error(`MCP server "${serverName}" not found`);
|
|
7945
|
+
}
|
|
7946
|
+
if (!client.isConnected()) {
|
|
7947
|
+
throw new Error(`MCP server "${serverName}" is not connected`);
|
|
7948
|
+
}
|
|
7949
|
+
return client.callTool(toolName, args);
|
|
7950
|
+
}
|
|
7951
|
+
async getToolsForServer(serverName) {
|
|
7952
|
+
const client = this.clients.get(serverName);
|
|
7953
|
+
if (!client || !client.isConnected()) {
|
|
7954
|
+
return [];
|
|
7955
|
+
}
|
|
7956
|
+
return client.listTools();
|
|
7957
|
+
}
|
|
7958
|
+
async disconnectAll() {
|
|
7959
|
+
for (const [, client] of this.clients) {
|
|
7960
|
+
try {
|
|
7961
|
+
await client.disconnect();
|
|
7962
|
+
} catch {
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7965
|
+
this.clients.clear();
|
|
7966
|
+
}
|
|
7967
|
+
};
|
|
7968
|
+
|
|
7969
|
+
// src/mcp/mcp-tool-adapter.ts
|
|
7970
|
+
import { z as z25 } from "zod";
|
|
7971
|
+
var MCPToolAdapter = class {
|
|
7972
|
+
name;
|
|
7973
|
+
description;
|
|
7974
|
+
inputSchema;
|
|
7975
|
+
riskLevel = "safe";
|
|
7976
|
+
concurrencySafe = true;
|
|
7977
|
+
readOnly = true;
|
|
7978
|
+
serverName;
|
|
7979
|
+
mcpManager;
|
|
7980
|
+
rawInputSchema;
|
|
7981
|
+
constructor(serverName, toolName, description, inputSchema2, mcpManager) {
|
|
7982
|
+
this.name = `mcp_${serverName}_${toolName}`;
|
|
7983
|
+
this.description = description;
|
|
7984
|
+
this.serverName = serverName;
|
|
7985
|
+
this.mcpManager = mcpManager;
|
|
7986
|
+
this.rawInputSchema = inputSchema2;
|
|
7987
|
+
this.inputSchema = this.buildZodSchema(inputSchema2);
|
|
7988
|
+
}
|
|
7989
|
+
async execute(input, context) {
|
|
7990
|
+
const allowed = await context.checkPermission(this.name, JSON.stringify(input));
|
|
7991
|
+
if (!allowed) {
|
|
7992
|
+
return `Error: Permission denied for MCP tool: ${this.name}`;
|
|
7993
|
+
}
|
|
7994
|
+
try {
|
|
7995
|
+
const result = await this.mcpManager.callTool(this.serverName, this.getOriginalName(), input);
|
|
7996
|
+
return typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
7997
|
+
} catch (err) {
|
|
7998
|
+
return `Error calling MCP tool ${this.name}: ${err.message}`;
|
|
7999
|
+
}
|
|
8000
|
+
}
|
|
8001
|
+
toToolDefinition() {
|
|
8002
|
+
return {
|
|
8003
|
+
name: this.name,
|
|
8004
|
+
description: this.description,
|
|
8005
|
+
inputSchema: this.rawInputSchema
|
|
8006
|
+
};
|
|
8007
|
+
}
|
|
8008
|
+
getOriginalName() {
|
|
8009
|
+
const parts = this.name.split("_");
|
|
8010
|
+
return parts.slice(2).join("_");
|
|
8011
|
+
}
|
|
8012
|
+
getServerName() {
|
|
8013
|
+
return this.serverName;
|
|
8014
|
+
}
|
|
8015
|
+
buildZodSchema(jsonSchema) {
|
|
8016
|
+
const properties = jsonSchema.properties ?? {};
|
|
8017
|
+
const required = new Set(jsonSchema.required ?? []);
|
|
8018
|
+
const shape = {};
|
|
8019
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
8020
|
+
const zodType = this.jsonSchemaPropertyToZod(propSchema);
|
|
8021
|
+
shape[key] = required.has(key) ? zodType : zodType.optional();
|
|
8022
|
+
}
|
|
8023
|
+
return z25.object(shape);
|
|
8024
|
+
}
|
|
8025
|
+
jsonSchemaPropertyToZod(prop) {
|
|
8026
|
+
const type = prop.type;
|
|
8027
|
+
switch (type) {
|
|
8028
|
+
case "string":
|
|
8029
|
+
if (prop.enum) return z25.enum(prop.enum);
|
|
8030
|
+
return z25.string();
|
|
8031
|
+
case "number":
|
|
8032
|
+
case "integer":
|
|
8033
|
+
return z25.number();
|
|
8034
|
+
case "boolean":
|
|
8035
|
+
return z25.boolean();
|
|
8036
|
+
case "array":
|
|
8037
|
+
if (prop.items && typeof prop.items === "object") {
|
|
8038
|
+
return z25.array(this.jsonSchemaPropertyToZod(prop.items));
|
|
8039
|
+
}
|
|
8040
|
+
return z25.array(z25.unknown());
|
|
8041
|
+
case "object":
|
|
8042
|
+
if (prop.properties && typeof prop.properties === "object") {
|
|
8043
|
+
return this.buildZodSchema(prop);
|
|
8044
|
+
}
|
|
8045
|
+
return z25.record(z25.unknown());
|
|
8046
|
+
default:
|
|
8047
|
+
return z25.unknown();
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
};
|
|
8051
|
+
async function registerMCPTools(mcpManager, registerFn) {
|
|
8052
|
+
const registered = [];
|
|
8053
|
+
for (const serverName of mcpManager.getConnectedServers()) {
|
|
8054
|
+
try {
|
|
8055
|
+
const tools = await mcpManager.getToolsForServer(serverName);
|
|
8056
|
+
for (const tool of tools) {
|
|
8057
|
+
const adapter = new MCPToolAdapter(
|
|
8058
|
+
serverName,
|
|
8059
|
+
tool.name,
|
|
8060
|
+
tool.description ?? `MCP tool: ${tool.name}`,
|
|
8061
|
+
tool.inputSchema,
|
|
8062
|
+
mcpManager
|
|
8063
|
+
);
|
|
8064
|
+
registerFn(adapter);
|
|
8065
|
+
registered.push(adapter.name);
|
|
8066
|
+
}
|
|
8067
|
+
} catch {
|
|
8068
|
+
}
|
|
8069
|
+
}
|
|
8070
|
+
return registered;
|
|
8071
|
+
}
|
|
8072
|
+
|
|
8073
|
+
// src/services/session-recovery.ts
|
|
8074
|
+
import { readFile as readFile9, readdir as readdir5 } from "fs/promises";
|
|
8075
|
+
import { join as join11 } from "path";
|
|
8076
|
+
import { existsSync as existsSync5 } from "fs";
|
|
8077
|
+
async function recoverSession(filePath) {
|
|
8078
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
8079
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
8080
|
+
const entries = [];
|
|
8081
|
+
for (const line of lines) {
|
|
8082
|
+
try {
|
|
8083
|
+
const entry = JSON.parse(line);
|
|
8084
|
+
if (entry.type && entry.content !== void 0) {
|
|
8085
|
+
entries.push(entry);
|
|
8086
|
+
}
|
|
8087
|
+
} catch {
|
|
8088
|
+
}
|
|
8089
|
+
}
|
|
8090
|
+
const messages = reconstructMessages(entries);
|
|
8091
|
+
const firstEntry = entries[0];
|
|
8092
|
+
const timestamp = firstEntry?.timestamp ?? 0;
|
|
8093
|
+
return {
|
|
8094
|
+
sessionId: filePath,
|
|
8095
|
+
filePath,
|
|
8096
|
+
timestamp,
|
|
8097
|
+
messages,
|
|
8098
|
+
entryCount: entries.length
|
|
8099
|
+
};
|
|
8100
|
+
}
|
|
8101
|
+
async function listSessions(limit = 20) {
|
|
8102
|
+
const dir = getSessionsDir();
|
|
8103
|
+
if (!existsSync5(dir)) return [];
|
|
8104
|
+
const files = await readdir5(dir);
|
|
8105
|
+
const sessionFiles = files.filter((f) => f.startsWith("session-") && f.endsWith(".jsonl"));
|
|
8106
|
+
const sessions = [];
|
|
8107
|
+
for (const fileName of sessionFiles) {
|
|
8108
|
+
const filePath = join11(dir, fileName);
|
|
8109
|
+
try {
|
|
8110
|
+
const raw = await readFile9(filePath, "utf-8");
|
|
8111
|
+
const lines = raw.split("\n").filter((line) => line.trim().length > 0);
|
|
8112
|
+
let lastActivity = 0;
|
|
8113
|
+
let entryCount = 0;
|
|
8114
|
+
for (const line of lines) {
|
|
8115
|
+
try {
|
|
8116
|
+
const entry = JSON.parse(line);
|
|
8117
|
+
if (entry.timestamp && entry.timestamp > lastActivity) {
|
|
8118
|
+
lastActivity = entry.timestamp;
|
|
8119
|
+
}
|
|
8120
|
+
entryCount++;
|
|
8121
|
+
} catch {
|
|
8122
|
+
}
|
|
8123
|
+
}
|
|
8124
|
+
const dateStr = fileName.replace("session-", "").replace(".jsonl", "");
|
|
8125
|
+
const fileTimestamp = Date.parse(dateStr) || lastActivity;
|
|
8126
|
+
sessions.push({
|
|
8127
|
+
fileName,
|
|
8128
|
+
filePath,
|
|
8129
|
+
timestamp: fileTimestamp,
|
|
8130
|
+
entryCount,
|
|
8131
|
+
lastActivity
|
|
8132
|
+
});
|
|
8133
|
+
} catch {
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8136
|
+
sessions.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
8137
|
+
return sessions.slice(0, limit);
|
|
8138
|
+
}
|
|
8139
|
+
async function findLatestSession() {
|
|
8140
|
+
const sessions = await listSessions(1);
|
|
8141
|
+
return sessions[0] ?? null;
|
|
8142
|
+
}
|
|
8143
|
+
function reconstructMessages(entries) {
|
|
8144
|
+
const messages = [];
|
|
8145
|
+
let i = 0;
|
|
8146
|
+
while (i < entries.length) {
|
|
8147
|
+
const entry = entries[i];
|
|
8148
|
+
if (entry.type === "user") {
|
|
8149
|
+
const content = [{ type: "text", text: entry.content }];
|
|
8150
|
+
messages.push({ role: "user", content });
|
|
8151
|
+
i++;
|
|
8152
|
+
} else if (entry.type === "assistant") {
|
|
8153
|
+
const content = [{ type: "text", text: entry.content }];
|
|
8154
|
+
messages.push({ role: "assistant", content });
|
|
8155
|
+
i++;
|
|
8156
|
+
} else if (entry.type === "tool_use") {
|
|
8157
|
+
const lastMsg = messages[messages.length - 1];
|
|
8158
|
+
if (lastMsg && lastMsg.role === "assistant") {
|
|
8159
|
+
}
|
|
8160
|
+
i++;
|
|
8161
|
+
} else if (entry.type === "tool_result") {
|
|
8162
|
+
const content = [{
|
|
8163
|
+
type: "tool_result",
|
|
8164
|
+
toolUseId: `recovered-${i}`,
|
|
8165
|
+
content: entry.content
|
|
8166
|
+
}];
|
|
8167
|
+
messages.push({ role: "user", content });
|
|
8168
|
+
i++;
|
|
8169
|
+
} else {
|
|
8170
|
+
i++;
|
|
8171
|
+
}
|
|
8172
|
+
}
|
|
8173
|
+
return messages;
|
|
8174
|
+
}
|
|
8175
|
+
|
|
6814
8176
|
// src/ui/repl.ts
|
|
6815
8177
|
var SessionSaver = class {
|
|
6816
8178
|
filePath;
|
|
6817
8179
|
sessionId;
|
|
6818
8180
|
constructor() {
|
|
6819
|
-
this.sessionId =
|
|
8181
|
+
this.sessionId = randomUUID8();
|
|
6820
8182
|
const dir = getSessionsDir();
|
|
6821
8183
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6822
|
-
this.filePath =
|
|
8184
|
+
this.filePath = join12(dir, `session-${timestamp}.jsonl`);
|
|
6823
8185
|
}
|
|
6824
8186
|
async init() {
|
|
6825
8187
|
const dir = getSessionsDir();
|
|
@@ -6857,6 +8219,36 @@ function buildModelRouter(adapterRegistry, config) {
|
|
|
6857
8219
|
defaultModel: config.models.defaultModel
|
|
6858
8220
|
});
|
|
6859
8221
|
}
|
|
8222
|
+
async function connectMcpServers(config, toolRegistry) {
|
|
8223
|
+
if (config.mcpServers.length === 0) return null;
|
|
8224
|
+
const mcpManager = new MCPConnectionManager();
|
|
8225
|
+
let connectedCount = 0;
|
|
8226
|
+
for (const serverConfig of config.mcpServers) {
|
|
8227
|
+
try {
|
|
8228
|
+
await mcpManager.addServer({
|
|
8229
|
+
name: serverConfig.name,
|
|
8230
|
+
command: serverConfig.command,
|
|
8231
|
+
args: serverConfig.args,
|
|
8232
|
+
env: serverConfig.env,
|
|
8233
|
+
transport: serverConfig.transport
|
|
8234
|
+
});
|
|
8235
|
+
connectedCount++;
|
|
8236
|
+
} catch (err) {
|
|
8237
|
+
console.error(`MCP server "${serverConfig.name}" connection failed: ${err.message}`);
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
if (connectedCount === 0) return mcpManager;
|
|
8241
|
+
const registeredTools = await registerMCPTools(mcpManager, (tool) => {
|
|
8242
|
+
try {
|
|
8243
|
+
toolRegistry.register(tool);
|
|
8244
|
+
} catch {
|
|
8245
|
+
}
|
|
8246
|
+
});
|
|
8247
|
+
if (registeredTools.length > 0) {
|
|
8248
|
+
console.log(` MCP: ${connectedCount} server(s) connected, ${registeredTools.length} tool(s) loaded`);
|
|
8249
|
+
}
|
|
8250
|
+
return mcpManager;
|
|
8251
|
+
}
|
|
6860
8252
|
async function runTuiRepl(config) {
|
|
6861
8253
|
const adapterRegistry = new AdapterRegistry();
|
|
6862
8254
|
for (const provider of config.providers) {
|
|
@@ -6869,12 +8261,15 @@ async function runTuiRepl(config) {
|
|
|
6869
8261
|
const adapter = config.defaultProvider ? adapterRegistry.get(config.defaultProvider) : adapterRegistry.getAll()[0];
|
|
6870
8262
|
const modelRouter = buildModelRouter(adapterRegistry, config);
|
|
6871
8263
|
const toolRegistry = createDefaultToolRegistry(modelRouter);
|
|
8264
|
+
const mcpManager = await connectMcpServers(config, toolRegistry);
|
|
6872
8265
|
ensureDataDir();
|
|
6873
8266
|
const sessionSaver = new SessionSaver();
|
|
6874
8267
|
await sessionSaver.init();
|
|
6875
8268
|
let sessionMessages = [];
|
|
8269
|
+
let activeAbortController = new AbortController();
|
|
6876
8270
|
const onSubmit = async (input, onEvent) => {
|
|
6877
8271
|
if (!adapter) return;
|
|
8272
|
+
activeAbortController = new AbortController();
|
|
6878
8273
|
await sessionSaver.append({
|
|
6879
8274
|
type: "user",
|
|
6880
8275
|
content: input,
|
|
@@ -6887,7 +8282,7 @@ async function runTuiRepl(config) {
|
|
|
6887
8282
|
config,
|
|
6888
8283
|
systemPrompt: config.systemPrompt ?? buildSystemPrompt(),
|
|
6889
8284
|
cwd: process.cwd(),
|
|
6890
|
-
abortSignal:
|
|
8285
|
+
abortSignal: activeAbortController.signal,
|
|
6891
8286
|
onPermissionRequest: async () => true,
|
|
6892
8287
|
sessionHistory: sessionMessages
|
|
6893
8288
|
})) {
|
|
@@ -6931,7 +8326,8 @@ async function runTuiRepl(config) {
|
|
|
6931
8326
|
renderInkApp({
|
|
6932
8327
|
model: adapter?.config.model ?? "N/A",
|
|
6933
8328
|
toolCount: toolRegistry.getAll().length,
|
|
6934
|
-
onSubmit
|
|
8329
|
+
onSubmit,
|
|
8330
|
+
onCancel: () => activeAbortController.abort()
|
|
6935
8331
|
});
|
|
6936
8332
|
}
|
|
6937
8333
|
async function runBasicRepl(config) {
|
|
@@ -6963,13 +8359,14 @@ async function runBasicRepl(config) {
|
|
|
6963
8359
|
`);
|
|
6964
8360
|
}
|
|
6965
8361
|
const toolRegistry = createDefaultToolRegistry(modelRouter);
|
|
8362
|
+
await connectMcpServers(config, toolRegistry);
|
|
6966
8363
|
const abortController = new AbortController();
|
|
6967
8364
|
ensureDataDir();
|
|
6968
8365
|
const sessionSaver = new SessionSaver();
|
|
6969
8366
|
await sessionSaver.init();
|
|
6970
8367
|
let sessionMessages = [];
|
|
6971
|
-
const { createInterface } = await import("readline");
|
|
6972
|
-
const rl =
|
|
8368
|
+
const { createInterface: createInterface2 } = await import("readline");
|
|
8369
|
+
const rl = createInterface2({
|
|
6973
8370
|
input: process.stdin,
|
|
6974
8371
|
output: process.stdout,
|
|
6975
8372
|
prompt: "> "
|
|
@@ -6991,6 +8388,42 @@ async function runBasicRepl(config) {
|
|
|
6991
8388
|
rl.close();
|
|
6992
8389
|
return;
|
|
6993
8390
|
}
|
|
8391
|
+
if (input === "/resume") {
|
|
8392
|
+
try {
|
|
8393
|
+
const latest = await findLatestSession();
|
|
8394
|
+
if (!latest) {
|
|
8395
|
+
console.log(" No previous sessions found.");
|
|
8396
|
+
rl.prompt();
|
|
8397
|
+
return;
|
|
8398
|
+
}
|
|
8399
|
+
const recovered = await recoverSession(latest.filePath);
|
|
8400
|
+
sessionMessages = recovered.messages;
|
|
8401
|
+
console.log(` Resumed session from ${new Date(recovered.timestamp).toISOString()} (${recovered.entryCount} entries, ${recovered.messages.length} messages)`);
|
|
8402
|
+
} catch (err) {
|
|
8403
|
+
console.error(` Failed to resume session: ${err.message}`);
|
|
8404
|
+
}
|
|
8405
|
+
rl.prompt();
|
|
8406
|
+
return;
|
|
8407
|
+
}
|
|
8408
|
+
if (input === "/sessions") {
|
|
8409
|
+
try {
|
|
8410
|
+
const sessions = await listSessions(10);
|
|
8411
|
+
if (sessions.length === 0) {
|
|
8412
|
+
console.log(" No sessions found.");
|
|
8413
|
+
} else {
|
|
8414
|
+
console.log(" Recent sessions:");
|
|
8415
|
+
for (const s of sessions) {
|
|
8416
|
+
const date = new Date(s.lastActivity).toLocaleString();
|
|
8417
|
+
console.log(` ${s.fileName} \u2014 ${s.entryCount} entries, last activity: ${date}`);
|
|
8418
|
+
}
|
|
8419
|
+
console.log(" Use /resume to restore the most recent session.");
|
|
8420
|
+
}
|
|
8421
|
+
} catch (err) {
|
|
8422
|
+
console.error(` Failed to list sessions: ${err.message}`);
|
|
8423
|
+
}
|
|
8424
|
+
rl.prompt();
|
|
8425
|
+
return;
|
|
8426
|
+
}
|
|
6994
8427
|
if (!adapter) {
|
|
6995
8428
|
console.log("[No provider configured]");
|
|
6996
8429
|
rl.prompt();
|
|
@@ -7011,10 +8444,10 @@ async function runBasicRepl(config) {
|
|
|
7011
8444
|
cwd: process.cwd(),
|
|
7012
8445
|
abortSignal: abortController.signal,
|
|
7013
8446
|
onPermissionRequest: async (operation, details) => {
|
|
7014
|
-
return new Promise((
|
|
8447
|
+
return new Promise((resolve10) => {
|
|
7015
8448
|
const detailStr = details ? ` (${details})` : "";
|
|
7016
8449
|
rl.question(` Allow ${operation}${detailStr}? [y/N]: `, (answer) => {
|
|
7017
|
-
|
|
8450
|
+
resolve10(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
7018
8451
|
});
|
|
7019
8452
|
});
|
|
7020
8453
|
},
|
|
@@ -7499,9 +8932,9 @@ function colorizeAnsi(text, ansiName) {
|
|
|
7499
8932
|
}
|
|
7500
8933
|
|
|
7501
8934
|
// src/services/cron/task-store.ts
|
|
7502
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as
|
|
7503
|
-
import { join as
|
|
7504
|
-
import { randomUUID as
|
|
8935
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
|
|
8936
|
+
import { join as join13, dirname as dirname4 } from "path";
|
|
8937
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
7505
8938
|
var DEFAULT_STORE_DIR = getDataDir();
|
|
7506
8939
|
var DEFAULT_STORE_FILE = "scheduled_tasks.json";
|
|
7507
8940
|
var TaskStore2 = class {
|
|
@@ -7510,10 +8943,10 @@ var TaskStore2 = class {
|
|
|
7510
8943
|
loaded = false;
|
|
7511
8944
|
constructor(storeDir) {
|
|
7512
8945
|
const dir = storeDir ?? DEFAULT_STORE_DIR;
|
|
7513
|
-
this.filePath =
|
|
8946
|
+
this.filePath = join13(dir, DEFAULT_STORE_FILE);
|
|
7514
8947
|
}
|
|
7515
8948
|
load() {
|
|
7516
|
-
if (!
|
|
8949
|
+
if (!existsSync6(this.filePath)) {
|
|
7517
8950
|
this.tasks.clear();
|
|
7518
8951
|
this.loaded = true;
|
|
7519
8952
|
return;
|
|
@@ -7531,8 +8964,8 @@ var TaskStore2 = class {
|
|
|
7531
8964
|
this.loaded = true;
|
|
7532
8965
|
}
|
|
7533
8966
|
save() {
|
|
7534
|
-
const dir =
|
|
7535
|
-
if (!
|
|
8967
|
+
const dir = dirname4(this.filePath);
|
|
8968
|
+
if (!existsSync6(dir)) {
|
|
7536
8969
|
mkdirSync2(dir, { recursive: true });
|
|
7537
8970
|
}
|
|
7538
8971
|
const data = Array.from(this.tasks.values());
|
|
@@ -7546,7 +8979,7 @@ var TaskStore2 = class {
|
|
|
7546
8979
|
addTask(expression, prompt, type = "recurring", nextRun = null) {
|
|
7547
8980
|
this.ensureLoaded();
|
|
7548
8981
|
const task = {
|
|
7549
|
-
id:
|
|
8982
|
+
id: randomUUID9(),
|
|
7550
8983
|
expression,
|
|
7551
8984
|
prompt,
|
|
7552
8985
|
type,
|
|
@@ -7841,14 +9274,14 @@ var CronScheduler = class {
|
|
|
7841
9274
|
}
|
|
7842
9275
|
}
|
|
7843
9276
|
delay(ms) {
|
|
7844
|
-
return new Promise((
|
|
9277
|
+
return new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
7845
9278
|
}
|
|
7846
9279
|
};
|
|
7847
9280
|
|
|
7848
9281
|
// src/services/doctor.ts
|
|
7849
9282
|
import { execSync as execSync3 } from "child_process";
|
|
7850
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
7851
|
-
import { join as
|
|
9283
|
+
import { readFileSync as readFileSync4, existsSync as existsSync7, statSync as statSync2 } from "fs";
|
|
9284
|
+
import { join as join14 } from "path";
|
|
7852
9285
|
var TOOLS = [
|
|
7853
9286
|
{ name: "git", command: "git", versionFlag: "--version", optional: false },
|
|
7854
9287
|
{ name: "ripgrep (rg)", command: "rg", versionFlag: "--version", optional: true },
|
|
@@ -7875,8 +9308,8 @@ var DoctorDiagnostic = class {
|
|
|
7875
9308
|
checkInstallation() {
|
|
7876
9309
|
const execPath = process.execPath;
|
|
7877
9310
|
const isNpmGlobal = execPath.includes("npm") || execPath.includes("npx");
|
|
7878
|
-
const isLocal =
|
|
7879
|
-
const isPackageManager =
|
|
9311
|
+
const isLocal = existsSync7(join14(process.cwd(), "node_modules", "cliskill"));
|
|
9312
|
+
const isPackageManager = existsSync7(join14(process.cwd(), "package.json"));
|
|
7880
9313
|
let installType = "unknown";
|
|
7881
9314
|
if (isNpmGlobal) installType = "npm-global";
|
|
7882
9315
|
else if (isLocal) installType = "local";
|
|
@@ -7988,7 +9421,7 @@ var DoctorDiagnostic = class {
|
|
|
7988
9421
|
checkConfig() {
|
|
7989
9422
|
const configPaths = getConfigSearchPaths();
|
|
7990
9423
|
for (const configPath of configPaths) {
|
|
7991
|
-
if (
|
|
9424
|
+
if (existsSync7(configPath)) {
|
|
7992
9425
|
try {
|
|
7993
9426
|
const raw = readFileSync4(configPath, "utf-8");
|
|
7994
9427
|
JSON.parse(raw);
|
|
@@ -8019,7 +9452,7 @@ var DoctorDiagnostic = class {
|
|
|
8019
9452
|
const configPaths = getConfigSearchPaths();
|
|
8020
9453
|
let baseUrl = "";
|
|
8021
9454
|
for (const configPath of configPaths) {
|
|
8022
|
-
if (
|
|
9455
|
+
if (existsSync7(configPath)) {
|
|
8023
9456
|
try {
|
|
8024
9457
|
const raw = readFileSync4(configPath, "utf-8");
|
|
8025
9458
|
const config = JSON.parse(raw);
|
|
@@ -8066,7 +9499,7 @@ var DoctorDiagnostic = class {
|
|
|
8066
9499
|
checkDiskSpace() {
|
|
8067
9500
|
const memoryDir = getDataDir();
|
|
8068
9501
|
const historyFile = getDiskHistoryPath();
|
|
8069
|
-
if (!
|
|
9502
|
+
if (!existsSync7(memoryDir)) {
|
|
8070
9503
|
return {
|
|
8071
9504
|
name: "Disk Space",
|
|
8072
9505
|
status: "ok",
|
|
@@ -8075,7 +9508,7 @@ var DoctorDiagnostic = class {
|
|
|
8075
9508
|
};
|
|
8076
9509
|
}
|
|
8077
9510
|
try {
|
|
8078
|
-
const historyStats =
|
|
9511
|
+
const historyStats = existsSync7(historyFile) ? statSync2(historyFile) : null;
|
|
8079
9512
|
const historySize = historyStats ? historyStats.size : 0;
|
|
8080
9513
|
return {
|
|
8081
9514
|
name: "Disk Space",
|
|
@@ -8143,9 +9576,9 @@ var DoctorDiagnostic = class {
|
|
|
8143
9576
|
};
|
|
8144
9577
|
|
|
8145
9578
|
// src/services/conversation-recovery.ts
|
|
8146
|
-
import { readFile as
|
|
8147
|
-
import { existsSync as
|
|
8148
|
-
import { join as
|
|
9579
|
+
import { readFile as readFile10, readdir as readdir6, stat as stat4 } from "fs/promises";
|
|
9580
|
+
import { existsSync as existsSync8 } from "fs";
|
|
9581
|
+
import { join as join15, basename as basename3 } from "path";
|
|
8149
9582
|
var DEFAULT_RECOVERY_OPTIONS = {
|
|
8150
9583
|
sessionId: "",
|
|
8151
9584
|
repairMode: false,
|
|
@@ -8159,7 +9592,7 @@ var ConversationRecovery = class {
|
|
|
8159
9592
|
const opts = { ...DEFAULT_RECOVERY_OPTIONS, ...options };
|
|
8160
9593
|
const warnings = [];
|
|
8161
9594
|
const repairs = [];
|
|
8162
|
-
if (!
|
|
9595
|
+
if (!existsSync8(filePath)) {
|
|
8163
9596
|
return {
|
|
8164
9597
|
success: false,
|
|
8165
9598
|
messages: [],
|
|
@@ -8174,7 +9607,7 @@ var ConversationRecovery = class {
|
|
|
8174
9607
|
}
|
|
8175
9608
|
};
|
|
8176
9609
|
}
|
|
8177
|
-
const rawContent = await
|
|
9610
|
+
const rawContent = await readFile10(filePath, "utf-8");
|
|
8178
9611
|
const rawMessages = this.parseJsonl(rawContent);
|
|
8179
9612
|
const totalMessages = rawMessages.length;
|
|
8180
9613
|
const validated = this.validateMessages(rawMessages, opts.repairMode);
|
|
@@ -8216,7 +9649,7 @@ var ConversationRecovery = class {
|
|
|
8216
9649
|
};
|
|
8217
9650
|
}
|
|
8218
9651
|
async findLatestSession(historyDir) {
|
|
8219
|
-
if (!
|
|
9652
|
+
if (!existsSync8(historyDir)) return null;
|
|
8220
9653
|
const sessions = await this.listSessions(historyDir);
|
|
8221
9654
|
if (sessions.length === 0) return null;
|
|
8222
9655
|
sessions.sort(
|
|
@@ -8225,7 +9658,7 @@ var ConversationRecovery = class {
|
|
|
8225
9658
|
return sessions[0].filePath;
|
|
8226
9659
|
}
|
|
8227
9660
|
async findSessionById(historyDir, sessionId) {
|
|
8228
|
-
if (!
|
|
9661
|
+
if (!existsSync8(historyDir)) return null;
|
|
8229
9662
|
const sessions = await this.listSessions(historyDir);
|
|
8230
9663
|
const match = sessions.find(
|
|
8231
9664
|
(s) => s.id === sessionId || s.id.startsWith(sessionId) || basename3(s.filePath).includes(sessionId)
|
|
@@ -8235,12 +9668,12 @@ var ConversationRecovery = class {
|
|
|
8235
9668
|
async validate(filePath) {
|
|
8236
9669
|
const errors = [];
|
|
8237
9670
|
const warnings = [];
|
|
8238
|
-
if (!
|
|
9671
|
+
if (!existsSync8(filePath)) {
|
|
8239
9672
|
return { valid: false, errors: ["File not found"], warnings: [] };
|
|
8240
9673
|
}
|
|
8241
9674
|
let rawContent;
|
|
8242
9675
|
try {
|
|
8243
|
-
rawContent = await
|
|
9676
|
+
rawContent = await readFile10(filePath, "utf-8");
|
|
8244
9677
|
} catch {
|
|
8245
9678
|
return { valid: false, errors: ["Cannot read file"], warnings: [] };
|
|
8246
9679
|
}
|
|
@@ -8275,15 +9708,15 @@ var ConversationRecovery = class {
|
|
|
8275
9708
|
};
|
|
8276
9709
|
}
|
|
8277
9710
|
async listSessions(historyDir) {
|
|
8278
|
-
if (!
|
|
8279
|
-
const entries = await
|
|
9711
|
+
if (!existsSync8(historyDir)) return [];
|
|
9712
|
+
const entries = await readdir6(historyDir);
|
|
8280
9713
|
const sessions = [];
|
|
8281
9714
|
for (const entry of entries) {
|
|
8282
|
-
const filePath =
|
|
8283
|
-
const fileStat = await
|
|
9715
|
+
const filePath = join15(historyDir, entry);
|
|
9716
|
+
const fileStat = await stat4(filePath);
|
|
8284
9717
|
if (fileStat.isDirectory()) {
|
|
8285
|
-
const sessionFile =
|
|
8286
|
-
if (
|
|
9718
|
+
const sessionFile = join15(filePath, "conversation.jsonl");
|
|
9719
|
+
if (existsSync8(sessionFile)) {
|
|
8287
9720
|
const info2 = await this.buildSessionInfo(
|
|
8288
9721
|
sessionFile,
|
|
8289
9722
|
fileStat.mtimeMs
|
|
@@ -8300,7 +9733,7 @@ var ConversationRecovery = class {
|
|
|
8300
9733
|
}
|
|
8301
9734
|
async buildSessionInfo(filePath, mtimeMs) {
|
|
8302
9735
|
try {
|
|
8303
|
-
const content = await
|
|
9736
|
+
const content = await readFile10(filePath, "utf-8");
|
|
8304
9737
|
const lines = content.split("\n").filter(Boolean);
|
|
8305
9738
|
const messages = [];
|
|
8306
9739
|
for (const line of lines) {
|
|
@@ -8494,7 +9927,7 @@ var ConversationRecovery = class {
|
|
|
8494
9927
|
// src/services/deep-links.ts
|
|
8495
9928
|
import { execFile as execFile4 } from "child_process";
|
|
8496
9929
|
import { platform as platform2 } from "os";
|
|
8497
|
-
import { resolve as
|
|
9930
|
+
import { resolve as resolve9, normalize, sep } from "path";
|
|
8498
9931
|
import { promisify } from "util";
|
|
8499
9932
|
var execFileAsync = promisify(execFile4);
|
|
8500
9933
|
var ALLOWED_ACTIONS = /* @__PURE__ */ new Set(["open", "run", "config"]);
|
|
@@ -8775,10 +10208,10 @@ var DeepLinkHandler = class {
|
|
|
8775
10208
|
}
|
|
8776
10209
|
async checkMacOS() {
|
|
8777
10210
|
try {
|
|
8778
|
-
const { stat:
|
|
10211
|
+
const { stat: stat5 } = await import("fs/promises");
|
|
8779
10212
|
const { getPlistPath } = await import("./paths-OODUHG6V.js");
|
|
8780
10213
|
const plistPath = getPlistPath();
|
|
8781
|
-
await
|
|
10214
|
+
await stat5(plistPath);
|
|
8782
10215
|
return true;
|
|
8783
10216
|
} catch {
|
|
8784
10217
|
return false;
|
|
@@ -8795,10 +10228,10 @@ NoDisplay=true
|
|
|
8795
10228
|
`;
|
|
8796
10229
|
const { writeFile: writeFile5, mkdir: mkdir5 } = await import("fs/promises");
|
|
8797
10230
|
const { homedir } = await import("os");
|
|
8798
|
-
const { join:
|
|
8799
|
-
const dir =
|
|
10231
|
+
const { join: join16 } = await import("path");
|
|
10232
|
+
const dir = join16(homedir(), ".local", "share", "applications");
|
|
8800
10233
|
await mkdir5(dir, { recursive: true });
|
|
8801
|
-
await writeFile5(
|
|
10234
|
+
await writeFile5(join16(dir, "cliskill.desktop"), desktopContent, "utf-8");
|
|
8802
10235
|
try {
|
|
8803
10236
|
await execFileAsync("update-desktop-database", [dir]);
|
|
8804
10237
|
} catch {
|
|
@@ -8815,8 +10248,8 @@ NoDisplay=true
|
|
|
8815
10248
|
async unregisterLinux() {
|
|
8816
10249
|
const { unlink: unlink4 } = await import("fs/promises");
|
|
8817
10250
|
const { homedir } = await import("os");
|
|
8818
|
-
const { join:
|
|
8819
|
-
const desktopPath =
|
|
10251
|
+
const { join: join16 } = await import("path");
|
|
10252
|
+
const desktopPath = join16(
|
|
8820
10253
|
homedir(),
|
|
8821
10254
|
".local",
|
|
8822
10255
|
"share",
|
|
@@ -8830,17 +10263,17 @@ NoDisplay=true
|
|
|
8830
10263
|
}
|
|
8831
10264
|
async checkLinux() {
|
|
8832
10265
|
try {
|
|
8833
|
-
const { stat:
|
|
10266
|
+
const { stat: stat5 } = await import("fs/promises");
|
|
8834
10267
|
const { homedir } = await import("os");
|
|
8835
|
-
const { join:
|
|
8836
|
-
const desktopPath =
|
|
10268
|
+
const { join: join16 } = await import("path");
|
|
10269
|
+
const desktopPath = join16(
|
|
8837
10270
|
homedir(),
|
|
8838
10271
|
".local",
|
|
8839
10272
|
"share",
|
|
8840
10273
|
"applications",
|
|
8841
10274
|
"cliskill.desktop"
|
|
8842
10275
|
);
|
|
8843
|
-
await
|
|
10276
|
+
await stat5(desktopPath);
|
|
8844
10277
|
return true;
|
|
8845
10278
|
} catch {
|
|
8846
10279
|
return false;
|
|
@@ -9754,18 +11187,18 @@ function WizardLayout() {
|
|
|
9754
11187
|
// src/ui/wizard/index.tsx
|
|
9755
11188
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
9756
11189
|
function renderWizard() {
|
|
9757
|
-
return new Promise((
|
|
11190
|
+
return new Promise((resolve10) => {
|
|
9758
11191
|
const { unmount } = render2(
|
|
9759
11192
|
/* @__PURE__ */ jsx16(
|
|
9760
11193
|
WizardProvider,
|
|
9761
11194
|
{
|
|
9762
11195
|
onComplete: (state) => {
|
|
9763
11196
|
unmount();
|
|
9764
|
-
|
|
11197
|
+
resolve10(state);
|
|
9765
11198
|
},
|
|
9766
11199
|
onCancel: () => {
|
|
9767
11200
|
unmount();
|
|
9768
|
-
|
|
11201
|
+
resolve10(null);
|
|
9769
11202
|
},
|
|
9770
11203
|
children: /* @__PURE__ */ jsx16(WizardLayout, {})
|
|
9771
11204
|
}
|
|
@@ -10030,6 +11463,7 @@ export {
|
|
|
10030
11463
|
QueryEngine,
|
|
10031
11464
|
AgentTool,
|
|
10032
11465
|
EnhancedMemoryStore,
|
|
11466
|
+
TaskManager,
|
|
10033
11467
|
runAgentLoop,
|
|
10034
11468
|
createDefaultToolRegistry,
|
|
10035
11469
|
StatusBar,
|
|
@@ -10038,6 +11472,8 @@ export {
|
|
|
10038
11472
|
renderInkApp,
|
|
10039
11473
|
formatAnsiMessage,
|
|
10040
11474
|
MessageList,
|
|
11475
|
+
MCPClient,
|
|
11476
|
+
MCPConnectionManager,
|
|
10041
11477
|
runCli
|
|
10042
11478
|
};
|
|
10043
|
-
//# sourceMappingURL=chunk-
|
|
11479
|
+
//# sourceMappingURL=chunk-S4ZZPUPF.js.map
|