agentix-cli 0.54.0 → 0.55.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-voice/Sources/AgentXVoice/AgentClient.swift +26 -0
- package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +55 -3
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +136 -13
- package/dist/{App-VRTLAPRF.js → App-4EM7IPB3.js} +2 -2
- package/dist/{ChatApp-YENWPZT4.js → ChatApp-FYTEEGSU.js} +2 -2
- package/dist/{agent-HVUGHYBF.js → agent-RZ46ZB4W.js} +2 -2
- package/dist/{board-dashboard-3XJVYDB3.js → board-dashboard-NRRYCDZW.js} +826 -180
- package/dist/board-dashboard-NRRYCDZW.js.map +1 -0
- package/dist/{chunk-M67AN4J7.js → chunk-4HEVSWO3.js} +2 -2
- package/dist/{chunk-KLDGJSR5.js → chunk-6AK6WKE5.js} +2 -2
- package/dist/{chunk-ENRJ2QEH.js → chunk-723K4SAW.js} +2 -2
- package/dist/{chunk-UYMK3XNB.js → chunk-76AHNHWC.js} +2 -2
- package/dist/{chunk-XWU7O3S5.js → chunk-C3VRBEJ7.js} +2 -2
- package/dist/{chunk-HQMISQ7L.js → chunk-CELYD5QW.js} +2 -2
- package/dist/{chunk-33QV5O6P.js → chunk-D3LSPFHK.js} +2 -2
- package/dist/chunk-EJ5ISSWW.js +151 -0
- package/dist/chunk-EJ5ISSWW.js.map +1 -0
- package/dist/{chunk-4HNHHKJF.js → chunk-EZN2YXRY.js} +2 -2
- package/dist/{chunk-BKDUCPZ3.js → chunk-GSOL2LW6.js} +2 -2
- package/dist/{chunk-TLFJVLVQ.js → chunk-JC5Q5MPI.js} +152 -151
- package/dist/chunk-JC5Q5MPI.js.map +1 -0
- package/dist/{chunk-6JHA3ZE6.js → chunk-K4RDE6YU.js} +211 -197
- package/dist/chunk-K4RDE6YU.js.map +1 -0
- package/dist/{chunk-WLRHL2P7.js → chunk-LKJSPO3S.js} +2 -2
- package/dist/{chunk-37EABIOJ.js → chunk-M4AG2YNX.js} +2 -2
- package/dist/{chunk-ZOJUHPD5.js → chunk-O3XG2NVR.js} +18 -18
- package/dist/{chunk-ZOJUHPD5.js.map → chunk-O3XG2NVR.js.map} +1 -1
- package/dist/{chunk-QYHABRLM.js → chunk-SA23V7WX.js} +2 -2
- package/dist/chunk-U4H35SJS.js +15 -0
- package/dist/chunk-U4H35SJS.js.map +1 -0
- package/dist/{chunk-3RCFUIYR.js → chunk-UIVHSYCN.js} +9 -9
- package/dist/chunk-UIVHSYCN.js.map +1 -0
- package/dist/{chunk-3W2KMI5O.js → chunk-W4YMMWJL.js} +2 -2
- package/dist/chunk-WOMQ2VGA.js +5 -0
- package/dist/{chunk-QX4R5WY7.js.map → chunk-WOMQ2VGA.js.map} +1 -1
- package/dist/chunk-X5C5W3E4.js +3 -0
- package/dist/chunk-X5C5W3E4.js.map +1 -0
- package/dist/{chunk-2TJYZF4E.js → chunk-XEOAKD5C.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-IA7ZPE56.js +2 -0
- package/dist/config-HPN2F7LH.js +2 -0
- package/dist/config-mutator-7GPTMTVB.js +2 -0
- package/dist/doctor-IBWQVQV4.js +2 -0
- package/dist/index.d.ts +185 -19
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/program-D5TAPSIH.js +2 -0
- package/dist/{promote-H6ZHJXTY.js → promote-T4UJROEH.js} +2 -2
- package/dist/query-VUJC4Y5O.js +2 -0
- package/dist/registry-DX2RI7I7.js +2 -0
- package/dist/{request-planner-ABD4WIGM.js → request-planner-B7DE7VJR.js} +2 -2
- package/dist/{review-candidates-B6574GOJ.js → review-candidates-4YJHIBAF.js} +2 -2
- package/dist/routing-MZG3UODH.js +2 -0
- package/dist/schedule-tool-A4OFQ4YZ.js +2 -0
- package/dist/scheduler-RHJW2V4M.js +2 -0
- package/dist/seat-2PEI7JRX.js +2 -0
- package/dist/{select-Y7SBUCGJ.js → select-4KIUR27O.js} +2 -2
- package/dist/{set-enabled-ARKMDLXN.js → set-enabled-SE7HQKPF.js} +2 -2
- package/dist/subscriptions-RYVPHVIV.js +2 -0
- package/dist/{tool-5EVYUN2G.js → tool-5VMD7IE4.js} +2 -2
- package/dist/wiki-2QFLAH4U.js +2 -0
- package/dist/wiki-2QFLAH4U.js.map +1 -0
- package/package.json +1 -1
- package/dist/board-dashboard-3XJVYDB3.js.map +0 -1
- package/dist/chunk-3RCFUIYR.js.map +0 -1
- package/dist/chunk-4FFWNPFX.js +0 -15
- package/dist/chunk-4FFWNPFX.js.map +0 -1
- package/dist/chunk-6JHA3ZE6.js.map +0 -1
- package/dist/chunk-BMSPRUEJ.js +0 -150
- package/dist/chunk-BMSPRUEJ.js.map +0 -1
- package/dist/chunk-QX4R5WY7.js +0 -5
- package/dist/chunk-TLFJVLVQ.js.map +0 -1
- package/dist/client-N4XQEAVL.js +0 -2
- package/dist/config-KHKIY6LI.js +0 -2
- package/dist/config-mutator-ZZKFQ7HJ.js +0 -2
- package/dist/doctor-QICAXIQB.js +0 -2
- package/dist/program-ZWNGK3RB.js +0 -2
- package/dist/query-FTE4FLYK.js +0 -2
- package/dist/registry-HDOFVNRK.js +0 -2
- package/dist/routing-BGKZOHOA.js +0 -2
- package/dist/schedule-tool-UDHXTKWV.js +0 -2
- package/dist/scheduler-NQM3HVUY.js +0 -2
- package/dist/seat-VGST5AQB.js +0 -2
- package/dist/wiki-GREI2TSP.js +0 -2
- /package/dist/{App-VRTLAPRF.js.map → App-4EM7IPB3.js.map} +0 -0
- /package/dist/{ChatApp-YENWPZT4.js.map → ChatApp-FYTEEGSU.js.map} +0 -0
- /package/dist/{agent-HVUGHYBF.js.map → agent-RZ46ZB4W.js.map} +0 -0
- /package/dist/{chunk-M67AN4J7.js.map → chunk-4HEVSWO3.js.map} +0 -0
- /package/dist/{chunk-KLDGJSR5.js.map → chunk-6AK6WKE5.js.map} +0 -0
- /package/dist/{chunk-ENRJ2QEH.js.map → chunk-723K4SAW.js.map} +0 -0
- /package/dist/{chunk-UYMK3XNB.js.map → chunk-76AHNHWC.js.map} +0 -0
- /package/dist/{chunk-XWU7O3S5.js.map → chunk-C3VRBEJ7.js.map} +0 -0
- /package/dist/{chunk-HQMISQ7L.js.map → chunk-CELYD5QW.js.map} +0 -0
- /package/dist/{chunk-33QV5O6P.js.map → chunk-D3LSPFHK.js.map} +0 -0
- /package/dist/{chunk-4HNHHKJF.js.map → chunk-EZN2YXRY.js.map} +0 -0
- /package/dist/{chunk-BKDUCPZ3.js.map → chunk-GSOL2LW6.js.map} +0 -0
- /package/dist/{chunk-WLRHL2P7.js.map → chunk-LKJSPO3S.js.map} +0 -0
- /package/dist/{chunk-37EABIOJ.js.map → chunk-M4AG2YNX.js.map} +0 -0
- /package/dist/{chunk-QYHABRLM.js.map → chunk-SA23V7WX.js.map} +0 -0
- /package/dist/{chunk-3W2KMI5O.js.map → chunk-W4YMMWJL.js.map} +0 -0
- /package/dist/{chunk-2TJYZF4E.js.map → chunk-XEOAKD5C.js.map} +0 -0
- /package/dist/{client-N4XQEAVL.js.map → client-IA7ZPE56.js.map} +0 -0
- /package/dist/{config-KHKIY6LI.js.map → config-HPN2F7LH.js.map} +0 -0
- /package/dist/{config-mutator-ZZKFQ7HJ.js.map → config-mutator-7GPTMTVB.js.map} +0 -0
- /package/dist/{doctor-QICAXIQB.js.map → doctor-IBWQVQV4.js.map} +0 -0
- /package/dist/{program-ZWNGK3RB.js.map → program-D5TAPSIH.js.map} +0 -0
- /package/dist/{promote-H6ZHJXTY.js.map → promote-T4UJROEH.js.map} +0 -0
- /package/dist/{query-FTE4FLYK.js.map → query-VUJC4Y5O.js.map} +0 -0
- /package/dist/{registry-HDOFVNRK.js.map → registry-DX2RI7I7.js.map} +0 -0
- /package/dist/{request-planner-ABD4WIGM.js.map → request-planner-B7DE7VJR.js.map} +0 -0
- /package/dist/{review-candidates-B6574GOJ.js.map → review-candidates-4YJHIBAF.js.map} +0 -0
- /package/dist/{routing-BGKZOHOA.js.map → routing-MZG3UODH.js.map} +0 -0
- /package/dist/{schedule-tool-UDHXTKWV.js.map → schedule-tool-A4OFQ4YZ.js.map} +0 -0
- /package/dist/{scheduler-NQM3HVUY.js.map → scheduler-RHJW2V4M.js.map} +0 -0
- /package/dist/{seat-VGST5AQB.js.map → seat-2PEI7JRX.js.map} +0 -0
- /package/dist/{select-Y7SBUCGJ.js.map → select-4KIUR27O.js.map} +0 -0
- /package/dist/{set-enabled-ARKMDLXN.js.map → set-enabled-SE7HQKPF.js.map} +0 -0
- /package/dist/{wiki-GREI2TSP.js.map → subscriptions-RYVPHVIV.js.map} +0 -0
- /package/dist/{tool-5EVYUN2G.js.map → tool-5VMD7IE4.js.map} +0 -0
|
@@ -126,6 +126,32 @@ enum AgentClient {
|
|
|
126
126
|
return (200..<300).contains(http.statusCode)
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/// Who is speaking and who waits, from the daemon's speaking queue.
|
|
130
|
+
struct QueueState: Decodable {
|
|
131
|
+
struct Item: Decodable { let agentId: String? }
|
|
132
|
+
let playing: Item?
|
|
133
|
+
let waiting: [Item]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// The speaking queue now, or nil when the daemon cannot be reached.
|
|
137
|
+
static func queueState() async -> QueueState? {
|
|
138
|
+
guard let url = URL(string: "\(Config.daemonURL)/voice/queue") else { return nil }
|
|
139
|
+
var req = URLRequest(url: url)
|
|
140
|
+
req.timeoutInterval = 2
|
|
141
|
+
guard let (data, _) = try? await URLSession.shared.data(for: req) else { return nil }
|
|
142
|
+
return try? JSONDecoder().decode(QueueState.self, from: data)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// The agent the words are addressed to ("Writer, …"), or `target`.
|
|
146
|
+
/// The daemon matches names, so the widget and config never disagree.
|
|
147
|
+
/// Never throws: no daemon means nobody else to address.
|
|
148
|
+
static func address(_ text: String, target: String) async -> String {
|
|
149
|
+
guard let (data, _) = try? await post("/voice/address", ["text": text, "target": target], timeout: 2) else { return target }
|
|
150
|
+
struct Reply: Decodable { let agentId: String? }
|
|
151
|
+
let id = (try? JSONDecoder().decode(Reply.self, from: data))?.agentId ?? ""
|
|
152
|
+
return id.isEmpty ? target : id
|
|
153
|
+
}
|
|
154
|
+
|
|
129
155
|
private static func post(_ path: String, _ body: [String: Any], timeout: TimeInterval) async throws -> (Data, URLResponse) {
|
|
130
156
|
var req = URLRequest(url: URL(string: "\(Config.daemonURL)\(path)")!)
|
|
131
157
|
req.httpMethod = "POST"
|
|
@@ -7,7 +7,8 @@ import AppKit
|
|
|
7
7
|
@MainActor
|
|
8
8
|
final class StatusMenu: NSObject, NSMenuDelegate {
|
|
9
9
|
let menu = NSMenu()
|
|
10
|
-
|
|
10
|
+
/// Variable width: the badge with the number of queued lines sits beside the icon.
|
|
11
|
+
private let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
|
11
12
|
|
|
12
13
|
private enum Roster {
|
|
13
14
|
case loading
|
|
@@ -19,6 +20,14 @@ final class StatusMenu: NSObject, NSMenuDelegate {
|
|
|
19
20
|
private var roster = Roster.loading
|
|
20
21
|
/// Reopen the menu once the retry's answer is in.
|
|
21
22
|
private var reopenAfterRefresh = false
|
|
23
|
+
/// The daemon's speaking queue, polled while anything is in flight.
|
|
24
|
+
private var queue: AgentClient.QueueState?
|
|
25
|
+
private var queuePoll: Timer?
|
|
26
|
+
|
|
27
|
+
/// Questions this widget has in flight, per agent. Set by the app.
|
|
28
|
+
var thinking: [String: Int] = [:] {
|
|
29
|
+
didSet { rebuild(); watchQueue(force: true) }
|
|
30
|
+
}
|
|
22
31
|
|
|
23
32
|
/// Set by the app.
|
|
24
33
|
var onStop: (() -> Void)?
|
|
@@ -48,7 +57,9 @@ final class StatusMenu: NSObject, NSMenuDelegate {
|
|
|
48
57
|
Task {
|
|
49
58
|
let agents = await AgentClient.agents()
|
|
50
59
|
roster = agents.map { .loaded($0) } ?? .down
|
|
60
|
+
queue = await AgentClient.queueState()
|
|
51
61
|
rebuild()
|
|
62
|
+
showBadge()
|
|
52
63
|
if reopenAfterRefresh { reopenAfterRefresh = false; open() }
|
|
53
64
|
}
|
|
54
65
|
}
|
|
@@ -69,6 +80,48 @@ final class StatusMenu: NSObject, NSMenuDelegate {
|
|
|
69
80
|
item.button?.toolTip = "AgentX Voice — \(label)"
|
|
70
81
|
}
|
|
71
82
|
|
|
83
|
+
/// The number of lines waiting to be spoken, beside the icon.
|
|
84
|
+
private func showBadge() {
|
|
85
|
+
let n = queue?.waiting.count ?? 0
|
|
86
|
+
item.button?.imagePosition = .imageLeading
|
|
87
|
+
item.button?.title = n > 0 ? "\(n)" : ""
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Poll the queue only while there is something to show: a question in
|
|
91
|
+
/// flight, or lines playing or waiting. `force` starts it whatever the
|
|
92
|
+
/// last poll saw: an answer that just came back is about to be queued,
|
|
93
|
+
/// so the poll carries on until it has been seen through.
|
|
94
|
+
private func watchQueue(force: Bool = false) {
|
|
95
|
+
let active = force || !thinking.isEmpty || queue?.playing != nil || !(queue?.waiting.isEmpty ?? true)
|
|
96
|
+
if !active { queuePoll?.invalidate(); queuePoll = nil; return }
|
|
97
|
+
guard queuePoll == nil else { return }
|
|
98
|
+
queuePoll = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { _ in
|
|
99
|
+
Task { @MainActor [weak self] in
|
|
100
|
+
guard let self else { return }
|
|
101
|
+
self.queue = await AgentClient.queueState()
|
|
102
|
+
self.rebuild()
|
|
103
|
+
self.showBadge()
|
|
104
|
+
self.watchQueue()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// "thinking", "speaking", "queued 2", or both, for one agent's row.
|
|
110
|
+
private func state(of agent: AgentClient.AgentInfo) -> String {
|
|
111
|
+
var parts: [String] = []
|
|
112
|
+
// Speaking one answer and thinking on the next are both true at once.
|
|
113
|
+
if queue?.playing?.agentId == agent.id { parts.append("speaking") }
|
|
114
|
+
let asked = thinking[agent.id, default: 0]
|
|
115
|
+
if asked > 0 {
|
|
116
|
+
// More than one: the rest wait for the answer in flight.
|
|
117
|
+
parts.append(asked > 1 ? "thinking (+\(asked - 1) asked)" : "thinking")
|
|
118
|
+
}
|
|
119
|
+
if parts.isEmpty && (agent.active ?? 0) > 0 { parts.append("working") }
|
|
120
|
+
let queued = queue?.waiting.filter { $0.agentId == agent.id }.count ?? 0
|
|
121
|
+
if queued > 0 { parts.append("queued \(queued)") }
|
|
122
|
+
return parts.isEmpty ? "idle" : parts.joined(separator: " · ")
|
|
123
|
+
}
|
|
124
|
+
|
|
72
125
|
// MARK: NSMenuDelegate
|
|
73
126
|
|
|
74
127
|
/// Show what is known now and refresh underneath; the open menu
|
|
@@ -95,9 +148,8 @@ final class StatusMenu: NSObject, NSMenuDelegate {
|
|
|
95
148
|
menu.addItem(NSMenuItem.sectionHeader(title: pinned ? "Agent (set by AGENTX_VOICE_AGENT)" : "Talk to"))
|
|
96
149
|
if agents.isEmpty { menu.addItem(disabled("No agents configured")) }
|
|
97
150
|
for (i, agent) in agents.enumerated() {
|
|
98
|
-
let busy = (agent.active ?? 0) > 0
|
|
99
151
|
// Digits pick an agent while the menu is open.
|
|
100
|
-
let row = action(agent.label +
|
|
152
|
+
let row = action(agent.label + " · " + state(of: agent), #selector(pick(_:)),
|
|
101
153
|
key: i < 9 ? "\(i + 1)" : "", modifiers: [])
|
|
102
154
|
row.representedObject = agent.id
|
|
103
155
|
row.state = agent.id == target ? .on : .off
|
|
@@ -47,6 +47,41 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
47
47
|
private var followUp: String?
|
|
48
48
|
/// "stop" while our own turn was running: drop its answer.
|
|
49
49
|
private var abandoned = false
|
|
50
|
+
/// The agent our own turn is asking. Usually the target; "Writer, …"
|
|
51
|
+
/// sends one question to Writer and leaves the target alone.
|
|
52
|
+
private var turnAgent = ""
|
|
53
|
+
/// Questions in flight per agent: our turn plus any asides.
|
|
54
|
+
private var inFlight: [String: Int] = [:]
|
|
55
|
+
/// Bumped by every stop, so an aside answered after it stays quiet.
|
|
56
|
+
private var stopGeneration = 0
|
|
57
|
+
/// Questions for an agent that already has one in flight, oldest
|
|
58
|
+
/// first. One /ask per agent at a time: a second question waits here
|
|
59
|
+
/// for the first answer instead of racing it into the same session.
|
|
60
|
+
private var waitingAsides: [String: [String]] = [:]
|
|
61
|
+
/// Asides not yet finished: asked, answering, or waiting to be spoken.
|
|
62
|
+
/// While any are open the microphone does not reopen by itself, or it
|
|
63
|
+
/// would hear the next answer as the listener's words.
|
|
64
|
+
private var openAsides = 0
|
|
65
|
+
|
|
66
|
+
/// /ask, counted in flight for the menu while it thinks.
|
|
67
|
+
private func ask(_ heard: String, agent: String) async throws -> AgentClient.Answer {
|
|
68
|
+
track(agent, 1)
|
|
69
|
+
defer { track(agent, -1) }
|
|
70
|
+
return try await AgentClient.ask(heard, agent: agent)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private func track(_ agent: String, _ delta: Int) {
|
|
74
|
+
let n = inFlight[agent, default: 0] + delta
|
|
75
|
+
inFlight[agent] = n > 0 ? n : nil
|
|
76
|
+
publishThinking()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// The menu's per-agent count: questions asked plus questions waiting.
|
|
80
|
+
private func publishThinking() {
|
|
81
|
+
var counts = inFlight
|
|
82
|
+
for (agent, words) in waitingAsides { counts[agent, default: 0] += words.count }
|
|
83
|
+
statusMenu.thinking = counts
|
|
84
|
+
}
|
|
50
85
|
|
|
51
86
|
/// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
|
|
52
87
|
private func smartPaste() {
|
|
@@ -97,7 +132,10 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
97
132
|
panel.onRender = { [weak self] state in self?.statusMenu.show(state) }
|
|
98
133
|
panel.contextMenu = { [weak self] in self?.statusMenu.menu ?? NSMenu() }
|
|
99
134
|
panel.agentName = { [weak self] in
|
|
100
|
-
|
|
135
|
+
guard let self else { return "" }
|
|
136
|
+
// A question sent by name shows who is answering it, not the target.
|
|
137
|
+
let who = self.busy && !self.turnAgent.isEmpty ? self.turnAgent : Config.effectiveAgentID
|
|
138
|
+
return who.isEmpty ? "" : self.statusMenu.name(of: who)
|
|
101
139
|
}
|
|
102
140
|
panel.alwaysVisible = Config.showPill
|
|
103
141
|
panel.render(.idle)
|
|
@@ -283,6 +321,9 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
283
321
|
private func stopSpeaking() {
|
|
284
322
|
Log.info("stop: silencing every voice")
|
|
285
323
|
if busy { silenced = true }
|
|
324
|
+
stopGeneration += 1
|
|
325
|
+
waitingAsides.removeAll()
|
|
326
|
+
publishThinking()
|
|
286
327
|
lastSpokeAt = Date()
|
|
287
328
|
Task { await Speech.stopAll() }
|
|
288
329
|
if !recorder.isRecording { panel.render(.idle) }
|
|
@@ -354,17 +395,95 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
354
395
|
if !midTurn { panel.render(.idle); busy = false }
|
|
355
396
|
return
|
|
356
397
|
}
|
|
398
|
+
// "Writer, …" sends this one question to Writer; the target stays.
|
|
399
|
+
let agent = await AgentClient.address(heard, target: Config.effectiveAgentID)
|
|
357
400
|
if midTurn {
|
|
401
|
+
// Another agent: ask it alongside ours rather than waiting.
|
|
402
|
+
if agent != turnAgent && !Self.isStop(heard) {
|
|
403
|
+
askAside(heard, agent: agent)
|
|
404
|
+
panel.render(.working("Asked \(statusMenu.name(of: agent))", 0))
|
|
405
|
+
return
|
|
406
|
+
}
|
|
358
407
|
// Our own turn is still thinking. The agent cannot change
|
|
359
408
|
// course mid-turn, so his words go next and the stale
|
|
360
|
-
// answer is not spoken; "stop" just drops it
|
|
361
|
-
|
|
362
|
-
|
|
409
|
+
// answer is not spoken; "stop" just drops it, and every
|
|
410
|
+
// aside with it.
|
|
411
|
+
if Self.isStop(heard) {
|
|
412
|
+
abandoned = true; followUp = nil
|
|
413
|
+
stopGeneration += 1; waitingAsides.removeAll(); publishThinking()
|
|
414
|
+
Log.info("door: stop → dropping the turn in flight")
|
|
415
|
+
} else {
|
|
416
|
+
followUp = heard
|
|
417
|
+
Log.info("door: \"\(heard)\" → next, instead of the answer in flight")
|
|
418
|
+
}
|
|
363
419
|
panel.render(.working("Got it — one moment", 0))
|
|
364
420
|
return
|
|
365
421
|
}
|
|
366
|
-
|
|
422
|
+
// That agent is still answering an aside: this question waits
|
|
423
|
+
// behind it rather than running beside it.
|
|
424
|
+
if inFlight[agent] != nil && !Self.isStop(heard) {
|
|
425
|
+
askAside(heard, agent: agent)
|
|
426
|
+
panel.render(.working("Queued for \(statusMenu.name(of: agent))", 0))
|
|
427
|
+
busy = false; resetSoon()
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
await runTurn(heard, agent: agent)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/// A question for another agent while our own turn thinks. It runs
|
|
435
|
+
/// alongside; the answer waits its turn in the daemon's speaking queue,
|
|
436
|
+
/// so it never talks over ours. An agent already answering an aside
|
|
437
|
+
/// gets this one next, not at the same time.
|
|
438
|
+
private func askAside(_ heard: String, agent: String) {
|
|
439
|
+
if inFlight[agent] != nil || waitingAsides[agent] != nil {
|
|
440
|
+
waitingAsides[agent, default: []].append(heard)
|
|
441
|
+
publishThinking()
|
|
442
|
+
Log.info("aside → \(agent) (waits for its answer in flight): \(heard)")
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
runAside(heard, agent: agent)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private func runAside(_ heard: String, agent: String) {
|
|
449
|
+
let generation = stopGeneration
|
|
450
|
+
openAsides += 1
|
|
451
|
+
Log.info("aside → \(agent): \(heard)")
|
|
452
|
+
Task { @MainActor in
|
|
453
|
+
defer { openAsides -= 1 }
|
|
454
|
+
let result: Result<AgentClient.Answer, Error>
|
|
455
|
+
do { result = .success(try await ask(heard, agent: agent)) }
|
|
456
|
+
catch { result = .failure(error) }
|
|
457
|
+
// The next question for this agent goes now, while this
|
|
458
|
+
// answer waits to be spoken.
|
|
459
|
+
startNextAside(for: agent)
|
|
460
|
+
guard generation == stopGeneration else { return }
|
|
461
|
+
switch result {
|
|
462
|
+
case .success(let answer):
|
|
463
|
+
Log.info("aside answer (\(answer.agentID ?? agent)): \(answer.text)")
|
|
464
|
+
if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
|
|
465
|
+
buttons: answer.buttons, imageURL: answer.imageURL) {
|
|
466
|
+
card.show(spoken: answer.text, written: answer.written,
|
|
467
|
+
buttons: answer.buttons, imageURL: answer.imageURL)
|
|
468
|
+
}
|
|
469
|
+
await Speech.say(answer.text, agentID: answer.agentID ?? agent, kind: "answer", voice: answer.voice)
|
|
470
|
+
case .failure(let error):
|
|
471
|
+
Log.warn("aside failed (\(agent)): \(error.localizedDescription)")
|
|
472
|
+
await Speech.say("Sorry, \(statusMenu.name(of: agent)) couldn't answer that.", agentID: nil, kind: "line", voice: nil)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/// Send the oldest question waiting for `agent`, if any.
|
|
478
|
+
private func startNextAside(for agent: String) {
|
|
479
|
+
guard var waiting = waitingAsides[agent], !waiting.isEmpty else {
|
|
480
|
+
waitingAsides[agent] = nil
|
|
481
|
+
return
|
|
367
482
|
}
|
|
483
|
+
let next = waiting.removeFirst()
|
|
484
|
+
waitingAsides[agent] = waiting.isEmpty ? nil : waiting
|
|
485
|
+
runAside(next, agent: agent)
|
|
486
|
+
publishThinking()
|
|
368
487
|
}
|
|
369
488
|
|
|
370
489
|
static func isStop(_ s: String) -> Bool {
|
|
@@ -374,12 +493,13 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
374
493
|
|
|
375
494
|
/// One question and its spoken answer. Words said through the door
|
|
376
495
|
/// while it thinks replace the answer with the next turn.
|
|
377
|
-
private func runTurn(_ heard: String) async {
|
|
496
|
+
private func runTurn(_ heard: String, agent: String) async {
|
|
378
497
|
silenced = false
|
|
498
|
+
turnAgent = agent
|
|
379
499
|
do {
|
|
380
|
-
beginNarration()
|
|
500
|
+
beginNarration(agent)
|
|
381
501
|
|
|
382
|
-
let answer = try await
|
|
502
|
+
let answer = try await ask(heard, agent: agent)
|
|
383
503
|
endNarration()
|
|
384
504
|
if abandoned {
|
|
385
505
|
abandoned = false
|
|
@@ -390,7 +510,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
390
510
|
followUp = nil
|
|
391
511
|
Log.info("superseded answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
392
512
|
panel.render(.thinking)
|
|
393
|
-
return await runTurn(next)
|
|
513
|
+
return await runTurn(next, agent: agent)
|
|
394
514
|
}
|
|
395
515
|
if let v = answer.voice { voice = v }
|
|
396
516
|
Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
@@ -419,7 +539,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
419
539
|
if recorder.isRecording { busy = false; return }
|
|
420
540
|
// Stopped: no follow-up window, the listener asked for quiet.
|
|
421
541
|
if silenced { silenced = false; panel.render(.idle); busy = false; return }
|
|
422
|
-
if let next = followUp { followUp = nil; return await runTurn(next) }
|
|
542
|
+
if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
|
|
423
543
|
panel.render(.idle)
|
|
424
544
|
// Leave the microphone open for a moment. Say nothing and
|
|
425
545
|
// it closes itself; start talking and the conversation
|
|
@@ -428,12 +548,15 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
428
548
|
// A live lesson now runs on screen and speaks for itself; an
|
|
429
549
|
// open mic would hear the agent. Option-Space is the door.
|
|
430
550
|
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
|
|
551
|
+
// Another agent's answer is still to come; an open mic would
|
|
552
|
+
// take it for the listener's words.
|
|
553
|
+
if openAsides > 0 { return }
|
|
431
554
|
listenHandsFree(followUp: true)
|
|
432
555
|
} catch {
|
|
433
556
|
endNarration()
|
|
434
557
|
Log.warn("turn failed: \(error.localizedDescription)")
|
|
435
558
|
if abandoned { abandoned = false; panel.render(.idle); busy = false; return }
|
|
436
|
-
if let next = followUp { followUp = nil; return await runTurn(next) }
|
|
559
|
+
if let next = followUp { followUp = nil; return await runTurn(next, agent: agent) }
|
|
437
560
|
panel.render(.error(short(error.localizedDescription)))
|
|
438
561
|
// Say it aloud too — a voice assistant that fails only in
|
|
439
562
|
// a 230px label has failed silently for anyone not looking.
|
|
@@ -449,7 +572,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
449
572
|
/// say what the agent is doing but arrive irregularly, and a 1-second
|
|
450
573
|
/// ticker proves the thing is still alive between them. Together they
|
|
451
574
|
/// answer both "what is it doing" and "is it stuck".
|
|
452
|
-
private func beginNarration() {
|
|
575
|
+
private func beginNarration(_ agent: String) {
|
|
453
576
|
startedAt = Date()
|
|
454
577
|
lastStep = "Thinking…"
|
|
455
578
|
spokenSteps.removeAll()
|
|
@@ -458,7 +581,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
458
581
|
|
|
459
582
|
// Progress delivers on its own serial queue; hop to main before
|
|
460
583
|
// touching any view.
|
|
461
|
-
progress = Progress(agentID:
|
|
584
|
+
progress = Progress(agentID: agent) { [weak self] step, voice in
|
|
462
585
|
Task { @MainActor in
|
|
463
586
|
guard let self, self.busy else { return }
|
|
464
587
|
if let voice { self.voice = voice }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{a as F,b as L,c as U,d as N,e as O,f as H,g as j}from"./chunk-U2CKL755.js";import{b as B,c as D,d as A,e as P,g as M}from"./chunk-
|
|
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-WOMQ2VGA.js";import"./chunk-U4H35SJS.js";import"./chunk-QIJLFPWE.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
|
|
2
2
|
|
|
3
3
|
`))>=0;){let h=y.slice(0,d);y=y.slice(d+2);let m=X(h);m&&(yield m)}}}finally{try{u.releaseLock()}catch{}}}function X(e){let r="message",t="";for(let s of e.split(`
|
|
4
4
|
`))s.startsWith("event: ")?r=s.slice(7):s.startsWith("data: ")&&(t=s.slice(6));if(!t)return null;let n;try{n=JSON.parse(t)}catch{n=t}return{event:r,data:n}}import{jsx as a,jsxs as c}from"react/jsx-runtime";function $(e,r){return r<=0||e<0?0:e>=r?r-1:e}function E(){return`tui:${z().slice(0,12)}`}function k(e,r){let t=[...e,r];return t.length>50&&t.splice(0,t.length-50),t}function Y(e,r){switch(r.type){case"agents":return{...e,agents:r.data,cursor:{...e.cursor,agents:$(e.cursor.agents,r.data.length)}};case"processes":return{...e,processes:r.data,cursor:{...e.cursor,processes:$(e.cursor.processes,r.data.length)}};case"crons":return{...e,crons:r.data};case"event":{let t=[r.row,...e.events];t.length>200&&(t.length=200);let n=e.channels;return r.channelTally&&(n=new Map(e.channels),n.set(r.channelTally.channel,r.channelTally)),{...e,events:t,channels:n}}case"connError":return{...e,connError:r.error};case"tick":return{...e,lastTick:Date.now()};case"focus":return{...e,focus:r.pane};case"cursor":{let t=r.pane==="agents"?e.agents.length:r.pane==="processes"?e.processes.length:e.events.length;return{...e,cursor:{...e.cursor,[r.pane]:$(e.cursor[r.pane]+r.delta,t)}}}case"bottomRight":return{...e,bottomRight:r.pane};case"chatStart":return{...e,chat:{active:!0,agentId:r.agentId,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatExit":return{...e,chat:{...e.chat,active:!1}};case"chatClear":return{...e,chat:{...e.chat,chatId:E(),history:[],text:"",status:"idle",error:null}};case"chatText":return{...e,chat:{...e.chat,text:r.text}};case"chatSubmitStart":return{...e,chat:{...e.chat,history:k(e.chat.history,r.you),text:"",status:"sending",error:null}};case"chatSubmitDone":return{...e,chat:{...e.chat,history:k(e.chat.history,r.reply),status:"idle",error:null}};case"chatSubmitError":return{...e,chat:{...e.chat,history:k(e.chat.history,{role:"error",text:r.error,at:Date.now()}),status:"idle",error:r.error}};case"chatStreamStart":{let t=k(e.chat.history,r.you),n=k(t,{role:"agent",text:"",at:Date.now(),streaming:!0,tools:[]});return{...e,chat:{...e.chat,history:n,text:"",status:"sending",error:null}}}case"chatStreamDelta":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,text:n.text+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamThinking":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,thinking:(n.thinking??"")+r.text}),{...e,chat:{...e.chat,history:t}}}case"chatStreamTool":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]={...n,tools:[...n.tools??[],{name:r.name,arg:r.arg,error:r.error}]}),{...e,chat:{...e.chat,history:t}}}case"chatStreamEnd":{let t=e.chat.history.slice(),n=t[t.length-1];return n?.role==="agent"&&n.streaming&&(t[t.length-1]=r.error?{...n,streaming:!1,role:"error",text:r.error}:{...n,streaming:!1,elapsedMs:r.elapsedMs,outTokens:r.outTokens}),{...e,chat:{...e.chat,history:t,status:"idle",error:r.error??null}}}case"toast":return{...e,toast:{text:r.text,color:r.color,at:Date.now()}};case"clearToast":return{...e,toast:null}}}var _={agents:[],processes:[],crons:[],events:[],channels:new Map,connError:null,lastTick:0,focus:"agents",cursor:{agents:0,processes:0,events:0},bottomRight:"crons",chat:{active:!1,agentId:null,chatId:E(),history:[],text:"",status:"idle",error:null},toast:null};function be({conn:e,pollMs:r=3e3}){let[t,n]=V(Y,_),{exit:s}=W(),u=G(null),p=G(null),y=t.agents.map(l=>l.id),f=t.chat.active?L(t.chat.text,y,process.cwd()):null;return R(()=>{if(!t.toast)return;let l=setTimeout(()=>n({type:"clearToast"}),3500);return()=>clearTimeout(l)},[t.toast]),J((l,d)=>{if(t.chat.active){if(d.escape){n({type:"chatExit"});return}if(d.ctrl&&l==="l"){n({type:"chatClear"});return}if(d.tab){let m=U(t.chat.text,p,y,process.cwd());m!==null&&n({type:"chatText",text:m});return}if(d.backspace||d.delete){p.current=null,n({type:"chatText",text:t.chat.text.slice(0,-1)});return}if(d.ctrl||d.meta)return;let h=N(l??"",d.return,t.chat.text);if(h.kind==="paste"||h.kind==="type"){p.current=null,n({type:"chatText",text:t.chat.text+h.text});return}if(h.kind==="continue"){n({type:"chatText",text:h.buffer});return}if(h.kind==="submit"){let m=h.text.trim(),g=t.chat.agentId;if(!m||!g||t.chat.status==="sending"){n({type:"chatText",text:""});return}p.current=null;let b=Date.now(),w=t.chat.chatId;n({type:"chatStreamStart",you:{role:"you",text:m,at:b}}),(async()=>{try{let C=await M(e,g,m,{channel:"tui",chatId:w,onText:x=>n({type:"chatStreamDelta",text:x}),onThinking:x=>n({type:"chatStreamThinking",text:x}),onTool:x=>{x.status==="start"&&x.name?n({type:"chatStreamTool",name:x.name,arg:x.arg}):x.status==="result"&&x.error&&n({type:"chatStreamTool",name:x.name??"tool",error:!0})}});n({type:"chatStreamEnd",elapsedMs:Date.now()-b,outTokens:C.usage?.outputTokens,error:C.error})}catch(C){n({type:"chatStreamEnd",elapsedMs:Date.now()-b,error:C?.message||String(C)})}})();return}return}if(l==="q"||d.ctrl&&l==="c"){u.current?.abort(),s();return}if(d.tab){let h=["agents","processes","events"],m=h.indexOf(t.focus);n({type:"focus",pane:h[(m+1)%h.length]});return}if(l==="a"){n({type:"focus",pane:"agents"});return}if(l==="p"){n({type:"focus",pane:"processes"});return}if(l==="e"){n({type:"focus",pane:"events"});return}if(l==="c"){n({type:"bottomRight",pane:t.bottomRight==="crons"?"channels":"crons"});return}if(d.upArrow){n({type:"cursor",pane:t.focus,delta:-1});return}if(d.downArrow){n({type:"cursor",pane:t.focus,delta:1});return}if(d.return&&t.focus==="agents"){let h=t.agents[t.cursor.agents];if(!h){n({type:"toast",text:"no agent selected",color:"yellow"});return}n({type:"chatStart",agentId:h.id});return}if(l==="k"&&t.focus==="processes"){let h=t.processes[t.cursor.processes];if(!h){n({type:"toast",text:"no process selected",color:"yellow"});return}(async()=>{try{await P(e,h.key,"tui"),n({type:"toast",text:`killed ${h.key.agentId}/${h.key.channel}/${h.key.chatId}`,color:"green"})}catch(m){n({type:"toast",text:`kill failed: ${m?.message||m}`,color:"red"})}})();return}}),R(()=>{let l=new AbortController;u.current=l;let d=!1,h=async()=>{try{let[g,b,w]=await Promise.all([B(e,l.signal).catch(()=>[]),D(e,l.signal).catch(()=>[]),A(e,l.signal).catch(()=>[])]);if(d)return;n({type:"agents",data:g}),n({type:"processes",data:b}),n({type:"crons",data:w}),n({type:"tick"}),n({type:"connError",error:null})}catch(g){d||n({type:"connError",error:g?.message||String(g)})}};h();let m=setInterval(h,r);return()=>{d=!0,clearInterval(m),l.abort()}},[e.baseUrl,e.token,r]),R(()=>{let l=!1,d=new AbortController,h=new Map;return(async()=>{let m=500;for(;!l;){try{for await(let g of q({baseUrl:e.baseUrl,token:e.token,signal:d.signal})){let b=ae(g),w;if(g.event==="channel"&&typeof g.data?.channel=="string"){let S=h.get(g.data.channel)??{channel:g.data.channel,inbound:0,outbound:0,lastAt:0,lastPreview:""},C=S.inbound+(g.data.direction==="in"?1:0),x=S.outbound+(g.data.direction==="out"?1:0);w={channel:g.data.channel,inbound:C,outbound:x,lastAt:b.at,lastPreview:(g.data.textPreview||"").slice(0,60)},h.set(g.data.channel,w)}n({type:"event",row:b,channelTally:w}),m=500}}catch(g){if(l)return;n({type:"connError",error:`events: ${g?.message||g}`})}await new Promise(g=>setTimeout(g,m)),m=Math.min(m*2,1e4)}})(),()=>{l=!0,d.abort()}},[e.baseUrl,e.token]),c(i,{flexDirection:"column",width:"100%",children:[a(K,{conn:e,lastTick:t.lastTick,connError:t.connError,chat:t.chat}),c(i,{flexDirection:"row",flexGrow:1,children:[c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"AGENTS",focused:!t.chat.active&&t.focus==="agents",children:a(Z,{rows:t.agents,cursor:t.cursor.agents,focused:!t.chat.active&&t.focus==="agents",talkingTo:t.chat.active?t.chat.agentId:null})}),a(v,{title:"PROCESSES",focused:!t.chat.active&&t.focus==="processes",children:a(ee,{rows:t.processes,cursor:t.cursor.processes,focused:!t.chat.active&&t.focus==="processes"})})]}),t.chat.active?a(oe,{chat:t.chat,suggest:f,agents:t.agents}):c(i,{flexDirection:"column",width:"50%",children:[a(v,{title:"LIVE EVENTS",focused:t.focus==="events",children:a(ne,{rows:t.events,cursor:t.cursor.events,focused:t.focus==="events"})}),a(v,{title:t.bottomRight==="crons"?"CRONS / SCHEDULES":"CHANNELS",focused:!1,children:t.bottomRight==="crons"?a(te,{rows:t.crons}):a(re,{rows:Array.from(t.channels.values())})})]})]}),a(Q,{focus:t.focus,bottomRight:t.bottomRight,chat:t.chat,toast:t.toast})]})}function K({conn:e,lastTick:r,connError:t,chat:n}){let s=t?c(o,{color:"red",children:["\u25CF ",t]}):r===0?a(o,{color:"yellow",children:"\u25CF connecting\u2026"}):a(o,{color:"green",children:"\u25CF live"}),u=n.active?c(o,{color:"green",bold:!0,children:["CHAT @",n.agentId]}):a(o,{bold:!0,children:"agentx tui"});return c(i,{paddingX:1,justifyContent:"space-between",children:[a(i,{children:u}),a(o,{dimColor:!0,children:e.baseUrl}),a(i,{children:s})]})}function Q({focus:e,bottomRight:r,chat:t,toast:n}){let s=t.active?["[Enter] send","[Esc] exit chat","[Ctrl-L] new session"]:[`[Tab/a/p/e] focus (now: ${e})`,e==="agents"?"[Enter] chat":null,e==="processes"?"[k] kill":null,`[c] ${r==="crons"?"\u2192channels":"\u2192crons"}`,"[\u2191/\u2193] move","[q] quit"].filter(Boolean);return c(i,{paddingX:1,borderStyle:"single",borderTop:!0,borderBottom:!1,borderLeft:!1,borderRight:!1,justifyContent:"space-between",children:[a(o,{dimColor:!0,children:s.join(" \xB7 ")}),n?a(o,{color:n.color,children:n.text}):a(o,{children:" "})]})}function v({title:e,focused:r,children:t}){return c(i,{flexDirection:"column",width:"100%",borderStyle:r?"double":"single",borderColor:r?"cyan":void 0,paddingX:1,children:[c(o,{bold:!0,color:r?"cyan":"white",children:[e,r?" \u25CF":""]}),a(i,{flexDirection:"column",marginTop:1,children:t})]})}function I(e,r){return e&&r?{inverse:!0}:{}}function Z({rows:e,cursor:r,focused:t,talkingTo:n}){return e.length===0?a(o,{dimColor:!0,children:"no agents"}):a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=s.errors>0?"red":s.active>0?"yellow":"green",f=s.id===n;return c(i,{children:[a(o,{...p,color:y,children:"\u25CF"}),c(o,{...p,children:[" ",T(s.name,14)]}),c(o,{...p,children:[T(s.tier,6)," "]}),c(o,{...p,children:[s.active,"/",s.total]}),s.errors>0?c(o,{...p,color:"red",children:[" err=",s.errors]}):null,f?a(o,{color:"green",children:" \u2194 chatting"}):null]},s.id)})})}function ee({rows:e,cursor:r,focused:t}){if(e.length===0)return a(o,{dimColor:!0,children:"no live processes"});let n=Date.now();return a(i,{flexDirection:"column",children:e.slice(0,12).map((s,u)=>{let p=I(t,u===r),y=Math.max(0,Math.round((n-(s.lastTurnAt||s.spawnedAt))/1e3)),f=s.state==="dead"?"red":s.state==="warm-hot"?"green":"yellow";return c(i,{children:[a(o,{...p,color:f,children:T(s.state,10)}),a(o,{...p,children:T(s.key.agentId,12)}),a(o,{...p,children:T(s.key.channel,10)}),c(o,{...p,children:[s.turnCount,"t ",y,"s"]})]},`${s.key.agentId}-${s.key.channel}-${s.key.chatId}-${u}`)})})}function te({rows:e}){return e.length===0?a(o,{dimColor:!0,children:"no crons"}):a(i,{flexDirection:"column",children:e.slice(0,12).map(r=>{let t=r.enabled?r.consecutiveErrors>0?a(o,{color:"red",children:"err"}):a(o,{color:"green",children:"ok"}):a(o,{dimColor:!0,children:"off"}),n=r.nextRun?new Date(r.nextRun).toLocaleTimeString():"\u2014";return c(i,{children:[a(o,{children:T(r.id,16)}),a(o,{dimColor:!0,children:T(r.schedule,12)}),a(o,{children:T(n,10)}),a(i,{children:t})]},r.id)})})}function re({rows:e}){if(e.length===0)return a(o,{dimColor:!0,children:"(waiting for channel events\u2026)"});let r=[...e].sort((t,n)=>n.lastAt-t.lastAt);return a(i,{flexDirection:"column",children:r.slice(0,12).map(t=>c(i,{children:[a(o,{children:T(t.channel,12)}),c(o,{color:"cyan",children:["\u2190",T(String(t.inbound),4)]}),c(o,{color:"magenta",children:["\u2192",T(String(t.outbound),4)]}),a(o,{dimColor:!0,children:t.lastPreview})]},t.channel))})}function ne({rows:e,cursor:r,focused:t}){return e.length===0?a(o,{dimColor:!0,children:"(waiting for events\u2026)"}):a(i,{flexDirection:"column",children:e.slice(0,18).map((n,s)=>{let u=I(t,s===r);return c(i,{children:[a(o,{...u,dimColor:!0,children:new Date(n.at).toLocaleTimeString().padEnd(10)}),a(o,{...u,color:ce(n.kind),children:T(n.kind,8)}),a(o,{...u,children:n.line})]},`${n.at}-${s}`)})})}function oe({chat:e,suggest:r,agents:t}){let n=e.history.slice(-14),s=t.find(f=>f.id===e.agentId),u=H(s?.tier??"",s?.model),p=e.status==="sending"?[...e.history].reverse().find(f=>f.role==="agent"&&f.streaming):void 0,y=e.status==="sending"?a(O,{startedAt:p?.at??Date.now(),phase:p?.text?"responding":"thinking",indent:""}):e.error?c(o,{color:"red",children:["\u25CF ",e.error]}):a(o,{dimColor:!0,children:"\u25CF ready"});return c(i,{flexDirection:"column",width:"50%",borderStyle:"double",borderColor:"green",paddingX:1,children:[c(i,{justifyContent:"space-between",children:[c(o,{bold:!0,color:"green",children:["CHAT \xB7 @",e.agentId]}),a(i,{children:y})]}),c(o,{children:[c(o,{color:u.color,bold:!0,children:[u.glyph," ",u.label]}),s?.model?c(o,{dimColor:!0,children:[" ",j(s.model)]}):null,c(o,{dimColor:!0,children:[" \xB7 ",e.chatId]})]}),a(i,{flexDirection:"column",flexGrow:1,marginTop:1,children:n.length===0?a(o,{dimColor:!0,children:"(type a message and press Enter \u2014 Esc exits, Ctrl-L starts fresh)"}):n.map((f,l)=>a(se,{turn:f,agentId:e.agentId},`${f.at}-${l}`))}),r?c(o,{dimColor:!0,children:[a(o,{color:"cyan",children:"\u21B9"})," ",r.items.slice(0,5).map((f,l)=>l===0?a(o,{color:"cyan",children:f},l):c(o,{children:[" ",f]},l))]}):null,c(i,{children:[a(o,{color:"cyan",children:"you \u203A "}),a(o,{children:e.text}),a(o,{inverse:!0,children:" "})]})]})}function se({turn:e,agentId:r}){if(e.role==="you")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"cyan",children:"you"}),e.text.split(`
|
|
5
5
|
`).map((s,u)=>c(o,{children:[" ",s]},u))]});if(e.role==="error")return c(i,{flexDirection:"column",marginBottom:1,children:[a(o,{color:"red",children:"error"}),c(o,{color:"red",children:[" ",e.text]})]});let t=e.elapsedMs!=null?` \xB7 ${(e.elapsedMs/1e3).toFixed(1)}s${e.outTokens!=null?`, ${e.outTokens} tok`:""}`:e.streaming?" \xB7 \u2026":"",n=F(e.text,58,{balance:e.streaming});return c(i,{flexDirection:"column",marginBottom:1,children:[c(o,{color:"green",children:["@",r??"agent",a(o,{dimColor:!0,children:t})]}),e.thinking?e.thinking.split(`
|
|
6
6
|
`).filter(s=>s.trim()).slice(-2).map((s,u)=>c(o,{dimColor:!0,italic:!0,children:[" ",s]},`th${u}`)):null,(e.tools??[]).map((s,u)=>c(o,{color:s.error?"red":"green",children:[" \u25CF ",c(o,{bold:!s.error,dimColor:s.error,children:[s.name,s.arg?c(o,{dimColor:!0,children:["(",s.arg,")"]}):null,s.error?" failed":""]})]},`tool${u}`)),n.split(`
|
|
7
7
|
`).slice(0,14).map((s,u)=>c(o,{children:[" ",s]},u))]})}function ae(e){let r=e.data??{},t=typeof r.at=="string"||typeof r.at=="number"?new Date(r.at).getTime():Date.now(),n="";switch(e.event){case"run":n=`${r.phase??""} ${r.workflowId??""} ${(r.runId??"").slice(0,8)} ${r.nodeId??""}`.trim();break;case"task":n=`${r.phase??""} ${r.workflowId??""} ${Array.isArray(r.assignedTo)?r.assignedTo.join(","):""} ${r.title?`"${r.title}"`:""}`.trim();break;case"signal":n=`${r.name??""} (${r.scope??""}) ${r.workflowId??""}`.trim();break;case"mesh":n=`${r.peer??""} ${r.healthy?"healthy":"lost"} ${r.delta??""}`.trim();break;case"channel":{n=`${r.direction==="in"?"\u2190":"\u2192"} ${r.channel??""}:${r.chatId??""} ${r.textPreview??""}`.trim();break}case"status":n=`node=${r.node??""} agents=${r.agents??0} active=${(r.active??[]).length}`;break;default:n=typeof r=="string"?r:JSON.stringify(r).slice(0,80)}return{at:Number.isFinite(t)?t:Date.now(),kind:e.event,line:n}}function ce(e){switch(e){case"run":return"cyan";case"task":return"magenta";case"signal":return"green";case"mesh":return"blue";case"channel":return"yellow";default:return"white"}}function T(e,r){let t=(e??"").toString();return t.length>=r?t.slice(0,r-1)+" ":t+" ".repeat(r-t.length)}export{be as App};
|
|
8
|
-
//# sourceMappingURL=App-
|
|
8
|
+
//# sourceMappingURL=App-4EM7IPB3.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-WOMQ2VGA.js";import"./chunk-U4H35SJS.js";import"./chunk-QIJLFPWE.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
|
|
2
2
|
`):[""],B=A.find(s=>s.id===T),D=ne(B?.tier??"",B?.model),pe=O?.rows||24,fe=z.slice(-40);return o(g,{flexDirection:"column",height:pe,children:[o(g,{flexDirection:"column",flexGrow:1,justifyContent:"flex-end",overflow:"hidden",children:[z.length===0&&!$?o(g,{flexDirection:"column",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),c(t,{dimColor:!0,children:" \u2014 chat with "}),o(t,{color:m.accent,children:["@",T]}),c(t,{dimColor:!0,children:" via "}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" (",B.model,")"]}):null]}),c(t,{dimColor:!0,children:"streaming \xB7 markdown \xB7 tools \xB7 type a message, or / for commands"})]}):null,fe.map(s=>c(ce,{turn:s,width:V},s.id)),$?c(ce,{turn:$,width:V}):null]}),N?c(g,{flexDirection:"column",marginTop:1,children:N.map((s,e)=>o(g,{children:[c(t,{color:e===0?m.accent:m.muted,bold:e===0,children:s.cmd.padEnd(10)}),c(t,{dimColor:!0,children:s.desc})]},s.cmd))}):j?o(g,{marginTop:1,children:[c(t,{color:m.accent,children:"\u21B9 "}),j.items.map((s,e)=>o(t,{color:e===0?m.accent:m.muted,children:[s,e<j.items.length-1?" ":""]},e))]}):J?c(g,{marginTop:1,children:c(t,{dimColor:!0,children:J})}):null,c(g,{borderStyle:"single",borderColor:G,borderLeft:!1,borderRight:!1,borderDimColor:!v,width:"100%",children:c(g,{flexDirection:"column",width:"100%",children:ge.map((s,e,r)=>o(g,{children:[c(t,{color:G,bold:!0,children:e===0?"\u203A ":" "}),c(t,{children:s}),e===r.length-1&&!v?c(t,{color:G,children:"\u258F"}):null]},e))})}),o(g,{justifyContent:"space-between",children:[o(t,{children:[c(t,{color:m.accent,bold:!0,children:q}),o(t,{dimColor:!0,children:[" \xB7 @",T," \xB7 "]}),o(t,{color:D.color,bold:!0,children:[D.glyph," ",D.label]}),B?.model?o(t,{dimColor:!0,children:[" ",re(B.model)]}):null]}),o(t,{dimColor:!0,children:["\u21B5 send / cmds @ mention esc ",v?"stop":"exit"]})]})]})}function ce({turn:n,width:p}){let f=H(()=>ye(n,p),[n,p]),a=n.segs.some(h=>h.type==="text"&&h.content);return o(g,{flexDirection:"column",marginBottom:1,children:[o(t,{color:m.accent,bold:!0,children:[F," you"]}),n.you.split(`
|
|
3
3
|
`).map((h,w)=>o(t,{color:"white",children:[" ",h]},`u${w}`)),o(t,{color:m.agent,bold:!0,children:[F," ",n.agentId??"agent"]}),n.thinking?c(g,{flexDirection:"column",children:we(n.thinking,n.live?3:2).map((h,w)=>o(t,{dimColor:!0,italic:!0,children:[" ",h]},`th${w}`))}):null,f,n.live?c(te,{startedAt:n.startedAt,phase:a?"responding":"thinking",hint:"esc to interrupt"}):n.interrupted?o(t,{dimColor:!0,children:[" \u2298 interrupted \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s"]}):n.error?o(t,{color:"red",children:[" \u2717 ",n.error]}):n.elapsedMs!=null?o(t,{dimColor:!0,children:[" \xB7 ",(n.elapsedMs/1e3).toFixed(1),"s",n.outTokens!=null?`, ${n.outTokens} tok`:""]}):null]})}function we(n,p){return n.split(`
|
|
4
4
|
`).filter(a=>a.trim()).slice(-p)}function ye(n,p){let f=[];return n.segs.forEach((a,h)=>{a.type==="tool"?f.push(o(t,{color:a.error?"red":"green",children:[" ","\u25CF ",o(t,{bold:!a.error,dimColor:a.error,children:[a.name,a.arg?o(t,{dimColor:!0,children:["(",a.arg,")"]}):null,a.error?" failed":""]})]},h)):a.content&&f.push(c(t,{children:Ce(Y(a.content,p,{balance:n.live}))},h))}),f}function Ce(n){return n.split(`
|
|
5
5
|
`).map(p=>p.length?" "+p:p).join(`
|
|
6
6
|
`)}export{Pe as ChatApp};
|
|
7
|
-
//# sourceMappingURL=ChatApp-
|
|
7
|
+
//# sourceMappingURL=ChatApp-FYTEEGSU.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-6AK6WKE5.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-SZJMS7K4.js";import"./chunk-7I7IEX4S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-QIJLFPWE.js";export{a as createAgentContext,b as generate,c as generateStream};
|
|
2
|
+
//# sourceMappingURL=agent-RZ46ZB4W.js.map
|