agentix-cli 0.35.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/mac-helper/Sources/AgentXHelper/Presence.swift +117 -89
- package/apps/mac-helper/Sources/AgentXHelper/PresenceTag.swift +147 -0
- package/apps/mac-helper/Sources/AgentXHelper/main.swift +3 -1
- package/apps/mac-voice/Sources/AgentXVoice/AgentClient.swift +33 -25
- package/apps/mac-voice/Sources/AgentXVoice/Config.swift +4 -0
- package/apps/mac-voice/Sources/AgentXVoice/Progress.swift +4 -4
- package/apps/mac-voice/Sources/AgentXVoice/Speech.swift +81 -13
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +114 -68
- package/dist/{App-K5J52F7O.js → App-TW5N7JY2.js} +2 -2
- package/dist/{ChatApp-LRRAPZH7.js → ChatApp-MRGX7JBT.js} +2 -2
- package/dist/{agent-XEXAYH5J.js → agent-FQHWBQP2.js} +2 -2
- package/dist/{board-dashboard-CBOESBMD.js → board-dashboard-2YEYVCUS.js} +4 -4
- package/dist/{chunk-PBVEYPZB.js → chunk-3SKFX6G3.js} +2 -2
- package/dist/{chunk-DXXWYYZI.js → chunk-4ZGX4L6Q.js} +196 -185
- package/dist/chunk-4ZGX4L6Q.js.map +1 -0
- package/dist/{chunk-WRBW4A2U.js → chunk-ADRAW342.js} +2 -2
- package/dist/{chunk-3PL3WLIR.js → chunk-AKIYB3IJ.js} +2 -2
- package/dist/{chunk-JSZRGMSA.js → chunk-EJWIRM52.js} +2 -2
- package/dist/{chunk-ZSNTLADV.js → chunk-FHF4RA4Q.js} +2 -2
- package/dist/{chunk-SYJUZ3F2.js → chunk-GL7VC26K.js} +2 -2
- package/dist/{chunk-3TNK7VGH.js → chunk-HEV3AVFJ.js} +2 -2
- package/dist/{chunk-FLNN3KSX.js → chunk-IEE475O2.js} +2 -2
- package/dist/{chunk-WEN2P4RS.js → chunk-JLEADOCC.js} +2 -2
- package/dist/{chunk-GYBYKREN.js → chunk-MC6I6ZLY.js} +2 -2
- package/dist/{chunk-4QNZI4WE.js → chunk-N6APC3DF.js} +5 -5
- package/dist/{chunk-LLKILWWO.js → chunk-RRLIZLBH.js} +2 -2
- package/dist/{chunk-LQFEH3SA.js → chunk-SR6TVIIS.js} +2 -2
- package/dist/{chunk-HPY6OOFO.js → chunk-UORMWVLP.js} +150 -142
- package/dist/chunk-UORMWVLP.js.map +1 -0
- package/dist/chunk-WFKS4O34.js +13 -0
- package/dist/chunk-WFKS4O34.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/client-4KD2UA75.js +2 -0
- package/dist/config-WN3R3BPU.js +2 -0
- package/dist/config-mutator-2FIUL4DA.js +2 -0
- package/dist/doctor-BNO46EOR.js +2 -0
- package/dist/index.d.ts +129 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/program-Z6EK44PV.js +2 -0
- package/dist/query-TKUDIOBZ.js +2 -0
- package/dist/{registry-GWN34G4J.js → registry-TDYF7HGH.js} +2 -2
- package/dist/{request-planner-V7T6BQU7.js → request-planner-DF74JHQB.js} +2 -2
- package/dist/{review-candidates-7QKNMRGU.js → review-candidates-SEAGMKI6.js} +2 -2
- package/dist/{routing-BHQXFUSH.js → routing-JKWL3L3Q.js} +2 -2
- package/dist/scheduler-7CYBDKQ7.js +2 -0
- package/dist/{seat-U57NWHKC.js → seat-C6TOB63Q.js} +2 -2
- package/dist/{select-JL3JJJP4.js → select-YB5KF27O.js} +2 -2
- package/dist/wiki-7FCINSKJ.js +2 -0
- package/package.json +1 -1
- package/dist/chunk-DXXWYYZI.js.map +0 -1
- package/dist/chunk-HPY6OOFO.js.map +0 -1
- package/dist/chunk-LA6AKS4N.js +0 -13
- package/dist/chunk-LA6AKS4N.js.map +0 -1
- package/dist/client-APAWETTA.js +0 -2
- package/dist/config-S3VHHVYW.js +0 -2
- package/dist/config-mutator-5B2YWZMK.js +0 -2
- package/dist/doctor-SCECRUJ7.js +0 -2
- package/dist/program-XJEQAWN3.js +0 -2
- package/dist/query-EDW72Q4O.js +0 -2
- package/dist/scheduler-LY4RBPB6.js +0 -2
- package/dist/wiki-AQ4K3F7V.js +0 -2
- /package/dist/{App-K5J52F7O.js.map → App-TW5N7JY2.js.map} +0 -0
- /package/dist/{ChatApp-LRRAPZH7.js.map → ChatApp-MRGX7JBT.js.map} +0 -0
- /package/dist/{agent-XEXAYH5J.js.map → agent-FQHWBQP2.js.map} +0 -0
- /package/dist/{board-dashboard-CBOESBMD.js.map → board-dashboard-2YEYVCUS.js.map} +0 -0
- /package/dist/{chunk-PBVEYPZB.js.map → chunk-3SKFX6G3.js.map} +0 -0
- /package/dist/{chunk-WRBW4A2U.js.map → chunk-ADRAW342.js.map} +0 -0
- /package/dist/{chunk-3PL3WLIR.js.map → chunk-AKIYB3IJ.js.map} +0 -0
- /package/dist/{chunk-JSZRGMSA.js.map → chunk-EJWIRM52.js.map} +0 -0
- /package/dist/{chunk-ZSNTLADV.js.map → chunk-FHF4RA4Q.js.map} +0 -0
- /package/dist/{chunk-SYJUZ3F2.js.map → chunk-GL7VC26K.js.map} +0 -0
- /package/dist/{chunk-3TNK7VGH.js.map → chunk-HEV3AVFJ.js.map} +0 -0
- /package/dist/{chunk-FLNN3KSX.js.map → chunk-IEE475O2.js.map} +0 -0
- /package/dist/{chunk-WEN2P4RS.js.map → chunk-JLEADOCC.js.map} +0 -0
- /package/dist/{chunk-GYBYKREN.js.map → chunk-MC6I6ZLY.js.map} +0 -0
- /package/dist/{chunk-4QNZI4WE.js.map → chunk-N6APC3DF.js.map} +0 -0
- /package/dist/{chunk-LLKILWWO.js.map → chunk-RRLIZLBH.js.map} +0 -0
- /package/dist/{chunk-LQFEH3SA.js.map → chunk-SR6TVIIS.js.map} +0 -0
- /package/dist/{client-APAWETTA.js.map → client-4KD2UA75.js.map} +0 -0
- /package/dist/{config-S3VHHVYW.js.map → config-WN3R3BPU.js.map} +0 -0
- /package/dist/{config-mutator-5B2YWZMK.js.map → config-mutator-2FIUL4DA.js.map} +0 -0
- /package/dist/{doctor-SCECRUJ7.js.map → doctor-BNO46EOR.js.map} +0 -0
- /package/dist/{program-XJEQAWN3.js.map → program-Z6EK44PV.js.map} +0 -0
- /package/dist/{query-EDW72Q4O.js.map → query-TKUDIOBZ.js.map} +0 -0
- /package/dist/{registry-GWN34G4J.js.map → registry-TDYF7HGH.js.map} +0 -0
- /package/dist/{request-planner-V7T6BQU7.js.map → request-planner-DF74JHQB.js.map} +0 -0
- /package/dist/{review-candidates-7QKNMRGU.js.map → review-candidates-SEAGMKI6.js.map} +0 -0
- /package/dist/{routing-BHQXFUSH.js.map → routing-JKWL3L3Q.js.map} +0 -0
- /package/dist/{scheduler-LY4RBPB6.js.map → scheduler-7CYBDKQ7.js.map} +0 -0
- /package/dist/{seat-U57NWHKC.js.map → seat-C6TOB63Q.js.map} +0 -0
- /package/dist/{select-JL3JJJP4.js.map → select-YB5KF27O.js.map} +0 -0
- /package/dist/{wiki-AQ4K3F7V.js.map → wiki-7FCINSKJ.js.map} +0 -0
|
@@ -1,13 +1,38 @@
|
|
|
1
1
|
import AVFoundation
|
|
2
2
|
import Foundation
|
|
3
3
|
|
|
4
|
-
///
|
|
4
|
+
/// How the daemon says an agent should sound (the `voice` of /ask and of
|
|
5
|
+
/// step events). Missing fields mean the free system voice.
|
|
6
|
+
struct VoiceChoice: Decodable {
|
|
7
|
+
var provider: String?
|
|
8
|
+
var elevenlabsVoiceId: String?
|
|
9
|
+
/// A macOS voice identifier for `say -v`; nil for the OS default.
|
|
10
|
+
var systemVoice: String?
|
|
11
|
+
/// When ElevenLabs cannot speak, use the system voice instead.
|
|
12
|
+
var fallback: Bool?
|
|
13
|
+
|
|
14
|
+
init(provider: String? = nil, elevenlabsVoiceId: String? = nil, systemVoice: String? = nil, fallback: Bool? = nil) {
|
|
15
|
+
self.provider = provider; self.elevenlabsVoiceId = elevenlabsVoiceId
|
|
16
|
+
self.systemVoice = systemVoice; self.fallback = fallback
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// From a step event, which arrives as untyped JSON.
|
|
20
|
+
init?(json: Any?) {
|
|
21
|
+
guard let o = json as? [String: Any] else { return nil }
|
|
22
|
+
self.init(provider: o["provider"] as? String, elevenlabsVoiceId: o["elevenlabsVoiceId"] as? String,
|
|
23
|
+
systemVoice: o["systemVoice"] as? String, fallback: o["fallback"] as? Bool)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Speech in and out. Speech out uses the free macOS voices unless the
|
|
28
|
+
/// daemon says an agent speaks through ElevenLabs; speech in uses
|
|
29
|
+
/// ElevenLabs when there is a key, with a local fallback.
|
|
5
30
|
///
|
|
6
31
|
/// The fallbacks are not decoration. This widget is push-to-talk on a
|
|
7
32
|
/// laptop: it will be used on hotel wifi, on a plane, and on the day the
|
|
8
33
|
/// key expires. A voice assistant that answers "network error" out loud is
|
|
9
34
|
/// worse than one that answers in a robot voice, so STT degrades to
|
|
10
|
-
/// on-device mlx-whisper and TTS degrades to `say`.
|
|
35
|
+
/// on-device mlx-whisper and ElevenLabs TTS degrades to `say`.
|
|
11
36
|
enum Speech {
|
|
12
37
|
// MARK: Speech to text
|
|
13
38
|
|
|
@@ -72,19 +97,28 @@ enum Speech {
|
|
|
72
97
|
|
|
73
98
|
// MARK: Text to speech
|
|
74
99
|
|
|
75
|
-
///
|
|
76
|
-
static func
|
|
100
|
+
/// Silence whatever this app is saying (an answer or a step line).
|
|
101
|
+
static func stop() { Player.shared.stop() }
|
|
102
|
+
|
|
103
|
+
/// `voice` is the answering agent's, as the daemon resolved it; nil
|
|
104
|
+
/// (no answer yet) means the provider this app is configured with.
|
|
105
|
+
static func speak(_ text: String, voice: VoiceChoice? = nil) async {
|
|
77
106
|
guard !text.isEmpty else { return }
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
107
|
+
if (voice?.provider ?? Config.voiceProvider) == "elevenlabs" {
|
|
108
|
+
if let key = Config.elevenLabsKey {
|
|
109
|
+
do {
|
|
110
|
+
let mp3 = try await elevenLabsTTS(text: text, voiceID: voice?.elevenlabsVoiceId ?? Config.voiceID, key: key)
|
|
111
|
+
try await Player.shared.play(mp3)
|
|
112
|
+
return
|
|
113
|
+
} catch {
|
|
114
|
+
Log.warn("ElevenLabs TTS failed (\(error.localizedDescription))")
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
Log.warn("ElevenLabs is the voice provider but there is no key")
|
|
85
118
|
}
|
|
119
|
+
guard voice?.fallback ?? true else { return }
|
|
86
120
|
}
|
|
87
|
-
|
|
121
|
+
await Player.shared.say(text, voice: voice?.systemVoice)
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
private static func elevenLabsTTS(text: String, voiceID: String, key: String) async throws -> Data {
|
|
@@ -135,11 +169,38 @@ enum VoiceError: LocalizedError {
|
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
/// Holds a strong reference to the player for the life of playback —
|
|
138
|
-
/// an AVAudioPlayer that goes out of scope stops mid-sentence
|
|
172
|
+
/// an AVAudioPlayer that goes out of scope stops mid-sentence — and to the
|
|
173
|
+
/// `say` process, so `stop()` silences either.
|
|
139
174
|
final class Player: NSObject, AVAudioPlayerDelegate {
|
|
140
175
|
static let shared = Player()
|
|
141
176
|
private var player: AVAudioPlayer?
|
|
142
177
|
private var finished: CheckedContinuation<Void, Never>?
|
|
178
|
+
private var sayProcess: Process?
|
|
179
|
+
|
|
180
|
+
/// Speak with a system voice; returns when done or stopped. The text
|
|
181
|
+
/// goes in on stdin, so a line that starts with "-" is not an option.
|
|
182
|
+
func say(_ text: String, voice: String?) async {
|
|
183
|
+
let p = Process()
|
|
184
|
+
p.executableURL = URL(fileURLWithPath: "/usr/bin/say")
|
|
185
|
+
p.arguments = voice.map { ["-v", $0] } ?? []
|
|
186
|
+
let input = Pipe()
|
|
187
|
+
p.standardInput = input
|
|
188
|
+
p.standardOutput = FileHandle.nullDevice
|
|
189
|
+
p.standardError = FileHandle.nullDevice
|
|
190
|
+
await withCheckedContinuation { (c: CheckedContinuation<Void, Never>) in
|
|
191
|
+
p.terminationHandler = { _ in c.resume() }
|
|
192
|
+
do { try p.run() } catch {
|
|
193
|
+
p.terminationHandler = nil
|
|
194
|
+
Log.warn("say failed to start (\(error.localizedDescription))")
|
|
195
|
+
c.resume()
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
sayProcess = p
|
|
199
|
+
input.fileHandleForWriting.write(Data(text.utf8))
|
|
200
|
+
try? input.fileHandleForWriting.close()
|
|
201
|
+
}
|
|
202
|
+
if sayProcess === p { sayProcess = nil }
|
|
203
|
+
}
|
|
143
204
|
|
|
144
205
|
func play(_ mp3: Data) async throws {
|
|
145
206
|
let p = try AVAudioPlayer(data: mp3)
|
|
@@ -154,6 +215,13 @@ final class Player: NSObject, AVAudioPlayerDelegate {
|
|
|
154
215
|
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
|
155
216
|
finished?.resume(); finished = nil; self.player = nil
|
|
156
217
|
}
|
|
218
|
+
|
|
219
|
+
/// Cut the line off now; the `play` awaiting it returns.
|
|
220
|
+
func stop() {
|
|
221
|
+
sayProcess?.terminate(); sayProcess = nil
|
|
222
|
+
player?.stop()
|
|
223
|
+
finished?.resume(); finished = nil; player = nil
|
|
224
|
+
}
|
|
157
225
|
}
|
|
158
226
|
|
|
159
227
|
/// Logs to stderr AND to a file.
|
|
@@ -30,10 +30,15 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
30
30
|
private var lastSpokeAt = Date.distantPast
|
|
31
31
|
/// The answering agent's voice, learned from the daemon. Nil until
|
|
32
32
|
/// then, which speaks in the global default.
|
|
33
|
-
private var
|
|
34
|
-
/// Set
|
|
35
|
-
///
|
|
36
|
-
private var talkCheck: Task<
|
|
33
|
+
private var voice: VoiceChoice?
|
|
34
|
+
/// Set when the door opens: what was speaking (and is now hushed), so
|
|
35
|
+
/// the words spoken go to that activity rather than to /ask.
|
|
36
|
+
private var talkCheck: Task<AgentClient.Hushed, Never>?
|
|
37
|
+
/// Words said while our own turn was still running: they go next, and
|
|
38
|
+
/// the stale answer is not spoken.
|
|
39
|
+
private var followUp: String?
|
|
40
|
+
/// "stop" while our own turn was running: drop its answer.
|
|
41
|
+
private var abandoned = false
|
|
37
42
|
|
|
38
43
|
/// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
|
|
39
44
|
private func smartPaste() {
|
|
@@ -226,12 +231,16 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
226
231
|
// A held key overrides any hands-free window that is open, so the
|
|
227
232
|
// two ways of talking never fight over the microphone.
|
|
228
233
|
stopPolling()
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
// is
|
|
234
|
-
|
|
234
|
+
guard !recorder.isRecording else { return }
|
|
235
|
+
// Option-Space is the door to everything spoken, always: our own
|
|
236
|
+
// answer or step line stops now, and the daemon hushes any talk,
|
|
237
|
+
// lesson or narration, remembering which it was. Even mid-turn —
|
|
238
|
+
// ignoring the key while busy is how Anis spoke to a lesson and
|
|
239
|
+
// nothing listened.
|
|
240
|
+
Speech.stop()
|
|
241
|
+
lastSpokeAt = Date()
|
|
242
|
+
talkCheck = Task { await AgentClient.hush() }
|
|
243
|
+
Log.info("door: opened\(busy ? " (a turn is running)" : "")")
|
|
235
244
|
do {
|
|
236
245
|
try recorder.start()
|
|
237
246
|
panel.render(.listening)
|
|
@@ -242,74 +251,111 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
242
251
|
|
|
243
252
|
private func stopAndSend() {
|
|
244
253
|
guard recorder.isRecording else { return }
|
|
245
|
-
|
|
254
|
+
// Clicked or hands-free: nothing hushed yet. They did speak, so
|
|
255
|
+
// hush now; whatever was talking gets the words, same as the key.
|
|
256
|
+
let door = talkCheck ?? Task { await AgentClient.hush() }
|
|
246
257
|
talkCheck = nil
|
|
247
258
|
guard let wav = recorder.stop() else {
|
|
248
259
|
panel.render(.error("Too short — hold while speaking"))
|
|
249
260
|
resetSoon()
|
|
250
261
|
return
|
|
251
262
|
}
|
|
263
|
+
let midTurn = busy
|
|
252
264
|
busy = true
|
|
253
|
-
panel.render(.thinking)
|
|
265
|
+
if !midTurn { panel.render(.thinking) }
|
|
254
266
|
|
|
255
267
|
Task { @MainActor in
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
panel.render(.idle)
|
|
269
|
-
busy = false
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
beginNarration()
|
|
273
|
-
|
|
274
|
-
let answer = try await AgentClient.ask(heard)
|
|
275
|
-
endNarration()
|
|
276
|
-
if let v = answer.voiceID { voiceID = v }
|
|
277
|
-
Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
278
|
-
// Show BEFORE speaking, but only when there is something
|
|
279
|
-
// the speech cannot deliver — a link, an image, or more
|
|
280
|
-
// text than was read aloud. A card that opens on every
|
|
281
|
-
// "Ok." teaches you to ignore it.
|
|
282
|
-
if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
|
|
283
|
-
buttons: answer.buttons, imageURL: answer.imageURL) {
|
|
284
|
-
card.show(spoken: answer.text, written: answer.written,
|
|
285
|
-
buttons: answer.buttons, imageURL: answer.imageURL)
|
|
286
|
-
} else {
|
|
287
|
-
card.orderOut(nil)
|
|
288
|
-
}
|
|
289
|
-
// Scroll the sentence being spoken, so it can be read as
|
|
290
|
-
// well as heard — and re-read after, which speech cannot do.
|
|
291
|
-
panel.render(.saying(answer.text))
|
|
292
|
-
await Speech.speak(answer.text, voiceID: voiceID)
|
|
293
|
-
panel.render(.idle)
|
|
294
|
-
// Leave the microphone open for a moment. Say nothing and
|
|
295
|
-
// it closes itself; start talking and the conversation
|
|
296
|
-
// simply continues.
|
|
297
|
-
busy = false
|
|
298
|
-
// A live lesson now runs on screen and speaks for itself; an
|
|
299
|
-
// open mic would hear the agent. Option-Space is the door.
|
|
300
|
-
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
|
|
301
|
-
listenHandsFree(followUp: true)
|
|
302
|
-
} catch {
|
|
303
|
-
endNarration()
|
|
304
|
-
Log.warn("turn failed: \(error.localizedDescription)")
|
|
305
|
-
panel.render(.error(short(error.localizedDescription)))
|
|
306
|
-
// Say it aloud too — a voice assistant that fails only in
|
|
307
|
-
// a 230px label has failed silently for anyone not looking.
|
|
308
|
-
await Speech.speak("Sorry, that didn't work.", voiceID: voiceID)
|
|
309
|
-
resetSoon()
|
|
268
|
+
let heard = (try? await Speech.transcribe(wav: wav)) ?? ""
|
|
269
|
+
guard !heard.isEmpty else {
|
|
270
|
+
if !midTurn { panel.render(.error("Didn't catch that")); busy = false; resetSoon() }
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
Log.info("heard: \(heard)")
|
|
274
|
+
let hushed = await door.value
|
|
275
|
+
if hushed.kind != nil, await AgentClient.door(heard) {
|
|
276
|
+
// The talk or lesson answers out loud through the daemon.
|
|
277
|
+
Log.info("door: \"\(heard)\" → \(hushed.kind!)\(hushed.agentID.map { " (\($0))" } ?? "")")
|
|
278
|
+
if !midTurn { panel.render(.idle); busy = false }
|
|
279
|
+
return
|
|
310
280
|
}
|
|
281
|
+
if midTurn {
|
|
282
|
+
// Our own turn is still thinking. The agent cannot change
|
|
283
|
+
// course mid-turn, so his words go next and the stale
|
|
284
|
+
// answer is not spoken; "stop" just drops it.
|
|
285
|
+
if Self.isStop(heard) { abandoned = true; followUp = nil; Log.info("door: stop → dropping the turn in flight") }
|
|
286
|
+
else { followUp = heard; Log.info("door: \"\(heard)\" → next, instead of the answer in flight") }
|
|
287
|
+
panel.render(.working("Got it — one moment", 0))
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
await runTurn(heard)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static func isStop(_ s: String) -> Bool {
|
|
295
|
+
s.range(of: #"^\s*(stop|stop talking|that'?s enough|enough)[\s.!]*$"#,
|
|
296
|
+
options: [.regularExpression, .caseInsensitive]) != nil
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// One question and its spoken answer. Words said through the door
|
|
300
|
+
/// while it thinks replace the answer with the next turn.
|
|
301
|
+
private func runTurn(_ heard: String) async {
|
|
302
|
+
do {
|
|
303
|
+
beginNarration()
|
|
304
|
+
|
|
305
|
+
let answer = try await AgentClient.ask(heard)
|
|
306
|
+
endNarration()
|
|
307
|
+
if abandoned {
|
|
308
|
+
abandoned = false
|
|
309
|
+
Log.info("dropped answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
310
|
+
panel.render(.idle); busy = false; return
|
|
311
|
+
}
|
|
312
|
+
if let next = followUp {
|
|
313
|
+
followUp = nil
|
|
314
|
+
Log.info("superseded answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
315
|
+
panel.render(.thinking)
|
|
316
|
+
return await runTurn(next)
|
|
317
|
+
}
|
|
318
|
+
if let v = answer.voice { voice = v }
|
|
319
|
+
Log.info("answer (\(answer.agentID ?? "?")): \(answer.text)")
|
|
320
|
+
// Show BEFORE speaking, but only when there is something
|
|
321
|
+
// the speech cannot deliver — a link, an image, or more
|
|
322
|
+
// text than was read aloud. A card that opens on every
|
|
323
|
+
// "Ok." teaches you to ignore it.
|
|
324
|
+
if ResultCard.isWorthShowing(spoken: answer.text, written: answer.written,
|
|
325
|
+
buttons: answer.buttons, imageURL: answer.imageURL) {
|
|
326
|
+
card.show(spoken: answer.text, written: answer.written,
|
|
327
|
+
buttons: answer.buttons, imageURL: answer.imageURL)
|
|
328
|
+
} else {
|
|
329
|
+
card.orderOut(nil)
|
|
330
|
+
}
|
|
331
|
+
// Scroll the sentence being spoken, so it can be read as
|
|
332
|
+
// well as heard — and re-read after, which speech cannot do.
|
|
333
|
+
panel.render(.saying(answer.text))
|
|
334
|
+
await Speech.speak(answer.text, voice: voice)
|
|
335
|
+
// Cut off by the door: the new words are being recorded.
|
|
336
|
+
if recorder.isRecording { busy = false; return }
|
|
337
|
+
if let next = followUp { followUp = nil; return await runTurn(next) }
|
|
338
|
+
panel.render(.idle)
|
|
339
|
+
// Leave the microphone open for a moment. Say nothing and
|
|
340
|
+
// it closes itself; start talking and the conversation
|
|
341
|
+
// simply continues.
|
|
311
342
|
busy = false
|
|
343
|
+
// A live lesson now runs on screen and speaks for itself; an
|
|
344
|
+
// open mic would hear the agent. Option-Space is the door.
|
|
345
|
+
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
|
|
346
|
+
listenHandsFree(followUp: true)
|
|
347
|
+
} catch {
|
|
348
|
+
endNarration()
|
|
349
|
+
Log.warn("turn failed: \(error.localizedDescription)")
|
|
350
|
+
if abandoned { abandoned = false; panel.render(.idle); busy = false; return }
|
|
351
|
+
if let next = followUp { followUp = nil; return await runTurn(next) }
|
|
352
|
+
panel.render(.error(short(error.localizedDescription)))
|
|
353
|
+
// Say it aloud too — a voice assistant that fails only in
|
|
354
|
+
// a 230px label has failed silently for anyone not looking.
|
|
355
|
+
await Speech.speak("Sorry, that didn't work.", voice: voice)
|
|
356
|
+
resetSoon()
|
|
312
357
|
}
|
|
358
|
+
busy = false
|
|
313
359
|
}
|
|
314
360
|
|
|
315
361
|
/// Narrate the wait using what the daemon says it is actually doing.
|
|
@@ -330,7 +376,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
330
376
|
progress = Progress(agentID: Config.agentID) { [weak self] step, voice in
|
|
331
377
|
Task { @MainActor in
|
|
332
378
|
guard let self, self.busy else { return }
|
|
333
|
-
if let voice { self.
|
|
379
|
+
if let voice { self.voice = voice }
|
|
334
380
|
self.lastStep = step
|
|
335
381
|
Log.info("step: \(step)")
|
|
336
382
|
self.panel.render(.working(step, Int(Date().timeIntervalSince(self.startedAt))))
|
|
@@ -381,7 +427,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
381
427
|
tool: tool, detail: detail, elapsed: Int(elapsed)) else { return }
|
|
382
428
|
guard !self.spokenSteps.contains(phrase) else { return }
|
|
383
429
|
self.spokenSteps.insert(phrase)
|
|
384
|
-
await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".",
|
|
430
|
+
await Speech.speak(phrase.prefix(1).capitalized + phrase.dropFirst() + ".", voice: self.voice)
|
|
385
431
|
}
|
|
386
432
|
}
|
|
387
433
|
|
|
@@ -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-FHF4RA4Q.js";import"./chunk-WFKS4O34.js";import"./chunk-FIOB3MEX.js";import{useEffect as R,useReducer as V,useRef as G}from"react";import{Box as i,Text as o,useApp as W,useInput as J}from"ink";import{randomUUID as z}from"crypto";async function*q(e){let r=[];e.type&&r.push(`type=${encodeURIComponent(e.type)}`);let t=`${e.baseUrl.replace(/\/+$/,"")}/events${r.length?`?${r.join("&")}`:""}`,n={Accept:"text/event-stream"};e.token&&(n.Authorization=`Bearer ${e.token}`);let s=await fetch(t,{headers:n,signal:e.signal});if(!s.ok||!s.body)throw new Error(`SSE connect failed: HTTP ${s.status}`);let u=s.body.getReader(),p=new TextDecoder,y="";try{for(;;){let{done:f,value:l}=await u.read();if(f)return;y+=p.decode(l,{stream:!0});let d;for(;(d=y.indexOf(`
|
|
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-TW5N7JY2.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-FHF4RA4Q.js";import"./chunk-WFKS4O34.js";import"./chunk-FIOB3MEX.js";import{useCallback as oe,useMemo as H,useRef as P,useState as k}from"react";import{Box as g,Text as t,useApp as he,useInput as xe,useStdout as Te}from"ink";import{randomUUID as se}from"crypto";var m={accent:"cyan",agent:"green",working:"magenta",error:"red",muted:"gray"},q="\u25C7 agentx",F="\u258C";import{jsx as c,jsxs as o}from"react/jsx-runtime";var ie="/help /agent <id> /agents /clear /who /exit",be=[{cmd:"/agent",desc:"switch the target agent (/agent <id>)"},{cmd:"/agents",desc:"list registered agents"},{cmd:"/clear",desc:"start a fresh session"},{cmd:"/who",desc:"show agent + chat id"},{cmd:"/help",desc:"list commands"},{cmd:"/exit",desc:"quit the chat"}];function Pe({conn:n,agentId:p,channel:f,chatId:a,agents:h}){let{exit:w}=he(),{stdout:O}=Te(),V=Math.max(40,Math.min((O?.columns||80)-4,100)),[z,U]=k([]),[$,S]=k(null),[T,le]=k(p),[A,ae]=k(h),[E,ue]=k(a),[y,x]=k(""),[J,de]=k(""),W=P([]),u=P(-1),M=P(null),R=P(null),v=$?.live??!1,j=H(()=>Z(y,A.map(s=>s.id),process.cwd()),[y,A]),N=H(()=>{let s=/^\/(\w*)$/.exec(y);if(!s)return null;let e=be.filter(r=>r.cmd.slice(1).startsWith(s[1].toLowerCase()));return e.length?e:null},[y]),b=s=>de(s),K=oe(s=>{let e=se().slice(0,8),r=Date.now(),i=new AbortController;R.current=i;let d={id:e,you:s,agentId:T,segs:[],live:!0,startedAt:r};S({...d});let L=()=>S({...d,segs:d.segs.map(C=>({...C}))});(async()=>{try{let C=await X(n,T,s,{channel:f,chatId:E,signal:i.signal,onText:l=>{let I=d.segs[d.segs.length-1];I&&I.type==="text"?I.content=(I.content??"")+l:d.segs.push({type:"text",content:l}),L()},onThinking:l=>{d.thinking=(d.thinking??"")+l,L()},onTool:l=>{l.status==="start"&&l.name?(d.segs.push({type:"tool",name:l.name,arg:l.arg}),L()):l.status==="result"&&l.error&&(d.segs.push({type:"tool",name:l.name??"tool",error:!0}),L())}});U(l=>[...l,{...d,live:!1,elapsedMs:Date.now()-r,outTokens:C.usage?.outputTokens,error:C.error}]),S(null)}catch(C){let l=i.signal.aborted||C?.name==="AbortError";U(I=>[...I,{...d,live:!1,elapsedMs:Date.now()-r,interrupted:l,error:l?void 0:C?.message||String(C)}]),S(null)}finally{R.current===i&&(R.current=null)}})()},[n,T,f,E]),me=oe(s=>{let e=s.trim();if(e){if(W.current.push(e),u.current=-1,e==="/exit"||e==="/quit"){w();return}if(e==="/help"){b(ie);return}if(e==="/who"){b(`@${T} \xB7 chatId=${E} \xB7 channel=${f}`);return}if(e==="/clear"){let r=`chat-cli:${se().slice(0,12)}`;ue(r),U([]),b(`fresh chatId=${r} (warm process dropped)`);return}if(e==="/agents"){Q(n).then(r=>{ae(r),b(r.map(i=>i.id===T?`[${i.id}]`:i.id).join(" "))}).catch(r=>b(`failed to list agents: ${r?.message||r}`));return}if(e.startsWith("/agent ")){let r=e.slice(7).replace(/^@/,"").trim();if(!r){b("usage: /agent <id>");return}if(!A.find(i=>i.id===r)){b(`unknown agent: @${r}`);return}le(r),b(`now talking to @${r}`);return}if(e.startsWith("/")){b(`unknown command \xB7 ${ie}`);return}K(e)}},[n,T,A,f,E,w,K]);xe((s,e)=>{if(e.ctrl&&s==="c"){w();return}if(e.escape){if(v&&R.current){R.current.abort();return}w();return}if(e.tab){if(N){x(N[0].cmd+" ");return}let i=_(y,M,A.map(d=>d.id),process.cwd());i!==null&&(x(i),u.current=-1);return}if(e.upArrow){let i=W.current;if(!i.length)return;u.current=u.current<0?i.length-1:Math.max(0,u.current-1),x(i[u.current]??"");return}if(e.downArrow){let i=W.current;if(u.current<0)return;if(u.current=u.current+1,u.current>=i.length){u.current=-1,x("");return}x(i[u.current]??"");return}if(e.backspace||e.delete){M.current=null,x(i=>i.slice(0,-1));return}if(e.ctrl||e.meta)return;let r=ee(s??"",e.return,y);switch(r.kind){case"paste":M.current=null,x(i=>i+r.text),u.current=-1;return;case"type":M.current=null,x(i=>i+r.text),u.current=-1;return;case"continue":x(r.buffer);return;case"submit":if(v)return;M.current=null,x(""),me(r.text);return;case"none":return}});let G=v?m.working:m.accent,ge=y.length?y.split(`
|
|
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-MRGX7JBT.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-HEV3AVFJ.js";import"./chunk-SFQUP3BP.js";import"./chunk-QOYAX2FT.js";import"./chunk-LCIREYTD.js";import"./chunk-L2WSPY6S.js";import"./chunk-VDMYBPYB.js";import"./chunk-DSYKYZMT.js";import"./chunk-FIOB3MEX.js";export{a as createAgentContext,b as generate,c as generateStream};
|
|
2
|
+
//# sourceMappingURL=agent-FQHWBQP2.js.map
|