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.
- package/README.md +19 -0
- package/dist/oclif/index.js +1000 -152
- package/package.json +7 -4
package/dist/oclif/index.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
27832
|
-
*
|
|
27833
|
-
*
|
|
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
|
-
|
|
27861
|
-
|
|
27862
|
-
|
|
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
|
-
|
|
27881
|
-
|
|
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 (
|
|
27914
|
-
|
|
27915
|
-
return true;
|
|
28569
|
+
} catch (error) {
|
|
28570
|
+
return error.code !== "ESRCH";
|
|
27916
28571
|
}
|
|
27917
28572
|
}
|
|
27918
|
-
function
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
27924
|
-
|
|
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
|
-
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
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
|
-
|
|
27954
|
-
|
|
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
|
-
|
|
27957
|
-
|
|
27958
|
-
|
|
27959
|
-
|
|
27960
|
-
|
|
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
|
-
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
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
|
-
|
|
27978
|
-
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
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
|
-
|
|
27993
|
-
|
|
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
|
|
28796
|
-
|
|
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 =
|
|
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
|
|
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.
|
|
31230
|
-
const CLI_VERSION_RANGE = "^1.
|
|
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-
|
|
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
|
-
|
|
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",
|