@isomorph.ai/app-sdk 2.6.0 → 2.6.2

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
@@ -298,6 +298,8 @@ export interface GmailAttachmentInput {
298
298
  export interface GmailMessageSendRequest {
299
299
  operation: 'gmail.message.send';
300
300
  resource: string;
301
+ /** `app` sends from the company mailbox IT connected (contracts 0.102.0); `user` (the default) sends as the signed-in person. */
302
+ mode?: SlackIdentityMode;
301
303
  input: {
302
304
  /** 1 to 50 email addresses per recipient list (at most 50 across to, cc and bcc). */
303
305
  to: string[];
package/dist/index.js CHANGED
@@ -835,7 +835,7 @@ export function createClient(options = {}) {
835
835
  }
836
836
  catch { /* not JSON: the status says what happened */ }
837
837
  if (!response.ok || body.error) {
838
- throw new IsomorphError(body.error?.category ?? 'INTERNAL', body.error?.message ?? `the public action answered ${response.status}`, body.requestId, undefined, response.status);
838
+ throw new IsomorphError(body.error?.category ?? 'INTERNAL', body.error?.message ?? `the public action answered ${response.status}`, body.requestId, body.error?.details, response.status);
839
839
  }
840
840
  return body.data;
841
841
  },
@@ -65,9 +65,9 @@ export declare const IntegrationOperations: {
65
65
  };
66
66
  readonly "gmail.message.send": {
67
67
  readonly kind: "saas";
68
- readonly identities: readonly ["user"];
68
+ readonly identities: readonly ["user", "app"];
69
69
  readonly readOnly: false;
70
- readonly declaredMode: false;
70
+ readonly declaredMode: true;
71
71
  readonly idempotencyKey: true;
72
72
  };
73
73
  readonly "gmail.attachment.read": {
@@ -90,10 +90,11 @@ export const IntegrationOperations = {
90
90
  "gmail.message.send": {
91
91
  "kind": "saas",
92
92
  "identities": [
93
- "user"
93
+ "user",
94
+ "app"
94
95
  ],
95
96
  "readOnly": false,
96
- "declaredMode": false,
97
+ "declaredMode": true,
97
98
  "idempotencyKey": true
98
99
  },
99
100
  "gmail.attachment.read": {
package/dist/runtime.d.ts CHANGED
@@ -70,6 +70,8 @@ export declare class Runs {
70
70
  cancel(id: string, owner: string): Promise<JobRun | null>;
71
71
  /** Rows whose lease ran out go back to the queue while attempts remain, else fail; how many there were. */
72
72
  reclaim(now: Date, active?: ReadonlySet<string>): Promise<number>;
73
+ /** Deletes finished runs created before `before` (the queue index covers status and created_at); how many went. */
74
+ prune(before: Date): Promise<number>;
73
75
  /** Claims the oldest queued run by a conditional update; null when the queue is empty or another worker won. */
74
76
  claim(now: Date): Promise<JobRun | null>;
75
77
  progress(id: string, percent: number, note?: string): Promise<void>;
@@ -120,6 +122,7 @@ export declare class Runtime {
120
122
  private queueWorkers;
121
123
  private queueWake;
122
124
  private claimTail;
125
+ private pruneDue;
123
126
  private readonly activeRuns;
124
127
  private readonly runs;
125
128
  private session;
@@ -193,6 +196,8 @@ export declare class Runtime {
193
196
  * when the queue is empty or both execution slots are occupied.
194
197
  */
195
198
  work(): Promise<boolean>;
199
+ /** A failed prune is logged and tried again next day; it never stops the pass that carries it. */
200
+ private pruneRuns;
196
201
  private serve;
197
202
  private transaction;
198
203
  private loadFailures;
package/dist/runtime.js CHANGED
@@ -88,6 +88,12 @@ const MAX_BODY_BYTES = 1 << 20;
88
88
  const LEASE_MS = 5 * 60_000;
89
89
  const QUEUE_WORKERS = 2;
90
90
  const QUEUE_POLL_MS = 2_000;
91
+ // A finished run's row (input, result, error) is kept this long, then the
92
+ // queue loop deletes it: once per process start and once a day after that,
93
+ // never on every pass. Nothing else ever removed a row (the outbox backlog
94
+ // of 2026-10-26 was the same shape).
95
+ const RUN_RETENTION_MS = 30 * 24 * 60 * 60_000;
96
+ const PRUNE_EVERY_MS = 24 * 60 * 60_000;
91
97
  const AI_RETRY_MS = 30_000;
92
98
  const TOKEN_REFRESH_MS = 60_000;
93
99
  // A gateway session lasts an hour. An action runs at most 300 s, so a request
@@ -280,6 +286,11 @@ export class Runs {
280
286
  }
281
287
  return interrupted.length;
282
288
  }
289
+ /** Deletes finished runs created before `before` (the queue index covers status and created_at); how many went. */
290
+ async prune(before) {
291
+ const { data } = await this.table().delete().in('status', ['succeeded', 'failed', 'cancelled']).lt('created_at', before.toISOString()).select('id');
292
+ return data.length;
293
+ }
283
294
  /** Claims the oldest queued run by a conditional update; null when the queue is empty or another worker won. */
284
295
  async claim(now) {
285
296
  while (true) {
@@ -332,6 +343,7 @@ export class Runtime {
332
343
  queueWorkers = 0;
333
344
  queueWake = 0;
334
345
  claimTail = Promise.resolve();
346
+ pruneDue = 0;
335
347
  activeRuns = new Set();
336
348
  runs;
337
349
  // Built on first use: the AWS signing modules load only on a serverless
@@ -671,6 +683,10 @@ export class Runtime {
671
683
  if (this.activeRuns.size >= QUEUE_WORKERS)
672
684
  return false;
673
685
  const now = new Date();
686
+ if (now.getTime() >= this.pruneDue) {
687
+ this.pruneDue = now.getTime() + PRUNE_EVERY_MS;
688
+ await this.pruneRuns(now);
689
+ }
674
690
  reclaimed = await this.runs.reclaim(now, this.activeRuns);
675
691
  run = await this.runs.claim(now);
676
692
  if (run)
@@ -711,6 +727,17 @@ export class Runtime {
711
727
  this.activeRuns.delete(run.id);
712
728
  }
713
729
  }
730
+ /** A failed prune is logged and tried again next day; it never stops the pass that carries it. */
731
+ async pruneRuns(now) {
732
+ try {
733
+ const pruned = await this.runs.prune(new Date(now.getTime() - RUN_RETENTION_MS));
734
+ if (pruned > 0)
735
+ this.log(`pruned ${pruned} finished run(s) older than 30 days`);
736
+ }
737
+ catch (error) {
738
+ this.log(`pruning finished runs failed: ${error instanceof Error ? error.message : String(error)}`);
739
+ }
740
+ }
714
741
  async serve(request, response) {
715
742
  const requestId = header(request, 'x-request-id') || undefined;
716
743
  const name = decodeURIComponent((request.url ?? '/').split('?')[0].replace(/^\/+/, ''));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isomorph.ai/app-sdk",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",