@cadraos/sdk 0.3.12 → 0.3.14-p102.1

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.
Files changed (39) hide show
  1. package/dist/board-host/index.cjs +217 -129
  2. package/dist/board-host/index.cjs.map +1 -1
  3. package/dist/board-host/index.d.cts +268 -3
  4. package/dist/board-host/index.d.ts +268 -3
  5. package/dist/board-host/index.js +218 -131
  6. package/dist/board-host/index.js.map +1 -1
  7. package/dist/board-host/server/index.d.cts +218 -2
  8. package/dist/board-host/server/index.d.ts +218 -2
  9. package/dist/chat/index.cjs +528 -47
  10. package/dist/chat/index.cjs.map +1 -1
  11. package/dist/chat/index.d.cts +29 -0
  12. package/dist/chat/index.d.ts +29 -0
  13. package/dist/chat/index.js +528 -47
  14. package/dist/chat/index.js.map +1 -1
  15. package/dist/index.cjs +453 -38
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +81 -1
  18. package/dist/index.d.ts +81 -1
  19. package/dist/index.js +453 -38
  20. package/dist/index.js.map +1 -1
  21. package/dist/kanban/engine/index.cjs +1535 -434
  22. package/dist/kanban/engine/index.cjs.map +1 -1
  23. package/dist/kanban/engine/index.d.cts +628 -6
  24. package/dist/kanban/engine/index.d.ts +628 -6
  25. package/dist/kanban/engine/index.js +1520 -435
  26. package/dist/kanban/engine/index.js.map +1 -1
  27. package/dist/kanban/index.cjs +751 -60
  28. package/dist/kanban/index.cjs.map +1 -1
  29. package/dist/kanban/index.d.cts +555 -9
  30. package/dist/kanban/index.d.ts +555 -9
  31. package/dist/kanban/index.js +747 -61
  32. package/dist/kanban/index.js.map +1 -1
  33. package/dist/teams/index.cjs +453 -38
  34. package/dist/teams/index.cjs.map +1 -1
  35. package/dist/teams/index.d.cts +27 -0
  36. package/dist/teams/index.d.ts +27 -0
  37. package/dist/teams/index.js +453 -38
  38. package/dist/teams/index.js.map +1 -1
  39. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -435,7 +435,8 @@ var SSEClient = class {
435
435
  "Authorization": this.config.authHeader,
436
436
  "X-Org-Id": String(this.config.orgId),
437
437
  "Accept": "text/event-stream",
438
- "Cache-Control": "no-cache"
438
+ "Cache-Control": "no-cache",
439
+ ...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
439
440
  },
440
441
  signal: this.abortController.signal
441
442
  });
@@ -661,13 +662,167 @@ function normalizeUrl(url) {
661
662
  return url;
662
663
  }
663
664
 
