nolo-cli 0.1.23 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/hybridRecordStore.ts +30 -9
  3. package/agent-runtime/localLoop.ts +12 -4
  4. package/agent-runtime/localToolPolicy.ts +22 -10
  5. package/agent-runtime/localWorkspaceTools.ts +140 -374
  6. package/agent-runtime/runtimeToolPolicy.ts +43 -2
  7. package/agent-runtime/types.ts +5 -0
  8. package/agentAliases.ts +1 -1
  9. package/agentCommandSupport.ts +77 -0
  10. package/agentInternalCommandEntries.ts +34 -0
  11. package/agentListCommands.ts +123 -0
  12. package/agentListHelpers.ts +187 -0
  13. package/agentMachineCommands.ts +402 -0
  14. package/agentRecordCommands.ts +141 -0
  15. package/agentRecordHelpers.ts +395 -0
  16. package/agentRunCommand.ts +1 -2
  17. package/agentRuntimeCommands.ts +7 -701
  18. package/ai/agent/_executeModel.ts +29 -3
  19. package/ai/agent/buildSystemPrompt.ts +17 -0
  20. package/ai/agent/cliChatClient.ts +38 -4
  21. package/ai/agent/cliExecutor.ts +304 -25
  22. package/ai/agent/createAgentSchema.ts +2 -2
  23. package/ai/agent/guidedCreation/assistant.ts +82 -0
  24. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  25. package/ai/agent/guidedCreation/draft.ts +62 -0
  26. package/ai/agent/guidedCreation/types.ts +38 -0
  27. package/ai/agent/machineRunPermissions.ts +21 -3
  28. package/ai/agent/runtimeGuidance.ts +1 -1
  29. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  30. package/ai/agent/streamAgentChatTurn.ts +492 -19
  31. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  32. package/ai/ai.locale.ts +14 -2
  33. package/ai/chat/sendOpenAICompletionsRequest.ts +28 -3
  34. package/ai/chat/sendOpenAIResponseRequest.ts +3 -2
  35. package/ai/llm/getPricing.ts +14 -0
  36. package/ai/llm/kimi.ts +3 -0
  37. package/ai/llm/modelAvatar.ts +1 -0
  38. package/ai/llm/providers.ts +1 -1
  39. package/ai/taskRun/taskRunProtocol.ts +24 -10
  40. package/ai/token/modelUsageQuery.ts +17 -0
  41. package/ai/tools/agent/agentTools.ts +21 -0
  42. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  43. package/ai/tools/checkEnvTool.ts +52 -20
  44. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  45. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  46. package/ai/tools/index.ts +24 -14
  47. package/ai/tools/marxistsOfflineBook.ts +253 -0
  48. package/ai/tools/modelUsageTools.ts +4 -2
  49. package/ai/tools/readTool.ts +31 -0
  50. package/ai/tools/toolApiClient.ts +35 -1
  51. package/ai/tools/toolNameAliases.ts +13 -0
  52. package/cliCommandAdapters.ts +28 -0
  53. package/cliCommandDispatch.ts +66 -0
  54. package/cliCommandFactories.ts +34 -0
  55. package/cliCommandTypes.ts +49 -0
  56. package/cliEnvHelpers.ts +44 -0
  57. package/client/agentRun.test.ts +123 -19
  58. package/client/agentRun.ts +40 -1
  59. package/client/hybridRecordStore.test.ts +42 -0
  60. package/client/hybridRecordStore.ts +11 -0
  61. package/client/localRuntimeAdapter.test.ts +189 -21
  62. package/client/localRuntimeAdapter.ts +84 -8
  63. package/client/localRuntimeDryRun.test.ts +5 -2
  64. package/client/taskRunPrompt.ts +1 -0
  65. package/commandRegistry.ts +11 -61
  66. package/connector-experimental/capabilities.ts +1 -0
  67. package/database/server/coreDataProxy.ts +43 -26
  68. package/index.ts +54 -191
  69. package/internalCommandEntries.ts +12 -0
  70. package/machineCommands.ts +301 -32
  71. package/offlineMarxistsAgentCommand.ts +314 -0
  72. package/package.json +20 -2
  73. package/runtimeDoctorCommands.ts +118 -0
  74. package/scriptCommandEntries.ts +71 -0
  75. package/systemInternalCommandEntries.ts +71 -0
  76. package/workflowInternalCommandEntries.ts +11 -0
