@sprigr/apps-app-sdk 0.9.0 → 0.10.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/README.md +3 -1
- package/dist/index.d.ts +231 -7
- package/dist/index.js +215 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ The runtime injects the `env.SPRIGR` host object **only on `/__sprigr/*` dispatc
|
|
|
14
14
|
- `createMarketplaceEmitter(integrationType, defaults?)` — pre-bind an app's integration type so call sites pass only what varies; `sourceIntegration` is built per call from `env.INSTALL_ID`.
|
|
15
15
|
- `canEmit(env)` — whether an emit could reach the platform by either transport. Gate work that only exists to feed an emit on this, not on `env.SPRIGR?.emit`.
|
|
16
16
|
- `withSprigrEmitFallback(env)` — repair `env.SPRIGR.emit` once and leave existing call sites untouched. Matches the host object's contract (resolves `{ ok, eventId, queued }`, throws on non-2xx).
|
|
17
|
-
- `
|
|
17
|
+
- `logToPlatform(env, entry | entry[], opts?)`: durable app logging from either context (`env.SPRIGR.log` on dispatch, `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/log` from an inline route). Rows land in the platform's `system_logs` Analytics Engine dataset (90-day retention, scoped to the install's company, category prefixed with the app slug), so use it instead of a per-webhook or per-tick audit row in your D1: D1 bills every row written, AE does not. Validation throws synchronously on a cap violation (summary 256, detail 4096, metadata JSON 3840, category 64 matching `[A-Za-z0-9._:-]`, 50 entries per call; nothing is ever truncated); after that the promise never rejects and resolves `{ ok: true, written }` or `{ ok: false, error, status?, detail? }`. Pass `{ waitUntil: ctx.waitUntil.bind(ctx) }` on an inline route so the send outlives the response. `withSprigrLogFallback(env, opts?)` installs `env.SPRIGR.log` once for many call sites; `canLog(env)` gates work whose only purpose is a log row; the `SprigrLogEntry` / `SprigrLogFn` types let your env interface declare the host member.
|
|
18
|
+
- `resolveInstallBridge(env)` / `installTokenPost(bridge, path, body, opts?)`: build your own `/internal/wfp/*` fallback (collections, files, inbox) with the auth and error extraction handled. A non-2xx throws an `Error` whose `.status`, `.error` (the platform's code) and `.detail` are set alongside the message.
|
|
18
19
|
- `overlaySprigr(env, sprigr)` — overlay a patched `SPRIGR` via `Object.create`. Never rebuild a dispatch-path env by spread: the real bindings live on the prototype and `SPRIGR` is non-enumerable, so `{ ...env }` yields an env whose `DB` is `undefined`.
|
|
19
20
|
|
|
20
21
|
## Per-actor tool wrappers
|
|
@@ -69,6 +70,7 @@ Three platform tiers, and they are not substitutes for each other. Full guide: [
|
|
|
69
70
|
|
|
70
71
|
- `requireApproval(handlers, specs, opts)` wraps the named tools so each returns an `_approval` card instead of writing, then on the granted pass captures a before-image through the **pinned** connection and offers `_undo`. It resolves the connection inside itself, before the label lookup, the hash and the capture: a gate that sits outside the app's own pin names the wrong store, hashes the wrong store and captures the wrong store's copy. Register the result after the originals: `Object.assign(registry, requireApproval(registry, SPECS, opts))`.
|
|
71
72
|
- `dispatcherApproval(specs, opts)`: the same gate for a one-tool-many-actions dispatcher. Build it beside the action registry and call `gate.run(action, args, env, () => def.execute(state, parsed))` from the dispatcher; params are read from `opts.inputField` (default `input`), the action name is hashed in, and `pinEnv` may return a per-actor token state rather than the env.
|
|
73
|
+
- Every resolver on the gate is a **default, overridable per spec**: `ApprovalSpec.resolveConnection`, `.describeTarget` and `.stampConnection` win over the gate's for that spec alone. Reach for it when one app has two families of write that reach a connection differently (creates that POST under an account, deletes that address a global object id on a path carrying no account). One gate-level rule has to pick one of them, and for the other it prints a false connection on the card a person taps and mixes it into the grant hash. A spec that hits no connection returns `''`.
|
|
72
74
|
- `approvalHash(rawId, connection, ...parts)`, `set(values)`, `seq(values)`: a stable operation identity so a tap survives the model reordering a list or dropping an optional argument on the retry.
|
|
73
75
|
- `undoEnvelope({...})` throws on a blank field. The platform drops the whole envelope on a blank and mints nothing, silently.
|
|
74
76
|
- `refuseWithoutForce(name, handler, opts)` / `archiveOfferRefusal(...)`: a delete refuses once and offers the vendor's native archive unless `force: true`.
|
package/dist/index.d.ts
CHANGED
|
@@ -465,6 +465,190 @@ declare function createMarketplaceEmitter<E extends WfpBridgeEnv = WfpBridgeEnv>
|
|
|
465
465
|
*/
|
|
466
466
|
declare function withSprigrEmitFallback<E extends WfpBridgeEnv & object>(env: E): E;
|
|
467
467
|
|
|
468
|
+
/**
|
|
469
|
+
* `env.SPRIGR.log`: durable app logging without a D1 audit table.
|
|
470
|
+
*
|
|
471
|
+
* The platform (sprigr-team#7154) exposes `env.SPRIGR.log(entry | entry[])`
|
|
472
|
+
* on every `/__sprigr/*` dispatch path. Each entry becomes one row in the
|
|
473
|
+
* platform's `system_logs` Analytics Engine dataset: 90-day retention,
|
|
474
|
+
* scoped to the install's company, `source = 'install'`, category prefixed
|
|
475
|
+
* with the app slug, metadata stamped with `app_slug` + `install_id`.
|
|
476
|
+
*
|
|
477
|
+
* Use it for every "we saw X" line a webhook, schedule tick, poll or tool
|
|
478
|
+
* call used to write into the app's own D1 as an audit row. D1 bills each
|
|
479
|
+
* row written (plus one per index), so a row per delivery is the largest
|
|
480
|
+
* D1 line item an app can produce (sprigr-apps#1519: one Shopify install
|
|
481
|
+
* wrote ~1.7M audit + dedup rows a day). AE has no per-row write bill.
|
|
482
|
+
*
|
|
483
|
+
* This module carries three things:
|
|
484
|
+
*
|
|
485
|
+
* 1. The TYPES of the host member (`SprigrLogEntry`, `SprigrLogFn`), so an
|
|
486
|
+
* app's env interface can declare `log` on its `SPRIGR` type.
|
|
487
|
+
* 2. `validateLogEntries`, a byte-for-byte mirror of the caps the host
|
|
488
|
+
* member and the platform route enforce. Caps are REJECTIONS, never
|
|
489
|
+
* truncation: an oversize entry throws before anything is sent.
|
|
490
|
+
* 3. The inline-route fallback (`logToPlatform`, `withSprigrLogFallback`).
|
|
491
|
+
* Inline Next.js route handlers never receive `env.SPRIGR`, so an app
|
|
492
|
+
* whose provider webhook lands on one has no `log` there. Same escape
|
|
493
|
+
* hatch as `emitMarketplaceEvent`: `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/log`
|
|
494
|
+
* with the `SPRIGR_INSTALL_TOKEN` bearer and `{ entries: [...] }`.
|
|
495
|
+
*
|
|
496
|
+
* Wire contract (workers/provisioning/src/wfp-log.ts in sprigr-team):
|
|
497
|
+
* 200 { ok: true, written }
|
|
498
|
+
* 400 { error, detail, index, field, max?, length? } with error one of
|
|
499
|
+
* summary_too_long, detail_too_long, metadata_too_large,
|
|
500
|
+
* too_many_entries, invalid_category, invalid_level, ...
|
|
501
|
+
* 413 body_too_large (over 512 KiB)
|
|
502
|
+
* 401 / 404 bad token / inactive install
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
/** Severity of one log row. Maps onto `system_logs.level`. */
|
|
506
|
+
type SprigrLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
507
|
+
declare const SPRIGR_LOG_LEVELS: readonly SprigrLogLevel[];
|
|
508
|
+
/**
|
|
509
|
+
* One row for `env.SPRIGR.log`. Every string cap is enforced client-side
|
|
510
|
+
* (throw) AND platform-side (400); nothing is ever truncated.
|
|
511
|
+
*/
|
|
512
|
+
interface SprigrLogEntry {
|
|
513
|
+
/** Required. */
|
|
514
|
+
level: SprigrLogLevel;
|
|
515
|
+
/**
|
|
516
|
+
* Required. 1-64 chars of `[A-Za-z0-9._:-]`, first char alphanumeric. The
|
|
517
|
+
* platform stores it as `<app_slug>.<category>` and it is the exact-match
|
|
518
|
+
* filter key when reading rows back, so make it a stable outcome name
|
|
519
|
+
* (`webhook.ok`, `webhook.duplicate`, `sync.tick`) and put the varying
|
|
520
|
+
* parts (topic, ids) in `metadata`.
|
|
521
|
+
*/
|
|
522
|
+
category: string;
|
|
523
|
+
/** Required. 1-256 chars. One line a human can scan in a log viewer. */
|
|
524
|
+
summary: string;
|
|
525
|
+
/** Optional, at most 4096 chars. Reserve it for error text; bulk goes in `metadata`. */
|
|
526
|
+
detail?: string;
|
|
527
|
+
/** Optional plain object whose `JSON.stringify` is at most 3840 chars.
|
|
528
|
+
* The platform merges `{ app_slug, install_id }` over it (platform keys win). */
|
|
529
|
+
metadata?: Record<string, unknown>;
|
|
530
|
+
/** Optional, at most 128 chars. Defaults on the dispatch path to the
|
|
531
|
+
* dispatching actor's agent id. */
|
|
532
|
+
agent_id?: string;
|
|
533
|
+
/** Optional, at most 128 chars. Defaults on the platform to the ambient request trace. */
|
|
534
|
+
trace_id?: string;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* What `env.SPRIGR.log` resolves to. The promise NEVER rejects: transport
|
|
538
|
+
* and platform failures come back as `{ ok: false }` (and a console.warn).
|
|
539
|
+
* Only a cap or shape violation throws, synchronously, before sending.
|
|
540
|
+
*/
|
|
541
|
+
type SprigrLogResult = {
|
|
542
|
+
ok: true;
|
|
543
|
+
written: number;
|
|
544
|
+
} | {
|
|
545
|
+
ok: false;
|
|
546
|
+
error: string;
|
|
547
|
+
status?: number;
|
|
548
|
+
detail?: string;
|
|
549
|
+
};
|
|
550
|
+
/**
|
|
551
|
+
* The host member's signature. Declare it on your app's `SPRIGR` env type:
|
|
552
|
+
*
|
|
553
|
+
* interface MyEnv { SPRIGR?: { emit: ...; log?: SprigrLogFn } }
|
|
554
|
+
*
|
|
555
|
+
* Mark it optional: wrapper builds older than sprigr-team#7154 do not carry
|
|
556
|
+
* it, and `logToPlatform` degrades cleanly when it is absent.
|
|
557
|
+
*/
|
|
558
|
+
type SprigrLogFn = (input: SprigrLogEntry | SprigrLogEntry[]) => Promise<SprigrLogResult>;
|
|
559
|
+
/**
|
|
560
|
+
* The caps, verbatim from the platform route (`WFP_LOG_*` in
|
|
561
|
+
* workers/provisioning/src/wfp-log.ts) and the wrapper's `log` member. The
|
|
562
|
+
* platform's 400 body echoes its live cap, so a mismatch here is
|
|
563
|
+
* self-describing rather than silent.
|
|
564
|
+
*/
|
|
565
|
+
declare const SPRIGR_LOG_LIMITS: Readonly<{
|
|
566
|
+
maxEntriesPerCall: 50;
|
|
567
|
+
maxCategoryChars: 64;
|
|
568
|
+
categoryPattern: RegExp;
|
|
569
|
+
maxSummaryChars: 256;
|
|
570
|
+
maxDetailChars: 4096;
|
|
571
|
+
/** 4096-char metadata blob minus the platform's 256-char reserve for
|
|
572
|
+
* `app_slug` + `install_id`. */
|
|
573
|
+
maxMetadataChars: 3840;
|
|
574
|
+
maxIdChars: 128;
|
|
575
|
+
}>;
|
|
576
|
+
/** Same 5s ceiling as the emit fallback: a webhook ack matters more than a log row. */
|
|
577
|
+
declare const DEFAULT_LOG_TIMEOUT_MS = 5000;
|
|
578
|
+
/**
|
|
579
|
+
* Thrown by `validateLogEntries` (and therefore by `logToPlatform` and the
|
|
580
|
+
* fallback `log`) before anything is sent. Carries the same fields as the
|
|
581
|
+
* platform's 400 body so a caller can log or branch on them.
|
|
582
|
+
*/
|
|
583
|
+
declare class SprigrLogValidationError extends Error {
|
|
584
|
+
readonly index: number;
|
|
585
|
+
readonly field: string;
|
|
586
|
+
readonly max?: number;
|
|
587
|
+
readonly length?: number;
|
|
588
|
+
constructor(message: string, fields: {
|
|
589
|
+
index: number;
|
|
590
|
+
field: string;
|
|
591
|
+
max?: number;
|
|
592
|
+
length?: number;
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Validate one entry against the platform caps. Returns a fresh, normalised
|
|
597
|
+
* copy (unknown keys dropped, `undefined` optionals omitted) or throws
|
|
598
|
+
* `SprigrLogValidationError`. Pure; `index` only labels the error.
|
|
599
|
+
*/
|
|
600
|
+
declare function validateLogEntry(raw: unknown, index?: number): SprigrLogEntry;
|
|
601
|
+
/**
|
|
602
|
+
* Normalise `entry | entry[]` to a validated array, or throw. One bad entry
|
|
603
|
+
* rejects the whole batch, matching the platform's all-or-nothing write.
|
|
604
|
+
*/
|
|
605
|
+
declare function validateLogEntries(input: unknown): SprigrLogEntry[];
|
|
606
|
+
interface LogToPlatformOptions {
|
|
607
|
+
/** Override the 5s ceiling on the HTTP fallback. */
|
|
608
|
+
timeoutMs?: number;
|
|
609
|
+
/**
|
|
610
|
+
* `ctx.waitUntil` from the route handler. On the HTTP fallback the row
|
|
611
|
+
* is only guaranteed to land if the fetch outlives the response, which
|
|
612
|
+
* on an inline route means registering it here. The injected host member
|
|
613
|
+
* does this on its own; the fallback cannot see `ctx`, so hand it over.
|
|
614
|
+
*/
|
|
615
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Whether a log row could reach the platform at all, by either transport.
|
|
619
|
+
* Gate work whose only purpose is to build a log entry on this, not on
|
|
620
|
+
* `env.SPRIGR?.log`, which is absent on every inline route.
|
|
621
|
+
*/
|
|
622
|
+
declare function canLog(env: WfpBridgeEnv): boolean;
|
|
623
|
+
/**
|
|
624
|
+
* Write log rows from any execution context.
|
|
625
|
+
*
|
|
626
|
+
* Prefers the injected `env.SPRIGR.log`; falls back to the install-token
|
|
627
|
+
* bridge when it is absent (an inline Next route). Validation runs first
|
|
628
|
+
* and THROWS on a cap or shape violation, exactly like the host member, so
|
|
629
|
+
* a bad entry is caught at the call site. Everything after validation is
|
|
630
|
+
* reported in the result, never raised: a webhook ack is never at risk.
|
|
631
|
+
*
|
|
632
|
+
* await logToPlatform(env, {
|
|
633
|
+
* level: 'info', category: 'webhook.ok', summary: 'orders/create 6699',
|
|
634
|
+
* metadata: { topic: 'orders/create', resource_id: '6699' },
|
|
635
|
+
* }, { waitUntil: ctx.waitUntil.bind(ctx) });
|
|
636
|
+
*/
|
|
637
|
+
declare function logToPlatform(env: WfpBridgeEnv, input: SprigrLogEntry | SprigrLogEntry[], opts?: LogToPlatformOptions): Promise<SprigrLogResult>;
|
|
638
|
+
/**
|
|
639
|
+
* Return an env whose `SPRIGR.log` works even on an inline route, leaving
|
|
640
|
+
* existing `env.SPRIGR.log(...)` call sites untouched. Same shape as
|
|
641
|
+
* `withSprigrEmitFallback`, and composable with it:
|
|
642
|
+
*
|
|
643
|
+
* const env = withSprigrLogFallback(withSprigrEmitFallback(rawEnv), { waitUntil });
|
|
644
|
+
*
|
|
645
|
+
* The installed `log` matches the host member's contract: it throws
|
|
646
|
+
* synchronously on a cap or shape violation and its promise never rejects.
|
|
647
|
+
* When the bindings are absent the env is returned as-is (`SPRIGR.log`
|
|
648
|
+
* stays undefined) so callers fail the same way they already do.
|
|
649
|
+
*/
|
|
650
|
+
declare function withSprigrLogFallback<E extends WfpBridgeEnv & object>(env: E, opts?: LogToPlatformOptions): E;
|
|
651
|
+
|
|
468
652
|
/**
|
|
469
653
|
* Caller identity stamped by the platform on agent-initiated WFP dispatches.
|
|
470
654
|
*
|
|
@@ -1250,12 +1434,25 @@ declare function refuseWithoutForce<E>(deleteAction: string, inner: ToolHandler<
|
|
|
1250
1434
|
* `_undo` ON A NULL CAPTURE. A failed capture does not fail the write: the
|
|
1251
1435
|
* user asked for it and a human approved it. It degrades to no token.
|
|
1252
1436
|
*
|
|
1437
|
+
* Every resolver here is gate-level by default and overridable per spec:
|
|
1438
|
+
* `ApprovalSpec.resolveConnection` / `.describeTarget` / `.stampConnection`
|
|
1439
|
+
* win over the gate's for that spec alone. One rule cannot describe two
|
|
1440
|
+
* families of write truthfully, and both consumers of the resolved value are
|
|
1441
|
+
* safety surfaces (the card a person reads, and the grant hash).
|
|
1442
|
+
*
|
|
1253
1443
|
* Generalised from the Shopify app's `require-approval.ts`; the per-tool
|
|
1254
1444
|
* questions stay in the app, because only the app knows what cannot be walked
|
|
1255
1445
|
* back.
|
|
1256
1446
|
*/
|
|
1257
|
-
/**
|
|
1258
|
-
|
|
1447
|
+
/**
|
|
1448
|
+
* Per-tool (or per-action) spec: how the card reads, and (optionally) how to
|
|
1449
|
+
* journal a before-image.
|
|
1450
|
+
*
|
|
1451
|
+
* `P` is the pinned connection handed to `capture` / `describeTarget`; `E` is
|
|
1452
|
+
* the env a spec-level `resolveConnection` receives. `E` defaults to `unknown`
|
|
1453
|
+
* so `ApprovalSpec<MyState>` keeps working unchanged.
|
|
1454
|
+
*/
|
|
1455
|
+
interface ApprovalSpec<P = unknown, E = unknown> {
|
|
1259
1456
|
/**
|
|
1260
1457
|
* Arg names that may carry the target id, most specific first. ONE list, used
|
|
1261
1458
|
* for both the card label and the grant hash, so the two cannot drift apart
|
|
@@ -1284,6 +1481,27 @@ interface ApprovalSpec<P = unknown> {
|
|
|
1284
1481
|
* can ever cover it, which is the safe side but not the useful one.
|
|
1285
1482
|
*/
|
|
1286
1483
|
count?: (args: ToolArgs) => number;
|
|
1484
|
+
/**
|
|
1485
|
+
* Overrides the gate's `resolveConnection` for THIS spec only; the gate's
|
|
1486
|
+
* value stays the default for every spec that omits it.
|
|
1487
|
+
*
|
|
1488
|
+
* One gate serves every spec, so a gate-level resolver has to pick one rule.
|
|
1489
|
+
* That is wrong the moment an app has two families of write that reach a
|
|
1490
|
+
* connection differently: in meta-ads (sprigr-apps#1462) six creates POST
|
|
1491
|
+
* under an ad account and three address a Graph object by id on a path that
|
|
1492
|
+
* carries no account at all, so the account resolved for the creates was
|
|
1493
|
+
* printed on the delete's card and mixed into its hash while being false.
|
|
1494
|
+
* A resolver returning '' here is the honest answer for such a spec.
|
|
1495
|
+
*/
|
|
1496
|
+
resolveConnection?: (env: E, args: ToolArgs) => Promise<string>;
|
|
1497
|
+
/** Overrides the gate's `describeTarget` for this spec only. */
|
|
1498
|
+
describeTarget?: (pinned: P, id: string, name: string) => Promise<string>;
|
|
1499
|
+
/**
|
|
1500
|
+
* Overrides the gate's `stampConnection` for this spec only. The stamp
|
|
1501
|
+
* repeats the card's claim in the agent's "done" report, so a spec that
|
|
1502
|
+
* resolves its own connection usually wants to stamp it its own way too.
|
|
1503
|
+
*/
|
|
1504
|
+
stampConnection?: (result: unknown, connection: string) => unknown;
|
|
1287
1505
|
/** Present only for tools whose write can be (partly) reversed. */
|
|
1288
1506
|
undo?: UndoCaptureSpec<P>;
|
|
1289
1507
|
}
|
|
@@ -1322,6 +1540,10 @@ interface ApprovalGateOptions<E, P = E> {
|
|
|
1322
1540
|
* arguments and the install, canonicalised (a label like "EU store" becomes
|
|
1323
1541
|
* the real domain). Return '' for a single-connection app. Never read it off
|
|
1324
1542
|
* the arguments alone: on the retry the model usually omits it.
|
|
1543
|
+
*
|
|
1544
|
+
* This is the DEFAULT for every spec. A spec whose write reaches a
|
|
1545
|
+
* connection by another rule overrides it with
|
|
1546
|
+
* `ApprovalSpec.resolveConnection`.
|
|
1325
1547
|
*/
|
|
1326
1548
|
resolveConnection: (env: E, args: ToolArgs) => Promise<string>;
|
|
1327
1549
|
/**
|
|
@@ -1334,7 +1556,8 @@ interface ApprovalGateOptions<E, P = E> {
|
|
|
1334
1556
|
* back to the id on any miss; never guess, a confidently wrong card is worse
|
|
1335
1557
|
* than a terse one. Omit to always show the raw id. `name` is the tool, or
|
|
1336
1558
|
* for a dispatcher the action, so a lookup can route by resource type when
|
|
1337
|
-
* the id alone does not say what it is (an Asana gid).
|
|
1559
|
+
* the id alone does not say what it is (an Asana gid). Overridable per
|
|
1560
|
+
* spec.
|
|
1338
1561
|
*/
|
|
1339
1562
|
describeTarget?: (pinned: P, id: string, name: string) => Promise<string>;
|
|
1340
1563
|
/**
|
|
@@ -1345,7 +1568,8 @@ interface ApprovalGateOptions<E, P = E> {
|
|
|
1345
1568
|
journal?: (env: E) => CaptureJournal;
|
|
1346
1569
|
/**
|
|
1347
1570
|
* Stamp the resolved connection onto a successful result (e.g. `{ store }`)
|
|
1348
|
-
* so an agent reporting "deleted it" can name where. Optional
|
|
1571
|
+
* so an agent reporting "deleted it" can name where. Optional, and
|
|
1572
|
+
* overridable per spec.
|
|
1349
1573
|
*/
|
|
1350
1574
|
stampConnection?: (result: unknown, connection: string) => unknown;
|
|
1351
1575
|
}
|
|
@@ -1382,7 +1606,7 @@ interface DispatcherApprovalGate<E> {
|
|
|
1382
1606
|
* rename that quietly ungated a permanent delete is exactly the failure this
|
|
1383
1607
|
* guard exists to prevent, and it would look identical to working.
|
|
1384
1608
|
*/
|
|
1385
|
-
declare function requireApproval<E>(handlers: Record<string, ToolHandler<E>>, specs: Record<string, ApprovalSpec<E>>, opts: RequireApprovalOptions<E>): Record<string, ToolHandler<E>>;
|
|
1609
|
+
declare function requireApproval<E>(handlers: Record<string, ToolHandler<E>>, specs: Record<string, ApprovalSpec<E, E>>, opts: RequireApprovalOptions<E>): Record<string, ToolHandler<E>>;
|
|
1386
1610
|
/**
|
|
1387
1611
|
* The same gate for a dispatcher tool (one tool, many actions selected by an
|
|
1388
1612
|
* input field). Build it once beside the action registry and call `run` from
|
|
@@ -1396,7 +1620,7 @@ declare function requireApproval<E>(handlers: Record<string, ToolHandler<E>>, sp
|
|
|
1396
1620
|
* params are read from `opts.inputField` (default `input`), or from the
|
|
1397
1621
|
* top-level args when that field is absent (the flat-verb envelope).
|
|
1398
1622
|
*/
|
|
1399
|
-
declare function dispatcherApproval<E, P = E>(specs: Record<string, ApprovalSpec<P>>, opts: DispatcherApprovalOptions<E, P>): DispatcherApprovalGate<E>;
|
|
1623
|
+
declare function dispatcherApproval<E, P = E>(specs: Record<string, ApprovalSpec<P, E>>, opts: DispatcherApprovalOptions<E, P>): DispatcherApprovalGate<E>;
|
|
1400
1624
|
|
|
1401
1625
|
/**
|
|
1402
1626
|
* The reversal entry point every adopting app declares as `undo.reverse_tool`.
|
|
@@ -2119,4 +2343,4 @@ interface ExecutionContextLike {
|
|
|
2119
2343
|
}
|
|
2120
2344
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
2121
2345
|
|
|
2122
|
-
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_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 ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, 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, bytesToBase64, canEmit, checkConfirmationPolicy, commonDestructiveVerbs, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, verifyViewerToken, withSprigrEmitFallback };
|
|
2346
|
+
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, 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 PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, 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, bytesToBase64, canEmit, canLog, 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, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrLogFallback };
|
package/dist/index.js
CHANGED
|
@@ -245,8 +245,13 @@ async function installTokenPost(bridge, path, body, opts) {
|
|
|
245
245
|
parsed = null;
|
|
246
246
|
}
|
|
247
247
|
if (!resp.ok) {
|
|
248
|
-
const
|
|
249
|
-
|
|
248
|
+
const obj = parsed && typeof parsed === "object" ? parsed : null;
|
|
249
|
+
const code = obj && "error" in obj ? String(obj.error) : text.slice(0, 200);
|
|
250
|
+
const err = new Error(`${label} failed: ${resp.status} ${code}`);
|
|
251
|
+
err.status = resp.status;
|
|
252
|
+
err.error = code;
|
|
253
|
+
if (obj && typeof obj.detail === "string") err.detail = obj.detail;
|
|
254
|
+
throw err;
|
|
250
255
|
}
|
|
251
256
|
return parsed ?? {};
|
|
252
257
|
} finally {
|
|
@@ -312,6 +317,198 @@ function withSprigrEmitFallback(env) {
|
|
|
312
317
|
return overlaySprigr(env, { ...existing, emit });
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
// src/platform-log.ts
|
|
321
|
+
var SPRIGR_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
322
|
+
var SPRIGR_LOG_LIMITS = Object.freeze({
|
|
323
|
+
maxEntriesPerCall: 50,
|
|
324
|
+
maxCategoryChars: 64,
|
|
325
|
+
categoryPattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/,
|
|
326
|
+
maxSummaryChars: 256,
|
|
327
|
+
maxDetailChars: 4096,
|
|
328
|
+
/** 4096-char metadata blob minus the platform's 256-char reserve for
|
|
329
|
+
* `app_slug` + `install_id`. */
|
|
330
|
+
maxMetadataChars: 3840,
|
|
331
|
+
maxIdChars: 128
|
|
332
|
+
});
|
|
333
|
+
var DEFAULT_LOG_TIMEOUT_MS = 5e3;
|
|
334
|
+
var SprigrLogValidationError = class extends Error {
|
|
335
|
+
index;
|
|
336
|
+
field;
|
|
337
|
+
max;
|
|
338
|
+
length;
|
|
339
|
+
constructor(message, fields) {
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "SprigrLogValidationError";
|
|
342
|
+
this.index = fields.index;
|
|
343
|
+
this.field = fields.field;
|
|
344
|
+
if (fields.max !== void 0) this.max = fields.max;
|
|
345
|
+
if (fields.length !== void 0) this.length = fields.length;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
function isPlainObject(v) {
|
|
349
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
350
|
+
}
|
|
351
|
+
function tooLong(index, field, length, max) {
|
|
352
|
+
return new SprigrLogValidationError(
|
|
353
|
+
`env.SPRIGR.log: entries[${index}].${field} is ${length} chars; max ${max}. Nothing was sent; the platform never truncates a log field. Move the long part into detail or metadata, or split it across rows.`,
|
|
354
|
+
{ index, field, max, length }
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function validateLogEntry(raw, index = 0) {
|
|
358
|
+
const at = `env.SPRIGR.log: entries[${index}]`;
|
|
359
|
+
if (!isPlainObject(raw)) {
|
|
360
|
+
throw new SprigrLogValidationError(`${at} must be an object`, { index, field: "entry" });
|
|
361
|
+
}
|
|
362
|
+
const L = SPRIGR_LOG_LIMITS;
|
|
363
|
+
const level = raw.level;
|
|
364
|
+
if (typeof level !== "string" || !SPRIGR_LOG_LEVELS.includes(level)) {
|
|
365
|
+
throw new SprigrLogValidationError(`${at}.level must be one of ${SPRIGR_LOG_LEVELS.join(" | ")}`, {
|
|
366
|
+
index,
|
|
367
|
+
field: "level"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const category = raw.category;
|
|
371
|
+
if (typeof category !== "string" || category.length === 0) {
|
|
372
|
+
throw new SprigrLogValidationError(`${at}.category required (string)`, { index, field: "category" });
|
|
373
|
+
}
|
|
374
|
+
if (category.length > L.maxCategoryChars) throw tooLong(index, "category", category.length, L.maxCategoryChars);
|
|
375
|
+
if (!L.categoryPattern.test(category)) {
|
|
376
|
+
throw new SprigrLogValidationError(
|
|
377
|
+
`${at}.category must match ${L.categoryPattern} (letters, digits, . _ : -; no spaces)`,
|
|
378
|
+
{ index, field: "category" }
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const summary = raw.summary;
|
|
382
|
+
if (typeof summary !== "string" || summary.length === 0) {
|
|
383
|
+
throw new SprigrLogValidationError(`${at}.summary required (string)`, { index, field: "summary" });
|
|
384
|
+
}
|
|
385
|
+
if (summary.length > L.maxSummaryChars) throw tooLong(index, "summary", summary.length, L.maxSummaryChars);
|
|
386
|
+
const out = { level, category, summary };
|
|
387
|
+
if (raw.detail !== void 0) {
|
|
388
|
+
if (typeof raw.detail !== "string") {
|
|
389
|
+
throw new SprigrLogValidationError(`${at}.detail must be a string`, { index, field: "detail" });
|
|
390
|
+
}
|
|
391
|
+
if (raw.detail.length > L.maxDetailChars) throw tooLong(index, "detail", raw.detail.length, L.maxDetailChars);
|
|
392
|
+
out.detail = raw.detail;
|
|
393
|
+
}
|
|
394
|
+
if (raw.metadata !== void 0) {
|
|
395
|
+
if (!isPlainObject(raw.metadata)) {
|
|
396
|
+
throw new SprigrLogValidationError(`${at}.metadata must be a plain object`, { index, field: "metadata" });
|
|
397
|
+
}
|
|
398
|
+
const serialised = JSON.stringify(raw.metadata).length;
|
|
399
|
+
if (serialised > L.maxMetadataChars) {
|
|
400
|
+
throw new SprigrLogValidationError(
|
|
401
|
+
`${at}.metadata JSON is ${serialised} chars; max ${L.maxMetadataChars}. Nothing was sent; drop or split fields.`,
|
|
402
|
+
{ index, field: "metadata", max: L.maxMetadataChars, length: serialised }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
out.metadata = raw.metadata;
|
|
406
|
+
}
|
|
407
|
+
for (const field of ["agent_id", "trace_id"]) {
|
|
408
|
+
const v = raw[field];
|
|
409
|
+
if (v === void 0) continue;
|
|
410
|
+
if (typeof v !== "string" || v.length === 0 || v.length > L.maxIdChars) {
|
|
411
|
+
throw new SprigrLogValidationError(
|
|
412
|
+
`${at}.${field} must be a non-empty string of at most ${L.maxIdChars} chars`,
|
|
413
|
+
{ index, field, max: L.maxIdChars, length: typeof v === "string" ? v.length : void 0 }
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
out[field] = v;
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
function validateLogEntries(input) {
|
|
421
|
+
const list = Array.isArray(input) ? input : [input];
|
|
422
|
+
if (list.length === 0) {
|
|
423
|
+
throw new SprigrLogValidationError("env.SPRIGR.log: at least one entry required", {
|
|
424
|
+
index: 0,
|
|
425
|
+
field: "entries"
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (list.length > SPRIGR_LOG_LIMITS.maxEntriesPerCall) {
|
|
429
|
+
throw new SprigrLogValidationError(
|
|
430
|
+
`env.SPRIGR.log: at most ${SPRIGR_LOG_LIMITS.maxEntriesPerCall} entries per call (got ${list.length}); nothing was sent`,
|
|
431
|
+
{
|
|
432
|
+
index: 0,
|
|
433
|
+
field: "entries",
|
|
434
|
+
max: SPRIGR_LOG_LIMITS.maxEntriesPerCall,
|
|
435
|
+
length: list.length
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
return list.map((e, i) => validateLogEntry(e, i));
|
|
440
|
+
}
|
|
441
|
+
function bindingLog(env) {
|
|
442
|
+
const sprigr = env.SPRIGR;
|
|
443
|
+
return typeof sprigr?.log === "function" ? sprigr.log.bind(sprigr) : null;
|
|
444
|
+
}
|
|
445
|
+
function canLog(env) {
|
|
446
|
+
return bindingLog(env) !== null || resolveInstallBridge(env) !== null;
|
|
447
|
+
}
|
|
448
|
+
async function postLog(env, entries, timeoutMs) {
|
|
449
|
+
const bridge = resolveInstallBridge(env);
|
|
450
|
+
if (!bridge) {
|
|
451
|
+
return { ok: false, error: "no_log_path", detail: describeMissingBridge(env) };
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const reply = await installTokenPost(bridge, "/internal/wfp/log", { entries }, {
|
|
455
|
+
label: "log",
|
|
456
|
+
timeoutMs
|
|
457
|
+
});
|
|
458
|
+
if (reply.ok === false) {
|
|
459
|
+
return { ok: false, error: reply.error ?? "log_failed", detail: reply.detail };
|
|
460
|
+
}
|
|
461
|
+
return { ok: true, written: typeof reply.written === "number" ? reply.written : entries.length };
|
|
462
|
+
} catch (err) {
|
|
463
|
+
const e = err;
|
|
464
|
+
const message = e instanceof Error ? e.message : String(err);
|
|
465
|
+
console.warn(`[sprigr-log] log fallback failed: ${message}`);
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: e.error ?? (e.status !== void 0 ? "log_failed" : "log_transport_failed"),
|
|
469
|
+
...e.status !== void 0 ? { status: e.status } : {},
|
|
470
|
+
detail: e.detail ?? message
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function logToPlatform(env, input, opts) {
|
|
475
|
+
const entries = validateLogEntries(input);
|
|
476
|
+
const injected = bindingLog(env);
|
|
477
|
+
let result;
|
|
478
|
+
if (injected) {
|
|
479
|
+
try {
|
|
480
|
+
result = Promise.resolve(injected(entries)).catch((err) => ({
|
|
481
|
+
ok: false,
|
|
482
|
+
error: "log_failed",
|
|
483
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
484
|
+
}));
|
|
485
|
+
} catch (err) {
|
|
486
|
+
result = Promise.resolve({
|
|
487
|
+
ok: false,
|
|
488
|
+
error: "log_failed",
|
|
489
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
result = postLog(env, entries, opts?.timeoutMs ?? DEFAULT_LOG_TIMEOUT_MS);
|
|
494
|
+
}
|
|
495
|
+
if (opts?.waitUntil) opts.waitUntil(result);
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
function withSprigrLogFallback(env, opts) {
|
|
499
|
+
if (bindingLog(env)) return env;
|
|
500
|
+
const bridge = resolveInstallBridge(env);
|
|
501
|
+
if (!bridge) return env;
|
|
502
|
+
const log = (input) => {
|
|
503
|
+
const entries = validateLogEntries(input);
|
|
504
|
+
const p = postLog(env, entries, opts?.timeoutMs ?? DEFAULT_LOG_TIMEOUT_MS);
|
|
505
|
+
if (opts?.waitUntil) opts.waitUntil(p);
|
|
506
|
+
return p;
|
|
507
|
+
};
|
|
508
|
+
const existing = env.SPRIGR ?? {};
|
|
509
|
+
return overlaySprigr(env, { ...existing, log });
|
|
510
|
+
}
|
|
511
|
+
|
|
315
512
|
// src/actor.ts
|
|
316
513
|
function parseActor(args) {
|
|
317
514
|
if (!args || typeof args !== "object") return void 0;
|
|
@@ -758,10 +955,11 @@ async function pin(opts, env, connection) {
|
|
|
758
955
|
return opts.pinEnv(env, connection);
|
|
759
956
|
}
|
|
760
957
|
async function askPass(name, spec, params, env, opts, hashPrefix) {
|
|
761
|
-
const connection = await opts.resolveConnection(env, params);
|
|
958
|
+
const connection = await (spec.resolveConnection ?? opts.resolveConnection)(env, params);
|
|
762
959
|
const rawId = rawIdOf(params, spec.keys);
|
|
763
960
|
const pinned = await pin(opts, env, connection);
|
|
764
|
-
const
|
|
961
|
+
const describeTarget = spec.describeTarget ?? opts.describeTarget;
|
|
962
|
+
const target = rawId && describeTarget ? await safeLabel(describeTarget, pinned, rawId, name) : rawId || "(unknown id)";
|
|
765
963
|
const extra = spec.hash ? spec.hash(params) : [];
|
|
766
964
|
const count = countOf(spec, params, rawId);
|
|
767
965
|
return {
|
|
@@ -774,8 +972,9 @@ async function askPass(name, spec, params, env, opts, hashPrefix) {
|
|
|
774
972
|
};
|
|
775
973
|
}
|
|
776
974
|
async function grantedPass(name, spec, params, env, opts, write) {
|
|
777
|
-
const connection = await opts.resolveConnection(env, params);
|
|
778
|
-
const
|
|
975
|
+
const connection = await (spec.resolveConnection ?? opts.resolveConnection)(env, params);
|
|
976
|
+
const stampConnection = spec.stampConnection ?? opts.stampConnection;
|
|
977
|
+
const stamp = (r) => connection && stampConnection ? stampConnection(r, connection) : r;
|
|
779
978
|
const undo = spec.undo;
|
|
780
979
|
const id = rawIdOf(params, spec.keys);
|
|
781
980
|
if (!undo || !id) return stamp(await write());
|
|
@@ -1165,11 +1364,15 @@ function commonDestructiveVerbs(subject) {
|
|
|
1165
1364
|
export {
|
|
1166
1365
|
APPROVAL_GRANTED_KEY,
|
|
1167
1366
|
DEFAULT_EMIT_TIMEOUT_MS,
|
|
1367
|
+
DEFAULT_LOG_TIMEOUT_MS,
|
|
1168
1368
|
DEFAULT_MAX_FILE_BYTES,
|
|
1169
1369
|
DEFAULT_MONEY_FIELDS,
|
|
1170
1370
|
DEFAULT_WRITE_PREFIXES,
|
|
1171
1371
|
InvalidUndoEnvelopeError,
|
|
1172
1372
|
NotConnectedError,
|
|
1373
|
+
SPRIGR_LOG_LEVELS,
|
|
1374
|
+
SPRIGR_LOG_LIMITS,
|
|
1375
|
+
SprigrLogValidationError,
|
|
1173
1376
|
UNIT_SEP,
|
|
1174
1377
|
VIEWER_ANONYMOUS,
|
|
1175
1378
|
VIEWER_CONTEXT_HEADER,
|
|
@@ -1187,6 +1390,7 @@ export {
|
|
|
1187
1390
|
buildMarketplaceWebhookUrl,
|
|
1188
1391
|
bytesToBase64,
|
|
1189
1392
|
canEmit,
|
|
1393
|
+
canLog,
|
|
1190
1394
|
checkConfirmationPolicy,
|
|
1191
1395
|
commonDestructiveVerbs,
|
|
1192
1396
|
constantTimeEqual,
|
|
@@ -1209,6 +1413,7 @@ export {
|
|
|
1209
1413
|
isNotConnectedError,
|
|
1210
1414
|
isStagingHost,
|
|
1211
1415
|
listAppFiles,
|
|
1416
|
+
logToPlatform,
|
|
1212
1417
|
manifestIsFresh,
|
|
1213
1418
|
offerUndo,
|
|
1214
1419
|
overlaySprigr,
|
|
@@ -1233,6 +1438,9 @@ export {
|
|
|
1233
1438
|
set,
|
|
1234
1439
|
tool,
|
|
1235
1440
|
undoEnvelope,
|
|
1441
|
+
validateLogEntries,
|
|
1442
|
+
validateLogEntry,
|
|
1236
1443
|
verifyViewerToken,
|
|
1237
|
-
withSprigrEmitFallback
|
|
1444
|
+
withSprigrEmitFallback,
|
|
1445
|
+
withSprigrLogFallback
|
|
1238
1446
|
};
|