deepline 0.1.91 → 0.1.94

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/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -43,6 +53,7 @@ __export(src_exports, {
43
53
  PLAY_BOOTSTRAP_TEMPLATES: () => PLAY_BOOTSTRAP_TEMPLATES,
44
54
  PROD_URL: () => PROD_URL,
45
55
  RateLimitError: () => RateLimitError,
56
+ RunObserveTransportUnavailableError: () => RunObserveTransportUnavailableError,
46
57
  SDK_API_CONTRACT: () => SDK_API_CONTRACT,
47
58
  SDK_VERSION: () => SDK_VERSION,
48
59
  defineInput: () => defineInput,
@@ -246,10 +257,10 @@ var import_node_path2 = require("path");
246
257
 
247
258
  // src/release.ts
248
259
  var SDK_RELEASE = {
249
- version: "0.1.91",
260
+ version: "0.1.94",
250
261
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
251
262
  supportPolicy: {
252
- latest: "0.1.91",
263
+ latest: "0.1.94",
253
264
  minimumSupported: "0.1.53",
254
265
  deprecatedBelow: "0.1.53"
255
266
  }
@@ -604,6 +615,807 @@ function sleep(ms) {
604
615
  return new Promise((resolve2) => setTimeout(resolve2, ms));
605
616
  }
606
617
 
618
+ // src/stream-reconnect.ts
619
+ var STREAM_RECONNECT_BASE_DELAY_MS = 500;
620
+ var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
621
+ var STREAM_HEALTHY_CONNECTION_MS = 3e4;
622
+ function streamReconnectDelayMs(attempt) {
623
+ const cappedExponentialMs = Math.min(
624
+ STREAM_RECONNECT_MAX_DELAY_MS,
625
+ STREAM_RECONNECT_BASE_DELAY_MS * 2 ** Math.max(0, attempt)
626
+ );
627
+ return Math.max(1, Math.floor(Math.random() * (cappedExponentialMs + 1)));
628
+ }
629
+ function isTransientPlayStreamError(error) {
630
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
631
+ return error.statusCode >= 500 && error.statusCode < 600;
632
+ }
633
+ const text = error instanceof Error ? error.message : String(error);
634
+ return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
635
+ text
636
+ );
637
+ }
638
+
639
+ // ../shared_libs/play-runtime/live-events.ts
640
+ function resolveTimingWindow(input) {
641
+ const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
642
+ const completedAt = typeof input.completedAt === "number" && Number.isFinite(input.completedAt) ? input.completedAt : null;
643
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : null;
644
+ return {
645
+ startedAt,
646
+ completedAt,
647
+ updatedAt
648
+ };
649
+ }
650
+
651
+ // ../shared_libs/play-runtime/run-ledger.ts
652
+ var LOG_TAIL_LIMIT = 100;
653
+ function isRecord(value) {
654
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
655
+ }
656
+ function finiteNumber(value) {
657
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
658
+ }
659
+ function optionalFiniteNumber(value) {
660
+ const normalized = finiteNumber(value);
661
+ return normalized === null ? void 0 : normalized;
662
+ }
663
+ function optionalString(value) {
664
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
665
+ }
666
+ function optionalNullableString(value) {
667
+ if (value === null) return null;
668
+ return optionalString(value);
669
+ }
670
+ function normalizePlayRunLedgerStatus(value) {
671
+ const normalized = String(value ?? "").trim().toLowerCase();
672
+ switch (normalized) {
673
+ case "queued":
674
+ case "pending":
675
+ return "queued";
676
+ case "running":
677
+ case "started":
678
+ return "running";
679
+ case "waiting":
680
+ return "waiting";
681
+ case "completed":
682
+ case "complete":
683
+ case "succeeded":
684
+ return "completed";
685
+ case "failed":
686
+ case "error":
687
+ return "failed";
688
+ case "cancelled":
689
+ case "canceled":
690
+ return "cancelled";
691
+ case "terminated":
692
+ return "terminated";
693
+ case "timed_out":
694
+ case "timeout":
695
+ return "timed_out";
696
+ default:
697
+ return "unknown";
698
+ }
699
+ }
700
+ function createEmptyPlayRunLedgerSnapshot(input) {
701
+ const status = normalizePlayRunLedgerStatus(input.status ?? "unknown");
702
+ const startedAt = finiteNumber(input.startedAt) ?? null;
703
+ const finishedAt = finiteNumber(input.finishedAt) ?? null;
704
+ return {
705
+ runId: input.runId,
706
+ playName: input.playName ?? null,
707
+ status,
708
+ error: optionalNullableString(input.error) ?? null,
709
+ createdAt: finiteNumber(input.createdAt) ?? null,
710
+ startedAt,
711
+ updatedAt: finiteNumber(input.updatedAt) ?? finishedAt ?? startedAt ?? finiteNumber(input.createdAt) ?? null,
712
+ finishedAt,
713
+ durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : null,
714
+ orderedStepIds: [],
715
+ stepsById: {},
716
+ logTail: [],
717
+ totalLogCount: 0,
718
+ logsTruncated: false,
719
+ activeStepId: null,
720
+ activeArtifactTableNamespace: null,
721
+ resultTableNamespace: null
722
+ };
723
+ }
724
+ function normalizePlayRunLedgerSnapshot(value, fallback) {
725
+ if (!isRecord(value)) {
726
+ return createEmptyPlayRunLedgerSnapshot(fallback);
727
+ }
728
+ const orderedStepIds = Array.isArray(value.orderedStepIds) ? value.orderedStepIds.filter(
729
+ (entry) => typeof entry === "string" && Boolean(entry.trim())
730
+ ) : [];
731
+ const rawSteps = isRecord(value.stepsById) ? value.stepsById : {};
732
+ const stepsById = {};
733
+ for (const [stepId, rawStep] of Object.entries(rawSteps)) {
734
+ if (!stepId.trim() || !isRecord(rawStep)) continue;
735
+ const rawStatus = normalizeStepStatus(rawStep.status);
736
+ if (!rawStatus) continue;
737
+ const completedAt = finiteNumber(rawStep.completedAt);
738
+ const status = rawStatus === "running" && completedAt !== null ? "completed" : rawStatus;
739
+ const rawProgress = isRecord(rawStep.progress) ? rawStep.progress : null;
740
+ stepsById[stepId] = {
741
+ stepId,
742
+ label: optionalString(rawStep.label),
743
+ kind: optionalString(rawStep.kind),
744
+ status,
745
+ artifactTableNamespace: optionalNullableString(
746
+ rawStep.artifactTableNamespace
747
+ ),
748
+ startedAt: finiteNumber(rawStep.startedAt),
749
+ completedAt,
750
+ updatedAt: finiteNumber(rawStep.updatedAt),
751
+ progress: rawProgress ? normalizeStepProgress(rawProgress) : null
752
+ };
753
+ }
754
+ const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
755
+ const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
756
+ const finishedAt = finiteNumber(value.finishedAt) ?? fallback.finishedAt ?? null;
757
+ const updatedAt = finiteNumber(value.updatedAt) ?? fallback.updatedAt ?? finishedAt ?? startedAt ?? createdAt ?? null;
758
+ const error = Object.prototype.hasOwnProperty.call(value, "error") ? optionalNullableString(value.error) ?? null : fallback.error ?? null;
759
+ const rawTail = Array.isArray(value.logTail) ? value.logTail : value.logs;
760
+ const logTail = Array.isArray(rawTail) ? rawTail.filter((line) => typeof line === "string") : [];
761
+ return {
762
+ runId: optionalString(value.runId) ?? fallback.runId,
763
+ playName: optionalNullableString(value.playName) ?? fallback.playName ?? null,
764
+ status: normalizePlayRunLedgerStatus(value.status ?? fallback.status),
765
+ error,
766
+ createdAt,
767
+ startedAt,
768
+ updatedAt,
769
+ finishedAt,
770
+ durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : finiteNumber(value.durationMs),
771
+ orderedStepIds: orderedStepIds.filter((stepId) => stepsById[stepId]),
772
+ stepsById,
773
+ logTail: logTail.slice(-LOG_TAIL_LIMIT),
774
+ // Snapshots persisted before totalLogCount existed only know the retained
775
+ // tail, so the best lower bound for the cumulative count is the tail size.
776
+ totalLogCount: Math.max(
777
+ finiteNumber(value.totalLogCount) ?? logTail.length,
778
+ logTail.length
779
+ ),
780
+ logsTruncated: value.logsTruncated === true,
781
+ activeStepId: optionalNullableString(value.activeStepId),
782
+ activeArtifactTableNamespace: optionalNullableString(
783
+ value.activeArtifactTableNamespace
784
+ ),
785
+ resultTableNamespace: optionalNullableString(value.resultTableNamespace),
786
+ resultSummary: value.resultSummary,
787
+ result: value.result
788
+ };
789
+ }
790
+ function normalizeStepStatus(value) {
791
+ const normalized = String(value ?? "").trim().toLowerCase();
792
+ if (normalized === "running" || normalized === "completed" || normalized === "failed" || normalized === "skipped") {
793
+ return normalized;
794
+ }
795
+ return null;
796
+ }
797
+ function normalizeStepProgress(value) {
798
+ return {
799
+ ...optionalFiniteNumber(value.completed) !== void 0 ? { completed: optionalFiniteNumber(value.completed) } : {},
800
+ ...optionalFiniteNumber(value.total) !== void 0 ? { total: optionalFiniteNumber(value.total) } : {},
801
+ ...optionalFiniteNumber(value.failed) !== void 0 ? { failed: optionalFiniteNumber(value.failed) } : {},
802
+ ...optionalString(value.message) ? { message: optionalString(value.message) } : {},
803
+ ...optionalNullableString(value.artifactTableNamespace) !== void 0 ? {
804
+ artifactTableNamespace: optionalNullableString(
805
+ value.artifactTableNamespace
806
+ )
807
+ } : {},
808
+ ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
809
+ };
810
+ }
811
+
812
+ // ../shared_libs/play-runtime/run-snapshot-stream.ts
813
+ function normalizePlayRunLiveStatus(value) {
814
+ const normalized = String(value ?? "").trim().toLowerCase();
815
+ switch (normalized) {
816
+ case "queued":
817
+ case "pending":
818
+ return "running";
819
+ case "running":
820
+ case "started":
821
+ return "running";
822
+ case "completed":
823
+ case "complete":
824
+ case "succeeded":
825
+ return "completed";
826
+ case "failed":
827
+ case "error":
828
+ return "failed";
829
+ case "cancelled":
830
+ case "canceled":
831
+ return "cancelled";
832
+ case "terminated":
833
+ return "terminated";
834
+ case "timed_out":
835
+ case "timeout":
836
+ return "timed_out";
837
+ default:
838
+ return "unknown";
839
+ }
840
+ }
841
+ function isTerminalPlayRunLiveStatus(status) {
842
+ return status === "completed" || status === "failed" || status === "cancelled" || status === "terminated" || status === "timed_out";
843
+ }
844
+ function buildSnapshotFromLedger(snapshot) {
845
+ const nodeStates = snapshot.orderedStepIds.map((stepId) => snapshot.stepsById[stepId]).filter((step) => Boolean(step)).map((step) => ({
846
+ nodeId: step.stepId,
847
+ status: step.status,
848
+ artifactTableNamespace: step.artifactTableNamespace ?? null,
849
+ progress: step.progress ? {
850
+ completed: step.progress.completed,
851
+ total: step.progress.total,
852
+ failed: step.progress.failed,
853
+ message: step.progress.message,
854
+ artifactTableNamespace: step.progress.artifactTableNamespace ?? step.artifactTableNamespace ?? null,
855
+ startedAt: step.startedAt ?? null,
856
+ completedAt: step.completedAt ?? null,
857
+ updatedAt: step.progress.updatedAt ?? step.updatedAt ?? null
858
+ } : null,
859
+ startedAt: step.startedAt ?? null,
860
+ completedAt: step.completedAt ?? null,
861
+ updatedAt: step.updatedAt ?? null
862
+ }));
863
+ return {
864
+ runId: snapshot.runId,
865
+ status: normalizePlayRunLiveStatus(snapshot.status),
866
+ updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
867
+ logs: snapshot.logTail,
868
+ totalLogCount: snapshot.totalLogCount,
869
+ ...snapshot.logsTruncated ? { logsTruncated: true } : {},
870
+ activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
871
+ resultTableNamespace: snapshot.resultTableNamespace ?? null,
872
+ nodeStates,
873
+ activeNodeId: snapshot.activeStepId ?? null
874
+ };
875
+ }
876
+ function buildPlayRunStatusSnapshot(input) {
877
+ const ledgerSnapshot = normalizePlayRunLedgerSnapshot(input.run.runSnapshot, {
878
+ runId: input.run.workflowId,
879
+ playName: input.run.name ?? null,
880
+ status: input.run.status,
881
+ createdAt: input.run.createdAt ?? null,
882
+ startedAt: input.run.startedAt ?? null,
883
+ updatedAt: input.run.updatedAt ?? null,
884
+ finishedAt: input.run.finishedAt ?? null
885
+ });
886
+ return buildSnapshotFromLedger(ledgerSnapshot);
887
+ }
888
+ function makeRunStreamEvent(input) {
889
+ return {
890
+ ...input,
891
+ scope: "play",
892
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString()
893
+ };
894
+ }
895
+ var EMPTY_PLAY_RUN_STREAM_DIFF_STATE = {
896
+ runSignature: "",
897
+ snapshotSignature: "",
898
+ stepStatusSignature: "",
899
+ stepProgressSignature: "",
900
+ lastLogSeq: 0
901
+ };
902
+ function getSnapshotCursor(snapshot) {
903
+ return String(snapshot.updatedAt ?? Date.now());
904
+ }
905
+ function getRunSignature(snapshot) {
906
+ return [snapshot.runId, snapshot.status, snapshot.updatedAt ?? 0].join(":");
907
+ }
908
+ function getStepStatusSignature(snapshot) {
909
+ return snapshot.nodeStates.map(
910
+ (state) => [
911
+ state.nodeId,
912
+ state.status,
913
+ state.artifactTableNamespace ?? "",
914
+ state.startedAt ?? "",
915
+ state.completedAt ?? "",
916
+ state.progress?.startedAt ?? "",
917
+ state.progress?.completedAt ?? "",
918
+ state.updatedAt ?? "",
919
+ state.progress?.updatedAt ?? ""
920
+ ].join(":")
921
+ ).join("|");
922
+ }
923
+ function getStepProgressSignature(snapshot) {
924
+ return snapshot.nodeStates.map(
925
+ (state) => [
926
+ state.nodeId,
927
+ state.progress?.completed ?? "",
928
+ state.progress?.total ?? "",
929
+ state.progress?.failed ?? "",
930
+ state.progress?.artifactTableNamespace ?? "",
931
+ state.progress?.startedAt ?? "",
932
+ state.progress?.completedAt ?? "",
933
+ state.progress?.updatedAt ?? "",
934
+ state.progress?.message ?? ""
935
+ ].join(":")
936
+ ).join("|");
937
+ }
938
+ function getSnapshotSignature(snapshot) {
939
+ return JSON.stringify(snapshot);
940
+ }
941
+ function resolvePlayRunLogGap(snapshot, lastLogSeq) {
942
+ if (snapshot.totalLogCount <= lastLogSeq) {
943
+ return null;
944
+ }
945
+ const tailFirstSeq = snapshot.totalLogCount - snapshot.logs.length + 1;
946
+ if (lastLogSeq + 1 >= tailFirstSeq) {
947
+ return null;
948
+ }
949
+ return {
950
+ missingCount: tailFirstSeq - 1 - lastLogSeq,
951
+ tailFirstSeq
952
+ };
953
+ }
954
+ function diffLogLines(input) {
955
+ const { logs, totalLogCount } = input.snapshot;
956
+ if (totalLogCount <= input.lastLogSeq) {
957
+ return { lines: [], lastLogSeq: input.lastLogSeq, firstSeq: null };
958
+ }
959
+ const tailFirstSeq = totalLogCount - logs.length + 1;
960
+ if (input.lastLogSeq + 1 >= tailFirstSeq) {
961
+ return {
962
+ lines: logs.slice(input.lastLogSeq + 1 - tailFirstSeq),
963
+ lastLogSeq: totalLogCount,
964
+ firstSeq: input.lastLogSeq + 1
965
+ };
966
+ }
967
+ const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
968
+ return {
969
+ lines: [
970
+ `[stream] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
971
+ ...logs
972
+ ],
973
+ lastLogSeq: totalLogCount,
974
+ firstSeq: null
975
+ };
976
+ }
977
+ function diffPlayRunStreamEvents(input) {
978
+ const { snapshot, streamId, previous } = input;
979
+ const cursor = getSnapshotCursor(snapshot);
980
+ const logDiff = diffLogLines({
981
+ snapshot,
982
+ lastLogSeq: previous.lastLogSeq
983
+ });
984
+ const next = {
985
+ runSignature: getRunSignature(snapshot),
986
+ stepStatusSignature: getStepStatusSignature(snapshot),
987
+ stepProgressSignature: getStepProgressSignature(snapshot),
988
+ snapshotSignature: getSnapshotSignature(snapshot),
989
+ lastLogSeq: logDiff.lastLogSeq
990
+ };
991
+ const events = [];
992
+ if (next.stepStatusSignature !== previous.stepStatusSignature) {
993
+ for (const state of snapshot.nodeStates) {
994
+ if (state.status === "idle") {
995
+ continue;
996
+ }
997
+ const persistedStartedAt = state.startedAt ?? state.progress?.startedAt ?? null;
998
+ const persistedCompletedAt = state.completedAt ?? state.progress?.completedAt ?? null;
999
+ events.push(
1000
+ makeRunStreamEvent({
1001
+ cursor,
1002
+ streamId,
1003
+ type: "play.step.status",
1004
+ payload: {
1005
+ runId: snapshot.runId,
1006
+ stepId: state.nodeId,
1007
+ status: state.status,
1008
+ artifactTableNamespace: state.artifactTableNamespace ?? null,
1009
+ ...resolveTimingWindow({
1010
+ startedAt: persistedStartedAt,
1011
+ completedAt: persistedCompletedAt,
1012
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1013
+ })
1014
+ }
1015
+ })
1016
+ );
1017
+ }
1018
+ }
1019
+ if (next.stepProgressSignature !== previous.stepProgressSignature) {
1020
+ for (const state of snapshot.nodeStates) {
1021
+ if (!state.progress) {
1022
+ continue;
1023
+ }
1024
+ events.push(
1025
+ makeRunStreamEvent({
1026
+ cursor: String(
1027
+ state.progress.updatedAt ?? snapshot.updatedAt ?? Date.now()
1028
+ ),
1029
+ streamId,
1030
+ type: "play.step.progress",
1031
+ payload: {
1032
+ runId: snapshot.runId,
1033
+ stepId: state.nodeId,
1034
+ completed: state.progress.completed,
1035
+ total: state.progress.total,
1036
+ failed: state.progress.failed,
1037
+ message: state.progress.message,
1038
+ artifactTableNamespace: state.progress.artifactTableNamespace ?? state.artifactTableNamespace ?? null,
1039
+ ...resolveTimingWindow({
1040
+ startedAt: state.startedAt ?? state.progress.startedAt ?? null,
1041
+ completedAt: state.completedAt ?? state.progress.completedAt ?? null,
1042
+ updatedAt: state.progress.updatedAt ?? snapshot.updatedAt ?? null
1043
+ })
1044
+ }
1045
+ })
1046
+ );
1047
+ }
1048
+ }
1049
+ if (logDiff.lines.length > 0) {
1050
+ events.push(
1051
+ makeRunStreamEvent({
1052
+ cursor,
1053
+ streamId,
1054
+ type: "play.run.log",
1055
+ payload: {
1056
+ runId: snapshot.runId,
1057
+ lines: logDiff.lines,
1058
+ source: "worker",
1059
+ ...logDiff.firstSeq !== null ? { firstSeq: logDiff.firstSeq } : {},
1060
+ totalLogCount: snapshot.totalLogCount
1061
+ }
1062
+ })
1063
+ );
1064
+ }
1065
+ if (next.snapshotSignature !== previous.snapshotSignature) {
1066
+ const enrichedNodeStates = snapshot.nodeStates.map((state) => {
1067
+ const timing = resolveTimingWindow({
1068
+ startedAt: state.startedAt ?? state.progress?.startedAt ?? null,
1069
+ completedAt: state.completedAt ?? state.progress?.completedAt ?? null,
1070
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1071
+ });
1072
+ return {
1073
+ ...state,
1074
+ ...timing,
1075
+ progress: state.progress ? {
1076
+ ...state.progress,
1077
+ ...resolveTimingWindow({
1078
+ startedAt: state.progress.startedAt ?? state.startedAt ?? null,
1079
+ completedAt: state.progress.completedAt ?? state.completedAt ?? null,
1080
+ updatedAt: state.progress.updatedAt ?? state.updatedAt ?? snapshot.updatedAt ?? null
1081
+ })
1082
+ } : state.progress
1083
+ };
1084
+ });
1085
+ events.push(
1086
+ makeRunStreamEvent({
1087
+ cursor,
1088
+ streamId,
1089
+ type: "play.run.snapshot",
1090
+ payload: { ...snapshot, nodeStates: enrichedNodeStates }
1091
+ })
1092
+ );
1093
+ }
1094
+ if (next.runSignature !== previous.runSignature) {
1095
+ events.push(
1096
+ makeRunStreamEvent({
1097
+ cursor,
1098
+ streamId,
1099
+ type: "play.run.status",
1100
+ payload: {
1101
+ runId: snapshot.runId,
1102
+ status: snapshot.status,
1103
+ updatedAt: snapshot.updatedAt
1104
+ }
1105
+ })
1106
+ );
1107
+ }
1108
+ return { events, next };
1109
+ }
1110
+
1111
+ // src/runs/observe-transport.ts
1112
+ var RunObserveTransportUnavailableError = class extends Error {
1113
+ constructor(message, reason) {
1114
+ super(message);
1115
+ this.reason = reason;
1116
+ this.name = "RunObserveTransportUnavailableError";
1117
+ }
1118
+ reason;
1119
+ };
1120
+ var OBSERVE_BOOTSTRAP_TIMEOUT_MS = 1e4;
1121
+ var OBSERVE_RECONNECT_NOTICE_MS = 1e4;
1122
+ var OBSERVE_STALE_WARNING_MS = 12e4;
1123
+ var OBSERVE_WATCHDOG_TICK_MS = 5e3;
1124
+ var GRANT_REFRESH_MARGIN_MS = 5 * 6e4;
1125
+ var BACKFILL_PAGE_LIMIT = 1e3;
1126
+ var BACKFILL_MAX_PAGES = 30;
1127
+ var OBSERVER_SNAPSHOT_QUERY = "runObservers:getPlayRunSnapshotForObserver";
1128
+ var OBSERVER_LOG_PAGE_QUERY = "runObservers:getRunLogPageForObserver";
1129
+ function errorText(error) {
1130
+ return error instanceof Error ? error.message : String(error);
1131
+ }
1132
+ async function mintRunObserveGrant(http, runId) {
1133
+ let response;
1134
+ try {
1135
+ response = await http.post(
1136
+ `/api/v2/runs/${encodeURIComponent(runId)}/observe-grant`,
1137
+ {}
1138
+ );
1139
+ } catch (error) {
1140
+ if (error instanceof DeeplineError) {
1141
+ if (error.statusCode === 401 || error.statusCode === 403) {
1142
+ throw error;
1143
+ }
1144
+ throw new RunObserveTransportUnavailableError(
1145
+ `observe-grant endpoint unavailable (${error.statusCode ?? "network"}): ${error.message}`,
1146
+ "grant_endpoint_unavailable"
1147
+ );
1148
+ }
1149
+ throw new RunObserveTransportUnavailableError(
1150
+ `observe-grant request failed: ${errorText(error)}`,
1151
+ "grant_request_failed"
1152
+ );
1153
+ }
1154
+ const grant = response;
1155
+ if (!grant || typeof grant.convexUrl !== "string" || !grant.convexUrl.trim() || typeof grant.token !== "string" || !grant.token.trim() || typeof grant.expiresAt !== "number") {
1156
+ throw new RunObserveTransportUnavailableError(
1157
+ "observe-grant endpoint returned an invalid grant payload.",
1158
+ "grant_payload_invalid"
1159
+ );
1160
+ }
1161
+ return grant;
1162
+ }
1163
+ async function backfillLogGap(input) {
1164
+ const lines = [];
1165
+ let cursor = input.lastLogSeq;
1166
+ for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
1167
+ if (cursor >= input.tailFirstSeq - 1) {
1168
+ break;
1169
+ }
1170
+ let logPage;
1171
+ try {
1172
+ logPage = await input.queryLogPage(
1173
+ cursor,
1174
+ Math.min(BACKFILL_PAGE_LIMIT, input.tailFirstSeq - 1 - cursor)
1175
+ );
1176
+ } catch {
1177
+ return null;
1178
+ }
1179
+ const entries = (logPage?.entries ?? []).filter(
1180
+ (entry) => entry.seq > cursor && entry.seq < input.tailFirstSeq
1181
+ );
1182
+ if (entries.length === 0) {
1183
+ break;
1184
+ }
1185
+ for (const entry of entries) {
1186
+ if (entry.seq !== cursor + 1) {
1187
+ return null;
1188
+ }
1189
+ lines.push(entry.line);
1190
+ cursor = entry.seq;
1191
+ }
1192
+ }
1193
+ if (cursor < input.tailFirstSeq - 1) {
1194
+ return null;
1195
+ }
1196
+ return lines;
1197
+ }
1198
+ async function* observeRunEvents(options) {
1199
+ const { http, runId } = options;
1200
+ let grant = await mintRunObserveGrant(http, runId);
1201
+ let convexBrowser;
1202
+ let convexServer;
1203
+ try {
1204
+ convexBrowser = await import("convex/browser");
1205
+ convexServer = await import("convex/server");
1206
+ } catch (error) {
1207
+ throw new RunObserveTransportUnavailableError(
1208
+ `convex client module unavailable: ${errorText(error)}`,
1209
+ "convex_module_unavailable"
1210
+ );
1211
+ }
1212
+ let webSocketConstructor;
1213
+ if (typeof WebSocket === "undefined") {
1214
+ try {
1215
+ const wsModuleName = "ws";
1216
+ const ws = await import(wsModuleName);
1217
+ webSocketConstructor = ws.default;
1218
+ } catch (error) {
1219
+ throw new RunObserveTransportUnavailableError(
1220
+ `no WebSocket implementation available: ${errorText(error)}`,
1221
+ "websocket_unavailable"
1222
+ );
1223
+ }
1224
+ }
1225
+ const snapshotQuery = convexServer.makeFunctionReference(
1226
+ OBSERVER_SNAPSHOT_QUERY
1227
+ );
1228
+ const logPageQuery = convexServer.makeFunctionReference(
1229
+ OBSERVER_LOG_PAGE_QUERY
1230
+ );
1231
+ const client = new convexBrowser.ConvexClient(grant.convexUrl, {
1232
+ ...webSocketConstructor ? { webSocketConstructor } : {},
1233
+ unsavedChangesWarning: false
1234
+ });
1235
+ const queue = [];
1236
+ let wake = null;
1237
+ const push = (item) => {
1238
+ queue.push(item);
1239
+ wake?.();
1240
+ wake = null;
1241
+ };
1242
+ let lastForcedRefreshAt = 0;
1243
+ client.setAuth(async ({ forceRefreshToken }) => {
1244
+ const now = Date.now();
1245
+ if (!forceRefreshToken && grant.expiresAt - now > GRANT_REFRESH_MARGIN_MS) {
1246
+ return grant.token;
1247
+ }
1248
+ if (forceRefreshToken && now - lastForcedRefreshAt < 5e3) {
1249
+ push({
1250
+ kind: "error",
1251
+ error: new DeeplineError(
1252
+ `Run observe grant for ${runId} was rejected after a re-mint. The server and Convex deployment disagree on the grant issuer/JWKS.`,
1253
+ 401,
1254
+ "RUN_OBSERVE_GRANT_REJECTED"
1255
+ )
1256
+ });
1257
+ return null;
1258
+ }
1259
+ if (forceRefreshToken) {
1260
+ lastForcedRefreshAt = now;
1261
+ }
1262
+ try {
1263
+ grant = await mintRunObserveGrant(http, runId);
1264
+ return grant.token;
1265
+ } catch (error) {
1266
+ push({ kind: "error", error });
1267
+ return null;
1268
+ }
1269
+ });
1270
+ const unsubscribe = client.onUpdate(
1271
+ snapshotQuery,
1272
+ { workflowId: runId },
1273
+ (run) => push({ kind: "run", run: run ?? null }),
1274
+ (error) => push({ kind: "error", error })
1275
+ );
1276
+ let lastUpdateAt = Date.now();
1277
+ let lastStatusTerminal = false;
1278
+ let disconnectedSince = null;
1279
+ let warnedReconnecting = false;
1280
+ let warnedStale = false;
1281
+ const watchdog = setInterval(() => {
1282
+ const now = Date.now();
1283
+ try {
1284
+ const connectionState = client.connectionState();
1285
+ if (connectionState.isWebSocketConnected) {
1286
+ disconnectedSince = null;
1287
+ warnedReconnecting = false;
1288
+ } else {
1289
+ disconnectedSince ??= now;
1290
+ if (!warnedReconnecting && now - disconnectedSince >= OBSERVE_RECONNECT_NOTICE_MS) {
1291
+ warnedReconnecting = true;
1292
+ options.onNotice?.(
1293
+ `[observe] connection lost; reconnecting to live run ${runId}\u2026`
1294
+ );
1295
+ }
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ if (!lastStatusTerminal && !warnedStale && now - lastUpdateAt >= OBSERVE_STALE_WARNING_MS) {
1300
+ warnedStale = true;
1301
+ options.onNotice?.(
1302
+ `[observe] no live updates for ${Math.round((now - lastUpdateAt) / 1e3)}s; run ${runId} may be stalled (status checks continue)`
1303
+ );
1304
+ }
1305
+ }, OBSERVE_WATCHDOG_TICK_MS);
1306
+ watchdog.unref?.();
1307
+ const abortListener = () => push({
1308
+ kind: "error",
1309
+ error: new DeeplineError(
1310
+ "Run observation aborted.",
1311
+ void 0,
1312
+ "ABORTED"
1313
+ )
1314
+ });
1315
+ options.signal?.addEventListener("abort", abortListener, { once: true });
1316
+ let diffState = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
1317
+ const streamId = ["observe", runId].join(":");
1318
+ let sawFirstSnapshot = false;
1319
+ try {
1320
+ for (; ; ) {
1321
+ if (queue.length === 0) {
1322
+ const waitForItem = new Promise((resolve2) => {
1323
+ wake = resolve2;
1324
+ });
1325
+ if (!sawFirstSnapshot) {
1326
+ const timedOut = await Promise.race([
1327
+ waitForItem.then(() => false),
1328
+ new Promise(
1329
+ (resolve2) => setTimeout(() => resolve2(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
1330
+ )
1331
+ ]);
1332
+ if (timedOut && queue.length === 0) {
1333
+ throw new RunObserveTransportUnavailableError(
1334
+ `no snapshot from Convex at ${grant.convexUrl} within ${OBSERVE_BOOTSTRAP_TIMEOUT_MS}ms`,
1335
+ "convex_unreachable"
1336
+ );
1337
+ }
1338
+ } else {
1339
+ await waitForItem;
1340
+ }
1341
+ continue;
1342
+ }
1343
+ const item = queue.shift();
1344
+ if (item.kind === "error") {
1345
+ if (options.signal?.aborted) {
1346
+ return;
1347
+ }
1348
+ throw item.error;
1349
+ }
1350
+ sawFirstSnapshot = true;
1351
+ lastUpdateAt = Date.now();
1352
+ warnedStale = false;
1353
+ if (item.run === null) {
1354
+ throw new DeeplineError(
1355
+ `Run ${runId} was not found (or is not visible to this grant).`,
1356
+ 404,
1357
+ "RUN_NOT_FOUND"
1358
+ );
1359
+ }
1360
+ const snapshot = buildPlayRunStatusSnapshot({ run: item.run });
1361
+ lastStatusTerminal = isTerminalPlayRunLiveStatus(snapshot.status);
1362
+ const gap = resolvePlayRunLogGap(snapshot, diffState.lastLogSeq);
1363
+ if (gap && diffState.lastLogSeq > 0) {
1364
+ const backfilled = await backfillLogGap({
1365
+ queryLogPage: (afterSeq, limit) => client.query(logPageQuery, {
1366
+ workflowId: runId,
1367
+ afterSeq,
1368
+ limit
1369
+ }),
1370
+ lastLogSeq: diffState.lastLogSeq,
1371
+ tailFirstSeq: gap.tailFirstSeq
1372
+ });
1373
+ if (backfilled && backfilled.length > 0) {
1374
+ yield {
1375
+ cursor: String(snapshot.updatedAt ?? Date.now()),
1376
+ streamId,
1377
+ scope: "play",
1378
+ type: "play.run.log",
1379
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1380
+ payload: {
1381
+ runId: snapshot.runId,
1382
+ lines: backfilled,
1383
+ source: "worker",
1384
+ firstSeq: diffState.lastLogSeq + 1,
1385
+ totalLogCount: snapshot.totalLogCount
1386
+ }
1387
+ };
1388
+ diffState = { ...diffState, lastLogSeq: gap.tailFirstSeq - 1 };
1389
+ }
1390
+ }
1391
+ const { events, next } = diffPlayRunStreamEvents({
1392
+ streamId,
1393
+ snapshot,
1394
+ previous: diffState
1395
+ });
1396
+ diffState = next;
1397
+ const ordered = [
1398
+ ...events.filter((event) => event.type === "play.run.snapshot"),
1399
+ ...events.filter((event) => event.type !== "play.run.snapshot")
1400
+ ];
1401
+ for (const event of ordered) {
1402
+ yield event;
1403
+ }
1404
+ if (lastStatusTerminal) {
1405
+ return;
1406
+ }
1407
+ }
1408
+ } finally {
1409
+ clearInterval(watchdog);
1410
+ options.signal?.removeEventListener("abort", abortListener);
1411
+ try {
1412
+ unsubscribe();
1413
+ } catch {
1414
+ }
1415
+ await client.close().catch(() => void 0);
1416
+ }
1417
+ }
1418
+
607
1419
  // src/client.ts
608
1420
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
609
1421
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
@@ -622,7 +1434,8 @@ function isTransientCompileManifestError(error) {
622
1434
  message
623
1435
  );
624
1436
  }
625
- function isRecord(value) {
1437
+ var RUN_LOGS_PAGE_LIMIT = 1e3;
1438
+ function isRecord2(value) {
626
1439
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
627
1440
  }
628
1441
  function isPrebuiltPlayDescription(play) {
@@ -692,10 +1505,31 @@ function normalizeLiveStatus(value) {
692
1505
  }
693
1506
  return null;
694
1507
  }
1508
+ function appendPlayLiveLogLines(state, payload) {
1509
+ const lines = readStringArray(payload.lines);
1510
+ if (lines.length === 0) {
1511
+ return;
1512
+ }
1513
+ const firstSeq = typeof payload.firstSeq === "number" && Number.isFinite(payload.firstSeq) && payload.firstSeq >= 1 ? Math.trunc(payload.firstSeq) : null;
1514
+ if (firstSeq === null) {
1515
+ state.logs.push(...lines);
1516
+ const totalLogCount = typeof payload.totalLogCount === "number" && Number.isFinite(payload.totalLogCount) ? Math.trunc(payload.totalLogCount) : null;
1517
+ if (totalLogCount !== null) {
1518
+ state.lastLogSeq = Math.max(state.lastLogSeq, totalLogCount);
1519
+ }
1520
+ return;
1521
+ }
1522
+ const skip = Math.max(0, state.lastLogSeq + 1 - firstSeq);
1523
+ if (skip >= lines.length) {
1524
+ return;
1525
+ }
1526
+ state.logs.push(...lines.slice(skip));
1527
+ state.lastLogSeq = Math.max(state.lastLogSeq, firstSeq + lines.length - 1);
1528
+ }
695
1529
  function updatePlayLiveStatusState(state, event) {
696
1530
  const payload = getPlayLiveEventPayload(event);
697
1531
  if (event.type === "play.run.log") {
698
- state.logs.push(...readStringArray(payload.lines));
1532
+ appendPlayLiveLogLines(state, payload);
699
1533
  return null;
700
1534
  }
701
1535
  if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
@@ -703,12 +1537,14 @@ function updatePlayLiveStatusState(state, event) {
703
1537
  }
704
1538
  const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : isPlayRunPackage(payload) ? payload.run.id : state.runId;
705
1539
  const status = normalizeLiveStatus(payload.status) ?? (isPlayRunPackage(payload) ? normalizeLiveStatus(payload.run.status) : null) ?? state.status;
706
- const progressPayload = isRecord(payload.progress) ? payload.progress : {};
707
- const payloadLogs = readStringArray(payload.logs);
708
- const progressLogs = readStringArray(progressPayload.logs);
709
- const logs = payloadLogs.length > 0 ? payloadLogs : progressLogs;
710
- if (logs.length > 0 || event.type === "play.run.snapshot" || event.type === "play.run.final_status" && !isPlayRunPackage(payload)) {
711
- state.logs = logs;
1540
+ const progressPayload = isRecord2(payload.progress) ? payload.progress : {};
1541
+ if (event.type === "play.run.final_status" && state.logs.length === 0 && state.lastLogSeq === 0) {
1542
+ const payloadLogs = readStringArray(payload.logs);
1543
+ const progressLogs = readStringArray(progressPayload.logs);
1544
+ const seedLogs = payloadLogs.length > 0 ? payloadLogs : progressLogs;
1545
+ if (seedLogs.length > 0) {
1546
+ state.logs = seedLogs;
1547
+ }
712
1548
  }
713
1549
  if ("result" in payload) {
714
1550
  state.result = payload.result;
@@ -802,9 +1638,9 @@ var DeeplineClient = class {
802
1638
  return fields.length > 0 ? { fields } : schema;
803
1639
  }
804
1640
  schemaMetadata(schema, key) {
805
- if (!isRecord(schema)) return null;
1641
+ if (!isRecord2(schema)) return null;
806
1642
  const value = schema[key];
807
- return isRecord(value) ? value : null;
1643
+ return isRecord2(value) ? value : null;
808
1644
  }
809
1645
  playRunCommand(play, options) {
810
1646
  const target = play.reference || play.name;
@@ -851,7 +1687,7 @@ var DeeplineClient = class {
851
1687
  aliases,
852
1688
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
853
1689
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
854
- staticPipeline: isRecord(play.staticPipeline) ? play.staticPipeline : isRecord(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
1690
+ staticPipeline: isRecord2(play.staticPipeline) ? play.staticPipeline : isRecord2(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord2(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
855
1691
  ...csvInput ? { csvInput } : {},
856
1692
  ...rowOutputSchema ? { rowOutputSchema } : {},
857
1693
  runCommand,
@@ -1490,6 +2326,93 @@ var DeeplineClient = class {
1490
2326
  );
1491
2327
  return response.runs ?? [];
1492
2328
  }
2329
+ // ---------------------------------------------------------------------------
2330
+ // Legacy workflows (double-shipped). Thin pass-throughs over the live cloud
2331
+ // `/api/v2/workflows/*` API so the SDK CLI keeps existing cloud workflows
2332
+ // working while users migrate them to plays via `workflows transform`. Kept
2333
+ // intentionally minimal — workflows are a deprecated surface.
2334
+ // ---------------------------------------------------------------------------
2335
+ /** List the org's workflows. `GET /api/v2/workflows`. */
2336
+ async listWorkflows(options) {
2337
+ const params = new URLSearchParams();
2338
+ if (typeof options?.limit === "number") {
2339
+ params.set("limit", String(options.limit));
2340
+ }
2341
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2342
+ return this.http.get(`/api/v2/workflows${query}`);
2343
+ }
2344
+ /**
2345
+ * Fetch a single workflow (including its published-revision config — the
2346
+ * input to `compileWorkflowConfigToPlay`). `GET /api/v2/workflows/:id`.
2347
+ */
2348
+ async getWorkflow(id) {
2349
+ return this.http.get(`/api/v2/workflows/${encodeURIComponent(id)}`);
2350
+ }
2351
+ /** Delete a workflow. `DELETE /api/v2/workflows/:id`. */
2352
+ async deleteWorkflow(id) {
2353
+ return this.http.delete(`/api/v2/workflows/${encodeURIComponent(id)}`);
2354
+ }
2355
+ /** Turn a workflow off. `POST /api/v2/workflows/:id/disable`. */
2356
+ async disableWorkflow(id) {
2357
+ return this.http.post(
2358
+ `/api/v2/workflows/${encodeURIComponent(id)}/disable`,
2359
+ {}
2360
+ );
2361
+ }
2362
+ /** Turn a workflow back on. `POST /api/v2/workflows/:id/enable`. */
2363
+ async enableWorkflow(id) {
2364
+ return this.http.post(
2365
+ `/api/v2/workflows/${encodeURIComponent(id)}/enable`,
2366
+ {}
2367
+ );
2368
+ }
2369
+ /** Create/update a workflow from config. `POST /api/v2/workflows/apply`. */
2370
+ async applyWorkflow(body) {
2371
+ return this.http.post("/api/v2/workflows/apply", body);
2372
+ }
2373
+ /** Validate a workflow config without saving. `POST /api/v2/workflows/lint`. */
2374
+ async lintWorkflow(body) {
2375
+ return this.http.post("/api/v2/workflows/lint", body);
2376
+ }
2377
+ /** Fetch live workflow request schemas. `GET /api/v2/workflows/schema`. */
2378
+ async getWorkflowSchema(subject) {
2379
+ const params = new URLSearchParams();
2380
+ if (subject) params.set("subject", subject);
2381
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2382
+ return this.http.get(`/api/v2/workflows/schema${query}`);
2383
+ }
2384
+ /** Queue a workflow run. `POST /api/v2/workflows/call`. */
2385
+ async callWorkflow(body) {
2386
+ return this.http.post("/api/v2/workflows/call", body);
2387
+ }
2388
+ /** List a workflow's runs. `GET /api/v2/workflows/:id/runs`. */
2389
+ async listWorkflowRuns(id, options) {
2390
+ const params = new URLSearchParams();
2391
+ if (typeof options?.limit === "number") {
2392
+ params.set("limit", String(options.limit));
2393
+ }
2394
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2395
+ return this.http.get(
2396
+ `/api/v2/workflows/${encodeURIComponent(id)}/runs${query}`
2397
+ );
2398
+ }
2399
+ /** Fetch one workflow run. `GET /api/v2/workflows/:id/runs/:runId`. */
2400
+ async getWorkflowRun(id, runId) {
2401
+ return this.http.get(
2402
+ `/api/v2/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(
2403
+ runId
2404
+ )}`
2405
+ );
2406
+ }
2407
+ /** Cancel a workflow run. `POST /api/v2/workflows/:id/runs/:runId/cancel`. */
2408
+ async cancelWorkflowRun(id, runId) {
2409
+ return this.http.post(
2410
+ `/api/v2/workflows/${encodeURIComponent(id)}/runs/${encodeURIComponent(
2411
+ runId
2412
+ )}/cancel`,
2413
+ {}
2414
+ );
2415
+ }
1493
2416
  /**
1494
2417
  * Get a run by id using the public runs resource model.
1495
2418
  *
@@ -1535,43 +2458,140 @@ var DeeplineClient = class {
1535
2458
  );
1536
2459
  return response.runs ?? [];
1537
2460
  }
1538
- /** Read the canonical run stream and return the latest run snapshot. */
1539
- async tailRun(runId, options) {
2461
+ /**
2462
+ * Observe one run's live events through the Convex Run Snapshot
2463
+ * subscription transport (ADR-0008). Yields the same `play.*` event
2464
+ * envelopes as {@link streamPlayRunEvents} and ends after the terminal
2465
+ * snapshot. Throws {@link RunObserveTransportUnavailableError} when this
2466
+ * server cannot serve the transport (older server, unconfigured grants, or
2467
+ * unreachable Convex) — callers fall back to the SSE stream with a notice.
2468
+ */
2469
+ observeRunEvents(runId, options) {
2470
+ return observeRunEvents({
2471
+ http: this.http,
2472
+ runId,
2473
+ signal: options?.signal,
2474
+ onNotice: options?.onNotice
2475
+ });
2476
+ }
2477
+ /**
2478
+ * Tail one run through the subscription transport until terminal, then
2479
+ * return one durable REST status read (the final Run Response Package).
2480
+ */
2481
+ async tailRunViaObserveTransport(runId, options) {
1540
2482
  const state = {
1541
2483
  runId,
1542
2484
  status: "running",
1543
2485
  logs: [],
2486
+ lastLogSeq: 0,
1544
2487
  latest: null
1545
2488
  };
1546
- let terminal = false;
1547
- for await (const event of this.streamPlayRunEvents(runId, {
1548
- mode: "cli",
1549
- signal: options?.signal
2489
+ for await (const event of this.observeRunEvents(runId, {
2490
+ signal: options?.signal,
2491
+ onNotice: options?.onNotice
1550
2492
  })) {
1551
2493
  const status = updatePlayLiveStatusState(state, event);
1552
- if (!status) {
2494
+ if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
1553
2495
  continue;
1554
2496
  }
1555
- terminal = TERMINAL_PLAY_STATUSES.has(status.status);
1556
- if (terminal) {
1557
- break;
1558
- }
1559
- }
1560
- if (terminal && state.latest) {
1561
- return await this.getRunStatus(state.latest.runId || runId).catch(
2497
+ return await this.getRunStatus(status.runId || runId).catch(
1562
2498
  () => state.latest ?? playRunStatusFromState(state)
1563
2499
  );
1564
2500
  }
1565
- if (state.latest) {
1566
- return state.latest;
2501
+ if (options?.signal?.aborted) {
2502
+ throw new DeeplineError("Run observation aborted.", void 0, "ABORTED");
2503
+ }
2504
+ const refreshed = await this.getRunStatus(runId);
2505
+ if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
2506
+ return refreshed;
1567
2507
  }
1568
2508
  throw new DeeplineError(
1569
- `Run stream for ${runId} ended before the initial snapshot.`,
2509
+ `Run observation for ${runId} ended before a terminal status.`,
1570
2510
  void 0,
1571
- "PLAY_RUN_STREAM_EMPTY",
1572
- { runId }
2511
+ "PLAY_LIVE_STREAM_ENDED"
1573
2512
  );
1574
2513
  }
2514
+ /**
2515
+ * Read the canonical run stream until a terminal run status is observed.
2516
+ *
2517
+ * Tries the Convex Run Snapshot subscription transport first (ADR-0008);
2518
+ * when the server cannot serve it (grant endpoint missing/unconfigured or
2519
+ * Convex unreachable) it falls back — with one `onNotice` message — to the
2520
+ * support-window SSE stream below.
2521
+ *
2522
+ * Server stream windows are finite: they end cleanly at the function
2523
+ * ceiling even while the run keeps executing. A window that ends (cleanly
2524
+ * or via transient network error) without a terminal event triggers one
2525
+ * durable-status re-check followed by a backed-off reconnect, so long runs
2526
+ * tail to completion. Abort via `options.signal` to stop waiting.
2527
+ */
2528
+ async tailRun(runId, options) {
2529
+ try {
2530
+ return await this.tailRunViaObserveTransport(runId, options);
2531
+ } catch (error) {
2532
+ if (!(error instanceof RunObserveTransportUnavailableError)) {
2533
+ throw error;
2534
+ }
2535
+ options?.onNotice?.(
2536
+ `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
2537
+ );
2538
+ }
2539
+ const state = {
2540
+ runId,
2541
+ status: "running",
2542
+ logs: [],
2543
+ lastLogSeq: 0,
2544
+ latest: null
2545
+ };
2546
+ let reconnectAttempt = 0;
2547
+ for (; ; ) {
2548
+ const connectedAt = Date.now();
2549
+ let sawEvent = false;
2550
+ let endedReason = "stream window ended before a terminal event";
2551
+ try {
2552
+ for await (const event of this.streamPlayRunEvents(runId, {
2553
+ mode: "cli",
2554
+ signal: options?.signal
2555
+ })) {
2556
+ sawEvent = true;
2557
+ const status = updatePlayLiveStatusState(state, event);
2558
+ if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
2559
+ continue;
2560
+ }
2561
+ return await this.getRunStatus(status.runId || runId).catch(
2562
+ () => state.latest ?? playRunStatusFromState(state)
2563
+ );
2564
+ }
2565
+ } catch (error) {
2566
+ if (options?.signal?.aborted || !isTransientPlayStreamError(error)) {
2567
+ throw error;
2568
+ }
2569
+ endedReason = error instanceof Error ? error.message : String(error);
2570
+ }
2571
+ let refreshed = null;
2572
+ try {
2573
+ refreshed = await this.getRunStatus(runId);
2574
+ } catch (error) {
2575
+ if (!isTransientPlayStreamError(error)) {
2576
+ throw error;
2577
+ }
2578
+ }
2579
+ if (refreshed && TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
2580
+ return refreshed;
2581
+ }
2582
+ if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
2583
+ reconnectAttempt = 0;
2584
+ }
2585
+ const delayMs = streamReconnectDelayMs(reconnectAttempt);
2586
+ reconnectAttempt += 1;
2587
+ options?.onReconnect?.({
2588
+ attempt: reconnectAttempt,
2589
+ delayMs,
2590
+ reason: endedReason
2591
+ });
2592
+ await sleep2(delayMs);
2593
+ }
2594
+ }
1575
2595
  /**
1576
2596
  * Fetch persisted logs for a run using the public runs resource model.
1577
2597
  *
@@ -1582,19 +2602,40 @@ var DeeplineClient = class {
1582
2602
  * ```
1583
2603
  */
1584
2604
  async getRunLogs(runId, options) {
1585
- const status = await this.getRunStatus(runId, { full: true });
1586
- const logs = status.progress?.logs ?? [];
1587
- const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1588
- const entries = logs.slice(Math.max(0, logs.length - limit));
2605
+ const limit = options?.all ? Number.MAX_SAFE_INTEGER : typeof options?.limit === "number" && Number.isFinite(options.limit) && options.limit > 0 ? Math.trunc(options.limit) : 200;
2606
+ const fetchPage = (afterSeq2, pageLimit) => this.http.get(
2607
+ `/api/v2/runs/${encodeURIComponent(runId)}/logs?afterSeq=${afterSeq2}&limit=${pageLimit}`
2608
+ );
2609
+ const probe = await fetchPage(0, 1);
2610
+ const lastStoredSeq = probe.lastStoredSeq;
2611
+ let afterSeq = options?.all ? 0 : Math.max(0, lastStoredSeq - limit);
2612
+ const entries = [];
2613
+ while (entries.length < limit) {
2614
+ const page = await fetchPage(
2615
+ afterSeq,
2616
+ Math.min(RUN_LOGS_PAGE_LIMIT, limit - entries.length)
2617
+ );
2618
+ if (page.entries.length === 0) {
2619
+ break;
2620
+ }
2621
+ entries.push(...page.entries);
2622
+ afterSeq = page.entries[page.entries.length - 1].seq;
2623
+ if (!page.hasMore) {
2624
+ break;
2625
+ }
2626
+ }
2627
+ const firstSequence = entries.length > 0 ? entries[0].seq : null;
2628
+ const lastSequence = entries.length > 0 ? entries[entries.length - 1].seq : null;
1589
2629
  return {
1590
- runId: status.runId,
1591
- totalCount: logs.length,
2630
+ runId: probe.runId,
2631
+ totalCount: probe.totalLogCount,
1592
2632
  returnedCount: entries.length,
1593
- firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1594
- lastSequence: logs.length === 0 ? null : logs.length,
1595
- truncated: logs.length > entries.length,
1596
- hasMore: logs.length > entries.length,
1597
- entries
2633
+ firstSequence,
2634
+ lastSequence,
2635
+ truncated: entries.length < probe.totalLogCount,
2636
+ hasMore: lastSequence !== null && lastSequence < lastStoredSeq,
2637
+ entries: entries.map((entry) => entry.line),
2638
+ ...probe.logsTruncated ? { logsTruncated: true } : {}
1598
2639
  };
1599
2640
  }
1600
2641
  /**
@@ -1885,6 +2926,7 @@ var DeeplineClient = class {
1885
2926
  runId: workflowId,
1886
2927
  status: "running",
1887
2928
  logs: [],
2929
+ lastLogSeq: 0,
1888
2930
  latest: null
1889
2931
  };
1890
2932
  if (options?.signal?.aborted) {
@@ -2038,41 +3080,6 @@ function formatPlayBootstrapFinderKindsForSentence() {
2038
3080
  }
2039
3081
 
2040
3082
  // ../shared_libs/play-runtime/email-status.ts
2041
- var DEFAULT_STATUS_MAP = {
2042
- verified: { status: "valid", verdict: "send", verified: true },
2043
- valid: { status: "valid", verdict: "send", verified: true },
2044
- deliverable: { status: "valid", verdict: "send", verified: true },
2045
- true: { status: "valid", verdict: "send", verified: true },
2046
- invalid: { status: "invalid", verdict: "drop", verified: false },
2047
- undeliverable: { status: "invalid", verdict: "drop", verified: false },
2048
- false: { status: "invalid", verdict: "drop", verified: false },
2049
- "catch-all": {
2050
- status: "catch_all",
2051
- verdict: "verify_next",
2052
- verified: false
2053
- },
2054
- catch_all: {
2055
- status: "catch_all",
2056
- verdict: "verify_next",
2057
- verified: false
2058
- },
2059
- valid_catch_all: {
2060
- status: "valid_catch_all",
2061
- verdict: "send_with_caution",
2062
- verified: true
2063
- },
2064
- accept_all: {
2065
- status: "catch_all",
2066
- verdict: "verify_next",
2067
- verified: false
2068
- },
2069
- unknown: { status: "unknown", verdict: "hold", verified: false },
2070
- unavailable: { status: "unknown", verdict: "hold", verified: false },
2071
- do_not_mail: { status: "do_not_mail", verdict: "drop", verified: false },
2072
- spamtrap: { status: "spamtrap", verdict: "drop", verified: false },
2073
- abuse: { status: "abuse", verdict: "drop", verified: false },
2074
- disposable: { status: "disposable", verdict: "drop", verified: false }
2075
- };
2076
3083
  function normalizeKey(value) {
2077
3084
  if (value == null) return null;
2078
3085
  if (typeof value === "boolean") return String(value);
@@ -2121,7 +3128,7 @@ function mxClass(mxProvider, mxRecord) {
2121
3128
  }
2122
3129
  function entryForStatus(key, map) {
2123
3130
  if (!key) return null;
2124
- return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
3131
+ return map?.[key] ?? null;
2125
3132
  }
2126
3133
  function read(values, name) {
2127
3134
  return values[name];
@@ -2255,7 +3262,7 @@ var TARGET_FALLBACK_KEYS = {
2255
3262
  status: [/^email_status$/i, /^status$/i],
2256
3263
  email_status: [/^email_status$/i, /^status$/i]
2257
3264
  };
2258
- function isRecord2(value) {
3265
+ function isRecord3(value) {
2259
3266
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2260
3267
  }
2261
3268
  function toV2RawToolOutputPath(path) {
@@ -2315,7 +3322,7 @@ function valuesAtSegments(current, segments, path = []) {
2315
3322
  if (!Array.isArray(current)) return [];
2316
3323
  return valuesAtSegments(current[segment], rest, [...path, segment]);
2317
3324
  }
2318
- if (!isRecord2(current)) return [];
3325
+ if (!isRecord3(current)) return [];
2319
3326
  const directMatches = valuesAtSegments(current[segment], rest, [
2320
3327
  ...path,
2321
3328
  segment
@@ -2345,7 +3352,7 @@ function getValuesAtPath(root, path) {
2345
3352
  return valuesAtSegments(root, parsePath(path)).map((entry) => entry.value);
2346
3353
  }
2347
3354
  function toResultEnvelope(value) {
2348
- if (isRecord2(value) && "data" in value) {
3355
+ if (isRecord3(value) && "data" in value) {
2349
3356
  const envelope = { data: value.data };
2350
3357
  if ("meta" in value) envelope.meta = value.meta;
2351
3358
  return envelope;
@@ -2398,7 +3405,7 @@ function getAtPath(root, path) {
2398
3405
  current = current[segment];
2399
3406
  continue;
2400
3407
  }
2401
- if (!isRecord2(current)) return void 0;
3408
+ if (!isRecord3(current)) return void 0;
2402
3409
  current = current[segment];
2403
3410
  }
2404
3411
  return current;
@@ -2415,7 +3422,7 @@ function normalizeString(value) {
2415
3422
  }
2416
3423
  function normalizeRows(value) {
2417
3424
  if (!Array.isArray(value)) return null;
2418
- return value.map((entry) => isRecord2(entry) ? entry : { value: entry });
3425
+ return value.map((entry) => isRecord3(entry) ? entry : { value: entry });
2419
3426
  }
2420
3427
  function findFirstTargetByPath(result, paths) {
2421
3428
  for (const path of paths ?? []) {
@@ -2474,7 +3481,7 @@ function findFirstTargetByKey(result, target, depth = 0, path = []) {
2474
3481
  }
2475
3482
  return null;
2476
3483
  }
2477
- if (!isRecord2(result)) return null;
3484
+ if (!isRecord3(result)) return null;
2478
3485
  const patterns = TARGET_FALLBACK_KEYS[target] ?? [
2479
3486
  new RegExp(`^${target}$`, "i")
2480
3487
  ];
@@ -2534,7 +3541,7 @@ function normalizeJobChangeStatus(value) {
2534
3541
  function firstExperienceDate(value) {
2535
3542
  if (!Array.isArray(value)) return null;
2536
3543
  for (const entry of value) {
2537
- if (!isRecord2(entry)) continue;
3544
+ if (!isRecord3(entry)) continue;
2538
3545
  const date = normalizeString(
2539
3546
  entry.start_date ?? entry.started_at ?? entry.startDate
2540
3547
  );
@@ -2543,10 +3550,10 @@ function firstExperienceDate(value) {
2543
3550
  return null;
2544
3551
  }
2545
3552
  function normalizeJobChange(value) {
2546
- const record = isRecord2(value) ? value : {};
2547
- const nested = isRecord2(record.job_change) ? record.job_change : record;
2548
- const output = isRecord2(nested.output) ? nested.output : nested;
2549
- const person = isRecord2(output.person) ? output.person : {};
3553
+ const record = isRecord3(value) ? value : {};
3554
+ const nested = isRecord3(record.job_change) ? record.job_change : record;
3555
+ const output = isRecord3(nested.output) ? nested.output : nested;
3556
+ const person = isRecord3(output.person) ? output.person : {};
2550
3557
  const status = normalizeJobChangeStatus(
2551
3558
  output.status ?? output.job_change_status ?? output.job_changed ?? output.changed
2552
3559
  );
@@ -3114,11 +4121,11 @@ var Deepline = class {
3114
4121
  return new DeeplineContext(options);
3115
4122
  }
3116
4123
  };
3117
- function isRecord3(value) {
4124
+ function isRecord4(value) {
3118
4125
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3119
4126
  }
3120
4127
  function stringArrayRecord(value) {
3121
- if (!isRecord3(value)) return {};
4128
+ if (!isRecord4(value)) return {};
3122
4129
  return Object.fromEntries(
3123
4130
  Object.entries(value).map(([key, paths]) => [
3124
4131
  key,
@@ -3129,22 +4136,60 @@ function stringArrayRecord(value) {
3129
4136
  function stringArray(value) {
3130
4137
  return Array.isArray(value) ? value.map(String) : [];
3131
4138
  }
4139
+ function emailStatusExtractorConfig(value) {
4140
+ if (!isRecord4(value)) return void 0;
4141
+ const readPaths = (key) => {
4142
+ const paths = stringArray(value[key]).map((path) => path.trim()).filter(Boolean);
4143
+ return paths.length > 0 ? paths : void 0;
4144
+ };
4145
+ const provider = typeof value.provider === "string" && value.provider.trim() ? value.provider.trim() : null;
4146
+ if (!provider) return void 0;
4147
+ const config = { provider };
4148
+ for (const key of [
4149
+ "rawStatus",
4150
+ "rawScore",
4151
+ "valid",
4152
+ "deliverability",
4153
+ "catchAll",
4154
+ "mxProvider",
4155
+ "mxRecord",
4156
+ "fraudScore",
4157
+ "disposable",
4158
+ "roleBased",
4159
+ "freeEmail",
4160
+ "abuse",
4161
+ "spamtrap",
4162
+ "suspect"
4163
+ ]) {
4164
+ const paths = readPaths(key);
4165
+ if (paths) config[key] = paths;
4166
+ }
4167
+ if (isRecord4(value.statusMap)) {
4168
+ config.statusMap = value.statusMap;
4169
+ }
4170
+ if (Array.isArray(value.rules)) {
4171
+ config.rules = value.rules;
4172
+ }
4173
+ return config;
4174
+ }
3132
4175
  function extractorDescriptorRecord(value) {
3133
- if (!isRecord3(value)) return {};
4176
+ if (!isRecord4(value)) return {};
3134
4177
  return Object.fromEntries(
3135
4178
  Object.entries(value).flatMap(([key, descriptor]) => {
3136
- if (!isRecord3(descriptor)) return [];
4179
+ if (!isRecord4(descriptor)) return [];
3137
4180
  const paths = stringArray(descriptor.paths).map((path) => path.trim()).filter(Boolean);
3138
4181
  if (paths.length === 0) return [];
3139
4182
  const transforms = stringArray(descriptor.transforms).map((transform) => transform.trim()).filter(Boolean);
3140
4183
  const enumValues = stringArray(descriptor.enum).map((entry) => entry.trim()).filter(Boolean);
4184
+ const emailStatus = emailStatusExtractorConfig(descriptor.emailStatus);
3141
4185
  return [
3142
4186
  [
3143
4187
  key,
3144
4188
  {
3145
4189
  paths,
3146
4190
  ...transforms.length > 0 ? { transforms } : {},
3147
- ...enumValues.length > 0 ? { enum: enumValues } : {}
4191
+ ...enumValues.length > 0 ? { enum: enumValues } : {},
4192
+ ...emailStatus ? { emailStatus } : {}
3148
4193
  }
3149
4194
  ]
3150
4195
  ];
@@ -3154,14 +4199,14 @@ function extractorDescriptorRecord(value) {
3154
4199
  function toolExecutionEnvelopeToResult(fallbackToolId, response) {
3155
4200
  const raw = response.toolResponse?.raw ?? null;
3156
4201
  const meta = response.toolResponse?.meta;
3157
- const metadata = isRecord3(response._metadata) ? response._metadata.tool : null;
3158
- const toolMetadata = isRecord3(metadata) ? metadata : {};
4202
+ const metadata = isRecord4(response._metadata) ? response._metadata.tool : null;
4203
+ const toolMetadata = isRecord4(metadata) ? metadata : {};
3159
4204
  return createToolExecuteResult({
3160
4205
  status: typeof response.status === "string" ? response.status : "completed",
3161
4206
  jobId: typeof response.job_id === "string" ? response.job_id : void 0,
3162
4207
  result: {
3163
4208
  data: raw,
3164
- ...isRecord3(meta) ? { meta } : {}
4209
+ ...isRecord4(meta) ? { meta } : {}
3165
4210
  },
3166
4211
  metadata: {
3167
4212
  toolId: typeof toolMetadata.toolId === "string" ? toolMetadata.toolId : fallbackToolId,
@@ -3175,7 +4220,7 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response) {
3175
4220
  cached: false,
3176
4221
  source: "live"
3177
4222
  },
3178
- meta: isRecord3(response.meta) ? response.meta : void 0
4223
+ meta: isRecord4(response.meta) ? response.meta : void 0
3179
4224
  });
3180
4225
  }
3181
4226
  function defineInput(schema) {
@@ -3477,6 +4522,7 @@ function extractSummaryFields(payload) {
3477
4522
  PLAY_BOOTSTRAP_TEMPLATES,
3478
4523
  PROD_URL,
3479
4524
  RateLimitError,
4525
+ RunObserveTransportUnavailableError,
3480
4526
  SDK_API_CONTRACT,
3481
4527
  SDK_VERSION,
3482
4528
  defineInput,