@join-pantheon/cli 0.23.1 → 0.24.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/dist/worker.js CHANGED
@@ -13,7 +13,7 @@ import { allowed, recordRun } from "./spend.js";
13
13
  import { loadProfile, reservedDecisionsNote } from "./profile.js";
14
14
  import { clip } from "./brief.js";
15
15
  import { composeWorker } from "./notice.js";
16
- import { installedTools } from "./notifier.js";
16
+ import { installedTools, preferredTool } from "./notifier.js";
17
17
  import { loadPersonas, matchPersona, personaByName, personaMatches, effectivePolicy, personaBrief, describePersona, } from "./persona.js";
18
18
  import { isContinuationFor, LIMITS, shouldDeferToHuman } from "./protocol.js";
19
19
  const git = (cwd, ...args) => execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
@@ -593,7 +593,7 @@ export class Worker {
593
593
  if (this.opts.notify && (!ok || loadProfile(this.cfg).notifications?.worker_done)) {
594
594
  try {
595
595
  const reply = client.mineSince(startedAt, o.goal).pop() ?? null;
596
- this.opts.notify(composeWorker({ ok, triggeredBy, obligationBody: o.body, goal: o.goal ? client.goals.get(o.goal) : undefined, reply, obligationRef: o.ref }, { tools: installedTools(), preferred: loadProfile(this.cfg).agent.primary_surface }));
596
+ this.opts.notify(composeWorker({ ok, triggeredBy, obligationBody: o.body, goal: o.goal ? client.goals.get(o.goal) : undefined, reply, obligationRef: o.ref }, (() => { const tools = installedTools(); return { tools, preferred: preferredTool(loadProfile(this.cfg), tools, this.opts.home) }; })()));
597
597
  }
598
598
  catch { }
599
599
  }
@@ -690,6 +690,8 @@ export class Worker {
690
690
  this.opts.log(`! self-upgrade failed: ${e.message}`);
691
691
  }
692
692
  if (result.ok && !result.alreadyCurrent && result.to === result.from) {
693
+ delete policy.attempts.self_upgrade;
694
+ savePolicy(policy, home);
693
695
  this.opts.log(`* self-upgrade: the registry does not serve ${relayPkg} yet (installed ${result.to}); will check again`);
694
696
  return;
695
697
  }
@@ -697,7 +699,7 @@ export class Worker {
697
699
  await client.send({
698
700
  act: "NOTE", goal: null, after_seq: client.readSeq,
699
701
  summary: `self-upgraded pantheon ${result.from} -> ${result.to}; worker restarting on the new build`,
700
- body: `This machine's worker upgraded pantheon ${result.from} -> ${result.to} (fast-forward pull + rebuild, granted by its human) and is restarting on the new build.`,
702
+ body: `This machine's worker upgraded pantheon ${result.from} -> ${result.to} (fast-forward pull + rebuild, granted by its human) and is restarting on the new build. Agent windows on this machine opened on 0.24 or later switch to the new version by themselves; a window opened on an older pantheon needs one reconnect (/mcp → pantheon → Reconnect, or reopen it).`,
701
703
  }).catch((e) => this.opts.log(`! could not report the upgrade on the ledger: ${e.message}`));
702
704
  await this.bringMachineAlong();
703
705
  this.opts.log(`* self-upgrade done: ${result.from} -> ${result.to}; restarting worker to load it`);
@@ -29,7 +29,7 @@ if args.count == 2, args[1] == "version" {
29
29
  exit(0)
30
30
  }
31
31
 
32
- struct Tool: Decodable { let id: String; let label: String }
32
+ struct Tool: Decodable { let id: String; let label: String; let kind: String?; let bundle: String?; let deeplink: String? }
33
33
  struct Primary: Decodable { let label: String; let `default`: String; let tools: [Tool]; let prompt: String }
34
34
  struct Details: Decodable { let act: String?; let decision: String? }
35
35
  struct Notice: Decodable {
@@ -105,17 +105,35 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
105
105
  NSApp.terminate(nil)
106
106
  }
107
107
 
108
- @objc func openTool(_ sender: NSMenuItem) {
109
- guard let p = notice.primary else { return }
110
- let tool = (sender.representedObject as? String) ?? p.default
111
- act(["action": "open", "tool": tool, "prompt": p.prompt, "decision": notice.details?.decision as Any, "act": notice.details?.act as Any].compactMapValues { $0 is NSNull ? nil : $0 }, command: "pantheon-explain.command")
108
+ /// Remember the pick: the next notice opens here by default.
109
+ private func remember(_ tool: String) {
110
+ guard !notice.bin_dir.isEmpty, let data = try? JSONSerialization.data(withJSONObject: ["tool": tool]) else { return }
111
+ try? data.write(to: URL(fileURLWithPath: notice.bin_dir).appendingPathComponent(".hud-pref.json"), options: .atomic)
112
112
  }
113
113
 
114
- @objc func openDefault(_ sender: Any?) {
114
+ private func open(_ toolId: String) {
115
115
  guard let p = notice.primary else { return }
116
- act(["action": "open", "tool": p.default, "prompt": p.prompt, "decision": notice.details?.decision as Any, "act": notice.details?.act as Any].compactMapValues { $0 is NSNull ? nil : $0 }, command: "pantheon-explain.command")
116
+ remember(toolId)
117
+ let tool = p.tools.first { $0.id == toolId }
118
+ if let t = tool, t.kind == "app" {
119
+ // A deep link carries the question itself; an app without one gets it on the clipboard and comes to the front.
120
+ if let template = t.deeplink, let encoded = p.prompt.addingPercentEncoding(withAllowedCharacters: .alphanumerics), let url = URL(string: template.replacingOccurrences(of: "{prompt}", with: encoded)) {
121
+ NSWorkspace.shared.open(url)
122
+ NSApp.terminate(nil); return
123
+ }
124
+ NSPasteboard.general.clearContents(); NSPasteboard.general.setString(p.prompt, forType: .string)
125
+ if let b = t.bundle, let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: b) {
126
+ let config = NSWorkspace.OpenConfiguration(); config.activates = true
127
+ NSWorkspace.shared.openApplication(at: appURL, configuration: config) { _, _ in NSApp.terminate(nil) }
128
+ } else { NSApp.terminate(nil) }
129
+ return
130
+ }
131
+ act(["action": "open", "tool": toolId, "prompt": p.prompt, "decision": notice.details?.decision as Any, "act": notice.details?.act as Any].compactMapValues { $0 is NSNull ? nil : $0 }, command: "pantheon-explain.command")
117
132
  }
118
133
 
134
+ @objc func openTool(_ sender: NSMenuItem) { open((sender.representedObject as? String) ?? notice.primary?.default ?? "") }
135
+ @objc func openDefault(_ sender: Any?) { open(notice.primary?.default ?? "") }
136
+
119
137
  @objc func showDetails(_ sender: Any?) {
120
138
  act(["action": "details", "act": notice.details?.act as Any, "decision": notice.details?.decision as Any].compactMapValues { $0 is NSNull ? nil : $0 }, command: "pantheon-details.command")
121
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@join-pantheon/cli",
3
- "version": "0.23.1",
3
+ "version": "0.24.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": {