nolo-cli 0.1.22 → 0.1.26
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 +25 -3
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/hybridRecordStore.ts +30 -9
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +14 -4
- package/agent-runtime/localToolPolicy.ts +22 -10
- package/agent-runtime/localWorkspaceTools.ts +140 -374
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +133 -0
- package/agent-runtime/types.ts +47 -0
- package/agentAliases.ts +1 -1
- package/agentCommandSupport.ts +77 -0
- package/agentInternalCommandEntries.ts +34 -0
- package/agentListCommands.ts +123 -0
- package/agentListHelpers.ts +187 -0
- package/agentMachineCommands.ts +402 -0
- package/agentRecordCommands.ts +141 -0
- package/agentRecordHelpers.ts +395 -0
- package/agentRunCommand.ts +75 -3
- package/agentRuntimeCommands.ts +7 -773
- package/ai/agent/_executeModel.ts +29 -3
- package/ai/agent/buildSystemPrompt.ts +17 -0
- package/ai/agent/cliChatClient.ts +38 -4
- package/ai/agent/cliExecutor.ts +304 -25
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/guidedCreation/assistant.ts +82 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/draft.ts +62 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/machineRunPermissions.ts +21 -3
- package/ai/agent/runtimeGuidance.ts +1 -1
- package/ai/agent/server/fetchPublicAgents.ts +6 -1
- package/ai/agent/streamAgentChatTurn.ts +593 -36
- package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
- package/ai/ai.locale.ts +14 -2
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
- package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
- package/ai/llm/getPricing.ts +14 -0
- package/ai/llm/kimi.ts +4 -1
- package/ai/llm/modelAvatar.ts +1 -0
- package/ai/llm/providers.ts +4 -1
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/taskRun/taskRunProtocol.ts +25 -10
- package/ai/token/modelUsageQuery.ts +17 -0
- package/ai/tools/agent/agentTools.ts +38 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/checkEnvTool.ts +52 -20
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
- package/ai/tools/index.ts +24 -14
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +9 -2
- package/ai/tools/readTool.ts +31 -0
- package/ai/tools/toolApiClient.ts +35 -1
- package/ai/tools/toolNameAliases.ts +13 -0
- package/cliCommandAdapters.ts +28 -0
- package/cliCommandDispatch.ts +66 -0
- package/cliCommandFactories.ts +34 -0
- package/cliCommandTypes.ts +49 -0
- package/cliEnvHelpers.ts +44 -0
- package/client/agentRun.test.ts +330 -24
- package/client/agentRun.ts +117 -35
- package/client/hybridRecordStore.test.ts +42 -0
- package/client/hybridRecordStore.ts +11 -0
- package/client/localRuntimeAdapter.test.ts +191 -21
- package/client/localRuntimeAdapter.ts +99 -10
- package/client/localRuntimeDryRun.test.ts +5 -2
- package/client/taskRunPrompt.ts +1 -0
- package/commandRegistry.ts +11 -61
- package/connector-experimental/capabilities.ts +1 -0
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/database/server/coreDataProxy.ts +43 -26
- package/index.ts +54 -191
- package/internalCommandEntries.ts +12 -0
- package/machineCommands.ts +688 -64
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +22 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/runtimeDoctorCommands.ts +118 -0
- package/scriptCommandEntries.ts +71 -0
- package/systemInternalCommandEntries.ts +71 -0
- package/tableCommands.ts +42 -5
- package/workflowInternalCommandEntries.ts +11 -0
package/machineCommands.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { MachineHeartbeat } from "./connector-experimental/protocol";
|
|
2
2
|
import { detectMachineInfo } from "./connector-experimental/machineInfo";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { mkdirSync, openSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, openSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
6
|
import { homedir } from "node:os";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
8
|
+
import { runLocalAgentTurn, type LocalAgentToolEvent } from "./agent-runtime/localLoop";
|
|
7
9
|
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
8
10
|
import {
|
|
9
11
|
collectConnectorRunArtifact,
|
|
@@ -19,7 +21,9 @@ import {
|
|
|
19
21
|
buildMachinePermissionPromptBlock,
|
|
20
22
|
resolveMachineRunPermissionPolicy,
|
|
21
23
|
} from "./ai/agent/machineRunPermissions";
|
|
22
|
-
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
24
|
+
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
25
|
+
import { createCliLocalRuntimeAdapter } from "./client/localRuntimeAdapter";
|
|
26
|
+
import { prepareTaskWorktree } from "./client/taskWorktree";
|
|
23
27
|
|
|
24
28
|
type EnvLike = Record<string, string | undefined>;
|
|
25
29
|
type OutputLike = { write(chunk: string): unknown };
|
|
@@ -28,11 +32,42 @@ type ConnectorWebSocketOptions = {
|
|
|
28
32
|
onMessage: (message: string) => void | Promise<void>;
|
|
29
33
|
sentMessages: string[];
|
|
30
34
|
};
|
|
31
|
-
type LocalCliExecutor = (
|
|
35
|
+
export type LocalCliExecutor = (
|
|
32
36
|
provider: string,
|
|
33
37
|
prompt: string,
|
|
34
38
|
options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean; env?: EnvLike }
|
|
35
39
|
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
40
|
+
|
|
41
|
+
type ConnectorRuntimePolicy = {
|
|
42
|
+
runtimeTools?: string[];
|
|
43
|
+
agentTools?: string[];
|
|
44
|
+
workspace?: {
|
|
45
|
+
mode?: string;
|
|
46
|
+
cwd?: string;
|
|
47
|
+
};
|
|
48
|
+
shell?: {
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
mode?: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type ConnectorRunProgress = {
|
|
55
|
+
eventType?: LocalAgentToolEvent["type"];
|
|
56
|
+
toolCallCount?: number;
|
|
57
|
+
toolResultCount?: number;
|
|
58
|
+
lastToolNames?: string[];
|
|
59
|
+
provider?: string;
|
|
60
|
+
promptBytes?: number;
|
|
61
|
+
promptHash?: string;
|
|
62
|
+
promptRef?: string;
|
|
63
|
+
promptPageKey?: string;
|
|
64
|
+
promptPageHash?: string;
|
|
65
|
+
requestId?: string;
|
|
66
|
+
workspaceRoot?: string;
|
|
67
|
+
workspaceKind?: string;
|
|
68
|
+
message?: string;
|
|
69
|
+
updatedAt?: number;
|
|
70
|
+
};
|
|
36
71
|
|
|
37
72
|
export type MachineSummary = {
|
|
38
73
|
machineId: string;
|
|
@@ -55,15 +90,26 @@ type MachineCommandDeps = {
|
|
|
55
90
|
fetchImpl?: typeof fetch;
|
|
56
91
|
machineInfo?: () => MachineHeartbeat;
|
|
57
92
|
runHeartbeatLoop?: (options: HeartbeatLoopOptions) => Promise<void>;
|
|
58
|
-
connectWebSocket?: (url: string, options: ConnectorWebSocketOptions) => Promise<void>;
|
|
59
|
-
executeCli?: LocalCliExecutor;
|
|
60
|
-
|
|
93
|
+
connectWebSocket?: (url: string, options: ConnectorWebSocketOptions) => Promise<void>;
|
|
94
|
+
executeCli?: LocalCliExecutor;
|
|
95
|
+
validateWorkspaceLinks?: (cwd: string) => string[] | Promise<string[]>;
|
|
96
|
+
spawnDaemon?: (args: {
|
|
61
97
|
cmd: string[];
|
|
62
98
|
cwd: string;
|
|
63
99
|
env: EnvLike;
|
|
64
100
|
logPath: string;
|
|
65
101
|
}) => { pid?: number };
|
|
66
|
-
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
type PackageJson = {
|
|
105
|
+
name?: string;
|
|
106
|
+
workspaces?: string[] | { packages?: string[] };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = new Set([
|
|
110
|
+
"connector-experimental",
|
|
111
|
+
"ai",
|
|
112
|
+
]);
|
|
67
113
|
|
|
68
114
|
function resolveServerUrl(env: EnvLike) {
|
|
69
115
|
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
@@ -79,9 +125,113 @@ function writeAuthMissing(output: OutputLike) {
|
|
|
79
125
|
);
|
|
80
126
|
}
|
|
81
127
|
|
|
82
|
-
function hasFlag(args: string[], flag: string) {
|
|
83
|
-
return args.includes(flag);
|
|
84
|
-
}
|
|
128
|
+
function hasFlag(args: string[], flag: string) {
|
|
129
|
+
return args.includes(flag);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizePath(path: string) {
|
|
133
|
+
const resolved = resolve(path);
|
|
134
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isInsideOrSame(path: string, parent: string) {
|
|
138
|
+
const relativePath = relative(parent, path);
|
|
139
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readJsonFile<T>(path: string): T | null {
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getWorkspacePatterns(packageJson: PackageJson) {
|
|
151
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
152
|
+
return packageJson.workspaces?.packages ?? [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function findWorkspaceRoot(startPath: string) {
|
|
156
|
+
let current = resolve(startPath);
|
|
157
|
+
while (true) {
|
|
158
|
+
const packageJsonPath = join(current, "package.json");
|
|
159
|
+
if (existsSync(packageJsonPath)) {
|
|
160
|
+
const packageJson = readJsonFile<PackageJson>(packageJsonPath);
|
|
161
|
+
if (packageJson && getWorkspacePatterns(packageJson).length > 0) {
|
|
162
|
+
return current;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const parent = dirname(current);
|
|
166
|
+
if (parent === current) return null;
|
|
167
|
+
current = parent;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function validateConnectorWorkspaceLinks(cwd: string) {
|
|
172
|
+
const repoRoot = findWorkspaceRoot(cwd);
|
|
173
|
+
if (!repoRoot) return [];
|
|
174
|
+
|
|
175
|
+
const rootPackageJson = readJsonFile<PackageJson>(join(repoRoot, "package.json"));
|
|
176
|
+
if (!rootPackageJson) return [];
|
|
177
|
+
|
|
178
|
+
const normalizedRepoRoot = normalizePath(realpathSync(repoRoot));
|
|
179
|
+
const errors: string[] = [];
|
|
180
|
+
|
|
181
|
+
for (const pattern of getWorkspacePatterns(rootPackageJson)) {
|
|
182
|
+
if (!pattern.endsWith("/*")) continue;
|
|
183
|
+
const workspaceParent = join(repoRoot, pattern.slice(0, -2));
|
|
184
|
+
if (!existsSync(workspaceParent)) continue;
|
|
185
|
+
|
|
186
|
+
for (const entry of Array.from(new Bun.Glob("*/package.json").scanSync({ cwd: workspaceParent }))) {
|
|
187
|
+
const packageDir = join(workspaceParent, dirname(entry));
|
|
188
|
+
const packageJson = readJsonFile<PackageJson>(join(workspaceParent, entry));
|
|
189
|
+
if (!packageJson?.name || !CONNECTOR_RUNTIME_WORKSPACE_PACKAGES.has(packageJson.name)) {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const linkPath = join(repoRoot, "node_modules", ...packageJson.name.split("/"));
|
|
194
|
+
if (!existsSync(linkPath)) {
|
|
195
|
+
errors.push(`${linkPath} is missing. Run bun install in ${repoRoot}.`);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const actualPath = normalizePath(realpathSync(linkPath));
|
|
200
|
+
const expectedPath = normalizePath(realpathSync(packageDir));
|
|
201
|
+
if (!isInsideOrSame(actualPath, normalizedRepoRoot)) {
|
|
202
|
+
errors.push(
|
|
203
|
+
`${linkPath} points outside this checkout: ${actualPath}. Run bun install in ${repoRoot}.`,
|
|
204
|
+
);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (actualPath !== expectedPath) {
|
|
208
|
+
errors.push(
|
|
209
|
+
`${linkPath} points at ${actualPath}, expected ${expectedPath}. Run bun install in ${repoRoot}.`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return errors;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function checkConnectorWorkspaceLinks(
|
|
219
|
+
cwd: string,
|
|
220
|
+
output: OutputLike,
|
|
221
|
+
validateWorkspaceLinks: (cwd: string) => string[] | Promise<string[]>,
|
|
222
|
+
) {
|
|
223
|
+
const errors = await validateWorkspaceLinks(cwd);
|
|
224
|
+
if (errors.length === 0) return true;
|
|
225
|
+
output.write(
|
|
226
|
+
[
|
|
227
|
+
"[nolo] Connector workspace package links are unsafe.",
|
|
228
|
+
"The connector would import workspace packages from a different checkout, so machine capabilities and runtime behavior may be stale.",
|
|
229
|
+
...errors,
|
|
230
|
+
"",
|
|
231
|
+
].join("\n"),
|
|
232
|
+
);
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
85
235
|
|
|
86
236
|
function readOption(args: string[], flag: string) {
|
|
87
237
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -112,6 +262,90 @@ function resolveDaemonLogPath(env: EnvLike) {
|
|
|
112
262
|
return env.NOLO_CONNECT_LOG || join(homedir(), ".nolo", "logs", "connector.log");
|
|
113
263
|
}
|
|
114
264
|
|
|
265
|
+
function byteLength(value: string) {
|
|
266
|
+
return Buffer.byteLength(value, "utf8");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function shortHash(value: string) {
|
|
270
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolvePromptInlineMaxBytes(env: EnvLike) {
|
|
274
|
+
const raw = Number(env.NOLO_CONNECTOR_PROMPT_INLINE_MAX_BYTES);
|
|
275
|
+
return Number.isFinite(raw) && raw >= 1024 ? raw : 32_000;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function materializeLargeConnectorPrompt(args: {
|
|
279
|
+
prompt: string;
|
|
280
|
+
cwd: string;
|
|
281
|
+
env: EnvLike;
|
|
282
|
+
requestId: string;
|
|
283
|
+
runtimePromptPage?: {
|
|
284
|
+
dbKey?: string;
|
|
285
|
+
promptHash?: string;
|
|
286
|
+
contentBytes?: number;
|
|
287
|
+
} | null;
|
|
288
|
+
}) {
|
|
289
|
+
const promptBytes = byteLength(args.prompt);
|
|
290
|
+
const promptHash = shortHash(args.prompt);
|
|
291
|
+
const maxInlineBytes = resolvePromptInlineMaxBytes(args.env);
|
|
292
|
+
if (promptBytes <= maxInlineBytes) {
|
|
293
|
+
return {
|
|
294
|
+
prompt: args.prompt,
|
|
295
|
+
promptBytes,
|
|
296
|
+
promptHash,
|
|
297
|
+
promptRef: null as string | null,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const promptDir = join(args.cwd, ".nolo", "agent-prompts");
|
|
302
|
+
mkdirSync(promptDir, { recursive: true });
|
|
303
|
+
const promptRef = join(promptDir, `${args.requestId}-${promptHash}.md`);
|
|
304
|
+
writeFileSync(promptRef, args.prompt, "utf8");
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
prompt: [
|
|
308
|
+
"A large Nolo runtime prompt has been materialized outside this command argument.",
|
|
309
|
+
...(args.runtimePromptPage?.dbKey
|
|
310
|
+
? [
|
|
311
|
+
"Cloud prompt page is the shared audit/context source.",
|
|
312
|
+
`Cloud prompt page: ${args.runtimePromptPage.dbKey}`,
|
|
313
|
+
`Read cloud page: bun packages/cli/index.ts doc read ${JSON.stringify(args.runtimePromptPage.dbKey)}`,
|
|
314
|
+
...(args.runtimePromptPage.promptHash
|
|
315
|
+
? [`Cloud prompt sha256 prefix: ${args.runtimePromptPage.promptHash}`]
|
|
316
|
+
: []),
|
|
317
|
+
"",
|
|
318
|
+
]
|
|
319
|
+
: []),
|
|
320
|
+
"Local prompt file is the execution-machine fallback and should match the cloud page content at run start.",
|
|
321
|
+
`Prompt file: ${promptRef}`,
|
|
322
|
+
`Prompt bytes: ${promptBytes}`,
|
|
323
|
+
`Prompt sha256 prefix: ${promptHash}`,
|
|
324
|
+
"",
|
|
325
|
+
"Prefer the cloud prompt page for shared context when readable; use the local prompt file as fallback.",
|
|
326
|
+
"Local repository edits, diffs, test output, and commits are still authoritative for local execution facts.",
|
|
327
|
+
"If neither source is readable, stop and report PROMPT_REF_UNREADABLE with both references.",
|
|
328
|
+
].join("\n"),
|
|
329
|
+
promptBytes,
|
|
330
|
+
promptHash,
|
|
331
|
+
promptRef,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function readRuntimePromptPageMeta(parsed: any) {
|
|
336
|
+
const page = parsed?.payload?.meta?.runtimePromptPage;
|
|
337
|
+
if (!page || typeof page !== "object") return null;
|
|
338
|
+
return {
|
|
339
|
+
dbKey: typeof page.dbKey === "string" ? page.dbKey : undefined,
|
|
340
|
+
promptHash: typeof page.promptHash === "string" ? page.promptHash : undefined,
|
|
341
|
+
contentBytes: typeof page.contentBytes === "number" ? page.contentBytes : undefined,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function logConnectorRun(message: string, fields: Record<string, unknown> = {}) {
|
|
346
|
+
console.error(`[nolo-connector] ${message} ${JSON.stringify(fields)}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
115
349
|
function buildDaemonCommand(cliEntrypointPath: string | undefined) {
|
|
116
350
|
return [process.execPath, cliEntrypointPath || import.meta.path, "connect", "--ws"];
|
|
117
351
|
}
|
|
@@ -245,6 +479,7 @@ function buildTaskRunBridgePrompt(args: {
|
|
|
245
479
|
"Run commands from the repository root. The runner already provides server URL and auth in the environment.",
|
|
246
480
|
"Release boundary: after review passes, AI/reviewer/Codex may advance alpha for verification. Do not merge, push, or release main/release unless the human owner explicitly authorizes it in the current task context.",
|
|
247
481
|
"Cost boundary: read taskRun.control from context. Prefer low-cost/free-window models for routine work, and reserve Codex for supervision, quality gates, complex unblocking, and main/release decisions.",
|
|
482
|
+
"Handoff context: start from read-context, then inspect existing refs such as workItems.dialogIds, workItems.outcome, review.dialogId, activityRefs, agentRuns.dialogId/resultSummary, and artifactRefs. Use dialog read/search for exact evidence; do not infer completion from handoff text alone.",
|
|
248
483
|
`Read context: bun packages/cli/index.ts task-run read-context --row-dbkey ${JSON.stringify(rowDbKey)}`,
|
|
249
484
|
...(workItemId
|
|
250
485
|
? [
|
|
@@ -264,8 +499,8 @@ function buildTaskRunBridgePrompt(args: {
|
|
|
264
499
|
function buildConnectorCliPrompt(agentConfig: any, userInput: string, bridgeArgs?: {
|
|
265
500
|
agentKey: string;
|
|
266
501
|
runtimeContext: any;
|
|
267
|
-
}) {
|
|
268
|
-
const policy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
502
|
+
}, permissionPolicy?: ReturnType<typeof resolveMachineRunPermissionPolicy>) {
|
|
503
|
+
const policy = permissionPolicy ?? resolveMachineRunPermissionPolicy(agentConfig);
|
|
269
504
|
return [
|
|
270
505
|
typeof agentConfig?.prompt === "string" ? agentConfig.prompt.trim() : "",
|
|
271
506
|
bridgeArgs ? buildTaskRunBridgePrompt({
|
|
@@ -278,6 +513,248 @@ function buildConnectorCliPrompt(agentConfig: any, userInput: string, bridgeArgs
|
|
|
278
513
|
].filter(Boolean).join("\n\n");
|
|
279
514
|
}
|
|
280
515
|
|
|
516
|
+
function isRecord(value: unknown): value is Record<string, any> {
|
|
517
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function runtimePolicyFromConnectorPayload(parsed: any): ConnectorRuntimePolicy | undefined {
|
|
521
|
+
const fromMeta = parsed?.payload?.meta?.runtimeToolPolicySnapshot;
|
|
522
|
+
const fromAgent = parsed?.payload?.agentConfig?.runtimeToolPolicy;
|
|
523
|
+
const policy = isRecord(fromMeta) ? fromMeta : isRecord(fromAgent) ? fromAgent : null;
|
|
524
|
+
return policy ? policy as ConnectorRuntimePolicy : undefined;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function requestsTaskWorktree(policy?: ConnectorRuntimePolicy) {
|
|
528
|
+
return policy?.workspace?.mode === "task-worktree" || policy?.workspace?.mode === "lease";
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function hasExplicitMachinePermissions(agentConfig: any) {
|
|
532
|
+
const runtimeBinding = isRecord(agentConfig?.runtimeBinding) ? agentConfig.runtimeBinding : {};
|
|
533
|
+
return Boolean(
|
|
534
|
+
isRecord(agentConfig?.machinePermissions) ||
|
|
535
|
+
isRecord(runtimeBinding.permissions) ||
|
|
536
|
+
isRecord(runtimeBinding.machinePermissions) ||
|
|
537
|
+
isRecord(agentConfig?.boundRuntimeMachine?.permissions)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function runtimeWorkspacePermissionPolicy(
|
|
542
|
+
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
543
|
+
cwd: string
|
|
544
|
+
): ReturnType<typeof resolveMachineRunPermissionPolicy> | null {
|
|
545
|
+
if (!requestsTaskWorktree(runtimePolicy)) return null;
|
|
546
|
+
return {
|
|
547
|
+
mode: "full_access",
|
|
548
|
+
allowFilesystemRead: true,
|
|
549
|
+
allowFilesystemWrite: true,
|
|
550
|
+
allowShell: runtimePolicy?.shell?.enabled !== false,
|
|
551
|
+
writableRoots: [cwd],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function scopePermissionPolicyToRuntimeWorkspace(
|
|
556
|
+
policy: ReturnType<typeof resolveMachineRunPermissionPolicy>,
|
|
557
|
+
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
558
|
+
cwd: string
|
|
559
|
+
): ReturnType<typeof resolveMachineRunPermissionPolicy> {
|
|
560
|
+
if (!requestsTaskWorktree(runtimePolicy)) return policy;
|
|
561
|
+
return {
|
|
562
|
+
...policy,
|
|
563
|
+
writableRoots: policy.allowFilesystemWrite ? [cwd] : [],
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function mergeToolNames(...values: unknown[]) {
|
|
568
|
+
const names: string[] = [];
|
|
569
|
+
for (const value of values) {
|
|
570
|
+
if (!Array.isArray(value)) continue;
|
|
571
|
+
for (const item of value) {
|
|
572
|
+
const name = typeof item === "string"
|
|
573
|
+
? item
|
|
574
|
+
: isRecord(item) && typeof item.name === "string"
|
|
575
|
+
? item.name
|
|
576
|
+
: isRecord(item) && typeof item.function?.name === "string"
|
|
577
|
+
? item.function.name
|
|
578
|
+
: "";
|
|
579
|
+
if (name && !names.includes(name)) names.push(name);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return names;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
|
|
586
|
+
return {
|
|
587
|
+
...runtimeEnv,
|
|
588
|
+
...(policy?.shell?.enabled && policy.shell.mode === "worktree"
|
|
589
|
+
? { NOLO_LOCAL_SHELL_MODE: "worktree" }
|
|
590
|
+
: {}),
|
|
591
|
+
...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function runtimeWorkspaceRootFromTrace(trace: unknown): string | undefined {
|
|
596
|
+
if (!Array.isArray(trace)) return undefined;
|
|
597
|
+
for (const message of trace) {
|
|
598
|
+
if (!isRecord(message)) continue;
|
|
599
|
+
const metadata = isRecord(message.tool_result_metadata)
|
|
600
|
+
? message.tool_result_metadata
|
|
601
|
+
: null;
|
|
602
|
+
const workspaceRoot = metadata && typeof metadata.workspaceRoot === "string"
|
|
603
|
+
? metadata.workspaceRoot.trim()
|
|
604
|
+
: "";
|
|
605
|
+
if (workspaceRoot) return workspaceRoot;
|
|
606
|
+
}
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function buildArtifactProgress(args: {
|
|
611
|
+
artifacts: unknown;
|
|
612
|
+
runtimePolicy?: ConnectorRuntimePolicy;
|
|
613
|
+
workspaceRoot: string;
|
|
614
|
+
workspaceKind?: string;
|
|
615
|
+
}): ConnectorRunProgress | null {
|
|
616
|
+
if (!isRecord(args.artifacts)) return null;
|
|
617
|
+
const changedFiles = Array.isArray(args.artifacts.changedFiles)
|
|
618
|
+
? args.artifacts.changedFiles
|
|
619
|
+
: [];
|
|
620
|
+
const statusShort = typeof args.artifacts.statusShort === "string"
|
|
621
|
+
? args.artifacts.statusShort.trim()
|
|
622
|
+
: "";
|
|
623
|
+
if (changedFiles.length === 0 && !statusShort) return null;
|
|
624
|
+
const runtimeTools = Array.isArray(args.runtimePolicy?.runtimeTools)
|
|
625
|
+
? args.runtimePolicy.runtimeTools
|
|
626
|
+
: [];
|
|
627
|
+
const shellTool = runtimeTools.includes("execShell")
|
|
628
|
+
? "execShell"
|
|
629
|
+
: "workspaceArtifact";
|
|
630
|
+
return {
|
|
631
|
+
eventType: "tool-result",
|
|
632
|
+
toolCallCount: 1,
|
|
633
|
+
toolResultCount: 1,
|
|
634
|
+
lastToolNames: [shellTool],
|
|
635
|
+
workspaceRoot: args.workspaceRoot,
|
|
636
|
+
...(args.workspaceKind ? { workspaceKind: args.workspaceKind } : {}),
|
|
637
|
+
message: statusShort || `${changedFiles.length} changed file(s)`,
|
|
638
|
+
updatedAt: Date.now(),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function runConnectorLocalRuntimeAgent(args: {
|
|
643
|
+
parsed: any;
|
|
644
|
+
runtimeEnv: EnvLike;
|
|
645
|
+
cwd: string;
|
|
646
|
+
fetchImpl?: typeof fetch;
|
|
647
|
+
onProgress?: (progress: ConnectorRunProgress) => void;
|
|
648
|
+
}): Promise<Awaited<ReturnType<typeof runLocalAgentTurn>> & { runtimeWorkspaceRoot?: string }> {
|
|
649
|
+
const agentKey = String(args.parsed.payload?.agentKey ?? "");
|
|
650
|
+
const payloadAgentConfig = isRecord(args.parsed.payload?.agentConfig)
|
|
651
|
+
? args.parsed.payload.agentConfig
|
|
652
|
+
: {};
|
|
653
|
+
const policy = runtimePolicyFromConnectorPayload(args.parsed);
|
|
654
|
+
const agentRecord = {
|
|
655
|
+
...payloadAgentConfig,
|
|
656
|
+
dbKey: agentKey,
|
|
657
|
+
id: agentKey,
|
|
658
|
+
key: agentKey,
|
|
659
|
+
apiSource: payloadAgentConfig.apiSource ?? "platform",
|
|
660
|
+
provider: payloadAgentConfig.provider ?? payloadAgentConfig.apiSource ?? "openai",
|
|
661
|
+
toolNames: mergeToolNames(
|
|
662
|
+
payloadAgentConfig.toolNames,
|
|
663
|
+
payloadAgentConfig.tools,
|
|
664
|
+
policy?.agentTools,
|
|
665
|
+
policy?.runtimeTools,
|
|
666
|
+
),
|
|
667
|
+
...(policy ? { runtimeToolPolicy: policy } : {}),
|
|
668
|
+
...(policy?.workspace?.mode === "task-worktree" || policy?.workspace?.mode === "lease"
|
|
669
|
+
? { localWorkspaceMode: "task-worktree" }
|
|
670
|
+
: {}),
|
|
671
|
+
};
|
|
672
|
+
const store = new Map<string, any>([
|
|
673
|
+
[agentKey, agentRecord],
|
|
674
|
+
[`agent-${args.runtimeEnv.NOLO_USER_ID || "local"}-${agentKey}`, agentRecord],
|
|
675
|
+
[`agent-${args.runtimeEnv.NOLO_LOCAL_USER_ID || args.runtimeEnv.NOLO_USER_ID || "local"}-${agentKey}`, agentRecord],
|
|
676
|
+
]);
|
|
677
|
+
const env = localRuntimeEnvFromPolicy(args.runtimeEnv, policy);
|
|
678
|
+
const wantsTaskWorktree = requestsTaskWorktree(policy);
|
|
679
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
680
|
+
env,
|
|
681
|
+
cwd: args.cwd,
|
|
682
|
+
useCwdAsTaskWorkspaceBase: wantsTaskWorktree,
|
|
683
|
+
...(wantsTaskWorktree
|
|
684
|
+
? {
|
|
685
|
+
prepareTaskWorktree: (input) => prepareTaskWorktree({
|
|
686
|
+
...input,
|
|
687
|
+
cwd: args.cwd,
|
|
688
|
+
}),
|
|
689
|
+
}
|
|
690
|
+
: {}),
|
|
691
|
+
fetchImpl: args.fetchImpl ?? fetch,
|
|
692
|
+
store: {
|
|
693
|
+
read: async (key) => store.get(key) ?? null,
|
|
694
|
+
batch: async (ops) => {
|
|
695
|
+
for (const op of ops) {
|
|
696
|
+
if (op.type === "put") store.set(op.key, op.value);
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
iterator: async function* (options) {
|
|
700
|
+
const keys = [...store.keys()].sort();
|
|
701
|
+
for (const key of keys) {
|
|
702
|
+
if (key < options.gte) continue;
|
|
703
|
+
if (options.lte && key > options.lte) continue;
|
|
704
|
+
if (options.lt && key >= options.lt) continue;
|
|
705
|
+
yield [key, store.get(key)];
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
let toolCallCount = 0;
|
|
711
|
+
let toolResultCount = 0;
|
|
712
|
+
const lastToolNames: string[] = [];
|
|
713
|
+
let runtimeWorkspaceRoot: string | undefined;
|
|
714
|
+
let runtimeWorkspaceKind: string | undefined;
|
|
715
|
+
const noteToolName = (toolName: string) => {
|
|
716
|
+
if (!toolName) return;
|
|
717
|
+
const existingIndex = lastToolNames.indexOf(toolName);
|
|
718
|
+
if (existingIndex >= 0) lastToolNames.splice(existingIndex, 1);
|
|
719
|
+
lastToolNames.push(toolName);
|
|
720
|
+
while (lastToolNames.length > 8) lastToolNames.shift();
|
|
721
|
+
};
|
|
722
|
+
const result = await runLocalAgentTurn({
|
|
723
|
+
adapter,
|
|
724
|
+
agentRef: agentKey,
|
|
725
|
+
input: String(args.parsed.payload?.userInput ?? ""),
|
|
726
|
+
continueDialogId: typeof args.parsed.payload?.continueDialogId === "string"
|
|
727
|
+
? args.parsed.payload.continueDialogId
|
|
728
|
+
: undefined,
|
|
729
|
+
onToolEvent: (event) => {
|
|
730
|
+
if (event.type === "tool-call") toolCallCount += 1;
|
|
731
|
+
if (event.type === "tool-result") toolResultCount += 1;
|
|
732
|
+
noteToolName(event.toolName);
|
|
733
|
+
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
734
|
+
if (typeof metadata.workspaceRoot === "string" && metadata.workspaceRoot.trim()) {
|
|
735
|
+
runtimeWorkspaceRoot = metadata.workspaceRoot.trim();
|
|
736
|
+
}
|
|
737
|
+
if (typeof metadata.workspaceKind === "string" && metadata.workspaceKind.trim()) {
|
|
738
|
+
runtimeWorkspaceKind = metadata.workspaceKind.trim();
|
|
739
|
+
}
|
|
740
|
+
args.onProgress?.({
|
|
741
|
+
eventType: event.type,
|
|
742
|
+
toolCallCount,
|
|
743
|
+
toolResultCount,
|
|
744
|
+
lastToolNames: [...lastToolNames],
|
|
745
|
+
...(runtimeWorkspaceRoot ? { workspaceRoot: runtimeWorkspaceRoot } : {}),
|
|
746
|
+
...(runtimeWorkspaceKind ? { workspaceKind: runtimeWorkspaceKind } : {}),
|
|
747
|
+
...(event.message ? { message: event.message } : {}),
|
|
748
|
+
updatedAt: Date.now(),
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
return {
|
|
753
|
+
...result,
|
|
754
|
+
runtimeWorkspaceRoot: runtimeWorkspaceRootFromTrace(result.trace) ?? runtimeWorkspaceRoot,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
281
758
|
function normalizeConnectorRunTimeoutMs(value: unknown): number | undefined {
|
|
282
759
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
283
760
|
return undefined;
|
|
@@ -285,12 +762,13 @@ function normalizeConnectorRunTimeoutMs(value: unknown): number | undefined {
|
|
|
285
762
|
return Math.floor(value);
|
|
286
763
|
}
|
|
287
764
|
|
|
288
|
-
async function handleConnectorRunMessage(
|
|
765
|
+
export async function handleConnectorRunMessage(
|
|
289
766
|
machine: MachineHeartbeat,
|
|
290
767
|
message: string,
|
|
291
768
|
send: (message: string) => void,
|
|
292
769
|
executeCli: LocalCliExecutor,
|
|
293
|
-
runtimeEnv: EnvLike
|
|
770
|
+
runtimeEnv: EnvLike,
|
|
771
|
+
fetchImpl?: typeof fetch
|
|
294
772
|
) {
|
|
295
773
|
let parsed: any;
|
|
296
774
|
try {
|
|
@@ -298,59 +776,193 @@ async function handleConnectorRunMessage(
|
|
|
298
776
|
} catch {
|
|
299
777
|
return;
|
|
300
778
|
}
|
|
301
|
-
if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
|
|
302
|
-
const agentConfig = parsed.payload?.agentConfig ?? {};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
779
|
+
if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
|
|
780
|
+
const agentConfig = parsed.payload?.agentConfig ?? {};
|
|
781
|
+
const sendProgress = (progress: ConnectorRunProgress) => {
|
|
782
|
+
send(JSON.stringify({
|
|
783
|
+
type: "agent.run.progress",
|
|
784
|
+
requestId: parsed.requestId,
|
|
785
|
+
progress,
|
|
786
|
+
}));
|
|
787
|
+
};
|
|
788
|
+
try {
|
|
789
|
+
const machinePermissionPolicy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
790
|
+
const runtimePolicy = runtimePolicyFromConnectorPayload(parsed);
|
|
309
791
|
const userInput = String(parsed.payload?.userInput ?? "");
|
|
310
792
|
const timeout = normalizeConnectorRunTimeoutMs(parsed.payload?.timeoutMs);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
793
|
+
let cwd = resolveConnectorRunCwd({ env: runtimeEnv, policy: machinePermissionPolicy });
|
|
794
|
+
let runContent = "";
|
|
795
|
+
let runModel = agentConfig.model;
|
|
796
|
+
let runTrace: unknown[] = [];
|
|
797
|
+
let artifactCwd = cwd;
|
|
798
|
+
let baseSha: string | null = null;
|
|
799
|
+
if (agentConfig.apiSource === "cli") {
|
|
800
|
+
if (requestsTaskWorktree(runtimePolicy)) {
|
|
801
|
+
const prepared = await prepareTaskWorktree({
|
|
802
|
+
agentKey: String(parsed.payload?.agentKey ?? "agent"),
|
|
803
|
+
cwd,
|
|
804
|
+
env: runtimeEnv,
|
|
805
|
+
});
|
|
806
|
+
cwd = prepared.path;
|
|
807
|
+
artifactCwd = prepared.path;
|
|
808
|
+
}
|
|
809
|
+
const runtimePermissionPolicy = !hasExplicitMachinePermissions(agentConfig)
|
|
810
|
+
? runtimeWorkspacePermissionPolicy(runtimePolicy, cwd)
|
|
811
|
+
: null;
|
|
812
|
+
const effectivePermissionPolicy = scopePermissionPolicyToRuntimeWorkspace(
|
|
813
|
+
runtimePermissionPolicy ?? machinePermissionPolicy,
|
|
814
|
+
runtimePolicy,
|
|
815
|
+
cwd,
|
|
816
|
+
);
|
|
817
|
+
assertMachineRunAllowed(userInput, effectivePermissionPolicy);
|
|
818
|
+
baseSha = await readConnectorGitHead(artifactCwd);
|
|
819
|
+
const provider = String(agentConfig.cliProvider || "copilot");
|
|
820
|
+
const builtPrompt = buildConnectorCliPrompt(agentConfig, userInput, {
|
|
317
821
|
agentKey: String(parsed.payload?.agentKey ?? ""),
|
|
318
822
|
runtimeContext: parsed.payload?.runtimeContext,
|
|
319
|
-
})
|
|
320
|
-
{
|
|
321
|
-
|
|
322
|
-
timeout,
|
|
823
|
+
}, effectivePermissionPolicy);
|
|
824
|
+
const materializedPrompt = materializeLargeConnectorPrompt({
|
|
825
|
+
prompt: builtPrompt,
|
|
323
826
|
cwd,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
827
|
+
env: runtimeEnv,
|
|
828
|
+
requestId: parsed.requestId,
|
|
829
|
+
runtimePromptPage: readRuntimePromptPageMeta(parsed),
|
|
830
|
+
});
|
|
831
|
+
const runtimePromptPage = readRuntimePromptPageMeta(parsed);
|
|
832
|
+
const startAt = Date.now();
|
|
833
|
+
const progressBase = {
|
|
834
|
+
provider,
|
|
835
|
+
requestId: parsed.requestId,
|
|
836
|
+
promptBytes: materializedPrompt.promptBytes,
|
|
837
|
+
promptHash: materializedPrompt.promptHash,
|
|
838
|
+
...(materializedPrompt.promptRef ? { promptRef: materializedPrompt.promptRef } : {}),
|
|
839
|
+
...(runtimePromptPage?.dbKey ? { promptPageKey: runtimePromptPage.dbKey } : {}),
|
|
840
|
+
...(runtimePromptPage?.promptHash ? { promptPageHash: runtimePromptPage.promptHash } : {}),
|
|
841
|
+
workspaceRoot: artifactCwd,
|
|
842
|
+
workspaceKind: requestsTaskWorktree(runtimePolicy) ? "task-worktree" : "current",
|
|
843
|
+
};
|
|
844
|
+
logConnectorRun("agent.run.cli.start", {
|
|
845
|
+
requestId: parsed.requestId,
|
|
846
|
+
agentKey: parsed.payload?.agentKey,
|
|
847
|
+
provider,
|
|
848
|
+
cwd,
|
|
849
|
+
artifactCwd,
|
|
850
|
+
timeout,
|
|
851
|
+
promptBytes: materializedPrompt.promptBytes,
|
|
852
|
+
promptHash: materializedPrompt.promptHash,
|
|
853
|
+
promptRef: materializedPrompt.promptRef,
|
|
854
|
+
promptPageKey: runtimePromptPage?.dbKey,
|
|
855
|
+
});
|
|
856
|
+
sendProgress({
|
|
857
|
+
...progressBase,
|
|
858
|
+
message: "cli-started",
|
|
859
|
+
updatedAt: startAt,
|
|
860
|
+
});
|
|
861
|
+
const heartbeat = setInterval(() => {
|
|
862
|
+
sendProgress({
|
|
863
|
+
...progressBase,
|
|
864
|
+
message: "cli-running",
|
|
865
|
+
updatedAt: Date.now(),
|
|
866
|
+
});
|
|
867
|
+
}, 15_000);
|
|
868
|
+
const result = await executeCli(
|
|
869
|
+
provider,
|
|
870
|
+
materializedPrompt.prompt,
|
|
871
|
+
{
|
|
872
|
+
model: agentConfig.model || undefined,
|
|
873
|
+
timeout,
|
|
874
|
+
cwd,
|
|
875
|
+
yolo: true,
|
|
876
|
+
env: {
|
|
877
|
+
NOLO_SERVER: runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL || runtimeEnv.BASE_URL,
|
|
878
|
+
NOLO_SERVER_URL: runtimeEnv.NOLO_SERVER_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.BASE_URL,
|
|
879
|
+
BASE_URL: runtimeEnv.BASE_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL,
|
|
880
|
+
AUTH_TOKEN: runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH || runtimeEnv.NOLO_MACHINE_API_KEY,
|
|
881
|
+
NOLO_MACHINE_API_KEY: runtimeEnv.NOLO_MACHINE_API_KEY || runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH,
|
|
882
|
+
},
|
|
883
|
+
}
|
|
884
|
+
).finally(() => clearInterval(heartbeat));
|
|
885
|
+
logConnectorRun("agent.run.cli.result", {
|
|
886
|
+
requestId: parsed.requestId,
|
|
887
|
+
provider,
|
|
888
|
+
elapsed: result.elapsed,
|
|
889
|
+
outputBytes: byteLength(result.raw ?? result.text ?? ""),
|
|
890
|
+
});
|
|
891
|
+
runContent = result.text;
|
|
892
|
+
runModel = String(agentConfig.model || "").trim() || provider;
|
|
893
|
+
runTrace = [{ role: "assistant", content: result.text }];
|
|
894
|
+
const artifacts = await collectConnectorRunArtifact({
|
|
895
|
+
cwd: artifactCwd,
|
|
896
|
+
baseSha,
|
|
897
|
+
exitStatus: "completed",
|
|
898
|
+
});
|
|
899
|
+
const artifactProgress = buildArtifactProgress({
|
|
900
|
+
artifacts,
|
|
901
|
+
runtimePolicy,
|
|
902
|
+
workspaceRoot: artifactCwd,
|
|
903
|
+
workspaceKind: requestsTaskWorktree(runtimePolicy) ? "task-worktree" : "current",
|
|
904
|
+
});
|
|
905
|
+
if (artifactProgress && requestsTaskWorktree(runtimePolicy)) sendProgress(artifactProgress);
|
|
906
|
+
send(JSON.stringify({
|
|
907
|
+
type: "agent.run.result",
|
|
908
|
+
requestId: parsed.requestId,
|
|
909
|
+
result: {
|
|
910
|
+
content: runContent,
|
|
911
|
+
model: runModel,
|
|
912
|
+
trace: runTrace,
|
|
913
|
+
artifacts,
|
|
331
914
|
},
|
|
915
|
+
}));
|
|
916
|
+
return;
|
|
917
|
+
} else {
|
|
918
|
+
if (!runtimePolicy) {
|
|
919
|
+
throw new Error("Connector can only execute non-CLI agents when runtimeToolPolicySnapshot requests a local workspace runtime.");
|
|
332
920
|
}
|
|
333
|
-
|
|
921
|
+
baseSha = await readConnectorGitHead(cwd);
|
|
922
|
+
const result = await runConnectorLocalRuntimeAgent({
|
|
923
|
+
parsed,
|
|
924
|
+
runtimeEnv,
|
|
925
|
+
cwd,
|
|
926
|
+
fetchImpl,
|
|
927
|
+
onProgress: sendProgress,
|
|
928
|
+
});
|
|
929
|
+
runContent = result.content;
|
|
930
|
+
runModel = result.model;
|
|
931
|
+
runTrace = result.trace ?? [];
|
|
932
|
+
artifactCwd = result.runtimeWorkspaceRoot ?? cwd;
|
|
933
|
+
}
|
|
334
934
|
const artifacts = await collectConnectorRunArtifact({
|
|
335
|
-
cwd,
|
|
935
|
+
cwd: artifactCwd,
|
|
336
936
|
baseSha,
|
|
337
937
|
exitStatus: "completed",
|
|
338
938
|
});
|
|
939
|
+
const artifactProgress = buildArtifactProgress({
|
|
940
|
+
artifacts,
|
|
941
|
+
runtimePolicy,
|
|
942
|
+
workspaceRoot: artifactCwd,
|
|
943
|
+
workspaceKind: requestsTaskWorktree(runtimePolicy) ? "task-worktree" : "current",
|
|
944
|
+
});
|
|
945
|
+
if (artifactProgress && requestsTaskWorktree(runtimePolicy)) sendProgress(artifactProgress);
|
|
339
946
|
send(JSON.stringify({
|
|
340
|
-
type: "agent.run.result",
|
|
341
|
-
requestId: parsed.requestId,
|
|
342
|
-
result: {
|
|
343
|
-
content:
|
|
344
|
-
model:
|
|
345
|
-
trace:
|
|
947
|
+
type: "agent.run.result",
|
|
948
|
+
requestId: parsed.requestId,
|
|
949
|
+
result: {
|
|
950
|
+
content: runContent,
|
|
951
|
+
model: runModel,
|
|
952
|
+
trace: runTrace,
|
|
346
953
|
artifacts,
|
|
347
954
|
},
|
|
348
955
|
}));
|
|
349
|
-
} catch (error) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
error: error instanceof Error ? error.message : String(error),
|
|
956
|
+
} catch (error) {
|
|
957
|
+
logConnectorRun("agent.run.error", {
|
|
958
|
+
requestId: parsed.requestId,
|
|
959
|
+
agentKey: parsed.payload?.agentKey,
|
|
960
|
+
error: error instanceof Error ? error.message : String(error),
|
|
961
|
+
});
|
|
962
|
+
send(JSON.stringify({
|
|
963
|
+
type: "agent.run.result",
|
|
964
|
+
requestId: parsed.requestId,
|
|
965
|
+
error: error instanceof Error ? error.message : String(error),
|
|
354
966
|
}));
|
|
355
967
|
}
|
|
356
968
|
}
|
|
@@ -385,6 +997,10 @@ async function runConnectorWebSocketSession(options: {
|
|
|
385
997
|
headers: { Authorization: `Bearer ${options.authToken}` },
|
|
386
998
|
fetchImpl: options.fetchImpl,
|
|
387
999
|
});
|
|
1000
|
+
const runtimeServerUrl =
|
|
1001
|
+
options.env.NOLO_CONNECT_RUNTIME_SERVER_URL ||
|
|
1002
|
+
options.env.NOLO_RUNTIME_SERVER_URL ||
|
|
1003
|
+
options.serverUrl;
|
|
388
1004
|
const websocketPromise = options.connectWebSocket(
|
|
389
1005
|
wsTarget,
|
|
390
1006
|
{
|
|
@@ -397,12 +1013,13 @@ async function runConnectorWebSocketSession(options: {
|
|
|
397
1013
|
options.executeCli,
|
|
398
1014
|
{
|
|
399
1015
|
...options.env,
|
|
400
|
-
NOLO_SERVER:
|
|
401
|
-
NOLO_SERVER_URL:
|
|
402
|
-
BASE_URL:
|
|
1016
|
+
NOLO_SERVER: runtimeServerUrl,
|
|
1017
|
+
NOLO_SERVER_URL: runtimeServerUrl,
|
|
1018
|
+
BASE_URL: runtimeServerUrl,
|
|
403
1019
|
AUTH_TOKEN: options.authToken,
|
|
404
1020
|
NOLO_MACHINE_API_KEY: options.authToken,
|
|
405
|
-
}
|
|
1021
|
+
},
|
|
1022
|
+
options.fetchImpl
|
|
406
1023
|
),
|
|
407
1024
|
}
|
|
408
1025
|
);
|
|
@@ -449,10 +1066,11 @@ export async function runMachineConnectCommand(
|
|
|
449
1066
|
return 1;
|
|
450
1067
|
}
|
|
451
1068
|
|
|
452
|
-
const serverUrl = resolveConnectServerUrl(args, env);
|
|
453
|
-
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
454
|
-
const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
455
|
-
const
|
|
1069
|
+
const serverUrl = resolveConnectServerUrl(args, env);
|
|
1070
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
1071
|
+
const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
1072
|
+
const validateWorkspaceLinks = deps.validateWorkspaceLinks ?? validateConnectorWorkspaceLinks;
|
|
1073
|
+
const sendHeartbeat = async () => {
|
|
456
1074
|
const res = await fetchImpl(`${serverUrl}/api/machines/heartbeat`, {
|
|
457
1075
|
method: "POST",
|
|
458
1076
|
headers: {
|
|
@@ -467,8 +1085,11 @@ export async function runMachineConnectCommand(
|
|
|
467
1085
|
throw new Error(`HTTP ${res.status}\n${text}`);
|
|
468
1086
|
}
|
|
469
1087
|
};
|
|
470
|
-
|
|
1088
|
+
|
|
471
1089
|
if (hasFlag(args, "--daemon") || hasFlag(args, "--background")) {
|
|
1090
|
+
if (!(await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks))) {
|
|
1091
|
+
return 1;
|
|
1092
|
+
}
|
|
472
1093
|
const logPath = resolveDaemonLogPath(env);
|
|
473
1094
|
const result = (deps.spawnDaemon ?? defaultSpawnDaemon)({
|
|
474
1095
|
cmd: buildDaemonCommand(deps.cliEntrypointPath),
|
|
@@ -478,9 +1099,12 @@ export async function runMachineConnectCommand(
|
|
|
478
1099
|
});
|
|
479
1100
|
output.write(`Connector daemon started${result.pid ? ` pid=${result.pid}` : ""}. Log: ${logPath}\n`);
|
|
480
1101
|
return 0;
|
|
481
|
-
}
|
|
482
|
-
|
|
1102
|
+
}
|
|
1103
|
+
|
|
483
1104
|
if (hasFlag(args, "--ws")) {
|
|
1105
|
+
if (!(await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks))) {
|
|
1106
|
+
return 1;
|
|
1107
|
+
}
|
|
484
1108
|
const maxAttempts = deps.maxConnectorAttempts ?? Infinity;
|
|
485
1109
|
const sleep = deps.sleep ?? defaultSleep;
|
|
486
1110
|
const reconnectDelayMs = resolveConnectorReconnectDelayMs(env);
|