@join-pantheon/cli 0.26.4 → 0.27.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/README.md +54 -1
- package/dist/agents.js +83 -5
- package/dist/cli.js +105 -14
- package/dist/doctor.js +21 -6
- package/dist/history-view.js +2 -1
- package/dist/hooks-install.js +46 -1
- package/dist/install.js +29 -4
- package/dist/kiro-files.js +56 -0
- package/dist/ledger.js +3 -0
- package/dist/machine.js +5 -1
- package/dist/mcp.js +1 -0
- package/dist/measure.js +31 -3
- package/dist/notice.js +3 -1
- package/dist/notify.js +15 -3
- package/dist/persona.js +1 -1
- package/dist/profile.js +4 -1
- package/dist/relay.js +8 -3
- package/dist/run-failure.js +1 -0
- package/dist/setup-guide.js +1 -1
- package/dist/tray.js +3 -3
- package/dist/workdir.js +30 -0
- package/dist/worker.js +4 -1
- package/helper/macos/menubar.swift +286 -38
- package/package.json +1 -1
|
@@ -68,6 +68,8 @@ struct Item {
|
|
|
68
68
|
let goal: String
|
|
69
69
|
let ref: String
|
|
70
70
|
let space: String
|
|
71
|
+
/// whose human must decide this: this member for their own decision, the teammate for theirs, "" when it is not a decision
|
|
72
|
+
let decider: String
|
|
71
73
|
|
|
72
74
|
var isDecision: Bool { kind == "decision" }
|
|
73
75
|
var heading: String {
|
|
@@ -152,7 +154,8 @@ func fetchState() -> TrayState {
|
|
|
152
154
|
when: (i["when"] as? Double) ?? Double(i["when"] as? Int ?? 0),
|
|
153
155
|
goal: i["goal"] as? String ?? "",
|
|
154
156
|
ref: i["ref"] as? String ?? "",
|
|
155
|
-
space: i["space"] as? String ?? ""
|
|
157
|
+
space: i["space"] as? String ?? "",
|
|
158
|
+
decider: i["decider"] as? String ?? ""
|
|
156
159
|
))
|
|
157
160
|
}
|
|
158
161
|
for d in (j["decisions"] as? [[String: Any]] ?? []) {
|
|
@@ -840,7 +843,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
840
843
|
private var panelRendered: NSAttributedString? // the act laid out for a person
|
|
841
844
|
private var panelRaw: String = "" // the act exactly as it was sent
|
|
842
845
|
private var panelShowingRaw = false
|
|
843
|
-
private var
|
|
846
|
+
private var panelRawButton: NSButton? // "Original text" / "Laid out", shown only when the two differ
|
|
847
|
+
private var panelButtons: NSStackView? // the action row; hidden while replying
|
|
848
|
+
private var panelReplyRow: NSStackView? // the reply box; shown only in reply mode
|
|
849
|
+
private var panelCancel: NSButton?
|
|
850
|
+
private var panelReplyButton: NSButton? // the primary action, hidden while replying
|
|
851
|
+
private var panelStatusRow: NSStackView? // collapses when there is nothing to say
|
|
852
|
+
private var panelReply: NSTextView? // the person's own words
|
|
853
|
+
private var panelReplyKind: NSPopUpButton? // Answer / Note / Ask back
|
|
854
|
+
private var panelSendButton: NSButton?
|
|
855
|
+
private var panelRetract: NSButton? // the undo, for a minute after a send
|
|
856
|
+
private var panelLastSent: String = "" // act id of what was just sent
|
|
844
857
|
|
|
845
858
|
private func label(_ text: String, size: CGFloat, weight: NSFont.Weight, color: NSColor, width: CGFloat) -> NSTextField {
|
|
846
859
|
let l = NSTextField(labelWithString: text)
|
|
@@ -951,7 +964,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
951
964
|
guard self.panelItem?.id == item.id else { return }
|
|
952
965
|
self.panelRendered = rendered
|
|
953
966
|
self.panelRaw = raw
|
|
954
|
-
self.
|
|
967
|
+
self.panelRawButton?.isHidden = raw.isEmpty
|
|
955
968
|
if !headline.isEmpty { self.panelHeadline?.stringValue = headline }
|
|
956
969
|
if !self.panelShowingRaw && rendered.length > 0 { self.showBody(rendered, width: width) }
|
|
957
970
|
}
|
|
@@ -962,7 +975,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
962
975
|
@objc private func panelToggleRaw() {
|
|
963
976
|
guard let item = panelItem else { return }
|
|
964
977
|
panelShowingRaw.toggle()
|
|
965
|
-
|
|
978
|
+
panelRawButton?.title = panelShowingRaw ? "Laid out" : "Original text"
|
|
966
979
|
let width: CGFloat = 620 - 52
|
|
967
980
|
if panelShowingRaw { showBody(plain(panelRaw.isEmpty ? item.detail : panelRaw), width: width) }
|
|
968
981
|
else if let r = panelRendered { showBody(r, width: width) }
|
|
@@ -1037,42 +1050,161 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1037
1050
|
if !item.goal.isEmpty { meta += " · goal \(item.goal)" }
|
|
1038
1051
|
stack.addArrangedSubview(label(meta, size: 11, weight: .regular, color: .tertiaryLabelColor, width: inner))
|
|
1039
1052
|
|
|
1040
|
-
|
|
1053
|
+
// Whose decision this is, said before the box that answers it. Your own
|
|
1054
|
+
// answer to a teammate's decision lands as an act and settles nothing, so the
|
|
1055
|
+
// panel offers Answer only to the person it is waiting on.
|
|
1056
|
+
let owner: String = item.isDecision ? "Yours to decide."
|
|
1057
|
+
: !item.decider.isEmpty ? "\(item.decider)'s to decide. You can add a note or ask back."
|
|
1058
|
+
: item.kind == "owed" ? "Your agent owes this. You can add a note or ask back."
|
|
1059
|
+
: "Waiting on \(item.who). You can add a note or ask back."
|
|
1060
|
+
let ownerRow = NSStackView()
|
|
1061
|
+
ownerRow.orientation = .horizontal
|
|
1062
|
+
ownerRow.alignment = .centerY
|
|
1063
|
+
ownerRow.spacing = 10
|
|
1064
|
+
ownerRow.translatesAutoresizingMaskIntoConstraints = false
|
|
1065
|
+
let ownerLabel = label(owner, size: 12, weight: .medium, color: .secondaryLabelColor, width: inner - 170)
|
|
1066
|
+
ownerLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1067
|
+
ownerRow.addArrangedSubview(ownerLabel)
|
|
1068
|
+
let ownerSpacer = NSView()
|
|
1069
|
+
ownerSpacer.translatesAutoresizingMaskIntoConstraints = false
|
|
1070
|
+
ownerSpacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1071
|
+
ownerRow.addArrangedSubview(ownerSpacer)
|
|
1072
|
+
// The one action that is the person's own voice, set apart from the rest and
|
|
1073
|
+
// reachable with Return. Pressing it only opens the box; nothing is sent.
|
|
1074
|
+
let replyBtn = NSButton(title: "Reply manually", target: self, action: #selector(panelReplyManually))
|
|
1075
|
+
replyBtn.bezelStyle = .rounded
|
|
1076
|
+
replyBtn.keyEquivalent = "\r"
|
|
1077
|
+
replyBtn.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
1078
|
+
replyBtn.toolTip = "Type your own answer, note or question. Nothing is drafted for you, and nothing is sent until you press Send."
|
|
1079
|
+
panelReplyButton = replyBtn
|
|
1080
|
+
ownerRow.addArrangedSubview(replyBtn)
|
|
1081
|
+
ownerRow.widthAnchor.constraint(equalToConstant: inner).isActive = true
|
|
1082
|
+
if let idLine = stack.arrangedSubviews.last { stack.setCustomSpacing(6, after: idLine) }
|
|
1083
|
+
stack.addArrangedSubview(ownerRow)
|
|
1084
|
+
// Clear air between the person's own action and the rest: a rule, with room
|
|
1085
|
+
// on both sides, so the blue button is not read as part of the row below.
|
|
1086
|
+
stack.setCustomSpacing(22, after: ownerRow)
|
|
1087
|
+
let rule = NSBox()
|
|
1088
|
+
rule.boxType = .separator
|
|
1089
|
+
rule.translatesAutoresizingMaskIntoConstraints = false
|
|
1090
|
+
rule.widthAnchor.constraint(equalToConstant: inner).isActive = true
|
|
1091
|
+
stack.addArrangedSubview(rule)
|
|
1092
|
+
stack.setCustomSpacing(22, after: rule)
|
|
1093
|
+
|
|
1094
|
+
// The reply, in the person's own words: one popup for what it is, the box,
|
|
1095
|
+
// one Send. Return sends; Shift-Return makes a new line.
|
|
1096
|
+
let replyRow = NSStackView()
|
|
1097
|
+
replyRow.orientation = .horizontal
|
|
1098
|
+
replyRow.alignment = .top
|
|
1099
|
+
replyRow.spacing = 8
|
|
1100
|
+
replyRow.translatesAutoresizingMaskIntoConstraints = false
|
|
1101
|
+
let kinds = NSPopUpButton(frame: .zero, pullsDown: false)
|
|
1102
|
+
for k in replyKinds(item) { kinds.addItem(withTitle: k) }
|
|
1103
|
+
kinds.toolTip = "Answer settles it and the agent continues. Note goes on the record and settles nothing. Ask back puts a question to the agent that raised it."
|
|
1104
|
+
kinds.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
1105
|
+
panelReplyKind = kinds
|
|
1106
|
+
replyRow.addArrangedSubview(kinds)
|
|
1107
|
+
let replyScroll = NSScrollView()
|
|
1108
|
+
replyScroll.hasVerticalScroller = false
|
|
1109
|
+
replyScroll.borderType = .bezelBorder
|
|
1110
|
+
replyScroll.translatesAutoresizingMaskIntoConstraints = false
|
|
1111
|
+
let reply = NSTextView()
|
|
1112
|
+
reply.isEditable = true
|
|
1113
|
+
reply.isRichText = false
|
|
1114
|
+
reply.font = .systemFont(ofSize: 13)
|
|
1115
|
+
reply.textContainerInset = NSSize(width: 4, height: 6)
|
|
1116
|
+
reply.isVerticallyResizable = true
|
|
1117
|
+
reply.isHorizontallyResizable = false
|
|
1118
|
+
reply.autoresizingMask = [.width]
|
|
1119
|
+
reply.textContainer?.widthTracksTextView = true
|
|
1120
|
+
reply.delegate = self
|
|
1121
|
+
replyScroll.documentView = reply
|
|
1122
|
+
replyScroll.heightAnchor.constraint(equalToConstant: 58).isActive = true
|
|
1123
|
+
replyScroll.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1124
|
+
panelReply = reply
|
|
1125
|
+
replyRow.addArrangedSubview(replyScroll)
|
|
1126
|
+
let send = NSButton(title: "Send", target: self, action: #selector(panelSend(_:)))
|
|
1127
|
+
send.bezelStyle = .rounded
|
|
1128
|
+
send.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
1129
|
+
panelSendButton = send
|
|
1130
|
+
replyRow.addArrangedSubview(send)
|
|
1131
|
+
let cancel = NSButton(title: "Cancel", target: self, action: #selector(panelCancelReply))
|
|
1132
|
+
cancel.bezelStyle = .rounded
|
|
1133
|
+
cancel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
|
1134
|
+
cancel.toolTip = "Back to the buttons. Your words stay in the box."
|
|
1135
|
+
panelCancel = cancel
|
|
1136
|
+
replyRow.addArrangedSubview(cancel)
|
|
1137
|
+
replyRow.widthAnchor.constraint(equalToConstant: inner).isActive = true
|
|
1138
|
+
panelReplyRow = replyRow
|
|
1139
|
+
stack.addArrangedSubview(replyRow)
|
|
1140
|
+
replyRow.isHidden = true
|
|
1141
|
+
|
|
1142
|
+
// What happened, and the undo for a slip: Retract shows for a minute after a send.
|
|
1143
|
+
let statusRow = NSStackView()
|
|
1144
|
+
statusRow.orientation = .horizontal
|
|
1145
|
+
statusRow.spacing = 10
|
|
1146
|
+
statusRow.translatesAutoresizingMaskIntoConstraints = false
|
|
1147
|
+
let status = label("", size: 11, weight: .medium, color: .secondaryLabelColor, width: inner - 90)
|
|
1041
1148
|
status.isHidden = true
|
|
1042
1149
|
panelStatus = status
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1150
|
+
statusRow.addArrangedSubview(status)
|
|
1151
|
+
let retract = NSButton(title: "Retract", target: self, action: #selector(panelRetractAction))
|
|
1152
|
+
retract.bezelStyle = .rounded
|
|
1153
|
+
retract.controlSize = .small
|
|
1154
|
+
retract.toolTip = "Withdraws what you just sent. Your words stay in the record as retracted."
|
|
1155
|
+
retract.isHidden = true
|
|
1156
|
+
panelRetract = retract
|
|
1157
|
+
statusRow.addArrangedSubview(retract)
|
|
1158
|
+
panelStatusRow = statusRow
|
|
1159
|
+
stack.addArrangedSubview(statusRow)
|
|
1160
|
+
statusRow.isHidden = true
|
|
1161
|
+
|
|
1162
|
+
// Two rows: what to do about it, then what to do with the text. Every
|
|
1163
|
+
// button refuses to compress, so the labels are always whole.
|
|
1164
|
+
func button(_ title: String, _ sel: Selector, _ tip: String) -> NSButton {
|
|
1165
|
+
let b = NSButton(title: title, target: self, action: sel)
|
|
1166
|
+
b.bezelStyle = .rounded
|
|
1167
|
+
b.toolTip = tip
|
|
1168
|
+
b.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
1169
|
+
return b
|
|
1170
|
+
}
|
|
1171
|
+
let rows = NSStackView()
|
|
1172
|
+
rows.orientation = .vertical
|
|
1173
|
+
rows.alignment = .leading
|
|
1174
|
+
rows.spacing = 8
|
|
1175
|
+
rows.translatesAutoresizingMaskIntoConstraints = false
|
|
1176
|
+
|
|
1177
|
+
let row1 = NSStackView()
|
|
1178
|
+
row1.orientation = .horizontal
|
|
1179
|
+
row1.spacing = 10
|
|
1180
|
+
row1.addArrangedSubview(button("Explain in \(state.surfaceName)", #selector(panelOpen),
|
|
1181
|
+
"Opens \(state.surfaceName) in the team's folder and asks it to lay out your options. It will not decide or reply for you."))
|
|
1056
1182
|
if item.isDecision {
|
|
1057
|
-
|
|
1058
|
-
done.bezelStyle = .rounded
|
|
1059
|
-
done.toolTip = "Answer it as handled, so it stops asking"
|
|
1060
|
-
row.addArrangedSubview(done)
|
|
1183
|
+
row1.addArrangedSubview(button("Mark done", #selector(panelDone), "Tells the agent you settled this in person, so it stops asking."))
|
|
1061
1184
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
let
|
|
1066
|
-
|
|
1067
|
-
|
|
1185
|
+
row1.addArrangedSubview(button("Show the thread", #selector(panelThread), "Every act in this goal, in Terminal, from the team's folder."))
|
|
1186
|
+
rows.addArrangedSubview(row1)
|
|
1187
|
+
|
|
1188
|
+
let row2 = NSStackView()
|
|
1189
|
+
row2.orientation = .horizontal
|
|
1190
|
+
row2.spacing = 10
|
|
1191
|
+
row2.addArrangedSubview(button("Copy the question", #selector(panelCopy), "Copies the question and its context as text, for an email or a message."))
|
|
1192
|
+
let rawToggle = button("Original text", #selector(panelToggleRaw), "Exactly as the agent wrote it, before Pantheon laid it out.")
|
|
1068
1193
|
rawToggle.isHidden = true
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
let
|
|
1072
|
-
|
|
1194
|
+
panelRawButton = rawToggle
|
|
1195
|
+
row2.addArrangedSubview(rawToggle)
|
|
1196
|
+
let spacer = NSView()
|
|
1197
|
+
spacer.translatesAutoresizingMaskIntoConstraints = false
|
|
1198
|
+
spacer.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
1199
|
+
row2.addArrangedSubview(spacer)
|
|
1200
|
+
let close = button("Close", #selector(panelClose), "Nothing changes.")
|
|
1073
1201
|
close.keyEquivalent = "\u{1b}"
|
|
1074
|
-
|
|
1075
|
-
|
|
1202
|
+
row2.addArrangedSubview(close)
|
|
1203
|
+
row2.widthAnchor.constraint(equalToConstant: inner).isActive = true
|
|
1204
|
+
rows.addArrangedSubview(row2)
|
|
1205
|
+
|
|
1206
|
+
panelButtons = rows
|
|
1207
|
+
stack.addArrangedSubview(rows)
|
|
1076
1208
|
|
|
1077
1209
|
NSLayoutConstraint.activate([
|
|
1078
1210
|
stack.leadingAnchor.constraint(equalTo: effect.leadingAnchor, constant: 26),
|
|
@@ -1080,7 +1212,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1080
1212
|
stack.topAnchor.constraint(equalTo: effect.topAnchor, constant: 36),
|
|
1081
1213
|
stack.bottomAnchor.constraint(equalTo: effect.bottomAnchor, constant: -22),
|
|
1082
1214
|
scroll.widthAnchor.constraint(equalTo: stack.widthAnchor),
|
|
1083
|
-
|
|
1215
|
+
rows.widthAnchor.constraint(lessThanOrEqualTo: stack.widthAnchor),
|
|
1084
1216
|
])
|
|
1085
1217
|
let bh = scroll.heightAnchor.constraint(equalToConstant: bodyHeight(plain(item.detail), width: inner))
|
|
1086
1218
|
bh.isActive = true
|
|
@@ -1104,6 +1236,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1104
1236
|
private func setStatus(_ text: String) {
|
|
1105
1237
|
panelStatus?.stringValue = text
|
|
1106
1238
|
panelStatus?.isHidden = text.isEmpty
|
|
1239
|
+
panelStatusRow?.isHidden = text.isEmpty && (panelRetract?.isHidden ?? true)
|
|
1107
1240
|
}
|
|
1108
1241
|
|
|
1109
1242
|
@objc private func panelClose() {
|
|
@@ -1114,14 +1247,120 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
1114
1247
|
panelItem = nil
|
|
1115
1248
|
panelHeadline = nil
|
|
1116
1249
|
panelRendered = nil
|
|
1117
|
-
|
|
1250
|
+
panelRawButton = nil
|
|
1251
|
+
panelButtons = nil
|
|
1252
|
+
panelReplyRow = nil
|
|
1253
|
+
panelCancel = nil
|
|
1254
|
+
panelReplyButton = nil
|
|
1255
|
+
panelStatusRow = nil
|
|
1256
|
+
panelReply = nil
|
|
1257
|
+
panelReplyKind = nil
|
|
1258
|
+
panelSendButton = nil
|
|
1259
|
+
panelRetract = nil
|
|
1260
|
+
panelLastSent = ""
|
|
1118
1261
|
panelRaw = ""
|
|
1119
1262
|
panelShowingRaw = false
|
|
1120
1263
|
}
|
|
1264
|
+
/// The question as text — what a person pasting into an email or a message
|
|
1265
|
+
/// wants. The agent prompt still rides with Explain, which copies it as a
|
|
1266
|
+
/// fallback when the agent cannot be opened.
|
|
1121
1267
|
@objc private func panelCopy() {
|
|
1122
1268
|
guard let item = panelItem else { return }
|
|
1123
|
-
|
|
1124
|
-
|
|
1269
|
+
let pb = NSPasteboard.general
|
|
1270
|
+
pb.clearContents()
|
|
1271
|
+
pb.setString(item.headline + "\n\n" + plain(panelRaw.isEmpty ? item.detail : panelRaw).string, forType: .string)
|
|
1272
|
+
setStatus("Copied the question.")
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/// Reply mode: the buttons step aside and only the box remains, with Cancel to
|
|
1276
|
+
/// bring them back. The body gives up a little height so the panel stays put.
|
|
1277
|
+
@objc private func panelReplyManually() {
|
|
1278
|
+
panelButtons?.isHidden = true
|
|
1279
|
+
panelReplyButton?.isHidden = true
|
|
1280
|
+
panelReplyButton?.keyEquivalent = ""
|
|
1281
|
+
panelSendButton?.keyEquivalent = "\r"
|
|
1282
|
+
panelReplyRow?.isHidden = false
|
|
1283
|
+
if let c = panelBodyHeight, c.constant > 140 { c.constant -= 20 }
|
|
1284
|
+
panelRetract?.isHidden = true
|
|
1285
|
+
setStatus("")
|
|
1286
|
+
if let tv = panelReply { detailPanel?.makeFirstResponder(tv) }
|
|
1287
|
+
}
|
|
1288
|
+
@objc private func panelCancelReply() { exitReplyMode() }
|
|
1289
|
+
private func exitReplyMode() {
|
|
1290
|
+
panelReplyRow?.isHidden = true
|
|
1291
|
+
panelSendButton?.keyEquivalent = ""
|
|
1292
|
+
panelButtons?.isHidden = false
|
|
1293
|
+
panelReplyButton?.isHidden = false
|
|
1294
|
+
panelReplyButton?.keyEquivalent = "\r"
|
|
1295
|
+
if let c = panelBodyHeight, c.constant > 0 { c.constant += 20 }
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/// What a person may send on this item. Answer only on their own decision:
|
|
1299
|
+
/// anyone else's answer would land as an act and settle nothing.
|
|
1300
|
+
private func replyKinds(_ item: Item) -> [String] {
|
|
1301
|
+
item.isDecision ? ["Answer", "Note", "Ask back"] : ["Note", "Ask back"]
|
|
1302
|
+
}
|
|
1303
|
+
private func kindCode(_ title: String?) -> String {
|
|
1304
|
+
switch title { case "Answer": return "ANSWER"; case "Ask back": return "ASK"; default: return "NOTE" }
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/// Send the words in the box as the chosen act. The CLI does the sending and
|
|
1308
|
+
/// prints "sent KIND id=… seq=…" or "not sent: why" on stdout; both are shown.
|
|
1309
|
+
@objc private func panelSend(_ sender: Any?) {
|
|
1310
|
+
guard let item = panelItem, let tv = panelReply else { return }
|
|
1311
|
+
let text = tv.string.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1312
|
+
if text.isEmpty { setStatus("Type your reply first."); return }
|
|
1313
|
+
let kind = kindCode(panelReplyKind?.titleOfSelectedItem)
|
|
1314
|
+
var payload: [String: Any] = ["action": "reply", "kind": kind, "body": text, "goal": item.goal, "space": item.space]
|
|
1315
|
+
if item.isDecision { payload["decision"] = item.id }
|
|
1316
|
+
if !item.ref.isEmpty { payload["act"] = item.ref }
|
|
1317
|
+
guard writeAction(payload) else { setStatus("Could not write the action. Use Show the thread instead."); return }
|
|
1318
|
+
setStatus("Sending…")
|
|
1319
|
+
panelSendButton?.isEnabled = false
|
|
1320
|
+
DispatchQueue.global().async {
|
|
1321
|
+
let r = run(pantheonBin, ["explain", "--queued"], timeout: 25)
|
|
1322
|
+
DispatchQueue.main.async {
|
|
1323
|
+
self.panelSendButton?.isEnabled = true
|
|
1324
|
+
let out = (r?.out ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1325
|
+
if r?.code == 0, let idTok = out.split(separator: " ").first(where: { $0.hasPrefix("id=") }) {
|
|
1326
|
+
self.panelLastSent = String(idTok.dropFirst(3))
|
|
1327
|
+
tv.string = ""
|
|
1328
|
+
let what = kind == "ANSWER" ? "Answered — it is no longer waiting on you." : kind == "ASK" ? "Asked — the agent that raised this owes you a reply." : "Noted — it stays waiting."
|
|
1329
|
+
self.setStatus(what)
|
|
1330
|
+
self.exitReplyMode()
|
|
1331
|
+
self.panelRetract?.isHidden = false
|
|
1332
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 60) { [weak self] in self?.panelRetract?.isHidden = true }
|
|
1333
|
+
self.refresh()
|
|
1334
|
+
} else {
|
|
1335
|
+
let why = out.hasPrefix("not sent: ") ? String(out.dropFirst("not sent: ".count)) : "the relay could not be reached"
|
|
1336
|
+
self.setStatus("Not sent — \(why) Your words are still in the box.")
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/// The undo. RETRACT withdraws the act; the record keeps it as retracted.
|
|
1343
|
+
@objc private func panelRetractAction() {
|
|
1344
|
+
guard !panelLastSent.isEmpty else { return }
|
|
1345
|
+
let id = panelLastSent
|
|
1346
|
+
guard writeAction(["action": "retract", "act": id]) else { setStatus("Could not write the action."); return }
|
|
1347
|
+
setStatus("Retracting…")
|
|
1348
|
+
panelRetract?.isEnabled = false
|
|
1349
|
+
DispatchQueue.global().async {
|
|
1350
|
+
let r = run(pantheonBin, ["explain", "--queued"], timeout: 25)
|
|
1351
|
+
DispatchQueue.main.async {
|
|
1352
|
+
self.panelRetract?.isEnabled = true
|
|
1353
|
+
if r?.code == 0 {
|
|
1354
|
+
self.panelLastSent = ""
|
|
1355
|
+
self.panelRetract?.isHidden = true
|
|
1356
|
+
self.setStatus("Retracted.")
|
|
1357
|
+
self.refresh()
|
|
1358
|
+
} else {
|
|
1359
|
+
let out = (r?.out ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1360
|
+
self.setStatus("Could not retract — \(out.hasPrefix("not sent: ") ? String(out.dropFirst("not sent: ".count)) : "the relay could not be reached")")
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1125
1364
|
}
|
|
1126
1365
|
@objc private func panelOpen() {
|
|
1127
1366
|
guard let item = panelItem else { return }
|
|
@@ -1328,3 +1567,12 @@ let app = NSApplication.shared
|
|
|
1328
1567
|
let delegate = AppDelegate()
|
|
1329
1568
|
app.delegate = delegate
|
|
1330
1569
|
app.run()
|
|
1570
|
+
|
|
1571
|
+
extension AppDelegate: NSTextViewDelegate {
|
|
1572
|
+
func textView(_ textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
|
|
1573
|
+
guard textView === panelReply, commandSelector == #selector(NSResponder.insertNewline(_:)) else { return false }
|
|
1574
|
+
if NSApp.currentEvent?.modifierFlags.contains(.shift) == true { return false }
|
|
1575
|
+
panelSend(nil)
|
|
1576
|
+
return true
|
|
1577
|
+
}
|
|
1578
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
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": {
|