agentix-cli 0.136.0 → 0.137.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 (136) hide show
  1. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +11 -1
  2. package/apps/mac-voice/Sources/AgentXVoice/Character.swift +18 -5
  3. package/apps/mac-voice/Sources/AgentXVoice/Floor.swift +46 -0
  4. package/apps/mac-voice/Sources/AgentXVoice/FloorWatcher.swift +78 -0
  5. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +12 -1
  6. package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +8 -0
  7. package/apps/mac-voice/Sources/AgentXVoice/main.swift +89 -10
  8. package/dist/{App-D6W6IV2L.js → App-MCYZBCMQ.js} +2 -2
  9. package/dist/{ChatApp-X7TNCFGW.js → ChatApp-7YXDEW3H.js} +2 -2
  10. package/dist/{absorb-eval-COPVH2M2.js → absorb-eval-L72I7PKM.js} +2 -2
  11. package/dist/{agent-VCGAUBER.js → agent-TPREVZGH.js} +2 -2
  12. package/dist/{board-dashboard-JZM3FQNV.js → board-dashboard-3VBZAKYM.js} +4 -4
  13. package/dist/{board-dashboard-JZM3FQNV.js.map → board-dashboard-3VBZAKYM.js.map} +1 -1
  14. package/dist/{chunk-ZPJVDX2U.js → chunk-264762O7.js} +2 -2
  15. package/dist/{chunk-C43E3YFT.js → chunk-44N5HXYE.js} +2 -2
  16. package/dist/{chunk-EVTTAM7S.js → chunk-5CEBOUBG.js} +2 -2
  17. package/dist/{chunk-HZZW35FY.js → chunk-5EIO6S3S.js} +2 -2
  18. package/dist/{chunk-QTDKNJXD.js → chunk-64ZACYOF.js} +245 -245
  19. package/dist/chunk-64ZACYOF.js.map +1 -0
  20. package/dist/chunk-6BWVJ7UJ.js +54 -0
  21. package/dist/chunk-6BWVJ7UJ.js.map +1 -0
  22. package/dist/{chunk-5X37CXJF.js → chunk-APIANBXV.js} +2 -2
  23. package/dist/{chunk-5J5USW2O.js → chunk-AYYEY5UJ.js} +2 -2
  24. package/dist/{chunk-WRJFKA43.js → chunk-DX6RYYGP.js} +2 -2
  25. package/dist/{chunk-FQLSV2C4.js → chunk-EFUPLTQ3.js} +2 -2
  26. package/dist/{chunk-HEJTE7OC.js → chunk-ETOOT5KD.js} +2 -2
  27. package/dist/{chunk-PV623BLN.js → chunk-GW4LKNQT.js} +3 -3
  28. package/dist/{chunk-OW5EXDVY.js → chunk-HSX5LMXD.js} +2 -2
  29. package/dist/{chunk-KZXQD6ZK.js → chunk-I5QKJH7V.js} +2 -2
  30. package/dist/{chunk-M5B4QG6J.js → chunk-JXIMGK7Z.js} +2 -2
  31. package/dist/chunk-KEJGWFGS.js +105 -0
  32. package/dist/chunk-KEJGWFGS.js.map +1 -0
  33. package/dist/{chunk-66DPMVUC.js → chunk-KOKPK3OL.js} +2 -2
  34. package/dist/{chunk-K75LFQE3.js → chunk-LGM22AOB.js} +2 -2
  35. package/dist/{chunk-VA24JS2S.js → chunk-OLZL32KG.js} +2 -2
  36. package/dist/{chunk-H2N7DOPT.js → chunk-P6PTY2NP.js} +2 -2
  37. package/dist/{chunk-H2N7DOPT.js.map → chunk-P6PTY2NP.js.map} +1 -1
  38. package/dist/{chunk-M5AHI2PZ.js → chunk-PLCK4ZAG.js} +2 -2
  39. package/dist/{chunk-QCUJTAAZ.js → chunk-PYR7GKK7.js} +5 -5
  40. package/dist/{chunk-QCUJTAAZ.js.map → chunk-PYR7GKK7.js.map} +1 -1
  41. package/dist/{chunk-GL3H7XMI.js → chunk-QKILE72V.js} +2 -2
  42. package/dist/{chunk-AK3XOA5N.js → chunk-QQ67J76O.js} +2 -2
  43. package/dist/{chunk-IPMXIPTS.js → chunk-RX2ISTNT.js} +2 -2
  44. package/dist/{chunk-HH7OFAEM.js → chunk-ZX2NJGIU.js} +2 -2
  45. package/dist/{chunk-HH7OFAEM.js.map → chunk-ZX2NJGIU.js.map} +1 -1
  46. package/dist/cli.js +1 -1
  47. package/dist/{client-SMOWSRJY.js → client-G2QA4TCG.js} +2 -2
  48. package/dist/{config-OGUDWYAA.js → config-OD5W6IQE.js} +2 -2
  49. package/dist/{config-mutate-BH27TMWK.js → config-mutate-JEXLMR2L.js} +2 -2
  50. package/dist/config-mutator-4FKROX3O.js +2 -0
  51. package/dist/{contributions-H6T65TCY.js → contributions-UXLHUMF2.js} +2 -2
  52. package/dist/{doctor-ZOXHT7ZQ.js → doctor-DO2WN4TS.js} +2 -2
  53. package/dist/{follow-up-tool-GUBBXNEZ.js → follow-up-tool-QPNJD3HR.js} +2 -2
  54. package/dist/{hub-IIW2GBO5.js → hub-RFE766AM.js} +2 -2
  55. package/dist/index.d.ts +69 -36
  56. package/dist/index.js +1 -1
  57. package/dist/{program-WROQ55F5.js → program-QENG5KI7.js} +208 -208
  58. package/dist/program-QENG5KI7.js.map +1 -0
  59. package/dist/{promote-HDLU44MX.js → promote-FJ3EK2J6.js} +2 -2
  60. package/dist/{query-6G27CBZQ.js → query-KYA6YYUU.js} +2 -2
  61. package/dist/{query-settings-G4M75LVQ.js → query-settings-P3UOGMAQ.js} +2 -2
  62. package/dist/{registry-3RE5SG22.js → registry-U4S4XXUZ.js} +2 -2
  63. package/dist/{request-planner-T3VCZVVQ.js → request-planner-SYVDD4PZ.js} +2 -2
  64. package/dist/{review-candidates-C3O3CENL.js → review-candidates-QUIX4AK7.js} +2 -2
  65. package/dist/{routing-LD35TJM5.js → routing-MDZOHYYH.js} +2 -2
  66. package/dist/rules-UVUJTVOE.js +24 -0
  67. package/dist/rules-UVUJTVOE.js.map +1 -0
  68. package/dist/schedule-tool-DKBPBQ3F.js +2 -0
  69. package/dist/{scheduler-I3QB4GI3.js → scheduler-76IIRAP7.js} +2 -2
  70. package/dist/{seat-4Y3EW3KU.js → seat-TV5SK2E7.js} +2 -2
  71. package/dist/{select-LZKN3FER.js → select-HKRDVQNW.js} +2 -2
  72. package/dist/{set-enabled-WSTOHJDE.js → set-enabled-I2V3PXOA.js} +2 -2
  73. package/dist/{tool-AKXO2EJU.js → tool-5ZFM6NSO.js} +2 -2
  74. package/dist/{tool-NLMIUOHR.js → tool-J7PYS4NF.js} +2 -2
  75. package/dist/{wiki-ZS6OHOWQ.js → wiki-3VG33ZZB.js} +2 -2
  76. package/dist/wiki-enrich-2FNORKEQ.js +2 -0
  77. package/package.json +1 -1
  78. package/dist/chunk-54OORW5I.js +0 -54
  79. package/dist/chunk-54OORW5I.js.map +0 -1
  80. package/dist/chunk-OISCRNMW.js +0 -105
  81. package/dist/chunk-OISCRNMW.js.map +0 -1
  82. package/dist/chunk-QTDKNJXD.js.map +0 -1
  83. package/dist/config-mutator-Q62AS2TR.js +0 -2
  84. package/dist/program-WROQ55F5.js.map +0 -1
  85. package/dist/rules-HUUFXQD6.js +0 -24
  86. package/dist/rules-HUUFXQD6.js.map +0 -1
  87. package/dist/schedule-tool-46TVGEYU.js +0 -2
  88. package/dist/wiki-enrich-NGPFWTVA.js +0 -2
  89. /package/dist/{App-D6W6IV2L.js.map → App-MCYZBCMQ.js.map} +0 -0
  90. /package/dist/{ChatApp-X7TNCFGW.js.map → ChatApp-7YXDEW3H.js.map} +0 -0
  91. /package/dist/{absorb-eval-COPVH2M2.js.map → absorb-eval-L72I7PKM.js.map} +0 -0
  92. /package/dist/{agent-VCGAUBER.js.map → agent-TPREVZGH.js.map} +0 -0
  93. /package/dist/{chunk-ZPJVDX2U.js.map → chunk-264762O7.js.map} +0 -0
  94. /package/dist/{chunk-C43E3YFT.js.map → chunk-44N5HXYE.js.map} +0 -0
  95. /package/dist/{chunk-EVTTAM7S.js.map → chunk-5CEBOUBG.js.map} +0 -0
  96. /package/dist/{chunk-HZZW35FY.js.map → chunk-5EIO6S3S.js.map} +0 -0
  97. /package/dist/{chunk-5X37CXJF.js.map → chunk-APIANBXV.js.map} +0 -0
  98. /package/dist/{chunk-5J5USW2O.js.map → chunk-AYYEY5UJ.js.map} +0 -0
  99. /package/dist/{chunk-WRJFKA43.js.map → chunk-DX6RYYGP.js.map} +0 -0
  100. /package/dist/{chunk-FQLSV2C4.js.map → chunk-EFUPLTQ3.js.map} +0 -0
  101. /package/dist/{chunk-HEJTE7OC.js.map → chunk-ETOOT5KD.js.map} +0 -0
  102. /package/dist/{chunk-PV623BLN.js.map → chunk-GW4LKNQT.js.map} +0 -0
  103. /package/dist/{chunk-OW5EXDVY.js.map → chunk-HSX5LMXD.js.map} +0 -0
  104. /package/dist/{chunk-KZXQD6ZK.js.map → chunk-I5QKJH7V.js.map} +0 -0
  105. /package/dist/{chunk-M5B4QG6J.js.map → chunk-JXIMGK7Z.js.map} +0 -0
  106. /package/dist/{chunk-66DPMVUC.js.map → chunk-KOKPK3OL.js.map} +0 -0
  107. /package/dist/{chunk-K75LFQE3.js.map → chunk-LGM22AOB.js.map} +0 -0
  108. /package/dist/{chunk-VA24JS2S.js.map → chunk-OLZL32KG.js.map} +0 -0
  109. /package/dist/{chunk-M5AHI2PZ.js.map → chunk-PLCK4ZAG.js.map} +0 -0
  110. /package/dist/{chunk-GL3H7XMI.js.map → chunk-QKILE72V.js.map} +0 -0
  111. /package/dist/{chunk-AK3XOA5N.js.map → chunk-QQ67J76O.js.map} +0 -0
  112. /package/dist/{chunk-IPMXIPTS.js.map → chunk-RX2ISTNT.js.map} +0 -0
  113. /package/dist/{client-SMOWSRJY.js.map → client-G2QA4TCG.js.map} +0 -0
  114. /package/dist/{config-OGUDWYAA.js.map → config-OD5W6IQE.js.map} +0 -0
  115. /package/dist/{config-mutate-BH27TMWK.js.map → config-mutate-JEXLMR2L.js.map} +0 -0
  116. /package/dist/{config-mutator-Q62AS2TR.js.map → config-mutator-4FKROX3O.js.map} +0 -0
  117. /package/dist/{contributions-H6T65TCY.js.map → contributions-UXLHUMF2.js.map} +0 -0
  118. /package/dist/{doctor-ZOXHT7ZQ.js.map → doctor-DO2WN4TS.js.map} +0 -0
  119. /package/dist/{follow-up-tool-GUBBXNEZ.js.map → follow-up-tool-QPNJD3HR.js.map} +0 -0
  120. /package/dist/{hub-IIW2GBO5.js.map → hub-RFE766AM.js.map} +0 -0
  121. /package/dist/{promote-HDLU44MX.js.map → promote-FJ3EK2J6.js.map} +0 -0
  122. /package/dist/{query-6G27CBZQ.js.map → query-KYA6YYUU.js.map} +0 -0
  123. /package/dist/{query-settings-G4M75LVQ.js.map → query-settings-P3UOGMAQ.js.map} +0 -0
  124. /package/dist/{registry-3RE5SG22.js.map → registry-U4S4XXUZ.js.map} +0 -0
  125. /package/dist/{request-planner-T3VCZVVQ.js.map → request-planner-SYVDD4PZ.js.map} +0 -0
  126. /package/dist/{review-candidates-C3O3CENL.js.map → review-candidates-QUIX4AK7.js.map} +0 -0
  127. /package/dist/{routing-LD35TJM5.js.map → routing-MDZOHYYH.js.map} +0 -0
  128. /package/dist/{schedule-tool-46TVGEYU.js.map → schedule-tool-DKBPBQ3F.js.map} +0 -0
  129. /package/dist/{scheduler-I3QB4GI3.js.map → scheduler-76IIRAP7.js.map} +0 -0
  130. /package/dist/{seat-4Y3EW3KU.js.map → seat-TV5SK2E7.js.map} +0 -0
  131. /package/dist/{select-LZKN3FER.js.map → select-HKRDVQNW.js.map} +0 -0
  132. /package/dist/{set-enabled-WSTOHJDE.js.map → set-enabled-I2V3PXOA.js.map} +0 -0
  133. /package/dist/{tool-AKXO2EJU.js.map → tool-5ZFM6NSO.js.map} +0 -0
  134. /package/dist/{tool-NLMIUOHR.js.map → tool-J7PYS4NF.js.map} +0 -0
  135. /package/dist/{wiki-ZS6OHOWQ.js.map → wiki-3VG33ZZB.js.map} +0 -0
  136. /package/dist/{wiki-enrich-NGPFWTVA.js.map → wiki-enrich-2FNORKEQ.js.map} +0 -0
