u-foo 3.0.6 → 3.0.7
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/planProjection.js +163 -62
- 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 +75 -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) {
|
|
@@ -1433,12 +1498,15 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1433
1498
|
? h(Text, { wrap: "truncate", color: "cyan" }, "none")
|
|
1434
1499
|
: (() => {
|
|
1435
1500
|
const labels = agents.map((a) => `@${getAgentLabel(a)}`);
|
|
1436
|
-
// Reserve
|
|
1437
|
-
//
|
|
1438
|
-
// when stdout.cols is unknown.
|
|
1501
|
+
// Reserve space for the context meter on the right plus the
|
|
1502
|
+
// "Agents: " prefix / hint. Clamp aggressively when cols unknown.
|
|
1439
1503
|
const cols = size.cols || 80;
|
|
1504
|
+
const meterLabel = String((contextMeter && contextMeter.label) || "").trim();
|
|
1505
|
+
const reservedForMeter = meterLabel
|
|
1506
|
+
? fmt.displayCellWidth(` ${meterLabel}`) + 1
|
|
1507
|
+
: 0;
|
|
1440
1508
|
const reservedForHint = fmt.displayCellWidth(` · ${agentsHint}`);
|
|
1441
|
-
const budget = Math.max(
|
|
1509
|
+
const budget = Math.max(12, cols - 10 - reservedForHint - reservedForMeter);
|
|
1442
1510
|
const plan = fmt.planAgentsFooter(
|
|
1443
1511
|
labels,
|
|
1444
1512
|
agentSelectionMode ? selectedAgentIndex : -1,
|
|
@@ -1461,6 +1529,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1461
1529
|
);
|
|
1462
1530
|
})(),
|
|
1463
1531
|
h(Text, { wrap: "truncate", color: "gray" }, ` · ${agentsHint}`),
|
|
1532
|
+
h(Box, { flexGrow: 1 }),
|
|
1533
|
+
h(Text, { wrap: "truncate", color: "gray" },
|
|
1534
|
+
String((contextMeter && contextMeter.label) || "").trim() || "0 / 200K"),
|
|
1464
1535
|
),
|
|
1465
1536
|
);
|
|
1466
1537
|
};
|
|
@@ -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 "─";
|