@agentprojectcontext/apx 1.15.6 → 1.17.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 (222) hide show
  1. package/package.json +46 -5
  2. package/src/cli/commands/log.js +113 -0
  3. package/src/cli/commands/overlay.js +253 -0
  4. package/src/cli/commands/sys.js +88 -16
  5. package/src/cli/index.js +23 -1
  6. package/src/cli/terminal-chat/renderer.js +71 -56
  7. package/src/cli-ts/commands/agent.ts +173 -0
  8. package/src/cli-ts/commands/chat.ts +119 -0
  9. package/src/cli-ts/commands/daemon.ts +112 -0
  10. package/src/cli-ts/commands/exec.ts +109 -0
  11. package/src/cli-ts/commands/mcp.ts +235 -0
  12. package/src/cli-ts/commands/session.ts +224 -0
  13. package/src/cli-ts/commands/status.ts +61 -0
  14. package/src/cli-ts/http.ts +36 -0
  15. package/src/cli-ts/index.ts +73 -0
  16. package/src/cli-ts/ui.ts +107 -0
  17. package/src/core/logging.js +81 -0
  18. package/src/daemon/api.js +58 -0
  19. package/src/daemon/engines/anthropic.js +60 -1
  20. package/src/daemon/engines/index.js +2 -1
  21. package/src/daemon/engines/ollama.js +70 -3
  22. package/src/daemon/index.js +58 -0
  23. package/src/daemon/overlay-ws.js +40 -0
  24. package/src/daemon/plugins/index.js +2 -1
  25. package/src/daemon/plugins/overlay.js +177 -0
  26. package/src/daemon/plugins/telegram.js +15 -3
  27. package/src/daemon/super-agent-langchain.js +296 -0
  28. package/src/daemon/super-agent.js +115 -19
  29. package/src/daemon/transcription.js +262 -59
  30. package/src/daemon/whisper-server.py +57 -6
  31. package/src/overlay/index.html +44 -0
  32. package/src/overlay/main.js +480 -0
  33. package/src/overlay/package.json +3 -0
  34. package/src/overlay/preload.js +34 -0
  35. package/src/overlay/renderer.js +371 -0
  36. package/src/overlay/style.css +250 -0
  37. package/src/tui/_shims/cli-error.ts +6 -0
  38. package/src/tui/_shims/cli-logo.ts +18 -0
  39. package/src/tui/_shims/cli-ui.ts +1 -0
  40. package/src/tui/_shims/config-console-state.ts +7 -0
  41. package/src/tui/_shims/core-any.ts +30 -0
  42. package/src/tui/_shims/core-binary.ts +13 -0
  43. package/src/tui/_shims/core-flag.ts +3 -0
  44. package/src/tui/_shims/core-log.ts +14 -0
  45. package/src/tui/_shims/lsp-language.ts +1 -0
  46. package/src/tui/_shims/opencode-any.ts +135 -0
  47. package/src/tui/_shims/opencode-sdk-v2.ts +48 -0
  48. package/src/tui/_shims/plugin-tui.ts +13 -0
  49. package/src/tui/_shims/provider-provider.ts +10 -0
  50. package/src/tui/_shims/session-retry.ts +1 -0
  51. package/src/tui/_shims/session-schema.ts +15 -0
  52. package/src/tui/_shims/session-session.ts +3 -0
  53. package/src/tui/_shims/snapshot.ts +4 -0
  54. package/src/tui/_shims/tool-any.ts +18 -0
  55. package/src/tui/_shims/util-error.ts +7 -0
  56. package/src/tui/_shims/util-filesystem.ts +79 -0
  57. package/src/tui/_shims/util-format.ts +7 -0
  58. package/src/tui/_shims/util-iife.ts +3 -0
  59. package/src/tui/_shims/util-locale.ts +10 -0
  60. package/src/tui/_shims/util-process.ts +38 -0
  61. package/src/tui/app.tsx +783 -0
  62. package/src/tui/asset/charge.wav +0 -0
  63. package/src/tui/asset/pulse-a.wav +0 -0
  64. package/src/tui/asset/pulse-b.wav +0 -0
  65. package/src/tui/asset/pulse-c.wav +0 -0
  66. package/src/tui/attach.ts +100 -0
  67. package/src/tui/component/bg-pulse-render.ts +436 -0
  68. package/src/tui/component/bg-pulse.tsx +99 -0
  69. package/src/tui/component/border.tsx +21 -0
  70. package/src/tui/component/dialog-agent.tsx +31 -0
  71. package/src/tui/component/dialog-console-org.tsx +103 -0
  72. package/src/tui/component/dialog-mcp.tsx +85 -0
  73. package/src/tui/component/dialog-model.tsx +175 -0
  74. package/src/tui/component/dialog-provider.tsx +456 -0
  75. package/src/tui/component/dialog-retry-action.tsx +160 -0
  76. package/src/tui/component/dialog-session-delete-failed.tsx +99 -0
  77. package/src/tui/component/dialog-session-list.tsx +323 -0
  78. package/src/tui/component/dialog-session-rename.tsx +31 -0
  79. package/src/tui/component/dialog-skill.tsx +36 -0
  80. package/src/tui/component/dialog-stash.tsx +87 -0
  81. package/src/tui/component/dialog-status.tsx +168 -0
  82. package/src/tui/component/dialog-tag.tsx +44 -0
  83. package/src/tui/component/dialog-theme-list.tsx +50 -0
  84. package/src/tui/component/dialog-variant.tsx +39 -0
  85. package/src/tui/component/dialog-workspace-create.tsx +302 -0
  86. package/src/tui/component/dialog-workspace-file-changes.tsx +138 -0
  87. package/src/tui/component/dialog-workspace-unavailable.tsx +69 -0
  88. package/src/tui/component/error-component.tsx +92 -0
  89. package/src/tui/component/logo.tsx +896 -0
  90. package/src/tui/component/plugin-route-missing.tsx +14 -0
  91. package/src/tui/component/prompt/autocomplete.tsx +869 -0
  92. package/src/tui/component/prompt/cwd.ts +0 -0
  93. package/src/tui/component/prompt/frecency.tsx +90 -0
  94. package/src/tui/component/prompt/history.tsx +108 -0
  95. package/src/tui/component/prompt/index.tsx +1809 -0
  96. package/src/tui/component/prompt/part.ts +16 -0
  97. package/src/tui/component/prompt/stash.tsx +101 -0
  98. package/src/tui/component/prompt/traits.ts +35 -0
  99. package/src/tui/component/spinner.tsx +24 -0
  100. package/src/tui/component/startup-loading.tsx +63 -0
  101. package/src/tui/component/todo-item.tsx +32 -0
  102. package/src/tui/component/use-connected.tsx +9 -0
  103. package/src/tui/component/workspace-label.tsx +19 -0
  104. package/src/tui/config/cwd.ts +5 -0
  105. package/src/tui/config/keybind.ts +432 -0
  106. package/src/tui/config/tui-migrate.ts +154 -0
  107. package/src/tui/config/tui-schema.ts +34 -0
  108. package/src/tui/config/tui.ts +46 -0
  109. package/src/tui/context/aggregate-failures.ts +34 -0
  110. package/src/tui/context/args.tsx +15 -0
  111. package/src/tui/context/command-palette.tsx +163 -0
  112. package/src/tui/context/directory.ts +15 -0
  113. package/src/tui/context/editor-zed.ts +283 -0
  114. package/src/tui/context/editor.ts +468 -0
  115. package/src/tui/context/event-apx.ts +22 -0
  116. package/src/tui/context/event.ts +6 -0
  117. package/src/tui/context/exit.tsx +60 -0
  118. package/src/tui/context/helper.tsx +25 -0
  119. package/src/tui/context/kv.tsx +81 -0
  120. package/src/tui/context/local.tsx +608 -0
  121. package/src/tui/context/path-format.tsx +39 -0
  122. package/src/tui/context/project-apx.tsx +48 -0
  123. package/src/tui/context/project.tsx +7 -0
  124. package/src/tui/context/prompt.tsx +18 -0
  125. package/src/tui/context/route.tsx +52 -0
  126. package/src/tui/context/sdk-apx.tsx +185 -0
  127. package/src/tui/context/sdk.tsx +6 -0
  128. package/src/tui/context/sync-apx.tsx +178 -0
  129. package/src/tui/context/sync-v2.tsx +16 -0
  130. package/src/tui/context/sync.tsx +118 -0
  131. package/src/tui/context/theme/aura.json +69 -0
  132. package/src/tui/context/theme/ayu.json +80 -0
  133. package/src/tui/context/theme/carbonfox.json +248 -0
  134. package/src/tui/context/theme/catppuccin-frappe.json +230 -0
  135. package/src/tui/context/theme/catppuccin-macchiato.json +230 -0
  136. package/src/tui/context/theme/catppuccin.json +112 -0
  137. package/src/tui/context/theme/cobalt2.json +225 -0
  138. package/src/tui/context/theme/cursor.json +249 -0
  139. package/src/tui/context/theme/dracula.json +219 -0
  140. package/src/tui/context/theme/everforest.json +241 -0
  141. package/src/tui/context/theme/flexoki.json +237 -0
  142. package/src/tui/context/theme/github.json +233 -0
  143. package/src/tui/context/theme/gruvbox.json +242 -0
  144. package/src/tui/context/theme/kanagawa.json +77 -0
  145. package/src/tui/context/theme/lucent-orng.json +234 -0
  146. package/src/tui/context/theme/material.json +235 -0
  147. package/src/tui/context/theme/matrix.json +77 -0
  148. package/src/tui/context/theme/mercury.json +252 -0
  149. package/src/tui/context/theme/monokai.json +221 -0
  150. package/src/tui/context/theme/nightowl.json +221 -0
  151. package/src/tui/context/theme/nord.json +223 -0
  152. package/src/tui/context/theme/one-dark.json +84 -0
  153. package/src/tui/context/theme/opencode.json +245 -0
  154. package/src/tui/context/theme/orng.json +249 -0
  155. package/src/tui/context/theme/osaka-jade.json +93 -0
  156. package/src/tui/context/theme/palenight.json +222 -0
  157. package/src/tui/context/theme/rosepine.json +234 -0
  158. package/src/tui/context/theme/solarized.json +223 -0
  159. package/src/tui/context/theme/synthwave84.json +226 -0
  160. package/src/tui/context/theme/tokyonight.json +243 -0
  161. package/src/tui/context/theme/vercel.json +245 -0
  162. package/src/tui/context/theme/vesper.json +218 -0
  163. package/src/tui/context/theme/zenburn.json +223 -0
  164. package/src/tui/context/theme.tsx +1247 -0
  165. package/src/tui/context/tui-config.tsx +9 -0
  166. package/src/tui/event.ts +16 -0
  167. package/src/tui/feature-plugins/home/footer.tsx +94 -0
  168. package/src/tui/feature-plugins/home/tips-view.tsx +166 -0
  169. package/src/tui/feature-plugins/home/tips.tsx +59 -0
  170. package/src/tui/feature-plugins/sidebar/context.tsx +65 -0
  171. package/src/tui/feature-plugins/sidebar/files.tsx +63 -0
  172. package/src/tui/feature-plugins/sidebar/footer.tsx +94 -0
  173. package/src/tui/feature-plugins/sidebar/lsp.tsx +65 -0
  174. package/src/tui/feature-plugins/sidebar/mcp.tsx +97 -0
  175. package/src/tui/feature-plugins/sidebar/todo.tsx +49 -0
  176. package/src/tui/feature-plugins/system/plugins.tsx +269 -0
  177. package/src/tui/feature-plugins/system/session-v2.tsx +1143 -0
  178. package/src/tui/feature-plugins/system/which-key.tsx +608 -0
  179. package/src/tui/keymap.tsx +166 -0
  180. package/src/tui/layer.ts +6 -0
  181. package/src/tui/plugin/api.tsx +381 -0
  182. package/src/tui/plugin/command-shim.ts +109 -0
  183. package/src/tui/plugin/internal.ts +33 -0
  184. package/src/tui/plugin/runtime.ts +1069 -0
  185. package/src/tui/plugin/slots.tsx +60 -0
  186. package/src/tui/routes/home.tsx +96 -0
  187. package/src/tui/routes/session/dialog-fork-from-timeline.tsx +76 -0
  188. package/src/tui/routes/session/dialog-message.tsx +108 -0
  189. package/src/tui/routes/session/dialog-subagent.tsx +26 -0
  190. package/src/tui/routes/session/dialog-timeline.tsx +47 -0
  191. package/src/tui/routes/session/footer.tsx +91 -0
  192. package/src/tui/routes/session/index.tsx +188 -0
  193. package/src/tui/routes/session/permission.tsx +722 -0
  194. package/src/tui/routes/session/question.tsx +490 -0
  195. package/src/tui/routes/session/sidebar.tsx +102 -0
  196. package/src/tui/routes/session/subagent-footer.tsx +133 -0
  197. package/src/tui/run.ts +84 -0
  198. package/src/tui/thread.ts +261 -0
  199. package/src/tui/tsconfig.json +40 -0
  200. package/src/tui/ui/dialog-alert.tsx +66 -0
  201. package/src/tui/ui/dialog-confirm.tsx +108 -0
  202. package/src/tui/ui/dialog-export-options.tsx +217 -0
  203. package/src/tui/ui/dialog-help.tsx +40 -0
  204. package/src/tui/ui/dialog-prompt.tsx +101 -0
  205. package/src/tui/ui/dialog-select.tsx +553 -0
  206. package/src/tui/ui/dialog.tsx +211 -0
  207. package/src/tui/ui/link.tsx +34 -0
  208. package/src/tui/ui/spinner.ts +368 -0
  209. package/src/tui/ui/toast.tsx +111 -0
  210. package/src/tui/util/clipboard.ts +217 -0
  211. package/src/tui/util/editor.ts +37 -0
  212. package/src/tui/util/model.ts +23 -0
  213. package/src/tui/util/provider-origin.ts +7 -0
  214. package/src/tui/util/revert-diff.ts +18 -0
  215. package/src/tui/util/scroll.ts +25 -0
  216. package/src/tui/util/selection.ts +65 -0
  217. package/src/tui/util/signal.ts +41 -0
  218. package/src/tui/util/sound.ts +156 -0
  219. package/src/tui/util/transcript.ts +112 -0
  220. package/src/tui/validate-session.ts +29 -0
  221. package/src/tui/win32.ts +130 -0
  222. package/src/tui/worker.ts +104 -0
