u-foo 3.0.7 → 3.0.8
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
|
@@ -27,6 +27,7 @@ function emptyExecutionState() {
|
|
|
27
27
|
pendingUserPrompts: [],
|
|
28
28
|
planGraph: require("./planGraphService").emptyPlanGraphState(),
|
|
29
29
|
graphs: {},
|
|
30
|
+
archivedPlans: [],
|
|
30
31
|
taskRuns: require("../runtime/taskRun").emptyTaskRunStore(),
|
|
31
32
|
agentMailbox: require("../runtime/loopMailbox").emptyMailbox(),
|
|
32
33
|
taskMailboxes: {},
|
|
@@ -48,6 +48,102 @@ function cloneJson(value) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
const MAX_ARCHIVED_PLANS = 5;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Snapshot a finished/replaced plan out of the live primary slot so the next
|
|
55
|
+
* create starts clean instead of overlaying the previous graph.
|
|
56
|
+
*/
|
|
57
|
+
function archivePlanGraph(executionState = null, planGraph = null, {
|
|
58
|
+
reason = "",
|
|
59
|
+
} = {}) {
|
|
60
|
+
if (!executionState || typeof executionState !== "object") return null;
|
|
61
|
+
const live = planGraph && typeof planGraph === "object" ? planGraph : null;
|
|
62
|
+
const graphId = String((live && live.graphId) || "").trim();
|
|
63
|
+
if (!live || !graphId) return null;
|
|
64
|
+
|
|
65
|
+
if (!Array.isArray(executionState.archivedPlans)) {
|
|
66
|
+
executionState.archivedPlans = [];
|
|
67
|
+
}
|
|
68
|
+
const archived = {
|
|
69
|
+
...cloneJson(live),
|
|
70
|
+
archivedAt: new Date().toISOString(),
|
|
71
|
+
archiveReason: String(reason || "").trim() || "archived",
|
|
72
|
+
};
|
|
73
|
+
executionState.archivedPlans.push(archived);
|
|
74
|
+
if (executionState.archivedPlans.length > MAX_ARCHIVED_PLANS) {
|
|
75
|
+
executionState.archivedPlans = executionState.archivedPlans.slice(-MAX_ARCHIVED_PLANS);
|
|
76
|
+
}
|
|
77
|
+
if (executionState.graphs && typeof executionState.graphs === "object") {
|
|
78
|
+
delete executionState.graphs[graphId];
|
|
79
|
+
}
|
|
80
|
+
return archived;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function buildPlanCompletionSummary(planGraph = null, advance = null) {
|
|
84
|
+
const pg = planGraph && typeof planGraph === "object" ? planGraph : {};
|
|
85
|
+
const nodes = Array.isArray(pg.nodes) ? pg.nodes : [];
|
|
86
|
+
const tasks = nodes.filter((node) => node && (node.type === "task" || !node.generated));
|
|
87
|
+
const succeeded = tasks.filter((n) => n.status === "succeeded").length;
|
|
88
|
+
const failed = tasks.filter((n) => (
|
|
89
|
+
n.status === "failed" || n.status === "blocked" || n.status === "cancelled"
|
|
90
|
+
)).length;
|
|
91
|
+
const lines = [
|
|
92
|
+
`Plan completed${pg.graphId ? ` (${pg.graphId})` : ""}.`,
|
|
93
|
+
];
|
|
94
|
+
if (pg.objective) lines.push(`Objective: ${String(pg.objective).slice(0, 240)}`);
|
|
95
|
+
lines.push(`Tasks: ${succeeded} succeeded, ${failed} failed/blocked, ${tasks.length} total.`);
|
|
96
|
+
for (const node of tasks.slice(0, 12)) {
|
|
97
|
+
const bit = (node.result && node.result.summary)
|
|
98
|
+
|| node.error
|
|
99
|
+
|| node.status
|
|
100
|
+
|| "";
|
|
101
|
+
lines.push(
|
|
102
|
+
`- ${node.id} [${node.status || "pending"}]`
|
|
103
|
+
+ (bit ? `: ${String(bit).slice(0, 160)}` : ""),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
const executed = advance && Array.isArray(advance.executedNodes) ? advance.executedNodes : [];
|
|
107
|
+
if (executed.length > 0 && tasks.length === 0) {
|
|
108
|
+
for (const entry of executed.slice(0, 8)) {
|
|
109
|
+
lines.push(`- ${entry.id}: ${entry.summary || entry.status || "done"}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
lines.push("Summarize the outcome for the user. The active plan has been cleared; use plan_graph create for a new objective.");
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* On graph_terminal for the primary Agent Loop plan: archive, clear primary
|
|
118
|
+
* slot, and exit auto Plan Mode. Child TaskLoop graphs stay in graphs[].
|
|
119
|
+
*/
|
|
120
|
+
function finalizeTerminalPrimaryPlan(executionState = null, planGraph = null, advance = null) {
|
|
121
|
+
const pg = planGraph && typeof planGraph === "object" ? planGraph : null;
|
|
122
|
+
if (!pg || !pg.graphId) return null;
|
|
123
|
+
if (!advance || String(advance.yieldReason || "") !== "graph_terminal") return null;
|
|
124
|
+
|
|
125
|
+
const completionSummary = buildPlanCompletionSummary(pg, advance);
|
|
126
|
+
const archived = archivePlanGraph(executionState, pg, { reason: "graph_terminal" });
|
|
127
|
+
executionState.planGraph = emptyPlanGraphState();
|
|
128
|
+
executionState.mode = "single_action";
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const { getPlanModeSource, setPlanMode } = require("./planMode");
|
|
132
|
+
if (getPlanModeSource(executionState) === "auto") {
|
|
133
|
+
setPlanMode(executionState, false, { reason: "plan completed" });
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// planMode is optional for pure unit callers
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
planCompleted: true,
|
|
141
|
+
archivedGraphId: (archived && archived.graphId) || pg.graphId,
|
|
142
|
+
completionSummary,
|
|
143
|
+
planMode: Boolean(executionState.planMode),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
51
147
|
function ensurePlanGraphState(executionState = null) {
|
|
52
148
|
const state = executionState && typeof executionState === "object"
|
|
53
149
|
? executionState
|
|
@@ -614,42 +710,73 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
614
710
|
}
|
|
615
711
|
if (advanced.advance) advance = advanced.advance;
|
|
616
712
|
}
|
|
617
|
-
const
|
|
618
|
-
const
|
|
713
|
+
const completedGraph = executionState.planGraph;
|
|
714
|
+
const terminal = selected.isPrimary
|
|
715
|
+
? finalizeTerminalPrimaryPlan(executionState, completedGraph, advance)
|
|
716
|
+
: null;
|
|
717
|
+
const live = terminal
|
|
718
|
+
? completedGraph
|
|
719
|
+
: executionState.planGraph;
|
|
720
|
+
const { readyNodes, waitingNodes } = summarizeReadyWaiting(
|
|
721
|
+
terminal ? [] : (live.nodes || []),
|
|
722
|
+
);
|
|
619
723
|
const payload = controlResult.ok
|
|
620
724
|
? accepted({
|
|
621
|
-
graphId: live.graphId || "",
|
|
622
|
-
commandRevision: Number(live.specRevision) || 0,
|
|
623
|
-
stateRevision: Number(live.stateRevision) || 0,
|
|
624
|
-
revision: Number(live.specRevision) || 0,
|
|
725
|
+
graphId: terminal ? "" : (live.graphId || ""),
|
|
726
|
+
commandRevision: terminal ? 0 : (Number(live.specRevision) || 0),
|
|
727
|
+
stateRevision: terminal ? 0 : (Number(live.stateRevision) || 0),
|
|
728
|
+
revision: terminal ? 0 : (Number(live.specRevision) || 0),
|
|
625
729
|
control: controlResult,
|
|
626
|
-
summary:
|
|
627
|
-
?
|
|
628
|
-
: (advance.
|
|
629
|
-
|
|
730
|
+
summary: terminal
|
|
731
|
+
? terminal.completionSummary
|
|
732
|
+
: (advance.waitingFor
|
|
733
|
+
? `waiting on ${advance.waitingFor.type}:${advance.waitingFor.id || ""}`
|
|
734
|
+
: (advance.yieldReason || "control actions applied")),
|
|
735
|
+
changes: {
|
|
736
|
+
nodesAdded: [],
|
|
737
|
+
nodesUpdated: [],
|
|
738
|
+
...(terminal ? { archivedGraphId: terminal.archivedGraphId } : {}),
|
|
739
|
+
},
|
|
630
740
|
nodesAdded: [],
|
|
631
741
|
nodesUpdated: [],
|
|
632
742
|
readyNodes,
|
|
633
743
|
waitingNodes,
|
|
634
|
-
waitingFor: live.waitingFor || null,
|
|
744
|
+
waitingFor: terminal ? null : (live.waitingFor || null),
|
|
635
745
|
advance,
|
|
636
|
-
planView: projectPlanView(live),
|
|
746
|
+
planView: terminal ? [] : projectPlanView(live),
|
|
637
747
|
validationWarnings: [],
|
|
748
|
+
...(terminal ? {
|
|
749
|
+
planCompleted: true,
|
|
750
|
+
archivedGraphId: terminal.archivedGraphId,
|
|
751
|
+
completionSummary: terminal.completionSummary,
|
|
752
|
+
} : {}),
|
|
638
753
|
})
|
|
639
754
|
: rejected(controlResult.errors || [{
|
|
640
755
|
code: "CONTROL_REJECTED",
|
|
641
756
|
message: "one or more control actions rejected",
|
|
642
757
|
}], { control: controlResult });
|
|
643
|
-
if (commandId && payload.status === "accepted") {
|
|
758
|
+
if (commandId && payload.status === "accepted" && !terminal) {
|
|
644
759
|
cacheCommand(executionState.planGraph, commandId, payload);
|
|
645
760
|
}
|
|
761
|
+
restorePrimaryGraph();
|
|
646
762
|
return { ...payload, executionState, modelPayload: payload, ok: payload.status === "accepted" };
|
|
647
763
|
}
|
|
648
764
|
|
|
649
765
|
if (operation === "cancel_graph" || operation === "clear") {
|
|
650
766
|
const previousId = planGraph.graphId || "";
|
|
767
|
+
if (previousId) {
|
|
768
|
+
archivePlanGraph(executionState, planGraph, { reason: "cancel_graph" });
|
|
769
|
+
}
|
|
651
770
|
executionState.planGraph = emptyPlanGraphState();
|
|
652
771
|
executionState.mode = "single_action";
|
|
772
|
+
try {
|
|
773
|
+
const { getPlanModeSource, setPlanMode } = require("./planMode");
|
|
774
|
+
if (getPlanModeSource(executionState) === "auto") {
|
|
775
|
+
setPlanMode(executionState, false, { reason: "plan cancelled" });
|
|
776
|
+
}
|
|
777
|
+
} catch {
|
|
778
|
+
// ignore
|
|
779
|
+
}
|
|
653
780
|
const payload = accepted({
|
|
654
781
|
graphId: "",
|
|
655
782
|
commandRevision: 0,
|
|
@@ -662,6 +789,9 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
662
789
|
waitingNodes: [],
|
|
663
790
|
advance: { status: "completed", yieldReason: "cancelled", executedNodes: [], failedNodes: [] },
|
|
664
791
|
validationWarnings: [],
|
|
792
|
+
summary: previousId
|
|
793
|
+
? `Plan ${previousId} cancelled and cleared.`
|
|
794
|
+
: "No active plan to cancel.",
|
|
665
795
|
});
|
|
666
796
|
restorePrimaryGraph();
|
|
667
797
|
return { ...payload, executionState, modelPayload: payload };
|
|
@@ -697,15 +827,22 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
697
827
|
return rejectWorking(payload);
|
|
698
828
|
}
|
|
699
829
|
|
|
700
|
-
const beforeIds =
|
|
830
|
+
const beforeIds = operation === "create"
|
|
831
|
+
? new Set()
|
|
832
|
+
: new Set(listNodeIds(planGraph.nodes));
|
|
701
833
|
let nextPlan = {
|
|
702
834
|
id: planGraph.graphId || createPlanId("plan"),
|
|
703
835
|
objective: planGraph.objective || "",
|
|
704
836
|
nodes: snapshotNodes(planGraph),
|
|
705
837
|
};
|
|
706
838
|
let nodesUpdated = [];
|
|
839
|
+
let replacedGraphId = "";
|
|
707
840
|
|
|
708
841
|
if (operation === "create") {
|
|
842
|
+
if (selected.isPrimary && planGraph.graphId) {
|
|
843
|
+
replacedGraphId = String(planGraph.graphId);
|
|
844
|
+
archivePlanGraph(executionState, planGraph, { reason: "replaced_by_create" });
|
|
845
|
+
}
|
|
709
846
|
const graphSource = command.graph || {};
|
|
710
847
|
if (Array.isArray(graphSource.nodes)) {
|
|
711
848
|
nextPlan = normalizePlanGraph({
|
|
@@ -717,6 +854,11 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
717
854
|
nextPlan.nodes = stripModelStatuses(nextPlan.nodes);
|
|
718
855
|
}
|
|
719
856
|
if (!nextPlan.id) nextPlan.id = createPlanId("plan");
|
|
857
|
+
// Prefer a fresh id when replacing a live plan so the new graph does not
|
|
858
|
+
// collide with the archived one in graphs[].
|
|
859
|
+
if (replacedGraphId && nextPlan.id === replacedGraphId) {
|
|
860
|
+
nextPlan.id = createPlanId("plan");
|
|
861
|
+
}
|
|
720
862
|
nodesUpdated = listNodeIds(nextPlan.nodes);
|
|
721
863
|
// Parent graphs are owned by the agent loop.
|
|
722
864
|
const { agentLoopOwner } = require("../runtime/graphOwner");
|
|
@@ -798,9 +940,10 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
798
940
|
}
|
|
799
941
|
|
|
800
942
|
// Prefer the patched node list (includes aggregate expand status).
|
|
943
|
+
// create starts clean — do not inherit statuses from a replaced graph.
|
|
801
944
|
const preferredNodes = applyStatusesFromStore(
|
|
802
945
|
(Array.isArray(nextPlan.nodes) ? nextPlan.nodes : []).map((node) => normalizePlanNode(node, node.id)),
|
|
803
|
-
planGraph.nodes,
|
|
946
|
+
operation === "create" ? [] : planGraph.nodes,
|
|
804
947
|
{ preferSourceStatus: operation === "patch" },
|
|
805
948
|
);
|
|
806
949
|
|
|
@@ -831,28 +974,53 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
831
974
|
}
|
|
832
975
|
}
|
|
833
976
|
|
|
834
|
-
const specRevision =
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
||
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
977
|
+
const specRevision = operation === "create"
|
|
978
|
+
? 1
|
|
979
|
+
: (Number(planGraph.specRevision) || 0) + 1;
|
|
980
|
+
if (operation === "create") {
|
|
981
|
+
executionState.planGraph = {
|
|
982
|
+
...emptyPlanGraphState(),
|
|
983
|
+
graphId: preferredCompile.planId || nextPlan.id,
|
|
984
|
+
specRevision,
|
|
985
|
+
stateRevision: 0,
|
|
986
|
+
revision: specRevision,
|
|
987
|
+
objective: preferredCompile.objective || nextPlan.objective || "",
|
|
988
|
+
failurePolicy: preferredCompile.failurePolicy
|
|
989
|
+
|| nextPlan.failurePolicy
|
|
990
|
+
|| "continue_independent",
|
|
991
|
+
nodes: mergedNodes,
|
|
992
|
+
outputs: {},
|
|
993
|
+
waitingFor: null,
|
|
994
|
+
lastStoppedAt: "",
|
|
995
|
+
lastYieldReason: "",
|
|
996
|
+
commandLog: {},
|
|
997
|
+
owner: nextPlan.owner || null,
|
|
998
|
+
parentGraphId: "",
|
|
999
|
+
parentNodeId: "",
|
|
1000
|
+
};
|
|
1001
|
+
} else {
|
|
1002
|
+
executionState.planGraph = {
|
|
1003
|
+
...planGraph,
|
|
1004
|
+
graphId: preferredCompile.planId || nextPlan.id,
|
|
1005
|
+
specRevision,
|
|
1006
|
+
stateRevision: Number(planGraph.stateRevision) || 0,
|
|
1007
|
+
revision: specRevision,
|
|
1008
|
+
objective: preferredCompile.objective || nextPlan.objective || "",
|
|
1009
|
+
failurePolicy: preferredCompile.failurePolicy
|
|
1010
|
+
|| nextPlan.failurePolicy
|
|
1011
|
+
|| planGraph.failurePolicy
|
|
1012
|
+
|| "continue_independent",
|
|
1013
|
+
nodes: mergedNodes,
|
|
1014
|
+
outputs: { ...(planGraph.outputs || {}) },
|
|
1015
|
+
waitingFor: null,
|
|
1016
|
+
lastStoppedAt: "",
|
|
1017
|
+
lastYieldReason: "",
|
|
1018
|
+
commandLog: planGraph.commandLog || {},
|
|
1019
|
+
owner: nextPlan.owner || planGraph.owner || null,
|
|
1020
|
+
parentGraphId: planGraph.parentGraphId || "",
|
|
1021
|
+
parentNodeId: planGraph.parentNodeId || "",
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
856
1024
|
if (!executionState.graphs || typeof executionState.graphs !== "object") {
|
|
857
1025
|
executionState.graphs = {};
|
|
858
1026
|
}
|
|
@@ -890,45 +1058,80 @@ function runPlanGraphCommand(commandInput = {}, options = {}) {
|
|
|
890
1058
|
...advanced.planGraph,
|
|
891
1059
|
specRevision,
|
|
892
1060
|
revision: specRevision,
|
|
893
|
-
commandLog: planGraph.commandLog || {},
|
|
894
|
-
owner: advanced.planGraph.owner || planGraph.owner || null,
|
|
895
|
-
parentGraphId:
|
|
896
|
-
|
|
1061
|
+
commandLog: operation === "create" ? {} : (planGraph.commandLog || {}),
|
|
1062
|
+
owner: advanced.planGraph.owner || (operation === "create" ? nextPlan.owner : planGraph.owner) || null,
|
|
1063
|
+
parentGraphId: operation === "create"
|
|
1064
|
+
? ""
|
|
1065
|
+
: (advanced.planGraph.parentGraphId || planGraph.parentGraphId || ""),
|
|
1066
|
+
parentNodeId: operation === "create"
|
|
1067
|
+
? ""
|
|
1068
|
+
: (advanced.planGraph.parentNodeId || planGraph.parentNodeId || ""),
|
|
897
1069
|
};
|
|
898
1070
|
}
|
|
899
1071
|
if (advanced.advance) advance = advanced.advance;
|
|
900
1072
|
}
|
|
901
1073
|
|
|
902
|
-
const
|
|
903
|
-
const
|
|
1074
|
+
const completedGraph = executionState.planGraph;
|
|
1075
|
+
const terminal = selected.isPrimary
|
|
1076
|
+
? finalizeTerminalPrimaryPlan(executionState, completedGraph, advance)
|
|
1077
|
+
: null;
|
|
1078
|
+
if (!terminal && !selected.isPrimary && advance.yieldReason === "graph_terminal") {
|
|
1079
|
+
// Child TaskLoop graph finished — keep snapshot; parent stays active.
|
|
1080
|
+
const childId = String(completedGraph.graphId || "").trim();
|
|
1081
|
+
if (childId) {
|
|
1082
|
+
executionState.graphs[childId] = {
|
|
1083
|
+
...completedGraph,
|
|
1084
|
+
completedAt: new Date().toISOString(),
|
|
1085
|
+
lastYieldReason: "graph_terminal",
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const live = terminal ? completedGraph : executionState.planGraph;
|
|
1091
|
+
const { readyNodes, waitingNodes } = summarizeReadyWaiting(
|
|
1092
|
+
terminal ? [] : (live.nodes || []),
|
|
1093
|
+
);
|
|
904
1094
|
const payload = accepted({
|
|
905
|
-
graphId: live.graphId,
|
|
906
|
-
commandRevision: live.specRevision,
|
|
907
|
-
stateRevision: live.stateRevision,
|
|
908
|
-
revision: live.specRevision,
|
|
1095
|
+
graphId: terminal ? "" : live.graphId,
|
|
1096
|
+
commandRevision: terminal ? 0 : live.specRevision,
|
|
1097
|
+
stateRevision: terminal ? 0 : live.stateRevision,
|
|
1098
|
+
revision: terminal ? 0 : live.specRevision,
|
|
909
1099
|
changes: {
|
|
910
1100
|
nodesAdded,
|
|
911
1101
|
nodesUpdated: Array.from(new Set(nodesUpdated)),
|
|
1102
|
+
...(replacedGraphId ? { replacedGraphId } : {}),
|
|
1103
|
+
...(terminal ? { archivedGraphId: terminal.archivedGraphId } : {}),
|
|
912
1104
|
},
|
|
913
1105
|
nodesAdded,
|
|
914
1106
|
nodesUpdated: Array.from(new Set(nodesUpdated)),
|
|
915
1107
|
readyNodes,
|
|
916
1108
|
waitingNodes,
|
|
917
|
-
waitingFor: live.waitingFor || null,
|
|
918
|
-
stoppedAt: live.lastStoppedAt || "",
|
|
1109
|
+
waitingFor: terminal ? null : (live.waitingFor || null),
|
|
1110
|
+
stoppedAt: terminal ? "graph_terminal" : (live.lastStoppedAt || ""),
|
|
919
1111
|
advance,
|
|
920
|
-
planView: projectPlanView(live),
|
|
1112
|
+
planView: terminal ? [] : projectPlanView(live),
|
|
921
1113
|
validationWarnings: preferredCompile.warnings || [],
|
|
922
|
-
summary:
|
|
923
|
-
?
|
|
924
|
-
: (advance.
|
|
1114
|
+
summary: terminal
|
|
1115
|
+
? terminal.completionSummary
|
|
1116
|
+
: (advance.waitingFor
|
|
1117
|
+
? `waiting on ${advance.waitingFor.type}:${advance.waitingFor.id || ""}`
|
|
1118
|
+
: (advance.yieldReason || "graph updated")),
|
|
1119
|
+
...(replacedGraphId ? { replacedGraphId } : {}),
|
|
1120
|
+
...(terminal ? {
|
|
1121
|
+
planCompleted: true,
|
|
1122
|
+
archivedGraphId: terminal.archivedGraphId,
|
|
1123
|
+
completionSummary: terminal.completionSummary,
|
|
1124
|
+
} : {}),
|
|
925
1125
|
});
|
|
926
1126
|
|
|
927
|
-
|
|
1127
|
+
if (!terminal) {
|
|
1128
|
+
cacheCommand(executionState.planGraph, commandId, payload);
|
|
1129
|
+
}
|
|
928
1130
|
|
|
929
1131
|
// Agent Loop create enables Plan Mode; TaskLoop child graphs must not.
|
|
1132
|
+
// Skip when create immediately ran to terminal (already exited Plan Mode).
|
|
930
1133
|
let planModeEntered = null;
|
|
931
|
-
if (operation === "create" && payload.status === "accepted") {
|
|
1134
|
+
if (operation === "create" && payload.status === "accepted" && !terminal) {
|
|
932
1135
|
const ownerKind = String(
|
|
933
1136
|
(executionState.planGraph && executionState.planGraph.owner && executionState.planGraph.owner.kind)
|
|
934
1137
|
|| "agent_loop",
|
|
@@ -968,6 +1171,9 @@ function activePlanRequiresExpansion(planGraph = {}) {
|
|
|
968
1171
|
module.exports = {
|
|
969
1172
|
emptyPlanGraphState,
|
|
970
1173
|
ensurePlanGraphState,
|
|
1174
|
+
archivePlanGraph,
|
|
1175
|
+
buildPlanCompletionSummary,
|
|
1176
|
+
finalizeTerminalPrimaryPlan,
|
|
971
1177
|
normalizePlanGraphCommand,
|
|
972
1178
|
normalizeExpandOp,
|
|
973
1179
|
runPlanGraphCommand,
|
|
@@ -54,6 +54,7 @@ function buildImmutablePrefix() {
|
|
|
54
54
|
"- Turning Plan Mode off does not cancel an existing graph or running TaskRuns. Cancel with task_run (standalone) or plan_graph operation=cancel_graph / control.cancel_task (graph-bound).",
|
|
55
55
|
"- When the user enables Plan Mode and no active graph exists, create a plan_graph before performing side effects.",
|
|
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
|
+
"- When the last plan node finishes (graph_terminal), Runtime archives and clears the active plan, exits auto Plan Mode, and returns a completion summary — narrate that summary to the user. Start a new objective with plan_graph create (create replaces any leftover graph); do not patch a finished plan for a new goal.",
|
|
57
58
|
"- 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
59
|
"- 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
60
|
"- 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.",
|
|
@@ -131,7 +131,8 @@ function shouldAutoContinuePlan(executionState = null) {
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Internal reminder injected by runtime when the model ends a turn while the
|
|
134
|
-
* plan is still waiting on a task.
|
|
134
|
+
* plan is still waiting on a task. Must NOT reuse the User reminder label —
|
|
135
|
+
* that is reserved for real mid-run user text and pollutes transcript/TUI.
|
|
135
136
|
*/
|
|
136
137
|
function buildPlanAutoContinueReminder(executionState = null) {
|
|
137
138
|
const waiting = executionState
|
|
@@ -140,14 +141,17 @@ function buildPlanAutoContinueReminder(executionState = null) {
|
|
|
140
141
|
? executionState.planGraph.waitingFor
|
|
141
142
|
: null;
|
|
142
143
|
if (!waiting || !waiting.id) return "";
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{
|
|
150
|
-
|
|
144
|
+
const label = waiting.title || waiting.objective || waiting.reason || waiting.id;
|
|
145
|
+
const lines = [
|
|
146
|
+
"Runtime wake (not a user message): active plan is still waiting.",
|
|
147
|
+
"Continue serving it now via plan_graph "
|
|
148
|
+
+ "(expand_node, control.start_task, or control.complete_task as appropriate). "
|
|
149
|
+
+ "Do not end the turn with text only while this node is waiting.",
|
|
150
|
+
`Prefer serving the current waiting ${waiting.type || "node"}: ${waiting.id}`
|
|
151
|
+
+ (label && label !== waiting.id ? ` — ${label}` : "")
|
|
152
|
+
+ ".",
|
|
153
|
+
];
|
|
154
|
+
return lines.join("\n");
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
module.exports = {
|
package/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -1131,7 +1131,19 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1131
1131
|
|
|
1132
1132
|
// While a native task is in flight, queue an additional user reminder
|
|
1133
1133
|
// for the next LLM turn instead of starting a second NL task.
|
|
1134
|
+
// Slash commands (/model, /plan, …) must still run immediately — same
|
|
1135
|
+
// rule as the REPL path — otherwise they pollute the nudge queue.
|
|
1134
1136
|
if (pendingTaskRef.current) {
|
|
1137
|
+
if (/^\//.test(trimmed)) {
|
|
1138
|
+
runChainRef.current = runChainRef.current
|
|
1139
|
+
.then(() => executeLine(modelText, {
|
|
1140
|
+
modelText,
|
|
1141
|
+
logText,
|
|
1142
|
+
preserveNewlines: attachments.length > 0,
|
|
1143
|
+
}))
|
|
1144
|
+
.catch((err) => appendLogText(`Error: ${err && err.message ? err.message : err}`, "error"));
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1135
1147
|
const { enqueueUserPrompt } = require("../../code/context/userNudge");
|
|
1136
1148
|
const { emptyExecutionState } = require("../../code/context/executionSegment");
|
|
1137
1149
|
if (!props.state || typeof props.state !== "object") {
|