agentix-cli 0.56.0 → 0.57.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 (111) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +43 -0
  2. package/apps/mac-voice/Sources/AgentXVoice/Hotkey.swift +8 -2
  3. package/apps/mac-voice/Sources/AgentXVoice/HotkeySpec.swift +80 -0
  4. package/apps/mac-voice/Sources/AgentXVoice/SettingsWindow.swift +518 -0
  5. package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +8 -2
  6. package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +4 -1
  7. package/apps/mac-voice/Sources/AgentXVoice/VoiceSettings.swift +101 -0
  8. package/apps/mac-voice/Sources/AgentXVoice/main.swift +100 -38
  9. package/dist/{App-4EM7IPB3.js → App-Z5VOAUXB.js} +2 -2
  10. package/dist/{ChatApp-FYTEEGSU.js → ChatApp-BE7ONQKS.js} +2 -2
  11. package/dist/{agent-RZ46ZB4W.js → agent-Q74EISKP.js} +2 -2
  12. package/dist/{board-dashboard-NRRYCDZW.js → board-dashboard-A7PQOI6C.js} +4 -4
  13. package/dist/{chunk-W4YMMWJL.js → chunk-2MIOMYT2.js} +2 -2
  14. package/dist/{chunk-EZN2YXRY.js → chunk-547G3HYQ.js} +2 -2
  15. package/dist/{chunk-LKJSPO3S.js → chunk-5LLGIX5M.js} +2 -2
  16. package/dist/{chunk-WOMQ2VGA.js → chunk-5W7NJHCS.js} +2 -2
  17. package/dist/{chunk-4HEVSWO3.js → chunk-6OABTCRA.js} +2 -2
  18. package/dist/{chunk-UIVHSYCN.js → chunk-6QNPCVRN.js} +3 -3
  19. package/dist/chunk-6QNPCVRN.js.map +1 -0
  20. package/dist/{chunk-CELYD5QW.js → chunk-CQUVUVXR.js} +2 -2
  21. package/dist/chunk-ERL6AS3O.js +15 -0
  22. package/dist/chunk-ERL6AS3O.js.map +1 -0
  23. package/dist/{chunk-EJ5ISSWW.js → chunk-I2LYULR4.js} +5 -5
  24. package/dist/{chunk-76AHNHWC.js → chunk-KKGFZEXS.js} +2 -2
  25. package/dist/{chunk-6AK6WKE5.js → chunk-KW7TIOS3.js} +2 -2
  26. package/dist/{chunk-SA23V7WX.js → chunk-NKMFHEGV.js} +2 -2
  27. package/dist/{chunk-WKMTT2UY.js → chunk-OZZPJ2YG.js} +197 -188
  28. package/dist/chunk-OZZPJ2YG.js.map +1 -0
  29. package/dist/{chunk-IL2XLW4H.js → chunk-POL7RAJQ.js} +13 -13
  30. package/dist/{chunk-D3LSPFHK.js → chunk-Q6SMLSRO.js} +2 -2
  31. package/dist/{chunk-723K4SAW.js → chunk-RXEAACVH.js} +2 -2
  32. package/dist/{chunk-O3XG2NVR.js → chunk-U5YBQJ5Q.js} +2 -2
  33. package/dist/{chunk-XEOAKD5C.js → chunk-V7ZK6ZIO.js} +2 -2
  34. package/dist/{chunk-M4AG2YNX.js → chunk-VI7GBDKV.js} +2 -2
  35. package/dist/{chunk-GSOL2LW6.js → chunk-VVBGPT6W.js} +2 -2
  36. package/dist/{chunk-C3VRBEJ7.js → chunk-XTD3SZIP.js} +2 -2
  37. package/dist/cli.js +1 -1
  38. package/dist/client-EFP2PKA3.js +2 -0
  39. package/dist/config-P6YXC6XZ.js +2 -0
  40. package/dist/config-mutator-3GKXLTHH.js +2 -0
  41. package/dist/doctor-JEWMHXYH.js +2 -0
  42. package/dist/index.d.ts +109 -3
  43. package/dist/index.js +1 -1
  44. package/dist/program-7DUQJDSQ.js +2 -0
  45. package/dist/{promote-T4UJROEH.js → promote-KVPLDSKT.js} +2 -2
  46. package/dist/query-AGOMCYTT.js +2 -0
  47. package/dist/{registry-DX2RI7I7.js → registry-AUOI4TW2.js} +2 -2
  48. package/dist/{request-planner-B7DE7VJR.js → request-planner-LMZ5ZAXC.js} +2 -2
  49. package/dist/{review-candidates-4YJHIBAF.js → review-candidates-WCBXLLDS.js} +2 -2
  50. package/dist/{routing-MZG3UODH.js → routing-FBASYKUL.js} +2 -2
  51. package/dist/schedule-tool-RJRJ63SS.js +2 -0
  52. package/dist/scheduler-ATA7Z44S.js +2 -0
  53. package/dist/{seat-2PEI7JRX.js → seat-DBFW3Z5P.js} +2 -2
  54. package/dist/{select-4KIUR27O.js → select-6CNVVFA5.js} +2 -2
  55. package/dist/{set-enabled-SE7HQKPF.js → set-enabled-AYZH3KSB.js} +2 -2
  56. package/dist/{tool-5VMD7IE4.js → tool-PKS4NBZ6.js} +2 -2
  57. package/dist/wiki-QW6BDNEQ.js +2 -0
  58. package/package.json +1 -1
  59. package/dist/chunk-U4H35SJS.js +0 -15
  60. package/dist/chunk-U4H35SJS.js.map +0 -1
  61. package/dist/chunk-UIVHSYCN.js.map +0 -1
  62. package/dist/chunk-WKMTT2UY.js.map +0 -1
  63. package/dist/client-IA7ZPE56.js +0 -2
  64. package/dist/config-HPN2F7LH.js +0 -2
  65. package/dist/config-mutator-7GPTMTVB.js +0 -2
  66. package/dist/doctor-IBWQVQV4.js +0 -2
  67. package/dist/program-G6DIFJUF.js +0 -2
  68. package/dist/query-VUJC4Y5O.js +0 -2
  69. package/dist/schedule-tool-A4OFQ4YZ.js +0 -2
  70. package/dist/scheduler-RHJW2V4M.js +0 -2
  71. package/dist/wiki-2QFLAH4U.js +0 -2
  72. /package/dist/{App-4EM7IPB3.js.map → App-Z5VOAUXB.js.map} +0 -0
  73. /package/dist/{ChatApp-FYTEEGSU.js.map → ChatApp-BE7ONQKS.js.map} +0 -0
  74. /package/dist/{agent-RZ46ZB4W.js.map → agent-Q74EISKP.js.map} +0 -0
  75. /package/dist/{board-dashboard-NRRYCDZW.js.map → board-dashboard-A7PQOI6C.js.map} +0 -0
  76. /package/dist/{chunk-W4YMMWJL.js.map → chunk-2MIOMYT2.js.map} +0 -0
  77. /package/dist/{chunk-EZN2YXRY.js.map → chunk-547G3HYQ.js.map} +0 -0
  78. /package/dist/{chunk-LKJSPO3S.js.map → chunk-5LLGIX5M.js.map} +0 -0
  79. /package/dist/{chunk-WOMQ2VGA.js.map → chunk-5W7NJHCS.js.map} +0 -0
  80. /package/dist/{chunk-4HEVSWO3.js.map → chunk-6OABTCRA.js.map} +0 -0
  81. /package/dist/{chunk-CELYD5QW.js.map → chunk-CQUVUVXR.js.map} +0 -0
  82. /package/dist/{chunk-EJ5ISSWW.js.map → chunk-I2LYULR4.js.map} +0 -0
  83. /package/dist/{chunk-76AHNHWC.js.map → chunk-KKGFZEXS.js.map} +0 -0
  84. /package/dist/{chunk-6AK6WKE5.js.map → chunk-KW7TIOS3.js.map} +0 -0
  85. /package/dist/{chunk-SA23V7WX.js.map → chunk-NKMFHEGV.js.map} +0 -0
  86. /package/dist/{chunk-IL2XLW4H.js.map → chunk-POL7RAJQ.js.map} +0 -0
  87. /package/dist/{chunk-D3LSPFHK.js.map → chunk-Q6SMLSRO.js.map} +0 -0
  88. /package/dist/{chunk-723K4SAW.js.map → chunk-RXEAACVH.js.map} +0 -0
  89. /package/dist/{chunk-O3XG2NVR.js.map → chunk-U5YBQJ5Q.js.map} +0 -0
  90. /package/dist/{chunk-XEOAKD5C.js.map → chunk-V7ZK6ZIO.js.map} +0 -0
  91. /package/dist/{chunk-M4AG2YNX.js.map → chunk-VI7GBDKV.js.map} +0 -0
  92. /package/dist/{chunk-GSOL2LW6.js.map → chunk-VVBGPT6W.js.map} +0 -0
  93. /package/dist/{chunk-C3VRBEJ7.js.map → chunk-XTD3SZIP.js.map} +0 -0
  94. /package/dist/{client-IA7ZPE56.js.map → client-EFP2PKA3.js.map} +0 -0
  95. /package/dist/{config-HPN2F7LH.js.map → config-P6YXC6XZ.js.map} +0 -0
  96. /package/dist/{config-mutator-7GPTMTVB.js.map → config-mutator-3GKXLTHH.js.map} +0 -0
  97. /package/dist/{doctor-IBWQVQV4.js.map → doctor-JEWMHXYH.js.map} +0 -0
  98. /package/dist/{program-G6DIFJUF.js.map → program-7DUQJDSQ.js.map} +0 -0
  99. /package/dist/{promote-T4UJROEH.js.map → promote-KVPLDSKT.js.map} +0 -0
  100. /package/dist/{query-VUJC4Y5O.js.map → query-AGOMCYTT.js.map} +0 -0
  101. /package/dist/{registry-DX2RI7I7.js.map → registry-AUOI4TW2.js.map} +0 -0
  102. /package/dist/{request-planner-B7DE7VJR.js.map → request-planner-LMZ5ZAXC.js.map} +0 -0
  103. /package/dist/{review-candidates-4YJHIBAF.js.map → review-candidates-WCBXLLDS.js.map} +0 -0
  104. /package/dist/{routing-MZG3UODH.js.map → routing-FBASYKUL.js.map} +0 -0
  105. /package/dist/{schedule-tool-A4OFQ4YZ.js.map → schedule-tool-RJRJ63SS.js.map} +0 -0
  106. /package/dist/{scheduler-RHJW2V4M.js.map → scheduler-ATA7Z44S.js.map} +0 -0
  107. /package/dist/{seat-2PEI7JRX.js.map → seat-DBFW3Z5P.js.map} +0 -0
  108. /package/dist/{select-4KIUR27O.js.map → select-6CNVVFA5.js.map} +0 -0
  109. /package/dist/{set-enabled-SE7HQKPF.js.map → set-enabled-AYZH3KSB.js.map} +0 -0
  110. /package/dist/{tool-5VMD7IE4.js.map → tool-PKS4NBZ6.js.map} +0 -0
  111. /package/dist/{wiki-2QFLAH4U.js.map → wiki-QW6BDNEQ.js.map} +0 -0
