agentix-cli 0.53.0 → 0.55.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 (148) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +26 -0
  2. package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +55 -3
  3. package/apps/mac-voice/Sources/AgentXVoice/main.swift +136 -13
  4. package/dist/{App-R62ZQVSF.js → App-4EM7IPB3.js} +2 -2
  5. package/dist/{ChatApp-AS567EFE.js → ChatApp-FYTEEGSU.js} +2 -2
  6. package/dist/{agent-H4NB4BUA.js → agent-RZ46ZB4W.js} +2 -2
  7. package/dist/{board-dashboard-E3MUVNVQ.js → board-dashboard-NRRYCDZW.js} +1097 -215
  8. package/dist/board-dashboard-NRRYCDZW.js.map +1 -0
  9. package/dist/chunk-2D3RYIDU.js +3 -0
  10. package/dist/chunk-2D3RYIDU.js.map +1 -0
  11. package/dist/{chunk-KB7YIM3W.js → chunk-4HEVSWO3.js} +2 -2
  12. package/dist/{chunk-7DPKPBRJ.js → chunk-6AK6WKE5.js} +2 -2
  13. package/dist/{chunk-TH5KNJLA.js → chunk-723K4SAW.js} +2 -2
  14. package/dist/{chunk-OMTRHHZJ.js → chunk-76AHNHWC.js} +44 -45
  15. package/dist/chunk-76AHNHWC.js.map +1 -0
  16. package/dist/{chunk-KJWBQZT7.js → chunk-C3VRBEJ7.js} +2 -2
  17. package/dist/{chunk-C5JXPMTW.js → chunk-CELYD5QW.js} +2 -2
  18. package/dist/{chunk-FSZXY53Z.js → chunk-D3LSPFHK.js} +2 -2
  19. package/dist/chunk-EJ5ISSWW.js +151 -0
  20. package/dist/chunk-EJ5ISSWW.js.map +1 -0
  21. package/dist/{chunk-FS4WBYY3.js → chunk-EZN2YXRY.js} +2 -2
  22. package/dist/{chunk-QU6ZUKAD.js → chunk-GSOL2LW6.js} +2 -2
  23. package/dist/{chunk-7OWXFLAM.js → chunk-JC5Q5MPI.js} +171 -164
  24. package/dist/chunk-JC5Q5MPI.js.map +1 -0
  25. package/dist/{chunk-SABOPOYX.js → chunk-K4RDE6YU.js} +211 -197
  26. package/dist/chunk-K4RDE6YU.js.map +1 -0
  27. package/dist/{ntfy-ZY2NPGBA.js → chunk-KZKDVIOE.js} +3 -3
  28. package/dist/chunk-KZKDVIOE.js.map +1 -0
  29. package/dist/{chunk-VD4TMPJI.js → chunk-LKJSPO3S.js} +2 -2
  30. package/dist/{chunk-NF52Q3TV.js → chunk-M4AG2YNX.js} +2 -2
  31. package/dist/{chunk-WKTFP3SK.js → chunk-NO2HOK4B.js} +2 -2
  32. package/dist/{chunk-KFG6WWFS.js → chunk-O3XG2NVR.js} +18 -18
  33. package/dist/{chunk-KFG6WWFS.js.map → chunk-O3XG2NVR.js.map} +1 -1
  34. package/dist/chunk-QD6IICK7.js +2 -0
  35. package/dist/chunk-QD6IICK7.js.map +1 -0
  36. package/dist/{chunk-PGJL5V6Y.js → chunk-SA23V7WX.js} +2 -2
  37. package/dist/chunk-U4H35SJS.js +15 -0
  38. package/dist/chunk-U4H35SJS.js.map +1 -0
  39. package/dist/chunk-UIVHSYCN.js +29 -0
  40. package/dist/chunk-UIVHSYCN.js.map +1 -0
  41. package/dist/{chunk-6OOK7Y4S.js → chunk-W4YMMWJL.js} +2 -2
  42. package/dist/chunk-WOMQ2VGA.js +5 -0
  43. package/dist/{chunk-OV35MJKS.js.map → chunk-WOMQ2VGA.js.map} +1 -1
  44. package/dist/chunk-WWMFNLKD.js +10 -0
  45. package/dist/chunk-WWMFNLKD.js.map +1 -0
  46. package/dist/chunk-X5C5W3E4.js +3 -0
  47. package/dist/chunk-X5C5W3E4.js.map +1 -0
  48. package/dist/{chunk-CCMM5TP4.js → chunk-XEOAKD5C.js} +2 -2
  49. package/dist/cli.js +1 -1
  50. package/dist/cli.js.map +1 -1
  51. package/dist/client-IA7ZPE56.js +2 -0
  52. package/dist/{compaction-NWPBPWY2.js → compaction-H5U32UU6.js} +2 -2
  53. package/dist/config-HPN2F7LH.js +2 -0
  54. package/dist/config-mutator-7GPTMTVB.js +2 -0
  55. package/dist/doctor-IBWQVQV4.js +2 -0
  56. package/dist/index.d.ts +273 -19
  57. package/dist/index.js +1 -1
  58. package/dist/index.js.map +1 -1
  59. package/dist/memory-extract-F76BE6KA.js +2 -0
  60. package/dist/ntfy-52EXHVMD.js +2 -0
  61. package/dist/program-D5TAPSIH.js +2 -0
  62. package/dist/{promote-DJFMTLRC.js → promote-T4UJROEH.js} +2 -2
  63. package/dist/push-HEDA6P3B.js +2 -0
  64. package/dist/push-HEDA6P3B.js.map +1 -0
  65. package/dist/push-keys-2DTD7YNQ.js +2 -0
  66. package/dist/push-store-LXTYULUF.js +2 -0
  67. package/dist/query-VUJC4Y5O.js +2 -0
  68. package/dist/registry-DX2RI7I7.js +2 -0
  69. package/dist/{request-planner-2H7HCUCZ.js → request-planner-B7DE7VJR.js} +2 -2
  70. package/dist/{review-candidates-U7R64OCS.js → review-candidates-4YJHIBAF.js} +2 -2
  71. package/dist/routing-MZG3UODH.js +2 -0
  72. package/dist/schedule-tool-A4OFQ4YZ.js +2 -0
  73. package/dist/scheduler-RHJW2V4M.js +2 -0
  74. package/dist/scheduler-RHJW2V4M.js.map +1 -0
  75. package/dist/seat-2PEI7JRX.js +2 -0
  76. package/dist/seat-2PEI7JRX.js.map +1 -0
  77. package/dist/{select-XGK27RPP.js → select-4KIUR27O.js} +2 -2
  78. package/dist/{set-enabled-7YGN2FAZ.js → set-enabled-SE7HQKPF.js} +2 -2
  79. package/dist/subscriptions-RYVPHVIV.js +2 -0
  80. package/dist/subscriptions-RYVPHVIV.js.map +1 -0
  81. package/dist/{tool-HKDIZ6I4.js → tool-5VMD7IE4.js} +2 -2
  82. package/dist/wiki-2QFLAH4U.js +2 -0
  83. package/dist/wiki-2QFLAH4U.js.map +1 -0
  84. package/package.json +3 -1
  85. package/dist/board-dashboard-E3MUVNVQ.js.map +0 -1
  86. package/dist/chunk-47EEUZBK.js +0 -15
  87. package/dist/chunk-47EEUZBK.js.map +0 -1
  88. package/dist/chunk-7OWXFLAM.js.map +0 -1
  89. package/dist/chunk-D4HAR3L3.js +0 -28
  90. package/dist/chunk-D4HAR3L3.js.map +0 -1
  91. package/dist/chunk-HRBKSZ34.js +0 -2
  92. package/dist/chunk-HRBKSZ34.js.map +0 -1
  93. package/dist/chunk-IDYP7G7Z.js +0 -150
  94. package/dist/chunk-IDYP7G7Z.js.map +0 -1
  95. package/dist/chunk-OMTRHHZJ.js.map +0 -1
  96. package/dist/chunk-OV35MJKS.js +0 -5
  97. package/dist/chunk-SABOPOYX.js.map +0 -1
  98. package/dist/client-XTQDYMOX.js +0 -2
  99. package/dist/config-3SW4V44Z.js +0 -2
  100. package/dist/config-mutator-H32FXUYJ.js +0 -2
  101. package/dist/doctor-J6RCHFVE.js +0 -2
  102. package/dist/memory-extract-MGGXIZTE.js +0 -2
  103. package/dist/ntfy-ZY2NPGBA.js.map +0 -1
  104. package/dist/program-SQTOPCQW.js +0 -2
  105. package/dist/query-UAZUMU4N.js +0 -2
  106. package/dist/registry-4OUBA2TY.js +0 -2
  107. package/dist/routing-LVQH6WOJ.js +0 -2
  108. package/dist/schedule-tool-2BONJVCJ.js +0 -2
  109. package/dist/scheduler-7CEXDECA.js +0 -2
  110. package/dist/seat-2KGFNCIW.js +0 -2
  111. package/dist/wiki-6YT7B5HU.js +0 -2
  112. /package/dist/{App-R62ZQVSF.js.map → App-4EM7IPB3.js.map} +0 -0
  113. /package/dist/{ChatApp-AS567EFE.js.map → ChatApp-FYTEEGSU.js.map} +0 -0
  114. /package/dist/{agent-H4NB4BUA.js.map → agent-RZ46ZB4W.js.map} +0 -0
  115. /package/dist/{chunk-KB7YIM3W.js.map → chunk-4HEVSWO3.js.map} +0 -0
  116. /package/dist/{chunk-7DPKPBRJ.js.map → chunk-6AK6WKE5.js.map} +0 -0
  117. /package/dist/{chunk-TH5KNJLA.js.map → chunk-723K4SAW.js.map} +0 -0
  118. /package/dist/{chunk-KJWBQZT7.js.map → chunk-C3VRBEJ7.js.map} +0 -0
  119. /package/dist/{chunk-C5JXPMTW.js.map → chunk-CELYD5QW.js.map} +0 -0
  120. /package/dist/{chunk-FSZXY53Z.js.map → chunk-D3LSPFHK.js.map} +0 -0
  121. /package/dist/{chunk-FS4WBYY3.js.map → chunk-EZN2YXRY.js.map} +0 -0
  122. /package/dist/{chunk-QU6ZUKAD.js.map → chunk-GSOL2LW6.js.map} +0 -0
  123. /package/dist/{chunk-VD4TMPJI.js.map → chunk-LKJSPO3S.js.map} +0 -0
  124. /package/dist/{chunk-NF52Q3TV.js.map → chunk-M4AG2YNX.js.map} +0 -0
  125. /package/dist/{chunk-WKTFP3SK.js.map → chunk-NO2HOK4B.js.map} +0 -0
  126. /package/dist/{chunk-PGJL5V6Y.js.map → chunk-SA23V7WX.js.map} +0 -0
  127. /package/dist/{chunk-6OOK7Y4S.js.map → chunk-W4YMMWJL.js.map} +0 -0
  128. /package/dist/{chunk-CCMM5TP4.js.map → chunk-XEOAKD5C.js.map} +0 -0
  129. /package/dist/{client-XTQDYMOX.js.map → client-IA7ZPE56.js.map} +0 -0
  130. /package/dist/{compaction-NWPBPWY2.js.map → compaction-H5U32UU6.js.map} +0 -0
  131. /package/dist/{config-3SW4V44Z.js.map → config-HPN2F7LH.js.map} +0 -0
  132. /package/dist/{config-mutator-H32FXUYJ.js.map → config-mutator-7GPTMTVB.js.map} +0 -0
  133. /package/dist/{doctor-J6RCHFVE.js.map → doctor-IBWQVQV4.js.map} +0 -0
  134. /package/dist/{memory-extract-MGGXIZTE.js.map → memory-extract-F76BE6KA.js.map} +0 -0
  135. /package/dist/{program-SQTOPCQW.js.map → ntfy-52EXHVMD.js.map} +0 -0
  136. /package/dist/{promote-DJFMTLRC.js.map → program-D5TAPSIH.js.map} +0 -0
  137. /package/dist/{query-UAZUMU4N.js.map → promote-T4UJROEH.js.map} +0 -0
  138. /package/dist/{registry-4OUBA2TY.js.map → push-keys-2DTD7YNQ.js.map} +0 -0
  139. /package/dist/{routing-LVQH6WOJ.js.map → push-store-LXTYULUF.js.map} +0 -0
  140. /package/dist/{schedule-tool-2BONJVCJ.js.map → query-VUJC4Y5O.js.map} +0 -0
  141. /package/dist/{scheduler-7CEXDECA.js.map → registry-DX2RI7I7.js.map} +0 -0
  142. /package/dist/{request-planner-2H7HCUCZ.js.map → request-planner-B7DE7VJR.js.map} +0 -0
  143. /package/dist/{review-candidates-U7R64OCS.js.map → review-candidates-4YJHIBAF.js.map} +0 -0
  144. /package/dist/{seat-2KGFNCIW.js.map → routing-MZG3UODH.js.map} +0 -0
  145. /package/dist/{wiki-6YT7B5HU.js.map → schedule-tool-A4OFQ4YZ.js.map} +0 -0
  146. /package/dist/{select-XGK27RPP.js.map → select-4KIUR27O.js.map} +0 -0
  147. /package/dist/{set-enabled-7YGN2FAZ.js.map → set-enabled-SE7HQKPF.js.map} +0 -0
  148. /package/dist/{tool-HKDIZ6I4.js.map → tool-5VMD7IE4.js.map} +0 -0
