primcli 1.74.1 → 1.75.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.
Files changed (26) hide show
  1. package/README.md +49 -9
  2. package/dist/{api-command-fKP3NYa7.js → api-command-rlS3eTlZ.js} +77 -43
  3. package/dist/{cli-config-C0Y_Ppol.js → cli-config-B80p0pLk.js} +7 -12
  4. package/dist/{client.gen-CaaMP86o.js → client.gen-6rZvmHfx.js} +7 -14
  5. package/dist/{json-output-Ce10AsSb.js → json-output-BzSBR-KX.js} +1 -2
  6. package/dist/{listen-background-cD6n8VYO.js → listen-background-C0F9gNDD.js} +2 -3
  7. package/dist/{machine-session-B6V5-grn.js → machine-session-Cq_nD5eF.js} +144 -92
  8. package/dist/{notify-session-DJAb9SGm.js → notify-session-CbY4FYUt.js} +10 -30
  9. package/dist/oclif/auto-signal-worker.js +5 -6
  10. package/dist/oclif/auto-signals.js +3 -4
  11. package/dist/oclif/help.js +127 -0
  12. package/dist/oclif/index.js +414 -344
  13. package/dist/oclif/listen-supervisor.js +1 -1
  14. package/dist/oclif/proxy-auto-detect.js +2 -3
  15. package/dist/oclif/receiver-heal.js +6 -6
  16. package/dist/oclif/root-signup-hint.js +2 -3
  17. package/dist/{repeats-BL7ZC-_6-B_ESsWTH.js → repeats-Dj_o6c-X-LyCkKYjp.js} +552 -213
  18. package/dist/{scoped-chat-DAiOb622.js → scoped-chat-B0thCwME.js} +3 -5
  19. package/dist/{shared-mail-files-CZfm7E2k.js → shared-mail-files-BAQSGxxz.js} +2 -4
  20. package/dist/{shared-mail-watch-DyQB0MK0.js → shared-mail-watch-BAPMsFNv.js} +2 -3
  21. package/dist/{signal-command-Cte4mAL3.js → signal-command-DCPsJBdz.js} +14 -23
  22. package/dist/skills/primitive-connect/references/connect.md +3 -1
  23. package/dist/skills/primitive-connect/references/contacts.md +9 -4
  24. package/dist/skills/primitive-connect.json +4 -4
  25. package/man/primitive.1 +10 -3
  26. package/package.json +14 -9
@@ -1,11 +1,11 @@
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 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-B80p0pLk.js";
2
+ 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-Dj_o6c-X-LyCkKYjp.js";
3
+ import { Pt as refreshStoredCliCredentials, gt as defaultConnectionName, mt as cliInvocation, xt as detectAgentRuntime } from "./api-command-rlS3eTlZ.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 { _ as installConnectSkill, a as NotificationRetryError, b as readBundledConnectSkill, d as defaultSessionSocket, f as NativeSessionDisconnectedError, l as SESSION_UUID, t as notificationScope, u as connectNativeSession } from "./notify-session-CbY4FYUt.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 { o as scopedChatSenderTrust } from "./scoped-chat-B0thCwME.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";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
11
  import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
@@ -14,7 +14,7 @@ import { execFile, execFileSync, spawn } from "node:child_process";
14
14
  import { homedir } from "node:os";
15
15
  import { promisify } from "node:util";
16
16
  import { createConnection } from "node:net";
17
- //#region src/oclif/owner-member-address.ts
17
+ // src/oclif/owner-member-address.ts
18
18
  const REFRESH_TIMEOUT_MS = 5e3;
19
19
  const MAX_RESPONSE_BYTES$1 = 65536;
20
20
  /** Reads at most MAX_RESPONSE_BYTES, stopping as soon as the body exceeds it. */
@@ -145,8 +145,7 @@ async function withOwnerMemberAddress(result, params) {
145
145
  ownerReportGuidance: ownerReportGuidance(identity)
146
146
  };
147
147
  }
148
- //#endregion
149
- //#region src/oclif/claude-session-receivers.ts
148
+ // src/oclif/claude-session-receivers.ts
150
149
  const FILE = /^([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})\.json$/;
151
150
  const LOCK_WAIT$2 = new Int32Array(new SharedArrayBuffer(4));
152
151
  function sessionReceiversDirectory(configDir) {
@@ -285,8 +284,7 @@ function pruneSessionReceivers(configDir) {
285
284
  }
286
285
  return dropped;
287
286
  }
