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
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Agy (Antigravity CLI) conversation-id capture and reuse.
5
+ *
6
+ * Agy ends every TUI session by printing a single line to stdout:
7
+ *
8
+ * Resume: agy --conversation=<UUID> (or -c)
9
+ *
10
+ * We grep this line out of the PTY output ring buffer and persist the UUID
11
+ * onto the agent meta as `provider_session_id`, so the next launch of agy
12
+ * on the same tty/tmux pane can pass `--conversation=<UUID>` to pick up
13
+ * exactly that conversation.
14
+ *
15
+ * The launcher writes the id; bin/uagy.js reads it back before spawning.
16
+ */
17
+
18
+ const fs = require("fs");
19
+ const path = require("path");
20
+
21
+ const { getUfooPaths } = require("../../coordination/state/paths");
22
+ const { isAgentPidAlive } = require("../../coordination/bus/utils");
23
+
24
+ // Capture group is a UUID v4-ish; agy uses standard 8-4-4-4-12 hex form.
25
+ // Allow trailing whitespace and anything after the close paren on the line.
26
+ const RESUME_LINE_RE =
27
+ /Resume:\s+agy\s+--conversation=([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/;
28
+
29
+ function extractResumeConversationId(text = "") {
30
+ const str = String(text || "");
31
+ if (!str) return "";
32
+ const match = str.match(RESUME_LINE_RE);
33
+ return match ? match[1] : "";
34
+ }
35
+
36
+ function readAgentsRegistry(projectRoot) {
37
+ try {
38
+ const file = getUfooPaths(projectRoot).agentsFile;
39
+ if (!fs.existsSync(file)) return { file, data: null };
40
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
41
+ return { file, data };
42
+ } catch {
43
+ return { file: "", data: null };
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Look up the most recent agy conversation id for the given tty/tmux pane.
49
+ *
50
+ * Mirrors src/agents/launch/launcher.js#findPreviousSession but only inspects
51
+ * `meta.provider_session_id`. Returns "" when none found.
52
+ *
53
+ * Selection rules (in this order):
54
+ * 1. Only `agy:` subscribers
55
+ * 2. tmux_pane match (when supplied) or tty match (fallback)
56
+ * 3. Skip entries whose pid is still alive — that session is still
57
+ * running, stealing its id would resume into the live process
58
+ * 4. Among remaining candidates, pick the one with the most recent
59
+ * `provider_session_updated_at` (insertion-order fallback when no
60
+ * timestamp is present)
61
+ */
62
+ function readPreviousConversationId(projectRoot, { tty = "", tmuxPane = "" } = {}) {
63
+ if (!projectRoot) return "";
64
+ if (!tty && !tmuxPane) return "";
65
+ const { data } = readAgentsRegistry(projectRoot);
66
+ if (!data || !data.agents) return "";
67
+
68
+ const candidates = [];
69
+ for (const [subscriberId, meta] of Object.entries(data.agents)) {
70
+ if (!subscriberId.startsWith("agy:")) continue;
71
+ if (!meta || typeof meta !== "object") continue;
72
+ if (tmuxPane) {
73
+ if (meta.tmux_pane !== tmuxPane) continue;
74
+ } else if (tty) {
75
+ if (meta.tty !== tty) continue;
76
+ } else {
77
+ continue;
78
+ }
79
+ const id = String(meta.provider_session_id || "").trim();
80
+ if (!id) continue;
81
+
82
+ // Skip sessions whose owning process is still alive — their
83
+ // conversation id belongs to a running TUI, not a closed one.
84
+ const pid = Number.parseInt(meta.pid, 10);
85
+ if (Number.isFinite(pid) && pid > 0 && isAgentPidAlive(pid)) continue;
86
+
87
+ const updatedAt = Date.parse(String(meta.provider_session_updated_at || "")) || 0;
88
+ candidates.push({ id, updatedAt });
89
+ }
90
+ if (candidates.length === 0) return "";
91
+ candidates.sort((a, b) => b.updatedAt - a.updatedAt);
92
+ return candidates[0].id;
93
+ }
94
+
95
+ function persistConversationId(projectRoot, subscriberId, conversationId) {
96
+ const id = String(conversationId || "").trim();
97
+ if (!projectRoot || !subscriberId || !id) return false;
98
+ try {
99
+ const file = getUfooPaths(projectRoot).agentsFile;
100
+ if (!fs.existsSync(file)) return false;
101
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
102
+ if (!data || !data.agents || !data.agents[subscriberId]) return false;
103
+ if (data.agents[subscriberId].provider_session_id === id) return false;
104
+ data.agents[subscriberId].provider_session_id = id;
105
+ data.agents[subscriberId].provider_session_updated_at = new Date().toISOString();
106
+ const tmp = `${file}.tmp`;
107
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
108
+ fs.renameSync(tmp, file);
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Build the agy command-line args for a fresh launch:
117
+ * - prepend --conversation=<uuid> if previousConversationId is set
118
+ * (unless the user already passed --continue / -c / --conversation)
119
+ * - prepend --dangerously-skip-permissions in internal/auto-approve mode
120
+ * - return the args caller hands to AgentLauncher.launch()
121
+ *
122
+ * Bootstrap merging (i.e. `-i <text>`) is handled by defaultBootstrap.js;
123
+ * this helper only deals with the resume + permission flags.
124
+ */
125
+ function buildAgyLaunchArgs({
126
+ userArgs = [],
127
+ previousConversationId = "",
128
+ skipPermissions = false,
129
+ } = {}) {
130
+ const args = Array.isArray(userArgs) ? userArgs.slice() : [];
131
+ const flat = args.map((item) => String(item || "").trim()).filter(Boolean);
132
+
133
+ const userHasResume = flat.some((item) =>
134
+ item === "-c"
135
+ || item === "--continue"
136
+ || item === "--conversation"
137
+ || item.startsWith("--conversation=")
138
+ );
139
+ const userHasSkipPerms = flat.includes("--dangerously-skip-permissions");
140
+
141
+ const out = [];
142
+ if (skipPermissions && !userHasSkipPerms) {
143
+ out.push("--dangerously-skip-permissions");
144
+ }
145
+ const resumeId = String(previousConversationId || "").trim();
146
+ if (resumeId && !userHasResume) {
147
+ out.push(`--conversation=${resumeId}`);
148
+ }
149
+ out.push(...args);
150
+ return out;
151
+ }
152
+
153
+ module.exports = {
154
+ RESUME_LINE_RE,
155
+ extractResumeConversationId,
156
+ readPreviousConversationId,
157
+ persistConversationId,
158
+ buildAgyLaunchArgs,
159
+ };
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ module.exports = {
4
+ AgentLauncher: require("./launcher"),
5
+ AgentNotifier: require("./notifier"),
6
+ PtyWrapper: require("./ptyWrapper"),
7
+ ReadyDetector: require("./readyDetector"),
8
+ ...require("./agyConversation"),
9
+ ...require("./launchEnvironment"),
10
+ ...require("./ptyRunner"),
11
+ ...require("./publisherRouting"),
12
+ };
@@ -18,8 +18,8 @@
18
18
  * Host is detected by env var alone here; callers that want to override host
19
19
  * with native-terminal evidence (e.g. daemon ops auto-resolution) layer that
20
20
  * on top of the detector's output.
21
- * Internal / internal-pty modes are always set explicitly by the daemon when
22
- * spawning worker processes (see ptyRunner / daemon ops), so they fall through
21
+ * Internal modes are always set explicitly by the daemon when
22
+ * spawning worker processes (see daemon ops), so they fall through
23
23
  * the explicit short-circuit and never need to be auto-detected here.
24
24
  */
25
25
 
@@ -28,7 +28,6 @@ const CANONICAL_LAUNCH_MODES = Object.freeze([
28
28
  "tmux",
29
29
  "host",
30
30
  "internal",
31
- "internal-pty",
32
31
  ]);
33
32
 
34
33
  const CANONICAL_LAUNCH_MODE_SET = new Set(CANONICAL_LAUNCH_MODES);
@@ -1,21 +1,25 @@
1
- const { IPC_REQUEST_TYPES } = require("../shared/eventContract");
2
- const { PTY_SOCKET_MESSAGE_TYPES } = require("../shared/ptySocketContract");
1
+ const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
2
+ const { PTY_SOCKET_MESSAGE_TYPES } = require("../../runtime/contracts/ptySocketContract");
3
3
  const { spawn, spawnSync } = require("child_process");
4
4
  const fs = require("fs");
5
5
  const net = require("net");
6
6
  const path = require("path");
7
- const EventBus = require("../bus");
8
- const { isAgentPidAlive } = require("../bus/utils");
9
- const { showBanner } = require("../utils/banner");
7
+ const EventBus = require("../../coordination/bus");
8
+ const { isAgentPidAlive } = require("../../coordination/bus/utils");
9
+ const { showBanner } = require("../../ui/format/banner");
10
10
  const AgentNotifier = require("./notifier");
11
- const { ActivityDetector } = require("./activityDetector");
12
- const { createActivityStatePublisher } = require("./activityStatePublisher");
11
+ const { ActivityDetector } = require("../activity/activityDetector");
12
+ const { createActivityStatePublisher } = require("../activity/activityStatePublisher");
13
13
  const { detectLaunchEnvironment } = require("./launchEnvironment");
14
- const { getUfooPaths } = require("../ufoo/paths");
15
- const { createTerminalAdapterRouter } = require("../terminal/adapterRouter");
16
- const { probeHostCapabilities } = require("../terminal/adapters/hostAdapter");
14
+ const { getUfooPaths } = require("../../coordination/state/paths");
15
+ const { createTerminalAdapterRouter } = require("../../runtime/terminal/adapterRouter");
16
+ const { probeHostCapabilities } = require("../../runtime/terminal/adapters/hostAdapter");
17
17
  const PtyWrapper = require("./ptyWrapper");
18
18
  const ReadyDetector = require("./readyDetector");
19
+ const {
20
+ extractResumeConversationId,
21
+ persistConversationId,
22
+ } = require("./agyConversation");
19
23
 
20
24
  function connectSocket(sockPath) {
21
25
  return new Promise((resolve, reject) => {
@@ -204,9 +208,12 @@ function shouldShowLaunchBanner(agentType = "") {
204
208
  function computeInjectedSubmitDelayMs(agentType, text) {
205
209
  const normalizedAgent = String(agentType || "").trim().toLowerCase();
206
210
  const input = typeof text === "string" ? text : "";
207
- let delayMs = normalizedAgent === "claude-code" ? 350 : 200;
211
+ // Agy uses an ink-style TUI like claude-code, so it needs a similar grace
212
+ // window before the input handler picks up injected text.
213
+ const isInkStyle = normalizedAgent === "claude-code" || normalizedAgent === "agy";
214
+ let delayMs = isInkStyle ? 350 : 200;
208
215
  if (input.includes("\n")) {
209
- delayMs += normalizedAgent === "claude-code" ? 250 : 120;
216
+ delayMs += isInkStyle ? 250 : 120;
210
217
  }
211
218
  if (input.length > 512) {
212
219
  delayMs += Math.min(1200, Math.ceil(input.length / 512) * 90);
@@ -237,7 +244,11 @@ async function injectPtyCommand(wrapper, agentType, commandText, source = "injec
237
244
  const normalizedAgentType = String(agentType || "").trim().toLowerCase();
238
245
  const submitDelayMs = computeInjectedSubmitDelayMs(agentType, text);
239
246
  wrapper.write(text);
240
- if (normalizedAgentType === "claude-code") {
247
+ // claude-code and agy both run ink-style TUIs that accept a bare CR to
248
+ // submit. codex needs the Esc-prefix trick to flush its multi-byte input
249
+ // handler before the CR.
250
+ const isInkStyle = normalizedAgentType === "claude-code" || normalizedAgentType === "agy";
251
+ if (isInkStyle) {
241
252
  await sleep(submitDelayMs);
242
253
  wrapper.write("\r");
243
254
  } else {
@@ -465,7 +476,7 @@ class AgentLauncher {
465
476
  hostName,
466
477
  hostSessionId,
467
478
  hostCapabilities,
468
- skipProbe: process.env.UFOO_SKIP_SESSION_PROBE === "1",
479
+ skipSessionResolve: process.env.UFOO_SKIP_SESSION_RESOLVE === "1",
469
480
  // 传递旧 session 信息用于复用(仅 terminal/tmux 模式)
470
481
  reuseSession: previousSession ? {
471
482
  sessionId: previousSession.sessionId,
@@ -694,10 +705,20 @@ class AgentLauncher {
694
705
  detail: snap.detail,
695
706
  });
696
707
  });
708
+ // Tail buffer for agy: when the TUI exits, agy prints a single line
709
+ // `Resume: agy --conversation=<UUID> (or -c)` to stdout. We keep
710
+ // the trailing ~4KB of post-frame output around and grep it on exit
711
+ // to capture the conversation id, then persist it as the agent's
712
+ // provider_session_id so the next launch can pass `--conversation`.
713
+ const agyTailBuffer = { text: "" };
714
+ const AGY_TAIL_MAX = 4096;
697
715
  wrapper.enableMonitoring((data) => {
698
716
  readyDetector.processOutput(data);
699
717
  const text = typeof data === "string" ? data : Buffer.from(data).toString("utf8");
700
718
  launcherActivityDetector.processOutput(text);
719
+ if (this.agentType === "agy") {
720
+ agyTailBuffer.text = (agyTailBuffer.text + text).slice(-AGY_TAIL_MAX);
721
+ }
701
722
  });
702
723
  readyDetector.onReady(async () => {
703
724
  launcherActivityDetector.markReady();
@@ -708,17 +729,19 @@ class AgentLauncher {
708
729
  // Claude Code's Ink TUI renders ❯ prompt before the input handler
709
730
  // is fully mounted. Wait a short period for the TUI to be ready to
710
731
  // accept injected text, otherwise only the trailing CR is processed
711
- // and the probe command is lost.
712
- if (this.agentType === "claude-code") {
732
+ // and the injected slash command is lost. Agy uses the same ink-style TUI
733
+ // so it gets the same grace window.
734
+ if (this.agentType === "claude-code" || this.agentType === "agy") {
713
735
  await new Promise((r) => setTimeout(r, 800));
714
736
  }
715
737
 
716
- // Claude Code: inject /rename 设置 session 标签(在 AGENT_READY/bootstrap 之前)
717
- // /rename slash 命令,瞬间完成,不调 LLM
718
- // 仅在有显式 nickname 时注入(UFOO_NICKNAME group launch 或用户指定)
719
- // 使用启动前保存的原始值,避免复用 nickname 污染
738
+ // Inject /rename to set the session label (before AGENT_READY/bootstrap).
739
+ // /rename is a slash command, completes instantly, doesn't hit the LLM.
740
+ // Only when an explicit nickname was supplied (UFOO_NICKNAME from group
741
+ // launch or user). Both claude-code and agy support /rename.
720
742
  const explicitNickname = this._originalNickname || "";
721
- if (this.agentType === "claude-code" && explicitNickname && wrapper.pty) {
743
+ const supportsRenameSlash = this.agentType === "claude-code" || this.agentType === "agy";
744
+ if (supportsRenameSlash && explicitNickname && wrapper.pty) {
722
745
  try {
723
746
  const safeNick = AgentLauncher._sanitizeNickname(explicitNickname);
724
747
  if (safeNick) {
@@ -750,6 +773,21 @@ class AgentLauncher {
750
773
  clearTimeout(forceReadyTimer);
751
774
  launcherActivityDetector.destroy();
752
775
 
776
+ // Agy: harvest conversation id from the trailing `Resume: agy
777
+ // --conversation=<UUID>` line that agy prints right before exit,
778
+ // and persist it onto the agent meta so the next launch can
779
+ // pass `--conversation=<UUID>`.
780
+ if (this.agentType === "agy") {
781
+ const conversationId = extractResumeConversationId(agyTailBuffer.text);
782
+ if (conversationId) {
783
+ try {
784
+ persistConversationId(this.cwd, subscriberId, conversationId);
785
+ } catch {
786
+ // ignore — best-effort capture only
787
+ }
788
+ }
789
+ }
790
+
753
791
  // 清理 bus 状态
754
792
  try {
755
793
  const bus = new EventBus(this.cwd);
@@ -956,5 +994,10 @@ AgentLauncher._sanitizeNickname = (nick) => nick.replace(/[^a-zA-Z0-9_-]/g, "");
956
994
  AgentLauncher._findPreviousSession = findPreviousSession;
957
995
  AgentLauncher._notifyDaemonAgentReady = notifyDaemonAgentReady;
958
996
  AgentLauncher._injectPtyCommand = injectPtyCommand;
997
+ // Exposed for bin/uagy.js so it can read the same tty value the launcher
998
+ // will later record on the agent meta — keeps autoResume lookups in sync
999
+ // with the launcher's own tty/tmux detection.
1000
+ AgentLauncher._detectTtyOnce = detectTtyOnce;
1001
+ AgentLauncher._getEnvTtyOverride = getEnvTtyOverride;
959
1002
 
960
1003
  module.exports = AgentLauncher;
@@ -1,15 +1,16 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const EventBus = require("../bus");
4
- const { readJSON, writeJSON } = require("../bus/utils");
5
- const Injector = require("../bus/inject");
6
- const { getUfooPaths } = require("../ufoo/paths");
7
- const { appendAgentRegistryDiagnostic } = require("../ufoo/agentRegistryDiagnostics");
8
- const { shakeTerminalByTty } = require("../bus/shake");
9
- const { isITerm2 } = require("../terminal/detect");
10
- const iterm2 = require("../terminal/iterm2");
11
- const { createActivityStatePublisher } = require("./activityStatePublisher");
12
- const { INJECTION_MODES, getInjectionModeFromEvent } = require("../bus/messageMeta");
3
+ const EventBus = require("../../coordination/bus");
4
+ const { readJSON, writeJSON } = require("../../coordination/bus/utils");
5
+ const Injector = require("../../coordination/bus/inject");
6
+ const { getUfooPaths } = require("../../coordination/state/paths");
7
+ const { appendAgentRegistryDiagnostic } = require("../../coordination/state/agentRegistryDiagnostics");
8
+ const { shakeTerminalByTty } = require("../../coordination/bus/shake");
9
+ const { isITerm2 } = require("../../runtime/terminal/detect");
10
+ const iterm2 = require("../../runtime/terminal/iterm2");
11
+ const { createActivityStatePublisher } = require("../activity/activityStatePublisher");
12
+ const { INJECTION_MODES, getInjectionModeFromEvent } = require("../../coordination/bus/messageMeta");
13
+ const { buildPromptInjectionText } = require("../../coordination/bus/promptEnvelope");
13
14
 
14
15
  /**
15
16
  * Agent 消息通知监听器
@@ -153,6 +154,16 @@ class AgentNotifier {
153
154
  }
154
155
  }
155
156
 
157
+ getAgentsMap() {
158
+ try {
159
+ if (!this.agentsFile || !fs.existsSync(this.agentsFile)) return {};
160
+ const data = readJSON(this.agentsFile, null);
161
+ return data && data.agents && typeof data.agents === "object" ? data.agents : {};
162
+ } catch {
163
+ return {};
164
+ }
165
+ }
166
+
156
167
  isBusyState(state = "") {
157
168
  const value = String(state || "").trim().toLowerCase();
158
169
  return value === "working"
@@ -280,9 +291,9 @@ class AgentNotifier {
280
291
  requeue.push(evt);
281
292
  continue;
282
293
  }
283
- const message = String(evt.data.message);
294
+ const message = buildPromptInjectionText(evt, this.subscriber, this.getAgentsMap());
284
295
  try {
285
- // Inject the actual message text into the terminal/tmux agent
296
+ // Inject the prompt-facing text into the terminal/tmux agent
286
297
  // (Bus is the source of truth; inject is the delivery adapter.)
287
298
  // eslint-disable-next-line no-await-in-loop
288
299
  await this.injector.inject(this.subscriber, message);
@@ -2,11 +2,11 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const net = require("net");
4
4
  const { spawnSync } = require("child_process");
5
- const EventBus = require("../bus");
6
- const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../shared/ptySocketContract");
7
- const { getUfooPaths } = require("../ufoo/paths");
8
- const { ActivityDetector } = require("./activityDetector");
9
- const { createActivityStatePublisher } = require("./activityStatePublisher");
5
+ const EventBus = require("../../coordination/bus");
6
+ const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../../runtime/contracts/ptySocketContract");
7
+ const { getUfooPaths } = require("../../coordination/state/paths");
8
+ const { ActivityDetector } = require("../activity/activityDetector");
9
+ const { createActivityStatePublisher } = require("../activity/activityStatePublisher");
10
10
  const {
11
11
  parseStreamEnvelope,
12
12
  shouldAutoReplyFromPtyToPublisher,
@@ -15,7 +15,8 @@ const {
15
15
  const {
16
16
  isValueForCodexOption,
17
17
  resolveDefaultManualBootstrap,
18
- } = require("./defaultBootstrap");
18
+ } = require("../prompts/defaultBootstrap");
19
+ const { buildPromptInjectionText } = require("../../coordination/bus/promptEnvelope");
19
20
 
20
21
  function sleep(ms) {
21
22
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -97,6 +98,32 @@ function parseInputMessage(message) {
97
98
  return { raw: false, text: message };
98
99
  }
99
100
 
101
+ function readAgentsMap(agentsFilePath) {
102
+ try {
103
+ if (!agentsFilePath || !fs.existsSync(agentsFilePath)) return {};
104
+ const data = JSON.parse(fs.readFileSync(agentsFilePath, "utf8"));
105
+ return data && data.agents && typeof data.agents === "object" ? data.agents : {};
106
+ } catch {
107
+ return {};
108
+ }
109
+ }
110
+
111
+ function buildPtyInputFromEvent(evt = {}, subscriber = "", agents = {}) {
112
+ if (!evt || !evt.data || typeof evt.data.message !== "string") return null;
113
+ const input = parseInputMessage(evt.data.message);
114
+ if (!input) return null;
115
+ if (input.raw) return input;
116
+
117
+ return {
118
+ raw: false,
119
+ text: buildPromptInjectionText(
120
+ { ...evt, data: { ...evt.data, message: input.text } },
121
+ subscriber,
122
+ agents
123
+ ),
124
+ };
125
+ }
126
+
100
127
  function getOuterTerminalSize() {
101
128
  const cols = Number.isInteger(process.stdout && process.stdout.columns) && process.stdout.columns > 0
102
129
  ? process.stdout.columns
@@ -197,10 +224,14 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
197
224
  let Terminal = null;
198
225
  let SerializeAddon = null;
199
226
  try {
200
- const xterm = await import("xterm-headless");
201
- const serialize = await import("xterm-addon-serialize");
202
- Terminal = xterm.Terminal || (xterm.default && xterm.default.Terminal);
203
- SerializeAddon = serialize.SerializeAddon || (serialize.default && serialize.default.SerializeAddon);
227
+ const xterm = await import("@xterm/headless");
228
+ const serialize = await import("@xterm/addon-serialize");
229
+ Terminal = xterm.Terminal
230
+ || (xterm.default && xterm.default.Terminal)
231
+ || (xterm["module.exports"] && xterm["module.exports"].Terminal);
232
+ SerializeAddon = serialize.SerializeAddon
233
+ || (serialize.default && serialize.default.SerializeAddon)
234
+ || (serialize["module.exports"] && serialize["module.exports"].SerializeAddon);
204
235
  } catch {
205
236
  Terminal = null;
206
237
  SerializeAddon = null;
@@ -1015,6 +1046,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
1015
1046
 
1016
1047
  const lines = drainQueue(queueFile);
1017
1048
  if (lines.length > 0) {
1049
+ const agents = readAgentsMap(agentsFilePath);
1018
1050
  const events = [];
1019
1051
  for (const line of lines) {
1020
1052
  try {
@@ -1024,8 +1056,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
1024
1056
  }
1025
1057
  }
1026
1058
  for (const evt of events) {
1027
- if (!evt || !evt.data || typeof evt.data.message !== "string") continue;
1028
- const input = parseInputMessage(evt.data.message);
1059
+ const input = buildPtyInputFromEvent(evt, subscriber, agents);
1029
1060
  if (!input) continue;
1030
1061
  const { raw, text } = input;
1031
1062
  if (messageQueue.length >= maxQueue) {
@@ -1045,6 +1076,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
1045
1076
 
1046
1077
  module.exports = {
1047
1078
  appendStartupBootstrapArg,
1079
+ buildPtyInputFromEvent,
1048
1080
  parseInputMessage,
1049
1081
  resolvePtyBootstrapArgs,
1050
1082
  resolveCommand,
@@ -1,6 +1,6 @@
1
1
  const pty = require("node-pty");
2
2
  const fs = require("fs");
3
- const { isITerm2 } = require("../terminal/detect");
3
+ const { isITerm2 } = require("../../runtime/terminal/detect");
4
4
 
5
5
  /**
6
6
  * PTY Wrapper - 包装原始agent命令,提供IO控制和监控
@@ -12,7 +12,7 @@ const { isITerm2 } = require("../terminal/detect");
12
12
  * - 完善的资源清理(防止泄漏)
13
13
  *
14
14
  * 参考:
15
- * - ptyRunner.js - PTY实现参考
15
+ * - launcher.js - PTY实现参考
16
16
  * - codex-44 review反馈 (2026-02-05)
17
17
  */
18
18
  class PtyWrapper {
@@ -1,5 +1,5 @@
1
1
  const fs = require("fs");
2
- const { getUfooPaths } = require("../ufoo/paths");
2
+ const { getUfooPaths } = require("../../coordination/state/paths");
3
3
 
4
4
  function normalizePublisher(publisher) {
5
5
  if (typeof publisher === "string") return publisher.trim();
@@ -87,6 +87,27 @@ class ReadyDetector {
87
87
  return false;
88
88
  }
89
89
 
90
+ /**
91
+ * Detect agy (Antigravity CLI) ready markers.
92
+ *
93
+ * agy renders an ink-style TUI with:
94
+ * - A status footer "? for shortcuts" that appears once the prompt box
95
+ * is mounted (most reliable signal — only present post-boot).
96
+ * - A bordered input row with a lone blue ">" prefix. We can't grep
97
+ * for just ">" without misfiring (codex/ucode use the same), so we
98
+ * anchor on the statusline phrase instead.
99
+ *
100
+ * If the account is blocked ("Eligibility check failed"), the TUI still
101
+ * mounts and "? for shortcuts" still appears — we let ready fire so the
102
+ * user sees the error. activityDetector handles the BLOCKED state.
103
+ */
104
+ _detectAgyReady(text) {
105
+ if (text.includes("? for shortcuts")) {
106
+ return true;
107
+ }
108
+ return false;
109
+ }
110
+
90
111
  /**
91
112
  * 检测ufoo-code/ucode的ready标记
92
113
  */
@@ -135,6 +156,8 @@ class ReadyDetector {
135
156
  isReady = this._detectClaudeCodeReady(this.buffer);
136
157
  } else if (this.agentType === "codex") {
137
158
  isReady = this._detectCodexReady(this.buffer);
159
+ } else if (this.agentType === "agy") {
160
+ isReady = this._detectAgyReady(this.buffer);
138
161
  } else if (this.agentType === "ufoo" || this.agentType === "ucode" || this.agentType === "ufoo-code") {
139
162
  isReady = this._detectUfooCodeReady(this.buffer);
140
163
  }