primcli 1.62.1 → 1.64.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 +23 -10
- package/dist/{api-command-Bj8WyqXK.js → api-command-C3XihKH7.js} +151 -13
- package/dist/{machine-session-Bdr3jFm4.js → machine-session-B3JznlvO.js} +826 -345
- package/dist/{notify-session-anckNIzA.js → notify-session-Bzcs3VBe.js} +2 -2
- package/dist/oclif/auto-signal-worker.js +2 -2
- package/dist/oclif/auto-signals.js +1 -1
- package/dist/oclif/index.js +558 -119
- package/dist/oclif/receiver-heal.js +1 -1
- package/dist/{repeats-x4QM2yoG-De3MV3st.js → repeats-BL7ZC-_6-Dzangtkf.js} +7772 -94
- package/dist/{scoped-chat-C7cpIxVS.js → scoped-chat-BW041zEY.js} +1 -1
- package/dist/{signal-command-DWKXZSsI.js → signal-command-CgNIftzG.js} +4 -4
- package/dist/skills/primitive-connect/SKILL.md +13 -1
- package/dist/skills/primitive-connect/references/conversations.md +27 -2
- package/dist/skills/primitive-connect/references/presence-and-receiving.md +65 -2
- package/dist/skills/primitive-connect.json +5 -5
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as __exportAll } from "./repeats-BL7ZC-_6-Dzangtkf.js";
|
|
2
2
|
import { B as parseOwnerMemberAddress, F as agentProfilesDirectory, I as connectedAgentIdentity, L as connectedApiBaseUrl, M as AgentConnectionSetupError, N as agentProfileDirectory, P as agentProfileName, R as loadConnectedAgentProfile, U as saveConnectedAgentProfile, V as profileAlreadyConnectedMessage, W as PrimitiveApiClient, j as AGENT_PROFILE_ENV, x as loadCliCredentials, z as parseConnectedAgentProfile } from "./cli-config-DMszFbGn.js";
|
|
3
|
-
import { Ct as cliInvocation, Jn as putAgentContactPolicy, Jt as refreshStoredCliCredentials, Pn as listDomains, Tt as defaultConnectionName, Vn as listSentEmails, Yn as putContact, an as deleteAgentContact, ar as sendEmail, jn as listContacts, kn as listAgentContacts, mn as getContact, nr as searchEmails, on as deleteContact, pn as getAgentContactPolicy, qn as putAgentContact, vn as getEmail } from "./api-command-
|
|
3
|
+
import { Ct as cliInvocation, Jn as putAgentContactPolicy, Jt as refreshStoredCliCredentials, Pn as listDomains, Tt as defaultConnectionName, Vn as listSentEmails, Yn as putContact, an as deleteAgentContact, ar as sendEmail, jn as listContacts, kn as listAgentContacts, mn as getContact, nr as searchEmails, on as deleteContact, pn as getAgentContactPolicy, qn as putAgentContact, vn as getEmail } from "./api-command-C3XihKH7.js";
|
|
4
4
|
import { _ as acquireListenLock, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, o as mailObject, r as mailAddress, s as mailString } from "./shared-mail-files-MwlVE90c.js";
|
|
5
|
-
import { _ as detectAgentRuntime, a as NotificationRetryError, d as defaultSessionSocket, f as NativeSessionDisconnectedError, l as SESSION_UUID, t as notificationScope, u as connectNativeSession, v as installConnectSkill, x as readBundledConnectSkill } from "./notify-session-
|
|
6
|
-
import { a as scopedChatSenderTrust } from "./scoped-chat-
|
|
5
|
+
import { _ as detectAgentRuntime, a as NotificationRetryError, d as defaultSessionSocket, f as NativeSessionDisconnectedError, l as SESSION_UUID, t as notificationScope, u as connectNativeSession, v as installConnectSkill, x as readBundledConnectSkill } from "./notify-session-Bzcs3VBe.js";
|
|
6
|
+
import { a as scopedChatSenderTrust } from "./scoped-chat-BW041zEY.js";
|
|
7
7
|
import { l as stopBackgroundListen, r as backgroundListenStatus } from "./listen-background--0peztKE.js";
|
|
8
8
|
import { t as readSharedMailOwner } from "./shared-mail-watch-DtKpsgg8.js";
|
|
9
9
|
import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -1632,6 +1632,174 @@ function uninstallClaudeWakeHook(options) {
|
|
|
1632
1632
|
}
|
|
1633
1633
|
}
|
|
1634
1634
|
//#endregion
|
|
1635
|
+
//#region src/oclif/session-records.ts
|
|
1636
|
+
const MACHINE_RUNTIMES = [
|
|
1637
|
+
"claude",
|
|
1638
|
+
"codex",
|
|
1639
|
+
"omp"
|
|
1640
|
+
];
|
|
1641
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
1642
|
+
const REASON = /^[a-z_]{1,40}$/;
|
|
1643
|
+
function sessionRecordsDirectory(configDir) {
|
|
1644
|
+
return join(configDir, "machine", "sessions");
|
|
1645
|
+
}
|
|
1646
|
+
function sessionRecordPath(configDir, session) {
|
|
1647
|
+
return join(sessionRecordsDirectory(configDir), `${session}.json`);
|
|
1648
|
+
}
|
|
1649
|
+
function parseRelease(value) {
|
|
1650
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1651
|
+
const row = value;
|
|
1652
|
+
if (typeof row.at !== "string" || row.reason !== "replaced_by_connect" && row.reason !== "credential_changed") return null;
|
|
1653
|
+
return {
|
|
1654
|
+
at: row.at,
|
|
1655
|
+
reason: row.reason,
|
|
1656
|
+
registeredAddress: typeof row.registeredAddress === "string" ? row.registeredAddress : null
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function readSessionRecord(configDir, session) {
|
|
1660
|
+
try {
|
|
1661
|
+
const value = readMailJson(sessionRecordPath(configDir, session));
|
|
1662
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1663
|
+
const row = value;
|
|
1664
|
+
if (row.version !== 1 || row.session !== session || typeof row.profile !== "string" || typeof row.name !== "string" || !MACHINE_RUNTIMES.includes(row.runtime) || row.createdBy !== "session-register" && row.createdBy !== "existing") return null;
|
|
1665
|
+
return {
|
|
1666
|
+
version: 1,
|
|
1667
|
+
runtime: row.runtime,
|
|
1668
|
+
session,
|
|
1669
|
+
profile: row.profile,
|
|
1670
|
+
name: row.name,
|
|
1671
|
+
createdBy: row.createdBy,
|
|
1672
|
+
address: typeof row.address === "string" ? row.address : null,
|
|
1673
|
+
invitationHash: typeof row.invitationHash === "string" && HASH.test(row.invitationHash) ? row.invitationHash : null,
|
|
1674
|
+
agentInfo: row.agentInfo === "created" || row.agentInfo === "already_present" ? row.agentInfo : null,
|
|
1675
|
+
registeredAt: typeof row.registeredAt === "string" ? row.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
1676
|
+
endedAt: typeof row.endedAt === "string" ? row.endedAt : null,
|
|
1677
|
+
disconnect: row.disconnect === "done" || row.disconnect === "pending" || row.disconnect === "pending_enrollment" || row.disconnect === "deferred" ? row.disconnect : typeof row.endedAt === "string" ? "done" : null,
|
|
1678
|
+
deferredUntil: typeof row.deferredUntil === "string" ? row.deferredUntil : null,
|
|
1679
|
+
endReason: typeof row.endReason === "string" && REASON.test(row.endReason) ? row.endReason : null,
|
|
1680
|
+
release: parseRelease(row.release)
|
|
1681
|
+
};
|
|
1682
|
+
} catch {
|
|
1683
|
+
return null;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
function saveSessionRecord(configDir, row) {
|
|
1687
|
+
writeMailJson(sessionRecordPath(configDir, row.session), row);
|
|
1688
|
+
}
|
|
1689
|
+
/** Serialize every read-modify-write of one session's record. */
|
|
1690
|
+
async function withRecordLock(configDir, session, action) {
|
|
1691
|
+
const directory = sessionRecordsDirectory(configDir);
|
|
1692
|
+
privateMailDirectory(directory, true);
|
|
1693
|
+
let release;
|
|
1694
|
+
for (let attempt = 0; attempt < 200 && !release; attempt++) try {
|
|
1695
|
+
release = acquireListenLock(directory, `session-record-${session}`);
|
|
1696
|
+
} catch {
|
|
1697
|
+
await setTimeout$1(50);
|
|
1698
|
+
}
|
|
1699
|
+
if (!release) throw new ListenStateError("The session record is locked by another run.");
|
|
1700
|
+
try {
|
|
1701
|
+
return await action();
|
|
1702
|
+
} finally {
|
|
1703
|
+
release();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/** Read, change and save one record under its lock. Returns the saved record. */
|
|
1707
|
+
function updateSessionRecord(configDir, session, change) {
|
|
1708
|
+
return withRecordLock(configDir, session, () => {
|
|
1709
|
+
const current = readSessionRecord(configDir, session);
|
|
1710
|
+
const next = change(current);
|
|
1711
|
+
if (next && next !== current) saveSessionRecord(configDir, next);
|
|
1712
|
+
return next ?? current;
|
|
1713
|
+
});
|
|
1714
|
+
}
|
|
1715
|
+
/** Every readable session record on this machine. */
|
|
1716
|
+
function listSessionRecords(configDir) {
|
|
1717
|
+
let names;
|
|
1718
|
+
try {
|
|
1719
|
+
names = readdirSync(sessionRecordsDirectory(configDir));
|
|
1720
|
+
} catch {
|
|
1721
|
+
return [];
|
|
1722
|
+
}
|
|
1723
|
+
return names.flatMap((name) => {
|
|
1724
|
+
const match = /^([0-9a-f-]{36})\.json$/.exec(name);
|
|
1725
|
+
if (!match?.[1]) return [];
|
|
1726
|
+
const record = readSessionRecord(configDir, match[1]);
|
|
1727
|
+
return record ? [record] : [];
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* The record no longer owns the credential in its profile: it becomes a
|
|
1732
|
+
* record of an agent connected some other way, which a session end leaves
|
|
1733
|
+
* connected. Any end that was waiting is dropped with it.
|
|
1734
|
+
*/
|
|
1735
|
+
function releasedRecord(record, reason, at, current) {
|
|
1736
|
+
return {
|
|
1737
|
+
...record,
|
|
1738
|
+
createdBy: "existing",
|
|
1739
|
+
address: current ? current.address : record.address,
|
|
1740
|
+
invitationHash: current ? current.invitationHash : null,
|
|
1741
|
+
endedAt: null,
|
|
1742
|
+
disconnect: null,
|
|
1743
|
+
deferredUntil: null,
|
|
1744
|
+
endReason: null,
|
|
1745
|
+
release: {
|
|
1746
|
+
at: at.toISOString(),
|
|
1747
|
+
reason,
|
|
1748
|
+
registeredAddress: record.address
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
/** The invitation hash an enrollment in this profile saved, if any. */
|
|
1753
|
+
function enrollmentInvitationHash(configDir, profileName) {
|
|
1754
|
+
try {
|
|
1755
|
+
const state = readMailJson(join(agentProfileDirectory(configDir, profileName), "enrollment", "state.json"), 65536);
|
|
1756
|
+
return typeof state?.invitationHash === "string" && HASH.test(state.invitationHash) ? state.invitationHash : null;
|
|
1757
|
+
} catch {
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* A credential was just claimed into `profileName`. Every session record that
|
|
1763
|
+
* says `session-register` created the agent in that profile is released,
|
|
1764
|
+
* unless this claim is that registration's own enrollment finishing, so a
|
|
1765
|
+
* later session end never disconnects the newly connected agent. Best effort:
|
|
1766
|
+
* a failure here leaves the end-time credential check as the safeguard.
|
|
1767
|
+
*/
|
|
1768
|
+
async function releaseRegistrationsForClaim(params) {
|
|
1769
|
+
const own = enrollmentInvitationHash(params.configDir, params.profileName);
|
|
1770
|
+
for (const listed of listSessionRecords(params.configDir)) {
|
|
1771
|
+
if (listed.profile !== params.profileName) continue;
|
|
1772
|
+
try {
|
|
1773
|
+
await updateSessionRecord(params.configDir, listed.session, (current) => {
|
|
1774
|
+
if (!current || current.profile !== params.profileName) return current;
|
|
1775
|
+
if (current.createdBy === "existing") return current.release && (current.address !== params.address || current.invitationHash !== params.invitationHash) ? {
|
|
1776
|
+
...current,
|
|
1777
|
+
address: params.address,
|
|
1778
|
+
invitationHash: params.invitationHash
|
|
1779
|
+
} : current;
|
|
1780
|
+
if (own === params.invitationHash && (current.address === null || current.address.toLowerCase() === params.address.toLowerCase())) return current;
|
|
1781
|
+
return releasedRecord(current, "replaced_by_connect", (params.now ?? (() => /* @__PURE__ */ new Date()))(), {
|
|
1782
|
+
address: params.address,
|
|
1783
|
+
invitationHash: params.invitationHash
|
|
1784
|
+
});
|
|
1785
|
+
});
|
|
1786
|
+
} catch {}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
/**
|
|
1790
|
+
* The profile's credential is being replaced on purpose (for example by
|
|
1791
|
+
* `--replace-existing`): release every registration record for it, keeping
|
|
1792
|
+
* the registered address for reference.
|
|
1793
|
+
*/
|
|
1794
|
+
async function releaseRegistrationsForProfile(params) {
|
|
1795
|
+
for (const listed of listSessionRecords(params.configDir)) {
|
|
1796
|
+
if (listed.profile !== params.profileName || listed.createdBy !== "session-register") continue;
|
|
1797
|
+
try {
|
|
1798
|
+
await updateSessionRecord(params.configDir, listed.session, (current) => current && current.profile === params.profileName && current.createdBy === "session-register" ? releasedRecord(current, "replaced_by_connect", (params.now ?? (() => /* @__PURE__ */ new Date()))()) : current);
|
|
1799
|
+
} catch {}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
//#endregion
|
|
1635
1803
|
//#region src/oclif/agent-connect.ts
|
|
1636
1804
|
const MAX_INVITATION_BYTES = 4096;
|
|
1637
1805
|
const MAX_RESPONSE_BYTES = 32768;
|
|
@@ -1952,7 +2120,7 @@ async function connectAgent(params) {
|
|
|
1952
2120
|
let release;
|
|
1953
2121
|
try {
|
|
1954
2122
|
privateMailDirectory(directory, true);
|
|
1955
|
-
release = acquireListenLock(directory, "agent-connection-setup");
|
|
2123
|
+
release = params.claimLockHeld ? () => {} : acquireListenLock(directory, "agent-connection-setup");
|
|
1956
2124
|
} catch {
|
|
1957
2125
|
throw new AgentConnectionSetupError("Agent setup is already running, or its private directory is unavailable. No invitation was submitted.");
|
|
1958
2126
|
}
|
|
@@ -2012,6 +2180,14 @@ async function connectAgent(params) {
|
|
|
2012
2180
|
const profile = profileFromClaim(body, invitation, invitationHash, params.now ?? Date.now);
|
|
2013
2181
|
const name = claimedConnectionName(body);
|
|
2014
2182
|
saveConnectedAgentProfile(params.configDir, profileName, profile);
|
|
2183
|
+
try {
|
|
2184
|
+
await releaseRegistrationsForClaim({
|
|
2185
|
+
configDir: params.configDir,
|
|
2186
|
+
profileName,
|
|
2187
|
+
address: profile.agent_address,
|
|
2188
|
+
invitationHash: profile.invitation_hash
|
|
2189
|
+
});
|
|
2190
|
+
} catch {}
|
|
2015
2191
|
try {
|
|
2016
2192
|
writeMailJson(journal, {
|
|
2017
2193
|
version: 1,
|
|
@@ -2365,6 +2541,297 @@ async function runAddressNotesRequest(client, request) {
|
|
|
2365
2541
|
return data;
|
|
2366
2542
|
}
|
|
2367
2543
|
//#endregion
|
|
2544
|
+
//#region src/oclif/agent-disconnect.ts
|
|
2545
|
+
var AgentDisconnectError = class extends Error {};
|
|
2546
|
+
/** The profile no longer holds the credential the caller expected to revoke. */
|
|
2547
|
+
var AgentCredentialChangedError = class extends AgentDisconnectError {};
|
|
2548
|
+
function credentialDigest(profile) {
|
|
2549
|
+
return createHash("sha256").update("primitive-agent-disconnect-v1\0").update(profile.api_key).digest("hex");
|
|
2550
|
+
}
|
|
2551
|
+
function boundSession(directory, profile) {
|
|
2552
|
+
const raw = readMailJson(join(directory, "setup.json"));
|
|
2553
|
+
if (raw === null) return null;
|
|
2554
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new AgentDisconnectError("Saved session setup is invalid. The connection was not changed.");
|
|
2555
|
+
const row = raw;
|
|
2556
|
+
if (row.receiverMode === "poll" && row.session === null && row.invitationHash === profile.invitation_hash) return null;
|
|
2557
|
+
if (typeof row.session !== "string" || !SESSION_UUID.test(row.session) || row.invitationHash !== profile.invitation_hash) throw new AgentDisconnectError("Saved session binding does not match this profile. The connection was not changed.");
|
|
2558
|
+
return row.session.toLowerCase();
|
|
2559
|
+
}
|
|
2560
|
+
function receiverStopped(status) {
|
|
2561
|
+
return status.phase === null || status.phase === "stopped" || status.phase === "failed" || status.reason === "exited";
|
|
2562
|
+
}
|
|
2563
|
+
function confirmationPath(directory, profile) {
|
|
2564
|
+
return join(directory, `disconnected-${profile.invitation_hash}.json`);
|
|
2565
|
+
}
|
|
2566
|
+
/** The saved setup's disconnect marker, when it is a well-formed record. */
|
|
2567
|
+
function markerRecord(directory, invitationHash) {
|
|
2568
|
+
const raw = readMailJson(join(directory, `disconnected-${invitationHash}.json`));
|
|
2569
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
2570
|
+
const row = raw;
|
|
2571
|
+
return row.version === 1 && typeof row.revoked_at === "string" && Number.isFinite(Date.parse(row.revoked_at)) ? row : null;
|
|
2572
|
+
}
|
|
2573
|
+
function confirmedLocally(directory, profile) {
|
|
2574
|
+
const raw = readMailJson(confirmationPath(directory, profile));
|
|
2575
|
+
if (raw === null) return false;
|
|
2576
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new AgentDisconnectError("Saved revocation confirmation is invalid. The credential was preserved.");
|
|
2577
|
+
const row = raw;
|
|
2578
|
+
if (row.version !== 1 || row.address !== profile.agent_address || row.api_base_url !== profile.api_base_url || row.credential_digest !== credentialDigest(profile) || typeof row.revoked_at !== "string" || !Number.isFinite(Date.parse(row.revoked_at))) throw new AgentDisconnectError("Saved revocation confirmation does not match this credential. The credential was preserved.");
|
|
2579
|
+
return true;
|
|
2580
|
+
}
|
|
2581
|
+
/** The API error code from an error envelope, when it has one. */
|
|
2582
|
+
function errorCode(error) {
|
|
2583
|
+
if (!error || typeof error !== "object") return void 0;
|
|
2584
|
+
const inner = error.error;
|
|
2585
|
+
const code = inner && typeof inner === "object" ? inner.code : error.code;
|
|
2586
|
+
return typeof code === "string" && /^[a-z0-9_]{1,64}$/.test(code) ? code : void 0;
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Whether Primitive confirms, with this same credential, that it no longer
|
|
2590
|
+
* authenticates: a definite 401 from the connection's own record, or that
|
|
2591
|
+
* record reporting the connection revoked. A server error, a timeout, a
|
|
2592
|
+
* network failure or any other answer is not a confirmation.
|
|
2593
|
+
*/
|
|
2594
|
+
async function credentialRevokedByServer(profile, fetcher = fetch) {
|
|
2595
|
+
try {
|
|
2596
|
+
const response = await fetcher(`${profile.api_base_url}/agent-connections/me`, {
|
|
2597
|
+
method: "GET",
|
|
2598
|
+
redirect: "error",
|
|
2599
|
+
signal: AbortSignal.timeout(1e4),
|
|
2600
|
+
headers: {
|
|
2601
|
+
authorization: `Bearer ${profile.api_key}`,
|
|
2602
|
+
accept: "application/json"
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
if (response.status !== 200) {
|
|
2606
|
+
await response.body?.cancel().catch(() => void 0);
|
|
2607
|
+
return response.status === 401;
|
|
2608
|
+
}
|
|
2609
|
+
const text = await response.text();
|
|
2610
|
+
if (text.length > 65536) return false;
|
|
2611
|
+
const body = JSON.parse(text);
|
|
2612
|
+
const connection = body?.data?.connection;
|
|
2613
|
+
return body?.success === true && typeof connection?.address === "string" && connection.address.toLowerCase() === profile.agent_address.toLowerCase() && connection.status === "revoked";
|
|
2614
|
+
} catch {
|
|
2615
|
+
return false;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
async function revoke(profile, fetcher) {
|
|
2619
|
+
const client = new PrimitiveApiClient({
|
|
2620
|
+
apiKey: profile.api_key,
|
|
2621
|
+
apiBaseUrl: profile.api_base_url,
|
|
2622
|
+
...fetcher ? { fetch: fetcher } : {}
|
|
2623
|
+
});
|
|
2624
|
+
let result;
|
|
2625
|
+
try {
|
|
2626
|
+
result = await client.client.delete({
|
|
2627
|
+
security: [{
|
|
2628
|
+
scheme: "bearer",
|
|
2629
|
+
type: "http"
|
|
2630
|
+
}],
|
|
2631
|
+
url: "/agent-connections/{address}",
|
|
2632
|
+
path: { address: profile.agent_address },
|
|
2633
|
+
redirect: "error",
|
|
2634
|
+
signal: AbortSignal.timeout(25e3),
|
|
2635
|
+
responseStyle: "fields"
|
|
2636
|
+
});
|
|
2637
|
+
} catch {
|
|
2638
|
+
throw new AgentDisconnectError("Revocation outcome is unknown after a transport error. The profile and credential were preserved. Check the agent in the app before retrying.");
|
|
2639
|
+
}
|
|
2640
|
+
if (result.error !== void 0) {
|
|
2641
|
+
const status = result.response?.status;
|
|
2642
|
+
if (status === void 0) throw new AgentDisconnectError("Revocation outcome is unknown: no response was received from Primitive. The profile and credential were preserved. Check your connection, then retry or check the agent in the app.");
|
|
2643
|
+
if (status === 401 && await credentialRevokedByServer(profile, fetcher)) return "already_revoked";
|
|
2644
|
+
const code = errorCode(result.error);
|
|
2645
|
+
const label = `HTTP ${status}${code ? `, ${code}` : ""}`;
|
|
2646
|
+
throw new AgentDisconnectError(status === 401 ? `Revocation was not confirmed (${label}). The credential was preserved; it may already be invalid. Check the agent in the app before retrying.` : status >= 500 ? `Primitive returned a server error while revoking this agent (${label}), so revocation is unconfirmed. The profile was preserved. Retry the same command; a 401 on retry means the credential was already revoked.` : `Revocation was refused by Primitive (${label}). The credential was preserved. Check the agent in the app before retrying.`);
|
|
2647
|
+
}
|
|
2648
|
+
const envelope = result.data;
|
|
2649
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new AgentDisconnectError("Revocation response was incomplete. The credential was preserved. Check the agent in the app before retrying.");
|
|
2650
|
+
const row = envelope;
|
|
2651
|
+
const data = row.data;
|
|
2652
|
+
const connection = data && typeof data === "object" && !Array.isArray(data) ? data.connection : void 0;
|
|
2653
|
+
if (row.success !== true || !connection || typeof connection !== "object" || Array.isArray(connection) || connection.address !== profile.agent_address || connection.owner_address !== profile.owner_address || connection.status !== "revoked") throw new AgentDisconnectError("Revocation response was incomplete. The credential was preserved. Check the agent in the app before retrying.");
|
|
2654
|
+
return "confirmed";
|
|
2655
|
+
}
|
|
2656
|
+
/** Revoke one selected connected credential; never alter OAuth login or mail evidence. */
|
|
2657
|
+
async function disconnectAgent(params) {
|
|
2658
|
+
const profileName = agentProfileName(params.profileName);
|
|
2659
|
+
if (!loadConnectedAgentProfile(params.configDir, profileName)) throw new AgentDisconnectError("This agent profile is not configured. Nothing was changed.");
|
|
2660
|
+
const directory = agentProfileDirectory(params.configDir, profileName);
|
|
2661
|
+
let releaseSetup;
|
|
2662
|
+
let releaseClaim;
|
|
2663
|
+
try {
|
|
2664
|
+
releaseSetup = acquireListenLock(directory, "setup");
|
|
2665
|
+
releaseClaim = acquireListenLock(agentProfilesDirectory(params.configDir), "agent-connection-setup");
|
|
2666
|
+
const profile = loadConnectedAgentProfile(params.configDir, profileName);
|
|
2667
|
+
if (!profile) throw new AgentDisconnectError("The selected profile changed before disconnect. Nothing was changed.");
|
|
2668
|
+
if (params.expected && (profile.agent_address.toLowerCase() !== params.expected.address.toLowerCase() || profile.invitation_hash !== params.expected.invitationHash)) throw new AgentCredentialChangedError("The profile holds a different credential than expected. Nothing was changed.");
|
|
2669
|
+
const session = boundSession(directory, profile);
|
|
2670
|
+
const receiver = session ? await (params.stopReceiver ?? stopBackgroundListen)({
|
|
2671
|
+
configDir: params.configDir,
|
|
2672
|
+
scope: notificationScope(profile.api_base_url, profile.api_key),
|
|
2673
|
+
threadId: session
|
|
2674
|
+
}) : null;
|
|
2675
|
+
if (receiver && !receiverStopped(receiver)) throw new AgentDisconnectError("The bound receiver has not confirmed stopping. The connection and credential were preserved. Check listener status before retrying.");
|
|
2676
|
+
const previous = confirmedLocally(directory, profile);
|
|
2677
|
+
const revocation = previous ? "previously_confirmed" : await revoke(profile, params.fetch);
|
|
2678
|
+
if (!previous) try {
|
|
2679
|
+
writeMailJson(confirmationPath(directory, profile), {
|
|
2680
|
+
version: 1,
|
|
2681
|
+
address: profile.agent_address,
|
|
2682
|
+
api_base_url: profile.api_base_url,
|
|
2683
|
+
credential_digest: credentialDigest(profile),
|
|
2684
|
+
revoked_at: (params.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
2685
|
+
...revocation === "already_revoked" ? { reason: "server_revoked" } : {}
|
|
2686
|
+
});
|
|
2687
|
+
} catch {
|
|
2688
|
+
throw new AgentDisconnectError("Primitive confirmed revocation, but its local confirmation could not be saved. The credential was preserved. Keep this profile for recovery.");
|
|
2689
|
+
}
|
|
2690
|
+
const current = loadConnectedAgentProfile(params.configDir, profileName);
|
|
2691
|
+
if (!current || current.api_key !== profile.api_key) throw new AgentDisconnectError("The selected profile changed after revocation. Its credential was preserved for inspection.");
|
|
2692
|
+
try {
|
|
2693
|
+
removeMailFile(join(directory, "connection.json"));
|
|
2694
|
+
} catch {
|
|
2695
|
+
throw new AgentDisconnectError("Primitive confirmed revocation, but the local credential could not be removed. Confirmation is saved; retry this command to finish local cleanup.");
|
|
2696
|
+
}
|
|
2697
|
+
const externalHook = session ? uninstallClaudeWakeHook({
|
|
2698
|
+
configDir: params.configDir,
|
|
2699
|
+
profileName,
|
|
2700
|
+
agentAddress: profile.agent_address,
|
|
2701
|
+
sessionId: session,
|
|
2702
|
+
env: params.env
|
|
2703
|
+
}) ? "removed" : "unavailable" : null;
|
|
2704
|
+
return {
|
|
2705
|
+
status: "disconnected",
|
|
2706
|
+
identity: connectedAgentIdentity(profileName, profile),
|
|
2707
|
+
receiver,
|
|
2708
|
+
revocation,
|
|
2709
|
+
externalHook
|
|
2710
|
+
};
|
|
2711
|
+
} catch (error) {
|
|
2712
|
+
if (error instanceof AgentDisconnectError) throw error;
|
|
2713
|
+
throw new AgentDisconnectError("Disconnect could not safely complete. The credential and local evidence were preserved. Check this profile and listener status before retrying.");
|
|
2714
|
+
} finally {
|
|
2715
|
+
releaseClaim?.();
|
|
2716
|
+
releaseSetup?.();
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
/**
|
|
2720
|
+
* Whether the saved setup bound to `invitationHash` belongs to a credential
|
|
2721
|
+
* whose revocation is confirmed locally: its disconnect marker exists and,
|
|
2722
|
+
* when the credential file is still present, matches that credential. Such
|
|
2723
|
+
* a setup is evidence only; it no longer binds this profile to an address.
|
|
2724
|
+
*/
|
|
2725
|
+
function savedSetupRevoked(configDir, profileName, invitationHash) {
|
|
2726
|
+
if (!/^[a-f0-9]{64}$/.test(invitationHash)) return false;
|
|
2727
|
+
try {
|
|
2728
|
+
const directory = agentProfileDirectory(configDir, profileName);
|
|
2729
|
+
if (!markerRecord(directory, invitationHash)) return false;
|
|
2730
|
+
const profile = loadConnectedAgentProfile(configDir, profileName);
|
|
2731
|
+
if (!profile) return true;
|
|
2732
|
+
return profile.invitation_hash === invitationHash && confirmedLocally(directory, profile);
|
|
2733
|
+
} catch {
|
|
2734
|
+
return false;
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Move a revoked setup aside, the same way a replacement does, so a new
|
|
2739
|
+
* invitation can be claimed into the same profile. Files are renamed, never
|
|
2740
|
+
* deleted, and kept for recovery.
|
|
2741
|
+
*/
|
|
2742
|
+
function archiveRevokedSetup(configDir, profileName, now) {
|
|
2743
|
+
const directory = agentProfileDirectory(configDir, profileName);
|
|
2744
|
+
const stamp = now.toISOString().replace(/[^0-9]/g, "");
|
|
2745
|
+
for (const path of [
|
|
2746
|
+
join(directory, "setup.json"),
|
|
2747
|
+
join(directory, "enrollment", "state.json"),
|
|
2748
|
+
join(directory, "connection.json")
|
|
2749
|
+
]) try {
|
|
2750
|
+
renameSync(path, join(dirname(path), `replaced-${stamp}-${basename(path)}`));
|
|
2751
|
+
} catch (error) {
|
|
2752
|
+
if (error.code !== "ENOENT") throw error;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
/**
|
|
2756
|
+
* Run a claim into `profileName`, first moving aside a saved setup whose
|
|
2757
|
+
* credential is confirmed revoked, so the new credential is never saved
|
|
2758
|
+
* beside a setup naming the old invitation. The profile's setup lock and the
|
|
2759
|
+
* machine-wide claim lock are taken (in the order setup and disconnect use)
|
|
2760
|
+
* before anything is checked or moved, and held through the claim, which is
|
|
2761
|
+
* told the claim lock is already held. A profile with no saved setup is
|
|
2762
|
+
* claimed as before, without these locks.
|
|
2763
|
+
*/
|
|
2764
|
+
async function claimOverRevokedSetup(params, claim) {
|
|
2765
|
+
const directory = agentProfileDirectory(params.configDir, params.profileName);
|
|
2766
|
+
const path = join(directory, "setup.json");
|
|
2767
|
+
if (!existsSync(path)) return claim(false);
|
|
2768
|
+
let releaseSetup;
|
|
2769
|
+
let releaseClaim;
|
|
2770
|
+
try {
|
|
2771
|
+
try {
|
|
2772
|
+
releaseSetup = acquireListenLock(directory, "setup");
|
|
2773
|
+
releaseClaim = acquireListenLock(agentProfilesDirectory(params.configDir), "agent-connection-setup");
|
|
2774
|
+
} catch {
|
|
2775
|
+
throw new AgentConnectionSetupError("Another agent setup is running on this machine. No invitation was claimed and nothing was changed; retry when it finishes.");
|
|
2776
|
+
}
|
|
2777
|
+
let saved = null;
|
|
2778
|
+
try {
|
|
2779
|
+
saved = readMailJson(path);
|
|
2780
|
+
} catch {}
|
|
2781
|
+
const savedHash = saved && typeof saved === "object" && !Array.isArray(saved) ? saved.invitationHash : void 0;
|
|
2782
|
+
if (typeof savedHash === "string" && savedHash !== params.invitationHash && savedSetupRevoked(params.configDir, params.profileName, savedHash)) archiveRevokedSetup(params.configDir, params.profileName, (params.now ?? (() => /* @__PURE__ */ new Date()))());
|
|
2783
|
+
return await claim(true);
|
|
2784
|
+
} finally {
|
|
2785
|
+
releaseClaim?.();
|
|
2786
|
+
releaseSetup?.();
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2790
|
+
* Of the addresses bound to a session, clear the ones Primitive confirms are
|
|
2791
|
+
* already revoked: each is disconnected locally (receiver stopped, hooks
|
|
2792
|
+
* removed, credential removed, marker written) so it no longer counts as the
|
|
2793
|
+
* session's address. A live credential, or one whose state cannot be
|
|
2794
|
+
* confirmed, is kept and still counts.
|
|
2795
|
+
*/
|
|
2796
|
+
async function clearRevokedAddresses(params) {
|
|
2797
|
+
const remaining = [];
|
|
2798
|
+
const cleared = [];
|
|
2799
|
+
for (const row of params.rows) {
|
|
2800
|
+
let checked = null;
|
|
2801
|
+
try {
|
|
2802
|
+
const profile = loadConnectedAgentProfile(params.configDir, row.profile);
|
|
2803
|
+
if (profile && await credentialRevokedByServer(profile, params.fetch)) checked = profile;
|
|
2804
|
+
} catch {
|
|
2805
|
+
checked = null;
|
|
2806
|
+
}
|
|
2807
|
+
if (!checked) {
|
|
2808
|
+
remaining.push(row);
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
try {
|
|
2812
|
+
await (params.disconnect ?? disconnectAgent)({
|
|
2813
|
+
configDir: params.configDir,
|
|
2814
|
+
profileName: row.profile,
|
|
2815
|
+
fetch: params.fetch,
|
|
2816
|
+
stopReceiver: params.stopReceiver,
|
|
2817
|
+
now: params.now,
|
|
2818
|
+
env: params.env,
|
|
2819
|
+
expected: {
|
|
2820
|
+
address: checked.agent_address,
|
|
2821
|
+
invitationHash: checked.invitation_hash
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
cleared.push(row);
|
|
2825
|
+
} catch {
|
|
2826
|
+
remaining.push(row);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return {
|
|
2830
|
+
remaining,
|
|
2831
|
+
cleared
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
//#endregion
|
|
2368
2835
|
//#region src/oclif/agent-setup-read.ts
|
|
2369
2836
|
function retryDelay(response, attempt, now) {
|
|
2370
2837
|
const raw = response?.headers.get("retry-after")?.trim();
|
|
@@ -3193,6 +3660,14 @@ function refuseSetupConflicts(state, params) {
|
|
|
3193
3660
|
];
|
|
3194
3661
|
if (conflicts.length) throw fail(`This profile's saved setup conflicts with ${conflicts.join(" and ")}. Omit the option to reuse the saved setup. Its session and notification preferences were not changed.${params.resume ? "" : " Do not create a separate profile on your own: if this session should get a different address, ask the user whether to keep the existing one or disconnect it first."}`);
|
|
3195
3662
|
}
|
|
3663
|
+
function invitationHashOrNull(invitation) {
|
|
3664
|
+
if (invitation === void 0) return null;
|
|
3665
|
+
try {
|
|
3666
|
+
return agentInvitationHash(invitation);
|
|
3667
|
+
} catch {
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3196
3671
|
/** One resumable operation owns setup plumbing; it never stores or replays the invitation. */
|
|
3197
3672
|
async function setupAgent(params) {
|
|
3198
3673
|
const profileName = agentProfileName(params.profileName);
|
|
@@ -3212,7 +3687,9 @@ async function setupAgent(params) {
|
|
|
3212
3687
|
const value = readMailJson(join(directory, "setup.json"));
|
|
3213
3688
|
saved = value === null ? null : parseState(value);
|
|
3214
3689
|
} catch {}
|
|
3215
|
-
|
|
3690
|
+
const newHash = params.resume ? null : invitationHashOrNull(params.invitation);
|
|
3691
|
+
const replacesRevoked = (state) => newHash !== null && state.invitationHash !== newHash && savedSetupRevoked(params.configDir, profileName, state.invitationHash);
|
|
3692
|
+
if (saved && !replacesRevoked(saved)) refuseSetupConflicts(saved, params);
|
|
3216
3693
|
const receiverMode = params.receiverMode ?? (params.resume ? saved?.receiverMode : void 0);
|
|
3217
3694
|
if (params.session === void 0 && (receiverMode ?? "native") !== "poll") throw fail("--session requires the exact loaded session UUID. Without one, use --receiver poll. No invitation was claimed.");
|
|
3218
3695
|
if ((receiverMode ?? "native") === "native" && params.session) try {
|
|
@@ -3222,12 +3699,26 @@ async function setupAgent(params) {
|
|
|
3222
3699
|
}
|
|
3223
3700
|
privateMailDirectory(directory, true);
|
|
3224
3701
|
const release = acquireListenLock(directory, "setup");
|
|
3702
|
+
let releaseClaim;
|
|
3225
3703
|
let discardStub = false;
|
|
3226
3704
|
let claimedName;
|
|
3227
3705
|
try {
|
|
3228
3706
|
const path = join(directory, "setup.json");
|
|
3229
3707
|
const value = readMailJson(path);
|
|
3230
3708
|
let state = value === null ? null : parseState(value);
|
|
3709
|
+
if (state && replacesRevoked(state)) {
|
|
3710
|
+
try {
|
|
3711
|
+
releaseClaim = acquireListenLock(agentProfilesDirectory(params.configDir), "agent-connection-setup");
|
|
3712
|
+
} catch {
|
|
3713
|
+
throw fail("Another agent setup is running on this machine. No invitation was claimed and nothing was changed; retry when it finishes.");
|
|
3714
|
+
}
|
|
3715
|
+
const current = readMailJson(path);
|
|
3716
|
+
state = current === null ? null : parseState(current);
|
|
3717
|
+
if (state && replacesRevoked(state)) {
|
|
3718
|
+
archiveRevokedSetup(params.configDir, profileName, new Date(dependencies.now()));
|
|
3719
|
+
state = null;
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3231
3722
|
if (state) refuseSetupConflicts(state, params);
|
|
3232
3723
|
if (!state && loadConnectedAgentProfile(params.configDir, profileName)) throw fail("This profile was configured without a setup binding. Use its existing session setup; do not adopt it into another session.");
|
|
3233
3724
|
if (params.resume && !state) throw fail("No resumable setup exists for this profile. Do not adopt another session's identity.");
|
|
@@ -3256,7 +3747,8 @@ async function setupAgent(params) {
|
|
|
3256
3747
|
profileName,
|
|
3257
3748
|
invitation: params.invitation ?? "",
|
|
3258
3749
|
fetch: params.fetch,
|
|
3259
|
-
presence: true
|
|
3750
|
+
presence: true,
|
|
3751
|
+
claimLockHeld: releaseClaim !== void 0
|
|
3260
3752
|
})).name;
|
|
3261
3753
|
} catch (error) {
|
|
3262
3754
|
if (error instanceof AgentInvitationRejectedError && created) {
|
|
@@ -3268,6 +3760,9 @@ async function setupAgent(params) {
|
|
|
3268
3760
|
}
|
|
3269
3761
|
}
|
|
3270
3762
|
throw error;
|
|
3763
|
+
} finally {
|
|
3764
|
+
releaseClaim?.();
|
|
3765
|
+
releaseClaim = void 0;
|
|
3271
3766
|
}
|
|
3272
3767
|
}
|
|
3273
3768
|
const profile = loadConnectedAgentProfile(params.configDir, profileName);
|
|
@@ -3357,6 +3852,7 @@ async function setupAgent(params) {
|
|
|
3357
3852
|
}
|
|
3358
3853
|
return result(state.receiverMode === "external" ? EXTERNAL_HOOKS_PENDING : state.receiverMode === "poll" ? "poll" : healthy ? "healthy" : "not_ready", ownerNotifications);
|
|
3359
3854
|
} finally {
|
|
3855
|
+
releaseClaim?.();
|
|
3360
3856
|
release();
|
|
3361
3857
|
if (discardStub) removeEmptyDirectory(directory);
|
|
3362
3858
|
}
|
|
@@ -3699,229 +4195,96 @@ async function runAgentConnect(options) {
|
|
|
3699
4195
|
});
|
|
3700
4196
|
}
|
|
3701
4197
|
const pendingPath = join(agentProfileDirectory(options.configDir, profileName), PENDING_AGENT_INFO_FILE);
|
|
3702
|
-
const noteValue = agentInfo ?? readPendingAgentInfo(pendingPath);
|
|
3703
|
-
let agentInfoState = "not_requested";
|
|
3704
|
-
if (noteValue === null) skipped.push({
|
|
3705
|
-
step: "agent_info",
|
|
3706
|
-
reason: "not_requested"
|
|
3707
|
-
});
|
|
3708
|
-
else if (!verified) {
|
|
3709
|
-
agentInfoState = "pending_verification";
|
|
3710
|
-
savePendingAgentInfo(pendingPath, noteValue);
|
|
3711
|
-
skipped.push({
|
|
3712
|
-
step: "agent_info",
|
|
3713
|
-
reason: "pending_verification"
|
|
3714
|
-
});
|
|
3715
|
-
} else {
|
|
3716
|
-
agentInfoState = await dependencies.seedAgentInfo(profileName, noteValue);
|
|
3717
|
-
if (agentInfoState === "failed") {
|
|
3718
|
-
savePendingAgentInfo(pendingPath, noteValue);
|
|
3719
|
-
skipped.push({
|
|
3720
|
-
step: "agent_info",
|
|
3721
|
-
reason: "failed"
|
|
3722
|
-
});
|
|
3723
|
-
} else clearPendingAgentInfo(pendingPath);
|
|
3724
|
-
if (agentInfoState === "already_present") skipped.push({
|
|
3725
|
-
step: "agent_info",
|
|
3726
|
-
reason: "already_present"
|
|
3727
|
-
});
|
|
3728
|
-
}
|
|
3729
|
-
const receiving = receiver === "external" ? {
|
|
3730
|
-
...result.receiving,
|
|
3731
|
-
state: externalReceivingState(externalHook),
|
|
3732
|
-
mode: "external",
|
|
3733
|
-
hook: externalHook
|
|
3734
|
-
} : receiver === "poll" ? {
|
|
3735
|
-
...result.receiving,
|
|
3736
|
-
mode: "poll",
|
|
3737
|
-
checkCommand: pollCheckCommand(invocation, result.identity.profileName),
|
|
3738
|
-
...fallback ? {
|
|
3739
|
-
fallbackReason: fallback.reason,
|
|
3740
|
-
fallbackDetail: fallback.detail,
|
|
3741
|
-
codexVersion: fallback.codexVersion
|
|
3742
|
-
} : {}
|
|
3743
|
-
} : {
|
|
3744
|
-
...result.receiving,
|
|
3745
|
-
mode: "native",
|
|
3746
|
-
mailCheck: mailCheck?.state ?? "not_started",
|
|
3747
|
-
lastSuccessfulMailCheckAt: mailCheck?.lastSuccessfulMailCheckAt ?? null
|
|
3748
|
-
};
|
|
3749
|
-
const receiverReady = receiver === "external" ? externalHook === "installed_unverified" : receiver === "poll" || result.receiving.state === "healthy";
|
|
3750
|
-
const resumeCommand = [
|
|
3751
|
-
invocation,
|
|
3752
|
-
"agent connect",
|
|
3753
|
-
`--profile ${result.identity.profileName}`,
|
|
3754
|
-
...session ? [`--session ${session}`] : [],
|
|
3755
|
-
`--receiver ${receiver}`,
|
|
3756
|
-
"--resume",
|
|
3757
|
-
...options.contactRequests ?? savedSetup(options.configDir, profileName)?.contactRequests ? ["--contact-requests"] : [],
|
|
3758
|
-
...options.skill === false ? ["--no-skill"] : [],
|
|
3759
|
-
...options.project ? ["--project"] : [],
|
|
3760
|
-
"--json"
|
|
3761
|
-
].join(" ");
|
|
3762
|
-
return {
|
|
3763
|
-
status: verified && receiverReady ? "connected" : "pending",
|
|
3764
|
-
address: result.identity.agentAddress,
|
|
3765
|
-
orgId: result.identity.orgId,
|
|
3766
|
-
ownerAddress: result.identity.ownerAddress,
|
|
3767
|
-
ownerMemberAddress,
|
|
3768
|
-
profile: result.identity.profileName,
|
|
3769
|
-
...result.connectionName === void 0 ? {} : {
|
|
3770
|
-
name: result.connectionName,
|
|
3771
|
-
nameIsDefault: defaultConnectionName(result.connectionName, result.identity.agentAddress)
|
|
3772
|
-
},
|
|
3773
|
-
sessionId: session ?? null,
|
|
3774
|
-
runtime,
|
|
3775
|
-
cli: {
|
|
3776
|
-
version: options.cliVersion,
|
|
3777
|
-
capabilities: [...AGENT_CONNECT_CAPABILITIES]
|
|
3778
|
-
},
|
|
3779
|
-
skill,
|
|
3780
|
-
verification: result.verification,
|
|
3781
|
-
receiving,
|
|
3782
|
-
...result.ownerNotifications ? { ownerNotifications: result.ownerNotifications } : {},
|
|
3783
|
-
agentInfo: agentInfoState,
|
|
3784
|
-
skipped,
|
|
3785
|
-
warnings,
|
|
3786
|
-
selectProfile: `PRIMITIVE_AGENT_PROFILE=${result.identity.profileName}`,
|
|
3787
|
-
resumeCommand,
|
|
3788
|
-
guidance: `${result.guidance} ${ownerReportGuidance(identity)}`,
|
|
3789
|
-
identity,
|
|
3790
|
-
externalHook
|
|
3791
|
-
};
|
|
3792
|
-
}
|
|
3793
|
-
//#endregion
|
|
3794
|
-
//#region src/oclif/agent-disconnect.ts
|
|
3795
|
-
var AgentDisconnectError = class extends Error {};
|
|
3796
|
-
function credentialDigest(profile) {
|
|
3797
|
-
return createHash("sha256").update("primitive-agent-disconnect-v1\0").update(profile.api_key).digest("hex");
|
|
3798
|
-
}
|
|
3799
|
-
function boundSession(directory, profile) {
|
|
3800
|
-
const raw = readMailJson(join(directory, "setup.json"));
|
|
3801
|
-
if (raw === null) return null;
|
|
3802
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new AgentDisconnectError("Saved session setup is invalid. The connection was not changed.");
|
|
3803
|
-
const row = raw;
|
|
3804
|
-
if (row.receiverMode === "poll" && row.session === null && row.invitationHash === profile.invitation_hash) return null;
|
|
3805
|
-
if (typeof row.session !== "string" || !SESSION_UUID.test(row.session) || row.invitationHash !== profile.invitation_hash) throw new AgentDisconnectError("Saved session binding does not match this profile. The connection was not changed.");
|
|
3806
|
-
return row.session.toLowerCase();
|
|
3807
|
-
}
|
|
3808
|
-
function receiverStopped(status) {
|
|
3809
|
-
return status.phase === null || status.phase === "stopped" || status.phase === "failed" || status.reason === "exited";
|
|
3810
|
-
}
|
|
3811
|
-
function confirmationPath(directory, profile) {
|
|
3812
|
-
return join(directory, `disconnected-${profile.invitation_hash}.json`);
|
|
3813
|
-
}
|
|
3814
|
-
function confirmedLocally(directory, profile) {
|
|
3815
|
-
const raw = readMailJson(confirmationPath(directory, profile));
|
|
3816
|
-
if (raw === null) return false;
|
|
3817
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new AgentDisconnectError("Saved revocation confirmation is invalid. The credential was preserved.");
|
|
3818
|
-
const row = raw;
|
|
3819
|
-
if (row.version !== 1 || row.address !== profile.agent_address || row.api_base_url !== profile.api_base_url || row.credential_digest !== credentialDigest(profile) || typeof row.revoked_at !== "string" || !Number.isFinite(Date.parse(row.revoked_at))) throw new AgentDisconnectError("Saved revocation confirmation does not match this credential. The credential was preserved.");
|
|
3820
|
-
return true;
|
|
3821
|
-
}
|
|
3822
|
-
/** The API error code from an error envelope, when it has one. */
|
|
3823
|
-
function errorCode(error) {
|
|
3824
|
-
if (!error || typeof error !== "object") return void 0;
|
|
3825
|
-
const inner = error.error;
|
|
3826
|
-
const code = inner && typeof inner === "object" ? inner.code : error.code;
|
|
3827
|
-
return typeof code === "string" && /^[a-z0-9_]{1,64}$/.test(code) ? code : void 0;
|
|
3828
|
-
}
|
|
3829
|
-
async function revoke(profile, fetcher) {
|
|
3830
|
-
const client = new PrimitiveApiClient({
|
|
3831
|
-
apiKey: profile.api_key,
|
|
3832
|
-
apiBaseUrl: profile.api_base_url,
|
|
3833
|
-
...fetcher ? { fetch: fetcher } : {}
|
|
3834
|
-
});
|
|
3835
|
-
let result;
|
|
3836
|
-
try {
|
|
3837
|
-
result = await client.client.delete({
|
|
3838
|
-
security: [{
|
|
3839
|
-
scheme: "bearer",
|
|
3840
|
-
type: "http"
|
|
3841
|
-
}],
|
|
3842
|
-
url: "/agent-connections/{address}",
|
|
3843
|
-
path: { address: profile.agent_address },
|
|
3844
|
-
redirect: "error",
|
|
3845
|
-
signal: AbortSignal.timeout(25e3),
|
|
3846
|
-
responseStyle: "fields"
|
|
3847
|
-
});
|
|
3848
|
-
} catch {
|
|
3849
|
-
throw new AgentDisconnectError("Revocation outcome is unknown after a transport error. The profile and credential were preserved. Check the agent in the app before retrying.");
|
|
3850
|
-
}
|
|
3851
|
-
if (result.error !== void 0) {
|
|
3852
|
-
const status = result.response?.status;
|
|
3853
|
-
if (status === void 0) throw new AgentDisconnectError("Revocation outcome is unknown: no response was received from Primitive. The profile and credential were preserved. Check your connection, then retry or check the agent in the app.");
|
|
3854
|
-
const code = errorCode(result.error);
|
|
3855
|
-
const label = `HTTP ${status}${code ? `, ${code}` : ""}`;
|
|
3856
|
-
throw new AgentDisconnectError(status === 401 ? `Revocation was not confirmed (${label}). The credential was preserved; it may already be invalid. Check the agent in the app before retrying.` : status >= 500 ? `Primitive returned a server error while revoking this agent (${label}), so revocation is unconfirmed. The profile was preserved. Retry the same command; a 401 on retry means the credential was already revoked.` : `Revocation was refused by Primitive (${label}). The credential was preserved. Check the agent in the app before retrying.`);
|
|
3857
|
-
}
|
|
3858
|
-
const envelope = result.data;
|
|
3859
|
-
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new AgentDisconnectError("Revocation response was incomplete. The credential was preserved. Check the agent in the app before retrying.");
|
|
3860
|
-
const row = envelope;
|
|
3861
|
-
const data = row.data;
|
|
3862
|
-
const connection = data && typeof data === "object" && !Array.isArray(data) ? data.connection : void 0;
|
|
3863
|
-
if (row.success !== true || !connection || typeof connection !== "object" || Array.isArray(connection) || connection.address !== profile.agent_address || connection.owner_address !== profile.owner_address || connection.status !== "revoked") throw new AgentDisconnectError("Revocation response was incomplete. The credential was preserved. Check the agent in the app before retrying.");
|
|
3864
|
-
}
|
|
3865
|
-
/** Revoke one selected connected credential; never alter OAuth login or mail evidence. */
|
|
3866
|
-
async function disconnectAgent(params) {
|
|
3867
|
-
const profileName = agentProfileName(params.profileName);
|
|
3868
|
-
if (!loadConnectedAgentProfile(params.configDir, profileName)) throw new AgentDisconnectError("This agent profile is not configured. Nothing was changed.");
|
|
3869
|
-
const directory = agentProfileDirectory(params.configDir, profileName);
|
|
3870
|
-
let releaseSetup;
|
|
3871
|
-
let releaseClaim;
|
|
3872
|
-
try {
|
|
3873
|
-
releaseSetup = acquireListenLock(directory, "setup");
|
|
3874
|
-
releaseClaim = acquireListenLock(agentProfilesDirectory(params.configDir), "agent-connection-setup");
|
|
3875
|
-
const profile = loadConnectedAgentProfile(params.configDir, profileName);
|
|
3876
|
-
if (!profile) throw new AgentDisconnectError("The selected profile changed before disconnect. Nothing was changed.");
|
|
3877
|
-
const session = boundSession(directory, profile);
|
|
3878
|
-
const receiver = session ? await (params.stopReceiver ?? stopBackgroundListen)({
|
|
3879
|
-
configDir: params.configDir,
|
|
3880
|
-
scope: notificationScope(profile.api_base_url, profile.api_key),
|
|
3881
|
-
threadId: session
|
|
3882
|
-
}) : null;
|
|
3883
|
-
if (receiver && !receiverStopped(receiver)) throw new AgentDisconnectError("The bound receiver has not confirmed stopping. The connection and credential were preserved. Check listener status before retrying.");
|
|
3884
|
-
const previous = confirmedLocally(directory, profile);
|
|
3885
|
-
if (!previous) await revoke(profile, params.fetch);
|
|
3886
|
-
if (!previous) try {
|
|
3887
|
-
writeMailJson(confirmationPath(directory, profile), {
|
|
3888
|
-
version: 1,
|
|
3889
|
-
address: profile.agent_address,
|
|
3890
|
-
api_base_url: profile.api_base_url,
|
|
3891
|
-
credential_digest: credentialDigest(profile),
|
|
3892
|
-
revoked_at: (params.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
4198
|
+
const noteValue = agentInfo ?? readPendingAgentInfo(pendingPath);
|
|
4199
|
+
let agentInfoState = "not_requested";
|
|
4200
|
+
if (noteValue === null) skipped.push({
|
|
4201
|
+
step: "agent_info",
|
|
4202
|
+
reason: "not_requested"
|
|
4203
|
+
});
|
|
4204
|
+
else if (!verified) {
|
|
4205
|
+
agentInfoState = "pending_verification";
|
|
4206
|
+
savePendingAgentInfo(pendingPath, noteValue);
|
|
4207
|
+
skipped.push({
|
|
4208
|
+
step: "agent_info",
|
|
4209
|
+
reason: "pending_verification"
|
|
4210
|
+
});
|
|
4211
|
+
} else {
|
|
4212
|
+
agentInfoState = await dependencies.seedAgentInfo(profileName, noteValue);
|
|
4213
|
+
if (agentInfoState === "failed") {
|
|
4214
|
+
savePendingAgentInfo(pendingPath, noteValue);
|
|
4215
|
+
skipped.push({
|
|
4216
|
+
step: "agent_info",
|
|
4217
|
+
reason: "failed"
|
|
3893
4218
|
});
|
|
3894
|
-
}
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
try {
|
|
3900
|
-
removeMailFile(join(directory, "connection.json"));
|
|
3901
|
-
} catch {
|
|
3902
|
-
throw new AgentDisconnectError("Primitive confirmed revocation, but the local credential could not be removed. Confirmation is saved; retry this command to finish local cleanup.");
|
|
3903
|
-
}
|
|
3904
|
-
const externalHook = session ? uninstallClaudeWakeHook({
|
|
3905
|
-
configDir: params.configDir,
|
|
3906
|
-
profileName,
|
|
3907
|
-
agentAddress: profile.agent_address,
|
|
3908
|
-
sessionId: session,
|
|
3909
|
-
env: params.env
|
|
3910
|
-
}) ? "removed" : "unavailable" : null;
|
|
3911
|
-
return {
|
|
3912
|
-
status: "disconnected",
|
|
3913
|
-
identity: connectedAgentIdentity(profileName, profile),
|
|
3914
|
-
receiver,
|
|
3915
|
-
revocation: previous ? "previously_confirmed" : "confirmed",
|
|
3916
|
-
externalHook
|
|
3917
|
-
};
|
|
3918
|
-
} catch (error) {
|
|
3919
|
-
if (error instanceof AgentDisconnectError) throw error;
|
|
3920
|
-
throw new AgentDisconnectError("Disconnect could not safely complete. The credential and local evidence were preserved. Check this profile and listener status before retrying.");
|
|
3921
|
-
} finally {
|
|
3922
|
-
releaseClaim?.();
|
|
3923
|
-
releaseSetup?.();
|
|
4219
|
+
} else clearPendingAgentInfo(pendingPath);
|
|
4220
|
+
if (agentInfoState === "already_present") skipped.push({
|
|
4221
|
+
step: "agent_info",
|
|
4222
|
+
reason: "already_present"
|
|
4223
|
+
});
|
|
3924
4224
|
}
|
|
4225
|
+
const receiving = receiver === "external" ? {
|
|
4226
|
+
...result.receiving,
|
|
4227
|
+
state: externalReceivingState(externalHook),
|
|
4228
|
+
mode: "external",
|
|
4229
|
+
hook: externalHook
|
|
4230
|
+
} : receiver === "poll" ? {
|
|
4231
|
+
...result.receiving,
|
|
4232
|
+
mode: "poll",
|
|
4233
|
+
checkCommand: pollCheckCommand(invocation, result.identity.profileName),
|
|
4234
|
+
...fallback ? {
|
|
4235
|
+
fallbackReason: fallback.reason,
|
|
4236
|
+
fallbackDetail: fallback.detail,
|
|
4237
|
+
codexVersion: fallback.codexVersion
|
|
4238
|
+
} : {}
|
|
4239
|
+
} : {
|
|
4240
|
+
...result.receiving,
|
|
4241
|
+
mode: "native",
|
|
4242
|
+
mailCheck: mailCheck?.state ?? "not_started",
|
|
4243
|
+
lastSuccessfulMailCheckAt: mailCheck?.lastSuccessfulMailCheckAt ?? null
|
|
4244
|
+
};
|
|
4245
|
+
const receiverReady = receiver === "external" ? externalHook === "installed_unverified" : receiver === "poll" || result.receiving.state === "healthy";
|
|
4246
|
+
const resumeCommand = [
|
|
4247
|
+
invocation,
|
|
4248
|
+
"agent connect",
|
|
4249
|
+
`--profile ${result.identity.profileName}`,
|
|
4250
|
+
...session ? [`--session ${session}`] : [],
|
|
4251
|
+
`--receiver ${receiver}`,
|
|
4252
|
+
"--resume",
|
|
4253
|
+
...options.contactRequests ?? savedSetup(options.configDir, profileName)?.contactRequests ? ["--contact-requests"] : [],
|
|
4254
|
+
...options.skill === false ? ["--no-skill"] : [],
|
|
4255
|
+
...options.project ? ["--project"] : [],
|
|
4256
|
+
"--json"
|
|
4257
|
+
].join(" ");
|
|
4258
|
+
return {
|
|
4259
|
+
status: verified && receiverReady ? "connected" : "pending",
|
|
4260
|
+
address: result.identity.agentAddress,
|
|
4261
|
+
orgId: result.identity.orgId,
|
|
4262
|
+
ownerAddress: result.identity.ownerAddress,
|
|
4263
|
+
ownerMemberAddress,
|
|
4264
|
+
profile: result.identity.profileName,
|
|
4265
|
+
...result.connectionName === void 0 ? {} : {
|
|
4266
|
+
name: result.connectionName,
|
|
4267
|
+
nameIsDefault: defaultConnectionName(result.connectionName, result.identity.agentAddress)
|
|
4268
|
+
},
|
|
4269
|
+
sessionId: session ?? null,
|
|
4270
|
+
runtime,
|
|
4271
|
+
cli: {
|
|
4272
|
+
version: options.cliVersion,
|
|
4273
|
+
capabilities: [...AGENT_CONNECT_CAPABILITIES]
|
|
4274
|
+
},
|
|
4275
|
+
skill,
|
|
4276
|
+
verification: result.verification,
|
|
4277
|
+
receiving,
|
|
4278
|
+
...result.ownerNotifications ? { ownerNotifications: result.ownerNotifications } : {},
|
|
4279
|
+
agentInfo: agentInfoState,
|
|
4280
|
+
skipped,
|
|
4281
|
+
warnings,
|
|
4282
|
+
selectProfile: `PRIMITIVE_AGENT_PROFILE=${result.identity.profileName}`,
|
|
4283
|
+
resumeCommand,
|
|
4284
|
+
guidance: `${result.guidance} ${ownerReportGuidance(identity)}`,
|
|
4285
|
+
identity,
|
|
4286
|
+
externalHook
|
|
4287
|
+
};
|
|
3925
4288
|
}
|
|
3926
4289
|
//#endregion
|
|
3927
4290
|
//#region src/oclif/agent-enroll.ts
|
|
@@ -4434,7 +4797,7 @@ async function enrollAgent(params) {
|
|
|
4434
4797
|
//#endregion
|
|
4435
4798
|
//#region src/oclif/machine-session.ts
|
|
4436
4799
|
var machine_session_exports = /* @__PURE__ */ __exportAll({
|
|
4437
|
-
|
|
4800
|
+
HOOK_END_GRACE_MS: () => HOOK_END_GRACE_MS,
|
|
4438
4801
|
OMP_MAIN: () => OMP_MAIN,
|
|
4439
4802
|
agentConnectionState: () => agentConnectionState,
|
|
4440
4803
|
agentInfoForSession: () => agentInfoForSession,
|
|
@@ -4453,40 +4816,6 @@ var machine_session_exports = /* @__PURE__ */ __exportAll({
|
|
|
4453
4816
|
runtimeSessionId: () => runtimeSessionId,
|
|
4454
4817
|
workspaceName: () => workspaceName
|
|
4455
4818
|
});
|
|
4456
|
-
const MACHINE_RUNTIMES = [
|
|
4457
|
-
"claude",
|
|
4458
|
-
"codex",
|
|
4459
|
-
"omp"
|
|
4460
|
-
];
|
|
4461
|
-
function sessionRecordPath(configDir, session) {
|
|
4462
|
-
return join(configDir, "machine", "sessions", `${session}.json`);
|
|
4463
|
-
}
|
|
4464
|
-
function readSessionRecord(configDir, session) {
|
|
4465
|
-
try {
|
|
4466
|
-
const value = readMailJson(sessionRecordPath(configDir, session));
|
|
4467
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4468
|
-
const row = value;
|
|
4469
|
-
if (row.version !== 1 || row.session !== session || typeof row.profile !== "string" || typeof row.name !== "string" || !MACHINE_RUNTIMES.includes(row.runtime) || row.createdBy !== "session-register" && row.createdBy !== "existing") return null;
|
|
4470
|
-
return {
|
|
4471
|
-
version: 1,
|
|
4472
|
-
runtime: row.runtime,
|
|
4473
|
-
session,
|
|
4474
|
-
profile: row.profile,
|
|
4475
|
-
name: row.name,
|
|
4476
|
-
createdBy: row.createdBy,
|
|
4477
|
-
address: typeof row.address === "string" ? row.address : null,
|
|
4478
|
-
agentInfo: row.agentInfo === "created" || row.agentInfo === "already_present" ? row.agentInfo : null,
|
|
4479
|
-
registeredAt: typeof row.registeredAt === "string" ? row.registeredAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
4480
|
-
endedAt: typeof row.endedAt === "string" ? row.endedAt : null,
|
|
4481
|
-
disconnect: row.disconnect === "done" || row.disconnect === "pending" || row.disconnect === "pending_enrollment" ? row.disconnect : typeof row.endedAt === "string" ? "done" : null
|
|
4482
|
-
};
|
|
4483
|
-
} catch {
|
|
4484
|
-
return null;
|
|
4485
|
-
}
|
|
4486
|
-
}
|
|
4487
|
-
function saveSessionRecord(configDir, row) {
|
|
4488
|
-
writeMailJson(sessionRecordPath(configDir, row.session), row);
|
|
4489
|
-
}
|
|
4490
4819
|
/** The repository (or directory) a session works in, for its default name. */
|
|
4491
4820
|
function workspaceName(cwd) {
|
|
4492
4821
|
let current = resolve(cwd);
|
|
@@ -4644,32 +4973,6 @@ function receiverMode(setupDirectory) {
|
|
|
4644
4973
|
return null;
|
|
4645
4974
|
}
|
|
4646
4975
|
}
|
|
4647
|
-
/** Serialize every read-modify-write of one session's record. */
|
|
4648
|
-
async function withRecordLock(configDir, session, action) {
|
|
4649
|
-
const directory = join(configDir, "machine", "sessions");
|
|
4650
|
-
privateMailDirectory(directory, true);
|
|
4651
|
-
let release;
|
|
4652
|
-
for (let attempt = 0; attempt < 200 && !release; attempt++) try {
|
|
4653
|
-
release = acquireListenLock(directory, `session-record-${session}`);
|
|
4654
|
-
} catch {
|
|
4655
|
-
await setTimeout$1(50);
|
|
4656
|
-
}
|
|
4657
|
-
if (!release) throw new ListenStateError("The session record is locked by another run.");
|
|
4658
|
-
try {
|
|
4659
|
-
return await action();
|
|
4660
|
-
} finally {
|
|
4661
|
-
release();
|
|
4662
|
-
}
|
|
4663
|
-
}
|
|
4664
|
-
/** Read, change and save one record under its lock. Returns the saved record. */
|
|
4665
|
-
function updateSessionRecord(configDir, session, change) {
|
|
4666
|
-
return withRecordLock(configDir, session, () => {
|
|
4667
|
-
const current = readSessionRecord(configDir, session);
|
|
4668
|
-
const next = change(current);
|
|
4669
|
-
if (next && next !== current) saveSessionRecord(configDir, next);
|
|
4670
|
-
return next ?? current;
|
|
4671
|
-
});
|
|
4672
|
-
}
|
|
4673
4976
|
/** The address an interrupted enrollment saved, when there is one. */
|
|
4674
4977
|
function enrollmentAddress(configDir, profileName) {
|
|
4675
4978
|
try {
|
|
@@ -4791,17 +5094,95 @@ async function revokeWithMemberLogin(configDir, address, fetchImpl = fetch) {
|
|
|
4791
5094
|
return false;
|
|
4792
5095
|
}
|
|
4793
5096
|
}
|
|
5097
|
+
const DISCONNECT_DEFAULTS = {
|
|
5098
|
+
disconnect: disconnectAgent,
|
|
5099
|
+
revokeByAddress: revokeWithMemberLogin,
|
|
5100
|
+
uninstallHook: (options) => uninstallClaudeWakeHook(options),
|
|
5101
|
+
now: () => /* @__PURE__ */ new Date()
|
|
5102
|
+
};
|
|
4794
5103
|
/** Enrollment saves its state before any request, so a quiet record this old created nothing. */
|
|
4795
5104
|
const ENROLLMENT_SETTLED_MS = 6e5;
|
|
5105
|
+
function sameAddress(a, b) {
|
|
5106
|
+
return a !== null && b !== null && a.toLowerCase() === b.toLowerCase();
|
|
5107
|
+
}
|
|
5108
|
+
/**
|
|
5109
|
+
* Whether the profile's current credential is the one this registration
|
|
5110
|
+
* created. Another credential can sit in the same profile after `agent
|
|
5111
|
+
* connect` or a replacement, and that one must never be disconnected here.
|
|
5112
|
+
*/
|
|
5113
|
+
function holdsRegisteredCredential(configDir, record, current) {
|
|
5114
|
+
if (record.invitationHash) return record.invitationHash === current.invitation_hash && (record.address === null || sameAddress(record.address, current.agent_address));
|
|
5115
|
+
if (record.address) return sameAddress(record.address, current.agent_address);
|
|
5116
|
+
return sameAddress(enrollmentAddress(configDir, record.profile).address, current.agent_address);
|
|
5117
|
+
}
|
|
5118
|
+
/** A saved revocation confirmation in the profile names this address. */
|
|
5119
|
+
function addressRevokedLocally(configDir, profileName, address) {
|
|
5120
|
+
try {
|
|
5121
|
+
const directory = agentProfileDirectory(configDir, profileName);
|
|
5122
|
+
return readdirSync(directory).some((name) => {
|
|
5123
|
+
if (!/^disconnected-[a-f0-9]{64}\.json$/.test(name)) return false;
|
|
5124
|
+
try {
|
|
5125
|
+
const row = readMailJson(join(directory, name));
|
|
5126
|
+
return typeof row?.address === "string" && sameAddress(row.address, address);
|
|
5127
|
+
} catch {
|
|
5128
|
+
return false;
|
|
5129
|
+
}
|
|
5130
|
+
});
|
|
5131
|
+
} catch {
|
|
5132
|
+
return false;
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
/**
|
|
5136
|
+
* The profile holds another credential now. Leave it connected; remove only
|
|
5137
|
+
* the registered agent's own exact-session hooks (they are keyed by its
|
|
5138
|
+
* address, so the current agent's hooks are untouched), and revoke the
|
|
5139
|
+
* registered agent by address if nothing confirms it is already revoked.
|
|
5140
|
+
* When the current credential has the registered address (the same address
|
|
5141
|
+
* reconnected with a new credential), hooks and a revocation by address would
|
|
5142
|
+
* both hit the current agent, so nothing is touched.
|
|
5143
|
+
*/
|
|
5144
|
+
async function cleanUpReplacedRegistration(configDir, record, current, deps, env, fetchImpl) {
|
|
5145
|
+
const address = record.address;
|
|
5146
|
+
if (!address || sameAddress(address, current.agent_address)) return;
|
|
5147
|
+
if (record.runtime === "claude") try {
|
|
5148
|
+
deps.uninstallHook({
|
|
5149
|
+
configDir,
|
|
5150
|
+
profileName: record.profile,
|
|
5151
|
+
agentAddress: address,
|
|
5152
|
+
sessionId: record.session,
|
|
5153
|
+
env
|
|
5154
|
+
});
|
|
5155
|
+
} catch {}
|
|
5156
|
+
if (addressRevokedLocally(configDir, record.profile, address)) return;
|
|
5157
|
+
try {
|
|
5158
|
+
await deps.revokeByAddress(configDir, address, fetchImpl);
|
|
5159
|
+
} catch {}
|
|
5160
|
+
}
|
|
4796
5161
|
async function disconnectSessionAgent(configDir, record, deps, env, fetchImpl, enrollmentSettled = false) {
|
|
4797
5162
|
try {
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
5163
|
+
const current = loadConnectedAgentProfile(configDir, record.profile);
|
|
5164
|
+
if (current) {
|
|
5165
|
+
const release = async (holder) => {
|
|
5166
|
+
await cleanUpReplacedRegistration(configDir, record, holder, deps, env, fetchImpl);
|
|
5167
|
+
return "released";
|
|
5168
|
+
};
|
|
5169
|
+
if (!holdsRegisteredCredential(configDir, record, current)) return await release(current);
|
|
5170
|
+
try {
|
|
5171
|
+
await deps.disconnect({
|
|
5172
|
+
configDir,
|
|
5173
|
+
profileName: record.profile,
|
|
5174
|
+
fetch: fetchImpl,
|
|
5175
|
+
env,
|
|
5176
|
+
expected: {
|
|
5177
|
+
address: current.agent_address,
|
|
5178
|
+
invitationHash: current.invitation_hash
|
|
5179
|
+
}
|
|
5180
|
+
});
|
|
5181
|
+
} catch (error) {
|
|
5182
|
+
if (!(error instanceof AgentCredentialChangedError)) throw error;
|
|
5183
|
+
const holder = loadConnectedAgentProfile(configDir, record.profile);
|
|
5184
|
+
return holder ? await release(holder) : "pending";
|
|
5185
|
+
}
|
|
4805
5186
|
return "done";
|
|
4806
5187
|
}
|
|
4807
5188
|
if (disconnectConfirmedLocally(configDir, record.profile)) return "done";
|
|
@@ -4818,10 +5199,16 @@ async function disconnectSessionAgent(configDir, record, deps, env, fetchImpl, e
|
|
|
4818
5199
|
}
|
|
4819
5200
|
async function finishSessionDisconnect(configDir, record, deps, env, fetchImpl, enrollmentSettled = false) {
|
|
4820
5201
|
const outcome = await disconnectSessionAgent(configDir, record, deps, env, fetchImpl, enrollmentSettled);
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
5202
|
+
const saved = await updateSessionRecord(configDir, record.session, (current) => {
|
|
5203
|
+
if (current?.createdBy !== "session-register" || current.disconnect === "done") return current;
|
|
5204
|
+
if (outcome === "released") return releasedRecord(current, "credential_changed", deps.now());
|
|
5205
|
+
return current.disconnect === outcome ? current : {
|
|
5206
|
+
...current,
|
|
5207
|
+
disconnect: outcome
|
|
5208
|
+
};
|
|
5209
|
+
});
|
|
5210
|
+
if (saved?.createdBy === "existing" && outcome === "released") return "released";
|
|
5211
|
+
return saved?.disconnect === "done" ? "done" : outcome;
|
|
4825
5212
|
}
|
|
4826
5213
|
const RECEIVING = {
|
|
4827
5214
|
claude: "external_hook",
|
|
@@ -4836,6 +5223,8 @@ function withDefined(defaults, overrides) {
|
|
|
4836
5223
|
}
|
|
4837
5224
|
const HELD_DETAIL = "This session already receives as another connected profile, so this profile's receive hooks were not added. Run `primitive machine doctor --fix --profile <profile>` to bind it deliberately.";
|
|
4838
5225
|
const ENDED_DETAIL = "This session ended, so its agent was disconnected. A new address is never created for the same session.";
|
|
5226
|
+
const ENDED_DEFERRED_DETAIL = "This session ended moments ago. Its agent is disconnected after a short wait unless the session starts again first.";
|
|
5227
|
+
const RELEASED_DETAIL = "This session's profile now holds an agent connected some other way, so it was left connected.";
|
|
4839
5228
|
const ENDED_PENDING_DETAIL = "This session ended, but disconnecting its agent has not been confirmed yet; `primitive machine doctor --fix` retries it.";
|
|
4840
5229
|
/**
|
|
4841
5230
|
* Give this session an address once. Safe to run on every start and resume:
|
|
@@ -4851,9 +5240,7 @@ async function registerSession(options) {
|
|
|
4851
5240
|
seedAgentInfo: (configDir, profileName, value) => seedAgentInfoNote(configDir, profileName, value, options.fetch),
|
|
4852
5241
|
connectionState: (profile) => agentConnectionState(profile, options.fetch),
|
|
4853
5242
|
ompSession: ompProcessSession,
|
|
4854
|
-
|
|
4855
|
-
disconnect: disconnectAgent,
|
|
4856
|
-
revokeByAddress: revokeWithMemberLogin
|
|
5243
|
+
...DISCONNECT_DEFAULTS
|
|
4857
5244
|
}, options.dependencies);
|
|
4858
5245
|
const runtime = options.runtime;
|
|
4859
5246
|
const base = {
|
|
@@ -4920,7 +5307,18 @@ async function registerSession(options) {
|
|
|
4920
5307
|
};
|
|
4921
5308
|
const cwd = resolve(options.cwd ?? process.cwd());
|
|
4922
5309
|
const ended = async (current, enrolledNow = false) => {
|
|
5310
|
+
if (current.disconnect === "deferred") return {
|
|
5311
|
+
...known,
|
|
5312
|
+
address: current.address,
|
|
5313
|
+
status: "ended",
|
|
5314
|
+
detail: ENDED_DEFERRED_DETAIL
|
|
5315
|
+
};
|
|
4923
5316
|
const outcome = current.disconnect === "done" && !enrolledNow ? "done" : await finishSessionDisconnect(options.configDir, current, deps, env, options.fetch, enrolledNow);
|
|
5317
|
+
if (outcome === "released") return {
|
|
5318
|
+
...known,
|
|
5319
|
+
status: "already_registered",
|
|
5320
|
+
detail: RELEASED_DETAIL
|
|
5321
|
+
};
|
|
4924
5322
|
return {
|
|
4925
5323
|
...known,
|
|
4926
5324
|
address: current.address,
|
|
@@ -4928,7 +5326,7 @@ async function registerSession(options) {
|
|
|
4928
5326
|
detail: outcome === "done" ? ENDED_DETAIL : ENDED_PENDING_DETAIL
|
|
4929
5327
|
};
|
|
4930
5328
|
};
|
|
4931
|
-
const newRecord = (createdBy, address) => ({
|
|
5329
|
+
const newRecord = (createdBy, address, invitationHash = null) => ({
|
|
4932
5330
|
version: 1,
|
|
4933
5331
|
runtime,
|
|
4934
5332
|
session: sessionId,
|
|
@@ -4936,13 +5334,23 @@ async function registerSession(options) {
|
|
|
4936
5334
|
name: defaultSessionName(runtime, cwd),
|
|
4937
5335
|
createdBy,
|
|
4938
5336
|
address,
|
|
5337
|
+
invitationHash,
|
|
4939
5338
|
agentInfo: null,
|
|
4940
5339
|
registeredAt: deps.now().toISOString(),
|
|
4941
5340
|
endedAt: null,
|
|
4942
|
-
disconnect: null
|
|
5341
|
+
disconnect: null,
|
|
5342
|
+
deferredUntil: null,
|
|
5343
|
+
endReason: null,
|
|
5344
|
+
release: null
|
|
4943
5345
|
});
|
|
4944
5346
|
try {
|
|
4945
|
-
let record =
|
|
5347
|
+
let record = await updateSessionRecord(options.configDir, sessionId, (current) => current?.endedAt && current.disconnect === "deferred" ? {
|
|
5348
|
+
...current,
|
|
5349
|
+
endedAt: null,
|
|
5350
|
+
disconnect: null,
|
|
5351
|
+
deferredUntil: null,
|
|
5352
|
+
endReason: null
|
|
5353
|
+
} : current);
|
|
4946
5354
|
if (record?.endedAt) return await ended(record);
|
|
4947
5355
|
const finishAgentInfo = async () => {
|
|
4948
5356
|
if (reused || record?.agentInfo) return;
|
|
@@ -4968,7 +5376,7 @@ async function registerSession(options) {
|
|
|
4968
5376
|
const installHook = (profile) => runtime !== "claude" || receiverMode(agentProfileDirectory(options.configDir, profileName)) !== "external" ? "installed" : claudeHook(profile.agent_address);
|
|
4969
5377
|
const existing = loadConnectedAgentProfile(options.configDir, profileName);
|
|
4970
5378
|
if (existing) {
|
|
4971
|
-
record = await updateSessionRecord(options.configDir, sessionId, (current) => current ?? newRecord("existing", existing.agent_address));
|
|
5379
|
+
record = await updateSessionRecord(options.configDir, sessionId, (current) => current ?? newRecord("existing", existing.agent_address, existing.invitation_hash));
|
|
4972
5380
|
if (!record) throw new Error("session record unavailable");
|
|
4973
5381
|
if (record.endedAt) return await ended(record);
|
|
4974
5382
|
const state = await deps.connectionState(existing);
|
|
@@ -5045,14 +5453,18 @@ async function registerSession(options) {
|
|
|
5045
5453
|
enrollError = error;
|
|
5046
5454
|
}
|
|
5047
5455
|
let created = result?.identity.agentAddress ?? null;
|
|
5048
|
-
|
|
5049
|
-
|
|
5456
|
+
let saved = null;
|
|
5457
|
+
try {
|
|
5458
|
+
saved = loadConnectedAgentProfile(options.configDir, profileName);
|
|
5050
5459
|
} catch {
|
|
5051
|
-
|
|
5460
|
+
saved = null;
|
|
5052
5461
|
}
|
|
5053
|
-
|
|
5462
|
+
created ??= saved?.agent_address ?? enrollmentAddress(options.configDir, profileName).address;
|
|
5463
|
+
const createdHash = saved && created && sameAddress(saved.agent_address, created) ? saved.invitation_hash : null;
|
|
5464
|
+
record = await updateSessionRecord(options.configDir, sessionId, (current) => current && current.createdBy === "session-register" && created && (current.address !== created || createdHash !== null && current.invitationHash !== createdHash) ? {
|
|
5054
5465
|
...current,
|
|
5055
|
-
address: created
|
|
5466
|
+
address: created,
|
|
5467
|
+
invitationHash: createdHash ?? current.invitationHash
|
|
5056
5468
|
} : current);
|
|
5057
5469
|
if (!record) throw new Error("session record unavailable");
|
|
5058
5470
|
if (record.endedAt) return await ended(record, true);
|
|
@@ -5100,17 +5512,30 @@ async function registerSession(options) {
|
|
|
5100
5512
|
}
|
|
5101
5513
|
}
|
|
5102
5514
|
/**
|
|
5515
|
+
* How long an end from a runtime hook waits before disconnecting. A runtime
|
|
5516
|
+
* reports the same end for a restart, a switch to another session and a real
|
|
5517
|
+
* exit, so the hook cannot tell them apart; a start of the same session
|
|
5518
|
+
* within this window cancels the end and keeps its agent.
|
|
5519
|
+
*/
|
|
5520
|
+
const HOOK_END_GRACE_MS = 3e5;
|
|
5521
|
+
/** The end of this record is a hook's, still waiting. */
|
|
5522
|
+
function deferredEnd(record) {
|
|
5523
|
+
return Boolean(record?.endedAt) && record?.disconnect === "deferred";
|
|
5524
|
+
}
|
|
5525
|
+
/**
|
|
5103
5526
|
* Disconnect the agent a session got from `session-register`. Agents
|
|
5104
|
-
* connected any other way are left alone
|
|
5105
|
-
* the
|
|
5106
|
-
*
|
|
5527
|
+
* connected any other way are left alone, including a credential connected
|
|
5528
|
+
* later into the same profile. The end is recorded first, under the record's
|
|
5529
|
+
* lock, so a registration still enrolling sees it and disconnects the agent
|
|
5530
|
+
* it creates. With `graceMs` (an end reported by a runtime hook) the
|
|
5531
|
+
* disconnect waits that long, and a start of the same session meanwhile
|
|
5532
|
+
* cancels it. Never throws.
|
|
5107
5533
|
*/
|
|
5108
5534
|
async function endSession(options) {
|
|
5109
5535
|
const env = options.env ?? process.env;
|
|
5110
5536
|
const deps = withDefined({
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
now: () => /* @__PURE__ */ new Date()
|
|
5537
|
+
...DISCONNECT_DEFAULTS,
|
|
5538
|
+
sleep: (milliseconds) => setTimeout$1(milliseconds)
|
|
5114
5539
|
}, options.dependencies);
|
|
5115
5540
|
const runtime = options.runtime;
|
|
5116
5541
|
const base = {
|
|
@@ -5135,20 +5560,36 @@ async function endSession(options) {
|
|
|
5135
5560
|
detail: "No session ID was given or found. Nothing was changed."
|
|
5136
5561
|
};
|
|
5137
5562
|
const sessionId = session.toLowerCase();
|
|
5563
|
+
const graceMs = Math.max(0, options.graceMs ?? 0);
|
|
5564
|
+
const reason = options.reason && /^[a-z_]{1,40}$/.test(options.reason) ? options.reason : null;
|
|
5138
5565
|
try {
|
|
5139
5566
|
let managed = false;
|
|
5140
5567
|
let alreadyDone = false;
|
|
5141
|
-
|
|
5568
|
+
let deferred = false;
|
|
5569
|
+
let record = await updateSessionRecord(options.configDir, sessionId, (current) => {
|
|
5142
5570
|
if (current?.createdBy !== "session-register") return current;
|
|
5143
5571
|
managed = true;
|
|
5144
5572
|
if (current.endedAt && current.disconnect === "done") {
|
|
5145
5573
|
alreadyDone = true;
|
|
5146
5574
|
return current;
|
|
5147
5575
|
}
|
|
5576
|
+
const now = deps.now();
|
|
5577
|
+
if (graceMs > 0 && (!current.endedAt || deferredEnd(current))) {
|
|
5578
|
+
deferred = true;
|
|
5579
|
+
return {
|
|
5580
|
+
...current,
|
|
5581
|
+
endedAt: now.toISOString(),
|
|
5582
|
+
disconnect: "deferred",
|
|
5583
|
+
deferredUntil: new Date(now.getTime() + graceMs).toISOString(),
|
|
5584
|
+
endReason: reason
|
|
5585
|
+
};
|
|
5586
|
+
}
|
|
5148
5587
|
return {
|
|
5149
5588
|
...current,
|
|
5150
|
-
endedAt: current.endedAt ??
|
|
5151
|
-
disconnect: "pending"
|
|
5589
|
+
endedAt: current.endedAt ?? now.toISOString(),
|
|
5590
|
+
disconnect: current.disconnect === "pending_enrollment" ? "pending_enrollment" : "pending",
|
|
5591
|
+
deferredUntil: null,
|
|
5592
|
+
endReason: current.endReason ?? reason
|
|
5152
5593
|
};
|
|
5153
5594
|
});
|
|
5154
5595
|
const known = {
|
|
@@ -5166,7 +5607,34 @@ async function endSession(options) {
|
|
|
5166
5607
|
status: "already_ended",
|
|
5167
5608
|
detail: "This session's agent was already disconnected."
|
|
5168
5609
|
};
|
|
5169
|
-
|
|
5610
|
+
if (deferred) {
|
|
5611
|
+
const marked = record.endedAt;
|
|
5612
|
+
await deps.sleep(graceMs);
|
|
5613
|
+
let resumed = false;
|
|
5614
|
+
record = await updateSessionRecord(options.configDir, sessionId, (current) => {
|
|
5615
|
+
if (current?.createdBy !== "session-register" || current.endedAt !== marked || current.disconnect !== "deferred") {
|
|
5616
|
+
resumed = true;
|
|
5617
|
+
return current;
|
|
5618
|
+
}
|
|
5619
|
+
return {
|
|
5620
|
+
...current,
|
|
5621
|
+
disconnect: "pending",
|
|
5622
|
+
deferredUntil: null
|
|
5623
|
+
};
|
|
5624
|
+
});
|
|
5625
|
+
if (resumed || !record) return {
|
|
5626
|
+
...known,
|
|
5627
|
+
status: "resumed",
|
|
5628
|
+
detail: "The session started again (or was ended again) before its end took effect, so this end left its agent connected."
|
|
5629
|
+
};
|
|
5630
|
+
}
|
|
5631
|
+
const outcome = await finishSessionDisconnect(options.configDir, record, deps, env, options.fetch);
|
|
5632
|
+
if (outcome === "released") return {
|
|
5633
|
+
...known,
|
|
5634
|
+
status: "left_connected",
|
|
5635
|
+
detail: "This session's profile now holds an agent that `agent session-register` did not create, so it was left connected. Only the registered agent's own receive hooks were removed."
|
|
5636
|
+
};
|
|
5637
|
+
return outcome === "done" ? {
|
|
5170
5638
|
...known,
|
|
5171
5639
|
status: "disconnected",
|
|
5172
5640
|
detail: "This session's agent was disconnected."
|
|
@@ -5184,30 +5652,42 @@ async function endSession(options) {
|
|
|
5184
5652
|
};
|
|
5185
5653
|
}
|
|
5186
5654
|
}
|
|
5187
|
-
/**
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
return [];
|
|
5195
|
-
}
|
|
5196
|
-
return names.flatMap((name) => {
|
|
5197
|
-
const match = /^([0-9a-f-]{36})\.json$/.exec(name);
|
|
5198
|
-
if (!match?.[1]) return [];
|
|
5199
|
-
const record = readSessionRecord(configDir, match[1]);
|
|
5200
|
-
return record?.endedAt && (record.disconnect === "pending" || record.disconnect === "pending_enrollment") ? [record] : [];
|
|
5201
|
-
});
|
|
5655
|
+
/**
|
|
5656
|
+
* Ended sessions whose agent disconnect still needs confirming, including a
|
|
5657
|
+
* hook's end whose grace period is over but whose own process never finished
|
|
5658
|
+
* it (for example because the machine shut down).
|
|
5659
|
+
*/
|
|
5660
|
+
function pendingSessionDisconnects(configDir, now = /* @__PURE__ */ new Date()) {
|
|
5661
|
+
return listSessionRecords(configDir).filter((record) => record.createdBy === "session-register" && record.endedAt && (record.disconnect === "pending" || record.disconnect === "pending_enrollment" || record.disconnect === "deferred" && !(Date.parse(record.deferredUntil ?? "") > now.getTime())));
|
|
5202
5662
|
}
|
|
5203
|
-
/**
|
|
5663
|
+
/**
|
|
5664
|
+
* Retry every pending disconnect. Each goes through the same credential check
|
|
5665
|
+
* as a session end, so a profile reconnected some other way is left connected
|
|
5666
|
+
* (and counts as resolved). Returns how many are now resolved.
|
|
5667
|
+
*/
|
|
5204
5668
|
async function retryPendingDisconnects(configDir, options = {}) {
|
|
5205
|
-
const deps = withDefined(
|
|
5206
|
-
disconnect: disconnectAgent,
|
|
5207
|
-
revokeByAddress: revokeWithMemberLogin
|
|
5208
|
-
}, options.dependencies);
|
|
5669
|
+
const deps = withDefined(DISCONNECT_DEFAULTS, options.dependencies);
|
|
5209
5670
|
let done = 0;
|
|
5210
|
-
for (const
|
|
5671
|
+
for (const listed of pendingSessionDisconnects(configDir, deps.now())) {
|
|
5672
|
+
let record = listed;
|
|
5673
|
+
if (listed.disconnect === "deferred") {
|
|
5674
|
+
let changed = false;
|
|
5675
|
+
record = await updateSessionRecord(configDir, listed.session, (current) => {
|
|
5676
|
+
if (!current || current.endedAt !== listed.endedAt || current.disconnect !== "deferred") {
|
|
5677
|
+
changed = true;
|
|
5678
|
+
return current;
|
|
5679
|
+
}
|
|
5680
|
+
return {
|
|
5681
|
+
...current,
|
|
5682
|
+
disconnect: "pending",
|
|
5683
|
+
deferredUntil: null
|
|
5684
|
+
};
|
|
5685
|
+
});
|
|
5686
|
+
if (changed || !record) continue;
|
|
5687
|
+
}
|
|
5688
|
+
const outcome = await finishSessionDisconnect(configDir, record, deps, options.env ?? process.env, options.fetch);
|
|
5689
|
+
if (outcome === "done" || outcome === "released") done++;
|
|
5690
|
+
}
|
|
5211
5691
|
return done;
|
|
5212
5692
|
}
|
|
5213
5693
|
/** Read Claude's hook JSON from stdin within a short budget. */
|
|
@@ -5232,7 +5712,8 @@ async function readClaudeHookInput(stdin, timeoutMs = 2e3) {
|
|
|
5232
5712
|
if (typeof value?.session_id !== "string" || !SESSION_UUID.test(value.session_id)) return null;
|
|
5233
5713
|
return {
|
|
5234
5714
|
sessionId: value.session_id.toLowerCase(),
|
|
5235
|
-
cwd: typeof value.cwd === "string" && value.cwd ? value.cwd : null
|
|
5715
|
+
cwd: typeof value.cwd === "string" && value.cwd ? value.cwd : null,
|
|
5716
|
+
reason: typeof value.reason === "string" && /^[a-z_]{1,40}$/.test(value.reason) ? value.reason : null
|
|
5236
5717
|
};
|
|
5237
5718
|
} catch {
|
|
5238
5719
|
return null;
|
|
@@ -5267,4 +5748,4 @@ async function runDetached(params) {
|
|
|
5267
5748
|
}
|
|
5268
5749
|
}
|
|
5269
5750
|
//#endregion
|
|
5270
|
-
export {
|
|
5751
|
+
export { boundSessionProfiles as $, AgentDisconnectError as A, runContactRequest as B, verificationReplySubmitted as C, createNotificationContactPolicy as D, NetworkAdmissionPendingError as E, addressNoteTarget as F, receiverStatusCommand as G, agentInvitationHash as H, runAddressNotesRequest as I, savedReceiver as J, refusalAfterReplacement as K, ContactsApiError as L, clearRevokedAddresses as M, disconnectAgent as N, evaluateContactPolicy as O, AddressNotesApiError as P, recordHookMailCheck as Q, contactAddress as R, externalReceivingState as S, NETWORK_ADMISSION_PENDING_RETRY_MS as T, connectAgent as U, agentConnectionStatus as V, readAgentInvitation as W, releaseRegistrationsForProfile as X, MACHINE_RUNTIMES as Y, installClaudeWakeHook as Z, defaultAgentProfileName as _, upsertManagedBlock as _t, endSession as a, ephemeralCliReason as at, runAgentConnect as b, refreshOwnerMemberAddressPeriodically as bt, pendingSessionDisconnects as c, inspectClaudeHooks as ct, registerSession as d, samePath as dt, claudeConfigDir as et, retryPendingDisconnects as f, writeClaudeSettings as ft, connectWarnings as g, readManagedFile as gt, enrollmentResumes as h, jsonIndentation as ht, connectedProfilesForSession as i, ensureHookLauncher as it, claimOverRevokedSetup as j, canonicalContactSelector as k, psProcessInfo as l, readClaudeSettings as lt, enrollAgent as m, inspectManagedBlock as mt, OMP_MAIN as n, describeSessionHookChange as nt, headlessClaudeRun as o, findOnPath as ot, runDetached as p, MachineFileError as pt, saveConnectionName as q, agentConnectionState as r, describeSessionHookItem as rt, machine_session_exports as s, hasMachineSessionHooks as st, HOOK_END_GRACE_MS as t, currentCliLocation as tt, readClaudeHookInput as u, repairClaudeHooks as ut, invitationProfileName as v, writeManagedFile as vt, ContactPolicyReadRetryError as w, RECEIVER_MODES as x, withOwnerMemberAddress as xt, pollCheckCommand as y, readHookLauncherWarning as yt, contactAgentAddress as z };
|