deepline 0.1.21 → 0.1.23

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.
@@ -243,7 +243,7 @@ function saveProjectDeeplineEnvValues(baseUrl, values, startDir = projectEnvStar
243
243
  }
244
244
 
245
245
  // src/version.ts
246
- var SDK_VERSION = "0.1.21";
246
+ var SDK_VERSION = "0.1.23";
247
247
  var SDK_API_CONTRACT = "2026-05-runs-v2";
248
248
 
249
249
  // ../shared_libs/play-runtime/coordinator-headers.ts
@@ -531,11 +531,24 @@ function sleep(ms) {
531
531
  // src/client.ts
532
532
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
533
533
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
534
+ var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
535
+ function sleep2(ms) {
536
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
537
+ }
538
+ function isTransientCompileManifestError(error) {
539
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
540
+ return error.statusCode === 408 || error.statusCode === 425 || error.statusCode === 499 || error.statusCode >= 500 && error.statusCode < 600;
541
+ }
542
+ const message = error instanceof Error ? error.message : String(error);
543
+ return /fetch failed|connection (?:closed|reset|terminated)|socket hang up|econnreset|etimedout|eai_again|abort/i.test(
544
+ message
545
+ );
546
+ }
534
547
  function isRecord(value) {
535
548
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
536
549
  }