@@ -37,8 +37,14 @@ struct VoiceChoice: Decodable {
37
37
  enum Speech {
38
38
  // MARK: Speech to text
39
39
 
40
- static func transcribe(wav: Data) async throws -> String {
41
- if let key = Config.elevenLabsKey {
40
+ /// `engine` is agentx.json's `voice.stt`: "auto" and "elevenlabs" use
41
+ /// ElevenLabs when there is a key, falling back to mlx-whisper; "local"
42
+ /// never sends audio off the Mac.
43
+ static func transcribe(wav: Data, engine: String = "auto") async throws -> String {
44
+ if engine == "elevenlabs" && Config.elevenLabsKey == nil {
45
+ Log.warn("speech to text is set to ElevenLabs but no key is set; using mlx-whisper")
46
+ }
47
+ if engine != "local", let key = Config.elevenLabsKey {
42
48
  do { return try await elevenLabsSTT(wav: wav, key: key) }
43
49
  catch { Log.warn("ElevenLabs STT failed (\(error.localizedDescription)); falling back to mlx-whisper") }
44
50
  }
@@ -35,6 +35,7 @@ final class StatusMenu: NSObject, NSMenuDelegate {
35
35
  var onTargetChanged: ((String) -> Void)?
36
36
  var onPillChanged: ((Bool) -> Void)?
37
37
  var onOrbChanged: ((Bool) -> Void)?
38
+ var onSettings: (() -> Void)?
38
39
 
39
40
  override init() {
40
41
  super.init()
@@ -183,6 +184,7 @@ final class StatusMenu: NSObject, NSMenuDelegate {
183
184
 
184
185
  menu.addItem(.separator())
185
186
  menu.addItem(action("Settings…", #selector(openSettings), key: ","))
187
+ menu.addItem(action("Dashboard…", #selector(openDashboardHome), key: "d"))
186
188
  menu.addItem(action("History…", #selector(openHistory), key: "y"))
187
189
  menu.addItem(.separator())
188
190
  menu.addItem(NSMenuItem(title: "Quit AgentX Voice",
@@ -234,7 +236,8 @@ final class StatusMenu: NSObject, NSMenuDelegate {
234
236
  onOrbChanged?(Config.showOrb)
235
237
  }
236
238
 
237
- @objc private func openSettings() { openDashboard("/admin") }
239
+ @objc private func openSettings() { onSettings?() }
240
+ @objc private func openDashboardHome() { openDashboard("/admin") }
238
241
  @objc private func openHistory() { openDashboard("/activity") }
239
242
 
240
243
  private func openDashboard(_ path: String) {
@@ -0,0 +1,101 @@
1
+ import Foundation
2
+
3
+ /// What GET /voice/settings returns, edited in the settings window and
4
+ /// sent back as the difference. Foundation only, so the tests can build it.
5
+ struct VoiceSettings: Codable, Equatable {
6
+ struct Hotkeys: Codable, Equatable {
7
+ var talk: String
8
+ var stop: String
9
+ var paste: String
10
+ }
11
+ struct General: Codable, Equatable {
12
+ var provider: String
13
+ var fallback: String
14
+ var stt: String
15
+ var hotkeys: Hotkeys
16
+ }
17
+ struct Voice: Codable, Equatable {
18
+ var provider: String?
19
+ var system: String?
20
+ var systemPerLanguage: Bool
21
+ var elevenlabsVoiceId: String?
22
+ var rate: Double?
23
+ var narrate: String?
24
+ var priority: String?
25
+ var hotkey: String?
26
+ }
27
+ struct Speaks: Codable, Equatable {
28
+ let provider: String
29
+ let systemVoice: String?
30
+ }
31
+ struct Agent: Codable, Equatable, Identifiable {
32
+ let id: String
33
+ let name: String
34
+ var color: String
35
+ var colorSet: Bool
36
+ var voice: Voice
37
+ let speaks: Speaks
38
+ }
39
+ struct SystemVoice: Codable, Equatable, Identifiable {
40
+ let id: String
41
+ let label: String
42
+ let locale: String
43
+ }
44
+
45
+ var general: General
46
+ var agents: [Agent]
47
+ let systemVoices: [SystemVoice]
48
+ let menuHotkey: String
49
+
50
+ /// The change from `old` to this, as POST /voice/settings takes it:
51
+ /// only what differs, and NSNull for a field put back to its default.
52
+ func patch(from old: VoiceSettings) -> [String: Any] {
53
+ var out: [String: Any] = [:]
54
+
55
+ var general: [String: Any] = [:]
56
+ if self.general.provider != old.general.provider { general["provider"] = self.general.provider }
57
+ if self.general.stt != old.general.stt { general["stt"] = self.general.stt }
58
+ var keys: [String: Any] = [:]
59
+ if self.general.hotkeys.talk != old.general.hotkeys.talk { keys["talk"] = self.general.hotkeys.talk }
60
+ if self.general.hotkeys.stop != old.general.hotkeys.stop { keys["stop"] = self.general.hotkeys.stop }
61
+ if self.general.hotkeys.paste != old.general.hotkeys.paste { keys["paste"] = self.general.hotkeys.paste }
62
+ if !keys.isEmpty { general["hotkeys"] = keys }
63
+ if !general.isEmpty { out["general"] = general }
64
+
65
+ var agents: [String: Any] = [:]
66
+ for agent in self.agents {
67
+ guard let before = old.agents.first(where: { $0.id == agent.id }) else { continue }
68
+ var a: [String: Any] = [:]
69
+ func field<T: Equatable>(_ name: String, _ now: T?, _ was: T?) {
70
+ if now != was { a[name] = now.map { $0 as Any } ?? NSNull() }
71
+ }
72
+ field("provider", agent.voice.provider, before.voice.provider)
73
+ field("system", agent.voice.system, before.voice.system)
74
+ field("elevenlabsVoiceId", agent.voice.elevenlabsVoiceId.flatMap { $0.isEmpty ? nil : $0 },
75
+ before.voice.elevenlabsVoiceId)
76
+ field("rate", agent.voice.rate, before.voice.rate)
77
+ field("narrate", agent.voice.narrate, before.voice.narrate)
78
+ field("priority", agent.voice.priority, before.voice.priority)
79
+ field("hotkey", agent.voice.hotkey, before.voice.hotkey)
80
+ if agent.colorSet != before.colorSet || (agent.colorSet && agent.color != before.color) {
81
+ a["color"] = agent.colorSet ? agent.color as Any : NSNull()
82
+ }
83
+ if !a.isEmpty { agents[agent.id] = a }
84
+ }
85
+ if !agents.isEmpty { out["agents"] = agents }
86
+ return out
87
+ }
88
+
89
+ /// The fields a preview speaks with: the draft's voice for one agent.
90
+ func previewVoice(for id: String) -> [String: Any] {
91
+ guard let v = agents.first(where: { $0.id == id })?.voice else { return [:] }
92
+ var out: [String: Any] = [
93
+ "provider": v.provider.map { $0 as Any } ?? NSNull(),
94
+ "elevenlabsVoiceId": v.elevenlabsVoiceId.flatMap { $0.isEmpty ? nil : $0 }.map { $0 as Any } ?? NSNull(),
95
+ "rate": v.rate.map { $0 as Any } ?? NSNull(),
96
+ ]
97
+ // A per-language list is not shown in the window; leave it be.
98
+ if !v.systemPerLanguage { out["system"] = v.system.map { $0 as Any } ?? NSNull() }
99
+ return out
100
+ }
101
+ }
@@ -29,6 +29,14 @@ final class App: NSObject, NSApplicationDelegate {
29
29
  private var pasteHotkey: Hotkey?
30
30
  private var stopHotkey: Hotkey?
31
31
  private var menuHotkey: Hotkey?
32
+ /// Hold to ask one agent, from agents[].voice.hotkey.
33
+ private var agentHotkeys: [Hotkey] = []
34
+ private let settingsWindow = SettingsWindow()
35
+ /// The saved settings: shortcuts and the speech-to-text engine. Read
36
+ /// from the daemon at launch and after every save; defaults until then.
37
+ private var settings: VoiceSettings?
38
+ /// The agent a per-agent shortcut is asking; nil for the talk key.
39
+ private var forcedAgent: String?
32
40
  /// Set by a stop: the answer being spoken ends without reopening the mic.
33
41
  private var silenced = false
34
42
  private var busy = false
@@ -88,9 +96,79 @@ final class App: NSObject, NSApplicationDelegate {
88
96
  statusMenu.thinking = counts
89
97
  }
90
98
 
91
- /// Who the panel and orb name: a by-name answer being spoken, else the
92
- /// agent our turn is asking, else the target.
99
+ // --- Shortcuts ---
100
+ //
101
+ // Talk, stop and smart paste come from agentx.json (voice.hotkeys),
102
+ // and each agent may have its own (agents[].voice.hotkey). ⌘⌥A, which
103
+ // opens the menu, is fixed. Saved in the settings window, they are
104
+ // registered again at once: no restart.
105
+
106
+ private func registerHotkeys() {
107
+ unregisterHotkeys()
108
+ let keys = settings?.general.hotkeys
109
+ func spec(_ text: String?, _ fallback: String) -> HotkeySpec {
110
+ text.flatMap(HotkeySpec.init) ?? HotkeySpec(fallback)!
111
+ }
112
+ let register = { (key: Hotkey, spec: HotkeySpec, what: String) in
113
+ if !key.register(spec) { Log.warn("shortcut \(spec.text ?? "?") for \(what) is taken by another app") }
114
+ }
115
+
116
+ // Hold to talk; release sends.
117
+ let talk = Hotkey(id: 1,
118
+ onPress: { [weak self] in self?.forcedAgent = nil; self?.startListening() },
119
+ onRelease: { [weak self] in self?.stopAndSend() })
120
+ register(talk, spec(keys?.talk, "opt+space"), "talk")
121
+ hotkey = talk
122
+
123
+ // Smart paste fires on RELEASE so the modifiers are up before cmd-V
124
+ // is sent; pressed while they are held it is a different chord.
125
+ let paste = Hotkey(id: 2, onPress: {}, onRelease: { [weak self] in self?.smartPaste() })
126
+ register(paste, spec(keys?.paste, "cmd+opt+v"), "smart paste")
127
+ pasteHotkey = paste
128
+
129
+ // Stop every voice now. ⌘. is the Mac's own "cancel"; Option keeps
130
+ // it from reaching the app in front.
131
+ let stop = Hotkey(id: 3, onPress: {}, onRelease: { [weak self] in self?.stopSpeaking() })
132
+ register(stop, spec(keys?.stop, "cmd+opt+period"), "stop")
133
+ stopHotkey = stop
134
+
135
+ // Open the menu-bar menu without the mouse.
136
+ let menu = Hotkey(id: 4, onPress: {}, onRelease: { [weak self] in self?.statusMenu.open() })
137
+ register(menu, HotkeySpec("cmd+opt+a")!, "the menu")
138
+ menuHotkey = menu
139
+
140
+ // Hold an agent's own shortcut to ask it; the target stays.
141
+ for (i, agent) in (settings?.agents ?? []).enumerated() {
142
+ guard let text = agent.voice.hotkey, let s = HotkeySpec(text) else { continue }
143
+ let id = agent.id
144
+ let key = Hotkey(id: UInt32(10 + i),
145
+ onPress: { [weak self] in self?.forcedAgent = id; self?.startListening() },
146
+ onRelease: { [weak self] in self?.stopAndSend() })
147
+ register(key, s, "asking \(agent.name)")
148
+ agentHotkeys.append(key)
149
+ }
150
+ }
151
+
152
+ /// Dropping a Hotkey unregisters it.
153
+ private func unregisterHotkeys() {
154
+ hotkey = nil; pasteHotkey = nil; stopHotkey = nil; menuHotkey = nil
155
+ agentHotkeys.removeAll()
156
+ }
157
+
158
+ /// Settings from the daemon: shortcuts now, the rest on next use.
159
+ private func apply(_ saved: VoiceSettings) {
160
+ settings = saved
161
+ if settingsWindow.model.recording == nil { registerHotkeys() }
162
+ // Colours may have changed.
163
+ statusMenu.refresh()
164
+ Log.info("settings: talk \(saved.general.hotkeys.talk), speech to text \(saved.general.stt)")
165
+ }
166
+
167
+ /// Who the panel and orb name: the agent a shortcut is asking, a
168
+ /// by-name answer being spoken, else the agent our turn is asking,
169
+ /// else the target.
93
170
  private var shownAgent: String {
171
+ if let forcedAgent, recorder.isRecording { return forcedAgent }
94
172
  if let asideSpeaker { return asideSpeaker }
95
173
  return busy && !turnAgent.isEmpty ? turnAgent : Config.effectiveAgentID
96
174
  }
@@ -199,12 +277,19 @@ final class App: NSObject, NSApplicationDelegate {
199
277
  }
200
278
  statusMenu.refresh()
201
279
 
202
- hotkey = Hotkey(
203
- id: 1,
204
- onPress: { [weak self] in self?.startListening() },
205
- onRelease: { [weak self] in self?.stopAndSend() })
206
- hotkey?.register()
280
+ registerHotkeys()
207
281
  panel.onClick = { [weak self] in self?.toggleListening() }
282
+ statusMenu.onSettings = { [weak self] in self?.settingsWindow.show() }
283
+ settingsWindow.model.onSaved = { [weak self] saved in self?.apply(saved) }
284
+ // While a shortcut field waits for keys, ours stand aside so the
285
+ // keys reach the field.
286
+ settingsWindow.model.onRecording = { [weak self] recording in
287
+ guard let self else { return }
288
+ if recording { self.unregisterHotkeys() } else { self.registerHotkeys() }
289
+ }
290
+ Task { @MainActor in
291
+ if let saved = await AgentClient.settings() { apply(saved) }
292
+ }
208
293
 
209
294
  // Turning the hold OFF is the moment anything that piled up
210
295
  // becomes welcome. Without this the queue is a hole rather than a
@@ -217,36 +302,8 @@ final class App: NSObject, NSApplicationDelegate {
217
302
  Log.info(nowOn ? "notifications held" : "notifications delivering")
218
303
  }
219
304
 
220
- // ⌘⌥V: reshape the clipboard for wherever the caret is, then paste.
221
- // Fires on RELEASE so the modifiers are up before cmd-V is sent —
222
- // pressing it while ⌘⌥ are still held produces a different chord
223
- // in the destination app.
224
- pasteHotkey = Hotkey(
225
- id: 2,
226
- onPress: {},
227
- onRelease: { [weak self] in self?.smartPaste() })
228
- pasteHotkey?.register(keyCode: UInt32(kVK_ANSI_V),
229
- modifiers: UInt32(cmdKey | optionKey))
230
-
231
- // ⌘⌥. (and the menu): stop every voice now. ⌘. is the Mac's
232
- // own "cancel"; Option keeps it from reaching the app in front.
233
- stopHotkey = Hotkey(
234
- id: 3,
235
- onPress: {},
236
- onRelease: { [weak self] in self?.stopSpeaking() })
237
- stopHotkey?.register(keyCode: UInt32(kVK_ANSI_Period),
238
- modifiers: UInt32(cmdKey | optionKey))
239
305
  statusMenu.onStop = { [weak self] in self?.stopSpeaking() }
240
306
 
241
- // ⌘⌥A: open the menu-bar menu without the mouse; arrows, digits
242
- // and Return work from there.
243
- menuHotkey = Hotkey(
244
- id: 4,
245
- onPress: {},
246
- onRelease: { [weak self] in self?.statusMenu.open() })
247
- menuHotkey?.register(keyCode: UInt32(kVK_ANSI_A),
248
- modifiers: UInt32(cmdKey | optionKey))
249
-
250
307
  // The target does not wait for the microphone: the menu shows it
251
308
  // either way.
252
309
  Task { @MainActor in
@@ -409,6 +466,8 @@ final class App: NSObject, NSApplicationDelegate {
409
466
  // hush now; whatever was talking gets the words, same as the key.
410
467
  let door = talkCheck ?? Task { await AgentClient.hush() }
411
468
  talkCheck = nil
469
+ let forced = forcedAgent
470
+ forcedAgent = nil
412
471
  guard let wav = recorder.stop() else {
413
472
  // Nothing said, so no door follows the hush: let the queue play on.
414
473
  Task { _ = await door.value; await AgentClient.resume() }
@@ -422,7 +481,7 @@ final class App: NSObject, NSApplicationDelegate {
422
481
 
423
482
  Task { @MainActor in
424
483
  var heard = ""
425
- do { heard = try await Speech.transcribe(wav: wav) }
484
+ do { heard = try await Speech.transcribe(wav: wav, engine: settings?.general.stt ?? "auto") }
426
485
  catch { Log.warn("transcription failed: \(error.localizedDescription)") }
427
486
  guard !heard.isEmpty else {
428
487
  _ = await door.value
@@ -447,8 +506,11 @@ final class App: NSObject, NSApplicationDelegate {
447
506
  if !midTurn { panel.render(.idle); busy = false }
448
507
  return
449
508
  }
450
- // "Writer, …" sends this one question to Writer; the target stays.
451
- let agent = await AgentClient.address(heard, target: Config.effectiveAgentID)
509
+ // "Writer, …" sends this one question to Writer; the target
510
+ // stays. An agent's own shortcut already said who.
511
+ let agent: String
512
+ if let forced { agent = forced }
513
+ else { agent = await AgentClient.address(heard, target: Config.effectiveAgentID) }
452
514
  if midTurn {
453
515
  // Another agent: ask it alongside ours rather than waiting.
454
516
  if agent != turnAgent && !Self.isStop(heard) {
@@ -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-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(`
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-5W7NJHCS.js";import"./chunk-ERL6AS3O.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-4EM7IPB3.js.map
8
+ //# sourceMappingURL=App-Z5VOAUXB.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-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(`
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-5W7NJHCS.js";import"./chunk-ERL6AS3O.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-FYTEEGSU.js.map
7
+ //# sourceMappingURL=ChatApp-BE7ONQKS.js.map
@@ -1,2 +1,2 @@
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
1
+ import{A as a,B as b,C as c}from"./chunk-KW7TIOS3.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-Q74EISKP.js.map