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
package/client/agentRun.test.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
findServerPlatformTools,
|
|
7
7
|
runAgentTurn,
|
|
8
8
|
} from "./agentRun";
|
|
9
|
-
import {
|
|
9
|
+
import { BUILTIN_NOLO_AGENT_KEY } from "./localRuntimeAdapter";
|
|
10
|
+
import { MIMO_MONTH_AGENT_KEY, NOLO_PROJECT_MANAGER_AGENT_KEY } from "../agentAliases";
|
|
10
11
|
|
|
11
12
|
class CaptureOutput extends Writable {
|
|
12
13
|
chunks: string[] = [];
|
|
@@ -34,11 +35,11 @@ describe("cli agent run client", () => {
|
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
test("identifies server platform tools that local runtime cannot provide", () => {
|
|
37
|
-
expect(findServerPlatformTools(["
|
|
38
|
+
expect(findServerPlatformTools(["readFile", "queryTableRows", "updateTableRow"])).toEqual([
|
|
38
39
|
"queryTableRows",
|
|
39
40
|
"updateTableRow",
|
|
40
41
|
]);
|
|
41
|
-
expect(findServerPlatformTools(["
|
|
42
|
+
expect(findServerPlatformTools(["readFile", "execShell"])).toEqual([]);
|
|
42
43
|
});
|
|
43
44
|
|
|
44
45
|
test("calls the Nolo HTTP API directly when AUTH_TOKEN is present", async () => {
|
|
@@ -93,6 +94,34 @@ describe("cli agent run client", () => {
|
|
|
93
94
|
expect(output.text()).not.toContain("tokens=");
|
|
94
95
|
});
|
|
95
96
|
|
|
97
|
+
test("prints agent run error details from failed HTTP responses", async () => {
|
|
98
|
+
const output = new CaptureOutput();
|
|
99
|
+
|
|
100
|
+
const result = await runAgentTurn({
|
|
101
|
+
agentName: "win-codex",
|
|
102
|
+
agentKey: "agent-win-codex",
|
|
103
|
+
serverUrl: "https://nolo.chat",
|
|
104
|
+
message: "restart connector",
|
|
105
|
+
scriptDir: "C:/missing/scripts",
|
|
106
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
107
|
+
output,
|
|
108
|
+
fetchImpl: async () =>
|
|
109
|
+
Response.json({
|
|
110
|
+
error: "Connector execution failed",
|
|
111
|
+
reason: "connector_execution_failed",
|
|
112
|
+
code: "connector_disconnected_mid_run",
|
|
113
|
+
message: "Connector disconnected for machine: machine-win",
|
|
114
|
+
}, { status: 502 }),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({ exitCode: 1 });
|
|
118
|
+
expect(output.text()).toContain("[nolo] Agent request failed: HTTP 502");
|
|
119
|
+
expect(output.text()).toContain("Connector execution failed");
|
|
120
|
+
expect(output.text()).toContain("Connector disconnected for machine: machine-win");
|
|
121
|
+
expect(output.text()).toContain("code=connector_disconnected_mid_run");
|
|
122
|
+
expect(output.text()).toContain("reason=connector_execution_failed");
|
|
123
|
+
});
|
|
124
|
+
|
|
96
125
|
test("prepends task-run context for HTTP agent runs", async () => {
|
|
97
126
|
const output = new CaptureOutput();
|
|
98
127
|
const requests: Array<{ body: any }> = [];
|
|
@@ -210,7 +239,6 @@ describe("cli agent run client", () => {
|
|
|
210
239
|
parentDialogId: "dialog-2",
|
|
211
240
|
background: true,
|
|
212
241
|
noStream: true,
|
|
213
|
-
maxToolRounds: 12,
|
|
214
242
|
timeoutMs: 600000,
|
|
215
243
|
scriptDir: "C:/missing/scripts",
|
|
216
244
|
env: { AUTH_TOKEN: "token-123" },
|
|
@@ -230,7 +258,6 @@ describe("cli agent run client", () => {
|
|
|
230
258
|
inheritedFromDialogKey: "dialog-user-1-dialog-2",
|
|
231
259
|
parentDialogId: "dialog-2",
|
|
232
260
|
background: true,
|
|
233
|
-
maxRounds: 12,
|
|
234
261
|
timeoutMs: 600000,
|
|
235
262
|
stream: false,
|
|
236
263
|
});
|
|
@@ -316,72 +343,75 @@ describe("cli agent run client", () => {
|
|
|
316
343
|
expect(output.text()).toContain("frontend > local:polish notifications");
|
|
317
344
|
});
|
|
318
345
|
|
|
319
|
-
test("
|
|
346
|
+
test("continues forced local tool loops until the provider returns final text", async () => {
|
|
320
347
|
const output = new CaptureOutput();
|
|
321
348
|
let completeCalls = 0;
|
|
349
|
+
let toolCalls = 0;
|
|
350
|
+
|
|
322
351
|
const result = await runAgentTurn({
|
|
323
352
|
agentName: "frontend",
|
|
324
353
|
agentKey: "frontend-local",
|
|
325
354
|
serverUrl: "https://nolo.chat",
|
|
326
|
-
message: "
|
|
355
|
+
message: "make screenshots",
|
|
327
356
|
scriptDir: "C:/missing/scripts",
|
|
328
|
-
env: { AUTH_TOKEN: "token-123"
|
|
357
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
329
358
|
output,
|
|
330
359
|
runtimeMode: "local",
|
|
331
|
-
maxToolRounds: 0,
|
|
332
360
|
localRuntimeAdapter: {
|
|
333
361
|
host: "cli",
|
|
334
|
-
capabilities: ["local-provider", "local-persistence"],
|
|
362
|
+
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
335
363
|
loadAgentConfig: async (agentRef) => ({
|
|
336
364
|
key: agentRef,
|
|
337
365
|
name: "Frontend",
|
|
338
366
|
prompt: "Fix UI",
|
|
339
367
|
model: "fake-local",
|
|
368
|
+
toolNames: ["readFile"],
|
|
340
369
|
}),
|
|
341
370
|
loadDialogHistory: async () => [],
|
|
342
|
-
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
371
|
+
saveTurn: async () => ({ dialogId: "dialog-local-retry" }),
|
|
343
372
|
resolveProvider: async () => ({
|
|
344
373
|
model: "fake-local",
|
|
345
374
|
complete: async () => {
|
|
346
375
|
completeCalls += 1;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
376
|
+
if (completeCalls <= 3) {
|
|
377
|
+
return {
|
|
378
|
+
content: "",
|
|
379
|
+
model: "fake-local",
|
|
380
|
+
tool_calls: [{
|
|
381
|
+
id: `call-${completeCalls}`,
|
|
382
|
+
type: "function",
|
|
383
|
+
function: { name: "readFile", arguments: "{}" },
|
|
384
|
+
}],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return { content: "done after retry", model: "fake-local" };
|
|
356
388
|
},
|
|
357
389
|
}),
|
|
358
|
-
executeTool: async () =>
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
390
|
+
executeTool: async () => {
|
|
391
|
+
toolCalls += 1;
|
|
392
|
+
return { content: "ok" };
|
|
393
|
+
},
|
|
362
394
|
},
|
|
363
395
|
});
|
|
364
396
|
|
|
365
|
-
expect(result).
|
|
366
|
-
expect(
|
|
367
|
-
expect(output.text()).toContain("
|
|
397
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-retry" });
|
|
398
|
+
expect(toolCalls).toBe(3);
|
|
399
|
+
expect(output.text()).toContain("frontend > done after retry");
|
|
368
400
|
});
|
|
369
401
|
|
|
370
|
-
test("
|
|
402
|
+
test("prints compact local tool trace by default", async () => {
|
|
371
403
|
const output = new CaptureOutput();
|
|
372
404
|
let completeCalls = 0;
|
|
373
|
-
let toolCalls = 0;
|
|
374
405
|
|
|
375
406
|
const result = await runAgentTurn({
|
|
376
407
|
agentName: "frontend",
|
|
377
408
|
agentKey: "frontend-local",
|
|
378
409
|
serverUrl: "https://nolo.chat",
|
|
379
|
-
message: "
|
|
410
|
+
message: "inspect repo",
|
|
380
411
|
scriptDir: "C:/missing/scripts",
|
|
381
412
|
env: { AUTH_TOKEN: "token-123" },
|
|
382
413
|
output,
|
|
383
414
|
runtimeMode: "local",
|
|
384
|
-
maxToolRounds: 1,
|
|
385
415
|
localRuntimeAdapter: {
|
|
386
416
|
host: "cli",
|
|
387
417
|
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
@@ -390,42 +420,39 @@ describe("cli agent run client", () => {
|
|
|
390
420
|
name: "Frontend",
|
|
391
421
|
prompt: "Fix UI",
|
|
392
422
|
model: "fake-local",
|
|
393
|
-
toolNames: ["
|
|
423
|
+
toolNames: ["readFile"],
|
|
394
424
|
}),
|
|
395
425
|
loadDialogHistory: async () => [],
|
|
396
|
-
saveTurn: async () => ({ dialogId: "dialog-local
|
|
426
|
+
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
397
427
|
resolveProvider: async () => ({
|
|
398
428
|
model: "fake-local",
|
|
399
429
|
complete: async () => {
|
|
400
430
|
completeCalls += 1;
|
|
401
|
-
if (completeCalls
|
|
431
|
+
if (completeCalls === 1) {
|
|
402
432
|
return {
|
|
403
433
|
content: "",
|
|
404
434
|
model: "fake-local",
|
|
405
435
|
tool_calls: [{
|
|
406
|
-
id:
|
|
436
|
+
id: "call-read",
|
|
407
437
|
type: "function",
|
|
408
|
-
function: { name: "
|
|
438
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
409
439
|
}],
|
|
410
440
|
};
|
|
411
441
|
}
|
|
412
|
-
return { content: "done
|
|
442
|
+
return { content: "done", model: "fake-local" };
|
|
413
443
|
},
|
|
414
444
|
}),
|
|
415
|
-
executeTool: async () => {
|
|
416
|
-
toolCalls += 1;
|
|
417
|
-
return { content: "ok" };
|
|
418
|
-
},
|
|
445
|
+
executeTool: async () => ({ content: "clean" }),
|
|
419
446
|
},
|
|
420
447
|
});
|
|
421
448
|
|
|
422
|
-
expect(result).
|
|
423
|
-
expect(
|
|
424
|
-
expect(output.text()).toContain("
|
|
425
|
-
expect(output.text()).toContain("
|
|
449
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
450
|
+
expect(output.text()).toContain("[nolo:tool] #1 -> readFile README.md");
|
|
451
|
+
expect(output.text()).toContain("[nolo:tool] #1 <- readFile");
|
|
452
|
+
expect(output.text()).toContain("1 line");
|
|
426
453
|
});
|
|
427
454
|
|
|
428
|
-
test("
|
|
455
|
+
test("does not print local tool trace when disabled by env", async () => {
|
|
429
456
|
const output = new CaptureOutput();
|
|
430
457
|
let completeCalls = 0;
|
|
431
458
|
|
|
@@ -435,10 +462,9 @@ describe("cli agent run client", () => {
|
|
|
435
462
|
serverUrl: "https://nolo.chat",
|
|
436
463
|
message: "inspect repo",
|
|
437
464
|
scriptDir: "C:/missing/scripts",
|
|
438
|
-
env: { AUTH_TOKEN: "token-123" },
|
|
465
|
+
env: { AUTH_TOKEN: "token-123", NOLO_TRACE_TOOLS: "0" },
|
|
439
466
|
output,
|
|
440
467
|
runtimeMode: "local",
|
|
441
|
-
traceTools: true,
|
|
442
468
|
localRuntimeAdapter: {
|
|
443
469
|
host: "cli",
|
|
444
470
|
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
@@ -447,10 +473,10 @@ describe("cli agent run client", () => {
|
|
|
447
473
|
name: "Frontend",
|
|
448
474
|
prompt: "Fix UI",
|
|
449
475
|
model: "fake-local",
|
|
450
|
-
toolNames: ["
|
|
476
|
+
toolNames: ["readFile"],
|
|
451
477
|
}),
|
|
452
478
|
loadDialogHistory: async () => [],
|
|
453
|
-
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
479
|
+
saveTurn: async () => ({ dialogId: "dialog-local-no-trace" }),
|
|
454
480
|
resolveProvider: async () => ({
|
|
455
481
|
model: "fake-local",
|
|
456
482
|
complete: async () => {
|
|
@@ -462,7 +488,7 @@ describe("cli agent run client", () => {
|
|
|
462
488
|
tool_calls: [{
|
|
463
489
|
id: "call-read",
|
|
464
490
|
type: "function",
|
|
465
|
-
function: { name: "
|
|
491
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
466
492
|
}],
|
|
467
493
|
};
|
|
468
494
|
}
|
|
@@ -473,9 +499,79 @@ describe("cli agent run client", () => {
|
|
|
473
499
|
},
|
|
474
500
|
});
|
|
475
501
|
|
|
476
|
-
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
477
|
-
expect(output.text()).toContain("[nolo:tool]
|
|
478
|
-
|
|
502
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local-no-trace" });
|
|
503
|
+
expect(output.text()).not.toContain("[nolo:tool]");
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("prints local tool events as jsonl when requested", async () => {
|
|
507
|
+
const output = new CaptureOutput();
|
|
508
|
+
let completeCalls = 0;
|
|
509
|
+
|
|
510
|
+
const result = await runAgentTurn({
|
|
511
|
+
agentName: "frontend",
|
|
512
|
+
agentKey: "frontend-local",
|
|
513
|
+
serverUrl: "https://nolo.chat",
|
|
514
|
+
message: "inspect repo",
|
|
515
|
+
scriptDir: "C:/missing/scripts",
|
|
516
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
517
|
+
output,
|
|
518
|
+
runtimeMode: "local",
|
|
519
|
+
eventsMode: "jsonl",
|
|
520
|
+
localRuntimeAdapter: {
|
|
521
|
+
host: "cli",
|
|
522
|
+
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
523
|
+
loadAgentConfig: async (agentRef) => ({
|
|
524
|
+
key: agentRef,
|
|
525
|
+
name: "Frontend",
|
|
526
|
+
prompt: "Fix UI",
|
|
527
|
+
model: "fake-local",
|
|
528
|
+
toolNames: ["execShell"],
|
|
529
|
+
}),
|
|
530
|
+
loadDialogHistory: async () => [],
|
|
531
|
+
saveTurn: async () => ({ dialogId: "dialog-local-jsonl" }),
|
|
532
|
+
resolveProvider: async () => ({
|
|
533
|
+
model: "fake-local",
|
|
534
|
+
complete: async () => {
|
|
535
|
+
completeCalls += 1;
|
|
536
|
+
if (completeCalls === 1) {
|
|
537
|
+
return {
|
|
538
|
+
content: "",
|
|
539
|
+
model: "fake-local",
|
|
540
|
+
tool_calls: [{
|
|
541
|
+
id: "call-shell",
|
|
542
|
+
type: "function",
|
|
543
|
+
function: { name: "execShell", arguments: JSON.stringify({ command: "git status --short" }) },
|
|
544
|
+
}],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return { content: "done", model: "fake-local" };
|
|
548
|
+
},
|
|
549
|
+
}),
|
|
550
|
+
executeTool: async () => ({ content: "stdout:\nclean\n\nexitCode: 0", metadata: { exitCode: 0 } }),
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local-jsonl" });
|
|
555
|
+
const events = output.text()
|
|
556
|
+
.split(/\r?\n/)
|
|
557
|
+
.filter((line) => line.startsWith("{"))
|
|
558
|
+
.map((line) => JSON.parse(line))
|
|
559
|
+
.filter((event) => typeof event.type === "string" && event.type.startsWith("tool-"));
|
|
560
|
+
expect(events).toHaveLength(2);
|
|
561
|
+
expect(events[0]).toMatchObject({
|
|
562
|
+
schemaVersion: 1,
|
|
563
|
+
type: "tool-call",
|
|
564
|
+
round: 1,
|
|
565
|
+
tool: "execShell",
|
|
566
|
+
argsPreview: "git status --short",
|
|
567
|
+
});
|
|
568
|
+
expect(events[1]).toMatchObject({
|
|
569
|
+
schemaVersion: 1,
|
|
570
|
+
type: "tool-result",
|
|
571
|
+
round: 1,
|
|
572
|
+
tool: "execShell",
|
|
573
|
+
summary: expect.stringContaining("exit=0"),
|
|
574
|
+
});
|
|
479
575
|
});
|
|
480
576
|
|
|
481
577
|
test("passes local tool evidence to task-run writeback", async () => {
|
|
@@ -507,7 +603,7 @@ describe("cli agent run client", () => {
|
|
|
507
603
|
name: "Frontend",
|
|
508
604
|
prompt: "Fix UI",
|
|
509
605
|
model: "fake-local",
|
|
510
|
-
toolNames: ["
|
|
606
|
+
toolNames: ["readFile"],
|
|
511
607
|
}),
|
|
512
608
|
loadDialogHistory: async () => [],
|
|
513
609
|
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
@@ -522,7 +618,7 @@ describe("cli agent run client", () => {
|
|
|
522
618
|
tool_calls: [{
|
|
523
619
|
id: "call-read",
|
|
524
620
|
type: "function",
|
|
525
|
-
function: { name: "
|
|
621
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
526
622
|
}],
|
|
527
623
|
};
|
|
528
624
|
}
|
|
@@ -536,7 +632,7 @@ describe("cli agent run client", () => {
|
|
|
536
632
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
537
633
|
expect(writebacks).toHaveLength(1);
|
|
538
634
|
expect(writebacks[0].toolEvidence).toEqual({
|
|
539
|
-
toolsUsed: ["
|
|
635
|
+
toolsUsed: ["readFile"],
|
|
540
636
|
summary: {
|
|
541
637
|
callCount: 1,
|
|
542
638
|
resultCount: 1,
|
|
@@ -791,6 +887,160 @@ describe("cli agent run client", () => {
|
|
|
791
887
|
expect(output.text()).toContain("pm > server ok");
|
|
792
888
|
});
|
|
793
889
|
|
|
890
|
+
test("auto mode does not skip monthly Mimo when a local shell cwd is already bound", async () => {
|
|
891
|
+
const output = new CaptureOutput();
|
|
892
|
+
const httpCalls: string[] = [];
|
|
893
|
+
let providerCalled = false;
|
|
894
|
+
|
|
895
|
+
const result = await runAgentTurn({
|
|
896
|
+
agentName: MIMO_MONTH_AGENT_KEY,
|
|
897
|
+
agentKey: MIMO_MONTH_AGENT_KEY,
|
|
898
|
+
serverUrl: "https://us.nolo.chat",
|
|
899
|
+
message: "fix tests and commit",
|
|
900
|
+
scriptDir: "C:/missing/scripts",
|
|
901
|
+
env: {
|
|
902
|
+
AUTH_TOKEN: "token-123",
|
|
903
|
+
},
|
|
904
|
+
output,
|
|
905
|
+
runtimeMode: "auto",
|
|
906
|
+
localRuntimeCwd: "/repo/.worktrees/nolo-agent-mimo",
|
|
907
|
+
localRuntimeAdapter: {
|
|
908
|
+
host: "cli",
|
|
909
|
+
capabilities: ["leveldb-agent-config", "local-provider", "local-tools"],
|
|
910
|
+
loadAgentConfig: async (agentRef) => ({
|
|
911
|
+
key: agentRef,
|
|
912
|
+
name: "包月 Mimo",
|
|
913
|
+
prompt: "Implement backend tasks.",
|
|
914
|
+
provider: "custom",
|
|
915
|
+
model: "mimo-v2.5-pro",
|
|
916
|
+
toolNames: ["queryTableRows"],
|
|
917
|
+
}),
|
|
918
|
+
loadDialogHistory: async () => [],
|
|
919
|
+
saveTurn: async () => ({ dialogId: "dialog-mimo-local" }),
|
|
920
|
+
resolveProvider: async () => ({
|
|
921
|
+
model: "mimo-v2.5-pro",
|
|
922
|
+
complete: async () => {
|
|
923
|
+
providerCalled = true;
|
|
924
|
+
return { content: "local mimo ok", model: "mimo-v2.5-pro" };
|
|
925
|
+
},
|
|
926
|
+
}),
|
|
927
|
+
executeTool: async () => ({ content: "ok" }),
|
|
928
|
+
},
|
|
929
|
+
fetchImpl: async (url) => {
|
|
930
|
+
httpCalls.push(String(url));
|
|
931
|
+
return Response.json({ content: "server" });
|
|
932
|
+
},
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
expect(providerCalled).toBe(true);
|
|
936
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-mimo-local" });
|
|
937
|
+
expect(httpCalls).toEqual([]);
|
|
938
|
+
expect(output.text()).not.toContain("skipping local runtime");
|
|
939
|
+
expect(output.text()).toContain(`${MIMO_MONTH_AGENT_KEY} -> working locally`);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test("auto mode skips local runtime for machine-bound localhost custom providers", async () => {
|
|
943
|
+
const output = new CaptureOutput();
|
|
944
|
+
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
945
|
+
|
|
946
|
+
const result = await runAgentTurn({
|
|
947
|
+
agentName: "win-qwen",
|
|
948
|
+
agentKey: "agent-win-qwen",
|
|
949
|
+
serverUrl: "https://nolo.chat",
|
|
950
|
+
message: "run on the bound Windows machine",
|
|
951
|
+
scriptDir: "C:/missing/scripts",
|
|
952
|
+
env: { AUTH_TOKEN: "token-123", OPENAI_API_KEY: "local-provider-present" },
|
|
953
|
+
output,
|
|
954
|
+
runtimeMode: "auto",
|
|
955
|
+
localRuntimeAdapter: {
|
|
956
|
+
host: "cli",
|
|
957
|
+
capabilities: ["leveldb-agent-config", "local-provider", "local-tools"],
|
|
958
|
+
loadAgentConfig: async (agentRef) => ({
|
|
959
|
+
key: agentRef,
|
|
960
|
+
name: "Win Qwen",
|
|
961
|
+
prompt: "Windows local model",
|
|
962
|
+
provider: "custom",
|
|
963
|
+
model: "qwen",
|
|
964
|
+
customProviderUrl: "http://127.0.0.1:8080/v1/chat/completions",
|
|
965
|
+
runtimeBinding: {
|
|
966
|
+
machineId: "machine-win",
|
|
967
|
+
ownerUserId: "user-1",
|
|
968
|
+
connectorSurface: "cli",
|
|
969
|
+
},
|
|
970
|
+
}),
|
|
971
|
+
loadDialogHistory: async () => [],
|
|
972
|
+
saveTurn: async () => {
|
|
973
|
+
throw new Error("local runtime should be skipped");
|
|
974
|
+
},
|
|
975
|
+
resolveProvider: async () => {
|
|
976
|
+
throw new Error("local provider should be skipped");
|
|
977
|
+
},
|
|
978
|
+
executeTool: async () => {
|
|
979
|
+
throw new Error("local tools should be skipped");
|
|
980
|
+
},
|
|
981
|
+
},
|
|
982
|
+
fetchImpl: async (url, init) => {
|
|
983
|
+
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
984
|
+
return Response.json({ content: "server connector ok", dialogId: "dialog-win" });
|
|
985
|
+
},
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-win" });
|
|
989
|
+
expect(httpCalls).toHaveLength(1);
|
|
990
|
+
expect(httpCalls[0]?.body.agentKey).toBe("agent-win-qwen");
|
|
991
|
+
expect(output.text()).toContain("auto runtime: skipping local runtime");
|
|
992
|
+
expect(output.text()).toContain("machine-bound localhost custom provider");
|
|
993
|
+
expect(output.text()).not.toContain("win-qwen -> working locally");
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
test("auto mode keeps builtin nolo on local runtime so CLI workspace tools are available", async () => {
|
|
997
|
+
const output = new CaptureOutput();
|
|
998
|
+
const httpCalls: string[] = [];
|
|
999
|
+
let providerCalled = false;
|
|
1000
|
+
|
|
1001
|
+
const result = await runAgentTurn({
|
|
1002
|
+
agentName: "nolo",
|
|
1003
|
+
agentKey: BUILTIN_NOLO_AGENT_KEY,
|
|
1004
|
+
serverUrl: "https://nolo.chat",
|
|
1005
|
+
message: "帮我总结最近 10 个对话",
|
|
1006
|
+
scriptDir: "C:/missing/scripts",
|
|
1007
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
1008
|
+
output,
|
|
1009
|
+
runtimeMode: "auto",
|
|
1010
|
+
localRuntimeAdapter: {
|
|
1011
|
+
host: "cli",
|
|
1012
|
+
capabilities: ["leveldb-agent-config", "local-provider", "local-tools"],
|
|
1013
|
+
loadAgentConfig: async () => ({
|
|
1014
|
+
key: BUILTIN_NOLO_AGENT_KEY,
|
|
1015
|
+
name: "nolo",
|
|
1016
|
+
prompt: "Use workspace tools.",
|
|
1017
|
+
model: "fake-local",
|
|
1018
|
+
toolNames: ["queryTableRows", "addTableRow", "updateTableRow"],
|
|
1019
|
+
}),
|
|
1020
|
+
loadDialogHistory: async () => [],
|
|
1021
|
+
saveTurn: async () => ({ dialogId: "dialog-builtin-nolo-local" }),
|
|
1022
|
+
resolveProvider: async () => ({
|
|
1023
|
+
model: "fake-local",
|
|
1024
|
+
complete: async () => {
|
|
1025
|
+
providerCalled = true;
|
|
1026
|
+
return { content: "local nolo with tools", model: "fake-local" };
|
|
1027
|
+
},
|
|
1028
|
+
}),
|
|
1029
|
+
executeTool: async () => ({ content: "[]" }),
|
|
1030
|
+
},
|
|
1031
|
+
fetchImpl: async (url) => {
|
|
1032
|
+
httpCalls.push(String(url));
|
|
1033
|
+
return Response.json({ content: "server" });
|
|
1034
|
+
},
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
expect(providerCalled).toBe(true);
|
|
1038
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-builtin-nolo-local" });
|
|
1039
|
+
expect(httpCalls).toEqual([]);
|
|
1040
|
+
expect(output.text()).not.toContain("skipping local runtime");
|
|
1041
|
+
expect(output.text()).toContain("nolo -> working locally");
|
|
1042
|
+
});
|
|
1043
|
+
|
|
794
1044
|
test("auto mode skips local runtime for server platform tools declared by runtime policy", async () => {
|
|
795
1045
|
const output = new CaptureOutput();
|
|
796
1046
|
const httpCalls: Array<{ url: string; body: any }> = [];
|