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 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.21";
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 : typeof raw.temporalStatus === "string" ? mapLegacyTemporalStatus(raw.temporalStatus) : "running";
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 legacy CLI: it ranks across
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 Workflow metadata including the `workflowId` for status polling
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
- * Consumers should still keep a polling fallback: SSE is the fast live-update
1140
- * transport, while the status endpoints remain the authoritative recovery path.
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 - Temporal workflow ID to cancel
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 - Temporal workflow ID to stop
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 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;
1259
- const params = new URLSearchParams();
1260
- if (Number.isFinite(afterLogIndex)) {
1261
- params.set("afterLogIndex", String(Number(afterLogIndex)));
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 (typeof options?.waitMs === "number") {
1264
- params.set("waitMs", String(options.waitMs));
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 (options?.terminalOnly) {
1267
- params.set("terminalOnly", "true");
1304
+ if (state.latest) {
1305
+ return state.latest;
1268
1306
  }
1269
- const suffix = params.toString() ? `?${params.toString()}` : "";
1270
- const response = await this.http.get(
1271
- `/api/v2/runs/${encodeURIComponent(runId)}/tail${suffix}`
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, poll until terminal, return result.
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
- * polls for status updates, and returns a structured result with logs
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
- while (true) {
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 = await this.getPlayStatus(workflowId);
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
- return {
1498
- success: status.status === "completed",
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
- if (process.platform === "darwin") (0, import_node_child_process.execSync)(`open "${url}"`, { stdio: "ignore" });
1607
- else if (process.platform === "win32")
1608
- (0, import_node_child_process.execSync)(`start "" "${url}"`, { stdio: "ignore" });
1609
- else (0, import_node_child_process.execSync)(`xdg-open "${url}"`, { stdio: "ignore" });
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 isTransientPlayStatusPollError(error) {
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 && isTransientPlayStatusPollError(error)) {
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}; falling back to polling (${reason})
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.play_start_stream_fallback",
5460
+ phase: "cli.play_start_stream_reconnect",
5139
5461
  ms: Date.now() - startedAt,
5140
- ok: false,
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 waitForPlayCompletionByPolling({
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}; falling back to polling`
5490
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
5170
5491
  );
5171
5492
  }
5172
5493
  recordCliTrace({
5173
- phase: "cli.play_start_stream_fallback",
5494
+ phase: "cli.play_start_stream_reconnect",
5174
5495
  ms: Date.now() - startedAt,
5175
- ok: false,
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 waitForPlayCompletionByPolling({
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 offset = typeof status.progress?.logOffset === "number" && Number.isFinite(status.progress.logOffset) ? Math.max(0, Math.trunc(status.progress.logOffset)) : 0;
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 : offset + logs.length - entries.length + 1;
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
- ` tail logs: deepline runs tail ${input.runId} --json`,
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 ((arg === "--poll-interval-ms" || arg === "--interval-ms") && args[index + 1]) {
5849
- pollIntervalMs = parsePositiveInteger2(args[++index], arg);
5850
- continue;
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
- progress.phase(`loading play on ${dashboardUrl}`);
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
- statusUrl: started.statusUrl,
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
- progress.phase(`loading play on ${dashboardUrl}`);
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
- statusUrl: started.statusUrl,
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 === "--cursor" || arg === "--reason" || arg === "--interval-ms" || arg === "--poll-interval-ms") && args[index + 1]) {
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] [--cursor <cursor>]";
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" && args[index + 1]) {
6420
- const parsed = Number(args[++index]);
6421
- if (Number.isInteger(parsed) && parsed >= 0) {
6422
- afterLogIndex = parsed;
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 status = await client.runs.tail(runId, {
6427
- ...afterLogIndex !== void 0 ? { afterLogIndex } : {}
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 Deepline cloud workflows. Local .play.ts files are bundled locally,
6970
- then validated and executed in Deepline cloud.
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
- Local play files are bundled locally, then validated and executed in Deepline cloud.
7002
- Named plays run the stored live cloud revision.
7003
- Unknown --foo and --foo.bar flags are treated as play input args.
7004
- File-like input args accept local paths; the CLI stages those files before submit.
7005
- Run performs server preflight automatically. Use \`deepline plays check <file>\`
7006
- to validate without starting a run.
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("--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) => {
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("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) => {
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) => {