nolo-cli 0.1.22 → 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 (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. 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
+ }
@@ -3,6 +3,7 @@ import { runAgentTurn, type RunAgentTurnResult } from "./client/agentRun";
3
3
  import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "./agentRuntimeLocal";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { extname, resolve } from "node:path";
6
+ import { parseSkillDocProtocol, type WorkflowReferenceConfig } from "./ai/skills/skillDocProtocol";
6
7
  import { resolveCliAgentKeyInput } from "./agentAliases";
7
8
  import {
8
9
  ensureWorkspacePackageLinks,
@@ -27,6 +28,7 @@ type AgentRunCommandDeps = {
27
28
  prepareTaskWorktree?: typeof prepareTaskWorktree;
28
29
  ensureWorkspacePackageLinks?: typeof ensureWorkspacePackageLinks;
29
30
  inspectLocalRunWorkspace?: typeof inspectLocalRunWorkspace;
31
+ resolveWorkflowReference?: typeof resolveWorkflowReference;
30
32
  };
31
33
 
32
34
  type LocalRunWorkspaceInspection = {
@@ -63,11 +65,17 @@ type ParsedAgentRunArgs = {
63
65
  traceTools: boolean;
64
66
  useTaskWorktree: boolean;
65
67
  taskRunContext?: TaskRunPromptContext;
68
+ workflowRef?: string;
69
+ };
70
+
71
+ type ResolvedWorkflowReference = {
72
+ ref: string;
73
+ content: string;
74
+ config?: Partial<WorkflowReferenceConfig>;
66
75
  };
67
76
 
68
77
  const DEFAULT_TEST_ROOT_DIALOG_ID = "01KN6V3MVEQA4Q1PNKVM2W7YY8";
69
78
  const DEFAULT_TEST_ROOT_DIALOG_KEY = `dialog-b2e06f801f-${DEFAULT_TEST_ROOT_DIALOG_ID}`;
70
- const DEFAULT_AGENT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
71
79
  const DEFAULT_TEST_DIALOG_CATEGORY_ID = "test-dialogs";
72
80
 
73
81
  function readFlagValue(args: string[], flag: string) {
@@ -171,6 +179,7 @@ export function parseAgentRunArgs(
171
179
  ...readRepeatedFlagValues(args, "--image-url"),
172
180
  ];
173
181
  const useTaskWorktree = args.includes("--worktree-task");
182
+ const workflowRef = readFlagValue(args, "--workflow");
174
183
  const taskRowDbKey = readFlagValue(args, "--task-row-dbkey");
175
184
  const artifactIds = readFlagValue(args, "--artifact-ids")
176
185
  ?.split(",")
@@ -183,6 +192,7 @@ export function parseAgentRunArgs(
183
192
  allowShell: args.includes("--dangerously-allow-shell"),
184
193
  traceTools: args.includes("--trace-tools"),
185
194
  useTaskWorktree,
195
+ ...(workflowRef ? { workflowRef } : {}),
186
196
  ...(taskRowDbKey
187
197
  ? {
188
198
  taskRunContext: {
@@ -198,7 +208,7 @@ export function parseAgentRunArgs(
198
208
  noStream: args.includes("--no-stream"),
199
209
  noDefaultTestRoot,
200
210
  ...(continueDialogId ? { continueDialogId } : {}),
201
- ...(!continueDialogId ? { spaceId: spaceId ?? DEFAULT_AGENT_SPACE_ID } : {}),
211
+ ...(!continueDialogId && spaceId ? { spaceId } : {}),
202
212
  ...(category ? { category } : !continueDialogId && !noDefaultTestRoot ? { category: DEFAULT_TEST_DIALOG_CATEGORY_ID } : {}),
203
213
  ...(inheritedRef?.dialogKey ? { inheritedFromDialogKey: inheritedRef.dialogKey } : {}),
204
214
  ...(inheritedRef?.dialogId ? { parentDialogId: inheritedRef.dialogId } : {}),
@@ -208,6 +218,57 @@ export function parseAgentRunArgs(
208
218
  };
209
219
  }
210
220
 
221
+ function workflowRefToCandidatePath(cwd: string, ref: string) {
222
+ const normalized = ref.trim();
223
+ if (!normalized) return "";
224
+ if (normalized.endsWith(".md") || normalized.includes("/") || normalized.includes("\\")) {
225
+ const directPath = resolve(cwd, normalized);
226
+ if (existsSync(directPath)) return directPath;
227
+ }
228
+ const fileName = normalized.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "");
229
+ return resolve(cwd, "docs", "workflows", `${fileName}.md`);
230
+ }
231
+
232
+ export async function resolveWorkflowReference(ref: string, cwd = process.cwd()): Promise<ResolvedWorkflowReference> {
233
+ const path = workflowRefToCandidatePath(cwd, ref);
234
+ if (!path || !existsSync(path)) {
235
+ throw new Error(`Workflow reference not found: ${ref}`);
236
+ }
237
+ const markdown = readFileSync(path, "utf8");
238
+ const parsed = parseSkillDocProtocol(markdown);
239
+ return {
240
+ ref,
241
+ content: parsed.content,
242
+ ...(parsed.meta?.workflowConfig ? { config: parsed.meta.workflowConfig } : {}),
243
+ };
244
+ }
245
+
246
+ export function prependWorkflowReferencePrompt(message: string, workflow?: ResolvedWorkflowReference): string {
247
+ if (!workflow) return message;
248
+ const config = workflow.config;
249
+ return [
250
+ "AI-native workflow reference:",
251
+ "- This reference is guidance for the agent, not a central workflow engine.",
252
+ `- ref: ${workflow.ref}`,
253
+ ...(config?.id ? [`- id: ${config.id}`] : []),
254
+ ...(config?.name ? [`- name: ${config.name}`] : []),
255
+ ...(config?.defaultAgent ? [`- suggested defaultAgent: ${config.defaultAgent}`] : []),
256
+ ...(config?.inputs?.length ? [`- inputs: ${config.inputs.join(", ")}`] : []),
257
+ ...(config?.recommendedTools?.length ? [`- recommendedTools: ${config.recommendedTools.join(", ")}`] : []),
258
+ ...(config?.requiredTools?.length ? [`- requiredTools: ${config.requiredTools.join(", ")}`] : []),
259
+ ...(config?.requiredOutputs?.length ? [`- requiredOutputs: ${config.requiredOutputs.join(", ")}`] : []),
260
+ ...(config?.gates?.length ? [`- gates: ${config.gates.join(", ")}`] : []),
261
+ ...(config?.contextStrategy ? [`- contextStrategy: ${config.contextStrategy}`] : []),
262
+ ...(config?.failureProtocol ? [`- failureProtocol: ${config.failureProtocol}`] : []),
263
+ "",
264
+ "Reference body:",
265
+ workflow.content,
266
+ "",
267
+ "User task:",
268
+ message,
269
+ ].join("\n");
270
+ }
271
+
211
272
  function parseDialogReference(rawInput: string) {
212
273
  const normalized = rawInput.trim();
213
274
  if (normalized.startsWith("dialog-")) {
@@ -353,6 +414,17 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
353
414
  }
354
415
 
355
416
  const runner = deps.runner ?? runAgentTurn;
417
+ let workflowReference: ResolvedWorkflowReference | undefined;
418
+ if (parsed.workflowRef) {
419
+ try {
420
+ workflowReference = await (deps.resolveWorkflowReference ?? resolveWorkflowReference)(
421
+ parsed.workflowRef
422
+ );
423
+ } catch (error) {
424
+ output.write(`[nolo] ${error instanceof Error ? error.message : String(error)}\n`);
425
+ return 1;
426
+ }
427
+ }
356
428
  let localRuntimeCwd = parsed.cwd;
357
429
  if (shouldPrepareTaskWorktree(parsed)) {
358
430
  try {
@@ -399,7 +471,7 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
399
471
  agentName: parsed.agentKey,
400
472
  agentKey: parsed.agentKey,
401
473
  serverUrl: resolveServerUrl(env),
402
- message: parsed.message,
474
+ message: prependWorkflowReferencePrompt(parsed.message, workflowReference),
403
475
  imageUrls: parsed.imageUrls.map(normalizeCliImageInput),
404
476
  scriptDir: deps.scriptDir,
405
477
  env: runEnv,