agentix-cli 0.27.0 → 0.28.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 (119) hide show
  1. package/README.md +67 -118
  2. package/apps/mac-helper/Sources/AgentXHelper/AXTree.swift +159 -0
  3. package/apps/mac-helper/Sources/AgentXHelper/Brand.swift +63 -0
  4. package/apps/mac-helper/Sources/AgentXHelper/Focus.swift +97 -0
  5. package/apps/mac-helper/Sources/AgentXHelper/HUD.swift +179 -0
  6. package/apps/mac-helper/Sources/AgentXHelper/OCR.swift +117 -0
  7. package/apps/mac-helper/Sources/AgentXHelper/Pointer.swift +309 -0
  8. package/apps/mac-helper/Sources/AgentXHelper/Screens.swift +67 -0
  9. package/apps/mac-helper/Sources/AgentXHelper/Typer.swift +185 -0
  10. package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +201 -0
  11. package/apps/mac-helper/Sources/AgentXHelper/main.swift +265 -0
  12. package/apps/mac-helper/build.sh +39 -0
  13. package/apps/mac-voice/Resources/Info.plist +18 -0
  14. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +108 -0
  15. package/apps/mac-voice/Sources/AgentXVoice/Brand.swift +123 -0
  16. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +68 -0
  17. package/apps/mac-voice/Sources/AgentXVoice/Hold.swift +74 -0
  18. package/apps/mac-voice/Sources/AgentXVoice/Hotkey.swift +76 -0
  19. package/apps/mac-voice/Sources/AgentXVoice/Markdown.swift +140 -0
  20. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +275 -0
  21. package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +147 -0
  22. package/apps/mac-voice/Sources/AgentXVoice/Recorder.swift +139 -0
  23. package/apps/mac-voice/Sources/AgentXVoice/ResultCard.swift +162 -0
  24. package/apps/mac-voice/Sources/AgentXVoice/SmartPaste.swift +72 -0
  25. package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +192 -0
  26. package/apps/mac-voice/Sources/AgentXVoice/StepLabel.swift +159 -0
  27. package/apps/mac-voice/Sources/AgentXVoice/main.swift +380 -0
  28. package/apps/mac-voice/build.sh +32 -0
  29. package/dist/{App-U52IPUK2.js → App-CKPH3HHF.js} +2 -2
  30. package/dist/{ChatApp-XP4OMHPL.js → ChatApp-MX7IG4K2.js} +2 -2
  31. package/dist/{agent-V2CA4TXD.js → agent-2U574KBZ.js} +2 -2
  32. package/dist/{board-dashboard-TTMAYRLG.js → board-dashboard-QR7CVTKA.js} +8 -10
  33. package/dist/board-dashboard-QR7CVTKA.js.map +1 -0
  34. package/dist/{chunk-UFAGYX73.js → chunk-3LYM22IV.js} +8 -8
  35. package/dist/chunk-3LYM22IV.js.map +1 -0
  36. package/dist/{chunk-VTE7L6LO.js → chunk-5IA2CYHN.js} +2 -2
  37. package/dist/{chunk-GDWJUGB5.js → chunk-64CAIB3C.js} +2 -2
  38. package/dist/{chunk-WZ72HX5C.js → chunk-E3UJHQGN.js} +145 -138
  39. package/dist/chunk-E3UJHQGN.js.map +1 -0
  40. package/dist/{chunk-IB3F7VBE.js → chunk-G2HMRDUQ.js} +3 -3
  41. package/dist/chunk-G2HMRDUQ.js.map +1 -0
  42. package/dist/{chunk-IBUSGCLQ.js → chunk-H2QQU2XV.js} +2 -2
  43. package/dist/{chunk-BPIRO4YD.js → chunk-MHIMGM4G.js} +2 -2
  44. package/dist/{chunk-CJQQIRPD.js → chunk-P5ZBBEPL.js} +7 -7
  45. package/dist/{chunk-CJQQIRPD.js.map → chunk-P5ZBBEPL.js.map} +1 -1
  46. package/dist/{chunk-L2IZPR6P.js → chunk-QHTQPBHU.js} +2 -2
  47. package/dist/chunk-S2MNL5V6.js +2 -0
  48. package/dist/chunk-S2MNL5V6.js.map +1 -0
  49. package/dist/{chunk-2T5W3ETS.js → chunk-SCTIUFL4.js} +2 -2
  50. package/dist/{chunk-OPHIXXGI.js → chunk-ULLAE6LE.js} +52 -52
  51. package/dist/chunk-ULLAE6LE.js.map +1 -0
  52. package/dist/{chunk-LNRB5MCU.js → chunk-VIH7ARPF.js} +151 -134
  53. package/dist/chunk-VIH7ARPF.js.map +1 -0
  54. package/dist/{chunk-DCDQZR7I.js → chunk-YLBERT7N.js} +6 -6
  55. package/dist/chunk-YLBERT7N.js.map +1 -0
  56. package/dist/{chunk-AX2PWSRT.js → chunk-YLGIZRHV.js} +2 -2
  57. package/dist/{chunk-FIEJDZVC.js → chunk-ZMYGXHPT.js} +2 -2
  58. package/dist/cli.js +1 -1
  59. package/dist/cli.js.map +1 -1
  60. package/dist/client-ZLSXGPTV.js +2 -0
  61. package/dist/config-HMW3EBZL.js +2 -0
  62. package/dist/config-mutator-4SZ5YEVD.js +2 -0
  63. package/dist/doctor-LRYV3QLK.js +2 -0
  64. package/dist/index.d.ts +37 -12
  65. package/dist/index.js +12 -19
  66. package/dist/index.js.map +1 -1
  67. package/dist/program-GSZ5AYAM.js +2 -0
  68. package/dist/query-SQDHE4HV.js +2 -0
  69. package/dist/registry-LE6FWYZC.js +2 -0
  70. package/dist/{review-candidates-AEUXRQQC.js → review-candidates-RQU5KNZM.js} +2 -2
  71. package/dist/routing-ILERJXB6.js +2 -0
  72. package/dist/scheduler-BFM3EINS.js +2 -0
  73. package/dist/{seat-VZ7AVO27.js → seat-7WM7II2B.js} +2 -2
  74. package/dist/{select-F5I5PO2O.js → select-X4KVMOP5.js} +2 -2
  75. package/dist/wiki-2AUFSIKO.js +2 -0
  76. package/dist/wiki-2AUFSIKO.js.map +1 -0
  77. package/package.json +14 -3
  78. package/scripts/postinstall.mjs +80 -0
  79. package/dist/board-dashboard-TTMAYRLG.js.map +0 -1
  80. package/dist/chunk-DCDQZR7I.js.map +0 -1
  81. package/dist/chunk-IB3F7VBE.js.map +0 -1
  82. package/dist/chunk-LNRB5MCU.js.map +0 -1
  83. package/dist/chunk-OPHIXXGI.js.map +0 -1
  84. package/dist/chunk-UFAGYX73.js.map +0 -1
  85. package/dist/chunk-WZ72HX5C.js.map +0 -1
  86. package/dist/client-PX25MD47.js +0 -2
  87. package/dist/config-HL5F36V4.js +0 -2
  88. package/dist/config-mutator-HBX53OMJ.js +0 -2
  89. package/dist/doctor-PBAH6XL3.js +0 -2
  90. package/dist/program-YGXVCV2Y.js +0 -2
  91. package/dist/query-ZKTFJMNE.js +0 -2
  92. package/dist/registry-PSZDPFZK.js +0 -2
  93. package/dist/routing-4PV66BK6.js +0 -2
  94. package/dist/routing-4PV66BK6.js.map +0 -1
  95. package/dist/scheduler-GZZAN6ZG.js +0 -2
  96. package/dist/wiki-N5SMP3DT.js +0 -2
  97. /package/dist/{App-U52IPUK2.js.map → App-CKPH3HHF.js.map} +0 -0
  98. /package/dist/{ChatApp-XP4OMHPL.js.map → ChatApp-MX7IG4K2.js.map} +0 -0
  99. /package/dist/{agent-V2CA4TXD.js.map → agent-2U574KBZ.js.map} +0 -0
  100. /package/dist/{chunk-VTE7L6LO.js.map → chunk-5IA2CYHN.js.map} +0 -0
  101. /package/dist/{chunk-GDWJUGB5.js.map → chunk-64CAIB3C.js.map} +0 -0
  102. /package/dist/{chunk-IBUSGCLQ.js.map → chunk-H2QQU2XV.js.map} +0 -0
  103. /package/dist/{chunk-BPIRO4YD.js.map → chunk-MHIMGM4G.js.map} +0 -0
  104. /package/dist/{chunk-L2IZPR6P.js.map → chunk-QHTQPBHU.js.map} +0 -0
  105. /package/dist/{chunk-2T5W3ETS.js.map → chunk-SCTIUFL4.js.map} +0 -0
  106. /package/dist/{chunk-AX2PWSRT.js.map → chunk-YLGIZRHV.js.map} +0 -0
  107. /package/dist/{chunk-FIEJDZVC.js.map → chunk-ZMYGXHPT.js.map} +0 -0
  108. /package/dist/{client-PX25MD47.js.map → client-ZLSXGPTV.js.map} +0 -0
  109. /package/dist/{config-HL5F36V4.js.map → config-HMW3EBZL.js.map} +0 -0
  110. /package/dist/{config-mutator-HBX53OMJ.js.map → config-mutator-4SZ5YEVD.js.map} +0 -0
  111. /package/dist/{doctor-PBAH6XL3.js.map → doctor-LRYV3QLK.js.map} +0 -0
  112. /package/dist/{program-YGXVCV2Y.js.map → program-GSZ5AYAM.js.map} +0 -0
  113. /package/dist/{query-ZKTFJMNE.js.map → query-SQDHE4HV.js.map} +0 -0
  114. /package/dist/{registry-PSZDPFZK.js.map → registry-LE6FWYZC.js.map} +0 -0
  115. /package/dist/{review-candidates-AEUXRQQC.js.map → review-candidates-RQU5KNZM.js.map} +0 -0
  116. /package/dist/{scheduler-GZZAN6ZG.js.map → routing-ILERJXB6.js.map} +0 -0
  117. /package/dist/{seat-VZ7AVO27.js.map → scheduler-BFM3EINS.js.map} +0 -0
  118. /package/dist/{wiki-N5SMP3DT.js.map → seat-7WM7II2B.js.map} +0 -0
  119. /package/dist/{select-F5I5PO2O.js.map → select-X4KVMOP5.js.map} +0 -0
