u-foo 3.0.6 → 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 +1 -1
- package/src/code/agent.js +42 -0
- package/src/code/context/executionSegment.js +1 -0
- package/src/code/context/planGraphService.js +261 -55
- package/src/code/context/planProjection.js +163 -62
- package/src/code/context/promptLayers.js +1 -0
- package/src/code/context/userNudge.js +13 -9
- package/src/code/contextWindow.js +117 -0
- package/src/code/nativeRunner.js +116 -29
- package/src/code/runtime/index.js +1 -0
- package/src/code/runtime/taskFocusContext.js +190 -0
- package/src/code/sessionStore.js +9 -0
- package/src/ui/format/index.js +8 -14
- package/src/ui/ink/ChatApp.js +20 -8
- package/src/ui/ink/UcodeApp.js +87 -4
- package/src/ui/ink/chatLogModel.js +4 -2
package/package.json
CHANGED
package/src/code/agent.js
CHANGED
|
@@ -610,6 +610,21 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
610
610
|
: runNativeAgentTask;
|
|
611
611
|
const onPhase = typeof options.onPhase === "function" ? options.onPhase : null;
|
|
612
612
|
const onThinkingDelta = typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null;
|
|
613
|
+
const onContextUsage = typeof options.onContextUsage === "function" ? options.onContextUsage : null;
|
|
614
|
+
const applyContextMeter = (meter = null) => {
|
|
615
|
+
if (!meter || typeof meter !== "object") return null;
|
|
616
|
+
state.contextMeter = {
|
|
617
|
+
usedTokens: Number(meter.usedTokens) || 0,
|
|
618
|
+
limitTokens: Number(meter.limitTokens) || 0,
|
|
619
|
+
model: String(meter.model || state.model || "").trim(),
|
|
620
|
+
label: String(meter.label || "").trim(),
|
|
621
|
+
updatedAt: String(meter.updatedAt || "").trim() || new Date().toISOString(),
|
|
622
|
+
};
|
|
623
|
+
if (typeof onContextUsage === "function") {
|
|
624
|
+
try { onContextUsage(state.contextMeter); } catch { /* ignore */ }
|
|
625
|
+
}
|
|
626
|
+
return state.contextMeter;
|
|
627
|
+
};
|
|
613
628
|
let lastTranscriptBaseline = 0;
|
|
614
629
|
const invokeNative = (sessionIdValue = "", timeoutOverrideMs = timeoutMs) => {
|
|
615
630
|
toolEventsThisAttempt = 0;
|
|
@@ -630,6 +645,7 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
630
645
|
onStreamDelta: onStream,
|
|
631
646
|
onThinkingDelta,
|
|
632
647
|
onPhase,
|
|
648
|
+
onContextUsage: applyContextMeter,
|
|
633
649
|
executionState: state.executionState || null,
|
|
634
650
|
onArtifactPersisted: (persisted) => recordToolCallInSession(state, persisted, workspaceRoot),
|
|
635
651
|
onToolEvent: (event) => {
|
|
@@ -779,6 +795,9 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
779
795
|
const artifactIds = Array.isArray(state.workingSet)
|
|
780
796
|
? state.workingSet.map((entry) => entry.artifactId).filter(Boolean)
|
|
781
797
|
: [];
|
|
798
|
+
if (cliRes && cliRes.contextMeter) {
|
|
799
|
+
applyContextMeter(cliRes.contextMeter);
|
|
800
|
+
}
|
|
782
801
|
if (cliRes && cliRes.waitingUserInteraction) {
|
|
783
802
|
return {
|
|
784
803
|
ok: true,
|
|
@@ -791,6 +810,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
791
810
|
streamLastChar,
|
|
792
811
|
waitingUserInteraction: true,
|
|
793
812
|
interactionId: cliRes.interactionId || "",
|
|
813
|
+
contextMeter: state.contextMeter || null,
|
|
814
|
+
usage: cliRes.usage || null,
|
|
794
815
|
};
|
|
795
816
|
}
|
|
796
817
|
return {
|
|
@@ -802,6 +823,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
|
|
|
802
823
|
metrics: {},
|
|
803
824
|
streamed: Boolean(streamed || cliRes.streamed),
|
|
804
825
|
streamLastChar,
|
|
826
|
+
contextMeter: state.contextMeter || null,
|
|
827
|
+
usage: cliRes.usage || null,
|
|
805
828
|
};
|
|
806
829
|
} catch (err) {
|
|
807
830
|
return {
|
|
@@ -925,6 +948,9 @@ function buildSessionSnapshotFromState(state = {}) {
|
|
|
925
948
|
? Math.max(0, Math.floor(source.toolCallsSinceCommit))
|
|
926
949
|
: 0,
|
|
927
950
|
activeSkills: Array.isArray(source.activeSkills) ? source.activeSkills : [],
|
|
951
|
+
contextMeter: source.contextMeter && typeof source.contextMeter === "object"
|
|
952
|
+
? source.contextMeter
|
|
953
|
+
: null,
|
|
928
954
|
};
|
|
929
955
|
}
|
|
930
956
|
|
|
@@ -999,6 +1025,9 @@ function resumeSessionState(state = {}, sessionId = "", workspaceRoot = process.
|
|
|
999
1025
|
? snapshot.toolCallsSinceCommit
|
|
1000
1026
|
: 0;
|
|
1001
1027
|
state.activeSkills = Array.isArray(snapshot.activeSkills) ? snapshot.activeSkills : [];
|
|
1028
|
+
state.contextMeter = snapshot.contextMeter && typeof snapshot.contextMeter === "object"
|
|
1029
|
+
? snapshot.contextMeter
|
|
1030
|
+
: null;
|
|
1002
1031
|
ensureContextSessionState(state);
|
|
1003
1032
|
const { ensureTranscript } = require("./context/assembler");
|
|
1004
1033
|
ensureTranscript(state, state.workspaceRoot);
|
|
@@ -1087,6 +1116,13 @@ async function resumeAfterUserInteraction(answerText = "", state = {}, options =
|
|
|
1087
1116
|
onStreamDelta: trackingOnDelta,
|
|
1088
1117
|
onThinkingDelta: typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null,
|
|
1089
1118
|
onPhase: typeof options.onPhase === "function" ? options.onPhase : null,
|
|
1119
|
+
onContextUsage: (meter) => {
|
|
1120
|
+
if (!meter || typeof meter !== "object") return;
|
|
1121
|
+
state.contextMeter = meter;
|
|
1122
|
+
if (typeof options.onContextUsage === "function") {
|
|
1123
|
+
try { options.onContextUsage(meter); } catch { /* ignore */ }
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1090
1126
|
executionState: state.executionState,
|
|
1091
1127
|
signal: options.signal,
|
|
1092
1128
|
resume: true,
|
|
@@ -1098,6 +1134,9 @@ async function resumeAfterUserInteraction(answerText = "", state = {}, options =
|
|
|
1098
1134
|
if (cliRes && Array.isArray(cliRes.messages)) {
|
|
1099
1135
|
state.nlMessages = stripSkillBlocksFromMessages(cliRes.messages);
|
|
1100
1136
|
}
|
|
1137
|
+
if (cliRes && cliRes.contextMeter) {
|
|
1138
|
+
state.contextMeter = cliRes.contextMeter;
|
|
1139
|
+
}
|
|
1101
1140
|
|
|
1102
1141
|
if (!cliRes || cliRes.ok === false) {
|
|
1103
1142
|
return {
|
|
@@ -1107,6 +1146,7 @@ async function resumeAfterUserInteraction(answerText = "", state = {}, options =
|
|
|
1107
1146
|
waitingUserInteraction: false,
|
|
1108
1147
|
streamed: false,
|
|
1109
1148
|
streamLastChar: "",
|
|
1149
|
+
contextMeter: state.contextMeter || null,
|
|
1110
1150
|
};
|
|
1111
1151
|
}
|
|
1112
1152
|
|
|
@@ -1119,6 +1159,7 @@ async function resumeAfterUserInteraction(answerText = "", state = {}, options =
|
|
|
1119
1159
|
interactionId: cliRes.interactionId || "",
|
|
1120
1160
|
streamed: Boolean(cliRes.streamed),
|
|
1121
1161
|
streamLastChar,
|
|
1162
|
+
contextMeter: state.contextMeter || null,
|
|
1122
1163
|
};
|
|
1123
1164
|
}
|
|
1124
1165
|
|
|
@@ -1129,6 +1170,7 @@ async function resumeAfterUserInteraction(answerText = "", state = {}, options =
|
|
|
1129
1170
|
waitingUserInteraction: false,
|
|
1130
1171
|
streamed: Boolean(cliRes.streamed),
|
|
1131
1172
|
streamLastChar,
|
|
1173
|
+
contextMeter: state.contextMeter || null,
|
|
1132
1174
|
};
|
|
1133
1175
|
}
|
|
1134
1176
|
|
|
@@ -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,
|