nolo-cli 0.1.22 → 0.1.26
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 +25 -3
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/hybridRecordStore.ts +30 -9
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +14 -4
- package/agent-runtime/localToolPolicy.ts +22 -10
- package/agent-runtime/localWorkspaceTools.ts +140 -374
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +133 -0
- package/agent-runtime/types.ts +47 -0
- package/agentAliases.ts +1 -1
- package/agentCommandSupport.ts +77 -0
- package/agentInternalCommandEntries.ts +34 -0
- package/agentListCommands.ts +123 -0
- package/agentListHelpers.ts +187 -0
- package/agentMachineCommands.ts +402 -0
- package/agentRecordCommands.ts +141 -0
- package/agentRecordHelpers.ts +395 -0
- package/agentRunCommand.ts +75 -3
- package/agentRuntimeCommands.ts +7 -773
- package/ai/agent/_executeModel.ts +29 -3
- package/ai/agent/buildSystemPrompt.ts +17 -0
- package/ai/agent/cliChatClient.ts +38 -4
- package/ai/agent/cliExecutor.ts +304 -25
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/guidedCreation/assistant.ts +82 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/draft.ts +62 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/machineRunPermissions.ts +21 -3
- package/ai/agent/runtimeGuidance.ts +1 -1
- package/ai/agent/server/fetchPublicAgents.ts +6 -1
- package/ai/agent/streamAgentChatTurn.ts +593 -36
- package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
- package/ai/ai.locale.ts +14 -2
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
- package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
- package/ai/llm/getPricing.ts +14 -0
- package/ai/llm/kimi.ts +4 -1
- package/ai/llm/modelAvatar.ts +1 -0
- package/ai/llm/providers.ts +4 -1
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/taskRun/taskRunProtocol.ts +25 -10
- package/ai/token/modelUsageQuery.ts +17 -0
- package/ai/tools/agent/agentTools.ts +38 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/checkEnvTool.ts +52 -20
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
- package/ai/tools/index.ts +24 -14
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +9 -2
- package/ai/tools/readTool.ts +31 -0
- package/ai/tools/toolApiClient.ts +35 -1
- package/ai/tools/toolNameAliases.ts +13 -0
- package/cliCommandAdapters.ts +28 -0
- package/cliCommandDispatch.ts +66 -0
- package/cliCommandFactories.ts +34 -0
- package/cliCommandTypes.ts +49 -0
- package/cliEnvHelpers.ts +44 -0
- package/client/agentRun.test.ts +330 -24
- package/client/agentRun.ts +117 -35
- package/client/hybridRecordStore.test.ts +42 -0
- package/client/hybridRecordStore.ts +11 -0
- package/client/localRuntimeAdapter.test.ts +191 -21
- package/client/localRuntimeAdapter.ts +99 -10
- package/client/localRuntimeDryRun.test.ts +5 -2
- package/client/taskRunPrompt.ts +1 -0
- package/commandRegistry.ts +11 -61
- package/connector-experimental/capabilities.ts +1 -0
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/database/server/coreDataProxy.ts +43 -26
- package/index.ts +54 -191
- package/internalCommandEntries.ts +12 -0
- package/machineCommands.ts +688 -64
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +22 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/runtimeDoctorCommands.ts +118 -0
- package/scriptCommandEntries.ts +71 -0
- package/systemInternalCommandEntries.ts +71 -0
- package/tableCommands.ts +42 -5
- package/workflowInternalCommandEntries.ts +11 -0
package/client/agentRun.test.ts
CHANGED
|
@@ -45,7 +45,7 @@ describe("cli agent run client", () => {
|
|
|
45
45
|
"queryTableRows",
|
|
46
46
|
"updateTableRow",
|
|
47
47
|
]);
|
|
48
|
-
expect(findServerPlatformTools(["readWorkspaceFile", "
|
|
48
|
+
expect(findServerPlatformTools(["readWorkspaceFile", "execShell"])).toEqual([]);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
test("calls the Nolo HTTP API directly when AUTH_TOKEN is present", async () => {
|
|
@@ -84,15 +84,15 @@ describe("cli agent run client", () => {
|
|
|
84
84
|
agentKey: "agent-pub-test",
|
|
85
85
|
userInput: "hello",
|
|
86
86
|
continueDialogId: "dialog-existing",
|
|
87
|
-
runtimeContext: {
|
|
88
|
-
surface: "cli",
|
|
89
|
-
host: "terminal",
|
|
90
|
-
runtime: "bun",
|
|
91
|
-
entrypoint: "nolo-cli",
|
|
92
|
-
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
93
|
-
},
|
|
94
|
-
stream: true,
|
|
95
|
-
},
|
|
87
|
+
runtimeContext: {
|
|
88
|
+
surface: "cli",
|
|
89
|
+
host: "terminal",
|
|
90
|
+
runtime: "bun",
|
|
91
|
+
entrypoint: "nolo-cli",
|
|
92
|
+
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
93
|
+
},
|
|
94
|
+
stream: true,
|
|
95
|
+
},
|
|
96
96
|
},
|
|
97
97
|
]);
|
|
98
98
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-1" });
|
|
@@ -132,6 +132,12 @@ describe("cli agent run client", () => {
|
|
|
132
132
|
expect(requests[0]?.body.userInput).toContain("rowDbKey: row-b2e06f801f-01TASK");
|
|
133
133
|
expect(requests[0]?.body.userInput).toContain("workItemId: frontend-filter");
|
|
134
134
|
expect(requests[0]?.body.userInput).toContain("User task:\nFix the filter UI");
|
|
135
|
+
expect(requests[0]?.body.runtimeContext.taskRun).toEqual({
|
|
136
|
+
rowDbKey: "row-b2e06f801f-01TASK",
|
|
137
|
+
taskRunId: "taskrun-1",
|
|
138
|
+
workItemId: "frontend-filter",
|
|
139
|
+
artifactIds: ["artifact-1"],
|
|
140
|
+
});
|
|
135
141
|
});
|
|
136
142
|
|
|
137
143
|
test("records task-run completion after an HTTP agent run returns a dialog", async () => {
|
|
@@ -243,6 +249,42 @@ describe("cli agent run client", () => {
|
|
|
243
249
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-1" });
|
|
244
250
|
});
|
|
245
251
|
|
|
252
|
+
test("records background task-run handoff as running instead of completed", async () => {
|
|
253
|
+
const output = new CaptureOutput();
|
|
254
|
+
const writebacks: any[] = [];
|
|
255
|
+
|
|
256
|
+
const result = await runAgentTurn({
|
|
257
|
+
agentName: "frontend-implementer",
|
|
258
|
+
agentKey: "agent-frontend",
|
|
259
|
+
serverUrl: "https://nolo.chat",
|
|
260
|
+
message: "Fix settings style FOUC",
|
|
261
|
+
scriptDir: "C:/missing/scripts",
|
|
262
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
263
|
+
runtimeMode: "server",
|
|
264
|
+
background: true,
|
|
265
|
+
noStream: true,
|
|
266
|
+
taskRunContext: {
|
|
267
|
+
rowDbKey: "row-b2e06f801f-01TASK",
|
|
268
|
+
workItemId: "self",
|
|
269
|
+
},
|
|
270
|
+
taskRunRecorder: async (args) => {
|
|
271
|
+
writebacks.push(args);
|
|
272
|
+
},
|
|
273
|
+
output,
|
|
274
|
+
scriptPathExists: () => false,
|
|
275
|
+
fetchImpl: async () => {
|
|
276
|
+
return Response.json({ dialogId: "dialog-bg", status: "pending" }, { status: 202 });
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-bg" });
|
|
281
|
+
expect(writebacks).toHaveLength(1);
|
|
282
|
+
expect(writebacks[0]).toMatchObject({
|
|
283
|
+
status: "running",
|
|
284
|
+
dialogId: "dialog-bg",
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
246
288
|
test("runs forced local turns through the injected runtime adapter without HTTP", async () => {
|
|
247
289
|
const output = new CaptureOutput();
|
|
248
290
|
const result = await runAgentTurn({
|
|
@@ -421,7 +463,7 @@ describe("cli agent run client", () => {
|
|
|
421
463
|
name: "Frontend",
|
|
422
464
|
prompt: "Fix UI",
|
|
423
465
|
model: "fake-local",
|
|
424
|
-
toolNames: ["
|
|
466
|
+
toolNames: ["readWorkspaceFile"],
|
|
425
467
|
}),
|
|
426
468
|
loadDialogHistory: async () => [],
|
|
427
469
|
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
@@ -434,9 +476,9 @@ describe("cli agent run client", () => {
|
|
|
434
476
|
content: "",
|
|
435
477
|
model: "fake-local",
|
|
436
478
|
tool_calls: [{
|
|
437
|
-
id: "call-
|
|
479
|
+
id: "call-read",
|
|
438
480
|
type: "function",
|
|
439
|
-
function: { name: "
|
|
481
|
+
function: { name: "readWorkspaceFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
440
482
|
}],
|
|
441
483
|
};
|
|
442
484
|
}
|
|
@@ -449,8 +491,8 @@ describe("cli agent run client", () => {
|
|
|
449
491
|
});
|
|
450
492
|
|
|
451
493
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
452
|
-
expect(output.text()).toContain("[nolo:tool] round 1 ->
|
|
453
|
-
expect(output.text()).toContain("[nolo:tool] round 1 <-
|
|
494
|
+
expect(output.text()).toContain("[nolo:tool] round 1 -> readWorkspaceFile (call-read)");
|
|
495
|
+
expect(output.text()).toContain("[nolo:tool] round 1 <- readWorkspaceFile (call-read)");
|
|
454
496
|
});
|
|
455
497
|
|
|
456
498
|
test("passes local tool evidence to task-run writeback", async () => {
|
|
@@ -482,7 +524,7 @@ describe("cli agent run client", () => {
|
|
|
482
524
|
name: "Frontend",
|
|
483
525
|
prompt: "Fix UI",
|
|
484
526
|
model: "fake-local",
|
|
485
|
-
toolNames: ["
|
|
527
|
+
toolNames: ["readWorkspaceFile"],
|
|
486
528
|
}),
|
|
487
529
|
loadDialogHistory: async () => [],
|
|
488
530
|
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
@@ -495,9 +537,9 @@ describe("cli agent run client", () => {
|
|
|
495
537
|
content: "",
|
|
496
538
|
model: "fake-local",
|
|
497
539
|
tool_calls: [{
|
|
498
|
-
id: "call-
|
|
540
|
+
id: "call-read",
|
|
499
541
|
type: "function",
|
|
500
|
-
function: { name: "
|
|
542
|
+
function: { name: "readWorkspaceFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
501
543
|
}],
|
|
502
544
|
};
|
|
503
545
|
}
|
|
@@ -512,7 +554,7 @@ describe("cli agent run client", () => {
|
|
|
512
554
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
513
555
|
expect(writebacks).toHaveLength(1);
|
|
514
556
|
expect(writebacks[0].toolEvidence).toEqual({
|
|
515
|
-
toolsUsed: ["
|
|
557
|
+
toolsUsed: ["readWorkspaceFile"],
|
|
516
558
|
summary: {
|
|
517
559
|
callCount: 1,
|
|
518
560
|
resultCount: 1,
|
|
@@ -522,6 +564,58 @@ describe("cli agent run client", () => {
|
|
|
522
564
|
});
|
|
523
565
|
});
|
|
524
566
|
|
|
567
|
+
test("records local background task-run handoff as running instead of completed", async () => {
|
|
568
|
+
const output = new CaptureOutput();
|
|
569
|
+
const writebacks: any[] = [];
|
|
570
|
+
|
|
571
|
+
const result = await runAgentTurn({
|
|
572
|
+
agentName: "frontend",
|
|
573
|
+
agentKey: "frontend-local",
|
|
574
|
+
serverUrl: "https://nolo.chat",
|
|
575
|
+
message: "inspect repo in background",
|
|
576
|
+
scriptDir: "C:/missing/scripts",
|
|
577
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
578
|
+
output,
|
|
579
|
+
runtimeMode: "local",
|
|
580
|
+
background: true,
|
|
581
|
+
taskRunContext: {
|
|
582
|
+
rowDbKey: "row-b2e06f801f-01TASK",
|
|
583
|
+
workItemId: "self",
|
|
584
|
+
},
|
|
585
|
+
taskRunRecorder: async (args) => {
|
|
586
|
+
writebacks.push(args);
|
|
587
|
+
},
|
|
588
|
+
localRuntimeAdapter: {
|
|
589
|
+
host: "cli",
|
|
590
|
+
capabilities: ["local-provider", "local-persistence"],
|
|
591
|
+
loadAgentConfig: async (agentRef) => ({
|
|
592
|
+
key: agentRef,
|
|
593
|
+
name: "Frontend",
|
|
594
|
+
prompt: "Fix UI",
|
|
595
|
+
model: "fake-local",
|
|
596
|
+
}),
|
|
597
|
+
loadDialogHistory: async () => [],
|
|
598
|
+
saveTurn: async () => ({ dialogId: "dialog-local-bg" }),
|
|
599
|
+
resolveProvider: async () => ({
|
|
600
|
+
model: "fake-local",
|
|
601
|
+
complete: async () => ({ content: "accepted locally", model: "fake-local" }),
|
|
602
|
+
}),
|
|
603
|
+
executeTool: async () => {
|
|
604
|
+
throw new Error("no tools expected");
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
scriptPathExists: () => false,
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-bg" });
|
|
611
|
+
expect(writebacks).toHaveLength(1);
|
|
612
|
+
expect(writebacks[0]).toMatchObject({
|
|
613
|
+
status: "running",
|
|
614
|
+
dialogId: "dialog-local-bg",
|
|
615
|
+
});
|
|
616
|
+
expect(writebacks[0].resultSummary).toBeUndefined();
|
|
617
|
+
});
|
|
618
|
+
|
|
525
619
|
test("auto mode prefers a working local runtime before HTTP", async () => {
|
|
526
620
|
const output = new CaptureOutput();
|
|
527
621
|
const httpCalls: string[] = [];
|
|
@@ -566,6 +660,110 @@ describe("cli agent run client", () => {
|
|
|
566
660
|
expect(output.text()).toContain("frontend -> working locally");
|
|
567
661
|
});
|
|
568
662
|
|
|
663
|
+
test("auto mode refreshes missing local agent config and retries local once before HTTP", async () => {
|
|
664
|
+
const output = new CaptureOutput();
|
|
665
|
+
const httpCalls: string[] = [];
|
|
666
|
+
let loadAgentConfigCalls = 0;
|
|
667
|
+
let localCompletions = 0;
|
|
668
|
+
|
|
669
|
+
const result = await runAgentTurn({
|
|
670
|
+
agentName: "frontend",
|
|
671
|
+
agentKey: "frontend-local",
|
|
672
|
+
serverUrl: "https://nolo.chat",
|
|
673
|
+
message: "polish notifications",
|
|
674
|
+
scriptDir: "C:/missing/scripts",
|
|
675
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
676
|
+
output,
|
|
677
|
+
runtimeMode: "auto",
|
|
678
|
+
localRuntimeAdapter: {
|
|
679
|
+
host: "cli",
|
|
680
|
+
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence"],
|
|
681
|
+
loadAgentConfig: async (agentRef) => {
|
|
682
|
+
loadAgentConfigCalls += 1;
|
|
683
|
+
if (loadAgentConfigCalls < 3) return null;
|
|
684
|
+
return {
|
|
685
|
+
key: agentRef,
|
|
686
|
+
name: "Frontend",
|
|
687
|
+
prompt: "Fix UI",
|
|
688
|
+
model: "fake-local",
|
|
689
|
+
};
|
|
690
|
+
},
|
|
691
|
+
loadDialogHistory: async () => [],
|
|
692
|
+
saveTurn: async () => ({ dialogId: "dialog-auto-local-retried" }),
|
|
693
|
+
resolveProvider: async () => ({
|
|
694
|
+
model: "fake-local",
|
|
695
|
+
complete: async () => {
|
|
696
|
+
localCompletions += 1;
|
|
697
|
+
return { content: "auto local after refresh", model: "fake-local" };
|
|
698
|
+
},
|
|
699
|
+
}),
|
|
700
|
+
executeTool: async () => {
|
|
701
|
+
throw new Error("no tools expected");
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
scriptPathExists: () => false,
|
|
705
|
+
fetchImpl: async (url) => {
|
|
706
|
+
httpCalls.push(String(url));
|
|
707
|
+
return Response.json({ content: "server" });
|
|
708
|
+
},
|
|
709
|
+
});
|
|
710
|
+
|
|
711
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-auto-local-retried" });
|
|
712
|
+
expect(loadAgentConfigCalls).toBe(4);
|
|
713
|
+
expect(localCompletions).toBe(1);
|
|
714
|
+
expect(httpCalls).toEqual([]);
|
|
715
|
+
expect(output.text()).toContain("refreshing from the configured server and retrying local once");
|
|
716
|
+
expect(output.text().match(/working locally/g)?.length).toBe(2);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test("auto mode falls back to HTTP when local agent config is still missing after one refresh retry", async () => {
|
|
720
|
+
const output = new CaptureOutput();
|
|
721
|
+
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
722
|
+
let loadAgentConfigCalls = 0;
|
|
723
|
+
|
|
724
|
+
const result = await runAgentTurn({
|
|
725
|
+
agentName: "frontend",
|
|
726
|
+
agentKey: "frontend-local",
|
|
727
|
+
serverUrl: "https://nolo.chat",
|
|
728
|
+
message: "polish notifications",
|
|
729
|
+
scriptDir: "C:/missing/scripts",
|
|
730
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
731
|
+
output,
|
|
732
|
+
runtimeMode: "auto",
|
|
733
|
+
localRuntimeAdapter: {
|
|
734
|
+
host: "cli",
|
|
735
|
+
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence"],
|
|
736
|
+
loadAgentConfig: async () => {
|
|
737
|
+
loadAgentConfigCalls += 1;
|
|
738
|
+
return null;
|
|
739
|
+
},
|
|
740
|
+
loadDialogHistory: async () => [],
|
|
741
|
+
saveTurn: async () => {
|
|
742
|
+
throw new Error("local runtime should not save when config is missing");
|
|
743
|
+
},
|
|
744
|
+
resolveProvider: async () => {
|
|
745
|
+
throw new Error("local provider should not run when config is missing");
|
|
746
|
+
},
|
|
747
|
+
executeTool: async () => {
|
|
748
|
+
throw new Error("no tools expected");
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
scriptPathExists: () => false,
|
|
752
|
+
fetchImpl: async (url, init) => {
|
|
753
|
+
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
754
|
+
return Response.json({ content: "server fallback", dialogId: "dialog-server-fallback" });
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-server-fallback" });
|
|
759
|
+
expect(loadAgentConfigCalls).toBe(3);
|
|
760
|
+
expect(httpCalls).toHaveLength(1);
|
|
761
|
+
expect(httpCalls[0]?.body.agentKey).toBe("frontend-local");
|
|
762
|
+
expect(output.text()).toContain("refreshing from the configured server and retrying local once");
|
|
763
|
+
expect(output.text()).toContain("frontend -> working");
|
|
764
|
+
expect(output.text()).toContain("frontend > server fallback");
|
|
765
|
+
});
|
|
766
|
+
|
|
569
767
|
test("auto mode skips local runtime for agents that declare server platform tools", async () => {
|
|
570
768
|
const output = new CaptureOutput();
|
|
571
769
|
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
@@ -616,6 +814,58 @@ describe("cli agent run client", () => {
|
|
|
616
814
|
expect(output.text()).toContain("pm > server ok");
|
|
617
815
|
});
|
|
618
816
|
|
|
817
|
+
test("auto mode skips local runtime for server platform tools declared by runtime policy", async () => {
|
|
818
|
+
const output = new CaptureOutput();
|
|
819
|
+
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
820
|
+
|
|
821
|
+
const result = await runAgentTurn({
|
|
822
|
+
agentName: "pm",
|
|
823
|
+
agentKey: "agent-policy-platform-tools",
|
|
824
|
+
serverUrl: "https://nolo.chat",
|
|
825
|
+
message: "query task rows",
|
|
826
|
+
scriptDir: "C:/missing/scripts",
|
|
827
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
828
|
+
output,
|
|
829
|
+
runtimeMode: "auto",
|
|
830
|
+
localRuntimeAdapter: {
|
|
831
|
+
host: "cli",
|
|
832
|
+
capabilities: ["leveldb-agent-config", "local-provider", "local-tools"],
|
|
833
|
+
loadAgentConfig: async (agentRef) => ({
|
|
834
|
+
key: agentRef,
|
|
835
|
+
name: "PM",
|
|
836
|
+
prompt: "Manage task rows",
|
|
837
|
+
model: "fake-local",
|
|
838
|
+
runtimeToolPolicy: {
|
|
839
|
+
version: 1,
|
|
840
|
+
agentTools: ["queryTableRows", "taskRun"],
|
|
841
|
+
runtimeTools: ["execShell"],
|
|
842
|
+
},
|
|
843
|
+
}),
|
|
844
|
+
loadDialogHistory: async () => [],
|
|
845
|
+
saveTurn: async () => {
|
|
846
|
+
throw new Error("local runtime should be skipped");
|
|
847
|
+
},
|
|
848
|
+
resolveProvider: async () => {
|
|
849
|
+
throw new Error("local provider should be skipped");
|
|
850
|
+
},
|
|
851
|
+
executeTool: async () => {
|
|
852
|
+
throw new Error("local tools should be skipped");
|
|
853
|
+
},
|
|
854
|
+
},
|
|
855
|
+
scriptPathExists: () => false,
|
|
856
|
+
fetchImpl: async (url, init) => {
|
|
857
|
+
httpCalls.push({ url: String(url), body: JSON.parse(String(init?.body)) });
|
|
858
|
+
return Response.json({ content: "server ok", dialogId: "dialog-server-policy" });
|
|
859
|
+
},
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-server-policy" });
|
|
863
|
+
expect(httpCalls).toHaveLength(1);
|
|
864
|
+
expect(httpCalls[0]?.body.agentKey).toBe("agent-policy-platform-tools");
|
|
865
|
+
expect(output.text()).toContain("auto runtime: skipping local runtime");
|
|
866
|
+
expect(output.text()).toContain("queryTableRows, taskRun");
|
|
867
|
+
});
|
|
868
|
+
|
|
619
869
|
test("auto mode skips known platform agents when local config cannot be read", async () => {
|
|
620
870
|
const output = new CaptureOutput();
|
|
621
871
|
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
@@ -733,11 +983,67 @@ describe("cli agent run client", () => {
|
|
|
733
983
|
});
|
|
734
984
|
|
|
735
985
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-stream" });
|
|
736
|
-
expect(output.text()).toContain("nolo -> working");
|
|
737
|
-
expect(output.text()).toContain("nolo > 你好");
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
test("
|
|
986
|
+
expect(output.text()).toContain("nolo -> working");
|
|
987
|
+
expect(output.text()).toContain("nolo > 你好");
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
test("returns a recoverable dialog when a server stream drops after dialog creation", async () => {
|
|
991
|
+
const output = new CaptureOutput();
|
|
992
|
+
|
|
993
|
+
const result = await runAgentTurn({
|
|
994
|
+
agentName: "nolo",
|
|
995
|
+
agentKey: "agent-pub-test",
|
|
996
|
+
serverUrl: "https://nolo.chat",
|
|
997
|
+
message: "hello",
|
|
998
|
+
scriptDir: "C:/missing/scripts",
|
|
999
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
1000
|
+
output,
|
|
1001
|
+
scriptPathExists: () => false,
|
|
1002
|
+
fetchImpl: async () => {
|
|
1003
|
+
let sent = false;
|
|
1004
|
+
return new Response(
|
|
1005
|
+
new ReadableStream({
|
|
1006
|
+
pull(controller) {
|
|
1007
|
+
const encoder = new TextEncoder();
|
|
1008
|
+
if (!sent) {
|
|
1009
|
+
sent = true;
|
|
1010
|
+
controller.enqueue(
|
|
1011
|
+
encoder.encode(
|
|
1012
|
+
[
|
|
1013
|
+
`data: ${JSON.stringify({
|
|
1014
|
+
type: "dialog",
|
|
1015
|
+
dialogId: "dialog-recoverable",
|
|
1016
|
+
status: "running",
|
|
1017
|
+
})}`,
|
|
1018
|
+
"",
|
|
1019
|
+
`data: ${JSON.stringify({ type: "text", content: "partial" })}`,
|
|
1020
|
+
"",
|
|
1021
|
+
].join("\n")
|
|
1022
|
+
)
|
|
1023
|
+
);
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
controller.error(new Error("socket closed"));
|
|
1027
|
+
},
|
|
1028
|
+
}),
|
|
1029
|
+
{
|
|
1030
|
+
status: 200,
|
|
1031
|
+
headers: { "Content-Type": "text/event-stream" },
|
|
1032
|
+
}
|
|
1033
|
+
);
|
|
1034
|
+
},
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
expect(result).toEqual({
|
|
1038
|
+
exitCode: 0,
|
|
1039
|
+
dialogId: "dialog-recoverable",
|
|
1040
|
+
streamInterrupted: true,
|
|
1041
|
+
});
|
|
1042
|
+
expect(output.text()).toContain("dialog dialog-recoverable was created");
|
|
1043
|
+
expect(output.text()).toContain("read the dialog before retrying");
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
test("prints an auth hint when installed without repo scripts or AUTH_TOKEN", async () => {
|
|
741
1047
|
const output = new CaptureOutput();
|
|
742
1048
|
|
|
743
1049
|
const result = await runAgentTurn({
|