@join-pantheon/cli 0.26.5 → 0.28.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 +83 -13
- package/dist/client.js +25 -3
- package/dist/doctor.js +47 -6
- package/dist/hooks-install.js +46 -1
- package/dist/install.js +29 -4
- package/dist/kiro-files.js +56 -0
- package/dist/machine.js +5 -1
- package/dist/measure.js +31 -3
- package/dist/network.js +394 -0
- 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/run-failure.js +1 -0
- package/dist/setup-guide.js +1 -1
- package/dist/tray.js +1 -1
- package/dist/update-check.js +2 -1
- package/dist/upgrade.js +2 -1
- package/dist/which.js +5 -3
- package/dist/worker.js +10 -3
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -404,7 +404,7 @@ with no human; the cap and lock governors under two workers per member.
|
|
|
404
404
|
|
|
405
405
|
### Which agent answers
|
|
406
406
|
|
|
407
|
-
The worker runs whichever agent CLI this machine has — `--agent claude-code|codex|cursor|copilot|auto`.
|
|
407
|
+
The worker runs whichever agent CLI this machine has — `--agent claude-code|codex|cursor|copilot|kiro|auto`.
|
|
408
408
|
With `auto` (the default) the order is: the agent a persona pins, then the one you chose at setup,
|
|
409
409
|
then PATH order — and a candidate that is not signed in is skipped and named in the run log, so a
|
|
410
410
|
run never fails on an expired session when another agent could have answered. If nothing is
|
|
@@ -418,6 +418,7 @@ actually works, with the same "no shell unless you said so" rule spelled in its
|
|
|
418
418
|
| codex | `--sandbox read-only` (codex has no edits-without-shell mode) | `--sandbox workspace-write` |
|
|
419
419
|
| cursor | proposes edits, never applies (`-p` without `--force`) | `--force` |
|
|
420
420
|
| copilot | `--allow-tool=pantheon-worker --allow-tool=write` (edits without shell, Copilot's `write` kind) | `--allow-all-tools` |
|
|
421
|
+
| kiro | an agent whose tools are pantheon's plus `read`, `write`, `grep`, `glob` — all pre-approved | the same, plus `shell` |
|
|
421
422
|
|
|
422
423
|
Claude Code, Cursor and Codex are verified with **real live woken runs** — an idle machine answering a
|
|
423
424
|
peer's ASK end to end. Codex quirks the tooling handles for
|
|
@@ -458,6 +459,58 @@ named as that, with `copilot login` as the fix.
|
|
|
458
459
|
also restrict MCP servers to an approved registry; the server is called `pantheon` and its only job
|
|
459
460
|
is moving messages between your teammates' agents — nothing here reads code or runs commands.
|
|
460
461
|
|
|
462
|
+
### Kiro
|
|
463
|
+
|
|
464
|
+
Kiro is one backend covering the **Kiro CLI** (`kiro-cli`) and the **Kiro IDE**, both of which read
|
|
465
|
+
`~/.kiro/settings/mcp.json`; `pantheon install --user` writes pantheon there. Everything below about
|
|
466
|
+
the CLI was checked live against kiro-cli 2.23.1.
|
|
467
|
+
|
|
468
|
+
**Sessions report presence on their own.** The entry names its agent (`PANTHEON_AGENT=kiro`), so a Kiro
|
|
469
|
+
session is labelled Kiro to your teammates and reports busy and idle through its own MCP server. Without
|
|
470
|
+
that label it would take the machine's main agent as its own, and on a Claude Code machine report nothing.
|
|
471
|
+
There is deliberately no repo-level Kiro file: Kiro uses a workspace-level server of the same name whole,
|
|
472
|
+
so a committed copy would replace each teammate's own entry.
|
|
473
|
+
|
|
474
|
+
**Approval.** The entry's `autoApprove` list carries your approval choice, and the Kiro IDE honours it.
|
|
475
|
+
The Kiro CLI ignores it — its default agent asks before each Pantheon call, and has no setting that
|
|
476
|
+
changes that.
|
|
477
|
+
|
|
478
|
+
**The `pantheon` agent, for the CLI.** In kiro-cli the only hooks that fire are the ones inside an agent
|
|
479
|
+
file; neither of its engines reads the standalone `.kiro/hooks/` files, and the built-in default agent
|
|
480
|
+
cannot carry any. So `pantheon hook install` adds an agent called `pantheon`: Kiro's default — the same
|
|
481
|
+
tools (`tools: ["*"]`) and your own MCP servers (`includeMcpJson: true`) — plus Pantheon's tools
|
|
482
|
+
pre-approved per your choice, a `userPromptSubmit` hook that reports a turn as started and prints
|
|
483
|
+
nothing, and a `stop` hook that holds the turn open while a teammate waits. It is opt-in and nothing
|
|
484
|
+
makes it your default:
|
|
485
|
+
|
|
486
|
+
```
|
|
487
|
+
kiro-cli chat --agent pantheon # this session
|
|
488
|
+
kiro-cli agent set-default pantheon # every session
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Checked live: with a teammate's question waiting and a prompt that said only "reply HELLO", the agent
|
|
492
|
+
replied, the stop hook held the turn, and it read, claimed and answered the question through Pantheon
|
|
493
|
+
without one approval prompt. An agent called `pantheon` that you wrote yourself is never overwritten.
|
|
494
|
+
|
|
495
|
+
**Woken runs.** A woken Kiro run is its own agent, `pantheon-worker`, in a `KIRO_HOME` of its own outside
|
|
496
|
+
the checkout. That matters: Kiro starts every MCP server in its config whatever the agent says, so your own
|
|
497
|
+
pantheon entry would otherwise join every run as a second server reporting you present. The run's home
|
|
498
|
+
holds only its agent and your Kiro settings; your sign-in, which lives in Kiro's data store, still applies.
|
|
499
|
+
The agent's `tools` and `allowedTools` are one list — Pantheon's server and `read`, `write`, `grep`,
|
|
500
|
+
`glob`, with `shell` only under `--allow-bash` — so nothing it can reach needs approval, and no trust flag
|
|
501
|
+
is passed. A repo's own committed Kiro MCP servers still start, as the repo's tooling; their tools are not
|
|
502
|
+
in the run's list, and one that fails does not stop the run.
|
|
503
|
+
|
|
504
|
+
**Sign in once with `kiro-cli login`, or set `KIRO_API_KEY`.** Signed out, Kiro does not fail: it opens a
|
|
505
|
+
browser to log in and waits. So a woken run is never started with Kiro signed out, even when a persona pins
|
|
506
|
+
Kiro or it is the only agent on the machine; the run is refused with the reason, and `pantheon doctor` says
|
|
507
|
+
so first. Pantheon reads the sign-in with `kiro-cli whoami --format json`, and only once Kiro has run on the
|
|
508
|
+
machine, because on a fresh install that one call creates `~/.kiro` and Kiro's data store.
|
|
509
|
+
|
|
510
|
+
Not supported: Kiro Crew, which is itself an always-on agent runner and would be a second answerer beside
|
|
511
|
+
Pantheon's worker, and Kiro Mobile, which has nowhere to load an MCP server. The Kiro IDE's own hooks are
|
|
512
|
+
not wired, because they could not be checked here; IDE sessions get the MCP entry and presence.
|
|
513
|
+
|
|
461
514
|
### Personas — telling the worker who to be
|
|
462
515
|
|
|
463
516
|
A worker with no instructions escalates too much and trusts too much. A **persona** is one
|
package/dist/agents.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { homedir, platform } from "node:os";
|
|
4
5
|
import { join, dirname, isAbsolute } from "node:path";
|
|
5
|
-
import { launcher } from "./install.js";
|
|
6
|
+
import { launcher, kiroDir } from "./install.js";
|
|
6
7
|
import { agentSpawn, which } from "./which.js";
|
|
7
8
|
import { PANTHEON_TOOL_NAMES, PANTHEON_TOOLS } from "./tools.js";
|
|
8
9
|
export function launchArgs(agent, prompt, name) {
|
|
@@ -10,6 +11,8 @@ export function launchArgs(agent, prompt, name) {
|
|
|
10
11
|
return ["--name", name, prompt];
|
|
11
12
|
if (agent === "copilot")
|
|
12
13
|
return ["-n", name, "-i", prompt];
|
|
14
|
+
if (agent === "kiro")
|
|
15
|
+
return ["chat", prompt];
|
|
13
16
|
return [prompt];
|
|
14
17
|
}
|
|
15
18
|
export const AGENTS = [
|
|
@@ -17,7 +20,11 @@ export const AGENTS = [
|
|
|
17
20
|
{ id: "codex", label: "Codex", binaries: ["codex"] },
|
|
18
21
|
{ id: "cursor", label: "Cursor", binaries: ["cursor-agent", "agent"] },
|
|
19
22
|
{ id: "copilot", label: "Copilot", binaries: ["copilot"] },
|
|
23
|
+
{ id: "kiro", label: "Kiro", binaries: ["kiro-cli"] },
|
|
20
24
|
];
|
|
25
|
+
export function kiroHome(home = homedir(), env = process.env) {
|
|
26
|
+
return kiroDir(home, env);
|
|
27
|
+
}
|
|
21
28
|
export function copilotHome(home = homedir(), env = process.env) {
|
|
22
29
|
return env.COPILOT_HOME || join(home, ".copilot");
|
|
23
30
|
}
|
|
@@ -57,6 +64,11 @@ export function detectForHooks(projectDir = process.cwd()) {
|
|
|
57
64
|
out.push(backend);
|
|
58
65
|
continue;
|
|
59
66
|
}
|
|
67
|
+
if (backend.id === "kiro") {
|
|
68
|
+
if (existsSync(kiroHome()) || existsSync(join(projectDir, ".kiro")))
|
|
69
|
+
out.push(backend);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
60
72
|
const userDir = backend.id === "claude-code" ? join(homedir(), ".claude")
|
|
61
73
|
: backend.id === "codex" ? join(homedir(), ".codex")
|
|
62
74
|
: join(homedir(), ".cursor");
|
|
@@ -107,7 +119,28 @@ export const SIGN_IN_CHECK = {
|
|
|
107
119
|
codex: { args: ["login", "status"], fix: "run `codex login`" },
|
|
108
120
|
cursor: { args: ["status"], fix: "run `cursor-agent login`" },
|
|
109
121
|
copilot: { args: [], fix: "run `copilot login` (or `gh auth login` — copilot uses gh's sign-in too)" },
|
|
122
|
+
kiro: { args: ["whoami", "--format", "json"], fix: "run `kiro-cli login` (or set KIRO_API_KEY)" },
|
|
110
123
|
};
|
|
124
|
+
export const NEEDS_SIGN_IN_BEFORE_SPAWN = new Set(["kiro"]);
|
|
125
|
+
export function kiroSignedIn(binary, run = runStatus, env = process.env, home = homedir(), os = platform()) {
|
|
126
|
+
if (env.KIRO_API_KEY)
|
|
127
|
+
return { ok: true, detail: "signed in (KIRO_API_KEY in the environment)" };
|
|
128
|
+
if (!kiroHasRunHere(home, env, os))
|
|
129
|
+
return { ok: false, detail: "never signed in on this machine" };
|
|
130
|
+
const r = run(binary, SIGN_IN_CHECK.kiro.args);
|
|
131
|
+
if (r.status === null)
|
|
132
|
+
return { ok: false, detail: `could not tell (${(r.out.split("\n")[0] ?? "").slice(0, 60) || "no answer"}), so it is not run unattended` };
|
|
133
|
+
const line = r.out.split("\n").map((l) => l.trim()).reverse().find((l) => l.startsWith("{"));
|
|
134
|
+
try {
|
|
135
|
+
const j = JSON.parse(line ?? "");
|
|
136
|
+
if (typeof j.accountType === "string" && j.accountType)
|
|
137
|
+
return { ok: true, detail: "signed in" };
|
|
138
|
+
if ("account" in j)
|
|
139
|
+
return j.account ? { ok: true, detail: "signed in" } : { ok: false, detail: "not signed in" };
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
return { ok: false, detail: "could not tell (kiro-cli whoami answered in a shape this build does not know), so it is not run unattended" };
|
|
143
|
+
}
|
|
111
144
|
export const COPILOT_TOKEN_VARS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
|
|
112
145
|
export function copilotSignedIn(run = runStatus, env = process.env, gh = () => !!cliOnPath("gh")) {
|
|
113
146
|
if (COPILOT_TOKEN_VARS.some((k) => env[k]))
|
|
@@ -132,10 +165,12 @@ const runStatus = (binary, args) => {
|
|
|
132
165
|
return { status: err.status ?? null, out: `${err.stdout ?? ""}${err.stderr ?? ""}`.trim() || (err.message ?? "") };
|
|
133
166
|
}
|
|
134
167
|
};
|
|
135
|
-
export const AGENT_HOME_DIR = { "claude-code": ".claude", codex: ".codex", cursor: ".cursor", copilot: ".copilot" };
|
|
168
|
+
export const AGENT_HOME_DIR = { "claude-code": ".claude", codex: ".codex", cursor: ".cursor", copilot: ".copilot", kiro: ".kiro" };
|
|
136
169
|
export function signedIn(backend, binary, run = runStatus, home = homedir()) {
|
|
137
170
|
if (backend.id === "copilot")
|
|
138
171
|
return copilotSignedIn(run);
|
|
172
|
+
if (backend.id === "kiro")
|
|
173
|
+
return kiroSignedIn(binary, run, process.env, home);
|
|
139
174
|
if (!existsSync(join(home, AGENT_HOME_DIR[backend.id])))
|
|
140
175
|
return { ok: false, detail: "never signed in on this machine" };
|
|
141
176
|
const check = SIGN_IN_CHECK[backend.id];
|
|
@@ -159,14 +194,14 @@ export function chooseAgent(preferred, opts = {}) {
|
|
|
159
194
|
if (preferred && preferred !== "auto") {
|
|
160
195
|
const backend = agentById(preferred);
|
|
161
196
|
if (!backend)
|
|
162
|
-
throw new Error(`unknown agent ${preferred} — use claude-code, codex, cursor, copilot, or auto`);
|
|
197
|
+
throw new Error(`unknown agent ${preferred} — use claude-code, codex, cursor, copilot, kiro, or auto`);
|
|
163
198
|
const hit = available.find((a) => a.backend.id === backend.id);
|
|
164
199
|
if (!hit)
|
|
165
200
|
throw new Error(`${backend.label} is not on PATH (looked for ${backend.binaries.join(", ")})`);
|
|
166
201
|
return { backend, binary: hit.binary, signIn: state(backend, hit.binary), because: "pinned", skipped: [] };
|
|
167
202
|
}
|
|
168
203
|
if (!available.length)
|
|
169
|
-
throw new Error("no agent CLI on PATH — install claude, codex, cursor-agent, or
|
|
204
|
+
throw new Error("no agent CLI on PATH — install claude, codex, cursor-agent, copilot, or kiro-cli");
|
|
170
205
|
const order = [];
|
|
171
206
|
const wanted = opts.prefer ? agentById(opts.prefer) : undefined;
|
|
172
207
|
const chosen = wanted ? available.find((a) => a.backend.id === wanted.id) : undefined;
|
|
@@ -182,9 +217,14 @@ export function chooseAgent(preferred, opts = {}) {
|
|
|
182
217
|
return { backend: c.backend, binary: c.binary, ...(s ? { signIn: s } : {}), because: c.because, skipped };
|
|
183
218
|
skipped.push({ backend: c.backend, why: `${s.detail} — ${SIGN_IN_CHECK[c.backend.id].fix}` });
|
|
184
219
|
}
|
|
185
|
-
const first = order[0];
|
|
220
|
+
const first = order.find((o) => !NEEDS_SIGN_IN_BEFORE_SPAWN.has(o.backend.id)) ?? order[0];
|
|
186
221
|
return { backend: first.backend, binary: first.binary, signIn: state(first.backend, first.binary), because: `${first.because} (none signed in)`, skipped };
|
|
187
222
|
}
|
|
223
|
+
export function unattendedRefusal(c) {
|
|
224
|
+
if (!NEEDS_SIGN_IN_BEFORE_SPAWN.has(c.backend.id) || c.signIn?.ok)
|
|
225
|
+
return null;
|
|
226
|
+
return `${c.backend.label} is not signed in (${c.signIn?.detail ?? "not checked"}), and signed out it opens a browser to log in, so it is not started while you are away — ${SIGN_IN_CHECK[c.backend.id].fix}`;
|
|
227
|
+
}
|
|
188
228
|
export function describeChoice(c) {
|
|
189
229
|
const head = `${c.backend.label}${c.signIn ? ` — ${c.signIn.detail}` : ""} (${c.because})`;
|
|
190
230
|
return c.skipped.length ? `${head}; skipped ${c.skipped.map((s) => `${s.backend.label} — ${s.why}`).join("; ")}` : head;
|
|
@@ -283,6 +323,30 @@ export function headlessSpawn(opts) {
|
|
|
283
323
|
...(opts.runHome && existsSync(settings) ? { copyFiles: [{ from: settings, to: join(home, "settings.json") }] } : {}),
|
|
284
324
|
};
|
|
285
325
|
}
|
|
326
|
+
if (opts.backend.id === "kiro") {
|
|
327
|
+
const home = opts.runHome ? join(opts.runHome, "kiro") : join(opts.worktree, ".pantheon", "kiro-home");
|
|
328
|
+
const tools = [...KIRO_WORKER_TOOLS, ...(opts.allowBash ? ["shell"] : []), `@${KIRO_WORKER_SERVER}`];
|
|
329
|
+
const agentName = `${KIRO_WORKER_AGENT}-${randomBytes(8).toString("hex")}`;
|
|
330
|
+
const settings = join(kiroHome(), "settings", "cli.json");
|
|
331
|
+
return {
|
|
332
|
+
command: opts.binary,
|
|
333
|
+
args: ["chat", "--no-interactive", "--agent", agentName, "--require-mcp-startup", "--wrap", "never", opts.prompt],
|
|
334
|
+
env: { ...procEnv, KIRO_HOME: home, KIRO_NO_AUTO_UPDATE: "1" },
|
|
335
|
+
writeFiles: [{
|
|
336
|
+
path: join(home, "agents", `${KIRO_WORKER_AGENT}.json`),
|
|
337
|
+
contents: JSON.stringify({
|
|
338
|
+
name: agentName,
|
|
339
|
+
description: "A Pantheon run: answers a teammate's request on an isolated branch, while its human is away",
|
|
340
|
+
tools,
|
|
341
|
+
allowedTools: tools,
|
|
342
|
+
includeMcpJson: false,
|
|
343
|
+
mcpServers: { [KIRO_WORKER_SERVER]: { command: l.command, args: l.args, env: fileEnv } },
|
|
344
|
+
resources: ["file://.kiro/steering/**/*.md"],
|
|
345
|
+
}, null, 2),
|
|
346
|
+
}],
|
|
347
|
+
...(opts.runHome && existsSync(settings) ? { copyFiles: [{ from: settings, to: join(home, "settings", "cli.json") }] } : {}),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
286
350
|
return {
|
|
287
351
|
command: opts.binary,
|
|
288
352
|
args: [
|
|
@@ -310,6 +374,20 @@ export function headlessSpawn(opts) {
|
|
|
310
374
|
};
|
|
311
375
|
}
|
|
312
376
|
export const COPILOT_WORKER_SERVER = "pantheon-worker";
|
|
377
|
+
export function kiroDataDir(home = homedir(), env = process.env, os = platform()) {
|
|
378
|
+
if (env.KIRO_DATA_DIR)
|
|
379
|
+
return env.KIRO_DATA_DIR;
|
|
380
|
+
if (os === "darwin")
|
|
381
|
+
return join(home, "Library", "Application Support", "kiro-cli");
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
export function kiroHasRunHere(home = homedir(), env = process.env, os = platform()) {
|
|
385
|
+
const data = kiroDataDir(home, env, os);
|
|
386
|
+
return data ? existsSync(join(data, "data.sqlite3")) : existsSync(kiroDir(home, env));
|
|
387
|
+
}
|
|
388
|
+
export const KIRO_WORKER_AGENT = "pantheon-worker";
|
|
389
|
+
export const KIRO_WORKER_SERVER = "pantheon";
|
|
390
|
+
export const KIRO_WORKER_TOOLS = ["read", "write", "grep", "glob"];
|
|
313
391
|
function readCopilotConfig() {
|
|
314
392
|
try {
|
|
315
393
|
const doc = JSON.parse(readFileSync(join(copilotHome(), "config.json"), "utf8"));
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,8 @@ import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, d
|
|
|
29
29
|
import { assess } from "./untrusted.js";
|
|
30
30
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
31
31
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
32
|
-
import { resolveDataDir } from "./config.js";
|
|
32
|
+
import { resolveDataDir, saveAll } from "./config.js";
|
|
33
|
+
import { currentPlan, installNetwork, redactProxy, normalizeProxy, usedSystemFallback } from "./network.js";
|
|
33
34
|
import { createInterface } from "node:readline";
|
|
34
35
|
import { homedir } from "node:os";
|
|
35
36
|
import { removeHook, decideStop, writeIntent, clearIntent, readIntent, installHooks, agentHookStates } from "./notify.js";
|
|
@@ -442,6 +443,8 @@ function printReady(tools) {
|
|
|
442
443
|
oneTime.set("cursor", `cursor enable the "pantheon" MCP server if it asks`);
|
|
443
444
|
if (/codex/i.test(t.tool))
|
|
444
445
|
oneTime.set("codex", `codex run /hooks inside codex once to approve the wake-up hook`);
|
|
446
|
+
if (/kiro/i.test(t.tool))
|
|
447
|
+
oneTime.set("kiro", `kiro run kiro-cli chat --agent pantheon (or kiro-cli agent set-default pantheon) to keep working while a teammate waits; stay signed in — a woken run never starts Kiro signed out, since Kiro would open a browser`);
|
|
445
448
|
if (/copilot/i.test(t.tool))
|
|
446
449
|
oneTime.set("copilot", `copilot say yes once when it asks to trust this folder; on a Copilot Business or Enterprise plan an org owner must have the "MCP servers in Copilot" policy on`);
|
|
447
450
|
}
|
|
@@ -501,7 +504,7 @@ ${B("control")}
|
|
|
501
504
|
pantheon admit <who> let someone in (--may-wake to allow unattended work here)
|
|
502
505
|
pantheon admit <who> --revoke remove them
|
|
503
506
|
pantheon worker stay answerable while you are away
|
|
504
|
-
(--agent claude-code|codex|cursor|copilot|auto, --persona <name>)
|
|
507
|
+
(--agent claude-code|codex|cursor|copilot|kiro|auto, --persona <name>)
|
|
505
508
|
pantheon policy what this machine's worker may do, in one screen (grant/revoke self-upgrade)
|
|
506
509
|
pantheon persona how a woken worker should behave: list / init <name> / show <name>
|
|
507
510
|
pantheon notifier alert you when a task requires your decision
|
|
@@ -514,11 +517,12 @@ ${B("control")}
|
|
|
514
517
|
pantheon approvals decisions waiting on you; show or answer one
|
|
515
518
|
pantheon stop [relay|worker|notifier|menubar|all] stop it
|
|
516
519
|
pantheon hook install your agent keeps working when a peer is waiting on it
|
|
517
|
-
(auto-detects claude / codex / cursor / copilot on this machine)
|
|
520
|
+
(auto-detects claude / codex / cursor / copilot / kiro on this machine)
|
|
518
521
|
pantheon spend woken runs on this machine, and what pantheon cannot see
|
|
519
522
|
pantheon leave remove what pantheon put on this machine
|
|
520
523
|
pantheon panic cut off all unattended work on this machine, now
|
|
521
524
|
pantheon doctor [--share] check this machine end to end; --share prints it without names or paths, to paste to us
|
|
525
|
+
pantheon network [--proxy <url> | --direct | --clear] how this machine reaches the relay; save a proxy for every Pantheon process here
|
|
522
526
|
pantheon setup three questions — your name, your role, may a woken run execute commands —
|
|
523
527
|
then one screen of defaults to keep or change: who may wake this machine,
|
|
524
528
|
alerts, what you personally decide, playbook, base branch. Re-run any time.
|
|
@@ -547,7 +551,26 @@ ${B("less common")}
|
|
|
547
551
|
pantheon version the pantheon version this machine is running
|
|
548
552
|
`;
|
|
549
553
|
const QUIET = new Set(["mcp", "hook", "pending", "version", "tray-status"]);
|
|
554
|
+
function rememberWorkingProxy() {
|
|
555
|
+
try {
|
|
556
|
+
const plan = currentPlan();
|
|
557
|
+
const via = plan.https ?? plan.http;
|
|
558
|
+
if (!via || (plan.source !== "env" && !(plan.fallbackOnly && usedSystemFallback())))
|
|
559
|
+
return;
|
|
560
|
+
const all = loadAll();
|
|
561
|
+
if (!all || all.network?.source === "pantheon network" || all.network?.proxy === via)
|
|
562
|
+
return;
|
|
563
|
+
const custom = plan.noProxy.filter((h) => !["localhost", "127.0.0.1", "::1", "[::1]"].includes(h));
|
|
564
|
+
saveAll({ ...all, network: { proxy: via, ...(custom.length ? { no_proxy: custom.join(",") } : {}), source: plan.source, saved_at: Date.now() } });
|
|
565
|
+
console.error(D(` saved the proxy ${redactProxy(via)} for this machine's other Pantheon processes (worker, editors) — change it with pantheon network`));
|
|
566
|
+
}
|
|
567
|
+
catch { }
|
|
568
|
+
}
|
|
550
569
|
async function main() {
|
|
570
|
+
try {
|
|
571
|
+
await installNetwork();
|
|
572
|
+
}
|
|
573
|
+
catch { }
|
|
551
574
|
if (!QUIET.has(cmd)) {
|
|
552
575
|
try {
|
|
553
576
|
console.error(D(updateBanner(VERSION)));
|
|
@@ -563,6 +586,35 @@ async function main() {
|
|
|
563
586
|
return interactiveOnboard();
|
|
564
587
|
}
|
|
565
588
|
switch (cmd) {
|
|
589
|
+
case "network": {
|
|
590
|
+
const all = loadAll() ?? die("not configured — sign up or join a team first");
|
|
591
|
+
const set = flag("proxy");
|
|
592
|
+
if (set !== undefined) {
|
|
593
|
+
const p = normalizeProxy(set) ?? die(`"${set}" is not a proxy address — use http://host:port (add user:password@ if it needs a login)`);
|
|
594
|
+
saveAll({ ...all, network: { proxy: p, ...(flag("no-proxy") ? { no_proxy: flag("no-proxy") } : {}), source: "pantheon network", saved_at: Date.now() } });
|
|
595
|
+
console.error(`${B("saved")} every Pantheon process on this machine now reaches the relay through ${redactProxy(p)}`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (argv.includes("--direct")) {
|
|
599
|
+
saveAll({ ...all, network: { proxy: "", source: "pantheon network --direct", saved_at: Date.now() } });
|
|
600
|
+
console.error(`${B("saved")} direct — set PANTHEON_PROXY=none in a shell to do the same for one command`);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (argv.includes("--clear")) {
|
|
604
|
+
const { network: _drop, ...rest } = all;
|
|
605
|
+
saveAll(rest);
|
|
606
|
+
console.error(`${B("cleared")} — Pantheon follows HTTPS_PROXY / NO_PROXY and the system settings again`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const plan = currentPlan();
|
|
610
|
+
const via = plan.https ?? plan.http;
|
|
611
|
+
console.log(`${B("network")} ${via ? `through ${redactProxy(via)}` : "direct"}${plan.source !== "none" ? D(` from ${plan.source}${plan.fallbackOnly ? ", used only if a direct connection fails" : ""}`) : ""}`);
|
|
612
|
+
if (via)
|
|
613
|
+
console.log(D(` not for: ${plan.noProxy.join(", ")}`));
|
|
614
|
+
if (plan.pac)
|
|
615
|
+
console.log(D(` this machine uses an auto-config script (${plan.pac}), which Pantheon cannot read — if the relay is unreachable, save the proxy it points to: pantheon network --proxy http://host:port`));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
566
618
|
case "start": {
|
|
567
619
|
const port = Number(flag("port", "8787"));
|
|
568
620
|
const setupFlags = parseSetupFlags(argv);
|
|
@@ -593,6 +645,7 @@ async function main() {
|
|
|
593
645
|
for (const w of r.warnings)
|
|
594
646
|
console.error(`\x1b[33m ! ${w}\x1b[0m`);
|
|
595
647
|
printGranted(r.granted);
|
|
648
|
+
rememberWorkingProxy();
|
|
596
649
|
if (!r.claimed) {
|
|
597
650
|
console.error(`\n\x1b[31m No team was created.\x1b[0m ${D("Fix the warning above, then run `pantheon start` again.")}`);
|
|
598
651
|
if (r.relay)
|
|
@@ -792,6 +845,7 @@ async function main() {
|
|
|
792
845
|
for (const w of r.warnings)
|
|
793
846
|
console.error(`\x1b[33m ! ${w}\x1b[0m`);
|
|
794
847
|
printGranted(r.granted);
|
|
848
|
+
rememberWorkingProxy();
|
|
795
849
|
printReady(r.tools);
|
|
796
850
|
{
|
|
797
851
|
const joined = load();
|
|
@@ -1332,13 +1386,13 @@ async function main() {
|
|
|
1332
1386
|
const sub = argv[1] ?? "status";
|
|
1333
1387
|
const hookCfg = resolveForHook();
|
|
1334
1388
|
const sessionOf = (raw) => {
|
|
1389
|
+
let fromJson;
|
|
1335
1390
|
try {
|
|
1336
1391
|
const j = JSON.parse(raw);
|
|
1337
|
-
|
|
1338
|
-
}
|
|
1339
|
-
catch {
|
|
1340
|
-
return "unknown";
|
|
1392
|
+
fromJson = j.session_id ?? j.sessionId ?? j.conversation_id;
|
|
1341
1393
|
}
|
|
1394
|
+
catch { }
|
|
1395
|
+
return fromJson ?? process.env.KIRO_SESSION_ID ?? "unknown";
|
|
1342
1396
|
};
|
|
1343
1397
|
const reportIdle = async (session, seen) => {
|
|
1344
1398
|
if (!hookCfg)
|
|
@@ -1353,6 +1407,22 @@ async function main() {
|
|
|
1353
1407
|
}
|
|
1354
1408
|
return false;
|
|
1355
1409
|
};
|
|
1410
|
+
if (sub === "kiro-prompt") {
|
|
1411
|
+
let raw = "";
|
|
1412
|
+
try {
|
|
1413
|
+
for await (const chunk of process.stdin)
|
|
1414
|
+
raw += chunk;
|
|
1415
|
+
}
|
|
1416
|
+
catch { }
|
|
1417
|
+
try {
|
|
1418
|
+
if (hookCfg) {
|
|
1419
|
+
const { reportActivity } = await import("./pending.js");
|
|
1420
|
+
await reportActivity({ cfg: hookCfg, session: `hook:${sessionOf(raw)}`, state: "busy" });
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
catch { }
|
|
1424
|
+
process.exit(0);
|
|
1425
|
+
}
|
|
1356
1426
|
if (sub === "prompt") {
|
|
1357
1427
|
let raw = "";
|
|
1358
1428
|
try {
|
|
@@ -1462,7 +1532,7 @@ async function main() {
|
|
|
1462
1532
|
process.exit(0);
|
|
1463
1533
|
}
|
|
1464
1534
|
}
|
|
1465
|
-
if (sub === "codex-stop" || sub === "copilot-stop") {
|
|
1535
|
+
if (sub === "codex-stop" || sub === "copilot-stop" || sub === "kiro-stop") {
|
|
1466
1536
|
let raw = "";
|
|
1467
1537
|
try {
|
|
1468
1538
|
for await (const chunk of process.stdin)
|
|
@@ -1571,7 +1641,7 @@ async function main() {
|
|
|
1571
1641
|
}
|
|
1572
1642
|
const states = agentHookStates();
|
|
1573
1643
|
if (!states.length) {
|
|
1574
|
-
console.log(`${D("not installed")}\n Install with: ${C("pantheon hook install")} (auto-detects claude / codex / cursor / copilot on PATH)`);
|
|
1644
|
+
console.log(`${D("not installed")}\n Install with: ${C("pantheon hook install")} (auto-detects claude / codex / cursor / copilot / kiro on PATH)`);
|
|
1575
1645
|
return;
|
|
1576
1646
|
}
|
|
1577
1647
|
for (const s of states) {
|
|
@@ -1654,7 +1724,7 @@ async function main() {
|
|
|
1654
1724
|
else {
|
|
1655
1725
|
console.error(` config: none to remove`);
|
|
1656
1726
|
}
|
|
1657
|
-
console.error(`\n ${D("Not touched: the MCP entries in Claude Code and
|
|
1727
|
+
console.error(`\n ${D("Not touched: the MCP entries in Claude Code, Cursor, Copilot, VS Code and Kiro (pantheon install wrote them; remove them there),")}`);
|
|
1658
1728
|
console.error(` ${D("the relay's ledger on disk, and anything you pushed to git. Codex's entry and approval line were removed above.")}`);
|
|
1659
1729
|
if (cfg) {
|
|
1660
1730
|
console.error(`\n ${D(`Your teammates still list you in "${cfg.space}". Ask one of them to run:`)}`);
|
|
@@ -2021,7 +2091,7 @@ async function main() {
|
|
|
2021
2091
|
const publicChange = name !== undefined || title !== undefined || responsibilities !== undefined ||
|
|
2022
2092
|
humanRequired !== undefined || agentCanDecide !== undefined || capabilities !== undefined || keywords !== undefined;
|
|
2023
2093
|
if (!surface && !publicChange) {
|
|
2024
|
-
die("usage: pantheon profile set [--surface codex|claude-code|cursor|copilot] [--name name] [--title title] [--responsibilities a,b] [--human-required a,b] [--agent-can-decide a,b] [--keywords 'domain=word|word;domain=word']");
|
|
2094
|
+
die("usage: pantheon profile set [--surface codex|claude-code|cursor|copilot|kiro] [--name name] [--title title] [--responsibilities a,b] [--human-required a,b] [--agent-can-decide a,b] [--keywords 'domain=word|word;domain=word']");
|
|
2025
2095
|
}
|
|
2026
2096
|
if (flag("space")) {
|
|
2027
2097
|
const pf = loadAll() ?? die("not configured");
|
|
@@ -2462,7 +2532,7 @@ async function main() {
|
|
|
2462
2532
|
if (hud.prompt) {
|
|
2463
2533
|
const agent = pickExplainAgent(hud.tool ?? p.agent.primary_surface, detectAvailable());
|
|
2464
2534
|
if (!agent)
|
|
2465
|
-
die("no agent CLI found on this machine (claude, cursor-agent, codex, or
|
|
2535
|
+
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2466
2536
|
console.error(D(`opening ${agent.id}…`));
|
|
2467
2537
|
const { spawnSync } = await import("node:child_process");
|
|
2468
2538
|
{
|
|
@@ -2494,7 +2564,7 @@ async function main() {
|
|
|
2494
2564
|
}
|
|
2495
2565
|
const agent = pickExplainAgent(loadProfile(cfg).agent.primary_surface, detectAvailable());
|
|
2496
2566
|
if (!agent)
|
|
2497
|
-
die("no agent CLI found on this machine (claude, cursor-agent, codex, or
|
|
2567
|
+
die("no agent CLI found on this machine (claude, cursor-agent, codex, copilot, or kiro-cli)");
|
|
2498
2568
|
console.error(D(`opening ${agent.id} with the question…`));
|
|
2499
2569
|
const { spawnSync } = await import("node:child_process");
|
|
2500
2570
|
{
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,7 @@ import WebSocket from "ws";
|
|
|
2
2
|
import { signChallenge } from "./identity.js";
|
|
3
3
|
import { newId } from "./protocol.js";
|
|
4
4
|
import { VERSION } from "./version.js";
|
|
5
|
+
import { describeNetError, explainNetError, trySystemFallback, wsOptions } from "./network.js";
|
|
5
6
|
export class PantheonClient {
|
|
6
7
|
cfg;
|
|
7
8
|
onEvent;
|
|
@@ -74,7 +75,28 @@ export class PantheonClient {
|
|
|
74
75
|
connect() {
|
|
75
76
|
if (!this.everConnected)
|
|
76
77
|
this.closed = false;
|
|
77
|
-
|
|
78
|
+
const first = this.localOverride ?? this.cfg.relay;
|
|
79
|
+
return this.attempt(first).catch(async (e) => {
|
|
80
|
+
if (!this.localOverride && trySystemFallback(first, e)) {
|
|
81
|
+
this.closed = false;
|
|
82
|
+
try {
|
|
83
|
+
return await this.attempt(first);
|
|
84
|
+
}
|
|
85
|
+
catch (e2) {
|
|
86
|
+
e = e2;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return this.afterFailure(e);
|
|
90
|
+
}).catch((e) => {
|
|
91
|
+
if (!explainNetError(e))
|
|
92
|
+
throw e;
|
|
93
|
+
const out = new Error(describeNetError(e));
|
|
94
|
+
out.code = e.code;
|
|
95
|
+
throw out;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async afterFailure(e) {
|
|
99
|
+
{
|
|
78
100
|
if (this.localOverride)
|
|
79
101
|
throw e;
|
|
80
102
|
const transport = /ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EHOSTUNREACH|ENETUNREACH|handshake has timed out|socket hang up/i
|
|
@@ -88,13 +110,13 @@ export class PantheonClient {
|
|
|
88
110
|
this.localOverride = local;
|
|
89
111
|
this.usingLoopback = true;
|
|
90
112
|
return this.attempt(local);
|
|
91
|
-
}
|
|
113
|
+
}
|
|
92
114
|
}
|
|
93
115
|
attempt(address) {
|
|
94
116
|
return new Promise((resolve, reject) => {
|
|
95
117
|
let settled = false;
|
|
96
118
|
this.ws?.terminate();
|
|
97
|
-
const ws = new WebSocket(address, { handshakeTimeout: 6000 });
|
|
119
|
+
const ws = new WebSocket(address, { handshakeTimeout: 6000, ...wsOptions(address) });
|
|
98
120
|
this.ws = ws;
|
|
99
121
|
ws.on("open", () => { });
|
|
100
122
|
ws.on("message", (raw) => {
|
package/dist/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { kiroDefaultIsPantheon, readKiroAgent, kiroAgentFile } from "./kiro-files.js";
|
|
1
2
|
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { instructionBudget } from "./mcp-instructions.js";
|
|
3
4
|
import { describeWindows, scanMcpWindows } from "./mcp-windows.js";
|
|
@@ -14,11 +15,12 @@ import { approvalSummary, chosenApproval, onPath } from "./install.js";
|
|
|
14
15
|
import { hookState, readIntent, agentHookStates } from "./notify.js";
|
|
15
16
|
import { loadPersonas } from "./persona.js";
|
|
16
17
|
import { workerProc, menubarProc, notifierProc } from "./machine.js";
|
|
17
|
-
import { chooseAgent, describeChoice } from "./agents.js";
|
|
18
|
+
import { chooseAgent, describeChoice, unattendedRefusal } from "./agents.js";
|
|
18
19
|
import { onPathNamed } from "./which.js";
|
|
19
20
|
import { VERSION } from "./version.js";
|
|
20
21
|
import { serviceInstalled, servicesUnavailable } from "./service.js";
|
|
21
22
|
import { hostEnv, platformName } from "./host-env.js";
|
|
23
|
+
import { currentPlan, explainNetError, proxyFor, redactProxy } from "./network.js";
|
|
22
24
|
const HOSTED_RELAY = "relay.joinpantheon.network";
|
|
23
25
|
const git = (args, cwd) => {
|
|
24
26
|
try {
|
|
@@ -156,10 +158,10 @@ export async function doctor(projectDir) {
|
|
|
156
158
|
if (branch)
|
|
157
159
|
add("pass", "git branch", branch);
|
|
158
160
|
}
|
|
159
|
-
const agents = ["claude", "codex", "cursor-agent", "copilot"].filter((a) => onPathNamed(a));
|
|
161
|
+
const agents = ["claude", "codex", "cursor-agent", "copilot", "kiro-cli"].filter((a) => onPathNamed(a));
|
|
160
162
|
agents.length
|
|
161
163
|
? add("pass", "agent cli", agents.join(", "), undefined, agents.join(", "))
|
|
162
|
-
: add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot on PATH");
|
|
164
|
+
: add("warn", "agent cli", "found none of claude / codex / cursor-agent / copilot / kiro-cli on PATH", "fine if you only drive agents through a GUI (Conductor, Cursor, VS Code, the Kiro IDE), but `pantheon worker` needs a CLI to spawn", "none of claude / codex / cursor-agent / copilot / kiro-cli on PATH");
|
|
163
165
|
if (agents.length) {
|
|
164
166
|
try {
|
|
165
167
|
const choice = chooseAgent("auto", { prefer: prof.agent.primary_surface });
|
|
@@ -167,7 +169,10 @@ export async function doctor(projectDir) {
|
|
|
167
169
|
const bad = choice.signIn && !choice.signIn.ok && !choice.signIn.unknown;
|
|
168
170
|
const fellBack = !!prof.agent.primary_surface && choice.backend.id !== prof.agent.primary_surface && agents.length > 0;
|
|
169
171
|
const shareLine = `${choice.backend.label} — ${choice.signIn?.detail ?? "not checked"}${choice.skipped.length ? `; ${choice.skipped.length} skipped (not signed in)` : ""}`;
|
|
170
|
-
|
|
172
|
+
const refused = unattendedRefusal(choice);
|
|
173
|
+
if (refused)
|
|
174
|
+
add("warn", "worker agent", line, `woken runs are refused here, not tried: ${refused}`, shareLine);
|
|
175
|
+
else if (bad)
|
|
171
176
|
add("warn", "worker agent", line, `nothing is signed in, so every woken run here fails until you sign in: ${choice.skipped.map((x) => x.why).join("; ") || "sign in to the agent CLI"}`, shareLine);
|
|
172
177
|
else if (fellBack)
|
|
173
178
|
add("warn", "worker agent", line, `you chose ${prof.agent.primary_surface} at setup; runs use ${choice.backend.label} until it is signed in again`, shareLine);
|
|
@@ -175,7 +180,13 @@ export async function doctor(projectDir) {
|
|
|
175
180
|
add("pass", "worker agent", line, undefined, shareLine);
|
|
176
181
|
}
|
|
177
182
|
catch (e) {
|
|
178
|
-
add("warn", "worker agent", e.message, "pantheon setup --agent <claude-code|codex|cursor|copilot> names one that is installed", "could not be chosen");
|
|
183
|
+
add("warn", "worker agent", e.message, "pantheon setup --agent <claude-code|codex|cursor|copilot|kiro> names one that is installed", "could not be chosen");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
{
|
|
187
|
+
const here = readKiroAgent(kiroAgentFile(join(projectDir, ".kiro")));
|
|
188
|
+
if (here && !here.ours) {
|
|
189
|
+
add("warn", "kiro agent", "this repo defines its own agent called pantheon — Kiro uses it instead of yours in this repo", "workspace agents win over your own in Kiro; read .kiro/agents/pantheon.json before running kiro-cli chat --agent pantheon here", "this repo defines its own pantheon agent");
|
|
179
190
|
}
|
|
180
191
|
}
|
|
181
192
|
const budget = instructionBudget(cfg);
|
|
@@ -212,6 +223,20 @@ export async function doctor(projectDir) {
|
|
|
212
223
|
else {
|
|
213
224
|
add("pass", "transport", url.protocol === "wss:" ? "wss:// (TLS)" : `ws:// on a private address (${url.host})`, undefined, url.protocol === "wss:" ? "wss:// (TLS)" : "ws:// on a private address");
|
|
214
225
|
}
|
|
226
|
+
const plan = currentPlan();
|
|
227
|
+
const via = proxyFor(cfg.relay, plan);
|
|
228
|
+
if (via) {
|
|
229
|
+
add("pass", "network", `through the proxy ${redactProxy(via)} (from ${plan.source})`, undefined, `through a proxy (${plan.source})`);
|
|
230
|
+
}
|
|
231
|
+
else if (plan.fallbackOnly && (plan.https || plan.http)) {
|
|
232
|
+
add("pass", "network", `direct; the ${plan.source === "macos" ? "macOS" : "Windows"} proxy ${redactProxy(plan.https ?? plan.http)} is used only if a direct connection fails`, undefined, `direct, system proxy as fallback`);
|
|
233
|
+
}
|
|
234
|
+
else if (plan.pac) {
|
|
235
|
+
add("warn", "network", `direct, but this machine uses a proxy auto-config script, which Pantheon cannot read`, "if the relay turns out unreachable below, save the proxy that script points to: pantheon network --proxy http://host:port", "direct; PAC script present");
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
add("pass", "network", "direct — no proxy set", undefined, "direct");
|
|
239
|
+
}
|
|
215
240
|
const health = `${cfg.relay.replace(/^ws/, "http")}/health`;
|
|
216
241
|
const where = hosted ? "hosted" : "self-hosted";
|
|
217
242
|
try {
|
|
@@ -235,6 +260,11 @@ export async function doctor(projectDir) {
|
|
|
235
260
|
}
|
|
236
261
|
catch (e) {
|
|
237
262
|
const msg = e.message;
|
|
263
|
+
const nx = explainNetError(e);
|
|
264
|
+
if (nx) {
|
|
265
|
+
add("fail", "relay", `cannot reach ${health} — ${nx.why}`, nx.fix, `unreachable (${where}): ${nx.share}`);
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
238
268
|
add("fail", "relay", `cannot reach ${health} — ${msg}`, "start it with `pantheon relay`, or check the tunnel/tailnet. Your teammate must be able to reach the SAME address.", `unreachable (${where}): ${/abort|timeout/i.test(msg) ? "timed out" : /ECONNREFUSED/.test(msg) ? "connection refused" : /ENOTFOUND|EAI_AGAIN/.test(msg) ? "name did not resolve" : "fetch failed"}`);
|
|
239
269
|
return out;
|
|
240
270
|
}
|
|
@@ -242,6 +272,12 @@ export async function doctor(projectDir) {
|
|
|
242
272
|
let joined = false;
|
|
243
273
|
await client.connect().then(() => { joined = true; }).catch(() => { });
|
|
244
274
|
if (!joined) {
|
|
275
|
+
const nx = client.pending ? null : explainNetError(new Error(client.lastError ?? ""));
|
|
276
|
+
if (nx) {
|
|
277
|
+
add("fail", "live link", `the health check got through but the live connection did not — ${nx.why}`, nx.fix, `live connection failed: ${nx.share}`);
|
|
278
|
+
client.close();
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
245
281
|
client.pending
|
|
246
282
|
? add("fail", "membership", "registered but not admitted", `ask an admitted member to run: pantheon admit ${cfg.member} --may-wake (confirm fingerprint ${fingerprint(cfg.publicKey)} with them by voice first)`, "registered, not admitted")
|
|
247
283
|
: add("fail", "membership", `refused: ${client.lastError ?? "unknown"}`, "if this is KEY_MISMATCH, someone else already registered your member name — pick another with --me, or have them revoke it", `refused: ${String(client.lastError ?? "unknown").split(/[\s:]/)[0]}`);
|
|
@@ -302,7 +338,7 @@ export async function doctor(projectDir) {
|
|
|
302
338
|
}
|
|
303
339
|
}
|
|
304
340
|
if (!hookStates.length && !hook.installed) {
|
|
305
|
-
add("warn", "wake-up hook", "not installed — your agent will not notice a peer's message until you ask it to", "pantheon hook install --user (auto-detects claude / codex / cursor / copilot on PATH)", "not installed");
|
|
341
|
+
add("warn", "wake-up hook", "not installed — your agent will not notice a peer's message until you ask it to", "pantheon hook install --user (auto-detects claude / codex / cursor / copilot / kiro on PATH)", "not installed");
|
|
306
342
|
}
|
|
307
343
|
else if (!reportsActivity) {
|
|
308
344
|
add("warn", "wake-up hook", `installed (${hook.events?.join(", ")}) but predates the activity hook, so a worker cannot tell your idle editor from one you are using`, "re-run `pantheon hook install --user` to add the UserPromptSubmit half", `installed (${hook.events?.join(", ")}), predates the activity hook`);
|
|
@@ -316,6 +352,11 @@ export async function doctor(projectDir) {
|
|
|
316
352
|
if (hookStates.some((s) => s.agent === "codex")) {
|
|
317
353
|
add("warn", "codex hooks", "installed — but codex skips new or changed hooks until they are approved", "run /hooks inside codex once to approve them (needs codex ≥ 0.115; sessions still report presence over MCP either way)", "installed, not yet approved inside codex");
|
|
318
354
|
}
|
|
355
|
+
if (hookStates.some((s) => s.agent === "kiro")) {
|
|
356
|
+
kiroDefaultIsPantheon()
|
|
357
|
+
? add("pass", "kiro hooks", "in the pantheon agent, which is your Kiro default — every kiro-cli chat keeps working while a teammate waits", undefined, "in the pantheon agent, the default")
|
|
358
|
+
: add("warn", "kiro hooks", "in the pantheon agent — used only when you run kiro-cli chat --agent pantheon", "kiro-cli agent set-default pantheon makes it every session; without it, Kiro sessions still report presence but end their turn with a teammate waiting", "in the pantheon agent, not the default");
|
|
359
|
+
}
|
|
319
360
|
const personas = loadPersonas(projectDir);
|
|
320
361
|
if (personas.length) {
|
|
321
362
|
const broken = personas.filter((p) => p.warnings.length);
|