u-foo 3.0.5 → 3.0.6
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
CHANGED
|
@@ -430,17 +430,128 @@ function cacheCommand(planGraph, commandId, payload) {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
/**
|
|
433
|
-
*
|
|
433
|
+
* Resolve which graph a plan_graph command targets.
|
|
434
|
+
* Parent lives in executionState.planGraph; TaskLoop children live in graphs[].
|
|
435
|
+
*/
|
|
436
|
+
function selectGraphForCommand(executionState = null, command = {}) {
|
|
437
|
+
const state = ensurePlanGraphState(executionState);
|
|
438
|
+
if (!state.graphs || typeof state.graphs !== "object") state.graphs = {};
|
|
439
|
+
const primary = state.planGraph && typeof state.planGraph === "object"
|
|
440
|
+
? state.planGraph
|
|
441
|
+
: emptyPlanGraphState();
|
|
442
|
+
if (primary.graphId) state.graphs[primary.graphId] = primary;
|
|
443
|
+
|
|
444
|
+
const requested = String(command && command.graphId || "").trim();
|
|
445
|
+
if (!requested) {
|
|
446
|
+
return {
|
|
447
|
+
ok: true,
|
|
448
|
+
graph: primary,
|
|
449
|
+
isPrimary: true,
|
|
450
|
+
primaryGraphId: String(primary.graphId || ""),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
if (primary.graphId && primary.graphId === requested) {
|
|
454
|
+
return {
|
|
455
|
+
ok: true,
|
|
456
|
+
graph: primary,
|
|
457
|
+
isPrimary: true,
|
|
458
|
+
primaryGraphId: primary.graphId,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
const mapped = state.graphs[requested];
|
|
462
|
+
if (mapped && typeof mapped === "object") {
|
|
463
|
+
return {
|
|
464
|
+
ok: true,
|
|
465
|
+
graph: mapped,
|
|
466
|
+
isPrimary: false,
|
|
467
|
+
primaryGraphId: String(primary.graphId || ""),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
ok: false,
|
|
472
|
+
code: "GRAPH_NOT_FOUND",
|
|
473
|
+
message: `graphId ${requested} not found`,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Apply a normalized plan_graph command against executionState.planGraph
|
|
479
|
+
* (or a TaskLoop child graph when command.graphId selects it).
|
|
434
480
|
*/
|
|
435
481
|
function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
436
482
|
const command = normalizePlanGraphCommand(commandInput) || commandInput;
|
|
437
483
|
const operation = String(command && command.operation || "").trim().toLowerCase();
|
|
438
484
|
const executionState = ensurePlanGraphState(options.executionState);
|
|
439
|
-
|
|
485
|
+
if (!executionState.graphs || typeof executionState.graphs !== "object") {
|
|
486
|
+
executionState.graphs = {};
|
|
487
|
+
}
|
|
440
488
|
const commandId = String(command.commandId || "").trim();
|
|
441
489
|
|
|
490
|
+
if (!operation) {
|
|
491
|
+
const payload = rejected([{ code: "MISSING_OPERATION", message: "operation is required" }]);
|
|
492
|
+
return { ...payload, executionState, modelPayload: payload };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// create/control always target the primary agent graph; patch/inspect may
|
|
496
|
+
// select a TaskLoop child via command.graphId.
|
|
497
|
+
const selected = (operation === "patch" || operation === "inspect")
|
|
498
|
+
? selectGraphForCommand(executionState, command)
|
|
499
|
+
: {
|
|
500
|
+
ok: true,
|
|
501
|
+
graph: executionState.planGraph,
|
|
502
|
+
isPrimary: true,
|
|
503
|
+
primaryGraphId: String(executionState.planGraph && executionState.planGraph.graphId || ""),
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
if (!selected.ok) {
|
|
507
|
+
const payload = rejected([{
|
|
508
|
+
code: selected.code || "GRAPH_NOT_FOUND",
|
|
509
|
+
message: selected.message || "graph not found",
|
|
510
|
+
}]);
|
|
511
|
+
return { ...payload, executionState, modelPayload: payload };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const primaryGraphId = selected.primaryGraphId
|
|
515
|
+
|| String(executionState.planGraph && executionState.planGraph.graphId || "");
|
|
516
|
+
// Work on the selected graph for this command; restore primary afterward if child.
|
|
517
|
+
if (!selected.isPrimary) {
|
|
518
|
+
executionState.planGraph = selected.graph;
|
|
519
|
+
}
|
|
520
|
+
const planGraph = executionState.planGraph;
|
|
521
|
+
|
|
522
|
+
function restorePrimaryGraph() {
|
|
523
|
+
if (executionState.planGraph && executionState.planGraph.graphId) {
|
|
524
|
+
executionState.graphs[executionState.planGraph.graphId] = executionState.planGraph;
|
|
525
|
+
}
|
|
526
|
+
if (!selected.isPrimary && primaryGraphId && executionState.graphs[primaryGraphId]) {
|
|
527
|
+
executionState.planGraph = executionState.graphs[primaryGraphId];
|
|
528
|
+
} else if (executionState.planGraph && executionState.planGraph.graphId) {
|
|
529
|
+
executionState.graphs[executionState.planGraph.graphId] = executionState.planGraph;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function resumeChildTaskLoopIfNeeded(payload = null) {
|
|
534
|
+
if (selected.isPrimary) return null;
|
|
535
|
+
if (!payload || payload.status !== "accepted") return null;
|
|
536
|
+
if (typeof options.runTool !== "function") return null;
|
|
537
|
+
const childId = String((selected.graph && selected.graph.graphId) || "").trim();
|
|
538
|
+
const childLive = childId ? executionState.graphs[childId] : null;
|
|
539
|
+
const owner = childLive && childLive.owner;
|
|
540
|
+
if (!owner || owner.kind !== "task_loop" || !owner.taskRunId) return null;
|
|
541
|
+
try {
|
|
542
|
+
const { processTaskRun } = require("../runtime/taskLoop");
|
|
543
|
+
return processTaskRun(executionState, owner.taskRunId, {
|
|
544
|
+
runTool: options.runTool,
|
|
545
|
+
knownTools: options.knownTools,
|
|
546
|
+
});
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
442
552
|
if (commandId && planGraph.commandLog && planGraph.commandLog[commandId]) {
|
|
443
553
|
const cached = cloneJson(planGraph.commandLog[commandId]);
|
|
554
|
+
restorePrimaryGraph();
|
|
444
555
|
return {
|
|
445
556
|
...cached,
|
|
446
557
|
ok: cached.status === "accepted",
|
|
@@ -450,13 +561,9 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
450
561
|
};
|
|
451
562
|
}
|
|
452
563
|
|
|
453
|
-
if (!operation) {
|
|
454
|
-
const payload = rejected([{ code: "MISSING_OPERATION", message: "operation is required" }]);
|
|
455
|
-
return { ...payload, executionState, modelPayload: payload };
|
|
456
|
-
}
|
|
457
|
-
|
|
458
564
|
if (operation === "inspect") {
|
|
459
565
|
const payload = inspectPlanGraph(planGraph);
|
|
566
|
+
restorePrimaryGraph();
|
|
460
567
|
return { ...payload, executionState, modelPayload: payload };
|
|
461
568
|
}
|
|
462
569
|
|
|
@@ -556,9 +663,15 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
556
663
|
advance: { status: "completed", yieldReason: "cancelled", executedNodes: [], failedNodes: [] },
|
|
557
664
|
validationWarnings: [],
|
|
558
665
|
});
|
|
666
|
+
restorePrimaryGraph();
|
|
559
667
|
return { ...payload, executionState, modelPayload: payload };
|
|
560
668
|
}
|
|
561
669
|
|
|
670
|
+
function rejectWorking(payload, extra = {}) {
|
|
671
|
+
restorePrimaryGraph();
|
|
672
|
+
return { ...payload, executionState, modelPayload: payload, ...extra };
|
|
673
|
+
}
|
|
674
|
+
|
|
562
675
|
if (
|
|
563
676
|
Number.isFinite(command.expectedSpecRevision)
|
|
564
677
|
&& command.expectedSpecRevision !== null
|
|
@@ -572,15 +685,16 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
572
685
|
commandRevision: Number(planGraph.specRevision) || 0,
|
|
573
686
|
stateRevision: Number(planGraph.stateRevision) || 0,
|
|
574
687
|
});
|
|
575
|
-
return
|
|
688
|
+
return rejectWorking(payload);
|
|
576
689
|
}
|
|
577
690
|
|
|
578
691
|
if (command.graphId && planGraph.graphId && command.graphId !== planGraph.graphId) {
|
|
692
|
+
// Should not happen after selectGraphForCommand switched the working graph.
|
|
579
693
|
const payload = rejected([{
|
|
580
694
|
code: "GRAPH_ID_MISMATCH",
|
|
581
695
|
message: `expected graphId ${command.graphId}, actual ${planGraph.graphId}`,
|
|
582
696
|
}]);
|
|
583
|
-
return
|
|
697
|
+
return rejectWorking(payload);
|
|
584
698
|
}
|
|
585
699
|
|
|
586
700
|
const beforeIds = new Set(listNodeIds(planGraph.nodes));
|
|
@@ -622,7 +736,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
622
736
|
code: "NESTED_TASK_LOOP_NOT_SUPPORTED",
|
|
623
737
|
message: "V1 child graphs cannot create task_loop nodes",
|
|
624
738
|
}]);
|
|
625
|
-
return
|
|
739
|
+
return rejectWorking(payload);
|
|
626
740
|
}
|
|
627
741
|
}
|
|
628
742
|
const targetId = String(op.nodeId || (op.node && op.node.id) || "").trim();
|
|
@@ -639,7 +753,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
639
753
|
code: "RUNNING_TASK_SPEC_FROZEN",
|
|
640
754
|
message: `cannot mutate running task_loop ${targetId}`,
|
|
641
755
|
}]);
|
|
642
|
-
return
|
|
756
|
+
return rejectWorking(payload);
|
|
643
757
|
}
|
|
644
758
|
}
|
|
645
759
|
}
|
|
@@ -654,7 +768,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
654
768
|
commandRevision: Number(planGraph.specRevision) || 0,
|
|
655
769
|
stateRevision: Number(planGraph.stateRevision) || 0,
|
|
656
770
|
});
|
|
657
|
-
return
|
|
771
|
+
return rejectWorking(payload);
|
|
658
772
|
}
|
|
659
773
|
nextPlan = applied;
|
|
660
774
|
for (const op of ops) {
|
|
@@ -668,7 +782,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
668
782
|
}
|
|
669
783
|
} else {
|
|
670
784
|
const payload = rejected([{ code: "UNKNOWN_OPERATION", message: `unknown operation: ${operation}` }]);
|
|
671
|
-
return
|
|
785
|
+
return rejectWorking(payload);
|
|
672
786
|
}
|
|
673
787
|
|
|
674
788
|
// Validate. Do not rewrite aggregate sinks via group rewrite when storing flat nodes.
|
|
@@ -680,7 +794,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
680
794
|
stateRevision: Number(planGraph.stateRevision) || 0,
|
|
681
795
|
validationWarnings: compiled.warnings || [],
|
|
682
796
|
});
|
|
683
|
-
return
|
|
797
|
+
return rejectWorking(payload, { compile: compiled });
|
|
684
798
|
}
|
|
685
799
|
|
|
686
800
|
// Prefer the patched node list (includes aggregate expand status).
|
|
@@ -702,7 +816,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
702
816
|
commandRevision: Number(planGraph.specRevision) || 0,
|
|
703
817
|
stateRevision: Number(planGraph.stateRevision) || 0,
|
|
704
818
|
});
|
|
705
|
-
return
|
|
819
|
+
return rejectWorking(payload, { compile: preferredCompile });
|
|
706
820
|
}
|
|
707
821
|
|
|
708
822
|
const mergedNodes = applyStatusesFromStore(preferredCompile.nodes, preferredNodes, {
|
|
@@ -736,6 +850,8 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
736
850
|
lastYieldReason: "",
|
|
737
851
|
commandLog: planGraph.commandLog || {},
|
|
738
852
|
owner: nextPlan.owner || planGraph.owner || null,
|
|
853
|
+
parentGraphId: planGraph.parentGraphId || "",
|
|
854
|
+
parentNodeId: planGraph.parentNodeId || "",
|
|
739
855
|
};
|
|
740
856
|
if (!executionState.graphs || typeof executionState.graphs !== "object") {
|
|
741
857
|
executionState.graphs = {};
|
|
@@ -767,7 +883,7 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
767
883
|
commandRevision: specRevision,
|
|
768
884
|
stateRevision: Number(executionState.planGraph.stateRevision) || 0,
|
|
769
885
|
});
|
|
770
|
-
return
|
|
886
|
+
return rejectWorking(payload);
|
|
771
887
|
}
|
|
772
888
|
if (advanced.planGraph) {
|
|
773
889
|
executionState.planGraph = {
|
|
@@ -775,6 +891,9 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
775
891
|
specRevision,
|
|
776
892
|
revision: specRevision,
|
|
777
893
|
commandLog: planGraph.commandLog || {},
|
|
894
|
+
owner: advanced.planGraph.owner || planGraph.owner || null,
|
|
895
|
+
parentGraphId: advanced.planGraph.parentGraphId || planGraph.parentGraphId || "",
|
|
896
|
+
parentNodeId: advanced.planGraph.parentNodeId || planGraph.parentNodeId || "",
|
|
778
897
|
};
|
|
779
898
|
}
|
|
780
899
|
if (advanced.advance) advance = advanced.advance;
|
|
@@ -822,12 +941,16 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
822
941
|
}
|
|
823
942
|
}
|
|
824
943
|
|
|
944
|
+
restorePrimaryGraph();
|
|
945
|
+
const resumed = resumeChildTaskLoopIfNeeded(payload);
|
|
946
|
+
|
|
825
947
|
return {
|
|
826
948
|
...payload,
|
|
827
949
|
executionState,
|
|
828
950
|
modelPayload: payload,
|
|
829
951
|
compile: preferredCompile,
|
|
830
952
|
planModeEntered,
|
|
953
|
+
taskLoopResume: resumed || null,
|
|
831
954
|
};
|
|
832
955
|
}
|
|
833
956
|
|
|
@@ -853,5 +976,6 @@ module.exports = {
|
|
|
853
976
|
executionSegmentToCreateGraph,
|
|
854
977
|
projectPlanView,
|
|
855
978
|
activePlanRequiresExpansion,
|
|
979
|
+
selectGraphForCommand,
|
|
856
980
|
stripModelStatuses,
|
|
857
981
|
};
|
|
@@ -56,6 +56,7 @@ function buildImmutablePrefix() {
|
|
|
56
56
|
"- After an accepted plan_graph create or patch, Runtime automatically advances ready tool nodes. Never invent or request an execute_graph tool.",
|
|
57
57
|
"- Do not call plan_graph or task_run together with read, read_image, write, edit, bash, or artifact_read in the same assistant turn.",
|
|
58
58
|
"- When an active graph is waiting on a task, advance that node through plan_graph instead of bypassing it with direct workspace tools: use patch.expand_node for execution.kind=expand, control.complete_task (nodeId) for execution.kind=inline_llm, or control.start_task for execution.kind=task_loop.",
|
|
59
|
+
"- TaskLoop start returns childGraphId. While that TaskRun is waiting_model on child root, patch with graphId=<childGraphId> and expand_node nodeId=root (add tool children). Do not ask the user to /plan off.",
|
|
59
60
|
"- Do not end a turn with text only while the plan is still waiting on a task; expand, start, or complete that node. Runtime will auto-continue if you stop early, but prefer advancing in the same turn.",
|
|
60
61
|
"- control.complete_task with nodeId completes a waiting_llm inline_llm task for the current Graph owner. control.complete_task with taskRunId (or task_run complete) is reserved for the owning TaskLoop. Do not directly complete expand or aggregate tasks.",
|
|
61
62
|
"- While Plan Mode is ON, workspace mutations must be represented as plan_graph tool nodes or performed inside a running TaskRun/task_loop.",
|
|
@@ -387,15 +387,40 @@ function resolveUserInteraction(executionState = null, rawText = "") {
|
|
|
387
387
|
};
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
function
|
|
390
|
+
function wrapLabeledBlock(label = "", prompt = "", cols = 80) {
|
|
391
|
+
const title = String(label || "Question").trim() || "Question";
|
|
392
|
+
const text = String(prompt || "").replace(/\s+/g, " ").trim();
|
|
393
|
+
const width = Math.max(24, Math.min(120, Math.floor(Number(cols) || 80) - 2));
|
|
394
|
+
if (!text) return [`${title}:`];
|
|
395
|
+
|
|
396
|
+
const prefix = `${title}: `;
|
|
397
|
+
const firstWidth = Math.max(8, width - prefix.length);
|
|
398
|
+
const contPrefix = " ";
|
|
399
|
+
const contWidth = Math.max(8, width - contPrefix.length);
|
|
400
|
+
const lines = [];
|
|
401
|
+
|
|
402
|
+
let offset = 0;
|
|
403
|
+
const firstChunk = text.slice(0, firstWidth);
|
|
404
|
+
lines.push(`${prefix}${firstChunk}`);
|
|
405
|
+
offset = firstChunk.length;
|
|
406
|
+
while (offset < text.length) {
|
|
407
|
+
const chunk = text.slice(offset, offset + contWidth);
|
|
408
|
+
lines.push(`${contPrefix}${chunk}`);
|
|
409
|
+
offset += chunk.length;
|
|
410
|
+
}
|
|
411
|
+
return lines;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function formatInteractionPromptLines(pending = null, options = {}) {
|
|
391
415
|
if (!pending) return [];
|
|
416
|
+
const cols = Number(options.cols) > 0 ? Number(options.cols) : 80;
|
|
392
417
|
const lines = [];
|
|
393
418
|
const kind = pending.kind || "chat";
|
|
394
419
|
if (kind === "approval") {
|
|
395
|
-
lines.push(
|
|
420
|
+
lines.push(...wrapLabeledBlock("Approval", pending.prompt, cols));
|
|
396
421
|
lines.push(" [yes] Yes [no] No or type a free-text reply");
|
|
397
422
|
} else if (kind === "choice") {
|
|
398
|
-
lines.push(
|
|
423
|
+
lines.push(...wrapLabeledBlock("Choice", pending.prompt, cols));
|
|
399
424
|
for (const opt of pending.options || []) {
|
|
400
425
|
lines.push(` [${opt.key}] ${opt.label}`);
|
|
401
426
|
}
|
|
@@ -403,7 +428,7 @@ function formatInteractionPromptLines(pending = null) {
|
|
|
403
428
|
lines.push(" or type a free-text reply");
|
|
404
429
|
}
|
|
405
430
|
} else {
|
|
406
|
-
lines.push(
|
|
431
|
+
lines.push(...wrapLabeledBlock("Question", pending.prompt, cols));
|
|
407
432
|
lines.push(" (type your reply)");
|
|
408
433
|
}
|
|
409
434
|
return lines;
|
|
@@ -96,14 +96,16 @@ function buildTaskRunWakeReminder(executionState = null) {
|
|
|
96
96
|
if (runs.length === 0 && !hasPendingAgentMailbox(executionState)) return "";
|
|
97
97
|
const lines = [
|
|
98
98
|
"Runtime wake (not a user message): active TaskRun(s) still need the Agent Loop.",
|
|
99
|
-
"Continue serving them now
|
|
100
|
-
"
|
|
99
|
+
"Continue serving them now. For a TaskLoop child graph waiting on root:",
|
|
100
|
+
"call plan_graph operation=patch with graphId=<childGraphId> and expand_node on nodeId=root",
|
|
101
|
+
"(tool children). Do not ask the user to /plan off. Do not end with text only.",
|
|
101
102
|
];
|
|
102
103
|
for (const run of runs.slice(0, 4)) {
|
|
103
104
|
const label = run.title || run.objective || run.parentNodeId || run.id;
|
|
104
105
|
lines.push(
|
|
105
106
|
`- taskRunId=${run.id} phase=${run.phase || ""} status=${run.status || ""}`
|
|
106
|
-
+ (
|
|
107
|
+
+ (run.childGraphId ? ` childGraphId=${run.childGraphId}` : "")
|
|
108
|
+
+ (label ? ` — ${String(label).slice(0, 120)}` : "")
|
|
107
109
|
);
|
|
108
110
|
}
|
|
109
111
|
return lines.join("\n");
|
package/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -172,7 +172,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
172
172
|
);
|
|
173
173
|
setPlanUi((prev) => (prev && prev.hash === next.hash ? prev : next));
|
|
174
174
|
const pending = getPendingUserInteraction(props.state && props.state.executionState);
|
|
175
|
-
setInteractionLines(pending ? formatInteractionPromptLines(pending
|
|
175
|
+
setInteractionLines(pending ? formatInteractionPromptLines(pending, {
|
|
176
|
+
cols: size.cols || 80,
|
|
177
|
+
}) : []);
|
|
176
178
|
return next;
|
|
177
179
|
} catch {
|
|
178
180
|
return null;
|
|
@@ -1284,7 +1286,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1284
1286
|
...interactionLines.map((line, idx) => h(Text, {
|
|
1285
1287
|
key: `ask-${idx}`,
|
|
1286
1288
|
color: "yellow",
|
|
1287
|
-
wrap: "
|
|
1289
|
+
wrap: "wrap",
|
|
1288
1290
|
}, line || " ")),
|
|
1289
1291
|
)
|
|
1290
1292
|
: null,
|