nolo-cli 0.1.38 → 0.1.40
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 +4 -4
- package/agent-runtime/agentRecordConfig.ts +0 -14
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -1
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +39 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +1 -17
- package/agent-runtime/localWorkspaceTools.ts +51 -110
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +3 -14
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +2 -1
- package/agentAliases.ts +8 -1
- package/agentRunCommand.ts +65 -95
- package/agentRuntimeLocal.ts +1 -7
- package/ai/agent/cliExecutor.ts +249 -34
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +8 -1
- package/cli/agentRunCommand.ts +65 -95
- package/cli/agentRuntimeLocal.ts +1 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +94 -93
- package/cli/client/localRuntimeAdapter.ts +250 -69
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +76 -39
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/cli/scriptCommandEntries.ts +0 -2
- package/cli/tui/readlineWorkspace.ts +69 -0
- package/cli/tui/session.ts +176 -1
- package/cliEnvHelpers.ts +11 -7
- package/client/agentConfigResolver.test.ts +4 -5
- package/client/agentRun.test.ts +307 -57
- package/client/agentRun.ts +94 -93
- package/client/localRuntimeAdapter.test.ts +332 -53
- package/client/localRuntimeAdapter.ts +250 -69
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/dialogCommands.ts +572 -0
- package/dialogInternalCommandEntries.ts +4 -0
- package/machineWsRunDispatch.ts +76 -39
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +11 -9
- package/scriptCommandEntries.ts +0 -2
- package/tui/readlineWorkspace.ts +69 -0
- package/tui/session.ts +176 -1
- package/agent-runtime/taskWorkspace.ts +0 -193
- package/agent-runtime/workspaceSession.ts +0 -76
- package/cli/client/taskWorktree.ts +0 -8
- package/cli/client/workspaceSession.ts +0 -11
- package/client/taskWorktree.ts +0 -8
- package/client/workspaceSession.test.ts +0 -57
- package/client/workspaceSession.ts +0 -11
package/cli/agentRunCommand.ts
CHANGED
|
@@ -4,13 +4,7 @@ import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "./agent
|
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { extname, resolve } from "node:path";
|
|
6
6
|
import { parseSkillDocProtocol, type WorkflowReferenceConfig } from "../ai/skills/skillDocProtocol";
|
|
7
|
-
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
8
|
-
import {
|
|
9
|
-
ensureWorkspacePackageLinks,
|
|
10
|
-
formatPreparedTaskWorktree,
|
|
11
|
-
prepareTaskWorktree,
|
|
12
|
-
type PreparedTaskWorktree,
|
|
13
|
-
} from "./client/taskWorktree";
|
|
7
|
+
import { MIMO_MONTH_AGENT_KEY, resolveCliAgentKeyInput } from "./agentAliases";
|
|
14
8
|
import type { TaskRunPromptContext } from "./client/taskRunPrompt";
|
|
15
9
|
|
|
16
10
|
type EnvLike = Record<string, string | undefined>;
|
|
@@ -25,8 +19,6 @@ type AgentRunCommandDeps = {
|
|
|
25
19
|
output?: OutputLike;
|
|
26
20
|
runner?: typeof runAgentTurn;
|
|
27
21
|
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
28
|
-
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
29
|
-
ensureWorkspacePackageLinks?: typeof ensureWorkspacePackageLinks;
|
|
30
22
|
inspectLocalRunWorkspace?: typeof inspectLocalRunWorkspace;
|
|
31
23
|
resolveWorkflowReference?: typeof resolveWorkflowReference;
|
|
32
24
|
};
|
|
@@ -58,12 +50,11 @@ type ParsedAgentRunArgs = {
|
|
|
58
50
|
parentDialogId?: string;
|
|
59
51
|
background: boolean;
|
|
60
52
|
noStream: boolean;
|
|
61
|
-
noDefaultTestRoot: boolean;
|
|
62
53
|
cwd?: string;
|
|
63
|
-
maxToolRounds?: number;
|
|
64
54
|
timeoutMs?: number;
|
|
65
55
|
traceTools: boolean;
|
|
66
|
-
|
|
56
|
+
eventsMode?: "jsonl";
|
|
57
|
+
injectFeatureWorktreeInstruction: boolean;
|
|
67
58
|
taskRunContext?: TaskRunPromptContext;
|
|
68
59
|
workflowRef?: string;
|
|
69
60
|
};
|
|
@@ -74,10 +65,6 @@ type ResolvedWorkflowReference = {
|
|
|
74
65
|
config?: Partial<WorkflowReferenceConfig>;
|
|
75
66
|
};
|
|
76
67
|
|
|
77
|
-
const DEFAULT_TEST_ROOT_DIALOG_ID = "01KN6V3MVEQA4Q1PNKVM2W7YY8";
|
|
78
|
-
const DEFAULT_TEST_ROOT_DIALOG_KEY = `dialog-b2e06f801f-${DEFAULT_TEST_ROOT_DIALOG_ID}`;
|
|
79
|
-
const DEFAULT_TEST_DIALOG_CATEGORY_ID = "test-dialogs";
|
|
80
|
-
|
|
81
68
|
function readFlagValue(args: string[], flag: string) {
|
|
82
69
|
const index = args.indexOf(flag);
|
|
83
70
|
if (index < 0) return undefined;
|
|
@@ -102,11 +89,19 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
|
|
|
102
89
|
return undefined;
|
|
103
90
|
}
|
|
104
91
|
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
function isMonthlyMimoAgentRef(raw: string | undefined, resolved: string) {
|
|
93
|
+
const normalized = raw?.trim().toLowerCase();
|
|
94
|
+
return (
|
|
95
|
+
resolved === MIMO_MONTH_AGENT_KEY ||
|
|
96
|
+
normalized === "fullstack" ||
|
|
97
|
+
normalized === "full-stack" ||
|
|
98
|
+
normalized === "nolo-fullstack" ||
|
|
99
|
+
normalized === "包月mimo" ||
|
|
100
|
+
normalized === "包月mimo2.5" ||
|
|
101
|
+
normalized === "mimo-month" ||
|
|
102
|
+
normalized === "全栈" ||
|
|
103
|
+
normalized === "nolo 全栈工程师"
|
|
104
|
+
);
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
function parsePositiveInteger(value: string | undefined) {
|
|
@@ -123,12 +118,10 @@ function positionalArgs(args: string[]) {
|
|
|
123
118
|
"--server",
|
|
124
119
|
"--auto",
|
|
125
120
|
"--dangerously-allow-shell",
|
|
126
|
-
"--worktree-task",
|
|
127
121
|
"--trace-tools",
|
|
128
122
|
"--bg",
|
|
129
123
|
"--no-stream",
|
|
130
124
|
"--debug",
|
|
131
|
-
"--no-default-test-root",
|
|
132
125
|
]);
|
|
133
126
|
for (let index = 0; index < args.length; index += 1) {
|
|
134
127
|
const arg = args[index];
|
|
@@ -146,7 +139,8 @@ export function parseAgentRunArgs(
|
|
|
146
139
|
args: string[],
|
|
147
140
|
options: ParseAgentRunArgsOptions = {}
|
|
148
141
|
): ParsedAgentRunArgs | null {
|
|
149
|
-
const
|
|
142
|
+
const rawAgentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
|
|
143
|
+
const agentKey = rawAgentKey ? resolveCliAgentKeyInput(rawAgentKey) : undefined;
|
|
150
144
|
const explicitMsg = readFlagValue(args, "--msg");
|
|
151
145
|
const msgFile = readFlagValue(args, "--msg-file");
|
|
152
146
|
const fileMessage = msgFile
|
|
@@ -160,25 +154,19 @@ export function parseAgentRunArgs(
|
|
|
160
154
|
const spaceId = readFlagValue(args, "--space");
|
|
161
155
|
const category = readFlagValue(args, "--category");
|
|
162
156
|
const inheritedFromDialog = readFlagValue(args, "--inherit-from-dialog");
|
|
163
|
-
const noDefaultTestRoot = args.includes("--no-default-test-root");
|
|
164
157
|
const cwd = readFlagValue(args, "--cwd");
|
|
165
|
-
const maxToolRounds = parseNonNegativeInteger(
|
|
166
|
-
readFlagValue(args, "--max-tool-rounds") ?? readFlagValue(args, "--rounds")
|
|
167
|
-
);
|
|
168
158
|
const timeoutMs = parsePositiveInteger(readFlagValue(args, "--timeout-ms"));
|
|
159
|
+
const rawEventsMode = readFlagValue(args, "--events");
|
|
160
|
+
const eventsMode = rawEventsMode === "jsonl" ? "jsonl" : undefined;
|
|
169
161
|
const inheritedRef = inheritedFromDialog
|
|
170
162
|
? parseDialogReference(inheritedFromDialog)
|
|
171
|
-
:
|
|
172
|
-
? {
|
|
173
|
-
dialogKey: DEFAULT_TEST_ROOT_DIALOG_KEY,
|
|
174
|
-
dialogId: DEFAULT_TEST_ROOT_DIALOG_ID,
|
|
175
|
-
}
|
|
176
|
-
: undefined;
|
|
163
|
+
: undefined;
|
|
177
164
|
const imageUrls = [
|
|
178
165
|
...readRepeatedFlagValues(args, "--image"),
|
|
179
166
|
...readRepeatedFlagValues(args, "--image-url"),
|
|
180
167
|
];
|
|
181
|
-
const
|
|
168
|
+
const mimoLocalWorkspaceDefault =
|
|
169
|
+
isMonthlyMimoAgentRef(rawAgentKey, agentKey) && runtimeMode !== "server";
|
|
182
170
|
const workflowRef = readFlagValue(args, "--workflow");
|
|
183
171
|
const taskRowDbKey = readFlagValue(args, "--task-row-dbkey");
|
|
184
172
|
const artifactIds = readFlagValue(args, "--artifact-ids")
|
|
@@ -186,12 +174,13 @@ export function parseAgentRunArgs(
|
|
|
186
174
|
.map((item) => item.trim())
|
|
187
175
|
.filter(Boolean);
|
|
188
176
|
return {
|
|
189
|
-
agentKey
|
|
177
|
+
agentKey,
|
|
190
178
|
message,
|
|
191
179
|
imageUrls,
|
|
192
|
-
allowShell: args.includes("--dangerously-allow-shell"),
|
|
180
|
+
allowShell: args.includes("--dangerously-allow-shell") || mimoLocalWorkspaceDefault,
|
|
193
181
|
traceTools: args.includes("--trace-tools"),
|
|
194
|
-
|
|
182
|
+
...(eventsMode ? { eventsMode } : {}),
|
|
183
|
+
injectFeatureWorktreeInstruction: mimoLocalWorkspaceDefault,
|
|
195
184
|
...(workflowRef ? { workflowRef } : {}),
|
|
196
185
|
...(taskRowDbKey
|
|
197
186
|
? {
|
|
@@ -203,17 +192,19 @@ export function parseAgentRunArgs(
|
|
|
203
192
|
},
|
|
204
193
|
}
|
|
205
194
|
: {}),
|
|
206
|
-
...(runtimeMode
|
|
195
|
+
...(runtimeMode
|
|
196
|
+
? { runtimeMode }
|
|
197
|
+
: mimoLocalWorkspaceDefault || args.includes("--dangerously-allow-shell")
|
|
198
|
+
? { runtimeMode: "local" as const }
|
|
199
|
+
: {}),
|
|
207
200
|
background: args.includes("--bg"),
|
|
208
201
|
noStream: args.includes("--no-stream"),
|
|
209
|
-
noDefaultTestRoot,
|
|
210
202
|
...(continueDialogId ? { continueDialogId } : {}),
|
|
211
203
|
...(!continueDialogId && spaceId ? { spaceId } : {}),
|
|
212
|
-
...(category ? { category } :
|
|
204
|
+
...(category ? { category } : {}),
|
|
213
205
|
...(inheritedRef?.dialogKey ? { inheritedFromDialogKey: inheritedRef.dialogKey } : {}),
|
|
214
206
|
...(inheritedRef?.dialogId ? { parentDialogId: inheritedRef.dialogId } : {}),
|
|
215
207
|
...(cwd ? { cwd } : {}),
|
|
216
|
-
...(typeof maxToolRounds === "number" ? { maxToolRounds } : {}),
|
|
217
208
|
...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
|
|
218
209
|
};
|
|
219
210
|
}
|
|
@@ -269,6 +260,20 @@ export function prependWorkflowReferencePrompt(message: string, workflow?: Resol
|
|
|
269
260
|
].join("\n");
|
|
270
261
|
}
|
|
271
262
|
|
|
263
|
+
function prependFeatureWorktreeInstruction(message: string, enabled: boolean) {
|
|
264
|
+
if (!enabled) return message;
|
|
265
|
+
return [
|
|
266
|
+
"Local execution rule:",
|
|
267
|
+
"- You are running in the current git checkout with shell access.",
|
|
268
|
+
"- For read-only checks, smoke tests, or answering questions, stay in the current directory.",
|
|
269
|
+
"- Before developing a new feature or making non-trivial code changes, create a separate git worktree yourself with git worktree and do the edits there.",
|
|
270
|
+
"- Commit and push only when the user explicitly asks or the task requires it.",
|
|
271
|
+
"",
|
|
272
|
+
"User task:",
|
|
273
|
+
message,
|
|
274
|
+
].join("\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
272
277
|
function parseDialogReference(rawInput: string) {
|
|
273
278
|
const normalized = rawInput.trim();
|
|
274
279
|
if (normalized.startsWith("dialog-")) {
|
|
@@ -381,26 +386,20 @@ function resolveServerUrl(env: EnvLike) {
|
|
|
381
386
|
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
382
387
|
}
|
|
383
388
|
|
|
384
|
-
function
|
|
385
|
-
return !parsed.cwd && (parsed.useTaskWorktree || parsed.allowShell);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function buildTaskWorktreeEnv(args: {
|
|
389
|
+
function buildLocalRunEnv(args: {
|
|
389
390
|
env: EnvLike;
|
|
390
|
-
worktreePath?: string;
|
|
391
391
|
allowShell: boolean;
|
|
392
392
|
}) {
|
|
393
|
+
void args.allowShell;
|
|
393
394
|
return {
|
|
394
395
|
...args.env,
|
|
395
|
-
...(args.allowShell ? { NOLO_LOCAL_SHELL_MODE: "worktree" } : {}),
|
|
396
|
-
...(args.worktreePath ? { NOLO_LOCAL_WORKTREE: args.worktreePath } : {}),
|
|
397
396
|
};
|
|
398
397
|
}
|
|
399
398
|
|
|
400
399
|
function writeUsage(output: OutputLike) {
|
|
401
400
|
output.write(
|
|
402
|
-
"Usage: nolo agent run <agent> <message> [--local|--server|--auto] [--continue <dialogId>] [--cwd <path>]
|
|
403
|
-
" nolo agent run --agent <agent> (--msg <message>|--msg-file <path>) [--image <url-or-path>] [--space <spaceId>] [--category <name>] [--inherit-from-dialog <dialog>] [--task-row-dbkey <key>] [--bg] [--timeout-ms <n>] [--no-stream]\n"
|
|
401
|
+
"Usage: nolo agent run <agent> <message> [--local|--server|--auto] [--continue <dialogId>] [--cwd <path>]\n" +
|
|
402
|
+
" nolo agent run --agent <agent> (--msg <message>|--msg-file <path>) [--image <url-or-path>] [--space <spaceId>] [--category <name>] [--inherit-from-dialog <dialog>] [--task-row-dbkey <key>] [--bg] [--timeout-ms <n>] [--events jsonl] [--no-stream]\n"
|
|
404
403
|
);
|
|
405
404
|
}
|
|
406
405
|
|
|
@@ -426,52 +425,24 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
|
|
|
426
425
|
}
|
|
427
426
|
}
|
|
428
427
|
let localRuntimeCwd = parsed.cwd;
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
const preparedWorktree = await (deps.prepareTaskWorktree ?? prepareTaskWorktree)({
|
|
432
|
-
agentKey: parsed.agentKey,
|
|
433
|
-
env,
|
|
434
|
-
});
|
|
435
|
-
localRuntimeCwd = preparedWorktree.path;
|
|
436
|
-
output.write(formatPreparedTaskWorktree(preparedWorktree));
|
|
437
|
-
} catch (error) {
|
|
438
|
-
output.write(
|
|
439
|
-
"[nolo] Could not prepare a local task worktree.\n" +
|
|
440
|
-
"Run from inside a git repository, or set NOLO_LOCAL_WORKTREE to an existing isolated checkout.\n" +
|
|
441
|
-
`Reason: ${error instanceof Error ? error.message : String(error)}\n`
|
|
442
|
-
);
|
|
443
|
-
return 1;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
const shouldBindLocalWorkspace =
|
|
447
|
-
!!localRuntimeCwd &&
|
|
448
|
-
(parsed.runtimeMode === "local" || parsed.allowShell || parsed.useTaskWorktree);
|
|
449
|
-
if (shouldBindLocalWorkspace) {
|
|
450
|
-
try {
|
|
451
|
-
await (deps.ensureWorkspacePackageLinks ?? ensureWorkspacePackageLinks)(
|
|
452
|
-
localRuntimeCwd!
|
|
453
|
-
);
|
|
454
|
-
} catch (error) {
|
|
455
|
-
output.write(
|
|
456
|
-
"[nolo] Dependency preflight failed for local task worktree.\n" +
|
|
457
|
-
"The worktree may have node_modules entries that point outside the task checkout, or paths that cannot be repaired safely.\n" +
|
|
458
|
-
`Reason: ${error instanceof Error ? error.message : String(error)}\n`
|
|
459
|
-
);
|
|
460
|
-
return 1;
|
|
461
|
-
}
|
|
428
|
+
if (!localRuntimeCwd && parsed.runtimeMode === "local") {
|
|
429
|
+
localRuntimeCwd = process.cwd();
|
|
462
430
|
}
|
|
463
|
-
const runEnv =
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
allowShell: parsed.allowShell,
|
|
468
|
-
})
|
|
469
|
-
: env;
|
|
431
|
+
const runEnv = buildLocalRunEnv({
|
|
432
|
+
env,
|
|
433
|
+
allowShell: parsed.allowShell,
|
|
434
|
+
});
|
|
470
435
|
const result: RunAgentTurnResult = await runner({
|
|
471
436
|
agentName: parsed.agentKey,
|
|
472
437
|
agentKey: parsed.agentKey,
|
|
473
438
|
serverUrl: resolveServerUrl(env),
|
|
474
|
-
message: prependWorkflowReferencePrompt(
|
|
439
|
+
message: prependWorkflowReferencePrompt(
|
|
440
|
+
prependFeatureWorktreeInstruction(
|
|
441
|
+
parsed.message,
|
|
442
|
+
parsed.injectFeatureWorktreeInstruction
|
|
443
|
+
),
|
|
444
|
+
workflowReference
|
|
445
|
+
),
|
|
475
446
|
imageUrls: parsed.imageUrls.map(normalizeCliImageInput),
|
|
476
447
|
scriptDir: deps.scriptDir,
|
|
477
448
|
env: runEnv,
|
|
@@ -488,10 +459,9 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
|
|
|
488
459
|
...(parsed.parentDialogId ? { parentDialogId: parsed.parentDialogId } : {}),
|
|
489
460
|
background: parsed.background,
|
|
490
461
|
noStream: parsed.noStream,
|
|
491
|
-
noDefaultTestRoot: parsed.noDefaultTestRoot,
|
|
492
|
-
...(typeof parsed.maxToolRounds === "number" ? { maxToolRounds: parsed.maxToolRounds } : {}),
|
|
493
462
|
...(typeof parsed.timeoutMs === "number" ? { timeoutMs: parsed.timeoutMs } : {}),
|
|
494
463
|
traceTools: parsed.traceTools,
|
|
464
|
+
...(parsed.eventsMode ? { eventsMode: parsed.eventsMode } : {}),
|
|
495
465
|
...(parsed.taskRunContext ? { taskRunContext: parsed.taskRunContext } : {}),
|
|
496
466
|
});
|
|
497
467
|
|
package/cli/agentRuntimeLocal.ts
CHANGED
|
@@ -5,12 +5,9 @@ export {
|
|
|
5
5
|
hasDirectOpenAiCompatibleProvider,
|
|
6
6
|
createHybridRecordStore,
|
|
7
7
|
executeLocalToolWithPolicy,
|
|
8
|
-
activateWorkspaceSession,
|
|
9
|
-
createWorkspaceSession,
|
|
10
8
|
buildAgentRuntimeAgentLookupKeys,
|
|
11
9
|
buildAgentRuntimeDialogWritePlan,
|
|
12
10
|
dialogMessageRecordToAgentRuntimeMessage,
|
|
13
|
-
formatWorkspaceSessionActivation,
|
|
14
11
|
pickAgentRuntimeInferenceOptions,
|
|
15
12
|
parseOpenAiCompatibleChatCompletionResponse,
|
|
16
13
|
parsePlatformChatCompletionData,
|
|
@@ -23,6 +20,7 @@ export {
|
|
|
23
20
|
resolveLocalToolPolicy,
|
|
24
21
|
resolveOpenAiCompatibleProviderConfig,
|
|
25
22
|
resolveRequestedRuntimeToolNames,
|
|
23
|
+
resolveRuntimeToolSurfaceForAgent,
|
|
26
24
|
evaluateShellCommandPolicy,
|
|
27
25
|
isDestructiveShellCommand,
|
|
28
26
|
shouldUsePlatformChatProvider,
|
|
@@ -44,8 +42,4 @@ export type {
|
|
|
44
42
|
HybridRecordKvDb,
|
|
45
43
|
HybridRecordStore,
|
|
46
44
|
LocalToolPolicyDecision,
|
|
47
|
-
PreparedTaskWorkspace,
|
|
48
|
-
PrepareTaskWorkspace,
|
|
49
|
-
WorkspaceSession,
|
|
50
|
-
WorkspaceSessionMode,
|
|
51
45
|
} from "../agent-runtime";
|
package/cli/cliEnvHelpers.ts
CHANGED
|
@@ -96,14 +96,18 @@ export function resolveAuthToken(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
export function parseUserIdFromAuthToken(token: string) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
const parts = token.trim().split(".").filter(Boolean);
|
|
100
|
+
const payloadCandidates = parts.length >= 3 ? [parts[1], parts[0]] : [parts[0]];
|
|
101
|
+
for (const payload of payloadCandidates) {
|
|
102
|
+
if (!payload) continue;
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
|
|
105
|
+
if (typeof parsed?.userId === "string") return parsed.userId;
|
|
106
|
+
} catch {
|
|
107
|
+
// Try the next token shape.
|
|
108
|
+
}
|
|
106
109
|
}
|
|
110
|
+
return "";
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
export function readOption(args: string[], flag: string) {
|
package/cli/client/agentRun.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../agentAliases";
|
|
10
10
|
import type { LocalAgentToolEvent } from "../../agent-runtime/localLoop";
|
|
11
11
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
12
|
-
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
12
|
+
import { createCliLocalRuntimeAdapter, isBuiltinNoloAgentRef } from "./localRuntimeAdapter";
|
|
13
13
|
import { createStreamingTextWriter } from "./streamingOutput";
|
|
14
14
|
import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
|
|
15
15
|
|
|
@@ -44,7 +44,6 @@ type RunAgentTurnOptions = {
|
|
|
44
44
|
parentDialogId?: string;
|
|
45
45
|
background?: boolean;
|
|
46
46
|
noStream?: boolean;
|
|
47
|
-
noDefaultTestRoot?: boolean;
|
|
48
47
|
scriptDir: string;
|
|
49
48
|
env: EnvLike;
|
|
50
49
|
output: OutputLike;
|
|
@@ -52,9 +51,9 @@ type RunAgentTurnOptions = {
|
|
|
52
51
|
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
53
52
|
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
54
53
|
localRuntimeCwd?: string;
|
|
55
|
-
maxToolRounds?: number;
|
|
56
54
|
timeoutMs?: number;
|
|
57
55
|
traceTools?: boolean;
|
|
56
|
+
eventsMode?: "jsonl";
|
|
58
57
|
taskRunContext?: TaskRunPromptContext;
|
|
59
58
|
taskRunRecorder?: (args: {
|
|
60
59
|
options: RunAgentTurnOptions;
|
|
@@ -180,10 +179,27 @@ function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
|
|
|
180
179
|
const normalizedKey = normalizeAgentRef(options.agentKey);
|
|
181
180
|
return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
|
|
182
181
|
}
|
|
183
|
-
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
182
|
+
|
|
183
|
+
function isMachineBoundLocalhostCustomProvider(agentConfig: any) {
|
|
184
|
+
const machineId =
|
|
185
|
+
agentConfig?.runtimeBinding && typeof agentConfig.runtimeBinding === "object"
|
|
186
|
+
? String(agentConfig.runtimeBinding.machineId ?? "").trim()
|
|
187
|
+
: "";
|
|
188
|
+
const providerUrl = typeof agentConfig?.customProviderUrl === "string"
|
|
189
|
+
? agentConfig.customProviderUrl.trim()
|
|
190
|
+
: "";
|
|
191
|
+
if (!machineId || !providerUrl) return false;
|
|
192
|
+
try {
|
|
193
|
+
const hostname = new URL(providerUrl).hostname.toLowerCase();
|
|
194
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resolveAuthToken(env: EnvLike) {
|
|
201
|
+
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
202
|
+
}
|
|
187
203
|
|
|
188
204
|
function shouldShowUsage(env: EnvLike) {
|
|
189
205
|
return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
|
|
@@ -214,6 +230,10 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
233
|
+
if (isBuiltinNoloAgentRef(options.agentKey)) return false;
|
|
234
|
+
if (options.localRuntimeCwd) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
217
237
|
if (isKnownServerPlatformAgent(options)) {
|
|
218
238
|
options.output.write(
|
|
219
239
|
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
@@ -229,6 +249,13 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
|
|
|
229
249
|
} catch {
|
|
230
250
|
return false;
|
|
231
251
|
}
|
|
252
|
+
if (isMachineBoundLocalhostCustomProvider(agentConfig)) {
|
|
253
|
+
options.output.write(
|
|
254
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a machine-bound localhost custom provider. ` +
|
|
255
|
+
"Use --local explicitly to force the current machine.\n"
|
|
256
|
+
);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
232
259
|
const serverTools = resolveServerPlatformToolNames(agentConfig);
|
|
233
260
|
if (serverTools.length === 0) return false;
|
|
234
261
|
options.output.write(
|
|
@@ -250,7 +277,14 @@ function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
|
250
277
|
}
|
|
251
278
|
|
|
252
279
|
function shouldTraceLocalTools(options: RunAgentTurnOptions) {
|
|
253
|
-
|
|
280
|
+
const setting = (options.env.NOLO_TRACE_TOOLS ?? "").trim().toLowerCase();
|
|
281
|
+
if (setting === "0" || setting === "false" || setting === "off") return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveAgentEventMode(options: RunAgentTurnOptions): "text" | "jsonl" {
|
|
286
|
+
if (options.eventsMode === "jsonl") return "jsonl";
|
|
287
|
+
return options.env.NOLO_AGENT_EVENTS === "jsonl" ? "jsonl" : "text";
|
|
254
288
|
}
|
|
255
289
|
|
|
256
290
|
function shouldCollectLocalToolEvidence(options: RunAgentTurnOptions) {
|
|
@@ -279,15 +313,6 @@ function summarizeLocalToolEvidence(events: LocalAgentToolEvent[]): TaskRunToolE
|
|
|
279
313
|
};
|
|
280
314
|
}
|
|
281
315
|
|
|
282
|
-
const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 512;
|
|
283
|
-
|
|
284
|
-
function parseLocalToolRoundLimitError(error: unknown) {
|
|
285
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
286
|
-
const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
|
|
287
|
-
if (!match) return null;
|
|
288
|
-
return Number(match[1]);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
316
|
function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
|
|
292
317
|
return Boolean(
|
|
293
318
|
error &&
|
|
@@ -298,37 +323,44 @@ function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
|
|
|
298
323
|
);
|
|
299
324
|
}
|
|
300
325
|
|
|
301
|
-
function parsePositiveInteger(value: string | undefined) {
|
|
302
|
-
if (!value) return null;
|
|
303
|
-
const parsed = Number(value);
|
|
304
|
-
if (!Number.isInteger(parsed) || parsed <= 0) return null;
|
|
305
|
-
return parsed;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
|
|
309
|
-
return (
|
|
310
|
-
parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
|
|
311
|
-
DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
|
|
312
|
-
);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function nextLocalToolRoundLimit(current: number) {
|
|
316
|
-
return current <= 0 ? 1 : current * 2;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
326
|
function formatToolTraceEvent(event: LocalAgentToolEvent) {
|
|
320
327
|
const round = event.round + 1;
|
|
328
|
+
const detail = event.argumentsPreview ? ` ${event.argumentsPreview}` : "";
|
|
321
329
|
if (event.type === "tool-call") {
|
|
322
|
-
return `[nolo:tool]
|
|
330
|
+
return `[nolo:tool] #${round} -> ${event.toolName}${detail}\n`;
|
|
323
331
|
}
|
|
324
332
|
if (event.type === "tool-error") {
|
|
325
|
-
|
|
333
|
+
const elapsed = typeof event.elapsedMs === "number" ? ` ${event.elapsedMs}ms` : "";
|
|
334
|
+
return `[nolo:tool] #${round} !! ${event.toolName}${elapsed}: ${event.message ?? "failed"}\n`;
|
|
326
335
|
}
|
|
327
|
-
|
|
336
|
+
const elapsed = typeof event.elapsedMs === "number" ? ` ${event.elapsedMs}ms` : "";
|
|
337
|
+
const summary = event.summary ? ` ${event.summary}` : "";
|
|
338
|
+
return `[nolo:tool] #${round} <- ${event.toolName}${elapsed}${summary}\n`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function formatToolJsonEvent(event: LocalAgentToolEvent) {
|
|
342
|
+
return `${JSON.stringify({
|
|
343
|
+
schemaVersion: 1,
|
|
344
|
+
type: event.type,
|
|
345
|
+
round: event.round + 1,
|
|
346
|
+
tool: event.toolName,
|
|
347
|
+
toolCallId: event.toolCallId,
|
|
348
|
+
...(event.argumentsPreview ? { argsPreview: event.argumentsPreview } : {}),
|
|
349
|
+
...(typeof event.elapsedMs === "number" ? { elapsedMs: event.elapsedMs } : {}),
|
|
350
|
+
...(event.summary ? { summary: event.summary } : {}),
|
|
351
|
+
...(event.message ? { message: event.message } : {}),
|
|
352
|
+
})}\n`;
|
|
328
353
|
}
|
|
329
354
|
|
|
330
355
|
function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
331
356
|
if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
|
|
357
|
+
if (
|
|
358
|
+
options.env.NOLO_DISABLE_CLI_WORKSPACE_TOOLS !== "1" &&
|
|
359
|
+
isBuiltinNoloAgentRef(options.agentKey) &&
|
|
360
|
+
resolveAuthToken(options.env)
|
|
361
|
+
) {
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
332
364
|
return Boolean(
|
|
333
365
|
options.env.NOLO_LOCAL_OPENAI_API_KEY ||
|
|
334
366
|
options.env.OPENAI_API_KEY ||
|
|
@@ -594,7 +626,6 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
594
626
|
: {}),
|
|
595
627
|
...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
|
|
596
628
|
...(options.background ? { background: true } : {}),
|
|
597
|
-
...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
|
|
598
629
|
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
599
630
|
stream: !options.noStream && !options.background,
|
|
600
631
|
}),
|
|
@@ -632,8 +663,21 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
632
663
|
|
|
633
664
|
if (!res.ok) {
|
|
634
665
|
options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
|
|
635
|
-
|
|
636
|
-
|
|
666
|
+
const errorText = typeof data?.error === "string" ? data.error.trim() : "";
|
|
667
|
+
const messageText = typeof data?.message === "string" ? data.message.trim() : "";
|
|
668
|
+
const reasonText = typeof data?.reason === "string" ? data.reason.trim() : "";
|
|
669
|
+
const codeText = typeof data?.code === "string" ? data.code.trim() : "";
|
|
670
|
+
if (errorText || messageText) {
|
|
671
|
+
options.output.write(`${errorText || messageText}\n`);
|
|
672
|
+
if (messageText && messageText !== errorText) {
|
|
673
|
+
options.output.write(`${messageText}\n`);
|
|
674
|
+
}
|
|
675
|
+
if (codeText && codeText !== errorText && codeText !== messageText) {
|
|
676
|
+
options.output.write(`code=${codeText}\n`);
|
|
677
|
+
}
|
|
678
|
+
if (reasonText && reasonText !== errorText && reasonText !== messageText) {
|
|
679
|
+
options.output.write(`reason=${reasonText}\n`);
|
|
680
|
+
}
|
|
637
681
|
}
|
|
638
682
|
await recordTaskRunOutcome({
|
|
639
683
|
options,
|
|
@@ -669,7 +713,7 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
669
713
|
}
|
|
670
714
|
|
|
671
715
|
async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
|
|
672
|
-
return
|
|
716
|
+
return runLocalAgentTurnForCli(options, { reportFailure: true });
|
|
673
717
|
}
|
|
674
718
|
|
|
675
719
|
async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
|
|
@@ -679,53 +723,6 @@ async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
|
|
|
679
723
|
return Boolean(agentConfig);
|
|
680
724
|
}
|
|
681
725
|
|
|
682
|
-
async function runLocalAgentTurnWithAutoRoundExpansion(
|
|
683
|
-
options: RunAgentTurnOptions,
|
|
684
|
-
settings: { reportFailure: boolean }
|
|
685
|
-
) {
|
|
686
|
-
const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
|
|
687
|
-
let currentMaxToolRounds = options.maxToolRounds;
|
|
688
|
-
let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
|
|
689
|
-
|
|
690
|
-
while (true) {
|
|
691
|
-
const attemptOptions = {
|
|
692
|
-
...options,
|
|
693
|
-
...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
|
|
694
|
-
};
|
|
695
|
-
const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
|
|
696
|
-
lastResult = result;
|
|
697
|
-
if (result.exitCode === 0) return result;
|
|
698
|
-
|
|
699
|
-
const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
|
|
700
|
-
if (exhaustedRounds === null) break;
|
|
701
|
-
|
|
702
|
-
const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
|
|
703
|
-
if (nextMaxToolRounds > maxRetryLimit) break;
|
|
704
|
-
|
|
705
|
-
options.output.write(
|
|
706
|
-
`[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
|
|
707
|
-
);
|
|
708
|
-
currentMaxToolRounds = nextMaxToolRounds;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (settings.reportFailure && lastResult?.localError) {
|
|
712
|
-
options.output.write(
|
|
713
|
-
`[nolo] Local agent run failed: ${
|
|
714
|
-
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
|
|
715
|
-
}\n`
|
|
716
|
-
);
|
|
717
|
-
await recordTaskRunOutcome({
|
|
718
|
-
options,
|
|
719
|
-
status: "failed",
|
|
720
|
-
errorCode: "LOCAL_AGENT_FAILED",
|
|
721
|
-
resultSummary:
|
|
722
|
-
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
|
|
723
|
-
toolEvidence: lastResult.toolEvidence,
|
|
724
|
-
});
|
|
725
|
-
}
|
|
726
|
-
return lastResult ?? { exitCode: 1 };
|
|
727
|
-
}
|
|
728
|
-
|
|
729
726
|
async function runLocalAgentTurnForCli(
|
|
730
727
|
options: RunAgentTurnOptions,
|
|
731
728
|
settings: { reportFailure: boolean }
|
|
@@ -742,6 +739,7 @@ async function runLocalAgentTurnForCli(
|
|
|
742
739
|
try {
|
|
743
740
|
const traceLocalTools = shouldTraceLocalTools(options);
|
|
744
741
|
const collectToolEvidence = shouldCollectLocalToolEvidence(options);
|
|
742
|
+
const eventMode = resolveAgentEventMode(options);
|
|
745
743
|
const result = await runLocalAgentTurn({
|
|
746
744
|
adapter,
|
|
747
745
|
agentRef: options.agentKey,
|
|
@@ -756,7 +754,6 @@ async function runLocalAgentTurnForCli(
|
|
|
756
754
|
parentDialogId: options.parentDialogId,
|
|
757
755
|
background: options.background,
|
|
758
756
|
noStream: options.noStream,
|
|
759
|
-
maxToolRounds: options.maxToolRounds,
|
|
760
757
|
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
761
758
|
...(traceLocalTools || collectToolEvidence
|
|
762
759
|
? {
|
|
@@ -764,7 +761,11 @@ async function runLocalAgentTurnForCli(
|
|
|
764
761
|
if (collectToolEvidence) toolEvents.push(event);
|
|
765
762
|
if (traceLocalTools) {
|
|
766
763
|
spinner.stop();
|
|
767
|
-
options.output.write(
|
|
764
|
+
options.output.write(
|
|
765
|
+
eventMode === "jsonl"
|
|
766
|
+
? formatToolJsonEvent(event)
|
|
767
|
+
: formatToolTraceEvent(event)
|
|
768
|
+
);
|
|
768
769
|
}
|
|
769
770
|
},
|
|
770
771
|
}
|
|
@@ -920,7 +921,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
920
921
|
if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
|
|
921
922
|
const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
|
|
922
923
|
if (!skipLocal) {
|
|
923
|
-
const localResult = await
|
|
924
|
+
const localResult = await runLocalAgentTurnForCli(options, { reportFailure: false });
|
|
924
925
|
if (localResult.exitCode === 0) {
|
|
925
926
|
return {
|
|
926
927
|
exitCode: localResult.exitCode,
|
|
@@ -934,7 +935,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
934
935
|
try {
|
|
935
936
|
const refreshed = await refreshMissingLocalAgentConfig(options);
|
|
936
937
|
if (refreshed) {
|
|
937
|
-
const retriedLocalResult = await
|
|
938
|
+
const retriedLocalResult = await runLocalAgentTurnForCli(options, {
|
|
938
939
|
reportFailure: false,
|
|
939
940
|
});
|
|
940
941
|
if (retriedLocalResult.exitCode === 0) {
|