nolo-cli 0.1.35 → 0.1.37
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 +11 -1
- package/agent-runtime/agentRecordConfig.ts +5 -1
- package/agent-runtime/index.ts +5 -0
- package/agent-runtime/localLoop.ts +1 -2
- package/agent-runtime/localToolPolicy.ts +8 -5
- package/agent-runtime/localWorkspaceTools.ts +68 -180
- package/agent-runtime/platformChatProvider.ts +17 -9
- package/agent-runtime/providerResolution.ts +71 -11
- package/agent-runtime/runtimeToolPolicy.ts +46 -6
- package/agent-runtime/taskWorkspace.ts +23 -23
- package/agent-runtime/types.ts +0 -1
- package/agentAliases.ts +50 -30
- package/agentRunCommand.ts +1 -1
- package/agentRuntimeLocal.ts +3 -0
- package/cli/agentAliases.ts +50 -30
- package/cli/agentRunCommand.ts +1 -1
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/client/agentRun.ts +15 -88
- package/cli/client/localRuntimeAdapter.ts +108 -3
- package/cli/machineWsRunDispatch.ts +45 -5
- package/client/agentRun.test.ts +2 -32
- package/client/agentRun.ts +15 -88
- package/client/localRuntimeAdapter.test.ts +64 -0
- package/client/localRuntimeAdapter.ts +108 -3
- package/machineWsRunDispatch.ts +45 -5
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -76,7 +76,8 @@ token directly:
|
|
|
76
76
|
nolo login --server https://nolo.chat --token <token>
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
Local repo development
|
|
79
|
+
Local repo development should use `nolo login` or an explicit `AUTH_TOKEN` for
|
|
80
|
+
agent runs.
|
|
80
81
|
|
|
81
82
|
Inside the TUI, `/update` is the shortcut for the same global `nolo update`
|
|
82
83
|
command.
|
|
@@ -207,6 +208,15 @@ commands instead of ad hoc scripts:
|
|
|
207
208
|
updates such as `--model`, `--cli-provider`, `--api-source`, and repeated
|
|
208
209
|
`--field key=value`.
|
|
209
210
|
|
|
211
|
+
Account ownership matters for private agent maintenance. The legacy
|
|
212
|
+
`b2e06f801f` / platform-demo account still owns older agents, task-board data,
|
|
213
|
+
and workspace records, while newer personal or machine-bound work may belong to
|
|
214
|
+
the current operator account. Use `nolo agent read`, `nolo agent update`, and
|
|
215
|
+
repo-native helpers as the normal entrypoints; only owner-only operations such
|
|
216
|
+
as private record updates/deletes should use the maintained platform-demo token
|
|
217
|
+
from `scripts/testUtils.ts` or a regenerated token from `scripts/generateToken.ts`.
|
|
218
|
+
Do not copy token strings into docs, logs, or prompts.
|
|
219
|
+
|
|
210
220
|
Inside the current TUI, examples of supported slash commands include:
|
|
211
221
|
`/agent`, `/agents`, `/switch`, `/context` (alias `/ctx`), `/dialog`, `/doc`,
|
|
212
222
|
`/help`, `/new`, `/customize`, `/login`, `/profile`, `/version`, `/quit`
|
|
@@ -33,6 +33,7 @@ function localWorkspaceModeField(record: AgentRecord): "current" | "task-worktre
|
|
|
33
33
|
|
|
34
34
|
function appendUniqueStrings(values: string[], next: unknown) {
|
|
35
35
|
if (!Array.isArray(next)) return values;
|
|
36
|
+
const seen = new Set(values);
|
|
36
37
|
for (const value of next) {
|
|
37
38
|
const toolName = typeof value === "string"
|
|
38
39
|
? value
|
|
@@ -41,7 +42,10 @@ function appendUniqueStrings(values: string[], next: unknown) {
|
|
|
41
42
|
: value && typeof value === "object" && typeof (value as any).function?.name === "string"
|
|
42
43
|
? (value as any).function.name
|
|
43
44
|
: "";
|
|
44
|
-
if (toolName && !
|
|
45
|
+
if (toolName && !seen.has(toolName)) {
|
|
46
|
+
seen.add(toolName);
|
|
47
|
+
values.push(toolName);
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
return values;
|
|
47
51
|
}
|
package/agent-runtime/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export {
|
|
|
14
14
|
buildProviderAuthHeaders,
|
|
15
15
|
buildProviderExecutionPlan,
|
|
16
16
|
resolveAgentProviderMode,
|
|
17
|
+
resolveAgentRuntimeLocation,
|
|
18
|
+
resolveProviderTransportDecision,
|
|
17
19
|
} from "./providerResolution";
|
|
18
20
|
export {
|
|
19
21
|
buildPlatformChatCompletionRequest,
|
|
@@ -57,8 +59,11 @@ export {
|
|
|
57
59
|
export {
|
|
58
60
|
mergeAgentRuntimeToolPolicies,
|
|
59
61
|
normalizeAgentRuntimeToolPolicy,
|
|
62
|
+
resolveCurrentRunRuntimeToolPolicy,
|
|
60
63
|
resolveRequestedRuntimeToolNames,
|
|
61
64
|
resolveEffectiveRuntimeToolPolicy,
|
|
65
|
+
resolveLocalRuntimeEnvFromPolicy,
|
|
66
|
+
resolveLocalWorkspaceExecutorOptionsFromPolicy,
|
|
62
67
|
} from "./runtimeToolPolicy";
|
|
63
68
|
export {
|
|
64
69
|
buildLocalWorkspaceOpenAiTools,
|
|
@@ -94,8 +94,7 @@ function extractUserInputText(content: AgentRuntimeMessageContent): string {
|
|
|
94
94
|
if (typeof content === "string") return content;
|
|
95
95
|
if (!Array.isArray(content)) return "";
|
|
96
96
|
return content
|
|
97
|
-
.
|
|
98
|
-
.filter(Boolean)
|
|
97
|
+
.flatMap((part) => (part?.type === "text" && part.text ? [part.text] : []))
|
|
99
98
|
.join("\n")
|
|
100
99
|
.trim();
|
|
101
100
|
}
|
|
@@ -38,16 +38,16 @@ const DEFAULT_LOCAL_TOOLS = new Set([
|
|
|
38
38
|
"editFile",
|
|
39
39
|
"searchFiles",
|
|
40
40
|
"searchWorkspace",
|
|
41
|
-
"applyPatch",
|
|
42
|
-
"runPackageScript",
|
|
43
41
|
"execShell",
|
|
44
42
|
]);
|
|
45
43
|
|
|
46
44
|
function parseToolAllowlist(value: string | undefined) {
|
|
47
45
|
return (value ?? "")
|
|
48
46
|
.split(",")
|
|
49
|
-
.
|
|
50
|
-
|
|
47
|
+
.flatMap((item) => {
|
|
48
|
+
const trimmed = item.trim();
|
|
49
|
+
return trimmed ? [trimmed] : [];
|
|
50
|
+
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function normalizeLocalToolName(toolName: string) {
|
|
@@ -129,7 +129,10 @@ export function resolveLocalToolPolicy(args: {
|
|
|
129
129
|
|
|
130
130
|
const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
|
|
131
131
|
const agentTools = new Set(
|
|
132
|
-
(args.agentToolNames ?? []).
|
|
132
|
+
(args.agentToolNames ?? []).flatMap((item) => {
|
|
133
|
+
const normalized = normalizeLocalToolName(item);
|
|
134
|
+
return normalized ? [normalized] : [];
|
|
135
|
+
}),
|
|
133
136
|
);
|
|
134
137
|
if (!restrictedMode && agentTools.has(toolName)) {
|
|
135
138
|
return { allowed: true, toolName };
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
type LocalWorkspaceToolArgs = {
|
|
10
10
|
workspaceRoot: string;
|
|
11
11
|
commandTimeoutMs?: number;
|
|
12
|
+
commandOutputLimit?: number;
|
|
12
13
|
commandPrefix?: string[];
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -21,9 +22,6 @@ type WorkspaceFileArgs = {
|
|
|
21
22
|
query?: unknown;
|
|
22
23
|
command?: unknown;
|
|
23
24
|
cmd?: unknown;
|
|
24
|
-
patch?: unknown;
|
|
25
|
-
script?: unknown;
|
|
26
|
-
args?: unknown;
|
|
27
25
|
branch?: unknown;
|
|
28
26
|
paths?: unknown;
|
|
29
27
|
message?: unknown;
|
|
@@ -56,8 +54,6 @@ const WORKSPACE_TOOL_NAMES = [
|
|
|
56
54
|
"searchWorkspace",
|
|
57
55
|
"applyEdit",
|
|
58
56
|
"applyLineEdits",
|
|
59
|
-
"applyPatch",
|
|
60
|
-
"runPackageScript",
|
|
61
57
|
"startPreview",
|
|
62
58
|
"getPreviewStatus",
|
|
63
59
|
"stopPreview",
|
|
@@ -84,7 +80,6 @@ const REMOVED_WORKSPACE_TOOL_NAMES = new Set([
|
|
|
84
80
|
"gitCommit",
|
|
85
81
|
"commitWorkspace",
|
|
86
82
|
]);
|
|
87
|
-
const DEFAULT_WORKSPACE_COMMAND_TIMEOUT_MS = 120_000;
|
|
88
83
|
|
|
89
84
|
function buildWorkspacePathProperty() {
|
|
90
85
|
return {
|
|
@@ -200,51 +195,6 @@ function buildSearchWorkspaceTool(): OpenAiCompatibleTool {
|
|
|
200
195
|
};
|
|
201
196
|
}
|
|
202
197
|
|
|
203
|
-
function buildApplyPatchTool(): OpenAiCompatibleTool {
|
|
204
|
-
return {
|
|
205
|
-
type: "function",
|
|
206
|
-
function: {
|
|
207
|
-
name: "applyPatch",
|
|
208
|
-
description: "Apply a unified diff patch to files inside the workspace.",
|
|
209
|
-
parameters: {
|
|
210
|
-
type: "object",
|
|
211
|
-
properties: {
|
|
212
|
-
patch: {
|
|
213
|
-
type: "string",
|
|
214
|
-
description: "Unified diff text accepted by git apply.",
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
required: ["patch"],
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function buildRunPackageScriptTool(): OpenAiCompatibleTool {
|
|
224
|
-
return {
|
|
225
|
-
type: "function",
|
|
226
|
-
function: {
|
|
227
|
-
name: "runPackageScript",
|
|
228
|
-
description: "Run a package script in the workspace, for example a test or lint script.",
|
|
229
|
-
parameters: {
|
|
230
|
-
type: "object",
|
|
231
|
-
properties: {
|
|
232
|
-
script: {
|
|
233
|
-
type: "string",
|
|
234
|
-
description: "Package script name to run.",
|
|
235
|
-
},
|
|
236
|
-
args: {
|
|
237
|
-
type: "array",
|
|
238
|
-
items: { type: "string" },
|
|
239
|
-
description: "Optional extra arguments passed after --.",
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
required: ["script"],
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
198
|
function buildPreviewLifecycleTool(toolName: string): OpenAiCompatibleTool {
|
|
249
199
|
const descriptions: Record<string, string> = {
|
|
250
200
|
startPreview: "Start the local preview stack for the current workspace.",
|
|
@@ -421,8 +371,6 @@ function buildWorkspaceToolDefinition(toolName: string) {
|
|
|
421
371
|
if (toolName === "searchWorkspace" || toolName === "searchFiles") {
|
|
422
372
|
return renameWorkspaceToolDefinition(buildSearchWorkspaceTool(), toolName);
|
|
423
373
|
}
|
|
424
|
-
if (toolName === "applyPatch") return buildApplyPatchTool();
|
|
425
|
-
if (toolName === "runPackageScript") return buildRunPackageScriptTool();
|
|
426
374
|
if (toolName === "startPreview" || toolName === "getPreviewStatus" || toolName === "stopPreview" || toolName === "releasePreview") {
|
|
427
375
|
return buildPreviewLifecycleTool(toolName);
|
|
428
376
|
}
|
|
@@ -585,12 +533,6 @@ function requireWorkspaceSearchQuery(args: WorkspaceFileArgs) {
|
|
|
585
533
|
return query;
|
|
586
534
|
}
|
|
587
535
|
|
|
588
|
-
function requireWorkspacePatch(args: WorkspaceFileArgs) {
|
|
589
|
-
const patch = typeof args.patch === "string" ? args.patch : "";
|
|
590
|
-
if (!patch.trim()) throw new Error("applyPatch requires a non-empty patch.");
|
|
591
|
-
return patch;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
536
|
function requireShellCommand(args: WorkspaceFileArgs, toolName: string) {
|
|
595
537
|
const command = typeof args.cmd === "string"
|
|
596
538
|
? args.cmd.trim()
|
|
@@ -601,24 +543,12 @@ function requireShellCommand(args: WorkspaceFileArgs, toolName: string) {
|
|
|
601
543
|
return command;
|
|
602
544
|
}
|
|
603
545
|
|
|
604
|
-
function requirePackageScript(args: WorkspaceFileArgs) {
|
|
605
|
-
const script = typeof args.script === "string" ? args.script.trim() : "";
|
|
606
|
-
if (!script) throw new Error("runPackageScript requires a non-empty script.");
|
|
607
|
-
return script;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
546
|
function requireVisualWaitSelector(args: WorkspaceFileArgs) {
|
|
611
547
|
const selector = typeof args.waitSelector === "string" ? args.waitSelector.trim() : "";
|
|
612
548
|
if (!selector) throw new Error("captureVisualState requires a non-empty waitSelector.");
|
|
613
549
|
return selector;
|
|
614
550
|
}
|
|
615
551
|
|
|
616
|
-
function readPackageScriptArgs(args: WorkspaceFileArgs) {
|
|
617
|
-
return Array.isArray(args.args)
|
|
618
|
-
? args.args.map((value) => String(value)).filter(Boolean)
|
|
619
|
-
: [];
|
|
620
|
-
}
|
|
621
|
-
|
|
622
552
|
function readOptionalStringArg(value: unknown) {
|
|
623
553
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
624
554
|
}
|
|
@@ -669,6 +599,51 @@ function formatAvailableScripts(scripts: string[]) {
|
|
|
669
599
|
return scripts.length ? scripts.join(", ") : "(none)";
|
|
670
600
|
}
|
|
671
601
|
|
|
602
|
+
async function runWorkspacePackageScript(args: {
|
|
603
|
+
workspaceRoot: string;
|
|
604
|
+
script: string;
|
|
605
|
+
extraArgs?: string[];
|
|
606
|
+
commandTimeoutMs?: number;
|
|
607
|
+
}): Promise<AgentRuntimeToolResult> {
|
|
608
|
+
const script = args.script.trim();
|
|
609
|
+
if (!script) throw new Error("package script name is required.");
|
|
610
|
+
const extraArgs = args.extraArgs ?? [];
|
|
611
|
+
const packageScripts = await readWorkspacePackageScripts(args.workspaceRoot);
|
|
612
|
+
if (!packageScripts.scripts.includes(script)) {
|
|
613
|
+
return {
|
|
614
|
+
content: [
|
|
615
|
+
`script not found: ${script}`,
|
|
616
|
+
`available scripts: ${formatAvailableScripts(packageScripts.scripts)}`,
|
|
617
|
+
...(packageScripts.error ? [`package.json read error: ${packageScripts.error}`] : []),
|
|
618
|
+
].join("\n"),
|
|
619
|
+
metadata: {
|
|
620
|
+
script,
|
|
621
|
+
exitCode: 1,
|
|
622
|
+
reason: "script-not-found",
|
|
623
|
+
availableScripts: packageScripts.scripts,
|
|
624
|
+
...(packageScripts.error ? { packageJsonError: packageScripts.error } : {}),
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
const result = await runWorkspaceCommand({
|
|
629
|
+
workspaceRoot: args.workspaceRoot,
|
|
630
|
+
command: ["bun", "run", script, ...(extraArgs.length ? ["--", ...extraArgs] : [])],
|
|
631
|
+
timeoutMs: args.commandTimeoutMs,
|
|
632
|
+
});
|
|
633
|
+
return {
|
|
634
|
+
content: result.content,
|
|
635
|
+
metadata: {
|
|
636
|
+
script,
|
|
637
|
+
args: extraArgs,
|
|
638
|
+
exitCode: result.exitCode,
|
|
639
|
+
reason: result.exitCode === 0 ? "ok" : "script-failed",
|
|
640
|
+
timedOut: result.timedOut,
|
|
641
|
+
stdoutTail: result.stdout.trim().slice(-4000),
|
|
642
|
+
stderrTail: result.stderr.trim().slice(-4000),
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
672
647
|
function truncateToolOutput(value: string, limit = 20_000) {
|
|
673
648
|
if (value.length <= limit) return value;
|
|
674
649
|
return `${value.slice(0, limit)}\n\n[truncated ${value.length - limit} chars]`;
|
|
@@ -712,9 +687,13 @@ async function runWorkspaceCommand(args: {
|
|
|
712
687
|
command: string[];
|
|
713
688
|
stdin?: string;
|
|
714
689
|
timeoutMs?: number;
|
|
690
|
+
outputLimit?: number;
|
|
715
691
|
commandPrefix?: string[];
|
|
716
692
|
}) {
|
|
717
|
-
const timeoutMs =
|
|
693
|
+
const timeoutMs =
|
|
694
|
+
typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs) && args.timeoutMs > 0
|
|
695
|
+
? args.timeoutMs
|
|
696
|
+
: undefined;
|
|
718
697
|
const detached = process.platform !== "win32";
|
|
719
698
|
const command = [
|
|
720
699
|
...(args.commandPrefix ?? []),
|
|
@@ -732,12 +711,14 @@ async function runWorkspaceCommand(args: {
|
|
|
732
711
|
const stderrPromise = new Response(proc.stderr).text();
|
|
733
712
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
734
713
|
const timeoutResult = Symbol("timeout");
|
|
735
|
-
const exitOrTimeout =
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
714
|
+
const exitOrTimeout = timeoutMs
|
|
715
|
+
? await Promise.race([
|
|
716
|
+
proc.exited,
|
|
717
|
+
new Promise<typeof timeoutResult>((resolveTimeout) => {
|
|
718
|
+
timeout = setTimeout(() => resolveTimeout(timeoutResult), timeoutMs);
|
|
719
|
+
}),
|
|
720
|
+
])
|
|
721
|
+
: await proc.exited;
|
|
741
722
|
if (timeout) clearTimeout(timeout);
|
|
742
723
|
const timedOut = exitOrTimeout === timeoutResult;
|
|
743
724
|
if (timedOut) {
|
|
@@ -769,7 +750,7 @@ async function runWorkspaceCommand(args: {
|
|
|
769
750
|
]);
|
|
770
751
|
const exitCode = timedOut ? 124 : Number(exitOrTimeout);
|
|
771
752
|
const stderr = timedOut
|
|
772
|
-
? `${rawStderr.trim() ? `${rawStderr.trim()}\n` : ""}command timed out after ${timeoutMs}ms\n`
|
|
753
|
+
? `${rawStderr.trim() ? `${rawStderr.trim()}\n` : ""}command timed out after ${timeoutMs ?? "unknown"}ms\n`
|
|
773
754
|
: rawStderr;
|
|
774
755
|
return {
|
|
775
756
|
stdout,
|
|
@@ -780,7 +761,7 @@ async function runWorkspaceCommand(args: {
|
|
|
780
761
|
stdout.trim() ? `stdout:\n${stdout.trim()}` : "",
|
|
781
762
|
stderr.trim() ? `stderr:\n${stderr.trim()}` : "",
|
|
782
763
|
`exitCode: ${exitCode}`,
|
|
783
|
-
].filter(Boolean).join("\n\n")),
|
|
764
|
+
].filter(Boolean).join("\n\n"), args.outputLimit),
|
|
784
765
|
};
|
|
785
766
|
}
|
|
786
767
|
|
|
@@ -983,82 +964,6 @@ async function searchWorkspaceTool(args: {
|
|
|
983
964
|
};
|
|
984
965
|
}
|
|
985
966
|
|
|
986
|
-
async function applyPatchTool(args: {
|
|
987
|
-
call: AgentRuntimeToolCallInput;
|
|
988
|
-
workspaceRoot: string;
|
|
989
|
-
}): Promise<AgentRuntimeToolResult> {
|
|
990
|
-
const parsed = parseWorkspaceToolArguments(args.call.arguments);
|
|
991
|
-
const patch = requireWorkspacePatch(parsed);
|
|
992
|
-
const checkResult = await runWorkspaceCommand({
|
|
993
|
-
workspaceRoot: args.workspaceRoot,
|
|
994
|
-
command: ["git", "apply", "--check", "--whitespace=nowarn", "-"],
|
|
995
|
-
stdin: patch,
|
|
996
|
-
});
|
|
997
|
-
if (checkResult.exitCode !== 0) {
|
|
998
|
-
throw new Error(
|
|
999
|
-
[
|
|
1000
|
-
"applyPatch failed during git apply --check.",
|
|
1001
|
-
"Use editFile/applyEdit for exact single-file edits when possible.",
|
|
1002
|
-
checkResult.content,
|
|
1003
|
-
].join("\n\n")
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
const result = await runWorkspaceCommand({
|
|
1007
|
-
workspaceRoot: args.workspaceRoot,
|
|
1008
|
-
command: ["git", "apply", "--whitespace=nowarn", "-"],
|
|
1009
|
-
stdin: patch,
|
|
1010
|
-
});
|
|
1011
|
-
if (result.exitCode !== 0) throw new Error(result.content);
|
|
1012
|
-
return {
|
|
1013
|
-
content: "patch applied",
|
|
1014
|
-
metadata: { bytes: Buffer.byteLength(patch) },
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
async function runPackageScriptTool(args: {
|
|
1019
|
-
call: AgentRuntimeToolCallInput;
|
|
1020
|
-
workspaceRoot: string;
|
|
1021
|
-
commandTimeoutMs?: number;
|
|
1022
|
-
}): Promise<AgentRuntimeToolResult> {
|
|
1023
|
-
const parsed = parseWorkspaceToolArguments(args.call.arguments);
|
|
1024
|
-
const script = requirePackageScript(parsed);
|
|
1025
|
-
const extraArgs = readPackageScriptArgs(parsed);
|
|
1026
|
-
const packageScripts = await readWorkspacePackageScripts(args.workspaceRoot);
|
|
1027
|
-
if (!packageScripts.scripts.includes(script)) {
|
|
1028
|
-
return {
|
|
1029
|
-
content: [
|
|
1030
|
-
`script not found: ${script}`,
|
|
1031
|
-
`available scripts: ${formatAvailableScripts(packageScripts.scripts)}`,
|
|
1032
|
-
...(packageScripts.error ? [`package.json read error: ${packageScripts.error}`] : []),
|
|
1033
|
-
].join("\n"),
|
|
1034
|
-
metadata: {
|
|
1035
|
-
script,
|
|
1036
|
-
exitCode: 1,
|
|
1037
|
-
reason: "script-not-found",
|
|
1038
|
-
availableScripts: packageScripts.scripts,
|
|
1039
|
-
...(packageScripts.error ? { packageJsonError: packageScripts.error } : {}),
|
|
1040
|
-
},
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
const result = await runWorkspaceCommand({
|
|
1044
|
-
workspaceRoot: args.workspaceRoot,
|
|
1045
|
-
command: ["bun", "run", script, ...(extraArgs.length ? ["--", ...extraArgs] : [])],
|
|
1046
|
-
timeoutMs: args.commandTimeoutMs,
|
|
1047
|
-
});
|
|
1048
|
-
return {
|
|
1049
|
-
content: result.content,
|
|
1050
|
-
metadata: {
|
|
1051
|
-
script,
|
|
1052
|
-
args: extraArgs,
|
|
1053
|
-
exitCode: result.exitCode,
|
|
1054
|
-
reason: result.exitCode === 0 ? "ok" : "script-failed",
|
|
1055
|
-
timedOut: result.timedOut,
|
|
1056
|
-
stdoutTail: result.stdout.trim().slice(-4000),
|
|
1057
|
-
stderrTail: result.stderr.trim().slice(-4000),
|
|
1058
|
-
},
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
967
|
async function resolveVisualStateBaseUrl(args: {
|
|
1063
968
|
workspaceRoot: string;
|
|
1064
969
|
explicitBaseUrl?: string;
|
|
@@ -1106,16 +1011,10 @@ async function captureVisualStateTool(args: {
|
|
|
1106
1011
|
"--metrics",
|
|
1107
1012
|
captureArgs.metricsPath,
|
|
1108
1013
|
];
|
|
1109
|
-
const result = await
|
|
1110
|
-
call: {
|
|
1111
|
-
id: args.call.id,
|
|
1112
|
-
name: "runPackageScript",
|
|
1113
|
-
arguments: JSON.stringify({
|
|
1114
|
-
script: "probe:visual-review",
|
|
1115
|
-
args: extraArgs,
|
|
1116
|
-
}),
|
|
1117
|
-
},
|
|
1014
|
+
const result = await runWorkspacePackageScript({
|
|
1118
1015
|
workspaceRoot: args.workspaceRoot,
|
|
1016
|
+
script: "probe:visual-review",
|
|
1017
|
+
extraArgs,
|
|
1119
1018
|
commandTimeoutMs: args.commandTimeoutMs,
|
|
1120
1019
|
});
|
|
1121
1020
|
const metrics = parseLastJsonObject(String(result.metadata?.stdoutTail ?? result.content));
|
|
@@ -1153,13 +1052,9 @@ async function previewLifecycleTool(args: {
|
|
|
1153
1052
|
commandTimeoutMs?: number;
|
|
1154
1053
|
script: "preview:start" | "preview:status" | "preview:stop" | "preview:release";
|
|
1155
1054
|
}): Promise<AgentRuntimeToolResult> {
|
|
1156
|
-
const result = await
|
|
1157
|
-
call: {
|
|
1158
|
-
id: args.call.id,
|
|
1159
|
-
name: "runPackageScript",
|
|
1160
|
-
arguments: JSON.stringify({ script: args.script }),
|
|
1161
|
-
},
|
|
1055
|
+
const result = await runWorkspacePackageScript({
|
|
1162
1056
|
workspaceRoot: args.workspaceRoot,
|
|
1057
|
+
script: args.script,
|
|
1163
1058
|
commandTimeoutMs: args.commandTimeoutMs,
|
|
1164
1059
|
});
|
|
1165
1060
|
const summary = parseLastJsonObject(String(result.metadata?.stdoutTail ?? result.content)) ?? {};
|
|
@@ -1195,6 +1090,7 @@ async function execShellTool(args: {
|
|
|
1195
1090
|
shell: parsed.shell,
|
|
1196
1091
|
}),
|
|
1197
1092
|
timeoutMs: args.commandTimeoutMs,
|
|
1093
|
+
outputLimit: args.commandOutputLimit,
|
|
1198
1094
|
commandPrefix: args.commandPrefix,
|
|
1199
1095
|
});
|
|
1200
1096
|
return {
|
|
@@ -1253,15 +1149,6 @@ export function createLocalWorkspaceToolExecutors(args: LocalWorkspaceToolArgs)
|
|
|
1253
1149
|
call,
|
|
1254
1150
|
workspaceRoot: args.workspaceRoot,
|
|
1255
1151
|
}),
|
|
1256
|
-
applyPatch: (call: AgentRuntimeToolCallInput) => applyPatchTool({
|
|
1257
|
-
call,
|
|
1258
|
-
workspaceRoot: args.workspaceRoot,
|
|
1259
|
-
}),
|
|
1260
|
-
runPackageScript: (call: AgentRuntimeToolCallInput) => runPackageScriptTool({
|
|
1261
|
-
call,
|
|
1262
|
-
workspaceRoot: args.workspaceRoot,
|
|
1263
|
-
commandTimeoutMs: args.commandTimeoutMs,
|
|
1264
|
-
}),
|
|
1265
1152
|
startPreview: (call: AgentRuntimeToolCallInput) => previewLifecycleTool({
|
|
1266
1153
|
call,
|
|
1267
1154
|
workspaceRoot: args.workspaceRoot,
|
|
@@ -1295,6 +1182,7 @@ export function createLocalWorkspaceToolExecutors(args: LocalWorkspaceToolArgs)
|
|
|
1295
1182
|
call,
|
|
1296
1183
|
workspaceRoot: args.workspaceRoot,
|
|
1297
1184
|
commandTimeoutMs: args.commandTimeoutMs,
|
|
1185
|
+
commandOutputLimit: args.commandOutputLimit,
|
|
1298
1186
|
commandPrefix: args.commandPrefix,
|
|
1299
1187
|
}),
|
|
1300
1188
|
};
|
|
@@ -7,9 +7,10 @@ import {
|
|
|
7
7
|
buildProviderExecutionPlan,
|
|
8
8
|
canUsePlatformChatProvider as canUsePlatformChatProviderFromEnv,
|
|
9
9
|
hasDirectOpenAiCompatibleProvider as hasDirectOpenAiCompatibleProviderFromEnv,
|
|
10
|
+
resolveAgentRuntimeLocation,
|
|
10
11
|
resolvePlatformAuthToken,
|
|
11
12
|
resolvePlatformServerUrl,
|
|
12
|
-
|
|
13
|
+
resolveProviderTransportDecision,
|
|
13
14
|
} from "./providerResolution";
|
|
14
15
|
|
|
15
16
|
type EnvLike = Record<string, string | undefined>;
|
|
@@ -206,16 +207,23 @@ export function hasDirectOpenAiCompatibleProvider(env: EnvLike) {
|
|
|
206
207
|
return hasDirectOpenAiCompatibleProviderFromEnv(env);
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
function agentRequiresPlatformChatProvider(agentConfig?: AgentRuntimeAgentConfig) {
|
|
210
|
-
if (agentConfig?.apiSource === "platform") return true;
|
|
211
|
-
if (agentConfig?.apiSource === "custom" || agentConfig?.customProviderUrl?.trim()) return false;
|
|
212
|
-
return agentConfig?.useServerProxy === true;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
210
|
export function shouldUsePlatformChatProvider(
|
|
216
211
|
env: EnvLike,
|
|
217
212
|
agentConfig?: AgentRuntimeAgentConfig
|
|
218
213
|
) {
|
|
219
|
-
if (
|
|
220
|
-
|
|
214
|
+
if (!agentConfig) {
|
|
215
|
+
return resolveProviderTransportDecision({
|
|
216
|
+
agentConfig: { key: "default-platform" },
|
|
217
|
+
env,
|
|
218
|
+
runtimeLocation: "local-host",
|
|
219
|
+
}).transport === "proxy";
|
|
220
|
+
}
|
|
221
|
+
return resolveProviderTransportDecision({
|
|
222
|
+
agentConfig,
|
|
223
|
+
env,
|
|
224
|
+
runtimeLocation: resolveAgentRuntimeLocation({
|
|
225
|
+
agentConfig,
|
|
226
|
+
runtimeKind: "local",
|
|
227
|
+
}),
|
|
228
|
+
}).transport === "proxy";
|
|
221
229
|
}
|
|
@@ -16,6 +16,21 @@ const PROVIDER_ENDPOINTS: Record<string, string> = {
|
|
|
16
16
|
|
|
17
17
|
export type AgentProviderMode = "cli" | "platform" | "custom";
|
|
18
18
|
export type ProviderExecutionTransport = "direct" | "proxy";
|
|
19
|
+
export type AgentRuntimeLocation = "server" | "bound-machine" | "local-host";
|
|
20
|
+
|
|
21
|
+
export type ProviderTransportDecision = {
|
|
22
|
+
mode: AgentProviderMode;
|
|
23
|
+
transport: ProviderExecutionTransport;
|
|
24
|
+
reason:
|
|
25
|
+
| "cli-provider"
|
|
26
|
+
| "custom-provider"
|
|
27
|
+
| "custom-server-proxy"
|
|
28
|
+
| "platform-agent"
|
|
29
|
+
| "forced-platform-env"
|
|
30
|
+
| "forced-direct-env"
|
|
31
|
+
| "direct-provider-env"
|
|
32
|
+
| "platform-proxy-fallback";
|
|
33
|
+
};
|
|
19
34
|
|
|
20
35
|
type ProviderExecutionPlanBase = {
|
|
21
36
|
mode: AgentProviderMode;
|
|
@@ -122,14 +137,50 @@ export function resolveAgentProviderMode(agentConfig: AgentRuntimeAgentConfig):
|
|
|
122
137
|
return "platform";
|
|
123
138
|
}
|
|
124
139
|
|
|
125
|
-
export function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
export function resolveAgentRuntimeLocation(args: {
|
|
141
|
+
agentConfig: AgentRuntimeAgentConfig;
|
|
142
|
+
runtimeKind: "local" | "desktop" | "server";
|
|
143
|
+
}): AgentRuntimeLocation {
|
|
144
|
+
const runtimeBinding = args.agentConfig.runtimeBinding;
|
|
145
|
+
const machineId = typeof runtimeBinding?.machineId === "string"
|
|
146
|
+
? runtimeBinding.machineId.trim()
|
|
147
|
+
: "";
|
|
148
|
+
if (machineId) return "bound-machine";
|
|
149
|
+
if (args.runtimeKind === "server") return "server";
|
|
150
|
+
return "local-host";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function resolveProviderTransportDecision(args: {
|
|
154
|
+
agentConfig: AgentRuntimeAgentConfig;
|
|
155
|
+
env: EnvLike;
|
|
156
|
+
runtimeLocation: AgentRuntimeLocation;
|
|
157
|
+
}): ProviderTransportDecision {
|
|
158
|
+
const mode = resolveAgentProviderMode(args.agentConfig);
|
|
159
|
+
if (mode === "cli") {
|
|
160
|
+
return { mode, transport: "direct", reason: "cli-provider" };
|
|
161
|
+
}
|
|
162
|
+
if (mode === "custom") {
|
|
163
|
+
if (args.runtimeLocation === "server" && args.agentConfig.useServerProxy === true) {
|
|
164
|
+
return { mode, transport: "proxy", reason: "custom-server-proxy" };
|
|
165
|
+
}
|
|
166
|
+
return { mode, transport: "direct", reason: "custom-provider" };
|
|
167
|
+
}
|
|
168
|
+
if (args.agentConfig.apiSource === "platform" || args.agentConfig.useServerProxy === true) {
|
|
169
|
+
return { mode, transport: "proxy", reason: "platform-agent" };
|
|
170
|
+
}
|
|
171
|
+
if (args.env.NOLO_LOCAL_LLM === "platform") {
|
|
172
|
+
return { mode, transport: "proxy", reason: "forced-platform-env" };
|
|
173
|
+
}
|
|
174
|
+
if (args.env.NOLO_LOCAL_LLM === "direct") {
|
|
175
|
+
return { mode, transport: "direct", reason: "forced-direct-env" };
|
|
176
|
+
}
|
|
177
|
+
if (hasDirectOpenAiCompatibleProvider(args.env)) {
|
|
178
|
+
return { mode, transport: "direct", reason: "direct-provider-env" };
|
|
179
|
+
}
|
|
180
|
+
if (canUsePlatformChatProvider(args.env)) {
|
|
181
|
+
return { mode, transport: "proxy", reason: "platform-proxy-fallback" };
|
|
182
|
+
}
|
|
183
|
+
return { mode, transport: "direct", reason: "direct-provider-env" };
|
|
133
184
|
}
|
|
134
185
|
|
|
135
186
|
export function resolveProviderAuthHeaderName(args: {
|
|
@@ -161,6 +212,15 @@ export function buildProviderExecutionPlan(args: {
|
|
|
161
212
|
}): ProviderExecutionPlan {
|
|
162
213
|
const { agentConfig, env } = args;
|
|
163
214
|
const mode = resolveAgentProviderMode(agentConfig);
|
|
215
|
+
const runtimeLocation = resolveAgentRuntimeLocation({
|
|
216
|
+
agentConfig,
|
|
217
|
+
runtimeKind: args.runtimeKind,
|
|
218
|
+
});
|
|
219
|
+
const transportDecision = resolveProviderTransportDecision({
|
|
220
|
+
agentConfig,
|
|
221
|
+
env,
|
|
222
|
+
runtimeLocation,
|
|
223
|
+
});
|
|
164
224
|
const model = agentConfig.model || "gpt-4.1-mini";
|
|
165
225
|
const requestOptions = pickAgentRuntimeInferenceOptions(agentConfig);
|
|
166
226
|
|
|
@@ -182,7 +242,7 @@ export function buildProviderExecutionPlan(args: {
|
|
|
182
242
|
endpoint,
|
|
183
243
|
apiKeyHeader: agentConfig.apiKeyHeader,
|
|
184
244
|
});
|
|
185
|
-
if (
|
|
245
|
+
if (transportDecision.transport === "proxy") {
|
|
186
246
|
return {
|
|
187
247
|
mode,
|
|
188
248
|
transport: "proxy",
|
|
@@ -211,10 +271,10 @@ export function buildProviderExecutionPlan(args: {
|
|
|
211
271
|
}
|
|
212
272
|
|
|
213
273
|
const provider = agentConfig.provider || agentConfig.apiSource || "openai";
|
|
214
|
-
const endpoint =
|
|
274
|
+
const endpoint = transportDecision.transport === "proxy"
|
|
215
275
|
? resolvePlatformProviderEndpoint(agentConfig)
|
|
216
276
|
: resolveChatCompletionsEndpoint(resolveOpenAiCompatibleBaseUrl(env));
|
|
217
|
-
if (
|
|
277
|
+
if (transportDecision.transport === "proxy") {
|
|
218
278
|
return {
|
|
219
279
|
mode,
|
|
220
280
|
transport: "proxy",
|