288
- //#endregion
289
- //#region src/oclif/hook-launcher.ts
287
+ // src/oclif/hook-launcher.ts
290
288
  /**
291
289
  * Claude Code hooks run Primitive through a small POSIX launcher instead of a
292
290
  * pinned Node binary. A version manager (nvm, n, asdf) or a package-runner
@@ -468,8 +466,7 @@ function readHookLauncherWarning(configDir) {
468
466
  return null;
469
467
  }
470
468
  }
471
- //#endregion
472
- //#region src/oclif/machine-files.ts
469
+ // src/oclif/machine-files.ts
473
470
  /** Hand-edited files larger than this are reported, never rewritten. */
474
471
  const MAX_MANAGED_FILE_BYTES = 1048576;
475
472
  /** Backups kept per file; older Primitive backups of the same file are pruned. */
@@ -695,8 +692,7 @@ function upsertManagedBlock(text, body) {
695
692
  function jsonIndentation(text) {
696
693
  return /^[{[][^\n]*\n([ \t]+)\S/.exec(text)?.[1] ?? 2;
697
694
  }
698
- //#endregion
699
- //#region src/oclif/claude-machine-hooks.ts
695
+ // src/oclif/claude-machine-hooks.ts
700
696
  function record$2(value) {
701
697
  return value !== null && typeof value === "object" && !Array.isArray(value);
702
698
  }
@@ -1556,8 +1552,7 @@ function repairClaudeHooks(settings, context, checks, options = {}) {
1556
1552
  receivers
1557
1553
  };
1558
1554
  }
1559
- //#endregion
1560
- //#region src/oclif/claude-wake-install.ts
1555
+ // src/oclif/claude-wake-install.ts
1561
1556
  const RECEIVE_EVENTS = [
1562
1557
  "Stop",
1563
1558
  "SessionStart",
@@ -1846,8 +1841,7 @@ function uninstallClaudeWakeHook(options) {
1846
1841
  return false;
1847
1842
  }
1848
1843
  }
1849
- //#endregion
1850
- //#region src/oclif/session-records.ts
1844
+ // src/oclif/session-records.ts
1851
1845
  const MACHINE_RUNTIMES = [
1852
1846
  "claude",
1853
1847
  "codex",
@@ -2016,8 +2010,7 @@ async function releaseRegistrationsForProfile(params) {
2016
2010
  } catch {}
2017
2011
  }
2018
2012
  }
2019
- //#endregion
2020
- //#region src/oclif/agent-connect.ts
2013
+ // src/oclif/agent-connect.ts
2021
2014
  const MAX_INVITATION_BYTES = 4096;
2022
2015
  const MAX_RESPONSE_BYTES = 32768;
2023
2016
  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 +2097,22 @@ function removeEmptyDirectory(path) {
2104
2097
  rmdirSync(path);
2105
2098
  } catch {}
2106
2099
  }
2100
+ const CONNECTION_ID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
2101
+ /** The whole of one setup challenge marker; its first group is the connection id. */
2102
+ const CHALLENGE_MARKER = /^primitive-connection:([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}):[1-9]\d*$/;
2103
+ /** The challenge hints a claim response carries. Unusable values are dropped. */
2104
+ function claimedChallenge(value) {
2105
+ const data = value?.data;
2106
+ const connection = data?.connection;
2107
+ const connectionId = typeof connection?.id === "string" && CONNECTION_ID.test(connection.id) ? connection.id : void 0;
2108
+ const claimedAt = typeof connection?.claimed_at === "string" ? Date.parse(connection.claimed_at) : NaN;
2109
+ const marker = typeof data?.challenge_marker === "string" ? CHALLENGE_MARKER.exec(data.challenge_marker) : null;
2110
+ return {
2111
+ ...marker && (!connectionId || marker[1] === connectionId) ? { marker: marker[0] } : {},
2112
+ ...connectionId ? { connectionId } : {},
2113
+ ...Number.isFinite(claimedAt) ? { claimedAt: new Date(claimedAt).toISOString() } : {}
2114
+ };
2115
+ }
2107
2116
  const CONNECTION_NAME_FILE = "connection-name.json";
