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
@@ -9,7 +9,7 @@ import {
9
9
  } from "../agentAliases";
10
10
  import type { LocalAgentToolEvent } from "../agent-runtime/localLoop";
11
11
  import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
12
- import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
12
+ import { createCliLocalRuntimeAdapter, isBuiltinNoloAgentRef } from "./localRuntimeAdapter";
13
13
  import { createStreamingTextWriter } from "./streamingOutput";
14
14
  import { prependTaskRunPrompt, type TaskRunPromptContext } from "./taskRunPrompt";
15
15
 
@@ -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;
@@ -180,10 +179,27 @@ function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
180
179
  const normalizedKey = normalizeAgentRef(options.agentKey);
181
180
  return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
182
181
  }
183
-
184
- function resolveAuthToken(env: EnvLike) {
185
- return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
186
- }
182
+
183
+ function isMachineBoundLocalhostCustomProvider(agentConfig: any) {
184
+ const machineId =
185
+ agentConfig?.runtimeBinding && typeof agentConfig.runtimeBinding === "object"
186
+ ? String(agentConfig.runtimeBinding.machineId ?? "").trim()
187
+ : "";
188
+ const providerUrl = typeof agentConfig?.customProviderUrl === "string"
189
+ ? agentConfig.customProviderUrl.trim()
190
+ : "";
191
+ if (!machineId || !providerUrl) return false;
192
+ try {
193
+ const hostname = new URL(providerUrl).hostname.toLowerCase();
194
+ return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1";
195
+ } catch {
196
+ return false;
197
+ }
198
+ }
199
+
200
+ function resolveAuthToken(env: EnvLike) {
201
+ return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
202
+ }
187
203
 
188
204
  function shouldShowUsage(env: EnvLike) {
189
205
  return env.NOLO_DEBUG === "1" || env.NOLO_SHOW_USAGE === "1";
@@ -214,6 +230,10 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
214
230
  }
215
231
 
216
232
  async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
