@join-pantheon/cli 0.31.1 → 0.31.2
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/README.md +4 -2
- package/dist/agent-outage.js +87 -0
- package/dist/agents.js +59 -20
- package/dist/claude-confine.js +21 -4
- package/dist/cli.js +254 -53
- package/dist/client.js +3 -3
- package/dist/config.js +1 -0
- package/dist/dbus.js +418 -0
- package/dist/decision-ui.js +89 -0
- package/dist/doctor.js +3 -3
- package/dist/email.js +21 -0
- package/dist/file-lock.js +3 -3
- package/dist/git-safe.js +274 -0
- package/dist/hooks-install.js +6 -1
- package/dist/install.js +21 -0
- package/dist/kiro-files.js +20 -4
- package/dist/mcp-instructions.js +2 -2
- package/dist/nonces.js +5 -3
- package/dist/notice.js +2 -1
- package/dist/notifier.js +1 -1
- package/dist/onboard-defaults.js +18 -0
- package/dist/persona.js +28 -2
- package/dist/plans.js +12 -3
- package/dist/profile.js +23 -5
- package/dist/relay.js +59 -16
- package/dist/roster-file.js +3 -0
- package/dist/run-failure.js +2 -1
- package/dist/safe-write.js +55 -0
- package/dist/setup-guide.js +9 -1
- package/dist/spend.js +65 -5
- package/dist/tray.js +11 -3
- package/dist/worker.js +890 -280
- package/helper/macos/menubar.swift +198 -28
- package/package.json +1 -1
|
@@ -82,6 +82,8 @@ struct Item {
|
|
|
82
82
|
var context: String = ""
|
|
83
83
|
/// a session of this person's has claimed it (handling: an agent is on it, not just queued)
|
|
84
84
|
var claimed: Bool = false
|
|
85
|
+
/// a delivery this person's agent committed to — theirs, not a teammate's request
|
|
86
|
+
var promise: Bool = false
|
|
85
87
|
|
|
86
88
|
var isDecision: Bool { kind == "decision" }
|
|
87
89
|
/// A tray-status from before lanes — or with a lane this helper does not know —
|
|
@@ -104,7 +106,7 @@ struct Item {
|
|
|
104
106
|
// which is the one fact a reader could already guess.
|
|
105
107
|
// Who has it, by where the board put it: a card is on this person; a handled row is an agent's.
|
|
106
108
|
if kind == "owed" {
|
|
107
|
-
parts.append(effectiveLane == "handling" ? (claimed ? "your agent is on it" : "your worker picks it up") : "waiting on you")
|
|
109
|
+
parts.append(promise ? "your agent committed to it" : effectiveLane == "handling" ? (claimed ? "your agent is on it" : "your worker picks it up") : "waiting on you")
|
|
108
110
|
} else if !who.isEmpty { parts.append(who) }
|
|
109
111
|
if when > 0 { parts.append(Item.age(when)) }
|
|
110
112
|
if showSpace && !space.isEmpty { parts.append(space) }
|
|
@@ -186,7 +188,8 @@ func fetchState() -> TrayState {
|
|
|
186
188
|
},
|
|
187
189
|
lane: i["lane"] as? String ?? "",
|
|
188
190
|
context: i["context"] as? String ?? "",
|
|
189
|
-
claimed: i["claimed"] as? Bool ?? false
|
|
191
|
+
claimed: i["claimed"] as? Bool ?? false,
|
|
192
|
+
promise: i["promise"] as? Bool ?? false
|
|
190
193
|
))
|
|
191
194
|
}
|
|
192
195
|
for d in (j["decisions"] as? [[String: Any]] ?? []) {
|
|
@@ -427,6 +430,48 @@ final class HeaderView: NSView {
|
|
|
427
430
|
}
|
|
428
431
|
}
|
|
429
432
|
|
|
433
|
+
/// A team's section on a board that spans several teams: a mark in the team's own
|
|
434
|
+
/// colour, its name, and how much of it needs this person — so each team reads as
|
|
435
|
+
/// its own block, not a suffix on every row.
|
|
436
|
+
final class BoardSpacer: NSView {
|
|
437
|
+
init(height: CGFloat, width: CGFloat) { super.init(frame: NSRect(x: 0, y: 0, width: width, height: height)); autoresizingMask = [.width] }
|
|
438
|
+
required init?(coder: NSCoder) { return nil }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
final class TeamHeaderView: NSView {
|
|
442
|
+
private let name: String
|
|
443
|
+
private let detail: String
|
|
444
|
+
private let tint: NSColor
|
|
445
|
+
init(name: String, detail: String, tint: NSColor, width: CGFloat) {
|
|
446
|
+
self.name = name; self.detail = detail; self.tint = tint
|
|
447
|
+
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 30))
|
|
448
|
+
autoresizingMask = [.width]
|
|
449
|
+
setAccessibilityRole(.staticText)
|
|
450
|
+
setAccessibilityLabel("\(name): \(detail)")
|
|
451
|
+
}
|
|
452
|
+
required init?(coder: NSCoder) { return nil }
|
|
453
|
+
static let tints: [NSColor] = [
|
|
454
|
+
NSColor(calibratedRed: 0.32, green: 0.74, blue: 0.76, alpha: 1),
|
|
455
|
+
NSColor(calibratedRed: 0.85, green: 0.74, blue: 0.42, alpha: 1),
|
|
456
|
+
NSColor(calibratedRed: 0.89, green: 0.57, blue: 0.42, alpha: 1),
|
|
457
|
+
NSColor(calibratedRed: 0.62, green: 0.56, blue: 0.86, alpha: 1),
|
|
458
|
+
NSColor(calibratedRed: 0.52, green: 0.78, blue: 0.52, alpha: 1),
|
|
459
|
+
]
|
|
460
|
+
override func draw(_ dirtyRect: NSRect) {
|
|
461
|
+
let band = NSRect(x: 6, y: bounds.minY + 2, width: bounds.width - 12, height: bounds.height - 4)
|
|
462
|
+
tint.withAlphaComponent(0.10).setFill()
|
|
463
|
+
NSBezierPath(roundedRect: band, xRadius: 6, yRadius: 6).fill()
|
|
464
|
+
tint.setFill()
|
|
465
|
+
NSBezierPath(roundedRect: NSRect(x: 12, y: bounds.midY - 6, width: 4, height: 12), xRadius: 2, yRadius: 2).fill()
|
|
466
|
+
let para = NSMutableParagraphStyle(); para.lineBreakMode = .byTruncatingTail
|
|
467
|
+
NSAttributedString(string: name.uppercased(), attributes: [.font: NSFont.systemFont(ofSize: 11.5, weight: .bold), .kern: 0.6, .foregroundColor: NSColor.labelColor, .paragraphStyle: para])
|
|
468
|
+
.draw(in: NSRect(x: 24, y: bounds.midY - 8, width: bounds.width * 0.6, height: 16))
|
|
469
|
+
let r = NSMutableParagraphStyle(); r.alignment = .right
|
|
470
|
+
NSAttributedString(string: detail, attributes: [.font: NSFont.systemFont(ofSize: 12), .foregroundColor: NSColor.secondaryLabelColor, .paragraphStyle: r])
|
|
471
|
+
.draw(in: NSRect(x: bounds.maxX - 14 - 220, y: bounds.midY - 8, width: 220, height: 16))
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
430
475
|
/*
|
|
431
476
|
* The board: an opaque, dark panel under the icon.
|
|
432
477
|
*
|
|
@@ -643,7 +688,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
643
688
|
let panel = makeBoardPanel()
|
|
644
689
|
boardPanel = panel
|
|
645
690
|
rebuildBoard()
|
|
646
|
-
|
|
691
|
+
var target: NSView? = panel.contentView
|
|
692
|
+
// PANTHEON_TRAY_PANEL=sent draws the first card's detail panel just after a
|
|
693
|
+
// reply was sent — the state that overlapped (2 Oct) — instead of the board.
|
|
694
|
+
if ProcessInfo.processInfo.environment["PANTHEON_TRAY_PANEL"] == "sent", let first = state.items.first(where: { $0.isDecision }) {
|
|
695
|
+
showDetail(first)
|
|
696
|
+
panelReplyManually()
|
|
697
|
+
setStatus("Answered — it is no longer waiting on you.")
|
|
698
|
+
exitReplyMode()
|
|
699
|
+
panelRetract?.isHidden = false
|
|
700
|
+
panelStatusRow?.isHidden = false
|
|
701
|
+
refitPanel()
|
|
702
|
+
target = detailPanel?.contentView
|
|
703
|
+
}
|
|
704
|
+
// PANTHEON_TRAY_PANEL=open draws the first card's panel once its full text has loaded.
|
|
705
|
+
if ProcessInfo.processInfo.environment["PANTHEON_TRAY_PANEL"] == "open", let first = state.items.first(where: { $0.isDecision }) {
|
|
706
|
+
showDetail(first)
|
|
707
|
+
RunLoop.main.run(until: Date().addingTimeInterval(2.5))
|
|
708
|
+
target = detailPanel?.contentView
|
|
709
|
+
}
|
|
710
|
+
guard let view = target, let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds) else { return }
|
|
647
711
|
view.cacheDisplay(in: view.bounds, to: rep)
|
|
648
712
|
try? rep.representation(using: .png, properties: [:])?.write(to: URL(fileURLWithPath: path))
|
|
649
713
|
}
|
|
@@ -709,26 +773,40 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
709
773
|
if !state.relayOk { note("Could not reach: \(state.unavailableSpaces.joined(separator: ", "))") }
|
|
710
774
|
if !state.decisionsOk { note("Some of this could not be checked — the list may be incomplete") }
|
|
711
775
|
sep()
|
|
712
|
-
for it in cards { rows.append(RowView(item: it, width: w, showSpace: multi, onOpen: openRow, onCopy: copyRow)) }
|
|
713
776
|
// The heading already says nothing needs you: said again only what it does not.
|
|
714
777
|
if cards.isEmpty && state.decisionsOk && handling.isEmpty && waiting.isEmpty && older.isEmpty && putAway.isEmpty {
|
|
715
778
|
note("All clear — nothing open for your agents either")
|
|
716
779
|
}
|
|
717
|
-
//
|
|
718
|
-
//
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
780
|
+
// One block per team when there are several (Sid, 2 Oct: "it should
|
|
781
|
+
// differentiate each space — here it's all dumped together"): the team's
|
|
782
|
+
// header in its own colour, its cards, then its own counted lines. One team
|
|
783
|
+
// keeps the plain board. (Plain loops: a local closure capturing both `rows`
|
|
784
|
+
// and a weak self crashed the optimised build.)
|
|
785
|
+
let teams: [String] = multi ? state.spaces : [""]
|
|
786
|
+
for (ti, team) in teams.enumerated() {
|
|
787
|
+
let mine = { (xs: [Item]) in multi ? xs.filter { $0.space == team } : xs }
|
|
788
|
+
let tCards = mine(cards), tHandling = mine(handling), tWaiting = mine(waiting), tOlder = mine(older), tLater = mine(putAway)
|
|
789
|
+
if multi {
|
|
790
|
+
let restCount = tHandling.count + tWaiting.count + tOlder.count + tLater.count
|
|
791
|
+
let detail = !tCards.isEmpty ? "\(tCards.count) need\(tCards.count == 1 ? "s" : "") you" : restCount > 0 ? "nothing needs you" : "all clear"
|
|
792
|
+
if ti > 0 { rows.append(BoardSpacer(height: 6, width: w)) }
|
|
793
|
+
rows.append(TeamHeaderView(name: team, detail: detail, tint: TeamHeaderView.tints[ti % TeamHeaderView.tints.count], width: w))
|
|
794
|
+
}
|
|
795
|
+
for it in tCards { rows.append(RowView(item: it, width: w, showSpace: false, onOpen: openRow, onCopy: copyRow)) }
|
|
796
|
+
let lanes: [(key: String, title: String, items: [Item])] = [
|
|
797
|
+
("handling", "Your agents are handling · \(tHandling.count)", tHandling),
|
|
798
|
+
("waiting", "Waiting on teammates · \(tWaiting.count)", tWaiting),
|
|
799
|
+
("older", "Older than a week · \(tOlder.count)", tOlder),
|
|
800
|
+
("later", "Later · \(tLater.count)", tLater),
|
|
801
|
+
]
|
|
802
|
+
if !multi && lanes.contains(where: { !$0.items.isEmpty }) { sep() }
|
|
803
|
+
for lane in lanes where !lane.items.isEmpty {
|
|
804
|
+
let key = multi ? "\(lane.key)|\(team)" : lane.key
|
|
805
|
+
let open = boardOpen.contains(key)
|
|
806
|
+
rows.append(ActionRow(lane.title, hint: open ? "hide" : "show", width: w, onClick: { [weak self] in self?.toggleBoardLine(key) }))
|
|
807
|
+
// Indented under their line, so an opened lane never reads as more cards.
|
|
808
|
+
if open { for it in lane.items { rows.append(RowView(item: it, width: w, inTeamSection: true, showSpace: false, onOpen: openRow, onCopy: copyRow)) } }
|
|
809
|
+
}
|
|
732
810
|
}
|
|
733
811
|
// The prompt it hands over asks about every question waiting on this person,
|
|
734
812
|
// put away or not, so the count is of all of them.
|
|
@@ -940,6 +1018,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
940
1018
|
private var panelText: NSTextView?
|
|
941
1019
|
private var panelBodyHeight: NSLayoutConstraint?
|
|
942
1020
|
private var panelHeadline: NSTextField?
|
|
1021
|
+
private var panelEyebrow: NSTextField?
|
|
1022
|
+
/// The decision's own stated options ("(a) … (b) …"), as one-click answers.
|
|
1023
|
+
private var panelOptions: NSStackView?
|
|
1024
|
+
private var panelOptionList: [(key: String, text: String)] = []
|
|
943
1025
|
private var panelRendered: NSAttributedString? // the act laid out for a person
|
|
944
1026
|
private var panelRaw: String = "" // the act exactly as it was sent
|
|
945
1027
|
private var panelShowingRaw = false
|
|
@@ -1036,12 +1118,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1036
1118
|
guard let tv = panelText else { return }
|
|
1037
1119
|
tv.textStorage?.setAttributedString(text)
|
|
1038
1120
|
panelBodyHeight?.constant = bodyHeight(text, width: width)
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1121
|
+
refitPanel()
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
/// The panel is measured again whenever a row shows or hides — the reply box,
|
|
1125
|
+
/// the status line, Retract, the buttons coming back. It was sized once, when it
|
|
1126
|
+
/// opened, so after a send the status row and the returning buttons outgrew it
|
|
1127
|
+
/// and AppKit drew them on top of each other (2 Oct). The top edge stays put; past
|
|
1128
|
+
/// the tallest panel, the body gives up height before anything can overlap.
|
|
1129
|
+
private func refitPanel() {
|
|
1130
|
+
guard let p = detailPanel, let v = p.contentView else { return }
|
|
1131
|
+
v.layoutSubtreeIfNeeded()
|
|
1132
|
+
var need = v.fittingSize.height + 30
|
|
1133
|
+
if need > 680, let bh = panelBodyHeight {
|
|
1134
|
+
bh.constant = max(80, bh.constant - (need - 680))
|
|
1135
|
+
v.layoutSubtreeIfNeeded()
|
|
1136
|
+
need = v.fittingSize.height + 30
|
|
1044
1137
|
}
|
|
1138
|
+
let h = min(max(need, 300), 680)
|
|
1139
|
+
let f = p.frame
|
|
1140
|
+
p.setFrame(NSRect(x: f.minX, y: f.maxY - h, width: f.width, height: h), display: true, animate: false)
|
|
1045
1141
|
}
|
|
1046
1142
|
|
|
1047
1143
|
/**
|
|
@@ -1052,7 +1148,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1052
1148
|
private func loadDetail(_ item: Item, width: CGFloat) {
|
|
1053
1149
|
guard !item.ref.isEmpty else { return }
|
|
1054
1150
|
DispatchQueue.global().async {
|
|
1055
|
-
|
|
1151
|
+
// The item's own team, so a decision in another team is found and shown whole.
|
|
1152
|
+
let r = run(pantheonBin, ["tray-detail", item.ref] + (item.space.isEmpty ? [] : ["--space", item.space]), timeout: 20)
|
|
1056
1153
|
guard let out = r?.out, r?.code == 0,
|
|
1057
1154
|
let data = out.data(using: .utf8),
|
|
1058
1155
|
let j = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
@@ -1061,8 +1158,34 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1061
1158
|
let rendered = self.render(blocks)
|
|
1062
1159
|
let raw = j["raw"] as? String ?? ""
|
|
1063
1160
|
let headline = j["headline"] as? String ?? ""
|
|
1161
|
+
let asker = j["asker"] as? String ?? ""
|
|
1162
|
+
let options: [(key: String, text: String)] = (j["options"] as? [[String: Any]] ?? []).compactMap { o in
|
|
1163
|
+
guard let k = o["key"] as? String, let t = o["text"] as? String else { return nil }
|
|
1164
|
+
return (key: k, text: t)
|
|
1165
|
+
}
|
|
1064
1166
|
DispatchQueue.main.async {
|
|
1065
1167
|
guard self.panelItem?.id == item.id else { return }
|
|
1168
|
+
// Who is asking, said plainly: "from your worker", never "sid escalated" to sid.
|
|
1169
|
+
if !asker.isEmpty, let eye = self.panelEyebrow, !eye.stringValue.contains("FROM ") { eye.stringValue += " · FROM " + asker.uppercased() }
|
|
1170
|
+
if item.isDecision && item.choices.isEmpty && options.count >= 2, let box = self.panelOptions {
|
|
1171
|
+
self.panelOptionList = options
|
|
1172
|
+
box.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
|
1173
|
+
let note = NSTextField(labelWithString: "Answer with one of its options:")
|
|
1174
|
+
note.font = .systemFont(ofSize: 12, weight: .medium)
|
|
1175
|
+
note.textColor = .secondaryLabelColor
|
|
1176
|
+
box.addArrangedSubview(note)
|
|
1177
|
+
for (n, o) in options.enumerated() {
|
|
1178
|
+
let title = "(\(o.key)) " + (o.text.count > 78 ? String(o.text.prefix(77)) + "…" : o.text)
|
|
1179
|
+
let b = NSButton(title: title, target: self, action: #selector(self.panelChooseOption(_:)))
|
|
1180
|
+
b.bezelStyle = .rounded
|
|
1181
|
+
b.tag = n
|
|
1182
|
+
b.keyEquivalent = ""
|
|
1183
|
+
b.toolTip = "Sends \"(\(o.key)) \(o.text)\" as your answer"
|
|
1184
|
+
box.addArrangedSubview(b)
|
|
1185
|
+
}
|
|
1186
|
+
box.isHidden = false
|
|
1187
|
+
self.refitPanel()
|
|
1188
|
+
}
|
|
1066
1189
|
self.panelRendered = rendered
|
|
1067
1190
|
self.panelRaw = raw
|
|
1068
1191
|
self.panelRawButton?.isHidden = raw.isEmpty
|
|
@@ -1119,6 +1242,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1119
1242
|
if item.when > 0 { eyebrow += " · " + Item.age(item.when) }
|
|
1120
1243
|
if !item.space.isEmpty { eyebrow += " · " + item.space }
|
|
1121
1244
|
let eye = label(eyebrow, size: 11, weight: .semibold, color: .secondaryLabelColor, width: inner)
|
|
1245
|
+
panelEyebrow = eye
|
|
1122
1246
|
stack.addArrangedSubview(eye)
|
|
1123
1247
|
let headline = label(item.headline, size: 17, weight: .semibold, color: .labelColor, width: inner)
|
|
1124
1248
|
panelHeadline = headline
|
|
@@ -1275,6 +1399,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1275
1399
|
rows.spacing = 8
|
|
1276
1400
|
rows.translatesAutoresizingMaskIntoConstraints = false
|
|
1277
1401
|
|
|
1402
|
+
// The decision's own options, filled in once its full text has loaded.
|
|
1403
|
+
if item.isDecision && item.choices.isEmpty {
|
|
1404
|
+
let opts = NSStackView()
|
|
1405
|
+
opts.orientation = .vertical
|
|
1406
|
+
opts.alignment = .leading
|
|
1407
|
+
opts.spacing = 6
|
|
1408
|
+
opts.isHidden = true
|
|
1409
|
+
panelOptions = opts
|
|
1410
|
+
rows.addArrangedSubview(opts)
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1278
1413
|
// A question this machine asked its own person: answered in one click, here.
|
|
1279
1414
|
if item.isDecision && !item.choices.isEmpty {
|
|
1280
1415
|
let row0 = NSStackView()
|
|
@@ -1360,6 +1495,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1360
1495
|
panelStatus?.stringValue = text
|
|
1361
1496
|
panelStatus?.isHidden = text.isEmpty
|
|
1362
1497
|
panelStatusRow?.isHidden = text.isEmpty && (panelRetract?.isHidden ?? true)
|
|
1498
|
+
refitPanel()
|
|
1363
1499
|
}
|
|
1364
1500
|
|
|
1365
1501
|
@objc private func panelClose() {
|
|
@@ -1380,6 +1516,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1380
1516
|
panelReplyKind = nil
|
|
1381
1517
|
panelSendButton = nil
|
|
1382
1518
|
panelRetract = nil
|
|
1519
|
+
panelEyebrow = nil
|
|
1520
|
+
panelOptions = nil
|
|
1521
|
+
panelOptionList = []
|
|
1383
1522
|
panelLastSent = ""
|
|
1384
1523
|
panelRaw = ""
|
|
1385
1524
|
panelShowingRaw = false
|
|
@@ -1405,7 +1544,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1405
1544
|
panelReplyRow?.isHidden = false
|
|
1406
1545
|
if let c = panelBodyHeight, c.constant > 140 { c.constant -= 20 }
|
|
1407
1546
|
panelRetract?.isHidden = true
|
|
1408
|
-
setStatus("")
|
|
1547
|
+
setStatus("") // refits
|
|
1409
1548
|
if let tv = panelReply { detailPanel?.makeFirstResponder(tv) }
|
|
1410
1549
|
}
|
|
1411
1550
|
@objc private func panelCancelReply() { exitReplyMode() }
|
|
@@ -1416,6 +1555,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1416
1555
|
panelReplyButton?.isHidden = false
|
|
1417
1556
|
panelReplyButton?.keyEquivalent = "\r"
|
|
1418
1557
|
if let c = panelBodyHeight, c.constant > 0 { c.constant += 20 }
|
|
1558
|
+
refitPanel()
|
|
1419
1559
|
}
|
|
1420
1560
|
|
|
1421
1561
|
/// What a person may send on this item. Answer only on their own decision:
|
|
@@ -1452,6 +1592,30 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1452
1592
|
}
|
|
1453
1593
|
}
|
|
1454
1594
|
|
|
1595
|
+
/// One of the decision's stated options, sent as its ANSWER — the same local path a typed answer takes.
|
|
1596
|
+
@objc private func panelChooseOption(_ sender: NSButton) {
|
|
1597
|
+
guard let item = panelItem, sender.tag >= 0, sender.tag < panelOptionList.count else { return }
|
|
1598
|
+
let o = panelOptionList[sender.tag]
|
|
1599
|
+
var payload: [String: Any] = ["action": "reply", "kind": "ANSWER", "body": "(\(o.key)) \(o.text)", "goal": item.goal, "space": item.space, "decision": item.id]
|
|
1600
|
+
if !item.ref.isEmpty { payload["act"] = item.ref }
|
|
1601
|
+
guard writeAction(payload) else { setStatus("Could not write the action. Use Reply manually instead."); return }
|
|
1602
|
+
setStatus("Answering…")
|
|
1603
|
+
panelOptions?.arrangedSubviews.forEach { ($0 as? NSButton)?.isEnabled = false }
|
|
1604
|
+
DispatchQueue.global().async {
|
|
1605
|
+
let r = run(pantheonBin, ["explain", "--queued"], timeout: 25)
|
|
1606
|
+
DispatchQueue.main.async {
|
|
1607
|
+
let out = (r?.out ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1608
|
+
if r?.code == 0 {
|
|
1609
|
+
self.setStatus("Answered (\(o.key)) — it is no longer waiting on you.")
|
|
1610
|
+
self.refresh()
|
|
1611
|
+
} else {
|
|
1612
|
+
self.panelOptions?.arrangedSubviews.forEach { ($0 as? NSButton)?.isEnabled = true }
|
|
1613
|
+
self.setStatus(out.hasPrefix("not sent: ") ? String(out.dropFirst("not sent: ".count)) : "the relay could not be reached")
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1455
1619
|
/// Send the words in the box as the chosen act. The CLI does the sending and
|
|
1456
1620
|
/// prints "sent KIND id=… seq=…" or "not sent: why" on stdout; both are shown.
|
|
1457
1621
|
@objc private func panelSend(_ sender: Any?) {
|
|
@@ -1481,7 +1645,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1481
1645
|
self.setStatus(what)
|
|
1482
1646
|
self.exitReplyMode()
|
|
1483
1647
|
self.panelRetract?.isHidden = false
|
|
1484
|
-
|
|
1648
|
+
self.panelStatusRow?.isHidden = false
|
|
1649
|
+
self.refitPanel()
|
|
1650
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in
|
|
1651
|
+
self?.panelRetract?.isHidden = true
|
|
1652
|
+
self?.panelStatusRow?.isHidden = (self?.panelStatus?.stringValue.isEmpty ?? true)
|
|
1653
|
+
self?.refitPanel()
|
|
1654
|
+
}
|
|
1485
1655
|
self.refresh()
|
|
1486
1656
|
} else {
|
|
1487
1657
|
let why = out.hasPrefix("not sent: ") ? String(out.dropFirst("not sent: ".count)) : "the relay could not be reached"
|
|
@@ -1507,7 +1677,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1507
1677
|
if r?.code == 0 {
|
|
1508
1678
|
self.panelLastSent = ""
|
|
1509
1679
|
self.panelRetract?.isHidden = true
|
|
1510
|
-
self.setStatus("Retracted.")
|
|
1680
|
+
self.setStatus("Retracted.") // refits
|
|
1511
1681
|
self.refresh()
|
|
1512
1682
|
} else {
|
|
1513
1683
|
let out = (r?.out ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.2",
|
|
4
4
|
"description": "Let your coding agent collaborate directly with your teammates' agents — ask, agree an interface, and deliver, without a human relaying messages.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|