nolo-cli 0.1.19 → 0.1.21
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 +13 -0
- package/agent-runtime/hybridRecordStore.ts +147 -0
- package/agent-runtime/index.ts +69 -0
- package/agent-runtime/localLoop.ts +69 -5
- 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 +1 -0
- package/agent-runtime/workspaceSession.ts +76 -0
- package/agentAliases.ts +37 -0
- package/agentPullCommand.ts +1 -1
- package/agentRunCommand.ts +278 -52
- 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 +882 -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 +430 -7
- package/client/agentRun.ts +504 -64
- 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 +621 -9
- package/client/localRuntimeAdapter.ts +275 -250
- 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 +265 -0
package/client/agentRun.ts
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
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
|
+
type TaskRunToolEvidence = {
|
|
19
|
+
toolsUsed: string[];
|
|
20
|
+
summary: {
|
|
21
|
+
callCount: number;
|
|
22
|
+
resultCount: number;
|
|
23
|
+
errorCount: number;
|
|
24
|
+
rounds: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
14
28
|
type RunAgentTurnOptions = {
|
|
15
29
|
agentName: string;
|
|
16
30
|
agentKey: string;
|
|
@@ -18,13 +32,32 @@ type RunAgentTurnOptions = {
|
|
|
18
32
|
message: string;
|
|
19
33
|
imageUrls?: string[];
|
|
20
34
|
continueDialogId?: string;
|
|
21
|
-
|
|
35
|
+
spaceId?: string;
|
|
36
|
+
category?: string;
|
|
37
|
+
inheritedFromDialogKey?: string;
|
|
38
|
+
parentDialogId?: string;
|
|
39
|
+
background?: boolean;
|
|
40
|
+
noStream?: boolean;
|
|
41
|
+
noDefaultTestRoot?: boolean;
|
|
42
|
+
scriptDir: string;
|
|
22
43
|
env: EnvLike;
|
|
23
44
|
output: OutputLike;
|
|
24
45
|
runtimeMode?: AgentRuntimeRequestedMode;
|
|
25
46
|
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
26
|
-
localRuntimeAdapterFactory?: (env: EnvLike) => AgentRuntimeHostAdapter;
|
|
47
|
+
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
27
48
|
localRuntimeCwd?: string;
|
|
49
|
+
maxToolRounds?: number;
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
traceTools?: boolean;
|
|
52
|
+
taskRunContext?: TaskRunPromptContext;
|
|
53
|
+
taskRunRecorder?: (args: {
|
|
54
|
+
options: RunAgentTurnOptions;
|
|
55
|
+
status: "completed" | "failed";
|
|
56
|
+
dialogId?: string;
|
|
57
|
+
resultSummary?: string;
|
|
58
|
+
errorCode?: string;
|
|
59
|
+
toolEvidence?: TaskRunToolEvidence;
|
|
60
|
+
}) => Promise<void>;
|
|
28
61
|
scriptPathExists?: (path: string) => boolean;
|
|
29
62
|
fetchImpl?: typeof fetch;
|
|
30
63
|
};
|
|
@@ -34,14 +67,31 @@ export type RunAgentTurnResult = {
|
|
|
34
67
|
dialogId?: string;
|
|
35
68
|
};
|
|
36
69
|
|
|
37
|
-
type ScriptBridgeDecision = {
|
|
38
|
-
hasAuthToken: boolean;
|
|
39
|
-
scriptPathExists: boolean;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
type ScriptBridgeDecision = {
|
|
71
|
+
hasAuthToken: boolean;
|
|
72
|
+
scriptPathExists: boolean;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
76
|
+
"addTableRow",
|
|
77
|
+
"addTableRows",
|
|
78
|
+
"deleteTableRow",
|
|
79
|
+
"deleteTableRows",
|
|
80
|
+
"queryTableRows",
|
|
81
|
+
"streamParallelAgents",
|
|
82
|
+
"taskRun",
|
|
83
|
+
"updateTableRow",
|
|
84
|
+
"updateTableRows",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
|
|
88
|
+
return !decision.hasAuthToken && decision.scriptPathExists;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function findServerPlatformTools(toolNames?: string[]) {
|
|
92
|
+
if (!Array.isArray(toolNames)) return [];
|
|
93
|
+
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
94
|
+
}
|
|
45
95
|
|
|
46
96
|
function resolveAuthToken(env: EnvLike) {
|
|
47
97
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
@@ -63,9 +113,36 @@ function buildDefaultLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
63
113
|
env: options.env,
|
|
64
114
|
fetchImpl: options.fetchImpl,
|
|
65
115
|
cwd: options.localRuntimeCwd,
|
|
116
|
+
output: options.output,
|
|
66
117
|
});
|
|
67
118
|
}
|
|
68
119
|
|
|
120
|
+
function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
121
|
+
return (
|
|
122
|
+
options.localRuntimeAdapter ||
|
|
123
|
+
options.localRuntimeAdapterFactory?.(options.env, { cwd: options.localRuntimeCwd }) ||
|
|
124
|
+
buildDefaultLocalRuntimeAdapter(options)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
129
|
+
const adapter = resolveLocalRuntimeAdapter(options);
|
|
130
|
+
if (!adapter) return false;
|
|
131
|
+
let agentConfig;
|
|
132
|
+
try {
|
|
133
|
+
agentConfig = await adapter.loadAgentConfig(options.agentKey);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const serverTools = findServerPlatformTools(agentConfig?.toolNames);
|
|
138
|
+
if (serverTools.length === 0) return false;
|
|
139
|
+
options.output.write(
|
|
140
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} declares server platform tools ` +
|
|
141
|
+
`(${serverTools.join(", ")}). Use --local explicitly to force local workspace tools.\n`
|
|
142
|
+
);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
69
146
|
function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
70
147
|
if (imageUrls.length === 0) return message;
|
|
71
148
|
return [
|
|
@@ -77,6 +154,74 @@ function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
|
77
154
|
];
|
|
78
155
|
}
|
|
79
156
|
|
|
157
|
+
function shouldTraceLocalTools(options: RunAgentTurnOptions) {
|
|
158
|
+
return Boolean(options.traceTools || options.env.NOLO_TRACE_TOOLS === "1");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function shouldCollectLocalToolEvidence(options: RunAgentTurnOptions) {
|
|
162
|
+
return Boolean(options.taskRunContext?.rowDbKey);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function summarizeLocalToolEvidence(events: LocalAgentToolEvent[]): TaskRunToolEvidence | undefined {
|
|
166
|
+
if (events.length === 0) return undefined;
|
|
167
|
+
const toolsUsed = [
|
|
168
|
+
...new Set(
|
|
169
|
+
events
|
|
170
|
+
.filter((event) => event.type === "tool-call")
|
|
171
|
+
.map((event) => event.toolName)
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
),
|
|
174
|
+
];
|
|
175
|
+
const rounds = new Set(events.map((event) => event.round)).size;
|
|
176
|
+
return {
|
|
177
|
+
toolsUsed,
|
|
178
|
+
summary: {
|
|
179
|
+
callCount: events.filter((event) => event.type === "tool-call").length,
|
|
180
|
+
resultCount: events.filter((event) => event.type === "tool-result").length,
|
|
181
|
+
errorCount: events.filter((event) => event.type === "tool-error").length,
|
|
182
|
+
rounds,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 128;
|
|
188
|
+
|
|
189
|
+
function parseLocalToolRoundLimitError(error: unknown) {
|
|
190
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
191
|
+
const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
|
|
192
|
+
if (!match) return null;
|
|
193
|
+
return Number(match[1]);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function parsePositiveInteger(value: string | undefined) {
|
|
197
|
+
if (!value) return null;
|
|
198
|
+
const parsed = Number(value);
|
|
199
|
+
if (!Number.isInteger(parsed) || parsed <= 0) return null;
|
|
200
|
+
return parsed;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
|
|
204
|
+
return (
|
|
205
|
+
parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
|
|
206
|
+
DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function nextLocalToolRoundLimit(current: number) {
|
|
211
|
+
return current <= 0 ? 1 : current * 2;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function formatToolTraceEvent(event: LocalAgentToolEvent) {
|
|
215
|
+
const round = event.round + 1;
|
|
216
|
+
if (event.type === "tool-call") {
|
|
217
|
+
return `[nolo:tool] round ${round} -> ${event.toolName} (${event.toolCallId})\n`;
|
|
218
|
+
}
|
|
219
|
+
if (event.type === "tool-error") {
|
|
220
|
+
return `[nolo:tool] round ${round} !! ${event.toolName}: ${event.message ?? "failed"}\n`;
|
|
221
|
+
}
|
|
222
|
+
return `[nolo:tool] round ${round} <- ${event.toolName} (${event.toolCallId})\n`;
|
|
223
|
+
}
|
|
224
|
+
|
|
80
225
|
function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
81
226
|
if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
|
|
82
227
|
return Boolean(
|
|
@@ -87,6 +232,174 @@ function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
|
87
232
|
options.env.NOLO_LOCAL_AGENT_KEY
|
|
88
233
|
);
|
|
89
234
|
}
|
|
235
|
+
|
|
236
|
+
function inferTaskRunAgentRole(agentRef: string): TaskRunAgentRole {
|
|
237
|
+
const normalized = agentRef.toLowerCase();
|
|
238
|
+
if (normalized.includes("project-manager") || normalized.includes("manager")) return "pm";
|
|
239
|
+
if (normalized.includes("frontend")) return "frontend";
|
|
240
|
+
if (normalized.includes("fullstack")) return "fullstack";
|
|
241
|
+
if (normalized.includes("review")) return "reviewer";
|
|
242
|
+
return "codex";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function classifyTaskRunReviewStatus(summary?: string): TaskRunReviewStatus | undefined {
|
|
246
|
+
const normalized = summary?.toLowerCase().trim();
|
|
247
|
+
if (!normalized) return undefined;
|
|
248
|
+
|
|
249
|
+
const explicit = normalized.match(/review\s+decision\s*:\s*(passed|needs_changes|blocked)/);
|
|
250
|
+
if (explicit?.[1]) return explicit[1] as TaskRunReviewStatus;
|
|
251
|
+
|
|
252
|
+
if (/\b(blocked|cannot review|unable to review)\b|无法审查|阻塞/.test(normalized)) {
|
|
253
|
+
return "blocked";
|
|
254
|
+
}
|
|
255
|
+
if (
|
|
256
|
+
/\b(needs changes|request changes|changes requested|not approved)\b|需要修改|需修改|发现问题/.test(
|
|
257
|
+
normalized
|
|
258
|
+
)
|
|
259
|
+
) {
|
|
260
|
+
return "needs_changes";
|
|
261
|
+
}
|
|
262
|
+
if (/\b(approved|lgtm|no issues|passed)\b|通过|无问题/.test(normalized)) {
|
|
263
|
+
return "passed";
|
|
264
|
+
}
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function runTaskRunCli(args: {
|
|
269
|
+
options: RunAgentTurnOptions;
|
|
270
|
+
cliArgs: string[];
|
|
271
|
+
}) {
|
|
272
|
+
const scriptPath = join(args.options.scriptDir, "taskRun.ts");
|
|
273
|
+
const proc = Bun.spawn({
|
|
274
|
+
cmd: [process.execPath, scriptPath, ...args.cliArgs],
|
|
275
|
+
stdout: "pipe",
|
|
276
|
+
stderr: "pipe",
|
|
277
|
+
env: {
|
|
278
|
+
...process.env,
|
|
279
|
+
...args.options.env,
|
|
280
|
+
BASE_URL: args.options.serverUrl,
|
|
281
|
+
},
|
|
282
|
+
});
|
|
283
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
284
|
+
new Response(proc.stdout).text(),
|
|
285
|
+
new Response(proc.stderr).text(),
|
|
286
|
+
proc.exited,
|
|
287
|
+
]);
|
|
288
|
+
if (exitCode !== 0) {
|
|
289
|
+
throw new Error((stderr || stdout || `taskRun.ts exited ${exitCode}`).trim());
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function defaultTaskRunRecorder(args: {
|
|
294
|
+
options: RunAgentTurnOptions;
|
|
295
|
+
status: "completed" | "failed";
|
|
296
|
+
dialogId?: string;
|
|
297
|
+
resultSummary?: string;
|
|
298
|
+
errorCode?: string;
|
|
299
|
+
toolEvidence?: TaskRunToolEvidence;
|
|
300
|
+
}) {
|
|
301
|
+
const context = args.options.taskRunContext;
|
|
302
|
+
if (!context?.rowDbKey) return;
|
|
303
|
+
const role = inferTaskRunAgentRole(args.options.agentName || args.options.agentKey);
|
|
304
|
+
if (role === "reviewer") {
|
|
305
|
+
await runTaskRunCli({
|
|
306
|
+
options: args.options,
|
|
307
|
+
cliArgs: [
|
|
308
|
+
"request-review",
|
|
309
|
+
"--row-dbkey",
|
|
310
|
+
context.rowDbKey,
|
|
311
|
+
"--base-url",
|
|
312
|
+
args.options.serverUrl,
|
|
313
|
+
"--reviewer-agent-key",
|
|
314
|
+
args.options.agentKey,
|
|
315
|
+
...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
|
|
316
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
317
|
+
],
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
await runTaskRunCli({
|
|
321
|
+
options: args.options,
|
|
322
|
+
cliArgs: [
|
|
323
|
+
"record-agent-run",
|
|
324
|
+
"--row-dbkey",
|
|
325
|
+
context.rowDbKey,
|
|
326
|
+
"--base-url",
|
|
327
|
+
args.options.serverUrl,
|
|
328
|
+
"--agent-key",
|
|
329
|
+
args.options.agentKey,
|
|
330
|
+
"--role",
|
|
331
|
+
role,
|
|
332
|
+
"--run-status",
|
|
333
|
+
args.status,
|
|
334
|
+
...(context.workItemId ? ["--work-item-id", context.workItemId] : []),
|
|
335
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
336
|
+
...(args.resultSummary ? ["--result-summary", args.resultSummary.slice(0, 240)] : []),
|
|
337
|
+
...(args.errorCode ? ["--error-code", args.errorCode] : []),
|
|
338
|
+
...(args.toolEvidence?.toolsUsed?.length
|
|
339
|
+
? ["--tools-used", args.toolEvidence.toolsUsed.join(",")]
|
|
340
|
+
: []),
|
|
341
|
+
...(args.toolEvidence?.summary
|
|
342
|
+
? ["--tool-trace-summary", JSON.stringify(args.toolEvidence.summary)]
|
|
343
|
+
: []),
|
|
344
|
+
],
|
|
345
|
+
});
|
|
346
|
+
if (context.workItemId && args.dialogId) {
|
|
347
|
+
await runTaskRunCli({
|
|
348
|
+
options: args.options,
|
|
349
|
+
cliArgs: [
|
|
350
|
+
"update-work-item",
|
|
351
|
+
"--row-dbkey",
|
|
352
|
+
context.rowDbKey,
|
|
353
|
+
"--base-url",
|
|
354
|
+
args.options.serverUrl,
|
|
355
|
+
"--id",
|
|
356
|
+
context.workItemId,
|
|
357
|
+
"--dialog-ids",
|
|
358
|
+
args.dialogId,
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
if (role === "reviewer") {
|
|
363
|
+
const reviewStatus =
|
|
364
|
+
args.status === "failed" ? "blocked" : classifyTaskRunReviewStatus(args.resultSummary);
|
|
365
|
+
if (reviewStatus) {
|
|
366
|
+
await runTaskRunCli({
|
|
367
|
+
options: args.options,
|
|
368
|
+
cliArgs: [
|
|
369
|
+
"review-result",
|
|
370
|
+
"--row-dbkey",
|
|
371
|
+
context.rowDbKey,
|
|
372
|
+
"--base-url",
|
|
373
|
+
args.options.serverUrl,
|
|
374
|
+
"--review-status",
|
|
375
|
+
reviewStatus,
|
|
376
|
+
"--reviewer-agent-key",
|
|
377
|
+
args.options.agentKey,
|
|
378
|
+
...(context.artifactIds?.length ? ["--artifact-ids", context.artifactIds.join(",")] : []),
|
|
379
|
+
...(args.dialogId ? ["--dialog-id", args.dialogId] : []),
|
|
380
|
+
],
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function recordTaskRunOutcome(args: {
|
|
387
|
+
options: RunAgentTurnOptions;
|
|
388
|
+
status: "completed" | "failed";
|
|
389
|
+
dialogId?: string;
|
|
390
|
+
resultSummary?: string;
|
|
391
|
+
errorCode?: string;
|
|
392
|
+
toolEvidence?: TaskRunToolEvidence;
|
|
393
|
+
}) {
|
|
394
|
+
if (!args.options.taskRunContext?.rowDbKey) return;
|
|
395
|
+
try {
|
|
396
|
+
await (args.options.taskRunRecorder ?? defaultTaskRunRecorder)(args);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
args.options.output.write(
|
|
399
|
+
`[nolo] task-run writeback failed: ${error instanceof Error ? error.message : String(error)}\n`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
90
403
|
|
|
91
404
|
function formatUsage(usage: any, dialogId: unknown) {
|
|
92
405
|
const parts: string[] = [];
|
|
@@ -134,12 +447,32 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
134
447
|
options.agentKey,
|
|
135
448
|
"--server",
|
|
136
449
|
options.serverUrl,
|
|
137
|
-
"--msg",
|
|
138
|
-
options.message,
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
450
|
+
"--msg",
|
|
451
|
+
options.message,
|
|
452
|
+
...(options.taskRunContext?.rowDbKey
|
|
453
|
+
? ["--task-row-dbkey", options.taskRunContext.rowDbKey]
|
|
454
|
+
: []),
|
|
455
|
+
...(options.taskRunContext?.taskRunId
|
|
456
|
+
? ["--task-run-id", options.taskRunContext.taskRunId]
|
|
457
|
+
: []),
|
|
458
|
+
...(options.taskRunContext?.workItemId
|
|
459
|
+
? ["--work-item-id", options.taskRunContext.workItemId]
|
|
460
|
+
: []),
|
|
461
|
+
...(options.taskRunContext?.artifactIds?.length
|
|
462
|
+
? ["--artifact-ids", options.taskRunContext.artifactIds.join(",")]
|
|
463
|
+
: []),
|
|
464
|
+
...(options.noDefaultTestRoot ? ["--no-default-test-root"] : []),
|
|
465
|
+
...(options.continueDialogId
|
|
466
|
+
? ["--continue", options.continueDialogId]
|
|
467
|
+
: []),
|
|
468
|
+
...(options.spaceId ? ["--space", options.spaceId] : []),
|
|
469
|
+
...(options.category ? ["--category", options.category] : []),
|
|
470
|
+
...(options.inheritedFromDialogKey
|
|
471
|
+
? ["--inherit-from-dialog", options.inheritedFromDialogKey]
|
|
472
|
+
: []),
|
|
473
|
+
...(options.background ? ["--bg"] : []),
|
|
474
|
+
...(typeof options.timeoutMs === "number" ? ["--timeout-ms", String(options.timeoutMs)] : []),
|
|
475
|
+
...(options.noStream ? ["--no-stream"] : []),
|
|
143
476
|
],
|
|
144
477
|
stdin: "inherit",
|
|
145
478
|
stdout: "inherit",
|
|
@@ -151,12 +484,12 @@ async function runScriptBridge(options: RunAgentTurnOptions, scriptPath: string)
|
|
|
151
484
|
},
|
|
152
485
|
});
|
|
153
486
|
|
|
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
|
-
}
|
|
487
|
+
const exitCode = await proc.exited;
|
|
488
|
+
if (exitCode !== 0) {
|
|
489
|
+
options.output.write(`\n[nolo] Agent run exited with code ${exitCode}.\n`);
|
|
490
|
+
}
|
|
491
|
+
return { exitCode };
|
|
492
|
+
}
|
|
160
493
|
|
|
161
494
|
async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string) {
|
|
162
495
|
options.output.write(`\n${options.agentName} -> working...\n`);
|
|
@@ -172,7 +505,10 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
172
505
|
},
|
|
173
506
|
body: JSON.stringify({
|
|
174
507
|
agentKey: options.agentKey,
|
|
175
|
-
userInput: buildUserInputContent(
|
|
508
|
+
userInput: buildUserInputContent(
|
|
509
|
+
prependTaskRunPrompt(options.message, options.taskRunContext),
|
|
510
|
+
options.imageUrls
|
|
511
|
+
),
|
|
176
512
|
runtimeContext: {
|
|
177
513
|
surface: "cli",
|
|
178
514
|
host: "terminal",
|
|
@@ -180,21 +516,37 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
180
516
|
entrypoint: "nolo-cli",
|
|
181
517
|
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
182
518
|
},
|
|
183
|
-
...(options.continueDialogId
|
|
184
|
-
? { continueDialogId: options.continueDialogId }
|
|
185
|
-
: {}),
|
|
186
|
-
|
|
187
|
-
|
|
519
|
+
...(options.continueDialogId
|
|
520
|
+
? { continueDialogId: options.continueDialogId }
|
|
521
|
+
: {}),
|
|
522
|
+
...(options.spaceId ? { spaceId: options.spaceId } : {}),
|
|
523
|
+
...(options.category ? { category: options.category } : {}),
|
|
524
|
+
...(options.inheritedFromDialogKey
|
|
525
|
+
? { inheritedFromDialogKey: options.inheritedFromDialogKey }
|
|
526
|
+
: {}),
|
|
527
|
+
...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
|
|
528
|
+
...(options.background ? { background: true } : {}),
|
|
529
|
+
...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
|
|
530
|
+
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
531
|
+
stream: !options.noStream && !options.background,
|
|
532
|
+
}),
|
|
188
533
|
});
|
|
189
534
|
} catch (error) {
|
|
190
535
|
options.output.write(buildTransportErrorHint(options.serverUrl, error));
|
|
191
536
|
return { exitCode: 1 };
|
|
192
537
|
}
|
|
193
538
|
|
|
194
|
-
const contentType = res.headers.get("content-type") || "";
|
|
195
|
-
if (contentType.includes("text/event-stream") && res.body) {
|
|
196
|
-
|
|
197
|
-
|
|
539
|
+
const contentType = res.headers.get("content-type") || "";
|
|
540
|
+
if (contentType.includes("text/event-stream") && res.body) {
|
|
541
|
+
const result = await readStreamingAgentRun(options, res);
|
|
542
|
+
await recordTaskRunOutcome({
|
|
543
|
+
options,
|
|
544
|
+
status: result.exitCode === 0 ? "completed" : "failed",
|
|
545
|
+
dialogId: result.dialogId,
|
|
546
|
+
...(result.exitCode !== 0 ? { errorCode: "STREAM_FAILED" } : {}),
|
|
547
|
+
});
|
|
548
|
+
return result;
|
|
549
|
+
}
|
|
198
550
|
|
|
199
551
|
let data: any = {};
|
|
200
552
|
try {
|
|
@@ -203,13 +555,19 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
203
555
|
data = {};
|
|
204
556
|
}
|
|
205
557
|
|
|
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
|
-
|
|
558
|
+
if (!res.ok) {
|
|
559
|
+
options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
|
|
560
|
+
if (data?.error || data?.message) {
|
|
561
|
+
options.output.write(`${data.error || data.message}\n`);
|
|
562
|
+
}
|
|
563
|
+
await recordTaskRunOutcome({
|
|
564
|
+
options,
|
|
565
|
+
status: "failed",
|
|
566
|
+
errorCode: `HTTP_${res.status}`,
|
|
567
|
+
resultSummary: String(data?.error || data?.message || "").trim() || undefined,
|
|
568
|
+
});
|
|
569
|
+
return { exitCode: 1 };
|
|
570
|
+
}
|
|
213
571
|
|
|
214
572
|
const content = String(data?.content ?? data?.message ?? "").trim();
|
|
215
573
|
if (content) {
|
|
@@ -218,40 +576,112 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
218
576
|
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
219
577
|
}
|
|
220
578
|
|
|
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
|
-
};
|
|
579
|
+
const usage = formatUsage(data?.usage, data?.dialogId);
|
|
580
|
+
if (usage && shouldShowUsage(options.env)) options.output.write(`${usage}\n`);
|
|
581
|
+
const result = {
|
|
582
|
+
exitCode: 0,
|
|
583
|
+
...(typeof data?.dialogId === "string" && data.dialogId
|
|
584
|
+
? { dialogId: data.dialogId }
|
|
585
|
+
: {}),
|
|
586
|
+
};
|
|
587
|
+
await recordTaskRunOutcome({
|
|
588
|
+
options,
|
|
589
|
+
status: "completed",
|
|
590
|
+
dialogId: result.dialogId,
|
|
591
|
+
resultSummary: content || undefined,
|
|
592
|
+
});
|
|
593
|
+
return result;
|
|
229
594
|
}
|
|
230
595
|
|
|
231
596
|
async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
|
|
232
|
-
return
|
|
597
|
+
return runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: true });
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function runLocalAgentTurnWithAutoRoundExpansion(
|
|
601
|
+
options: RunAgentTurnOptions,
|
|
602
|
+
settings: { reportFailure: boolean }
|
|
603
|
+
) {
|
|
604
|
+
const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
|
|
605
|
+
let currentMaxToolRounds = options.maxToolRounds;
|
|
606
|
+
let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
|
|
607
|
+
|
|
608
|
+
while (true) {
|
|
609
|
+
const attemptOptions = {
|
|
610
|
+
...options,
|
|
611
|
+
...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
|
|
612
|
+
};
|
|
613
|
+
const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
|
|
614
|
+
lastResult = result;
|
|
615
|
+
if (result.exitCode === 0) return result;
|
|
616
|
+
|
|
617
|
+
const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
|
|
618
|
+
if (exhaustedRounds === null) break;
|
|
619
|
+
|
|
620
|
+
const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
|
|
621
|
+
if (nextMaxToolRounds > maxRetryLimit) break;
|
|
622
|
+
|
|
623
|
+
options.output.write(
|
|
624
|
+
`[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
|
|
625
|
+
);
|
|
626
|
+
currentMaxToolRounds = nextMaxToolRounds;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (settings.reportFailure && lastResult?.localError) {
|
|
630
|
+
options.output.write(
|
|
631
|
+
`[nolo] Local agent run failed: ${
|
|
632
|
+
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
|
|
633
|
+
}\n`
|
|
634
|
+
);
|
|
635
|
+
await recordTaskRunOutcome({
|
|
636
|
+
options,
|
|
637
|
+
status: "failed",
|
|
638
|
+
errorCode: "LOCAL_AGENT_FAILED",
|
|
639
|
+
resultSummary:
|
|
640
|
+
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
|
|
641
|
+
toolEvidence: lastResult.toolEvidence,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return lastResult ?? { exitCode: 1 };
|
|
233
645
|
}
|
|
234
646
|
|
|
235
647
|
async function runLocalAgentTurnForCli(
|
|
236
648
|
options: RunAgentTurnOptions,
|
|
237
649
|
settings: { reportFailure: boolean }
|
|
238
650
|
) {
|
|
239
|
-
const adapter =
|
|
240
|
-
options.localRuntimeAdapter ||
|
|
241
|
-
options.localRuntimeAdapterFactory?.(options.env) ||
|
|
242
|
-
buildDefaultLocalRuntimeAdapter(options);
|
|
651
|
+
const adapter = resolveLocalRuntimeAdapter(options);
|
|
243
652
|
if (!adapter) {
|
|
244
653
|
options.output.write("[nolo] Local runtime was requested but no local runtime adapter is available.\n");
|
|
245
654
|
return { exitCode: 1 };
|
|
246
655
|
}
|
|
247
656
|
|
|
248
657
|
options.output.write(`\n${options.agentName} -> working locally...\n`);
|
|
658
|
+
const toolEvents: LocalAgentToolEvent[] = [];
|
|
249
659
|
try {
|
|
660
|
+
const traceLocalTools = shouldTraceLocalTools(options);
|
|
661
|
+
const collectToolEvidence = shouldCollectLocalToolEvidence(options);
|
|
250
662
|
const result = await runLocalAgentTurn({
|
|
251
663
|
adapter,
|
|
252
664
|
agentRef: options.agentKey,
|
|
253
|
-
input: buildUserInputContent(
|
|
665
|
+
input: buildUserInputContent(
|
|
666
|
+
prependTaskRunPrompt(options.message, options.taskRunContext),
|
|
667
|
+
options.imageUrls
|
|
668
|
+
),
|
|
254
669
|
continueDialogId: options.continueDialogId,
|
|
670
|
+
spaceId: options.spaceId,
|
|
671
|
+
category: options.category,
|
|
672
|
+
inheritedFromDialogKey: options.inheritedFromDialogKey,
|
|
673
|
+
parentDialogId: options.parentDialogId,
|
|
674
|
+
background: options.background,
|
|
675
|
+
noStream: options.noStream,
|
|
676
|
+
maxToolRounds: options.maxToolRounds,
|
|
677
|
+
...(traceLocalTools || collectToolEvidence
|
|
678
|
+
? {
|
|
679
|
+
onToolEvent: (event) => {
|
|
680
|
+
if (collectToolEvidence) toolEvents.push(event);
|
|
681
|
+
if (traceLocalTools) options.output.write(formatToolTraceEvent(event));
|
|
682
|
+
},
|
|
683
|
+
}
|
|
684
|
+
: {}),
|
|
255
685
|
});
|
|
256
686
|
const content = result.content.trim();
|
|
257
687
|
if (content) {
|
|
@@ -259,6 +689,13 @@ async function runLocalAgentTurnForCli(
|
|
|
259
689
|
} else {
|
|
260
690
|
options.output.write(`\n${options.agentName} > (no text response)\n`);
|
|
261
691
|
}
|
|
692
|
+
await recordTaskRunOutcome({
|
|
693
|
+
options,
|
|
694
|
+
status: "completed",
|
|
695
|
+
dialogId: result.dialogId,
|
|
696
|
+
resultSummary: content || undefined,
|
|
697
|
+
toolEvidence: summarizeLocalToolEvidence(toolEvents),
|
|
698
|
+
});
|
|
262
699
|
return { exitCode: 0, dialogId: result.dialogId };
|
|
263
700
|
} catch (error) {
|
|
264
701
|
if (settings.reportFailure) {
|
|
@@ -268,7 +705,7 @@ async function runLocalAgentTurnForCli(
|
|
|
268
705
|
}\n`
|
|
269
706
|
);
|
|
270
707
|
}
|
|
271
|
-
return { exitCode: 1, localError: error };
|
|
708
|
+
return { exitCode: 1, localError: error, toolEvidence: summarizeLocalToolEvidence(toolEvents) };
|
|
272
709
|
}
|
|
273
710
|
}
|
|
274
711
|
|
|
@@ -381,12 +818,15 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
381
818
|
}
|
|
382
819
|
|
|
383
820
|
if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
|
|
384
|
-
const
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
821
|
+
const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
|
|
822
|
+
if (!skipLocal) {
|
|
823
|
+
const localResult = await runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: false });
|
|
824
|
+
if (localResult.exitCode === 0) {
|
|
825
|
+
return {
|
|
826
|
+
exitCode: localResult.exitCode,
|
|
827
|
+
...(localResult.dialogId ? { dialogId: localResult.dialogId } : {}),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
390
830
|
}
|
|
391
831
|
}
|
|
392
832
|
|