u-foo 2.3.32 → 2.4.0

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.
Files changed (235) hide show
  1. package/README.md +157 -213
  2. package/README.zh-CN.md +151 -197
  3. package/SKILLS/ufoo/SKILL.md +8 -8
  4. package/bin/uagy.js +69 -0
  5. package/bin/uclaude.js +2 -2
  6. package/bin/ucode.js +4 -4
  7. package/bin/ucodex.js +2 -2
  8. package/bin/ufoo.js +5 -23
  9. package/modules/AGENTS.template.md +1 -1
  10. package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
  11. package/package.json +5 -5
  12. package/scripts/chat-app-smoke.js +1 -1
  13. package/scripts/global-chat-switch-benchmark.js +5 -5
  14. package/scripts/ink-demo.js +1 -1
  15. package/scripts/ink-smoke.js +1 -1
  16. package/scripts/ucode-app-smoke.js +1 -1
  17. package/src/{agent → agents/activity}/activityDetector.js +39 -2
  18. package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
  19. package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
  20. package/src/{agent → agents/activity}/activityTracker.js +1 -1
  21. package/src/agents/activity/index.js +8 -0
  22. package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
  23. package/src/agents/controller/index.js +8 -0
  24. package/src/{agent → agents/controller}/loopObservability.js +2 -2
  25. package/src/{agent → agents/controller}/loopRuntime.js +1 -1
  26. package/src/{agent → agents/controller}/ufooAgent.js +9 -9
  27. package/src/agents/index.js +10 -0
  28. package/src/agents/internal/index.js +3 -0
  29. package/src/{agent → agents/internal}/internalRunner.js +45 -22
  30. package/src/agents/launch/agyConversation.js +159 -0
  31. package/src/agents/launch/index.js +12 -0
  32. package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
  33. package/src/{agent → agents/launch}/launcher.js +64 -21
  34. package/src/{agent → agents/launch}/notifier.js +23 -12
  35. package/src/{agent → agents/launch}/ptyRunner.js +44 -12
  36. package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
  37. package/src/{agent → agents/launch}/publisherRouting.js +1 -1
  38. package/src/{agent → agents/launch}/readyDetector.js +23 -0
  39. package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
  40. package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
  41. package/src/agents/prompts/index.js +8 -0
  42. package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
  43. package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
  44. package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
  45. package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
  46. package/src/agents/providers/index.js +13 -0
  47. package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
  48. package/src/{chat → app/chat}/agentSockets.js +1 -1
  49. package/src/{chat → app/chat}/commandExecutor.js +50 -26
  50. package/src/{chat → app/chat}/commands.js +119 -5
  51. package/src/{chat → app/chat}/daemonConnection.js +1 -1
  52. package/src/{chat → app/chat}/daemonMessageRouter.js +45 -3
  53. package/src/{chat → app/chat}/dashboardView.js +2 -1
  54. package/src/app/chat/index.js +6 -0
  55. package/src/{chat → app/chat}/inputSubmitHandler.js +4 -13
  56. package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
  57. package/src/app/chat/multiWindow/index.js +268 -0
  58. package/src/app/chat/multiWindow/paneLayout.js +84 -0
  59. package/src/app/chat/multiWindow/paneManager.js +299 -0
  60. package/src/app/chat/multiWindow/renderer.js +384 -0
  61. package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
  62. package/src/{chat → app/chat}/transport.js +1 -1
  63. package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
  64. package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
  65. package/src/{init/index.js → app/cli/features/init.js} +14 -32
  66. package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
  67. package/src/app/cli/index.js +9 -0
  68. package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
  69. package/src/{cli.js → app/cli/run.js} +59 -57
  70. package/src/app/index.js +6 -0
  71. package/src/code/agent.js +10 -9
  72. package/src/code/index.js +2 -0
  73. package/src/code/launcher/index.js +9 -0
  74. package/src/{agent → code/launcher}/ucode.js +7 -8
  75. package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
  76. package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
  77. package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
  78. package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
  79. package/src/code/nativeRunner.js +4 -4
  80. package/src/code/tui.js +3 -1454
  81. package/src/config.js +15 -2
  82. package/src/{bus → coordination/bus}/activate.js +2 -2
  83. package/src/{bus → coordination/bus}/daemon.js +15 -5
  84. package/src/coordination/bus/envelope.js +173 -0
  85. package/src/{bus → coordination/bus}/index.js +7 -3
  86. package/src/{bus → coordination/bus}/inject.js +11 -3
  87. package/src/{bus → coordination/bus}/message.js +1 -1
  88. package/src/coordination/bus/messageMeta.js +130 -0
  89. package/src/coordination/bus/promptEnvelope.js +65 -0
  90. package/src/{bus → coordination/bus}/shake.js +1 -1
  91. package/src/{bus → coordination/bus}/store.js +3 -3
  92. package/src/{bus → coordination/bus}/subscriber.js +2 -2
  93. package/src/{bus → coordination/bus}/utils.js +2 -2
  94. package/src/{history → coordination/history}/inputTimeline.js +5 -5
  95. package/src/coordination/index.js +10 -0
  96. package/src/{memory → coordination/memory}/historySearch.js +1 -1
  97. package/src/{memory → coordination/memory}/index.js +3 -3
  98. package/src/{report → coordination/report}/store.js +2 -2
  99. package/src/{status → coordination/status}/index.js +3 -3
  100. package/src/online/bridge.js +2 -2
  101. package/src/{controller → orchestration/controller}/flags.js +1 -1
  102. package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
  103. package/src/orchestration/controller/index.js +10 -0
  104. package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
  105. package/src/orchestration/groups/bootstrap.js +3 -0
  106. package/src/orchestration/groups/index.js +10 -0
  107. package/src/orchestration/groups/promptProfiles.js +3 -0
  108. package/src/{group → orchestration/groups}/templates.js +1 -1
  109. package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
  110. package/src/orchestration/index.js +7 -0
  111. package/src/orchestration/solo/index.js +3 -0
  112. package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
  113. package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
  114. package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
  115. package/src/{daemon → runtime/daemon}/index.js +105 -53
  116. package/src/{daemon → runtime/daemon}/ipcServer.js +1 -1
  117. package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
  118. package/src/{daemon → runtime/daemon}/ops.js +48 -61
  119. package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
  120. package/src/{daemon → runtime/daemon}/promptRequest.js +7 -7
  121. package/src/runtime/daemon/providerSessions.js +230 -0
  122. package/src/{daemon → runtime/daemon}/reporting.js +4 -4
  123. package/src/{daemon → runtime/daemon}/run.js +4 -4
  124. package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
  125. package/src/{daemon → runtime/daemon}/status.js +5 -5
  126. package/src/runtime/index.js +10 -0
  127. package/src/{projects → runtime/projects}/registry.js +1 -1
  128. package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
  129. package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
  130. package/src/tools/handlers/common.js +1 -1
  131. package/src/tools/handlers/listAgents.js +1 -1
  132. package/src/tools/handlers/memory.js +3 -3
  133. package/src/tools/handlers/readBusSummary.js +1 -1
  134. package/src/tools/handlers/readOpenDecisions.js +1 -1
  135. package/src/tools/handlers/readProjectRegistry.js +1 -1
  136. package/src/tools/handlers/readPromptHistory.js +2 -2
  137. package/src/tools/schemaFixtures.js +1 -1
  138. package/src/ui/MIGRATION.md +42 -88
  139. package/src/ui/format/index.js +5 -28
  140. package/src/ui/index.js +1 -1
  141. package/src/ui/{components → ink}/ChatApp.js +812 -88
  142. package/src/ui/ink/DashboardBar.js +685 -0
  143. package/src/ui/{components → ink}/MultilineInput.js +230 -5
  144. package/src/ui/{components → ink}/UcodeApp.js +16 -7
  145. package/src/ui/{components → ink}/agentMirror.js +24 -19
  146. package/src/ui/{components → ink}/chatReducer.js +29 -7
  147. package/src/bus/messageMeta.js +0 -52
  148. package/src/chat/agentViewController.js +0 -1072
  149. package/src/chat/chatLogController.js +0 -138
  150. package/src/chat/completionController.js +0 -533
  151. package/src/chat/dashboardKeyController.js +0 -533
  152. package/src/chat/index.js +0 -2222
  153. package/src/chat/inputHistoryController.js +0 -135
  154. package/src/chat/inputListenerController.js +0 -470
  155. package/src/chat/layout.js +0 -186
  156. package/src/chat/pasteController.js +0 -81
  157. package/src/chat/statusLineController.js +0 -223
  158. package/src/chat/streamTracker.js +0 -156
  159. package/src/code/config +0 -0
  160. package/src/daemon/providerSessions.js +0 -488
  161. package/src/terminal/adapters/internalPtyAdapter.js +0 -42
  162. package/src/ui/components/DashboardBar.js +0 -417
  163. /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
  164. /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
  165. /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
  166. /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
  167. /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
  168. /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
  169. /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
  170. /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
  171. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
  172. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
  173. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
  174. /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
  175. /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
  176. /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
  177. /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
  178. /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
  179. /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
  180. /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
  181. /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
  182. /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
  183. /package/src/{agent → agents/providers}/credentials/index.js +0 -0
  184. /package/src/{chat → app/chat}/agentBar.js +0 -0
  185. /package/src/{chat → app/chat}/agentDirectory.js +0 -0
  186. /package/src/{chat → app/chat}/cronScheduler.js +0 -0
  187. /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
  188. /package/src/{chat → app/chat}/daemonReconnect.js +0 -0
  189. /package/src/{chat → app/chat}/daemonTransport.js +0 -0
  190. /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
  191. /package/src/{chat → app/chat}/inputMath.js +0 -0
  192. /package/src/{chat → app/chat}/projectCloseController.js +0 -0
  193. /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
  194. /package/src/{chat → app/chat}/settingsController.js +0 -0
  195. /package/src/{chat → app/chat}/shellCommand.js +0 -0
  196. /package/src/{chat → app/chat}/text.js +0 -0
  197. /package/src/{chat → app/chat}/transientAgentState.js +0 -0
  198. /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
  199. /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
  200. /package/src/{bus → coordination/bus}/nickname.js +0 -0
  201. /package/src/{bus → coordination/bus}/queue.js +0 -0
  202. /package/src/{context → coordination/context}/decisions.js +0 -0
  203. /package/src/{context → coordination/context}/doctor.js +0 -0
  204. /package/src/{context → coordination/context}/index.js +0 -0
  205. /package/src/{context → coordination/context}/sync.js +0 -0
  206. /package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +0 -0
  207. /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
  208. /package/src/{ufoo → coordination/state}/paths.js +0 -0
  209. /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
  210. /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
  211. /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
  212. /package/src/{group → orchestration/groups}/diagram.js +0 -0
  213. /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
  214. /package/src/{solo → orchestration/solo}/commands.js +0 -0
  215. /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
  216. /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
  217. /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
  218. /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
  219. /package/src/{utils → runtime/process}/nodeExecutable.js +0 -0
  220. /package/src/{projects → runtime/projects}/identity.js +0 -0
  221. /package/src/{projects → runtime/projects}/index.js +0 -0
  222. /package/src/{projects → runtime/projects}/projectId.js +0 -0
  223. /package/src/{projects → runtime/projects}/runtimes.js +0 -0
  224. /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
  225. /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
  226. /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
  227. /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
  228. /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
  229. /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
  230. /package/src/{terminal → runtime/terminal}/detect.js +0 -0
  231. /package/src/{terminal → runtime/terminal}/index.js +0 -0
  232. /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
  233. /package/src/{utils → ui/format}/banner.js +0 -0
  234. /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
  235. /package/src/ui/{components → ink}/InkDemo.js +0 -0