2108
2117
  /**
2109
2118
  * The display name this profile's claim reported, saved beside the profile
@@ -2409,6 +2418,7 @@ async function connectAgent(params) {
2409
2418
  const body = await readClaimResponse(response);
2410
2419
  const profile = profileFromClaim(body, invitation, invitationHash, params.now ?? Date.now);
2411
2420
  const name = claimedConnectionName(body);
2421
+ const challenge = claimedChallenge(body);
2412
2422
  saveConnectedAgentProfile(params.configDir, profileName, profile);
2413
2423
  try {
2414
2424
  await releaseRegistrationsForClaim({
@@ -2430,7 +2440,8 @@ async function connectAgent(params) {
2430
2440
  return {
2431
2441
  status: "claimed",
2432
2442
  identity: connectedAgentIdentity(profileName, profile),
2433
- ...name === void 0 ? {} : { name }
2443
+ ...name === void 0 ? {} : { name },
2444
+ ...Object.keys(challenge).length ? { challenge } : {}
2434
2445
  };
2435
2446
  } catch (error) {
2436
2447
  if (error instanceof AgentInvitationRejectedError) throw error;
@@ -2440,8 +2451,7 @@ async function connectAgent(params) {
2440
2451
  release();
2441
2452
  }
2442
2453
  }
2443
- //#endregion
2444
- //#region src/oclif/contacts.ts
2454
+ // src/oclif/contacts.ts
2445
2455
  var ContactsApiError = class extends Error {
2446
2456
  payload;
2447
2457
  directoryAvailable = false;
@@ -2638,8 +2648,7 @@ async function runContactRequest(client, request) {
2638
2648
  throw error;
2639
2649
  }
2640
2650
  }
2641
- //#endregion
2642
- //#region src/oclif/address-notes.ts
2651
+ // src/oclif/address-notes.ts
2643
2652
  var AddressNotesApiError = class extends Error {
2644
2653
  payload;
2645
2654
  status;
@@ -2772,8 +2781,7 @@ async function runAddressNotesRequest(client, request) {
2772
2781
  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
2782
  return data;
2774
2783
  }
2775
- //#endregion
2776
- //#region src/oclif/agent-resume-note.ts
2784
+ // src/oclif/agent-resume-note.ts
2777
2785
  /**
2778
2786
  * The address note holding the command that resumes the coding session an
2779
2787
  * agent is bound to, so an app can offer to reopen that exact session.
@@ -2886,8 +2894,7 @@ async function clearProfileResumeNote(options) {
2886
2894
  return "failed";
2887
2895
  }
2888
2896
  }
2889
- //#endregion
2890
- //#region src/oclif/agent-disconnect.ts
2897
+ // src/oclif/agent-disconnect.ts
2891
2898
  var AgentDisconnectError = class extends Error {};
2892
2899
  /** The profile no longer holds the credential the caller expected to revoke. */
2893
2900
  var AgentCredentialChangedError = class extends AgentDisconnectError {};
@@ -3185,8 +3192,7 @@ async function clearRevokedAddresses(params) {
3185
3192
  cleared
3186
3193
  };
3187
3194
  }