665
+ // src/streaming/poll-policy.ts
666
+ var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
667
+ function asDict(value) {
668
+ return typeof value === "object" && value !== null ? value : null;
669
+ }
670
+ var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
671
+ "rate_limit_error",
672
+ "rate_limited",
673
+ "rate_limit_exceeded",
674
+ "too_many_requests"
675
+ ]);
676
+ function isRateLimited(error) {
677
+ if (typeof error === "string") return /\b429\b/.test(error);
678
+ const err = asDict(error);
679
+ if (!err) return false;
680
+ for (const key of ["status", "statusCode", "httpStatus", "code"]) {
681
+ if (err[key] === 429 || err[key] === "429") return true;
682
+ }
683
+ const response = asDict(err.response);
684
+ if (response && (response.status === 429 || response.statusCode === 429)) return true;
685
+ if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
686
+ if (err.name === "RateLimitError") return true;
687
+ const message = typeof err.message === "string" ? err.message : "";
688
+ return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
689
+ }
690
+ function readHeader(bag, name) {
691
+ if (bag === null || bag === void 0) return null;
692
+ const getter = bag.get;
693
+ if (typeof getter === "function") {
694
+ try {
695
+ const value = getter.call(bag, name);
696
+ if (value !== null && value !== void 0) return String(value);
697
+ } catch {
698
+ }
699
+ }
700
+ const dict = asDict(bag);
701
+ if (!dict) return null;
702
+ const wanted = name.toLowerCase();
703
+ for (const [key, value] of Object.entries(dict)) {
704
+ if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
705
+ return String(value);
706
+ }
707
+ }
708
+ return null;
709
+ }
710
+ function parseRetryAfter(raw, now) {
711
+ if (raw === null || raw === void 0) return null;
712
+ if (typeof raw === "number") {
713
+ return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
714
+ }
715
+ if (typeof raw !== "string") return null;
716
+ const trimmed = raw.trim();
717
+ if (trimmed === "") return null;
718
+ if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
719
+ const at = Date.parse(trimmed);
720
+ return Number.isNaN(at) ? null : Math.max(0, at - now);
721
+ }
722
+ function parseResetAt(raw, now) {
723
+ if (raw === null || raw === void 0) return null;
724
+ const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
725
+ if (numeric !== null) {
726
+ if (!Number.isFinite(numeric) || numeric < 0) return null;
727
+ if (numeric > 1e11) return Math.max(0, numeric - now);
728
+ if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
729
+ return numeric * 1e3;
730
+ }
731
+ if (typeof raw !== "string") return null;
732
+ const at = Date.parse(raw.trim());
733
+ return Number.isNaN(at) ? null : Math.max(0, at - now);
734
+ }
735
+ function getRetryAfterMs(error, now = Date.now()) {
736
+ const err = asDict(error);
737
+ if (!err) return null;
738
+ const details = asDict(err.details);
739
+ const response = asDict(err.response);
740
+ const body = asDict(err.body) ?? asDict(response?.data);
741
+ const bodyError = asDict(body?.error);
742
+ const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
743
+ for (const source of [err, details, bodyDetails]) {
744
+ const ms = source?.retryAfterMs;
745
+ if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
746
+ }
747
+ for (const source of [err, details, bodyDetails, bodyError]) {
748
+ const parsed = parseRetryAfter(source?.retryAfter, now);
749
+ if (parsed !== null) return parsed;
750
+ }
751
+ const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
752
+ for (const bag of headerBags) {
753
+ const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
754
+ if (parsed !== null) return parsed;
755
+ }
756
+ for (const source of [details, bodyDetails, err]) {
757
+ for (const key of ["resetAt", "reset", "rateLimitReset"]) {
758
+ const parsed = parseResetAt(source?.[key], now);
759
+ if (parsed !== null) return parsed;
760
+ }
761
+ }
762
+ for (const bag of headerBags) {
763
+ for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
764
+ const parsed = parseResetAt(readHeader(bag, header), now);
765
+ if (parsed !== null) return parsed;
766
+ }
767
+ }
768
+ return null;
769
+ }
770
+ function getHttpStatus(error) {
771
+ const err = asDict(error);
772
+ if (!err) return null;
773
+ for (const key of ["status", "statusCode", "httpStatus"]) {
774
+ const value = err[key];
775
+ if (typeof value === "number" && Number.isFinite(value)) return value;
776
+ if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
777
+ }
778
+ const response = asDict(err.response);
779
+ for (const key of ["status", "statusCode"]) {
780
+ const value = response?.[key];
781
+ if (typeof value === "number" && Number.isFinite(value)) return value;
782
+ }
783
+ return null;
784
+ }
785
+ function isTerminalClientError(error) {
786
+ const status = getHttpStatus(error);
787
+ if (status === null) return false;
788
+ return status >= 400 && status < 500 && status !== 429 && status !== 408;
789
+ }
790
+ var FAILURE_GROWTH_FACTOR = 2;
791
+ var IDLE_GROWTH_FACTOR = 1.5;
792
+ function nextFailureDelayMs(currentMs, maxMs) {
793
+ return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
794
+ }
795
+ function nextIdleDelayMs(currentMs, maxMs) {
796
+ return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
797
+ }
798
+ function applyJitter(delayMs, ratio, random = Math.random) {
799
+ if (ratio <= 0) return delayMs;
800
+ return Math.round(delayMs * (1 + random() * ratio));
801
+ }
802
+
664
803
  // src/streaming/stream-controller.ts