@@ -27,6 +27,9 @@ enum AgentClient {
27
27
  let voice: VoiceChoice?
28
28
  /// Presence mode the daemon chose for this turn (talk, teach, …).
29
29
  var presenceMode: String? = nil
30
+ /// The daemon took the words for the floor holder's own sound
31
+ /// (#924): no agent was asked and nothing is said back.
32
+ var dropped = false
30
33
  }
31
34
 
32
35
  /// What, if anything, to say about the step now running.
@@ -265,7 +268,8 @@ enum AgentClient {
265
268
  return await agents()?.first?.id ?? ""
266
269
  }
267
270
 
268
- static func ask(_ message: String, agent: String) async throws -> Answer {
271
+ /// `handsFree`: the microphone had opened by itself, no key held.
272
+ static func ask(_ message: String, agent: String, handsFree: Bool = false) async throws -> Answer {
269
273
  var req = URLRequest(url: URL(string: "\(Config.daemonURL)/ask")!)
270
274
  req.httpMethod = "POST"
271
275
  // An agent turn routinely takes minutes. The default 60s would cut
@@ -275,6 +279,7 @@ enum AgentClient {
275
279
  var payload: [String: Any] = ["message": message, "session": Config.voiceSession]
276
280
  // No agent known: the daemon answers with its default agent.
277
281
  if !agent.isEmpty { payload["agent"] = agent }
282
+ if handsFree { payload["handsFree"] = true }
278
283
  req.httpBody = try JSONSerialization.data(withJSONObject: payload)
279
284
 
280
285
  let (data, response) = try await URLSession.shared.data(for: req)
@@ -291,9 +296,14 @@ enum AgentClient {
291
296
  let ui: Ui?
292
297
  let error: String?
293
298
  let duration: Int?
299
+ let dropped: String?
294
300
  }
295
301
  let reply = try? JSONDecoder().decode(Reply.self, from: data)
296
302
  let voice = reply?.voice
303
+ if reply?.dropped != nil {
304
+ return Answer(text: "", written: nil, buttons: [], imageURL: nil, durationMs: nil,
305
+ agentID: reply?.agentId, voice: voice, dropped: true)
306
+ }
297
307
 
298
308
  // 202 means accepted-but-busy: the daemon has already written a
299
309
  // speakable explanation into `text`. Falling through to the error
@@ -35,6 +35,10 @@ final class CharacterHost {
35
35
  private var hidden = false
36
36
  /// On the page, playing (#580): its window is out until it is back.
37
37
  private var away = false
38
+ /// A recording holds the floor (#924): out until it ends.
39
+ private var concealed = false
40
+ /// Hidden, by the owner or for a recording.
41
+ private var out: Bool { hidden || concealed }
38
42
  private var animated = true
39
43
  private var reduceMotion = NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
40
44
  private var activity = CharacterMath.Activity.idle
@@ -94,7 +98,7 @@ final class CharacterHost {
94
98
  /// A still picture: Reduce Motion, or "Animated orb" off.
95
99
  private var still: Bool { !animated || reduceMotion }
96
100
  /// The look is the character: whether it is on screen. Nil with the orb.
97
- var onScreen: Bool? { shown ? !hidden && !away : nil }
101
+ var onScreen: Bool? { shown ? !out && !away : nil }
98
102
  /// Told when it comes on screen or leaves it: shown or hidden, or the look changed.
99
103
  var onScreenChanged: (() -> Void)?
100
104
 
@@ -146,6 +150,15 @@ final class CharacterHost {
146
150
  onScreenChanged?()
147
151
  }
148
152
 
153
+ /// Out for a recording that holds the floor (#924), and back as it
154
+ /// was when the recording ends: hidden stays hidden.
155
+ func setConcealed(_ on: Bool) {
156
+ guard concealed != on else { return }
157
+ concealed = on
158
+ run()
159
+ onScreenChanged?()
160
+ }
161
+
149
162
  /// Hide it, as its bubble is hidden: gone until it is asked back.
150
163
  func setHidden(_ on: Bool) {
151
164
  guard hidden != on else { return }
@@ -187,8 +200,8 @@ final class CharacterHost {
187
200
  /// Go beside `rect` and mark it, or with nil go back to where it
188
201
  /// rests; in the state `asked`, or with nil its real one.
189
202
  func guide(to rect: NSRect?, mark kind: GuideMath.Mark = .none, caption: String? = nil, asked mood: CharacterMath.Mood? = nil) {
190
- asked = shown && !hidden && !away ? mood : nil
191
- if shown, !hidden, !away, carried == nil, let rect {
203
+ asked = shown && !out && !away ? mood : nil
204
+ if shown, !out, !away, carried == nil, let rect {
192
205
  guide.show(rect, kind, caption: caption, color: view.stops[2], animated: !still)
193
206
  } else {
194
207
  guide.end()
@@ -200,14 +213,14 @@ final class CharacterHost {
200
213
  /// Standing still, it is drawn again when something changes: its
201
214
  /// state, or its bubble showing or hiding.
202
215
  func redraw() {
203
- if shown && !hidden && still { tick() }
216
+ if shown && !out && still { tick() }
204
217
  }
205
218
 
206
219
  /// The frames run only while it is on screen and may move.
207
220
  private func run() {
208
221
  timer?.invalidate()
209
222
  timer = nil
210
- guard shown, !hidden, !away else { carried = nil; guide(to: nil); window.orderOut(nil); return }
223
+ guard shown, !out, !away else { carried = nil; guide(to: nil); window.orderOut(nil); return }
211
224
  tick()
212
225
  window.orderFrontRegardless()
213
226
  // Still, it only watches for the hand that moves it.
@@ -0,0 +1,46 @@
1
+ import Foundation
2
+
3
+ /// The daemon's floor (#924): who may speak and show a bubble on this
4
+ /// Mac. A demo, a recording or a script takes it; until it lets go, an
5
+ /// answer waits to be shown and spoken, the microphone does not open by
6
+ /// itself, and for a recording the character and the pill stay out.
7
+ /// See src/voice/floor.ts; FloorWatcher.swift follows it.
8
+ struct FloorState: Decodable, Equatable {
9
+ var seq = 0
10
+ var held = false
11
+ /// Who holds it, as the owner reads it.
12
+ var holder: String?
13
+ /// The holder plays sound of its own.
14
+ var sound = false
15
+ /// A recording: nothing of the assistant shows.
16
+ var hide = false
17
+ /// Milliseconds since 1970, as the daemon sends them.
18
+ var expiresAt: Double?
19
+
20
+ static let free = FloorState()
21
+
22
+ /// As it is at `now` (seconds since 1970): a hold past its time is
23
+ /// over, whether or not the daemon was there to say so.
24
+ func at(_ now: TimeInterval) -> FloorState {
25
+ guard held, let expiresAt, now * 1000 >= expiresAt else { return self }
26
+ return FloorState(seq: seq)
27
+ }
28
+
29
+ /// An answer waits: shown and spoken once the floor is free.
30
+ var holdsAnswers: Bool { held }
31
+ /// The microphone must not open by itself, and a window it opened is
32
+ /// thrown away: it would hear the holder and take it for the owner.
33
+ var closesOpenMicrophone: Bool { held && sound }
34
+ /// The character, the pill and every bubble stay off the screen.
35
+ var hidesEverything: Bool { held && hide }
36
+
37
+ /// The menu's row while it is held: who, and for how long at most.
38
+ func menuTitle(now: TimeInterval) -> String? {
39
+ guard held else { return nil }
40
+ let who = holder ?? "Someone"
41
+ guard let expiresAt else { return "\(who) holds the floor" }
42
+ let left = max(0, Int((expiresAt / 1000 - now).rounded(.up)))
43
+ let time = left >= 90 ? "\(Int((Double(left) / 60).rounded())) min" : "\(left) s"
44
+ return "\(who) holds the floor (up to \(time))"
45
+ }
46
+ }
@@ -0,0 +1,78 @@
1
+ import Foundation
2
+
3
+ /// The daemon's /voice/floor route (#924).
4
+ enum FloorClient {
5
+ /// GET /voice/floor: the floor after `seq`, as soon as it changes;
6
+ /// the current one after 25 s; nil when the daemon cannot be reached
7
+ /// or is too old to know the route.
8
+ static func next(after seq: Int) async -> FloorState? {
9
+ guard let url = URL(string: "\(Config.daemonURL)/voice/floor?after=\(seq)") else { return nil }
10
+ var req = URLRequest(url: url)
11
+ req.timeoutInterval = 35
12
+ guard let (data, response) = try? await URLSession.shared.data(for: req),
13
+ (response as? HTTPURLResponse)?.statusCode == 200 else { return nil }
14
+ return try? JSONDecoder().decode(FloorState.self, from: data)
15
+ }
16
+
17
+ /// The owner takes the floor back, whoever holds it.
18
+ static func takeBack() async {
19
+ guard let url = URL(string: "\(Config.daemonURL)/voice/floor") else { return }
20
+ var req = URLRequest(url: url)
21
+ req.httpMethod = "DELETE"
22
+ req.timeoutInterval = 2
23
+ _ = try? await URLSession.shared.data(for: req)
24
+ }
25
+ }
26
+
27
+ /// Follows the floor for as long as the app runs. A hold ends at its
28
+ /// time limit whether or not the daemon is there to say so: nothing of
29
+ /// the assistant stays hidden behind a holder nobody can ask about.
30
+ @MainActor
31
+ final class FloorWatcher {
32
+ private var last = FloorState.free
33
+ private var task: Task<Void, Never>?
34
+ private var expiry: Timer?
35
+ /// The floor now.
36
+ var state: FloorState { last.at(Date().timeIntervalSince1970) }
37
+ /// Told of every change: taken, renewed, freed.
38
+ var onChange: ((FloorState) -> Void)?
39
+
40
+ func start() {
41
+ task?.cancel()
42
+ task = Task { [weak self] in
43
+ while !Task.isCancelled {
44
+ guard let self else { return }
45
+ await self.turn()
46
+ }
47
+ }
48
+ }
49
+
50
+ private func turn() async {
51
+ guard let next = await FloorClient.next(after: last.seq) else {
52
+ // The daemon is restarting or gone. A hold is kept until its
53
+ // own time limit: a recording must not have the character
54
+ // walk back in because one request failed.
55
+ try? await Task.sleep(nanoseconds: 2_000_000_000)
56
+ return
57
+ }
58
+ set(next)
59
+ }
60
+
61
+ private func set(_ next: FloorState) {
62
+ guard next != last else { return }
63
+ let before = state
64
+ last = next
65
+ // A hold ends at its time even if the daemon is not there to say so.
66
+ expiry?.invalidate()
67
+ expiry = nil
68
+ if next.held, let at = next.expiresAt {
69
+ expiry = Timer.scheduledTimer(withTimeInterval: max(0, at / 1000 - Date().timeIntervalSince1970) + 0.1, repeats: false) { _ in
70
+ Task { @MainActor [weak self] in
71
+ guard let self else { return }
72
+ self.onChange?(self.state)
73
+ }
74
+ }
75
+ }
76
+ if state != before { onChange?(state) }
77
+ }
78
+ }
@@ -36,6 +36,9 @@ final class Panel: NSPanel {
36
36
  let separator = NSBox()
37
37
  /// Hidden by close, Esc or the menu until the next talk key.
38
38
  var dismissed = false
39
+ /// A recording holds the floor (#924): out until it ends, whatever is
40
+ /// rendered meanwhile, then back as it would be.
41
+ private(set) var concealed = false
39
42
  /// Full, or reduced (#457): the orb alone, a small circle with no words
40
43
  /// and no buttons, dragged and remembered apart from the pill; with the
41
44
  /// character standing in for the orb, the character alone, its speech
@@ -531,6 +534,14 @@ final class Panel: NSPanel {
531
534
  render(current)
532
535
  }
533
536
 
537
+ /// Out for a recording, and back when it ends.
538
+ @MainActor
539
+ func setConcealed(_ on: Bool) {
540
+ guard concealed != on else { return }
541
+ concealed = on
542
+ render(current)
543
+ }
544
+
534
545
  /// The orb at the pill's head, or none while the character shows.
535
546
  @MainActor
536
547
  func setShowsOrb(_ on: Bool) {
@@ -642,7 +653,7 @@ final class Panel: NSPanel {
642
653
  // A caption shows like any words; away with none, the hint does not.
643
654
  let quiet = state.isMeta && !expanded && busyCount == 0
644
655
  let gone = reduced && !showsOrb
645
- if dismissed || gone || (quiet && idle == .nothing) || (quiet && idle == .hint && !alwaysVisible && !reduced) { hide() } else { show() }
656
+ if dismissed || concealed || gone || (quiet && idle == .nothing) || (quiet && idle == .hint && !alwaysVisible && !reduced) { hide() } else { show() }
646
657
  // After showing or hiding: a still character draws its bubble's
647
658
  // tail only while the bubble is on screen.
648
659
  onLook?(state, tint, colors)
@@ -39,6 +39,9 @@ final class StatusMenu: NSObject, NSMenuDelegate {
39
39
 
40
40
  /// Set by the app.
41
41
  var onStop: (() -> Void)?
42
+ /// Who holds the floor (#924): a row says so, with a way to take it back.
43
+ var floor = FloorState.free { didSet { if floor != oldValue { rebuild() } } }
44
+ var onTakeFloorBack: (() -> Void)?
42
45
  var onHoldChanged: ((Bool) -> Void)?
43
46
  var onTargetChanged: ((String) -> Void)?
44
47
  var onPillChanged: ((Bool) -> Void)?
@@ -245,6 +248,10 @@ final class StatusMenu: NSObject, NSMenuDelegate {
245
248
  recent.addItems(to: menu, name: name(of:))
246
249
 
247
250
  menu.addItem(.separator())
251
+ if let held = floor.menuTitle(now: Date().timeIntervalSince1970) {
252
+ menu.addItem(disabled(held))
253
+ menu.addItem(action("Take the floor back", #selector(takeFloorBack), key: ""))
254
+ }
248
255
  menu.addItem(action("Stop speaking", #selector(stop), key: ".", modifiers: [.command, .option]))
249
256
  let hold = action("Hold notifications", #selector(toggleHold), key: "")
250
257
  hold.state = Hold.isOn ? .on : .off
@@ -316,6 +323,7 @@ final class StatusMenu: NSObject, NSMenuDelegate {
316
323
  }
317
324
 
318
325
  @objc private func stop() { onStop?() }
326
+ @objc private func takeFloorBack() { onTakeFloorBack?() }
319
327
 
320
328
  @objc private func toggleHold() {
321
329
  onHoldChanged?(Hold.toggle())
@@ -91,6 +91,13 @@ final class App: NSObject, NSApplicationDelegate {
91
91
  // An agent ringing the owner (#321).
92
92
  private let callWatcher = CallWatcher()
93
93
  private let guideWatcher = GuideWatcher()
94
+ /// Who may speak and show a bubble on this Mac (#924).
95
+ private let floorWatcher = FloorWatcher()
96
+ /// Who held the floor at its last change, for the log.
97
+ private var floorHolder: String?
98
+ /// The microphone is open with no key held and no click: a follow-up
99
+ /// window. What it hears while a holder plays sound is thrown away.
100
+ private var openedByItself = false
94
101
  /// A play runs, or its picture is being taken (#562).
95
102
  private var playing = false
96
103
  /// When the app started, for the hint in the character's bubble.
@@ -105,10 +112,35 @@ final class App: NSObject, NSApplicationDelegate {
105
112
  private var activeCall: IncomingCall?
106
113
 
107
114
  /// /ask, counted in flight for the menu while it thinks.
108
- private func ask(_ heard: String, agent: String) async throws -> AgentClient.Answer {
115
+ private func ask(_ heard: String, agent: String, handsFree: Bool = false) async throws -> AgentClient.Answer {
109
116
  track(agent, 1)
110
117
  defer { track(agent, -1) }
111
- return try await AgentClient.ask(heard, agent: agent)
118
+ return try await AgentClient.ask(heard, agent: agent, handsFree: handsFree)
119
+ }
120
+
121
+ /// The floor was taken, renewed or freed (#924). A recording takes
122
+ /// the character and the pill off the screen; a holder that plays
123
+ /// sound closes a microphone that had opened by itself.
124
+ private func floorChanged(_ floor: FloorState) {
125
+ if floor.holder != floorHolder {
126
+ floorHolder = floor.holder
127
+ // Just taken: an answer being read or said leaves the screen.
128
+ // Its line is cut on the daemon and plays again after.
129
+ if floor.held && !recorder.isRecording { panel.collapse(); panel.render(rest) }
130
+ Log.info(floor.held
131
+ ? "floor: held by \(floor.holder ?? "?")\(floor.sound ? ", plays sound" : "")\(floor.hide ? ", everything hidden" : "")"
132
+ : "floor: free")
133
+ }
134
+ character.setConcealed(floor.hidesEverything)
135
+ panel.setConcealed(floor.hidesEverything)
136
+ statusMenu.floor = floor
137
+ if floor.closesOpenMicrophone && recorder.isRecording && openedByItself {
138
+ Log.info("floor: closing the microphone it opened by itself")
139
+ openedByItself = false
140
+ stopPolling()
141
+ _ = recorder.stop()
142
+ panel.render(rest)
143
+ }
112
144
  }
113
145
 
114
146
  private func track(_ agent: String, _ delta: Int) {
@@ -245,7 +277,7 @@ final class App: NSObject, NSApplicationDelegate {
245
277
  private var canPlay: Bool {
246
278
  character.onScreen == true && Config.animatedOrb
247
279
  && !NSWorkspace.shared.accessibilityDisplayShouldReduceMotion
248
- && !busy && !recorder.isRecording && !play.running
280
+ && !busy && !recorder.isRecording && !play.running && !floorWatcher.state.held
249
281
  }
250
282
 
251
283
  /// What the idle bubble says (#562): no hint at a guide stop, on the
@@ -551,6 +583,9 @@ final class App: NSObject, NSApplicationDelegate {
551
583
  DispatchQueue.main.asyncAfter(deadline: .now() + GuideMath.hintSeconds) { [weak self] in self?.idleWords() }
552
584
  guideWatcher.start()
553
585
  character.onScreenChanged = { [weak self] in self?.guideWatcher.start() }
586
+ floorWatcher.onChange = { [weak self] floor in self?.floorChanged(floor) }
587
+ statusMenu.onTakeFloorBack = { Task { await FloorClient.takeBack() } }
588
+ floorWatcher.start()
554
589
 
555
590
  // The target does not wait for the microphone: the menu shows it
556
591
  // either way.
@@ -598,6 +633,8 @@ final class App: NSObject, NSApplicationDelegate {
598
633
  guard !busy, !recorder.isRecording else { return }
599
634
  // A replay is playing (#492): the follow-up window would hear it.
600
635
  if followUp && replaying != nil { return }
636
+ // So it would the floor's holder, and take it for the owner (#924).
637
+ if followUp && floorWatcher.state.closesOpenMicrophone { return }
601
638
  recorder.endOfTurn = settings?.general.endOfTurn ?? "vad"
602
639
  do {
603
640
  try recorder.start()
@@ -606,6 +643,7 @@ final class App: NSObject, NSApplicationDelegate {
606
643
  return
607
644
  }
608
645
  if !followUp && lessonOn { talkCheck = Task { await AgentClient.hush() } }
646
+ openedByItself = followUp
609
647
  openedAt = Date()
610
648
  patience = followUp ? (activeCall == nil ? followUpPatience : callPatience) : clickPatience
611
649
  silentClose = followUp
@@ -739,6 +777,7 @@ final class App: NSObject, NSApplicationDelegate {
739
777
  lastSpokeAt = Date()
740
778
  talkCheck = Task { await AgentClient.hush() }
741
779
  Log.info("door: opened\(busy ? " (a turn is running)" : "")")
780
+ openedByItself = false
742
781
  // Push-to-talk ends on key release; no end-of-turn detection needed.
743
782
  recorder.endOfTurn = "hold"
744
783
  do {
@@ -752,6 +791,16 @@ final class App: NSObject, NSApplicationDelegate {
752
791
 
753
792
  private func stopAndSend() {
754
793
  guard recorder.isRecording else { return }
794
+ let handsFree = openedByItself
795
+ openedByItself = false
796
+ // The floor's holder was playing sound and nobody asked for the
797
+ // microphone: what it heard is the holder, not the owner (#924).
798
+ if handsFree && floorWatcher.state.closesOpenMicrophone {
799
+ Log.info("floor: dropped what the open microphone heard")
800
+ _ = recorder.stop()
801
+ panel.render(rest)
802
+ return
803
+ }
755
804
  // Clicked or hands-free: nothing hushed yet. They did speak, so
756
805
  // hush now; whatever was talking gets the words, same as the key.
757
806
  let door = talkCheck ?? Task { await AgentClient.hush() }
@@ -780,7 +829,7 @@ final class App: NSObject, NSApplicationDelegate {
780
829
  if !midTurn {
781
830
  panel.render(.error("Didn't catch that"))
782
831
  // Say it aloud: someone not looking would think it was sent.
783
- await Speech.say("Sorry, I didn't hear that. Please say it again.", agentID: nil, kind: "line", voice: voice)
832
+ await apologise("Sorry, I didn't hear that. Please say it again.", voice: voice)
784
833
  busy = false; resetSoon()
785
834
  }
786
835
  return
@@ -845,10 +894,33 @@ final class App: NSObject, NSApplicationDelegate {
845
894
  busy = false; resetSoon()
846
895
  return
847
896
  }
848
- await runTurn(heard, agent: agent)
897
+ await runTurn(heard, agent: agent, handsFree: handsFree)
898
+ }
899
+ }
900
+
901
+ /// Someone holds the floor (#924): the answer is kept, and shown and
902
+ /// said once it is free. Until then nothing of it is on screen.
903
+ private func waitForFloor(_ agent: String?, ownTurn: Bool = true) async {
904
+ let floor = floorWatcher.state
905
+ guard floor.holdsAnswers else { return }
906
+ Log.info("floor: answer\(agent.map { " (\($0))" } ?? "") kept until \(floor.holder ?? "the holder") lets go")
907
+ if !recorder.isRecording { panel.render(rest) }
908
+ // The owner's own words end the wait: "stop" drops the answer,
909
+ // anything else replaces it, and neither should wait for the holder.
910
+ let generation = stopGeneration
911
+ while floorWatcher.state.holdsAnswers && generation == stopGeneration && !(ownTurn && (abandoned || followUp != nil || silenced)) {
912
+ try? await Task.sleep(nanoseconds: 250_000_000)
849
913
  }
850
914
  }
851
915
 
916
+ /// An apology said aloud, for someone not looking. Not while the
917
+ /// floor is held: the line would wait behind the holder with the
918
+ /// turn still open, and the pill already says it.
919
+ private func apologise(_ text: String, voice: VoiceChoice?) async {
920
+ guard !floorWatcher.state.held else { return }
921
+ await Speech.say(text, agentID: nil, kind: "line", voice: voice)
922
+ }
923
+
852
924
  /// A question for another agent while our own turn thinks. It runs
853
925
  /// alongside; the answer waits its turn in the daemon's speaking queue,
854
926
  /// so it never talks over ours. An agent already answering an aside
@@ -875,6 +947,7 @@ final class App: NSObject, NSApplicationDelegate {
875
947
  // The next question for this agent goes now, while this
876
948
  // answer waits to be spoken.
877
949
  startNextAside(for: agent)
950
+ if case .success(let answer) = result { await waitForFloor(answer.agentID ?? agent, ownTurn: false) }
878
951
  guard generation == stopGeneration else { return }
879
952
  switch result {
880
953
  case .success(let answer):
@@ -896,7 +969,7 @@ final class App: NSObject, NSApplicationDelegate {
896
969
  }
897
970
  case .failure(let error):
898
971
  Log.warn("aside failed (\(agent)): \(error.localizedDescription)")
899
- await Speech.say("Sorry, \(statusMenu.name(of: agent)) couldn't answer that.", agentID: nil, kind: "line", voice: nil)
972
+ await apologise("Sorry, \(statusMenu.name(of: agent)) couldn't answer that.", voice: nil)
900
973
  }
901
974
  }
902
975
  }
@@ -920,14 +993,20 @@ final class App: NSObject, NSApplicationDelegate {
920
993
 
921
994
  /// One question and its spoken answer. Words said through the door
922
995
  /// while it thinks replace the answer with the next turn.
923
- private func runTurn(_ heard: String, agent: String) async {
996
+ private func runTurn(_ heard: String, agent: String, handsFree: Bool = false) async {
924
997
  silenced = false
925
998
  turnAgent = agent
926
999
  do {
927
1000
  beginNarration(agent)
928
1001
 
929
- let answer = try await ask(heard, agent: agent)
1002
+ let answer = try await ask(heard, agent: agent, handsFree: handsFree)
930
1003
  endNarration()
1004
+ // The daemon took the words for the floor holder's sound.
1005
+ if answer.dropped {
1006
+ Log.info("floor: the daemon dropped hands-free words")
1007
+ panel.render(.idle); busy = false; return
1008
+ }
1009
+ await waitForFloor(answer.agentID)
931
1010
  if abandoned {
932
1011
  abandoned = false
933
1012
  Log.info("dropped answer (\(answer.agentID ?? "?")): \(answer.text)")
@@ -988,7 +1067,7 @@ final class App: NSObject, NSApplicationDelegate {
988
1067
  panel.render(.error(short(error.localizedDescription)))
989
1068
  // Say it aloud too — a voice assistant that fails only in
990
1069
  // a 230px label has failed silently for anyone not looking.
991
- await Speech.say("Sorry, that didn't work.", agentID: nil, kind: "line", voice: voice)
1070
+ await apologise("Sorry, that didn't work.", voice: voice)
992
1071
  resetSoon()
993
1072
  }
994
1073
  busy = false
@@ -1078,7 +1157,7 @@ final class App: NSObject, NSApplicationDelegate {
1078
1157
  private var rest: Panel.State { activeCall == nil ? .idle : .onCall }
1079
1158
 
1080
1159
  /// A new call can ring: no call in progress, no turn, microphone closed.
1081
- private var canRing: Bool { activeCall == nil && !busy && !recorder.isRecording }
1160
+ private var canRing: Bool { activeCall == nil && !busy && !recorder.isRecording && !floorWatcher.state.held }
1082
1161
 
1083
1162
  private func polled(_ state: RingingCalls) {
1084
1163
  switch CallModel.action(ringing: ringingCall?.id, calls: state.calls, canRing: canRing) {
@@ -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-5X37CXJF.js";import"./chunk-54OORW5I.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.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-APIANBXV.js";import"./chunk-6BWVJ7UJ.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.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-D6W6IV2L.js.map
8
+ //# sourceMappingURL=App-MCYZBCMQ.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-5X37CXJF.js";import"./chunk-54OORW5I.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.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-APIANBXV.js";import"./chunk-6BWVJ7UJ.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.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-X7TNCFGW.js.map
7
+ //# sourceMappingURL=ChatApp-7YXDEW3H.js.map
@@ -1,2 +1,2 @@
1
- import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o}from"./chunk-5J5USW2O.js";import"./chunk-KZXQD6ZK.js";import"./chunk-PV623BLN.js";import"./chunk-SKSYII3K.js";import"./chunk-OW5EXDVY.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-66DPMVUC.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-MCI3DIWW.js";import"./chunk-6TURWSMN.js";import"./chunk-QD6IICK7.js";import"./chunk-QOYAX2FT.js";import"./chunk-2PJKGZWP.js";import"./chunk-7I7IEX4S.js";import"./chunk-54OORW5I.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{i as JUDGE_ENTRY_CHARS,e as WEAK_CITATION,j as buildJudgePrompt,l as buildScorecard,f as checkArticle,n as envelopeUsage,h as findDuplicates,k as parseJudgeReply,b as pickSample,m as renderScorecard,a as seededRandom,o as summariseRuns,d as termOverlap,c as terms,g as uncitedWithFacts};
2
- //# sourceMappingURL=absorb-eval-COPVH2M2.js.map
1
+ import{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o}from"./chunk-AYYEY5UJ.js";import"./chunk-I5QKJH7V.js";import"./chunk-GW4LKNQT.js";import"./chunk-SKSYII3K.js";import"./chunk-HSX5LMXD.js";import"./chunk-WPNRDEHW.js";import"./chunk-MWNCS57L.js";import"./chunk-4LWFJ4MZ.js";import"./chunk-BIJHMWXK.js";import"./chunk-KOKPK3OL.js";import"./chunk-72SZI2UW.js";import"./chunk-FCPTN6NF.js";import"./chunk-MCI3DIWW.js";import"./chunk-6TURWSMN.js";import"./chunk-QD6IICK7.js";import"./chunk-QOYAX2FT.js";import"./chunk-2PJKGZWP.js";import"./chunk-7I7IEX4S.js";import"./chunk-6BWVJ7UJ.js";import"./chunk-BRK6M6LA.js";import"./chunk-UIBUZ2N3.js";import"./chunk-2QNT5JDS.js";import"./chunk-RGDKHYY2.js";import"./chunk-QIJLFPWE.js";export{i as JUDGE_ENTRY_CHARS,e as WEAK_CITATION,j as buildJudgePrompt,l as buildScorecard,f as checkArticle,n as envelopeUsage,h as findDuplicates,k as parseJudgeReply,b as pickSample,m as renderScorecard,a as seededRandom,o as summariseRuns,d as termOverlap,c as terms,g as uncitedWithFacts};
2
+ //# sourceMappingURL=absorb-eval-L72I7PKM.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-ZPJVDX2U.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-2PJKGZWP.js";import"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import"./chunk-QIJLFPWE.js";export{a as createAgentContext,b as generate,c as generateStream};
2
- //# sourceMappingURL=agent-VCGAUBER.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-264762O7.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-2PJKGZWP.js";import"./chunk-7I7IEX4S.js";import"./chunk-JHCI6XCJ.js";import"./chunk-DSYKYZMT.js";import"./chunk-QIJLFPWE.js";export{a as createAgentContext,b as generate,c as generateStream};
2
+ //# sourceMappingURL=agent-TPREVZGH.js.map