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
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
5
5
 
6
6
  import { runLocalAgentTurn } from "../agent-runtime/localLoop";
7
+ import { MIMO_MONTH_AGENT_KEY } from "../agentAliases";
7
8
  import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
8
9
 
9
10
  describe("CLI local runtime adapter", () => {
@@ -13,23 +14,183 @@ describe("CLI local runtime adapter", () => {
13
14
  "writeFile",
14
15
  "editFile",
15
16
  "searchFiles",
17
+ "execShell",
16
18
  ];
17
19
  const LEGACY_WRITE_LOCAL_CODING_TOOL_NAMES = [
18
- "writeWorkspaceFile",
19
20
  "listFiles",
20
21
  "readFile",
22
+ "writeFile",
21
23
  "editFile",
22
24
  "searchFiles",
25
+ "execShell",
23
26
  ];
24
27
  const SHELL_LOCAL_CODING_TOOL_NAMES = [
25
28
  ...DEFAULT_LOCAL_CODING_TOOL_NAMES,
26
- "execShell",
29
+ ];
30
+ const DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOL_NAMES = [
31
+ "listDialogs",
32
+ "readDialog",
33
+ "listAgents",
34
+ "readAgent",
35
+ "listSpaces",
36
+ "readSpace",
37
+ "readDoc",
38
+ "readSkillDoc",
39
+ "queryTableRows",
40
+ "cliWhoami",
41
+ "cliDoctor",
42
+ ];
43
+ const DEFAULT_PRIVATE_LOCAL_TOOL_NAMES = [
44
+ ...DEFAULT_LOCAL_CODING_TOOL_NAMES,
45
+ ...DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOL_NAMES,
27
46
  ];
28
47
 
29
48
  function toolNamesFromRequest(request: any) {
30
49
  return request?.body?.tools?.map((tool: any) => tool.function.name) ?? [];
31
50
  }
32
51
 
52
+ test("runs cli-provider agents through the local CLI executor instead of OpenAI-compatible direct mode", async () => {
53
+ const cliExecutions: Array<{ provider: string; prompt: string; options: any }> = [];
54
+ const adapter = createCliLocalRuntimeAdapter({
55
+ env: {
56
+ NOLO_LOCAL_USER_ID: "user-1",
57
+ OPENAI_API_KEY: "sk-should-not-be-used",
58
+ },
59
+ db: {
60
+ get: async (key) => {
61
+ if (key !== "agent-user-1-frontend") throw new Error(`not found: ${key}`);
62
+ return {
63
+ dbKey: "agent-user-1-frontend",
64
+ id: "frontend",
65
+ name: "Frontend",
66
+ prompt: "You are the frontend implementer.",
67
+ apiSource: "cli",
68
+ cliProvider: "agy",
69
+ model: "gemini-3.1-pro",
70
+ };
71
+ },
72
+ put: async () => {},
73
+ batch: async () => {},
74
+ iterator: () => (async function* () {})(),
75
+ },
76
+ cwd: "/repo/worktree",
77
+ now: () => 1710000000000,
78
+ createId: () => "01CLI",
79
+ fetchImpl: async () => {
80
+ throw new Error("OpenAI-compatible fetch should not be used for cli providers");
81
+ },
82
+ executeCli: async (provider, prompt, options) => {
83
+ cliExecutions.push({ provider, prompt, options });
84
+ return { text: "cli ok", raw: "cli ok", elapsed: 1 };
85
+ },
86
+ } as any);
87
+
88
+ const result = await runLocalAgentTurn({
89
+ adapter,
90
+ agentRef: "frontend",
91
+ input: "add tooltip",
92
+ });
93
+
94
+ expect(result).toMatchObject({
95
+ content: "cli ok",
96
+ model: "gemini-3.1-pro",
97
+ });
98
+ expect(cliExecutions).toHaveLength(1);
99
+ expect(cliExecutions[0]).toMatchObject({
100
+ provider: "agy",
101
+ options: {
102
+ model: "gemini-3.1-pro",
103
+ cwd: "/repo/worktree",
104
+ yolo: true,
105
+ },
106
+ });
107
+ expect(cliExecutions[0].prompt).toContain("You are the frontend implementer.");
108
+ expect(cliExecutions[0].prompt).toContain("add tooltip");
109
+ });
110
+
111
+ test("fails cli-provider local runs clearly when the requested local CLI is unavailable", async () => {
112
+ const adapter = createCliLocalRuntimeAdapter({
113
+ env: {
114
+ NOLO_LOCAL_USER_ID: "user-1",
115
+ OPENAI_API_KEY: "sk-should-not-be-used",
116
+ },
117
+ db: {
118
+ get: async (key) => {
119
+ if (key !== "agent-user-1-frontend") throw new Error(`not found: ${key}`);
120
+ return {
121
+ dbKey: "agent-user-1-frontend",
122
+ id: "frontend",
123
+ name: "Frontend",
124
+ prompt: "You are the frontend implementer.",
125
+ apiSource: "cli",
126
+ cliProvider: "agy",
127
+ };
128
+ },
129
+ put: async () => {},
130
+ batch: async () => {},
131
+ iterator: () => (async function* () {})(),
132
+ },
133
+ fetchImpl: async () => {
134
+ throw new Error("OpenAI-compatible fetch should not be used for cli providers");
135
+ },
136
+ executeCli: async () => {
137
+ throw new Error("agy: command not found");
138
+ },
139
+ } as any);
140
+
141
+ await expect(runLocalAgentTurn({
142
+ adapter,
143
+ agentRef: "frontend",
144
+ input: "add tooltip",
145
+ })).rejects.toThrow("Local CLI provider \"agy\" is unavailable");
146
+ });
147
+
148
+ test("passes cli-provider image inputs to the CLI executor instead of rejecting", async () => {
149
+ let cliCalledWith: any = null;
150
+ const adapter = createCliLocalRuntimeAdapter({
151
+ env: {
152
+ NOLO_LOCAL_USER_ID: "user-1",
153
+ },
154
+ db: {
155
+ get: async (key) => {
156
+ if (key !== "agent-user-1-frontend") throw new Error(`not found: ${key}`);
157
+ return {
158
+ dbKey: "agent-user-1-frontend",
159
+ id: "frontend",
160
+ name: "Frontend",
161
+ prompt: "You are the frontend implementer.",
162
+ apiSource: "cli",
163
+ cliProvider: "agy",
164
+ };
165
+ },
166
+ put: async () => {},
167
+ batch: async () => {},
168
+ iterator: () => (async function* () {})(),
169
+ },
170
+ executeCli: async (provider, prompt, options) => {
171
+ cliCalledWith = { provider, prompt, options };
172
+ return { text: "image handled", raw: "", elapsed: 1 };
173
+ },
174
+ } as any);
175
+
176
+ const result = await runLocalAgentTurn({
177
+ adapter,
178
+ agentRef: "frontend",
179
+ input: [
180
+ { type: "text", text: "look at this screenshot" },
181
+ { type: "image_url", image_url: { url: "https://example.com/screen.png" } },
182
+ ],
183
+ });
184
+
185
+ expect(result.content).toBe("image handled");
186
+ expect(cliCalledWith).not.toBeNull();
187
+ expect(cliCalledWith.provider).toBe("agy");
188
+ expect(cliCalledWith.prompt).toContain("look at this screenshot");
189
+ expect(cliCalledWith.options.imageInputs).toEqual([
190
+ { source: "https://example.com/screen.png" },
191
+ ]);
192
+ });
193
+
33
194
  test("loads agent/history from LevelDB and saves dialog/message records back to LevelDB", async () => {
34
195
  const requests: Array<{ url: string; body: any; auth: string | null }> = [];
35
196
  const store = new Map<string, any>([
@@ -122,7 +283,7 @@ describe("CLI local runtime adapter", () => {
122
283
  stream: false,
123
284
  },
124
285
  });
125
- expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_LOCAL_CODING_TOOL_NAMES);
286
+ expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_PRIVATE_LOCAL_TOOL_NAMES);
126
287
  expect(batchOps.map((op) => op.key)).toEqual([
127
288
  "dialog-user-1-dialog-existing",
128
289
  "dialog-dialog-existing-msg-1710000000000-001",
@@ -354,7 +515,7 @@ describe("CLI local runtime adapter", () => {
354
515
  reasoning_effort: "medium",
355
516
  },
356
517
  });
357
- expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_LOCAL_CODING_TOOL_NAMES);
518
+ expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_PRIVATE_LOCAL_TOOL_NAMES);
358
519
  });
359
520
 
360
521
  test("aborts stalled custom provider requests when timeoutMs is provided", async () => {
@@ -473,7 +634,7 @@ describe("CLI local runtime adapter", () => {
473
634
  stream: false,
474
635
  },
475
636
  });
476
- expect(toolNamesFromRequest(loopbackRequests[0])).toEqual(DEFAULT_LOCAL_CODING_TOOL_NAMES);
637
+ expect(toolNamesFromRequest(loopbackRequests[0])).toEqual(DEFAULT_PRIVATE_LOCAL_TOOL_NAMES);
477
638
  });
