agentix-cli 0.47.1 → 0.49.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 (178) hide show
  1. package/apps/mac-helper/Sources/AgentXHelper/Buffer.swift +65 -0
  2. package/apps/mac-helper/Sources/AgentXHelper/Notify.swift +26 -0
  3. package/apps/mac-helper/Sources/AgentXHelper/Ring.swift +58 -0
  4. package/apps/mac-helper/Sources/AgentXHelper/Screens.swift +7 -0
  5. package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +13 -8
  6. package/apps/mac-helper/Sources/AgentXHelper/Watch.swift +87 -0
  7. package/apps/mac-helper/Sources/AgentXHelper/main.swift +72 -18
  8. package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +17 -5
  9. package/apps/mac-voice/Sources/AgentXVoice/Config.swift +6 -1
  10. package/apps/mac-voice/Sources/AgentXVoice/main.swift +5 -2
  11. package/dist/{App-RZDJH6MK.js → App-KE4SURSS.js} +2 -2
  12. package/dist/{ChatApp-6TXOFUDH.js → ChatApp-NNHIKXO2.js} +2 -2
  13. package/dist/{agent-I6RXAEXG.js → agent-J7SLJYQ6.js} +2 -2
  14. package/dist/{board-dashboard-4LAQOB74.js → board-dashboard-LG3ECH7C.js} +494 -132
  15. package/dist/board-dashboard-LG3ECH7C.js.map +1 -0
  16. package/dist/chunk-3L2FKVR6.js +19 -0
  17. package/dist/chunk-3L2FKVR6.js.map +1 -0
  18. package/dist/{chunk-BZLHS3VF.js → chunk-4NBWF4TL.js} +2 -2
  19. package/dist/{schedule-tool-6SNLG2NA.js → chunk-4TITGMPX.js} +3 -3
  20. package/dist/{schedule-tool-6SNLG2NA.js.map → chunk-4TITGMPX.js.map} +1 -1
  21. package/dist/{chunk-NXJBAKBR.js → chunk-5ALS7SVV.js} +2 -2
  22. package/dist/chunk-5XQCGPQJ.js +2 -0
  23. package/dist/chunk-5XQCGPQJ.js.map +1 -0
  24. package/dist/chunk-7DXE264B.js +2 -0
  25. package/dist/chunk-7DXE264B.js.map +1 -0
  26. package/dist/{chunk-FRHSQVFN.js → chunk-AQASR6KR.js} +3 -3
  27. package/dist/chunk-AQASR6KR.js.map +1 -0
  28. package/dist/{chunk-LYOXXD4T.js → chunk-B5XHX44Y.js} +2 -2
  29. package/dist/{chunk-7CVJ4JOE.js → chunk-DLKRPK5M.js} +2 -2
  30. package/dist/chunk-DTJKN5VT.js +28 -0
  31. package/dist/chunk-DTJKN5VT.js.map +1 -0
  32. package/dist/chunk-DVNIFP7S.js +23 -0
  33. package/dist/chunk-DVNIFP7S.js.map +1 -0
  34. package/dist/chunk-FM2GGPSD.js +626 -0
  35. package/dist/chunk-FM2GGPSD.js.map +1 -0
  36. package/dist/chunk-FULEQYM6.js +13 -0
  37. package/dist/chunk-FULEQYM6.js.map +1 -0
  38. package/dist/{chunk-3RHPZXCR.js → chunk-GCHUE3TN.js} +2 -2
  39. package/dist/{chunk-YIXQDOPN.js → chunk-HGT4AKOH.js} +185 -179
  40. package/dist/chunk-HGT4AKOH.js.map +1 -0
  41. package/dist/{chunk-XMRI32CY.js → chunk-HVQJIROJ.js} +64 -74
  42. package/dist/chunk-HVQJIROJ.js.map +1 -0
  43. package/dist/{chunk-BJWK7FPT.js → chunk-IXUEJBHD.js} +2 -2
  44. package/dist/{chunk-KTM7HSEB.js → chunk-JVNFRS26.js} +2 -2
  45. package/dist/{chunk-V7FYSZSV.js → chunk-LQRBGQIR.js} +2 -2
  46. package/dist/chunk-LQRBGQIR.js.map +1 -0
  47. package/dist/{chunk-XMWOV2D5.js → chunk-MWUJOAAW.js} +2 -2
  48. package/dist/{chunk-DJQA2CWD.js → chunk-N6EWZGIX.js} +6 -6
  49. package/dist/{chunk-DJQA2CWD.js.map → chunk-N6EWZGIX.js.map} +1 -1
  50. package/dist/{chunk-J63MRNXF.js → chunk-NLZD2QKF.js} +2 -2
  51. package/dist/chunk-OHA3J5ZV.js +243 -0
  52. package/dist/chunk-OHA3J5ZV.js.map +1 -0
  53. package/dist/chunk-OTW5MPFT.js +7 -0
  54. package/dist/chunk-OTW5MPFT.js.map +1 -0
  55. package/dist/chunk-OUZ7YVQZ.js +5 -0
  56. package/dist/{chunk-BHPIAG3E.js.map → chunk-OUZ7YVQZ.js.map} +1 -1
  57. package/dist/chunk-P2ZYT5D6.js +15 -0
  58. package/dist/chunk-P2ZYT5D6.js.map +1 -0
  59. package/dist/{chunk-FNLGY4UV.js → chunk-PZCTCCC5.js} +2 -2
  60. package/dist/chunk-Q7C3X3R6.js +61 -0
  61. package/dist/chunk-Q7C3X3R6.js.map +1 -0
  62. package/dist/{chunk-MDTKQZC6.js → chunk-QDE75R4U.js} +2 -2
  63. package/dist/{chunk-VHTFHGVW.js → chunk-RKAJR37K.js} +51 -51
  64. package/dist/chunk-RKAJR37K.js.map +1 -0
  65. package/dist/chunk-RKPAVWXF.js +2 -0
  66. package/dist/chunk-RKPAVWXF.js.map +1 -0
  67. package/dist/{chunk-SFLF64Q3.js → chunk-Z3IP66SL.js} +2 -2
  68. package/dist/cli.js +1 -1
  69. package/dist/cli.js.map +1 -1
  70. package/dist/client-JZTH46N2.js +2 -0
  71. package/dist/{compaction-QXU3A4LQ.js → compaction-U5SAYTX6.js} +2 -2
  72. package/dist/config-R47ITLIY.js +2 -0
  73. package/dist/config-mutator-T6JGXJ26.js +2 -0
  74. package/dist/context-planner-FJMU5WAT.js +17 -0
  75. package/dist/context-planner-FJMU5WAT.js.map +1 -0
  76. package/dist/doctor-2DMGAWMW.js +2 -0
  77. package/dist/failure-candidates-ARRMFXRY.js +8 -0
  78. package/dist/failure-candidates-ARRMFXRY.js.map +1 -0
  79. package/dist/index.d.ts +272 -0
  80. package/dist/index.js +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/memory-extract-YZI2QZVR.js +2 -0
  83. package/dist/processes-XWRGOLMT.js +2 -0
  84. package/dist/program-3CDFUJVE.js +2 -0
  85. package/dist/promote-57YNOVXV.js +2 -0
  86. package/dist/proposals-TX2HBBYV.js +2 -0
  87. package/dist/query-ZT7WXY2F.js +2 -0
  88. package/dist/registry-722GXALE.js +2 -0
  89. package/dist/{request-planner-5354IOYM.js → request-planner-PEAYYXAP.js} +2 -2
  90. package/dist/{review-candidates-UTWMO7UO.js → review-candidates-W7NIHNAI.js} +2 -2
  91. package/dist/{routing-JGNWB6VJ.js → routing-7LCO7UJ7.js} +2 -2
  92. package/dist/schedule-tool-ZD3VBUBW.js +2 -0
  93. package/dist/scheduler-GC3FQKXY.js +2 -0
  94. package/dist/{seat-STLDEYSO.js → seat-HR6QMAR2.js} +2 -2
  95. package/dist/{select-VP66P6XB.js → select-4XYRIZ5L.js} +2 -2
  96. package/dist/{surface-usage-FM2ZHGYE.js → surface-usage-V2BX4SBD.js} +2 -2
  97. package/dist/tool-PAQ47HV2.js +2 -0
  98. package/dist/tool-PAQ47HV2.js.map +1 -0
  99. package/dist/wiki-EREMPSAE.js +2 -0
  100. package/dist/wiki-EREMPSAE.js.map +1 -0
  101. package/package.json +1 -1
  102. package/dist/board-dashboard-4LAQOB74.js.map +0 -1
  103. package/dist/chunk-7L6M3P2N.js +0 -13
  104. package/dist/chunk-7L6M3P2N.js.map +0 -1
  105. package/dist/chunk-ANLKBCYH.js +0 -15
  106. package/dist/chunk-ANLKBCYH.js.map +0 -1
  107. package/dist/chunk-BHPIAG3E.js +0 -5
  108. package/dist/chunk-CQR6C5FU.js +0 -23
  109. package/dist/chunk-CQR6C5FU.js.map +0 -1
  110. package/dist/chunk-FQBPSL36.js +0 -25
  111. package/dist/chunk-FQBPSL36.js.map +0 -1
  112. package/dist/chunk-FRHSQVFN.js.map +0 -1
  113. package/dist/chunk-FXGSSZAP.js +0 -9
  114. package/dist/chunk-FXGSSZAP.js.map +0 -1
  115. package/dist/chunk-OSSI6R4K.js +0 -2
  116. package/dist/chunk-OSSI6R4K.js.map +0 -1
  117. package/dist/chunk-QURVHYBP.js +0 -616
  118. package/dist/chunk-QURVHYBP.js.map +0 -1
  119. package/dist/chunk-V7FYSZSV.js.map +0 -1
  120. package/dist/chunk-VHTFHGVW.js.map +0 -1
  121. package/dist/chunk-VZDKE2EZ.js +0 -241
  122. package/dist/chunk-VZDKE2EZ.js.map +0 -1
  123. package/dist/chunk-W544ZCCO.js +0 -7
  124. package/dist/chunk-W544ZCCO.js.map +0 -1
  125. package/dist/chunk-WZGABQ72.js +0 -59
  126. package/dist/chunk-WZGABQ72.js.map +0 -1
  127. package/dist/chunk-XMRI32CY.js.map +0 -1
  128. package/dist/chunk-YIXQDOPN.js.map +0 -1
  129. package/dist/client-LAIP23IP.js +0 -2
  130. package/dist/config-OZ2OOKFA.js +0 -2
  131. package/dist/config-mutator-EUF7QQ4V.js +0 -2
  132. package/dist/context-planner-AQHJMVY2.js +0 -17
  133. package/dist/context-planner-AQHJMVY2.js.map +0 -1
  134. package/dist/doctor-PEE3F5TX.js +0 -2
  135. package/dist/memory-extract-3TO7UC5B.js +0 -2
  136. package/dist/processes-I3XAPHG7.js +0 -2
  137. package/dist/program-PMHQRD5E.js +0 -2
  138. package/dist/promote-RNSGW4AJ.js +0 -2
  139. package/dist/proposals-MWMEXRDW.js +0 -2
  140. package/dist/query-BOUVRUU4.js +0 -2
  141. package/dist/registry-GGU43EOI.js +0 -2
  142. package/dist/scheduler-N25EAWF6.js +0 -2
  143. package/dist/wiki-SKIIAVCU.js +0 -2
  144. /package/dist/{App-RZDJH6MK.js.map → App-KE4SURSS.js.map} +0 -0
  145. /package/dist/{ChatApp-6TXOFUDH.js.map → ChatApp-NNHIKXO2.js.map} +0 -0
  146. /package/dist/{agent-I6RXAEXG.js.map → agent-J7SLJYQ6.js.map} +0 -0
  147. /package/dist/{chunk-BZLHS3VF.js.map → chunk-4NBWF4TL.js.map} +0 -0
  148. /package/dist/{chunk-NXJBAKBR.js.map → chunk-5ALS7SVV.js.map} +0 -0
  149. /package/dist/{chunk-LYOXXD4T.js.map → chunk-B5XHX44Y.js.map} +0 -0
  150. /package/dist/{chunk-7CVJ4JOE.js.map → chunk-DLKRPK5M.js.map} +0 -0
  151. /package/dist/{chunk-3RHPZXCR.js.map → chunk-GCHUE3TN.js.map} +0 -0
  152. /package/dist/{chunk-BJWK7FPT.js.map → chunk-IXUEJBHD.js.map} +0 -0
  153. /package/dist/{chunk-KTM7HSEB.js.map → chunk-JVNFRS26.js.map} +0 -0
  154. /package/dist/{chunk-XMWOV2D5.js.map → chunk-MWUJOAAW.js.map} +0 -0
  155. /package/dist/{chunk-J63MRNXF.js.map → chunk-NLZD2QKF.js.map} +0 -0
  156. /package/dist/{chunk-FNLGY4UV.js.map → chunk-PZCTCCC5.js.map} +0 -0
  157. /package/dist/{chunk-MDTKQZC6.js.map → chunk-QDE75R4U.js.map} +0 -0
  158. /package/dist/{chunk-SFLF64Q3.js.map → chunk-Z3IP66SL.js.map} +0 -0
  159. /package/dist/{client-LAIP23IP.js.map → client-JZTH46N2.js.map} +0 -0
  160. /package/dist/{compaction-QXU3A4LQ.js.map → compaction-U5SAYTX6.js.map} +0 -0
  161. /package/dist/{config-OZ2OOKFA.js.map → config-R47ITLIY.js.map} +0 -0
  162. /package/dist/{config-mutator-EUF7QQ4V.js.map → config-mutator-T6JGXJ26.js.map} +0 -0
  163. /package/dist/{doctor-PEE3F5TX.js.map → doctor-2DMGAWMW.js.map} +0 -0
  164. /package/dist/{memory-extract-3TO7UC5B.js.map → memory-extract-YZI2QZVR.js.map} +0 -0
  165. /package/dist/{processes-I3XAPHG7.js.map → processes-XWRGOLMT.js.map} +0 -0
  166. /package/dist/{program-PMHQRD5E.js.map → program-3CDFUJVE.js.map} +0 -0
  167. /package/dist/{promote-RNSGW4AJ.js.map → promote-57YNOVXV.js.map} +0 -0
  168. /package/dist/{proposals-MWMEXRDW.js.map → proposals-TX2HBBYV.js.map} +0 -0
  169. /package/dist/{query-BOUVRUU4.js.map → query-ZT7WXY2F.js.map} +0 -0
  170. /package/dist/{registry-GGU43EOI.js.map → registry-722GXALE.js.map} +0 -0
  171. /package/dist/{request-planner-5354IOYM.js.map → request-planner-PEAYYXAP.js.map} +0 -0
  172. /package/dist/{review-candidates-UTWMO7UO.js.map → review-candidates-W7NIHNAI.js.map} +0 -0
  173. /package/dist/{routing-JGNWB6VJ.js.map → routing-7LCO7UJ7.js.map} +0 -0
  174. /package/dist/{scheduler-N25EAWF6.js.map → schedule-tool-ZD3VBUBW.js.map} +0 -0
  175. /package/dist/{seat-STLDEYSO.js.map → scheduler-GC3FQKXY.js.map} +0 -0
  176. /package/dist/{surface-usage-FM2ZHGYE.js.map → seat-HR6QMAR2.js.map} +0 -0
  177. /package/dist/{select-VP66P6XB.js.map → select-4XYRIZ5L.js.map} +0 -0
  178. /package/dist/{wiki-SKIIAVCU.js.map → surface-usage-V2BX4SBD.js.map} +0 -0
