agentix-cli 0.54.0 → 0.56.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 (126) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +29 -0
  2. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +7 -0
  3. package/apps/mac-voice/Sources/AgentXVoice/Orb.swift +311 -0
  4. package/apps/mac-voice/Sources/AgentXVoice/OrbMath.swift +57 -0
  5. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +14 -1
  6. package/apps/mac-voice/Sources/AgentXVoice/ResultCard.swift +33 -1
  7. package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +74 -3
  8. package/apps/mac-voice/Sources/AgentXVoice/main.swift +197 -14
  9. package/apps/mac-voice/build.sh +1 -1
  10. package/dist/{App-VRTLAPRF.js → App-4EM7IPB3.js} +2 -2
  11. package/dist/{ChatApp-YENWPZT4.js → ChatApp-FYTEEGSU.js} +2 -2
  12. package/dist/{agent-HVUGHYBF.js → agent-RZ46ZB4W.js} +2 -2
  13. package/dist/{board-dashboard-3XJVYDB3.js → board-dashboard-NRRYCDZW.js} +826 -180
  14. package/dist/board-dashboard-NRRYCDZW.js.map +1 -0
  15. package/dist/{chunk-M67AN4J7.js → chunk-4HEVSWO3.js} +2 -2
  16. package/dist/{chunk-KLDGJSR5.js → chunk-6AK6WKE5.js} +2 -2
  17. package/dist/{chunk-ENRJ2QEH.js → chunk-723K4SAW.js} +2 -2
  18. package/dist/{chunk-UYMK3XNB.js → chunk-76AHNHWC.js} +2 -2
  19. package/dist/{chunk-XWU7O3S5.js → chunk-C3VRBEJ7.js} +2 -2
  20. package/dist/{chunk-HQMISQ7L.js → chunk-CELYD5QW.js} +2 -2
  21. package/dist/{chunk-33QV5O6P.js → chunk-D3LSPFHK.js} +2 -2
  22. package/dist/chunk-EJ5ISSWW.js +151 -0
  23. package/dist/chunk-EJ5ISSWW.js.map +1 -0
  24. package/dist/{chunk-4HNHHKJF.js → chunk-EZN2YXRY.js} +2 -2
  25. package/dist/{chunk-BKDUCPZ3.js → chunk-GSOL2LW6.js} +2 -2
  26. package/dist/{chunk-TLFJVLVQ.js → chunk-IL2XLW4H.js} +152 -151
  27. package/dist/chunk-IL2XLW4H.js.map +1 -0
  28. package/dist/{chunk-WLRHL2P7.js → chunk-LKJSPO3S.js} +2 -2
  29. package/dist/{chunk-37EABIOJ.js → chunk-M4AG2YNX.js} +2 -2
  30. package/dist/{chunk-ZOJUHPD5.js → chunk-O3XG2NVR.js} +18 -18
  31. package/dist/{chunk-ZOJUHPD5.js.map → chunk-O3XG2NVR.js.map} +1 -1
  32. package/dist/{chunk-QYHABRLM.js → chunk-SA23V7WX.js} +2 -2
  33. package/dist/chunk-U4H35SJS.js +15 -0
  34. package/dist/chunk-U4H35SJS.js.map +1 -0
  35. package/dist/{chunk-3RCFUIYR.js → chunk-UIVHSYCN.js} +9 -9
  36. package/dist/chunk-UIVHSYCN.js.map +1 -0
  37. package/dist/{chunk-3W2KMI5O.js → chunk-W4YMMWJL.js} +2 -2
  38. package/dist/{chunk-6JHA3ZE6.js → chunk-WKMTT2UY.js} +211 -197
  39. package/dist/chunk-WKMTT2UY.js.map +1 -0
  40. package/dist/chunk-WOMQ2VGA.js +5 -0
  41. package/dist/{chunk-QX4R5WY7.js.map → chunk-WOMQ2VGA.js.map} +1 -1
  42. package/dist/chunk-X5C5W3E4.js +3 -0
  43. package/dist/chunk-X5C5W3E4.js.map +1 -0
  44. package/dist/{chunk-2TJYZF4E.js → chunk-XEOAKD5C.js} +2 -2
  45. package/dist/cli.js +1 -1
  46. package/dist/cli.js.map +1 -1
  47. package/dist/client-IA7ZPE56.js +2 -0
  48. package/dist/config-HPN2F7LH.js +2 -0
  49. package/dist/config-mutator-7GPTMTVB.js +2 -0
  50. package/dist/doctor-IBWQVQV4.js +2 -0
  51. package/dist/index.d.ts +185 -19
  52. package/dist/index.js +1 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/program-G6DIFJUF.js +2 -0
  55. package/dist/{promote-H6ZHJXTY.js → promote-T4UJROEH.js} +2 -2
  56. package/dist/query-VUJC4Y5O.js +2 -0
  57. package/dist/registry-DX2RI7I7.js +2 -0
  58. package/dist/{request-planner-ABD4WIGM.js → request-planner-B7DE7VJR.js} +2 -2
  59. package/dist/{review-candidates-B6574GOJ.js → review-candidates-4YJHIBAF.js} +2 -2
  60. package/dist/routing-MZG3UODH.js +2 -0
  61. package/dist/schedule-tool-A4OFQ4YZ.js +2 -0
  62. package/dist/scheduler-RHJW2V4M.js +2 -0
  63. package/dist/seat-2PEI7JRX.js +2 -0
  64. package/dist/{select-Y7SBUCGJ.js → select-4KIUR27O.js} +2 -2
  65. package/dist/{set-enabled-ARKMDLXN.js → set-enabled-SE7HQKPF.js} +2 -2
  66. package/dist/subscriptions-RYVPHVIV.js +2 -0
  67. package/dist/{tool-5EVYUN2G.js → tool-5VMD7IE4.js} +2 -2
  68. package/dist/wiki-2QFLAH4U.js +2 -0
  69. package/dist/wiki-2QFLAH4U.js.map +1 -0
  70. package/package.json +1 -1
  71. package/dist/board-dashboard-3XJVYDB3.js.map +0 -1
  72. package/dist/chunk-3RCFUIYR.js.map +0 -1
  73. package/dist/chunk-4FFWNPFX.js +0 -15
  74. package/dist/chunk-4FFWNPFX.js.map +0 -1
  75. package/dist/chunk-6JHA3ZE6.js.map +0 -1
  76. package/dist/chunk-BMSPRUEJ.js +0 -150
  77. package/dist/chunk-BMSPRUEJ.js.map +0 -1
  78. package/dist/chunk-QX4R5WY7.js +0 -5
  79. package/dist/chunk-TLFJVLVQ.js.map +0 -1
  80. package/dist/client-N4XQEAVL.js +0 -2
  81. package/dist/config-KHKIY6LI.js +0 -2
  82. package/dist/config-mutator-ZZKFQ7HJ.js +0 -2
  83. package/dist/doctor-QICAXIQB.js +0 -2
  84. package/dist/program-ZWNGK3RB.js +0 -2
  85. package/dist/query-FTE4FLYK.js +0 -2
  86. package/dist/registry-HDOFVNRK.js +0 -2
  87. package/dist/routing-BGKZOHOA.js +0 -2
  88. package/dist/schedule-tool-UDHXTKWV.js +0 -2
  89. package/dist/scheduler-NQM3HVUY.js +0 -2
  90. package/dist/seat-VGST5AQB.js +0 -2
  91. package/dist/wiki-GREI2TSP.js +0 -2
  92. /package/dist/{App-VRTLAPRF.js.map → App-4EM7IPB3.js.map} +0 -0
  93. /package/dist/{ChatApp-YENWPZT4.js.map → ChatApp-FYTEEGSU.js.map} +0 -0
  94. /package/dist/{agent-HVUGHYBF.js.map → agent-RZ46ZB4W.js.map} +0 -0
  95. /package/dist/{chunk-M67AN4J7.js.map → chunk-4HEVSWO3.js.map} +0 -0
  96. /package/dist/{chunk-KLDGJSR5.js.map → chunk-6AK6WKE5.js.map} +0 -0
  97. /package/dist/{chunk-ENRJ2QEH.js.map → chunk-723K4SAW.js.map} +0 -0
  98. /package/dist/{chunk-UYMK3XNB.js.map → chunk-76AHNHWC.js.map} +0 -0
  99. /package/dist/{chunk-XWU7O3S5.js.map → chunk-C3VRBEJ7.js.map} +0 -0
  100. /package/dist/{chunk-HQMISQ7L.js.map → chunk-CELYD5QW.js.map} +0 -0
  101. /package/dist/{chunk-33QV5O6P.js.map → chunk-D3LSPFHK.js.map} +0 -0
  102. /package/dist/{chunk-4HNHHKJF.js.map → chunk-EZN2YXRY.js.map} +0 -0
  103. /package/dist/{chunk-BKDUCPZ3.js.map → chunk-GSOL2LW6.js.map} +0 -0
  104. /package/dist/{chunk-WLRHL2P7.js.map → chunk-LKJSPO3S.js.map} +0 -0
  105. /package/dist/{chunk-37EABIOJ.js.map → chunk-M4AG2YNX.js.map} +0 -0
  106. /package/dist/{chunk-QYHABRLM.js.map → chunk-SA23V7WX.js.map} +0 -0
  107. /package/dist/{chunk-3W2KMI5O.js.map → chunk-W4YMMWJL.js.map} +0 -0
  108. /package/dist/{chunk-2TJYZF4E.js.map → chunk-XEOAKD5C.js.map} +0 -0
  109. /package/dist/{client-N4XQEAVL.js.map → client-IA7ZPE56.js.map} +0 -0
  110. /package/dist/{config-KHKIY6LI.js.map → config-HPN2F7LH.js.map} +0 -0
  111. /package/dist/{config-mutator-ZZKFQ7HJ.js.map → config-mutator-7GPTMTVB.js.map} +0 -0
  112. /package/dist/{doctor-QICAXIQB.js.map → doctor-IBWQVQV4.js.map} +0 -0
  113. /package/dist/{program-ZWNGK3RB.js.map → program-G6DIFJUF.js.map} +0 -0
  114. /package/dist/{promote-H6ZHJXTY.js.map → promote-T4UJROEH.js.map} +0 -0
  115. /package/dist/{query-FTE4FLYK.js.map → query-VUJC4Y5O.js.map} +0 -0
  116. /package/dist/{registry-HDOFVNRK.js.map → registry-DX2RI7I7.js.map} +0 -0
  117. /package/dist/{request-planner-ABD4WIGM.js.map → request-planner-B7DE7VJR.js.map} +0 -0
  118. /package/dist/{review-candidates-B6574GOJ.js.map → review-candidates-4YJHIBAF.js.map} +0 -0
  119. /package/dist/{routing-BGKZOHOA.js.map → routing-MZG3UODH.js.map} +0 -0
  120. /package/dist/{schedule-tool-UDHXTKWV.js.map → schedule-tool-A4OFQ4YZ.js.map} +0 -0
  121. /package/dist/{scheduler-NQM3HVUY.js.map → scheduler-RHJW2V4M.js.map} +0 -0
  122. /package/dist/{seat-VGST5AQB.js.map → seat-2PEI7JRX.js.map} +0 -0
  123. /package/dist/{select-Y7SBUCGJ.js.map → select-4KIUR27O.js.map} +0 -0
  124. /package/dist/{set-enabled-ARKMDLXN.js.map → set-enabled-SE7HQKPF.js.map} +0 -0
  125. /package/dist/{wiki-GREI2TSP.js.map → subscriptions-RYVPHVIV.js.map} +0 -0
  126. /package/dist/{tool-5EVYUN2G.js.map → tool-5VMD7IE4.js.map} +0 -0