@@ -0,0 +1,380 @@
1
+ import AppKit
2
+ import Carbon.HIToolbox
3
+
4
+ /// AgentX Voice — push-to-talk widget over the daemon's /ask endpoint.
5
+ ///
6
+ /// Hold ⌥Space, speak, release. Transcribe (ElevenLabs, falling back to
7
+ /// on-device mlx-whisper), POST to /ask, speak the answer (ElevenLabs,
8
+ /// falling back to `say`).
9
+ ///
10
+ /// Deliberately has NO computer-use capability in this slice. The point is
11
+ /// to find out whether talking to an agent this way is actually pleasant
12
+ /// before building the part that can click things.
13
+ /// Main-actor isolated in full. An NSApplicationDelegate touches AppKit in
14
+ /// every method, and the crash that prompted this was UI state reached from
15
+ /// a background queue. Isolating the whole class turns that from a runtime
16
+ /// heap corruption into a compile error.
17
+ @MainActor
18
+ final class App: NSObject, NSApplicationDelegate {
19
+ private let panel = Panel()
20
+ private let card = ResultCard()
21
+ private let recorder = Recorder()
22
+ private var hotkey: Hotkey?
23
+ private var pasteHotkey: Hotkey?
24
+ private var busy = false
25
+ private var progress: Progress?
26
+ private var ticker: Timer?
27
+ private var startedAt = Date()
28
+ private var lastStep = "Thinking…"
29
+ private var spokenSteps = Set<String>()
30
+ private var lastSpokeAt = Date.distantPast
31
+
32
+ /// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
33
+ private func smartPaste() {
34
+ guard !busy else { return }
35
+ busy = true
36
+ panel.render(.working("Pasting…", 0))
37
+ SmartPaste.run { [weak self] result in
38
+ guard let self else { return }
39
+ self.busy = false
40
+ guard let result else {
41
+ self.panel.render(.error("Smart paste unavailable"))
42
+ return
43
+ }
44
+ // Quiet when nothing changed: "pasted as copied" is the common
45
+ // case and does not deserve an announcement.
46
+ if let what = SmartPaste.summary(result) {
47
+ self.panel.render(.working(what, 0))
48
+ }
49
+ self.panel.render(.idle)
50
+ }
51
+ }
52
+
53
+ /// Only one widget, however it was started.
54
+ ///
55
+ /// Two pills were sitting on screen at once: the installed copy in
56
+ /// /Applications and a freshly built one from the source tree. Same
57
+ /// bundle identifier, different paths, so macOS is happy to run both —
58
+ /// and the older one keeps rendering an older UI, which looks exactly
59
+ /// like a bug in the new build.
60
+ ///
61
+ /// The NEWEST wins. After an upgrade or a rebuild the thing you just
62
+ /// made is the one you want; a stale copy showing yesterday's
63
+ /// behaviour has no claim to the screen.
64
+ private func retireOlderInstances() {
65
+ let me = ProcessInfo.processInfo.processIdentifier
66
+ let mine = Bundle.main.bundleIdentifier
67
+ for app in NSWorkspace.shared.runningApplications {
68
+ guard app.bundleIdentifier == mine,
69
+ app.processIdentifier != me else { continue }
70
+ Log.info("retiring an older instance (pid \(app.processIdentifier))")
71
+ if !app.terminate() { app.forceTerminate() }
72
+ }
73
+ }
74
+
75
+ func applicationDidFinishLaunching(_ note: Notification) {
76
+ NSApp.setActivationPolicy(.accessory)
77
+ retireOlderInstances()
78
+ panel.orderFrontRegardless()
79
+
80
+ hotkey = Hotkey(
81
+ id: 1,
82
+ onPress: { [weak self] in self?.startListening() },
83
+ onRelease: { [weak self] in self?.stopAndSend() })
84
+ hotkey?.register()
85
+ panel.onClick = { [weak self] in self?.toggleListening() }
86
+
87
+ // Turning the hold OFF is the moment anything that piled up
88
+ // becomes welcome. Without this the queue is a hole rather than a
89
+ // delay — the daemon's watcher only notices SYSTEM Focus ending,
90
+ // and this switch is not that.
91
+ panel.onHoldChanged = { [weak self] nowOn in
92
+ guard let self else { return }
93
+ self.panel.render(.idle)
94
+ if !nowOn { Hold.flushHeld() }
95
+ Log.info(nowOn ? "notifications held" : "notifications delivering")
96
+ }
97
+
98
+ // ⌘⌥V: reshape the clipboard for wherever the caret is, then paste.
99
+ // Fires on RELEASE so the modifiers are up before cmd-V is sent —
100
+ // pressing it while ⌘⌥ are still held produces a different chord
101
+ // in the destination app.
102
+ pasteHotkey = Hotkey(
103
+ id: 2,
104
+ onPress: {},
105
+ onRelease: { [weak self] in self?.smartPaste() })
106
+ pasteHotkey?.register(keyCode: UInt32(kVK_ANSI_V),
107
+ modifiers: UInt32(cmdKey | optionKey))
108
+
109
+ recorder.requestPermission { [weak self] granted in
110
+ Task { @MainActor in
111
+ guard let self else { return }
112
+ if !granted { self.panel.render(.error("Microphone denied")) }
113
+ else { Log.info("ready — hold ⌥Space to talk (agent: \(Config.agentID))") }
114
+ }
115
+ }
116
+ }
117
+
118
+ // --- Hands-free listening ---
119
+ //
120
+ // Holding a chord is fine for one question and wrong for a
121
+ // conversation: the reply arrives, you want to say one more thing, and
122
+ // you have to find ⌥Space again while the assistant sits idle. So the
123
+ // microphone reopens by itself after every answer, and closes again on
124
+ // its own if you say nothing.
125
+ //
126
+ // Ending on SILENCE rather than a timer is the whole trick. A fixed
127
+ // window either cuts people off mid-sentence or leaves the mic open
128
+ // staring at them; the only honest signal that a sentence has finished
129
+ // is the room going quiet.
130
+
131
+ /// RMS above which the microphone is hearing a voice rather than a room.
132
+ private let speechLevel: Float = 0.02
133
+ /// Quiet this long after speech ends the turn.
134
+ private let endSilence: TimeInterval = 1.2
135
+ /// How long an unprompted follow-up window waits before giving up.
136
+ private let followUpPatience: TimeInterval = 4.0
137
+ /// How long a clicked session waits for you to start talking.
138
+ private let clickPatience: TimeInterval = 8.0
139
+
140
+ private var listenPoll: Timer?
141
+ private var heardSpeech = false
142
+ private var quietSince: Date?
143
+ private var openedAt = Date()
144
+ private var patience: TimeInterval = 8.0
145
+ /// True when nothing was said and the window should close in silence.
146
+ private var silentClose = false
147
+
148
+ /// Open the microphone with no key held.
149
+ private func listenHandsFree(followUp: Bool) {
150
+ guard !busy, !recorder.isRecording else { return }
151
+ do {
152
+ try recorder.start()
153
+ } catch {
154
+ panel.render(.error(error.localizedDescription))
155
+ return
156
+ }
157
+ heardSpeech = false
158
+ quietSince = nil
159
+ openedAt = Date()
160
+ patience = followUp ? followUpPatience : clickPatience
161
+ silentClose = followUp
162
+ panel.render(.listening)
163
+
164
+ listenPoll?.invalidate()
165
+ // Timer fires on the run loop but its closure is not main-actor
166
+ // isolated, and pollLevel touches UI. Hopping explicitly keeps it
167
+ // correct under Swift 6 rather than relying on the timer happening
168
+ // to run where we want it.
169
+ listenPoll = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
170
+ Task { @MainActor [weak self] in self?.pollLevel() }
171
+ }
172
+ }
173
+
174
+ private func pollLevel() {
175
+ guard recorder.isRecording else { stopPolling(); return }
176
+ let level = recorder.level
177
+
178
+ if level > speechLevel {
179
+ heardSpeech = true
180
+ quietSince = nil
181
+ return
182
+ }
183
+ if heardSpeech {
184
+ // Speech has stopped. Give it a beat before deciding the
185
+ // sentence is over — people pause inside sentences.
186
+ let since = quietSince ?? Date()
187
+ quietSince = since
188
+ if Date().timeIntervalSince(since) >= endSilence {
189
+ stopPolling()
190
+ stopAndSend()
191
+ }
192
+ return
193
+ }
194
+ // Nothing said yet. Close quietly rather than making the person
195
+ // dismiss a window they did not ask for.
196
+ if Date().timeIntervalSince(openedAt) >= patience {
197
+ stopPolling()
198
+ _ = recorder.stop()
199
+ panel.render(silentClose ? .idle : .error("Didn't catch that"))
200
+ if !silentClose { resetSoon() }
201
+ }
202
+ }
203
+
204
+ private func stopPolling() {
205
+ listenPoll?.invalidate()
206
+ listenPoll = nil
207
+ }
208
+
209
+ /// The pill was clicked: start if idle, finish early if already listening.
210
+ private func toggleListening() {
211
+ if recorder.isRecording {
212
+ stopPolling()
213
+ stopAndSend()
214
+ return
215
+ }
216
+ listenHandsFree(followUp: false)
217
+ }
218
+
219
+ private func startListening() {
220
+ // A held key overrides any hands-free window that is open, so the
221
+ // two ways of talking never fight over the microphone.
222
+ stopPolling()
223
+ // A turn already in flight must not be interrupted by a stray
224
+ // keypress; the answer is still coming and will be spoken.
225
+ guard !busy, !recorder.isRecording else { return }
226
+ do {
227
+ try recorder.start()
228
+ panel.render(.listening)
229
+ } catch {
230
+ panel.render(.error(error.localizedDescription))
231
+ }
232
+ }
233
+
234
+ private func stopAndSend() {
235
+ guard recorder.isRecording else { return }
236
+ guard let wav = recorder.stop() else {
237
+ panel.render(.error("Too short — hold while speaking"))
238
+ resetSoon()
239
+ return
240
+ }
241
+ busy = true
242
+ panel.render(.thinking)
243
+
244
+ Task { @MainActor in
245
+ do {
246
+ let heard = try await Speech.transcribe(wav: wav)
247
+ guard !heard.isEmpty else {
248
+ panel.render(.error("Didn't catch that"))
249
+ busy = false; resetSoon(); return
250
+ }
251
+ Log.info("heard: \(heard)")
252
+ beginNarration()
253
+
254
+ let answer = try await AgentClient.ask(heard)
255
+ endNarration()
256
+ Log.info("answer: \(answer.text)")
257
+ // Show BEFORE speaking, but only when there is something
258
+ // the speech cannot deliver — a link, an image, or more
259
+ // text than was read aloud. A card that opens on every
260
+ // "Ok." teaches you to ignore it.
261
+ if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
262
+ buttons: answer.buttons, imageURL: answer.imageURL) {
263
+ card.show(spoken: answer.text, written: answer.written,
264
+ buttons: answer.buttons, imageURL: answer.imageURL)
265
+ } else {
266
+ card.orderOut(nil)
267
+ }
268
+ // Scroll the sentence being spoken, so it can be read as
269
+ // well as heard — and re-read after, which speech cannot do.
270
+ panel.render(.saying(answer.text))
271
+ await Speech.speak(answer.text)
272
+ panel.render(.idle)
273
+ // Leave the microphone open for a moment. Say nothing and
274
+ // it closes itself; start talking and the conversation
275
+ // simply continues.
276
+ busy = false
277
+ listenHandsFree(followUp: true)
278
+ } catch {
279
+ endNarration()
280
+ Log.warn("turn failed: \(error.localizedDescription)")
281
+ panel.render(.error(short(error.localizedDescription)))
282
+ // Say it aloud too — a voice assistant that fails only in
283
+ // a 230px label has failed silently for anyone not looking.
284
+ await Speech.speak("Sorry, that didn't work.")
285
+ resetSoon()
286
+ }
287
+ busy = false
288
+ }
289
+ }
290
+
291
+ /// Narrate the wait using what the daemon says it is actually doing.
292
+ ///
293
+ /// Two sources, because neither alone is enough: `task:step` events
294
+ /// say what the agent is doing but arrive irregularly, and a 1-second
295
+ /// ticker proves the thing is still alive between them. Together they
296
+ /// answer both "what is it doing" and "is it stuck".
297
+ private func beginNarration() {
298
+ startedAt = Date()
299
+ lastStep = "Thinking…"
300
+ spokenSteps.removeAll()
301
+ lastSpokeAt = Date()
302
+ panel.render(.working(lastStep, 0))
303
+
304
+ // Progress delivers on its own serial queue; hop to main before
305
+ // touching any view.
306
+ progress = Progress(agentID: Config.agentID) { [weak self] step in
307
+ Task { @MainActor in
308
+ guard let self, self.busy else { return }
309
+ self.lastStep = step
310
+ Log.info("step: \(step)")
311
+ self.panel.render(.working(step, Int(Date().timeIntervalSince(self.startedAt))))
312
+ self.speakStepIfDue(step)
313
+ }
314
+ }
315
+ progress?.start()
316
+
317
+ ticker = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
318
+ Task { @MainActor in
319
+ guard let self, self.busy else { return }
320
+ self.panel.render(.working(self.lastStep, Int(Date().timeIntervalSince(self.startedAt))))
321
+ }
322
+ }
323
+ }
324
+
325
+ private func endNarration() {
326
+ ticker?.invalidate(); ticker = nil
327
+ progress?.stop(); progress = nil
328
+ }
329
+
330
+ /// Say what it is doing, sparingly.
331
+ ///
332
+ /// Silence during a long turn reads as a broken assistant, and a
333
+ /// visible label does not help someone who asked by voice precisely
334
+ /// because they were not looking at the screen. But narrating every
335
+ /// step would talk over itself and be worse than silence — so: nothing
336
+ /// for the first stretch, then at most one short line every 15
337
+ /// seconds, and never the same step twice.
338
+ private func speakStepIfDue(_ step: String) {
339
+ let elapsed = Date().timeIntervalSince(startedAt)
340
+ guard elapsed > 12 else { return }
341
+ guard Date().timeIntervalSince(lastSpokeAt) > 15 else { return }
342
+
343
+ // The step is "Tool: detail". Both halves go to the daemon, which
344
+ // decides whether it is worth saying and in what words. Reading the
345
+ // detail aloud directly is what produced "still working, osascript
346
+ // tell application Calendar".
347
+ let parts = step.split(separator: ":", maxSplits: 1).map(String.init)
348
+ let tool = parts.first?.trimmingCharacters(in: .whitespaces) ?? step
349
+ let detail = parts.count > 1 ? parts[1].trimmingCharacters(in: .whitespaces) : ""
350
+
351
+ // Reserve the slot before awaiting, or two steps arriving together
352
+ // both pass the interval check and talk over each other.
353
+ lastSpokeAt = Date()
354
+ Task { @MainActor in
355
+ guard let phrase = await AgentClient.phrase(
356
+ tool: tool, detail: detail, elapsed: Int(elapsed)) else { return }
357
+ guard !self.spokenSteps.contains(phrase) else { return }
358
+ self.spokenSteps.insert(phrase)
359
+ await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".")
360
+ }
361
+ }
362
+
363
+ private func short(_ s: String) -> String {
364
+ s.count > 40 ? String(s.prefix(38)) + "…" : s
365
+ }
366
+
367
+ private func resetSoon() {
368
+ DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
369
+ MainActor.assumeIsolated {
370
+ guard let self, !self.recorder.isRecording, !self.busy else { return }
371
+ self.panel.render(.idle)
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ let app = NSApplication.shared
378
+ let delegate = MainActor.assumeIsolated { App() }
379
+ app.delegate = delegate
380
+ app.run()
@@ -0,0 +1,32 @@
1
+ #!/bin/bash
2
+ # Build AgentX Voice into a .app bundle.
3
+ #
4
+ # swiftc directly, not SwiftPM: a CommandLineTools-only install ships a
5
+ # broken PackageDescription ManifestAPI, and this target has no
6
+ # dependencies, so the package manager buys nothing here.
7
+ #
8
+ # The .app wrapper is not cosmetic — macOS grants microphone access to
9
+ # bundles with an Info.plist, so a bare executable can never get a TCC
10
+ # prompt and will fail with an inscrutable silence instead.
11
+ set -euo pipefail
12
+ cd "$(dirname "$0")"
13
+
14
+ APP="build/AgentX Voice.app"
15
+ BIN="$APP/Contents/MacOS"
16
+ rm -rf build && mkdir -p "$BIN" "$APP/Contents/Resources"
17
+
18
+ swiftc -O \
19
+ -o "$BIN/AgentXVoice" \
20
+ Sources/AgentXVoice/*.swift \
21
+ -framework AppKit -framework AVFoundation -framework Carbon \
22
+ -target arm64-apple-macosx14.0
23
+
24
+ cp Resources/Info.plist "$APP/Contents/Info.plist"
25
+
26
+ # Ad-hoc signature. Unsigned bundles get a fresh TCC identity on every
27
+ # rebuild, so macOS re-asks for the microphone every single launch.
28
+ codesign --force --sign - --identifier tn.acme.agentx.voice "$APP" 2>/dev/null \
29
+ || echo "warning: codesign failed; expect repeated microphone prompts"
30
+
31
+ echo "built: $APP"
32
+ echo "run: open \"$APP\" (or ./build/AgentX\\ Voice.app/Contents/MacOS/AgentXVoice for logs)"
@@ -1,8 +1,8 @@
1
- import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-VTE7L6LO.js";import"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
1
+ import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-5IA2CYHN.js";import"./chunk-P5ZBBEPL.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
2
2
 
3
3
  `))>=0;){let h=y.slice(0,d);y=y.slice(d+2);let m=X(h);m&&(yield m)}}}finally{try{u.releaseLock()}catch{}}}function X(e){let r="message",t="";for(let s of e.split(`
4
4
  `))s.startsWith("event: ")?r=s.slice(7):s.startsWith("data: ")&&(t=s.slice(6));if(!t)return null;let n;try{n=JSON.parse(t)}catch{n=t}return{event:r,data:n}}import{jsx as a,jsxs as c}from"react/jsx-runtime";function $(e,r){return r<=0||e<0?0:e>=r?r-1:e}function E(){return`tui:${z().slice(0,12)}`}function k(e,r){let t=[...e,r];return t.length>50&&t.splice(0,t.length-50),t}function Y(e,r){switch(r.type){case"agents":return{...e,agents:r.data,cursor:{...e.cursor,agents:$(e.cursor.agents,r.data.length)}};case"processes":return{...e,processes:r.data,cursor:{...e.cursor,processes:$(e.cursor.processes,r.data.length)}};case"crons":return{...e,crons:r.data};case"event":{let t=[r.row,...e.events];t.length>200&&(t.length=200);let n=e.channels;return r.channelTally&&(n=new Map(e.channels),n.set(r.channelTally.channel,r.channelTally)),{...e,events:t,channels:n}}case"connError":return{...e,connError:r.error};case"tick":return{...e,lastTick:Date.now()};case"focus":return{...e,focus:r.pane};case"cursor":{let t=r.pane==="agents"?e.agents.length:r.pane==="processes"?e.processes.length:e.events.length;return{...e,cursor:{...e.cursor,[r.pane]:$(e.cursor[r.pane]+r.delta,t)}}}case"bottomRight":return{...e,bottomRight:r.pane};case"chatStart":return{...e,chat:{active:!0,agentId:r.agentId,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatExit":return{...e,chat:{...e.chat,active:!1}};case"chatClear":return{...e,chat:{...e.chat,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatText":return{...e,chat:{...e.chat,text:r.text}};case"chatSubmitStart":return{...e,chat:{...e.chat,history:k(e.chat.history,r.you),text:"",status:"sending",error:null}};case"chatSubmitDone":return{...e,chat:{...e.chat,history:k(e.chat.history,r.reply),status:"idle",error:null}};case"chatSubmitError":return{...e,chat:{...e.chat,history:k(e.chat.history,{role:"error",text:r.error,at:Date.now()}),status:"idle",error:r.error}};case"chatStreamStart":{let t=k(e.chat.history,r.you),n=k(t,{role:"agent",text:"",at:Date.now(),streaming:!0,tools:[]});return{...e,chat:{...e.chat,history:n,text:"",status:"sending",error:null}}}case"chatStreamDelta":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,text:n.text+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamThinking":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,thinking:(n.thinking??"")+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamTool":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,tools:[...n.tools??[],{name:r.name,arg:r.arg,error:r.error}]}),{...e,chat:{...e.chat,history:t}}}case"chatStreamEnd":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]=r.error?{...n,streaming:!1,role:"error",text:r.error}:{...n,streaming:!1,elapsedMs:r.elapsedMs,outTokens:r.outTokens}),{...e,chat:{...e.chat,history:t,status:"idle",error:r.error??null}}}case"toast":return{...e,toast:{text:r.text,color:r.color,at:Date.now()}};case"clearToast":return{...e,toast:null}}}var _={agents:[],processes:[],crons:[],events:[],channels:new Map,connError:null,lastTick:0,focus:"agents",cursor:{agents:0,processes:0,events:0},bottomRight:"crons",chat:{active:!1,agentId:null,chatId:E(),history:[],text:"",status:"idle",error:null},toast:null};function be({conn:e,pollMs:r=3e3}){let[t,n]=V(Y,_),{exit:s}=W(),u=G(null),p=G(null),y=t.agents.map(l=>l.id),f=t.chat.active?L(t.chat.text,y,process.cwd()):null;return R(()=>{if(!t.toast)return;let l=setTimeout(()=>n({type:"clearToast"}),3500);return()=>clearTimeout(l)},[t.toast]),J((l,d)=>{if(t.chat.active){if(d.escape){n({type:"chatExit"});return}if(d.ctrl&&l==="l"){n({type:"chatClear"});return}if(d.tab){let m=U(t.chat.text,p,y,process.cwd());m!==null&&n({type:"chatText",text:m});return}if(d.backspace||d.delete){p.current=null,n({type:"chatText",text:t.chat.text.slice(0,-1)});return}if(d.ctrl||d.meta)return;let h=N(l??"",d.return,t.chat.text);if(h.kind==="paste"||h.kind==="type"){p.current=null,n({type:"chatText",text:t.chat.text+h.text});return}if(h.kind==="continue"){n({type:"chatText",text:h.buffer});return}if(h.kind==="submit"){let m=h.text.trim(),g=t.chat.agentId;if(!m||!g||t.chat.status==="sending"){n({type:"chatText",text:""});return}p.current=null;let b=Date.now(),w=t.chat.chatId;n({type:"chatStreamStart",you:{role:"you",text:m,at:b}}),(async()=>{try{let C=await M(e,g,m,{channel:"tui",chatId:w,onText:x=>n({type:"chatStreamDelta",text:x}),onThinking:x=>n({type:"chatStreamThinking",text:x}),onTool:x=>{x.status==="start"&&x.name?n({type:"chatStreamTool",name:x.name,arg:x.arg}):x.status==="result"&&x.error&&n({type:"chatStreamTool",name:x.name??"tool",error:!0})}});n({type:"chatStreamEnd",elapsedMs:Date.now()-b,outTokens:C.usage?.outputTokens,error:C.error})}catch(C){n({type:"chatStreamEnd",elapsedMs:Date.now()-b,error:C?.message||String(C)})}})();return}return}if(l==="q"||d.ctrl&&l==="c"){u.current?.abort(),s();return}if(d.tab){let h=["agents","processes","events"],m=h.indexOf(t.focus);n({type:"focus",pane:h[(m+1)%h.length]});return}if(l==="a"){n({type:"focus",pane:"agents"});return}if(l==="p"){n({type:"focus",pane:"processes"});return}if(l==="e"){n({type:"focus",pane:"events"});return}if(l==="c"){n({type:"bottomRight",pane:t.bottomRight==="crons"?"channels":"crons"});return}if(d.upArrow){n({type:"cursor",pane:t.focus,delta:-1});return}if(d.downArrow){n({type:"cursor",pane:t.focus,delta:1});return}if(d.return&&t.focus==="agents"){let h=t.agents[t.cursor.agents];if(!h){n({type:"toast",text:"no agent selected",color:"yellow"});return}n({type:"chatStart",agentId:h.id});return}if(l==="k"&&t.focus==="processes"){let h=t.processes[t.cursor.processes];if(!h){n({type:"toast",text:"no process selected",color:"yellow"});return}(async()=>{try{await P(e,h.key,"tui"),n({type:"toast",text:`killed ${h.key.agentId}/${h.key.channel}/${h.key.chatId}`,color:"green"})}catch(m){n({type:"toast",text:`kill failed: ${m?.message||m}`,color:"red"})}})();return}}),R(()=>{let l=new AbortController;u.current=l;let d=!1,h=async()=>{try{let[g,b,w]=await Promise.all([B(e,l.signal).catch(()=>[]),D(e,l.signal).catch(()=>[]),A(e,l.signal).catch(()=>[])]);if(d)return;n({type:"agents",data:g}),n({type:"processes",data:b}),n({type:"crons",data:w}),n({type:"tick"}),n({type:"connError",error:null})}catch(g){d||n({type:"connError",error:g?.message||String(g)})}};h();let m=setInterval(h,r);return()=>{d=!0,clearInterval(m),l.abort()}},[e.baseUrl,e.token,r]),R(()=>{let l=!1,d=new AbortController,h=new Map;return(async()=>{let m=500;for(;!l;){try{for await(let g of q({baseUrl:e.baseUrl,token:e.token,signal:d.signal})){let b=ae(g),w;if(g.event==="channel"&&typeof g.data?.channel=="string"){let S=h.get(g.data.channel)??{channel:g.data.channel,inbound:0,outbound:0,lastAt:0,lastPreview:""},C=S.inbound+(g.data.direction==="in"?1:0),x=S.outbound+(g.data.direction==="out"?1:0);w={channel:g.data.channel,inbound:C,outbound:x,lastAt:b.at,lastPreview:(g.data.textPreview||"").slice(0,60)},h.set(g.data.channel,w)}n({type:"event",row:b,channelTally:w}),m=500}}catch(g){if(l)return;n({type:"connError",error:`events: ${g?.message||g}`})}await new Promise(g=>setTimeout(g,m)),m=Math.min(m*2,1e4)}})(),()=>{l=!0,d.abort()}},[e.baseUrl,e.token]),c(i,{flexDirection:"column",width:"100%",children:[a(K,{conn:e,lastTick:t.lastTick,connError:t.connError,chat:t.chat}),c(i,{flexDirection:"row",flexGrow:1,children:[c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"AGENTS",focused:!t.chat.active&&t.focus==="agents",children:a(Z,{rows:t.agents,cursor:t.cursor.agents,focused:!t.chat.active&&t.focus==="agents",talkingTo:t.chat.active?t.chat.agentId:null})}),a(v,{title:"PROCESSES",focused:!t.chat.active&&t.focus==="processes",children:a(ee,{rows:t.processes,cursor:t.cursor.processes,focused:!t.chat.active&&t.focus==="processes"})})]}),t.chat.active?a(oe,{chat:t.chat,suggest:f,agents:t.agents}):c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"LIVE EVENTS",focused:t.focus==="events",children:a(ne,{rows:t.events,cursor:t.cursor.events,focused:t.focus==="events"})}),a(v,{title:t.bottomRight==="crons"?"CRONS / SCHEDULES":"CHANNELS",focused:!1,children:t.bottomRight==="crons"?a(te,{rows:t.crons}):a(re,{rows:Array.from(t.channels.values())})})]})]}),a(Q,{focus:t.focus,bottomRight:t.bottomRight,chat:t.chat,toast:t.toast})]})}function K({conn:e,lastTick:r,connError:t,chat:n}){let s=t?c(o,{color:"red",children:["\u25CF ",t]}):r===0?a(o,{color:"yellow",children:"\u25CF connecting\u2026"}):a(o,{color:"green",children:"\u25CF live"}),u=n.active?c(o,{color:"green",bold:!0,children:["CHAT @",n.agentId]}):a(o,{bold:!0,children:"agentx tui"});return c(i,{paddingX:1,justifyContent:"space-between",children:[a(i,{children:u}),a(o,{dimColor:!0,children:e.baseUrl}),a(i,{children:s})]})}function Q({focus:e,bottomRight:r,chat:t,toast:n}){let s=t.active?["[Enter] send","[Esc] exit chat","[Ctrl-L] new session"]:[`[Tab/a/p/e] focus (now: ${e})`,e==="agents"?"[Enter] chat":null,e==="processes"?"[k] kill":null,`[c] ${r==="crons"?"\u2192channels":"\u2192crons"}`,"[\u2191/\u2193] move","[q] quit"].filter(Boolean);return c(i,{paddingX:1,borderStyle:"single",borderTop:!0,borderBottom:!1,borderLeft:!1,borderRight:!1,justifyContent:"space-between",children:[a(o,{dimColor:!0,children:s.join(" \xB7 ")}),n?a(o,{color:n.color,children:n.text}):a(o,{children:" "})]})}function v({title:e,focused:r,children:t}){return c(i,{flexDirection:"column",width:"100%",borderStyle:r?"double":"single",borderColor:r?"cyan":void 0,paddingX:1,children:[c(o,{bold:!0,color:r?"cyan":"white",children:[e,r?" \u25CF":""]}),a(i,{flexDirection:"column",marginTop:1,children:t})]})}function I(e,r){return e&&r?{inverse:!0}:{}}function Z({rows:e,cursor:r,focused:t,talkingTo:n}){return e.length===0?a(o,{dimColor:!0,children:"no agents"}):a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=s.errors>0?"red":s.active>0?"yellow":"green",f=s.id===n;return c(i,{children:[a(o,{...p,color:y,children:"\u25CF"}),c(o,{...p,children:[" ",T(s.name,14)]}),c(o,{...p,children:[T(s.tier,6)," "]}),c(o,{...p,children:[s.active,"/",s.total]}),s.errors>0?c(o,{...p,color:"red",children:[" err=",s.errors]}):null,f?a(o,{color:"green",children:" \u2194 chatting"}):null]},s.id)})})}function ee({rows:e,cursor:r,focused:t}){if(e.length===0)return a(o,{dimColor:!0,children:"no live processes"});let n=Date.now();return a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=Math.max(0,Math.round((n-(s.lastTurnAt||s.spawnedAt))/1e3)),f=s.state==="dead"?"red":s.state==="warm-hot"?"green":"yellow";return c(i,{children:[a(o,{...p,color:f,children:T(s.state,10)}),a(o,{...p,children:T(s.key.agentId,12)}),a(o,{...p,children:T(s.key.channel,10)}),c(o,{...p,children:[s.turnCount,"t ",y,"s"]})]},`${s.key.agentId}-${s.key.channel}-${s.key.chatId}-${u}`)})})}function te({rows:e}){return e.length===0?a(o,{dimColor:!0,children:"no crons"}):a(i,{flexDirection:"column",children:e.slice(0,12).map(r=>{let t=r.enabled?r.consecutiveErrors>0?a(o,{color:"red",children:"err"}):a(o,{color:"green",children:"ok"}):a(o,{dimColor:!0,children:"off"}),n=r.nextRun?new Date(r.nextRun).toLocaleTimeString():"\u2014";return c(i,{children:[a(o,{children:T(r.id,16)}),a(o,{dimColor:!0,children:T(r.schedule,12)}),a(o,{children:T(n,10)}),a(i,{children:t})]},r.id)})})}function re({rows:e}){if(e.length===0)return a(o,{dimColor:!0,children:"(waiting for channel events\u2026)"});let r=[...e].sort((t,n)=>n.lastAt-t.lastAt);return a(i,{flexDirection:"column",children:r.slice(0,12).map(t=>c(i,{children:[a(o,{children:T(t.channel,12)}),c(o,{color:"cyan",children:["\u2190",T(String(t.inbound),4)]}),c(o,{color:"magenta",children:["\u2192",T(String(t.outbound),4)]}),a(o,{dimColor:!0,children:t.lastPreview})]},t.channel))})}function ne({rows:e,cursor:r,focused:t}){return e.length===0?a(o,{dimColor:!0,children:"(waiting for events\u2026)"}):a(i,{flexDirection:"column",children:e.slice(0,18).map((n,s)=>{let u=I(t,s===r);return c(i,{children:[a(o,{...u,dimColor:!0,children:new Date(n.at).toLocaleTimeString().padEnd(10)}),a(o,{...u,color:ce(n.kind),children:T(n.kind,8)}),a(o,{...u,children:n.line})]},`${n.at}-${s}`)})})}function oe({chat:e,suggest:r,agents:t}){let n=e.history.slice(-14),s=t.find(f=>f.id===e.agentId),u=H(s?.tier??"",s?.model),p=e.status==="sending"?[...e.history].reverse().find(f=>f.role==="agent"&&f.streaming):void 0,y=e.status==="sending"?a(O,{startedAt:p?.at??Date.now(),phase:p?.text?"responding":"thinking",indent:""}):e.error?c(o,{color:"red",children:["\u25CF ",e.error]}):a(o,{dimColor:!0,children:"\u25CF ready"});return c(i,{flexDirection:"column",width:"50%",borderStyle:"double",borderColor:"green",paddingX:1,children:[c(i,{justifyContent:"space-between",children:[c(o,{bold:!0,color:"green",children:["CHAT \xB7 @",e.agentId]}),a(i,{children:y})]}),c(o,{children:[c(o,{color:u.color,bold:!0,children:[u.glyph," ",u.label]}),s?.model?c(o,{dimColor:!0,children:[" ",j(s.model)]}):null,c(o,{dimColor:!0,children:[" \xB7 ",e.chatId]})]}),a(i,{flexDirection:"column",flexGrow:1,marginTop:1,children:n.length===0?a(o,{dimColor:!0,children:"(type a message and press Enter \u2014 Esc exits, Ctrl-L starts fresh)"}):n.map((f,l)=>a(se,{turn:f,agentId:e.agentId},`${f.at}-${l}`))}),r?c(o,{dimColor:!0,children:[a(o,{color:"cyan",children:"\u21B9"})," ",r.items.slice(0,5).map((f,l)=>l===0?a(o,{color:"cyan",children:f},l):c(o,{children:[" ",f]},l))]}):null,c(i,{children:[a(o,{color:"cyan",children:"you \u203A "}),a(o,{children:e.text}),a(o,{inverse:!0,children:" "})]})]})}function se({turn:e,agentId:r}){if(e.role==="you")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"cyan",children:"you"}),e.text.split(`
5
5
  `).map((s,u)=>c(o,{children:[" ",s]},u))]});if(e.role==="error")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"red",children:"error"}),c(o,{color:"red",children:[" ",e.text]})]});let t=e.elapsedMs!=null?` \xB7 ${(e.elapsedMs/1e3).toFixed(1)}s${e.outTokens!=null?`, ${e.outTokens} tok`:""}`:e.streaming?" \xB7 \u2026":"",n=F(e.text,58,{balance:e.streaming});return c(i,{flexDirection:"column",marginBottom:1,children:[c(o,{color:"green",children:["@",r??"agent",a(o,{dimColor:!0,children:t})]}),e.thinking?e.thinking.split(`
6
6
  `).filter(s=>s.trim()).slice(-2).map((s,u)=>c(o,{dimColor:!0,italic:!0,children:[" ",s]},`th${u}`)):null,(e.tools??[]).map((s,u)=>c(o,{color:s.error?"red":"green",children:[" \u25CF ",c(o,{bold:!s.error,dimColor:s.error,children:[s.name,s.arg?c(o,{dimColor:!0,children:["(",s.arg,")"]}):null,s.error?" failed":""]})]},`tool${u}`)),n.split(`
7
7
  `).slice(0,14).map((s,u)=>c(o,{children:[" ",s]},u))]})}function ae(e){let r=e.data??{},t=typeof r.at=="string"||typeof r.at=="number"?new Date(r.at).getTime():Date.now(),n="";switch(e.event){case"run":n=`${r.phase??""} ${r.workflowId??""} ${(r.runId??"").slice(0,8)} ${r.nodeId??""}`.trim();break;case"task":n=`${r.phase??""} ${r.workflowId??""} ${Array.isArray(r.assignedTo)?r.assignedTo.join(","):""} ${r.title?`"${r.title}"`:""}`.trim();break;case"signal":n=`${r.name??""} (${r.scope??""}) ${r.workflowId??""}`.trim();break;case"mesh":n=`${r.peer??""} ${r.healthy?"healthy":"lost"} ${r.delta??""}`.trim();break;case"channel":{n=`${r.direction==="in"?"\u2190":"\u2192"} ${r.channel??""}:${r.chatId??""} ${r.textPreview??""}`.trim();break}case"status":n=`node=${r.node??""} agents=${r.agents??0} active=${(r.active??[]).length}`;break;default:n=typeof r=="string"?r:JSON.stringify(r).slice(0,80)}return{at:Number.isFinite(t)?t:Date.now(),kind:e.event,line:n}}function ce(e){switch(e){case"run":return"cyan";case"task":return"magenta";case"signal":return"green";case"mesh":return"blue";case"channel":return"yellow";default:return"white"}}function T(e,r){let t=(e??"").toString();return t.length>=r?t.slice(0,r-1)+" ":t+" ".repeat(r-t.length)}export{be as App};
8
- //# sourceMappingURL=App-U52IPUK2.js.map
8
+ //# sourceMappingURL=App-CKPH3HHF.js.map
@@ -1,7 +1,7 @@
1
- import{a as Y,b as Z,c as _,d as ee,e as te,f as ne,g as re}from"./chunk-U2CKL755.js";import{b as Q,g as X}from"./chunk-VTE7L6LO.js";import"./chunk-CJQQIRPD.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
1
+ import{a as Y,b as Z,c as _,d as ee,e as te,f as ne,g as re}from"./chunk-U2CKL755.js";import{b as Q,g as X}from"./chunk-5IA2CYHN.js";import"./chunk-P5ZBBEPL.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
2
2
  `):[""],B=A.find(s=>s.id===T),D=ne(B?.tier??"",B?.model),pe=O?.rows||24,fe=z.slice(-40);return o(g,{flexDirection:"column",height:pe,children:[o(g,{flexDirection:"column",flexGrow:1,justifyContent:"flex-end",overflow:"hidden",children:[z.length===0&&!$?o(g,{flexDirection:"column",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),c(t,{dimColor:!0,children:" \u2014 chat with "}),o(t,{color:m.accent,children:["@",T]}),c(t,{dimColor:!0,children:" via "}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" (",B.model,")"]}):null]}),c(t,{dimColor:!0,children:"streaming \xB7 markdown \xB7 tools \xB7 type a message, or / for commands"})]}):null,fe.map(s=>c(ce,{turn:s,width:V},s.id)),$?c(ce,{turn:$,width:V}):null]}),N?c(g,{flexDirection:"column",marginTop:1,children:N.map((s,e)=>o(g,{children:[c(t,{color:e===0?m.accent:m.muted,bold:e===0,children:s.cmd.padEnd(10)}),c(t,{dimColor:!0,children:s.desc})]},s.cmd))}):j?o(g,{marginTop:1,children:[c(t,{color:m.accent,children:"\u21B9 "}),j.items.map((s,e)=>o(t,{color:e===0?m.accent:m.muted,children:[s,e<j.items.length-1?" ":""]},e))]}):J?c(g,{marginTop:1,children:c(t,{dimColor:!0,children:J})}):null,c(g,{borderStyle:"single",borderColor:G,borderLeft:!1,borderRight:!1,borderDimColor:!v,width:"100%",children:c(g,{flexDirection:"column",width:"100%",children:ge.map((s,e,r)=>o(g,{children:[c(t,{color:G,bold:!0,children:e===0?"\u203A ":" "}),c(t,{children:s}),e===r.length-1&&!v?c(t,{color:G,children:"\u258F"}):null]},e))})}),o(g,{justifyContent:"space-between",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),o(t,{dimColor:!0,children:[" \xB7 @",T," \xB7 "]}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" ",re(B.model)]}):null]}),o(t,{dimColor:!0,children:["\u21B5 send / cmds @ mention esc ",v?"stop":"exit"]})]})]})}function ce({turn:n,width:p}){let f=H(()=>ye(n,p),[n,p]),a=n.segs.some(h=>h.type==="text"&&h.content);return o(g,{flexDirection:"column",marginBottom:1,children:[o(t,{color:m.accent,bold:!0,children:[F," you"]}),n.you.split(`
3
3
  `).map((h,w)=>o(t,{color:"white",children:[" ",h]},`u${w}`)),o(t,{color:m.agent,bold:!0,children:[F," ",n.agentId??"agent"]}),n.thinking?c(g,{flexDirection:"column",children:we(n.thinking,n.live?3:2).map((h,w)=>o(t,{dimColor:!0,italic:!0,children:[" ",h]},`th${w}`))}):null,f,n.live?c(te,{startedAt:n.startedAt,phase:a?"responding":"thinking",hint:"esc to interrupt"}):n.interrupted?o(t,{dimColor:!0,children:[" \u2298 interrupted \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s"]}):n.error?o(t,{color:"red",children:[" \u2717 ",n.error]}):n.elapsedMs!=null?o(t,{dimColor:!0,children:[" \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s",n.outTokens!=null?`, ${n.outTokens} tok`:""]}):null]})}function we(n,p){return n.split(`
4
4
  `).filter(a=>a.trim()).slice(-p)}function ye(n,p){let f=[];return n.segs.forEach((a,h)=>{a.type==="tool"?f.push(o(t,{color:a.error?"red":"green",children:[" ","\u25CF ",o(t,{bold:!a.error,dimColor:a.error,children:[a.name,a.arg?o(t,{dimColor:!0,children:["(",a.arg,")"]}):null,a.error?" failed":""]})]},h)):a.content&&f.push(c(t,{children:Ce(Y(a.content,p,{balance:n.live}))},h))}),f}function Ce(n){return n.split(`
5
5
  `).map(p=>p.length?" "+p:p).join(`
6
6
  `)}export{Pe as ChatApp};
7
- //# sourceMappingURL=ChatApp-XP4OMHPL.js.map
7
+ //# sourceMappingURL=ChatApp-MX7IG4K2.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-BPIRO4YD.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
2
- //# sourceMappingURL=agent-V2CA4TXD.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-MHIMGM4G.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
2
+ //# sourceMappingURL=agent-2U574KBZ.js.map