nolo-cli 0.1.32 → 0.1.34
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 -10
- package/agent-runtime/agentRecordConfig.ts +10 -0
- package/agent-runtime/hostAdapter.ts +10 -1
- package/agent-runtime/index.ts +9 -20
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +76 -34
- package/agent-runtime/localWorkspaceTools.ts +30 -333
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +47 -73
- package/agent-runtime/providerResolution.ts +244 -0
- package/agent-runtime/runtimeToolPolicy.ts +77 -29
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentAliases.ts +14 -83
- package/agentCommandSupport.ts +3 -6
- package/agentInternalCommandEntries.ts +4 -13
- package/agentListCommands.ts +18 -27
- package/agentMachineCommands.ts +40 -336
- package/agentPullCommand.ts +30 -55
- package/agentRecordCommands.ts +84 -1
- package/agentRecordHelpers.ts +102 -186
- package/agentRunCommand.ts +438 -107
- package/agentRuntimeLocal.ts +2 -9
- package/ai/agent/cliExecutor.ts +19 -323
- package/ai/tools/toolNameAliases.ts +70 -0
- package/authCommands.ts +2 -36
- package/cli/agentAliases.ts +14 -83
- package/cli/agentCommandSupport.ts +3 -6
- package/cli/agentInternalCommandEntries.ts +4 -13
- package/cli/agentListCommands.ts +18 -27
- package/cli/agentMachineCommands.ts +40 -336
- package/cli/agentPullCommand.ts +30 -55
- package/cli/agentRecordCommands.ts +84 -1
- package/cli/agentRecordHelpers.ts +102 -186
- package/cli/agentRunCommand.ts +438 -107
- package/cli/agentRuntimeLocal.ts +2 -9
- package/cli/authCommands.ts +2 -36
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +34 -107
- package/cli/client/agentRun.ts +854 -57
- package/cli/client/localRuntimeAdapter.ts +59 -159
- package/cli/client/streamingOutput.ts +3 -1
- package/cli/client/taskRunPrompt.ts +24 -7
- package/cli/commandRegistry.ts +4 -5
- package/cli/connectorRunArtifact.ts +10 -34
- package/cli/connectorWebSocketTarget.ts +6 -10
- package/cli/internalCommandEntries.ts +0 -8
- package/cli/localRuntimeDb.ts +2 -39
- package/cli/machineCommands.ts +189 -190
- package/cli/machineDaemonCommands.ts +165 -9
- package/cli/machineHeartbeatCommands.ts +2 -1
- package/cli/machineStatusCommands.ts +13 -5
- package/cli/machineWatchCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +30 -44
- package/cli/machineWsSession.ts +0 -2
- package/cli/offlineMarxistsAgentCommand.ts +314 -0
- package/cli/runtimeDoctorCommands.ts +72 -15
- package/cli/scriptCommandEntries.ts +45 -5
- package/cli/tableCommands.ts +133 -33
- package/cli/taskRunCommand.ts +211 -16
- package/cli/tui/session.ts +372 -43
- package/cli/workflowInternalCommandEntries.ts +0 -20
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +34 -107
- package/client/agentConfigResolver.test.ts +2 -0
- package/client/agentRun.test.ts +12 -208
- package/client/agentRun.ts +854 -57
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +296 -171
- package/client/localRuntimeAdapter.ts +59 -159
- package/client/localToolPolicy.test.ts +4 -28
- package/client/streamingOutput.ts +3 -1
- package/client/taskRunPrompt.ts +24 -7
- package/commandRegistry.ts +4 -5
- package/connectorRunArtifact.ts +10 -34
- package/connectorWebSocketTarget.ts +6 -10
- package/database/server/serverStoreFactory.ts +2 -8
- package/internalCommandEntries.ts +0 -8
- package/localRuntimeDb.ts +2 -39
- package/machineCommands.ts +189 -190
- package/machineDaemonCommands.ts +165 -9
- package/machineHeartbeatCommands.ts +2 -1
- package/machineStatusCommands.ts +13 -5
- package/machineWatchCommands.ts +1 -1
- package/machineWsRunDispatch.ts +30 -44
- package/machineWsSession.ts +0 -2
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +52 -188
- package/runtimeDoctorCommands.ts +72 -15
- package/scriptCommandEntries.ts +45 -5
- package/tableCommands.ts +133 -33
- package/taskRunCommand.ts +211 -16
- package/tui/session.ts +372 -43
- package/workflowInternalCommandEntries.ts +0 -20
- package/agent-runtime/dialogStatus.ts +0 -139
- package/agent-runtime/hostRuntimeConfig.ts +0 -86
- package/agentCreateCustomCommands.ts +0 -278
- package/agentDialogsCommands.ts +0 -227
- package/agentDoctorCommands.ts +0 -203
- package/agentRunLocalWorkspace.ts +0 -182
- package/agentRunParsing.ts +0 -298
- package/agentSuperviseCommands.ts +0 -8
- package/agentUnpublishCommands.ts +0 -113
- package/ai/agent/agentRunDialogLineage.ts +0 -56
- package/ai/policy/types.ts +0 -64
- package/ai/skills/skillSummaryMarker.ts +0 -26
- package/app/types.ts +0 -358
- package/build.ts +0 -16
- package/buildPublish.ts +0 -138
- package/buildPublishArtifactAssembly.ts +0 -724
- package/buildPublishImportAnalysis.ts +0 -242
- package/chat/messages/fetchMessages.ts +0 -84
- package/chat/messages/types.ts +0 -185
- package/cli/agentCreateCustomCommands.ts +0 -278
- package/cli/agentDialogsCommands.ts +0 -227
- package/cli/agentDoctorCommands.ts +0 -203
- package/cli/agentRunLocalWorkspace.ts +0 -182
- package/cli/agentRunParsing.ts +0 -298
- package/cli/agentSuperviseCommands.ts +0 -8
- package/cli/agentUnpublishCommands.ts +0 -113
- package/cli/buildPublish.ts +0 -138
- package/cli/buildPublishArtifactAssembly.ts +0 -724
- package/cli/buildPublishImportAnalysis.ts +0 -242
- package/cli/cliIoTypes.ts +0 -3
- package/cli/cliRuntimeDoctorReport.ts +0 -193
- package/cli/client/agentRunInput.ts +0 -10
- package/cli/client/agentRunLocalExecution.ts +0 -124
- package/cli/client/agentRunLocalExecutor.ts +0 -179
- package/cli/client/agentRunRuntimeSelection.ts +0 -145
- package/cli/client/agentRunServerTransport.ts +0 -397
- package/cli/client/agentRunTaskRunRecorder.ts +0 -176
- package/cli/customCodingAgentFlow.ts +0 -208
- package/cli/customCodingAgentRecord.ts +0 -179
- package/cli/dialogDataAccess.ts +0 -186
- package/cli/dialogInternalCommandEntries.ts +0 -11
- package/cli/dialogReadCommands.ts +0 -180
- package/cli/dialogReadHelpers.ts +0 -226
- package/cli/dialogStatusCommands.ts +0 -123
- package/cli/docInternalCommandEntries.ts +0 -20
- package/cli/hybridReadHelpers.ts +0 -109
- package/cli/machineCommandPrompts.ts +0 -79
- package/cli/machineCommandResolution.ts +0 -43
- package/cli/noloReadBridgePrompt.ts +0 -16
- package/cli/pageCreateCommands.ts +0 -219
- package/cli/pageDeleteCommands.ts +0 -167
- package/cli/pageReadCommands.ts +0 -151
- package/cli/pageUpdateCommands.ts +0 -249
- package/cli/pageWriteHelpers.ts +0 -209
- package/cli/publishPackage.ts +0 -36
- package/cli/runtimeInternalCommandEntries.ts +0 -15
- package/cli/runtimePassthroughCommands.ts +0 -22
- package/cli/scriptPassthroughHelpers.ts +0 -28
- package/cli/secretScanHelpers.ts +0 -31
- package/cli/spaceAttachCommands.ts +0 -134
- package/cli/spaceCategoryCommands.ts +0 -263
- package/cli/spaceCommands.ts +0 -295
- package/cli/spaceDeleteCommands.ts +0 -312
- package/cli/spaceInternalCommandEntries.ts +0 -21
- package/cli/spaceMutationHelpers.ts +0 -135
- package/cli/spaceReadCommands.ts +0 -323
- package/cli/tableDataOutput.ts +0 -41
- package/cli/tableDataSafety.ts +0 -26
- package/cli/tableMetaCommands.ts +0 -307
- package/cli/tableMutationCommands.ts +0 -266
- package/cli/tableQueryParsing.ts +0 -90
- package/cli/tableQueryRequest.ts +0 -53
- package/cli/taskRunActions.ts +0 -122
- package/cli/taskRunParsing.ts +0 -86
- package/cli/taskRunPromptShared.ts +0 -55
- package/cli/tui/tuiCatalog.ts +0 -22
- package/cli/tui/tuiCommandInterpreter.ts +0 -253
- package/cli/tui/tuiPresentation.ts +0 -146
- package/cliIoTypes.ts +0 -3
- package/cliRuntimeDoctorReport.ts +0 -193
- package/client/agentRunInput.ts +0 -10
- package/client/agentRunLocalExecution.test.ts +0 -123
- package/client/agentRunLocalExecution.ts +0 -124
- package/client/agentRunLocalExecutor.ts +0 -179
- package/client/agentRunRuntimeSelection.test.ts +0 -159
- package/client/agentRunRuntimeSelection.ts +0 -145
- package/client/agentRunServerTransport.ts +0 -397
- package/client/agentRunTaskRunRecorder.ts +0 -176
- package/connector-experimental/daemonLauncher.ts +0 -223
- package/create/space/spaceKeys.ts +0 -65
- package/create/types.ts +0 -21
- package/customCodingAgentFlow.ts +0 -208
- package/customCodingAgentRecord.ts +0 -179
- package/database/keys.ts +0 -765
- package/database/tombstones.ts +0 -52
- package/database/utils/ulid.ts +0 -1
- package/dialogDataAccess.ts +0 -186
- package/dialogInternalCommandEntries.ts +0 -11
- package/dialogReadCommands.ts +0 -180
- package/dialogReadHelpers.ts +0 -226
- package/dialogStatusCommands.ts +0 -123
- package/docInternalCommandEntries.ts +0 -20
- package/hybridReadHelpers.ts +0 -109
- package/machineCommandPrompts.ts +0 -79
- package/machineCommandResolution.ts +0 -43
- package/noloReadBridgePrompt.ts +0 -16
- package/pageCreateCommands.ts +0 -219
- package/pageDeleteCommands.ts +0 -167
- package/pageReadCommands.ts +0 -151
- package/pageUpdateCommands.ts +0 -249
- package/pageWriteHelpers.ts +0 -209
- package/publishPackage.ts +0 -36
- package/runtimeInternalCommandEntries.ts +0 -15
- package/runtimePassthroughCommands.ts +0 -22
- package/scriptPassthroughHelpers.ts +0 -28
- package/secretScanHelpers.ts +0 -31
- package/share/helpers.ts +0 -302
- package/share/keys.ts +0 -243
- package/share/types.ts +0 -100
- package/spaceAttachCommands.ts +0 -134
- package/spaceCategoryCommands.ts +0 -263
- package/spaceCommands.ts +0 -295
- package/spaceDeleteCommands.ts +0 -312
- package/spaceInternalCommandEntries.ts +0 -21
- package/spaceMutationHelpers.ts +0 -135
- package/spaceReadCommands.ts +0 -323
- package/tableDataOutput.ts +0 -41
- package/tableDataSafety.ts +0 -26
- package/tableMetaCommands.ts +0 -307
- package/tableMutationCommands.ts +0 -266
- package/tableQueryParsing.ts +0 -90
- package/tableQueryRequest.ts +0 -53
- package/taskRunActions.ts +0 -122
- package/taskRunParsing.ts +0 -86
- package/taskRunPromptShared.ts +0 -55
- package/tui/tuiCatalog.ts +0 -22
- package/tui/tuiCommandInterpreter.test.ts +0 -169
- package/tui/tuiCommandInterpreter.ts +0 -253
- package/tui/tuiPresentation.test.ts +0 -154
- package/tui/tuiPresentation.ts +0 -146
|
@@ -11,15 +11,18 @@ describe("CLI local provider resolver", () => {
|
|
|
11
11
|
provider: "custom",
|
|
12
12
|
apiSource: "custom",
|
|
13
13
|
customProviderUrl: "https://provider.example/v1/chat/completions",
|
|
14
|
+
apiKey: "sk-agent-custom",
|
|
15
|
+
apiKeyHeader: "api-key",
|
|
14
16
|
},
|
|
15
17
|
env: {
|
|
16
|
-
OPENAI_API_KEY: "sk-env",
|
|
18
|
+
OPENAI_API_KEY: "sk-env-should-not-win",
|
|
17
19
|
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
18
20
|
},
|
|
19
21
|
})).toEqual({
|
|
20
22
|
model: "qwen-coder",
|
|
21
23
|
endpoint: "https://provider.example/v1/chat/completions",
|
|
22
|
-
apiKey: "sk-
|
|
24
|
+
apiKey: "sk-agent-custom",
|
|
25
|
+
apiKeyHeader: "api-key",
|
|
23
26
|
provider: "custom",
|
|
24
27
|
requestOptions: {},
|
|
25
28
|
});
|
|
@@ -53,6 +56,27 @@ describe("CLI local provider resolver", () => {
|
|
|
53
56
|
});
|
|
54
57
|
});
|
|
55
58
|
|
|
59
|
+
test("defaults Xiaomi custom endpoints to api-key auth header", () => {
|
|
60
|
+
expect(resolveCliOpenAiProviderConfig({
|
|
61
|
+
agentConfig: {
|
|
62
|
+
key: "agent-user-1-mimo",
|
|
63
|
+
provider: "custom",
|
|
64
|
+
apiSource: "custom",
|
|
65
|
+
model: "mimo-v2.5-pro",
|
|
66
|
+
customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
67
|
+
apiKey: "mimo-monthly-key",
|
|
68
|
+
},
|
|
69
|
+
env: {},
|
|
70
|
+
})).toEqual({
|
|
71
|
+
model: "mimo-v2.5-pro",
|
|
72
|
+
endpoint: "https://token-plan-cn.xiaomimimo.com/v1/chat/completions",
|
|
73
|
+
apiKey: "mimo-monthly-key",
|
|
74
|
+
apiKeyHeader: "api-key",
|
|
75
|
+
provider: "custom",
|
|
76
|
+
requestOptions: {},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
56
80
|
test("carries explicit agent inference options for request body construction", () => {
|
|
57
81
|
expect(resolveCliOpenAiProviderConfig({
|
|
58
82
|
agentConfig: {
|
|
@@ -3,7 +3,6 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
|
|
6
|
-
import { createHostRuntimeConfig } from "../agent-runtime";
|
|
7
6
|
import { runLocalAgentTurn } from "../agent-runtime/localLoop";
|
|
8
7
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
9
8
|
|
|
@@ -23,9 +22,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
23
22
|
"searchFiles",
|
|
24
23
|
];
|
|
25
24
|
const SHELL_LOCAL_CODING_TOOL_NAMES = [
|
|
26
|
-
|
|
27
|
-
"writeFile",
|
|
28
|
-
"editFile",
|
|
25
|
+
...DEFAULT_LOCAL_CODING_TOOL_NAMES,
|
|
29
26
|
"execShell",
|
|
30
27
|
];
|
|
31
28
|
|
|
@@ -33,14 +30,6 @@ describe("CLI local runtime adapter", () => {
|
|
|
33
30
|
return request?.body?.tools?.map((tool: any) => tool.function.name) ?? [];
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
function createCliHostRuntimeConfig(allowShell = false, workspaceBinding: "explicit-cwd" | "default-cwd" = "explicit-cwd") {
|
|
37
|
-
return createHostRuntimeConfig({
|
|
38
|
-
surface: "cli-local",
|
|
39
|
-
workspaceBinding,
|
|
40
|
-
allowShell,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
33
|
test("loads agent/history from LevelDB and saves dialog/message records back to LevelDB", async () => {
|
|
45
34
|
const requests: Array<{ url: string; body: any; auth: string | null }> = [];
|
|
46
35
|
const store = new Map<string, any>([
|
|
@@ -289,59 +278,8 @@ describe("CLI local runtime adapter", () => {
|
|
|
289
278
|
});
|
|
290
279
|
});
|
|
291
280
|
|
|
292
|
-
test("
|
|
293
|
-
const
|
|
294
|
-
const adapter = createCliLocalRuntimeAdapter({
|
|
295
|
-
env: {
|
|
296
|
-
OPENAI_API_KEY: "sk-local",
|
|
297
|
-
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
298
|
-
},
|
|
299
|
-
cwd: import.meta.dir,
|
|
300
|
-
db: {
|
|
301
|
-
get: async () => ({
|
|
302
|
-
dbKey: "agent-local-frontend",
|
|
303
|
-
id: "frontend",
|
|
304
|
-
apiSource: "cli",
|
|
305
|
-
cliProvider: "agy",
|
|
306
|
-
prompt: "Fix UI",
|
|
307
|
-
model: "",
|
|
308
|
-
}),
|
|
309
|
-
put: async () => {},
|
|
310
|
-
batch: async () => {},
|
|
311
|
-
iterator: () => (async function* () {})(),
|
|
312
|
-
},
|
|
313
|
-
fetchImpl: async () => {
|
|
314
|
-
throw new Error("OpenAI-compatible fetch should not run for local CLI agents");
|
|
315
|
-
},
|
|
316
|
-
executeCli: async (provider, prompt, options) => {
|
|
317
|
-
executions.push({ provider, prompt, options });
|
|
318
|
-
return { text: "cli local ok", raw: "cli local ok", elapsed: 10 };
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
const result = await runLocalAgentTurn({
|
|
323
|
-
adapter,
|
|
324
|
-
agentRef: "frontend",
|
|
325
|
-
input: "make it cleaner",
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
expect(result).toMatchObject({
|
|
329
|
-
content: "cli local ok",
|
|
330
|
-
model: "agy",
|
|
331
|
-
});
|
|
332
|
-
expect(executions).toEqual([{
|
|
333
|
-
provider: "agy",
|
|
334
|
-
prompt: "make it cleaner",
|
|
335
|
-
options: expect.objectContaining({
|
|
336
|
-
cwd: import.meta.dir,
|
|
337
|
-
yolo: true,
|
|
338
|
-
systemPrompt: "Fix UI",
|
|
339
|
-
}),
|
|
340
|
-
}]);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test("uses agent customProviderUrl for local OpenAI-compatible requests", async () => {
|
|
344
|
-
const requests: Array<{ url: string; auth: string | null; body: any }> = [];
|
|
281
|
+
test("uses agent-owned custom credentials for local OpenAI-compatible requests", async () => {
|
|
282
|
+
const requests: Array<{ url: string; auth: string | null; apiKeyHeader: string | null; body: any }> = [];
|
|
345
283
|
const store = new Map<string, any>([
|
|
346
284
|
["agent-user-1-custom", {
|
|
347
285
|
dbKey: "agent-user-1-custom",
|
|
@@ -351,6 +289,8 @@ describe("CLI local runtime adapter", () => {
|
|
|
351
289
|
provider: "custom-openai-compatible",
|
|
352
290
|
apiSource: "custom",
|
|
353
291
|
customProviderUrl: "https://provider.example/v1/chat/completions",
|
|
292
|
+
apiKey: "sk-agent-custom",
|
|
293
|
+
apiKeyHeader: "api-key",
|
|
354
294
|
temperature: 0.2,
|
|
355
295
|
max_tokens: 4096,
|
|
356
296
|
reasoning_effort: "medium",
|
|
@@ -381,6 +321,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
381
321
|
requests.push({
|
|
382
322
|
url: String(url),
|
|
383
323
|
auth: new Headers(init?.headers).get("Authorization"),
|
|
324
|
+
apiKeyHeader: new Headers(init?.headers).get("api-key"),
|
|
384
325
|
body: JSON.parse(String(init?.body)),
|
|
385
326
|
});
|
|
386
327
|
return Response.json({
|
|
@@ -399,7 +340,8 @@ describe("CLI local runtime adapter", () => {
|
|
|
399
340
|
expect(result.provider).toBe("custom-openai-compatible");
|
|
400
341
|
expect(requests[0]).toMatchObject({
|
|
401
342
|
url: "https://provider.example/v1/chat/completions",
|
|
402
|
-
auth:
|
|
343
|
+
auth: null,
|
|
344
|
+
apiKeyHeader: "sk-agent-custom",
|
|
403
345
|
body: {
|
|
404
346
|
model: "custom-coder",
|
|
405
347
|
messages: [
|
|
@@ -415,6 +357,57 @@ describe("CLI local runtime adapter", () => {
|
|
|
415
357
|
expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_LOCAL_CODING_TOOL_NAMES);
|
|
416
358
|
});
|
|
417
359
|
|
|
360
|
+
test("aborts stalled custom provider requests when timeoutMs is provided", async () => {
|
|
361
|
+
const store = new Map<string, any>([
|
|
362
|
+
["agent-user-1-mimo", {
|
|
363
|
+
dbKey: "agent-user-1-mimo",
|
|
364
|
+
id: "mimo",
|
|
365
|
+
prompt: "Use monthly mimo.",
|
|
366
|
+
model: "mimo-v2.5-pro",
|
|
367
|
+
provider: "custom",
|
|
368
|
+
apiSource: "custom",
|
|
369
|
+
customProviderUrl: "https://token-plan-cn.xiaomimimo.com/v1",
|
|
370
|
+
apiKey: "mimo-monthly-key",
|
|
371
|
+
}],
|
|
372
|
+
]);
|
|
373
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
374
|
+
env: {
|
|
375
|
+
NOLO_LOCAL_USER_ID: "user-1",
|
|
376
|
+
},
|
|
377
|
+
db: {
|
|
378
|
+
get: async (key) => {
|
|
379
|
+
if (!store.has(key)) throw new Error(`not found: ${key}`);
|
|
380
|
+
return store.get(key);
|
|
381
|
+
},
|
|
382
|
+
put: async (key, value) => {
|
|
383
|
+
store.set(key, value);
|
|
384
|
+
},
|
|
385
|
+
batch: async (ops) => {
|
|
386
|
+
for (const op of ops) {
|
|
387
|
+
if (op.type === "put") store.set(op.key, op.value);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
iterator: () => (async function* () {})(),
|
|
391
|
+
},
|
|
392
|
+
fetchImpl: async (_url, init) => {
|
|
393
|
+
if (init?.signal?.aborted) {
|
|
394
|
+
throw init.signal.reason ?? new Error("aborted");
|
|
395
|
+
}
|
|
396
|
+
await new Promise((_, reject) => {
|
|
397
|
+
init?.signal?.addEventListener("abort", () => reject(init.signal?.reason ?? new Error("aborted")));
|
|
398
|
+
});
|
|
399
|
+
throw new Error("unreachable");
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await expect(runLocalAgentTurn({
|
|
404
|
+
adapter,
|
|
405
|
+
agentRef: "mimo",
|
|
406
|
+
input: "hello",
|
|
407
|
+
timeoutMs: 10,
|
|
408
|
+
})).rejects.toThrow(/timed out|aborted/i);
|
|
409
|
+
});
|
|
410
|
+
|
|
418
411
|
test("uses loopback transport for localhost custom providers when the default fetch path cannot connect", async () => {
|
|
419
412
|
const store = new Map<string, any>([
|
|
420
413
|
["agent-user-1-localhost", {
|
|
@@ -746,10 +739,6 @@ describe("CLI local runtime adapter", () => {
|
|
|
746
739
|
id: "shell",
|
|
747
740
|
prompt: "Use shell.",
|
|
748
741
|
model: "gpt-4.1-mini",
|
|
749
|
-
runtimeToolPolicy: {
|
|
750
|
-
version: 1,
|
|
751
|
-
runtimeTools: ["execShell"],
|
|
752
|
-
},
|
|
753
742
|
}],
|
|
754
743
|
]);
|
|
755
744
|
const adapter = createCliLocalRuntimeAdapter({
|
|
@@ -1054,28 +1043,17 @@ describe("CLI local runtime adapter", () => {
|
|
|
1054
1043
|
});
|
|
1055
1044
|
});
|
|
1056
1045
|
|
|
1057
|
-
test("rejects
|
|
1046
|
+
test("rejects tools unless the local tool policy allows and registers them", async () => {
|
|
1058
1047
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1059
|
-
env: {
|
|
1060
|
-
hostRuntimeConfig: createCliHostRuntimeConfig(),
|
|
1061
|
-
db: {
|
|
1062
|
-
get: async () => ({
|
|
1063
|
-
dbKey: "agent-user-1-shell",
|
|
1064
|
-
toolNames: ["execShell"],
|
|
1065
|
-
}),
|
|
1066
|
-
put: async () => {},
|
|
1067
|
-
batch: async () => {},
|
|
1068
|
-
iterator: () => (async function* () {})(),
|
|
1069
|
-
},
|
|
1048
|
+
env: {},
|
|
1070
1049
|
fetchImpl: async () => Response.json({}),
|
|
1071
1050
|
});
|
|
1072
1051
|
|
|
1073
|
-
await adapter.loadAgentConfig("shell");
|
|
1074
1052
|
await expect(adapter.executeTool({
|
|
1075
1053
|
id: "call-1",
|
|
1076
1054
|
name: "execShell",
|
|
1077
1055
|
arguments: "{}",
|
|
1078
|
-
})).rejects.toThrow("execShell
|
|
1056
|
+
})).rejects.toThrow("execShell requires NOLO_LOCAL_SHELL_MODE");
|
|
1079
1057
|
});
|
|
1080
1058
|
|
|
1081
1059
|
test("executes explicitly allowed registered local tools declared by the agent", async () => {
|
|
@@ -1117,22 +1095,19 @@ describe("CLI local runtime adapter", () => {
|
|
|
1117
1095
|
expect(result.content).toContain("README.md");
|
|
1118
1096
|
});
|
|
1119
1097
|
|
|
1120
|
-
test("
|
|
1098
|
+
test("advertises execShell to OpenAI-compatible providers when shell mode is enabled", async () => {
|
|
1121
1099
|
const requests: Array<{ body: any }> = [];
|
|
1122
1100
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1123
1101
|
env: {
|
|
1124
1102
|
OPENAI_API_KEY: "sk-local",
|
|
1125
1103
|
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1104
|
+
NOLO_LOCAL_SHELL_MODE: "worktree",
|
|
1126
1105
|
},
|
|
1127
1106
|
db: {
|
|
1128
1107
|
get: async () => ({
|
|
1129
1108
|
dbKey: "agent-local-shell",
|
|
1130
1109
|
prompt: "Use shell.",
|
|
1131
1110
|
model: "gpt-4.1-mini",
|
|
1132
|
-
runtimeToolPolicy: {
|
|
1133
|
-
version: 1,
|
|
1134
|
-
runtimeTools: ["execShell"],
|
|
1135
|
-
},
|
|
1136
1111
|
}),
|
|
1137
1112
|
put: async () => {},
|
|
1138
1113
|
batch: async () => {},
|
|
@@ -1155,81 +1130,6 @@ describe("CLI local runtime adapter", () => {
|
|
|
1155
1130
|
expect(toolNamesFromRequest(requests[0])).toEqual(SHELL_LOCAL_CODING_TOOL_NAMES);
|
|
1156
1131
|
});
|
|
1157
1132
|
|
|
1158
|
-
test("keeps advertised fallback workspace tools executable when shell is explicitly disabled", async () => {
|
|
1159
|
-
const workspaceRoot = mkdtempSync(join(tmpdir(), "nolo-cli-runtime-shell-off-"));
|
|
1160
|
-
try {
|
|
1161
|
-
const adapter = createCliLocalRuntimeAdapter({
|
|
1162
|
-
env: {},
|
|
1163
|
-
hostRuntimeConfig: createCliHostRuntimeConfig(false),
|
|
1164
|
-
db: {
|
|
1165
|
-
get: async () => ({
|
|
1166
|
-
dbKey: "agent-local-shell-off",
|
|
1167
|
-
runtimeToolPolicy: {
|
|
1168
|
-
version: 1,
|
|
1169
|
-
runtimeTools: ["execShell"],
|
|
1170
|
-
},
|
|
1171
|
-
}),
|
|
1172
|
-
put: async () => {},
|
|
1173
|
-
batch: async () => {},
|
|
1174
|
-
iterator: () => (async function* () {})(),
|
|
1175
|
-
},
|
|
1176
|
-
cwd: workspaceRoot,
|
|
1177
|
-
fetchImpl: async () => Response.json({}),
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
await adapter.loadAgentConfig("shell-off");
|
|
1181
|
-
const result = await adapter.executeTool({
|
|
1182
|
-
id: "call-list-files",
|
|
1183
|
-
name: "listFiles",
|
|
1184
|
-
arguments: JSON.stringify({ path: "." }),
|
|
1185
|
-
});
|
|
1186
|
-
|
|
1187
|
-
expect(result.metadata).toMatchObject({ path: ".", count: 0 });
|
|
1188
|
-
expect(result.content).toBe("");
|
|
1189
|
-
} finally {
|
|
1190
|
-
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1191
|
-
}
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
test("does not advertise execShell when only a legacy runtimeToolPolicy.shell block is present", async () => {
|
|
1195
|
-
const requests: Array<{ body: any }> = [];
|
|
1196
|
-
const adapter = createCliLocalRuntimeAdapter({
|
|
1197
|
-
env: {
|
|
1198
|
-
OPENAI_API_KEY: "sk-local",
|
|
1199
|
-
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1200
|
-
},
|
|
1201
|
-
hostRuntimeConfig: createCliHostRuntimeConfig(true),
|
|
1202
|
-
db: {
|
|
1203
|
-
get: async () => ({
|
|
1204
|
-
dbKey: "agent-local-legacy-shell",
|
|
1205
|
-
prompt: "Do not get shell from legacy policy.",
|
|
1206
|
-
model: "gpt-4.1-mini",
|
|
1207
|
-
runtimeToolPolicy: {
|
|
1208
|
-
version: 1,
|
|
1209
|
-
shell: { enabled: true, mode: "worktree" },
|
|
1210
|
-
},
|
|
1211
|
-
}),
|
|
1212
|
-
put: async () => {},
|
|
1213
|
-
batch: async () => {},
|
|
1214
|
-
iterator: () => (async function* () {})(),
|
|
1215
|
-
},
|
|
1216
|
-
fetchImpl: async (_url, init) => {
|
|
1217
|
-
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1218
|
-
return Response.json({
|
|
1219
|
-
choices: [{ message: { content: "done" } }],
|
|
1220
|
-
});
|
|
1221
|
-
},
|
|
1222
|
-
});
|
|
1223
|
-
|
|
1224
|
-
await runLocalAgentTurn({
|
|
1225
|
-
adapter,
|
|
1226
|
-
agentRef: "legacy-shell",
|
|
1227
|
-
input: "pwd",
|
|
1228
|
-
});
|
|
1229
|
-
|
|
1230
|
-
expect(toolNamesFromRequest(requests[0])).not.toContain("execShell");
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
1133
|
test("allows default semantic workspace tools without legacy agent tool declarations", async () => {
|
|
1234
1134
|
const workspaceRoot = mkdtempSync(join(tmpdir(), "nolo-cli-runtime-"));
|
|
1235
1135
|
try {
|
|
@@ -1391,17 +1291,13 @@ describe("CLI local runtime adapter", () => {
|
|
|
1391
1291
|
}
|
|
1392
1292
|
});
|
|
1393
1293
|
|
|
1394
|
-
test("runs execShell locally
|
|
1294
|
+
test("runs execShell locally in explicit worktree shell mode", async () => {
|
|
1395
1295
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1396
|
-
env: {},
|
|
1397
|
-
hostRuntimeConfig: createCliHostRuntimeConfig(true),
|
|
1296
|
+
env: { NOLO_LOCAL_SHELL_MODE: "worktree" },
|
|
1398
1297
|
db: {
|
|
1399
1298
|
get: async () => ({
|
|
1400
1299
|
dbKey: "agent-local-shell",
|
|
1401
|
-
|
|
1402
|
-
version: 1,
|
|
1403
|
-
runtimeTools: ["execShell"],
|
|
1404
|
-
},
|
|
1300
|
+
toolNames: ["execShell"],
|
|
1405
1301
|
}),
|
|
1406
1302
|
put: async () => {},
|
|
1407
1303
|
batch: async () => {},
|
|
@@ -1428,6 +1324,235 @@ describe("CLI local runtime adapter", () => {
|
|
|
1428
1324
|
expect(result.metadata).toMatchObject({ exitCode: 0 });
|
|
1429
1325
|
});
|
|
1430
1326
|
|
|
1327
|
+
test("accepts execShell aliases from the local model", async () => {
|
|
1328
|
+
const requests: Array<{ body: any }> = [];
|
|
1329
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1330
|
+
env: {
|
|
1331
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1332
|
+
NOLO_LOCAL_SHELL_MODE: "worktree",
|
|
1333
|
+
},
|
|
1334
|
+
db: {
|
|
1335
|
+
get: async () => ({
|
|
1336
|
+
dbKey: "agent-local-shell-alias",
|
|
1337
|
+
prompt: "Use shell when needed.",
|
|
1338
|
+
model: "qwen-coder",
|
|
1339
|
+
toolNames: ["execShell"],
|
|
1340
|
+
}),
|
|
1341
|
+
put: async () => {},
|
|
1342
|
+
batch: async () => {},
|
|
1343
|
+
iterator: () => (async function* () {})(),
|
|
1344
|
+
},
|
|
1345
|
+
cwd: import.meta.dir,
|
|
1346
|
+
localToolExecutors: {
|
|
1347
|
+
execShell: async () => ({
|
|
1348
|
+
content: import.meta.dir,
|
|
1349
|
+
metadata: { exitCode: 0, timedOut: false },
|
|
1350
|
+
}),
|
|
1351
|
+
},
|
|
1352
|
+
fetchImpl: async (_url, init) => {
|
|
1353
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1354
|
+
if (requests.length === 1) {
|
|
1355
|
+
return Response.json({
|
|
1356
|
+
choices: [{
|
|
1357
|
+
message: {
|
|
1358
|
+
content: "",
|
|
1359
|
+
tool_calls: [{
|
|
1360
|
+
id: "call-1",
|
|
1361
|
+
type: "function",
|
|
1362
|
+
function: { name: "runCommand", arguments: "{\"cmd\":\"pwd\"}" },
|
|
1363
|
+
}],
|
|
1364
|
+
},
|
|
1365
|
+
}],
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
return Response.json({
|
|
1369
|
+
choices: [{ message: { content: "alias ok" } }],
|
|
1370
|
+
});
|
|
1371
|
+
},
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
const result = await runLocalAgentTurn({
|
|
1375
|
+
adapter,
|
|
1376
|
+
agentRef: "shell",
|
|
1377
|
+
input: "print cwd",
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
expect(result.content).toBe("alias ok");
|
|
1381
|
+
expect(requests[1]?.body.messages.at(-1)?.content).toContain(import.meta.dir);
|
|
1382
|
+
});
|
|
1383
|
+
|
|
1384
|
+
test("blocks destructive local execShell calls unless the user explicitly asked to delete", async () => {
|
|
1385
|
+
let executorCalls = 0;
|
|
1386
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1387
|
+
env: {
|
|
1388
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1389
|
+
NOLO_LOCAL_SHELL_MODE: "worktree",
|
|
1390
|
+
},
|
|
1391
|
+
db: {
|
|
1392
|
+
get: async () => ({
|
|
1393
|
+
dbKey: "agent-local-shell-guard",
|
|
1394
|
+
prompt: "Use shell when useful.",
|
|
1395
|
+
model: "qwen-coder",
|
|
1396
|
+
toolNames: ["execShell"],
|
|
1397
|
+
}),
|
|
1398
|
+
put: async () => {},
|
|
1399
|
+
batch: async () => {},
|
|
1400
|
+
iterator: () => (async function* () {})(),
|
|
1401
|
+
},
|
|
1402
|
+
cwd: import.meta.dir,
|
|
1403
|
+
localToolExecutors: {
|
|
1404
|
+
execShell: async () => {
|
|
1405
|
+
executorCalls += 1;
|
|
1406
|
+
return {
|
|
1407
|
+
content: "should not run",
|
|
1408
|
+
};
|
|
1409
|
+
},
|
|
1410
|
+
},
|
|
1411
|
+
fetchImpl: async (_url, init) => {
|
|
1412
|
+
const body = JSON.parse(String(init?.body));
|
|
1413
|
+
const lastToolMessage = [...body.messages]
|
|
1414
|
+
.reverse()
|
|
1415
|
+
.find((message: any) => message.role === "tool");
|
|
1416
|
+
if (lastToolMessage) {
|
|
1417
|
+
expect(String(lastToolMessage.content)).toContain(
|
|
1418
|
+
"destructive_action_requires_confirmation",
|
|
1419
|
+
);
|
|
1420
|
+
return Response.json({
|
|
1421
|
+
choices: [{ message: { content: "guard ok" } }],
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return Response.json({
|
|
1425
|
+
choices: [{
|
|
1426
|
+
message: {
|
|
1427
|
+
content: "",
|
|
1428
|
+
tool_calls: [{
|
|
1429
|
+
id: "call-1",
|
|
1430
|
+
type: "function",
|
|
1431
|
+
function: {
|
|
1432
|
+
name: "execShell",
|
|
1433
|
+
arguments: "{\"cmd\":\"rm -rf ./tmp\"}",
|
|
1434
|
+
},
|
|
1435
|
+
}],
|
|
1436
|
+
},
|
|
1437
|
+
}],
|
|
1438
|
+
});
|
|
1439
|
+
},
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
const result = await runLocalAgentTurn({
|
|
1443
|
+
adapter,
|
|
1444
|
+
agentRef: "shell",
|
|
1445
|
+
input: "inspect cwd but don't delete files",
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
expect(result.content).toBe("guard ok");
|
|
1449
|
+
expect(executorCalls).toBe(0);
|
|
1450
|
+
});
|
|
1451
|
+
|
|
1452
|
+
test("exposes runtime policy tools to the local model even when toolNames omits them", async () => {
|
|
1453
|
+
const requests: Array<{ body: any }> = [];
|
|
1454
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1455
|
+
env: {
|
|
1456
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1457
|
+
NOLO_LOCAL_SHELL_MODE: "worktree",
|
|
1458
|
+
},
|
|
1459
|
+
db: {
|
|
1460
|
+
get: async () => ({
|
|
1461
|
+
dbKey: "agent-local-policy-shell",
|
|
1462
|
+
prompt: "Use shell when needed.",
|
|
1463
|
+
model: "qwen-coder",
|
|
1464
|
+
runtimeToolPolicy: {
|
|
1465
|
+
version: 1,
|
|
1466
|
+
runtimeTools: ["execShell"],
|
|
1467
|
+
},
|
|
1468
|
+
}),
|
|
1469
|
+
put: async () => {},
|
|
1470
|
+
batch: async () => {},
|
|
1471
|
+
iterator: () => (async function* () {})(),
|
|
1472
|
+
},
|
|
1473
|
+
cwd: import.meta.dir,
|
|
1474
|
+
localToolExecutors: {
|
|
1475
|
+
execShell: async () => ({
|
|
1476
|
+
content: import.meta.dir,
|
|
1477
|
+
metadata: { exitCode: 0, timedOut: false },
|
|
1478
|
+
}),
|
|
1479
|
+
},
|
|
1480
|
+
fetchImpl: async (_url, init) => {
|
|
1481
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1482
|
+
if (requests.length === 1) {
|
|
1483
|
+
return Response.json({
|
|
1484
|
+
choices: [{
|
|
1485
|
+
message: {
|
|
1486
|
+
content: "",
|
|
1487
|
+
tool_calls: [{
|
|
1488
|
+
id: "call-1",
|
|
1489
|
+
type: "function",
|
|
1490
|
+
function: { name: "execShell", arguments: "{\"cmd\":\"pwd\"}" },
|
|
1491
|
+
}],
|
|
1492
|
+
},
|
|
1493
|
+
}],
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
return Response.json({
|
|
1497
|
+
choices: [{ message: { content: "policy shell ok" } }],
|
|
1498
|
+
});
|
|
1499
|
+
},
|
|
1500
|
+
});
|
|
1501
|
+
|
|
1502
|
+
const result = await runLocalAgentTurn({
|
|
1503
|
+
adapter,
|
|
1504
|
+
agentRef: "shell",
|
|
1505
|
+
input: "print cwd",
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
expect(result.content).toBe("policy shell ok");
|
|
1509
|
+
expect(toolNamesFromRequest(requests[0])).toContain("execShell");
|
|
1510
|
+
expect(requests[1]?.body.messages.at(-1)?.content).toContain(import.meta.dir);
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1513
|
+
test("treats runtime policy visual tools as declared local tools", async () => {
|
|
1514
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1515
|
+
env: {
|
|
1516
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1517
|
+
},
|
|
1518
|
+
db: {
|
|
1519
|
+
get: async () => ({
|
|
1520
|
+
dbKey: "agent-local-visual",
|
|
1521
|
+
prompt: "Inspect the page.",
|
|
1522
|
+
model: "qwen-coder",
|
|
1523
|
+
runtimeToolPolicy: {
|
|
1524
|
+
version: 1,
|
|
1525
|
+
runtimeTools: ["captureVisualState"],
|
|
1526
|
+
},
|
|
1527
|
+
}),
|
|
1528
|
+
put: async () => {},
|
|
1529
|
+
batch: async () => {},
|
|
1530
|
+
iterator: () => (async function* () {})(),
|
|
1531
|
+
},
|
|
1532
|
+
cwd: import.meta.dir,
|
|
1533
|
+
localToolExecutors: {
|
|
1534
|
+
captureVisualState: async () => ({
|
|
1535
|
+
content: "{\"status\":\"ok\"}",
|
|
1536
|
+
}),
|
|
1537
|
+
},
|
|
1538
|
+
fetchImpl: async (_url, init) => {
|
|
1539
|
+
expect(toolNamesFromRequest({ body: JSON.parse(String(init?.body)) })).toContain("captureVisualState");
|
|
1540
|
+
return Response.json({
|
|
1541
|
+
choices: [{ message: { content: "visual ok" } }],
|
|
1542
|
+
});
|
|
1543
|
+
},
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
await adapter.loadAgentConfig("visual");
|
|
1547
|
+
await expect(adapter.executeTool({
|
|
1548
|
+
id: "call-visual",
|
|
1549
|
+
name: "captureVisualState",
|
|
1550
|
+
arguments: JSON.stringify({ waitSelector: "body" }),
|
|
1551
|
+
})).resolves.toMatchObject({
|
|
1552
|
+
content: "{\"status\":\"ok\"}",
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1431
1556
|
test("runs allowed workspace file tools through default local executors", async () => {
|
|
1432
1557
|
const workspaceRoot = mkdtempSync(join(tmpdir(), "nolo-cli-runtime-"));
|
|
1433
1558
|
try {
|