agentix-cli 0.35.0 → 0.37.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 (93) hide show
  1. package/apps/mac-helper/Sources/AgentXHelper/Presence.swift +117 -89
  2. package/apps/mac-helper/Sources/AgentXHelper/PresenceTag.swift +147 -0
  3. package/apps/mac-helper/Sources/AgentXHelper/main.swift +3 -1
  4. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +33 -25
  5. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +4 -0
  6. package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +4 -4
  7. package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +81 -13
  8. package/apps/mac-voice/Sources/AgentXVoice/main.swift +114 -68
  9. package/dist/{App-K5J52F7O.js → App-TW5N7JY2.js} +2 -2
  10. package/dist/{ChatApp-LRRAPZH7.js → ChatApp-MRGX7JBT.js} +2 -2
  11. package/dist/{agent-XEXAYH5J.js → agent-FQHWBQP2.js} +2 -2
  12. package/dist/{board-dashboard-CBOESBMD.js → board-dashboard-2YEYVCUS.js} +4 -4
  13. package/dist/{chunk-PBVEYPZB.js → chunk-3SKFX6G3.js} +2 -2
  14. package/dist/{chunk-DXXWYYZI.js → chunk-4ZGX4L6Q.js} +196 -185
  15. package/dist/chunk-4ZGX4L6Q.js.map +1 -0
  16. package/dist/{chunk-WRBW4A2U.js → chunk-ADRAW342.js} +2 -2
  17. package/dist/{chunk-3PL3WLIR.js → chunk-AKIYB3IJ.js} +2 -2
  18. package/dist/{chunk-JSZRGMSA.js → chunk-EJWIRM52.js} +2 -2
  19. package/dist/{chunk-ZSNTLADV.js → chunk-FHF4RA4Q.js} +2 -2
  20. package/dist/{chunk-SYJUZ3F2.js → chunk-GL7VC26K.js} +2 -2
  21. package/dist/{chunk-3TNK7VGH.js → chunk-HEV3AVFJ.js} +2 -2
  22. package/dist/{chunk-FLNN3KSX.js → chunk-IEE475O2.js} +2 -2
  23. package/dist/{chunk-WEN2P4RS.js → chunk-JLEADOCC.js} +2 -2
  24. package/dist/{chunk-GYBYKREN.js → chunk-MC6I6ZLY.js} +2 -2
  25. package/dist/{chunk-4QNZI4WE.js → chunk-N6APC3DF.js} +5 -5
  26. package/dist/{chunk-LLKILWWO.js → chunk-RRLIZLBH.js} +2 -2
  27. package/dist/{chunk-LQFEH3SA.js → chunk-SR6TVIIS.js} +2 -2
  28. package/dist/{chunk-HPY6OOFO.js → chunk-UORMWVLP.js} +150 -142
  29. package/dist/chunk-UORMWVLP.js.map +1 -0
  30. package/dist/chunk-WFKS4O34.js +13 -0
  31. package/dist/chunk-WFKS4O34.js.map +1 -0
  32. package/dist/cli.js +1 -1
  33. package/dist/client-4KD2UA75.js +2 -0
  34. package/dist/config-WN3R3BPU.js +2 -0
  35. package/dist/config-mutator-2FIUL4DA.js +2 -0
  36. package/dist/doctor-BNO46EOR.js +2 -0
  37. package/dist/index.d.ts +129 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/program-Z6EK44PV.js +2 -0
  41. package/dist/query-TKUDIOBZ.js +2 -0
  42. package/dist/{registry-GWN34G4J.js → registry-TDYF7HGH.js} +2 -2
  43. package/dist/{request-planner-V7T6BQU7.js → request-planner-DF74JHQB.js} +2 -2
  44. package/dist/{review-candidates-7QKNMRGU.js → review-candidates-SEAGMKI6.js} +2 -2
  45. package/dist/{routing-BHQXFUSH.js → routing-JKWL3L3Q.js} +2 -2
  46. package/dist/scheduler-7CYBDKQ7.js +2 -0
  47. package/dist/{seat-U57NWHKC.js → seat-C6TOB63Q.js} +2 -2
  48. package/dist/{select-JL3JJJP4.js → select-YB5KF27O.js} +2 -2
  49. package/dist/wiki-7FCINSKJ.js +2 -0
  50. package/package.json +1 -1
  51. package/dist/chunk-DXXWYYZI.js.map +0 -1
  52. package/dist/chunk-HPY6OOFO.js.map +0 -1
  53. package/dist/chunk-LA6AKS4N.js +0 -13
  54. package/dist/chunk-LA6AKS4N.js.map +0 -1
  55. package/dist/client-APAWETTA.js +0 -2
  56. package/dist/config-S3VHHVYW.js +0 -2
  57. package/dist/config-mutator-5B2YWZMK.js +0 -2
  58. package/dist/doctor-SCECRUJ7.js +0 -2
  59. package/dist/program-XJEQAWN3.js +0 -2
  60. package/dist/query-EDW72Q4O.js +0 -2
  61. package/dist/scheduler-LY4RBPB6.js +0 -2
  62. package/dist/wiki-AQ4K3F7V.js +0 -2
  63. /package/dist/{App-K5J52F7O.js.map → App-TW5N7JY2.js.map} +0 -0
  64. /package/dist/{ChatApp-LRRAPZH7.js.map → ChatApp-MRGX7JBT.js.map} +0 -0
  65. /package/dist/{agent-XEXAYH5J.js.map → agent-FQHWBQP2.js.map} +0 -0
  66. /package/dist/{board-dashboard-CBOESBMD.js.map → board-dashboard-2YEYVCUS.js.map} +0 -0
  67. /package/dist/{chunk-PBVEYPZB.js.map → chunk-3SKFX6G3.js.map} +0 -0
  68. /package/dist/{chunk-WRBW4A2U.js.map → chunk-ADRAW342.js.map} +0 -0
  69. /package/dist/{chunk-3PL3WLIR.js.map → chunk-AKIYB3IJ.js.map} +0 -0
  70. /package/dist/{chunk-JSZRGMSA.js.map → chunk-EJWIRM52.js.map} +0 -0
  71. /package/dist/{chunk-ZSNTLADV.js.map → chunk-FHF4RA4Q.js.map} +0 -0
  72. /package/dist/{chunk-SYJUZ3F2.js.map → chunk-GL7VC26K.js.map} +0 -0
  73. /package/dist/{chunk-3TNK7VGH.js.map → chunk-HEV3AVFJ.js.map} +0 -0
  74. /package/dist/{chunk-FLNN3KSX.js.map → chunk-IEE475O2.js.map} +0 -0
  75. /package/dist/{chunk-WEN2P4RS.js.map → chunk-JLEADOCC.js.map} +0 -0
  76. /package/dist/{chunk-GYBYKREN.js.map → chunk-MC6I6ZLY.js.map} +0 -0
  77. /package/dist/{chunk-4QNZI4WE.js.map → chunk-N6APC3DF.js.map} +0 -0
  78. /package/dist/{chunk-LLKILWWO.js.map → chunk-RRLIZLBH.js.map} +0 -0
  79. /package/dist/{chunk-LQFEH3SA.js.map → chunk-SR6TVIIS.js.map} +0 -0
  80. /package/dist/{client-APAWETTA.js.map → client-4KD2UA75.js.map} +0 -0
  81. /package/dist/{config-S3VHHVYW.js.map → config-WN3R3BPU.js.map} +0 -0
  82. /package/dist/{config-mutator-5B2YWZMK.js.map → config-mutator-2FIUL4DA.js.map} +0 -0
  83. /package/dist/{doctor-SCECRUJ7.js.map → doctor-BNO46EOR.js.map} +0 -0
  84. /package/dist/{program-XJEQAWN3.js.map → program-Z6EK44PV.js.map} +0 -0
  85. /package/dist/{query-EDW72Q4O.js.map → query-TKUDIOBZ.js.map} +0 -0
  86. /package/dist/{registry-GWN34G4J.js.map → registry-TDYF7HGH.js.map} +0 -0
  87. /package/dist/{request-planner-V7T6BQU7.js.map → request-planner-DF74JHQB.js.map} +0 -0
  88. /package/dist/{review-candidates-7QKNMRGU.js.map → review-candidates-SEAGMKI6.js.map} +0 -0
  89. /package/dist/{routing-BHQXFUSH.js.map → routing-JKWL3L3Q.js.map} +0 -0
  90. /package/dist/{scheduler-LY4RBPB6.js.map → scheduler-7CYBDKQ7.js.map} +0 -0
  91. /package/dist/{seat-U57NWHKC.js.map → seat-C6TOB63Q.js.map} +0 -0
  92. /package/dist/{select-JL3JJJP4.js.map → select-YB5KF27O.js.map} +0 -0
  93. /package/dist/{wiki-AQ4K3F7V.js.map → wiki-7FCINSKJ.js.map} +0 -0
