nolo-cli 0.1.38 → 0.1.39
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/agent-runtime/agentRecordConfig.ts +0 -14
- package/agent-runtime/hostAdapter.ts +0 -1
- package/agent-runtime/index.ts +24 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localToolPolicy.ts +2 -7
- package/agent-runtime/localWorkspaceTools.ts +9 -0
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +1 -3
- package/agent-runtime/types.ts +1 -1
- package/agentAliases.ts +6 -0
- package/agentRunCommand.ts +57 -65
- package/agentRuntimeLocal.ts +0 -7
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +6 -0
- package/cli/agentRunCommand.ts +57 -65
- package/cli/agentRuntimeLocal.ts +0 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +55 -7
- package/cli/client/localRuntimeAdapter.ts +87 -59
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +29 -37
- 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 +2 -3
- package/client/agentRun.test.ts +185 -1
- package/client/agentRun.ts +55 -7
- package/client/localRuntimeAdapter.test.ts +90 -25
- package/client/localRuntimeAdapter.ts +87 -59
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- 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 +29 -37
- package/package.json +10 -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
|
};
|
|
@@ -63,7 +55,7 @@ type ParsedAgentRunArgs = {
|
|
|
63
55
|
maxToolRounds?: number;
|
|
64
56
|
timeoutMs?: number;
|
|
65
57
|
traceTools: boolean;
|
|
66
|
-
|
|
58
|
+
injectFeatureWorktreeInstruction: boolean;
|
|
67
59
|
taskRunContext?: TaskRunPromptContext;
|
|
68
60
|
workflowRef?: string;
|
|
69
61
|
};
|
|
@@ -102,6 +94,21 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
|
|
|
102
94
|
return undefined;
|
|
103
95
|
}
|
|
104
96
|
|
|
97
|
+
function isMonthlyMimoAgentRef(raw: string | undefined, resolved: string) {
|
|
98
|
+
const normalized = raw?.trim().toLowerCase();
|
|
99
|
+
return (
|
|
100
|
+
resolved === MIMO_MONTH_AGENT_KEY ||
|
|
101
|
+
normalized === "fullstack" ||
|
|
102
|
+
normalized === "full-stack" ||
|
|
103
|
+
normalized === "nolo-fullstack" ||
|
|
104
|
+
normalized === "包月mimo" ||
|
|
105
|
+
normalized === "包月mimo2.5" ||
|
|
106
|
+
normalized === "mimo-month" ||
|
|
107
|
+
normalized === "全栈" ||
|
|
108
|
+
normalized === "nolo 全栈工程师"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
105
112
|
function parseNonNegativeInteger(value: string | undefined) {
|
|
106
113
|
if (!value) return undefined;
|
|
107
114
|
const parsed = Number(value);
|
|
@@ -123,7 +130,6 @@ function positionalArgs(args: string[]) {
|
|
|
123
130
|
"--server",
|
|
124
131
|
"--auto",
|
|
125
132
|
"--dangerously-allow-shell",
|
|
126
|
-
"--worktree-task",
|
|
127
133
|
"--trace-tools",
|
|
128
134
|
"--bg",
|
|
129
135
|
"--no-stream",
|
|
@@ -146,7 +152,8 @@ export function parseAgentRunArgs(
|
|
|
146
152
|
args: string[],
|
|
147
153
|
options: ParseAgentRunArgsOptions = {}
|
|
148
154
|
): ParsedAgentRunArgs | null {
|
|
149
|
-
const
|
|
155
|
+
const rawAgentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
|
|
156
|
+
const agentKey = rawAgentKey ? resolveCliAgentKeyInput(rawAgentKey) : undefined;
|
|
150
157
|
const explicitMsg = readFlagValue(args, "--msg");
|
|
151
158
|
const msgFile = readFlagValue(args, "--msg-file");
|
|
152
159
|
const fileMessage = msgFile
|
|
@@ -178,7 +185,8 @@ export function parseAgentRunArgs(
|
|
|
178
185
|
...readRepeatedFlagValues(args, "--image"),
|
|
179
186
|
...readRepeatedFlagValues(args, "--image-url"),
|
|
180
187
|
];
|
|
181
|
-
const
|
|
188
|
+
const mimoLocalWorkspaceDefault =
|
|
189
|
+
isMonthlyMimoAgentRef(rawAgentKey, agentKey) && runtimeMode !== "server";
|
|
182
190
|
const workflowRef = readFlagValue(args, "--workflow");
|
|
183
191
|
const taskRowDbKey = readFlagValue(args, "--task-row-dbkey");
|
|
184
192
|
const artifactIds = readFlagValue(args, "--artifact-ids")
|
|
@@ -186,12 +194,12 @@ export function parseAgentRunArgs(
|
|
|
186
194
|
.map((item) => item.trim())
|
|
187
195
|
.filter(Boolean);
|
|
188
196
|
return {
|
|
189
|
-
agentKey
|
|
197
|
+
agentKey,
|
|
190
198
|
message,
|
|
191
199
|
imageUrls,
|
|
192
|
-
allowShell: args.includes("--dangerously-allow-shell"),
|
|
200
|
+
allowShell: args.includes("--dangerously-allow-shell") || mimoLocalWorkspaceDefault,
|
|
193
201
|
traceTools: args.includes("--trace-tools"),
|
|
194
|
-
|
|
202
|
+
injectFeatureWorktreeInstruction: mimoLocalWorkspaceDefault,
|
|
195
203
|
...(workflowRef ? { workflowRef } : {}),
|
|
196
204
|
...(taskRowDbKey
|
|
197
205
|
? {
|
|
@@ -203,7 +211,11 @@ export function parseAgentRunArgs(
|
|
|
203
211
|
},
|
|
204
212
|
}
|
|
205
213
|
: {}),
|
|
206
|
-
...(runtimeMode
|
|
214
|
+
...(runtimeMode
|
|
215
|
+
? { runtimeMode }
|
|
216
|
+
: mimoLocalWorkspaceDefault || args.includes("--dangerously-allow-shell")
|
|
217
|
+
? { runtimeMode: "local" as const }
|
|
218
|
+
: {}),
|
|
207
219
|
background: args.includes("--bg"),
|
|
208
220
|
noStream: args.includes("--no-stream"),
|
|
209
221
|
noDefaultTestRoot,
|
|
@@ -269,6 +281,20 @@ export function prependWorkflowReferencePrompt(message: string, workflow?: Resol
|
|
|
269
281
|
].join("\n");
|
|
270
282
|
}
|
|
271
283
|
|
|
284
|
+
function prependFeatureWorktreeInstruction(message: string, enabled: boolean) {
|
|
285
|
+
if (!enabled) return message;
|
|
286
|
+
return [
|
|
287
|
+
"Local execution rule:",
|
|
288
|
+
"- You are running in the current git checkout with shell access.",
|
|
289
|
+
"- For read-only checks, smoke tests, or answering questions, stay in the current directory.",
|
|
290
|
+
"- 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.",
|
|
291
|
+
"- Commit and push only when the user explicitly asks or the task requires it.",
|
|
292
|
+
"",
|
|
293
|
+
"User task:",
|
|
294
|
+
message,
|
|
295
|
+
].join("\n");
|
|
296
|
+
}
|
|
297
|
+
|
|
272
298
|
function parseDialogReference(rawInput: string) {
|
|
273
299
|
const normalized = rawInput.trim();
|
|
274
300
|
if (normalized.startsWith("dialog-")) {
|
|
@@ -381,19 +407,13 @@ function resolveServerUrl(env: EnvLike) {
|
|
|
381
407
|
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
382
408
|
}
|
|
383
409
|
|
|
384
|
-
function
|
|
385
|
-
return !parsed.cwd && (parsed.useTaskWorktree || parsed.allowShell);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function buildTaskWorktreeEnv(args: {
|
|
410
|
+
function buildLocalRunEnv(args: {
|
|
389
411
|
env: EnvLike;
|
|
390
|
-
worktreePath?: string;
|
|
391
412
|
allowShell: boolean;
|
|
392
413
|
}) {
|
|
393
414
|
return {
|
|
394
415
|
...args.env,
|
|
395
416
|
...(args.allowShell ? { NOLO_LOCAL_SHELL_MODE: "worktree" } : {}),
|
|
396
|
-
...(args.worktreePath ? { NOLO_LOCAL_WORKTREE: args.worktreePath } : {}),
|
|
397
417
|
};
|
|
398
418
|
}
|
|
399
419
|
|
|
@@ -426,52 +446,24 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
|
|
|
426
446
|
}
|
|
427
447
|
}
|
|
428
448
|
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
|
-
}
|
|
449
|
+
if (!localRuntimeCwd && parsed.runtimeMode === "local") {
|
|
450
|
+
localRuntimeCwd = process.cwd();
|
|
445
451
|
}
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const runEnv = shouldBindLocalWorkspace
|
|
464
|
-
? buildTaskWorktreeEnv({
|
|
465
|
-
env,
|
|
466
|
-
worktreePath: localRuntimeCwd,
|
|
467
|
-
allowShell: parsed.allowShell,
|
|
468
|
-
})
|
|
469
|
-
: env;
|
|
452
|
+
const runEnv = buildLocalRunEnv({
|
|
453
|
+
env,
|
|
454
|
+
allowShell: parsed.allowShell,
|
|
455
|
+
});
|
|
470
456
|
const result: RunAgentTurnResult = await runner({
|
|
471
457
|
agentName: parsed.agentKey,
|
|
472
458
|
agentKey: parsed.agentKey,
|
|
473
459
|
serverUrl: resolveServerUrl(env),
|
|
474
|
-
message: prependWorkflowReferencePrompt(
|
|
460
|
+
message: prependWorkflowReferencePrompt(
|
|
461
|
+
prependFeatureWorktreeInstruction(
|
|
462
|
+
parsed.message,
|
|
463
|
+
parsed.injectFeatureWorktreeInstruction
|
|
464
|
+
),
|
|
465
|
+
workflowReference
|
|
466
|
+
),
|
|
475
467
|
imageUrls: parsed.imageUrls.map(normalizeCliImageInput),
|
|
476
468
|
scriptDir: deps.scriptDir,
|
|
477
469
|
env: runEnv,
|
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,
|
|
@@ -44,8 +41,4 @@ export type {
|
|
|
44
41
|
HybridRecordKvDb,
|
|
45
42
|
HybridRecordStore,
|
|
46
43
|
LocalToolPolicyDecision,
|
|
47
|
-
PreparedTaskWorkspace,
|
|
48
|
-
PrepareTaskWorkspace,
|
|
49
|
-
WorkspaceSession,
|
|
50
|
-
WorkspaceSessionMode,
|
|
51
44
|
} 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
|
|
|
@@ -180,10 +180,27 @@ function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
|
|
|
180
180
|
const normalizedKey = normalizeAgentRef(options.agentKey);
|
|
181
181
|
return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
|
|
182
182
|
}
|
|
183
|
-
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
|
|
184
|
+
function isMachineBoundLocalhostCustomProvider(agentConfig: any) {
|
|
185
|
+
const machineId =
|
|
186
|
+
agentConfig?.runtimeBinding && typeof agentConfig.runtimeBinding === "object"
|
|
187
|
+
? String(agentConfig.runtimeBinding.machineId ?? "").trim()
|
|
188
|
+
: "";
|
|
189
|
+
const providerUrl = typeof agentConfig?.customProviderUrl === "string"
|
|
190
|
+
? agentConfig.customProviderUrl.trim()
|
|
191
|
+
: "";
|
|
192
|
+
if (!machineId || !providerUrl) return false;
|
|
193
|
+
try {
|
|
194
|
+
const hostname = new URL(providerUrl).hostname.toLowerCase();
|
|
195
|
+
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function resolveAuthToken(env: EnvLike) {
|
|
202
|
+
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
203
|
+
}
|
|
187
204
|
|
|
188
205
|
function shouldShowUsage(env: EnvLike) {
|
|
189
206
|
return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
|
|
@@ -214,6 +231,10 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
234
|
+
if (isBuiltinNoloAgentRef(options.agentKey)) return false;
|
|
235
|
+
if (options.localRuntimeCwd && options.env.NOLO_LOCAL_SHELL_MODE === "worktree") {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
217
238
|
if (isKnownServerPlatformAgent(options)) {
|
|
218
239
|
options.output.write(
|
|
219
240
|
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
@@ -229,6 +250,13 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
|
|
|
229
250
|
} catch {
|
|
230
251
|
return false;
|
|
231
252
|
}
|
|
253
|
+
if (isMachineBoundLocalhostCustomProvider(agentConfig)) {
|
|
254
|
+
options.output.write(
|
|
255
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a machine-bound localhost custom provider. ` +
|
|
256
|
+
"Use --local explicitly to force the current machine.\n"
|
|
257
|
+
);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
232
260
|
const serverTools = resolveServerPlatformToolNames(agentConfig);
|
|
233
261
|
if (serverTools.length === 0) return false;
|
|
234
262
|
options.output.write(
|
|
@@ -329,6 +357,13 @@ function formatToolTraceEvent(event: LocalAgentToolEvent) {
|
|
|
329
357
|
|
|
330
358
|
function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
331
359
|
if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
|
|
360
|
+
if (
|
|
361
|
+
options.env.NOLO_DISABLE_CLI_WORKSPACE_TOOLS !== "1" &&
|
|
362
|
+
isBuiltinNoloAgentRef(options.agentKey) &&
|
|
363
|
+
resolveAuthToken(options.env)
|
|
364
|
+
) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
332
367
|
return Boolean(
|
|
333
368
|
options.env.NOLO_LOCAL_OPENAI_API_KEY ||
|
|
334
369
|
options.env.OPENAI_API_KEY ||
|
|
@@ -632,8 +667,21 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
632
667
|
|
|
633
668
|
if (!res.ok) {
|
|
634
669
|
options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
|
|
635
|
-
|
|
636
|
-
|
|
670
|
+
const errorText = typeof data?.error === "string" ? data.error.trim() : "";
|
|
671
|
+
const messageText = typeof data?.message === "string" ? data.message.trim() : "";
|
|
672
|
+
const reasonText = typeof data?.reason === "string" ? data.reason.trim() : "";
|
|
673
|
+
const codeText = typeof data?.code === "string" ? data.code.trim() : "";
|
|
674
|
+
if (errorText || messageText) {
|
|
675
|
+
options.output.write(`${errorText || messageText}\n`);
|
|
676
|
+
if (messageText && messageText !== errorText) {
|
|
677
|
+
options.output.write(`${messageText}\n`);
|
|
678
|
+
}
|
|
679
|
+
if (codeText && codeText !== errorText && codeText !== messageText) {
|
|
680
|
+
options.output.write(`code=${codeText}\n`);
|
|
681
|
+
}
|
|
682
|
+
if (reasonText && reasonText !== errorText && reasonText !== messageText) {
|
|
683
|
+
options.output.write(`reason=${reasonText}\n`);
|
|
684
|
+
}
|
|
637
685
|
}
|
|
638
686
|
await recordTaskRunOutcome({
|
|
639
687
|
options,
|
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
} from "../agentRuntimeLocal";
|
|
6
6
|
import { request as httpRequest } from "node:http";
|
|
7
7
|
import { request as httpsRequest } from "node:https";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
8
10
|
import {
|
|
9
11
|
buildLocalWorkspacePolicyToolNames,
|
|
10
12
|
buildLocalWorkspaceToolset,
|
|
@@ -40,19 +42,28 @@ import {
|
|
|
40
42
|
} from "./hybridRecordStore";
|
|
41
43
|
import { executeLocalToolWithPolicy } from "./localToolPolicy";
|
|
42
44
|
import { taskRunFunctionSchema } from "../../ai/tools/agent/taskRunTool";
|
|
43
|
-
import { prepareTaskWorktree } from "./taskWorktree";
|
|
44
45
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
NOLO_DEFAULT_AGENT_ID,
|
|
47
|
+
NOLO_DEFAULT_AGENT_KEY,
|
|
48
|
+
} from "../agentAliases";
|
|
49
|
+
import {
|
|
50
|
+
buildNoloWorkspaceCliToolExecutors,
|
|
51
|
+
buildNoloWorkspaceOpenAiTools,
|
|
52
|
+
filterNoloWorkspaceToolNames,
|
|
53
|
+
NOLO_WORKSPACE_TOOL_NAMES,
|
|
54
|
+
NOLO_WORKSPACE_TOOL_PROMPT,
|
|
55
|
+
parseNoloWorkspaceToolArguments,
|
|
56
|
+
} from "../../agent-runtime/noloWorkspaceTools";
|
|
50
57
|
|
|
51
58
|
type EnvLike = Record<string, string | undefined>;
|
|
52
59
|
type FetchInput = Parameters<typeof fetch>[0];
|
|
53
60
|
type FetchInit = Parameters<typeof fetch>[1];
|
|
54
61
|
const TRANSIENT_FETCH_MAX_ATTEMPTS = 8;
|
|
55
62
|
const TRANSIENT_FETCH_RETRY_BASE_DELAY_MS = 250;
|
|
63
|
+
export const BUILTIN_NOLO_AGENT_ID = NOLO_DEFAULT_AGENT_ID;
|
|
64
|
+
export const BUILTIN_NOLO_AGENT_KEY = NOLO_DEFAULT_AGENT_KEY;
|
|
65
|
+
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
66
|
+
const CLI_ENTRYPOINT = join(CLI_DIR, "..", "index.ts");
|
|
56
67
|
|
|
57
68
|
export type CliLocalRuntimeDb = CliKvDb;
|
|
58
69
|
|
|
@@ -64,9 +75,7 @@ export type CliLocalRuntimeAdapterDeps = {
|
|
|
64
75
|
createId?: () => string;
|
|
65
76
|
fetchImpl?: typeof fetch;
|
|
66
77
|
cwd?: string;
|
|
67
|
-
useCwdAsTaskWorkspaceBase?: boolean;
|
|
68
78
|
output?: { write(chunk: string): unknown };
|
|
69
|
-
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
70
79
|
localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
|
|
71
80
|
sleep?: (ms: number) => Promise<void>;
|
|
72
81
|
loopbackRequest?: (input: FetchInput, init?: FetchInit) => Promise<Response>;
|
|
@@ -85,7 +94,7 @@ function resolveLocalUserId(env: EnvLike) {
|
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
function shellModeEnabled(env: EnvLike) {
|
|
88
|
-
return
|
|
97
|
+
return env.NOLO_LOCAL_SHELL_MODE === "worktree";
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
function parseLocalToolBudgets(env: EnvLike) {
|
|
@@ -246,6 +255,17 @@ function logLocalRuntimeDiagnostic(event: string, fields: Record<string, unknown
|
|
|
246
255
|
console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
|
|
247
256
|
}
|
|
248
257
|
|
|
258
|
+
function summarizeOpenAiToolNames(tools: Array<Record<string, unknown>>) {
|
|
259
|
+
return tools
|
|
260
|
+
.map((tool) => {
|
|
261
|
+
const fn = tool.function;
|
|
262
|
+
return fn && typeof fn === "object" && "name" in fn && typeof fn.name === "string"
|
|
263
|
+
? fn.name
|
|
264
|
+
: null;
|
|
265
|
+
})
|
|
266
|
+
.filter((name): name is string => Boolean(name));
|
|
267
|
+
}
|
|
268
|
+
|
|
249
269
|
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
250
270
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
251
271
|
return [
|
|
@@ -254,6 +274,7 @@ function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
|
254
274
|
exposeShellTools: toolset.exposeShellTools,
|
|
255
275
|
}),
|
|
256
276
|
...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
|
|
277
|
+
...buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }),
|
|
257
278
|
];
|
|
258
279
|
}
|
|
259
280
|
|
|
@@ -272,6 +293,7 @@ function buildLocalPolicyToolNames(args: { toolNames?: string[]; env: EnvLike })
|
|
|
272
293
|
exposeShellTools: shellModeEnabled(args.env),
|
|
273
294
|
}),
|
|
274
295
|
...((args.toolNames ?? []).includes("taskRun") ? ["taskRun"] : []),
|
|
296
|
+
...filterNoloWorkspaceToolNames(args.toolNames),
|
|
275
297
|
];
|
|
276
298
|
}
|
|
277
299
|
|
|
@@ -287,13 +309,44 @@ function buildServerPlatformOpenAiTools(args: { toolNames?: string[] }) {
|
|
|
287
309
|
];
|
|
288
310
|
}
|
|
289
311
|
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
312
|
+
function buildCliWorkspaceToolExecutors(args: {
|
|
313
|
+
env: EnvLike;
|
|
314
|
+
cliEntrypoint?: string;
|
|
315
|
+
}) {
|
|
316
|
+
return buildNoloWorkspaceCliToolExecutors({
|
|
317
|
+
cliEntrypoint: args.cliEntrypoint ?? CLI_ENTRYPOINT,
|
|
318
|
+
env: args.env,
|
|
319
|
+
metadataKind: "cliWorkspaceTool",
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function isBuiltinNoloAgentRef(ref: unknown) {
|
|
324
|
+
if (typeof ref !== "string") return false;
|
|
325
|
+
const normalized = ref.trim();
|
|
326
|
+
return (
|
|
327
|
+
normalized === BUILTIN_NOLO_AGENT_KEY ||
|
|
328
|
+
normalized === BUILTIN_NOLO_AGENT_ID ||
|
|
329
|
+
normalized.endsWith(`-${BUILTIN_NOLO_AGENT_ID}`)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isBuiltinNoloAgentConfig(agentConfig: AgentRuntimeAgentConfig | null | undefined) {
|
|
334
|
+
const key = agentConfig?.key || agentConfig?.rawRecord?.dbKey || agentConfig?.rawRecord?.agentKey;
|
|
335
|
+
const id = agentConfig?.rawRecord?.id;
|
|
336
|
+
return isBuiltinNoloAgentRef(key) || isBuiltinNoloAgentRef(id);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function withBuiltinNoloCliTools(agentConfig: AgentRuntimeAgentConfig | null) {
|
|
340
|
+
if (!agentConfig || !isBuiltinNoloAgentConfig(agentConfig)) return agentConfig;
|
|
341
|
+
const toolNames = [...new Set([...(agentConfig.toolNames ?? []), ...NOLO_WORKSPACE_TOOL_NAMES])];
|
|
342
|
+
return {
|
|
343
|
+
...agentConfig,
|
|
344
|
+
toolNames,
|
|
345
|
+
prompt: [
|
|
346
|
+
agentConfig.prompt?.trim() ?? "",
|
|
347
|
+
NOLO_WORKSPACE_TOOL_PROMPT,
|
|
348
|
+
].filter(Boolean).join("\n\n"),
|
|
349
|
+
};
|
|
297
350
|
}
|
|
298
351
|
|
|
299
352
|
function resolveRuntimeServerUrl(env: EnvLike) {
|
|
@@ -320,7 +373,7 @@ function buildServerPlatformToolExecutors(args: {
|
|
|
320
373
|
"Content-Type": "application/json",
|
|
321
374
|
Authorization: `Bearer ${authToken}`,
|
|
322
375
|
},
|
|
323
|
-
body: JSON.stringify(
|
|
376
|
+
body: JSON.stringify(parseNoloWorkspaceToolArguments(call.arguments)),
|
|
324
377
|
});
|
|
325
378
|
const text = await response.text().catch(() => "");
|
|
326
379
|
if (!response.ok) {
|
|
@@ -352,17 +405,13 @@ function buildLocalToolExecutors(args: {
|
|
|
352
405
|
env: args.env,
|
|
353
406
|
fetchImpl: args.fetchImpl,
|
|
354
407
|
}),
|
|
408
|
+
...buildCliWorkspaceToolExecutors({
|
|
409
|
+
env: args.env,
|
|
410
|
+
}),
|
|
355
411
|
...(args.localToolExecutors ?? {}),
|
|
356
412
|
};
|
|
357
413
|
}
|
|
358
414
|
|
|
359
|
-
function readLocalWorkspaceMode(agentConfig: AgentRuntimeAgentConfig | null) {
|
|
360
|
-
if (agentConfig?.localWorkspaceMode === "task-worktree") return "task-worktree";
|
|
361
|
-
const runtimeBinding = agentConfig?.runtimeBinding;
|
|
362
|
-
const value = runtimeBinding?.localWorkspaceMode ?? runtimeBinding?.workspaceMode;
|
|
363
|
-
return value === "task-worktree" ? "task-worktree" : "current";
|
|
364
|
-
}
|
|
365
|
-
|
|
366
415
|
async function resolveStore(deps: CliLocalRuntimeAdapterDeps) {
|
|
367
416
|
if (deps.store) return deps.store;
|
|
368
417
|
return createCliHybridRecordStore({
|
|
@@ -437,51 +486,25 @@ export function createCliLocalRuntimeAdapter(
|
|
|
437
486
|
const localToolBudgets = parseLocalToolBudgets(deps.env);
|
|
438
487
|
const localToolUsage = new Map<string, number>();
|
|
439
488
|
let activeAgentToolNames: string[] = [];
|
|
440
|
-
|
|
441
|
-
deps.useCwdAsTaskWorkspaceBase
|
|
442
|
-
? { defaultCwd: deps.cwd }
|
|
443
|
-
: { cwd: deps.cwd }
|
|
444
|
-
);
|
|
489
|
+
const workspaceRoot = deps.cwd ?? process.cwd();
|
|
445
490
|
let runtimeToolExecutionLimits: ReturnType<typeof resolveLocalWorkspaceExecutorOptionsFromPolicy> = {};
|
|
446
491
|
let localToolExecutors = buildLocalToolExecutors({
|
|
447
|
-
workspaceRoot
|
|
492
|
+
workspaceRoot,
|
|
448
493
|
env: deps.env,
|
|
449
494
|
fetchImpl,
|
|
450
495
|
localToolExecutors: deps.localToolExecutors,
|
|
451
496
|
...runtimeToolExecutionLimits,
|
|
452
497
|
});
|
|
453
498
|
|
|
454
|
-
async function activateAgentWorkspaceSession(args: {
|
|
455
|
-
agentRef: string;
|
|
456
|
-
agentConfig: AgentRuntimeAgentConfig | null;
|
|
457
|
-
}) {
|
|
458
|
-
const previousWorkspaceRoot = workspaceSession.workspaceRoot;
|
|
459
|
-
workspaceSession = await activateWorkspaceSession(workspaceSession, {
|
|
460
|
-
agentKey: args.agentConfig?.key ?? args.agentRef,
|
|
461
|
-
mode: readLocalWorkspaceMode(args.agentConfig),
|
|
462
|
-
env: deps.env,
|
|
463
|
-
prepareTaskWorkspace: deps.prepareTaskWorktree,
|
|
464
|
-
});
|
|
465
|
-
if (workspaceSession.workspaceRoot === previousWorkspaceRoot) return;
|
|
466
|
-
localToolExecutors = buildLocalToolExecutors({
|
|
467
|
-
workspaceRoot: workspaceSession.workspaceRoot,
|
|
468
|
-
env: deps.env,
|
|
469
|
-
fetchImpl,
|
|
470
|
-
localToolExecutors: deps.localToolExecutors,
|
|
471
|
-
...runtimeToolExecutionLimits,
|
|
472
|
-
});
|
|
473
|
-
deps.output?.write(formatWorkspaceSessionActivation(workspaceSession));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
499
|
return {
|
|
477
500
|
host: "cli",
|
|
478
501
|
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
|
|
479
502
|
loadAgentConfig: async (agentRef) => {
|
|
480
|
-
const agentConfig = await readAgentFromStore({
|
|
503
|
+
const agentConfig = withBuiltinNoloCliTools(await readAgentFromStore({
|
|
481
504
|
agentRef,
|
|
482
505
|
store: await resolveStore(deps),
|
|
483
506
|
userId,
|
|
484
|
-
});
|
|
507
|
+
}));
|
|
485
508
|
const requestedToolNames = agentConfig
|
|
486
509
|
? resolveRequestedRuntimeToolNames({ agentConfig })
|
|
487
510
|
: [];
|
|
@@ -492,9 +515,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
492
515
|
runtimeToolExecutionLimits = resolveLocalWorkspaceExecutorOptionsFromPolicy(
|
|
493
516
|
resolveCurrentRunRuntimeToolPolicy(agentConfig),
|
|
494
517
|
);
|
|
495
|
-
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
496
518
|
localToolExecutors = buildLocalToolExecutors({
|
|
497
|
-
workspaceRoot
|
|
519
|
+
workspaceRoot,
|
|
498
520
|
env: deps.env,
|
|
499
521
|
fetchImpl,
|
|
500
522
|
localToolExecutors: deps.localToolExecutors,
|
|
@@ -512,7 +534,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
512
534
|
userId,
|
|
513
535
|
now,
|
|
514
536
|
createId,
|
|
515
|
-
cwd:
|
|
537
|
+
cwd: workspaceRoot,
|
|
516
538
|
}),
|
|
517
539
|
resolveProvider: async (agentConfig) => {
|
|
518
540
|
if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
|
|
@@ -558,6 +580,9 @@ export function createCliLocalRuntimeAdapter(
|
|
|
558
580
|
model: providerConfig.model,
|
|
559
581
|
messageCount: messages.length,
|
|
560
582
|
toolCount: tools.length,
|
|
583
|
+
requestedToolNames,
|
|
584
|
+
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
585
|
+
shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
561
586
|
timeoutMs: options?.timeoutMs ?? null,
|
|
562
587
|
});
|
|
563
588
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
@@ -627,6 +652,9 @@ export function createCliLocalRuntimeAdapter(
|
|
|
627
652
|
model: providerConfig.model,
|
|
628
653
|
messageCount: messages.length,
|
|
629
654
|
toolCount: tools.length,
|
|
655
|
+
requestedToolNames,
|
|
656
|
+
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
657
|
+
shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
630
658
|
timeoutMs: options?.timeoutMs ?? null,
|
|
631
659
|
});
|
|
632
660
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
@@ -672,8 +700,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
672
700
|
...result,
|
|
673
701
|
metadata: {
|
|
674
702
|
...(result.metadata ?? {}),
|
|
675
|
-
workspaceRoot
|
|
676
|
-
workspaceKind:
|
|
703
|
+
workspaceRoot,
|
|
704
|
+
workspaceKind: "current",
|
|
677
705
|
},
|
|
678
706
|
};
|
|
679
707
|
},
|
|
@@ -32,7 +32,6 @@ export function resolveConnectorRunCwd(args: {
|
|
|
32
32
|
const explicit =
|
|
33
33
|
args.env?.NOLO_CONNECTOR_WORKDIR ||
|
|
34
34
|
args.env?.NOLO_AGENT_WORKDIR ||
|
|
35
|
-
args.env?.NOLO_LOCAL_WORKTREE ||
|
|
36
35
|
"";
|
|
37
36
|
const candidate = explicit || args.policy.writableRoots[0] || args.fallbackCwd || process.cwd();
|
|
38
37
|
const cwd = normalizePath(candidate);
|