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.
@@ -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: "truncate",
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 1 col for borders, the "Agents: " prefix, the hint
1435
- // and a few spaces for safety. We just clamp aggressively
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(20, cols - 10 - reservedForHint);
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
- marker: "▣",
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 "─";