u-foo 2.3.30 → 2.3.31

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "u-foo",
3
- "version": "2.3.30",
3
+ "version": "2.3.31",
4
4
  "description": "Multi-Agent Workspace Protocol. Just add u. claude → uclaude, codex → ucodex.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://ufoo.dev",
package/src/code/agent.js CHANGED
@@ -491,6 +491,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
491
491
  const runNativeAgentImpl = typeof options.runNativeAgentImpl === "function"
492
492
  ? options.runNativeAgentImpl
493
493
  : runNativeAgentTask;
494
+ const onPhase = typeof options.onPhase === "function" ? options.onPhase : null;
495
+ const onThinkingDelta = typeof options.onThinkingDelta === "function" ? options.onThinkingDelta : null;
494
496
  const invokeNative = (sessionIdValue = "", timeoutOverrideMs = timeoutMs) => runNativeAgentImpl({
495
497
  workspaceRoot,
496
498
  provider,
@@ -501,6 +503,8 @@ async function runNaturalLanguageTask(task = "", state = {}, options = {}) {
501
503
  sessionId: String(sessionIdValue || ""),
502
504
  timeoutMs: timeoutOverrideMs,
503
505
  onStreamDelta: onStream,
506
+ onThinkingDelta,
507
+ onPhase,
504
508
  onToolEvent: (event) => {
505
509
  pushToolLog(event);
506
510
  },
@@ -512,12 +512,23 @@ function runCoreTool({ tool = "", args = {}, workspaceRoot = process.cwd(), onTo
512
512
  return result;
513
513
  }
514
514
 
515
+ function emitPhase(callback, event = {}) {
516
+ if (typeof callback !== "function") return;
517
+ try {
518
+ callback(event);
519
+ } catch {
520
+ // ignore phase callback failures
521
+ }
522
+ }
523
+
515
524
  async function runOpenAiLikeTurn({
516
525
  url = "",
517
526
  apiKey = "",
518
527
  model = "",
519
528
  messages = [],
520
529
  onTextDelta = null,
530
+ onThinkingDelta = null,
531
+ onPhase = null,
521
532
  signal = null,
522
533
  timeoutMs = 300000,
523
534
  } = {}) {
@@ -540,6 +551,8 @@ async function runOpenAiLikeTurn({
540
551
 
541
552
  const request = createRequestController({ signal, timeoutMs });
542
553
 
554
+ emitPhase(onPhase, { type: "request_start" });
555
+
543
556
  try {
544
557
  const response = await fetch(url, {
545
558
  method: "POST",
@@ -574,6 +587,7 @@ async function runOpenAiLikeTurn({
574
587
  const toolCallMap = new Map();
575
588
  let rawBuffer = "";
576
589
  let responseText = "";
590
+ const announcedToolNames = new Set();
577
591
 
578
592
  while (true) {
579
593
  const { done, value } = await reader.read();
@@ -599,8 +613,19 @@ async function runOpenAiLikeTurn({
599
613
 
600
614
  const delta = choice.delta && typeof choice.delta === "object" ? choice.delta : {};
601
615
 
616
+ const reasoningChunk = typeof delta.reasoning_content === "string"
617
+ ? delta.reasoning_content
618
+ : (typeof delta.reasoning === "string" ? delta.reasoning : "");
619
+ if (reasoningChunk) {
620
+ emitPhase(onPhase, { type: "thinking_delta", text: reasoningChunk });
621
+ if (typeof onThinkingDelta === "function") {
622
+ onThinkingDelta(reasoningChunk);
623
+ }
624
+ }
625
+
602
626
  if (typeof delta.content === "string" && delta.content) {
603
627
  responseText += delta.content;
628
+ emitPhase(onPhase, { type: "text_delta", text: delta.content });
604
629
  if (typeof onTextDelta === "function") {
605
630
  onTextDelta(delta.content);
606
631
  }
@@ -629,6 +654,13 @@ async function runOpenAiLikeTurn({
629
654
  }
630
655
 
631
656
  toolCallMap.set(index, previous);
657
+
658
+ const toolName = previous.function.name;
659
+ const announceKey = `${index}:${toolName}`;
660
+ if (toolName && !announcedToolNames.has(announceKey)) {
661
+ announcedToolNames.add(announceKey);
662
+ emitPhase(onPhase, { type: "tool_request", name: toolName });
663
+ }
632
664
  }
633
665
  }
634
666
  }
@@ -716,6 +748,8 @@ async function runAnthropicTurn({
716
748
  systemPrompt = "",
717
749
  messages = [],
718
750
  onTextDelta = null,
751
+ onThinkingDelta = null,
752
+ onPhase = null,
719
753
  signal = null,
720
754
  timeoutMs = 300000,
721
755
  } = {}) {
@@ -741,6 +775,8 @@ async function runAnthropicTurn({
741
775
 
742
776
  const request = createRequestController({ signal, timeoutMs });
743
777
 
778
+ emitPhase(onPhase, { type: "request_start" });
779
+
744
780
  try {
745
781
  const response = await fetch(url, {
746
782
  method: "POST",
@@ -811,6 +847,12 @@ async function runAnthropicTurn({
811
847
  type: "text",
812
848
  text: String(contentBlock.text || ""),
813
849
  });
850
+ } else if (contentBlock.type === "thinking") {
851
+ blockMap.set(index, {
852
+ order: index,
853
+ type: "thinking",
854
+ text: String(contentBlock.thinking || ""),
855
+ });
814
856
  } else if (contentBlock.type === "tool_use") {
815
857
  blockMap.set(index, {
816
858
  order: index,
@@ -822,6 +864,10 @@ async function runAnthropicTurn({
822
864
  : {},
823
865
  inputJson: "",
824
866
  });
867
+ const toolName = String(contentBlock.name || "");
868
+ if (toolName) {
869
+ emitPhase(onPhase, { type: "tool_request", name: toolName });
870
+ }
825
871
  }
826
872
  continue;
827
873
  }
@@ -840,6 +886,7 @@ async function runAnthropicTurn({
840
886
  blockMap.set(index, current);
841
887
  if (deltaText) {
842
888
  responseText += deltaText;
889
+ emitPhase(onPhase, { type: "text_delta", text: deltaText });
843
890
  if (typeof onTextDelta === "function") {
844
891
  onTextDelta(deltaText);
845
892
  }
@@ -847,6 +894,20 @@ async function runAnthropicTurn({
847
894
  continue;
848
895
  }
849
896
 
897
+ if (delta.type === "thinking_delta") {
898
+ const deltaText = String(delta.thinking || "");
899
+ current.type = "thinking";
900
+ current.text = `${String(current.text || "")}${deltaText}`;
901
+ blockMap.set(index, current);
902
+ if (deltaText) {
903
+ emitPhase(onPhase, { type: "thinking_delta", text: deltaText });
904
+ if (typeof onThinkingDelta === "function") {
905
+ onThinkingDelta(deltaText);
906
+ }
907
+ }
908
+ continue;
909
+ }
910
+
850
911
  if (delta.type === "input_json_delta") {
851
912
  current.type = "tool_use";
852
913
  current.inputJson = `${String(current.inputJson || "")}${String(delta.partial_json || "")}`;
@@ -859,6 +920,7 @@ async function runAnthropicTurn({
859
920
 
860
921
  const assistantContent = Array.from(blockMap.values())
861
922
  .sort((a, b) => a.order - b.order)
923
+ .filter((item) => item.type !== "thinking")
862
924
  .map((item) => {
863
925
  if (item.type === "text") {
864
926
  return {
@@ -919,6 +981,8 @@ async function runNativeLoopOpenAi({
919
981
  apiKey = "",
920
982
  timeoutMs = 300000,
921
983
  onStreamDelta = null,
984
+ onThinkingDelta = null,
985
+ onPhase = null,
922
986
  onToolEvent = null,
923
987
  signal = null,
924
988
  guards,
@@ -957,6 +1021,8 @@ async function runNativeLoopOpenAi({
957
1021
  messages,
958
1022
  signal,
959
1023
  timeoutMs,
1024
+ onPhase,
1025
+ onThinkingDelta,
960
1026
  onTextDelta: (chunk) => {
961
1027
  const text = String(chunk || "");
962
1028
  if (!text) return;
@@ -1061,6 +1127,8 @@ async function runNativeLoopAnthropic({
1061
1127
  apiKey = "",
1062
1128
  timeoutMs = 300000,
1063
1129
  onStreamDelta = null,
1130
+ onThinkingDelta = null,
1131
+ onPhase = null,
1064
1132
  onToolEvent = null,
1065
1133
  signal = null,
1066
1134
  guards,
@@ -1098,6 +1166,8 @@ async function runNativeLoopAnthropic({
1098
1166
  messages,
1099
1167
  signal,
1100
1168
  timeoutMs,
1169
+ onPhase,
1170
+ onThinkingDelta,
1101
1171
  onTextDelta: (chunk) => {
1102
1172
  const text = String(chunk || "");
1103
1173
  if (!text) return;
@@ -1198,6 +1268,8 @@ async function runNativeAgentTask({
1198
1268
  sessionId = "",
1199
1269
  timeoutMs = 300000,
1200
1270
  onStreamDelta = null,
1271
+ onThinkingDelta = null,
1272
+ onPhase = null,
1201
1273
  onToolEvent = null,
1202
1274
  signal = null,
1203
1275
  } = {}) {
@@ -1238,6 +1310,8 @@ async function runNativeAgentTask({
1238
1310
  apiKey: runtime.apiKey,
1239
1311
  timeoutMs,
1240
1312
  onStreamDelta,
1313
+ onThinkingDelta,
1314
+ onPhase,
1241
1315
  onToolEvent,
1242
1316
  signal,
1243
1317
  guards,
package/src/code/tui.js CHANGED
@@ -1615,29 +1615,44 @@ function runUcodeTui({
1615
1615
  }
1616
1616
 
1617
1617
  if (result.kind === "nl") {
1618
- const statusMessages = [
1619
- "Thinking...",
1620
- "Processing your request...",
1621
- "Analyzing...",
1622
- "Working on it...",
1623
- ];
1624
- const randomStatus = statusMessages[Math.floor(Math.random() * statusMessages.length)];
1625
1618
  const abortController = new AbortController();
1626
1619
  const escapeStripper = createEscapeTagStripper();
1627
1620
  pendingTask = {
1628
1621
  abortController,
1629
1622
  startedAt: Date.now(),
1630
1623
  };
1631
- updateStatus(randomStatus, "thinking", {
1632
- showTimer: true,
1633
- startedAt: pendingTask.startedAt,
1634
- });
1624
+ const TOOL_LABELS = {
1625
+ read: "Reading file",
1626
+ write: "Writing file",
1627
+ edit: "Editing file",
1628
+ bash: "Running command",
1629
+ };
1630
+ const setNlStatus = (msg) => {
1631
+ updateStatus(msg, "thinking", {
1632
+ showTimer: true,
1633
+ startedAt: pendingTask.startedAt,
1634
+ });
1635
+ };
1636
+ setNlStatus("Waiting for model...");
1635
1637
  let streamState = null;
1636
1638
  let renderedToolLogCount = 0;
1637
1639
  let nlResult = null;
1638
1640
  try {
1639
1641
  nlResult = await runNaturalLanguageTask(result.task, state, {
1640
1642
  signal: abortController.signal,
1643
+ onPhase: (event) => {
1644
+ if (!event || typeof event !== "object") return;
1645
+ if (event.type === "request_start") {
1646
+ setNlStatus("Waiting for model...");
1647
+ } else if (event.type === "thinking_delta") {
1648
+ setNlStatus("Thinking...");
1649
+ } else if (event.type === "text_delta") {
1650
+ setNlStatus("Generating response...");
1651
+ } else if (event.type === "tool_request") {
1652
+ const label = TOOL_LABELS[String(event.name || "").toLowerCase()] || `Calling ${event.name}`;
1653
+ setNlStatus(`${label}...`);
1654
+ }
1655
+ },
1641
1656
  onDelta: (delta) => {
1642
1657
  const text = escapeStripper.write(String(delta || ""));
1643
1658
  if (!text) return;
@@ -1648,6 +1663,10 @@ function runUcodeTui({
1648
1663
  },
1649
1664
  onToolLog: (entry) => {
1650
1665
  renderedToolLogCount += 1;
1666
+ if (entry && entry.tool && entry.phase === "start") {
1667
+ const label = TOOL_LABELS[String(entry.tool || "").toLowerCase()] || `Calling ${entry.tool}`;
1668
+ setNlStatus(`${label}...`);
1669
+ }
1651
1670
  logToolHint(entry);
1652
1671
  },
1653
1672
  });