@sprigr/apps-app-sdk 0.14.0 → 0.15.1
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/index.d.ts +270 -1
- package/dist/index.js +142 -8
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1278,6 +1278,13 @@ declare function putAppFile(env: AppFilesEnv, args: PutAppFileArgs): Promise<Put
|
|
|
1278
1278
|
* and (with a `ReadableStream` body) never buffers them whole in the
|
|
1279
1279
|
* isolate — pipe a source `Response.body` directly through for multi-MB
|
|
1280
1280
|
* blobs. Metadata travels in headers; the body is opaque bytes.
|
|
1281
|
+
*
|
|
1282
|
+
* The platform's put-stream route cannot retry a transient R2 fault itself
|
|
1283
|
+
* (its body is spent once read), so a resendable body — `ArrayBuffer` /
|
|
1284
|
+
* `Uint8Array` already in hand, not a `ReadableStream` — gets a bounded
|
|
1285
|
+
* retry here instead. The key is deterministic, so a retry is a full
|
|
1286
|
+
* overwrite, exactly the argument the platform's buffered `put` route makes
|
|
1287
|
+
* for its own retry (wfp-files.ts, sprigr-team).
|
|
1281
1288
|
*/
|
|
1282
1289
|
declare function putAppFileStream(env: AppFilesEnv, args: PutAppFileStreamArgs): Promise<PutAppFileResult>;
|
|
1283
1290
|
/** Mint a signed, time-limited download URL for an app file. */
|
|
@@ -2096,6 +2103,268 @@ declare function deriveConfirmationPolicy(registry: Iterable<string>, opts: Deri
|
|
|
2096
2103
|
*/
|
|
2097
2104
|
declare function commonDestructiveVerbs(subject: string): VerbRule[];
|
|
2098
2105
|
|
|
2106
|
+
/**
|
|
2107
|
+
* The inbox contract for mail and ticket apps (sprigr-team decision 0150).
|
|
2108
|
+
*
|
|
2109
|
+
* An app that writes into the Sprigr inbox does four things, all typed here:
|
|
2110
|
+
*
|
|
2111
|
+
* 1. Declares each channel it writes under `capabilities.inbox_channels` in
|
|
2112
|
+
* its manifest (`InboxChannelDeclaration`). Append refuses undeclared
|
|
2113
|
+
* channels, and replies cannot send without a declared send tool.
|
|
2114
|
+
* 2. Appends synced messages with `env.SPRIGR.inbox.append` (`SyncedMessage`,
|
|
2115
|
+
* `InboxOwner` on every batch). `withSprigrInboxFallback` makes that work
|
|
2116
|
+
* from an inline route too.
|
|
2117
|
+
* 3. Implements the declared send tool on the one `sprigr.inbox.send/v1`
|
|
2118
|
+
* argument shape, through `defineInboxSendHandler`, which enforces the
|
|
2119
|
+
* mailbox-owner rule so it cannot be forgotten.
|
|
2120
|
+
* 4. If it collects a credential on its own page (no OAuth bouncer),
|
|
2121
|
+
* reports the connection with `connectComplete`, which forwards the
|
|
2122
|
+
* platform-signed viewer token so the platform knows who connected.
|
|
2123
|
+
*/
|
|
2124
|
+
|
|
2125
|
+
declare const INBOX_SEND_CONTRACT_V1: "sprigr.inbox.send/v1";
|
|
2126
|
+
interface InboxSendSupports {
|
|
2127
|
+
bcc: boolean;
|
|
2128
|
+
html: boolean;
|
|
2129
|
+
recipients: boolean;
|
|
2130
|
+
importance: boolean;
|
|
2131
|
+
internal_note: boolean;
|
|
2132
|
+
}
|
|
2133
|
+
/** One entry of `capabilities.inbox_channels`. Validated at publish. */
|
|
2134
|
+
interface InboxChannelDeclaration {
|
|
2135
|
+
/** Must start with your slug in snake case plus `_` (`acme-mail` -> `acme_mail_...`). */
|
|
2136
|
+
channel: string;
|
|
2137
|
+
kind: 'email' | 'ticket';
|
|
2138
|
+
/** 1-40 printable characters, no `<` or `>`. */
|
|
2139
|
+
label: string;
|
|
2140
|
+
icon: 'mail' | 'ticket' | 'chat';
|
|
2141
|
+
send?: {
|
|
2142
|
+
tool: string;
|
|
2143
|
+
contract: typeof INBOX_SEND_CONTRACT_V1;
|
|
2144
|
+
returns_message_id: {
|
|
2145
|
+
send: boolean;
|
|
2146
|
+
reply: boolean;
|
|
2147
|
+
};
|
|
2148
|
+
attachments: 'file_key' | 'inline' | 'none';
|
|
2149
|
+
supports: InboxSendSupports;
|
|
2150
|
+
};
|
|
2151
|
+
signature_family?: string;
|
|
2152
|
+
attachment_repair_tool?: string;
|
|
2153
|
+
/** https only, host in `permissions.network_domains`; placeholders
|
|
2154
|
+
* `{source_thread_id}` and `{source_id}`. */
|
|
2155
|
+
deep_link_template?: string | null;
|
|
2156
|
+
}
|
|
2157
|
+
interface SyncedAttachment {
|
|
2158
|
+
filename: string;
|
|
2159
|
+
mimeType: string;
|
|
2160
|
+
size: number;
|
|
2161
|
+
/** Opaque to the platform; your `fetch_attachment` tool resolves it. It
|
|
2162
|
+
* must identify the connection, because that dispatch carries no actor. */
|
|
2163
|
+
providerAttachmentId?: string;
|
|
2164
|
+
contentId?: string;
|
|
2165
|
+
inline?: boolean;
|
|
2166
|
+
}
|
|
2167
|
+
interface SyncedMessage {
|
|
2168
|
+
/** Unique within the channel; the platform dedups on (channel, sourceId). */
|
|
2169
|
+
sourceId: string;
|
|
2170
|
+
sourceIndex: string;
|
|
2171
|
+
sourceThreadId?: string;
|
|
2172
|
+
/** RFC 5322 Message-ID (brackets optional). */
|
|
2173
|
+
rfcMessageId?: string;
|
|
2174
|
+
channel?: string;
|
|
2175
|
+
direction: 'inbound' | 'outbound' | 'internal';
|
|
2176
|
+
fromName?: string;
|
|
2177
|
+
fromEmail?: string;
|
|
2178
|
+
toRecipients?: Array<{
|
|
2179
|
+
name: string;
|
|
2180
|
+
email: string;
|
|
2181
|
+
}>;
|
|
2182
|
+
ccRecipients?: Array<{
|
|
2183
|
+
name: string;
|
|
2184
|
+
email: string;
|
|
2185
|
+
}>;
|
|
2186
|
+
subject?: string;
|
|
2187
|
+
/** Plain text. May be '' when `bodyHtml` carries the content. */
|
|
2188
|
+
body: string;
|
|
2189
|
+
bodyHtml?: string;
|
|
2190
|
+
/** ISO 8601. */
|
|
2191
|
+
timestamp: string;
|
|
2192
|
+
sourceIsRead?: boolean;
|
|
2193
|
+
tags?: string[];
|
|
2194
|
+
attachments?: SyncedAttachment[];
|
|
2195
|
+
}
|
|
2196
|
+
interface InboxAppendRequest {
|
|
2197
|
+
channel?: string;
|
|
2198
|
+
/** At most 500 per call. */
|
|
2199
|
+
messages: SyncedMessage[];
|
|
2200
|
+
/** The MAILBOX owner (from your connection row), never the dispatch caller. */
|
|
2201
|
+
owner?: InboxOwner;
|
|
2202
|
+
}
|
|
2203
|
+
interface InboxAppendResult {
|
|
2204
|
+
ok: boolean;
|
|
2205
|
+
threadsCreated: number;
|
|
2206
|
+
messagesLinked: number;
|
|
2207
|
+
threadIds: string[];
|
|
2208
|
+
contentlessSkipped?: number;
|
|
2209
|
+
}
|
|
2210
|
+
interface InboxFoldersRequest {
|
|
2211
|
+
channel?: string;
|
|
2212
|
+
/** At most 200 per call. Folder names match manual Sprigr folders. */
|
|
2213
|
+
items: Array<{
|
|
2214
|
+
sourceId: string;
|
|
2215
|
+
addFolders?: string[];
|
|
2216
|
+
removeFolders?: string[];
|
|
2217
|
+
}>;
|
|
2218
|
+
owner?: InboxOwner;
|
|
2219
|
+
}
|
|
2220
|
+
interface InboxFoldersResult {
|
|
2221
|
+
ok: boolean;
|
|
2222
|
+
filed: number;
|
|
2223
|
+
unfiled: number;
|
|
2224
|
+
skippedGuarded: number;
|
|
2225
|
+
unknownFolders: string[];
|
|
2226
|
+
}
|
|
2227
|
+
interface SprigrInboxApi {
|
|
2228
|
+
append(req: InboxAppendRequest): Promise<InboxAppendResult>;
|
|
2229
|
+
folders?(req: InboxFoldersRequest): Promise<InboxFoldersResult>;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Return an env whose `SPRIGR.inbox` works from any execution context.
|
|
2233
|
+
*
|
|
2234
|
+
* `env.SPRIGR` is injected only on the `/__sprigr/*` dispatch path. An inline
|
|
2235
|
+
* route (a provider push receiver, your settings page) gets the raw bindings,
|
|
2236
|
+
* so this wires the same contract over the install-token HTTP bridge. Append
|
|
2237
|
+
* and folders THROW on a non-2xx, so a caller never advances a sync cursor
|
|
2238
|
+
* past messages the platform did not accept. With no bridge bindings the env
|
|
2239
|
+
* comes back unchanged and `SPRIGR.inbox` stays undefined: fail closed.
|
|
2240
|
+
*/
|
|
2241
|
+
declare function withSprigrInboxFallback<E extends WfpBridgeEnv & object>(env: E): E;
|
|
2242
|
+
interface InboxAddress {
|
|
2243
|
+
email: string;
|
|
2244
|
+
name?: string;
|
|
2245
|
+
}
|
|
2246
|
+
/** What the platform sends your declared send tool. snake_case. */
|
|
2247
|
+
interface InboxSendV1Args {
|
|
2248
|
+
contract: typeof INBOX_SEND_CONTRACT_V1;
|
|
2249
|
+
from_email?: string;
|
|
2250
|
+
to?: InboxAddress[];
|
|
2251
|
+
cc?: InboxAddress[];
|
|
2252
|
+
bcc?: InboxAddress[];
|
|
2253
|
+
subject?: string;
|
|
2254
|
+
body_text: string;
|
|
2255
|
+
body_html?: string;
|
|
2256
|
+
attachments?: Array<{
|
|
2257
|
+
file_key: string;
|
|
2258
|
+
filename: string;
|
|
2259
|
+
content_type: string;
|
|
2260
|
+
}> | Array<{
|
|
2261
|
+
filename: string;
|
|
2262
|
+
mime_type?: string;
|
|
2263
|
+
base64_body: string;
|
|
2264
|
+
}>;
|
|
2265
|
+
/** `source_id` / `source_thread_id` are YOUR stored ids, present only when
|
|
2266
|
+
* your install received the mail being answered. */
|
|
2267
|
+
reply?: {
|
|
2268
|
+
source_id?: string;
|
|
2269
|
+
source_thread_id?: string;
|
|
2270
|
+
rfc_message_id?: string;
|
|
2271
|
+
references: string[];
|
|
2272
|
+
};
|
|
2273
|
+
internal_note?: boolean;
|
|
2274
|
+
author_email?: string;
|
|
2275
|
+
importance?: 'high' | 'low';
|
|
2276
|
+
idempotency_key?: string;
|
|
2277
|
+
}
|
|
2278
|
+
/** What your send tool returns. `message_id` must equal the `sourceId` your
|
|
2279
|
+
* sync will write for the sent copy, so the Sent echo dedups. */
|
|
2280
|
+
interface InboxSendV1Result {
|
|
2281
|
+
message_id?: string;
|
|
2282
|
+
internet_message_id?: string;
|
|
2283
|
+
thread_id?: string;
|
|
2284
|
+
}
|
|
2285
|
+
/** Your connection record, as far as the owner check needs it. */
|
|
2286
|
+
interface InboxSendConnection {
|
|
2287
|
+
/** `u:<platformUserId>` or `a:<agentId>`: the key the connection is filed under. */
|
|
2288
|
+
actorKey: string;
|
|
2289
|
+
}
|
|
2290
|
+
interface InboxSendHandlerImpl<E, C extends InboxSendConnection> {
|
|
2291
|
+
/** The connection that owns one of YOUR stored source ids, or null when the
|
|
2292
|
+
* id is not yours. Parse your own id format here. */
|
|
2293
|
+
connectionForSourceId(env: E, sourceId: string): Promise<C | null>;
|
|
2294
|
+
/** The connection this actor key sends a NEW mail from. When `fromEmail` is
|
|
2295
|
+
* set, return only a connection that may send as that address, else null. */
|
|
2296
|
+
connectionForActor(env: E, actorKey: string, fromEmail: string | undefined): Promise<C | null>;
|
|
2297
|
+
send(env: E, connection: C, args: InboxSendV1Args): Promise<InboxSendV1Result>;
|
|
2298
|
+
}
|
|
2299
|
+
declare class InboxSendRefusal extends Error {
|
|
2300
|
+
readonly code: 'no_caller_identity' | 'not_your_mailbox' | 'not_connected' | 'invalid_args';
|
|
2301
|
+
constructor(code: 'no_caller_identity' | 'not_your_mailbox' | 'not_connected' | 'invalid_args', message: string);
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Build the handler for your declared `sprigr.inbox.send/v1` tool.
|
|
2305
|
+
*
|
|
2306
|
+
* THE MAILBOX-OWNER RULE, enforced here so no app can omit it: replies are
|
|
2307
|
+
* dispatched under the WRITER's actor, and anyone who can read a thread can
|
|
2308
|
+
* press Reply. So the connection you send from must be filed under one of the
|
|
2309
|
+
* writer's own keys (`u:<platformUserId>`, `a:<agentId>`). A reply whose
|
|
2310
|
+
* source belongs to someone else's connection is REFUSED, never re-routed to
|
|
2311
|
+
* the writer's own mailbox and never sent from the owner's: either would
|
|
2312
|
+
* deliver mail the writer is not entitled to send.
|
|
2313
|
+
*/
|
|
2314
|
+
declare function defineInboxSendHandler<E, C extends InboxSendConnection>(impl: InboxSendHandlerImpl<E, C>): (args: Record<string, unknown>, env: E) => Promise<ToolResult<InboxSendV1Result>>;
|
|
2315
|
+
interface ConnectCompleteRequest {
|
|
2316
|
+
/** The raw `X-Sprigr-Viewer` header of the PAGE request that submitted the
|
|
2317
|
+
* credential. The platform takes the user only from this. */
|
|
2318
|
+
viewer_token: string;
|
|
2319
|
+
bind?: {
|
|
2320
|
+
kind: 'user';
|
|
2321
|
+
} | {
|
|
2322
|
+
kind: 'agent';
|
|
2323
|
+
agent_id: string;
|
|
2324
|
+
};
|
|
2325
|
+
account: {
|
|
2326
|
+
email: string;
|
|
2327
|
+
name?: string;
|
|
2328
|
+
};
|
|
2329
|
+
identities?: Array<{
|
|
2330
|
+
email: string;
|
|
2331
|
+
name?: string;
|
|
2332
|
+
}>;
|
|
2333
|
+
}
|
|
2334
|
+
interface ConnectCompleteResult {
|
|
2335
|
+
ok: boolean;
|
|
2336
|
+
/** File your connection under this key. */
|
|
2337
|
+
actor_key: string;
|
|
2338
|
+
identities: {
|
|
2339
|
+
inserted: number;
|
|
2340
|
+
updated: number;
|
|
2341
|
+
deleted: number;
|
|
2342
|
+
};
|
|
2343
|
+
/** The same address already connected through another app in the company. */
|
|
2344
|
+
existing_mailboxes: Array<{
|
|
2345
|
+
app_slug: string;
|
|
2346
|
+
email: string;
|
|
2347
|
+
account_email: string;
|
|
2348
|
+
}>;
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* Report a mailbox connected on your own page (token paste, app password).
|
|
2352
|
+
* Call it from the inline route that handled the page's POST, passing that
|
|
2353
|
+
* request's `X-Sprigr-Viewer` header. Throws on a non-2xx with `.status` and
|
|
2354
|
+
* `.error` (`viewer_unverified`, `viewer_not_a_member`, `agent_bind_refused`,
|
|
2355
|
+
* `agent_bind_requires_admin`, ...).
|
|
2356
|
+
*/
|
|
2357
|
+
declare function connectComplete(env: WfpBridgeEnv, req: ConnectCompleteRequest): Promise<ConnectCompleteResult>;
|
|
2358
|
+
/** Retire a connection's From-picker identities (install token only, so a
|
|
2359
|
+
* background sync can call it when a credential dies). */
|
|
2360
|
+
declare function connectDisconnect(env: WfpBridgeEnv, req: {
|
|
2361
|
+
actor_key: string;
|
|
2362
|
+
account_email?: string;
|
|
2363
|
+
}): Promise<{
|
|
2364
|
+
ok: boolean;
|
|
2365
|
+
deleted: number;
|
|
2366
|
+
}>;
|
|
2367
|
+
|
|
2099
2368
|
/**
|
|
2100
2369
|
* @sprigr/apps-app-sdk
|
|
2101
2370
|
*
|
|
@@ -2636,4 +2905,4 @@ interface ExecutionContextLike {
|
|
|
2636
2905
|
}
|
|
2637
2906
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
2638
2907
|
|
|
2639
|
-
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DATA_PARTIAL_UPDATE_PATH, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_LOG_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type LogToPlatformOptions, type ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PartialUpdateDataOptions, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, SPRIGR_DATA_MAX_OBJECTS_PER_CALL, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataPartialUpdateBody, type SprigrDataPartialUpdateOpts, type SprigrDataPartialUpdateResult, type SprigrDataPatch, type SprigrDataSearchOpts, type SprigrDataSearchResult, SprigrDataValidationError, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type SprigrFilesListItem, type SprigrFilesListResult, type SprigrLogEntry, type SprigrLogFn, type SprigrLogLevel, type SprigrLogResult, SprigrLogValidationError, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, VIEWER_ANONYMOUS, VIEWER_CONTEXT_HEADER, VIEWER_PLATFORM_USER_ID_HEADER, VIEWER_ROLE_HEADER, VIEWER_USER_ID_HEADER, type VerbRule, type ViewerContext, type ViewerContextFailure, type ViewerContextVerification, type ViewerEnv, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, applyConfirmationPolicies, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, buildPartialUpdateBody, bytesToBase64, canEmit, canLog, canPartialUpdate, checkConfirmationPolicy, commonDestructiveVerbs, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, logToPlatform, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, partialUpdateData, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrLogFallback };
|
|
2908
|
+
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type ConnectCompleteRequest, type ConnectCompleteResult, type D1Like, type D1PreparedStatementLike, DATA_PARTIAL_UPDATE_PATH, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_LOG_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, INBOX_SEND_CONTRACT_V1, type InboxAddress, type InboxAppendRequest, type InboxAppendResult, type InboxChannelDeclaration, type InboxFoldersRequest, type InboxFoldersResult, type InboxOwner, type InboxSendConnection, type InboxSendHandlerImpl, InboxSendRefusal, type InboxSendSupports, type InboxSendV1Args, type InboxSendV1Result, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type LogToPlatformOptions, type ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PartialUpdateDataOptions, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, SPRIGR_DATA_MAX_OBJECTS_PER_CALL, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataPartialUpdateBody, type SprigrDataPartialUpdateOpts, type SprigrDataPartialUpdateResult, type SprigrDataPatch, type SprigrDataSearchOpts, type SprigrDataSearchResult, SprigrDataValidationError, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type SprigrFilesListItem, type SprigrFilesListResult, type SprigrInboxApi, type SprigrLogEntry, type SprigrLogFn, type SprigrLogLevel, type SprigrLogResult, SprigrLogValidationError, type SyncedAttachment, type SyncedMessage, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, VIEWER_ANONYMOUS, VIEWER_CONTEXT_HEADER, VIEWER_PLATFORM_USER_ID_HEADER, VIEWER_ROLE_HEADER, VIEWER_USER_ID_HEADER, type VerbRule, type ViewerContext, type ViewerContextFailure, type ViewerContextVerification, type ViewerEnv, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, applyConfirmationPolicies, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, buildPartialUpdateBody, bytesToBase64, canEmit, canLog, canPartialUpdate, checkConfirmationPolicy, commonDestructiveVerbs, connectComplete, connectDisconnect, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, defineInboxSendHandler, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, logToPlatform, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, partialUpdateData, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrInboxFallback, withSprigrLogFallback };
|
package/dist/index.js
CHANGED
|
@@ -893,6 +893,17 @@ function putAppFile(env, args) {
|
|
|
893
893
|
filename: args.filename
|
|
894
894
|
});
|
|
895
895
|
}
|
|
896
|
+
var STREAM_PUT_MAX_ATTEMPTS = 3;
|
|
897
|
+
var STREAM_PUT_BASE_DELAY_MS = 500;
|
|
898
|
+
function isTransientPutStreamFailure(status, message) {
|
|
899
|
+
if (status === 503) return true;
|
|
900
|
+
if (status !== 502) return false;
|
|
901
|
+
const msg = message.toLowerCase();
|
|
902
|
+
return msg.includes("(10001)") || msg.includes("(10043)");
|
|
903
|
+
}
|
|
904
|
+
function sleep2(ms) {
|
|
905
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
906
|
+
}
|
|
896
907
|
async function putAppFileStream(env, args) {
|
|
897
908
|
const token = requireToken(env);
|
|
898
909
|
const headers = {
|
|
@@ -905,14 +916,28 @@ async function putAppFileStream(env, args) {
|
|
|
905
916
|
if (typeof args.contentLength === "number" && Number.isFinite(args.contentLength)) {
|
|
906
917
|
headers["x-app-file-length"] = String(args.contentLength);
|
|
907
918
|
}
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
919
|
+
const resendable = !(args.body instanceof ReadableStream);
|
|
920
|
+
const attempts = resendable ? STREAM_PUT_MAX_ATTEMPTS : 1;
|
|
921
|
+
let lastErr;
|
|
922
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
923
|
+
const res = await fetch(`${platformBase(env)}/internal/wfp/file/put-stream`, {
|
|
924
|
+
method: "POST",
|
|
925
|
+
headers,
|
|
926
|
+
body: args.body,
|
|
927
|
+
// Required by the Workers runtime when sending a streaming request body.
|
|
928
|
+
...args.body instanceof ReadableStream ? { duplex: "half" } : {}
|
|
929
|
+
});
|
|
930
|
+
try {
|
|
931
|
+
return await parseResult(res, "put-stream");
|
|
932
|
+
} catch (err) {
|
|
933
|
+
lastErr = err;
|
|
934
|
+
if (!resendable || attempt === attempts || !(err instanceof AppFilesError) || !isTransientPutStreamFailure(err.status, err.message)) {
|
|
935
|
+
throw err;
|
|
936
|
+
}
|
|
937
|
+
await sleep2(STREAM_PUT_BASE_DELAY_MS * 2 ** (attempt - 1));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
throw lastErr;
|
|
916
941
|
}
|
|
917
942
|
function appFileUrl(env, args) {
|
|
918
943
|
return call(env, "url", { key: args.key, expiresIn: args.expiresIn });
|
|
@@ -1488,6 +1513,109 @@ function commonDestructiveVerbs(subject) {
|
|
|
1488
1513
|
{ match: "deactivate_", tier: "always", describe: (_n, rest) => `Deactivate ${rest}${where}` }
|
|
1489
1514
|
];
|
|
1490
1515
|
}
|
|
1516
|
+
|
|
1517
|
+
// src/inbox.ts
|
|
1518
|
+
var INBOX_SEND_CONTRACT_V1 = "sprigr.inbox.send/v1";
|
|
1519
|
+
function withSprigrInboxFallback(env) {
|
|
1520
|
+
const bridge = resolveInstallBridge(env);
|
|
1521
|
+
const existing = env.SPRIGR ?? void 0;
|
|
1522
|
+
const folders = bridge ? async (req) => await installTokenPost(bridge, "/internal/wfp/inbox/folders", req, { label: "inbox folders" }) : void 0;
|
|
1523
|
+
if (existing?.inbox) {
|
|
1524
|
+
if (existing.inbox.folders || !folders) return env;
|
|
1525
|
+
const inbox = existing.inbox;
|
|
1526
|
+
return overlaySprigr(env, { ...existing, inbox: { append: inbox.append.bind(inbox), folders } });
|
|
1527
|
+
}
|
|
1528
|
+
if (!bridge) return env;
|
|
1529
|
+
const append = async (req) => {
|
|
1530
|
+
const r = await installTokenPost(bridge, "/internal/wfp/inbox/append", req, { label: "inbox append" });
|
|
1531
|
+
return {
|
|
1532
|
+
ok: r.ok !== false,
|
|
1533
|
+
threadsCreated: r.threadsCreated ?? 0,
|
|
1534
|
+
messagesLinked: r.messagesLinked ?? 0,
|
|
1535
|
+
threadIds: Array.isArray(r.threadIds) ? r.threadIds : [],
|
|
1536
|
+
...typeof r.contentlessSkipped === "number" ? { contentlessSkipped: r.contentlessSkipped } : {}
|
|
1537
|
+
};
|
|
1538
|
+
};
|
|
1539
|
+
return overlaySprigr(env, { ...existing ?? {}, inbox: { append, folders } });
|
|
1540
|
+
}
|
|
1541
|
+
var InboxSendRefusal = class extends Error {
|
|
1542
|
+
constructor(code, message) {
|
|
1543
|
+
super(message);
|
|
1544
|
+
this.code = code;
|
|
1545
|
+
this.name = "InboxSendRefusal";
|
|
1546
|
+
}
|
|
1547
|
+
code;
|
|
1548
|
+
};
|
|
1549
|
+
function defineInboxSendHandler(impl) {
|
|
1550
|
+
return async (rawArgs, env) => {
|
|
1551
|
+
try {
|
|
1552
|
+
const actor = parseActor(rawArgs);
|
|
1553
|
+
const keys = [
|
|
1554
|
+
...actor?.platformUserId ? [`u:${actor.platformUserId}`] : [],
|
|
1555
|
+
...actor?.agentId ? [`a:${actor.agentId}`] : []
|
|
1556
|
+
];
|
|
1557
|
+
if (keys.length === 0) {
|
|
1558
|
+
throw new InboxSendRefusal("no_caller_identity", "The platform stamped no sender identity on this send.");
|
|
1559
|
+
}
|
|
1560
|
+
const args = rawArgs;
|
|
1561
|
+
if (args.contract !== INBOX_SEND_CONTRACT_V1 || typeof args.body_text !== "string") {
|
|
1562
|
+
throw new InboxSendRefusal("invalid_args", `Expected ${INBOX_SEND_CONTRACT_V1} arguments.`);
|
|
1563
|
+
}
|
|
1564
|
+
let connection = null;
|
|
1565
|
+
const sourceId = args.reply?.source_id;
|
|
1566
|
+
if (sourceId) {
|
|
1567
|
+
const owner = await impl.connectionForSourceId(env, sourceId);
|
|
1568
|
+
if (owner) {
|
|
1569
|
+
if (!keys.includes(owner.actorKey)) {
|
|
1570
|
+
throw new InboxSendRefusal(
|
|
1571
|
+
"not_your_mailbox",
|
|
1572
|
+
"This conversation arrived in a mailbox that belongs to someone else, so you cannot reply from it."
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
if (args.from_email) {
|
|
1576
|
+
const asFrom = await impl.connectionForActor(env, owner.actorKey, args.from_email);
|
|
1577
|
+
if (!asFrom || asFrom.actorKey !== owner.actorKey) {
|
|
1578
|
+
throw new InboxSendRefusal("not_your_mailbox", `This mailbox cannot send as ${args.from_email}.`);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
connection = owner;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
if (!connection) {
|
|
1585
|
+
for (const key of keys) {
|
|
1586
|
+
connection = await impl.connectionForActor(env, key, args.from_email);
|
|
1587
|
+
if (connection) break;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (!connection) {
|
|
1591
|
+
throw new InboxSendRefusal(
|
|
1592
|
+
"not_connected",
|
|
1593
|
+
args.from_email ? `You have no connection that can send as ${args.from_email}.` : "You have not connected a mailbox with this app."
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
if (!keys.includes(connection.actorKey)) {
|
|
1597
|
+
throw new InboxSendRefusal("not_your_mailbox", "Resolved connection does not belong to the sender.");
|
|
1598
|
+
}
|
|
1599
|
+
return { ok: true, result: await impl.send(env, connection, args) };
|
|
1600
|
+
} catch (err) {
|
|
1601
|
+
if (err instanceof InboxSendRefusal || err instanceof Error && err.name === "InboxSendRefusal") {
|
|
1602
|
+
const code = err.code;
|
|
1603
|
+
return { ok: false, error: `${code}: ${err.message}`, status: code === "invalid_args" ? 400 : 412 };
|
|
1604
|
+
}
|
|
1605
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
async function connectComplete(env, req) {
|
|
1610
|
+
const bridge = resolveInstallBridge(env);
|
|
1611
|
+
if (!bridge) throw new Error("connectComplete: SPRIGR_PLATFORM_BASE / SPRIGR_INSTALL_TOKEN are not bound");
|
|
1612
|
+
return await installTokenPost(bridge, "/internal/wfp/connect/complete", req, { label: "connect complete" });
|
|
1613
|
+
}
|
|
1614
|
+
async function connectDisconnect(env, req) {
|
|
1615
|
+
const bridge = resolveInstallBridge(env);
|
|
1616
|
+
if (!bridge) throw new Error("connectDisconnect: SPRIGR_PLATFORM_BASE / SPRIGR_INSTALL_TOKEN are not bound");
|
|
1617
|
+
return await installTokenPost(bridge, "/internal/wfp/connect/disconnect", req, { label: "connect disconnect" });
|
|
1618
|
+
}
|
|
1491
1619
|
export {
|
|
1492
1620
|
APPROVAL_GRANTED_KEY,
|
|
1493
1621
|
DATA_PARTIAL_UPDATE_PATH,
|
|
@@ -1496,6 +1624,8 @@ export {
|
|
|
1496
1624
|
DEFAULT_MAX_FILE_BYTES,
|
|
1497
1625
|
DEFAULT_MONEY_FIELDS,
|
|
1498
1626
|
DEFAULT_WRITE_PREFIXES,
|
|
1627
|
+
INBOX_SEND_CONTRACT_V1,
|
|
1628
|
+
InboxSendRefusal,
|
|
1499
1629
|
InvalidUndoEnvelopeError,
|
|
1500
1630
|
NotConnectedError,
|
|
1501
1631
|
SPRIGR_DATA_MAX_OBJECTS_PER_CALL,
|
|
@@ -1525,10 +1655,13 @@ export {
|
|
|
1525
1655
|
canPartialUpdate,
|
|
1526
1656
|
checkConfirmationPolicy,
|
|
1527
1657
|
commonDestructiveVerbs,
|
|
1658
|
+
connectComplete,
|
|
1659
|
+
connectDisconnect,
|
|
1528
1660
|
constantTimeEqual,
|
|
1529
1661
|
createMarketplaceEmitter,
|
|
1530
1662
|
createToolWrappers,
|
|
1531
1663
|
decodeState,
|
|
1664
|
+
defineInboxSendHandler,
|
|
1532
1665
|
deleteAppFile,
|
|
1533
1666
|
deriveConfirmationPolicy,
|
|
1534
1667
|
describeMissingBridge,
|
|
@@ -1575,5 +1708,6 @@ export {
|
|
|
1575
1708
|
validateLogEntry,
|
|
1576
1709
|
verifyViewerToken,
|
|
1577
1710
|
withSprigrEmitFallback,
|
|
1711
|
+
withSprigrInboxFallback,
|
|
1578
1712
|
withSprigrLogFallback
|
|
1579
1713
|
};
|