nolo-cli 0.1.33 → 0.1.34
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 +4 -4
- package/agent-runtime/agentRecordConfig.ts +8 -0
- package/agent-runtime/hostAdapter.ts +9 -1
- package/agent-runtime/index.ts +10 -0
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +70 -21
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +48 -71
- package/agent-runtime/providerResolution.ts +244 -0
- package/agent-runtime/runtimeToolPolicy.ts +33 -0
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentInternalCommandEntries.ts +2 -0
- package/agentListCommands.ts +2 -2
- package/agentPullCommand.ts +6 -11
- package/agentRecordCommands.ts +87 -2
- package/agentRecordHelpers.ts +87 -4
- package/agentRuntimeLocal.ts +3 -0
- package/ai/tools/toolNameAliases.ts +70 -0
- package/cli/agentInternalCommandEntries.ts +2 -0
- package/cli/agentListCommands.ts +2 -2
- package/cli/agentPullCommand.ts +6 -11
- package/cli/agentRecordCommands.ts +87 -2
- package/cli/agentRecordHelpers.ts +87 -4
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +80 -12
- package/cli/client/agentRun.ts +1 -0
- package/cli/client/localRuntimeAdapter.ts +44 -7
- package/cli/commandRegistry.ts +2 -2
- package/cli/machineCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +29 -9
- package/cli/offlineMarxistsAgentCommand.ts +1 -1
- package/cli/runtimeDoctorCommands.ts +10 -4
- package/cli/tableCommands.ts +1 -1
- package/cli/taskRunCommand.ts +1 -1
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +80 -12
- package/client/agentRun.ts +1 -0
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +287 -3
- package/client/localRuntimeAdapter.ts +44 -7
- package/commandRegistry.ts +2 -2
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/db.ts +15 -34
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +75 -0
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/machineCommands.ts +1 -1
- package/machineWsRunDispatch.ts +29 -9
- package/offlineMarxistsAgentCommand.ts +1 -1
- package/package.json +10 -2
- package/runtimeDoctorCommands.ts +10 -4
- package/tableCommands.ts +1 -1
- package/taskRunCommand.ts +1 -1
package/cli/cliEnvHelpers.ts
CHANGED
|
@@ -3,19 +3,17 @@ import { NOLO_CLUSTER_SERVERS } from "../database/config";
|
|
|
3
3
|
|
|
4
4
|
export type EnvLike = Record<string, string | undefined>;
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function resolveServerUrlFromEnv(env: EnvLike) {
|
|
7
7
|
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
...NOLO_CLUSTER_SERVERS,
|
|
18
|
-
];
|
|
10
|
+
type ResolverInput = string[] | EnvLike;
|
|
11
|
+
|
|
12
|
+
function isCliArgs(value: ResolverInput): value is string[] {
|
|
13
|
+
return Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildServerCandidates(values: Array<string | null | undefined>) {
|
|
19
17
|
return [...new Set(
|
|
20
18
|
values
|
|
21
19
|
.filter((value): value is string => typeof value === "string" && value.trim().length > 0)
|
|
@@ -23,8 +21,78 @@ export function resolveServerCandidates(env: EnvLike, preferred?: string | null)
|
|
|
23
21
|
)];
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
export function
|
|
27
|
-
|
|
24
|
+
export function resolveServerUrl(env: EnvLike): string;
|
|
25
|
+
export function resolveServerUrl(args: string[], env: EnvLike): string;
|
|
26
|
+
export function resolveServerUrl(input: ResolverInput, env?: EnvLike) {
|
|
27
|
+
if (isCliArgs(input)) {
|
|
28
|
+
const explicit =
|
|
29
|
+
readOption(input, "--server-url") ||
|
|
30
|
+
readOption(input, "--server");
|
|
31
|
+
return explicit?.replace(/\/+$/, "") || resolveServerUrlFromEnv(env ?? {});
|
|
32
|
+
}
|
|
33
|
+
return resolveServerUrlFromEnv(input);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveServerCandidates(env: EnvLike, preferred?: string | null): string[];
|
|
37
|
+
export function resolveServerCandidates(
|
|
38
|
+
args: string[],
|
|
39
|
+
env: EnvLike,
|
|
40
|
+
preferred?: string | null,
|
|
41
|
+
): string[];
|
|
42
|
+
export function resolveServerCandidates(
|
|
43
|
+
input: ResolverInput,
|
|
44
|
+
envOrPreferred?: EnvLike | string | null,
|
|
45
|
+
preferred?: string | null,
|
|
46
|
+
) {
|
|
47
|
+
if (isCliArgs(input)) {
|
|
48
|
+
const env = (envOrPreferred ?? {}) as EnvLike;
|
|
49
|
+
const explicitServer = resolveServerUrl(input, env);
|
|
50
|
+
return buildServerCandidates([
|
|
51
|
+
explicitServer,
|
|
52
|
+
preferred?.trim(),
|
|
53
|
+
env.NOLO_SERVER,
|
|
54
|
+
env.BASE_URL,
|
|
55
|
+
env.NOLO_SERVER_URL,
|
|
56
|
+
DEFAULT_NOLO_SERVER_URL,
|
|
57
|
+
...NOLO_CLUSTER_SERVERS,
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const env = input;
|
|
62
|
+
return buildServerCandidates([
|
|
63
|
+
typeof envOrPreferred === "string" ? envOrPreferred.trim() : undefined,
|
|
64
|
+
env.NOLO_SERVER,
|
|
65
|
+
env.BASE_URL,
|
|
66
|
+
env.NOLO_SERVER_URL,
|
|
67
|
+
DEFAULT_NOLO_SERVER_URL,
|
|
68
|
+
...NOLO_CLUSTER_SERVERS,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveAuthTokenFromEnv(env: EnvLike, extraEnvKeys: string[] = []) {
|
|
73
|
+
for (const key of ["AUTH_TOKEN", "AUTH", ...extraEnvKeys]) {
|
|
74
|
+
const value = env[key];
|
|
75
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
76
|
+
}
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveAuthToken(env: EnvLike, extraEnvKeys?: string[]): string;
|
|
81
|
+
export function resolveAuthToken(args: string[], env: EnvLike, extraEnvKeys?: string[]): string;
|
|
82
|
+
export function resolveAuthToken(
|
|
83
|
+
input: ResolverInput,
|
|
84
|
+
envOrExtraEnvKeys?: EnvLike | string[],
|
|
85
|
+
extraEnvKeys: string[] = [],
|
|
86
|
+
) {
|
|
87
|
+
if (isCliArgs(input)) {
|
|
88
|
+
const env = (envOrExtraEnvKeys ?? {}) as EnvLike;
|
|
89
|
+
return (
|
|
90
|
+
readOption(input, "--token") ||
|
|
91
|
+
readOption(input, "--machine-key") ||
|
|
92
|
+
resolveAuthTokenFromEnv(env, extraEnvKeys)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return resolveAuthTokenFromEnv(input, (envOrExtraEnvKeys as string[] | undefined) ?? []);
|
|
28
96
|
}
|
|
29
97
|
|
|
30
98
|
export function parseUserIdFromAuthToken(token: string) {
|
package/cli/client/agentRun.ts
CHANGED
|
@@ -778,6 +778,7 @@ async function runLocalAgentTurnForCli(
|
|
|
778
778
|
background: options.background,
|
|
779
779
|
noStream: options.noStream,
|
|
780
780
|
maxToolRounds: options.maxToolRounds,
|
|
781
|
+
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
781
782
|
...(traceLocalTools || collectToolEvidence
|
|
782
783
|
? {
|
|
783
784
|
onToolEvent: (event) => {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
parsePlatformChatCompletionData,
|
|
17
17
|
parsePlatformChatCompletionResponse,
|
|
18
18
|
resolvePlatformChatProviderConfig,
|
|
19
|
+
resolveRequestedRuntimeToolNames,
|
|
19
20
|
shouldUsePlatformChatProvider,
|
|
20
21
|
} from "../agentRuntimeLocal";
|
|
21
22
|
import type { AgentRuntimeChatMessage } from "../../agent-runtime";
|
|
@@ -169,6 +170,12 @@ async function defaultLoopbackRequest(input: FetchInput, init?: FetchInit) {
|
|
|
169
170
|
});
|
|
170
171
|
});
|
|
171
172
|
req.on("error", reject);
|
|
173
|
+
init?.signal?.addEventListener("abort", () => {
|
|
174
|
+
req.destroy(
|
|
175
|
+
init.signal?.reason instanceof Error ? init.signal.reason : new Error("request aborted")
|
|
176
|
+
);
|
|
177
|
+
reject(init.signal?.reason ?? new Error("request aborted"));
|
|
178
|
+
}, { once: true });
|
|
172
179
|
if (body) req.write(body);
|
|
173
180
|
req.end();
|
|
174
181
|
});
|
|
@@ -191,6 +198,7 @@ async function fetchWithTransientRetry(
|
|
|
191
198
|
}
|
|
192
199
|
return await fetchImpl(input, init);
|
|
193
200
|
} catch (error) {
|
|
201
|
+
if (init?.signal?.aborted) throw error;
|
|
194
202
|
if (!isTransientFetchError(error)) throw error;
|
|
195
203
|
lastError = error;
|
|
196
204
|
if (attempt < TRANSIENT_FETCH_MAX_ATTEMPTS) {
|
|
@@ -201,6 +209,18 @@ async function fetchWithTransientRetry(
|
|
|
201
209
|
throw lastError;
|
|
202
210
|
}
|
|
203
211
|
|
|
212
|
+
function buildRequestTimeoutSignal(timeoutMs?: number) {
|
|
213
|
+
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
|
|
217
|
+
return AbortSignal.timeout(timeoutMs);
|
|
218
|
+
}
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
setTimeout(() => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
221
|
+
return controller.signal;
|
|
222
|
+
}
|
|
223
|
+
|
|
204
224
|
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
205
225
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
206
226
|
return [
|
|
@@ -428,8 +448,11 @@ export function createCliLocalRuntimeAdapter(
|
|
|
428
448
|
store: await resolveStore(deps),
|
|
429
449
|
userId,
|
|
430
450
|
});
|
|
451
|
+
const requestedToolNames = agentConfig
|
|
452
|
+
? resolveRequestedRuntimeToolNames({ agentConfig })
|
|
453
|
+
: [];
|
|
431
454
|
activeAgentToolNames = buildLocalPolicyToolNames({
|
|
432
|
-
toolNames:
|
|
455
|
+
toolNames: requestedToolNames,
|
|
433
456
|
env: deps.env,
|
|
434
457
|
});
|
|
435
458
|
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
@@ -455,9 +478,12 @@ export function createCliLocalRuntimeAdapter(
|
|
|
455
478
|
});
|
|
456
479
|
return {
|
|
457
480
|
model: providerConfig.model,
|
|
458
|
-
complete: async (messages) => {
|
|
481
|
+
complete: async (messages, options) => {
|
|
482
|
+
const requestedToolNames = resolveRequestedRuntimeToolNames({
|
|
483
|
+
agentConfig,
|
|
484
|
+
});
|
|
459
485
|
const tools = buildOpenAiTools({
|
|
460
|
-
toolNames:
|
|
486
|
+
toolNames: requestedToolNames,
|
|
461
487
|
env: deps.env,
|
|
462
488
|
});
|
|
463
489
|
const request = buildPlatformChatCompletionRequest({
|
|
@@ -465,7 +491,11 @@ export function createCliLocalRuntimeAdapter(
|
|
|
465
491
|
messages,
|
|
466
492
|
tools,
|
|
467
493
|
});
|
|
468
|
-
const
|
|
494
|
+
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
495
|
+
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
496
|
+
...request.init,
|
|
497
|
+
...(signal ? { signal } : {}),
|
|
498
|
+
}, {
|
|
469
499
|
sleep: deps.sleep,
|
|
470
500
|
loopbackRequest,
|
|
471
501
|
});
|
|
@@ -488,9 +518,12 @@ export function createCliLocalRuntimeAdapter(
|
|
|
488
518
|
});
|
|
489
519
|
return {
|
|
490
520
|
model: providerConfig.model,
|
|
491
|
-
complete: async (messages) => {
|
|
521
|
+
complete: async (messages, options) => {
|
|
522
|
+
const requestedToolNames = resolveRequestedRuntimeToolNames({
|
|
523
|
+
agentConfig,
|
|
524
|
+
});
|
|
492
525
|
const tools = buildOpenAiTools({
|
|
493
|
-
toolNames:
|
|
526
|
+
toolNames: requestedToolNames,
|
|
494
527
|
env: deps.env,
|
|
495
528
|
});
|
|
496
529
|
const request = buildOpenAiCompatibleChatCompletionRequest({
|
|
@@ -498,7 +531,11 @@ export function createCliLocalRuntimeAdapter(
|
|
|
498
531
|
messages,
|
|
499
532
|
tools,
|
|
500
533
|
});
|
|
501
|
-
const
|
|
534
|
+
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
535
|
+
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
536
|
+
...request.init,
|
|
537
|
+
...(signal ? { signal } : {}),
|
|
538
|
+
}, {
|
|
502
539
|
sleep: deps.sleep,
|
|
503
540
|
loopbackRequest,
|
|
504
541
|
});
|
package/cli/commandRegistry.ts
CHANGED
|
@@ -49,8 +49,8 @@ export function renderHelpText() {
|
|
|
49
49
|
" nolo connect",
|
|
50
50
|
" nolo connect --watch",
|
|
51
51
|
" nolo connect --ws",
|
|
52
|
-
" nolo connect --ws --server-url https://api.nolo.chat --
|
|
53
|
-
" nolo connect --daemon --server-url https://api.nolo.chat --
|
|
52
|
+
" nolo connect --ws --server-url https://api.nolo.chat --machine-key sk_machine_xxx",
|
|
53
|
+
" nolo connect --daemon --server-url https://api.nolo.chat --machine-key sk_machine_xxx",
|
|
54
54
|
" nolo machine status",
|
|
55
55
|
" nolo doctor",
|
|
56
56
|
" nolo doctor runtime",
|
package/cli/machineCommands.ts
CHANGED
|
@@ -95,7 +95,7 @@ function resolveConnectServerUrl(args: string[], env: EnvLike) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
function resolveConnectAuthToken(args: string[], env: EnvLike) {
|
|
98
|
-
return readOption(args, "--
|
|
98
|
+
return readOption(args, "--machine-key") || readOption(args, "--token") || resolveAuthToken(env);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
const defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
@@ -148,6 +148,7 @@ function hasExplicitMachinePermissions(agentConfig: any) {
|
|
|
148
148
|
|
|
149
149
|
function runtimeWorkspacePermissionPolicy(
|
|
150
150
|
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
151
|
+
runtimeEnv: EnvLike,
|
|
151
152
|
cwd: string
|
|
152
153
|
): PermissionPolicy | null {
|
|
153
154
|
if (!requestsTaskWorktree(runtimePolicy)) return null;
|
|
@@ -155,7 +156,10 @@ function runtimeWorkspacePermissionPolicy(
|
|
|
155
156
|
mode: "full_access",
|
|
156
157
|
allowFilesystemRead: true,
|
|
157
158
|
allowFilesystemWrite: true,
|
|
158
|
-
allowShell:
|
|
159
|
+
allowShell:
|
|
160
|
+
["worktree", "dangerous", "1", "true"].includes(runtimeEnv.NOLO_LOCAL_SHELL_MODE || "") &&
|
|
161
|
+
Array.isArray(runtimePolicy?.runtimeTools) &&
|
|
162
|
+
runtimePolicy.runtimeTools.includes("execShell"),
|
|
159
163
|
writableRoots: [cwd],
|
|
160
164
|
};
|
|
161
165
|
}
|
|
@@ -445,7 +449,7 @@ export async function handleConnectorRunMessage(
|
|
|
445
449
|
artifactCwd = prepared.path;
|
|
446
450
|
}
|
|
447
451
|
const runtimePermissionPolicy = !hasExplicitMachinePermissions(agentConfig)
|
|
448
|
-
? runtimeWorkspacePermissionPolicy(runtimePolicy, cwd)
|
|
452
|
+
? runtimeWorkspacePermissionPolicy(runtimePolicy, runtimeEnv, cwd)
|
|
449
453
|
: null;
|
|
450
454
|
const effectivePermissionPolicy = scopePermissionPolicyToRuntimeWorkspace(
|
|
451
455
|
runtimePermissionPolicy ?? machinePermissionPolicy,
|
|
@@ -499,21 +503,37 @@ export async function handleConnectorRunMessage(
|
|
|
499
503
|
const heartbeat = setInterval(() => {
|
|
500
504
|
sendProgress({
|
|
501
505
|
...progressBase,
|
|
502
|
-
|
|
506
|
+
message: "cli-running",
|
|
503
507
|
updatedAt: Date.now(),
|
|
504
508
|
});
|
|
505
509
|
}, 15_000);
|
|
510
|
+
const effectiveRuntimeEnv = withForwardedUserAuthToken(runtimeEnv, parsed);
|
|
506
511
|
const result = await executeCli(provider, materializedPrompt.prompt, {
|
|
507
512
|
model: agentConfig.model || undefined,
|
|
508
513
|
timeout,
|
|
509
514
|
cwd,
|
|
510
515
|
yolo: true,
|
|
511
516
|
env: {
|
|
512
|
-
NOLO_SERVER:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
NOLO_SERVER:
|
|
518
|
+
effectiveRuntimeEnv.NOLO_SERVER ||
|
|
519
|
+
effectiveRuntimeEnv.NOLO_SERVER_URL ||
|
|
520
|
+
effectiveRuntimeEnv.BASE_URL,
|
|
521
|
+
NOLO_SERVER_URL:
|
|
522
|
+
effectiveRuntimeEnv.NOLO_SERVER_URL ||
|
|
523
|
+
effectiveRuntimeEnv.NOLO_SERVER ||
|
|
524
|
+
effectiveRuntimeEnv.BASE_URL,
|
|
525
|
+
BASE_URL:
|
|
526
|
+
effectiveRuntimeEnv.BASE_URL ||
|
|
527
|
+
effectiveRuntimeEnv.NOLO_SERVER ||
|
|
528
|
+
effectiveRuntimeEnv.NOLO_SERVER_URL,
|
|
529
|
+
AUTH_TOKEN:
|
|
530
|
+
effectiveRuntimeEnv.AUTH_TOKEN ||
|
|
531
|
+
effectiveRuntimeEnv.AUTH ||
|
|
532
|
+
effectiveRuntimeEnv.NOLO_MACHINE_API_KEY,
|
|
533
|
+
NOLO_MACHINE_API_KEY:
|
|
534
|
+
effectiveRuntimeEnv.NOLO_MACHINE_API_KEY ||
|
|
535
|
+
effectiveRuntimeEnv.AUTH_TOKEN ||
|
|
536
|
+
effectiveRuntimeEnv.AUTH,
|
|
517
537
|
},
|
|
518
538
|
}).finally(() => clearInterval(heartbeat));
|
|
519
539
|
logConnectorRun("agent.run.cli.result", {
|
|
@@ -555,7 +575,7 @@ export async function handleConnectorRunMessage(
|
|
|
555
575
|
baseSha = await readConnectorGitHead(cwd);
|
|
556
576
|
const result = await runConnectorLocalRuntimeAgent({
|
|
557
577
|
parsed,
|
|
558
|
-
runtimeEnv,
|
|
578
|
+
runtimeEnv: withForwardedUserAuthToken(runtimeEnv, parsed),
|
|
559
579
|
cwd,
|
|
560
580
|
fetchImpl,
|
|
561
581
|
onProgress: sendProgress,
|
|
@@ -28,7 +28,7 @@ type ParsedArgs = {
|
|
|
28
28
|
|
|
29
29
|
const DEFAULT_USER_ID = "b2e06f801f";
|
|
30
30
|
const DEFAULT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
|
|
31
|
-
const DEFAULT_SOURCE_AGENT_ID = "
|
|
31
|
+
const DEFAULT_SOURCE_AGENT_ID = "01MIMO25MONTH0000000NEW001";
|
|
32
32
|
const DEFAULT_TARGET_AGENT_ID = "01OFFMARXBOOK000000010AHL1";
|
|
33
33
|
const DEFAULT_AGENT_NAME = "离线马克思主义文库书籍转换助手";
|
|
34
34
|
const TOOL_NAME = "convertMarxistsBookToOfflineHtml";
|
|
@@ -64,13 +64,13 @@ async function defaultLocalRuntimeProbe(env: EnvLike): Promise<LocalRuntimeProbe
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export async function runDoctorRuntimeCommand(
|
|
67
|
-
|
|
67
|
+
args: string[],
|
|
68
68
|
deps: AgentCommandDeps = {}
|
|
69
69
|
) {
|
|
70
70
|
const env = deps.env ?? process.env;
|
|
71
71
|
const output = deps.output ?? process.stdout;
|
|
72
|
-
const authToken = resolveAuthToken(env);
|
|
73
|
-
const serverUrl = resolveServerUrl(env);
|
|
72
|
+
const authToken = resolveAuthToken(args, env);
|
|
73
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
74
74
|
const localProbe = await (deps.localRuntimeProbe ?? defaultLocalRuntimeProbe)(env);
|
|
75
75
|
const hasLocalAgentConfig = detectLocalAgentConfig(env) && localProbe.ok && localProbe.agentFound;
|
|
76
76
|
const hasLocalProvider = detectLocalProvider(env);
|
|
@@ -88,7 +88,13 @@ export async function runDoctorRuntimeCommand(
|
|
|
88
88
|
hasLocalPersistence: true,
|
|
89
89
|
missingLocalCapabilities,
|
|
90
90
|
requiresServer: false,
|
|
91
|
-
serverFallbackAvailable: Boolean(
|
|
91
|
+
serverFallbackAvailable: Boolean(
|
|
92
|
+
authToken ||
|
|
93
|
+
args.includes("--server") ||
|
|
94
|
+
args.includes("--server-url") ||
|
|
95
|
+
env.NOLO_SERVER ||
|
|
96
|
+
env.BASE_URL
|
|
97
|
+
),
|
|
92
98
|
});
|
|
93
99
|
|
|
94
100
|
output.write(`Runtime: ${decision.mode}\n`);
|
package/cli/tableCommands.ts
CHANGED
|
@@ -80,7 +80,7 @@ function resolveServerUrl(args: string[], env: EnvLike): string {
|
|
|
80
80
|
|
|
81
81
|
function resolveAuthToken(args: string[], env: EnvLike): string {
|
|
82
82
|
return (
|
|
83
|
-
readOption(args, "--
|
|
83
|
+
readOption(args, "--machine-key") ||
|
|
84
84
|
readOption(args, "--token") ||
|
|
85
85
|
env.AUTH_TOKEN ||
|
|
86
86
|
env.AUTH ||
|
package/cli/taskRunCommand.ts
CHANGED
|
@@ -82,7 +82,7 @@ function resolveServerUrl(args: string[], env: EnvLike) {
|
|
|
82
82
|
|
|
83
83
|
function resolveAuthToken(args: string[], env: EnvLike) {
|
|
84
84
|
return (
|
|
85
|
-
readOption(args, "--
|
|
85
|
+
readOption(args, "--machine-key") ||
|
|
86
86
|
readOption(args, "--token") ||
|
|
87
87
|
env.NOLO_TASK_RUN_TOKEN ||
|
|
88
88
|
env.NOLO_MACHINE_API_KEY ||
|
package/cliCommandDispatch.ts
CHANGED
|
@@ -31,7 +31,7 @@ function readOption(args: string[], flag: string) {
|
|
|
31
31
|
|
|
32
32
|
function buildDaemonEnv(args: string[], runtimeEnv: NodeJS.ProcessEnv) {
|
|
33
33
|
const serverUrl = readOption(args, "--server-url") || readOption(args, "--server");
|
|
34
|
-
const apiKey = readOption(args, "--
|
|
34
|
+
const apiKey = readOption(args, "--machine-key") || readOption(args, "--token");
|
|
35
35
|
return {
|
|
36
36
|
...runtimeEnv,
|
|
37
37
|
...(serverUrl ? { NOLO_SERVER: serverUrl } : {}),
|
|
@@ -40,7 +40,7 @@ function buildDaemonEnv(args: string[], runtimeEnv: NodeJS.ProcessEnv) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export function looksLikeDaemonShortcut(args: string[]) {
|
|
43
|
-
return args.includes("--server-url") || args.includes("--
|
|
43
|
+
return args.includes("--server-url") || args.includes("--machine-key");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function createCliRuntimeContext(args: {
|
package/cliEnvHelpers.ts
CHANGED
|
@@ -3,19 +3,17 @@ import { NOLO_CLUSTER_SERVERS } from "./database/config";
|
|
|
3
3
|
|
|
4
4
|
export type EnvLike = Record<string, string | undefined>;
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function resolveServerUrlFromEnv(env: EnvLike) {
|
|
7
7
|
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
...NOLO_CLUSTER_SERVERS,
|
|
18
|
-
];
|
|
10
|
+
type ResolverInput = string[] | EnvLike;
|
|
11
|
+
|
|
12
|
+
function isCliArgs(value: ResolverInput): value is string[] {
|
|
13
|
+
return Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildServerCandidates(values: Array<string | null | undefined>) {
|
|
19
17
|
return [...new Set(
|
|
20
18
|
values
|
|
21
19
|
.filter((value): value is string => typeof value === "string" && value.trim().length > 0)
|
|
@@ -23,8 +21,78 @@ export function resolveServerCandidates(env: EnvLike, preferred?: string | null)
|
|
|
23
21
|
)];
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
export function
|
|
27
|
-
|
|
24
|
+
export function resolveServerUrl(env: EnvLike): string;
|
|
25
|
+
export function resolveServerUrl(args: string[], env: EnvLike): string;
|
|
26
|
+
export function resolveServerUrl(input: ResolverInput, env?: EnvLike) {
|
|
27
|
+
if (isCliArgs(input)) {
|
|
28
|
+
const explicit =
|
|
29
|
+
readOption(input, "--server-url") ||
|
|
30
|
+
readOption(input, "--server");
|
|
31
|
+
return explicit?.replace(/\/+$/, "") || resolveServerUrlFromEnv(env ?? {});
|
|
32
|
+
}
|
|
33
|
+
return resolveServerUrlFromEnv(input);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveServerCandidates(env: EnvLike, preferred?: string | null): string[];
|
|
37
|
+
export function resolveServerCandidates(
|
|
38
|
+
args: string[],
|
|
39
|
+
env: EnvLike,
|
|
40
|
+
preferred?: string | null,
|
|
41
|
+
): string[];
|
|
42
|
+
export function resolveServerCandidates(
|
|
43
|
+
input: ResolverInput,
|
|
44
|
+
envOrPreferred?: EnvLike | string | null,
|
|
45
|
+
preferred?: string | null,
|
|
46
|
+
) {
|
|
47
|
+
if (isCliArgs(input)) {
|
|
48
|
+
const env = (envOrPreferred ?? {}) as EnvLike;
|
|
49
|
+
const explicitServer = resolveServerUrl(input, env);
|
|
50
|
+
return buildServerCandidates([
|
|
51
|
+
explicitServer,
|
|
52
|
+
preferred?.trim(),
|
|
53
|
+
env.NOLO_SERVER,
|
|
54
|
+
env.BASE_URL,
|
|
55
|
+
env.NOLO_SERVER_URL,
|
|
56
|
+
DEFAULT_NOLO_SERVER_URL,
|
|
57
|
+
...NOLO_CLUSTER_SERVERS,
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const env = input;
|
|
62
|
+
return buildServerCandidates([
|
|
63
|
+
typeof envOrPreferred === "string" ? envOrPreferred.trim() : undefined,
|
|
64
|
+
env.NOLO_SERVER,
|
|
65
|
+
env.BASE_URL,
|
|
66
|
+
env.NOLO_SERVER_URL,
|
|
67
|
+
DEFAULT_NOLO_SERVER_URL,
|
|
68
|
+
...NOLO_CLUSTER_SERVERS,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveAuthTokenFromEnv(env: EnvLike, extraEnvKeys: string[] = []) {
|
|
73
|
+
for (const key of ["AUTH_TOKEN", "AUTH", ...extraEnvKeys]) {
|
|
74
|
+
const value = env[key];
|
|
75
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
76
|
+
}
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveAuthToken(env: EnvLike, extraEnvKeys?: string[]): string;
|
|
81
|
+
export function resolveAuthToken(args: string[], env: EnvLike, extraEnvKeys?: string[]): string;
|
|
82
|
+
export function resolveAuthToken(
|
|
83
|
+
input: ResolverInput,
|
|
84
|
+
envOrExtraEnvKeys?: EnvLike | string[],
|
|
85
|
+
extraEnvKeys: string[] = [],
|
|
86
|
+
) {
|
|
87
|
+
if (isCliArgs(input)) {
|
|
88
|
+
const env = (envOrExtraEnvKeys ?? {}) as EnvLike;
|
|
89
|
+
return (
|
|
90
|
+
readOption(input, "--token") ||
|
|
91
|
+
readOption(input, "--machine-key") ||
|
|
92
|
+
resolveAuthTokenFromEnv(env, extraEnvKeys)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return resolveAuthTokenFromEnv(input, (envOrExtraEnvKeys as string[] | undefined) ?? []);
|
|
28
96
|
}
|
|
29
97
|
|
|
30
98
|
export function parseUserIdFromAuthToken(token: string) {
|
package/client/agentRun.ts
CHANGED
|
@@ -778,6 +778,7 @@ async function runLocalAgentTurnForCli(
|
|
|
778
778
|
background: options.background,
|
|
779
779
|
noStream: options.noStream,
|
|
780
780
|
maxToolRounds: options.maxToolRounds,
|
|
781
|
+
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
781
782
|
...(traceLocalTools || collectToolEvidence
|
|
782
783
|
? {
|
|
783
784
|
onToolEvent: (event) => {
|
|
@@ -11,15 +11,18 @@ describe("CLI local provider resolver", () => {
|
|
|
11
11
|
provider: "custom",
|
|
12
12
|
apiSource: "custom",
|
|
13
13
|
customProviderUrl: "https://provider.example/v1/chat/completions",
|
|
14
|
+
apiKey: "sk-agent-custom",
|
|
15
|
+
apiKeyHeader: "api-key",
|
|
14
16
|
},
|
|
15
17
|
env: {
|
|
16
|
-
OPENAI_API_KEY: "sk-env",
|
|
18
|
+
OPENAI_API_KEY: "sk-env-should-not-win",
|
|
17
19
|
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
18
20
|
},
|
|
19
21
|
})).toEqual({
|
|
20
22
|
model: "qwen-coder",
|
|
21
23
|
endpoint: "https://provider.example/v1/chat/completions",
|
|
22
|
-
apiKey: "sk-
|
|
24
|
+
apiKey: "sk-agent-custom",
|
|
25
|
+
apiKeyHeader: "api-key",
|
|
23
26
|
provider: "custom",
|
|
24
27
|
requestOptions: {},
|
|
25
28
|
});
|
|
@@ -53,6 +56,27 @@ describe("CLI local provider resolver", () => {
|
|
|
53
56
|
});
|
|
54
57
|
});
|
|
55
58
|
|
|
59
|
+
test("defaults Xiaomi custom endpoints to api-key auth header", () => {
|
|
60
|
+
expect(resolveCliOpenAiProviderConfig({
|
|
61
|
+
agentConfig: {
|
|
62
|
+
key: "agent-user-1-mimo",
|
|
63
|
+
provider: "custom",
|
|
64
|
+
apiSource: "custom",
|
|
65
|
+
model: "mimo-v2.5-pro",
|
|
66
|
+
customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
67
|
+
apiKey: "mimo-monthly-key",
|
|
68
|
+
},
|
|
69
|
+
env: {},
|
|
70
|
+
})).toEqual({
|
|
71
|
+
model: "mimo-v2.5-pro",
|
|
72
|
+
endpoint: "https://token-plan-cn.xiaomimimo.com/v1/chat/completions",
|
|
73
|
+
apiKey: "mimo-monthly-key",
|
|
74
|
+
apiKeyHeader: "api-key",
|
|
75
|
+
provider: "custom",
|
|
76
|
+
requestOptions: {},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
56
80
|
test("carries explicit agent inference options for request body construction", () => {
|
|
57
81
|
expect(resolveCliOpenAiProviderConfig({
|
|
58
82
|
agentConfig: {
|