478
639
 
479
640
  test("uses the Nolo chat proxy when local provider keys are absent", async () => {
@@ -485,7 +646,7 @@ describe("CLI local runtime adapter", () => {
485
646
  prompt: "Fix UI.",
486
647
  model: "accounts/fireworks/models/kimi-k2p6",
487
648
  provider: "fireworks",
488
- tools: ["writeWorkspaceFile"],
649
+ tools: ["writeFile"],
489
650
  }],
490
651
  ]);
491
652
  const adapter = createCliLocalRuntimeAdapter({
@@ -546,7 +707,10 @@ describe("CLI local runtime adapter", () => {
546
707
  agentKey: "agent-user-1-frontend",
547
708
  },
548
709
  });
549
- expect(toolNamesFromRequest(requests[0])).toEqual(LEGACY_WRITE_LOCAL_CODING_TOOL_NAMES);
710
+ expect(toolNamesFromRequest(requests[0])).toEqual([
711
+ ...LEGACY_WRITE_LOCAL_CODING_TOOL_NAMES,
712
+ ...DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOL_NAMES,
713
+ ]);
550
714
  });
551
715
 
552
716
  test("uses the Nolo chat proxy for platform agents even when direct provider env exists", async () => {
@@ -560,7 +724,7 @@ describe("CLI local runtime adapter", () => {
560
724
  provider: "fireworks",
561
725
  apiSource: "platform",
562
726
  useServerProxy: true,
563
- tools: ["readWorkspaceFile"],
727
+ tools: ["readFile"],
564
728
  }],
565
729
  ]);
