@sprigr/apps-app-sdk 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,6 +78,7 @@ export default {
78
78
  - `parseActor` / `actorKey` / `ownerFromActorKey` — per-actor token scoping
79
79
  - `putAppFile` / `putAppFileStream` / `appFileUrl` / `getAppFile` / `listAppFiles` / `deleteAppFile` — durable app-scoped file storage from outside the injected bridge
80
80
  - `fetchFileBytes` / `fetchFileAsBase64` / `bytesToBase64` / `base64ToBytes` — file byte helpers
81
+ - `readableStoredKey(appKey, downloadUrl)` / `agentFileKeyFromUrl` — turn the install-relative key your app stored a file under into the absolute key the platform's `read_file` tool opens (read off the signed URL `appFileUrl` minted). Return it as a tool's `file_key` so an agent can open the file. `appKeyFromCallerKey` / `callerKeyRefusal` go the other way, for a key an agent hands back to your tool (0.17.0)
81
82
 
82
83
  Full platform semantics: [`docs/platform-reference.md`](../../docs/platform-reference.md).
83
84
 
package/dist/index.d.ts CHANGED
@@ -1496,6 +1496,19 @@ interface AppApprovalEnvelope {
1496
1496
  * `ApprovalSpec.count`.
1497
1497
  */
1498
1498
  count?: number;
1499
+ /**
1500
+ * What the platform does when nobody can be asked (a schedule, workflow
1501
+ * step or delegation turn). Omitted, the write is withheld unless the
1502
+ * step's standing approval covers it. `'proceed'` runs the granted pass
1503
+ * instead, so the card appears only when a person is on the turn: the
1504
+ * contract the platform's built-in email send gate has always had. Use it
1505
+ * for writes automations legitimately make (sending mail, posting a chat
1506
+ * message); keep the default for deletes, money and bulk writes. Needs a
1507
+ * platform with sprigr-team decision 0160; an older one ignores it and
1508
+ * withholds. Set it through `ApprovalSpec.unattended` when you use the
1509
+ * wrappers.
1510
+ */
1511
+ unattended?: 'proceed';
1499
1512
  }
1500
1513
  /**
1501
1514
  * Platform-stamped marker telling an app that a human approved this call. The
@@ -1562,6 +1575,29 @@ declare function seq(values: unknown): string;
1562
1575
  */
1563
1576
  declare function approvalHash(...parts: Array<string | number | undefined | null>): string;
1564
1577
 
1578
+ /**
1579
+ * Helpers for the text on an approval card for a message send (an email, a
1580
+ * chat post), shared so each mail app does not grow its own copy.
1581
+ *
1582
+ * The card is display only. The grant hash and the arguments the platform
1583
+ * replays on approval (sprigr-team decision 0012) carry the full message, so
1584
+ * shortening the PREVIEW drops nothing that is sent.
1585
+ */
1586
+ /**
1587
+ * Lower-cased addresses from any recipient shape a send tool accepts: an array
1588
+ * of `{ email }` objects or strings, or one string separated by commas or
1589
+ * semicolons. Feed it to `set()` for an order-insensitive hash part.
1590
+ */
1591
+ declare function approvalRecipients(value: unknown): string[];
1592
+ /** Default preview length: enough to recognise the message, short enough not to bury the question. */
1593
+ declare const APPROVAL_PREVIEW_MAX = 600;
1594
+ /**
1595
+ * Plain-text preview of a message body for the card: tags stripped, `&nbsp;`
1596
+ * and runs of whitespace collapsed, and an ellipsis when it is cut so the
1597
+ * reader can tell the preview is not the whole message.
1598
+ */
1599
+ declare function approvalPreview(value: unknown, max?: number): string;
1600
+
1565
1601
  /**
1566
1602
  * Thrown by `undoEnvelope` for a field the platform would reject.
1567
1603
  *
@@ -1720,6 +1756,13 @@ interface ApprovalSpec<P = unknown, E = unknown> {
1720
1756
  * can ever cover it, which is the safe side but not the useful one.
1721
1757
  */
1722
1758
  count?: (args: ToolArgs) => number;
1759
+ /**
1760
+ * `'proceed'`: ask only when a person is on the turn, and let an unattended
1761
+ * run (schedule, workflow step, delegation) do the write. Omit to withhold
1762
+ * unattended writes unless a standing approval covers them. See
1763
+ * `AppApprovalEnvelope.unattended`.
1764
+ */
1765
+ unattended?: 'proceed';
1723
1766
  /**
1724
1767
  * Overrides the gate's `resolveConnection` for THIS spec only; the gate's
1725
1768
  * value stays the default for every spec that omits it.
@@ -2472,6 +2515,79 @@ declare function connectDisconnect(env: WfpBridgeEnv, req: {
2472
2515
  deleted: number;
2473
2516
  }>;
2474
2517
 
2518
+ /**
2519
+ * Stored-file keys an agent can open (sprigr/sprigr-apps#2394).
2520
+ *
2521
+ * The platform hands an app its storage keys INSTALL-RELATIVE
2522
+ * (`drive/reads/<id>.docx`): that is the app's own view of its storage, and the
2523
+ * platform never names the install prefix or the per-user owner segment in a
2524
+ * files response. The platform tool read_file needs the ABSOLUTE key:
2525
+ *
2526
+ * `_apps/<installId>/<appKey>` (shared storage)
2527
+ * `_apps/<installId>/~u/<ownerHash>/<appKey>` (a manifest
2528
+ * actor_scoped_storage_prefixes key,
2529
+ * sprigr/sprigr-team#9621)
2530
+ *
2531
+ * A signed URL from env.SPRIGR.files.url carries exactly that key as its path,
2532
+ * so a tool that stores a file and mints a link can hand the agent a key that
2533
+ * opens. The github app found the same gap first (sprigr/sprigr-apps#2276,
2534
+ * deriveAgentFileKey); this version also accepts the owner segment.
2535
+ */
2536
+ /** Root every marketplace-app file lives under. */
2537
+ declare const APP_FILES_ROOT = "_apps/";
2538
+ /** The platform's per-user owner segment (sprigr/sprigr-team#9621). An app never
2539
+ * names it in a key it hands env.SPRIGR.files; the platform refuses that. */
2540
+ declare const APP_OWNER_SEGMENT = "~u";
2541
+ /**
2542
+ * The absolute key behind a minted download URL, for the app key it was minted
2543
+ * for. Returns null, never a guess, unless the URL path is exactly
2544
+ * `_apps/<one segment>/<appKey>` or `_apps/<one segment>/~u/<one segment>/<appKey>`:
2545
+ * a key handed to an agent as openable that is not the stored object's key is
2546
+ * worse than no key.
2547
+ */
2548
+ declare function agentFileKeyFromUrl(downloadUrl: string, appKey: string): string | null;
2549
+ /**
2550
+ * The key a tool returns as `file_key` for a file it stored under `appKey`
2551
+ * and minted `downloadUrl` for: the absolute key when it can be read off the
2552
+ * URL, otherwise the app key unchanged (the behaviour before sprigr/sprigr-apps#2394).
2553
+ */
2554
+ declare function readableStoredKey(appKey: string, downloadUrl: string): string;
2555
+ /** A caller-supplied stored-file key, resolved to this install's app key. */
2556
+ type CallerKeyResolution = {
2557
+ ok: true;
2558
+ /** The install-relative key to hand env.SPRIGR.files. */
2559
+ appKey: string;
2560
+ /** True when the caller passed the absolute form or a signed link. */
2561
+ wasAbsolute: boolean;
2562
+ } | {
2563
+ ok: false;
2564
+ /** `other_install`: an absolute key for a different install. `malformed`:
2565
+ * an `_apps/` key or a link with no file path after the prefix. */
2566
+ reason: 'other_install' | 'malformed';
2567
+ /** The install the key names, for `other_install`. */
2568
+ installId?: string;
2569
+ };
2570
+ /**
2571
+ * Accept a stored-file key in any form a tool may have handed out and return
2572
+ * the app-relative key for env.SPRIGR.files:
2573
+ * - the app-relative key itself (`drive/reads/<id>.docx`): unchanged;
2574
+ * - the absolute key (`_apps/<installId>/[~u/<hash>/]drive/reads/<id>.docx`);
2575
+ * - the signed download link, whose path is that absolute key.
2576
+ *
2577
+ * The owner segment is dropped, not checked: the platform re-derives the
2578
+ * owner from the call's verified actor (sprigr/sprigr-team#9621), so another user's
2579
+ * key resolves to the caller's own copy, which usually does not exist. An
2580
+ * absolute key naming a different install is refused when `ownInstallId` is
2581
+ * known, because the platform would otherwise read THIS install's file at the
2582
+ * same relative path. When it is unknown the key is accepted: the platform
2583
+ * still confines every read to this install.
2584
+ */
2585
+ declare function appKeyFromCallerKey(key: string, ownInstallId?: string | null): CallerKeyResolution;
2586
+ /** The sentence a refusal from appKeyFromCallerKey ends with, naming the key. */
2587
+ declare function callerKeyRefusal(key: string, r: Extract<CallerKeyResolution, {
2588
+ ok: false;
2589
+ }>): string;
2590
+
2475
2591
  /**
2476
2592
  * @sprigr/apps-app-sdk
2477
2593
  *
@@ -3027,4 +3143,4 @@ interface ExecutionContextLike {
3027
3143
  }
3028
3144
  type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
3029
3145
 
3030
- export { ACTOR_RETIRED_HOOK_TOOL, ACTOR_RETIRE_REASONS, APPROVAL_GRANTED_KEY, 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 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 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, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorRetiredHook, 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, parseActorRetiredArgs, 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 };
3146
+ 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 ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CallerKeyResolution, 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 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, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorRetiredHook, actorTool, agentFileKeyFromUrl, appFileUrl, appKeyFromCallerKey, applyConfirmationPolicies, approvalHash, approvalPreview, approvalRecipients, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, buildPartialUpdateBody, bytesToBase64, callerKeyRefusal, 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, parseActorRetiredArgs, partialUpdateData, putAppFile, putAppFileStream, randomHex, readableStoredKey, 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
@@ -1037,6 +1037,32 @@ function approvalHash(...parts) {
1037
1037
  return parts.map((p) => p === void 0 || p === null ? "" : String(p)).join(UNIT_SEP);
1038
1038
  }
1039
1039
 
1040
+ // src/write-protection/approval-card.ts
1041
+ function approvalRecipients(value) {
1042
+ const out = [];
1043
+ const add = (s) => {
1044
+ if (typeof s !== "string") return;
1045
+ for (const part of s.split(/[,;]/)) {
1046
+ const t = part.trim().toLowerCase();
1047
+ if (t) out.push(t);
1048
+ }
1049
+ };
1050
+ if (Array.isArray(value)) {
1051
+ for (const item of value) {
1052
+ if (item && typeof item === "object") add(item.email);
1053
+ else add(item);
1054
+ }
1055
+ } else {
1056
+ add(value);
1057
+ }
1058
+ return out;
1059
+ }
1060
+ var APPROVAL_PREVIEW_MAX = 600;
1061
+ function approvalPreview(value, max = APPROVAL_PREVIEW_MAX) {
1062
+ const text = (typeof value === "string" ? value : "").replace(/<[^>]*>/g, " ").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
1063
+ return text.length > max ? `${text.slice(0, max)}\u2026` : text;
1064
+ }
1065
+
1040
1066
  // src/write-protection/undo-envelope.ts
1041
1067
  var InvalidUndoEnvelopeError = class extends Error {
1042
1068
  constructor(message) {
@@ -1187,7 +1213,8 @@ async function askPass(name, spec, params, env, opts, hashPrefix) {
1187
1213
  _approval: {
1188
1214
  ...spec.describe(target, params, connection),
1189
1215
  hash: approvalHash(...hashPrefix, rawId, connection, ...extra),
1190
- ...count !== void 0 ? { count } : {}
1216
+ ...count !== void 0 ? { count } : {},
1217
+ ...spec.unattended === "proceed" ? { unattended: "proceed" } : {}
1191
1218
  }
1192
1219
  };
1193
1220
  }
@@ -1684,10 +1711,89 @@ async function connectDisconnect(env, req) {
1684
1711
  if (!bridge) throw new Error("connectDisconnect: SPRIGR_PLATFORM_BASE / SPRIGR_INSTALL_TOKEN are not bound");
1685
1712
  return await installTokenPost(bridge, "/internal/wfp/connect/disconnect", req, { label: "connect disconnect" });
1686
1713
  }
1714
+
1715
+ // src/stored-file-keys.ts
1716
+ var APP_FILES_ROOT = "_apps/";
1717
+ var APP_OWNER_SEGMENT = "~u";
1718
+ function isSegment(s) {
1719
+ return typeof s === "string" && s.length > 0 && !s.includes("/");
1720
+ }
1721
+ function trimLeadingSlashes(key) {
1722
+ return key.replace(/^\/+/, "");
1723
+ }
1724
+ function parseAbsolute(key) {
1725
+ if (!key.startsWith(APP_FILES_ROOT)) return null;
1726
+ const parts = key.slice(APP_FILES_ROOT.length).split("/");
1727
+ const installId = parts[0];
1728
+ if (!isSegment(installId)) return null;
1729
+ let i = 1;
1730
+ let ownerHash = null;
1731
+ if (parts[1] === APP_OWNER_SEGMENT) {
1732
+ if (!isSegment(parts[2])) return null;
1733
+ ownerHash = parts[2];
1734
+ i = 3;
1735
+ }
1736
+ const rest = parts.slice(i).join("/");
1737
+ if (!rest || rest.split("/").some((seg) => seg === "")) return null;
1738
+ return { installId, ownerHash, rest };
1739
+ }
1740
+ function urlPathKey(url) {
1741
+ try {
1742
+ return trimLeadingSlashes(decodeURIComponent(new URL(url).pathname));
1743
+ } catch {
1744
+ return null;
1745
+ }
1746
+ }
1747
+ function agentFileKeyFromUrl(downloadUrl, appKey) {
1748
+ const rel = trimLeadingSlashes(appKey);
1749
+ if (!rel) return null;
1750
+ const path = urlPathKey(downloadUrl);
1751
+ if (!path) return null;
1752
+ const parsed = parseAbsolute(path);
1753
+ if (!parsed || parsed.rest !== rel) return null;
1754
+ return path;
1755
+ }
1756
+ function readableStoredKey(appKey, downloadUrl) {
1757
+ return agentFileKeyFromUrl(downloadUrl, appKey) ?? appKey;
1758
+ }
1759
+ function appKeyFromCallerKey(key, ownInstallId) {
1760
+ let candidate = key.trim();
1761
+ let wasAbsolute = false;
1762
+ if (/^https?:\/\//i.test(candidate)) {
1763
+ const path = urlPathKey(candidate);
1764
+ if (!path || !path.startsWith(APP_FILES_ROOT)) return { ok: false, reason: "malformed" };
1765
+ candidate = path;
1766
+ }
1767
+ candidate = trimLeadingSlashes(candidate);
1768
+ if (candidate.startsWith(APP_FILES_ROOT)) {
1769
+ const parsed = parseAbsolute(candidate);
1770
+ if (!parsed) return { ok: false, reason: "malformed" };
1771
+ if (ownInstallId && parsed.installId !== ownInstallId) {
1772
+ return { ok: false, reason: "other_install", installId: parsed.installId };
1773
+ }
1774
+ return { ok: true, appKey: parsed.rest, wasAbsolute: true };
1775
+ }
1776
+ const parts = candidate.split("/");
1777
+ if (parts[0] === APP_OWNER_SEGMENT) {
1778
+ const rest = parts.slice(2).join("/");
1779
+ if (!isSegment(parts[1]) || !rest) return { ok: false, reason: "malformed" };
1780
+ return { ok: true, appKey: rest, wasAbsolute: true };
1781
+ }
1782
+ return { ok: true, appKey: candidate, wasAbsolute };
1783
+ }
1784
+ function callerKeyRefusal(key, r) {
1785
+ if (r.reason === "other_install") {
1786
+ return `"${key}" is a stored file of a different app install (${r.installId}), not this one. Pass a file_key this app returned to you.`;
1787
+ }
1788
+ return `"${key}" is not a stored file key: it has no file path after the app storage prefix.`;
1789
+ }
1687
1790
  export {
1688
1791
  ACTOR_RETIRED_HOOK_TOOL,
1689
1792
  ACTOR_RETIRE_REASONS,
1690
1793
  APPROVAL_GRANTED_KEY,
1794
+ APPROVAL_PREVIEW_MAX,
1795
+ APP_FILES_ROOT,
1796
+ APP_OWNER_SEGMENT,
1691
1797
  DATA_PARTIAL_UPDATE_PATH,
1692
1798
  DEFAULT_EMIT_TIMEOUT_MS,
1693
1799
  DEFAULT_LOG_TIMEOUT_MS,
@@ -1712,15 +1818,20 @@ export {
1712
1818
  actorKey,
1713
1819
  actorRetiredHook,
1714
1820
  actorTool,
1821
+ agentFileKeyFromUrl,
1715
1822
  appFileUrl,
1823
+ appKeyFromCallerKey,
1716
1824
  applyConfirmationPolicies,
1717
1825
  approvalHash,
1826
+ approvalPreview,
1827
+ approvalRecipients,
1718
1828
  archiveOfferRefusal,
1719
1829
  base64ToBytes,
1720
1830
  buildConfirmationPolicy,
1721
1831
  buildMarketplaceWebhookUrl,
1722
1832
  buildPartialUpdateBody,
1723
1833
  bytesToBase64,
1834
+ callerKeyRefusal,
1724
1835
  canEmit,
1725
1836
  canLog,
1726
1837
  canPartialUpdate,
@@ -1760,6 +1871,7 @@ export {
1760
1871
  putAppFile,
1761
1872
  putAppFileStream,
1762
1873
  randomHex,
1874
+ readableStoredKey,
1763
1875
  recreatedWarning,
1764
1876
  refuseWithoutForce,
1765
1877
  requireApproval,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprigr/apps-app-sdk",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",