llmist 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-LBHWVCZ2.js → chunk-GANXNBIZ.js} +123 -1
- package/dist/chunk-GANXNBIZ.js.map +1 -0
- package/dist/{chunk-LFSIEPAE.js → chunk-ZDNV7DDO.js} +2 -2
- package/dist/cli.cjs +283 -96
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +163 -98
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +122 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -2
- package/dist/{mock-stream-BQHut0lQ.d.cts → mock-stream-wRfUqXx4.d.cts} +96 -6
- package/dist/{mock-stream-BQHut0lQ.d.ts → mock-stream-wRfUqXx4.d.ts} +96 -6
- package/dist/testing/index.cjs +122 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +2 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LBHWVCZ2.js.map +0 -1
- /package/dist/{chunk-LFSIEPAE.js.map → chunk-ZDNV7DDO.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -1020,6 +1020,100 @@ var init_gadget = __esm({
|
|
|
1020
1020
|
throw new AbortError();
|
|
1021
1021
|
}
|
|
1022
1022
|
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Register a cleanup function to run when execution is aborted (timeout or cancellation).
|
|
1025
|
+
* The cleanup function is called immediately if the signal is already aborted.
|
|
1026
|
+
* Errors thrown by the cleanup function are silently ignored.
|
|
1027
|
+
*
|
|
1028
|
+
* Use this to clean up resources like browser instances, database connections,
|
|
1029
|
+
* or child processes when the gadget is cancelled due to timeout.
|
|
1030
|
+
*
|
|
1031
|
+
* @param ctx - The execution context containing the abort signal
|
|
1032
|
+
* @param cleanup - Function to run on abort (can be sync or async)
|
|
1033
|
+
*
|
|
1034
|
+
* @example
|
|
1035
|
+
* ```typescript
|
|
1036
|
+
* class BrowserGadget extends Gadget({
|
|
1037
|
+
* description: 'Fetches web page content',
|
|
1038
|
+
* schema: z.object({ url: z.string() }),
|
|
1039
|
+
* }) {
|
|
1040
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1041
|
+
* const browser = await chromium.launch();
|
|
1042
|
+
* this.onAbort(ctx, () => browser.close());
|
|
1043
|
+
*
|
|
1044
|
+
* const page = await browser.newPage();
|
|
1045
|
+
* this.onAbort(ctx, () => page.close());
|
|
1046
|
+
*
|
|
1047
|
+
* await page.goto(params.url);
|
|
1048
|
+
* const content = await page.content();
|
|
1049
|
+
*
|
|
1050
|
+
* await browser.close();
|
|
1051
|
+
* return content;
|
|
1052
|
+
* }
|
|
1053
|
+
* }
|
|
1054
|
+
* ```
|
|
1055
|
+
*/
|
|
1056
|
+
onAbort(ctx, cleanup) {
|
|
1057
|
+
if (!ctx?.signal) return;
|
|
1058
|
+
const safeCleanup = () => {
|
|
1059
|
+
try {
|
|
1060
|
+
const result = cleanup();
|
|
1061
|
+
if (result && typeof result === "object" && "catch" in result) {
|
|
1062
|
+
result.catch(() => {
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
if (ctx.signal.aborted) {
|
|
1069
|
+
safeCleanup();
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
ctx.signal.addEventListener("abort", safeCleanup, { once: true });
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Create an AbortController linked to the execution context's signal.
|
|
1076
|
+
* When the parent signal aborts, the returned controller also aborts with the same reason.
|
|
1077
|
+
*
|
|
1078
|
+
* Useful for passing abort signals to child operations like fetch() while still
|
|
1079
|
+
* being able to abort them independently if needed.
|
|
1080
|
+
*
|
|
1081
|
+
* @param ctx - The execution context containing the parent abort signal
|
|
1082
|
+
* @returns A new AbortController linked to the parent signal
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```typescript
|
|
1086
|
+
* class FetchGadget extends Gadget({
|
|
1087
|
+
* description: 'Fetches data from URL',
|
|
1088
|
+
* schema: z.object({ url: z.string() }),
|
|
1089
|
+
* }) {
|
|
1090
|
+
* async execute(params: this['params'], ctx?: ExecutionContext): Promise<string> {
|
|
1091
|
+
* const controller = this.createLinkedAbortController(ctx);
|
|
1092
|
+
*
|
|
1093
|
+
* // fetch() will automatically abort when parent times out
|
|
1094
|
+
* const response = await fetch(params.url, { signal: controller.signal });
|
|
1095
|
+
* return response.text();
|
|
1096
|
+
* }
|
|
1097
|
+
* }
|
|
1098
|
+
* ```
|
|
1099
|
+
*/
|
|
1100
|
+
createLinkedAbortController(ctx) {
|
|
1101
|
+
const controller = new AbortController();
|
|
1102
|
+
if (ctx?.signal) {
|
|
1103
|
+
if (ctx.signal.aborted) {
|
|
1104
|
+
controller.abort(ctx.signal.reason);
|
|
1105
|
+
} else {
|
|
1106
|
+
ctx.signal.addEventListener(
|
|
1107
|
+
"abort",
|
|
1108
|
+
() => {
|
|
1109
|
+
controller.abort(ctx.signal.reason);
|
|
1110
|
+
},
|
|
1111
|
+
{ once: true }
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return controller;
|
|
1116
|
+
}
|
|
1023
1117
|
/**
|
|
1024
1118
|
* Auto-generated instruction text for the LLM.
|
|
1025
1119
|
* Combines name, description, and parameter schema into a formatted instruction.
|
|
@@ -3741,6 +3835,23 @@ var init_agent = __esm({
|
|
|
3741
3835
|
maxIterations: this.maxIterations
|
|
3742
3836
|
});
|
|
3743
3837
|
while (currentIteration < this.maxIterations) {
|
|
3838
|
+
if (this.signal?.aborted) {
|
|
3839
|
+
this.logger.info("Agent loop terminated by abort signal", {
|
|
3840
|
+
iteration: currentIteration,
|
|
3841
|
+
reason: this.signal.reason
|
|
3842
|
+
});
|
|
3843
|
+
await this.safeObserve(async () => {
|
|
3844
|
+
if (this.hooks.observers?.onAbort) {
|
|
3845
|
+
const context = {
|
|
3846
|
+
iteration: currentIteration,
|
|
3847
|
+
reason: this.signal?.reason,
|
|
3848
|
+
logger: this.logger
|
|
3849
|
+
};
|
|
3850
|
+
await this.hooks.observers.onAbort(context);
|
|
3851
|
+
}
|
|
3852
|
+
});
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3744
3855
|
this.logger.debug("Starting iteration", { iteration: currentIteration });
|
|
3745
3856
|
try {
|
|
3746
3857
|
if (this.compactionManager) {
|
|
@@ -3802,6 +3913,17 @@ var init_agent = __esm({
|
|
|
3802
3913
|
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
3803
3914
|
}
|
|
3804
3915
|
}
|
|
3916
|
+
await this.safeObserve(async () => {
|
|
3917
|
+
if (this.hooks.observers?.onLLMCallReady) {
|
|
3918
|
+
const context = {
|
|
3919
|
+
iteration: currentIteration,
|
|
3920
|
+
maxIterations: this.maxIterations,
|
|
3921
|
+
options: llmOptions,
|
|
3922
|
+
logger: this.logger
|
|
3923
|
+
};
|
|
3924
|
+
await this.hooks.observers.onLLMCallReady(context);
|
|
3925
|
+
}
|
|
3926
|
+
});
|
|
3805
3927
|
this.logger.info("Calling LLM", { model: this.model });
|
|
3806
3928
|
this.logger.silly("LLM request details", {
|
|
3807
3929
|
model: llmOptions.model,
|
|
@@ -6888,7 +7010,6 @@ var OPTION_FLAGS = {
|
|
|
6888
7010
|
logFile: "--log-file <path>",
|
|
6889
7011
|
logReset: "--log-reset",
|
|
6890
7012
|
logLlmRequests: "--log-llm-requests [dir]",
|
|
6891
|
-
logLlmResponses: "--log-llm-responses [dir]",
|
|
6892
7013
|
noBuiltins: "--no-builtins",
|
|
6893
7014
|
noBuiltinInteraction: "--no-builtin-interaction",
|
|
6894
7015
|
quiet: "-q, --quiet",
|
|
@@ -6907,8 +7028,7 @@ var OPTION_DESCRIPTIONS = {
|
|
|
6907
7028
|
logLevel: "Log level: silly, trace, debug, info, warn, error, fatal.",
|
|
6908
7029
|
logFile: "Path to log file. When set, logs are written to file instead of stderr.",
|
|
6909
7030
|
logReset: "Reset (truncate) the log file at session start instead of appending.",
|
|
6910
|
-
logLlmRequests: "Save
|
|
6911
|
-
logLlmResponses: "Save raw LLM responses as plain text. Optional dir, defaults to ~/.llmist/logs/responses/",
|
|
7031
|
+
logLlmRequests: "Save LLM requests/responses to session directories. Optional dir, defaults to ~/.llmist/logs/requests/",
|
|
6912
7032
|
noBuiltins: "Disable built-in gadgets (AskUser, TellUser).",
|
|
6913
7033
|
noBuiltinInteraction: "Disable interactive gadgets (AskUser) while keeping TellUser.",
|
|
6914
7034
|
quiet: "Suppress all output except content (text and TellUser messages).",
|
|
@@ -6925,7 +7045,7 @@ var import_commander2 = require("commander");
|
|
|
6925
7045
|
// package.json
|
|
6926
7046
|
var package_default = {
|
|
6927
7047
|
name: "llmist",
|
|
6928
|
-
version: "1.
|
|
7048
|
+
version: "2.1.0",
|
|
6929
7049
|
description: "Universal TypeScript LLM client with streaming-first agent framework. Works with any model - no structured outputs or native tool calling required. Implements its own flexible grammar for function calling.",
|
|
6930
7050
|
type: "module",
|
|
6931
7051
|
main: "dist/index.cjs",
|
|
@@ -7098,6 +7218,14 @@ ${addedLines}`;
|
|
|
7098
7218
|
}
|
|
7099
7219
|
|
|
7100
7220
|
// src/cli/approval/context-providers.ts
|
|
7221
|
+
function formatGadgetSummary(gadgetName, params) {
|
|
7222
|
+
const paramEntries = Object.entries(params);
|
|
7223
|
+
if (paramEntries.length === 0) {
|
|
7224
|
+
return `${gadgetName}()`;
|
|
7225
|
+
}
|
|
7226
|
+
const paramStr = paramEntries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
7227
|
+
return `${gadgetName}(${paramStr})`;
|
|
7228
|
+
}
|
|
7101
7229
|
var WriteFileContextProvider = class {
|
|
7102
7230
|
gadgetName = "WriteFile";
|
|
7103
7231
|
async getContext(params) {
|
|
@@ -7106,14 +7234,14 @@ var WriteFileContextProvider = class {
|
|
|
7106
7234
|
const resolvedPath = (0, import_node_path2.resolve)(process.cwd(), filePath);
|
|
7107
7235
|
if (!(0, import_node_fs2.existsSync)(resolvedPath)) {
|
|
7108
7236
|
return {
|
|
7109
|
-
summary:
|
|
7237
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
7110
7238
|
details: formatNewFileDiff(filePath, newContent)
|
|
7111
7239
|
};
|
|
7112
7240
|
}
|
|
7113
7241
|
const oldContent = (0, import_node_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
7114
7242
|
const diff = (0, import_diff.createPatch)(filePath, oldContent, newContent, "original", "modified");
|
|
7115
7243
|
return {
|
|
7116
|
-
summary:
|
|
7244
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
7117
7245
|
details: diff
|
|
7118
7246
|
};
|
|
7119
7247
|
}
|
|
@@ -7127,37 +7255,27 @@ var EditFileContextProvider = class {
|
|
|
7127
7255
|
const newContent = String(params.content);
|
|
7128
7256
|
if (!(0, import_node_fs2.existsSync)(resolvedPath)) {
|
|
7129
7257
|
return {
|
|
7130
|
-
summary:
|
|
7258
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
7131
7259
|
details: formatNewFileDiff(filePath, newContent)
|
|
7132
7260
|
};
|
|
7133
7261
|
}
|
|
7134
7262
|
const oldContent = (0, import_node_fs2.readFileSync)(resolvedPath, "utf-8");
|
|
7135
7263
|
const diff = (0, import_diff.createPatch)(filePath, oldContent, newContent, "original", "modified");
|
|
7136
7264
|
return {
|
|
7137
|
-
summary:
|
|
7265
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
7138
7266
|
details: diff
|
|
7139
7267
|
};
|
|
7140
7268
|
}
|
|
7141
7269
|
if ("commands" in params) {
|
|
7142
7270
|
const commands = String(params.commands);
|
|
7143
7271
|
return {
|
|
7144
|
-
summary:
|
|
7272
|
+
summary: formatGadgetSummary(this.gadgetName, params),
|
|
7145
7273
|
details: `Commands:
|
|
7146
7274
|
${commands}`
|
|
7147
7275
|
};
|
|
7148
7276
|
}
|
|
7149
7277
|
return {
|
|
7150
|
-
summary:
|
|
7151
|
-
};
|
|
7152
|
-
}
|
|
7153
|
-
};
|
|
7154
|
-
var RunCommandContextProvider = class {
|
|
7155
|
-
gadgetName = "RunCommand";
|
|
7156
|
-
async getContext(params) {
|
|
7157
|
-
const command = String(params.command ?? "");
|
|
7158
|
-
const cwd = params.cwd ? ` (in ${params.cwd})` : "";
|
|
7159
|
-
return {
|
|
7160
|
-
summary: `Execute: ${command}${cwd}`
|
|
7278
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
7161
7279
|
};
|
|
7162
7280
|
}
|
|
7163
7281
|
};
|
|
@@ -7166,27 +7284,15 @@ var DefaultContextProvider = class {
|
|
|
7166
7284
|
this.gadgetName = gadgetName;
|
|
7167
7285
|
}
|
|
7168
7286
|
async getContext(params) {
|
|
7169
|
-
const paramEntries = Object.entries(params);
|
|
7170
|
-
if (paramEntries.length === 0) {
|
|
7171
|
-
return {
|
|
7172
|
-
summary: `${this.gadgetName}()`
|
|
7173
|
-
};
|
|
7174
|
-
}
|
|
7175
|
-
const formatValue = (value) => {
|
|
7176
|
-
const MAX_LEN = 50;
|
|
7177
|
-
const str = JSON.stringify(value);
|
|
7178
|
-
return str.length > MAX_LEN ? `${str.slice(0, MAX_LEN - 3)}...` : str;
|
|
7179
|
-
};
|
|
7180
|
-
const paramStr = paramEntries.map(([k, v]) => `${k}=${formatValue(v)}`).join(", ");
|
|
7181
7287
|
return {
|
|
7182
|
-
summary:
|
|
7288
|
+
summary: formatGadgetSummary(this.gadgetName, params)
|
|
7183
7289
|
};
|
|
7184
7290
|
}
|
|
7185
7291
|
};
|
|
7186
7292
|
var builtinContextProviders = [
|
|
7187
7293
|
new WriteFileContextProvider(),
|
|
7188
|
-
new EditFileContextProvider()
|
|
7189
|
-
|
|
7294
|
+
new EditFileContextProvider()
|
|
7295
|
+
// Note: RunCommand uses DefaultContextProvider - no custom details needed
|
|
7190
7296
|
];
|
|
7191
7297
|
|
|
7192
7298
|
// src/cli/approval/manager.ts
|
|
@@ -7197,11 +7303,13 @@ var ApprovalManager = class {
|
|
|
7197
7303
|
* @param config - Approval configuration with per-gadget modes
|
|
7198
7304
|
* @param env - CLI environment for I/O operations
|
|
7199
7305
|
* @param progress - Optional progress indicator to pause during prompts
|
|
7306
|
+
* @param keyboard - Optional keyboard coordinator to disable ESC listener during prompts
|
|
7200
7307
|
*/
|
|
7201
|
-
constructor(config, env, progress) {
|
|
7308
|
+
constructor(config, env, progress, keyboard) {
|
|
7202
7309
|
this.config = config;
|
|
7203
7310
|
this.env = env;
|
|
7204
7311
|
this.progress = progress;
|
|
7312
|
+
this.keyboard = keyboard;
|
|
7205
7313
|
for (const provider of builtinContextProviders) {
|
|
7206
7314
|
this.registerProvider(provider);
|
|
7207
7315
|
}
|
|
@@ -7270,26 +7378,34 @@ var ApprovalManager = class {
|
|
|
7270
7378
|
const provider = this.providers.get(gadgetName.toLowerCase()) ?? new DefaultContextProvider(gadgetName);
|
|
7271
7379
|
const context = await provider.getContext(params);
|
|
7272
7380
|
this.progress?.pause();
|
|
7273
|
-
this.
|
|
7381
|
+
if (this.keyboard?.cleanupEsc) {
|
|
7382
|
+
this.keyboard.cleanupEsc();
|
|
7383
|
+
this.keyboard.cleanupEsc = null;
|
|
7384
|
+
}
|
|
7385
|
+
try {
|
|
7386
|
+
this.env.stderr.write(`
|
|
7274
7387
|
${import_chalk2.default.yellow("\u{1F512} Approval required:")} ${context.summary}
|
|
7275
7388
|
`);
|
|
7276
|
-
|
|
7277
|
-
|
|
7389
|
+
if (context.details) {
|
|
7390
|
+
this.env.stderr.write(`
|
|
7278
7391
|
${renderColoredDiff(context.details)}
|
|
7279
7392
|
`);
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7393
|
+
}
|
|
7394
|
+
const response = await this.prompt(" \u23CE approve, or type to reject: ");
|
|
7395
|
+
const isApproved = response === "" || response.toLowerCase() === "y";
|
|
7396
|
+
if (isApproved) {
|
|
7397
|
+
this.env.stderr.write(` ${import_chalk2.default.green("\u2713 Approved")}
|
|
7285
7398
|
|
|
7286
7399
|
`);
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7400
|
+
return { approved: true };
|
|
7401
|
+
}
|
|
7402
|
+
this.env.stderr.write(` ${import_chalk2.default.red("\u2717 Denied")}
|
|
7290
7403
|
|
|
7291
7404
|
`);
|
|
7292
|
-
|
|
7405
|
+
return { approved: false, reason: response || "Rejected by user" };
|
|
7406
|
+
} finally {
|
|
7407
|
+
this.keyboard?.restore();
|
|
7408
|
+
}
|
|
7293
7409
|
}
|
|
7294
7410
|
/**
|
|
7295
7411
|
* Prompts for user input.
|
|
@@ -7819,6 +7935,22 @@ var runCommand = createGadget({
|
|
|
7819
7935
|
params: { argv: ["gh", "pr", "review", "123", "--comment", "--body", "Review with `backticks` and 'quotes'"], timeout: 3e4 },
|
|
7820
7936
|
output: "status=0\n\n(no output)",
|
|
7821
7937
|
comment: "Complex arguments with special characters - no escaping needed"
|
|
7938
|
+
},
|
|
7939
|
+
{
|
|
7940
|
+
params: {
|
|
7941
|
+
argv: [
|
|
7942
|
+
"gh",
|
|
7943
|
+
"pr",
|
|
7944
|
+
"review",
|
|
7945
|
+
"123",
|
|
7946
|
+
"--approve",
|
|
7947
|
+
"--body",
|
|
7948
|
+
"## Review Summary\n\n**Looks good!**\n\n- Clean code\n- Tests pass"
|
|
7949
|
+
],
|
|
7950
|
+
timeout: 3e4
|
|
7951
|
+
},
|
|
7952
|
+
output: "status=0\n\nApproving pull request #123",
|
|
7953
|
+
comment: "Multiline body: --body flag and content must be SEPARATE array elements"
|
|
7822
7954
|
}
|
|
7823
7955
|
],
|
|
7824
7956
|
execute: async ({ argv, cwd, timeout }) => {
|
|
@@ -7826,6 +7958,7 @@ var runCommand = createGadget({
|
|
|
7826
7958
|
if (argv.length === 0) {
|
|
7827
7959
|
return "status=1\n\nerror: argv array cannot be empty";
|
|
7828
7960
|
}
|
|
7961
|
+
let timeoutId;
|
|
7829
7962
|
try {
|
|
7830
7963
|
const proc = Bun.spawn(argv, {
|
|
7831
7964
|
cwd: workingDir,
|
|
@@ -7833,12 +7966,15 @@ var runCommand = createGadget({
|
|
|
7833
7966
|
stderr: "pipe"
|
|
7834
7967
|
});
|
|
7835
7968
|
const timeoutPromise = new Promise((_, reject) => {
|
|
7836
|
-
setTimeout(() => {
|
|
7969
|
+
timeoutId = setTimeout(() => {
|
|
7837
7970
|
proc.kill();
|
|
7838
7971
|
reject(new Error(`Command timed out after ${timeout}ms`));
|
|
7839
7972
|
}, timeout);
|
|
7840
7973
|
});
|
|
7841
7974
|
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
7975
|
+
if (timeoutId) {
|
|
7976
|
+
clearTimeout(timeoutId);
|
|
7977
|
+
}
|
|
7842
7978
|
const stdout = await new Response(proc.stdout).text();
|
|
7843
7979
|
const stderr = await new Response(proc.stderr).text();
|
|
7844
7980
|
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
@@ -7846,6 +7982,9 @@ var runCommand = createGadget({
|
|
|
7846
7982
|
|
|
7847
7983
|
${output || "(no output)"}`;
|
|
7848
7984
|
} catch (error) {
|
|
7985
|
+
if (timeoutId) {
|
|
7986
|
+
clearTimeout(timeoutId);
|
|
7987
|
+
}
|
|
7849
7988
|
const message = error instanceof Error ? error.message : String(error);
|
|
7850
7989
|
return `status=1
|
|
7851
7990
|
|
|
@@ -8018,6 +8157,30 @@ async function writeLogFile(dir, filename, content) {
|
|
|
8018
8157
|
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
8019
8158
|
await (0, import_promises2.writeFile)((0, import_node_path7.join)(dir, filename), content, "utf-8");
|
|
8020
8159
|
}
|
|
8160
|
+
function formatSessionTimestamp(date = /* @__PURE__ */ new Date()) {
|
|
8161
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
8162
|
+
const year = date.getFullYear();
|
|
8163
|
+
const month = pad(date.getMonth() + 1);
|
|
8164
|
+
const day = pad(date.getDate());
|
|
8165
|
+
const hours = pad(date.getHours());
|
|
8166
|
+
const minutes = pad(date.getMinutes());
|
|
8167
|
+
const seconds = pad(date.getSeconds());
|
|
8168
|
+
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
|
|
8169
|
+
}
|
|
8170
|
+
async function createSessionDir(baseDir) {
|
|
8171
|
+
const timestamp = formatSessionTimestamp();
|
|
8172
|
+
const sessionDir = (0, import_node_path7.join)(baseDir, timestamp);
|
|
8173
|
+
try {
|
|
8174
|
+
await (0, import_promises2.mkdir)(sessionDir, { recursive: true });
|
|
8175
|
+
return sessionDir;
|
|
8176
|
+
} catch (error) {
|
|
8177
|
+
console.warn(`[llmist] Failed to create log session directory: ${sessionDir}`, error);
|
|
8178
|
+
return void 0;
|
|
8179
|
+
}
|
|
8180
|
+
}
|
|
8181
|
+
function formatCallNumber(n) {
|
|
8182
|
+
return n.toString().padStart(4, "0");
|
|
8183
|
+
}
|
|
8021
8184
|
|
|
8022
8185
|
// src/cli/utils.ts
|
|
8023
8186
|
var import_chalk4 = __toESM(require("chalk"), 1);
|
|
@@ -8181,7 +8344,7 @@ function formatBytes(bytes) {
|
|
|
8181
8344
|
}
|
|
8182
8345
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8183
8346
|
}
|
|
8184
|
-
function
|
|
8347
|
+
function formatGadgetSummary2(result) {
|
|
8185
8348
|
const gadgetLabel = import_chalk3.default.magenta.bold(result.gadgetName);
|
|
8186
8349
|
const timeLabel = import_chalk3.default.dim(`${Math.round(result.executionTimeMs)}ms`);
|
|
8187
8350
|
const paramsStr = formatParametersInline(result.parameters);
|
|
@@ -8266,12 +8429,21 @@ function isInteractive(stream2) {
|
|
|
8266
8429
|
}
|
|
8267
8430
|
var ESC_KEY = 27;
|
|
8268
8431
|
var ESC_TIMEOUT_MS = 50;
|
|
8269
|
-
|
|
8432
|
+
var CTRL_C = 3;
|
|
8433
|
+
function createEscKeyListener(stdin, onEsc, onCtrlC) {
|
|
8270
8434
|
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
8271
8435
|
return null;
|
|
8272
8436
|
}
|
|
8273
8437
|
let escTimeout = null;
|
|
8274
8438
|
const handleData = (data) => {
|
|
8439
|
+
if (data[0] === CTRL_C && onCtrlC) {
|
|
8440
|
+
if (escTimeout) {
|
|
8441
|
+
clearTimeout(escTimeout);
|
|
8442
|
+
escTimeout = null;
|
|
8443
|
+
}
|
|
8444
|
+
onCtrlC();
|
|
8445
|
+
return;
|
|
8446
|
+
}
|
|
8275
8447
|
if (data[0] === ESC_KEY) {
|
|
8276
8448
|
if (data.length === 1) {
|
|
8277
8449
|
escTimeout = setTimeout(() => {
|
|
@@ -8719,7 +8891,7 @@ function addCompleteOptions(cmd, defaults) {
|
|
|
8719
8891
|
OPTION_DESCRIPTIONS.maxTokens,
|
|
8720
8892
|
createNumericParser({ label: "Max tokens", integer: true, min: 1 }),
|
|
8721
8893
|
defaults?.["max-tokens"]
|
|
8722
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"])
|
|
8894
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]);
|
|
8723
8895
|
}
|
|
8724
8896
|
function addAgentOptions(cmd, defaults) {
|
|
8725
8897
|
const gadgetAccumulator = (value, previous = []) => [
|
|
@@ -8743,7 +8915,7 @@ function addAgentOptions(cmd, defaults) {
|
|
|
8743
8915
|
OPTION_FLAGS.noBuiltinInteraction,
|
|
8744
8916
|
OPTION_DESCRIPTIONS.noBuiltinInteraction,
|
|
8745
8917
|
defaults?.["builtin-interaction"] !== false
|
|
8746
|
-
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.
|
|
8918
|
+
).option(OPTION_FLAGS.quiet, OPTION_DESCRIPTIONS.quiet, defaults?.quiet).option(OPTION_FLAGS.logLlmRequests, OPTION_DESCRIPTIONS.logLlmRequests, defaults?.["log-llm-requests"]).option(OPTION_FLAGS.docker, OPTION_DESCRIPTIONS.docker).option(OPTION_FLAGS.dockerRo, OPTION_DESCRIPTIONS.dockerRo).option(OPTION_FLAGS.noDocker, OPTION_DESCRIPTIONS.noDocker).option(OPTION_FLAGS.dockerDev, OPTION_DESCRIPTIONS.dockerDev);
|
|
8747
8919
|
}
|
|
8748
8920
|
function configToCompleteOptions(config) {
|
|
8749
8921
|
const result = {};
|
|
@@ -8753,7 +8925,6 @@ function configToCompleteOptions(config) {
|
|
|
8753
8925
|
if (config["max-tokens"] !== void 0) result.maxTokens = config["max-tokens"];
|
|
8754
8926
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
8755
8927
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
8756
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
8757
8928
|
return result;
|
|
8758
8929
|
}
|
|
8759
8930
|
function configToAgentOptions(config) {
|
|
@@ -8777,7 +8948,6 @@ function configToAgentOptions(config) {
|
|
|
8777
8948
|
result.gadgetApproval = config["gadget-approval"];
|
|
8778
8949
|
if (config.quiet !== void 0) result.quiet = config.quiet;
|
|
8779
8950
|
if (config["log-llm-requests"] !== void 0) result.logLlmRequests = config["log-llm-requests"];
|
|
8780
|
-
if (config["log-llm-responses"] !== void 0) result.logLlmResponses = config["log-llm-responses"];
|
|
8781
8951
|
if (config.docker !== void 0) result.docker = config.docker;
|
|
8782
8952
|
if (config["docker-cwd-permission"] !== void 0)
|
|
8783
8953
|
result.dockerCwdPermission = config["docker-cwd-permission"];
|
|
@@ -8795,7 +8965,8 @@ var DOCKER_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8795
8965
|
"env-vars",
|
|
8796
8966
|
"image-name",
|
|
8797
8967
|
"dev-mode",
|
|
8798
|
-
"dev-source"
|
|
8968
|
+
"dev-source",
|
|
8969
|
+
"docker-args"
|
|
8799
8970
|
]);
|
|
8800
8971
|
var DEFAULT_IMAGE_NAME = "llmist-sandbox";
|
|
8801
8972
|
var DEFAULT_CWD_PERMISSION = "rw";
|
|
@@ -8910,7 +9081,6 @@ var COMPLETE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8910
9081
|
"log-file",
|
|
8911
9082
|
"log-reset",
|
|
8912
9083
|
"log-llm-requests",
|
|
8913
|
-
"log-llm-responses",
|
|
8914
9084
|
"type",
|
|
8915
9085
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
8916
9086
|
"docker",
|
|
@@ -8943,7 +9113,6 @@ var AGENT_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
8943
9113
|
"log-file",
|
|
8944
9114
|
"log-reset",
|
|
8945
9115
|
"log-llm-requests",
|
|
8946
|
-
"log-llm-responses",
|
|
8947
9116
|
"type",
|
|
8948
9117
|
// Allowed for inheritance compatibility, ignored for built-in commands
|
|
8949
9118
|
"docker",
|
|
@@ -9131,13 +9300,6 @@ function validateCompleteConfig(raw, section) {
|
|
|
9131
9300
|
section
|
|
9132
9301
|
);
|
|
9133
9302
|
}
|
|
9134
|
-
if ("log-llm-responses" in rawObj) {
|
|
9135
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
9136
|
-
rawObj["log-llm-responses"],
|
|
9137
|
-
"log-llm-responses",
|
|
9138
|
-
section
|
|
9139
|
-
);
|
|
9140
|
-
}
|
|
9141
9303
|
return result;
|
|
9142
9304
|
}
|
|
9143
9305
|
function validateAgentConfig(raw, section) {
|
|
@@ -9216,13 +9378,6 @@ function validateAgentConfig(raw, section) {
|
|
|
9216
9378
|
section
|
|
9217
9379
|
);
|
|
9218
9380
|
}
|
|
9219
|
-
if ("log-llm-responses" in rawObj) {
|
|
9220
|
-
result["log-llm-responses"] = validateStringOrBoolean(
|
|
9221
|
-
rawObj["log-llm-responses"],
|
|
9222
|
-
"log-llm-responses",
|
|
9223
|
-
section
|
|
9224
|
-
);
|
|
9225
|
-
}
|
|
9226
9381
|
return result;
|
|
9227
9382
|
}
|
|
9228
9383
|
function validateStringOrBoolean(value, field, section) {
|
|
@@ -9664,6 +9819,9 @@ function validateDockerConfig(raw, section) {
|
|
|
9664
9819
|
if ("dev-source" in rawObj) {
|
|
9665
9820
|
result["dev-source"] = validateString2(rawObj["dev-source"], "dev-source", section);
|
|
9666
9821
|
}
|
|
9822
|
+
if ("docker-args" in rawObj) {
|
|
9823
|
+
result["docker-args"] = validateStringArray2(rawObj["docker-args"], "docker-args", section);
|
|
9824
|
+
}
|
|
9667
9825
|
return result;
|
|
9668
9826
|
}
|
|
9669
9827
|
|
|
@@ -9675,6 +9833,8 @@ FROM oven/bun:1-debian
|
|
|
9675
9833
|
|
|
9676
9834
|
# Install essential tools
|
|
9677
9835
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
9836
|
+
# ed for EditFile gadget (line-oriented editor)
|
|
9837
|
+
ed \\
|
|
9678
9838
|
# ripgrep for fast file searching
|
|
9679
9839
|
ripgrep \\
|
|
9680
9840
|
# git for version control operations
|
|
@@ -9707,6 +9867,7 @@ FROM oven/bun:1-debian
|
|
|
9707
9867
|
|
|
9708
9868
|
# Install essential tools (same as production)
|
|
9709
9869
|
RUN apt-get update && apt-get install -y --no-install-recommends \\
|
|
9870
|
+
ed \\
|
|
9710
9871
|
ripgrep \\
|
|
9711
9872
|
git \\
|
|
9712
9873
|
curl \\
|
|
@@ -9941,6 +10102,9 @@ function buildDockerRunArgs(ctx, imageName, devMode) {
|
|
|
9941
10102
|
}
|
|
9942
10103
|
}
|
|
9943
10104
|
}
|
|
10105
|
+
if (ctx.config["docker-args"]) {
|
|
10106
|
+
args.push(...ctx.config["docker-args"]);
|
|
10107
|
+
}
|
|
9944
10108
|
args.push(imageName);
|
|
9945
10109
|
args.push(...ctx.forwardArgs);
|
|
9946
10110
|
return args;
|
|
@@ -10107,6 +10271,8 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10107
10271
|
env.stderr.write(import_chalk5.default.yellow(`
|
|
10108
10272
|
[Cancelled] ${progress.formatStats()}
|
|
10109
10273
|
`));
|
|
10274
|
+
} else {
|
|
10275
|
+
handleQuit();
|
|
10110
10276
|
}
|
|
10111
10277
|
};
|
|
10112
10278
|
const keyboard = {
|
|
@@ -10114,7 +10280,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10114
10280
|
cleanupSigint: null,
|
|
10115
10281
|
restore: () => {
|
|
10116
10282
|
if (stdinIsInteractive && stdinStream.isTTY && !wasCancelled) {
|
|
10117
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
10283
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
10118
10284
|
}
|
|
10119
10285
|
}
|
|
10120
10286
|
};
|
|
@@ -10139,7 +10305,7 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10139
10305
|
process.exit(130);
|
|
10140
10306
|
};
|
|
10141
10307
|
if (stdinIsInteractive && stdinStream.isTTY) {
|
|
10142
|
-
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel);
|
|
10308
|
+
keyboard.cleanupEsc = createEscKeyListener(stdinStream, handleCancel, handleCancel);
|
|
10143
10309
|
}
|
|
10144
10310
|
keyboard.cleanupSigint = createSigintListener(
|
|
10145
10311
|
handleCancel,
|
|
@@ -10165,11 +10331,11 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10165
10331
|
gadgetApprovals,
|
|
10166
10332
|
defaultMode: "allowed"
|
|
10167
10333
|
};
|
|
10168
|
-
const approvalManager = new ApprovalManager(approvalConfig, env, progress);
|
|
10334
|
+
const approvalManager = new ApprovalManager(approvalConfig, env, progress, keyboard);
|
|
10169
10335
|
let usage;
|
|
10170
10336
|
let iterations = 0;
|
|
10171
|
-
const
|
|
10172
|
-
|
|
10337
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
10338
|
+
let llmSessionDir;
|
|
10173
10339
|
let llmCallCounter = 0;
|
|
10174
10340
|
const countMessagesTokens = async (model, messages) => {
|
|
10175
10341
|
try {
|
|
@@ -10201,10 +10367,19 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10201
10367
|
);
|
|
10202
10368
|
progress.startCall(context.options.model, inputTokens);
|
|
10203
10369
|
progress.setInputTokens(inputTokens, false);
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10370
|
+
},
|
|
10371
|
+
// onLLMCallReady: Log the exact request being sent to the LLM
|
|
10372
|
+
// This fires AFTER controller modifications (e.g., trailing messages)
|
|
10373
|
+
onLLMCallReady: async (context) => {
|
|
10374
|
+
if (llmLogsBaseDir) {
|
|
10375
|
+
if (!llmSessionDir) {
|
|
10376
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
10377
|
+
}
|
|
10378
|
+
if (llmSessionDir) {
|
|
10379
|
+
const filename = `${formatCallNumber(llmCallCounter)}.request`;
|
|
10380
|
+
const content = formatLlmRequest(context.options.messages);
|
|
10381
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
10382
|
+
}
|
|
10208
10383
|
}
|
|
10209
10384
|
},
|
|
10210
10385
|
// onStreamChunk: Real-time updates as LLM generates tokens
|
|
@@ -10269,9 +10444,9 @@ async function executeAgent(promptArg, options, env) {
|
|
|
10269
10444
|
`);
|
|
10270
10445
|
}
|
|
10271
10446
|
}
|
|
10272
|
-
if (
|
|
10273
|
-
const filename = `${
|
|
10274
|
-
await writeLogFile(
|
|
10447
|
+
if (llmSessionDir) {
|
|
10448
|
+
const filename = `${formatCallNumber(llmCallCounter)}.response`;
|
|
10449
|
+
await writeLogFile(llmSessionDir, filename, context.rawResponse);
|
|
10275
10450
|
}
|
|
10276
10451
|
}
|
|
10277
10452
|
},
|
|
@@ -10368,6 +10543,13 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10368
10543
|
parameterMapping: (text) => ({ message: text, done: false, type: "info" }),
|
|
10369
10544
|
resultMapping: (text) => `\u2139\uFE0F ${text}`
|
|
10370
10545
|
});
|
|
10546
|
+
builder.withTrailingMessage(
|
|
10547
|
+
(ctx) => [
|
|
10548
|
+
`[Iteration ${ctx.iteration + 1}/${ctx.maxIterations}]`,
|
|
10549
|
+
"Think carefully: what gadget invocations can you make in parallel right now?",
|
|
10550
|
+
"Maximize efficiency by batching independent operations in a single response."
|
|
10551
|
+
].join(" ")
|
|
10552
|
+
);
|
|
10371
10553
|
const agent = builder.ask(prompt);
|
|
10372
10554
|
let textBuffer = "";
|
|
10373
10555
|
const flushTextBuffer = () => {
|
|
@@ -10393,7 +10575,7 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10393
10575
|
}
|
|
10394
10576
|
} else {
|
|
10395
10577
|
const tokenCount = await countGadgetOutputTokens(event.result.result);
|
|
10396
|
-
env.stderr.write(`${
|
|
10578
|
+
env.stderr.write(`${formatGadgetSummary2({ ...event.result, tokenCount })}
|
|
10397
10579
|
`);
|
|
10398
10580
|
}
|
|
10399
10581
|
}
|
|
@@ -10405,7 +10587,10 @@ Denied: ${result.reason ?? "by user"}`
|
|
|
10405
10587
|
} finally {
|
|
10406
10588
|
isStreaming = false;
|
|
10407
10589
|
keyboard.cleanupEsc?.();
|
|
10408
|
-
keyboard.cleanupSigint
|
|
10590
|
+
if (keyboard.cleanupSigint) {
|
|
10591
|
+
keyboard.cleanupSigint();
|
|
10592
|
+
process.once("SIGINT", () => process.exit(130));
|
|
10593
|
+
}
|
|
10409
10594
|
}
|
|
10410
10595
|
flushTextBuffer();
|
|
10411
10596
|
progress.complete();
|
|
@@ -10453,13 +10638,15 @@ async function executeComplete(promptArg, options, env) {
|
|
|
10453
10638
|
}
|
|
10454
10639
|
builder.addUser(prompt);
|
|
10455
10640
|
const messages = builder.build();
|
|
10456
|
-
const
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10641
|
+
const llmLogsBaseDir = resolveLogDir(options.logLlmRequests, "requests");
|
|
10642
|
+
let llmSessionDir;
|
|
10643
|
+
if (llmLogsBaseDir) {
|
|
10644
|
+
llmSessionDir = await createSessionDir(llmLogsBaseDir);
|
|
10645
|
+
if (llmSessionDir) {
|
|
10646
|
+
const filename = "0001.request";
|
|
10647
|
+
const content = formatLlmRequest(messages);
|
|
10648
|
+
await writeLogFile(llmSessionDir, filename, content);
|
|
10649
|
+
}
|
|
10463
10650
|
}
|
|
10464
10651
|
const stream2 = client.stream({
|
|
10465
10652
|
model,
|
|
@@ -10498,9 +10685,9 @@ async function executeComplete(promptArg, options, env) {
|
|
|
10498
10685
|
progress.endCall(usage);
|
|
10499
10686
|
progress.complete();
|
|
10500
10687
|
printer.ensureNewline();
|
|
10501
|
-
if (
|
|
10502
|
-
const filename =
|
|
10503
|
-
await writeLogFile(
|
|
10688
|
+
if (llmSessionDir) {
|
|
10689
|
+
const filename = "0001.response";
|
|
10690
|
+
await writeLogFile(llmSessionDir, filename, accumulatedResponse);
|
|
10504
10691
|
}
|
|
10505
10692
|
if (stderrTTY && !options.quiet) {
|
|
10506
10693
|
const summary = renderSummary({ finishReason, usage, cost: progress.getTotalCost() });
|