@@ -0,0 +1,371 @@
1
+ // APX Overlay renderer — chat UI logic.
2
+ // Runs in the Electron BrowserWindow (renderer process).
3
+ // Communicates with main process via window.apx (contextBridge).
4
+
5
+ (() => {
6
+ "use strict";
7
+
8
+ // ── DOM refs ──────────────────────────────────────────────────────────
9
+ const $messages = document.getElementById("messages");
10
+ const $emptyState = document.getElementById("empty-state");
11
+ const $liveBar = document.getElementById("live-bar");
12
+ const $liveText = document.getElementById("live-text");
13
+ const $statusText = document.getElementById("status-text");
14
+ const $connBadge = document.getElementById("conn-badge");
15
+ const $btnClose = document.getElementById("btn-close");
16
+ const $hintBar = document.getElementById("hint-bar");
17
+
18
+ // Show the actual configured shortcut in the hint bar and empty state
19
+ window.apx?.getShortcut?.().then(shortcut => {
20
+ if (!shortcut) return;
21
+ const label = shortcut
22
+ .replace("CommandOrControl", window.apx.platform === "darwin" ? "⌘" : "Ctrl")
23
+ .replace("Command", "⌘")
24
+ .replace("Control", "Ctrl")
25
+ .replace("Shift", "⇧")
26
+ .replace("Option", "⌥")
27
+ .replace("Alt", "Alt")
28
+ .replace(/\+/g, "");
29
+ const hint = document.getElementById("shortcut-hint");
30
+ if (hint) hint.textContent = label;
31
+ const emptyHint = document.getElementById("empty-shortcut-hint");
32
+ if (emptyHint) emptyHint.textContent = label;
33
+ }).catch(() => {});
34
+
35
+ // ── State ─────────────────────────────────────────────────────────────
36
+ let isRecording = false;
37
+ let isStreaming = false;
38
+ let isCancelled = false;
39
+ let liveAccum = ""; // accumulated live transcription
40
+ let mediaRecorder = null;
41
+ let audioStream = null;
42
+ let streamingBubble = null; // current streaming agent bubble element
43
+ let currentExchangeStart = 0; // index in allMessages for current exchange
44
+ let allMessages = []; // { role, content, el }
45
+ let whisperPollTimer = null; // timer for model-loading poll
46
+
47
+ // ── Recording ─────────────────────────────────────────────────────────
48
+
49
+ window.apx?.onRecordingStart(async () => {
50
+ isRecording = true;
51
+ isCancelled = false;
52
+ liveAccum = "";
53
+ setStatus("Recording…");
54
+ showLiveBar(true);
55
+
56
+ // Check if whisper model is loaded; if not, show loading state while mic starts
57
+ let whisperReady = false;
58
+ try {
59
+ const status = await window.apx.checkWhisperReady();
60
+ whisperReady = status?.ready === true;
61
+ } catch {}
62
+
63
+ if (!whisperReady) {
64
+ setLiveText("Cargando modelo…");
65
+ // Poll until model is loaded, then switch to Listening...
66
+ whisperPollTimer = setInterval(async () => {
67
+ if (!isRecording) { clearInterval(whisperPollTimer); whisperPollTimer = null; return; }
68
+ try {
69
+ const s = await window.apx.checkWhisperReady();
70
+ if (s?.ready) {
71
+ clearInterval(whisperPollTimer);
72
+ whisperPollTimer = null;
73
+ if (isRecording) setLiveText("Listening…");
74
+ }
75
+ } catch {}
76
+ }, 1500);
77
+ }
78
+
79
+ startMic();
80
+ });
81
+
82
+ window.apx?.onRecordingStop(() => {
83
+ isRecording = false;
84
+ if (whisperPollTimer) { clearInterval(whisperPollTimer); whisperPollTimer = null; }
85
+ stopMic();
86
+ // liveAccum already contains the transcription — send it
87
+ const text = liveAccum.trim();
88
+ if (text && !isCancelled) {
89
+ commitUserMessage(text);
90
+ } else {
91
+ showLiveBar(false);
92
+ setStatus("Ready");
93
+ }
94
+ });
95
+
96
+ // ── Mic capture ───────────────────────────────────────────────────────
97
+
98
+ async function startMic() {
99
+ try {
100
+ audioStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
101
+ const mimeType = MediaRecorder.isTypeSupported("audio/webm;codecs=opus")
102
+ ? "audio/webm;codecs=opus"
103
+ : "audio/ogg;codecs=opus";
104
+ const format = mimeType.includes("webm") ? "webm" : "ogg";
105
+
106
+ mediaRecorder = new MediaRecorder(audioStream, { mimeType, audioBitsPerSecond: 32000 });
107
+
108
+ mediaRecorder.ondataavailable = async (e) => {
109
+ if (!e.data || e.data.size < 200) return; // skip nearly-empty chunks
110
+ if (!isRecording && !liveAccum) return;
111
+
112
+ const buf = await e.data.arrayBuffer();
113
+ const result = await window.apx.transcribeChunk(buf, format, "auto");
114
+ if (result?.ok && result.text?.trim()) {
115
+ liveAccum = (liveAccum + " " + result.text).trim();
116
+ $liveText.textContent = liveAccum || "Listening…";
117
+ }
118
+ };
119
+
120
+ // Send a chunk every 2.5 seconds while recording
121
+ mediaRecorder.start(2500);
122
+ } catch (e) {
123
+ setStatus("Mic error: " + e.message);
124
+ showLiveBar(false);
125
+ isRecording = false;
126
+ }
127
+ }
128
+
129
+ function stopMic() {
130
+ try { mediaRecorder?.stop(); } catch {}
131
+ try { audioStream?.getTracks().forEach(t => t.stop()); } catch {}
132
+ mediaRecorder = null;
133
+ audioStream = null;
134
+ }
135
+
136
+ // ── Send message flow ─────────────────────────────────────────────────
137
+
138
+ async function commitUserMessage(text) {
139
+ showLiveBar(false);
140
+ hideEmptyState();
141
+
142
+ // Record where this exchange begins
143
+ currentExchangeStart = allMessages.length;
144
+
145
+ // Render user bubble
146
+ const userBubble = addBubble("user", text);
147
+ allMessages.push({ role: "user", content: text, el: userBubble });
148
+
149
+ // Start streaming agent bubble
150
+ streamingBubble = addBubble("agent", "");
151
+ streamingBubble.classList.add("streaming");
152
+ const agentEntry = { role: "agent", content: "", el: streamingBubble };
153
+ allMessages.push(agentEntry);
154
+
155
+ isStreaming = true;
156
+ setStatus("Thinking…");
157
+ scrollToBottom();
158
+
159
+ // Send to daemon via IPC
160
+ try {
161
+ const history = buildHistory();
162
+ await window.apx.sendMessage(text, history);
163
+ } catch (e) {
164
+ finalizeAgentBubble("Error: " + e.message, true);
165
+ }
166
+ }
167
+
168
+ function buildHistory() {
169
+ // Last N turns excluding the current in-progress exchange
170
+ return allMessages
171
+ .slice(0, currentExchangeStart)
172
+ .slice(-20)
173
+ .map(m => ({ role: m.role === "agent" ? "assistant" : m.role, content: m.content }));
174
+ }
175
+
176
+ // ── Daemon events ─────────────────────────────────────────────────────
177
+
178
+ window.apx?.onDaemonEvent((msg) => {
179
+ switch (msg.type) {
180
+ case "thinking":
181
+ setStatus("Thinking…");
182
+ break;
183
+
184
+ case "token":
185
+ if (streamingBubble && !isCancelled) {
186
+ const entry = allMessages[allMessages.length - 1];
187
+ if (entry) entry.content += msg.text;
188
+ appendToken(streamingBubble, msg.text);
189
+ scrollToBottom();
190
+ }
191
+ break;
192
+
193
+ case "tool_start":
194
+ addToolPill(msg.name, false);
195
+ setStatus(`Running ${msg.name}…`);
196
+ break;
197
+
198
+ case "tool_done":
199
+ updateToolPill(msg.name, true);
200
+ setStatus("Thinking…");
201
+ break;
202
+
203
+ case "done": {
204
+ const finalText = msg.text || streamingBubble?.dataset.content || "";
205
+ finalizeAgentBubble(finalText);
206
+ setStatus("Ready");
207
+ break;
208
+ }
209
+
210
+ case "error":
211
+ finalizeAgentBubble(msg.message || "Unknown error", true);
212
+ setStatus("Error");
213
+ break;
214
+
215
+ case "cancelled":
216
+ if (streamingBubble) {
217
+ streamingBubble.classList.remove("streaming");
218
+ if (!streamingBubble.dataset.content) {
219
+ streamingBubble.closest(".bubble-row")?.remove();
220
+ allMessages.pop();
221
+ }
222
+ }
223
+ streamingBubble = null;
224
+ isStreaming = false;
225
+ setStatus("Cancelled");
226
+ setTimeout(() => setStatus("Ready"), 2000);
227
+ break;
228
+ }
229
+ });
230
+
231
+ window.apx?.onDaemonConnected(() => {
232
+ $connBadge.classList.remove("show");
233
+ });
234
+ window.apx?.onDaemonDisconnected(() => {
235
+ $connBadge.classList.add("show");
236
+ });
237
+
238
+ // ── Bubble helpers ────────────────────────────────────────────────────
239
+
240
+ function addBubble(role, text) {
241
+ const row = document.createElement("div");
242
+ row.className = `bubble-row ${role}`;
243
+
244
+ const bub = document.createElement("div");
245
+ bub.className = "bubble";
246
+ bub.dataset.content = text;
247
+ if (text) bub.textContent = text;
248
+
249
+ row.appendChild(bub);
250
+ $messages.appendChild(row);
251
+ return bub;
252
+ }
253
+
254
+ function appendToken(bubbleEl, token) {
255
+ bubbleEl.dataset.content = (bubbleEl.dataset.content || "") + token;
256
+ // Re-render full text (handles whitespace/newlines correctly)
257
+ bubbleEl.textContent = bubbleEl.dataset.content;
258
+ }
259
+
260
+ function finalizeAgentBubble(text, isError = false) {
261
+ if (!streamingBubble) return;
262
+ streamingBubble.classList.remove("streaming");
263
+ if (isError) streamingBubble.classList.add("error");
264
+ const finalContent = text || streamingBubble.dataset.content || "";
265
+ streamingBubble.textContent = finalContent;
266
+ streamingBubble.dataset.content = finalContent;
267
+
268
+ // Update allMessages entry
269
+ const entry = allMessages[allMessages.length - 1];
270
+ if (entry && entry.role === "agent") entry.content = finalContent;
271
+
272
+ streamingBubble = null;
273
+ isStreaming = false;
274
+ scrollToBottom();
275
+ }
276
+
277
+ // Tool pills appear between bubbles
278
+ let activePills = {}; // name → pill element
279
+
280
+ function addToolPill(name, done) {
281
+ const pill = document.createElement("div");
282
+ pill.className = "tool-pill";
283
+ pill.dataset.tool = name;
284
+ pill.innerHTML = done
285
+ ? `<span class="check">✓</span><span>${name}</span>`
286
+ : `<div class="spinner"></div><span>${name}</span>`;
287
+ // Insert before the streaming agent bubble row
288
+ if (streamingBubble) {
289
+ const row = streamingBubble.closest(".bubble-row");
290
+ $messages.insertBefore(pill, row);
291
+ } else {
292
+ $messages.appendChild(pill);
293
+ }
294
+ activePills[name] = pill;
295
+ scrollToBottom();
296
+ }
297
+
298
+ function updateToolPill(name, done) {
299
+ const pill = activePills[name];
300
+ if (!pill) return;
301
+ if (done) {
302
+ pill.innerHTML = `<span class="check">✓</span><span>${name}</span>`;
303
+ }
304
+ }
305
+
306
+ // ── Live bar ──────────────────────────────────────────────────────────
307
+
308
+ function showLiveBar(show) {
309
+ if (show) {
310
+ $liveBar.classList.add("active");
311
+ $liveText.textContent = "Listening…";
312
+ } else {
313
+ $liveBar.classList.remove("active");
314
+ $liveText.textContent = "Listening…";
315
+ }
316
+ }
317
+
318
+ function setLiveText(text) {
319
+ $liveText.textContent = text;
320
+ }
321
+
322
+ // ── Misc UI ───────────────────────────────────────────────────────────
323
+
324
+ function hideEmptyState() {
325
+ $emptyState?.remove();
326
+ }
327
+
328
+ function setStatus(text) {
329
+ if ($statusText) $statusText.textContent = text;
330
+ }
331
+
332
+ function scrollToBottom() {
333
+ $messages.scrollTop = $messages.scrollHeight;
334
+ }
335
+
336
+ // ── Keyboard handling ─────────────────────────────────────────────────
337
+
338
+ document.addEventListener("keydown", (e) => {
339
+ if (e.key === "Escape") {
340
+ e.preventDefault();
341
+ if (isRecording) {
342
+ // Cancel recording without sending
343
+ isCancelled = true;
344
+ isRecording = false;
345
+ stopMic();
346
+ showLiveBar(false);
347
+ setStatus("Cancelled");
348
+ setTimeout(() => setStatus("Ready"), 1500);
349
+ window.apx?.cancel();
350
+ } else if (isStreaming) {
351
+ // Interrupt agent response (keeps user bubble, clears streaming)
352
+ isCancelled = true;
353
+ window.apx?.cancel();
354
+ // finalizeAgentBubble called on "cancelled" event from daemon
355
+ } else {
356
+ // Close overlay
357
+ window.apx?.close();
358
+ }
359
+ }
360
+ });
361
+
362
+ // Close button
363
+ $btnClose?.addEventListener("click", () => {
364
+ if (isStreaming) {
365
+ isCancelled = true;
366
+ window.apx?.cancel();
367
+ }
368
+ window.apx?.close();
369
+ });
370
+
371
+ })();
@@ -0,0 +1,250 @@
1
+ /* APX Overlay — transparent floating chat */
2
+
3
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
4
+
5
+ :root {
6
+ --bg-window: rgba(18, 18, 22, 0.72);
7
+ --bg-user: rgba(99, 102, 241, 0.85);
8
+ --bg-agent: rgba(30, 32, 40, 0.88);
9
+ --bg-tool: rgba(50, 55, 70, 0.75);
10
+ --text-main: #f0f0f5;
11
+ --text-dim: rgba(200, 200, 220, 0.6);
12
+ --text-user: #ffffff;
13
+ --accent: #818cf8;
14
+ --red: #f87171;
15
+ --green: #4ade80;
16
+ --radius: 16px;
17
+ --radius-sm: 8px;
18
+ --font: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
19
+ }
20
+
21
+ html, body {
22
+ width: 100%;
23
+ height: 100%;
24
+ background: transparent;
25
+ color: var(--text-main);
26
+ font-family: var(--font);
27
+ font-size: 14px;
28
+ line-height: 1.5;
29
+ overflow: hidden;
30
+ user-select: none;
31
+ -webkit-app-region: no-drag;
32
+ }
33
+
34
+ /* ─── Root layout ──────────────────────────────────────────────────── */
35
+
36
+ #app {
37
+ display: flex;
38
+ flex-direction: column;
39
+ height: 100vh;
40
+ background: var(--bg-window);
41
+ border-radius: 18px;
42
+ border: 1px solid rgba(255,255,255,0.08);
43
+ box-shadow: 0 24px 64px rgba(0,0,0,0.6);
44
+ overflow: hidden;
45
+ backdrop-filter: blur(20px);
46
+ -webkit-backdrop-filter: blur(20px);
47
+ }
48
+
49
+ /* ─── Header bar ───────────────────────────────────────────────────── */
50
+
51
+ #header {
52
+ display: flex;
53
+ align-items: center;
54
+ padding: 10px 14px;
55
+ gap: 8px;
56
+ -webkit-app-region: drag;
57
+ flex-shrink: 0;
58
+ border-bottom: 1px solid rgba(255,255,255,0.06);
59
+ }
60
+ #header .logo {
61
+ font-size: 11px;
62
+ font-weight: 700;
63
+ letter-spacing: 0.12em;
64
+ color: var(--accent);
65
+ text-transform: uppercase;
66
+ -webkit-app-region: no-drag;
67
+ }
68
+ #header .status {
69
+ flex: 1;
70
+ font-size: 11px;
71
+ color: var(--text-dim);
72
+ -webkit-app-region: no-drag;
73
+ }
74
+ #header .btn-close {
75
+ background: none;
76
+ border: none;
77
+ cursor: pointer;
78
+ color: var(--text-dim);
79
+ padding: 2px 6px;
80
+ border-radius: 6px;
81
+ font-size: 15px;
82
+ line-height: 1;
83
+ -webkit-app-region: no-drag;
84
+ transition: color 0.15s, background 0.15s;
85
+ }
86
+ #header .btn-close:hover { color: var(--red); background: rgba(248,113,113,0.12); }
87
+
88
+ /* ─── Messages area ────────────────────────────────────────────────── */
89
+
90
+ #messages {
91
+ flex: 1;
92
+ overflow-y: auto;
93
+ padding: 12px 12px 6px;
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 8px;
97
+ scroll-behavior: smooth;
98
+ }
99
+ #messages::-webkit-scrollbar { width: 4px; }
100
+ #messages::-webkit-scrollbar-track { background: transparent; }
101
+ #messages::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 4px; }
102
+
103
+ /* ─── Bubble base ──────────────────────────────────────────────────── */
104
+
105
+ .bubble-row {
106
+ display: flex;
107
+ flex-direction: column;
108
+ max-width: 84%;
109
+ }
110
+ .bubble-row.user { align-self: flex-end; align-items: flex-end; }
111
+ .bubble-row.agent { align-self: flex-start; align-items: flex-start; }
112
+
113
+ .bubble {
114
+ padding: 9px 13px;
115
+ border-radius: var(--radius);
116
+ font-size: 13.5px;
117
+ line-height: 1.55;
118
+ word-break: break-word;
119
+ white-space: pre-wrap;
120
+ position: relative;
121
+ }
122
+ .bubble-row.user .bubble {
123
+ background: var(--bg-user);
124
+ color: var(--text-user);
125
+ border-bottom-right-radius: 5px;
126
+ }
127
+ .bubble-row.agent .bubble {
128
+ background: var(--bg-agent);
129
+ color: var(--text-main);
130
+ border-bottom-left-radius: 5px;
131
+ min-width: 60px;
132
+ }
133
+
134
+ /* Streaming cursor */
135
+ .bubble.streaming::after {
136
+ content: "▋";
137
+ display: inline-block;
138
+ animation: blink 0.8s step-end infinite;
139
+ color: var(--accent);
140
+ margin-left: 2px;
141
+ }
142
+ @keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
143
+
144
+ /* ─── Tool pill ────────────────────────────────────────────────────── */
145
+
146
+ .tool-pill {
147
+ align-self: center;
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 6px;
151
+ background: var(--bg-tool);
152
+ border: 1px solid rgba(255,255,255,0.08);
153
+ border-radius: 20px;
154
+ padding: 4px 12px;
155
+ font-size: 11.5px;
156
+ color: var(--text-dim);
157
+ }
158
+ .tool-pill .spinner {
159
+ width: 10px; height: 10px;
160
+ border: 1.5px solid rgba(255,255,255,0.2);
161
+ border-top-color: var(--accent);
162
+ border-radius: 50%;
163
+ animation: spin 0.7s linear infinite;
164
+ flex-shrink: 0;
165
+ }
166
+ .tool-pill .check { color: var(--green); font-size: 11px; }
167
+ @keyframes spin { to { transform: rotate(360deg); } }
168
+
169
+ /* ─── Live transcription bar ───────────────────────────────────────── */
170
+
171
+ #live-bar {
172
+ display: none;
173
+ padding: 10px 14px;
174
+ border-top: 1px solid rgba(255,255,255,0.06);
175
+ flex-shrink: 0;
176
+ }
177
+ #live-bar.active { display: flex; align-items: center; gap: 10px; }
178
+
179
+ .rec-dot {
180
+ width: 8px; height: 8px;
181
+ border-radius: 50%;
182
+ background: var(--red);
183
+ flex-shrink: 0;
184
+ animation: pulse 1.2s ease-in-out infinite;
185
+ }
186
+ @keyframes pulse {
187
+ 0%,100% { transform: scale(1); opacity: 1; }
188
+ 50% { transform: scale(1.4); opacity: 0.6; }
189
+ }
190
+
191
+ #live-text {
192
+ flex: 1;
193
+ font-size: 13px;
194
+ color: var(--text-main);
195
+ min-height: 18px;
196
+ font-style: italic;
197
+ opacity: 0.85;
198
+ }
199
+
200
+ /* ─── Bottom hint bar ──────────────────────────────────────────────── */
201
+
202
+ #hint-bar {
203
+ padding: 6px 14px;
204
+ font-size: 10.5px;
205
+ color: var(--text-dim);
206
+ display: flex;
207
+ gap: 14px;
208
+ border-top: 1px solid rgba(255,255,255,0.04);
209
+ flex-shrink: 0;
210
+ }
211
+ .hint kbd {
212
+ background: rgba(255,255,255,0.08);
213
+ border-radius: 3px;
214
+ padding: 0 4px;
215
+ font-family: inherit;
216
+ font-size: 10px;
217
+ }
218
+
219
+ /* ─── States ───────────────────────────────────────────────────────── */
220
+
221
+ #empty-state {
222
+ flex: 1;
223
+ display: flex;
224
+ flex-direction: column;
225
+ align-items: center;
226
+ justify-content: center;
227
+ gap: 8px;
228
+ color: var(--text-dim);
229
+ font-size: 13px;
230
+ }
231
+ #empty-state .icon { font-size: 28px; opacity: 0.5; }
232
+
233
+ /* Disconnected indicator */
234
+ #conn-badge {
235
+ display: none;
236
+ position: absolute;
237
+ top: 10px; right: 40px;
238
+ width: 7px; height: 7px;
239
+ border-radius: 50%;
240
+ background: var(--red);
241
+ }
242
+ #conn-badge.show { display: block; }
243
+
244
+ /* Error bubble */
245
+ .bubble.error {
246
+ background: rgba(248,113,113,0.18);
247
+ border: 1px solid rgba(248,113,113,0.35);
248
+ color: var(--red);
249
+ font-size: 12px;
250
+ }
@@ -0,0 +1,6 @@
1
+ export function FormatError(_error: unknown): string | undefined {
2
+ return undefined
3
+ }
4
+ export function FormatUnknownError(error: unknown): string {
5
+ return error instanceof Error ? error.message : String(error)
6
+ }
@@ -0,0 +1,18 @@
1
+ export type LogoShape = { left: string[]; right: string[] }
2
+
3
+ export const logo: LogoShape = {
4
+ left: [
5
+ " __ ____ _ _ ",
6
+ " /__\\ ( _ )( \\/ ) ",
7
+ "/ _ \\ ) __/ ) ( ",
8
+ "\\___/ (__) (__/\\_)",
9
+ ],
10
+ right: [
11
+ " __ ____ _ _ ",
12
+ " / _\\ ( _ \\( \\/ ) ",
13
+ "/ \\ )___/ ) / ",
14
+ "\\_/\\_/(__) (__/ ",
15
+ ],
16
+ }
17
+
18
+ export const go = logo
@@ -0,0 +1 @@
1
+ export const UI: Record<string, unknown> = {}
@@ -0,0 +1,7 @@
1
+ export type ConsoleState = {
2
+ switchableOrgCount: number
3
+ activeOrgName?: string
4
+ }
5
+ export const emptyConsoleState: ConsoleState = {
6
+ switchableOrgCount: 0,
7
+ }
@@ -0,0 +1,30 @@
1
+ // Catch-all shim for any unmapped @opencode-ai/core/* imports
2
+ import os from "os"
3
+ import path from "path"
4
+ export default {}
5
+ const _apxStateDir = path.join(os.homedir(), ".apx", "tui-state")
6
+ export const Global: any = {
7
+ Path: {
8
+ state: _apxStateDir,
9
+ config: path.join(os.homedir(), ".apx"),
10
+ data: _apxStateDir,
11
+ cache: path.join(os.homedir(), ".apx", "cache"),
12
+ },
13
+ }
14
+ export const AppFileSystem: any = {}
15
+ export const Flock: any = {}
16
+ export const Glob: any = {}
17
+ export const Npm: any = {}
18
+ export const Observability: any = {}
19
+ export const PositiveInt: any = {}
20
+ export const InstallationVersion: any = {}
21
+ export const InstallationChannel: any = {}
22
+ export const InstallationLocal: any = {}
23
+ export const makeRuntime: any = () => ({})
24
+ export const ensureProcessMetadata: any = () => {}
25
+ export type DeepMutable<T> = T
26
+ export const Schema: any = {}
27
+ export const Context: any = {}
28
+ export const Effect: any = {}
29
+ export const Fiber: any = {}
30
+ export const Layer: any = {}
@@ -0,0 +1,13 @@
1
+ export const Binary = {
2
+ search<T>(arr: T[], id: string, fn: (item: T) => string): { found: boolean; index: number } {
3
+ let lo = 0, hi = arr.length
4
+ while (lo < hi) {
5
+ const mid = (lo + hi) >> 1
6
+ const v = fn(arr[mid])
7
+ if (v === id) return { found: true, index: mid }
8
+ if (v < id) lo = mid + 1
9
+ else hi = mid
10
+ }
11
+ return { found: false, index: lo }
12
+ },
13
+ }