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/agentRecordHelpers.ts
CHANGED
|
@@ -205,14 +205,19 @@ export async function writeAgentRecord(args: {
|
|
|
205
205
|
|
|
206
206
|
export async function resolveAgentRecordFromHybridStore(args: {
|
|
207
207
|
agentInput: string;
|
|
208
|
+
cliArgs?: string[];
|
|
208
209
|
env: EnvLike;
|
|
209
210
|
db: CliKvDb;
|
|
210
211
|
fetchImpl: typeof fetch;
|
|
211
212
|
fallbackFetchImpl?: typeof fetch;
|
|
212
213
|
}) {
|
|
213
214
|
const agentKey = resolveCliAgentKeyInput(args.agentInput);
|
|
214
|
-
const authToken =
|
|
215
|
-
|
|
215
|
+
const authToken = args.cliArgs
|
|
216
|
+
? resolveAuthToken(args.cliArgs, args.env)
|
|
217
|
+
: resolveAuthToken(args.env);
|
|
218
|
+
const defaultServerUrl = args.cliArgs
|
|
219
|
+
? resolveServerUrl(args.cliArgs, args.env)
|
|
220
|
+
: resolveServerUrl(args.env);
|
|
216
221
|
|
|
217
222
|
for (const key of buildLocalAgentLookupKeys({
|
|
218
223
|
agentRef: agentKey,
|
|
@@ -232,7 +237,11 @@ export async function resolveAgentRecordFromHybridStore(args: {
|
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
if (!shouldReadAgentKeyRemotely(key)) continue;
|
|
235
|
-
for (const serverUrl of
|
|
240
|
+
for (const serverUrl of (
|
|
241
|
+
args.cliArgs
|
|
242
|
+
? resolveServerCandidates(args.cliArgs, args.env, defaultServerUrl)
|
|
243
|
+
: resolveServerCandidates(args.env, defaultServerUrl)
|
|
244
|
+
)) {
|
|
236
245
|
try {
|
|
237
246
|
const remoteRecord = await readAgentRecord({
|
|
238
247
|
agentKey: key,
|
|
@@ -290,6 +299,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
290
299
|
const tools = readOption(args, "--tools");
|
|
291
300
|
const copyProviderFrom = readOption(args, "--copy-provider-from");
|
|
292
301
|
const name = readOption(args, "--name");
|
|
302
|
+
const customProviderUrl = readOption(args, "--custom-provider-url");
|
|
303
|
+
const apiKey = readOption(args, "--api-key");
|
|
293
304
|
|
|
294
305
|
const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
|
|
295
306
|
if (promptSources.length > 1) {
|
|
@@ -304,6 +315,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
304
315
|
if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
|
|
305
316
|
if (tools) updates.tools = parseJsonishValue(tools);
|
|
306
317
|
if (name) updates.name = name;
|
|
318
|
+
if (customProviderUrl) updates.customProviderUrl = customProviderUrl;
|
|
319
|
+
if (apiKey) updates.apiKey = apiKey;
|
|
307
320
|
|
|
308
321
|
for (const entry of readRepeatedOption(args, "--field")) {
|
|
309
322
|
const index = entry.indexOf("=");
|
|
@@ -322,7 +335,73 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
322
335
|
return { agentInput, updates, promptDoc, copyProviderFrom };
|
|
323
336
|
}
|
|
324
337
|
|
|
338
|
+
export async function buildCreatedAgentRecord(args: {
|
|
339
|
+
cliArgs?: string[];
|
|
340
|
+
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
341
|
+
env: EnvLike;
|
|
342
|
+
db: CliKvDb;
|
|
343
|
+
fetchImpl: typeof fetch;
|
|
344
|
+
fallbackFetchImpl?: typeof fetch;
|
|
345
|
+
authToken: string;
|
|
346
|
+
}) {
|
|
347
|
+
const agentKey = resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
348
|
+
const serverUrl = args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env);
|
|
349
|
+
|
|
350
|
+
if (args.parsed.copyProviderFrom) {
|
|
351
|
+
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
352
|
+
agentInput: args.parsed.copyProviderFrom,
|
|
353
|
+
cliArgs: args.cliArgs,
|
|
354
|
+
env: args.env,
|
|
355
|
+
db: args.db,
|
|
356
|
+
fetchImpl: args.fetchImpl,
|
|
357
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
358
|
+
});
|
|
359
|
+
if (!providerSource) {
|
|
360
|
+
throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
|
|
361
|
+
}
|
|
362
|
+
for (const field of PROVIDER_COPY_FIELDS) {
|
|
363
|
+
if (field in providerSource.record) {
|
|
364
|
+
args.parsed.updates[field] = providerSource.record[field];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (args.parsed.promptDoc) {
|
|
370
|
+
const promptDoc = await readDbRecord({
|
|
371
|
+
dbKey: args.parsed.promptDoc,
|
|
372
|
+
authToken: args.authToken,
|
|
373
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
374
|
+
fetchImpl: args.fetchImpl,
|
|
375
|
+
serverUrl,
|
|
376
|
+
});
|
|
377
|
+
const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
|
|
378
|
+
if (!promptBody.trim()) {
|
|
379
|
+
throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
|
|
380
|
+
}
|
|
381
|
+
args.parsed.updates.prompt = promptBody;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const userId = parseUserIdFromAuthToken(args.authToken);
|
|
385
|
+
|
|
386
|
+
const nextRecord = {
|
|
387
|
+
...args.parsed.updates,
|
|
388
|
+
dbKey: agentKey,
|
|
389
|
+
key: agentKey,
|
|
390
|
+
createdAt: Date.now(),
|
|
391
|
+
updatedAt: Date.now(),
|
|
392
|
+
userId,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
agentKey,
|
|
397
|
+
serverUrl,
|
|
398
|
+
nextRecord,
|
|
399
|
+
updates: args.parsed.updates,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
325
403
|
export async function buildUpdatedAgentRecord(args: {
|
|
404
|
+
cliArgs?: string[];
|
|
326
405
|
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
327
406
|
env: EnvLike;
|
|
328
407
|
db: CliKvDb;
|
|
@@ -332,13 +411,16 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
332
411
|
}) {
|
|
333
412
|
const cached = await resolveAgentRecordFromHybridStore({
|
|
334
413
|
agentInput: args.parsed.agentInput,
|
|
414
|
+
cliArgs: args.cliArgs,
|
|
335
415
|
env: args.env,
|
|
336
416
|
db: args.db,
|
|
337
417
|
fetchImpl: args.fetchImpl,
|
|
338
418
|
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
339
419
|
});
|
|
340
420
|
const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
341
|
-
const serverUrl = cached?.record?.serverOrigin ??
|
|
421
|
+
const serverUrl = cached?.record?.serverOrigin ?? (
|
|
422
|
+
args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env)
|
|
423
|
+
);
|
|
342
424
|
const currentRecord = await readAgentRecord({
|
|
343
425
|
agentKey,
|
|
344
426
|
authToken: args.authToken,
|
|
@@ -350,6 +432,7 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
350
432
|
if (args.parsed.copyProviderFrom) {
|
|
351
433
|
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
352
434
|
agentInput: args.parsed.copyProviderFrom,
|
|
435
|
+
cliArgs: args.cliArgs,
|
|
353
436
|
env: args.env,
|
|
354
437
|
db: args.db,
|
|
355
438
|
fetchImpl: args.fetchImpl,
|
package/agentRuntimeLocal.ts
CHANGED
|
@@ -19,6 +19,9 @@ export {
|
|
|
19
19
|
resolvePlatformChatProviderConfig,
|
|
20
20
|
resolveLocalToolPolicy,
|
|
21
21
|
resolveOpenAiCompatibleProviderConfig,
|
|
22
|
+
resolveRequestedRuntimeToolNames,
|
|
23
|
+
evaluateShellCommandPolicy,
|
|
24
|
+
isDestructiveShellCommand,
|
|
22
25
|
shouldUsePlatformChatProvider,
|
|
23
26
|
shouldCacheHybridRemoteRecord,
|
|
24
27
|
shouldFetchAgentRuntimeRecordRemotely,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const LEGACY_TOOL_NAME_ALIASES: Record<string, string> = {
|
|
2
|
+
createPage: "createDoc",
|
|
3
|
+
updatePage: "updateDoc",
|
|
4
|
+
update_page: "updateDoc",
|
|
5
|
+
create_page: "createDoc",
|
|
6
|
+
fetchwebpage: "fetchWebpage",
|
|
7
|
+
fetch_webpage: "fetchWebpage",
|
|
8
|
+
"fetch-page": "fetchWebpage",
|
|
9
|
+
exasearch: "exa_search",
|
|
10
|
+
"exa-search": "exa_search",
|
|
11
|
+
exa_search: "exa_search",
|
|
12
|
+
readDoc: "readDoc",
|
|
13
|
+
readpage: "readDoc",
|
|
14
|
+
read_page: "readDoc",
|
|
15
|
+
read_doc: "readDoc",
|
|
16
|
+
createdoc: "createDoc",
|
|
17
|
+
updateDocTool: "updateDoc",
|
|
18
|
+
readLocal: "readLocalFile",
|
|
19
|
+
read_local: "readLocalFile",
|
|
20
|
+
readLocalTool: "readLocalFile",
|
|
21
|
+
listLocal: "listLocalFiles",
|
|
22
|
+
list_local: "listLocalFiles",
|
|
23
|
+
terminalCommand: "execShell",
|
|
24
|
+
terminal_command: "execShell",
|
|
25
|
+
runCommand: "execShell",
|
|
26
|
+
run_command: "execShell",
|
|
27
|
+
runInBash: "execShell",
|
|
28
|
+
run_in_bash: "execShell",
|
|
29
|
+
executeCommand: "execShell",
|
|
30
|
+
execute_command: "execShell",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const normalizeToolName = (name: string): string =>
|
|
34
|
+
name.replace(/[-_]/g, "").toLowerCase();
|
|
35
|
+
|
|
36
|
+
export const canonicalizeToolName = (rawName: string): string => {
|
|
37
|
+
const trimmedName = rawName.trim();
|
|
38
|
+
if (!trimmedName) return rawName;
|
|
39
|
+
|
|
40
|
+
if (LEGACY_TOOL_NAME_ALIASES[trimmedName]) {
|
|
41
|
+
return LEGACY_TOOL_NAME_ALIASES[trimmedName];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalizedRawName = normalizeToolName(trimmedName);
|
|
45
|
+
const matchedAlias = Object.entries(LEGACY_TOOL_NAME_ALIASES).find(
|
|
46
|
+
([alias]) => normalizeToolName(alias) === normalizedRawName,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return matchedAlias?.[1] ?? trimmedName;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const canonicalizeToolNames = (toolNames: string[]): string[] =>
|
|
53
|
+
Array.from(
|
|
54
|
+
new Set(
|
|
55
|
+
toolNames
|
|
56
|
+
.filter((toolName): toolName is string => typeof toolName === "string")
|
|
57
|
+
.map(canonicalizeToolName),
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export const prioritizeToolNames = (
|
|
62
|
+
toolNames: string[],
|
|
63
|
+
preferredToolNames: string[],
|
|
64
|
+
): string[] => {
|
|
65
|
+
const canonicalTools = canonicalizeToolNames(toolNames);
|
|
66
|
+
const preferred = new Set(canonicalizeToolNames(preferredToolNames));
|
|
67
|
+
const prioritized = canonicalTools.filter((toolName) => preferred.has(toolName));
|
|
68
|
+
const remaining = canonicalTools.filter((toolName) => !preferred.has(toolName));
|
|
69
|
+
return [...prioritized, ...remaining];
|
|
70
|
+
};
|
|
@@ -8,6 +8,7 @@ import { runAgentPullCommand } from "./agentPullCommand";
|
|
|
8
8
|
import {
|
|
9
9
|
runAgentReadCommand,
|
|
10
10
|
runAgentUpdateCommand,
|
|
11
|
+
runAgentCreateCommand,
|
|
11
12
|
} from "./agentRecordCommands";
|
|
12
13
|
import { runAgentRunCommand } from "./agentRunCommand";
|
|
13
14
|
import {
|
|
@@ -21,6 +22,7 @@ export function getAgentInternalCommandEntries(): CommandEntry[] {
|
|
|
21
22
|
return [
|
|
22
23
|
createEnvScriptDirCommand(["chat"], "Chat with an agent", runAgentRunCommand),
|
|
23
24
|
createEnvCommand(["agent", "list"], "List owned agents", runAgentListCommand),
|
|
25
|
+
createEnvCommand(["agent", "create"], "Create a new agent", runAgentCreateCommand),
|
|
24
26
|
createEnvCommand(["agent", "pull"], "Cache an agent for local runs", runAgentPullCommand),
|
|
25
27
|
createEnvCommand(["agent", "read"], "Read a single agent", runAgentReadCommand),
|
|
26
28
|
createEnvScriptDirCommand(["agent", "run"], "Run an agent", runAgentRunCommand),
|
package/cli/agentListCommands.ts
CHANGED
|
@@ -23,7 +23,7 @@ export async function runAgentListCommand(
|
|
|
23
23
|
const output = deps.output ?? process.stdout;
|
|
24
24
|
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
25
25
|
|
|
26
|
-
const authToken = resolveAuthToken(env);
|
|
26
|
+
const authToken = resolveAuthToken(args, env);
|
|
27
27
|
if (!authToken) {
|
|
28
28
|
output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
29
29
|
return 1;
|
|
@@ -46,7 +46,7 @@ export async function runAgentListCommand(
|
|
|
46
46
|
agents = await listLocalCachedAgents({ db, userId });
|
|
47
47
|
source = "local-cache";
|
|
48
48
|
} catch {
|
|
49
|
-
const serverUrl = resolveServerUrl(env);
|
|
49
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
50
50
|
agents = await listRemoteAgents({
|
|
51
51
|
authToken,
|
|
52
52
|
fallbackFetchImpl,
|
package/cli/agentPullCommand.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
2
1
|
import { getDefaultCliLocalRuntimeDb } from "./localRuntimeDb";
|
|
3
2
|
import type { CliLocalRuntimeDb } from "./client/localRuntimeAdapter";
|
|
4
3
|
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
4
|
+
import { resolveAuthToken, resolveServerUrl } from "./cliEnvHelpers";
|
|
5
5
|
|
|
6
6
|
type EnvLike = Record<string, string | undefined>;
|
|
7
7
|
|
|
@@ -44,14 +44,6 @@ export function parseAgentPullArgs(args: string[]): ParsedAgentPullArgs | null {
|
|
|
44
44
|
return { agentKey: resolveCliAgentKeyInput(agentKey) };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function resolveServerUrl(env: EnvLike) {
|
|
48
|
-
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function resolveAuthToken(env: EnvLike) {
|
|
52
|
-
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
47
|
function extractToolNames(record: any) {
|
|
56
48
|
if (Array.isArray(record?.toolNames)) return record.toolNames.filter((tool: unknown) => typeof tool === "string");
|
|
57
49
|
if (!Array.isArray(record?.tools)) return [];
|
|
@@ -113,8 +105,11 @@ export async function runAgentPullCommand(args: string[], deps: AgentPullCommand
|
|
|
113
105
|
return 1;
|
|
114
106
|
}
|
|
115
107
|
|
|
116
|
-
const serverUrl = resolveServerUrl(env);
|
|
117
|
-
const authToken =
|
|
108
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
109
|
+
const authToken =
|
|
110
|
+
readFlagValue(args, "--token") ||
|
|
111
|
+
readFlagValue(args, "--machine-key") ||
|
|
112
|
+
resolveAuthToken(env, ["BENCHMARK_AUTH_TOKEN"]);
|
|
118
113
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
119
114
|
const db = deps.db ?? await getDefaultCliLocalRuntimeDb();
|
|
120
115
|
|
|
@@ -2,6 +2,7 @@ import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
|
2
2
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
3
3
|
import {
|
|
4
4
|
buildUpdatedAgentRecord,
|
|
5
|
+
buildCreatedAgentRecord,
|
|
5
6
|
normalizeAgentRecordForOutput,
|
|
6
7
|
parseAgentUpdateArgs,
|
|
7
8
|
resolveAgentRecordFromHybridStore,
|
|
@@ -21,7 +22,7 @@ export async function runAgentReadCommand(
|
|
|
21
22
|
return agentInput ? 0 : 1;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
const authToken = resolveAuthToken(env);
|
|
25
|
+
const authToken = resolveAuthToken(args, env);
|
|
25
26
|
if (!authToken) {
|
|
26
27
|
output.write("[nolo] agent read requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
27
28
|
return 1;
|
|
@@ -35,6 +36,7 @@ export async function runAgentReadCommand(
|
|
|
35
36
|
try {
|
|
36
37
|
const result = await resolveAgentRecordFromHybridStore({
|
|
37
38
|
agentInput,
|
|
39
|
+
cliArgs: args,
|
|
38
40
|
env,
|
|
39
41
|
db,
|
|
40
42
|
fetchImpl,
|
|
@@ -79,7 +81,7 @@ export async function runAgentUpdateCommand(
|
|
|
79
81
|
return args[0] ? 0 : 1;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
const authToken = resolveAuthToken(env);
|
|
84
|
+
const authToken = resolveAuthToken(args, env);
|
|
83
85
|
if (!authToken) {
|
|
84
86
|
output.write("[nolo] agent update requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
85
87
|
return 1;
|
|
@@ -97,6 +99,7 @@ export async function runAgentUpdateCommand(
|
|
|
97
99
|
|
|
98
100
|
try {
|
|
99
101
|
const built = await buildUpdatedAgentRecord({
|
|
102
|
+
cliArgs: args,
|
|
100
103
|
parsed,
|
|
101
104
|
env,
|
|
102
105
|
db,
|
|
@@ -139,3 +142,85 @@ export async function runAgentUpdateCommand(
|
|
|
139
142
|
return 1;
|
|
140
143
|
}
|
|
141
144
|
}
|
|
145
|
+
|
|
146
|
+
export async function runAgentCreateCommand(
|
|
147
|
+
args: string[],
|
|
148
|
+
deps: AgentCommandDeps = {}
|
|
149
|
+
) {
|
|
150
|
+
const env = deps.env ?? process.env;
|
|
151
|
+
const output = deps.output ?? process.stdout;
|
|
152
|
+
let parsed;
|
|
153
|
+
try {
|
|
154
|
+
parsed = parseAgentUpdateArgs(args);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
output.write(`[nolo] agent create failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
if (!parsed) {
|
|
160
|
+
output.write(
|
|
161
|
+
"Usage: nolo agent create <agent> [--model <id>] [--cli-provider <provider>] [--api-source <source>] [--prompt <text> | --prompt-file <path> | --prompt-doc <pageKey>] [--tools <json>] [--copy-provider-from <agent>] [--name <name>] [--custom-provider-url <url>] [--provider-api-key <key>] [--field key=value]\n"
|
|
162
|
+
);
|
|
163
|
+
return args[0] ? 0 : 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const authToken = resolveAuthToken(args, env);
|
|
167
|
+
if (!authToken) {
|
|
168
|
+
output.write("[nolo] agent create requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const userId = parseUserIdFromAuthToken(authToken);
|
|
173
|
+
if (!userId) {
|
|
174
|
+
output.write("[nolo] agent create could not read userId from AUTH_TOKEN.\n");
|
|
175
|
+
return 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
179
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
180
|
+
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const built = await buildCreatedAgentRecord({
|
|
184
|
+
cliArgs: args,
|
|
185
|
+
parsed,
|
|
186
|
+
env,
|
|
187
|
+
db,
|
|
188
|
+
fetchImpl,
|
|
189
|
+
fallbackFetchImpl,
|
|
190
|
+
authToken,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
await writeAgentRecord({
|
|
194
|
+
agentKey: built.agentKey,
|
|
195
|
+
authToken,
|
|
196
|
+
fallbackFetchImpl,
|
|
197
|
+
fetchImpl,
|
|
198
|
+
serverUrl: built.serverUrl,
|
|
199
|
+
userId,
|
|
200
|
+
record: built.nextRecord,
|
|
201
|
+
});
|
|
202
|
+
await db.put(built.agentKey, {
|
|
203
|
+
...built.nextRecord,
|
|
204
|
+
dbKey: built.agentKey,
|
|
205
|
+
key: built.nextRecord?.key || built.agentKey,
|
|
206
|
+
serverOrigin: built.serverUrl,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
output.write(JSON.stringify({
|
|
210
|
+
ok: true,
|
|
211
|
+
agentKey: built.agentKey,
|
|
212
|
+
baseUrl: built.serverUrl,
|
|
213
|
+
updates: built.updates,
|
|
214
|
+
record: built.nextRecord,
|
|
215
|
+
}, null, 2));
|
|
216
|
+
output.write("\n");
|
|
217
|
+
return 0;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
output.write(
|
|
220
|
+
`[nolo] agent create failed: ${
|
|
221
|
+
error instanceof Error ? error.message : String(error)
|
|
222
|
+
}\n`
|
|
223
|
+
);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -205,14 +205,19 @@ export async function writeAgentRecord(args: {
|
|
|
205
205
|
|
|
206
206
|
export async function resolveAgentRecordFromHybridStore(args: {
|
|
207
207
|
agentInput: string;
|
|
208
|
+
cliArgs?: string[];
|
|
208
209
|
env: EnvLike;
|
|
209
210
|
db: CliKvDb;
|
|
210
211
|
fetchImpl: typeof fetch;
|
|
211
212
|
fallbackFetchImpl?: typeof fetch;
|
|
212
213
|
}) {
|
|
213
214
|
const agentKey = resolveCliAgentKeyInput(args.agentInput);
|
|
214
|
-
const authToken =
|
|
215
|
-
|
|
215
|
+
const authToken = args.cliArgs
|
|
216
|
+
? resolveAuthToken(args.cliArgs, args.env)
|
|
217
|
+
: resolveAuthToken(args.env);
|
|
218
|
+
const defaultServerUrl = args.cliArgs
|
|
219
|
+
? resolveServerUrl(args.cliArgs, args.env)
|
|
220
|
+
: resolveServerUrl(args.env);
|
|
216
221
|
|
|
217
222
|
for (const key of buildLocalAgentLookupKeys({
|
|
218
223
|
agentRef: agentKey,
|
|
@@ -232,7 +237,11 @@ export async function resolveAgentRecordFromHybridStore(args: {
|
|
|
232
237
|
}
|
|
233
238
|
|
|
234
239
|
if (!shouldReadAgentKeyRemotely(key)) continue;
|
|
235
|
-
for (const serverUrl of
|
|
240
|
+
for (const serverUrl of (
|
|
241
|
+
args.cliArgs
|
|
242
|
+
? resolveServerCandidates(args.cliArgs, args.env, defaultServerUrl)
|
|
243
|
+
: resolveServerCandidates(args.env, defaultServerUrl)
|
|
244
|
+
)) {
|
|
236
245
|
try {
|
|
237
246
|
const remoteRecord = await readAgentRecord({
|
|
238
247
|
agentKey: key,
|
|
@@ -290,6 +299,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
290
299
|
const tools = readOption(args, "--tools");
|
|
291
300
|
const copyProviderFrom = readOption(args, "--copy-provider-from");
|
|
292
301
|
const name = readOption(args, "--name");
|
|
302
|
+
const customProviderUrl = readOption(args, "--custom-provider-url");
|
|
303
|
+
const apiKey = readOption(args, "--api-key");
|
|
293
304
|
|
|
294
305
|
const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
|
|
295
306
|
if (promptSources.length > 1) {
|
|
@@ -304,6 +315,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
304
315
|
if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
|
|
305
316
|
if (tools) updates.tools = parseJsonishValue(tools);
|
|
306
317
|
if (name) updates.name = name;
|
|
318
|
+
if (customProviderUrl) updates.customProviderUrl = customProviderUrl;
|
|
319
|
+
if (apiKey) updates.apiKey = apiKey;
|
|
307
320
|
|
|
308
321
|
for (const entry of readRepeatedOption(args, "--field")) {
|
|
309
322
|
const index = entry.indexOf("=");
|
|
@@ -322,7 +335,73 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
322
335
|
return { agentInput, updates, promptDoc, copyProviderFrom };
|
|
323
336
|
}
|
|
324
337
|
|
|
338
|
+
export async function buildCreatedAgentRecord(args: {
|
|
339
|
+
cliArgs?: string[];
|
|
340
|
+
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
341
|
+
env: EnvLike;
|
|
342
|
+
db: CliKvDb;
|
|
343
|
+
fetchImpl: typeof fetch;
|
|
344
|
+
fallbackFetchImpl?: typeof fetch;
|
|
345
|
+
authToken: string;
|
|
346
|
+
}) {
|
|
347
|
+
const agentKey = resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
348
|
+
const serverUrl = args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env);
|
|
349
|
+
|
|
350
|
+
if (args.parsed.copyProviderFrom) {
|
|
351
|
+
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
352
|
+
agentInput: args.parsed.copyProviderFrom,
|
|
353
|
+
cliArgs: args.cliArgs,
|
|
354
|
+
env: args.env,
|
|
355
|
+
db: args.db,
|
|
356
|
+
fetchImpl: args.fetchImpl,
|
|
357
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
358
|
+
});
|
|
359
|
+
if (!providerSource) {
|
|
360
|
+
throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
|
|
361
|
+
}
|
|
362
|
+
for (const field of PROVIDER_COPY_FIELDS) {
|
|
363
|
+
if (field in providerSource.record) {
|
|
364
|
+
args.parsed.updates[field] = providerSource.record[field];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (args.parsed.promptDoc) {
|
|
370
|
+
const promptDoc = await readDbRecord({
|
|
371
|
+
dbKey: args.parsed.promptDoc,
|
|
372
|
+
authToken: args.authToken,
|
|
373
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
374
|
+
fetchImpl: args.fetchImpl,
|
|
375
|
+
serverUrl,
|
|
376
|
+
});
|
|
377
|
+
const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
|
|
378
|
+
if (!promptBody.trim()) {
|
|
379
|
+
throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
|
|
380
|
+
}
|
|
381
|
+
args.parsed.updates.prompt = promptBody;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const userId = parseUserIdFromAuthToken(args.authToken);
|
|
385
|
+
|
|
386
|
+
const nextRecord = {
|
|
387
|
+
...args.parsed.updates,
|
|
388
|
+
dbKey: agentKey,
|
|
389
|
+
key: agentKey,
|
|
390
|
+
createdAt: Date.now(),
|
|
391
|
+
updatedAt: Date.now(),
|
|
392
|
+
userId,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
agentKey,
|
|
397
|
+
serverUrl,
|
|
398
|
+
nextRecord,
|
|
399
|
+
updates: args.parsed.updates,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
325
403
|
export async function buildUpdatedAgentRecord(args: {
|
|
404
|
+
cliArgs?: string[];
|
|
326
405
|
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
327
406
|
env: EnvLike;
|
|
328
407
|
db: CliKvDb;
|
|
@@ -332,13 +411,16 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
332
411
|
}) {
|
|
333
412
|
const cached = await resolveAgentRecordFromHybridStore({
|
|
334
413
|
agentInput: args.parsed.agentInput,
|
|
414
|
+
cliArgs: args.cliArgs,
|
|
335
415
|
env: args.env,
|
|
336
416
|
db: args.db,
|
|
337
417
|
fetchImpl: args.fetchImpl,
|
|
338
418
|
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
339
419
|
});
|
|
340
420
|
const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
341
|
-
const serverUrl = cached?.record?.serverOrigin ??
|
|
421
|
+
const serverUrl = cached?.record?.serverOrigin ?? (
|
|
422
|
+
args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env)
|
|
423
|
+
);
|
|
342
424
|
const currentRecord = await readAgentRecord({
|
|
343
425
|
agentKey,
|
|
344
426
|
authToken: args.authToken,
|
|
@@ -350,6 +432,7 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
350
432
|
if (args.parsed.copyProviderFrom) {
|
|
351
433
|
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
352
434
|
agentInput: args.parsed.copyProviderFrom,
|
|
435
|
+
cliArgs: args.cliArgs,
|
|
353
436
|
env: args.env,
|
|
354
437
|
db: args.db,
|
|
355
438
|
fetchImpl: args.fetchImpl,
|
package/cli/agentRuntimeLocal.ts
CHANGED
|
@@ -19,6 +19,9 @@ export {
|
|
|
19
19
|
resolvePlatformChatProviderConfig,
|
|
20
20
|
resolveLocalToolPolicy,
|
|
21
21
|
resolveOpenAiCompatibleProviderConfig,
|
|
22
|
+
resolveRequestedRuntimeToolNames,
|
|
23
|
+
evaluateShellCommandPolicy,
|
|
24
|
+
isDestructiveShellCommand,
|
|
22
25
|
shouldUsePlatformChatProvider,
|
|
23
26
|
shouldCacheHybridRemoteRecord,
|
|
24
27
|
shouldFetchAgentRuntimeRecordRemotely,
|
|
@@ -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: {
|