nolo-cli 0.1.19 → 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 +13 -0
  8. package/agent-runtime/hybridRecordStore.ts +147 -0
  9. package/agent-runtime/index.ts +69 -0
  10. package/agent-runtime/localLoop.ts +69 -5
  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 +1 -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 +278 -52
  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 +621 -9
  89. package/client/localRuntimeAdapter.ts +275 -250
  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,28 +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) {
356
+ let localRuntimeCwd = parsed.cwd;
357
+ if (shouldPrepareTaskWorktree(parsed)) {
184
358
  try {
185
- localRuntimeCwd = await (deps.prepareShellWorktree ?? prepareShellWorktree)({
359
+ const preparedWorktree = await (deps.prepareTaskWorktree ?? prepareTaskWorktree)({
186
360
  agentKey: parsed.agentKey,
187
361
  env,
188
362
  });
363
+ localRuntimeCwd = preparedWorktree.path;
364
+ output.write(formatPreparedTaskWorktree(preparedWorktree));
189
365
  } catch (error) {
190
366
  output.write(
191
- "[nolo] Could not prepare a local shell worktree.\n" +
367
+ "[nolo] Could not prepare a local task worktree.\n" +
192
368
  "Run from inside a git repository, or set NOLO_LOCAL_WORKTREE to an existing isolated checkout.\n" +
193
369
  `Reason: ${error instanceof Error ? error.message : String(error)}\n`
194
370
  );
195
371
  return 1;
196
372
  }
197
373
  }
198
- const runEnv = parsed.allowShell
199
- ? {
200
- ...env,
201
- NOLO_LOCAL_SHELL_MODE: "worktree",
202
- ...(localRuntimeCwd ? { NOLO_LOCAL_WORKTREE: localRuntimeCwd } : {}),
203
- }
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
+ }
390
+ }
391
+ const runEnv = shouldBindLocalWorkspace
392
+ ? buildTaskWorktreeEnv({
393
+ env,
394
+ worktreePath: localRuntimeCwd,
395
+ allowShell: parsed.allowShell,
396
+ })
204
397
  : env;
205
398
  const result: RunAgentTurnResult = await runner({
206
399
  agentName: parsed.agentKey,
@@ -211,13 +404,46 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
211
404
  scriptDir: deps.scriptDir,
212
405
  env: runEnv,
213
406
  output,
407
+ ...(deps.localRuntimeAdapterFactory
408
+ ? { localRuntimeAdapterFactory: deps.localRuntimeAdapterFactory }
409
+ : {}),
214
410
  ...(localRuntimeCwd ? { localRuntimeCwd } : {}),
215
411
  ...(parsed.runtimeMode ? { runtimeMode: parsed.runtimeMode } : {}),
216
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 } : {}),
217
424
  });
218
425
 
219
426
  if (result.dialogId) {
220
- 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
+ }
221
447
  }
222
448
  return result.exitCode;
223
449
  }