u-foo 3.0.5 → 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/planGraphService.js +140 -16
- package/src/code/context/planProjection.js +163 -62
- package/src/code/context/promptLayers.js +1 -0
- package/src/code/context/userInteraction.js +29 -4
- package/src/code/contextWindow.js +117 -0
- package/src/code/nativeRunner.js +116 -29
- package/src/code/runtime/agentWakeup.js +5 -3
- 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 +79 -6
- 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);
|
|
@@ -172,7 +190,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
172
190
|
);
|
|
173
191
|
setPlanUi((prev) => (prev && prev.hash === next.hash ? prev : next));
|
|
174
192
|
const pending = getPendingUserInteraction(props.state && props.state.executionState);
|
|
175
|
-
setInteractionLines(pending ? formatInteractionPromptLines(pending
|
|
193
|
+
setInteractionLines(pending ? formatInteractionPromptLines(pending, {
|
|
194
|
+
cols: size.cols || 80,
|
|
195
|
+
}) : []);
|
|
176
196
|
return next;
|
|
177
197
|
} catch {
|
|
178
198
|
return null;
|
|
@@ -583,6 +603,21 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
583
603
|
workspaceRoot: runtimeWorkspace,
|
|
584
604
|
});
|
|
585
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
|
+
}
|
|
586
621
|
if (applied.ok && result.action === "set" && typeof props.persistSessionState === "function") {
|
|
587
622
|
try {
|
|
588
623
|
const persisted = props.persistSessionState(props.state);
|
|
@@ -685,6 +720,20 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
685
720
|
);
|
|
686
721
|
setActiveMerge(null);
|
|
687
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 */ }
|
|
688
737
|
return;
|
|
689
738
|
}
|
|
690
739
|
case "tool": {
|
|
@@ -780,6 +829,10 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
780
829
|
try {
|
|
781
830
|
nlResult = await props.runNaturalLanguageTask(result.task, props.state, {
|
|
782
831
|
signal: abortController.signal,
|
|
832
|
+
onContextUsage: (meter) => {
|
|
833
|
+
if (!meter || typeof meter !== "object") return;
|
|
834
|
+
setContextMeter(meter);
|
|
835
|
+
},
|
|
783
836
|
onPhase: (event) => {
|
|
784
837
|
if (!event || typeof event !== "object") return;
|
|
785
838
|
if (event.type === "request_start") {
|
|
@@ -868,6 +921,11 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
868
921
|
if (summary) appendLogText(summary);
|
|
869
922
|
}
|
|
870
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
|
+
}
|
|
871
929
|
try {
|
|
872
930
|
const persisted = props.persistSessionState(props.state);
|
|
873
931
|
if (persisted && persisted.ok === false) {
|
|
@@ -1016,6 +1074,10 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1016
1074
|
let streamStarted = false;
|
|
1017
1075
|
let dropLeadingStreamBlank = false;
|
|
1018
1076
|
const result = await submit(trimmed, props.state, {
|
|
1077
|
+
onContextUsage: (meter) => {
|
|
1078
|
+
if (!meter || typeof meter !== "object") return;
|
|
1079
|
+
setContextMeter(meter);
|
|
1080
|
+
},
|
|
1019
1081
|
onDelta: (delta) => {
|
|
1020
1082
|
const text = String(delta || "");
|
|
1021
1083
|
if (!text) return;
|
|
@@ -1042,6 +1104,11 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1042
1104
|
}
|
|
1043
1105
|
flushTableBuffer();
|
|
1044
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
|
+
}
|
|
1045
1112
|
if (!result || result.ok === false) {
|
|
1046
1113
|
appendLogText(`Error: ${(result && result.error) || "resume failed"}`, "error");
|
|
1047
1114
|
} else if (result.shouldEchoSummary) {
|
|
@@ -1284,7 +1351,7 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1284
1351
|
...interactionLines.map((line, idx) => h(Text, {
|
|
1285
1352
|
key: `ask-${idx}`,
|
|
1286
1353
|
color: "yellow",
|
|
1287
|
-
wrap: "
|
|
1354
|
+
wrap: "wrap",
|
|
1288
1355
|
}, line || " ")),
|
|
1289
1356
|
)
|
|
1290
1357
|
: null,
|
|
@@ -1431,12 +1498,15 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1431
1498
|
? h(Text, { wrap: "truncate", color: "cyan" }, "none")
|
|
1432
1499
|
: (() => {
|
|
1433
1500
|
const labels = agents.map((a) => `@${getAgentLabel(a)}`);
|
|
1434
|
-
// Reserve
|
|
1435
|
-
//
|
|
1436
|
-
// 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.
|
|
1437
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;
|
|
1438
1508
|
const reservedForHint = fmt.displayCellWidth(` · ${agentsHint}`);
|
|
1439
|
-
const budget = Math.max(
|
|
1509
|
+
const budget = Math.max(12, cols - 10 - reservedForHint - reservedForMeter);
|
|
1440
1510
|
const plan = fmt.planAgentsFooter(
|
|
1441
1511
|
labels,
|
|
1442
1512
|
agentSelectionMode ? selectedAgentIndex : -1,
|
|
@@ -1459,6 +1529,9 @@ function createUcodeApp({ React, ink, props, interactive = true }) {
|
|
|
1459
1529
|
);
|
|
1460
1530
|
})(),
|
|
1461
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"),
|
|
1462
1535
|
),
|
|
1463
1536
|
);
|
|
1464
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 "─";
|