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.
- package/README.md +4 -4
- package/agent-runtime/agentRecordConfig.ts +0 -14
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -1
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +39 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +1 -17
- package/agent-runtime/localWorkspaceTools.ts +51 -110
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +3 -14
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +2 -1
- package/agentAliases.ts +8 -1
- package/agentRunCommand.ts +65 -95
- package/agentRuntimeLocal.ts +1 -7
- package/ai/agent/cliExecutor.ts +249 -34
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +8 -1
- package/cli/agentRunCommand.ts +65 -95
- package/cli/agentRuntimeLocal.ts +1 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +94 -93
- package/cli/client/localRuntimeAdapter.ts +250 -69
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +76 -39
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/cli/scriptCommandEntries.ts +0 -2
- package/cli/tui/readlineWorkspace.ts +69 -0
- package/cli/tui/session.ts +176 -1
- package/cliEnvHelpers.ts +11 -7
- package/client/agentConfigResolver.test.ts +4 -5
- package/client/agentRun.test.ts +307 -57
- package/client/agentRun.ts +94 -93
- package/client/localRuntimeAdapter.test.ts +332 -53
- package/client/localRuntimeAdapter.ts +250 -69
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/dialogCommands.ts +572 -0
- package/dialogInternalCommandEntries.ts +4 -0
- package/machineWsRunDispatch.ts +76 -39
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +11 -9
- package/scriptCommandEntries.ts +0 -2
- package/tui/readlineWorkspace.ts +69 -0
- package/tui/session.ts +176 -1
- package/agent-runtime/taskWorkspace.ts +0 -193
- package/agent-runtime/workspaceSession.ts +0 -76
- package/cli/client/taskWorktree.ts +0 -8
- package/cli/client/workspaceSession.ts +0 -11
- package/client/taskWorktree.ts +0 -8
- package/client/workspaceSession.test.ts +0 -57
- 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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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: ["
|
|
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(
|
|
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: ["
|
|
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: ["
|
|
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: "
|
|
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
|
-
|
|
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: "
|
|
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("
|
|
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
|
|
1217
|
+
const result = await adapter.executeTool({
|
|
1053
1218
|
id: "call-1",
|
|
1054
1219
|
name: "execShell",
|
|
1055
|
-
arguments: "{}",
|
|
1056
|
-
})
|
|
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
|
|
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("
|
|
1252
|
-
const
|
|
1253
|
-
const
|
|
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-
|
|
1264
|
-
id: "
|
|
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("
|
|
1553
|
+
await adapter.loadAgentConfig("agent-pub-01NOLOAPPBLD000000019KCKT0");
|
|
1280
1554
|
const result = await adapter.executeTool({
|
|
1281
|
-
id: "call-
|
|
1282
|
-
name: "
|
|
1283
|
-
arguments: JSON.stringify({
|
|
1555
|
+
id: "call-list-dialogs",
|
|
1556
|
+
name: "listDialogs",
|
|
1557
|
+
arguments: JSON.stringify({ limit: 3 }),
|
|
1284
1558
|
});
|
|
1285
1559
|
|
|
1286
|
-
expect(result.content).toBe("
|
|
1287
|
-
expect(
|
|
1288
|
-
|
|
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
|
-
|
|
1573
|
+
Bun.spawn = originalSpawn;
|
|
1291
1574
|
}
|
|
1292
1575
|
});
|
|
1293
1576
|
|
|
1294
|
-
test("runs execShell locally
|
|
1577
|
+
test("runs execShell locally by default", async () => {
|
|
1295
1578
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1296
|
-
env: {
|
|
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
|
|
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'
|
|
1365
|
-
: "
|
|
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("
|
|
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: ["
|
|
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: "
|
|
1929
|
+
name: "writeFile",
|
|
1651
1930
|
arguments: JSON.stringify({
|
|
1652
1931
|
path: "src/app.ts",
|
|
1653
1932
|
content: "export const cliValue = 1;\n",
|