primitive 1.23.0 → 1.24.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/dist/oclif/index.js +765 -152
- package/package.json +3 -3
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";
|
|
@@ -66,7 +67,9 @@ var sdk_gen_exports = /* @__PURE__ */ __exportAll({
|
|
|
66
67
|
discardEmailContent: () => discardEmailContent,
|
|
67
68
|
downloadAttachments: () => downloadAttachments,
|
|
68
69
|
downloadDomainZoneFile: () => downloadDomainZoneFile,
|
|
70
|
+
downloadEmailAttachmentPart: () => downloadEmailAttachmentPart,
|
|
69
71
|
downloadRawEmail: () => downloadRawEmail,
|
|
72
|
+
downloadSentAttachmentPart: () => downloadSentAttachmentPart,
|
|
70
73
|
getAccount: () => getAccount,
|
|
71
74
|
getAgent: () => getAgent,
|
|
72
75
|
getChallenge: () => getChallenge,
|
|
@@ -784,6 +787,49 @@ const downloadAttachments = (options) => (options.client ?? client).get({
|
|
|
784
787
|
...options
|
|
785
788
|
});
|
|
786
789
|
/**
|
|
790
|
+
* Download one inbound email attachment
|
|
791
|
+
*
|
|
792
|
+
* Pending service release. This contract does not establish live availability.
|
|
793
|
+
* Downloads the original bytes of one ordinary email attachment, selected by
|
|
794
|
+
* its metadata `part_index`, not its position in an attachments array. Refresh
|
|
795
|
+
* the email detail after `attachment_changed` before choosing an index again.
|
|
796
|
+
* Uses bearer authentication only; signed raw-email download tokens are not accepted.
|
|
797
|
+
* A paired-agent credential may read only a message addressed to its claimed address.
|
|
798
|
+
* Function credentials are denied. Other bearer credentials retain their
|
|
799
|
+
* existing email access boundaries. This endpoint serves email bytes only.
|
|
800
|
+
*
|
|
801
|
+
*/
|
|
802
|
+
const downloadEmailAttachmentPart = (options) => (options.client ?? client).get({
|
|
803
|
+
security: [{
|
|
804
|
+
scheme: "bearer",
|
|
805
|
+
type: "http"
|
|
806
|
+
}],
|
|
807
|
+
url: "/emails/{id}/attachments/{part_index}",
|
|
808
|
+
...options
|
|
809
|
+
});
|
|
810
|
+
/**
|
|
811
|
+
* Download one sent email attachment
|
|
812
|
+
*
|
|
813
|
+
* Pending service release. This contract does not establish live availability.
|
|
814
|
+
* Downloads the original bytes of one ordinary email attachment, selected by
|
|
815
|
+
* its metadata `part_index`, not its position in an attachments array. Refresh
|
|
816
|
+
* the email detail after `attachment_changed` before choosing an index again.
|
|
817
|
+
* Uses bearer authentication only; signed raw-email download tokens are not accepted.
|
|
818
|
+
* A paired-agent credential may read only mail sent from its claimed address.
|
|
819
|
+
* Receiving a copy does not authorize downloading another sender's sent content.
|
|
820
|
+
* Function credentials are denied. Other bearer credentials retain their
|
|
821
|
+
* existing email access boundaries. This endpoint serves email bytes only.
|
|
822
|
+
*
|
|
823
|
+
*/
|
|
824
|
+
const downloadSentAttachmentPart = (options) => (options.client ?? client).get({
|
|
825
|
+
security: [{
|
|
826
|
+
scheme: "bearer",
|
|
827
|
+
type: "http"
|
|
828
|
+
}],
|
|
829
|
+
url: "/sent-emails/{id}/attachments/{part_index}",
|
|
830
|
+
...options
|
|
831
|
+
});
|
|
832
|
+
/**
|
|
787
833
|
* Reply to an inbound email
|
|
788
834
|
*
|
|
789
835
|
* Sends an outbound reply to the inbound email identified by `id`.
|
|
@@ -3887,6 +3933,100 @@ const openapiDocument = {
|
|
|
3887
3933
|
}
|
|
3888
3934
|
}
|
|
3889
3935
|
},
|
|
3936
|
+
"/emails/{id}/attachments/{part_index}": {
|
|
3937
|
+
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }, { "$ref": "#/components/parameters/AttachmentPartIndex" }],
|
|
3938
|
+
"get": {
|
|
3939
|
+
"operationId": "downloadEmailAttachmentPart",
|
|
3940
|
+
"summary": "Download one inbound email attachment",
|
|
3941
|
+
"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",
|
|
3942
|
+
"tags": ["Emails"],
|
|
3943
|
+
"security": [{ "BearerAuth": [] }],
|
|
3944
|
+
"responses": {
|
|
3945
|
+
"200": { "$ref": "#/components/responses/AttachmentPart" },
|
|
3946
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
3947
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3948
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
3949
|
+
"404": {
|
|
3950
|
+
"description": "Missing or inaccessible message, or missing attachment part",
|
|
3951
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3952
|
+
},
|
|
3953
|
+
"409": {
|
|
3954
|
+
"description": "attachment_changed; refresh email detail before selecting a part again",
|
|
3955
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3956
|
+
},
|
|
3957
|
+
"410": {
|
|
3958
|
+
"description": "content_discarded; the original content is no longer available",
|
|
3959
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3960
|
+
},
|
|
3961
|
+
"413": {
|
|
3962
|
+
"description": "attachment_limit_exceeded; the attachment exceeds download limits",
|
|
3963
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3964
|
+
},
|
|
3965
|
+
"502": {
|
|
3966
|
+
"description": "attachment_integrity_failed; stored content failed integrity validation",
|
|
3967
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3968
|
+
},
|
|
3969
|
+
"503": {
|
|
3970
|
+
"description": "attachment_not_ready or attachment_storage_unavailable; retry after the indicated delay",
|
|
3971
|
+
"headers": { "Retry-After": {
|
|
3972
|
+
"description": "Delay in seconds before retrying this read",
|
|
3973
|
+
"schema": {
|
|
3974
|
+
"type": "integer",
|
|
3975
|
+
"minimum": 0
|
|
3976
|
+
}
|
|
3977
|
+
} },
|
|
3978
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
},
|
|
3983
|
+
"/sent-emails/{id}/attachments/{part_index}": {
|
|
3984
|
+
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }, { "$ref": "#/components/parameters/AttachmentPartIndex" }],
|
|
3985
|
+
"get": {
|
|
3986
|
+
"operationId": "downloadSentAttachmentPart",
|
|
3987
|
+
"summary": "Download one sent email attachment",
|
|
3988
|
+
"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",
|
|
3989
|
+
"tags": ["Sending"],
|
|
3990
|
+
"security": [{ "BearerAuth": [] }],
|
|
3991
|
+
"responses": {
|
|
3992
|
+
"200": { "$ref": "#/components/responses/AttachmentPart" },
|
|
3993
|
+
"400": { "$ref": "#/components/responses/ValidationError" },
|
|
3994
|
+
"401": { "$ref": "#/components/responses/Unauthorized" },
|
|
3995
|
+
"403": { "$ref": "#/components/responses/Forbidden" },
|
|
3996
|
+
"404": {
|
|
3997
|
+
"description": "Missing or inaccessible message, or missing attachment part",
|
|
3998
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
3999
|
+
},
|
|
4000
|
+
"409": {
|
|
4001
|
+
"description": "attachment_changed; refresh email detail before selecting a part again",
|
|
4002
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
4003
|
+
},
|
|
4004
|
+
"410": {
|
|
4005
|
+
"description": "content_discarded; the original content is no longer available",
|
|
4006
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
4007
|
+
},
|
|
4008
|
+
"413": {
|
|
4009
|
+
"description": "attachment_limit_exceeded; the attachment exceeds download limits",
|
|
4010
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
4011
|
+
},
|
|
4012
|
+
"502": {
|
|
4013
|
+
"description": "attachment_integrity_failed; stored content failed integrity validation",
|
|
4014
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
4015
|
+
},
|
|
4016
|
+
"503": {
|
|
4017
|
+
"description": "attachment_not_ready or attachment_storage_unavailable; retry after the indicated delay",
|
|
4018
|
+
"headers": { "Retry-After": {
|
|
4019
|
+
"description": "Delay in seconds before retrying this read",
|
|
4020
|
+
"schema": {
|
|
4021
|
+
"type": "integer",
|
|
4022
|
+
"minimum": 0
|
|
4023
|
+
}
|
|
4024
|
+
} },
|
|
4025
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
},
|
|
3890
4030
|
"/emails/{id}/reply": {
|
|
3891
4031
|
"parameters": [{ "$ref": "#/components/parameters/ResourceId" }],
|
|
3892
4032
|
"post": {
|
|
@@ -6570,6 +6710,18 @@ const openapiDocument = {
|
|
|
6570
6710
|
}
|
|
6571
6711
|
},
|
|
6572
6712
|
"parameters": {
|
|
6713
|
+
"AttachmentPartIndex": {
|
|
6714
|
+
"name": "part_index",
|
|
6715
|
+
"in": "path",
|
|
6716
|
+
"required": true,
|
|
6717
|
+
"description": "The attachment metadata `part_index`, not its offset in the attachments array",
|
|
6718
|
+
"schema": {
|
|
6719
|
+
"type": "integer",
|
|
6720
|
+
"format": "int32",
|
|
6721
|
+
"minimum": 0,
|
|
6722
|
+
"maximum": 2147483647
|
|
6723
|
+
}
|
|
6724
|
+
},
|
|
6573
6725
|
"ResourceId": {
|
|
6574
6726
|
"name": "id",
|
|
6575
6727
|
"in": "path",
|
|
@@ -6640,6 +6792,33 @@ const openapiDocument = {
|
|
|
6640
6792
|
}
|
|
6641
6793
|
},
|
|
6642
6794
|
"responses": {
|
|
6795
|
+
"AttachmentPart": {
|
|
6796
|
+
"description": "Original attachment bytes; never a JSON envelope or a base64 string",
|
|
6797
|
+
"content": { "application/octet-stream": { "schema": {
|
|
6798
|
+
"type": "string",
|
|
6799
|
+
"format": "binary"
|
|
6800
|
+
} } },
|
|
6801
|
+
"headers": {
|
|
6802
|
+
"X-Content-SHA256": {
|
|
6803
|
+
"description": "SHA-256 hex digest of the original bytes",
|
|
6804
|
+
"schema": {
|
|
6805
|
+
"type": "string",
|
|
6806
|
+
"pattern": "^[a-fA-F0-9]{64}$"
|
|
6807
|
+
}
|
|
6808
|
+
},
|
|
6809
|
+
"Content-Disposition": {
|
|
6810
|
+
"description": "Safe attachment disposition with a sanitized filename",
|
|
6811
|
+
"schema": { "type": "string" }
|
|
6812
|
+
},
|
|
6813
|
+
"Cache-Control": {
|
|
6814
|
+
"description": "Attachment responses are private and must not be stored by caches",
|
|
6815
|
+
"schema": {
|
|
6816
|
+
"type": "string",
|
|
6817
|
+
"example": "private, no-store"
|
|
6818
|
+
}
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
},
|
|
6643
6822
|
"PullContentGone": {
|
|
6644
6823
|
"description": "Queued content is no longer available",
|
|
6645
6824
|
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
|
|
@@ -7658,6 +7837,12 @@ const openapiDocument = {
|
|
|
7658
7837
|
"conflict",
|
|
7659
7838
|
"mx_conflict",
|
|
7660
7839
|
"not_scheduled",
|
|
7840
|
+
"attachment_changed",
|
|
7841
|
+
"content_discarded",
|
|
7842
|
+
"attachment_limit_exceeded",
|
|
7843
|
+
"attachment_integrity_failed",
|
|
7844
|
+
"attachment_not_ready",
|
|
7845
|
+
"attachment_storage_unavailable",
|
|
7661
7846
|
"outbound_disabled",
|
|
7662
7847
|
"cannot_send_from_domain",
|
|
7663
7848
|
"recipient_not_allowed",
|
|
@@ -10467,6 +10652,53 @@ const openapiDocument = {
|
|
|
10467
10652
|
"body_html": {
|
|
10468
10653
|
"type": ["string", "null"],
|
|
10469
10654
|
"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"
|
|
10655
|
+
},
|
|
10656
|
+
"attachments": {
|
|
10657
|
+
"type": "array",
|
|
10658
|
+
"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.",
|
|
10659
|
+
"items": {
|
|
10660
|
+
"type": "object",
|
|
10661
|
+
"description": "Metadata for one submitted inline attachment.",
|
|
10662
|
+
"additionalProperties": true,
|
|
10663
|
+
"properties": {
|
|
10664
|
+
"filename": { "type": ["string", "null"] },
|
|
10665
|
+
"content_type": { "type": "string" },
|
|
10666
|
+
"size_bytes": {
|
|
10667
|
+
"type": "integer",
|
|
10668
|
+
"minimum": 0,
|
|
10669
|
+
"maximum": 9007199254740991
|
|
10670
|
+
},
|
|
10671
|
+
"sha256": { "type": "string" },
|
|
10672
|
+
"part_index": {
|
|
10673
|
+
"type": "integer",
|
|
10674
|
+
"minimum": 0,
|
|
10675
|
+
"maximum": 9007199254740991
|
|
10676
|
+
},
|
|
10677
|
+
"tar_path": { "type": "string" }
|
|
10678
|
+
},
|
|
10679
|
+
"required": [
|
|
10680
|
+
"filename",
|
|
10681
|
+
"content_type",
|
|
10682
|
+
"size_bytes",
|
|
10683
|
+
"sha256",
|
|
10684
|
+
"part_index",
|
|
10685
|
+
"tar_path"
|
|
10686
|
+
]
|
|
10687
|
+
}
|
|
10688
|
+
},
|
|
10689
|
+
"attachments_size_bytes": {
|
|
10690
|
+
"type": "integer",
|
|
10691
|
+
"minimum": 0,
|
|
10692
|
+
"maximum": 9007199254740991,
|
|
10693
|
+
"description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
|
|
10694
|
+
},
|
|
10695
|
+
"attachments_complete": {
|
|
10696
|
+
"type": "boolean",
|
|
10697
|
+
"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."
|
|
10698
|
+
},
|
|
10699
|
+
"attachments_download_available": {
|
|
10700
|
+
"type": "boolean",
|
|
10701
|
+
"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
10702
|
}
|
|
10471
10703
|
}
|
|
10472
10704
|
}]
|
|
@@ -16256,6 +16488,38 @@ const operationManifest = [
|
|
|
16256
16488
|
"tag": "Emails",
|
|
16257
16489
|
"tagCommand": "emails"
|
|
16258
16490
|
},
|
|
16491
|
+
{
|
|
16492
|
+
"binaryResponse": true,
|
|
16493
|
+
"bodyRequired": false,
|
|
16494
|
+
"command": "download-email-attachment-part",
|
|
16495
|
+
"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",
|
|
16496
|
+
"hasJsonBody": false,
|
|
16497
|
+
"method": "GET",
|
|
16498
|
+
"operationId": "downloadEmailAttachmentPart",
|
|
16499
|
+
"path": "/emails/{id}/attachments/{part_index}",
|
|
16500
|
+
"pathParams": [{
|
|
16501
|
+
"description": "Resource UUID",
|
|
16502
|
+
"enum": null,
|
|
16503
|
+
"name": "id",
|
|
16504
|
+
"required": true,
|
|
16505
|
+
"type": "string"
|
|
16506
|
+
}, {
|
|
16507
|
+
"description": "The attachment metadata `part_index`, not its offset in the attachments array",
|
|
16508
|
+
"enum": null,
|
|
16509
|
+
"maximum": 2147483647,
|
|
16510
|
+
"minimum": 0,
|
|
16511
|
+
"name": "part_index",
|
|
16512
|
+
"required": true,
|
|
16513
|
+
"type": "integer"
|
|
16514
|
+
}],
|
|
16515
|
+
"queryParams": [],
|
|
16516
|
+
"requestSchema": null,
|
|
16517
|
+
"responseSchema": null,
|
|
16518
|
+
"sdkName": "downloadEmailAttachmentPart",
|
|
16519
|
+
"summary": "Download one inbound email attachment",
|
|
16520
|
+
"tag": "Emails",
|
|
16521
|
+
"tagCommand": "emails"
|
|
16522
|
+
},
|
|
16259
16523
|
{
|
|
16260
16524
|
"binaryResponse": true,
|
|
16261
16525
|
"bodyRequired": false,
|
|
@@ -23742,6 +24006,53 @@ const operationManifest = [
|
|
|
23742
24006
|
"body_html": {
|
|
23743
24007
|
"type": ["string", "null"],
|
|
23744
24008
|
"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"
|
|
24009
|
+
},
|
|
24010
|
+
"attachments": {
|
|
24011
|
+
"type": "array",
|
|
24012
|
+
"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.",
|
|
24013
|
+
"items": {
|
|
24014
|
+
"type": "object",
|
|
24015
|
+
"description": "Metadata for one submitted inline attachment.",
|
|
24016
|
+
"additionalProperties": true,
|
|
24017
|
+
"properties": {
|
|
24018
|
+
"filename": { "type": ["string", "null"] },
|
|
24019
|
+
"content_type": { "type": "string" },
|
|
24020
|
+
"size_bytes": {
|
|
24021
|
+
"type": "integer",
|
|
24022
|
+
"minimum": 0,
|
|
24023
|
+
"maximum": 9007199254740991
|
|
24024
|
+
},
|
|
24025
|
+
"sha256": { "type": "string" },
|
|
24026
|
+
"part_index": {
|
|
24027
|
+
"type": "integer",
|
|
24028
|
+
"minimum": 0,
|
|
24029
|
+
"maximum": 9007199254740991
|
|
24030
|
+
},
|
|
24031
|
+
"tar_path": { "type": "string" }
|
|
24032
|
+
},
|
|
24033
|
+
"required": [
|
|
24034
|
+
"filename",
|
|
24035
|
+
"content_type",
|
|
24036
|
+
"size_bytes",
|
|
24037
|
+
"sha256",
|
|
24038
|
+
"part_index",
|
|
24039
|
+
"tar_path"
|
|
24040
|
+
]
|
|
24041
|
+
}
|
|
24042
|
+
},
|
|
24043
|
+
"attachments_size_bytes": {
|
|
24044
|
+
"type": "integer",
|
|
24045
|
+
"minimum": 0,
|
|
24046
|
+
"maximum": 9007199254740991,
|
|
24047
|
+
"description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
|
|
24048
|
+
},
|
|
24049
|
+
"attachments_complete": {
|
|
24050
|
+
"type": "boolean",
|
|
24051
|
+
"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."
|
|
24052
|
+
},
|
|
24053
|
+
"attachments_download_available": {
|
|
24054
|
+
"type": "boolean",
|
|
24055
|
+
"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
24056
|
}
|
|
23746
24057
|
}
|
|
23747
24058
|
}]
|
|
@@ -23751,6 +24062,38 @@ const operationManifest = [
|
|
|
23751
24062
|
"tag": "Sending",
|
|
23752
24063
|
"tagCommand": "sending"
|
|
23753
24064
|
},
|
|
24065
|
+
{
|
|
24066
|
+
"binaryResponse": true,
|
|
24067
|
+
"bodyRequired": false,
|
|
24068
|
+
"command": "download-sent-attachment-part",
|
|
24069
|
+
"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",
|
|
24070
|
+
"hasJsonBody": false,
|
|
24071
|
+
"method": "GET",
|
|
24072
|
+
"operationId": "downloadSentAttachmentPart",
|
|
24073
|
+
"path": "/sent-emails/{id}/attachments/{part_index}",
|
|
24074
|
+
"pathParams": [{
|
|
24075
|
+
"description": "Resource UUID",
|
|
24076
|
+
"enum": null,
|
|
24077
|
+
"name": "id",
|
|
24078
|
+
"required": true,
|
|
24079
|
+
"type": "string"
|
|
24080
|
+
}, {
|
|
24081
|
+
"description": "The attachment metadata `part_index`, not its offset in the attachments array",
|
|
24082
|
+
"enum": null,
|
|
24083
|
+
"maximum": 2147483647,
|
|
24084
|
+
"minimum": 0,
|
|
24085
|
+
"name": "part_index",
|
|
24086
|
+
"required": true,
|
|
24087
|
+
"type": "integer"
|
|
24088
|
+
}],
|
|
24089
|
+
"queryParams": [],
|
|
24090
|
+
"requestSchema": null,
|
|
24091
|
+
"responseSchema": null,
|
|
24092
|
+
"sdkName": "downloadSentAttachmentPart",
|
|
24093
|
+
"summary": "Download one sent email attachment",
|
|
24094
|
+
"tag": "Sending",
|
|
24095
|
+
"tagCommand": "sending"
|
|
24096
|
+
},
|
|
23754
24097
|
{
|
|
23755
24098
|
"binaryResponse": false,
|
|
23756
24099
|
"bodyRequired": false,
|
|
@@ -24219,6 +24562,53 @@ const operationManifest = [
|
|
|
24219
24562
|
"body_html": {
|
|
24220
24563
|
"type": ["string", "null"],
|
|
24221
24564
|
"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"
|
|
24565
|
+
},
|
|
24566
|
+
"attachments": {
|
|
24567
|
+
"type": "array",
|
|
24568
|
+
"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.",
|
|
24569
|
+
"items": {
|
|
24570
|
+
"type": "object",
|
|
24571
|
+
"description": "Metadata for one submitted inline attachment.",
|
|
24572
|
+
"additionalProperties": true,
|
|
24573
|
+
"properties": {
|
|
24574
|
+
"filename": { "type": ["string", "null"] },
|
|
24575
|
+
"content_type": { "type": "string" },
|
|
24576
|
+
"size_bytes": {
|
|
24577
|
+
"type": "integer",
|
|
24578
|
+
"minimum": 0,
|
|
24579
|
+
"maximum": 9007199254740991
|
|
24580
|
+
},
|
|
24581
|
+
"sha256": { "type": "string" },
|
|
24582
|
+
"part_index": {
|
|
24583
|
+
"type": "integer",
|
|
24584
|
+
"minimum": 0,
|
|
24585
|
+
"maximum": 9007199254740991
|
|
24586
|
+
},
|
|
24587
|
+
"tar_path": { "type": "string" }
|
|
24588
|
+
},
|
|
24589
|
+
"required": [
|
|
24590
|
+
"filename",
|
|
24591
|
+
"content_type",
|
|
24592
|
+
"size_bytes",
|
|
24593
|
+
"sha256",
|
|
24594
|
+
"part_index",
|
|
24595
|
+
"tar_path"
|
|
24596
|
+
]
|
|
24597
|
+
}
|
|
24598
|
+
},
|
|
24599
|
+
"attachments_size_bytes": {
|
|
24600
|
+
"type": "integer",
|
|
24601
|
+
"minimum": 0,
|
|
24602
|
+
"maximum": 9007199254740991,
|
|
24603
|
+
"description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
|
|
24604
|
+
},
|
|
24605
|
+
"attachments_complete": {
|
|
24606
|
+
"type": "boolean",
|
|
24607
|
+
"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."
|
|
24608
|
+
},
|
|
24609
|
+
"attachments_download_available": {
|
|
24610
|
+
"type": "boolean",
|
|
24611
|
+
"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
24612
|
}
|
|
24223
24613
|
}
|
|
24224
24614
|
}]
|
|
@@ -24943,6 +25333,53 @@ const operationManifest = [
|
|
|
24943
25333
|
"body_html": {
|
|
24944
25334
|
"type": ["string", "null"],
|
|
24945
25335
|
"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"
|
|
25336
|
+
},
|
|
25337
|
+
"attachments": {
|
|
25338
|
+
"type": "array",
|
|
25339
|
+
"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.",
|
|
25340
|
+
"items": {
|
|
25341
|
+
"type": "object",
|
|
25342
|
+
"description": "Metadata for one submitted inline attachment.",
|
|
25343
|
+
"additionalProperties": true,
|
|
25344
|
+
"properties": {
|
|
25345
|
+
"filename": { "type": ["string", "null"] },
|
|
25346
|
+
"content_type": { "type": "string" },
|
|
25347
|
+
"size_bytes": {
|
|
25348
|
+
"type": "integer",
|
|
25349
|
+
"minimum": 0,
|
|
25350
|
+
"maximum": 9007199254740991
|
|
25351
|
+
},
|
|
25352
|
+
"sha256": { "type": "string" },
|
|
25353
|
+
"part_index": {
|
|
25354
|
+
"type": "integer",
|
|
25355
|
+
"minimum": 0,
|
|
25356
|
+
"maximum": 9007199254740991
|
|
25357
|
+
},
|
|
25358
|
+
"tar_path": { "type": "string" }
|
|
25359
|
+
},
|
|
25360
|
+
"required": [
|
|
25361
|
+
"filename",
|
|
25362
|
+
"content_type",
|
|
25363
|
+
"size_bytes",
|
|
25364
|
+
"sha256",
|
|
25365
|
+
"part_index",
|
|
25366
|
+
"tar_path"
|
|
25367
|
+
]
|
|
25368
|
+
}
|
|
25369
|
+
},
|
|
25370
|
+
"attachments_size_bytes": {
|
|
25371
|
+
"type": "integer",
|
|
25372
|
+
"minimum": 0,
|
|
25373
|
+
"maximum": 9007199254740991,
|
|
25374
|
+
"description": "Original decoded attachment byte total accepted for this send, not the compressed archive size. It may exceed the retained inline total after offloading."
|
|
25375
|
+
},
|
|
25376
|
+
"attachments_complete": {
|
|
25377
|
+
"type": "boolean",
|
|
25378
|
+
"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."
|
|
25379
|
+
},
|
|
25380
|
+
"attachments_download_available": {
|
|
25381
|
+
"type": "boolean",
|
|
25382
|
+
"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
25383
|
}
|
|
24947
25384
|
}
|
|
24948
25385
|
}]
|
|
@@ -27828,60 +28265,54 @@ function readAttachmentFiles(paths, readFile = readFileSync) {
|
|
|
27828
28265
|
//#endregion
|
|
27829
28266
|
//#region src/oclif/chat-lock.ts
|
|
27830
28267
|
/**
|
|
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.
|
|
28268
|
+
* Conversation locks cover one request/reply cycle. The default lock retains
|
|
28269
|
+
* its historical filename and protects short shared chat-state transactions.
|
|
28270
|
+
* Scope names are hashed so addresses and other identifiers stay out of paths.
|
|
27858
28271
|
*/
|
|
27859
28272
|
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
|
-
*/
|
|
28273
|
+
const STATE_LOCK_TIMEOUT_MS = 5e3;
|
|
28274
|
+
const STATE_LOCK_RETRY_MS = 25;
|
|
28275
|
+
const processHolders = /* @__PURE__ */ new Map();
|
|
27875
28276
|
let exitListenersInstalled = false;
|
|
28277
|
+
function lockPath(configDir, scope) {
|
|
28278
|
+
const name = scope === void 0 ? LOCK_FILENAME : `chat-${createHash("sha256").update(scope).digest("hex")}.lock`;
|
|
28279
|
+
return join(resolve(configDir), name);
|
|
28280
|
+
}
|
|
28281
|
+
function readSnapshot(path) {
|
|
28282
|
+
let fd;
|
|
28283
|
+
try {
|
|
28284
|
+
if (!lstatSync(path).isFile()) return null;
|
|
28285
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
28286
|
+
} catch (error) {
|
|
28287
|
+
if (error.code === "ENOENT") return null;
|
|
28288
|
+
throw error;
|
|
28289
|
+
}
|
|
28290
|
+
try {
|
|
28291
|
+
const stat = fstatSync(fd);
|
|
28292
|
+
return {
|
|
28293
|
+
device: stat.dev,
|
|
28294
|
+
inode: stat.ino,
|
|
28295
|
+
contents: stat.size <= 128 ? readFileSync(fd, "utf8") : ""
|
|
28296
|
+
};
|
|
28297
|
+
} finally {
|
|
28298
|
+
closeSync(fd);
|
|
28299
|
+
}
|
|
28300
|
+
}
|
|
28301
|
+
function matchesSnapshot(path, snapshot) {
|
|
28302
|
+
const current = readSnapshot(path);
|
|
28303
|
+
return current !== null && current.device === snapshot.device && current.inode === snapshot.inode && current.contents === snapshot.contents;
|
|
28304
|
+
}
|
|
28305
|
+
function removeOwnedLock(holder) {
|
|
28306
|
+
try {
|
|
28307
|
+
if (matchesSnapshot(holder.path, holder)) unlinkSync(holder.path);
|
|
28308
|
+
} catch {}
|
|
28309
|
+
}
|
|
27876
28310
|
function installExitListenersOnce() {
|
|
27877
28311
|
if (exitListenersInstalled) return;
|
|
27878
28312
|
exitListenersInstalled = true;
|
|
27879
28313
|
const cleanup = () => {
|
|
27880
|
-
|
|
27881
|
-
|
|
27882
|
-
unlinkSync(lockPath(processHolder.configDir));
|
|
27883
|
-
} catch {}
|
|
27884
|
-
processHolder = null;
|
|
28314
|
+
for (const holder of processHolders.values()) removeOwnedLock(holder);
|
|
28315
|
+
processHolders.clear();
|
|
27885
28316
|
};
|
|
27886
28317
|
process.on("exit", cleanup);
|
|
27887
28318
|
for (const signal of [
|
|
@@ -27897,100 +28328,218 @@ function installExitListenersOnce() {
|
|
|
27897
28328
|
process.on(signal, handler);
|
|
27898
28329
|
}
|
|
27899
28330
|
}
|
|
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
28331
|
function pidIsAlive(pid) {
|
|
27910
28332
|
try {
|
|
27911
28333
|
process.kill(pid, 0);
|
|
27912
28334
|
return true;
|
|
27913
|
-
} catch (
|
|
27914
|
-
|
|
27915
|
-
return true;
|
|
28335
|
+
} catch (error) {
|
|
28336
|
+
return error.code !== "ESRCH";
|
|
27916
28337
|
}
|
|
27917
28338
|
}
|
|
27918
|
-
function
|
|
27919
|
-
|
|
27920
|
-
|
|
27921
|
-
|
|
27922
|
-
|
|
27923
|
-
|
|
27924
|
-
|
|
28339
|
+
function holderPid(snapshot) {
|
|
28340
|
+
const lines = snapshot?.contents.trim().split("\n");
|
|
28341
|
+
if (!lines || lines.length > 2 || !/^[1-9]\d*$/.test(lines[0] ?? "")) return null;
|
|
28342
|
+
if (lines.length === 2 && !/^[0-9a-f-]{36}$/.test(lines[1] ?? "")) return null;
|
|
28343
|
+
const pid = Number(lines[0]);
|
|
28344
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
28345
|
+
}
|
|
28346
|
+
function reclaimStaleLock(path, previous, temporary, contender) {
|
|
28347
|
+
const guards = [];
|
|
28348
|
+
const visited = /* @__PURE__ */ new Set();
|
|
28349
|
+
let target = previous;
|
|
28350
|
+
for (;;) {
|
|
28351
|
+
const guardPath = `${path}.reclaim-${createHash("sha256").update(`${target.device}:${target.inode}:${target.contents}`).digest("hex")}`;
|
|
28352
|
+
if (visited.has(guardPath)) throw new ChatLockContentionError(0);
|
|
28353
|
+
visited.add(guardPath);
|
|
28354
|
+
try {
|
|
28355
|
+
linkSync(temporary, guardPath);
|
|
28356
|
+
} catch (error) {
|
|
28357
|
+
if (error.code !== "EEXIST") throw error;
|
|
28358
|
+
const guard = readSnapshot(guardPath);
|
|
28359
|
+
const pid = holderPid(guard);
|
|
28360
|
+
if (!guard || pid === null || pidIsAlive(pid)) throw new ChatLockContentionError(pid ?? 0);
|
|
28361
|
+
guards.push({
|
|
28362
|
+
path: guardPath,
|
|
28363
|
+
snapshot: guard
|
|
28364
|
+
});
|
|
28365
|
+
target = guard;
|
|
28366
|
+
continue;
|
|
28367
|
+
}
|
|
28368
|
+
guards.push({
|
|
28369
|
+
path: guardPath,
|
|
28370
|
+
snapshot: contender
|
|
28371
|
+
});
|
|
28372
|
+
let removed = false;
|
|
28373
|
+
try {
|
|
28374
|
+
const pid = holderPid(previous);
|
|
28375
|
+
if (matchesSnapshot(path, previous) && pid !== null && !pidIsAlive(pid)) unlinkSync(path);
|
|
28376
|
+
removed = !matchesSnapshot(path, previous);
|
|
28377
|
+
} finally {
|
|
28378
|
+
if (removed) for (const guard of guards) try {
|
|
28379
|
+
if (matchesSnapshot(guard.path, guard.snapshot)) unlinkSync(guard.path);
|
|
28380
|
+
} catch {}
|
|
28381
|
+
}
|
|
28382
|
+
return;
|
|
27925
28383
|
}
|
|
27926
28384
|
}
|
|
27927
28385
|
var ChatLockContentionError = class extends Error {
|
|
27928
28386
|
constructor(holderPid) {
|
|
27929
|
-
super(`Another \`primitive chat\` invocation (pid ${holderPid}) is in progress. Wait for it to finish, or kill it before retrying.`);
|
|
28387
|
+
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
28388
|
this.holderPid = holderPid;
|
|
27931
28389
|
this.name = "ChatLockContentionError";
|
|
27932
28390
|
}
|
|
27933
28391
|
};
|
|
27934
|
-
|
|
27935
|
-
|
|
27936
|
-
|
|
27937
|
-
|
|
27938
|
-
|
|
27939
|
-
|
|
27940
|
-
|
|
27941
|
-
|
|
27942
|
-
|
|
27943
|
-
|
|
27944
|
-
|
|
27945
|
-
|
|
27946
|
-
|
|
27947
|
-
|
|
28392
|
+
function releaseHolder(holder) {
|
|
28393
|
+
let released = false;
|
|
28394
|
+
return () => {
|
|
28395
|
+
if (released) return;
|
|
28396
|
+
released = true;
|
|
28397
|
+
if (processHolders.get(holder.path) !== holder) return;
|
|
28398
|
+
holder.depth -= 1;
|
|
28399
|
+
if (holder.depth === 0) {
|
|
28400
|
+
removeOwnedLock(holder);
|
|
28401
|
+
processHolders.delete(holder.path);
|
|
28402
|
+
}
|
|
28403
|
+
};
|
|
28404
|
+
}
|
|
28405
|
+
/** Acquire synchronously; the same scope is reentrant within this process. */
|
|
28406
|
+
function acquireChatLock(configDir, scope) {
|
|
28407
|
+
const path = lockPath(configDir, scope);
|
|
28408
|
+
const held = processHolders.get(path);
|
|
28409
|
+
if (held) {
|
|
28410
|
+
if (!matchesSnapshot(path, held)) throw new ChatLockContentionError(0);
|
|
28411
|
+
held.depth += 1;
|
|
28412
|
+
return releaseHolder(held);
|
|
27948
28413
|
}
|
|
27949
|
-
mkdirSync(configDir, {
|
|
28414
|
+
mkdirSync(resolve(configDir), {
|
|
27950
28415
|
mode: 448,
|
|
27951
28416
|
recursive: true
|
|
27952
28417
|
});
|
|
27953
|
-
|
|
27954
|
-
|
|
28418
|
+
const contents = `${process.pid}\n${randomUUID()}\n`;
|
|
28419
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
28420
|
+
const fd = openSync(temporary, "wx", 384);
|
|
28421
|
+
let snapshot;
|
|
28422
|
+
try {
|
|
28423
|
+
writeFileSync(fd, contents);
|
|
28424
|
+
const stat = fstatSync(fd);
|
|
28425
|
+
snapshot = {
|
|
28426
|
+
device: stat.dev,
|
|
28427
|
+
inode: stat.ino,
|
|
28428
|
+
contents
|
|
28429
|
+
};
|
|
28430
|
+
} catch (error) {
|
|
28431
|
+
closeSync(fd);
|
|
28432
|
+
unlinkSync(temporary);
|
|
28433
|
+
throw error;
|
|
28434
|
+
}
|
|
28435
|
+
closeSync(fd);
|
|
28436
|
+
const publish = () => {
|
|
27955
28437
|
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);
|
|
28438
|
+
linkSync(temporary, path);
|
|
28439
|
+
return true;
|
|
28440
|
+
} catch (error) {
|
|
28441
|
+
if (error.code === "EEXIST") return false;
|
|
28442
|
+
throw error;
|
|
27969
28443
|
}
|
|
27970
|
-
|
|
27971
|
-
|
|
27972
|
-
|
|
27973
|
-
|
|
28444
|
+
};
|
|
28445
|
+
try {
|
|
28446
|
+
let acquired = publish();
|
|
28447
|
+
if (!acquired) {
|
|
28448
|
+
const previous = readSnapshot(path);
|
|
28449
|
+
const pid = holderPid(previous);
|
|
28450
|
+
if (!previous || pid === null || pidIsAlive(pid)) throw new ChatLockContentionError(pid ?? 0);
|
|
28451
|
+
reclaimStaleLock(path, previous, temporary, snapshot);
|
|
28452
|
+
acquired = publish();
|
|
28453
|
+
if (!acquired) throw new ChatLockContentionError(holderPid(readSnapshot(path)) ?? 0);
|
|
28454
|
+
}
|
|
28455
|
+
const holder = {
|
|
28456
|
+
...snapshot,
|
|
28457
|
+
path,
|
|
27974
28458
|
depth: 1
|
|
27975
28459
|
};
|
|
28460
|
+
processHolders.set(path, holder);
|
|
27976
28461
|
installExitListenersOnce();
|
|
27977
|
-
|
|
27978
|
-
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
|
|
27986
|
-
|
|
27987
|
-
|
|
27988
|
-
|
|
27989
|
-
|
|
28462
|
+
return releaseHolder(holder);
|
|
28463
|
+
} finally {
|
|
28464
|
+
try {
|
|
28465
|
+
unlinkSync(temporary);
|
|
28466
|
+
} catch {}
|
|
28467
|
+
}
|
|
28468
|
+
}
|
|
28469
|
+
/**
|
|
28470
|
+
* Serialize short state transactions, including independent callers in this
|
|
28471
|
+
* process. Do not nest this API or hold its release function across network IO.
|
|
28472
|
+
*/
|
|
28473
|
+
async function acquireChatStateLock(configDir) {
|
|
28474
|
+
const deadline = performance.now() + STATE_LOCK_TIMEOUT_MS;
|
|
28475
|
+
const path = lockPath(configDir);
|
|
28476
|
+
for (;;) try {
|
|
28477
|
+
if (processHolders.has(path)) throw new ChatLockContentionError(process.pid);
|
|
28478
|
+
return acquireChatLock(configDir);
|
|
28479
|
+
} catch (error) {
|
|
28480
|
+
if (!(error instanceof ChatLockContentionError)) throw error;
|
|
28481
|
+
const remaining = deadline - performance.now();
|
|
28482
|
+
if (remaining <= 0) throw error;
|
|
28483
|
+
await setTimeout$1(Math.min(STATE_LOCK_RETRY_MS, remaining));
|
|
28484
|
+
}
|
|
28485
|
+
}
|
|
28486
|
+
//#endregion
|
|
28487
|
+
//#region src/oclif/chat-receipt.ts
|
|
28488
|
+
/** Keep API credentials out of request fingerprints, including low-entropy local keys. */
|
|
28489
|
+
function chatCredentialIdentity(key, apiOrigin) {
|
|
28490
|
+
return key === void 0 ? void 0 : scryptSync(key, apiOrigin, 32).toString("hex");
|
|
28491
|
+
}
|
|
28492
|
+
function chatRequestHash(value) {
|
|
28493
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
28494
|
+
}
|
|
28495
|
+
function saveChatReceipt(receipt) {
|
|
28496
|
+
const temporary = `${receipt.path}.${process.pid}.${randomUUID()}.tmp`;
|
|
28497
|
+
try {
|
|
28498
|
+
writeFileSync(temporary, `${JSON.stringify(receipt.data)}\n`, { mode: 384 });
|
|
28499
|
+
renameSync(temporary, receipt.path);
|
|
28500
|
+
} finally {
|
|
28501
|
+
rmSync(temporary, { force: true });
|
|
28502
|
+
}
|
|
28503
|
+
}
|
|
28504
|
+
function parseReceipt(raw) {
|
|
28505
|
+
const value = JSON.parse(raw);
|
|
28506
|
+
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");
|
|
28507
|
+
return value;
|
|
28508
|
+
}
|
|
28509
|
+
/** Caller holds the request/parent lock until completion. Unknown POSTs never retry. */
|
|
28510
|
+
function beginChatReceipt(configDir, scope, requestHash) {
|
|
28511
|
+
const directory = join(configDir, "chat-receipts");
|
|
28512
|
+
mkdirSync(directory, {
|
|
28513
|
+
recursive: true,
|
|
28514
|
+
mode: 448
|
|
28515
|
+
});
|
|
28516
|
+
const path = join(directory, `${chatRequestHash(scope)}.json`);
|
|
28517
|
+
let previous;
|
|
28518
|
+
try {
|
|
28519
|
+
previous = parseReceipt(readFileSync(path, "utf8"));
|
|
28520
|
+
} catch (error) {
|
|
28521
|
+
if (error.code !== "ENOENT") throw new Error(`Cannot safely read chat receipt ${path}. Inspect it and sent history before retrying.`, { cause: error });
|
|
28522
|
+
}
|
|
28523
|
+
if (previous && !previous.completed) {
|
|
28524
|
+
if (previous.sent === null) throw new Error(`A previous send has an uncertain outcome. Inspect sent history before sending again. Receipt: ${path}`);
|
|
28525
|
+
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}`);
|
|
28526
|
+
return {
|
|
28527
|
+
path,
|
|
28528
|
+
data: previous
|
|
27990
28529
|
};
|
|
27991
28530
|
}
|
|
27992
|
-
|
|
27993
|
-
|
|
28531
|
+
const receipt = {
|
|
28532
|
+
path,
|
|
28533
|
+
data: {
|
|
28534
|
+
version: 1,
|
|
28535
|
+
request_hash: requestHash,
|
|
28536
|
+
sent_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
28537
|
+
sent: null,
|
|
28538
|
+
completed: false
|
|
28539
|
+
}
|
|
28540
|
+
};
|
|
28541
|
+
saveChatReceipt(receipt);
|
|
28542
|
+
return receipt;
|
|
27994
28543
|
}
|
|
27995
28544
|
//#endregion
|
|
27996
28545
|
//#region src/oclif/outbound-defaults.ts
|
|
@@ -28465,7 +29014,8 @@ function buildChatJsonEnvelope(context) {
|
|
|
28465
29014
|
follow_up_commands: buildChatFollowUpCommands(context)
|
|
28466
29015
|
};
|
|
28467
29016
|
}
|
|
28468
|
-
function persistActiveChat(params) {
|
|
29017
|
+
async function persistActiveChat(params) {
|
|
29018
|
+
const release = await acquireChatStateLock(params.configDir);
|
|
28469
29019
|
try {
|
|
28470
29020
|
return saveActiveChatState(params.configDir, {
|
|
28471
29021
|
from: params.context.from,
|
|
@@ -28482,6 +29032,8 @@ function persistActiveChat(params) {
|
|
|
28482
29032
|
const detail = error instanceof Error ? error.message : String(error);
|
|
28483
29033
|
params.writeWarning?.(`Warning: could not save local chat state: ${detail}\n`);
|
|
28484
29034
|
return null;
|
|
29035
|
+
} finally {
|
|
29036
|
+
release();
|
|
28485
29037
|
}
|
|
28486
29038
|
}
|
|
28487
29039
|
/**
|
|
@@ -28636,7 +29188,12 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28636
29188
|
is less exact than strict matching. Use --strict-only when matching
|
|
28637
29189
|
the wrong reply is worse than timing out. Progress is written to
|
|
28638
29190
|
stderr while the CLI waits. Exits non-zero on timeout and prints
|
|
28639
|
-
recovery commands when the send succeeded but no reply was returned
|
|
29191
|
+
recovery commands when the send succeeded but no reply was returned.
|
|
29192
|
+
|
|
29193
|
+
Independent addressed chats can run concurrently. Replies reserve their
|
|
29194
|
+
selected local chat. The last successfully saved reply becomes active.
|
|
29195
|
+
Retrying an interrupted or timed-out request resumes its recorded send;
|
|
29196
|
+
an uncertain send outcome requires inspection instead of a blind resend.`;
|
|
28640
29197
|
static summary = "Chat with an agent over email (send and wait for the reply)";
|
|
28641
29198
|
static examples = [
|
|
28642
29199
|
"<%= config.bin %> chat help@agent.acme.dev 'how do I rotate my API key?'",
|
|
@@ -28721,12 +29278,6 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28721
29278
|
if (!message.trim()) throw cliError$6(replyMode ? "Reply body is empty." : "Message body is empty.");
|
|
28722
29279
|
await runWithTiming(flags.time, async () => {
|
|
28723
29280
|
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
29281
|
try {
|
|
28731
29282
|
const { apiClient, auth, baseUrlOverridden } = await createAuthenticatedCliApiClient({
|
|
28732
29283
|
apiKey: flags["api-key"],
|
|
@@ -28792,10 +29343,46 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28792
29343
|
from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
28793
29344
|
subject = flags.subject ?? deriveSubject(message);
|
|
28794
29345
|
}
|
|
28795
|
-
const
|
|
28796
|
-
|
|
29346
|
+
const requestHash = chatRequestHash({
|
|
29347
|
+
api: auth.apiBaseUrl,
|
|
29348
|
+
account: auth.credentials?.org_id ?? chatCredentialIdentity(auth.apiKey, auth.apiBaseUrl),
|
|
29349
|
+
from,
|
|
29350
|
+
recipient: args.recipient,
|
|
29351
|
+
subject,
|
|
29352
|
+
message,
|
|
29353
|
+
parent: parentReply?.id ?? flags["in-reply-to"],
|
|
29354
|
+
attachments
|
|
29355
|
+
});
|
|
29356
|
+
const scope = parentReply ? chatRequestHash({
|
|
29357
|
+
api: auth.apiBaseUrl,
|
|
29358
|
+
from,
|
|
29359
|
+
parent: parentReply.id
|
|
29360
|
+
}) : requestHash;
|
|
29361
|
+
try {
|
|
29362
|
+
releaseLock = acquireChatLock(this.config.configDir, scope);
|
|
29363
|
+
} catch (error) {
|
|
29364
|
+
if (error instanceof ChatLockContentionError) throw cliError$6(error.message);
|
|
29365
|
+
throw error;
|
|
29366
|
+
}
|
|
29367
|
+
if (flags["chat-local-id"] !== void 0) {
|
|
29368
|
+
const releaseState = await acquireChatStateLock(this.config.configDir);
|
|
29369
|
+
try {
|
|
29370
|
+
const current = loadChatConversationByLocalId(this.config.configDir, flags["chat-local-id"]);
|
|
29371
|
+
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.");
|
|
29372
|
+
} finally {
|
|
29373
|
+
releaseState();
|
|
29374
|
+
}
|
|
29375
|
+
}
|
|
29376
|
+
const receipt = beginChatReceipt(this.config.configDir, scope, requestHash);
|
|
29377
|
+
const sentAtIso = receipt.data.sent_at;
|
|
29378
|
+
process.stderr.write(`Chat receipt: ${receipt.path}\n`);
|
|
29379
|
+
if (receipt.data.sent !== null) progress?.start("Resuming the recorded send without sending again");
|
|
29380
|
+
else if (replyMode) progress?.update(`Sending reply to ${args.recipient}`);
|
|
28797
29381
|
else progress?.start(`Sending message to ${args.recipient}`);
|
|
28798
|
-
const sendResult =
|
|
29382
|
+
const sendResult = receipt.data.sent !== null ? {
|
|
29383
|
+
data: { data: receipt.data.sent },
|
|
29384
|
+
error: void 0
|
|
29385
|
+
} : parentReply !== void 0 ? await replyToEmail({
|
|
28799
29386
|
body: {
|
|
28800
29387
|
body_text: message,
|
|
28801
29388
|
from,
|
|
@@ -28817,6 +29404,20 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28817
29404
|
responseStyle: "fields"
|
|
28818
29405
|
});
|
|
28819
29406
|
if (sendResult.error) {
|
|
29407
|
+
const status = "response" in sendResult ? sendResult.response?.status : void 0;
|
|
29408
|
+
if (status !== void 0 && [
|
|
29409
|
+
400,
|
|
29410
|
+
401,
|
|
29411
|
+
402,
|
|
29412
|
+
403,
|
|
29413
|
+
404,
|
|
29414
|
+
413,
|
|
29415
|
+
422,
|
|
29416
|
+
429
|
|
29417
|
+
].includes(status)) {
|
|
29418
|
+
receipt.data.completed = true;
|
|
29419
|
+
saveChatReceipt(receipt);
|
|
29420
|
+
}
|
|
28820
29421
|
progress?.fail(replyMode ? "Reply send failed." : "Message send failed.");
|
|
28821
29422
|
const errorPayload = extractErrorPayload(sendResult.error);
|
|
28822
29423
|
writeErrorWithHints(errorPayload);
|
|
@@ -28832,6 +29433,8 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28832
29433
|
progress?.fail("Send succeeded but the API returned no data.");
|
|
28833
29434
|
throw cliError$6("Send succeeded but the API returned no data.");
|
|
28834
29435
|
}
|
|
29436
|
+
receipt.data.sent = sent;
|
|
29437
|
+
saveChatReceipt(receipt);
|
|
28835
29438
|
const replyAddress = sent.from || from;
|
|
28836
29439
|
const baseContext = {
|
|
28837
29440
|
from: replyAddress,
|
|
@@ -28878,7 +29481,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28878
29481
|
matchStrategy: "strict",
|
|
28879
29482
|
reply: detail
|
|
28880
29483
|
};
|
|
28881
|
-
const localChatId = persistActiveChat({
|
|
29484
|
+
const localChatId = await persistActiveChat({
|
|
28882
29485
|
configDir: this.config.configDir,
|
|
28883
29486
|
context: outputContext,
|
|
28884
29487
|
preferredLocalId: flags["chat-local-id"],
|
|
@@ -28888,6 +29491,8 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28888
29491
|
...outputContext,
|
|
28889
29492
|
localChatId
|
|
28890
29493
|
};
|
|
29494
|
+
receipt.data.completed = localChatId !== null;
|
|
29495
|
+
saveChatReceipt(receipt);
|
|
28891
29496
|
if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
|
|
28892
29497
|
else this.log(formatChatResponse(outputContext));
|
|
28893
29498
|
return;
|
|
@@ -28942,7 +29547,7 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28942
29547
|
matchStrategy: replyResult.matchStrategy,
|
|
28943
29548
|
reply: replyResult.reply
|
|
28944
29549
|
};
|
|
28945
|
-
const localChatId = persistActiveChat({
|
|
29550
|
+
const localChatId = await persistActiveChat({
|
|
28946
29551
|
configDir: this.config.configDir,
|
|
28947
29552
|
context: outputContext,
|
|
28948
29553
|
preferredLocalId: flags["chat-local-id"],
|
|
@@ -28952,10 +29557,12 @@ var ChatCommand = class ChatCommand extends Command {
|
|
|
28952
29557
|
...outputContext,
|
|
28953
29558
|
localChatId
|
|
28954
29559
|
};
|
|
29560
|
+
receipt.data.completed = localChatId !== null;
|
|
29561
|
+
saveChatReceipt(receipt);
|
|
28955
29562
|
if (flags.json) this.log(JSON.stringify(buildChatJsonEnvelope(outputContext), null, 2));
|
|
28956
29563
|
else this.log(formatChatResponse(outputContext));
|
|
28957
29564
|
} finally {
|
|
28958
|
-
releaseLock();
|
|
29565
|
+
releaseLock?.();
|
|
28959
29566
|
}
|
|
28960
29567
|
});
|
|
28961
29568
|
}
|
|
@@ -29035,15 +29642,19 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
29035
29642
|
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
29643
|
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
29644
|
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
29645
|
try {
|
|
29045
29646
|
const localId = flags.id ?? (typeof positionalLocalId === "number" ? positionalLocalId : void 0);
|
|
29046
|
-
const
|
|
29647
|
+
const releaseState = await acquireChatStateLock(this.config.configDir);
|
|
29648
|
+
let state;
|
|
29649
|
+
try {
|
|
29650
|
+
state = localId === void 0 ? loadActiveChatState(this.config.configDir) : loadChatConversationByLocalId(this.config.configDir, localId);
|
|
29651
|
+
if (state) release = acquireChatLock(this.config.configDir, `local:${state.local_id}`);
|
|
29652
|
+
} catch (error) {
|
|
29653
|
+
if (error instanceof ChatLockContentionError) throw cliError$6(error.message);
|
|
29654
|
+
throw error;
|
|
29655
|
+
} finally {
|
|
29656
|
+
releaseState();
|
|
29657
|
+
}
|
|
29047
29658
|
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
29659
|
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
29660
|
if (!message.trim()) throw cliError$6("Reply body is empty.");
|
|
@@ -29075,7 +29686,7 @@ var ChatReplyCommand = class ChatReplyCommand extends Command {
|
|
|
29075
29686
|
if (flags.time) argv.push("--time");
|
|
29076
29687
|
await ChatCommand.run(argv, { root: this.config.root });
|
|
29077
29688
|
} finally {
|
|
29078
|
-
release();
|
|
29689
|
+
release?.();
|
|
29079
29690
|
}
|
|
29080
29691
|
}
|
|
29081
29692
|
};
|
|
@@ -31226,8 +31837,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
31226
31837
|
name: "Primitive Team",
|
|
31227
31838
|
url: "https://primitive.dev"
|
|
31228
31839
|
};
|
|
31229
|
-
const SDK_VERSION_RANGE = "^1.
|
|
31230
|
-
const CLI_VERSION_RANGE = "^1.
|
|
31840
|
+
const SDK_VERSION_RANGE = "^1.24.0";
|
|
31841
|
+
const CLI_VERSION_RANGE = "^1.24.0";
|
|
31231
31842
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
31232
31843
|
function renderHandler() {
|
|
31233
31844
|
return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
|
|
@@ -33475,7 +34086,7 @@ function createListenHandler(options = {}) {
|
|
|
33475
34086
|
validateListenHandlerOptions(options);
|
|
33476
34087
|
return async (delivery, signal) => {
|
|
33477
34088
|
signal.throwIfAborted();
|
|
33478
|
-
const start = performance.now();
|
|
34089
|
+
const start = performance$1.now();
|
|
33479
34090
|
const work = new AbortController();
|
|
33480
34091
|
let timedOut = false;
|
|
33481
34092
|
const cancel = () => work.abort(signal.reason);
|
|
@@ -33484,7 +34095,7 @@ function createListenHandler(options = {}) {
|
|
|
33484
34095
|
timedOut = true;
|
|
33485
34096
|
work.abort(/* @__PURE__ */ new Error("Event handler timed out."));
|
|
33486
34097
|
}, HANDLER_TIMEOUT_MS);
|
|
33487
|
-
const duration = () => Math.min(HANDLER_TIMEOUT_MS, Math.max(0, Math.round(performance.now() - start)));
|
|
34098
|
+
const duration = () => Math.min(HANDLER_TIMEOUT_MS, Math.max(0, Math.round(performance$1.now() - start)));
|
|
33488
34099
|
try {
|
|
33489
34100
|
if (options.exec !== void 0) {
|
|
33490
34101
|
const code = await execute(options.exec, delivery, options.stderr ?? process.stderr, work.signal);
|
|
@@ -36122,7 +36733,7 @@ var OrgSecretsSetCommand = class OrgSecretsSetCommand extends Command {
|
|
|
36122
36733
|
}
|
|
36123
36734
|
};
|
|
36124
36735
|
//#endregion
|
|
36125
|
-
//#region ../sdk-node/dist/payloads-
|
|
36736
|
+
//#region ../sdk-node/dist/payloads-BfJAtqW9.js
|
|
36126
36737
|
/**
|
|
36127
36738
|
* Load a Node builtin at call time. Exported for direct testing of the
|
|
36128
36739
|
* unavailable-module error path; production code goes through the typed
|
|
@@ -36155,7 +36766,9 @@ const MANIFEST_VERSION = 1;
|
|
|
36155
36766
|
const CHUNK_KDF_INFO = "payloads-chunk";
|
|
36156
36767
|
const OBJECT_ID_BYTES = 16;
|
|
36157
36768
|
const CEK_BYTES = 32;
|
|
36158
|
-
|
|
36769
|
+
function subtle() {
|
|
36770
|
+
return globalThis.crypto.subtle;
|
|
36771
|
+
}
|
|
36159
36772
|
const textEncoder = new TextEncoder();
|
|
36160
36773
|
function randomBytes(length) {
|
|
36161
36774
|
const out = new Uint8Array(length);
|
|
@@ -36175,7 +36788,7 @@ function fromHex(hex) {
|
|
|
36175
36788
|
return out;
|
|
36176
36789
|
}
|
|
36177
36790
|
async function contentHash(bytes) {
|
|
36178
|
-
return new Uint8Array(await subtle.digest("SHA-256", bytes));
|
|
36791
|
+
return new Uint8Array(await subtle().digest("SHA-256", bytes));
|
|
36179
36792
|
}
|
|
36180
36793
|
async function contentHashHex(bytes) {
|
|
36181
36794
|
return toHex(await contentHash(bytes));
|
|
@@ -36199,9 +36812,9 @@ async function merkleRoot(leafHashesHex) {
|
|
|
36199
36812
|
return toHex(level[0]);
|
|
36200
36813
|
}
|
|
36201
36814
|
async function deriveChunkKey(cek, index) {
|
|
36202
|
-
const baseKey = await subtle.importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
36815
|
+
const baseKey = await subtle().importKey("raw", cek, "HKDF", false, ["deriveKey"]);
|
|
36203
36816
|
const info = textEncoder.encode(`${CHUNK_KDF_INFO}:${index}`);
|
|
36204
|
-
return subtle.deriveKey({
|
|
36817
|
+
return subtle().deriveKey({
|
|
36205
36818
|
name: "HKDF",
|
|
36206
36819
|
hash: "SHA-256",
|
|
36207
36820
|
salt: new Uint8Array(32),
|
|
@@ -36224,7 +36837,7 @@ function chunkAad(objectId, index) {
|
|
|
36224
36837
|
}
|
|
36225
36838
|
async function encryptChunk(cek, objectId, index, plaintext) {
|
|
36226
36839
|
const key = await deriveChunkKey(cek, index);
|
|
36227
|
-
const ct = await subtle.encrypt({
|
|
36840
|
+
const ct = await subtle().encrypt({
|
|
36228
36841
|
name: "AES-GCM",
|
|
36229
36842
|
iv: chunkNonce(index),
|
|
36230
36843
|
additionalData: chunkAad(objectId, index)
|
|
@@ -36233,7 +36846,7 @@ async function encryptChunk(cek, objectId, index, plaintext) {
|
|
|
36233
36846
|
}
|
|
36234
36847
|
async function decryptChunk(cek, objectId, index, ciphertext) {
|
|
36235
36848
|
const key = await deriveChunkKey(cek, index);
|
|
36236
|
-
const pt = await subtle.decrypt({
|
|
36849
|
+
const pt = await subtle().decrypt({
|
|
36237
36850
|
name: "AES-GCM",
|
|
36238
36851
|
iv: chunkNonce(index),
|
|
36239
36852
|
additionalData: chunkAad(objectId, index)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primitive",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -172,12 +172,12 @@
|
|
|
172
172
|
"@primitivedotdev/api-core": "workspace:*",
|
|
173
173
|
"@primitivedotdev/sdk": "workspace:*",
|
|
174
174
|
"@types/node": "^22.10.2",
|
|
175
|
-
"@vitest/coverage-v8": "^4.1.
|
|
175
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
176
176
|
"oclif": "^4.23.0",
|
|
177
177
|
"tsdown": "^0.21.10",
|
|
178
178
|
"tsx": "^4.21.0",
|
|
179
179
|
"typescript": "^5.7.2",
|
|
180
180
|
"vite": "^8.0.8",
|
|
181
|
-
"vitest": "^4.1.
|
|
181
|
+
"vitest": "^4.1.11"
|
|
182
182
|
}
|
|
183
183
|
}
|