deepline 0.3.304 → 0.3.306

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.
Files changed (103) hide show
  1. package/dist/bundling-sources/sdk/src/cli-invocation-id.ts +14 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +24 -1
  3. package/dist/bundling-sources/sdk/src/http.ts +11 -0
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/tool-calls.ts +19 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1 -1
  7. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +51 -25
  8. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/product-notifications/billing-sales.ts +13 -0
  10. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +69 -0
  11. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +26 -1
  12. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +9 -1
  13. package/dist/cli/{auth-YKJHKHIQ.mjs → auth-WTC7LCQ6.mjs} +4 -4
  14. package/dist/cli/auth-ZT3UF33R.js +31 -0
  15. package/dist/cli/{billing-EOIKAZES.js → billing-3DYTHO32.js} +70 -70
  16. package/dist/cli/{billing-XEEW5SV2.mjs → billing-VQM65LVP.mjs} +4 -4
  17. package/dist/cli/chunk-2IKIG333.js +65 -0
  18. package/dist/cli/{chunk-NC4JXVVS.js → chunk-2KR56MPF.js} +2 -2
  19. package/dist/cli/{chunk-7QV545D7.mjs → chunk-4PSSDZQB.mjs} +7 -1
  20. package/dist/cli/{chunk-NW7I3LDU.mjs → chunk-5HS5EEFE.mjs} +3 -3
  21. package/dist/cli/{chunk-35SCWK4Y.mjs → chunk-5V56XCQN.mjs} +1 -1
  22. package/dist/cli/{chunk-JKY2QANB.mjs → chunk-6CRUYVU7.mjs} +4 -4
  23. package/dist/cli/{chunk-IHT6XITB.mjs → chunk-7XORS6WQ.mjs} +2 -2
  24. package/dist/cli/{chunk-7HYH533G.mjs → chunk-FJ3DANI4.mjs} +1 -1
  25. package/dist/cli/{chunk-KP5TOAI4.js → chunk-FZNO6B4W.js} +6 -6
  26. package/dist/cli/{chunk-JZWC4ES6.mjs → chunk-LLGF6V6I.mjs} +44 -8
  27. package/dist/cli/{chunk-DOCDA2YP.js → chunk-M6HCRRV2.js} +10 -4
  28. package/dist/cli/{chunk-BBXO7APG.js → chunk-MMNZ6GBT.js} +6 -6
  29. package/dist/cli/{chunk-INOSSTSG.js → chunk-OF7QP2XX.js} +15 -15
  30. package/dist/cli/{chunk-ZQSQCHG3.js → chunk-OZIFVQRY.js} +1 -1
  31. package/dist/cli/{chunk-FDEU7UY7.js → chunk-PHNAFCSB.js} +275 -239
  32. package/dist/cli/{chunk-TABL2Q6H.mjs → chunk-QDC4XTO3.mjs} +2 -2
  33. package/dist/cli/{chunk-DNBJRKVE.js → chunk-RODIZCGU.js} +60 -60
  34. package/dist/cli/{chunk-NJS3RHQO.js → chunk-SZQPJW2L.js} +6 -6
  35. package/dist/cli/{chunk-7NQJDQPH.js → chunk-U37SW6IK.js} +48 -48
  36. package/dist/cli/chunk-UABLQKTZ.mjs +65 -0
  37. package/dist/cli/{chunk-AOUGAM72.mjs → chunk-UT3VLSLM.mjs} +5 -5
  38. package/dist/cli/{chunk-USJBSNRI.js → chunk-W7ELTWDM.js} +26 -26
  39. package/dist/cli/{chunk-YJ2QYYB5.mjs → chunk-WV64SNDV.mjs} +1 -1
  40. package/dist/cli/{chunk-7OXXCRGZ.mjs → chunk-WWPI4GJI.mjs} +1 -1
  41. package/dist/cli/{client-FZPHVNWV.mjs → client-DCEK34D5.mjs} +3 -3
  42. package/dist/cli/client-UQXVU2T6.js +20 -0
  43. package/dist/cli/{csv-ANJ4KSHF.js → csv-H42JDJ6G.js} +12 -12
  44. package/dist/cli/{csv-CCHVZPRD.mjs → csv-LXBF5UIV.mjs} +3 -3
  45. package/dist/cli/{db-JFVR6STL.mjs → db-3O5CFLNP.mjs} +4 -4
  46. package/dist/cli/{db-AE3MLMRF.js → db-DP5GU4MU.js} +20 -20
  47. package/dist/cli/{executions-DGX53UEW.js → executions-5FFTPHEX.js} +6 -6
  48. package/dist/cli/{executions-WNXCURLN.mjs → executions-QC2UZKIP.mjs} +4 -4
  49. package/dist/cli/{feedback-L4A66OA2.mjs → feedback-JCGE6JFW.mjs} +3 -3
  50. package/dist/cli/{feedback-DGEFG6JI.js → feedback-XAU3FI5S.js} +6 -6
  51. package/dist/cli/git-CNO2UFZE.js +23 -0
  52. package/dist/cli/{git-LIZ3FGVR.mjs → git-S3Y3E2NL.mjs} +4 -4
  53. package/dist/cli/index.js +155 -121
  54. package/dist/cli/index.mjs +121 -87
  55. package/dist/cli/{legacy-noops-UCKGOSJA.mjs → legacy-noops-FQF2IRVS.mjs} +4 -4
  56. package/dist/cli/{legacy-noops-L7GGGAOR.js → legacy-noops-TXOUX4FX.js} +7 -7
  57. package/dist/cli/{monitors-NGACMMWV.js → monitors-4BQCO6B7.js} +85 -85
  58. package/dist/cli/{monitors-NLMFDD4C.mjs → monitors-F3PELPSH.mjs} +4 -4
  59. package/dist/cli/{org-2OW6TPHF.mjs → org-ER5J3ATG.mjs} +4 -4
  60. package/dist/cli/{org-FJSCZ6S3.js → org-VQM6OOLK.js} +60 -60
  61. package/dist/cli/{play-ZZ4AQLL5.mjs → play-B5H2RN4N.mjs} +6 -6
  62. package/dist/cli/{play-GJC4AIAX.js → play-WKZPAOW3.js} +200 -200
  63. package/dist/cli/{providers-D7KLXB4S.js → providers-5TEA2FJG.js} +7 -7
  64. package/dist/cli/{providers-6BJNJUSG.mjs → providers-6ODEAYC2.mjs} +4 -4
  65. package/dist/cli/{quickstart-FH4NGHEJ.mjs → quickstart-BCYT7BCY.mjs} +3 -3
  66. package/dist/cli/{quickstart-6WYRYZJZ.js → quickstart-GJYCNMFC.js} +5 -5
  67. package/dist/cli/{search-KOSJMN3U.js → search-KKJ535OX.js} +6 -6
  68. package/dist/cli/{search-SUT5XFI6.mjs → search-RY547KOT.mjs} +5 -5
  69. package/dist/cli/{secrets-Y2OI2P7O.js → secrets-RFGDXSEW.js} +13 -13
  70. package/dist/cli/{secrets-QK5QP4YU.mjs → secrets-U42ZEYIU.mjs} +4 -4
  71. package/dist/cli/{sessions-VQKZHOQJ.mjs → sessions-YWEFB62D.mjs} +4 -4
  72. package/dist/cli/{sessions-MLDROWXY.js → sessions-ZRTWHMIH.js} +5 -5
  73. package/dist/cli/{settings-SPUINZYG.mjs → settings-AS4U6NVP.mjs} +7 -7
  74. package/dist/cli/{settings-6WOSPHBC.js → settings-XUWFXIL7.js} +74 -74
  75. package/dist/cli/{setup-ZE2SDMC4.mjs → setup-2GUIV2EW.mjs} +6 -6
  76. package/dist/cli/{setup-E2UJV6PW.js → setup-2QPWP2FI.js} +41 -41
  77. package/dist/cli/skills-4E5KYW2Y.js +28 -0
  78. package/dist/cli/{skills-OJYRPY2N.mjs → skills-5QS7ERVF.mjs} +4 -4
  79. package/dist/cli/{tools-BNHRVKLA.js → tools-J47LIO2H.js} +120 -87
  80. package/dist/cli/{tools-O6EIDEKW.mjs → tools-PVWSGCZD.mjs} +76 -43
  81. package/dist/cli/update-GZKGOMKD.js +28 -0
  82. package/dist/cli/{update-NWESWMYF.mjs → update-KS4TRSZI.mjs} +6 -6
  83. package/dist/cli/{workflow-SAJAOPYE.js → workflow-HQRUUTTL.js} +15 -15
  84. package/dist/cli/{workflow-YJHXDD57.mjs → workflow-QCLR7GVC.mjs} +5 -5
  85. package/dist/{client-Me88eH3z.d.mts → client-Dj8MAkDc.d.mts} +91 -1
  86. package/dist/{client-DjJfoW8r.d.ts → client-DsrFGXJf.d.ts} +91 -1
  87. package/dist/index.d.mts +2 -2
  88. package/dist/index.d.ts +2 -2
  89. package/dist/index.js +6 -6
  90. package/dist/index.mjs +3 -3
  91. package/dist/install-integrity.json +74 -70
  92. package/dist/plays/run-request.d.mts +1 -1
  93. package/dist/plays/run-request.d.ts +1 -1
  94. package/dist/release.d.mts +1 -1
  95. package/dist/release.d.ts +1 -1
  96. package/dist/release.js +2 -2
  97. package/dist/release.mjs +1 -1
  98. package/package.json +1 -1
  99. package/dist/cli/auth-UBWZC26A.js +0 -31
  100. package/dist/cli/client-IMFLPCD7.js +0 -20
  101. package/dist/cli/git-6QWUB6CR.js +0 -23
  102. package/dist/cli/skills-J3QIHO5L.js +0 -28
  103. package/dist/cli/update-TDOIF3C4.js +0 -28
