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
|
@@ -40,15 +40,17 @@ function readFlagValue(args: string[], flag: string) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function parseUserIdFromToken(token: string) {
|
|
43
|
-
const payload = token.split(".")[
|
|
44
|
-
if (!payload) return
|
|
43
|
+
const payload = token.split(".")[1];
|
|
44
|
+
if (!payload) return undefined;
|
|
45
45
|
try {
|
|
46
46
|
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
47
47
|
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
48
48
|
const parsed = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
49
|
-
return typeof parsed?.userId === "string"
|
|
49
|
+
return typeof parsed?.userId === "string" && parsed.userId.trim()
|
|
50
|
+
? parsed.userId.trim()
|
|
51
|
+
: undefined;
|
|
50
52
|
} catch {
|
|
51
|
-
return
|
|
53
|
+
return undefined;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -15,7 +15,7 @@ describe("agent config resolver", () => {
|
|
|
15
15
|
cliProvider: "codex",
|
|
16
16
|
customProviderUrl: "https://provider.example/v1",
|
|
17
17
|
toolNames: ["legacyTool", "readFile"],
|
|
18
|
-
tools: ["readFile", "
|
|
18
|
+
tools: ["readFile", "editFile"],
|
|
19
19
|
runtimeBinding: { machineId: "machine-1" },
|
|
20
20
|
delegation: { target: "local" },
|
|
21
21
|
temperature: 0.2,
|
|
@@ -33,7 +33,7 @@ describe("agent config resolver", () => {
|
|
|
33
33
|
apiSource: "platform",
|
|
34
34
|
cliProvider: "codex",
|
|
35
35
|
customProviderUrl: "https://provider.example/v1",
|
|
36
|
-
toolNames: ["legacyTool", "readFile", "
|
|
36
|
+
toolNames: ["legacyTool", "readFile", "editFile"],
|
|
37
37
|
runtimeBinding: { machineId: "machine-1" },
|
|
38
38
|
delegation: { target: "local" },
|
|
39
39
|
temperature: 0.2,
|
package/client/agentRun.test.ts
CHANGED
|
@@ -35,11 +35,11 @@ describe("cli agent run client", () => {
|
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
test("identifies server platform tools that local runtime cannot provide", () => {
|
|
38
|
-
expect(findServerPlatformTools(["
|
|
38
|
+
expect(findServerPlatformTools(["readFile", "queryTableRows", "updateTableRow"])).toEqual([
|
|
39
39
|
"queryTableRows",
|
|
40
40
|
"updateTableRow",
|
|
41
41
|
]);
|
|
42
|
-
expect(findServerPlatformTools(["
|
|
42
|
+
expect(findServerPlatformTools(["readFile", "execShell"])).toEqual([]);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
test("calls the Nolo HTTP API directly when AUTH_TOKEN is present", async () => {
|
|
@@ -239,7 +239,6 @@ describe("cli agent run client", () => {
|
|
|
239
239
|
parentDialogId: "dialog-2",
|
|
240
240
|
background: true,
|
|
241
241
|
noStream: true,
|
|
242
|
-
maxToolRounds: 12,
|
|
243
242
|
timeoutMs: 600000,
|
|
244
243
|
scriptDir: "C:/missing/scripts",
|
|
245
244
|
env: { AUTH_TOKEN: "token-123" },
|
|
@@ -259,7 +258,6 @@ describe("cli agent run client", () => {
|
|
|
259
258
|
inheritedFromDialogKey: "dialog-user-1-dialog-2",
|
|
260
259
|
parentDialogId: "dialog-2",
|
|
261
260
|
background: true,
|
|
262
|
-
maxRounds: 12,
|
|
263
261
|
timeoutMs: 600000,
|
|
264
262
|
stream: false,
|
|
265
263
|
});
|
|
@@ -345,72 +343,75 @@ describe("cli agent run client", () => {
|
|
|
345
343
|
expect(output.text()).toContain("frontend > local:polish notifications");
|
|
346
344
|
});
|
|
347
345
|
|
|
348
|
-
test("
|
|
346
|
+
test("continues forced local tool loops until the provider returns final text", async () => {
|
|
349
347
|
const output = new CaptureOutput();
|
|
350
348
|
let completeCalls = 0;
|
|
349
|
+
let toolCalls = 0;
|
|
350
|
+
|
|
351
351
|
const result = await runAgentTurn({
|
|
352
352
|
agentName: "frontend",
|
|
353
353
|
agentKey: "frontend-local",
|
|
354
354
|
serverUrl: "https://nolo.chat",
|
|
355
|
-
message: "
|
|
355
|
+
message: "make screenshots",
|
|
356
356
|
scriptDir: "C:/missing/scripts",
|
|
357
|
-
env: { AUTH_TOKEN: "token-123"
|
|
357
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
358
358
|
output,
|
|
359
359
|
runtimeMode: "local",
|
|
360
|
-
maxToolRounds: 0,
|
|
361
360
|
localRuntimeAdapter: {
|
|
362
361
|
host: "cli",
|
|
363
|
-
capabilities: ["local-provider", "local-persistence"],
|
|
362
|
+
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
364
363
|
loadAgentConfig: async (agentRef) => ({
|
|
365
364
|
key: agentRef,
|
|
366
365
|
name: "Frontend",
|
|
367
366
|
prompt: "Fix UI",
|
|
368
367
|
model: "fake-local",
|
|
368
|
+
toolNames: ["readFile"],
|
|
369
369
|
}),
|
|
370
370
|
loadDialogHistory: async () => [],
|
|
371
|
-
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
371
|
+
saveTurn: async () => ({ dialogId: "dialog-local-retry" }),
|
|
372
372
|
resolveProvider: async () => ({
|
|
373
373
|
model: "fake-local",
|
|
374
374
|
complete: async () => {
|
|
375
375
|
completeCalls += 1;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
376
|
+
if (completeCalls <= 3) {
|
|
377
|
+
return {
|
|
378
|
+
content: "",
|
|
379
|
+
model: "fake-local",
|
|
380
|
+
tool_calls: [{
|
|
381
|
+
id: `call-${completeCalls}`,
|
|
382
|
+
type: "function",
|
|
383
|
+
function: { name: "readFile", arguments: "{}" },
|
|
384
|
+
}],
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return { content: "done after retry", model: "fake-local" };
|
|
385
388
|
},
|
|
386
389
|
}),
|
|
387
|
-
executeTool: async () =>
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
390
|
+
executeTool: async () => {
|
|
391
|
+
toolCalls += 1;
|
|
392
|
+
return { content: "ok" };
|
|
393
|
+
},
|
|
391
394
|
},
|
|
392
395
|
});
|
|
393
396
|
|
|
394
|
-
expect(result).
|
|
395
|
-
expect(
|
|
396
|
-
expect(output.text()).toContain("
|
|
397
|
+
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-local-retry" });
|
|
398
|
+
expect(toolCalls).toBe(3);
|
|
399
|
+
expect(output.text()).toContain("frontend > done after retry");
|
|
397
400
|
});
|
|
398
401
|
|
|
399
|
-
test("
|
|
402
|
+
test("prints compact local tool trace by default", async () => {
|
|
400
403
|
const output = new CaptureOutput();
|
|
401
404
|
let completeCalls = 0;
|
|
402
|
-
let toolCalls = 0;
|
|
403
405
|
|
|
404
406
|
const result = await runAgentTurn({
|
|
405
407
|
agentName: "frontend",
|
|
406
408
|
agentKey: "frontend-local",
|
|
407
409
|
serverUrl: "https://nolo.chat",
|
|
408
|
-
message: "
|
|
410
|
+
message: "inspect repo",
|
|
409
411
|
scriptDir: "C:/missing/scripts",
|
|
410
412
|
env: { AUTH_TOKEN: "token-123" },
|
|
411
413
|
output,
|
|
412
414
|
runtimeMode: "local",
|
|
413
|
-
maxToolRounds: 1,
|
|
414
415
|
localRuntimeAdapter: {
|
|
415
416
|
host: "cli",
|
|
416
417
|
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
@@ -419,42 +420,39 @@ describe("cli agent run client", () => {
|
|
|
419
420
|
name: "Frontend",
|
|
420
421
|
prompt: "Fix UI",
|
|
421
422
|
model: "fake-local",
|
|
422
|
-
toolNames: ["
|
|
423
|
+
toolNames: ["readFile"],
|
|
423
424
|
}),
|
|
424
425
|
loadDialogHistory: async () => [],
|
|
425
|
-
saveTurn: async () => ({ dialogId: "dialog-local
|
|
426
|
+
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
426
427
|
resolveProvider: async () => ({
|
|
427
428
|
model: "fake-local",
|
|
428
429
|
complete: async () => {
|
|
429
430
|
completeCalls += 1;
|
|
430
|
-
if (completeCalls
|
|
431
|
+
if (completeCalls === 1) {
|
|
431
432
|
return {
|
|
432
433
|
content: "",
|
|
433
434
|
model: "fake-local",
|
|
434
435
|
tool_calls: [{
|
|
435
|
-
id:
|
|
436
|
+
id: "call-read",
|
|
436
437
|
type: "function",
|
|
437
|
-
function: { name: "
|
|
438
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
438
439
|
}],
|
|
439
440
|
};
|
|
440
441
|
}
|
|
441
|
-
return { content: "done
|
|
442
|
+
return { content: "done", model: "fake-local" };
|
|
442
443
|
},
|
|
443
444
|
}),
|
|
444
|
-
executeTool: async () => {
|
|
445
|
-
toolCalls += 1;
|
|
446
|
-
return { content: "ok" };
|
|
447
|
-
},
|
|
445
|
+
executeTool: async () => ({ content: "clean" }),
|
|
448
446
|
},
|
|
449
447
|
});
|
|
450
448
|
|
|
451
|
-
expect(result).
|
|
452
|
-
expect(
|
|
453
|
-
expect(output.text()).toContain("
|
|
454
|
-
expect(output.text()).toContain("
|
|
449
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
450
|
+
expect(output.text()).toContain("[nolo:tool] #1 -> readFile README.md");
|
|
451
|
+
expect(output.text()).toContain("[nolo:tool] #1 <- readFile");
|
|
452
|
+
expect(output.text()).toContain("1 line");
|
|
455
453
|
});
|
|
456
454
|
|
|
457
|
-
test("
|
|
455
|
+
test("does not print local tool trace when disabled by env", async () => {
|
|
458
456
|
const output = new CaptureOutput();
|
|
459
457
|
let completeCalls = 0;
|
|
460
458
|
|
|
@@ -464,10 +462,9 @@ describe("cli agent run client", () => {
|
|
|
464
462
|
serverUrl: "https://nolo.chat",
|
|
465
463
|
message: "inspect repo",
|
|
466
464
|
scriptDir: "C:/missing/scripts",
|
|
467
|
-
env: { AUTH_TOKEN: "token-123" },
|
|
465
|
+
env: { AUTH_TOKEN: "token-123", NOLO_TRACE_TOOLS: "0" },
|
|
468
466
|
output,
|
|
469
467
|
runtimeMode: "local",
|
|
470
|
-
traceTools: true,
|
|
471
468
|
localRuntimeAdapter: {
|
|
472
469
|
host: "cli",
|
|
473
470
|
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
@@ -476,10 +473,10 @@ describe("cli agent run client", () => {
|
|
|
476
473
|
name: "Frontend",
|
|
477
474
|
prompt: "Fix UI",
|
|
478
475
|
model: "fake-local",
|
|
479
|
-
toolNames: ["
|
|
476
|
+
toolNames: ["readFile"],
|
|
480
477
|
}),
|
|
481
478
|
loadDialogHistory: async () => [],
|
|
482
|
-
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
479
|
+
saveTurn: async () => ({ dialogId: "dialog-local-no-trace" }),
|
|
483
480
|
resolveProvider: async () => ({
|
|
484
481
|
model: "fake-local",
|
|
485
482
|
complete: async () => {
|
|
@@ -491,7 +488,7 @@ describe("cli agent run client", () => {
|
|
|
491
488
|
tool_calls: [{
|
|
492
489
|
id: "call-read",
|
|
493
490
|
type: "function",
|
|
494
|
-
function: { name: "
|
|
491
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
495
492
|
}],
|
|
496
493
|
};
|
|
497
494
|
}
|
|
@@ -502,9 +499,79 @@ describe("cli agent run client", () => {
|
|
|
502
499
|
},
|
|
503
500
|
});
|
|
504
501
|
|
|
505
|
-
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
506
|
-
expect(output.text()).toContain("[nolo:tool]
|
|
507
|
-
|
|
502
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local-no-trace" });
|
|
503
|
+
expect(output.text()).not.toContain("[nolo:tool]");
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("prints local tool events as jsonl when requested", async () => {
|
|
507
|
+
const output = new CaptureOutput();
|
|
508
|
+
let completeCalls = 0;
|
|
509
|
+
|
|
510
|
+
const result = await runAgentTurn({
|
|
511
|
+
agentName: "frontend",
|
|
512
|
+
agentKey: "frontend-local",
|
|
513
|
+
serverUrl: "https://nolo.chat",
|
|
514
|
+
message: "inspect repo",
|
|
515
|
+
scriptDir: "C:/missing/scripts",
|
|
516
|
+
env: { AUTH_TOKEN: "token-123" },
|
|
517
|
+
output,
|
|
518
|
+
runtimeMode: "local",
|
|
519
|
+
eventsMode: "jsonl",
|
|
520
|
+
localRuntimeAdapter: {
|
|
521
|
+
host: "cli",
|
|
522
|
+
capabilities: ["local-provider", "local-persistence", "local-tools"],
|
|
523
|
+
loadAgentConfig: async (agentRef) => ({
|
|
524
|
+
key: agentRef,
|
|
525
|
+
name: "Frontend",
|
|
526
|
+
prompt: "Fix UI",
|
|
527
|
+
model: "fake-local",
|
|
528
|
+
toolNames: ["execShell"],
|
|
529
|
+
}),
|
|
530
|
+
loadDialogHistory: async () => [],
|
|
531
|
+
saveTurn: async () => ({ dialogId: "dialog-local-jsonl" }),
|
|
532
|
+
resolveProvider: async () => ({
|
|
533
|
+
model: "fake-local",
|
|
534
|
+
complete: async () => {
|
|
535
|
+
completeCalls += 1;
|
|
536
|
+
if (completeCalls === 1) {
|
|
537
|
+
return {
|
|
538
|
+
content: "",
|
|
539
|
+
model: "fake-local",
|
|
540
|
+
tool_calls: [{
|
|
541
|
+
id: "call-shell",
|
|
542
|
+
type: "function",
|
|
543
|
+
function: { name: "execShell", arguments: JSON.stringify({ command: "git status --short" }) },
|
|
544
|
+
}],
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return { content: "done", model: "fake-local" };
|
|
548
|
+
},
|
|
549
|
+
}),
|
|
550
|
+
executeTool: async () => ({ content: "stdout:\nclean\n\nexitCode: 0", metadata: { exitCode: 0 } }),
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local-jsonl" });
|
|
555
|
+
const events = output.text()
|
|
556
|
+
.split(/\r?\n/)
|
|
557
|
+
.filter((line) => line.startsWith("{"))
|
|
558
|
+
.map((line) => JSON.parse(line))
|
|
559
|
+
.filter((event) => typeof event.type === "string" && event.type.startsWith("tool-"));
|
|
560
|
+
expect(events).toHaveLength(2);
|
|
561
|
+
expect(events[0]).toMatchObject({
|
|
562
|
+
schemaVersion: 1,
|
|
563
|
+
type: "tool-call",
|
|
564
|
+
round: 1,
|
|
565
|
+
tool: "execShell",
|
|
566
|
+
argsPreview: "git status --short",
|
|
567
|
+
});
|
|
568
|
+
expect(events[1]).toMatchObject({
|
|
569
|
+
schemaVersion: 1,
|
|
570
|
+
type: "tool-result",
|
|
571
|
+
round: 1,
|
|
572
|
+
tool: "execShell",
|
|
573
|
+
summary: expect.stringContaining("exit=0"),
|
|
574
|
+
});
|
|
508
575
|
});
|
|
509
576
|
|
|
510
577
|
test("passes local tool evidence to task-run writeback", async () => {
|
|
@@ -536,7 +603,7 @@ describe("cli agent run client", () => {
|
|
|
536
603
|
name: "Frontend",
|
|
537
604
|
prompt: "Fix UI",
|
|
538
605
|
model: "fake-local",
|
|
539
|
-
toolNames: ["
|
|
606
|
+
toolNames: ["readFile"],
|
|
540
607
|
}),
|
|
541
608
|
loadDialogHistory: async () => [],
|
|
542
609
|
saveTurn: async () => ({ dialogId: "dialog-local" }),
|
|
@@ -551,7 +618,7 @@ describe("cli agent run client", () => {
|
|
|
551
618
|
tool_calls: [{
|
|
552
619
|
id: "call-read",
|
|
553
620
|
type: "function",
|
|
554
|
-
function: { name: "
|
|
621
|
+
function: { name: "readFile", arguments: JSON.stringify({ path: "README.md" }) },
|
|
555
622
|
}],
|
|
556
623
|
};
|
|
557
624
|
}
|
|
@@ -565,7 +632,7 @@ describe("cli agent run client", () => {
|
|
|
565
632
|
expect(result).toMatchObject({ exitCode: 0, dialogId: "dialog-local" });
|
|
566
633
|
expect(writebacks).toHaveLength(1);
|
|
567
634
|
expect(writebacks[0].toolEvidence).toEqual({
|
|
568
|
-
toolsUsed: ["
|
|
635
|
+
toolsUsed: ["readFile"],
|
|
569
636
|
summary: {
|
|
570
637
|
callCount: 1,
|
|
571
638
|
resultCount: 1,
|
|
@@ -833,7 +900,6 @@ describe("cli agent run client", () => {
|
|
|
833
900
|
scriptDir: "C:/missing/scripts",
|
|
834
901
|
env: {
|
|
835
902
|
AUTH_TOKEN: "token-123",
|
|
836
|
-
NOLO_LOCAL_SHELL_MODE: "worktree",
|
|
837
903
|
},
|
|
838
904
|
output,
|
|
839
905
|
runtimeMode: "auto",
|
package/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) {
|