537
550
  function normalizePlayStatus(raw) {
538
- const status = typeof raw.status === "string" ? raw.status : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
551
+ const status = typeof raw.status === "string" ? raw.status : "running";
539
552
  const runId = typeof raw.runId === "string" ? raw.runId : typeof raw.workflowId === "string" ? raw.workflowId : "";
540
553
  return {
541
554
  ...raw,
@@ -543,23 +556,6 @@ function normalizePlayStatus(raw) {
543
556
  status
544
557
  };
545
558
  }
546
- function mapLegacyTemporalStatus(status) {
547
- switch (status.trim().toUpperCase()) {
548
- case "PENDING":
549
- return "queued";
550
- case "COMPLETED":
551
- return "completed";
552
- case "FAILED":
553
- return "failed";
554
- case "CANCELLED":
555
- case "TERMINATED":
556
- case "TIMED_OUT":
557
- return "cancelled";
558
- case "RUNNING":
559
- default:
560
- return "running";
561
- }
562
- }
563
559
  function decodeBase64Bytes(value) {
564
560
  const binary = atob(value);
565
561
  const bytes = new Uint8Array(binary.length);
@@ -568,6 +564,79 @@ function decodeBase64Bytes(value) {
568
564
  }
569
565
  return bytes;
570
566
  }
567
+ function readStringArray(value) {
568
+ return Array.isArray(value) ? value.filter((line) => typeof line === "string") : [];
569
+ }
570
+ function getPlayLiveEventPayload(event) {
571
+ return event.payload && typeof event.payload === "object" ? event.payload : {};
572
+ }
573
+ function normalizeLiveStatus(value) {
574
+ if (value === "queued" || value === "running" || value === "waiting" || value === "completed" || value === "failed" || value === "cancelled") {
575
+ return value;
576
+ }
577
+ return null;
578
+ }
579
+ function updatePlayLiveStatusState(state, event) {
580
+ const payload = getPlayLiveEventPayload(event);
581
+ if (event.type === "play.run.log") {
582
+ state.logs.push(...readStringArray(payload.lines));
583
+ return null;
584
+ }
585
+ if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
586
+ return null;
587
+ }
588
+ const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : state.runId;
589
+ const status = normalizeLiveStatus(payload.status) ?? state.status;
590
+ const logs = readStringArray(payload.logs);
591
+ if (logs.length > 0 || event.type === "play.run.snapshot") {
592
+ state.logs = logs;
593
+ }
594
+ if ("result" in payload) {
595
+ state.result = payload.result;
596
+ }
597
+ if (typeof payload.error === "string" && payload.error.trim()) {
598
+ state.error = payload.error;
599
+ }
600
+ state.runId = runId;
601
+ state.status = status;
602
+ const progressRecord = payload.progress && typeof payload.progress === "object" && !Array.isArray(payload.progress) ? payload.progress : {};
603
+ const next = {
604
+ ...payload,
605
+ runId,
606
+ status,
607
+ progress: {
608
+ ...progressRecord,
609
+ status: typeof progressRecord.status === "string" ? progressRecord.status : status,
610
+ logs: state.logs,
611
+ ...state.error ? { error: state.error } : {}
612
+ },
613
+ ..."result" in state ? { result: state.result } : {}
614
+ };
615
+ state.latest = next;
616
+ return next;
617
+ }
618
+ function playRunResultFromStatus(status, startedAt, fallbackRunId) {
619
+ return {
620
+ success: status.status === "completed",
621
+ runId: status.runId || fallbackRunId,
622
+ result: status.result,
623
+ logs: status.progress?.logs ?? [],
624
+ durationMs: Date.now() - startedAt,
625
+ error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
626
+ };
627
+ }
628
+ function playRunStatusFromState(state) {
629
+ return {
630
+ runId: state.runId,
631
+ status: state.status,
632
+ progress: {
633
+ status: state.status,
634
+ logs: state.logs,
635
+ ...state.error ? { error: state.error } : {}
636
+ },
637
+ ..."result" in state ? { result: state.result } : {}
638
+ };
639
+ }
571
640
  var DeeplineClient = class {
572
641
  http;
573
642
  config;
@@ -677,7 +746,7 @@ var DeeplineClient = class {
677
746
  /**
678
747
  * Search available tools using Deepline's ranked backend search.
679
748
  *
680
- * This is the same discovery surface used by the legacy CLI: it ranks across
749
+ * This is the same discovery surface used by the CLI: it ranks across
681
750
  * tool metadata, categories, agent guidance, and input schema fields.
682
751
  */
683
752
  async searchTools(options = {}) {
@@ -770,7 +839,7 @@ var DeeplineClient = class {
770
839
  * `progress.logs`; they are not part of the user output object.
771
840
  *
772
841
  * @param request - Play run configuration (name, code, input, etc.)
773
- * @returns Workflow metadata including the `workflowId` for status polling
842
+ * @returns Run metadata including the public `workflowId`
774
843
  *
775
844
  * @example
776
845
  * ```typescript
@@ -880,8 +949,22 @@ var DeeplineClient = class {
880
949
  });
881
950
  }
882
951
  async compilePlayManifest(input) {
883
- const response = await this.http.post("/api/v2/plays/compile-manifest", input);
884
- return response.compilerManifest;
952
+ const retryDelays = COMPILE_MANIFEST_RETRY_DELAYS_MS.slice(
953
+ 0,
954
+ Math.max(0, this.config.maxRetries)
955
+ );
956
+ for (let attempt = 0; ; attempt += 1) {
957
+ try {
958
+ const response = await this.http.post("/api/v2/plays/compile-manifest", input);
959
+ return response.compilerManifest;
960
+ } catch (error) {
961
+ const delayMs = retryDelays[attempt];
962
+ if (delayMs === void 0 || !isTransientCompileManifestError(error)) {
963
+ throw error;
964
+ }
965
+ await sleep2(delayMs);
966
+ }
967
+ }
885
968
  }
886
969
  /**
887
970
  * Check a bundled play artifact against the server's current play compiler.
@@ -1062,9 +1145,6 @@ var DeeplineClient = class {
1062
1145
  * Internal/advanced primitive. Public callers should usually prefer
1063
1146
  * {@link runPlay}, {@link PlayJob.get}, or `deepline play run --watch`.
1064
1147
  *
1065
- * Poll this method until `status` reaches a terminal state:
1066
- * `'completed'`, `'failed'`, or `'cancelled'`.
1067
- *
1068
1148
  * @param workflowId - Play-run id from {@link startPlayRun}
1069
1149
  * @returns Current status with progress logs and partial results
1070
1150
  *
@@ -1086,35 +1166,11 @@ var DeeplineClient = class {
1086
1166
  );
1087
1167
  return normalizePlayStatus(response);
1088
1168
  }
1089
- /**
1090
- * Get the lightweight tail-polling status for a play execution.
1091
- *
1092
- * This is intentionally smaller than {@link getPlayStatus}: it returns the
1093
- * fields needed for CLI log tailing while the run is in flight, without
1094
- * forcing the API to rebuild final result views on every poll. Call
1095
- * {@link getPlayStatus} once after a terminal state for the full result.
1096
- */
1097
- async getPlayTailStatus(workflowId, options) {
1098
- const params = new URLSearchParams({ mode: "tail" });
1099
- if (typeof options?.afterLogIndex === "number") {
1100
- params.set("afterLogIndex", String(options.afterLogIndex));
1101
- }
1102
- if (typeof options?.waitMs === "number") {
1103
- params.set("waitMs", String(options.waitMs));
1104
- }
1105
- if (options?.terminalOnly) {
1106
- params.set("terminalOnly", "true");
1107
- }
1108
- const response = await this.http.get(
1109
- `/api/v2/plays/run/${encodeURIComponent(workflowId)}?${params.toString()}`
1110
- );
1111
- return normalizePlayStatus(response);
1112
- }
1113
1169
  /**
1114
1170
  * Stream semantic play-run events using the same SSE feed as the dashboard.
1115
1171
  *
1116
- * Consumers should still keep a polling fallback: SSE is the fast live-update
1117
- * transport, while the status endpoints remain the authoritative recovery path.
1172
+ * The server emits a canonical `play.run.snapshot` event first for every
1173
+ * connection, then incremental live events until terminal state or reconnect.
1118
1174
  */
1119
1175
  async *streamPlayRunEvents(workflowId, options) {
1120
1176
  const headers = options?.lastEventId && options.lastEventId.trim() ? { "Last-Event-ID": options.lastEventId.trim() } : void 0;
@@ -1134,7 +1190,7 @@ var DeeplineClient = class {
1134
1190
  *
1135
1191
  * Sends a stop request for the run.
1136
1192
  *
1137
- * @param workflowId - Temporal workflow ID to cancel
1193
+ * @param workflowId - Public Deepline play-run id to cancel
1138
1194
  *
1139
1195
  * @example
1140
1196
  * ```typescript
@@ -1150,7 +1206,7 @@ var DeeplineClient = class {
1150
1206
  /**
1151
1207
  * Stop a running play execution, including open HITL waits.
1152
1208
  *
1153
- * @param workflowId - Temporal workflow ID to stop
1209
+ * @param workflowId - Public Deepline play-run id to stop
1154
1210
  * @param options.reason - Optional audit/debug reason
1155
1211
  */
1156
1212
  async stopPlay(workflowId, options) {
@@ -1222,32 +1278,42 @@ var DeeplineClient = class {
1222
1278
  );
1223
1279
  return response.runs ?? [];
1224
1280
  }
1225
- /**
1226
- * Fetch the lightweight tail status for a run using the public runs resource model.
1227
- *
1228
- * This is the SDK equivalent of:
1229
- *
1230
- * ```bash
1231
- * deepline runs tail <run-id> --json
1232
- * ```
1233
- */
1281
+ /** Read the canonical run stream and return the latest run snapshot. */
1234
1282
  async tailRun(runId, options) {
1235
- const afterLogIndex = typeof options?.afterLogIndex === "number" ? options.afterLogIndex : typeof options?.cursor === "number" ? options.cursor : typeof options?.cursor === "string" && options.cursor.trim() ? Number(options.cursor) : void 0;
1236
- const params = new URLSearchParams();
1237
- if (Number.isFinite(afterLogIndex)) {
1238
- params.set("afterLogIndex", String(Number(afterLogIndex)));
1283
+ const state = {
1284
+ runId,
1285
+ status: "running",
1286
+ logs: [],
1287
+ latest: null
1288
+ };
1289
+ let terminal = false;
1290
+ for await (const event of this.streamPlayRunEvents(runId, {
1291
+ mode: "cli",
1292
+ signal: options?.signal
1293
+ })) {
1294
+ const status = updatePlayLiveStatusState(state, event);
1295
+ if (!status) {
1296
+ continue;
1297
+ }
1298
+ terminal = TERMINAL_PLAY_STATUSES.has(status.status);
1299
+ if (terminal) {
1300
+ break;
1301
+ }
1239
1302
  }
1240
- if (typeof options?.waitMs === "number") {
1241
- params.set("waitMs", String(options.waitMs));
1303
+ if (terminal && state.latest) {
1304
+ return await this.getRunStatus(state.latest.runId || runId).catch(
1305
+ () => state.latest ?? playRunStatusFromState(state)
1306
+ );
1242
1307
  }
1243
- if (options?.terminalOnly) {
1244
- params.set("terminalOnly", "true");
1308
+ if (state.latest) {
1309
+ return state.latest;
1245
1310
  }
1246
- const suffix = params.toString() ? `?${params.toString()}` : "";
1247
- const response = await this.http.get(
1248
- `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
1311
+ throw new DeeplineError(
1312
+ `Run stream for ${runId} ended before the initial snapshot.`,
1313
+ void 0,
1314
+ "PLAY_RUN_STREAM_EMPTY",
1315
+ { runId }
1249
1316
  );
1250
- return normalizePlayStatus(response);
1251
1317
  }
1252
1318
  /**
1253
1319
  * Fetch persisted logs for a run using the public runs resource model.
@@ -1404,11 +1470,11 @@ var DeeplineClient = class {
1404
1470
  // Plays — high-level orchestration
1405
1471
  // ——————————————————————————————————————————————————————————
1406
1472
  /**
1407
- * Run a play end-to-end: submit, poll until terminal, return result.
1473
+ * Run a play end-to-end: submit, stream until terminal, return result.
1408
1474
  *
1409
1475
  * This is the highest-level play execution method. It submits the play,
1410
- * polls for status updates, and returns a structured result with logs
1411
- * and timing. Supports cancellation via `AbortSignal`.
1476
+ * reads the canonical run stream for status updates, and returns a structured
1477
+ * result with logs and timing. Supports cancellation via `AbortSignal`.
1412
1478
  *
1413
1479
  * @param code - Source string fallback; pass the bundled artifact in `options.artifact`
1414
1480
  * @param csvPath - Input CSV path, or `null`
@@ -1424,7 +1490,6 @@ var DeeplineClient = class {
1424
1490
  * const logs = status.progress?.logs ?? [];
1425
1491
  * console.log(`[${status.status}] ${logs.length} log lines`);
1426
1492
  * },
1427
- * pollIntervalMs: 1000,
1428
1493
  * });
1429
1494
  *
1430
1495
  * if (result.success) {
@@ -1455,33 +1520,53 @@ var DeeplineClient = class {
1455
1520
  packagedFiles: options?.packagedFiles,
1456
1521
  force: options?.force
1457
1522
  });
1458
- const pollInterval = options?.pollIntervalMs ?? 500;
1459
1523
  const start = Date.now();
1460
- while (true) {
1524
+ const state = {
1525
+ runId: workflowId,
1526
+ status: "running",
1527
+ logs: [],
1528
+ latest: null
1529
+ };
1530
+ if (options?.signal?.aborted) {
1531
+ await this.cancelPlay(workflowId);
1532
+ return {
1533
+ success: false,
1534
+ runId: workflowId,
1535
+ logs: [],
1536
+ durationMs: Date.now() - start,
1537
+ error: "Cancelled by user"
1538
+ };
1539
+ }
1540
+ for await (const event of this.streamPlayRunEvents(workflowId, {
1541
+ mode: "cli",
1542
+ signal: options?.signal
1543
+ })) {
1461
1544
  if (options?.signal?.aborted) {
1462
1545
  await this.cancelPlay(workflowId);
1463
1546
  return {
1464
1547
  success: false,
1465
1548
  runId: workflowId,
1466
- logs: [],
1549
+ logs: state.logs,
1467
1550
  durationMs: Date.now() - start,
1468
1551
  error: "Cancelled by user"
1469
1552
  };
1470
1553
  }
1471
- const status = await this.getPlayStatus(workflowId);
1554
+ const status = updatePlayLiveStatusState(state, event);
1555
+ if (!status) {
1556
+ continue;
1557
+ }
1472
1558
  options?.onProgress?.(status);
1473
1559
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1474
- return {
1475
- success: status.status === "completed",
1476
- runId: status.runId || workflowId,
1477
- result: status.result,
1478
- logs: status.progress?.logs ?? [],
1479
- durationMs: Date.now() - start,
1480
- error: status.progress?.error ?? (status.status !== "completed" ? status.status : void 0)
1481
- };
1560
+ const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1561
+ return playRunResultFromStatus(finalStatus, start, workflowId);
1482
1562
  }
1483
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
1484
1563
  }
1564
+ throw new DeeplineError(
1565
+ `Run stream for ${workflowId} ended before the run reached a terminal state.`,
1566
+ void 0,
1567
+ "PLAY_RUN_STREAM_ENDED",
1568
+ { runId: workflowId, workflowId }
1569
+ );
1485
1570
  }
1486
1571
  // ——————————————————————————————————————————————————————————
1487
1572
  // Health
@@ -1555,18 +1640,24 @@ async function enforceSdkCompatibility(baseUrl) {
1555
1640
  }
1556
1641
 
1557
1642
  // src/cli/commands/auth.ts
1558
- import { writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1643
+ import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
1559
1644
  import { hostname } from "os";
1560
- import { dirname as dirname2 } from "path";
1645
+ import { dirname as dirname3 } from "path";
1561
1646
 
1562
1647
  // src/cli/utils.ts
1563
- import { readFileSync as readFileSync2 } from "fs";
1648
+ import {
1649
+ existsSync as existsSync2,
1650
+ mkdirSync as mkdirSync2,
1651
+ readFileSync as readFileSync2,
1652
+ writeFileSync as writeFileSync2
1653
+ } from "fs";
1564
1654
  import { mkdir, writeFile } from "fs/promises";
1565
1655
  import { homedir as homedir2 } from "os";
1566
- import { join as join2, resolve as resolve2 } from "path";
1567
- import { execSync } from "child_process";
1656
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1657
+ import { execFileSync, spawnSync } from "child_process";
1568
1658
  import { parse } from "csv-parse/sync";
1569
1659
  import { stringify } from "csv-stringify/sync";
1660
+ var BROWSER_FOCUS_COOLDOWN_MS = 3e4;
1570
1661
  function getAuthedHttpClient() {
1571
1662
  const config = resolveConfig();
1572
1663
  return { config, http: new HttpClient(config) };
@@ -1578,12 +1669,215 @@ async function writeOutputFile(filename, content) {
1578
1669
  await writeFile(fullPath, content, "utf-8");
1579
1670
  return fullPath;
1580
1671
  }
1672
+ function browserFocusStateFile() {
1673
+ const homeDir = process.env.HOME || homedir2();
1674
+ return join2(
1675
+ homeDir,
1676
+ ".local",
1677
+ "deepline",
1678
+ "runtime",
1679
+ "state",
1680
+ "browser-focus.json"
1681
+ );
1682
+ }
1683
+ function claimBrowserFocus(now = Date.now()) {
1684
+ const statePath = browserFocusStateFile();
1685
+ try {
1686
+ mkdirSync2(dirname2(statePath), { recursive: true });
1687
+ let lastFocusedAt = 0;
1688
+ if (existsSync2(statePath)) {
1689
+ const payload = JSON.parse(readFileSync2(statePath, "utf-8"));
1690
+ const value = payload.lastFocusedAt ?? payload.last_focused_at;
1691
+ if (typeof value === "number" && Number.isFinite(value)) {
1692
+ lastFocusedAt = value;
1693
+ }
1694
+ }
1695
+ if (lastFocusedAt > now) {
1696
+ lastFocusedAt = 0;
1697
+ }
1698
+ if (now - lastFocusedAt < BROWSER_FOCUS_COOLDOWN_MS) {
1699
+ return false;
1700
+ }
1701
+ writeFileSync2(statePath, JSON.stringify({ lastFocusedAt: now }), "utf-8");
1702
+ return true;
1703
+ } catch {
1704
+ return true;
1705
+ }
1706
+ }
1707
+ function extractUrlHost(raw) {
1708
+ try {
1709
+ const parsed = new URL(raw.includes("://") ? raw : `https://${raw}`);
1710
+ return parsed.port ? `${parsed.hostname.toLowerCase()}:${parsed.port}` : parsed.hostname.toLowerCase();
1711
+ } catch {
1712
+ return "";
1713
+ }
1714
+ }
1715
+ function browserAppNameFromBundleId(bundleId) {
1716
+ const names = {
1717
+ "com.google.chrome": "Google Chrome",
1718
+ "com.google.chrome.canary": "Google Chrome Canary",
1719
+ "com.microsoft.edgemac": "Microsoft Edge",
1720
+ "com.brave.browser": "Brave Browser",
1721
+ "com.operasoftware.opera": "Opera",
1722
+ "com.operasoftware.operagx": "Opera GX",
1723
+ "com.vivaldi.vivaldi": "Vivaldi",
1724
+ "company.thebrowser.browser": "Arc",
1725
+ "com.apple.safari": "Safari"
1726
+ };
1727
+ return names[bundleId.toLowerCase()] ?? "";
1728
+ }
1729
+ function readDefaultMacBrowserBundleId() {
1730
+ try {
1731
+ const output = execFileSync(
1732
+ "/usr/bin/defaults",
1733
+ [
1734
+ "read",
1735
+ `${homedir2()}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`,
1736
+ "LSHandlers"
1737
+ ],
1738
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
1739
+ );
1740
+ const httpsMatch = output.match(
1741
+ /LSHandlerURLScheme\s*=\s*https;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1742
+ );
1743
+ const httpMatch = output.match(
1744
+ /LSHandlerURLScheme\s*=\s*http;[\s\S]*?LSHandlerRole(?:All|Viewer|Editor)\s*=\s*"([^"]+)"/
1745
+ );
1746
+ return (httpsMatch?.[1] ?? httpMatch?.[1] ?? "").trim();
1747
+ } catch {
1748
+ return "";
1749
+ }
1750
+ }
1751
+ function browserStrategyForBundleId(bundleId) {
1752
+ const normalized = bundleId.toLowerCase();
1753
+ if ((/* @__PURE__ */ new Set([
1754
+ "com.google.chrome",
1755
+ "com.google.chrome.canary",
1756
+ "com.microsoft.edgemac",
1757
+ "com.brave.browser",
1758
+ "com.operasoftware.opera",
1759
+ "com.operasoftware.operagx",
1760
+ "com.vivaldi.vivaldi",
1761
+ "company.thebrowser.browser"
1762
+ ])).has(normalized)) {
1763
+ return "chromium";
1764
+ }
1765
+ return normalized === "com.apple.safari" ? "safari" : "fallback";
1766
+ }
1767
+ function runAppleScript(script, args) {
1768
+ const result = spawnSync("osascript", ["-", ...args], {
1769
+ input: script,
1770
+ encoding: "utf-8",
1771
+ stdio: ["pipe", "ignore", "ignore"],
1772
+ timeout: 5e3
1773
+ });
1774
+ return result.status === 0;
1775
+ }
1776
+ function retargetChromiumMacos(appName, targetUrl, allowFocus) {
1777
+ const host = extractUrlHost(targetUrl);
1778
+ if (!host) return false;
1779
+ const escapedAppName = appName.replace(/"/g, '\\"');
1780
+ const activateBlock = allowFocus ? " activate\n" : "";
1781
+ 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";
1782
+ 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";
1783
+ const script = `
1784
+ on run argv
1785
+ set targetUrl to item 1 of argv
1786
+ set targetHost to item 2 of argv
1787
+ tell application "${escapedAppName}"
1788
+ ${activateBlock} if (count of windows) is 0 then
1789
+ make new window
1790
+ end if
1791
+ set foundTab to false
1792
+ repeat with w in windows
1793
+ set tabCount to count of tabs of w
1794
+ repeat with i from 1 to tabCount
1795
+ set t to tab i of w
1796
+ if (URL of t) contains targetHost then
1797
+ ${foundTabBlock} set foundTab to true
1798
+ exit repeat
1799
+ end if
1800
+ end repeat
1801
+ if foundTab then exit repeat
1802
+ end repeat
1803
+ if not foundTab then
1804
+ ${newTabBlock} end if
1805
+ end tell
1806
+ end run
1807
+ `;
1808
+ return runAppleScript(script, [targetUrl, host]);
1809
+ }
1810
+ function retargetSafariMacos(appName, targetUrl, allowFocus) {
1811
+ const host = extractUrlHost(targetUrl);
1812
+ if (!host) return false;
1813
+ const escapedAppName = appName.replace(/"/g, '\\"');
1814
+ const activateBlock = allowFocus ? " activate\n" : "";
1815
+ 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";
1816
+ 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";
1817
+ const script = `
1818
+ on run argv
1819
+ set targetUrl to item 1 of argv
1820
+ set targetHost to item 2 of argv
1821
+ tell application "${escapedAppName}"
1822
+ ${activateBlock} if (count of windows) is 0 then
1823
+ make new document
1824
+ end if
1825
+ set foundTab to false
1826
+ repeat with w in windows
1827
+ set tabCount to count of tabs of w
1828
+ repeat with i from 1 to tabCount
1829
+ set t to tab i of w
1830
+ if (URL of t) contains targetHost then
1831
+ ${foundTabBlock} set foundTab to true
1832
+ exit repeat
1833
+ end if
1834
+ end repeat
1835
+ if foundTab then exit repeat
1836
+ end repeat
1837
+ if not foundTab then
1838
+ ${newTabBlock} end if
1839
+ end tell
1840
+ end run
1841
+ `;
1842
+ return runAppleScript(script, [targetUrl, host]);
1843
+ }
1844
+ function openUrlMacos(targetUrl, allowFocus) {
1845
+ const defaultBundleId = readDefaultMacBrowserBundleId();
1846
+ const appName = defaultBundleId ? browserAppNameFromBundleId(defaultBundleId) : "";
1847
+ const strategy = browserStrategyForBundleId(defaultBundleId);
1848
+ if (appName && strategy === "chromium" && retargetChromiumMacos(appName, targetUrl, allowFocus)) {
1849
+ return true;
1850
+ }
1851
+ if (appName && strategy === "safari" && retargetSafariMacos(appName, targetUrl, allowFocus)) {
1852
+ return true;
1853
+ }
1854
+ if (!allowFocus) {
1855
+ return false;
1856
+ }
1857
+ try {
1858
+ execFileSync("open", [targetUrl], { stdio: "ignore" });
1859
+ return true;
1860
+ } catch {
1861
+ return false;
1862
+ }
1863
+ }
1581
1864
  function openInBrowser(url) {
1582
1865
  try {
1583
- if (process.platform === "darwin") execSync(`open "${url}"`, { stdio: "ignore" });
1584
- else if (process.platform === "win32")
1585
- execSync(`start "" "${url}"`, { stdio: "ignore" });
1586
- else execSync(`xdg-open "${url}"`, { stdio: "ignore" });
1866
+ const targetUrl = String(url || "").trim();
1867
+ if (!targetUrl) return;
1868
+ const allowFocus = claimBrowserFocus();
1869
+ if (process.platform === "darwin") {
1870
+ openUrlMacos(targetUrl, allowFocus);
1871
+ return;
1872
+ }
1873
+ if (!allowFocus) return;
1874
+ if (process.platform === "win32") {
1875
+ execFileSync("cmd.exe", ["/c", "start", "", targetUrl], {
1876
+ stdio: "ignore"
1877
+ });
1878
+ return;
1879
+ }
1880
+ execFileSync("xdg-open", [targetUrl], { stdio: "ignore" });
1587
1881
  } catch {
1588
1882
  }
1589
1883
  }
@@ -1667,14 +1961,14 @@ function envFilePath(baseUrl) {
1667
1961
  }
1668
1962
  function saveEnvValues(values, baseUrl) {
1669
1963
  const filePath = envFilePath(baseUrl);
1670
- const dir = dirname2(filePath);
1671
- if (!existsSync2(dir)) {
1672
- mkdirSync2(dir, { recursive: true });
1964
+ const dir = dirname3(filePath);
1965
+ if (!existsSync3(dir)) {
1966
+ mkdirSync3(dir, { recursive: true });
1673
1967
  }
1674
- const existing = existsSync2(filePath) ? parseEnvFile(filePath) : {};
1968
+ const existing = existsSync3(filePath) ? parseEnvFile(filePath) : {};
1675
1969
  const merged = { ...existing, ...values };
1676
1970
  const lines = Object.entries(merged).filter(([, v]) => v !== "").map(([k, v]) => `${k}=${v}`);
1677
- writeFileSync2(filePath, lines.join("\n") + "\n", "utf-8");
1971
+ writeFileSync3(filePath, lines.join("\n") + "\n", "utf-8");
1678
1972
  saveProjectDeeplineEnvValues(baseUrl, values);
1679
1973
  }
1680
1974
  async function httpJson(method, url, apiKey, body) {
@@ -1728,7 +2022,7 @@ function buildCandidateUrls2(url) {
1728
2022
  return [url];
1729
2023
  }
1730
2024
  }
1731
- function sleep2(ms) {
2025
+ function sleep3(ms) {
1732
2026
  return new Promise((resolve8) => setTimeout(resolve8, ms));
1733
2027
  }
1734
2028
  function printDeeplineLogo() {
@@ -1811,7 +2105,7 @@ async function handleRegister(args) {
1811
2105
  return EXIT_AUTH;
1812
2106
  }
1813
2107
  if (s >= 500 || s === 0 || s === 400) {
1814
- await sleep2(2e3);
2108
+ await sleep3(2e3);
1815
2109
  continue;
1816
2110
  }
1817
2111
  if (s >= 400) {
@@ -1835,7 +2129,7 @@ async function handleRegister(args) {
1835
2129
  console.log("That approval link expired. Please run: deepline auth register");
1836
2130
  return EXIT_AUTH;
1837
2131
  }
1838
- await sleep2(2e3);
2132
+ await sleep3(2e3);
1839
2133
  }
1840
2134
  }
1841
2135
  async function handleWait(args) {
@@ -1872,7 +2166,7 @@ async function handleWait(args) {
1872
2166
  return EXIT_AUTH;
1873
2167
  }
1874
2168
  if (status >= 500 || status === 0 || status === 400) {
1875
- await sleep2(2e3);
2169
+ await sleep3(2e3);
1876
2170
  continue;
1877
2171
  }
1878
2172
  if (status >= 400) {
@@ -1896,7 +2190,7 @@ async function handleWait(args) {
1896
2190
  console.error("That approval link expired. Run: deepline auth register");
1897
2191
  return EXIT_AUTH;
1898
2192
  }
1899
- await sleep2(2e3);
2193
+ await sleep3(2e3);
1900
2194
  }
1901
2195
  console.error("Still pending. Approve the browser link, then run: deepline auth wait");
1902
2196
  return EXIT_AUTH;
@@ -2213,7 +2507,7 @@ function registerBillingCommands(program) {
2213
2507
  }
2214
2508
 
2215
2509
  // src/cli/dataset-stats.ts
2216
- import { writeFileSync as writeFileSync3 } from "fs";
2510
+ import { writeFileSync as writeFileSync4 } from "fs";
2217
2511
  import { resolve as resolve3 } from "path";
2218
2512
  var CSV_PROJECTED_FIELDS_KEY = "__deeplineCsvProjectedFields";
2219
2513
  function csvProjectedFields(row) {
@@ -2545,7 +2839,7 @@ function writeCanonicalRowsCsv(rowsInfo, outPath) {
2545
2839
  columns: rowsInfo.columns
2546
2840
  });
2547
2841
  const resolved = resolve3(outPath);
2548
- writeFileSync3(
2842
+ writeFileSync4(
2549
2843
  resolved,
2550
2844
  csvStringFromRows(sanitized.rows, sanitized.columns),
2551
2845
  "utf-8"
@@ -2838,25 +3132,25 @@ function registerOrgCommands(program) {
2838
3132
  // src/cli/commands/play.ts
2839
3133
  import { createHash as createHash3 } from "crypto";
2840
3134
  import {
2841
- existsSync as existsSync4,
3135
+ existsSync as existsSync5,
2842
3136
  readFileSync as readFileSync3,
2843
3137
  readdirSync,
2844
3138
  realpathSync,
2845
- writeFileSync as writeFileSync4
3139
+ writeFileSync as writeFileSync5
2846
3140
  } from "fs";
2847
- import { basename as basename3, dirname as dirname6, join as join6, resolve as resolve7 } from "path";
3141
+ import { basename as basename3, dirname as dirname7, join as join6, resolve as resolve7 } from "path";
2848
3142
 
2849
3143
  // src/plays/bundle-play-file.ts
2850
3144
  import { tmpdir as tmpdir2 } from "os";
2851
- import { dirname as dirname5, join as join5, resolve as resolve6 } from "path";
3145
+ import { dirname as dirname6, join as join5, resolve as resolve6 } from "path";
2852
3146
  import { fileURLToPath } from "url";
2853
- import { existsSync as existsSync3 } from "fs";
3147
+ import { existsSync as existsSync4 } from "fs";
2854
3148
 
2855
3149
  // ../shared_libs/plays/bundling/index.ts
2856
3150
  import { createHash } from "crypto";
2857
3151
  import { mkdir as mkdir2, readFile, realpath, stat, writeFile as writeFile2 } from "fs/promises";
2858
3152
  import { tmpdir } from "os";
2859
- import { basename, dirname as dirname3, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
3153
+ import { basename, dirname as dirname4, extname, isAbsolute, join as join3, resolve as resolve4 } from "path";
2860
3154
  import { builtinModules, createRequire } from "module";
2861
3155
  import { build } from "esbuild";
2862
3156
 
@@ -2955,7 +3249,7 @@ async function normalizeLocalPath(filePath) {
2955
3249
  function createPlayWorkspace(entryFile) {
2956
3250
  return {
2957
3251
  entryFile,
2958
- rootDir: dirname3(entryFile)
3252
+ rootDir: dirname4(entryFile)
2959
3253
  };
2960
3254
  }
2961
3255
  function isPathInsideDirectory(filePath, directory) {
@@ -3156,7 +3450,7 @@ function workersNamedPlayEntryAliasPlugin(playFilePath, exportName) {
3156
3450
  contents: `export { ${exportName} as default } from ${JSON.stringify(playFilePath)};
3157
3451
  `,
3158
3452
  loader: "ts",
3159
- resolveDir: dirname3(playFilePath)
3453
+ resolveDir: dirname4(playFilePath)
3160
3454
  })
3161
3455
  );
3162
3456
  }
@@ -3320,7 +3614,7 @@ function importedPlayProxyPlugin(importedPlayDependencies) {
3320
3614
  return {
3321
3615
  contents: buildImportedPlayProxyModule(dependency.playName),
3322
3616
  loader: "ts",
3323
- resolveDir: dirname3(args.path)
3617
+ resolveDir: dirname4(args.path)
3324
3618
  };
3325
3619
  });
3326
3620
  }
@@ -3338,7 +3632,7 @@ async function resolveLocalImport(fromFile, specifier) {
3338
3632
  if (specifier.startsWith("file:")) {
3339
3633
  return normalizeLocalPath(new URL(specifier).pathname);
3340
3634
  }
3341
- const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname3(fromFile), specifier);
3635
+ const base = isAbsolute(specifier) ? resolve4(specifier) : resolve4(dirname4(fromFile), specifier);
3342
3636
  const candidates = [base];
3343
3637
  const explicitExtension = extname(base).toLowerCase();
3344
3638
  if (!explicitExtension) {
@@ -3588,7 +3882,7 @@ async function runEsbuildForCjsNode(entryFile, importedPlayDependencies, adapter
3588
3882
  ...namedExportShim ? {
3589
3883
  stdin: {
3590
3884
  contents: namedExportShim,
3591
- resolveDir: dirname3(entryFile),
3885
+ resolveDir: dirname4(entryFile),
3592
3886
  sourcefile: `${basename(entryFile)}.${exportName}.entry.ts`,
3593
3887
  loader: "ts"
3594
3888
  }
@@ -3844,13 +4138,6 @@ var PLAY_DEDUP_BACKENDS = {
3844
4138
 
3845
4139
  // ../shared_libs/play-runtime/profiles.ts
3846
4140
  var PLAY_EXECUTION_PROFILES = {
3847
- legacy: {
3848
- id: "legacy",
3849
- scheduler: PLAY_SCHEDULER_BACKENDS.temporal,
3850
- runner: PLAY_RUNTIME_BACKENDS.daytona,
3851
- dedup: PLAY_DEDUP_BACKENDS.inMemory,
3852
- label: "Daytona + Temporal (production today)"
3853
- },
3854
4141
  workers_edge: {
3855
4142
  id: "workers_edge",
3856
4143
  scheduler: PLAY_SCHEDULER_BACKENDS.cfWorkflows,
@@ -3885,7 +4172,7 @@ function resolveExecutionProfile(override) {
3885
4172
  // src/plays/local-file-discovery.ts
3886
4173
  import { createHash as createHash2 } from "crypto";
3887
4174
  import { readFile as readFile2, stat as stat2 } from "fs/promises";
3888
- import { basename as basename2, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
4175
+ import { basename as basename2, dirname as dirname5, extname as extname2, isAbsolute as isAbsolute2, join as join4, relative, resolve as resolve5 } from "path";
3889
4176
  var SOURCE_EXTENSIONS2 = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".json"];
3890
4177
  function sha2562(buffer) {
3891
4178
  return createHash2("sha256").update(buffer).digest("hex");
@@ -4086,7 +4373,7 @@ function isPathInsideDirectory2(filePath, directory) {
4086
4373
  return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute2(relativePath);
4087
4374
  }
4088
4375
  async function resolveLocalImport2(fromFile, specifier) {
4089
- const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname4(fromFile), specifier);
4376
+ const base = isAbsolute2(specifier) ? resolve5(specifier) : resolve5(dirname5(fromFile), specifier);
4090
4377
  const candidates = [base];
4091
4378
  const explicitExtension = extname2(base).toLowerCase();
4092
4379
  if (!explicitExtension) {
@@ -4105,7 +4392,7 @@ async function resolveLocalImport2(fromFile, specifier) {
4105
4392
  }
4106
4393
  async function discoverPackagedLocalFiles(entryFile) {
4107
4394
  const absoluteEntryFile = resolve5(entryFile);
4108
- const packagingRoot = dirname4(absoluteEntryFile);
4395
+ const packagingRoot = dirname5(absoluteEntryFile);
4109
4396
  const files = /* @__PURE__ */ new Map();
4110
4397
  const unresolved = [];
4111
4398
  const visitedFiles = /* @__PURE__ */ new Set();
@@ -4142,7 +4429,7 @@ async function discoverPackagedLocalFiles(entryFile) {
4142
4429
  message: "Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file."
4143
4430
  });
4144
4431
  } else {
4145
- const absoluteCsvPath = resolve5(dirname4(absolutePath), resolvedPath);
4432
+ const absoluteCsvPath = resolve5(dirname5(absolutePath), resolvedPath);
4146
4433
  if (isAbsolute2(resolvedPath) || !isPathInsideDirectory2(absoluteCsvPath, packagingRoot)) {
4147
4434
  unresolved.push({
4148
4435
  sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
@@ -4181,14 +4468,14 @@ async function discoverPackagedLocalFiles(entryFile) {
4181
4468
 
4182
4469
  // src/plays/bundle-play-file.ts
4183
4470
  var PLAY_BUNDLE_CACHE_VERSION2 = 26;
4184
- var MODULE_DIR = dirname5(fileURLToPath(import.meta.url));
4471
+ var MODULE_DIR = dirname6(fileURLToPath(import.meta.url));
4185
4472
  var SDK_PACKAGE_ROOT = resolve6(MODULE_DIR, "..", "..");
4186
4473
  var SOURCE_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "..");
4187
- var HAS_SOURCE_BUNDLING_SOURCES = existsSync3(
4474
+ var HAS_SOURCE_BUNDLING_SOURCES = existsSync4(
4188
4475
  resolve6(SOURCE_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4189
4476
  );
4190
4477
  var PACKAGED_REPO_ROOT = resolve6(SDK_PACKAGE_ROOT, "dist", "repo");
4191
- var HAS_PACKAGED_BUNDLING_SOURCES = existsSync3(
4478
+ var HAS_PACKAGED_BUNDLING_SOURCES = existsSync4(
4192
4479
  resolve6(PACKAGED_REPO_ROOT, "apps", "play-runner-workers", "src", "entry.ts")
4193
4480
  );
4194
4481
  var PROJECT_ROOT = HAS_SOURCE_BUNDLING_SOURCES ? SOURCE_REPO_ROOT : HAS_PACKAGED_BUNDLING_SOURCES ? PACKAGED_REPO_ROOT : resolve6(SDK_PACKAGE_ROOT, "..");
@@ -4227,7 +4514,7 @@ function createSdkPlayBundlingAdapter() {
4227
4514
  sdkSourceRoot: SDK_SOURCE_ROOT,
4228
4515
  sdkPackageJson: SDK_PACKAGE_JSON,
4229
4516
  sdkEntryFile: SDK_ENTRY_FILE,
4230
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync3(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
4517
+ sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES || !existsSync4(SDK_TYPES_ENTRY_FILE) ? SDK_ENTRY_FILE : SDK_TYPES_ENTRY_FILE,
4231
4518
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
4232
4519
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
4233
4520
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -4513,15 +4800,15 @@ function materializeRemotePlaySource(input) {
4513
4800
  return null;
4514
4801
  }
4515
4802
  const outputPath = input.outPath ?? defaultMaterializedPlayPath(input.playName);
4516
- if (existsSync4(outputPath)) {
4803
+ if (existsSync5(outputPath)) {
4517
4804
  const existingSource = readFileSync3(outputPath, "utf-8");
4518
4805
  if (existingSource === input.sourceCode) {
4519
4806
  return { path: outputPath, status: "unchanged", created: false };
4520
4807
  }
4521
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4808
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4522
4809
  return { path: outputPath, status: "updated", created: false };
4523
4810
  }
4524
- writeFileSync4(outputPath, input.sourceCode, "utf-8");
4811
+ writeFileSync5(outputPath, input.sourceCode, "utf-8");
4525
4812
  return { path: outputPath, status: "created", created: true };
4526
4813
  }
4527
4814
  function formatLoadedPlayMessage(materializedFile) {
@@ -4566,7 +4853,7 @@ function extractPlayName(code, filePath) {
4566
4853
  throw buildMissingDefinePlayError(filePath);
4567
4854
  }
4568
4855
  function isFileTarget(target) {
4569
- return existsSync4(resolve7(target));
4856
+ return existsSync5(resolve7(target));
4570
4857
  }
4571
4858
  function looksLikeFilePath(target) {
4572
4859
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
@@ -4695,7 +4982,7 @@ function applyCsvShortcutInput(input) {
4695
4982
  function isLocalFilePathValue(value) {
4696
4983
  if (typeof value !== "string" || !value.trim()) return false;
4697
4984
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim())) return false;
4698
- return existsSync4(resolve7(value));
4985
+ return existsSync5(resolve7(value));
4699
4986
  }
4700
4987
  function inputContainsLocalFilePath(value) {
4701
4988
  if (isLocalFilePathValue(value)) {
@@ -4900,7 +5187,7 @@ function formatTimestamp(value) {
4900
5187
  function formatRunLine(run) {
4901
5188
  return `${run.workflowId} ${run.status} ${formatTimestamp(run.startTime)}`;
4902
5189
  }
4903
- function isTransientPlayStatusPollError(error) {
5190
+ function isTransientPlayStreamError(error) {
4904
5191
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
4905
5192
  return error.statusCode >= 500 && error.statusCode < 600;
4906
5193
  }
@@ -4909,12 +5196,6 @@ function isTransientPlayStatusPollError(error) {
4909
5196
  text
4910
5197
  );
4911
5198
  }
4912
- function isTerminalPlayStatusPollError(input) {
4913
- if (input.error instanceof DeeplineError && input.error.statusCode === 404 && input.hasSeenRun) {
4914
- return true;
4915
- }
4916
- return false;
4917
- }
4918
5199
  var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
4919
5200
  "completed",
4920
5201
  "failed",
@@ -4980,6 +5261,12 @@ function buildPlayDashboardUrl(baseUrl, playName) {
4980
5261
  const encodedPlayName = encodeURIComponent(playName);
4981
5262
  return `${trimmedBase}/dashboard/plays/${encodedPlayName}`;
4982
5263
  }
5264
+ function openPlayDashboard(input) {
5265
+ if (input.jsonOutput || input.noOpen || !input.dashboardUrl) {
5266
+ return;
5267
+ }
5268
+ openInBrowser(input.dashboardUrl);
5269
+ }
4983
5270
  function getDashboardUrlFromLiveEvent(event) {
4984
5271
  const dashboardUrl = getEventPayload(event).dashboardUrl;
4985
5272
  return typeof dashboardUrl === "string" && dashboardUrl.trim() ? dashboardUrl.trim() : null;
@@ -5016,6 +5303,68 @@ function assertPlayWaitNotTimedOut(input) {
5016
5303
  );
5017
5304
  }
5018
5305
  }
5306
+ async function waitForPlayCompletionByStream(input) {
5307
+ const controller = new AbortController();
5308
+ let timedOut = false;
5309
+ let lastPhase = null;
5310
+ const timeout = input.waitTimeoutMs === null ? null : setTimeout(
5311
+ () => {
5312
+ timedOut = true;
5313
+ controller.abort();
5314
+ },
5315
+ Math.max(1, input.waitTimeoutMs - (Date.now() - input.startedAt))
5316
+ );
5317
+ try {
5318
+ for await (const event of input.client.streamPlayRunEvents(
5319
+ input.workflowId,
5320
+ { signal: controller.signal }
5321
+ )) {
5322
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5323
+ const phase = describeLiveEventPhase(event);
5324
+ if (phase) {
5325
+ lastPhase = phase;
5326
+ input.progress.phase(phase);
5327
+ }
5328
+ printPlayLogLines({
5329
+ lines: getLogLinesFromLiveEvent(event),
5330
+ status: null,
5331
+ jsonOutput: input.jsonOutput,
5332
+ emitLogs: input.emitLogs,
5333
+ state: input.state,
5334
+ progress: input.progress
5335
+ });
5336
+ const status = getStatusFromLiveEvent(event);
5337
+ if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
5338
+ const finalStatus = await input.client.getPlayStatus(input.workflowId, {
5339
+ billing: false
5340
+ });
5341
+ if (TERMINAL_PLAY_STATUSES2.has(finalStatus.status)) {
5342
+ return finalStatus;
5343
+ }
5344
+ }
5345
+ }
5346
+ } catch (error) {
5347
+ if (timedOut) {
5348
+ assertPlayWaitNotTimedOut({ ...input, lastPhase });
5349
+ }
5350
+ throw error;
5351
+ } finally {
5352
+ if (timeout) {
5353
+ clearTimeout(timeout);
5354
+ }
5355
+ }
5356
+ const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
5357
+ throw new DeeplineError(
5358
+ `Play live stream ended before the run reached a terminal state runId=${input.workflowId}${phaseSuffix}.`,
5359
+ void 0,
5360
+ "PLAY_LIVE_STREAM_ENDED",
5361
+ {
5362
+ runId: input.workflowId,
5363
+ workflowId: input.workflowId,
5364
+ ...lastPhase ? { phase: lastPhase } : {}
5365
+ }
5366
+ );
5367
+ }
5019
5368
  async function startAndWaitForPlayCompletionByStream(input) {
5020
5369
  const startedAt = Date.now();
5021
5370
  const state = {
@@ -5049,6 +5398,11 @@ async function startAndWaitForPlayCompletionByStream(input) {
5049
5398
  const workflowId = lastKnownWorkflowId || "pending";
5050
5399
  if (workflowId !== "pending" && !emittedDashboardUrl) {
5051
5400
  const dashboardUrl = getDashboardUrlFromLiveEvent(event) ?? buildPlayDashboardUrl(input.client.baseUrl, input.playName);
5401
+ openPlayDashboard({
5402
+ dashboardUrl,
5403
+ jsonOutput: input.jsonOutput,
5404
+ noOpen: input.noOpen
5405
+ });
5052
5406
  if (!input.jsonOutput) {
5053
5407
  writeStartedPlayRun({
5054
5408
  runId: workflowId,
@@ -5104,21 +5458,21 @@ async function startAndWaitForPlayCompletionByStream(input) {
5104
5458
  lastPhase
5105
5459
  });
5106
5460
  }
5107
- if (lastKnownWorkflowId && isTransientPlayStatusPollError(error)) {
5461
+ if (lastKnownWorkflowId && isTransientPlayStreamError(error)) {
5108
5462
  if (timeout) {
5109
5463
  clearTimeout(timeout);
5110
5464
  }
5111
5465
  const reason = error instanceof Error ? error.message : String(error);
5112
5466
  if (!input.jsonOutput) {
5113
5467
  process.stderr.write(
5114
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; falling back to polling (${reason})
5468
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
5115
5469
  `
5116
5470
  );
5117
5471
  }
5118
5472
  recordCliTrace({
5119
- phase: "cli.play_start_stream_fallback",
5473
+ phase: "cli.play_start_stream_reconnect",
5120
5474
  ms: Date.now() - startedAt,
5121
- ok: false,
5475
+ ok: true,
5122
5476
  playName: input.playName,
5123
5477
  workflowId: lastKnownWorkflowId,
5124
5478
  eventCount,
@@ -5126,10 +5480,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
5126
5480
  lastPhase,
5127
5481
  reason
5128
5482
  });
5129
- return waitForPlayCompletionByPolling({
5483
+ return waitForPlayCompletionByStream({
5130
5484
  client: input.client,
5131
5485
  workflowId: lastKnownWorkflowId,
5132
- pollIntervalMs: 500,
5133
5486
  jsonOutput: input.jsonOutput,
5134
5487
  emitLogs: input.emitLogs,
5135
5488
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5147,13 +5500,13 @@ async function startAndWaitForPlayCompletionByStream(input) {
5147
5500
  if (lastKnownWorkflowId) {
5148
5501
  if (!input.jsonOutput) {
5149
5502
  input.progress.writeLine(
5150
- `[play watch] start stream ended after run ${lastKnownWorkflowId}; falling back to polling`
5503
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
5151
5504
  );
5152
5505
  }
5153
5506
  recordCliTrace({
5154
- phase: "cli.play_start_stream_fallback",
5507
+ phase: "cli.play_start_stream_reconnect",
5155
5508
  ms: Date.now() - startedAt,
5156
- ok: false,
5509
+ ok: true,
5157
5510
  playName: input.playName,
5158
5511
  workflowId: lastKnownWorkflowId,
5159
5512
  eventCount,
@@ -5161,10 +5514,9 @@ async function startAndWaitForPlayCompletionByStream(input) {
5161
5514
  lastPhase,
5162
5515
  reason: "stream ended before terminal event"
5163
5516
  });
5164
- return waitForPlayCompletionByPolling({
5517
+ return waitForPlayCompletionByStream({
5165
5518
  client: input.client,
5166
5519
  workflowId: lastKnownWorkflowId,
5167
- pollIntervalMs: 500,
5168
5520
  jsonOutput: input.jsonOutput,
5169
5521
  emitLogs: input.emitLogs,
5170
5522
  waitTimeoutMs: input.waitTimeoutMs,
@@ -5185,72 +5537,6 @@ async function startAndWaitForPlayCompletionByStream(input) {
5185
5537
  }
5186
5538
  );
5187
5539
  }
5188
- async function waitForPlayCompletionByPolling(input) {
5189
- let lastTransientPollWarningAt = 0;
5190
- let hasSeenRun = false;
5191
- while (true) {
5192
- assertPlayWaitNotTimedOut(input);
5193
- let status;
5194
- try {
5195
- status = await input.client.getPlayTailStatus(input.workflowId, {
5196
- afterLogIndex: input.state.lastLogIndex,
5197
- // Keep the server-side tail wait close to the caller's requested poll
5198
- // cadence. A long wait makes tiny remote runs look slow whenever the
5199
- // terminal update lands just after the held request starts.
5200
- waitMs: Math.max(50, Math.min(input.pollIntervalMs, 1e3))
5201
- });
5202
- } catch (error) {
5203
- if (isTerminalPlayStatusPollError({ error, hasSeenRun })) {
5204
- throw new DeeplineError(
5205
- `Play run ${input.workflowId} no longer exists on the server (404). The run was deleted or the backend lost it.`,
5206
- 404,
5207
- "PLAY_RUN_NOT_FOUND"
5208
- );
5209
- }
5210
- if (!isTransientPlayStatusPollError(error)) {
5211
- throw error;
5212
- }
5213
- const now = Date.now();
5214
- if (now - lastTransientPollWarningAt >= 3e4) {
5215
- const message = error instanceof Error ? error.message : String(error);
5216
- input.progress.writeLine(
5217
- `[play tail] transient status poll failed; retrying: ${message}`
5218
- );
5219
- lastTransientPollWarningAt = now;
5220
- }
5221
- await new Promise(
5222
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5223
- );
5224
- continue;
5225
- }
5226
- hasSeenRun = true;
5227
- const logs = status.progress?.logs ?? [];
5228
- input.progress.phase(status.status);
5229
- printPlayLogLines({
5230
- lines: logs.slice(input.state.lastLogIndex),
5231
- status,
5232
- jsonOutput: input.jsonOutput,
5233
- emitLogs: input.emitLogs,
5234
- state: input.state,
5235
- progress: input.progress
5236
- });
5237
- if (TERMINAL_PLAY_STATUSES2.has(status.status)) {
5238
- return status.result !== void 0 ? status : await input.client.getPlayStatus(input.workflowId, { billing: false });
5239
- }
5240
- const authoritativeStatus = await input.client.getPlayStatus(
5241
- input.workflowId,
5242
- { billing: false }
5243
- );
5244
- if (TERMINAL_PLAY_STATUSES2.has(authoritativeStatus.status)) {
5245
- return authoritativeStatus;
5246
- }
5247
- if ((status.progress?.logs ?? []).length === input.state.lastLogIndex) {
5248
- await new Promise(
5249
- (resolvePromise) => setTimeout(resolvePromise, input.pollIntervalMs)
5250
- );
5251
- }
5252
- }
5253
- }
5254
5540
  function formatInteger(value) {
5255
5541
  return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
5256
5542
  }
@@ -5391,7 +5677,6 @@ function buildRunWarnings(status, rowsInfo) {
5391
5677
  function buildRunNextCommands(runId, rowsInfo) {
5392
5678
  const commands = {
5393
5679
  get: `deepline runs get ${runId} --json`,
5394
- tail: `deepline runs tail ${runId} --json`,
5395
5680
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
5396
5681
  logs: `deepline runs logs ${runId} --out run.log --json`
5397
5682
  };
@@ -5507,10 +5792,9 @@ function normalizeErrorsForEnvelope(status, error) {
5507
5792
  }
5508
5793
  function normalizeLogsForEnvelope(status) {
5509
5794
  const logs = Array.isArray(status.progress?.logs) ? status.progress.logs : [];
5510
- const offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
5511
- const totalCount = offset + logs.length;
5795
+ const totalCount = logs.length;
5512
5796
  const entries = logs.slice(Math.max(0, logs.length - RUN_LOG_PREVIEW_LIMIT));
5513
- const firstSequence = entries.length === 0 ? null : offset + logs.length - entries.length + 1;
5797
+ const firstSequence = entries.length === 0 ? null : logs.length - entries.length + 1;
5514
5798
  const lastSequence = totalCount === 0 ? null : totalCount;
5515
5799
  return {
5516
5800
  totalCount,
@@ -5756,7 +6040,6 @@ function writeStartedPlayRun(input) {
5756
6040
  workflowId: input.runId,
5757
6041
  name: input.playName,
5758
6042
  status: input.status ?? "started",
5759
- statusUrl: input.statusUrl,
5760
6043
  dashboardUrl: input.dashboardUrl
5761
6044
  };
5762
6045
  if (input.jsonOutput) {
@@ -5768,7 +6051,7 @@ function writeStartedPlayRun(input) {
5768
6051
  `Started ${input.playName}`,
5769
6052
  ` run id: ${input.runId}`,
5770
6053
  ` get status: deepline runs get ${input.runId} --json`,
5771
- ` tail logs: deepline runs tail ${input.runId} --json`,
6054
+ ` logs: deepline runs logs ${input.runId} --json`,
5772
6055
  ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
5773
6056
  ` result JSON: deepline runs get ${input.runId} --json`
5774
6057
  ];
@@ -5783,7 +6066,7 @@ function writeStartedPlayRun(input) {
5783
6066
  console.log(output);
5784
6067
  }
5785
6068
  function parsePlayRunOptions(args) {
5786
- 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]";
6069
+ 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.";
5787
6070
  let filePath = null;
5788
6071
  let playName = null;
5789
6072
  let input = null;
@@ -5793,8 +6076,8 @@ function parsePlayRunOptions(args) {
5793
6076
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
5794
6077
  const emitLogs = !jsonOutput || args.includes("--logs");
5795
6078
  const force = args.includes("--force");
6079
+ const noOpen = args.includes("--no-open");
5796
6080
  let outPath = null;
5797
- let pollIntervalMs = 500;
5798
6081
  let waitTimeoutMs = null;
5799
6082
  for (let index = 0; index < args.length; index += 1) {
5800
6083
  const arg = args[index];
@@ -5826,15 +6109,16 @@ function parsePlayRunOptions(args) {
5826
6109
  outPath = resolve7(args[++index]);
5827
6110
  continue;
5828
6111
  }
5829
- if ((arg === "--poll-interval-ms" || arg === "--interval-ms") && args[index + 1]) {
5830
- pollIntervalMs = parsePositiveInteger2(args[++index], arg);
5831
- continue;
6112
+ if (arg === "--poll-interval-ms" || arg === "--interval-ms") {
6113
+ throw new Error(
6114
+ `${arg} was removed. --watch uses the canonical run stream directly.`
6115
+ );
5832
6116
  }
5833
6117
  if ((arg === "--tail-timeout-ms" || arg === "--timeout-ms") && args[index + 1]) {
5834
6118
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
5835
6119
  continue;
5836
6120
  }
5837
- if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force") {
6121
+ if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
5838
6122
  if (arg === "--watch") {
5839
6123
  continue;
5840
6124
  }
@@ -5903,10 +6187,10 @@ function parsePlayRunOptions(args) {
5903
6187
  watch,
5904
6188
  emitLogs,
5905
6189
  jsonOutput,
5906
- pollIntervalMs,
5907
6190
  waitTimeoutMs,
5908
6191
  force,
5909
- outPath
6192
+ outPath,
6193
+ noOpen
5910
6194
  };
5911
6195
  }
5912
6196
  function parsePlayCheckOptions(args) {
@@ -6081,6 +6365,7 @@ async function handleFileBackedRun(options) {
6081
6365
  jsonOutput: options.jsonOutput,
6082
6366
  emitLogs: options.emitLogs,
6083
6367
  waitTimeoutMs: options.waitTimeoutMs,
6368
+ noOpen: options.noOpen,
6084
6369
  progress
6085
6370
  })
6086
6371
  );
@@ -6108,14 +6393,19 @@ async function handleFileBackedRun(options) {
6108
6393
  () => client.startPlayRun(startRequest)
6109
6394
  );
6110
6395
  const dashboardUrl = buildPlayDashboardUrl(client.baseUrl, playName);
6111
- progress.phase(`loading play on ${dashboardUrl}`);
6396
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6397
+ openPlayDashboard({
6398
+ dashboardUrl: resolvedDashboardUrl,
6399
+ jsonOutput: options.jsonOutput,
6400
+ noOpen: options.noOpen
6401
+ });
6402
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
6112
6403
  progress.complete();
6113
6404
  writeStartedPlayRun({
6114
6405
  runId: started.workflowId,
6115
6406
  playName,
6116
6407
  status: started.status,
6117
- statusUrl: started.statusUrl,
6118
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6408
+ dashboardUrl: resolvedDashboardUrl,
6119
6409
  jsonOutput: options.jsonOutput,
6120
6410
  progress
6121
6411
  });
@@ -6220,6 +6510,7 @@ async function handleNamedRun(options) {
6220
6510
  jsonOutput: options.jsonOutput,
6221
6511
  emitLogs: options.emitLogs,
6222
6512
  waitTimeoutMs: options.waitTimeoutMs,
6513
+ noOpen: options.noOpen,
6223
6514
  progress
6224
6515
  })
6225
6516
  );
@@ -6250,14 +6541,19 @@ async function handleNamedRun(options) {
6250
6541
  client.baseUrl,
6251
6542
  playName
6252
6543
  );
6253
- progress.phase(`loading play on ${dashboardUrl}`);
6544
+ const resolvedDashboardUrl = started.dashboardUrl ?? dashboardUrl;
6545
+ openPlayDashboard({
6546
+ dashboardUrl: resolvedDashboardUrl,
6547
+ jsonOutput: options.jsonOutput,
6548
+ noOpen: options.noOpen
6549
+ });
6550
+ progress.phase(`loading play on ${resolvedDashboardUrl}`);
6254
6551
  progress.complete();
6255
6552
  writeStartedPlayRun({
6256
6553
  runId: started.workflowId,
6257
6554
  playName: started.name ?? playName,
6258
6555
  status: started.status,
6259
- statusUrl: started.statusUrl,
6260
- dashboardUrl: started.dashboardUrl ?? dashboardUrl,
6556
+ dashboardUrl: resolvedDashboardUrl,
6261
6557
  jsonOutput: options.jsonOutput,
6262
6558
  progress
6263
6559
  });
@@ -6271,8 +6567,8 @@ async function handlePlayRun(args) {
6271
6567
  }
6272
6568
  const resolved = resolve7(options.target.path);
6273
6569
  console.error(`File not found: ${resolved}`);
6274
- const dir = dirname6(resolved);
6275
- if (existsSync4(dir)) {
6570
+ const dir = dirname7(resolved);
6571
+ if (existsSync5(dir)) {
6276
6572
  const base = basename3(resolved);
6277
6573
  try {
6278
6574
  const siblings = readdirSync(dir).filter(
@@ -6300,7 +6596,7 @@ function parseRunIdPositional(args, usage) {
6300
6596
  }
6301
6597
  continue;
6302
6598
  }
6303
- if ((arg === "--out" || arg === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
6599
+ if ((arg === "--out" || arg === "--reason") && args[index + 1]) {
6304
6600
  index += 1;
6305
6601
  continue;
6306
6602
  }
@@ -6385,7 +6681,7 @@ async function handleRunsList(args) {
6385
6681
  return 0;
6386
6682
  }
6387
6683
  async function handleRunTail(args) {
6388
- const usage = "Usage: deepline runs tail <run-id> [--json] [--compact] [--cursor <cursor>]";
6684
+ const usage = "Usage: deepline runs tail <run-id> [--json] [--compact]";
6389
6685
  let runId;
6390
6686
  try {
6391
6687
  runId = parseRunIdPositional(args, usage);
@@ -6393,20 +6689,19 @@ async function handleRunTail(args) {
6393
6689
  console.error(error instanceof Error ? error.message : usage);
6394
6690
  return 1;
6395
6691
  }
6396
- const client = new DeeplineClient();
6397
- let afterLogIndex;
6398
6692
  for (let index = 0; index < args.length; index += 1) {
6399
6693
  const arg = args[index];
6400
- if (arg === "--cursor" && args[index + 1]) {
6401
- const parsed = Number(args[++index]);
6402
- if (Number.isInteger(parsed) && parsed >= 0) {
6403
- afterLogIndex = parsed;
6404
- }
6694
+ if (arg === "--cursor") {
6695
+ console.error("--cursor was removed. deepline runs tail reads the canonical run stream.");
6696
+ return 1;
6697
+ }
6698
+ if (arg.startsWith("--") && arg !== "--json" && arg !== "--compact") {
6699
+ console.error(`${arg} is not supported by deepline runs tail.`);
6700
+ return 1;
6405
6701
  }
6406
6702
  }
6407
- const status = await client.runs.tail(runId, {
6408
- ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
6409
- });
6703
+ const client = new DeeplineClient();
6704
+ const status = await client.runs.tail(runId);
6410
6705
  writePlayResult(status, argsWantJson(args));
6411
6706
  return status.status === "failed" ? 1 : 0;
6412
6707
  }
@@ -6435,7 +6730,7 @@ async function handleRunLogs(args) {
6435
6730
  const status = await client.runs.get(runId);
6436
6731
  const logs = status.progress?.logs ?? [];
6437
6732
  if (outPath) {
6438
- writeFileSync4(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6733
+ writeFileSync5(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
6439
6734
  if (argsWantJson(args)) {
6440
6735
  process.stdout.write(
6441
6736
  `${JSON.stringify({
@@ -6947,8 +7242,9 @@ function registerPlayCommands(program) {
6947
7242
  "after",
6948
7243
  `
6949
7244
  Concepts:
6950
- Plays are durable Deepline cloud workflows. Local .play.ts files are bundled locally,
6951
- then validated and executed in Deepline cloud.
7245
+ Plays are durable cloud workflows.
7246
+ Stable ctx.tools.execute({ id, tool, input }) calls are replay-safe.
7247
+ ctx.map adds row keys and row progress.
6952
7248
 
6953
7249
  Common commands:
6954
7250
  deepline plays search email --json
@@ -6979,17 +7275,42 @@ Examples:
6979
7275
  "after",
6980
7276
  `
6981
7277
  Notes:
6982
- Local play files are bundled locally, then validated and executed in Deepline cloud.
6983
- Named plays run the stored live cloud revision.
6984
- Unknown --foo and --foo.bar flags are treated as play input args.
6985
- File-like input args accept local paths; the CLI stages those files before submit.
6986
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
6987
- to validate without starting a run.
7278
+ Local files are bundled, preflighted, then run in Deepline cloud.
7279
+ Named plays run the live saved revision.
7280
+ Unknown --foo and --foo.bar flags are treated as play input args.
7281
+ File args accept local paths; the CLI stages files before submit.
7282
+ --watch prints logs, previews, stats, and next commands.
7283
+ The play page opens in your browser as soon as the run starts; use --no-open
7284
+ to only print the URL.
7285
+ --force supersedes active runs; it does not bypass completed reuse.
7286
+
7287
+ Idempotent execution:
7288
+ Stable tool call ids are the reuse key:
7289
+
7290
+ await ctx.tools.execute({ id: 'company_lookup', tool, input });
7291
+
7292
+ For rows, use ctx.map plus a stable row key:
7293
+
7294
+ const rows = await ctx
7295
+ .map('companies_v1', companies)
7296
+ .step('cto', (row, ctx) => ctx.tools.execute({
7297
+ id: 'find_cto',
7298
+ tool: 'apollo_search_people_with_match',
7299
+ input: { q_organization_domains_list: [row.domain], per_page: 1 },
7300
+ }))
7301
+ .run({ key: 'domain' });
7302
+
7303
+ Reuse needs the same play, tool id, map name, row key, and compatible logic.
7304
+ To refresh, change the id/map key or set staleAfterSeconds:
7305
+
7306
+ .run({ key: 'domain', staleAfterSeconds: 86400 })
6988
7307
 
6989
7308
  Examples:
6990
7309
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
6991
7310
  deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
6992
7311
  deepline plays run enrich.play.ts --csv leads.csv --watch --out leads-enriched.csv
7312
+ deepline plays run cto-search.play.ts --limit 5 --watch
7313
+ deepline runs get <run-id>
6993
7314
  `
6994
7315
  ).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(
6995
7316
  "--revision-id <id>",
@@ -7000,7 +7321,7 @@ Examples:
7000
7321
  ).option("--watch", "Stream logs until completion").option(
7001
7322
  "--logs",
7002
7323
  "When output is non-interactive, stream play logs to stderr while waiting"
7003
- ).option("--poll-interval-ms <ms>", "Polling interval while tailing").option("--tail-timeout-ms <ms>", "Timeout while tailing").option("--force", "Supersede any active runs for this play").option("--json", "Emit JSON output").action(async (target, options, command) => {
7324
+ ).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) => {
7004
7325
  const passthroughArgs = [...command.args];
7005
7326
  const explicitTarget = options.file || options.name;
7006
7327
  const targetIsInputFlag = typeof target === "string" && target.startsWith("--");
@@ -7022,9 +7343,9 @@ Examples:
7022
7343
  ...options.out ? ["--out", options.out] : [],
7023
7344
  ...options.watch ? ["--watch"] : [],
7024
7345
  ...options.logs ? ["--logs"] : [],
7025
- ...options.pollIntervalMs ? ["--poll-interval-ms", options.pollIntervalMs] : [],
7026
7346
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
7027
7347
  ...options.force ? ["--force"] : [],
7348
+ ...options.open === false ? ["--no-open"] : [],
7028
7349
  ...options.json ? ["--json"] : [],
7029
7350
  ...passthroughArgs
7030
7351
  ]);
@@ -7131,12 +7452,11 @@ Examples:
7131
7452
  ...options.json ? ["--json"] : []
7132
7453
  ]);
7133
7454
  });
7134
- runs.command("tail <runId>").description("Tail or fetch recent live events for a play run.").option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--compact", "Drop verbose fields from JSON output").option("--cursor <cursor>", "Resume after a previously returned event cursor").action(async (runId, options) => {
7455
+ 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) => {
7135
7456
  process.exitCode = await handleRunTail([
7136
7457
  runId,
7137
7458
  ...options.json ? ["--json"] : [],
7138
- ...options.compact ? ["--compact"] : [],
7139
- ...options.cursor ? ["--cursor", options.cursor] : []
7459
+ ...options.compact ? ["--compact"] : []
7140
7460
  ]);
7141
7461
  });
7142
7462
  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) => {
@@ -7165,12 +7485,12 @@ Examples:
7165
7485
  }
7166
7486
 
7167
7487
  // src/cli/commands/tools.ts
7168
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync6 } from "fs";
7488
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync7 } from "fs";
7169
7489
  import { tmpdir as tmpdir3 } from "os";
7170
7490
  import { join as join8 } from "path";
7171
7491
 
7172
7492
  // src/tool-output.ts
7173
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
7493
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
7174
7494
  import { homedir as homedir3 } from "os";
7175
7495
  import { join as join7 } from "path";
7176
7496
  function isPlainObject(value) {
@@ -7248,13 +7568,13 @@ function tryConvertToList(payload, options) {
7248
7568
  }
7249
7569
  function ensureOutputDir() {
7250
7570
  const outputDir = join7(homedir3(), ".local", "share", "deepline", "data");
7251
- mkdirSync3(outputDir, { recursive: true });
7571
+ mkdirSync4(outputDir, { recursive: true });
7252
7572
  return outputDir;
7253
7573
  }
7254
7574
  function writeJsonOutputFile(payload, stem) {
7255
7575
  const outputDir = ensureOutputDir();
7256
7576
  const outputPath = join7(outputDir, `${stem}_${Date.now()}.json`);
7257
- writeFileSync5(outputPath, JSON.stringify(payload, null, 2), "utf-8");
7577
+ writeFileSync6(outputPath, JSON.stringify(payload, null, 2), "utf-8");
7258
7578
  return outputPath;
7259
7579
  }
7260
7580
  function writeCsvOutputFile(rows, stem) {
@@ -7282,7 +7602,7 @@ function writeCsvOutputFile(rows, stem) {
7282
7602
  for (const row of rows) {
7283
7603
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
7284
7604
  }
7285
- writeFileSync5(outputPath, `${lines.join("\n")}
7605
+ writeFileSync6(outputPath, `${lines.join("\n")}
7286
7606
  `, "utf-8");
7287
7607
  const previewRows = rows.slice(0, 5);
7288
7608
  const previewColumns = columns.slice(0, 5);
@@ -7819,7 +8139,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
7819
8139
  };
7820
8140
  });
7821
8141
  `;
7822
- writeFileSync6(scriptPath, script, { encoding: "utf-8", mode: 384 });
8142
+ writeFileSync7(scriptPath, script, { encoding: "utf-8", mode: 384 });
7823
8143
  return {
7824
8144
  path: scriptPath,
7825
8145
  projectDir,
@@ -7926,10 +8246,10 @@ async function executeTool(args) {
7926
8246
  }
7927
8247
 
7928
8248
  // src/cli/skills-sync.ts
7929
- import { spawn, spawnSync } from "child_process";
7930
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync7 } from "fs";
8249
+ import { spawn, spawnSync as spawnSync2 } from "child_process";
8250
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync8 } from "fs";
7931
8251
  import { homedir as homedir4 } from "os";
7932
- import { dirname as dirname7, join as join9 } from "path";
8252
+ import { dirname as dirname8, join as join9 } from "path";
7933
8253
  var CHECK_TIMEOUT_MS2 = 3e3;
7934
8254
  var SDK_SKILL_NAME = "deepline-sdk";
7935
8255
  var SKILL_AGENTS = ["codex", "claude-code", "cursor"];
@@ -7944,7 +8264,7 @@ function sdkSkillsVersionPath(baseUrl) {
7944
8264
  }
7945
8265
  function readLocalSkillsVersion(baseUrl) {
7946
8266
  const path = sdkSkillsVersionPath(baseUrl);
7947
- if (!existsSync5(path)) return "";
8267
+ if (!existsSync6(path)) return "";
7948
8268
  try {
7949
8269
  return readFileSync4(path, "utf-8").trim();
7950
8270
  } catch {
@@ -7953,8 +8273,8 @@ function readLocalSkillsVersion(baseUrl) {
7953
8273
  }
7954
8274
  function writeLocalSkillsVersion(baseUrl, version) {
7955
8275
  const path = sdkSkillsVersionPath(baseUrl);
7956
- mkdirSync4(dirname7(path), { recursive: true });
7957
- writeFileSync7(path, `${version}
8276
+ mkdirSync5(dirname8(path), { recursive: true });
8277
+ writeFileSync8(path, `${version}
7958
8278
  `, "utf-8");
7959
8279
  }
7960
8280
  async function fetchSkillsUpdate(baseUrl, localVersion) {
@@ -8018,7 +8338,7 @@ function buildBunxSkillsInstallArgs(baseUrl) {
8018
8338
  ];
8019
8339
  }
8020
8340
  function hasCommand(command) {
8021
- const result = spawnSync(command, ["--version"], {
8341
+ const result = spawnSync2(command, ["--version"], {
8022
8342
  stdio: "ignore",
8023
8343
  shell: process.platform === "win32"
8024
8344
  });