nolo-cli 0.1.18 → 0.1.20
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 -1
- package/agent-runtime/agentConfigOptions.ts +12 -0
- package/agent-runtime/agentRecordConfig.ts +99 -0
- package/agent-runtime/agentRecordKeys.ts +14 -0
- package/agent-runtime/dialogMessageRecord.ts +16 -0
- package/agent-runtime/dialogWritePlan.ts +130 -0
- package/agent-runtime/hostAdapter.ts +14 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +78 -6
- package/agent-runtime/localToolPolicy.ts +130 -0
- package/agent-runtime/localWorkspaceTools.ts +1532 -0
- package/agent-runtime/openAiCompatibleProvider.ts +70 -0
- package/agent-runtime/openAiCompatibleProviderConfig.ts +38 -0
- package/agent-runtime/platformChatProvider.ts +241 -0
- package/agent-runtime/taskWorkspace.ts +193 -0
- package/agent-runtime/types.ts +2 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +289 -54
- package/agentRuntimeCommands.ts +354 -164
- package/agentRuntimeLocal.ts +38 -0
- package/ai/agent/agentSlice.ts +10 -0
- package/ai/agent/buildEditingContext.ts +5 -0
- package/ai/agent/buildSystemPrompt.ts +41 -18
- package/ai/agent/canvasEditingContext.ts +49 -0
- package/ai/agent/cliExecutor.ts +15 -4
- package/ai/agent/createAgentSchema.ts +2 -0
- package/ai/agent/executeToolCall.ts +3 -2
- package/ai/agent/hooks/usePublicAgents.ts +6 -0
- package/ai/agent/pageBuilderHandoffRules.ts +75 -0
- package/ai/agent/runAgentClientLoop.ts +4 -1
- package/ai/agent/runtimeGuidance.ts +19 -0
- package/ai/agent/server/fetchPublicAgents.ts +51 -1
- package/ai/agent/streamAgentChatTurn.ts +20 -2
- package/ai/agent/streamAgentChatTurnUtils.ts +60 -16
- package/ai/chat/accumulateToolCallChunks.ts +40 -9
- package/ai/chat/parseApiError.ts +3 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +23 -10
- package/ai/chat/sendOpenAICompletionsRequest.ts +13 -1
- package/ai/chat/updateTotalUsage.ts +26 -9
- package/ai/llm/deepinfra.ts +51 -0
- package/ai/llm/getPricing.ts +6 -0
- package/ai/llm/kimi.ts +2 -0
- package/ai/llm/openrouterModels.ts +0 -135
- package/ai/llm/providers.ts +1 -0
- package/ai/llm/types.ts +8 -0
- package/ai/taskRun/taskRunProtocol.ts +823 -0
- package/ai/token/calculatePrice.ts +30 -0
- package/ai/token/externalToolCost.ts +49 -29
- package/ai/token/prepareTokenUsageData.ts +6 -1
- package/ai/token/serverTokenWriter.ts +4 -2
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/presets/appBuilderPreset.ts +7 -0
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -1
- package/ai/tools/agent/taskRunTool.ts +112 -0
- package/ai/tools/applyEditTool.ts +6 -3
- package/ai/tools/applyLineEditsTool.ts +6 -3
- package/ai/tools/checkEnvTool.ts +14 -9
- package/ai/tools/codeSearchTool.ts +17 -5
- package/ai/tools/execBashTool.ts +33 -29
- package/ai/tools/fetchWebpageSupport.ts +24 -0
- package/ai/tools/fetchWebpageTool.ts +18 -5
- package/ai/tools/index.ts +158 -0
- package/ai/tools/jdProductScraperTool.ts +821 -0
- package/ai/tools/listFilesTool.ts +6 -3
- package/ai/tools/localFilesTool.ts +200 -0
- package/ai/tools/readFileTool.ts +6 -3
- package/ai/tools/searchRepoTool.ts +6 -3
- package/ai/tools/table/rowTools.ts +6 -1
- package/ai/tools/taobaoTmallProductScraperTool.ts +49 -0
- package/ai/tools/toolApiClient.ts +20 -6
- package/ai/tools/wereadGatewayTool.ts +152 -0
- package/ai/tools/writeFileTool.ts +6 -3
- package/client/agentConfigResolver.test.ts +70 -0
- package/client/agentConfigResolver.ts +1 -0
- package/client/agentRun.test.ts +361 -7
- package/client/agentRun.ts +449 -63
- package/client/hybridRecordStore.test.ts +115 -0
- package/client/hybridRecordStore.ts +41 -0
- package/client/localAgentRecords.test.ts +27 -0
- package/client/localAgentRecords.ts +7 -0
- package/client/localDialogRecords.test.ts +124 -0
- package/client/localDialogRecords.ts +30 -0
- package/client/localProviderResolver.test.ts +78 -0
- package/client/localProviderResolver.ts +1 -0
- package/client/localRuntimeAdapter.test.ts +813 -20
- package/client/localRuntimeAdapter.ts +279 -232
- package/client/localRuntimeDryRun.test.ts +116 -0
- package/client/localToolPolicy.ts +8 -81
- package/client/taskRunPrompt.ts +26 -0
- package/client/taskWorktree.ts +8 -0
- package/client/workspaceSession.test.ts +57 -0
- package/client/workspaceSession.ts +11 -0
- package/commandRegistry.ts +23 -6
- package/connectorRunArtifact.ts +121 -0
- package/database/actions/write.ts +16 -2
- package/database/hooks/useUserData.ts +9 -3
- package/database/server/dataHandlers.ts +18 -20
- package/database/server/emailRepository.ts +3 -3
- package/database/server/patch.ts +18 -10
- package/database/server/query.ts +43 -4
- package/database/server/read.ts +24 -38
- package/database/server/recordIdentity.ts +100 -0
- package/database/server/write.ts +21 -25
- package/index.ts +70 -33
- package/machineCommands.ts +318 -144
- package/package.json +4 -1
- package/tableCommands.ts +181 -0
- package/taskRunCommand.ts +237 -0
package/client/agentRun.ts
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
4
|
+
import type { LocalAgentToolEvent } from "../agent-runtime/localLoop";
|
|
4
5
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
5
6
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
6
7
|
import { createStreamingTextWriter } from "./streamingOutput";
|
|
8
|
+
import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
|
|
7
9
|
|
|
8
10
|
type EnvLike = Record<string, string | undefined>;
|
|
9
11
|
|
|
10
|
-
type OutputLike = {
|
|
11
|
-
write(chunk: string): unknown;
|
|
12
|
-
};
|
|
13
|
-
|
|
12
|
+
type OutputLike = {
|
|
13
|
+
write(chunk: string): unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TaskRunAgentRole = "pm" | "frontend" | "fullstack" | "reviewer" | "codex";
|
|
17
|
+
type TaskRunReviewStatus = "passed" | "needs_changes" | "blocked";
|
|
18
|
+
|
|
14
19
|
type RunAgentTurnOptions = {
|
|
15
20
|
agentName: string;
|
|
16
21
|
agentKey: string;
|
|
@@ -18,13 +23,31 @@ type RunAgentTurnOptions = {
|
|
|
18
23
|
message: string;
|
|
19
24
|
imageUrls?: string[];
|
|
20
25
|
continueDialogId?: string;
|
|
21
|
-
|
|
26
|
+
spaceId?: string;
|
|
27
|
+
category?: string;
|
|
28
|
+
inheritedFromDialogKey?: string;
|
|
29
|
+
parentDialogId?: string;
|
|
30
|
+
background?: boolean;
|
|
31
|
+
noStream?: boolean;
|
|
32
|
+
noDefaultTestRoot?: boolean;
|
|
33
|
+
scriptDir: string;
|
|
22
34
|
env: EnvLike;
|
|
23
35
|
output: OutputLike;
|
|
24
36
|
runtimeMode?: AgentRuntimeRequestedMode;
|
|
25
37
|
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
26
|
-
localRuntimeAdapterFactory?: (env: EnvLike) => AgentRuntimeHostAdapter;
|
|
38
|
+
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
27
39
|
localRuntimeCwd?: string;
|
|
40
|
+
maxToolRounds?: number;
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
traceTools?: boolean;
|
|
43
|
+
taskRunContext?: TaskRunPromptContext;
|
|
44
|
+
taskRunRecorder?: (args: {
|
|
45
|
+
options: RunAgentTurnOptions;
|
|
46
|
+
status: "completed" | "failed";
|
|
47
|
+
dialogId?: string;
|
|
48
|
+
resultSummary?: string;
|
|
49
|
+
errorCode?: string;
|
|
50
|
+
}) => Promise<void>;
|
|
28
51
|
scriptPathExists?: (path: string) => boolean;
|
|
29
52
|
fetchImpl?: typeof fetch;
|
|
30
53
|
};
|
|
@@ -34,14 +57,31 @@ export type RunAgentTurnResult = {
|
|
|
34
57
|
dialogId?: string;
|
|
35
58
|
};
|
|
36
59
|
|
|
37
|
-
type ScriptBridgeDecision = {
|
|
38
|
-
hasAuthToken: boolean;
|
|
39
|
-
scriptPathExists: boolean;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
type ScriptBridgeDecision = {
|
|
61
|
+
hasAuthToken: boolean;
|
|
62
|
+
scriptPathExists: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
66
|
+
"addTableRow",
|
|
67
|
+
"addTableRows",
|
|
68
|
+
"deleteTableRow",
|
|
69
|
+
"deleteTableRows",
|
|
70
|
+
"queryTableRows",
|
|
71
|
+
"streamParallelAgents",
|
|
72
|
+
"taskRun",
|
|
73
|
+
"updateTableRow",
|
|
74
|
+
"updateTableRows",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
|
|
78
|
+
return !decision.hasAuthToken && decision.scriptPathExists;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function findServerPlatformTools(toolNames?: string[]) {
|
|
82
|
+
if (!Array.isArray(toolNames)) return [];
|
|
83
|
+
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
84
|
+
}
|
|
45
85
|
|
|
46
86
|
function resolveAuthToken(env: EnvLike) {
|
|
47
87
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
@@ -63,9 +103,36 @@ function buildDefaultLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
63
103
|
env: options.env,
|
|
64
104
|
fetchImpl: options.fetchImpl,
|
|
65
105
|
cwd: options.localRuntimeCwd,
|
|
106
|
+
output: options.output,
|
|
66
107
|
});
|
|
67
108
|
}
|
|
68
109
|
|
|
110
|
+
function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
111
|
+
return (
|
|
112
|
+
options.localRuntimeAdapter ||
|
|
113
|
+
options.localRuntimeAdapterFactory?.(options.env, { cwd: options.localRuntimeCwd }) ||
|
|
114
|
+
buildDefaultLocalRuntimeAdapter(options)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
119
|
+
const adapter = resolveLocalRuntimeAdapter(options);
|
|
120
|
+
if (!adapter) return false;
|
|
121
|
+
let agentConfig;
|
|
122
|
+
try {
|
|
123
|
+
agentConfig = await adapter.loadAgentConfig(options.agentKey);
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const serverTools = findServerPlatformTools(agentConfig?.toolNames);
|
|
128
|
+
if (serverTools.length === 0) return false;
|
|
129
|
+
options.output.write(
|
|
130
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} declares server platform tools ` +
|
|
131
|
+
`(${serverTools.join(", ")}). Use --local explicitly to force local workspace tools.\n`
|
|
132
|
+
);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
69
136
|
function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
70
137
|
if (imageUrls.length === 0) return message;
|
|
71
138
|
return [
|
|
@@ -77,6 +144,48 @@ function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
|
77
144
|
];
|
|
78
145
|
}
|
|
79
146
|
|
|
147
|
+
function shouldTraceLocalTools(options: RunAgentTurnOptions) {
|
|
148
|
+
return Boolean(options.traceTools || options.env.NOLO_TRACE_TOOLS === "1");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 128;
|
|
152
|
+
|
|
153
|
+
function parseLocalToolRoundLimitError(error: unknown) {
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
|
|
156
|
+
if (!match) return null;
|
|
157
|
+
return Number(match[1]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parsePositiveInteger(value: string | undefined) {
|
|
161
|
+
if (!value) return null;
|
|
162
|
+
const parsed = Number(value);
|
|
163
|
+
if (!Number.isInteger(parsed) || parsed <= 0) return null;
|
|
164
|
+
return parsed;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
|
|
168
|
+
return (
|
|
169
|
+
parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
|
|
170
|
+
DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function nextLocalToolRoundLimit(current: number) {
|
|
175
|
+
return current <= 0 ? 1 : current * 2;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatToolTraceEvent(event: LocalAgentToolEvent) {
|
|
179
|
+
const round = event.round + 1;
|
|
180
|
+
if (event.type === "tool-call") {
|
|
181
|
+
return `[nolo:tool] round ${round} -> ${event.toolName} (${event.toolCallId})\n`;
|
|
182
|
+
}
|
|
183
|
+
if (event.type === "tool-error") {
|
|
184
|
+
return `[nolo:tool] round ${round} !! ${event.toolName}: ${event.message ?? "failed"}\n`;
|
|
185
|
+
}
|
|
186
|
+
return `[nolo:tool] round ${round} <- ${event.toolName} (${event.toolCallId})\n`;
|
|
187
|
+
}
|
|
188
|
+
|
|
80
189
|
function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
81
190
|
if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
|
|
82
191
|
return Boolean(
|
|
@@ -87,6 +196,166 @@ function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
|
87
196
|
options.env.NOLO_LOCAL_AGENT_KEY
|
|
88
197
|
);
|
|
89
198
|
}
|
|
199
|
+
|
|
200
|
+
function inferTaskRunAgentRole(agentRef: string): TaskRunAgentRole {
|
|
201
|
+
const normalized = agentRef.toLowerCase();
|
|
202
|
+
if (normalized.includes("project-manager") || normalized.includes("manager")) return "pm";
|
|
203
|
+
if (normalized.includes("frontend")) return "frontend";
|
|
204
|
+
if (normalized.includes("fullstack")) return "fullstack";
|
|
205
|
+
if (normalized.includes("review")) return "reviewer";
|
|
206
|
+
return "codex";
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function classifyTaskRunReviewStatus(summary?: string): TaskRunReviewStatus | undefined {
|
|
210
|
+
const normalized = summary?.toLowerCase().trim();
|
|
211
|
+
if (!normalized) return undefined;
|
|
212
|
+
|
|
213
|
+
const explicit = normalized.match(/review\s+decision\s*:\s*(passed|needs_changes|blocked)/);
|
|
214
|
+
if (explicit?.[1]) return explicit[1] as TaskRunReviewStatus;
|
|
215
|
+
|
|
216
|
+
if (/\b(blocked|cannot review|unable to review)\b|无法审查|阻塞/.test(normalized)) {
|
|
217
|
+
return "blocked";
|
|
218
|
+
}
|
|
219
|
+
if (
|
|
220
|
+
/\b(needs changes|request changes|changes requested|not approved)\b|需要修改|需修改|发现问题/.test(
|
|
221
|
+
normalized
|
|
222
|
+
)
|
|
223
|
+
) {
|
|
224
|
+
return "needs_changes";
|
|
225
|
+
}
|
|
226
|
+
if (/\b(approved|lgtm|no issues|passed)\b|通过|无问题/.test(normalized)) {
|
|
227
|
+
return "passed";
|
|
228
|
+
}
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function runTaskRunCli(args: {
|
|
233
|
+
options: RunAgentTurnOptions;
|
|
234
|
+
cliArgs: string[];
|
|
235
|
+
}) {
|
|
236
|
+
const scriptPath = join(args.options.scriptDir, "taskRun.ts");
|
|
237
|
+
const proc = Bun.spawn({
|
|
238
|
+
cmd: [process.execPath, scriptPath, ...args.cliArgs],
|
|
239
|
+
stdout: "pipe",
|
|
240
|
+
stderr: "pipe",
|
|
241
|
+
env: {
|
|
242
|
+
...process.env,
|
|
243
|
+
...args.options.env,
|
|
244
|
+
BASE_URL: args.options.serverUrl,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
248
|
+
new Response(proc.stdout).text(),
|
|
249
|
+
new Response(proc.stderr).text(),
|
|
250
|
+
proc.exited,
|
|
251
|
+
]);
|
|
252
|
+
if (exitCode !== 0) {
|
|
253
|
+
throw new Error((stderr || stdout || `taskRun.ts exited ${exitCode}`).trim());
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function defaultTaskRunRecorder(args: {
|
|
258
|
+
options: RunAgentTurnOptions;
|
|
259
|
+
status: "completed" | "failed";
|
|
260
|
+
dialogId?: string;
|
|
261
|
+
resultSummary?: string;
|
|
262
|
+
errorCode?: string;
|
|
263
|
+
}) {
|
|
264
|
+
const context = args.options.taskRunContext;
|
|
265
|
+
if (!context?.rowDbKey) return;
|
|
266
|
+
const role = inferTaskRunAgentRole(args.options.agentName || args.options.agentKey);
|
|
267
|
+
if (role === "reviewer") {
|
|
268
|
+
await runTaskRunCli({
|
|
269
|
+
options: args.options,
|
|
270
|
+
cliArgs: [
|
|
271
|
+
"request-review",
|
|
272
|
+
"--row-dbkey",
|
|
273
|
+
context.rowDbKey,
|
|
274
|
+
"--base-url",
|
|
275
|
+
args.options.serverUrl,
|
|
276
|
+
"--reviewer-agent-key",
|
|
277
|
+
args.options.agentKey,
|
|
278
|
+
...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
|
|
279
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
280
|
+
],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
await runTaskRunCli({
|
|
284
|
+
options: args.options,
|
|
285
|
+
cliArgs: [
|
|
286
|
+
"record-agent-run",
|
|
287
|
+
"--row-dbkey",
|
|
288
|
+
context.rowDbKey,
|
|
289
|
+
"--base-url",
|
|
290
|
+
args.options.serverUrl,
|
|
291
|
+
"--agent-key",
|
|
292
|
+
args.options.agentKey,
|
|
293
|
+
"--role",
|
|
294
|
+
role,
|
|
295
|
+
"--run-status",
|
|
296
|
+
args.status,
|
|
297
|
+
...(context.workItemId ? ["--work-item-id", context.workItemId] : []),
|
|
298
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
299
|
+
...(args.resultSummary ? ["--result-summary", args.resultSummary.slice(0, 240)] : []),
|
|
300
|
+
...(args.errorCode ? ["--error-code", args.errorCode] : []),
|
|
301
|
+
],
|
|
302
|
+
});
|
|
303
|
+
if (context.workItemId && args.dialogId) {
|
|
304
|
+
await runTaskRunCli({
|
|
305
|
+
options: args.options,
|
|
306
|
+
cliArgs: [
|
|
307
|
+
"update-work-item",
|
|
308
|
+
"--row-dbkey",
|
|
309
|
+
context.rowDbKey,
|
|
310
|
+
"--base-url",
|
|
311
|
+
args.options.serverUrl,
|
|
312
|
+
"--id",
|
|
313
|
+
context.workItemId,
|
|
314
|
+
"--dialog-ids",
|
|
315
|
+
args.dialogId,
|
|
316
|
+
],
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (role === "reviewer") {
|
|
320
|
+
const reviewStatus =
|
|
321
|
+
args.status === "failed" ? "blocked" : classifyTaskRunReviewStatus(args.resultSummary);
|
|
322
|
+
if (reviewStatus) {
|
|
323
|
+
await runTaskRunCli({
|
|
324
|
+
options: args.options,
|
|
325
|
+
cliArgs: [
|
|
326
|
+
"review-result",
|
|
327
|
+
"--row-dbkey",
|
|
328
|
+
context.rowDbKey,
|
|
329
|
+
"--base-url",
|
|
330
|
+
args.options.serverUrl,
|
|
331
|
+
"--review-status",
|
|
332
|
+
reviewStatus,
|
|
333
|
+
"--reviewer-agent-key",
|
|
334
|
+
args.options.agentKey,
|
|
335
|
+
...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
|
|
336
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function recordTaskRunOutcome(args: {
|
|
344
|
+
options: RunAgentTurnOptions;
|
|
345
|
+
status: "completed" | "failed";
|
|
346
|
+
dialogId?: string;
|
|
347
|
+
resultSummary?: string;
|
|
348
|
+
errorCode?: string;
|
|
349
|
+
}) {
|
|
350
|
+
if (!args.options.taskRunContext?.rowDbKey) return;
|
|
351
|
+
try {
|
|
352
|
+
await (args.options.taskRunRecorder ?? defaultTaskRunRecorder)(args);
|
|
353
|
+
} catch (error) {
|
|
354
|
+
args.options.output.write(
|
|
355
|
+
`[nolo] task-run writeback failed: ${error instanceof Error ? error.message : String(error)}\n`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
90
359
|
|
|
91
360
|
function formatUsage(usage: any, dialogId: unknown) {
|
|
92
361
|
const parts: string[] = [];
|
|
@@ -134,12 +403,32 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
134
403
|
options.agentKey,
|
|
135
404
|
"--server",
|
|
136
405
|
options.serverUrl,
|
|
137
|
-
"--msg",
|
|
138
|
-
options.message,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
406
|
+
"--msg",
|
|
407
|
+
options.message,
|
|
408
|
+
...(options.taskRunContext?.rowDbKey
|
|
409
|
+
? ["--task-row-dbkey", options.taskRunContext.rowDbKey]
|
|
410
|
+
: []),
|
|
411
|
+
...(options.taskRunContext?.taskRunId
|
|
412
|
+
? ["--task-run-id", options.taskRunContext.taskRunId]
|
|
413
|
+
: []),
|
|
414
|
+
...(options.taskRunContext?.workItemId
|
|
415
|
+
? ["--work-item-id", options.taskRunContext.workItemId]
|
|
416
|
+
: []),
|
|
417
|
+
...(options.taskRunContext?.artifactIds?.length
|
|
418
|
+
? ["--artifact-ids", options.taskRunContext.artifactIds.join(",")]
|
|
419
|
+
: []),
|
|
420
|
+
...(options.noDefaultTestRoot ? ["--no-default-test-root"] : []),
|
|
421
|
+
...(options.continueDialogId
|
|
422
|
+
? ["--continue", options.continueDialogId]
|
|
423
|
+
: []),
|
|
424
|
+
...(options.spaceId ? ["--space", options.spaceId] : []),
|
|
425
|
+
...(options.category ? ["--category", options.category] : []),
|
|
426
|
+
...(options.inheritedFromDialogKey
|
|
427
|
+
? ["--inherit-from-dialog", options.inheritedFromDialogKey]
|
|
428
|
+
: []),
|
|
429
|
+
...(options.background ? ["--bg"] : []),
|
|
430
|
+
...(typeof options.timeoutMs === "number" ? ["--timeout-ms", String(options.timeoutMs)] : []),
|
|
431
|
+
...(options.noStream ? ["--no-stream"] : []),
|
|
143
432
|
],
|
|
144
433
|
stdin: "inherit",
|
|
145
434
|
stdout: "inherit",
|
|
@@ -151,12 +440,12 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
151
440
|
},
|
|
152
441
|
});
|
|
153
442
|
|
|
154
|
-
const exitCode = await proc.exited;
|
|
155
|
-
if (exitCode !== 0) {
|
|
156
|
-
options.output.write(`\n[nolo] Agent run exited with code ${exitCode}.\n`);
|
|
157
|
-
}
|
|
158
|
-
return { exitCode };
|
|
159
|
-
}
|
|
443
|
+
const exitCode = await proc.exited;
|
|
444
|
+
if (exitCode !== 0) {
|
|
445
|
+
options.output.write(`\n[nolo] Agent run exited with code ${exitCode}.\n`);
|
|
446
|
+
}
|
|
447
|
+
return { exitCode };
|
|
448
|
+
}
|
|
160
449
|
|
|
161
450
|
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
162
451
|
options.output.write(`\n${options.agentName} -> working...\n`);
|
|
@@ -172,7 +461,10 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
172
461
|
},
|
|
173
462
|
body: JSON.stringify({
|
|
174
463
|
agentKey: options.agentKey,
|
|
175
|
-
userInput: buildUserInputContent(
|
|
464
|
+
userInput: buildUserInputContent(
|
|
465
|
+
prependTaskRunPrompt(options.message, options.taskRunContext),
|
|
466
|
+
options.imageUrls
|
|
467
|
+
),
|
|
176
468
|
runtimeContext: {
|
|
177
469
|
surface: "cli",
|
|
178
470
|
host: "terminal",
|
|
@@ -180,21 +472,37 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
180
472
|
entrypoint: "nolo-cli",
|
|
181
473
|
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
182
474
|
},
|
|
183
|
-
...(options.continueDialogId
|
|
184
|
-
? { continueDialogId: options.continueDialogId }
|
|
185
|
-
: {}),
|
|
186
|
-
|
|
187
|
-
|
|
475
|
+
...(options.continueDialogId
|
|
476
|
+
? { continueDialogId: options.continueDialogId }
|
|
477
|
+
: {}),
|
|
478
|
+
...(options.spaceId ? { spaceId: options.spaceId } : {}),
|
|
479
|
+
...(options.category ? { category: options.category } : {}),
|
|
480
|
+
...(options.inheritedFromDialogKey
|
|
481
|
+
? { inheritedFromDialogKey: options.inheritedFromDialogKey }
|
|
482
|
+
: {}),
|
|
483
|
+
...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
|
|
484
|
+
...(options.background ? { background: true } : {}),
|
|
485
|
+
...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
|
|
486
|
+
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
487
|
+
stream: !options.noStream && !options.background,
|
|
488
|
+
}),
|
|
188
489
|
});
|
|
189
490
|
} catch (error) {
|
|
190
491
|
options.output.write(buildTransportErrorHint(options.serverUrl, error));
|
|
191
492
|
return { exitCode: 1 };
|
|
192
493
|
}
|
|
193
494
|
|
|
194
|
-
const contentType = res.headers.get("content-type") || "";
|
|
195
|
-
if (contentType.includes("text/event-stream") && res.body) {
|
|
196
|
-
|
|
197
|
-
|
|
495
|
+
const contentType = res.headers.get("content-type") || "";
|
|
496
|
+
if (contentType.includes("text/event-stream") && res.body) {
|
|
497
|
+
const result = await readStreamingAgentRun(options, res);
|
|
498
|
+
await recordTaskRunOutcome({
|
|
499
|
+
options,
|
|
500
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
501
|
+
dialogId: result.dialogId,
|
|
502
|
+
...(result.exitCode !== 0 ? { errorCode: "STREAM_FAILED" } : {}),
|
|
503
|
+
});
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
198
506
|
|
|
199
507
|
let data: any = {};
|
|
200
508
|
try {
|
|
@@ -203,13 +511,19 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
203
511
|
data = {};
|
|
204
512
|
}
|
|
205
513
|
|
|
206
|
-
if (!res.ok) {
|
|
207
|
-
options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
|
|
208
|
-
if (data?.error || data?.message) {
|
|
209
|
-
options.output.write(`${data.error || data.message}\n`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
514
|
+
if (!res.ok) {
|
|
515
|
+
options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
|
|
516
|
+
if (data?.error || data?.message) {
|
|
517
|
+
options.output.write(`${data.error || data.message}\n`);
|
|
518
|
+
}
|
|
519
|
+
await recordTaskRunOutcome({
|
|
520
|
+
options,
|
|
521
|
+
status: "failed",
|
|
522
|
+
errorCode: `HTTP_${res.status}`,
|
|
523
|
+
resultSummary: String(data?.error || data?.message || "").trim() || undefined,
|
|
524
|
+
});
|
|
525
|
+
return { exitCode: 1 };
|
|
526
|
+
}
|
|
213
527
|
|
|
214
528
|
const content = String(data?.content ?? data?.message ?? "").trim();
|
|
215
529
|
if (content) {
|
|
@@ -218,28 +532,78 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
218
532
|
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
219
533
|
}
|
|
220
534
|
|
|
221
|
-
const usage = formatUsage(data?.usage, data?.dialogId);
|
|
222
|
-
if (usage && shouldShowUsage(options.env)) options.output.write(`${usage}\n`);
|
|
223
|
-
|
|
224
|
-
exitCode: 0,
|
|
225
|
-
...(typeof data?.dialogId === "string" && data.dialogId
|
|
226
|
-
? { dialogId: data.dialogId }
|
|
227
|
-
: {}),
|
|
228
|
-
};
|
|
535
|
+
const usage = formatUsage(data?.usage, data?.dialogId);
|
|
536
|
+
if (usage && shouldShowUsage(options.env)) options.output.write(`${usage}\n`);
|
|
537
|
+
const result = {
|
|
538
|
+
exitCode: 0,
|
|
539
|
+
...(typeof data?.dialogId === "string" && data.dialogId
|
|
540
|
+
? { dialogId: data.dialogId }
|
|
541
|
+
: {}),
|
|
542
|
+
};
|
|
543
|
+
await recordTaskRunOutcome({
|
|
544
|
+
options,
|
|
545
|
+
status: "completed",
|
|
546
|
+
dialogId: result.dialogId,
|
|
547
|
+
resultSummary: content || undefined,
|
|
548
|
+
});
|
|
549
|
+
return result;
|
|
229
550
|
}
|
|
230
551
|
|
|
231
552
|
async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
|
|
232
|
-
return
|
|
553
|
+
return runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: true });
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function runLocalAgentTurnWithAutoRoundExpansion(
|
|
557
|
+
options: RunAgentTurnOptions,
|
|
558
|
+
settings: { reportFailure: boolean }
|
|
559
|
+
) {
|
|
560
|
+
const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
|
|
561
|
+
let currentMaxToolRounds = options.maxToolRounds;
|
|
562
|
+
let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
|
|
563
|
+
|
|
564
|
+
while (true) {
|
|
565
|
+
const attemptOptions = {
|
|
566
|
+
...options,
|
|
567
|
+
...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
|
|
568
|
+
};
|
|
569
|
+
const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
|
|
570
|
+
lastResult = result;
|
|
571
|
+
if (result.exitCode === 0) return result;
|
|
572
|
+
|
|
573
|
+
const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
|
|
574
|
+
if (exhaustedRounds === null) break;
|
|
575
|
+
|
|
576
|
+
const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
|
|
577
|
+
if (nextMaxToolRounds > maxRetryLimit) break;
|
|
578
|
+
|
|
579
|
+
options.output.write(
|
|
580
|
+
`[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
|
|
581
|
+
);
|
|
582
|
+
currentMaxToolRounds = nextMaxToolRounds;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (settings.reportFailure && lastResult?.localError) {
|
|
586
|
+
options.output.write(
|
|
587
|
+
`[nolo] Local agent run failed: ${
|
|
588
|
+
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
|
|
589
|
+
}\n`
|
|
590
|
+
);
|
|
591
|
+
await recordTaskRunOutcome({
|
|
592
|
+
options,
|
|
593
|
+
status: "failed",
|
|
594
|
+
errorCode: "LOCAL_AGENT_FAILED",
|
|
595
|
+
resultSummary:
|
|
596
|
+
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
return lastResult ?? { exitCode: 1 };
|
|
233
600
|
}
|
|
234
601
|
|
|
235
602
|
async function runLocalAgentTurnForCli(
|
|
236
603
|
options: RunAgentTurnOptions,
|
|
237
604
|
settings: { reportFailure: boolean }
|
|
238
605
|
) {
|
|
239
|
-
const adapter =
|
|
240
|
-
options.localRuntimeAdapter ||
|
|
241
|
-
options.localRuntimeAdapterFactory?.(options.env) ||
|
|
242
|
-
buildDefaultLocalRuntimeAdapter(options);
|
|
606
|
+
const adapter = resolveLocalRuntimeAdapter(options);
|
|
243
607
|
if (!adapter) {
|
|
244
608
|
options.output.write("[nolo] Local runtime was requested but no local runtime adapter is available.\n");
|
|
245
609
|
return { exitCode: 1 };
|
|
@@ -250,8 +614,21 @@ async function runLocalAgentTurnForCli(
|
|
|
250
614
|
const result = await runLocalAgentTurn({
|
|
251
615
|
adapter,
|
|
252
616
|
agentRef: options.agentKey,
|
|
253
|
-
input: buildUserInputContent(
|
|
617
|
+
input: buildUserInputContent(
|
|
618
|
+
prependTaskRunPrompt(options.message, options.taskRunContext),
|
|
619
|
+
options.imageUrls
|
|
620
|
+
),
|
|
254
621
|
continueDialogId: options.continueDialogId,
|
|
622
|
+
spaceId: options.spaceId,
|
|
623
|
+
category: options.category,
|
|
624
|
+
inheritedFromDialogKey: options.inheritedFromDialogKey,
|
|
625
|
+
parentDialogId: options.parentDialogId,
|
|
626
|
+
background: options.background,
|
|
627
|
+
noStream: options.noStream,
|
|
628
|
+
maxToolRounds: options.maxToolRounds,
|
|
629
|
+
...(shouldTraceLocalTools(options)
|
|
630
|
+
? { onToolEvent: (event) => options.output.write(formatToolTraceEvent(event)) }
|
|
631
|
+
: {}),
|
|
255
632
|
});
|
|
256
633
|
const content = result.content.trim();
|
|
257
634
|
if (content) {
|
|
@@ -259,6 +636,12 @@ async function runLocalAgentTurnForCli(
|
|
|
259
636
|
} else {
|
|
260
637
|
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
261
638
|
}
|
|
639
|
+
await recordTaskRunOutcome({
|
|
640
|
+
options,
|
|
641
|
+
status: "completed",
|
|
642
|
+
dialogId: result.dialogId,
|
|
643
|
+
resultSummary: content || undefined,
|
|
644
|
+
});
|
|
262
645
|
return { exitCode: 0, dialogId: result.dialogId };
|
|
263
646
|
} catch (error) {
|
|
264
647
|
if (settings.reportFailure) {
|
|
@@ -381,12 +764,15 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
381
764
|
}
|
|
382
765
|
|
|
383
766
|
if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
|
|
384
|
-
const
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
767
|
+
const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
|
|
768
|
+
if (!skipLocal) {
|
|
769
|
+
const localResult = await runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: false });
|
|
770
|
+
if (localResult.exitCode === 0) {
|
|
771
|
+
return {
|
|
772
|
+
exitCode: localResult.exitCode,
|
|
773
|
+
...(localResult.dialogId ? { dialogId: localResult.dialogId } : {}),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
390
776
|
}
|
|
391
777
|
}
|
|
392
778
|
|