@@ -1,10 +1,10 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const { getUfooPaths } = require("../ufoo/paths");
4
- const { isMetaActive } = require("../bus/utils");
3
+ const { getUfooPaths } = require("../../coordination/state/paths");
4
+ const { isMetaActive } = require("../../coordination/bus/utils");
5
5
  const { resolveDisplayNickname, resolveScopedNickname } = require("./nicknameScope");
6
- const { readReportSummary, countControllerInboxEntries } = require("../report/store");
7
- const { readRecentLoopSummary } = require("../agent/loopObservability");
6
+ const { readReportSummary, countControllerInboxEntries } = require("../../coordination/report/store");
7
+ const { readRecentLoopSummary } = require("../../agents/controller/loopObservability");
8
8
 
9
9
  function readBus(projectRoot) {
10
10
  const busPath = getUfooPaths(projectRoot).agentsFile;
@@ -16,7 +16,7 @@ function readBus(projectRoot) {
16
16
  }
17
17
 
18
18
  function readDecisions(projectRoot) {
19
- const DecisionsManager = require("../context/decisions");
19
+ const DecisionsManager = require("../../coordination/context/decisions");
20
20
  const manager = new DecisionsManager(projectRoot);
21
21
  const dir = manager.decisionsDir;
22
22
  let open = 0;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ projects: require("./projects"),
5
+ terminal: require("./terminal"),
6
+ contracts: {
7
+ ...require("./contracts/eventContract"),
8
+ ...require("./contracts/ptySocketContract"),
9
+ },
10
+ };
@@ -2,7 +2,7 @@ const fs = require("fs");
2
2
  const os = require("os");
3
3
  const path = require("path");
4
4
  const { canonicalProjectRoot, buildProjectId, trimTrailingSlashes } = require("./projectId");
5
- const { getUfooPaths } = require("../ufoo/paths");
5
+ const { getUfooPaths } = require("../../coordination/state/paths");
6
6
 
7
7
  const DEFAULT_STALE_TTL_MS = 30 * 1000;
8
8
  const DEFAULT_TMP_CLEANUP_AGE_MS = 5 * 60 * 1000;
@@ -5,7 +5,6 @@ const {
5
5
  const { createTerminalAdapter } = require("./adapters/terminalAdapter");
6
6
  const { createTmuxAdapter } = require("./adapters/tmuxAdapter");
7
7
  const { createInternalQueueAdapter } = require("./adapters/internalQueueAdapter");
8
- const { createInternalPtyAdapter } = require("./adapters/internalPtyAdapter");
9
8
  const { createHostAdapter } = require("./adapters/hostAdapter");
10
9
 
11
10
  function createTerminalAdapterRouter(options = {}) {
@@ -65,15 +64,6 @@ function createTerminalAdapterRouter(options = {}) {
65
64
  });
66
65
  }
67
66
 
68
- if (launchMode === "internal-pty") {
69
- return createInternalPtyAdapter({
70
- sendRaw,
71
- sendResize,
72
- requestSnapshot,
73
- createAdapter,
74
- });
75
- }
76
-
77
67
  if (launchMode === "internal") {
78
68
  return createInternalQueueAdapter({
79
69
  sendRaw,
@@ -1,10 +1,6 @@
1
- const { createInternalPtyAdapter } = require("./internalPtyAdapter");
2
1
  const { createInternalQueueAdapter } = require("./internalQueueAdapter");
3
2
 
4
3
  function createInternalAdapter(options = {}) {
5
- if (options.usePty) {
6
- return createInternalPtyAdapter(options);
7
- }
8
4
  return createInternalQueueAdapter(options);
9
5
  }
10
6
 
@@ -1,4 +1,4 @@
1
- const EventBus = require("../../bus");
1
+ const EventBus = require("../../coordination/bus");
2
2
  const { CALLER_TIERS, normalizeCallerTier } = require("../types");
3
3
 
4
4
  function extractAuditFields(ctx = {}) {
@@ -1,4 +1,4 @@
1
- const { buildStatus } = require("../../daemon/status");
1
+ const { buildStatus } = require("../../runtime/daemon/status");
2
2
 
3
3
  function listAgentsHandler(ctx = {}) {
4
4
  const status = buildStatus(ctx.projectRoot);
@@ -1,6 +1,6 @@
1
- const MemoryManager = require("../../memory");
2
- const { estimateTokens } = require("../../memory");
3
- const { searchHistory } = require("../../memory/historySearch");
1
+ const MemoryManager = require("../../coordination/memory");
2
+ const { estimateTokens } = require("../../coordination/memory");
3
+ const { searchHistory } = require("../../coordination/memory/historySearch");
4
4
  const { buildToolError, extractAuditFields, requireSubscriber } = require("./common");
5
5
 
6
6
  const BANNED_TIME_PATTERNS = [
@@ -1,4 +1,4 @@
1
- const { buildStatus } = require("../../daemon/status");
1
+ const { buildStatus } = require("../../runtime/daemon/status");
2
2
 
3
3
  function readBusSummaryHandler(ctx = {}) {
4
4
  const status = buildStatus(ctx.projectRoot);
@@ -1,4 +1,4 @@
1
- const DecisionsManager = require("../../context/decisions");
1
+ const DecisionsManager = require("../../coordination/context/decisions");
2
2
 
3
3
  function readOpenDecisionsHandler(ctx = {}, args = {}) {
4
4
  const limit = Number.isFinite(Number(args.limit)) && Number(args.limit) > 0
@@ -1,4 +1,4 @@
1
- const { listProjectRuntimes } = require("../../projects/registry");
1
+ const { listProjectRuntimes } = require("../../runtime/projects/registry");
2
2
 
3
3
  function readProjectRegistryHandler(_ctx = {}, args = {}) {
4
4
  const validate = args.validate !== false;
@@ -1,8 +1,8 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
- const { buildStatus } = require("../../daemon/status");
5
- const { getUfooPaths } = require("../../ufoo/paths");
4
+ const { buildStatus } = require("../../runtime/daemon/status");
5
+ const { getUfooPaths } = require("../../coordination/state/paths");
6
6
 
7
7
  function clipPromptText(value = "", maxChars = 240) {
8
8
  const text = String(value || "").replace(/\s+/g, " ").trim();
@@ -456,7 +456,7 @@ const LAUNCH_AGENT_SCHEMA = Object.freeze({
456
456
  properties: Object.freeze({
457
457
  agent: Object.freeze({
458
458
  type: "string",
459
- enum: Object.freeze(["codex", "claude", "ucode"]),
459
+ enum: Object.freeze(["codex", "claude", "ucode", "agy"]),
460
460
  }),
461
461
  count: Object.freeze({ type: "integer", minimum: 1 }),
462
462
  nickname: Object.freeze({ type: "string" }),
@@ -1,14 +1,14 @@
1
1
  # Ink TUI Migration Plan
2
2
 
3
- Status: Ink is the default TUI for chat and ucode. The legacy blessed
4
- renderer remains available with `UFOO_TUI=blessed` as a temporary fallback.
3
+ Status: Ink is the only TUI for chat and ucode. The legacy blessed renderer
4
+ has been removed after the parity close-out.
5
5
 
6
6
  ## Why
7
7
 
8
- The legacy chat, ucode and internal-agent TUIs all use blessed. Blessed is
9
- an unmaintained imperative widget tree with no modern equivalent of React's
10
- component model, and it's awkward to extend (manual layout math, manual
11
- redraws, no useful test harness).
8
+ The removed legacy chat, ucode and internal-agent TUIs used blessed. Blessed
9
+ is an unmaintained imperative widget tree with no modern equivalent of
10
+ React's component model, and it was awkward to extend (manual layout math,
11
+ manual redraws, no useful test harness).
12
12
 
13
13
  ink (the React-for-terminals library, what Claude Code, Codex CLI fronts
14
14
  and the Gemini CLI all use) gives us declarative components, flexbox
@@ -16,11 +16,10 @@ layout, hooks, and proper isolation of pure logic from rendering.
16
16
 
17
17
  ## Approach
18
18
 
19
- - Ink is the default renderer; `UFOO_TUI=blessed` keeps the legacy path
20
- available while fallback removal is evaluated separately.
21
- - Pure helpers live in `src/ui/format/` and are shared by both, so behaviour
22
- parity is enforced by test rather than copy/paste.
23
- - Components live in `src/ui/components/`, written in plain JS via
19
+ - Ink is the renderer for both chat and ucode.
20
+ - Pure helpers live in `src/ui/format/`, so behaviour parity is enforced by
21
+ test rather than copy/paste.
22
+ - Components live in `src/ui/ink/`, written in plain JS via
24
23
  `React.createElement` (no JSX, no build step) so jest stays vanilla.
25
24
  - ink is loaded through `src/ui/runInk.js`, a thin CJS→ESM bridge so the
26
25
  rest of the codebase stays CommonJS.
@@ -61,7 +60,6 @@ layout, hooks, and proper isolation of pure logic from rendering.
61
60
 
62
61
  ```sh
63
62
  ./bin/ucode.js
64
- # fallback: UFOO_TUI=blessed ./bin/ucode.js
65
63
  ```
66
64
 
67
65
  ### Editor
@@ -129,25 +127,18 @@ layout, hooks, and proper isolation of pure logic from rendering.
129
127
 
130
128
  ## P2 dropped, folded into P3
131
129
 
132
- The internal-agent view in chat is not an independent program it's a
133
- view mode owned by `src/chat/agentViewController.js`. Today it works by
134
- detaching `screen.children`, writing raw `\x1b[2J` to stdout, and
135
- flipping `screen.grabKeys`. There is no clean seam to mount an isolated
136
- ink subtree inside a still-blessed chat host, so attempting P2 in
137
- isolation would force us to build a stdout-arbitration layer we'd throw
138
- away once chat itself is on ink. P3.6 ports the agent view as a chat
139
- sub-mode instead.
130
+ The internal-agent view in chat is not an independent program. It is now
131
+ an Ink chat sub-mode, so the earlier plan to mount a separate subtree was
132
+ folded into P3.6.
140
133
 
141
134
  ## P3 audit (chat TUI surface)
142
135
 
143
- Source: `src/chat/index.js` (2215 lines) + ~30 controllers in
144
- `src/chat/`. Highlights:
136
+ Source: the current Ink host in `src/ui/ink/ChatApp.js`, with
137
+ shared daemon and command helpers under `src/app/chat/`. Highlights:
145
138
 
146
139
  ### Lifecycle
147
140
  - Public entrypoint `runChat(projectRoot, { globalMode })` from
148
- `src/chat/index.js`. Wires `daemonCoordinator.connect()` then loops
149
- forever; exit goes through `process.exit(0)` from a screen `destroy`
150
- hook.
141
+ `src/app/chat/index.js`; it delegates directly to the Ink ChatApp.
151
142
  - Runners injected via closures: `daemonCoordinator.send`,
152
143
  `executeCommand`, `inputSubmitHandler.handleSubmit`,
153
144
  `daemonMessageRouter.handleMessage`.
@@ -187,19 +178,10 @@ Source: `src/chat/index.js` (2215 lines) + ~30 controllers in
187
178
  - `daemonReconnect.restartDaemonFlow()` provides a per-project lock for
188
179
  daemon restarts.
189
180
 
190
- ### Side controllers
181
+ ### Shared helpers
191
182
  - `cronScheduler` — `/cron start|stop|list` + the cron dashboard view.
192
183
  - `settingsController` — launch mode / agent provider,
193
184
  with daemon restart on mode/provider change. `autoResume` stays config/command-driven.
194
- - `chatLogController` — log buffer + history file replay
195
- (`loadHistory`, `appendHistory`, `markStreamStart`,
196
- `setHistoryTarget`, `resetViewState`).
197
- - `statusLineController` — debounced status line with background-task
198
- suffix (e.g. `(ufoo-agent processing)`); `queueStatusLine`,
199
- `resolveStatusLine`, `enqueueBusStatus`, `resolveBusStatus`.
200
- - `streamTracker` — per-publisher stream state + markdown rendering
201
- (`beginStream`, `appendStreamDelta`, `finalizeStream`,
202
- `markPendingDelivery`, `consumePendingDelivery`).
203
185
  - `transientAgentState` — TTL-bounded `working / waiting_input /
204
186
  blocked` markers per agent.
205
187
  - `projectCloseController` — `requestCloseProject(index)` runs daemon
@@ -207,31 +189,20 @@ Source: `src/chat/index.js` (2215 lines) + ~30 controllers in
207
189
  - `agentDirectory` — agent label resolution + window clamping (pure).
208
190
  - `internalAgentLogHistory` — bus log replay for internal agents.
209
191
 
210
- ### Internal-agent sub-view (`agentViewController`, 1072 lines)
211
- - Public API: `getCurrentView`, `getViewingAgent`,
212
- `isAgentViewUsesBus`, `getAgentInputSuppressUntil`,
213
- `get/setAgentOutputSuppressed`, `renderAgentDashboard`,
214
- `setAgentBarVisible`, `enterAgentView(agentId, options)`,
215
- `exitAgentView`, `sendRawToAgent`, `sendResizeToAgent`,
216
- `requestAgentSnapshot`, `writeToAgentTerm`, `placeAgentCursor`,
217
- `handleBusAgentKey`, `handleResizeInAgentView`, `refreshAgentView`.
218
- - Two render modes inside it: PTY mirror (raw ANSI passthrough +
219
- cursor placement, `agentSockets.connectOutput/Input`,
220
- `requestSnapshot`) and an embedded bus subview (own input value,
221
- cursor, log, animated status indicator).
222
- - `agentBar.computeAgentBar` renders the agent strip across both
223
- modes.
192
+ ### Internal-agent sub-view
193
+ - Ink owns the agent sub-view inside ChatApp.
194
+ - PTY mirror mode uses `agentSockets.connectOutput/Input` and
195
+ `requestSnapshot`.
196
+ - Embedded bus mode keeps its own input value, cursor, log and status.
224
197
  - `agentSockets.createAgentSockets` owns the PTY/bus socket
225
198
  lifecycle.
226
- - Exit/restore reattaches `screen.children`, restores scroll region
227
- and unfreezes `screen.render`.
228
199
 
229
- ### Layout (`createChatLayout`)
230
- - 9 widgets: screen, logBox, statusLine, completionPanel, dashboard,
231
- inputBottomLine, promptBox, input, inputTopLine.
232
- - Geometry rules: dashboard 1-2 lines, input 5-9 lines (autosizes by
233
- content), log fills the rest. Many `height: "100%-N"` strings →
234
- ink flex layout in the port.
200
+ ### Layout (Ink)
201
+ - `ChatApp` owns the chat surface as a React tree under
202
+ `src/ui/ink/`.
203
+ - Dashboard, log, status, completion, internal-agent panes and input are
204
+ rendered from React state with ink flex layout. No blessed widget geometry
205
+ or controller layer remains.
235
206
 
236
207
  ### Commands
237
208
  `/bus`, `/ctx`, `/daemon`, `/doctor`, `/cron`, `/group`, `/init`,
@@ -243,45 +214,30 @@ and `shouldEchoCommandInChat(text)` are pure.
243
214
 
244
215
  ### Cross-cutting
245
216
  - `text.js`: `escapeBlessed`, `stripBlessedTags`, `stripAnsi`,
246
- `truncateAnsi`, `decodeEscapedNewlines`. Pervasive used by every
247
- log message, status line and dashboard line. The blessed-tag
248
- helpers (`escapeBlessed`/`stripBlessedTags`) become no-ops in ink;
249
- the ANSI helpers stay relevant.
217
+ `truncateAnsi`, `decodeEscapedNewlines`. The blessed-tag helpers are now
218
+ compatibility shims for older daemon/router log strings; ink call sites
219
+ strip or normalize those tags before rendering. The ANSI helpers stay
220
+ relevant.
250
221
  - `rawKeyMap.keyToRaw(ch, key)`: converts ink-style key events to
251
222
  PTY bytes for the agent view. Stays as-is.
252
223
  - `transport.js`: `startDaemon`, `stopDaemon`, `connectWithRetry`.
253
224
  Framework-agnostic, no migration needed.
254
225
 
255
- ### Migration concern shortlist (1 per section)
256
- 1. **Entry**: 50+ `screen.render()` calls turn into React state
257
- updates; build a `useReducer` so dispatchers are async-safe.
258
- 2. **State machine**: `setGlobalScope` is async + debounced — model
259
- it as an effect, not a setter.
260
- 3. **Input**: cursor math already lives in `src/ui/format` (P0); we
261
- reuse it.
262
- 4. **Daemon**: keep `daemonConnection` / `daemonCoordinator` as-is;
263
- wrap the message router in a `useEffect` subscription that pumps
264
- into `dispatch`.
265
- 5. **Side controllers**: keep the controllers as plain modules;
266
- `useEffect` subscribes/unsubscribes instead of attaching to
267
- blessed events.
268
- 6. **Agent view**: ink can't render arbitrary ANSI inside a `<Box>`,
269
- but it can yield stdout to a "raw mode" component that writes
270
- straight through during PTY mirror; we'll model it with
271
- `<Static>`-style raw write or by suspending ink's render and
272
- passing stdout through, then re-mount on exit.
273
- 7. **Layout**: replace `height: "100%-N"` with flexbox + `flexGrow`.
274
- 8. **Commands**: long-running commands run on a serialised promise
275
- chain like ucode's `runChainRef`.
276
- 9. **Cross-cutting**: drop blessed-tag helpers from ink call sites;
277
- ANSI/text helpers stay.
226
+ ### Removal notes
227
+ 1. **Entry**: `src/app/chat/index.js` delegates directly to `runChatInk()`.
228
+ 2. **ucode**: `src/code/tui.js` is a compatibility export wrapper around
229
+ `src/ui/format/` and `runUcodeInkTui()`.
230
+ 3. **Controllers**: the blessed widget controllers and their tests were
231
+ removed with the fallback path.
232
+ 4. **Markup**: old brace-tag helpers remain only where shared daemon/chat
233
+ helpers still emit or sanitize legacy log markup.
278
234
 
279
235
  ### P3 phase plan
280
236
 
281
237
  | Step | Goal | Status |
282
238
  |---|---|---|
283
239
  | P3.1 | This audit | ✅ |
284
- | P3.2 | `UFOO_TUI=ink` switch in `runChat()` | ✅ |
240
+ | P3.2 | Ink-only `runChat()` entrypoint | ✅ |
285
241
  | P3.3 | ChatApp shell (banner + log + input + status) | ✅ |
286
242
  | P3.4 | Five dashboard views as React components | ✅ |
287
243
  | P3.5 | Daemon connection + PROMPT/BUS_SEND wiring | ✅ |
@@ -317,7 +273,6 @@ and `shouldEchoCommandInChat(text)` are pure.
317
273
  ```sh
318
274
  ./bin/ufoo.js chat # project mode
319
275
  ./bin/ufoo.js chat --global # global controller mode
320
- # fallback: UFOO_TUI=blessed ./bin/ufoo.js chat
321
276
  ```
322
277
 
323
278
  ### Layout
@@ -366,8 +321,7 @@ and `shouldEchoCommandInChat(text)` are pure.
366
321
  with daemon stop/start/restart, cron IPC, project switching and agent
367
322
  activation callbacks.
368
323
  - **Input history persistence** — `<projectRoot>/.ufoo/chat/input-history.jsonl`
369
- is loaded on mount and appended on every submit; format matches the
370
- blessed inputHistoryController.
324
+ is loaded on mount and appended on every submit.
371
325
  - **Daemon message routing** — Ink routes daemon envelopes through
372
326
  `daemonMessageRouter`, including BUS phase status, transient states,
373
327
  pending delivery markers, streams, close/launch refreshes and loop
@@ -1,12 +1,8 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Pure formatting + input-math helpers shared between the legacy blessed TUI
5
- * (src/code/tui.js, src/chat/index.js) and the new ink-based TUIs under
6
- * src/ui/components/. No blessed import allowed in this module.
7
- *
8
- * Anything that touches a blessed widget (escapeBlessedLiteral, the blessed
9
- * banner builder, resolveLogContentWidth) stays in src/code/tui.js.
4
+ * Pure formatting + input-math helpers for the ink-based TUIs under
5
+ * src/ui/ink/. No terminal widget import is allowed in this module.
10
6
  */
11
7
 
12
8
  const chalk = require("chalk");
@@ -72,24 +68,6 @@ function displayCellWidth(text = "") {
72
68
  );
73
69
  }
74
70
 
75
- // NOTE: returns a blessed-flavoured tag string ("{cyan-bg}{white-fg}...").
76
- // Used by the legacy blessed TUI; ink callers should not render this directly
77
- // (the tags would show up as literal text). When P1 needs the same output for
78
- // ink, add a sibling helper that emits chalk/ANSI instead.
79
- function formatHighlightedUserInput(text = "", {
80
- width = 80,
81
- escapeText = (value) => String(value || ""),
82
- } = {}) {
83
- const plain = String(text || "").trim();
84
- if (!plain) return "";
85
- const targetWidth = Math.max(1, Math.floor(Number(width) || 80) - 1);
86
- const prefix = " → ";
87
- const suffix = " ";
88
- const contentWidth = displayCellWidth(`${prefix}${plain}${suffix}`);
89
- const pad = " ".repeat(Math.max(0, targetWidth - contentWidth));
90
- return `{cyan-bg}{white-fg}${prefix}${escapeText(plain)}${suffix}${pad}{/white-fg}{/cyan-bg}`;
91
- }
92
-
93
71
  class StreamBuffer {
94
72
  constructor(writer, options = {}) {
95
73
  this.writer = writer;
@@ -254,7 +232,7 @@ function loadActiveAgents(workspaceRoot) {
254
232
  }
255
233
 
256
234
  function renderLogLinesWithMarkdown(text = "", state = {}, escapeFn = (value) => String(value || "")) {
257
- const { renderMarkdownLines } = require("../../shared/markdownRenderer");
235
+ const { renderMarkdownLines } = require("./markdownRenderer");
258
236
  return renderMarkdownLines(text, state, escapeFn);
259
237
  }
260
238
 
@@ -331,7 +309,7 @@ function moveCursorToVisualLineBoundary({
331
309
  boundary = "start",
332
310
  strWidth,
333
311
  } = {}) {
334
- const inputMath = require("../../chat/inputMath");
312
+ const inputMath = require("../../app/chat/inputMath");
335
313
  const text = String(inputValue || "");
336
314
  const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
337
315
  const pos = clampCursorPos(cursorPos, text);
@@ -350,7 +328,7 @@ function moveCursorVertically({
350
328
  preferredCol = null,
351
329
  strWidth,
352
330
  } = {}) {
353
- const inputMath = require("../../chat/inputMath");
331
+ const inputMath = require("../../app/chat/inputMath");
354
332
  const text = String(inputValue || "");
355
333
  const normalizedWidth = Number.isFinite(width) ? Math.max(1, Math.floor(width)) : 1;
356
334
  const pos = clampCursorPos(cursorPos, text);
@@ -974,7 +952,6 @@ module.exports = {
974
952
  findLogicalLineEnd,
975
953
  findLogicalLineStart,
976
954
  findTrailingEscapeTagPrefix,
977
- formatHighlightedUserInput,
978
955
  formatPendingElapsed,
979
956
  loadActiveAgents,
980
957
  moveCursorByWord,
package/src/ui/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  const { runInk } = require("./runInk");
4
- const { createInkDemo } = require("./components/InkDemo");
4
+ const { createInkDemo } = require("./ink/InkDemo");
5
5
 
6
6
  module.exports = {
7
7
  runInk,