@@ -126,6 +126,32 @@ enum AgentClient {
126
126
  return (200..<300).contains(http.statusCode)
127
127
  }
128
128
 
129
+ /// Who is speaking and who waits, from the daemon's speaking queue.
130
+ struct QueueState: Decodable {
131
+ struct Item: Decodable { let agentId: String? }
132
+ let playing: Item?
133
+ let waiting: [Item]
134
+ }
135
+
136
+ /// The speaking queue now, or nil when the daemon cannot be reached.
137
+ static func queueState() async -> QueueState? {
138
+ guard let url = URL(string: "\(Config.daemonURL)/voice/queue") else { return nil }
139
+ var req = URLRequest(url: url)
140
+ req.timeoutInterval = 2
141
+ guard let (data, _) = try? await URLSession.shared.data(for: req) else { return nil }
142
+ return try? JSONDecoder().decode(QueueState.self, from: data)
143
+ }
144
+
145
+ /// The agent the words are addressed to ("Writer, …"), or `target`.
146
+ /// The daemon matches names, so the widget and config never disagree.
147
+ /// Never throws: no daemon means nobody else to address.
148
+ static func address(_ text: String, target: String) async -> String {
149
+ guard let (data, _) = try? await post("/voice/address", ["text": text, "target": target], timeout: 2) else { return target }
150
+ struct Reply: Decodable { let agentId: String? }
151
+ let id = (try? JSONDecoder().decode(Reply.self, from: data))?.agentId ?? ""
152
+ return id.isEmpty ? target : id
153
+ }
154
+
129
155
  private static func post(_ path: String, _ body: [String: Any], timeout: TimeInterval) async throws -> (Data, URLResponse) {
130
156
  var req = URLRequest(url: URL(string: "\(Config.daemonURL)\(path)")!)
131
157
  req.httpMethod = "POST"
@@ -7,7 +7,8 @@ import AppKit
7
7
  @MainActor
8
8
  final class StatusMenu: NSObject, NSMenuDelegate {
9
9
  let menu = NSMenu()
10
- private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
10
+ /// Variable width: the badge with the number of queued lines sits beside the icon.
11
+ private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
11
12
 
12
13
  private enum Roster {
13
14
  case loading
@@ -19,6 +20,14 @@ final class StatusMenu: NSObject, NSMenuDelegate {
19
20
  private var roster = Roster.loading
20
21
  /// Reopen the menu once the retry's answer is in.
21
22
  private var reopenAfterRefresh = false
23
+ /// The daemon's speaking queue, polled while anything is in flight.
24
+ private var queue: AgentClient.QueueState?
25
+ private var queuePoll: Timer?
26
+
27
+ /// Questions this widget has in flight, per agent. Set by the app.
28
+ var thinking: [String: Int] = [:] {
29
+ didSet { rebuild(); watchQueue(force: true) }
30
+ }
22
31
 
23
32
  /// Set by the app.
24
33
  var onStop: (() -> Void)?
@@ -48,7 +57,9 @@ final class StatusMenu: NSObject, NSMenuDelegate {
48
57
  Task {
49
58
  let agents = await AgentClient.agents()
50
59
  roster = agents.map { .loaded($0) } ?? .down
60
+ queue = await AgentClient.queueState()
51
61
  rebuild()
62
+ showBadge()
52
63
  if reopenAfterRefresh { reopenAfterRefresh = false; open() }
53
64
  }
54
65
  }
@@ -69,6 +80,48 @@ final class StatusMenu: NSObject, NSMenuDelegate {
69
80
  item.button?.toolTip = "AgentX Voice — \(label)"
70
81
  }
71
82
 
83
+ /// The number of lines waiting to be spoken, beside the icon.
84
+ private func showBadge() {
85
+ let n = queue?.waiting.count ?? 0
86
+ item.button?.imagePosition = .imageLeading
87
+ item.button?.title = n > 0 ? "\(n)" : ""
88
+ }
89
+
90
+ /// Poll the queue only while there is something to show: a question in
91
+ /// flight, or lines playing or waiting. `force` starts it whatever the
92
+ /// last poll saw: an answer that just came back is about to be queued,
93
+ /// so the poll carries on until it has been seen through.
94
+ private func watchQueue(force: Bool = false) {
95
+ let active = force || !thinking.isEmpty || queue?.playing != nil || !(queue?.waiting.isEmpty ?? true)
96
+ if !active { queuePoll?.invalidate(); queuePoll = nil; return }
97
+ guard queuePoll == nil else { return }
98
+ queuePoll = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
99
+ Task { @MainActor [weak self] in
100
+ guard let self else { return }
101
+ self.queue = await AgentClient.queueState()
102
+ self.rebuild()
103
+ self.showBadge()
104
+ self.watchQueue()
105
+ }
106
+ }
107
+ }
108
+
109
+ /// "thinking", "speaking", "queued 2", or both, for one agent's row.
110
+ private func state(of agent: AgentClient.AgentInfo) -> String {
111
+ var parts: [String] = []
112
+ // Speaking one answer and thinking on the next are both true at once.
113
+ if queue?.playing?.agentId == agent.id { parts.append("speaking") }
114
+ let asked = thinking[agent.id, default: 0]
115
+ if asked > 0 {
116
+ // More than one: the rest wait for the answer in flight.
117
+ parts.append(asked > 1 ? "thinking (+\(asked - 1) asked)" : "thinking")
118
+ }
119
+ if parts.isEmpty && (agent.active ?? 0) > 0 { parts.append("working") }
120
+ let queued = queue?.waiting.filter { $0.agentId == agent.id }.count ?? 0
121
+ if queued > 0 { parts.append("queued \(queued)") }
122
+ return parts.isEmpty ? "idle" : parts.joined(separator: " · ")
123
+ }
124
+
72
125
  // MARK: NSMenuDelegate
73
126
 
74
127
  /// Show what is known now and refresh underneath; the open menu
@@ -95,9 +148,8 @@ final class StatusMenu: NSObject, NSMenuDelegate {
95
148
  menu.addItem(NSMenuItem.sectionHeader(title: pinned ? "Agent (set by AGENTX_VOICE_AGENT)" : "Talk to"))
96
149
  if agents.isEmpty { menu.addItem(disabled("No agents configured")) }
97
150
  for (i, agent) in agents.enumerated() {
98
- let busy = (agent.active ?? 0) > 0
99
151
  // Digits pick an agent while the menu is open.
100
- let row = action(agent.label + (busy ? " · working" : " · idle"), #selector(pick(_:)),
152
+ let row = action(agent.label + " · " + state(of: agent), #selector(pick(_:)),
101
153
  key: i < 9 ? "\(i + 1)" : "", modifiers: [])
102
154
  row.representedObject = agent.id
103
155
  row.state = agent.id == target ? .on : .off
@@ -47,6 +47,41 @@ final class App: NSObject, NSApplicationDelegate {
47
47
  private var followUp: String?
48
48
  /// "stop" while our own turn was running: drop its answer.
49
49
  private var abandoned = false
50
+ /// The agent our own turn is asking. Usually the target; "Writer, …"
51
+ /// sends one question to Writer and leaves the target alone.
52
+ private var turnAgent = ""
53
+ /// Questions in flight per agent: our turn plus any asides.
54
+ private var inFlight: [String: Int] = [:]
55
+ /// Bumped by every stop, so an aside answered after it stays quiet.
56
+ private var stopGeneration = 0
57
+ /// Questions for an agent that already has one in flight, oldest
58
+ /// first. One /ask per agent at a time: a second question waits here
59
+ /// for the first answer instead of racing it into the same session.
60
+ private var waitingAsides: [String: [String]] = [:]
61
+ /// Asides not yet finished: asked, answering, or waiting to be spoken.
62
+ /// While any are open the microphone does not reopen by itself, or it
63
+ /// would hear the next answer as the listener's words.
64
+ private var openAsides = 0
65
+
66
+ /// /ask, counted in flight for the menu while it thinks.
67
+ private func ask(_ heard: String, agent: String) async throws -> AgentClient.Answer {
68
+ track(agent, 1)
69
+ defer { track(agent, -1) }
70
+ return try await AgentClient.ask(heard, agent: agent)
71
+ }
72
+
73
+ private func track(_ agent: String, _ delta: Int) {
74
+ let n = inFlight[agent, default: 0] + delta
75
+ inFlight[agent] = n > 0 ? n : nil
76
+ publishThinking()
77
+ }
78
+
79
+ /// The menu's per-agent count: questions asked plus questions waiting.
80
+ private func publishThinking() {
81
+ var counts = inFlight
82
+ for (agent, words) in waitingAsides { counts[agent, default: 0] += words.count }
83
+ statusMenu.thinking = counts
84
+ }
50
85
 
51
86
  /// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
52
87
  private func smartPaste() {
@@ -97,7 +132,10 @@ final class App: NSObject, NSApplicationDelegate {
97
132
  panel.onRender = { [weak self] state in self?.statusMenu.show(state) }
98
133
  panel.contextMenu = { [weak self] in self?.statusMenu.menu ?? NSMenu() }
99
134
  panel.agentName = { [weak self] in
100
- Config.effectiveAgentID.isEmpty ? "" : self?.statusMenu.name(of: Config.effectiveAgentID) ?? ""
135
+ guard let self else { return "" }
136
+ // A question sent by name shows who is answering it, not the target.
137
+ let who = self.busy && !self.turnAgent.isEmpty ? self.turnAgent : Config.effectiveAgentID
138
+ return who.isEmpty ? "" : self.statusMenu.name(of: who)
101
139
  }
102
140
  panel.alwaysVisible = Config.showPill
103
141
  panel.render(.idle)
@@ -283,6 +321,9 @@ final class App: NSObject, NSApplicationDelegate {
283
321
  private func stopSpeaking() {
284
322
  Log.info("stop: silencing every voice")
285
323
  if busy { silenced = true }
324
+ stopGeneration += 1
325
+ waitingAsides.removeAll()
326
+ publishThinking()
286
327
  lastSpokeAt = Date()
287
328
  Task { await Speech.stopAll() }
288
329
  if !recorder.isRecording { panel.render(.idle) }
@@ -354,17 +395,95 @@ final class App: NSObject, NSApplicationDelegate {
354
395
  if !midTurn { panel.render(.idle); busy = false }
355
396
  return
356
397
  }
398
+ // "Writer, …" sends this one question to Writer; the target stays.
399
+ let agent = await AgentClient.address(heard, target: Config.effectiveAgentID)
357
400
  if midTurn {
401
+ // Another agent: ask it alongside ours rather than waiting.
402
+ if agent != turnAgent && !Self.isStop(heard) {
403
+ askAside(heard, agent: agent)
404
+ panel.render(.working("Asked \(statusMenu.name(of: agent))", 0))
405
+ return
406
+ }
358
407
  // Our own turn is still thinking. The agent cannot change
359
408
  // 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") }
409
+ // answer is not spoken; "stop" just drops it, and every
410
+ // aside with it.
411
+ if Self.isStop(heard) {
412
+ abandoned = true; followUp = nil
413
+ stopGeneration += 1; waitingAsides.removeAll(); publishThinking()
414
+ Log.info("door: stop → dropping the turn in flight")
415
+ } else {
416
+ followUp = heard
417
+ Log.info("door: \"\(heard)\" → next, instead of the answer in flight")
418
+ }
363
419
  panel.render(.working("Got it — one moment", 0))
364
420
  return
365
421
  }
366
- await runTurn(heard)
422
+ // That agent is still answering an aside: this question waits
423
+ // behind it rather than running beside it.
424
+ if inFlight[agent] != nil && !Self.isStop(heard) {
425
+ askAside(heard, agent: agent)
426
+ panel.render(.working("Queued for \(statusMenu.name(of: agent))", 0))
427
+ busy = false; resetSoon()
428
+ return
429
+ }
430
+ await runTurn(heard, agent: agent)
431
+ }
432
+ }
433
+
434
+ /// A question for another agent while our own turn thinks. It runs
435
+ /// alongside; the answer waits its turn in the daemon's speaking queue,
436
+ /// so it never talks over ours. An agent already answering an aside
437
+ /// gets this one next, not at the same time.
438
+ private func askAside(_ heard: String, agent: String) {
439
+ if inFlight[agent] != nil || waitingAsides[agent] != nil {
440
+ waitingAsides[agent, default: []].append(heard)
441
+ publishThinking()
442
+ Log.info("aside → \(agent) (waits for its answer in flight): \(heard)")
443
+ return
444
+ }
445
+ runAside(heard, agent: agent)
446
+ }
447
+
448
+ private func runAside(_ heard: String, agent: String) {
449
+ let generation = stopGeneration
450
+ openAsides += 1
451
+ Log.info("aside → \(agent): \(heard)")
452
+ Task { @MainActor in
453
+ defer { openAsides -= 1 }
454
+ let result: Result<AgentClient.Answer, Error>
455
+ do { result = .success(try await ask(heard, agent: agent)) }
456
+ catch { result = .failure(error) }
457
+ // The next question for this agent goes now, while this
458
+ // answer waits to be spoken.
459
+ startNextAside(for: agent)
460
+ guard generation == stopGeneration else { return }
461
+ switch result {
462
+ case .success(let answer):
463
+ Log.info("aside answer (\(answer.agentID ?? agent)): \(answer.text)")
464
+ if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
465
+ buttons: answer.buttons, imageURL: answer.imageURL) {
466
+ card.show(spoken: answer.text, written: answer.written,
467
+ buttons: answer.buttons, imageURL: answer.imageURL)
468
+ }
469
+ await Speech.say(answer.text, agentID: answer.agentID ?? agent, kind: "answer", voice: answer.voice)
470
+ case .failure(let error):
471
+ Log.warn("aside failed (\(agent)): \(error.localizedDescription)")
472
+ await Speech.say("Sorry, \(statusMenu.name(of: agent)) couldn't answer that.", agentID: nil, kind: "line", voice: nil)
473
+ }
474
+ }
475
+ }
476
+
477
+ /// Send the oldest question waiting for `agent`, if any.
478
+ private func startNextAside(for agent: String) {
479
+ guard var waiting = waitingAsides[agent], !waiting.isEmpty else {
480
+ waitingAsides[agent] = nil
481
+ return
367
482
  }
483
+ let next = waiting.removeFirst()
484
+ waitingAsides[agent] = waiting.isEmpty ? nil : waiting
485
+ runAside(next, agent: agent)
486
+ publishThinking()
368
487
  }
369
488
 
370
489
  static func isStop(_ s: String) -> Bool {
@@ -374,12 +493,13 @@ final class App: NSObject, NSApplicationDelegate {
374
493
 
375
494
  /// One question and its spoken answer. Words said through the door
376
495
  /// while it thinks replace the answer with the next turn.
377
- private func runTurn(_ heard: String) async {
496
+ private func runTurn(_ heard: String, agent: String) async {
378
497
  silenced = false
498
+ turnAgent = agent
379
499
  do {
380
- beginNarration()
500
+ beginNarration(agent)
381
501
 
382
- let answer = try await AgentClient.ask(heard, agent: Config.effectiveAgentID)
502
+ let answer = try await ask(heard, agent: agent)
383
503
  endNarration()
384
504
  if abandoned {
385
505
  abandoned = false
@@ -390,7 +510,7 @@ final class App: NSObject, NSApplicationDelegate {
390
510
  followUp = nil
391
511
  Log.info("superseded answer (\(answer.agentID ?? "?")): \(answer.text)")
392
512
  panel.render(.thinking)
393
- return await runTurn(next)
513
+ return await runTurn(next, agent: agent)
394
514
  }
395
515
  if let v = answer.voice { voice = v }
396
516
  Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
@@ -419,7 +539,7 @@ final class App: NSObject, NSApplicationDelegate {
419
539
  if recorder.isRecording { busy = false; return }
420
540
  // Stopped: no follow-up window, the listener asked for quiet.
421
541
  if silenced { silenced = false; panel.render(.idle); busy = false; return }
422
- if let next = followUp { followUp = nil; return await runTurn(next) }
542
+ if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
423
543
  panel.render(.idle)
424
544
  // Leave the microphone open for a moment. Say nothing and
425
545
  // it closes itself; start talking and the conversation
@@ -428,12 +548,15 @@ final class App: NSObject, NSApplicationDelegate {
428
548
  // A live lesson now runs on screen and speaks for itself; an
429
549
  // open mic would hear the agent. Option-Space is the door.
430
550
  if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
551
+ // Another agent's answer is still to come; an open mic would
552
+ // take it for the listener's words.
553
+ if openAsides > 0 { return }
431
554
  listenHandsFree(followUp: true)
432
555
  } catch {
433
556
  endNarration()
434
557
  Log.warn("turn failed: \(error.localizedDescription)")
435
558
  if abandoned { abandoned = false; panel.render(.idle); busy = false; return }
436
- if let next = followUp { followUp = nil; return await runTurn(next) }
559
+ if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
437
560
  panel.render(.error(short(error.localizedDescription)))
438
561
  // Say it aloud too — a voice assistant that fails only in
439
562
  // a 230px label has failed silently for anyone not looking.
@@ -449,7 +572,7 @@ final class App: NSObject, NSApplicationDelegate {
449
572
  /// say what the agent is doing but arrive irregularly, and a 1-second
450
573
  /// ticker proves the thing is still alive between them. Together they
451
574
  /// answer both "what is it doing" and "is it stuck".
452
- private func beginNarration() {
575
+ private func beginNarration(_ agent: String) {
453
576
  startedAt = Date()
454
577
  lastStep = "Thinking…"
455
578
  spokenSteps.removeAll()
@@ -458,7 +581,7 @@ final class App: NSObject, NSApplicationDelegate {
458
581
 
459
582
  // Progress delivers on its own serial queue; hop to main before
460
583
  // touching any view.
461
- progress = Progress(agentID: Config.effectiveAgentID) { [weak self] step, voice in
584
+ progress = Progress(agentID: agent) { [weak self] step, voice in
462
585
  Task { @MainActor in
463
586
  guard let self, self.busy else { return }
464
587
  if let voice { self.voice = voice }
@@ -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-OV35MJKS.js";import"./chunk-47EEUZBK.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-R62ZQVSF.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-OV35MJKS.js";import"./chunk-47EEUZBK.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-AS567EFE.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-7DPKPBRJ.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-H4NB4BUA.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