nolo-cli 0.1.32 → 0.1.33

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.
Files changed (230) hide show
  1. package/README.md +0 -6
  2. package/agent-runtime/agentRecordConfig.ts +2 -0
  3. package/agent-runtime/hostAdapter.ts +1 -0
  4. package/agent-runtime/index.ts +0 -21
  5. package/agent-runtime/localToolPolicy.ts +6 -13
  6. package/agent-runtime/localWorkspaceTools.ts +30 -333
  7. package/agent-runtime/platformChatProvider.ts +1 -4
  8. package/agent-runtime/runtimeToolPolicy.ts +47 -32
  9. package/agentAliases.ts +14 -83
  10. package/agentCommandSupport.ts +3 -6
  11. package/agentInternalCommandEntries.ts +2 -13
  12. package/agentListCommands.ts +19 -28
  13. package/agentMachineCommands.ts +40 -336
  14. package/agentPullCommand.ts +36 -56
  15. package/agentRecordCommands.ts +3 -5
  16. package/agentRecordHelpers.ts +22 -189
  17. package/agentRunCommand.ts +438 -107
  18. package/agentRuntimeLocal.ts +0 -10
  19. package/ai/agent/cliExecutor.ts +19 -323
  20. package/authCommands.ts +2 -36
  21. package/cli/agentAliases.ts +14 -83
  22. package/cli/agentCommandSupport.ts +3 -6
  23. package/cli/agentInternalCommandEntries.ts +2 -13
  24. package/cli/agentListCommands.ts +19 -28
  25. package/cli/agentMachineCommands.ts +40 -336
  26. package/cli/agentPullCommand.ts +36 -56
  27. package/cli/agentRecordCommands.ts +3 -5
  28. package/cli/agentRecordHelpers.ts +22 -189
  29. package/cli/agentRunCommand.ts +438 -107
  30. package/cli/agentRuntimeLocal.ts +0 -10
  31. package/cli/authCommands.ts +2 -36
  32. package/cli/cliEnvHelpers.ts +21 -162
  33. package/cli/client/agentRun.ts +853 -57
  34. package/cli/client/localRuntimeAdapter.ts +22 -159
  35. package/cli/client/streamingOutput.ts +3 -1
  36. package/cli/client/taskRunPrompt.ts +24 -7
  37. package/cli/commandRegistry.ts +2 -3
  38. package/cli/connectorRunArtifact.ts +10 -34
  39. package/cli/connectorWebSocketTarget.ts +6 -10
  40. package/cli/internalCommandEntries.ts +0 -8
  41. package/cli/localRuntimeDb.ts +2 -39
  42. package/cli/machineCommands.ts +189 -190
  43. package/cli/machineDaemonCommands.ts +165 -9
  44. package/cli/machineHeartbeatCommands.ts +2 -1
  45. package/cli/machineStatusCommands.ts +13 -5
  46. package/cli/machineWatchCommands.ts +1 -1
  47. package/cli/machineWsRunDispatch.ts +14 -48
  48. package/cli/machineWsSession.ts +0 -2
  49. package/cli/offlineMarxistsAgentCommand.ts +314 -0
  50. package/cli/runtimeDoctorCommands.ts +69 -18
  51. package/cli/scriptCommandEntries.ts +45 -5
  52. package/cli/tableCommands.ts +133 -33
  53. package/cli/taskRunCommand.ts +211 -16
  54. package/cli/tui/session.ts +372 -43
  55. package/cli/workflowInternalCommandEntries.ts +0 -20
  56. package/cliEnvHelpers.ts +21 -162
  57. package/client/agentConfigResolver.test.ts +2 -0
  58. package/client/agentRun.test.ts +12 -208
  59. package/client/agentRun.ts +853 -57
  60. package/client/localRuntimeAdapter.test.ts +9 -168
  61. package/client/localRuntimeAdapter.ts +22 -159
  62. package/client/localToolPolicy.test.ts +4 -28
  63. package/client/streamingOutput.ts +3 -1
  64. package/client/taskRunPrompt.ts +24 -7
  65. package/commandRegistry.ts +2 -3
  66. package/connectorRunArtifact.ts +10 -34
  67. package/connectorWebSocketTarget.ts +6 -10
  68. package/database/server/db.ts +34 -15
  69. package/internalCommandEntries.ts +0 -8
  70. package/localRuntimeDb.ts +2 -39
  71. package/machineCommands.ts +189 -190
  72. package/machineDaemonCommands.ts +165 -9
  73. package/machineHeartbeatCommands.ts +2 -1
  74. package/machineStatusCommands.ts +13 -5
  75. package/machineWatchCommands.ts +1 -1
  76. package/machineWsRunDispatch.ts +14 -48
  77. package/machineWsSession.ts +0 -2
  78. package/offlineMarxistsAgentCommand.ts +314 -0
  79. package/package.json +49 -193
  80. package/runtimeDoctorCommands.ts +69 -18
  81. package/scriptCommandEntries.ts +45 -5
  82. package/tableCommands.ts +133 -33
  83. package/taskRunCommand.ts +211 -16
  84. package/tui/session.ts +372 -43
  85. package/workflowInternalCommandEntries.ts +0 -20
  86. package/agent-runtime/dialogStatus.ts +0 -139
  87. package/agent-runtime/hostRuntimeConfig.ts +0 -86
  88. package/agentCreateCustomCommands.ts +0 -278
  89. package/agentDialogsCommands.ts +0 -227
  90. package/agentDoctorCommands.ts +0 -203
  91. package/agentRunLocalWorkspace.ts +0 -182
  92. package/agentRunParsing.ts +0 -298
  93. package/agentSuperviseCommands.ts +0 -8
  94. package/agentUnpublishCommands.ts +0 -113
  95. package/ai/agent/agentRunDialogLineage.ts +0 -56
  96. package/ai/policy/types.ts +0 -64
  97. package/ai/skills/skillSummaryMarker.ts +0 -26
  98. package/app/types.ts +0 -358
  99. package/build.ts +0 -16
  100. package/buildPublish.ts +0 -138
  101. package/buildPublishArtifactAssembly.ts +0 -724
  102. package/buildPublishImportAnalysis.ts +0 -242
  103. package/chat/messages/fetchMessages.ts +0 -84
  104. package/chat/messages/types.ts +0 -185
  105. package/cli/agentCreateCustomCommands.ts +0 -278
  106. package/cli/agentDialogsCommands.ts +0 -227
  107. package/cli/agentDoctorCommands.ts +0 -203
  108. package/cli/agentRunLocalWorkspace.ts +0 -182
  109. package/cli/agentRunParsing.ts +0 -298
  110. package/cli/agentSuperviseCommands.ts +0 -8
  111. package/cli/agentUnpublishCommands.ts +0 -113
  112. package/cli/buildPublish.ts +0 -138
  113. package/cli/buildPublishArtifactAssembly.ts +0 -724
  114. package/cli/buildPublishImportAnalysis.ts +0 -242
  115. package/cli/cliIoTypes.ts +0 -3
  116. package/cli/cliRuntimeDoctorReport.ts +0 -193
  117. package/cli/client/agentRunInput.ts +0 -10
  118. package/cli/client/agentRunLocalExecution.ts +0 -124
  119. package/cli/client/agentRunLocalExecutor.ts +0 -179
  120. package/cli/client/agentRunRuntimeSelection.ts +0 -145
  121. package/cli/client/agentRunServerTransport.ts +0 -397
  122. package/cli/client/agentRunTaskRunRecorder.ts +0 -176
  123. package/cli/customCodingAgentFlow.ts +0 -208
  124. package/cli/customCodingAgentRecord.ts +0 -179
  125. package/cli/dialogDataAccess.ts +0 -186
  126. package/cli/dialogInternalCommandEntries.ts +0 -11
  127. package/cli/dialogReadCommands.ts +0 -180
  128. package/cli/dialogReadHelpers.ts +0 -226
  129. package/cli/dialogStatusCommands.ts +0 -123
  130. package/cli/docInternalCommandEntries.ts +0 -20
  131. package/cli/hybridReadHelpers.ts +0 -109
  132. package/cli/machineCommandPrompts.ts +0 -79
  133. package/cli/machineCommandResolution.ts +0 -43
  134. package/cli/noloReadBridgePrompt.ts +0 -16
  135. package/cli/pageCreateCommands.ts +0 -219
  136. package/cli/pageDeleteCommands.ts +0 -167
  137. package/cli/pageReadCommands.ts +0 -151
  138. package/cli/pageUpdateCommands.ts +0 -249
  139. package/cli/pageWriteHelpers.ts +0 -209
  140. package/cli/publishPackage.ts +0 -36
  141. package/cli/runtimeInternalCommandEntries.ts +0 -15
  142. package/cli/runtimePassthroughCommands.ts +0 -22
  143. package/cli/scriptPassthroughHelpers.ts +0 -28
  144. package/cli/secretScanHelpers.ts +0 -31
  145. package/cli/spaceAttachCommands.ts +0 -134
  146. package/cli/spaceCategoryCommands.ts +0 -263
  147. package/cli/spaceCommands.ts +0 -295
  148. package/cli/spaceDeleteCommands.ts +0 -312
  149. package/cli/spaceInternalCommandEntries.ts +0 -21
  150. package/cli/spaceMutationHelpers.ts +0 -135
  151. package/cli/spaceReadCommands.ts +0 -323
  152. package/cli/tableDataOutput.ts +0 -41
  153. package/cli/tableDataSafety.ts +0 -26
  154. package/cli/tableMetaCommands.ts +0 -307
  155. package/cli/tableMutationCommands.ts +0 -266
  156. package/cli/tableQueryParsing.ts +0 -90
  157. package/cli/tableQueryRequest.ts +0 -53
  158. package/cli/taskRunActions.ts +0 -122
  159. package/cli/taskRunParsing.ts +0 -86
  160. package/cli/taskRunPromptShared.ts +0 -55
  161. package/cli/tui/tuiCatalog.ts +0 -22
  162. package/cli/tui/tuiCommandInterpreter.ts +0 -253
  163. package/cli/tui/tuiPresentation.ts +0 -146
  164. package/cliIoTypes.ts +0 -3
  165. package/cliRuntimeDoctorReport.ts +0 -193
  166. package/client/agentRunInput.ts +0 -10
  167. package/client/agentRunLocalExecution.test.ts +0 -123
  168. package/client/agentRunLocalExecution.ts +0 -124
  169. package/client/agentRunLocalExecutor.ts +0 -179
  170. package/client/agentRunRuntimeSelection.test.ts +0 -159
  171. package/client/agentRunRuntimeSelection.ts +0 -145
  172. package/client/agentRunServerTransport.ts +0 -397
  173. package/client/agentRunTaskRunRecorder.ts +0 -176
  174. package/connector-experimental/daemonLauncher.ts +0 -223
  175. package/create/space/spaceKeys.ts +0 -65
  176. package/create/types.ts +0 -21
  177. package/customCodingAgentFlow.ts +0 -208
  178. package/customCodingAgentRecord.ts +0 -179
  179. package/database/keys.ts +0 -765
  180. package/database/server/authorityStoreTypes.ts +0 -31
  181. package/database/server/legacyServerDb.ts +0 -58
  182. package/database/server/levelAuthorityStore.ts +0 -75
  183. package/database/server/memoryAuthorityStore.ts +0 -133
  184. package/database/server/serverStoreFactory.ts +0 -124
  185. package/database/tombstones.ts +0 -52
  186. package/database/utils/ulid.ts +0 -1
  187. package/dialogDataAccess.ts +0 -186
  188. package/dialogInternalCommandEntries.ts +0 -11
  189. package/dialogReadCommands.ts +0 -180
  190. package/dialogReadHelpers.ts +0 -226
  191. package/dialogStatusCommands.ts +0 -123
  192. package/docInternalCommandEntries.ts +0 -20
  193. package/hybridReadHelpers.ts +0 -109
  194. package/machineCommandPrompts.ts +0 -79
  195. package/machineCommandResolution.ts +0 -43
  196. package/noloReadBridgePrompt.ts +0 -16
  197. package/pageCreateCommands.ts +0 -219
  198. package/pageDeleteCommands.ts +0 -167
  199. package/pageReadCommands.ts +0 -151
  200. package/pageUpdateCommands.ts +0 -249
  201. package/pageWriteHelpers.ts +0 -209
  202. package/publishPackage.ts +0 -36
  203. package/runtimeInternalCommandEntries.ts +0 -15
  204. package/runtimePassthroughCommands.ts +0 -22
  205. package/scriptPassthroughHelpers.ts +0 -28
  206. package/secretScanHelpers.ts +0 -31
  207. package/share/helpers.ts +0 -302
  208. package/share/keys.ts +0 -243
  209. package/share/types.ts +0 -100
  210. package/spaceAttachCommands.ts +0 -134
  211. package/spaceCategoryCommands.ts +0 -263
  212. package/spaceCommands.ts +0 -295
  213. package/spaceDeleteCommands.ts +0 -312
  214. package/spaceInternalCommandEntries.ts +0 -21
  215. package/spaceMutationHelpers.ts +0 -135
  216. package/spaceReadCommands.ts +0 -323
  217. package/tableDataOutput.ts +0 -41
  218. package/tableDataSafety.ts +0 -26
  219. package/tableMetaCommands.ts +0 -307
  220. package/tableMutationCommands.ts +0 -266
  221. package/tableQueryParsing.ts +0 -90
  222. package/tableQueryRequest.ts +0 -53
  223. package/taskRunActions.ts +0 -122
  224. package/taskRunParsing.ts +0 -86
  225. package/taskRunPromptShared.ts +0 -55
  226. package/tui/tuiCatalog.ts +0 -22
  227. package/tui/tuiCommandInterpreter.test.ts +0 -169
  228. package/tui/tuiCommandInterpreter.ts +0 -253
  229. package/tui/tuiPresentation.test.ts +0 -154
  230. package/tui/tuiPresentation.ts +0 -146