@@ -0,0 +1,14 @@
1
+ import { DeeplineError } from './errors.js';
2
+
3
+ const CLI_INVOCATION_ID_PATTERN =
4
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
5
+
6
+ export function validateCliInvocationId(value: string): void {
7
+ if (!CLI_INVOCATION_ID_PATTERN.test(value)) {
8
+ throw new DeeplineError(
9
+ 'CLI invocation IDs must be UUID v4 values.',
10
+ undefined,
11
+ 'CLI_INVOCATION_ID_INVALID',
12
+ );
13
+ }
14
+ }
@@ -57,6 +57,7 @@ import {
57
57
  type ExecutionByKeyResult,
58
58
  type ToolExecution,
59
59
  } from './tool-calls.js';
60
+ import { validateCliInvocationId } from './cli-invocation-id.js';
60
61
  export type {
61
62
  ExecutionRecovery,
62
63
  ExecutionByKeyResult,
@@ -3263,7 +3264,13 @@ export class DeeplineClient {
3263
3264
  * console.log(`Input schema:`, meta.inputSchema);
3264
3265
  * ```
3265
3266
  */
3266
- async getTool(toolId: string): Promise<ToolMetadata> {
3267
+ async getTool(
3268
+ toolId: string,
3269
+ options?: { cliInvocationId?: string },
3270
+ ): Promise<ToolMetadata> {
3271
+ if (options?.cliInvocationId !== undefined) {
3272
+ validateCliInvocationId(options.cliInvocationId);
3273
+ }
3267
3274
  return this.http.request<ToolMetadata>(
3268
3275
  `/api/v2/integrations/${encodeURIComponent(toolId)}/get`,
3269
3276
  {
@@ -3274,6 +3281,9 @@ export class DeeplineClient {
3274
3281
  retryApiErrors: true,
3275
3282
  headers: {
3276
3283
  'x-deepline-tool-meta-only': '1',
3284
+ ...(options?.cliInvocationId !== undefined
3285
+ ? { 'x-deepline-cli-invocation-id': options.cliInvocationId }
3286
+ : {}),
3277
3287
  },
3278
3288
  },
3279
3289
  );
@@ -5427,7 +5437,11 @@ export class DeeplineClient {
5427
5437
  query: string;
5428
5438
  compact?: boolean;
5429
5439
  scope?: 'prebuilt' | 'owned' | 'all';
5440
+ cliInvocationId?: string;
5430
5441
  }): Promise<PlayDescription[]> {
5442
+ if (options.cliInvocationId !== undefined) {
5443
+ validateCliInvocationId(options.cliInvocationId);
5444
+ }
5431
5445
  const params = new URLSearchParams();
5432
5446
  params.set('search', options.query.trim());
5433
5447
  const scope = options.scope ?? 'prebuilt';
@@ -5436,6 +5450,15 @@ export class DeeplineClient {
5436
5450
  }
5437
5451
  const response = await this.http.get<{ plays: PlayListItem[] }>(
5438
5452
  `/api/v2/plays?${params.toString()}`,
5453
+ {
5454
+ ...(options.cliInvocationId !== undefined
5455
+ ? {
5456
+ headers: {
5457
+ 'x-deepline-cli-invocation-id': options.cliInvocationId,
5458
+ },
5459
+ }
5460
+ : {}),
5461
+ },
5439
5462
  );
5440
5463
  const plays = (response.plays ?? []).map((play) =>
5441
5464
  this.summarizePlayListItem(play, options),
@@ -8,6 +8,8 @@
8
8
  *
9
9
  * - **Max attempts**: `1 + maxRetries` (default: 4 total attempts)
10
10
  * - **Backoff**: exponential — 1s, 2s, 4s, 8s, ... capped at 30s
11
+ * - **Total automatic retry wait**: 30s per request; a longer upstream
12
+ * `Retry-After` is returned to the caller instead of blocking the request
11
13
  * - **Retryable**: network errors, timeouts, HTTP 429 (rate limit)
12
14
  * - **Not retryable**: HTTP 401 (auth error), HTTP 403 and other 4xx API errors
13
15
  *
@@ -54,6 +56,7 @@ const COWORK_NETWORK_HINT =
54
56
  'Claude Cowork appears to be running Deepline in a network-restricted sandbox. In Claude Desktop, open Settings > Capabilities, turn on Allow network egress, and set Domain allowlist to All domains for the Cowork session.';
55
57
  const REQUEST_TIMEOUT_MARKER = Symbol('deeplineRequestTimeout');
56
58
  const REQUEST_ABORT_MARKER = Symbol('deeplineRequestAbort');
59
+ const MAX_AUTOMATIC_RETRY_WAIT_MS = 30_000;
57
60
 
58
61
  interface RequestOptions {
59
62
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -431,6 +434,7 @@ export class HttpClient {
431
434
  ? [url]
432
435
  : buildCandidateUrls(url);
433
436
  let retryAfterDelayMs: number | null = null;
437
+ let totalRetryWaitMs = 0;
434
438
 
435
439
  const maxRetries = options?.maxRetries ?? this.config.maxRetries;
436
440
 
@@ -447,6 +451,13 @@ export class HttpClient {
447
451
  ? backoffMs
448
452
  : Math.max(backoffMs, retryAfterDelayMs);
449
453
  retryAfterDelayMs = null;
454
+ if (totalRetryWaitMs + delayMs > MAX_AUTOMATIC_RETRY_WAIT_MS) {
455
+ // Honor the upstream delay by returning its original error. Never
456
+ // retry sooner than requested, and never let automatic retries hold
457
+ // the caller for an unbounded series of Retry-After sleeps.
458
+ break requestAttempts;
459
+ }
460
+ totalRetryWaitMs += delayMs;
450
461
  networkPhase = 'retry_wait';
451
462
  try {
452
463
  await sleep(delayMs, options?.signal);
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.304',
203
+ version: '0.3.306',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -18,6 +18,7 @@ import {
18
18
 
19
19
  import { DeeplineError, ToolExecutionError } from './errors.js';
20
20
  import type { HttpClient } from './http.js';
21
+ import { validateCliInvocationId } from './cli-invocation-id.js';
21
22
 
22
23
  const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
23
24
  const EXECUTE_RESPONSE_CONTRACT_HEADER = 'x-deepline-execute-response-contract';
@@ -132,6 +133,8 @@ export type ExecuteToolRawOptions = {
132
133
  recoveryTimeoutMs?: number;
133
134
  /** The `search_ref` of the tools search that found this tool. */
134
135
  searchRef?: string;
136
+ /** Correlates one CLI command with its execute request and server telemetry. */
137
+ cliInvocationId?: string;
135
138
  };
136
139
 
137
140
  /** Durable state returned for a keyed tool execution. */
@@ -327,6 +330,9 @@ export class ToolCalls {
327
330
  input: Record<string, unknown>,
328
331
  options?: ExecuteToolRawOptions,
329
332
  ): Promise<ToolExecution<TData, TMeta>> {
333
+ if (options?.cliInvocationId !== undefined) {
334
+ validateCliInvocationId(options.cliInvocationId);
335
+ }
330
336
  // Snapshot JSON input before the caller's awaited onExecution callback and
331
337
  // before any retry, so every POST is byte-equivalent in meaning.
332
338
  const inputSnapshot = JSON.parse(JSON.stringify(input)) as Record<
@@ -365,7 +371,11 @@ export class ToolCalls {
365
371
  this.configuredRequestTimeoutMs,
366
372
  );
367
373
  if (idempotencyKey !== undefined) {
368
- await this.lookupExecutionByKey(idempotencyKey, timeoutMs);
374
+ await this.lookupExecutionByKey(
375
+ idempotencyKey,
376
+ timeoutMs,
377
+ options?.cliInvocationId,
378
+ );
369
379
  }
370
380
  const isApifySync = toolId.trim().toLowerCase() === 'apify_run_actor_sync';
371
381
  const headers = {
@@ -384,6 +394,9 @@ export class ToolCalls {
384
394
  ...(options?.searchRef
385
395
  ? { 'x-deepline-search-ref': options.searchRef }
386
396
  : {}),
397
+ ...(options?.cliInvocationId !== undefined
398
+ ? { 'x-deepline-cli-invocation-id': options.cliInvocationId }
399
+ : {}),
387
400
  };
388
401
  const request = (requestTimeoutMs: number | undefined) =>
389
402
  this.http.post<ToolExecution<TData, TMeta>>(
@@ -434,6 +447,7 @@ export class ToolCalls {
434
447
  this.defaultRequestTimeoutMs,
435
448
  remainingMs,
436
449
  ),
450
+ options?.cliInvocationId,
437
451
  );
438
452
  } catch (error) {
439
453
  if (!isRecoverableExecutionLookupError(error)) throw error;
@@ -525,6 +539,7 @@ export class ToolCalls {
525
539
  private async lookupExecutionByKey(
526
540
  idempotencyKey: string,
527
541
  timeoutMs?: number,
542
+ cliInvocationId?: string,
528
543
  ): Promise<ExecutionByKeyResult | null> {
529
544
  validateExecutionIdempotencyKey(idempotencyKey);
530
545
  let supported = false;
@@ -536,6 +551,9 @@ export class ToolCalls {
536
551
  timeout: timeoutMs,
537
552
  idempotencyKey,
538
553
  allowNotFound: true,
554
+ ...(cliInvocationId !== undefined
555
+ ? { headers: { 'x-deepline-cli-invocation-id': cliInvocationId } }
556
+ : {}),
539
557
  onResponse: (response) => {
540
558
  supported =
541
559
  response.headers.get('X-Deepline-Idempotency-Supported') === 'true';
@@ -1070,7 +1070,7 @@ export async function withRuntimeSchedulerClient<T>(
1070
1070
  let queryCount = 0;
1071
1071
  let queryMs = 0;
1072
1072
  try {
1073
- if (!entry.databasePoolAttached) {
1073
+ if (process.env.VERCEL?.trim() && !entry.databasePoolAttached) {
1074
1074
  attachDatabasePool(entry.pool);
1075
1075
  entry.databasePoolAttached = true;
1076
1076
  }
@@ -14701,8 +14701,10 @@ function terminalLogProjectionForOutbox(
14701
14701
  metadata: PlayLogMetadata | null;
14702
14702
  entryId: string | null;
14703
14703
  } => entry !== null,
14704
- )
14705
- .slice(-LOG_TAIL_LIMIT);
14704
+ );
14705
+ // This carrier is consumed into the paginated log store by the terminal
14706
+ // statement. The runner already bounds its envelope by bytes; applying the
14707
+ // status tail here loses retained lines when live delivery was interrupted.
14706
14708
  return {
14707
14709
  lines: entries.map((entry) => entry.line),
14708
14710
  lineContexts: entries.map((entry) => entry.context),
@@ -18091,10 +18093,10 @@ async function claimPostgresSchedulerOutboxPrefix(
18091
18093
  ? `owned_run AS MATERIALIZED (
18092
18094
  SELECT run_id FROM ${tableName('runs', options)}
18093
18095
  WHERE run_id = $2
18094
- -- This dispatcher already owns the head. A producer holding the run
18095
- -- lock is not a lost delivery lease: wait for its short transaction,
18096
- -- then revalidate the head below. New-run selection still skips locks.
18097
- FOR UPDATE
18096
+ -- The head is already owned. Wait for the producer, then revalidate
18097
+ -- its lease below; initial selection still skips unrelated busy runs.
18098
+ -- Child-row foreign-key share locks can coexist with this fence.
18099
+ FOR NO KEY UPDATE
18098
18100
  ),`
18099
18101
  : ''
18100
18102
  } head AS MATERIALIZED (
@@ -18117,7 +18119,7 @@ async function claimPostgresSchedulerOutboxPrefix(
18117
18119
  ${
18118
18120
  input.extendOwnedHead
18119
18121
  ? `AND candidate.event_id = $5
18120
- AND candidate.lease_owner = $3 AND candidate.lease_expires_at > now()`
18122
+ AND candidate.lease_owner = $3 AND candidate.lease_expires_at > clock_timestamp()`
18121
18123
  : ''
18122
18124
  }
18123
18125
  AND candidate.failed_at IS NULL
@@ -20340,23 +20342,47 @@ async function dispatchRunLedgerOutboxBatchWithScope(
20340
20342
  events.length === 1 &&
20341
20343
  first.eventType === RUN_SCHEDULER_OUTBOX_EVENT_TYPE.runEvents
20342
20344
  ) {
20343
- const widened = await withClient((client) =>
20344
- readPendingPostgresSchedulerOutboxEvents(
20345
- client,
20346
- {
20347
- runId: first.runId,
20348
- leaseOwner,
20349
- leaseSeconds: input.leaseSeconds ?? 120,
20350
- limit: Math.min(
20351
- RUN_LEDGER_PROJECTION_PREFIX_MAX_FACTS,
20352
- remainingEvents ?? RUN_LEDGER_PROJECTION_PREFIX_MAX_FACTS,
20353
- ),
20354
- allowIngressPrefix: true,
20355
- extendOwnedHead: first.eventId,
20356
- },
20357
- options,
20358
- ),
20359
- );
20345
+ const widened = await withClient(async (client) => {
20346
+ await client.query('BEGIN');
20347
+ try {
20348
+ await client.query(
20349
+ `SELECT set_config('lock_timeout', $1, true),
20350
+ set_config('statement_timeout', $2, true)`,
20351
+ [
20352
+ `${RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS}ms`,
20353
+ `${RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS}ms`,
20354
+ ],
20355
+ );
20356
+ // An owned head must extend from a fresh snapshot after the producer
20357
+ // commits. Keep the wait inside the existing SQL budget and permit
20358
+ // child-row foreign-key share locks under the ingress fence.
20359
+ await client.query(
20360
+ `SELECT run_id FROM ${tableName('runs', options)} WHERE run_id = $1 FOR NO KEY UPDATE`,
20361
+ [first.runId],
20362
+ );
20363
+ const prefix = await readPendingPostgresSchedulerOutboxEvents(
20364
+ client,
20365
+ {
20366
+ runId: first.runId,
20367
+ leaseOwner,
20368
+ leaseSeconds: input.leaseSeconds ?? 120,
20369
+ limit: Math.min(
20370
+ RUN_LEDGER_PROJECTION_PREFIX_MAX_FACTS,
20371
+ remainingEvents ?? RUN_LEDGER_PROJECTION_PREFIX_MAX_FACTS,
20372
+ ),
20373
+ allowIngressPrefix: true,
20374
+ extendOwnedHead: first.eventId,
20375
+ inTransaction: true,
20376
+ },
20377
+ options,
20378
+ );
20379
+ await client.query('COMMIT');
20380
+ return prefix;
20381
+ } catch (error) {
20382
+ await client.query('ROLLBACK');
20383
+ throw error;
20384
+ }
20385
+ });
20360
20386
  if (widened[0]?.eventId !== first.eventId) {
20361
20387
  throw new RunLedgerProjectionLeaseLostError(
20362
20388
  'head_lease_owner_mismatch',
@@ -20729,7 +20755,7 @@ async function dispatchRunLedgerOutboxBatchWithScope(
20729
20755
  }
20730
20756
  const failure = runLedgerDeliveryFailure({ error });
20731
20757
  console.warn('[run-ledger-outbox.batch_dispatch_failed]', {
20732
- runId: input.runId ?? null,
20758
+ runId: input.runId ?? events[0]?.runId ?? null,
20733
20759
  eventIds: events.map((event) => event.eventId),
20734
20760
  error: error instanceof Error ? error.message : String(error),
20735
20761
  code: failure.code,
@@ -2793,6 +2793,16 @@ export async function hasRuntimeEventLogPositionIndex(
2793
2793
  return true;
2794
2794
  }
2795
2795
 
2796
+ /** Deployment-only check for all supported legacy and current log reads. */
2797
+ export async function hasRuntimeEventQueryIndexes(
2798
+ client: PostgresSchedulerQueryClient,
2799
+ ): Promise<boolean> {
2800
+ for (const expected of RUNTIME_EVENT_QUERY_INDEXES) {
2801
+ if (!(await hasRuntimeEventQueryIndex(client, expected))) return false;
2802
+ }
2803
+ return true;
2804
+ }
2805
+
2796
2806
  /** Prepare old and new position indexes before replacing the projection body. */
2797
2807
  export async function prepareRuntimeEventLogPositionIndex(
2798
2808
  client: PostgresSchedulerQueryClient,
@@ -0,0 +1,13 @@
1
+ export const BILLING_SALES_GROUP_KEY = 'shadow.billing-sales';
2
+ export const BILLING_SALES_DESTINATION_KEY = 'shadow.billing-sales.slack';
3
+ export const BILLING_SALES_SLACK_CHANNEL_ID = 'C0AGWVD2TNK';
4
+ export const BILLING_SALES_CONNECTION_ID = 'platform-reporting-bot';
5
+ export const BILLING_SALES_CREDIT_PURCHASE_MINIMUM_CENTS = 15_000;
6
+ export const BILLING_SALES_RULE_KEYS: readonly string[] = [
7
+ 'internal.billing.credit_purchase',
8
+ 'internal.billing.paid_plan',
9
+ ];
10
+
11
+ export function isPlatformNotificationGroup(groupKey: string): boolean {
12
+ return groupKey === 'shadow' || groupKey === BILLING_SALES_GROUP_KEY;
13
+ }
@@ -13,6 +13,10 @@ import {
13
13
  } from './events';
14
14
  import { legacySlackNotificationType } from './legacy-slack';
15
15
  import type { NotificationText } from './content';
16
+ import {
17
+ BILLING_SALES_CREDIT_PURCHASE_MINIMUM_CENTS,
18
+ BILLING_SALES_GROUP_KEY,
19
+ } from './billing-sales';
16
20
 
17
21
  type NotificationTypeDefinition = {
18
22
  id: string;
@@ -734,6 +738,71 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
734
738
  }),
735
739
  },
736
740
  },
741
+ {
742
+ id: 'internal.billing.credit_purchase',
743
+ eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
744
+ requiredFilter: { currency: 'usd' },
745
+ allowedTriggers: ['threshold_crossing'],
746
+ catalog: undefined,
747
+ shadow: true,
748
+ rule: {
749
+ default: {
750
+ ruleKey: 'internal.billing.credit_purchase',
751
+ groupKey: BILLING_SALES_GROUP_KEY,
752
+ },
753
+ create: (_input, base) => ({
754
+ ...base,
755
+ repeatMode: 'every_match',
756
+ trigger: {
757
+ kind: 'threshold_crossing',
758
+ direction: 'at_or_above',
759
+ threshold: BILLING_SALES_CREDIT_PURCHASE_MINIMUM_CENTS,
760
+ unit: 'usd_cents',
761
+ },
762
+ }),
763
+ present: ({ payload }) => ({
764
+ title: `Credit purchase: $${(Number(payload.amountPaidCents) / 100).toFixed(2)}`,
765
+ summary: [
766
+ { workspace: true },
767
+ ` purchased ${formatCredits(Number(payload.credits))} credits.`,
768
+ ],
769
+ details: [],
770
+ targetUrl: String(payload.targetUrl),
771
+ subjectKind: 'workspace',
772
+ subjectId: String(payload.accountId),
773
+ }),
774
+ },
775
+ },
776
+ {
777
+ id: 'internal.billing.paid_plan',
778
+ eventName: ProductNotificationEvent.BillingPlanPurchased,
779
+ requiredFilter: {},
780
+ allowedTriggers: ['each_match'],
781
+ catalog: undefined,
782
+ shadow: true,
783
+ rule: {
784
+ default: {
785
+ ruleKey: 'internal.billing.paid_plan',
786
+ groupKey: BILLING_SALES_GROUP_KEY,
787
+ },
788
+ create: (_input, base) => ({
789
+ ...base,
790
+ repeatMode: 'every_match',
791
+ trigger: { kind: 'each_match' },
792
+ }),
793
+ present: ({ payload }) => ({
794
+ title: 'New paid plan signup',
795
+ summary: [
796
+ { workspace: true },
797
+ ` signed up for the ${payload.planName} plan.`,
798
+ ],
799
+ details: [],
800
+ targetUrl: String(payload.targetUrl),
801
+ subjectKind: 'workspace',
802
+ subjectId: String(payload.accountId),
803
+ }),
804
+ },
805
+ },
737
806
  {
738
807
  id: 'play.failed',
739
808
  eventName: ProductNotificationEvent.PlayRunCompleted,
@@ -8,13 +8,19 @@ import {
8
8
  validateProductNotificationDeliveryTiming,
9
9
  type ProductNotificationDefaultPolicy,
10
10
  } from './configuration';
11
+ import {
12
+ BILLING_SALES_CONNECTION_ID,
13
+ BILLING_SALES_DESTINATION_KEY,
14
+ BILLING_SALES_GROUP_KEY,
15
+ BILLING_SALES_SLACK_CHANNEL_ID,
16
+ } from './billing-sales';
11
17
 
12
18
  /**
13
19
  * Product defaults stay in code. Workspaces see these rows synthetically until
14
20
  * they create sparse copy-on-write overrides.
15
21
  */
16
22
  export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
17
- version: 8,
23
+ version: 9,
18
24
  groups: [
19
25
  {
20
26
  groupKey: 'default',
@@ -32,6 +38,12 @@ export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
32
38
  enabled: true,
33
39
  deliveryTiming: DEFAULT_PRODUCT_NOTIFICATION_DELIVERY_TIMING,
34
40
  },
41
+ {
42
+ groupKey: BILLING_SALES_GROUP_KEY,
43
+ name: 'Billing sales alerts',
44
+ enabled: true,
45
+ deliveryTiming: { mode: 'immediate' },
46
+ },
35
47
  ],
36
48
  destinations: [
37
49
  {
@@ -48,6 +60,19 @@ export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
48
60
  enabled: true,
49
61
  settings: { kind: 'email_roles', roles: ['owner'] },
50
62
  },
63
+ {
64
+ destinationKey: BILLING_SALES_DESTINATION_KEY,
65
+ groupKey: BILLING_SALES_GROUP_KEY,
66
+ name: 'Deepline signup channel',
67
+ enabled: true,
68
+ settings: {
69
+ kind: 'slack',
70
+ targetKind: 'channel',
71
+ targetId: BILLING_SALES_SLACK_CHANNEL_ID,
72
+ targetName: 'Deepline signup channel',
73
+ oauthConnectionId: BILLING_SALES_CONNECTION_ID,
74
+ },
75
+ },
51
76
  ],
52
77
  rules: PRODUCT_NOTIFICATION_DEFAULT_RULES,
53
78
  } satisfies ProductNotificationDefaultPolicy;
@@ -206,9 +206,10 @@ export const PRODUCT_NOTIFICATION_EVENT_DEFINITIONS: readonly ProductNotificatio
206
206
  credits: 'number',
207
207
  targetUrl: 'string',
208
208
  },
209
+ optionalFacts: { amountPaidCents: 'number', currency: 'string' },
209
210
  orderingSubjectFact: 'accountId',
210
211
  allowedTriggers: ['threshold_crossing'],
211
- allowedThresholdUnits: ['credits'],
212
+ allowedThresholdUnits: ['credits', 'usd_cents'],
212
213
  },
213
214
  {
214
215
  // The workspace moved onto a paid acquisition offer.
@@ -451,6 +452,13 @@ export function adaptProductNotificationObservation(input: {
451
452
  };
452
453
  }
453
454
  if (event.eventName === ProductNotificationEvent.CreditsPurchaseCompleted) {
455
+ if (trigger.kind === 'threshold_crossing' && trigger.unit === 'usd_cents') {
456
+ return {
457
+ kind: 'threshold_state',
458
+ currentValue: Number(event.payload.amountPaidCents ?? 0),
459
+ unit: 'usd_cents',
460
+ };
461
+ }
454
462
  return {
455
463
  kind: 'threshold_state',
456
464
  currentValue: Number(event.payload.credits),
@@ -9,11 +9,11 @@ import {
9
9
  registerAuthCommands,
10
10
  reportSetupCompleted,
11
11
  resolvePendingAuthClaimForResume
12
- } from "./chunk-IHT6XITB.mjs";
13
- import "./chunk-35SCWK4Y.mjs";
14
- import "./chunk-7QV545D7.mjs";
12
+ } from "./chunk-7XORS6WQ.mjs";
13
+ import "./chunk-5V56XCQN.mjs";
14
+ import "./chunk-4PSSDZQB.mjs";
15
15
  import "./chunk-DLIRSHV6.mjs";
16
- import "./chunk-7OXXCRGZ.mjs";
16
+ import "./chunk-WWPI4GJI.mjs";
17
17
  import "./chunk-NXGCO4JB.mjs";
18
18
  import "./chunk-WZYKRD7T.mjs";
19
19
  import "./chunk-KRD6H754.mjs";
@@ -0,0 +1,31 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ var _chunkU37SW6IKjs = require('./chunk-U37SW6IK.js');
13
+ require('./chunk-MMNZ6GBT.js');
14
+ require('./chunk-M6HCRRV2.js');
15
+ require('./chunk-XNYIZMNQ.js');
16
+ require('./chunk-OZIFVQRY.js');
17
+ require('./chunk-7P5AA3SN.js');
18
+ require('./chunk-OG64RBAC.js');
19
+ require('./chunk-COGTRVWW.js');
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+ exports.EXIT_WORKSPACE_MISMATCH = _chunkU37SW6IKjs.EXIT_WORKSPACE_MISMATCH; exports.clearPendingClaim = _chunkU37SW6IKjs.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkU37SW6IKjs.clearSatisfiedPendingClaim; exports.handleRegister = _chunkU37SW6IKjs.handleRegister; exports.handleStatus = _chunkU37SW6IKjs.handleStatus; exports.handleWait = _chunkU37SW6IKjs.handleWait; exports.readPendingAuthClaim = _chunkU37SW6IKjs.readPendingAuthClaim; exports.registerAuthCommands = _chunkU37SW6IKjs.registerAuthCommands; exports.reportSetupCompleted = _chunkU37SW6IKjs.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkU37SW6IKjs.resolvePendingAuthClaimForResume;