agentix-cli 0.37.0 → 0.40.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 (95) hide show
  1. package/apps/mac-helper/Sources/AgentXHelper/AXTree.swift +29 -11
  2. package/apps/mac-helper/Sources/AgentXHelper/Pointer.swift +7 -4
  3. package/apps/mac-helper/Sources/AgentXHelper/Presence.swift +7 -3
  4. package/apps/mac-helper/Sources/AgentXHelper/Typer.swift +2 -1
  5. package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +53 -4
  6. package/apps/mac-helper/Sources/AgentXHelper/main.swift +4 -2
  7. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +6 -0
  8. package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +8 -0
  9. package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +82 -4
  10. package/apps/mac-voice/Sources/AgentXVoice/SpeechEnd.swift +71 -0
  11. package/apps/mac-voice/Sources/AgentXVoice/main.swift +28 -0
  12. package/dist/{App-TW5N7JY2.js → App-HSRGG5VA.js} +2 -2
  13. package/dist/{ChatApp-MRGX7JBT.js → ChatApp-4FOU6Q3S.js} +2 -2
  14. package/dist/{agent-FQHWBQP2.js → agent-54NUP2Z3.js} +2 -2
  15. package/dist/{board-dashboard-2YEYVCUS.js → board-dashboard-R7FWWFFK.js} +4 -4
  16. package/dist/{chunk-3SKFX6G3.js → chunk-B32HVIDN.js} +2 -2
  17. package/dist/{chunk-ADRAW342.js → chunk-B5HKRYCU.js} +2 -2
  18. package/dist/{chunk-N6APC3DF.js → chunk-DIKZWTL5.js} +5 -5
  19. package/dist/{chunk-AKIYB3IJ.js → chunk-FAJEBVZE.js} +2 -2
  20. package/dist/{chunk-SR6TVIIS.js → chunk-H4MIYENA.js} +2 -2
  21. package/dist/chunk-IAG6FI3Q.js +13 -0
  22. package/dist/chunk-IAG6FI3Q.js.map +1 -0
  23. package/dist/{chunk-4ZGX4L6Q.js → chunk-L2RUE6T4.js} +260 -174
  24. package/dist/chunk-L2RUE6T4.js.map +1 -0
  25. package/dist/{chunk-RRLIZLBH.js → chunk-NZ6AQIKJ.js} +2 -2
  26. package/dist/{chunk-IEE475O2.js → chunk-P7LNYB53.js} +2 -2
  27. package/dist/{chunk-UORMWVLP.js → chunk-R3VXG2Q5.js} +133 -133
  28. package/dist/chunk-R3VXG2Q5.js.map +1 -0
  29. package/dist/{chunk-MC6I6ZLY.js → chunk-RCVGIKC2.js} +2 -2
  30. package/dist/{chunk-GL7VC26K.js → chunk-REPZID5M.js} +2 -2
  31. package/dist/{chunk-EJWIRM52.js → chunk-RQKUQOFC.js} +2 -2
  32. package/dist/{chunk-FHF4RA4Q.js → chunk-SJPLXXCI.js} +2 -2
  33. package/dist/{chunk-HEV3AVFJ.js → chunk-WBGROLOX.js} +2 -2
  34. package/dist/{chunk-JLEADOCC.js → chunk-X763UOES.js} +2 -2
  35. package/dist/cli.js +1 -1
  36. package/dist/client-6BGTH2TT.js +2 -0
  37. package/dist/config-SRTXHGSK.js +2 -0
  38. package/dist/config-mutator-KJ5GEMVV.js +2 -0
  39. package/dist/doctor-J4FLV45L.js +2 -0
  40. package/dist/index.d.ts +32 -32
  41. package/dist/index.js +1 -1
  42. package/dist/program-E6AXVO5W.js +2 -0
  43. package/dist/query-ZSUBNWOL.js +2 -0
  44. package/dist/{registry-TDYF7HGH.js → registry-OMUDCBKM.js} +2 -2
  45. package/dist/{request-planner-DF74JHQB.js → request-planner-326NOKPZ.js} +2 -2
  46. package/dist/{review-candidates-SEAGMKI6.js → review-candidates-LEOPEOEG.js} +2 -2
  47. package/dist/{routing-JKWL3L3Q.js → routing-AWIRBYGU.js} +2 -2
  48. package/dist/scheduler-PGGYW5AB.js +2 -0
  49. package/dist/{seat-C6TOB63Q.js → seat-XB4EGUKJ.js} +2 -2
  50. package/dist/{select-YB5KF27O.js → select-L4VIYSFJ.js} +2 -2
  51. package/dist/wiki-GBC6I7ZX.js +2 -0
  52. package/package.json +1 -1
  53. package/dist/chunk-4ZGX4L6Q.js.map +0 -1
  54. package/dist/chunk-UORMWVLP.js.map +0 -1
  55. package/dist/chunk-WFKS4O34.js +0 -13
  56. package/dist/chunk-WFKS4O34.js.map +0 -1
  57. package/dist/client-4KD2UA75.js +0 -2
  58. package/dist/config-WN3R3BPU.js +0 -2
  59. package/dist/config-mutator-2FIUL4DA.js +0 -2
  60. package/dist/doctor-BNO46EOR.js +0 -2
  61. package/dist/program-Z6EK44PV.js +0 -2
  62. package/dist/query-TKUDIOBZ.js +0 -2
  63. package/dist/scheduler-7CYBDKQ7.js +0 -2
  64. package/dist/wiki-7FCINSKJ.js +0 -2
  65. /package/dist/{App-TW5N7JY2.js.map → App-HSRGG5VA.js.map} +0 -0
  66. /package/dist/{ChatApp-MRGX7JBT.js.map → ChatApp-4FOU6Q3S.js.map} +0 -0
  67. /package/dist/{agent-FQHWBQP2.js.map → agent-54NUP2Z3.js.map} +0 -0
  68. /package/dist/{board-dashboard-2YEYVCUS.js.map → board-dashboard-R7FWWFFK.js.map} +0 -0
  69. /package/dist/{chunk-3SKFX6G3.js.map → chunk-B32HVIDN.js.map} +0 -0
  70. /package/dist/{chunk-ADRAW342.js.map → chunk-B5HKRYCU.js.map} +0 -0
  71. /package/dist/{chunk-N6APC3DF.js.map → chunk-DIKZWTL5.js.map} +0 -0
  72. /package/dist/{chunk-AKIYB3IJ.js.map → chunk-FAJEBVZE.js.map} +0 -0
  73. /package/dist/{chunk-SR6TVIIS.js.map → chunk-H4MIYENA.js.map} +0 -0
  74. /package/dist/{chunk-RRLIZLBH.js.map → chunk-NZ6AQIKJ.js.map} +0 -0
  75. /package/dist/{chunk-IEE475O2.js.map → chunk-P7LNYB53.js.map} +0 -0
  76. /package/dist/{chunk-MC6I6ZLY.js.map → chunk-RCVGIKC2.js.map} +0 -0
  77. /package/dist/{chunk-GL7VC26K.js.map → chunk-REPZID5M.js.map} +0 -0
  78. /package/dist/{chunk-EJWIRM52.js.map → chunk-RQKUQOFC.js.map} +0 -0
  79. /package/dist/{chunk-FHF4RA4Q.js.map → chunk-SJPLXXCI.js.map} +0 -0
  80. /package/dist/{chunk-HEV3AVFJ.js.map → chunk-WBGROLOX.js.map} +0 -0
  81. /package/dist/{chunk-JLEADOCC.js.map → chunk-X763UOES.js.map} +0 -0
  82. /package/dist/{client-4KD2UA75.js.map → client-6BGTH2TT.js.map} +0 -0
  83. /package/dist/{config-WN3R3BPU.js.map → config-SRTXHGSK.js.map} +0 -0
  84. /package/dist/{config-mutator-2FIUL4DA.js.map → config-mutator-KJ5GEMVV.js.map} +0 -0
  85. /package/dist/{doctor-BNO46EOR.js.map → doctor-J4FLV45L.js.map} +0 -0
  86. /package/dist/{program-Z6EK44PV.js.map → program-E6AXVO5W.js.map} +0 -0
  87. /package/dist/{query-TKUDIOBZ.js.map → query-ZSUBNWOL.js.map} +0 -0
  88. /package/dist/{registry-TDYF7HGH.js.map → registry-OMUDCBKM.js.map} +0 -0
  89. /package/dist/{request-planner-DF74JHQB.js.map → request-planner-326NOKPZ.js.map} +0 -0
  90. /package/dist/{review-candidates-SEAGMKI6.js.map → review-candidates-LEOPEOEG.js.map} +0 -0
  91. /package/dist/{routing-JKWL3L3Q.js.map → routing-AWIRBYGU.js.map} +0 -0
  92. /package/dist/{scheduler-7CYBDKQ7.js.map → scheduler-PGGYW5AB.js.map} +0 -0
  93. /package/dist/{seat-C6TOB63Q.js.map → seat-XB4EGUKJ.js.map} +0 -0
  94. /package/dist/{select-YB5KF27O.js.map → select-L4VIYSFJ.js.map} +0 -0
  95. /package/dist/{wiki-7FCINSKJ.js.map → wiki-GBC6I7ZX.js.map} +0 -0
