nolo-cli 0.1.38 → 0.1.40

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 (65) hide show
  1. package/README.md +4 -4
  2. package/agent-runtime/agentRecordConfig.ts +0 -14
  3. package/agent-runtime/dialogWritePlan.ts +39 -0
  4. package/agent-runtime/hostAdapter.ts +4 -1
  5. package/agent-runtime/hybridRecordStore.ts +1 -1
  6. package/agent-runtime/index.ts +39 -18
  7. package/agent-runtime/localDialogRead.ts +23 -0
  8. package/agent-runtime/localLoop.ts +87 -7
  9. package/agent-runtime/localToolPolicy.ts +1 -17
  10. package/agent-runtime/localWorkspaceTools.ts +51 -110
  11. package/agent-runtime/noloWorkspaceTools.ts +371 -0
  12. package/agent-runtime/runtimeToolPolicy.ts +3 -14
  13. package/agent-runtime/runtimeToolSurface.ts +198 -0
  14. package/agent-runtime/types.ts +2 -1
  15. package/agentAliases.ts +8 -1
  16. package/agentRunCommand.ts +65 -95
  17. package/agentRuntimeLocal.ts +1 -7
  18. package/ai/agent/cliExecutor.ts +249 -34
  19. package/chat/messages/fetchMessages.ts +84 -0
  20. package/chat/messages/types.ts +185 -0
  21. package/cli/agentAliases.ts +8 -1
  22. package/cli/agentRunCommand.ts +65 -95
  23. package/cli/agentRuntimeLocal.ts +1 -7
  24. package/cli/cliEnvHelpers.ts +11 -7
  25. package/cli/client/agentRun.ts +94 -93
  26. package/cli/client/localRuntimeAdapter.ts +250 -69
  27. package/cli/commandRegistry.ts +2 -2
  28. package/cli/connectorRunArtifact.ts +0 -1
  29. package/cli/dialogCommands.ts +572 -0
  30. package/cli/dialogInternalCommandEntries.ts +4 -0
  31. package/cli/machineWsRunDispatch.ts +76 -39
  32. package/cli/offlineMarxistsAgentCommand.ts +6 -4
  33. package/cli/scriptCommandEntries.ts +0 -2
  34. package/cli/tui/readlineWorkspace.ts +69 -0
  35. package/cli/tui/session.ts +176 -1
  36. package/cliEnvHelpers.ts +11 -7
  37. package/client/agentConfigResolver.test.ts +4 -5
  38. package/client/agentRun.test.ts +307 -57
  39. package/client/agentRun.ts +94 -93
  40. package/client/localRuntimeAdapter.test.ts +332 -53
  41. package/client/localRuntimeAdapter.ts +250 -69
  42. package/client/localRuntimeDryRun.test.ts +23 -9
  43. package/client/localToolPolicy.test.ts +4 -4
  44. package/commandRegistry.ts +2 -2
  45. package/connectorRunArtifact.ts +0 -1
  46. package/database/server/db.ts +99 -0
  47. package/database/server/ensureDbOpen.ts +12 -0
  48. package/database/server/levelAuthorityStore.ts +20 -1
  49. package/database/server/memoryAuthorityStore.ts +133 -0
  50. package/database/server/serverStoreFactory.ts +118 -0
  51. package/dialogCommands.ts +572 -0
  52. package/dialogInternalCommandEntries.ts +4 -0
  53. package/machineWsRunDispatch.ts +76 -39
  54. package/offlineMarxistsAgentCommand.ts +6 -4
  55. package/package.json +11 -9
  56. package/scriptCommandEntries.ts +0 -2
  57. package/tui/readlineWorkspace.ts +69 -0
  58. package/tui/session.ts +176 -1
  59. package/agent-runtime/taskWorkspace.ts +0 -193
  60. package/agent-runtime/workspaceSession.ts +0 -76
  61. package/cli/client/taskWorktree.ts +0 -8
  62. package/cli/client/workspaceSession.ts +0 -11
  63. package/client/taskWorktree.ts +0 -8
  64. package/client/workspaceSession.test.ts +0 -57
  65. package/client/workspaceSession.ts +0 -11
package/README.md CHANGED
@@ -178,10 +178,10 @@ nolo dialog list --space <space>
178
178
  nolo dialog read <dialog>
179
179
  nolo dialog delete <dialog> --yes
180
180
  nolo doc list --agent <agent>
