primcli 1.74.1 → 3.0.0-preview.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/README.md +165 -72
- package/bin/claude-pending-mail.mjs +1 -1
- package/bin/run.js +14 -0
- package/dist/{api-command-fKP3NYa7.js → api-command-ctHBqTL5.js} +825 -95
- package/dist/{cli-config-C0Y_Ppol.js → cli-config-Ms_0vNjq.js} +66 -28
- package/dist/{client.gen-CaaMP86o.js → client.gen-6rZvmHfx.js} +7 -14
- package/dist/{json-output-Ce10AsSb.js → json-output-BzSBR-KX.js} +1 -2
- package/dist/{listen-background-cD6n8VYO.js → listen-background-C0F9gNDD.js} +2 -3
- package/dist/{machine-session-B6V5-grn.js → machine-session-Cp_eQ9t_.js} +164 -99
- package/dist/{notify-session-DJAb9SGm.js → notify-session-BN0JTPZ3.js} +341 -31
- package/dist/oclif/auto-signal-worker.js +5 -6
- package/dist/oclif/auto-signals.js +4 -5
- package/dist/oclif/global-org-flag.js +76 -0
- package/dist/oclif/help.js +127 -0
- package/dist/oclif/index.js +93239 -31954
- package/dist/oclif/listen-supervisor.js +1 -1
- package/dist/oclif/proxy-auto-detect.js +2 -3
- package/dist/oclif/receiver-heal.js +6 -6
- package/dist/oclif/root-signup-hint.js +3 -4
- package/dist/receiver.d.ts +95 -0
- package/dist/receiver.js +569 -0
- package/dist/{repeats-BL7ZC-_6-B_ESsWTH.js → repeats-Dj_o6c-X-Bf9G0xds.js} +4617 -1744
- package/dist/{scoped-chat-DAiOb622.js → scoped-chat-DZ42V3jq.js} +3 -5
- package/dist/{shared-mail-files-CZfm7E2k.js → shared-mail-files-BAQSGxxz.js} +2 -4
- package/dist/{shared-mail-watch-DyQB0MK0.js → shared-mail-watch-BAPMsFNv.js} +2 -3
- package/dist/{signal-command-Cte4mAL3.js → signal-command-CRUMUCNa.js} +19 -27
- package/dist/skills/primitive-connect/references/connect.md +3 -1
- package/dist/skills/primitive-connect/references/contacts.md +9 -4
- package/dist/skills/primitive-connect.json +4 -4
- package/man/primitive.1 +44 -3
- package/package.json +65 -15
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
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, b as loadCliCredentials, j as AGENT_PROFILE_ENV, z as parseConnectedAgentProfile } from "./cli-config-Ms_0vNjq.js";
|
|
2
|
+
import { $n as __exportAll, $t as listContacts, Cn as putContact, It as getEmail, Jt as inviteAgentConnection, Mt as getContact, Pn as sendEmail, Sn as putAgentContactPolicy, Xt as listAgentConnections, Zt as listAgentContacts, hn as listSentEmails, jn as searchEmails, jt as getAgentContactPolicy, rn as listDomains, vt as deleteAgentContact, xn as putAgentContact, yt as deleteContact } from "./repeats-Dj_o6c-X-Bf9G0xds.js";
|
|
3
|
+
import { Ft as refuseOrgSelectionForProfile, Pt as refreshStoredCliCredentials, St as detectAgentRuntime, _t as defaultConnectionName, ht as cliInvocation } from "./api-command-ctHBqTL5.js";
|
|
4
|
+
import { _ as acquireListenLock, b as listenLockHeld, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, o as mailObject, p as syncMailDirectory, r as mailAddress, s as mailString } from "./shared-mail-files-BAQSGxxz.js";
|
|
5
|
+
import { S as readBundledConnectSkill, a as NotificationRetryError, d as SESSION_UUID, f as connectNativeSession, m as NativeSessionDisconnectedError, p as defaultSessionSocket, t as notificationScope, y as installConnectSkill } from "./notify-session-BN0JTPZ3.js";
|
|
6
|
+
import { l as stopBackgroundListen, r as backgroundListenStatus } from "./listen-background-C0F9gNDD.js";
|
|
7
|
+
import { t as readSharedMailOwner } from "./shared-mail-watch-BAPMsFNv.js";
|
|
8
|
+
import { withoutOrgSelection } from "./oclif/global-org-flag.js";
|
|
9
|
+
import { o as scopedChatSenderTrust } from "./scoped-chat-DZ42V3jq.js";
|
|
9
10
|
import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
10
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
12
|
import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -14,7 +15,7 @@ import { execFile, execFileSync, spawn } from "node:child_process";
|
|
|
14
15
|
import { homedir } from "node:os";
|
|
15
16
|
import { promisify } from "node:util";
|
|
16
17
|
import { createConnection } from "node:net";
|
|
17
|
-
|
|
18
|
+
// src/oclif/owner-member-address.ts
|
|
18
19
|
const REFRESH_TIMEOUT_MS = 5e3;
|
|
19
20
|
const MAX_RESPONSE_BYTES$1 = 65536;
|
|
20
21
|
/** Reads at most MAX_RESPONSE_BYTES, stopping as soon as the body exceeds it. */
|
|
@@ -145,8 +146,7 @@ async function withOwnerMemberAddress(result, params) {
|
|
|
145
146
|
ownerReportGuidance: ownerReportGuidance(identity)
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
|
-
|
|
149
|
-
//#region src/oclif/claude-session-receivers.ts
|
|
149
|
+
// src/oclif/claude-session-receivers.ts
|
|
150
150
|
const FILE = /^([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})\.json$/;
|
|
151
151
|
const LOCK_WAIT$2 = new Int32Array(new SharedArrayBuffer(4));
|
|
152
152
|
function sessionReceiversDirectory(configDir) {
|
|
@@ -285,8 +285,7 @@ function pruneSessionReceivers(configDir) {
|
|
|
285
285
|
}
|
|
286
286
|
return dropped;
|
|
287
287
|
}
|
|
288
|
-
|
|
289
|
-
//#region src/oclif/hook-launcher.ts
|
|
288
|
+
// src/oclif/hook-launcher.ts
|
|
290
289
|
/**
|
|
291
290
|
* Claude Code hooks run Primitive through a small POSIX launcher instead of a
|
|
292
291
|
* pinned Node binary. A version manager (nvm, n, asdf) or a package-runner
|
|
@@ -468,8 +467,7 @@ function readHookLauncherWarning(configDir) {
|
|
|
468
467
|
return null;
|
|
469
468
|
}
|
|
470
469
|
}
|
|
471
|
-
|
|
472
|
-
//#region src/oclif/machine-files.ts
|
|
470
|
+
// src/oclif/machine-files.ts
|
|
473
471
|
/** Hand-edited files larger than this are reported, never rewritten. */
|
|
474
472
|
const MAX_MANAGED_FILE_BYTES = 1048576;
|
|
475
473
|
/** Backups kept per file; older Primitive backups of the same file are pruned. */
|
|
@@ -695,8 +693,7 @@ function upsertManagedBlock(text, body) {
|
|
|
695
693
|
function jsonIndentation(text) {
|
|
696
694
|
return /^[{[][^\n]*\n([ \t]+)\S/.exec(text)?.[1] ?? 2;
|
|
697
695
|
}
|
|
698
|
-
|
|
699
|
-
//#region src/oclif/claude-machine-hooks.ts
|
|
696
|
+
// src/oclif/claude-machine-hooks.ts
|
|
700
697
|
function record$2(value) {
|
|
701
698
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
702
699
|
}
|
|
@@ -1556,8 +1553,7 @@ function repairClaudeHooks(settings, context, checks, options = {}) {
|
|
|
1556
1553
|
receivers
|
|
1557
1554
|
};
|
|
1558
1555
|
}
|
|
1559
|
-
|
|
1560
|
-
//#region src/oclif/claude-wake-install.ts
|
|
1556
|
+
// src/oclif/claude-wake-install.ts
|
|
1561
1557
|
const RECEIVE_EVENTS = [
|
|
1562
1558
|
"Stop",
|
|
1563
1559
|
"SessionStart",
|
|
@@ -1846,8 +1842,7 @@ function uninstallClaudeWakeHook(options) {
|
|
|
1846
1842
|
return false;
|
|
1847
1843
|
}
|
|
1848
1844
|
}
|
|
1849
|
-
|
|
1850
|
-
//#region src/oclif/session-records.ts
|
|
1845
|
+
// src/oclif/session-records.ts
|
|
1851
1846
|
const MACHINE_RUNTIMES = [
|
|
1852
1847
|
"claude",
|
|
1853
1848
|
"codex",
|
|
@@ -2016,8 +2011,7 @@ async function releaseRegistrationsForProfile(params) {
|
|
|
2016
2011
|
} catch {}
|
|
2017
2012
|
}
|
|
2018
2013
|
}
|
|
2019
|
-
|
|
2020
|
-
//#region src/oclif/agent-connect.ts
|
|
2014
|
+
// src/oclif/agent-connect.ts
|
|
2021
2015
|
const MAX_INVITATION_BYTES = 4096;
|
|
2022
2016
|
const MAX_RESPONSE_BYTES = 32768;
|
|
2023
2017
|
const UNCERTAIN_CLAIM = "Setup did not complete safely. The invitation may have been consumed. Request a fresh invitation from the owner; do not retry this invitation.";
|
|
@@ -2104,6 +2098,22 @@ function removeEmptyDirectory(path) {
|
|
|
2104
2098
|
rmdirSync(path);
|
|
2105
2099
|
} catch {}
|
|
2106
2100
|
}
|
|
2101
|
+
const CONNECTION_ID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
|
|
2102
|
+
/** The whole of one setup challenge marker; its first group is the connection id. */
|
|
2103
|
+
const CHALLENGE_MARKER = /^primitive-connection:([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}):[1-9]\d*$/;
|
|
2104
|
+
/** The challenge hints a claim response carries. Unusable values are dropped. */
|
|
2105
|
+
function claimedChallenge(value) {
|
|
2106
|
+
const data = value?.data;
|
|
2107
|
+
const connection = data?.connection;
|
|
2108
|
+
const connectionId = typeof connection?.id === "string" && CONNECTION_ID.test(connection.id) ? connection.id : void 0;
|
|
2109
|
+
const claimedAt = typeof connection?.claimed_at === "string" ? Date.parse(connection.claimed_at) : NaN;
|
|
2110
|
+
const marker = typeof data?.challenge_marker === "string" ? CHALLENGE_MARKER.exec(data.challenge_marker) : null;
|
|
2111
|
+
return {
|
|
2112
|
+
...marker && (!connectionId || marker[1] === connectionId) ? { marker: marker[0] } : {},
|
|
2113
|
+
...connectionId ? { connectionId } : {},
|
|
2114
|
+
...Number.isFinite(claimedAt) ? { claimedAt: new Date(claimedAt).toISOString() } : {}
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2107
2117
|
const CONNECTION_NAME_FILE = "connection-name.json";
|
|
2108
2118
|
/**
|
|
2109
2119
|
* The display name this profile's claim reported, saved beside the profile
|
|
@@ -2409,6 +2419,7 @@ async function connectAgent(params) {
|
|
|
2409
2419
|
const body = await readClaimResponse(response);
|
|
2410
2420
|
const profile = profileFromClaim(body, invitation, invitationHash, params.now ?? Date.now);
|
|
2411
2421
|
const name = claimedConnectionName(body);
|
|
2422
|
+
const challenge = claimedChallenge(body);
|
|
2412
2423
|
saveConnectedAgentProfile(params.configDir, profileName, profile);
|
|
2413
2424
|
try {
|
|
2414
2425
|
await releaseRegistrationsForClaim({
|
|
@@ -2430,7 +2441,8 @@ async function connectAgent(params) {
|
|
|
2430
2441
|
return {
|
|
2431
2442
|
status: "claimed",
|
|
2432
2443
|
identity: connectedAgentIdentity(profileName, profile),
|
|
2433
|
-
...name === void 0 ? {} : { name }
|
|
2444
|
+
...name === void 0 ? {} : { name },
|
|
2445
|
+
...Object.keys(challenge).length ? { challenge } : {}
|
|
2434
2446
|
};
|
|
2435
2447
|
} catch (error) {
|
|
2436
2448
|
if (error instanceof AgentInvitationRejectedError) throw error;
|
|
@@ -2440,8 +2452,7 @@ async function connectAgent(params) {
|
|
|
2440
2452
|
release();
|
|
2441
2453
|
}
|
|
2442
2454
|
}
|
|
2443
|
-
|
|
2444
|
-
//#region src/oclif/contacts.ts
|
|
2455
|
+
// src/oclif/contacts.ts
|
|
2445
2456
|
var ContactsApiError = class extends Error {
|
|
2446
2457
|
payload;
|
|
2447
2458
|
directoryAvailable = false;
|
|
@@ -2638,8 +2649,7 @@ async function runContactRequest(client, request) {
|
|
|
2638
2649
|
throw error;
|
|
2639
2650
|
}
|
|
2640
2651
|
}
|
|
2641
|
-
|
|
2642
|
-
//#region src/oclif/address-notes.ts
|
|
2652
|
+
// src/oclif/address-notes.ts
|
|
2643
2653
|
var AddressNotesApiError = class extends Error {
|
|
2644
2654
|
payload;
|
|
2645
2655
|
status;
|
|
@@ -2772,8 +2782,7 @@ async function runAddressNotesRequest(client, request) {
|
|
|
2772
2782
|
if (!data || typeof data !== "object" || Array.isArray(data) || typeof data.deleted !== "boolean") throw new Error("The address notes API returned an invalid deletion response.");
|
|
2773
2783
|
return data;
|
|
2774
2784
|
}
|
|
2775
|
-
|
|
2776
|
-
//#region src/oclif/agent-resume-note.ts
|
|
2785
|
+
// src/oclif/agent-resume-note.ts
|
|
2777
2786
|
/**
|
|
2778
2787
|
* The address note holding the command that resumes the coding session an
|
|
2779
2788
|
* agent is bound to, so an app can offer to reopen that exact session.
|
|
@@ -2886,8 +2895,7 @@ async function clearProfileResumeNote(options) {
|
|
|
2886
2895
|
return "failed";
|
|
2887
2896
|
}
|
|
2888
2897
|
}
|
|
2889
|
-
|
|
2890
|
-
//#region src/oclif/agent-disconnect.ts
|
|
2898
|
+
// src/oclif/agent-disconnect.ts
|
|
2891
2899
|
var AgentDisconnectError = class extends Error {};
|
|
2892
2900
|
/** The profile no longer holds the credential the caller expected to revoke. */
|
|
2893
2901
|
var AgentCredentialChangedError = class extends AgentDisconnectError {};
|
|
@@ -3002,6 +3010,7 @@ async function revoke(profile, fetcher) {
|
|
|
3002
3010
|
/** Revoke one selected connected credential; never alter OAuth login or mail evidence. */
|
|
3003
3011
|
async function disconnectAgent(params) {
|
|
3004
3012
|
const profileName = agentProfileName(params.profileName);
|
|
3013
|
+
refuseOrgSelectionForProfile(profileName);
|
|
3005
3014
|
if (!loadConnectedAgentProfile(params.configDir, profileName)) throw new AgentDisconnectError("This agent profile is not configured. Nothing was changed.");
|
|
3006
3015
|
const directory = agentProfileDirectory(params.configDir, profileName);
|
|
3007
3016
|
let releaseSetup;
|
|
@@ -3185,8 +3194,7 @@ async function clearRevokedAddresses(params) {
|
|
|
3185
3194
|
cleared
|
|
3186
3195
|
};
|
|
3187
3196
|
}
|
|
3188
|
-
|
|
3189
|
-
//#region src/oclif/agent-setup-read.ts
|
|
3197
|
+
// src/oclif/agent-setup-read.ts
|
|
3190
3198
|
function retryDelay(response, attempt, now) {
|
|
3191
3199
|
const raw = response?.headers.get("retry-after")?.trim();
|
|
3192
3200
|
if (raw && /^\d+(?:\.\d+)?$/.test(raw)) {
|
|
@@ -3223,8 +3231,7 @@ async function readSetupApi(budget, stage, read) {
|
|
|
3223
3231
|
budget.retries++;
|
|
3224
3232
|
}
|
|
3225
3233
|
}
|
|
3226
|
-
|
|
3227
|
-
//#region src/oclif/contact-rule-matcher.ts
|
|
3234
|
+
// src/oclif/contact-rule-matcher.ts
|
|
3228
3235
|
const localPart = /^[a-z0-9!#$%&'+/=^_`{|}~-]+(?:\.[a-z0-9!#$%&'+/=^_`{|}~-]+)*$/;
|
|
3229
3236
|
const patternPart = /^[a-z0-9.!#$%&'+/=^_`{|}~*-]+$/;
|
|
3230
3237
|
const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -3301,8 +3308,7 @@ function matchesContactSelector(input, sender) {
|
|
|
3301
3308
|
const [localPattern, hostPattern] = splitAddress(selector.value) ?? [];
|
|
3302
3309
|
return Boolean(localPattern && hostPattern && matchesDomain(hostPattern, host) && matchesLocal(localPattern, local));
|
|
3303
3310
|
}
|
|
3304
|
-
|
|
3305
|
-
//#region src/oclif/contact-policy.ts
|
|
3311
|
+
// src/oclif/contact-policy.ts
|
|
3306
3312
|
function record(value) {
|
|
3307
3313
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid contact policy.");
|
|
3308
3314
|
return value;
|
|
@@ -3444,6 +3450,11 @@ var ContactPolicyReadRetryError = class extends NotificationRetryError {
|
|
|
3444
3450
|
super("Contact notification policy is temporarily unavailable. Mail remains pending while permissions are refreshed.");
|
|
3445
3451
|
}
|
|
3446
3452
|
};
|
|
3453
|
+
/** A network decision is trusted only when every field agrees with the others. */
|
|
3454
|
+
function consistentNetworkAdmission(decision) {
|
|
3455
|
+
const relation = decision.sender_relation;
|
|
3456
|
+
return typeof decision.allowed === "boolean" && typeof decision.pending === "boolean" && typeof decision.member_policy_required === "boolean" && (relation === void 0 || decision.member_policy_required && (relation === "owner" || relation === "member")) && !(decision.member_policy_required && decision.allowed && !relation) && !(decision.pending && (decision.allowed || decision.allowed_since !== null)) && (decision.allowed ? typeof decision.allowed_since === "string" : decision.allowed_since === null);
|
|
3457
|
+
}
|
|
3447
3458
|
const unavailable = () => new ListenStateError("Contact notification preferences are unavailable or invalid. No contact notification was submitted; restart after access is restored.");
|
|
3448
3459
|
const changed = () => new NotificationRetryError("Contact notification permission changed or expired before dispatch.");
|
|
3449
3460
|
/** Read one connected address's policy. Partial pages never authorize delivery. */
|
|
@@ -3557,10 +3568,8 @@ function createNotificationContactPolicy(options) {
|
|
|
3557
3568
|
if (!emailId) throw unavailable();
|
|
3558
3569
|
const network = await networkReader(emailId, sender, signal);
|
|
3559
3570
|
signal.throwIfAborted();
|
|
3560
|
-
if (
|
|
3561
|
-
if (network.sender_relation !== void 0 && (!network.member_policy_required || !["owner", "member"].includes(network.sender_relation))) throw unavailable();
|
|
3571
|
+
if (!consistentNetworkAdmission(network)) throw unavailable();
|
|
3562
3572
|
if (network.member_policy_required) {
|
|
3563
|
-
if (network.allowed && !network.sender_relation) throw unavailable();
|
|
3564
3573
|
if (network.pending) throw new NetworkAdmissionPendingError();
|
|
3565
3574
|
if (!network.allowed) return null;
|
|
3566
3575
|
if (decision.kind === "silent" && decision.source !== "default") return null;
|
|
@@ -3638,8 +3647,7 @@ function createNotificationContactPolicy(options) {
|
|
|
3638
3647
|
}
|
|
3639
3648
|
};
|
|
3640
3649
|
}
|
|
3641
|
-
|
|
3642
|
-
//#region src/oclif/registration-log.ts
|
|
3650
|
+
// src/oclif/registration-log.ts
|
|
3643
3651
|
/**
|
|
3644
3652
|
* A short private record of this profile's registration attempts, so a
|
|
3645
3653
|
* stuck registration can be explained by `agent connect --status` and
|
|
@@ -3772,8 +3780,7 @@ function failedSessionRegistration(configDir, session) {
|
|
|
3772
3780
|
const summary = registrationSummary(configDir, profile);
|
|
3773
3781
|
return summary?.lastFailure ? summary : null;
|
|
3774
3782
|
}
|
|
3775
|
-
|
|
3776
|
-
//#region src/oclif/agent-setup.ts
|
|
3783
|
+
// src/oclif/agent-setup.ts
|
|
3777
3784
|
const SUBJECT = "Connect your agent to Primitive";
|
|
3778
3785
|
const MARKER = /\bprimitive-connection:([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}):([1-9]\d*)\b/g;
|
|
3779
3786
|
const fail = (message) => new AgentConnectionSetupError(message);
|
|
@@ -3869,6 +3876,32 @@ async function checkVerification(context, timeoutMs) {
|
|
|
3869
3876
|
return { state: "unavailable" };
|
|
3870
3877
|
}
|
|
3871
3878
|
}
|
|
3879
|
+
async function readClaim(context) {
|
|
3880
|
+
try {
|
|
3881
|
+
const response = await context.fetch(`${context.profile.api_base_url}/agent-connections/me`, {
|
|
3882
|
+
method: "GET",
|
|
3883
|
+
redirect: "error",
|
|
3884
|
+
signal: AbortSignal.timeout(5e3),
|
|
3885
|
+
headers: {
|
|
3886
|
+
authorization: `Bearer ${context.profile.api_key}`,
|
|
3887
|
+
accept: "application/json"
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
3890
|
+
if (response.status !== 200) {
|
|
3891
|
+
await response.body?.cancel();
|
|
3892
|
+
return {};
|
|
3893
|
+
}
|
|
3894
|
+
const text = await response.text();
|
|
3895
|
+
if (text.length > 65536) return {};
|
|
3896
|
+
const body = JSON.parse(text);
|
|
3897
|
+
const address = body?.data?.connection?.address;
|
|
3898
|
+
if (body?.success !== true || typeof address !== "string" || address.toLowerCase() !== context.identity.agentAddress.toLowerCase()) return {};
|
|
3899
|
+
const { marker: _marker, ...hint } = claimedChallenge(body);
|
|
3900
|
+
return hint;
|
|
3901
|
+
} catch {
|
|
3902
|
+
return {};
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3872
3905
|
/**
|
|
3873
3906
|
* Poll until the server reports the connection verified, the budget runs out,
|
|
3874
3907
|
* or the status read is unavailable. Elapsed time is the larger of the clock
|
|
@@ -3892,8 +3925,41 @@ async function awaitVerification(context, dependencies, timeoutMs) {
|
|
|
3892
3925
|
waited += wait;
|
|
3893
3926
|
}
|
|
3894
3927
|
}
|
|
3928
|
+
const OPTIONAL_STATE_KEYS = [
|
|
3929
|
+
"receiverMode",
|
|
3930
|
+
"challengeMarker",
|
|
3931
|
+
"challengeConnectionId"
|
|
3932
|
+
];
|
|
3933
|
+
/**
|
|
3934
|
+
* How far before the claim's server time the search starts. With the exact
|
|
3935
|
+
* marker the window only bounds the search, so it allows generous skew
|
|
3936
|
+
* between the database and the mail pipeline clocks. Without it the window
|
|
3937
|
+
* is what excludes an earlier challenge, so it starts at the claim itself:
|
|
3938
|
+
* the server sends the challenge only after the claim commits.
|
|
3939
|
+
*/
|
|
3940
|
+
const MARKER_WINDOW_MS = 6e4;
|
|
3941
|
+
/**
|
|
3942
|
+
* Points this setup at the challenge its claim started. Leaves the state
|
|
3943
|
+
* unchanged when the claim said too little, which keeps the earlier rule.
|
|
3944
|
+
*/
|
|
3945
|
+
function narrowToClaim(state, claim) {
|
|
3946
|
+
const claimedAt = claim?.claimedAt ? Date.parse(claim.claimedAt) : NaN;
|
|
3947
|
+
if (claim?.marker) {
|
|
3948
|
+
state.challengeMarker = claim.marker;
|
|
3949
|
+
if (Number.isFinite(claimedAt)) state.since = (/* @__PURE__ */ new Date(claimedAt - MARKER_WINDOW_MS)).toISOString();
|
|
3950
|
+
} else if (claim?.connectionId && Number.isFinite(claimedAt)) {
|
|
3951
|
+
state.challengeConnectionId = claim.connectionId;
|
|
3952
|
+
state.since = new Date(claimedAt).toISOString();
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
function expectedChallenge(state) {
|
|
3956
|
+
return {
|
|
3957
|
+
...state.challengeMarker ? { marker: state.challengeMarker } : {},
|
|
3958
|
+
...state.challengeConnectionId ? { connectionId: state.challengeConnectionId } : {}
|
|
3959
|
+
};
|
|
3960
|
+
}
|
|
3895
3961
|
function parseState(value) {
|
|
3896
|
-
const
|
|
3962
|
+
const row = mailObject(value, [...[
|
|
3897
3963
|
"version",
|
|
3898
3964
|
"session",
|
|
3899
3965
|
"invitationHash",
|
|
@@ -3902,9 +3968,8 @@ function parseState(value) {
|
|
|
3902
3968
|
"challenge",
|
|
3903
3969
|
"phase",
|
|
3904
3970
|
"receipt"
|
|
3905
|
-
];
|
|
3906
|
-
|
|
3907
|
-
if (row.version !== 1 || row.receiverMode !== void 0 && !RECEIVER_MODES.includes(row.receiverMode) || typeof row.contactRequests !== "boolean" || ![
|
|
3971
|
+
], ...OPTIONAL_STATE_KEYS.filter((key) => value && typeof value === "object" && Object.hasOwn(value, key))]);
|
|
3972
|
+
if (row.version !== 1 || row.challengeMarker !== void 0 && (typeof row.challengeMarker !== "string" || !CHALLENGE_MARKER.test(row.challengeMarker)) || row.challengeConnectionId !== void 0 && (typeof row.challengeConnectionId !== "string" || !CONNECTION_ID.test(row.challengeConnectionId)) || row.receiverMode !== void 0 && !RECEIVER_MODES.includes(row.receiverMode) || typeof row.contactRequests !== "boolean" || ![
|
|
3908
3973
|
"waiting",
|
|
3909
3974
|
"sending",
|
|
3910
3975
|
"sent"
|
|
@@ -3930,23 +3995,28 @@ function parseState(value) {
|
|
|
3930
3995
|
messageId: mailString(challenge.messageId, 1e3),
|
|
3931
3996
|
marker: mailString(challenge.marker, 100)
|
|
3932
3997
|
} : null,
|
|
3933
|
-
receipt
|
|
3998
|
+
receipt,
|
|
3999
|
+
...typeof row.challengeMarker === "string" ? { challengeMarker: row.challengeMarker } : {},
|
|
4000
|
+
...typeof row.challengeConnectionId === "string" ? { challengeConnectionId: row.challengeConnectionId } : {}
|
|
3934
4001
|
};
|
|
3935
4002
|
if (state.phase !== "waiting" && !state.challenge || state.phase === "sent" && !state.receipt) throw fail("Saved setup progress is inconsistent. Preserve its private files.");
|
|
3936
4003
|
return state;
|
|
3937
4004
|
}
|
|
3938
4005
|
/** Match only authenticated owner mail. Subject/search results alone grant no authority. */
|
|
3939
|
-
function setupChallenge(detail, identity, since) {
|
|
4006
|
+
function setupChallenge(detail, identity, since, expected = {}) {
|
|
3940
4007
|
if (detail.subject !== SUBJECT || detail.recipient.toLowerCase() !== identity.agentAddress || detail.to_email.toLowerCase() !== identity.agentAddress || !["accepted", "completed"].includes(detail.status) || !detail.message_id || /[\r\n]/.test(detail.message_id) || detail.parsed?.status !== "complete" || Date.parse(detail.received_at) < Date.parse(since) || !Number.isFinite(Date.parse(detail.received_at)) || !scopedChatSenderTrust(detail, identity.ownerAddress).trusted) return null;
|
|
3941
4008
|
const markers = [...new Set((detail.body_text ?? "").match(MARKER) ?? [])];
|
|
3942
4009
|
if (markers.length !== 1) return null;
|
|
4010
|
+
const marker = markers[0] ?? "";
|
|
4011
|
+
if (expected.marker && marker !== expected.marker) return null;
|
|
4012
|
+
if (expected.connectionId && !marker.startsWith(`primitive-connection:${expected.connectionId}:`)) return null;
|
|
3943
4013
|
return {
|
|
3944
4014
|
id: mailId(detail.id),
|
|
3945
4015
|
messageId: detail.message_id,
|
|
3946
4016
|
marker: markers[0] ?? ""
|
|
3947
4017
|
};
|
|
3948
4018
|
}
|
|
3949
|
-
async function findChallenge(context, since) {
|
|
4019
|
+
async function findChallenge(context, since, expected = {}) {
|
|
3950
4020
|
const page = await readSetupApi(context.readBudget, "challenge search", (signal) => searchEmails({
|
|
3951
4021
|
client: context.client.client,
|
|
3952
4022
|
query: {
|
|
@@ -3971,8 +4041,14 @@ async function findChallenge(context, since) {
|
|
|
3971
4041
|
responseStyle: "fields"
|
|
3972
4042
|
}));
|
|
3973
4043
|
if (response.error || !response.data?.data) throw fail("The setup challenge could not be read. Resume this profile; do not claim again.");
|
|
3974
|
-
const match = setupChallenge(response.data.data, context.identity, since);
|
|
3975
|
-
if (match) matches.push(match);
|
|
4044
|
+
const match = setupChallenge(response.data.data, context.identity, since, expected);
|
|
4045
|
+
if (match && !matches.some((seen) => seen.messageId === match.messageId && seen.marker === match.marker)) matches.push(match);
|
|
4046
|
+
}
|
|
4047
|
+
if (expected.connectionId && !expected.marker && matches.length > 1) {
|
|
4048
|
+
const generation = (row) => Number(row.marker.slice(row.marker.lastIndexOf(":") + 1));
|
|
4049
|
+
const top = Math.max(...matches.map(generation));
|
|
4050
|
+
const current = matches.filter((row) => generation(row) === top);
|
|
4051
|
+
if (current.length === 1) return current[0] ?? null;
|
|
3976
4052
|
}
|
|
3977
4053
|
if (matches.length > 1) throw fail("Several recent setup challenges match. No challenge was guessed or answered. Ask the owner to confirm the current challenge.");
|
|
3978
4054
|
return matches[0] ?? null;
|
|
@@ -4061,6 +4137,7 @@ async function nativeSessionPreflight(session) {
|
|
|
4061
4137
|
function defaults$1() {
|
|
4062
4138
|
return {
|
|
4063
4139
|
checkVerification,
|
|
4140
|
+
readClaim,
|
|
4064
4141
|
now: Date.now,
|
|
4065
4142
|
sleep: async (ms) => {
|
|
4066
4143
|
await setTimeout$1(ms);
|
|
@@ -4120,11 +4197,11 @@ function defaults$1() {
|
|
|
4120
4197
|
"--contacts",
|
|
4121
4198
|
...contactRequests ? ["--contact-requests"] : []
|
|
4122
4199
|
], {
|
|
4123
|
-
env: {
|
|
4200
|
+
env: withoutOrgSelection({
|
|
4124
4201
|
...process.env,
|
|
4125
4202
|
[AGENT_PROFILE_ENV]: profile,
|
|
4126
4203
|
PRIMITIVE_CONFIG_DIR: configDir
|
|
4127
|
-
},
|
|
4204
|
+
}),
|
|
4128
4205
|
timeout: 35e3,
|
|
4129
4206
|
maxBuffer: 65536
|
|
4130
4207
|
});
|
|
@@ -4240,6 +4317,8 @@ async function setupAgent(params) {
|
|
|
4240
4317
|
};
|
|
4241
4318
|
let discardStub = false;
|
|
4242
4319
|
let claimedName;
|
|
4320
|
+
let claimedChallengeHint;
|
|
4321
|
+
let claimedThisRun = false;
|
|
4243
4322
|
const takeEnrollment = () => {
|
|
4244
4323
|
try {
|
|
4245
4324
|
releaseEnrollment = acquireListenLock(join(directory, "enrollment"), ENROLLMENT_LOCK_SCOPE);
|
|
@@ -4298,7 +4377,7 @@ async function setupAgent(params) {
|
|
|
4298
4377
|
writeMailJson(path, state);
|
|
4299
4378
|
}
|
|
4300
4379
|
try {
|
|
4301
|
-
|
|
4380
|
+
const claim = await connectAgent({
|
|
4302
4381
|
configDir: params.configDir,
|
|
4303
4382
|
profileName,
|
|
4304
4383
|
invitation: params.invitation ?? "",
|
|
@@ -4306,7 +4385,10 @@ async function setupAgent(params) {
|
|
|
4306
4385
|
presence: true,
|
|
4307
4386
|
claimLockHeld: releaseClaim !== void 0,
|
|
4308
4387
|
claimLockWaitMs: params.claimLockWaitMs ?? CLAIM_LOCK_WAIT_MS
|
|
4309
|
-
})
|
|
4388
|
+
});
|
|
4389
|
+
claimedName = claim.name;
|
|
4390
|
+
claimedChallengeHint = claim.challenge;
|
|
4391
|
+
claimedThisRun = claim.status === "claimed";
|
|
4310
4392
|
if (retireEnrollment) moveAside(join(directory, "enrollment", "state.json"), new Date(dependencies.now()));
|
|
4311
4393
|
if (replacedUnfinished?.address) appendRegistrationLog(params.configDir, profileName, {
|
|
4312
4394
|
event: "replaced_unfinished",
|
|
@@ -4334,6 +4416,12 @@ async function setupAgent(params) {
|
|
|
4334
4416
|
releaseClaim = void 0;
|
|
4335
4417
|
}
|
|
4336
4418
|
}
|
|
4419
|
+
if (state && claimedChallengeHint) {
|
|
4420
|
+
narrowToClaim(state, claimedChallengeHint);
|
|
4421
|
+
try {
|
|
4422
|
+
writeMailJson(join(directory, "setup.json"), state);
|
|
4423
|
+
} catch {}
|
|
4424
|
+
}
|
|
4337
4425
|
const profile = loadConnectedAgentProfile(params.configDir, profileName);
|
|
4338
4426
|
if (!profile) throw fail(unfinishedSetupStep(pendingEnrollmentAddress(directory)));
|
|
4339
4427
|
if (!state || profile.invitation_hash !== state.invitationHash) throw fail("The claim has no matching saved credential. Preserve private state and request a fresh invitation; do not replay this one.");
|
|
@@ -4394,15 +4482,18 @@ async function setupAgent(params) {
|
|
|
4394
4482
|
state.phase = "sent";
|
|
4395
4483
|
writeMailJson(path, state);
|
|
4396
4484
|
}
|
|
4485
|
+
if (state.phase === "waiting" && !claimedThisRun && !state.challengeMarker && !state.challengeConnectionId) narrowToClaim(state, await dependencies.readClaim(context));
|
|
4397
4486
|
if (state.phase === "waiting") {
|
|
4398
4487
|
const deadline = dependencies.now() + timeoutMs;
|
|
4399
4488
|
do {
|
|
4400
|
-
state.challenge = await dependencies.findChallenge(context, state.since);
|
|
4489
|
+
state.challenge = await dependencies.findChallenge(context, state.since, expectedChallenge(state));
|
|
4401
4490
|
if (state.challenge || dependencies.now() >= deadline) break;
|
|
4402
4491
|
await dependencies.sleep(Math.min(1500, deadline - dependencies.now()));
|
|
4403
4492
|
} while (dependencies.now() <= deadline);
|
|
4404
4493
|
if (!state.challenge) return result("not_started");
|
|
4405
4494
|
state.phase = "sending";
|
|
4495
|
+
delete state.challengeMarker;
|
|
4496
|
+
delete state.challengeConnectionId;
|
|
4406
4497
|
writeMailJson(path, state);
|
|
4407
4498
|
try {
|
|
4408
4499
|
state.receipt = parseReceipt(await dependencies.sendVerification({
|
|
@@ -4432,8 +4523,7 @@ async function setupAgent(params) {
|
|
|
4432
4523
|
if (discardStub) removeEmptyDirectory(directory);
|
|
4433
4524
|
}
|
|
4434
4525
|
}
|
|
4435
|
-
|
|
4436
|
-
//#region src/oclif/agent-connect-flow.ts
|
|
4526
|
+
// src/oclif/agent-connect-flow.ts
|
|
4437
4527
|
/** What this CLI version can do in one `agent connect --session` call. */
|
|
4438
4528
|
const AGENT_CONNECT_CAPABILITIES = [
|
|
4439
4529
|
"invitation_claim_from_stdin",
|
|
@@ -4885,8 +4975,7 @@ async function runAgentConnect(options) {
|
|
|
4885
4975
|
externalHook
|
|
4886
4976
|
};
|
|
4887
4977
|
}
|
|
4888
|
-
|
|
4889
|
-
//#region src/oclif/agent-enroll.ts
|
|
4978
|
+
// src/oclif/agent-enroll.ts
|
|
4890
4979
|
const RESPONSE_LIMIT = 32768;
|
|
4891
4980
|
const CONNECTION_LIST_LIMIT = 131072;
|
|
4892
4981
|
const CONFIRMATION_BUDGET_MS = 12e4;
|
|
@@ -4974,11 +5063,18 @@ function assertNativeSessionIdentity(session, env) {
|
|
|
4974
5063
|
if (env.CODEX_THREAD_ID && env.CODEX_THREAD_ID !== session) throw refuse("The requested session differs from this Codex thread. No address was created.");
|
|
4975
5064
|
if (!env.CODEX_THREAD_ID && env.CODEX_SESSION_ID && env.CODEX_SESSION_ID !== session) throw refuse("The requested session differs from this Codex session. No address was created.");
|
|
4976
5065
|
}
|
|
5066
|
+
/**
|
|
5067
|
+
* Preflight for enrollment: the managed domains an address could land on.
|
|
5068
|
+
* Null when the member's grant may not list domains; creating a connection
|
|
5069
|
+
* needs only agent_connections:manage, and the server picks the address and
|
|
5070
|
+
* refuses with connection_domain_unavailable when none is usable.
|
|
5071
|
+
*/
|
|
4977
5072
|
async function managedDomains(client, apiBaseUrl) {
|
|
4978
5073
|
const response = await listDomains({
|
|
4979
5074
|
client: client.client,
|
|
4980
5075
|
responseStyle: "fields"
|
|
4981
5076
|
});
|
|
5077
|
+
if (response.response?.status === 403) return null;
|
|
4982
5078
|
const rows = response.data?.data;
|
|
4983
5079
|
if (response.error || !Array.isArray(rows)) throw refuse("Could not list the owner's managed domains. No address was created.");
|
|
4984
5080
|
const suffix = apiBaseUrl === "https://api.primitive-staging-1.com/v1" ? ".primitive-staging.email" : ".primitive.email";
|
|
@@ -5495,11 +5591,12 @@ async function enrollAgent(params) {
|
|
|
5495
5591
|
if (state?.continueAttempted) throw refuse(stuckEnrollmentStep(resolvedEnrollment(state).address));
|
|
5496
5592
|
if (!state) {
|
|
5497
5593
|
if (params.continueSetup) throw refuse("There is no saved enrollment to continue. Start enrollment normally first.");
|
|
5498
|
-
|
|
5594
|
+
const domains = await managedDomains(new PrimitiveApiClient({
|
|
5499
5595
|
apiKey: credentials.access_token,
|
|
5500
5596
|
apiBaseUrl,
|
|
5501
5597
|
fetch: params.fetch
|
|
5502
|
-
}), apiBaseUrl)
|
|
5598
|
+
}), apiBaseUrl);
|
|
5599
|
+
if (domains && !domains.length) throw refuse("No managed domain is available for enrollment.");
|
|
5503
5600
|
state = {
|
|
5504
5601
|
version: 2,
|
|
5505
5602
|
session: params.session,
|
|
@@ -5634,32 +5731,8 @@ async function enrollAgent(params) {
|
|
|
5634
5731
|
release();
|
|
5635
5732
|
}
|
|
5636
5733
|
}
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
var machine_session_exports = /* @__PURE__ */ __exportAll({
|
|
5640
|
-
HOOK_END_GRACE_MS: () => HOOK_END_GRACE_MS,
|
|
5641
|
-
OMP_MAIN: () => OMP_MAIN,
|
|
5642
|
-
RECONNECT_HINT: () => RECONNECT_HINT,
|
|
5643
|
-
STALE_SESSION_MS: () => STALE_SESSION_MS,
|
|
5644
|
-
agentConnectionState: () => agentConnectionState,
|
|
5645
|
-
agentInfoForSession: () => agentInfoForSession,
|
|
5646
|
-
connectedProfilesForSession: () => connectedProfilesForSession,
|
|
5647
|
-
defaultSessionName: () => defaultSessionName,
|
|
5648
|
-
endSession: () => endSession,
|
|
5649
|
-
headlessClaudeRun: () => headlessClaudeRun,
|
|
5650
|
-
ompProcessSession: () => ompProcessSession,
|
|
5651
|
-
pendingSessionDisconnects: () => pendingSessionDisconnects,
|
|
5652
|
-
psProcessInfo: () => psProcessInfo,
|
|
5653
|
-
readClaudeHookInput: () => readClaudeHookInput,
|
|
5654
|
-
registerSession: () => registerSession,
|
|
5655
|
-
retryPendingDisconnects: () => retryPendingDisconnects,
|
|
5656
|
-
revokeStaleSessionAgents: () => revokeStaleSessionAgents,
|
|
5657
|
-
revokeWithMemberLogin: () => revokeWithMemberLogin,
|
|
5658
|
-
runDetached: () => runDetached,
|
|
5659
|
-
runtimeSessionId: () => runtimeSessionId,
|
|
5660
|
-
staleSessionAgents: () => staleSessionAgents,
|
|
5661
|
-
workspaceName: () => workspaceName
|
|
5662
|
-
});
|
|
5734
|
+
// src/oclif/machine-session.ts
|
|
5735
|
+
var machine_session_exports = /* @__PURE__ */ __exportAll({ connectedProfilesForSession: () => connectedProfilesForSession });
|
|
5663
5736
|
/** The repository (or directory) a session works in, for its default name. */
|
|
5664
5737
|
function workspaceName(cwd) {
|
|
5665
5738
|
let current = resolve(cwd);
|
|
@@ -6406,13 +6479,6 @@ async function registerSession(options) {
|
|
|
6406
6479
|
* within this window cancels the end.
|
|
6407
6480
|
*/
|
|
6408
6481
|
const HOOK_END_GRACE_MS = 3e5;
|
|
6409
|
-
/**
|
|
6410
|
-
* How long an ended session keeps the agent `session-register` created for
|
|
6411
|
-
* it. A session that has not started again for this long has its agent
|
|
6412
|
-
* disconnected by `machine doctor --fix` or the next registration on this
|
|
6413
|
-
* machine, so closed sessions do not keep live credentials forever.
|
|
6414
|
-
*/
|
|
6415
|
-
const STALE_SESSION_MS = 2592e6;
|
|
6416
6482
|
/** The end of this record is a hook's, still waiting. */
|
|
6417
6483
|
function deferredEnd(record) {
|
|
6418
6484
|
return Boolean(record?.endedAt) && record?.disconnect === "deferred";
|
|
@@ -6771,5 +6837,4 @@ async function runDetached(params) {
|
|
|
6771
6837
|
return "failed";
|
|
6772
6838
|
}
|
|
6773
6839
|
}
|
|
6774
|
-
//#endregion
|
|
6775
6840
|
export { saveConnectionName as $, ContactPolicyReadRetryError as A, AddressNotesApiError as B, RECEIVER_MODES as C, upsertManagedBlock as Ct, registrationSummary as D, refreshOwnerMemberAddressPeriodically as Dt, failedSessionRegistration as E, addSessionReceiver as Et, canonicalContactSelector as F, contactAgentAddress as G, runAddressNotesRequest as H, AgentDisconnectError as I, agentInvitationHash as J, runContactRequest as K, claimOverRevokedSetup as L, NetworkAdmissionPendingError as M, createNotificationContactPolicy as N, registrationSummaryLine as O, withOwnerMemberAddress as Ot, evaluateContactPolicy as P, refusalAfterReplacement as Q, clearRevokedAddresses as R, runAgentConnect as S, readManagedFile as St, verificationReplySubmitted as T, readHookLauncherWarning as Tt, ContactsApiError as U, addressNoteTarget as V, contactAddress as W, readAgentInvitation as X, connectAgent as Y, receiverStatusCommand as Z, enrollmentResumes as _, samePath as _t, endSession as a, boundSessionProfiles as at, invitationProfileName as b, inspectManagedBlock as bt, pendingSessionDisconnects as c, describeSessionHookChange as ct, registerSession as d, ephemeralCliReason as dt, savedReceiver as et, retryPendingDisconnects as f, findOnPath as ft, enrollAgent as g, repairClaudeHooks as gt, staleSessionAgents as h, readClaudeSettings as ht, connectedProfilesForSession as i, recordHookMailCheck as it, NETWORK_ADMISSION_PENDING_RETRY_MS as j, waitForRegistrations as k, psProcessInfo as l, describeSessionHookItem as lt, runDetached as m, inspectClaudeHooks as mt, OMP_MAIN as n, releaseRegistrationsForProfile as nt, headlessClaudeRun as o, claudeConfigDir as ot, revokeStaleSessionAgents as p, hasMachineSessionHooks as pt, agentConnectionStatus as q, agentConnectionState as r, installClaudeWakeHook as rt, machine_session_exports as s, currentCliLocation as st, HOOK_END_GRACE_MS as t, MACHINE_RUNTIMES as tt, readClaudeHookInput as u, ensureHookLauncher as ut, connectWarnings as v, writeClaudeSettings as vt, externalReceivingState as w, writeManagedFile as wt, pollCheckCommand as x, jsonIndentation as xt, defaultAgentProfileName as y, MachineFileError as yt, disconnectAgent as z };
|