nolo-cli 0.1.41 → 0.1.43
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 +27 -12
- package/agent-runtime/agentThread.ts +222 -0
- package/agent-runtime/agentThreadAdmission.ts +74 -0
- package/agent-runtime/dialogWritePlan.ts +42 -0
- package/agent-runtime/hostAdapter.ts +1 -0
- package/agent-runtime/index.ts +43 -0
- package/agent-runtime/localLoop.ts +31 -2
- package/agent-runtime/localToolPolicy.ts +1 -0
- package/agent-runtime/localWorkspaceTools.ts +1258 -78
- package/agent-runtime/noloWorkspaceTools.ts +20 -1
- package/agent-runtime/runtimeToolPolicy.ts +1 -5
- package/agent-runtime/runtimeToolSurface.ts +1 -0
- package/agentAliases.ts +29 -9
- package/agentInternalCommandEntries.ts +11 -3
- package/agentMachineCommands.ts +1 -0
- package/agentRecordCommands.ts +2 -2
- package/agentRecordHelpers.ts +59 -6
- package/agentRunCommand.ts +84 -19
- package/ai/agent/buildSystemPrompt.ts +14 -0
- package/ai/agent/cliExecutor.ts +202 -43
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/liveAudioModel.ts +1 -1
- package/ai/agent/streamAgentChatTurn.ts +154 -20
- package/ai/agent/streamAgentChatTurnUtils.ts +63 -0
- package/ai/ai.locale.ts +70 -0
- package/ai/chat/inlineImageUrlsForCustomProvider.ts +7 -2
- package/ai/chat/sendOpenAICompletionsRequest.ts +25 -1
- package/ai/llm/kimi.ts +0 -1
- package/ai/llm/openrouterModels.ts +27 -2
- package/ai/memory/policy.ts +49 -0
- package/ai/memory/queryShared.ts +10 -4
- package/ai/memory/recentRelationshipRecap.ts +4 -0
- package/ai/memory/remember.ts +48 -104
- package/ai/memory/scope.ts +74 -0
- package/ai/memory/types.ts +6 -0
- package/ai/memory/understanding.ts +10 -18
- package/ai/policy/runtimePolicy.ts +95 -23
- package/ai/tools/agent/agentTools.ts +0 -21
- package/ai/tools/agent/startAgentDialogTool.ts +2 -1
- package/ai/tools/index.ts +2 -20
- package/ai/tools/prepareTools.ts +171 -1
- package/ai/tools/readXhsProfileTool.ts +80 -69
- package/ai/tools/table/createTableTool.ts +11 -0
- package/ai/tools/toolPacks.ts +19 -0
- package/ai/types.ts +3 -0
- package/app/favorite/deletedFavoriteProjection.ts +31 -0
- package/app/hooks/deleteDbKey.ts +24 -3
- package/app/i18n/translations/interface.locale.ts +48 -16
- package/app/types/appSummary.ts +4 -0
- package/app/utils/desktopAgentRuntimeTurnClient.ts +2 -0
- package/auth/adminPermissions.ts +64 -4
- package/auth/routes.ts +5 -0
- package/chat/chat.locale.ts +4 -0
- package/chat/dialog/actions/handleSendMessageAction.ts +7 -2
- package/chat/dialog/dialogAttachmentCleanup.ts +135 -0
- package/chat/dialog/dialogSlice.ts +117 -4
- package/chat/messages/activityCompletion.ts +97 -0
- package/chat/messages/imagePayloadPersistence.ts +22 -0
- package/chat/messages/messageContent.ts +20 -16
- package/chat/messages/messageSlice.ts +20 -1
- package/chat/messages/toolThunks.ts +32 -11
- package/chat/messages/types.ts +8 -1
- package/chat/messages/web/toolDisplayName.ts +686 -0
- package/cli/agentAliases.ts +29 -9
- package/cli/agentInternalCommandEntries.ts +11 -3
- package/cli/agentMachineCommands.ts +1 -0
- package/cli/agentRecordCommands.ts +2 -2
- package/cli/agentRecordHelpers.ts +59 -6
- package/cli/agentRunCommand.ts +84 -19
- package/cli/client/agentRun.ts +65 -253
- package/cli/client/localRuntimeAdapter.ts +179 -53
- package/cli/commandRegistry.ts +7 -4
- package/cli/dialogAttachmentCleanup.ts +151 -0
- package/cli/dialogCommands.ts +163 -61
- package/cli/dialogInternalCommandEntries.ts +1 -1
- package/cli/docCreateCommands.ts +345 -0
- package/cli/docInternalCommandEntries.ts +11 -0
- package/cli/docPageHelpers.ts +80 -0
- package/cli/docSkillHelpers.ts +67 -0
- package/cli/docSpaceHelpers.ts +46 -0
- package/cli/internalCommandEntries.ts +4 -0
- package/cli/machineCommands.ts +18 -34
- package/cli/memoryCommands.ts +253 -0
- package/cli/memoryInternalCommandEntries.ts +9 -0
- package/cli/qoderUsageProbe.ts +252 -0
- package/cli/scriptCommandEntries.ts +0 -3
- package/cli/secretScan.ts +31 -0
- package/cli/tableCommands.ts +248 -13
- package/cli/workflowInternalCommandEntries.ts +2 -3
- package/client/agentRun.test.ts +68 -84
- package/client/agentRun.ts +65 -253
- package/client/localRuntimeAdapter.test.ts +505 -74
- package/client/localRuntimeAdapter.ts +179 -53
- package/commandRegistry.ts +7 -4
- package/connector-experimental/capabilities.ts +10 -9
- package/create/space/sidebarVisibleTypes.ts +19 -0
- package/dialogAttachmentCleanup.ts +151 -0
- package/dialogCommands.ts +163 -61
- package/dialogInternalCommandEntries.ts +1 -1
- package/docCreateCommands.ts +345 -0
- package/docInternalCommandEntries.ts +11 -0
- package/docPageHelpers.ts +80 -0
- package/docSkillHelpers.ts +67 -0
- package/docSpaceHelpers.ts +46 -0
- package/integrations/google/models.ts +4 -4
- package/integrations/openai/generateOpenAIRequestBody.ts +0 -3
- package/integrations/xhs-reader/types.ts +36 -3
- package/integrations/xhs-reader/url.ts +24 -1
- package/internalCommandEntries.ts +4 -0
- package/machineCommands.ts +18 -34
- package/memoryCommands.ts +253 -0
- package/memoryInternalCommandEntries.ts +9 -0
- package/package.json +141 -114
- package/qoderUsageProbe.ts +252 -0
- package/render/contentIcon/types.ts +167 -0
- package/render/page/docSlice.ts +30 -3
- package/render/page/types.ts +2 -0
- package/render/table/createTableAction.ts +4 -0
- package/render/table/tableSlice.ts +61 -0
- package/render/table/types.ts +10 -0
- package/scriptCommandEntries.ts +0 -3
- package/secretScan.ts +31 -0
- package/server/handlers/agentRun/types.ts +24 -23
- package/tableCommands.ts +248 -13
- package/workflowInternalCommandEntries.ts +2 -3
- package/ai/tools/agent/taskRunTool.ts +0 -112
- package/cli/client/taskRunPrompt.ts +0 -27
- package/cli/taskRunCommand.ts +0 -265
- package/client/taskRunPrompt.ts +0 -27
- package/integrations/xhs-reader/analyze/profileAnalyzer.ts +0 -205
- package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +0 -624
- package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +0 -227
- package/integrations/xhs-reader/client/xhsWebApiClient.ts +0 -229
- package/integrations/xhs-reader/normalize.ts +0 -390
- package/integrations/xhs-reader/orchestrator.ts +0 -178
- package/taskRunCommand.ts +0 -265
|
@@ -4,7 +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
|
+
import { LOCAL_CODEX_AGENT_KEY, LOCAL_QODER_AGENT_KEY, MIMO_MONTH_AGENT_KEY } from "../agentAliases";
|
|
8
8
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
9
9
|
|
|
10
10
|
describe("CLI local runtime adapter", () => {
|
|
@@ -13,6 +13,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
13
13
|
"readFile",
|
|
14
14
|
"writeFile",
|
|
15
15
|
"editFile",
|
|
16
|
+
"globFiles",
|
|
16
17
|
"searchFiles",
|
|
17
18
|
"execShell",
|
|
18
19
|
];
|
|
@@ -21,6 +22,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
21
22
|
"readFile",
|
|
22
23
|
"writeFile",
|
|
23
24
|
"editFile",
|
|
25
|
+
"globFiles",
|
|
24
26
|
"searchFiles",
|
|
25
27
|
"execShell",
|
|
26
28
|
];
|
|
@@ -36,6 +38,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
36
38
|
"readSpace",
|
|
37
39
|
"readDoc",
|
|
38
40
|
"readSkillDoc",
|
|
41
|
+
"listTables",
|
|
39
42
|
"queryTableRows",
|
|
40
43
|
"cliWhoami",
|
|
41
44
|
"cliDoctor",
|
|
@@ -49,6 +52,87 @@ describe("CLI local runtime adapter", () => {
|
|
|
49
52
|
return request?.body?.tools?.map((tool: any) => tool.function.name) ?? [];
|
|
50
53
|
}
|
|
51
54
|
|
|
55
|
+
function publicSchemaKeys(schema: any) {
|
|
56
|
+
return Object.keys(schema.parameters.properties).filter((key) => key !== "_activity");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test("loads stored local CLI agent records before falling back to built-ins", async () => {
|
|
60
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
61
|
+
env: {
|
|
62
|
+
NOLO_LOCAL_USER_ID: "0e95801d90",
|
|
63
|
+
},
|
|
64
|
+
db: {
|
|
65
|
+
get: async (key) => {
|
|
66
|
+
if (key !== LOCAL_CODEX_AGENT_KEY) throw new Error(`not found: ${key}`);
|
|
67
|
+
return {
|
|
68
|
+
dbKey: LOCAL_CODEX_AGENT_KEY,
|
|
69
|
+
id: "01LOCALCODEXCLI000000NEW",
|
|
70
|
+
name: "User Edited Local Codex",
|
|
71
|
+
prompt: "User-owned local Codex prompt.",
|
|
72
|
+
apiSource: "cli",
|
|
73
|
+
provider: "cli",
|
|
74
|
+
cliProvider: "codex",
|
|
75
|
+
model: "gpt-5.4",
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
put: async () => {},
|
|
79
|
+
batch: async () => {},
|
|
80
|
+
iterator: () => (async function* () {})(),
|
|
81
|
+
},
|
|
82
|
+
fetchImpl: async () => new Response("not found", { status: 404 }),
|
|
83
|
+
} as any);
|
|
84
|
+
|
|
85
|
+
await expect(adapter.loadAgentConfig(LOCAL_CODEX_AGENT_KEY)).resolves.toMatchObject({
|
|
86
|
+
key: LOCAL_CODEX_AGENT_KEY,
|
|
87
|
+
name: "User Edited Local Codex",
|
|
88
|
+
prompt: "User-owned local Codex prompt.",
|
|
89
|
+
apiSource: "cli",
|
|
90
|
+
provider: "cli",
|
|
91
|
+
cliProvider: "codex",
|
|
92
|
+
model: "gpt-5.4",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("falls back to built-in local Codex and Qoder CLI agents without machine binding", async () => {
|
|
97
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
98
|
+
env: {
|
|
99
|
+
NOLO_LOCAL_USER_ID: "user-1",
|
|
100
|
+
},
|
|
101
|
+
db: {
|
|
102
|
+
get: async (key) => {
|
|
103
|
+
throw new Error(`not found: ${key}`);
|
|
104
|
+
},
|
|
105
|
+
put: async () => {},
|
|
106
|
+
batch: async () => {},
|
|
107
|
+
iterator: () => (async function* () {})(),
|
|
108
|
+
},
|
|
109
|
+
fetchImpl: async () => new Response("not found", { status: 404 }),
|
|
110
|
+
} as any);
|
|
111
|
+
|
|
112
|
+
const codex = await adapter.loadAgentConfig(LOCAL_CODEX_AGENT_KEY);
|
|
113
|
+
const qoder = await adapter.loadAgentConfig(LOCAL_QODER_AGENT_KEY);
|
|
114
|
+
|
|
115
|
+
expect(codex).toMatchObject({
|
|
116
|
+
key: LOCAL_CODEX_AGENT_KEY,
|
|
117
|
+
name: "Local Codex",
|
|
118
|
+
apiSource: "cli",
|
|
119
|
+
provider: "cli",
|
|
120
|
+
cliProvider: "codex",
|
|
121
|
+
});
|
|
122
|
+
expect(qoder).toMatchObject({
|
|
123
|
+
key: LOCAL_QODER_AGENT_KEY,
|
|
124
|
+
name: "Local Qoder",
|
|
125
|
+
apiSource: "cli",
|
|
126
|
+
provider: "cli",
|
|
127
|
+
cliProvider: "qoder",
|
|
128
|
+
model: "Qwen3.7-Max",
|
|
129
|
+
});
|
|
130
|
+
expect((codex as any)?.runtimeBinding).toBeUndefined();
|
|
131
|
+
expect((qoder as any)?.runtimeBinding).toBeUndefined();
|
|
132
|
+
expect((codex as any)?.rawRecord?.runtimeBinding).toBeUndefined();
|
|
133
|
+
expect((qoder as any)?.rawRecord?.runtimeBinding).toBeUndefined();
|
|
134
|
+
});
|
|
135
|
+
|
|
52
136
|
test("runs cli-provider agents through the local CLI executor instead of OpenAI-compatible direct mode", async () => {
|
|
53
137
|
const cliExecutions: Array<{ provider: string; prompt: string; options: any }> = [];
|
|
54
138
|
const adapter = createCliLocalRuntimeAdapter({
|
|
@@ -1324,7 +1408,6 @@ describe("CLI local runtime adapter", () => {
|
|
|
1324
1408
|
"execShell",
|
|
1325
1409
|
"checkEnv",
|
|
1326
1410
|
"queryTableRows",
|
|
1327
|
-
"taskRun",
|
|
1328
1411
|
],
|
|
1329
1412
|
}),
|
|
1330
1413
|
put: async () => {},
|
|
@@ -1348,52 +1431,58 @@ describe("CLI local runtime adapter", () => {
|
|
|
1348
1431
|
expect(toolNamesFromRequest(requests[0])).toEqual(SHELL_LOCAL_CODING_TOOL_NAMES);
|
|
1349
1432
|
});
|
|
1350
1433
|
|
|
1351
|
-
test("
|
|
1352
|
-
const
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
},
|
|
1368
|
-
|
|
1369
|
-
|
|
1434
|
+
test("can expose only declared local workspace tools for tool ablations", async () => {
|
|
1435
|
+
const requests: Array<{ body: any }> = [];
|
|
1436
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1437
|
+
env: {
|
|
1438
|
+
OPENAI_API_KEY: "sk-local",
|
|
1439
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1440
|
+
NOLO_LOCAL_WORKSPACE_TOOLSET: "declared-only",
|
|
1441
|
+
},
|
|
1442
|
+
db: {
|
|
1443
|
+
get: async () => ({
|
|
1444
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1445
|
+
prompt: "Use exactly the declared local tools.",
|
|
1446
|
+
model: "mimo-v2.5-pro",
|
|
1447
|
+
provider: "custom",
|
|
1448
|
+
tools: ["listFiles", "readFile", "execShell"],
|
|
1449
|
+
}),
|
|
1450
|
+
put: async () => {},
|
|
1451
|
+
batch: async () => {},
|
|
1452
|
+
iterator: () => (async function* () {})(),
|
|
1453
|
+
},
|
|
1454
|
+
fetchImpl: async (_url, init) => {
|
|
1455
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1456
|
+
return Response.json({
|
|
1457
|
+
choices: [{ message: { content: "done" } }],
|
|
1458
|
+
});
|
|
1459
|
+
},
|
|
1460
|
+
});
|
|
1370
1461
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
});
|
|
1379
|
-
} finally {
|
|
1380
|
-
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1381
|
-
}
|
|
1462
|
+
await runLocalAgentTurn({
|
|
1463
|
+
adapter,
|
|
1464
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1465
|
+
input: "inspect cwd",
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
expect(toolNamesFromRequest(requests[0])).toEqual(["listFiles", "readFile", "execShell"]);
|
|
1382
1469
|
});
|
|
1383
1470
|
|
|
1384
|
-
test("
|
|
1471
|
+
test("defaults local workspace tools to strategy descriptions and rich parameters", async () => {
|
|
1385
1472
|
const requests: Array<{ body: any }> = [];
|
|
1386
1473
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1387
1474
|
env: {
|
|
1388
1475
|
OPENAI_API_KEY: "sk-local",
|
|
1389
1476
|
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1477
|
+
NOLO_LOCAL_WORKSPACE_TOOLSET: "declared-only",
|
|
1390
1478
|
},
|
|
1391
1479
|
db: {
|
|
1392
1480
|
get: async () => ({
|
|
1393
|
-
dbKey:
|
|
1394
|
-
prompt: "Use
|
|
1395
|
-
model: "
|
|
1396
|
-
|
|
1481
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1482
|
+
prompt: "Use local coding tools.",
|
|
1483
|
+
model: "mimo-v2.5-pro",
|
|
1484
|
+
provider: "custom",
|
|
1485
|
+
tools: ["listFiles", "readFile", "globFiles", "searchFiles"],
|
|
1397
1486
|
}),
|
|
1398
1487
|
put: async () => {},
|
|
1399
1488
|
batch: async () => {},
|
|
@@ -1409,61 +1498,272 @@ describe("CLI local runtime adapter", () => {
|
|
|
1409
1498
|
|
|
1410
1499
|
await runLocalAgentTurn({
|
|
1411
1500
|
adapter,
|
|
1412
|
-
agentRef:
|
|
1413
|
-
input: "
|
|
1501
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1502
|
+
input: "inspect cwd",
|
|
1414
1503
|
});
|
|
1415
1504
|
|
|
1416
|
-
|
|
1505
|
+
const tools = new Map(
|
|
1506
|
+
requests[0]?.body.tools.map((tool: any) => [tool.function.name, tool.function]),
|
|
1507
|
+
);
|
|
1508
|
+
const listFiles = tools.get("listFiles") as any;
|
|
1509
|
+
const readFile = tools.get("readFile") as any;
|
|
1510
|
+
const globFiles = tools.get("globFiles") as any;
|
|
1511
|
+
const searchFiles = tools.get("searchFiles") as any;
|
|
1512
|
+
|
|
1513
|
+
expect(listFiles.description).toContain("not for path-pattern discovery across the repo");
|
|
1514
|
+
expect(publicSchemaKeys(listFiles)).toEqual([
|
|
1515
|
+
"path",
|
|
1516
|
+
"maxDepth",
|
|
1517
|
+
"maxResults",
|
|
1518
|
+
"entryType",
|
|
1519
|
+
]);
|
|
1520
|
+
expect(readFile.description).toContain("Use line ranges");
|
|
1521
|
+
expect(publicSchemaKeys(readFile)).toEqual([
|
|
1522
|
+
"path",
|
|
1523
|
+
"startLine",
|
|
1524
|
+
"endLine",
|
|
1525
|
+
"maxLines",
|
|
1526
|
+
"tailLines",
|
|
1527
|
+
]);
|
|
1528
|
+
expect(globFiles.description).toContain("any file discovery task");
|
|
1529
|
+
expect(publicSchemaKeys(globFiles)).toEqual([
|
|
1530
|
+
"pattern",
|
|
1531
|
+
"path",
|
|
1532
|
+
"exclude",
|
|
1533
|
+
"includeIgnored",
|
|
1534
|
+
"maxResults",
|
|
1535
|
+
]);
|
|
1536
|
+
expect(searchFiles.description).toContain("using ripgrep when available");
|
|
1537
|
+
expect(publicSchemaKeys(searchFiles)).toEqual([
|
|
1538
|
+
"query",
|
|
1539
|
+
"path",
|
|
1540
|
+
"exclude",
|
|
1541
|
+
"includeIgnored",
|
|
1542
|
+
"maxResults",
|
|
1543
|
+
"literal",
|
|
1544
|
+
"caseSensitive",
|
|
1545
|
+
"contextLines",
|
|
1546
|
+
]);
|
|
1417
1547
|
});
|
|
1418
1548
|
|
|
1419
|
-
test("
|
|
1420
|
-
const requests: Array<{
|
|
1549
|
+
test("can vary globFiles schema through local runtime env", async () => {
|
|
1550
|
+
const requests: Array<{ body: any }> = [];
|
|
1421
1551
|
const adapter = createCliLocalRuntimeAdapter({
|
|
1422
1552
|
env: {
|
|
1423
|
-
|
|
1424
|
-
|
|
1553
|
+
OPENAI_API_KEY: "sk-local",
|
|
1554
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1555
|
+
NOLO_GLOBFILES_DESCRIPTION_VARIANT: "antiShell",
|
|
1556
|
+
NOLO_GLOBFILES_PARAMETER_VARIANT: "rich",
|
|
1425
1557
|
},
|
|
1426
1558
|
db: {
|
|
1427
1559
|
get: async () => ({
|
|
1428
|
-
dbKey:
|
|
1429
|
-
|
|
1560
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1561
|
+
prompt: "Use local coding tools.",
|
|
1562
|
+
model: "mimo-v2.5-pro",
|
|
1563
|
+
provider: "custom",
|
|
1564
|
+
tools: ["globFiles"],
|
|
1430
1565
|
}),
|
|
1431
1566
|
put: async () => {},
|
|
1432
1567
|
batch: async () => {},
|
|
1433
1568
|
iterator: () => (async function* () {})(),
|
|
1434
1569
|
},
|
|
1435
|
-
fetchImpl: async (
|
|
1436
|
-
requests.push({
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
body: JSON.parse(String(init?.body)),
|
|
1570
|
+
fetchImpl: async (_url, init) => {
|
|
1571
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1572
|
+
return Response.json({
|
|
1573
|
+
choices: [{ message: { content: "done" } }],
|
|
1440
1574
|
});
|
|
1441
|
-
return Response.json({ ok: true, row: { rowId: "task-1" } });
|
|
1442
1575
|
},
|
|
1443
1576
|
});
|
|
1444
1577
|
|
|
1445
|
-
await
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
arguments: JSON.stringify({
|
|
1450
|
-
action: "readTaskContext",
|
|
1451
|
-
dbKey: "row-user-NOLOTASKBOARD-task-1",
|
|
1452
|
-
}),
|
|
1578
|
+
await runLocalAgentTurn({
|
|
1579
|
+
adapter,
|
|
1580
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1581
|
+
input: "find tests",
|
|
1453
1582
|
});
|
|
1454
1583
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1584
|
+
const globFiles = requests[0]?.body.tools.find((tool: any) => tool.function.name === "globFiles")?.function;
|
|
1585
|
+
expect(globFiles.description).toContain("Do not use execShell or listFiles for path discovery");
|
|
1586
|
+
expect(publicSchemaKeys(globFiles)).toEqual([
|
|
1587
|
+
"pattern",
|
|
1588
|
+
"path",
|
|
1589
|
+
"exclude",
|
|
1590
|
+
"includeIgnored",
|
|
1591
|
+
"maxResults",
|
|
1592
|
+
]);
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
test("can vary listFiles schema through local runtime env", async () => {
|
|
1596
|
+
const requests: Array<{ body: any }> = [];
|
|
1597
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1598
|
+
env: {
|
|
1599
|
+
OPENAI_API_KEY: "sk-local",
|
|
1600
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1601
|
+
NOLO_LISTFILES_DESCRIPTION_VARIANT: "antiShell",
|
|
1602
|
+
NOLO_LISTFILES_PARAMETER_VARIANT: "rich",
|
|
1603
|
+
},
|
|
1604
|
+
db: {
|
|
1605
|
+
get: async () => ({
|
|
1606
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1607
|
+
prompt: "Use local coding tools.",
|
|
1608
|
+
model: "mimo-v2.5-pro",
|
|
1609
|
+
provider: "custom",
|
|
1610
|
+
tools: ["listFiles"],
|
|
1611
|
+
}),
|
|
1612
|
+
put: async () => {},
|
|
1613
|
+
batch: async () => {},
|
|
1614
|
+
iterator: () => (async function* () {})(),
|
|
1615
|
+
},
|
|
1616
|
+
fetchImpl: async (_url, init) => {
|
|
1617
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1618
|
+
return Response.json({
|
|
1619
|
+
choices: [{ message: { content: "done" } }],
|
|
1620
|
+
});
|
|
1463
1621
|
},
|
|
1622
|
+
});
|
|
1623
|
+
|
|
1624
|
+
await runLocalAgentTurn({
|
|
1625
|
+
adapter,
|
|
1626
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1627
|
+
input: "list directories",
|
|
1628
|
+
});
|
|
1629
|
+
|
|
1630
|
+
const listFiles = requests[0]?.body.tools.find((tool: any) => tool.function.name === "listFiles")?.function;
|
|
1631
|
+
expect(listFiles.description).toContain("Do not use execShell with ls/find/tree");
|
|
1632
|
+
expect(publicSchemaKeys(listFiles)).toEqual([
|
|
1633
|
+
"path",
|
|
1634
|
+
"maxDepth",
|
|
1635
|
+
"maxResults",
|
|
1636
|
+
"entryType",
|
|
1464
1637
|
]);
|
|
1465
|
-
|
|
1466
|
-
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
test("can vary readFile schema through local runtime env", async () => {
|
|
1641
|
+
const requests: Array<{ body: any }> = [];
|
|
1642
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1643
|
+
env: {
|
|
1644
|
+
OPENAI_API_KEY: "sk-local",
|
|
1645
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1646
|
+
NOLO_READFILE_DESCRIPTION_VARIANT: "strategy",
|
|
1647
|
+
NOLO_READFILE_PARAMETER_VARIANT: "rich",
|
|
1648
|
+
},
|
|
1649
|
+
db: {
|
|
1650
|
+
get: async () => ({
|
|
1651
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1652
|
+
prompt: "Use local coding tools.",
|
|
1653
|
+
model: "mimo-v2.5-pro",
|
|
1654
|
+
provider: "custom",
|
|
1655
|
+
tools: ["readFile"],
|
|
1656
|
+
}),
|
|
1657
|
+
put: async () => {},
|
|
1658
|
+
batch: async () => {},
|
|
1659
|
+
iterator: () => (async function* () {})(),
|
|
1660
|
+
},
|
|
1661
|
+
fetchImpl: async (_url, init) => {
|
|
1662
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1663
|
+
return Response.json({
|
|
1664
|
+
choices: [{ message: { content: "done" } }],
|
|
1665
|
+
});
|
|
1666
|
+
},
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
await runLocalAgentTurn({
|
|
1670
|
+
adapter,
|
|
1671
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1672
|
+
input: "read a file range",
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
const readFile = requests[0]?.body.tools.find((tool: any) => tool.function.name === "readFile")?.function;
|
|
1676
|
+
expect(readFile.description).toContain("Use line ranges");
|
|
1677
|
+
expect(readFile.parameters.properties.maxLines.description).toContain("each readFile preview consumes one budget slot");
|
|
1678
|
+
expect(publicSchemaKeys(readFile)).toEqual([
|
|
1679
|
+
"path",
|
|
1680
|
+
"startLine",
|
|
1681
|
+
"endLine",
|
|
1682
|
+
"maxLines",
|
|
1683
|
+
"tailLines",
|
|
1684
|
+
]);
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
test("can vary searchFiles schema through local runtime env", async () => {
|
|
1688
|
+
const requests: Array<{ body: any }> = [];
|
|
1689
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1690
|
+
env: {
|
|
1691
|
+
OPENAI_API_KEY: "sk-local",
|
|
1692
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
1693
|
+
NOLO_SEARCHFILES_DESCRIPTION_VARIANT: "antiShell",
|
|
1694
|
+
NOLO_SEARCHFILES_PARAMETER_VARIANT: "rich",
|
|
1695
|
+
},
|
|
1696
|
+
db: {
|
|
1697
|
+
get: async () => ({
|
|
1698
|
+
dbKey: MIMO_MONTH_AGENT_KEY,
|
|
1699
|
+
prompt: "Use local coding tools.",
|
|
1700
|
+
model: "mimo-v2.5-pro",
|
|
1701
|
+
provider: "custom",
|
|
1702
|
+
tools: ["searchFiles"],
|
|
1703
|
+
}),
|
|
1704
|
+
put: async () => {},
|
|
1705
|
+
batch: async () => {},
|
|
1706
|
+
iterator: () => (async function* () {})(),
|
|
1707
|
+
},
|
|
1708
|
+
fetchImpl: async (_url, init) => {
|
|
1709
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
1710
|
+
return Response.json({
|
|
1711
|
+
choices: [{ message: { content: "done" } }],
|
|
1712
|
+
});
|
|
1713
|
+
},
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
await runLocalAgentTurn({
|
|
1717
|
+
adapter,
|
|
1718
|
+
agentRef: MIMO_MONTH_AGENT_KEY,
|
|
1719
|
+
input: "find TODO",
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
const searchFiles = requests[0]?.body.tools.find((tool: any) => tool.function.name === "searchFiles")?.function;
|
|
1723
|
+
expect(searchFiles.description).toContain("Do not use execShell for content search");
|
|
1724
|
+
expect(publicSchemaKeys(searchFiles)).toEqual([
|
|
1725
|
+
"query",
|
|
1726
|
+
"path",
|
|
1727
|
+
"exclude",
|
|
1728
|
+
"includeIgnored",
|
|
1729
|
+
"maxResults",
|
|
1730
|
+
"literal",
|
|
1731
|
+
"caseSensitive",
|
|
1732
|
+
"contextLines",
|
|
1733
|
+
]);
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
test("allows default semantic workspace tools without legacy agent tool declarations", async () => {
|
|
1737
|
+
const workspaceRoot = mkdtempSync(join(tmpdir(), "nolo-cli-runtime-"));
|
|
1738
|
+
try {
|
|
1739
|
+
await Bun.write(join(workspaceRoot, "README.md"), "local ok\n");
|
|
1740
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
1741
|
+
env: {},
|
|
1742
|
+
cwd: workspaceRoot,
|
|
1743
|
+
db: {
|
|
1744
|
+
get: async () => ({
|
|
1745
|
+
dbKey: "agent-local-default-tools",
|
|
1746
|
+
prompt: "Use local workspace tools.",
|
|
1747
|
+
model: "gpt-4.1-mini",
|
|
1748
|
+
}),
|
|
1749
|
+
put: async () => {},
|
|
1750
|
+
batch: async () => {},
|
|
1751
|
+
iterator: () => (async function* () {})(),
|
|
1752
|
+
},
|
|
1753
|
+
fetchImpl: async () => Response.json({}),
|
|
1754
|
+
});
|
|
1755
|
+
|
|
1756
|
+
await adapter.loadAgentConfig("default-tools");
|
|
1757
|
+
await expect(adapter.executeTool({
|
|
1758
|
+
id: "call-read",
|
|
1759
|
+
name: "readFile",
|
|
1760
|
+
arguments: JSON.stringify({ path: "README.md" }),
|
|
1761
|
+
})).resolves.toMatchObject({
|
|
1762
|
+
content: "local ok\n",
|
|
1763
|
+
});
|
|
1764
|
+
} finally {
|
|
1765
|
+
rmSync(workspaceRoot, { recursive: true, force: true });
|
|
1766
|
+
}
|
|
1467
1767
|
});
|
|
1468
1768
|
|
|
1469
1769
|
test("exposes declared server table write tools in local runtime requests", async () => {
|
|
@@ -1541,6 +1841,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
1541
1841
|
userInput: "采集这个用户并写入 table",
|
|
1542
1842
|
arguments: JSON.stringify({
|
|
1543
1843
|
title: "XHS profile",
|
|
1844
|
+
purpose: "agent_eval_workbench",
|
|
1544
1845
|
columns: [{ name: "nickname" }],
|
|
1545
1846
|
}),
|
|
1546
1847
|
});
|
|
@@ -1551,6 +1852,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
1551
1852
|
auth: "Bearer token-1",
|
|
1552
1853
|
body: {
|
|
1553
1854
|
title: "XHS profile",
|
|
1855
|
+
purpose: "agent_eval_workbench",
|
|
1554
1856
|
columns: [{ name: "nickname" }],
|
|
1555
1857
|
},
|
|
1556
1858
|
},
|
|
@@ -2063,6 +2365,76 @@ describe("CLI local runtime adapter", () => {
|
|
|
2063
2365
|
expect(toolNamesFromRequest(requests[0])).toContain("read_xhs_profile");
|
|
2064
2366
|
});
|
|
2065
2367
|
|
|
2368
|
+
test("exposes read_x_post to OpenAI-compatible providers when the agent declares it", async () => {
|
|
2369
|
+
const requests: Array<{ body: any }> = [];
|
|
2370
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
2371
|
+
env: {
|
|
2372
|
+
OPENAI_API_KEY: "sk-local",
|
|
2373
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
2374
|
+
},
|
|
2375
|
+
db: {
|
|
2376
|
+
get: async () => ({
|
|
2377
|
+
dbKey: "agent-local-x-post",
|
|
2378
|
+
prompt: "Read X posts.",
|
|
2379
|
+
model: "gpt-4.1-mini",
|
|
2380
|
+
toolNames: ["read_x_post"],
|
|
2381
|
+
}),
|
|
2382
|
+
put: async () => {},
|
|
2383
|
+
batch: async () => {},
|
|
2384
|
+
iterator: () => (async function* () {})(),
|
|
2385
|
+
},
|
|
2386
|
+
fetchImpl: async (_url, init) => {
|
|
2387
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
2388
|
+
return Response.json({
|
|
2389
|
+
choices: [{ message: { content: "done" } }],
|
|
2390
|
+
});
|
|
2391
|
+
},
|
|
2392
|
+
});
|
|
2393
|
+
|
|
2394
|
+
await runLocalAgentTurn({
|
|
2395
|
+
adapter,
|
|
2396
|
+
agentRef: "x-post",
|
|
2397
|
+
input: "读取 X 帖子",
|
|
2398
|
+
});
|
|
2399
|
+
|
|
2400
|
+
expect(toolNamesFromRequest(requests[0])).toContain("read_x_post");
|
|
2401
|
+
});
|
|
2402
|
+
|
|
2403
|
+
test("adds read_x_post to local web-capable agent tool surface", async () => {
|
|
2404
|
+
const requests: Array<{ body: any }> = [];
|
|
2405
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
2406
|
+
env: {
|
|
2407
|
+
OPENAI_API_KEY: "sk-local",
|
|
2408
|
+
NOLO_LOCAL_OPENAI_BASE_URL: "http://127.0.0.1:11434/v1",
|
|
2409
|
+
},
|
|
2410
|
+
db: {
|
|
2411
|
+
get: async () => ({
|
|
2412
|
+
dbKey: "agent-local-web-reader",
|
|
2413
|
+
prompt: "Read web pages.",
|
|
2414
|
+
model: "gpt-4.1-mini",
|
|
2415
|
+
toolNames: ["fetchWebpage"],
|
|
2416
|
+
}),
|
|
2417
|
+
put: async () => {},
|
|
2418
|
+
batch: async () => {},
|
|
2419
|
+
iterator: () => (async function* () {})(),
|
|
2420
|
+
},
|
|
2421
|
+
fetchImpl: async (_url, init) => {
|
|
2422
|
+
requests.push({ body: JSON.parse(String(init?.body)) });
|
|
2423
|
+
return Response.json({
|
|
2424
|
+
choices: [{ message: { content: "done" } }],
|
|
2425
|
+
});
|
|
2426
|
+
},
|
|
2427
|
+
});
|
|
2428
|
+
|
|
2429
|
+
await runLocalAgentTurn({
|
|
2430
|
+
adapter,
|
|
2431
|
+
agentRef: "web-reader",
|
|
2432
|
+
input: "读取 X 帖子",
|
|
2433
|
+
});
|
|
2434
|
+
|
|
2435
|
+
expect(toolNamesFromRequest(requests[0])).toContain("read_x_post");
|
|
2436
|
+
});
|
|
2437
|
+
|
|
2066
2438
|
test("does not expose read_xhs_profile when the agent does not declare it", async () => {
|
|
2067
2439
|
const requests: Array<{ body: any }> = [];
|
|
2068
2440
|
const adapter = createCliLocalRuntimeAdapter({
|
|
@@ -2159,6 +2531,67 @@ describe("CLI local runtime adapter", () => {
|
|
|
2159
2531
|
expect(parsed.data.profile.nickname).toBe("test-soul");
|
|
2160
2532
|
});
|
|
2161
2533
|
|
|
2534
|
+
test("default read_x_post executor calls the local bridge reader", async () => {
|
|
2535
|
+
const readerCalls: Array<{ args: any }> = [];
|
|
2536
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
2537
|
+
env: {
|
|
2538
|
+
NOLO_LOCAL_USER_ID: "user-1",
|
|
2539
|
+
},
|
|
2540
|
+
db: {
|
|
2541
|
+
get: async () => ({
|
|
2542
|
+
dbKey: "agent-user-1-x-post-default",
|
|
2543
|
+
id: "x-post-default",
|
|
2544
|
+
prompt: "Read X posts.",
|
|
2545
|
+
toolNames: ["read_x_post"],
|
|
2546
|
+
}),
|
|
2547
|
+
put: async () => {},
|
|
2548
|
+
batch: async () => {},
|
|
2549
|
+
iterator: () => (async function* () {})(),
|
|
2550
|
+
},
|
|
2551
|
+
readXPost: async (args) => {
|
|
2552
|
+
readerCalls.push({ args });
|
|
2553
|
+
return {
|
|
2554
|
+
rawData: {
|
|
2555
|
+
ok: true,
|
|
2556
|
+
backend: "desktop_local_browser",
|
|
2557
|
+
fetchedAt: "2026-06-08T00:00:00.000Z",
|
|
2558
|
+
data: {
|
|
2559
|
+
id: "2063842222307704944",
|
|
2560
|
+
url: "https://x.com/BohuTANG/status/2063842222307704944",
|
|
2561
|
+
text: "mock x post",
|
|
2562
|
+
author: {
|
|
2563
|
+
handle: "BohuTANG",
|
|
2564
|
+
displayName: "Bohu",
|
|
2565
|
+
},
|
|
2566
|
+
},
|
|
2567
|
+
} as any,
|
|
2568
|
+
displayData: "已读取 X 帖子:@BohuTANG",
|
|
2569
|
+
};
|
|
2570
|
+
},
|
|
2571
|
+
fetchImpl: async () => Response.json({}),
|
|
2572
|
+
});
|
|
2573
|
+
|
|
2574
|
+
await adapter.loadAgentConfig("x-post-default");
|
|
2575
|
+
const result = await adapter.executeTool({
|
|
2576
|
+
id: "call-x-post-default",
|
|
2577
|
+
name: "read_x_post",
|
|
2578
|
+
arguments: JSON.stringify({
|
|
2579
|
+
url: "https://x.com/BohuTANG/status/2063842222307704944",
|
|
2580
|
+
keepOpen: true,
|
|
2581
|
+
}),
|
|
2582
|
+
});
|
|
2583
|
+
|
|
2584
|
+
expect(readerCalls).toHaveLength(1);
|
|
2585
|
+
expect(readerCalls[0].args).toMatchObject({
|
|
2586
|
+
url: "https://x.com/BohuTANG/status/2063842222307704944",
|
|
2587
|
+
keepOpen: true,
|
|
2588
|
+
});
|
|
2589
|
+
expect(result.metadata).toMatchObject({ xPostLocalBridge: true });
|
|
2590
|
+
const parsed = JSON.parse(result.content);
|
|
2591
|
+
expect(parsed.ok).toBe(true);
|
|
2592
|
+
expect(parsed.data.text).toBe("mock x post");
|
|
2593
|
+
});
|
|
2594
|
+
|
|
2162
2595
|
test("default read_xhs_profile executor calls the local bridge reader", async () => {
|
|
2163
2596
|
const readerCalls: Array<{ args: any; thunkApi: any }> = [];
|
|
2164
2597
|
const adapter = createCliLocalRuntimeAdapter({
|
|
@@ -2221,9 +2654,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
2221
2654
|
includeComments: true,
|
|
2222
2655
|
maxCommentPagesPerNote: 1,
|
|
2223
2656
|
});
|
|
2224
|
-
expect(
|
|
2225
|
-
"chat.nolo.desktop/stable/external-readers/xhs-profile"
|
|
2226
|
-
);
|
|
2657
|
+
expect(readerCalls[0].args.profileDir).toBeUndefined();
|
|
2227
2658
|
expect(readerCalls[0].thunkApi).toBeUndefined();
|
|
2228
2659
|
expect(result.metadata).toMatchObject({
|
|
2229
2660
|
xhsLocalBridge: true,
|
|
@@ -2232,7 +2663,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
2232
2663
|
expect(JSON.parse(result.content).data.profile.nickname).toBe("default-bridge");
|
|
2233
2664
|
});
|
|
2234
2665
|
|
|
2235
|
-
test("default read_xhs_profile executor
|
|
2666
|
+
test("default read_xhs_profile executor ignores explicit XHS profile env", async () => {
|
|
2236
2667
|
const readerCalls: Array<{ args: any }> = [];
|
|
2237
2668
|
const adapter = createCliLocalRuntimeAdapter({
|
|
2238
2669
|
env: {
|