nolo-cli 0.1.38 → 0.1.39

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 (52) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -14
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/index.ts +24 -18
  4. package/agent-runtime/localDialogRead.ts +23 -0
  5. package/agent-runtime/localToolPolicy.ts +2 -7
  6. package/agent-runtime/localWorkspaceTools.ts +9 -0
  7. package/agent-runtime/noloWorkspaceTools.ts +371 -0
  8. package/agent-runtime/runtimeToolPolicy.ts +1 -3
  9. package/agent-runtime/types.ts +1 -1
  10. package/agentAliases.ts +6 -0
  11. package/agentRunCommand.ts +57 -65
  12. package/agentRuntimeLocal.ts +0 -7
  13. package/chat/messages/fetchMessages.ts +84 -0
  14. package/chat/messages/types.ts +185 -0
  15. package/cli/agentAliases.ts +6 -0
  16. package/cli/agentRunCommand.ts +57 -65
  17. package/cli/agentRuntimeLocal.ts +0 -7
  18. package/cli/cliEnvHelpers.ts +11 -7
  19. package/cli/client/agentRun.ts +55 -7
  20. package/cli/client/localRuntimeAdapter.ts +87 -59
  21. package/cli/connectorRunArtifact.ts +0 -1
  22. package/cli/dialogCommands.ts +572 -0
  23. package/cli/dialogInternalCommandEntries.ts +4 -0
  24. package/cli/machineWsRunDispatch.ts +29 -37
  25. package/cli/scriptCommandEntries.ts +0 -2
  26. package/cli/tui/readlineWorkspace.ts +69 -0
  27. package/cli/tui/session.ts +176 -1
  28. package/cliEnvHelpers.ts +11 -7
  29. package/client/agentConfigResolver.test.ts +2 -3
  30. package/client/agentRun.test.ts +185 -1
  31. package/client/agentRun.ts +55 -7
  32. package/client/localRuntimeAdapter.test.ts +90 -25
  33. package/client/localRuntimeAdapter.ts +87 -59
  34. package/connectorRunArtifact.ts +0 -1
  35. package/database/server/db.ts +99 -0
  36. package/database/server/ensureDbOpen.ts +12 -0
  37. package/database/server/memoryAuthorityStore.ts +133 -0
  38. package/database/server/serverStoreFactory.ts +118 -0
  39. package/dialogCommands.ts +572 -0
  40. package/dialogInternalCommandEntries.ts +4 -0
  41. package/machineWsRunDispatch.ts +29 -37
  42. package/package.json +10 -9
  43. package/scriptCommandEntries.ts +0 -2
  44. package/tui/readlineWorkspace.ts +69 -0
  45. package/tui/session.ts +176 -1
  46. package/agent-runtime/taskWorkspace.ts +0 -193
  47. package/agent-runtime/workspaceSession.ts +0 -76
  48. package/cli/client/taskWorktree.ts +0 -8
  49. package/cli/client/workspaceSession.ts +0 -11
  50. package/client/taskWorktree.ts +0 -8
  51. package/client/workspaceSession.test.ts +0 -57
  52. package/client/workspaceSession.ts +0 -11
@@ -4,13 +4,7 @@ import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "./agent
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { extname, resolve } from "node:path";
6
6
  import { parseSkillDocProtocol, type WorkflowReferenceConfig } from "../ai/skills/skillDocProtocol";
7
- import { resolveCliAgentKeyInput } from "./agentAliases";
8
- import {
9
- ensureWorkspacePackageLinks,
10
- formatPreparedTaskWorktree,
11
- prepareTaskWorktree,
12
- type PreparedTaskWorktree,
13
- } from "./client/taskWorktree";
7
+ import { MIMO_MONTH_AGENT_KEY, resolveCliAgentKeyInput } from "./agentAliases";
14
8
  import type { TaskRunPromptContext } from "./client/taskRunPrompt";
15
9
 
16
10
  type EnvLike = Record<string, string | undefined>;
@@ -25,8 +19,6 @@ type AgentRunCommandDeps = {
25
19
  output?: OutputLike;
26
20
  runner?: typeof runAgentTurn;
27
21
  localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
28
- prepareTaskWorktree?: typeof prepareTaskWorktree;
29
- ensureWorkspacePackageLinks?: typeof ensureWorkspacePackageLinks;
30
22
  inspectLocalRunWorkspace?: typeof inspectLocalRunWorkspace;
31
23
  resolveWorkflowReference?: typeof resolveWorkflowReference;
32
24
  };
