@join-pantheon/cli 0.21.2 → 0.22.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 +20 -9
- package/dist/cli.js +139 -18
- package/dist/history-view.js +51 -0
- package/dist/mcp.js +4 -2
- package/dist/profile.js +8 -0
- package/dist/setup-guide.js +60 -22
- package/dist/worker.js +45 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,21 +45,27 @@ out with `pantheon sessions`; every identity and membership event is in `pantheo
|
|
|
45
45
|
audit`. (Self-hosting or on a LAN/tailnet? Accounts are optional — skip straight
|
|
46
46
|
to `pantheon start`.)
|
|
47
47
|
|
|
48
|
-
**Then, from your project's repo:**
|
|
48
|
+
**Then, from your project's repo — or anywhere, if there is no repo yet (see below):**
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
pantheon start
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
**Your teammate, from their clone of the project:**
|
|
54
|
+
**Your teammate, from their clone of the project if they have one:**
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
pantheon join pantheons://relay.joinpantheon.network/acme#C1qhJfdHBaPjuG3jF48vcqrP
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Signed in, `pantheon start` puts your space on the hosted relay, so that link works from any
|
|
61
|
-
network — no tunnel, no VPN. The space is named after
|
|
62
|
-
that name, start picks `<
|
|
61
|
+
network — no tunnel, no VPN. The space is named after the folder you run it from (your repo,
|
|
62
|
+
usually); if another team already took that name, start picks `<name>-<you>` and says so. Choose
|
|
63
|
+
your own with `--space` — always do that when there is no repo to name it after.
|
|
64
|
+
|
|
65
|
+
**You do not need a git repository.** Messages, questions, decisions and escalations work from any
|
|
66
|
+
folder. A repo adds exactly two things: handing over code (a `DELIVERED` names a pushed branch and
|
|
67
|
+
commit) and letting a teammate wake this machine while you are away; `pantheon repo add` binds one
|
|
68
|
+
whenever you have it.
|
|
63
69
|
|
|
64
70
|
That's it — one command each sets up the **whole machine**: a relay and space (you) or membership
|
|
65
71
|
(them), pantheon wired into whichever agents each of you already has (**user scope** — every project,
|
|
@@ -67,13 +73,17 @@ every Conductor window, nothing to commit), the wake-up hook so an agent finishe
|
|
|
67
73
|
waiting for, the **worker** so the machine answers the team even while its human is away (on by
|
|
68
74
|
default when run inside a git repo; `--no-worker` opts out — and a peer still needs an explicit
|
|
69
75
|
`--may-wake` grant before they can use it), native decision alerts, and the menu bar. Each command
|
|
70
|
-
ends with a "You're set" summary, then a short guided setup on a terminal:
|
|
71
|
-
your
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
ends with a "You're set" summary, then a short guided setup on a terminal: three questions — your
|
|
77
|
+
name, your role, and (inside a repo) whether a run a teammate wakes may execute commands — and one
|
|
78
|
+
screen of defaults to keep or change: who may wake your machine while you are away, which alerts you
|
|
79
|
+
want, which decisions are yours alone (so agents stop and ask you), the worker's playbook and its base
|
|
80
|
+
branch. `pantheon setup` re-runs it any time. An agent setting you up runs `pantheon setup --questions`,
|
|
81
|
+
asks you in chat, and passes your answers back as flags — to `pantheon setup --yes`, or straight to
|
|
82
|
+
`pantheon start` / `pantheon join`, which apply them the moment the team exists — so nobody has to
|
|
83
|
+
open a terminal. Restart your agent once and ask it *"check pantheon_status"*.
|
|
74
84
|
|
|
75
85
|
Already a member — new laptop, or just pulled a newer build? The same full setup is one command:
|
|
76
|
-
`pantheon install --user` (from your project's repo).
|
|
86
|
+
`pantheon install --user` (from your project's repo if you have one).
|
|
77
87
|
|
|
78
88
|
**Consent happens once, here.** Setup pre-approves exactly the six pantheon *message* tools in
|
|
79
89
|
each agent's own permission system (Claude Code `permissions.allow`, Cursor `Mcp()` tokens,
|
|
@@ -451,6 +461,7 @@ pantheon doctor preflight: node, keys, MCP entry, relay, memb
|
|
|
451
461
|
pantheon worker / service stay answerable while you are away (install as a service)
|
|
452
462
|
pantheon persona list / init / show — what a woken worker may do
|
|
453
463
|
pantheon spaces / use / repo several teams on one machine — what is bound where; bind this repo; a team's repos
|
|
464
|
+
pantheon history / show read the thread, or one act, in full with ids — from a terminal, no agent needed
|
|
454
465
|
pantheon sessions · logout your devices and sessions; sign this machine out
|
|
455
466
|
pantheon account delete remove your account and everything the relay holds on you
|
|
456
467
|
pantheon upgrade pull the latest release and re-wire your agents
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ process.emitWarning = ((warning, ...rest) => {
|
|
|
8
8
|
});
|
|
9
9
|
import { randomBytes } from "node:crypto";
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
|
-
import { dirname, join, resolve } from "node:path";
|
|
11
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
12
12
|
import { PantheonClient } from "./client.js";
|
|
13
13
|
import { fromInvite, load, save, toInvite, resolveFor, loadAll, repoInfo, bindRepo, findMembership, setDefaultSpace, removeMembership, configFor, CONFIG_DIR, CONFIG_PATH, DEFAULT_RELAY, resolveForHook } from "./config.js";
|
|
14
14
|
import { fmtLimit } from "./plans.js";
|
|
@@ -20,6 +20,7 @@ import { Worker } from "./worker.js";
|
|
|
20
20
|
import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, sendDesktopNotification } from "./notifier.js";
|
|
21
21
|
import { AGENT_SURFACES, applyProfileInit, cleanList, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, wakeDefault } from "./profile.js";
|
|
22
22
|
import { runGuide, terminalIO, parseSetupFlags, questionnaire } from "./setup-guide.js";
|
|
23
|
+
import { selectActs, formatAct, resolveRef } from "./history-view.js";
|
|
23
24
|
import { doctor, render } from "./doctor.js";
|
|
24
25
|
import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, dataSummary, stopProc, statFile } from "./machine.js";
|
|
25
26
|
import { assess } from "./untrusted.js";
|
|
@@ -321,6 +322,13 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
321
322
|
rl?.close();
|
|
322
323
|
}
|
|
323
324
|
}
|
|
325
|
+
async function fetchHistory(cfg, since = 0) {
|
|
326
|
+
const base = cfg.relay.replace(/^ws/, "http").replace(/\/+$/, "");
|
|
327
|
+
const res = await fetch(`${base}/history?space=${encodeURIComponent(cfg.space)}&token=${encodeURIComponent(cfg.token)}&since=${since}`, { signal: AbortSignal.timeout(8000) });
|
|
328
|
+
if (!res.ok)
|
|
329
|
+
throw new Error(`relay answered ${res.status} for the ledger`);
|
|
330
|
+
return (await res.json()).acts ?? [];
|
|
331
|
+
}
|
|
324
332
|
function workerMidRun() {
|
|
325
333
|
try {
|
|
326
334
|
const s = JSON.parse(readFileSync(join(CONFIG_DIR, "worker-state.json"), "utf8"));
|
|
@@ -365,11 +373,16 @@ ${B("first time — verify your email once")}
|
|
|
365
373
|
pantheon login --email you@work.com a second machine, same account
|
|
366
374
|
pantheon whoami · pantheon sessions who you are; your devices & sessions
|
|
367
375
|
|
|
368
|
-
${B("getting going —
|
|
376
|
+
${B("getting going — two commands, from your project's repo if you have one")}
|
|
369
377
|
pantheon start you: sets up this whole machine + one line to send your teammate
|
|
370
378
|
pantheon join <line> them: that one line sets up their whole machine
|
|
371
379
|
pantheon invite another single-use line, for a third person or a second machine
|
|
372
380
|
|
|
381
|
+
Both take the setup answers as flags (--name, --title, --wake, --commands, … — pantheon setup
|
|
382
|
+
--questions lists them), so an agent sets a person up in one command. No repo? Messages, questions
|
|
383
|
+
and decisions work anywhere; a repo only adds handing over code and being woken. Name the team
|
|
384
|
+
with --space when there is no repo to name it after.
|
|
385
|
+
|
|
373
386
|
One command does everything: agents wired and pre-approved, wake-up hook (your
|
|
374
387
|
agent finishes what a peer is waiting for), worker (this machine answers the
|
|
375
388
|
team while you are away — on when run from a git repo; a peer still needs an
|
|
@@ -416,9 +429,11 @@ ${B("control")}
|
|
|
416
429
|
pantheon leave remove what pantheon put on this machine
|
|
417
430
|
pantheon panic cut off all unattended work on this machine, now
|
|
418
431
|
pantheon doctor check this machine end to end
|
|
419
|
-
pantheon setup
|
|
420
|
-
|
|
421
|
-
|
|
432
|
+
pantheon setup three questions — your name, your role, may a woken run execute commands —
|
|
433
|
+
then one screen of defaults to keep or change: who may wake this machine,
|
|
434
|
+
alerts, what you personally decide, playbook, base branch. Re-run any time.
|
|
435
|
+
(--questions prints them for an agent to ask you; answers go back as flags,
|
|
436
|
+
to setup --yes or straight to start / join)
|
|
422
437
|
pantheon upgrade pull the latest pantheon, rebuild, and re-wire your agents
|
|
423
438
|
|
|
424
439
|
${D("more: pantheon help --all")}
|
|
@@ -432,6 +447,9 @@ ${B("less common")}
|
|
|
432
447
|
pantheon whoami your name and fingerprint
|
|
433
448
|
pantheon pending what a live agent window has not yet seen
|
|
434
449
|
pantheon say <ACT> <text> send one act by hand (NOTE, ASK, ANSWER, PROPOSE, ...)
|
|
450
|
+
replies pick the act they answer; --ref <id | seq | last> to choose
|
|
451
|
+
pantheon history the thread in full, with ids (--goal <id>, --from <who>, --last N, --short)
|
|
452
|
+
pantheon show <id | seq> one act in full, and what it replied to
|
|
435
453
|
pantheon invite mint another single-use invite link to send
|
|
436
454
|
pantheon install re-wire this machine's agents (after a Node/agent change)
|
|
437
455
|
pantheon mcp the server your agent runs; not for you
|
|
@@ -457,9 +475,14 @@ async function main() {
|
|
|
457
475
|
switch (cmd) {
|
|
458
476
|
case "start": {
|
|
459
477
|
const port = Number(flag("port", "8787"));
|
|
478
|
+
const setupFlags = parseSetupFlags(argv);
|
|
479
|
+
if (setupFlags.errors.length)
|
|
480
|
+
die(setupFlags.errors.join("\n") + "\n pantheon setup --questions lists every answer");
|
|
460
481
|
const wp = unattendedProject();
|
|
461
482
|
requireSignInOnHostedRelay(flag("relay"));
|
|
462
483
|
console.error(`${B("pantheon start")}\n`);
|
|
484
|
+
if (!flag("space") && !repoInfo(process.cwd()))
|
|
485
|
+
console.error(D(` No git repo here, so the team will be named after this folder: "${basename(process.cwd()).toLowerCase().replace(/[^a-z0-9._-]/g, "-") || "team"}". Pass --space <name> to choose.\n`));
|
|
463
486
|
const r = await qsStart({
|
|
464
487
|
port,
|
|
465
488
|
dataDir: resolveDataDir(flag("data")).dir,
|
|
@@ -489,7 +512,7 @@ async function main() {
|
|
|
489
512
|
if (r.invite) {
|
|
490
513
|
console.error(`\n${B("Send your teammate this one line — it sets up their whole machine:")}\n`);
|
|
491
514
|
console.error(` ${C(`pantheon join ${r.invite}`)}\n`);
|
|
492
|
-
console.error(D(`
|
|
515
|
+
console.error(D(` From their project's repo if they have one — a repo is only needed to hand over code or to be woken. Single use, expires in ${Math.round(Number(flag("ttl", "1440")) / 60)}h.`));
|
|
493
516
|
console.error(D(` Redeeming it admits them — no separate approval step. Need another? \`pantheon invite\``));
|
|
494
517
|
console.error(D(`\n Not technical? Forward them this instead, and their agent does the rest:`));
|
|
495
518
|
console.error(` ${D("Tell your agent: run")} ${C("curl -fsSL https://www.joinpantheon.network/skills/join.md")} ${D("and follow it. Invite:")} ${C(`pantheon join ${r.invite}`)}`);
|
|
@@ -500,7 +523,7 @@ async function main() {
|
|
|
500
523
|
}
|
|
501
524
|
printReady(r.tools);
|
|
502
525
|
if (r.claimed)
|
|
503
|
-
await guidedSetup(load() ?? r.cfg, repoInfo(process.cwd())?.root ?? null);
|
|
526
|
+
await guidedSetup(load() ?? r.cfg, repoInfo(process.cwd())?.root ?? null, { preset: setupFlags.preset, force: Object.keys(setupFlags.preset).length > 0 || argv.includes("--yes") });
|
|
504
527
|
if (r.relay) {
|
|
505
528
|
console.error(D(`\nKeeping the relay running here. Ctrl-C stops it (your teammate goes offline too).`));
|
|
506
529
|
await new Promise(() => { });
|
|
@@ -593,7 +616,10 @@ async function main() {
|
|
|
593
616
|
return;
|
|
594
617
|
}
|
|
595
618
|
case "join": {
|
|
596
|
-
const invite = argv[1] ?? die("usage: pantheon join pantheon://host/space#token");
|
|
619
|
+
const invite = argv[1] ?? die("usage: pantheon join pantheon://host/space#token [setup answers as flags — pantheon setup --questions]");
|
|
620
|
+
const setupFlags = parseSetupFlags(argv);
|
|
621
|
+
if (setupFlags.errors.length)
|
|
622
|
+
die(setupFlags.errors.join("\n") + "\n pantheon setup --questions lists every answer");
|
|
597
623
|
let parsed;
|
|
598
624
|
try {
|
|
599
625
|
parsed = fromInvite(invite);
|
|
@@ -637,7 +663,7 @@ async function main() {
|
|
|
637
663
|
{
|
|
638
664
|
const joined = load();
|
|
639
665
|
if (joined)
|
|
640
|
-
await guidedSetup(joined, repoInfo(process.cwd())?.root ?? null);
|
|
666
|
+
await guidedSetup(joined, repoInfo(process.cwd())?.root ?? null, { preset: setupFlags.preset, force: Object.keys(setupFlags.preset).length > 0 || argv.includes("--yes") });
|
|
641
667
|
}
|
|
642
668
|
console.error(`\n${D('Then ask your agent: "check pantheon_status and say hello to the team".')}`);
|
|
643
669
|
return;
|
|
@@ -685,6 +711,35 @@ async function main() {
|
|
|
685
711
|
}
|
|
686
712
|
if (r.alreadyCurrent) {
|
|
687
713
|
console.error(`\n${B("already up to date")} ${D(`(${r.from})`)}`);
|
|
714
|
+
if (process.platform === "darwin") {
|
|
715
|
+
const { trayStale, ensureTrayBinary, writeTrayCommands } = await import("./tray.js");
|
|
716
|
+
const { notifierStale, ensureNativeNotifier } = await import("./notifier.js");
|
|
717
|
+
const { restartService } = await import("./service.js");
|
|
718
|
+
if (trayStale()) {
|
|
719
|
+
try {
|
|
720
|
+
ensureTrayBinary();
|
|
721
|
+
writeTrayCommands();
|
|
722
|
+
for (const l of restartService("menubar"))
|
|
723
|
+
console.error(l);
|
|
724
|
+
}
|
|
725
|
+
catch (e) {
|
|
726
|
+
console.error(` menu bar: could not rebuild (${e.message.split("\n")[0]})`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (notifierStale()) {
|
|
730
|
+
try {
|
|
731
|
+
ensureNativeNotifier();
|
|
732
|
+
for (const l of restartService("notifier"))
|
|
733
|
+
console.error(l);
|
|
734
|
+
}
|
|
735
|
+
catch (e) {
|
|
736
|
+
console.error(` alerts: could not rebuild (${e.message.split("\n")[0]})`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const cur = load();
|
|
741
|
+
if (cur && loadProfile(cur).agent.wake_default === undefined)
|
|
742
|
+
await guidedSetup(cur, repoInfo(process.cwd())?.root ?? null);
|
|
688
743
|
return;
|
|
689
744
|
}
|
|
690
745
|
console.error(`\n${B(`upgraded ${r.from} → ${r.to}`)}`);
|
|
@@ -793,7 +848,7 @@ async function main() {
|
|
|
793
848
|
for (const o of human) {
|
|
794
849
|
const who = o.owner.replace(/^human:/, "");
|
|
795
850
|
const stale = Date.now() - o.created_at > 3_600_000;
|
|
796
|
-
console.log(` ${stale ? "\x1b[33m" : C("")}${who.padEnd(14)}${ageOf(o.created_at).padEnd(6)}\x1b[0m ${o.body}`);
|
|
851
|
+
console.log(` ${stale ? "\x1b[33m" : C("")}${who.padEnd(14)}${ageOf(o.created_at).padEnd(6)}\x1b[0m ${o.body.split("\n")[0].slice(0, 110)} ${D(o.ref)}`);
|
|
797
852
|
if (who === cfg.member)
|
|
798
853
|
console.log(` ${D("your peer was told this decision was coming — answer it or say it is dropped")}`);
|
|
799
854
|
}
|
|
@@ -801,7 +856,10 @@ async function main() {
|
|
|
801
856
|
if (!between.length)
|
|
802
857
|
console.log(` ${D("(nothing owed)")}`);
|
|
803
858
|
for (const o of between) {
|
|
804
|
-
console.log(` ${o.kind.padEnd(7)} ${o.owner.padEnd(14)}${ageOf(o.created_at).padEnd(6)} ${o.body}`);
|
|
859
|
+
console.log(` ${o.kind.padEnd(7)} ${o.owner.padEnd(14)}${ageOf(o.created_at).padEnd(6)} ${o.body.split("\n")[0].slice(0, 110)} ${D(o.ref)}`);
|
|
860
|
+
}
|
|
861
|
+
if (human.length || between.length || goals.some((g) => g.state !== "CLOSED")) {
|
|
862
|
+
console.log(D(`\n read in full: pantheon show <id> · a thread: pantheon history --goal <goal> · reply: pantheon say ANSWER "…" --ref <id>`));
|
|
805
863
|
}
|
|
806
864
|
client.close();
|
|
807
865
|
};
|
|
@@ -902,13 +960,58 @@ async function main() {
|
|
|
902
960
|
console.log(argv.includes("--json") ? JSON.stringify(report) : renderPending(report));
|
|
903
961
|
return;
|
|
904
962
|
}
|
|
963
|
+
case "history": {
|
|
964
|
+
const cfg = need();
|
|
965
|
+
const acts = await fetchHistory(cfg, numFlag("since", 0)).catch((e) => die(`cannot read the ledger — ${e.message}`));
|
|
966
|
+
const picked = selectActs(acts, {
|
|
967
|
+
...(flag("goal") ? { goal: flag("goal") } : {}), ...(flag("from") ? { from: flag("from") } : {}),
|
|
968
|
+
last: numFlag("last", 20), presence: argv.includes("--presence"),
|
|
969
|
+
});
|
|
970
|
+
if (argv.includes("--json")) {
|
|
971
|
+
console.log(JSON.stringify(picked, null, 2));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (!picked.length) {
|
|
975
|
+
console.log(D("no acts match"));
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
979
|
+
const titles = new Map();
|
|
980
|
+
try {
|
|
981
|
+
await client.connect();
|
|
982
|
+
for (const g of client.goals.values())
|
|
983
|
+
titles.set(g.id, g.title);
|
|
984
|
+
}
|
|
985
|
+
catch { }
|
|
986
|
+
finally {
|
|
987
|
+
client.close();
|
|
988
|
+
}
|
|
989
|
+
for (const a of picked)
|
|
990
|
+
console.log(formatAct(a, { short: argv.includes("--short"), ...(a.goal && titles.get(a.goal) ? { goalTitle: titles.get(a.goal) } : {}) }) + "\n");
|
|
991
|
+
console.log(D(`${picked.length} of ${acts.filter((a) => a.act !== "JOIN" && a.act !== "LEAVE").length} acts · --last N · --goal <id> · --from <member> · --short · --json · reply: pantheon say ANSWER "…" --ref <id or seq>`));
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
case "show": {
|
|
995
|
+
const cfg = need();
|
|
996
|
+
const key = argv[1] ?? die("usage: pantheon show <act id | sequence number>");
|
|
997
|
+
const acts = await fetchHistory(cfg).catch((e) => die(`cannot read the ledger — ${e.message}`));
|
|
998
|
+
const a = acts.find((x) => x.id === key || String(x.seq) === key) ?? die(`no act ${key} in what the relay still holds (pantheon history lists them)`);
|
|
999
|
+
console.log(formatAct(a));
|
|
1000
|
+
if (a.ref) {
|
|
1001
|
+
const r = acts.find((x) => x.id === a.ref);
|
|
1002
|
+
if (r)
|
|
1003
|
+
console.log(D(`\nin reply to:\n`) + formatAct(r, { short: true }));
|
|
1004
|
+
}
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
905
1007
|
case "say": {
|
|
906
1008
|
const cfg = need();
|
|
907
|
-
const act = argv[1]?.toUpperCase() ?? die("usage: pantheon say <ACT> <body> [--goal id] [--to a,b] [--domains technical,gtm]");
|
|
1009
|
+
const act = argv[1]?.toUpperCase() ?? die("usage: pantheon say <ACT> <body> [--goal id] [--to a,b] [--ref <id | seq | last>] [--summary 'one line'] [--domains technical,gtm]");
|
|
908
1010
|
const stop = argv.findIndex((a, i) => i >= 2 && a.startsWith("--"));
|
|
909
1011
|
const body = argv.slice(2, stop === -1 ? undefined : stop).join(" ");
|
|
910
1012
|
if (!body)
|
|
911
1013
|
die("nothing to say — pantheon say NOTE 'your message here'");
|
|
1014
|
+
const summary = flag("summary");
|
|
912
1015
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
913
1016
|
await client.connect().catch((e) => die(`cannot reach relay — ${e.message}`));
|
|
914
1017
|
const to = flag("to");
|
|
@@ -921,13 +1024,28 @@ async function main() {
|
|
|
921
1024
|
const domains = flag("domains");
|
|
922
1025
|
if (domains)
|
|
923
1026
|
meta.decision_domains = cleanList(domains.split(","));
|
|
1027
|
+
let goalArg = flag("goal") ?? null;
|
|
1028
|
+
let ref = flag("ref") ?? null;
|
|
1029
|
+
if (ref !== null || ["ANSWER", "ACCEPT", "OBJECT"].includes(act)) {
|
|
1030
|
+
const acts = await fetchHistory(cfg).catch(() => []);
|
|
1031
|
+
const rr = resolveRef(acts, ref, act, goalArg, cfg.member);
|
|
1032
|
+
if (rr.error) {
|
|
1033
|
+
client.close();
|
|
1034
|
+
die(rr.error);
|
|
1035
|
+
}
|
|
1036
|
+
if (rr.note)
|
|
1037
|
+
console.error(D(` ${rr.note}`));
|
|
1038
|
+
ref = rr.id;
|
|
1039
|
+
goalArg = goalArg ?? rr.goal;
|
|
1040
|
+
}
|
|
924
1041
|
const r = await client.send({
|
|
925
1042
|
act: act, body,
|
|
926
|
-
goal:
|
|
1043
|
+
goal: goalArg,
|
|
927
1044
|
to: to ? to.split(",").map((s) => s.trim()) : null,
|
|
928
|
-
ref
|
|
1045
|
+
ref,
|
|
1046
|
+
...(summary ? { summary } : {}),
|
|
929
1047
|
...(Object.keys(meta).length ? { meta } : {}),
|
|
930
|
-
}).catch((e) => die(e.message));
|
|
1048
|
+
}).catch((e) => die(/SUMMARY_REQUIRED/.test(e.message) ? `${e.message}\n Add one: --summary 'the point they must not miss'` : e.message));
|
|
931
1049
|
console.log(`sent ${act} id=${r.id} seq=${r.seq}`);
|
|
932
1050
|
client.close();
|
|
933
1051
|
return;
|
|
@@ -1984,10 +2102,13 @@ async function main() {
|
|
|
1984
2102
|
const find = (want) => all.find((x) => x.o.id === want);
|
|
1985
2103
|
if (sub === "answer") {
|
|
1986
2104
|
if (!id)
|
|
1987
|
-
die("usage: pantheon approvals answer <obligation-id> <decision>");
|
|
2105
|
+
die("usage: pantheon approvals answer <obligation-id> <decision> [--summary 'one line']");
|
|
1988
2106
|
const hit = find(id) ?? die(`no decision ${id} is waiting on you${conns.length > 1 ? ` in any of your ${conns.length} teams` : ""}`);
|
|
1989
|
-
const
|
|
1990
|
-
const
|
|
2107
|
+
const stopAt = argv.findIndex((a, i) => i >= 3 && a.startsWith("--"));
|
|
2108
|
+
const answer = argv.slice(3, stopAt === -1 ? undefined : stopAt).join(" ").trim() || die("state the decision; it will be written to the audit ledger");
|
|
2109
|
+
const summary = flag("summary");
|
|
2110
|
+
const sent = await hit.client.send({ act: "ANSWER", body: answer, goal: null, ref: hit.o.ref, after_seq: hit.client.readSeq, ...(summary ? { summary } : {}) })
|
|
2111
|
+
.catch((e) => { closeAll(); return die(/SUMMARY_REQUIRED/.test(e.message) ? `${e.message}\n Add one: --summary 'the point they must not miss'` : e.message); });
|
|
1991
2112
|
console.log(`${C("answered")} ${id}${conns.length > 1 ? ` in ${B(hit.cfg.space)}` : ""} — ledger act ${sent.id}`);
|
|
1992
2113
|
closeAll();
|
|
1993
2114
|
return;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function selectActs(acts, opts = {}) {
|
|
2
|
+
const kept = acts.filter((a) => (opts.presence || (a.act !== "JOIN" && a.act !== "LEAVE"))
|
|
3
|
+
&& (opts.goal === undefined || a.goal === opts.goal)
|
|
4
|
+
&& (opts.from === undefined || a.from === opts.from)
|
|
5
|
+
&& (opts.since === undefined || a.seq > opts.since));
|
|
6
|
+
const n = opts.last ?? 20;
|
|
7
|
+
return kept.slice(Math.max(0, kept.length - n));
|
|
8
|
+
}
|
|
9
|
+
const when = (ts) => new Date(ts).toISOString().slice(0, 19).replace("T", " ") + "Z";
|
|
10
|
+
export function formatAct(a, opts = {}) {
|
|
11
|
+
const who = `${a.from}${a.role && a.role !== "interactive" ? ` (${a.role})` : ""}`;
|
|
12
|
+
const head = `[${a.seq}] ${a.act} from ${who}${a.to?.length ? ` to ${a.to.join(",")}` : ""} ${when(a.ts)} id=${a.id}${a.ref ? ` ref=${a.ref}` : ""}${a.goal ? ` goal=${a.goal}${opts.goalTitle ? ` "${opts.goalTitle}"` : ""}` : ""}`;
|
|
13
|
+
if (opts.short)
|
|
14
|
+
return `${head}\n ${(a.summary ?? a.body).split("\n")[0].slice(0, 160)}`;
|
|
15
|
+
const meta = a.meta && Object.keys(a.meta).length ? `\n meta: ${JSON.stringify(a.meta)}` : "";
|
|
16
|
+
const summary = a.summary ? `\n >> ${a.summary}` : "";
|
|
17
|
+
return `${head}${summary}\n${a.body.split("\n").map((l) => ` ${l}`).join("\n")}${meta}`;
|
|
18
|
+
}
|
|
19
|
+
const REPLIES_TO = {
|
|
20
|
+
ANSWER: ["ASK", "ESCALATE", "HUMAN_REQUIRED"],
|
|
21
|
+
ACCEPT: ["DELIVERED", "PROPOSE", "CONTRACT"],
|
|
22
|
+
OBJECT: ["DELIVERED", "PROPOSE", "CONTRACT"],
|
|
23
|
+
};
|
|
24
|
+
export function resolveRef(acts, ref, act, goal, me) {
|
|
25
|
+
const inGoal = (a) => goal === null || a.goal === goal;
|
|
26
|
+
if (ref && /^a_/.test(ref)) {
|
|
27
|
+
const hit = acts.find((a) => a.id === ref);
|
|
28
|
+
return { id: ref, goal: hit?.goal ?? goal };
|
|
29
|
+
}
|
|
30
|
+
if (ref && /^\d+$/.test(ref)) {
|
|
31
|
+
const hit = acts.find((a) => a.seq === Number(ref));
|
|
32
|
+
return hit ? { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from}` } : { id: null, goal, error: `no act with sequence ${ref} in what this relay still holds` };
|
|
33
|
+
}
|
|
34
|
+
const others = acts.filter((a) => a.from !== me && a.act !== "JOIN" && a.act !== "LEAVE" && inGoal(a));
|
|
35
|
+
if (ref === "last") {
|
|
36
|
+
const hit = others[others.length - 1];
|
|
37
|
+
return hit ? { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from}` } : { id: null, goal, error: "nothing from a peer to reply to" };
|
|
38
|
+
}
|
|
39
|
+
if (ref)
|
|
40
|
+
return { id: ref, goal };
|
|
41
|
+
const wants = REPLIES_TO[act];
|
|
42
|
+
if (!wants)
|
|
43
|
+
return { id: null, goal };
|
|
44
|
+
for (const kind of wants) {
|
|
45
|
+
const hit = [...others].reverse().find((a) => a.act === kind && (!a.to?.length || a.to.includes(me)));
|
|
46
|
+
if (hit)
|
|
47
|
+
return { id: hit.id, goal: hit.goal ?? goal, note: `replying to [${hit.seq}] ${hit.act} from ${hit.from} (pass --ref to pick another)` };
|
|
48
|
+
}
|
|
49
|
+
return { id: null, goal, error: `${act} must reference the act it answers — none from a peer found${goal ? ` in ${goal}` : ""}. See pantheon history, then --ref <id or sequence number>` };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=history-view.js.map
|
package/dist/mcp.js
CHANGED
|
@@ -9,7 +9,7 @@ import { VERSION } from "./version.js";
|
|
|
9
9
|
import { backendFromLabel } from "./agents.js";
|
|
10
10
|
import { agentHookStates } from "./notify.js";
|
|
11
11
|
import { reportMcpBusy, reportMcpIdle } from "./mcp-activity.js";
|
|
12
|
-
import { loadProfile, organizationProfile, profileInitialized, routeDecision } from "./profile.js";
|
|
12
|
+
import { loadProfile, organizationProfile, profileInitialized, routeDecision, reservedDecisionsNote } from "./profile.js";
|
|
13
13
|
import { identityLine, renderBrief } from "./brief.js";
|
|
14
14
|
import { recentRuns } from "./spend.js";
|
|
15
15
|
const ACT_HELP = {
|
|
@@ -60,6 +60,7 @@ YOU WERE WOKEN BY A PEER. No human is present in this session.
|
|
|
60
60
|
`
|
|
61
61
|
: "";
|
|
62
62
|
const profile = loadProfile(cfg);
|
|
63
|
+
const reserved = reservedDecisionsNote(profile);
|
|
63
64
|
const identity = profileInitialized(profile)
|
|
64
65
|
? ` You work as ${identityLine({
|
|
65
66
|
member: cfg.member,
|
|
@@ -87,7 +88,8 @@ RULES
|
|
|
87
88
|
The organization directory — not job-title guesswork — determines whose human or agent owns it.
|
|
88
89
|
When a peer's human personally must decide and their agent may be offline, use HUMAN_REQUIRED and name that one peer in to.
|
|
89
90
|
Do not use HUMAN_REQUIRED instead of ASK for something their delegate can answer. Their delegate settles it with ANSWER.
|
|
90
|
-
ESCALATE and HUMAN_REQUIRED outlive a goal close until the named human has actually answered
|
|
91
|
+
ESCALATE and HUMAN_REQUIRED outlive a goal close until the named human has actually answered.${reserved ? `
|
|
92
|
+
- ${reserved}` : ""}
|
|
91
93
|
- Agree the interface BEFORE writing code that crosses it: PROPOSE, then CONTRACT once accepted.
|
|
92
94
|
- Answer every ASK addressed to you. An unanswered ASK blocks a peer and eventually stalls the goal.
|
|
93
95
|
- ACCEPT means you actually checked. If something is wrong, OBJECT with the reason.
|
package/dist/profile.js
CHANGED
|
@@ -33,6 +33,14 @@ export function defaultProfile(cfg) {
|
|
|
33
33
|
updated_at: 0,
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
export function reservedDecisionsNote(profile) {
|
|
37
|
+
const mine = Object.entries(profile.authority).filter(([, m]) => m === "human_required").map(([d]) => d);
|
|
38
|
+
if (!mine.length)
|
|
39
|
+
return "";
|
|
40
|
+
return `YOUR HUMAN DECIDES THESE PERSONALLY: ${mine.join(", ")}. If a peer asks you to settle one — approve a
|
|
41
|
+
deploy, agree a price, sign off a release — do not answer it yourself, however well you have checked the
|
|
42
|
+
work. Say the review result if you have one, then ESCALATE and stop; tell the peer it is with your human.`;
|
|
43
|
+
}
|
|
36
44
|
export function wakeDefault(profile) {
|
|
37
45
|
return (profile.agent.wake_default ?? "anyone") === "anyone";
|
|
38
46
|
}
|
package/dist/setup-guide.js
CHANGED
|
@@ -59,7 +59,13 @@ export function questionnaire() {
|
|
|
59
59
|
const opt = (flag, id, label) => ` ${label} → --${flag} ${id}`;
|
|
60
60
|
return [
|
|
61
61
|
"pantheon setup — the questions, and the flag for each answer.",
|
|
62
|
-
"Ask your human
|
|
62
|
+
"Ask your human in plain words, then pass the answers as flags — to `pantheon setup --yes`, or straight to",
|
|
63
|
+
"`pantheon start` / `pantheon join`, which apply them the moment the team exists.",
|
|
64
|
+
"",
|
|
65
|
+
"Only 3 and 4 need the person every time. Ask 2 whenever there is a repo (it is the one default people are",
|
|
66
|
+
"surprised by). The rest have defaults you may take and say out loud:",
|
|
67
|
+
" --wake anyone --decides none --alerts decisions --playbook yes --base <what origin calls its default>",
|
|
68
|
+
"Without a repo, --wake, --commands, --playbook and --base do nothing yet: skip them and say so.",
|
|
63
69
|
"",
|
|
64
70
|
"1. While they're away, may teammates hand work to this machine? (their agent runs here on its own,",
|
|
65
71
|
" in a separate copy of the repo, and delivers on a branch for review)",
|
|
@@ -146,34 +152,66 @@ export async function runGuide(io, ctx, deps = {}, preset = {}) {
|
|
|
146
152
|
}
|
|
147
153
|
const applied = [], warnings = [];
|
|
148
154
|
if (io) {
|
|
149
|
-
io.say("
|
|
150
|
-
const wakeIdx = await io.choose("While you're away, may teammates hand work to this machine? Your agent would run here on its own, in a separate copy of the repo, and deliver on a branch for review.", WAKE_OPTIONS.map((o) => o.label), Math.max(0, WAKE_OPTIONS.findIndex((o) => o.id === d.wake)));
|
|
151
|
-
a.wake = WAKE_OPTIONS[wakeIdx].id;
|
|
152
|
-
if (a.wake !== "no" && ctx.project) {
|
|
153
|
-
const c = await io.choose("When a teammate's request wakes this machine, what may the run do?", ["Edit files only — it can answer questions and draft changes, but cannot run tests, commit, or push.",
|
|
154
|
-
"Run commands too — needed to run tests and to commit and push work for review. Only requests from people you granted wake permission start a run."], a.commands ? 1 : 0);
|
|
155
|
-
a.commands = c === 1;
|
|
156
|
-
}
|
|
157
|
-
else if (a.wake === "no") {
|
|
158
|
-
a.commands = false;
|
|
159
|
-
}
|
|
155
|
+
io.say("Three questions, then one screen of defaults. Enter keeps the suggested answer.\n");
|
|
160
156
|
a.name = (await io.ask("Your name, as teammates will see it", d.name)).trim() || d.name;
|
|
161
157
|
const known = TITLE_OPTIONS.findIndex((t) => t.toLowerCase() === d.title.toLowerCase());
|
|
162
158
|
const t = await io.choose("Your role, in a word — it goes on the team directory", TITLE_OPTIONS, known >= 0 ? known : d.title ? TITLE_OPTIONS.length - 1 : 0);
|
|
163
159
|
a.title = t === TITLE_OPTIONS.length - 1 ? (await io.ask("Your role", d.title)).trim() || d.title || "Member" : TITLE_OPTIONS[t];
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
const askCommands = async () => {
|
|
161
|
+
const undecided = profile.agent.allow_bash === undefined && preset.commands === undefined;
|
|
162
|
+
const c = await io.choose("Teammates can hand work to this machine while you're away. It runs in a separate copy of the repo and delivers on a branch you review. May a run also execute commands?", ["Yes — run tests, commit, and push work for review (recommended)",
|
|
163
|
+
"No — edit files and answer only; it cannot run tests, commit, or push"], undecided || a.commands ? 0 : 1);
|
|
164
|
+
a.commands = c === 0;
|
|
165
|
+
};
|
|
166
|
+
let commandsAsked = false;
|
|
168
167
|
if (a.wake !== "no" && ctx.project) {
|
|
169
|
-
|
|
170
|
-
|
|
168
|
+
await askCommands();
|
|
169
|
+
commandsAsked = true;
|
|
171
170
|
}
|
|
172
171
|
const detected = ctx.project && deps.setRepoBase ? detectBaseBranch(ctx.project) : null;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
172
|
+
const suggestedBase = detected ? (preset.base ?? detected) : undefined;
|
|
173
|
+
if (a.wake !== "no" && ctx.project)
|
|
174
|
+
a.persona = preset.persona ?? true;
|
|
175
|
+
const shortAlert = (id) => ALERT_OPTIONS.find((o) => o.id === id).label.split(" — ")[0].replace(/ \(recommended\)/, "");
|
|
176
|
+
io.say("\nThe rest has defaults. Change them now, or any time with `pantheon setup`:");
|
|
177
|
+
io.say(` • Wake: ${a.wake === "no" ? "off — this machine only works while you are at it"
|
|
178
|
+
: !ctx.project ? "on once this machine has a repo (run pantheon setup from your project's repo)"
|
|
179
|
+
: a.wake === "anyone" ? "teammates may hand work to this machine while you're away; invites you send grant it, and you can revoke anyone (pantheon admit <name> --revoke)"
|
|
180
|
+
: "only people you approve by hand (pantheon admit <name> --may-wake)"}`);
|
|
181
|
+
io.say(` • Alerts: ${shortAlert(a.notifications).toLowerCase()}`);
|
|
182
|
+
io.say(` • Decisions: ${a.decides.length ? `you personally decide ${a.decides.join(", ")}` : "none reserved for you — your agent may decide; add an area when one comes up"}`);
|
|
183
|
+
if (a.wake !== "no" && ctx.project)
|
|
184
|
+
io.say(` • Playbook: ${a.persona ? "a short starter playbook for your worker, in this repo" : "none"}`);
|
|
185
|
+
if (suggestedBase)
|
|
186
|
+
io.say(` • Base branch: ${suggestedBase}${suggestedBase === detected ? " (what origin calls its default)" : ""}`);
|
|
187
|
+
const keep = await io.choose("Keep these?", ["Keep them (recommended)", "Change some"], 0);
|
|
188
|
+
if (keep === 0) {
|
|
189
|
+
if (suggestedBase)
|
|
190
|
+
a.base = suggestedBase;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const wakeIdx = await io.choose("While you're away, may teammates hand work to this machine? Your agent would run here on its own, in a separate copy of the repo, and deliver on a branch for review.", WAKE_OPTIONS.map((o) => o.label), Math.max(0, WAKE_OPTIONS.findIndex((o) => o.id === a.wake)));
|
|
194
|
+
a.wake = WAKE_OPTIONS[wakeIdx].id;
|
|
195
|
+
if (a.wake === "no") {
|
|
196
|
+
a.commands = false;
|
|
197
|
+
a.persona = false;
|
|
198
|
+
}
|
|
199
|
+
else if (ctx.project && !commandsAsked) {
|
|
200
|
+
await askCommands();
|
|
201
|
+
a.persona = preset.persona ?? true;
|
|
202
|
+
}
|
|
203
|
+
const picks = await io.chooseMany("Which of these do you decide personally? Agents — yours and your teammates' — will stop and ask you before acting on them. Pick any number, or none.", DECISION_AREAS.map((x) => x.label), a.decides.map((id) => DECISION_AREAS.findIndex((x) => x.id === id)).filter((i) => i >= 0));
|
|
204
|
+
a.decides = [...new Set(picks.map((i) => DECISION_AREAS[i].id))];
|
|
205
|
+
const n = await io.choose("Which desktop alerts do you want?", ALERT_OPTIONS.map((o) => o.label), Math.max(0, ALERT_OPTIONS.findIndex((o) => o.id === a.notifications)));
|
|
206
|
+
a.notifications = ALERT_OPTIONS[n].id;
|
|
207
|
+
if (a.wake !== "no" && ctx.project) {
|
|
208
|
+
const p = await io.choose("Should your worker follow a short written playbook for this repo? It says what a woken run may do here and what it must leave to you. You can edit it any time.", ["Yes — write a starter playbook now (recommended)", "Not now"], a.persona ? 0 : 1);
|
|
209
|
+
a.persona = p === 0;
|
|
210
|
+
}
|
|
211
|
+
if (suggestedBase) {
|
|
212
|
+
const b = await io.choose("Which branch should your worker start from, and deliveries be checked against? Usually the branch this project deploys or releases from.", [`${suggestedBase}${suggestedBase === detected ? " (what origin calls its default)" : ""}`, "A different branch"], 0);
|
|
213
|
+
a.base = b === 0 ? suggestedBase : ((await io.ask("Branch name", suggestedBase)).trim() || suggestedBase);
|
|
214
|
+
}
|
|
177
215
|
}
|
|
178
216
|
}
|
|
179
217
|
let next = a.title ? applyProfileInit(profile, { name: a.name, title: a.title }) : { ...profile, human: { ...profile.human, display_name: a.name } };
|
package/dist/worker.js
CHANGED
|
@@ -10,7 +10,7 @@ import { applyAnswer, loadPolicy, mayAsk, needsSelfUpgrade, recordAsk, savePolic
|
|
|
10
10
|
import { agentHookStates, repairHook } from "./notify.js";
|
|
11
11
|
import { fence, assess } from "./untrusted.js";
|
|
12
12
|
import { allowed, recordRun } from "./spend.js";
|
|
13
|
-
import { loadProfile } from "./profile.js";
|
|
13
|
+
import { loadProfile, reservedDecisionsNote } from "./profile.js";
|
|
14
14
|
import { clip, workerDoneNotice } from "./brief.js";
|
|
15
15
|
import { loadPersonas, matchPersona, personaByName, personaMatches, effectivePolicy, personaBrief, describePersona, } from "./persona.js";
|
|
16
16
|
import { isContinuationFor, LIMITS, shouldDeferToHuman } from "./protocol.js";
|
|
@@ -216,6 +216,43 @@ export class Worker {
|
|
|
216
216
|
this.cfg = cfg;
|
|
217
217
|
this.opts = opts;
|
|
218
218
|
}
|
|
219
|
+
async bringMachineAlong() {
|
|
220
|
+
if (this.opts.selfUpgrade?.run)
|
|
221
|
+
return;
|
|
222
|
+
try {
|
|
223
|
+
const { restartService, serviceInstalled } = await import("./service.js");
|
|
224
|
+
if (process.platform === "darwin") {
|
|
225
|
+
const { trayStale, ensureTrayBinary, writeTrayCommands } = await import("./tray.js");
|
|
226
|
+
const { notifierStale, ensureNativeNotifier } = await import("./notifier.js");
|
|
227
|
+
if (trayStale()) {
|
|
228
|
+
try {
|
|
229
|
+
ensureTrayBinary();
|
|
230
|
+
writeTrayCommands();
|
|
231
|
+
this.opts.log(" menu bar rebuilt from the new source");
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
this.opts.log(` menu bar not rebuilt: ${e.message.split("\n")[0]}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (notifierStale()) {
|
|
238
|
+
try {
|
|
239
|
+
ensureNativeNotifier();
|
|
240
|
+
this.opts.log(" alerts helper rebuilt from the new source");
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
this.opts.log(` alerts helper not rebuilt: ${e.message.split("\n")[0]}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const kind of ["notifier", "menubar"])
|
|
248
|
+
if (serviceInstalled(kind))
|
|
249
|
+
for (const l of restartService(kind))
|
|
250
|
+
this.opts.log(l);
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
this.opts.log(` could not bring the other services along: ${e.message.split("\n")[0]}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
219
256
|
repoBase() {
|
|
220
257
|
return this.client?.repos.find((r) => r.fingerprint === this.cfg.repo)?.base ?? null;
|
|
221
258
|
}
|
|
@@ -499,7 +536,8 @@ export class Worker {
|
|
|
499
536
|
this.opts.log(`! ${detail}`);
|
|
500
537
|
return { obligation: o, branch, worktree, ok: false, detail };
|
|
501
538
|
}
|
|
502
|
-
const
|
|
539
|
+
const reserved = reservedDecisionsNote(loadProfile(this.cfg));
|
|
540
|
+
const brief = makeBrief(branch) + baseNote(baseRef, baseSha) + (reserved ? `\n\n${reserved}` : "");
|
|
503
541
|
writeFileSync(join(worktree, ".pantheon-task.md"), brief);
|
|
504
542
|
let command;
|
|
505
543
|
let args;
|
|
@@ -655,12 +693,17 @@ export class Worker {
|
|
|
655
693
|
result = { ok: false, alreadyCurrent: false, from: VERSION, to: relayPkg ?? "?" };
|
|
656
694
|
this.opts.log(`! self-upgrade failed: ${e.message}`);
|
|
657
695
|
}
|
|
696
|
+
if (result.ok && !result.alreadyCurrent && result.to === result.from) {
|
|
697
|
+
this.opts.log(`* self-upgrade: the registry does not serve ${relayPkg} yet (installed ${result.to}); will check again`);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
658
700
|
if (result.ok && !result.alreadyCurrent) {
|
|
659
701
|
await client.send({
|
|
660
702
|
act: "NOTE", goal: null, after_seq: client.readSeq,
|
|
661
703
|
summary: `self-upgraded pantheon ${result.from} -> ${result.to}; worker restarting on the new build`,
|
|
662
704
|
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.`,
|
|
663
705
|
}).catch((e) => this.opts.log(`! could not report the upgrade on the ledger: ${e.message}`));
|
|
706
|
+
await this.bringMachineAlong();
|
|
664
707
|
this.opts.log(`* self-upgrade done: ${result.from} -> ${result.to}; restarting worker to load it`);
|
|
665
708
|
(this.opts.selfUpgrade?.restart ?? (() => process.exit(0)))();
|
|
666
709
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@join-pantheon/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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": {
|