agentix-cli 0.101.2 → 0.102.1
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 +11 -4
- package/apps/mac-voice/Sources/AgentXVoice/Guide.swift +12 -5
- package/apps/mac-voice/Sources/AgentXVoice/GuideMath.swift +18 -0
- package/apps/mac-voice/Sources/AgentXVoice/Panel.swift +24 -4
- package/apps/mac-voice/Sources/AgentXVoice/PanelBubble.swift +20 -1
- package/apps/mac-voice/Sources/AgentXVoice/main.swift +18 -2
- package/dist/{chunk-AZGV6KOT.js → chunk-4KERZKTO.js} +180 -180
- package/dist/{chunk-AZGV6KOT.js.map → chunk-4KERZKTO.js.map} +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-I546NXHR.js → program-N3CSTSJM.js} +5 -5
- package/dist/program-N3CSTSJM.js.map +1 -0
- package/package.json +1 -1
- package/dist/program-I546NXHR.js.map +0 -1
|
@@ -69,6 +69,11 @@ final class CharacterHost {
|
|
|
69
69
|
private var carried: (from: NSPoint, rest: CGPoint)?
|
|
70
70
|
/// What the answering agent sent it to show (#482), and its mark.
|
|
71
71
|
private let guide = CharacterGuide()
|
|
72
|
+
/// Sent somewhere, and what its bubble says there (#562).
|
|
73
|
+
var sent: Bool { guide.showing != nil }
|
|
74
|
+
var caption: String? { guide.caption }
|
|
75
|
+
/// Told when it is sent somewhere, to its next stop or back.
|
|
76
|
+
var onGuide: (() -> Void)?
|
|
72
77
|
/// A still picture: Reduce Motion, or "Animated orb" off.
|
|
73
78
|
private var still: Bool { !animated || reduceMotion }
|
|
74
79
|
/// The look is the character: whether it is on screen. Nil with the orb.
|
|
@@ -147,12 +152,13 @@ final class CharacterHost {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
/// Go beside `rect` and mark it, or with nil go back to where it rests.
|
|
150
|
-
func guide(to rect: NSRect?, mark kind: GuideMath.Mark = .none) {
|
|
155
|
+
func guide(to rect: NSRect?, mark kind: GuideMath.Mark = .none, caption: String? = nil) {
|
|
151
156
|
if shown, !hidden, carried == nil, let rect {
|
|
152
|
-
guide.show(rect, kind, color: view.stops[2], animated: !still)
|
|
157
|
+
guide.show(rect, kind, caption: caption, color: view.stops[2], animated: !still)
|
|
153
158
|
} else {
|
|
154
159
|
guide.end()
|
|
155
160
|
}
|
|
161
|
+
onGuide?()
|
|
156
162
|
redraw()
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -192,7 +198,7 @@ final class CharacterHost {
|
|
|
192
198
|
switch phase {
|
|
193
199
|
case .began:
|
|
194
200
|
let mouse = pointerSource()
|
|
195
|
-
if body.contains(mouse) { guide.end(glide: false); carried = (mouse, CGPoint(x: body.midX, y: rest.y)) }
|
|
201
|
+
if body.contains(mouse) { guide.end(glide: false); onGuide?(); carried = (mouse, CGPoint(x: body.midX, y: rest.y)) }
|
|
196
202
|
case .moved:
|
|
197
203
|
if carried != nil { tick() }
|
|
198
204
|
case .ended:
|
|
@@ -271,7 +277,8 @@ final class CharacterHost {
|
|
|
271
277
|
let head = min(max(top + 2 - PillPlacement.tail, Self.head), room)
|
|
272
278
|
let moving = carried != nil || abs(sim.speed) > 8 || rest.y != spot.place.y || head > Self.head
|
|
273
279
|
if still { motion = BubbleMotion() }
|
|
274
|
-
|
|
280
|
+
// A caption is to be read from the moment it is sent (#566).
|
|
281
|
+
let small = still ? 0 : motion.step(now: now, moving: moving, holds: guide.caption != nil || bubbleShows?() ?? false)
|
|
275
282
|
let at = NSPoint(x: origin.x + Self.size.width / 2, y: origin.y + Self.ground + head)
|
|
276
283
|
bubbleFrame = bubble?(at, visible, CGFloat(small))
|
|
277
284
|
// Its tail, unless the bubble had to go beside it.
|
|
@@ -10,6 +10,9 @@ enum GuideClient {
|
|
|
10
10
|
/// Nil: back to where it rests.
|
|
11
11
|
let rect: Rect?
|
|
12
12
|
let mark: String
|
|
13
|
+
/// What its bubble says at the stop (#562). Nil: no bubble there,
|
|
14
|
+
/// and a daemon too old to send one.
|
|
15
|
+
let text: String?
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/// GET /voice/guide: the command after `seq`, as soon as there is
|
|
@@ -34,8 +37,8 @@ final class GuideWatcher {
|
|
|
34
37
|
private var task: Task<Void, Never>?
|
|
35
38
|
/// The character is on screen.
|
|
36
39
|
var wanted: (() -> Bool)?
|
|
37
|
-
/// Show this, in AppKit coordinates, or go home.
|
|
38
|
-
var onCommand: ((NSRect?, GuideMath.Mark) -> Void)?
|
|
40
|
+
/// Show this, in AppKit coordinates, with this caption, or go home.
|
|
41
|
+
var onCommand: ((NSRect?, GuideMath.Mark, String?) -> Void)?
|
|
39
42
|
|
|
40
43
|
/// (Re)start the wait. Called again when the character comes on screen
|
|
41
44
|
/// or leaves it, so a wait already open is ended: cancelling it closes
|
|
@@ -53,7 +56,7 @@ final class GuideWatcher {
|
|
|
53
56
|
private func turn() async {
|
|
54
57
|
guard wanted?() == true, let command = await GuideClient.next(after: seq) else {
|
|
55
58
|
// Hidden, or the daemon is gone: nothing is left on screen.
|
|
56
|
-
if away { away = false; onCommand?(nil, .none) }
|
|
59
|
+
if away { away = false; onCommand?(nil, .none, nil) }
|
|
57
60
|
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
|
58
61
|
return
|
|
59
62
|
}
|
|
@@ -65,7 +68,7 @@ final class GuideWatcher {
|
|
|
65
68
|
}
|
|
66
69
|
away = rect != nil
|
|
67
70
|
Log.info("guide: \(rect.map { "to \(Int($0.minX)),\(Int($0.minY)) \(Int($0.width))x\(Int($0.height)) \(command.mark)" } ?? "home")")
|
|
68
|
-
onCommand?(rect, GuideMath.Mark(rawValue: command.mark) ?? .box)
|
|
71
|
+
onCommand?(rect, GuideMath.Mark(rawValue: command.mark) ?? .box, rect == nil ? nil : command.text)
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -74,13 +77,16 @@ final class GuideWatcher {
|
|
|
74
77
|
@MainActor
|
|
75
78
|
final class CharacterGuide {
|
|
76
79
|
private(set) var showing: NSRect?
|
|
80
|
+
/// What its bubble says there (#562).
|
|
81
|
+
private(set) var caption: String?
|
|
77
82
|
private let mark = GuideMark()
|
|
78
83
|
/// On its way up or down, to what it shows or back; and the last frame.
|
|
79
84
|
private var gliding = false
|
|
80
85
|
private var ticked = 0.0
|
|
81
86
|
|
|
82
|
-
func show(_ rect: NSRect, _ kind: GuideMath.Mark, color: NSColor, animated: Bool) {
|
|
87
|
+
func show(_ rect: NSRect, _ kind: GuideMath.Mark, caption text: String?, color: NSColor, animated: Bool) {
|
|
83
88
|
showing = rect
|
|
89
|
+
caption = text
|
|
84
90
|
gliding = true
|
|
85
91
|
mark.show(kind, around: rect, color: color, animated: animated)
|
|
86
92
|
}
|
|
@@ -89,6 +95,7 @@ final class CharacterGuide {
|
|
|
89
95
|
func end(glide: Bool = true) {
|
|
90
96
|
gliding = glide && showing != nil
|
|
91
97
|
showing = nil
|
|
98
|
+
caption = nil
|
|
92
99
|
mark.hide()
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -40,6 +40,24 @@ enum GuideMath {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/// What the bubble says while the assistant has nothing to say (#562).
|
|
44
|
+
enum Idle: Equatable { case hint, caption(String), nothing }
|
|
45
|
+
|
|
46
|
+
/// At a stop the bubble says the command's caption, and with none
|
|
47
|
+
/// there is no bubble; the idle hint is for where it rests, not for
|
|
48
|
+
/// a stop, the way there or a play. `away`: sent somewhere, or playing.
|
|
49
|
+
static func idle(caption: String?, away: Bool) -> Idle {
|
|
50
|
+
if let caption, !caption.isEmpty { return .caption(caption) }
|
|
51
|
+
return away ? .nothing : .hint
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// How wide the bubble is around a caption `words` wide (#566): its
|
|
55
|
+
/// words and the room at both ends of the row, no wider; never less
|
|
56
|
+
/// than holds its tail, nor more than the pill, where a long one scrolls.
|
|
57
|
+
static func bubbleWidth(words: CGFloat, full: CGFloat) -> CGFloat {
|
|
58
|
+
min(max(words.rounded(.up) + 52, 96), full)
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
/// One frame of its way up or down to where it is sent: most of the
|
|
44
62
|
/// way in a third of a second, and there once it is within a point.
|
|
45
63
|
static func glide(_ y: CGFloat, toward goal: CGFloat, dt: Double) -> CGFloat {
|
|
@@ -130,6 +130,10 @@ final class Panel: NSPanel {
|
|
|
130
130
|
var small: CGFloat = 0
|
|
131
131
|
let dots = BubbleDots()
|
|
132
132
|
|
|
133
|
+
/// What the bubble says while the assistant is idle (#562): the hint,
|
|
134
|
+
/// the caption of what the character shows, or nothing while it is away.
|
|
135
|
+
private(set) var idle = GuideMath.Idle.hint
|
|
136
|
+
|
|
133
137
|
/// Told of every state rendered, so the menu-bar icon can follow.
|
|
134
138
|
var onRender: ((State) -> Void)?
|
|
135
139
|
|
|
@@ -358,7 +362,7 @@ final class Panel: NSPanel {
|
|
|
358
362
|
clip.frame.size.width = clipWidth
|
|
359
363
|
// Text that scrolled may fit now, and the other way round.
|
|
360
364
|
marqueeText = ""
|
|
361
|
-
if !current.isMeta { setText(label.stringValue) }
|
|
365
|
+
if !current.isMeta || caption != nil { setText(label.stringValue) }
|
|
362
366
|
}
|
|
363
367
|
answer.frame = NSRect(x: 0, y: above ? h : 0, width: Self.expandedWidth, height: answerHeight)
|
|
364
368
|
separator.frame = NSRect(x: 0, y: above ? h - 1 : answerHeight, width: bounds.width, height: 1)
|
|
@@ -424,7 +428,7 @@ final class Panel: NSPanel {
|
|
|
424
428
|
// Growing or collapsing: it catches up on the next frame.
|
|
425
429
|
guard placing == 0 else { return }
|
|
426
430
|
shrink(expanded ? 0 : amount)
|
|
427
|
-
let shape = PillPlacement.shrunk(
|
|
431
|
+
let shape = PillPlacement.shrunk(bubbleSize, small: small)
|
|
428
432
|
let size = expanded ? frame.size : shape.size
|
|
429
433
|
let to = NSRect(origin: PillPlacement.bubble(size: size, head: head, visible: visible, reach: shape.reach), size: size)
|
|
430
434
|
guard to != frame else { return }
|
|
@@ -569,7 +573,13 @@ final class Panel: NSPanel {
|
|
|
569
573
|
}
|
|
570
574
|
orb.show(state.orbPhase, tint: tint, colors: colors)
|
|
571
575
|
|
|
572
|
-
|
|
576
|
+
label.font = Brand.body(size: 12)
|
|
577
|
+
if let words = caption {
|
|
578
|
+
// In the answer's text size, to be read in a small video.
|
|
579
|
+
label.font = Brand.body()
|
|
580
|
+
label.textColor = .labelColor
|
|
581
|
+
setText(words)
|
|
582
|
+
} else if state.isMeta {
|
|
573
583
|
stopMarquee()
|
|
574
584
|
label.attributedStringValue = Brand.metaString(state.text, color: state.color)
|
|
575
585
|
label.frame.origin.x = 0
|
|
@@ -583,7 +593,9 @@ final class Panel: NSPanel {
|
|
|
583
593
|
// Grown into an answer, it stays until it collapses, idle or not;
|
|
584
594
|
// so does a pill with agents still busy in its mini orbs.
|
|
585
595
|
// Reduced, the orb is the assistant's place on screen: it stays, idle or not.
|
|
586
|
-
|
|
596
|
+
// A caption shows like any words; away with none, the hint does not.
|
|
597
|
+
let quiet = state.isMeta && !expanded && busyCount == 0
|
|
598
|
+
if dismissed || (quiet && idle == .nothing) || (quiet && idle == .hint && !alwaysVisible && !reduced) { hide() } else { show() }
|
|
587
599
|
// After showing or hiding: a still character draws its bubble's
|
|
588
600
|
// tail only while the bubble is on screen.
|
|
589
601
|
onLook?(state, tint, colors)
|
|
@@ -591,6 +603,14 @@ final class Panel: NSPanel {
|
|
|
591
603
|
onRender?(state)
|
|
592
604
|
}
|
|
593
605
|
|
|
606
|
+
/// The character was sent somewhere or back, or a play began or ended.
|
|
607
|
+
@MainActor
|
|
608
|
+
func setIdle(_ words: GuideMath.Idle) {
|
|
609
|
+
guard words != idle else { return }
|
|
610
|
+
idle = words
|
|
611
|
+
render(current)
|
|
612
|
+
}
|
|
613
|
+
|
|
594
614
|
/// On screen with something to use: an answer, an error or the call
|
|
595
615
|
/// buttons. The character does not play with the pointer then (#537).
|
|
596
616
|
@MainActor
|
|
@@ -15,12 +15,31 @@ extension Panel {
|
|
|
15
15
|
small = amount
|
|
16
16
|
row.alphaValue = max(1 - 2 * amount, 0)
|
|
17
17
|
dots.alphaValue = max(2 * amount - 1, 0)
|
|
18
|
-
(contentView as? Surface)?.shape(size: amount > 0 ? PillPlacement.shrunk(
|
|
18
|
+
(contentView as? Surface)?.shape(size: amount > 0 ? PillPlacement.shrunk(bubbleSize, small: amount).size : nil)
|
|
19
19
|
invalidateShadow()
|
|
20
20
|
// The row was not laid out while reduced, and the frame may already
|
|
21
21
|
// be the full pill's: nothing else would lay it out again.
|
|
22
22
|
if amount == 0, let content = contentView { layoutContent(content.bounds) }
|
|
23
23
|
}
|
|
24
|
+
|
|
25
|
+
/// The caption the bubble says now (#566): at a stop it shows whatever
|
|
26
|
+
/// the assistant is doing; only an error or a call takes its place.
|
|
27
|
+
@MainActor
|
|
28
|
+
var caption: String? {
|
|
29
|
+
guard case .caption(let words) = idle else { return nil }
|
|
30
|
+
switch current {
|
|
31
|
+
case .error, .ringing, .onCall: return nil
|
|
32
|
+
default: return words
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The bubble's size: with a caption, as wide as its words, no wider.
|
|
37
|
+
@MainActor
|
|
38
|
+
var bubbleSize: NSSize {
|
|
39
|
+
guard let caption else { return Self.size }
|
|
40
|
+
let words = (caption as NSString).size(withAttributes: [.font: Brand.body()]).width
|
|
41
|
+
return NSSize(width: GuideMath.bubbleWidth(words: words, full: Self.size.width), height: Self.size.height)
|
|
42
|
+
}
|
|
24
43
|
}
|
|
25
44
|
|
|
26
45
|
/// Three dots, in the middle of the bubble.
|
|
@@ -86,6 +86,8 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
86
86
|
// An agent ringing the owner (#321).
|
|
87
87
|
private let callWatcher = CallWatcher()
|
|
88
88
|
private let guideWatcher = GuideWatcher()
|
|
89
|
+
/// A play runs, or its picture is being taken (#562).
|
|
90
|
+
private var playing = false
|
|
89
91
|
private let ringer = Ringer()
|
|
90
92
|
/// The call ringing on the pill now.
|
|
91
93
|
private var ringingCall: IncomingCall?
|
|
@@ -236,6 +238,12 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
236
238
|
&& !busy && !recorder.isRecording && !play.running
|
|
237
239
|
}
|
|
238
240
|
|
|
241
|
+
/// What the idle bubble says (#562): no hint at a guide stop, on the
|
|
242
|
+
/// way there or during a play; the stop's caption if it has one.
|
|
243
|
+
private func idleWords() {
|
|
244
|
+
panel.setIdle(GuideMath.idle(caption: character.caption, away: character.sent || playing))
|
|
245
|
+
}
|
|
246
|
+
|
|
239
247
|
/// "Play on this page" (#505): one picture of the character's screen, the
|
|
240
248
|
/// lines of text read on it, and the character plays on the picture,
|
|
241
249
|
/// differently each time. The page itself is never touched.
|
|
@@ -252,12 +260,17 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
252
260
|
}
|
|
253
261
|
let stops = character.stops, size = screen.frame.size
|
|
254
262
|
let start = PlayMath.Point(x: foot.x - screen.frame.minX, y: screen.frame.maxY - foot.y)
|
|
255
|
-
// Off its edge, so it is not in the picture twice
|
|
263
|
+
// Off its edge, so it is not in the picture twice; its idle
|
|
264
|
+
// bubble too, which would stay in the picture all the play long.
|
|
256
265
|
character.setShown(false)
|
|
266
|
+
playing = true
|
|
267
|
+
idleWords()
|
|
257
268
|
Task { @MainActor in
|
|
258
269
|
try? await Task.sleep(nanoseconds: 150_000_000)
|
|
259
270
|
guard let picture = PlayRead.picture(of: screen), !busy, !recorder.isRecording else {
|
|
271
|
+
playing = false
|
|
260
272
|
character.setShown(settings?.general.look == "character")
|
|
273
|
+
idleWords()
|
|
261
274
|
return
|
|
262
275
|
}
|
|
263
276
|
play.start(picture: picture, on: screen, stops: stops, at: start)
|
|
@@ -403,7 +416,9 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
403
416
|
statusMenu.onPlay = { [weak self] in self?.startPlay() }
|
|
404
417
|
play.onEnd = { [weak self] in
|
|
405
418
|
guard let self else { return }
|
|
419
|
+
self.playing = false
|
|
406
420
|
self.character.setShown(self.settings?.general.look == "character")
|
|
421
|
+
self.idleWords()
|
|
407
422
|
}
|
|
408
423
|
panel.onShown = { [weak self] in self?.character.redraw() }
|
|
409
424
|
panel.onDismiss = { [weak self] in self?.dismissPill() }
|
|
@@ -511,7 +526,8 @@ final class App: NSObject, NSApplicationDelegate {
|
|
|
511
526
|
callWatcher.start()
|
|
512
527
|
// The answering agent sends the character to something (#482).
|
|
513
528
|
guideWatcher.wanted = { [weak self] in self?.character.onScreen == true }
|
|
514
|
-
guideWatcher.onCommand = { [weak self] rect, mark in self?.character.guide(to: rect, mark: mark) }
|
|
529
|
+
guideWatcher.onCommand = { [weak self] rect, mark, caption in self?.character.guide(to: rect, mark: mark, caption: caption) }
|
|
530
|
+
character.onGuide = { [weak self] in self?.idleWords() }
|
|
515
531
|
guideWatcher.start()
|
|
516
532
|
character.onScreenChanged = { [weak self] in self?.guideWatcher.start() }
|
|
517
533
|
|