@sprigr/apps-app-sdk 0.13.0 → 0.15.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/index.d.ts +287 -3
- package/dist/index.js +154 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -669,6 +669,12 @@ declare function withSprigrLogFallback<E extends WfpBridgeEnv & object>(env: E,
|
|
|
669
669
|
* fails with `shard_field_invalid`: a patch cannot be routed to its shard
|
|
670
670
|
* otherwise. The per-call cap is the same 1000 objects as `import`.
|
|
671
671
|
*
|
|
672
|
+
* `withAcl: true` is a narrower mode: it re-stamps `acl_principals` on rows
|
|
673
|
+
* already in the app's `-acl-files` index and nothing else. Every patch must
|
|
674
|
+
* be exactly `{ objectID, acl_principals }`, and `createIfNotExists` and
|
|
675
|
+
* `index` cannot be combined with it. Content is written only by an ACL
|
|
676
|
+
* `import`, whole.
|
|
677
|
+
*
|
|
672
678
|
* This module carries two things:
|
|
673
679
|
*
|
|
674
680
|
* 1. `buildPartialUpdateBody`, the wire body both transports send, with
|
|
@@ -687,10 +693,13 @@ declare function withSprigrLogFallback<E extends WfpBridgeEnv & object>(env: E,
|
|
|
687
693
|
* swallow.
|
|
688
694
|
*
|
|
689
695
|
* Wire contract (workers/provisioning/src/wfp-data.ts in sprigr-team):
|
|
690
|
-
* POST { index?, objects, createIfNotExists? }
|
|
696
|
+
* POST { index?, objects, createIfNotExists?, withAcl? }
|
|
691
697
|
* 200 { ok: true, updated, skippedMissing, index, physical_indexes? }
|
|
692
698
|
* 400 { error, detail, index? } with error one of too_many_objects,
|
|
693
|
-
* invalid_object, unknown_data_index, shard_field_invalid,
|
|
699
|
+
* invalid_object, unknown_data_index, shard_field_invalid,
|
|
700
|
+
* acl_partial_update_fields, acl_partial_update_create_unsupported,
|
|
701
|
+
* acl_index_combination_unsupported, missing_acl_principals,
|
|
702
|
+
* invalid_acl_principal, ...
|
|
694
703
|
* 401 / 404 bad token / inactive install
|
|
695
704
|
*/
|
|
696
705
|
|
|
@@ -708,6 +717,8 @@ interface SprigrDataPartialUpdateBody {
|
|
|
708
717
|
index?: string;
|
|
709
718
|
objects: SprigrDataPatch[];
|
|
710
719
|
createIfNotExists: boolean;
|
|
720
|
+
/** Present (and true) only for an ACL re-stamp. */
|
|
721
|
+
withAcl?: true;
|
|
711
722
|
}
|
|
712
723
|
interface PartialUpdateDataOptions extends SprigrDataPartialUpdateOpts {
|
|
713
724
|
/** Ceiling on the HTTP fallback. No default: a data write is awaited for its result. */
|
|
@@ -2085,6 +2096,268 @@ declare function deriveConfirmationPolicy(registry: Iterable<string>, opts: Deri
|
|
|
2085
2096
|
*/
|
|
2086
2097
|
declare function commonDestructiveVerbs(subject: string): VerbRule[];
|
|
2087
2098
|
|
|
2099
|
+
/**
|
|
2100
|
+
* The inbox contract for mail and ticket apps (sprigr-team decision 0150).
|
|
2101
|
+
*
|
|
2102
|
+
* An app that writes into the Sprigr inbox does four things, all typed here:
|
|
2103
|
+
*
|
|
2104
|
+
* 1. Declares each channel it writes under `capabilities.inbox_channels` in
|
|
2105
|
+
* its manifest (`InboxChannelDeclaration`). Append refuses undeclared
|
|
2106
|
+
* channels, and replies cannot send without a declared send tool.
|
|
2107
|
+
* 2. Appends synced messages with `env.SPRIGR.inbox.append` (`SyncedMessage`,
|
|
2108
|
+
* `InboxOwner` on every batch). `withSprigrInboxFallback` makes that work
|
|
2109
|
+
* from an inline route too.
|
|
2110
|
+
* 3. Implements the declared send tool on the one `sprigr.inbox.send/v1`
|
|
2111
|
+
* argument shape, through `defineInboxSendHandler`, which enforces the
|
|
2112
|
+
* mailbox-owner rule so it cannot be forgotten.
|
|
2113
|
+
* 4. If it collects a credential on its own page (no OAuth bouncer),
|
|
2114
|
+
* reports the connection with `connectComplete`, which forwards the
|
|
2115
|
+
* platform-signed viewer token so the platform knows who connected.
|
|
2116
|
+
*/
|
|
2117
|
+
|
|
2118
|
+
declare const INBOX_SEND_CONTRACT_V1: "sprigr.inbox.send/v1";
|
|
2119
|
+
interface InboxSendSupports {
|
|
2120
|
+
bcc: boolean;
|
|
2121
|
+
html: boolean;
|
|
2122
|
+
recipients: boolean;
|
|
2123
|
+
importance: boolean;
|
|
2124
|
+
internal_note: boolean;
|
|
2125
|
+
}
|
|
2126
|
+
/** One entry of `capabilities.inbox_channels`. Validated at publish. */
|
|
2127
|
+
interface InboxChannelDeclaration {
|
|
2128
|
+
/** Must start with your slug in snake case plus `_` (`acme-mail` -> `acme_mail_...`). */
|
|
2129
|
+
channel: string;
|
|
2130
|
+
kind: 'email' | 'ticket';
|
|
2131
|
+
/** 1-40 printable characters, no `<` or `>`. */
|
|
2132
|
+
label: string;
|
|
2133
|
+
icon: 'mail' | 'ticket' | 'chat';
|
|
2134
|
+
send?: {
|
|
2135
|
+
tool: string;
|
|
2136
|
+
contract: typeof INBOX_SEND_CONTRACT_V1;
|
|
2137
|
+
returns_message_id: {
|
|
2138
|
+
send: boolean;
|
|
2139
|
+
reply: boolean;
|
|
2140
|
+
};
|
|
2141
|
+
attachments: 'file_key' | 'inline' | 'none';
|
|
2142
|
+
supports: InboxSendSupports;
|
|
2143
|
+
};
|
|
2144
|
+
signature_family?: string;
|
|
2145
|
+
attachment_repair_tool?: string;
|
|
2146
|
+
/** https only, host in `permissions.network_domains`; placeholders
|
|
2147
|
+
* `{source_thread_id}` and `{source_id}`. */
|
|
2148
|
+
deep_link_template?: string | null;
|
|
2149
|
+
}
|
|
2150
|
+
interface SyncedAttachment {
|
|
2151
|
+
filename: string;
|
|
2152
|
+
mimeType: string;
|
|
2153
|
+
size: number;
|
|
2154
|
+
/** Opaque to the platform; your `fetch_attachment` tool resolves it. It
|
|
2155
|
+
* must identify the connection, because that dispatch carries no actor. */
|
|
2156
|
+
providerAttachmentId?: string;
|
|
2157
|
+
contentId?: string;
|
|
2158
|
+
inline?: boolean;
|
|
2159
|
+
}
|
|
2160
|
+
interface SyncedMessage {
|
|
2161
|
+
/** Unique within the channel; the platform dedups on (channel, sourceId). */
|
|
2162
|
+
sourceId: string;
|
|
2163
|
+
sourceIndex: string;
|
|
2164
|
+
sourceThreadId?: string;
|
|
2165
|
+
/** RFC 5322 Message-ID (brackets optional). */
|
|
2166
|
+
rfcMessageId?: string;
|
|
2167
|
+
channel?: string;
|
|
2168
|
+
direction: 'inbound' | 'outbound' | 'internal';
|
|
2169
|
+
fromName?: string;
|
|
2170
|
+
fromEmail?: string;
|
|
2171
|
+
toRecipients?: Array<{
|
|
2172
|
+
name: string;
|
|
2173
|
+
email: string;
|
|
2174
|
+
}>;
|
|
2175
|
+
ccRecipients?: Array<{
|
|
2176
|
+
name: string;
|
|
2177
|
+
email: string;
|
|
2178
|
+
}>;
|
|
2179
|
+
subject?: string;
|
|
2180
|
+
/** Plain text. May be '' when `bodyHtml` carries the content. */
|
|
2181
|
+
body: string;
|
|
2182
|
+
bodyHtml?: string;
|
|
2183
|
+
/** ISO 8601. */
|
|
2184
|
+
timestamp: string;
|
|
2185
|
+
sourceIsRead?: boolean;
|
|
2186
|
+
tags?: string[];
|
|
2187
|
+
attachments?: SyncedAttachment[];
|
|
2188
|
+
}
|
|
2189
|
+
interface InboxAppendRequest {
|
|
2190
|
+
channel?: string;
|
|
2191
|
+
/** At most 500 per call. */
|
|
2192
|
+
messages: SyncedMessage[];
|
|
2193
|
+
/** The MAILBOX owner (from your connection row), never the dispatch caller. */
|
|
2194
|
+
owner?: InboxOwner;
|
|
2195
|
+
}
|
|
2196
|
+
interface InboxAppendResult {
|
|
2197
|
+
ok: boolean;
|
|
2198
|
+
threadsCreated: number;
|
|
2199
|
+
messagesLinked: number;
|
|
2200
|
+
threadIds: string[];
|
|
2201
|
+
contentlessSkipped?: number;
|
|
2202
|
+
}
|
|
2203
|
+
interface InboxFoldersRequest {
|
|
2204
|
+
channel?: string;
|
|
2205
|
+
/** At most 200 per call. Folder names match manual Sprigr folders. */
|
|
2206
|
+
items: Array<{
|
|
2207
|
+
sourceId: string;
|
|
2208
|
+
addFolders?: string[];
|
|
2209
|
+
removeFolders?: string[];
|
|
2210
|
+
}>;
|
|
2211
|
+
owner?: InboxOwner;
|
|
2212
|
+
}
|
|
2213
|
+
interface InboxFoldersResult {
|
|
2214
|
+
ok: boolean;
|
|
2215
|
+
filed: number;
|
|
2216
|
+
unfiled: number;
|
|
2217
|
+
skippedGuarded: number;
|
|
2218
|
+
unknownFolders: string[];
|
|
2219
|
+
}
|
|
2220
|
+
interface SprigrInboxApi {
|
|
2221
|
+
append(req: InboxAppendRequest): Promise<InboxAppendResult>;
|
|
2222
|
+
folders?(req: InboxFoldersRequest): Promise<InboxFoldersResult>;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Return an env whose `SPRIGR.inbox` works from any execution context.
|
|
2226
|
+
*
|
|
2227
|
+
* `env.SPRIGR` is injected only on the `/__sprigr/*` dispatch path. An inline
|
|
2228
|
+
* route (a provider push receiver, your settings page) gets the raw bindings,
|
|
2229
|
+
* so this wires the same contract over the install-token HTTP bridge. Append
|
|
2230
|
+
* and folders THROW on a non-2xx, so a caller never advances a sync cursor
|
|
2231
|
+
* past messages the platform did not accept. With no bridge bindings the env
|
|
2232
|
+
* comes back unchanged and `SPRIGR.inbox` stays undefined: fail closed.
|
|
2233
|
+
*/
|
|
2234
|
+
declare function withSprigrInboxFallback<E extends WfpBridgeEnv & object>(env: E): E;
|
|
2235
|
+
interface InboxAddress {
|
|
2236
|
+
email: string;
|
|
2237
|
+
name?: string;
|
|
2238
|
+
}
|
|
2239
|
+
/** What the platform sends your declared send tool. snake_case. */
|
|
2240
|
+
interface InboxSendV1Args {
|
|
2241
|
+
contract: typeof INBOX_SEND_CONTRACT_V1;
|
|
2242
|
+
from_email?: string;
|
|
2243
|
+
to?: InboxAddress[];
|
|
2244
|
+
cc?: InboxAddress[];
|
|
2245
|
+
bcc?: InboxAddress[];
|
|
2246
|
+
subject?: string;
|
|
2247
|
+
body_text: string;
|
|
2248
|
+
body_html?: string;
|
|
2249
|
+
attachments?: Array<{
|
|
2250
|
+
file_key: string;
|
|
2251
|
+
filename: string;
|
|
2252
|
+
content_type: string;
|
|
2253
|
+
}> | Array<{
|
|
2254
|
+
filename: string;
|
|
2255
|
+
mime_type?: string;
|
|
2256
|
+
base64_body: string;
|
|
2257
|
+
}>;
|
|
2258
|
+
/** `source_id` / `source_thread_id` are YOUR stored ids, present only when
|
|
2259
|
+
* your install received the mail being answered. */
|
|
2260
|
+
reply?: {
|
|
2261
|
+
source_id?: string;
|
|
2262
|
+
source_thread_id?: string;
|
|
2263
|
+
rfc_message_id?: string;
|
|
2264
|
+
references: string[];
|
|
2265
|
+
};
|
|
2266
|
+
internal_note?: boolean;
|
|
2267
|
+
author_email?: string;
|
|
2268
|
+
importance?: 'high' | 'low';
|
|
2269
|
+
idempotency_key?: string;
|
|
2270
|
+
}
|
|
2271
|
+
/** What your send tool returns. `message_id` must equal the `sourceId` your
|
|
2272
|
+
* sync will write for the sent copy, so the Sent echo dedups. */
|
|
2273
|
+
interface InboxSendV1Result {
|
|
2274
|
+
message_id?: string;
|
|
2275
|
+
internet_message_id?: string;
|
|
2276
|
+
thread_id?: string;
|
|
2277
|
+
}
|
|
2278
|
+
/** Your connection record, as far as the owner check needs it. */
|
|
2279
|
+
interface InboxSendConnection {
|
|
2280
|
+
/** `u:<platformUserId>` or `a:<agentId>`: the key the connection is filed under. */
|
|
2281
|
+
actorKey: string;
|
|
2282
|
+
}
|
|
2283
|
+
interface InboxSendHandlerImpl<E, C extends InboxSendConnection> {
|
|
2284
|
+
/** The connection that owns one of YOUR stored source ids, or null when the
|
|
2285
|
+
* id is not yours. Parse your own id format here. */
|
|
2286
|
+
connectionForSourceId(env: E, sourceId: string): Promise<C | null>;
|
|
2287
|
+
/** The connection this actor key sends a NEW mail from. When `fromEmail` is
|
|
2288
|
+
* set, return only a connection that may send as that address, else null. */
|
|
2289
|
+
connectionForActor(env: E, actorKey: string, fromEmail: string | undefined): Promise<C | null>;
|
|
2290
|
+
send(env: E, connection: C, args: InboxSendV1Args): Promise<InboxSendV1Result>;
|
|
2291
|
+
}
|
|
2292
|
+
declare class InboxSendRefusal extends Error {
|
|
2293
|
+
readonly code: 'no_caller_identity' | 'not_your_mailbox' | 'not_connected' | 'invalid_args';
|
|
2294
|
+
constructor(code: 'no_caller_identity' | 'not_your_mailbox' | 'not_connected' | 'invalid_args', message: string);
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Build the handler for your declared `sprigr.inbox.send/v1` tool.
|
|
2298
|
+
*
|
|
2299
|
+
* THE MAILBOX-OWNER RULE, enforced here so no app can omit it: replies are
|
|
2300
|
+
* dispatched under the WRITER's actor, and anyone who can read a thread can
|
|
2301
|
+
* press Reply. So the connection you send from must be filed under one of the
|
|
2302
|
+
* writer's own keys (`u:<platformUserId>`, `a:<agentId>`). A reply whose
|
|
2303
|
+
* source belongs to someone else's connection is REFUSED, never re-routed to
|
|
2304
|
+
* the writer's own mailbox and never sent from the owner's: either would
|
|
2305
|
+
* deliver mail the writer is not entitled to send.
|
|
2306
|
+
*/
|
|
2307
|
+
declare function defineInboxSendHandler<E, C extends InboxSendConnection>(impl: InboxSendHandlerImpl<E, C>): (args: Record<string, unknown>, env: E) => Promise<ToolResult<InboxSendV1Result>>;
|
|
2308
|
+
interface ConnectCompleteRequest {
|
|
2309
|
+
/** The raw `X-Sprigr-Viewer` header of the PAGE request that submitted the
|
|
2310
|
+
* credential. The platform takes the user only from this. */
|
|
2311
|
+
viewer_token: string;
|
|
2312
|
+
bind?: {
|
|
2313
|
+
kind: 'user';
|
|
2314
|
+
} | {
|
|
2315
|
+
kind: 'agent';
|
|
2316
|
+
agent_id: string;
|
|
2317
|
+
};
|
|
2318
|
+
account: {
|
|
2319
|
+
email: string;
|
|
2320
|
+
name?: string;
|
|
2321
|
+
};
|
|
2322
|
+
identities?: Array<{
|
|
2323
|
+
email: string;
|
|
2324
|
+
name?: string;
|
|
2325
|
+
}>;
|
|
2326
|
+
}
|
|
2327
|
+
interface ConnectCompleteResult {
|
|
2328
|
+
ok: boolean;
|
|
2329
|
+
/** File your connection under this key. */
|
|
2330
|
+
actor_key: string;
|
|
2331
|
+
identities: {
|
|
2332
|
+
inserted: number;
|
|
2333
|
+
updated: number;
|
|
2334
|
+
deleted: number;
|
|
2335
|
+
};
|
|
2336
|
+
/** The same address already connected through another app in the company. */
|
|
2337
|
+
existing_mailboxes: Array<{
|
|
2338
|
+
app_slug: string;
|
|
2339
|
+
email: string;
|
|
2340
|
+
account_email: string;
|
|
2341
|
+
}>;
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Report a mailbox connected on your own page (token paste, app password).
|
|
2345
|
+
* Call it from the inline route that handled the page's POST, passing that
|
|
2346
|
+
* request's `X-Sprigr-Viewer` header. Throws on a non-2xx with `.status` and
|
|
2347
|
+
* `.error` (`viewer_unverified`, `viewer_not_a_member`, `agent_bind_refused`,
|
|
2348
|
+
* `agent_bind_requires_admin`, ...).
|
|
2349
|
+
*/
|
|
2350
|
+
declare function connectComplete(env: WfpBridgeEnv, req: ConnectCompleteRequest): Promise<ConnectCompleteResult>;
|
|
2351
|
+
/** Retire a connection's From-picker identities (install token only, so a
|
|
2352
|
+
* background sync can call it when a credential dies). */
|
|
2353
|
+
declare function connectDisconnect(env: WfpBridgeEnv, req: {
|
|
2354
|
+
actor_key: string;
|
|
2355
|
+
account_email?: string;
|
|
2356
|
+
}): Promise<{
|
|
2357
|
+
ok: boolean;
|
|
2358
|
+
deleted: number;
|
|
2359
|
+
}>;
|
|
2360
|
+
|
|
2088
2361
|
/**
|
|
2089
2362
|
* @sprigr/apps-app-sdk
|
|
2090
2363
|
*
|
|
@@ -2244,6 +2517,17 @@ interface SprigrDataPartialUpdateOpts extends SprigrDataIndexOpts {
|
|
|
2244
2517
|
* missing target is counted in `skippedMissing` instead of being written.
|
|
2245
2518
|
*/
|
|
2246
2519
|
createIfNotExists?: boolean;
|
|
2520
|
+
/**
|
|
2521
|
+
* Re-stamp `acl_principals` on rows already in the app's `-acl-files`
|
|
2522
|
+
* index without rewriting the rest of each row. Every object must be
|
|
2523
|
+
* exactly `{ objectID, acl_principals }` with a non-empty, valid principal
|
|
2524
|
+
* list; `createIfNotExists` and `index` are refused alongside it. Use it
|
|
2525
|
+
* when the source changes who may see an item without reporting the item
|
|
2526
|
+
* as changed (OneDrive's delta omits sharing-only changes). Needs a
|
|
2527
|
+
* platform with sprigr/sprigr-team#9843; older wrappers drop the flag,
|
|
2528
|
+
* which `partialUpdateData` detects and throws on.
|
|
2529
|
+
*/
|
|
2530
|
+
withAcl?: boolean;
|
|
2247
2531
|
}
|
|
2248
2532
|
/** Reply of `env.SPRIGR.data.partialUpdate` (`POST /internal/wfp/data/partial-update`). */
|
|
2249
2533
|
interface SprigrDataPartialUpdateResult {
|
|
@@ -2614,4 +2898,4 @@ interface ExecutionContextLike {
|
|
|
2614
2898
|
}
|
|
2615
2899
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
2616
2900
|
|
|
2617
|
-
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 };
|
|
2901
|
+
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
|
@@ -512,6 +512,7 @@ function withSprigrLogFallback(env, opts) {
|
|
|
512
512
|
// src/platform-data.ts
|
|
513
513
|
var DATA_PARTIAL_UPDATE_PATH = "/internal/wfp/data/partial-update";
|
|
514
514
|
var SPRIGR_DATA_MAX_OBJECTS_PER_CALL = 1e3;
|
|
515
|
+
var ACL_INDEX_SUFFIX = "-acl-files";
|
|
515
516
|
var SprigrDataValidationError = class extends Error {
|
|
516
517
|
error;
|
|
517
518
|
index;
|
|
@@ -556,12 +557,51 @@ function buildPartialUpdateBody(objects, opts) {
|
|
|
556
557
|
error: "unknown_data_index"
|
|
557
558
|
});
|
|
558
559
|
}
|
|
560
|
+
const withAcl = opts?.withAcl === true;
|
|
561
|
+
if (withAcl) checkAclRestamp(objects, opts, at);
|
|
559
562
|
return {
|
|
560
563
|
...opts?.index !== void 0 ? { index: opts.index } : {},
|
|
561
564
|
objects,
|
|
562
|
-
createIfNotExists: opts?.createIfNotExists === true
|
|
565
|
+
createIfNotExists: opts?.createIfNotExists === true,
|
|
566
|
+
...withAcl ? { withAcl: true } : {}
|
|
563
567
|
};
|
|
564
568
|
}
|
|
569
|
+
function checkAclRestamp(objects, opts, at) {
|
|
570
|
+
if (opts?.createIfNotExists === true) {
|
|
571
|
+
throw new SprigrDataValidationError(
|
|
572
|
+
`${at}: withAcl only re-stamps existing rows; createIfNotExists would create a row with principals and no content`,
|
|
573
|
+
{ error: "acl_partial_update_create_unsupported" }
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
if (opts?.index !== void 0) {
|
|
577
|
+
throw new SprigrDataValidationError(`${at}: withAcl targets the reserved -acl-files index and cannot be combined with index`, {
|
|
578
|
+
error: "acl_index_combination_unsupported"
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
for (let i = 0; i < objects.length; i++) {
|
|
582
|
+
const o = objects[i];
|
|
583
|
+
const extra = Object.keys(o).find((k) => k !== "objectID" && k !== "acl_principals");
|
|
584
|
+
if (extra !== void 0) {
|
|
585
|
+
throw new SprigrDataValidationError(
|
|
586
|
+
`${at}: objects[${i}] carries "${extra}"; a withAcl partial update sets only objectID and acl_principals (write content with data.import withAcl)`,
|
|
587
|
+
{ error: "acl_partial_update_fields", index: i }
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
const principals = o.acl_principals;
|
|
591
|
+
if (!Array.isArray(principals) || principals.length === 0) {
|
|
592
|
+
throw new SprigrDataValidationError(`${at}: objects[${i}] needs a non-empty acl_principals: string[]`, {
|
|
593
|
+
error: "missing_acl_principals",
|
|
594
|
+
index: i
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function assertAclIndex(result) {
|
|
600
|
+
if (typeof result?.index === "string" && result.index.endsWith(ACL_INDEX_SUFFIX)) return result;
|
|
601
|
+
throw new Error(
|
|
602
|
+
`data.partialUpdate withAcl landed on ${String(result?.index)}, not the ${ACL_INDEX_SUFFIX} index: this app was built by a platform wrapper that drops withAcl. Republish the app to pick up the current wrapper.`
|
|
603
|
+
);
|
|
604
|
+
}
|
|
565
605
|
function bindingPartialUpdate(env) {
|
|
566
606
|
const data = env.SPRIGR?.data;
|
|
567
607
|
return typeof data?.partialUpdate === "function" ? data.partialUpdate.bind(data) : null;
|
|
@@ -579,10 +619,12 @@ async function partialUpdateData(env, objects, opts) {
|
|
|
579
619
|
const body = buildPartialUpdateBody(objects, dataOpts);
|
|
580
620
|
const injected = bindingPartialUpdate(env);
|
|
581
621
|
if (injected) {
|
|
582
|
-
|
|
622
|
+
const result = await injected(body.objects, {
|
|
583
623
|
...body.index !== void 0 ? { index: body.index } : {},
|
|
584
|
-
createIfNotExists: body.createIfNotExists
|
|
624
|
+
createIfNotExists: body.createIfNotExists,
|
|
625
|
+
...body.withAcl ? { withAcl: true } : {}
|
|
585
626
|
});
|
|
627
|
+
return body.withAcl ? assertAclIndex(result) : result;
|
|
586
628
|
}
|
|
587
629
|
const bridge = resolveInstallBridge(env);
|
|
588
630
|
if (!bridge) {
|
|
@@ -1446,6 +1488,109 @@ function commonDestructiveVerbs(subject) {
|
|
|
1446
1488
|
{ match: "deactivate_", tier: "always", describe: (_n, rest) => `Deactivate ${rest}${where}` }
|
|
1447
1489
|
];
|
|
1448
1490
|
}
|
|
1491
|
+
|
|
1492
|
+
// src/inbox.ts
|
|
1493
|
+
var INBOX_SEND_CONTRACT_V1 = "sprigr.inbox.send/v1";
|
|
1494
|
+
function withSprigrInboxFallback(env) {
|
|
1495
|
+
const bridge = resolveInstallBridge(env);
|
|
1496
|
+
const existing = env.SPRIGR ?? void 0;
|
|
1497
|
+
const folders = bridge ? async (req) => await installTokenPost(bridge, "/internal/wfp/inbox/folders", req, { label: "inbox folders" }) : void 0;
|
|
1498
|
+
if (existing?.inbox) {
|
|
1499
|
+
if (existing.inbox.folders || !folders) return env;
|
|
1500
|
+
const inbox = existing.inbox;
|
|
1501
|
+
return overlaySprigr(env, { ...existing, inbox: { append: inbox.append.bind(inbox), folders } });
|
|
1502
|
+
}
|
|
1503
|
+
if (!bridge) return env;
|
|
1504
|
+
const append = async (req) => {
|
|
1505
|
+
const r = await installTokenPost(bridge, "/internal/wfp/inbox/append", req, { label: "inbox append" });
|
|
1506
|
+
return {
|
|
1507
|
+
ok: r.ok !== false,
|
|
1508
|
+
threadsCreated: r.threadsCreated ?? 0,
|
|
1509
|
+
messagesLinked: r.messagesLinked ?? 0,
|
|
1510
|
+
threadIds: Array.isArray(r.threadIds) ? r.threadIds : [],
|
|
1511
|
+
...typeof r.contentlessSkipped === "number" ? { contentlessSkipped: r.contentlessSkipped } : {}
|
|
1512
|
+
};
|
|
1513
|
+
};
|
|
1514
|
+
return overlaySprigr(env, { ...existing ?? {}, inbox: { append, folders } });
|
|
1515
|
+
}
|
|
1516
|
+
var InboxSendRefusal = class extends Error {
|
|
1517
|
+
constructor(code, message) {
|
|
1518
|
+
super(message);
|
|
1519
|
+
this.code = code;
|
|
1520
|
+
this.name = "InboxSendRefusal";
|
|
1521
|
+
}
|
|
1522
|
+
code;
|
|
1523
|
+
};
|
|
1524
|
+
function defineInboxSendHandler(impl) {
|
|
1525
|
+
return async (rawArgs, env) => {
|
|
1526
|
+
try {
|
|
1527
|
+
const actor = parseActor(rawArgs);
|
|
1528
|
+
const keys = [
|
|
1529
|
+
...actor?.platformUserId ? [`u:${actor.platformUserId}`] : [],
|
|
1530
|
+
...actor?.agentId ? [`a:${actor.agentId}`] : []
|
|
1531
|
+
];
|
|
1532
|
+
if (keys.length === 0) {
|
|
1533
|
+
throw new InboxSendRefusal("no_caller_identity", "The platform stamped no sender identity on this send.");
|
|
1534
|
+
}
|
|
1535
|
+
const args = rawArgs;
|
|
1536
|
+
if (args.contract !== INBOX_SEND_CONTRACT_V1 || typeof args.body_text !== "string") {
|
|
1537
|
+
throw new InboxSendRefusal("invalid_args", `Expected ${INBOX_SEND_CONTRACT_V1} arguments.`);
|
|
1538
|
+
}
|
|
1539
|
+
let connection = null;
|
|
1540
|
+
const sourceId = args.reply?.source_id;
|
|
1541
|
+
if (sourceId) {
|
|
1542
|
+
const owner = await impl.connectionForSourceId(env, sourceId);
|
|
1543
|
+
if (owner) {
|
|
1544
|
+
if (!keys.includes(owner.actorKey)) {
|
|
1545
|
+
throw new InboxSendRefusal(
|
|
1546
|
+
"not_your_mailbox",
|
|
1547
|
+
"This conversation arrived in a mailbox that belongs to someone else, so you cannot reply from it."
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
if (args.from_email) {
|
|
1551
|
+
const asFrom = await impl.connectionForActor(env, owner.actorKey, args.from_email);
|
|
1552
|
+
if (!asFrom || asFrom.actorKey !== owner.actorKey) {
|
|
1553
|
+
throw new InboxSendRefusal("not_your_mailbox", `This mailbox cannot send as ${args.from_email}.`);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
connection = owner;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (!connection) {
|
|
1560
|
+
for (const key of keys) {
|
|
1561
|
+
connection = await impl.connectionForActor(env, key, args.from_email);
|
|
1562
|
+
if (connection) break;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
if (!connection) {
|
|
1566
|
+
throw new InboxSendRefusal(
|
|
1567
|
+
"not_connected",
|
|
1568
|
+
args.from_email ? `You have no connection that can send as ${args.from_email}.` : "You have not connected a mailbox with this app."
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
if (!keys.includes(connection.actorKey)) {
|
|
1572
|
+
throw new InboxSendRefusal("not_your_mailbox", "Resolved connection does not belong to the sender.");
|
|
1573
|
+
}
|
|
1574
|
+
return { ok: true, result: await impl.send(env, connection, args) };
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
if (err instanceof InboxSendRefusal || err instanceof Error && err.name === "InboxSendRefusal") {
|
|
1577
|
+
const code = err.code;
|
|
1578
|
+
return { ok: false, error: `${code}: ${err.message}`, status: code === "invalid_args" ? 400 : 412 };
|
|
1579
|
+
}
|
|
1580
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
async function connectComplete(env, req) {
|
|
1585
|
+
const bridge = resolveInstallBridge(env);
|
|
1586
|
+
if (!bridge) throw new Error("connectComplete: SPRIGR_PLATFORM_BASE / SPRIGR_INSTALL_TOKEN are not bound");
|
|
1587
|
+
return await installTokenPost(bridge, "/internal/wfp/connect/complete", req, { label: "connect complete" });
|
|
1588
|
+
}
|
|
1589
|
+
async function connectDisconnect(env, req) {
|
|
1590
|
+
const bridge = resolveInstallBridge(env);
|
|
1591
|
+
if (!bridge) throw new Error("connectDisconnect: SPRIGR_PLATFORM_BASE / SPRIGR_INSTALL_TOKEN are not bound");
|
|
1592
|
+
return await installTokenPost(bridge, "/internal/wfp/connect/disconnect", req, { label: "connect disconnect" });
|
|
1593
|
+
}
|
|
1449
1594
|
export {
|
|
1450
1595
|
APPROVAL_GRANTED_KEY,
|
|
1451
1596
|
DATA_PARTIAL_UPDATE_PATH,
|
|
@@ -1454,6 +1599,8 @@ export {
|
|
|
1454
1599
|
DEFAULT_MAX_FILE_BYTES,
|
|
1455
1600
|
DEFAULT_MONEY_FIELDS,
|
|
1456
1601
|
DEFAULT_WRITE_PREFIXES,
|
|
1602
|
+
INBOX_SEND_CONTRACT_V1,
|
|
1603
|
+
InboxSendRefusal,
|
|
1457
1604
|
InvalidUndoEnvelopeError,
|
|
1458
1605
|
NotConnectedError,
|
|
1459
1606
|
SPRIGR_DATA_MAX_OBJECTS_PER_CALL,
|
|
@@ -1483,10 +1630,13 @@ export {
|
|
|
1483
1630
|
canPartialUpdate,
|
|
1484
1631
|
checkConfirmationPolicy,
|
|
1485
1632
|
commonDestructiveVerbs,
|
|
1633
|
+
connectComplete,
|
|
1634
|
+
connectDisconnect,
|
|
1486
1635
|
constantTimeEqual,
|
|
1487
1636
|
createMarketplaceEmitter,
|
|
1488
1637
|
createToolWrappers,
|
|
1489
1638
|
decodeState,
|
|
1639
|
+
defineInboxSendHandler,
|
|
1490
1640
|
deleteAppFile,
|
|
1491
1641
|
deriveConfirmationPolicy,
|
|
1492
1642
|
describeMissingBridge,
|
|
@@ -1533,5 +1683,6 @@ export {
|
|
|
1533
1683
|
validateLogEntry,
|
|
1534
1684
|
verifyViewerToken,
|
|
1535
1685
|
withSprigrEmitFallback,
|
|
1686
|
+
withSprigrInboxFallback,
|
|
1536
1687
|
withSprigrLogFallback
|
|
1537
1688
|
};
|