deepline 0.1.21 → 0.1.22
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/cli/index.js +518 -230
- package/dist/cli/index.mjs +575 -282
- package/dist/index.d.mts +21 -58
- package/dist/index.d.ts +21 -58
- package/dist/index.js +148 -90
- package/dist/index.mjs +148 -90
- package/dist/repo/apps/play-runner-workers/src/entry.ts +147 -0
- package/dist/repo/sdk/src/client.ts +205 -120
- package/dist/repo/sdk/src/types.ts +8 -14
- package/dist/repo/sdk/src/version.ts +1 -1
- package/dist/repo/shared_libs/play-runtime/profiles.ts +4 -14
- package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -266,7 +266,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// src/version.ts
|
|
269
|
-
var SDK_VERSION = "0.1.
|
|
269
|
+
var SDK_VERSION = "0.1.22";
|
|
270
270
|
var SDK_API_CONTRACT = "2026-05-runs-v2";
|
|
271
271
|
|
|
272
272
|
// ../shared_libs/play-runtime/coordinator-headers.ts
|
|
@@ -558,7 +558,7 @@ function isRecord(value) {
|
|
|
558
558
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
559
559
|
}
|
|
560
560
|
function normalizePlayStatus(raw) {
|
|
561
|
-
const status = typeof raw.status === "string" ? raw.status :
|
|
561
|
+
const status = typeof raw.status === "string" ? raw.status : "running";
|
|
562
562
|
const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
|
|
563
563
|
return {
|
|
564
564
|
...raw,
|
|
@@ -566,23 +566,6 @@ function normalizePlayStatus(raw) {
|
|
|
566
566
|
status
|
|
567
567
|
};
|
|
568
568
|
}
|
|
569
|
-
function mapLegacyTemporalStatus(status) {
|
|
570
|
-
switch (status.trim().toUpperCase()) {
|
|
571
|
-
case "PENDING":
|
|
572
|
-
return "queued";
|
|
573
|
-
case "COMPLETED":
|
|
574
|
-
return "completed";
|
|
575
|
-
case "FAILED":
|
|
576
|
-
return "failed";
|
|
577
|
-
case "CANCELLED":
|
|
578
|
-
case "TERMINATED":
|
|
579
|
-
case "TIMED_OUT":
|
|
580
|
-
return "cancelled";
|
|
581
|
-
case "RUNNING":
|
|
582
|
-
default:
|
|
583
|
-
return "running";
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
569
|
function decodeBase64Bytes(value) {
|
|
587
570
|
const binary = atob(value);
|
|
588
571
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -591,6 +574,79 @@ function decodeBase64Bytes(value) {
|
|
|
591
574
|
}
|
|
592
575
|
return bytes;
|
|
593
576
|
}
|
|
577
|
+
function readStringArray(value) {
|
|
578
|
+
return Array.isArray(value) ? value.filter((line) => typeof line === "string") : [];
|
|
579
|
+
}
|
|
580
|
+
function getPlayLiveEventPayload(event) {
|
|
581
|
+
return event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
582
|
+
}
|
|
583
|
+
function normalizeLiveStatus(value) {
|
|
584
|
+
if (value === "queued" || value === "running" || value === "waiting" || value === "completed" || value === "failed" || value === "cancelled") {
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
function updatePlayLiveStatusState(state, event) {
|
|
590
|
+
const payload = getPlayLiveEventPayload(event);
|
|
591
|
+
if (event.type === "play.run.log") {
|
|
592
|
+
state.logs.push(...readStringArray(payload.lines));
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : state.runId;
|
|
599
|
+
const status = normalizeLiveStatus(payload.status) ?? state.status;
|
|
600
|
+
const logs = readStringArray(payload.logs);
|
|
601
|
+
if (logs.length > 0 || event.type === "play.run.snapshot") {
|
|
602
|
+
state.logs = logs;
|
|
603
|
+
}
|
|
604
|
+
if ("result" in payload) {
|
|
605
|
+
state.result = payload.result;
|
|
606
|
+
}
|
|
607
|
+
if (typeof payload.error === "string" && payload.error.trim()) {
|
|
608
|
+
state.error = payload.error;
|
|
609
|
+
}
|
|
610
|
+
state.runId = runId;
|
|
611
|
+
state.status = status;
|
|
612
|
+
const progressRecord = payload.progress && typeof payload.progress === "object" && !Array.isArray(payload.progress) ? payload.progress : {};
|
|
613
|
+
const next = {
|
|
614
|
+
...payload,
|
|
615
|
+
runId,
|
|
616
|
+
status,
|
|
617
|
+
progress: {
|
|
618
|
+
...progressRecord,
|
|
619
|
+
status: typeof progressRecord.status === "string" ? progressRecord.status : status,
|
|
620
|
+
logs: state.logs,
|
|
621
|
+
...state.error ? { error: state.error } : {}
|
|
622
|
+
},
|
|
623
|
+
..."result" in state ? { result: state.result } : {}
|
|
624
|
+
};
|
|
625
|
+
state.latest = next;
|
|
626
|
+
return next;
|
|
627
|
+
}
|
|
628
|
+
function playRunResultFromStatus(status, startedAt, fallbackRunId) {
|
|
629
|
+
return {
|
|
630
|
+
success: status.status === "completed",
|
|
631
|
+
runId: status.runId || fallbackRunId,
|
|
632
|
+
result: status.result,
|
|
633
|
+
logs: status.progress?.logs ?? [],
|
|
634
|
+
durationMs: Date.now() - startedAt,
|
|
635
|
+
error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function playRunStatusFromState(state) {
|
|
639
|
+
return {
|
|
640
|
+
runId: state.runId,
|
|
641
|
+
status: state.status,
|
|
642
|
+
progress: {
|
|
643
|
+
status: state.status,
|
|
644
|
+
logs: state.logs,
|
|
645
|
+
...state.error ? { error: state.error } : {}
|
|
646
|
+
},
|
|
647
|
+
..."result" in state ? { result: state.result } : {}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
594
650
|
var DeeplineClient = class {
|
|
595
651
|
http;
|
|
596
652
|
config;
|
|
@@ -700,7 +756,7 @@ var DeeplineClient = class {
|
|
|
700
756
|
/**
|
|
701
757
|
* Search available tools using Deepline's ranked backend search.
|
|
702
758
|
*
|
|
703
|
-
* This is the same discovery surface used by the
|
|
759
|
+
* This is the same discovery surface used by the CLI: it ranks across
|
|
704
760
|
* tool metadata, categories, agent guidance, and input schema fields.
|
|
705
761
|
*/
|
|
706
762
|
async searchTools(options = {}) {
|
|
@@ -793,7 +849,7 @@ var DeeplineClient = class {
|
|
|
793
849
|
* `progress.logs`; they are not part of the user output object.
|
|
794
850
|
*
|
|
795
851
|
* @param request - Play run configuration (name, code, input, etc.)
|
|
796
|
-
* @returns
|
|
852
|
+
* @returns Run metadata including the public `workflowId`
|
|
797
853
|
*
|
|
798
854
|
* @example
|
|
799
855
|
* ```typescript
|
|
@@ -1085,9 +1141,6 @@ var DeeplineClient = class {
|
|
|
1085
1141
|
* Internal/advanced primitive. Public callers should usually prefer
|
|
1086
1142
|
* {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
|
|
1087
1143
|
*
|
|
1088
|
-
* Poll this method until `status` reaches a terminal state:
|
|
1089
|
-
* `'completed'`, `'failed'`, or `'cancelled'`.
|
|
1090
|
-
*
|
|
1091
1144
|
* @param workflowId - Play-run id from {@link startPlayRun}
|
|
1092
1145
|
* @returns Current status with progress logs and partial results
|
|
1093
1146
|
*
|
|
@@ -1109,35 +1162,11 @@ var DeeplineClient = class {
|
|
|
1109
1162
|
);
|
|
1110
1163
|
return normalizePlayStatus(response);
|
|
1111
1164
|
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Get the lightweight tail-polling status for a play execution.
|
|
1114
|
-
*
|
|
1115
|
-
* This is intentionally smaller than {@link getPlayStatus}: it returns the
|
|
1116
|
-
* fields needed for CLI log tailing while the run is in flight, without
|
|
1117
|
-
* forcing the API to rebuild final result views on every poll. Call
|
|
1118
|
-
* {@link getPlayStatus} once after a terminal state for the full result.
|
|
1119
|
-
*/
|
|
1120
|
-
async getPlayTailStatus(workflowId, options) {
|
|
1121
|
-
const params = new URLSearchParams({ mode: "tail" });
|
|
1122
|
-
if (typeof options?.afterLogIndex === "number") {
|
|
1123
|
-
params.set("afterLogIndex", String(options.afterLogIndex));
|
|
1124
|
-
}
|
|
1125
|
-
if (typeof options?.waitMs === "number") {
|
|
1126
|
-
params.set("waitMs", String(options.waitMs));
|
|
1127
|
-
}
|
|
1128
|
-
if (options?.terminalOnly) {
|
|
1129
|
-
params.set("terminalOnly", "true");
|
|
1130
|
-
}
|
|
1131
|
-
const response = await this.http.get(
|
|
1132
|
-
`/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
|
|
1133
|
-
);
|
|
1134
|
-
return normalizePlayStatus(response);
|
|
1135
|
-
}
|
|
1136
1165
|
/**
|
|
1137
1166
|
* Stream semantic play-run events using the same SSE feed as the dashboard.
|
|
1138
1167
|
*
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1168
|
+
* The server emits a canonical `play.run.snapshot` event first for every
|
|
1169
|
+
* connection, then incremental live events until terminal state or reconnect.
|
|
1141
1170
|
*/
|
|
1142
1171
|
async *streamPlayRunEvents(workflowId, options) {
|
|
1143
1172
|
const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
|
|
@@ -1157,7 +1186,7 @@ var DeeplineClient = class {
|
|
|
1157
1186
|
*
|
|
1158
1187
|
* Sends a stop request for the run.
|
|
1159
1188
|
*
|
|
1160
|
-
* @param workflowId -
|
|
1189
|
+
* @param workflowId - Public Deepline play-run id to cancel
|
|
1161
1190
|
*
|
|
1162
1191
|
* @example
|
|
1163
1192
|
* ```typescript
|
|
@@ -1173,7 +1202,7 @@ var DeeplineClient = class {
|
|
|
1173
1202
|
/**
|
|
1174
1203
|
* Stop a running play execution, including open HITL waits.
|
|
1175
1204
|
*
|
|
1176
|
-
* @param workflowId -
|
|
1205
|
+
* @param workflowId - Public Deepline play-run id to stop
|
|
1177
1206
|
* @param options.reason - Optional audit/debug reason
|
|
1178
1207
|
*/
|
|
1179
1208
|
async stopPlay(workflowId, options) {
|
|
@@ -1245,32 +1274,42 @@ var DeeplineClient = class {
|
|
|
1245
1274
|
);
|
|
1246
1275
|
return response.runs ?? [];
|
|
1247
1276
|
}
|
|
1248
|
-
/**
|
|
1249
|
-
* Fetch the lightweight tail status for a run using the public runs resource model.
|
|
1250
|
-
*
|
|
1251
|
-
* This is the SDK equivalent of:
|
|
1252
|
-
*
|
|
1253
|
-
* ```bash
|
|
1254
|
-
* deepline runs tail <run-id> --json
|
|
1255
|
-
* ```
|
|
1256
|
-
*/
|
|
1277
|
+
/** Read the canonical run stream and return the latest run snapshot. */
|
|
1257
1278
|
async tailRun(runId, options) {
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1279
|
+
const state = {
|
|
1280
|
+
runId,
|
|
1281
|
+
status: "running",
|
|
1282
|
+
logs: [],
|
|
1283
|
+
latest: null
|
|
1284
|
+
};
|
|
1285
|
+
let terminal = false;
|
|
1286
|
+
for await (const event of this.streamPlayRunEvents(runId, {
|
|
1287
|
+
mode: "cli",
|
|
1288
|
+
signal: options?.signal
|
|
1289
|
+
})) {
|
|
1290
|
+
const status = updatePlayLiveStatusState(state, event);
|
|
1291
|
+
if (!status) {
|
|
1292
|
+
continue;
|
|
1293
|
+
}
|
|
1294
|
+
terminal = TERMINAL_PLAY_STATUSES.has(status.status);
|
|
1295
|
+
if (terminal) {
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1262
1298
|
}
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1299
|
+
if (terminal && state.latest) {
|
|
1300
|
+
return await this.getRunStatus(state.latest.runId || runId).catch(
|
|
1301
|
+
() => state.latest ?? playRunStatusFromState(state)
|
|
1302
|
+
);
|
|
1265
1303
|
}
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1304
|
+
if (state.latest) {
|
|
1305
|
+
return state.latest;
|
|
1268
1306
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1307
|
+
throw new DeeplineError(
|
|
1308
|
+
`Run stream for ${runId} ended before the initial snapshot.`,
|
|
1309
|
+
void 0,
|
|
1310
|
+
"PLAY_RUN_STREAM_EMPTY",
|
|
1311
|
+
{ runId }
|
|
1272
1312
|
);
|
|
1273
|
-
return normalizePlayStatus(response);
|
|
1274
1313
|
}
|
|
1275
1314
|
/**
|
|
1276
1315
|
* Fetch persisted logs for a run using the public runs resource model.
|
|
@@ -1427,11 +1466,11 @@ var DeeplineClient = class {
|
|
|
1427
1466
|
// Plays — high-level orchestration
|
|
1428
1467
|
// ——————————————————————————————————————————————————————————
|
|
1429
1468
|
/**
|
|
1430
|
-
* Run a play end-to-end: submit,
|
|
1469
|
+
* Run a play end-to-end: submit, stream until terminal, return result.
|
|
1431
1470
|
*
|
|
1432
1471
|
* This is the highest-level play execution method. It submits the play,
|
|
1433
|
-
*
|
|
1434
|
-
* and timing. Supports cancellation via `AbortSignal`.
|
|
1472
|
+
* reads the canonical run stream for status updates, and returns a structured
|
|
1473
|
+
* result with logs and timing. Supports cancellation via `AbortSignal`.
|
|
1435
1474
|
*
|
|
1436
1475
|
* @param code - Source string fallback; pass the bundled artifact in `options.artifact`
|
|
1437
1476
|
* @param csvPath - Input CSV path, or `null`
|
|
@@ -1447,7 +1486,6 @@ var DeeplineClient = class {
|
|
|
1447
1486
|
* const logs = status.progress?.logs ?? [];
|
|
1448
1487
|
* console.log(`[${status.status}] ${logs.length} log lines`);
|
|
1449
1488
|
* },
|
|
1450
|
-
* pollIntervalMs: 1000,
|
|
1451
1489
|
* });
|
|
1452
1490
|
*
|
|
1453
1491
|
* if (result.success) {
|
|
@@ -1478,33 +1516,53 @@ var DeeplineClient = class {
|
|
|
1478
1516
|
packagedFiles: options?.packagedFiles,
|
|
1479
1517
|
force: options?.force
|
|
1480
1518
|
});
|
|
1481
|
-
const pollInterval = options?.pollIntervalMs ?? 500;
|
|
1482
1519
|
const start = Date.now();
|
|
1483
|
-
|
|
1520
|
+
const state = {
|
|
1521
|
+
runId: workflowId,
|
|
1522
|
+
status: "running",
|
|
1523
|
+
logs: [],
|
|
1524
|
+
latest: null
|
|
1525
|
+
};
|
|
1526
|
+
if (options?.signal?.aborted) {
|
|
1527
|
+
await this.cancelPlay(workflowId);
|
|
1528
|
+
return {
|
|
1529
|
+
success: false,
|
|
1530
|
+
runId: workflowId,
|
|
1531
|
+
logs: [],
|
|
1532
|
+
durationMs: Date.now() - start,
|
|
1533
|
+
error: "Cancelled by user"
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
for await (const event of this.streamPlayRunEvents(workflowId, {
|
|
1537
|
+
mode: "cli",
|
|
1538
|
+
signal: options?.signal
|
|
1539
|
+
})) {
|
|
1484
1540
|
if (options?.signal?.aborted) {
|
|
1485
1541
|
await this.cancelPlay(workflowId);
|
|
1486
1542
|
return {
|
|
1487
1543
|
success: false,
|
|
1488
1544
|
runId: workflowId,
|
|
1489
|
-
logs:
|
|
1545
|
+
logs: state.logs,
|
|
1490
1546
|
durationMs: Date.now() - start,
|
|
1491
1547
|
error: "Cancelled by user"
|
|
1492
1548
|
};
|
|
1493
1549
|
}
|
|
1494
|
-
const status =
|
|
1550
|
+
const status = updatePlayLiveStatusState(state, event);
|
|
1551
|
+
if (!status) {
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1495
1554
|
options?.onProgress?.(status);
|
|
1496
1555
|
if (TERMINAL_PLAY_STATUSES.has(status.status)) {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
runId: status.runId || workflowId,
|
|
1500
|
-
result: status.result,
|
|
1501
|
-
logs: status.progress?.logs ?? [],
|
|
1502
|
-
durationMs: Date.now() - start,
|
|
1503
|
-
error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
|
|
1504
|
-
};
|
|
1556
|
+
const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
|
|
1557
|
+
return playRunResultFromStatus(finalStatus, start, workflowId);
|
|
1505
1558
|
}
|
|
1506
|
-
await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
|
|
1507
1559
|
}
|
|
1560
|
+
throw new DeeplineError(
|
|
1561
|
+
`Run stream for ${workflowId} ended before the run reached a terminal state.`,
|
|
1562
|
+
void 0,
|
|
1563
|
+
"PLAY_RUN_STREAM_ENDED",
|
|
1564
|
+
{ runId: workflowId, workflowId }
|
|
1565
|
+
);
|
|
1508
1566
|
}
|
|
1509
1567
|
// ——————————————————————————————————————————————————————————
|
|
1510
1568
|
// Health
|
|
@@ -1590,6 +1648,7 @@ var import_node_path2 = require("path");
|
|
|
1590
1648
|
var import_node_child_process = require("child_process");
|
|
1591
1649
|
var import_sync = require("csv-parse/sync");
|
|
1592
1650
|
var import_sync2 = require("csv-stringify/sync");
|
|
1651
|
+
var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
|
|
1593
1652
|
function getAuthedHttpClient() {
|
|
1594
1653
|
const config = resolveConfig();
|
|
1595
1654
|
return { config, http: new HttpClient(config) };
|
|
@@ -1601,12 +1660,215 @@ async function writeOutputFile(filename, content) {
|
|
|
1601
1660
|
await (0, import_promises.writeFile)(fullPath, content, "utf-8");
|
|
1602
1661
|
return fullPath;
|
|
1603
1662
|
}
|
|
1663
|
+
function browserFocusStateFile() {
|
|
1664
|
+
const homeDir = process.env.HOME || (0, import_node_os2.homedir)();
|
|
1665
|
+
return (0, import_node_path2.join)(
|
|
1666
|
+
homeDir,
|
|
1667
|
+
".local",
|
|
1668
|
+
"deepline",
|
|
1669
|
+
"runtime",
|
|
1670
|
+
"state",
|
|
1671
|
+
"browser-focus.json"
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
function claimBrowserFocus(now = Date.now()) {
|
|
1675
|
+
const statePath = browserFocusStateFile();
|
|
1676
|
+
try {
|
|
1677
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(statePath), { recursive: true });
|
|
1678
|
+
let lastFocusedAt = 0;
|
|
1679
|
+
if ((0, import_node_fs2.existsSync)(statePath)) {
|
|
1680
|
+
const payload = JSON.parse((0, import_node_fs2.readFileSync)(statePath, "utf-8"));
|
|
1681
|
+
const value = payload.lastFocusedAt ?? payload.last_focused_at;
|
|
1682
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1683
|
+
lastFocusedAt = value;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (lastFocusedAt > now) {
|
|
1687
|
+
lastFocusedAt = 0;
|
|
1688
|
+
}
|
|
1689
|
+
if (now - lastFocusedAt < BROWSER_FOCUS_COOLDOWN_MS) {
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
(0, import_node_fs2.writeFileSync)(statePath, JSON.stringify({ lastFocusedAt: now }), "utf-8");
|
|
1693
|
+
return true;
|
|
1694
|
+
} catch {
|
|
1695
|
+
return true;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function extractUrlHost(raw) {
|
|
1699
|
+
try {
|
|
1700
|
+
const parsed = new URL(raw.includes("://") ? raw : `https://${raw}`);
|
|
1701
|
+
return parsed.port ? `${parsed.hostname.toLowerCase()}:${parsed.port}` : parsed.hostname.toLowerCase();
|
|
1702
|
+
} catch {
|
|
1703
|
+
return "";
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
function browserAppNameFromBundleId(bundleId) {
|
|
1707
|
+
const names = {
|
|
1708
|
+
"com.google.chrome": "Google Chrome",
|
|
1709
|
+
"com.google.chrome.canary": "Google Chrome Canary",
|
|
1710
|
+
"com.microsoft.edgemac": "Microsoft Edge",
|
|
1711
|
+
"com.brave.browser": "Brave Browser",
|
|
1712
|
+
"com.operasoftware.opera": "Opera",
|
|
1713
|
+
"com.operasoftware.operagx": "Opera GX",
|
|
1714
|
+
"com.vivaldi.vivaldi": "Vivaldi",
|
|
1715
|
+
"company.thebrowser.browser": "Arc",
|
|
1716
|
+
"com.apple.safari": "Safari"
|
|
1717
|
+
};
|
|
1718
|
+
return names[bundleId.toLowerCase()] ?? "";
|
|
1719
|
+
}
|
|
1720
|
+
function readDefaultMacBrowserBundleId() {
|
|
1721
|
+
try {
|
|
1722
|
+
const output = (0, import_node_child_process.execFileSync)(
|
|
1723
|
+
"/usr/bin/defaults",
|
|
1724
|
+
[
|
|
1725
|
+
"read",
|
|
1726
|
+
`${(0, import_node_os2.homedir)()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`,
|
|
1727
|
+
"LSHandlers"
|
|
1728
|
+
],
|
|
1729
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
1730
|
+
);
|
|
1731
|
+
const httpsMatch = output.match(
|
|
1732
|
+
/LSHandlerURLScheme\s*=\s*https;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
|
|
1733
|
+
);
|
|
1734
|
+
const httpMatch = output.match(
|
|
1735
|
+
/LSHandlerURLScheme\s*=\s*http;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
|
|
1736
|
+
);
|
|
1737
|
+
return (httpsMatch?.[1] ?? httpMatch?.[1] ?? "").trim();
|
|
1738
|
+
} catch {
|
|
1739
|
+
return "";
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
function browserStrategyForBundleId(bundleId) {
|
|
1743
|
+
const normalized = bundleId.toLowerCase();
|
|
1744
|
+
if ((/* @__PURE__ */ new Set([
|
|
1745
|
+
"com.google.chrome",
|
|
1746
|
+
"com.google.chrome.canary",
|
|
1747
|
+
"com.microsoft.edgemac",
|
|
1748
|
+
"com.brave.browser",
|
|
1749
|
+
"com.operasoftware.opera",
|
|
1750
|
+
"com.operasoftware.operagx",
|
|
1751
|
+
"com.vivaldi.vivaldi",
|
|
1752
|
+
"company.thebrowser.browser"
|
|
1753
|
+
])).has(normalized)) {
|
|
1754
|
+
return "chromium";
|
|
1755
|
+
}
|
|
1756
|
+
return normalized === "com.apple.safari" ? "safari" : "fallback";
|
|
1757
|
+
}
|
|
1758
|
+
function runAppleScript(script, args) {
|
|
1759
|
+
const result = (0, import_node_child_process.spawnSync)("osascript", ["-", ...args], {
|
|
1760
|
+
input: script,
|
|
1761
|
+
encoding: "utf-8",
|
|
1762
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
1763
|
+
timeout: 5e3
|
|
1764
|
+
});
|
|
1765
|
+
return result.status === 0;
|
|
1766
|
+
}
|
|
1767
|
+
function retargetChromiumMacos(appName, targetUrl, allowFocus) {
|
|
1768
|
+
const host = extractUrlHost(targetUrl);
|
|
1769
|
+
if (!host) return false;
|
|
1770
|
+
const escapedAppName = appName.replace(/"/g, '\\"');
|
|
1771
|
+
const activateBlock = allowFocus ? " activate\n" : "";
|
|
1772
|
+
const foundTabBlock = allowFocus ? " set active tab index of w to i\n set index of w to 1\n set URL of t to targetUrl\n" : " set URL of t to targetUrl\n";
|
|
1773
|
+
const newTabBlock = allowFocus ? " tell window 1\n make new tab with properties {URL:targetUrl}\n set active tab index to (count of tabs)\n set index to 1\n end tell\n" : " tell window 1\n make new tab with properties {URL:targetUrl}\n end tell\n";
|
|
1774
|
+
const script = `
|
|
1775
|
+
on run argv
|
|
1776
|
+
set targetUrl to item 1 of argv
|
|
1777
|
+
set targetHost to item 2 of argv
|
|
1778
|
+
tell application "${escapedAppName}"
|
|
1779
|
+
${activateBlock} if (count of windows) is 0 then
|
|
1780
|
+
make new window
|
|
1781
|
+
end if
|
|
1782
|
+
set foundTab to false
|
|
1783
|
+
repeat with w in windows
|
|
1784
|
+
set tabCount to count of tabs of w
|
|
1785
|
+
repeat with i from 1 to tabCount
|
|
1786
|
+
set t to tab i of w
|
|
1787
|
+
if (URL of t) contains targetHost then
|
|
1788
|
+
${foundTabBlock} set foundTab to true
|
|
1789
|
+
exit repeat
|
|
1790
|
+
end if
|
|
1791
|
+
end repeat
|
|
1792
|
+
if foundTab then exit repeat
|
|
1793
|
+
end repeat
|
|
1794
|
+
if not foundTab then
|
|
1795
|
+
${newTabBlock} end if
|
|
1796
|
+
end tell
|
|
1797
|
+
end run
|
|
1798
|
+
`;
|
|
1799
|
+
return runAppleScript(script, [targetUrl, host]);
|
|
1800
|
+
}
|
|
1801
|
+
function retargetSafariMacos(appName, targetUrl, allowFocus) {
|
|
1802
|
+
const host = extractUrlHost(targetUrl);
|
|
1803
|
+
if (!host) return false;
|
|
1804
|
+
const escapedAppName = appName.replace(/"/g, '\\"');
|
|
1805
|
+
const activateBlock = allowFocus ? " activate\n" : "";
|
|
1806
|
+
const foundTabBlock = allowFocus ? " set current tab of w to t\n set index of w to 1\n set URL of t to targetUrl\n" : " set URL of t to targetUrl\n";
|
|
1807
|
+
const newTabBlock = allowFocus ? " tell window 1\n set current tab to (make new tab with properties {URL:targetUrl})\n set index to 1\n end tell\n" : " tell window 1\n make new tab with properties {URL:targetUrl}\n end tell\n";
|
|
1808
|
+
const script = `
|
|
1809
|
+
on run argv
|
|
1810
|
+
set targetUrl to item 1 of argv
|
|
1811
|
+
set targetHost to item 2 of argv
|
|
1812
|
+
tell application "${escapedAppName}"
|
|
1813
|
+
${activateBlock} if (count of windows) is 0 then
|
|
1814
|
+
make new document
|
|
1815
|
+
end if
|
|
1816
|
+
set foundTab to false
|
|
1817
|
+
repeat with w in windows
|
|
1818
|
+
set tabCount to count of tabs of w
|
|
1819
|
+
repeat with i from 1 to tabCount
|
|
1820
|
+
set t to tab i of w
|
|
1821
|
+
if (URL of t) contains targetHost then
|
|
1822
|
+
${foundTabBlock} set foundTab to true
|
|
1823
|
+
exit repeat
|
|
1824
|
+
end if
|
|
1825
|
+
end repeat
|
|
1826
|
+
if foundTab then exit repeat
|
|
1827
|
+
end repeat
|
|
1828
|
+
if not foundTab then
|
|
1829
|
+
${newTabBlock} end if
|
|
1830
|
+
end tell
|
|
1831
|
+
end run
|
|
1832
|
+
`;
|
|
1833
|
+
return runAppleScript(script, [targetUrl, host]);
|
|
1834
|
+
}
|
|
1835
|
+
function openUrlMacos(targetUrl, allowFocus) {
|
|
1836
|
+
const defaultBundleId = readDefaultMacBrowserBundleId();
|
|
1837
|
+
const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
|
|
1838
|
+
const strategy = browserStrategyForBundleId(defaultBundleId);
|
|
1839
|
+
if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
|
|
1840
|
+
return true;
|
|
1841
|
+
}
|
|
1842
|
+
if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
|
|
1843
|
+
return true;
|
|
1844
|
+
}
|
|
1845
|
+
if (!allowFocus) {
|
|
1846
|
+
return false;
|
|
1847
|
+
}
|
|
1848
|
+
try {
|
|
1849
|
+
(0, import_node_child_process.execFileSync)("open", [targetUrl], { stdio: "ignore" });
|
|
1850
|
+
return true;
|
|
1851
|
+
} catch {
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1604
1855
|
function openInBrowser(url) {
|
|
1605
1856
|
try {
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1857
|
+
const targetUrl = String(url || "").trim();
|
|
1858
|
+
if (!targetUrl) return;
|
|
1859
|
+
const allowFocus = claimBrowserFocus();
|
|
1860
|
+
if (process.platform === "darwin") {
|
|
1861
|
+
openUrlMacos(targetUrl, allowFocus);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
if (!allowFocus) return;
|
|
1865
|
+
if (process.platform === "win32") {
|
|
1866
|
+
(0, import_node_child_process.execFileSync)("cmd.exe", ["/c", "start", "", targetUrl], {
|
|
1867
|
+
stdio: "ignore"
|
|
1868
|
+
});
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
(0, import_node_child_process.execFileSync)("xdg-open", [targetUrl], { stdio: "ignore" });
|
|
1610
1872
|
} catch {
|
|
1611
1873
|
}
|
|
1612
1874
|
}
|
|
@@ -3862,13 +4124,6 @@ var PLAY_DEDUP_BACKENDS = {
|
|
|
3862
4124
|
|
|
3863
4125
|
// ../shared_libs/play-runtime/profiles.ts
|
|
3864
4126
|
var PLAY_EXECUTION_PROFILES = {
|
|
3865
|
-
legacy: {
|
|
3866
|
-
id: "legacy",
|
|
3867
|
-
scheduler: PLAY_SCHEDULER_BACKENDS.temporal,
|
|
3868
|
-
runner: PLAY_RUNTIME_BACKENDS.daytona,
|
|
3869
|
-
dedup: PLAY_DEDUP_BACKENDS.inMemory,
|
|
3870
|
-
label: "Daytona + Temporal (production today)"
|
|
3871
|
-
},
|
|
3872
4127
|
workers_edge: {
|
|
3873
4128
|
id: "workers_edge",
|
|
3874
4129
|
scheduler: PLAY_SCHEDULER_BACKENDS.cfWorkflows,
|
|
@@ -4919,7 +5174,7 @@ function formatTimestamp(value) {
|
|
|
4919
5174
|
function formatRunLine(run) {
|
|
4920
5175
|
return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
|
|
4921
5176
|
}
|
|
4922
|
-
function
|
|
5177
|
+
function isTransientPlayStreamError(error) {
|
|
4923
5178
|
if (error instanceof DeeplineError && typeof error.statusCode === "number") {
|
|
4924
5179
|
return error.statusCode >= 500 && error.statusCode < 600;
|
|
4925
5180
|
}
|
|
@@ -4928,12 +5183,6 @@ function isTransientPlayStatusPollError(error) {
|
|
|
4928
5183
|
text
|
|
4929
5184
|
);
|
|
4930
5185
|
}
|
|
4931
|
-
function isTerminalPlayStatusPollError(input) {
|
|
4932
|
-
if (input.error instanceof DeeplineError && input.error.statusCode === 404 && input.hasSeenRun) {
|
|
4933
|
-
return true;
|
|
4934
|
-
}
|
|
4935
|
-
return false;
|
|
4936
|
-
}
|
|
4937
5186
|
var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
|
|
4938
5187
|
"completed",
|
|
4939
5188
|
"failed",
|
|
@@ -4999,6 +5248,12 @@ function buildPlayDashboardUrl(baseUrl, playName) {
|
|
|
4999
5248
|
const encodedPlayName = encodeURIComponent(playName);
|
|
5000
5249
|
return `${trimmedBase}/dashboard/plays/${encodedPlayName}`;
|
|
5001
5250
|
}
|
|
5251
|
+
function openPlayDashboard(input) {
|
|
5252
|
+
if (input.jsonOutput || input.noOpen || !input.dashboardUrl) {
|
|
5253
|
+
return;
|
|
5254
|
+
}
|
|
5255
|
+
openInBrowser(input.dashboardUrl);
|
|
5256
|
+
}
|
|
5002
5257
|
function getDashboardUrlFromLiveEvent(event) {
|
|
5003
5258
|
const dashboardUrl = getEventPayload(event).dashboardUrl;
|
|
5004
5259
|
return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
|
|
@@ -5035,6 +5290,68 @@ function assertPlayWaitNotTimedOut(input) {
|
|
|
5035
5290
|
);
|
|
5036
5291
|
}
|
|
5037
5292
|
}
|
|
5293
|
+
async function waitForPlayCompletionByStream(input) {
|
|
5294
|
+
const controller = new AbortController();
|
|
5295
|
+
let timedOut = false;
|
|
5296
|
+
let lastPhase = null;
|
|
5297
|
+
const timeout = input.waitTimeoutMs === null ? null : setTimeout(
|
|
5298
|
+
() => {
|
|
5299
|
+
timedOut = true;
|
|
5300
|
+
controller.abort();
|
|
5301
|
+
},
|
|
5302
|
+
Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
|
|
5303
|
+
);
|
|
5304
|
+
try {
|
|
5305
|
+
for await (const event of input.client.streamPlayRunEvents(
|
|
5306
|
+
input.workflowId,
|
|
5307
|
+
{ signal: controller.signal }
|
|
5308
|
+
)) {
|
|
5309
|
+
assertPlayWaitNotTimedOut({ ...input, lastPhase });
|
|
5310
|
+
const phase = describeLiveEventPhase(event);
|
|
5311
|
+
if (phase) {
|
|
5312
|
+
lastPhase = phase;
|
|
5313
|
+
input.progress.phase(phase);
|
|
5314
|
+
}
|
|
5315
|
+
printPlayLogLines({
|
|
5316
|
+
lines: getLogLinesFromLiveEvent(event),
|
|
5317
|
+
status: null,
|
|
5318
|
+
jsonOutput: input.jsonOutput,
|
|
5319
|
+
emitLogs: input.emitLogs,
|
|
5320
|
+
state: input.state,
|
|
5321
|
+
progress: input.progress
|
|
5322
|
+
});
|
|
5323
|
+
const status = getStatusFromLiveEvent(event);
|
|
5324
|
+
if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
|
|
5325
|
+
const finalStatus = await input.client.getPlayStatus(input.workflowId, {
|
|
5326
|
+
billing: false
|
|
5327
|
+
});
|
|
5328
|
+
if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
|
|
5329
|
+
return finalStatus;
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
} catch (error) {
|
|
5334
|
+
if (timedOut) {
|
|
5335
|
+
assertPlayWaitNotTimedOut({ ...input, lastPhase });
|
|
5336
|
+
}
|
|
5337
|
+
throw error;
|
|
5338
|
+
} finally {
|
|
5339
|
+
if (timeout) {
|
|
5340
|
+
clearTimeout(timeout);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
|
|
5344
|
+
throw new DeeplineError(
|
|
5345
|
+
`Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
|
|
5346
|
+
void 0,
|
|
5347
|
+
"PLAY_LIVE_STREAM_ENDED",
|
|
5348
|
+
{
|
|
5349
|
+
runId: input.workflowId,
|
|
5350
|
+
workflowId: input.workflowId,
|
|
5351
|
+
...lastPhase ? { phase: lastPhase } : {}
|
|
5352
|
+
}
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5038
5355
|
async function startAndWaitForPlayCompletionByStream(input) {
|
|
5039
5356
|
const startedAt = Date.now();
|
|
5040
5357
|
const state = {
|
|
@@ -5068,6 +5385,11 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5068
5385
|
const workflowId = lastKnownWorkflowId || "pending";
|
|
5069
5386
|
if (workflowId !== "pending" && !emittedDashboardUrl) {
|
|
5070
5387
|
const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
|
|
5388
|
+
openPlayDashboard({
|
|
5389
|
+
dashboardUrl,
|
|
5390
|
+
jsonOutput: input.jsonOutput,
|
|
5391
|
+
noOpen: input.noOpen
|
|
5392
|
+
});
|
|
5071
5393
|
if (!input.jsonOutput) {
|
|
5072
5394
|
writeStartedPlayRun({
|
|
5073
5395
|
runId: workflowId,
|
|
@@ -5123,21 +5445,21 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5123
5445
|
lastPhase
|
|
5124
5446
|
});
|
|
5125
5447
|
}
|
|
5126
|
-
if (lastKnownWorkflowId &&
|
|
5448
|
+
if (lastKnownWorkflowId && isTransientPlayStreamError(error)) {
|
|
5127
5449
|
if (timeout) {
|
|
5128
5450
|
clearTimeout(timeout);
|
|
5129
5451
|
}
|
|
5130
5452
|
const reason = error instanceof Error ? error.message : String(error);
|
|
5131
5453
|
if (!input.jsonOutput) {
|
|
5132
5454
|
process.stderr.write(
|
|
5133
|
-
`[play watch] start stream failed after run ${lastKnownWorkflowId};
|
|
5455
|
+
`[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
|
|
5134
5456
|
`
|
|
5135
5457
|
);
|
|
5136
5458
|
}
|
|
5137
5459
|
recordCliTrace({
|
|
5138
|
-
phase: "cli.
|
|
5460
|
+
phase: "cli.play_start_stream_reconnect",
|
|
5139
5461
|
ms: Date.now() - startedAt,
|
|
5140
|
-
ok:
|
|
5462
|
+
ok: true,
|
|
5141
5463
|
playName: input.playName,
|
|
5142
5464
|
workflowId: lastKnownWorkflowId,
|
|
5143
5465
|
eventCount,
|
|
@@ -5145,10 +5467,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5145
5467
|
lastPhase,
|
|
5146
5468
|
reason
|
|
5147
5469
|
});
|
|
5148
|
-
return
|
|
5470
|
+
return waitForPlayCompletionByStream({
|
|
5149
5471
|
client: input.client,
|
|
5150
5472
|
workflowId: lastKnownWorkflowId,
|
|
5151
|
-
pollIntervalMs: 500,
|
|
5152
5473
|
jsonOutput: input.jsonOutput,
|
|
5153
5474
|
emitLogs: input.emitLogs,
|
|
5154
5475
|
waitTimeoutMs: input.waitTimeoutMs,
|
|
@@ -5166,13 +5487,13 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5166
5487
|
if (lastKnownWorkflowId) {
|
|
5167
5488
|
if (!input.jsonOutput) {
|
|
5168
5489
|
input.progress.writeLine(
|
|
5169
|
-
`[play watch] start stream ended after run ${lastKnownWorkflowId};
|
|
5490
|
+
`[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
|
|
5170
5491
|
);
|
|
5171
5492
|
}
|
|
5172
5493
|
recordCliTrace({
|
|
5173
|
-
phase: "cli.
|
|
5494
|
+
phase: "cli.play_start_stream_reconnect",
|
|
5174
5495
|
ms: Date.now() - startedAt,
|
|
5175
|
-
ok:
|
|
5496
|
+
ok: true,
|
|
5176
5497
|
playName: input.playName,
|
|
5177
5498
|
workflowId: lastKnownWorkflowId,
|
|
5178
5499
|
eventCount,
|
|
@@ -5180,10 +5501,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5180
5501
|
lastPhase,
|
|
5181
5502
|
reason: "stream ended before terminal event"
|
|
5182
5503
|
});
|
|
5183
|
-
return
|
|
5504
|
+
return waitForPlayCompletionByStream({
|
|
5184
5505
|
client: input.client,
|
|
5185
5506
|
workflowId: lastKnownWorkflowId,
|
|
5186
|
-
pollIntervalMs: 500,
|
|
5187
5507
|
jsonOutput: input.jsonOutput,
|
|
5188
5508
|
emitLogs: input.emitLogs,
|
|
5189
5509
|
waitTimeoutMs: input.waitTimeoutMs,
|
|
@@ -5204,72 +5524,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
|
|
|
5204
5524
|
}
|
|
5205
5525
|
);
|
|
5206
5526
|
}
|
|
5207
|
-
async function waitForPlayCompletionByPolling(input) {
|
|
5208
|
-
let lastTransientPollWarningAt = 0;
|
|
5209
|
-
let hasSeenRun = false;
|
|
5210
|
-
while (true) {
|
|
5211
|
-
assertPlayWaitNotTimedOut(input);
|
|
5212
|
-
let status;
|
|
5213
|
-
try {
|
|
5214
|
-
status = await input.client.getPlayTailStatus(input.workflowId, {
|
|
5215
|
-
afterLogIndex: input.state.lastLogIndex,
|
|
5216
|
-
// Keep the server-side tail wait close to the caller's requested poll
|
|
5217
|
-
// cadence. A long wait makes tiny remote runs look slow whenever the
|
|
5218
|
-
// terminal update lands just after the held request starts.
|
|
5219
|
-
waitMs: Math.max(50, Math.min(input.pollIntervalMs, 1e3))
|
|
5220
|
-
});
|
|
5221
|
-
} catch (error) {
|
|
5222
|
-
if (isTerminalPlayStatusPollError({ error, hasSeenRun })) {
|
|
5223
|
-
throw new DeeplineError(
|
|
5224
|
-
`Play run ${input.workflowId} no longer exists on the server (404). The run was deleted or the backend lost it.`,
|
|
5225
|
-
404,
|
|
5226
|
-
"PLAY_RUN_NOT_FOUND"
|
|
5227
|
-
);
|
|
5228
|
-
}
|
|
5229
|
-
if (!isTransientPlayStatusPollError(error)) {
|
|
5230
|
-
throw error;
|
|
5231
|
-
}
|
|
5232
|
-
const now = Date.now();
|
|
5233
|
-
if (now - lastTransientPollWarningAt >= 3e4) {
|
|
5234
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
5235
|
-
input.progress.writeLine(
|
|
5236
|
-
`[play tail] transient status poll failed; retrying: ${message}`
|
|
5237
|
-
);
|
|
5238
|
-
lastTransientPollWarningAt = now;
|
|
5239
|
-
}
|
|
5240
|
-
await new Promise(
|
|
5241
|
-
(resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
|
|
5242
|
-
);
|
|
5243
|
-
continue;
|
|
5244
|
-
}
|
|
5245
|
-
hasSeenRun = true;
|
|
5246
|
-
const logs = status.progress?.logs ?? [];
|
|
5247
|
-
input.progress.phase(status.status);
|
|
5248
|
-
printPlayLogLines({
|
|
5249
|
-
lines: logs.slice(input.state.lastLogIndex),
|
|
5250
|
-
status,
|
|
5251
|
-
jsonOutput: input.jsonOutput,
|
|
5252
|
-
emitLogs: input.emitLogs,
|
|
5253
|
-
state: input.state,
|
|
5254
|
-
progress: input.progress
|
|
5255
|
-
});
|
|
5256
|
-
if (TERMINAL_PLAY_STATUSES2.has(status.status)) {
|
|
5257
|
-
return status.result !== void 0 ? status : await input.client.getPlayStatus(input.workflowId, { billing: false });
|
|
5258
|
-
}
|
|
5259
|
-
const authoritativeStatus = await input.client.getPlayStatus(
|
|
5260
|
-
input.workflowId,
|
|
5261
|
-
{ billing: false }
|
|
5262
|
-
);
|
|
5263
|
-
if (TERMINAL_PLAY_STATUSES2.has(authoritativeStatus.status)) {
|
|
5264
|
-
return authoritativeStatus;
|
|
5265
|
-
}
|
|
5266
|
-
if ((status.progress?.logs ?? []).length === input.state.lastLogIndex) {
|
|
5267
|
-
await new Promise(
|
|
5268
|
-
(resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
|
|
5269
|
-
);
|
|
5270
|
-
}
|
|
5271
|
-
}
|
|
5272
|
-
}
|
|
5273
5527
|
function formatInteger(value) {
|
|
5274
5528
|
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
|
|
5275
5529
|
}
|
|
@@ -5410,7 +5664,6 @@ function buildRunWarnings(status, rowsInfo) {
|
|
|
5410
5664
|
function buildRunNextCommands(runId, rowsInfo) {
|
|
5411
5665
|
const commands = {
|
|
5412
5666
|
get: `deepline runs get ${runId} --json`,
|
|
5413
|
-
tail: `deepline runs tail ${runId} --json`,
|
|
5414
5667
|
stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
|
|
5415
5668
|
logs: `deepline runs logs ${runId} --out run.log --json`
|
|
5416
5669
|
};
|
|
@@ -5526,10 +5779,9 @@ function normalizeErrorsForEnvelope(status, error) {
|
|
|
5526
5779
|
}
|
|
5527
5780
|
function normalizeLogsForEnvelope(status) {
|
|
5528
5781
|
const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
|
|
5529
|
-
const
|
|
5530
|
-
const totalCount = offset + logs.length;
|
|
5782
|
+
const totalCount = logs.length;
|
|
5531
5783
|
const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
|
|
5532
|
-
const firstSequence = entries.length === 0 ? null :
|
|
5784
|
+
const firstSequence = entries.length === 0 ? null : logs.length - entries.length + 1;
|
|
5533
5785
|
const lastSequence = totalCount === 0 ? null : totalCount;
|
|
5534
5786
|
return {
|
|
5535
5787
|
totalCount,
|
|
@@ -5775,7 +6027,6 @@ function writeStartedPlayRun(input) {
|
|
|
5775
6027
|
workflowId: input.runId,
|
|
5776
6028
|
name: input.playName,
|
|
5777
6029
|
status: input.status ?? "started",
|
|
5778
|
-
statusUrl: input.statusUrl,
|
|
5779
6030
|
dashboardUrl: input.dashboardUrl
|
|
5780
6031
|
};
|
|
5781
6032
|
if (input.jsonOutput) {
|
|
@@ -5787,7 +6038,7 @@ function writeStartedPlayRun(input) {
|
|
|
5787
6038
|
`Started ${input.playName}`,
|
|
5788
6039
|
` run id: ${input.runId}`,
|
|
5789
6040
|
` get status: deepline runs get ${input.runId} --json`,
|
|
5790
|
-
`
|
|
6041
|
+
` logs: deepline runs logs ${input.runId} --json`,
|
|
5791
6042
|
` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
|
|
5792
6043
|
` result JSON: deepline runs get ${input.runId} --json`
|
|
5793
6044
|
];
|
|
@@ -5802,7 +6053,7 @@ function writeStartedPlayRun(input) {
|
|
|
5802
6053
|
console.log(output);
|
|
5803
6054
|
}
|
|
5804
6055
|
function parsePlayRunOptions(args) {
|
|
5805
|
-
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--json] [--<input> value]";
|
|
6056
|
+
const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--out output.csv] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
|
|
5806
6057
|
let filePath = null;
|
|
5807
6058
|
let playName = null;
|
|
5808
6059
|
let input = null;
|
|
@@ -5812,8 +6063,8 @@ function parsePlayRunOptions(args) {
|
|
|
5812
6063
|
let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
|
|
5813
6064
|
const emitLogs = !jsonOutput || args.includes("--logs");
|
|
5814
6065
|
const force = args.includes("--force");
|
|
6066
|
+
const noOpen = args.includes("--no-open");
|
|
5815
6067
|
let outPath = null;
|
|
5816
|
-
let pollIntervalMs = 500;
|
|
5817
6068
|
let waitTimeoutMs = null;
|
|
5818
6069
|
for (let index = 0; index < args.length; index += 1) {
|
|
5819
6070
|
const arg = args[index];
|
|
@@ -5845,15 +6096,16 @@ function parsePlayRunOptions(args) {
|
|
|
5845
6096
|
outPath = (0, import_node_path8.resolve)(args[++index]);
|
|
5846
6097
|
continue;
|
|
5847
6098
|
}
|
|
5848
|
-
if (
|
|
5849
|
-
|
|
5850
|
-
|
|
6099
|
+
if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
|
|
6100
|
+
throw new Error(
|
|
6101
|
+
`${arg} was removed. --watch uses the canonical run stream directly.`
|
|
6102
|
+
);
|
|
5851
6103
|
}
|
|
5852
6104
|
if ((arg === "--tail-timeout-ms" || arg === "--timeout-ms") && args[index + 1]) {
|
|
5853
6105
|
waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
|
|
5854
6106
|
continue;
|
|
5855
6107
|
}
|
|
5856
|
-
if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force") {
|
|
6108
|
+
if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
|
|
5857
6109
|
if (arg === "--watch") {
|
|
5858
6110
|
continue;
|
|
5859
6111
|
}
|
|
@@ -5922,10 +6174,10 @@ function parsePlayRunOptions(args) {
|
|
|
5922
6174
|
watch,
|
|
5923
6175
|
emitLogs,
|
|
5924
6176
|
jsonOutput,
|
|
5925
|
-
pollIntervalMs,
|
|
5926
6177
|
waitTimeoutMs,
|
|
5927
6178
|
force,
|
|
5928
|
-
outPath
|
|
6179
|
+
outPath,
|
|
6180
|
+
noOpen
|
|
5929
6181
|
};
|
|
5930
6182
|
}
|
|
5931
6183
|
function parsePlayCheckOptions(args) {
|
|
@@ -6100,6 +6352,7 @@ async function handleFileBackedRun(options) {
|
|
|
6100
6352
|
jsonOutput: options.jsonOutput,
|
|
6101
6353
|
emitLogs: options.emitLogs,
|
|
6102
6354
|
waitTimeoutMs: options.waitTimeoutMs,
|
|
6355
|
+
noOpen: options.noOpen,
|
|
6103
6356
|
progress
|
|
6104
6357
|
})
|
|
6105
6358
|
);
|
|
@@ -6127,14 +6380,19 @@ async function handleFileBackedRun(options) {
|
|
|
6127
6380
|
() => client.startPlayRun(startRequest)
|
|
6128
6381
|
);
|
|
6129
6382
|
const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
|
|
6130
|
-
|
|
6383
|
+
const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
|
|
6384
|
+
openPlayDashboard({
|
|
6385
|
+
dashboardUrl: resolvedDashboardUrl,
|
|
6386
|
+
jsonOutput: options.jsonOutput,
|
|
6387
|
+
noOpen: options.noOpen
|
|
6388
|
+
});
|
|
6389
|
+
progress.phase(`loading play on ${resolvedDashboardUrl}`);
|
|
6131
6390
|
progress.complete();
|
|
6132
6391
|
writeStartedPlayRun({
|
|
6133
6392
|
runId: started.workflowId,
|
|
6134
6393
|
playName,
|
|
6135
6394
|
status: started.status,
|
|
6136
|
-
|
|
6137
|
-
dashboardUrl: started.dashboardUrl ?? dashboardUrl,
|
|
6395
|
+
dashboardUrl: resolvedDashboardUrl,
|
|
6138
6396
|
jsonOutput: options.jsonOutput,
|
|
6139
6397
|
progress
|
|
6140
6398
|
});
|
|
@@ -6239,6 +6497,7 @@ async function handleNamedRun(options) {
|
|
|
6239
6497
|
jsonOutput: options.jsonOutput,
|
|
6240
6498
|
emitLogs: options.emitLogs,
|
|
6241
6499
|
waitTimeoutMs: options.waitTimeoutMs,
|
|
6500
|
+
noOpen: options.noOpen,
|
|
6242
6501
|
progress
|
|
6243
6502
|
})
|
|
6244
6503
|
);
|
|
@@ -6269,14 +6528,19 @@ async function handleNamedRun(options) {
|
|
|
6269
6528
|
client.baseUrl,
|
|
6270
6529
|
playName
|
|
6271
6530
|
);
|
|
6272
|
-
|
|
6531
|
+
const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
|
|
6532
|
+
openPlayDashboard({
|
|
6533
|
+
dashboardUrl: resolvedDashboardUrl,
|
|
6534
|
+
jsonOutput: options.jsonOutput,
|
|
6535
|
+
noOpen: options.noOpen
|
|
6536
|
+
});
|
|
6537
|
+
progress.phase(`loading play on ${resolvedDashboardUrl}`);
|
|
6273
6538
|
progress.complete();
|
|
6274
6539
|
writeStartedPlayRun({
|
|
6275
6540
|
runId: started.workflowId,
|
|
6276
6541
|
playName: started.name ?? playName,
|
|
6277
6542
|
status: started.status,
|
|
6278
|
-
|
|
6279
|
-
dashboardUrl: started.dashboardUrl ?? dashboardUrl,
|
|
6543
|
+
dashboardUrl: resolvedDashboardUrl,
|
|
6280
6544
|
jsonOutput: options.jsonOutput,
|
|
6281
6545
|
progress
|
|
6282
6546
|
});
|
|
@@ -6319,7 +6583,7 @@ function parseRunIdPositional(args, usage) {
|
|
|
6319
6583
|
}
|
|
6320
6584
|
continue;
|
|
6321
6585
|
}
|
|
6322
|
-
if ((arg === "--out" || arg === "--
|
|
6586
|
+
if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
|
|
6323
6587
|
index += 1;
|
|
6324
6588
|
continue;
|
|
6325
6589
|
}
|
|
@@ -6404,7 +6668,7 @@ async function handleRunsList(args) {
|
|
|
6404
6668
|
return 0;
|
|
6405
6669
|
}
|
|
6406
6670
|
async function handleRunTail(args) {
|
|
6407
|
-
const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]
|
|
6671
|
+
const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]";
|
|
6408
6672
|
let runId;
|
|
6409
6673
|
try {
|
|
6410
6674
|
runId = parseRunIdPositional(args, usage);
|
|
@@ -6412,20 +6676,19 @@ async function handleRunTail(args) {
|
|
|
6412
6676
|
console.error(error instanceof Error ? error.message : usage);
|
|
6413
6677
|
return 1;
|
|
6414
6678
|
}
|
|
6415
|
-
const client = new DeeplineClient();
|
|
6416
|
-
let afterLogIndex;
|
|
6417
6679
|
for (let index = 0; index < args.length; index += 1) {
|
|
6418
6680
|
const arg = args[index];
|
|
6419
|
-
if (arg === "--cursor"
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6681
|
+
if (arg === "--cursor") {
|
|
6682
|
+
console.error("--cursor was removed. deepline runs tail reads the canonical run stream.");
|
|
6683
|
+
return 1;
|
|
6684
|
+
}
|
|
6685
|
+
if (arg.startsWith("--") && arg !== "--json" && arg !== "--compact") {
|
|
6686
|
+
console.error(`${arg} is not supported by deepline runs tail.`);
|
|
6687
|
+
return 1;
|
|
6424
6688
|
}
|
|
6425
6689
|
}
|
|
6426
|
-
const
|
|
6427
|
-
|
|
6428
|
-
});
|
|
6690
|
+
const client = new DeeplineClient();
|
|
6691
|
+
const status = await client.runs.tail(runId);
|
|
6429
6692
|
writePlayResult(status, argsWantJson(args));
|
|
6430
6693
|
return status.status === "failed" ? 1 : 0;
|
|
6431
6694
|
}
|
|
@@ -6966,8 +7229,9 @@ function registerPlayCommands(program) {
|
|
|
6966
7229
|
"after",
|
|
6967
7230
|
`
|
|
6968
7231
|
Concepts:
|
|
6969
|
-
Plays are durable
|
|
6970
|
-
|
|
7232
|
+
Plays are durable cloud workflows.
|
|
7233
|
+
Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
|
|
7234
|
+
ctx.map adds row keys and row progress.
|
|
6971
7235
|
|
|
6972
7236
|
Common commands:
|
|
6973
7237
|
deepline plays search email --json
|
|
@@ -6998,17 +7262,42 @@ Examples:
|
|
|
6998
7262
|
"after",
|
|
6999
7263
|
`
|
|
7000
7264
|
Notes:
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7265
|
+
Local files are bundled, preflighted, then run in Deepline cloud.
|
|
7266
|
+
Named plays run the live saved revision.
|
|
7267
|
+
Unknown --foo and --foo.bar flags are treated as play input args.
|
|
7268
|
+
File args accept local paths; the CLI stages files before submit.
|
|
7269
|
+
--watch prints logs, previews, stats, and next commands.
|
|
7270
|
+
The play page opens in your browser as soon as the run starts; use --no-open
|
|
7271
|
+
to only print the URL.
|
|
7272
|
+
--force supersedes active runs; it does not bypass completed reuse.
|
|
7273
|
+
|
|
7274
|
+
Idempotent execution:
|
|
7275
|
+
Stable tool call ids are the reuse key:
|
|
7276
|
+
|
|
7277
|
+
await ctx.tools.execute({ id: 'company_lookup', tool, input });
|
|
7278
|
+
|
|
7279
|
+
For rows, use ctx.map plus a stable row key:
|
|
7280
|
+
|
|
7281
|
+
const rows = await ctx
|
|
7282
|
+
.map('companies_v1', companies)
|
|
7283
|
+
.step('cto', (row, ctx) => ctx.tools.execute({
|
|
7284
|
+
id: 'find_cto',
|
|
7285
|
+
tool: 'apollo_search_people_with_match',
|
|
7286
|
+
input: { q_organization_domains_list: [row.domain], per_page: 1 },
|
|
7287
|
+
}))
|
|
7288
|
+
.run({ key: 'domain' });
|
|
7289
|
+
|
|
7290
|
+
Reuse needs the same play, tool id, map name, row key, and compatible logic.
|
|
7291
|
+
To refresh, change the id/map key or set staleAfterSeconds:
|
|
7292
|
+
|
|
7293
|
+
.run({ key: 'domain', staleAfterSeconds: 86400 })
|
|
7007
7294
|
|
|
7008
7295
|
Examples:
|
|
7009
7296
|
deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
|
|
7010
7297
|
deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
|
|
7011
7298
|
deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
|
|
7299
|
+
deepline plays run cto-search.play.ts --limit 5 --watch
|
|
7300
|
+
deepline runs get <run-id>
|
|
7012
7301
|
`
|
|
7013
7302
|
).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
|
|
7014
7303
|
"--revision-id <id>",
|
|
@@ -7019,7 +7308,7 @@ Examples:
|
|
|
7019
7308
|
).option("--watch", "Stream logs until completion").option(
|
|
7020
7309
|
"--logs",
|
|
7021
7310
|
"When output is non-interactive, stream play logs to stderr while waiting"
|
|
7022
|
-
).option("--
|
|
7311
|
+
).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Supersede any active runs for this play").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").action(async (target, options, command) => {
|
|
7023
7312
|
const passthroughArgs = [...command.args];
|
|
7024
7313
|
const explicitTarget = options.file || options.name;
|
|
7025
7314
|
const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
|
|
@@ -7041,9 +7330,9 @@ Examples:
|
|
|
7041
7330
|
...options.out ? ["--out", options.out] : [],
|
|
7042
7331
|
...options.watch ? ["--watch"] : [],
|
|
7043
7332
|
...options.logs ? ["--logs"] : [],
|
|
7044
|
-
...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
|
|
7045
7333
|
...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
|
|
7046
7334
|
...options.force ? ["--force"] : [],
|
|
7335
|
+
...options.open === false ? ["--no-open"] : [],
|
|
7047
7336
|
...options.json ? ["--json"] : [],
|
|
7048
7337
|
...passthroughArgs
|
|
7049
7338
|
]);
|
|
@@ -7150,12 +7439,11 @@ Examples:
|
|
|
7150
7439
|
...options.json ? ["--json"] : []
|
|
7151
7440
|
]);
|
|
7152
7441
|
});
|
|
7153
|
-
runs.command("tail <runId>").description("
|
|
7442
|
+
runs.command("tail <runId>").description("Read the canonical live stream for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").action(async (runId, options) => {
|
|
7154
7443
|
process.exitCode = await handleRunTail([
|
|
7155
7444
|
runId,
|
|
7156
7445
|
...options.json ? ["--json"] : [],
|
|
7157
|
-
...options.compact ? ["--compact"] : []
|
|
7158
|
-
...options.cursor ? ["--cursor", options.cursor] : []
|
|
7446
|
+
...options.compact ? ["--compact"] : []
|
|
7159
7447
|
]);
|
|
7160
7448
|
});
|
|
7161
7449
|
runs.command("logs <runId>").description("Fetch persisted logs for a play run.").option("--limit <count>", "Maximum recent log lines to print without --out", "200").option("--out <path>", "Write the full persisted log stream to a file").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (runId, options) => {
|