@@ -1,13 +1,38 @@
1
1
  import AVFoundation
2
2
  import Foundation
3
3
 
4
- /// Speech in and out, ElevenLabs first with a local fallback on each side.
4
+ /// How the daemon says an agent should sound (the `voice` of /ask and of
5
+ /// step events). Missing fields mean the free system voice.
6
+ struct VoiceChoice: Decodable {
7
+ var provider: String?
8
+ var elevenlabsVoiceId: String?
9
+ /// A macOS voice identifier for `say -v`; nil for the OS default.
10
+ var systemVoice: String?
11
+ /// When ElevenLabs cannot speak, use the system voice instead.
12
+ var fallback: Bool?
13
+
14
+ init(provider: String? = nil, elevenlabsVoiceId: String? = nil, systemVoice: String? = nil, fallback: Bool? = nil) {
15
+ self.provider = provider; self.elevenlabsVoiceId = elevenlabsVoiceId
16
+ self.systemVoice = systemVoice; self.fallback = fallback
17
+ }
18
+
19
+ /// From a step event, which arrives as untyped JSON.
20
+ init?(json: Any?) {
21
+ guard let o = json as? [String: Any] else { return nil }
22
+ self.init(provider: o["provider"] as? String, elevenlabsVoiceId: o["elevenlabsVoiceId"] as? String,
23
+ systemVoice: o["systemVoice"] as? String, fallback: o["fallback"] as? Bool)
24
+ }
25
+ }
26
+
27
+ /// Speech in and out. Speech out uses the free macOS voices unless the
28
+ /// daemon says an agent speaks through ElevenLabs; speech in uses
29
+ /// ElevenLabs when there is a key, with a local fallback.
5
30
  ///