@@ -0,0 +1,65 @@
1
+ import AppKit
2
+
3
+ /// The `buffer` verb: a region's last few seconds, held in memory.
4
+ ///
5
+ /// Samples the region `fps` times a second into a `Ring`, keeping a frame
6
+ /// only when it differs from the one before. Frames are downscaled to
7
+ /// `maxPixels` before they are kept, so ten seconds of a whole Retina
8
+ /// screen costs a few megabytes, not a few hundred.
9
+ ///
10
+ /// Commands on stdin, one per line:
11
+ ///
12
+ /// dump SECONDS DIR write the frames from the last SECONDS to DIR as
13
+ /// PNGs and answer with one JSON line listing them
14
+ /// (DIR last, so it may contain spaces)
15
+ ///
16
+ /// EOF ends the process: the daemon that owns it closes stdin, or dies.
17
+ enum Buffer {
18
+
19
+ static func run(region: CGRect, fps: Double, seconds: Double, maxPixels: Int, threshold: Double) {
20
+ let clock = { ProcessInfo.processInfo.systemUptime }
21
+ let lock = NSLock()
22
+ var ring = Ring<CGImage>(seconds: seconds, capacity: Int(ceil(seconds * max(fps, 0.1))) + 1,
23
+ threshold: threshold)
24
+
25
+ let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility))
26
+ timer.schedule(deadline: .now(), repeating: 1 / max(fps, 0.1))
27
+ timer.setEventHandler {
28
+ guard let image = Vision.snapshot(region), let grid = Vision.grid(image) else { return }
29
+ lock.lock(); defer { lock.unlock() }
30
+ let now = clock()
31
+ guard ring.wants(grid) else { ring.prune(now: now); return }
32
+ ring.add(Vision.scaled(image, maxPixels: maxPixels), grid: grid, at: now)
33
+ }
34
+ timer.resume()
35
+
36
+ while let line = readLine() {
37
+ let parts = line.split(separator: " ", maxSplits: 2).map(String.init)
38
+ guard parts.first == "dump", parts.count == 3, let within = Double(parts[1]) else {
39
+ reply(["ok": false, "error": "expected: dump SECONDS DIR"])
40
+ continue
41
+ }
42
+ lock.lock()
43
+ let now = clock()
44
+ let frames = ring.recent(within: within, now: now)
45
+ lock.unlock()
46
+ var written: [[String: Any]] = []
47
+ for (i, entry) in frames.enumerated() {
48
+ let path = (parts[2] as NSString).appendingPathComponent(String(format: "frame-%03d.png", i))
49
+ if Vision.write(entry.frame, to: path) {
50
+ written.append(["path": path, "ageMs": Int((now - entry.at) * 1000),
51
+ "width": entry.frame.width, "height": entry.frame.height])
52
+ }
53
+ }
54
+ reply(["ok": true, "frames": written,
55
+ "region": ["x": region.minX, "y": region.minY, "w": region.width, "h": region.height]])
56
+ }
57
+ timer.cancel()
58
+ }
59
+
60
+ private static func reply(_ payload: [String: Any]) {
61
+ guard var data = try? JSONSerialization.data(withJSONObject: payload) else { return }
62
+ data.append(0x0A)
63
+ FileHandle.standardOutput.write(data)
64
+ }
65
+ }
@@ -48,4 +48,30 @@ enum Notify {
48
48
  guard done.wait(timeout: .now() + timeout) == .success else { return .notAllowed }
49
49
  return outcome
50
50
  }
