nolo-cli 0.1.18 → 0.1.20

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 (111) hide show
  1. package/README.md +9 -1
  2. package/agent-runtime/agentConfigOptions.ts +12 -0
  3. package/agent-runtime/agentRecordConfig.ts +99 -0
  4. package/agent-runtime/agentRecordKeys.ts +14 -0
  5. package/agent-runtime/dialogMessageRecord.ts +16 -0
  6. package/agent-runtime/dialogWritePlan.ts +130 -0
  7. package/agent-runtime/hostAdapter.ts +14 -0
  8. package/agent-runtime/hybridRecordStore.ts +147 -0
  9. package/agent-runtime/index.ts +69 -0
  10. package/agent-runtime/localLoop.ts +78 -6
  11. package/agent-runtime/localToolPolicy.ts +130 -0
  12. package/agent-runtime/localWorkspaceTools.ts +1532 -0
  13. package/agent-runtime/openAiCompatibleProvider.ts +70 -0
  14. package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
  15. package/agent-runtime/platformChatProvider.ts +241 -0
  16. package/agent-runtime/taskWorkspace.ts +193 -0
  17. package/agent-runtime/types.ts +2 -0
  18. package/agent-runtime/workspaceSession.ts +76 -0
  19. package/agentAliases.ts +37 -0
  20. package/agentPullCommand.ts +1 -1
  21. package/agentRunCommand.ts +289 -54
  22. package/agentRuntimeCommands.ts +354 -164
  23. package/agentRuntimeLocal.ts +38 -0
  24. package/ai/agent/agentSlice.ts +10 -0
  25. package/ai/agent/buildEditingContext.ts +5 -0
  26. package/ai/agent/buildSystemPrompt.ts +41 -18
  27. package/ai/agent/canvasEditingContext.ts +49 -0
  28. package/ai/agent/cliExecutor.ts +15 -4
  29. package/ai/agent/createAgentSchema.ts +2 -0
  30. package/ai/agent/executeToolCall.ts +3 -2
  31. package/ai/agent/hooks/usePublicAgents.ts +6 -0
  32. package/ai/agent/pageBuilderHandoffRules.ts +75 -0
  33. package/ai/agent/runAgentClientLoop.ts +4 -1
  34. package/ai/agent/runtimeGuidance.ts +19 -0
  35. package/ai/agent/server/fetchPublicAgents.ts +51 -1
  36. package/ai/agent/streamAgentChatTurn.ts +20 -2
  37. package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
  38. package/ai/chat/accumulateToolCallChunks.ts +40 -9
  39. package/ai/chat/parseApiError.ts +3 -0
  40. package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
  41. package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
  42. package/ai/chat/updateTotalUsage.ts +26 -9
  43. package/ai/llm/deepinfra.ts +51 -0
  44. package/ai/llm/getPricing.ts +6 -0
  45. package/ai/llm/kimi.ts +2 -0
  46. package/ai/llm/openrouterModels.ts +0 -135
  47. package/ai/llm/providers.ts +1 -0
  48. package/ai/llm/types.ts +8 -0
  49. package/ai/taskRun/taskRunProtocol.ts +823 -0
  50. package/ai/token/calculatePrice.ts +30 -0
  51. package/ai/token/externalToolCost.ts +49 -29
  52. package/ai/token/prepareTokenUsageData.ts +6 -1
  53. package/ai/token/serverTokenWriter.ts +4 -2
  54. package/ai/tools/agent/agentTools.ts +21 -0
  55. package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
  56. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
  57. package/ai/tools/agent/taskRunTool.ts +112 -0
  58. package/ai/tools/applyEditTool.ts +6 -3
  59. package/ai/tools/applyLineEditsTool.ts +6 -3
  60. package/ai/tools/checkEnvTool.ts +14 -9
  61. package/ai/tools/codeSearchTool.ts +17 -5
  62. package/ai/tools/execBashTool.ts +33 -29
  63. package/ai/tools/fetchWebpageSupport.ts +24 -0
  64. package/ai/tools/fetchWebpageTool.ts +18 -5
  65. package/ai/tools/index.ts +158 -0
  66. package/ai/tools/jdProductScraperTool.ts +821 -0
  67. package/ai/tools/listFilesTool.ts +6 -3
  68. package/ai/tools/localFilesTool.ts +200 -0
  69. package/ai/tools/readFileTool.ts +6 -3
  70. package/ai/tools/searchRepoTool.ts +6 -3
  71. package/ai/tools/table/rowTools.ts +6 -1
  72. package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
  73. package/ai/tools/toolApiClient.ts +20 -6
  74. package/ai/tools/wereadGatewayTool.ts +152 -0
  75. package/ai/tools/writeFileTool.ts +6 -3
  76. package/client/agentConfigResolver.test.ts +70 -0
  77. package/client/agentConfigResolver.ts +1 -0
  78. package/client/agentRun.test.ts +361 -7
  79. package/client/agentRun.ts +449 -63
  80. package/client/hybridRecordStore.test.ts +115 -0
  81. package/client/hybridRecordStore.ts +41 -0
  82. package/client/localAgentRecords.test.ts +27 -0
  83. package/client/localAgentRecords.ts +7 -0
  84. package/client/localDialogRecords.test.ts +124 -0
  85. package/client/localDialogRecords.ts +30 -0
  86. package/client/localProviderResolver.test.ts +78 -0
  87. package/client/localProviderResolver.ts +1 -0
  88. package/client/localRuntimeAdapter.test.ts +813 -20
  89. package/client/localRuntimeAdapter.ts +279 -232
  90. package/client/localRuntimeDryRun.test.ts +116 -0
  91. package/client/localToolPolicy.ts +8 -81
  92. package/client/taskRunPrompt.ts +26 -0
  93. package/client/taskWorktree.ts +8 -0
  94. package/client/workspaceSession.test.ts +57 -0
  95. package/client/workspaceSession.ts +11 -0
  96. package/commandRegistry.ts +23 -6
  97. package/connectorRunArtifact.ts +121 -0
  98. package/database/actions/write.ts +16 -2
  99. package/database/hooks/useUserData.ts +9 -3
  100. package/database/server/dataHandlers.ts +18 -20
  101. package/database/server/emailRepository.ts +3 -3
  102. package/database/server/patch.ts +18 -10
  103. package/database/server/query.ts +43 -4
  104. package/database/server/read.ts +24 -38
  105. package/database/server/recordIdentity.ts +100 -0
  106. package/database/server/write.ts +21 -25
  107. package/index.ts +70 -33
  108. package/machineCommands.ts +318 -144
  109. package/package.json +4 -1
  110. package/tableCommands.ts +181 -0
  111. package/taskRunCommand.ts +237 -0
