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/src/ui/ink/UcodeApp.js
CHANGED
|
@@ -98,6 +98,24 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
98
98
|
const [interactionLines, setInteractionLines] = useState([]);
|
|
99
99
|
const [spinnerTick, setSpinnerTick] = useState(0);
|
|
100
100
|
const [size, setSize] = useState({ cols: 0, rows: 0 });
|
|
101
|
+
const [contextMeter, setContextMeter] = useState(() => {
|
|
102
|
+
try {
|
|
103
|
+
const {
|
|
104
|
+
buildContextMeter,
|
|
105
|
+
normalizeContextMeter,
|
|
106
|
+
} = require("../../code/contextWindow");
|
|
107
|
+
const existing = props.state && props.state.contextMeter;
|
|
108
|
+
if (existing && typeof existing === "object") {
|
|
109
|
+
return normalizeContextMeter(existing, (props.state && props.state.model) || "");
|
|
110
|
+
}
|
|
111
|
+
return buildContextMeter({
|
|
112
|
+
usedTokens: 0,
|
|
113
|
+
model: (props.state && props.state.model) || process.env.UFOO_UCODE_MODEL || "",
|
|
114
|
+
});
|
|
115
|
+
} catch {
|
|
116
|
+
return { usedTokens: 0, limitTokens: 200000, label: "0 / 200K", model: "" };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
101
119
|
const [agents, setAgents] = useState([]);
|
|
102
120
|
const [selectedAgentIndex, setSelectedAgentIndex] = useState(-1);
|
|
103
121
|
const [agentSelectionMode, setAgentSelectionMode] = useState(false);
|
|
@@ -585,6 +603,21 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
585
603
|
workspaceRoot: runtimeWorkspace,
|
|
586
604
|
});
|
|
587
605
|
appendLogText(applied.output || "", applied.ok ? "system" : "error");
|
|
606
|
+
if (applied.ok && result.action === "set") {
|
|
607
|
+
try {
|
|
608
|
+
const { buildContextMeter } = require("../../code/contextWindow");
|
|
609
|
+
setContextMeter((prev) => {
|
|
610
|
+
const nextMeter = buildContextMeter({
|
|
611
|
+
usedTokens: (prev && prev.usedTokens) || 0,
|
|
612
|
+
model: (props.state && props.state.model) || "",
|
|
613
|
+
});
|
|
614
|
+
if (props.state && typeof props.state === "object") {
|
|
615
|
+
props.state.contextMeter = nextMeter;
|
|
616
|
+
}
|
|
617
|
+
return nextMeter;
|
|
618
|
+
});
|
|
619
|
+
} catch { /* ignore */ }
|
|
620
|
+
}
|
|
588
621
|
if (applied.ok && result.action === "set" && typeof props.persistSessionState === "function") {
|
|
589
622
|
try {
|
|
590
623
|
const persisted = props.persistSessionState(props.state);
|
|
@@ -687,6 +720,20 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
687
720
|
);
|
|
688
721
|
setActiveMerge(null);
|
|
689
722
|
lastMergeRef.current = null;
|
|
723
|
+
try {
|
|
724
|
+
const {
|
|
725
|
+
buildContextMeter,
|
|
726
|
+
normalizeContextMeter,
|
|
727
|
+
} = require("../../code/contextWindow");
|
|
728
|
+
const restored = props.state && props.state.contextMeter;
|
|
729
|
+
const nextMeter = restored && typeof restored === "object"
|
|
730
|
+
? normalizeContextMeter(restored, (props.state && props.state.model) || "")
|
|
731
|
+
: buildContextMeter({
|
|
732
|
+
usedTokens: 0,
|
|
733
|
+
model: (props.state && props.state.model) || "",
|
|
734
|
+
});
|
|
735
|
+
setContextMeter(nextMeter);
|
|
736
|
+
} catch { /* ignore */ }
|
|
690
737
|
return;
|
|
691
738
|
}
|
|
692
739
|
case "tool": {
|
|
@@ -782,6 +829,10 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
782
829
|
try {
|
|
783
830
|
nlResult = await props.runNaturalLanguageTask(result.task, props.state, {
|
|
784
831
|
signal: abortController.signal,
|
|
832
|
+
onContextUsage: (meter) => {
|
|
833
|
+
if (!meter || typeof meter !== "object") return;
|
|
834
|
+
setContextMeter(meter);
|
|
835
|
+
},
|
|
785
836
|
onPhase: (event) => {
|
|
786
837
|
if (!event || typeof event !== "object") return;
|
|
787
838
|
if (event.type === "request_start") {
|
|
@@ -870,6 +921,11 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
870
921
|
if (summary) appendLogText(summary);
|
|
871
922
|
}
|
|
872
923
|
flushActiveMerge();
|
|
924
|
+
if (nlResult && nlResult.contextMeter) {
|
|
925
|
+
setContextMeter(nlResult.contextMeter);
|
|
926
|
+
} else if (props.state && props.state.contextMeter) {
|
|
927
|
+
setContextMeter(props.state.contextMeter);
|
|
928
|
+
}
|
|
873
929
|
try {
|
|
874
930
|
const persisted = props.persistSessionState(props.state);
|
|
875
931
|
if (persisted && persisted.ok === false) {
|
|
@@ -1018,6 +1074,10 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1018
1074
|
let streamStarted = false;
|
|
1019
1075
|
let dropLeadingStreamBlank = false;
|
|
1020
1076
|
const result = await submit(trimmed, props.state, {
|
|
1077
|
+
onContextUsage: (meter) => {
|
|
1078
|
+
if (!meter || typeof meter !== "object") return;
|
|
1079
|
+
setContextMeter(meter);
|
|
1080
|
+
},
|
|
1021
1081
|
onDelta: (delta) => {
|
|
1022
1082
|
const text = String(delta || "");
|
|
1023
1083
|
if (!text) return;
|
|
@@ -1044,6 +1104,11 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1044
1104
|
}
|
|
1045
1105
|
flushTableBuffer();
|
|
1046
1106
|
refreshPlanUi();
|
|
1107
|
+
if (result && result.contextMeter) {
|
|
1108
|
+
setContextMeter(result.contextMeter);
|
|
1109
|
+
} else if (props.state && props.state.contextMeter) {
|
|
1110
|
+
setContextMeter(props.state.contextMeter);
|
|
1111
|
+
}
|
|
1047
1112
|
if (!result || result.ok === false) {
|
|
1048
1113
|
appendLogText(`Error: ${(result && result.error) || "resume failed"}`, "error");
|
|
1049
1114
|
} else if (result.shouldEchoSummary) {
|
|
@@ -1066,7 +1131,19 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1066
1131
|
|
|
1067
1132
|
// While a native task is in flight, queue an additional user reminder
|
|
1068
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.
|
|
1069
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
|
+
}
|
|
1070
1147
|
const { enqueueUserPrompt } = require("../../code/context/userNudge");
|
|
1071
1148
|
const { emptyExecutionState } = require("../../code/context/executionSegment");
|
|
1072
1149
|
if (!props.state || typeof props.state !== "object") {
|
|
@@ -1433,12 +1510,15 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1433
1510
|
? h(Text, { wrap: "truncate", color: "cyan" }, "none")
|
|
1434
1511
|
: (() => {
|
|
1435
1512
|
const labels = agents.map((a) => `@${getAgentLabel(a)}`);
|
|
1436
|
-
// Reserve
|
|
1437
|
-
//
|
|
1438
|
-
// when stdout.cols is unknown.
|
|
1513
|
+
// Reserve space for the context meter on the right plus the
|
|
1514
|
+
// "Agents: " prefix / hint. Clamp aggressively when cols unknown.
|
|
1439
1515
|
const cols = size.cols || 80;
|
|
1516
|
+
const meterLabel = String((contextMeter && contextMeter.label) || "").trim();
|
|
1517
|
+
const reservedForMeter = meterLabel
|
|
1518
|
+
? fmt.displayCellWidth(` ${meterLabel}`) + 1
|
|
1519
|
+
: 0;
|
|
1440
1520
|
const reservedForHint = fmt.displayCellWidth(` · ${agentsHint}`);
|
|
1441
|
-
const budget = Math.max(
|
|
1521
|
+
const budget = Math.max(12, cols - 10 - reservedForHint - reservedForMeter);
|
|
1442
1522
|
const plan = fmt.planAgentsFooter(
|
|
1443
1523
|
labels,
|
|
1444
1524
|
agentSelectionMode ? selectedAgentIndex : -1,
|
|
@@ -1461,6 +1541,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1461
1541
|
);
|
|
1462
1542
|
})(),
|
|
1463
1543
|
h(Text, { wrap: "truncate", color: "gray" }, ` · ${agentsHint}`),
|
|
1544
|
+
h(Box, { flexGrow: 1 }),
|
|
1545
|
+
h(Text, { wrap: "truncate", color: "gray" },
|
|
1546
|
+
String((contextMeter && contextMeter.label) || "").trim() || "0 / 200K"),
|
|
1464
1547
|
),
|
|
1465
1548
|
);
|
|
1466
1549
|
};
|
|
@@ -195,7 +195,9 @@ function applySourceTypeToRow(row, sourceType = "", meta = {}) {
|
|
|
195
195
|
return {
|
|
196
196
|
...base,
|
|
197
197
|
kind: "report",
|
|
198
|
-
|
|
198
|
+
// Prefer ● over ▣: square box glyphs sit off the Latin baseline in
|
|
199
|
+
// most terminal fonts and look misaligned next to speaker · body.
|
|
200
|
+
marker: "●",
|
|
199
201
|
speaker,
|
|
200
202
|
body: base.body || " ",
|
|
201
203
|
};
|
|
@@ -256,7 +258,7 @@ function defaultMarkerForKind(kind = "", speaker = "") {
|
|
|
256
258
|
if (kind === "user") return "›";
|
|
257
259
|
if (kind === "assistant") return "◆";
|
|
258
260
|
if (kind === "agent") return "◇";
|
|
259
|
-
if (kind === "report") return "
|
|
261
|
+
if (kind === "report") return "●";
|
|
260
262
|
if (kind === "error") return "!";
|
|
261
263
|
if (kind === "success") return "✓";
|
|
262
264
|
if (kind === "divider") return "─";
|