u-foo 2.5.15 → 3.0.0
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 +333 -243
- package/src/code/commands.js +16 -0
- package/src/code/context/assembler.js +18 -13
- package/src/code/context/executionSegment.js +97 -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 +398 -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 +2 -0
- package/src/code/nativeRunner.js +518 -37
- package/src/code/repl.js +160 -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 +383 -0
- package/src/code/runtime/taskRun.js +187 -0
- package/src/code/runtime/toolProvenance.js +70 -0
- package/src/code/runtime/workspaceLease.js +208 -0
- package/src/code/sessionStore.js +0 -10
- package/src/code/skills/injection.js +1 -0
- package/src/code/taskDecomposer.js +32 -8
- 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 +285 -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,43 @@ 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");
|
|
22
37
|
const { stableStringify } = require("./context/stableJson");
|
|
23
38
|
const { getReadToolDescription } = require("../agents/prompts/native/toolDescriptions/read");
|
|
24
39
|
const { getWriteToolDescription } = require("../agents/prompts/native/toolDescriptions/write");
|
|
25
40
|
const { getEditToolDescription } = require("../agents/prompts/native/toolDescriptions/edit");
|
|
26
41
|
const { getBashToolDescription } = require("../agents/prompts/native/toolDescriptions/bash");
|
|
27
42
|
|
|
28
|
-
const CORE_TOOL_NAMES = new Set([
|
|
43
|
+
const CORE_TOOL_NAMES = new Set([
|
|
44
|
+
"read",
|
|
45
|
+
"write",
|
|
46
|
+
"edit",
|
|
47
|
+
"bash",
|
|
48
|
+
"artifact_read",
|
|
49
|
+
"plan_graph",
|
|
50
|
+
"ask_user",
|
|
51
|
+
]);
|
|
52
|
+
const EXECUTABLE_GRAPH_TOOLS = new Set(["read", "write", "edit", "bash", "artifact_read"]);
|
|
29
53
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
30
54
|
const DEFAULT_ANTHROPIC_BASE_URL = "https://api.anthropic.com/v1";
|
|
31
55
|
const DEFAULT_KIMI_BASE_URL = "https://api.kimi.com/coding/v1";
|
|
@@ -184,7 +208,9 @@ function createGuards({ signal = null, timeoutMs = DEFAULT_NATIVE_TIMEOUT_MS } =
|
|
|
184
208
|
function emitToolEvent(callback, event = {}) {
|
|
185
209
|
if (typeof callback !== "function") return;
|
|
186
210
|
try {
|
|
187
|
-
|
|
211
|
+
const payload = event && typeof event === "object" ? { ...event } : {};
|
|
212
|
+
if (payload.origin == null) delete payload.origin;
|
|
213
|
+
callback(payload);
|
|
188
214
|
} catch {
|
|
189
215
|
// ignore callback failures
|
|
190
216
|
}
|
|
@@ -383,7 +409,11 @@ function buildCoreToolSpecs() {
|
|
|
383
409
|
type: "function",
|
|
384
410
|
function: {
|
|
385
411
|
name: "artifact_read",
|
|
386
|
-
description:
|
|
412
|
+
description: [
|
|
413
|
+
"Read previously stored tool output by artifactId.",
|
|
414
|
+
"This does not read workspace files; use `read` for repository paths.",
|
|
415
|
+
"Optionally read a slice with startLine/endLine, maxChars, or tailLines.",
|
|
416
|
+
].join(" "),
|
|
387
417
|
parameters: {
|
|
388
418
|
type: "object",
|
|
389
419
|
properties: {
|
|
@@ -398,6 +428,128 @@ function buildCoreToolSpecs() {
|
|
|
398
428
|
},
|
|
399
429
|
},
|
|
400
430
|
},
|
|
431
|
+
{
|
|
432
|
+
type: "function",
|
|
433
|
+
function: {
|
|
434
|
+
name: "plan_graph",
|
|
435
|
+
description: [
|
|
436
|
+
"Manage the persistent Plan Graph and asynchronous TaskRuns.",
|
|
437
|
+
"Use create, patch, inspect, or cancel_graph for graph operations, and control for TaskRun lifecycle.",
|
|
438
|
+
"`control.start_task` starts a `task_loop` asynchronously and returns immediately.",
|
|
439
|
+
"Use `inline_llm` for work handled by the current graph owner,",
|
|
440
|
+
"`expand` for tasks that must be lowered into child nodes,",
|
|
441
|
+
"and `task_loop` for asynchronous work in an independent TaskLoop.",
|
|
442
|
+
"Do not call `plan_graph` together with data-plane tools in the same assistant turn.",
|
|
443
|
+
].join(" "),
|
|
444
|
+
parameters: {
|
|
445
|
+
type: "object",
|
|
446
|
+
properties: {
|
|
447
|
+
operation: {
|
|
448
|
+
type: "string",
|
|
449
|
+
enum: [
|
|
450
|
+
"create",
|
|
451
|
+
"patch",
|
|
452
|
+
"inspect",
|
|
453
|
+
"clear",
|
|
454
|
+
"cancel_graph",
|
|
455
|
+
"control",
|
|
456
|
+
],
|
|
457
|
+
description: [
|
|
458
|
+
"create/patch/inspect/cancel_graph mutate or inspect the graph spec;",
|
|
459
|
+
"control runs TaskRun lifecycle and node status actions.",
|
|
460
|
+
].join(" "),
|
|
461
|
+
},
|
|
462
|
+
graph: {
|
|
463
|
+
type: "object",
|
|
464
|
+
description: "Full graph for create (objective + nodes). group is input sugar only.",
|
|
465
|
+
},
|
|
466
|
+
operations: {
|
|
467
|
+
type: "array",
|
|
468
|
+
description: [
|
|
469
|
+
"Patch ops only: add_node, expand_node, add_dependency, remove_dependency.",
|
|
470
|
+
"Status actions (complete_task, skip_node, cancel_subtree) belong under control.actions.",
|
|
471
|
+
].join(" "),
|
|
472
|
+
items: { type: "object" },
|
|
473
|
+
},
|
|
474
|
+
actions: {
|
|
475
|
+
type: "array",
|
|
476
|
+
description: [
|
|
477
|
+
"Control actions: start_task, cancel_task, fail_task, complete_task, skip_node, cancel_subtree.",
|
|
478
|
+
"complete_task with taskRunId finishes a TaskLoop TaskRun;",
|
|
479
|
+
"complete_task with nodeId finishes a waiting_llm inline task owned by the graph owner.",
|
|
480
|
+
].join(" "),
|
|
481
|
+
items: { type: "object" },
|
|
482
|
+
},
|
|
483
|
+
reason: {
|
|
484
|
+
type: "string",
|
|
485
|
+
description: "Optional reason for cancel_graph or fail/cancel task.",
|
|
486
|
+
},
|
|
487
|
+
commandId: {
|
|
488
|
+
type: "string",
|
|
489
|
+
description: [
|
|
490
|
+
"Optional idempotency key for explicit replay.",
|
|
491
|
+
"When omitted, the Runtime should derive one from the tool invocation when available.",
|
|
492
|
+
].join(" "),
|
|
493
|
+
},
|
|
494
|
+
expectedSpecRevision: {
|
|
495
|
+
type: "integer",
|
|
496
|
+
description: "Optional optimistic concurrency token for patch.",
|
|
497
|
+
},
|
|
498
|
+
graphId: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Optional graph id check for patch/control.",
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
required: ["operation"],
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
type: "function",
|
|
509
|
+
function: {
|
|
510
|
+
name: "ask_user",
|
|
511
|
+
description: [
|
|
512
|
+
"Ask the user for input and pause the current Agent loop until the reply arrives.",
|
|
513
|
+
"Use only when user input is required to proceed, not for routine updates or decisions the agent can safely make.",
|
|
514
|
+
"`kind=approval` requests yes/no confirmation; `kind=choice` presents the supplied options; `kind=chat` requests free text.",
|
|
515
|
+
"This must be the only tool call in the turn.",
|
|
516
|
+
"The reply is returned only as this tool result, not as a separate user message or pending user prompt.",
|
|
517
|
+
"After the tool returns, continue from the answer and do not ask the same question again.",
|
|
518
|
+
"Running TaskRuns are not paused automatically.",
|
|
519
|
+
].join(" "),
|
|
520
|
+
parameters: {
|
|
521
|
+
type: "object",
|
|
522
|
+
properties: {
|
|
523
|
+
kind: {
|
|
524
|
+
type: "string",
|
|
525
|
+
enum: ["approval", "choice", "chat"],
|
|
526
|
+
description: "Interaction type.",
|
|
527
|
+
},
|
|
528
|
+
prompt: {
|
|
529
|
+
type: "string",
|
|
530
|
+
description: "Question shown to the user.",
|
|
531
|
+
},
|
|
532
|
+
options: {
|
|
533
|
+
type: "array",
|
|
534
|
+
description: "For choice: option labels (or {key,label} objects). Ignored for chat.",
|
|
535
|
+
items: {
|
|
536
|
+
oneOf: [
|
|
537
|
+
{ type: "string" },
|
|
538
|
+
{
|
|
539
|
+
type: "object",
|
|
540
|
+
properties: {
|
|
541
|
+
key: { type: "string" },
|
|
542
|
+
label: { type: "string" },
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
required: ["kind", "prompt"],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
401
553
|
];
|
|
402
554
|
}
|
|
403
555
|
|
|
@@ -526,8 +678,10 @@ function runCoreTool({
|
|
|
526
678
|
workspaceRoot = process.cwd(),
|
|
527
679
|
onToolEvent = null,
|
|
528
680
|
sessionId = "",
|
|
529
|
-
contextV2 = false,
|
|
530
681
|
onArtifactPersisted = null,
|
|
682
|
+
executionState = null,
|
|
683
|
+
origin = null,
|
|
684
|
+
resume = null,
|
|
531
685
|
} = {}) {
|
|
532
686
|
const normalizedTool = normalizeToolName(tool);
|
|
533
687
|
if (!normalizedTool) {
|
|
@@ -536,6 +690,7 @@ function runCoreTool({
|
|
|
536
690
|
phase: "error",
|
|
537
691
|
args: args && typeof args === "object" ? { ...args } : {},
|
|
538
692
|
error: `unsupported tool: ${tool}`,
|
|
693
|
+
origin,
|
|
539
694
|
});
|
|
540
695
|
return {
|
|
541
696
|
ok: false,
|
|
@@ -552,8 +707,86 @@ function runCoreTool({
|
|
|
552
707
|
phase: "start",
|
|
553
708
|
args: safeArgs,
|
|
554
709
|
error: "",
|
|
710
|
+
origin,
|
|
555
711
|
});
|
|
556
712
|
|
|
713
|
+
if (normalizedTool === "plan_graph") {
|
|
714
|
+
const state = executionState && typeof executionState === "object"
|
|
715
|
+
? executionState
|
|
716
|
+
: emptyExecutionState();
|
|
717
|
+
const result = runPlanGraphCommand(safeArgs, {
|
|
718
|
+
executionState: state,
|
|
719
|
+
autoAdvance: true,
|
|
720
|
+
parallel: true,
|
|
721
|
+
runTool: ({ node, args: nestedArgs, tool: nestedTool, stepId }) => {
|
|
722
|
+
const nested = runCoreTool({
|
|
723
|
+
tool: nestedTool,
|
|
724
|
+
args: nestedArgs,
|
|
725
|
+
workspaceRoot,
|
|
726
|
+
onToolEvent,
|
|
727
|
+
sessionId,
|
|
728
|
+
onArtifactPersisted,
|
|
729
|
+
executionState: state,
|
|
730
|
+
origin: {
|
|
731
|
+
kind: "plan_graph",
|
|
732
|
+
graphId: String(state.planGraph && state.planGraph.graphId || ""),
|
|
733
|
+
graphRevision: Number(state.planGraph && state.planGraph.specRevision) || 0,
|
|
734
|
+
commandRevision: Number(state.planGraph && state.planGraph.specRevision) || 0,
|
|
735
|
+
nodeId: stepId || (node && node.id) || "",
|
|
736
|
+
attempt: Number(node && node.attempt) || 0,
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
return nested;
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
if (result.ok === false) {
|
|
743
|
+
emitToolEvent(onToolEvent, {
|
|
744
|
+
tool: "plan_graph",
|
|
745
|
+
phase: "error",
|
|
746
|
+
args: safeArgs,
|
|
747
|
+
error: Array.isArray(result.errors)
|
|
748
|
+
? result.errors.map((e) => e.message || e.code).join("; ")
|
|
749
|
+
: "plan_graph rejected",
|
|
750
|
+
origin,
|
|
751
|
+
});
|
|
752
|
+
} else {
|
|
753
|
+
syncInteractionFromPlanGraph(result.executionState || state);
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
...result.modelPayload,
|
|
757
|
+
ok: result.status === "accepted",
|
|
758
|
+
executionState: result.executionState || state,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (normalizedTool === "ask_user") {
|
|
763
|
+
const state = executionState && typeof executionState === "object"
|
|
764
|
+
? executionState
|
|
765
|
+
: emptyExecutionState();
|
|
766
|
+
const result = runAskUserTool(safeArgs, {
|
|
767
|
+
executionState: state,
|
|
768
|
+
resume: resume || null,
|
|
769
|
+
});
|
|
770
|
+
const ok = result.ok !== false && result.status !== "rejected";
|
|
771
|
+
emitToolEvent(onToolEvent, {
|
|
772
|
+
tool: "ask_user",
|
|
773
|
+
phase: ok ? "end" : "error",
|
|
774
|
+
args: safeArgs,
|
|
775
|
+
result: result.modelPayload || result,
|
|
776
|
+
error: ok ? "" : (result.error || "ask_user rejected"),
|
|
777
|
+
origin,
|
|
778
|
+
});
|
|
779
|
+
return {
|
|
780
|
+
...(result.modelPayload || result),
|
|
781
|
+
ok,
|
|
782
|
+
status: result.status,
|
|
783
|
+
waiting_user: Boolean(result.waiting_user || result.status === "waiting_user"),
|
|
784
|
+
interactionId: result.interactionId || "",
|
|
785
|
+
executionState: result.executionState || state,
|
|
786
|
+
deferToolResult: ok && result.status === "waiting_user",
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
557
790
|
const toolOptions = { workspaceRoot, cwd: workspaceRoot };
|
|
558
791
|
if (normalizedTool === "artifact_read" && sessionId) {
|
|
559
792
|
toolOptions.sessionId = sessionId;
|
|
@@ -569,12 +802,12 @@ function runCoreTool({
|
|
|
569
802
|
phase: "error",
|
|
570
803
|
args: safeArgs,
|
|
571
804
|
error: String((result && result.error) || `${normalizedTool} failed`),
|
|
805
|
+
origin,
|
|
572
806
|
});
|
|
573
807
|
return result;
|
|
574
808
|
}
|
|
575
809
|
|
|
576
|
-
|
|
577
|
-
if (useContextV2 && normalizedTool !== "artifact_read") {
|
|
810
|
+
if (normalizedTool !== "artifact_read" && EXECUTABLE_GRAPH_TOOLS.has(normalizedTool)) {
|
|
578
811
|
const persisted = persistToolResultToContext({
|
|
579
812
|
workspaceRoot,
|
|
580
813
|
sessionId,
|
|
@@ -589,9 +822,14 @@ function runCoreTool({
|
|
|
589
822
|
// ignore
|
|
590
823
|
}
|
|
591
824
|
}
|
|
592
|
-
|
|
825
|
+
const payload = persisted.modelPayload || result;
|
|
826
|
+
if (origin) payload.origin = origin;
|
|
827
|
+
return payload;
|
|
593
828
|
}
|
|
594
829
|
|
|
830
|
+
if (origin && result && typeof result === "object") {
|
|
831
|
+
return { ...result, origin };
|
|
832
|
+
}
|
|
595
833
|
return result;
|
|
596
834
|
}
|
|
597
835
|
|
|
@@ -1375,9 +1613,10 @@ async function runNativeLoop({
|
|
|
1375
1613
|
onToolEvent = null,
|
|
1376
1614
|
onArtifactPersisted = null,
|
|
1377
1615
|
sessionId = "",
|
|
1378
|
-
contextV2 = false,
|
|
1379
1616
|
signal = null,
|
|
1380
1617
|
guards,
|
|
1618
|
+
executionState: initialExecutionState = null,
|
|
1619
|
+
resume = false,
|
|
1381
1620
|
} = {}) {
|
|
1382
1621
|
const requestModel = String(model || "").trim();
|
|
1383
1622
|
if (!requestModel) {
|
|
@@ -1390,19 +1629,38 @@ async function runNativeLoop({
|
|
|
1390
1629
|
}
|
|
1391
1630
|
|
|
1392
1631
|
const messages = sanitizeModelMessages(cloneMessageList(historyMessages));
|
|
1393
|
-
|
|
1632
|
+
if (!resume) {
|
|
1633
|
+
transport.prepareMessages({ messages, systemPrompt, prompt });
|
|
1634
|
+
}
|
|
1394
1635
|
|
|
1395
1636
|
let aggregated = "";
|
|
1396
1637
|
let streamed = false;
|
|
1397
1638
|
let toolCallsExecuted = 0;
|
|
1398
1639
|
let toolErrors = 0;
|
|
1399
|
-
let executionState =
|
|
1640
|
+
let executionState = initialExecutionState && typeof initialExecutionState === "object"
|
|
1641
|
+
? initialExecutionState
|
|
1642
|
+
: emptyExecutionState();
|
|
1643
|
+
if (typeof executionState.planMode !== "boolean") executionState.planMode = false;
|
|
1644
|
+
ensurePendingUserPrompts(executionState);
|
|
1400
1645
|
const toolBudget = resolveNativeToolBudget();
|
|
1401
1646
|
const usage = createUsageTotals();
|
|
1402
1647
|
|
|
1648
|
+
function injectPendingUserReminders() {
|
|
1649
|
+
const nudges = drainUserPrompts(executionState);
|
|
1650
|
+
if (nudges.length === 0) return;
|
|
1651
|
+
const waiting = executionState.planGraph && executionState.planGraph.waitingFor
|
|
1652
|
+
? executionState.planGraph.waitingFor
|
|
1653
|
+
: null;
|
|
1654
|
+
const content = formatUserReminderMessage(nudges, { waitingFor: waiting });
|
|
1655
|
+
if (!content) return;
|
|
1656
|
+
messages.push({ role: "user", content });
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1403
1659
|
while (true) {
|
|
1404
1660
|
guards.ensureActive();
|
|
1405
1661
|
|
|
1662
|
+
injectPendingUserReminders();
|
|
1663
|
+
|
|
1406
1664
|
const turnResult = await transport.runTurn({
|
|
1407
1665
|
url: requestUrl,
|
|
1408
1666
|
apiKey,
|
|
@@ -1433,31 +1691,33 @@ async function runNativeLoop({
|
|
|
1433
1691
|
|
|
1434
1692
|
if (toolCalls.length === 0) {
|
|
1435
1693
|
const text = String(turnResult.text || "").trim();
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
if (segment && segment.steps && segment.steps.length > 0) {
|
|
1694
|
+
const sideEffects = parseStructuredSideEffects(text);
|
|
1695
|
+
const planCommand = sideEffects ? normalizePlanGraphCommand(sideEffects) : null;
|
|
1696
|
+
if (planCommand) {
|
|
1440
1697
|
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1698
|
+
const planResult = runCoreTool({
|
|
1699
|
+
tool: "plan_graph",
|
|
1700
|
+
args: planCommand,
|
|
1701
|
+
workspaceRoot,
|
|
1702
|
+
onToolEvent,
|
|
1703
|
+
sessionId,
|
|
1704
|
+
onArtifactPersisted,
|
|
1443
1705
|
executionState,
|
|
1444
|
-
|
|
1445
|
-
tool,
|
|
1446
|
-
args,
|
|
1447
|
-
workspaceRoot,
|
|
1448
|
-
onToolEvent,
|
|
1449
|
-
sessionId,
|
|
1450
|
-
contextV2,
|
|
1451
|
-
onArtifactPersisted,
|
|
1452
|
-
}),
|
|
1706
|
+
origin: { kind: "legacy_side_effect", source: planCommand.source || "legacy" },
|
|
1453
1707
|
});
|
|
1454
|
-
|
|
1708
|
+
if (planResult && planResult.executionState) {
|
|
1709
|
+
executionState = planResult.executionState;
|
|
1710
|
+
}
|
|
1455
1711
|
messages.push({
|
|
1456
1712
|
role: "user",
|
|
1457
|
-
content:
|
|
1713
|
+
content: JSON.stringify({
|
|
1714
|
+
type: "plan_graph_result",
|
|
1715
|
+
...((planResult && planResult.status)
|
|
1716
|
+
? planResult
|
|
1717
|
+
: { status: "rejected", ok: false, error: "plan_graph failed" }),
|
|
1718
|
+
}),
|
|
1458
1719
|
});
|
|
1459
1720
|
continue;
|
|
1460
|
-
}
|
|
1461
1721
|
}
|
|
1462
1722
|
transport.appendFinalAssistantMessage({ messages, turnResult });
|
|
1463
1723
|
if (!aggregated.trim() && text) {
|
|
@@ -1469,6 +1729,7 @@ async function runNativeLoop({
|
|
|
1469
1729
|
toolCallsExecuted,
|
|
1470
1730
|
messages,
|
|
1471
1731
|
usage,
|
|
1732
|
+
executionState,
|
|
1472
1733
|
};
|
|
1473
1734
|
}
|
|
1474
1735
|
|
|
@@ -1480,24 +1741,180 @@ async function runNativeLoop({
|
|
|
1480
1741
|
toolCallsExecuted,
|
|
1481
1742
|
messages,
|
|
1482
1743
|
usage,
|
|
1744
|
+
executionState,
|
|
1483
1745
|
};
|
|
1484
1746
|
}
|
|
1485
1747
|
|
|
1748
|
+
const callNames = pendingCalls.map((call) => String(call.name || "").trim().toLowerCase());
|
|
1749
|
+
const hasPlanGraph = callNames.includes("plan_graph");
|
|
1750
|
+
const hasAskUser = callNames.includes("ask_user");
|
|
1751
|
+
const hasDataTool = callNames.some((name) => EXECUTABLE_GRAPH_TOOLS.has(name));
|
|
1752
|
+
if (hasPlanGraph && hasDataTool) {
|
|
1753
|
+
// prepareToolCalls already appended the assistant tool_calls / tool_use
|
|
1754
|
+
// message; every declared call must get a contiguous tool result.
|
|
1755
|
+
const collectedResults = [];
|
|
1756
|
+
for (const pending of pendingCalls) {
|
|
1757
|
+
transport.appendToolResult({
|
|
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
|
+
});
|
|
1768
|
+
toolCallsExecuted += 1;
|
|
1769
|
+
toolErrors += 1;
|
|
1770
|
+
}
|
|
1771
|
+
if (typeof transport.flushToolResults === "function") {
|
|
1772
|
+
transport.flushToolResults({ messages, collected: collectedResults });
|
|
1773
|
+
}
|
|
1774
|
+
continue;
|
|
1775
|
+
}
|
|
1776
|
+
if (hasAskUser && pendingCalls.length > 1) {
|
|
1777
|
+
const collectedResults = [];
|
|
1778
|
+
for (const pending of pendingCalls) {
|
|
1779
|
+
transport.appendToolResult({
|
|
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
|
+
});
|
|
1790
|
+
toolCallsExecuted += 1;
|
|
1791
|
+
toolErrors += 1;
|
|
1792
|
+
}
|
|
1793
|
+
if (typeof transport.flushToolResults === "function") {
|
|
1794
|
+
transport.flushToolResults({ messages, collected: collectedResults });
|
|
1795
|
+
}
|
|
1796
|
+
continue;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1486
1799
|
const collectedResults = [];
|
|
1800
|
+
let deferredAskUser = null;
|
|
1487
1801
|
for (const pending of pendingCalls) {
|
|
1802
|
+
const pendingName = String(pending.name || "").trim().toLowerCase();
|
|
1803
|
+
if (
|
|
1804
|
+
EXECUTABLE_GRAPH_TOOLS.has(pendingName)
|
|
1805
|
+
&& activePlanRequiresExpansion(executionState.planGraph)
|
|
1806
|
+
) {
|
|
1807
|
+
const blocked = {
|
|
1808
|
+
ok: false,
|
|
1809
|
+
status: "rejected",
|
|
1810
|
+
errors: [{
|
|
1811
|
+
code: "ACTIVE_PLAN_REQUIRES_EXPANSION",
|
|
1812
|
+
message: "Active plan is waiting on a task; use plan_graph expand_node or control.complete_task instead of direct tools",
|
|
1813
|
+
}],
|
|
1814
|
+
};
|
|
1815
|
+
toolCallsExecuted += 1;
|
|
1816
|
+
toolErrors += 1;
|
|
1817
|
+
transport.appendToolResult({
|
|
1818
|
+
messages,
|
|
1819
|
+
collected: collectedResults,
|
|
1820
|
+
call: pending,
|
|
1821
|
+
toolResult: blocked,
|
|
1822
|
+
});
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
if (planModeBlocksDirectTool(pendingName, executionState)) {
|
|
1826
|
+
const blocked = {
|
|
1827
|
+
ok: false,
|
|
1828
|
+
status: "rejected",
|
|
1829
|
+
errors: [{
|
|
1830
|
+
code: "PLAN_MODE_BLOCKS_SIDE_EFFECT",
|
|
1831
|
+
message: "Plan mode is on; use plan_graph for write/edit/bash, or ask the user to /plan off",
|
|
1832
|
+
}],
|
|
1833
|
+
};
|
|
1834
|
+
toolCallsExecuted += 1;
|
|
1835
|
+
toolErrors += 1;
|
|
1836
|
+
transport.appendToolResult({
|
|
1837
|
+
messages,
|
|
1838
|
+
collected: collectedResults,
|
|
1839
|
+
call: pending,
|
|
1840
|
+
toolResult: blocked,
|
|
1841
|
+
});
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
const leaseCheck = checkWriteAllowed(executionState, {
|
|
1845
|
+
tool: pendingName,
|
|
1846
|
+
originKind: "agent_loop",
|
|
1847
|
+
});
|
|
1848
|
+
if (!leaseCheck.ok) {
|
|
1849
|
+
const blocked = {
|
|
1850
|
+
ok: false,
|
|
1851
|
+
status: "rejected",
|
|
1852
|
+
errors: [{
|
|
1853
|
+
code: leaseCheck.code || "WORKSPACE_WRITE_LEASE_HELD",
|
|
1854
|
+
message: leaseCheck.message
|
|
1855
|
+
|| "Workspace write lease held by an active TaskRun",
|
|
1856
|
+
owner: leaseCheck.owner || null,
|
|
1857
|
+
}],
|
|
1858
|
+
};
|
|
1859
|
+
toolCallsExecuted += 1;
|
|
1860
|
+
toolErrors += 1;
|
|
1861
|
+
transport.appendToolResult({
|
|
1862
|
+
messages,
|
|
1863
|
+
collected: collectedResults,
|
|
1864
|
+
call: pending,
|
|
1865
|
+
toolResult: blocked,
|
|
1866
|
+
});
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
const toolCallId = pending.source && (pending.source.id || (pending.source.function && pending.source.id))
|
|
1871
|
+
? String(pending.source.id || "")
|
|
1872
|
+
: "";
|
|
1873
|
+
const resumeForAsk = pendingName === "ask_user"
|
|
1874
|
+
? {
|
|
1875
|
+
toolCallId: toolCallId || String((pending.source && pending.source.id) || `call_${randomUUID()}`),
|
|
1876
|
+
toolName: "ask_user",
|
|
1877
|
+
call: {
|
|
1878
|
+
name: pending.name,
|
|
1879
|
+
args: pending.args,
|
|
1880
|
+
source: pending.source,
|
|
1881
|
+
},
|
|
1882
|
+
}
|
|
1883
|
+
: null;
|
|
1884
|
+
|
|
1488
1885
|
const toolResult = runCoreTool({
|
|
1489
1886
|
tool: pending.name,
|
|
1490
1887
|
args: pending.args,
|
|
1491
1888
|
workspaceRoot,
|
|
1492
1889
|
onToolEvent,
|
|
1493
1890
|
sessionId,
|
|
1494
|
-
contextV2,
|
|
1495
1891
|
onArtifactPersisted,
|
|
1892
|
+
executionState,
|
|
1893
|
+
resume: resumeForAsk,
|
|
1496
1894
|
});
|
|
1895
|
+
if (toolResult && toolResult.executionState) {
|
|
1896
|
+
executionState = toolResult.executionState;
|
|
1897
|
+
}
|
|
1497
1898
|
toolCallsExecuted += 1;
|
|
1498
1899
|
if (!toolResult || toolResult.ok === false) {
|
|
1499
1900
|
toolErrors += 1;
|
|
1500
1901
|
}
|
|
1902
|
+
|
|
1903
|
+
if (pendingName === "ask_user" && toolResult && toolResult.deferToolResult) {
|
|
1904
|
+
// Attach resume metadata onto pending interaction for contiguous tool_result later.
|
|
1905
|
+
const pendingInteraction = getPendingUserInteraction(executionState);
|
|
1906
|
+
if (pendingInteraction) {
|
|
1907
|
+
pendingInteraction.resume = {
|
|
1908
|
+
...(pendingInteraction.resume || {}),
|
|
1909
|
+
...(resumeForAsk || {}),
|
|
1910
|
+
mode: "ask_user",
|
|
1911
|
+
transport: provider === "anthropic" ? "anthropic-messages" : "openai-chat",
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
deferredAskUser = { call: pending, interactionId: toolResult.interactionId || "" };
|
|
1915
|
+
continue;
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1501
1918
|
enforceNativeToolBudget({
|
|
1502
1919
|
toolCallsExecuted,
|
|
1503
1920
|
toolErrors,
|
|
@@ -1517,9 +1934,61 @@ async function runNativeLoop({
|
|
|
1517
1934
|
if (typeof transport.flushToolResults === "function") {
|
|
1518
1935
|
transport.flushToolResults({ messages, collected: collectedResults });
|
|
1519
1936
|
}
|
|
1937
|
+
|
|
1938
|
+
if (deferredAskUser) {
|
|
1939
|
+
return {
|
|
1940
|
+
text: aggregated,
|
|
1941
|
+
streamed,
|
|
1942
|
+
toolCallsExecuted,
|
|
1943
|
+
messages,
|
|
1944
|
+
usage,
|
|
1945
|
+
executionState,
|
|
1946
|
+
waitingUserInteraction: true,
|
|
1947
|
+
interactionId: deferredAskUser.interactionId || "",
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
if (hasPendingUserInteraction(executionState)) {
|
|
1952
|
+
// Checkpoint approval synced from plan_graph — pause for TUI.
|
|
1953
|
+
return {
|
|
1954
|
+
text: aggregated,
|
|
1955
|
+
streamed,
|
|
1956
|
+
toolCallsExecuted,
|
|
1957
|
+
messages,
|
|
1958
|
+
usage,
|
|
1959
|
+
executionState,
|
|
1960
|
+
waitingUserInteraction: true,
|
|
1961
|
+
interactionId: (getPendingUserInteraction(executionState) || {}).id || "",
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1520
1964
|
}
|
|
1521
1965
|
}
|
|
1522
1966
|
|
|
1967
|
+
function appendAnswerToolResult(messages = [], resume = null, answer = {}) {
|
|
1968
|
+
const call = resume && resume.call ? resume.call : null;
|
|
1969
|
+
if (!call || !call.source) return { ok: false, error: "missing deferred tool call" };
|
|
1970
|
+
const transportName = String(resume.transport || "openai-chat");
|
|
1971
|
+
const content = clipText(toJsonString(answer), 12000);
|
|
1972
|
+
if (transportName === "anthropic-messages") {
|
|
1973
|
+
messages.push({
|
|
1974
|
+
role: "user",
|
|
1975
|
+
content: [{
|
|
1976
|
+
type: "tool_result",
|
|
1977
|
+
tool_use_id: String(call.source.id || resume.toolCallId || ""),
|
|
1978
|
+
content,
|
|
1979
|
+
is_error: false,
|
|
1980
|
+
}],
|
|
1981
|
+
});
|
|
1982
|
+
} else {
|
|
1983
|
+
messages.push({
|
|
1984
|
+
role: "tool",
|
|
1985
|
+
tool_call_id: String(call.source.id || resume.toolCallId || ""),
|
|
1986
|
+
content,
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
return { ok: true };
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1523
1992
|
async function runNativeAgentTask({
|
|
1524
1993
|
workspaceRoot = process.cwd(),
|
|
1525
1994
|
prompt = "",
|
|
@@ -1535,8 +2004,9 @@ async function runNativeAgentTask({
|
|
|
1535
2004
|
onPhase = null,
|
|
1536
2005
|
onToolEvent = null,
|
|
1537
2006
|
onArtifactPersisted = null,
|
|
1538
|
-
contextV2 = false,
|
|
1539
2007
|
signal = null,
|
|
2008
|
+
executionState = null,
|
|
2009
|
+
resume = false,
|
|
1540
2010
|
} = {}) {
|
|
1541
2011
|
const guards = createGuards({ signal, timeoutMs });
|
|
1542
2012
|
const nextSessionId = String(sessionId || "").trim() || `native-${randomUUID()}`;
|
|
@@ -1556,7 +2026,7 @@ async function runNativeAgentTask({
|
|
|
1556
2026
|
try {
|
|
1557
2027
|
guards.ensureActive();
|
|
1558
2028
|
|
|
1559
|
-
if (!promptText) {
|
|
2029
|
+
if (!resume && !promptText) {
|
|
1560
2030
|
return {
|
|
1561
2031
|
ok: false,
|
|
1562
2032
|
error: "empty task",
|
|
@@ -1594,7 +2064,7 @@ async function runNativeAgentTask({
|
|
|
1594
2064
|
const runResult = await runNativeLoop({
|
|
1595
2065
|
transport,
|
|
1596
2066
|
workspaceRoot,
|
|
1597
|
-
prompt: promptText,
|
|
2067
|
+
prompt: resume ? "" : promptText,
|
|
1598
2068
|
systemPrompt,
|
|
1599
2069
|
systemBlocks,
|
|
1600
2070
|
historyMessages: messages,
|
|
@@ -1609,9 +2079,10 @@ async function runNativeAgentTask({
|
|
|
1609
2079
|
onToolEvent,
|
|
1610
2080
|
onArtifactPersisted,
|
|
1611
2081
|
sessionId: nextSessionId,
|
|
1612
|
-
contextV2: contextV2 || isContextV2Enabled(),
|
|
1613
2082
|
signal,
|
|
1614
2083
|
guards,
|
|
2084
|
+
executionState,
|
|
2085
|
+
resume: Boolean(resume),
|
|
1615
2086
|
});
|
|
1616
2087
|
|
|
1617
2088
|
const outputText = String(runResult.text || "").trim() || (
|
|
@@ -1641,26 +2112,36 @@ async function runNativeAgentTask({
|
|
|
1641
2112
|
messages: cloneMessageList(runResult.messages),
|
|
1642
2113
|
sessionId: nextSessionId,
|
|
1643
2114
|
usage,
|
|
2115
|
+
executionState: runResult.executionState || executionState || null,
|
|
1644
2116
|
// The loop marks streamed=true whenever it receives a stream callback;
|
|
1645
2117
|
// only report it when the caller actually registered one.
|
|
1646
2118
|
streamed: Boolean(runResult.streamed) && typeof onStreamDelta === "function",
|
|
2119
|
+
waitingUserInteraction: Boolean(runResult.waitingUserInteraction),
|
|
2120
|
+
interactionId: runResult.interactionId || "",
|
|
1647
2121
|
};
|
|
1648
2122
|
} catch (err) {
|
|
1649
2123
|
const message = err && err.message ? err.message : "native runner failed";
|
|
2124
|
+
if (executionState && typeof executionState === "object") {
|
|
2125
|
+
clearUserPrompts(executionState);
|
|
2126
|
+
}
|
|
1650
2127
|
return {
|
|
1651
2128
|
ok: false,
|
|
1652
2129
|
error: message,
|
|
1653
2130
|
output: partialOutput.trim(),
|
|
1654
2131
|
sessionId: nextSessionId,
|
|
1655
2132
|
streamed: false,
|
|
2133
|
+
executionState: executionState || null,
|
|
1656
2134
|
};
|
|
1657
2135
|
}
|
|
1658
2136
|
}
|
|
1659
2137
|
|
|
1660
2138
|
module.exports = {
|
|
1661
2139
|
runNativeAgentTask,
|
|
2140
|
+
appendAnswerToolResult,
|
|
1662
2141
|
resolveRuntimeConfig,
|
|
1663
2142
|
resolveCompletionUrl,
|
|
1664
2143
|
resolveAnthropicMessagesUrl,
|
|
1665
2144
|
resolveTransport,
|
|
2145
|
+
buildCoreToolSpecs,
|
|
2146
|
+
buildAnthropicToolSpecs,
|
|
1666
2147
|
};
|