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