6
31
  /// The fallbacks are not decoration. This widget is push-to-talk on a
7
32
  /// laptop: it will be used on hotel wifi, on a plane, and on the day the
8
33
  /// key expires. A voice assistant that answers "network error" out loud is
9
34
  /// worse than one that answers in a robot voice, so STT degrades to
10
- /// on-device mlx-whisper and TTS degrades to `say`.
35
+ /// on-device mlx-whisper and ElevenLabs TTS degrades to `say`.
11
36
  enum Speech {
12
37
  // MARK: Speech to text
13
38
 
@@ -72,19 +97,28 @@ enum Speech {
72
97
 
73
98
  // MARK: Text to speech
74
99
 
75
- /// `voiceID` is the answering agent's voice; nil means the global default.
76
- static func speak(_ text: String, voiceID: String? = nil) async {
100
+ /// Silence whatever this app is saying (an answer or a step line).
101
+ static func stop() { Player.shared.stop() }
102
+
103
+ /// `voice` is the answering agent's, as the daemon resolved it; nil
104
+ /// (no answer yet) means the provider this app is configured with.
105
+ static func speak(_ text: String, voice: VoiceChoice? = nil) async {
77
106
  guard !text.isEmpty else { return }
78
- if let key = Config.elevenLabsKey {
79
- do {
80
- let mp3 = try await elevenLabsTTS(text: text, voiceID: voiceID ?? Config.voiceID, key: key)
81
- try await Player.shared.play(mp3)
82
- return
83
- } catch {
84
- Log.warn("ElevenLabs TTS failed (\(error.localizedDescription)); falling back to say")
107
+ if (voice?.provider ?? Config.voiceProvider) == "elevenlabs" {
108
+ if let key = Config.elevenLabsKey {
109
+ do {
110
+ let mp3 = try await elevenLabsTTS(text: text, voiceID: voice?.elevenlabsVoiceId ?? Config.voiceID, key: key)
111
+ try await Player.shared.play(mp3)
112
+ return
113
+ } catch {
114
+ Log.warn("ElevenLabs TTS failed (\(error.localizedDescription))")
115
+ }
116
+ } else {
117
+ Log.warn("ElevenLabs is the voice provider but there is no key")
85
118
  }
119
+ guard voice?.fallback ?? true else { return }
86
120
  }
87
- _ = try? run("/usr/bin/say", [text])
121
+ await Player.shared.say(text, voice: voice?.systemVoice)
88
122
  }
89
123
 
90
124
  private static func elevenLabsTTS(text: String, voiceID: String, key: String) async throws -> Data {
@@ -135,11 +169,38 @@ enum VoiceError: LocalizedError {
135
169
  }
136
170
 
137
171
  /// Holds a strong reference to the player for the life of playback —
138
- /// an AVAudioPlayer that goes out of scope stops mid-sentence.
172
+ /// an AVAudioPlayer that goes out of scope stops mid-sentence — and to the
173
+ /// `say` process, so `stop()` silences either.
139
174
  final class Player: NSObject, AVAudioPlayerDelegate {
140
175
  static let shared = Player()
141
176
  private var player: AVAudioPlayer?
142
177
  private var finished: CheckedContinuation<Void, Never>?
178
+ private var sayProcess: Process?
179
+
180
+ /// Speak with a system voice; returns when done or stopped. The text
181
+ /// goes in on stdin, so a line that starts with "-" is not an option.
182
+ func say(_ text: String, voice: String?) async {
183
+ let p = Process()
184
+ p.executableURL = URL(fileURLWithPath: "/usr/bin/say")
185
+ p.arguments = voice.map { ["-v", $0] } ?? []
186
+ let input = Pipe()
187
+ p.standardInput = input
188
+ p.standardOutput = FileHandle.nullDevice
189
+ p.standardError = FileHandle.nullDevice
190
+ await withCheckedContinuation { (c: CheckedContinuation<Void, Never>) in
191
+ p.terminationHandler = { _ in c.resume() }
192
+ do { try p.run() } catch {
193
+ p.terminationHandler = nil
194
+ Log.warn("say failed to start (\(error.localizedDescription))")
195
+ c.resume()
196
+ return
197
+ }
198
+ sayProcess = p
199
+ input.fileHandleForWriting.write(Data(text.utf8))
200
+ try? input.fileHandleForWriting.close()
201
+ }
202
+ if sayProcess === p { sayProcess = nil }
203
+ }
143
204
 
144
205
  func play(_ mp3: Data) async throws {
145
206
  let p = try AVAudioPlayer(data: mp3)
@@ -154,6 +215,13 @@ final class Player: NSObject, AVAudioPlayerDelegate {
154
215
  func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
155
216
  finished?.resume(); finished = nil; self.player = nil
156
217
  }
218
+
219
+ /// Cut the line off now; the `play` awaiting it returns.
220
+ func stop() {
221
+ sayProcess?.terminate(); sayProcess = nil
222
+ player?.stop()
223
+ finished?.resume(); finished = nil; player = nil
224
+ }
157
225
  }
158
226
 
159
227
  /// Logs to stderr AND to a file.
@@ -30,10 +30,15 @@ final class App: NSObject, NSApplicationDelegate {
30
30
  private var lastSpokeAt = Date.distantPast
31
31
  /// The answering agent's voice, learned from the daemon. Nil until
32
32
  /// then, which speaks in the global default.
33
- private var voiceID: String?
34
- /// Set on Option-Space: whether a talk was running (and is now hushed),
35
- /// so the words spoken go to the talk rather than to /ask.
36
- private var talkCheck: Task<Bool, Never>?
33
+ private var voice: VoiceChoice?
34
+ /// Set when the door opens: what was speaking (and is now hushed), so
35
+ /// the words spoken go to that activity rather than to /ask.
36
+ private var talkCheck: Task<AgentClient.Hushed, Never>?
37
+ /// Words said while our own turn was still running: they go next, and
38
+ /// the stale answer is not spoken.
39
+ private var followUp: String?
40
+ /// "stop" while our own turn was running: drop its answer.
41
+ private var abandoned = false
37
42
 
38
43
  /// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
39
44
  private func smartPaste() {
@@ -226,12 +231,16 @@ final class App: NSObject, NSApplicationDelegate {
226
231
  // A held key overrides any hands-free window that is open, so the
227
232
  // two ways of talking never fight over the microphone.
228
233
  stopPolling()
229
- // A turn already in flight must not be interrupted by a stray
230
- // keypress; the answer is still coming and will be spoken.
231
- guard !busy, !recorder.isRecording else { return }
232
- // Two agents may be talking out loud. Hush them now, before a word
233
- // is said, and remember whether they were there.
234
- talkCheck = Task { await AgentClient.talkHush() }
234
+ guard !recorder.isRecording else { return }
235
+ // Option-Space is the door to everything spoken, always: our own
236
+ // answer or step line stops now, and the daemon hushes any talk,
237
+ // lesson or narration, remembering which it was. Even mid-turn —
238
+ // ignoring the key while busy is how Anis spoke to a lesson and
239
+ // nothing listened.
240
+ Speech.stop()
241
+ lastSpokeAt = Date()
242
+ talkCheck = Task { await AgentClient.hush() }
243
+ Log.info("door: opened\(busy ? " (a turn is running)" : "")")
235
244
  do {
236
245
  try recorder.start()
237
246
  panel.render(.listening)
@@ -242,74 +251,111 @@ final class App: NSObject, NSApplicationDelegate {
242
251
 
243
252
  private func stopAndSend() {
244
253
  guard recorder.isRecording else { return }
245
- let talk = talkCheck
254
+ // Clicked or hands-free: nothing hushed yet. They did speak, so
255
+ // hush now; whatever was talking gets the words, same as the key.
256
+ let door = talkCheck ?? Task { await AgentClient.hush() }
246
257
  talkCheck = nil
247
258
  guard let wav = recorder.stop() else {
248
259
  panel.render(.error("Too short — hold while speaking"))
249
260
  resetSoon()
250
261
  return
251
262
  }
263
+ let midTurn = busy
252
264
  busy = true
253
- panel.render(.thinking)
265
+ if !midTurn { panel.render(.thinking) }
254
266
 
255
267
  Task { @MainActor in
256
- do {
257
- let heard = try await Speech.transcribe(wav: wav)
258
- guard !heard.isEmpty else {
259
- panel.render(.error("Didn't catch that"))
260
- busy = false; resetSoon(); return
261
- }
262
- Log.info("heard: \(heard)")
263
- if await talk?.value == true {
264
- // The talk answers out loud through the daemon; nothing
265
- // to speak here.
266
- try await AgentClient.talkDoor(heard)
267
- Log.info("sent to the talk")
268
- panel.render(.idle)
269
- busy = false
270
- return
271
- }
272
- beginNarration()
273
-
274
- let answer = try await AgentClient.ask(heard)
275
- endNarration()
276
- if let v = answer.voiceID { voiceID = v }
277
- Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
278
- // Show BEFORE speaking, but only when there is something
279
- // the speech cannot deliver — a link, an image, or more
280
- // text than was read aloud. A card that opens on every
281
- // "Ok." teaches you to ignore it.
282
- if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
283
- buttons: answer.buttons, imageURL: answer.imageURL) {
284
- card.show(spoken: answer.text, written: answer.written,
285
- buttons: answer.buttons, imageURL: answer.imageURL)
286
- } else {
287
- card.orderOut(nil)
288
- }
289
- // Scroll the sentence being spoken, so it can be read as
290
- // well as heard — and re-read after, which speech cannot do.
291
- panel.render(.saying(answer.text))
292
- await Speech.speak(answer.text, voiceID: voiceID)
293
- panel.render(.idle)
294
- // Leave the microphone open for a moment. Say nothing and
295
- // it closes itself; start talking and the conversation
296
- // simply continues.
297
- busy = false
298
- // A live lesson now runs on screen and speaks for itself; an
299
- // open mic would hear the agent. Option-Space is the door.
300
- if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
301
- listenHandsFree(followUp: true)
302
- } catch {
303
- endNarration()
304
- Log.warn("turn failed: \(error.localizedDescription)")
305
- panel.render(.error(short(error.localizedDescription)))
306
- // Say it aloud too — a voice assistant that fails only in
307
- // a 230px label has failed silently for anyone not looking.
308
- await Speech.speak("Sorry, that didn't work.", voiceID: voiceID)
309
- resetSoon()
268
+ let heard = (try? await Speech.transcribe(wav: wav)) ?? ""
269
+ guard !heard.isEmpty else {
270
+ if !midTurn { panel.render(.error("Didn't catch that")); busy = false; resetSoon() }
271
+ return
272
+ }
273
+ Log.info("heard: \(heard)")
274
+ let hushed = await door.value
275
+ if hushed.kind != nil, await AgentClient.door(heard) {
276
+ // The talk or lesson answers out loud through the daemon.
277
+ Log.info("door: \"\(heard)\" → \(hushed.kind!)\(hushed.agentID.map { " (\($0))" } ?? "")")
278
+ if !midTurn { panel.render(.idle); busy = false }
279
+ return
310
280
  }
281
+ if midTurn {
282
+ // Our own turn is still thinking. The agent cannot change
283
+ // course mid-turn, so his words go next and the stale
284
+ // answer is not spoken; "stop" just drops it.
285
+ if Self.isStop(heard) { abandoned = true; followUp = nil; Log.info("door: stop → dropping the turn in flight") }
286
+ else { followUp = heard; Log.info("door: \"\(heard)\" → next, instead of the answer in flight") }
287
+ panel.render(.working("Got it — one moment", 0))
288
+ return
289
+ }
290
+ await runTurn(heard)
291
+ }
292
+ }
293
+
294
+ static func isStop(_ s: String) -> Bool {
295
+ s.range(of: #"^\s*(stop|stop talking|that'?s enough|enough)[\s.!]*$"#,
296
+ options: [.regularExpression, .caseInsensitive]) != nil
297
+ }
298
+
299
+ /// One question and its spoken answer. Words said through the door
300
+ /// while it thinks replace the answer with the next turn.
301
+ private func runTurn(_ heard: String) async {
302
+ do {
303
+ beginNarration()
304
+
305
+ let answer = try await AgentClient.ask(heard)
306
+ endNarration()
307
+ if abandoned {
308
+ abandoned = false
309
+ Log.info("dropped answer (\(answer.agentID ?? "?")): \(answer.text)")
310
+ panel.render(.idle); busy = false; return
311
+ }
312
+ if let next = followUp {
313
+ followUp = nil
314
+ Log.info("superseded answer (\(answer.agentID ?? "?")): \(answer.text)")
315
+ panel.render(.thinking)
316
+ return await runTurn(next)
317
+ }
318
+ if let v = answer.voice { voice = v }
319
+ Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
320
+ // Show BEFORE speaking, but only when there is something
321
+ // the speech cannot deliver — a link, an image, or more
322
+ // text than was read aloud. A card that opens on every
323
+ // "Ok." teaches you to ignore it.
324
+ if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
325
+ buttons: answer.buttons, imageURL: answer.imageURL) {
326
+ card.show(spoken: answer.text, written: answer.written,
327
+ buttons: answer.buttons, imageURL: answer.imageURL)
328
+ } else {
329
+ card.orderOut(nil)
330
+ }
331
+ // Scroll the sentence being spoken, so it can be read as
332
+ // well as heard — and re-read after, which speech cannot do.
333
+ panel.render(.saying(answer.text))
334
+ await Speech.speak(answer.text, voice: voice)
335
+ // Cut off by the door: the new words are being recorded.
336
+ if recorder.isRecording { busy = false; return }
337
+ if let next = followUp { followUp = nil; return await runTurn(next) }
338
+ panel.render(.idle)
339
+ // Leave the microphone open for a moment. Say nothing and
340
+ // it closes itself; start talking and the conversation
341
+ // simply continues.
311
342
  busy = false
343
+ // A live lesson now runs on screen and speaks for itself; an
344
+ // open mic would hear the agent. Option-Space is the door.
345
+ if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
346
+ listenHandsFree(followUp: true)
347
+ } catch {
348
+ endNarration()
349
+ Log.warn("turn failed: \(error.localizedDescription)")
350
+ if abandoned { abandoned = false; panel.render(.idle); busy = false; return }
351
+ if let next = followUp { followUp = nil; return await runTurn(next) }
352
+ panel.render(.error(short(error.localizedDescription)))
353
+ // Say it aloud too — a voice assistant that fails only in
354
+ // a 230px label has failed silently for anyone not looking.
355
+ await Speech.speak("Sorry, that didn't work.", voice: voice)
356
+ resetSoon()
312
357
  }
358
+ busy = false
313
359
  }
314
360
 
315
361
  /// Narrate the wait using what the daemon says it is actually doing.
@@ -330,7 +376,7 @@ final class App: NSObject, NSApplicationDelegate {
330
376
  progress = Progress(agentID: Config.agentID) { [weak self] step, voice in
331
377
  Task { @MainActor in
332
378
  guard let self, self.busy else { return }
333
- if let voice { self.voiceID = voice }
379
+ if let voice { self.voice = voice }
334
380
  self.lastStep = step
335
381
  Log.info("step: \(step)")
336
382
  self.panel.render(.working(step, Int(Date().timeIntervalSince(self.startedAt))))
@@ -381,7 +427,7 @@ final class App: NSObject, NSApplicationDelegate {
381
427
  tool: tool, detail: detail, elapsed: Int(elapsed)) else { return }
382
428
  guard !self.spokenSteps.contains(phrase) else { return }
383
429
  self.spokenSteps.insert(phrase)
384
- await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".", voiceID: self.voiceID)
430
+ await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".", voice: self.voice)
385
431
  }
386
432
  }
387
433
 
@@ -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-ZSNTLADV.js";import"./chunk-LA6AKS4N.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-FHF4RA4Q.js";import"./chunk-WFKS4O34.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-K5J52F7O.js.map
8
+ //# sourceMappingURL=App-TW5N7JY2.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-ZSNTLADV.js";import"./chunk-LA6AKS4N.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-FHF4RA4Q.js";import"./chunk-WFKS4O34.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-LRRAPZH7.js.map
7
+ //# sourceMappingURL=ChatApp-MRGX7JBT.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-3TNK7VGH.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-XEXAYH5J.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-HEV3AVFJ.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-FQHWBQP2.js.map