@join-pantheon/cli 0.33.4-rc.1 → 0.33.5-rc.1
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/accounts.js +3 -1
- package/dist/agents.js +23 -0
- package/dist/claude-config.js +2 -1
- package/dist/cli.js +72 -13
- package/dist/client.js +84 -2
- package/dist/decision-ui.js +10 -0
- package/dist/doctor.js +12 -2
- package/dist/email-templates.js +219 -0
- package/dist/email.js +28 -65
- package/dist/history-view.js +5 -2
- package/dist/install.js +6 -4
- package/dist/mcp-instructions.js +3 -1
- package/dist/mcp.js +49 -14
- package/dist/notice.js +25 -2
- package/dist/notifier.js +31 -3
- package/dist/notify.js +2 -0
- package/dist/profile.js +71 -5
- package/dist/protocol.js +7 -2
- package/dist/quickstart.js +6 -7
- package/dist/relay.js +105 -17
- package/dist/secret-guard.js +4 -0
- package/dist/setup-guide.js +25 -5
- package/helper/macos/prebuilt/manifest.json +1 -1
- package/package.json +2 -2
package/dist/accounts.js
CHANGED
|
@@ -349,8 +349,10 @@ export class Accounts {
|
|
|
349
349
|
this.db.exec("BEGIN");
|
|
350
350
|
try {
|
|
351
351
|
let account;
|
|
352
|
+
let welcome = false;
|
|
352
353
|
if (v.purpose === "signup") {
|
|
353
354
|
const existing = this.accountByEmail(e);
|
|
355
|
+
welcome = existing?.status !== "active";
|
|
354
356
|
if (existing?.status === "suspended") {
|
|
355
357
|
this.db.exec("ROLLBACK");
|
|
356
358
|
return { ok: false, reason: "suspended" };
|
|
@@ -378,7 +380,7 @@ export class Accounts {
|
|
|
378
380
|
run("DELETE FROM verify_failures WHERE email = ?").run(keyId);
|
|
379
381
|
this.db.exec("COMMIT");
|
|
380
382
|
this.audit({ actor_account: account.id, event: v.purpose === "signup" ? "email_verified" : "login", target: device.fingerprint, ip });
|
|
381
|
-
return { ok: true, account, session, device };
|
|
383
|
+
return { ok: true, account, session, device, welcome };
|
|
382
384
|
}
|
|
383
385
|
catch (err) {
|
|
384
386
|
this.db.exec("ROLLBACK");
|
package/dist/agents.js
CHANGED
|
@@ -93,6 +93,29 @@ export function detectAvailable() {
|
|
|
93
93
|
}
|
|
94
94
|
return out;
|
|
95
95
|
}
|
|
96
|
+
export function detectedAgentId(home = homedir()) {
|
|
97
|
+
const onPath = detectAvailable()[0]?.backend.id;
|
|
98
|
+
if (onPath)
|
|
99
|
+
return onPath;
|
|
100
|
+
for (const [dir, id] of [[".claude", "claude-code"], [".codex", "codex"], [".cursor", "cursor"], [".kiro", "kiro"], [".copilot", "copilot"]]) {
|
|
101
|
+
if (existsSync(join(home, dir)))
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
export function recordedAgent(prior, opts = {}) {
|
|
107
|
+
const known = (x) => (x && x.trim() && x !== "unknown" ? x : null);
|
|
108
|
+
const env = known((opts.env ?? process.env).PANTHEON_AGENT);
|
|
109
|
+
if (env)
|
|
110
|
+
return env;
|
|
111
|
+
const detect = () => { try {
|
|
112
|
+
return known((opts.detect ?? detectedAgentId)());
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
} };
|
|
117
|
+
return (opts.fresh ? detect() ?? known(prior) : known(prior) ?? detect()) ?? "unknown";
|
|
118
|
+
}
|
|
96
119
|
export function detectOnPath() {
|
|
97
120
|
for (const a of AGENTS) {
|
|
98
121
|
if (resolveBinary(a))
|
package/dist/claude-config.js
CHANGED
|
@@ -11,7 +11,8 @@ export function claudeHomeConflict(env = process.env, os = platform(), home = ho
|
|
|
11
11
|
return null;
|
|
12
12
|
return `HOME (${env.HOME}) is not your Windows profile (${home}), whose ~/.claude.json Claude Code reads — it was left as it is`;
|
|
13
13
|
}
|
|
14
|
-
export const
|
|
14
|
+
export const CLAUDE_AGENT_ENV = { PANTHEON_AGENT: "claude-code" };
|
|
15
|
+
export const claudeUserEntryShape = (entry) => ({ type: "stdio", command: entry.command, args: [...entry.args], env: { ...CLAUDE_AGENT_ENV } });
|
|
15
16
|
const STALE_MS = 10_000;
|
|
16
17
|
const WAIT_MS = 15_000;
|
|
17
18
|
const sleepSync = (ms) => { try {
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ import { DEFAULT_REMINDER_MS, ensureNativeNotifier, HumanNotifier, runNotifiers,
|
|
|
23
23
|
import { linuxDesktopReachable } from "./notice-linux.js";
|
|
24
24
|
import { desktopUnavailable, hostEnv } from "./host-env.js";
|
|
25
25
|
import { AGENT_SURFACES, applyProfileInit, cleanList, isAgentSurface, loadProfile, organizationProfile, profileInitialized, saveProfile, teamProfile, wakeDefault, wakeMode } from "./profile.js";
|
|
26
|
-
import { runGuide, terminalIO, questionOrNull, parseSetupFlags, questionnaire, optOutsFrom } from "./setup-guide.js";
|
|
26
|
+
import { runGuide, terminalIO, questionOrNull, parseSetupFlags, questionnaire, optOutsFrom, decisionAreasProblem } from "./setup-guide.js";
|
|
27
27
|
import { selectActs, formatAct, resolveRef } from "./history-view.js";
|
|
28
28
|
import { doctor, render, shareBlock } from "./doctor.js";
|
|
29
29
|
import { FEEDBACK_ADDRESS } from "./feedback.js";
|
|
@@ -31,7 +31,7 @@ import { menubarProc, notifierProc, relayProc, workerProc, wiring, logFileFor, d
|
|
|
31
31
|
import { assess } from "./untrusted.js";
|
|
32
32
|
import { report as spendReport, recentRuns } from "./spend.js";
|
|
33
33
|
import { renderBrief, renderTeam } from "./brief.js";
|
|
34
|
-
import { resolveDataDir, saveAll } from "./config.js";
|
|
34
|
+
import { resolveDataDir, saveAll, withConfigLock } from "./config.js";
|
|
35
35
|
import { currentPlan, installNetwork, redactProxy, redactText, normalizeProxy, proxyToRemember, describeNetError, describeRelayFailure, checkRelayCertificate, interceptedText, interceptionExplanation, relayJson } from "./network.js";
|
|
36
36
|
import { createInterface } from "node:readline";
|
|
37
37
|
import { homedir } from "node:os";
|
|
@@ -39,6 +39,7 @@ import { removeHook, decideStop, writeIntent, clearIntent, readIntent, repairNot
|
|
|
39
39
|
import { pending, render as renderPending } from "./pending.js";
|
|
40
40
|
import { signedRead } from "./reads.js";
|
|
41
41
|
import { start as qsStart, join as qsJoin, wireUp, teamNameFor, deviceLabel, bindableFolder, startReconnects } from "./quickstart.js";
|
|
42
|
+
import { recordedAgent } from "./agents.js";
|
|
42
43
|
import { alreadyMemberLine, askedLine, configureLines, fetchSeats, inUseLine, offerSeats, seatsForTeam, seatsToMove, startGate, RECOVER_WAITING_EXIT } from "./recover-offer.js";
|
|
43
44
|
import { shellArg, teamNameProblem } from "./team-name.js";
|
|
44
45
|
import { folderRefusalHere, isTemporaryRefusal, projectFolderAnswer } from "./folder-guard.js";
|
|
@@ -253,7 +254,7 @@ async function interactiveOnboard(relayArg, opts = {}) {
|
|
|
253
254
|
const authUser = mode === "signup" ? signupUsername : undefined;
|
|
254
255
|
save({ ...(prior ?? {}), relay, member: signupUsername || prior?.member || "me",
|
|
255
256
|
space: prior?.space ?? "", token: prior?.token ?? "",
|
|
256
|
-
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: prior?.agent
|
|
257
|
+
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: recordedAgent(prior?.agent),
|
|
257
258
|
pendingAuth: { email, ...(authUser ? { username: authUser } : {}), purpose: mode, at: recentPend?.at ?? (already && pend?.email === email && pend.at ? pend.at : Date.now()) } });
|
|
258
259
|
if (started.j.reused) {
|
|
259
260
|
console.error(`\n ${C("A code was sent")} to ${B(email)} ${Math.max(1, Math.round((Date.now() - recentPend.at) / 60_000))} min ago — enter that one, or type 'resend' for a new one. ${SPAM_HINT}\n`);
|
|
@@ -285,7 +286,7 @@ async function interactiveOnboard(relayArg, opts = {}) {
|
|
|
285
286
|
if (v.status === 200) {
|
|
286
287
|
const acct = v.j.account;
|
|
287
288
|
const cfg = { ...(prior ?? {}), relay, member: acct.username, space: prior?.space ?? "", token: prior?.token ?? "",
|
|
288
|
-
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: prior?.agent
|
|
289
|
+
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: recordedAgent(prior?.agent),
|
|
289
290
|
account: { username: acct.username, email: acct.email, verified_at: Date.now() } };
|
|
290
291
|
delete cfg.pendingAuth;
|
|
291
292
|
save(cfg);
|
|
@@ -867,11 +868,12 @@ async function guidedSetup(cfg, project, opts = {}) {
|
|
|
867
868
|
catch {
|
|
868
869
|
return [];
|
|
869
870
|
} },
|
|
870
|
-
publish: async (p) => {
|
|
871
|
+
publish: async (p, keep) => {
|
|
871
872
|
const c = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
872
873
|
try {
|
|
873
874
|
await c.connect();
|
|
874
|
-
await c.publishProfile(organizationProfile(p, cfg.spaceId, cfg.space));
|
|
875
|
+
await c.publishProfile(organizationProfile(p, cfg.spaceId, cfg.space), keep);
|
|
876
|
+
return [...c.profiles.values()];
|
|
875
877
|
}
|
|
876
878
|
finally {
|
|
877
879
|
c.close();
|
|
@@ -1129,6 +1131,7 @@ ${B("several teams on one machine")}
|
|
|
1129
1131
|
pantheon leave <team> drop one team from this machine (its token and repo bindings)
|
|
1130
1132
|
pantheon space delete <team> --yes dissolve a team you founded: disconnects everyone, frees a plan slot
|
|
1131
1133
|
pantheon profile set --space <team> … authority that applies in one team only (e.g. --human-required pricing)
|
|
1134
|
+
pantheon profile clear-authority <member> [--space <team>] founder: take a member's decision claims off (they can set them again)
|
|
1132
1135
|
|
|
1133
1136
|
${B("day to day")}
|
|
1134
1137
|
pantheon status who is here, what is being worked on, what needs you
|
|
@@ -1652,7 +1655,7 @@ async function main() {
|
|
|
1652
1655
|
publicKey: kp.publicKey,
|
|
1653
1656
|
privateKey: kp.privateKey,
|
|
1654
1657
|
display: flag("display"),
|
|
1655
|
-
agent: flag("agent",
|
|
1658
|
+
agent: flag("agent") ?? recordedAgent(had?.agent, { fresh: true }),
|
|
1656
1659
|
...(had?.account ? { account: had.account } : {}),
|
|
1657
1660
|
};
|
|
1658
1661
|
save(cfg);
|
|
@@ -1853,6 +1856,21 @@ async function main() {
|
|
|
1853
1856
|
}
|
|
1854
1857
|
case "rewire": {
|
|
1855
1858
|
const rows = installForUser(undefined, undefined, { direct: argv.includes("--direct") }).map((t) => ({ tool: t.tool, status: t.status }));
|
|
1859
|
+
try {
|
|
1860
|
+
const said = withConfigLock(() => {
|
|
1861
|
+
const f = loadAll();
|
|
1862
|
+
if (!f || (f.agent && f.agent !== "unknown"))
|
|
1863
|
+
return null;
|
|
1864
|
+
const a = recordedAgent(f.agent, { fresh: true });
|
|
1865
|
+
if (a === "unknown")
|
|
1866
|
+
return null;
|
|
1867
|
+
saveAll({ ...f, agent: a });
|
|
1868
|
+
return a;
|
|
1869
|
+
});
|
|
1870
|
+
if (said)
|
|
1871
|
+
rows.push({ tool: "agent", status: `recorded as ${said} (was unknown)` });
|
|
1872
|
+
}
|
|
1873
|
+
catch { }
|
|
1856
1874
|
try {
|
|
1857
1875
|
const { refreshUserHooks } = await import("./notify.js");
|
|
1858
1876
|
const h = refreshUserHooks();
|
|
@@ -2300,6 +2318,8 @@ async function main() {
|
|
|
2300
2318
|
const domains = flag("domains");
|
|
2301
2319
|
if (domains)
|
|
2302
2320
|
meta.decision_domains = cleanList(domains.split(","));
|
|
2321
|
+
if (act === "HUMAN_REQUIRED" && to && !inWokenRun())
|
|
2322
|
+
meta.named_by_requester = true;
|
|
2303
2323
|
let goalArg = flag("goal") ?? null;
|
|
2304
2324
|
let ref = flag("ref") ?? null;
|
|
2305
2325
|
if (ref !== null || ["ANSWER", "ACCEPT", "OBJECT"].includes(act)) {
|
|
@@ -2346,6 +2366,11 @@ async function main() {
|
|
|
2346
2366
|
: /GOAL_REQUIRED|NO_SUCH_GOAL|GOAL_CLOSED/.test(e.message) ? `${e.message.replace(/ — open one with pantheon_open_goal/, "").replace(/pantheon_open_goal/g, "pantheon goal open")}\n Open one from here: pantheon goal open "<what it is about>" --with <member> then add --goal <id> (pantheon goal list shows the open ones)`
|
|
2347
2367
|
: e.message));
|
|
2348
2368
|
console.log(`sent ${act} id=${r.id} seq=${r.seq}`);
|
|
2369
|
+
{
|
|
2370
|
+
const note = (await import("./secret-guard.js")).privateLinkNote(body, summary);
|
|
2371
|
+
if (note)
|
|
2372
|
+
console.error(`\x1b[33mnote\x1b[0m: ${note}`);
|
|
2373
|
+
}
|
|
2349
2374
|
if (flag("space"))
|
|
2350
2375
|
console.error(D(` in "${cfg.space}"`));
|
|
2351
2376
|
else if (resolveFor(process.cwd()).reason === "default")
|
|
@@ -3282,7 +3307,7 @@ async function main() {
|
|
|
3282
3307
|
if (r.status !== 200)
|
|
3283
3308
|
die(String(r.j.error ?? `signup failed (${r.status})`));
|
|
3284
3309
|
save({ ...(prior ?? {}), relay, member: username ?? prior?.member ?? "me", space: prior?.space ?? "", token: prior?.token ?? "",
|
|
3285
|
-
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: prior?.agent
|
|
3310
|
+
publicKey: kp.publicKey, privateKey: kp.privateKey, agent: recordedAgent(prior?.agent),
|
|
3286
3311
|
pendingAuth: { email, ...(username ? { username } : {}), purpose, at: Date.now() } });
|
|
3287
3312
|
if (r.j.sent === false && r.j.via === "resend") {
|
|
3288
3313
|
console.error(`${B("signup accepted, but the email could not be sent just now.")}`);
|
|
@@ -3476,14 +3501,43 @@ async function main() {
|
|
|
3476
3501
|
return;
|
|
3477
3502
|
}
|
|
3478
3503
|
case "profile": {
|
|
3504
|
+
if (argv[1] === "clear-authority") {
|
|
3505
|
+
if (inWokenRun())
|
|
3506
|
+
die("a woken run cannot clear anyone's decision claims — a person has to run this here");
|
|
3507
|
+
const who = argv[2] && !argv[2].startsWith("--") ? argv[2] : die("usage: pantheon profile clear-authority <member> [--space <team>]");
|
|
3508
|
+
let tc;
|
|
3509
|
+
if (flag("space")) {
|
|
3510
|
+
const pf = loadAll() ?? die("not configured — pantheon join <invite> first");
|
|
3511
|
+
const m = findMembership(pf, flag("space")) ?? die(`you are not in a team called "${flag("space")}"`);
|
|
3512
|
+
tc = configFor(pf, m, null);
|
|
3513
|
+
}
|
|
3514
|
+
else
|
|
3515
|
+
tc = need();
|
|
3516
|
+
const client = new PantheonClient(tc, undefined, { role: "cli" });
|
|
3517
|
+
await client.connect().catch((e) => die(connectFailure(e, tc.relay)));
|
|
3518
|
+
const claimed = Object.keys(client.profiles.get(who)?.authority ?? {});
|
|
3519
|
+
try {
|
|
3520
|
+
await client.clearAuthority(who);
|
|
3521
|
+
}
|
|
3522
|
+
catch (e) {
|
|
3523
|
+
client.close();
|
|
3524
|
+
const msg = e.message;
|
|
3525
|
+
die(/BAD_MESSAGE/.test(msg) && /discriminator|\bt\b/.test(msg)
|
|
3526
|
+
? `"${tc.space}"'s relay runs a Pantheon older than 0.33.5, which cannot clear someone's claims — nothing changed. Ask ${who} to run pantheon setup --decides none, or upgrade the relay.`
|
|
3527
|
+
: msg);
|
|
3528
|
+
}
|
|
3529
|
+
client.close();
|
|
3530
|
+
console.error(`${C("cleared")} — ${who}'s decision claims in "${tc.space}"${claimed.length ? ` (${claimed.join(", ")})` : ""}. The team was told in a NOTE; ${who} can set them again with pantheon setup --decides`);
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3479
3533
|
const cfg = need();
|
|
3480
3534
|
const sub = argv[1] ?? "show";
|
|
3481
3535
|
let profile = loadProfile(cfg);
|
|
3482
|
-
const publish = async () => {
|
|
3536
|
+
const publish = async (keep = []) => {
|
|
3483
3537
|
const client = new PantheonClient(cfg, undefined, { role: "cli" });
|
|
3484
3538
|
try {
|
|
3485
3539
|
await client.connect();
|
|
3486
|
-
await client.publishProfile(organizationProfile(profile, cfg.spaceId, cfg.space));
|
|
3540
|
+
await client.publishProfile(organizationProfile(profile, cfg.spaceId, cfg.space), keep);
|
|
3487
3541
|
}
|
|
3488
3542
|
finally {
|
|
3489
3543
|
client.close();
|
|
@@ -3515,6 +3569,11 @@ async function main() {
|
|
|
3515
3569
|
const name = flag("name");
|
|
3516
3570
|
const title = flag("title");
|
|
3517
3571
|
const responsibilities = listFlag("responsibilities");
|
|
3572
|
+
for (const f of ["human-required", "agent-can-decide"]) {
|
|
3573
|
+
const bad = decisionAreasProblem(f, flag(f));
|
|
3574
|
+
if (bad)
|
|
3575
|
+
die(bad);
|
|
3576
|
+
}
|
|
3518
3577
|
const humanRequired = listFlag("human-required");
|
|
3519
3578
|
const agentCanDecide = listFlag("agent-can-decide");
|
|
3520
3579
|
const capabilities = listFlag("capabilities");
|
|
@@ -3546,7 +3605,7 @@ async function main() {
|
|
|
3546
3605
|
const tcl = new PantheonClient(tc, undefined, { role: "cli" });
|
|
3547
3606
|
try {
|
|
3548
3607
|
await tcl.connect();
|
|
3549
|
-
await tcl.publishProfile(organizationProfile(profile, m.id, m.name));
|
|
3608
|
+
await tcl.publishProfile(organizationProfile(profile, m.id, m.name), [...(humanRequired ?? []), ...(agentCanDecide ?? [])]);
|
|
3550
3609
|
console.error(`${C("published")} into "${m.name}"`);
|
|
3551
3610
|
}
|
|
3552
3611
|
catch (e) {
|
|
@@ -3579,7 +3638,7 @@ async function main() {
|
|
|
3579
3638
|
console.error(`${C("saved")} — ${surface ? `decision notifications open ${surface}` : "local organization profile updated"}`);
|
|
3580
3639
|
if (publicChange) {
|
|
3581
3640
|
try {
|
|
3582
|
-
await publish();
|
|
3641
|
+
await publish([...(humanRequired ?? []), ...(agentCanDecide ?? [])]);
|
|
3583
3642
|
console.error(`${C("published")} — the organization can now route decisions using this authority`);
|
|
3584
3643
|
}
|
|
3585
3644
|
catch (e) {
|
|
@@ -3645,7 +3704,7 @@ async function main() {
|
|
|
3645
3704
|
console.error(`${C("published")} — organization profile is current`);
|
|
3646
3705
|
}
|
|
3647
3706
|
else if (sub !== "show") {
|
|
3648
|
-
die("usage: pantheon profile [show | init ... | publish | set ... | delegate ... | undelegate ...]");
|
|
3707
|
+
die("usage: pantheon profile [show | init ... | publish | set ... | delegate ... | undelegate ... | clear-authority <member>]");
|
|
3649
3708
|
}
|
|
3650
3709
|
console.log(JSON.stringify(profile, null, 2));
|
|
3651
3710
|
return;
|
package/dist/client.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
-
import { adoptSpaceId, rememberTeamRepos, updateMembershipToken } from "./config.js";
|
|
2
|
+
import { adoptSpaceId, loadAll, rememberTeamRepos, updateMembershipToken } from "./config.js";
|
|
3
|
+
import { honourClearedAuthority } from "./profile.js";
|
|
3
4
|
import { markContinued } from "./continued.js";
|
|
4
5
|
import { markAnswered, nonceOf } from "./nonces.js";
|
|
5
6
|
import { signedRead } from "./reads.js";
|
|
@@ -248,9 +249,11 @@ export class PantheonClient {
|
|
|
248
249
|
case "members":
|
|
249
250
|
this.members = msg.members;
|
|
250
251
|
this.observePins();
|
|
252
|
+
this.honourCleared();
|
|
251
253
|
break;
|
|
252
254
|
case "profiles":
|
|
253
255
|
this.profiles = new Map((msg.profiles ?? []).map((p) => [p.member, p]));
|
|
256
|
+
this.honourCleared();
|
|
254
257
|
break;
|
|
255
258
|
case "claimed":
|
|
256
259
|
break;
|
|
@@ -325,6 +328,7 @@ export class PantheonClient {
|
|
|
325
328
|
this.features = msg.features ?? [];
|
|
326
329
|
this.observePins();
|
|
327
330
|
this.profiles = new Map((msg.profiles ?? []).map((p) => [p.member, p]));
|
|
331
|
+
this.honourCleared();
|
|
328
332
|
for (const g of msg.goals)
|
|
329
333
|
this.goals.set(g.id, g);
|
|
330
334
|
if (!settled) {
|
|
@@ -527,6 +531,30 @@ export class PantheonClient {
|
|
|
527
531
|
});
|
|
528
532
|
}
|
|
529
533
|
async send(a) {
|
|
534
|
+
let inferredTo = false;
|
|
535
|
+
if ((a.act === "ANSWER" || a.act === "ACCEPT" || a.act === "OBJECT") && a.ref && a.to == null) {
|
|
536
|
+
const author = await this.authorOf(a.ref);
|
|
537
|
+
const goal = a.goal ? this.goals.get(a.goal) : undefined;
|
|
538
|
+
const entry = author ? this.members.find((m) => m.member === author) : undefined;
|
|
539
|
+
const known = !!entry && entry.admitted && !entry.revoked_at;
|
|
540
|
+
if (author && author !== this.cfg.member && known && (!a.goal || goal?.members.includes(author))) {
|
|
541
|
+
a = { ...a, to: [author] };
|
|
542
|
+
inferredTo = true;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (inferredTo) {
|
|
546
|
+
try {
|
|
547
|
+
return await this.sendAct(a);
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
if (!/NO_SUCH_MEMBER|NOT_IN_GOAL/.test(e.message))
|
|
551
|
+
throw e;
|
|
552
|
+
return this.sendAct({ ...a, to: null });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return this.sendAct(a);
|
|
556
|
+
}
|
|
557
|
+
async sendAct(a) {
|
|
530
558
|
const repo = this.cfg.repo;
|
|
531
559
|
const meta = a.act === "DELIVERED" && repo && !(a.meta && "repo" in a.meta) ? { ...(a.meta ?? {}), repo } : a.meta;
|
|
532
560
|
const id = newActId();
|
|
@@ -756,9 +784,63 @@ export class PantheonClient {
|
|
|
756
784
|
setMayWake(member, mayWake) {
|
|
757
785
|
return this.rpc({ t: "admit", member, may_wake: mayWake, wake_only: true });
|
|
758
786
|
}
|
|
759
|
-
publishProfile(profile) {
|
|
787
|
+
publishProfile(profile, keep = []) {
|
|
788
|
+
const own = this.profiles.get(this.cfg.member);
|
|
789
|
+
if (own?.authority_cleared) {
|
|
790
|
+
try {
|
|
791
|
+
honourClearedAuthority(this.cfg, own, { keep: [...keep], force: true, teams: this.teamCount(), founder: this.founder() });
|
|
792
|
+
}
|
|
793
|
+
catch { }
|
|
794
|
+
const gone = new Set(own.authority_cleared.by === this.founder() ? own.authority_cleared.domains.filter((d) => !keep.includes(d)) : []);
|
|
795
|
+
profile = { ...profile, authority: Object.fromEntries(Object.entries(profile.authority).filter(([d]) => !gone.has(d))) };
|
|
796
|
+
}
|
|
760
797
|
return this.rpc({ t: "publish_profile", profile });
|
|
761
798
|
}
|
|
799
|
+
async authorOf(id) {
|
|
800
|
+
const seen = this.seen.get(id);
|
|
801
|
+
if (seen)
|
|
802
|
+
return seen.from;
|
|
803
|
+
const read = (async () => {
|
|
804
|
+
try {
|
|
805
|
+
const res = await signedRead(this.cfg, "/act", { id }, { timeoutMs: 300 });
|
|
806
|
+
return res.ok ? (await res.json()).act?.from ?? null : null;
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
})();
|
|
812
|
+
let timer;
|
|
813
|
+
const late = new Promise((r) => { timer = setTimeout(() => r(null), 300); });
|
|
814
|
+
try {
|
|
815
|
+
return await Promise.race([read, late]);
|
|
816
|
+
}
|
|
817
|
+
finally {
|
|
818
|
+
clearTimeout(timer);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
clearAuthority(member) {
|
|
822
|
+
return this.rpc({ t: "clear_authority", member });
|
|
823
|
+
}
|
|
824
|
+
founder() {
|
|
825
|
+
return this.members.find((m) => m.founder)?.member ?? null;
|
|
826
|
+
}
|
|
827
|
+
teamCount() {
|
|
828
|
+
try {
|
|
829
|
+
return Object.keys(loadAll()?.spaces ?? {}).length;
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
return 1;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
honourCleared() {
|
|
836
|
+
const own = this.profiles.get(this.cfg.member);
|
|
837
|
+
if (!own?.authority_cleared)
|
|
838
|
+
return;
|
|
839
|
+
try {
|
|
840
|
+
honourClearedAuthority(this.cfg, own, { teams: this.teamCount(), founder: this.founder() });
|
|
841
|
+
}
|
|
842
|
+
catch { }
|
|
843
|
+
}
|
|
762
844
|
adoptKnownRepo() {
|
|
763
845
|
const c = this.cfg;
|
|
764
846
|
if (c.repo || !c.cwdRepo)
|
package/dist/decision-ui.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
const oneLine = (value) => value.replace(/\s+/g, " ").trim();
|
|
2
|
+
export function namedMarker(from, meta, passed) {
|
|
3
|
+
if (meta?.authority !== "named")
|
|
4
|
+
return null;
|
|
5
|
+
const p = (x, n) => String(x).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029\[\]]+/g, " ").slice(0, n);
|
|
6
|
+
const over = (passed ?? []).slice(0, 4).map((o) => `${p(o.member, 48)} (${o.domains.slice(0, 4).map((d) => p(d, 48)).join(", ")})`);
|
|
7
|
+
return `named by ${p(from, 48)}${over.length ? ` — passes over ${over.join(", ")}` : ""}`;
|
|
8
|
+
}
|
|
9
|
+
export function namedMarkerIn(text) {
|
|
10
|
+
return /\[(named by [^\]\n]{1,400})\]/.exec(text ?? "")?.[1] ?? null;
|
|
11
|
+
}
|
|
2
12
|
const LAYERS = [
|
|
3
13
|
/^([a-z0-9._-]+)\s+escalated\s*:\s*/i,
|
|
4
14
|
/^([a-z0-9._-]+)\s+needs? a decision\s*:\s*/i,
|
package/dist/doctor.js
CHANGED
|
@@ -226,8 +226,18 @@ export async function doctor(projectDir) {
|
|
|
226
226
|
add("pass", "team", folderHere ? `this folder is bound to "${here.cfg.space}" (${here.binding?.path ?? projectDir}) — no git needed` : `this repo is bound to "${here.cfg.space}"${here.repo?.origin ? ` (${here.repo.origin})` : ""}`, undefined, `this ${folderHere ? "folder" : "repo"} is bound to a team (${nTeams} on this machine)`);
|
|
227
227
|
else if (here.reason === "single")
|
|
228
228
|
add("pass", "team", `"${cfg.space}" — your only team, so every folder resolves to it`, undefined, "one team on this machine");
|
|
229
|
-
else if (here.reason === "unbound")
|
|
230
|
-
|
|
229
|
+
else if (here.reason === "unbound") {
|
|
230
|
+
const all = loadAll();
|
|
231
|
+
const names = new Map(Object.values(all?.spaces ?? {}).map((m) => [m.id, m.name]));
|
|
232
|
+
const bound = Object.values(all?.bindings ?? {}).filter((b) => names.has(b.space));
|
|
233
|
+
if (bound.length) {
|
|
234
|
+
const list = bound.slice(0, 4).map((b) => `${b.path} (${names.get(b.space)})`).join(", ") + (bound.length > 4 ? `, and ${bound.length - 4} more` : "");
|
|
235
|
+
add("info", "team", `run doctor from a project folder to check it; bound here: ${list}`, undefined, `run outside a project; ${bound.length} folder${bound.length === 1 ? "" : "s"} bound on this machine`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
add("fail", "team", `this folder is not bound to any of your ${nTeams} teams — agents opened here will refuse to send`, `from the team's checkout: pantheon use <team> --here (pantheon spaces lists them)`, `folder not bound to any of ${nTeams} teams`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
231
241
|
else if (here.reason === "stale")
|
|
232
242
|
add("fail", "team", `this ${here.repo ? "repo" : "folder"} is bound to a team this machine has left (${here.binding?.space})`, "pantheon use <team> --here, or pantheon join <invite>", "bound to a team this machine has left");
|
|
233
243
|
if (here.repo && !here.repo.shared)
|