deepline 0.1.133 → 0.1.134
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/bundling-sources/apps/play-runner-workers/src/entry.ts +228 -24
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/builtin-pacing.ts +48 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +82 -58
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +70 -18
- package/dist/cli/index.js +26 -18
- package/dist/cli/index.mjs +26 -18
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
|
@@ -49,6 +49,11 @@ import {
|
|
|
49
49
|
type GovernanceSnapshot,
|
|
50
50
|
type PlayExecutionGovernor,
|
|
51
51
|
} from '../../../shared_libs/play-runtime/governor/governor';
|
|
52
|
+
import {
|
|
53
|
+
CTX_FETCH_EGRESS_PROVIDER,
|
|
54
|
+
CTX_FETCH_EGRESS_TOOL_ID,
|
|
55
|
+
resolveBuiltinPacing,
|
|
56
|
+
} from '../../../shared_libs/play-runtime/builtin-pacing';
|
|
52
57
|
import {
|
|
53
58
|
CoordinatorRateStateBackend,
|
|
54
59
|
type CoordinatorRatePort,
|
|
@@ -165,6 +170,7 @@ import { safePublicFetch } from '../../../shared_libs/security/safe-fetch';
|
|
|
165
170
|
import {
|
|
166
171
|
assertPublicHttpUrl,
|
|
167
172
|
isIpAddressLiteral,
|
|
173
|
+
normalizeUrlHostname,
|
|
168
174
|
UnsafeOutboundUrlError,
|
|
169
175
|
} from '../../../shared_libs/security/outbound-url-policy';
|
|
170
176
|
import type {
|
|
@@ -475,6 +481,7 @@ async function probeHarnessOnce(
|
|
|
475
481
|
*/
|
|
476
482
|
const RUNTIME_API_TIMEOUT_MS = 30_000;
|
|
477
483
|
const RUNTIME_API_PLAY_RUN_TIMEOUT_MS = 75_000;
|
|
484
|
+
const RUNTIME_API_EGRESS_FETCH_TIMEOUT_MS = 180_000;
|
|
478
485
|
const RUNTIME_API_INTEGRATION_EXECUTE_TIMEOUT_MS = 180_000;
|
|
479
486
|
const RUNTIME_API_RETRY_DELAYS_MS = [
|
|
480
487
|
250, 750, 1500, 3000, 5000, 10000,
|
|
@@ -488,9 +495,11 @@ async function fetchRuntimeApi(
|
|
|
488
495
|
const timeoutMs =
|
|
489
496
|
path === '/api/v2/plays/run'
|
|
490
497
|
? RUNTIME_API_PLAY_RUN_TIMEOUT_MS
|
|
491
|
-
:
|
|
492
|
-
?
|
|
493
|
-
:
|
|
498
|
+
: path === '/api/v2/plays/internal/egress-fetch'
|
|
499
|
+
? RUNTIME_API_EGRESS_FETCH_TIMEOUT_MS
|
|
500
|
+
: /^\/api\/v2\/integrations\/[^/]+\/execute$/.test(path)
|
|
501
|
+
? RUNTIME_API_INTEGRATION_EXECUTE_TIMEOUT_MS
|
|
502
|
+
: RUNTIME_API_TIMEOUT_MS;
|
|
494
503
|
const controller = new AbortController();
|
|
495
504
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
496
505
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
@@ -2313,6 +2322,20 @@ type WorkerFetchResponse = {
|
|
|
2313
2322
|
json: unknown | null;
|
|
2314
2323
|
};
|
|
2315
2324
|
|
|
2325
|
+
type WorkerFetchBodyDescriptor =
|
|
2326
|
+
| { kind: 'none' }
|
|
2327
|
+
| { kind: 'string'; value: string }
|
|
2328
|
+
| { kind: 'urlSearchParams'; value: string };
|
|
2329
|
+
|
|
2330
|
+
type RuntimeEgressFetchPayload = {
|
|
2331
|
+
url: string;
|
|
2332
|
+
method: string;
|
|
2333
|
+
headers: Record<string, string>;
|
|
2334
|
+
body: WorkerFetchBodyDescriptor;
|
|
2335
|
+
redirect?: RequestInit['redirect'];
|
|
2336
|
+
sensitiveHeaders: string[];
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2316
2339
|
function normalizeFetchHeaders(
|
|
2317
2340
|
headers: RequestInit['headers'],
|
|
2318
2341
|
): Record<string, string> {
|
|
@@ -2344,6 +2367,19 @@ function fetchBodyIdentity(body: RequestInit['body']): string | null {
|
|
|
2344
2367
|
);
|
|
2345
2368
|
}
|
|
2346
2369
|
|
|
2370
|
+
function fetchBodyDescriptor(
|
|
2371
|
+
body: RequestInit['body'],
|
|
2372
|
+
): WorkerFetchBodyDescriptor {
|
|
2373
|
+
if (body === undefined || body === null) return { kind: 'none' };
|
|
2374
|
+
if (typeof body === 'string') return { kind: 'string', value: body };
|
|
2375
|
+
if (body instanceof URLSearchParams) {
|
|
2376
|
+
return { kind: 'urlSearchParams', value: body.toString() };
|
|
2377
|
+
}
|
|
2378
|
+
throw new Error(
|
|
2379
|
+
'ctx.fetch(...) in the Workers backend only supports string or URLSearchParams request bodies for egress.',
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2347
2383
|
function parseFetchJsonOrNull(bodyText: string): unknown | null {
|
|
2348
2384
|
if (!bodyText.trim()) return null;
|
|
2349
2385
|
try {
|
|
@@ -2371,7 +2407,7 @@ async function safeWorkerPublicFetch(
|
|
|
2371
2407
|
!allowedOrigins.has(url.origin)
|
|
2372
2408
|
) {
|
|
2373
2409
|
throw new UnsafeOutboundUrlError(
|
|
2374
|
-
'workers_edge
|
|
2410
|
+
'workers_edge direct fetch is reserved for public IP literals and Deepline runtime origins. Public hostnames must go through the Deepline egress endpoint with safeOutboundFetch.',
|
|
2375
2411
|
);
|
|
2376
2412
|
}
|
|
2377
2413
|
return fetch(url, nextInit);
|
|
@@ -2379,6 +2415,121 @@ async function safeWorkerPublicFetch(
|
|
|
2379
2415
|
});
|
|
2380
2416
|
}
|
|
2381
2417
|
|
|
2418
|
+
function isDeeplineControlledWorkerFetchHost(hostname: string): boolean {
|
|
2419
|
+
const normalized = normalizeUrlHostname(hostname);
|
|
2420
|
+
return (
|
|
2421
|
+
normalized === 'deepline.com' ||
|
|
2422
|
+
normalized.endsWith('.deepline.com') ||
|
|
2423
|
+
normalized === 'deeplinedeveloper.com' ||
|
|
2424
|
+
normalized.endsWith('.deeplinedeveloper.com')
|
|
2425
|
+
);
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function shouldUseRuntimeEgressFetch(
|
|
2429
|
+
input: string | URL,
|
|
2430
|
+
allowedOrigins: Iterable<string>,
|
|
2431
|
+
): boolean {
|
|
2432
|
+
const url = assertPublicHttpUrl(input);
|
|
2433
|
+
const allowedOriginSet = new Set(allowedOrigins);
|
|
2434
|
+
if (allowedOriginSet.has(url.origin)) return false;
|
|
2435
|
+
if (isIpAddressLiteral(url.hostname)) return false;
|
|
2436
|
+
if (isDeeplineControlledWorkerFetchHost(url.hostname)) {
|
|
2437
|
+
throw new UnsafeOutboundUrlError(
|
|
2438
|
+
'ctx.fetch cannot call a non-runtime Deepline-owned origin from workers_edge. Use the run runtime origin or a Deepline integration tool.',
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
return true;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
function shouldPaceWorkerEgressFetch(
|
|
2445
|
+
input: string | URL,
|
|
2446
|
+
allowedOrigins: Iterable<string>,
|
|
2447
|
+
): boolean {
|
|
2448
|
+
const url = assertPublicHttpUrl(input);
|
|
2449
|
+
return !new Set(allowedOrigins).has(url.origin);
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
function isWorkerFetchResponse(value: unknown): value is WorkerFetchResponse {
|
|
2453
|
+
if (!isRecord(value)) return false;
|
|
2454
|
+
return (
|
|
2455
|
+
typeof value.ok === 'boolean' &&
|
|
2456
|
+
typeof value.status === 'number' &&
|
|
2457
|
+
typeof value.statusText === 'string' &&
|
|
2458
|
+
typeof value.url === 'string' &&
|
|
2459
|
+
isRecord(value.headers) &&
|
|
2460
|
+
Object.values(value.headers).every((entry) => typeof entry === 'string') &&
|
|
2461
|
+
typeof value.bodyText === 'string' &&
|
|
2462
|
+
'json' in value
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
async function postRuntimeEgressFetch(
|
|
2467
|
+
req: RunRequest,
|
|
2468
|
+
payload: RuntimeEgressFetchPayload,
|
|
2469
|
+
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
2470
|
+
onRetryAttempt?: () => void,
|
|
2471
|
+
): Promise<WorkerFetchResponse> {
|
|
2472
|
+
let lastError: Error | null = null;
|
|
2473
|
+
for (
|
|
2474
|
+
let attempt = 1;
|
|
2475
|
+
attempt <= WORKER_TOOL_RATE_LIMIT_MAX_ATTEMPTS;
|
|
2476
|
+
attempt += 1
|
|
2477
|
+
) {
|
|
2478
|
+
const response = await fetchRuntimeApi(
|
|
2479
|
+
req.baseUrl,
|
|
2480
|
+
'/api/v2/plays/internal/egress-fetch',
|
|
2481
|
+
{
|
|
2482
|
+
method: 'POST',
|
|
2483
|
+
headers: {
|
|
2484
|
+
authorization: `Bearer ${req.executorToken}`,
|
|
2485
|
+
'content-type': 'application/json',
|
|
2486
|
+
},
|
|
2487
|
+
body: JSON.stringify(payload),
|
|
2488
|
+
},
|
|
2489
|
+
);
|
|
2490
|
+
const bodyText = await response.text();
|
|
2491
|
+
let parsed: unknown = null;
|
|
2492
|
+
try {
|
|
2493
|
+
parsed = bodyText.trim() ? JSON.parse(bodyText) : null;
|
|
2494
|
+
} catch {
|
|
2495
|
+
parsed = null;
|
|
2496
|
+
}
|
|
2497
|
+
if (response.ok) {
|
|
2498
|
+
if (!isWorkerFetchResponse(parsed)) {
|
|
2499
|
+
throw new Error(
|
|
2500
|
+
'ctx.fetch egress returned an invalid response payload.',
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
return parsed;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
const message =
|
|
2507
|
+
isRecord(parsed) && typeof parsed.error === 'string'
|
|
2508
|
+
? parsed.error
|
|
2509
|
+
: bodyText || `HTTP ${response.status}`;
|
|
2510
|
+
lastError = new Error(`ctx.fetch egress failed: ${message}`);
|
|
2511
|
+
if (response.status !== 429) {
|
|
2512
|
+
throw lastError;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
const retryAfterSeconds = Number(response.headers.get('retry-after'));
|
|
2516
|
+
const retryAfterMs =
|
|
2517
|
+
Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
|
|
2518
|
+
? Math.ceil(retryAfterSeconds * 1000)
|
|
2519
|
+
: 1_000;
|
|
2520
|
+
onProviderBackpressure?.(retryAfterMs);
|
|
2521
|
+
if (attempt >= WORKER_TOOL_RATE_LIMIT_MAX_ATTEMPTS) {
|
|
2522
|
+
throw lastError;
|
|
2523
|
+
}
|
|
2524
|
+
onRetryAttempt?.();
|
|
2525
|
+
await sleepWorkerMs(
|
|
2526
|
+
Math.min(5_000, Math.max(retryAfterMs, attempt * 1000)),
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
throw lastError ?? new Error('ctx.fetch egress failed before execution.');
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2382
2533
|
function normalizeAllowedWorkerFetchOrigin(rawUrl: string): string | null {
|
|
2383
2534
|
try {
|
|
2384
2535
|
return assertPublicHttpUrl(rawUrl).origin;
|
|
@@ -3172,6 +3323,8 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
|
|
|
3172
3323
|
return (toolId: string) => {
|
|
3173
3324
|
const normalized = String(toolId || '').trim();
|
|
3174
3325
|
if (!normalized) return Promise.resolve(null);
|
|
3326
|
+
const builtin = resolveBuiltinPacing(normalized);
|
|
3327
|
+
if (builtin) return Promise.resolve(builtin);
|
|
3175
3328
|
const cached = cache.get(normalized);
|
|
3176
3329
|
if (cached) return cached;
|
|
3177
3330
|
const promise = (async () => {
|
|
@@ -5406,26 +5559,77 @@ function createMinimalWorkerCtx(
|
|
|
5406
5559
|
...normalizeFetchHeaders(init.headers),
|
|
5407
5560
|
...secretHeaders,
|
|
5408
5561
|
};
|
|
5409
|
-
const
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
allowedOrigins
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5562
|
+
const allowedOrigins = getAllowedWorkerFetchOrigins(req);
|
|
5563
|
+
const useRuntimeEgressFetch = shouldUseRuntimeEgressFetch(
|
|
5564
|
+
url,
|
|
5565
|
+
allowedOrigins,
|
|
5566
|
+
);
|
|
5567
|
+
const egressSlot = shouldPaceWorkerEgressFetch(url, allowedOrigins)
|
|
5568
|
+
? await governor.acquireToolSlot(CTX_FETCH_EGRESS_TOOL_ID, {
|
|
5569
|
+
signal: abortSignal,
|
|
5570
|
+
})
|
|
5571
|
+
: null;
|
|
5572
|
+
try {
|
|
5573
|
+
if (useRuntimeEgressFetch) {
|
|
5574
|
+
// Customer-authored plays are allowed to call arbitrary public HTTP(S)
|
|
5575
|
+
// endpoints. The thing we must not do is let an isolate perform DNS
|
|
5576
|
+
// resolution for attacker-controlled hostnames and then trust only
|
|
5577
|
+
// pre-fetch URL validation: the original pentest issue was exactly
|
|
5578
|
+
// redirect/DNS-rebinding SSRF into private metadata or Deepline
|
|
5579
|
+
// runtime addresses. Cloudflare Worker fetch does not expose a
|
|
5580
|
+
// connect-time DNS validation hook, so arbitrary hostnames go through
|
|
5581
|
+
// the Deepline Node egress broker, which uses safeOutboundFetch for
|
|
5582
|
+
// every connection and redirect.
|
|
5583
|
+
//
|
|
5584
|
+
// The egressSlot is acquired before the broker call: ctx.fetch and
|
|
5585
|
+
// generic_http_request share the same generic_http pacing bucket, so
|
|
5586
|
+
// switching syntax cannot bypass arbitrary-HTTP egress limits.
|
|
5587
|
+
// Direct Worker fetch below is still limited to runtime origins and
|
|
5588
|
+
// IP literals; public IP literals also consume this slot because
|
|
5589
|
+
// they are customer egress.
|
|
5590
|
+
const egressResponse = await postRuntimeEgressFetch(
|
|
5591
|
+
req,
|
|
5592
|
+
{
|
|
5593
|
+
url,
|
|
5594
|
+
method,
|
|
5595
|
+
headers,
|
|
5596
|
+
body: fetchBodyDescriptor(init.body),
|
|
5597
|
+
redirect: init.redirect,
|
|
5598
|
+
sensitiveHeaders: Object.keys(secretHeaderMarkers),
|
|
5599
|
+
},
|
|
5600
|
+
(retryAfterMs) =>
|
|
5601
|
+
governor.reportProviderBackpressure({
|
|
5602
|
+
provider: CTX_FETCH_EGRESS_PROVIDER,
|
|
5603
|
+
retryAfterMs,
|
|
5604
|
+
}),
|
|
5605
|
+
() => governor.chargeBudget('retry'),
|
|
5606
|
+
);
|
|
5607
|
+
assertNotAborted(abortSignal);
|
|
5608
|
+
return secretRedactor.redact(egressResponse);
|
|
5609
|
+
}
|
|
5610
|
+
const fetchInit = { ...init, headers };
|
|
5611
|
+
delete fetchInit.auth;
|
|
5612
|
+
const response = await safeWorkerPublicFetch(url, fetchInit, {
|
|
5613
|
+
allowedOrigins,
|
|
5614
|
+
sensitiveHeaders: Object.keys(secretHeaderMarkers),
|
|
5615
|
+
});
|
|
5616
|
+
assertNotAborted(abortSignal);
|
|
5617
|
+
const bodyText = await response.text();
|
|
5618
|
+
const redactedBodyText = secretRedactor.redactString(bodyText);
|
|
5619
|
+
return {
|
|
5620
|
+
ok: response.ok,
|
|
5621
|
+
status: response.status,
|
|
5622
|
+
statusText: response.statusText,
|
|
5623
|
+
url: response.url,
|
|
5624
|
+
headers: secretRedactor.redact(
|
|
5625
|
+
Object.fromEntries(response.headers.entries()),
|
|
5626
|
+
) as Record<string, string>,
|
|
5627
|
+
bodyText: redactedBodyText,
|
|
5628
|
+
json: secretRedactor.redact(parseFetchJsonOrNull(bodyText)),
|
|
5629
|
+
};
|
|
5630
|
+
} finally {
|
|
5631
|
+
egressSlot?.release();
|
|
5632
|
+
}
|
|
5429
5633
|
});
|
|
5430
5634
|
},
|
|
5431
5635
|
secrets: {
|
|
@@ -101,10 +101,10 @@ export const SDK_RELEASE = {
|
|
|
101
101
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
102
102
|
// the SDK enrich generator's one-second stale policy.
|
|
103
103
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
104
|
-
version: '0.1.
|
|
104
|
+
version: '0.1.134',
|
|
105
105
|
apiContract: '2026-06-dataset-column-cell-stale-hard-cutover',
|
|
106
106
|
supportPolicy: {
|
|
107
|
-
latest: '0.1.
|
|
107
|
+
latest: '0.1.134',
|
|
108
108
|
minimumSupported: '0.1.53',
|
|
109
109
|
deprecatedBelow: '0.1.53',
|
|
110
110
|
commandMinimumSupported: [
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PacingRule } from './governor/rate-state-backend';
|
|
2
|
+
|
|
3
|
+
export interface BuiltinPacingPolicy {
|
|
4
|
+
readonly provider: string;
|
|
5
|
+
readonly rules: readonly PacingRule[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Shared arbitrary-HTTP egress lane.
|
|
10
|
+
*
|
|
11
|
+
* Deepline exposes a first-class `generic_http_request` integration tool, and
|
|
12
|
+
* play authors also have the more ergonomic `ctx.fetch(...)` primitive. Those
|
|
13
|
+
* are two user-facing spellings of the same risk: customer-controlled outbound
|
|
14
|
+
* HTTP to arbitrary public endpoints.
|
|
15
|
+
*
|
|
16
|
+
* Do not create a separate `ctx.fetch` provider bucket. Both surfaces must pace
|
|
17
|
+
* through the existing `generic_http` provider identity so a play cannot bypass
|
|
18
|
+
* generic HTTP limits by switching syntax.
|
|
19
|
+
*/
|
|
20
|
+
export const GENERIC_HTTP_REQUEST_TOOL_ID = 'generic_http_request';
|
|
21
|
+
export const GENERIC_HTTP_PROVIDER = 'generic_http';
|
|
22
|
+
export const GENERIC_HTTP_PROVIDER_SHARED_RULE_ID =
|
|
23
|
+
'generic_http:provider_shared';
|
|
24
|
+
export const GENERIC_HTTP_PROVIDER_SHARED_REQUESTS_PER_SECOND = 25;
|
|
25
|
+
|
|
26
|
+
export const CTX_FETCH_EGRESS_TOOL_ID = GENERIC_HTTP_REQUEST_TOOL_ID;
|
|
27
|
+
export const CTX_FETCH_EGRESS_PROVIDER = GENERIC_HTTP_PROVIDER;
|
|
28
|
+
export const CTX_FETCH_EGRESS_PACING_RULE = {
|
|
29
|
+
ruleId: GENERIC_HTTP_PROVIDER_SHARED_RULE_ID,
|
|
30
|
+
requestsPerWindow: GENERIC_HTTP_PROVIDER_SHARED_REQUESTS_PER_SECOND,
|
|
31
|
+
windowMs: 1000,
|
|
32
|
+
maxConcurrency: null,
|
|
33
|
+
} satisfies PacingRule;
|
|
34
|
+
|
|
35
|
+
export const CTX_FETCH_EGRESS_PACING = {
|
|
36
|
+
provider: CTX_FETCH_EGRESS_PROVIDER,
|
|
37
|
+
rules: [CTX_FETCH_EGRESS_PACING_RULE],
|
|
38
|
+
} satisfies BuiltinPacingPolicy;
|
|
39
|
+
|
|
40
|
+
export function resolveBuiltinPacing(
|
|
41
|
+
toolId: string,
|
|
42
|
+
): { provider: string; rules: PacingRule[] } | null {
|
|
43
|
+
if (toolId !== CTX_FETCH_EGRESS_TOOL_ID) return null;
|
|
44
|
+
return {
|
|
45
|
+
provider: CTX_FETCH_EGRESS_PACING.provider,
|
|
46
|
+
rules: [...CTX_FETCH_EGRESS_PACING.rules],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -35,6 +35,10 @@ import {
|
|
|
35
35
|
type PacingResolver,
|
|
36
36
|
type PlayExecutionGovernor,
|
|
37
37
|
} from './governor/governor';
|
|
38
|
+
import {
|
|
39
|
+
CTX_FETCH_EGRESS_TOOL_ID,
|
|
40
|
+
resolveBuiltinPacing,
|
|
41
|
+
} from './builtin-pacing';
|
|
38
42
|
import { InMemoryRateStateBackend } from './governor/in-memory-rate-state-backend';
|
|
39
43
|
import type { PacingRule } from './governor/rate-state-backend';
|
|
40
44
|
import {
|
|
@@ -642,6 +646,8 @@ function createPacingResolver(
|
|
|
642
646
|
getToolQueueHints: ContextOptions['getToolQueueHints'],
|
|
643
647
|
): PacingResolver {
|
|
644
648
|
return async (toolId: string) => {
|
|
649
|
+
const builtin = resolveBuiltinPacing(toolId);
|
|
650
|
+
if (builtin) return builtin;
|
|
645
651
|
const hints = getToolQueueHints ? await getToolQueueHints(toolId) : [];
|
|
646
652
|
if (hints.length === 0) {
|
|
647
653
|
return null;
|
|
@@ -4147,71 +4153,89 @@ export class PlayContextImpl {
|
|
|
4147
4153
|
}
|
|
4148
4154
|
}
|
|
4149
4155
|
|
|
4156
|
+
// ctx.fetch is arbitrary customer-directed egress. Pace it through
|
|
4157
|
+
// the same generic_http_request lane as the explicit Generic HTTP
|
|
4158
|
+
// integration tool so switching syntax cannot bypass per-org limits.
|
|
4159
|
+
const egressSlot = await this.governor.acquireToolSlot(
|
|
4160
|
+
CTX_FETCH_EGRESS_TOOL_ID,
|
|
4161
|
+
);
|
|
4150
4162
|
let response: Response | null = null;
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4163
|
+
try {
|
|
4164
|
+
const canRetryTransport = ['GET', 'HEAD', 'OPTIONS'].includes(
|
|
4165
|
+
method,
|
|
4166
|
+
);
|
|
4167
|
+
const { safePublicFetch } = await loadSafeFetch();
|
|
4168
|
+
for (
|
|
4169
|
+
let attempt = 1;
|
|
4170
|
+
attempt <= FETCH_TRANSPORT_MAX_ATTEMPTS;
|
|
4171
|
+
attempt += 1
|
|
4172
|
+
) {
|
|
4173
|
+
try {
|
|
4174
|
+
response = await safePublicFetch(url, fetchInit, {
|
|
4175
|
+
fetchImpl: this.#options.fetchImpl,
|
|
4176
|
+
sensitiveHeaders: Object.keys(secretHeaderMarkers),
|
|
4177
|
+
});
|
|
4178
|
+
break;
|
|
4179
|
+
} catch (error) {
|
|
4180
|
+
if (isUnsafeOutboundUrlError(error)) {
|
|
4181
|
+
throw error;
|
|
4182
|
+
}
|
|
4183
|
+
const message =
|
|
4184
|
+
error instanceof Error ? error.message : String(error);
|
|
4185
|
+
if (
|
|
4186
|
+
canRetryTransport &&
|
|
4187
|
+
attempt < FETCH_TRANSPORT_MAX_ATTEMPTS
|
|
4188
|
+
) {
|
|
4189
|
+
this.log(
|
|
4190
|
+
`ctx.fetch(${method} ${url}) transport failed on attempt ${attempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}; retrying: ${message}`,
|
|
4191
|
+
);
|
|
4192
|
+
await new Promise((resolve) =>
|
|
4193
|
+
setTimeout(
|
|
4194
|
+
resolve,
|
|
4195
|
+
FETCH_TRANSPORT_RETRY_DELAY_MS * attempt,
|
|
4196
|
+
),
|
|
4197
|
+
);
|
|
4198
|
+
continue;
|
|
4199
|
+
}
|
|
4200
|
+
throw new Error(
|
|
4201
|
+
`ctx.fetch(${method} ${url}) failed on attempt ${attempt}/${FETCH_TRANSPORT_MAX_ATTEMPTS}: ${message}`,
|
|
4176
4202
|
);
|
|
4177
|
-
continue;
|
|
4178
4203
|
}
|
|
4204
|
+
}
|
|
4205
|
+
if (!response) {
|
|
4179
4206
|
throw new Error(
|
|
4180
|
-
`ctx.fetch(${method} ${url}) failed
|
|
4207
|
+
`ctx.fetch(${method} ${url}) failed before receiving a response.`,
|
|
4181
4208
|
);
|
|
4182
4209
|
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
headers: this.secretRedactor.redact(
|
|
4197
|
-
Object.fromEntries(response.headers.entries()),
|
|
4198
|
-
) as Record<string, string>,
|
|
4199
|
-
bodyText: redactedBodyText,
|
|
4200
|
-
json: this.secretRedactor.redact(parseJsonOrNull(bodyText)),
|
|
4201
|
-
};
|
|
4210
|
+
const bodyText = await response.text();
|
|
4211
|
+
const redactedBodyText = this.secretRedactor.redactString(bodyText);
|
|
4212
|
+
const output: PlayFetchResponse = {
|
|
4213
|
+
ok: response.ok,
|
|
4214
|
+
status: response.status,
|
|
4215
|
+
statusText: response.statusText,
|
|
4216
|
+
url: response.url,
|
|
4217
|
+
headers: this.secretRedactor.redact(
|
|
4218
|
+
Object.fromEntries(response.headers.entries()),
|
|
4219
|
+
) as Record<string, string>,
|
|
4220
|
+
bodyText: redactedBodyText,
|
|
4221
|
+
json: this.secretRedactor.redact(parseJsonOrNull(bodyText)),
|
|
4222
|
+
};
|
|
4202
4223
|
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4224
|
+
this.checkpoint.resolvedBoundaries = {
|
|
4225
|
+
...(this.checkpoint.resolvedBoundaries ?? {}),
|
|
4226
|
+
[boundaryId]: {
|
|
4227
|
+
kind: 'fetch',
|
|
4228
|
+
url,
|
|
4229
|
+
method,
|
|
4230
|
+
output,
|
|
4231
|
+
completedAt: Date.now(),
|
|
4232
|
+
},
|
|
4233
|
+
};
|
|
4234
|
+
this.#options.onBatchComplete?.(this.checkpoint);
|
|
4235
|
+
return output;
|
|
4236
|
+
} finally {
|
|
4237
|
+
egressSlot.release();
|
|
4238
|
+
}
|
|
4215
4239
|
},
|
|
4216
4240
|
markSkipped: (output) => {
|
|
4217
4241
|
this.log(
|
|
@@ -15,6 +15,7 @@ type NodeSafeFetchOptions = {
|
|
|
15
15
|
maxRedirects?: number;
|
|
16
16
|
maxResponseBytes?: number;
|
|
17
17
|
sensitiveHeaders?: Iterable<string>;
|
|
18
|
+
validateUrl?: (url: URL) => void;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
type PreparedBody = {
|
|
@@ -24,6 +25,24 @@ type PreparedBody = {
|
|
|
24
25
|
|
|
25
26
|
export { UnsafeOutboundUrlError };
|
|
26
27
|
|
|
28
|
+
const NULL_BODY_STATUS_CODES = new Set([204, 205, 304]);
|
|
29
|
+
const TRANSPORT_OWNED_REQUEST_HEADERS = new Set([
|
|
30
|
+
'connection',
|
|
31
|
+
'content-length',
|
|
32
|
+
'host',
|
|
33
|
+
'keep-alive',
|
|
34
|
+
'proxy-authenticate',
|
|
35
|
+
'proxy-authorization',
|
|
36
|
+
'te',
|
|
37
|
+
'trailer',
|
|
38
|
+
'transfer-encoding',
|
|
39
|
+
'upgrade',
|
|
40
|
+
'forwarded',
|
|
41
|
+
'x-forwarded-for',
|
|
42
|
+
'x-forwarded-host',
|
|
43
|
+
'x-forwarded-proto',
|
|
44
|
+
]);
|
|
45
|
+
|
|
27
46
|
function cloneHeaders(headers: RequestInit['headers']): Headers {
|
|
28
47
|
return new Headers(headers);
|
|
29
48
|
}
|
|
@@ -32,14 +51,34 @@ function deleteHeader(headers: Headers, name: string) {
|
|
|
32
51
|
if (headers.has(name)) headers.delete(name);
|
|
33
52
|
}
|
|
34
53
|
|
|
54
|
+
function removeTransportOwnedHeaders(headers: Headers): void {
|
|
55
|
+
for (const header of TRANSPORT_OWNED_REQUEST_HEADERS) {
|
|
56
|
+
deleteHeader(headers, header);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function withContentLength(
|
|
61
|
+
body: Buffer | string | undefined,
|
|
62
|
+
headers: Headers,
|
|
63
|
+
): PreparedBody {
|
|
64
|
+
removeTransportOwnedHeaders(headers);
|
|
65
|
+
if (body !== undefined) {
|
|
66
|
+
headers.set(
|
|
67
|
+
'content-length',
|
|
68
|
+
String(typeof body === 'string' ? Buffer.byteLength(body) : body.length),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return { body, headers };
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
async function prepareBody(init: RequestInit): Promise<PreparedBody> {
|
|
36
75
|
const headers = cloneHeaders(init.headers);
|
|
37
76
|
const body = init.body;
|
|
38
77
|
if (body === undefined || body === null) {
|
|
39
|
-
return
|
|
78
|
+
return withContentLength(undefined, headers);
|
|
40
79
|
}
|
|
41
80
|
if (typeof body === 'string') {
|
|
42
|
-
return
|
|
81
|
+
return withContentLength(body, headers);
|
|
43
82
|
}
|
|
44
83
|
if (body instanceof URLSearchParams) {
|
|
45
84
|
if (!headers.has('content-type')) {
|
|
@@ -48,19 +87,19 @@ async function prepareBody(init: RequestInit): Promise<PreparedBody> {
|
|
|
48
87
|
'application/x-www-form-urlencoded;charset=UTF-8',
|
|
49
88
|
);
|
|
50
89
|
}
|
|
51
|
-
return
|
|
90
|
+
return withContentLength(body.toString(), headers);
|
|
52
91
|
}
|
|
53
92
|
if (body instanceof ArrayBuffer) {
|
|
54
|
-
return
|
|
93
|
+
return withContentLength(Buffer.from(body), headers);
|
|
55
94
|
}
|
|
56
95
|
if (ArrayBuffer.isView(body)) {
|
|
57
|
-
return
|
|
58
|
-
|
|
96
|
+
return withContentLength(
|
|
97
|
+
Buffer.from(body.buffer, body.byteOffset, body.byteLength),
|
|
59
98
|
headers,
|
|
60
|
-
|
|
99
|
+
);
|
|
61
100
|
}
|
|
62
101
|
if (typeof Blob !== 'undefined' && body instanceof Blob) {
|
|
63
|
-
return
|
|
102
|
+
return withContentLength(Buffer.from(await body.arrayBuffer()), headers);
|
|
64
103
|
}
|
|
65
104
|
|
|
66
105
|
throw new TypeError(
|
|
@@ -80,6 +119,10 @@ function validateResolvedAddress(address: string) {
|
|
|
80
119
|
}
|
|
81
120
|
}
|
|
82
121
|
|
|
122
|
+
function responseMustNotHaveBody(method: string, status: number): boolean {
|
|
123
|
+
return method === 'HEAD' || NULL_BODY_STATUS_CODES.has(status);
|
|
124
|
+
}
|
|
125
|
+
|
|
83
126
|
const safeLookup: LookupFunction = (hostname, options, callback) => {
|
|
84
127
|
const lookupOptions: dns.LookupAllOptions = {
|
|
85
128
|
all: true,
|
|
@@ -129,27 +172,29 @@ function createRequest(
|
|
|
129
172
|
): Promise<Response> {
|
|
130
173
|
return new Promise((resolve, reject) => {
|
|
131
174
|
const transport = url.protocol === 'https:' ? https : http;
|
|
175
|
+
const method = String(init.method ?? 'GET').toUpperCase();
|
|
132
176
|
const request = transport.request(
|
|
133
177
|
url,
|
|
134
178
|
{
|
|
135
|
-
method
|
|
179
|
+
method,
|
|
136
180
|
headers: headersToRecord(prepared.headers),
|
|
137
181
|
agent: false,
|
|
138
182
|
lookup: safeLookup,
|
|
139
183
|
signal: init.signal ?? undefined,
|
|
140
184
|
},
|
|
141
185
|
(response) => {
|
|
186
|
+
const status = response.statusCode ?? 0;
|
|
187
|
+
const noBodyResponse = responseMustNotHaveBody(method, status);
|
|
142
188
|
const contentLength = Number(response.headers['content-length']);
|
|
143
189
|
if (
|
|
190
|
+
!noBodyResponse &&
|
|
144
191
|
maxResponseBytes !== undefined &&
|
|
145
192
|
Number.isFinite(contentLength) &&
|
|
146
193
|
contentLength > maxResponseBytes
|
|
147
194
|
) {
|
|
148
195
|
response.resume();
|
|
149
196
|
reject(
|
|
150
|
-
new Error(
|
|
151
|
-
`Response body exceeds ${maxResponseBytes} byte limit.`,
|
|
152
|
-
),
|
|
197
|
+
new Error(`Response body exceeds ${maxResponseBytes} byte limit.`),
|
|
153
198
|
);
|
|
154
199
|
return;
|
|
155
200
|
}
|
|
@@ -170,16 +215,21 @@ function createRequest(
|
|
|
170
215
|
);
|
|
171
216
|
return;
|
|
172
217
|
}
|
|
173
|
-
|
|
218
|
+
if (!noBodyResponse) {
|
|
219
|
+
chunks.push(buffer);
|
|
220
|
+
}
|
|
174
221
|
});
|
|
175
222
|
response.on('error', reject);
|
|
176
223
|
response.on('end', () => {
|
|
177
224
|
resolve(
|
|
178
|
-
new Response(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
225
|
+
new Response(
|
|
226
|
+
noBodyResponse ? null : Buffer.concat(chunks),
|
|
227
|
+
{
|
|
228
|
+
status,
|
|
229
|
+
statusText: response.statusMessage,
|
|
230
|
+
headers: response.headers as HeadersInit,
|
|
231
|
+
},
|
|
232
|
+
),
|
|
183
233
|
);
|
|
184
234
|
});
|
|
185
235
|
},
|
|
@@ -241,6 +291,7 @@ export async function safeOutboundFetch(
|
|
|
241
291
|
const maxRedirects = options.maxRedirects ?? 10;
|
|
242
292
|
const maxResponseBytes = options.maxResponseBytes;
|
|
243
293
|
const sensitiveHeaders = options.sensitiveHeaders ?? [];
|
|
294
|
+
const validateUrl = options.validateUrl;
|
|
244
295
|
let currentUrl = assertPublicHttpUrl(input);
|
|
245
296
|
let currentInit: RequestInit = { ...init, redirect: 'manual' };
|
|
246
297
|
|
|
@@ -250,6 +301,7 @@ export async function safeOutboundFetch(
|
|
|
250
301
|
redirectCount += 1
|
|
251
302
|
) {
|
|
252
303
|
const hostname = normalizeUrlHostname(currentUrl.hostname);
|
|
304
|
+
validateUrl?.(currentUrl);
|
|
253
305
|
if (isBlockedIpAddress(hostname)) {
|
|
254
306
|
throw new UnsafeOutboundUrlError(
|
|
255
307
|
`Target host "${hostname}" is not allowed.`,
|
package/dist/cli/index.js
CHANGED
|
@@ -413,10 +413,10 @@ var SDK_RELEASE = {
|
|
|
413
413
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
414
414
|
// the SDK enrich generator's one-second stale policy.
|
|
415
415
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
416
|
-
version: "0.1.
|
|
416
|
+
version: "0.1.134",
|
|
417
417
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
418
418
|
supportPolicy: {
|
|
419
|
-
latest: "0.1.
|
|
419
|
+
latest: "0.1.134",
|
|
420
420
|
minimumSupported: "0.1.53",
|
|
421
421
|
deprecatedBelow: "0.1.53",
|
|
422
422
|
commandMinimumSupported: [
|
|
@@ -18916,38 +18916,46 @@ function renderSecret(secret) {
|
|
|
18916
18916
|
const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
|
|
18917
18917
|
return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
|
|
18918
18918
|
}
|
|
18919
|
-
async function readHiddenLine(prompt) {
|
|
18920
|
-
|
|
18919
|
+
async function readHiddenLine(prompt, streams = {}) {
|
|
18920
|
+
const inputStream = streams.input ?? import_node_process.stdin;
|
|
18921
|
+
const outputStream = streams.output ?? import_node_process.stdout;
|
|
18922
|
+
if (!inputStream.isTTY || !outputStream.isTTY) {
|
|
18921
18923
|
throw new Error(
|
|
18922
18924
|
"Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
|
|
18923
18925
|
);
|
|
18924
18926
|
}
|
|
18925
|
-
|
|
18926
|
-
const previousRawMode =
|
|
18927
|
-
|
|
18927
|
+
outputStream.write(prompt);
|
|
18928
|
+
const previousRawMode = inputStream.isRaw;
|
|
18929
|
+
const wasPaused = typeof inputStream.isPaused === "function" ? inputStream.isPaused() : false;
|
|
18930
|
+
if (typeof inputStream.setRawMode === "function") {
|
|
18931
|
+
inputStream.setRawMode(true);
|
|
18932
|
+
}
|
|
18928
18933
|
let value = "";
|
|
18929
|
-
|
|
18934
|
+
inputStream.resume();
|
|
18930
18935
|
return await new Promise((resolve13, reject) => {
|
|
18931
18936
|
let settled = false;
|
|
18932
18937
|
const cleanup = () => {
|
|
18933
|
-
|
|
18934
|
-
|
|
18935
|
-
|
|
18936
|
-
if (typeof
|
|
18937
|
-
|
|
18938
|
+
inputStream.off("data", onData);
|
|
18939
|
+
inputStream.off("end", onEnd);
|
|
18940
|
+
inputStream.off("error", onError);
|
|
18941
|
+
if (typeof inputStream.setRawMode === "function") {
|
|
18942
|
+
inputStream.setRawMode(previousRawMode);
|
|
18943
|
+
}
|
|
18944
|
+
if (wasPaused) {
|
|
18945
|
+
inputStream.pause();
|
|
18938
18946
|
}
|
|
18939
18947
|
};
|
|
18940
18948
|
const finish = (line) => {
|
|
18941
18949
|
if (settled) return;
|
|
18942
18950
|
settled = true;
|
|
18943
|
-
|
|
18951
|
+
outputStream.write("\n");
|
|
18944
18952
|
cleanup();
|
|
18945
18953
|
resolve13(line);
|
|
18946
18954
|
};
|
|
18947
18955
|
const fail = (error) => {
|
|
18948
18956
|
if (settled) return;
|
|
18949
18957
|
settled = true;
|
|
18950
|
-
|
|
18958
|
+
outputStream.write("\n");
|
|
18951
18959
|
cleanup();
|
|
18952
18960
|
reject(error);
|
|
18953
18961
|
};
|
|
@@ -18979,9 +18987,9 @@ async function readHiddenLine(prompt) {
|
|
|
18979
18987
|
};
|
|
18980
18988
|
const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
|
|
18981
18989
|
const onError = (error) => fail(error);
|
|
18982
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
18990
|
+
inputStream.on("data", onData);
|
|
18991
|
+
inputStream.once("end", onEnd);
|
|
18992
|
+
inputStream.once("error", onError);
|
|
18985
18993
|
if (hiddenInputBuffer) {
|
|
18986
18994
|
const buffered = hiddenInputBuffer;
|
|
18987
18995
|
hiddenInputBuffer = "";
|
package/dist/cli/index.mjs
CHANGED
|
@@ -390,10 +390,10 @@ var SDK_RELEASE = {
|
|
|
390
390
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
391
391
|
// the SDK enrich generator's one-second stale policy.
|
|
392
392
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
393
|
-
version: "0.1.
|
|
393
|
+
version: "0.1.134",
|
|
394
394
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
395
395
|
supportPolicy: {
|
|
396
|
-
latest: "0.1.
|
|
396
|
+
latest: "0.1.134",
|
|
397
397
|
minimumSupported: "0.1.53",
|
|
398
398
|
deprecatedBelow: "0.1.53",
|
|
399
399
|
commandMinimumSupported: [
|
|
@@ -18932,38 +18932,46 @@ function renderSecret(secret) {
|
|
|
18932
18932
|
const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
|
|
18933
18933
|
return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
|
|
18934
18934
|
}
|
|
18935
|
-
async function readHiddenLine(prompt) {
|
|
18936
|
-
|
|
18935
|
+
async function readHiddenLine(prompt, streams = {}) {
|
|
18936
|
+
const inputStream = streams.input ?? input;
|
|
18937
|
+
const outputStream = streams.output ?? output;
|
|
18938
|
+
if (!inputStream.isTTY || !outputStream.isTTY) {
|
|
18937
18939
|
throw new Error(
|
|
18938
18940
|
"Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
|
|
18939
18941
|
);
|
|
18940
18942
|
}
|
|
18941
|
-
|
|
18942
|
-
const previousRawMode =
|
|
18943
|
-
|
|
18943
|
+
outputStream.write(prompt);
|
|
18944
|
+
const previousRawMode = inputStream.isRaw;
|
|
18945
|
+
const wasPaused = typeof inputStream.isPaused === "function" ? inputStream.isPaused() : false;
|
|
18946
|
+
if (typeof inputStream.setRawMode === "function") {
|
|
18947
|
+
inputStream.setRawMode(true);
|
|
18948
|
+
}
|
|
18944
18949
|
let value = "";
|
|
18945
|
-
|
|
18950
|
+
inputStream.resume();
|
|
18946
18951
|
return await new Promise((resolve13, reject) => {
|
|
18947
18952
|
let settled = false;
|
|
18948
18953
|
const cleanup = () => {
|
|
18949
|
-
|
|
18950
|
-
|
|
18951
|
-
|
|
18952
|
-
if (typeof
|
|
18953
|
-
|
|
18954
|
+
inputStream.off("data", onData);
|
|
18955
|
+
inputStream.off("end", onEnd);
|
|
18956
|
+
inputStream.off("error", onError);
|
|
18957
|
+
if (typeof inputStream.setRawMode === "function") {
|
|
18958
|
+
inputStream.setRawMode(previousRawMode);
|
|
18959
|
+
}
|
|
18960
|
+
if (wasPaused) {
|
|
18961
|
+
inputStream.pause();
|
|
18954
18962
|
}
|
|
18955
18963
|
};
|
|
18956
18964
|
const finish = (line) => {
|
|
18957
18965
|
if (settled) return;
|
|
18958
18966
|
settled = true;
|
|
18959
|
-
|
|
18967
|
+
outputStream.write("\n");
|
|
18960
18968
|
cleanup();
|
|
18961
18969
|
resolve13(line);
|
|
18962
18970
|
};
|
|
18963
18971
|
const fail = (error) => {
|
|
18964
18972
|
if (settled) return;
|
|
18965
18973
|
settled = true;
|
|
18966
|
-
|
|
18974
|
+
outputStream.write("\n");
|
|
18967
18975
|
cleanup();
|
|
18968
18976
|
reject(error);
|
|
18969
18977
|
};
|
|
@@ -18995,9 +19003,9 @@ async function readHiddenLine(prompt) {
|
|
|
18995
19003
|
};
|
|
18996
19004
|
const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
|
|
18997
19005
|
const onError = (error) => fail(error);
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
|
|
19006
|
+
inputStream.on("data", onData);
|
|
19007
|
+
inputStream.once("end", onEnd);
|
|
19008
|
+
inputStream.once("error", onError);
|
|
19001
19009
|
if (hiddenInputBuffer) {
|
|
19002
19010
|
const buffered = hiddenInputBuffer;
|
|
19003
19011
|
hiddenInputBuffer = "";
|
package/dist/index.js
CHANGED
|
@@ -284,10 +284,10 @@ var SDK_RELEASE = {
|
|
|
284
284
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
285
285
|
// the SDK enrich generator's one-second stale policy.
|
|
286
286
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
287
|
-
version: "0.1.
|
|
287
|
+
version: "0.1.134",
|
|
288
288
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
289
289
|
supportPolicy: {
|
|
290
|
-
latest: "0.1.
|
|
290
|
+
latest: "0.1.134",
|
|
291
291
|
minimumSupported: "0.1.53",
|
|
292
292
|
deprecatedBelow: "0.1.53",
|
|
293
293
|
commandMinimumSupported: [
|
package/dist/index.mjs
CHANGED
|
@@ -206,10 +206,10 @@ var SDK_RELEASE = {
|
|
|
206
206
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
207
207
|
// the SDK enrich generator's one-second stale policy.
|
|
208
208
|
// 0.1.110 ships authored V2 prebuilts and required top-level play descriptions.
|
|
209
|
-
version: "0.1.
|
|
209
|
+
version: "0.1.134",
|
|
210
210
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
211
211
|
supportPolicy: {
|
|
212
|
-
latest: "0.1.
|
|
212
|
+
latest: "0.1.134",
|
|
213
213
|
minimumSupported: "0.1.53",
|
|
214
214
|
deprecatedBelow: "0.1.53",
|
|
215
215
|
commandMinimumSupported: [
|