51
+
52
+ /// What macOS allows right now, without posting a banner or showing the
53
+ /// "allow notifications" prompt. `agentx notifications show` and
54
+ /// `agentx doctor` read it to explain a banner that shows the wrong icon.
55
+ static func status(timeout: TimeInterval = 4) -> [String: Any] {
56
+ let done = DispatchSemaphore(value: 0)
57
+ var payload: [String: Any] = ["ok": true, "authorization": "unknown", "alertStyle": "unknown"]
58
+ UNUserNotificationCenter.current().getNotificationSettings { settings in
59
+ switch settings.authorizationStatus {
60
+ case .notDetermined: payload["authorization"] = "notDetermined"
61
+ case .denied: payload["authorization"] = "denied"
62
+ case .authorized: payload["authorization"] = "authorized"
63
+ case .provisional: payload["authorization"] = "provisional"
64
+ @unknown default: break
65
+ }
66
+ switch settings.alertStyle {
67
+ case .none: payload["alertStyle"] = "none"
68
+ case .banner: payload["alertStyle"] = "banner"
69
+ case .alert: payload["alertStyle"] = "alert"
70
+ @unknown default: break
71
+ }
72
+ done.signal()
73
+ }
74
+ _ = done.wait(timeout: .now() + timeout)
75
+ return payload
76
+ }
51
77
  }