@@ -1,30 +1,39 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { normalizeAgentRunDialogLineage } from "../../ai/agent/agentRunDialogLineage";
4
- import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
5
- import type { EnvLike } from "../cliEnvHelpers";
6
- import type { OutputLike } from "../cliIoTypes";
7
- import type { TaskRunPromptContext } from "./taskRunPrompt";
8
- import { type TaskRunRecorder } from "./agentRunTaskRunRecorder";
9
- import { buildUserInputContent } from "./agentRunInput";
10
- import {
11
- resolveRequestedRuntimeMode,
12
- shouldAttemptAutoLocal,
13
- shouldSkipAutoLocalForServerPlatformTools,
14
- shouldUseScriptBridge,
15
- } from "./agentRunRuntimeSelection";
3
+ import { runLocalAgentTurn } from "../agentRuntimeLocal";
4
+ import { LOCAL_AGENT_CONFIG_MISSING_CODE } from "../../agent-runtime/localLoop";
16
5
  import {
17
- isMissingLocalAgentConfigError,
18
- refreshMissingLocalAgentConfig,
19
- resolveLocalRuntimeAdapter,
20
- runInjectedLocalAgentTurn,
21
- runLocalAgentTurnWithAutoRoundExpansion,
22
- } from "./agentRunLocalExecutor";
23
- import { runHttpAgentTurn } from "./agentRunServerTransport";
24
- export { classifyTaskRunReviewStatus } from "./agentRunLocalExecution";
25
- export { findServerPlatformTools, shouldUseScriptBridge } from "./agentRunRuntimeSelection";
26
-
27
- export type RunAgentTurnOptions = {
6
+ FRONTEND_IMPLEMENTER_AGENT_KEY,
7
+ NOLO_FULLSTACK_AGENT_KEY,
8
+ NOLO_PROJECT_MANAGER_AGENT_KEY,
9
+ NOLO_REVIEW_AGENT_KEY,
10
+ NOLO_SENIOR_FULLSTACK_AGENT_KEY,
11
+ } from "../agentAliases";
12
+ import type { LocalAgentToolEvent } from "../../agent-runtime/localLoop";
13
+ import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
14
+ import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
15
+ import { createStreamingTextWriter } from "./streamingOutput";
16
+ import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
17
+
18
+ type EnvLike = Record<string, string | undefined>;
19
+
20
+ type OutputLike = {
21
+ write(chunk: string): unknown;
22
+ };
23
+
24
+ type TaskRunAgentRole = "pm" | "frontend" | "fullstack" | "reviewer" | "codex";
25
+ type TaskRunReviewStatus = "passed" | "needs_changes" | "blocked";
26
+ type TaskRunToolEvidence = {
27
+ toolsUsed: string[];
28
+ summary: {
29
+ callCount: number;
30
+ resultCount: number;
31
+ errorCount: number;
32
+ rounds: number;
33
+ };
34
+ };
35
+
36
+ type RunAgentTurnOptions = {
28
37
  agentName: string;
29
38
  agentKey: string;
30
39
  serverUrl: string;
@@ -49,7 +58,14 @@ export type RunAgentTurnOptions = {
49
58
  timeoutMs?: number;
50
59
  traceTools?: boolean;
51
60
  taskRunContext?: TaskRunPromptContext;
52
- taskRunRecorder?: TaskRunRecorder<RunAgentTurnOptions>;
61
+ taskRunRecorder?: (args: {
62
+ options: RunAgentTurnOptions;
63
+ status: "running" | "completed" | "failed";
64
+ dialogId?: string;
65
+ resultSummary?: string;
66
+ errorCode?: string;
67
+ toolEvidence?: TaskRunToolEvidence;
68
+ }) => Promise<void>;
53
69
  scriptPathExists?: (path: string) => boolean;
54
70
  fetchImpl?: typeof fetch;
55
71
  };
@@ -59,12 +75,446 @@ export type RunAgentTurnResult = {
59
75
  dialogId?: string;
60
76
  streamInterrupted?: boolean;
61
77
  };
78
+
79
+ type ScriptBridgeDecision = {
80
+ hasAuthToken: boolean;
81
+ scriptPathExists: boolean;
82
+ };
83
+
84
+ const SERVER_PLATFORM_TOOL_NAMES = new Set([
85
+ "addTableRow",
86
+ "addTableRows",
87
+ "deleteTableRow",
88
+ "deleteTableRows",
89
+ "queryTableRows",
90
+ "streamParallelAgents",
91
+ "taskRun",
92
+ "updateTableRow",
93
+ "updateTableRows",
94
+ ]);
95
+
96
+ const KNOWN_SERVER_PLATFORM_AGENT_KEYS = new Set([
97
+ FRONTEND_IMPLEMENTER_AGENT_KEY,
98
+ NOLO_FULLSTACK_AGENT_KEY,
99
+ NOLO_PROJECT_MANAGER_AGENT_KEY,
100
+ NOLO_REVIEW_AGENT_KEY,
101
+ NOLO_SENIOR_FULLSTACK_AGENT_KEY,
102
+ ]);
103
+
104
+ const KNOWN_SERVER_PLATFORM_AGENT_ALIASES = new Set([
105
+ "code-review",
106
+ "frontend",
107
+ "frontend-agent",
108
+ "frontend-implementer",
109
+ "full-stack",
110
+ "fullstack",
111
+ "nolo code review",
112
+ "nolo frontend",
113
+ "nolo fullstack",
114
+ "nolo project manager",
115
+ "nolo reviewer",
116
+ "nolo-code-review",
117
+ "nolo-frontend",
118
+ "nolo-fullstack",
119
+ "nolo-pm",
120
+ "nolo-project-manager",
121
+ "nolo-reviewer",
122
+ "pm",
123
+ "project-manager",
124
+ "review",
125
+ "reviewer",
126
+ ]);
127
+
128
+ export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
129
+ return !decision.hasAuthToken && decision.scriptPathExists;
130
+ }
131
+
132
+ export function findServerPlatformTools(toolNames?: string[]) {
133
+ if (!Array.isArray(toolNames)) return [];
134
+ return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
135
+ }
136
+
137
+ function resolveServerPlatformToolNames(agentConfig: any) {
138
+ return findServerPlatformTools([
139
+ ...(Array.isArray(agentConfig?.toolNames) ? agentConfig.toolNames : []),
140
+ ...(Array.isArray(agentConfig?.runtimeToolPolicy?.agentTools)
141
+ ? agentConfig.runtimeToolPolicy.agentTools
142
+ : []),
143
+ ]);
144
+ }
145
+
146
+ function normalizeAgentRef(ref?: string) {
147
+ return ref?.trim().toLowerCase().replace(/\s+/g, " ");
148
+ }
149
+
150
+ function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
151
+ if (KNOWN_SERVER_PLATFORM_AGENT_KEYS.has(options.agentKey)) return true;
152
+ const normalizedKey = normalizeAgentRef(options.agentKey);
153
+ return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
154
+ }
155
+
156
+ function resolveAuthToken(env: EnvLike) {
157
+ return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
158
+ }
159
+
160
+ function shouldShowUsage(env: EnvLike) {
161
+ return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
162
+ }
163
+
164
+ function resolveRequestedRuntimeMode(options: RunAgentTurnOptions) {
165
+ const envMode = options.env.NOLO_RUNTIME_MODE;
166
+ if (options.runtimeMode) return options.runtimeMode;
167
+ if (envMode === "local" || envMode === "server" || envMode === "auto") return envMode;
168
+ return "auto";
169
+ }
170
+
171
+ function buildDefaultLocalRuntimeAdapter(options: RunAgentTurnOptions) {
172
+ return createCliLocalRuntimeAdapter({
173
+ env: options.env,
174
+ fetchImpl: options.fetchImpl,
175
+ cwd: options.localRuntimeCwd,
176
+ output: options.output,
177
+ });
178
+ }
179
+
180
+ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
181
+ return (
182
+ options.localRuntimeAdapter ||
183
+ options.localRuntimeAdapterFactory?.(options.env, { cwd: options.localRuntimeCwd }) ||
184
+ buildDefaultLocalRuntimeAdapter(options)
185
+ );
186
+ }
187
+
188
+ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
189
+ if (isKnownServerPlatformAgent(options)) {
190
+ options.output.write(
191
+ `[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
192
+ "Use --local explicitly to force local workspace tools.\n"
193
+ );
194
+ return true;
195
+ }
196
+ const adapter = resolveLocalRuntimeAdapter(options);
197
+ if (!adapter) return false;
198
+ let agentConfig;
199
+ try {
200
+ agentConfig = await adapter.loadAgentConfig(options.agentKey);
201
+ } catch {
202
+ return false;
203
+ }
204
+ const serverTools = resolveServerPlatformToolNames(agentConfig);
205
+ if (serverTools.length === 0) return false;
206
+ options.output.write(
207
+ `[nolo] auto runtime: skipping local runtime because ${options.agentKey} declares server platform tools ` +
208
+ `(${serverTools.join(", ")}). Use --local explicitly to force local workspace tools.\n`
209
+ );
210
+ return true;
211
+ }
212
+
213
+ function buildUserInputContent(message: string, imageUrls: string[] = []) {
214
+ if (imageUrls.length === 0) return message;
215
+ return [
216
+ ...(message.trim() ? [{ type: "text" as const, text: message }] : []),
217
+ ...imageUrls.map((url) => ({
218
+ type: "image_url" as const,
219
+ image_url: { url },
220
+ })),
221
+ ];
222
+ }
223
+
224
+ function shouldTraceLocalTools(options: RunAgentTurnOptions) {
225
+ return Boolean(options.traceTools || options.env.NOLO_TRACE_TOOLS === "1");
226
+ }
62
227
 
63
- function resolveAuthToken(env: EnvLike) {
64
- return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
228
+ function shouldCollectLocalToolEvidence(options: RunAgentTurnOptions) {
229
+ return Boolean(options.taskRunContext?.rowDbKey);
65
230
  }
66
231
 
67
- async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string) {
232
+ function summarizeLocalToolEvidence(events: LocalAgentToolEvent[]): TaskRunToolEvidence | undefined {
233
+ if (events.length === 0) return undefined;
234
+ const toolsUsed = [
235
+ ...new Set(
236
+ events
237
+ .filter((event) => event.type === "tool-call")
238
+ .map((event) => event.toolName)
239
+ .filter(Boolean)
240
+ ),
241
+ ];
242
+ const rounds = new Set(events.map((event) => event.round)).size;
243
+ return {
244
+ toolsUsed,
245
+ summary: {
246
+ callCount: events.filter((event) => event.type === "tool-call").length,
247
+ resultCount: events.filter((event) => event.type === "tool-result").length,
248
+ errorCount: events.filter((event) => event.type === "tool-error").length,
249
+ rounds,
250
+ },
251
+ };
252
+ }
253
+
254
+ const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 512;
255
+
256
+ function parseLocalToolRoundLimitError(error: unknown) {
257
+ const message = error instanceof Error ? error.message : String(error);
258
+ const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
259
+ if (!match) return null;
260
+ return Number(match[1]);
261
+ }
262
+
263
+ function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
264
+ return Boolean(
265
+ error &&
266
+ typeof error === "object" &&
267
+ "code" in error &&
268
+ (error as { code?: string; agentRef?: string }).code === LOCAL_AGENT_CONFIG_MISSING_CODE &&
269
+ (error as { code?: string; agentRef?: string }).agentRef === agentRef
270
+ );
271
+ }
272
+
273
+ function parsePositiveInteger(value: string | undefined) {
274
+ if (!value) return null;
275
+ const parsed = Number(value);
276
+ if (!Number.isInteger(parsed) || parsed <= 0) return null;
277
+ return parsed;
278
+ }
279
+
280
+ function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
281
+ return (
282
+ parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
283
+ DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
284
+ );
285
+ }
286
+
287
+ function nextLocalToolRoundLimit(current: number) {
288
+ return current <= 0 ? 1 : current * 2;
289
+ }
290
+
291
+ function formatToolTraceEvent(event: LocalAgentToolEvent) {
292
+ const round = event.round + 1;
293
+ if (event.type === "tool-call") {
294
+ return `[nolo:tool] round ${round} -> ${event.toolName} (${event.toolCallId})\n`;
295
+ }
296
+ if (event.type === "tool-error") {
297
+ return `[nolo:tool] round ${round} !! ${event.toolName}: ${event.message ?? "failed"}\n`;
298
+ }
299
+ return `[nolo:tool] round ${round} <- ${event.toolName} (${event.toolCallId})\n`;
300
+ }
301
+
302
+ function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
303
+ if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
304
+ return Boolean(
305
+ options.env.NOLO_LOCAL_OPENAI_API_KEY ||
306
+ options.env.OPENAI_API_KEY ||
307
+ options.env.NOLO_LOCAL_OPENAI_BASE_URL ||
308
+ options.env.OPENAI_BASE_URL ||
309
+ options.env.NOLO_LOCAL_AGENT_KEY
310
+ );
311
+ }
312
+
313
+ function inferTaskRunAgentRole(agentRef: string): TaskRunAgentRole {
314
+ const normalized = agentRef.toLowerCase();
315
+ if (normalized.includes("project-manager") || normalized.includes("manager")) return "pm";
316
+ if (normalized.includes("frontend")) return "frontend";
317
+ if (normalized.includes("fullstack")) return "fullstack";
318
+ if (normalized.includes("review")) return "reviewer";
319
+ return "codex";
320
+ }
321
+
322
+ export function classifyTaskRunReviewStatus(summary?: string): TaskRunReviewStatus | undefined {
323
+ const normalized = summary?.toLowerCase().trim();
324
+ if (!normalized) return undefined;
325
+
326
+ const explicit = normalized.match(/review\s+decision\s*:\s*(passed|needs_changes|blocked)/);
327
+ if (explicit?.[1]) return explicit[1] as TaskRunReviewStatus;
328
+
329
+ if (/\b(blocked|cannot review|unable to review)\b|无法审查|阻塞/.test(normalized)) {
330
+ return "blocked";
331
+ }
332
+ if (
333
+ /\b(needs changes|request changes|changes requested|not approved)\b|需要修改|需修改|发现问题/.test(
334
+ normalized
335
+ )
336
+ ) {
337
+ return "needs_changes";
338
+ }
339
+ if (/\b(approved|lgtm|no issues|passed)\b|通过|无问题/.test(normalized)) {
340
+ return "passed";
341
+ }
342
+ return undefined;
343
+ }
344
+
345
+ async function runTaskRunCli(args: {
346
+ options: RunAgentTurnOptions;
347
+ cliArgs: string[];
348
+ }) {
349
+ const scriptPath = join(args.options.scriptDir, "taskRun.ts");
350
+ const proc = Bun.spawn({
351
+ cmd: [process.execPath, scriptPath, ...args.cliArgs],
352
+ stdout: "pipe",
353
+ stderr: "pipe",
354
+ env: {
355
+ ...process.env,
356
+ ...args.options.env,
357
+ BASE_URL: args.options.serverUrl,
358
+ },
359
+ });
360
+ const [stdout, stderr, exitCode] = await Promise.all([
361
+ new Response(proc.stdout).text(),
362
+ new Response(proc.stderr).text(),
363
+ proc.exited,
364
+ ]);
365
+ if (exitCode !== 0) {
366
+ throw new Error((stderr || stdout || `taskRun.ts exited ${exitCode}`).trim());
367
+ }
368
+ }
369
+
370
+ async function defaultTaskRunRecorder(args: {
371
+ options: RunAgentTurnOptions;
372
+ status: "running" | "completed" | "failed";
373
+ dialogId?: string;
374
+ resultSummary?: string;
375
+ errorCode?: string;
376
+ toolEvidence?: TaskRunToolEvidence;
377
+ }) {
378
+ const context = args.options.taskRunContext;
379
+ if (!context?.rowDbKey) return;
380
+ const role = inferTaskRunAgentRole(args.options.agentName || args.options.agentKey);
381
+ if (role === "reviewer" && args.status !== "running") {
382
+ await runTaskRunCli({
383
+ options: args.options,
384
+ cliArgs: [
385
+ "request-review",
386
+ "--row-dbkey",
387
+ context.rowDbKey,
388
+ "--base-url",
389
+ args.options.serverUrl,
390
+ "--reviewer-agent-key",
391
+ args.options.agentKey,
392
+ ...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
393
+ ...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
394
+ ],
395
+ });
396
+ }
397
+ await runTaskRunCli({
398
+ options: args.options,
399
+ cliArgs: [
400
+ "record-agent-run",
401
+ "--row-dbkey",
402
+ context.rowDbKey,
403
+ "--base-url",
404
+ args.options.serverUrl,
405
+ "--agent-key",
406
+ args.options.agentKey,
407
+ "--role",
408
+ role,
409
+ "--run-status",
410
+ args.status,
411
+ ...(context.workItemId ? ["--work-item-id", context.workItemId] : []),
412
+ ...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
413
+ ...(args.resultSummary ? ["--result-summary", args.resultSummary.slice(0, 240)] : []),
414
+ ...(args.errorCode ? ["--error-code", args.errorCode] : []),
415
+ ...(args.toolEvidence?.toolsUsed?.length
416
+ ? ["--tools-used", args.toolEvidence.toolsUsed.join(",")]
417
+ : []),
418
+ ...(args.toolEvidence?.summary
419
+ ? ["--tool-trace-summary", JSON.stringify(args.toolEvidence.summary)]
420
+ : []),
421
+ ],
422
+ });
423
+ if (context.workItemId && args.dialogId) {
424
+ await runTaskRunCli({
425
+ options: args.options,
426
+ cliArgs: [
427
+ "update-work-item",
428
+ "--row-dbkey",
429
+ context.rowDbKey,
430
+ "--base-url",
431
+ args.options.serverUrl,
432
+ "--id",
433
+ context.workItemId,
434
+ "--dialog-ids",
435
+ args.dialogId,
436
+ ],
437
+ });
438
+ }
439
+ if (role === "reviewer" && args.status !== "running") {
440
+ const reviewStatus =
441
+ args.status === "failed" ? "blocked" : classifyTaskRunReviewStatus(args.resultSummary);
442
+ if (reviewStatus) {
443
+ await runTaskRunCli({
444
+ options: args.options,
445
+ cliArgs: [
446
+ "review-result",
447
+ "--row-dbkey",
448
+ context.rowDbKey,
449
+ "--base-url",
450
+ args.options.serverUrl,
451
+ "--review-status",
452
+ reviewStatus,
453
+ "--reviewer-agent-key",
454
+ args.options.agentKey,
455
+ ...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
456
+ ...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
457
+ ],
458
+ });
459
+ }
460
+ }
461
+ }
462
+
463
+ async function recordTaskRunOutcome(args: {
464
+ options: RunAgentTurnOptions;
465
+ status: "running" | "completed" | "failed";
466
+ dialogId?: string;
467
+ resultSummary?: string;
468
+ errorCode?: string;
469
+ toolEvidence?: TaskRunToolEvidence;
470
+ }) {
471
+ if (!args.options.taskRunContext?.rowDbKey) return;
472
+ try {
473
+ await (args.options.taskRunRecorder ?? defaultTaskRunRecorder)(args);
474
+ } catch (error) {
475
+ args.options.output.write(
476
+ `[nolo] task-run writeback failed: ${error instanceof Error ? error.message : String(error)}\n`
477
+ );
478
+ }
479
+ }
480
+
481
+ function formatUsage(usage: any, dialogId: unknown) {
482
+ const parts: string[] = [];
483
+ if (typeof dialogId === "string" && dialogId) parts.push(`dialog=${dialogId}`);
484
+
485
+ const input = usage?.input_tokens ?? usage?.prompt_tokens ?? 0;
486
+ const output = usage?.output_tokens ?? usage?.completion_tokens ?? 0;
487
+ if (input || output) parts.push(`tokens=${input}+${output}`);
488
+
489
+ return parts.length ? ` (${parts.join(" ")})` : "";
490
+ }
491
+
492
+ function buildTransportErrorHint(serverUrl: string, error: unknown) {
493
+ const endpoint = `${serverUrl}/api/agent/run`;
494
+ const reason = error instanceof Error ? error.message : String(error);
495
+
496
+ let detail =
497
+ `[nolo] Could not reach ${endpoint}.\n` +
498
+ `Reason: ${reason}\n`;
499
+
500
+ try {
501
+ const parsed = new URL(serverUrl);
502
+ if (parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost") {
503
+ detail +=
504
+ "If you meant local dev, start the local API first.\n" +
505
+ "Otherwise set NOLO_SERVER to a reachable server, or re-run `nolo login --server https://nolo.chat`.\n";
506
+ return detail;
507
+ }
508
+ } catch {
509
+ // Keep the generic hint below when serverUrl is not a valid absolute URL.
510
+ }
511
+
512
+ detail +=
513
+ "Check NOLO_SERVER / BASE_URL and make sure the configured server is reachable.\n";
514
+ return detail;
515
+ }
516
+
517
+ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string) {
68
518
  options.output.write(`\n${options.agentName} -> working via local script...\n\n`);
69
519
  const proc = Bun.spawn({
70
520
  cmd: [
@@ -117,45 +567,391 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
117
567
  }
118
568
  return { exitCode };
119
569
  }
570
+
571
+ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
572
+ options.output.write(`\n${options.agentName} -> working...\n`);
573
+
574
+ const fetchImpl = options.fetchImpl ?? fetch;
575
+ let res: Response;
576
+ try {
577
+ res = await fetchImpl(`${options.serverUrl}/api/agent/run`, {
578
+ method: "POST",
579
+ headers: {
580
+ Authorization: `Bearer ${authToken}`,
581
+ "Content-Type": "application/json",
582
+ },
583
+ body: JSON.stringify({
584
+ agentKey: options.agentKey,
585
+ userInput: buildUserInputContent(
586
+ prependTaskRunPrompt(options.message, options.taskRunContext),
587
+ options.imageUrls
588
+ ),
589
+ runtimeContext: {
590
+ surface: "cli",
591
+ host: "terminal",
592
+ runtime: "bun",
593
+ entrypoint: "nolo-cli",
594
+ capabilities: ["text-io", "streaming", "slash-commands"],
595
+ ...(options.taskRunContext
596
+ ? {
597
+ taskRun: {
598
+ rowDbKey: options.taskRunContext.rowDbKey,
599
+ ...(options.taskRunContext.taskRunId
600
+ ? { taskRunId: options.taskRunContext.taskRunId }
601
+ : {}),
602
+ ...(options.taskRunContext.workItemId
603
+ ? { workItemId: options.taskRunContext.workItemId }
604
+ : {}),
605
+ ...(options.taskRunContext.artifactIds?.length
606
+ ? { artifactIds: options.taskRunContext.artifactIds }
607
+ : {}),
608
+ },
609
+ }
610
+ : {}),
611
+ },
612
+ ...(options.continueDialogId
613
+ ? { continueDialogId: options.continueDialogId }
614
+ : {}),
615
+ ...(options.spaceId ? { spaceId: options.spaceId } : {}),
616
+ ...(options.category ? { category: options.category } : {}),
617
+ ...(options.inheritedFromDialogKey
618
+ ? { inheritedFromDialogKey: options.inheritedFromDialogKey }
619
+ : {}),
620
+ ...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
621
+ ...(options.background ? { background: true } : {}),
622
+ ...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
623
+ ...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
624
+ stream: !options.noStream && !options.background,
625
+ }),
626
+ });
627
+ } catch (error) {
628
+ options.output.write(buildTransportErrorHint(options.serverUrl, error));
629
+ return { exitCode: 1 };
630
+ }
631
+
632
+ const contentType = res.headers.get("content-type") || "";
633
+ if (contentType.includes("text/event-stream") && res.body) {
634
+ const result = await readStreamingAgentRun(options, res);
635
+ await recordTaskRunOutcome({
636
+ options,
637
+ status: result.streamInterrupted
638
+ ? "running"
639
+ : result.exitCode === 0
640
+ ? "completed"
641
+ : "failed",
642
+ dialogId: result.dialogId,
643
+ ...(result.exitCode !== 0 ? { errorCode: "STREAM_FAILED" } : {}),
644
+ });
645
+ return result;
646
+ }
647
+
648
+ let data: any = {};
649
+ try {
650
+ data = await res.json();
651
+ } catch {
652
+ data = {};
653
+ }
654
+
655
+ if (!res.ok) {
656
+ options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
657
+ if (data?.error || data?.message) {
658
+ options.output.write(`${data.error || data.message}\n`);
659
+ }
660
+ await recordTaskRunOutcome({
661
+ options,
662
+ status: "failed",
663
+ errorCode: `HTTP_${res.status}`,
664
+ resultSummary: String(data?.error || data?.message || "").trim() || undefined,
665
+ });
666
+ return { exitCode: 1 };
667
+ }
668
+
669
+ const content = String(data?.content ?? data?.message ?? "").trim();
670
+ if (content) {
671
+ options.output.write(`\n${options.agentName} > ${content}\n`);
672
+ } else {
673
+ options.output.write(`\n${options.agentName} > (no text response)\n`);
674
+ }
675
+
676
+ const usage = formatUsage(data?.usage, data?.dialogId);
677
+ if (usage && shouldShowUsage(options.env)) options.output.write(`${usage}\n`);
678
+ const result = {
679
+ exitCode: 0,
680
+ ...(typeof data?.dialogId === "string" && data.dialogId
681
+ ? { dialogId: data.dialogId }
682
+ : {}),
683
+ };
684
+ await recordTaskRunOutcome({
685
+ options,
686
+ status: options.background ? "running" : "completed",
687
+ dialogId: result.dialogId,
688
+ ...(!options.background && content ? { resultSummary: content } : {}),
689
+ });
690
+ return result;
691
+ }
692
+
693
+ async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
694
+ return runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: true });
695
+ }
696
+
697
+ async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
698
+ const adapter = resolveLocalRuntimeAdapter(options);
699
+ if (!adapter) return false;
700
+ const agentConfig = await adapter.loadAgentConfig(options.agentKey);
701
+ return Boolean(agentConfig);
702
+ }
703
+
704
+ async function runLocalAgentTurnWithAutoRoundExpansion(
705
+ options: RunAgentTurnOptions,
706
+ settings: { reportFailure: boolean }
707
+ ) {
708
+ const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
709
+ let currentMaxToolRounds = options.maxToolRounds;
710
+ let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
711
+
712
+ while (true) {
713
+ const attemptOptions = {
714
+ ...options,
715
+ ...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
716
+ };
717
+ const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
718
+ lastResult = result;
719
+ if (result.exitCode === 0) return result;
720
+
721
+ const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
722
+ if (exhaustedRounds === null) break;
723
+
724
+ const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
725
+ if (nextMaxToolRounds > maxRetryLimit) break;
726
+
727
+ options.output.write(
728
+ `[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
729
+ );
730
+ currentMaxToolRounds = nextMaxToolRounds;
731
+ }
120
732
 
733
+ if (settings.reportFailure && lastResult?.localError) {
734
+ options.output.write(
735
+ `[nolo] Local agent run failed: ${
736
+ lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
737
+ }\n`
738
+ );
739
+ await recordTaskRunOutcome({
740
+ options,
741
+ status: "failed",
742
+ errorCode: "LOCAL_AGENT_FAILED",
743
+ resultSummary:
744
+ lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
745
+ toolEvidence: lastResult.toolEvidence,
746
+ });
747
+ }
748
+ return lastResult ?? { exitCode: 1 };
749
+ }
750
+
751
+ async function runLocalAgentTurnForCli(
752
+ options: RunAgentTurnOptions,
753
+ settings: { reportFailure: boolean }
754
+ ) {
755
+ const adapter = resolveLocalRuntimeAdapter(options);
756
+ if (!adapter) {
757
+ options.output.write("[nolo] Local runtime was requested but no local runtime adapter is available.\n");
758
+ return { exitCode: 1 };
759
+ }
760
+
761
+ options.output.write(`\n${options.agentName} -> working locally...\n`);
762
+ const toolEvents: LocalAgentToolEvent[] = [];
763
+ try {
764
+ const traceLocalTools = shouldTraceLocalTools(options);
765
+ const collectToolEvidence = shouldCollectLocalToolEvidence(options);
766
+ const result = await runLocalAgentTurn({
767
+ adapter,
768
+ agentRef: options.agentKey,
769
+ input: buildUserInputContent(
770
+ prependTaskRunPrompt(options.message, options.taskRunContext),
771
+ options.imageUrls
772
+ ),
773
+ continueDialogId: options.continueDialogId,
774
+ spaceId: options.spaceId,
775
+ category: options.category,
776
+ inheritedFromDialogKey: options.inheritedFromDialogKey,
777
+ parentDialogId: options.parentDialogId,
778
+ background: options.background,
779
+ noStream: options.noStream,
780
+ maxToolRounds: options.maxToolRounds,
781
+ ...(traceLocalTools || collectToolEvidence
782
+ ? {
783
+ onToolEvent: (event) => {
784
+ if (collectToolEvidence) toolEvents.push(event);
785
+ if (traceLocalTools) options.output.write(formatToolTraceEvent(event));
786
+ },
787
+ }
788
+ : {}),
789
+ });
790
+ const content = result.content.trim();
791
+ if (content) {
792
+ options.output.write(`\n${options.agentName} > ${content}\n`);
793
+ } else {
794
+ options.output.write(`\n${options.agentName} > (no text response)\n`);
795
+ }
796
+ await recordTaskRunOutcome({
797
+ options,
798
+ status: options.background ? "running" : "completed",
799
+ dialogId: result.dialogId,
800
+ ...(!options.background && content ? { resultSummary: content } : {}),
801
+ toolEvidence: summarizeLocalToolEvidence(toolEvents),
802
+ });
803
+ return { exitCode: 0, dialogId: result.dialogId };
804
+ } catch (error) {
805
+ if (settings.reportFailure) {
806
+ options.output.write(
807
+ `[nolo] Local agent run failed: ${
808
+ error instanceof Error ? error.message : String(error)
809
+ }\n`
810
+ );
811
+ }
812
+ return { exitCode: 1, localError: error, toolEvidence: summarizeLocalToolEvidence(toolEvents) };
813
+ }
814
+ }
815
+
816
+ async function readStreamingAgentRun(
817
+ options: RunAgentTurnOptions,
818
+ res: Response,
819
+ ): Promise<RunAgentTurnResult> {
820
+ const reader = res.body?.getReader();
821
+ if (!reader) {
822
+ options.output.write("[nolo] Agent stream response did not include a readable body.\n");
823
+ return { exitCode: 1 };
824
+ }
825
+
826
+ const decoder = new TextDecoder();
827
+ const writer = createStreamingTextWriter({
828
+ write: (chunk) => options.output.write(chunk),
829
+ });
830
+ let buffer = "";
831
+ let content = "";
832
+ let dialogId: string | undefined;
833
+ let usage: any;
834
+ let hasPrintedLabel = false;
835
+
836
+ const printLabel = () => {
837
+ if (hasPrintedLabel) return;
838
+ options.output.write(`\n${options.agentName} > `);
839
+ hasPrintedLabel = true;
840
+ };
841
+
842
+ const handlePayload = (payload: any) => {
843
+ if (typeof payload?.dialogId === "string" && payload.dialogId.trim()) {
844
+ dialogId = payload.dialogId;
845
+ }
846
+ if (payload?.error || payload?.type === "error") {
847
+ throw new Error(String(payload.error || payload.message || "Agent stream failed"));
848
+ }
849
+ if (payload?.type === "done") {
850
+ usage = payload.usage;
851
+ return;
852
+ }
853
+ if (payload?.type === "dialog" || payload?.type === "status") {
854
+ return;
855
+ }
856
+
857
+ const chunk =
858
+ payload?.type === "text"
859
+ ? payload.content
860
+ : typeof payload?.chunk === "string"
861
+ ? payload.chunk
862
+ : "";
863
+ if (!chunk) return;
864
+
865
+ printLabel();
866
+ content += chunk;
867
+ writer.push(chunk);
868
+ };
869
+
870
+ try {
871
+ while (true) {
872
+ const { done, value } = await reader.read();
873
+ if (done) break;
874
+
875
+ buffer += decoder.decode(value, { stream: true });
876
+ const events = buffer.split("\n\n");
877
+ buffer = events.pop() ?? "";
878
+
879
+ for (const event of events) {
880
+ const dataLines = event
881
+ .split("\n")
882
+ .filter((line) => line.startsWith("data:"))
883
+ .map((line) => line.slice(5).trim())
884
+ .filter(Boolean);
885
+ for (const raw of dataLines) {
886
+ handlePayload(JSON.parse(raw));
887
+ }
888
+ }
889
+ }
890
+ if (buffer.trim()) {
891
+ const raw = buffer
892
+ .split("\n")
893
+ .find((line) => line.startsWith("data:"))
894
+ ?.slice(5)
895
+ .trim();
896
+ if (raw) handlePayload(JSON.parse(raw));
897
+ }
898
+ } catch (error) {
899
+ const message = error instanceof Error ? error.message : String(error);
900
+ if (dialogId) {
901
+ options.output.write(
902
+ `\n[nolo] Agent stream transport interrupted after dialog ${dialogId} was created: ${message}\n`
903
+ );
904
+ options.output.write("[nolo] The agent run may still finish on the server; read the dialog before retrying.\n");
905
+ return { exitCode: 0, dialogId, streamInterrupted: true };
906
+ }
907
+ options.output.write(`\n[nolo] Agent stream failed: ${message}\n`);
908
+ return { exitCode: 1 };
909
+ } finally {
910
+ writer.flushAll();
911
+ }
912
+
913
+ if (!content) {
914
+ options.output.write(`\n${options.agentName} > (no text response)\n`);
915
+ } else {
916
+ options.output.write("\n");
917
+ }
918
+
919
+ const usageText = formatUsage(usage, dialogId);
920
+ if (usageText && shouldShowUsage(options.env)) options.output.write(`${usageText}\n`);
921
+ return {
922
+ exitCode: 0,
923
+ ...(dialogId ? { dialogId } : {}),
924
+ };
925
+ }
926
+
121
927
  export async function runAgentTurn(options: RunAgentTurnOptions) {
122
- const dialogLineage = normalizeAgentRunDialogLineage(options);
123
- const effectiveOptions: RunAgentTurnOptions = {
124
- ...options,
125
- continueDialogId: dialogLineage.continueDialogId,
126
- inheritedFromDialogKey: dialogLineage.inheritedFromDialogKey,
127
- parentDialogId: dialogLineage.parentDialogId,
128
- };
129
928
  const authToken = resolveAuthToken(options.env);
130
- const scriptPath = join(effectiveOptions.scriptDir, "chatWithAgent.ts");
929
+ const scriptPath = join(options.scriptDir, "chatWithAgent.ts");
131
930
  const scriptPathExists = (options.scriptPathExists ?? existsSync)(scriptPath);
132
- const runtimeMode = resolveRequestedRuntimeMode(effectiveOptions);
931
+ const runtimeMode = resolveRequestedRuntimeMode(options);
133
932
 
134
933
  if (runtimeMode === "local") {
135
- return runInjectedLocalAgentTurn(effectiveOptions);
934
+ return runInjectedLocalAgentTurn(options);
136
935
  }
137
936
 
138
- if (runtimeMode === "auto" && shouldAttemptAutoLocal(effectiveOptions)) {
139
- const skipLocal = await shouldSkipAutoLocalForServerPlatformTools({
140
- ...effectiveOptions,
141
- resolveLocalRuntimeAdapter: () => resolveLocalRuntimeAdapter(effectiveOptions),
142
- });
937
+ if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
938
+ const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
143
939
  if (!skipLocal) {
144
- const localResult = await runLocalAgentTurnWithAutoRoundExpansion(effectiveOptions, { reportFailure: false });
940
+ const localResult = await runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: false });
145
941
  if (localResult.exitCode === 0) {
146
942
  return {
147
943
  exitCode: localResult.exitCode,
148
944
  ...(localResult.dialogId ? { dialogId: localResult.dialogId } : {}),
149
945
  };
150
946
  }
151
- if (isMissingLocalAgentConfigError(localResult.localError, effectiveOptions.agentKey)) {
152
- effectiveOptions.output.write(
947
+ if (isMissingLocalAgentConfigError(localResult.localError, options.agentKey)) {
948
+ options.output.write(
153
949
  `[nolo] Local agent config was missing; refreshing from the configured server and retrying local once.\n`
154
950
  );
155
951
  try {
156
- const refreshed = await refreshMissingLocalAgentConfig(effectiveOptions);
952
+ const refreshed = await refreshMissingLocalAgentConfig(options);
157
953
  if (refreshed) {
158
- const retriedLocalResult = await runLocalAgentTurnWithAutoRoundExpansion(effectiveOptions, {
954
+ const retriedLocalResult = await runLocalAgentTurnWithAutoRoundExpansion(options, {
159
955
  reportFailure: false,
160
956
  });
161
957
  if (retriedLocalResult.exitCode === 0) {
@@ -173,16 +969,16 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
173
969
  }
174
970
 
175
971
  if (shouldUseScriptBridge({ hasAuthToken: Boolean(authToken), scriptPathExists })) {
176
- return runScriptBridge(effectiveOptions, scriptPath);
972
+ return runScriptBridge(options, scriptPath);
177
973
  }
178
-
179
- if (!authToken) {
180
- effectiveOptions.output.write(
181
- "[nolo] This install needs an auth token before it can talk to agents.\n" +
182
- "Set AUTH_TOKEN, or use the repo-local CLI where the dev script bridge is available.\n"
183
- );
974
+
975
+ if (!authToken) {
976
+ options.output.write(
977
+ "[nolo] This install needs an auth token before it can talk to agents.\n" +
978
+ "Set AUTH_TOKEN, or use the repo-local CLI where the dev script bridge is available.\n"
979
+ );
184
980
  return { exitCode: 1 };
185
981
  }
186
982
 
187
- return runHttpAgentTurn(effectiveOptions, { authToken });
188
- }
983
+ return runHttpAgentTurn(options, authToken);
984
+ }