181
- nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --limit 20
182
- nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --columns '["title","status","owner","priority","codeStatus"]' --no-base-fields --output items
183
- nolo table update-row --table meta-b2e06f801f-NOLOTASKBOARD --row 01ROWID --changes '{"status":"已完成"}'
184
- nolo table add-column --table meta-b2e06f801f-NOLOTASKBOARD --schema-write-ok --name "blockedBy" --label "Blocked By"
181
+ nolo table query --table meta-0e95801d90-NOLOTASKBOARD --limit 20
182
+ nolo table query --table meta-0e95801d90-NOLOTASKBOARD --columns '["title","status","owner","priority","codeStatus"]' --no-base-fields --output items
183
+ nolo table update-row --table meta-0e95801d90-NOLOTASKBOARD --row 01ROWID --changes '{"status":"已完成"}'
184
+ nolo table add-column --table meta-0e95801d90-NOLOTASKBOARD --schema-write-ok --name "blockedBy" --label "Blocked By"
185
185
  ```
186
186
 
187
187
  `agent list`, `dialog list`, and record delete commands use global server
@@ -19,18 +19,6 @@ function objectField(record: AgentRecord, key: string): Record<string, unknown>
19
19
  : undefined;
20
20
  }
21
21
 
22
- function localWorkspaceModeField(record: AgentRecord): "current" | "task-worktree" | undefined {
23
- const directValue = stringField(record, "localWorkspaceMode");
24
- const runtimeBinding = objectField(record, "runtimeBinding");
25
- const runtimeValue = typeof runtimeBinding?.localWorkspaceMode === "string"
26
- ? runtimeBinding.localWorkspaceMode
27
- : typeof runtimeBinding?.workspaceMode === "string"
28
- ? runtimeBinding.workspaceMode
29
- : undefined;
30
- const value = directValue ?? runtimeValue;
31
- return value === "task-worktree" || value === "current" ? value : undefined;
32
- }
33
-
34
22
  function appendUniqueStrings(values: string[], next: unknown) {
35
23
  if (!Array.isArray(next)) return values;
36
24
  const seen = new Set(values);
@@ -84,7 +72,6 @@ export function resolveAgentRuntimeConfigFromRecord(
84
72
  const runtimeToolPolicy =
85
73
  objectField(record, "runtimeToolPolicy") ??
86
74
  objectField(runtimeBinding ?? {}, "runtimeToolPolicy");
87
- const localWorkspaceMode = localWorkspaceModeField(record);
88
75
  const delegation = objectField(record, "delegation");
89
76
  return {
90
77
  key,
@@ -108,7 +95,6 @@ export function resolveAgentRuntimeConfigFromRecord(
108
95
  ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
109
96
  ...(runtimeBinding ? { runtimeBinding } : {}),
110
97
  ...(runtimeToolPolicy ? { runtimeToolPolicy } : {}),
111
- ...(localWorkspaceMode ? { localWorkspaceMode } : {}),
112
98
  ...(delegation ? { delegation } : {}),
113
99
  rawRecord: record,
114
100
  };
@@ -34,6 +34,28 @@ function resolveDialogTitle(args: {
34
34
  return lastUserText ? lastUserText.slice(0, 80) : "Local agent run";
35
35
  }
36
36
 
37
+ function normalizeNonEmptyString(value: unknown) {
38
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
39
+ }
40
+
41
+ function buildDialogLineageFields(args: {
42
+ input: AgentRuntimeSaveTurnInput;
43
+ existingDialog?: DialogRecord | null;
44
+ }) {
45
+ const inheritedFromDialogKey = normalizeNonEmptyString(args.input.inheritedFromDialogKey);
46
+ const parentDialogId = normalizeNonEmptyString(args.input.parentDialogId);
47
+ if (!inheritedFromDialogKey && !parentDialogId) return {};
48
+ const rootDialogId =
49
+ normalizeNonEmptyString(args.existingDialog?.rootDialogId) ??
50
+ normalizeNonEmptyString(args.existingDialog?.parentDialogId) ??
51
+ parentDialogId;
52
+ return {
53
+ ...(inheritedFromDialogKey ? { inheritedFromDialogKey } : {}),
54
+ ...(parentDialogId ? { parentDialogId } : {}),
55
+ ...(rootDialogId ? { rootDialogId } : {}),
56
+ };
57
+ }
58
+
37
59
  function buildDialogMessageWriteOps(args: {
38
60
  dialogId: string;
39
61
  input: AgentRuntimeSaveTurnInput;
@@ -102,6 +124,12 @@ export function buildAgentRuntimeDialogWritePlan(args: {
102
124
  updatedAt: nowIso,
103
125
  finishedAt: args.now,
104
126
  usage: args.input.result.usage,
127
+ ...(normalizeNonEmptyString(args.input.spaceId) ? { spaceId: normalizeNonEmptyString(args.input.spaceId) } : {}),
128
+ ...(normalizeNonEmptyString(args.input.category) ? { category: normalizeNonEmptyString(args.input.category) } : {}),
129
+ ...buildDialogLineageFields({
130
+ input: args.input,
131
+ existingDialog: args.existingDialog,
132
+ }),
105
133
  ...(typeof args.input.result.toolCallCount === "number"
106
134
  ? { toolCallCount: args.input.result.toolCallCount }
107
135
  : {}),
@@ -109,6 +137,17 @@ export function buildAgentRuntimeDialogWritePlan(args: {
109
137
  host: args.runtimeHost,
110
138
  ...(args.runtimeMetadata ?? {}),
111
139
  },
140
+ ...(args.input.result.runtimeToolSurface
141
+ ? {
142
+ runtimeCheckpoint: {
143
+ ...(args.existingDialog?.runtimeCheckpoint &&
144
+ typeof args.existingDialog.runtimeCheckpoint === "object"
145
+ ? args.existingDialog.runtimeCheckpoint
146
+ : {}),
147
+ toolSurface: args.input.result.runtimeToolSurface,
148
+ },
149
+ }
150
+ : {}),
112
151
  };
113
152
  return {
114
153
  dialogId,
@@ -27,7 +27,6 @@ export type AgentRuntimeAgentConfig = {
27
27
  reasoning_effort?: string;
28
28
  runtimeBinding?: Record<string, unknown>;
29
29
  runtimeToolPolicy?: AgentRuntimeToolPolicy;
30
- localWorkspaceMode?: "current" | "task-worktree";
31
30
  delegation?: Record<string, unknown>;
32
31
  rawRecord?: Record<string, unknown>;
33
32
  };
@@ -57,6 +56,10 @@ export type AgentRuntimeSaveTurnInput = {
57
56
  messages: AgentRuntimeChatMessage[];
58
57
  result: AgentRuntimeResult;
59
58
  continueDialogId?: string;
59
+ spaceId?: string;
60
+ category?: string;
61
+ inheritedFromDialogKey?: string;
62
+ parentDialogId?: string;
60
63
  };
61
64
 
62
65
  export type AgentRuntimeHostAdapter = {
@@ -126,7 +126,7 @@ export function createHybridRecordStore(
126
126
  return {
127
127
  read: async (dbKey, options) => {
128
128
  const localRecord = await readHybridLocalRecord(deps.db, dbKey);
129
- if (localRecord) return { ...localRecord, dbKey };
129
+ if (localRecord) return normalizeHybridRecord(dbKey, localRecord);
130
130
  if (options?.remote === false) return null;
131
131
 
132
132
  for (const server of resolveHybridServers(
@@ -39,15 +39,6 @@ export {
39
39
  createHybridRecordStore,
40
40
  shouldCacheHybridRemoteRecord,
41
41
  } from "./hybridRecordStore";
42
- export {
43
- activateWorkspaceSession,
44
- createWorkspaceSession,
45
- formatWorkspaceSessionActivation,
46
- } from "./workspaceSession";
47
- export {
48
- formatPreparedGitTaskWorkspace,
49
- prepareGitTaskWorkspace,
50
- } from "./taskWorkspace";
51
42
  export {
52
43
  executeLocalToolWithPolicy,
53
44
  resolveLocalToolPolicy,
@@ -65,12 +56,44 @@ export {
65
56
  resolveLocalRuntimeEnvFromPolicy,
66
57
  resolveLocalWorkspaceExecutorOptionsFromPolicy,
67
58
  } from "./runtimeToolPolicy";
59
+ export {
60
+ DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOLS,
61
+ inferOwnerIdFromRuntimeAgentKey,
62
+ isPublicRuntimeAgentRef,
63
+ redactAgentRecordForWorkspaceTool,
64
+ resolveRuntimeToolSurfaceForAgent,
65
+ resolveRuntimeToolSurface,
66
+ } from "./runtimeToolSurface";
68
67
  export {
69
68
  buildLocalWorkspaceOpenAiTools,
70
69
  buildLocalWorkspacePolicyToolNames,
71
70
  buildLocalWorkspaceToolset,
72
71
  createLocalWorkspaceToolExecutors,
73
72
  } from "./localWorkspaceTools";
73
+ export {
74
+ buildNoloWorkspaceCommandArgs,
75
+ buildNoloWorkspaceCliToolExecutors,
76
+ buildNoloWorkspaceOpenAiTools,
77
+ buildNoloTableQueryRequest,
78
+ clampNoloPositiveInteger,
79
+ filterNoloWorkspaceToolNames,
80
+ getNoloComparableUpdatedAt,
81
+ getNoloDialogIdFromKey,
82
+ getNoloSpaceContentKeys,
83
+ isNoloWorkspaceToolName,
84
+ noloPositiveIntegerString,
85
+ noloStringArg,
86
+ normalizeNoloDocReadArgs,
87
+ normalizeNoloSpaceInput,
88
+ NOLO_WORKSPACE_TOOL_NAMES,
89
+ NOLO_WORKSPACE_TOOL_PROMPT,
90
+ parseNoloWorkspaceToolArguments,
91
+ resolveNoloDialogInput,
92
+ runNoloWorkspaceCliTool,
93
+ } from "./noloWorkspaceTools";
94
+ export type {
95
+ NoloWorkspaceToolName,
96
+ } from "./noloWorkspaceTools";
74
97
  export type {
75
98
  AgentRuntimeAgentConfig,
76
99
  AgentRuntimeHostAdapter,
@@ -87,15 +110,6 @@ export type {
87
110
  HybridRecordKvDb,
88
111
  HybridRecordStore,
89
112
  } from "./hybridRecordStore";
90
- export type {
91
- PreparedTaskWorkspace,
92
- PrepareTaskWorkspace,
93
- WorkspaceSession,
94
- WorkspaceSessionMode,
95
- } from "./workspaceSession";
96
- export type {
97
- PreparedGitTaskWorkspace,
98
- } from "./taskWorkspace";
99
113
  export type {
100
114
  LocalToolPolicyDecision,
101
115
  } from "./localToolPolicy";
@@ -105,6 +119,13 @@ export type {
105
119
  export type {
106
120
  PlatformChatProviderConfig,
107
121
  } from "./platformChatProvider";
122
+ export type {
123
+ RuntimeToolSurfaceHost,
124
+ RuntimeToolSurfaceForAgentInput,
125
+ RuntimeToolSurfaceInput,
126
+ RuntimeToolSurfaceResult,
127
+ RuntimeToolSurfaceVisibility,
128
+ } from "./runtimeToolSurface";
108
129
  export type {
109
130
  AgentRuntimeChatMessage,
110
131
  AgentRuntimeDecision,
@@ -0,0 +1,23 @@
1
+ export type LocalDialogReadResult = {
2
+ meta: any;
3
+ msgs: any[];
4
+ };
5
+
6
+ export async function readDialogFromLocalDb(args: {
7
+ dialogKey: string;
8
+ dialogId: string;
9
+ limit: number;
10
+ }): Promise<LocalDialogReadResult> {
11
+ const [{ default: serverDb, ensureServerDbOpen }, { fetchMessages }] = await Promise.all([
12
+ import("../database/server/db"),
13
+ import("../chat/messages/fetchMessages"),
14
+ ]);
15
+ await ensureServerDbOpen();
16
+ return {
17
+ meta: await serverDb.get(args.dialogKey),
18
+ msgs: await fetchMessages(serverDb, args.dialogId, {
19
+ limit: args.limit,
20
+ throwOnError: true,
21
+ }),
22
+ };
23
+ }
@@ -12,7 +12,10 @@ export type LocalAgentTurnInput = {
12
12
  agentRef: string;
13
13
  input: AgentRuntimeMessageContent;
14
14
  continueDialogId?: string;
15
- maxToolRounds?: number;
15
+ spaceId?: string;
16
+ category?: string;
17
+ inheritedFromDialogKey?: string;
18
+ parentDialogId?: string;
16
19
  timeoutMs?: number;
17
20
  onToolEvent?: (event: LocalAgentToolEvent) => void;
18
21
  };
@@ -26,11 +29,13 @@ export type LocalAgentToolEvent = {
26
29
  round: number;
27
30
  toolCallId: string;
28
31
  toolName: string;
32
+ argumentsPreview?: string;
33
+ elapsedMs?: number;
34
+ summary?: string;
29
35
  message?: string;
30
36
  metadata?: Record<string, unknown>;
31
37
  };
32
38
 
33
- export const DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS = 128;
34
39
  export const LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
35
40
 
36
41
  function formatToolExecutionError(args: {
@@ -75,6 +80,72 @@ function emitToolEvent(
75
80
  input.onToolEvent?.(event);
76
81
  }
77
82
 
83
+ function compactWhitespace(value: string) {
84
+ return value.replace(/\s+/g, " ").trim();
85
+ }
86
+
87
+ function clip(value: string, max = 240) {
88
+ const compact = compactWhitespace(value);
89
+ return compact.length > max ? `${compact.slice(0, max - 3)}...` : compact;
90
+ }
91
+
92
+ function parseToolArguments(raw: string | undefined) {
93
+ if (!raw?.trim()) return {};
94
+ try {
95
+ const parsed = JSON.parse(raw);
96
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
97
+ ? parsed as Record<string, unknown>
98
+ : {};
99
+ } catch {
100
+ return {};
101
+ }
102
+ }
103
+
104
+ function summarizeToolArguments(toolName: string, rawArgs: string | undefined) {
105
+ const args = parseToolArguments(rawArgs);
106
+ const pick = (...keys: string[]) => {
107
+ for (const key of keys) {
108
+ const value = args[key];
109
+ if (typeof value === "string" && value.trim()) return value.trim();
110
+ }
111
+ return "";
112
+ };
113
+ const command = pick("command", "cmd", "runCommand", "executeCommand", "bash");
114
+ if (command) return clip(command);
115
+ const filePath = pick("filePath", "path", "filename", "file");
116
+ if (filePath) return clip(filePath);
117
+ const query = pick("query", "pattern", "search", "q");
118
+ if (query) return clip(query);
119
+ const keys = Object.keys(args);
120
+ if (keys.length === 0) return "";
121
+ return clip(keys.slice(0, 6).map((key) => {
122
+ const value = args[key];
123
+ if (typeof value === "string") return `${key}=${clip(value, 80)}`;
124
+ if (typeof value === "number" || typeof value === "boolean") return `${key}=${String(value)}`;
125
+ if (Array.isArray(value)) return `${key}[${value.length}]`;
126
+ return `${key}=${value === null ? "null" : typeof value}`;
127
+ }).join(" "));
128
+ }
129
+
130
+ function summarizeToolResult(content: unknown, metadata?: Record<string, unknown>) {
131
+ const parts: string[] = [];
132
+ const exitCode = metadata?.exitCode;
133
+ if (typeof exitCode === "number") parts.push(`exit=${exitCode}`);
134
+ if (typeof content === "string") {
135
+ const trimmed = content.trim();
136
+ if (trimmed) {
137
+ const lines = trimmed.split(/\r?\n/).length;
138
+ parts.push(`${lines} line${lines === 1 ? "" : "s"}`);
139
+ parts.push(`${trimmed.length} chars`);
140
+ const tail = clip(trimmed.slice(-160), 160);
141
+ if (tail) parts.push(`tail="${tail}"`);
142
+ } else {
143
+ parts.push("empty");
144
+ }
145
+ }
146
+ return parts.join(" ");
147
+ }
148
+
78
149
  function buildMessages(args: {
79
150
  prompt?: string;
80
151
  history: AgentRuntimeChatMessage[];
@@ -124,19 +195,16 @@ export async function runLocalAgentTurn(
124
195
  input: input.input,
125
196
  });
126
197
  const provider = await input.adapter.resolveProvider(agentConfig);
127
- const maxToolRounds = input.maxToolRounds ?? DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS;
128
198
  const userInputText = extractUserInputText(input.input);
129
199
  let toolCallCount = 0;
130
200
  let result: AgentRuntimeResult;
131
- for (let round = 0; round <= maxToolRounds; round += 1) {
201
+ let round = 0;
202
+ while (true) {
132
203
  result = await provider.complete(messages, {
133
204
  ...(typeof input.timeoutMs === "number" ? { timeoutMs: input.timeoutMs } : {}),
134
205
  });
135
206
  const toolCalls = result.tool_calls ?? [];
136
207
  if (toolCalls.length === 0) break;
137
- if (round === maxToolRounds) {
138
- throw new Error(`Local agent exceeded max tool rounds: ${maxToolRounds}`);
139
- }
140
208
  toolCallCount += toolCalls.length;
141
209
  messages.push({
142
210
  role: "assistant",
@@ -147,11 +215,13 @@ export async function runLocalAgentTurn(
147
215
  for (const toolCall of toolCalls) {
148
216
  const toolName = toolCall.function.name;
149
217
  let toolResult;
218
+ const startedAt = Date.now();
150
219
  emitToolEvent(input, {
151
220
  type: "tool-call",
152
221
  round,
153
222
  toolCallId: toolCall.id,
154
223
  toolName,
224
+ argumentsPreview: summarizeToolArguments(toolName, toolCall.function.arguments),
155
225
  });
156
226
  try {
157
227
  toolResult = await input.adapter.executeTool({
@@ -165,6 +235,8 @@ export async function runLocalAgentTurn(
165
235
  round,
166
236
  toolCallId: toolCall.id,
167
237
  toolName,
238
+ elapsedMs: Math.max(0, Date.now() - startedAt),
239
+ summary: summarizeToolResult(toolResult.content, toolResult.metadata),
168
240
  metadata: toolResult.metadata,
169
241
  });
170
242
  } catch (error) {
@@ -174,6 +246,7 @@ export async function runLocalAgentTurn(
174
246
  round,
175
247
  toolCallId: toolCall.id,
176
248
  toolName,
249
+ elapsedMs: Math.max(0, Date.now() - startedAt),
177
250
  message: error instanceof Error ? error.message : String(error),
178
251
  });
179
252
  toolResult = {
@@ -201,6 +274,7 @@ export async function runLocalAgentTurn(
201
274
  ...(toolResult.metadata ? { tool_result_metadata: toolResult.metadata } : {}),
202
275
  });
203
276
  }
277
+ round += 1;
204
278
  }
205
279
  result = result!;
206
280
  messages.push({
@@ -213,13 +287,19 @@ export async function runLocalAgentTurn(
213
287
  result: {
214
288
  ...result,
215
289
  ...(toolCallCount > 0 ? { toolCallCount } : {}),
290
+ ...((agentConfig as any).toolSurface ? { runtimeToolSurface: (agentConfig as any).toolSurface } : {}),
216
291
  },
217
292
  ...(input.continueDialogId ? { continueDialogId: input.continueDialogId } : {}),
293
+ ...(input.spaceId ? { spaceId: input.spaceId } : {}),
294
+ ...(input.category ? { category: input.category } : {}),
295
+ ...(input.inheritedFromDialogKey ? { inheritedFromDialogKey: input.inheritedFromDialogKey } : {}),
296
+ ...(input.parentDialogId ? { parentDialogId: input.parentDialogId } : {}),
218
297
  });
219
298
 
220
299
  return {
221
300
  ...result,
222
301
  ...(toolCallCount > 0 ? { toolCallCount } : {}),
302
+ ...((agentConfig as any).toolSurface ? { runtimeToolSurface: (agentConfig as any).toolSurface } : {}),
223
303
  dialogId: saved.dialogId,
224
304
  };
225
305
  }
@@ -28,16 +28,11 @@ const REMOVED_LOCAL_TOOLS = new Set([
28
28
  ]);
29
29
 
30
30
  const DEFAULT_LOCAL_TOOLS = new Set([
31
- "listWorkspaceFiles",
32
- "readWorkspaceFile",
33
- "writeWorkspaceFile",
34
- "replaceWorkspaceText",
35
31
  "listFiles",
36
32
  "readFile",
37
33
  "writeFile",
38
34
  "editFile",
39
35
  "searchFiles",
40
- "searchWorkspace",
41
36
  "execShell",
42
37
  ]);
43
38
 
@@ -66,10 +61,6 @@ function parseShellCommandPayload(rawArguments: string) {
66
61
  }
67
62
  }
68
63
 
69
- function isLocalShellModeEnabled(env: EnvLike) {
70
- return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
71
- }
72
-
73
64
  function isRestrictedLocalToolMode(env: EnvLike) {
74
65
  return env.NOLO_LOCAL_TOOL_MODE === "restricted";
75
66
  }
@@ -101,14 +92,7 @@ export function resolveLocalToolPolicy(args: {
101
92
  }
102
93
 
103
94
  if (toolName === "execShell") {
104
- if (isLocalShellModeEnabled(args.env)) {
105
- return { allowed: true, toolName };
106
- }
107
- return {
108
- allowed: false,
109
- toolName,
110
- reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree.",
111
- };
95
+ return { allowed: true, toolName };
112
96
  }
113
97
 
114
98
  if (NEVER_LOCAL_TOOLS.has(toolName)) {