@@ -0,0 +1,58 @@
1
+ import Foundation
2
+
3
+ /// The last few seconds of a region, in memory.
4
+ ///
5
+ /// For a caller that arrives late: the banner it wanted to see has gone,
6
+ /// but a frame showing it is still here. Frames are kept only while they
7
+ /// are younger than `seconds` and never more than `capacity` of them, and
8
+ /// a frame identical to the one before is not kept at all — a still screen
9
+ /// costs one frame, not one per tick.
10
+ ///
11
+ /// Nothing here touches the disk. Frames leave memory only when a caller
12
+ /// asks for them (the `buffer` verb's `dump` command).
13
+ struct Ring<Frame> {
14
+ struct Entry {
15
+ let at: TimeInterval
16
+ let frame: Frame
17
+ /// The comparison grid, so the next sample can be checked against it.
18
+ let grid: [UInt8]
19
+ }
20
+
21
+ let seconds: TimeInterval
22
+ let capacity: Int
23
+ let threshold: Double
24
+ private(set) var entries: [Entry] = []
25
+
26
+ init(seconds: TimeInterval, capacity: Int, threshold: Double) {
27
+ self.seconds = seconds
28
+ self.capacity = max(1, capacity)
29
+ self.threshold = threshold
30
+ }
31
+
32
+ /// Whether a sample with this grid would be kept: always when empty,
33
+ /// otherwise only when it differs from the newest frame.
34
+ func wants(_ grid: [UInt8]) -> Bool {
35
+ guard let last = entries.last else { return true }
36
+ guard let d = Watch.difference(last.grid, grid) else { return true }
37
+ return d > threshold
38
+ }
39
+
40
+ mutating func add(_ frame: Frame, grid: [UInt8], at: TimeInterval) {
41
+ entries.append(Entry(at: at, frame: frame, grid: grid))
42
+ prune(now: at)
43
+ }
44
+
45
+ mutating func prune(now: TimeInterval) {
46
+ // The newest frame stays even when old: on a still screen it IS
47
+ // what has been showing for the whole window.
48
+ while entries.count > 1, now - entries[0].at > seconds { entries.removeFirst() }
49
+ if entries.count > capacity { entries.removeFirst(entries.count - capacity) }
50
+ }
51
+
52
+ /// Frames from the last `within` seconds, oldest first, plus the frame
53
+ /// that was already showing when that window opened.
54
+ func recent(within: TimeInterval, now: TimeInterval) -> [Entry] {
55
+ guard let first = entries.lastIndex(where: { now - $0.at > within }) else { return entries }
56
+ return Array(entries[first...])
57
+ }
58
+ }
@@ -64,4 +64,11 @@ enum Screens {
64
64
  static func menuBar(_ info: Info) -> CGRect {
65
65
  CGRect(x: info.x, y: info.y, width: info.width, height: 26)
66
66
  }
67
+
68
+ /// Where macOS shows notification banners: the top-right corner of the
69
+ /// primary screen, under the menu bar. Generous, because banner size
70
+ /// varies with the text; a caller that knows better passes a rect.
71
+ static func notifications(_ info: Info) -> CGRect {
72
+ CGRect(x: info.x + info.width - 420, y: info.y + 26, width: 420, height: 180)
73
+ }
67
74
  }
