agentix-cli 0.92.4 → 0.94.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/Character.swift +161 -27
- package/apps/mac-voice/Sources/AgentXVoice/CharacterSim.swift +14 -3
- package/apps/mac-voice/Sources/AgentXVoice/Config.swift +15 -0
- package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +44 -3
- package/apps/mac-voice/Sources/AgentXVoice/PillPlacement.swift +49 -0
- package/apps/mac-voice/Sources/AgentXVoice/StatusMenu.swift +15 -3
- package/apps/mac-voice/Sources/AgentXVoice/VoiceSettings.swift +6 -0
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +52 -9
- package/dist/{chunk-GWOJJ7FE.js → chunk-VPCW4477.js} +168 -168
- package/dist/{chunk-GWOJJ7FE.js.map → chunk-VPCW4477.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-IAOEFLZP.js → program-BRC2SSDI.js} +4 -4
- package/package.json +1 -1
- /package/dist/{program-IAOEFLZP.js.map → program-BRC2SSDI.js.map} +0 -0
|
@@ -50,6 +50,10 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
50
50
|
/// Set when the door opens: what was speaking (and is now hushed), so
|
|
51
51
|
/// the words spoken go to that activity rather than to /ask.
|
|
52
52
|
private var talkCheck: Task<AgentClient.Hushed, Never>?
|
|
53
|
+
/// A lesson our last turn started may still be speaking: a click then
|
|
54
|
+
/// opens the door at once, like the key, so the microphone does not
|
|
55
|
+
/// take the lesson's voice for the listener's.
|
|
56
|
+
private var lessonOn = false
|
|
53
57
|
/// Waiting for our answer to be spoken by the daemon's queue. The door
|
|
54
58
|
/// lets go of it: the answer stays queued and plays after the turn.
|
|
55
59
|
private var speaking: Task<Void, Never>?
|
|
@@ -204,6 +208,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
204
208
|
let asCharacter = saved.general.look == "character"
|
|
205
209
|
panel.setShowsOrb(!asCharacter)
|
|
206
210
|
character.setShown(asCharacter)
|
|
211
|
+
if !asCharacter { panel.detach() }
|
|
207
212
|
if settingsWindow.model.recording == nil { registerHotkeys() }
|
|
208
213
|
// Colours may have changed.
|
|
209
214
|
statusMenu.refresh()
|
|
@@ -228,15 +233,16 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
228
233
|
NSWorkspace.shared.open(url)
|
|
229
234
|
}
|
|
230
235
|
|
|
231
|
-
/// Close, Esc or "Hide pill": the pill goes
|
|
232
|
-
/// same stop as ⌘⌥. An open microphone closes
|
|
233
|
-
/// next talk key brings
|
|
236
|
+
/// Close, Esc or "Hide pill": the pill goes, the character with it,
|
|
237
|
+
/// and the voice stops, the same stop as ⌘⌥. An open microphone closes
|
|
238
|
+
/// without sending. The next talk key brings them back.
|
|
234
239
|
private func dismissPill() {
|
|
235
240
|
Log.info("pill: dismissed")
|
|
236
241
|
// Closing the pill declines a ringing call and ends one in progress.
|
|
237
242
|
if ringingCall != nil { endRinging { await CallClient.decline($0) } }
|
|
238
243
|
if activeCall != nil { hangUp() }
|
|
239
244
|
panel.dismiss()
|
|
245
|
+
character.setHidden(true)
|
|
240
246
|
if recorder.isRecording {
|
|
241
247
|
stopPolling()
|
|
242
248
|
_ = recorder.stop()
|
|
@@ -246,6 +252,13 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
246
252
|
stopSpeaking()
|
|
247
253
|
}
|
|
248
254
|
|
|
255
|
+
/// The talk key, a call or the menu: a hidden pill may show again,
|
|
256
|
+
/// and a hidden character is back.
|
|
257
|
+
private func summonPill() {
|
|
258
|
+
character.setHidden(false)
|
|
259
|
+
panel.summon()
|
|
260
|
+
}
|
|
261
|
+
|
|
249
262
|
/// ⌘⌥V. Everything except the hotkey lives in `agentx paste`.
|
|
250
263
|
private func smartPaste() {
|
|
251
264
|
guard !busy else { return }
|
|
@@ -314,9 +327,17 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
314
327
|
panel.orb.levelSource = { [weak self] in self?.recorder.level ?? 0 }
|
|
315
328
|
character.setAnimated(Config.animatedOrb)
|
|
316
329
|
character.levelSource = { [weak self] in self?.recorder.level ?? 0 }
|
|
330
|
+
// The pill is the character's speech bubble: it goes where the
|
|
331
|
+
// character goes.
|
|
332
|
+
character.bubble = { [weak self] head, visible in
|
|
333
|
+
guard let panel = self?.panel else { return nil }
|
|
334
|
+
panel.attach(head: head, visible: visible)
|
|
335
|
+
return panel.isVisible ? panel.frame : nil
|
|
336
|
+
}
|
|
317
337
|
panel.onLook = { [weak self] state, tint, colors in
|
|
318
338
|
self?.character.show(state.activity, tint: tint, colors: colors)
|
|
319
339
|
}
|
|
340
|
+
panel.onShown = { [weak self] in self?.character.redraw() }
|
|
320
341
|
panel.onDismiss = { [weak self] in self?.dismissPill() }
|
|
321
342
|
panel.agentPalette = { [weak self] in
|
|
322
343
|
guard let self else { return nil }
|
|
@@ -325,7 +346,13 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
325
346
|
panel.answer.onOpenChat = { [weak self] in self?.openChat() }
|
|
326
347
|
statusMenu.pillVisible = { [weak self] in self?.panel.isVisible ?? false }
|
|
327
348
|
statusMenu.onHidePill = { [weak self] in self?.dismissPill() }
|
|
328
|
-
statusMenu.
|
|
349
|
+
statusMenu.characterVisible = { [weak self] in self?.character.onScreen }
|
|
350
|
+
statusMenu.onShowCharacter = { [weak self] in self?.summonPill() }
|
|
351
|
+
// Each does nothing with the other's look.
|
|
352
|
+
statusMenu.onResetPosition = { [weak self] in
|
|
353
|
+
self?.panel.resetPosition()
|
|
354
|
+
self?.character.resetPosition()
|
|
355
|
+
}
|
|
329
356
|
statusMenu.onAnimatedOrbChanged = { [weak self] on in
|
|
330
357
|
self?.panel.orb.setAnimated(on)
|
|
331
358
|
self?.panel.miniOrbs.setAnimated(on)
|
|
@@ -336,7 +363,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
336
363
|
guard let self else { return }
|
|
337
364
|
self.panel.alwaysVisible = on
|
|
338
365
|
// Asking for the pill brings back one that was dismissed.
|
|
339
|
-
if on { self.
|
|
366
|
+
if on { self.summonPill() }
|
|
340
367
|
if !self.busy && !self.recorder.isRecording { self.panel.render(.idle) }
|
|
341
368
|
}
|
|
342
369
|
statusMenu.onTargetChanged = { [weak self] id in
|
|
@@ -357,8 +384,17 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
357
384
|
guard let self else { return }
|
|
358
385
|
if recording { self.unregisterHotkeys() } else { self.registerHotkeys() }
|
|
359
386
|
}
|
|
387
|
+
// At login the app can start before the daemon answers. Keep
|
|
388
|
+
// asking: one missed read left the orb and the built-in
|
|
389
|
+
// shortcuts until the app was restarted.
|
|
360
390
|
Task { @MainActor in
|
|
361
|
-
|
|
391
|
+
var failures = 0
|
|
392
|
+
while true {
|
|
393
|
+
if let saved = await AgentClient.settings() { apply(saved); break }
|
|
394
|
+
failures += 1
|
|
395
|
+
if failures == 1 { Log.warn("settings: the daemon did not answer, asking again") }
|
|
396
|
+
try? await Task.sleep(for: .seconds(VoiceSettings.retryDelay(after: failures)))
|
|
397
|
+
}
|
|
362
398
|
}
|
|
363
399
|
|
|
364
400
|
// Turning the hold OFF is the moment anything that piled up
|
|
@@ -436,6 +472,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
436
472
|
panel.render(.error(error.localizedDescription))
|
|
437
473
|
return
|
|
438
474
|
}
|
|
475
|
+
if !followUp && lessonOn { talkCheck = Task { await AgentClient.hush() } }
|
|
439
476
|
openedAt = Date()
|
|
440
477
|
patience = followUp ? (activeCall == nil ? followUpPatience : callPatience) : clickPatience
|
|
441
478
|
silentClose = followUp
|
|
@@ -467,6 +504,11 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
467
504
|
if Date().timeIntervalSince(openedAt) >= patience {
|
|
468
505
|
stopPolling()
|
|
469
506
|
_ = recorder.stop()
|
|
507
|
+
// Nothing said after an early hush: let the queue play on.
|
|
508
|
+
if let door = talkCheck {
|
|
509
|
+
talkCheck = nil
|
|
510
|
+
Task { _ = await door.value; await AgentClient.resume() }
|
|
511
|
+
}
|
|
470
512
|
panel.render(silentClose ? rest : .error("Didn't catch that"))
|
|
471
513
|
if !silentClose { resetSoon() }
|
|
472
514
|
}
|
|
@@ -507,7 +549,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
507
549
|
stopPolling()
|
|
508
550
|
guard !recorder.isRecording else { return }
|
|
509
551
|
// A dismissed pill comes back with the talk key.
|
|
510
|
-
|
|
552
|
+
summonPill()
|
|
511
553
|
// Option-Space is the door to everything spoken, always: our own
|
|
512
554
|
// answer or step line stops now, and the daemon hushes any talk,
|
|
513
555
|
// lesson or narration, remembering which it was. Even mid-turn —
|
|
@@ -574,6 +616,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
574
616
|
return
|
|
575
617
|
}
|
|
576
618
|
let hushed = await door.value
|
|
619
|
+
lessonOn = hushed.kind == "lesson"
|
|
577
620
|
// Always through the door, even with nothing hushed: the
|
|
578
621
|
// listener's turn is over, so the daemon's queue plays on.
|
|
579
622
|
if await AgentClient.door(heard) {
|
|
@@ -752,7 +795,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
752
795
|
busy = false
|
|
753
796
|
// A live lesson now runs on screen and speaks for itself; an
|
|
754
797
|
// open mic would hear the agent. Option-Space is the door.
|
|
755
|
-
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { return }
|
|
798
|
+
if ["teach", "watch", "act"].contains(answer.presenceMode ?? "") { lessonOn = true; return }
|
|
756
799
|
// Another agent's answer is still to come; an open mic would
|
|
757
800
|
// take it for the listener's words.
|
|
758
801
|
if openAsides > 0 { return }
|
|
@@ -870,7 +913,7 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
870
913
|
case .ring(let call):
|
|
871
914
|
Log.info("call: \(call.agentId) is calling: \(call.reason)")
|
|
872
915
|
ringingCall = call
|
|
873
|
-
|
|
916
|
+
summonPill()
|
|
874
917
|
panel.showCall(.ringing)
|
|
875
918
|
panel.render(.ringing(CallModel.ringingText(name: statusMenu.name(of: call.agentId), reason: call.reason)))
|
|
876
919
|
ringer.start(sound: state.ringSound)
|