@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.
- package/dist/board-host/index.cjs +217 -129
- package/dist/board-host/index.cjs.map +1 -1
- package/dist/board-host/index.d.cts +268 -3
- package/dist/board-host/index.d.ts +268 -3
- package/dist/board-host/index.js +218 -131
- package/dist/board-host/index.js.map +1 -1
- package/dist/board-host/server/index.d.cts +218 -2
- package/dist/board-host/server/index.d.ts +218 -2
- package/dist/chat/index.cjs +528 -47
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +29 -0
- package/dist/chat/index.d.ts +29 -0
- package/dist/chat/index.js +528 -47
- package/dist/chat/index.js.map +1 -1
- package/dist/index.cjs +453 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -1
- package/dist/index.d.ts +81 -1
- package/dist/index.js +453 -38
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +1535 -434
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.d.cts +628 -6
- package/dist/kanban/engine/index.d.ts +628 -6
- package/dist/kanban/engine/index.js +1520 -435
- package/dist/kanban/engine/index.js.map +1 -1
- package/dist/kanban/index.cjs +751 -60
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.d.cts +555 -9
- package/dist/kanban/index.d.ts +555 -9
- package/dist/kanban/index.js +747 -61
- package/dist/kanban/index.js.map +1 -1
- package/dist/teams/index.cjs +453 -38
- package/dist/teams/index.cjs.map +1 -1
- package/dist/teams/index.d.cts +27 -0
- package/dist/teams/index.d.ts +27 -0
- package/dist/teams/index.js +453 -38
- package/dist/teams/index.js.map +1 -1
- package/package.json +1 -1
package/dist/teams/index.js
CHANGED
|
@@ -437,7 +437,8 @@ var SSEClient = class {
|
|
|
437
437
|
"Authorization": this.config.authHeader,
|
|
438
438
|
"X-Org-Id": String(this.config.orgId),
|
|
439
439
|
"Accept": "text/event-stream",
|
|
440
|
-
"Cache-Control": "no-cache"
|
|
440
|
+
"Cache-Control": "no-cache",
|
|
441
|
+
...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
|
|
441
442
|
},
|
|
442
443
|
signal: this.abortController.signal
|
|
443
444
|
});
|
|
@@ -653,13 +654,167 @@ function normalizeUrl(url) {
|
|
|
653
654
|
return url;
|
|
654
655
|
}
|
|
655
656
|
|
|
657
|
+
// src/streaming/poll-policy.ts
|
|
658
|
+
var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
|
|
659
|
+
function asDict(value) {
|
|
660
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
661
|
+
}
|
|
662
|
+
var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
|
|
663
|
+
"rate_limit_error",
|
|
664
|
+
"rate_limited",
|
|
665
|
+
"rate_limit_exceeded",
|
|
666
|
+
"too_many_requests"
|
|
667
|
+
]);
|
|
668
|
+
function isRateLimited(error) {
|
|
669
|
+
if (typeof error === "string") return /\b429\b/.test(error);
|
|
670
|
+
const err = asDict(error);
|
|
671
|
+
if (!err) return false;
|
|
672
|
+
for (const key of ["status", "statusCode", "httpStatus", "code"]) {
|
|
673
|
+
if (err[key] === 429 || err[key] === "429") return true;
|
|
674
|
+
}
|
|
675
|
+
const response = asDict(err.response);
|
|
676
|
+
if (response && (response.status === 429 || response.statusCode === 429)) return true;
|
|
677
|
+
if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
|
|
678
|
+
if (err.name === "RateLimitError") return true;
|
|
679
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
680
|
+
return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
|
|
681
|
+
}
|
|
682
|
+
function readHeader(bag, name) {
|
|
683
|
+
if (bag === null || bag === void 0) return null;
|
|
684
|
+
const getter = bag.get;
|
|
685
|
+
if (typeof getter === "function") {
|
|
686
|
+
try {
|
|
687
|
+
const value = getter.call(bag, name);
|
|
688
|
+
if (value !== null && value !== void 0) return String(value);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const dict = asDict(bag);
|
|
693
|
+
if (!dict) return null;
|
|
694
|
+
const wanted = name.toLowerCase();
|
|
695
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
696
|
+
if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
|
|
697
|
+
return String(value);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
function parseRetryAfter(raw, now) {
|
|
703
|
+
if (raw === null || raw === void 0) return null;
|
|
704
|
+
if (typeof raw === "number") {
|
|
705
|
+
return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
|
|
706
|
+
}
|
|
707
|
+
if (typeof raw !== "string") return null;
|
|
708
|
+
const trimmed = raw.trim();
|
|
709
|
+
if (trimmed === "") return null;
|
|
710
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
711
|
+
const at = Date.parse(trimmed);
|
|
712
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
713
|
+
}
|
|
714
|
+
function parseResetAt(raw, now) {
|
|
715
|
+
if (raw === null || raw === void 0) return null;
|
|
716
|
+
const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
|
|
717
|
+
if (numeric !== null) {
|
|
718
|
+
if (!Number.isFinite(numeric) || numeric < 0) return null;
|
|
719
|
+
if (numeric > 1e11) return Math.max(0, numeric - now);
|
|
720
|
+
if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
|
|
721
|
+
return numeric * 1e3;
|
|
722
|
+
}
|
|
723
|
+
if (typeof raw !== "string") return null;
|
|
724
|
+
const at = Date.parse(raw.trim());
|
|
725
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
726
|
+
}
|
|
727
|
+
function getRetryAfterMs(error, now = Date.now()) {
|
|
728
|
+
const err = asDict(error);
|
|
729
|
+
if (!err) return null;
|
|
730
|
+
const details = asDict(err.details);
|
|
731
|
+
const response = asDict(err.response);
|
|
732
|
+
const body = asDict(err.body) ?? asDict(response?.data);
|
|
733
|
+
const bodyError = asDict(body?.error);
|
|
734
|
+
const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
|
|
735
|
+
for (const source of [err, details, bodyDetails]) {
|
|
736
|
+
const ms = source?.retryAfterMs;
|
|
737
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
|
|
738
|
+
}
|
|
739
|
+
for (const source of [err, details, bodyDetails, bodyError]) {
|
|
740
|
+
const parsed = parseRetryAfter(source?.retryAfter, now);
|
|
741
|
+
if (parsed !== null) return parsed;
|
|
742
|
+
}
|
|
743
|
+
const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
|
|
744
|
+
for (const bag of headerBags) {
|
|
745
|
+
const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
|
|
746
|
+
if (parsed !== null) return parsed;
|
|
747
|
+
}
|
|
748
|
+
for (const source of [details, bodyDetails, err]) {
|
|
749
|
+
for (const key of ["resetAt", "reset", "rateLimitReset"]) {
|
|
750
|
+
const parsed = parseResetAt(source?.[key], now);
|
|
751
|
+
if (parsed !== null) return parsed;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
for (const bag of headerBags) {
|
|
755
|
+
for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
756
|
+
const parsed = parseResetAt(readHeader(bag, header), now);
|
|
757
|
+
if (parsed !== null) return parsed;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
function getHttpStatus(error) {
|
|
763
|
+
const err = asDict(error);
|
|
764
|
+
if (!err) return null;
|
|
765
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
766
|
+
const value = err[key];
|
|
767
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
768
|
+
if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
|
|
769
|
+
}
|
|
770
|
+
const response = asDict(err.response);
|
|
771
|
+
for (const key of ["status", "statusCode"]) {
|
|
772
|
+
const value = response?.[key];
|
|
773
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
774
|
+
}
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
function isTerminalClientError(error) {
|
|
778
|
+
const status = getHttpStatus(error);
|
|
779
|
+
if (status === null) return false;
|
|
780
|
+
return status >= 400 && status < 500 && status !== 429 && status !== 408;
|
|
781
|
+
}
|
|
782
|
+
var FAILURE_GROWTH_FACTOR = 2;
|
|
783
|
+
var IDLE_GROWTH_FACTOR = 1.5;
|
|
784
|
+
function nextFailureDelayMs(currentMs, maxMs) {
|
|
785
|
+
return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
|
|
786
|
+
}
|
|
787
|
+
function nextIdleDelayMs(currentMs, maxMs) {
|
|
788
|
+
return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
|
|
789
|
+
}
|
|
790
|
+
function applyJitter(delayMs, ratio, random = Math.random) {
|
|
791
|
+
if (ratio <= 0) return delayMs;
|
|
792
|
+
return Math.round(delayMs * (1 + random() * ratio));
|
|
793
|
+
}
|
|
794
|
+
|
|
656
795
|
// src/streaming/stream-controller.ts
|
|
657
796
|
var DEFAULT_STREAMING_CONFIG = {
|
|
658
797
|
maxReconnectAttempts: 5,
|
|
659
798
|
reconnectBaseDelayMs: 1e3,
|
|
660
799
|
reconnectMaxDelayMs: 1e4,
|
|
800
|
+
// NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
|
|
801
|
+
// SSE connection after 30s of silence and routes it through the same
|
|
802
|
+
// handleConnectionError path as a real transport failure. Ordinary LLM
|
|
803
|
+
// latency exceeds 30s routinely, so it fires against working streams — that
|
|
804
|
+
// is what drives sessions into the polling fallback in the first place. The
|
|
805
|
+
// fix is a server-side SSE heartbeat, not a bigger number here; raising this
|
|
806
|
+
// is a guess against a moving target. Everything below only bounds what the
|
|
807
|
+
// fallback costs once it has been entered.
|
|
661
808
|
activityTimeoutMs: 3e4,
|
|
662
|
-
pollingIntervalMs:
|
|
809
|
+
pollingIntervalMs: 5e3,
|
|
810
|
+
pollingMaxIntervalMs: 3e4,
|
|
811
|
+
pollingMaxConsecutiveFailures: 10,
|
|
812
|
+
pollingMaxConsecutiveRateLimits: 3,
|
|
813
|
+
pollingMaxConsecutiveClientErrors: 3,
|
|
814
|
+
pollingMaxDurationMs: 18e5,
|
|
815
|
+
pollingMaxRetryAfterMs: 3e5,
|
|
816
|
+
pollingJitterRatio: 0.1,
|
|
817
|
+
sseRetryIntervalMs: 6e4
|
|
663
818
|
};
|
|
664
819
|
var DEFAULT_POLLING_ENABLED = true;
|
|
665
820
|
var ExecutionStreamControllerImpl = class {
|
|
@@ -672,9 +827,24 @@ var ExecutionStreamControllerImpl = class {
|
|
|
672
827
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
673
828
|
_lastEventId = null;
|
|
674
829
|
activityTimeoutId = null;
|
|
675
|
-
pollingIntervalId = null;
|
|
676
830
|
finalExecution = null;
|
|
677
831
|
finalError = null;
|
|
832
|
+
// --- Fallback polling state ---
|
|
833
|
+
// A self-rescheduling timeout, not setInterval: the delay has to change
|
|
834
|
+
// between ticks (that is what backoff IS), and a timeout chain also cannot
|
|
835
|
+
// stack overlapping requests the way a fixed interval does when the API is
|
|
836
|
+
// slow to answer.
|
|
837
|
+
pollTimeoutId = null;
|
|
838
|
+
pollDelayMs = 0;
|
|
839
|
+
pollConsecutiveFailures = 0;
|
|
840
|
+
pollConsecutiveRateLimits = 0;
|
|
841
|
+
pollConsecutiveClientErrors = 0;
|
|
842
|
+
/** When the loop last observed anything new — the clock the give-up bound runs on. */
|
|
843
|
+
pollLastProgressAt = 0;
|
|
844
|
+
pollInFlight = false;
|
|
845
|
+
lastPolledStatus = null;
|
|
846
|
+
sseRetryTimeoutId = null;
|
|
847
|
+
sseRetryInFlight = false;
|
|
678
848
|
// Event callbacks
|
|
679
849
|
listeners = {
|
|
680
850
|
log: [],
|
|
@@ -703,6 +873,39 @@ var ExecutionStreamControllerImpl = class {
|
|
|
703
873
|
this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
|
|
704
874
|
this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
|
|
705
875
|
}
|
|
876
|
+
// Resolved fallback-poll settings. Read through `??` rather than off the
|
|
877
|
+
// spread config so an explicit `undefined` in a caller's partial config still
|
|
878
|
+
// lands on the default instead of NaN-ing the arithmetic.
|
|
879
|
+
get pollBaseIntervalMs() {
|
|
880
|
+
return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
|
|
881
|
+
}
|
|
882
|
+
get pollMaxIntervalMs() {
|
|
883
|
+
return Math.max(
|
|
884
|
+
this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
|
|
885
|
+
this.pollBaseIntervalMs
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
get pollMaxConsecutiveFailures() {
|
|
889
|
+
return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
|
|
890
|
+
}
|
|
891
|
+
get pollMaxConsecutiveRateLimits() {
|
|
892
|
+
return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
|
|
893
|
+
}
|
|
894
|
+
get pollMaxConsecutiveClientErrors() {
|
|
895
|
+
return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
|
|
896
|
+
}
|
|
897
|
+
get pollMaxDurationMs() {
|
|
898
|
+
return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
|
|
899
|
+
}
|
|
900
|
+
get pollMaxRetryAfterMs() {
|
|
901
|
+
return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
|
|
902
|
+
}
|
|
903
|
+
get pollJitterRatio() {
|
|
904
|
+
return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
|
|
905
|
+
}
|
|
906
|
+
get sseRetryIntervalMs() {
|
|
907
|
+
return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
|
|
908
|
+
}
|
|
706
909
|
/**
|
|
707
910
|
* Get the last event ID (for resume support)
|
|
708
911
|
*/
|
|
@@ -793,28 +996,45 @@ var ExecutionStreamControllerImpl = class {
|
|
|
793
996
|
console.log("[SDK:StreamController] executionId:", this.options.executionId);
|
|
794
997
|
console.log("[SDK:StreamController] orgId:", this.options.orgId);
|
|
795
998
|
console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
|
|
796
|
-
this.sseClient
|
|
999
|
+
if (this.sseClient) {
|
|
1000
|
+
this.sseClient.disconnect();
|
|
1001
|
+
this.sseClient = null;
|
|
1002
|
+
}
|
|
1003
|
+
const client = new SSEClient({
|
|
797
1004
|
url,
|
|
798
1005
|
authHeader: `Bearer ${authToken}`,
|
|
799
1006
|
orgId: this.options.orgId,
|
|
800
1007
|
fetch: this.options.fetch,
|
|
801
|
-
|
|
1008
|
+
// Resume from where this session left off. On a first connect this is
|
|
1009
|
+
// null and the header is omitted.
|
|
1010
|
+
lastEventId: this._lastEventId,
|
|
1011
|
+
onEvent: (event) => {
|
|
1012
|
+
if (this.sseClient !== client) return;
|
|
1013
|
+
this.handleEvent(event);
|
|
1014
|
+
},
|
|
802
1015
|
onError: (error) => {
|
|
1016
|
+
if (this.sseClient !== client) return;
|
|
803
1017
|
console.error("[SDK:StreamController] SSE error:", error);
|
|
804
1018
|
this.handleConnectionError(error);
|
|
805
1019
|
},
|
|
806
1020
|
onConnectionChange: (connected) => {
|
|
1021
|
+
if (this.sseClient !== client) return;
|
|
807
1022
|
console.log("[SDK:StreamController] SSE connection change:", connected);
|
|
808
1023
|
if (connected) {
|
|
809
1024
|
this.status = "connected";
|
|
810
1025
|
this.reconnectAttempts = 0;
|
|
1026
|
+
this.stopPolling();
|
|
811
1027
|
this.resetActivityTimeout();
|
|
812
1028
|
}
|
|
813
|
-
this.emit("connectionChange", {
|
|
1029
|
+
this.emit("connectionChange", {
|
|
1030
|
+
connected,
|
|
1031
|
+
usingFallback: this.status === "fallback_polling"
|
|
1032
|
+
});
|
|
814
1033
|
}
|
|
815
1034
|
});
|
|
1035
|
+
this.sseClient = client;
|
|
816
1036
|
try {
|
|
817
|
-
await
|
|
1037
|
+
await client.connect();
|
|
818
1038
|
console.log("[SDK:StreamController] SSE connect() completed");
|
|
819
1039
|
} catch (error) {
|
|
820
1040
|
console.error("[SDK:StreamController] SSE connect() threw:", error);
|
|
@@ -1053,6 +1273,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1053
1273
|
handleComplete(data) {
|
|
1054
1274
|
this.status = "closed";
|
|
1055
1275
|
this.stopActivityTimeout();
|
|
1276
|
+
this.stopPolling();
|
|
1056
1277
|
const sessionId = data.sessionId ?? this.options.sessionId;
|
|
1057
1278
|
const execution = {
|
|
1058
1279
|
uuid: this.options.executionId,
|
|
@@ -1079,6 +1300,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1079
1300
|
handleError(error) {
|
|
1080
1301
|
this.status = "closed";
|
|
1081
1302
|
this.stopActivityTimeout();
|
|
1303
|
+
this.stopPolling();
|
|
1082
1304
|
this.finalError = error;
|
|
1083
1305
|
this.emit("error", error);
|
|
1084
1306
|
if (this.waitReject) {
|
|
@@ -1105,6 +1327,10 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1105
1327
|
this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
|
|
1106
1328
|
return;
|
|
1107
1329
|
}
|
|
1330
|
+
if (this.status === "fallback_polling") {
|
|
1331
|
+
this.startPolling();
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1108
1334
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
1109
1335
|
this.status = "reconnecting";
|
|
1110
1336
|
this.reconnectAttempts++;
|
|
@@ -1174,42 +1400,216 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1174
1400
|
}
|
|
1175
1401
|
}
|
|
1176
1402
|
/**
|
|
1177
|
-
* Start polling fallback
|
|
1403
|
+
* Start polling fallback.
|
|
1404
|
+
*
|
|
1405
|
+
* Idempotent — calling it while a loop is already running is a no-op, so no
|
|
1406
|
+
* path can end up with two loops racing the same execution.
|
|
1178
1407
|
*/
|
|
1179
1408
|
startPolling() {
|
|
1180
|
-
this.
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1409
|
+
if (this.pollTimeoutId !== null || this.pollInFlight) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
this.pollLastProgressAt = Date.now();
|
|
1413
|
+
this.pollDelayMs = this.pollBaseIntervalMs;
|
|
1414
|
+
this.pollConsecutiveFailures = 0;
|
|
1415
|
+
this.pollConsecutiveRateLimits = 0;
|
|
1416
|
+
this.pollConsecutiveClientErrors = 0;
|
|
1417
|
+
this.lastPolledStatus = null;
|
|
1418
|
+
this.stopActivityTimeout();
|
|
1419
|
+
this.scheduleSSERetry();
|
|
1420
|
+
this.schedulePoll(this.pollDelayMs);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Queue the next poll tick.
|
|
1424
|
+
*/
|
|
1425
|
+
schedulePoll(delayMs) {
|
|
1426
|
+
if (this.status !== "fallback_polling") {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
1430
|
+
this.pollTimeoutId = null;
|
|
1431
|
+
void this.runPoll();
|
|
1432
|
+
}, Math.max(0, Math.round(delayMs)));
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* One poll tick: fetch state, emit what is new, then decide how long to wait
|
|
1436
|
+
* before the next one — or whether there should be a next one at all.
|
|
1437
|
+
*/
|
|
1438
|
+
async runPoll() {
|
|
1439
|
+
if (this.status !== "fallback_polling") {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
|
|
1443
|
+
this.failPolling(
|
|
1444
|
+
`Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
|
|
1445
|
+
"polling_timeout"
|
|
1446
|
+
);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
this.pollInFlight = true;
|
|
1450
|
+
try {
|
|
1451
|
+
const execution = await this.options.getExecution(this.options.executionId);
|
|
1452
|
+
this.pollConsecutiveFailures = 0;
|
|
1453
|
+
this.pollConsecutiveRateLimits = 0;
|
|
1454
|
+
this.pollConsecutiveClientErrors = 0;
|
|
1455
|
+
let sawNewInfo = false;
|
|
1456
|
+
for (const log of execution.logs ?? []) {
|
|
1457
|
+
if (!this.seenEventIds.has(log.uuid)) {
|
|
1458
|
+
this.seenEventIds.add(log.uuid);
|
|
1459
|
+
this.emit("log", log);
|
|
1460
|
+
sawNewInfo = true;
|
|
1198
1461
|
}
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
console.error("Polling error:", error);
|
|
1201
1462
|
}
|
|
1202
|
-
|
|
1463
|
+
if (execution.status !== this.lastPolledStatus) {
|
|
1464
|
+
this.lastPolledStatus = execution.status;
|
|
1465
|
+
sawNewInfo = true;
|
|
1466
|
+
}
|
|
1467
|
+
if (sawNewInfo) {
|
|
1468
|
+
this.pollLastProgressAt = Date.now();
|
|
1469
|
+
}
|
|
1470
|
+
if (execution.status === "completed") {
|
|
1471
|
+
this.stopPolling();
|
|
1472
|
+
this.handleComplete(execution);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (execution.status === "failed" || execution.status === "cancelled") {
|
|
1476
|
+
this.stopPolling();
|
|
1477
|
+
this.handleError({
|
|
1478
|
+
error: execution.error ?? `Execution ${execution.status}`,
|
|
1479
|
+
errorCode: execution.status
|
|
1480
|
+
});
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
if (!this.handlePollError(error)) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
} finally {
|
|
1489
|
+
this.pollInFlight = false;
|
|
1490
|
+
}
|
|
1491
|
+
this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Apply backpressure for a failed poll.
|
|
1495
|
+
*
|
|
1496
|
+
* @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
|
|
1497
|
+
* the loop has been terminated.
|
|
1498
|
+
*/
|
|
1499
|
+
handlePollError(error) {
|
|
1500
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
1501
|
+
const rateLimited = isRateLimited(error) || retryAfterMs !== null;
|
|
1502
|
+
if (rateLimited) {
|
|
1503
|
+
this.pollConsecutiveRateLimits++;
|
|
1504
|
+
console.warn(
|
|
1505
|
+
"[SDK:StreamController] Poll rate-limited",
|
|
1506
|
+
retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
|
|
1507
|
+
);
|
|
1508
|
+
if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
|
|
1509
|
+
this.failPolling(
|
|
1510
|
+
`Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
|
|
1511
|
+
"rate_limited"
|
|
1512
|
+
);
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
|
|
1516
|
+
this.failPolling(
|
|
1517
|
+
"Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
|
|
1518
|
+
"rate_limited"
|
|
1519
|
+
);
|
|
1520
|
+
return false;
|
|
1521
|
+
}
|
|
1522
|
+
this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
|
|
1523
|
+
return true;
|
|
1524
|
+
}
|
|
1525
|
+
if (isTerminalClientError(error)) {
|
|
1526
|
+
this.pollConsecutiveClientErrors++;
|
|
1527
|
+
const status = getHttpStatus(error);
|
|
1528
|
+
console.warn(
|
|
1529
|
+
`[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
|
|
1530
|
+
);
|
|
1531
|
+
if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
|
|
1532
|
+
this.failPolling(
|
|
1533
|
+
`Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
|
|
1534
|
+
"polling_unrecoverable"
|
|
1535
|
+
);
|
|
1536
|
+
return false;
|
|
1537
|
+
}
|
|
1538
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1539
|
+
return true;
|
|
1540
|
+
}
|
|
1541
|
+
this.pollConsecutiveFailures++;
|
|
1542
|
+
console.warn(
|
|
1543
|
+
`[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
|
|
1544
|
+
error
|
|
1545
|
+
);
|
|
1546
|
+
if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
|
|
1547
|
+
this.failPolling(
|
|
1548
|
+
`Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
|
|
1549
|
+
"polling_failed"
|
|
1550
|
+
);
|
|
1551
|
+
return false;
|
|
1552
|
+
}
|
|
1553
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1554
|
+
return true;
|
|
1203
1555
|
}
|
|
1204
1556
|
/**
|
|
1205
|
-
*
|
|
1557
|
+
* Terminate the fallback loop and surface the reason.
|
|
1558
|
+
*/
|
|
1559
|
+
failPolling(error, errorCode) {
|
|
1560
|
+
this.stopPolling();
|
|
1561
|
+
this.handleError({ error, errorCode });
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Stop polling, including the periodic SSE re-attempt that belongs to it.
|
|
1206
1565
|
*/
|
|
1207
1566
|
stopPolling() {
|
|
1208
|
-
if (this.
|
|
1209
|
-
|
|
1210
|
-
this.
|
|
1567
|
+
if (this.pollTimeoutId !== null) {
|
|
1568
|
+
clearTimeout(this.pollTimeoutId);
|
|
1569
|
+
this.pollTimeoutId = null;
|
|
1570
|
+
}
|
|
1571
|
+
if (this.sseRetryTimeoutId !== null) {
|
|
1572
|
+
clearTimeout(this.sseRetryTimeoutId);
|
|
1573
|
+
this.sseRetryTimeoutId = null;
|
|
1211
1574
|
}
|
|
1212
1575
|
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Queue the next attempt to get the real stream back.
|
|
1578
|
+
*/
|
|
1579
|
+
scheduleSSERetry() {
|
|
1580
|
+
if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
this.sseRetryTimeoutId = setTimeout(() => {
|
|
1584
|
+
this.sseRetryTimeoutId = null;
|
|
1585
|
+
this.attemptSSERecovery();
|
|
1586
|
+
}, this.sseRetryIntervalMs);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Re-attempt SSE from inside the fallback, so a transient outage costs the
|
|
1590
|
+
* session a few polls rather than its stream for the lifetime of the tab.
|
|
1591
|
+
*
|
|
1592
|
+
* Success is NOT observed here: `SSEClient.connect()` resolves when the
|
|
1593
|
+
* stream ENDS, not when it opens. The open is observed by
|
|
1594
|
+
* `onConnectionChange(true)`, which flips status and stops this loop. The
|
|
1595
|
+
* promise below only tells us the attempt is over so the next one can be
|
|
1596
|
+
* queued.
|
|
1597
|
+
*/
|
|
1598
|
+
attemptSSERecovery() {
|
|
1599
|
+
if (this.status !== "fallback_polling") {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
if (this.sseRetryInFlight) {
|
|
1603
|
+
this.scheduleSSERetry();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
this.sseRetryInFlight = true;
|
|
1607
|
+
void this.connectSSE().catch(() => {
|
|
1608
|
+
}).finally(() => {
|
|
1609
|
+
this.sseRetryInFlight = false;
|
|
1610
|
+
this.scheduleSSERetry();
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1213
1613
|
/**
|
|
1214
1614
|
* Reset activity timeout
|
|
1215
1615
|
*/
|
|
@@ -1340,12 +1740,23 @@ async function collectImagesFromStream(stream, options = {}) {
|
|
|
1340
1740
|
});
|
|
1341
1741
|
}
|
|
1342
1742
|
|
|
1743
|
+
// src/utils/api-paths.ts
|
|
1744
|
+
var API_V1 = "/api/v1";
|
|
1745
|
+
var EXECUTION_PATH = `${API_V1}/executions`;
|
|
1746
|
+
function executionPath(executionId) {
|
|
1747
|
+
return `${EXECUTION_PATH}/${executionId}`;
|
|
1748
|
+
}
|
|
1749
|
+
function executionCancelPath(executionId) {
|
|
1750
|
+
return `${EXECUTION_PATH}/${executionId}/cancel`;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1343
1753
|
// src/resources/agents.ts
|
|
1344
1754
|
var AgentsAPI = class extends BaseResourceAPI {
|
|
1345
1755
|
constructor(http, getStreamConfig) {
|
|
1346
1756
|
super(http, "/api/v1/agents");
|
|
1347
1757
|
this.getStreamConfig = getStreamConfig;
|
|
1348
1758
|
}
|
|
1759
|
+
getStreamConfig;
|
|
1349
1760
|
// ===== LIST Operations =====
|
|
1350
1761
|
/**
|
|
1351
1762
|
* List all agents in the organization
|
|
@@ -1602,7 +2013,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
1602
2013
|
* ```
|
|
1603
2014
|
*/
|
|
1604
2015
|
async getExecution(executionId) {
|
|
1605
|
-
return this.http.get(
|
|
2016
|
+
return this.http.get(executionPath(executionId));
|
|
1606
2017
|
}
|
|
1607
2018
|
// ===== EDIT Operations =====
|
|
1608
2019
|
/**
|
|
@@ -1657,7 +2068,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
1657
2068
|
*/
|
|
1658
2069
|
async cancelExecution(executionId) {
|
|
1659
2070
|
return this.http.post(
|
|
1660
|
-
|
|
2071
|
+
executionCancelPath(executionId),
|
|
1661
2072
|
void 0,
|
|
1662
2073
|
{ skipRetry: true }
|
|
1663
2074
|
);
|
|
@@ -2158,6 +2569,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
|
|
|
2158
2569
|
}
|
|
2159
2570
|
return this.http.request({
|
|
2160
2571
|
method: "POST",
|
|
2572
|
+
// KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
|
|
2161
2573
|
path: `/executions/${executionId}/artifacts`,
|
|
2162
2574
|
body: formData,
|
|
2163
2575
|
headers: {
|
|
@@ -2251,6 +2663,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
2251
2663
|
super(http, "/api/v1/sessions");
|
|
2252
2664
|
this.getStreamConfig = getStreamConfig;
|
|
2253
2665
|
}
|
|
2666
|
+
getStreamConfig;
|
|
2254
2667
|
// ===== LIST Operations =====
|
|
2255
2668
|
/**
|
|
2256
2669
|
* List all sessions in the organization
|
|
@@ -2430,7 +2843,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
2430
2843
|
* Get execution result
|
|
2431
2844
|
*/
|
|
2432
2845
|
async getExecution(executionId) {
|
|
2433
|
-
return this.http.get(
|
|
2846
|
+
return this.http.get(executionPath(executionId));
|
|
2434
2847
|
}
|
|
2435
2848
|
// ===== EDIT Operations =====
|
|
2436
2849
|
/**
|
|
@@ -2570,6 +2983,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
2570
2983
|
super(http, "/api/v1/kanban");
|
|
2571
2984
|
this.getStreamConfig = getStreamConfig;
|
|
2572
2985
|
}
|
|
2986
|
+
getStreamConfig;
|
|
2573
2987
|
// ===== BOARD =====
|
|
2574
2988
|
/**
|
|
2575
2989
|
* Get the current org's board (lanes in declared order). Auto-provisions
|
|
@@ -2704,7 +3118,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
2704
3118
|
* @returns Execution details.
|
|
2705
3119
|
*/
|
|
2706
3120
|
async getCardRun(executionId) {
|
|
2707
|
-
return this.http.get(
|
|
3121
|
+
return this.http.get(executionPath(executionId));
|
|
2708
3122
|
}
|
|
2709
3123
|
/**
|
|
2710
3124
|
* Create a stream controller for a card's run, keyed on the card's
|
|
@@ -2771,6 +3185,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
2771
3185
|
super(http, "/api/v1/remote-agents");
|
|
2772
3186
|
this.getStreamConfig = getStreamConfig;
|
|
2773
3187
|
}
|
|
3188
|
+
getStreamConfig;
|
|
2774
3189
|
/**
|
|
2775
3190
|
* List all remote agents in the organization
|
|
2776
3191
|
*
|
|
@@ -2904,7 +3319,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
2904
3319
|
apiKey: config.apiKey,
|
|
2905
3320
|
tokenProvider: config.tokenProvider,
|
|
2906
3321
|
orgId: config.orgId,
|
|
2907
|
-
getExecution: (id) => this.http.get(
|
|
3322
|
+
getExecution: (id) => this.http.get(executionPath(id))
|
|
2908
3323
|
});
|
|
2909
3324
|
return controller;
|
|
2910
3325
|
}
|