@@ -121,6 +121,11 @@ enum Vision {
121
121
  guard let image = CGWindowListCreateImage(
122
122
  rect, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution])
123
123
  else { return false }
124
+ return write(scaled(image, maxWidth: maxWidth, maxPixels: maxPixels), to: path)
125
+ }
126
+
127
+ /// The image within the pixel budget and width cap, aspect kept.
128
+ static func scaled(_ image: CGImage, maxWidth: Int? = nil, maxPixels: Int? = nil) -> CGImage {
124
129
  var target = maxWidth
125
130
  if let budget = maxPixels, budget > 0 {
126
131
  let area = image.width * image.height
@@ -131,8 +136,11 @@ enum Vision {
131
136
  target = min(target ?? fit, fit)
132
137
  }
133
138
  }
134
- let out = target.flatMap { downscale(image, maxWidth: $0) } ?? image
135
- let rep = NSBitmapImageRep(cgImage: out)
139
+ return target.flatMap { downscale(image, maxWidth: $0) } ?? image
140
+ }
141
+
142
+ static func write(_ image: CGImage, to path: String) -> Bool {
143
+ let rep = NSBitmapImageRep(cgImage: image)
136
144
  guard let png = rep.representation(using: .png, properties: [:]) else { return false }
137
145
  return (try? png.write(to: URL(fileURLWithPath: path))) != nil
138
146
  }
@@ -165,11 +173,8 @@ enum Vision {
165
173
  guard let now = CGWindowListCreateImage(a, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution]),
166
174
  let before = b
167
175
  else { return nil }
168
- guard let g1 = grid(before), let g2 = grid(now), g1.count == g2.count, !g1.isEmpty
169
- else { return nil }
170
- var total = 0.0
171
- for i in 0..<g1.count { total += abs(Double(g1[i]) - Double(g2[i])) / 255.0 }
172
- return total / Double(g1.count)
176
+ guard let g1 = grid(before), let g2 = grid(now) else { return nil }
177
+ return Watch.difference(g1, g2)
173
178
  }
174
179
 
