nolo-cli 0.1.39 → 0.1.40
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 +4 -4
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -0
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +15 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +2 -13
- package/agent-runtime/localWorkspaceTools.ts +42 -110
- package/agent-runtime/runtimeToolPolicy.ts +2 -11
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +1 -0
- package/agentAliases.ts +2 -1
- package/agentRunCommand.ts +10 -32
- package/agentRuntimeLocal.ts +1 -0
- package/ai/agent/cliExecutor.ts +249 -34
- package/cli/agentAliases.ts +2 -1
- package/cli/agentRunCommand.ts +10 -32
- package/cli/agentRuntimeLocal.ts +1 -0
- package/cli/client/agentRun.ts +40 -87
- package/cli/client/localRuntimeAdapter.ts +179 -26
- package/cli/commandRegistry.ts +2 -2
- package/cli/machineWsRunDispatch.ts +51 -6
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/client/agentConfigResolver.test.ts +2 -2
- package/client/agentRun.test.ts +123 -57
- package/client/agentRun.ts +40 -87
- package/client/localRuntimeAdapter.test.ts +245 -31
- package/client/localRuntimeAdapter.ts +179 -26
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/database/server/levelAuthorityStore.ts +20 -1
- package/machineWsRunDispatch.ts +51 -6
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +2 -1
package/cli/client/agentRun.ts
CHANGED
|
@@ -44,7 +44,6 @@ type RunAgentTurnOptions = {
|
|
|
44
44
|
parentDialogId?: string;
|
|
45
45
|
background?: boolean;
|
|
46
46
|
noStream?: boolean;
|
|
47
|
-
noDefaultTestRoot?: boolean;
|
|
48
47
|
scriptDir: string;
|
|
49
48
|
env: EnvLike;
|
|
50
49
|
output: OutputLike;
|
|
@@ -52,9 +51,9 @@ type RunAgentTurnOptions = {
|
|
|
52
51
|
localRuntimeAdapter?: AgentRuntimeHostAdapter;
|
|
53
52
|
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
54
53
|
localRuntimeCwd?: string;
|
|
55
|
-
maxToolRounds?: number;
|
|
56
54
|
timeoutMs?: number;
|
|
57
55
|
traceTools?: boolean;
|
|
56
|
+
eventsMode?: "jsonl";
|
|
58
57
|
taskRunContext?: TaskRunPromptContext;
|
|
59
58
|
taskRunRecorder?: (args: {
|
|
60
59
|
options: RunAgentTurnOptions;
|
|
@@ -232,7 +231,7 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
232
231
|
|
|
233
232
|
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
234
233
|
if (isBuiltinNoloAgentRef(options.agentKey)) return false;
|
|
235
|
-
if (options.localRuntimeCwd
|
|
234
|
+
if (options.localRuntimeCwd) {
|
|
236
235
|
return false;
|
|
237
236
|
}
|
|
238
237
|
if (isKnownServerPlatformAgent(options)) {
|
|
@@ -278,7 +277,14 @@ function buildUserInputContent(message: string, imageUrls: string[] = []) {
|
|
|
278
277
|
}
|
|
279
278
|
|
|
280
279
|
function shouldTraceLocalTools(options: RunAgentTurnOptions) {
|
|
281
|
-
|
|
280
|
+
const setting = (options.env.NOLO_TRACE_TOOLS ?? "").trim().toLowerCase();
|
|
281
|
+
if (setting === "0" || setting === "false" || setting === "off") return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveAgentEventMode(options: RunAgentTurnOptions): "text" | "jsonl" {
|
|
286
|
+
if (options.eventsMode === "jsonl") return "jsonl";
|
|
287
|
+
return options.env.NOLO_AGENT_EVENTS === "jsonl" ? "jsonl" : "text";
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
function shouldCollectLocalToolEvidence(options: RunAgentTurnOptions) {
|
|
@@ -307,15 +313,6 @@ function summarizeLocalToolEvidence(events: LocalAgentToolEvent[]): TaskRunToolE
|
|
|
307
313
|
};
|
|
308
314
|
}
|
|
309
315
|
|
|
310
|
-
const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 512;
|
|
311
|
-
|
|
312
|
-
function parseLocalToolRoundLimitError(error: unknown) {
|
|
313
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
314
|
-
const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
|
|
315
|
-
if (!match) return null;
|
|
316
|
-
return Number(match[1]);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
316
|
function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
|
|
320
317
|
return Boolean(
|
|
321
318
|
error &&
|
|
@@ -326,33 +323,33 @@ function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
|
|
|
326
323
|
);
|
|
327
324
|
}
|
|
328
325
|
|
|
329
|
-
function parsePositiveInteger(value: string | undefined) {
|
|
330
|
-
if (!value) return null;
|
|
331
|
-
const parsed = Number(value);
|
|
332
|
-
if (!Number.isInteger(parsed) || parsed <= 0) return null;
|
|
333
|
-
return parsed;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
|
|
337
|
-
return (
|
|
338
|
-
parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
|
|
339
|
-
DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function nextLocalToolRoundLimit(current: number) {
|
|
344
|
-
return current <= 0 ? 1 : current * 2;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
326
|
function formatToolTraceEvent(event: LocalAgentToolEvent) {
|
|
348
327
|
const round = event.round + 1;
|
|
328
|
+
const detail = event.argumentsPreview ? ` ${event.argumentsPreview}` : "";
|
|
349
329
|
if (event.type === "tool-call") {
|
|
350
|
-
return `[nolo:tool]
|
|
330
|
+
return `[nolo:tool] #${round} -> ${event.toolName}${detail}\n`;
|
|
351
331
|
}
|
|
352
332
|
if (event.type === "tool-error") {
|
|
353
|
-
|
|
333
|
+
const elapsed = typeof event.elapsedMs === "number" ? ` ${event.elapsedMs}ms` : "";
|
|
334
|
+
return `[nolo:tool] #${round} !! ${event.toolName}${elapsed}: ${event.message ?? "failed"}\n`;
|
|
354
335
|
}
|
|
355
|
-
|
|
336
|
+
const elapsed = typeof event.elapsedMs === "number" ? ` ${event.elapsedMs}ms` : "";
|
|
337
|
+
const summary = event.summary ? ` ${event.summary}` : "";
|
|
338
|
+
return `[nolo:tool] #${round} <- ${event.toolName}${elapsed}${summary}\n`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function formatToolJsonEvent(event: LocalAgentToolEvent) {
|
|
342
|
+
return `${JSON.stringify({
|
|
343
|
+
schemaVersion: 1,
|
|
344
|
+
type: event.type,
|
|
345
|
+
round: event.round + 1,
|
|
346
|
+
tool: event.toolName,
|
|
347
|
+
toolCallId: event.toolCallId,
|
|
348
|
+
...(event.argumentsPreview ? { argsPreview: event.argumentsPreview } : {}),
|
|
349
|
+
...(typeof event.elapsedMs === "number" ? { elapsedMs: event.elapsedMs } : {}),
|
|
350
|
+
...(event.summary ? { summary: event.summary } : {}),
|
|
351
|
+
...(event.message ? { message: event.message } : {}),
|
|
352
|
+
})}\n`;
|
|
356
353
|
}
|
|
357
354
|
|
|
358
355
|
function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
|
|
@@ -629,7 +626,6 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
629
626
|
: {}),
|
|
630
627
|
...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
|
|
631
628
|
...(options.background ? { background: true } : {}),
|
|
632
|
-
...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
|
|
633
629
|
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
634
630
|
stream: !options.noStream && !options.background,
|
|
635
631
|
}),
|
|
@@ -717,7 +713,7 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
717
713
|
}
|
|
718
714
|
|
|
719
715
|
async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
|
|
720
|
-
return
|
|
716
|
+
return runLocalAgentTurnForCli(options, { reportFailure: true });
|
|
721
717
|
}
|
|
722
718
|
|
|
723
719
|
async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
|
|
@@ -727,53 +723,6 @@ async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
|
|
|
727
723
|
return Boolean(agentConfig);
|
|
728
724
|
}
|
|
729
725
|
|
|
730
|
-
async function runLocalAgentTurnWithAutoRoundExpansion(
|
|
731
|
-
options: RunAgentTurnOptions,
|
|
732
|
-
settings: { reportFailure: boolean }
|
|
733
|
-
) {
|
|
734
|
-
const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
|
|
735
|
-
let currentMaxToolRounds = options.maxToolRounds;
|
|
736
|
-
let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
|
|
737
|
-
|
|
738
|
-
while (true) {
|
|
739
|
-
const attemptOptions = {
|
|
740
|
-
...options,
|
|
741
|
-
...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
|
|
742
|
-
};
|
|
743
|
-
const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
|
|
744
|
-
lastResult = result;
|
|
745
|
-
if (result.exitCode === 0) return result;
|
|
746
|
-
|
|
747
|
-
const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
|
|
748
|
-
if (exhaustedRounds === null) break;
|
|
749
|
-
|
|
750
|
-
const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
|
|
751
|
-
if (nextMaxToolRounds > maxRetryLimit) break;
|
|
752
|
-
|
|
753
|
-
options.output.write(
|
|
754
|
-
`[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
|
|
755
|
-
);
|
|
756
|
-
currentMaxToolRounds = nextMaxToolRounds;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
if (settings.reportFailure && lastResult?.localError) {
|
|
760
|
-
options.output.write(
|
|
761
|
-
`[nolo] Local agent run failed: ${
|
|
762
|
-
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
|
|
763
|
-
}\n`
|
|
764
|
-
);
|
|
765
|
-
await recordTaskRunOutcome({
|
|
766
|
-
options,
|
|
767
|
-
status: "failed",
|
|
768
|
-
errorCode: "LOCAL_AGENT_FAILED",
|
|
769
|
-
resultSummary:
|
|
770
|
-
lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
|
|
771
|
-
toolEvidence: lastResult.toolEvidence,
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
return lastResult ?? { exitCode: 1 };
|
|
775
|
-
}
|
|
776
|
-
|
|
777
726
|
async function runLocalAgentTurnForCli(
|
|
778
727
|
options: RunAgentTurnOptions,
|
|
779
728
|
settings: { reportFailure: boolean }
|
|
@@ -790,6 +739,7 @@ async function runLocalAgentTurnForCli(
|
|
|
790
739
|
try {
|
|
791
740
|
const traceLocalTools = shouldTraceLocalTools(options);
|
|
792
741
|
const collectToolEvidence = shouldCollectLocalToolEvidence(options);
|
|
742
|
+
const eventMode = resolveAgentEventMode(options);
|
|
793
743
|
const result = await runLocalAgentTurn({
|
|
794
744
|
adapter,
|
|
795
745
|
agentRef: options.agentKey,
|
|
@@ -804,7 +754,6 @@ async function runLocalAgentTurnForCli(
|
|
|
804
754
|
parentDialogId: options.parentDialogId,
|
|
805
755
|
background: options.background,
|
|
806
756
|
noStream: options.noStream,
|
|
807
|
-
maxToolRounds: options.maxToolRounds,
|
|
808
757
|
...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
|
|
809
758
|
...(traceLocalTools || collectToolEvidence
|
|
810
759
|
? {
|
|
@@ -812,7 +761,11 @@ async function runLocalAgentTurnForCli(
|
|
|
812
761
|
if (collectToolEvidence) toolEvents.push(event);
|
|
813
762
|
if (traceLocalTools) {
|
|
814
763
|
spinner.stop();
|
|
815
|
-
options.output.write(
|
|
764
|
+
options.output.write(
|
|
765
|
+
eventMode === "jsonl"
|
|
766
|
+
? formatToolJsonEvent(event)
|
|
767
|
+
: formatToolTraceEvent(event)
|
|
768
|
+
);
|
|
816
769
|
}
|
|
817
770
|
},
|
|
818
771
|
}
|
|
@@ -968,7 +921,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
968
921
|
if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
|
|
969
922
|
const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
|
|
970
923
|
if (!skipLocal) {
|
|
971
|
-
const localResult = await
|
|
924
|
+
const localResult = await runLocalAgentTurnForCli(options, { reportFailure: false });
|
|
972
925
|
if (localResult.exitCode === 0) {
|
|
973
926
|
return {
|
|
974
927
|
exitCode: localResult.exitCode,
|
|
@@ -982,7 +935,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
|
|
|
982
935
|
try {
|
|
983
936
|
const refreshed = await refreshMissingLocalAgentConfig(options);
|
|
984
937
|
if (refreshed) {
|
|
985
|
-
const retriedLocalResult = await
|
|
938
|
+
const retriedLocalResult = await runLocalAgentTurnForCli(options, {
|
|
986
939
|
reportFailure: false,
|
|
987
940
|
});
|
|
988
941
|
if (retriedLocalResult.exitCode === 0) {
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
resolveCurrentRunRuntimeToolPolicy,
|
|
22
22
|
resolveLocalWorkspaceExecutorOptionsFromPolicy,
|
|
23
23
|
resolveRequestedRuntimeToolNames,
|
|
24
|
+
resolveRuntimeToolSurfaceForAgent,
|
|
24
25
|
shouldUsePlatformChatProvider,
|
|
25
26
|
} from "../agentRuntimeLocal";
|
|
26
27
|
import type { AgentRuntimeChatMessage } from "../../agent-runtime";
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
import { executeLocalToolWithPolicy } from "./localToolPolicy";
|
|
44
45
|
import { taskRunFunctionSchema } from "../../ai/tools/agent/taskRunTool";
|
|
45
46
|
import {
|
|
47
|
+
MIMO_MONTH_AGENT_KEY,
|
|
46
48
|
NOLO_DEFAULT_AGENT_ID,
|
|
47
49
|
NOLO_DEFAULT_AGENT_KEY,
|
|
48
50
|
} from "../agentAliases";
|
|
@@ -50,14 +52,26 @@ import {
|
|
|
50
52
|
buildNoloWorkspaceCliToolExecutors,
|
|
51
53
|
buildNoloWorkspaceOpenAiTools,
|
|
52
54
|
filterNoloWorkspaceToolNames,
|
|
53
|
-
NOLO_WORKSPACE_TOOL_NAMES,
|
|
54
|
-
NOLO_WORKSPACE_TOOL_PROMPT,
|
|
55
55
|
parseNoloWorkspaceToolArguments,
|
|
56
56
|
} from "../../agent-runtime/noloWorkspaceTools";
|
|
57
|
+
import { executeCli as defaultExecuteCli, type CliExecuteResult, type CliImageInput } from "../../ai/agent/cliExecutor";
|
|
58
|
+
import { buildCliPrompt } from "../../ai/agent/cliPrompt";
|
|
57
59
|
|
|
58
60
|
type EnvLike = Record<string, string | undefined>;
|
|
59
61
|
type FetchInput = Parameters<typeof fetch>[0];
|
|
60
62
|
type FetchInit = Parameters<typeof fetch>[1];
|
|
63
|
+
type LocalCliExecutor = (
|
|
64
|
+
provider: string,
|
|
65
|
+
prompt: string,
|
|
66
|
+
options: {
|
|
67
|
+
model?: string;
|
|
68
|
+
timeout?: number;
|
|
69
|
+
cwd?: string;
|
|
70
|
+
yolo?: boolean;
|
|
71
|
+
env?: Record<string, string | undefined>;
|
|
72
|
+
imageInputs?: CliImageInput[];
|
|
73
|
+
}
|
|
74
|
+
) => Promise<CliExecuteResult>;
|
|
61
75
|
const TRANSIENT_FETCH_MAX_ATTEMPTS = 8;
|
|
62
76
|
const TRANSIENT_FETCH_RETRY_BASE_DELAY_MS = 250;
|
|
63
77
|
export const BUILTIN_NOLO_AGENT_ID = NOLO_DEFAULT_AGENT_ID;
|
|
@@ -77,6 +91,7 @@ export type CliLocalRuntimeAdapterDeps = {
|
|
|
77
91
|
cwd?: string;
|
|
78
92
|
output?: { write(chunk: string): unknown };
|
|
79
93
|
localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
|
|
94
|
+
executeCli?: LocalCliExecutor;
|
|
80
95
|
sleep?: (ms: number) => Promise<void>;
|
|
81
96
|
loopbackRequest?: (input: FetchInput, init?: FetchInit) => Promise<Response>;
|
|
82
97
|
};
|
|
@@ -93,10 +108,6 @@ function resolveLocalUserId(env: EnvLike) {
|
|
|
93
108
|
return env.NOLO_LOCAL_USER_ID || env.NOLO_USER_ID || "local";
|
|
94
109
|
}
|
|
95
110
|
|
|
96
|
-
function shellModeEnabled(env: EnvLike) {
|
|
97
|
-
return env.NOLO_LOCAL_SHELL_MODE === "worktree";
|
|
98
|
-
}
|
|
99
|
-
|
|
100
111
|
function parseLocalToolBudgets(env: EnvLike) {
|
|
101
112
|
const raw = env.NOLO_LOCAL_TOOL_BUDGETS?.trim();
|
|
102
113
|
if (!raw) return {};
|
|
@@ -251,7 +262,79 @@ function parseJsonObject(raw: string) {
|
|
|
251
262
|
}
|
|
252
263
|
}
|
|
253
264
|
|
|
265
|
+
function isCliProviderAgent(agentConfig: AgentRuntimeAgentConfig) {
|
|
266
|
+
return Boolean(
|
|
267
|
+
agentConfig.apiSource === "cli" ||
|
|
268
|
+
agentConfig.provider === "cli" ||
|
|
269
|
+
agentConfig.cliProvider
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolveCliProviderName(agentConfig: AgentRuntimeAgentConfig) {
|
|
274
|
+
return (agentConfig.cliProvider || agentConfig.provider || "codex").trim() || "codex";
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function stringifyRuntimeMessageContent(content: AgentRuntimeChatMessage["content"]) {
|
|
278
|
+
if (typeof content === "string") return content;
|
|
279
|
+
if (Array.isArray(content)) {
|
|
280
|
+
return content
|
|
281
|
+
.map((part) => {
|
|
282
|
+
if (typeof part === "string") return part;
|
|
283
|
+
if (part && typeof part === "object" && "text" in part) {
|
|
284
|
+
return String((part as any).text ?? "");
|
|
285
|
+
}
|
|
286
|
+
return JSON.stringify(part);
|
|
287
|
+
})
|
|
288
|
+
.filter((part) => part.trim())
|
|
289
|
+
.join("\n");
|
|
290
|
+
}
|
|
291
|
+
return content == null ? "" : String(content);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildPromptForCliProvider(messages: AgentRuntimeChatMessage[]) {
|
|
295
|
+
const systemPrompt = messages
|
|
296
|
+
.filter((message) => message.role === "system")
|
|
297
|
+
.map((message) => stringifyRuntimeMessageContent(message.content).trim())
|
|
298
|
+
.filter(Boolean)
|
|
299
|
+
.join("\n\n");
|
|
300
|
+
const taskPrompt = messages
|
|
301
|
+
.filter((message) => message.role !== "system")
|
|
302
|
+
.map((message) => {
|
|
303
|
+
const content = stringifyRuntimeMessageContent(message.content).trim();
|
|
304
|
+
if (!content) return "";
|
|
305
|
+
return `[${message.role}]\n${content}`;
|
|
306
|
+
})
|
|
307
|
+
.filter(Boolean)
|
|
308
|
+
.join("\n\n");
|
|
309
|
+
return buildCliPrompt(systemPrompt, taskPrompt);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function collectCliProviderImageInputs(messages: AgentRuntimeChatMessage[]) {
|
|
313
|
+
const urls: string[] = [];
|
|
314
|
+
for (const message of messages) {
|
|
315
|
+
if (!Array.isArray(message.content)) continue;
|
|
316
|
+
for (const part of message.content) {
|
|
317
|
+
if (
|
|
318
|
+
part &&
|
|
319
|
+
typeof part === "object" &&
|
|
320
|
+
(part as any).type === "image_url" &&
|
|
321
|
+
typeof (part as any).image_url?.url === "string" &&
|
|
322
|
+
(part as any).image_url.url.trim()
|
|
323
|
+
) {
|
|
324
|
+
urls.push((part as any).image_url.url.trim());
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return urls;
|
|
329
|
+
}
|
|
330
|
+
|
|
254
331
|
function logLocalRuntimeDiagnostic(event: string, fields: Record<string, unknown>) {
|
|
332
|
+
if (
|
|
333
|
+
process.env.NOLO_LOCAL_RUNTIME_DEBUG !== "1" &&
|
|
334
|
+
process.env.NOLO_DEBUG !== "1"
|
|
335
|
+
) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
255
338
|
console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
|
|
256
339
|
}
|
|
257
340
|
|
|
@@ -266,31 +349,38 @@ function summarizeOpenAiToolNames(tools: Array<Record<string, unknown>>) {
|
|
|
266
349
|
.filter((name): name is string => Boolean(name));
|
|
267
350
|
}
|
|
268
351
|
|
|
269
|
-
function
|
|
352
|
+
function shouldExposeLocalPlatformTools(agentKey?: string) {
|
|
353
|
+
return agentKey !== MIMO_MONTH_AGENT_KEY;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function buildOpenAiTools(args: { agentKey?: string; toolNames?: string[]; env: EnvLike }) {
|
|
270
357
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
358
|
+
const exposePlatformTools = shouldExposeLocalPlatformTools(args.agentKey);
|
|
271
359
|
return [
|
|
272
360
|
...buildLocalWorkspaceOpenAiTools({
|
|
273
361
|
toolNames: toolset.toolNames,
|
|
274
362
|
exposeShellTools: toolset.exposeShellTools,
|
|
275
363
|
}),
|
|
276
|
-
...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
|
|
277
|
-
...buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }),
|
|
364
|
+
...(exposePlatformTools ? buildServerPlatformOpenAiTools({ toolNames: args.toolNames }) : []),
|
|
365
|
+
...(exposePlatformTools ? buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }) : []),
|
|
278
366
|
];
|
|
279
367
|
}
|
|
280
368
|
|
|
281
369
|
function buildLocalWorkspaceToolsetForEnv(args: { toolNames?: string[]; env: EnvLike }) {
|
|
370
|
+
void args.env;
|
|
282
371
|
const toolset = buildLocalWorkspaceToolset({
|
|
283
372
|
declaredToolNames: args.toolNames,
|
|
284
|
-
exposeShellTools:
|
|
373
|
+
exposeShellTools: true,
|
|
285
374
|
});
|
|
286
375
|
return toolset;
|
|
287
376
|
}
|
|
288
377
|
|
|
289
378
|
function buildLocalPolicyToolNames(args: { toolNames?: string[]; env: EnvLike }) {
|
|
379
|
+
void args.env;
|
|
290
380
|
return [
|
|
291
381
|
...buildLocalWorkspacePolicyToolNames({
|
|
292
382
|
declaredToolNames: args.toolNames,
|
|
293
|
-
exposeShellTools:
|
|
383
|
+
exposeShellTools: true,
|
|
294
384
|
}),
|
|
295
385
|
...((args.toolNames ?? []).includes("taskRun") ? ["taskRun"] : []),
|
|
296
386
|
...filterNoloWorkspaceToolNames(args.toolNames),
|
|
@@ -336,16 +426,32 @@ function isBuiltinNoloAgentConfig(agentConfig: AgentRuntimeAgentConfig | null |
|
|
|
336
426
|
return isBuiltinNoloAgentRef(key) || isBuiltinNoloAgentRef(id);
|
|
337
427
|
}
|
|
338
428
|
|
|
339
|
-
function
|
|
340
|
-
|
|
341
|
-
|
|
429
|
+
function withResolvedRuntimeToolSurface(
|
|
430
|
+
agentConfig: AgentRuntimeAgentConfig | null,
|
|
431
|
+
env: EnvLike
|
|
432
|
+
) {
|
|
433
|
+
if (!agentConfig) return agentConfig;
|
|
434
|
+
const currentUserId = resolveLocalUserId(env);
|
|
435
|
+
const rawRecord = (agentConfig as any).rawRecord ?? {};
|
|
436
|
+
const ownerId =
|
|
437
|
+
typeof rawRecord.userId === "string" && rawRecord.userId.trim()
|
|
438
|
+
? rawRecord.userId.trim()
|
|
439
|
+
: null;
|
|
440
|
+
const toolSurface = resolveRuntimeToolSurfaceForAgent({
|
|
441
|
+
explicitToolNames: agentConfig.toolNames,
|
|
442
|
+
currentUserId,
|
|
443
|
+
agentOwnerId: ownerId,
|
|
444
|
+
agentKey: rawRecord.dbKey ?? agentConfig.key,
|
|
445
|
+
isPublic: !isBuiltinNoloAgentConfig(agentConfig) && rawRecord.isPublic === true,
|
|
446
|
+
sharingLevel: typeof rawRecord.sharingLevel === "string" ? rawRecord.sharingLevel : null,
|
|
447
|
+
trustedPrivateInvocation: isBuiltinNoloAgentConfig(agentConfig),
|
|
448
|
+
runtimeHost: "cli",
|
|
449
|
+
});
|
|
342
450
|
return {
|
|
343
451
|
...agentConfig,
|
|
344
|
-
toolNames,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
NOLO_WORKSPACE_TOOL_PROMPT,
|
|
348
|
-
].filter(Boolean).join("\n\n"),
|
|
452
|
+
toolNames: toolSurface.finalToolNames,
|
|
453
|
+
toolSurface,
|
|
454
|
+
prompt: agentConfig.prompt,
|
|
349
455
|
};
|
|
350
456
|
}
|
|
351
457
|
|
|
@@ -500,11 +606,14 @@ export function createCliLocalRuntimeAdapter(
|
|
|
500
606
|
host: "cli",
|
|
501
607
|
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
|
|
502
608
|
loadAgentConfig: async (agentRef) => {
|
|
503
|
-
const agentConfig =
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
609
|
+
const agentConfig = withResolvedRuntimeToolSurface(
|
|
610
|
+
await readAgentFromStore({
|
|
611
|
+
agentRef,
|
|
612
|
+
store: await resolveStore(deps),
|
|
613
|
+
userId,
|
|
614
|
+
}),
|
|
615
|
+
deps.env
|
|
616
|
+
);
|
|
508
617
|
const requestedToolNames = agentConfig
|
|
509
618
|
? resolveRequestedRuntimeToolNames({ agentConfig })
|
|
510
619
|
: [];
|
|
@@ -537,6 +646,50 @@ export function createCliLocalRuntimeAdapter(
|
|
|
537
646
|
cwd: workspaceRoot,
|
|
538
647
|
}),
|
|
539
648
|
resolveProvider: async (agentConfig) => {
|
|
649
|
+
if (isCliProviderAgent(agentConfig)) {
|
|
650
|
+
const provider = resolveCliProviderName(agentConfig);
|
|
651
|
+
logLocalRuntimeDiagnostic("provider.selected", {
|
|
652
|
+
agentKey: agentConfig.key,
|
|
653
|
+
transport: "local-cli",
|
|
654
|
+
apiSource: agentConfig.apiSource ?? null,
|
|
655
|
+
provider,
|
|
656
|
+
model: agentConfig.model ?? null,
|
|
657
|
+
cwd: workspaceRoot,
|
|
658
|
+
});
|
|
659
|
+
return {
|
|
660
|
+
model: agentConfig.model || provider,
|
|
661
|
+
complete: async (messages, options) => {
|
|
662
|
+
const executeCli = deps.executeCli ?? (defaultExecuteCli as LocalCliExecutor);
|
|
663
|
+
const imageUrls = collectCliProviderImageInputs(messages);
|
|
664
|
+
const imageInputs: CliImageInput[] | undefined =
|
|
665
|
+
imageUrls.length > 0
|
|
666
|
+
? imageUrls.map((url) => ({ source: url }))
|
|
667
|
+
: undefined;
|
|
668
|
+
const prompt = buildPromptForCliProvider(messages);
|
|
669
|
+
try {
|
|
670
|
+
const result = await executeCli(provider, prompt, {
|
|
671
|
+
...(agentConfig.model ? { model: agentConfig.model } : {}),
|
|
672
|
+
...(options?.timeoutMs ? { timeout: options.timeoutMs } : {}),
|
|
673
|
+
cwd: workspaceRoot,
|
|
674
|
+
yolo: true,
|
|
675
|
+
env: deps.env,
|
|
676
|
+
...(imageInputs ? { imageInputs } : {}),
|
|
677
|
+
});
|
|
678
|
+
return {
|
|
679
|
+
content: result.text,
|
|
680
|
+
model: agentConfig.model || provider,
|
|
681
|
+
raw: result.raw,
|
|
682
|
+
};
|
|
683
|
+
} catch (error) {
|
|
684
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
685
|
+
throw new Error(
|
|
686
|
+
`Local CLI provider "${provider}" is unavailable or failed: ${message}`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
540
693
|
if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
|
|
541
694
|
const providerConfig = resolvePlatformChatProviderConfig({
|
|
542
695
|
agentConfig,
|
|
@@ -563,6 +716,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
563
716
|
agentConfig,
|
|
564
717
|
});
|
|
565
718
|
const tools = buildOpenAiTools({
|
|
719
|
+
agentKey: agentConfig.key,
|
|
566
720
|
toolNames: requestedToolNames,
|
|
567
721
|
env: deps.env,
|
|
568
722
|
});
|
|
@@ -582,7 +736,6 @@ export function createCliLocalRuntimeAdapter(
|
|
|
582
736
|
toolCount: tools.length,
|
|
583
737
|
requestedToolNames,
|
|
584
738
|
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
585
|
-
shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
586
739
|
timeoutMs: options?.timeoutMs ?? null,
|
|
587
740
|
});
|
|
588
741
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
@@ -636,6 +789,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
636
789
|
agentConfig,
|
|
637
790
|
});
|
|
638
791
|
const tools = buildOpenAiTools({
|
|
792
|
+
agentKey: agentConfig.key,
|
|
639
793
|
toolNames: requestedToolNames,
|
|
640
794
|
env: deps.env,
|
|
641
795
|
});
|
|
@@ -654,7 +808,6 @@ export function createCliLocalRuntimeAdapter(
|
|
|
654
808
|
toolCount: tools.length,
|
|
655
809
|
requestedToolNames,
|
|
656
810
|
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
657
|
-
shellMode: deps.env.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
658
811
|
timeoutMs: options?.timeoutMs ?? null,
|
|
659
812
|
});
|
|
660
813
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
package/cli/commandRegistry.ts
CHANGED
|
@@ -69,8 +69,8 @@ export function renderHelpText() {
|
|
|
69
69
|
" nolo space read 01KKY77TT0DA9NY7TNW3R7255N --content-key page-user-id --brief",
|
|
70
70
|
" nolo space delete --name-prefix rn_owner_verify_0504 --yes",
|
|
71
71
|
" nolo table query --table 01ABCXYZ",
|
|
72
|
-
' nolo table query --table meta-
|
|
73
|
-
' nolo table update-row --table meta-
|
|
72
|
+
' nolo table query --table meta-0e95801d90-NOLOTASKBOARD --columns \'["title","status","owner","priority","codeStatus"]\' --no-base-fields --output items',
|
|
73
|
+
' nolo table update-row --table meta-0e95801d90-NOLOTASKBOARD --row 01ROWID --changes \'{"status":"已完成"}\'',
|
|
74
74
|
" nolo task-run read-context --row-dbkey row-user-table-row",
|
|
75
75
|
" nolo llama status",
|
|
76
76
|
];
|
|
@@ -20,9 +20,52 @@ type EnvLike = Record<string, string | undefined>;
|
|
|
20
20
|
export type LocalCliExecutor = (
|
|
21
21
|
provider: string,
|
|
22
22
|
prompt: string,
|
|
23
|
-
options: {
|
|
23
|
+
options: {
|
|
24
|
+
model?: string;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
yolo?: boolean;
|
|
28
|
+
env?: EnvLike;
|
|
29
|
+
imageInputs?: Array<{ source: string; materializedPath?: string }>;
|
|
30
|
+
}
|
|
24
31
|
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
25
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 从 connector payload 的 userInput 中提取 multimodal 内容。
|
|
35
|
+
* 如果 userInput 是字符串,直接返回 { text, imageUrls: [] }。
|
|
36
|
+
* 如果 userInput 是 multimodal content array(`[{ type: "text" }, { type: "image_url" }]`),
|
|
37
|
+
* 提取 text 和 image_url parts。
|
|
38
|
+
*/
|
|
39
|
+
function extractMultimodalUserInput(userInput: unknown): {
|
|
40
|
+
text: string;
|
|
41
|
+
imageUrls: string[];
|
|
42
|
+
} {
|
|
43
|
+
if (typeof userInput === "string") {
|
|
44
|
+
return { text: userInput, imageUrls: [] };
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(userInput)) {
|
|
47
|
+
return { text: String(userInput ?? ""), imageUrls: [] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const textParts: string[] = [];
|
|
51
|
+
const imageUrls: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (const part of userInput) {
|
|
54
|
+
if (!part || typeof part !== "object") continue;
|
|
55
|
+
if ((part as any).type === "text" && typeof (part as any).text === "string") {
|
|
56
|
+
textParts.push((part as any).text);
|
|
57
|
+
} else if (
|
|
58
|
+
(part as any).type === "image_url" &&
|
|
59
|
+
typeof (part as any).image_url?.url === "string" &&
|
|
60
|
+
(part as any).image_url.url.trim()
|
|
61
|
+
) {
|
|
62
|
+
imageUrls.push((part as any).image_url.url.trim());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { text: textParts.join("\n"), imageUrls };
|
|
67
|
+
}
|
|
68
|
+
|
|
26
69
|
type ConnectorRuntimePolicy = {
|
|
27
70
|
runtimeTools?: string[];
|
|
28
71
|
agentTools?: string[];
|
|
@@ -103,7 +146,6 @@ function summarizeRuntimePolicyForLogs(policy?: ConnectorRuntimePolicy) {
|
|
|
103
146
|
workspaceMode: policy?.workspace?.mode ?? null,
|
|
104
147
|
workspaceCwd: policy?.workspace?.cwd ?? null,
|
|
105
148
|
shellEnabled: policy?.shell?.enabled ?? null,
|
|
106
|
-
shellMode: policy?.shell?.mode ?? null,
|
|
107
149
|
};
|
|
108
150
|
}
|
|
109
151
|
|
|
@@ -188,13 +230,13 @@ function runtimeWorkspacePermissionPolicy(
|
|
|
188
230
|
runtimeEnv: EnvLike,
|
|
189
231
|
cwd: string
|
|
190
232
|
): PermissionPolicy | null {
|
|
233
|
+
void runtimeEnv;
|
|
191
234
|
if (!requestsLocalWorkspaceRuntime(runtimePolicy)) return null;
|
|
192
235
|
return {
|
|
193
236
|
mode: "full_access",
|
|
194
237
|
allowFilesystemRead: true,
|
|
195
238
|
allowFilesystemWrite: true,
|
|
196
239
|
allowShell:
|
|
197
|
-
runtimeEnv.NOLO_LOCAL_SHELL_MODE === "worktree" &&
|
|
198
240
|
Array.isArray(runtimePolicy?.runtimeTools) &&
|
|
199
241
|
runtimePolicy.runtimeTools.includes("execShell"),
|
|
200
242
|
writableRoots: [cwd],
|
|
@@ -332,8 +374,6 @@ async function defaultRunConnectorLocalRuntimeAgent(args: {
|
|
|
332
374
|
requestId: args.parsed.requestId,
|
|
333
375
|
agentKey,
|
|
334
376
|
cwd: args.cwd,
|
|
335
|
-
runtimeEnvShellModeBeforePolicy: args.runtimeEnv.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
336
|
-
runtimeEnvShellModeAfterPolicy: env.NOLO_LOCAL_SHELL_MODE ?? null,
|
|
337
377
|
...summarizeRuntimePolicyForLogs(policy),
|
|
338
378
|
});
|
|
339
379
|
const adapter = createCliLocalRuntimeAdapter({
|
|
@@ -454,7 +494,9 @@ export async function handleConnectorRunMessage(
|
|
|
454
494
|
try {
|
|
455
495
|
const machinePermissionPolicy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
456
496
|
const runtimePolicy = runtimePolicyFromConnectorPayload(parsed);
|
|
457
|
-
const
|
|
497
|
+
const extractedInput = extractMultimodalUserInput(parsed.payload?.userInput);
|
|
498
|
+
const userInput = extractedInput.text;
|
|
499
|
+
const connectorImageUrls = extractedInput.imageUrls;
|
|
458
500
|
const timeout = normalizeConnectorRunTimeoutMs(parsed.payload?.timeoutMs);
|
|
459
501
|
let cwd = resolveConnectorRunCwd({ env: runtimeEnv, policy: machinePermissionPolicy });
|
|
460
502
|
let runContent = "";
|
|
@@ -553,6 +595,9 @@ export async function handleConnectorRunMessage(
|
|
|
553
595
|
effectiveRuntimeEnv.AUTH_TOKEN ||
|
|
554
596
|
effectiveRuntimeEnv.AUTH,
|
|
555
597
|
},
|
|
598
|
+
...(connectorImageUrls.length > 0
|
|
599
|
+
? { imageInputs: connectorImageUrls.map((url) => ({ source: url })) }
|
|
600
|
+
: {}),
|
|
556
601
|
}).finally(() => clearInterval(heartbeat));
|
|
557
602
|
logConnectorRun("agent.run.cli.result", {
|
|
558
603
|
requestId: parsed.requestId,
|