@@ -1,9 +1,16 @@
1
1
  import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
2
2
  import { runAgentTurn, type RunAgentTurnResult } from "./client/agentRun";
3
- import type { AgentRuntimeRequestedMode } from "./agentRuntimeLocal";
3
+ import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "./agentRuntimeLocal";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
- import { extname, join, resolve } from "node:path";
5
+ import { extname, resolve } from "node:path";
6
6
  import { resolveCliAgentKeyInput } from "./agentAliases";
7
+ import {
8
+ ensureWorkspacePackageLinks,
9
+ formatPreparedTaskWorktree,
10
+ prepareTaskWorktree,
11
+ type PreparedTaskWorktree,
12
+ } from "./client/taskWorktree";
13
+ import type { TaskRunPromptContext } from "./client/taskRunPrompt";
7
14
 
8
15
  type EnvLike = Record<string, string | undefined>;
9
16
 
@@ -16,7 +23,24 @@ type AgentRunCommandDeps = {
16
23
  scriptDir: string;
17
24
  output?: OutputLike;
18
25
  runner?: typeof runAgentTurn;
19
- prepareShellWorktree?: typeof prepareShellWorktree;
26
+ localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
27
+ prepareTaskWorktree?: typeof prepareTaskWorktree;
28
+ ensureWorkspacePackageLinks?: typeof ensureWorkspacePackageLinks;
29
+ inspectLocalRunWorkspace?: typeof inspectLocalRunWorkspace;
30
+ };
31
+
32
+ type LocalRunWorkspaceInspection = {
33
+ cwd: string;
34
+ clean: boolean;
35
+ status: string;
36
+ commit?: {
37
+ hash: string;
38
+ subject: string;
39
+ };
40
+ };
41
+
42
+ type ParseAgentRunArgsOptions = {
43
+ readTextFile?: (path: string) => string;
20
44
  };
