nolo-cli 0.1.33 → 0.1.35
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 +15 -5
- 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 +71 -22
- package/agent-runtime/localWorkspaceTools.ts +5 -2
- 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 +242 -0
- package/agent-runtime/runtimeToolPolicy.ts +33 -0
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentCommandSupport.ts +4 -0
- package/agentInternalCommandEntries.ts +2 -0
- package/agentListCommands.ts +74 -12
- package/agentListHelpers.ts +56 -1
- package/agentPullCommand.ts +6 -11
- package/agentRecordCommands.ts +87 -2
- package/agentRecordHelpers.ts +132 -9
- package/agentRuntimeLocal.ts +3 -0
- package/ai/tools/toolNameAliases.ts +70 -0
- package/cli/agentCommandSupport.ts +4 -0
- package/cli/agentInternalCommandEntries.ts +2 -0
- package/cli/agentListCommands.ts +74 -12
- package/cli/agentListHelpers.ts +56 -1
- package/cli/agentPullCommand.ts +6 -11
- package/cli/agentRecordCommands.ts +87 -2
- package/cli/agentRecordHelpers.ts +132 -9
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +80 -12
- package/cli/cliSpaceHelpers.ts +39 -0
- package/cli/client/agentRun.ts +1 -0
- package/cli/client/localRuntimeAdapter.ts +44 -7
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorWebSocketTarget.ts +10 -3
- package/cli/dialogCommands.ts +446 -0
- package/cli/dialogInternalCommandEntries.ts +13 -0
- package/cli/globalRecordOperations.ts +258 -0
- package/cli/internalCommandEntries.ts +2 -0
- package/cli/localRuntimeAuthority.ts +182 -0
- package/cli/localRuntimeDb.ts +2 -4
- package/cli/machineCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +29 -12
- package/cli/machineWsSession.ts +2 -0
- package/cli/offlineMarxistsAgentCommand.ts +1 -1
- package/cli/runtimeDoctorCommands.ts +65 -9
- package/cli/tableCommands.ts +1 -1
- package/cli/taskRunCommand.ts +1 -1
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +80 -12
- package/cliSpaceHelpers.ts +39 -0
- 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/connectorWebSocketTarget.ts +10 -3
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/cliAuthorityBrokerClient.ts +227 -0
- package/database/server/cliAuthorityBrokerServer.ts +283 -0
- package/database/server/cliAuthorityBrokerTypes.ts +43 -0
- package/database/server/cliAuthorityStoreDriver.ts +98 -0
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +95 -0
- package/database/tombstones.ts +52 -0
- package/database/userDataMerge.ts +95 -0
- package/dialogCommands.ts +446 -0
- package/dialogInternalCommandEntries.ts +13 -0
- package/globalRecordOperations.ts +258 -0
- package/internalCommandEntries.ts +2 -0
- package/localRuntimeAuthority.ts +182 -0
- package/localRuntimeDb.ts +2 -4
- package/machineCommands.ts +1 -1
- package/machineWsRunDispatch.ts +29 -12
- package/machineWsSession.ts +2 -0
- package/offlineMarxistsAgentCommand.ts +1 -1
- package/package.json +24 -4
- package/runtimeDoctorCommands.ts +65 -9
- package/tableCommands.ts +1 -1
- package/taskRunCommand.ts +1 -1
- package/database/server/db.ts +0 -118
- package/database/server/ensureDbOpen.ts +0 -12
package/agentRecordCommands.ts
CHANGED
|
@@ -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
|
+
}
|
package/agentRecordHelpers.ts
CHANGED
|
@@ -125,10 +125,12 @@ export async function readDbRecord(args: {
|
|
|
125
125
|
authToken: string;
|
|
126
126
|
fallbackFetchImpl?: typeof fetch;
|
|
127
127
|
fetchImpl: typeof fetch;
|
|
128
|
+
includeDeleted?: boolean;
|
|
128
129
|
serverUrl: string;
|
|
129
130
|
}) {
|
|
131
|
+
const query = args.includeDeleted ? "?includeDeleted=true" : "";
|
|
130
132
|
const res = await fetchWithTransportFallback(
|
|
131
|
-
`${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.dbKey)}`,
|
|
133
|
+
`${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.dbKey)}${query}`,
|
|
132
134
|
{
|
|
133
135
|
method: "GET",
|
|
134
136
|
headers: { Authorization: `Bearer ${args.authToken}` },
|
|
@@ -146,19 +148,29 @@ export async function queryUserRecords(args: {
|
|
|
146
148
|
authToken: string;
|
|
147
149
|
fallbackFetchImpl?: typeof fetch;
|
|
148
150
|
fetchImpl: typeof fetch;
|
|
151
|
+
limit?: number;
|
|
149
152
|
serverUrl: string;
|
|
153
|
+
summary?: boolean;
|
|
154
|
+
includeDeleted?: boolean;
|
|
150
155
|
userId: string;
|
|
151
|
-
type:
|
|
152
|
-
}) {
|
|
156
|
+
type: string | string[];
|
|
157
|
+
}): Promise<any[]> {
|
|
158
|
+
const query = typeof args.limit === "number" && args.limit > 0
|
|
159
|
+
? `?limit=${Math.floor(args.limit)}`
|
|
160
|
+
: "";
|
|
153
161
|
const res = await fetchWithTransportFallback(
|
|
154
|
-
`${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}`,
|
|
162
|
+
`${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}${query}`,
|
|
155
163
|
{
|
|
156
164
|
method: "POST",
|
|
157
165
|
headers: {
|
|
158
166
|
Authorization: `Bearer ${args.authToken}`,
|
|
159
167
|
"Content-Type": "application/json",
|
|
160
168
|
},
|
|
161
|
-
body: JSON.stringify({
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
type: args.type,
|
|
171
|
+
...(args.includeDeleted ? { includeDeleted: true } : {}),
|
|
172
|
+
...(args.summary ? { summary: true } : {}),
|
|
173
|
+
}),
|
|
162
174
|
},
|
|
163
175
|
args
|
|
164
176
|
);
|
|
@@ -173,6 +185,34 @@ export async function queryUserRecords(args: {
|
|
|
173
185
|
: [];
|
|
174
186
|
}
|
|
175
187
|
|
|
188
|
+
export async function deleteDbRecord(args: {
|
|
189
|
+
dbKey: string;
|
|
190
|
+
deleteOptions?: { type?: "single" | "table" | "messages" };
|
|
191
|
+
authToken: string;
|
|
192
|
+
fallbackFetchImpl?: typeof fetch;
|
|
193
|
+
fetchImpl: typeof fetch;
|
|
194
|
+
serverUrl: string;
|
|
195
|
+
}) {
|
|
196
|
+
const type = args.deleteOptions?.type;
|
|
197
|
+
const query =
|
|
198
|
+
type && type !== "single"
|
|
199
|
+
? `?type=${encodeURIComponent(type)}`
|
|
200
|
+
: "";
|
|
201
|
+
const res = await fetchWithTransportFallback(
|
|
202
|
+
`${args.serverUrl}/api/v1/db/delete/${encodeURIComponent(args.dbKey)}${query}`,
|
|
203
|
+
{
|
|
204
|
+
method: "DELETE",
|
|
205
|
+
headers: { Authorization: `Bearer ${args.authToken}` },
|
|
206
|
+
},
|
|
207
|
+
args
|
|
208
|
+
);
|
|
209
|
+
const data = await res.json().catch(() => ({}));
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
throw new Error(`delete failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
212
|
+
}
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
|
|
176
216
|
export async function writeAgentRecord(args: {
|
|
177
217
|
agentKey: string;
|
|
178
218
|
authToken: string;
|
|
@@ -205,14 +245,19 @@ export async function writeAgentRecord(args: {
|
|
|
205
245
|
|
|
206
246
|
export async function resolveAgentRecordFromHybridStore(args: {
|
|
207
247
|
agentInput: string;
|
|
248
|
+
cliArgs?: string[];
|
|
208
249
|
env: EnvLike;
|
|
209
250
|
db: CliKvDb;
|
|
210
251
|
fetchImpl: typeof fetch;
|
|
211
252
|
fallbackFetchImpl?: typeof fetch;
|
|
212
253
|
}) {
|
|
213
254
|
const agentKey = resolveCliAgentKeyInput(args.agentInput);
|
|
214
|
-
const authToken =
|
|
215
|
-
|
|
255
|
+
const authToken = args.cliArgs
|
|
256
|
+
? resolveAuthToken(args.cliArgs, args.env)
|
|
257
|
+
: resolveAuthToken(args.env);
|
|
258
|
+
const defaultServerUrl = args.cliArgs
|
|
259
|
+
? resolveServerUrl(args.cliArgs, args.env)
|
|
260
|
+
: resolveServerUrl(args.env);
|
|
216
261
|
|
|
217
262
|
for (const key of buildLocalAgentLookupKeys({
|
|
218
263
|
agentRef: agentKey,
|
|
@@ -232,7 +277,11 @@ export async function resolveAgentRecordFromHybridStore(args: {
|
|
|
232
277
|
}
|
|
233
278
|
|
|
234
279
|
if (!shouldReadAgentKeyRemotely(key)) continue;
|
|
235
|
-
for (const serverUrl of
|
|
280
|
+
for (const serverUrl of (
|
|
281
|
+
args.cliArgs
|
|
282
|
+
? resolveServerCandidates(args.cliArgs, args.env, defaultServerUrl)
|
|
283
|
+
: resolveServerCandidates(args.env, defaultServerUrl)
|
|
284
|
+
)) {
|
|
236
285
|
try {
|
|
237
286
|
const remoteRecord = await readAgentRecord({
|
|
238
287
|
agentKey: key,
|
|
@@ -290,6 +339,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
290
339
|
const tools = readOption(args, "--tools");
|
|
291
340
|
const copyProviderFrom = readOption(args, "--copy-provider-from");
|
|
292
341
|
const name = readOption(args, "--name");
|
|
342
|
+
const customProviderUrl = readOption(args, "--custom-provider-url");
|
|
343
|
+
const apiKey = readOption(args, "--api-key");
|
|
293
344
|
|
|
294
345
|
const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
|
|
295
346
|
if (promptSources.length > 1) {
|
|
@@ -304,6 +355,8 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
304
355
|
if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
|
|
305
356
|
if (tools) updates.tools = parseJsonishValue(tools);
|
|
306
357
|
if (name) updates.name = name;
|
|
358
|
+
if (customProviderUrl) updates.customProviderUrl = customProviderUrl;
|
|
359
|
+
if (apiKey) updates.apiKey = apiKey;
|
|
307
360
|
|
|
308
361
|
for (const entry of readRepeatedOption(args, "--field")) {
|
|
309
362
|
const index = entry.indexOf("=");
|
|
@@ -322,7 +375,73 @@ export function parseAgentUpdateArgs(args: string[]) {
|
|
|
322
375
|
return { agentInput, updates, promptDoc, copyProviderFrom };
|
|
323
376
|
}
|
|
324
377
|
|
|
378
|
+
export async function buildCreatedAgentRecord(args: {
|
|
379
|
+
cliArgs?: string[];
|
|
380
|
+
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
381
|
+
env: EnvLike;
|
|
382
|
+
db: CliKvDb;
|
|
383
|
+
fetchImpl: typeof fetch;
|
|
384
|
+
fallbackFetchImpl?: typeof fetch;
|
|
385
|
+
authToken: string;
|
|
386
|
+
}) {
|
|
387
|
+
const agentKey = resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
388
|
+
const serverUrl = args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env);
|
|
389
|
+
|
|
390
|
+
if (args.parsed.copyProviderFrom) {
|
|
391
|
+
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
392
|
+
agentInput: args.parsed.copyProviderFrom,
|
|
393
|
+
cliArgs: args.cliArgs,
|
|
394
|
+
env: args.env,
|
|
395
|
+
db: args.db,
|
|
396
|
+
fetchImpl: args.fetchImpl,
|
|
397
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
398
|
+
});
|
|
399
|
+
if (!providerSource) {
|
|
400
|
+
throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
|
|
401
|
+
}
|
|
402
|
+
for (const field of PROVIDER_COPY_FIELDS) {
|
|
403
|
+
if (field in providerSource.record) {
|
|
404
|
+
args.parsed.updates[field] = providerSource.record[field];
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (args.parsed.promptDoc) {
|
|
410
|
+
const promptDoc = await readDbRecord({
|
|
411
|
+
dbKey: args.parsed.promptDoc,
|
|
412
|
+
authToken: args.authToken,
|
|
413
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
414
|
+
fetchImpl: args.fetchImpl,
|
|
415
|
+
serverUrl,
|
|
416
|
+
});
|
|
417
|
+
const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
|
|
418
|
+
if (!promptBody.trim()) {
|
|
419
|
+
throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
|
|
420
|
+
}
|
|
421
|
+
args.parsed.updates.prompt = promptBody;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const userId = parseUserIdFromAuthToken(args.authToken);
|
|
425
|
+
|
|
426
|
+
const nextRecord = {
|
|
427
|
+
...args.parsed.updates,
|
|
428
|
+
dbKey: agentKey,
|
|
429
|
+
key: agentKey,
|
|
430
|
+
createdAt: Date.now(),
|
|
431
|
+
updatedAt: Date.now(),
|
|
432
|
+
userId,
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
agentKey,
|
|
437
|
+
serverUrl,
|
|
438
|
+
nextRecord,
|
|
439
|
+
updates: args.parsed.updates,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
325
443
|
export async function buildUpdatedAgentRecord(args: {
|
|
444
|
+
cliArgs?: string[];
|
|
326
445
|
parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
|
|
327
446
|
env: EnvLike;
|
|
328
447
|
db: CliKvDb;
|
|
@@ -332,13 +451,16 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
332
451
|
}) {
|
|
333
452
|
const cached = await resolveAgentRecordFromHybridStore({
|
|
334
453
|
agentInput: args.parsed.agentInput,
|
|
454
|
+
cliArgs: args.cliArgs,
|
|
335
455
|
env: args.env,
|
|
336
456
|
db: args.db,
|
|
337
457
|
fetchImpl: args.fetchImpl,
|
|
338
458
|
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
339
459
|
});
|
|
340
460
|
const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
|
|
341
|
-
const serverUrl = cached?.record?.serverOrigin ??
|
|
461
|
+
const serverUrl = cached?.record?.serverOrigin ?? (
|
|
462
|
+
args.cliArgs ? resolveServerUrl(args.cliArgs, args.env) : resolveServerUrl(args.env)
|
|
463
|
+
);
|
|
342
464
|
const currentRecord = await readAgentRecord({
|
|
343
465
|
agentKey,
|
|
344
466
|
authToken: args.authToken,
|
|
@@ -350,6 +472,7 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
350
472
|
if (args.parsed.copyProviderFrom) {
|
|
351
473
|
const providerSource = await resolveAgentRecordFromHybridStore({
|
|
352
474
|
agentInput: args.parsed.copyProviderFrom,
|
|
475
|
+
cliArgs: args.cliArgs,
|
|
353
476
|
env: args.env,
|
|
354
477
|
db: args.db,
|
|
355
478
|
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
|
+
};
|
|
@@ -20,6 +20,10 @@ export type LocalCliExecutor = (
|
|
|
20
20
|
export type LocalRuntimeProbeResult = {
|
|
21
21
|
ok: boolean;
|
|
22
22
|
dbPath: string;
|
|
23
|
+
authorityDriver?: string;
|
|
24
|
+
authorityEndpoint?: string;
|
|
25
|
+
authorityMetadataPath?: string;
|
|
26
|
+
authorityHealthPath?: string;
|
|
23
27
|
agentFound: boolean;
|
|
24
28
|
agentKey?: string;
|
|
25
29
|
error?: string;
|
|
@@ -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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
2
2
|
import {
|
|
3
|
+
decorateAgentsWithPublicStatusAcrossServers,
|
|
3
4
|
listLocalCachedAgents,
|
|
5
|
+
listRemoteAgentsAcrossServers,
|
|
4
6
|
listRemoteAgents,
|
|
5
7
|
parseAgentListArgs,
|
|
6
8
|
type ListedAgent,
|
|
@@ -9,11 +11,15 @@ import {
|
|
|
9
11
|
queryUserRecords,
|
|
10
12
|
readDbRecord,
|
|
11
13
|
} from "./agentRecordHelpers";
|
|
14
|
+
import { buildSpaceLookup, getSpaceContentKeys } from "./cliSpaceHelpers";
|
|
12
15
|
import {
|
|
13
16
|
parseUserIdFromAuthToken,
|
|
17
|
+
readOption,
|
|
14
18
|
resolveAuthToken,
|
|
19
|
+
resolveServerCandidates,
|
|
15
20
|
resolveServerUrl,
|
|
16
21
|
} from "./cliEnvHelpers";
|
|
22
|
+
import { readLiveDbRecordAfterTombstoneMerge } from "./globalRecordOperations";
|
|
17
23
|
|
|
18
24
|
export async function runAgentListCommand(
|
|
19
25
|
args: string[],
|
|
@@ -22,8 +28,9 @@ export async function runAgentListCommand(
|
|
|
22
28
|
const env = deps.env ?? process.env;
|
|
23
29
|
const output = deps.output ?? process.stdout;
|
|
24
30
|
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
31
|
+
const spaceInput = readOption(args, "--space") ?? readOption(args, "--space-id");
|
|
25
32
|
|
|
26
|
-
const authToken = resolveAuthToken(env);
|
|
33
|
+
const authToken = resolveAuthToken(args, env);
|
|
27
34
|
if (!authToken) {
|
|
28
35
|
output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
29
36
|
return 1;
|
|
@@ -35,34 +42,79 @@ export async function runAgentListCommand(
|
|
|
35
42
|
return 1;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const db = deps.db ?? await getReadableCliDb(output);
|
|
39
45
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
40
46
|
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
47
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
48
|
+
const serverUrls = resolveServerCandidates(args, env, serverUrl);
|
|
41
49
|
|
|
42
50
|
try {
|
|
43
51
|
let agents: ListedAgent[];
|
|
44
|
-
let source: "local-cache" | "remote-cache";
|
|
52
|
+
let source: "local-cache" | "remote-cache" | "global-cache";
|
|
53
|
+
let serverFailures: Array<{ serverUrl: string; error: string }> = [];
|
|
45
54
|
try {
|
|
46
|
-
|
|
47
|
-
source = "local-cache";
|
|
48
|
-
} catch {
|
|
49
|
-
const serverUrl = resolveServerUrl(env);
|
|
50
|
-
agents = await listRemoteAgents({
|
|
55
|
+
const remoteResult = await listRemoteAgentsAcrossServers({
|
|
51
56
|
authToken,
|
|
52
57
|
fallbackFetchImpl,
|
|
53
58
|
fetchImpl,
|
|
54
59
|
includeLegacy,
|
|
55
|
-
|
|
60
|
+
serverUrls,
|
|
56
61
|
userId,
|
|
57
|
-
queryUserRecords,
|
|
58
|
-
readDbRecord,
|
|
59
62
|
});
|
|
60
|
-
|
|
63
|
+
agents = remoteResult.agents;
|
|
64
|
+
serverFailures = remoteResult.failures;
|
|
65
|
+
source = "global-cache";
|
|
66
|
+
} catch {
|
|
67
|
+
try {
|
|
68
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
69
|
+
agents = await listLocalCachedAgents({ db, userId });
|
|
70
|
+
source = "local-cache";
|
|
71
|
+
} catch {
|
|
72
|
+
agents = await listRemoteAgents({
|
|
73
|
+
authToken,
|
|
74
|
+
fallbackFetchImpl,
|
|
75
|
+
fetchImpl,
|
|
76
|
+
includeLegacy,
|
|
77
|
+
serverUrl,
|
|
78
|
+
userId,
|
|
79
|
+
queryUserRecords,
|
|
80
|
+
readDbRecord,
|
|
81
|
+
});
|
|
82
|
+
source = "remote-cache";
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
if (!includeLegacy) {
|
|
64
87
|
agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
|
|
65
88
|
}
|
|
89
|
+
let resolvedSpaceId: string | null = null;
|
|
90
|
+
if (spaceInput) {
|
|
91
|
+
const { spaceId, spaceKey } = buildSpaceLookup(spaceInput);
|
|
92
|
+
resolvedSpaceId = spaceId;
|
|
93
|
+
const spaceRead = await readLiveDbRecordAfterTombstoneMerge({
|
|
94
|
+
authToken,
|
|
95
|
+
dbKey: spaceKey,
|
|
96
|
+
fallbackFetchImpl,
|
|
97
|
+
fetchImpl,
|
|
98
|
+
serverUrls,
|
|
99
|
+
});
|
|
100
|
+
serverFailures = [...serverFailures, ...spaceRead.failures];
|
|
101
|
+
const spaceRecord = spaceRead.record;
|
|
102
|
+
const spaceContentKeys = getSpaceContentKeys(spaceRecord);
|
|
103
|
+
agents = agents.filter((agent) =>
|
|
104
|
+
spaceContentKeys.has(agent.privateKey) ||
|
|
105
|
+
spaceContentKeys.has(agent.publicKey) ||
|
|
106
|
+
spaceContentKeys.has(agent.id)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (source === "global-cache") {
|
|
110
|
+
await decorateAgentsWithPublicStatusAcrossServers({
|
|
111
|
+
agents,
|
|
112
|
+
authToken,
|
|
113
|
+
fallbackFetchImpl,
|
|
114
|
+
fetchImpl,
|
|
115
|
+
serverUrls,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
66
118
|
if (publicOnly) {
|
|
67
119
|
agents = agents.filter((agent) => agent.publicRecordExists);
|
|
68
120
|
}
|
|
@@ -75,6 +127,9 @@ export async function runAgentListCommand(
|
|
|
75
127
|
if (wantJson) {
|
|
76
128
|
output.write(JSON.stringify({
|
|
77
129
|
userId,
|
|
130
|
+
...(resolvedSpaceId ? { spaceId: resolvedSpaceId } : {}),
|
|
131
|
+
targetServers: serverUrls,
|
|
132
|
+
...(serverFailures.length ? { serverFailures } : {}),
|
|
78
133
|
total: agents.length,
|
|
79
134
|
publicCount: agents.filter((agent) => agent.publicRecordExists).length,
|
|
80
135
|
source,
|
|
@@ -85,6 +140,13 @@ export async function runAgentListCommand(
|
|
|
85
140
|
}
|
|
86
141
|
|
|
87
142
|
output.write(`userId: ${userId}\n`);
|
|
143
|
+
if (resolvedSpaceId) {
|
|
144
|
+
output.write(`spaceId: ${resolvedSpaceId}\n`);
|
|
145
|
+
}
|
|
146
|
+
output.write(`targetServers: ${serverUrls.join(", ")}\n`);
|
|
147
|
+
if (serverFailures.length) {
|
|
148
|
+
output.write(`serverFailures: ${serverFailures.length}\n`);
|
|
149
|
+
}
|
|
88
150
|
output.write(`total agents: ${agents.length}\n`);
|
|
89
151
|
output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
|
|
90
152
|
output.write(`source: ${source}\n`);
|
package/cli/agentListHelpers.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
2
|
+
import {
|
|
3
|
+
listUserRecordsFromServers,
|
|
4
|
+
readLiveDbRecordAfterTombstoneMerge,
|
|
5
|
+
} from "./globalRecordOperations";
|
|
2
6
|
|
|
3
7
|
export type ListedAgent = {
|
|
4
8
|
id: string;
|
|
@@ -93,7 +97,7 @@ export async function listLocalCachedAgents(args: {
|
|
|
93
97
|
}) {
|
|
94
98
|
const privateRecords = new Map<string, any>();
|
|
95
99
|
const publicKeys = new Set<string>();
|
|
96
|
-
for await (const [key, value] of args.db.iterator()) {
|
|
100
|
+
for await (const [key, value] of args.db.iterator({ gte: "", lte: "\uffff" })) {
|
|
97
101
|
if (typeof key !== "string" || !value || typeof value !== "object") continue;
|
|
98
102
|
if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
|
|
99
103
|
privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
|
|
@@ -185,3 +189,54 @@ export async function listRemoteAgents(args: {
|
|
|
185
189
|
);
|
|
186
190
|
return agents;
|
|
187
191
|
}
|
|
192
|
+
|
|
193
|
+
export async function listRemoteAgentsAcrossServers(args: {
|
|
194
|
+
authToken: string;
|
|
195
|
+
fallbackFetchImpl?: typeof fetch;
|
|
196
|
+
fetchImpl: typeof fetch;
|
|
197
|
+
includeLegacy: boolean;
|
|
198
|
+
serverUrls: string[];
|
|
199
|
+
userId: string;
|
|
200
|
+
}) {
|
|
201
|
+
const remoteResult = await listUserRecordsFromServers({
|
|
202
|
+
authToken: args.authToken,
|
|
203
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
204
|
+
fetchImpl: args.fetchImpl,
|
|
205
|
+
label: "agent query",
|
|
206
|
+
serverUrls: args.serverUrls,
|
|
207
|
+
type: args.includeLegacy ? ["agent", "cybot"] : "agent",
|
|
208
|
+
userId: args.userId,
|
|
209
|
+
});
|
|
210
|
+
const agents = sortListedAgents(
|
|
211
|
+
remoteResult.records
|
|
212
|
+
.map((record) => normalizeListedAgent(record))
|
|
213
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
214
|
+
);
|
|
215
|
+
return { agents, failures: remoteResult.failures };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function decorateAgentsWithPublicStatusAcrossServers(args: {
|
|
219
|
+
agents: ListedAgent[];
|
|
220
|
+
authToken: string;
|
|
221
|
+
fallbackFetchImpl?: typeof fetch;
|
|
222
|
+
fetchImpl: typeof fetch;
|
|
223
|
+
serverUrls: string[];
|
|
224
|
+
}) {
|
|
225
|
+
await Promise.all(
|
|
226
|
+
args.agents.map(async (agent) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = await readLiveDbRecordAfterTombstoneMerge({
|
|
229
|
+
authToken: args.authToken,
|
|
230
|
+
dbKey: agent.publicKey,
|
|
231
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
232
|
+
fetchImpl: args.fetchImpl,
|
|
233
|
+
serverUrls: args.serverUrls,
|
|
234
|
+
});
|
|
235
|
+
agent.publicRecordExists = result.record?.isPublic === true;
|
|
236
|
+
} catch {
|
|
237
|
+
agent.publicRecordExists = false;
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
return args.agents;
|
|
242
|
+
}
|