nolo-cli 0.1.38 → 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/agentRecordConfig.ts +0 -14
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -1
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +39 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +1 -17
- package/agent-runtime/localWorkspaceTools.ts +51 -110
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +3 -14
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +2 -1
- package/agentAliases.ts +8 -1
- package/agentRunCommand.ts +65 -95
- package/agentRuntimeLocal.ts +1 -7
- package/ai/agent/cliExecutor.ts +249 -34
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +8 -1
- package/cli/agentRunCommand.ts +65 -95
- package/cli/agentRuntimeLocal.ts +1 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +94 -93
- package/cli/client/localRuntimeAdapter.ts +250 -69
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +76 -39
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/cli/scriptCommandEntries.ts +0 -2
- package/cli/tui/readlineWorkspace.ts +69 -0
- package/cli/tui/session.ts +176 -1
- package/cliEnvHelpers.ts +11 -7
- package/client/agentConfigResolver.test.ts +4 -5
- package/client/agentRun.test.ts +307 -57
- package/client/agentRun.ts +94 -93
- package/client/localRuntimeAdapter.test.ts +332 -53
- package/client/localRuntimeAdapter.ts +250 -69
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/dialogCommands.ts +572 -0
- package/dialogInternalCommandEntries.ts +4 -0
- package/machineWsRunDispatch.ts +76 -39
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +11 -9
- package/scriptCommandEntries.ts +0 -2
- package/tui/readlineWorkspace.ts +69 -0
- package/tui/session.ts +176 -1
- package/agent-runtime/taskWorkspace.ts +0 -193
- package/agent-runtime/workspaceSession.ts +0 -76
- package/cli/client/taskWorktree.ts +0 -8
- package/cli/client/workspaceSession.ts +0 -11
- package/client/taskWorktree.ts +0 -8
- package/client/workspaceSession.test.ts +0 -57
- package/client/workspaceSession.ts +0 -11
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
} from "../agentRuntimeLocal";
|
|
6
6
|
import { request as httpRequest } from "node:http";
|
|
7
7
|
import { request as httpsRequest } from "node:https";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
8
10
|
import {
|
|
9
11
|
buildLocalWorkspacePolicyToolNames,
|
|
10
12
|
buildLocalWorkspaceToolset,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
resolveCurrentRunRuntimeToolPolicy,
|
|
20
22
|
resolveLocalWorkspaceExecutorOptionsFromPolicy,
|
|
21
23
|
resolveRequestedRuntimeToolNames,
|
|
24
|
+
resolveRuntimeToolSurfaceForAgent,
|
|
22
25
|
shouldUsePlatformChatProvider,
|
|
23
26
|
} from "../agentRuntimeLocal";
|
|
24
27
|
import type { AgentRuntimeChatMessage } from "../agent-runtime";
|
|
@@ -40,19 +43,41 @@ import {
|
|
|
40
43
|
} from "./hybridRecordStore";
|
|
41
44
|
import { executeLocalToolWithPolicy } from "./localToolPolicy";
|
|
42
45
|
import { taskRunFunctionSchema } from "../ai/tools/agent/taskRunTool";
|
|
43
|
-
import { prepareTaskWorktree } from "./taskWorktree";
|
|
44
46
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
MIMO_MONTH_AGENT_KEY,
|
|
48
|
+
NOLO_DEFAULT_AGENT_ID,
|
|
49
|
+
NOLO_DEFAULT_AGENT_KEY,
|
|
50
|
+
} from "../agentAliases";
|
|
51
|
+
import {
|
|
52
|
+
buildNoloWorkspaceCliToolExecutors,
|
|
53
|
+
buildNoloWorkspaceOpenAiTools,
|
|
54
|
+
filterNoloWorkspaceToolNames,
|
|
55
|
+
parseNoloWorkspaceToolArguments,
|
|
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";
|
|
50
59
|
|
|
51
60
|
type EnvLike = Record<string, string | undefined>;
|
|
52
61
|
type FetchInput = Parameters<typeof fetch>[0];
|
|
53
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>;
|
|
54
75
|
const TRANSIENT_FETCH_MAX_ATTEMPTS = 8;
|
|
55
76
|
const TRANSIENT_FETCH_RETRY_BASE_DELAY_MS = 250;
|
|
77
|
+
export const BUILTIN_NOLO_AGENT_ID = NOLO_DEFAULT_AGENT_ID;
|
|
78
|
+
export const BUILTIN_NOLO_AGENT_KEY = NOLO_DEFAULT_AGENT_KEY;
|
|
79
|
+
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
80
|
+
const CLI_ENTRYPOINT = join(CLI_DIR, "..", "index.ts");
|
|
56
81
|
|
|
57
82
|
export type CliLocalRuntimeDb = CliKvDb;
|
|
58
83
|
|
|
@@ -64,10 +89,9 @@ export type CliLocalRuntimeAdapterDeps = {
|
|
|
64
89
|
createId?: () => string;
|
|
65
90
|
fetchImpl?: typeof fetch;
|
|
66
91
|
cwd?: string;
|
|
67
|
-
useCwdAsTaskWorkspaceBase?: boolean;
|
|
68
92
|
output?: { write(chunk: string): unknown };
|
|
69
|
-
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
70
93
|
localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
|
|
94
|
+
executeCli?: LocalCliExecutor;
|
|
71
95
|
sleep?: (ms: number) => Promise<void>;
|
|
72
96
|
loopbackRequest?: (input: FetchInput, init?: FetchInit) => Promise<Response>;
|
|
73
97
|
};
|
|
@@ -84,10 +108,6 @@ function resolveLocalUserId(env: EnvLike) {
|
|
|
84
108
|
return env.NOLO_LOCAL_USER_ID || env.NOLO_USER_ID || "local";
|
|
85
109
|
}
|
|
86
110
|
|
|
87
|
-
function shellModeEnabled(env: EnvLike) {
|
|
88
|
-
return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
|
|
89
|
-
}
|
|
90
|
-
|
|
91
111
|
function parseLocalToolBudgets(env: EnvLike) {
|
|
92
112
|
const raw = env.NOLO_LOCAL_TOOL_BUDGETS?.trim();
|
|
93
113
|
if (!raw) return {};
|
|
@@ -242,36 +262,128 @@ function parseJsonObject(raw: string) {
|
|
|
242
262
|
}
|
|
243
263
|
}
|
|
244
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
|
+
|
|
245
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
|
+
}
|
|
246
338
|
console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
|
|
247
339
|
}
|
|
248
340
|
|
|
249
|
-
function
|
|
341
|
+
function summarizeOpenAiToolNames(tools: Array<Record<string, unknown>>) {
|
|
342
|
+
return tools
|
|
343
|
+
.map((tool) => {
|
|
344
|
+
const fn = tool.function;
|
|
345
|
+
return fn && typeof fn === "object" && "name" in fn && typeof fn.name === "string"
|
|
346
|
+
? fn.name
|
|
347
|
+
: null;
|
|
348
|
+
})
|
|
349
|
+
.filter((name): name is string => Boolean(name));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function shouldExposeLocalPlatformTools(agentKey?: string) {
|
|
353
|
+
return agentKey !== MIMO_MONTH_AGENT_KEY;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function buildOpenAiTools(args: { agentKey?: string; toolNames?: string[]; env: EnvLike }) {
|
|
250
357
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
358
|
+
const exposePlatformTools = shouldExposeLocalPlatformTools(args.agentKey);
|
|
251
359
|
return [
|
|
252
360
|
...buildLocalWorkspaceOpenAiTools({
|
|
253
361
|
toolNames: toolset.toolNames,
|
|
254
362
|
exposeShellTools: toolset.exposeShellTools,
|
|
255
363
|
}),
|
|
256
|
-
...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
|
|
364
|
+
...(exposePlatformTools ? buildServerPlatformOpenAiTools({ toolNames: args.toolNames }) : []),
|
|
365
|
+
...(exposePlatformTools ? buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }) : []),
|
|
257
366
|
];
|
|
258
367
|
}
|
|
259
368
|
|
|
260
369
|
function buildLocalWorkspaceToolsetForEnv(args: { toolNames?: string[]; env: EnvLike }) {
|
|
370
|
+
void args.env;
|
|
261
371
|
const toolset = buildLocalWorkspaceToolset({
|
|
262
372
|
declaredToolNames: args.toolNames,
|
|
263
|
-
exposeShellTools:
|
|
373
|
+
exposeShellTools: true,
|
|
264
374
|
});
|
|
265
375
|
return toolset;
|
|
266
376
|
}
|
|
267
377
|
|
|
268
378
|
function buildLocalPolicyToolNames(args: { toolNames?: string[]; env: EnvLike }) {
|
|
379
|
+
void args.env;
|
|
269
380
|
return [
|
|
270
381
|
...buildLocalWorkspacePolicyToolNames({
|
|
271
382
|
declaredToolNames: args.toolNames,
|
|
272
|
-
exposeShellTools:
|
|
383
|
+
exposeShellTools: true,
|
|
273
384
|
}),
|
|
274
385
|
...((args.toolNames ?? []).includes("taskRun") ? ["taskRun"] : []),
|
|
386
|
+
...filterNoloWorkspaceToolNames(args.toolNames),
|
|
275
387
|
];
|
|
276
388
|
}
|
|
277
389
|
|
|
@@ -287,13 +399,60 @@ function buildServerPlatformOpenAiTools(args: { toolNames?: string[] }) {
|
|
|
287
399
|
];
|
|
288
400
|
}
|
|
289
401
|
|
|
290
|
-
function
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
402
|
+
function buildCliWorkspaceToolExecutors(args: {
|
|
403
|
+
env: EnvLike;
|
|
404
|
+
cliEntrypoint?: string;
|
|
405
|
+
}) {
|
|
406
|
+
return buildNoloWorkspaceCliToolExecutors({
|
|
407
|
+
cliEntrypoint: args.cliEntrypoint ?? CLI_ENTRYPOINT,
|
|
408
|
+
env: args.env,
|
|
409
|
+
metadataKind: "cliWorkspaceTool",
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function isBuiltinNoloAgentRef(ref: unknown) {
|
|
414
|
+
if (typeof ref !== "string") return false;
|
|
415
|
+
const normalized = ref.trim();
|
|
416
|
+
return (
|
|
417
|
+
normalized === BUILTIN_NOLO_AGENT_KEY ||
|
|
418
|
+
normalized === BUILTIN_NOLO_AGENT_ID ||
|
|
419
|
+
normalized.endsWith(`-${BUILTIN_NOLO_AGENT_ID}`)
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isBuiltinNoloAgentConfig(agentConfig: AgentRuntimeAgentConfig | null | undefined) {
|
|
424
|
+
const key = agentConfig?.key || agentConfig?.rawRecord?.dbKey || agentConfig?.rawRecord?.agentKey;
|
|
425
|
+
const id = agentConfig?.rawRecord?.id;
|
|
426
|
+
return isBuiltinNoloAgentRef(key) || isBuiltinNoloAgentRef(id);
|
|
427
|
+
}
|
|
428
|
+
|
|
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
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
...agentConfig,
|
|
452
|
+
toolNames: toolSurface.finalToolNames,
|
|
453
|
+
toolSurface,
|
|
454
|
+
prompt: agentConfig.prompt,
|
|
455
|
+
};
|
|
297
456
|
}
|
|
298
457
|
|
|
299
458
|
function resolveRuntimeServerUrl(env: EnvLike) {
|
|
@@ -320,7 +479,7 @@ function buildServerPlatformToolExecutors(args: {
|
|
|
320
479
|
"Content-Type": "application/json",
|
|
321
480
|
Authorization: `Bearer ${authToken}`,
|
|
322
481
|
},
|
|
323
|
-
body: JSON.stringify(
|
|
482
|
+
body: JSON.stringify(parseNoloWorkspaceToolArguments(call.arguments)),
|
|
324
483
|
});
|
|
325
484
|
const text = await response.text().catch(() => "");
|
|
326
485
|
if (!response.ok) {
|
|
@@ -352,17 +511,13 @@ function buildLocalToolExecutors(args: {
|
|
|
352
511
|
env: args.env,
|
|
353
512
|
fetchImpl: args.fetchImpl,
|
|
354
513
|
}),
|
|
514
|
+
...buildCliWorkspaceToolExecutors({
|
|
515
|
+
env: args.env,
|
|
516
|
+
}),
|
|
355
517
|
...(args.localToolExecutors ?? {}),
|
|
356
518
|
};
|
|
357
519
|
}
|
|
358
520
|
|
|
359
|
-
function readLocalWorkspaceMode(agentConfig: AgentRuntimeAgentConfig | null) {
|
|
360
|
-
if (agentConfig?.localWorkspaceMode === "task-worktree") return "task-worktree";
|
|
361
|
-
const runtimeBinding = agentConfig?.runtimeBinding;
|
|
362
|
-
const value = runtimeBinding?.localWorkspaceMode ?? runtimeBinding?.workspaceMode;
|
|
363
|
-
return value === "task-worktree" ? "task-worktree" : "current";
|
|
364
|
-
}
|
|
365
|
-
|
|
366
521
|
async function resolveStore(deps: CliLocalRuntimeAdapterDeps) {
|
|
367
522
|
if (deps.store) return deps.store;
|
|
368
523
|
return createCliHybridRecordStore({
|
|
@@ -437,51 +592,28 @@ export function createCliLocalRuntimeAdapter(
|
|
|
437
592
|
const localToolBudgets = parseLocalToolBudgets(deps.env);
|
|
438
593
|
const localToolUsage = new Map<string, number>();
|
|
439
594
|
let activeAgentToolNames: string[] = [];
|
|
440
|
-
|
|
441
|
-
deps.useCwdAsTaskWorkspaceBase
|
|
442
|
-
? { defaultCwd: deps.cwd }
|
|
443
|
-
: { cwd: deps.cwd }
|
|
444
|
-
);
|
|
595
|
+
const workspaceRoot = deps.cwd ?? process.cwd();
|
|
445
596
|
let runtimeToolExecutionLimits: ReturnType<typeof resolveLocalWorkspaceExecutorOptionsFromPolicy> = {};
|
|
446
597
|
let localToolExecutors = buildLocalToolExecutors({
|
|
447
|
-
workspaceRoot
|
|
598
|
+
workspaceRoot,
|
|
448
599
|
env: deps.env,
|
|
449
600
|
fetchImpl,
|
|
450
601
|
localToolExecutors: deps.localToolExecutors,
|
|
451
602
|
...runtimeToolExecutionLimits,
|
|
452
603
|
});
|
|
453
604
|
|
|
454
|
-
async function activateAgentWorkspaceSession(args: {
|
|
455
|
-
agentRef: string;
|
|
456
|
-
agentConfig: AgentRuntimeAgentConfig | null;
|
|
457
|
-
}) {
|
|
458
|
-
const previousWorkspaceRoot = workspaceSession.workspaceRoot;
|
|
459
|
-
workspaceSession = await activateWorkspaceSession(workspaceSession, {
|
|
460
|
-
agentKey: args.agentConfig?.key ?? args.agentRef,
|
|
461
|
-
mode: readLocalWorkspaceMode(args.agentConfig),
|
|
462
|
-
env: deps.env,
|
|
463
|
-
prepareTaskWorkspace: deps.prepareTaskWorktree,
|
|
464
|
-
});
|
|
465
|
-
if (workspaceSession.workspaceRoot === previousWorkspaceRoot) return;
|
|
466
|
-
localToolExecutors = buildLocalToolExecutors({
|
|
467
|
-
workspaceRoot: workspaceSession.workspaceRoot,
|
|
468
|
-
env: deps.env,
|
|
469
|
-
fetchImpl,
|
|
470
|
-
localToolExecutors: deps.localToolExecutors,
|
|
471
|
-
...runtimeToolExecutionLimits,
|
|
472
|
-
});
|
|
473
|
-
deps.output?.write(formatWorkspaceSessionActivation(workspaceSession));
|
|
474
|
-
}
|
|
475
|
-
|
|
476
605
|
return {
|
|
477
606
|
host: "cli",
|
|
478
607
|
capabilities: ["leveldb-agent-config", "local-provider", "leveldb-persistence", "local-tools"],
|
|
479
608
|
loadAgentConfig: async (agentRef) => {
|
|
480
|
-
const agentConfig =
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
609
|
+
const agentConfig = withResolvedRuntimeToolSurface(
|
|
610
|
+
await readAgentFromStore({
|
|
611
|
+
agentRef,
|
|
612
|
+
store: await resolveStore(deps),
|
|
613
|
+
userId,
|
|
614
|
+
}),
|
|
615
|
+
deps.env
|
|
616
|
+
);
|
|
485
617
|
const requestedToolNames = agentConfig
|
|
486
618
|
? resolveRequestedRuntimeToolNames({ agentConfig })
|
|
487
619
|
: [];
|
|
@@ -492,9 +624,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
492
624
|
runtimeToolExecutionLimits = resolveLocalWorkspaceExecutorOptionsFromPolicy(
|
|
493
625
|
resolveCurrentRunRuntimeToolPolicy(agentConfig),
|
|
494
626
|
);
|
|
495
|
-
await activateAgentWorkspaceSession({ agentRef, agentConfig });
|
|
496
627
|
localToolExecutors = buildLocalToolExecutors({
|
|
497
|
-
workspaceRoot
|
|
628
|
+
workspaceRoot,
|
|
498
629
|
env: deps.env,
|
|
499
630
|
fetchImpl,
|
|
500
631
|
localToolExecutors: deps.localToolExecutors,
|
|
@@ -512,9 +643,53 @@ export function createCliLocalRuntimeAdapter(
|
|
|
512
643
|
userId,
|
|
513
644
|
now,
|
|
514
645
|
createId,
|
|
515
|
-
cwd:
|
|
646
|
+
cwd: workspaceRoot,
|
|
516
647
|
}),
|
|
517
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
|
+
|
|
518
693
|
if (shouldUsePlatformChatProvider(deps.env, agentConfig)) {
|
|
519
694
|
const providerConfig = resolvePlatformChatProviderConfig({
|
|
520
695
|
agentConfig,
|
|
@@ -541,6 +716,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
541
716
|
agentConfig,
|
|
542
717
|
});
|
|
543
718
|
const tools = buildOpenAiTools({
|
|
719
|
+
agentKey: agentConfig.key,
|
|
544
720
|
toolNames: requestedToolNames,
|
|
545
721
|
env: deps.env,
|
|
546
722
|
});
|
|
@@ -558,6 +734,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
558
734
|
model: providerConfig.model,
|
|
559
735
|
messageCount: messages.length,
|
|
560
736
|
toolCount: tools.length,
|
|
737
|
+
requestedToolNames,
|
|
738
|
+
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
561
739
|
timeoutMs: options?.timeoutMs ?? null,
|
|
562
740
|
});
|
|
563
741
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
@@ -611,6 +789,7 @@ export function createCliLocalRuntimeAdapter(
|
|
|
611
789
|
agentConfig,
|
|
612
790
|
});
|
|
613
791
|
const tools = buildOpenAiTools({
|
|
792
|
+
agentKey: agentConfig.key,
|
|
614
793
|
toolNames: requestedToolNames,
|
|
615
794
|
env: deps.env,
|
|
616
795
|
});
|
|
@@ -627,6 +806,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
627
806
|
model: providerConfig.model,
|
|
628
807
|
messageCount: messages.length,
|
|
629
808
|
toolCount: tools.length,
|
|
809
|
+
requestedToolNames,
|
|
810
|
+
openAiToolNames: summarizeOpenAiToolNames(tools),
|
|
630
811
|
timeoutMs: options?.timeoutMs ?? null,
|
|
631
812
|
});
|
|
632
813
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
@@ -672,8 +853,8 @@ export function createCliLocalRuntimeAdapter(
|
|
|
672
853
|
...result,
|
|
673
854
|
metadata: {
|
|
674
855
|
...(result.metadata ?? {}),
|
|
675
|
-
workspaceRoot
|
|
676
|
-
workspaceKind:
|
|
856
|
+
workspaceRoot,
|
|
857
|
+
workspaceKind: "current",
|
|
677
858
|
},
|
|
678
859
|
};
|
|
679
860
|
},
|
|
@@ -7,6 +7,26 @@ import { runLocalAgentTurn } from "../agent-runtime/localLoop";
|
|
|
7
7
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
8
8
|
|
|
9
9
|
describe("CLI local runtime dry run", () => {
|
|
10
|
+
const DEFAULT_PRIVATE_LOCAL_TOOL_NAMES = [
|
|
11
|
+
"listFiles",
|
|
12
|
+
"readFile",
|
|
13
|
+
"writeFile",
|
|
14
|
+
"editFile",
|
|
15
|
+
"searchFiles",
|
|
16
|
+
"execShell",
|
|
17
|
+
"listDialogs",
|
|
18
|
+
"readDialog",
|
|
19
|
+
"listAgents",
|
|
20
|
+
"readAgent",
|
|
21
|
+
"listSpaces",
|
|
22
|
+
"readSpace",
|
|
23
|
+
"readDoc",
|
|
24
|
+
"readSkillDoc",
|
|
25
|
+
"queryTableRows",
|
|
26
|
+
"cliWhoami",
|
|
27
|
+
"cliDoctor",
|
|
28
|
+
];
|
|
29
|
+
|
|
10
30
|
test("lets a declared workspace file tool write a file and save the tool trace", async () => {
|
|
11
31
|
const workspaceRoot = mkdtempSync(join(tmpdir(), "nolo-local-runtime-dry-run-"));
|
|
12
32
|
try {
|
|
@@ -18,7 +38,7 @@ describe("CLI local runtime dry run", () => {
|
|
|
18
38
|
prompt: "Use workspace file tools to edit files.",
|
|
19
39
|
model: "qwen-coder",
|
|
20
40
|
tools: [
|
|
21
|
-
{ type: "function", function: { name: "
|
|
41
|
+
{ type: "function", function: { name: "writeFile" } },
|
|
22
42
|
],
|
|
23
43
|
}],
|
|
24
44
|
]);
|
|
@@ -52,13 +72,7 @@ describe("CLI local runtime dry run", () => {
|
|
|
52
72
|
const body = JSON.parse(String(init?.body));
|
|
53
73
|
completeCount += 1;
|
|
54
74
|
if (completeCount === 1) {
|
|
55
|
-
expect(body.tools.map((tool: any) => tool.function.name)).toEqual(
|
|
56
|
-
"writeWorkspaceFile",
|
|
57
|
-
"listFiles",
|
|
58
|
-
"readFile",
|
|
59
|
-
"editFile",
|
|
60
|
-
"searchFiles",
|
|
61
|
-
]);
|
|
75
|
+
expect(body.tools.map((tool: any) => tool.function.name)).toEqual(DEFAULT_PRIVATE_LOCAL_TOOL_NAMES);
|
|
62
76
|
return Response.json({
|
|
63
77
|
model: "qwen-coder",
|
|
64
78
|
choices: [{
|
|
@@ -68,7 +82,7 @@ describe("CLI local runtime dry run", () => {
|
|
|
68
82
|
id: "call-1",
|
|
69
83
|
type: "function",
|
|
70
84
|
function: {
|
|
71
|
-
name: "
|
|
85
|
+
name: "writeFile",
|
|
72
86
|
arguments: JSON.stringify({
|
|
73
87
|
path: "src/notification.css",
|
|
74
88
|
content: ".notification { border-radius: 8px; }\n",
|
|
@@ -3,20 +3,20 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import { executeLocalToolWithPolicy, resolveLocalToolPolicy } from "./localToolPolicy";
|
|
4
4
|
|
|
5
5
|
describe("CLI local tool policy", () => {
|
|
6
|
-
test("allows execShell
|
|
6
|
+
test("allows execShell by default", () => {
|
|
7
7
|
expect(resolveLocalToolPolicy({
|
|
8
|
-
env: {
|
|
8
|
+
env: {},
|
|
9
9
|
agentToolNames: [],
|
|
10
10
|
toolName: "execShell",
|
|
11
11
|
})).toEqual({ allowed: true, toolName: "execShell" });
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
test("
|
|
14
|
+
test("allows execShell even without env allowlist", () => {
|
|
15
15
|
expect(resolveLocalToolPolicy({
|
|
16
16
|
env: { NOLO_LOCAL_ALLOWED_TOOLS: "execShell" },
|
|
17
17
|
agentToolNames: ["execShell"],
|
|
18
18
|
toolName: "execShell",
|
|
19
|
-
})).
|
|
19
|
+
})).toEqual({ allowed: true, toolName: "execShell" });
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
test("requires both env allowlist and agent declaration", () => {
|
package/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
|
];
|
package/connectorRunArtifact.ts
CHANGED
|
@@ -32,7 +32,6 @@ export function resolveConnectorRunCwd(args: {
|
|
|
32
32
|
const explicit =
|
|
33
33
|
args.env?.NOLO_CONNECTOR_WORKDIR ||
|
|
34
34
|
args.env?.NOLO_AGENT_WORKDIR ||
|
|
35
|
-
args.env?.NOLO_LOCAL_WORKTREE ||
|
|
36
35
|
"";
|
|
37
36
|
const candidate = explicit || args.policy.writableRoots[0] || args.fallbackCwd || process.cwd();
|
|
38
37
|
const cwd = normalizePath(candidate);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// 文件路径: packages/database/server/db.ts
|
|
2
|
+
|
|
3
|
+
import { ensureDbOpen } from "./ensureDbOpen";
|
|
4
|
+
import { resolveServerDbPath } from "./dbPath";
|
|
5
|
+
import type { LegacyServerDb } from "./legacyServerDb";
|
|
6
|
+
import type { AuthorityStore } from "./authorityStoreTypes";
|
|
7
|
+
import { getOrCreateServerStoreRuntime } from "./serverStoreFactory";
|
|
8
|
+
|
|
9
|
+
const DB_PATH = resolveServerDbPath();
|
|
10
|
+
|
|
11
|
+
console.log("数据库配置:");
|
|
12
|
+
console.log("- 当前工作目录:", process.cwd());
|
|
13
|
+
console.log("- 数据库路径:", DB_PATH);
|
|
14
|
+
|
|
15
|
+
export function isServerDbLockError(error: unknown): boolean {
|
|
16
|
+
const queue: unknown[] = [error];
|
|
17
|
+
const seen = new Set<unknown>();
|
|
18
|
+
|
|
19
|
+
while (queue.length > 0) {
|
|
20
|
+
const current = queue.shift();
|
|
21
|
+
if (!current || seen.has(current)) continue;
|
|
22
|
+
seen.add(current);
|
|
23
|
+
|
|
24
|
+
const text = `${(current as any)?.code ?? ""} ${(current as any)?.message ?? current}`;
|
|
25
|
+
if (/LEVEL_LOCKED|Resource temporarily unavailable|\/LOCK|LOCK:/.test(text)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ((current as any)?.cause) queue.push((current as any).cause);
|
|
30
|
+
if (Array.isArray((current as any)?.errors)) {
|
|
31
|
+
queue.push(...(current as any).errors);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
authorityStore,
|
|
40
|
+
serverDb,
|
|
41
|
+
}: { authorityStore: AuthorityStore; serverDb: LegacyServerDb } =
|
|
42
|
+
getOrCreateServerStoreRuntime(DB_PATH);
|
|
43
|
+
|
|
44
|
+
console.log("- LevelDB实际路径:", serverDb.location);
|
|
45
|
+
|
|
46
|
+
// ✅ 保留默认导出:兼容现有的 import serverDb from "./db"
|
|
47
|
+
export default serverDb;
|
|
48
|
+
|
|
49
|
+
// 具名导出:需要的时候可以引用
|
|
50
|
+
export { serverDb };
|
|
51
|
+
export function getServerAuthorityStore() {
|
|
52
|
+
return authorityStore;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 确保数据库处于 open 状态。
|
|
57
|
+
* - 已 open / opening:直接返回
|
|
58
|
+
* - 其他状态:尝试 open,一旦失败抛错
|
|
59
|
+
*/
|
|
60
|
+
export async function ensureServerDbOpen() {
|
|
61
|
+
const status = authorityStore.status;
|
|
62
|
+
if (status === "open") return;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await ensureDbOpen(authorityStore);
|
|
66
|
+
if (status !== "opening") {
|
|
67
|
+
console.log("✅ LevelDB 已打开");
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (isServerDbLockError(err)) {
|
|
71
|
+
console.error("❌ 打开 LevelDB 失败: 数据库已被其他进程占用");
|
|
72
|
+
} else {
|
|
73
|
+
console.error("❌ 打开 LevelDB 失败:", err);
|
|
74
|
+
}
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 兼容入口调用:启动时显式 open 一次
|
|
81
|
+
*/
|
|
82
|
+
export async function openServerDb() {
|
|
83
|
+
return ensureServerDbOpen();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 优雅关机时关闭 DB
|
|
88
|
+
*/
|
|
89
|
+
export async function closeServerDb() {
|
|
90
|
+
const status = authorityStore.status;
|
|
91
|
+
if (status !== "open") return;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
await authorityStore.close();
|
|
95
|
+
console.log("✅ LevelDB 已关闭");
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error("❌ 关闭 LevelDB 失败:", err);
|
|
98
|
+
}
|
|
99
|
+
}
|