175
180
  static func snapshot(_ rect: CGRect) -> CGImage? {
@@ -178,7 +183,7 @@ enum Vision {
178
183
 
179
184
  /// 16×16 greyscale, which is plenty to notice a page changing and
180
185
  /// cheap enough to run between every action.
181
- private static func grid(_ image: CGImage, side: Int = 16) -> [UInt8]? {
186
+ static func grid(_ image: CGImage, side: Int = 16) -> [UInt8]? {
182
187
  let space = CGColorSpaceCreateDeviceGray()
183
188
  var pixels = [UInt8](repeating: 0, count: side * side)
184
189
  guard let ctx = CGContext(data: &pixels, width: side, height: side,
@@ -0,0 +1,87 @@
1
+ import Foundation
2
+
3
+ /// Waiting for the right moment to capture.
4
+ ///
5
+ /// A single frame taken whenever the caller gets round to it misses
6
+ /// anything short-lived: a notification banner is gone within seconds, and
7
+ /// a page that is still loading is captured half-drawn. So a capture can
8
+ /// wait for its region to CHANGE (something appeared), to be STABLE
9
+ /// (whatever was moving has stopped), or both in that order.
10
+ ///
11
+ /// Pure logic over an injected sampler and clock, so it is testable
12
+ /// without a screen. A sample is a small greyscale grid (see
13
+ /// `Vision.grid`); comparing grids ignores antialiasing and one-pixel
14
+ /// shifts.
15
+ enum Watch {
16
+
17
+ struct Options {
18
+ /// Mean absolute difference, 0…1, above which two samples differ.
19
+ var threshold: Double = 0.015
20
+ /// Time between samples.
21
+ var interval: TimeInterval = 0.1
22
+ /// Give up after this long; the caller still gets a frame.
23
+ var timeout: TimeInterval = 5
24
+ /// How long the region must stay unchanged to count as stable.
25
+ var stableFor: TimeInterval = 0.4
26
+ }
27
+
28
+ struct Outcome: Equatable {
29
+ /// The region changed from the baseline (only when asked to wait for it).
30
+ var changed: Bool
31
+ /// The region settled (only when asked to wait for it).
32
+ var stable: Bool
33
+ var timedOut: Bool
34
+ var waited: TimeInterval
35
+ }
36
+
37
+ /// Mean absolute difference of two grids, 0…1; nil when they cannot be compared.
38
+ static func difference(_ a: [UInt8], _ b: [UInt8]) -> Double? {
39
+ guard a.count == b.count, !a.isEmpty else { return nil }
40
+ var total = 0.0
41
+ for i in 0..<a.count { total += abs(Double(a[i]) - Double(b[i])) / 255.0 }
42
+ return total / Double(a.count)
43
+ }
44
+
45
+ /// Waits for a change from `baseline` (when given), then for stability
46
+ /// (when `untilStable`). Returns as soon as the requested conditions
47
+ /// hold, or at the timeout.
48
+ static func wait(baseline: [UInt8]?, untilStable: Bool, options o: Options,
49
+ sample: () -> [UInt8]?, now: () -> TimeInterval,
50
+ sleep: (TimeInterval) -> Void) -> Outcome {
51
+ let start = now()
52
+ var outcome = Outcome(changed: false, stable: false, timedOut: false, waited: 0)
53
+ func expired() -> Bool { now() - start >= o.timeout }
54
+ func finish(timedOut: Bool) -> Outcome {
55
+ outcome.timedOut = timedOut
56
+ outcome.waited = now() - start
57
+ return outcome
58
+ }
59
+
60
+ if let base = baseline {
61
+ while true {
62
+ if let s = sample(), let d = difference(base, s), d > o.threshold { break }
63
+ if expired() { return finish(timedOut: true) }
64
+ sleep(o.interval)
65
+ }
66
+ outcome.changed = true
67
+ }
68
+ guard untilStable else { return finish(timedOut: false) }
69
+
70
+ var last = sample()
71
+ var quietSince = now()
72
+ while true {
73
+ if expired() { return finish(timedOut: true) }
74
+ sleep(o.interval)
75
+ let s = sample()
76
+ if let a = last, let b = s, let d = difference(a, b), d <= o.threshold {
77
+ if now() - quietSince >= o.stableFor {
78
+ outcome.stable = true
79
+ return finish(timedOut: false)
80
+ }
81
+ } else {
82
+ quietSince = now()
83
+ }
84
+ last = s
85
+ }
86
+ }
87
+ }
@@ -35,6 +35,34 @@ func flag(_ name: String) -> String? {
35
35
  return args[i + 1]
36
36
  }
37
37
 
38
+ /// The region a capture or buffer covers: an explicit rect, a named part
39
+ /// of a screen, or the focused window.
40
+ func captureRegion() -> CGRect {
41
+ let screen = Screens.list().first(where: { $0.index == Int(flag("screen") ?? "") })
42
+ ?? Screens.active()
43
+ if let x = Double(flag("x") ?? ""), let y = Double(flag("y") ?? ""),
44
+ let w = Double(flag("w") ?? ""), let h = Double(flag("h") ?? "") {
45
+ return CGRect(x: x, y: y, width: w, height: h)
46
+ }
47
+ if args.contains("--notifications") {
48
+ // Banners always appear on the primary screen.
49
+ guard let s = Screens.list().first(where: { $0.primary }) else { fail("no screen found") }
50
+ return Screens.notifications(s)
51
+ }
52
+ if args.contains("--menubar") {
53
+ guard let s = screen else { fail("no screen found") }
54
+ return Screens.menuBar(s)
55
+ }
56
+ if args.contains("--screen-full") {
57
+ guard let s = screen else { fail("no screen found") }
58
+ return Screens.rect(s)
59
+ }
60
+ guard let win = OCR.focusedWindowFrame() else {
61
+ fail("no focused window — pass --x/--y/--w/--h, --notifications, --menubar or --screen-full")
62
+ }
63
+ return win
64
+ }
65
+
38
66
  switch verb {
39
67
  case "read":
40
68
  guard AXTree.trusted() else {
@@ -110,37 +138,59 @@ case "capture":
110
138
  // without the caller knowing the display's size, because the state
111
139
  // worth checking — a recording indicator, a capture pill — lives in
112
140
  // the menu bar, which belongs to no window at all.
141
+ //
142
+ // --until-changed / --until-stable wait for the right moment instead
143
+ // of taking whatever is there now. With --armed, a line goes to stderr
144
+ // once the baseline is taken, so a caller can start its action only
145
+ // then and never race the thing it wants to see.
113
146
  guard let out = flag("out") else { fail("capture needs --out") }
114
- let screen = Screens.list().first(where: { $0.index == Int(flag("screen") ?? "") })
115
- ?? Screens.active()
116
- let region: CGRect
117
- if let x = Double(flag("x") ?? ""), let y = Double(flag("y") ?? ""),
118
- let w = Double(flag("w") ?? ""), let h = Double(flag("h") ?? "") {
119
- region = CGRect(x: x, y: y, width: w, height: h)
120
- } else if args.contains("--menubar") {
121
- guard let s = screen else { fail("no screen found") }
122
- region = Screens.menuBar(s)
123
- } else if args.contains("--screen-full") {
124
- guard let s = screen else { fail("no screen found") }
125
- region = Screens.rect(s)
126
- } else if let win = OCR.focusedWindowFrame() {
127
- region = win
128
- } else {
129
- fail("no focused window — pass --x/--y/--w/--h, --menubar or --screen-full")
147
+ let region = captureRegion()
148
+ var outcome: Watch.Outcome?
149
+ let untilChanged = args.contains("--until-changed"), untilStable = args.contains("--until-stable")
150
+ if untilChanged || untilStable {
151
+ let sample = { Vision.snapshot(region).flatMap { Vision.grid($0) } }
152
+ let baseline = untilChanged ? sample() : nil
153
+ if untilChanged && baseline == nil { fail("capture failed — check Screen Recording permission") }
154
+ if args.contains("--armed") { FileHandle.standardError.write("armed\n".data(using: .utf8)!) }
155
+ var o = Watch.Options()
156
+ if let v = Double(flag("threshold") ?? "") { o.threshold = v }
157
+ if let v = Double(flag("interval") ?? "") { o.interval = v / 1000 }
158
+ if let v = Double(flag("timeout") ?? "") { o.timeout = v / 1000 }
159
+ if let v = Double(flag("stable-ms") ?? "") { o.stableFor = v / 1000 }
160
+ outcome = Watch.wait(baseline: baseline, untilStable: untilStable, options: o, sample: sample,
161
+ now: { ProcessInfo.processInfo.systemUptime },
162
+ sleep: { Thread.sleep(forTimeInterval: $0) })
130
163
  }
131
164
  guard Vision.capture(region, to: out,
132
165
  maxWidth: Int(flag("max-width") ?? ""),
133
166
  maxPixels: Int(flag("max-pixels") ?? "")) else {
134
167
  fail("capture failed — check Screen Recording permission")
135
168
  }
136
- let shot: [String: Any] = [
169
+ var shot: [String: Any] = [
137
170
  "ok": true, "captured": true, "path": out,
138
171
  "region": ["x": region.minX, "y": region.minY,
139
172
  "w": region.width, "h": region.height],
140
173
  ]
174
+ if let o = outcome {
175
+ shot["changed"] = o.changed
176
+ shot["stable"] = o.stable
177
+ shot["timedOut"] = o.timedOut
178
+ shot["waitedMs"] = Int(o.waited * 1000)
179
+ }
141
180
  FileHandle.standardOutput.write(
142
181
  (try? JSONSerialization.data(withJSONObject: shot)) ?? Data())
143
182
 
183
+ case "buffer":
184
+ // Long-lived: keeps the last --seconds of a region in memory, sampled
185
+ // --fps times a second, and writes frames out only when asked on
186
+ // stdin ("dump SECONDS DIR"). Ends at EOF, so it never outlives the
187
+ // daemon that started it.
188
+ Buffer.run(region: captureRegion(),
189
+ fps: Double(flag("fps") ?? "") ?? 2,
190
+ seconds: Double(flag("seconds") ?? "") ?? 10,
191
+ maxPixels: Int(flag("max-pixels") ?? "") ?? 300_000,
192
+ threshold: Double(flag("threshold") ?? "") ?? 0.015)
193
+
144
194
  case "screens":
145
195
  // Display geometry in top-left coordinates, so a caller can aim at a
146
196
  // second monitor without redoing AppKit's origin flip.
@@ -280,10 +330,14 @@ case "notify":
280
330
  }
281
331
  FileHandle.standardOutput.write(#"{"ok":true,"notified":true}"#.data(using: .utf8)!)
282
332
 
333
+ case "notify-status":
334
+ // Whether macOS lets the helper post banners. Never prompts or posts.
335
+ FileHandle.standardOutput.write(try! JSONSerialization.data(withJSONObject: Notify.status()))
336
+
283
337
  case "trusted":
284
338
  let payload = ["ok": true, "trusted": AXTree.trusted()] as [String: Any]
285
339
  FileHandle.standardOutput.write(try! JSONSerialization.data(withJSONObject: payload))
286
340
 
287
341
  default:
288
- fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud, presence, notify or trusted")
342
+ fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud, presence, buffer, notify, notify-status or trusted")
289
343
  }
@@ -102,6 +102,19 @@ enum AgentClient {
102
102
  return try await URLSession.shared.data(for: req)
103
103
  }
104
104
 
105
+ /// The agent that answers: the configured one, or the daemon's
106
+ /// `node.defaultAgent` from /health. "" when neither is known.
107
+ static func resolveAgent() async -> String {
108
+ if let agent = Config.agentID { return agent }
109
+ guard let url = URL(string: "\(Config.daemonURL)/health"),
110
+ let (data, _) = try? await URLSession.shared.data(from: url),
111
+ let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
112
+ let node = obj["node"] as? [String: Any],
113
+ let agent = node["defaultAgent"] as? String
114
+ else { return "" }
115
+ return agent
116
+ }
117
+
105
118
  static func ask(_ message: String) async throws -> Answer {
106
119
  var req = URLRequest(url: URL(string: "\(Config.daemonURL)/ask")!)
107
120
  req.httpMethod = "POST"
@@ -109,11 +122,10 @@ enum AgentClient {
109
122
  // off exactly the thoughtful answers worth waiting for.
110
123
  req.timeoutInterval = 600
111
124
  req.setValue("application/json", forHTTPHeaderField: "Content-Type")
112
- req.httpBody = try JSONSerialization.data(withJSONObject: [
113
- "message": message,
114
- "agent": Config.agentID,
115
- "session": Config.voiceSession,
116
- ])
125
+ var payload: [String: Any] = ["message": message, "session": Config.voiceSession]
126
+ // No agent configured: the daemon answers with its default agent.
127
+ if let agent = Config.agentID { payload["agent"] = agent }
128
+ req.httpBody = try JSONSerialization.data(withJSONObject: payload)
117
129
 
118
130
  let (data, response) = try await URLSession.shared.data(for: req)
119
131
  struct UiButton: Decodable { let label: String; let url: String }
@@ -8,7 +8,12 @@ import Foundation
8
8
  /// from a terminal and mysteriously fails when you double-click it.
9
9
  enum Config {
10
10
  static let daemonURL = env("AGENTX_DAEMON_URL") ?? "http://127.0.0.1:18800"
11
- static let agentID = env("AGENTX_VOICE_AGENT") ?? "secretary-agent"
11
+ /// The agent that answers. Unset: the daemon's `node.defaultAgent`.
12
+ static let agentID = env("AGENTX_VOICE_AGENT")
13
+
14
+ /// The agent actually answering: `agentID`, or the daemon's default,
15
+ /// read once at launch (AgentClient.resolveAgent). Progress follows it.
16
+ @MainActor static var effectiveAgentID: String = ""
12
17
 
13
18
  /// Who speaks before the daemon has said which voice an agent uses:
14
19
  /// "system" (free macOS voices, the default) or "elevenlabs".
@@ -134,7 +134,10 @@ final class App: NSObject, NSApplicationDelegate {
134
134
  Task { @MainActor in
135
135
  guard let self else { return }
136
136
  if !granted { self.panel.render(.error("Microphone denied")) }
137
- else { Log.info("ready — hold ⌥Space to talk (agent: \(Config.agentID))") }
137
+ else {
138
+ Config.effectiveAgentID = await AgentClient.resolveAgent()
139
+ Log.info("ready — hold ⌥Space to talk (agent: \(Config.effectiveAgentID.isEmpty ? "the daemon's default" : Config.effectiveAgentID))")
140
+ }
138
141
  }
139
142
  }
140
143
  }
@@ -408,7 +411,7 @@ final class App: NSObject, NSApplicationDelegate {
408
411
 
409
412
  // Progress delivers on its own serial queue; hop to main before
410
413
  // touching any view.
411
- progress = Progress(agentID: Config.agentID) { [weak self] step, voice in
414
+ progress = Progress(agentID: Config.effectiveAgentID) { [weak self] step, voice in
412
415
  Task { @MainActor in
413
416
  guard let self, self.busy else { return }
414
417
  if let voice { self.voice = voice }
@@ -1,8 +1,8 @@
1
- import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-BHPIAG3E.js";import"./chunk-ANLKBCYH.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-OUZ7YVQZ.js";import"./chunk-P2ZYT5D6.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-RZDJH6MK.js.map
8
+ //# sourceMappingURL=App-KE4SURSS.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-BHPIAG3E.js";import"./chunk-ANLKBCYH.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-OUZ7YVQZ.js";import"./chunk-P2ZYT5D6.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-6TXOFUDH.js.map
7
+ //# sourceMappingURL=ChatApp-NNHIKXO2.js.map
@@ -1,2 +1,2 @@
1
- import{A as a,B as b,C as c}from"./chunk-XMWOV2D5.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-I6RXAEXG.js.map
1
+ import{A as a,B as b,C as c}from"./chunk-MWUJOAAW.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-J7SLJYQ6.js.map