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
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
parsePlatformChatCompletionData,
|
|
17
17
|
parsePlatformChatCompletionResponse,
|
|
18
18
|
resolvePlatformChatProviderConfig,
|
|
19
|
+
resolveCurrentRunRuntimeToolPolicy,
|
|
20
|
+
resolveLocalWorkspaceExecutorOptionsFromPolicy,
|
|
19
21
|
resolveRequestedRuntimeToolNames,
|
|
20
22
|
shouldUsePlatformChatProvider,
|
|
21
23
|
} from "../agentRuntimeLocal";
|
|
@@ -221,6 +223,29 @@ function buildRequestTimeoutSignal(timeoutMs?: number) {
|
|
|
221
223
|
return controller.signal;
|
|
222
224
|
}
|
|
223
225
|
|
|
226
|
+
function summarizeEndpoint(value: unknown) {
|
|
227
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
228
|
+
try {
|
|
229
|
+
const url = new URL(value);
|
|
230
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
231
|
+
} catch {
|
|
232
|
+
return "invalid-url";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseJsonObject(raw: string) {
|
|
237
|
+
try {
|
|
238
|
+
const parsed = JSON.parse(raw);
|
|
239
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
240
|
+
} catch {
|
|
241
|
+
return {};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function logLocalRuntimeDiagnostic(event: string, fields: Record<string, unknown>) {
|
|
246
|
+
console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
224
249
|
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
225
250
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
226
251
|
return [
|
|
@@ -314,9 +339,15 @@ function buildLocalToolExecutors(args: {
|
|
|
314
339
|
env: EnvLike;
|
|
315
340
|
fetchImpl: typeof fetch;
|
|
316
341
|
localToolExecutors?: CliLocalRuntimeAdapterDeps["localToolExecutors"];
|
|
342
|
+
commandTimeoutMs?: number;
|
|
343
|
+
commandOutputLimit?: number;
|
|
317
344
|
}) {
|
|
318
345
|
return {
|
|
319
|
-
...createLocalWorkspaceToolExecutors({
|
|
346
|
+
...createLocalWorkspaceToolExecutors({
|
|
347
|
+
workspaceRoot: args.workspaceRoot,
|
|
348
|
+
commandTimeoutMs: args.commandTimeoutMs,
|
|
349
|
+
commandOutputLimit: args.commandOutputLimit,
|
|
350
|
+
}),
|
|
320
351
|
...buildServerPlatformToolExecutors({
|
|
321
352
|
env: args.env,
|
|
322
353
|
fetchImpl: args.fetchImpl,
|
|
@@ -411,11 +442,13 @@ export function createCliLocalRuntimeAdapter(
|
|
|
411
442
|
? { defaultCwd: deps.cwd }
|
|
412
443
|
: { cwd: deps.cwd }
|
|
413
444
|
);
|
|
445
|
+
let runtimeToolExecutionLimits: ReturnType<typeof resolveLocalWorkspaceExecutorOptionsFromPolicy> = {};
|
|
414
446
|
let localToolExecutors = buildLocalToolExecutors({
|
|
415
447
|
workspaceRoot: workspaceSession.workspaceRoot,
|
|
416
448
|
env: deps.env,
|
|
417
449
|
fetchImpl,
|
|
418
450
|
localToolExecutors: deps.localToolExecutors,
|
|
451
|
+
...runtimeToolExecutionLimits,
|
|
419
452
|
});
|
|
420
453
|
|
|
421
454
|
async function activateAgentWorkspaceSession(args: {
|
|
@@ -435,6 +468,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
435
468
|
env: deps.env,
|
|
436
469
|
fetchImpl,
|
|
437
470
|
localToolExecutors: deps.localToolExecutors,
|
|
471
|
+
...runtimeToolExecutionLimits,
|
|
438
472
|
});
|
|
439
473
|
deps.output?.write(formatWorkspaceSessionActivation(workspaceSession));
|
|
440
474
|
}
|
|
@@ -455,7 +489,17 @@ export function createCliLocalRuntimeAdapter(
|
|
|
455
489
|
toolNames: requestedToolNames,
|
|
456
490
|
env: deps.env,
|
|
457
491
|
});
|
|
492
|
+
runtimeToolExecutionLimits = resolveLocalWorkspaceExecutorOptionsFromPolicy(
|
|
493
|
+
resolveCurrentRunRuntimeToolPolicy(agentConfig),
|
|
494
|
+
);
|
|
458
495
|
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
496
|
+
localToolExecutors = buildLocalToolExecutors({
|
|
497
|
+
workspaceRoot: workspaceSession.workspaceRoot,
|
|
498
|
+
env: deps.env,
|
|
499
|
+
fetchImpl,
|
|
500
|
+
localToolExecutors: deps.localToolExecutors,
|
|
501
|
+
...runtimeToolExecutionLimits,
|
|
502
|
+
});
|
|
459
503
|
return agentConfig;
|
|
460
504
|
},
|
|
461
505
|
loadDialogHistory: async (dialogId) => readDialogMessages({
|
|
@@ -476,6 +520,20 @@ export function createCliLocalRuntimeAdapter(
|
|
|
476
520
|
agentConfig,
|
|
477
521
|
env: deps.env,
|
|
478
522
|
});
|
|
523
|
+
logLocalRuntimeDiagnostic("provider.selected", {
|
|
524
|
+
agentKey: agentConfig.key,
|
|
525
|
+
transport: "platform-proxy",
|
|
526
|
+
apiSource: agentConfig.apiSource ?? null,
|
|
527
|
+
provider: providerConfig.provider,
|
|
528
|
+
model: providerConfig.model,
|
|
529
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
530
|
+
proxyServer: summarizeEndpoint(providerConfig.serverUrl) ?? null,
|
|
531
|
+
hasAuthToken: Boolean(providerConfig.authToken),
|
|
532
|
+
hasApiKey: Boolean(providerConfig.apiKey),
|
|
533
|
+
apiKeyHeader: providerConfig.apiKeyHeader ?? null,
|
|
534
|
+
useServerProxy: agentConfig.useServerProxy ?? null,
|
|
535
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig.customProviderUrl) ?? null,
|
|
536
|
+
});
|
|
479
537
|
return {
|
|
480
538
|
model: providerConfig.model,
|
|
481
539
|
complete: async (messages, options) => {
|
|
@@ -492,6 +550,16 @@ export function createCliLocalRuntimeAdapter(
|
|
|
492
550
|
tools,
|
|
493
551
|
});
|
|
494
552
|
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
553
|
+
logLocalRuntimeDiagnostic("provider.request.start", {
|
|
554
|
+
agentKey: agentConfig.key,
|
|
555
|
+
transport: "platform-proxy",
|
|
556
|
+
requestUrl: summarizeEndpoint(request.url) ?? null,
|
|
557
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
558
|
+
model: providerConfig.model,
|
|
559
|
+
messageCount: messages.length,
|
|
560
|
+
toolCount: tools.length,
|
|
561
|
+
timeoutMs: options?.timeoutMs ?? null,
|
|
562
|
+
});
|
|
495
563
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
496
564
|
...request.init,
|
|
497
565
|
...(signal ? { signal } : {}),
|
|
@@ -499,7 +567,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
499
567
|
sleep: deps.sleep,
|
|
500
568
|
loopbackRequest,
|
|
501
569
|
});
|
|
502
|
-
const
|
|
570
|
+
const raw = await res.text().catch(() => "");
|
|
571
|
+
logLocalRuntimeDiagnostic("provider.request.result", {
|
|
572
|
+
agentKey: agentConfig.key,
|
|
573
|
+
transport: "platform-proxy",
|
|
574
|
+
status: res.status,
|
|
575
|
+
ok: res.ok,
|
|
576
|
+
responseBytes: raw.length,
|
|
577
|
+
});
|
|
578
|
+
const data = parsePlatformChatCompletionData(raw);
|
|
503
579
|
if (!res.ok) {
|
|
504
580
|
throw new Error(`platform provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
505
581
|
}
|
|
@@ -516,6 +592,18 @@ export function createCliLocalRuntimeAdapter(
|
|
|
516
592
|
agentConfig,
|
|
517
593
|
env: deps.env,
|
|
518
594
|
});
|
|
595
|
+
logLocalRuntimeDiagnostic("provider.selected", {
|
|
596
|
+
agentKey: agentConfig.key,
|
|
597
|
+
transport: "direct-openai-compatible",
|
|
598
|
+
apiSource: agentConfig.apiSource ?? null,
|
|
599
|
+
provider: providerConfig.provider,
|
|
600
|
+
model: providerConfig.model,
|
|
601
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
602
|
+
hasApiKey: Boolean(providerConfig.apiKey),
|
|
603
|
+
apiKeyHeader: providerConfig.apiKeyHeader ?? null,
|
|
604
|
+
useServerProxy: agentConfig.useServerProxy ?? null,
|
|
605
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig.customProviderUrl) ?? null,
|
|
606
|
+
});
|
|
519
607
|
return {
|
|
520
608
|
model: providerConfig.model,
|
|
521
609
|
complete: async (messages, options) => {
|
|
@@ -532,6 +620,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
532
620
|
tools,
|
|
533
621
|
});
|
|
534
622
|
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
623
|
+
logLocalRuntimeDiagnostic("provider.request.start", {
|
|
624
|
+
agentKey: agentConfig.key,
|
|
625
|
+
transport: "direct-openai-compatible",
|
|
626
|
+
requestUrl: summarizeEndpoint(request.url) ?? null,
|
|
627
|
+
model: providerConfig.model,
|
|
628
|
+
messageCount: messages.length,
|
|
629
|
+
toolCount: tools.length,
|
|
630
|
+
timeoutMs: options?.timeoutMs ?? null,
|
|
631
|
+
});
|
|
535
632
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
536
633
|
...request.init,
|
|
537
634
|
...(signal ? { signal } : {}),
|
|
@@ -539,7 +636,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
539
636
|
sleep: deps.sleep,
|
|
540
637
|
loopbackRequest,
|
|
541
638
|
});
|
|
542
|
-
const
|
|
639
|
+
const raw = await res.text().catch(() => "");
|
|
640
|
+
logLocalRuntimeDiagnostic("provider.request.result", {
|
|
641
|
+
agentKey: agentConfig.key,
|
|
642
|
+
transport: "direct-openai-compatible",
|
|
643
|
+
status: res.status,
|
|
644
|
+
ok: res.ok,
|
|
645
|
+
responseBytes: raw.length,
|
|
646
|
+
});
|
|
647
|
+
const data = parseJsonObject(raw);
|
|
543
648
|
if (!res.ok) {
|
|
544
649
|
throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
545
650
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runLocalAgentTurn, type LocalAgentToolEvent } from "../agent-runtime/localLoop";
|
|
2
|
+
import { resolveLocalRuntimeEnvFromPolicy } from "../agent-runtime/runtimeToolPolicy";
|
|
2
3
|
import {
|
|
3
4
|
assertMachineRunAllowed as defaultAssertMachineRunAllowed,
|
|
4
5
|
resolveMachineRunPermissionPolicy as defaultResolveMachineRunPermissionPolicy,
|
|
@@ -96,6 +97,37 @@ function logConnectorRun(message: string, fields: Record<string, unknown> = {})
|
|
|
96
97
|
console.error(`[nolo-connector] ${message} ${JSON.stringify(fields)}`);
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function summarizeEndpoint(value: unknown) {
|
|
101
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
102
|
+
try {
|
|
103
|
+
const url = new URL(value);
|
|
104
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
105
|
+
} catch {
|
|
106
|
+
return "invalid-url";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function summarizeAgentConfigForLogs(agentConfig: any) {
|
|
111
|
+
const runtimeBinding = isRecord(agentConfig?.runtimeBinding) ? agentConfig.runtimeBinding : {};
|
|
112
|
+
return {
|
|
113
|
+
apiSource: agentConfig?.apiSource ?? null,
|
|
114
|
+
provider: agentConfig?.provider ?? null,
|
|
115
|
+
cliProvider: agentConfig?.cliProvider ?? null,
|
|
116
|
+
model: agentConfig?.model ?? null,
|
|
117
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig?.customProviderUrl) ?? null,
|
|
118
|
+
useServerProxy: agentConfig?.useServerProxy ?? null,
|
|
119
|
+
skipLocalRuntimeEnsure: agentConfig?.skipLocalRuntimeEnsure ?? null,
|
|
120
|
+
hasApiKey: typeof agentConfig?.apiKey === "string" && agentConfig.apiKey.length > 0,
|
|
121
|
+
apiKeyHeader: agentConfig?.apiKeyHeader ?? null,
|
|
122
|
+
hasApiKeyFromAgentKey:
|
|
123
|
+
typeof agentConfig?.apiKeyFromAgentKey === "string" &&
|
|
124
|
+
agentConfig.apiKeyFromAgentKey.length > 0,
|
|
125
|
+
runtimeBindingKind: runtimeBinding.kind ?? null,
|
|
126
|
+
runtimeMachineId: runtimeBinding.machineId ?? null,
|
|
127
|
+
connectorSurface: runtimeBinding.connectorSurface ?? runtimeBinding.surface ?? null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
99
131
|
function isRecord(value: unknown): value is Record<string, any> {
|
|
100
132
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
101
133
|
}
|
|
@@ -194,11 +226,8 @@ function mergeToolNames(...values: unknown[]) {
|
|
|
194
226
|
return names;
|
|
195
227
|
}
|
|
196
228
|
|
|
197
|
-
function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
|
|
198
|
-
return
|
|
199
|
-
...runtimeEnv,
|
|
200
|
-
...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
|
|
201
|
-
};
|
|
229
|
+
export function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
|
|
230
|
+
return resolveLocalRuntimeEnvFromPolicy(runtimeEnv, policy);
|
|
202
231
|
}
|
|
203
232
|
|
|
204
233
|
function forwardedUserAuthToken(parsed: any): string {
|
|
@@ -569,6 +598,15 @@ export async function handleConnectorRunMessage(
|
|
|
569
598
|
if (!requestsLocalWorkspaceRuntime(runtimePolicy) && !isMachineBoundLocalCustomProvider(agentConfig)) {
|
|
570
599
|
throw new Error("Connector can only execute non-CLI agents when runtimeToolPolicySnapshot requests a local workspace runtime.");
|
|
571
600
|
}
|
|
601
|
+
logConnectorRun("agent.run.local-runtime.start", {
|
|
602
|
+
requestId: parsed.requestId,
|
|
603
|
+
agentKey: parsed.payload?.agentKey,
|
|
604
|
+
cwd,
|
|
605
|
+
timeout,
|
|
606
|
+
machineBoundLocalCustomProvider: isMachineBoundLocalCustomProvider(agentConfig),
|
|
607
|
+
runtimePolicy: Boolean(runtimePolicy),
|
|
608
|
+
...summarizeAgentConfigForLogs(agentConfig),
|
|
609
|
+
});
|
|
572
610
|
baseSha = await readConnectorGitHead(cwd);
|
|
573
611
|
const result = await runConnectorLocalRuntimeAgent({
|
|
574
612
|
parsed,
|
|
@@ -607,6 +645,8 @@ export async function handleConnectorRunMessage(
|
|
|
607
645
|
logConnectorRun("agent.run.error", {
|
|
608
646
|
requestId: parsed.requestId,
|
|
609
647
|
agentKey: parsed.payload?.agentKey,
|
|
648
|
+
...summarizeAgentConfigForLogs(agentConfig),
|
|
649
|
+
errorName: error instanceof Error ? error.name : null,
|
|
610
650
|
error: error instanceof Error ? error.message : String(error),
|
|
611
651
|
});
|
|
612
652
|
send(JSON.stringify({
|
package/client/agentRun.test.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
classifyTaskRunReviewStatus,
|
|
6
6
|
findServerPlatformTools,
|
|
7
7
|
runAgentTurn,
|
|
8
|
-
shouldUseScriptBridge,
|
|
9
8
|
} from "./agentRun";
|
|
10
9
|
import { NOLO_PROJECT_MANAGER_AGENT_KEY } from "../agentAliases";
|
|
11
10
|
|
|
@@ -34,12 +33,6 @@ describe("cli agent run client", () => {
|
|
|
34
33
|
expect(classifyTaskRunReviewStatus("I looked at the diff and have notes")).toBeUndefined();
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
test("uses the repo script bridge only when no auth token is available", () => {
|
|
38
|
-
expect(shouldUseScriptBridge({ hasAuthToken: false, scriptPathExists: true })).toBe(true);
|
|
39
|
-
expect(shouldUseScriptBridge({ hasAuthToken: true, scriptPathExists: true })).toBe(false);
|
|
40
|
-
expect(shouldUseScriptBridge({ hasAuthToken: false, scriptPathExists: false })).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
36
|
test("identifies server platform tools that local runtime cannot provide", () => {
|
|
44
37
|
expect(findServerPlatformTools(["readWorkspaceFile", "queryTableRows", "updateTableRow"])).toEqual([
|
|
45
38
|
"queryTableRows",
|
|
@@ -61,7 +54,6 @@ describe("cli agent run client", () => {
|
|
|
61
54
|
scriptDir: "C:/missing/scripts",
|
|
62
55
|
env: { AUTH_TOKEN: "token-123" },
|
|
63
56
|
output,
|
|
64
|
-
scriptPathExists: () => false,
|
|
65
57
|
fetchImpl: async (url, init) => {
|
|
66
58
|
requests.push({
|
|
67
59
|
url: String(url),
|
|
@@ -120,7 +112,6 @@ describe("cli agent run client", () => {
|
|
|
120
112
|
workItemId: "frontend-filter",
|
|
121
113
|
artifactIds: ["artifact-1"],
|
|
122
114
|
},
|
|
123
|
-
scriptPathExists: () => false,
|
|
124
115
|
fetchImpl: async (_url, init) => {
|
|
125
116
|
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
126
117
|
return Response.json({ content: "ok", dialogId: "dialog-1" });
|
|
@@ -160,7 +151,6 @@ describe("cli agent run client", () => {
|
|
|
160
151
|
taskRunRecorder: async (args) => {
|
|
161
152
|
writebacks.push(args);
|
|
162
153
|
},
|
|
163
|
-
scriptPathExists: () => false,
|
|
164
154
|
fetchImpl: async () => {
|
|
165
155
|
return Response.json({ content: "patched", dialogId: "dialog-1" });
|
|
166
156
|
},
|
|
@@ -193,7 +183,6 @@ describe("cli agent run client", () => {
|
|
|
193
183
|
env: { AUTH_TOKEN: "token-123" },
|
|
194
184
|
output,
|
|
195
185
|
runtimeMode: "server",
|
|
196
|
-
scriptPathExists: () => false,
|
|
197
186
|
fetchImpl: async (_url, init) => {
|
|
198
187
|
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
199
188
|
return Response.json({ content: "ok" });
|
|
@@ -206,7 +195,7 @@ describe("cli agent run client", () => {
|
|
|
206
195
|
]);
|
|
207
196
|
});
|
|
208
197
|
|
|
209
|
-
test("sends
|
|
198
|
+
test("sends CLI dialog metadata over HTTP", async () => {
|
|
210
199
|
const output = new CaptureOutput();
|
|
211
200
|
const requests: Array<{ body: any }> = [];
|
|
212
201
|
|
|
@@ -227,7 +216,6 @@ describe("cli agent run client", () => {
|
|
|
227
216
|
env: { AUTH_TOKEN: "token-123" },
|
|
228
217
|
output,
|
|
229
218
|
runtimeMode: "server",
|
|
230
|
-
scriptPathExists: () => false,
|
|
231
219
|
fetchImpl: async (_url, init) => {
|
|
232
220
|
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
233
221
|
return Response.json({ dialogId: "dialog-1", status: "queued" });
|
|
@@ -271,7 +259,6 @@ describe("cli agent run client", () => {
|
|
|
271
259
|
writebacks.push(args);
|
|
272
260
|
},
|
|
273
261
|
output,
|
|
274
|
-
scriptPathExists: () => false,
|
|
275
262
|
fetchImpl: async () => {
|
|
276
263
|
return Response.json({ dialogId: "dialog-bg", status: "pending" }, { status: 202 });
|
|
277
264
|
},
|
|
@@ -319,7 +306,6 @@ describe("cli agent run client", () => {
|
|
|
319
306
|
throw new Error("no tools expected");
|
|
320
307
|
},
|
|
321
308
|
},
|
|
322
|
-
scriptPathExists: () => false,
|
|
323
309
|
fetchImpl: async () => {
|
|
324
310
|
throw new Error("HTTP should not be called for forced local runs");
|
|
325
311
|
},
|
|
@@ -371,7 +357,6 @@ describe("cli agent run client", () => {
|
|
|
371
357
|
}),
|
|
372
358
|
executeTool: async () => ({ content: "ok" }),
|
|
373
359
|
},
|
|
374
|
-
scriptPathExists: () => false,
|
|
375
360
|
fetchImpl: async () => {
|
|
376
361
|
throw new Error("HTTP should not be called for forced local runs");
|
|
377
362
|
},
|
|
@@ -432,7 +417,6 @@ describe("cli agent run client", () => {
|
|
|
432
417
|
return { content: "ok" };
|
|
433
418
|
},
|
|
434
419
|
},
|
|
435
|
-
scriptPathExists: () => false,
|
|
436
420
|
});
|
|
437
421
|
|
|
438
422
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-retry" });
|
|
@@ -487,7 +471,6 @@ describe("cli agent run client", () => {
|
|
|
487
471
|
}),
|
|
488
472
|
executeTool: async () => ({ content: "clean" }),
|
|
489
473
|
},
|
|
490
|
-
scriptPathExists: () => false,
|
|
491
474
|
});
|
|
492
475
|
|
|
493
476
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
@@ -548,7 +531,6 @@ describe("cli agent run client", () => {
|
|
|
548
531
|
}),
|
|
549
532
|
executeTool: async () => ({ content: "clean" }),
|
|
550
533
|
},
|
|
551
|
-
scriptPathExists: () => false,
|
|
552
534
|
});
|
|
553
535
|
|
|
554
536
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
@@ -604,7 +586,6 @@ describe("cli agent run client", () => {
|
|
|
604
586
|
throw new Error("no tools expected");
|
|
605
587
|
},
|
|
606
588
|
},
|
|
607
|
-
scriptPathExists: () => false,
|
|
608
589
|
});
|
|
609
590
|
|
|
610
591
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-bg" });
|
|
@@ -648,7 +629,6 @@ describe("cli agent run client", () => {
|
|
|
648
629
|
throw new Error("no tools expected");
|
|
649
630
|
},
|
|
650
631
|
},
|
|
651
|
-
scriptPathExists: () => false,
|
|
652
632
|
fetchImpl: async (url) => {
|
|
653
633
|
httpCalls.push(String(url));
|
|
654
634
|
return Response.json({ content: "server" });
|
|
@@ -701,7 +681,6 @@ describe("cli agent run client", () => {
|
|
|
701
681
|
throw new Error("no tools expected");
|
|
702
682
|
},
|
|
703
683
|
},
|
|
704
|
-
scriptPathExists: () => false,
|
|
705
684
|
fetchImpl: async (url) => {
|
|
706
685
|
httpCalls.push(String(url));
|
|
707
686
|
return Response.json({ content: "server" });
|
|
@@ -748,7 +727,6 @@ describe("cli agent run client", () => {
|
|
|
748
727
|
throw new Error("no tools expected");
|
|
749
728
|
},
|
|
750
729
|
},
|
|
751
|
-
scriptPathExists: () => false,
|
|
752
730
|
fetchImpl: async (url, init) => {
|
|
753
731
|
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
754
732
|
return Response.json({ content: "server fallback", dialogId: "dialog-server-fallback" });
|
|
@@ -798,7 +776,6 @@ describe("cli agent run client", () => {
|
|
|
798
776
|
throw new Error("local tools should be skipped");
|
|
799
777
|
},
|
|
800
778
|
},
|
|
801
|
-
scriptPathExists: () => false,
|
|
802
779
|
fetchImpl: async (url, init) => {
|
|
803
780
|
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
804
781
|
return Response.json({ content: "server ok", dialogId: "dialog-server" });
|
|
@@ -852,7 +829,6 @@ describe("cli agent run client", () => {
|
|
|
852
829
|
throw new Error("local tools should be skipped");
|
|
853
830
|
},
|
|
854
831
|
},
|
|
855
|
-
scriptPathExists: () => false,
|
|
856
832
|
fetchImpl: async (url, init) => {
|
|
857
833
|
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
858
834
|
return Response.json({ content: "server ok", dialogId: "dialog-server-policy" });
|
|
@@ -898,7 +874,6 @@ describe("cli agent run client", () => {
|
|
|
898
874
|
throw new Error("local tools should be skipped");
|
|
899
875
|
},
|
|
900
876
|
},
|
|
901
|
-
scriptPathExists: () => false,
|
|
902
877
|
fetchImpl: async (url, init) => {
|
|
903
878
|
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
904
879
|
return Response.json({ content: "server ok", dialogId: "dialog-server" });
|
|
@@ -942,7 +917,6 @@ describe("cli agent run client", () => {
|
|
|
942
917
|
},
|
|
943
918
|
};
|
|
944
919
|
},
|
|
945
|
-
scriptPathExists: () => false,
|
|
946
920
|
fetchImpl: async () => {
|
|
947
921
|
throw new Error("HTTP should not be called for env local runs");
|
|
948
922
|
},
|
|
@@ -964,7 +938,6 @@ describe("cli agent run client", () => {
|
|
|
964
938
|
scriptDir: "C:/missing/scripts",
|
|
965
939
|
env: { AUTH_TOKEN: "token-123" },
|
|
966
940
|
output,
|
|
967
|
-
scriptPathExists: () => false,
|
|
968
941
|
fetchImpl: async () =>
|
|
969
942
|
new Response(
|
|
970
943
|
[
|
|
@@ -998,7 +971,6 @@ describe("cli agent run client", () => {
|
|
|
998
971
|
scriptDir: "C:/missing/scripts",
|
|
999
972
|
env: { AUTH_TOKEN: "token-123" },
|
|
1000
973
|
output,
|
|
1001
|
-
scriptPathExists: () => false,
|
|
1002
974
|
fetchImpl: async () => {
|
|
1003
975
|
let sent = false;
|
|
1004
976
|
return new Response(
|
|
@@ -1054,14 +1026,13 @@ describe("cli agent run client", () => {
|
|
|
1054
1026
|
scriptDir: "C:/missing/scripts",
|
|
1055
1027
|
env: {},
|
|
1056
1028
|
output,
|
|
1057
|
-
scriptPathExists: () => false,
|
|
1058
1029
|
fetchImpl: async () => {
|
|
1059
1030
|
throw new Error("fetch should not be called");
|
|
1060
1031
|
},
|
|
1061
1032
|
});
|
|
1062
1033
|
|
|
1063
1034
|
expect(result.exitCode).toBe(1);
|
|
1064
|
-
expect(output.text()).toContain("
|
|
1035
|
+
expect(output.text()).toContain("Run `nolo login`");
|
|
1065
1036
|
});
|
|
1066
1037
|
|
|
1067
1038
|
test("prints a friendly connection hint instead of crashing on transport errors", async () => {
|
|
@@ -1075,7 +1046,6 @@ describe("cli agent run client", () => {
|
|
|
1075
1046
|
scriptDir: "C:/missing/scripts",
|
|
1076
1047
|
env: { AUTH_TOKEN: "token-123" },
|
|
1077
1048
|
output,
|
|
1078
|
-
scriptPathExists: () => false,
|
|
1079
1049
|
fetchImpl: async () => {
|
|
1080
1050
|
throw new Error("ConnectionRefused");
|
|
1081
1051
|
},
|
package/client/agentRun.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
4
3
|
import { LOCAL_AGENT_CONFIG_MISSING_CODE } from "../agent-runtime/localLoop";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
NOLO_FULLSTACK_AGENT_KEY,
|
|
5
|
+
MIMO_MONTH_AGENT_KEY,
|
|
8
6
|
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
9
|
-
NOLO_REVIEW_AGENT_KEY,
|
|
10
7
|
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
8
|
+
WIN_CODEX_AGENT_KEY,
|
|
11
9
|
} from "../agentAliases";
|
|
12
10
|
import type { LocalAgentToolEvent } from "../agent-runtime/localLoop";
|
|
13
11
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
14
12
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
15
13
|
import { createStreamingTextWriter } from "./streamingOutput";
|
|
16
14
|
import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
|
|
17
|
-
|
|
18
|
-
type EnvLike = Record<string, string | undefined>;
|
|
19
|
-
|
|
15
|
+
|
|
16
|
+
type EnvLike = Record<string, string | undefined>;
|
|
17
|
+
|
|
20
18
|
type OutputLike = {
|
|
21
19
|
write(chunk: string): unknown;
|
|
22
20
|
};
|
|
@@ -48,7 +46,7 @@ type RunAgentTurnOptions = {
|
|
|
48
46
|
noStream?: boolean;
|
|
49
47
|
noDefaultTestRoot?: boolean;
|
|
50
48
|
scriptDir: string;
|
|
51
|
-
env: EnvLike;
|
|
49
|
+
env: EnvLike;
|
|
52
50
|
output: OutputLike;
|
|
53
51
|
runtimeMode?: AgentRuntimeRequestedMode;
|
|
54
52
|
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
@@ -66,20 +64,14 @@ type RunAgentTurnOptions = {
|
|
|
66
64
|
errorCode?: string;
|
|
67
65
|
toolEvidence?: TaskRunToolEvidence;
|
|
68
66
|
}) => Promise<void>;
|
|
69
|
-
scriptPathExists?: (path: string) => boolean;
|
|
70
67
|
fetchImpl?: typeof fetch;
|
|
71
68
|
};
|
|
72
|
-
|
|
69
|
+
|
|
73
70
|
export type RunAgentTurnResult = {
|
|
74
71
|
exitCode: number;
|
|
75
72
|
dialogId?: string;
|
|
76
73
|
streamInterrupted?: boolean;
|
|
77
74
|
};
|
|
78
|
-
|
|
79
|
-
type ScriptBridgeDecision = {
|
|
80
|
-
hasAuthToken: boolean;
|
|
81
|
-
scriptPathExists: boolean;
|
|
82
|
-
};
|
|
83
75
|
|
|
84
76
|
const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
85
77
|
"addTableRow",
|
|
@@ -94,10 +86,9 @@ const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
|
94
86
|
]);
|
|
95
87
|
|
|
96
88
|
const KNOWN_SERVER_PLATFORM_AGENT_KEYS = new Set([
|
|
97
|
-
|
|
98
|
-
NOLO_FULLSTACK_AGENT_KEY,
|
|
89
|
+
MIMO_MONTH_AGENT_KEY,
|
|
99
90
|
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
100
|
-
|
|
91
|
+
WIN_CODEX_AGENT_KEY,
|
|
101
92
|
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
102
93
|
]);
|
|
103
94
|
|
|
@@ -125,10 +116,6 @@ const KNOWN_SERVER_PLATFORM_AGENT_ALIASES = new Set([
|
|
|
125
116
|
"reviewer",
|
|
126
117
|
]);
|
|
127
118
|
|
|
128
|
-
export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
|
|
129
|
-
return !decision.hasAuthToken && decision.scriptPathExists;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
119
|
export function findServerPlatformTools(toolNames?: string[]) {
|
|
133
120
|
if (!Array.isArray(toolNames)) return [];
|
|
134
121
|
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
@@ -514,60 +501,6 @@ function buildTransportErrorHint(serverUrl: string, error: unknown) {
|
|
|
514
501
|
return detail;
|
|
515
502
|
}
|
|
516
503
|
|
|
517
|
-
async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string) {
|
|
518
|
-
options.output.write(`\n${options.agentName} -> working via local script...\n\n`);
|
|
519
|
-
const proc = Bun.spawn({
|
|
520
|
-
cmd: [
|
|
521
|
-
process.execPath,
|
|
522
|
-
scriptPath,
|
|
523
|
-
"--agent",
|
|
524
|
-
options.agentKey,
|
|
525
|
-
"--server",
|
|
526
|
-
options.serverUrl,
|
|
527
|
-
"--msg",
|
|
528
|
-
options.message,
|
|
529
|
-
...(options.taskRunContext?.rowDbKey
|
|
530
|
-
? ["--task-row-dbkey", options.taskRunContext.rowDbKey]
|
|
531
|
-
: []),
|
|
532
|
-
...(options.taskRunContext?.taskRunId
|
|
533
|
-
? ["--task-run-id", options.taskRunContext.taskRunId]
|
|
534
|
-
: []),
|
|
535
|
-
...(options.taskRunContext?.workItemId
|
|
536
|
-
? ["--work-item-id", options.taskRunContext.workItemId]
|
|
537
|
-
: []),
|
|
538
|
-
...(options.taskRunContext?.artifactIds?.length
|
|
539
|
-
? ["--artifact-ids", options.taskRunContext.artifactIds.join(",")]
|
|
540
|
-
: []),
|
|
541
|
-
...(options.noDefaultTestRoot ? ["--no-default-test-root"] : []),
|
|
542
|
-
...(options.continueDialogId
|
|
543
|
-
? ["--continue", options.continueDialogId]
|
|
544
|
-
: []),
|
|
545
|
-
...(options.spaceId ? ["--space", options.spaceId] : []),
|
|
546
|
-
...(options.category ? ["--category", options.category] : []),
|
|
547
|
-
...(options.inheritedFromDialogKey
|
|
548
|
-
? ["--inherit-from-dialog", options.inheritedFromDialogKey]
|
|
549
|
-
: []),
|
|
550
|
-
...(options.background ? ["--bg"] : []),
|
|
551
|
-
...(typeof options.timeoutMs === "number" ? ["--timeout-ms", String(options.timeoutMs)] : []),
|
|
552
|
-
...(options.noStream ? ["--no-stream"] : []),
|
|
553
|
-
],
|
|
554
|
-
stdin: "inherit",
|
|
555
|
-
stdout: "inherit",
|
|
556
|
-
stderr: "inherit",
|
|
557
|
-
env: {
|
|
558
|
-
...process.env,
|
|
559
|
-
...options.env,
|
|
560
|
-
ASSISTANT_LABEL: options.agentName,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const exitCode = await proc.exited;
|
|
565
|
-
if (exitCode !== 0) {
|
|
566
|
-
options.output.write(`\n[nolo] Agent run exited with code ${exitCode}.\n`);
|
|
567
|
-
}
|
|
568
|
-
return { exitCode };
|
|
569
|
-
}
|
|
570
|
-
|
|
571
504
|
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
572
505
|
options.output.write(`\n${options.agentName} -> working...\n`);
|
|
573
506
|
|
|
@@ -927,8 +860,6 @@ async function readStreamingAgentRun(
|
|
|
927
860
|
|
|
928
861
|
export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
929
862
|
const authToken = resolveAuthToken(options.env);
|
|
930
|
-
const scriptPath = join(options.scriptDir, "chatWithAgent.ts");
|
|
931
|
-
const scriptPathExists = (options.scriptPathExists ?? existsSync)(scriptPath);
|
|
932
863
|
const runtimeMode = resolveRequestedRuntimeMode(options);
|
|
933
864
|
|
|
934
865
|
if (runtimeMode === "local") {
|
|
@@ -969,17 +900,13 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
969
900
|
}
|
|
970
901
|
}
|
|
971
902
|
|
|
972
|
-
if (
|
|
973
|
-
|
|
903
|
+
if (!authToken) {
|
|
904
|
+
options.output.write(
|
|
905
|
+
"[nolo] This install needs an auth token before it can talk to agents.\n" +
|
|
906
|
+
"Run `nolo login`, or set AUTH_TOKEN / NOLO_SERVER for non-interactive runs.\n"
|
|
907
|
+
);
|
|
908
|
+
return { exitCode: 1 };
|
|
974
909
|
}
|
|
975
910
|
|
|
976
|
-
if (!authToken) {
|
|
977
|
-
options.output.write(
|
|
978
|
-
"[nolo] This install needs an auth token before it can talk to agents.\n" +
|
|
979
|
-
"Set AUTH_TOKEN, or use the repo-local CLI where the dev script bridge is available.\n"
|
|
980
|
-
);
|
|
981
|
-
return { exitCode: 1 };
|
|
982
|
-
}
|
|
983
|
-
|
|
984
911
|
return runHttpAgentTurn(options, authToken);
|
|
985
912
|
}
|