deepline 0.1.226 → 0.1.228
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 +88 -47
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/ledger-event-batches.ts +81 -0
- package/dist/bundling-sources/sdk/src/client.ts +24 -2
- package/dist/bundling-sources/sdk/src/play.ts +7 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +315 -16
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +114 -21
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +3 -9
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +5 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +194 -27
- package/dist/bundling-sources/shared_libs/play-runtime/transport-error-diagnostics.ts +63 -0
- package/dist/cli/index.js +78 -11
- package/dist/cli/index.mjs +78 -11
- package/dist/index.d.mts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +14 -5
- package/dist/index.mjs +14 -5
- package/package.json +1 -1
|
@@ -140,6 +140,11 @@ import type {
|
|
|
140
140
|
PlayRunLedgerStepProgress,
|
|
141
141
|
PlayRunLedgerStepStatus,
|
|
142
142
|
} from '../../../shared_libs/play-runtime/run-ledger';
|
|
143
|
+
import {
|
|
144
|
+
MAX_LEDGER_LOG_LINE_LENGTH,
|
|
145
|
+
MAX_LEDGER_LOG_LINES_PER_EVENT,
|
|
146
|
+
} from '../../../shared_libs/play-runtime/ledger-safe-payload';
|
|
147
|
+
import { partitionWorkerLedgerEvents } from './runtime/ledger-event-batches';
|
|
143
148
|
import type { PlayHarnessRpc } from '../../play-harness-worker/src/rpc-types';
|
|
144
149
|
import {
|
|
145
150
|
createCsvDatasetHandle,
|
|
@@ -2539,6 +2544,7 @@ type WorkerStepProgram = {
|
|
|
2539
2544
|
|
|
2540
2545
|
type WorkerMapOptions = {
|
|
2541
2546
|
description?: string;
|
|
2547
|
+
mode?: 'upsert' | 'net_new';
|
|
2542
2548
|
concurrency?: number;
|
|
2543
2549
|
key?:
|
|
2544
2550
|
| string
|
|
@@ -4870,9 +4876,18 @@ function createMinimalWorkerCtx(
|
|
|
4870
4876
|
runId: req.runId,
|
|
4871
4877
|
inputOffset: 0,
|
|
4872
4878
|
attemptLeaseTtlMs: runtimeSheetAttemptLeaseTtlMs(req),
|
|
4879
|
+
...(opts?.mode ? { mode: opts.mode } : {}),
|
|
4873
4880
|
...mapWriteAttempt,
|
|
4874
4881
|
});
|
|
4875
4882
|
const mapWriteVersion = mapReservation?.writeVersion ?? null;
|
|
4883
|
+
const netNewRowKeys =
|
|
4884
|
+
opts?.mode === 'net_new'
|
|
4885
|
+
? new Set(
|
|
4886
|
+
(mapReservation?.pendingRows ?? [])
|
|
4887
|
+
.map((row) => resolveMapRowOutcomeKey(row))
|
|
4888
|
+
.filter((key): key is string => Boolean(key)),
|
|
4889
|
+
)
|
|
4890
|
+
: null;
|
|
4876
4891
|
|
|
4877
4892
|
let totalRowsWritten = 0;
|
|
4878
4893
|
let datasetLifecycleRegistered = false;
|
|
@@ -4896,7 +4911,7 @@ function createMinimalWorkerCtx(
|
|
|
4896
4911
|
chunkIndex,
|
|
4897
4912
|
});
|
|
4898
4913
|
const keyStartedAt = nowMs();
|
|
4899
|
-
|
|
4914
|
+
let chunkEntries = chunkRows.map((row, localIndex) => {
|
|
4900
4915
|
const absoluteIndex = baseOffset + chunkStart + localIndex;
|
|
4901
4916
|
const rowKey = resolveRowKey(
|
|
4902
4917
|
row,
|
|
@@ -4906,6 +4921,11 @@ function createMinimalWorkerCtx(
|
|
|
4906
4921
|
);
|
|
4907
4922
|
return { row, absoluteIndex, rowKey };
|
|
4908
4923
|
});
|
|
4924
|
+
if (netNewRowKeys) {
|
|
4925
|
+
chunkEntries = chunkEntries.filter(({ rowKey }) =>
|
|
4926
|
+
netNewRowKeys.has(rowKey),
|
|
4927
|
+
);
|
|
4928
|
+
}
|
|
4909
4929
|
recordRunnerPerfTrace({
|
|
4910
4930
|
req,
|
|
4911
4931
|
phase: 'runner.map_chunk.keys',
|
|
@@ -4968,7 +4988,10 @@ function createMinimalWorkerCtx(
|
|
|
4968
4988
|
},
|
|
4969
4989
|
});
|
|
4970
4990
|
mapLatencyProfile.recordPhase('prepare_rows', nowMs() - prepareStartedAt);
|
|
4971
|
-
|
|
4991
|
+
// net_new reserves the entire source before chunk execution, so its
|
|
4992
|
+
// progress describes the admitted subset rather than the source CSV.
|
|
4993
|
+
const progressTotalRows =
|
|
4994
|
+
netNewRowKeys?.size ?? rowCountHint ?? chunkRows.length;
|
|
4972
4995
|
const preparedCompletedRows = Math.min(
|
|
4973
4996
|
progressTotalRows,
|
|
4974
4997
|
totalRowsWritten,
|
|
@@ -5023,15 +5046,8 @@ function createMinimalWorkerCtx(
|
|
|
5023
5046
|
({ rowKey }) => !preparedKeys.has(rowKey),
|
|
5024
5047
|
);
|
|
5025
5048
|
if (missingPreparedRows.length > 0) {
|
|
5026
|
-
const rowKeySamples = missingPreparedRows
|
|
5027
|
-
.map(({ rowKey }) => rowKey)
|
|
5028
|
-
.slice(0, MAP_ROW_FAILURE_SAMPLE_LIMIT);
|
|
5029
5049
|
throw new Error(
|
|
5030
|
-
`ctx.dataset("${name}")
|
|
5031
|
-
`Every input row must be claimed, completed, or blocked before execution starts.` +
|
|
5032
|
-
(rowKeySamples.length > 0
|
|
5033
|
-
? ` Missing row keys: ${rowKeySamples.join(', ')}`
|
|
5034
|
-
: ''),
|
|
5050
|
+
`ctx.dataset("${name}") preparation omitted ${missingPreparedRows.length} row disposition(s).`,
|
|
5035
5051
|
);
|
|
5036
5052
|
}
|
|
5037
5053
|
const rowsToExecuteEntries = chunkEntries.filter(({ rowKey }) =>
|
|
@@ -5986,9 +6002,8 @@ function createMinimalWorkerCtx(
|
|
|
5986
6002
|
// dataset (their recoverable state lives in the runtime sheet).
|
|
5987
6003
|
if (key && executedRow) resultByKey.set(key, executedRow);
|
|
5988
6004
|
}
|
|
5989
|
-
const outEntries =
|
|
5990
|
-
.map((
|
|
5991
|
-
const entry = chunkEntries[index]!;
|
|
6005
|
+
const outEntries = chunkEntries
|
|
6006
|
+
.map((entry) => {
|
|
5992
6007
|
const row = resultByKey.get(entry.rowKey);
|
|
5993
6008
|
return row
|
|
5994
6009
|
? {
|
|
@@ -6531,11 +6546,11 @@ function createMinimalWorkerCtx(
|
|
|
6531
6546
|
}
|
|
6532
6547
|
enqueueMapProgressUpdate({
|
|
6533
6548
|
completed: totalRowsWritten,
|
|
6534
|
-
total: rowCountHint ?? undefined,
|
|
6549
|
+
total: netNewRowKeys?.size ?? rowCountHint ?? undefined,
|
|
6535
6550
|
...(totalRowsFailed > 0 ? { failed: totalRowsFailed } : {}),
|
|
6536
6551
|
message: formatMapProgressMessage(
|
|
6537
6552
|
totalRowsWritten,
|
|
6538
|
-
rowCountHint ?? undefined,
|
|
6553
|
+
netNewRowKeys?.size ?? rowCountHint ?? undefined,
|
|
6539
6554
|
),
|
|
6540
6555
|
});
|
|
6541
6556
|
if (previewRows.length < WORKER_DATASET_PREVIEW_ROWS) {
|
|
@@ -7768,7 +7783,10 @@ async function executeRunRequest(
|
|
|
7768
7783
|
let ledgerFlushQueueDepth = 0;
|
|
7769
7784
|
|
|
7770
7785
|
const appendRunLogLine = (line: string) => {
|
|
7771
|
-
const trimmed = redactSecretsFromLogString(line.trim())
|
|
7786
|
+
const trimmed = redactSecretsFromLogString(line.trim()).slice(
|
|
7787
|
+
0,
|
|
7788
|
+
MAX_LEDGER_LOG_LINE_LENGTH,
|
|
7789
|
+
);
|
|
7772
7790
|
if (!trimmed) return;
|
|
7773
7791
|
workBudgetMeter.count('log');
|
|
7774
7792
|
totalEmittedLogLines += 1;
|
|
@@ -7851,17 +7869,27 @@ async function executeRunRequest(
|
|
|
7851
7869
|
pendingLedgerEvents = [];
|
|
7852
7870
|
|
|
7853
7871
|
if (pendingRunLogLines.length > 0) {
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7872
|
+
const channelOffset = totalEmittedLogLines - pendingRunLogLines.length;
|
|
7873
|
+
for (
|
|
7874
|
+
let start = 0;
|
|
7875
|
+
start < pendingRunLogLines.length;
|
|
7876
|
+
start += MAX_LEDGER_LOG_LINES_PER_EVENT
|
|
7877
|
+
) {
|
|
7878
|
+
events.push({
|
|
7879
|
+
type: 'log.appended',
|
|
7880
|
+
runId: req.runId,
|
|
7881
|
+
source: 'worker',
|
|
7882
|
+
occurredAt,
|
|
7883
|
+
lines: pendingRunLogLines.slice(
|
|
7884
|
+
start,
|
|
7885
|
+
start + MAX_LEDGER_LOG_LINES_PER_EVENT,
|
|
7886
|
+
),
|
|
7887
|
+
// Positional cursor: pendingRunLogLines always holds the LAST
|
|
7888
|
+
// pending lines emitted on this channel, so the offset of its first
|
|
7889
|
+
// line is total-emitted minus pending length.
|
|
7890
|
+
channelOffset: channelOffset + start,
|
|
7891
|
+
});
|
|
7892
|
+
}
|
|
7865
7893
|
pendingRunLogLines = [];
|
|
7866
7894
|
}
|
|
7867
7895
|
|
|
@@ -7946,19 +7974,26 @@ async function executeRunRequest(
|
|
|
7946
7974
|
ledgerFlushInFlight = ledgerFlushInFlight
|
|
7947
7975
|
.catch(() => undefined)
|
|
7948
7976
|
.then(async () => {
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7977
|
+
const batches = partitionWorkerLedgerEvents(events);
|
|
7978
|
+
for (let index = 0; index < batches.length; index += 1) {
|
|
7979
|
+
try {
|
|
7980
|
+
await postRuntimeApi(req.baseUrl, req.executorToken, {
|
|
7981
|
+
action: 'append_run_events',
|
|
7982
|
+
playId: req.runId,
|
|
7983
|
+
events: batches[index]!,
|
|
7984
|
+
});
|
|
7985
|
+
} catch {
|
|
7986
|
+
pendingLedgerEvents = [
|
|
7987
|
+
...batches.slice(index).flat(),
|
|
7988
|
+
...pendingLedgerEvents,
|
|
7989
|
+
];
|
|
7990
|
+
throw new Error('runtime run-ledger append failed');
|
|
7991
|
+
}
|
|
7960
7992
|
}
|
|
7961
7993
|
})
|
|
7994
|
+
.finally(() => {
|
|
7995
|
+
ledgerFlushQueueDepth = Math.max(0, ledgerFlushQueueDepth - 1);
|
|
7996
|
+
})
|
|
7962
7997
|
.catch(() => undefined);
|
|
7963
7998
|
return force ? ledgerFlushInFlight : Promise.resolve();
|
|
7964
7999
|
};
|
|
@@ -7978,15 +8013,21 @@ async function executeRunRequest(
|
|
|
7978
8013
|
]);
|
|
7979
8014
|
const events = [...drainPendingLedgerEvents(now), terminalEvent];
|
|
7980
8015
|
if (events.length === 0) return;
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
8016
|
+
const batches = partitionWorkerLedgerEvents(events);
|
|
8017
|
+
for (let index = 0; index < batches.length; index += 1) {
|
|
8018
|
+
try {
|
|
8019
|
+
await postRuntimeApi(req.baseUrl, req.executorToken, {
|
|
8020
|
+
action: 'append_run_events',
|
|
8021
|
+
playId: req.runId,
|
|
8022
|
+
events: batches[index]!,
|
|
8023
|
+
});
|
|
8024
|
+
} catch (error) {
|
|
8025
|
+
pendingLedgerEvents = [
|
|
8026
|
+
...batches.slice(index).flat(),
|
|
8027
|
+
...pendingLedgerEvents,
|
|
8028
|
+
];
|
|
8029
|
+
throw error;
|
|
8030
|
+
}
|
|
7990
8031
|
}
|
|
7991
8032
|
};
|
|
7992
8033
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAX_LEDGER_LOG_LINE_LENGTH,
|
|
3
|
+
MAX_LEDGER_LOG_LINES_PER_EVENT,
|
|
4
|
+
} from '../../../../shared_libs/play-runtime/ledger-safe-payload';
|
|
5
|
+
import type { PlayRunLedgerEvent } from '../../../../shared_libs/play-runtime/run-ledger';
|
|
6
|
+
|
|
7
|
+
/** Keep a Worker-to-runtime append comfortably below the API and Convex limits. */
|
|
8
|
+
export const WORKER_LEDGER_EVENT_BATCH_MAX_BYTES = 128 * 1024;
|
|
9
|
+
export const WORKER_LEDGER_EVENTS_PER_APPEND_MAX = 100;
|
|
10
|
+
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
|
|
13
|
+
function encodedJsonBytes(value: unknown): number {
|
|
14
|
+
return encoder.encode(JSON.stringify(value)).length;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function splitLogEvent(
|
|
18
|
+
event: Extract<PlayRunLedgerEvent, { type: 'log.appended' }>,
|
|
19
|
+
): PlayRunLedgerEvent[] {
|
|
20
|
+
const events: PlayRunLedgerEvent[] = [];
|
|
21
|
+
const lines = event.lines.map((line) =>
|
|
22
|
+
line.slice(0, MAX_LEDGER_LOG_LINE_LENGTH),
|
|
23
|
+
);
|
|
24
|
+
let start = 0;
|
|
25
|
+
while (start < lines.length) {
|
|
26
|
+
let end = start;
|
|
27
|
+
let bytes = encodedJsonBytes({ ...event, lines: [] });
|
|
28
|
+
while (end < lines.length && end - start < MAX_LEDGER_LOG_LINES_PER_EVENT) {
|
|
29
|
+
const lineBytes = encodedJsonBytes(lines[end]!);
|
|
30
|
+
if (
|
|
31
|
+
end > start &&
|
|
32
|
+
bytes + lineBytes + 1 > WORKER_LEDGER_EVENT_BATCH_MAX_BYTES
|
|
33
|
+
) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
bytes += lineBytes + 1;
|
|
37
|
+
end += 1;
|
|
38
|
+
}
|
|
39
|
+
events.push({
|
|
40
|
+
...event,
|
|
41
|
+
lines: lines.slice(start, end),
|
|
42
|
+
...(typeof event.channelOffset === 'number'
|
|
43
|
+
? { channelOffset: event.channelOffset + start }
|
|
44
|
+
: {}),
|
|
45
|
+
});
|
|
46
|
+
start = end;
|
|
47
|
+
}
|
|
48
|
+
return events;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Partition ledger delivery without dropping positional log lines. The worker
|
|
53
|
+
* retries only unacknowledged batches, so each batch must be independently
|
|
54
|
+
* valid for the app-runtime and Convex boundaries.
|
|
55
|
+
*/
|
|
56
|
+
export function partitionWorkerLedgerEvents(
|
|
57
|
+
events: readonly PlayRunLedgerEvent[],
|
|
58
|
+
): PlayRunLedgerEvent[][] {
|
|
59
|
+
const batches: PlayRunLedgerEvent[][] = [];
|
|
60
|
+
let batch: PlayRunLedgerEvent[] = [];
|
|
61
|
+
let batchBytes = 2;
|
|
62
|
+
|
|
63
|
+
for (const event of events.flatMap((event) =>
|
|
64
|
+
event.type === 'log.appended' ? splitLogEvent(event) : [event],
|
|
65
|
+
)) {
|
|
66
|
+
const eventBytes = encodedJsonBytes(event);
|
|
67
|
+
if (
|
|
68
|
+
batch.length > 0 &&
|
|
69
|
+
(batch.length >= WORKER_LEDGER_EVENTS_PER_APPEND_MAX ||
|
|
70
|
+
batchBytes + eventBytes + 1 > WORKER_LEDGER_EVENT_BATCH_MAX_BYTES)
|
|
71
|
+
) {
|
|
72
|
+
batches.push(batch);
|
|
73
|
+
batch = [];
|
|
74
|
+
batchBytes = 2;
|
|
75
|
+
}
|
|
76
|
+
batch.push(event);
|
|
77
|
+
batchBytes += eventBytes + 1;
|
|
78
|
+
}
|
|
79
|
+
if (batch.length > 0) batches.push(batch);
|
|
80
|
+
return batches;
|
|
81
|
+
}
|
|
@@ -243,8 +243,30 @@ type ExecuteToolRawOptions = {
|
|
|
243
243
|
maxRetries?: number;
|
|
244
244
|
};
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
/**
|
|
247
|
+
* The server launches the Apify run, polls it for `payload.timeoutMs`, then
|
|
248
|
+
* reads its dataset before replying. Keep the client request alive through
|
|
249
|
+
* that final hand-off so a pollable run id is never lost to the SDK default
|
|
250
|
+
* timeout.
|
|
251
|
+
*/
|
|
252
|
+
const APIFY_SYNC_DEFAULT_TIMEOUT_MS = 300_000;
|
|
253
|
+
const APIFY_SYNC_RESPONSE_GRACE_MS = 90_000;
|
|
254
|
+
|
|
255
|
+
function resolveToolExecuteTimeoutMs(
|
|
256
|
+
toolId: string,
|
|
257
|
+
input: Record<string, unknown>,
|
|
258
|
+
): number | undefined {
|
|
247
259
|
const normalized = toolId.trim().toLowerCase();
|
|
260
|
+
if (normalized === 'apify_run_actor_sync') {
|
|
261
|
+
const requestedTimeoutMs = input.timeoutMs ?? APIFY_SYNC_DEFAULT_TIMEOUT_MS;
|
|
262
|
+
if (
|
|
263
|
+
typeof requestedTimeoutMs === 'number' &&
|
|
264
|
+
Number.isFinite(requestedTimeoutMs) &&
|
|
265
|
+
requestedTimeoutMs > 0
|
|
266
|
+
) {
|
|
267
|
+
return Math.floor(requestedTimeoutMs) + APIFY_SYNC_RESPONSE_GRACE_MS;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
248
270
|
return normalized === 'deeplineagent' ||
|
|
249
271
|
normalized === 'deeplineagent_deeplineagent' ||
|
|
250
272
|
normalized === 'ai_inference' ||
|
|
@@ -1417,7 +1439,7 @@ export class DeeplineClient {
|
|
|
1417
1439
|
headers,
|
|
1418
1440
|
{
|
|
1419
1441
|
forbiddenAsApiError: true,
|
|
1420
|
-
timeout: options?.timeout ?? resolveToolExecuteTimeoutMs(toolId),
|
|
1442
|
+
timeout: options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, input),
|
|
1421
1443
|
maxRetries: options?.maxRetries ?? 0,
|
|
1422
1444
|
exactUrlOnly: true,
|
|
1423
1445
|
},
|
|
@@ -161,7 +161,7 @@ export interface PlayCallOptions {
|
|
|
161
161
|
* sqlListeners: [
|
|
162
162
|
* {
|
|
163
163
|
* id: 'jobs',
|
|
164
|
-
* tool: '
|
|
164
|
+
* tool: 'deepline_native.company_radar',
|
|
165
165
|
* stream: 'company_job_openings',
|
|
166
166
|
* operations: ['INSERT'],
|
|
167
167
|
* },
|
|
@@ -237,7 +237,7 @@ export type SqlListenerWhere = {
|
|
|
237
237
|
export type SqlListenerDeclaration = {
|
|
238
238
|
/** Short id unique inside this play. Deepline stores it as playName.id. */
|
|
239
239
|
id: string;
|
|
240
|
-
/** Modeled monitor tool id, for example "
|
|
240
|
+
/** Modeled monitor tool id, for example "deepline_native.company_radar". */
|
|
241
241
|
tool: string;
|
|
242
242
|
/** Stream key exposed by the modeled monitor tool, for example "company_job_openings". */
|
|
243
243
|
stream: string;
|
|
@@ -560,6 +560,11 @@ export type DatasetBuilder<
|
|
|
560
560
|
*/
|
|
561
561
|
run(options?: {
|
|
562
562
|
description?: string;
|
|
563
|
+
/**
|
|
564
|
+
* `upsert` (default) returns every input row, reusing persisted work for
|
|
565
|
+
* existing keys. `net_new` atomically inserts and returns only unseen keys.
|
|
566
|
+
*/
|
|
567
|
+
mode?: 'upsert' | 'net_new';
|
|
563
568
|
key?:
|
|
564
569
|
| (keyof InputRow & string)
|
|
565
570
|
| readonly (keyof InputRow & string)[]
|
|
@@ -108,10 +108,10 @@ export const SDK_RELEASE = {
|
|
|
108
108
|
// 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
|
|
109
109
|
// 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
|
|
110
110
|
// automatically without blocking their current command.
|
|
111
|
-
version: '0.1.
|
|
111
|
+
version: '0.1.228',
|
|
112
112
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
113
113
|
supportPolicy: {
|
|
114
|
-
latest: '0.1.
|
|
114
|
+
latest: '0.1.228',
|
|
115
115
|
minimumSupported: '0.1.53',
|
|
116
116
|
deprecatedBelow: '0.1.219',
|
|
117
117
|
commandMinimumSupported: [
|