665
804
  var DEFAULT_STREAMING_CONFIG = {
666
805
  maxReconnectAttempts: 5,
667
806
  reconnectBaseDelayMs: 1e3,
668
807
  reconnectMaxDelayMs: 1e4,
808
+ // NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
809
+ // SSE connection after 30s of silence and routes it through the same
810
+ // handleConnectionError path as a real transport failure. Ordinary LLM
811
+ // latency exceeds 30s routinely, so it fires against working streams — that
812
+ // is what drives sessions into the polling fallback in the first place. The
813
+ // fix is a server-side SSE heartbeat, not a bigger number here; raising this
814
+ // is a guess against a moving target. Everything below only bounds what the
815
+ // fallback costs once it has been entered.
669
816
  activityTimeoutMs: 3e4,
670
- pollingIntervalMs: 2e3
817
+ pollingIntervalMs: 5e3,
818
+ pollingMaxIntervalMs: 3e4,
819
+ pollingMaxConsecutiveFailures: 10,
820
+ pollingMaxConsecutiveRateLimits: 3,
821
+ pollingMaxConsecutiveClientErrors: 3,
822
+ pollingMaxDurationMs: 18e5,
823
+ pollingMaxRetryAfterMs: 3e5,
824
+ pollingJitterRatio: 0.1,
825
+ sseRetryIntervalMs: 6e4
671
826
  };
672
827
  var DEFAULT_POLLING_ENABLED = true;
