nolo-cli 0.1.49 → 0.1.50
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/index.js +191 -10
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1576,6 +1576,26 @@ function formatToolMessageContent(args2) {
|
|
|
1576
1576
|
[tool metadata]
|
|
1577
1577
|
${JSON.stringify(args2.metadata)}`;
|
|
1578
1578
|
}
|
|
1579
|
+
function buildUserAction(args2) {
|
|
1580
|
+
const rawAction = args2.metadata?.requiresUserAction;
|
|
1581
|
+
if (!rawAction || typeof rawAction !== "object" || Array.isArray(rawAction)) return null;
|
|
1582
|
+
const action = rawAction;
|
|
1583
|
+
if (action.type !== "terminal_command") return null;
|
|
1584
|
+
if (!Array.isArray(action.argv)) return null;
|
|
1585
|
+
const argv = action.argv.flatMap(
|
|
1586
|
+
(item) => typeof item === "string" && item.trim() ? [item.trim()] : []
|
|
1587
|
+
);
|
|
1588
|
+
if (argv.length === 0) return null;
|
|
1589
|
+
return {
|
|
1590
|
+
type: "terminal_command",
|
|
1591
|
+
toolName: args2.toolName,
|
|
1592
|
+
toolCallId: args2.toolCallId,
|
|
1593
|
+
argv,
|
|
1594
|
+
...typeof action.displayCommand === "string" ? { displayCommand: action.displayCommand } : {},
|
|
1595
|
+
...typeof action.reason === "string" ? { reason: action.reason, message: action.reason } : {},
|
|
1596
|
+
...typeof action.resumeHint === "string" ? { resumeHint: action.resumeHint } : {}
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1579
1599
|
function summarizeHistoricalToolContent(content) {
|
|
1580
1600
|
if (typeof content !== "string") return content;
|
|
1581
1601
|
if (content.length <= MAX_HISTORICAL_TOOL_CONTENT_CHARS) return content;
|
|
@@ -1677,6 +1697,17 @@ async function runLocalAgentTurn(input2) {
|
|
|
1677
1697
|
arguments: toolCall.function.arguments,
|
|
1678
1698
|
...userInputText ? { userInput: userInputText } : {}
|
|
1679
1699
|
});
|
|
1700
|
+
const userAction = buildUserAction({
|
|
1701
|
+
toolName,
|
|
1702
|
+
toolCallId: toolCall.id,
|
|
1703
|
+
metadata: toolResult.metadata
|
|
1704
|
+
});
|
|
1705
|
+
if (userAction && input2.onUserAction) {
|
|
1706
|
+
const replacement = await input2.onUserAction(userAction);
|
|
1707
|
+
if (replacement) {
|
|
1708
|
+
toolResult = replacement;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1680
1711
|
emitToolEvent(input2, {
|
|
1681
1712
|
type: "tool-result",
|
|
1682
1713
|
round,
|
|
@@ -5131,6 +5162,81 @@ function readTrimmedString(value) {
|
|
|
5131
5162
|
function readFiniteNumber(value) {
|
|
5132
5163
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5133
5164
|
}
|
|
5165
|
+
function resolveExecShellTimeoutMs(override) {
|
|
5166
|
+
if (typeof override === "number" && Number.isFinite(override) && override > 0) {
|
|
5167
|
+
return override;
|
|
5168
|
+
}
|
|
5169
|
+
const raw = process.env[EXEC_SHELL_TIMEOUT_ENV];
|
|
5170
|
+
if (raw !== void 0) {
|
|
5171
|
+
const parsed = Number(raw);
|
|
5172
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
5173
|
+
}
|
|
5174
|
+
return void 0;
|
|
5175
|
+
}
|
|
5176
|
+
function tokenizeShellPrefix(command2) {
|
|
5177
|
+
const tokens = [];
|
|
5178
|
+
const pattern = /"([^"]*)"|'([^']*)'|([^\s"'|;&<>]+)/g;
|
|
5179
|
+
let match;
|
|
5180
|
+
while (match = pattern.exec(command2)) {
|
|
5181
|
+
tokens.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
5182
|
+
}
|
|
5183
|
+
return tokens;
|
|
5184
|
+
}
|
|
5185
|
+
function extractInteractiveGhAuthCommand(command2) {
|
|
5186
|
+
const tokens = tokenizeShellPrefix(command2);
|
|
5187
|
+
if (tokens[0] !== "gh" || tokens[1] !== "auth") return null;
|
|
5188
|
+
const subcommand = tokens[2];
|
|
5189
|
+
if (subcommand !== "login" && subcommand !== "refresh") return null;
|
|
5190
|
+
if (tokens.includes("--help")) return null;
|
|
5191
|
+
const result = ["gh", "auth", subcommand];
|
|
5192
|
+
for (let index = 3; index < tokens.length; index += 1) {
|
|
5193
|
+
const token = tokens[index];
|
|
5194
|
+
if (!token) continue;
|
|
5195
|
+
if (token === "-h" || token === "--hostname" || token === "-s" || token === "--scopes" || token === "--remove-scopes" || token === "-r") {
|
|
5196
|
+
const value = tokens[index + 1];
|
|
5197
|
+
if (value) {
|
|
5198
|
+
result.push(token, value);
|
|
5199
|
+
index += 1;
|
|
5200
|
+
}
|
|
5201
|
+
continue;
|
|
5202
|
+
}
|
|
5203
|
+
if (token === "--clipboard" || token === "-c" || token === "--insecure-storage" || token === "--reset-scopes") {
|
|
5204
|
+
result.push(token);
|
|
5205
|
+
continue;
|
|
5206
|
+
}
|
|
5207
|
+
if (token.startsWith("--hostname=") || token.startsWith("--scopes=") || token.startsWith("--remove-scopes=")) {
|
|
5208
|
+
result.push(token);
|
|
5209
|
+
continue;
|
|
5210
|
+
}
|
|
5211
|
+
}
|
|
5212
|
+
return result.join(" ");
|
|
5213
|
+
}
|
|
5214
|
+
function splitShellWords(command2) {
|
|
5215
|
+
return tokenizeShellPrefix(command2);
|
|
5216
|
+
}
|
|
5217
|
+
function buildInteractiveCommandBlockedResult(command2) {
|
|
5218
|
+
const argv = splitShellWords(command2);
|
|
5219
|
+
return {
|
|
5220
|
+
content: [
|
|
5221
|
+
"requires_user_action: terminal_command",
|
|
5222
|
+
`command: ${command2}`,
|
|
5223
|
+
"Run this in the current TUI terminal, then resume the agent turn.",
|
|
5224
|
+
"exitCode: 130"
|
|
5225
|
+
].join("\n"),
|
|
5226
|
+
metadata: {
|
|
5227
|
+
exitCode: 130,
|
|
5228
|
+
timedOut: false,
|
|
5229
|
+
requiresUserAction: {
|
|
5230
|
+
type: "terminal_command",
|
|
5231
|
+
argv,
|
|
5232
|
+
displayCommand: command2,
|
|
5233
|
+
reason: "This command requires an interactive terminal.",
|
|
5234
|
+
resumeHint: "Continue after the terminal command exits."
|
|
5235
|
+
},
|
|
5236
|
+
reason: "interactive-command-requires-terminal"
|
|
5237
|
+
}
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5134
5240
|
function extractActivityRefs(rawRefs) {
|
|
5135
5241
|
if (!Array.isArray(rawRefs)) return void 0;
|
|
5136
5242
|
const refs = rawRefs.flatMap((entry) => {
|
|
@@ -6802,6 +6908,18 @@ async function previewLifecycleTool(args2) {
|
|
|
6802
6908
|
async function execShellTool(args2) {
|
|
6803
6909
|
const parsed = parseWorkspaceToolArguments(args2.call.arguments);
|
|
6804
6910
|
const command2 = requireShellCommand(parsed, args2.call.name);
|
|
6911
|
+
const interactiveAuthCommand = extractInteractiveGhAuthCommand(command2);
|
|
6912
|
+
if (interactiveAuthCommand) {
|
|
6913
|
+
const activity2 = extractActivity(parsed);
|
|
6914
|
+
const blocked = buildInteractiveCommandBlockedResult(interactiveAuthCommand);
|
|
6915
|
+
return {
|
|
6916
|
+
...blocked,
|
|
6917
|
+
metadata: {
|
|
6918
|
+
...blocked.metadata,
|
|
6919
|
+
...activity2 ? { activity: activity2 } : {}
|
|
6920
|
+
}
|
|
6921
|
+
};
|
|
6922
|
+
}
|
|
6805
6923
|
const result = await runWorkspaceCommand({
|
|
6806
6924
|
workspaceRoot: args2.workspaceRoot,
|
|
6807
6925
|
command: buildWorkspaceShellCommand({
|
|
@@ -6809,7 +6927,7 @@ async function execShellTool(args2) {
|
|
|
6809
6927
|
command: command2,
|
|
6810
6928
|
shell: parsed.shell
|
|
6811
6929
|
}),
|
|
6812
|
-
timeoutMs: args2.commandTimeoutMs,
|
|
6930
|
+
timeoutMs: resolveExecShellTimeoutMs(args2.commandTimeoutMs),
|
|
6813
6931
|
outputLimit: args2.commandOutputLimit,
|
|
6814
6932
|
commandPrefix: args2.commandPrefix
|
|
6815
6933
|
});
|
|
@@ -6887,11 +7005,12 @@ function createLocalWorkspaceToolExecutors(args2) {
|
|
|
6887
7005
|
})
|
|
6888
7006
|
};
|
|
6889
7007
|
}
|
|
6890
|
-
var WORKSPACE_TOOL_NAMES, DEFAULT_LOCAL_CODING_TOOL_NAMES, SHELL_TOOL_NAMES, WORKSPACE_TOOL_NAME_SET, REMOVED_WORKSPACE_TOOL_NAMES;
|
|
7008
|
+
var EXEC_SHELL_TIMEOUT_ENV, WORKSPACE_TOOL_NAMES, DEFAULT_LOCAL_CODING_TOOL_NAMES, SHELL_TOOL_NAMES, WORKSPACE_TOOL_NAME_SET, REMOVED_WORKSPACE_TOOL_NAMES;
|
|
6891
7009
|
var init_localWorkspaceTools = __esm({
|
|
6892
7010
|
"packages/agent-runtime/localWorkspaceTools.ts"() {
|
|
6893
7011
|
"use strict";
|
|
6894
7012
|
init_runtimeCompat();
|
|
7013
|
+
EXEC_SHELL_TIMEOUT_ENV = "NOLO_EXEC_SHELL_TIMEOUT_MS";
|
|
6895
7014
|
WORKSPACE_TOOL_NAMES = [
|
|
6896
7015
|
"listFiles",
|
|
6897
7016
|
"readFile",
|
|
@@ -7698,6 +7817,7 @@ function spawnProcess(options) {
|
|
|
7698
7817
|
} catch {
|
|
7699
7818
|
return {
|
|
7700
7819
|
exited: Promise.resolve(127),
|
|
7820
|
+
stdin: null,
|
|
7701
7821
|
stdout: null,
|
|
7702
7822
|
stderr: null
|
|
7703
7823
|
};
|
|
@@ -7708,6 +7828,7 @@ function spawnProcess(options) {
|
|
|
7708
7828
|
});
|
|
7709
7829
|
return {
|
|
7710
7830
|
exited,
|
|
7831
|
+
stdin: child.stdin,
|
|
7711
7832
|
stdout: child.stdout,
|
|
7712
7833
|
stderr: child.stderr
|
|
7713
7834
|
};
|
|
@@ -68427,8 +68548,18 @@ function clip4(value, max = 72) {
|
|
|
68427
68548
|
const compact2 = value.replace(/\s+/g, " ").trim();
|
|
68428
68549
|
return compact2.length > max ? `${compact2.slice(0, max - 1)}\u2026` : compact2;
|
|
68429
68550
|
}
|
|
68430
|
-
function compactResultHint(
|
|
68551
|
+
function compactResultHint(event, toolName) {
|
|
68552
|
+
const rawAction = event.metadata?.requiresUserAction;
|
|
68553
|
+
if (rawAction && typeof rawAction === "object" && !Array.isArray(rawAction)) {
|
|
68554
|
+
const action = rawAction;
|
|
68555
|
+
const command2 = typeof action.displayCommand === "string" ? action.displayCommand : Array.isArray(action.argv) ? action.argv.filter((item) => typeof item === "string").join(" ") : "";
|
|
68556
|
+
return command2.trim() ? `needs terminal: ${clip4(command2, 120)}` : "needs terminal";
|
|
68557
|
+
}
|
|
68558
|
+
if (event.metadata?.timedOut) return "timed out";
|
|
68559
|
+
const summary = event.summary;
|
|
68431
68560
|
if (!summary) return "";
|
|
68561
|
+
const exitMatch = summary.match(/exit=(\d+)/);
|
|
68562
|
+
if (exitMatch && exitMatch[1] !== "0") return `exit ${exitMatch[1]}`;
|
|
68432
68563
|
const linesMatch = summary.match(/(\d+)\s+lines?/);
|
|
68433
68564
|
if (linesMatch) {
|
|
68434
68565
|
if (toolName === "readFile" || toolName === "listFiles" || toolName === "globFiles") {
|
|
@@ -68438,10 +68569,13 @@ function compactResultHint(summary, toolName) {
|
|
|
68438
68569
|
return `${linesMatch[1]} lines`;
|
|
68439
68570
|
}
|
|
68440
68571
|
}
|
|
68441
|
-
const exitMatch = summary.match(/exit=(\d+)/);
|
|
68442
|
-
if (exitMatch && exitMatch[1] !== "0") return `exit ${exitMatch[1]}`;
|
|
68443
68572
|
return "";
|
|
68444
68573
|
}
|
|
68574
|
+
function isFailedToolResult(event) {
|
|
68575
|
+
const exitCode2 = event.metadata?.exitCode;
|
|
68576
|
+
if (typeof exitCode2 === "number" && exitCode2 !== 0) return true;
|
|
68577
|
+
return Boolean(event.metadata?.timedOut || event.metadata?.requiresUserAction);
|
|
68578
|
+
}
|
|
68445
68579
|
function formatToolTraceLine(text, colorEnabled, accent = "none") {
|
|
68446
68580
|
if (!colorEnabled) return `${text}
|
|
68447
68581
|
`;
|
|
@@ -68483,10 +68617,12 @@ function formatCompactToolLine(event, pending, colorEnabled) {
|
|
|
68483
68617
|
const timing2 = ms ? ` \xB7 ${ms}` : "";
|
|
68484
68618
|
return formatToolTraceLine(` \u25B8 ${label} \u2717 ${message}${timing2}`, colorEnabled, "error");
|
|
68485
68619
|
}
|
|
68486
|
-
const hint = compactResultHint(event
|
|
68620
|
+
const hint = compactResultHint(event, toolName);
|
|
68487
68621
|
const timing = ms ? ` ${ms}` : "";
|
|
68488
68622
|
const suffix = hint ? ` \xB7 ${hint}` : "";
|
|
68489
|
-
|
|
68623
|
+
const marker = isFailedToolResult(event) ? "\u2717" : "\u2713";
|
|
68624
|
+
const accent = isFailedToolResult(event) ? "error" : "none";
|
|
68625
|
+
return formatToolTraceLine(` \u25B8 ${label} ${marker}${timing}${suffix}`, colorEnabled, accent);
|
|
68490
68626
|
}
|
|
68491
68627
|
function createToolEventFormatter(mode, colorEnabled = resolveCliColorEnabled()) {
|
|
68492
68628
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -69028,6 +69164,7 @@ async function runLocalAgentTurnForCli(options, settings) {
|
|
|
69028
69164
|
}
|
|
69029
69165
|
} : {},
|
|
69030
69166
|
...typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {},
|
|
69167
|
+
...options.userActionHandler ? { onUserAction: options.userActionHandler } : {},
|
|
69031
69168
|
...traceLocalTools ? {
|
|
69032
69169
|
onToolEvent: (event) => {
|
|
69033
69170
|
spinner.stop();
|
|
@@ -75050,7 +75187,7 @@ ${renderTuiHelp()}`
|
|
|
75050
75187
|
}
|
|
75051
75188
|
|
|
75052
75189
|
// packages/cli/tui/readlineWorkspace.ts
|
|
75053
|
-
async function runAgentChat(scriptDir, state, message, env, output2, agentRunner = runAgentTurn) {
|
|
75190
|
+
async function runAgentChat(scriptDir, state, message, env, output2, agentRunner = runAgentTurn, userActionHandler) {
|
|
75054
75191
|
const result = await agentRunner({
|
|
75055
75192
|
agentName: state.agentName,
|
|
75056
75193
|
agentKey: state.agentKey,
|
|
@@ -75066,10 +75203,52 @@ async function runAgentChat(scriptDir, state, message, env, output2, agentRunner
|
|
|
75066
75203
|
NOLO_CLI_TOOLS: state.toolDisplay,
|
|
75067
75204
|
NOLO_CLI_RENDER: state.renderDisplay
|
|
75068
75205
|
},
|
|
75069
|
-
output: output2
|
|
75206
|
+
output: output2,
|
|
75207
|
+
...userActionHandler ? { userActionHandler } : {}
|
|
75070
75208
|
});
|
|
75071
75209
|
return result;
|
|
75072
75210
|
}
|
|
75211
|
+
function waitForManualUserAction(rl, input2, output2, action, spawnRunner) {
|
|
75212
|
+
const displayCommand = action.displayCommand ?? action.argv.join(" ");
|
|
75213
|
+
output2.write("\n[nolo] Action needed in your terminal\n");
|
|
75214
|
+
if (action.reason) output2.write(`[nolo] ${action.reason}
|
|
75215
|
+
`);
|
|
75216
|
+
output2.write(` ${displayCommand}
|
|
75217
|
+
`);
|
|
75218
|
+
output2.write("[nolo] Press Enter to run it now. Follow any prompts below, or Ctrl+C to cancel.\n");
|
|
75219
|
+
return new Promise((resolve6) => {
|
|
75220
|
+
rl.question("", async () => {
|
|
75221
|
+
const rawInput = input2;
|
|
75222
|
+
const restoreRawMode = Boolean(rawInput.isRaw);
|
|
75223
|
+
rl.pause();
|
|
75224
|
+
rawInput.setRawMode?.(false);
|
|
75225
|
+
let exitCode2 = 1;
|
|
75226
|
+
try {
|
|
75227
|
+
const proc = spawnRunner({
|
|
75228
|
+
cmd: action.argv,
|
|
75229
|
+
stdin: "inherit",
|
|
75230
|
+
stdout: "inherit",
|
|
75231
|
+
stderr: "inherit"
|
|
75232
|
+
});
|
|
75233
|
+
exitCode2 = await proc.exited;
|
|
75234
|
+
} finally {
|
|
75235
|
+
if (restoreRawMode) rawInput.setRawMode?.(true);
|
|
75236
|
+
rl.resume();
|
|
75237
|
+
}
|
|
75238
|
+
resolve6({
|
|
75239
|
+
content: exitCode2 === 0 ? `user action completed: ${displayCommand}` : `user action failed with exit code ${exitCode2}: ${displayCommand}`,
|
|
75240
|
+
metadata: {
|
|
75241
|
+
exitCode: exitCode2,
|
|
75242
|
+
userActionCompleted: exitCode2 === 0,
|
|
75243
|
+
userActionFailed: exitCode2 !== 0,
|
|
75244
|
+
requiresUserActionCompleted: true,
|
|
75245
|
+
argv: action.argv,
|
|
75246
|
+
displayCommand
|
|
75247
|
+
}
|
|
75248
|
+
});
|
|
75249
|
+
});
|
|
75250
|
+
});
|
|
75251
|
+
}
|
|
75073
75252
|
async function pipeReadableToOutput(stream, output2) {
|
|
75074
75253
|
const text = await readPipeText(stream);
|
|
75075
75254
|
if (text) output2.write(text);
|
|
@@ -75110,6 +75289,7 @@ async function startTuiWorkspace(options) {
|
|
|
75110
75289
|
const output2 = options.output ?? defaultOutput;
|
|
75111
75290
|
const cliEntrypointPath = options.cliEntrypointPath ?? resolveDefaultCliEntrypoint(options.scriptDir);
|
|
75112
75291
|
const cliCommandRunner = options.cliCommandRunner ?? runCliCommandInChildProcess;
|
|
75292
|
+
const spawnRunner = options.spawnRunner ?? spawnProcess;
|
|
75113
75293
|
const selfUpdater = options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
|
|
75114
75294
|
const rl = createInterface2({ input: input2, output: output2 });
|
|
75115
75295
|
output2.write(renderWelcome(state));
|
|
@@ -75252,7 +75432,8 @@ async function startTuiWorkspace(options) {
|
|
|
75252
75432
|
result.action.message,
|
|
75253
75433
|
options.env ?? process.env,
|
|
75254
75434
|
output2,
|
|
75255
|
-
options.agentRunner
|
|
75435
|
+
options.agentRunner,
|
|
75436
|
+
(action) => waitForManualUserAction(rl, input2, output2, action, spawnRunner)
|
|
75256
75437
|
);
|
|
75257
75438
|
if (runResult.dialogId || runResult.turnTokens) {
|
|
75258
75439
|
state = {
|