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.
@@ -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 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.
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(20, cols - 10 - reservedForHint);
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
- 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 "─";