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.
Files changed (65) hide show
  1. package/README.md +4 -4
  2. package/agent-runtime/agentRecordConfig.ts +0 -14
  3. package/agent-runtime/dialogWritePlan.ts +39 -0
  4. package/agent-runtime/hostAdapter.ts +4 -1
  5. package/agent-runtime/hybridRecordStore.ts +1 -1
  6. package/agent-runtime/index.ts +39 -18
  7. package/agent-runtime/localDialogRead.ts +23 -0
  8. package/agent-runtime/localLoop.ts +87 -7
  9. package/agent-runtime/localToolPolicy.ts +1 -17
  10. package/agent-runtime/localWorkspaceTools.ts +51 -110
  11. package/agent-runtime/noloWorkspaceTools.ts +371 -0
  12. package/agent-runtime/runtimeToolPolicy.ts +3 -14
  13. package/agent-runtime/runtimeToolSurface.ts +198 -0
  14. package/agent-runtime/types.ts +2 -1
  15. package/agentAliases.ts +8 -1
  16. package/agentRunCommand.ts +65 -95
  17. package/agentRuntimeLocal.ts +1 -7
  18. package/ai/agent/cliExecutor.ts +249 -34
  19. package/chat/messages/fetchMessages.ts +84 -0
  20. package/chat/messages/types.ts +185 -0
  21. package/cli/agentAliases.ts +8 -1
  22. package/cli/agentRunCommand.ts +65 -95
  23. package/cli/agentRuntimeLocal.ts +1 -7
  24. package/cli/cliEnvHelpers.ts +11 -7
  25. package/cli/client/agentRun.ts +94 -93
  26. package/cli/client/localRuntimeAdapter.ts +250 -69
  27. package/cli/commandRegistry.ts +2 -2
  28. package/cli/connectorRunArtifact.ts +0 -1
  29. package/cli/dialogCommands.ts +572 -0
  30. package/cli/dialogInternalCommandEntries.ts +4 -0
  31. package/cli/machineWsRunDispatch.ts +76 -39
  32. package/cli/offlineMarxistsAgentCommand.ts +6 -4
  33. package/cli/scriptCommandEntries.ts +0 -2
  34. package/cli/tui/readlineWorkspace.ts +69 -0
  35. package/cli/tui/session.ts +176 -1
  36. package/cliEnvHelpers.ts +11 -7
  37. package/client/agentConfigResolver.test.ts +4 -5
  38. package/client/agentRun.test.ts +307 -57
  39. package/client/agentRun.ts +94 -93
  40. package/client/localRuntimeAdapter.test.ts +332 -53
  41. package/client/localRuntimeAdapter.ts +250 -69
  42. package/client/localRuntimeDryRun.test.ts +23 -9
  43. package/client/localToolPolicy.test.ts +4 -4
  44. package/commandRegistry.ts +2 -2
  45. package/connectorRunArtifact.ts +0 -1
  46. package/database/server/db.ts +99 -0
  47. package/database/server/ensureDbOpen.ts +12 -0
  48. package/database/server/levelAuthorityStore.ts +20 -1
  49. package/database/server/memoryAuthorityStore.ts +133 -0
  50. package/database/server/serverStoreFactory.ts +118 -0
  51. package/dialogCommands.ts +572 -0
  52. package/dialogInternalCommandEntries.ts +4 -0
  53. package/machineWsRunDispatch.ts +76 -39
  54. package/offlineMarxistsAgentCommand.ts +6 -4
  55. package/package.json +11 -9
  56. package/scriptCommandEntries.ts +0 -2
  57. package/tui/readlineWorkspace.ts +69 -0
  58. package/tui/session.ts +176 -1
  59. package/agent-runtime/taskWorkspace.ts +0 -193
  60. package/agent-runtime/workspaceSession.ts +0 -76
  61. package/cli/client/taskWorktree.ts +0 -8
  62. package/cli/client/workspaceSession.ts +0 -11
  63. package/client/taskWorktree.ts +0 -8
  64. package/client/workspaceSession.test.ts +0 -57
  65. 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
- activateWorkspaceSession,
46
- createWorkspaceSession,
47
- formatWorkspaceSessionActivation,
48
- type WorkspaceSession,
49
- } from "./workspaceSession";
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 buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
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: shellModeEnabled(args.env),
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: shellModeEnabled(args.env),
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 parseToolArguments(raw: string) {
291
- try {
292
- const parsed = JSON.parse(raw || "{}");
293
- return parsed && typeof parsed === "object" ? parsed : {};
294
- } catch {
295
- return {};
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(parseToolArguments(call.arguments)),
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
- let workspaceSession: WorkspaceSession = createWorkspaceSession(
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: workspaceSession.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 = await readAgentFromStore({
481
- agentRef,
482
- store: await resolveStore(deps),
483
- userId,
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: workspaceSession.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: workspaceSession.workspaceRoot,
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: workspaceSession.workspaceRoot,
676
- workspaceKind: workspaceSession.kind,
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: "writeWorkspaceFile" } },
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: "writeWorkspaceFile",
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 in explicit worktree shell mode", () => {
6
+ test("allows execShell by default", () => {
7
7
  expect(resolveLocalToolPolicy({
8
- env: { NOLO_LOCAL_SHELL_MODE: "worktree" },
8
+ env: {},
9
9
  agentToolNames: [],
10
10
  toolName: "execShell",
11
11
  })).toEqual({ allowed: true, toolName: "execShell" });
12
12
  });
13
13
 
14
- test("blocks execShell unless explicit shell mode is enabled", () => {
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
- })).toMatchObject({ allowed: false });
19
+ })).toEqual({ allowed: true, toolName: "execShell" });
20
20
  });
21
21
 
22
22
  test("requires both env allowlist and agent declaration", () => {
@@ -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-b2e06f801f-NOLOTASKBOARD --columns \'["title","status","owner","priority","codeStatus"]\' --no-base-fields --output items',
73
- ' nolo table update-row --table meta-b2e06f801f-NOLOTASKBOARD --row 01ROWID --changes \'{"status":"已完成"}\'',
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
  ];
@@ -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
+ }