nolo-cli 0.1.36 → 0.1.38
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 +3 -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/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 +83 -105
- package/cli/client/localRuntimeAdapter.ts +22 -1
- package/cli/machineWsRunDispatch.ts +3 -5
- package/client/agentRun.test.ts +2 -32
- package/client/agentRun.ts +83 -105
- package/client/localRuntimeAdapter.test.ts +64 -0
- package/client/localRuntimeAdapter.ts +22 -1
- package/machineWsRunDispatch.ts +3 -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
|
@@ -59,8 +59,11 @@ export {
|
|
|
59
59
|
export {
|
|
60
60
|
mergeAgentRuntimeToolPolicies,
|
|
61
61
|
normalizeAgentRuntimeToolPolicy,
|
|
62
|
+
resolveCurrentRunRuntimeToolPolicy,
|
|
62
63
|
resolveRequestedRuntimeToolNames,
|
|
63
64
|
resolveEffectiveRuntimeToolPolicy,
|
|
65
|
+
resolveLocalRuntimeEnvFromPolicy,
|
|
66
|
+
resolveLocalWorkspaceExecutorOptionsFromPolicy,
|
|
64
67
|
} from "./runtimeToolPolicy";
|
|
65
68
|
export {
|
|
66
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
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
2
|
import type { AgentRuntimeHost, AgentRuntimeToolPolicy } from "./types";
|
|
3
3
|
|
|
4
|
+
type EnvLike = Record<string, string | undefined>;
|
|
5
|
+
|
|
4
6
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
7
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
8
|
}
|
|
@@ -56,6 +58,11 @@ function runtimeToolNamesFromPolicy(policy: AgentRuntimeToolPolicy | undefined)
|
|
|
56
58
|
return stringArray(policy?.runtimeTools);
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
function readPositiveNumber(value: unknown) {
|
|
62
|
+
const parsed = Number(value);
|
|
63
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
export function mergeAgentRuntimeToolPolicies(
|
|
60
67
|
base?: AgentRuntimeToolPolicy,
|
|
61
68
|
override?: AgentRuntimeToolPolicy,
|
|
@@ -110,6 +117,44 @@ export function resolveRequestedRuntimeToolNames(args: {
|
|
|
110
117
|
]);
|
|
111
118
|
}
|
|
112
119
|
|
|
120
|
+
export function resolveCurrentRunRuntimeToolPolicy(
|
|
121
|
+
agentConfig: Pick<AgentRuntimeAgentConfig, "runtimeToolPolicy" | "runtimeBinding"> | null | undefined,
|
|
122
|
+
): AgentRuntimeToolPolicy | undefined {
|
|
123
|
+
const runtimeBinding = runtimeBindingRecord(agentConfig?.runtimeBinding);
|
|
124
|
+
return normalizeAgentRuntimeToolPolicy(
|
|
125
|
+
runtimeBinding?.runtimeToolPolicySnapshot ??
|
|
126
|
+
agentConfig?.runtimeToolPolicy ??
|
|
127
|
+
runtimeBinding?.runtimeToolPolicy,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveLocalRuntimeEnvFromPolicy(
|
|
132
|
+
runtimeEnv: EnvLike,
|
|
133
|
+
policy?: AgentRuntimeToolPolicy,
|
|
134
|
+
): EnvLike {
|
|
135
|
+
const enablesWorktreeShell =
|
|
136
|
+
Array.isArray(policy?.runtimeTools) &&
|
|
137
|
+
policy.runtimeTools.includes("execShell") &&
|
|
138
|
+
policy.shell?.enabled === true &&
|
|
139
|
+
policy.shell?.mode === "worktree";
|
|
140
|
+
return {
|
|
141
|
+
...runtimeEnv,
|
|
142
|
+
...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
|
|
143
|
+
...(enablesWorktreeShell && !runtimeEnv.NOLO_LOCAL_SHELL_MODE
|
|
144
|
+
? { NOLO_LOCAL_SHELL_MODE: "worktree" }
|
|
145
|
+
: {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveLocalWorkspaceExecutorOptionsFromPolicy(
|
|
150
|
+
policy?: AgentRuntimeToolPolicy,
|
|
151
|
+
) {
|
|
152
|
+
const maxOutputBytes = readPositiveNumber(policy?.shell?.maxOutputBytes);
|
|
153
|
+
return {
|
|
154
|
+
...(maxOutputBytes ? { commandOutputLimit: Math.floor(maxOutputBytes) } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
113
158
|
function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefined) {
|
|
114
159
|
if (!policy) return false;
|
|
115
160
|
const workspaceMode = policy.workspace?.mode;
|
|
@@ -150,12 +195,7 @@ export function resolveEffectiveRuntimeToolPolicy(args: {
|
|
|
150
195
|
taskRunControl?: { runtimeToolPolicy?: unknown } | null;
|
|
151
196
|
host?: AgentRuntimeHost;
|
|
152
197
|
}): AgentRuntimeToolPolicy | undefined {
|
|
153
|
-
const agentPolicy =
|
|
154
|
-
args.agentConfig.runtimeToolPolicy ??
|
|
155
|
-
(isRecord(args.agentConfig.runtimeBinding)
|
|
156
|
-
? args.agentConfig.runtimeBinding.runtimeToolPolicy
|
|
157
|
-
: undefined),
|
|
158
|
-
);
|
|
198
|
+
const agentPolicy = resolveCurrentRunRuntimeToolPolicy(args.agentConfig);
|
|
159
199
|
const taskPolicy = normalizeAgentRuntimeToolPolicy(
|
|
160
200
|
args.taskRunControl?.runtimeToolPolicy,
|
|
161
201
|
);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
readlink,
|
|
7
7
|
rm,
|
|
8
8
|
symlink,
|
|
9
|
+
unlink,
|
|
9
10
|
} from "node:fs/promises";
|
|
10
11
|
import { dirname, join, resolve, sep } from "node:path";
|
|
11
12
|
|
|
@@ -73,22 +74,23 @@ export async function collectWorkspacePackageLinks(workspacePath: string) {
|
|
|
73
74
|
() => []
|
|
74
75
|
);
|
|
75
76
|
const nodeModulesPath = join(workspacePath, "node_modules");
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
77
|
+
|
|
78
|
+
const linksPromises = packageDirs
|
|
79
|
+
.filter((entry) => entry.isDirectory())
|
|
80
|
+
.map(async (entry) => {
|
|
81
|
+
const target = join(packagesRoot, entry.name);
|
|
82
|
+
const name = await readPackageName(join(target, "package.json")).catch(
|
|
83
|
+
() => null
|
|
84
|
+
);
|
|
85
|
+
if (!name) return null;
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
target,
|
|
89
|
+
linkPath: packageLinkPath(nodeModulesPath, name),
|
|
90
|
+
};
|
|
89
91
|
});
|
|
90
|
-
}
|
|
91
92
|
|
|
93
|
+
const links = (await Promise.all(linksPromises)).filter(Boolean) as WorkspacePackageLink[];
|
|
92
94
|
return links;
|
|
93
95
|
}
|
|
94
96
|
|
|
@@ -106,7 +108,7 @@ async function ensureWorkspacePackageLink(link: WorkspacePackageLink) {
|
|
|
106
108
|
if (!existingTarget) {
|
|
107
109
|
throw new Error(`Cannot replace non-symlink workspace package path: ${link.linkPath}`);
|
|
108
110
|
}
|
|
109
|
-
await
|
|
111
|
+
await unlink(link.linkPath);
|
|
110
112
|
}
|
|
111
113
|
await symlink(link.target, link.linkPath, "dir");
|
|
112
114
|
return true;
|
|
@@ -134,7 +136,7 @@ async function ensureWorkspaceNodeModulesDirectory(workspacePath: string) {
|
|
|
134
136
|
if (symlinkTarget) {
|
|
135
137
|
const resolvedTarget = resolve(dirname(nodeModulesPath), symlinkTarget);
|
|
136
138
|
if (!isInsideWorkspace(workspacePath, resolvedTarget)) {
|
|
137
|
-
await
|
|
139
|
+
await unlink(nodeModulesPath);
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
await mkdir(nodeModulesPath, { recursive: true });
|
|
@@ -150,12 +152,10 @@ export async function ensureWorkspacePackageLinks(workspacePath: string) {
|
|
|
150
152
|
};
|
|
151
153
|
}
|
|
152
154
|
await ensureWorkspaceNodeModulesDirectory(workspacePath);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
}
|
|
155
|
+
const results = await Promise.all(
|
|
156
|
+
links.map((link) => ensureWorkspacePackageLink(link))
|
|
157
|
+
);
|
|
158
|
+
const createdOrUpdated = results.filter(Boolean).length;
|
|
159
159
|
return {
|
|
160
160
|
workspacePath,
|
|
161
161
|
links,
|
|
@@ -173,7 +173,7 @@ export async function prepareGitTaskWorkspace(args: {
|
|
|
173
173
|
return { path: args.env.NOLO_LOCAL_WORKTREE };
|
|
174
174
|
}
|
|
175
175
|
const cwd = args.cwd ?? process.cwd();
|
|
176
|
-
const root = await runGitForTaskWorkspace(["rev-parse", "--show-toplevel"], cwd);
|
|
176
|
+
const root = resolve(await runGitForTaskWorkspace(["rev-parse", "--show-toplevel"], cwd));
|
|
177
177
|
if (isFormalTaskWorkspacePath(root)) {
|
|
178
178
|
await ensureWorkspacePackageLinks(root);
|
|
179
179
|
return { path: root };
|
package/agent-runtime/types.ts
CHANGED