@@ -19,6 +19,11 @@ final class App: NSObject, NSApplicationDelegate {
19
19
  private let panel = Panel()
20
20
  private let statusMenu = StatusMenu()
21
21
  private let card = ResultCard()
22
+ private let orb = OrbOverlay()
23
+ /// What the listener said this turn, shown under the orb.
24
+ private var heardLine = ""
25
+ /// The agent whose by-name answer is on screen now, while no turn runs.
26
+ private var asideSpeaker: String?
22
27
  private let recorder = Recorder()
23
28
  private var hotkey: Hotkey?
24
29
  private var pasteHotkey: Hotkey?
@@ -47,6 +52,72 @@ final class App: NSObject, NSApplicationDelegate {
47
52
  private var followUp: String?
48
53
  /// "stop" while our own turn was running: drop its answer.
49
54
  private var abandoned = false
55
+ /// The agent our own turn is asking. Usually the target; "Writer, …"
56
+ /// sends one question to Writer and leaves the target alone.
57
+ private var turnAgent = ""
58
+ /// Questions in flight per agent: our turn plus any asides.
59
+ private var inFlight: [String: Int] = [:]
60
+ /// Bumped by every stop, so an aside answered after it stays quiet.
61
+ private var stopGeneration = 0
62
+ /// Questions for an agent that already has one in flight, oldest
63
+ /// first. One /ask per agent at a time: a second question waits here
64
+ /// for the first answer instead of racing it into the same session.
65
+ private var waitingAsides: [String: [String]] = [:]
66
+ /// Asides not yet finished: asked, answering, or waiting to be spoken.
67
+ /// While any are open the microphone does not reopen by itself, or it
68
+ /// would hear the next answer as the listener's words.
69
+ private var openAsides = 0
70
+
71
+ /// /ask, counted in flight for the menu while it thinks.
72
+ private func ask(_ heard: String, agent: String) async throws -> AgentClient.Answer {
73
+ track(agent, 1)
74
+ defer { track(agent, -1) }
75
+ return try await AgentClient.ask(heard, agent: agent)
76
+ }
77
+
78
+ private func track(_ agent: String, _ delta: Int) {
79
+ let n = inFlight[agent, default: 0] + delta
80
+ inFlight[agent] = n > 0 ? n : nil
81
+ publishThinking()
82
+ }
83
+
84
+ /// The menu's per-agent count: questions asked plus questions waiting.
85
+ private func publishThinking() {
86
+ var counts = inFlight
87
+ for (agent, words) in waitingAsides { counts[agent, default: 0] += words.count }
88
+ statusMenu.thinking = counts
89
+ }
90
+
91
+ /// Who the panel and orb name: a by-name answer being spoken, else the
92
+ /// agent our turn is asking, else the target.
93
+ private var shownAgent: String {
94
+ if let asideSpeaker { return asideSpeaker }
95
+ return busy && !turnAgent.isEmpty ? turnAgent : Config.effectiveAgentID
96
+ }
97
+
98
+ /// The orb follows the pill's state: listening, thinking or speaking
99
+ /// show it; idle and errors hide it.
100
+ private func renderOrb(_ state: Panel.State) {
101
+ guard Config.showOrb else { orb.hide(); return }
102
+ let who = shownAgent
103
+ let name = who.isEmpty ? "" : statusMenu.name(of: who)
104
+ let tint = statusMenu.color(of: who)
105
+ switch state {
106
+ case .idle, .error:
107
+ orb.hide()
108
+ case .listening:
109
+ heardLine = ""
110
+ orb.show(.listening, name: name, tint: tint, line: "Listening…", status: "")
111
+ case .thinking:
112
+ orb.show(.thinking, name: name, tint: tint, line: heardLine, status: "Thinking…")
113
+ case .working(let step, let secs):
114
+ orb.show(.thinking, name: name, tint: tint, line: heardLine, status: "\(step) · \(secs)s")
115
+ case .speaking:
116
+ orb.show(.speaking, name: name, tint: tint, line: heardLine, status: "")
117
+ case .saying(let text):
118
+ orb.show(.speaking, name: name, tint: tint, line: text, status: "")
119
+ }
120
+ }
50
121
 
51
122
  /// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
52
123
  private func smartPaste() {
@@ -94,12 +165,26 @@ final class App: NSObject, NSApplicationDelegate {
94
165
  func applicationDidFinishLaunching(_ note: Notification) {
95
166
  NSApp.setActivationPolicy(.accessory)
96
167
  retireOlderInstances()
97
- panel.onRender = { [weak self] state in self?.statusMenu.show(state) }
168
+ panel.onRender = { [weak self] state in
169
+ self?.statusMenu.show(state)
170
+ self?.renderOrb(state)
171
+ }
98
172
  panel.contextMenu = { [weak self] in self?.statusMenu.menu ?? NSMenu() }
99
173
  panel.agentName = { [weak self] in
100
- Config.effectiveAgentID.isEmpty ? "" : self?.statusMenu.name(of: Config.effectiveAgentID) ?? ""
174
+ guard let self else { return "" }
175
+ let who = self.shownAgent
176
+ return who.isEmpty ? "" : self.statusMenu.name(of: who)
101
177
  }
102
178
  panel.alwaysVisible = Config.showPill
179
+ panel.yieldsActiveStates = Config.showOrb
180
+ orb.levelSource = { [weak self] in self?.recorder.level ?? 0 }
181
+ card.anchor = { [weak self] in self?.orb.contentFrame }
182
+ statusMenu.onOrbChanged = { [weak self] on in
183
+ guard let self else { return }
184
+ self.panel.yieldsActiveStates = on
185
+ if !on { self.orb.hide() }
186
+ if !self.busy && !self.recorder.isRecording { self.panel.render(.idle) }
187
+ }
103
188
  panel.render(.idle)
104
189
  statusMenu.onPillChanged = { [weak self] on in
105
190
  guard let self else { return }
@@ -221,6 +306,7 @@ final class App: NSObject, NSApplicationDelegate {
221
306
  openedAt = Date()
222
307
  patience = followUp ? followUpPatience : clickPatience
223
308
  silentClose = followUp
309
+ asideSpeaker = nil
224
310
  panel.render(.listening)
225
311
 
226
312
  listenPoll?.invalidate()
@@ -283,6 +369,10 @@ final class App: NSObject, NSApplicationDelegate {
283
369
  private func stopSpeaking() {
284
370
  Log.info("stop: silencing every voice")
285
371
  if busy { silenced = true }
372
+ stopGeneration += 1
373
+ waitingAsides.removeAll()
374
+ publishThinking()
375
+ asideSpeaker = nil
286
376
  lastSpokeAt = Date()
287
377
  Task { await Speech.stopAll() }
288
378
  if !recorder.isRecording { panel.render(.idle) }
@@ -306,6 +396,7 @@ final class App: NSObject, NSApplicationDelegate {
306
396
  Log.info("door: opened\(busy ? " (a turn is running)" : "")")
307
397
  do {
308
398
  try recorder.start()
399
+ asideSpeaker = nil
309
400
  panel.render(.listening)
310
401
  } catch {
311
402
  panel.render(.error(error.localizedDescription))
@@ -345,6 +436,8 @@ final class App: NSObject, NSApplicationDelegate {
345
436
  return
346
437
  }
347
438
  Log.info("heard: \(heard)")
439
+ heardLine = heard
440
+ if orb.isVisible { orb.model.line = heard }
348
441
  let hushed = await door.value
349
442
  // Always through the door, even with nothing hushed: the
350
443
  // listener's turn is over, so the daemon's queue plays on.
@@ -354,17 +447,103 @@ final class App: NSObject, NSApplicationDelegate {
354
447
  if !midTurn { panel.render(.idle); busy = false }
355
448
  return
356
449
  }
450
+ // "Writer, …" sends this one question to Writer; the target stays.
451
+ let agent = await AgentClient.address(heard, target: Config.effectiveAgentID)
357
452
  if midTurn {
453
+ // Another agent: ask it alongside ours rather than waiting.
454
+ if agent != turnAgent && !Self.isStop(heard) {
455
+ askAside(heard, agent: agent)
456
+ panel.render(.working("Asked \(statusMenu.name(of: agent))", 0))
457
+ return
458
+ }
358
459
  // Our own turn is still thinking. The agent cannot change
359
460
  // course mid-turn, so his words go next and the stale
360
- // answer is not spoken; "stop" just drops it.
361
- if Self.isStop(heard) { abandoned = true; followUp = nil; Log.info("door: stop → dropping the turn in flight") }
362
- else { followUp = heard; Log.info("door: \"\(heard)\" → next, instead of the answer in flight") }
461
+ // answer is not spoken; "stop" just drops it, and every
462
+ // aside with it.
463
+ if Self.isStop(heard) {
464
+ abandoned = true; followUp = nil
465
+ stopGeneration += 1; waitingAsides.removeAll(); publishThinking()
466
+ Log.info("door: stop → dropping the turn in flight")
467
+ } else {
468
+ followUp = heard
469
+ Log.info("door: \"\(heard)\" → next, instead of the answer in flight")
470
+ }
363
471
  panel.render(.working("Got it — one moment", 0))
364
472
  return
365
473
  }
366
- await runTurn(heard)
474
+ // That agent is still answering an aside: this question waits
475
+ // behind it rather than running beside it.
476
+ if inFlight[agent] != nil && !Self.isStop(heard) {
477
+ askAside(heard, agent: agent)
478
+ panel.render(.working("Queued for \(statusMenu.name(of: agent))", 0))
479
+ busy = false; resetSoon()
480
+ return
481
+ }
482
+ await runTurn(heard, agent: agent)
483
+ }
484
+ }
485
+
486
+ /// A question for another agent while our own turn thinks. It runs
487
+ /// alongside; the answer waits its turn in the daemon's speaking queue,
488
+ /// so it never talks over ours. An agent already answering an aside
489
+ /// gets this one next, not at the same time.
490
+ private func askAside(_ heard: String, agent: String) {
491
+ if inFlight[agent] != nil || waitingAsides[agent] != nil {
492
+ waitingAsides[agent, default: []].append(heard)
493
+ publishThinking()
494
+ Log.info("aside → \(agent) (waits for its answer in flight): \(heard)")
495
+ return
496
+ }
497
+ runAside(heard, agent: agent)
498
+ }
499
+
500
+ private func runAside(_ heard: String, agent: String) {
501
+ let generation = stopGeneration
502
+ openAsides += 1
503
+ Log.info("aside → \(agent): \(heard)")
504
+ Task { @MainActor in
505
+ defer { openAsides -= 1 }
506
+ let result: Result<AgentClient.Answer, Error>
507
+ do { result = .success(try await ask(heard, agent: agent)) }
508
+ catch { result = .failure(error) }
509
+ // The next question for this agent goes now, while this
510
+ // answer waits to be spoken.
511
+ startNextAside(for: agent)
512
+ guard generation == stopGeneration else { return }
513
+ switch result {
514
+ case .success(let answer):
515
+ Log.info("aside answer (\(answer.agentID ?? agent)): \(answer.text)")
516
+ if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
517
+ buttons: answer.buttons, imageURL: answer.imageURL) {
518
+ card.show(spoken: answer.text, written: answer.written,
519
+ buttons: answer.buttons, imageURL: answer.imageURL)
520
+ }
521
+ // Nothing else on screen: the orb shows this answer, in
522
+ // this agent's colour, while it is spoken.
523
+ let onScreen = !busy && !recorder.isRecording
524
+ if onScreen { asideSpeaker = agent; panel.render(.saying(answer.text)) }
525
+ await Speech.say(answer.text, agentID: answer.agentID ?? agent, kind: "answer", voice: answer.voice)
526
+ if onScreen && asideSpeaker == agent {
527
+ asideSpeaker = nil
528
+ if !busy && !recorder.isRecording { panel.render(.idle) }
529
+ }
530
+ case .failure(let error):
531
+ Log.warn("aside failed (\(agent)): \(error.localizedDescription)")
532
+ await Speech.say("Sorry, \(statusMenu.name(of: agent)) couldn't answer that.", agentID: nil, kind: "line", voice: nil)
533
+ }
534
+ }
535
+ }
536
+
537
+ /// Send the oldest question waiting for `agent`, if any.
538
+ private func startNextAside(for agent: String) {
539
+ guard var waiting = waitingAsides[agent], !waiting.isEmpty else {
540
+ waitingAsides[agent] = nil
541
+ return
367
542
  }
543
+ let next = waiting.removeFirst()
544
+ waitingAsides[agent] = waiting.isEmpty ? nil : waiting
545
+ runAside(next, agent: agent)
546
+ publishThinking()
368
547
  }
369
548
 
370
549
  static func isStop(_ s: String) -> Bool {
@@ -374,12 +553,13 @@ final class App: NSObject, NSApplicationDelegate {
374
553
 
375
554
  /// One question and its spoken answer. Words said through the door
376
555
  /// while it thinks replace the answer with the next turn.
377
- private func runTurn(_ heard: String) async {
556
+ private func runTurn(_ heard: String, agent: String) async {
378
557
  silenced = false
558
+ turnAgent = agent
379
559
  do {
380
- beginNarration()
560
+ beginNarration(agent)
381
561
 
382
- let answer = try await AgentClient.ask(heard, agent: Config.effectiveAgentID)
562
+ let answer = try await ask(heard, agent: agent)
383
563
  endNarration()
384
564
  if abandoned {
385
565
  abandoned = false
@@ -390,7 +570,7 @@ final class App: NSObject, NSApplicationDelegate {
390
570
  followUp = nil
391
571
  Log.info("superseded answer (\(answer.agentID ?? "?")): \(answer.text)")
392
572
  panel.render(.thinking)
393
- return await runTurn(next)
573
+ return await runTurn(next, agent: agent)
394
574
  }
395
575
  if let v = answer.voice { voice = v }
396
576
  Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
@@ -419,7 +599,7 @@ final class App: NSObject, NSApplicationDelegate {
419
599
  if recorder.isRecording { busy = false; return }
420
600
  // Stopped: no follow-up window, the listener asked for quiet.
421
601
  if silenced { silenced = false; panel.render(.idle); busy = false; return }
422
- if let next = followUp { followUp = nil; return await runTurn(next) }
602
+ if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
423
603
  panel.render(.idle)
424
604
  // Leave the microphone open for a moment. Say nothing and
425
605
  // it closes itself; start talking and the conversation
@@ -428,12 +608,15 @@ final class App: NSObject, NSApplicationDelegate {
428
608
  // A live lesson now runs on screen and speaks for itself; an
429
609
  // open mic would hear the agent. Option-Space is the door.
430
610
  if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
611
+ // Another agent's answer is still to come; an open mic would
612
+ // take it for the listener's words.
613
+ if openAsides > 0 { return }
431
614
  listenHandsFree(followUp: true)
432
615
  } catch {
433
616
  endNarration()
434
617
  Log.warn("turn failed: \(error.localizedDescription)")
435
618
  if abandoned { abandoned = false; panel.render(.idle); busy = false; return }
436
- if let next = followUp { followUp = nil; return await runTurn(next) }
619
+ if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
437
620
  panel.render(.error(short(error.localizedDescription)))
438
621
  // Say it aloud too — a voice assistant that fails only in
439
622
  // a 230px label has failed silently for anyone not looking.
@@ -449,7 +632,7 @@ final class App: NSObject, NSApplicationDelegate {
449
632
  /// say what the agent is doing but arrive irregularly, and a 1-second
450
633
  /// ticker proves the thing is still alive between them. Together they
451
634
  /// answer both "what is it doing" and "is it stuck".
452
- private func beginNarration() {
635
+ private func beginNarration(_ agent: String) {
453
636
  startedAt = Date()
454
637
  lastStep = "Thinking…"
455
638
  spokenSteps.removeAll()
@@ -458,7 +641,7 @@ final class App: NSObject, NSApplicationDelegate {
458
641
 
459
642
  // Progress delivers on its own serial queue; hop to main before
460
643
  // touching any view.
461
- progress = Progress(agentID: Config.effectiveAgentID) { [weak self] step, voice in
644
+ progress = Progress(agentID: agent) { [weak self] step, voice in
462
645
  Task { @MainActor in
463
646
  guard let self, self.busy else { return }
464
647
  if let voice { self.voice = voice }
@@ -18,7 +18,7 @@ rm -rf build && mkdir -p "$BIN" "$APP/Contents/Resources"
18
18
  swiftc -O \
19
19
  -o "$BIN/AgentXVoice" \
20
20
  Sources/AgentXVoice/*.swift \
21
- -framework AppKit -framework AVFoundation -framework Carbon \
21
+ -framework AppKit -framework AVFoundation -framework Carbon -framework SwiftUI \
22
22
  -target arm64-apple-macosx14.0
23
23
 
24
24
  cp Resources/Info.plist "$APP/Contents/Info.plist"
@@ -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-QX4R5WY7.js";import"./chunk-4FFWNPFX.js";import"./chunk-QIJLFPWE.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-WOMQ2VGA.js";import"./chunk-U4H35SJS.js";import"./chunk-QIJLFPWE.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-VRTLAPRF.js.map
8
+ //# sourceMappingURL=App-4EM7IPB3.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-QX4R5WY7.js";import"./chunk-4FFWNPFX.js";import"./chunk-QIJLFPWE.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-WOMQ2VGA.js";import"./chunk-U4H35SJS.js";import"./chunk-QIJLFPWE.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-YENWPZT4.js.map
7
+ //# sourceMappingURL=ChatApp-FYTEEGSU.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-KLDGJSR5.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-SZJMS7K4.js";import"./chunk-7I7IEX4S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-QIJLFPWE.js";export{a as createAgentContext,b as generate,c as generateStream};
2
- //# sourceMappingURL=agent-HVUGHYBF.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-6AK6WKE5.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-SZJMS7K4.js";import"./chunk-7I7IEX4S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-QIJLFPWE.js";export{a as createAgentContext,b as generate,c as generateStream};
2
+ //# sourceMappingURL=agent-RZ46ZB4W.js.map