@@ -63,7 +55,7 @@ type ParsedAgentRunArgs = {
63
55
  maxToolRounds?: number;
64
56
  timeoutMs?: number;
65
57
  traceTools: boolean;
66
- useTaskWorktree: boolean;
58
+ injectFeatureWorktreeInstruction: boolean;
67
59
  taskRunContext?: TaskRunPromptContext;
68
60
  workflowRef?: string;
69
61
  };
@@ -102,6 +94,21 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
102
94
  return undefined;
103
95
  }
104
96
 
97
+ function isMonthlyMimoAgentRef(raw: string | undefined, resolved: string) {
98
+ const normalized = raw?.trim().toLowerCase();
99
+ return (
100
+ resolved === MIMO_MONTH_AGENT_KEY ||
101
+ normalized === "fullstack" ||
102
+ normalized === "full-stack" ||
103
+ normalized === "nolo-fullstack" ||
104
+ normalized === "包月mimo" ||
105
+ normalized === "包月mimo2.5" ||
106
+ normalized === "mimo-month" ||
107
+ normalized === "全栈" ||
108
+ normalized === "nolo 全栈工程师"
109
+ );
110
+ }
111
+
105
112
  function parseNonNegativeInteger(value: string | undefined) {
106
113
  if (!value) return undefined;
107
114
  const parsed = Number(value);
@@ -123,7 +130,6 @@ function positionalArgs(args: string[]) {
123
130
  "--server",
124
131
  "--auto",
125
132
  "--dangerously-allow-shell",
126
- "--worktree-task",
127
133
  "--trace-tools",
128
134
  "--bg",
129
135
  "--no-stream",
@@ -146,7 +152,8 @@ export function parseAgentRunArgs(
146
152
  args: string[],
147
153
  options: ParseAgentRunArgsOptions = {}
148
154
  ): ParsedAgentRunArgs | null {
149
- const agentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
155
+ const rawAgentKey = readFlagValue(args, "--agent") ?? positionalArgs(args)[0];
156
+ const agentKey = rawAgentKey ? resolveCliAgentKeyInput(rawAgentKey) : undefined;
150
157
  const explicitMsg = readFlagValue(args, "--msg");
151
158
  const msgFile = readFlagValue(args, "--msg-file");
152
159
  const fileMessage = msgFile
@@ -178,7 +185,8 @@ export function parseAgentRunArgs(
178
185
  ...readRepeatedFlagValues(args, "--image"),
179
186
  ...readRepeatedFlagValues(args, "--image-url"),
180
187
  ];
181
- const useTaskWorktree = args.includes("--worktree-task");
188
+ const mimoLocalWorkspaceDefault =
189
+ isMonthlyMimoAgentRef(rawAgentKey, agentKey) && runtimeMode !== "server";
182
190
  const workflowRef = readFlagValue(args, "--workflow");
183
191
  const taskRowDbKey = readFlagValue(args, "--task-row-dbkey");
184
192
  const artifactIds = readFlagValue(args, "--artifact-ids")
@@ -186,12 +194,12 @@ export function parseAgentRunArgs(
186
194
  .map((item) => item.trim())
187
195
  .filter(Boolean);
188
196
  return {
189
- agentKey: resolveCliAgentKeyInput(agentKey),
197
+ agentKey,
190
198
  message,
191
199
  imageUrls,
192
- allowShell: args.includes("--dangerously-allow-shell"),
200
+ allowShell: args.includes("--dangerously-allow-shell") || mimoLocalWorkspaceDefault,
193
201
  traceTools: args.includes("--trace-tools"),
194
- useTaskWorktree,
202
+ injectFeatureWorktreeInstruction: mimoLocalWorkspaceDefault,
195
203
  ...(workflowRef ? { workflowRef } : {}),
196
204
  ...(taskRowDbKey
197
205
  ? {
@@ -203,7 +211,11 @@ export function parseAgentRunArgs(
203
211
  },
204
212
  }
205
213
  : {}),
206
- ...(runtimeMode ? { runtimeMode } : useTaskWorktree ? { runtimeMode: "local" as const } : {}),
214
+ ...(runtimeMode
215
+ ? { runtimeMode }
216
+ : mimoLocalWorkspaceDefault || args.includes("--dangerously-allow-shell")
217
+ ? { runtimeMode: "local" as const }
218
+ : {}),
207
219
  background: args.includes("--bg"),
208
220
  noStream: args.includes("--no-stream"),
209
221
  noDefaultTestRoot,
@@ -269,6 +281,20 @@ export function prependWorkflowReferencePrompt(message: string, workflow?: Resol
269
281
  ].join("\n");
270
282
  }
271
283
 
284
+ function prependFeatureWorktreeInstruction(message: string, enabled: boolean) {
285
+ if (!enabled) return message;
286
+ return [
287
+ "Local execution rule:",
288
+ "- You are running in the current git checkout with shell access.",
289
+ "- For read-only checks, smoke tests, or answering questions, stay in the current directory.",
290
+ "- Before developing a new feature or making non-trivial code changes, create a separate git worktree yourself with git worktree and do the edits there.",
291
+ "- Commit and push only when the user explicitly asks or the task requires it.",
292
+ "",
293
+ "User task:",
294
+ message,
295
+ ].join("\n");
296
+ }
297
+
272
298
  function parseDialogReference(rawInput: string) {
273
299
  const normalized = rawInput.trim();
274
300
  if (normalized.startsWith("dialog-")) {
@@ -381,19 +407,13 @@ function resolveServerUrl(env: EnvLike) {
381
407
  return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
382
408
  }
383
409
 
384
- function shouldPrepareTaskWorktree(parsed: ParsedAgentRunArgs) {
385
- return !parsed.cwd && (parsed.useTaskWorktree || parsed.allowShell);
386
- }
387
-
388
- function buildTaskWorktreeEnv(args: {
410
+ function buildLocalRunEnv(args: {
389
411
  env: EnvLike;
390
- worktreePath?: string;
391
412
  allowShell: boolean;
392
413
  }) {
393
414
  return {
394
415
  ...args.env,
395
416
  ...(args.allowShell ? { NOLO_LOCAL_SHELL_MODE: "worktree" } : {}),
396
- ...(args.worktreePath ? { NOLO_LOCAL_WORKTREE: args.worktreePath } : {}),
397
417
  };
398
418
  }
399
419
 
@@ -426,52 +446,24 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
426
446
  }
427
447
  }
428
448
  let localRuntimeCwd = parsed.cwd;
429
- if (shouldPrepareTaskWorktree(parsed)) {
430
- try {
431
- const preparedWorktree = await (deps.prepareTaskWorktree ?? prepareTaskWorktree)({
432
- agentKey: parsed.agentKey,
433
- env,
434
- });
435
- localRuntimeCwd = preparedWorktree.path;
436
- output.write(formatPreparedTaskWorktree(preparedWorktree));
437
- } catch (error) {
438
- output.write(
439
- "[nolo] Could not prepare a local task worktree.\n" +
440
- "Run from inside a git repository, or set NOLO_LOCAL_WORKTREE to an existing isolated checkout.\n" +
441
- `Reason: ${error instanceof Error ? error.message : String(error)}\n`
442
- );
443
- return 1;
444
- }
449
+ if (!localRuntimeCwd && parsed.runtimeMode === "local") {
450
+ localRuntimeCwd = process.cwd();
445
451
  }
446
- const shouldBindLocalWorkspace =
447
- !!localRuntimeCwd &&
448
- (parsed.runtimeMode === "local" || parsed.allowShell || parsed.useTaskWorktree);
449
- if (shouldBindLocalWorkspace) {
450
- try {
451
- await (deps.ensureWorkspacePackageLinks ?? ensureWorkspacePackageLinks)(
452
- localRuntimeCwd!
453
- );
454
- } catch (error) {
455
- output.write(
456
- "[nolo] Dependency preflight failed for local task worktree.\n" +
457
- "The worktree may have node_modules entries that point outside the task checkout, or paths that cannot be repaired safely.\n" +
458
- `Reason: ${error instanceof Error ? error.message : String(error)}\n`
459
- );
460
- return 1;
461
- }
462
- }
463
- const runEnv = shouldBindLocalWorkspace
464
- ? buildTaskWorktreeEnv({
465
- env,
466
- worktreePath: localRuntimeCwd,
467
- allowShell: parsed.allowShell,
468
- })
469
- : env;
452
+ const runEnv = buildLocalRunEnv({
453
+ env,
454
+ allowShell: parsed.allowShell,
455
+ });
470
456
  const result: RunAgentTurnResult = await runner({
471
457
  agentName: parsed.agentKey,
472
458
  agentKey: parsed.agentKey,
473
459
  serverUrl: resolveServerUrl(env),
474
- message: prependWorkflowReferencePrompt(parsed.message, workflowReference),
460
+ message: prependWorkflowReferencePrompt(
461
+ prependFeatureWorktreeInstruction(
462
+ parsed.message,
463
+ parsed.injectFeatureWorktreeInstruction
464
+ ),
465
+ workflowReference
466
+ ),
475
467
  imageUrls: parsed.imageUrls.map(normalizeCliImageInput),
476
468
  scriptDir: deps.scriptDir,
477
469
  env: runEnv,
@@ -5,12 +5,9 @@ export {
5
5
  hasDirectOpenAiCompatibleProvider,
6
6
  createHybridRecordStore,
7
7
  executeLocalToolWithPolicy,
8
- activateWorkspaceSession,
9
- createWorkspaceSession,
10
8
  buildAgentRuntimeAgentLookupKeys,
11
9
  buildAgentRuntimeDialogWritePlan,
12
10
  dialogMessageRecordToAgentRuntimeMessage,
13
- formatWorkspaceSessionActivation,
14
11
  pickAgentRuntimeInferenceOptions,
15
12
  parseOpenAiCompatibleChatCompletionResponse,
16
13
  parsePlatformChatCompletionData,
@@ -44,8 +41,4 @@ export type {
44
41
  HybridRecordKvDb,
45
42
  HybridRecordStore,
46
43
  LocalToolPolicyDecision,
47
- PreparedTaskWorkspace,
48
- PrepareTaskWorkspace,
49
- WorkspaceSession,
50
- WorkspaceSessionMode,
51
44
  } from "../agent-runtime";
@@ -96,14 +96,18 @@ export function resolveAuthToken(
96
96
  }
97
97
 
98
98
  export function parseUserIdFromAuthToken(token: string) {
99
- const payload = token.trim().split(".")[0];
100
- if (!payload) return "";
101
- try {
102
- const parsed = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
103
- return typeof parsed?.userId === "string" ? parsed.userId : "";
104
- } catch {
105
- return "";
99
+ const parts = token.trim().split(".").filter(Boolean);
100
+ const payloadCandidates = parts.length >= 3 ? [parts[1], parts[0]] : [parts[0]];
101
+ for (const payload of payloadCandidates) {
102
+ if (!payload) continue;
103
+ try {
104
+ const parsed = JSON.parse(Buffer.from(payload, "base64").toString("utf8"));
105
+ if (typeof parsed?.userId === "string") return parsed.userId;
106
+ } catch {
107
+ // Try the next token shape.
108
+ }
106
109
  }
110
+ return "";
107
111
  }
108
112
 
109
113
  export function readOption(args: string[], flag: string) {
@@ -9,7 +9,7 @@ import {
9
9
  } from "../agentAliases";
10
10
  import type { LocalAgentToolEvent } from "../../agent-runtime/localLoop";
11
11
  import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
12
- import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
12
+ import { createCliLocalRuntimeAdapter, isBuiltinNoloAgentRef } from "./localRuntimeAdapter";
13
13
  import { createStreamingTextWriter } from "./streamingOutput";
14
14
  import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
15
15
 
@@ -180,10 +180,27 @@ function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
180
180
  const normalizedKey = normalizeAgentRef(options.agentKey);
181
181
  return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
182
182
  }
183
-
184
- function resolveAuthToken(env: EnvLike) {
185
- return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
186
- }
183
+
184
+ function isMachineBoundLocalhostCustomProvider(agentConfig: any) {
185
+ const machineId =
186
+ agentConfig?.runtimeBinding && typeof agentConfig.runtimeBinding === "object"
187
+ ? String(agentConfig.runtimeBinding.machineId ?? "").trim()
188
+ : "";
189
+ const providerUrl = typeof agentConfig?.customProviderUrl === "string"
190
+ ? agentConfig.customProviderUrl.trim()
191
+ : "";
192
+ if (!machineId || !providerUrl) return false;
193
+ try {
194
+ const hostname = new URL(providerUrl).hostname.toLowerCase();
195
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+
201
+ function resolveAuthToken(env: EnvLike) {
202
+ return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
203
+ }
187
204
 
188
205
  function shouldShowUsage(env: EnvLike) {
189
206
  return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
@@ -214,6 +231,10 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
214
231
  }
215
232
 
216
233
  async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
234
+ if (isBuiltinNoloAgentRef(options.agentKey)) return false;
235
+ if (options.localRuntimeCwd && options.env.NOLO_LOCAL_SHELL_MODE === "worktree") {
236
+ return false;
237
+ }
217
238
  if (isKnownServerPlatformAgent(options)) {
218
239
  options.output.write(
219
240
  `[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
@@ -229,6 +250,13 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
229
250
  } catch {
230
251
  return false;
231
252
  }
253
+ if (isMachineBoundLocalhostCustomProvider(agentConfig)) {
254
+ options.output.write(
255
+ `[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a machine-bound localhost custom provider. ` +
256
+ "Use --local explicitly to force the current machine.\n"
257
+ );
258
+ return true;
259
+ }
232
260
  const serverTools = resolveServerPlatformToolNames(agentConfig);
233
261
  if (serverTools.length === 0) return false;
234
262
  options.output.write(
@@ -329,6 +357,13 @@ function formatToolTraceEvent(event: LocalAgentToolEvent) {
329
357
 
330
358
  function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
331
359
  if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
360
+ if (
361
+ options.env.NOLO_DISABLE_CLI_WORKSPACE_TOOLS !== "1" &&
362
+ isBuiltinNoloAgentRef(options.agentKey) &&
363
+ resolveAuthToken(options.env)
364
+ ) {
365
+ return true;
366
+ }
332
367
  return Boolean(
333
368
  options.env.NOLO_LOCAL_OPENAI_API_KEY ||
334
369
  options.env.OPENAI_API_KEY ||
@@ -632,8 +667,21 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
632
667
 
633
668
  if (!res.ok) {
634
669
  options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
635
- if (data?.error || data?.message) {
636
- options.output.write(`${data.error || data.message}\n`);
670
+ const errorText = typeof data?.error === "string" ? data.error.trim() : "";
671
+ const messageText = typeof data?.message === "string" ? data.message.trim() : "";
672
+ const reasonText = typeof data?.reason === "string" ? data.reason.trim() : "";
673
+ const codeText = typeof data?.code === "string" ? data.code.trim() : "";
674
+ if (errorText || messageText) {
675
+ options.output.write(`${errorText || messageText}\n`);
676
+ if (messageText && messageText !== errorText) {
677
+ options.output.write(`${messageText}\n`);
678
+ }
679
+ if (codeText && codeText !== errorText && codeText !== messageText) {
680
+ options.output.write(`code=${codeText}\n`);
681
+ }
682
+ if (reasonText && reasonText !== errorText && reasonText !== messageText) {
683
+ options.output.write(`reason=${reasonText}\n`);
684
+ }
637
685
  }
638
686
  await recordTaskRunOutcome({
639
687
  options,
@@ -5,6 +5,8 @@ import type {
5
5
  } from "../agentRuntimeLocal";
6
6
  import { request as httpRequest } from "node:http";
7
7
  import { request as httpsRequest } from "node:https";
8
+ import { dirname, join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import {
9
11
  buildLocalWorkspacePolicyToolNames,
10
12
  buildLocalWorkspaceToolset,
@@ -40,19 +42,28 @@ import {
40
42
  } from "./hybridRecordStore";
41
43
  import { executeLocalToolWithPolicy } from "./localToolPolicy";
42
44
  import { taskRunFunctionSchema } from "../../ai/tools/agent/taskRunTool";
43
- import { prepareTaskWorktree } from "./taskWorktree";
44
45
  import {
45
- activateWorkspaceSession,
46
- createWorkspaceSession,
47
- formatWorkspaceSessionActivation,
48
- type WorkspaceSession,
49
- } from "./workspaceSession";
46
+ NOLO_DEFAULT_AGENT_ID,
47
+ NOLO_DEFAULT_AGENT_KEY,
48
+ } from "../agentAliases";
49
+ import {
50
+ buildNoloWorkspaceCliToolExecutors,
51
+ buildNoloWorkspaceOpenAiTools,
52
+ filterNoloWorkspaceToolNames,
53
+ NOLO_WORKSPACE_TOOL_NAMES,
54
+ NOLO_WORKSPACE_TOOL_PROMPT,
55
+ parseNoloWorkspaceToolArguments,
56
+ } from "../../agent-runtime/noloWorkspaceTools";
50
57
 
51
58
  type EnvLike = Record<string, string | undefined>;
52
59
  type FetchInput = Parameters<typeof fetch>[0];
53
60
  type FetchInit = Parameters<typeof fetch>[1];
54
61
  const TRANSIENT_FETCH_MAX_ATTEMPTS = 8;
55
62
  const TRANSIENT_FETCH_RETRY_BASE_DELAY_MS = 250;
63
+ export const BUILTIN_NOLO_AGENT_ID = NOLO_DEFAULT_AGENT_ID;
64
+ export const BUILTIN_NOLO_AGENT_KEY = NOLO_DEFAULT_AGENT_KEY;
65
+ const CLI_DIR = dirname(fileURLToPath(import.meta.url));
66
+ const CLI_ENTRYPOINT = join(CLI_DIR, "..", "index.ts");
56
67
 
57
68
  export type CliLocalRuntimeDb = CliKvDb;
58
69
 
@@ -64,9 +75,7 @@ export type CliLocalRuntimeAdapterDeps = {
64
75
  createId?: () => string;
65
76
  fetchImpl?: typeof fetch;
66
77
  cwd?: string;
67
- useCwdAsTaskWorkspaceBase?: boolean;
68
78
  output?: { write(chunk: string): unknown };
69
- prepareTaskWorktree?: typeof prepareTaskWorktree;
70
79
  localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
71
80
  sleep?: (ms: number) => Promise<void>;
72
81
  loopbackRequest?: (input: FetchInput, init?: FetchInit) => Promise<Response>;
@@ -85,7 +94,7 @@ function resolveLocalUserId(env: EnvLike) {
85
94
  }
86
95
 
87
96
  function shellModeEnabled(env: EnvLike) {
88
- return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
97
+ return env.NOLO_LOCAL_SHELL_MODE === "worktree";
89
98
  }
90
99
 
91
100
  function parseLocalToolBudgets(env: EnvLike) {
@@ -246,6 +255,17 @@ function logLocalRuntimeDiagnostic(event: string, fields: Record<string, unknown
246
255
  console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
247
256
  }
248
257
 
258
+ function summarizeOpenAiToolNames(tools: Array<Record<string, unknown>>) {
259
+ return tools
260
+ .map((tool) => {
261
+ const fn = tool.function;
262
+ return fn && typeof fn === "object" && "name" in fn && typeof fn.name === "string"
263
+ ? fn.name
264
+ : null;
265
+ })
266
+ .filter((name): name is string => Boolean(name));
267
+ }
268
+
249
269
  function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
250
270
  const toolset = buildLocalWorkspaceToolsetForEnv(args);
251
271
  return [
@@ -254,6 +274,7 @@ function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
254
274
  exposeShellTools: toolset.exposeShellTools,
255
275
  }),
256
276
  ...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
277
+ ...buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }),
257
278
  ];
258
279
  }
259
280
 
@@ -272,6 +293,7 @@ function buildLocalPolicyToolNames(args: { toolNames?: string[]; env: EnvLike })
272
293
  exposeShellTools: shellModeEnabled(args.env),
273
294
  }),
274
295
  ...((args.toolNames ?? []).includes("taskRun") ? ["taskRun"] : []),
296
+ ...filterNoloWorkspaceToolNames(args.toolNames),
275
297
  ];
276
298
  }
277
299
 
@@ -287,13 +309,44 @@ function buildServerPlatformOpenAiTools(args: { toolNames?: string[] }) {
287
309
  ];
288
310
  }
289
311
 
290
- function parseToolArguments(raw: string) {
291
- try {
292
- const parsed = JSON.parse(raw || "{}");
293
- return parsed && typeof parsed === "object" ? parsed : {};
294
- } catch {
295
- return {};
296
- }
312
+ function buildCliWorkspaceToolExecutors(args: {
313
+ env: EnvLike;
314
+ cliEntrypoint?: string;
315
+ }) {
316
+ return buildNoloWorkspaceCliToolExecutors({
317
+ cliEntrypoint: args.cliEntrypoint ?? CLI_ENTRYPOINT,
318
+ env: args.env,
319
+ metadataKind: "cliWorkspaceTool",
320
+ });
321
+ }
322
+
323
+ export function isBuiltinNoloAgentRef(ref: unknown) {
324
+ if (typeof ref !== "string") return false;
325
+ const normalized = ref.trim();
326
+ return (
327
+ normalized === BUILTIN_NOLO_AGENT_KEY ||
328
+ normalized === BUILTIN_NOLO_AGENT_ID ||
329
+ normalized.endsWith(`-${BUILTIN_NOLO_AGENT_ID}`)
330
+ );
331
+ }
332
+
333
+ function isBuiltinNoloAgentConfig(agentConfig: AgentRuntimeAgentConfig | null | undefined) {
334
+ const key = agentConfig?.key || agentConfig?.rawRecord?.dbKey || agentConfig?.rawRecord?.agentKey;
335
+ const id = agentConfig?.rawRecord?.id;
336
+ return isBuiltinNoloAgentRef(key) || isBuiltinNoloAgentRef(id);
337
+ }
338
+
339
+ function withBuiltinNoloCliTools(agentConfig: AgentRuntimeAgentConfig | null) {
340
+ if (!agentConfig || !isBuiltinNoloAgentConfig(agentConfig)) return agentConfig;
341
+ const toolNames = [...new Set([...(agentConfig.toolNames ?? []), ...NOLO_WORKSPACE_TOOL_NAMES])];
342
+ return {
343
+ ...agentConfig,
344
+ toolNames,
345
+ prompt: [
346
+ agentConfig.prompt?.trim() ?? "",
347
+ NOLO_WORKSPACE_TOOL_PROMPT,
348
+ ].filter(Boolean).join("\n\n"),
349
+ };
297
350
  }
298
351
 
299
352
  function resolveRuntimeServerUrl(env: EnvLike) {
@@ -320,7 +373,7 @@ function buildServerPlatformToolExecutors(args: {
320
373
  "Content-Type": "application/json",
321
374
  Authorization: `Bearer ${authToken}`,
322
375
  },
323
- body: JSON.stringify(parseToolArguments(call.arguments)),
376
+ body: JSON.stringify(parseNoloWorkspaceToolArguments(call.arguments)),
324
377
  });
325
378
  const text = await response.text().catch(() => "");
326
379
  if (!response.ok) {
@@ -352,17 +405,13 @@ function buildLocalToolExecutors(args: {
352
405
  env: args.env,
353
406
  fetchImpl: args.fetchImpl,
354
407
  }),
408
+ ...buildCliWorkspaceToolExecutors({
409
+ env: args.env,
410
+ }),
355
411
  ...(args.localToolExecutors ?? {}),
356
412
  };
357
413
  }
358
414
 
359
- function readLocalWorkspaceMode(agentConfig: AgentRuntimeAgentConfig | null) {
360
- if (agentConfig?.localWorkspaceMode === "task-worktree") return "task-worktree";
361
- const runtimeBinding = agentConfig?.runtimeBinding;
362
- const value = runtimeBinding?.localWorkspaceMode ?? runtimeBinding?.workspaceMode;
363
- return value === "task-worktree" ? "task-worktree" : "current";
364
- }
365
-
366
415
  async function resolveStore(deps: CliLocalRuntimeAdapterDeps) {
367
416
  if (deps.store) return deps.store;
368
417
  return createCliHybridRecordStore({
@@ -437,51 +486,25 @@ export function createCliLocalRuntimeAdapter(
437
486
  const localToolBudgets = parseLocalToolBudgets(deps.env);
438
487
  const localToolUsage = new Map<string, number>();
439
488
  let activeAgentToolNames: string[] = [];
440
- let workspaceSession: WorkspaceSession = createWorkspaceSession(
441
- deps.useCwdAsTaskWorkspaceBase
442
- ? { defaultCwd: deps.cwd }
443
- : { cwd: deps.cwd }
444
- );
489
+ const workspaceRoot = deps.cwd ?? process.cwd();
445
490
  let runtimeToolExecutionLimits: ReturnType<typeof resolveLocalWorkspaceExecutorOptionsFromPolicy> = {};
446
491
  let localToolExecutors = buildLocalToolExecutors({
447
- workspaceRoot: workspaceSession.workspaceRoot,
492
+ workspaceRoot,
448
493
  env: deps.env,
449
494
  fetchImpl,
450
495
  localToolExecutors: deps.localToolExecutors,
451
496
  ...runtimeToolExecutionLimits,
452
497
  });
453
498
 
454
- async function activateAgentWorkspaceSession(args: {
455
- agentRef: string;
456
- agentConfig: AgentRuntimeAgentConfig | null;
457
- }) {
458
- const previousWorkspaceRoot = workspaceSession.workspaceRoot;
459
- workspaceSession = await activateWorkspaceSession(workspaceSession, {
460
- agentKey: args.agentConfig?.key ?? args.agentRef,
461
- mode: readLocalWorkspaceMode(args.agentConfig),
462
- env: deps.env,
463
- prepareTaskWorkspace: deps.prepareTaskWorktree,
464
- });
465
- if (workspaceSession.workspaceRoot === previousWorkspaceRoot) return;
466
- localToolExecutors = buildLocalToolExecutors({
467
- workspaceRoot: workspaceSession.workspaceRoot,
468
- env: deps.env,
469
- fetchImpl,
470
- localToolExecutors: deps.localToolExecutors,
471
- ...runtimeToolExecutionLimits,
472
- });
473
- deps.output?.write(formatWorkspaceSessionActivation(workspaceSession));
474
- }
475
-
476
499
  return {
477
500
  host: "cli",
478
501
  capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
479
502
  loadAgentConfig: async (agentRef) => {
480
- const agentConfig = await readAgentFromStore({
503
+ const agentConfig = withBuiltinNoloCliTools(await readAgentFromStore({
481
504
  agentRef,
482
505
  store: await resolveStore(deps),
483
506
  userId,
484
- });
507
+ }));
485
508
  const requestedToolNames = agentConfig
486
509
  ? resolveRequestedRuntimeToolNames({ agentConfig })
487
510
  : [];
@@ -492,9 +515,8 @@ export function createCliLocalRuntimeAdapter(
492
515
  runtimeToolExecutionLimits = resolveLocalWorkspaceExecutorOptionsFromPolicy(
493
516
  resolveCurrentRunRuntimeToolPolicy(agentConfig),
494
517
  );
495
- await activateAgentWorkspaceSession({ agentRef, agentConfig });
496
518
  localToolExecutors = buildLocalToolExecutors({
497
- workspaceRoot: workspaceSession.workspaceRoot,
519
+ workspaceRoot,
498
520
  env: deps.env,
499
521
  fetchImpl,
500
522
  localToolExecutors: deps.localToolExecutors,
@@ -512,7 +534,7 @@ export function createCliLocalRuntimeAdapter(
512
534
  userId,
513
535
  now,
514
536
  createId,
515
- cwd: workspaceSession.workspaceRoot,
537
+ cwd: workspaceRoot,
516
538
  }),
517
539
  resolveProvider: async (agentConfig) => {
518
540
  if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
@@ -558,6 +580,9 @@ export function createCliLocalRuntimeAdapter(
558
580
  model: providerConfig.model,
559
581
  messageCount: messages.length,
560
582
  toolCount: tools.length,
583
+ requestedToolNames,
584
+ openAiToolNames: summarizeOpenAiToolNames(tools),
585
+ shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
561
586
  timeoutMs: options?.timeoutMs ?? null,
562
587
  });
563
588
  const res = await fetchWithTransientRetry(fetchImpl, request.url, {
@@ -627,6 +652,9 @@ export function createCliLocalRuntimeAdapter(
627
652
  model: providerConfig.model,
628
653
  messageCount: messages.length,
629
654
  toolCount: tools.length,
655
+ requestedToolNames,
656
+ openAiToolNames: summarizeOpenAiToolNames(tools),
657
+ shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
630
658
  timeoutMs: options?.timeoutMs ?? null,
631
659
  });
632
660
  const res = await fetchWithTransientRetry(fetchImpl, request.url, {
@@ -672,8 +700,8 @@ export function createCliLocalRuntimeAdapter(
672
700
  ...result,
673
701
  metadata: {
674
702
  ...(result.metadata ?? {}),
675
- workspaceRoot: workspaceSession.workspaceRoot,
676
- workspaceKind: workspaceSession.kind,
703
+ workspaceRoot,
704
+ workspaceKind: "current",
677
705
  },
678
706
  };
679
707
  },
@@ -32,7 +32,6 @@ export function resolveConnectorRunCwd(args: {
32
32
  const explicit =
33
33
  args.env?.NOLO_CONNECTOR_WORKDIR ||
34
34
  args.env?.NOLO_AGENT_WORKDIR ||
35
- args.env?.NOLO_LOCAL_WORKTREE ||
36
35
  "";
37
36
  const candidate = explicit || args.policy.writableRoots[0] || args.fallbackCwd || process.cwd();
38
37
  const cwd = normalizePath(candidate);