@@ -0,0 +1,395 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolveCliAgentKeyInput } from "./agentAliases";
3
+ import type { CliKvDb } from "./client/hybridRecordStore";
4
+ import { buildLocalAgentLookupKeys, shouldReadAgentKeyRemotely } from "./client/localAgentRecords";
5
+ import {
6
+ parseUserIdFromAuthToken,
7
+ readOption,
8
+ resolveAuthToken,
9
+ resolveServerCandidates,
10
+ resolveServerUrl,
11
+ type EnvLike,
12
+ } from "./cliEnvHelpers";
13
+
14
+ const PROVIDER_COPY_FIELDS = [
15
+ "apiSource",
16
+ "provider",
17
+ "cliProvider",
18
+ "model",
19
+ "customProviderUrl",
20
+ "apiKey",
21
+ "useServerProxy",
22
+ "inputPrice",
23
+ "outputPrice",
24
+ ] as const;
25
+
26
+ function readRepeatedOption(args: string[], flag: string) {
27
+ const values: string[] = [];
28
+ for (let index = 0; index < args.length; index += 1) {
29
+ if (args[index] !== flag || !args[index + 1]) continue;
30
+ values.push(args[index + 1]);
31
+ index += 1;
32
+ }
33
+ return values;
34
+ }
35
+
36
+ function parseJsonishValue(raw: string) {
37
+ try {
38
+ return JSON.parse(raw);
39
+ } catch {
40
+ return raw;
41
+ }
42
+ }
43
+
44
+ async function curlFetch(url: string, init?: RequestInit): Promise<Response> {
45
+ const method = init?.method ?? "GET";
46
+ const headers = new Headers(init?.headers ?? {});
47
+ const command = ["curl", "-sS", "-L", "-X", method];
48
+
49
+ headers.forEach((value, key) => {
50
+ command.push("-H", `${key}: ${value}`);
51
+ });
52
+
53
+ if (typeof init?.body === "string" && init.body.length > 0) {
54
+ command.push("--data", init.body);
55
+ }
56
+
57
+ command.push("-w", "\n__NOLO_STATUS__:%{http_code}", url);
58
+ const proc = Bun.spawn(command, { stdout: "pipe", stderr: "pipe" });
59
+ const [stdout, stderr, exitCode] = await Promise.all([
60
+ new Response(proc.stdout).text(),
61
+ new Response(proc.stderr).text(),
62
+ proc.exited,
63
+ ]);
64
+ if (exitCode !== 0) {
65
+ throw new Error(stderr.trim() || `curl failed for ${url}`);
66
+ }
67
+
68
+ const marker = "\n__NOLO_STATUS__:";
69
+ const markerIndex = stdout.lastIndexOf(marker);
70
+ const body = markerIndex >= 0 ? stdout.slice(0, markerIndex) : stdout;
71
+ const statusText = markerIndex >= 0 ? stdout.slice(markerIndex + marker.length).trim() : "0";
72
+ const status = Number(statusText);
73
+ return new Response(body, {
74
+ status: Number.isFinite(status) ? status : 0,
75
+ headers: { "Content-Type": headers.get("Content-Type") ?? "application/json" },
76
+ });
77
+ }
78
+
79
+ function shouldUseCurlTransportFallback(error: unknown) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ return /Unable to connect|ConnectionRefused|ECONNREFUSED|Failed to connect|Was there a typo|timed out|Timeout|handshake|certificate|ECONNRESET|socket|network/i.test(message);
82
+ }
83
+
84
+ async function fetchWithTransportFallback(
85
+ url: string,
86
+ init: RequestInit,
87
+ options: { fallbackFetchImpl?: typeof fetch; fetchImpl: typeof fetch }
88
+ ): Promise<Response> {
89
+ try {
90
+ return await options.fetchImpl(url, init);
91
+ } catch (error) {
92
+ if (!shouldUseCurlTransportFallback(error)) throw error;
93
+ if (options.fallbackFetchImpl) {
94
+ return options.fallbackFetchImpl(url, init);
95
+ }
96
+ if (options.fetchImpl !== fetch) throw error;
97
+ return curlFetch(url, init);
98
+ }
99
+ }
100
+
101
+ export async function readAgentRecord(args: {
102
+ agentKey: string;
103
+ authToken: string;
104
+ fallbackFetchImpl?: typeof fetch;
105
+ fetchImpl: typeof fetch;
106
+ serverUrl: string;
107
+ }) {
108
+ const res = await fetchWithTransportFallback(
109
+ `${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.agentKey)}`,
110
+ {
111
+ method: "GET",
112
+ headers: { Authorization: `Bearer ${args.authToken}` },
113
+ },
114
+ args
115
+ );
116
+ const data = await res.json().catch(() => ({}));
117
+ if (!res.ok) {
118
+ throw new Error(`read failed: HTTP ${res.status} ${JSON.stringify(data)}`);
119
+ }
120
+ return data?.data ?? data;
121
+ }
122
+
123
+ export async function readDbRecord(args: {
124
+ dbKey: string;
125
+ authToken: string;
126
+ fallbackFetchImpl?: typeof fetch;
127
+ fetchImpl: typeof fetch;
128
+ serverUrl: string;
129
+ }) {
130
+ const res = await fetchWithTransportFallback(
131
+ `${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.dbKey)}`,
132
+ {
133
+ method: "GET",
134
+ headers: { Authorization: `Bearer ${args.authToken}` },
135
+ },
136
+ args
137
+ );
138
+ const data = await res.json().catch(() => ({}));
139
+ if (!res.ok) {
140
+ throw new Error(`read failed: HTTP ${res.status} ${JSON.stringify(data)}`);
141
+ }
142
+ return data?.data ?? data;
143
+ }
144
+
145
+ export async function queryUserRecords(args: {
146
+ authToken: string;
147
+ fallbackFetchImpl?: typeof fetch;
148
+ fetchImpl: typeof fetch;
149
+ serverUrl: string;
150
+ userId: string;
151
+ type: "agent" | "cybot";
152
+ }) {
153
+ const res = await fetchWithTransportFallback(
154
+ `${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}`,
155
+ {
156
+ method: "POST",
157
+ headers: {
158
+ Authorization: `Bearer ${args.authToken}`,
159
+ "Content-Type": "application/json",
160
+ },
161
+ body: JSON.stringify({ type: args.type }),
162
+ },
163
+ args
164
+ );
165
+ const data = await res.json().catch(() => ({}));
166
+ if (!res.ok) {
167
+ throw new Error(`query failed: HTTP ${res.status} ${JSON.stringify(data)}`);
168
+ }
169
+ return Array.isArray(data?.data?.data)
170
+ ? data.data.data
171
+ : Array.isArray(data?.data)
172
+ ? data.data
173
+ : [];
174
+ }
175
+
176
+ export async function writeAgentRecord(args: {
177
+ agentKey: string;
178
+ authToken: string;
179
+ fallbackFetchImpl?: typeof fetch;
180
+ fetchImpl: typeof fetch;
181
+ serverUrl: string;
182
+ userId: string;
183
+ record: Record<string, any>;
184
+ }) {
185
+ const res = await fetchWithTransportFallback(`${args.serverUrl}/api/v1/db/write/`, {
186
+ method: "POST",
187
+ headers: {
188
+ Authorization: `Bearer ${args.authToken}`,
189
+ "Content-Type": "application/json",
190
+ },
191
+ body: JSON.stringify({
192
+ customKey: args.agentKey,
193
+ userId: args.userId,
194
+ data: {
195
+ ...args.record,
196
+ dbKey: args.agentKey,
197
+ },
198
+ }),
199
+ }, args);
200
+ const data = await res.json().catch(() => ({}));
201
+ if (!res.ok) {
202
+ throw new Error(`write failed: HTTP ${res.status} ${JSON.stringify(data)}`);
203
+ }
204
+ }
205
+
206
+ export async function resolveAgentRecordFromHybridStore(args: {
207
+ agentInput: string;
208
+ env: EnvLike;
209
+ db: CliKvDb;
210
+ fetchImpl: typeof fetch;
211
+ fallbackFetchImpl?: typeof fetch;
212
+ }) {
213
+ const agentKey = resolveCliAgentKeyInput(args.agentInput);
214
+ const authToken = resolveAuthToken(args.env);
215
+ const defaultServerUrl = resolveServerUrl(args.env);
216
+
217
+ for (const key of buildLocalAgentLookupKeys({
218
+ agentRef: agentKey,
219
+ userId: parseUserIdFromAuthToken(authToken) || "local",
220
+ })) {
221
+ try {
222
+ const record = await args.db.get(key);
223
+ if (record && typeof record === "object") {
224
+ return {
225
+ agentKey: key,
226
+ record,
227
+ source: record?.serverOrigin ? "remote-cache" : "local-cache",
228
+ } as const;
229
+ }
230
+ } catch {
231
+ // local miss
232
+ }
233
+
234
+ if (!shouldReadAgentKeyRemotely(key)) continue;
235
+ for (const serverUrl of resolveServerCandidates(args.env, defaultServerUrl)) {
236
+ try {
237
+ const remoteRecord = await readAgentRecord({
238
+ agentKey: key,
239
+ authToken,
240
+ fallbackFetchImpl: args.fallbackFetchImpl,
241
+ fetchImpl: args.fetchImpl,
242
+ serverUrl,
243
+ });
244
+ const cached = { ...remoteRecord, dbKey: key, serverOrigin: serverUrl };
245
+ await args.db.put(key, cached);
246
+ return {
247
+ agentKey: key,
248
+ record: cached,
249
+ source: "remote-cache",
250
+ } as const;
251
+ } catch {
252
+ // try next
253
+ }
254
+ }
255
+ }
256
+
257
+ return null;
258
+ }
259
+
260
+ export function normalizeAgentRecordForOutput(agentKey: string, authToken: string, agent: any) {
261
+ return {
262
+ agentKey,
263
+ baseUrl: agent?.serverOrigin ?? null,
264
+ name: agent?.name,
265
+ greeting: agent?.greeting,
266
+ model: agent?.model,
267
+ provider: agent?.provider ?? agent?.apiSource ?? null,
268
+ cliProvider: agent?.cliProvider ?? null,
269
+ customProviderUrl: agent?.customProviderUrl ?? null,
270
+ tools: agent?.tools ?? [],
271
+ isPublic: agent?.isPublic,
272
+ authUserId: parseUserIdFromAuthToken(authToken),
273
+ userId: agent?.userId,
274
+ record: agent,
275
+ };
276
+ }
277
+
278
+ export function parseAgentUpdateArgs(args: string[]) {
279
+ const agentInput = readOption(args, "--agent") ?? readOption(args, "--id") ?? args[0]?.trim();
280
+ if (!agentInput || agentInput === "--help" || agentInput === "-h") return null;
281
+
282
+ const updates: Record<string, unknown> = {};
283
+ const model = readOption(args, "--model");
284
+ const cliProvider = readOption(args, "--cli-provider");
285
+ const apiSource = readOption(args, "--api-source");
286
+ const provider = readOption(args, "--provider");
287
+ const prompt = readOption(args, "--prompt");
288
+ const promptFile = readOption(args, "--prompt-file");
289
+ const promptDoc = readOption(args, "--prompt-doc");
290
+ const tools = readOption(args, "--tools");
291
+ const copyProviderFrom = readOption(args, "--copy-provider-from");
292
+ const name = readOption(args, "--name");
293
+
294
+ const promptSources = [prompt, promptFile, promptDoc].filter(Boolean);
295
+ if (promptSources.length > 1) {
296
+ throw new Error("--prompt / --prompt-file / --prompt-doc are mutually exclusive.");
297
+ }
298
+
299
+ if (model) updates.model = model;
300
+ if (cliProvider) updates.cliProvider = cliProvider;
301
+ if (apiSource) updates.apiSource = apiSource;
302
+ if (provider) updates.provider = provider;
303
+ if (prompt) updates.prompt = prompt;
304
+ if (promptFile) updates.prompt = readFileSync(promptFile, "utf8");
305
+ if (tools) updates.tools = parseJsonishValue(tools);
306
+ if (name) updates.name = name;
307
+
308
+ for (const entry of readRepeatedOption(args, "--field")) {
309
+ const index = entry.indexOf("=");
310
+ if (index <= 0) {
311
+ throw new Error(`--field must use key=value, received ${JSON.stringify(entry)}`);
312
+ }
313
+ updates[entry.slice(0, index)] = parseJsonishValue(entry.slice(index + 1));
314
+ }
315
+
316
+ if (Object.keys(updates).length === 0) {
317
+ if (!promptDoc && !copyProviderFrom) {
318
+ throw new Error("No updates provided. Use --model / --cli-provider / --api-source / --field key=value.");
319
+ }
320
+ }
321
+
322
+ return { agentInput, updates, promptDoc, copyProviderFrom };
323
+ }
324
+
325
+ export async function buildUpdatedAgentRecord(args: {
326
+ parsed: NonNullable<ReturnType<typeof parseAgentUpdateArgs>>;
327
+ env: EnvLike;
328
+ db: CliKvDb;
329
+ fetchImpl: typeof fetch;
330
+ fallbackFetchImpl?: typeof fetch;
331
+ authToken: string;
332
+ }) {
333
+ const cached = await resolveAgentRecordFromHybridStore({
334
+ agentInput: args.parsed.agentInput,
335
+ env: args.env,
336
+ db: args.db,
337
+ fetchImpl: args.fetchImpl,
338
+ fallbackFetchImpl: args.fallbackFetchImpl,
339
+ });
340
+ const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
341
+ const serverUrl = cached?.record?.serverOrigin ?? resolveServerUrl(args.env);
342
+ const currentRecord = await readAgentRecord({
343
+ agentKey,
344
+ authToken: args.authToken,
345
+ fallbackFetchImpl: args.fallbackFetchImpl,
346
+ fetchImpl: args.fetchImpl,
347
+ serverUrl,
348
+ });
349
+
350
+ if (args.parsed.copyProviderFrom) {
351
+ const providerSource = await resolveAgentRecordFromHybridStore({
352
+ agentInput: args.parsed.copyProviderFrom,
353
+ env: args.env,
354
+ db: args.db,
355
+ fetchImpl: args.fetchImpl,
356
+ fallbackFetchImpl: args.fallbackFetchImpl,
357
+ });
358
+ if (!providerSource) {
359
+ throw new Error(`provider source agent not found: ${args.parsed.copyProviderFrom}`);
360
+ }
361
+ for (const field of PROVIDER_COPY_FIELDS) {
362
+ if (field in providerSource.record) {
363
+ args.parsed.updates[field] = providerSource.record[field];
364
+ }
365
+ }
366
+ }
367
+
368
+ if (args.parsed.promptDoc) {
369
+ const promptDoc = await readDbRecord({
370
+ dbKey: args.parsed.promptDoc,
371
+ authToken: args.authToken,
372
+ fallbackFetchImpl: args.fallbackFetchImpl,
373
+ fetchImpl: args.fetchImpl,
374
+ serverUrl,
375
+ });
376
+ const promptBody = typeof promptDoc?.content === "string" ? promptDoc.content : "";
377
+ if (!promptBody.trim()) {
378
+ throw new Error(`prompt doc is empty or unreadable: ${args.parsed.promptDoc}`);
379
+ }
380
+ args.parsed.updates.prompt = promptBody;
381
+ }
382
+
383
+ const nextRecord = {
384
+ ...currentRecord,
385
+ ...args.parsed.updates,
386
+ updatedAt: Date.now(),
387
+ };
388
+
389
+ return {
390
+ agentKey,
391
+ serverUrl,
392
+ nextRecord,
393
+ updates: args.parsed.updates,
394
+ };
395
+ }
@@ -76,7 +76,6 @@ type ResolvedWorkflowReference = {
76
76
 
77
77
  const DEFAULT_TEST_ROOT_DIALOG_ID = "01KN6V3MVEQA4Q1PNKVM2W7YY8";
78
78
  const DEFAULT_TEST_ROOT_DIALOG_KEY = `dialog-b2e06f801f-${DEFAULT_TEST_ROOT_DIALOG_ID}`;
79
- const DEFAULT_AGENT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
80
79
  const DEFAULT_TEST_DIALOG_CATEGORY_ID = "test-dialogs";
81
80
 
82
81
  function readFlagValue(args: string[], flag: string) {
@@ -209,7 +208,7 @@ export function parseAgentRunArgs(
209
208
  noStream: args.includes("--no-stream"),
210
209
  noDefaultTestRoot,
211
210
  ...(continueDialogId ? { continueDialogId } : {}),
212
- ...(!continueDialogId ? { spaceId: spaceId ?? DEFAULT_AGENT_SPACE_ID } : {}),
211
+ ...(!continueDialogId && spaceId ? { spaceId } : {}),
213
212
  ...(category ? { category } : !continueDialogId && !noDefaultTestRoot ? { category: DEFAULT_TEST_DIALOG_CATEGORY_ID } : {}),
214
213
  ...(inheritedRef?.dialogKey ? { inheritedFromDialogKey: inheritedRef.dialogKey } : {}),
215
214
  ...(inheritedRef?.dialogId ? { parentDialogId: inheritedRef.dialogId } : {}),