agentix-cli 0.33.0 → 0.35.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.
- package/apps/mac-helper/Sources/AgentXHelper/Presence.swift +266 -0
- package/apps/mac-helper/Sources/AgentXHelper/main.swift +7 -1
- package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +48 -2
- package/apps/mac-voice/Sources/AgentXVoice/Config.swift +6 -1
- package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +5 -3
- package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +5 -4
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +30 -5
- package/dist/{App-2LAWZNMG.js → App-K5J52F7O.js} +2 -2
- package/dist/{ChatApp-2KRDHMTV.js → ChatApp-LRRAPZH7.js} +2 -2
- package/dist/{agent-ZRRIMLTX.js → agent-XEXAYH5J.js} +2 -2
- package/dist/{board-dashboard-XYB7M5BH.js → board-dashboard-CBOESBMD.js} +4 -4
- package/dist/{chunk-GWQIMAJ7.js → chunk-3PL3WLIR.js} +2 -2
- package/dist/{chunk-K6E7WJ7O.js → chunk-3TNK7VGH.js} +2 -2
- package/dist/{chunk-6VQKHMEQ.js → chunk-4QNZI4WE.js} +5 -5
- package/dist/{chunk-UEP7IRBD.js → chunk-DXXWYYZI.js} +218 -194
- package/dist/chunk-DXXWYYZI.js.map +1 -0
- package/dist/{chunk-5FEQI22L.js → chunk-FLNN3KSX.js} +2 -2
- package/dist/{chunk-MFVI5FOM.js → chunk-GYBYKREN.js} +2 -2
- package/dist/chunk-HPY6OOFO.js +542 -0
- package/dist/chunk-HPY6OOFO.js.map +1 -0
- package/dist/{chunk-JGJE5Y2J.js → chunk-JSZRGMSA.js} +2 -2
- package/dist/chunk-LA6AKS4N.js +13 -0
- package/dist/chunk-LA6AKS4N.js.map +1 -0
- package/dist/{chunk-A4YR3PQY.js → chunk-LLKILWWO.js} +2 -2
- package/dist/{chunk-2DBGOCCW.js → chunk-LQFEH3SA.js} +2 -2
- package/dist/{chunk-JWKIFAXA.js → chunk-PBVEYPZB.js} +2 -2
- package/dist/{chunk-B2XTYYXV.js → chunk-SYJUZ3F2.js} +2 -2
- package/dist/{chunk-W3YGESNK.js → chunk-WEN2P4RS.js} +2 -2
- package/dist/{chunk-G5MFPNGJ.js → chunk-WRBW4A2U.js} +2 -2
- package/dist/{chunk-4CR463C6.js → chunk-ZSNTLADV.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/client-APAWETTA.js +2 -0
- package/dist/config-S3VHHVYW.js +2 -0
- package/dist/config-mutator-5B2YWZMK.js +2 -0
- package/dist/doctor-SCECRUJ7.js +2 -0
- package/dist/index.d.ts +194 -0
- package/dist/index.js +1 -1
- package/dist/program-XJEQAWN3.js +2 -0
- package/dist/query-EDW72Q4O.js +2 -0
- package/dist/{registry-OB6S66XX.js → registry-GWN34G4J.js} +2 -2
- package/dist/{request-planner-F5NVL2G7.js → request-planner-V7T6BQU7.js} +2 -2
- package/dist/{review-candidates-IRPKFWL2.js → review-candidates-7QKNMRGU.js} +2 -2
- package/dist/{routing-OSPRKRAY.js → routing-BHQXFUSH.js} +2 -2
- package/dist/scheduler-LY4RBPB6.js +2 -0
- package/dist/{seat-2MHKVMW2.js → seat-U57NWHKC.js} +2 -2
- package/dist/{select-7LCHJVPK.js → select-JL3JJJP4.js} +2 -2
- package/dist/wiki-AQ4K3F7V.js +2 -0
- package/package.json +1 -1
- package/dist/chunk-2DMVKLLE.js +0 -13
- package/dist/chunk-2DMVKLLE.js.map +0 -1
- package/dist/chunk-UEP7IRBD.js.map +0 -1
- package/dist/chunk-WZD3OS7T.js +0 -550
- package/dist/chunk-WZD3OS7T.js.map +0 -1
- package/dist/client-IP643GRO.js +0 -2
- package/dist/config-IVDP46ZG.js +0 -2
- package/dist/config-mutator-UCGAV3SP.js +0 -2
- package/dist/doctor-MMT4SREV.js +0 -2
- package/dist/program-PK5R7GIC.js +0 -2
- package/dist/query-RHVR6QUZ.js +0 -2
- package/dist/scheduler-JL6K5HHI.js +0 -2
- package/dist/wiki-MY32MON3.js +0 -2
- /package/dist/{App-2LAWZNMG.js.map → App-K5J52F7O.js.map} +0 -0
- /package/dist/{ChatApp-2KRDHMTV.js.map → ChatApp-LRRAPZH7.js.map} +0 -0
- /package/dist/{agent-ZRRIMLTX.js.map → agent-XEXAYH5J.js.map} +0 -0
- /package/dist/{board-dashboard-XYB7M5BH.js.map → board-dashboard-CBOESBMD.js.map} +0 -0
- /package/dist/{chunk-GWQIMAJ7.js.map → chunk-3PL3WLIR.js.map} +0 -0
- /package/dist/{chunk-K6E7WJ7O.js.map → chunk-3TNK7VGH.js.map} +0 -0
- /package/dist/{chunk-6VQKHMEQ.js.map → chunk-4QNZI4WE.js.map} +0 -0
- /package/dist/{chunk-5FEQI22L.js.map → chunk-FLNN3KSX.js.map} +0 -0
- /package/dist/{chunk-MFVI5FOM.js.map → chunk-GYBYKREN.js.map} +0 -0
- /package/dist/{chunk-JGJE5Y2J.js.map → chunk-JSZRGMSA.js.map} +0 -0
- /package/dist/{chunk-A4YR3PQY.js.map → chunk-LLKILWWO.js.map} +0 -0
- /package/dist/{chunk-2DBGOCCW.js.map → chunk-LQFEH3SA.js.map} +0 -0
- /package/dist/{chunk-JWKIFAXA.js.map → chunk-PBVEYPZB.js.map} +0 -0
- /package/dist/{chunk-B2XTYYXV.js.map → chunk-SYJUZ3F2.js.map} +0 -0
- /package/dist/{chunk-W3YGESNK.js.map → chunk-WEN2P4RS.js.map} +0 -0
- /package/dist/{chunk-G5MFPNGJ.js.map → chunk-WRBW4A2U.js.map} +0 -0
- /package/dist/{chunk-4CR463C6.js.map → chunk-ZSNTLADV.js.map} +0 -0
- /package/dist/{client-IP643GRO.js.map → client-APAWETTA.js.map} +0 -0
- /package/dist/{config-IVDP46ZG.js.map → config-S3VHHVYW.js.map} +0 -0
- /package/dist/{config-mutator-UCGAV3SP.js.map → config-mutator-5B2YWZMK.js.map} +0 -0
- /package/dist/{doctor-MMT4SREV.js.map → doctor-SCECRUJ7.js.map} +0 -0
- /package/dist/{program-PK5R7GIC.js.map → program-XJEQAWN3.js.map} +0 -0
- /package/dist/{query-RHVR6QUZ.js.map → query-EDW72Q4O.js.map} +0 -0
- /package/dist/{registry-OB6S66XX.js.map → registry-GWN34G4J.js.map} +0 -0
- /package/dist/{request-planner-F5NVL2G7.js.map → request-planner-V7T6BQU7.js.map} +0 -0
- /package/dist/{review-candidates-IRPKFWL2.js.map → review-candidates-7QKNMRGU.js.map} +0 -0
- /package/dist/{routing-OSPRKRAY.js.map → routing-BHQXFUSH.js.map} +0 -0
- /package/dist/{scheduler-JL6K5HHI.js.map → scheduler-LY4RBPB6.js.map} +0 -0
- /package/dist/{seat-2MHKVMW2.js.map → seat-U57NWHKC.js.map} +0 -0
- /package/dist/{select-7LCHJVPK.js.map → select-JL3JJJP4.js.map} +0 -0
- /package/dist/{wiki-MY32MON3.js.map → wiki-AQ4K3F7V.js.map} +0 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// An agent's own cursor on screen: its colour, its initial, its name.
|
|
4
|
+
///
|
|
5
|
+
/// Drawn, never driven. `point` moves the person's real mouse, which is
|
|
6
|
+
/// right for a lesson the person watches and wrong for one they take part
|
|
7
|
+
/// in — their hand is on that mouse. This cursor is a click-through window
|
|
8
|
+
/// that glides to what the agent means, highlights it, and says a line in
|
|
9
|
+
/// a bubble, while the real pointer stays exactly where the person left it.
|
|
10
|
+
///
|
|
11
|
+
/// Driven over stdin, one JSON object per line, like the HUD:
|
|
12
|
+
///
|
|
13
|
+
/// {"cmd":"move","x":120,"y":80,"w":90,"h":24,"highlight":true}
|
|
14
|
+
/// {"cmd":"say","text":"The export button is top right."}
|
|
15
|
+
/// {"cmd":"clear"} drop the highlight
|
|
16
|
+
/// {"cmd":"park"} rest in the bottom-right corner, no highlight
|
|
17
|
+
///
|
|
18
|
+
/// Coordinates are accessibility coordinates (top-left origin, global),
|
|
19
|
+
/// the same as `read`, `ocr` and `point`. EOF fades everything out.
|
|
20
|
+
enum Presence {
|
|
21
|
+
|
|
22
|
+
// MARK: Geometry
|
|
23
|
+
|
|
24
|
+
/// Accessibility → AppKit. Global AX coordinates are measured from the
|
|
25
|
+
/// top of the PRIMARY display, whichever screen the point is on.
|
|
26
|
+
static func toAppKit(_ p: CGPoint) -> CGPoint {
|
|
27
|
+
let h = NSScreen.screens.first?.frame.maxY ?? 0
|
|
28
|
+
return CGPoint(x: p.x, y: h - p.y)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static func toAppKit(_ r: CGRect) -> CGRect {
|
|
32
|
+
let o = toAppKit(CGPoint(x: r.minX, y: r.maxY))
|
|
33
|
+
return CGRect(x: o.x, y: o.y, width: r.width, height: r.height)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// MARK: Windows
|
|
37
|
+
|
|
38
|
+
private static func panel(_ rect: NSRect) -> NSPanel {
|
|
39
|
+
let p = NSPanel(contentRect: rect, styleMask: [.borderless, .nonactivatingPanel],
|
|
40
|
+
backing: .buffered, defer: false)
|
|
41
|
+
p.level = .screenSaver
|
|
42
|
+
p.isOpaque = false
|
|
43
|
+
p.backgroundColor = .clear
|
|
44
|
+
p.hasShadow = false
|
|
45
|
+
// Click-through: the person's clicks land on the app underneath.
|
|
46
|
+
p.ignoresMouseEvents = true
|
|
47
|
+
p.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary]
|
|
48
|
+
return p
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// The arrow, the initial badge, the name, and the speech bubble.
|
|
52
|
+
private final class AvatarView: NSView {
|
|
53
|
+
let color: NSColor
|
|
54
|
+
let initial: String
|
|
55
|
+
let name: String
|
|
56
|
+
let bubble = NSTextField(wrappingLabelWithString: "")
|
|
57
|
+
/// The bubble's background, so the text gets padding.
|
|
58
|
+
let box = NSView()
|
|
59
|
+
/// Wide on both sides of the tip, so the bubble can sit to the left
|
|
60
|
+
/// when the target is near the right edge of the screen.
|
|
61
|
+
static let size = NSSize(width: 720, height: 170)
|
|
62
|
+
/// The arrow tip, in view coordinates (bottom-left origin).
|
|
63
|
+
static let tip = NSPoint(x: 360, y: size.height - 6)
|
|
64
|
+
var bubbleLeft = false { didSet { if bubbleLeft != oldValue { layoutBubble() } } }
|
|
65
|
+
|
|
66
|
+
init(color: NSColor, initial: String, name: String) {
|
|
67
|
+
self.color = color; self.initial = initial; self.name = name
|
|
68
|
+
super.init(frame: NSRect(origin: .zero, size: AvatarView.size))
|
|
69
|
+
wantsLayer = true
|
|
70
|
+
bubble.font = Brand.body(size: 13)
|
|
71
|
+
bubble.textColor = Brand.ink
|
|
72
|
+
bubble.maximumNumberOfLines = 4
|
|
73
|
+
box.wantsLayer = true
|
|
74
|
+
box.layer?.backgroundColor = Brand.paper.cgColor
|
|
75
|
+
box.layer?.cornerRadius = Brand.Radius.md
|
|
76
|
+
box.layer?.borderWidth = 2
|
|
77
|
+
box.layer?.borderColor = color.cgColor
|
|
78
|
+
box.isHidden = true
|
|
79
|
+
box.addSubview(bubble)
|
|
80
|
+
addSubview(box)
|
|
81
|
+
}
|
|
82
|
+
required init?(coder: NSCoder) { fatalError() }
|
|
83
|
+
|
|
84
|
+
/// Under the badge and name, never over them.
|
|
85
|
+
func setBubble(_ text: String) {
|
|
86
|
+
box.isHidden = text.isEmpty
|
|
87
|
+
bubble.stringValue = text
|
|
88
|
+
let textW: CGFloat = 300
|
|
89
|
+
bubble.preferredMaxLayoutWidth = textW
|
|
90
|
+
let fit = bubble.sizeThatFits(NSSize(width: textW, height: 90))
|
|
91
|
+
let w = min(textW, ceil(fit.width)), h = min(90, ceil(fit.height))
|
|
92
|
+
bubble.frame = NSRect(x: 10, y: 6, width: w, height: h)
|
|
93
|
+
box.setFrameSize(NSSize(width: w + 20, height: h + 12))
|
|
94
|
+
layoutBubble()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func layoutBubble() {
|
|
98
|
+
let t = AvatarView.tip, s = box.frame.size
|
|
99
|
+
box.setFrameOrigin(NSPoint(x: bubbleLeft ? t.x - s.width - 4 : t.x + 16, y: t.y - 58 - s.height))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
override func draw(_ dirtyRect: NSRect) {
|
|
103
|
+
let t = AvatarView.tip
|
|
104
|
+
// The arrow: the system cursor's silhouette, in the agent's colour.
|
|
105
|
+
let arrow = NSBezierPath()
|
|
106
|
+
arrow.move(to: t)
|
|
107
|
+
arrow.line(to: NSPoint(x: t.x, y: t.y - 26))
|
|
108
|
+
arrow.line(to: NSPoint(x: t.x + 7, y: t.y - 20))
|
|
109
|
+
arrow.line(to: NSPoint(x: t.x + 12, y: t.y - 30))
|
|
110
|
+
arrow.line(to: NSPoint(x: t.x + 16, y: t.y - 28))
|
|
111
|
+
arrow.line(to: NSPoint(x: t.x + 11, y: t.y - 18))
|
|
112
|
+
arrow.line(to: NSPoint(x: t.x + 20, y: t.y - 18))
|
|
113
|
+
arrow.close()
|
|
114
|
+
NSGraphicsContext.current?.saveGraphicsState()
|
|
115
|
+
let shadow = NSShadow()
|
|
116
|
+
shadow.shadowBlurRadius = 4
|
|
117
|
+
shadow.shadowOffset = NSSize(width: 0, height: -1)
|
|
118
|
+
shadow.shadowColor = NSColor.black.withAlphaComponent(0.35)
|
|
119
|
+
shadow.set()
|
|
120
|
+
color.setFill(); arrow.fill()
|
|
121
|
+
NSGraphicsContext.current?.restoreGraphicsState()
|
|
122
|
+
NSColor.white.setStroke(); arrow.lineWidth = 1.5; arrow.stroke()
|
|
123
|
+
|
|
124
|
+
// The badge with the initial, and the name pill beside it.
|
|
125
|
+
let badge = NSRect(x: t.x + 16, y: t.y - 50, width: 24, height: 24)
|
|
126
|
+
color.setFill(); NSBezierPath(ovalIn: badge).fill()
|
|
127
|
+
NSColor.white.setStroke()
|
|
128
|
+
let ring = NSBezierPath(ovalIn: badge.insetBy(dx: 0.75, dy: 0.75)); ring.lineWidth = 1.5; ring.stroke()
|
|
129
|
+
draw(initial, in: badge, size: 11, weight: .bold, color: .white)
|
|
130
|
+
|
|
131
|
+
let attrs: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: 11, weight: .semibold)]
|
|
132
|
+
let nameW = (name as NSString).size(withAttributes: attrs).width + 14
|
|
133
|
+
let pill = NSRect(x: badge.maxX + 4, y: badge.minY + 3, width: nameW, height: 18)
|
|
134
|
+
color.setFill(); NSBezierPath(roundedRect: pill, xRadius: 9, yRadius: 9).fill()
|
|
135
|
+
draw(name, in: pill, size: 11, weight: .semibold, color: .white)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private func draw(_ s: String, in r: NSRect, size: CGFloat, weight: NSFont.Weight, color: NSColor) {
|
|
139
|
+
let a: [NSAttributedString.Key: Any] = [.font: NSFont.systemFont(ofSize: size, weight: weight), .foregroundColor: color]
|
|
140
|
+
let sz = (s as NSString).size(withAttributes: a)
|
|
141
|
+
(s as NSString).draw(at: NSPoint(x: r.midX - sz.width / 2, y: r.midY - sz.height / 2), withAttributes: a)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// MARK: Behaviour
|
|
146
|
+
|
|
147
|
+
private final class Controller {
|
|
148
|
+
let avatar: NSPanel
|
|
149
|
+
let view: AvatarView
|
|
150
|
+
let ring: NSPanel
|
|
151
|
+
let color: NSColor
|
|
152
|
+
var at: CGPoint // tip position, AppKit coordinates
|
|
153
|
+
var timer: Timer?
|
|
154
|
+
|
|
155
|
+
init(color: NSColor, initial: String, name: String) {
|
|
156
|
+
self.color = color
|
|
157
|
+
view = AvatarView(color: color, initial: initial, name: name)
|
|
158
|
+
avatar = Presence.panel(NSRect(origin: .zero, size: AvatarView.size))
|
|
159
|
+
avatar.contentView = view
|
|
160
|
+
ring = Presence.panel(.zero)
|
|
161
|
+
let ringView = NSView()
|
|
162
|
+
ringView.wantsLayer = true
|
|
163
|
+
ringView.layer?.borderWidth = 3
|
|
164
|
+
ringView.layer?.borderColor = color.cgColor
|
|
165
|
+
ringView.layer?.cornerRadius = 8
|
|
166
|
+
ringView.layer?.backgroundColor = color.withAlphaComponent(0.10).cgColor
|
|
167
|
+
ring.contentView = ringView
|
|
168
|
+
at = Controller.parking()
|
|
169
|
+
place(at)
|
|
170
|
+
avatar.alphaValue = 0
|
|
171
|
+
avatar.orderFrontRegardless()
|
|
172
|
+
NSAnimationContext.runAnimationGroup { $0.duration = 0.2; avatar.animator().alphaValue = 1 }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static func parking() -> CGPoint {
|
|
176
|
+
let v = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1440, height: 900)
|
|
177
|
+
return CGPoint(x: v.maxX - 380, y: v.minY + 170)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func place(_ tip: CGPoint) {
|
|
181
|
+
avatar.setFrameOrigin(NSPoint(x: tip.x - AvatarView.tip.x, y: tip.y - AvatarView.tip.y))
|
|
182
|
+
let screen = NSScreen.screens.first(where: { $0.frame.contains(tip) }) ?? NSScreen.main
|
|
183
|
+
view.bubbleLeft = (screen?.visibleFrame.maxX ?? .greatestFiniteMagnitude) - tip.x < 360
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// Glide with minimum-jerk easing; duration follows distance, as in
|
|
187
|
+
/// Pointer, so a long move reads as a reach and a short one as a nudge.
|
|
188
|
+
func glide(to target: CGPoint) {
|
|
189
|
+
timer?.invalidate()
|
|
190
|
+
let from = at
|
|
191
|
+
let d = hypot(target.x - from.x, target.y - from.y)
|
|
192
|
+
let duration = min(0.9, max(0.2, 0.12 + d / 2200))
|
|
193
|
+
let start = Date()
|
|
194
|
+
timer = Timer.scheduledTimer(withTimeInterval: 1.0 / 60, repeats: true) { [weak self] t in
|
|
195
|
+
guard let self else { t.invalidate(); return }
|
|
196
|
+
let u = min(1, Date().timeIntervalSince(start) / duration)
|
|
197
|
+
let e = 10 * pow(u, 3) - 15 * pow(u, 4) + 6 * pow(u, 5)
|
|
198
|
+
self.at = CGPoint(x: from.x + (target.x - from.x) * e, y: from.y + (target.y - from.y) * e)
|
|
199
|
+
self.place(self.at)
|
|
200
|
+
if u >= 1 { t.invalidate() }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func highlight(_ rect: CGRect?) {
|
|
205
|
+
guard let rect else { ring.orderOut(nil); return }
|
|
206
|
+
ring.setFrame(Presence.toAppKit(rect).insetBy(dx: -6, dy: -6), display: true)
|
|
207
|
+
ring.orderFrontRegardless()
|
|
208
|
+
avatar.orderFrontRegardless()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
func handle(_ obj: [String: Any]) {
|
|
212
|
+
switch obj["cmd"] as? String {
|
|
213
|
+
case "move":
|
|
214
|
+
guard let x = obj["x"] as? Double, let y = obj["y"] as? Double else { return }
|
|
215
|
+
let w = obj["w"] as? Double ?? 0, h = obj["h"] as? Double ?? 0
|
|
216
|
+
let rect = CGRect(x: x, y: y, width: w, height: h)
|
|
217
|
+
// Aim just inside the control's lower-left third, so the
|
|
218
|
+
// arrow points AT it rather than covering its label.
|
|
219
|
+
let aim = CGPoint(x: x + min(w * 0.5, 24), y: y + h * 0.7)
|
|
220
|
+
glide(to: Presence.toAppKit(aim))
|
|
221
|
+
highlight((obj["highlight"] as? Bool ?? false) && w > 0 && h > 0 ? rect : nil)
|
|
222
|
+
case "say":
|
|
223
|
+
view.setBubble(obj["text"] as? String ?? "")
|
|
224
|
+
case "clear":
|
|
225
|
+
highlight(nil)
|
|
226
|
+
case "park":
|
|
227
|
+
highlight(nil)
|
|
228
|
+
glide(to: Controller.parking())
|
|
229
|
+
default:
|
|
230
|
+
break
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
func fadeOut(_ done: @escaping () -> Void) {
|
|
235
|
+
ring.orderOut(nil)
|
|
236
|
+
NSAnimationContext.runAnimationGroup { ctx in
|
|
237
|
+
ctx.duration = 0.25
|
|
238
|
+
avatar.animator().alphaValue = 0
|
|
239
|
+
} completionHandler: { done() }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static func color(hex: String?) -> NSColor {
|
|
244
|
+
guard let hex, hex.count == 7, hex.hasPrefix("#"), let v = UInt32(hex.dropFirst(), radix: 16) else {
|
|
245
|
+
return Brand.primaryBright
|
|
246
|
+
}
|
|
247
|
+
return NSColor(srgbRed: CGFloat((v >> 16) & 0xff) / 255, green: CGFloat((v >> 8) & 0xff) / 255,
|
|
248
|
+
blue: CGFloat(v & 0xff) / 255, alpha: 1)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Read commands until stdin closes.
|
|
252
|
+
static func run(name: String, initial: String, colorHex: String?) {
|
|
253
|
+
let app = NSApplication.shared
|
|
254
|
+
app.setActivationPolicy(.accessory)
|
|
255
|
+
let ctl = Controller(color: color(hex: colorHex), initial: initial, name: name)
|
|
256
|
+
DispatchQueue(label: "tn.acme.agentx.presence.stdin").async {
|
|
257
|
+
while let line = readLine(strippingNewline: true) {
|
|
258
|
+
guard let data = line.data(using: .utf8),
|
|
259
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { continue }
|
|
260
|
+
DispatchQueue.main.async { ctl.handle(obj) }
|
|
261
|
+
}
|
|
262
|
+
DispatchQueue.main.async { ctl.fadeOut { app.terminate(nil) } }
|
|
263
|
+
}
|
|
264
|
+
app.run()
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -256,10 +256,16 @@ case "hud":
|
|
|
256
256
|
// Long-lived: reads update lines on stdin until EOF.
|
|
257
257
|
HUD.run()
|
|
258
258
|
|
|
259
|
+
case "presence":
|
|
260
|
+
// Long-lived: the agent's own drawn cursor. Never moves the real mouse.
|
|
261
|
+
let name = flag("name") ?? "Agent"
|
|
262
|
+
Presence.run(name: name, initial: flag("initial") ?? String(name.prefix(1)).uppercased(),
|
|
263
|
+
colorHex: flag("color"))
|
|
264
|
+
|
|
259
265
|
case "trusted":
|
|
260
266
|
let payload = ["ok": true, "trusted": AXTree.trusted()] as [String: Any]
|
|
261
267
|
FileHandle.standardOutput.write(try! JSONSerialization.data(withJSONObject: payload))
|
|
262
268
|
|
|
263
269
|
default:
|
|
264
|
-
fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud or trusted")
|
|
270
|
+
fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud, presence or trusted")
|
|
265
271
|
}
|
|
@@ -22,6 +22,11 @@ enum AgentClient {
|
|
|
22
22
|
let buttons: [(String, String)]
|
|
23
23
|
let imageURL: String?
|
|
24
24
|
let durationMs: Int?
|
|
25
|
+
/// The agent that answered and its ElevenLabs voice, when it has one.
|
|
26
|
+
let agentID: String?
|
|
27
|
+
let voiceID: String?
|
|
28
|
+
/// Presence mode the daemon chose for this turn (talk, teach, …).
|
|
29
|
+
var presenceMode: String? = nil
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/// What, if anything, to say about the step now running.
|
|
@@ -49,6 +54,39 @@ enum AgentClient {
|
|
|
49
54
|
return (try? JSONDecoder().decode(Reply.self, from: data))?.say
|
|
50
55
|
}
|
|
51
56
|
|
|
57
|
+
// MARK: Talk mode
|
|
58
|
+
//
|
|
59
|
+
// While two agents talk out loud (`agentx talk`), Option-Space is the
|
|
60
|
+
// door: pressing it hushes them at once, and what is said goes to the
|
|
61
|
+
// talk instead of /ask. See src/daemon/voice-talk-api.ts.
|
|
62
|
+
|
|
63
|
+
/// Silence a running talk. Returns whether one is running, so the
|
|
64
|
+
/// caller knows where the words about to be spoken should go. Never
|
|
65
|
+
/// throws: no daemon means no talk.
|
|
66
|
+
static func talkHush() async -> Bool {
|
|
67
|
+
guard let (data, _) = try? await post("/talk/hush", [:], timeout: 2) else { return false }
|
|
68
|
+
struct Reply: Decodable { let active: Bool? }
|
|
69
|
+
return (try? JSONDecoder().decode(Reply.self, from: data))?.active ?? false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Hand the listener's words to the talk; "stop" ends it.
|
|
73
|
+
static func talkDoor(_ text: String) async throws {
|
|
74
|
+
let (_, response) = try await post("/talk/door", ["text": text], timeout: 5)
|
|
75
|
+
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
|
|
76
|
+
throw NSError(domain: "AgentXVoice", code: http.statusCode,
|
|
77
|
+
userInfo: [NSLocalizedDescriptionKey: "The talk has ended"])
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static func post(_ path: String, _ body: [String: Any], timeout: TimeInterval) async throws -> (Data, URLResponse) {
|
|
82
|
+
var req = URLRequest(url: URL(string: "\(Config.daemonURL)\(path)")!)
|
|
83
|
+
req.httpMethod = "POST"
|
|
84
|
+
req.timeoutInterval = timeout
|
|
85
|
+
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
86
|
+
req.httpBody = try JSONSerialization.data(withJSONObject: body)
|
|
87
|
+
return try await URLSession.shared.data(for: req)
|
|
88
|
+
}
|
|
89
|
+
|
|
52
90
|
static func ask(_ message: String) async throws -> Answer {
|
|
53
91
|
var req = URLRequest(url: URL(string: "\(Config.daemonURL)/ask")!)
|
|
54
92
|
req.httpMethod = "POST"
|
|
@@ -59,13 +97,19 @@ enum AgentClient {
|
|
|
59
97
|
req.httpBody = try JSONSerialization.data(withJSONObject: [
|
|
60
98
|
"message": message,
|
|
61
99
|
"agent": Config.agentID,
|
|
100
|
+
"session": Config.voiceSession,
|
|
62
101
|
])
|
|
63
102
|
|
|
64
103
|
let (data, response) = try await URLSession.shared.data(for: req)
|
|
65
104
|
struct UiButton: Decodable { let label: String; let url: String }
|
|
66
105
|
struct UiMedia: Decodable { let type: String; let url: String; let caption: String? }
|
|
67
106
|
struct Ui: Decodable { let buttons: [UiButton]?; let media: UiMedia? }
|
|
107
|
+
struct Voice: Decodable { let elevenlabsVoiceId: String? }
|
|
108
|
+
struct Presence: Decodable { let mode: String? }
|
|
68
109
|
struct Reply: Decodable {
|
|
110
|
+
let agentId: String?
|
|
111
|
+
let voice: Voice?
|
|
112
|
+
let presence: Presence?
|
|
69
113
|
let text: String?
|
|
70
114
|
let full: String?
|
|
71
115
|
let ui: Ui?
|
|
@@ -73,6 +117,7 @@ enum AgentClient {
|
|
|
73
117
|
let duration: Int?
|
|
74
118
|
}
|
|
75
119
|
let reply = try? JSONDecoder().decode(Reply.self, from: data)
|
|
120
|
+
let voiceID = reply?.voice?.elevenlabsVoiceId
|
|
76
121
|
|
|
77
122
|
// 202 means accepted-but-busy: the daemon has already written a
|
|
78
123
|
// speakable explanation into `text`. Falling through to the error
|
|
@@ -81,7 +126,7 @@ enum AgentClient {
|
|
|
81
126
|
if let http = response as? HTTPURLResponse, http.statusCode == 202,
|
|
82
127
|
let queuedText = reply?.text, !queuedText.isEmpty {
|
|
83
128
|
return Answer(text: queuedText, written: nil, buttons: [], imageURL: nil,
|
|
84
|
-
durationMs: reply?.duration)
|
|
129
|
+
durationMs: reply?.duration, agentID: reply?.agentId, voiceID: voiceID)
|
|
85
130
|
}
|
|
86
131
|
|
|
87
132
|
if let http = response as? HTTPURLResponse, !(200..<300).contains(http.statusCode) {
|
|
@@ -103,6 +148,7 @@ enum AgentClient {
|
|
|
103
148
|
|
|
104
149
|
return Answer(text: text, written: reply?.full,
|
|
105
150
|
buttons: buttons + extra, imageURL: image,
|
|
106
|
-
durationMs: reply?.duration
|
|
151
|
+
durationMs: reply?.duration, agentID: reply?.agentId, voiceID: voiceID,
|
|
152
|
+
presenceMode: reply?.presence?.mode)
|
|
107
153
|
}
|
|
108
154
|
}
|
|
@@ -10,8 +10,13 @@ enum Config {
|
|
|
10
10
|
static let daemonURL = env("AGENTX_DAEMON_URL") ?? "http://127.0.0.1:18800"
|
|
11
11
|
static let agentID = env("AGENTX_VOICE_AGENT") ?? "secretary-agent"
|
|
12
12
|
|
|
13
|
-
/// ElevenLabs default voice
|
|
13
|
+
/// ElevenLabs default voice, used when the answering agent has none
|
|
14
|
+
/// configured. Override with AGENTX_VOICE_ID.
|
|
14
15
|
static let voiceID = env("AGENTX_VOICE_ID") ?? "21m00Tcm4TlvDq8ikWAM"
|
|
16
|
+
|
|
17
|
+
/// One voice session per launch. The daemon uses it to decide whether
|
|
18
|
+
/// an agent still needs to introduce itself.
|
|
19
|
+
static let voiceSession = UUID().uuidString
|
|
15
20
|
static let sttModel = env("AGENTX_STT_MODEL") ?? "scribe_v1"
|
|
16
21
|
|
|
17
22
|
/// How to invoke smart paste. Run through a login shell, so this is a
|
|
@@ -16,7 +16,8 @@ final class Progress: NSObject, URLSessionDataDelegate {
|
|
|
16
16
|
private var session: URLSession?
|
|
17
17
|
private var buffer = Data()
|
|
18
18
|
private let agentID: String
|
|
19
|
-
|
|
19
|
+
/// The step label, and the working agent's voice id when it has one.
|
|
20
|
+
private let onStep: (String, String?) -> Void
|
|
20
21
|
|
|
21
22
|
/// Every touch of `buffer`, `task` and `session` happens here.
|
|
22
23
|
///
|
|
@@ -30,7 +31,7 @@ final class Progress: NSObject, URLSessionDataDelegate {
|
|
|
30
31
|
private let queue = DispatchQueue(label: "tn.acme.agentx.voice.progress")
|
|
31
32
|
private var stopped = false
|
|
32
33
|
|
|
33
|
-
init(agentID: String, onStep: @escaping (String) -> Void) {
|
|
34
|
+
init(agentID: String, onStep: @escaping (String, String?) -> Void) {
|
|
34
35
|
self.agentID = agentID
|
|
35
36
|
self.onStep = onStep
|
|
36
37
|
}
|
|
@@ -112,7 +113,8 @@ final class Progress: NSObject, URLSessionDataDelegate {
|
|
|
112
113
|
// and narrating theirs would be both confusing and a privacy leak.
|
|
113
114
|
guard (obj["agentId"] as? String) == agentID else { return }
|
|
114
115
|
guard let label = summarise(obj) else { return }
|
|
115
|
-
|
|
116
|
+
let voiceID = (obj["voice"] as? [String: Any])?["elevenlabsVoiceId"] as? String
|
|
117
|
+
DispatchQueue.main.async { self.onStep(label, voiceID) }
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
/// Turn a step event into something worth showing in 180 points of
|
|
@@ -72,11 +72,12 @@ enum Speech {
|
|
|
72
72
|
|
|
73
73
|
// MARK: Text to speech
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
/// `voiceID` is the answering agent's voice; nil means the global default.
|
|
76
|
+
static func speak(_ text: String, voiceID: String? = nil) async {
|
|
76
77
|
guard !text.isEmpty else { return }
|
|
77
78
|
if let key = Config.elevenLabsKey {
|
|
78
79
|
do {
|
|
79
|
-
let mp3 = try await elevenLabsTTS(text: text, key: key)
|
|
80
|
+
let mp3 = try await elevenLabsTTS(text: text, voiceID: voiceID ?? Config.voiceID, key: key)
|
|
80
81
|
try await Player.shared.play(mp3)
|
|
81
82
|
return
|
|
82
83
|
} catch {
|
|
@@ -86,8 +87,8 @@ enum Speech {
|
|
|
86
87
|
_ = try? run("/usr/bin/say", [text])
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
private static func elevenLabsTTS(text: String, key: String) async throws -> Data {
|
|
90
|
-
let url = URL(string: "https://api.elevenlabs.io/v1/text-to-speech/\(
|
|
90
|
+
private static func elevenLabsTTS(text: String, voiceID: String, key: String) async throws -> Data {
|
|
91
|
+
let url = URL(string: "https://api.elevenlabs.io/v1/text-to-speech/\(voiceID)")!
|
|
91
92
|
var req = URLRequest(url: url)
|
|
92
93
|
req.httpMethod = "POST"
|
|
93
94
|
req.timeoutInterval = 60
|
|
@@ -28,6 +28,12 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
28
28
|
private var lastStep = "Thinking…"
|
|
29
29
|
private var spokenSteps = Set<String>()
|
|
30
30
|
private var lastSpokeAt = Date.distantPast
|
|
31
|
+
/// The answering agent's voice, learned from the daemon. Nil until
|
|
32
|
+
/// then, which speaks in the global default.
|
|
33
|
+
private var voiceID: String?
|
|
34
|
+
/// Set on Option-Space: whether a talk was running (and is now hushed),
|
|
35
|
+
/// so the words spoken go to the talk rather than to /ask.
|
|
36
|
+
private var talkCheck: Task<Bool, Never>?
|
|
31
37
|
|
|
32
38
|
/// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
|
|
33
39
|
private func smartPaste() {
|
|
@@ -223,6 +229,9 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
223
229
|
// A turn already in flight must not be interrupted by a stray
|
|
224
230
|
// keypress; the answer is still coming and will be spoken.
|
|
225
231
|
guard !busy, !recorder.isRecording else { return }
|
|
232
|
+
// Two agents may be talking out loud. Hush them now, before a word
|
|
233
|
+
// is said, and remember whether they were there.
|
|
234
|
+
talkCheck = Task { await AgentClient.talkHush() }
|
|
226
235
|
do {
|
|
227
236
|
try recorder.start()
|
|
228
237
|
panel.render(.listening)
|
|
@@ -233,6 +242,8 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
233
242
|
|
|
234
243
|
private func stopAndSend() {
|
|
235
244
|
guard recorder.isRecording else { return }
|
|
245
|
+
let talk = talkCheck
|
|
246
|
+
talkCheck = nil
|
|
236
247
|
guard let wav = recorder.stop() else {
|
|
237
248
|
panel.render(.error("Too short — hold while speaking"))
|
|
238
249
|
resetSoon()
|
|
@@ -249,11 +260,21 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
249
260
|
busy = false; resetSoon(); return
|
|
250
261
|
}
|
|
251
262
|
Log.info("heard: \(heard)")
|
|
263
|
+
if await talk?.value == true {
|
|
264
|
+
// The talk answers out loud through the daemon; nothing
|
|
265
|
+
// to speak here.
|
|
266
|
+
try await AgentClient.talkDoor(heard)
|
|
267
|
+
Log.info("sent to the talk")
|
|
268
|
+
panel.render(.idle)
|
|
269
|
+
busy = false
|
|
270
|
+
return
|
|
271
|
+
}
|
|
252
272
|
beginNarration()
|
|
253
273
|
|
|
254
274
|
let answer = try await AgentClient.ask(heard)
|
|
255
275
|
endNarration()
|
|
256
|
-
|
|
276
|
+
if let v = answer.voiceID { voiceID = v }
|
|
277
|
+
Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
257
278
|
// Show BEFORE speaking, but only when there is something
|
|
258
279
|
// the speech cannot deliver — a link, an image, or more
|
|
259
280
|
// text than was read aloud. A card that opens on every
|
|
@@ -268,12 +289,15 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
268
289
|
// Scroll the sentence being spoken, so it can be read as
|
|
269
290
|
// well as heard — and re-read after, which speech cannot do.
|
|
270
291
|
panel.render(.saying(answer.text))
|
|
271
|
-
await Speech.speak(answer.text)
|
|
292
|
+
await Speech.speak(answer.text, voiceID: voiceID)
|
|
272
293
|
panel.render(.idle)
|
|
273
294
|
// Leave the microphone open for a moment. Say nothing and
|
|
274
295
|
// it closes itself; start talking and the conversation
|
|
275
296
|
// simply continues.
|
|
276
297
|
busy = false
|
|
298
|
+
// A live lesson now runs on screen and speaks for itself; an
|
|
299
|
+
// open mic would hear the agent. Option-Space is the door.
|
|
300
|
+
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
|
|
277
301
|
listenHandsFree(followUp: true)
|
|
278
302
|
} catch {
|
|
279
303
|
endNarration()
|
|
@@ -281,7 +305,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
281
305
|
panel.render(.error(short(error.localizedDescription)))
|
|
282
306
|
// Say it aloud too — a voice assistant that fails only in
|
|
283
307
|
// a 230px label has failed silently for anyone not looking.
|
|
284
|
-
await Speech.speak("Sorry, that didn't work.")
|
|
308
|
+
await Speech.speak("Sorry, that didn't work.", voiceID: voiceID)
|
|
285
309
|
resetSoon()
|
|
286
310
|
}
|
|
287
311
|
busy = false
|
|
@@ -303,9 +327,10 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
303
327
|
|
|
304
328
|
// Progress delivers on its own serial queue; hop to main before
|
|
305
329
|
// touching any view.
|
|
306
|
-
progress = Progress(agentID: Config.agentID) { [weak self] step in
|
|
330
|
+
progress = Progress(agentID: Config.agentID) { [weak self] step, voice in
|
|
307
331
|
Task { @MainActor in
|
|
308
332
|
guard let self, self.busy else { return }
|
|
333
|
+
if let voice { self.voiceID = voice }
|
|
309
334
|
self.lastStep = step
|
|
310
335
|
Log.info("step: \(step)")
|
|
311
336
|
self.panel.render(.working(step, Int(Date().timeIntervalSince(self.startedAt))))
|
|
@@ -356,7 +381,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
356
381
|
tool: tool, detail: detail, elapsed: Int(elapsed)) else { return }
|
|
357
382
|
guard !self.spokenSteps.contains(phrase) else { return }
|
|
358
383
|
self.spokenSteps.insert(phrase)
|
|
359
|
-
await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".")
|
|
384
|
+
await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".", voiceID: self.voiceID)
|
|
360
385
|
}
|
|
361
386
|
}
|
|
362
387
|
|
|
@@ -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-
|
|
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-ZSNTLADV.js";import"./chunk-LA6AKS4N.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-
|
|
8
|
+
//# sourceMappingURL=App-K5J52F7O.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-
|
|
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-ZSNTLADV.js";import"./chunk-LA6AKS4N.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-
|
|
7
|
+
//# sourceMappingURL=ChatApp-LRRAPZH7.js.map
|