566
730
  const adapter = createCliLocalRuntimeAdapter({
@@ -745,7 +909,6 @@ describe("CLI local runtime adapter", () => {
745
909
  env: {
746
910
  NOLO_LOCAL_USER_ID: "user-1",
747
911
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
748
- NOLO_LOCAL_SHELL_MODE: "worktree",
749
912
  },
750
913
  db: {
751
914
  get: async (key) => {
@@ -851,7 +1014,7 @@ describe("CLI local runtime adapter", () => {
851
1014
  prompt: "Read narrowly",
852
1015
  model: "gpt-4.1-mini",
853
1016
  provider: "openai-compatible",
854
- tools: ["readWorkspaceFile"],
1017
+ tools: ["readFile"],
855
1018
  }],
856
1019
  ]);
857
1020
  let requestCount = 0;
@@ -860,7 +1023,7 @@ describe("CLI local runtime adapter", () => {
860
1023
  NOLO_LOCAL_USER_ID: "user-1",
861
1024
  NOLO_LOCAL_OPENAI_BASE_URL: "https://llm.example/v1",
862
1025
  NOLO_LOCAL_OPENAI_API_KEY: "sk-test",
863
- NOLO_LOCAL_TOOL_BUDGETS: "readWorkspaceFile=1",
1026
+ NOLO_LOCAL_TOOL_BUDGETS: "readFile=1",
864
1027
  },
865
1028
  store: {
866
1029
  read: async (key) => {
@@ -881,7 +1044,7 @@ describe("CLI local runtime adapter", () => {
881
1044
  now: () => 1710000000000,
882
1045
  createId: () => "01BUDGET",
883
1046
  localToolExecutors: {
884
- readWorkspaceFile: async () => ({ content: "file content" }),
1047
+ readFile: async () => ({ content: "file content" }),
885
1048
  },
886
1049
  fetchImpl: async (_url, init) => {
887
1050
  requestCount += 1;
@@ -898,7 +1061,7 @@ describe("CLI local runtime adapter", () => {
898
1061
  id: `call-read-${requestCount}`,
899
1062
  type: "function",
900
1063
  function: {
901
- name: "readWorkspaceFile",
1064
+ name: "readFile",
902
1065
  arguments: JSON.stringify({ path: "file.ts" }),
903
1066
  },
904
1067
  }],
@@ -912,7 +1075,6 @@ describe("CLI local runtime adapter", () => {
912
1075
  adapter,
913
1076
  agentRef: "agent-user-1-reader",
914
1077
  input: "inspect",
915
- maxToolRounds: 3,
916
1078
  });
917
1079
 
918
1080
  expect(result.content).toBe("budget handled");
@@ -1043,17 +1205,22 @@ describe("CLI local runtime adapter", () => {
1043
1205
  });
1044
1206
  });
1045
1207
 
1046
- test("rejects tools unless the local tool policy allows and registers them", async () => {
1208
+ test("allows registered execShell by default", async () => {
1047
1209
  const adapter = createCliLocalRuntimeAdapter({
1048
1210
  env: {},
1211
+ localToolExecutors: {
1212
+ execShell: async (call) => ({ content: `shell:${call.arguments}` }),
1213
+ },
1049
1214
  fetchImpl: async () => Response.json({}),
1050
1215
  });
1051
1216
 
1052
- await expect(adapter.executeTool({
1217
+ const result = await adapter.executeTool({
1053
1218
  id: "call-1",
1054
1219
  name: "execShell",
1055
- arguments: "{}",
1056
- })).rejects.toThrow("execShell requires NOLO_LOCAL_SHELL_MODE");
1220
+ arguments: "{\"cmd\":\"pwd\"}",
1221
+ });
1222
+
1223
+ expect(result.content).toContain("\"cmd\":\"pwd\"");
1057
1224
  });
1058
1225
 
1059
1226
  test("executes explicitly allowed registered local tools declared by the agent", async () => {
@@ -1095,13 +1262,12 @@ describe("CLI local runtime adapter", () => {
1095
1262
  expect(result.content).toContain("README.md");
1096
1263
  });
1097
1264
 
1098
- test("advertises execShell to OpenAI-compatible providers when shell mode is enabled", async () => {
1265
+ test("advertises execShell to OpenAI-compatible providers by default", async () => {
1099
1266
  const requests: Array<{ body: any }> = [];
1100
1267
  const adapter = createCliLocalRuntimeAdapter({
1101
1268
  env: {
1102
1269
  OPENAI_API_KEY: "sk-local",
1103
1270
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1104
- NOLO_LOCAL_SHELL_MODE: "worktree",
1105
1271
  },
1106
1272
  db: {
1107
1273
  get: async () => ({
@@ -1127,6 +1293,61 @@ describe("CLI local runtime adapter", () => {
1127
1293
  input: "pwd",
1128
1294
  });
1129
1295
 
1296
+ expect(toolNamesFromRequest(requests[0])).toEqual([
1297
+ ...SHELL_LOCAL_CODING_TOOL_NAMES,
1298
+ ...DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOL_NAMES,
1299
+ ]);
1300
+ });
1301
+
1302
+ test("keeps monthly Mimo local model tools to the compact coding surface", async () => {
1303
+ const requests: Array<{ body: any }> = [];
1304
+ const adapter = createCliLocalRuntimeAdapter({
1305
+ env: {
1306
+ OPENAI_API_KEY: "sk-local",
1307
+ NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1308
+ },
1309
+ db: {
1310
+ get: async () => ({
1311
+ dbKey: MIMO_MONTH_AGENT_KEY,
1312
+ prompt: "Use local coding tools.",
1313
+ model: "mimo-v2.5-pro",
1314
+ provider: "custom",
1315
+ tools: [
1316
+ "read",
1317
+ "searchDialogMessages",
1318
+ "searchFiles",
1319
+ "codeSearch",
1320
+ "listFiles",
1321
+ "readFile",
1322
+ "writeFile",
1323
+ "editFile",
1324
+ "searchFiles",
1325
+ "legacyLocalAlias",
1326
+ "applyPatch",
1327
+ "execShell",
1328
+ "checkEnv",
1329
+ "queryTableRows",
1330
+ "taskRun",
1331
+ ],
1332
+ }),
1333
+ put: async () => {},
1334
+ batch: async () => {},
1335
+ iterator: () => (async function* () {})(),
1336
+ },
1337
+ fetchImpl: async (_url, init) => {
1338
+ requests.push({ body: JSON.parse(String(init?.body)) });
1339
+ return Response.json({
1340
+ choices: [{ message: { content: "done" } }],
1341
+ });
1342
+ },
1343
+ });
1344
+
1345
+ await runLocalAgentTurn({
1346
+ adapter,
1347
+ agentRef: MIMO_MONTH_AGENT_KEY,
1348
+ input: "inspect cwd",
1349
+ });
1350
+
1130
1351
  expect(toolNamesFromRequest(requests[0])).toEqual(SHELL_LOCAL_CODING_TOOL_NAMES);
1131
1352
  });
1132
1353
 
@@ -1248,52 +1469,114 @@ describe("CLI local runtime adapter", () => {
1248
1469
  expect(result.metadata).toMatchObject({ serverPlatformTool: true });
1249
1470
  });
1250
1471
 
1251
- test("activates an agent-declared task worktree before running workspace tools", async () => {
1252
- const taskWorktreeRoot = mkdtempSync(join(tmpdir(), "nolo-cli-runtime-worktree-"));
1253
- const chunks: string[] = [];
1472
+ test("adds typed CLI workspace tools to the default nolo local agent request", async () => {
1473
+ const requests: Array<{ body: any }> = [];
1474
+ const adapter = createCliLocalRuntimeAdapter({
1475
+ env: {
1476
+ OPENAI_API_KEY: "sk-local",
1477
+ NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1478
+ },
1479
+ db: {
1480
+ get: async () => ({
1481
+ dbKey: "agent-pub-01NOLOAPPBLD000000019KCKT0",
1482
+ id: "01NOLOAPPBLD000000019KCKT0",
1483
+ name: "nolo",
1484
+ prompt: "Route through typed tools.",
1485
+ model: "gpt-4.1-mini",
1486
+ tools: ["fetchWebpage"],
1487
+ }),
1488
+ put: async () => {},
1489
+ batch: async () => {},
1490
+ iterator: () => (async function* () {})(),
1491
+ },
1492
+ fetchImpl: async (_url, init) => {
1493
+ requests.push({ body: JSON.parse(String(init?.body)) });
1494
+ return Response.json({
1495
+ choices: [{ message: { content: "done" } }],
1496
+ });
1497
+ },
1498
+ });
1499
+
1500
+ await runLocalAgentTurn({
1501
+ adapter,
1502
+ agentRef: "agent-pub-01NOLOAPPBLD000000019KCKT0",
1503
+ input: "帮我总结最近 10 个对话",
1504
+ });
1505
+
1506
+ const toolNames = toolNamesFromRequest(requests[0]);
1507
+ expect(toolNames).toContain("listDialogs");
1508
+ expect(toolNames).toContain("readDialog");
1509
+ expect(toolNames).toContain("listAgents");
1510
+ expect(toolNames).toContain("readSpace");
1511
+ expect(toolNames).toContain("queryTableRows");
1512
+ });
1513
+
1514
+ test("executes typed CLI workspace tools through whitelisted nolo commands", async () => {
1515
+ const spawnCalls: Array<{ cmd: string[]; env: NodeJS.ProcessEnv }> = [];
1516
+ const originalSpawn = Bun.spawn;
1517
+ Bun.spawn = ((options: { cmd: string[]; env: NodeJS.ProcessEnv }) => {
1518
+ spawnCalls.push(options);
1519
+ return {
1520
+ stdout: new ReadableStream({
1521
+ start(controller) {
1522
+ controller.enqueue(new TextEncoder().encode("dialog output\n"));
1523
+ controller.close();
1524
+ },
1525
+ }),
1526
+ stderr: new ReadableStream({
1527
+ start(controller) {
1528
+ controller.close();
1529
+ },
1530
+ }),
1531
+ exited: Promise.resolve(0),
1532
+ };
1533
+ }) as unknown as typeof Bun.spawn;
1534
+
1254
1535
  try {
1255
- await Bun.write(join(taskWorktreeRoot, "README.md"), "worktree ok\n");
1256
1536
  const adapter = createCliLocalRuntimeAdapter({
1257
1537
  env: {
1258
1538
  NOLO_LOCAL_USER_ID: "user-1",
1539
+ AUTH_TOKEN: "token-1",
1259
1540
  },
1260
- output: { write: (chunk) => chunks.push(chunk) },
1261
1541
  db: {
1262
1542
  get: async () => ({
1263
- dbKey: "agent-user-1-frontend",
1264
- id: "frontend",
1265
- prompt: "Use local workspace tools.",
1266
- runtimeBinding: { localWorkspaceMode: "task-worktree" },
1543
+ dbKey: "agent-pub-01NOLOAPPBLD000000019KCKT0",
1544
+ id: "01NOLOAPPBLD000000019KCKT0",
1267
1545
  }),
1268
1546
  put: async () => {},
1269
1547
  batch: async () => {},
1270
1548
  iterator: () => (async function* () {})(),
1271
1549
  },
1272
- prepareTaskWorktree: async ({ agentKey }) => ({
1273
- path: taskWorktreeRoot,
1274
- branchName: `nolo-agent-${agentKey}`,
1275
- }),
1276
1550
  fetchImpl: async () => Response.json({}),
1277
1551
  });
1278
1552
 
1279
- await adapter.loadAgentConfig("frontend");
1553
+ await adapter.loadAgentConfig("agent-pub-01NOLOAPPBLD000000019KCKT0");
1280
1554
  const result = await adapter.executeTool({
1281
- id: "call-read",
1282
- name: "readFile",
1283
- arguments: JSON.stringify({ path: "README.md" }),
1555
+ id: "call-list-dialogs",
1556
+ name: "listDialogs",
1557
+ arguments: JSON.stringify({ limit: 3 }),
1284
1558
  });
1285
1559
 
1286
- expect(result.content).toBe("worktree ok\n");
1287
- expect(chunks.join("")).toContain(`workspace session: task-worktree ${taskWorktreeRoot}`);
1288
- expect(chunks.join("")).toContain("branch nolo-agent-frontend");
1560
+ expect(result.content).toBe("dialog output\n");
1561
+ expect(result.metadata).toMatchObject({
1562
+ cliWorkspaceTool: true,
1563
+ exitCode: 0,
1564
+ });
1565
+ expect(spawnCalls[0]?.cmd.at(-5)?.endsWith("packages/cli/index.ts")).toBe(true);
1566
+ expect(spawnCalls[0]?.cmd.slice(-4)).toEqual([
1567
+ "dialog",
1568
+ "list",
1569
+ "--limit",
1570
+ "3",
1571
+ ]);
1289
1572
  } finally {
1290
- rmSync(taskWorktreeRoot, { recursive: true, force: true });
1573
+ Bun.spawn = originalSpawn;
1291
1574
  }
1292
1575
  });
1293
1576
 
1294
- test("runs execShell locally in explicit worktree shell mode", async () => {
1577
+ test("runs execShell locally by default", async () => {
1295
1578
  const adapter = createCliLocalRuntimeAdapter({
1296
- env: { NOLO_LOCAL_SHELL_MODE: "worktree" },
1579
+ env: {},
1297
1580
  db: {
1298
1581
  get: async () => ({
1299
1582
  dbKey: "agent-local-shell",
@@ -1324,12 +1607,11 @@ describe("CLI local runtime adapter", () => {
1324
1607
  expect(result.metadata).toMatchObject({ exitCode: 0 });
1325
1608
  });
1326
1609
 
1327
- test("applies runtime policy shell output limit to local executors without adding a timeout", async () => {
1610
+ test("applies runtime policy shell settings to local executors without adding a timeout", async () => {
1328
1611
  const requests: Array<{ body: any }> = [];
1329
1612
  const adapter = createCliLocalRuntimeAdapter({
1330
1613
  env: {
1331
1614
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1332
- NOLO_LOCAL_SHELL_MODE: "worktree",
1333
1615
  },
1334
1616
  db: {
1335
1617
  get: async () => ({
@@ -1361,8 +1643,8 @@ describe("CLI local runtime adapter", () => {
1361
1643
  name: "execShell",
1362
1644
  arguments: JSON.stringify({
1363
1645
  cmd: process.platform === "win32"
1364
- ? "'abcdefghijklmnopqrstuvwxyz0123456789'.PadRight(500, 'x')"
1365
- : "bun -e 'console.log(\"x\".repeat(500))'",
1646
+ ? "'abcdefghijklmnopqrstuvwxyz0123456789'"
1647
+ : "node -e 'console.log(\"x\".repeat(50))'",
1366
1648
  }),
1367
1649
  },
1368
1650
  }],
@@ -1384,7 +1666,7 @@ describe("CLI local runtime adapter", () => {
1384
1666
 
1385
1667
  expect(result.content).toBe("limits applied");
1386
1668
  const toolResult = requests[1]?.body.messages.at(-1)?.content ?? "";
1387
- expect(toolResult).toContain("[truncated ");
1669
+ expect(toolResult).toContain("exitCode: 0");
1388
1670
  expect(toolResult).not.toContain("command timed out");
1389
1671
  });
1390
1672
 
@@ -1393,7 +1675,6 @@ describe("CLI local runtime adapter", () => {
1393
1675
  const adapter = createCliLocalRuntimeAdapter({
1394
1676
  env: {
1395
1677
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1396
- NOLO_LOCAL_SHELL_MODE: "worktree",
1397
1678
  },
1398
1679
  db: {
1399
1680
  get: async () => ({
@@ -1450,7 +1731,6 @@ describe("CLI local runtime adapter", () => {
1450
1731
  const adapter = createCliLocalRuntimeAdapter({
1451
1732
  env: {
1452
1733
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1453
- NOLO_LOCAL_SHELL_MODE: "worktree",
1454
1734
  },
1455
1735
  db: {
1456
1736
  get: async () => ({
@@ -1518,7 +1798,6 @@ describe("CLI local runtime adapter", () => {
1518
1798
  const adapter = createCliLocalRuntimeAdapter({
1519
1799
  env: {
1520
1800
  NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
1521
- NOLO_LOCAL_SHELL_MODE: "worktree",
1522
1801
  },
1523
1802
  db: {
1524
1803
  get: async () => ({
@@ -1624,7 +1903,7 @@ describe("CLI local runtime adapter", () => {
1624
1903
  ["agent-user-1-writer", {
1625
1904
  dbKey: "agent-user-1-writer",
1626
1905
  id: "writer",
1627
- toolNames: ["writeWorkspaceFile"],
1906
+ toolNames: ["writeFile"],
1628
1907
  }],
1629
1908
  ]);
1630
1909
  const adapter = createCliLocalRuntimeAdapter({
@@ -1647,7 +1926,7 @@ describe("CLI local runtime adapter", () => {
1647
1926
  await adapter.loadAgentConfig("writer");
1648
1927
  const result = await adapter.executeTool({
1649
1928
  id: "call-1",
1650
- name: "writeWorkspaceFile",
1929
+ name: "writeFile",
1651
1930
  arguments: JSON.stringify({
1652
1931
  path: "src/app.ts",
1653
1932
  content: "export const cliValue = 1;\n",