@@ -48,6 +48,34 @@ enum AXTree {
48
48
  truncated: false, note: "no frontmost application")
49
49
  }
50
50
  let axApp = AXUIElementCreateApplication(app.processIdentifier)
51
+ // Electron (tldraw offline, VS Code, Slack) builds its web content's
52
+ // tree only once an assistive client asks for it; until then the
53
+ // window is empty. Other apps reject the attribute, harmlessly.
54
+ let asked = AXUIElementSetAttributeValue(axApp, "AXManualAccessibility" as CFString, kCFBooleanTrue) == .success
55
+ var elements: [Element] = []
56
+ var truncated = false
57
+ // The first time, Chromium fills the tree in over a moment.
58
+ for attempt in 0..<6 {
59
+ (elements, truncated) = walk(axApp, maxElements: maxElements)
60
+ if !asked || elements.count >= 3 || attempt == 5 { break }
61
+ Thread.sleep(forTimeInterval: 0.25)
62
+ }
63
+ var windowRef: CFTypeRef?
64
+ AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &windowRef)
65
+ let windowTitle = string((windowRef as! AXUIElement?) ?? axApp, kAXTitleAttribute)
66
+
67
+ return Snapshot(
68
+ app: app.localizedName ?? "unknown",
69
+ pid: app.processIdentifier,
70
+ window: windowTitle,
71
+ elements: elements,
72
+ truncated: truncated,
73
+ note: elements.count < 3
74
+ ? "this app exposes almost no accessibility information — the tree is not usable here"
75
+ : nil)
76
+ }
77
+
78
+ private static func walk(_ axApp: AXUIElement, maxElements: Int) -> ([Element], Bool) {
51
79
  var elements: [Element] = []
52
80
  var truncated = false
53
81
 
@@ -57,7 +85,6 @@ enum AXTree {
57
85
  var windowRef: CFTypeRef?
58
86
  AXUIElementCopyAttributeValue(axApp, kAXFocusedWindowAttribute as CFString, &windowRef)
59
87
  let root = (windowRef as! AXUIElement?) ?? axApp
60
- let windowTitle = string(root, kAXTitleAttribute)
61
88
 
62
89
  // Traverse everything, but only SPEND the budget on elements worth
63
90
  // choosing between.
@@ -84,16 +111,7 @@ enum AXTree {
84
111
  for c in children { queue.append((c, keptIndex)) }
85
112
  }
86
113
  }
87
-
88
- return Snapshot(
89
- app: app.localizedName ?? "unknown",
90
- pid: app.processIdentifier,
91
- window: windowTitle,
92
- elements: elements,
93
- truncated: truncated,
94
- note: elements.count < 3
95
- ? "this app exposes almost no accessibility information — the tree is not usable here"
96
- : nil)
114
+ return (elements, truncated)
97
115
  }
98
116
 
99
117
  /// Containers earn a slot only when they say something.