673
828
  var ExecutionStreamControllerImpl = class {
@@ -680,9 +835,24 @@ var ExecutionStreamControllerImpl = class {
680
835
  seenEventIds = /* @__PURE__ */ new Set();
681
836
  _lastEventId = null;
682
837
  activityTimeoutId = null;
683
- pollingIntervalId = null;
684
838
  finalExecution = null;
685
839
  finalError = null;
840
+ // --- Fallback polling state ---
841
+ // A self-rescheduling timeout, not setInterval: the delay has to change
842
+ // between ticks (that is what backoff IS), and a timeout chain also cannot
843
+ // stack overlapping requests the way a fixed interval does when the API is
844
+ // slow to answer.
845
+ pollTimeoutId = null;
846
+ pollDelayMs = 0;
847
+ pollConsecutiveFailures = 0;
848
+ pollConsecutiveRateLimits = 0;
849
+ pollConsecutiveClientErrors = 0;
850
+ /** When the loop last observed anything new — the clock the give-up bound runs on. */
851
+ pollLastProgressAt = 0;
852
+ pollInFlight = false;
853
+ lastPolledStatus = null;
854
+ sseRetryTimeoutId = null;
855
+ sseRetryInFlight = false;
686
856
  // Event callbacks
687
857
  listeners = {
688
858
  log: [],
@@ -711,6 +881,39 @@ var ExecutionStreamControllerImpl = class {
711
881
  this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
712
882
  this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
713
883
  }
884
+ // Resolved fallback-poll settings. Read through `??` rather than off the
885
+ // spread config so an explicit `undefined` in a caller's partial config still
886
+ // lands on the default instead of NaN-ing the arithmetic.
887
+ get pollBaseIntervalMs() {
888
+ return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
889
+ }
890
+ get pollMaxIntervalMs() {
891
+ return Math.max(
892
+ this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
893
+ this.pollBaseIntervalMs
894
+ );
895
+ }
896
+ get pollMaxConsecutiveFailures() {
897
+ return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
898
+ }
899
+ get pollMaxConsecutiveRateLimits() {
900
+ return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
901
+ }
902
+ get pollMaxConsecutiveClientErrors() {
903
+ return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
904
+ }
905
+ get pollMaxDurationMs() {
906
+ return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
907
+ }
908
+ get pollMaxRetryAfterMs() {
909
+ return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
910
+ }
911
+ get pollJitterRatio() {
912
+ return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
913
+ }
914
+ get sseRetryIntervalMs() {
915
+ return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
916
+ }
714
917
  /**
715
918
  * Get the last event ID (for resume support)
716
919
  */
@@ -801,28 +1004,45 @@ var ExecutionStreamControllerImpl = class {
801
1004
  console.log("[SDK:StreamController] executionId:", this.options.executionId);
802
1005
  console.log("[SDK:StreamController] orgId:", this.options.orgId);
803
1006
  console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
804
- this.sseClient = new SSEClient({
1007
+ if (this.sseClient) {
1008
+ this.sseClient.disconnect();
1009
+ this.sseClient = null;
1010
+ }
1011
+ const client = new SSEClient({
805
1012
  url,
806
1013
  authHeader: `Bearer ${authToken}`,
807
1014
  orgId: this.options.orgId,
808
1015
  fetch: this.options.fetch,
809
- onEvent: (event) => this.handleEvent(event),
1016
+ // Resume from where this session left off. On a first connect this is
1017
+ // null and the header is omitted.
1018
+ lastEventId: this._lastEventId,
1019
+ onEvent: (event) => {
1020
+ if (this.sseClient !== client) return;
1021
+ this.handleEvent(event);
1022
+ },
810
1023
  onError: (error) => {
1024
+ if (this.sseClient !== client) return;
811
1025
  console.error("[SDK:StreamController] SSE error:", error);
812
1026
  this.handleConnectionError(error);
813
1027
  },
814
1028
  onConnectionChange: (connected) => {
1029
+ if (this.sseClient !== client) return;
815
1030
  console.log("[SDK:StreamController] SSE connection change:", connected);
816
1031
  if (connected) {
817
1032
  this.status = "connected";
818
1033
  this.reconnectAttempts = 0;
1034
+ this.stopPolling();
819
1035
  this.resetActivityTimeout();
820
1036
  }
821
- this.emit("connectionChange", { connected, usingFallback: false });
1037
+ this.emit("connectionChange", {
1038
+ connected,
1039
+ usingFallback: this.status === "fallback_polling"
1040
+ });
822
1041
  }
823
1042
  });
1043
+ this.sseClient = client;
824
1044
  try {
825
- await this.sseClient.connect();
1045
+ await client.connect();
826
1046
  console.log("[SDK:StreamController] SSE connect() completed");
827
1047
  } catch (error) {
828
1048
  console.error("[SDK:StreamController] SSE connect() threw:", error);
@@ -1061,6 +1281,7 @@ var ExecutionStreamControllerImpl = class {
1061
1281
  handleComplete(data) {
1062
1282
  this.status = "closed";
1063
1283
  this.stopActivityTimeout();
1284
+ this.stopPolling();
1064
1285
  const sessionId = data.sessionId ?? this.options.sessionId;
1065
1286
  const execution = {
1066
1287
  uuid: this.options.executionId,
@@ -1087,6 +1308,7 @@ var ExecutionStreamControllerImpl = class {
1087
1308
  handleError(error) {
1088
1309
  this.status = "closed";
1089
1310
  this.stopActivityTimeout();
1311
+ this.stopPolling();
1090
1312
  this.finalError = error;
1091
1313
  this.emit("error", error);
1092
1314
  if (this.waitReject) {
@@ -1113,6 +1335,10 @@ var ExecutionStreamControllerImpl = class {
1113
1335
  this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
1114
1336
  return;
1115
1337
  }
1338
+ if (this.status === "fallback_polling") {
1339
+ this.startPolling();
1340
+ return;
1341
+ }
1116
1342
  if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
1117
1343
  this.status = "reconnecting";
1118
1344
  this.reconnectAttempts++;
@@ -1182,42 +1408,216 @@ var ExecutionStreamControllerImpl = class {
1182
1408
  }
1183
1409
  }
1184
1410
  /**
1185
- * Start polling fallback
1411
+ * Start polling fallback.
1412
+ *
1413
+ * Idempotent — calling it while a loop is already running is a no-op, so no
1414
+ * path can end up with two loops racing the same execution.
1186
1415
  */
1187
1416
  startPolling() {
1188
- this.pollingIntervalId = setInterval(async () => {
1189
- try {
1190
- const execution = await this.options.getExecution(this.options.executionId);
1191
- for (const log of execution.logs) {
1192
- if (!this.seenEventIds.has(log.uuid)) {
1193
- this.seenEventIds.add(log.uuid);
1194
- this.emit("log", log);
1195
- }
1196
- }
1197
- if (execution.status === "completed") {
1198
- this.stopPolling();
1199
- this.handleComplete(execution);
1200
- } else if (execution.status === "failed" || execution.status === "cancelled") {
1201
- this.stopPolling();
1202
- this.handleError({
1203
- error: execution.error ?? `Execution ${execution.status}`,
1204
- errorCode: execution.status
1205
- });
1417
+ if (this.pollTimeoutId !== null || this.pollInFlight) {
1418
+ return;
1419
+ }
1420
+ this.pollLastProgressAt = Date.now();
1421
+ this.pollDelayMs = this.pollBaseIntervalMs;
1422
+ this.pollConsecutiveFailures = 0;
1423
+ this.pollConsecutiveRateLimits = 0;
1424
+ this.pollConsecutiveClientErrors = 0;
1425
+ this.lastPolledStatus = null;
1426
+ this.stopActivityTimeout();
1427
+ this.scheduleSSERetry();
1428
+ this.schedulePoll(this.pollDelayMs);
1429
+ }
1430
+ /**
1431
+ * Queue the next poll tick.
1432
+ */
1433
+ schedulePoll(delayMs) {
1434
+ if (this.status !== "fallback_polling") {
1435
+ return;
1436
+ }
1437
+ this.pollTimeoutId = setTimeout(() => {
1438
+ this.pollTimeoutId = null;
1439
+ void this.runPoll();
1440
+ }, Math.max(0, Math.round(delayMs)));
1441
+ }
1442
+ /**
1443
+ * One poll tick: fetch state, emit what is new, then decide how long to wait
1444
+ * before the next one — or whether there should be a next one at all.
1445
+ */
1446
+ async runPoll() {
1447
+ if (this.status !== "fallback_polling") {
1448
+ return;
1449
+ }
1450
+ if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
1451
+ this.failPolling(
1452
+ `Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
1453
+ "polling_timeout"
1454
+ );
1455
+ return;
1456
+ }
1457
+ this.pollInFlight = true;
1458
+ try {
1459
+ const execution = await this.options.getExecution(this.options.executionId);
1460
+ this.pollConsecutiveFailures = 0;
1461
+ this.pollConsecutiveRateLimits = 0;
1462
+ this.pollConsecutiveClientErrors = 0;
1463
+ let sawNewInfo = false;
1464
+ for (const log of execution.logs ?? []) {
1465
+ if (!this.seenEventIds.has(log.uuid)) {
1466
+ this.seenEventIds.add(log.uuid);
1467
+ this.emit("log", log);
1468
+ sawNewInfo = true;
1206
1469
  }
1207
- } catch (error) {
1208
- console.error("Polling error:", error);
1209
1470
  }
1210
- }, this.config.pollingIntervalMs);
1471
+ if (execution.status !== this.lastPolledStatus) {
1472
+ this.lastPolledStatus = execution.status;
1473
+ sawNewInfo = true;
1474
+ }
1475
+ if (sawNewInfo) {
1476
+ this.pollLastProgressAt = Date.now();
1477
+ }
1478
+ if (execution.status === "completed") {
1479
+ this.stopPolling();
1480
+ this.handleComplete(execution);
1481
+ return;
1482
+ }
1483
+ if (execution.status === "failed" || execution.status === "cancelled") {
1484
+ this.stopPolling();
1485
+ this.handleError({
1486
+ error: execution.error ?? `Execution ${execution.status}`,
1487
+ errorCode: execution.status
1488
+ });
1489
+ return;
1490
+ }
1491
+ this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
1492
+ } catch (error) {
1493
+ if (!this.handlePollError(error)) {
1494
+ return;
1495
+ }
1496
+ } finally {
1497
+ this.pollInFlight = false;
1498
+ }
1499
+ this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
1500
+ }
1501
+ /**
1502
+ * Apply backpressure for a failed poll.
1503
+ *
1504
+ * @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
1505
+ * the loop has been terminated.
1506
+ */
1507
+ handlePollError(error) {
1508
+ const retryAfterMs = getRetryAfterMs(error);
1509
+ const rateLimited = isRateLimited(error) || retryAfterMs !== null;
1510
+ if (rateLimited) {
1511
+ this.pollConsecutiveRateLimits++;
1512
+ console.warn(
1513
+ "[SDK:StreamController] Poll rate-limited",
1514
+ retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
1515
+ );
1516
+ if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
1517
+ this.failPolling(
1518
+ `Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
1519
+ "rate_limited"
1520
+ );
1521
+ return false;
1522
+ }
1523
+ if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
1524
+ this.failPolling(
1525
+ "Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
1526
+ "rate_limited"
1527
+ );
1528
+ return false;
1529
+ }
1530
+ this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
1531
+ return true;
1532
+ }
1533
+ if (isTerminalClientError(error)) {
1534
+ this.pollConsecutiveClientErrors++;
1535
+ const status = getHttpStatus(error);
1536
+ console.warn(
1537
+ `[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
1538
+ );
1539
+ if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
1540
+ this.failPolling(
1541
+ `Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
1542
+ "polling_unrecoverable"
1543
+ );
1544
+ return false;
1545
+ }
1546
+ this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
1547
+ return true;
1548
+ }
1549
+ this.pollConsecutiveFailures++;
1550
+ console.warn(
1551
+ `[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
1552
+ error
1553
+ );
1554
+ if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
1555
+ this.failPolling(
1556
+ `Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
1557
+ "polling_failed"
1558
+ );
1559
+ return false;
1560
+ }
1561
+ this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
1562
+ return true;
1211
1563
  }
1212
1564
  /**
1213
- * Stop polling
1565
+ * Terminate the fallback loop and surface the reason.
1566
+ */
1567
+ failPolling(error, errorCode) {
1568
+ this.stopPolling();
1569
+ this.handleError({ error, errorCode });
1570
+ }
1571
+ /**
1572
+ * Stop polling, including the periodic SSE re-attempt that belongs to it.
1214
1573
  */
1215
1574
  stopPolling() {
1216
- if (this.pollingIntervalId) {
1217
- clearInterval(this.pollingIntervalId);
1218
- this.pollingIntervalId = null;
1575
+ if (this.pollTimeoutId !== null) {
1576
+ clearTimeout(this.pollTimeoutId);
1577
+ this.pollTimeoutId = null;
1578
+ }
1579
+ if (this.sseRetryTimeoutId !== null) {
1580
+ clearTimeout(this.sseRetryTimeoutId);
1581
+ this.sseRetryTimeoutId = null;
1219
1582
  }
1220
1583
  }
1584
+ /**
1585
+ * Queue the next attempt to get the real stream back.
1586
+ */
1587
+ scheduleSSERetry() {
1588
+ if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
1589
+ return;
1590
+ }
1591
+ this.sseRetryTimeoutId = setTimeout(() => {
1592
+ this.sseRetryTimeoutId = null;
1593
+ this.attemptSSERecovery();
1594
+ }, this.sseRetryIntervalMs);
1595
+ }
1596
+ /**
1597
+ * Re-attempt SSE from inside the fallback, so a transient outage costs the
1598
+ * session a few polls rather than its stream for the lifetime of the tab.
1599
+ *
1600
+ * Success is NOT observed here: `SSEClient.connect()` resolves when the
1601
+ * stream ENDS, not when it opens. The open is observed by
1602
+ * `onConnectionChange(true)`, which flips status and stops this loop. The
1603
+ * promise below only tells us the attempt is over so the next one can be
1604
+ * queued.
1605
+ */
1606
+ attemptSSERecovery() {
1607
+ if (this.status !== "fallback_polling") {
1608
+ return;
1609
+ }
1610
+ if (this.sseRetryInFlight) {
1611
+ this.scheduleSSERetry();
1612
+ return;
1613
+ }
1614
+ this.sseRetryInFlight = true;
1615
+ void this.connectSSE().catch(() => {
1616
+ }).finally(() => {
1617
+ this.sseRetryInFlight = false;
1618
+ this.scheduleSSERetry();
1619
+ });
1620
+ }
1221
1621
  /**
1222
1622
  * Reset activity timeout
1223
1623
  */
@@ -1348,12 +1748,23 @@ async function collectImagesFromStream(stream, options = {}) {
1348
1748
  });
1349
1749
  }
1350
1750
 
1751
+ // src/utils/api-paths.ts
1752
+ var API_V1 = "/api/v1";
1753
+ var EXECUTION_PATH = `${API_V1}/executions`;
1754
+ function executionPath(executionId) {
1755
+ return `${EXECUTION_PATH}/${executionId}`;
1756
+ }
1757
+ function executionCancelPath(executionId) {
1758
+ return `${EXECUTION_PATH}/${executionId}/cancel`;
1759
+ }
1760
+
1351
1761
  // src/resources/agents.ts
1352
1762
  var AgentsAPI = class extends BaseResourceAPI {
1353
1763
  constructor(http, getStreamConfig) {
1354
1764
  super(http, "/api/v1/agents");
1355
1765
  this.getStreamConfig = getStreamConfig;
1356
1766
  }
1767
+ getStreamConfig;
1357
1768
  // ===== LIST Operations =====
1358
1769
  /**
1359
1770
  * List all agents in the organization
@@ -1610,7 +2021,7 @@ var AgentsAPI = class extends BaseResourceAPI {
1610
2021
  * ```
1611
2022
  */
1612
2023
  async getExecution(executionId) {
1613
- return this.http.get(`/executions/${executionId}`);
2024
+ return this.http.get(executionPath(executionId));
1614
2025
  }
1615
2026
  // ===== EDIT Operations =====
1616
2027
  /**
@@ -1665,7 +2076,7 @@ var AgentsAPI = class extends BaseResourceAPI {
1665
2076
  */
1666
2077
  async cancelExecution(executionId) {
1667
2078
  return this.http.post(
1668
- `/executions/${executionId}/cancel`,
2079
+ executionCancelPath(executionId),
1669
2080
  void 0,
1670
2081
  { skipRetry: true }
1671
2082
  );
@@ -2166,6 +2577,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
2166
2577
  }
2167
2578
  return this.http.request({
2168
2579
  method: "POST",
2580
+ // KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
2169
2581
  path: `/executions/${executionId}/artifacts`,
2170
2582
  body: formData,
2171
2583
  headers: {
@@ -2259,6 +2671,7 @@ var SessionsAPI = class extends BaseResourceAPI {
2259
2671
  super(http, "/api/v1/sessions");
2260
2672
  this.getStreamConfig = getStreamConfig;
2261
2673
  }
2674
+ getStreamConfig;
2262
2675
  // ===== LIST Operations =====
2263
2676
  /**
2264
2677
  * List all sessions in the organization
@@ -2438,7 +2851,7 @@ var SessionsAPI = class extends BaseResourceAPI {
2438
2851
  * Get execution result
2439
2852
  */
2440
2853
  async getExecution(executionId) {
2441
- return this.http.get(`/executions/${executionId}`);
2854
+ return this.http.get(executionPath(executionId));
2442
2855
  }
2443
2856
  // ===== EDIT Operations =====
2444
2857
  /**
@@ -2578,6 +2991,7 @@ var KanbanAPI = class extends BaseResourceAPI {
2578
2991
  super(http, "/api/v1/kanban");
2579
2992
  this.getStreamConfig = getStreamConfig;
2580
2993
  }
2994
+ getStreamConfig;
2581
2995
  // ===== BOARD =====
2582
2996
  /**
2583
2997
  * Get the current org's board (lanes in declared order). Auto-provisions
@@ -2712,7 +3126,7 @@ var KanbanAPI = class extends BaseResourceAPI {
2712
3126
  * @returns Execution details.
2713
3127
  */
2714
3128
  async getCardRun(executionId) {
2715
- return this.http.get(`/api/v1/agents/executions/${executionId}`);
3129
+ return this.http.get(executionPath(executionId));
2716
3130
  }
2717
3131
  /**
2718
3132
  * Create a stream controller for a card's run, keyed on the card's
@@ -2779,6 +3193,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
2779
3193
  super(http, "/api/v1/remote-agents");
2780
3194
  this.getStreamConfig = getStreamConfig;
2781
3195
  }
3196
+ getStreamConfig;
2782
3197
  /**
2783
3198
  * List all remote agents in the organization
2784
3199
  *
@@ -2912,7 +3327,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
2912
3327
  apiKey: config.apiKey,
2913
3328
  tokenProvider: config.tokenProvider,
2914
3329
  orgId: config.orgId,
2915
- getExecution: (id) => this.http.get(`/executions/${id}`)
3330
+ getExecution: (id) => this.http.get(executionPath(id))
2916
3331
  });
2917
3332
  return controller;
2918
3333
  }