u-foo 3.0.4 → 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 +1 -1
- package/src/code/context/planGraphService.js +140 -16
- package/src/code/context/planProjection.js +199 -42
- package/src/code/context/promptLayers.js +1 -0
- package/src/code/context/userInteraction.js +29 -4
- package/src/code/nativeRunner.js +60 -13
- package/src/code/runtime/agentWakeup.js +66 -1
- package/src/ui/ink/UcodeApp.js +24 -9
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
|
};
|
|
@@ -204,6 +204,168 @@ function buildCompactSummary(rows = [], focusId = "") {
|
|
|
204
204
|
.join(" · ");
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Top-level plan tasks from planGraph JSON (no parent, not generated tools).
|
|
209
|
+
*/
|
|
210
|
+
function listTopLevelPlanTasks(planGraph = {}) {
|
|
211
|
+
const nodes = Array.isArray(planGraph.nodes) ? planGraph.nodes : [];
|
|
212
|
+
return nodes.filter((node) => (
|
|
213
|
+
node
|
|
214
|
+
&& node.type === "task"
|
|
215
|
+
&& !String(node.parentTaskId || "").trim()
|
|
216
|
+
&& !node.generated
|
|
217
|
+
));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Parse planGraph JSON into a DAG IR: nodes, edges, dependency waves.
|
|
222
|
+
*/
|
|
223
|
+
function buildPlanDag(planGraph = {}) {
|
|
224
|
+
const tasks = listTopLevelPlanTasks(planGraph);
|
|
225
|
+
const idSet = new Set(tasks.map((node) => String(node.id || "").trim()).filter(Boolean));
|
|
226
|
+
const nodes = tasks.map((task) => {
|
|
227
|
+
const id = String(task.id || "").trim();
|
|
228
|
+
const deps = (Array.isArray(task.dependsOn) ? task.dependsOn : [])
|
|
229
|
+
.map((dep) => String(dep || "").trim())
|
|
230
|
+
.filter((dep) => dep && idSet.has(dep));
|
|
231
|
+
const { mark, kind } = statusToMark(task.status);
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
title: nodeTitle(task),
|
|
235
|
+
status: String(task.status || "pending"),
|
|
236
|
+
mark,
|
|
237
|
+
kind,
|
|
238
|
+
dependsOn: deps,
|
|
239
|
+
displayOrder: Number(task.displayOrder) || 0,
|
|
240
|
+
};
|
|
241
|
+
}).filter((node) => node.id);
|
|
242
|
+
|
|
243
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
244
|
+
const edges = [];
|
|
245
|
+
for (const node of nodes) {
|
|
246
|
+
for (const dep of node.dependsOn) {
|
|
247
|
+
edges.push({ from: dep, to: node.id });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const depthMemo = new Map();
|
|
252
|
+
function depthOf(id, stack = new Set()) {
|
|
253
|
+
if (depthMemo.has(id)) return depthMemo.get(id);
|
|
254
|
+
if (stack.has(id)) return 0;
|
|
255
|
+
stack.add(id);
|
|
256
|
+
const node = byId.get(id);
|
|
257
|
+
let depth = 0;
|
|
258
|
+
if (node) {
|
|
259
|
+
for (const dep of node.dependsOn) {
|
|
260
|
+
depth = Math.max(depth, depthOf(dep, stack) + 1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
stack.delete(id);
|
|
264
|
+
depthMemo.set(id, depth);
|
|
265
|
+
return depth;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (const node of nodes) depthOf(node.id);
|
|
269
|
+
|
|
270
|
+
const maxDepth = nodes.reduce((max, node) => Math.max(max, depthMemo.get(node.id) || 0), 0);
|
|
271
|
+
const buckets = Array.from({ length: maxDepth + 1 }, () => []);
|
|
272
|
+
const ordered = nodes.slice().sort((a, b) => {
|
|
273
|
+
const depthDiff = (depthMemo.get(a.id) || 0) - (depthMemo.get(b.id) || 0);
|
|
274
|
+
if (depthDiff !== 0) return depthDiff;
|
|
275
|
+
if (a.displayOrder !== b.displayOrder) return a.displayOrder - b.displayOrder;
|
|
276
|
+
return a.id.localeCompare(b.id);
|
|
277
|
+
});
|
|
278
|
+
for (const node of ordered) {
|
|
279
|
+
buckets[depthMemo.get(node.id) || 0].push(node);
|
|
280
|
+
}
|
|
281
|
+
const waves = buckets.filter((wave) => wave.length > 0);
|
|
282
|
+
return {
|
|
283
|
+
nodes,
|
|
284
|
+
edges,
|
|
285
|
+
waves,
|
|
286
|
+
linear: waves.length > 0 && waves.every((wave) => wave.length === 1),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function waveStepLabel(waveIndex = 0, nodeIndex = 0, waveSize = 1) {
|
|
291
|
+
const step = Math.max(1, Math.floor(Number(waveIndex) || 0) + 1);
|
|
292
|
+
if (waveSize <= 1) return String(step);
|
|
293
|
+
const letter = String.fromCharCode(97 + Math.max(0, Math.min(25, Math.floor(Number(nodeIndex) || 0))));
|
|
294
|
+
return `${step}${letter}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function formatParallelWaveLines(wave = [], waveIndex = 0, titleMax = 40) {
|
|
298
|
+
const lines = [];
|
|
299
|
+
const size = wave.length;
|
|
300
|
+
wave.forEach((node, nodeIndex) => {
|
|
301
|
+
const label = waveStepLabel(waveIndex, nodeIndex, size);
|
|
302
|
+
const body = `${label} ${node.mark} ${truncate(node.title, titleMax)}`;
|
|
303
|
+
if (nodeIndex === 0) {
|
|
304
|
+
lines.push(` ┌─ ${body}`);
|
|
305
|
+
if (size > 1) lines.push("──┤");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (nodeIndex === size - 1) {
|
|
309
|
+
lines.push(` └─ ${body}`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
lines.push(` ├─ ${body}`);
|
|
313
|
+
});
|
|
314
|
+
return lines;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Build markdown (linear list) or ASCII flowchart (parallel waves) from planGraph JSON.
|
|
319
|
+
*/
|
|
320
|
+
function buildRoadmapMarkdown(planGraph = {}, {
|
|
321
|
+
cols = 80,
|
|
322
|
+
taskRunLine = "",
|
|
323
|
+
maxRows = 10,
|
|
324
|
+
} = {}) {
|
|
325
|
+
const dag = buildPlanDag(planGraph);
|
|
326
|
+
if (dag.nodes.length === 0) {
|
|
327
|
+
return { markdown: "", lines: [], dag };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const titleMax = Math.max(12, Math.min(48, Math.floor(Number(cols) || 80) - 12));
|
|
331
|
+
const objective = truncate(String(planGraph.objective || "").trim(), titleMax);
|
|
332
|
+
const lines = [objective ? `**Plan** · ${objective}` : "**Plan**"];
|
|
333
|
+
|
|
334
|
+
if (dag.linear) {
|
|
335
|
+
dag.waves.forEach((wave, waveIndex) => {
|
|
336
|
+
const node = wave[0];
|
|
337
|
+
lines.push(`${waveIndex + 1}. ${node.mark} ${truncate(node.title, titleMax)}`);
|
|
338
|
+
});
|
|
339
|
+
} else {
|
|
340
|
+
dag.waves.forEach((wave, waveIndex) => {
|
|
341
|
+
if (wave.length === 1) {
|
|
342
|
+
const node = wave[0];
|
|
343
|
+
lines.push(`${waveStepLabel(waveIndex, 0, 1)} ${node.mark} ${truncate(node.title, titleMax)}`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
for (const line of formatParallelWaveLines(wave, waveIndex, Math.max(8, titleMax - 4))) {
|
|
347
|
+
lines.push(line);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const extra = String(taskRunLine || "").trim();
|
|
353
|
+
if (extra) lines.push(extra);
|
|
354
|
+
|
|
355
|
+
const limit = Number.isFinite(maxRows) && maxRows > 0 ? Math.floor(maxRows) : 10;
|
|
356
|
+
let clipped = lines.slice(0, Math.max(1, limit));
|
|
357
|
+
if (lines.length > clipped.length) {
|
|
358
|
+
clipped = clipped.slice(0, Math.max(1, limit - 1));
|
|
359
|
+
clipped.push(`… +${lines.length - clipped.length} more`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
markdown: clipped.join("\n"),
|
|
364
|
+
lines: clipped,
|
|
365
|
+
dag,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
207
369
|
function buildDebugLines(executionState = null, planGraph = {}) {
|
|
208
370
|
const lines = [];
|
|
209
371
|
const pg = planGraph && typeof planGraph === "object" ? planGraph : {};
|
|
@@ -305,61 +467,52 @@ function buildPlanUiProjection(executionState = null, options = {}) {
|
|
|
305
467
|
const focusTitle = focus ? truncate(focus.title, narrow ? 18 : 28) : "";
|
|
306
468
|
|
|
307
469
|
let bandLines = [];
|
|
470
|
+
let roadmapMarkdown = "";
|
|
308
471
|
let visible = false;
|
|
472
|
+
let planDag = null;
|
|
473
|
+
|
|
474
|
+
const taskRunSuffix = (() => {
|
|
475
|
+
if (!taskRun) return "";
|
|
476
|
+
const leaseBit = leaseHeld ? "writing" : taskRun.phase;
|
|
477
|
+
const files = taskRun.changedFilesHint ? ` · ${taskRun.changedFilesHint}` : "";
|
|
478
|
+
return `TaskLoop ${leaseBit}${files}`;
|
|
479
|
+
})();
|
|
309
480
|
|
|
310
481
|
if (hasPlan && bandMode !== "hidden") {
|
|
311
482
|
visible = true;
|
|
312
483
|
if (bandMode === "debug") {
|
|
313
484
|
bandLines = buildDebugLines(state, pg);
|
|
485
|
+
roadmapMarkdown = "";
|
|
314
486
|
} else if (narrow) {
|
|
315
487
|
const summary = buildCompactSummary(tree, focus && focus.nodeId);
|
|
316
488
|
bandLines = [truncate(
|
|
317
489
|
`Plan${focusTitle ? ` · ${focusTitle}` : ""}${progressLabel ? ` (${progressLabel})` : ""}${summary && !focusTitle ? ` ${summary}` : ""}`,
|
|
318
490
|
Math.max(24, cols - 2)
|
|
319
491
|
)];
|
|
320
|
-
|
|
321
|
-
const summary = buildCompactSummary(tree, focus && focus.nodeId);
|
|
322
|
-
const header = truncate(
|
|
323
|
-
`Plan${pg.objective ? ` · ${pg.objective}` : ""}${summary ? ` ${summary}` : ""}`,
|
|
324
|
-
Math.max(24, cols - 2)
|
|
325
|
-
);
|
|
326
|
-
bandLines = [header];
|
|
327
|
-
if (focus) {
|
|
328
|
-
const focusChildren = view
|
|
329
|
-
.filter((node) => node && node.parentId === focus.nodeId)
|
|
330
|
-
.map((node) => {
|
|
331
|
-
const { mark } = statusToMark(node.status);
|
|
332
|
-
return `${mark} ${nodeTitle(node)}`;
|
|
333
|
-
});
|
|
334
|
-
if (focusChildren.length > 0) {
|
|
335
|
-
bandLines.push(truncate(
|
|
336
|
-
` └ ${focusChildren.join(" · ")}`,
|
|
337
|
-
Math.max(24, cols - 2)
|
|
338
|
-
));
|
|
339
|
-
} else if (focus.title) {
|
|
340
|
-
bandLines.push(truncate(` → ${focus.title}`, Math.max(24, cols - 2)));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
if (taskRun) {
|
|
344
|
-
const leaseBit = leaseHeld ? "writing" : taskRun.phase;
|
|
345
|
-
const files = taskRun.changedFilesHint ? ` · ${taskRun.changedFilesHint}` : "";
|
|
346
|
-
bandLines.push(truncate(` TaskLoop ${leaseBit}${files}`, Math.max(24, cols - 2)));
|
|
347
|
-
}
|
|
348
|
-
const maxRows = Number.isFinite(options.maxBandRows) ? options.maxBandRows : 3;
|
|
349
|
-
bandLines = bandLines.slice(0, Math.max(1, maxRows));
|
|
492
|
+
roadmapMarkdown = "";
|
|
350
493
|
} else {
|
|
351
|
-
// expanded
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
494
|
+
// auto + expanded: JSON → DAG → roadmap markdown
|
|
495
|
+
const maxRows = Number.isFinite(options.maxBandRows)
|
|
496
|
+
? options.maxBandRows
|
|
497
|
+
: (bandMode === "expanded" ? 16 : 10);
|
|
498
|
+
const roadmap = buildRoadmapMarkdown(pg, {
|
|
499
|
+
cols,
|
|
500
|
+
taskRunLine: taskRunSuffix,
|
|
501
|
+
maxRows,
|
|
502
|
+
});
|
|
503
|
+
planDag = roadmap.dag;
|
|
504
|
+
roadmapMarkdown = roadmap.markdown;
|
|
505
|
+
bandLines = roadmap.lines.slice();
|
|
506
|
+
if (bandMode === "expanded" && tree.length > 0) {
|
|
507
|
+
// Keep tree as fallback detail only when roadmap empty (shouldn't happen).
|
|
508
|
+
if (bandLines.length === 0) {
|
|
509
|
+
const title = pg.objective ? `Plan · ${pg.objective}` : "Plan";
|
|
510
|
+
bandLines = [truncate(title, Math.max(24, cols - 2))];
|
|
511
|
+
for (const row of tree) {
|
|
512
|
+
bandLines.push(truncate(formatTreeLine(row), Math.max(24, cols - 2)));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
360
515
|
}
|
|
361
|
-
const maxRows = Number.isFinite(options.maxBandRows) ? options.maxBandRows : 7;
|
|
362
|
-
bandLines = bandLines.slice(0, Math.max(1, maxRows));
|
|
363
516
|
}
|
|
364
517
|
}
|
|
365
518
|
|
|
@@ -402,7 +555,7 @@ function buildPlanUiProjection(executionState = null, options = {}) {
|
|
|
402
555
|
leaseHeld,
|
|
403
556
|
progressDone: progress.done,
|
|
404
557
|
progressTotal: progress.total,
|
|
405
|
-
bandLines,
|
|
558
|
+
bandLines: roadmapMarkdown ? [roadmapMarkdown] : bandLines,
|
|
406
559
|
});
|
|
407
560
|
|
|
408
561
|
return {
|
|
@@ -413,11 +566,13 @@ function buildPlanUiProjection(executionState = null, options = {}) {
|
|
|
413
566
|
progress,
|
|
414
567
|
focus,
|
|
415
568
|
tree,
|
|
569
|
+
dag: planDag,
|
|
416
570
|
taskRun,
|
|
417
571
|
leaseHeld,
|
|
418
572
|
statusLine,
|
|
419
573
|
idleHint,
|
|
420
574
|
activityStatusLine,
|
|
575
|
+
roadmapMarkdown,
|
|
421
576
|
bandLines,
|
|
422
577
|
hash,
|
|
423
578
|
};
|
|
@@ -428,5 +583,7 @@ module.exports = {
|
|
|
428
583
|
getBandMode,
|
|
429
584
|
setBandMode,
|
|
430
585
|
statusToMark,
|
|
586
|
+
buildPlanDag,
|
|
587
|
+
buildRoadmapMarkdown,
|
|
431
588
|
buildPlanUiProjection,
|
|
432
589
|
};
|
|
@@ -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;
|
package/src/code/nativeRunner.js
CHANGED
|
@@ -30,6 +30,12 @@ const {
|
|
|
30
30
|
shouldAutoContinuePlan,
|
|
31
31
|
buildPlanAutoContinueReminder,
|
|
32
32
|
} = require("./context/userNudge");
|
|
33
|
+
const {
|
|
34
|
+
drainAgentMailboxForTurn,
|
|
35
|
+
shouldAutoContinueForTaskWake,
|
|
36
|
+
buildTaskRunWakeReminder,
|
|
37
|
+
listTaskRunsAwaitingModel,
|
|
38
|
+
} = require("./runtime/agentWakeup");
|
|
33
39
|
const {
|
|
34
40
|
runAskUserTool,
|
|
35
41
|
syncInteractionFromPlanGraph,
|
|
@@ -1782,33 +1788,74 @@ async function runNativeLoop({
|
|
|
1782
1788
|
messages.push({ role: "user", content });
|
|
1783
1789
|
}
|
|
1784
1790
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1791
|
+
/** Deliver mid-loop TaskRun runtime events before the next model call. */
|
|
1792
|
+
function injectRuntimeMailboxEvents() {
|
|
1793
|
+
const drained = drainAgentMailboxForTurn(executionState);
|
|
1794
|
+
if (!drained.text) return false;
|
|
1795
|
+
messages.push({ role: "user", content: drained.text });
|
|
1796
|
+
return true;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
function nextAutoContinueKey() {
|
|
1788
1800
|
const waitingId = String(
|
|
1789
1801
|
(executionState.planGraph && executionState.planGraph.waitingFor
|
|
1790
1802
|
&& executionState.planGraph.waitingFor.id) || ""
|
|
1791
1803
|
).trim();
|
|
1804
|
+
if (waitingId) return `plan:${waitingId}`;
|
|
1805
|
+
const runs = listTaskRunsAwaitingModel(executionState);
|
|
1806
|
+
if (runs.length > 0) {
|
|
1807
|
+
return `task:${runs.map((run) => run.id).sort().join(",")}`;
|
|
1808
|
+
}
|
|
1809
|
+
return "mailbox";
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function tryInjectAgentAutoContinue() {
|
|
1813
|
+
if (planAutoContinues >= DEFAULT_MAX_PLAN_AUTO_CONTINUES) return false;
|
|
1814
|
+
const continueKey = nextAutoContinueKey();
|
|
1792
1815
|
if (
|
|
1793
1816
|
consecutiveEmptyAutoContinues >= 2
|
|
1794
|
-
&&
|
|
1795
|
-
&&
|
|
1817
|
+
&& continueKey
|
|
1818
|
+
&& continueKey === lastAutoContinueWaitingId
|
|
1796
1819
|
) {
|
|
1797
1820
|
return false;
|
|
1798
1821
|
}
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1822
|
+
|
|
1823
|
+
// Prefer draining fresh runtime mail (task_started, etc.) before nudges.
|
|
1824
|
+
if (injectRuntimeMailboxEvents()) {
|
|
1825
|
+
planAutoContinues += 1;
|
|
1826
|
+
lastAutoContinueWaitingId = continueKey;
|
|
1827
|
+
consecutiveEmptyAutoContinues += 1;
|
|
1828
|
+
return true;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
if (shouldAutoContinuePlan(executionState)) {
|
|
1832
|
+
const reminder = buildPlanAutoContinueReminder(executionState);
|
|
1833
|
+
if (!reminder) return false;
|
|
1834
|
+
messages.push({ role: "user", content: reminder });
|
|
1835
|
+
planAutoContinues += 1;
|
|
1836
|
+
lastAutoContinueWaitingId = continueKey;
|
|
1837
|
+
consecutiveEmptyAutoContinues += 1;
|
|
1838
|
+
return true;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (shouldAutoContinueForTaskWake(executionState)) {
|
|
1842
|
+
const reminder = buildTaskRunWakeReminder(executionState);
|
|
1843
|
+
if (!reminder) return false;
|
|
1844
|
+
messages.push({ role: "user", content: reminder });
|
|
1845
|
+
planAutoContinues += 1;
|
|
1846
|
+
lastAutoContinueWaitingId = continueKey;
|
|
1847
|
+
consecutiveEmptyAutoContinues += 1;
|
|
1848
|
+
return true;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
return false;
|
|
1806
1852
|
}
|
|
1807
1853
|
|
|
1808
1854
|
while (true) {
|
|
1809
1855
|
guards.ensureActive();
|
|
1810
1856
|
|
|
1811
1857
|
injectPendingUserReminders();
|
|
1858
|
+
injectRuntimeMailboxEvents();
|
|
1812
1859
|
|
|
1813
1860
|
if (activeLedger) {
|
|
1814
1861
|
runProviderTurnGate(activeLedger);
|
|
@@ -1876,7 +1923,7 @@ async function runNativeLoop({
|
|
|
1876
1923
|
if (!aggregated.trim() && text) {
|
|
1877
1924
|
aggregated = text;
|
|
1878
1925
|
}
|
|
1879
|
-
if (
|
|
1926
|
+
if (tryInjectAgentAutoContinue()) {
|
|
1880
1927
|
continue;
|
|
1881
1928
|
}
|
|
1882
1929
|
return {
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Drain Agent Loop mailbox into a turnDynamic block (never as user role).
|
|
5
|
+
* Mid-loop wakeups (nativeRunner) also consume the same mailbox into the
|
|
6
|
+
* conversation so TaskRun waiting_model does not strand the Agent Loop.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
const { drainAgentMailbox, ensureMailbox } = require("./loopMailbox");
|
|
9
|
+
const { drainAgentMailbox, ensureMailbox, peek } = require("./loopMailbox");
|
|
10
|
+
const { ensureTaskRunStore } = require("./taskRun");
|
|
8
11
|
|
|
9
12
|
function formatAgentRuntimeEvents(events = []) {
|
|
10
13
|
const list = Array.isArray(events) ? events : [];
|
|
@@ -43,6 +46,11 @@ function peekAgentMailboxText(executionState = null) {
|
|
|
43
46
|
return formatAgentRuntimeEvents(box.queue || []);
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
function hasPendingAgentMailbox(executionState = null) {
|
|
50
|
+
const state = executionState && typeof executionState === "object" ? executionState : {};
|
|
51
|
+
return Boolean(peek(ensureMailbox(state, "agentMailbox")));
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
function drainAgentMailboxForTurn(executionState = null) {
|
|
47
55
|
const events = drainAgentMailbox(executionState);
|
|
48
56
|
return {
|
|
@@ -51,8 +59,65 @@ function drainAgentMailboxForTurn(executionState = null) {
|
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
const AWAITING_MODEL_PHASES = new Set([
|
|
63
|
+
"waiting_model",
|
|
64
|
+
"planning",
|
|
65
|
+
"initializing",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* TaskRuns that still need the Agent Loop (model turn / planning).
|
|
70
|
+
*/
|
|
71
|
+
function listTaskRunsAwaitingModel(executionState = null) {
|
|
72
|
+
const store = ensureTaskRunStore(executionState);
|
|
73
|
+
return Object.values(store.byId || {}).filter((run) => (
|
|
74
|
+
run
|
|
75
|
+
&& (run.status === "running" || run.status === "queued")
|
|
76
|
+
&& AWAITING_MODEL_PHASES.has(String(run.phase || "").trim().toLowerCase())
|
|
77
|
+
));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function shouldWakeAgentForTaskRuns(executionState = null) {
|
|
81
|
+
return listTaskRunsAwaitingModel(executionState).length > 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Whether the Agent Loop must keep going after a text-only model turn
|
|
86
|
+
* because TaskRuns or unread runtime mail still need service.
|
|
87
|
+
*/
|
|
88
|
+
function shouldAutoContinueForTaskWake(executionState = null) {
|
|
89
|
+
if (!executionState || typeof executionState !== "object") return false;
|
|
90
|
+
if (executionState.pendingUserInteraction) return false;
|
|
91
|
+
return hasPendingAgentMailbox(executionState) || shouldWakeAgentForTaskRuns(executionState);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildTaskRunWakeReminder(executionState = null) {
|
|
95
|
+
const runs = listTaskRunsAwaitingModel(executionState);
|
|
96
|
+
if (runs.length === 0 && !hasPendingAgentMailbox(executionState)) return "";
|
|
97
|
+
const lines = [
|
|
98
|
+
"Runtime wake (not a user message): active TaskRun(s) still need the Agent Loop.",
|
|
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.",
|
|
102
|
+
];
|
|
103
|
+
for (const run of runs.slice(0, 4)) {
|
|
104
|
+
const label = run.title || run.objective || run.parentNodeId || run.id;
|
|
105
|
+
lines.push(
|
|
106
|
+
`- taskRunId=${run.id} phase=${run.phase || ""} status=${run.status || ""}`
|
|
107
|
+
+ (run.childGraphId ? ` childGraphId=${run.childGraphId}` : "")
|
|
108
|
+
+ (label ? ` — ${String(label).slice(0, 120)}` : "")
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return lines.join("\n");
|
|
112
|
+
}
|
|
113
|
+
|
|
54
114
|
module.exports = {
|
|
55
115
|
formatAgentRuntimeEvents,
|
|
56
116
|
peekAgentMailboxText,
|
|
117
|
+
hasPendingAgentMailbox,
|
|
57
118
|
drainAgentMailboxForTurn,
|
|
119
|
+
listTaskRunsAwaitingModel,
|
|
120
|
+
shouldWakeAgentForTaskRuns,
|
|
121
|
+
shouldAutoContinueForTaskWake,
|
|
122
|
+
buildTaskRunWakeReminder,
|
|
58
123
|
};
|
package/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -90,6 +90,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
90
90
|
hasPlan: false,
|
|
91
91
|
visible: false,
|
|
92
92
|
bandLines: [],
|
|
93
|
+
roadmapMarkdown: "",
|
|
93
94
|
idleHint: "",
|
|
94
95
|
statusLine: "",
|
|
95
96
|
hash: "",
|
|
@@ -171,7 +172,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
171
172
|
);
|
|
172
173
|
setPlanUi((prev) => (prev && prev.hash === next.hash ? prev : next));
|
|
173
174
|
const pending = getPendingUserInteraction(props.state && props.state.executionState);
|
|
174
|
-
setInteractionLines(pending ? formatInteractionPromptLines(pending
|
|
175
|
+
setInteractionLines(pending ? formatInteractionPromptLines(pending, {
|
|
176
|
+
cols: size.cols || 80,
|
|
177
|
+
}) : []);
|
|
175
178
|
return next;
|
|
176
179
|
} catch {
|
|
177
180
|
return null;
|
|
@@ -1248,18 +1251,30 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1248
1251
|
renderMergeText(activeMerge)
|
|
1249
1252
|
),
|
|
1250
1253
|
) : null,
|
|
1251
|
-
planUi.visible && planUi.bandLines.length > 0
|
|
1254
|
+
planUi.visible && (planUi.roadmapMarkdown || (planUi.bandLines && planUi.bandLines.length > 0))
|
|
1252
1255
|
? h(Box, {
|
|
1253
1256
|
flexDirection: "column",
|
|
1254
1257
|
width: "100%",
|
|
1255
1258
|
marginTop: 1,
|
|
1256
1259
|
},
|
|
1257
|
-
...
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1260
|
+
...(() => {
|
|
1261
|
+
let lines = Array.isArray(planUi.bandLines) ? planUi.bandLines.slice() : [];
|
|
1262
|
+
const md = String(planUi.roadmapMarkdown || "").trim();
|
|
1263
|
+
if (md) {
|
|
1264
|
+
try {
|
|
1265
|
+
const rendered = fmt.renderLogLinesWithMarkdownAnsi(md, { inCodeBlock: false });
|
|
1266
|
+
if (Array.isArray(rendered) && rendered.length > 0) lines = rendered;
|
|
1267
|
+
} catch {
|
|
1268
|
+
lines = md.split(/\r?\n/);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
return lines.map((line, idx) => h(Text, {
|
|
1272
|
+
key: `plan-band-${idx}`,
|
|
1273
|
+
color: md ? undefined : "magenta",
|
|
1274
|
+
dimColor: !md && idx > 0,
|
|
1275
|
+
wrap: "truncate",
|
|
1276
|
+
}, line || " "));
|
|
1277
|
+
})(),
|
|
1263
1278
|
)
|
|
1264
1279
|
: null,
|
|
1265
1280
|
interactionLines.length > 0
|
|
@@ -1271,7 +1286,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1271
1286
|
...interactionLines.map((line, idx) => h(Text, {
|
|
1272
1287
|
key: `ask-${idx}`,
|
|
1273
1288
|
color: "yellow",
|
|
1274
|
-
wrap: "
|
|
1289
|
+
wrap: "wrap",
|
|
1275
1290
|
}, line || " ")),
|
|
1276
1291
|
)
|
|
1277
1292
|
: null,
|