nolo-cli 0.1.13 → 0.1.14
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 +9 -2
- package/agent-runtime/hostAdapter.ts +53 -0
- package/agent-runtime/index.ts +28 -0
- package/agent-runtime/localLoop.ts +62 -0
- package/agent-runtime/runtimeDecision.ts +70 -0
- package/agent-runtime/types.ts +87 -0
- package/agentRuntimeCommands.ts +139 -22
- package/agentRuntimeLocal.ts +7 -0
- package/ai/agent/_executeModel.ts +118 -0
- package/ai/agent/agentSlice.ts +544 -1
- package/ai/agent/appWorkingMemory.ts +126 -0
- package/ai/agent/avatarUtils.ts +24 -0
- package/ai/agent/buildEditingContext.ts +373 -0
- package/ai/agent/buildSystemPrompt.ts +532 -0
- package/ai/agent/cleanAgentMessages.ts +140 -0
- package/ai/agent/cliChatClient.ts +119 -0
- package/ai/agent/contextCompiler.ts +107 -0
- package/ai/agent/contextLayerContract.ts +44 -0
- package/ai/agent/createAgentSchema.ts +234 -0
- package/ai/agent/executeToolCall.ts +58 -0
- package/ai/agent/fetchAgentContexts.ts +42 -0
- package/ai/agent/generatePrompt.ts +3 -0
- package/ai/agent/getFullChatContextKeys.ts +168 -0
- package/ai/agent/hooks/fetchPublicAgents.ts +133 -0
- package/ai/agent/hooks/useAgentConfig.ts +61 -0
- package/ai/agent/hooks/useAgentDialog.ts +35 -0
- package/ai/agent/hooks/useAgentFormValidation.ts +202 -0
- package/ai/agent/hooks/usePublicAgents.ts +473 -0
- package/ai/agent/persistMessageWithFixedId.ts +37 -0
- package/ai/agent/planSlice.ts +259 -0
- package/ai/agent/referenceUtils.ts +229 -0
- package/ai/agent/runAgentBackground.ts +238 -0
- package/ai/agent/runAgentClientLoop.ts +138 -0
- package/ai/agent/runtimeGuidance.ts +97 -0
- package/ai/agent/runtimeServerBase.ts +37 -0
- package/ai/agent/server/fetchPublicAgents.ts +128 -0
- package/ai/agent/startParallelAgentStreams.ts +424 -0
- package/ai/agent/startupProtocol.ts +53 -0
- package/ai/agent/streamAgentChatTurn.ts +1299 -0
- package/ai/agent/streamAgentChatTurnUtils.ts +738 -0
- package/ai/agent/types.ts +71 -0
- package/ai/agent/utils/imageOutput.ts +39 -0
- package/ai/agent/utils/publicImageAgentMode.ts +26 -0
- package/ai/agent/utils/sortUtils.ts +250 -0
- package/ai/agent/web/referencePickerUtils.ts +146 -0
- package/ai/ai.locale.ts +1083 -0
- package/ai/chat/accumulateToolCallChunks.ts +95 -0
- package/ai/chat/fetchUtils.native.ts +276 -0
- package/ai/chat/fetchUtils.ts +153 -0
- package/ai/chat/inlineImageUrlsForCustomProvider.ts +117 -0
- package/ai/chat/parseApiError.ts +64 -0
- package/ai/chat/parseMultilineSSE.ts +95 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +682 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +712 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +512 -0
- package/ai/chat/shouldUseServerProxy.ts +18 -0
- package/ai/chat/sseClient.native.ts +91 -0
- package/ai/chat/sseClient.ts +67 -0
- package/ai/chat/streamReader.native.ts +31 -0
- package/ai/chat/streamReader.ts +62 -0
- package/ai/chat/updateTotalUsage.ts +72 -0
- package/ai/context/buildReferenceContext.ts +437 -0
- package/ai/context/calculateContextUsage.ts +133 -0
- package/ai/context/retention.ts +165 -0
- package/ai/context/tokenUtils.ts +78 -0
- package/ai/index.ts +1 -1
- package/ai/llm/agentCapabilities.ts +74 -0
- package/ai/llm/calculateGeminiImageTokens.ts +57 -0
- package/ai/llm/deepinfra.ts +28 -0
- package/ai/llm/fireworks.ts +68 -0
- package/ai/llm/generateRequestBody.ts +165 -0
- package/ai/llm/getModelContextWindow.ts +84 -0
- package/ai/llm/getNoloKey.ts +37 -0
- package/ai/llm/getPricing.ts +232 -0
- package/ai/llm/hooks/useModelPricing.ts +75 -0
- package/ai/llm/imagePricing.ts +66 -0
- package/ai/llm/isResponseAPIModel.ts +13 -0
- package/ai/llm/kimi.ts +18 -0
- package/ai/llm/mimo.ts +71 -0
- package/ai/llm/mistral.ts +22 -0
- package/ai/llm/modelAvatar.ts +427 -0
- package/ai/llm/models.ts +45 -0
- package/ai/llm/openrouterModels.ts +141 -0
- package/ai/llm/providers.ts +307 -0
- package/ai/llm/reasoningModels.ts +28 -0
- package/ai/llm/types.ts +59 -0
- package/ai/llm/usageRequestOptions.ts +59 -0
- package/ai/memory/capture.ts +148 -0
- package/ai/memory/consolidate.ts +104 -0
- package/ai/memory/delete.ts +147 -0
- package/ai/memory/overlay.ts +84 -0
- package/ai/memory/query.ts +38 -0
- package/ai/memory/queryShared.ts +160 -0
- package/ai/memory/rank.ts +105 -0
- package/ai/memory/recentRelationshipRecap.ts +247 -0
- package/ai/memory/remember.ts +167 -0
- package/ai/memory/runtime.ts +76 -0
- package/ai/memory/store.ts +20 -0
- package/ai/memory/storeShared.ts +76 -0
- package/ai/memory/types.ts +46 -0
- package/ai/memory/understanding.ts +349 -0
- package/ai/memory/understandingGreeting.ts +264 -0
- package/ai/messages/type.ts +20 -0
- package/ai/policy/personalizationDialog.ts +333 -0
- package/ai/policy/runtimePolicy.ts +440 -0
- package/ai/policy/selfUpdateFields.ts +48 -0
- package/ai/policy/types.ts +64 -0
- package/ai/skills/referenceRuntime.ts +274 -0
- package/ai/skills/skillDiagnostics.ts +251 -0
- package/ai/skills/skillDocBuilder.ts +139 -0
- package/ai/skills/skillDocProtocol.ts +434 -0
- package/ai/skills/skillReferenceSummary.ts +63 -0
- package/ai/skills/skillSummaryMarker.ts +26 -0
- package/ai/token/calculatePrice.ts +546 -0
- package/ai/token/db.ts +98 -0
- package/ai/token/externalToolCost.ts +321 -0
- package/ai/token/hooks/useRecords.ts +65 -0
- package/ai/token/missingUsageEstimate.ts +42 -0
- package/ai/token/modelUsageQuery.ts +252 -0
- package/ai/token/normalizeUsage.ts +84 -0
- package/ai/token/openaiImageGenerationUsage.ts +56 -0
- package/ai/token/prepareTokenUsageData.ts +88 -0
- package/ai/token/query.ts +88 -0
- package/ai/token/queryUserTokens.ts +59 -0
- package/ai/token/resolveBillingTarget.ts +52 -0
- package/ai/token/saveTokenRecord.ts +53 -0
- package/ai/token/serverDialogProjection.ts +78 -0
- package/ai/token/serverTokenWriter.ts +143 -0
- package/ai/token/stats.ts +21 -0
- package/ai/token/tokenThunks.ts +24 -0
- package/ai/token/types.ts +93 -0
- package/ai/tools/agent/agentTools.ts +176 -0
- package/ai/tools/agent/agentUpdateShared.ts +311 -0
- package/ai/tools/agent/callAgentTool.ts +139 -0
- package/ai/tools/agent/createAgentTool.ts +512 -0
- package/ai/tools/agent/createDialogTool.ts +69 -0
- package/ai/tools/agent/createSkillAgentTool.ts +62 -0
- package/ai/tools/agent/parallelBudget.ts +221 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +147 -0
- package/ai/tools/agent/runLlmTool.ts +96 -0
- package/ai/tools/agent/runStreamingAgentTool.ts +73 -0
- package/ai/tools/agent/skillAgentArgs.ts +106 -0
- package/ai/tools/agent/skillAgentPreset.ts +89 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +122 -0
- package/ai/tools/agent/updateAgentTool.ts +96 -0
- package/ai/tools/agent/updateSelfTool.ts +113 -0
- package/ai/tools/amazonProductScraperTool.ts +86 -0
- package/ai/tools/apifyActorClient.ts +45 -0
- package/ai/tools/appEditGuard.ts +372 -0
- package/ai/tools/appReadSnapshot.ts +153 -0
- package/ai/tools/appTools.ts +1549 -0
- package/ai/tools/applyEditTool.ts +256 -0
- package/ai/tools/applyLineEditsTool.ts +312 -0
- package/ai/tools/browserTools/click.ts +33 -0
- package/ai/tools/browserTools/closeSession.ts +29 -0
- package/ai/tools/browserTools/common.ts +27 -0
- package/ai/tools/browserTools/openSession.ts +48 -0
- package/ai/tools/browserTools/readContent.ts +38 -0
- package/ai/tools/browserTools/selectOption.ts +46 -0
- package/ai/tools/browserTools/typeText.ts +42 -0
- package/ai/tools/category/createCategoryTool.ts +66 -0
- package/ai/tools/category/queryContentsByCategoryTool.ts +69 -0
- package/ai/tools/category/updateContentCategoryTool.ts +75 -0
- package/ai/tools/cfBrowserTools.ts +319 -0
- package/ai/tools/cfSpeechToTextTool.ts +49 -0
- package/ai/tools/checkEnvTool.ts +65 -0
- package/ai/tools/cloudflareCrawlTool.ts +289 -0
- package/ai/tools/codeSearchTool.ts +111 -0
- package/ai/tools/codeTools.ts +101 -0
- package/ai/tools/createDocTool.ts +132 -0
- package/ai/tools/createPlanTool.ts +999 -0
- package/ai/tools/createSkillDocTool.ts +155 -0
- package/ai/tools/createWorkflowTool.ts +154 -0
- package/ai/tools/deepseekOcrTool.ts +34 -0
- package/ai/tools/delayTool.ts +31 -0
- package/ai/tools/deleteSpacesTool.ts +325 -0
- package/ai/tools/deleteSpacesToolModel.ts +159 -0
- package/ai/tools/devReloadUtils.ts +29 -0
- package/ai/tools/dialogMessageSearch.ts +137 -0
- package/ai/tools/doctorSkillTool.ts +72 -0
- package/ai/tools/ecommerceScraperTool.ts +86 -0
- package/ai/tools/emailTools.ts +549 -0
- package/ai/tools/evalSkillTool.ts +92 -0
- package/ai/tools/exaSearchTool.ts +64 -0
- package/ai/tools/execBashTool.ts +379 -0
- package/ai/tools/executeSqlTool.ts +192 -0
- package/ai/tools/fetchWebpageSupport.ts +309 -0
- package/ai/tools/fetchWebpageTool.ts +84 -0
- package/ai/tools/geminiImagePreviewTool.ts +361 -0
- package/ai/tools/generateDocxTool.ts +215 -0
- package/ai/tools/googleSearchScraperTool.ts +106 -0
- package/ai/tools/importDataTool.ts +133 -0
- package/ai/tools/importSkillTool.ts +162 -0
- package/ai/tools/index.ts +1927 -0
- package/ai/tools/listFilesTool.ts +82 -0
- package/ai/tools/listUserSpacesTool.ts +113 -0
- package/ai/tools/modelUsageTools.ts +199 -0
- package/ai/tools/olmOcrTool.ts +34 -0
- package/ai/tools/openaiImageTool.ts +267 -0
- package/ai/tools/prepareTools.ts +23 -0
- package/ai/tools/readDocTool.ts +84 -0
- package/ai/tools/readFileTool.ts +211 -0
- package/ai/tools/readTool.ts +163 -0
- package/ai/tools/readXPostTool.ts +233 -0
- package/ai/tools/rememberMemoryTool.ts +84 -0
- package/ai/tools/remotionVideoTool.ts +151 -0
- package/ai/tools/searchDialogMessagesTool.ts +222 -0
- package/ai/tools/searchRepoTool.ts +115 -0
- package/ai/tools/searchWorkspaceTool.ts +259 -0
- package/ai/tools/skillFollowup.ts +86 -0
- package/ai/tools/surfWeatherTool.ts +169 -0
- package/ai/tools/table/addTableRowTool.ts +217 -0
- package/ai/tools/table/createTableTool.ts +315 -0
- package/ai/tools/table/rowTools.ts +366 -0
- package/ai/tools/table/schemaTools.ts +244 -0
- package/ai/tools/table/shareTableTool.ts +148 -0
- package/ai/tools/table/toolShared.ts +129 -0
- package/ai/tools/toolApiClient.ts +198 -0
- package/ai/tools/toolNameAliases.ts +57 -0
- package/ai/tools/toolResultError.ts +42 -0
- package/ai/tools/toolRunSlice.ts +303 -0
- package/ai/tools/toolSchemaCompatibility.ts +53 -0
- package/ai/tools/toolVisibility.ts +4 -0
- package/ai/tools/types.ts +20 -0
- package/ai/tools/uiAskChoiceTool.ts +104 -0
- package/ai/tools/updateContentTitleTool.ts +84 -0
- package/ai/tools/updateDocTool.ts +105 -0
- package/ai/tools/updateUserPreferenceProfileTool.ts +145 -0
- package/ai/tools/whisperTool.ts +77 -0
- package/ai/tools/writeFileTool.ts +210 -0
- package/ai/tools/youtubeScraperTool.ts +116 -0
- package/ai/tools/ziweiChartTool.ts +678 -0
- package/ai/types.ts +55 -0
- package/ai/workflow/workflowExecutor.ts +323 -0
- package/ai/workflow/workflowSlice.ts +73 -0
- package/ai/workflow/workflowTypes.ts +106 -0
- package/client/agentRun.test.ts +240 -0
- package/client/agentRun.ts +182 -19
- package/client/compactDialog.test.ts +238 -0
- package/client/localRuntimeAdapter.test.ts +135 -0
- package/client/localRuntimeAdapter.ts +244 -0
- package/client/profileConfig.test.ts +40 -0
- package/client/streamingOutput.test.ts +22 -0
- package/client/streamingOutput.ts +38 -0
- package/commandRegistry.ts +9 -2
- package/connector-experimental/index.ts +5 -0
- package/database/actions/cacheMergedUserData.ts +64 -0
- package/database/actions/common.ts +242 -0
- package/database/actions/deleteFile.ts +40 -0
- package/database/actions/fetchUserData.ts +16 -0
- package/database/actions/fileContent.ts +125 -0
- package/database/actions/patch.ts +155 -0
- package/database/actions/read.ts +337 -0
- package/database/actions/readAndWait.ts +224 -0
- package/database/actions/readRequestManager.ts +120 -0
- package/database/actions/remove.ts +94 -0
- package/database/actions/replication.ts +366 -0
- package/database/actions/upload.ts +174 -0
- package/database/actions/upsert.ts +56 -0
- package/database/actions/write.ts +126 -0
- package/database/client/db.native.ts +73 -0
- package/database/client/db.ts +51 -0
- package/database/client/fetchUserData.ts +61 -0
- package/database/client/handleError.ts +19 -0
- package/database/client/queryRequest.ts +21 -0
- package/database/config.ts +21 -0
- package/database/dbActionThunks.ts +1 -0
- package/database/dbSlice.ts +149 -0
- package/database/email.ts +42 -0
- package/database/fileRing.ts +51 -0
- package/database/fileSharding.ts +70 -0
- package/database/fileStorage.native.ts +92 -0
- package/database/fileStorage.ts +232 -0
- package/database/fileUrl.ts +34 -0
- package/database/hooks/useUserData.ts +489 -0
- package/database/index.ts +1 -0
- package/database/keys.ts +765 -0
- package/database/queryPrefixes.ts +14 -0
- package/database/requests.ts +443 -0
- package/database/runtimeServerContext.ts +35 -0
- package/database/server/MemoryDB.ts +76 -0
- package/database/server/actorAccess.ts +76 -0
- package/database/server/agentDelegation.ts +124 -0
- package/database/server/coreDataOwnership.ts +13 -0
- package/database/server/coreDataProxy.ts +76 -0
- package/database/server/cybotReadonly.ts +18 -0
- package/database/server/dataHandlers.ts +111 -0
- package/database/server/db.ts +118 -0
- package/database/server/dbPath.ts +20 -0
- package/database/server/delete.ts +499 -0
- package/database/server/emailRepository.ts +1480 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/fileRead.ts +337 -0
- package/database/server/fileService.ts +436 -0
- package/database/server/handleTransaction.ts +86 -0
- package/database/server/patch.ts +282 -0
- package/database/server/query.ts +138 -0
- package/database/server/read.ts +325 -0
- package/database/server/resourceAccess.ts +211 -0
- package/database/server/routes.ts +110 -0
- package/database/server/spaceMemberAuthority.ts +67 -0
- package/database/server/upload.ts +159 -0
- package/database/server/write.ts +494 -0
- package/database/server/writeAuthority.ts +133 -0
- package/database/sqliteDb.ts +46 -0
- package/database/table/deleteTable.ts +120 -0
- package/database/tenantPlacement.ts +57 -0
- package/database/tombstones.ts +52 -0
- package/database/userDataLoadDecision.ts +17 -0
- package/database/userDataMerge.ts +95 -0
- package/database/userPreferenceRegister.ts +108 -0
- package/database/utils/dbPath.ts +47 -0
- package/database/utils/ulid.native.ts +6 -0
- package/database/utils/ulid.ts +1 -0
- package/index.ts +25 -15
- package/localRuntimeDb.ts +28 -0
- package/package.json +16 -4
- package/runtimeModeArgs.ts +33 -0
- package/tui/readlineWorkspace.ts +1 -0
- package/tui/session.ts +22 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { runAgentTurn, shouldUseScriptBridge } from "./agentRun";
|
|
5
|
+
|
|
6
|
+
class CaptureOutput extends Writable {
|
|
7
|
+
chunks: string[] = [];
|
|
8
|
+
|
|
9
|
+
_write(chunk: unknown, _encoding: BufferEncoding, callback: (error?: Error | null) => void) {
|
|
10
|
+
this.chunks.push(String(chunk));
|
|
11
|
+
callback();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
text() {
|
|
15
|
+
return this.chunks.join("");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("cli agent run client", () => {
|
|
20
|
+
test("uses the repo script bridge only when no auth token is available", () => {
|
|
21
|
+
expect(shouldUseScriptBridge({ hasAuthToken: false, scriptPathExists: true })).toBe(true);
|
|
22
|
+
expect(shouldUseScriptBridge({ hasAuthToken: true, scriptPathExists: true })).toBe(false);
|
|
23
|
+
expect(shouldUseScriptBridge({ hasAuthToken: false, scriptPathExists: false })).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("calls the Nolo HTTP API directly when AUTH_TOKEN is present", async () => {
|
|
27
|
+
const output = new CaptureOutput();
|
|
28
|
+
const requests: Array<{ url: string; body: any; auth: string | null }> = [];
|
|
29
|
+
|
|
30
|
+
const result = await runAgentTurn({
|
|
31
|
+
agentName: "nolo",
|
|
32
|
+
agentKey: "agent-pub-test",
|
|
33
|
+
serverUrl: "https://nolo.chat",
|
|
34
|
+
message: "hello",
|
|
35
|
+
continueDialogId: "dialog-existing",
|
|
36
|
+
scriptDir: "C:/missing/scripts",
|
|
37
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
38
|
+
output,
|
|
39
|
+
scriptPathExists: () => false,
|
|
40
|
+
fetchImpl: async (url, init) => {
|
|
41
|
+
requests.push({
|
|
42
|
+
url: String(url),
|
|
43
|
+
body: JSON.parse(String(init?.body)),
|
|
44
|
+
auth: new Headers(init?.headers).get("Authorization"),
|
|
45
|
+
});
|
|
46
|
+
return Response.json({
|
|
47
|
+
content: "hi",
|
|
48
|
+
dialogId: "dialog-1",
|
|
49
|
+
usage: { input_tokens: 2, output_tokens: 3 },
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(requests).toEqual([
|
|
55
|
+
{
|
|
56
|
+
url: "https://nolo.chat/api/agent/run",
|
|
57
|
+
auth: "Bearer token-123",
|
|
58
|
+
body: {
|
|
59
|
+
agentKey: "agent-pub-test",
|
|
60
|
+
userInput: "hello",
|
|
61
|
+
continueDialogId: "dialog-existing",
|
|
62
|
+
runtimeContext: {
|
|
63
|
+
surface: "cli",
|
|
64
|
+
host: "terminal",
|
|
65
|
+
runtime: "bun",
|
|
66
|
+
entrypoint: "nolo-cli",
|
|
67
|
+
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
68
|
+
},
|
|
69
|
+
stream: true,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-1" });
|
|
74
|
+
expect(output.text()).toContain("nolo -> working");
|
|
75
|
+
expect(output.text()).toContain("nolo > hi");
|
|
76
|
+
expect(output.text()).not.toContain("tokens=");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("runs forced local turns through the injected runtime adapter without HTTP", async () => {
|
|
80
|
+
const output = new CaptureOutput();
|
|
81
|
+
const result = await runAgentTurn({
|
|
82
|
+
agentName: "frontend",
|
|
83
|
+
agentKey: "frontend-local",
|
|
84
|
+
serverUrl: "https://nolo.chat",
|
|
85
|
+
message: "polish notifications",
|
|
86
|
+
scriptDir: "C:/missing/scripts",
|
|
87
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
88
|
+
output,
|
|
89
|
+
runtimeMode: "local",
|
|
90
|
+
localRuntimeAdapter: {
|
|
91
|
+
host: "cli",
|
|
92
|
+
capabilities: ["local-provider", "local-persistence"],
|
|
93
|
+
loadAgentConfig: async (agentRef) => ({
|
|
94
|
+
key: agentRef,
|
|
95
|
+
name: "Frontend",
|
|
96
|
+
prompt: "Fix UI",
|
|
97
|
+
model: "fake-local",
|
|
98
|
+
}),
|
|
99
|
+
loadDialogHistory: async () => [],
|
|
100
|
+
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
101
|
+
resolveProvider: async () => ({
|
|
102
|
+
model: "fake-local",
|
|
103
|
+
complete: async (messages) => ({
|
|
104
|
+
content: `local:${messages.at(-1)?.content}`,
|
|
105
|
+
model: "fake-local",
|
|
106
|
+
trace: messages,
|
|
107
|
+
}),
|
|
108
|
+
}),
|
|
109
|
+
executeTool: async () => {
|
|
110
|
+
throw new Error("no tools expected");
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
scriptPathExists: () => false,
|
|
114
|
+
fetchImpl: async () => {
|
|
115
|
+
throw new Error("HTTP should not be called for forced local runs");
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local" });
|
|
120
|
+
expect(output.text()).toContain("frontend -> working locally");
|
|
121
|
+
expect(output.text()).toContain("frontend > local:polish notifications");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("builds the default local adapter when env requests local mode", async () => {
|
|
125
|
+
const output = new CaptureOutput();
|
|
126
|
+
const builtModes: string[] = [];
|
|
127
|
+
|
|
128
|
+
const result = await runAgentTurn({
|
|
129
|
+
agentName: "frontend",
|
|
130
|
+
agentKey: "frontend-local",
|
|
131
|
+
serverUrl: "https://nolo.chat",
|
|
132
|
+
message: "polish notifications",
|
|
133
|
+
scriptDir: "C:/missing/scripts",
|
|
134
|
+
env: { NOLO_RUNTIME_MODE: "local" },
|
|
135
|
+
output,
|
|
136
|
+
localRuntimeAdapterFactory: (env) => {
|
|
137
|
+
builtModes.push(env.NOLO_RUNTIME_MODE ?? "");
|
|
138
|
+
return {
|
|
139
|
+
host: "cli",
|
|
140
|
+
capabilities: ["local-provider", "local-persistence"],
|
|
141
|
+
loadAgentConfig: async (agentRef) => ({ key: agentRef, prompt: "Fix UI" }),
|
|
142
|
+
loadDialogHistory: async () => [],
|
|
143
|
+
saveTurn: async () => ({ dialogId: "dialog-local-env" }),
|
|
144
|
+
resolveProvider: async () => ({
|
|
145
|
+
model: "fake-local",
|
|
146
|
+
complete: async () => ({ content: "local env ok", model: "fake-local" }),
|
|
147
|
+
}),
|
|
148
|
+
executeTool: async () => {
|
|
149
|
+
throw new Error("no tools expected");
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
},
|
|
153
|
+
scriptPathExists: () => false,
|
|
154
|
+
fetchImpl: async () => {
|
|
155
|
+
throw new Error("HTTP should not be called for env local runs");
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-env" });
|
|
160
|
+
expect(builtModes).toEqual(["local"]);
|
|
161
|
+
expect(output.text()).toContain("frontend -> working locally");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("streams agent text responses to terminal output", async () => {
|
|
165
|
+
const output = new CaptureOutput();
|
|
166
|
+
|
|
167
|
+
const result = await runAgentTurn({
|
|
168
|
+
agentName: "nolo",
|
|
169
|
+
agentKey: "agent-pub-test",
|
|
170
|
+
serverUrl: "https://nolo.chat",
|
|
171
|
+
message: "hello",
|
|
172
|
+
scriptDir: "C:/missing/scripts",
|
|
173
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
174
|
+
output,
|
|
175
|
+
scriptPathExists: () => false,
|
|
176
|
+
fetchImpl: async () =>
|
|
177
|
+
new Response(
|
|
178
|
+
[
|
|
179
|
+
`data: ${JSON.stringify({ type: "text", content: "你" })}`,
|
|
180
|
+
"",
|
|
181
|
+
`data: ${JSON.stringify({ type: "text", content: "好" })}`,
|
|
182
|
+
"",
|
|
183
|
+
`data: ${JSON.stringify({ type: "done", dialogId: "dialog-stream" })}`,
|
|
184
|
+
"",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
{
|
|
187
|
+
status: 200,
|
|
188
|
+
headers: { "Content-Type": "text/event-stream" },
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-stream" });
|
|
194
|
+
expect(output.text()).toContain("nolo -> working");
|
|
195
|
+
expect(output.text()).toContain("nolo > 你好");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("prints an auth hint when installed without repo scripts or AUTH_TOKEN", async () => {
|
|
199
|
+
const output = new CaptureOutput();
|
|
200
|
+
|
|
201
|
+
const result = await runAgentTurn({
|
|
202
|
+
agentName: "nolo",
|
|
203
|
+
agentKey: "agent-pub-test",
|
|
204
|
+
serverUrl: "https://nolo.chat",
|
|
205
|
+
message: "hello",
|
|
206
|
+
scriptDir: "C:/missing/scripts",
|
|
207
|
+
env: {},
|
|
208
|
+
output,
|
|
209
|
+
scriptPathExists: () => false,
|
|
210
|
+
fetchImpl: async () => {
|
|
211
|
+
throw new Error("fetch should not be called");
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.exitCode).toBe(1);
|
|
216
|
+
expect(output.text()).toContain("Set AUTH_TOKEN");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("prints a friendly connection hint instead of crashing on transport errors", async () => {
|
|
220
|
+
const output = new CaptureOutput();
|
|
221
|
+
|
|
222
|
+
const result = await runAgentTurn({
|
|
223
|
+
agentName: "nolo",
|
|
224
|
+
agentKey: "agent-pub-test",
|
|
225
|
+
serverUrl: "http://127.0.0.1:38123",
|
|
226
|
+
message: "hello",
|
|
227
|
+
scriptDir: "C:/missing/scripts",
|
|
228
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
229
|
+
output,
|
|
230
|
+
scriptPathExists: () => false,
|
|
231
|
+
fetchImpl: async () => {
|
|
232
|
+
throw new Error("ConnectionRefused");
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(result).toEqual({ exitCode: 1 });
|
|
237
|
+
expect(output.text()).toContain("Could not reach http://127.0.0.1:38123/api/agent/run");
|
|
238
|
+
expect(output.text()).toContain("set NOLO_SERVER");
|
|
239
|
+
});
|
|
240
|
+
});
|
package/client/agentRun.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
4
|
+
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
5
|
+
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
6
|
+
import { createStreamingTextWriter } from "./streamingOutput";
|
|
3
7
|
|
|
4
8
|
type EnvLike = Record<string, string | undefined>;
|
|
5
9
|
|
|
@@ -15,10 +19,13 @@ type RunAgentTurnOptions = {
|
|
|
15
19
|
continueDialogId?: string;
|
|
16
20
|
scriptDir: string;
|
|
17
21
|
env: EnvLike;
|
|
18
|
-
output: OutputLike;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
output: OutputLike;
|
|
23
|
+
runtimeMode?: AgentRuntimeRequestedMode;
|
|
24
|
+
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
25
|
+
localRuntimeAdapterFactory?: (env: EnvLike) => AgentRuntimeHostAdapter;
|
|
26
|
+
scriptPathExists?: (path: string) => boolean;
|
|
27
|
+
fetchImpl?: typeof fetch;
|
|
28
|
+
};
|
|
22
29
|
|
|
23
30
|
export type RunAgentTurnResult = {
|
|
24
31
|
exitCode: number;
|
|
@@ -38,9 +45,23 @@ function resolveAuthToken(env: EnvLike) {
|
|
|
38
45
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
function shouldShowUsage(env: EnvLike) {
|
|
42
|
-
return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
|
|
43
|
-
}
|
|
48
|
+
function shouldShowUsage(env: EnvLike) {
|
|
49
|
+
return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolveRequestedRuntimeMode(options: RunAgentTurnOptions) {
|
|
53
|
+
const envMode = options.env.NOLO_RUNTIME_MODE;
|
|
54
|
+
if (options.runtimeMode) return options.runtimeMode;
|
|
55
|
+
if (envMode === "local" || envMode === "server" || envMode === "auto") return envMode;
|
|
56
|
+
return "auto";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildDefaultLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
60
|
+
return createCliLocalRuntimeAdapter({
|
|
61
|
+
env: options.env,
|
|
62
|
+
fetchImpl: options.fetchImpl,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
44
65
|
|
|
45
66
|
function formatUsage(usage: any, dialogId: unknown) {
|
|
46
67
|
const parts: string[] = [];
|
|
@@ -112,7 +133,7 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
112
133
|
return { exitCode };
|
|
113
134
|
}
|
|
114
135
|
|
|
115
|
-
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
136
|
+
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
116
137
|
options.output.write(`\n${options.agentName} -> working...\n`);
|
|
117
138
|
|
|
118
139
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -132,12 +153,12 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
132
153
|
host: "terminal",
|
|
133
154
|
runtime: "bun",
|
|
134
155
|
entrypoint: "nolo-cli",
|
|
135
|
-
capabilities: ["text-io", "slash-commands"],
|
|
156
|
+
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
136
157
|
},
|
|
137
158
|
...(options.continueDialogId
|
|
138
159
|
? { continueDialogId: options.continueDialogId }
|
|
139
160
|
: {}),
|
|
140
|
-
stream:
|
|
161
|
+
stream: true,
|
|
141
162
|
}),
|
|
142
163
|
});
|
|
143
164
|
} catch (error) {
|
|
@@ -145,6 +166,11 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
145
166
|
return { exitCode: 1 };
|
|
146
167
|
}
|
|
147
168
|
|
|
169
|
+
const contentType = res.headers.get("content-type") || "";
|
|
170
|
+
if (contentType.includes("text/event-stream") && res.body) {
|
|
171
|
+
return readStreamingAgentRun(options, res);
|
|
172
|
+
}
|
|
173
|
+
|
|
148
174
|
let data: any = {};
|
|
149
175
|
try {
|
|
150
176
|
data = await res.json();
|
|
@@ -175,17 +201,154 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
175
201
|
? { dialogId: data.dialogId }
|
|
176
202
|
: {}),
|
|
177
203
|
};
|
|
178
|
-
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
|
|
207
|
+
const adapter =
|
|
208
|
+
options.localRuntimeAdapter ||
|
|
209
|
+
options.localRuntimeAdapterFactory?.(options.env) ||
|
|
210
|
+
buildDefaultLocalRuntimeAdapter(options);
|
|
211
|
+
if (!adapter) {
|
|
212
|
+
options.output.write("[nolo] Local runtime was requested but no local runtime adapter is available.\n");
|
|
213
|
+
return { exitCode: 1 };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
options.output.write(`\n${options.agentName} -> working locally...\n`);
|
|
217
|
+
try {
|
|
218
|
+
const result = await runLocalAgentTurn({
|
|
219
|
+
adapter,
|
|
220
|
+
agentRef: options.agentKey,
|
|
221
|
+
input: options.message,
|
|
222
|
+
continueDialogId: options.continueDialogId,
|
|
223
|
+
});
|
|
224
|
+
const content = result.content.trim();
|
|
225
|
+
if (content) {
|
|
226
|
+
options.output.write(`\n${options.agentName} > ${content}\n`);
|
|
227
|
+
} else {
|
|
228
|
+
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
229
|
+
}
|
|
230
|
+
return { exitCode: 0, dialogId: result.dialogId };
|
|
231
|
+
} catch (error) {
|
|
232
|
+
options.output.write(
|
|
233
|
+
`[nolo] Local agent run failed: ${
|
|
234
|
+
error instanceof Error ? error.message : String(error)
|
|
235
|
+
}\n`
|
|
236
|
+
);
|
|
237
|
+
return { exitCode: 1 };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function readStreamingAgentRun(
|
|
242
|
+
options: RunAgentTurnOptions,
|
|
243
|
+
res: Response,
|
|
244
|
+
): Promise<RunAgentTurnResult> {
|
|
245
|
+
const reader = res.body?.getReader();
|
|
246
|
+
if (!reader) {
|
|
247
|
+
options.output.write("[nolo] Agent stream response did not include a readable body.\n");
|
|
248
|
+
return { exitCode: 1 };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const decoder = new TextDecoder();
|
|
252
|
+
const writer = createStreamingTextWriter({
|
|
253
|
+
write: (chunk) => options.output.write(chunk),
|
|
254
|
+
});
|
|
255
|
+
let buffer = "";
|
|
256
|
+
let content = "";
|
|
257
|
+
let dialogId: string | undefined;
|
|
258
|
+
let usage: any;
|
|
259
|
+
let hasPrintedLabel = false;
|
|
260
|
+
|
|
261
|
+
const printLabel = () => {
|
|
262
|
+
if (hasPrintedLabel) return;
|
|
263
|
+
options.output.write(`\n${options.agentName} > `);
|
|
264
|
+
hasPrintedLabel = true;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const handlePayload = (payload: any) => {
|
|
268
|
+
if (payload?.error || payload?.type === "error") {
|
|
269
|
+
throw new Error(String(payload.error || payload.message || "Agent stream failed"));
|
|
270
|
+
}
|
|
271
|
+
if (payload?.type === "done") {
|
|
272
|
+
if (typeof payload.dialogId === "string") dialogId = payload.dialogId;
|
|
273
|
+
usage = payload.usage;
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const chunk =
|
|
278
|
+
payload?.type === "text"
|
|
279
|
+
? payload.content
|
|
280
|
+
: typeof payload?.chunk === "string"
|
|
281
|
+
? payload.chunk
|
|
282
|
+
: "";
|
|
283
|
+
if (!chunk) return;
|
|
284
|
+
|
|
285
|
+
printLabel();
|
|
286
|
+
content += chunk;
|
|
287
|
+
writer.push(chunk);
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
while (true) {
|
|
292
|
+
const { done, value } = await reader.read();
|
|
293
|
+
if (done) break;
|
|
179
294
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const scriptPathExists = (options.scriptPathExists ?? existsSync)(scriptPath);
|
|
295
|
+
buffer += decoder.decode(value, { stream: true });
|
|
296
|
+
const events = buffer.split("\n\n");
|
|
297
|
+
buffer = events.pop() ?? "";
|
|
184
298
|
|
|
185
|
-
|
|
186
|
-
|
|
299
|
+
for (const event of events) {
|
|
300
|
+
const dataLines = event
|
|
301
|
+
.split("\n")
|
|
302
|
+
.filter((line) => line.startsWith("data:"))
|
|
303
|
+
.map((line) => line.slice(5).trim())
|
|
304
|
+
.filter(Boolean);
|
|
305
|
+
for (const raw of dataLines) {
|
|
306
|
+
handlePayload(JSON.parse(raw));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (buffer.trim()) {
|
|
311
|
+
const raw = buffer
|
|
312
|
+
.split("\n")
|
|
313
|
+
.find((line) => line.startsWith("data:"))
|
|
314
|
+
?.slice(5)
|
|
315
|
+
.trim();
|
|
316
|
+
if (raw) handlePayload(JSON.parse(raw));
|
|
317
|
+
}
|
|
318
|
+
} catch (error) {
|
|
319
|
+
options.output.write(`\n[nolo] Agent stream failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
320
|
+
return { exitCode: 1 };
|
|
321
|
+
} finally {
|
|
322
|
+
writer.flushAll();
|
|
187
323
|
}
|
|
188
324
|
|
|
325
|
+
if (!content) {
|
|
326
|
+
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
327
|
+
} else {
|
|
328
|
+
options.output.write("\n");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const usageText = formatUsage(usage, dialogId);
|
|
332
|
+
if (usageText && shouldShowUsage(options.env)) options.output.write(`${usageText}\n`);
|
|
333
|
+
return {
|
|
334
|
+
exitCode: 0,
|
|
335
|
+
...(dialogId ? { dialogId } : {}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
340
|
+
const authToken = resolveAuthToken(options.env);
|
|
341
|
+
const scriptPath = join(options.scriptDir, "chatWithAgent.ts");
|
|
342
|
+
const scriptPathExists = (options.scriptPathExists ?? existsSync)(scriptPath);
|
|
343
|
+
|
|
344
|
+
if (resolveRequestedRuntimeMode(options) === "local") {
|
|
345
|
+
return runInjectedLocalAgentTurn(options);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (shouldUseScriptBridge({ hasAuthToken: Boolean(authToken), scriptPathExists })) {
|
|
349
|
+
return runScriptBridge(options, scriptPath);
|
|
350
|
+
}
|
|
351
|
+
|
|
189
352
|
if (!authToken) {
|
|
190
353
|
options.output.write(
|
|
191
354
|
"[nolo] This install needs an auth token before it can talk to agents.\n" +
|