agentix-cli 0.47.1 → 0.48.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/Buffer.swift +65 -0
- package/apps/mac-helper/Sources/AgentXHelper/Notify.swift +26 -0
- package/apps/mac-helper/Sources/AgentXHelper/Ring.swift +58 -0
- package/apps/mac-helper/Sources/AgentXHelper/Screens.swift +7 -0
- package/apps/mac-helper/Sources/AgentXHelper/Vision.swift +13 -8
- package/apps/mac-helper/Sources/AgentXHelper/Watch.swift +87 -0
- package/apps/mac-helper/Sources/AgentXHelper/main.swift +72 -18
- package/dist/{App-RZDJH6MK.js → App-EXB4HP3B.js} +2 -2
- package/dist/{ChatApp-6TXOFUDH.js → ChatApp-XMNE3SPH.js} +2 -2
- package/dist/{agent-I6RXAEXG.js → agent-ORYXXL5K.js} +2 -2
- package/dist/{board-dashboard-4LAQOB74.js → board-dashboard-2B3HYIXD.js} +493 -131
- package/dist/board-dashboard-2B3HYIXD.js.map +1 -0
- package/dist/{chunk-BZLHS3VF.js → chunk-26OP3X5B.js} +2 -2
- package/dist/{chunk-VHTFHGVW.js → chunk-2GSEEKQK.js} +51 -51
- package/dist/chunk-2GSEEKQK.js.map +1 -0
- package/dist/{chunk-FNLGY4UV.js → chunk-2WPTP5HO.js} +2 -2
- package/dist/{chunk-MDTKQZC6.js → chunk-3WLO2UKI.js} +2 -2
- package/dist/chunk-3ZHMIWSV.js +2 -0
- package/dist/chunk-3ZHMIWSV.js.map +1 -0
- package/dist/{chunk-YIXQDOPN.js → chunk-4ZX4E7P3.js} +185 -179
- package/dist/chunk-4ZX4E7P3.js.map +1 -0
- package/dist/chunk-7DXE264B.js +2 -0
- package/dist/chunk-7DXE264B.js.map +1 -0
- package/dist/chunk-DIV66W3N.js +28 -0
- package/dist/chunk-DIV66W3N.js.map +1 -0
- package/dist/{chunk-7CVJ4JOE.js → chunk-DLKRPK5M.js} +2 -2
- package/dist/chunk-DVNIFP7S.js +23 -0
- package/dist/chunk-DVNIFP7S.js.map +1 -0
- package/dist/chunk-FULEQYM6.js +13 -0
- package/dist/chunk-FULEQYM6.js.map +1 -0
- package/dist/chunk-GAGVFSZM.js +15 -0
- package/dist/{chunk-ANLKBCYH.js.map → chunk-GAGVFSZM.js.map} +1 -1
- package/dist/{chunk-3RHPZXCR.js → chunk-GCHUE3TN.js} +2 -2
- package/dist/chunk-GIK7S3OY.js +19 -0
- package/dist/chunk-GIK7S3OY.js.map +1 -0
- package/dist/{chunk-J63MRNXF.js → chunk-HEQPHUTR.js} +2 -2
- package/dist/{chunk-KTM7HSEB.js → chunk-LDILAHMN.js} +2 -2
- package/dist/{chunk-V7FYSZSV.js → chunk-LQRBGQIR.js} +2 -2
- package/dist/chunk-LQRBGQIR.js.map +1 -0
- package/dist/{chunk-LYOXXD4T.js → chunk-M5R3PWW5.js} +2 -2
- package/dist/{chunk-BJWK7FPT.js → chunk-MLLEOFX2.js} +2 -2
- package/dist/{chunk-QURVHYBP.js → chunk-OZX7HVHC.js} +168 -165
- package/dist/chunk-OZX7HVHC.js.map +1 -0
- package/dist/chunk-P5JXQRBH.js +7 -0
- package/dist/chunk-P5JXQRBH.js.map +1 -0
- package/dist/{schedule-tool-6SNLG2NA.js → chunk-P7MR53U7.js} +3 -3
- package/dist/{schedule-tool-6SNLG2NA.js.map → chunk-P7MR53U7.js.map} +1 -1
- package/dist/chunk-RKPAVWXF.js +2 -0
- package/dist/chunk-RKPAVWXF.js.map +1 -0
- package/dist/{chunk-VZDKE2EZ.js → chunk-RYLTCOYE.js} +2 -2
- package/dist/{chunk-XMWOV2D5.js → chunk-SL657MIM.js} +2 -2
- package/dist/chunk-WMI5B36E.js +5 -0
- package/dist/{chunk-BHPIAG3E.js.map → chunk-WMI5B36E.js.map} +1 -1
- package/dist/{chunk-NXJBAKBR.js → chunk-XM7ZQW4F.js} +2 -2
- package/dist/{chunk-XMRI32CY.js → chunk-ZUZESPCH.js} +61 -71
- package/dist/chunk-ZUZESPCH.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-6HCY7UMW.js +2 -0
- package/dist/{compaction-QXU3A4LQ.js → compaction-U5SAYTX6.js} +2 -2
- package/dist/config-ROQ4VEU3.js +2 -0
- package/dist/config-mutator-ML4BAOFT.js +2 -0
- package/dist/doctor-3HWGAPET.js +2 -0
- package/dist/index.d.ts +258 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-extract-YZI2QZVR.js +2 -0
- package/dist/processes-XWRGOLMT.js +2 -0
- package/dist/program-JLZOHWIU.js +2 -0
- package/dist/{promote-RNSGW4AJ.js → promote-YGNTKEXI.js} +2 -2
- package/dist/query-V76JDSOC.js +2 -0
- package/dist/registry-DKN4HEI3.js +2 -0
- package/dist/{request-planner-5354IOYM.js → request-planner-FAFNFVFX.js} +2 -2
- package/dist/{review-candidates-UTWMO7UO.js → review-candidates-2XKJ2R6U.js} +2 -2
- package/dist/{routing-JGNWB6VJ.js → routing-PUPVGJ77.js} +2 -2
- package/dist/schedule-tool-6J7AHAHH.js +2 -0
- package/dist/scheduler-ZHQBSSRH.js +2 -0
- package/dist/{seat-STLDEYSO.js → seat-2MW3DEXX.js} +2 -2
- package/dist/{select-VP66P6XB.js → select-Q3AGLQQX.js} +2 -2
- package/dist/tool-IVUNSAXY.js +2 -0
- package/dist/tool-IVUNSAXY.js.map +1 -0
- package/dist/wiki-FPX2YBKS.js +2 -0
- package/dist/wiki-FPX2YBKS.js.map +1 -0
- package/package.json +1 -1
- package/dist/board-dashboard-4LAQOB74.js.map +0 -1
- package/dist/chunk-7L6M3P2N.js +0 -13
- package/dist/chunk-7L6M3P2N.js.map +0 -1
- package/dist/chunk-ANLKBCYH.js +0 -15
- package/dist/chunk-BHPIAG3E.js +0 -5
- package/dist/chunk-CQR6C5FU.js +0 -23
- package/dist/chunk-CQR6C5FU.js.map +0 -1
- package/dist/chunk-FQBPSL36.js +0 -25
- package/dist/chunk-FQBPSL36.js.map +0 -1
- package/dist/chunk-FXGSSZAP.js +0 -9
- package/dist/chunk-FXGSSZAP.js.map +0 -1
- package/dist/chunk-OSSI6R4K.js +0 -2
- package/dist/chunk-OSSI6R4K.js.map +0 -1
- package/dist/chunk-QURVHYBP.js.map +0 -1
- package/dist/chunk-V7FYSZSV.js.map +0 -1
- package/dist/chunk-VHTFHGVW.js.map +0 -1
- package/dist/chunk-W544ZCCO.js +0 -7
- package/dist/chunk-W544ZCCO.js.map +0 -1
- package/dist/chunk-XMRI32CY.js.map +0 -1
- package/dist/chunk-YIXQDOPN.js.map +0 -1
- package/dist/client-LAIP23IP.js +0 -2
- package/dist/config-OZ2OOKFA.js +0 -2
- package/dist/config-mutator-EUF7QQ4V.js +0 -2
- package/dist/doctor-PEE3F5TX.js +0 -2
- package/dist/memory-extract-3TO7UC5B.js +0 -2
- package/dist/processes-I3XAPHG7.js +0 -2
- package/dist/program-PMHQRD5E.js +0 -2
- package/dist/query-BOUVRUU4.js +0 -2
- package/dist/registry-GGU43EOI.js +0 -2
- package/dist/scheduler-N25EAWF6.js +0 -2
- package/dist/wiki-SKIIAVCU.js +0 -2
- /package/dist/{App-RZDJH6MK.js.map → App-EXB4HP3B.js.map} +0 -0
- /package/dist/{ChatApp-6TXOFUDH.js.map → ChatApp-XMNE3SPH.js.map} +0 -0
- /package/dist/{agent-I6RXAEXG.js.map → agent-ORYXXL5K.js.map} +0 -0
- /package/dist/{chunk-BZLHS3VF.js.map → chunk-26OP3X5B.js.map} +0 -0
- /package/dist/{chunk-FNLGY4UV.js.map → chunk-2WPTP5HO.js.map} +0 -0
- /package/dist/{chunk-MDTKQZC6.js.map → chunk-3WLO2UKI.js.map} +0 -0
- /package/dist/{chunk-7CVJ4JOE.js.map → chunk-DLKRPK5M.js.map} +0 -0
- /package/dist/{chunk-3RHPZXCR.js.map → chunk-GCHUE3TN.js.map} +0 -0
- /package/dist/{chunk-J63MRNXF.js.map → chunk-HEQPHUTR.js.map} +0 -0
- /package/dist/{chunk-KTM7HSEB.js.map → chunk-LDILAHMN.js.map} +0 -0
- /package/dist/{chunk-LYOXXD4T.js.map → chunk-M5R3PWW5.js.map} +0 -0
- /package/dist/{chunk-BJWK7FPT.js.map → chunk-MLLEOFX2.js.map} +0 -0
- /package/dist/{chunk-VZDKE2EZ.js.map → chunk-RYLTCOYE.js.map} +0 -0
- /package/dist/{chunk-XMWOV2D5.js.map → chunk-SL657MIM.js.map} +0 -0
- /package/dist/{chunk-NXJBAKBR.js.map → chunk-XM7ZQW4F.js.map} +0 -0
- /package/dist/{client-LAIP23IP.js.map → client-6HCY7UMW.js.map} +0 -0
- /package/dist/{compaction-QXU3A4LQ.js.map → compaction-U5SAYTX6.js.map} +0 -0
- /package/dist/{config-OZ2OOKFA.js.map → config-ROQ4VEU3.js.map} +0 -0
- /package/dist/{config-mutator-EUF7QQ4V.js.map → config-mutator-ML4BAOFT.js.map} +0 -0
- /package/dist/{doctor-PEE3F5TX.js.map → doctor-3HWGAPET.js.map} +0 -0
- /package/dist/{memory-extract-3TO7UC5B.js.map → memory-extract-YZI2QZVR.js.map} +0 -0
- /package/dist/{processes-I3XAPHG7.js.map → processes-XWRGOLMT.js.map} +0 -0
- /package/dist/{program-PMHQRD5E.js.map → program-JLZOHWIU.js.map} +0 -0
- /package/dist/{promote-RNSGW4AJ.js.map → promote-YGNTKEXI.js.map} +0 -0
- /package/dist/{query-BOUVRUU4.js.map → query-V76JDSOC.js.map} +0 -0
- /package/dist/{registry-GGU43EOI.js.map → registry-DKN4HEI3.js.map} +0 -0
- /package/dist/{request-planner-5354IOYM.js.map → request-planner-FAFNFVFX.js.map} +0 -0
- /package/dist/{review-candidates-UTWMO7UO.js.map → review-candidates-2XKJ2R6U.js.map} +0 -0
- /package/dist/{routing-JGNWB6VJ.js.map → routing-PUPVGJ77.js.map} +0 -0
- /package/dist/{scheduler-N25EAWF6.js.map → schedule-tool-6J7AHAHH.js.map} +0 -0
- /package/dist/{seat-STLDEYSO.js.map → scheduler-ZHQBSSRH.js.map} +0 -0
- /package/dist/{wiki-SKIIAVCU.js.map → seat-2MW3DEXX.js.map} +0 -0
- /package/dist/{select-VP66P6XB.js.map → select-Q3AGLQQX.js.map} +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import AppKit
|
|
2
|
+
|
|
3
|
+
/// The `buffer` verb: a region's last few seconds, held in memory.
|
|
4
|
+
///
|
|
5
|
+
/// Samples the region `fps` times a second into a `Ring`, keeping a frame
|
|
6
|
+
/// only when it differs from the one before. Frames are downscaled to
|
|
7
|
+
/// `maxPixels` before they are kept, so ten seconds of a whole Retina
|
|
8
|
+
/// screen costs a few megabytes, not a few hundred.
|
|
9
|
+
///
|
|
10
|
+
/// Commands on stdin, one per line:
|
|
11
|
+
///
|
|
12
|
+
/// dump SECONDS DIR write the frames from the last SECONDS to DIR as
|
|
13
|
+
/// PNGs and answer with one JSON line listing them
|
|
14
|
+
/// (DIR last, so it may contain spaces)
|
|
15
|
+
///
|
|
16
|
+
/// EOF ends the process: the daemon that owns it closes stdin, or dies.
|
|
17
|
+
enum Buffer {
|
|
18
|
+
|
|
19
|
+
static func run(region: CGRect, fps: Double, seconds: Double, maxPixels: Int, threshold: Double) {
|
|
20
|
+
let clock = { ProcessInfo.processInfo.systemUptime }
|
|
21
|
+
let lock = NSLock()
|
|
22
|
+
var ring = Ring<CGImage>(seconds: seconds, capacity: Int(ceil(seconds * max(fps, 0.1))) + 1,
|
|
23
|
+
threshold: threshold)
|
|
24
|
+
|
|
25
|
+
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .utility))
|
|
26
|
+
timer.schedule(deadline: .now(), repeating: 1 / max(fps, 0.1))
|
|
27
|
+
timer.setEventHandler {
|
|
28
|
+
guard let image = Vision.snapshot(region), let grid = Vision.grid(image) else { return }
|
|
29
|
+
lock.lock(); defer { lock.unlock() }
|
|
30
|
+
let now = clock()
|
|
31
|
+
guard ring.wants(grid) else { ring.prune(now: now); return }
|
|
32
|
+
ring.add(Vision.scaled(image, maxPixels: maxPixels), grid: grid, at: now)
|
|
33
|
+
}
|
|
34
|
+
timer.resume()
|
|
35
|
+
|
|
36
|
+
while let line = readLine() {
|
|
37
|
+
let parts = line.split(separator: " ", maxSplits: 2).map(String.init)
|
|
38
|
+
guard parts.first == "dump", parts.count == 3, let within = Double(parts[1]) else {
|
|
39
|
+
reply(["ok": false, "error": "expected: dump SECONDS DIR"])
|
|
40
|
+
continue
|
|
41
|
+
}
|
|
42
|
+
lock.lock()
|
|
43
|
+
let now = clock()
|
|
44
|
+
let frames = ring.recent(within: within, now: now)
|
|
45
|
+
lock.unlock()
|
|
46
|
+
var written: [[String: Any]] = []
|
|
47
|
+
for (i, entry) in frames.enumerated() {
|
|
48
|
+
let path = (parts[2] as NSString).appendingPathComponent(String(format: "frame-%03d.png", i))
|
|
49
|
+
if Vision.write(entry.frame, to: path) {
|
|
50
|
+
written.append(["path": path, "ageMs": Int((now - entry.at) * 1000),
|
|
51
|
+
"width": entry.frame.width, "height": entry.frame.height])
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
reply(["ok": true, "frames": written,
|
|
55
|
+
"region": ["x": region.minX, "y": region.minY, "w": region.width, "h": region.height]])
|
|
56
|
+
}
|
|
57
|
+
timer.cancel()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private static func reply(_ payload: [String: Any]) {
|
|
61
|
+
guard var data = try? JSONSerialization.data(withJSONObject: payload) else { return }
|
|
62
|
+
data.append(0x0A)
|
|
63
|
+
FileHandle.standardOutput.write(data)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -48,4 +48,30 @@ enum Notify {
|
|
|
48
48
|
guard done.wait(timeout: .now() + timeout) == .success else { return .notAllowed }
|
|
49
49
|
return outcome
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
/// What macOS allows right now, without posting a banner or showing the
|
|
53
|
+
/// "allow notifications" prompt. `agentx notifications show` and
|
|
54
|
+
/// `agentx doctor` read it to explain a banner that shows the wrong icon.
|
|
55
|
+
static func status(timeout: TimeInterval = 4) -> [String: Any] {
|
|
56
|
+
let done = DispatchSemaphore(value: 0)
|
|
57
|
+
var payload: [String: Any] = ["ok": true, "authorization": "unknown", "alertStyle": "unknown"]
|
|
58
|
+
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
59
|
+
switch settings.authorizationStatus {
|
|
60
|
+
case .notDetermined: payload["authorization"] = "notDetermined"
|
|
61
|
+
case .denied: payload["authorization"] = "denied"
|
|
62
|
+
case .authorized: payload["authorization"] = "authorized"
|
|
63
|
+
case .provisional: payload["authorization"] = "provisional"
|
|
64
|
+
@unknown default: break
|
|
65
|
+
}
|
|
66
|
+
switch settings.alertStyle {
|
|
67
|
+
case .none: payload["alertStyle"] = "none"
|
|
68
|
+
case .banner: payload["alertStyle"] = "banner"
|
|
69
|
+
case .alert: payload["alertStyle"] = "alert"
|
|
70
|
+
@unknown default: break
|
|
71
|
+
}
|
|
72
|
+
done.signal()
|
|
73
|
+
}
|
|
74
|
+
_ = done.wait(timeout: .now() + timeout)
|
|
75
|
+
return payload
|
|
76
|
+
}
|
|
51
77
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// The last few seconds of a region, in memory.
|
|
4
|
+
///
|
|
5
|
+
/// For a caller that arrives late: the banner it wanted to see has gone,
|
|
6
|
+
/// but a frame showing it is still here. Frames are kept only while they
|
|
7
|
+
/// are younger than `seconds` and never more than `capacity` of them, and
|
|
8
|
+
/// a frame identical to the one before is not kept at all — a still screen
|
|
9
|
+
/// costs one frame, not one per tick.
|
|
10
|
+
///
|
|
11
|
+
/// Nothing here touches the disk. Frames leave memory only when a caller
|
|
12
|
+
/// asks for them (the `buffer` verb's `dump` command).
|
|
13
|
+
struct Ring<Frame> {
|
|
14
|
+
struct Entry {
|
|
15
|
+
let at: TimeInterval
|
|
16
|
+
let frame: Frame
|
|
17
|
+
/// The comparison grid, so the next sample can be checked against it.
|
|
18
|
+
let grid: [UInt8]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let seconds: TimeInterval
|
|
22
|
+
let capacity: Int
|
|
23
|
+
let threshold: Double
|
|
24
|
+
private(set) var entries: [Entry] = []
|
|
25
|
+
|
|
26
|
+
init(seconds: TimeInterval, capacity: Int, threshold: Double) {
|
|
27
|
+
self.seconds = seconds
|
|
28
|
+
self.capacity = max(1, capacity)
|
|
29
|
+
self.threshold = threshold
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Whether a sample with this grid would be kept: always when empty,
|
|
33
|
+
/// otherwise only when it differs from the newest frame.
|
|
34
|
+
func wants(_ grid: [UInt8]) -> Bool {
|
|
35
|
+
guard let last = entries.last else { return true }
|
|
36
|
+
guard let d = Watch.difference(last.grid, grid) else { return true }
|
|
37
|
+
return d > threshold
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
mutating func add(_ frame: Frame, grid: [UInt8], at: TimeInterval) {
|
|
41
|
+
entries.append(Entry(at: at, frame: frame, grid: grid))
|
|
42
|
+
prune(now: at)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
mutating func prune(now: TimeInterval) {
|
|
46
|
+
// The newest frame stays even when old: on a still screen it IS
|
|
47
|
+
// what has been showing for the whole window.
|
|
48
|
+
while entries.count > 1, now - entries[0].at > seconds { entries.removeFirst() }
|
|
49
|
+
if entries.count > capacity { entries.removeFirst(entries.count - capacity) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Frames from the last `within` seconds, oldest first, plus the frame
|
|
53
|
+
/// that was already showing when that window opened.
|
|
54
|
+
func recent(within: TimeInterval, now: TimeInterval) -> [Entry] {
|
|
55
|
+
guard let first = entries.lastIndex(where: { now - $0.at > within }) else { return entries }
|
|
56
|
+
return Array(entries[first...])
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -64,4 +64,11 @@ enum Screens {
|
|
|
64
64
|
static func menuBar(_ info: Info) -> CGRect {
|
|
65
65
|
CGRect(x: info.x, y: info.y, width: info.width, height: 26)
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
/// Where macOS shows notification banners: the top-right corner of the
|
|
69
|
+
/// primary screen, under the menu bar. Generous, because banner size
|
|
70
|
+
/// varies with the text; a caller that knows better passes a rect.
|
|
71
|
+
static func notifications(_ info: Info) -> CGRect {
|
|
72
|
+
CGRect(x: info.x + info.width - 420, y: info.y + 26, width: 420, height: 180)
|
|
73
|
+
}
|
|
67
74
|
}
|
|
@@ -121,6 +121,11 @@ enum Vision {
|
|
|
121
121
|
guard let image = CGWindowListCreateImage(
|
|
122
122
|
rect, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution])
|
|
123
123
|
else { return false }
|
|
124
|
+
return write(scaled(image, maxWidth: maxWidth, maxPixels: maxPixels), to: path)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// The image within the pixel budget and width cap, aspect kept.
|
|
128
|
+
static func scaled(_ image: CGImage, maxWidth: Int? = nil, maxPixels: Int? = nil) -> CGImage {
|
|
124
129
|
var target = maxWidth
|
|
125
130
|
if let budget = maxPixels, budget > 0 {
|
|
126
131
|
let area = image.width * image.height
|
|
@@ -131,8 +136,11 @@ enum Vision {
|
|
|
131
136
|
target = min(target ?? fit, fit)
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
|
-
|
|
135
|
-
|
|
139
|
+
return target.flatMap { downscale(image, maxWidth: $0) } ?? image
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
static func write(_ image: CGImage, to path: String) -> Bool {
|
|
143
|
+
let rep = NSBitmapImageRep(cgImage: image)
|
|
136
144
|
guard let png = rep.representation(using: .png, properties: [:]) else { return false }
|
|
137
145
|
return (try? png.write(to: URL(fileURLWithPath: path))) != nil
|
|
138
146
|
}
|
|
@@ -165,11 +173,8 @@ enum Vision {
|
|
|
165
173
|
guard let now = CGWindowListCreateImage(a, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution]),
|
|
166
174
|
let before = b
|
|
167
175
|
else { return nil }
|
|
168
|
-
guard let g1 = grid(before), let g2 = grid(now)
|
|
169
|
-
|
|
170
|
-
var total = 0.0
|
|
171
|
-
for i in 0..<g1.count { total += abs(Double(g1[i]) - Double(g2[i])) / 255.0 }
|
|
172
|
-
return total / Double(g1.count)
|
|
176
|
+
guard let g1 = grid(before), let g2 = grid(now) else { return nil }
|
|
177
|
+
return Watch.difference(g1, g2)
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
static func snapshot(_ rect: CGRect) -> CGImage? {
|
|
@@ -178,7 +183,7 @@ enum Vision {
|
|
|
178
183
|
|
|
179
184
|
/// 16×16 greyscale, which is plenty to notice a page changing and
|
|
180
185
|
/// cheap enough to run between every action.
|
|
181
|
-
|
|
186
|
+
static func grid(_ image: CGImage, side: Int = 16) -> [UInt8]? {
|
|
182
187
|
let space = CGColorSpaceCreateDeviceGray()
|
|
183
188
|
var pixels = [UInt8](repeating: 0, count: side * side)
|
|
184
189
|
guard let ctx = CGContext(data: &pixels, width: side, height: side,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Waiting for the right moment to capture.
|
|
4
|
+
///
|
|
5
|
+
/// A single frame taken whenever the caller gets round to it misses
|
|
6
|
+
/// anything short-lived: a notification banner is gone within seconds, and
|
|
7
|
+
/// a page that is still loading is captured half-drawn. So a capture can
|
|
8
|
+
/// wait for its region to CHANGE (something appeared), to be STABLE
|
|
9
|
+
/// (whatever was moving has stopped), or both in that order.
|
|
10
|
+
///
|
|
11
|
+
/// Pure logic over an injected sampler and clock, so it is testable
|
|
12
|
+
/// without a screen. A sample is a small greyscale grid (see
|
|
13
|
+
/// `Vision.grid`); comparing grids ignores antialiasing and one-pixel
|
|
14
|
+
/// shifts.
|
|
15
|
+
enum Watch {
|
|
16
|
+
|
|
17
|
+
struct Options {
|
|
18
|
+
/// Mean absolute difference, 0…1, above which two samples differ.
|
|
19
|
+
var threshold: Double = 0.015
|
|
20
|
+
/// Time between samples.
|
|
21
|
+
var interval: TimeInterval = 0.1
|
|
22
|
+
/// Give up after this long; the caller still gets a frame.
|
|
23
|
+
var timeout: TimeInterval = 5
|
|
24
|
+
/// How long the region must stay unchanged to count as stable.
|
|
25
|
+
var stableFor: TimeInterval = 0.4
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
struct Outcome: Equatable {
|
|
29
|
+
/// The region changed from the baseline (only when asked to wait for it).
|
|
30
|
+
var changed: Bool
|
|
31
|
+
/// The region settled (only when asked to wait for it).
|
|
32
|
+
var stable: Bool
|
|
33
|
+
var timedOut: Bool
|
|
34
|
+
var waited: TimeInterval
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Mean absolute difference of two grids, 0…1; nil when they cannot be compared.
|
|
38
|
+
static func difference(_ a: [UInt8], _ b: [UInt8]) -> Double? {
|
|
39
|
+
guard a.count == b.count, !a.isEmpty else { return nil }
|
|
40
|
+
var total = 0.0
|
|
41
|
+
for i in 0..<a.count { total += abs(Double(a[i]) - Double(b[i])) / 255.0 }
|
|
42
|
+
return total / Double(a.count)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Waits for a change from `baseline` (when given), then for stability
|
|
46
|
+
/// (when `untilStable`). Returns as soon as the requested conditions
|
|
47
|
+
/// hold, or at the timeout.
|
|
48
|
+
static func wait(baseline: [UInt8]?, untilStable: Bool, options o: Options,
|
|
49
|
+
sample: () -> [UInt8]?, now: () -> TimeInterval,
|
|
50
|
+
sleep: (TimeInterval) -> Void) -> Outcome {
|
|
51
|
+
let start = now()
|
|
52
|
+
var outcome = Outcome(changed: false, stable: false, timedOut: false, waited: 0)
|
|
53
|
+
func expired() -> Bool { now() - start >= o.timeout }
|
|
54
|
+
func finish(timedOut: Bool) -> Outcome {
|
|
55
|
+
outcome.timedOut = timedOut
|
|
56
|
+
outcome.waited = now() - start
|
|
57
|
+
return outcome
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if let base = baseline {
|
|
61
|
+
while true {
|
|
62
|
+
if let s = sample(), let d = difference(base, s), d > o.threshold { break }
|
|
63
|
+
if expired() { return finish(timedOut: true) }
|
|
64
|
+
sleep(o.interval)
|
|
65
|
+
}
|
|
66
|
+
outcome.changed = true
|
|
67
|
+
}
|
|
68
|
+
guard untilStable else { return finish(timedOut: false) }
|
|
69
|
+
|
|
70
|
+
var last = sample()
|
|
71
|
+
var quietSince = now()
|
|
72
|
+
while true {
|
|
73
|
+
if expired() { return finish(timedOut: true) }
|
|
74
|
+
sleep(o.interval)
|
|
75
|
+
let s = sample()
|
|
76
|
+
if let a = last, let b = s, let d = difference(a, b), d <= o.threshold {
|
|
77
|
+
if now() - quietSince >= o.stableFor {
|
|
78
|
+
outcome.stable = true
|
|
79
|
+
return finish(timedOut: false)
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
quietSince = now()
|
|
83
|
+
}
|
|
84
|
+
last = s
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -35,6 +35,34 @@ func flag(_ name: String) -> String? {
|
|
|
35
35
|
return args[i + 1]
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/// The region a capture or buffer covers: an explicit rect, a named part
|
|
39
|
+
/// of a screen, or the focused window.
|
|
40
|
+
func captureRegion() -> CGRect {
|
|
41
|
+
let screen = Screens.list().first(where: { $0.index == Int(flag("screen") ?? "") })
|
|
42
|
+
?? Screens.active()
|
|
43
|
+
if let x = Double(flag("x") ?? ""), let y = Double(flag("y") ?? ""),
|
|
44
|
+
let w = Double(flag("w") ?? ""), let h = Double(flag("h") ?? "") {
|
|
45
|
+
return CGRect(x: x, y: y, width: w, height: h)
|
|
46
|
+
}
|
|
47
|
+
if args.contains("--notifications") {
|
|
48
|
+
// Banners always appear on the primary screen.
|
|
49
|
+
guard let s = Screens.list().first(where: { $0.primary }) else { fail("no screen found") }
|
|
50
|
+
return Screens.notifications(s)
|
|
51
|
+
}
|
|
52
|
+
if args.contains("--menubar") {
|
|
53
|
+
guard let s = screen else { fail("no screen found") }
|
|
54
|
+
return Screens.menuBar(s)
|
|
55
|
+
}
|
|
56
|
+
if args.contains("--screen-full") {
|
|
57
|
+
guard let s = screen else { fail("no screen found") }
|
|
58
|
+
return Screens.rect(s)
|
|
59
|
+
}
|
|
60
|
+
guard let win = OCR.focusedWindowFrame() else {
|
|
61
|
+
fail("no focused window — pass --x/--y/--w/--h, --notifications, --menubar or --screen-full")
|
|
62
|
+
}
|
|
63
|
+
return win
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
switch verb {
|
|
39
67
|
case "read":
|
|
40
68
|
guard AXTree.trusted() else {
|
|
@@ -110,37 +138,59 @@ case "capture":
|
|
|
110
138
|
// without the caller knowing the display's size, because the state
|
|
111
139
|
// worth checking — a recording indicator, a capture pill — lives in
|
|
112
140
|
// the menu bar, which belongs to no window at all.
|
|
141
|
+
//
|
|
142
|
+
// --until-changed / --until-stable wait for the right moment instead
|
|
143
|
+
// of taking whatever is there now. With --armed, a line goes to stderr
|
|
144
|
+
// once the baseline is taken, so a caller can start its action only
|
|
145
|
+
// then and never race the thing it wants to see.
|
|
113
146
|
guard let out = flag("out") else { fail("capture needs --out") }
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
let
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
let region = captureRegion()
|
|
148
|
+
var outcome: Watch.Outcome?
|
|
149
|
+
let untilChanged = args.contains("--until-changed"), untilStable = args.contains("--until-stable")
|
|
150
|
+
if untilChanged || untilStable {
|
|
151
|
+
let sample = { Vision.snapshot(region).flatMap { Vision.grid($0) } }
|
|
152
|
+
let baseline = untilChanged ? sample() : nil
|
|
153
|
+
if untilChanged && baseline == nil { fail("capture failed — check Screen Recording permission") }
|
|
154
|
+
if args.contains("--armed") { FileHandle.standardError.write("armed\n".data(using: .utf8)!) }
|
|
155
|
+
var o = Watch.Options()
|
|
156
|
+
if let v = Double(flag("threshold") ?? "") { o.threshold = v }
|
|
157
|
+
if let v = Double(flag("interval") ?? "") { o.interval = v / 1000 }
|
|
158
|
+
if let v = Double(flag("timeout") ?? "") { o.timeout = v / 1000 }
|
|
159
|
+
if let v = Double(flag("stable-ms") ?? "") { o.stableFor = v / 1000 }
|
|
160
|
+
outcome = Watch.wait(baseline: baseline, untilStable: untilStable, options: o, sample: sample,
|
|
161
|
+
now: { ProcessInfo.processInfo.systemUptime },
|
|
162
|
+
sleep: { Thread.sleep(forTimeInterval: $0) })
|
|
130
163
|
}
|
|
131
164
|
guard Vision.capture(region, to: out,
|
|
132
165
|
maxWidth: Int(flag("max-width") ?? ""),
|
|
133
166
|
maxPixels: Int(flag("max-pixels") ?? "")) else {
|
|
134
167
|
fail("capture failed — check Screen Recording permission")
|
|
135
168
|
}
|
|
136
|
-
|
|
169
|
+
var shot: [String: Any] = [
|
|
137
170
|
"ok": true, "captured": true, "path": out,
|
|
138
171
|
"region": ["x": region.minX, "y": region.minY,
|
|
139
172
|
"w": region.width, "h": region.height],
|
|
140
173
|
]
|
|
174
|
+
if let o = outcome {
|
|
175
|
+
shot["changed"] = o.changed
|
|
176
|
+
shot["stable"] = o.stable
|
|
177
|
+
shot["timedOut"] = o.timedOut
|
|
178
|
+
shot["waitedMs"] = Int(o.waited * 1000)
|
|
179
|
+
}
|
|
141
180
|
FileHandle.standardOutput.write(
|
|
142
181
|
(try? JSONSerialization.data(withJSONObject: shot)) ?? Data())
|
|
143
182
|
|
|
183
|
+
case "buffer":
|
|
184
|
+
// Long-lived: keeps the last --seconds of a region in memory, sampled
|
|
185
|
+
// --fps times a second, and writes frames out only when asked on
|
|
186
|
+
// stdin ("dump SECONDS DIR"). Ends at EOF, so it never outlives the
|
|
187
|
+
// daemon that started it.
|
|
188
|
+
Buffer.run(region: captureRegion(),
|
|
189
|
+
fps: Double(flag("fps") ?? "") ?? 2,
|
|
190
|
+
seconds: Double(flag("seconds") ?? "") ?? 10,
|
|
191
|
+
maxPixels: Int(flag("max-pixels") ?? "") ?? 300_000,
|
|
192
|
+
threshold: Double(flag("threshold") ?? "") ?? 0.015)
|
|
193
|
+
|
|
144
194
|
case "screens":
|
|
145
195
|
// Display geometry in top-left coordinates, so a caller can aim at a
|
|
146
196
|
// second monitor without redoing AppKit's origin flip.
|
|
@@ -280,10 +330,14 @@ case "notify":
|
|
|
280
330
|
}
|
|
281
331
|
FileHandle.standardOutput.write(#"{"ok":true,"notified":true}"#.data(using: .utf8)!)
|
|
282
332
|
|
|
333
|
+
case "notify-status":
|
|
334
|
+
// Whether macOS lets the helper post banners. Never prompts or posts.
|
|
335
|
+
FileHandle.standardOutput.write(try! JSONSerialization.data(withJSONObject: Notify.status()))
|
|
336
|
+
|
|
283
337
|
case "trusted":
|
|
284
338
|
let payload = ["ok": true, "trusted": AXTree.trusted()] as [String: Any]
|
|
285
339
|
FileHandle.standardOutput.write(try! JSONSerialization.data(withJSONObject: payload))
|
|
286
340
|
|
|
287
341
|
default:
|
|
288
|
-
fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud, presence, notify or trusted")
|
|
342
|
+
fail("unknown verb \"\(verb)\" — expected read, screens, point, click, type, key, scroll, drag, ocr, capture, hittest, focused, hud, presence, buffer, notify, notify-status or trusted")
|
|
289
343
|
}
|
|
@@ -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-WMI5B36E.js";import"./chunk-GAGVFSZM.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-EXB4HP3B.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-WMI5B36E.js";import"./chunk-GAGVFSZM.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-XMNE3SPH.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-SL657MIM.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-ORYXXL5K.js.map
|