21
45
 
22
46
  type ParsedAgentRunArgs = {
@@ -26,8 +50,26 @@ type ParsedAgentRunArgs = {
26
50
  allowShell: boolean;
27
51
  runtimeMode?: AgentRuntimeRequestedMode;
28
52
  continueDialogId?: string;
53
+ spaceId?: string;
54
+ category?: string;
55
+ inheritedFromDialogKey?: string;
56
+ parentDialogId?: string;
57
+ background: boolean;
58
+ noStream: boolean;
59
+ noDefaultTestRoot: boolean;
60
+ cwd?: string;
61
+ maxToolRounds?: number;
62
+ timeoutMs?: number;
63
+ traceTools: boolean;
64
+ useTaskWorktree: boolean;
65
+ taskRunContext?: TaskRunPromptContext;
29
66
  };
30
67
 
68
+ const DEFAULT_TEST_ROOT_DIALOG_ID = "01KN6V3MVEQA4Q1PNKVM2W7YY8";
69
+ const DEFAULT_TEST_ROOT_DIALOG_KEY = `dialog-b2e06f801f-${DEFAULT_TEST_ROOT_DIALOG_ID}`;
70
+ const DEFAULT_AGENT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
71
+ const DEFAULT_TEST_DIALOG_CATEGORY_ID = "test-dialogs";
72
+
31
73
  function readFlagValue(args: string[], flag: string) {
32
74
  const index = args.indexOf(flag);
33
75
  if (index < 0) return undefined;
@@ -52,6 +94,20 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
52
94
  return undefined;
53
95
  }
54
96
 
97
+ function parseNonNegativeInteger(value: string | undefined) {
98
+ if (!value) return undefined;
99
+ const parsed = Number(value);
100
+ if (!Number.isFinite(parsed) || parsed < 0) return undefined;
101
+ return Math.floor(parsed);
102
+ }
103
+
104
+ function parsePositiveInteger(value: string | undefined) {
105
+ if (!value) return undefined;
106
+ const parsed = Number(value);
107
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
108
+ return Math.floor(parsed);
109
+ }
110
+
55
111
  function positionalArgs(args: string[]) {
56
112
  const values: string[] = [];
57
113
  const valuelessFlags = new Set([
@@ -59,6 +115,12 @@ function positionalArgs(args: string[]) {
59
115
  "--server",
60
116
  "--auto",
61
117
  "--dangerously-allow-shell",
118
+ "--worktree-task",
119
+ "--trace-tools",
120
+ "--bg",
121
+ "--no-stream",
122
+ "--debug",
123
+ "--no-default-test-root",
62
124
  ]);
63
125
  for (let index = 0; index < args.length; index += 1) {
64
126
  const arg = args[index];
@@ -72,24 +134,98 @@ function positionalArgs(args: string[]) {
72
134
  return values;
73
135
  }
74
136
 
75
- export function parseAgentRunArgs(args: string[]): ParsedAgentRunArgs | null {
137
+ export function parseAgentRunArgs(
138
+ args: string[],
139
+ options: ParseAgentRunArgsOptions = {}
140
+ ): ParsedAgentRunArgs | null {
76
141
  const agentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
77
142
  const explicitMsg = readFlagValue(args, "--msg");
78
- const message = explicitMsg ?? positionalArgs(args).slice(1).join(" ");
79
- if (!agentKey || !message.trim()) return null;
143
+ const msgFile = readFlagValue(args, "--msg-file");
144
+ const fileMessage = msgFile
145
+ ? (options.readTextFile ?? ((path: string) => readFileSync(path, "utf8")))(msgFile)
146
+ : undefined;
147
+ const rawMessage = explicitMsg ?? fileMessage ?? positionalArgs(args).slice(1).join(" ");
148
+ if (!agentKey || !rawMessage.trim()) return null;
149
+ const message = rawMessage.trim();
80
150
  const runtimeMode = runtimeModeFromArgs(args);
81
151
  const continueDialogId = readFlagValue(args, "--continue") ?? readFlagValue(args, "--dialog");
152
+ const spaceId = readFlagValue(args, "--space");
153
+ const category = readFlagValue(args, "--category");
154
+ const inheritedFromDialog = readFlagValue(args, "--inherit-from-dialog");
155
+ const noDefaultTestRoot = args.includes("--no-default-test-root");
156
+ const cwd = readFlagValue(args, "--cwd");
157
+ const maxToolRounds = parseNonNegativeInteger(
158
+ readFlagValue(args, "--max-tool-rounds") ?? readFlagValue(args, "--rounds")
159
+ );
160
+ const timeoutMs = parsePositiveInteger(readFlagValue(args, "--timeout-ms"));
161
+ const inheritedRef = inheritedFromDialog
162
+ ? parseDialogReference(inheritedFromDialog)
163
+ : !continueDialogId && !noDefaultTestRoot
164
+ ? {
165
+ dialogKey: DEFAULT_TEST_ROOT_DIALOG_KEY,
166
+ dialogId: DEFAULT_TEST_ROOT_DIALOG_ID,
167
+ }
168
+ : undefined;
82
169
  const imageUrls = [
83
170
  ...readRepeatedFlagValues(args, "--image"),
84
171
  ...readRepeatedFlagValues(args, "--image-url"),
85
172
  ];
173
+ const useTaskWorktree = args.includes("--worktree-task");
174
+ const taskRowDbKey = readFlagValue(args, "--task-row-dbkey");
175
+ const artifactIds = readFlagValue(args, "--artifact-ids")
176
+ ?.split(",")
177
+ .map((item) => item.trim())
178
+ .filter(Boolean);
86
179
  return {
87
180
  agentKey: resolveCliAgentKeyInput(agentKey),
88
- message: message.trim(),
181
+ message,
89
182
  imageUrls,
90
183
  allowShell: args.includes("--dangerously-allow-shell"),
91
- ...(runtimeMode ? { runtimeMode } : {}),
184
+ traceTools: args.includes("--trace-tools"),
185
+ useTaskWorktree,
186
+ ...(taskRowDbKey
187
+ ? {
188
+ taskRunContext: {
189
+ rowDbKey: taskRowDbKey,
190
+ ...(readFlagValue(args, "--task-run-id") ? { taskRunId: readFlagValue(args, "--task-run-id") } : {}),
191
+ ...(readFlagValue(args, "--work-item-id") ? { workItemId: readFlagValue(args, "--work-item-id") } : {}),
192
+ ...(artifactIds?.length ? { artifactIds } : {}),
193
+ },
194
+ }
195
+ : {}),
196
+ ...(runtimeMode ? { runtimeMode } : useTaskWorktree ? { runtimeMode: "local" as const } : {}),
197
+ background: args.includes("--bg"),
198
+ noStream: args.includes("--no-stream"),
199
+ noDefaultTestRoot,
92
200
  ...(continueDialogId ? { continueDialogId } : {}),
201
+ ...(!continueDialogId ? { spaceId: spaceId ?? DEFAULT_AGENT_SPACE_ID } : {}),
202
+ ...(category ? { category } : !continueDialogId && !noDefaultTestRoot ? { category: DEFAULT_TEST_DIALOG_CATEGORY_ID } : {}),
203
+ ...(inheritedRef?.dialogKey ? { inheritedFromDialogKey: inheritedRef.dialogKey } : {}),
204
+ ...(inheritedRef?.dialogId ? { parentDialogId: inheritedRef.dialogId } : {}),
205
+ ...(cwd ? { cwd } : {}),
206
+ ...(typeof maxToolRounds === "number" ? { maxToolRounds } : {}),
207
+ ...(typeof timeoutMs === "number" ? { timeoutMs } : {}),
208
+ };
209
+ }
210
+
211
+ function parseDialogReference(rawInput: string) {
212
+ const normalized = rawInput.trim();
213
+ if (normalized.startsWith("dialog-")) {
214
+ const parts = normalized.split("-");
215
+ return {
216
+ dialogKey: normalized,
217
+ dialogId: parts.at(-1) ?? normalized,
218
+ };
219
+ }
220
+ const dialogMatch = normalized.match(/dialog-([a-zA-Z0-9]+)-([a-zA-Z0-9]+)/);
221
+ if (dialogMatch) {
222
+ return {
223
+ dialogKey: `dialog-${dialogMatch[1]}-${dialogMatch[2]}`,
224
+ dialogId: dialogMatch[2],
225
+ };
226
+ }
227
+ return {
228
+ dialogId: normalized,
93
229
  };
94
230
  }
95
231
 
@@ -115,24 +251,14 @@ export function normalizeCliImageInput(input: string) {
115
251
  return `data:${mimeTypeForPath(absolutePath)};base64,${base64}`;
116
252
  }
117
253
 
118
- function resolveServerUrl(env: EnvLike) {
119
- return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
120
- }
121
-
122
- function safePathSegment(input: string) {
123
- return input.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "agent";
124
- }
125
-
126
- function createTaskId() {
127
- return Date.now().toString(36).toUpperCase();
128
- }
129
-
130
- async function runGit(args: string[], cwd: string) {
131
- const proc = Bun.spawn(["git", ...args], {
132
- cwd,
254
+ async function runGitForLocalSummary(args: {
255
+ cwd: string;
256
+ gitArgs: string[];
257
+ }) {
258
+ const proc = Bun.spawn(["git", ...args.gitArgs], {
259
+ cwd: args.cwd,
133
260
  stdout: "pipe",
134
261
  stderr: "pipe",
135
- stdin: "ignore",
136
262
  });
137
263
  const [stdout, stderr, exitCode] = await Promise.all([
138
264
  new Response(proc.stdout).text(),
@@ -140,32 +266,80 @@ async function runGit(args: string[], cwd: string) {
140
266
  proc.exited,
141
267
  ]);
142
268
  if (exitCode !== 0) {
143
- throw new Error(stderr.trim() || stdout.trim() || `git ${args.join(" ")} failed`);
269
+ throw new Error((stderr || stdout || `git ${args.gitArgs.join(" ")} failed`).trim());
144
270
  }
145
271
  return stdout.trim();
146
272
  }
147
273
 
148
- export async function prepareShellWorktree(args: {
149
- agentKey: string;
150
- cwd?: string;
151
- env?: EnvLike;
274
+ async function inspectLocalRunWorkspace(cwd: string): Promise<LocalRunWorkspaceInspection> {
275
+ const status = await runGitForLocalSummary({
276
+ cwd,
277
+ gitArgs: ["status", "--short"],
278
+ });
279
+ const rawCommit = await runGitForLocalSummary({
280
+ cwd,
281
+ gitArgs: ["log", "-1", "--format=%h%x00%s"],
282
+ }).catch(() => "");
283
+ const [hash, subject] = rawCommit.split("\0");
284
+ return {
285
+ cwd,
286
+ clean: status.trim() === "",
287
+ status,
288
+ ...(hash ? { commit: { hash, subject: subject ?? "" } } : {}),
289
+ };
290
+ }
291
+
292
+ function shouldPrintLocalRunSummary(args: {
293
+ parsed: ParsedAgentRunArgs;
294
+ localRuntimeCwd?: string;
295
+ }) {
296
+ return Boolean(args.localRuntimeCwd && args.parsed.runtimeMode === "local");
297
+ }
298
+
299
+ function formatLocalRunSummary(args: {
300
+ dialogId?: string;
301
+ inspection: LocalRunWorkspaceInspection;
302
+ }) {
303
+ return [
304
+ "",
305
+ "[nolo] local run summary",
306
+ `workspace: ${args.inspection.cwd}`,
307
+ ...(args.dialogId ? [`dialog: ${args.dialogId}`] : []),
308
+ ...(args.inspection.commit
309
+ ? [`commit: ${args.inspection.commit.hash} ${args.inspection.commit.subject}`]
310
+ : []),
311
+ `dirty: ${args.inspection.clean ? "clean" : "dirty"}`,
312
+ ...(!args.inspection.clean && args.inspection.status.trim()
313
+ ? [`status:\n${args.inspection.status.trim()}`]
314
+ : []),
315
+ "",
316
+ ].join("\n");
317
+ }
318
+
319
+ function resolveServerUrl(env: EnvLike) {
320
+ return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
321
+ }
322
+
323
+ function shouldPrepareTaskWorktree(parsed: ParsedAgentRunArgs) {
324
+ return !parsed.cwd && (parsed.useTaskWorktree || parsed.allowShell);
325
+ }
326
+
327
+ function buildTaskWorktreeEnv(args: {
328
+ env: EnvLike;
329
+ worktreePath?: string;
330
+ allowShell: boolean;
152
331
  }) {
153
- if (args.env?.NOLO_LOCAL_WORKTREE) return args.env.NOLO_LOCAL_WORKTREE;
154
- const cwd = args.cwd ?? process.cwd();
155
- const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
156
- const parent = join(root, ".worktrees");
157
- const safeAgent = safePathSegment(args.agentKey);
158
- const taskId = createTaskId();
159
- const worktreePath = join(parent, `nolo-agent-${safeAgent}-${taskId}`);
160
- const branchName = `nolo-agent-${safeAgent}-${taskId}`;
161
- await runGit(["worktree", "add", worktreePath, "-b", branchName, "HEAD"], root);
162
- return worktreePath;
332
+ return {
333
+ ...args.env,
334
+ ...(args.allowShell ? { NOLO_LOCAL_SHELL_MODE: "worktree" } : {}),
335
+ ...(args.worktreePath ? { NOLO_LOCAL_WORKTREE: args.worktreePath } : {}),
336
+ };
163
337
  }
164
338
 
165
339
  function writeUsage(output: OutputLike) {
166
340
  output.write(
167
- "Usage: nolo agent run <agent> <message> [--local|--server|--auto] [--continue <dialogId>]\n" +
168
- " nolo agent run --agent <agent> --msg <message> [--image <url-or-path>] [--dangerously-allow-shell] [--local|--server|--auto]\n"
341
+ "Usage: nolo agent run <agent> <message> [--local|--server|--auto] [--continue <dialogId>] [--cwd <path>] [--max-tool-rounds <n>]\n" +
342
+ " nolo agent run --agent <agent> --msg <message> [--image <url-or-path>] [--space <spaceId>] [--category <name>] [--inherit-from-dialog <dialog>] [--task-row-dbkey <key>] [--bg] [--timeout-ms <n>] [--no-stream]\n"
169
343
  );
170
344
  }
171
345
 
@@ -179,19 +353,47 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
179
353
  }
180
354
 
181
355
  const runner = deps.runner ?? runAgentTurn;
182
- let localRuntimeCwd: string | undefined;
183
- if (parsed.allowShell) {
184
- localRuntimeCwd = await (deps.prepareShellWorktree ?? prepareShellWorktree)({
185
- agentKey: parsed.agentKey,
186
- env,
187
- });
356
+ let localRuntimeCwd = parsed.cwd;
357
+ if (shouldPrepareTaskWorktree(parsed)) {
358
+ try {
359
+ const preparedWorktree = await (deps.prepareTaskWorktree ?? prepareTaskWorktree)({
360
+ agentKey: parsed.agentKey,
361
+ env,
362
+ });
363
+ localRuntimeCwd = preparedWorktree.path;
364
+ output.write(formatPreparedTaskWorktree(preparedWorktree));
365
+ } catch (error) {
366
+ output.write(
367
+ "[nolo] Could not prepare a local task worktree.\n" +
368
+ "Run from inside a git repository, or set NOLO_LOCAL_WORKTREE to an existing isolated checkout.\n" +
369
+ `Reason: ${error instanceof Error ? error.message : String(error)}\n`
370
+ );
371
+ return 1;
372
+ }
373
+ }
374
+ const shouldBindLocalWorkspace =
375
+ !!localRuntimeCwd &&
376
+ (parsed.runtimeMode === "local" || parsed.allowShell || parsed.useTaskWorktree);
377
+ if (shouldBindLocalWorkspace) {
378
+ try {
379
+ await (deps.ensureWorkspacePackageLinks ?? ensureWorkspacePackageLinks)(
380
+ localRuntimeCwd!
381
+ );
382
+ } catch (error) {
383
+ output.write(
384
+ "[nolo] Dependency preflight failed for local task worktree.\n" +
385
+ "The worktree may have node_modules entries that point outside the task checkout, or paths that cannot be repaired safely.\n" +
386
+ `Reason: ${error instanceof Error ? error.message : String(error)}\n`
387
+ );
388
+ return 1;
389
+ }
188
390
  }
189
- const runEnv = parsed.allowShell
190
- ? {
191
- ...env,
192
- NOLO_LOCAL_SHELL_MODE: "worktree",
193
- ...(localRuntimeCwd ? { NOLO_LOCAL_WORKTREE: localRuntimeCwd } : {}),
194
- }
391
+ const runEnv = shouldBindLocalWorkspace
392
+ ? buildTaskWorktreeEnv({
393
+ env,
394
+ worktreePath: localRuntimeCwd,
395
+ allowShell: parsed.allowShell,
396
+ })
195
397
  : env;
196
398
  const result: RunAgentTurnResult = await runner({
197
399
  agentName: parsed.agentKey,
@@ -202,13 +404,46 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
202
404
  scriptDir: deps.scriptDir,
203
405
  env: runEnv,
204
406
  output,
407
+ ...(deps.localRuntimeAdapterFactory
408
+ ? { localRuntimeAdapterFactory: deps.localRuntimeAdapterFactory }
409
+ : {}),
205
410
  ...(localRuntimeCwd ? { localRuntimeCwd } : {}),
206
411
  ...(parsed.runtimeMode ? { runtimeMode: parsed.runtimeMode } : {}),
207
412
  ...(parsed.continueDialogId ? { continueDialogId: parsed.continueDialogId } : {}),
413
+ ...(parsed.spaceId ? { spaceId: parsed.spaceId } : {}),
414
+ ...(parsed.category ? { category: parsed.category } : {}),
415
+ ...(parsed.inheritedFromDialogKey ? { inheritedFromDialogKey: parsed.inheritedFromDialogKey } : {}),
416
+ ...(parsed.parentDialogId ? { parentDialogId: parsed.parentDialogId } : {}),
417
+ background: parsed.background,
418
+ noStream: parsed.noStream,
419
+ noDefaultTestRoot: parsed.noDefaultTestRoot,
420
+ ...(typeof parsed.maxToolRounds === "number" ? { maxToolRounds: parsed.maxToolRounds } : {}),
421
+ ...(typeof parsed.timeoutMs === "number" ? { timeoutMs: parsed.timeoutMs } : {}),
422
+ traceTools: parsed.traceTools,
423
+ ...(parsed.taskRunContext ? { taskRunContext: parsed.taskRunContext } : {}),
208
424
  });
209
425
 
210
426
  if (result.dialogId) {
211
- output.write(`\n[nolo] dialog ${result.dialogId}\n`);
427
+ if (parsed.background) {
428
+ output.write(`\n[nolo] background dialog ${result.dialogId}\n`);
429
+ output.write(`[nolo] read: nolo dialog read ${result.dialogId}\n`);
430
+ } else {
431
+ output.write(`\n[nolo] dialog ${result.dialogId}\n`);
432
+ }
433
+ }
434
+ if (shouldPrintLocalRunSummary({ parsed, localRuntimeCwd })) {
435
+ try {
436
+ const inspect = deps.inspectLocalRunWorkspace ?? inspectLocalRunWorkspace;
437
+ const inspection = await inspect(localRuntimeCwd!);
438
+ output.write(formatLocalRunSummary({
439
+ dialogId: result.dialogId,
440
+ inspection,
441
+ }));
442
+ } catch (error) {
443
+ output.write(
444
+ `\n[nolo] local run summary unavailable: ${error instanceof Error ? error.message : String(error)}\n`
445
+ );
446
+ }
212
447
  }
213
448
  return result.exitCode;
214
449
  }