@sprigr/apps-app-sdk 0.26.0 → 0.28.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 CHANGED
@@ -493,9 +493,10 @@ declare function readFileRef(env: FileRefEnv, ref: FileRef | null | undefined, o
493
493
  * Apps that receive webhooks on their OWN per-install URL (e.g.
494
494
  * procore POSTs to `<procore-XXX.staging-apps.sprigr.com>/api/webhook/procore`)
495
495
  * don't go through the marketplace runtime dispatcher. Those apps
496
- * derive the callback URL from the inbound request's `Host` header
497
- * instead, which is naturally env-correct. This helper is only for
498
- * the marketplace-dispatcher pattern.
496
+ * build the callback URL from `publicBaseUrl(env)` below. Never from
497
+ * the inbound request's `Host` or `X-Forwarded-Host`: a visitor
498
+ * controls those, and a callback URL built from one hands an outside
499
+ * host to the provider (sprigr-apps#3453).
499
500
  */
500
501
  /**
501
502
  * Minimal env shape this helper reads. Apps' ShopifyEnv /
@@ -531,6 +532,35 @@ declare function resolvePlatformWebhookBase(env: PlatformHostEnv, override?: str
531
532
  * form should pre-translate before calling.
532
533
  */
533
534
  declare function buildMarketplaceWebhookUrl(env: PlatformHostEnv, installId: string, topicPath: string, override?: string | null): string;
535
+ /** Minimal env shape `publicBaseUrl` reads; pass the full app env. */
536
+ interface PublicBaseUrlEnv {
537
+ SITE_PUBLIC_BASE_URL?: string;
538
+ }
539
+ /**
540
+ * This install's own public https origin, as the platform bound it:
541
+ * the install site's custom domain when one is configured, else its
542
+ * slug-free `<app>-<tail>.apps.sprigr.com` host (`.staging-apps` on
543
+ * staging). Never has a trailing slash or a path.
544
+ *
545
+ * const base = publicBaseUrl(env);
546
+ * if (!base) return refuse('no_public_base_url');
547
+ * const callbackUrl = `${base}/api/public/myapp/webhook/${subKey}`;
548
+ *
549
+ * Use it for every URL handed to an outside system: webhook and push
550
+ * callbacks, links in outbound mail, a provider's "return to" URL.
551
+ * Do NOT derive one from a request's `Host` / `X-Forwarded-Host` (a
552
+ * visitor sets those), and do NOT store a request-derived origin for
553
+ * later use (sprigr-apps#3453). The binding is not tied to the
554
+ * company slug, so it survives a company rename (sprigr-team#11787).
555
+ * It does change when a custom domain is added or removed (the
556
+ * platform rebinds the install), so an app that registered callbacks
557
+ * should re-check them on its reconcile, not only at connect time.
558
+ *
559
+ * Returns `null` when the binding is absent or is not an https URL
560
+ * (a unit test, a local `sprigr app dev`). Callers should refuse and
561
+ * say why rather than fall back to a request-derived host.
562
+ */
563
+ declare function publicBaseUrl(env: PublicBaseUrlEnv): string | null;
534
564
  /**
535
565
  * Is this URL (or bare hostname) served by a STAGING host?
536
566
  *
@@ -1018,7 +1048,12 @@ interface SprigrDataPartialUpdateBody {
1018
1048
  withAcl?: true;
1019
1049
  }
1020
1050
  interface PartialUpdateDataOptions extends SprigrDataPartialUpdateOpts {
1021
- /** Ceiling on the HTTP fallback. No default: a data write is awaited for its result. */
1051
+ /**
1052
+ * Ceiling on the call, by either transport: the injected member bounds its
1053
+ * own fetch (sprigr/sprigr-team#11950; older wrapper builds ignore it), the
1054
+ * HTTP fallback its request. No default: a data write is awaited for its
1055
+ * result.
1056
+ */
1022
1057
  timeoutMs?: number;
1023
1058
  }
1024
1059
  /**
@@ -1728,9 +1763,14 @@ interface ApprovalPreviewSpec {
1728
1763
  bcc?: ApprovalPreviewSlot;
1729
1764
  subject?: ApprovalPreviewSlot;
1730
1765
  /** Always an argument, never a stated value: the body must be what is sent. */
1766
+ /**
1767
+ * `format: 'auto'` declares that the tool sends the body through
1768
+ * `emailBodyHtml`: the card shows a body with HTML markup as HTML and plain
1769
+ * text as text. A platform that predates 'auto' reads it as 'html'.
1770
+ */
1731
1771
  body?: {
1732
1772
  arg: string;
1733
- format: 'html' | 'text';
1773
+ format: 'html' | 'text' | 'auto';
1734
1774
  };
1735
1775
  /** Argument holding an array of attachment names or `{ name, size }`. */
1736
1776
  attachments?: string;
@@ -1853,8 +1893,11 @@ interface EmailPreviewInput {
1853
1893
  subject?: ApprovalPreviewSlot;
1854
1894
  /** Argument name of the body. */
1855
1895
  body: string;
1856
- /** Defaults to 'html'. */
1857
- bodyFormat?: 'html' | 'text';
1896
+ /**
1897
+ * Defaults to 'html'. Use 'auto' when the tool sends the body through
1898
+ * `emailBodyHtml`, so a plain-text body shows on the card with its lines.
1899
+ */
1900
+ bodyFormat?: 'html' | 'text' | 'auto';
1858
1901
  attachments?: string;
1859
1902
  sendAt?: ApprovalPreviewSlot;
1860
1903
  /** Defaults to ['irreversible']: a sent email cannot be recalled. */
@@ -3083,6 +3126,30 @@ interface CompanyTimezoneEnv {
3083
3126
  */
3084
3127
  declare function getCompanyTimezone(env: CompanyTimezoneEnv | null | undefined): string | undefined;
3085
3128
 
3129
+ /**
3130
+ * Email bodies that a mail API always sends as HTML.
3131
+ *
3132
+ * Outlook (Graph `contentType: 'HTML'`) and Gmail (`text/html`) render the body
3133
+ * as HTML, and the agent often writes plain text with line breaks. Rendered as
3134
+ * HTML those breaks collapse, so a three-paragraph email arrives as one run-on
3135
+ * line. `emailBodyHtml` keeps a body that already carries HTML markup exactly
3136
+ * as written, and turns a plain-text body into HTML that shows the same lines:
3137
+ * a blank line starts a new paragraph, a single line break becomes `<br>`, and
3138
+ * `&`, `<`, `>` and `"` are escaped so the text reads as written.
3139
+ *
3140
+ * A tool that sends through this declares `emailPreview({ bodyFormat: 'auto' })`.
3141
+ * The platform's approval card then applies the same test (`hasHtmlMarkup`) and
3142
+ * shows a plain-text body as text, the way it will arrive. Keep the element
3143
+ * list in step with `packages/agent-core/src/approval-preview.ts` in sprigr-team.
3144
+ */
3145
+ /** True when the body contains an HTML element tag or a doctype. */
3146
+ declare function hasHtmlMarkup(body: string): boolean;
3147
+ /**
3148
+ * The body to hand an HTML-only mail API. HTML input is returned unchanged;
3149
+ * plain text becomes paragraphs and line breaks. An empty body stays empty.
3150
+ */
3151
+ declare function emailBodyHtml(body: string): string;
3152
+
3086
3153
  /**
3087
3154
  * @sprigr/apps-app-sdk
3088
3155
  *
@@ -3234,8 +3301,24 @@ interface SprigrDataIndexOpts {
3234
3301
  /** LOGICAL index name (a `data_indexes` key). See {@link SprigrDataSearchOpts.index}. */
3235
3302
  index?: string;
3236
3303
  }
3304
+ /**
3305
+ * Opt-in bound for one data write (`import`, `partialUpdate`, `delete`),
3306
+ * the same knob `data.search` takes (sprigr/sprigr-team#11950).
3307
+ */
3308
+ interface SprigrDataWriteTimeoutOpts {
3309
+ /**
3310
+ * Abort the call after this many ms and throw a named error
3311
+ * (`data_import_timeout`, `data_partial_update_timeout`,
3312
+ * `data_delete_timeout`) whose message names the index. Omit it and the
3313
+ * call is unbounded as before (`import` keeps the platform's 100 s
3314
+ * backstop). The abort is client-side only: the platform call may still
3315
+ * complete, so retry only idempotent writes. Ignored by wrapper builds
3316
+ * older than sprigr/sprigr-team#11950.
3317
+ */
3318
+ timeoutMs?: number;
3319
+ }
3237
3320
  /** Options for `env.SPRIGR.data.import`. */
3238
- interface SprigrDataImportOpts extends SprigrDataIndexOpts {
3321
+ interface SprigrDataImportOpts extends SprigrDataIndexOpts, SprigrDataWriteTimeoutOpts {
3239
3322
  /**
3240
3323
  * Write to the app's reserved ACL file index
3241
3324
  * (`<companyId>-app-<slug>-acl-files`) instead of its data index. Every
@@ -3250,7 +3333,7 @@ interface SprigrDataImportOpts extends SprigrDataIndexOpts {
3250
3333
  withAcl?: boolean;
3251
3334
  }
3252
3335
  /** Options for `env.SPRIGR.data.partialUpdate`. */
3253
- interface SprigrDataPartialUpdateOpts extends SprigrDataIndexOpts {
3336
+ interface SprigrDataPartialUpdateOpts extends SprigrDataIndexOpts, SprigrDataWriteTimeoutOpts {
3254
3337
  /**
3255
3338
  * Create an object that does not exist yet from the patch alone. Default
3256
3339
  * `false`: a patch never creates half an object unless asked, and a
@@ -3390,7 +3473,7 @@ interface SprigrDataApi {
3390
3473
  * uncertainty), per the mirror-maintenance guidance in
3391
3474
  * docs/platform-reference.md.
3392
3475
  */
3393
- delete?(objectIDs: string[], opts?: SprigrDataIndexOpts & {
3476
+ delete?(objectIDs: string[], opts?: SprigrDataIndexOpts & SprigrDataWriteTimeoutOpts & {
3394
3477
  withAcl?: boolean;
3395
3478
  }): Promise<{
3396
3479
  ok: boolean;
@@ -3439,10 +3522,12 @@ interface SprigrFilesApi {
3439
3522
  * Stream bytes into the app's store under `key` (app-relative; the platform
3440
3523
  * adds/strips the `_apps/{installId}/` prefix). A `ReadableStream` is piped
3441
3524
  * straight through to R2 without buffering whole in the isolate, so use it
3442
- * for multi-MB blobs (videos, full-res photos, PDFs). Size cap: 50 MB.
3525
+ * for multi-MB blobs (videos, full-res photos, PDFs). Size cap: 200 MB
3526
+ * (`MAX_STREAM_PUT_BYTES` in sprigr-team `wfp-files.ts`).
3443
3527
  * `opts.length` declares the byte length so the platform can enforce the cap
3444
3528
  * before opening the write; forward it for streamed bodies, which carry no
3445
- * Content-Length (`bytes` comes back `null` when it is omitted).
3529
+ * Content-Length. The platform checks the cap ONLY against a declared
3530
+ * length, so always pass it (`bytes` comes back `null` when it is omitted).
3446
3531
  */
3447
3532
  putStream(key: string, body: ReadableStream | ArrayBuffer | Uint8Array | string, opts?: {
3448
3533
  contentType?: string;
@@ -3657,4 +3742,4 @@ interface ExecutionContextLike {
3657
3742
  }
3658
3743
  type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
3659
3744
 
3660
- export { ACTOR_RETIRED_HOOK_TOOL, ACTOR_RETIRE_REASONS, APPROVAL_GRANTED_KEY, APPROVAL_PREVIEW_MAX, APP_FILES_ROOT, APP_OWNER_SEGMENT, type Actor, type ActorRetireReason, type ActorRetired, type ActorRetiredHookBody, type ActorRetiredResult, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalCoverage, type ApprovalGateOptions, type ApprovalPreviewFact, type ApprovalPreviewSlot, type ApprovalPreviewSpec, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CallerKeyResolution, type CaptureJournal, type CompanyTimezoneEnv, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type ConnectCompleteRequest, type ConnectCompleteResult, type D1Like, type D1PreparedStatementLike, DATA_DELETE_MAX_IDS, DATA_PARTIAL_UPDATE_PATH, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_FILE_READ_TIMEOUT_MS, DEFAULT_LOG_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DataClient, type DataIdsOpts, type DataListIdsResult, type DataTransport, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmailPreviewInput, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EncodeStateWithinEnvelopeOptions, type EncodedStateWithinEnvelope, type EventArgs, type ExecutionContextLike, FILE_REF_SOURCES_HELP, type FetchFileResult, type FetchWithRetryOptions, type FileRef, type FileRefContent, type FileRefEnv, FileRefError, type FileRefErrorCode, type FileRefStream, type FileRefVia, type GetAppFileResult, type HandlerFn, INBOX_SEND_CONTRACT_V1, type IdPrefixSpec, type IdSnapshot, 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, OAUTH_STATE_ENVELOPE_MAX_CHARS, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PartialUpdateDataOptions, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PurgeResult, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ReadFileRefOptions, type RequireApprovalOptions, type ResolveFileRefOptions, type ResolveViewerOptions, type ResolvedFileRef, type RestoreResult, type RestoreSpec, SPRIGR_DATA_MAX_OBJECTS_PER_CALL, SPRIGR_FILE_HOSTS, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SmsPreviewInput, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataImportOpts, 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, WORKSPACE_FILE_HOW_TO, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorRetiredHook, actorTool, agentFileKeyFromUrl, appFileUrl, appKeyFromCallerKey, applyConfirmationPolicies, approvalHash, approvalPreview, approvalRecipients, archiveOfferRefusal, attendedFindings, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, buildPartialUpdateBody, bytesToBase64, callerKeyRefusal, canEmit, canLog, canPartialUpdate, checkConfirmationPolicy, commonDestructiveVerbs, connectComplete, connectDisconnect, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, defineInboxSendHandler, deleteAppFile, deleteSnapshotIds, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emailPreview, emitMarketplaceEvent, encodeState, encodeStateWithinEnvelope, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fieldsPreview, fullWarning, getAppFile, getCompanyTimezone, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, logToPlatform, makeDataClient, manifestIsFresh, oauthStateEnvelopeOverhead, offerUndo, openFileRef, overlaySprigr, ownerFromActorKey, parseActor, parseActorRetiredArgs, partialUpdateData, purgeIndexByPrefix, putAppFile, putAppFileStream, randomHex, readFileRef, readableStoredKey, recreatedWarning, refuseWithoutForce, requireApproval, resolveFileRef, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, smsPreview, snapshotIndexIds, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrInboxFallback, withSprigrLogFallback };
3745
+ export { ACTOR_RETIRED_HOOK_TOOL, ACTOR_RETIRE_REASONS, APPROVAL_GRANTED_KEY, APPROVAL_PREVIEW_MAX, APP_FILES_ROOT, APP_OWNER_SEGMENT, type Actor, type ActorRetireReason, type ActorRetired, type ActorRetiredHookBody, type ActorRetiredResult, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalCoverage, type ApprovalGateOptions, type ApprovalPreviewFact, type ApprovalPreviewSlot, type ApprovalPreviewSpec, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CallerKeyResolution, type CaptureJournal, type CompanyTimezoneEnv, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type ConnectCompleteRequest, type ConnectCompleteResult, type D1Like, type D1PreparedStatementLike, DATA_DELETE_MAX_IDS, DATA_PARTIAL_UPDATE_PATH, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_FILE_READ_TIMEOUT_MS, DEFAULT_LOG_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DataClient, type DataIdsOpts, type DataListIdsResult, type DataTransport, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmailPreviewInput, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EncodeStateWithinEnvelopeOptions, type EncodedStateWithinEnvelope, type EventArgs, type ExecutionContextLike, FILE_REF_SOURCES_HELP, type FetchFileResult, type FetchWithRetryOptions, type FileRef, type FileRefContent, type FileRefEnv, FileRefError, type FileRefErrorCode, type FileRefStream, type FileRefVia, type GetAppFileResult, type HandlerFn, INBOX_SEND_CONTRACT_V1, type IdPrefixSpec, type IdSnapshot, 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, OAUTH_STATE_ENVELOPE_MAX_CHARS, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PartialUpdateDataOptions, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PublicBaseUrlEnv, type PurgeResult, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type ReadFileRefOptions, type RequireApprovalOptions, type ResolveFileRefOptions, type ResolveViewerOptions, type ResolvedFileRef, type RestoreResult, type RestoreSpec, SPRIGR_DATA_MAX_OBJECTS_PER_CALL, SPRIGR_FILE_HOSTS, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SmsPreviewInput, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataImportOpts, type SprigrDataIndexOpts, type SprigrDataPartialUpdateBody, type SprigrDataPartialUpdateOpts, type SprigrDataPartialUpdateResult, type SprigrDataPatch, type SprigrDataSearchOpts, type SprigrDataSearchResult, SprigrDataValidationError, type SprigrDataWriteTimeoutOpts, 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, WORKSPACE_FILE_HOW_TO, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorRetiredHook, actorTool, agentFileKeyFromUrl, appFileUrl, appKeyFromCallerKey, applyConfirmationPolicies, approvalHash, approvalPreview, approvalRecipients, archiveOfferRefusal, attendedFindings, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, buildPartialUpdateBody, bytesToBase64, callerKeyRefusal, canEmit, canLog, canPartialUpdate, checkConfirmationPolicy, commonDestructiveVerbs, connectComplete, connectDisconnect, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, defineInboxSendHandler, deleteAppFile, deleteSnapshotIds, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emailBodyHtml, emailPreview, emitMarketplaceEvent, encodeState, encodeStateWithinEnvelope, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fieldsPreview, fullWarning, getAppFile, getCompanyTimezone, hasHtmlMarkup, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, logToPlatform, makeDataClient, manifestIsFresh, oauthStateEnvelopeOverhead, offerUndo, openFileRef, overlaySprigr, ownerFromActorKey, parseActor, parseActorRetiredArgs, partialUpdateData, publicBaseUrl, purgeIndexByPrefix, putAppFile, putAppFileStream, randomHex, readFileRef, readableStoredKey, recreatedWarning, refuseWithoutForce, requireApproval, resolveFileRef, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, smsPreview, snapshotIndexIds, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrInboxFallback, withSprigrLogFallback };
package/dist/index.js CHANGED
@@ -603,6 +603,16 @@ function buildMarketplaceWebhookUrl(env, installId, topicPath, override) {
603
603
  const base = resolvePlatformWebhookBase(env, override);
604
604
  return `${base}/webhook/marketplace/${installId}/${topicPath}`;
605
605
  }
606
+ function publicBaseUrl(env) {
607
+ const raw = typeof env.SITE_PUBLIC_BASE_URL === "string" ? env.SITE_PUBLIC_BASE_URL.trim() : "";
608
+ if (!raw) return null;
609
+ try {
610
+ const url = new URL(raw);
611
+ return url.protocol === "https:" ? url.origin : null;
612
+ } catch {
613
+ return null;
614
+ }
615
+ }
606
616
  var OAUTH_BOUNCER_PROD = "https://oauth-bouncer.sprigr.com";
607
617
  var OAUTH_BOUNCER_STAGING = "https://staging-oauth-bouncer.sprigr.com";
608
618
  function isStagingHost(urlOrHostname) {
@@ -689,6 +699,9 @@ async function installTokenPost(bridge, path, body, opts) {
689
699
  err.status = resp.status;
690
700
  err.error = code;
691
701
  if (obj && typeof obj.detail === "string") err.detail = obj.detail;
702
+ const retryAfter = resp.headers.get("retry-after");
703
+ const retryAfterSeconds = retryAfter === null ? NaN : Number(retryAfter.trim());
704
+ if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) err.retryAfterSeconds = retryAfterSeconds;
692
705
  throw err;
693
706
  }
694
707
  return parsed ?? {};
@@ -1067,7 +1080,8 @@ async function partialUpdateData(env, objects, opts) {
1067
1080
  const result = await injected(body.objects, {
1068
1081
  ...body.index !== void 0 ? { index: body.index } : {},
1069
1082
  createIfNotExists: body.createIfNotExists,
1070
- ...body.withAcl ? { withAcl: true } : {}
1083
+ ...body.withAcl ? { withAcl: true } : {},
1084
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
1071
1085
  });
1072
1086
  return body.withAcl ? assertAclIndex(result) : result;
1073
1087
  }
@@ -2124,6 +2138,10 @@ async function connectDisconnect(env, req) {
2124
2138
 
2125
2139
  // src/data-purge.ts
2126
2140
  var BRIDGE_TIMEOUT_MS = 2e4;
2141
+ var IMPORT_RETRY_MAX_WAIT_MS = 3e3;
2142
+ var IMPORT_RETRY_WINDOW_MS = 12e3;
2143
+ var IMPORT_TOTAL_BUDGET_MS = 25e3;
2144
+ var IMPORT_RETRY_MIN_MS = 1500;
2127
2145
  var DATA_DELETE_MAX_IDS = 1e3;
2128
2146
  function injectedData(env) {
2129
2147
  const data = env.SPRIGR?.data;
@@ -2162,7 +2180,26 @@ function makeDataClient(env) {
2162
2180
  return {
2163
2181
  via: "http",
2164
2182
  async import(objects, opts) {
2165
- const r = await post("/internal/wfp/data/import", { objects, ...opts ?? {} }, "data.import");
2183
+ const body = { objects, ...opts ?? {} };
2184
+ const startedAt = Date.now();
2185
+ const callerMs = typeof opts?.timeoutMs === "number" && opts.timeoutMs > 0 ? opts.timeoutMs : void 0;
2186
+ const sendImport = (timeoutMs) => installTokenPost(bridge, "/internal/wfp/data/import", body, { label: "data.import", timeoutMs });
2187
+ let r;
2188
+ try {
2189
+ r = await sendImport(Math.min(BRIDGE_TIMEOUT_MS, callerMs ?? BRIDGE_TIMEOUT_MS));
2190
+ } catch (err) {
2191
+ const e = err;
2192
+ if (e?.status !== 503) throw err;
2193
+ const waitMs = Math.min(
2194
+ typeof e.retryAfterSeconds === "number" ? e.retryAfterSeconds * 1e3 : 1e3,
2195
+ IMPORT_RETRY_MAX_WAIT_MS
2196
+ );
2197
+ const spentMs = Date.now() - startedAt;
2198
+ const retryMs = Math.min(BRIDGE_TIMEOUT_MS, (callerMs ?? IMPORT_TOTAL_BUDGET_MS) - spentMs - waitMs);
2199
+ if (spentMs + waitMs > IMPORT_RETRY_WINDOW_MS || retryMs < IMPORT_RETRY_MIN_MS) throw err;
2200
+ if (waitMs > 0) await new Promise((resolve) => setTimeout(resolve, waitMs));
2201
+ r = await sendImport(retryMs);
2202
+ }
2166
2203
  return { ok: r.ok !== false, indexed: typeof r.indexed === "number" ? r.indexed : objects.length, index: String(r.index ?? opts?.index ?? "") };
2167
2204
  },
2168
2205
  async search(opts) {
@@ -2266,6 +2303,21 @@ function getCompanyTimezone(env) {
2266
2303
  return void 0;
2267
2304
  }
2268
2305
  }
2306
+
2307
+ // src/email-body.ts
2308
+ var HTML_ELEMENT = /<\/?(?:a|abbr|b|blockquote|body|br|center|code|del|div|em|font|h[1-6]|head|hr|html|i|img|ins|li|mark|ol|p|pre|s|section|small|span|strong|style|sub|sup|table|tbody|td|tfoot|th|thead|tr|u|ul)(?:\s[^<>]*)?\/?>/i;
2309
+ function hasHtmlMarkup(body) {
2310
+ return HTML_ELEMENT.test(body) || /<!doctype\s+html/i.test(body);
2311
+ }
2312
+ function escapeHtml(text) {
2313
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2314
+ }
2315
+ function emailBodyHtml(body) {
2316
+ if (body.length === 0 || hasHtmlMarkup(body)) return body;
2317
+ const normalized = body.replace(/\r\n?/g, "\n").trim();
2318
+ if (normalized.length === 0) return body;
2319
+ return normalized.split(/\n[ \t]*\n+/).map((para) => `<p>${escapeHtml(para.replace(/^\n+|\n+$/g, "")).replace(/\n/g, "<br>")}</p>`).join("");
2320
+ }
2269
2321
  export {
2270
2322
  ACTOR_RETIRED_HOOK_TOOL,
2271
2323
  ACTOR_RETIRE_REASONS,
@@ -2336,6 +2388,7 @@ export {
2336
2388
  deriveConfirmationPolicy,
2337
2389
  describeMissingBridge,
2338
2390
  dispatcherApproval,
2391
+ emailBodyHtml,
2339
2392
  emailPreview,
2340
2393
  emitMarketplaceEvent,
2341
2394
  encodeState,
@@ -2347,6 +2400,7 @@ export {
2347
2400
  fullWarning,
2348
2401
  getAppFile,
2349
2402
  getCompanyTimezone,
2403
+ hasHtmlMarkup,
2350
2404
  hmacSha256Hex,
2351
2405
  installTokenPost,
2352
2406
  isNotConnectedError,
@@ -2363,6 +2417,7 @@ export {
2363
2417
  parseActor,
2364
2418
  parseActorRetiredArgs,
2365
2419
  partialUpdateData,
2420
+ publicBaseUrl,
2366
2421
  purgeIndexByPrefix,
2367
2422
  putAppFile,
2368
2423
  putAppFileStream,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprigr/apps-app-sdk",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",