@@ -57,9 +57,11 @@ enum Pointer {
57
57
  let control = CGPoint(x: from.x + dx * 0.5 + nx * bow,
58
58
  y: from.y + dy * 0.5 + ny * bow)
59
59
 
60
- let steps = max(24, Int(duration * 110))
61
- for i in 1...steps {
62
- let t = Double(i) / Double(steps)
60
+ // Paced by the clock, not a step count: each warp costs ~20 ms on
61
+ // its own, so a fixed ~100 steps stretched a 0.95 s move to ~3 s.
62
+ let start = Date()
63
+ while true {
64
+ let t = min(1, Date().timeIntervalSince(start) / duration)
63
65
  // Minimum-jerk: 10t³ − 15t⁴ + 6t⁵. Starts and ends at zero
64
66
  // velocity AND zero acceleration, which is why it looks settled
65
67
  // rather than braked.
@@ -72,7 +74,8 @@ enum Pointer {
72
74
  let jitter = CGPoint(x: Double.random(in: -tremor...tremor),
73
75
  y: Double.random(in: -tremor...tremor))
74
76
  CGWarpMouseCursorPosition(CGPoint(x: p.x + jitter.x, y: p.y + jitter.y))
75
- Thread.sleep(forTimeInterval: duration / Double(steps))
77
+ if t >= 1 { break }
78
+ Thread.sleep(forTimeInterval: 1.0 / 110)
76
79
  }
77
80
 
78
81
  // Long moves overshoot slightly and correct — the correction is
@@ -13,7 +13,7 @@ import AppKit
13
13
  /// {"cmd":"move","x":120,"y":80,"w":90,"h":24,"highlight":true}
14
14
  /// {"cmd":"say","text":"The export button is top right."}
15
15
  /// {"cmd":"clear"} drop the highlight
16
- /// {"cmd":"park"} rest in the bottom-right corner, no highlight
16
+ /// {"cmd":"park"} return beside the person's own pointer, no highlight
17
17
  /// {"cmd":"ping"} nothing; keeps an idle overlay alive
18
18
  ///
19
19
  /// Coordinates are accessibility coordinates (top-left origin, global),
@@ -175,9 +175,13 @@ enum Presence {
175
175
  }
176
176
  }
177
177
 
178
+ /// Just below and right of the person's real pointer, so handing
179
+ /// back reads as returning to them, without covering their cursor.
178
180
  static func parking() -> CGPoint {
179
- let v = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
180
- return CGPoint(x: v.maxX - 380, y: v.minY + 170)
181
+ let m = NSEvent.mouseLocation
182
+ let v = (NSScreen.screens.first(where: { $0.frame.contains(m) }) ?? NSScreen.main)?.visibleFrame
183
+ ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
184
+ return CGPoint(x: min(max(m.x + 28, v.minX), v.maxX - 40), y: min(max(m.y - 28, v.minY + 40), v.maxY))
181
185
  }
182
186
 
183
187
  func place(_ tip: CGPoint) {
@@ -91,13 +91,14 @@ enum Typer {
91
91
  return true
92
92
  }
93
93
 
94
- /// Single letters and digits, so `key --name a --mod cmd` works.
94
+ /// Single letters, digits, comma and period, so `key --name a --mod cmd` works.
95
95
  private static func letterCode(_ name: String) -> CGKeyCode? {
96
96
  let map: [Character: CGKeyCode] = [
97
97
  "a": 0, "s": 1, "d": 2, "f": 3, "h": 4, "g": 5, "z": 6, "x": 7, "c": 8, "v": 9,
98
98
  "b": 11, "q": 12, "w": 13, "e": 14, "r": 15, "y": 16, "t": 17,
99
99
  "1": 18, "2": 19, "3": 20, "4": 21, "6": 22, "5": 23, "9": 25, "7": 26, "8": 28, "0": 29,
100
100
  "o": 31, "u": 32, "i": 34, "p": 35, "l": 37, "j": 38, "k": 40, "n": 45, "m": 46,
101
+ ",": 43, ".": 47,
101
102
  ]
102
103
  guard name.count == 1, let ch = name.lowercased().first else { return nil }
103
104
  return map[ch]
@@ -37,6 +37,10 @@ enum Vision {
37
37
  /// True when the topmost element's frame matches the one asked
38
38
  /// about, within a tolerance.
39
39
  let matchesExpected: Bool?
40
+ /// "role label" of the elements containing it, nearest first. Web
41
+ /// controls answer a hit test with an unnamed inner node (the icon,
42
+ /// the text span); the control that carries the name is a parent.
43
+ let ancestors: [String]
40
44
  }
41
45
 
42
46
  /// What is actually on top at a screen point.
@@ -52,9 +56,16 @@ enum Vision {
52
56
  else { return nil }
53
57
 
54
58
  let role = attr(element, kAXRoleAttribute) ?? ""
55
- let label = [kAXTitleAttribute, kAXDescriptionAttribute, kAXValueAttribute]
56
- .compactMap { attr(element, $0) }
57
- .first(where: { !$0.isEmpty }) ?? ""
59
+ let label = name(element)
60
+ var ancestors: [String] = []
61
+ var node = element
62
+ for _ in 0..<8 {
63
+ var parent: CFTypeRef?
64
+ guard AXUIElementCopyAttributeValue(node, kAXParentAttribute as CFString, &parent) == .success,
65
+ let p = parent, CFGetTypeID(p) == AXUIElementGetTypeID() else { break }
66
+ node = p as! AXUIElement
67
+ ancestors.append("\(attr(node, kAXRoleAttribute) ?? "") \(name(node))")
68
+ }
58
69
 
59
70
  var owner = "unknown"
60
71
  var pid: pid_t = 0
@@ -77,7 +88,7 @@ enum Vision {
77
88
  matches = false
78
89
  }
79
90
  }
80
- return HitResult(role: role, label: label, app: owner, matchesExpected: matches)
91
+ return HitResult(role: role, label: label, app: owner, matchesExpected: matches, ancestors: ancestors)
81
92
  }
82
93
 
83
94
  // MARK: Capture
@@ -181,6 +192,44 @@ enum Vision {
181
192
 
182
193
  // MARK: Helpers
183
194
 
195
+ /// Whether an element named `expected` covers the point in the window
196
+ /// that `hit` belongs to. Chromium answers a hit test on a web app's
197
+ /// toolbar with an unnamed full-window layer, not the button under it;
198
+ /// the window server has still decided which window is in front, so
199
+ /// looking inside that window for the named control is safe.
200
+ static func windowShows(_ expected: String, at p: CGPoint) -> Bool {
201
+ let system = AXUIElementCreateSystemWide()
202
+ var ref: AXUIElement?
203
+ guard AXUIElementCopyElementAtPosition(system, Float(p.x), Float(p.y), &ref) == .success,
204
+ let hit = ref else { return false }
205
+ // Web nodes often lack AXWindow; climb to it instead.
206
+ var w = hit
207
+ for _ in 0..<32 where attr(w, kAXRoleAttribute) != kAXWindowRole {
208
+ var parent: CFTypeRef?
209
+ guard AXUIElementCopyAttributeValue(w, kAXParentAttribute as CFString, &parent) == .success,
210
+ let up = parent, CFGetTypeID(up) == AXUIElementGetTypeID() else { break }
211
+ w = up as! AXUIElement
212
+ }
213
+ let want = expected.lowercased()
214
+ var stack = [w]
215
+ var budget = 3000
216
+ while let node = stack.popLast(), budget > 0 {
217
+ budget -= 1
218
+ if let f = frame(node), !f.contains(p), f.width >= 1 { continue }
219
+ if "\(attr(node, kAXRoleAttribute) ?? "") \(name(node))".lowercased().contains(want) { return true }
220
+ var kids: CFTypeRef?
221
+ if AXUIElementCopyAttributeValue(node, kAXChildrenAttribute as CFString, &kids) == .success,
222
+ let children = kids as? [AXUIElement] { stack.append(contentsOf: children) }
223
+ }
224
+ return false
225
+ }
226
+
227
+ private static func name(_ node: AXUIElement) -> String {
228
+ [kAXTitleAttribute, kAXDescriptionAttribute, kAXValueAttribute]
229
+ .compactMap { attr(node, $0) }
230
+ .first(where: { !$0.isEmpty }) ?? ""
231
+ }
232
+
184
233
  private static func attr(_ node: AXUIElement, _ name: String) -> String? {
185
234
  var ref: CFTypeRef?
186
235
  guard AXUIElementCopyAttributeValue(node, name as CFString, &ref) == .success else { return nil }
@@ -198,8 +198,10 @@ case "click":
198
198
  if !args.contains("--force"), let p = CGEvent(source: nil)?.location {
199
199
  if let hit = Vision.hitTest(x: p.x, y: p.y),
200
200
  let expected = flag("expect"), !expected.isEmpty {
201
- let seen = "\(hit.role) \(hit.label)".lowercased()
202
- if !seen.contains(expected.lowercased()) {
201
+ // The named control may be a parent of what the point lands on.
202
+ let seen = (["\(hit.role) \(hit.label)"] + hit.ancestors).map { $0.lowercased() }
203
+ if !seen.contains(where: { $0.contains(expected.lowercased()) }),
204
+ !(hit.label.isEmpty && Vision.windowShows(expected, at: p)) {
203
205
  fail("refusing to click: \(hit.app) shows \(hit.role) \"\(hit.label)\" at the cursor, not \"\(expected)\" — something is covering it")
204
206
  }
205
207
  }
@@ -78,6 +78,12 @@ enum AgentClient {
78
78
  return Hushed(kind: r?.kind, agentID: r?.agentId)
79
79
  }
80
80
 
81
+ /// Silence everything the daemon is saying, keeping nothing for the
82
+ /// door. Never throws: no daemon means nothing of its was speaking.
83
+ static func stopVoice() async {
84
+ _ = try? await post("/voice/stop", [:], timeout: 2)
85
+ }
86
+
81
87
  /// Hand the listener's words through the door. True when an activity
82
88
  /// took them (a talk or lesson answers; "stop" ends it); false means
83
89
  /// nothing did, and they are an ordinary question.
@@ -51,6 +51,9 @@ final class Panel: NSPanel {
51
51
  item.target = self
52
52
  item.state = holding ? .on : .off
53
53
  menu.addItem(item)
54
+ let stop = NSMenuItem(title: "Stop speaking ⌘⌥.", action: #selector(stopSpeaking), keyEquivalent: "")
55
+ stop.target = self
56
+ menu.addItem(stop)
54
57
  menu.addItem(.separator())
55
58
  let quit = NSMenuItem(title: "Quit AgentX Voice",
56
59
  action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
@@ -66,6 +69,11 @@ final class Panel: NSPanel {
66
69
  /// Told when the hold flips, so the app can redraw and flush.
67
70
  var onHoldChanged: ((Bool) -> Void)?
68
71
 
72
+ @objc private func stopSpeaking() { onStop?() }
73
+
74
+ /// "Stop speaking" was chosen. Set by the app.
75
+ var onStop: (() -> Void)?
76
+
69
77
  override func mouseUp(with event: NSEvent) {
70
78
  defer { pressedAt = nil }
71
79
  if let start = pressedAt {
@@ -6,7 +6,8 @@ import Foundation
6
6
  struct VoiceChoice: Decodable {
7
7
  var provider: String?
8
8
  var elevenlabsVoiceId: String?
9
- /// A macOS voice identifier for `say -v`; nil for the OS default.
9
+ /// A macOS voice identifier for `say -v`, or a Siri voice id; nil for
10
+ /// the OS default.
10
11
  var systemVoice: String?
11
12
  /// When ElevenLabs cannot speak, use the system voice instead.
12
13
  var fallback: Bool?
@@ -100,6 +101,15 @@ enum Speech {
100
101
  /// Silence whatever this app is saying (an answer or a step line).
101
102
  static func stop() { Player.shared.stop() }
102
103
 
104
+ /// Barge-in: silence every voice on this Mac and drop every queued
105
+ /// line. This app's line, whatever holds the shared script's lock, and
106
+ /// the daemon's talk, lesson or narration (POST /voice/stop).
107
+ static func stopAll() async {
108
+ Player.shared.stop()
109
+ Player.stopScript()
110
+ await AgentClient.stopVoice()
111
+ }
112
+
103
113
  /// `voice` is the answering agent's, as the daemon resolved it; nil
104
114
  /// (no answer yet) means the provider this app is configured with.
105
115
  static func speak(_ text: String, voice: VoiceChoice? = nil) async {
@@ -179,16 +189,31 @@ final class Player: NSObject, AVAudioPlayerDelegate {
179
189
 
180
190
  /// Speak with a system voice; returns when done or stopped. The text
181
191
  /// goes in on stdin, so a line that starts with "-" is not an option.
192
+ ///
193
+ /// Every line goes through the daemon's script when it can: it switches
194
+ /// the default for a Siri voice (`say -v` cannot use one), holds the one
195
+ /// speaker lock, bounds the line, and is what a stop reaches. A script
196
+ /// from before `--stop` treats any id as a Siri one, so it only gets
197
+ /// Siri and default lines.
182
198
  func say(_ text: String, voice: String?) async {
183
199
  let p = Process()
184
- p.executableURL = URL(fileURLWithPath: "/usr/bin/say")
185
- p.arguments = voice.map { ["-v", $0] } ?? []
200
+ let script = Self.script
201
+ let siri = voice?.hasPrefix("com.apple.ttsbundle.gryphon-neural_") ?? false
202
+ let current = (try? String(contentsOfFile: script, encoding: .utf8))?.contains("--stop") ?? false
203
+ if (voice == nil || siri || current) && FileManager.default.fileExists(atPath: script) {
204
+ p.executableURL = URL(fileURLWithPath: "/bin/sh")
205
+ p.arguments = [script] + (voice.map { [$0] } ?? [])
206
+ } else {
207
+ p.executableURL = URL(fileURLWithPath: "/usr/bin/say")
208
+ p.arguments = voice.flatMap { siri ? nil : ["-v", $0] } ?? []
209
+ }
186
210
  let input = Pipe()
187
211
  p.standardInput = input
188
212
  p.standardOutput = FileHandle.nullDevice
189
213
  p.standardError = FileHandle.nullDevice
190
214
  await withCheckedContinuation { (c: CheckedContinuation<Void, Never>) in
191
- p.terminationHandler = { _ in c.resume() }
215
+ let line = LineWatch(c)
216
+ p.terminationHandler = { _ in DispatchQueue.main.async { line.finish() } }
192
217
  do { try p.run() } catch {
193
218
  p.terminationHandler = nil
194
219
  Log.warn("say failed to start (\(error.localizedDescription))")
@@ -198,10 +223,33 @@ final class Player: NSObject, AVAudioPlayerDelegate {
198
223
  sayProcess = p
199
224
  input.fileHandleForWriting.write(Data(text.utf8))
200
225
  try? input.fileHandleForWriting.close()
226
+ // The line ends when its voice stops, even if `say` does not exit.
227
+ let pid = p.processIdentifier
228
+ line.watch { AudioOutput.playing(pid: pid) } onEnd: {
229
+ guard p.isRunning else { return }
230
+ // The script's trap stops `say`, restores the voice, and
231
+ // frees the lock for the next line.
232
+ Log.warn("say went quiet but kept running; stopping it")
233
+ p.terminate()
234
+ }
201
235
  }
202
236
  if sayProcess === p { sayProcess = nil }
203
237
  }
204
238
 
239
+ static let script = NSHomeDirectory() + "/.agentx/voice/siri-say.sh"
240
+
241
+ /// Stop the line holding the script's lock, whoever started it, and
242
+ /// drop every line queued behind it.
243
+ static func stopScript() {
244
+ guard FileManager.default.fileExists(atPath: script) else { return }
245
+ let p = Process()
246
+ p.executableURL = URL(fileURLWithPath: "/bin/sh")
247
+ p.arguments = [script, "--stop"]
248
+ p.standardOutput = FileHandle.nullDevice
249
+ p.standardError = FileHandle.nullDevice
250
+ do { try p.run() } catch { Log.warn("stop failed to start (\(error.localizedDescription))") }
251
+ }
252
+
205
253
  func play(_ mp3: Data) async throws {
206
254
  let p = try AVAudioPlayer(data: mp3)
207
255
  p.delegate = self
@@ -224,6 +272,36 @@ final class Player: NSObject, AVAudioPlayerDelegate {
224
272
  }
225
273
  }
226
274
 
275
+ /// One `say` line: resumed once, by the process exiting or by its voice
276
+ /// going quiet (SpeechEnd), whichever comes first. Main-queue confined.
277
+ private final class LineWatch: @unchecked Sendable {
278
+ private var continuation: CheckedContinuation<Void, Never>?
279
+ private var timer: DispatchSourceTimer?
280
+
281
+ init(_ c: CheckedContinuation<Void, Never>) { continuation = c }
282
+
283
+ func watch(_ playing: @escaping () -> Bool, onEnd: @escaping () -> Void) {
284
+ DispatchQueue.main.async {
285
+ guard self.continuation != nil else { return }
286
+ var end = SpeechEnd()
287
+ let t = DispatchSource.makeTimerSource(queue: .main)
288
+ t.schedule(deadline: .now() + 0.1, repeating: 0.1)
289
+ t.setEventHandler {
290
+ guard end.ended(playing: playing(), at: Date()) else { return }
291
+ onEnd()
292
+ self.finish()
293
+ }
294
+ self.timer = t
295
+ t.resume()
296
+ }
297
+ }
298
+
299
+ func finish() {
300
+ timer?.cancel(); timer = nil
301
+ continuation?.resume(); continuation = nil
302
+ }
303
+ }
304
+
227
305
  /// Logs to stderr AND to a file.
228
306
  ///
229
307
  /// The app is normally launched with `open`, which sends stderr nowhere.
@@ -0,0 +1,71 @@
1
+ import CoreAudio
2
+ import Darwin
3
+ import Foundation
4
+
5
+ /// When a spoken line has ended, as heard rather than as reported.
6
+ ///
7
+ /// `say` can go quiet and never exit: on 2026-09-25 one sat for eight
8
+ /// minutes after its Siri line, holding the speaker lock. Waiting for the
9
+ /// process kept the line scrolling in the widget long after the voice
10
+ /// stopped, and queued every later line, the daemon's too, behind it.
11
+ ///
12
+ /// So a line has ended once its process was heard playing audio and has
13
+ /// been silent for `quiet` seconds since. Before its first sound (waiting
14
+ /// for the speaker lock, starting up) nothing ends it.
15
+ struct SpeechEnd {
16
+ var quiet: TimeInterval = 0.3
17
+ private var heard = false
18
+ private var quietSince: Date?
19
+
20
+ init(quiet: TimeInterval = 0.3) { self.quiet = quiet }
21
+
22
+ /// One poll: is the line's process playing audio right now.
23
+ mutating func ended(playing: Bool, at now: Date) -> Bool {
24
+ if playing { heard = true; quietSince = nil; return false }
25
+ guard heard else { return false }
26
+ let since = quietSince ?? now
27
+ quietSince = since
28
+ return now.timeIntervalSince(since) >= quiet
29
+ }
30
+ }
31
+
32
+ enum AudioOutput {
33
+ /// True when `pid`, or a child of it (the script's `say`), is playing
34
+ /// audio. Core Audio's per-process view; another process's speech is
35
+ /// not visible to NSSpeechSynthesizer.isAnyApplicationSpeaking.
36
+ static func playing(pid: pid_t) -> Bool {
37
+ let pids = Set([pid] + children(of: pid))
38
+ return processObjects().contains { obj in
39
+ value(obj, kAudioProcessPropertyIsRunningOutput, UInt32(0)) != 0
40
+ && pids.contains(value(obj, kAudioProcessPropertyPID, pid_t(0)))
41
+ }
42
+ }
43
+
44
+ private static func children(of pid: pid_t) -> [pid_t] {
45
+ var buf = [pid_t](repeating: 0, count: 16)
46
+ let n = proc_listchildpids(pid, &buf, Int32(buf.count * MemoryLayout<pid_t>.size))
47
+ return n > 0 ? Array(buf.prefix(Int(n))) : []
48
+ }
49
+
50
+ private static func processObjects() -> [AudioObjectID] {
51
+ var addr = address(kAudioHardwarePropertyProcessObjectList)
52
+ let system = AudioObjectID(kAudioObjectSystemObject)
53
+ var size: UInt32 = 0
54
+ guard AudioObjectGetPropertyDataSize(system, &addr, 0, nil, &size) == noErr else { return [] }
55
+ var ids = [AudioObjectID](repeating: 0, count: Int(size) / MemoryLayout<AudioObjectID>.size)
56
+ guard AudioObjectGetPropertyData(system, &addr, 0, nil, &size, &ids) == noErr else { return [] }
57
+ return ids
58
+ }
59
+
60
+ private static func value<T: FixedWidthInteger>(_ obj: AudioObjectID, _ selector: AudioObjectPropertySelector, _ fallback: T) -> T {
61
+ var addr = address(selector)
62
+ var v = fallback
63
+ var size = UInt32(MemoryLayout<T>.size)
64
+ return AudioObjectGetPropertyData(obj, &addr, 0, nil, &size, &v) == noErr ? v : fallback
65
+ }
66
+
67
+ private static func address(_ selector: AudioObjectPropertySelector) -> AudioObjectPropertyAddress {
68
+ AudioObjectPropertyAddress(mSelector: selector, mScope: kAudioObjectPropertyScopeGlobal,
69
+ mElement: kAudioObjectPropertyElementMain)
70
+ }
71
+ }
@@ -21,6 +21,9 @@ final class App: NSObject, NSApplicationDelegate {
21
21
  private let recorder = Recorder()
22
22
  private var hotkey: Hotkey?
23
23
  private var pasteHotkey: Hotkey?
24
+ private var stopHotkey: Hotkey?
25
+ /// Set by a stop: the answer being spoken ends without reopening the mic.
26
+ private var silenced = false
24
27
  private var busy = false
25
28
  private var progress: Progress?
26
29
  private var ticker: Timer?
@@ -117,6 +120,16 @@ final class App: NSObject, NSApplicationDelegate {
117
120
  pasteHotkey?.register(keyCode: UInt32(kVK_ANSI_V),
118
121
  modifiers: UInt32(cmdKey | optionKey))
119
122
 
123
+ // ⌘⌥. (and the pill's menu): stop every voice now. ⌘. is the Mac's
124
+ // own "cancel"; Option keeps it from reaching the app in front.
125
+ stopHotkey = Hotkey(
126
+ id: 3,
127
+ onPress: {},
128
+ onRelease: { [weak self] in self?.stopSpeaking() })
129
+ stopHotkey?.register(keyCode: UInt32(kVK_ANSI_Period),
130
+ modifiers: UInt32(cmdKey | optionKey))
131
+ panel.onStop = { [weak self] in self?.stopSpeaking() }
132
+
120
133
  recorder.requestPermission { [weak self] granted in
121
134
  Task { @MainActor in
122
135
  guard let self else { return }
@@ -227,6 +240,16 @@ final class App: NSObject, NSApplicationDelegate {
227
240
  listenHandsFree(followUp: false)
228
241
  }
229
242
 
243
+ /// Barge-in without a question: every voice stops, queued lines are
244
+ /// dropped, and the widget goes quiet.
245
+ private func stopSpeaking() {
246
+ Log.info("stop: silencing every voice")
247
+ if busy { silenced = true }
248
+ lastSpokeAt = Date()
249
+ Task { await Speech.stopAll() }
250
+ if !recorder.isRecording { panel.render(.idle) }
251
+ }
252
+
230
253
  private func startListening() {
231
254
  // A held key overrides any hands-free window that is open, so the
232
255
  // two ways of talking never fight over the microphone.
@@ -299,6 +322,7 @@ final class App: NSObject, NSApplicationDelegate {
299
322
  /// One question and its spoken answer. Words said through the door
300
323
  /// while it thinks replace the answer with the next turn.
301
324
  private func runTurn(_ heard: String) async {
325
+ silenced = false
302
326
  do {
303
327
  beginNarration()
304
328
 
@@ -328,12 +352,16 @@ final class App: NSObject, NSApplicationDelegate {
328
352
  } else {
329
353
  card.orderOut(nil)
330
354
  }
355
+ // Stopped while it was thinking: the answer is not spoken.
356
+ if silenced { silenced = false; panel.render(.idle); busy = false; return }
331
357
  // Scroll the sentence being spoken, so it can be read as
332
358
  // well as heard — and re-read after, which speech cannot do.
333
359
  panel.render(.saying(answer.text))
334
360
  await Speech.speak(answer.text, voice: voice)
335
361
  // Cut off by the door: the new words are being recorded.
336
362
  if recorder.isRecording { busy = false; return }
363
+ // Stopped: no follow-up window, the listener asked for quiet.
364
+ if silenced { silenced = false; panel.render(.idle); busy = false; return }
337
365
  if let next = followUp { followUp = nil; return await runTurn(next) }
338
366
  panel.render(.idle)
339
367
  // Leave the microphone open for a moment. Say nothing and
@@ -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-FHF4RA4Q.js";import"./chunk-WFKS4O34.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
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-SJPLXXCI.js";import"./chunk-IAG6FI3Q.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
2
2
 
3
3
  `))>=0;){let h=y.slice(0,d);y=y.slice(d+2);let m=X(h);m&&(yield m)}}}finally{try{u.releaseLock()}catch{}}}function X(e){let r="message",t="";for(let s of e.split(`
4
4
  `))s.startsWith("event: ")?r=s.slice(7):s.startsWith("data: ")&&(t=s.slice(6));if(!t)return null;let n;try{n=JSON.parse(t)}catch{n=t}return{event:r,data:n}}import{jsx as a,jsxs as c}from"react/jsx-runtime";function $(e,r){return r<=0||e<0?0:e>=r?r-1:e}function E(){return`tui:${z().slice(0,12)}`}function k(e,r){let t=[...e,r];return t.length>50&&t.splice(0,t.length-50),t}function Y(e,r){switch(r.type){case"agents":return{...e,agents:r.data,cursor:{...e.cursor,agents:$(e.cursor.agents,r.data.length)}};case"processes":return{...e,processes:r.data,cursor:{...e.cursor,processes:$(e.cursor.processes,r.data.length)}};case"crons":return{...e,crons:r.data};case"event":{let t=[r.row,...e.events];t.length>200&&(t.length=200);let n=e.channels;return r.channelTally&&(n=new Map(e.channels),n.set(r.channelTally.channel,r.channelTally)),{...e,events:t,channels:n}}case"connError":return{...e,connError:r.error};case"tick":return{...e,lastTick:Date.now()};case"focus":return{...e,focus:r.pane};case"cursor":{let t=r.pane==="agents"?e.agents.length:r.pane==="processes"?e.processes.length:e.events.length;return{...e,cursor:{...e.cursor,[r.pane]:$(e.cursor[r.pane]+r.delta,t)}}}case"bottomRight":return{...e,bottomRight:r.pane};case"chatStart":return{...e,chat:{active:!0,agentId:r.agentId,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatExit":return{...e,chat:{...e.chat,active:!1}};case"chatClear":return{...e,chat:{...e.chat,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatText":return{...e,chat:{...e.chat,text:r.text}};case"chatSubmitStart":return{...e,chat:{...e.chat,history:k(e.chat.history,r.you),text:"",status:"sending",error:null}};case"chatSubmitDone":return{...e,chat:{...e.chat,history:k(e.chat.history,r.reply),status:"idle",error:null}};case"chatSubmitError":return{...e,chat:{...e.chat,history:k(e.chat.history,{role:"error",text:r.error,at:Date.now()}),status:"idle",error:r.error}};case"chatStreamStart":{let t=k(e.chat.history,r.you),n=k(t,{role:"agent",text:"",at:Date.now(),streaming:!0,tools:[]});return{...e,chat:{...e.chat,history:n,text:"",status:"sending",error:null}}}case"chatStreamDelta":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,text:n.text+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamThinking":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,thinking:(n.thinking??"")+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamTool":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,tools:[...n.tools??[],{name:r.name,arg:r.arg,error:r.error}]}),{...e,chat:{...e.chat,history:t}}}case"chatStreamEnd":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]=r.error?{...n,streaming:!1,role:"error",text:r.error}:{...n,streaming:!1,elapsedMs:r.elapsedMs,outTokens:r.outTokens}),{...e,chat:{...e.chat,history:t,status:"idle",error:r.error??null}}}case"toast":return{...e,toast:{text:r.text,color:r.color,at:Date.now()}};case"clearToast":return{...e,toast:null}}}var _={agents:[],processes:[],crons:[],events:[],channels:new Map,connError:null,lastTick:0,focus:"agents",cursor:{agents:0,processes:0,events:0},bottomRight:"crons",chat:{active:!1,agentId:null,chatId:E(),history:[],text:"",status:"idle",error:null},toast:null};function be({conn:e,pollMs:r=3e3}){let[t,n]=V(Y,_),{exit:s}=W(),u=G(null),p=G(null),y=t.agents.map(l=>l.id),f=t.chat.active?L(t.chat.text,y,process.cwd()):null;return R(()=>{if(!t.toast)return;let l=setTimeout(()=>n({type:"clearToast"}),3500);return()=>clearTimeout(l)},[t.toast]),J((l,d)=>{if(t.chat.active){if(d.escape){n({type:"chatExit"});return}if(d.ctrl&&l==="l"){n({type:"chatClear"});return}if(d.tab){let m=U(t.chat.text,p,y,process.cwd());m!==null&&n({type:"chatText",text:m});return}if(d.backspace||d.delete){p.current=null,n({type:"chatText",text:t.chat.text.slice(0,-1)});return}if(d.ctrl||d.meta)return;let h=N(l??"",d.return,t.chat.text);if(h.kind==="paste"||h.kind==="type"){p.current=null,n({type:"chatText",text:t.chat.text+h.text});return}if(h.kind==="continue"){n({type:"chatText",text:h.buffer});return}if(h.kind==="submit"){let m=h.text.trim(),g=t.chat.agentId;if(!m||!g||t.chat.status==="sending"){n({type:"chatText",text:""});return}p.current=null;let b=Date.now(),w=t.chat.chatId;n({type:"chatStreamStart",you:{role:"you",text:m,at:b}}),(async()=>{try{let C=await M(e,g,m,{channel:"tui",chatId:w,onText:x=>n({type:"chatStreamDelta",text:x}),onThinking:x=>n({type:"chatStreamThinking",text:x}),onTool:x=>{x.status==="start"&&x.name?n({type:"chatStreamTool",name:x.name,arg:x.arg}):x.status==="result"&&x.error&&n({type:"chatStreamTool",name:x.name??"tool",error:!0})}});n({type:"chatStreamEnd",elapsedMs:Date.now()-b,outTokens:C.usage?.outputTokens,error:C.error})}catch(C){n({type:"chatStreamEnd",elapsedMs:Date.now()-b,error:C?.message||String(C)})}})();return}return}if(l==="q"||d.ctrl&&l==="c"){u.current?.abort(),s();return}if(d.tab){let h=["agents","processes","events"],m=h.indexOf(t.focus);n({type:"focus",pane:h[(m+1)%h.length]});return}if(l==="a"){n({type:"focus",pane:"agents"});return}if(l==="p"){n({type:"focus",pane:"processes"});return}if(l==="e"){n({type:"focus",pane:"events"});return}if(l==="c"){n({type:"bottomRight",pane:t.bottomRight==="crons"?"channels":"crons"});return}if(d.upArrow){n({type:"cursor",pane:t.focus,delta:-1});return}if(d.downArrow){n({type:"cursor",pane:t.focus,delta:1});return}if(d.return&&t.focus==="agents"){let h=t.agents[t.cursor.agents];if(!h){n({type:"toast",text:"no agent selected",color:"yellow"});return}n({type:"chatStart",agentId:h.id});return}if(l==="k"&&t.focus==="processes"){let h=t.processes[t.cursor.processes];if(!h){n({type:"toast",text:"no process selected",color:"yellow"});return}(async()=>{try{await P(e,h.key,"tui"),n({type:"toast",text:`killed ${h.key.agentId}/${h.key.channel}/${h.key.chatId}`,color:"green"})}catch(m){n({type:"toast",text:`kill failed: ${m?.message||m}`,color:"red"})}})();return}}),R(()=>{let l=new AbortController;u.current=l;let d=!1,h=async()=>{try{let[g,b,w]=await Promise.all([B(e,l.signal).catch(()=>[]),D(e,l.signal).catch(()=>[]),A(e,l.signal).catch(()=>[])]);if(d)return;n({type:"agents",data:g}),n({type:"processes",data:b}),n({type:"crons",data:w}),n({type:"tick"}),n({type:"connError",error:null})}catch(g){d||n({type:"connError",error:g?.message||String(g)})}};h();let m=setInterval(h,r);return()=>{d=!0,clearInterval(m),l.abort()}},[e.baseUrl,e.token,r]),R(()=>{let l=!1,d=new AbortController,h=new Map;return(async()=>{let m=500;for(;!l;){try{for await(let g of q({baseUrl:e.baseUrl,token:e.token,signal:d.signal})){let b=ae(g),w;if(g.event==="channel"&&typeof g.data?.channel=="string"){let S=h.get(g.data.channel)??{channel:g.data.channel,inbound:0,outbound:0,lastAt:0,lastPreview:""},C=S.inbound+(g.data.direction==="in"?1:0),x=S.outbound+(g.data.direction==="out"?1:0);w={channel:g.data.channel,inbound:C,outbound:x,lastAt:b.at,lastPreview:(g.data.textPreview||"").slice(0,60)},h.set(g.data.channel,w)}n({type:"event",row:b,channelTally:w}),m=500}}catch(g){if(l)return;n({type:"connError",error:`events: ${g?.message||g}`})}await new Promise(g=>setTimeout(g,m)),m=Math.min(m*2,1e4)}})(),()=>{l=!0,d.abort()}},[e.baseUrl,e.token]),c(i,{flexDirection:"column",width:"100%",children:[a(K,{conn:e,lastTick:t.lastTick,connError:t.connError,chat:t.chat}),c(i,{flexDirection:"row",flexGrow:1,children:[c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"AGENTS",focused:!t.chat.active&&t.focus==="agents",children:a(Z,{rows:t.agents,cursor:t.cursor.agents,focused:!t.chat.active&&t.focus==="agents",talkingTo:t.chat.active?t.chat.agentId:null})}),a(v,{title:"PROCESSES",focused:!t.chat.active&&t.focus==="processes",children:a(ee,{rows:t.processes,cursor:t.cursor.processes,focused:!t.chat.active&&t.focus==="processes"})})]}),t.chat.active?a(oe,{chat:t.chat,suggest:f,agents:t.agents}):c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"LIVE EVENTS",focused:t.focus==="events",children:a(ne,{rows:t.events,cursor:t.cursor.events,focused:t.focus==="events"})}),a(v,{title:t.bottomRight==="crons"?"CRONS / SCHEDULES":"CHANNELS",focused:!1,children:t.bottomRight==="crons"?a(te,{rows:t.crons}):a(re,{rows:Array.from(t.channels.values())})})]})]}),a(Q,{focus:t.focus,bottomRight:t.bottomRight,chat:t.chat,toast:t.toast})]})}function K({conn:e,lastTick:r,connError:t,chat:n}){let s=t?c(o,{color:"red",children:["\u25CF ",t]}):r===0?a(o,{color:"yellow",children:"\u25CF connecting\u2026"}):a(o,{color:"green",children:"\u25CF live"}),u=n.active?c(o,{color:"green",bold:!0,children:["CHAT @",n.agentId]}):a(o,{bold:!0,children:"agentx tui"});return c(i,{paddingX:1,justifyContent:"space-between",children:[a(i,{children:u}),a(o,{dimColor:!0,children:e.baseUrl}),a(i,{children:s})]})}function Q({focus:e,bottomRight:r,chat:t,toast:n}){let s=t.active?["[Enter] send","[Esc] exit chat","[Ctrl-L] new session"]:[`[Tab/a/p/e] focus (now: ${e})`,e==="agents"?"[Enter] chat":null,e==="processes"?"[k] kill":null,`[c] ${r==="crons"?"\u2192channels":"\u2192crons"}`,"[\u2191/\u2193] move","[q] quit"].filter(Boolean);return c(i,{paddingX:1,borderStyle:"single",borderTop:!0,borderBottom:!1,borderLeft:!1,borderRight:!1,justifyContent:"space-between",children:[a(o,{dimColor:!0,children:s.join(" \xB7 ")}),n?a(o,{color:n.color,children:n.text}):a(o,{children:" "})]})}function v({title:e,focused:r,children:t}){return c(i,{flexDirection:"column",width:"100%",borderStyle:r?"double":"single",borderColor:r?"cyan":void 0,paddingX:1,children:[c(o,{bold:!0,color:r?"cyan":"white",children:[e,r?" \u25CF":""]}),a(i,{flexDirection:"column",marginTop:1,children:t})]})}function I(e,r){return e&&r?{inverse:!0}:{}}function Z({rows:e,cursor:r,focused:t,talkingTo:n}){return e.length===0?a(o,{dimColor:!0,children:"no agents"}):a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=s.errors>0?"red":s.active>0?"yellow":"green",f=s.id===n;return c(i,{children:[a(o,{...p,color:y,children:"\u25CF"}),c(o,{...p,children:[" ",T(s.name,14)]}),c(o,{...p,children:[T(s.tier,6)," "]}),c(o,{...p,children:[s.active,"/",s.total]}),s.errors>0?c(o,{...p,color:"red",children:[" err=",s.errors]}):null,f?a(o,{color:"green",children:" \u2194 chatting"}):null]},s.id)})})}function ee({rows:e,cursor:r,focused:t}){if(e.length===0)return a(o,{dimColor:!0,children:"no live processes"});let n=Date.now();return a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=Math.max(0,Math.round((n-(s.lastTurnAt||s.spawnedAt))/1e3)),f=s.state==="dead"?"red":s.state==="warm-hot"?"green":"yellow";return c(i,{children:[a(o,{...p,color:f,children:T(s.state,10)}),a(o,{...p,children:T(s.key.agentId,12)}),a(o,{...p,children:T(s.key.channel,10)}),c(o,{...p,children:[s.turnCount,"t ",y,"s"]})]},`${s.key.agentId}-${s.key.channel}-${s.key.chatId}-${u}`)})})}function te({rows:e}){return e.length===0?a(o,{dimColor:!0,children:"no crons"}):a(i,{flexDirection:"column",children:e.slice(0,12).map(r=>{let t=r.enabled?r.consecutiveErrors>0?a(o,{color:"red",children:"err"}):a(o,{color:"green",children:"ok"}):a(o,{dimColor:!0,children:"off"}),n=r.nextRun?new Date(r.nextRun).toLocaleTimeString():"\u2014";return c(i,{children:[a(o,{children:T(r.id,16)}),a(o,{dimColor:!0,children:T(r.schedule,12)}),a(o,{children:T(n,10)}),a(i,{children:t})]},r.id)})})}function re({rows:e}){if(e.length===0)return a(o,{dimColor:!0,children:"(waiting for channel events\u2026)"});let r=[...e].sort((t,n)=>n.lastAt-t.lastAt);return a(i,{flexDirection:"column",children:r.slice(0,12).map(t=>c(i,{children:[a(o,{children:T(t.channel,12)}),c(o,{color:"cyan",children:["\u2190",T(String(t.inbound),4)]}),c(o,{color:"magenta",children:["\u2192",T(String(t.outbound),4)]}),a(o,{dimColor:!0,children:t.lastPreview})]},t.channel))})}function ne({rows:e,cursor:r,focused:t}){return e.length===0?a(o,{dimColor:!0,children:"(waiting for events\u2026)"}):a(i,{flexDirection:"column",children:e.slice(0,18).map((n,s)=>{let u=I(t,s===r);return c(i,{children:[a(o,{...u,dimColor:!0,children:new Date(n.at).toLocaleTimeString().padEnd(10)}),a(o,{...u,color:ce(n.kind),children:T(n.kind,8)}),a(o,{...u,children:n.line})]},`${n.at}-${s}`)})})}function oe({chat:e,suggest:r,agents:t}){let n=e.history.slice(-14),s=t.find(f=>f.id===e.agentId),u=H(s?.tier??"",s?.model),p=e.status==="sending"?[...e.history].reverse().find(f=>f.role==="agent"&&f.streaming):void 0,y=e.status==="sending"?a(O,{startedAt:p?.at??Date.now(),phase:p?.text?"responding":"thinking",indent:""}):e.error?c(o,{color:"red",children:["\u25CF ",e.error]}):a(o,{dimColor:!0,children:"\u25CF ready"});return c(i,{flexDirection:"column",width:"50%",borderStyle:"double",borderColor:"green",paddingX:1,children:[c(i,{justifyContent:"space-between",children:[c(o,{bold:!0,color:"green",children:["CHAT \xB7 @",e.agentId]}),a(i,{children:y})]}),c(o,{children:[c(o,{color:u.color,bold:!0,children:[u.glyph," ",u.label]}),s?.model?c(o,{dimColor:!0,children:[" ",j(s.model)]}):null,c(o,{dimColor:!0,children:[" \xB7 ",e.chatId]})]}),a(i,{flexDirection:"column",flexGrow:1,marginTop:1,children:n.length===0?a(o,{dimColor:!0,children:"(type a message and press Enter \u2014 Esc exits, Ctrl-L starts fresh)"}):n.map((f,l)=>a(se,{turn:f,agentId:e.agentId},`${f.at}-${l}`))}),r?c(o,{dimColor:!0,children:[a(o,{color:"cyan",children:"\u21B9"})," ",r.items.slice(0,5).map((f,l)=>l===0?a(o,{color:"cyan",children:f},l):c(o,{children:[" ",f]},l))]}):null,c(i,{children:[a(o,{color:"cyan",children:"you \u203A "}),a(o,{children:e.text}),a(o,{inverse:!0,children:" "})]})]})}function se({turn:e,agentId:r}){if(e.role==="you")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"cyan",children:"you"}),e.text.split(`
5
5
  `).map((s,u)=>c(o,{children:[" ",s]},u))]});if(e.role==="error")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"red",children:"error"}),c(o,{color:"red",children:[" ",e.text]})]});let t=e.elapsedMs!=null?` \xB7 ${(e.elapsedMs/1e3).toFixed(1)}s${e.outTokens!=null?`, ${e.outTokens} tok`:""}`:e.streaming?" \xB7 \u2026":"",n=F(e.text,58,{balance:e.streaming});return c(i,{flexDirection:"column",marginBottom:1,children:[c(o,{color:"green",children:["@",r??"agent",a(o,{dimColor:!0,children:t})]}),e.thinking?e.thinking.split(`
6
6
  `).filter(s=>s.trim()).slice(-2).map((s,u)=>c(o,{dimColor:!0,italic:!0,children:[" ",s]},`th${u}`)):null,(e.tools??[]).map((s,u)=>c(o,{color:s.error?"red":"green",children:[" \u25CF ",c(o,{bold:!s.error,dimColor:s.error,children:[s.name,s.arg?c(o,{dimColor:!0,children:["(",s.arg,")"]}):null,s.error?" failed":""]})]},`tool${u}`)),n.split(`
7
7
  `).slice(0,14).map((s,u)=>c(o,{children:[" ",s]},u))]})}function ae(e){let r=e.data??{},t=typeof r.at=="string"||typeof r.at=="number"?new Date(r.at).getTime():Date.now(),n="";switch(e.event){case"run":n=`${r.phase??""} ${r.workflowId??""} ${(r.runId??"").slice(0,8)} ${r.nodeId??""}`.trim();break;case"task":n=`${r.phase??""} ${r.workflowId??""} ${Array.isArray(r.assignedTo)?r.assignedTo.join(","):""} ${r.title?`"${r.title}"`:""}`.trim();break;case"signal":n=`${r.name??""} (${r.scope??""}) ${r.workflowId??""}`.trim();break;case"mesh":n=`${r.peer??""} ${r.healthy?"healthy":"lost"} ${r.delta??""}`.trim();break;case"channel":{n=`${r.direction==="in"?"\u2190":"\u2192"} ${r.channel??""}:${r.chatId??""} ${r.textPreview??""}`.trim();break}case"status":n=`node=${r.node??""} agents=${r.agents??0} active=${(r.active??[]).length}`;break;default:n=typeof r=="string"?r:JSON.stringify(r).slice(0,80)}return{at:Number.isFinite(t)?t:Date.now(),kind:e.event,line:n}}function ce(e){switch(e){case"run":return"cyan";case"task":return"magenta";case"signal":return"green";case"mesh":return"blue";case"channel":return"yellow";default:return"white"}}function T(e,r){let t=(e??"").toString();return t.length>=r?t.slice(0,r-1)+" ":t+" ".repeat(r-t.length)}export{be as App};
8
- //# sourceMappingURL=App-TW5N7JY2.js.map
8
+ //# sourceMappingURL=App-HSRGG5VA.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-FHF4RA4Q.js";import"./chunk-WFKS4O34.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
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-SJPLXXCI.js";import"./chunk-IAG6FI3Q.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
2
2
  `):[""],B=A.find(s=>s.id===T),D=ne(B?.tier??"",B?.model),pe=O?.rows||24,fe=z.slice(-40);return o(g,{flexDirection:"column",height:pe,children:[o(g,{flexDirection:"column",flexGrow:1,justifyContent:"flex-end",overflow:"hidden",children:[z.length===0&&!$?o(g,{flexDirection:"column",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),c(t,{dimColor:!0,children:" \u2014 chat with "}),o(t,{color:m.accent,children:["@",T]}),c(t,{dimColor:!0,children:" via "}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" (",B.model,")"]}):null]}),c(t,{dimColor:!0,children:"streaming \xB7 markdown \xB7 tools \xB7 type a message, or / for commands"})]}):null,fe.map(s=>c(ce,{turn:s,width:V},s.id)),$?c(ce,{turn:$,width:V}):null]}),N?c(g,{flexDirection:"column",marginTop:1,children:N.map((s,e)=>o(g,{children:[c(t,{color:e===0?m.accent:m.muted,bold:e===0,children:s.cmd.padEnd(10)}),c(t,{dimColor:!0,children:s.desc})]},s.cmd))}):j?o(g,{marginTop:1,children:[c(t,{color:m.accent,children:"\u21B9 "}),j.items.map((s,e)=>o(t,{color:e===0?m.accent:m.muted,children:[s,e<j.items.length-1?" ":""]},e))]}):J?c(g,{marginTop:1,children:c(t,{dimColor:!0,children:J})}):null,c(g,{borderStyle:"single",borderColor:G,borderLeft:!1,borderRight:!1,borderDimColor:!v,width:"100%",children:c(g,{flexDirection:"column",width:"100%",children:ge.map((s,e,r)=>o(g,{children:[c(t,{color:G,bold:!0,children:e===0?"\u203A ":" "}),c(t,{children:s}),e===r.length-1&&!v?c(t,{color:G,children:"\u258F"}):null]},e))})}),o(g,{justifyContent:"space-between",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),o(t,{dimColor:!0,children:[" \xB7 @",T," \xB7 "]}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" ",re(B.model)]}):null]}),o(t,{dimColor:!0,children:["\u21B5 send / cmds @ mention esc ",v?"stop":"exit"]})]})]})}function ce({turn:n,width:p}){let f=H(()=>ye(n,p),[n,p]),a=n.segs.some(h=>h.type==="text"&&h.content);return o(g,{flexDirection:"column",marginBottom:1,children:[o(t,{color:m.accent,bold:!0,children:[F," you"]}),n.you.split(`
3
3
  `).map((h,w)=>o(t,{color:"white",children:[" ",h]},`u${w}`)),o(t,{color:m.agent,bold:!0,children:[F," ",n.agentId??"agent"]}),n.thinking?c(g,{flexDirection:"column",children:we(n.thinking,n.live?3:2).map((h,w)=>o(t,{dimColor:!0,italic:!0,children:[" ",h]},`th${w}`))}):null,f,n.live?c(te,{startedAt:n.startedAt,phase:a?"responding":"thinking",hint:"esc to interrupt"}):n.interrupted?o(t,{dimColor:!0,children:[" \u2298 interrupted \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s"]}):n.error?o(t,{color:"red",children:[" \u2717 ",n.error]}):n.elapsedMs!=null?o(t,{dimColor:!0,children:[" \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s",n.outTokens!=null?`, ${n.outTokens} tok`:""]}):null]})}function we(n,p){return n.split(`
4
4
  `).filter(a=>a.trim()).slice(-p)}function ye(n,p){let f=[];return n.segs.forEach((a,h)=>{a.type==="tool"?f.push(o(t,{color:a.error?"red":"green",children:[" ","\u25CF ",o(t,{bold:!a.error,dimColor:a.error,children:[a.name,a.arg?o(t,{dimColor:!0,children:["(",a.arg,")"]}):null,a.error?" failed":""]})]},h)):a.content&&f.push(c(t,{children:Ce(Y(a.content,p,{balance:n.live}))},h))}),f}function Ce(n){return n.split(`
5
5
  `).map(p=>p.length?" "+p:p).join(`
6
6
  `)}export{Pe as ChatApp};
7
- //# sourceMappingURL=ChatApp-MRGX7JBT.js.map
7
+ //# sourceMappingURL=ChatApp-4FOU6Q3S.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-HEV3AVFJ.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
2
- //# sourceMappingURL=agent-FQHWBQP2.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-WBGROLOX.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
2
+ //# sourceMappingURL=agent-54NUP2Z3.js.map