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