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.
Files changed (31) hide show
  1. package/README.md +165 -72
  2. package/bin/claude-pending-mail.mjs +1 -1
  3. package/bin/run.js +14 -0
  4. package/dist/{api-command-fKP3NYa7.js → api-command-ctHBqTL5.js} +825 -95
  5. package/dist/{cli-config-C0Y_Ppol.js → cli-config-Ms_0vNjq.js} +66 -28
  6. package/dist/{client.gen-CaaMP86o.js → client.gen-6rZvmHfx.js} +7 -14
  7. package/dist/{json-output-Ce10AsSb.js → json-output-BzSBR-KX.js} +1 -2
  8. package/dist/{listen-background-cD6n8VYO.js → listen-background-C0F9gNDD.js} +2 -3
  9. package/dist/{machine-session-B6V5-grn.js → machine-session-Cp_eQ9t_.js} +164 -99
  10. package/dist/{notify-session-DJAb9SGm.js → notify-session-BN0JTPZ3.js} +341 -31
  11. package/dist/oclif/auto-signal-worker.js +5 -6
  12. package/dist/oclif/auto-signals.js +4 -5
  13. package/dist/oclif/global-org-flag.js +76 -0
  14. package/dist/oclif/help.js +127 -0
  15. package/dist/oclif/index.js +93239 -31954
  16. package/dist/oclif/listen-supervisor.js +1 -1
  17. package/dist/oclif/proxy-auto-detect.js +2 -3
  18. package/dist/oclif/receiver-heal.js +6 -6
  19. package/dist/oclif/root-signup-hint.js +3 -4
  20. package/dist/receiver.d.ts +95 -0
  21. package/dist/receiver.js +569 -0
  22. package/dist/{repeats-BL7ZC-_6-B_ESsWTH.js → repeats-Dj_o6c-X-Bf9G0xds.js} +4617 -1744
  23. package/dist/{scoped-chat-DAiOb622.js → scoped-chat-DZ42V3jq.js} +3 -5
  24. package/dist/{shared-mail-files-CZfm7E2k.js → shared-mail-files-BAQSGxxz.js} +2 -4
  25. package/dist/{shared-mail-watch-DyQB0MK0.js → shared-mail-watch-BAPMsFNv.js} +2 -3
  26. package/dist/{signal-command-Cte4mAL3.js → signal-command-CRUMUCNa.js} +19 -27
  27. package/dist/skills/primitive-connect/references/connect.md +3 -1
  28. package/dist/skills/primitive-connect/references/contacts.md +9 -4
  29. package/dist/skills/primitive-connect.json +4 -4
  30. package/man/primitive.1 +44 -3
  31. package/package.json +65 -15
@@ -1,11 +1,12 @@
1
- import { $t as listDomains, At as getAgentContactPolicy, Dn as sendEmail, Ft as getEmail, Jt as listAgentContacts, Kn as __exportAll, Kt as inviteAgentConnection, Xt as listContacts, cn as listSentEmails, gn as putContact, hn as putAgentContactPolicy, jt as getContact, mn as putAgentContact, qt as listAgentConnections, vt as deleteAgentContact, wn as searchEmails, yt as deleteContact } from "./repeats-BL7ZC-_6-B_ESsWTH.js";
2
- import { B as parseConnectedAgentProfile, F as agentProfileName, G as PrimitiveApiClient, H as profileAlreadyConnectedMessage, I as agentProfilesDirectory, L as connectedAgentIdentity, M as AGENT_PROFILE_ENV, N as AgentConnectionSetupError, P as agentProfileDirectory, R as connectedApiBaseUrl, V as parseOwnerMemberAddress, W as saveConnectedAgentProfile, x as loadCliCredentials, z as loadConnectedAgentProfile } from "./cli-config-C0Y_Ppol.js";
3
- 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-CZfm7E2k.js";
4
- import { gt as defaultConnectionName, jt as refreshStoredCliCredentials, mt as cliInvocation } from "./api-command-fKP3NYa7.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-DJAb9SGm.js";
6
- import { o as scopedChatSenderTrust } from "./scoped-chat-DAiOb622.js";
7
- import { l as stopBackgroundListen, r as backgroundListenStatus } from "./listen-background-cD6n8VYO.js";
8
- import { t as readSharedMailOwner } from "./shared-mail-watch-DyQB0MK0.js";
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
- //#region src/oclif/owner-member-address.ts
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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
- //#endregion
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 (typeof network.member_policy_required !== "boolean") throw unavailable();
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
- //#endregion
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
- //#endregion
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 keys = [
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
- const row = mailObject(value, value && typeof value === "object" && Object.hasOwn(value, "receiverMode") ? [...keys, "receiverMode"] : keys);
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
- claimedName = (await connectAgent({
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
- })).name;
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
- //#endregion
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
- //#endregion
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
- if (!(await managedDomains(new PrimitiveApiClient({
5594
+ const domains = await managedDomains(new PrimitiveApiClient({
5499
5595
  apiKey: credentials.access_token,
5500
5596
  apiBaseUrl,
5501
5597
  fetch: params.fetch
5502
- }), apiBaseUrl)).length) throw refuse("No managed domain is available for enrollment.");
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
- //#endregion
5638
- //#region src/oclif/machine-session.ts
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 };