u-foo 2.5.15 → 3.0.1
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/package.json +1 -1
- package/src/code/agent.js +350 -246
- package/src/code/commands.js +16 -0
- package/src/code/context/assembler.js +18 -13
- package/src/code/context/executionSegment.js +102 -119
- package/src/code/context/index.js +11 -1
- package/src/code/context/planGraph.js +1410 -0
- package/src/code/context/planGraphService.js +857 -0
- package/src/code/context/planMode.js +405 -0
- package/src/code/context/planProjection.js +432 -0
- package/src/code/context/promptLayers.js +21 -5
- package/src/code/context/stateCommit.js +2 -0
- package/src/code/context/toolRuntime.js +172 -0
- package/src/code/context/userInteraction.js +457 -0
- package/src/code/context/userNudge.js +116 -0
- package/src/code/dispatch.js +17 -1
- package/src/code/index.js +4 -0
- package/src/code/nativeRunner.js +589 -172
- package/src/code/protocol/controlPlane.js +93 -0
- package/src/code/protocol/faultHarness.js +90 -0
- package/src/code/protocol/index.js +20 -0
- package/src/code/protocol/loopEvents.js +102 -0
- package/src/code/protocol/materialize.js +107 -0
- package/src/code/protocol/messageFixtures.js +116 -0
- package/src/code/protocol/ownership.js +147 -0
- package/src/code/protocol/protocolValidator.js +165 -0
- package/src/code/protocol/suspension.js +173 -0
- package/src/code/protocol/toolCallLedger.js +222 -0
- package/src/code/protocol/transitions.js +97 -0
- package/src/code/providers/anthropicMessagesTransport.js +93 -0
- package/src/code/providers/index.js +7 -0
- package/src/code/providers/openaiChatTransport.js +98 -0
- package/src/code/providers/transportContract.js +46 -0
- package/src/code/repl.js +147 -18
- package/src/code/runtime/agentWakeup.js +58 -0
- package/src/code/runtime/graphOwner.js +41 -0
- package/src/code/runtime/graphYieldRouter.js +42 -0
- package/src/code/runtime/index.js +15 -0
- package/src/code/runtime/loopMailbox.js +124 -0
- package/src/code/runtime/runtimeEvents.js +39 -0
- package/src/code/runtime/taskControl.js +565 -0
- package/src/code/runtime/taskFocus.js +165 -0
- package/src/code/runtime/taskLoop.js +394 -0
- package/src/code/runtime/taskRun.js +348 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +249 -0
- package/src/code/sessionStore.js +1 -10
- package/src/code/skills/injection.js +1 -0
- package/src/code/taskDecomposer.js +32 -8
- package/src/code/taskRoute.js +73 -0
- package/src/code/tools/askUser.js +11 -0
- package/src/code/tools/planGraph.js +29 -0
- package/src/ui/format/index.js +25 -1
- package/src/ui/format/markdownRenderer.js +224 -2
- package/src/ui/ink/UcodeApp.js +268 -22
- package/src/code/context/featureFlag.js +0 -13
package/src/code/nativeRunner.js
CHANGED
|
@@ -6,7 +6,6 @@ const {
|
|
|
6
6
|
} = require("../agents/providers/credentials/kimi");
|
|
7
7
|
const { runToolCall } = require("./dispatch");
|
|
8
8
|
const { appendUsageRecord } = require("./usageStore");
|
|
9
|
-
const { isContextV2Enabled } = require("./context/featureFlag");
|
|
10
9
|
const {
|
|
11
10
|
persistToolResultToContext,
|
|
12
11
|
sanitizeModelMessages,
|
|
@@ -14,18 +13,56 @@ const {
|
|
|
14
13
|
const { systemBlocksToAnthropicPayload } = require("./context/promptLayers");
|
|
15
14
|
const { parseStructuredSideEffects } = require("./context/stateCommit");
|
|
16
15
|
const {
|
|
17
|
-
parseExecutionSegment,
|
|
18
|
-
executeExecutionSegment,
|
|
19
|
-
formatSegmentResultMessage,
|
|
20
16
|
emptyExecutionState,
|
|
21
17
|
} = require("./context/executionSegment");
|
|
18
|
+
const {
|
|
19
|
+
normalizePlanGraphCommand,
|
|
20
|
+
runPlanGraphCommand,
|
|
21
|
+
activePlanRequiresExpansion,
|
|
22
|
+
} = require("./context/planGraphService");
|
|
23
|
+
const { planModeBlocksDirectTool } = require("./context/planMode");
|
|
24
|
+
const {
|
|
25
|
+
drainUserPrompts,
|
|
26
|
+
clearUserPrompts,
|
|
27
|
+
formatUserReminderMessage,
|
|
28
|
+
ensurePendingUserPrompts,
|
|
29
|
+
} = require("./context/userNudge");
|
|
30
|
+
const {
|
|
31
|
+
runAskUserTool,
|
|
32
|
+
syncInteractionFromPlanGraph,
|
|
33
|
+
hasPendingUserInteraction,
|
|
34
|
+
getPendingUserInteraction,
|
|
35
|
+
} = require("./context/userInteraction");
|
|
36
|
+
const { checkWriteAllowed } = require("./runtime/workspaceLease");
|
|
37
|
+
const {
|
|
38
|
+
createToolCallLedger,
|
|
39
|
+
declareCalls,
|
|
40
|
+
markExecuting,
|
|
41
|
+
deferCall,
|
|
42
|
+
resolveCall,
|
|
43
|
+
snapshotLedger,
|
|
44
|
+
runProviderTurnGate,
|
|
45
|
+
withFaultPoint,
|
|
46
|
+
checkFaultPoint,
|
|
47
|
+
materializeResolvedToolResults,
|
|
48
|
+
materializeAnswerToolResult,
|
|
49
|
+
} = require("./protocol");
|
|
22
50
|
const { stableStringify } = require("./context/stableJson");
|
|
23
51
|
const { getReadToolDescription } = require("../agents/prompts/native/toolDescriptions/read");
|
|
24
52
|
const { getWriteToolDescription } = require("../agents/prompts/native/toolDescriptions/write");
|
|
25
53
|
const { getEditToolDescription } = require("../agents/prompts/native/toolDescriptions/edit");
|
|
26
54
|
const { getBashToolDescription } = require("../agents/prompts/native/toolDescriptions/bash");
|
|
27
55
|
|
|
28
|
-
const CORE_TOOL_NAMES = new Set([
|
|
56
|
+
const CORE_TOOL_NAMES = new Set([
|
|
57
|
+
"read",
|
|
58
|
+
"write",
|
|
59
|
+
"edit",
|
|
60
|
+
"bash",
|
|
61
|
+
"artifact_read",
|
|
62
|
+
"plan_graph",
|
|
63
|
+
"ask_user",
|
|
64
|
+
]);
|
|
65
|
+
const EXECUTABLE_GRAPH_TOOLS = new Set(["read", "write", "edit", "bash", "artifact_read"]);
|
|
29
66
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
30
67
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
31
68
|
const DEFAULT_KIMI_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
@@ -184,7 +221,9 @@ function createGuards({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } =
|
|
|
184
221
|
function emitToolEvent(callback, event = {}) {
|
|
185
222
|
if (typeof callback !== "function") return;
|
|
186
223
|
try {
|
|
187
|
-
|
|
224
|
+
const payload = event && typeof event === "object" ? { ...event } : {};
|
|
225
|
+
if (payload.origin == null) delete payload.origin;
|
|
226
|
+
callback(payload);
|
|
188
227
|
} catch {
|
|
189
228
|
// ignore callback failures
|
|
190
229
|
}
|
|
@@ -383,7 +422,11 @@ function buildCoreToolSpecs() {
|
|
|
383
422
|
type: "function",
|
|
384
423
|
function: {
|
|
385
424
|
name: "artifact_read",
|
|
386
|
-
description:
|
|
425
|
+
description: [
|
|
426
|
+
"Read previously stored tool output by artifactId.",
|
|
427
|
+
"This does not read workspace files; use `read` for repository paths.",
|
|
428
|
+
"Optionally read a slice with startLine/endLine, maxChars, or tailLines.",
|
|
429
|
+
].join(" "),
|
|
387
430
|
parameters: {
|
|
388
431
|
type: "object",
|
|
389
432
|
properties: {
|
|
@@ -398,6 +441,128 @@ function buildCoreToolSpecs() {
|
|
|
398
441
|
},
|
|
399
442
|
},
|
|
400
443
|
},
|
|
444
|
+
{
|
|
445
|
+
type: "function",
|
|
446
|
+
function: {
|
|
447
|
+
name: "plan_graph",
|
|
448
|
+
description: [
|
|
449
|
+
"Manage the persistent Plan Graph and asynchronous TaskRuns.",
|
|
450
|
+
"Use create, patch, inspect, or cancel_graph for graph operations, and control for TaskRun lifecycle.",
|
|
451
|
+
"`control.start_task` starts a `task_loop` asynchronously and returns immediately.",
|
|
452
|
+
"Use `inline_llm` for work handled by the current graph owner,",
|
|
453
|
+
"`expand` for tasks that must be lowered into child nodes,",
|
|
454
|
+
"and `task_loop` for asynchronous work in an independent TaskLoop.",
|
|
455
|
+
"Do not call `plan_graph` together with data-plane tools in the same assistant turn.",
|
|
456
|
+
].join(" "),
|
|
457
|
+
parameters: {
|
|
458
|
+
type: "object",
|
|
459
|
+
properties: {
|
|
460
|
+
operation: {
|
|
461
|
+
type: "string",
|
|
462
|
+
enum: [
|
|
463
|
+
"create",
|
|
464
|
+
"patch",
|
|
465
|
+
"inspect",
|
|
466
|
+
"clear",
|
|
467
|
+
"cancel_graph",
|
|
468
|
+
"control",
|
|
469
|
+
],
|
|
470
|
+
description: [
|
|
471
|
+
"create/patch/inspect/cancel_graph mutate or inspect the graph spec;",
|
|
472
|
+
"control runs TaskRun lifecycle and node status actions.",
|
|
473
|
+
].join(" "),
|
|
474
|
+
},
|
|
475
|
+
graph: {
|
|
476
|
+
type: "object",
|
|
477
|
+
description: "Full graph for create (objective + nodes). group is input sugar only.",
|
|
478
|
+
},
|
|
479
|
+
operations: {
|
|
480
|
+
type: "array",
|
|
481
|
+
description: [
|
|
482
|
+
"Patch ops only: add_node, expand_node, add_dependency, remove_dependency.",
|
|
483
|
+
"Status actions (complete_task, skip_node, cancel_subtree) belong under control.actions.",
|
|
484
|
+
].join(" "),
|
|
485
|
+
items: { type: "object" },
|
|
486
|
+
},
|
|
487
|
+
actions: {
|
|
488
|
+
type: "array",
|
|
489
|
+
description: [
|
|
490
|
+
"Control actions: start_task, cancel_task, fail_task, complete_task, skip_node, cancel_subtree.",
|
|
491
|
+
"complete_task with taskRunId finishes a TaskLoop TaskRun;",
|
|
492
|
+
"complete_task with nodeId finishes a waiting_llm inline task owned by the graph owner.",
|
|
493
|
+
].join(" "),
|
|
494
|
+
items: { type: "object" },
|
|
495
|
+
},
|
|
496
|
+
reason: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "Optional reason for cancel_graph or fail/cancel task.",
|
|
499
|
+
},
|
|
500
|
+
commandId: {
|
|
501
|
+
type: "string",
|
|
502
|
+
description: [
|
|
503
|
+
"Optional idempotency key for explicit replay.",
|
|
504
|
+
"When omitted, the Runtime should derive one from the tool invocation when available.",
|
|
505
|
+
].join(" "),
|
|
506
|
+
},
|
|
507
|
+
expectedSpecRevision: {
|
|
508
|
+
type: "integer",
|
|
509
|
+
description: "Optional optimistic concurrency token for patch.",
|
|
510
|
+
},
|
|
511
|
+
graphId: {
|
|
512
|
+
type: "string",
|
|
513
|
+
description: "Optional graph id check for patch/control.",
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
required: ["operation"],
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
type: "function",
|
|
522
|
+
function: {
|
|
523
|
+
name: "ask_user",
|
|
524
|
+
description: [
|
|
525
|
+
"Ask the user for input and pause the current Agent loop until the reply arrives.",
|
|
526
|
+
"Use only when user input is required to proceed, not for routine updates or decisions the agent can safely make.",
|
|
527
|
+
"`kind=approval` requests yes/no confirmation; `kind=choice` presents the supplied options; `kind=chat` requests free text.",
|
|
528
|
+
"This must be the only tool call in the turn.",
|
|
529
|
+
"The reply is returned only as this tool result, not as a separate user message or pending user prompt.",
|
|
530
|
+
"After the tool returns, continue from the answer and do not ask the same question again.",
|
|
531
|
+
"Running TaskRuns are not paused automatically.",
|
|
532
|
+
].join(" "),
|
|
533
|
+
parameters: {
|
|
534
|
+
type: "object",
|
|
535
|
+
properties: {
|
|
536
|
+
kind: {
|
|
537
|
+
type: "string",
|
|
538
|
+
enum: ["approval", "choice", "chat"],
|
|
539
|
+
description: "Interaction type.",
|
|
540
|
+
},
|
|
541
|
+
prompt: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Question shown to the user.",
|
|
544
|
+
},
|
|
545
|
+
options: {
|
|
546
|
+
type: "array",
|
|
547
|
+
description: "For choice: option labels (or {key,label} objects). Ignored for chat.",
|
|
548
|
+
items: {
|
|
549
|
+
oneOf: [
|
|
550
|
+
{ type: "string" },
|
|
551
|
+
{
|
|
552
|
+
type: "object",
|
|
553
|
+
properties: {
|
|
554
|
+
key: { type: "string" },
|
|
555
|
+
label: { type: "string" },
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
],
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
required: ["kind", "prompt"],
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
401
566
|
];
|
|
402
567
|
}
|
|
403
568
|
|
|
@@ -526,8 +691,10 @@ function runCoreTool({
|
|
|
526
691
|
workspaceRoot = process.cwd(),
|
|
527
692
|
onToolEvent = null,
|
|
528
693
|
sessionId = "",
|
|
529
|
-
contextV2 = false,
|
|
530
694
|
onArtifactPersisted = null,
|
|
695
|
+
executionState = null,
|
|
696
|
+
origin = null,
|
|
697
|
+
resume = null,
|
|
531
698
|
} = {}) {
|
|
532
699
|
const normalizedTool = normalizeToolName(tool);
|
|
533
700
|
if (!normalizedTool) {
|
|
@@ -536,6 +703,7 @@ function runCoreTool({
|
|
|
536
703
|
phase: "error",
|
|
537
704
|
args: args && typeof args === "object" ? { ...args } : {},
|
|
538
705
|
error: `unsupported tool: ${tool}`,
|
|
706
|
+
origin,
|
|
539
707
|
});
|
|
540
708
|
return {
|
|
541
709
|
ok: false,
|
|
@@ -552,8 +720,86 @@ function runCoreTool({
|
|
|
552
720
|
phase: "start",
|
|
553
721
|
args: safeArgs,
|
|
554
722
|
error: "",
|
|
723
|
+
origin,
|
|
555
724
|
});
|
|
556
725
|
|
|
726
|
+
if (normalizedTool === "plan_graph") {
|
|
727
|
+
const state = executionState && typeof executionState === "object"
|
|
728
|
+
? executionState
|
|
729
|
+
: emptyExecutionState();
|
|
730
|
+
const result = runPlanGraphCommand(safeArgs, {
|
|
731
|
+
executionState: state,
|
|
732
|
+
autoAdvance: true,
|
|
733
|
+
parallel: true,
|
|
734
|
+
runTool: ({ node, args: nestedArgs, tool: nestedTool, stepId }) => {
|
|
735
|
+
const nested = runCoreTool({
|
|
736
|
+
tool: nestedTool,
|
|
737
|
+
args: nestedArgs,
|
|
738
|
+
workspaceRoot,
|
|
739
|
+
onToolEvent,
|
|
740
|
+
sessionId,
|
|
741
|
+
onArtifactPersisted,
|
|
742
|
+
executionState: state,
|
|
743
|
+
origin: {
|
|
744
|
+
kind: "plan_graph",
|
|
745
|
+
graphId: String(state.planGraph && state.planGraph.graphId || ""),
|
|
746
|
+
graphRevision: Number(state.planGraph && state.planGraph.specRevision) || 0,
|
|
747
|
+
commandRevision: Number(state.planGraph && state.planGraph.specRevision) || 0,
|
|
748
|
+
nodeId: stepId || (node && node.id) || "",
|
|
749
|
+
attempt: Number(node && node.attempt) || 0,
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
return nested;
|
|
753
|
+
},
|
|
754
|
+
});
|
|
755
|
+
if (result.ok === false) {
|
|
756
|
+
emitToolEvent(onToolEvent, {
|
|
757
|
+
tool: "plan_graph",
|
|
758
|
+
phase: "error",
|
|
759
|
+
args: safeArgs,
|
|
760
|
+
error: Array.isArray(result.errors)
|
|
761
|
+
? result.errors.map((e) => e.message || e.code).join("; ")
|
|
762
|
+
: "plan_graph rejected",
|
|
763
|
+
origin,
|
|
764
|
+
});
|
|
765
|
+
} else {
|
|
766
|
+
syncInteractionFromPlanGraph(result.executionState || state);
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
...result.modelPayload,
|
|
770
|
+
ok: result.status === "accepted",
|
|
771
|
+
executionState: result.executionState || state,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (normalizedTool === "ask_user") {
|
|
776
|
+
const state = executionState && typeof executionState === "object"
|
|
777
|
+
? executionState
|
|
778
|
+
: emptyExecutionState();
|
|
779
|
+
const result = runAskUserTool(safeArgs, {
|
|
780
|
+
executionState: state,
|
|
781
|
+
resume: resume || null,
|
|
782
|
+
});
|
|
783
|
+
const ok = result.ok !== false && result.status !== "rejected";
|
|
784
|
+
emitToolEvent(onToolEvent, {
|
|
785
|
+
tool: "ask_user",
|
|
786
|
+
phase: ok ? "end" : "error",
|
|
787
|
+
args: safeArgs,
|
|
788
|
+
result: result.modelPayload || result,
|
|
789
|
+
error: ok ? "" : (result.error || "ask_user rejected"),
|
|
790
|
+
origin,
|
|
791
|
+
});
|
|
792
|
+
return {
|
|
793
|
+
...(result.modelPayload || result),
|
|
794
|
+
ok,
|
|
795
|
+
status: result.status,
|
|
796
|
+
waiting_user: Boolean(result.waiting_user || result.status === "waiting_user"),
|
|
797
|
+
interactionId: result.interactionId || "",
|
|
798
|
+
executionState: result.executionState || state,
|
|
799
|
+
deferToolResult: ok && result.status === "waiting_user",
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
557
803
|
const toolOptions = { workspaceRoot, cwd: workspaceRoot };
|
|
558
804
|
if (normalizedTool === "artifact_read" && sessionId) {
|
|
559
805
|
toolOptions.sessionId = sessionId;
|
|
@@ -569,12 +815,12 @@ function runCoreTool({
|
|
|
569
815
|
phase: "error",
|
|
570
816
|
args: safeArgs,
|
|
571
817
|
error: String((result && result.error) || `${normalizedTool} failed`),
|
|
818
|
+
origin,
|
|
572
819
|
});
|
|
573
820
|
return result;
|
|
574
821
|
}
|
|
575
822
|
|
|
576
|
-
|
|
577
|
-
if (useContextV2 && normalizedTool !== "artifact_read") {
|
|
823
|
+
if (normalizedTool !== "artifact_read" && EXECUTABLE_GRAPH_TOOLS.has(normalizedTool)) {
|
|
578
824
|
const persisted = persistToolResultToContext({
|
|
579
825
|
workspaceRoot,
|
|
580
826
|
sessionId,
|
|
@@ -589,9 +835,14 @@ function runCoreTool({
|
|
|
589
835
|
// ignore
|
|
590
836
|
}
|
|
591
837
|
}
|
|
592
|
-
|
|
838
|
+
const payload = persisted.modelPayload || result;
|
|
839
|
+
if (origin) payload.origin = origin;
|
|
840
|
+
return payload;
|
|
593
841
|
}
|
|
594
842
|
|
|
843
|
+
if (origin && result && typeof result === "object") {
|
|
844
|
+
return { ...result, origin };
|
|
845
|
+
}
|
|
595
846
|
return result;
|
|
596
847
|
}
|
|
597
848
|
|
|
@@ -1222,140 +1473,76 @@ async function runAnthropicTurn({
|
|
|
1222
1473
|
});
|
|
1223
1474
|
}
|
|
1224
1475
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1476
|
+
const {
|
|
1477
|
+
createOpenAiChatTransport,
|
|
1478
|
+
createAnthropicMessagesTransport,
|
|
1479
|
+
} = require("./providers");
|
|
1480
|
+
|
|
1481
|
+
// Transport descriptors: wire-format only. Plan Mode / leases / policy live in the loop.
|
|
1229
1482
|
const TRANSPORTS = {
|
|
1230
|
-
"openai-chat": {
|
|
1483
|
+
"openai-chat": createOpenAiChatTransport({
|
|
1231
1484
|
resolveUrl: resolveCompletionUrl,
|
|
1232
|
-
prepareMessages({ messages, systemPrompt, prompt }) {
|
|
1233
|
-
const systemText = String(systemPrompt || "").trim();
|
|
1234
|
-
const hasSystem = messages.some((entry) => String(entry.role || "").trim() === "system");
|
|
1235
|
-
if (systemText && !hasSystem) {
|
|
1236
|
-
messages.unshift({ role: "system", content: systemText });
|
|
1237
|
-
}
|
|
1238
|
-
messages.push({ role: "user", content: String(prompt || "") });
|
|
1239
|
-
},
|
|
1240
1485
|
runTurn: runOpenAiLikeTurn,
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
}
|
|
1254
|
-
},
|
|
1255
|
-
prepareToolCalls({ messages, toolCalls }) {
|
|
1256
|
-
const assistantToolCalls = [];
|
|
1257
|
-
for (const call of toolCalls) {
|
|
1258
|
-
const callId = String(call.id || `call_${randomUUID()}`);
|
|
1259
|
-
const name = normalizeToolName(call.function.name || "");
|
|
1260
|
-
const args = normalizeToolCallArgs(call.function.arguments || "");
|
|
1261
|
-
|
|
1262
|
-
assistantToolCalls.push({
|
|
1263
|
-
id: callId,
|
|
1264
|
-
type: "function",
|
|
1265
|
-
function: {
|
|
1266
|
-
name: name || String(call.function.name || ""),
|
|
1267
|
-
arguments: toJsonString(args),
|
|
1268
|
-
},
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1486
|
+
normalizeToolName,
|
|
1487
|
+
normalizeToolCallArgs,
|
|
1488
|
+
toJsonString,
|
|
1489
|
+
clipText,
|
|
1490
|
+
}),
|
|
1491
|
+
"anthropic-messages": createAnthropicMessagesTransport({
|
|
1492
|
+
resolveUrl: resolveAnthropicMessagesUrl,
|
|
1493
|
+
runTurn: runAnthropicTurn,
|
|
1494
|
+
toJsonString,
|
|
1495
|
+
clipText,
|
|
1496
|
+
}),
|
|
1497
|
+
};
|
|
1271
1498
|
|
|
1272
|
-
|
|
1499
|
+
function pendingToolCallId(pending = null) {
|
|
1500
|
+
if (!pending || !pending.source) return "";
|
|
1501
|
+
return String(pending.source.id || "").trim();
|
|
1502
|
+
}
|
|
1273
1503
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1504
|
+
function shadowDeclarePendingCalls(ledger, pendingCalls = []) {
|
|
1505
|
+
const entries = (Array.isArray(pendingCalls) ? pendingCalls : []).map((pending) => ({
|
|
1506
|
+
callId: pendingToolCallId(pending) || `call_${randomUUID()}`,
|
|
1507
|
+
name: String(pending && pending.name || "").trim().toLowerCase(),
|
|
1508
|
+
args: pending && pending.args != null ? pending.args : {},
|
|
1509
|
+
}));
|
|
1510
|
+
// Keep source ids aligned when we had to synthesize.
|
|
1511
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
1512
|
+
const pending = pendingCalls[i];
|
|
1513
|
+
if (pending && pending.source && !pending.source.id) {
|
|
1514
|
+
pending.source.id = entries[i].callId;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return declareCalls(ledger, entries);
|
|
1518
|
+
}
|
|
1279
1519
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
tool_call_id: call.source.id,
|
|
1290
|
-
content: clipText(toJsonString(toolResult), 12000),
|
|
1291
|
-
});
|
|
1292
|
-
},
|
|
1293
|
-
},
|
|
1294
|
-
"anthropic-messages": {
|
|
1295
|
-
resolveUrl: resolveAnthropicMessagesUrl,
|
|
1296
|
-
prepareMessages({ messages, prompt }) {
|
|
1297
|
-
messages.push({
|
|
1298
|
-
role: "user",
|
|
1299
|
-
content: String(prompt || ""),
|
|
1300
|
-
});
|
|
1301
|
-
},
|
|
1302
|
-
runTurn: runAnthropicTurn,
|
|
1303
|
-
getToolCalls(turnResult) {
|
|
1304
|
-
return Array.isArray(turnResult.toolCalls) ? turnResult.toolCalls : [];
|
|
1305
|
-
},
|
|
1306
|
-
appendFinalAssistantMessage({ messages, turnResult }) {
|
|
1307
|
-
const assistantContent = Array.isArray(turnResult.assistantContent)
|
|
1308
|
-
? turnResult.assistantContent
|
|
1309
|
-
: [];
|
|
1310
|
-
if (assistantContent.length > 0) {
|
|
1311
|
-
messages.push({
|
|
1312
|
-
role: "assistant",
|
|
1313
|
-
content: assistantContent,
|
|
1314
|
-
});
|
|
1315
|
-
} else if (String(turnResult.text || "").trim()) {
|
|
1316
|
-
messages.push({
|
|
1317
|
-
role: "assistant",
|
|
1318
|
-
content: [
|
|
1319
|
-
{
|
|
1320
|
-
type: "text",
|
|
1321
|
-
text: String(turnResult.text || ""),
|
|
1322
|
-
},
|
|
1323
|
-
],
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
},
|
|
1327
|
-
prepareToolCalls({ messages, turnResult, toolCalls }) {
|
|
1328
|
-
const assistantContent = Array.isArray(turnResult.assistantContent)
|
|
1329
|
-
? turnResult.assistantContent
|
|
1330
|
-
: [];
|
|
1331
|
-
|
|
1332
|
-
messages.push({
|
|
1333
|
-
role: "assistant",
|
|
1334
|
-
content: assistantContent,
|
|
1335
|
-
});
|
|
1520
|
+
function shadowResolvePending(ledger, pending, toolResult) {
|
|
1521
|
+
if (!ledger) return;
|
|
1522
|
+
const callId = pendingToolCallId(pending);
|
|
1523
|
+
if (!callId) return;
|
|
1524
|
+
resolveCall(ledger, callId, {
|
|
1525
|
+
result: toolResult,
|
|
1526
|
+
isError: Boolean(!toolResult || toolResult.ok === false),
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1336
1529
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
role: "user",
|
|
1354
|
-
content: collected,
|
|
1355
|
-
});
|
|
1356
|
-
},
|
|
1357
|
-
},
|
|
1358
|
-
};
|
|
1530
|
+
function pendingByIdMap(pendingCalls = []) {
|
|
1531
|
+
const map = Object.create(null);
|
|
1532
|
+
for (const pending of pendingCalls) {
|
|
1533
|
+
const id = pendingToolCallId(pending);
|
|
1534
|
+
if (id) map[id] = pending;
|
|
1535
|
+
}
|
|
1536
|
+
return map;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function flushLedgerToolResults(ledger, transport, messages, pendingCalls) {
|
|
1540
|
+
return materializeResolvedToolResults(ledger, {
|
|
1541
|
+
transport,
|
|
1542
|
+
messages,
|
|
1543
|
+
pendingById: pendingByIdMap(pendingCalls),
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1359
1546
|
|
|
1360
1547
|
async function runNativeLoop({
|
|
1361
1548
|
transport,
|
|
@@ -1375,9 +1562,10 @@ async function runNativeLoop({
|
|
|
1375
1562
|
onToolEvent = null,
|
|
1376
1563
|
onArtifactPersisted = null,
|
|
1377
1564
|
sessionId = "",
|
|
1378
|
-
contextV2 = false,
|
|
1379
1565
|
signal = null,
|
|
1380
1566
|
guards,
|
|
1567
|
+
executionState: initialExecutionState = null,
|
|
1568
|
+
resume = false,
|
|
1381
1569
|
} = {}) {
|
|
1382
1570
|
const requestModel = String(model || "").trim();
|
|
1383
1571
|
if (!requestModel) {
|
|
@@ -1390,19 +1578,50 @@ async function runNativeLoop({
|
|
|
1390
1578
|
}
|
|
1391
1579
|
|
|
1392
1580
|
const messages = sanitizeModelMessages(cloneMessageList(historyMessages));
|
|
1393
|
-
|
|
1581
|
+
if (!resume) {
|
|
1582
|
+
transport.prepareMessages({ messages, systemPrompt, prompt });
|
|
1583
|
+
}
|
|
1394
1584
|
|
|
1395
1585
|
let aggregated = "";
|
|
1396
1586
|
let streamed = false;
|
|
1397
1587
|
let toolCallsExecuted = 0;
|
|
1398
1588
|
let toolErrors = 0;
|
|
1399
|
-
let executionState =
|
|
1589
|
+
let executionState = initialExecutionState && typeof initialExecutionState === "object"
|
|
1590
|
+
? initialExecutionState
|
|
1591
|
+
: emptyExecutionState();
|
|
1592
|
+
if (typeof executionState.planMode !== "boolean") executionState.planMode = false;
|
|
1593
|
+
ensurePendingUserPrompts(executionState);
|
|
1400
1594
|
const toolBudget = resolveNativeToolBudget();
|
|
1401
1595
|
const usage = createUsageTotals();
|
|
1596
|
+
// Shadow Tool Call Ledger (R1). Observes declare/defer/resolve; does not
|
|
1597
|
+
// materialize Provider messages yet. STRICT via UFOO_UCODE_PROTOCOL_STRICT=1.
|
|
1598
|
+
let activeLedger = null;
|
|
1599
|
+
let lastProtocolLedger = null;
|
|
1600
|
+
|
|
1601
|
+
if (resume) {
|
|
1602
|
+
await withFaultPoint("before_provider_resume", () => {});
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function injectPendingUserReminders() {
|
|
1606
|
+
const nudges = drainUserPrompts(executionState);
|
|
1607
|
+
if (nudges.length === 0) return;
|
|
1608
|
+
const waiting = executionState.planGraph && executionState.planGraph.waitingFor
|
|
1609
|
+
? executionState.planGraph.waitingFor
|
|
1610
|
+
: null;
|
|
1611
|
+
const content = formatUserReminderMessage(nudges, { waitingFor: waiting });
|
|
1612
|
+
if (!content) return;
|
|
1613
|
+
messages.push({ role: "user", content });
|
|
1614
|
+
}
|
|
1402
1615
|
|
|
1403
1616
|
while (true) {
|
|
1404
1617
|
guards.ensureActive();
|
|
1405
1618
|
|
|
1619
|
+
injectPendingUserReminders();
|
|
1620
|
+
|
|
1621
|
+
if (activeLedger) {
|
|
1622
|
+
runProviderTurnGate(activeLedger);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1406
1625
|
const turnResult = await transport.runTurn({
|
|
1407
1626
|
url: requestUrl,
|
|
1408
1627
|
apiKey,
|
|
@@ -1433,31 +1652,33 @@ async function runNativeLoop({
|
|
|
1433
1652
|
|
|
1434
1653
|
if (toolCalls.length === 0) {
|
|
1435
1654
|
const text = String(turnResult.text || "").trim();
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
if (segment && segment.steps && segment.steps.length > 0) {
|
|
1655
|
+
const sideEffects = parseStructuredSideEffects(text);
|
|
1656
|
+
const planCommand = sideEffects ? normalizePlanGraphCommand(sideEffects) : null;
|
|
1657
|
+
if (planCommand) {
|
|
1440
1658
|
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1659
|
+
const planResult = runCoreTool({
|
|
1660
|
+
tool: "plan_graph",
|
|
1661
|
+
args: planCommand,
|
|
1662
|
+
workspaceRoot,
|
|
1663
|
+
onToolEvent,
|
|
1664
|
+
sessionId,
|
|
1665
|
+
onArtifactPersisted,
|
|
1443
1666
|
executionState,
|
|
1444
|
-
|
|
1445
|
-
tool,
|
|
1446
|
-
args,
|
|
1447
|
-
workspaceRoot,
|
|
1448
|
-
onToolEvent,
|
|
1449
|
-
sessionId,
|
|
1450
|
-
contextV2,
|
|
1451
|
-
onArtifactPersisted,
|
|
1452
|
-
}),
|
|
1667
|
+
origin: { kind: "legacy_side_effect", source: planCommand.source || "legacy" },
|
|
1453
1668
|
});
|
|
1454
|
-
|
|
1669
|
+
if (planResult && planResult.executionState) {
|
|
1670
|
+
executionState = planResult.executionState;
|
|
1671
|
+
}
|
|
1455
1672
|
messages.push({
|
|
1456
1673
|
role: "user",
|
|
1457
|
-
content:
|
|
1674
|
+
content: JSON.stringify({
|
|
1675
|
+
type: "plan_graph_result",
|
|
1676
|
+
...((planResult && planResult.status)
|
|
1677
|
+
? planResult
|
|
1678
|
+
: { status: "rejected", ok: false, error: "plan_graph failed" }),
|
|
1679
|
+
}),
|
|
1458
1680
|
});
|
|
1459
1681
|
continue;
|
|
1460
|
-
}
|
|
1461
1682
|
}
|
|
1462
1683
|
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1463
1684
|
if (!aggregated.trim() && text) {
|
|
@@ -1469,6 +1690,8 @@ async function runNativeLoop({
|
|
|
1469
1690
|
toolCallsExecuted,
|
|
1470
1691
|
messages,
|
|
1471
1692
|
usage,
|
|
1693
|
+
executionState,
|
|
1694
|
+
protocolLedger: lastProtocolLedger || snapshotLedger(activeLedger),
|
|
1472
1695
|
};
|
|
1473
1696
|
}
|
|
1474
1697
|
|
|
@@ -1480,24 +1703,161 @@ async function runNativeLoop({
|
|
|
1480
1703
|
toolCallsExecuted,
|
|
1481
1704
|
messages,
|
|
1482
1705
|
usage,
|
|
1706
|
+
executionState,
|
|
1707
|
+
protocolLedger: lastProtocolLedger || snapshotLedger(activeLedger),
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
activeLedger = createToolCallLedger({ provider, sessionId });
|
|
1712
|
+
shadowDeclarePendingCalls(activeLedger, pendingCalls);
|
|
1713
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1714
|
+
await withFaultPoint("after_prepare_tool_calls", () => {});
|
|
1715
|
+
await withFaultPoint("before_tool_exec", () => {});
|
|
1716
|
+
|
|
1717
|
+
const callNames = pendingCalls.map((call) => String(call.name || "").trim().toLowerCase());
|
|
1718
|
+
const hasPlanGraph = callNames.includes("plan_graph");
|
|
1719
|
+
const hasAskUser = callNames.includes("ask_user");
|
|
1720
|
+
const hasDataTool = callNames.some((name) => EXECUTABLE_GRAPH_TOOLS.has(name));
|
|
1721
|
+
if (hasPlanGraph && hasDataTool) {
|
|
1722
|
+
// prepareToolCalls already appended the assistant tool_calls / tool_use
|
|
1723
|
+
// message; every declared call must get a contiguous tool result via ledger.
|
|
1724
|
+
const rejected = {
|
|
1725
|
+
ok: false,
|
|
1726
|
+
status: "rejected",
|
|
1727
|
+
error: "Do not mix plan_graph with data-plane tools in the same turn",
|
|
1728
|
+
code: "MIXED_PLAN_AND_DATA_TOOLS",
|
|
1483
1729
|
};
|
|
1730
|
+
for (const pending of pendingCalls) {
|
|
1731
|
+
shadowResolvePending(activeLedger, pending, rejected);
|
|
1732
|
+
toolCallsExecuted += 1;
|
|
1733
|
+
toolErrors += 1;
|
|
1734
|
+
}
|
|
1735
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
1736
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (hasAskUser && pendingCalls.length > 1) {
|
|
1740
|
+
const rejected = {
|
|
1741
|
+
ok: false,
|
|
1742
|
+
status: "rejected",
|
|
1743
|
+
error: "ask_user must be the only tool call in the turn",
|
|
1744
|
+
code: "ASK_USER_MUST_BE_ALONE",
|
|
1745
|
+
};
|
|
1746
|
+
for (const pending of pendingCalls) {
|
|
1747
|
+
shadowResolvePending(activeLedger, pending, rejected);
|
|
1748
|
+
toolCallsExecuted += 1;
|
|
1749
|
+
toolErrors += 1;
|
|
1750
|
+
}
|
|
1751
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
1752
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1753
|
+
continue;
|
|
1484
1754
|
}
|
|
1485
1755
|
|
|
1486
|
-
|
|
1756
|
+
let deferredAskUser = null;
|
|
1487
1757
|
for (const pending of pendingCalls) {
|
|
1758
|
+
const pendingName = String(pending.name || "").trim().toLowerCase();
|
|
1759
|
+
if (
|
|
1760
|
+
EXECUTABLE_GRAPH_TOOLS.has(pendingName)
|
|
1761
|
+
&& activePlanRequiresExpansion(executionState.planGraph)
|
|
1762
|
+
) {
|
|
1763
|
+
const blocked = {
|
|
1764
|
+
ok: false,
|
|
1765
|
+
status: "rejected",
|
|
1766
|
+
errors: [{
|
|
1767
|
+
code: "ACTIVE_PLAN_REQUIRES_EXPANSION",
|
|
1768
|
+
message: "Active plan is waiting on a task; use plan_graph expand_node or control.complete_task instead of direct tools",
|
|
1769
|
+
}],
|
|
1770
|
+
};
|
|
1771
|
+
toolCallsExecuted += 1;
|
|
1772
|
+
toolErrors += 1;
|
|
1773
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
if (planModeBlocksDirectTool(pendingName, executionState)) {
|
|
1777
|
+
const blocked = {
|
|
1778
|
+
ok: false,
|
|
1779
|
+
status: "rejected",
|
|
1780
|
+
errors: [{
|
|
1781
|
+
code: "PLAN_MODE_BLOCKS_SIDE_EFFECT",
|
|
1782
|
+
message: "Plan mode is on; use plan_graph for write/edit/bash, or ask the user to /plan off",
|
|
1783
|
+
}],
|
|
1784
|
+
};
|
|
1785
|
+
toolCallsExecuted += 1;
|
|
1786
|
+
toolErrors += 1;
|
|
1787
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
const leaseCheck = checkWriteAllowed(executionState, {
|
|
1791
|
+
tool: pendingName,
|
|
1792
|
+
originKind: "agent_loop",
|
|
1793
|
+
});
|
|
1794
|
+
if (!leaseCheck.ok) {
|
|
1795
|
+
const blocked = {
|
|
1796
|
+
ok: false,
|
|
1797
|
+
status: "rejected",
|
|
1798
|
+
errors: [{
|
|
1799
|
+
code: leaseCheck.code || "WORKSPACE_WRITE_LEASE_HELD",
|
|
1800
|
+
message: leaseCheck.message
|
|
1801
|
+
|| "Workspace write lease held by an active TaskRun",
|
|
1802
|
+
owner: leaseCheck.owner || null,
|
|
1803
|
+
}],
|
|
1804
|
+
};
|
|
1805
|
+
toolCallsExecuted += 1;
|
|
1806
|
+
toolErrors += 1;
|
|
1807
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
const toolCallId = pending.source && (pending.source.id || (pending.source.function && pending.source.id))
|
|
1812
|
+
? String(pending.source.id || "")
|
|
1813
|
+
: "";
|
|
1814
|
+
const resumeForAsk = pendingName === "ask_user"
|
|
1815
|
+
? {
|
|
1816
|
+
toolCallId: toolCallId || String((pending.source && pending.source.id) || `call_${randomUUID()}`),
|
|
1817
|
+
toolName: "ask_user",
|
|
1818
|
+
call: {
|
|
1819
|
+
name: pending.name,
|
|
1820
|
+
args: pending.args,
|
|
1821
|
+
source: pending.source,
|
|
1822
|
+
},
|
|
1823
|
+
}
|
|
1824
|
+
: null;
|
|
1825
|
+
|
|
1826
|
+
markExecuting(activeLedger, pendingToolCallId(pending));
|
|
1488
1827
|
const toolResult = runCoreTool({
|
|
1489
1828
|
tool: pending.name,
|
|
1490
1829
|
args: pending.args,
|
|
1491
1830
|
workspaceRoot,
|
|
1492
1831
|
onToolEvent,
|
|
1493
1832
|
sessionId,
|
|
1494
|
-
contextV2,
|
|
1495
1833
|
onArtifactPersisted,
|
|
1834
|
+
executionState,
|
|
1835
|
+
resume: resumeForAsk,
|
|
1496
1836
|
});
|
|
1837
|
+
if (toolResult && toolResult.executionState) {
|
|
1838
|
+
executionState = toolResult.executionState;
|
|
1839
|
+
}
|
|
1497
1840
|
toolCallsExecuted += 1;
|
|
1498
1841
|
if (!toolResult || toolResult.ok === false) {
|
|
1499
1842
|
toolErrors += 1;
|
|
1500
1843
|
}
|
|
1844
|
+
|
|
1845
|
+
if (pendingName === "ask_user" && toolResult && toolResult.deferToolResult) {
|
|
1846
|
+
// Attach resume metadata onto pending interaction for contiguous tool_result later.
|
|
1847
|
+
const pendingInteraction = getPendingUserInteraction(executionState);
|
|
1848
|
+
if (pendingInteraction) {
|
|
1849
|
+
pendingInteraction.resume = {
|
|
1850
|
+
...(pendingInteraction.resume || {}),
|
|
1851
|
+
...(resumeForAsk || {}),
|
|
1852
|
+
mode: "ask_user",
|
|
1853
|
+
transport: provider === "anthropic" ? "anthropic-messages" : "openai-chat",
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
deferCall(activeLedger, pendingToolCallId(pending), { reason: "ask_user" });
|
|
1857
|
+
deferredAskUser = { call: pending, interactionId: toolResult.interactionId || "" };
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1501
1861
|
enforceNativeToolBudget({
|
|
1502
1862
|
toolCallsExecuted,
|
|
1503
1863
|
toolErrors,
|
|
@@ -1506,18 +1866,62 @@ async function runNativeLoop({
|
|
|
1506
1866
|
lastTool: pending.name,
|
|
1507
1867
|
lastError: toolResult && toolResult.error ? String(toolResult.error) : "",
|
|
1508
1868
|
});
|
|
1509
|
-
|
|
1869
|
+
shadowResolvePending(activeLedger, pending, toolResult);
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
1873
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1874
|
+
|
|
1875
|
+
if (deferredAskUser) {
|
|
1876
|
+
return {
|
|
1877
|
+
text: aggregated,
|
|
1878
|
+
streamed,
|
|
1879
|
+
toolCallsExecuted,
|
|
1510
1880
|
messages,
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1881
|
+
usage,
|
|
1882
|
+
executionState,
|
|
1883
|
+
waitingUserInteraction: true,
|
|
1884
|
+
interactionId: deferredAskUser.interactionId || "",
|
|
1885
|
+
protocolLedger: lastProtocolLedger,
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
if (hasPendingUserInteraction(executionState)) {
|
|
1890
|
+
// Checkpoint approval synced from plan_graph — pause for TUI.
|
|
1891
|
+
return {
|
|
1892
|
+
text: aggregated,
|
|
1893
|
+
streamed,
|
|
1894
|
+
toolCallsExecuted,
|
|
1895
|
+
messages,
|
|
1896
|
+
usage,
|
|
1897
|
+
executionState,
|
|
1898
|
+
waitingUserInteraction: true,
|
|
1899
|
+
interactionId: (getPendingUserInteraction(executionState) || {}).id || "",
|
|
1900
|
+
protocolLedger: lastProtocolLedger,
|
|
1901
|
+
};
|
|
1515
1902
|
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1516
1905
|
|
|
1517
|
-
|
|
1518
|
-
|
|
1906
|
+
function appendAnswerToolResult(messages = [], resume = null, answer = {}, options = {}) {
|
|
1907
|
+
const materialized = materializeAnswerToolResult(messages, resume, answer);
|
|
1908
|
+
if (!materialized.ok) return materialized;
|
|
1909
|
+
const ledger = options && options.ledger ? options.ledger : null;
|
|
1910
|
+
if (ledger) {
|
|
1911
|
+
const call = resume && resume.call ? resume.call : null;
|
|
1912
|
+
const callId = String(
|
|
1913
|
+
(call && call.source && call.source.id) || (resume && resume.toolCallId) || ""
|
|
1914
|
+
).trim();
|
|
1915
|
+
if (callId) {
|
|
1916
|
+
resolveCall(ledger, callId, {
|
|
1917
|
+
result: answer,
|
|
1918
|
+
isError: false,
|
|
1919
|
+
allowFromDeferred: true,
|
|
1920
|
+
});
|
|
1519
1921
|
}
|
|
1520
1922
|
}
|
|
1923
|
+
checkFaultPoint("after_answer_commit");
|
|
1924
|
+
return { ok: true };
|
|
1521
1925
|
}
|
|
1522
1926
|
|
|
1523
1927
|
async function runNativeAgentTask({
|
|
@@ -1535,8 +1939,9 @@ async function runNativeAgentTask({
|
|
|
1535
1939
|
onPhase = null,
|
|
1536
1940
|
onToolEvent = null,
|
|
1537
1941
|
onArtifactPersisted = null,
|
|
1538
|
-
contextV2 = false,
|
|
1539
1942
|
signal = null,
|
|
1943
|
+
executionState = null,
|
|
1944
|
+
resume = false,
|
|
1540
1945
|
} = {}) {
|
|
1541
1946
|
const guards = createGuards({ signal, timeoutMs });
|
|
1542
1947
|
const nextSessionId = String(sessionId || "").trim() || `native-${randomUUID()}`;
|
|
@@ -1556,7 +1961,7 @@ async function runNativeAgentTask({
|
|
|
1556
1961
|
try {
|
|
1557
1962
|
guards.ensureActive();
|
|
1558
1963
|
|
|
1559
|
-
if (!promptText) {
|
|
1964
|
+
if (!resume && !promptText) {
|
|
1560
1965
|
return {
|
|
1561
1966
|
ok: false,
|
|
1562
1967
|
error: "empty task",
|
|
@@ -1594,7 +1999,7 @@ async function runNativeAgentTask({
|
|
|
1594
1999
|
const runResult = await runNativeLoop({
|
|
1595
2000
|
transport,
|
|
1596
2001
|
workspaceRoot,
|
|
1597
|
-
prompt: promptText,
|
|
2002
|
+
prompt: resume ? "" : promptText,
|
|
1598
2003
|
systemPrompt,
|
|
1599
2004
|
systemBlocks,
|
|
1600
2005
|
historyMessages: messages,
|
|
@@ -1609,9 +2014,10 @@ async function runNativeAgentTask({
|
|
|
1609
2014
|
onToolEvent,
|
|
1610
2015
|
onArtifactPersisted,
|
|
1611
2016
|
sessionId: nextSessionId,
|
|
1612
|
-
contextV2: contextV2 || isContextV2Enabled(),
|
|
1613
2017
|
signal,
|
|
1614
2018
|
guards,
|
|
2019
|
+
executionState,
|
|
2020
|
+
resume: Boolean(resume),
|
|
1615
2021
|
});
|
|
1616
2022
|
|
|
1617
2023
|
const outputText = String(runResult.text || "").trim() || (
|
|
@@ -1641,26 +2047,37 @@ async function runNativeAgentTask({
|
|
|
1641
2047
|
messages: cloneMessageList(runResult.messages),
|
|
1642
2048
|
sessionId: nextSessionId,
|
|
1643
2049
|
usage,
|
|
2050
|
+
executionState: runResult.executionState || executionState || null,
|
|
1644
2051
|
// The loop marks streamed=true whenever it receives a stream callback;
|
|
1645
2052
|
// only report it when the caller actually registered one.
|
|
1646
2053
|
streamed: Boolean(runResult.streamed) && typeof onStreamDelta === "function",
|
|
2054
|
+
waitingUserInteraction: Boolean(runResult.waitingUserInteraction),
|
|
2055
|
+
interactionId: runResult.interactionId || "",
|
|
2056
|
+
protocolLedger: runResult.protocolLedger || null,
|
|
1647
2057
|
};
|
|
1648
2058
|
} catch (err) {
|
|
1649
2059
|
const message = err && err.message ? err.message : "native runner failed";
|
|
2060
|
+
if (executionState && typeof executionState === "object") {
|
|
2061
|
+
clearUserPrompts(executionState);
|
|
2062
|
+
}
|
|
1650
2063
|
return {
|
|
1651
2064
|
ok: false,
|
|
1652
2065
|
error: message,
|
|
1653
2066
|
output: partialOutput.trim(),
|
|
1654
2067
|
sessionId: nextSessionId,
|
|
1655
2068
|
streamed: false,
|
|
2069
|
+
executionState: executionState || null,
|
|
1656
2070
|
};
|
|
1657
2071
|
}
|
|
1658
2072
|
}
|
|
1659
2073
|
|
|
1660
2074
|
module.exports = {
|
|
1661
2075
|
runNativeAgentTask,
|
|
2076
|
+
appendAnswerToolResult,
|
|
1662
2077
|
resolveRuntimeConfig,
|
|
1663
2078
|
resolveCompletionUrl,
|
|
1664
2079
|
resolveAnthropicMessagesUrl,
|
|
1665
2080
|
resolveTransport,
|
|
2081
|
+
buildCoreToolSpecs,
|
|
2082
|
+
buildAnthropicToolSpecs,
|
|
1666
2083
|
};
|