233
+ if (isBuiltinNoloAgentRef(options.agentKey)) return false;
234
+ if (options.localRuntimeCwd) {
235
+ return false;
236
+ }
217
237
  if (isKnownServerPlatformAgent(options)) {
218
238
  options.output.write(
219
239
  `[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
@@ -229,6 +249,13 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
229
249
  } catch {
230
250
  return false;
231
251
  }
252
+ if (isMachineBoundLocalhostCustomProvider(agentConfig)) {
253
+ options.output.write(
254
+ `[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a machine-bound localhost custom provider. ` +
255
+ "Use --local explicitly to force the current machine.\n"
256
+ );
257
+ return true;
258
+ }
232
259
  const serverTools = resolveServerPlatformToolNames(agentConfig);
233
260
  if (serverTools.length === 0) return false;
234
261
  options.output.write(
@@ -250,7 +277,14 @@ function buildUserInputContent(message: string, imageUrls: string[] = []) {
250
277
  }
251
278
 
252
279
  function shouldTraceLocalTools(options: RunAgentTurnOptions) {
253
- return Boolean(options.traceTools || options.env.NOLO_TRACE_TOOLS === "1");
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";
254
288
  }
255
289
 
256
290
  function shouldCollectLocalToolEvidence(options: RunAgentTurnOptions) {
@@ -279,15 +313,6 @@ function summarizeLocalToolEvidence(events: LocalAgentToolEvent[]): TaskRunToolE
279
313
  };
280
314
  }
281
315
 
282
- const DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT = 512;
283
-
284
- function parseLocalToolRoundLimitError(error: unknown) {
285
- const message = error instanceof Error ? error.message : String(error);
286
- const match = message.match(/Local agent exceeded max tool rounds:\s*(\d+)/);
287
- if (!match) return null;
288
- return Number(match[1]);
289
- }
290
-
291
316
  function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
292
317
  return Boolean(
293
318
  error &&
@@ -298,37 +323,44 @@ function isMissingLocalAgentConfigError(error: unknown, agentRef: string) {
298
323
  );
299
324
  }
300
325
 
301
- function parsePositiveInteger(value: string | undefined) {
302
- if (!value) return null;
303
- const parsed = Number(value);
304
- if (!Number.isInteger(parsed) || parsed <= 0) return null;
305
- return parsed;
306
- }
307
-
308
- function resolveLocalToolRoundRetryLimit(options: RunAgentTurnOptions) {
309
- return (
310
- parsePositiveInteger(options.env.NOLO_LOCAL_MAX_TOOL_ROUNDS_LIMIT) ??
311
- DEFAULT_LOCAL_AGENT_TOOL_ROUND_RETRY_LIMIT
312
- );
313
- }
314
-
315
- function nextLocalToolRoundLimit(current: number) {
316
- return current <= 0 ? 1 : current * 2;
317
- }
318
-
319
326
  function formatToolTraceEvent(event: LocalAgentToolEvent) {
320
327
  const round = event.round + 1;
328
+ const detail = event.argumentsPreview ? ` ${event.argumentsPreview}` : "";
321
329
  if (event.type === "tool-call") {
322
- return `[nolo:tool] round ${round} -> ${event.toolName} (${event.toolCallId})\n`;
330
+ return `[nolo:tool] #${round} -> ${event.toolName}${detail}\n`;
323
331
  }
324
332
  if (event.type === "tool-error") {
325
- return `[nolo:tool] round ${round} !! ${event.toolName}: ${event.message ?? "failed"}\n`;
333
+ const elapsed = typeof event.elapsedMs === "number" ? ` ${event.elapsedMs}ms` : "";
334
+ return `[nolo:tool] #${round} !! ${event.toolName}${elapsed}: ${event.message ?? "failed"}\n`;
326
335
  }
327
- return `[nolo:tool] round ${round} <- ${event.toolName} (${event.toolCallId})\n`;
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`;
328
353
  }
329
354
 
330
355
  function shouldAttemptAutoLocal(options: RunAgentTurnOptions) {
331
356
  if (options.localRuntimeAdapter || options.localRuntimeAdapterFactory) return true;
357
+ if (
358
+ options.env.NOLO_DISABLE_CLI_WORKSPACE_TOOLS !== "1" &&
359
+ isBuiltinNoloAgentRef(options.agentKey) &&
360
+ resolveAuthToken(options.env)
361
+ ) {
362
+ return true;
363
+ }
332
364
  return Boolean(
333
365
  options.env.NOLO_LOCAL_OPENAI_API_KEY ||
334
366
  options.env.OPENAI_API_KEY ||
@@ -594,7 +626,6 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
594
626
  : {}),
595
627
  ...(options.parentDialogId ? { parentDialogId: options.parentDialogId } : {}),
596
628
  ...(options.background ? { background: true } : {}),
597
- ...(typeof options.maxToolRounds === "number" ? { maxRounds: options.maxToolRounds } : {}),
598
629
  ...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
599
630
  stream: !options.noStream && !options.background,
600
631
  }),
@@ -632,8 +663,21 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
632
663
 
633
664
  if (!res.ok) {
634
665
  options.output.write(`[nolo] Agent request failed: HTTP ${res.status}\n`);
635
- if (data?.error || data?.message) {
636
- options.output.write(`${data.error || data.message}\n`);
666
+ const errorText = typeof data?.error === "string" ? data.error.trim() : "";
667
+ const messageText = typeof data?.message === "string" ? data.message.trim() : "";
668
+ const reasonText = typeof data?.reason === "string" ? data.reason.trim() : "";
669
+ const codeText = typeof data?.code === "string" ? data.code.trim() : "";
670
+ if (errorText || messageText) {
671
+ options.output.write(`${errorText || messageText}\n`);
672
+ if (messageText && messageText !== errorText) {
673
+ options.output.write(`${messageText}\n`);
674
+ }
675
+ if (codeText && codeText !== errorText && codeText !== messageText) {
676
+ options.output.write(`code=${codeText}\n`);
677
+ }
678
+ if (reasonText && reasonText !== errorText && reasonText !== messageText) {
679
+ options.output.write(`reason=${reasonText}\n`);
680
+ }
637
681
  }
638
682
  await recordTaskRunOutcome({
639
683
  options,
@@ -669,7 +713,7 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
669
713
  }
670
714
 
671
715
  async function runInjectedLocalAgentTurn(options: RunAgentTurnOptions) {
672
- return runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: true });
716
+ return runLocalAgentTurnForCli(options, { reportFailure: true });
673
717
  }
674
718
 
675
719
  async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
@@ -679,53 +723,6 @@ async function refreshMissingLocalAgentConfig(options: RunAgentTurnOptions) {
679
723
  return Boolean(agentConfig);
680
724
  }
681
725
 
682
- async function runLocalAgentTurnWithAutoRoundExpansion(
683
- options: RunAgentTurnOptions,
684
- settings: { reportFailure: boolean }
685
- ) {
686
- const maxRetryLimit = resolveLocalToolRoundRetryLimit(options);
687
- let currentMaxToolRounds = options.maxToolRounds;
688
- let lastResult: Awaited<ReturnType<typeof runLocalAgentTurnForCli>> | null = null;
689
-
690
- while (true) {
691
- const attemptOptions = {
692
- ...options,
693
- ...(typeof currentMaxToolRounds === "number" ? { maxToolRounds: currentMaxToolRounds } : {}),
694
- };
695
- const result = await runLocalAgentTurnForCli(attemptOptions, { reportFailure: false });
696
- lastResult = result;
697
- if (result.exitCode === 0) return result;
698
-
699
- const exhaustedRounds = parseLocalToolRoundLimitError(result.localError);
700
- if (exhaustedRounds === null) break;
701
-
702
- const nextMaxToolRounds = nextLocalToolRoundLimit(exhaustedRounds);
703
- if (nextMaxToolRounds > maxRetryLimit) break;
704
-
705
- options.output.write(
706
- `[nolo] Local agent used all ${exhaustedRounds} tool rounds; retrying with ${nextMaxToolRounds}.\n`
707
- );
708
- currentMaxToolRounds = nextMaxToolRounds;
709
- }
710
-
711
- if (settings.reportFailure && lastResult?.localError) {
712
- options.output.write(
713
- `[nolo] Local agent run failed: ${
714
- lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError)
715
- }\n`
716
- );
717
- await recordTaskRunOutcome({
718
- options,
719
- status: "failed",
720
- errorCode: "LOCAL_AGENT_FAILED",
721
- resultSummary:
722
- lastResult.localError instanceof Error ? lastResult.localError.message : String(lastResult.localError),
723
- toolEvidence: lastResult.toolEvidence,
724
- });
725
- }
726
- return lastResult ?? { exitCode: 1 };
727
- }
728
-
729
726
  async function runLocalAgentTurnForCli(
730
727
  options: RunAgentTurnOptions,
731
728
  settings: { reportFailure: boolean }
@@ -742,6 +739,7 @@ async function runLocalAgentTurnForCli(
742
739
  try {
743
740
  const traceLocalTools = shouldTraceLocalTools(options);
744
741
  const collectToolEvidence = shouldCollectLocalToolEvidence(options);
742
+ const eventMode = resolveAgentEventMode(options);
745
743
  const result = await runLocalAgentTurn({
746
744
  adapter,
747
745
  agentRef: options.agentKey,
@@ -756,7 +754,6 @@ async function runLocalAgentTurnForCli(
756
754
  parentDialogId: options.parentDialogId,
757
755
  background: options.background,
758
756
  noStream: options.noStream,
759
- maxToolRounds: options.maxToolRounds,
760
757
  ...(typeof options.timeoutMs === "number" ? { timeoutMs: options.timeoutMs } : {}),
761
758
  ...(traceLocalTools || collectToolEvidence
762
759
  ? {
@@ -764,7 +761,11 @@ async function runLocalAgentTurnForCli(
764
761
  if (collectToolEvidence) toolEvents.push(event);
765
762
  if (traceLocalTools) {
766
763
  spinner.stop();
767
- options.output.write(formatToolTraceEvent(event));
764
+ options.output.write(
765
+ eventMode === "jsonl"
766
+ ? formatToolJsonEvent(event)
767
+ : formatToolTraceEvent(event)
768
+ );
768
769
  }
769
770
  },
770
771
  }
@@ -920,7 +921,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
920
921
  if (runtimeMode === "auto" && shouldAttemptAutoLocal(options)) {
921
922
  const skipLocal = await shouldSkipAutoLocalForServerPlatformTools(options);
922
923
  if (!skipLocal) {
923
- const localResult = await runLocalAgentTurnWithAutoRoundExpansion(options, { reportFailure: false });
924
+ const localResult = await runLocalAgentTurnForCli(options, { reportFailure: false });
924
925
  if (localResult.exitCode === 0) {
925
926
  return {
926
927
  exitCode: localResult.exitCode,
@@ -934,7 +935,7 @@ export async function runAgentTurn(options: RunAgentTurnOptions) {
934
935
  try {
935
936
  const refreshed = await refreshMissingLocalAgentConfig(options);
936
937
  if (refreshed) {
937
- const retriedLocalResult = await runLocalAgentTurnWithAutoRoundExpansion(options, {
938
+ const retriedLocalResult = await runLocalAgentTurnForCli(options, {
938
939
  reportFailure: false,
939
940
  });
940
941
  if (retriedLocalResult.exitCode === 0) {