u-foo 3.0.0 → 3.0.2
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/agents/prompts/native/tasks.js +4 -1
- package/src/app/chat/commandExecutor.js +111 -1
- package/src/app/chat/commands.js +2 -1
- package/src/app/chat/daemonMessageRouter.js +1 -1
- package/src/app/chat/inputSubmitHandler.js +3 -2
- package/src/code/agent.js +17 -3
- package/src/code/commands.js +3 -3
- package/src/code/context/executionSegment.js +5 -0
- package/src/code/context/planMode.js +8 -1
- package/src/code/context/promptLayers.js +10 -9
- package/src/code/dispatch.js +4 -0
- package/src/code/index.js +2 -0
- package/src/code/modelCommand.js +199 -23
- package/src/code/nativeRunner.js +299 -225
- 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 +8 -0
- package/src/code/providers/modelsCatalog.js +304 -0
- package/src/code/providers/openaiChatTransport.js +98 -0
- package/src/code/providers/transportContract.js +46 -0
- package/src/code/repl.js +45 -29
- package/src/code/runtime/taskControl.js +177 -53
- package/src/code/runtime/taskFocus.js +30 -10
- package/src/code/runtime/taskLoop.js +25 -3
- package/src/code/runtime/taskRun.js +172 -2
- package/src/code/runtime/workspaceLease.js +41 -0
- package/src/code/sessionStore.js +1 -0
- package/src/code/taskRoute.js +73 -0
- package/src/code/thinkingLevels.js +132 -0
- package/src/code/tools/taskRun.js +118 -0
- package/src/config.js +10 -1
- package/src/ui/format/index.js +48 -3
- package/src/ui/ink/ChatApp.js +137 -25
- package/src/ui/ink/UcodeApp.js +38 -30
- package/src/ui/ink/chatLogModel.js +238 -32
- package/src/ui/ink/chatReducer.js +18 -6
package/src/code/nativeRunner.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
resolveKimiUpstreamCredentials,
|
|
6
6
|
} = require("../agents/providers/credentials/kimi");
|
|
7
7
|
const { runToolCall } = require("./dispatch");
|
|
8
|
+
const { runTaskRunTool } = require("./tools/taskRun");
|
|
8
9
|
const { appendUsageRecord } = require("./usageStore");
|
|
9
10
|
const {
|
|
10
11
|
persistToolResultToContext,
|
|
@@ -34,6 +35,19 @@ const {
|
|
|
34
35
|
getPendingUserInteraction,
|
|
35
36
|
} = require("./context/userInteraction");
|
|
36
37
|
const { checkWriteAllowed } = require("./runtime/workspaceLease");
|
|
38
|
+
const {
|
|
39
|
+
createToolCallLedger,
|
|
40
|
+
declareCalls,
|
|
41
|
+
markExecuting,
|
|
42
|
+
deferCall,
|
|
43
|
+
resolveCall,
|
|
44
|
+
snapshotLedger,
|
|
45
|
+
runProviderTurnGate,
|
|
46
|
+
withFaultPoint,
|
|
47
|
+
checkFaultPoint,
|
|
48
|
+
materializeResolvedToolResults,
|
|
49
|
+
materializeAnswerToolResult,
|
|
50
|
+
} = require("./protocol");
|
|
37
51
|
const { stableStringify } = require("./context/stableJson");
|
|
38
52
|
const { getReadToolDescription } = require("../agents/prompts/native/toolDescriptions/read");
|
|
39
53
|
const { getWriteToolDescription } = require("../agents/prompts/native/toolDescriptions/write");
|
|
@@ -47,9 +61,11 @@ const CORE_TOOL_NAMES = new Set([
|
|
|
47
61
|
"bash",
|
|
48
62
|
"artifact_read",
|
|
49
63
|
"plan_graph",
|
|
64
|
+
"task_run",
|
|
50
65
|
"ask_user",
|
|
51
66
|
]);
|
|
52
67
|
const EXECUTABLE_GRAPH_TOOLS = new Set(["read", "write", "edit", "bash", "artifact_read"]);
|
|
68
|
+
const CONTROL_PLANE_TOOLS = new Set(["plan_graph", "task_run"]);
|
|
53
69
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
54
70
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
55
71
|
const DEFAULT_KIMI_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
@@ -65,11 +81,9 @@ const DEFAULT_NATIVE_TIMEOUT_MS = 43200000; // 12 hours
|
|
|
65
81
|
// via UFOO_UCODE_MAX_TOKENS (positive integer).
|
|
66
82
|
const DEFAULT_OPENAI_MAX_TOKENS = 131072;
|
|
67
83
|
const DEFAULT_ANTHROPIC_MAX_TOKENS = 64000;
|
|
68
|
-
// Extended thinking
|
|
69
|
-
//
|
|
70
|
-
// UFOO_UCODE_THINKING_BUDGET_TOKENS overrides
|
|
71
|
-
// disables thinking (the payload then omits the field entirely).
|
|
72
|
-
const DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 10000;
|
|
84
|
+
// Extended thinking defaults live in thinkingLevels.js (medium = 10k).
|
|
85
|
+
// UFOO_UCODE_THINKING=off|low|medium|high|max selects a preset; numeric
|
|
86
|
+
// UFOO_UCODE_THINKING_BUDGET_TOKENS still overrides. 0 disables thinking.
|
|
73
87
|
// Prompt caching is GA on the current Messages API: cache_control blocks need
|
|
74
88
|
// no anthropic-beta header. Kept as a constant so the marker shape stays in
|
|
75
89
|
// one place (system block + last history message, 2 of the 4 allowed
|
|
@@ -103,14 +117,40 @@ function resolveMaxTokens(fallback) {
|
|
|
103
117
|
return normalizePositiveInt(process.env.UFOO_UCODE_MAX_TOKENS, fallback);
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
function resolveThinkingBudgetTokens() {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
function resolveThinkingBudgetTokens(options = {}) {
|
|
121
|
+
const { resolveThinkingFromEnvAndConfig } = require("./thinkingLevels");
|
|
122
|
+
const { loadGlobalUcodeConfig } = require("../config");
|
|
123
|
+
let configLevel = String(options.configLevel || "").trim();
|
|
124
|
+
if (!configLevel) {
|
|
125
|
+
try {
|
|
126
|
+
configLevel = String((loadGlobalUcodeConfig() || {}).ucodeThinking || "").trim();
|
|
127
|
+
} catch {
|
|
128
|
+
configLevel = "";
|
|
129
|
+
}
|
|
110
130
|
}
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
131
|
+
const resolved = resolveThinkingFromEnvAndConfig({
|
|
132
|
+
env: options.env || process.env,
|
|
133
|
+
configLevel,
|
|
134
|
+
});
|
|
135
|
+
return resolved.budgetTokens;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveReasoningEffort(options = {}) {
|
|
139
|
+
const { resolveThinkingFromEnvAndConfig } = require("./thinkingLevels");
|
|
140
|
+
const { loadGlobalUcodeConfig } = require("../config");
|
|
141
|
+
let configLevel = String(options.configLevel || "").trim();
|
|
142
|
+
if (!configLevel) {
|
|
143
|
+
try {
|
|
144
|
+
configLevel = String((loadGlobalUcodeConfig() || {}).ucodeThinking || "").trim();
|
|
145
|
+
} catch {
|
|
146
|
+
configLevel = "";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const resolved = resolveThinkingFromEnvAndConfig({
|
|
150
|
+
env: options.env || process.env,
|
|
151
|
+
configLevel,
|
|
152
|
+
});
|
|
153
|
+
return resolved.reasoningEffort || "";
|
|
114
154
|
}
|
|
115
155
|
|
|
116
156
|
function toUsageInt(value) {
|
|
@@ -433,12 +473,13 @@ function buildCoreToolSpecs() {
|
|
|
433
473
|
function: {
|
|
434
474
|
name: "plan_graph",
|
|
435
475
|
description: [
|
|
436
|
-
"Manage the persistent Plan Graph and
|
|
437
|
-
"
|
|
438
|
-
"
|
|
476
|
+
"Manage the persistent Plan Graph and graph-bound TaskRuns.",
|
|
477
|
+
"TaskRuns are orthogonal to Plan Mode; for a standalone TaskRun without a plan, use `task_run` instead.",
|
|
478
|
+
"Use create, patch, inspect, or cancel_graph for graph operations, and control for graph-bound TaskRun lifecycle.",
|
|
479
|
+
"`control.start_task` starts a graph `task_loop` asynchronously and returns immediately.",
|
|
439
480
|
"Use `inline_llm` for work handled by the current graph owner,",
|
|
440
481
|
"`expand` for tasks that must be lowered into child nodes,",
|
|
441
|
-
"and `task_loop` for asynchronous work in an independent TaskLoop.",
|
|
482
|
+
"and `task_loop` for asynchronous work in an independent TaskLoop attached to a plan node.",
|
|
442
483
|
"Do not call `plan_graph` together with data-plane tools in the same assistant turn.",
|
|
443
484
|
].join(" "),
|
|
444
485
|
parameters: {
|
|
@@ -504,6 +545,63 @@ function buildCoreToolSpecs() {
|
|
|
504
545
|
},
|
|
505
546
|
},
|
|
506
547
|
},
|
|
548
|
+
{
|
|
549
|
+
type: "function",
|
|
550
|
+
function: {
|
|
551
|
+
name: "task_run",
|
|
552
|
+
description: [
|
|
553
|
+
"Start, inspect, cancel, fail, or complete a TaskRun.",
|
|
554
|
+
"TaskRuns are orthogonal to Plan Mode and do not require a plan_graph.",
|
|
555
|
+
"Use operation=start with an objective for a standalone single-point TaskRun; it returns immediately.",
|
|
556
|
+
"On complex multi-goal work, decompose into concrete objectives and start one or more TaskRuns.",
|
|
557
|
+
"Use plan_graph control.start_task only when the TaskRun is attached to a plan_graph task_loop node.",
|
|
558
|
+
"Do not call `task_run` together with data-plane tools in the same assistant turn.",
|
|
559
|
+
].join(" "),
|
|
560
|
+
parameters: {
|
|
561
|
+
type: "object",
|
|
562
|
+
properties: {
|
|
563
|
+
operation: {
|
|
564
|
+
type: "string",
|
|
565
|
+
enum: ["start", "cancel", "fail", "complete", "inspect"],
|
|
566
|
+
description: [
|
|
567
|
+
"start creates a standalone TaskRun from objective;",
|
|
568
|
+
"cancel/fail/complete/inspect address an existing taskRunId",
|
|
569
|
+
"(cancel/fail may also use nodeId for graph-bound runs).",
|
|
570
|
+
].join(" "),
|
|
571
|
+
},
|
|
572
|
+
objective: {
|
|
573
|
+
type: "string",
|
|
574
|
+
description: "Required for start: concrete TaskRun objective.",
|
|
575
|
+
},
|
|
576
|
+
title: {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "Optional short title for start.",
|
|
579
|
+
},
|
|
580
|
+
taskRunId: {
|
|
581
|
+
type: "string",
|
|
582
|
+
description: "TaskRun id for cancel, fail, complete, or inspect.",
|
|
583
|
+
},
|
|
584
|
+
nodeId: {
|
|
585
|
+
type: "string",
|
|
586
|
+
description: "Optional graph node id for cancel/fail of a graph-bound TaskRun.",
|
|
587
|
+
},
|
|
588
|
+
reason: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "Optional reason for cancel or fail.",
|
|
591
|
+
},
|
|
592
|
+
result: {
|
|
593
|
+
type: "object",
|
|
594
|
+
description: "Optional result payload for complete (TaskLoop owner).",
|
|
595
|
+
},
|
|
596
|
+
commandId: {
|
|
597
|
+
type: "string",
|
|
598
|
+
description: "Optional idempotency key for explicit replay.",
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
required: ["operation"],
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
},
|
|
507
605
|
{
|
|
508
606
|
type: "function",
|
|
509
607
|
function: {
|
|
@@ -759,6 +857,51 @@ function runCoreTool({
|
|
|
759
857
|
};
|
|
760
858
|
}
|
|
761
859
|
|
|
860
|
+
if (normalizedTool === "task_run") {
|
|
861
|
+
const state = executionState && typeof executionState === "object"
|
|
862
|
+
? executionState
|
|
863
|
+
: emptyExecutionState();
|
|
864
|
+
const result = runTaskRunTool(safeArgs, {
|
|
865
|
+
executionState: state,
|
|
866
|
+
runTool: ({ node, args: nestedArgs, tool: nestedTool, stepId }) => {
|
|
867
|
+
const nested = runCoreTool({
|
|
868
|
+
tool: nestedTool,
|
|
869
|
+
args: nestedArgs,
|
|
870
|
+
workspaceRoot,
|
|
871
|
+
onToolEvent,
|
|
872
|
+
sessionId,
|
|
873
|
+
onArtifactPersisted,
|
|
874
|
+
executionState: state,
|
|
875
|
+
origin: {
|
|
876
|
+
kind: "task_run",
|
|
877
|
+
taskRunId: String(safeArgs.taskRunId || ""),
|
|
878
|
+
nodeId: stepId || (node && node.id) || "",
|
|
879
|
+
attempt: Number(node && node.attempt) || 0,
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
return nested;
|
|
883
|
+
},
|
|
884
|
+
});
|
|
885
|
+
const ok = result.ok !== false && result.status !== "rejected";
|
|
886
|
+
emitToolEvent(onToolEvent, {
|
|
887
|
+
tool: "task_run",
|
|
888
|
+
phase: ok ? "end" : "error",
|
|
889
|
+
args: safeArgs,
|
|
890
|
+
result,
|
|
891
|
+
error: ok
|
|
892
|
+
? ""
|
|
893
|
+
: (Array.isArray(result.errors)
|
|
894
|
+
? result.errors.map((e) => e.message || e.code).join("; ")
|
|
895
|
+
: (result.error || "task_run rejected")),
|
|
896
|
+
origin,
|
|
897
|
+
});
|
|
898
|
+
return {
|
|
899
|
+
...result,
|
|
900
|
+
ok,
|
|
901
|
+
executionState: result.executionState || state,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
762
905
|
if (normalizedTool === "ask_user") {
|
|
763
906
|
const state = executionState && typeof executionState === "object"
|
|
764
907
|
? executionState
|
|
@@ -957,6 +1100,12 @@ async function runOpenAiLikeTurn({
|
|
|
957
1100
|
// Kimi k3 rejects any temperature other than 1.
|
|
958
1101
|
temperature: normalizeProvider(provider) === "kimi" ? 1 : 0,
|
|
959
1102
|
};
|
|
1103
|
+
const reasoningEffort = resolveReasoningEffort();
|
|
1104
|
+
if (reasoningEffort) {
|
|
1105
|
+
// OpenAI-compatible gateways that support reasoning models accept this;
|
|
1106
|
+
// unknown fields are typically ignored by plain chat models.
|
|
1107
|
+
payload.reasoning_effort = reasoningEffort;
|
|
1108
|
+
}
|
|
960
1109
|
|
|
961
1110
|
const headers = {
|
|
962
1111
|
"content-type": "application/json",
|
|
@@ -1460,140 +1609,76 @@ async function runAnthropicTurn({
|
|
|
1460
1609
|
});
|
|
1461
1610
|
}
|
|
1462
1611
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1612
|
+
const {
|
|
1613
|
+
createOpenAiChatTransport,
|
|
1614
|
+
createAnthropicMessagesTransport,
|
|
1615
|
+
} = require("./providers");
|
|
1616
|
+
|
|
1617
|
+
// Transport descriptors: wire-format only. Plan Mode / leases / policy live in the loop.
|
|
1467
1618
|
const TRANSPORTS = {
|
|
1468
|
-
"openai-chat": {
|
|
1619
|
+
"openai-chat": createOpenAiChatTransport({
|
|
1469
1620
|
resolveUrl: resolveCompletionUrl,
|
|
1470
|
-
prepareMessages({ messages, systemPrompt, prompt }) {
|
|
1471
|
-
const systemText = String(systemPrompt || "").trim();
|
|
1472
|
-
const hasSystem = messages.some((entry) => String(entry.role || "").trim() === "system");
|
|
1473
|
-
if (systemText && !hasSystem) {
|
|
1474
|
-
messages.unshift({ role: "system", content: systemText });
|
|
1475
|
-
}
|
|
1476
|
-
messages.push({ role: "user", content: String(prompt || "") });
|
|
1477
|
-
},
|
|
1478
1621
|
runTurn: runOpenAiLikeTurn,
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
}
|
|
1492
|
-
},
|
|
1493
|
-
prepareToolCalls({ messages, toolCalls }) {
|
|
1494
|
-
const assistantToolCalls = [];
|
|
1495
|
-
for (const call of toolCalls) {
|
|
1496
|
-
const callId = String(call.id || `call_${randomUUID()}`);
|
|
1497
|
-
const name = normalizeToolName(call.function.name || "");
|
|
1498
|
-
const args = normalizeToolCallArgs(call.function.arguments || "");
|
|
1499
|
-
|
|
1500
|
-
assistantToolCalls.push({
|
|
1501
|
-
id: callId,
|
|
1502
|
-
type: "function",
|
|
1503
|
-
function: {
|
|
1504
|
-
name: name || String(call.function.name || ""),
|
|
1505
|
-
arguments: toJsonString(args),
|
|
1506
|
-
},
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1622
|
+
normalizeToolName,
|
|
1623
|
+
normalizeToolCallArgs,
|
|
1624
|
+
toJsonString,
|
|
1625
|
+
clipText,
|
|
1626
|
+
}),
|
|
1627
|
+
"anthropic-messages": createAnthropicMessagesTransport({
|
|
1628
|
+
resolveUrl: resolveAnthropicMessagesUrl,
|
|
1629
|
+
runTurn: runAnthropicTurn,
|
|
1630
|
+
toJsonString,
|
|
1631
|
+
clipText,
|
|
1632
|
+
}),
|
|
1633
|
+
};
|
|
1509
1634
|
|
|
1510
|
-
|
|
1635
|
+
function pendingToolCallId(pending = null) {
|
|
1636
|
+
if (!pending || !pending.source) return "";
|
|
1637
|
+
return String(pending.source.id || "").trim();
|
|
1638
|
+
}
|
|
1511
1639
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1640
|
+
function shadowDeclarePendingCalls(ledger, pendingCalls = []) {
|
|
1641
|
+
const entries = (Array.isArray(pendingCalls) ? pendingCalls : []).map((pending) => ({
|
|
1642
|
+
callId: pendingToolCallId(pending) || `call_${randomUUID()}`,
|
|
1643
|
+
name: String(pending && pending.name || "").trim().toLowerCase(),
|
|
1644
|
+
args: pending && pending.args != null ? pending.args : {},
|
|
1645
|
+
}));
|
|
1646
|
+
// Keep source ids aligned when we had to synthesize.
|
|
1647
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
1648
|
+
const pending = pendingCalls[i];
|
|
1649
|
+
if (pending && pending.source && !pending.source.id) {
|
|
1650
|
+
pending.source.id = entries[i].callId;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return declareCalls(ledger, entries);
|
|
1654
|
+
}
|
|
1517
1655
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
tool_call_id: call.source.id,
|
|
1528
|
-
content: clipText(toJsonString(toolResult), 12000),
|
|
1529
|
-
});
|
|
1530
|
-
},
|
|
1531
|
-
},
|
|
1532
|
-
"anthropic-messages": {
|
|
1533
|
-
resolveUrl: resolveAnthropicMessagesUrl,
|
|
1534
|
-
prepareMessages({ messages, prompt }) {
|
|
1535
|
-
messages.push({
|
|
1536
|
-
role: "user",
|
|
1537
|
-
content: String(prompt || ""),
|
|
1538
|
-
});
|
|
1539
|
-
},
|
|
1540
|
-
runTurn: runAnthropicTurn,
|
|
1541
|
-
getToolCalls(turnResult) {
|
|
1542
|
-
return Array.isArray(turnResult.toolCalls) ? turnResult.toolCalls : [];
|
|
1543
|
-
},
|
|
1544
|
-
appendFinalAssistantMessage({ messages, turnResult }) {
|
|
1545
|
-
const assistantContent = Array.isArray(turnResult.assistantContent)
|
|
1546
|
-
? turnResult.assistantContent
|
|
1547
|
-
: [];
|
|
1548
|
-
if (assistantContent.length > 0) {
|
|
1549
|
-
messages.push({
|
|
1550
|
-
role: "assistant",
|
|
1551
|
-
content: assistantContent,
|
|
1552
|
-
});
|
|
1553
|
-
} else if (String(turnResult.text || "").trim()) {
|
|
1554
|
-
messages.push({
|
|
1555
|
-
role: "assistant",
|
|
1556
|
-
content: [
|
|
1557
|
-
{
|
|
1558
|
-
type: "text",
|
|
1559
|
-
text: String(turnResult.text || ""),
|
|
1560
|
-
},
|
|
1561
|
-
],
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
},
|
|
1565
|
-
prepareToolCalls({ messages, turnResult, toolCalls }) {
|
|
1566
|
-
const assistantContent = Array.isArray(turnResult.assistantContent)
|
|
1567
|
-
? turnResult.assistantContent
|
|
1568
|
-
: [];
|
|
1569
|
-
|
|
1570
|
-
messages.push({
|
|
1571
|
-
role: "assistant",
|
|
1572
|
-
content: assistantContent,
|
|
1573
|
-
});
|
|
1656
|
+
function shadowResolvePending(ledger, pending, toolResult) {
|
|
1657
|
+
if (!ledger) return;
|
|
1658
|
+
const callId = pendingToolCallId(pending);
|
|
1659
|
+
if (!callId) return;
|
|
1660
|
+
resolveCall(ledger, callId, {
|
|
1661
|
+
result: toolResult,
|
|
1662
|
+
isError: Boolean(!toolResult || toolResult.ok === false),
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1574
1665
|
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
role: "user",
|
|
1592
|
-
content: collected,
|
|
1593
|
-
});
|
|
1594
|
-
},
|
|
1595
|
-
},
|
|
1596
|
-
};
|
|
1666
|
+
function pendingByIdMap(pendingCalls = []) {
|
|
1667
|
+
const map = Object.create(null);
|
|
1668
|
+
for (const pending of pendingCalls) {
|
|
1669
|
+
const id = pendingToolCallId(pending);
|
|
1670
|
+
if (id) map[id] = pending;
|
|
1671
|
+
}
|
|
1672
|
+
return map;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
function flushLedgerToolResults(ledger, transport, messages, pendingCalls) {
|
|
1676
|
+
return materializeResolvedToolResults(ledger, {
|
|
1677
|
+
transport,
|
|
1678
|
+
messages,
|
|
1679
|
+
pendingById: pendingByIdMap(pendingCalls),
|
|
1680
|
+
});
|
|
1681
|
+
}
|
|
1597
1682
|
|
|
1598
1683
|
async function runNativeLoop({
|
|
1599
1684
|
transport,
|
|
@@ -1644,6 +1729,14 @@ async function runNativeLoop({
|
|
|
1644
1729
|
ensurePendingUserPrompts(executionState);
|
|
1645
1730
|
const toolBudget = resolveNativeToolBudget();
|
|
1646
1731
|
const usage = createUsageTotals();
|
|
1732
|
+
// Shadow Tool Call Ledger (R1). Observes declare/defer/resolve; does not
|
|
1733
|
+
// materialize Provider messages yet. STRICT via UFOO_UCODE_PROTOCOL_STRICT=1.
|
|
1734
|
+
let activeLedger = null;
|
|
1735
|
+
let lastProtocolLedger = null;
|
|
1736
|
+
|
|
1737
|
+
if (resume) {
|
|
1738
|
+
await withFaultPoint("before_provider_resume", () => {});
|
|
1739
|
+
}
|
|
1647
1740
|
|
|
1648
1741
|
function injectPendingUserReminders() {
|
|
1649
1742
|
const nudges = drainUserPrompts(executionState);
|
|
@@ -1661,6 +1754,10 @@ async function runNativeLoop({
|
|
|
1661
1754
|
|
|
1662
1755
|
injectPendingUserReminders();
|
|
1663
1756
|
|
|
1757
|
+
if (activeLedger) {
|
|
1758
|
+
runProviderTurnGate(activeLedger);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1664
1761
|
const turnResult = await transport.runTurn({
|
|
1665
1762
|
url: requestUrl,
|
|
1666
1763
|
apiKey,
|
|
@@ -1730,6 +1827,7 @@ async function runNativeLoop({
|
|
|
1730
1827
|
messages,
|
|
1731
1828
|
usage,
|
|
1732
1829
|
executionState,
|
|
1830
|
+
protocolLedger: lastProtocolLedger || snapshotLedger(activeLedger),
|
|
1733
1831
|
};
|
|
1734
1832
|
}
|
|
1735
1833
|
|
|
@@ -1742,61 +1840,55 @@ async function runNativeLoop({
|
|
|
1742
1840
|
messages,
|
|
1743
1841
|
usage,
|
|
1744
1842
|
executionState,
|
|
1843
|
+
protocolLedger: lastProtocolLedger || snapshotLedger(activeLedger),
|
|
1745
1844
|
};
|
|
1746
1845
|
}
|
|
1747
1846
|
|
|
1847
|
+
activeLedger = createToolCallLedger({ provider, sessionId });
|
|
1848
|
+
shadowDeclarePendingCalls(activeLedger, pendingCalls);
|
|
1849
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1850
|
+
await withFaultPoint("after_prepare_tool_calls", () => {});
|
|
1851
|
+
await withFaultPoint("before_tool_exec", () => {});
|
|
1852
|
+
|
|
1748
1853
|
const callNames = pendingCalls.map((call) => String(call.name || "").trim().toLowerCase());
|
|
1749
|
-
const
|
|
1854
|
+
const hasControlPlane = callNames.some((name) => CONTROL_PLANE_TOOLS.has(name));
|
|
1750
1855
|
const hasAskUser = callNames.includes("ask_user");
|
|
1751
1856
|
const hasDataTool = callNames.some((name) => EXECUTABLE_GRAPH_TOOLS.has(name));
|
|
1752
|
-
if (
|
|
1857
|
+
if (hasControlPlane && hasDataTool) {
|
|
1753
1858
|
// prepareToolCalls already appended the assistant tool_calls / tool_use
|
|
1754
|
-
// message; every declared call must get a contiguous tool result.
|
|
1755
|
-
const
|
|
1859
|
+
// message; every declared call must get a contiguous tool result via ledger.
|
|
1860
|
+
const rejected = {
|
|
1861
|
+
ok: false,
|
|
1862
|
+
status: "rejected",
|
|
1863
|
+
error: "Do not mix plan_graph/task_run with data-plane tools in the same turn",
|
|
1864
|
+
code: "MIXED_PLAN_AND_DATA_TOOLS",
|
|
1865
|
+
};
|
|
1756
1866
|
for (const pending of pendingCalls) {
|
|
1757
|
-
|
|
1758
|
-
messages,
|
|
1759
|
-
collected: collectedResults,
|
|
1760
|
-
call: pending,
|
|
1761
|
-
toolResult: {
|
|
1762
|
-
ok: false,
|
|
1763
|
-
status: "rejected",
|
|
1764
|
-
error: "Do not mix plan_graph with data-plane tools in the same turn",
|
|
1765
|
-
code: "MIXED_PLAN_AND_DATA_TOOLS",
|
|
1766
|
-
},
|
|
1767
|
-
});
|
|
1867
|
+
shadowResolvePending(activeLedger, pending, rejected);
|
|
1768
1868
|
toolCallsExecuted += 1;
|
|
1769
1869
|
toolErrors += 1;
|
|
1770
1870
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
}
|
|
1871
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
1872
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1774
1873
|
continue;
|
|
1775
1874
|
}
|
|
1776
1875
|
if (hasAskUser && pendingCalls.length > 1) {
|
|
1777
|
-
const
|
|
1876
|
+
const rejected = {
|
|
1877
|
+
ok: false,
|
|
1878
|
+
status: "rejected",
|
|
1879
|
+
error: "ask_user must be the only tool call in the turn",
|
|
1880
|
+
code: "ASK_USER_MUST_BE_ALONE",
|
|
1881
|
+
};
|
|
1778
1882
|
for (const pending of pendingCalls) {
|
|
1779
|
-
|
|
1780
|
-
messages,
|
|
1781
|
-
collected: collectedResults,
|
|
1782
|
-
call: pending,
|
|
1783
|
-
toolResult: {
|
|
1784
|
-
ok: false,
|
|
1785
|
-
status: "rejected",
|
|
1786
|
-
error: "ask_user must be the only tool call in the turn",
|
|
1787
|
-
code: "ASK_USER_MUST_BE_ALONE",
|
|
1788
|
-
},
|
|
1789
|
-
});
|
|
1883
|
+
shadowResolvePending(activeLedger, pending, rejected);
|
|
1790
1884
|
toolCallsExecuted += 1;
|
|
1791
1885
|
toolErrors += 1;
|
|
1792
1886
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
}
|
|
1887
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
1888
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1796
1889
|
continue;
|
|
1797
1890
|
}
|
|
1798
1891
|
|
|
1799
|
-
const collectedResults = [];
|
|
1800
1892
|
let deferredAskUser = null;
|
|
1801
1893
|
for (const pending of pendingCalls) {
|
|
1802
1894
|
const pendingName = String(pending.name || "").trim().toLowerCase();
|
|
@@ -1814,12 +1906,7 @@ async function runNativeLoop({
|
|
|
1814
1906
|
};
|
|
1815
1907
|
toolCallsExecuted += 1;
|
|
1816
1908
|
toolErrors += 1;
|
|
1817
|
-
|
|
1818
|
-
messages,
|
|
1819
|
-
collected: collectedResults,
|
|
1820
|
-
call: pending,
|
|
1821
|
-
toolResult: blocked,
|
|
1822
|
-
});
|
|
1909
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1823
1910
|
continue;
|
|
1824
1911
|
}
|
|
1825
1912
|
if (planModeBlocksDirectTool(pendingName, executionState)) {
|
|
@@ -1833,12 +1920,7 @@ async function runNativeLoop({
|
|
|
1833
1920
|
};
|
|
1834
1921
|
toolCallsExecuted += 1;
|
|
1835
1922
|
toolErrors += 1;
|
|
1836
|
-
|
|
1837
|
-
messages,
|
|
1838
|
-
collected: collectedResults,
|
|
1839
|
-
call: pending,
|
|
1840
|
-
toolResult: blocked,
|
|
1841
|
-
});
|
|
1923
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1842
1924
|
continue;
|
|
1843
1925
|
}
|
|
1844
1926
|
const leaseCheck = checkWriteAllowed(executionState, {
|
|
@@ -1858,12 +1940,7 @@ async function runNativeLoop({
|
|
|
1858
1940
|
};
|
|
1859
1941
|
toolCallsExecuted += 1;
|
|
1860
1942
|
toolErrors += 1;
|
|
1861
|
-
|
|
1862
|
-
messages,
|
|
1863
|
-
collected: collectedResults,
|
|
1864
|
-
call: pending,
|
|
1865
|
-
toolResult: blocked,
|
|
1866
|
-
});
|
|
1943
|
+
shadowResolvePending(activeLedger, pending, blocked);
|
|
1867
1944
|
continue;
|
|
1868
1945
|
}
|
|
1869
1946
|
|
|
@@ -1882,6 +1959,7 @@ async function runNativeLoop({
|
|
|
1882
1959
|
}
|
|
1883
1960
|
: null;
|
|
1884
1961
|
|
|
1962
|
+
markExecuting(activeLedger, pendingToolCallId(pending));
|
|
1885
1963
|
const toolResult = runCoreTool({
|
|
1886
1964
|
tool: pending.name,
|
|
1887
1965
|
args: pending.args,
|
|
@@ -1911,6 +1989,7 @@ async function runNativeLoop({
|
|
|
1911
1989
|
transport: provider === "anthropic" ? "anthropic-messages" : "openai-chat",
|
|
1912
1990
|
};
|
|
1913
1991
|
}
|
|
1992
|
+
deferCall(activeLedger, pendingToolCallId(pending), { reason: "ask_user" });
|
|
1914
1993
|
deferredAskUser = { call: pending, interactionId: toolResult.interactionId || "" };
|
|
1915
1994
|
continue;
|
|
1916
1995
|
}
|
|
@@ -1923,17 +2002,11 @@ async function runNativeLoop({
|
|
|
1923
2002
|
lastTool: pending.name,
|
|
1924
2003
|
lastError: toolResult && toolResult.error ? String(toolResult.error) : "",
|
|
1925
2004
|
});
|
|
1926
|
-
|
|
1927
|
-
messages,
|
|
1928
|
-
collected: collectedResults,
|
|
1929
|
-
call: pending,
|
|
1930
|
-
toolResult,
|
|
1931
|
-
});
|
|
2005
|
+
shadowResolvePending(activeLedger, pending, toolResult);
|
|
1932
2006
|
}
|
|
1933
2007
|
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
}
|
|
2008
|
+
flushLedgerToolResults(activeLedger, transport, messages, pendingCalls);
|
|
2009
|
+
lastProtocolLedger = snapshotLedger(activeLedger);
|
|
1937
2010
|
|
|
1938
2011
|
if (deferredAskUser) {
|
|
1939
2012
|
return {
|
|
@@ -1945,6 +2018,7 @@ async function runNativeLoop({
|
|
|
1945
2018
|
executionState,
|
|
1946
2019
|
waitingUserInteraction: true,
|
|
1947
2020
|
interactionId: deferredAskUser.interactionId || "",
|
|
2021
|
+
protocolLedger: lastProtocolLedger,
|
|
1948
2022
|
};
|
|
1949
2023
|
}
|
|
1950
2024
|
|
|
@@ -1959,33 +2033,30 @@ async function runNativeLoop({
|
|
|
1959
2033
|
executionState,
|
|
1960
2034
|
waitingUserInteraction: true,
|
|
1961
2035
|
interactionId: (getPendingUserInteraction(executionState) || {}).id || "",
|
|
2036
|
+
protocolLedger: lastProtocolLedger,
|
|
1962
2037
|
};
|
|
1963
2038
|
}
|
|
1964
2039
|
}
|
|
1965
2040
|
}
|
|
1966
2041
|
|
|
1967
|
-
function appendAnswerToolResult(messages = [], resume = null, answer = {}) {
|
|
1968
|
-
const
|
|
1969
|
-
if (!
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
messages.push({
|
|
1984
|
-
role: "tool",
|
|
1985
|
-
tool_call_id: String(call.source.id || resume.toolCallId || ""),
|
|
1986
|
-
content,
|
|
1987
|
-
});
|
|
2042
|
+
function appendAnswerToolResult(messages = [], resume = null, answer = {}, options = {}) {
|
|
2043
|
+
const materialized = materializeAnswerToolResult(messages, resume, answer);
|
|
2044
|
+
if (!materialized.ok) return materialized;
|
|
2045
|
+
const ledger = options && options.ledger ? options.ledger : null;
|
|
2046
|
+
if (ledger) {
|
|
2047
|
+
const call = resume && resume.call ? resume.call : null;
|
|
2048
|
+
const callId = String(
|
|
2049
|
+
(call && call.source && call.source.id) || (resume && resume.toolCallId) || ""
|
|
2050
|
+
).trim();
|
|
2051
|
+
if (callId) {
|
|
2052
|
+
resolveCall(ledger, callId, {
|
|
2053
|
+
result: answer,
|
|
2054
|
+
isError: false,
|
|
2055
|
+
allowFromDeferred: true,
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
1988
2058
|
}
|
|
2059
|
+
checkFaultPoint("after_answer_commit");
|
|
1989
2060
|
return { ok: true };
|
|
1990
2061
|
}
|
|
1991
2062
|
|
|
@@ -2118,6 +2189,7 @@ async function runNativeAgentTask({
|
|
|
2118
2189
|
streamed: Boolean(runResult.streamed) && typeof onStreamDelta === "function",
|
|
2119
2190
|
waitingUserInteraction: Boolean(runResult.waitingUserInteraction),
|
|
2120
2191
|
interactionId: runResult.interactionId || "",
|
|
2192
|
+
protocolLedger: runResult.protocolLedger || null,
|
|
2121
2193
|
};
|
|
2122
2194
|
} catch (err) {
|
|
2123
2195
|
const message = err && err.message ? err.message : "native runner failed";
|
|
@@ -2142,6 +2214,8 @@ module.exports = {
|
|
|
2142
2214
|
resolveCompletionUrl,
|
|
2143
2215
|
resolveAnthropicMessagesUrl,
|
|
2144
2216
|
resolveTransport,
|
|
2217
|
+
resolveThinkingBudgetTokens,
|
|
2218
|
+
resolveReasoningEffort,
|
|
2145
2219
|
buildCoreToolSpecs,
|
|
2146
2220
|
buildAnthropicToolSpecs,
|
|
2147
2221
|
};
|