primitive 1.72.2 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -10
- package/bin/claude-pending-mail.mjs +292 -136
- package/bin/claude-wake.mjs +129 -60
- package/dist/api-command-fKP3NYa7.js +2904 -0
- package/dist/{cli-config--YTRdJ3t.js → cli-config-C0Y_Ppol.js} +3 -598
- package/dist/client.gen-CaaMP86o.js +597 -0
- package/dist/{listen-background--0peztKE.js → listen-background-cD6n8VYO.js} +1 -1
- package/dist/{machine-session-Be6j29GE.js → machine-session-B6V5-grn.js} +907 -207
- package/dist/{notify-session-Byt5sPWW.js → notify-session-DJAb9SGm.js} +3 -3
- package/dist/oclif/auto-signal-worker.js +4 -3
- package/dist/oclif/auto-signals.js +2 -2
- package/dist/oclif/index.js +260 -198
- package/dist/oclif/listen-supervisor.js +1 -1
- package/dist/oclif/receiver-heal.js +4 -4
- package/dist/oclif/root-signup-hint.js +1 -1
- package/dist/{repeats-BL7ZC-_6-CKMKBlx7.js → repeats-BL7ZC-_6-B_ESsWTH.js} +4005 -1
- package/dist/{scoped-chat-DTFkP360.js → scoped-chat-DAiOb622.js} +10 -2
- package/dist/{shared-mail-files-MwlVE90c.js → shared-mail-files-CZfm7E2k.js} +21 -1
- package/dist/{shared-mail-watch-DtKpsgg8.js → shared-mail-watch-DyQB0MK0.js} +1 -1
- package/dist/{signal-command-DeEEZPbQ.js → signal-command-Cte4mAL3.js} +8 -7
- package/package.json +1 -1
- package/dist/api-command-BrwPbyPP.js +0 -6546
package/dist/oclif/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { a as REPEAT_IDLE_MAX_MINUTES, b as
|
|
2
|
-
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, F as agentProfileName, G as PrimitiveApiClient, I as agentProfilesDirectory, M as AGENT_PROFILE_ENV, N as AgentConnectionSetupError, O as loadActiveChatState, P as agentProfileDirectory, T as saveSignupCredentials, U as requireDefaultLoginProfile, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as AGENT_ADDRESS_ENV, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname, z as loadConnectedAgentProfile } from "../cli-config
|
|
3
|
-
import {
|
|
4
|
-
import { S as
|
|
5
|
-
import { _ as detectAgentRuntime, a as NotificationRetryError, b as installedVersion, c as readConversationStatusContent, d as defaultSessionSocket, f as NativeSessionDisconnectedError, g as connectSkillTarget, h as NotificationOutcomeUnknownError, i as notificationReceiptPage, l as SESSION_UUID$1, m as NativeSessionNotLoadedError, n as notificationSenders, o as isRoutineNotificationContent, p as NativeSessionError, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, v as installConnectSkill, x as readBundledConnectSkill, y as installedConnectSkillFile } from "../notify-session-
|
|
6
|
-
import { $ as
|
|
7
|
-
import { a as
|
|
8
|
-
import { c as startBackgroundListen, i as backgroundListenToken, l as stopBackgroundListen, o as runBackgroundListen, r as backgroundListenStatus, u as verifyBackgroundListenTarget } from "../listen-background
|
|
9
|
-
import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-
|
|
1
|
+
import { $ as AutomatedFilterUnsupportedError, $t as listDomains, A as sleep$3, An as setMemory, At as getAgentContactPolicy, B as hasReplyState, Bn as updateRepeatingSend, Bt as getMemory, C as collectNewAcceptedEmails, Ct as deleteWakeSchedule, D as filtersFromFlags, Dn as sendEmail, Dt as downloadSentAttachmentArchiveMember, E as fetchEmailSearchPage, En as semanticSearch, F as assertAwaitingFilterRows, Fn as stopRepeatFromEmail, Ft as getEmail, Gn as verifyAgentSignup, Gt as getWakeSchedule, H as queryUsesAwaiting, Hn as updateWakeAuthorization, Ht as getRepeatingSend, I as awaitingRejectedError, In as testFunction, It as getFunction, J as parseWebhookEvent, Jt as listAgentContacts, K as isEmailReceivedEvent, L as ensureSearchReportsReplyState, Ln as unsetFunctionRoute, Lt as getFunctionRouting, M as AWAITING_VALUES, Mn as startAgentClaim, Mt as getConversation, N as REPLY_STATE_UNSUPPORTED_CODE, Nn as startAgentSignup, Nt as getCreditBalance, O as quoteDslValue, On as setFunctionRoute, Ot as downloadSentAttachmentByFilename, P as ReplyStateUnsupportedError, Pn as startCliLogin, Pt as getDefaultNetworkAgent, Q as AUTOMATED_VALUES, Qt as listDefaultNetworkMembers, R as formatAwaitingCell, Rn as updateDefaultNetworkMember, Rt as getFunctionTestRunTrace, S as transientFailure, Sn as runWakeSchedule, St as deleteWakeAuthorization, T as encodeReceivedAtSearchCursor, Tn as searchMemories, Tt as downloadEmailAttachmentByFilename, Un as updateWakeSchedule, Ut as getSentEmail, V as isAwaitingRejectedError, Vn as updateRoute, Vt as getOrgRoutingTopology, Wn as verifyAgentClaim, Wt as getThread, X as AUTOMATED_FILTER_UNSUPPORTED_CODE, Y as parseFromHeader, Yt as listAgentNetworks, Z as AUTOMATED_FLAG_DESCRIPTION, Zt as listDefaultNetworkAgents, _ as describeApiFailure, _n as redeemCreditCode, _t as createWakeSchedule, a as REPEAT_IDLE_MAX_MINUTES, an as listRepeatingSends, at as hasAutomatedVerdict, b as retryTransient, bn as replyToEmail, bt as deleteMemory, cn as listSentEmails, ct as cancelSentEmail, d as parseRepeatTick, dn as listWakeSchedules, dt as cliLogout, en as listEmailAttachmentArchiveMembers, et as assertAutomatedVerdict, f as preparePresenceAliveEmail, fn as pollCliLogin, ft as completeWebhookEvent, g as WaitDeadlineReached, gt as createWakeAuthorization, ht as createRoute, i as REPEAT_EVERY_MAX_MINUTES, in as listFunctions, j as AWAITING_FLAG_DESCRIPTION, jn as simulateRoute, k as sinceFromFlags, kn as setFunctionSecret, kt as getAccount, l as parseInteractionEnvelope$1, ln as listWakeAuthorizations, lt as cancelSentEmailByIdempotencyKey, m as repeatStopCommand, mt as createFunction, nn as listEndpoints, o as REPEAT_MAX_SENDS_MAX, on as listRoutes, ot as isAutomatedRejectedError, p as prepareSignalEmail, pn as pullWebhookEvent, pt as createEndpoint, rn as listFunctionLogs, s as buildRepeatStopBody, sn as listSentAttachmentArchiveMembers, st as addDefaultNetworkMember, tn as listEmails, tt as automatedRejectedError, u as parsePresenceEnvelope, un as listWakeDispatches, ut as checkDefaultNetworkContactAdmission, vn as removeDefaultNetworkMember, w as cursorFromAcceptedRows, wn as searchEmails, wt as downloadEmailAttachmentArchiveMember, x as retryingOperation, xn as resendAgentSignupVerification, xt as deleteRoute, y as pauseForTransientFailure, yn as reorderRoutes, z as formatRepliesCell, zn as updateFunction, zt as getInboxStatus } from "../repeats-BL7ZC-_6-B_ESsWTH.js";
|
|
2
|
+
import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, F as agentProfileName, G as PrimitiveApiClient, I as agentProfilesDirectory, M as AGENT_PROFILE_ENV, N as AgentConnectionSetupError, O as loadActiveChatState, P as agentProfileDirectory, T as saveSignupCredentials, U as requireDefaultLoginProfile, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as AGENT_ADDRESS_ENV, k as loadChatConversationByLocalId, l as saveCliConfig, m as cliAccessTokenExpiresAt, n as deleteCliConfig, o as redactCliEnvironment, p as acquireCliCredentialsLock, r as emptyCliConfig, s as removeCliEnvironment, u as upsertCliEnvironment, v as deleteCliCredentialsLock, w as saveCliCredentials, x as loadCliCredentials, y as detectPrimitiveKeyEnvMisname, z as loadConnectedAgentProfile } from "../cli-config-C0Y_Ppol.js";
|
|
3
|
+
import { C as resolveListenSubscription, S as normalizeListenOrigin, _ as acquireListenLock, a as mailLockBusy, c as mailTime, d as readMailJson, f as removeMailFile, g as ListenStateError, h as writeMailJson, i as mailId, l as privateMailDirectory, m as withMailLock, o as mailObject, u as privateMailPermissions, y as listenIdentity } from "../shared-mail-files-CZfm7E2k.js";
|
|
4
|
+
import { $ as formatDeletedEarlierSendNotice, A as clearReadPendingMail, At as createCliApiClient, C as serverRelationship, Ct as withFlagSuggestion, D as wakeRelationship, Dt as ConnectedAgentKeyRequiredError, E as wakeRecipientField, Et as withAttachmentWait, F as interactionHumanLine, G as assertValidIdempotencyKey, H as MAX_SEND_WAIT_TIMEOUT_MS, I as interactionNextActions, J as checkPriorReplies, K as buildFollowUpCommand, L as readEmailInteraction, M as readPendingMail, Mt as resolveCliApiRequestConfig, N as recordPendingMail, O as claimPendingMail, P as removePendingMail, Q as followUpCommandPrefix, R as replyExpectation, S as serverMuted, St as writeIdempotentReplayBannerIfReplay, T as wakeReadCommand, Tt as ATTACHMENT_WAIT_MAX_MS, U as SEND_OUTCOME_HELP, V as IDEMPOTENCY_KEY_FLAG_DESCRIPTION, W as apiResultHttpStatus, X as deriveSendIdempotencyKey, Y as classifySendError, Z as excludeSignalReplies, _ as displayAddress, _t as generatedAgentName, a as extractErrorCode, at as reportSendCommandResult, b as readThreadContext, bt as currentMailSessionKey, c as hasControlCharacter$1, ct as sendWaitClampNotice, d as runWithTiming, dt as successfulSendOutcome, et as formatPriorRepliesCheckSkipped, f as surfaceUnauthorizedHint, ft as thrownErrorExitCode, g as describeWake, h as bareAddress, ht as connectNextSteps, i as createOperationCommand, it as priorRepliesThatWentOut, j as pendingMailPath, jt as refreshStoredCliCredentials, k as clearConsumedPendingMail, kt as createAuthenticatedCliApiClient, l as headerFlagName, lt as sentHistoryWindowStart, m as explainPendingMailNotFound, mt as cliInvocation, n as API_ERROR_CODES, nt as formatSendFailureSummary, o as extractErrorPayload, ot as sendIdentityFields, p as writeErrorWithHints, q as buildThrownSendFailureEnvelope, r as TIME_FLAG_DESCRIPTION, rt as formatSendRecordFailureSummary, s as formatErrorPayload, st as sendOutcomeExitCode, t as API_BASE_URL_FLAG_DESCRIPTION, tt as formatPriorRepliesWarning, u as readTextFileFlag, ut as serializeErrorPayload, v as formatMailWakeLine, vt as identitySuggestions, wt as ATTACHMENT_WAIT_FLAG_DESCRIPTION, x as sentInThread, xt as formatAlreadySentNotice, y as latestOwnOutbound, yt as loadSkillLine, z as replyInteractionWarning } from "../api-command-fKP3NYa7.js";
|
|
5
|
+
import { _ as detectAgentRuntime, a as NotificationRetryError, b as installedVersion, c as readConversationStatusContent, d as defaultSessionSocket, f as NativeSessionDisconnectedError, g as connectSkillTarget, h as NotificationOutcomeUnknownError, i as notificationReceiptPage, l as SESSION_UUID$1, m as NativeSessionNotLoadedError, n as notificationSenders, o as isRoutineNotificationContent, p as NativeSessionError, r as openSessionNotifications, s as notificationPartReader, t as notificationScope, v as installConnectSkill, x as readBundledConnectSkill, y as installedConnectSkillFile } from "../notify-session-DJAb9SGm.js";
|
|
6
|
+
import { $ as saveConnectionName, A as ContactPolicyReadRetryError, B as AddressNotesApiError, C as RECEIVER_MODES, Ct as upsertManagedBlock, D as registrationSummary, Dt as refreshOwnerMemberAddressPeriodically, E as failedSessionRegistration, Et as addSessionReceiver, F as canonicalContactSelector, G as contactAgentAddress, H as runAddressNotesRequest, I as AgentDisconnectError, J as agentInvitationHash, K as runContactRequest, L as claimOverRevokedSetup, M as NetworkAdmissionPendingError, N as createNotificationContactPolicy, O as registrationSummaryLine, Ot as withOwnerMemberAddress, P as evaluateContactPolicy, Q as refusalAfterReplacement, R as clearRevokedAddresses, S as runAgentConnect, St as readManagedFile, T as verificationReplySubmitted, Tt as readHookLauncherWarning, U as ContactsApiError, V as addressNoteTarget, W as contactAddress, X as readAgentInvitation, Y as connectAgent, Z as receiverStatusCommand, _ as enrollmentResumes, _t as samePath, a as endSession, at as boundSessionProfiles, b as invitationProfileName, bt as inspectManagedBlock, c as pendingSessionDisconnects, ct as describeSessionHookChange, d as registerSession, dt as ephemeralCliReason, et as savedReceiver, f as retryPendingDisconnects, ft as findOnPath, g as enrollAgent, gt as repairClaudeHooks, h as staleSessionAgents, ht as readClaudeSettings, i as connectedProfilesForSession, it as recordHookMailCheck, j as NETWORK_ADMISSION_PENDING_RETRY_MS, k as waitForRegistrations, l as psProcessInfo, lt as describeSessionHookItem, m as runDetached, mt as inspectClaudeHooks, n as OMP_MAIN, nt as releaseRegistrationsForProfile, o as headlessClaudeRun, ot as claudeConfigDir, p as revokeStaleSessionAgents, pt as hasMachineSessionHooks, q as agentConnectionStatus, r as agentConnectionState, rt as installClaudeWakeHook, st as currentCliLocation, t as HOOK_END_GRACE_MS, tt as MACHINE_RUNTIMES, u as readClaudeHookInput, ut as ensureHookLauncher, v as connectWarnings, vt as writeClaudeSettings, w as externalReceivingState, wt as writeManagedFile, x as pollCheckCommand, xt as jsonIndentation, y as defaultAgentProfileName, yt as MachineFileError, z as disconnectAgent } from "../machine-session-B6V5-grn.js";
|
|
7
|
+
import { a as readBeforeDeadline, c as readPresenceProjection, i as readApiBeforeDeadline, n as isPlainChatReply, o as scopedChatSenderTrust, r as isScopedChatReply, s as presenceDisposition, t as isConnectedChatCredential } from "../scoped-chat-DAiOb622.js";
|
|
8
|
+
import { c as startBackgroundListen, i as backgroundListenToken, l as stopBackgroundListen, o as runBackgroundListen, r as backgroundListenStatus, u as verifyBackgroundListenTarget } from "../listen-background-cD6n8VYO.js";
|
|
9
|
+
import { a as ensureSharedMailSubscription, c as isConnectedAgentKey, d as reserveSharedMailSubscription, f as followEmailConversation, g as EventReceiverError, h as EventConnection, i as sharedMailScope, l as createSharedMailWaiter, m as otherParticipants, n as reconcileChatSend, p as readConversationFollow, r as openSharedMailReceiver, s as AGENT_CONNECTION_REQUIRED_MESSAGE, t as sendSignal, u as openSharedMailStore } from "../signal-command-Cte4mAL3.js";
|
|
10
10
|
import { autoSignalEligible, dispatchAutoRead, dispatchAutoWorking, haltAutoWorking, isSentSignal, readWorkingLease, restoreAutoWorking } from "./auto-signals.js";
|
|
11
11
|
import { n as jsonOutputRequested, r as runStreamingJson, t as guardJsonOutputCommands } from "../json-output-Ce10AsSb.js";
|
|
12
12
|
import { Args, Command, Errors, Flags, ux } from "@oclif/core";
|
|
@@ -40006,7 +40006,7 @@ const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
|
40006
40006
|
503,
|
|
40007
40007
|
504
|
|
40008
40008
|
]);
|
|
40009
|
-
function sleep$
|
|
40009
|
+
function sleep$2(ms) {
|
|
40010
40010
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
40011
40011
|
}
|
|
40012
40012
|
/**
|
|
@@ -40028,7 +40028,7 @@ async function retryingFetch(url, init, label, maxRetries = DEFAULT_MAX_RETRIES)
|
|
|
40028
40028
|
throw new Error(`${label}: network error after ${attempt} retries: ${networkError?.message ?? networkError}`);
|
|
40029
40029
|
}
|
|
40030
40030
|
if (res) await res.text().catch(() => {});
|
|
40031
|
-
await sleep$
|
|
40031
|
+
await sleep$2(Math.min(1e3 * 2 ** attempt, 15e3) + Math.floor(Math.random() * 250));
|
|
40032
40032
|
}
|
|
40033
40033
|
}
|
|
40034
40034
|
async function initiate(opts, manifest) {
|
|
@@ -40371,7 +40371,7 @@ function withClearedNote(message, cleared) {
|
|
|
40371
40371
|
return `${message} Before that, this run cleared ${cleared.map((row) => `${row.address} (profile ${row.profile})`).join(", ")} on this machine because Primitive confirmed ${cleared.length === 1 ? "its credential was" : "their credentials were"} already revoked; ${cleared.length === 1 ? "it stays" : "they stay"} disconnected.`;
|
|
40372
40372
|
}
|
|
40373
40373
|
var AgentConnectCommand = class AgentConnectCommand extends Command {
|
|
40374
|
-
static description = `Connect this coding session to Primitive in one call. With --session, the command checks this CLI's capabilities, installs or refreshes the matching primitive-connect skill for the detected runtime (Claude Code or Codex) from files bundled in this CLI, claims the owner's private setup invitation from piped stdin, answers one authenticated setup challenge, enables owner notifications, starts receiving, seeds a private AGENT_INFO note from --name and --info when absent, writes a private AGENT_RESUME note holding the command that resumes the bound session ("claude --resume <session>" or "codex resume <session>") once verified (or deletes an existing one when no resumable session is bound), and prints one JSON result. The receiver defaults to external hooks in Claude Code and the native background listener elsewhere. When the native default cannot work because this machine has no local session socket (for example Codex older than 0.158.0), the command connects with poll receiving instead and reports receiving.fallbackReason and receiving.fallbackDetail. Without a session (no --session, or an empty one) the receiver is poll: for runtimes with no local session ID or hooks, such as a cloud-hosted conversation whose commands run in a separate sandbox, the invitation is claimed and verified the same way, nothing is installed, and the result's receiving.checkCommand is the command the agent runs to check for new mail at the start of each turn and after it sends. Native receiving starts or reuses a supervised background listener and waits briefly for its first successful mail check; external receiving installs the exact Claude session's fail-open Stop hook and resume SessionStart hook after the verification reply; a real idle mail event must still verify wake. The profile defaults to session-<session>, or to connection-<invitation hash prefix> without a session. --resume continues saved progress without reading stdin or replaying a claim or uncertain verification send. After the reply is submitted it waits up to a minute for the server to confirm the connection and reports verification.state verified, or reply_submitted while confirmation is still pending. Verification is separate from receiver health. Both official production (https://api.primitive.dev/v1) and staging (https://api.primitive-staging-1.com/v1) invitations pin their API origin. Never pass an invitation as a command argument. One address per session: an address whose credential Primitive confirms was already revoked (for example, revoked in the app before the owner reconnected it) no longer counts; it is disconnected locally, reported in clearedRevoked, and its saved setup is moved aside so the new invitation can be claimed into the same profile. Otherwise, if this session already has a connected Primitive address, including one saved in the profile being connected, the command claims nothing, leaves stdin unread, and exits 3 with status already_connected. Its detail names how to continue that profile's own connection when that is the only one: --resume for a setup with saved progress, or --keep-existing to refresh a claim-only profile from the same invitation. Otherwise ask the user whether to keep the existing address or disconnect it first, then rerun with --keep-existing or --replace-existing. Never choose for the user. Select the saved identity with PRIMITIVE_AGENT_PROFILE. Use --status --json with --profile, with PRIMITIVE_AGENT_PROFILE set, or with --session (the address connected for that session) to inspect saved identity and local receiver health offline; it exits 1 when nothing is configured. A status check before connecting is optional: connect itself claims nothing and exits 3 when the session already has an address. A completed connection's result carries nextSteps, in order: report to the owner in two short sentences, make one offer to rename a generated name and record where the agent runs, and load the primitive-connect skill before handling mail. When the claim reported one, the result also carries the connection's name and nameIsDefault.`;
|
|
40374
|
+
static description = `Connect this coding session to Primitive in one call. With --session, the command checks this CLI's capabilities, installs or refreshes the matching primitive-connect skill for the detected runtime (Claude Code or Codex) from files bundled in this CLI, claims the owner's private setup invitation from piped stdin, answers one authenticated setup challenge, enables owner notifications, starts receiving, seeds a private AGENT_INFO note from --name and --info when absent, writes a private AGENT_RESUME note holding the command that resumes the bound session ("claude --resume <session>" or "codex resume <session>") once verified (or deletes an existing one when no resumable session is bound), and prints one JSON result. The receiver defaults to external hooks in Claude Code and the native background listener elsewhere. When the native default cannot work because this machine has no local session socket (for example Codex older than 0.158.0), the command connects with poll receiving instead and reports receiving.fallbackReason and receiving.fallbackDetail. Without a session (no --session, or an empty one) the receiver is poll: for runtimes with no local session ID or hooks, such as a cloud-hosted conversation whose commands run in a separate sandbox, the invitation is claimed and verified the same way, nothing is installed, and the result's receiving.checkCommand is the command the agent runs to check for new mail at the start of each turn and after it sends. Native receiving starts or reuses a supervised background listener and waits briefly for its first successful mail check; external receiving installs the exact Claude session's fail-open Stop hook and resume SessionStart hook after the verification reply; a real idle mail event must still verify wake. The profile defaults to session-<session>, or to connection-<invitation hash prefix> without a session. --resume continues saved progress without reading stdin or replaying a claim or uncertain verification send. After the reply is submitted it waits up to a minute for the server to confirm the connection and reports verification.state verified, or reply_submitted while confirmation is still pending. Verification is separate from receiver health. Both official production (https://api.primitive.dev/v1) and staging (https://api.primitive-staging-1.com/v1) invitations pin their API origin. Never pass an invitation as a command argument. One address per session: an address whose credential Primitive confirms was already revoked (for example, revoked in the app before the owner reconnected it) no longer counts; it is disconnected locally, reported in clearedRevoked, and its saved setup is moved aside so the new invitation can be claimed into the same profile. A profile whose earlier setup or enrollment never saved a credential is not an address: a pasted invitation is claimed into it (its unfinished state is moved aside), and the result's replacedUnfinished says whether that finished the same pending address. Otherwise, if this session already has a connected Primitive address, including one saved in the profile being connected, the command claims nothing, leaves stdin unread, and exits 3 with status already_connected. Its detail names how to continue that profile's own connection when that is the only one: --resume for a setup with saved progress, or --keep-existing to refresh a claim-only profile from the same invitation. Otherwise ask the user whether to keep the existing address or disconnect it first, then rerun with --keep-existing or --replace-existing. Never choose for the user. Select the saved identity with PRIMITIVE_AGENT_PROFILE. Use --status --json with --profile, with PRIMITIVE_AGENT_PROFILE set, or with --session (the address connected for that session) to inspect saved identity and local receiver health offline; it exits 1 when nothing is configured, and reports the session's last registration failure as registration when one was recorded. A status check before connecting is optional: connect itself claims nothing and exits 3 when the session already has an address. A completed connection's result carries nextSteps, in order: report to the owner in two short sentences, make one offer to rename a generated name and record where the agent runs, and load the primitive-connect skill before handling mail. When the claim reported one, the result also carries the connection's name and nameIsDefault.`;
|
|
40375
40375
|
static summary = "Connect and verify an agent address";
|
|
40376
40376
|
static examples = [
|
|
40377
40377
|
"<%= config.bin %> agent connect --session \"$CODEX_THREAD_ID\" --name Research --info \"Reviews pull requests\" --json < private-invitation.txt",
|
|
@@ -40444,12 +40444,14 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
|
|
|
40444
40444
|
});
|
|
40445
40445
|
if (picked.profile === null && picked.session === null) throw new AgentConnectionSetupError(`Pass --profile <name>, --session <id> or set ${AGENT_PROFILE_ENV}.`);
|
|
40446
40446
|
if (picked.profile === null) {
|
|
40447
|
-
const
|
|
40447
|
+
const registration = picked.bound.length === 0 && picked.session ? registrationSummary(this.config.configDir, defaultAgentProfileName(picked.session)) : null;
|
|
40448
|
+
const detail = registration ? `${statusProfileMissingDetail(picked.bound)} ${registrationSummaryLine(registration)}` : statusProfileMissingDetail(picked.bound);
|
|
40448
40449
|
this.log(flags.json ? JSON.stringify({
|
|
40449
40450
|
status: "not_configured",
|
|
40450
40451
|
profileName: null,
|
|
40451
40452
|
session: picked.session,
|
|
40452
40453
|
...picked.bound.length > 1 ? { profiles: picked.bound } : {},
|
|
40454
|
+
...registration ? { registration } : {},
|
|
40453
40455
|
detail
|
|
40454
40456
|
}) : detail);
|
|
40455
40457
|
process.exitCode = 1;
|
|
@@ -40457,20 +40459,25 @@ var AgentConnectCommand = class AgentConnectCommand extends Command {
|
|
|
40457
40459
|
}
|
|
40458
40460
|
const profileName = picked.profile;
|
|
40459
40461
|
const result = agentConnectionStatus(this.config.configDir, profileName);
|
|
40462
|
+
const registration = registrationSummary(this.config.configDir, profileName);
|
|
40460
40463
|
if (result.status !== "configured") {
|
|
40461
|
-
const detail = `Agent profile ${profileName} is not configured. Run \`primitive agent connect\` with the owner's invitation to connect it.`;
|
|
40464
|
+
const detail = registration ? `Agent profile ${profileName} is not configured. ${registrationSummaryLine(registration)}` : `Agent profile ${profileName} is not configured. Run \`primitive agent connect\` with the owner's invitation to connect it.`;
|
|
40462
40465
|
this.log(flags.json ? JSON.stringify({
|
|
40463
40466
|
...result,
|
|
40467
|
+
...registration ? { registration } : {},
|
|
40464
40468
|
detail
|
|
40465
40469
|
}) : detail);
|
|
40466
40470
|
process.exitCode = 1;
|
|
40467
40471
|
return;
|
|
40468
40472
|
}
|
|
40469
|
-
if (flags.json) this.log(JSON.stringify(
|
|
40473
|
+
if (flags.json) this.log(JSON.stringify(registration?.lastFailure ? {
|
|
40474
|
+
...result,
|
|
40475
|
+
registration
|
|
40476
|
+
} : result));
|
|
40470
40477
|
else {
|
|
40471
40478
|
const receiving = result.receiving;
|
|
40472
40479
|
const why = [receiving.reason, receiving.failureCode !== receiving.reason ? receiving.failureCode : null].filter(Boolean);
|
|
40473
|
-
this.log(`Agent profile ${profileName} is configured for ${result.identity.agentAddress}. Local receiving: ${receiving.state}${why.length ? ` (${why.join(", ")})` : ""}.${receiving.detail ? ` ${receiving.detail}` : ""}${receiving.lastFiredAt ? ` Receive hook last ran at ${receiving.lastFiredAt}.` : ""}${receiving.lastSuccessfulMailCheckAt ? ` Last completed mail check at ${receiving.lastSuccessfulMailCheckAt}.` : ""}`);
|
|
40480
|
+
this.log(`Agent profile ${profileName} is configured for ${result.identity.agentAddress}. Local receiving: ${receiving.state}${why.length ? ` (${why.join(", ")})` : ""}.${receiving.detail ? ` ${receiving.detail}` : ""}${receiving.lastFiredAt ? ` Receive hook last ran at ${receiving.lastFiredAt}.` : ""}${receiving.lastSuccessfulMailCheckAt ? ` Last completed mail check at ${receiving.lastSuccessfulMailCheckAt}.` : ""}${registration?.lastFailure ? ` ${registrationSummaryLine(registration)}` : ""}`);
|
|
40474
40481
|
}
|
|
40475
40482
|
return;
|
|
40476
40483
|
}
|
|
@@ -40855,7 +40862,7 @@ function invocation(entry) {
|
|
|
40855
40862
|
}
|
|
40856
40863
|
var AgentEnrollCommand = class AgentEnrollCommand extends Command {
|
|
40857
40864
|
static summary = "Give this coding session an address in your organization";
|
|
40858
|
-
static description = `On this trusted machine, use the saved member OAuth login to create one address for the exact loaded session, claim its one-use invitation privately, answer the email challenge, and poll the owner's connection list for confirmed pairing. Native receiving starts when supported. With --receiver external in the exact Claude session, install a fail-open Stop hook and resume SessionStart hook in that runtime's settings after the verification reply; idle wake remains unverified until tested with real mail. With --contact-requests, the login conditionally enables first-contact intake for this exact address after verification, preserving existing policy rules. An explicit disable or uncertain policy update is reported separately without losing pairing or receiver setup. The server allocates a readable address. An uncertain creation resumes the same saved request; recovered results do not include another invitation. Use --continue-setup once to explicitly continue a recovered pending connection. An uncertain continuation
|
|
40865
|
+
static description = `On this trusted machine, use the saved member OAuth login to create one address for the exact loaded session, claim its one-use invitation privately, answer the email challenge, and poll the owner's connection list for confirmed pairing. Native receiving starts when supported. With --receiver external in the exact Claude session, install a fail-open Stop hook and resume SessionStart hook in that runtime's settings after the verification reply; idle wake remains unverified until tested with real mail. With --contact-requests, the login conditionally enables first-contact intake for this exact address after verification, preserving existing policy rules. An explicit disable or uncertain policy update is reported separately without losing pairing or receiver setup. The server allocates a readable address. An uncertain creation resumes the same saved request; recovered results do not include another invitation. Use --continue-setup once to explicitly continue a recovered pending connection. An uncertain continuation is held for owner recovery, never repeated automatically. When a claim left no saved credential (it was never sent, was refused, or its answer was lost), a rerun signed in as the agent's owner in the same organization asks Primitive for one fresh invitation for the same address and claims it; without that login the refusal names the step that finishes it from the app. Each attempt is recorded, without secrets, in the profile's registration log, which agent connect --status reports. Requires a verified Primitive-managed domain. This command does not accept API keys, a connection profile, or an invitation argument. One address per session: if this session already has another connected Primitive address, nothing is created and the command exits 3 with status already_connected; ask the user whether to keep the existing address or disconnect it first, then rerun with --keep-existing or --replace-existing. Never choose for the user.`;
|
|
40859
40866
|
static examples = ["<%= config.bin %> agent enroll --session 11111111-1111-4111-8111-111111111111 --name Research --contact-requests --json", "<%= config.bin %> agent enroll --session 11111111-1111-4111-8111-111111111111 --receiver external --name Research --json"];
|
|
40860
40867
|
static flags = {
|
|
40861
40868
|
session: Flags.string({
|
|
@@ -41002,7 +41009,7 @@ var AgentEnrollCommand = class AgentEnrollCommand extends Command {
|
|
|
41002
41009
|
}
|
|
41003
41010
|
if (externalHook === "unavailable" || flags["contact-requests"] && result.contactRequestPolicy !== "enabled" || result.connection.status !== "connected" || flags.receiver !== "external" && flags.receiver !== "poll" && result.receiving.state !== "healthy") process.exitCode = 2;
|
|
41004
41011
|
} catch (error) {
|
|
41005
|
-
throw new Errors.CLIError(error instanceof AgentConnectionSetupError ? error.message : "Enrollment
|
|
41012
|
+
throw new Errors.CLIError(error instanceof AgentConnectionSetupError ? error.message : "Enrollment stopped on an unexpected error. Rerun the same command to resume this session's saved enrollment; it never requests a second address.", { exit: 1 });
|
|
41006
41013
|
}
|
|
41007
41014
|
}
|
|
41008
41015
|
};
|
|
@@ -42601,7 +42608,7 @@ function searchFailure(response) {
|
|
|
42601
42608
|
}
|
|
42602
42609
|
/** Inspect an exact email ID obtained from a targeted query or event journal. */
|
|
42603
42610
|
async function inspectTargetedReply(params) {
|
|
42604
|
-
const result = await
|
|
42611
|
+
const result = await readApiBeforeDeadline(params.deadline, (signal) => getEmail({
|
|
42605
42612
|
client: params.apiClient.client,
|
|
42606
42613
|
path: { id: params.id },
|
|
42607
42614
|
signal,
|
|
@@ -42609,7 +42616,8 @@ async function inspectTargetedReply(params) {
|
|
|
42609
42616
|
}));
|
|
42610
42617
|
if (result === null) return null;
|
|
42611
42618
|
const email = result.data?.data;
|
|
42612
|
-
if (result.error
|
|
42619
|
+
if (result.error) throw invalid$2(`Could not inspect reply candidate ${params.id}: ${describeApiFailure(result)}.`);
|
|
42620
|
+
if (!email || email.id !== params.id) throw invalid$2(`Could not inspect reply candidate ${params.id}.`);
|
|
42613
42621
|
const presence = presenceDisposition(email);
|
|
42614
42622
|
if (presence !== "ordinary") return {
|
|
42615
42623
|
kind: presence === "pending" ? "pending" : "unrelated",
|
|
@@ -42685,7 +42693,7 @@ async function inspectTargetedReply(params) {
|
|
|
42685
42693
|
/** One targeted recovery page. The event owner decides when recovery is needed. */
|
|
42686
42694
|
async function readTargetedReplyPage(params) {
|
|
42687
42695
|
if (!params.sentId || !params.from || !params.recipient) throw invalid$2("Targeted reply recovery requires the exact sent email, receiving address, and peer.");
|
|
42688
|
-
const result = await
|
|
42696
|
+
const result = await readApiBeforeDeadline(params.deadline, (signal) => searchEmails({
|
|
42689
42697
|
client: params.apiClient.client,
|
|
42690
42698
|
signal,
|
|
42691
42699
|
query: {
|
|
@@ -42715,6 +42723,11 @@ async function readTargetedReplyPage(params) {
|
|
|
42715
42723
|
}
|
|
42716
42724
|
//#endregion
|
|
42717
42725
|
//#region src/oclif/connected-reply-wait.ts
|
|
42726
|
+
/**
|
|
42727
|
+
* Slack for clock differences between this machine, which records when a
|
|
42728
|
+
* wait was created, and the server, which records when mail was received.
|
|
42729
|
+
*/
|
|
42730
|
+
const REPLY_CLOCK_SKEW_MS = 6e5;
|
|
42718
42731
|
function recoveryEventId(id) {
|
|
42719
42732
|
const h = createHash("sha256").update(`reply-recovery:${id}`).digest("hex");
|
|
42720
42733
|
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
@@ -42802,6 +42815,20 @@ async function openConnectedReplyWait(options) {
|
|
|
42802
42815
|
let closed = false;
|
|
42803
42816
|
let closing;
|
|
42804
42817
|
const timedOut = () => options.deadline != null && Date.now() >= options.deadline;
|
|
42818
|
+
let earliestReply;
|
|
42819
|
+
/**
|
|
42820
|
+
* The earliest receipt time a reply to this wait's send can have: the wait
|
|
42821
|
+
* is registered before the send, so mail received well before it is
|
|
42822
|
+
* retained history, not a reply.
|
|
42823
|
+
*/
|
|
42824
|
+
async function replyFloor() {
|
|
42825
|
+
if (earliestReply !== void 0) return earliestReply;
|
|
42826
|
+
const intent = await store.readWait(requestId);
|
|
42827
|
+
const created = Date.parse(intent?.createdAt ?? "");
|
|
42828
|
+
const earlier = [options.sentAt, options.since].map((value) => Date.parse(value ?? "")).filter(Number.isFinite);
|
|
42829
|
+
earliestReply = Number.isFinite(created) ? Math.min(created, ...earlier) - REPLY_CLOCK_SKEW_MS : Number.NEGATIVE_INFINITY;
|
|
42830
|
+
return earliestReply;
|
|
42831
|
+
}
|
|
42805
42832
|
async function release() {
|
|
42806
42833
|
if (released) return;
|
|
42807
42834
|
await (await openSharedMailStore({
|
|
@@ -42892,6 +42919,7 @@ async function openConnectedReplyWait(options) {
|
|
|
42892
42919
|
requestId = bound.requestId;
|
|
42893
42920
|
sentId = bound.sentEmailId ?? void 0;
|
|
42894
42921
|
recoveryNeeded = true;
|
|
42922
|
+
earliestReply = void 0;
|
|
42895
42923
|
},
|
|
42896
42924
|
uncertain: () => store.markWaitUncertain(requestId),
|
|
42897
42925
|
async cancelBeforeSend() {
|
|
@@ -42981,7 +43009,9 @@ async function openConnectedReplyWait(options) {
|
|
|
42981
43009
|
limit: options.pageSize,
|
|
42982
43010
|
cursor: localCursor
|
|
42983
43011
|
});
|
|
43012
|
+
const floor = await replyFloor();
|
|
42984
43013
|
for (const row of local.emails) {
|
|
43014
|
+
if (!row.details && Date.parse(row.receivedAt) < floor) continue;
|
|
42985
43015
|
if (row.details && row.details.authorization !== "pending" && (row.details.peer !== options.recipient || row.details.replyToSentEmailId !== sentId)) continue;
|
|
42986
43016
|
const reply = await inspect(row.emailId);
|
|
42987
43017
|
if (reply) return reply;
|
|
@@ -43658,132 +43688,6 @@ function warnIfSharedProfile(params) {
|
|
|
43658
43688
|
const warning = sharedProfileWarning(params);
|
|
43659
43689
|
if (warning) process.stderr.write(`Warning: ${warning}\n`);
|
|
43660
43690
|
}
|
|
43661
|
-
//#endregion
|
|
43662
|
-
//#region src/oclif/commands/emails-poll.ts
|
|
43663
|
-
function quoteDslValue(value) {
|
|
43664
|
-
if (/^[^\s"]+$/.test(value)) return value;
|
|
43665
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
43666
|
-
}
|
|
43667
|
-
function combineQ(q, domain) {
|
|
43668
|
-
const parts = [q?.trim(), domain ? `domain:${quoteDslValue(domain.trim())}` : void 0].filter((part) => Boolean(part));
|
|
43669
|
-
return parts.length > 0 ? parts.join(" ") : void 0;
|
|
43670
|
-
}
|
|
43671
|
-
function normalizeIsoDate(value, label) {
|
|
43672
|
-
const parsed = new Date(value);
|
|
43673
|
-
if (Number.isNaN(parsed.getTime())) throw new Error(`${label} must be a valid date or ISO-8601 timestamp.`);
|
|
43674
|
-
return parsed.toISOString();
|
|
43675
|
-
}
|
|
43676
|
-
function filtersFromFlags(flags) {
|
|
43677
|
-
if (flags.automated !== void 0 && !AUTOMATED_VALUES.includes(flags.automated)) throw new Error("--automated must be true or false.");
|
|
43678
|
-
return {
|
|
43679
|
-
automated: flags.automated === void 0 ? void 0 : flags.automated === "true",
|
|
43680
|
-
awaiting: flags.awaiting,
|
|
43681
|
-
body: flags.body,
|
|
43682
|
-
domain: flags.domain,
|
|
43683
|
-
domainId: flags["domain-id"],
|
|
43684
|
-
from: flags.from,
|
|
43685
|
-
hasAttachment: flags["has-attachment"],
|
|
43686
|
-
q: flags.q,
|
|
43687
|
-
replyToSentEmailId: flags["reply-to-sent-email-id"],
|
|
43688
|
-
spamScoreGte: flags["spam-score-gte"],
|
|
43689
|
-
spamScoreLt: flags["spam-score-lt"],
|
|
43690
|
-
subject: flags.subject,
|
|
43691
|
-
to: flags.to
|
|
43692
|
-
};
|
|
43693
|
-
}
|
|
43694
|
-
function sinceFromFlags(flags) {
|
|
43695
|
-
if (flags.since) return normalizeIsoDate(flags.since, "--since");
|
|
43696
|
-
return flags["include-existing"] ? void 0 : (/* @__PURE__ */ new Date()).toISOString();
|
|
43697
|
-
}
|
|
43698
|
-
function buildEmailSearchQuery(params) {
|
|
43699
|
-
const query = {
|
|
43700
|
-
include_facets: "false",
|
|
43701
|
-
limit: params.pageSize,
|
|
43702
|
-
snippet: "false",
|
|
43703
|
-
sort: "received_at_asc"
|
|
43704
|
-
};
|
|
43705
|
-
const q = combineQ(params.filters.q, params.filters.domain);
|
|
43706
|
-
if (q) query.q = q;
|
|
43707
|
-
if (params.filters.awaiting) query.awaiting = params.filters.awaiting;
|
|
43708
|
-
if (params.filters.automated !== void 0) query.automated = params.filters.automated ? "true" : "false";
|
|
43709
|
-
if (params.filters.body) query.body = params.filters.body;
|
|
43710
|
-
if (params.filters.domainId) query.domain_id = params.filters.domainId;
|
|
43711
|
-
if (params.filters.from) query.from = params.filters.from;
|
|
43712
|
-
if (params.filters.hasAttachment !== void 0) query.has_attachment = params.filters.hasAttachment ? "true" : "false";
|
|
43713
|
-
if (params.filters.spamScoreGte !== void 0) query.spam_score_gte = params.filters.spamScoreGte;
|
|
43714
|
-
if (params.filters.spamScoreLt !== void 0) query.spam_score_lt = params.filters.spamScoreLt;
|
|
43715
|
-
if (params.filters.replyToSentEmailId) query.reply_to_sent_email_id = params.filters.replyToSentEmailId;
|
|
43716
|
-
if (params.filters.subject) query.subject = params.filters.subject;
|
|
43717
|
-
if (params.filters.to) query.to = params.filters.to;
|
|
43718
|
-
if (params.since) query.date_from = params.since;
|
|
43719
|
-
if (params.cursor) query.cursor = params.cursor;
|
|
43720
|
-
return query;
|
|
43721
|
-
}
|
|
43722
|
-
function encodeReceivedAtSearchCursor(email) {
|
|
43723
|
-
const raw = `r|${new Date(email.received_at).toISOString()}|${email.id}`;
|
|
43724
|
-
return Buffer.from(raw, "utf8").toString("base64url");
|
|
43725
|
-
}
|
|
43726
|
-
function cursorFromRows(rows) {
|
|
43727
|
-
const last = rows.at(-1);
|
|
43728
|
-
return last ? encodeReceivedAtSearchCursor(last) : null;
|
|
43729
|
-
}
|
|
43730
|
-
function cursorFromAcceptedRows(rows) {
|
|
43731
|
-
for (let i = rows.length - 1; i >= 0; i--) {
|
|
43732
|
-
const row = rows[i];
|
|
43733
|
-
if (row.status === "accepted" || row.status === "completed") return encodeReceivedAtSearchCursor(row);
|
|
43734
|
-
}
|
|
43735
|
-
return null;
|
|
43736
|
-
}
|
|
43737
|
-
function collectNewAcceptedEmails(rows, seenIds) {
|
|
43738
|
-
const fresh = [];
|
|
43739
|
-
for (const row of rows) {
|
|
43740
|
-
if (row.status !== "accepted" && row.status !== "completed") continue;
|
|
43741
|
-
if (seenIds.has(row.id)) continue;
|
|
43742
|
-
seenIds.add(row.id);
|
|
43743
|
-
fresh.push(row);
|
|
43744
|
-
}
|
|
43745
|
-
return fresh;
|
|
43746
|
-
}
|
|
43747
|
-
async function fetchEmailSearchPage(params) {
|
|
43748
|
-
const result = await searchEmails({
|
|
43749
|
-
client: params.apiClient.client,
|
|
43750
|
-
query: buildEmailSearchQuery({
|
|
43751
|
-
cursor: params.cursor,
|
|
43752
|
-
filters: params.filters,
|
|
43753
|
-
pageSize: params.pageSize,
|
|
43754
|
-
since: params.since
|
|
43755
|
-
}),
|
|
43756
|
-
responseStyle: "fields"
|
|
43757
|
-
});
|
|
43758
|
-
const wantsReplyState = Boolean(params.filters.awaiting) || queryUsesAwaiting(params.filters.q);
|
|
43759
|
-
const wantsAutomated = params.filters.automated !== void 0 || queryUsesAutomated(params.filters.q);
|
|
43760
|
-
if (result.error) {
|
|
43761
|
-
if (wantsReplyState && isAwaitingRejectedError(result.error)) throw awaitingRejectedError("GET /emails/search");
|
|
43762
|
-
if (wantsAutomated && isAutomatedRejectedError(result.error)) throw automatedRejectedError("GET /emails/search");
|
|
43763
|
-
return {
|
|
43764
|
-
ok: false,
|
|
43765
|
-
error: result.error
|
|
43766
|
-
};
|
|
43767
|
-
}
|
|
43768
|
-
const envelope = result.data;
|
|
43769
|
-
const rows = envelope?.data ?? [];
|
|
43770
|
-
if (wantsReplyState) {
|
|
43771
|
-
assertAwaitingFilterRows(rows, "GET /emails/search");
|
|
43772
|
-
if (rows.length === 0) await ensureSearchReportsReplyState(params.apiClient);
|
|
43773
|
-
}
|
|
43774
|
-
if (wantsAutomated) {
|
|
43775
|
-
assertAutomatedVerdict(rows, "GET /emails/search", expectedAutomatedVerdicts(params.filters.automated, params.filters.q));
|
|
43776
|
-
if (rows.length === 0) await ensureSearchReportsAutomated(params.apiClient);
|
|
43777
|
-
}
|
|
43778
|
-
return {
|
|
43779
|
-
ok: true,
|
|
43780
|
-
cursor: envelope?.meta.cursor ?? cursorFromRows(rows),
|
|
43781
|
-
rows
|
|
43782
|
-
};
|
|
43783
|
-
}
|
|
43784
|
-
function sleep$2(ms) {
|
|
43785
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
43786
|
-
}
|
|
43787
43691
|
function cliError$6(message) {
|
|
43788
43692
|
return new Errors.CLIError(message, { exit: 1 });
|
|
43789
43693
|
}
|
|
@@ -45399,10 +45303,23 @@ async function waitForReply(params) {
|
|
|
45399
45303
|
deadline: totalDeadline
|
|
45400
45304
|
});
|
|
45401
45305
|
let strictFilterUnsupported = false;
|
|
45306
|
+
let notBefore = 0;
|
|
45307
|
+
const holdRetryAfter = (failure) => {
|
|
45308
|
+
if (failure.retryAfterMs !== void 0) notBefore = Math.max(notBefore, Date.now() + failure.retryAfterMs);
|
|
45309
|
+
};
|
|
45402
45310
|
for (const phase of phases) {
|
|
45403
45311
|
if (phase.label === "strict" && strictFilterUnsupported) continue;
|
|
45312
|
+
if (notBefore > Date.now()) {
|
|
45313
|
+
const remaining = totalDeadline === null ? Number.POSITIVE_INFINITY : totalDeadline - Date.now();
|
|
45314
|
+
if (remaining <= notBefore - Date.now()) {
|
|
45315
|
+
await sleep$3(Math.max(0, remaining));
|
|
45316
|
+
return null;
|
|
45317
|
+
}
|
|
45318
|
+
await sleep$3(notBefore - Date.now());
|
|
45319
|
+
}
|
|
45404
45320
|
const seenIds = /* @__PURE__ */ new Set();
|
|
45405
45321
|
let cursor = null;
|
|
45322
|
+
let failures = 0;
|
|
45406
45323
|
while (true) {
|
|
45407
45324
|
if (phase.deadline !== null && Date.now() >= phase.deadline) break;
|
|
45408
45325
|
const page = await fetchEmailSearchPage({
|
|
@@ -45413,14 +45330,25 @@ async function waitForReply(params) {
|
|
|
45413
45330
|
since: params.sentAtIso
|
|
45414
45331
|
});
|
|
45415
45332
|
if (!page.ok) {
|
|
45333
|
+
const failure = transientFailure(page);
|
|
45334
|
+
if (failure) {
|
|
45335
|
+
if (!await pauseForTransientFailure(failure, failures, {
|
|
45336
|
+
deadline: phase.deadline,
|
|
45337
|
+
notice,
|
|
45338
|
+
onTransientFailure: holdRetryAfter
|
|
45339
|
+
})) break;
|
|
45340
|
+
failures += 1;
|
|
45341
|
+
continue;
|
|
45342
|
+
}
|
|
45416
45343
|
const payload = extractErrorPayload(page.error);
|
|
45417
|
-
writeErrorWithHints(payload);
|
|
45344
|
+
writeErrorWithHints(payload, page.response);
|
|
45418
45345
|
surfaceUnauthorizedHint({
|
|
45419
45346
|
...params.authFailureContext,
|
|
45420
45347
|
payload
|
|
45421
45348
|
});
|
|
45422
45349
|
throw new Errors.CLIError("Failed to poll for reply.", { exit: 1 });
|
|
45423
45350
|
}
|
|
45351
|
+
failures = 0;
|
|
45424
45352
|
let lastAccepted;
|
|
45425
45353
|
for (let i = page.rows.length - 1; i >= 0; i--) {
|
|
45426
45354
|
const row = page.rows[i];
|
|
@@ -45432,14 +45360,19 @@ async function waitForReply(params) {
|
|
|
45432
45360
|
if (lastAccepted) cursor = encodeReceivedAtSearchCursor(lastAccepted);
|
|
45433
45361
|
const matches = collectNewAcceptedEmails(page.rows, seenIds);
|
|
45434
45362
|
for (const match of matches) {
|
|
45435
|
-
const full = await getEmail({
|
|
45363
|
+
const full = await retryTransient(() => getEmail({
|
|
45436
45364
|
client: params.apiClient.client,
|
|
45437
45365
|
path: { id: match.id },
|
|
45438
45366
|
responseStyle: "fields"
|
|
45367
|
+
}), {
|
|
45368
|
+
deadline: phase.deadline,
|
|
45369
|
+
notice,
|
|
45370
|
+
onTransientFailure: holdRetryAfter
|
|
45439
45371
|
});
|
|
45372
|
+
if (full === null) break;
|
|
45440
45373
|
if (full.error) {
|
|
45441
45374
|
const payload = extractErrorPayload(full.error);
|
|
45442
|
-
writeErrorWithHints(payload);
|
|
45375
|
+
writeErrorWithHints(payload, full.response);
|
|
45443
45376
|
surfaceUnauthorizedHint({
|
|
45444
45377
|
...params.authFailureContext,
|
|
45445
45378
|
payload
|
|
@@ -45463,7 +45396,7 @@ async function waitForReply(params) {
|
|
|
45463
45396
|
if (lastAccepted !== void 0) continue;
|
|
45464
45397
|
if (phase.deadline !== null && Date.now() >= phase.deadline) break;
|
|
45465
45398
|
if (totalDeadline !== null && Date.now() >= totalDeadline) return null;
|
|
45466
|
-
await sleep$
|
|
45399
|
+
await sleep$3(params.interval * 1e3);
|
|
45467
45400
|
}
|
|
45468
45401
|
}
|
|
45469
45402
|
return null;
|
|
@@ -47622,7 +47555,7 @@ async function resolveScopedEmailWait(params) {
|
|
|
47622
47555
|
const requestedTo = to === void 0 ? void 0 : exactAddress(to, "--to");
|
|
47623
47556
|
if (Object.values(unsupported).some((value) => value !== void 0 && value !== false)) throw invalid$1("Connected email waits support exact --from, --to, and --reply-to-sent-email-id matching only; search, body, subject, domain, attachment, and spam filters are unavailable.");
|
|
47624
47557
|
const sentId = replyToSentEmailId.toLowerCase();
|
|
47625
|
-
const result = await
|
|
47558
|
+
const result = await readApiBeforeDeadline(params.deadline, (signal) => getSentEmail({
|
|
47626
47559
|
signal,
|
|
47627
47560
|
client: params.apiClient.client,
|
|
47628
47561
|
path: { id: sentId },
|
|
@@ -47630,7 +47563,8 @@ async function resolveScopedEmailWait(params) {
|
|
|
47630
47563
|
}));
|
|
47631
47564
|
if (result === null) return null;
|
|
47632
47565
|
const sent = result.data?.data;
|
|
47633
|
-
if (result.error
|
|
47566
|
+
if (result.error) throw invalid$1(`Could not read that sent email with the connected credential: ${describeApiFailure(result)}. Inspect the existing send before retrying; do not send it again.`);
|
|
47567
|
+
if (!sent || sent.id !== sentId) throw invalid$1("Could not read that sent email with the connected credential. Inspect the existing send before retrying; do not send it again.");
|
|
47634
47568
|
const ownAddress = exactAddress(sent.from_address, "The sent email's from_address");
|
|
47635
47569
|
if (sent.from_header != null && sent.from_header !== "") {
|
|
47636
47570
|
const header = typeof sent.from_header === "string" ? parseFromHeader(sent.from_header) : null;
|
|
@@ -47640,7 +47574,8 @@ async function resolveScopedEmailWait(params) {
|
|
|
47640
47574
|
return {
|
|
47641
47575
|
from: ownAddress,
|
|
47642
47576
|
recipient,
|
|
47643
|
-
sentId
|
|
47577
|
+
sentId,
|
|
47578
|
+
...typeof sent.created_at === "string" ? { sentAt: sent.created_at } : {}
|
|
47644
47579
|
};
|
|
47645
47580
|
}
|
|
47646
47581
|
//#endregion
|
|
@@ -47665,7 +47600,10 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
47665
47600
|
Connected waits share an address event subscription and recover replies with
|
|
47666
47601
|
exact-parent search. They never scan inbox history. Additional content
|
|
47667
47602
|
filters. The command never sends mail. On timeout it exits 1; run the same
|
|
47668
|
-
wait again to recover a reply to the existing send without resending it
|
|
47603
|
+
wait again to recover a reply to the existing send without resending it.
|
|
47604
|
+
A rate limit (HTTP 429, honouring Retry-After), a 502, 503 or 504, or a
|
|
47605
|
+
network error during the wait is retried with backoff until --timeout,
|
|
47606
|
+
with one note on stderr per retry.`;
|
|
47669
47607
|
static summary = "Wait for matching inbound emails";
|
|
47670
47608
|
static examples = [
|
|
47671
47609
|
"<%= config.bin %> emails wait --reply-to-sent-email-id <sent-id> --from peer@example.com",
|
|
@@ -47776,6 +47714,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
47776
47714
|
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
47777
47715
|
const seenIds = /* @__PURE__ */ new Set();
|
|
47778
47716
|
let cursor = null;
|
|
47717
|
+
let failures = 0;
|
|
47779
47718
|
let matched = 0;
|
|
47780
47719
|
let headerPrinted = false;
|
|
47781
47720
|
const printEmail = (email) => {
|
|
@@ -47834,8 +47773,14 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
47834
47773
|
throw error;
|
|
47835
47774
|
}
|
|
47836
47775
|
if (!page.ok) {
|
|
47776
|
+
const failure = transientFailure(page);
|
|
47777
|
+
if (failure) {
|
|
47778
|
+
if (!await pauseForTransientFailure(failure, failures, { deadline })) break;
|
|
47779
|
+
failures += 1;
|
|
47780
|
+
continue;
|
|
47781
|
+
}
|
|
47837
47782
|
const payload = extractErrorPayload(page.error);
|
|
47838
|
-
writeErrorWithHints(payload);
|
|
47783
|
+
writeErrorWithHints(payload, page.response);
|
|
47839
47784
|
surfaceUnauthorizedHint({
|
|
47840
47785
|
auth,
|
|
47841
47786
|
baseUrlOverridden,
|
|
@@ -47845,6 +47790,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
47845
47790
|
process.exitCode = 1;
|
|
47846
47791
|
return;
|
|
47847
47792
|
}
|
|
47793
|
+
failures = 0;
|
|
47848
47794
|
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
47849
47795
|
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
47850
47796
|
if (nextCursor) cursor = nextCursor;
|
|
@@ -47854,7 +47800,7 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
47854
47800
|
}
|
|
47855
47801
|
if (cursorAdvanced) continue;
|
|
47856
47802
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
47857
|
-
await sleep$
|
|
47803
|
+
await sleep$3(flags.interval * 1e3);
|
|
47858
47804
|
}
|
|
47859
47805
|
const message = `Timed out waiting for ${flags.number} matching email${flags.number === 1 ? "" : "s"}; received ${matched}.`;
|
|
47860
47806
|
this.timedOut = {
|
|
@@ -47948,6 +47894,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
47948
47894
|
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
47949
47895
|
const seenIds = /* @__PURE__ */ new Set();
|
|
47950
47896
|
let cursor = null;
|
|
47897
|
+
let failures = 0;
|
|
47951
47898
|
let printed = 0;
|
|
47952
47899
|
let headerPrinted = false;
|
|
47953
47900
|
while (deadline === null || Date.now() < deadline) {
|
|
@@ -47965,8 +47912,14 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
47965
47912
|
throw error;
|
|
47966
47913
|
}
|
|
47967
47914
|
if (!page.ok) {
|
|
47915
|
+
const failure = transientFailure(page);
|
|
47916
|
+
if (failure) {
|
|
47917
|
+
if (!await pauseForTransientFailure(failure, failures, { deadline })) break;
|
|
47918
|
+
failures += 1;
|
|
47919
|
+
continue;
|
|
47920
|
+
}
|
|
47968
47921
|
const payload = extractErrorPayload(page.error);
|
|
47969
|
-
writeErrorWithHints(payload);
|
|
47922
|
+
writeErrorWithHints(payload, page.response);
|
|
47970
47923
|
surfaceUnauthorizedHint({
|
|
47971
47924
|
auth,
|
|
47972
47925
|
baseUrlOverridden,
|
|
@@ -47976,6 +47929,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
47976
47929
|
process.exitCode = 1;
|
|
47977
47930
|
return;
|
|
47978
47931
|
}
|
|
47932
|
+
failures = 0;
|
|
47979
47933
|
const nextCursor = cursorFromAcceptedRows(page.rows);
|
|
47980
47934
|
const cursorAdvanced = Boolean(nextCursor && nextCursor !== cursor);
|
|
47981
47935
|
if (nextCursor) cursor = nextCursor;
|
|
@@ -47993,7 +47947,7 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
47993
47947
|
}
|
|
47994
47948
|
if (cursorAdvanced) continue;
|
|
47995
47949
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
47996
|
-
await sleep$
|
|
47950
|
+
await sleep$3(flags.interval * 1e3);
|
|
47997
47951
|
}
|
|
47998
47952
|
}
|
|
47999
47953
|
};
|
|
@@ -49155,7 +49109,7 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
49155
49109
|
url: "https://primitive.dev"
|
|
49156
49110
|
};
|
|
49157
49111
|
const SDK_VERSION_RANGE = "^2.0.0";
|
|
49158
|
-
const CLI_VERSION_RANGE = "^1.
|
|
49112
|
+
const CLI_VERSION_RANGE = "^1.73.0";
|
|
49159
49113
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
49160
49114
|
function renderHandler() {
|
|
49161
49115
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -50020,7 +49974,7 @@ var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
|
50020
49974
|
return;
|
|
50021
49975
|
}
|
|
50022
49976
|
completedInitialFollowPoll = true;
|
|
50023
|
-
await sleep$
|
|
49977
|
+
await sleep$3(flags["poll-interval"] * 1e3);
|
|
50024
49978
|
}
|
|
50025
49979
|
});
|
|
50026
49980
|
}
|
|
@@ -50931,7 +50885,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
50931
50885
|
trace = fetched;
|
|
50932
50886
|
break;
|
|
50933
50887
|
}
|
|
50934
|
-
await sleep$
|
|
50888
|
+
await sleep$3(pollIntervalMs);
|
|
50935
50889
|
}
|
|
50936
50890
|
if (!trace) {
|
|
50937
50891
|
const finalTrace = ((await getFunctionTestRunTrace({
|
|
@@ -51096,6 +51050,32 @@ const DEFAULT_API = {
|
|
|
51096
51050
|
getEmail,
|
|
51097
51051
|
listEmails
|
|
51098
51052
|
};
|
|
51053
|
+
/**
|
|
51054
|
+
* The API for a wait: a rate limit, gateway failure or network error backs
|
|
51055
|
+
* off and reads again until the deadline (null: no deadline), then throws
|
|
51056
|
+
* WaitDeadlineReached. These are all reads, so a retry repeats nothing.
|
|
51057
|
+
*/
|
|
51058
|
+
function waitingInboxApi(deadline, api = DEFAULT_API) {
|
|
51059
|
+
const options = { deadline };
|
|
51060
|
+
return {
|
|
51061
|
+
...api,
|
|
51062
|
+
getConversation: retryingOperation(api.getConversation, options),
|
|
51063
|
+
getEmail: retryingOperation(api.getEmail, options),
|
|
51064
|
+
listEmails: retryingOperation(api.listEmails, options, (input) => {
|
|
51065
|
+
const query = input.query;
|
|
51066
|
+
if (deadline === null || query?.wait === void 0) return input;
|
|
51067
|
+
const remainingMs = deadline - Date.now();
|
|
51068
|
+
if (remainingMs <= 0) return null;
|
|
51069
|
+
return {
|
|
51070
|
+
...input,
|
|
51071
|
+
query: {
|
|
51072
|
+
...query,
|
|
51073
|
+
wait: Math.min(query.wait, Math.floor(remainingMs / 1e3))
|
|
51074
|
+
}
|
|
51075
|
+
};
|
|
51076
|
+
})
|
|
51077
|
+
};
|
|
51078
|
+
}
|
|
51099
51079
|
async function listPage(api, apiClient, query) {
|
|
51100
51080
|
let result = await api.listEmails({
|
|
51101
51081
|
client: apiClient.client,
|
|
@@ -51415,7 +51395,7 @@ var InboxNextCommand = class InboxNextCommand extends Command {
|
|
|
51415
51395
|
|
|
51416
51396
|
NOT A WORK QUEUE. Nothing is claimed or locked. Two agents running \`inbox next\` on the same inbox get the same email until one of them replies, and both may answer it. Run one agent per inbox, or coordinate outside Primitive.
|
|
51417
51397
|
|
|
51418
|
-
--wait blocks until something awaits you. It takes the inbox's newest position before checking, then long-polls from that position, re-checking reply state whenever mail arrives and at least every 30 seconds, so nothing that arrives between the check and the wait is missed.
|
|
51398
|
+
--wait blocks until something awaits you. It takes the inbox's newest position before checking, then long-polls from that position, re-checking reply state whenever mail arrives and at least every 30 seconds, so nothing that arrives between the check and the wait is missed. A rate limit (HTTP 429, honouring Retry-After), a 502, 503 or 504, or a network error during the wait is retried with backoff until --timeout, with one note on stderr per retry; if it lasts until then, the wait exits 5 as if nothing arrived.
|
|
51419
51399
|
|
|
51420
51400
|
--json prints one stable envelope (version 1): \`outcome\` ("email" | "empty" | "error"), \`email\` (id, thread_id, message_id, received_at, from, from_email, to, subject, awaiting, reply_count, last_replied_at, body_text, from_known_address, auth { spf, dmarc }), \`automated\` ({ automated, reasons[], automation_headers_known }, the server's verdict), \`conversation\` (thread_id, subject, message_count, truncated, messages[] with role user|assistant), \`reply_command\`, \`interaction\` ({ hint, kind, fyi, category, plain_reply_completes, no_reply_needed }, decided by the server from a DKIM-authenticated interaction part; null when not reported), \`next_actions\` (the commands that answer the email, best first: each has kind, command, argv, description, placeholders, requires_message; empty when no reply is needed or this CLI cannot answer the interaction), \`automated_awaiting\` ({ total, capped }: automated mail also awaiting a reply, on the empty outcome; null otherwise), and \`error\` ({ code, message }) on failure.
|
|
51421
51401
|
|
|
@@ -51480,12 +51460,17 @@ var InboxNextCommand = class InboxNextCommand extends Command {
|
|
|
51480
51460
|
process.exitCode = INBOX_NEXT_EXIT_CODES.error;
|
|
51481
51461
|
};
|
|
51482
51462
|
const deadline = flags.wait && flags.timeout > 0 ? Date.now() + flags.timeout * 1e3 : null;
|
|
51463
|
+
const api = flags.wait ? waitingInboxApi(deadline) : void 0;
|
|
51483
51464
|
try {
|
|
51484
|
-
let since = flags.wait ? await baselineCursor({
|
|
51465
|
+
let since = flags.wait ? await baselineCursor({
|
|
51466
|
+
apiClient,
|
|
51467
|
+
api
|
|
51468
|
+
}) : null;
|
|
51485
51469
|
let announcedWait = false;
|
|
51486
51470
|
for (;;) {
|
|
51487
51471
|
const result = await findNextAwaiting({
|
|
51488
51472
|
apiClient,
|
|
51473
|
+
api,
|
|
51489
51474
|
includeAutomated: flags["include-automated"]
|
|
51490
51475
|
});
|
|
51491
51476
|
if (result.outcome === "email" || !flags.wait || since === null) {
|
|
@@ -51509,12 +51494,20 @@ var InboxNextCommand = class InboxNextCommand extends Command {
|
|
|
51509
51494
|
const pollStartedAt = Date.now();
|
|
51510
51495
|
since = await waitForActivity({
|
|
51511
51496
|
apiClient,
|
|
51497
|
+
api,
|
|
51512
51498
|
since,
|
|
51513
51499
|
seconds: Math.floor(remainingMs / 1e3)
|
|
51514
51500
|
});
|
|
51515
51501
|
if (since === before && Date.now() - pollStartedAt < 1e3) await sleep$1(1e3);
|
|
51516
51502
|
}
|
|
51517
51503
|
} catch (error) {
|
|
51504
|
+
if (error instanceof WaitDeadlineReached) {
|
|
51505
|
+
await this.printResult({
|
|
51506
|
+
outcome: "empty",
|
|
51507
|
+
automated_awaiting: null
|
|
51508
|
+
}, bin, flags, apiClient);
|
|
51509
|
+
return;
|
|
51510
|
+
}
|
|
51518
51511
|
if (error instanceof ReplyStateUnsupportedError || error instanceof AutomatedFilterUnsupportedError) {
|
|
51519
51512
|
fail(error.code, error.message);
|
|
51520
51513
|
return;
|
|
@@ -53241,7 +53234,9 @@ async function runStandaloneListen(options) {
|
|
|
53241
53234
|
const random = options.random ?? Math.random;
|
|
53242
53235
|
const sleep = options.sleep ?? listenSleep;
|
|
53243
53236
|
const stderr = options.stderr ?? process.stderr;
|
|
53244
|
-
const
|
|
53237
|
+
const hard = options.signal;
|
|
53238
|
+
const receiving = options.stopReceiving ? AbortSignal.any([hard, options.stopReceiving]) : hard;
|
|
53239
|
+
let signal = receiving;
|
|
53245
53240
|
const mode = options.mode ?? "stdout";
|
|
53246
53241
|
const events = options.events;
|
|
53247
53242
|
if (options.number !== void 0 && (!Number.isSafeInteger(options.number) || options.number < 1)) throw new ListenError("--number must be a positive integer.");
|
|
@@ -53398,6 +53393,7 @@ async function runStandaloneListen(options) {
|
|
|
53398
53393
|
continue;
|
|
53399
53394
|
}
|
|
53400
53395
|
signal.throwIfAborted();
|
|
53396
|
+
signal = hard;
|
|
53401
53397
|
let handled;
|
|
53402
53398
|
try {
|
|
53403
53399
|
handled = await options.handler(delivery, signal);
|
|
@@ -53439,6 +53435,7 @@ async function runStandaloneListen(options) {
|
|
|
53439
53435
|
if (!(error instanceof RequestFailure) || error.status !== 409 || error.code !== "stale_delivery") throw error;
|
|
53440
53436
|
stderr.write("Delivery ownership expired before confirmation; it may be delivered again.\n");
|
|
53441
53437
|
}
|
|
53438
|
+
signal = receiving;
|
|
53442
53439
|
}
|
|
53443
53440
|
} catch (error) {
|
|
53444
53441
|
if (!signal.aborted) throw error;
|
|
@@ -53793,14 +53790,16 @@ async function createWakeMail(options) {
|
|
|
53793
53790
|
kind: "mail",
|
|
53794
53791
|
context
|
|
53795
53792
|
})) return outcome(false);
|
|
53796
|
-
if (admission.kind === "request")
|
|
53797
|
-
|
|
53798
|
-
|
|
53799
|
-
|
|
53800
|
-
|
|
53801
|
-
|
|
53802
|
-
|
|
53803
|
-
|
|
53793
|
+
if (admission.kind === "request") {
|
|
53794
|
+
if (pendingRequest) completePending();
|
|
53795
|
+
pendingRequest = {
|
|
53796
|
+
sender,
|
|
53797
|
+
emailId: detail.id,
|
|
53798
|
+
eventId,
|
|
53799
|
+
decidedSenders: [...policy.members()],
|
|
53800
|
+
context
|
|
53801
|
+
};
|
|
53802
|
+
} else {
|
|
53804
53803
|
const wake = {
|
|
53805
53804
|
wakeId: detail.id,
|
|
53806
53805
|
senderRelation: admission.senderRelation,
|
|
@@ -53855,6 +53854,11 @@ const RESUME_LOCK_RETRY_MS = 4e3;
|
|
|
53855
53854
|
* newer mail) waits for that newer mail before waking for the held one.
|
|
53856
53855
|
*/
|
|
53857
53856
|
const HELD_WAKE_SETTLE_MS = 5e3;
|
|
53857
|
+
/**
|
|
53858
|
+
* After a wake, how long the listener keeps taking mail that is already
|
|
53859
|
+
* queued, so mail that arrived together is announced together.
|
|
53860
|
+
*/
|
|
53861
|
+
const WAKE_DRAIN_MS = 1500;
|
|
53858
53862
|
const MAIL_CHECK_RECORD_MS = 5e3;
|
|
53859
53863
|
const RESUME_LOCK_RETRY_STEP_MS = 250;
|
|
53860
53864
|
function isListenLockContention(error) {
|
|
@@ -54245,9 +54249,12 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
54245
54249
|
});
|
|
54246
54250
|
let timedOut = false;
|
|
54247
54251
|
let lastMailCheckRecord = Number.NEGATIVE_INFINITY;
|
|
54252
|
+
const receiveWindow = new AbortController();
|
|
54253
|
+
const sharedNotify = Boolean(target && flags["notify-session"] && requestConfig);
|
|
54248
54254
|
const timer = flags.timeout === void 0 ? void 0 : setTimeout(() => {
|
|
54249
54255
|
timedOut = true;
|
|
54250
|
-
|
|
54256
|
+
receiveWindow.abort();
|
|
54257
|
+
if (sharedNotify) controller.abort();
|
|
54251
54258
|
}, flags.timeout * 1e3);
|
|
54252
54259
|
const cancel = () => controller.abort();
|
|
54253
54260
|
process.on("SIGINT", cancel);
|
|
@@ -54291,6 +54298,7 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
54291
54298
|
socketPath
|
|
54292
54299
|
} : void 0,
|
|
54293
54300
|
handler: wake?.handler ?? handler,
|
|
54301
|
+
stopReceiving: receiveWindow.signal,
|
|
54294
54302
|
stderr: flags.wake ? new Writable({ write(_chunk, _encoding, done) {
|
|
54295
54303
|
done();
|
|
54296
54304
|
} }) : void 0,
|
|
@@ -54318,7 +54326,7 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
54318
54326
|
for (;;) try {
|
|
54319
54327
|
await runListen(settle ? {
|
|
54320
54328
|
...options,
|
|
54321
|
-
|
|
54329
|
+
stopReceiving: AbortSignal.any([receiveWindow.signal, settle])
|
|
54322
54330
|
} : options);
|
|
54323
54331
|
break;
|
|
54324
54332
|
} catch (error) {
|
|
@@ -54330,7 +54338,15 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
54330
54338
|
}
|
|
54331
54339
|
wake?.completed();
|
|
54332
54340
|
if (settle?.aborted && !controller.signal.aborted) wake?.wakeHeld?.();
|
|
54333
|
-
} while (wake && !wake.wakeId() && !wake.status() && !controller.signal.aborted);
|
|
54341
|
+
} while (wake && !wake.wakeId() && !wake.status() && !controller.signal.aborted && !receiveWindow.signal.aborted);
|
|
54342
|
+
if (wake && hookSessionId && (wake.wakeId() || wake.status()) && !controller.signal.aborted) try {
|
|
54343
|
+
await runListen({
|
|
54344
|
+
...options,
|
|
54345
|
+
number: void 0,
|
|
54346
|
+
stopReceiving: AbortSignal.any([receiveWindow.signal, AbortSignal.timeout(WAKE_DRAIN_MS)])
|
|
54347
|
+
});
|
|
54348
|
+
wake.completed();
|
|
54349
|
+
} catch {}
|
|
54334
54350
|
} else {
|
|
54335
54351
|
const notify = options.notifySession;
|
|
54336
54352
|
let expectedCwd;
|
|
@@ -54410,7 +54426,7 @@ var ListenCommand = class ListenCommand extends Command {
|
|
|
54410
54426
|
configDir: this.config.configDir,
|
|
54411
54427
|
...auto
|
|
54412
54428
|
});
|
|
54413
|
-
} else if (controller.signal.aborted) process.exitCode = timedOut && flags.wake ? 0 : timedOut ? 2 : 130;
|
|
54429
|
+
} else if (controller.signal.aborted || timedOut) process.exitCode = timedOut && flags.wake ? 0 : timedOut ? 2 : 130;
|
|
54414
54430
|
}
|
|
54415
54431
|
};
|
|
54416
54432
|
//#endregion
|
|
@@ -54672,7 +54688,7 @@ var ListenInitCommand = class ListenInitCommand extends Command {
|
|
|
54672
54688
|
const UUID$3 = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
54673
54689
|
var ListenPendingCommand = class ListenPendingCommand extends Command {
|
|
54674
54690
|
static summary = "List mail a session's wake listener accepted but not yet read";
|
|
54675
|
-
static description = `Prints the durable pending notices for one Claude Code session of the selected connected profile. A notice is written before its wake event is acknowledged and is removed when the session reads that exact email with \`primitive emails get --id <id>\`. Notices carry only server-derived metadata (sender, thread, whether this profile sent in the thread, newer-mail count), never subject or body. Status notices (kind status) name a peer signal on a message this session sent and have nothing to read; clear them with --clear after delivering them.
|
|
54691
|
+
static description = `Prints the durable pending notices for one Claude Code session of the selected connected profile. A notice is written before its wake event is acknowledged and is removed when the session reads that exact email with \`primitive emails get --id <id>\`. Notices carry only server-derived metadata (sender, thread, whether this profile sent in the thread, newer-mail count), never subject or body. Status notices (kind status) name a peer signal on a message this session sent and have nothing to read; clear them with --clear after delivering them. --claim marks exact ids as announced to the session and prints the ids this call claimed as \`claimed\`; receive hooks use it so each email is announced at most once. Past 50 mail notices the oldest announced one gives way; mail the session has not been shown is never dropped, so when all 50 are unannounced the list reports full and newer mail stays queued, unacknowledged. Reading never takes the writer lock, so it works while a listener holds it.`;
|
|
54676
54692
|
static examples = ["PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> listen pending --session <claude-session-id>", "PRIMITIVE_AGENT_PROFILE=work <%= config.bin %> listen pending --session <claude-session-id> --clear <email-id>"];
|
|
54677
54693
|
static flags = {
|
|
54678
54694
|
session: Flags.string({ description: "Claude Code session ID (defaults to CLAUDE_CODE_SESSION_ID in a Claude session)" }),
|
|
@@ -54680,6 +54696,11 @@ var ListenPendingCommand = class ListenPendingCommand extends Command {
|
|
|
54680
54696
|
multiple: true,
|
|
54681
54697
|
description: "Remove these exact email IDs after delivering them (repeatable)"
|
|
54682
54698
|
}),
|
|
54699
|
+
claim: Flags.string({
|
|
54700
|
+
multiple: true,
|
|
54701
|
+
exclusive: ["clear"],
|
|
54702
|
+
description: "Mark these exact email IDs as announced to the session and report which ones this call claimed (repeatable)"
|
|
54703
|
+
}),
|
|
54683
54704
|
json: Flags.boolean({ description: "Print JSON (already the default)" })
|
|
54684
54705
|
};
|
|
54685
54706
|
async run() {
|
|
@@ -54692,6 +54713,9 @@ var ListenPendingCommand = class ListenPendingCommand extends Command {
|
|
|
54692
54713
|
if (!session || !UUID$3.test(session)) this.error("Pass --session with the Claude Code session UUID.");
|
|
54693
54714
|
const clear = flags.clear ?? [];
|
|
54694
54715
|
if (clear.some((id) => !UUID$3.test(id))) this.error("--clear takes exact email UUIDs.");
|
|
54716
|
+
const claim = flags.claim ?? [];
|
|
54717
|
+
if (claim.some((id) => !UUID$3.test(id))) this.error("--claim takes exact email UUIDs.");
|
|
54718
|
+
const claimed = claim.length ? await claimPendingMail(this.config.configDir, profileName, session, claim) : void 0;
|
|
54695
54719
|
const notices = clear.length ? await removePendingMail(this.config.configDir, profileName, session, clear) : readPendingMail(this.config.configDir, profileName, session);
|
|
54696
54720
|
this.log(JSON.stringify({
|
|
54697
54721
|
session_id: session.toLowerCase(),
|
|
@@ -54700,6 +54724,7 @@ var ListenPendingCommand = class ListenPendingCommand extends Command {
|
|
|
54700
54724
|
read_command: wakeReadCommand("<id>", profileName),
|
|
54701
54725
|
path: pendingMailPath(this.config.configDir, profileName, session),
|
|
54702
54726
|
full: notices.length >= 50,
|
|
54727
|
+
...claimed ? { claimed } : {},
|
|
54703
54728
|
notices
|
|
54704
54729
|
}, null, 2));
|
|
54705
54730
|
}
|
|
@@ -54852,6 +54877,8 @@ function listenForCopyHotkey(params) {
|
|
|
54852
54877
|
//#endregion
|
|
54853
54878
|
//#region src/oclif/commands/login.ts
|
|
54854
54879
|
const MAX_CLI_LOGIN_POLL_INTERVAL_SECONDS = 60;
|
|
54880
|
+
/** How long a login waits for an in-flight agent registration on this machine. */
|
|
54881
|
+
const REGISTRATION_WAIT_MS = 15e3;
|
|
54855
54882
|
function cliError$3(message) {
|
|
54856
54883
|
return new Errors.CLIError(message, { exit: 1 });
|
|
54857
54884
|
}
|
|
@@ -54987,6 +55014,7 @@ var LoginCommand$1 = class extends Command {
|
|
|
54987
55014
|
await this.runBrowserLogin(flags, this.retryCommand());
|
|
54988
55015
|
}
|
|
54989
55016
|
async runBrowserLogin(flags, retryCommand = this.retryCommand()) {
|
|
55017
|
+
await waitForRegistrations(this.config.configDir, REGISTRATION_WAIT_MS, { onWait: () => process.stderr.write("Waiting a few seconds for this machine's agent registration to finish before changing the login.\n") });
|
|
54990
55018
|
let releaseCredentialsLock;
|
|
54991
55019
|
try {
|
|
54992
55020
|
releaseCredentialsLock = acquireCliCredentialsLock(this.config.configDir);
|
|
@@ -56274,7 +56302,7 @@ const TITLES = {
|
|
|
56274
56302
|
"claude.settings_valid": "Claude Code settings file",
|
|
56275
56303
|
"claude.hook.session_start": "Claude SessionStart hook",
|
|
56276
56304
|
"claude.hook.session_end": "Claude SessionEnd hook",
|
|
56277
|
-
"claude.hook.stop": "Claude
|
|
56305
|
+
"claude.hook.stop": "Claude receive hooks",
|
|
56278
56306
|
"claude.instructions": "Claude Code instructions",
|
|
56279
56307
|
"codex.hook.session_start": "Codex SessionStart hook",
|
|
56280
56308
|
"codex.instructions": "Codex instructions",
|
|
@@ -56710,7 +56738,11 @@ async function runMachineDoctor(options) {
|
|
|
56710
56738
|
throw new MachineFileError(`The hook launcher ${cli.launcher?.path ?? ""} could not be written.`);
|
|
56711
56739
|
}
|
|
56712
56740
|
if (!repaired.changed.size) return id === "claude.hook.stop";
|
|
56713
|
-
|
|
56741
|
+
for (const item of repaired.receivers) addSessionReceiver(item.configDir, item.session, {
|
|
56742
|
+
profile: item.profile,
|
|
56743
|
+
address: item.address
|
|
56744
|
+
});
|
|
56745
|
+
if (JSON.stringify(repaired.settings) !== JSON.stringify(settingsRead.settings)) writeClaudeSettings({
|
|
56714
56746
|
claudeDir: paths.claudeDir,
|
|
56715
56747
|
read: settingsRead.read,
|
|
56716
56748
|
settings: repaired.settings,
|
|
@@ -59592,7 +59624,7 @@ async function pollForSettlementInteraction(params) {
|
|
|
59592
59624
|
}
|
|
59593
59625
|
if (receipt) return receipt;
|
|
59594
59626
|
if (deadline !== null && Date.now() >= deadline) break;
|
|
59595
|
-
await sleep$
|
|
59627
|
+
await sleep$3(params.intervalSeconds * 1e3);
|
|
59596
59628
|
}
|
|
59597
59629
|
return null;
|
|
59598
59630
|
}
|
|
@@ -63269,8 +63301,8 @@ function setupChallengeIds(configDir, agent) {
|
|
|
63269
63301
|
const id = savedSetupChallengeId(agentProfileDirectory(configDir, agent.profileName));
|
|
63270
63302
|
return id ? [id] : [];
|
|
63271
63303
|
}
|
|
63272
|
-
async function listRows(apiClient, query) {
|
|
63273
|
-
const result = await listEmails({
|
|
63304
|
+
async function listRows(api, apiClient, query) {
|
|
63305
|
+
const result = await api.listEmails({
|
|
63274
63306
|
client: apiClient.client,
|
|
63275
63307
|
query,
|
|
63276
63308
|
responseStyle: "fields"
|
|
@@ -63282,8 +63314,8 @@ async function listRows(apiClient, query) {
|
|
|
63282
63314
|
cursor: envelope?.meta?.cursor ?? null
|
|
63283
63315
|
};
|
|
63284
63316
|
}
|
|
63285
|
-
async function readDetail(apiClient, id) {
|
|
63286
|
-
const result = await getEmail({
|
|
63317
|
+
async function readDetail(api, apiClient, id) {
|
|
63318
|
+
const result = await api.getEmail({
|
|
63287
63319
|
client: apiClient.client,
|
|
63288
63320
|
path: { id },
|
|
63289
63321
|
responseStyle: "fields"
|
|
@@ -63296,13 +63328,13 @@ async function readDetail(apiClient, id) {
|
|
|
63296
63328
|
const detail = result.data?.data;
|
|
63297
63329
|
return detail !== null && typeof detail === "object" ? detail : null;
|
|
63298
63330
|
}
|
|
63299
|
-
async function firstMatch(apiClient, rows, filters, seen) {
|
|
63331
|
+
async function firstMatch(api, apiClient, rows, filters, seen) {
|
|
63300
63332
|
for (const row of rows) {
|
|
63301
63333
|
const id = str(row.id);
|
|
63302
63334
|
if (!id || seen.has(id)) continue;
|
|
63303
63335
|
seen.add(id);
|
|
63304
63336
|
if (!rowMayMatch(row, filters)) continue;
|
|
63305
|
-
const detail = await readDetail(apiClient, id);
|
|
63337
|
+
const detail = await readDetail(api, apiClient, id);
|
|
63306
63338
|
if (detail && detailMatches(detail, filters)) return detail;
|
|
63307
63339
|
}
|
|
63308
63340
|
return null;
|
|
@@ -63317,32 +63349,49 @@ function arrivedSince(row, threshold) {
|
|
|
63317
63349
|
* the deadline.
|
|
63318
63350
|
*/
|
|
63319
63351
|
async function waitForEmail(params) {
|
|
63352
|
+
const { deadline } = params;
|
|
63353
|
+
const api = waitingInboxApi(deadline, {
|
|
63354
|
+
getConversation,
|
|
63355
|
+
getEmail,
|
|
63356
|
+
listEmails
|
|
63357
|
+
});
|
|
63358
|
+
try {
|
|
63359
|
+
return await tailForEmail(api, params);
|
|
63360
|
+
} catch (error) {
|
|
63361
|
+
if (error instanceof WaitDeadlineReached) return null;
|
|
63362
|
+
throw error;
|
|
63363
|
+
}
|
|
63364
|
+
}
|
|
63365
|
+
async function tailForEmail(api, params) {
|
|
63320
63366
|
const { apiClient, filters, deadline } = params;
|
|
63321
63367
|
const seen = new Set(params.skipIds ?? []);
|
|
63322
|
-
let since = await baselineCursor({
|
|
63323
|
-
|
|
63324
|
-
|
|
63368
|
+
let since = await baselineCursor({
|
|
63369
|
+
apiClient,
|
|
63370
|
+
api
|
|
63371
|
+
});
|
|
63372
|
+
const recent = await listRows(api, apiClient, { limit: EXISTING_PAGE_SIZE });
|
|
63373
|
+
const early = await firstMatch(api, apiClient, recent.rows.filter((row) => arrivedSince(row, params.startedAt)).reverse(), filters, seen);
|
|
63325
63374
|
if (early) return early;
|
|
63326
63375
|
if (params.includeExisting) {
|
|
63327
|
-
const found = await firstMatch(apiClient, recent.rows, filters, seen);
|
|
63376
|
+
const found = await firstMatch(api, apiClient, recent.rows, filters, seen);
|
|
63328
63377
|
if (found) return found;
|
|
63329
63378
|
}
|
|
63330
63379
|
for (;;) {
|
|
63331
63380
|
const remainingMs = deadline === null ? MAX_LONG_POLL_SECONDS * 1e3 : deadline - Date.now();
|
|
63332
63381
|
if (remainingMs <= 0) return null;
|
|
63333
63382
|
const startedAt = Date.now();
|
|
63334
|
-
const page = await listRows(apiClient, {
|
|
63383
|
+
const page = await listRows(api, apiClient, {
|
|
63335
63384
|
limit: TAIL_PAGE_SIZE,
|
|
63336
63385
|
since,
|
|
63337
63386
|
wait: Math.max(0, Math.min(MAX_LONG_POLL_SECONDS, Math.floor(remainingMs / 1e3)))
|
|
63338
63387
|
});
|
|
63339
|
-
const found = await firstMatch(apiClient, page.rows, filters, seen);
|
|
63388
|
+
const found = await firstMatch(api, apiClient, page.rows, filters, seen);
|
|
63340
63389
|
if (found) return found;
|
|
63341
63390
|
if (page.rows.length > 0 && page.cursor) {
|
|
63342
63391
|
since = page.cursor;
|
|
63343
63392
|
continue;
|
|
63344
63393
|
}
|
|
63345
|
-
if (Date.now() - startedAt < 1e3) await sleep$
|
|
63394
|
+
if (Date.now() - startedAt < 1e3) await sleep$3(Math.max(0, Math.min(1e3, remainingMs)));
|
|
63346
63395
|
}
|
|
63347
63396
|
}
|
|
63348
63397
|
function describeFilters(filters) {
|
|
@@ -63364,6 +63413,8 @@ Only mail that arrives after the command starts matches, unless --include-existi
|
|
|
63364
63413
|
|
|
63365
63414
|
--code prints only the verification or one-time code, with spaces or dashes between its groups removed. --link prints only the most likely verification, sign-in, confirm, magic or reset link, with an obvious click-tracking wrapper removed. Without either, the email is printed: sender, subject, time, its ID and the start of its text, ready for \`primitive reply --id <id>\`. --json prints one object with id, thread_id, from, to, subject, received_at, text and, when asked, code and link (null when none was found).
|
|
63366
63415
|
|
|
63416
|
+
A rate limit (HTTP 429, honouring Retry-After), a 502, 503 or 504, or a network error during the wait is retried with backoff until --timeout, with one note on stderr per retry; if it lasts until then, the wait times out (exit 5).
|
|
63417
|
+
|
|
63367
63418
|
Exit codes:
|
|
63368
63419
|
- 0: a matching email arrived and was printed.
|
|
63369
63420
|
- 1: error (API failure or authentication).
|
|
@@ -64117,10 +64168,10 @@ var WakeSchedulesUpdateCommand = class WakeSchedulesUpdateCommand extends Comman
|
|
|
64117
64168
|
};
|
|
64118
64169
|
//#endregion
|
|
64119
64170
|
//#region src/oclif/commands/whoami.ts
|
|
64120
|
-
function formatWhoamiSummary(account, managedInboxDomain) {
|
|
64171
|
+
function formatWhoamiSummary(account, managedInboxDomain, orgName = null) {
|
|
64121
64172
|
const lines = [
|
|
64122
64173
|
`Authenticated as ${account.email}`,
|
|
64123
|
-
`
|
|
64174
|
+
orgName ? `Organization: ${orgName} (${account.id})` : `Organization id: ${account.id}`,
|
|
64124
64175
|
`Plan: ${account.plan}`
|
|
64125
64176
|
];
|
|
64126
64177
|
if (managedInboxDomain) lines.push(`Managed inbox: any-local-part@${managedInboxDomain}`);
|
|
@@ -64209,14 +64260,25 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
64209
64260
|
});
|
|
64210
64261
|
if (!domainsResult.error) managedInboxDomain = (domainsResult.data?.data ?? []).find((row) => row.verified && row.managed_zone !== null)?.domain ?? null;
|
|
64211
64262
|
} catch {}
|
|
64263
|
+
const orgName = auth.credentials?.org_id === account.id ? auth.credentials.org_name ?? null : null;
|
|
64264
|
+
const session = guardedSession(void 0, process.env);
|
|
64265
|
+
let registration = null;
|
|
64266
|
+
try {
|
|
64267
|
+
registration = session ? failedSessionRegistration(this.config.configDir, session) : null;
|
|
64268
|
+
} catch {
|
|
64269
|
+
registration = null;
|
|
64270
|
+
}
|
|
64212
64271
|
if (flags.json) {
|
|
64213
64272
|
this.log(JSON.stringify({
|
|
64214
64273
|
...account,
|
|
64215
|
-
managed_inbox_domain: managedInboxDomain
|
|
64274
|
+
managed_inbox_domain: managedInboxDomain,
|
|
64275
|
+
org_name: orgName,
|
|
64276
|
+
...registration ? { session_registration: registration } : {}
|
|
64216
64277
|
}, null, 2));
|
|
64217
64278
|
return;
|
|
64218
64279
|
}
|
|
64219
|
-
this.log(formatWhoamiSummary(account, managedInboxDomain));
|
|
64280
|
+
this.log(formatWhoamiSummary(account, managedInboxDomain, orgName));
|
|
64281
|
+
if (registration) this.log(registrationSummaryLine(registration));
|
|
64220
64282
|
});
|
|
64221
64283
|
}
|
|
64222
64284
|
};
|