deepline 0.1.132 → 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 +68 -23
- package/dist/cli/index.mjs +68 -23
- 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 = "";
|
|
@@ -21798,6 +21806,23 @@ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
|
|
|
21798
21806
|
}
|
|
21799
21807
|
|
|
21800
21808
|
// src/cli/commands/update.ts
|
|
21809
|
+
var NPM_SDK_INSTALL_COMMON_FLAGS = ["--no-audit", "--no-fund"];
|
|
21810
|
+
var NPM_SDK_GLOBAL_INSTALL_FLAGS = [
|
|
21811
|
+
"--no-audit",
|
|
21812
|
+
"--no-fund",
|
|
21813
|
+
"--allow-scripts=esbuild"
|
|
21814
|
+
];
|
|
21815
|
+
var NPM_SDK_SIDECAR_PACKAGE_JSON = `${JSON.stringify(
|
|
21816
|
+
{
|
|
21817
|
+
private: true,
|
|
21818
|
+
allowScripts: {
|
|
21819
|
+
esbuild: true
|
|
21820
|
+
}
|
|
21821
|
+
},
|
|
21822
|
+
null,
|
|
21823
|
+
2
|
|
21824
|
+
)}
|
|
21825
|
+
`;
|
|
21801
21826
|
function posixShellQuote(value) {
|
|
21802
21827
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21803
21828
|
}
|
|
@@ -21815,6 +21840,16 @@ function buildSourceUpdateCommand(sourceRoot) {
|
|
|
21815
21840
|
const cdCommand = process.platform === "win32" ? `cd /d ${quotedRoot}` : `cd ${quotedRoot}`;
|
|
21816
21841
|
return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
|
|
21817
21842
|
}
|
|
21843
|
+
function buildSidecarProjectConfigCommand(versionDir, nodeBin) {
|
|
21844
|
+
const script = [
|
|
21845
|
+
"const fs=require('node:fs');",
|
|
21846
|
+
"const path=require('node:path');",
|
|
21847
|
+
"const dir=process.argv[1];",
|
|
21848
|
+
"fs.mkdirSync(dir,{recursive:true});",
|
|
21849
|
+
`fs.writeFileSync(path.join(dir,'package.json'),${JSON.stringify(NPM_SDK_SIDECAR_PACKAGE_JSON)});`
|
|
21850
|
+
].join("");
|
|
21851
|
+
return `${shellQuote4(nodeBin)} -e ${shellQuote4(script)} ${shellQuote4(versionDir)}`;
|
|
21852
|
+
}
|
|
21818
21853
|
function sidecarStateDir(input2) {
|
|
21819
21854
|
const scope = input2.env.DEEPLINE_CONFIG_SCOPE?.trim();
|
|
21820
21855
|
if (!scope || scope.includes("/") || scope.includes("\\")) {
|
|
@@ -21851,7 +21886,8 @@ function resolvePythonSidecarUpdatePlan(options) {
|
|
|
21851
21886
|
);
|
|
21852
21887
|
const packageSpec = options.packageSpec || "deepline@latest";
|
|
21853
21888
|
const npmCommand = "npm";
|
|
21854
|
-
const
|
|
21889
|
+
const versionDir = (0, import_node_path18.join)(stateDir, "versions", "<version>");
|
|
21890
|
+
const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote4(versionDir)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote4).join(" ")} ${shellQuote4(packageSpec)}`;
|
|
21855
21891
|
return {
|
|
21856
21892
|
kind: "python-sidecar",
|
|
21857
21893
|
stateDir,
|
|
@@ -21918,7 +21954,14 @@ function resolveUpdatePlan(options = {}) {
|
|
|
21918
21954
|
const command = "npm";
|
|
21919
21955
|
const packageSpec = options.packageSpec || "deepline@latest";
|
|
21920
21956
|
const installPrefix = entrypoint ? inferNpmGlobalPrefixFromEntrypoint(entrypoint) : null;
|
|
21921
|
-
const args = installPrefix ? [
|
|
21957
|
+
const args = installPrefix ? [
|
|
21958
|
+
"install",
|
|
21959
|
+
"-g",
|
|
21960
|
+
"--prefix",
|
|
21961
|
+
installPrefix,
|
|
21962
|
+
...NPM_SDK_GLOBAL_INSTALL_FLAGS,
|
|
21963
|
+
packageSpec
|
|
21964
|
+
] : ["install", "-g", ...NPM_SDK_GLOBAL_INSTALL_FLAGS, packageSpec];
|
|
21922
21965
|
return {
|
|
21923
21966
|
kind: "npm-global",
|
|
21924
21967
|
command,
|
|
@@ -22093,7 +22136,7 @@ async function runPythonSidecarUpdatePlan(plan) {
|
|
|
22093
22136
|
);
|
|
22094
22137
|
(0, import_node_fs15.rmSync)(tempDir, { recursive: true, force: true });
|
|
22095
22138
|
(0, import_node_fs15.mkdirSync)(tempDir, { recursive: true });
|
|
22096
|
-
(0, import_node_fs15.writeFileSync)((0, import_node_path18.join)(tempDir, "package.json"),
|
|
22139
|
+
(0, import_node_fs15.writeFileSync)((0, import_node_path18.join)(tempDir, "package.json"), NPM_SDK_SIDECAR_PACKAGE_JSON);
|
|
22097
22140
|
const env = {
|
|
22098
22141
|
...process.env,
|
|
22099
22142
|
PATH: `${(0, import_node_path18.dirname)(plan.nodeBin)}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`
|
|
@@ -22104,8 +22147,7 @@ async function runPythonSidecarUpdatePlan(plan) {
|
|
|
22104
22147
|
"install",
|
|
22105
22148
|
"--prefix",
|
|
22106
22149
|
tempDir,
|
|
22107
|
-
|
|
22108
|
-
"--no-fund",
|
|
22150
|
+
...NPM_SDK_INSTALL_COMMON_FLAGS,
|
|
22109
22151
|
plan.packageSpec
|
|
22110
22152
|
],
|
|
22111
22153
|
env
|
|
@@ -22705,6 +22747,9 @@ function shouldDeferSkillsSyncForCommand() {
|
|
|
22705
22747
|
const args = process.argv.slice(2);
|
|
22706
22748
|
const command = args[0];
|
|
22707
22749
|
const subcommand = args[1];
|
|
22750
|
+
if (command === "tools" && ["list", "search", "grep", "describe", "get"].includes(subcommand ?? "")) {
|
|
22751
|
+
return true;
|
|
22752
|
+
}
|
|
22708
22753
|
return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
|
|
22709
22754
|
}
|
|
22710
22755
|
function isLegacyNoopInvocation() {
|
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 = "";
|
|
@@ -21829,6 +21837,23 @@ async function syncSdkSkillsIfNeeded(baseUrl, options = {}) {
|
|
|
21829
21837
|
}
|
|
21830
21838
|
|
|
21831
21839
|
// src/cli/commands/update.ts
|
|
21840
|
+
var NPM_SDK_INSTALL_COMMON_FLAGS = ["--no-audit", "--no-fund"];
|
|
21841
|
+
var NPM_SDK_GLOBAL_INSTALL_FLAGS = [
|
|
21842
|
+
"--no-audit",
|
|
21843
|
+
"--no-fund",
|
|
21844
|
+
"--allow-scripts=esbuild"
|
|
21845
|
+
];
|
|
21846
|
+
var NPM_SDK_SIDECAR_PACKAGE_JSON = `${JSON.stringify(
|
|
21847
|
+
{
|
|
21848
|
+
private: true,
|
|
21849
|
+
allowScripts: {
|
|
21850
|
+
esbuild: true
|
|
21851
|
+
}
|
|
21852
|
+
},
|
|
21853
|
+
null,
|
|
21854
|
+
2
|
|
21855
|
+
)}
|
|
21856
|
+
`;
|
|
21832
21857
|
function posixShellQuote(value) {
|
|
21833
21858
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21834
21859
|
}
|
|
@@ -21846,6 +21871,16 @@ function buildSourceUpdateCommand(sourceRoot) {
|
|
|
21846
21871
|
const cdCommand = process.platform === "win32" ? `cd /d ${quotedRoot}` : `cd ${quotedRoot}`;
|
|
21847
21872
|
return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
|
|
21848
21873
|
}
|
|
21874
|
+
function buildSidecarProjectConfigCommand(versionDir, nodeBin) {
|
|
21875
|
+
const script = [
|
|
21876
|
+
"const fs=require('node:fs');",
|
|
21877
|
+
"const path=require('node:path');",
|
|
21878
|
+
"const dir=process.argv[1];",
|
|
21879
|
+
"fs.mkdirSync(dir,{recursive:true});",
|
|
21880
|
+
`fs.writeFileSync(path.join(dir,'package.json'),${JSON.stringify(NPM_SDK_SIDECAR_PACKAGE_JSON)});`
|
|
21881
|
+
].join("");
|
|
21882
|
+
return `${shellQuote4(nodeBin)} -e ${shellQuote4(script)} ${shellQuote4(versionDir)}`;
|
|
21883
|
+
}
|
|
21849
21884
|
function sidecarStateDir(input2) {
|
|
21850
21885
|
const scope = input2.env.DEEPLINE_CONFIG_SCOPE?.trim();
|
|
21851
21886
|
if (!scope || scope.includes("/") || scope.includes("\\")) {
|
|
@@ -21882,7 +21917,8 @@ function resolvePythonSidecarUpdatePlan(options) {
|
|
|
21882
21917
|
);
|
|
21883
21918
|
const packageSpec = options.packageSpec || "deepline@latest";
|
|
21884
21919
|
const npmCommand = "npm";
|
|
21885
|
-
const
|
|
21920
|
+
const versionDir = join13(stateDir, "versions", "<version>");
|
|
21921
|
+
const manualCommand = `${buildSidecarProjectConfigCommand(versionDir, nodeBin)} && ${npmCommand} install --prefix ${shellQuote4(versionDir)} ${NPM_SDK_INSTALL_COMMON_FLAGS.map(shellQuote4).join(" ")} ${shellQuote4(packageSpec)}`;
|
|
21886
21922
|
return {
|
|
21887
21923
|
kind: "python-sidecar",
|
|
21888
21924
|
stateDir,
|
|
@@ -21949,7 +21985,14 @@ function resolveUpdatePlan(options = {}) {
|
|
|
21949
21985
|
const command = "npm";
|
|
21950
21986
|
const packageSpec = options.packageSpec || "deepline@latest";
|
|
21951
21987
|
const installPrefix = entrypoint ? inferNpmGlobalPrefixFromEntrypoint(entrypoint) : null;
|
|
21952
|
-
const args = installPrefix ? [
|
|
21988
|
+
const args = installPrefix ? [
|
|
21989
|
+
"install",
|
|
21990
|
+
"-g",
|
|
21991
|
+
"--prefix",
|
|
21992
|
+
installPrefix,
|
|
21993
|
+
...NPM_SDK_GLOBAL_INSTALL_FLAGS,
|
|
21994
|
+
packageSpec
|
|
21995
|
+
] : ["install", "-g", ...NPM_SDK_GLOBAL_INSTALL_FLAGS, packageSpec];
|
|
21953
21996
|
return {
|
|
21954
21997
|
kind: "npm-global",
|
|
21955
21998
|
command,
|
|
@@ -22124,7 +22167,7 @@ async function runPythonSidecarUpdatePlan(plan) {
|
|
|
22124
22167
|
);
|
|
22125
22168
|
rmSync3(tempDir, { recursive: true, force: true });
|
|
22126
22169
|
mkdirSync9(tempDir, { recursive: true });
|
|
22127
|
-
writeFileSync14(join13(tempDir, "package.json"),
|
|
22170
|
+
writeFileSync14(join13(tempDir, "package.json"), NPM_SDK_SIDECAR_PACKAGE_JSON);
|
|
22128
22171
|
const env = {
|
|
22129
22172
|
...process.env,
|
|
22130
22173
|
PATH: `${dirname11(plan.nodeBin)}${process.platform === "win32" ? ";" : ":"}${process.env.PATH ?? ""}`
|
|
@@ -22135,8 +22178,7 @@ async function runPythonSidecarUpdatePlan(plan) {
|
|
|
22135
22178
|
"install",
|
|
22136
22179
|
"--prefix",
|
|
22137
22180
|
tempDir,
|
|
22138
|
-
|
|
22139
|
-
"--no-fund",
|
|
22181
|
+
...NPM_SDK_INSTALL_COMMON_FLAGS,
|
|
22140
22182
|
plan.packageSpec
|
|
22141
22183
|
],
|
|
22142
22184
|
env
|
|
@@ -22736,6 +22778,9 @@ function shouldDeferSkillsSyncForCommand() {
|
|
|
22736
22778
|
const args = process.argv.slice(2);
|
|
22737
22779
|
const command = args[0];
|
|
22738
22780
|
const subcommand = args[1];
|
|
22781
|
+
if (command === "tools" && ["list", "search", "grep", "describe", "get"].includes(subcommand ?? "")) {
|
|
22782
|
+
return true;
|
|
22783
|
+
}
|
|
22739
22784
|
return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
|
|
22740
22785
|
}
|
|
22741
22786
|
function isLegacyNoopInvocation() {
|
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: [
|