primitive 1.23.0 → 1.25.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 (3) hide show
  1. package/README.md +19 -0
  2. package/dist/oclif/index.js +1000 -152
  3. package/package.json +7 -4
@@ -1,14 +1,15 @@
1
1
  import { A as saveActiveChatState, C as resolveCliAuth, D as deleteChatState, E as chatStatePath, M as createClient, N as createConfig, O as loadActiveChatState, S as normalizeApiBaseUrl, T as saveSignupCredentials, _ as deleteCliCredentials, a as normalizeCliEnvironmentName, b as hasStoredCliLogin, c as resolveConfigEnvironment, d as validateCliHeaderName, f as validateCliHeaderValue, g as credentialsPath, h as credentialsLockPath, i as loadCliConfig, j as PrimitiveApiClient, 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 } from "../cli-config-BKso8oJu.js";
2
2
  import { Args, Command, Errors, Flags, ux } from "@oclif/core";
3
- import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
4
- import { createHash, randomUUID } from "node:crypto";
3
+ import { chmodSync, closeSync, constants, existsSync, fstatSync, fsyncSync, linkSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { createHash, randomUUID, scryptSync } from "node:crypto";
5
5
  import path, { basename, dirname, join, relative, resolve, sep, win32 } from "node:path";
6
6
  import process$1 from "node:process";
7
7
  import { createInterface } from "node:readline/promises";
8
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
8
9
  import { execFileSync, spawn } from "node:child_process";
9
10
  import { request } from "node:http";
10
11
  import { request as request$1 } from "node:https";
11
- import { performance } from "node:perf_hooks";
12
+ import { performance as performance$1 } from "node:perf_hooks";
12
13
  import { hostname } from "node:os";
13
14
  import { gunzipSync } from "node:zlib";
14
15
  import { concat, getAddress, hexToBytes, keccak256, stringToBytes } from "viem";
@@ -61,12 +62,15 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
61
62
  deleteOrgSecret: () => deleteOrgSecret,
62
63
  deleteRegistry: () => deleteRegistry,
63
64
  deleteRoute: () => deleteRoute,
65
+ deleteSentEmail: () => deleteSentEmail,
64
66
  deleteWakeAuthorization: () => deleteWakeAuthorization,
65
67
  deleteWakeSchedule: () => deleteWakeSchedule,
66
68
  discardEmailContent: () => discardEmailContent,
67
69
  downloadAttachments: () => downloadAttachments,
68
70
  downloadDomainZoneFile: () => downloadDomainZoneFile,
71
+ downloadEmailAttachmentPart: () => downloadEmailAttachmentPart,
69
72
  downloadRawEmail: () => downloadRawEmail,
73
+ downloadSentAttachmentPart: () => downloadSentAttachmentPart,
70
74
  getAccount: () => getAccount,
71
75
  getAgent: () => getAgent,
72
76
  getChallenge: () => getChallenge,
@@ -115,6 +119,7 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
115
119
  publishAgent: () => publishAgent,
116
120
  pullWebhookEvent: () => pullWebhookEvent,
117
121
  registerPayoutAddress: () => registerPayoutAddress,
122
+ removeAgentConnection: () => removeAgentConnection,
118
123
  reorderRoutes: () => reorderRoutes,
119
124
  replayDelivery: () => replayDelivery,
120
125
  replayEmailWebhooks: () => replayEmailWebhooks,
@@ -784,6 +789,49 @@ const downloadAttachments = (options) => (options.client ?? client).get({
784
789
  ...options
785
790
  });
786
791
  /**
792
+ * Download one inbound email attachment
793
+ *
794
+ * Pending service release. This contract does not establish live availability.
795
+ * Downloads the original bytes of one ordinary email attachment, selected by
796
+ * its metadata `part_index`, not its position in an attachments array. Refresh
797
+ * the email detail after `attachment_changed` before choosing an index again.
798
+ * Uses bearer authentication only; signed raw-email download tokens are not accepted.
799
+ * A paired-agent credential may read only a message addressed to its claimed address.
800
+ * Function credentials are denied. Other bearer credentials retain their
801
+ * existing email access boundaries. This endpoint serves email bytes only.
802
+ *
803
+ */
804
+ const downloadEmailAttachmentPart = (options) => (options.client ?? client).get({
805
+ security: [{
806
+ scheme: "bearer",
807
+ type: "http"
808
+ }],
809
+ url: "/emails/{id}/attachments/{part_index}",
810
+ ...options
811
+ });
812
+ /**
813
+ * Download one sent email attachment
814
+ *
815
+ * Pending service release. This contract does not establish live availability.
816
+ * Downloads the original bytes of one ordinary email attachment, selected by
817
+ * its metadata `part_index`, not its position in an attachments array. Refresh
818
+ * the email detail after `attachment_changed` before choosing an index again.
819
+ * Uses bearer authentication only; signed raw-email download tokens are not accepted.
820
+ * A paired-agent credential may read only mail sent from its claimed address.
821
+ * Receiving a copy does not authorize downloading another sender's sent content.
822
+ * Function credentials are denied. Other bearer credentials retain their
823
+ * existing email access boundaries. This endpoint serves email bytes only.
824
+ *
825
+ */
826
+ const downloadSentAttachmentPart = (options) => (options.client ?? client).get({
827
+ security: [{
828
+ scheme: "bearer",
829
+ type: "http"
830
+ }],
831
+ url: "/sent-emails/{id}/attachments/{part_index}",
832
+ ...options
833
+ });
834
+ /**
787
835
  * Reply to an inbound email
788
836
  *
789
837
  * Sends an outbound reply to the inbound email identified by `id`.
@@ -1581,6 +1629,29 @@ const listSentEmails = (options) => (options?.client ?? client).get({
1581
1629
  ...options
1582
1630
  });
1583
1631
  /**
1632
+ * Delete a sent email
1633
+ *
1634
+ * Permanently deletes sender mailbox history and its owned attachment archive
1635
+ * and transient payload. Allows delivered, bounced, agent_failed, gate_denied,
1636
+ * canceled, deferred and wait_timeout; other states return 409. Cancel scheduled
1637
+ * sends first. This does not recall or cancel delivery already admitted.
1638
+ * Recipient copies, received replies and independent audit records are preserved.
1639
+ * Occupied idempotency keys and delivered-reply deduplication remain reserved.
1640
+ * Keys already released by cancellation, gate denial or a never-attempted failure
1641
+ * remain reusable. Repeating a completed deletion succeeds; unknown or foreign
1642
+ * IDs return 404. Organization API keys, sessions and OAuth are supported;
1643
+ * Function and connected-agent credentials are denied.
1644
+ *
1645
+ */
1646
+ const deleteSentEmail = (options) => (options.client ?? client).delete({
1647
+ security: [{
1648
+ scheme: "bearer",
1649
+ type: "http"
1650
+ }],
1651
+ url: "/sent-emails/{id}",
1652
+ ...options
1653
+ });
1654
+ /**
1584
1655
  * Get a sent email by id
1585
1656
  *
1586
1657
  * Returns the full sent-email record by id, including
@@ -1630,6 +1701,26 @@ const rescheduleSentEmail = (options) => (options.client ?? client).patch({
1630
1701
  }
1631
1702
  });
1632
1703
  /**
1704
+ * Remove a revoked agent connection
1705
+ *
1706
+ * Permanently removes a revoked connection record. Requires an organization
1707
+ * owner or admin session or OAuth token; organization API keys are denied.
1708
+ * Disconnect first using DELETE /agent-connections/{address}. An active
1709
+ * connection returns 409 connection_not_revoked. Missing or already removed
1710
+ * records return 404. Mail, address notes, domains and external runtimes are
1711
+ * preserved. The same address can be paired again with a new invitation;
1712
+ * old credentials and invitations remain invalid.
1713
+ *
1714
+ */
1715
+ const removeAgentConnection = (options) => (options.client ?? client).post({
1716
+ security: [{
1717
+ scheme: "bearer",
1718
+ type: "http"
1719
+ }],
1720
+ url: "/agent-connections/{address}/remove",
1721
+ ...options
1722
+ });
1723
+ /**
1633
1724
  * Cancel a scheduled send
1634
1725
  *
1635
1726
  * Cancels a STILL-SCHEDULED send (status `scheduled`), moving it
@@ -2676,6 +2767,10 @@ const openapiDocument = {
2676
2767
  }],
2677
2768
  "security": [{ "BearerAuth": [] }],
2678
2769
  "tags": [
2770
+ {
2771
+ "name": "Agent Connections",
2772
+ "description": "Manage address-bound connections to external agent runtimes."
2773
+ },
2679
2774
  {
2680
2775
  "name": "CLI",
2681
2776
  "description": "Browser-assisted CLI authentication"
@@ -3887,6 +3982,100 @@ const openapiDocument = {
3887
3982
  }
3888
3983
  }
3889
3984
  },
3985
+ "/emails/{id}/attachments/{part_index}": {
3986
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }, { "$ref": "#/components/parameters/AttachmentPartIndex" }],
3987
+ "get": {
3988
+ "operationId": "downloadEmailAttachmentPart",
3989
+ "summary": "Download one inbound email attachment",
3990
+ "description": "Pending service release. This contract does not establish live availability.\nDownloads the original bytes of one ordinary email attachment, selected by\nits metadata `part_index`, not its position in an attachments array. Refresh\nthe email detail after `attachment_changed` before choosing an index again.\nUses bearer authentication only; signed raw-email download tokens are not accepted.\nA paired-agent credential may read only a message addressed to its claimed address.\nFunction credentials are denied. Other bearer credentials retain their\nexisting email access boundaries. This endpoint serves email bytes only.\n",
3991
+ "tags": ["Emails"],
3992
+ "security": [{ "BearerAuth": [] }],
3993
+ "responses": {
3994
+ "200": { "$ref": "#/components/responses/AttachmentPart" },
3995
+ "400": { "$ref": "#/components/responses/ValidationError" },
3996
+ "401": { "$ref": "#/components/responses/Unauthorized" },
3997
+ "403": { "$ref": "#/components/responses/Forbidden" },
3998
+ "404": {
3999
+ "description": "Missing or inaccessible message, or missing attachment part",
4000
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4001
+ },
4002
+ "409": {
4003
+ "description": "attachment_changed; refresh email detail before selecting a part again",
4004
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4005
+ },
4006
+ "410": {
4007
+ "description": "content_discarded; the original content is no longer available",
4008
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4009
+ },
4010
+ "413": {
4011
+ "description": "attachment_limit_exceeded; the attachment exceeds download limits",
4012
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4013
+ },
4014
+ "502": {
4015
+ "description": "attachment_integrity_failed; stored content failed integrity validation",
4016
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4017
+ },
4018
+ "503": {
4019
+ "description": "attachment_not_ready or attachment_storage_unavailable; retry after the indicated delay",
4020
+ "headers": { "Retry-After": {
4021
+ "description": "Delay in seconds before retrying this read",
4022
+ "schema": {
4023
+ "type": "integer",
4024
+ "minimum": 0
4025
+ }
4026
+ } },
4027
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4028
+ }
4029
+ }
4030
+ }
4031
+ },
4032
+ "/sent-emails/{id}/attachments/{part_index}": {
4033
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }, { "$ref": "#/components/parameters/AttachmentPartIndex" }],
4034
+ "get": {
4035
+ "operationId": "downloadSentAttachmentPart",
4036
+ "summary": "Download one sent email attachment",
4037
+ "description": "Pending service release. This contract does not establish live availability.\nDownloads the original bytes of one ordinary email attachment, selected by\nits metadata `part_index`, not its position in an attachments array. Refresh\nthe email detail after `attachment_changed` before choosing an index again.\nUses bearer authentication only; signed raw-email download tokens are not accepted.\nA paired-agent credential may read only mail sent from its claimed address.\nReceiving a copy does not authorize downloading another sender's sent content.\nFunction credentials are denied. Other bearer credentials retain their\nexisting email access boundaries. This endpoint serves email bytes only.\n",
4038
+ "tags": ["Sending"],
4039
+ "security": [{ "BearerAuth": [] }],
4040
+ "responses": {
4041
+ "200": { "$ref": "#/components/responses/AttachmentPart" },
4042
+ "400": { "$ref": "#/components/responses/ValidationError" },
4043
+ "401": { "$ref": "#/components/responses/Unauthorized" },
4044
+ "403": { "$ref": "#/components/responses/Forbidden" },
4045
+ "404": {
4046
+ "description": "Missing or inaccessible message, or missing attachment part",
4047
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4048
+ },
4049
+ "409": {
4050
+ "description": "attachment_changed; refresh email detail before selecting a part again",
4051
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4052
+ },
4053
+ "410": {
4054
+ "description": "content_discarded; the original content is no longer available",
4055
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4056
+ },
4057
+ "413": {
4058
+ "description": "attachment_limit_exceeded; the attachment exceeds download limits",
4059
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4060
+ },
4061
+ "502": {
4062
+ "description": "attachment_integrity_failed; stored content failed integrity validation",
4063
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4064
+ },
4065
+ "503": {
4066
+ "description": "attachment_not_ready or attachment_storage_unavailable; retry after the indicated delay",
4067
+ "headers": { "Retry-After": {
4068
+ "description": "Delay in seconds before retrying this read",
4069
+ "schema": {
4070
+ "type": "integer",
4071
+ "minimum": 0
4072
+ }
4073
+ } },
4074
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4075
+ }
4076
+ }
4077
+ }
4078
+ },
3890
4079
  "/emails/{id}/reply": {
3891
4080
  "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
3892
4081
  "post": {
@@ -3917,6 +4106,10 @@ const openapiDocument = {
3917
4106
  "401": { "$ref": "#/components/responses/Unauthorized" },
3918
4107
  "403": { "$ref": "#/components/responses/Forbidden" },
3919
4108
  "404": { "$ref": "#/components/responses/NotFound" },
4109
+ "410": {
4110
+ "description": "The prior send was deleted. Its idempotency key or automatic reply suppression remains reserved, so this request will not send another message. Do not retry with a new key to bypass this refusal.",
4111
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
4112
+ },
3920
4113
  "422": {
3921
4114
  "description": "Inbound is not repliable: the row exists but lacks a\n`message_id` (no thread anchor) or a `recipient` (cannot\nderive the From address).\n",
3922
4115
  "content": { "application/json": {
@@ -4809,6 +5002,10 @@ const openapiDocument = {
4809
5002
  "400": { "$ref": "#/components/responses/ValidationError" },
4810
5003
  "401": { "$ref": "#/components/responses/Unauthorized" },
4811
5004
  "403": { "$ref": "#/components/responses/Forbidden" },
5005
+ "410": {
5006
+ "description": "The prior send was deleted. Its idempotency key or automatic reply suppression remains reserved, so this request will not send another message. Do not retry with a new key to bypass this refusal.",
5007
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
5008
+ },
4812
5009
  "429": { "$ref": "#/components/responses/RateLimited" },
4813
5010
  "500": { "$ref": "#/components/responses/InternalError" },
4814
5011
  "502": { "$ref": "#/components/responses/BadGateway" },
@@ -4931,6 +5128,50 @@ const openapiDocument = {
4931
5128
  }
4932
5129
  } },
4933
5130
  "/sent-emails/{id}": {
5131
+ "delete": {
5132
+ "operationId": "deleteSentEmail",
5133
+ "summary": "Delete a sent email",
5134
+ "description": "Permanently deletes sender mailbox history and its owned attachment archive\nand transient payload. Allows delivered, bounced, agent_failed, gate_denied,\ncanceled, deferred and wait_timeout; other states return 409. Cancel scheduled\nsends first. This does not recall or cancel delivery already admitted.\nRecipient copies, received replies and independent audit records are preserved.\nOccupied idempotency keys and delivered-reply deduplication remain reserved.\nKeys already released by cancellation, gate denial or a never-attempted failure\nremain reusable. Repeating a completed deletion succeeds; unknown or foreign\nIDs return 404. Organization API keys, sessions and OAuth are supported;\nFunction and connected-agent credentials are denied.\n",
5135
+ "tags": ["Sending"],
5136
+ "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
5137
+ "responses": {
5138
+ "200": {
5139
+ "description": "Deletion completed",
5140
+ "content": { "application/json": { "schema": {
5141
+ "type": "object",
5142
+ "required": ["success", "data"],
5143
+ "properties": {
5144
+ "success": {
5145
+ "type": "boolean",
5146
+ "const": true
5147
+ },
5148
+ "data": {
5149
+ "type": "object",
5150
+ "required": ["deleted"],
5151
+ "properties": { "deleted": {
5152
+ "type": "boolean",
5153
+ "const": true
5154
+ } }
5155
+ }
5156
+ }
5157
+ } } }
5158
+ },
5159
+ "400": { "$ref": "#/components/responses/ValidationError" },
5160
+ "401": { "$ref": "#/components/responses/Unauthorized" },
5161
+ "403": { "$ref": "#/components/responses/Forbidden" },
5162
+ "404": { "$ref": "#/components/responses/NotFound" },
5163
+ "409": { "$ref": "#/components/responses/Conflict" },
5164
+ "429": { "$ref": "#/components/responses/RateLimited" },
5165
+ "500": {
5166
+ "description": "Database failure. Files may already be removed while the message record and cleanup references remain. Retry the same DELETE; failure does not restore files.",
5167
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
5168
+ },
5169
+ "503": {
5170
+ "description": "Storage cleanup failed. Some files may already be removed; the message record and cleanup references remain for an idempotent retry. Failure does not restore files.",
5171
+ "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
5172
+ }
5173
+ }
5174
+ },
4934
5175
  "get": {
4935
5176
  "operationId": "getSentEmail",
4936
5177
  "summary": "Get a sent email by id",
@@ -4991,6 +5232,52 @@ const openapiDocument = {
4991
5232
  }
4992
5233
  }
4993
5234
  },
5235
+ "/agent-connections/{address}/remove": { "post": {
5236
+ "operationId": "removeAgentConnection",
5237
+ "summary": "Remove a revoked agent connection",
5238
+ "description": "Permanently removes a revoked connection record. Requires an organization\nowner or admin session or OAuth token; organization API keys are denied.\nDisconnect first using DELETE /agent-connections/{address}. An active\nconnection returns 409 connection_not_revoked. Missing or already removed\nrecords return 404. Mail, address notes, domains and external runtimes are\npreserved. The same address can be paired again with a new invitation;\nold credentials and invitations remain invalid.\n",
5239
+ "tags": ["Agent Connections"],
5240
+ "parameters": [{
5241
+ "name": "address",
5242
+ "in": "path",
5243
+ "required": true,
5244
+ "description": "The email address identifying the revoked connection.",
5245
+ "schema": {
5246
+ "type": "string",
5247
+ "format": "email",
5248
+ "maxLength": 254
5249
+ }
5250
+ }],
5251
+ "responses": {
5252
+ "200": {
5253
+ "description": "Deletion completed",
5254
+ "content": { "application/json": { "schema": {
5255
+ "type": "object",
5256
+ "required": ["success", "data"],
5257
+ "properties": {
5258
+ "success": {
5259
+ "type": "boolean",
5260
+ "const": true
5261
+ },
5262
+ "data": {
5263
+ "type": "object",
5264
+ "required": ["deleted"],
5265
+ "properties": { "deleted": {
5266
+ "type": "boolean",
5267
+ "const": true
5268
+ } }
5269
+ }
5270
+ }
5271
+ } } }
5272
+ },
5273
+ "400": { "$ref": "#/components/responses/ValidationError" },
5274
+ "401": { "$ref": "#/components/responses/Unauthorized" },
5275
+ "403": { "$ref": "#/components/responses/Forbidden" },
5276
+ "404": { "$ref": "#/components/responses/NotFound" },
5277
+ "409": { "$ref": "#/components/responses/Conflict" },
5278
+ "429": { "$ref": "#/components/responses/RateLimited" }
5279
+ }
5280
+ } },
4994
5281
  "/sent-emails/{id}/cancel": {
4995
5282
  "parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
4996
5283
  "post": {
@@ -6570,6 +6857,18 @@ const openapiDocument = {
6570
6857
  }
6571
6858
  },
6572
6859
  "parameters": {
6860
+ "AttachmentPartIndex": {
6861
+ "name": "part_index",
6862
+ "in": "path",
6863
+ "required": true,
6864
+ "description": "The attachment metadata `part_index`, not its offset in the attachments array",
6865
+ "schema": {
6866
+ "type": "integer",
6867
+ "format": "int32",
6868
+ "minimum": 0,
6869
+ "maximum": 2147483647
6870
+ }
6871
+ },
6573
6872
  "ResourceId": {
6574
6873
  "name": "id",
6575
6874
  "in": "path",
@@ -6640,6 +6939,33 @@ const openapiDocument = {
6640
6939
  }
6641
6940
  },
6642
6941
  "responses": {
6942
+ "AttachmentPart": {
6943
+ "description": "Original attachment bytes; never a JSON envelope or a base64 string",
6944
+ "content": { "application/octet-stream": { "schema": {
6945
+ "type": "string",
6946
+ "format": "binary"
6947
+ } } },
6948
+ "headers": {
6949
+ "X-Content-SHA256": {
6950
+ "description": "SHA-256 hex digest of the original bytes",
6951
+ "schema": {
6952
+ "type": "string",
6953
+ "pattern": "^[a-fA-F0-9]{64}$"
6954
+ }
6955
+ },
6956
+ "Content-Disposition": {
6957
+ "description": "Safe attachment disposition with a sanitized filename",
6958
+ "schema": { "type": "string" }
6959
+ },
6960
+ "Cache-Control": {
6961
+ "description": "Attachment responses are private and must not be stored by caches",
6962
+ "schema": {
6963
+ "type": "string",
6964
+ "example": "private, no-store"
6965
+ }
6966
+ }
6967
+ }
6968
+ },
6643
6969
  "PullContentGone": {
6644
6970
  "description": "Queued content is no longer available",
6645
6971
  "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
@@ -7658,6 +7984,17 @@ const openapiDocument = {
7658
7984
  "conflict",
7659
7985
  "mx_conflict",
7660
7986
  "not_scheduled",
7987
+ "sent_email_deleted",
7988
+ "sent_email_not_settled",
7989
+ "sent_email_changed",
7990
+ "sent_email_cleanup_failed",
7991
+ "connection_not_revoked",
7992
+ "attachment_changed",
7993
+ "content_discarded",
7994
+ "attachment_limit_exceeded",
7995
+ "attachment_integrity_failed",
7996
+ "attachment_not_ready",
7997
+ "attachment_storage_unavailable",
7661
7998
  "outbound_disabled",
7662
7999
  "cannot_send_from_domain",
7663
8000
  "recipient_not_allowed",
@@ -10467,6 +10804,53 @@ const openapiDocument = {
10467
10804
  "body_html": {
10468
10805
  "type": ["string", "null"],
10469
10806
  "description": "HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"
10807
+ },
10808
+ "attachments": {
10809
+ "type": "array",
10810
+ "description": "Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.",
10811
+ "items": {
10812
+ "type": "object",
10813
+ "description": "Metadata for one submitted inline attachment.",
10814
+ "additionalProperties": true,
10815
+ "properties": {
10816
+ "filename": { "type": ["string", "null"] },
10817
+ "content_type": { "type": "string" },
10818
+ "size_bytes": {
10819
+ "type": "integer",
10820
+ "minimum": 0,
10821
+ "maximum": 9007199254740991
10822
+ },
10823
+ "sha256": { "type": "string" },
10824
+ "part_index": {
10825
+ "type": "integer",
10826
+ "minimum": 0,
10827
+ "maximum": 9007199254740991
10828
+ },
10829
+ "tar_path": { "type": "string" }
10830
+ },
10831
+ "required": [
10832
+ "filename",
10833
+ "content_type",
10834
+ "size_bytes",
10835
+ "sha256",
10836
+ "part_index",
10837
+ "tar_path"
10838
+ ]
10839
+ }
10840
+ },
10841
+ "attachments_size_bytes": {
10842
+ "type": "integer",
10843
+ "minimum": 0,
10844
+ "maximum": 9007199254740991,
10845
+ "description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
10846
+ },
10847
+ "attachments_complete": {
10848
+ "type": "boolean",
10849
+ "description": "Whether attachments enumerates the complete original submitted attachment set, including zero-byte and inline CID parts. True requires no submitted payload-reference, offloaded, or visibility-filtered attachment to be omitted, retained content, and a published archive when the attachment inventory is nonempty. False or absent provides no completeness proof, including for legacy or discarded content. This field grants no download access and has no interaction semantics."
10850
+ },
10851
+ "attachments_download_available": {
10852
+ "type": "boolean",
10853
+ "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
10470
10854
  }
10471
10855
  }
10472
10856
  }]
@@ -14519,6 +14903,47 @@ const operationManifest = [
14519
14903
  "tag": "Agent",
14520
14904
  "tagCommand": "agent"
14521
14905
  },
14906
+ {
14907
+ "binaryResponse": false,
14908
+ "bodyRequired": false,
14909
+ "command": "remove-agent-connection",
14910
+ "description": "Permanently removes a revoked connection record. Requires an organization\nowner or admin session or OAuth token; organization API keys are denied.\nDisconnect first using DELETE /agent-connections/{address}. An active\nconnection returns 409 connection_not_revoked. Missing or already removed\nrecords return 404. Mail, address notes, domains and external runtimes are\npreserved. The same address can be paired again with a new invitation;\nold credentials and invitations remain invalid.\n",
14911
+ "hasJsonBody": false,
14912
+ "method": "POST",
14913
+ "operationId": "removeAgentConnection",
14914
+ "path": "/agent-connections/{address}/remove",
14915
+ "pathParams": [{
14916
+ "description": "The email address identifying the revoked connection.",
14917
+ "enum": null,
14918
+ "name": "address",
14919
+ "required": true,
14920
+ "type": "string"
14921
+ }],
14922
+ "queryParams": [],
14923
+ "requestSchema": null,
14924
+ "responseSchema": {
14925
+ "type": "object",
14926
+ "required": ["success", "data"],
14927
+ "properties": {
14928
+ "success": {
14929
+ "type": "boolean",
14930
+ "const": true
14931
+ },
14932
+ "data": {
14933
+ "type": "object",
14934
+ "required": ["deleted"],
14935
+ "properties": { "deleted": {
14936
+ "type": "boolean",
14937
+ "const": true
14938
+ } }
14939
+ }
14940
+ }
14941
+ },
14942
+ "sdkName": "removeAgentConnection",
14943
+ "summary": "Remove a revoked agent connection",
14944
+ "tag": "Agent Connections",
14945
+ "tagCommand": "agent-connections"
14946
+ },
14522
14947
  {
14523
14948
  "binaryResponse": false,
14524
14949
  "bodyRequired": false,
@@ -16256,6 +16681,38 @@ const operationManifest = [
16256
16681
  "tag": "Emails",
16257
16682
  "tagCommand": "emails"
16258
16683
  },
16684
+ {
16685
+ "binaryResponse": true,
16686
+ "bodyRequired": false,
16687
+ "command": "download-email-attachment-part",
16688
+ "description": "Pending service release. This contract does not establish live availability.\nDownloads the original bytes of one ordinary email attachment, selected by\nits metadata `part_index`, not its position in an attachments array. Refresh\nthe email detail after `attachment_changed` before choosing an index again.\nUses bearer authentication only; signed raw-email download tokens are not accepted.\nA paired-agent credential may read only a message addressed to its claimed address.\nFunction credentials are denied. Other bearer credentials retain their\nexisting email access boundaries. This endpoint serves email bytes only.\n",
16689
+ "hasJsonBody": false,
16690
+ "method": "GET",
16691
+ "operationId": "downloadEmailAttachmentPart",
16692
+ "path": "/emails/{id}/attachments/{part_index}",
16693
+ "pathParams": [{
16694
+ "description": "Resource UUID",
16695
+ "enum": null,
16696
+ "name": "id",
16697
+ "required": true,
16698
+ "type": "string"
16699
+ }, {
16700
+ "description": "The attachment metadata `part_index`, not its offset in the attachments array",
16701
+ "enum": null,
16702
+ "maximum": 2147483647,
16703
+ "minimum": 0,
16704
+ "name": "part_index",
16705
+ "required": true,
16706
+ "type": "integer"
16707
+ }],
16708
+ "queryParams": [],
16709
+ "requestSchema": null,
16710
+ "responseSchema": null,
16711
+ "sdkName": "downloadEmailAttachmentPart",
16712
+ "summary": "Download one inbound email attachment",
16713
+ "tag": "Emails",
16714
+ "tagCommand": "emails"
16715
+ },
16259
16716
  {
16260
16717
  "binaryResponse": true,
16261
16718
  "bodyRequired": false,
@@ -23742,6 +24199,53 @@ const operationManifest = [
23742
24199
  "body_html": {
23743
24200
  "type": ["string", "null"],
23744
24201
  "description": "HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"
24202
+ },
24203
+ "attachments": {
24204
+ "type": "array",
24205
+ "description": "Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.",
24206
+ "items": {
24207
+ "type": "object",
24208
+ "description": "Metadata for one submitted inline attachment.",
24209
+ "additionalProperties": true,
24210
+ "properties": {
24211
+ "filename": { "type": ["string", "null"] },
24212
+ "content_type": { "type": "string" },
24213
+ "size_bytes": {
24214
+ "type": "integer",
24215
+ "minimum": 0,
24216
+ "maximum": 9007199254740991
24217
+ },
24218
+ "sha256": { "type": "string" },
24219
+ "part_index": {
24220
+ "type": "integer",
24221
+ "minimum": 0,
24222
+ "maximum": 9007199254740991
24223
+ },
24224
+ "tar_path": { "type": "string" }
24225
+ },
24226
+ "required": [
24227
+ "filename",
24228
+ "content_type",
24229
+ "size_bytes",
24230
+ "sha256",
24231
+ "part_index",
24232
+ "tar_path"
24233
+ ]
24234
+ }
24235
+ },
24236
+ "attachments_size_bytes": {
24237
+ "type": "integer",
24238
+ "minimum": 0,
24239
+ "maximum": 9007199254740991,
24240
+ "description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
24241
+ },
24242
+ "attachments_complete": {
24243
+ "type": "boolean",
24244
+ "description": "Whether attachments enumerates the complete original submitted attachment set, including zero-byte and inline CID parts. True requires no submitted payload-reference, offloaded, or visibility-filtered attachment to be omitted, retained content, and a published archive when the attachment inventory is nonempty. False or absent provides no completeness proof, including for legacy or discarded content. This field grants no download access and has no interaction semantics."
24245
+ },
24246
+ "attachments_download_available": {
24247
+ "type": "boolean",
24248
+ "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
23745
24249
  }
23746
24250
  }
23747
24251
  }]
@@ -23751,6 +24255,79 @@ const operationManifest = [
23751
24255
  "tag": "Sending",
23752
24256
  "tagCommand": "sending"
23753
24257
  },
24258
+ {
24259
+ "binaryResponse": false,
24260
+ "bodyRequired": false,
24261
+ "command": "delete-sent-email",
24262
+ "description": "Permanently deletes sender mailbox history and its owned attachment archive\nand transient payload. Allows delivered, bounced, agent_failed, gate_denied,\ncanceled, deferred and wait_timeout; other states return 409. Cancel scheduled\nsends first. This does not recall or cancel delivery already admitted.\nRecipient copies, received replies and independent audit records are preserved.\nOccupied idempotency keys and delivered-reply deduplication remain reserved.\nKeys already released by cancellation, gate denial or a never-attempted failure\nremain reusable. Repeating a completed deletion succeeds; unknown or foreign\nIDs return 404. Organization API keys, sessions and OAuth are supported;\nFunction and connected-agent credentials are denied.\n",
24263
+ "hasJsonBody": false,
24264
+ "method": "DELETE",
24265
+ "operationId": "deleteSentEmail",
24266
+ "path": "/sent-emails/{id}",
24267
+ "pathParams": [{
24268
+ "description": "Resource UUID",
24269
+ "enum": null,
24270
+ "name": "id",
24271
+ "required": true,
24272
+ "type": "string"
24273
+ }],
24274
+ "queryParams": [],
24275
+ "requestSchema": null,
24276
+ "responseSchema": {
24277
+ "type": "object",
24278
+ "required": ["success", "data"],
24279
+ "properties": {
24280
+ "success": {
24281
+ "type": "boolean",
24282
+ "const": true
24283
+ },
24284
+ "data": {
24285
+ "type": "object",
24286
+ "required": ["deleted"],
24287
+ "properties": { "deleted": {
24288
+ "type": "boolean",
24289
+ "const": true
24290
+ } }
24291
+ }
24292
+ }
24293
+ },
24294
+ "sdkName": "deleteSentEmail",
24295
+ "summary": "Delete a sent email",
24296
+ "tag": "Sending",
24297
+ "tagCommand": "sending"
24298
+ },
24299
+ {
24300
+ "binaryResponse": true,
24301
+ "bodyRequired": false,
24302
+ "command": "download-sent-attachment-part",
24303
+ "description": "Pending service release. This contract does not establish live availability.\nDownloads the original bytes of one ordinary email attachment, selected by\nits metadata `part_index`, not its position in an attachments array. Refresh\nthe email detail after `attachment_changed` before choosing an index again.\nUses bearer authentication only; signed raw-email download tokens are not accepted.\nA paired-agent credential may read only mail sent from its claimed address.\nReceiving a copy does not authorize downloading another sender's sent content.\nFunction credentials are denied. Other bearer credentials retain their\nexisting email access boundaries. This endpoint serves email bytes only.\n",
24304
+ "hasJsonBody": false,
24305
+ "method": "GET",
24306
+ "operationId": "downloadSentAttachmentPart",
24307
+ "path": "/sent-emails/{id}/attachments/{part_index}",
24308
+ "pathParams": [{
24309
+ "description": "Resource UUID",
24310
+ "enum": null,
24311
+ "name": "id",
24312
+ "required": true,
24313
+ "type": "string"
24314
+ }, {
24315
+ "description": "The attachment metadata `part_index`, not its offset in the attachments array",
24316
+ "enum": null,
24317
+ "maximum": 2147483647,
24318
+ "minimum": 0,
24319
+ "name": "part_index",
24320
+ "required": true,
24321
+ "type": "integer"
24322
+ }],
24323
+ "queryParams": [],
24324
+ "requestSchema": null,
24325
+ "responseSchema": null,
24326
+ "sdkName": "downloadSentAttachmentPart",
24327
+ "summary": "Download one sent email attachment",
24328
+ "tag": "Sending",
24329
+ "tagCommand": "sending"
24330
+ },
23754
24331
  {
23755
24332
  "binaryResponse": false,
23756
24333
  "bodyRequired": false,
@@ -24219,6 +24796,53 @@ const operationManifest = [
24219
24796
  "body_html": {
24220
24797
  "type": ["string", "null"],
24221
24798
  "description": "HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"
24799
+ },
24800
+ "attachments": {
24801
+ "type": "array",
24802
+ "description": "Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.",
24803
+ "items": {
24804
+ "type": "object",
24805
+ "description": "Metadata for one submitted inline attachment.",
24806
+ "additionalProperties": true,
24807
+ "properties": {
24808
+ "filename": { "type": ["string", "null"] },
24809
+ "content_type": { "type": "string" },
24810
+ "size_bytes": {
24811
+ "type": "integer",
24812
+ "minimum": 0,
24813
+ "maximum": 9007199254740991
24814
+ },
24815
+ "sha256": { "type": "string" },
24816
+ "part_index": {
24817
+ "type": "integer",
24818
+ "minimum": 0,
24819
+ "maximum": 9007199254740991
24820
+ },
24821
+ "tar_path": { "type": "string" }
24822
+ },
24823
+ "required": [
24824
+ "filename",
24825
+ "content_type",
24826
+ "size_bytes",
24827
+ "sha256",
24828
+ "part_index",
24829
+ "tar_path"
24830
+ ]
24831
+ }
24832
+ },
24833
+ "attachments_size_bytes": {
24834
+ "type": "integer",
24835
+ "minimum": 0,
24836
+ "maximum": 9007199254740991,
24837
+ "description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
24838
+ },
24839
+ "attachments_complete": {
24840
+ "type": "boolean",
24841
+ "description": "Whether attachments enumerates the complete original submitted attachment set, including zero-byte and inline CID parts. True requires no submitted payload-reference, offloaded, or visibility-filtered attachment to be omitted, retained content, and a published archive when the attachment inventory is nonempty. False or absent provides no completeness proof, including for legacy or discarded content. This field grants no download access and has no interaction semantics."
24842
+ },
24843
+ "attachments_download_available": {
24844
+ "type": "boolean",
24845
+ "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
24222
24846
  }
24223
24847
  }
24224
24848
  }]
@@ -24943,6 +25567,53 @@ const operationManifest = [
24943
25567
  "body_html": {
24944
25568
  "type": ["string", "null"],
24945
25569
  "description": "HTML body sent on the wire. Null when the send\ncarried only a plain-text body, or when bodies\nhave been discarded post-send.\n"
25570
+ },
25571
+ "attachments": {
25572
+ "type": "array",
25573
+ "description": "Metadata for submitted inline attachments; download availability is reported separately. Metadata may exist when the archive is unavailable, and legacy sends may have no metadata. Offloaded payload files are not included in the inline archive.",
25574
+ "items": {
25575
+ "type": "object",
25576
+ "description": "Metadata for one submitted inline attachment.",
25577
+ "additionalProperties": true,
25578
+ "properties": {
25579
+ "filename": { "type": ["string", "null"] },
25580
+ "content_type": { "type": "string" },
25581
+ "size_bytes": {
25582
+ "type": "integer",
25583
+ "minimum": 0,
25584
+ "maximum": 9007199254740991
25585
+ },
25586
+ "sha256": { "type": "string" },
25587
+ "part_index": {
25588
+ "type": "integer",
25589
+ "minimum": 0,
25590
+ "maximum": 9007199254740991
25591
+ },
25592
+ "tar_path": { "type": "string" }
25593
+ },
25594
+ "required": [
25595
+ "filename",
25596
+ "content_type",
25597
+ "size_bytes",
25598
+ "sha256",
25599
+ "part_index",
25600
+ "tar_path"
25601
+ ]
25602
+ }
25603
+ },
25604
+ "attachments_size_bytes": {
25605
+ "type": "integer",
25606
+ "minimum": 0,
25607
+ "maximum": 9007199254740991,
25608
+ "description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
25609
+ },
25610
+ "attachments_complete": {
25611
+ "type": "boolean",
25612
+ "description": "Whether attachments enumerates the complete original submitted attachment set, including zero-byte and inline CID parts. True requires no submitted payload-reference, offloaded, or visibility-filtered attachment to be omitted, retained content, and a published archive when the attachment inventory is nonempty. False or absent provides no completeness proof, including for legacy or discarded content. This field grants no download access and has no interaction semantics."
25613
+ },
25614
+ "attachments_download_available": {
25615
+ "type": "boolean",
25616
+ "description": "Whether an inline attachment archive has been successfully retained and is available for download. Download authorization is checked separately; address-bound agent connection keys cannot download archives."
24946
25617
  }
24947
25618
  }
24948
25619
  }]
@@ -27828,60 +28499,54 @@ function readAttachmentFiles(paths, readFile = readFileSync) {
27828
28499
  //#endregion
27829
28500
  //#region src/oclif/chat-lock.ts
27830
28501
  /**
27831
- * Filesystem mutex around the chat-state read → POST → save sequence.
27832
- *
27833
- * Why this exists. The chat reply flow is read-modify-write across an
27834
- * RTT to the API: `loadActiveChatState` → `replyToEmail` → poll → save.
27835
- * Two concurrent invocations (e.g. the user re-runs `primitive chat
27836
- * reply` before the first cycle finishes its 5–12 s poll) read the
27837
- * same stale `last_reply_email_id` and POST to it, producing a
27838
- * duplicate /v1/emails/{id}/reply that the server deduplicates by
27839
- * content_hash. The second invocation then polls forever for a reply
27840
- * that arrived in response to the *first* send and has already been
27841
- * surfaced by the *first* invocation.
27842
- *
27843
- * The lock is per-process-config-dir, not per-conversation: holding it
27844
- * for a few seconds while one chat reply completes is a reasonable UX
27845
- * constraint and clearly explained on contention. The lock is
27846
- * re-entrant within a single Node process (ChatReplyCommand wraps
27847
- * ChatCommand internally), but rejects cross-process contention.
27848
- *
27849
- * Liveness. The lock file stores the holder's PID. On EEXIST we probe
27850
- * the holder with `process.kill(pid, 0)`; if the holder is gone (e.g.
27851
- * a previous chat invocation crashed without releasing), we steal the
27852
- * lock. This avoids needing a heartbeat or mtime-based stale check,
27853
- * either of which has its own race surface.
27854
- *
27855
- * Releases. The returned function is idempotent. Callers must call it
27856
- * in a finally block. We also register process-exit / signal handlers
27857
- * so a Ctrl-C during the poll loop still cleans up.
28502
+ * Conversation locks cover one request/reply cycle. The default lock retains
28503
+ * its historical filename and protects short shared chat-state transactions.
28504
+ * Scope names are hashed so addresses and other identifiers stay out of paths.
27858
28505
  */
27859
28506
  const LOCK_FILENAME = "chat-state.lock";
27860
- let processHolder = null;
27861
- /**
27862
- * Whether we've installed our exit / signal listeners on the
27863
- * `process` object. Done lazily on first acquire and never undone:
27864
- * adding handlers per-acquire would let them accumulate (each
27865
- * acquire registers four listeners, and `process.once` can't be
27866
- * un-once'd), which is harmless in production but produces
27867
- * `MaxListenersExceededWarning` + cascading no-op fires in tests
27868
- * that acquire/release across many `it()` blocks. Greptile P2.
27869
- *
27870
- * The handlers consult the current `processHolder` and act only if
27871
- * a lock is actually held, so leaving them installed across
27872
- * release boundaries is safe: a released or never-acquired lock
27873
- * makes them no-ops.
27874
- */
28507
+ const STATE_LOCK_TIMEOUT_MS = 5e3;
28508
+ const STATE_LOCK_RETRY_MS = 25;
28509
+ const processHolders = /* @__PURE__ */ new Map();
27875
28510
  let exitListenersInstalled = false;
28511
+ function lockPath(configDir, scope) {
28512
+ const name = scope === void 0 ? LOCK_FILENAME : `chat-${createHash("sha256").update(scope).digest("hex")}.lock`;
28513
+ return join(resolve(configDir), name);
28514
+ }
28515
+ function readSnapshot(path) {
28516
+ let fd;
28517
+ try {
28518
+ if (!lstatSync(path).isFile()) return null;
28519
+ fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
28520
+ } catch (error) {
28521
+ if (error.code === "ENOENT") return null;
28522
+ throw error;
28523
+ }
28524
+ try {
28525
+ const stat = fstatSync(fd);
28526
+ return {
28527
+ device: stat.dev,
28528
+ inode: stat.ino,
28529
+ contents: stat.size <= 128 ? readFileSync(fd, "utf8") : ""
28530
+ };
28531
+ } finally {
28532
+ closeSync(fd);
28533
+ }
28534
+ }
28535
+ function matchesSnapshot(path, snapshot) {
28536
+ const current = readSnapshot(path);
28537
+ return current !== null && current.device === snapshot.device && current.inode === snapshot.inode && current.contents === snapshot.contents;
28538
+ }
28539
+ function removeOwnedLock(holder) {
28540
+ try {
28541
+ if (matchesSnapshot(holder.path, holder)) unlinkSync(holder.path);
28542
+ } catch {}
28543
+ }
27876
28544
  function installExitListenersOnce() {
27877
28545
  if (exitListenersInstalled) return;
27878
28546
  exitListenersInstalled = true;
27879
28547
  const cleanup = () => {
27880
- if (processHolder === null) return;
27881
- try {
27882
- unlinkSync(lockPath(processHolder.configDir));
27883
- } catch {}
27884
- processHolder = null;
28548
+ for (const holder of processHolders.values()) removeOwnedLock(holder);
28549
+ processHolders.clear();
27885
28550
  };
27886
28551
  process.on("exit", cleanup);
27887
28552
  for (const signal of [
@@ -27897,100 +28562,218 @@ function installExitListenersOnce() {
27897
28562
  process.on(signal, handler);
27898
28563
  }
27899
28564
  }
27900
- function lockPath(configDir) {
27901
- return join(configDir, LOCK_FILENAME);
27902
- }
27903
- /**
27904
- * `process.kill(pid, 0)` returns true if the process exists and we
27905
- * have permission to signal it. Throws ESRCH when the pid is gone,
27906
- * EPERM if it exists but isn't ours. Both "exists" cases mean we
27907
- * should NOT steal the lock; only ESRCH proves the holder is dead.
27908
- */
27909
28565
  function pidIsAlive(pid) {
27910
28566
  try {
27911
28567
  process.kill(pid, 0);
27912
28568
  return true;
27913
- } catch (err) {
27914
- if (err.code === "ESRCH") return false;
27915
- return true;
28569
+ } catch (error) {
28570
+ return error.code !== "ESRCH";
27916
28571
  }
27917
28572
  }
27918
- function readHolderPid(configDir) {
27919
- try {
27920
- const raw = readFileSync(lockPath(configDir), "utf8").trim();
27921
- const pid = Number.parseInt(raw, 10);
27922
- return Number.isFinite(pid) && pid > 0 ? pid : null;
27923
- } catch {
27924
- return null;
28573
+ function holderPid(snapshot) {
28574
+ const lines = snapshot?.contents.trim().split("\n");
28575
+ if (!lines || lines.length > 2 || !/^[1-9]\d*$/.test(lines[0] ?? "")) return null;
28576
+ if (lines.length === 2 && !/^[0-9a-f-]{36}$/.test(lines[1] ?? "")) return null;
28577
+ const pid = Number(lines[0]);
28578
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
28579
+ }
28580
+ function reclaimStaleLock(path, previous, temporary, contender) {
28581
+ const guards = [];
28582
+ const visited = /* @__PURE__ */ new Set();
28583
+ let target = previous;
28584
+ for (;;) {
28585
+ const guardPath = `${path}.reclaim-${createHash("sha256").update(`${target.device}:${target.inode}:${target.contents}`).digest("hex")}`;
28586
+ if (visited.has(guardPath)) throw new ChatLockContentionError(0);
28587
+ visited.add(guardPath);
28588
+ try {
28589
+ linkSync(temporary, guardPath);
28590
+ } catch (error) {
28591
+ if (error.code !== "EEXIST") throw error;
28592
+ const guard = readSnapshot(guardPath);
28593
+ const pid = holderPid(guard);
28594
+ if (!guard || pid === null || pidIsAlive(pid)) throw new ChatLockContentionError(pid ?? 0);
28595
+ guards.push({
28596
+ path: guardPath,
28597
+ snapshot: guard
28598
+ });
28599
+ target = guard;
28600
+ continue;
28601
+ }
28602
+ guards.push({
28603
+ path: guardPath,
28604
+ snapshot: contender
28605
+ });
28606
+ let removed = false;
28607
+ try {
28608
+ const pid = holderPid(previous);
28609
+ if (matchesSnapshot(path, previous) && pid !== null && !pidIsAlive(pid)) unlinkSync(path);
28610
+ removed = !matchesSnapshot(path, previous);
28611
+ } finally {
28612
+ if (removed) for (const guard of guards) try {
28613
+ if (matchesSnapshot(guard.path, guard.snapshot)) unlinkSync(guard.path);
28614
+ } catch {}
28615
+ }
28616
+ return;
27925
28617
  }
27926
28618
  }
27927
28619
  var ChatLockContentionError = class extends Error {
27928
28620
  constructor(holderPid) {
27929
- super(`Another \`primitive chat\` invocation (pid ${holderPid}) is in progress. Wait for it to finish, or kill it before retrying.`);
28621
+ super(holderPid > 0 ? `Another \`primitive chat\` invocation (pid ${holderPid}) is in progress. Wait for it to finish, or kill it before retrying.` : "The chat lock holder could not be verified. Retry after the current invocation finishes.");
27930
28622
  this.holderPid = holderPid;
27931
28623
  this.name = "ChatLockContentionError";
27932
28624
  }
27933
28625
  };
27934
- /**
27935
- * Acquire the chat-state mutex for this configDir. Returns a release
27936
- * function that is safe to call any number of times. Throws
27937
- * `ChatLockContentionError` if another live process holds the lock.
27938
- */
27939
- function acquireChatLock(configDir) {
27940
- if (processHolder?.configDir === configDir) {
27941
- processHolder.depth += 1;
27942
- let released = false;
27943
- return () => {
27944
- if (released) return;
27945
- released = true;
27946
- if (processHolder !== null) processHolder.depth -= 1;
27947
- };
28626
+ function releaseHolder(holder) {
28627
+ let released = false;
28628
+ return () => {
28629
+ if (released) return;
28630
+ released = true;
28631
+ if (processHolders.get(holder.path) !== holder) return;
28632
+ holder.depth -= 1;
28633
+ if (holder.depth === 0) {
28634
+ removeOwnedLock(holder);
28635
+ processHolders.delete(holder.path);
28636
+ }
28637
+ };
28638
+ }
28639
+ /** Acquire synchronously; the same scope is reentrant within this process. */
28640
+ function acquireChatLock(configDir, scope) {
28641
+ const path = lockPath(configDir, scope);
28642
+ const held = processHolders.get(path);
28643
+ if (held) {
28644
+ if (!matchesSnapshot(path, held)) throw new ChatLockContentionError(0);
28645
+ held.depth += 1;
28646
+ return releaseHolder(held);
27948
28647
  }
27949
- mkdirSync(configDir, {
28648
+ mkdirSync(resolve(configDir), {
27950
28649
  mode: 448,
27951
28650
  recursive: true
27952
28651
  });
27953
- for (let attempt = 0; attempt < 2; attempt++) {
27954
- let fd;
28652
+ const contents = `${process.pid}\n${randomUUID()}\n`;
28653
+ const temporary = `${path}.${randomUUID()}.tmp`;
28654
+ const fd = openSync(temporary, "wx", 384);
28655
+ let snapshot;
28656
+ try {
28657
+ writeFileSync(fd, contents);
28658
+ const stat = fstatSync(fd);
28659
+ snapshot = {
28660
+ device: stat.dev,
28661
+ inode: stat.ino,
28662
+ contents
28663
+ };
28664
+ } catch (error) {
28665
+ closeSync(fd);
28666
+ unlinkSync(temporary);
28667
+ throw error;
28668
+ }
28669
+ closeSync(fd);
28670
+ const publish = () => {
27955
28671
  try {
27956
- fd = openSync(lockPath(configDir), "wx", 384);
27957
- } catch (err) {
27958
- if (err.code !== "EEXIST") throw err;
27959
- const holder = readHolderPid(configDir);
27960
- if (holder === null || !pidIsAlive(holder)) {
27961
- try {
27962
- unlinkSync(lockPath(configDir));
27963
- } catch (unlinkErr) {
27964
- if (unlinkErr.code !== "ENOENT") throw unlinkErr;
27965
- }
27966
- continue;
27967
- }
27968
- throw new ChatLockContentionError(holder);
28672
+ linkSync(temporary, path);
28673
+ return true;
28674
+ } catch (error) {
28675
+ if (error.code === "EEXIST") return false;
28676
+ throw error;
27969
28677
  }
27970
- writeSync(fd, `${process.pid}\n`);
27971
- closeSync(fd);
27972
- processHolder = {
27973
- configDir,
28678
+ };
28679
+ try {
28680
+ let acquired = publish();
28681
+ if (!acquired) {
28682
+ const previous = readSnapshot(path);
28683
+ const pid = holderPid(previous);
28684
+ if (!previous || pid === null || pidIsAlive(pid)) throw new ChatLockContentionError(pid ?? 0);
28685
+ reclaimStaleLock(path, previous, temporary, snapshot);
28686
+ acquired = publish();
28687
+ if (!acquired) throw new ChatLockContentionError(holderPid(readSnapshot(path)) ?? 0);
28688
+ }
28689
+ const holder = {
28690
+ ...snapshot,
28691
+ path,
27974
28692
  depth: 1
27975
28693
  };
28694
+ processHolders.set(path, holder);
27976
28695
  installExitListenersOnce();
27977
- let released = false;
27978
- return () => {
27979
- if (released) return;
27980
- released = true;
27981
- if (processHolder?.configDir === configDir) {
27982
- processHolder.depth -= 1;
27983
- if (processHolder.depth <= 0) {
27984
- try {
27985
- unlinkSync(lockPath(configDir));
27986
- } catch {}
27987
- processHolder = null;
27988
- }
27989
- }
28696
+ return releaseHolder(holder);
28697
+ } finally {
28698
+ try {
28699
+ unlinkSync(temporary);
28700
+ } catch {}
28701
+ }
28702
+ }
28703
+ /**
28704
+ * Serialize short state transactions, including independent callers in this
28705
+ * process. Do not nest this API or hold its release function across network IO.
28706
+ */
28707
+ async function acquireChatStateLock(configDir) {
28708
+ const deadline = performance.now() + STATE_LOCK_TIMEOUT_MS;
28709
+ const path = lockPath(configDir);
28710
+ for (;;) try {
28711
+ if (processHolders.has(path)) throw new ChatLockContentionError(process.pid);
28712
+ return acquireChatLock(configDir);
28713
+ } catch (error) {
28714
+ if (!(error instanceof ChatLockContentionError)) throw error;
28715
+ const remaining = deadline - performance.now();
28716
+ if (remaining <= 0) throw error;
28717
+ await setTimeout$1(Math.min(STATE_LOCK_RETRY_MS, remaining));
28718
+ }
28719
+ }
28720
+ //#endregion
28721
+ //#region src/oclif/chat-receipt.ts
28722
+ /** Keep API credentials out of request fingerprints, including low-entropy local keys. */
28723
+ function chatCredentialIdentity(key, apiOrigin) {
28724
+ return key === void 0 ? void 0 : scryptSync(key, apiOrigin, 32).toString("hex");
28725
+ }
28726
+ function chatRequestHash(value) {
28727
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex");
28728
+ }
28729
+ function saveChatReceipt(receipt) {
28730
+ const temporary = `${receipt.path}.${process.pid}.${randomUUID()}.tmp`;
28731
+ try {
28732
+ writeFileSync(temporary, `${JSON.stringify(receipt.data)}\n`, { mode: 384 });
28733
+ renameSync(temporary, receipt.path);
28734
+ } finally {
28735
+ rmSync(temporary, { force: true });
28736
+ }
28737
+ }
28738
+ function parseReceipt(raw) {
28739
+ const value = JSON.parse(raw);
28740
+ if (!value || value.version !== 1 || typeof value.request_hash !== "string" || typeof value.sent_at !== "string" || !Number.isFinite(Date.parse(value.sent_at)) || typeof value.completed !== "boolean" || value.sent !== null && (!value.sent || typeof value.sent.id !== "string" || !value.sent.id || typeof value.sent.status !== "string")) throw new Error("Invalid chat receipt");
28741
+ return value;
28742
+ }
28743
+ /** Caller holds the request/parent lock until completion. Unknown POSTs never retry. */
28744
+ function beginChatReceipt(configDir, scope, requestHash) {
28745
+ const directory = join(configDir, "chat-receipts");
28746
+ mkdirSync(directory, {
28747
+ recursive: true,
28748
+ mode: 448
28749
+ });
28750
+ const path = join(directory, `${chatRequestHash(scope)}.json`);
28751
+ let previous;
28752
+ try {
28753
+ previous = parseReceipt(readFileSync(path, "utf8"));
28754
+ } catch (error) {
28755
+ if (error.code !== "ENOENT") throw new Error(`Cannot safely read chat receipt ${path}. Inspect it and sent history before retrying.`, { cause: error });
28756
+ }
28757
+ if (previous && !previous.completed) {
28758
+ if (previous.sent === null) throw new Error(`A previous send has an uncertain outcome. Inspect sent history before sending again. Receipt: ${path}`);
28759
+ if (previous.request_hash !== requestHash) throw new Error(`This reply still awaits a response to sent email ${previous.sent.id}. Retry the same message to resume, or inspect that send. Receipt: ${path}`);
28760
+ return {
28761
+ path,
28762
+ data: previous
27990
28763
  };
27991
28764
  }
27992
- /* v8 ignore next 4 -- the for-loop returns or throws on every iteration; the unreachable trailer keeps TS happy. */
27993
- throw new Error("acquireChatLock: exhausted retries (this is a bug — should not be reachable)");
28765
+ const receipt = {
28766
+ path,
28767
+ data: {
28768
+ version: 1,
28769
+ request_hash: requestHash,
28770
+ sent_at: (/* @__PURE__ */ new Date()).toISOString(),
28771
+ sent: null,
28772
+ completed: false
28773
+ }
28774
+ };
28775
+ saveChatReceipt(receipt);
28776
+ return receipt;
27994
28777
  }
27995
28778
  //#endregion
27996
28779
  //#region src/oclif/outbound-defaults.ts
@@ -28465,7 +29248,8 @@ function buildChatJsonEnvelope(context) {
28465
29248
  follow_up_commands: buildChatFollowUpCommands(context)
28466
29249
  };
28467
29250
  }
28468
- function persistActiveChat(params) {
29251
+ async function persistActiveChat(params) {
29252
+ const release = await acquireChatStateLock(params.configDir);
28469
29253
  try {
28470
29254
  return saveActiveChatState(params.configDir, {
28471
29255
  from: params.context.from,
@@ -28482,6 +29266,8 @@ function persistActiveChat(params) {
28482
29266
  const detail = error instanceof Error ? error.message : String(error);
28483
29267
  params.writeWarning?.(`Warning: could not save local chat state: ${detail}\n`);
28484
29268
  return null;
29269
+ } finally {
29270
+ release();
28485
29271
  }
28486
29272
  }
28487
29273
  /**
@@ -28636,7 +29422,12 @@ var ChatCommand = class ChatCommand extends Command {
28636
29422
  is less exact than strict matching. Use --strict-only when matching
28637
29423
  the wrong reply is worse than timing out. Progress is written to
28638
29424
  stderr while the CLI waits. Exits non-zero on timeout and prints
28639
- recovery commands when the send succeeded but no reply was returned.`;
29425
+ recovery commands when the send succeeded but no reply was returned.
29426
+
29427
+ Independent addressed chats can run concurrently. Replies reserve their
29428
+ selected local chat. The last successfully saved reply becomes active.
29429
+ Retrying an interrupted or timed-out request resumes its recorded send;
29430
+ an uncertain send outcome requires inspection instead of a blind resend.`;
28640
29431
  static summary = "Chat with an agent over email (send and wait for the reply)";
28641
29432
  static examples = [
28642
29433
  "<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
@@ -28721,12 +29512,6 @@ var ChatCommand = class ChatCommand extends Command {
28721
29512
  if (!message.trim()) throw cliError$6(replyMode ? "Reply body is empty." : "Message body is empty.");
28722
29513
  await runWithTiming(flags.time, async () => {
28723
29514
  let releaseLock;
28724
- try {
28725
- releaseLock = acquireChatLock(this.config.configDir);
28726
- } catch (err) {
28727
- if (err instanceof ChatLockContentionError) throw cliError$6(err.message);
28728
- throw err;
28729
- }
28730
29515
  try {
28731
29516
  const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
28732
29517
  apiKey: flags["api-key"],
@@ -28792,10 +29577,46 @@ var ChatCommand = class ChatCommand extends Command {
28792
29577
  from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
28793
29578
  subject = flags.subject ?? deriveSubject(message);
28794
29579
  }
28795
- const sentAtIso = (/* @__PURE__ */ new Date()).toISOString();
28796
- if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
29580
+ const requestHash = chatRequestHash({
29581
+ api: auth.apiBaseUrl,
29582
+ account: auth.credentials?.org_id ?? chatCredentialIdentity(auth.apiKey, auth.apiBaseUrl),
29583
+ from,
29584
+ recipient: args.recipient,
29585
+ subject,
29586
+ message,
29587
+ parent: parentReply?.id ?? flags["in-reply-to"],
29588
+ attachments
29589
+ });
29590
+ const scope = parentReply ? chatRequestHash({
29591
+ api: auth.apiBaseUrl,
29592
+ from,
29593
+ parent: parentReply.id
29594
+ }) : requestHash;
29595
+ try {
29596
+ releaseLock = acquireChatLock(this.config.configDir, scope);
29597
+ } catch (error) {
29598
+ if (error instanceof ChatLockContentionError) throw cliError$6(error.message);
29599
+ throw error;
29600
+ }
29601
+ if (flags["chat-local-id"] !== void 0) {
29602
+ const releaseState = await acquireChatStateLock(this.config.configDir);
29603
+ try {
29604
+ const current = loadChatConversationByLocalId(this.config.configDir, flags["chat-local-id"]);
29605
+ if (!current || current.last_reply_email_id !== parentReply?.id) throw cliError$6("This local chat advanced while preparing the reply. Retry to use its latest reply.");
29606
+ } finally {
29607
+ releaseState();
29608
+ }
29609
+ }
29610
+ const receipt = beginChatReceipt(this.config.configDir, scope, requestHash);
29611
+ const sentAtIso = receipt.data.sent_at;
29612
+ process.stderr.write(`Chat receipt: ${receipt.path}\n`);
29613
+ if (receipt.data.sent !== null) progress?.start("Resuming the recorded send without sending again");
29614
+ else if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
28797
29615
  else progress?.start(`Sending message to ${args.recipient}`);
28798
- const sendResult = parentReply !== void 0 ? await replyToEmail({
29616
+ const sendResult = receipt.data.sent !== null ? {
29617
+ data: { data: receipt.data.sent },
29618
+ error: void 0
29619
+ } : parentReply !== void 0 ? await replyToEmail({
28799
29620
  body: {
28800
29621
  body_text: message,
28801
29622
  from,
@@ -28817,6 +29638,20 @@ var ChatCommand = class ChatCommand extends Command {
28817
29638
  responseStyle: "fields"
28818
29639
  });
28819
29640
  if (sendResult.error) {
29641
+ const status = "response" in sendResult ? sendResult.response?.status : void 0;
29642
+ if (status !== void 0 && [
29643
+ 400,
29644
+ 401,
29645
+ 402,
29646
+ 403,
29647
+ 404,
29648
+ 413,
29649
+ 422,
29650
+ 429
29651
+ ].includes(status)) {
29652
+ receipt.data.completed = true;
29653
+ saveChatReceipt(receipt);
29654
+ }
28820
29655
  progress?.fail(replyMode ? "Reply send failed." : "Message send failed.");
28821
29656
  const errorPayload = extractErrorPayload(sendResult.error);
28822
29657
  writeErrorWithHints(errorPayload);
@@ -28832,6 +29667,8 @@ var ChatCommand = class ChatCommand extends Command {
28832
29667
  progress?.fail("Send succeeded but the API returned no data.");
28833
29668
  throw cliError$6("Send succeeded but the API returned no data.");
28834
29669
  }
29670
+ receipt.data.sent = sent;
29671
+ saveChatReceipt(receipt);
28835
29672
  const replyAddress = sent.from || from;
28836
29673
  const baseContext = {
28837
29674
  from: replyAddress,
@@ -28878,7 +29715,7 @@ var ChatCommand = class ChatCommand extends Command {
28878
29715
  matchStrategy: "strict",
28879
29716
  reply: detail
28880
29717
  };
28881
- const localChatId = persistActiveChat({
29718
+ const localChatId = await persistActiveChat({
28882
29719
  configDir: this.config.configDir,
28883
29720
  context: outputContext,
28884
29721
  preferredLocalId: flags["chat-local-id"],
@@ -28888,6 +29725,8 @@ var ChatCommand = class ChatCommand extends Command {
28888
29725
  ...outputContext,
28889
29726
  localChatId
28890
29727
  };
29728
+ receipt.data.completed = localChatId !== null;
29729
+ saveChatReceipt(receipt);
28891
29730
  if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
28892
29731
  else this.log(formatChatResponse(outputContext));
28893
29732
  return;
@@ -28942,7 +29781,7 @@ var ChatCommand = class ChatCommand extends Command {
28942
29781
  matchStrategy: replyResult.matchStrategy,
28943
29782
  reply: replyResult.reply
28944
29783
  };
28945
- const localChatId = persistActiveChat({
29784
+ const localChatId = await persistActiveChat({
28946
29785
  configDir: this.config.configDir,
28947
29786
  context: outputContext,
28948
29787
  preferredLocalId: flags["chat-local-id"],
@@ -28952,10 +29791,12 @@ var ChatCommand = class ChatCommand extends Command {
28952
29791
  ...outputContext,
28953
29792
  localChatId
28954
29793
  };
29794
+ receipt.data.completed = localChatId !== null;
29795
+ saveChatReceipt(receipt);
28955
29796
  if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
28956
29797
  else this.log(formatChatResponse(outputContext));
28957
29798
  } finally {
28958
- releaseLock();
29799
+ releaseLock?.();
28959
29800
  }
28960
29801
  });
28961
29802
  }
@@ -29035,15 +29876,19 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
29035
29876
  if (flags.id === void 0 && args.message !== void 0 && positionalLocalId === null) throw cliError$6("When passing two positional arguments to `primitive chat reply`, the first must be a local chat id. Use `primitive chat reply '<message>'` for the active chat or `primitive chat reply --id <id> '<message>'` for a specific chat.");
29036
29877
  if (flags.id !== void 0 && args.message !== void 0) throw cliError$6("With --id, pass the reply body as a single positional argument or pipe it via stdin.");
29037
29878
  let release;
29038
- try {
29039
- release = acquireChatLock(this.config.configDir);
29040
- } catch (err) {
29041
- if (err instanceof ChatLockContentionError) throw cliError$6(err.message);
29042
- throw err;
29043
- }
29044
29879
  try {
29045
29880
  const localId = flags.id ?? (typeof positionalLocalId === "number" ? positionalLocalId : void 0);
29046
- const state = localId === void 0 ? loadActiveChatState(this.config.configDir) : loadChatConversationByLocalId(this.config.configDir, localId);
29881
+ const releaseState = await acquireChatStateLock(this.config.configDir);
29882
+ let state;
29883
+ try {
29884
+ state = localId === void 0 ? loadActiveChatState(this.config.configDir) : loadChatConversationByLocalId(this.config.configDir, localId);
29885
+ if (state) release = acquireChatLock(this.config.configDir, `local:${state.local_id}`);
29886
+ } catch (error) {
29887
+ if (error instanceof ChatLockContentionError) throw cliError$6(error.message);
29888
+ throw error;
29889
+ } finally {
29890
+ releaseState();
29891
+ }
29047
29892
  if (!state) throw cliError$6(localId === void 0 ? "No open chat. Start one with `primitive chat <email> '<message>'`." : `No local chat ${localId}. Start one with \`primitive chat <email> '<message>'\` or omit --id to use the active chat.`);
29048
29893
  const message = args.message !== void 0 ? args.message : args.idOrMessage !== void 0 && args.idOrMessage !== "" ? args.idOrMessage : await readStdinToString("No reply body provided. Pass the reply body as a positional argument or pipe it via stdin.");
29049
29894
  if (!message.trim()) throw cliError$6("Reply body is empty.");
@@ -29075,7 +29920,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
29075
29920
  if (flags.time) argv.push("--time");
29076
29921
  await ChatCommand.run(argv, { root: this.config.root });
29077
29922
  } finally {
29078
- release();
29923
+ release?.();
29079
29924
  }
29080
29925
  }
29081
29926
  };
@@ -31226,8 +32071,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
31226
32071
  name: "Primitive Team",
31227
32072
  url: "https://primitive.dev"
31228
32073
  };
31229
- const SDK_VERSION_RANGE = "^1.23.0";
31230
- const CLI_VERSION_RANGE = "^1.23.0";
32074
+ const SDK_VERSION_RANGE = "^1.25.0";
32075
+ const CLI_VERSION_RANGE = "^1.25.0";
31231
32076
  const ESBUILD_VERSION_RANGE = "^0.27.0";
31232
32077
  function renderHandler() {
31233
32078
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
@@ -33475,7 +34320,7 @@ function createListenHandler(options = {}) {
33475
34320
  validateListenHandlerOptions(options);
33476
34321
  return async (delivery, signal) => {
33477
34322
  signal.throwIfAborted();
33478
- const start = performance.now();
34323
+ const start = performance$1.now();
33479
34324
  const work = new AbortController();
33480
34325
  let timedOut = false;
33481
34326
  const cancel = () => work.abort(signal.reason);
@@ -33484,7 +34329,7 @@ function createListenHandler(options = {}) {
33484
34329
  timedOut = true;
33485
34330
  work.abort(/* @__PURE__ */ new Error("Event handler timed out."));
33486
34331
  }, HANDLER_TIMEOUT_MS);
33487
- const duration = () => Math.min(HANDLER_TIMEOUT_MS, Math.max(0, Math.round(performance.now() - start)));
34332
+ const duration = () => Math.min(HANDLER_TIMEOUT_MS, Math.max(0, Math.round(performance$1.now() - start)));
33488
34333
  try {
33489
34334
  if (options.exec !== void 0) {
33490
34335
  const code = await execute(options.exec, delivery, options.stderr ?? process.stderr, work.signal);
@@ -36122,7 +36967,7 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
36122
36967
  }
36123
36968
  };
36124
36969
  //#endregion
36125
- //#region ../sdk-node/dist/payloads-CQtSsUT6.js
36970
+ //#region ../sdk-node/dist/payloads-BfJAtqW9.js
36126
36971
  /**
36127
36972
  * Load a Node builtin at call time. Exported for direct testing of the
36128
36973
  * unavailable-module error path; production code goes through the typed
@@ -36155,7 +37000,9 @@ const MANIFEST_VERSION = 1;
36155
37000
  const CHUNK_KDF_INFO = "payloads-chunk";
36156
37001
  const OBJECT_ID_BYTES = 16;
36157
37002
  const CEK_BYTES = 32;
36158
- const subtle = globalThis.crypto.subtle;
37003
+ function subtle() {
37004
+ return globalThis.crypto.subtle;
37005
+ }
36159
37006
  const textEncoder = new TextEncoder();
36160
37007
  function randomBytes(length) {
36161
37008
  const out = new Uint8Array(length);
@@ -36175,7 +37022,7 @@ function fromHex(hex) {
36175
37022
  return out;
36176
37023
  }
36177
37024
  async function contentHash(bytes) {
36178
- return new Uint8Array(await subtle.digest("SHA-256", bytes));
37025
+ return new Uint8Array(await subtle().digest("SHA-256", bytes));
36179
37026
  }
36180
37027
  async function contentHashHex(bytes) {
36181
37028
  return toHex(await contentHash(bytes));
@@ -36199,9 +37046,9 @@ async function merkleRoot(leafHashesHex) {
36199
37046
  return toHex(level[0]);
36200
37047
  }
36201
37048
  async function deriveChunkKey(cek, index) {
36202
- const baseKey = await subtle.importKey("raw", cek, "HKDF", false, ["deriveKey"]);
37049
+ const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
36203
37050
  const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
36204
- return subtle.deriveKey({
37051
+ return subtle().deriveKey({
36205
37052
  name: "HKDF",
36206
37053
  hash: "SHA-256",
36207
37054
  salt: new Uint8Array(32),
@@ -36224,7 +37071,7 @@ function chunkAad(objectId, index) {
36224
37071
  }
36225
37072
  async function encryptChunk(cek, objectId, index, plaintext) {
36226
37073
  const key = await deriveChunkKey(cek, index);
36227
- const ct = await subtle.encrypt({
37074
+ const ct = await subtle().encrypt({
36228
37075
  name: "AES-GCM",
36229
37076
  iv: chunkNonce(index),
36230
37077
  additionalData: chunkAad(objectId, index)
@@ -36233,7 +37080,7 @@ async function encryptChunk(cek, objectId, index, plaintext) {
36233
37080
  }
36234
37081
  async function decryptChunk(cek, objectId, index, ciphertext) {
36235
37082
  const key = await deriveChunkKey(cek, index);
36236
- const pt = await subtle.decrypt({
37083
+ const pt = await subtle().decrypt({
36237
37084
  name: "AES-GCM",
36238
37085
  iv: chunkNonce(index),
36239
37086
  additionalData: chunkAad(objectId, index)
@@ -40883,6 +41730,7 @@ const CANONICAL_OPERATION_ALIASES = {
40883
41730
  "sending:permissions": "sending:get-send-permissions",
40884
41731
  "sending:reply": "sending:reply-to-email",
40885
41732
  "sending:send": "sending:send-email",
41733
+ "sent:delete": "sending:delete-sent-email",
40886
41734
  "sent:get": "sending:get-sent-email",
40887
41735
  "sent:list": "sending:list-sent-emails",
40888
41736
  "threads:get": "threads:get-thread",