@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.cjs
CHANGED
|
@@ -458,7 +458,8 @@ var SSEClient = class {
|
|
|
458
458
|
"Authorization": this.config.authHeader,
|
|
459
459
|
"X-Org-Id": String(this.config.orgId),
|
|
460
460
|
"Accept": "text/event-stream",
|
|
461
|
-
"Cache-Control": "no-cache"
|
|
461
|
+
"Cache-Control": "no-cache",
|
|
462
|
+
...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
|
|
462
463
|
},
|
|
463
464
|
signal: this.abortController.signal
|
|
464
465
|
});
|
|
@@ -674,13 +675,167 @@ function normalizeUrl(url) {
|
|
|
674
675
|
return url;
|
|
675
676
|
}
|
|
676
677
|
|
|
678
|
+
// src/streaming/poll-policy.ts
|
|
679
|
+
var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
|
|
680
|
+
function asDict(value) {
|
|
681
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
682
|
+
}
|
|
683
|
+
var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
|
|
684
|
+
"rate_limit_error",
|
|
685
|
+
"rate_limited",
|
|
686
|
+
"rate_limit_exceeded",
|
|
687
|
+
"too_many_requests"
|
|
688
|
+
]);
|
|
689
|
+
function isRateLimited(error) {
|
|
690
|
+
if (typeof error === "string") return /\b429\b/.test(error);
|
|
691
|
+
const err = asDict(error);
|
|
692
|
+
if (!err) return false;
|
|
693
|
+
for (const key of ["status", "statusCode", "httpStatus", "code"]) {
|
|
694
|
+
if (err[key] === 429 || err[key] === "429") return true;
|
|
695
|
+
}
|
|
696
|
+
const response = asDict(err.response);
|
|
697
|
+
if (response && (response.status === 429 || response.statusCode === 429)) return true;
|
|
698
|
+
if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
|
|
699
|
+
if (err.name === "RateLimitError") return true;
|
|
700
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
701
|
+
return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
|
|
702
|
+
}
|
|
703
|
+
function readHeader(bag, name) {
|
|
704
|
+
if (bag === null || bag === void 0) return null;
|
|
705
|
+
const getter = bag.get;
|
|
706
|
+
if (typeof getter === "function") {
|
|
707
|
+
try {
|
|
708
|
+
const value = getter.call(bag, name);
|
|
709
|
+
if (value !== null && value !== void 0) return String(value);
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const dict = asDict(bag);
|
|
714
|
+
if (!dict) return null;
|
|
715
|
+
const wanted = name.toLowerCase();
|
|
716
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
717
|
+
if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
|
|
718
|
+
return String(value);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
function parseRetryAfter(raw, now) {
|
|
724
|
+
if (raw === null || raw === void 0) return null;
|
|
725
|
+
if (typeof raw === "number") {
|
|
726
|
+
return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
|
|
727
|
+
}
|
|
728
|
+
if (typeof raw !== "string") return null;
|
|
729
|
+
const trimmed = raw.trim();
|
|
730
|
+
if (trimmed === "") return null;
|
|
731
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
732
|
+
const at = Date.parse(trimmed);
|
|
733
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
734
|
+
}
|
|
735
|
+
function parseResetAt(raw, now) {
|
|
736
|
+
if (raw === null || raw === void 0) return null;
|
|
737
|
+
const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
|
|
738
|
+
if (numeric !== null) {
|
|
739
|
+
if (!Number.isFinite(numeric) || numeric < 0) return null;
|
|
740
|
+
if (numeric > 1e11) return Math.max(0, numeric - now);
|
|
741
|
+
if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
|
|
742
|
+
return numeric * 1e3;
|
|
743
|
+
}
|
|
744
|
+
if (typeof raw !== "string") return null;
|
|
745
|
+
const at = Date.parse(raw.trim());
|
|
746
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
747
|
+
}
|
|
748
|
+
function getRetryAfterMs(error, now = Date.now()) {
|
|
749
|
+
const err = asDict(error);
|
|
750
|
+
if (!err) return null;
|
|
751
|
+
const details = asDict(err.details);
|
|
752
|
+
const response = asDict(err.response);
|
|
753
|
+
const body = asDict(err.body) ?? asDict(response?.data);
|
|
754
|
+
const bodyError = asDict(body?.error);
|
|
755
|
+
const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
|
|
756
|
+
for (const source of [err, details, bodyDetails]) {
|
|
757
|
+
const ms = source?.retryAfterMs;
|
|
758
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
|
|
759
|
+
}
|
|
760
|
+
for (const source of [err, details, bodyDetails, bodyError]) {
|
|
761
|
+
const parsed = parseRetryAfter(source?.retryAfter, now);
|
|
762
|
+
if (parsed !== null) return parsed;
|
|
763
|
+
}
|
|
764
|
+
const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
|
|
765
|
+
for (const bag of headerBags) {
|
|
766
|
+
const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
|
|
767
|
+
if (parsed !== null) return parsed;
|
|
768
|
+
}
|
|
769
|
+
for (const source of [details, bodyDetails, err]) {
|
|
770
|
+
for (const key of ["resetAt", "reset", "rateLimitReset"]) {
|
|
771
|
+
const parsed = parseResetAt(source?.[key], now);
|
|
772
|
+
if (parsed !== null) return parsed;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
for (const bag of headerBags) {
|
|
776
|
+
for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
777
|
+
const parsed = parseResetAt(readHeader(bag, header), now);
|
|
778
|
+
if (parsed !== null) return parsed;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
function getHttpStatus(error) {
|
|
784
|
+
const err = asDict(error);
|
|
785
|
+
if (!err) return null;
|
|
786
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
787
|
+
const value = err[key];
|
|
788
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
789
|
+
if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
|
|
790
|
+
}
|
|
791
|
+
const response = asDict(err.response);
|
|
792
|
+
for (const key of ["status", "statusCode"]) {
|
|
793
|
+
const value = response?.[key];
|
|
794
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
795
|
+
}
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
function isTerminalClientError(error) {
|
|
799
|
+
const status = getHttpStatus(error);
|
|
800
|
+
if (status === null) return false;
|
|
801
|
+
return status >= 400 && status < 500 && status !== 429 && status !== 408;
|
|
802
|
+
}
|
|
803
|
+
var FAILURE_GROWTH_FACTOR = 2;
|
|
804
|
+
var IDLE_GROWTH_FACTOR = 1.5;
|
|
805
|
+
function nextFailureDelayMs(currentMs, maxMs) {
|
|
806
|
+
return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
|
|
807
|
+
}
|
|
808
|
+
function nextIdleDelayMs(currentMs, maxMs) {
|
|
809
|
+
return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
|
|
810
|
+
}
|
|
811
|
+
function applyJitter(delayMs, ratio, random = Math.random) {
|
|
812
|
+
if (ratio <= 0) return delayMs;
|
|
813
|
+
return Math.round(delayMs * (1 + random() * ratio));
|
|
814
|
+
}
|
|
815
|
+
|
|
677
816
|
// src/streaming/stream-controller.ts
|
|
678
817
|
var DEFAULT_STREAMING_CONFIG = {
|
|
679
818
|
maxReconnectAttempts: 5,
|
|
680
819
|
reconnectBaseDelayMs: 1e3,
|
|
681
820
|
reconnectMaxDelayMs: 1e4,
|
|
821
|
+
// NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
|
|
822
|
+
// SSE connection after 30s of silence and routes it through the same
|
|
823
|
+
// handleConnectionError path as a real transport failure. Ordinary LLM
|
|
824
|
+
// latency exceeds 30s routinely, so it fires against working streams — that
|
|
825
|
+
// is what drives sessions into the polling fallback in the first place. The
|
|
826
|
+
// fix is a server-side SSE heartbeat, not a bigger number here; raising this
|
|
827
|
+
// is a guess against a moving target. Everything below only bounds what the
|
|
828
|
+
// fallback costs once it has been entered.
|
|
682
829
|
activityTimeoutMs: 3e4,
|
|
683
|
-
pollingIntervalMs:
|
|
830
|
+
pollingIntervalMs: 5e3,
|
|
831
|
+
pollingMaxIntervalMs: 3e4,
|
|
832
|
+
pollingMaxConsecutiveFailures: 10,
|
|
833
|
+
pollingMaxConsecutiveRateLimits: 3,
|
|
834
|
+
pollingMaxConsecutiveClientErrors: 3,
|
|
835
|
+
pollingMaxDurationMs: 18e5,
|
|
836
|
+
pollingMaxRetryAfterMs: 3e5,
|
|
837
|
+
pollingJitterRatio: 0.1,
|
|
838
|
+
sseRetryIntervalMs: 6e4
|
|
684
839
|
};
|
|
685
840
|
var DEFAULT_POLLING_ENABLED = true;
|
|
686
841
|
var ExecutionStreamControllerImpl = class {
|
|
@@ -693,9 +848,24 @@ var ExecutionStreamControllerImpl = class {
|
|
|
693
848
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
694
849
|
_lastEventId = null;
|
|
695
850
|
activityTimeoutId = null;
|
|
696
|
-
pollingIntervalId = null;
|
|
697
851
|
finalExecution = null;
|
|
698
852
|
finalError = null;
|
|
853
|
+
// --- Fallback polling state ---
|
|
854
|
+
// A self-rescheduling timeout, not setInterval: the delay has to change
|
|
855
|
+
// between ticks (that is what backoff IS), and a timeout chain also cannot
|
|
856
|
+
// stack overlapping requests the way a fixed interval does when the API is
|
|
857
|
+
// slow to answer.
|
|
858
|
+
pollTimeoutId = null;
|
|
859
|
+
pollDelayMs = 0;
|
|
860
|
+
pollConsecutiveFailures = 0;
|
|
861
|
+
pollConsecutiveRateLimits = 0;
|
|
862
|
+
pollConsecutiveClientErrors = 0;
|
|
863
|
+
/** When the loop last observed anything new — the clock the give-up bound runs on. */
|
|
864
|
+
pollLastProgressAt = 0;
|
|
865
|
+
pollInFlight = false;
|
|
866
|
+
lastPolledStatus = null;
|
|
867
|
+
sseRetryTimeoutId = null;
|
|
868
|
+
sseRetryInFlight = false;
|
|
699
869
|
// Event callbacks
|
|
700
870
|
listeners = {
|
|
701
871
|
log: [],
|
|
@@ -724,6 +894,39 @@ var ExecutionStreamControllerImpl = class {
|
|
|
724
894
|
this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
|
|
725
895
|
this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
|
|
726
896
|
}
|
|
897
|
+
// Resolved fallback-poll settings. Read through `??` rather than off the
|
|
898
|
+
// spread config so an explicit `undefined` in a caller's partial config still
|
|
899
|
+
// lands on the default instead of NaN-ing the arithmetic.
|
|
900
|
+
get pollBaseIntervalMs() {
|
|
901
|
+
return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
|
|
902
|
+
}
|
|
903
|
+
get pollMaxIntervalMs() {
|
|
904
|
+
return Math.max(
|
|
905
|
+
this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
|
|
906
|
+
this.pollBaseIntervalMs
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
get pollMaxConsecutiveFailures() {
|
|
910
|
+
return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
|
|
911
|
+
}
|
|
912
|
+
get pollMaxConsecutiveRateLimits() {
|
|
913
|
+
return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
|
|
914
|
+
}
|
|
915
|
+
get pollMaxConsecutiveClientErrors() {
|
|
916
|
+
return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
|
|
917
|
+
}
|
|
918
|
+
get pollMaxDurationMs() {
|
|
919
|
+
return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
|
|
920
|
+
}
|
|
921
|
+
get pollMaxRetryAfterMs() {
|
|
922
|
+
return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
|
|
923
|
+
}
|
|
924
|
+
get pollJitterRatio() {
|
|
925
|
+
return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
|
|
926
|
+
}
|
|
927
|
+
get sseRetryIntervalMs() {
|
|
928
|
+
return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
|
|
929
|
+
}
|
|
727
930
|
/**
|
|
728
931
|
* Get the last event ID (for resume support)
|
|
729
932
|
*/
|
|
@@ -814,28 +1017,45 @@ var ExecutionStreamControllerImpl = class {
|
|
|
814
1017
|
console.log("[SDK:StreamController] executionId:", this.options.executionId);
|
|
815
1018
|
console.log("[SDK:StreamController] orgId:", this.options.orgId);
|
|
816
1019
|
console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
|
|
817
|
-
this.sseClient
|
|
1020
|
+
if (this.sseClient) {
|
|
1021
|
+
this.sseClient.disconnect();
|
|
1022
|
+
this.sseClient = null;
|
|
1023
|
+
}
|
|
1024
|
+
const client = new SSEClient({
|
|
818
1025
|
url,
|
|
819
1026
|
authHeader: `Bearer ${authToken}`,
|
|
820
1027
|
orgId: this.options.orgId,
|
|
821
1028
|
fetch: this.options.fetch,
|
|
822
|
-
|
|
1029
|
+
// Resume from where this session left off. On a first connect this is
|
|
1030
|
+
// null and the header is omitted.
|
|
1031
|
+
lastEventId: this._lastEventId,
|
|
1032
|
+
onEvent: (event) => {
|
|
1033
|
+
if (this.sseClient !== client) return;
|
|
1034
|
+
this.handleEvent(event);
|
|
1035
|
+
},
|
|
823
1036
|
onError: (error) => {
|
|
1037
|
+
if (this.sseClient !== client) return;
|
|
824
1038
|
console.error("[SDK:StreamController] SSE error:", error);
|
|
825
1039
|
this.handleConnectionError(error);
|
|
826
1040
|
},
|
|
827
1041
|
onConnectionChange: (connected) => {
|
|
1042
|
+
if (this.sseClient !== client) return;
|
|
828
1043
|
console.log("[SDK:StreamController] SSE connection change:", connected);
|
|
829
1044
|
if (connected) {
|
|
830
1045
|
this.status = "connected";
|
|
831
1046
|
this.reconnectAttempts = 0;
|
|
1047
|
+
this.stopPolling();
|
|
832
1048
|
this.resetActivityTimeout();
|
|
833
1049
|
}
|
|
834
|
-
this.emit("connectionChange", {
|
|
1050
|
+
this.emit("connectionChange", {
|
|
1051
|
+
connected,
|
|
1052
|
+
usingFallback: this.status === "fallback_polling"
|
|
1053
|
+
});
|
|
835
1054
|
}
|
|
836
1055
|
});
|
|
1056
|
+
this.sseClient = client;
|
|
837
1057
|
try {
|
|
838
|
-
await
|
|
1058
|
+
await client.connect();
|
|
839
1059
|
console.log("[SDK:StreamController] SSE connect() completed");
|
|
840
1060
|
} catch (error) {
|
|
841
1061
|
console.error("[SDK:StreamController] SSE connect() threw:", error);
|
|
@@ -1074,6 +1294,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1074
1294
|
handleComplete(data) {
|
|
1075
1295
|
this.status = "closed";
|
|
1076
1296
|
this.stopActivityTimeout();
|
|
1297
|
+
this.stopPolling();
|
|
1077
1298
|
const sessionId = data.sessionId ?? this.options.sessionId;
|
|
1078
1299
|
const execution = {
|
|
1079
1300
|
uuid: this.options.executionId,
|
|
@@ -1100,6 +1321,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1100
1321
|
handleError(error) {
|
|
1101
1322
|
this.status = "closed";
|
|
1102
1323
|
this.stopActivityTimeout();
|
|
1324
|
+
this.stopPolling();
|
|
1103
1325
|
this.finalError = error;
|
|
1104
1326
|
this.emit("error", error);
|
|
1105
1327
|
if (this.waitReject) {
|
|
@@ -1126,6 +1348,10 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1126
1348
|
this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
|
|
1127
1349
|
return;
|
|
1128
1350
|
}
|
|
1351
|
+
if (this.status === "fallback_polling") {
|
|
1352
|
+
this.startPolling();
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1129
1355
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
1130
1356
|
this.status = "reconnecting";
|
|
1131
1357
|
this.reconnectAttempts++;
|
|
@@ -1195,42 +1421,216 @@ var ExecutionStreamControllerImpl = class {
|
|
|
1195
1421
|
}
|
|
1196
1422
|
}
|
|
1197
1423
|
/**
|
|
1198
|
-
* Start polling fallback
|
|
1424
|
+
* Start polling fallback.
|
|
1425
|
+
*
|
|
1426
|
+
* Idempotent — calling it while a loop is already running is a no-op, so no
|
|
1427
|
+
* path can end up with two loops racing the same execution.
|
|
1199
1428
|
*/
|
|
1200
1429
|
startPolling() {
|
|
1201
|
-
this.
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1430
|
+
if (this.pollTimeoutId !== null || this.pollInFlight) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
this.pollLastProgressAt = Date.now();
|
|
1434
|
+
this.pollDelayMs = this.pollBaseIntervalMs;
|
|
1435
|
+
this.pollConsecutiveFailures = 0;
|
|
1436
|
+
this.pollConsecutiveRateLimits = 0;
|
|
1437
|
+
this.pollConsecutiveClientErrors = 0;
|
|
1438
|
+
this.lastPolledStatus = null;
|
|
1439
|
+
this.stopActivityTimeout();
|
|
1440
|
+
this.scheduleSSERetry();
|
|
1441
|
+
this.schedulePoll(this.pollDelayMs);
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Queue the next poll tick.
|
|
1445
|
+
*/
|
|
1446
|
+
schedulePoll(delayMs) {
|
|
1447
|
+
if (this.status !== "fallback_polling") {
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
1451
|
+
this.pollTimeoutId = null;
|
|
1452
|
+
void this.runPoll();
|
|
1453
|
+
}, Math.max(0, Math.round(delayMs)));
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* One poll tick: fetch state, emit what is new, then decide how long to wait
|
|
1457
|
+
* before the next one — or whether there should be a next one at all.
|
|
1458
|
+
*/
|
|
1459
|
+
async runPoll() {
|
|
1460
|
+
if (this.status !== "fallback_polling") {
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
|
|
1464
|
+
this.failPolling(
|
|
1465
|
+
`Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
|
|
1466
|
+
"polling_timeout"
|
|
1467
|
+
);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
this.pollInFlight = true;
|
|
1471
|
+
try {
|
|
1472
|
+
const execution = await this.options.getExecution(this.options.executionId);
|
|
1473
|
+
this.pollConsecutiveFailures = 0;
|
|
1474
|
+
this.pollConsecutiveRateLimits = 0;
|
|
1475
|
+
this.pollConsecutiveClientErrors = 0;
|
|
1476
|
+
let sawNewInfo = false;
|
|
1477
|
+
for (const log of execution.logs ?? []) {
|
|
1478
|
+
if (!this.seenEventIds.has(log.uuid)) {
|
|
1479
|
+
this.seenEventIds.add(log.uuid);
|
|
1480
|
+
this.emit("log", log);
|
|
1481
|
+
sawNewInfo = true;
|
|
1219
1482
|
}
|
|
1220
|
-
} catch (error) {
|
|
1221
|
-
console.error("Polling error:", error);
|
|
1222
1483
|
}
|
|
1223
|
-
|
|
1484
|
+
if (execution.status !== this.lastPolledStatus) {
|
|
1485
|
+
this.lastPolledStatus = execution.status;
|
|
1486
|
+
sawNewInfo = true;
|
|
1487
|
+
}
|
|
1488
|
+
if (sawNewInfo) {
|
|
1489
|
+
this.pollLastProgressAt = Date.now();
|
|
1490
|
+
}
|
|
1491
|
+
if (execution.status === "completed") {
|
|
1492
|
+
this.stopPolling();
|
|
1493
|
+
this.handleComplete(execution);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (execution.status === "failed" || execution.status === "cancelled") {
|
|
1497
|
+
this.stopPolling();
|
|
1498
|
+
this.handleError({
|
|
1499
|
+
error: execution.error ?? `Execution ${execution.status}`,
|
|
1500
|
+
errorCode: execution.status
|
|
1501
|
+
});
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
if (!this.handlePollError(error)) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
} finally {
|
|
1510
|
+
this.pollInFlight = false;
|
|
1511
|
+
}
|
|
1512
|
+
this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Apply backpressure for a failed poll.
|
|
1516
|
+
*
|
|
1517
|
+
* @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
|
|
1518
|
+
* the loop has been terminated.
|
|
1519
|
+
*/
|
|
1520
|
+
handlePollError(error) {
|
|
1521
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
1522
|
+
const rateLimited = isRateLimited(error) || retryAfterMs !== null;
|
|
1523
|
+
if (rateLimited) {
|
|
1524
|
+
this.pollConsecutiveRateLimits++;
|
|
1525
|
+
console.warn(
|
|
1526
|
+
"[SDK:StreamController] Poll rate-limited",
|
|
1527
|
+
retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
|
|
1528
|
+
);
|
|
1529
|
+
if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
|
|
1530
|
+
this.failPolling(
|
|
1531
|
+
`Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
|
|
1532
|
+
"rate_limited"
|
|
1533
|
+
);
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
|
|
1537
|
+
this.failPolling(
|
|
1538
|
+
"Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
|
|
1539
|
+
"rate_limited"
|
|
1540
|
+
);
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
|
|
1544
|
+
return true;
|
|
1545
|
+
}
|
|
1546
|
+
if (isTerminalClientError(error)) {
|
|
1547
|
+
this.pollConsecutiveClientErrors++;
|
|
1548
|
+
const status = getHttpStatus(error);
|
|
1549
|
+
console.warn(
|
|
1550
|
+
`[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
|
|
1551
|
+
);
|
|
1552
|
+
if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
|
|
1553
|
+
this.failPolling(
|
|
1554
|
+
`Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
|
|
1555
|
+
"polling_unrecoverable"
|
|
1556
|
+
);
|
|
1557
|
+
return false;
|
|
1558
|
+
}
|
|
1559
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1560
|
+
return true;
|
|
1561
|
+
}
|
|
1562
|
+
this.pollConsecutiveFailures++;
|
|
1563
|
+
console.warn(
|
|
1564
|
+
`[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
|
|
1565
|
+
error
|
|
1566
|
+
);
|
|
1567
|
+
if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
|
|
1568
|
+
this.failPolling(
|
|
1569
|
+
`Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
|
|
1570
|
+
"polling_failed"
|
|
1571
|
+
);
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
1575
|
+
return true;
|
|
1224
1576
|
}
|
|
1225
1577
|
/**
|
|
1226
|
-
*
|
|
1578
|
+
* Terminate the fallback loop and surface the reason.
|
|
1579
|
+
*/
|
|
1580
|
+
failPolling(error, errorCode) {
|
|
1581
|
+
this.stopPolling();
|
|
1582
|
+
this.handleError({ error, errorCode });
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Stop polling, including the periodic SSE re-attempt that belongs to it.
|
|
1227
1586
|
*/
|
|
1228
1587
|
stopPolling() {
|
|
1229
|
-
if (this.
|
|
1230
|
-
|
|
1231
|
-
this.
|
|
1588
|
+
if (this.pollTimeoutId !== null) {
|
|
1589
|
+
clearTimeout(this.pollTimeoutId);
|
|
1590
|
+
this.pollTimeoutId = null;
|
|
1591
|
+
}
|
|
1592
|
+
if (this.sseRetryTimeoutId !== null) {
|
|
1593
|
+
clearTimeout(this.sseRetryTimeoutId);
|
|
1594
|
+
this.sseRetryTimeoutId = null;
|
|
1232
1595
|
}
|
|
1233
1596
|
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Queue the next attempt to get the real stream back.
|
|
1599
|
+
*/
|
|
1600
|
+
scheduleSSERetry() {
|
|
1601
|
+
if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
this.sseRetryTimeoutId = setTimeout(() => {
|
|
1605
|
+
this.sseRetryTimeoutId = null;
|
|
1606
|
+
this.attemptSSERecovery();
|
|
1607
|
+
}, this.sseRetryIntervalMs);
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Re-attempt SSE from inside the fallback, so a transient outage costs the
|
|
1611
|
+
* session a few polls rather than its stream for the lifetime of the tab.
|
|
1612
|
+
*
|
|
1613
|
+
* Success is NOT observed here: `SSEClient.connect()` resolves when the
|
|
1614
|
+
* stream ENDS, not when it opens. The open is observed by
|
|
1615
|
+
* `onConnectionChange(true)`, which flips status and stops this loop. The
|
|
1616
|
+
* promise below only tells us the attempt is over so the next one can be
|
|
1617
|
+
* queued.
|
|
1618
|
+
*/
|
|
1619
|
+
attemptSSERecovery() {
|
|
1620
|
+
if (this.status !== "fallback_polling") {
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
if (this.sseRetryInFlight) {
|
|
1624
|
+
this.scheduleSSERetry();
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
this.sseRetryInFlight = true;
|
|
1628
|
+
void this.connectSSE().catch(() => {
|
|
1629
|
+
}).finally(() => {
|
|
1630
|
+
this.sseRetryInFlight = false;
|
|
1631
|
+
this.scheduleSSERetry();
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1234
1634
|
/**
|
|
1235
1635
|
* Reset activity timeout
|
|
1236
1636
|
*/
|
|
@@ -1361,12 +1761,23 @@ async function collectImagesFromStream(stream, options = {}) {
|
|
|
1361
1761
|
});
|
|
1362
1762
|
}
|
|
1363
1763
|
|
|
1764
|
+
// src/utils/api-paths.ts
|
|
1765
|
+
var API_V1 = "/api/v1";
|
|
1766
|
+
var EXECUTION_PATH = `${API_V1}/executions`;
|
|
1767
|
+
function executionPath(executionId) {
|
|
1768
|
+
return `${EXECUTION_PATH}/${executionId}`;
|
|
1769
|
+
}
|
|
1770
|
+
function executionCancelPath(executionId) {
|
|
1771
|
+
return `${EXECUTION_PATH}/${executionId}/cancel`;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1364
1774
|
// src/resources/agents.ts
|
|
1365
1775
|
var AgentsAPI = class extends BaseResourceAPI {
|
|
1366
1776
|
constructor(http, getStreamConfig) {
|
|
1367
1777
|
super(http, "/api/v1/agents");
|
|
1368
1778
|
this.getStreamConfig = getStreamConfig;
|
|
1369
1779
|
}
|
|
1780
|
+
getStreamConfig;
|
|
1370
1781
|
// ===== LIST Operations =====
|
|
1371
1782
|
/**
|
|
1372
1783
|
* List all agents in the organization
|
|
@@ -1623,7 +2034,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
1623
2034
|
* ```
|
|
1624
2035
|
*/
|
|
1625
2036
|
async getExecution(executionId) {
|
|
1626
|
-
return this.http.get(
|
|
2037
|
+
return this.http.get(executionPath(executionId));
|
|
1627
2038
|
}
|
|
1628
2039
|
// ===== EDIT Operations =====
|
|
1629
2040
|
/**
|
|
@@ -1678,7 +2089,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
1678
2089
|
*/
|
|
1679
2090
|
async cancelExecution(executionId) {
|
|
1680
2091
|
return this.http.post(
|
|
1681
|
-
|
|
2092
|
+
executionCancelPath(executionId),
|
|
1682
2093
|
void 0,
|
|
1683
2094
|
{ skipRetry: true }
|
|
1684
2095
|
);
|
|
@@ -2179,6 +2590,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
|
|
|
2179
2590
|
}
|
|
2180
2591
|
return this.http.request({
|
|
2181
2592
|
method: "POST",
|
|
2593
|
+
// KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
|
|
2182
2594
|
path: `/executions/${executionId}/artifacts`,
|
|
2183
2595
|
body: formData,
|
|
2184
2596
|
headers: {
|
|
@@ -2272,6 +2684,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
2272
2684
|
super(http, "/api/v1/sessions");
|
|
2273
2685
|
this.getStreamConfig = getStreamConfig;
|
|
2274
2686
|
}
|
|
2687
|
+
getStreamConfig;
|
|
2275
2688
|
// ===== LIST Operations =====
|
|
2276
2689
|
/**
|
|
2277
2690
|
* List all sessions in the organization
|
|
@@ -2451,7 +2864,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
2451
2864
|
* Get execution result
|
|
2452
2865
|
*/
|
|
2453
2866
|
async getExecution(executionId) {
|
|
2454
|
-
return this.http.get(
|
|
2867
|
+
return this.http.get(executionPath(executionId));
|
|
2455
2868
|
}
|
|
2456
2869
|
// ===== EDIT Operations =====
|
|
2457
2870
|
/**
|
|
@@ -2591,6 +3004,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
2591
3004
|
super(http, "/api/v1/kanban");
|
|
2592
3005
|
this.getStreamConfig = getStreamConfig;
|
|
2593
3006
|
}
|
|
3007
|
+
getStreamConfig;
|
|
2594
3008
|
// ===== BOARD =====
|
|
2595
3009
|
/**
|
|
2596
3010
|
* Get the current org's board (lanes in declared order). Auto-provisions
|
|
@@ -2725,7 +3139,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
2725
3139
|
* @returns Execution details.
|
|
2726
3140
|
*/
|
|
2727
3141
|
async getCardRun(executionId) {
|
|
2728
|
-
return this.http.get(
|
|
3142
|
+
return this.http.get(executionPath(executionId));
|
|
2729
3143
|
}
|
|
2730
3144
|
/**
|
|
2731
3145
|
* Create a stream controller for a card's run, keyed on the card's
|
|
@@ -2792,6 +3206,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
2792
3206
|
super(http, "/api/v1/remote-agents");
|
|
2793
3207
|
this.getStreamConfig = getStreamConfig;
|
|
2794
3208
|
}
|
|
3209
|
+
getStreamConfig;
|
|
2795
3210
|
/**
|
|
2796
3211
|
* List all remote agents in the organization
|
|
2797
3212
|
*
|
|
@@ -2925,7 +3340,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
2925
3340
|
apiKey: config.apiKey,
|
|
2926
3341
|
tokenProvider: config.tokenProvider,
|
|
2927
3342
|
orgId: config.orgId,
|
|
2928
|
-
getExecution: (id) => this.http.get(
|
|
3343
|
+
getExecution: (id) => this.http.get(executionPath(id))
|
|
2929
3344
|
});
|
|
2930
3345
|
return controller;
|
|
2931
3346
|
}
|