3188
- //#endregion
3189
- //#region src/oclif/agent-setup-read.ts
3195
+ // src/oclif/agent-setup-read.ts
3190
3196
  function retryDelay(response, attempt, now) {
3191
3197
  const raw = response?.headers.get("retry-after")?.trim();
3192
3198
  if (raw && /^\d+(?:\.\d+)?$/.test(raw)) {
@@ -3223,8 +3229,7 @@ async function readSetupApi(budget, stage, read) {
3223
3229
  budget.retries++;
3224
3230
  }
3225
3231
  }
3226
- //#endregion
3227
- //#region src/oclif/contact-rule-matcher.ts
3232
+ // src/oclif/contact-rule-matcher.ts
3228
3233
  const localPart = /^[a-z0-9!#$%&'+/=^_`{|}~-]+(?:\.[a-z0-9!#$%&'+/=^_`{|}~-]+)*$/;
3229
3234
  const patternPart = /^[a-z0-9.!#$%&'+/=^_`{|}~*-]+$/;
3230
3235
  const label = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
@@ -3301,8 +3306,7 @@ function matchesContactSelector(input, sender) {
3301
3306
  const [localPattern, hostPattern] = splitAddress(selector.value) ?? [];
3302
3307
  return Boolean(localPattern && hostPattern && matchesDomain(hostPattern, host) && matchesLocal(localPattern, local));
3303
3308
  }
3304
- //#endregion
3305
- //#region src/oclif/contact-policy.ts
3309
+ // src/oclif/contact-policy.ts
3306
3310
  function record(value) {
3307
3311
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid contact policy.");
3308
3312
  return value;
@@ -3638,8 +3642,7 @@ function createNotificationContactPolicy(options) {
3638
3642
  }
3639
3643
  };
3640
3644
  }
3641
- //#endregion
3642
- //#region src/oclif/registration-log.ts
3645
+ // src/oclif/registration-log.ts
3643
3646
  /**
3644
3647
  * A short private record of this profile's registration attempts, so a
3645
3648
  * stuck registration can be explained by `agent connect --status` and
@@ -3772,8 +3775,7 @@ function failedSessionRegistration(configDir, session) {
3772
3775
  const summary = registrationSummary(configDir, profile);
3773
3776
  return summary?.lastFailure ? summary : null;
3774
3777
  }
3775
- //#endregion
3776
- //#region src/oclif/agent-setup.ts
3778
+ // src/oclif/agent-setup.ts
3777
3779
  const SUBJECT = "Connect your agent to Primitive";
3778
3780
  const MARKER = /\bprimitive-connection:([a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}):([1-9]\d*)\b/g;
3779
3781
  const fail = (message) => new AgentConnectionSetupError(message);
@@ -3869,6 +3871,32 @@ async function checkVerification(context, timeoutMs) {
3869
3871
  return { state: "unavailable" };
3870
3872
  }
3871
3873
  }
3874
+ async function readClaim(context) {
3875
+ try {
3876
+ const response = await context.fetch(`${context.profile.api_base_url}/agent-connections/me`, {
3877
+ method: "GET",
3878
+ redirect: "error",
3879
+ signal: AbortSignal.timeout(5e3),
3880
+ headers: {
3881
+ authorization: `Bearer ${context.profile.api_key}`,
3882
+ accept: "application/json"
3883
+ }
3884
+ });
3885
+ if (response.status !== 200) {
3886
+ await response.body?.cancel();
3887
+ return {};
3888
+ }
3889
+ const text = await response.text();
3890
+ if (text.length > 65536) return {};
3891
+ const body = JSON.parse(text);
3892
+ const address = body?.data?.connection?.address;
3893
+ if (body?.success !== true || typeof address !== "string" || address.toLowerCase() !== context.identity.agentAddress.toLowerCase()) return {};
3894
+ const { marker: _marker, ...hint } = claimedChallenge(body);
3895
+ return hint;
3896
+ } catch {
3897
+ return {};
3898
+ }
3899
+ }
3872
3900
  /**
3873
3901
  * Poll until the server reports the connection verified, the budget runs out,
3874
3902
  * or the status read is unavailable. Elapsed time is the larger of the clock
@@ -3892,8 +3920,41 @@ async function awaitVerification(context, dependencies, timeoutMs) {
3892
3920
  waited += wait;
3893
3921
  }
3894
3922
  }
3923
+ const OPTIONAL_STATE_KEYS = [
3924
+ "receiverMode",
3925
+ "challengeMarker",
3926
+ "challengeConnectionId"
3927
+ ];
3928
+ /**
3929
+ * How far before the claim's server time the search starts. With the exact
3930
+ * marker the window only bounds the search, so it allows generous skew
3931
+ * between the database and the mail pipeline clocks. Without it the window
3932
+ * is what excludes an earlier challenge, so it starts at the claim itself:
3933
+ * the server sends the challenge only after the claim commits.
3934
+ */
3935
+ const MARKER_WINDOW_MS = 6e4;
3936
+ /**
3937
+ * Points this setup at the challenge its claim started. Leaves the state
3938
+ * unchanged when the claim said too little, which keeps the earlier rule.
3939
+ */
3940
+ function narrowToClaim(state, claim) {
3941
+ const claimedAt = claim?.claimedAt ? Date.parse(claim.claimedAt) : NaN;
3942
+ if (claim?.marker) {
3943
+ state.challengeMarker = claim.marker;
3944
+ if (Number.isFinite(claimedAt)) state.since = (/* @__PURE__ */ new Date(claimedAt - MARKER_WINDOW_MS)).toISOString();
3945
+ } else if (claim?.connectionId && Number.isFinite(claimedAt)) {
3946
+ state.challengeConnectionId = claim.connectionId;
3947
+ state.since = new Date(claimedAt).toISOString();
3948
+ }
3949
+ }
3950
+ function expectedChallenge(state) {
3951
+ return {
3952
+ ...state.challengeMarker ? { marker: state.challengeMarker } : {},
3953
+ ...state.challengeConnectionId ? { connectionId: state.challengeConnectionId } : {}
3954
+ };
3955
+ }
3895
3956
  function parseState(value) {
3896
- const keys = [
3957
+ const row = mailObject(value, [...[
3897
3958
  "version",
3898
3959
  "session",
3899
3960
  "invitationHash",
@@ -3902,9 +3963,8 @@ function parseState(value) {
3902
3963
  "challenge",
3903
3964
  "phase",
3904
3965
  "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" || ![
3966
+ ], ...OPTIONAL_STATE_KEYS.filter((key) => value && typeof value === "object" && Object.hasOwn(value, key))]);
3967
+ 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
3968
  "waiting",
3909
3969
  "sending",
3910
3970
  "sent"
@@ -3930,23 +3990,28 @@ function parseState(value) {
3930
3990
  messageId: mailString(challenge.messageId, 1e3),
3931
3991
  marker: mailString(challenge.marker, 100)
3932
3992
  } : null,
3933
- receipt
3993
+ receipt,
3994
+ ...typeof row.challengeMarker === "string" ? { challengeMarker: row.challengeMarker } : {},
3995
+ ...typeof row.challengeConnectionId === "string" ? { challengeConnectionId: row.challengeConnectionId } : {}
3934
3996
  };
3935
3997
  if (state.phase !== "waiting" && !state.challenge || state.phase === "sent" && !state.receipt) throw fail("Saved setup progress is inconsistent. Preserve its private files.");
3936
3998
  return state;
3937
3999
  }
3938
4000
  /** Match only authenticated owner mail. Subject/search results alone grant no authority. */
3939
- function setupChallenge(detail, identity, since) {
4001
+ function setupChallenge(detail, identity, since, expected = {}) {
3940
4002
  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
4003
  const markers = [...new Set((detail.body_text ?? "").match(MARKER) ?? [])];
3942
4004
  if (markers.length !== 1) return null;
4005
+ const marker = markers[0] ?? "";
4006
+ if (expected.marker && marker !== expected.marker) return null;
4007
+ if (expected.connectionId && !marker.startsWith(`primitive-connection:${expected.connectionId}:`)) return null;
3943
4008
  return {
3944
4009
  id: mailId(detail.id),
3945
4010
  messageId: detail.message_id,
3946
4011
  marker: markers[0] ?? ""
3947
4012
  };
3948
4013
  }
3949
- async function findChallenge(context, since) {
4014
+ async function findChallenge(context, since, expected = {}) {
3950
4015
  const page = await readSetupApi(context.readBudget, "challenge search", (signal) => searchEmails({
3951
4016
  client: context.client.client,
3952
4017
  query: {
@@ -3971,8 +4036,14 @@ async function findChallenge(context, since) {
3971
4036
  responseStyle: "fields"
3972
4037
  }));
3973
4038
  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);
4039
+ const match = setupChallenge(response.data.data, context.identity, since, expected);
4040
+ if (match && !matches.some((seen) => seen.messageId === match.messageId && seen.marker === match.marker)) matches.push(match);
4041
+ }
4042
+ if (expected.connectionId && !expected.marker && matches.length > 1) {
4043
+ const generation = (row) => Number(row.marker.slice(row.marker.lastIndexOf(":") + 1));
4044
+ const top = Math.max(...matches.map(generation));
4045
+ const current = matches.filter((row) => generation(row) === top);
4046
+ if (current.length === 1) return current[0] ?? null;
3976
4047
  }
3977
4048
  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
4049
  return matches[0] ?? null;
@@ -4061,6 +4132,7 @@ async function nativeSessionPreflight(session) {
4061
4132
  function defaults$1() {
4062
4133
  return {
4063
4134
  checkVerification,
4135
+ readClaim,
4064
4136
  now: Date.now,
4065
4137
  sleep: async (ms) => {
4066
4138
  await setTimeout$1(ms);
@@ -4240,6 +4312,8 @@ async function setupAgent(params) {
4240
4312
  };
4241
4313
  let discardStub = false;
4242
4314
  let claimedName;
4315
+ let claimedChallengeHint;
4316
+ let claimedThisRun = false;
4243
4317
  const takeEnrollment = () => {
4244
4318
  try {
4245
4319
  releaseEnrollment = acquireListenLock(join(directory, "enrollment"), ENROLLMENT_LOCK_SCOPE);
@@ -4298,7 +4372,7 @@ async function setupAgent(params) {
4298
4372
  writeMailJson(path, state);
4299
4373
  }
4300
4374
  try {
4301
- claimedName = (await connectAgent({
4375
+ const claim = await connectAgent({
4302
4376
  configDir: params.configDir,
4303
4377
  profileName,
4304
4378
  invitation: params.invitation ?? "",
@@ -4306,7 +4380,10 @@ async function setupAgent(params) {
4306
4380
  presence: true,
4307
4381
  claimLockHeld: releaseClaim !== void 0,
4308
4382
  claimLockWaitMs: params.claimLockWaitMs ?? CLAIM_LOCK_WAIT_MS
4309
- })).name;
4383
+ });
4384
+ claimedName = claim.name;
4385
+ claimedChallengeHint = claim.challenge;
4386
+ claimedThisRun = claim.status === "claimed";
4310
4387
  if (retireEnrollment) moveAside(join(directory, "enrollment", "state.json"), new Date(dependencies.now()));
4311
4388
  if (replacedUnfinished?.address) appendRegistrationLog(params.configDir, profileName, {
4312
4389
  event: "replaced_unfinished",
@@ -4334,6 +4411,12 @@ async function setupAgent(params) {
4334
4411
  releaseClaim = void 0;
4335
4412
  }
4336
4413
  }
4414
+ if (state && claimedChallengeHint) {
4415
+ narrowToClaim(state, claimedChallengeHint);
4416
+ try {
4417
+ writeMailJson(join(directory, "setup.json"), state);
4418
+ } catch {}
4419
+ }
4337
4420
  const profile = loadConnectedAgentProfile(params.configDir, profileName);
4338
4421
  if (!profile) throw fail(unfinishedSetupStep(pendingEnrollmentAddress(directory)));
4339
4422
  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 +4477,18 @@ async function setupAgent(params) {
4394
4477
  state.phase = "sent";
4395
4478
  writeMailJson(path, state);
4396
4479
  }
4480
+ if (state.phase === "waiting" && !claimedThisRun && !state.challengeMarker && !state.challengeConnectionId) narrowToClaim(state, await dependencies.readClaim(context));
4397
4481
  if (state.phase === "waiting") {
4398
4482
  const deadline = dependencies.now() + timeoutMs;
4399
4483
  do {
4400
- state.challenge = await dependencies.findChallenge(context, state.since);
4484
+ state.challenge = await dependencies.findChallenge(context, state.since, expectedChallenge(state));
4401
4485
  if (state.challenge || dependencies.now() >= deadline) break;
4402
4486
  await dependencies.sleep(Math.min(1500, deadline - dependencies.now()));
4403
4487
  } while (dependencies.now() <= deadline);
4404
4488
  if (!state.challenge) return result("not_started");
4405
4489
  state.phase = "sending";
4490
+ delete state.challengeMarker;
4491
+ delete state.challengeConnectionId;
4406
4492
  writeMailJson(path, state);
4407
4493
  try {
4408
4494
  state.receipt = parseReceipt(await dependencies.sendVerification({
@@ -4432,8 +4518,7 @@ async function setupAgent(params) {
4432
4518
  if (discardStub) removeEmptyDirectory(directory);
4433
4519
  }
4434
4520
  }
4435
- //#endregion
4436
- //#region src/oclif/agent-connect-flow.ts
4521
+ // src/oclif/agent-connect-flow.ts
4437
4522
  /** What this CLI version can do in one `agent connect --session` call. */
4438
4523
  const AGENT_CONNECT_CAPABILITIES = [
4439
4524
  "invitation_claim_from_stdin",
@@ -4885,8 +4970,7 @@ async function runAgentConnect(options) {
4885
4970
  externalHook
4886
4971
  };
4887
4972
  }
4888
- //#endregion
4889
- //#region src/oclif/agent-enroll.ts
4973
+ // src/oclif/agent-enroll.ts
4890
4974
  const RESPONSE_LIMIT = 32768;
4891
4975
  const CONNECTION_LIST_LIMIT = 131072;
4892
4976
  const CONFIRMATION_BUDGET_MS = 12e4;
@@ -5634,32 +5718,8 @@ async function enrollAgent(params) {
5634
5718
  release();
5635
5719
  }
5636
5720
  }
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
- });
5721
+ // src/oclif/machine-session.ts
5722
+ var machine_session_exports = /* @__PURE__ */ __exportAll({ connectedProfilesForSession: () => connectedProfilesForSession });
5663
5723
  /** The repository (or directory) a session works in, for its default name. */
5664
5724
  function workspaceName(cwd) {
5665
5725
  let current = resolve(cwd);
@@ -6406,13 +6466,6 @@ async function registerSession(options) {
6406
6466
  * within this window cancels the end.
6407
6467
  */
6408
6468
  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
6469
  /** The end of this record is a hook's, still waiting. */
6417
6470
  function deferredEnd(record) {
6418
6471
  return Boolean(record?.endedAt) && record?.disconnect === "deferred";
@@ -6771,5 +6824,4 @@ async function runDetached(params) {
6771
6824
  return "failed";
6772
6825
  }
6773
6826
  }
6774
- //#endregion
6775
6827
  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 };
@@ -1,6 +1,6 @@
1
- import { Et as downloadEmailAttachmentPart, J as parseWebhookEvent, K as isEmailReceivedEvent, c as classifySignalContent, q as isTrustedSender } from "./repeats-BL7ZC-_6-B_ESsWTH.js";
2
- import { _ as acquireListenLock, g as ListenStateError, y as listenIdentity } from "./shared-mail-files-CZfm7E2k.js";
3
- import { B as wakeInteractionSentence, T as wakeReadCommand, w as skillFirstLine } from "./api-command-fKP3NYa7.js";
1
+ import { Et as downloadEmailAttachmentPart, J as parseWebhookEvent, K as isEmailReceivedEvent, c as classifySignalContent, q as isTrustedSender } from "./repeats-Dj_o6c-X-LyCkKYjp.js";
2
+ import { B as wakeInteractionSentence, T as wakeReadCommand, w as skillFirstLine } from "./api-command-rlS3eTlZ.js";
3
+ import { _ as acquireListenLock, g as ListenStateError, y as listenIdentity } from "./shared-mail-files-BAQSGxxz.js";
4
4
  import { closeSync, copyFileSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, opendirSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { createHash, randomUUID, scryptSync } from "node:crypto";
6
6
  import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
@@ -8,22 +8,8 @@ import { homedir } from "node:os";
8
8
  import { lstat, realpath } from "node:fs/promises";
9
9
  import { isDeepStrictEqual } from "node:util";
10
10
  import WebSocket from "ws";
11
- //#region src/oclif/connect-skill.ts
11
+ // src/oclif/connect-skill.ts
12
12
  const CONNECT_SKILL_NAME = "primitive-connect";
13
- const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14
- /**
15
- * The runtime whose tool environment names this exact session. A session ID
16
- * that matches neither runtime's variable has no detectable runtime.
17
- */
18
- function detectAgentRuntime(session, env = process.env) {
19
- const wanted = session.toLowerCase();
20
- const claude = env.CLAUDE_CODE_SESSION_ID?.trim().toLowerCase();
21
- const codex = (env.CODEX_THREAD_ID?.trim() || env.CODEX_SESSION_ID?.trim())?.toLowerCase();
22
- if (!UUID.test(wanted)) return null;
23
- if (claude === wanted && codex !== wanted) return "claude";
24
- if (codex === wanted && claude !== wanted) return "codex";
25
- return null;
26
- }
27
13
  function connectSkillTarget(options) {
28
14
  const env = options.env ?? process.env;
29
15
  const cwd = resolve(options.cwd ?? process.cwd());
@@ -288,8 +274,7 @@ function installConnectSkill(options) {
288
274
  };
289
275
  }
290
276
  }
291
- //#endregion
292
- //#region src/oclif/notify-session-errors.ts
277
+ // src/oclif/notify-session-errors.ts
293
278
  var NativeSessionError = class extends ListenStateError {
294
279
  submitted;
295
280
  constructor(message, submitted = false) {
@@ -315,8 +300,7 @@ var NotificationOutcomeUnknownError = class extends NativeSessionError {
315
300
  super(message, true);
316
301
  }
317
302
  };
318
- //#endregion
319
- //#region src/oclif/notify-session-native.ts
303
+ // src/oclif/notify-session-native.ts
320
304
  /** Codex explicitly refused this input before adding it to the turn queue. */
321
305
  var NativeTurnNotSubmittedError = class extends NativeSessionError {
322
306
  constructor() {
@@ -580,8 +564,7 @@ async function connectNativeSession(options) {
580
564
  }
581
565
  };
582
566
  }
583
- //#endregion
584
- //#region src/oclif/notify-session-content.ts
567
+ // src/oclif/notify-session-content.ts
585
568
  var NotificationRetryError = class extends ListenStateError {};
586
569
  function notificationPartReader(client) {
587
570
  return async (emailId, partIndex, signal) => {
@@ -683,8 +666,7 @@ async function classifyNotificationContent(email, readPart, signal) {
683
666
  if (classification.classification === "unavailable") throw new NotificationRetryError("Interaction classification is unavailable; retrying through the delivery queue.");
684
667
  return classification;
685
668
  }
686
- //#endregion
687
- //#region src/oclif/notify-session-state.ts
669
+ // src/oclif/notify-session-state.ts
688
670
  const invalid = () => new ListenStateError("Notification receipts are unavailable or inconsistent. Preserve this state to avoid duplicate notifications.");
689
671
  function uuid(value) {
690
672
  if (typeof value !== "string" || !SESSION_UUID.test(value)) throw invalid();
@@ -934,8 +916,7 @@ function notificationReceiptPage(configDir, scope, threadId, options = {}) {
934
916
  nextCursor: hasMore ? ids.at(-1) ?? null : null
935
917
  };
936
918
  }
937
- //#endregion
938
- //#region src/oclif/notify-session.ts
919
+ // src/oclif/notify-session.ts
939
920
  function notificationScope(origin, apiKey) {
940
921
  if (!apiKey?.startsWith("pconn_")) throw new ListenStateError("--notify-session requires a connected-agent credential scoped to its own address.");
941
922
  return listenIdentity(origin, `connection:${scryptSync(apiKey, "primitive-listener-identity-v1", 32).toString("hex")}`);
@@ -1133,5 +1114,4 @@ async function openSessionNotifications(options) {
1133
1114
  }
1134
1115
  var ContactNoticeSuppressed = class extends Error {};
1135
1116
  var ContactNoticeDeferred = class extends Error {};
1136
- //#endregion
1137
- export { detectAgentRuntime as _, NotificationRetryError as a, installedVersion as b, readConversationStatusContent as c, defaultSessionSocket as d, NativeSessionDisconnectedError as f, connectSkillTarget as g, NotificationOutcomeUnknownError as h, notificationReceiptPage as i, SESSION_UUID as l, NativeSessionNotLoadedError as m, notificationSenders as n, isRoutineNotificationContent as o, NativeSessionError as p, openSessionNotifications as r, notificationPartReader as s, notificationScope as t, connectNativeSession as u, installConnectSkill as v, readBundledConnectSkill as x, installedConnectSkillFile as y };
1117
+ export { installConnectSkill as _, NotificationRetryError as a, readBundledConnectSkill as b, readConversationStatusContent as c, defaultSessionSocket as d, NativeSessionDisconnectedError as f, connectSkillTarget as g, NotificationOutcomeUnknownError as h, notificationReceiptPage as i, SESSION_UUID as l, NativeSessionNotLoadedError as m, notificationSenders as n, isRoutineNotificationContent as o, NativeSessionError as p, openSessionNotifications as r, notificationPartReader as s, notificationScope as t, connectNativeSession as u, installedConnectSkillFile as v, installedVersion as y };
@@ -1,11 +1,11 @@
1
- import { Ut as getSentEmail } from "../repeats-BL7ZC-_6-B_ESsWTH.js";
2
- import { _ as acquireListenLock } from "../shared-mail-files-CZfm7E2k.js";
3
- import { b as readThreadContext, kt as createAuthenticatedCliApiClient } from "../api-command-fKP3NYa7.js";
4
- import { t as sendSignal } from "../signal-command-Cte4mAL3.js";
1
+ import { Ut as getSentEmail } from "../repeats-Dj_o6c-X-LyCkKYjp.js";
2
+ import { Mt as createAuthenticatedCliApiClient, b as readThreadContext } from "../api-command-rlS3eTlZ.js";
5
3
  import { AUTO_SIGNAL_KIND_ENV, AUTO_WORKING_RENEW_MS, autoSignalsDisabled, clearWorkingSending, markWorkingSending, readAutoClaim, readWorkingLease, recordRenewer, stopWorkingLease, writeWorkingLease } from "./auto-signals.js";
4
+ import { _ as acquireListenLock } from "../shared-mail-files-BAQSGxxz.js";
5
+ import { t as sendSignal } from "../signal-command-DCPsJBdz.js";
6
6
  import { join } from "node:path";
7
7
  import { homedir } from "node:os";
8
- //#region src/oclif/auto-signal-worker.ts
8
+ // src/oclif/auto-signal-worker.ts
9
9
  function configDirectory(env) {
10
10
  if (env.PRIMITIVE_CONFIG_DIR) return env.PRIMITIVE_CONFIG_DIR;
11
11
  return join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "primitive");
@@ -164,5 +164,4 @@ async function runAutoSignalWorker(env = process.env, deps = {}) {
164
164
  }
165
165
  } catch {}
166
166
  }
167
- //#endregion
168
167
  export { runAutoSignalWorker };
@@ -1,11 +1,11 @@
1
- import { d as readMailJson, h as writeMailJson, i as mailId, l as privateMailDirectory, r as mailAddress } from "../shared-mail-files-CZfm7E2k.js";
2
- import { n as isPlainChatReply, o as scopedChatSenderTrust } from "../scoped-chat-DAiOb622.js";
1
+ import { n as isPlainChatReply, o as scopedChatSenderTrust } from "../scoped-chat-B0thCwME.js";
2
+ import { d as readMailJson, h as writeMailJson, i as mailId, l as privateMailDirectory, r as mailAddress } from "../shared-mail-files-BAQSGxxz.js";
3
3
  import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { join } from "node:path";
6
6
  import { spawn } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
- //#region src/oclif/auto-signals.ts
8
+ // src/oclif/auto-signals.ts
9
9
  /**
10
10
  * Automatic communication signals. When mail from the verified owner or a
11
11
  * verified same-organization member is surfaced to an agent session, the CLI
@@ -612,5 +612,4 @@ function resumeAutoWorking(configDir, options = {}) {
612
612
  return 0;
613
613
  }
614
614
  }
615
- //#endregion
616
615
  export { AUTO_SIGNALS_OPT_OUT_ENV, AUTO_SIGNAL_EMAIL_ENV, AUTO_SIGNAL_KIND_ENV, AUTO_SIGNAL_WORKER_ENV, AUTO_WORKING_CAP_MS, AUTO_WORKING_EXPIRES_SECONDS, AUTO_WORKING_RENEW_MS, activeWorkingLeases, autoSignalEligible, autoSignalEntrypoint, autoSignalsDisabled, claimAutoRead, clearWorkingSending, dispatchAutoRead, dispatchAutoWorking, haltAutoWorking, isSentSignal, markWorkingSending, readAutoClaim, readWorkingLease, recordRenewer, recordSentSignal, releaseStopHold, renewerAlive, restoreAutoWorking, restoreWorkingLease, resumeAutoWorking, spawnAutoSignalWorker, startWorkingLease, stopWorkingLease, writeWorkingLease };