@cadraos/sdk 0.3.11 → 0.3.13
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/chat/index.cjs +528 -48
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +9 -1
- package/dist/chat/index.d.ts +9 -1
- package/dist/chat/index.js +528 -48
- 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 +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +453 -38
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +30 -8
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.js +30 -8
- package/dist/kanban/engine/index.js.map +1 -1
- package/dist/kanban/index.cjs +490 -315
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.d.cts +5 -110
- package/dist/kanban/index.d.ts +5 -110
- package/dist/kanban/index.js +490 -315
- 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.js +453 -38
- package/dist/teams/index.js.map +1 -1
- package/package.json +1 -1
package/dist/kanban/index.js
CHANGED
|
@@ -258,12 +258,9 @@ function useKanbanBoard(options) {
|
|
|
258
258
|
]);
|
|
259
259
|
setBoard(nextBoard);
|
|
260
260
|
setCards(cardList.cards);
|
|
261
|
-
setSelectedCard(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (!next) return prev;
|
|
265
|
-
return next.run ? next : { ...next, run: prev.run };
|
|
266
|
-
});
|
|
261
|
+
setSelectedCard(
|
|
262
|
+
(prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
|
|
263
|
+
);
|
|
267
264
|
} catch (err) {
|
|
268
265
|
fail(err, "Failed to load board");
|
|
269
266
|
} finally {
|
|
@@ -278,12 +275,9 @@ function useKanbanBoard(options) {
|
|
|
278
275
|
]);
|
|
279
276
|
setBoard(nextBoard);
|
|
280
277
|
setCards(cardList.cards);
|
|
281
|
-
setSelectedCard(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (!next) return prev;
|
|
285
|
-
return next.run ? next : { ...next, run: prev.run };
|
|
286
|
-
});
|
|
278
|
+
setSelectedCard(
|
|
279
|
+
(prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
|
|
280
|
+
);
|
|
287
281
|
} catch {
|
|
288
282
|
}
|
|
289
283
|
}, [adapter]);
|
|
@@ -304,7 +298,7 @@ function useKanbanBoard(options) {
|
|
|
304
298
|
try {
|
|
305
299
|
const full = await adapter.getCard(cardId);
|
|
306
300
|
setSelectedCard(
|
|
307
|
-
(prev) => prev
|
|
301
|
+
(prev) => prev && prev.id === cardId ? full : prev
|
|
308
302
|
);
|
|
309
303
|
} catch (err) {
|
|
310
304
|
if (!known) fail(err, "Failed to open card");
|
|
@@ -413,27 +407,6 @@ function useKanbanBoard(options) {
|
|
|
413
407
|
},
|
|
414
408
|
[refresh]
|
|
415
409
|
);
|
|
416
|
-
const deleteCard = useCallback(
|
|
417
|
-
async (cardId) => {
|
|
418
|
-
if (!adapter.deleteCard) return false;
|
|
419
|
-
setError(null);
|
|
420
|
-
const prevCards = cards;
|
|
421
|
-
const prevSelected = selectedCard;
|
|
422
|
-
setCards((prev) => prev.filter((c) => c.id !== cardId));
|
|
423
|
-
setSelectedCard((prev) => prev && prev.id === cardId ? null : prev);
|
|
424
|
-
try {
|
|
425
|
-
await adapter.deleteCard(cardId);
|
|
426
|
-
void silentRefresh();
|
|
427
|
-
return true;
|
|
428
|
-
} catch (err) {
|
|
429
|
-
setCards(prevCards);
|
|
430
|
-
setSelectedCard(prevSelected);
|
|
431
|
-
fail(err, "Failed to delete card");
|
|
432
|
-
return false;
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
[adapter, cards, selectedCard, silentRefresh, fail]
|
|
436
|
-
);
|
|
437
410
|
return {
|
|
438
411
|
board,
|
|
439
412
|
lanes: board?.lanes ?? [],
|
|
@@ -449,7 +422,6 @@ function useKanbanBoard(options) {
|
|
|
449
422
|
createCard,
|
|
450
423
|
updateCard,
|
|
451
424
|
transitionCard,
|
|
452
|
-
deleteCard,
|
|
453
425
|
handleRunTerminal
|
|
454
426
|
};
|
|
455
427
|
}
|
|
@@ -2743,9 +2715,6 @@ function KanbanCard({
|
|
|
2743
2715
|
onClick,
|
|
2744
2716
|
selected,
|
|
2745
2717
|
dueState,
|
|
2746
|
-
draggable,
|
|
2747
|
-
onDragStart,
|
|
2748
|
-
onDragEnd,
|
|
2749
2718
|
className
|
|
2750
2719
|
}) {
|
|
2751
2720
|
const runStatus = card.run?.status;
|
|
@@ -2774,20 +2743,10 @@ function KanbanCard({
|
|
|
2774
2743
|
onClick: () => onClick?.(card),
|
|
2775
2744
|
"aria-pressed": selected,
|
|
2776
2745
|
"aria-busy": isWorking,
|
|
2777
|
-
draggable,
|
|
2778
|
-
onDragStart: draggable ? (e) => {
|
|
2779
|
-
e.dataTransfer.effectAllowed = "move";
|
|
2780
|
-
e.dataTransfer.setData("text/plain", card.id);
|
|
2781
|
-
onDragStart?.(card);
|
|
2782
|
-
} : void 0,
|
|
2783
|
-
onDragEnd: draggable ? () => onDragEnd?.() : void 0,
|
|
2784
2746
|
className: cn9(
|
|
2785
|
-
|
|
2786
|
-
// matching the host's cockpit cards (work-shell v2 visual pass).
|
|
2787
|
-
"relative w-full text-left rounded-xl border bg-card bg-gradient-to-b from-foreground/[0.03] to-transparent p-3 shadow-sm motion-safe:transition-shadow",
|
|
2747
|
+
"relative w-full text-left rounded-lg border bg-card p-3 shadow-sm transition",
|
|
2788
2748
|
"hover:shadow-md hover:border-primary/40",
|
|
2789
2749
|
"focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
2790
|
-
draggable && "cursor-grab active:cursor-grabbing",
|
|
2791
2750
|
// The working card carries the SINGLE focal motion on the board (F11).
|
|
2792
2751
|
isWorking ? "border-info/60 ring-2 ring-info/40 animate-pulse" : selected ? "border-primary/60 ring-2 ring-primary/30" : "border-border",
|
|
2793
2752
|
className
|
|
@@ -2864,11 +2823,6 @@ function CapMeter({ count, cap, className }) {
|
|
|
2864
2823
|
}
|
|
2865
2824
|
);
|
|
2866
2825
|
}
|
|
2867
|
-
var AGENT_GROUPS = [
|
|
2868
|
-
{ kind: "cadra", label: "Cadra agents" },
|
|
2869
|
-
{ kind: "remote", label: "Your own agents" },
|
|
2870
|
-
{ kind: "human", label: "Teammates" }
|
|
2871
|
-
];
|
|
2872
2826
|
function AgentKindIcon({ kind }) {
|
|
2873
2827
|
const common = {
|
|
2874
2828
|
viewBox: "0 0 24 24",
|
|
@@ -2911,55 +2865,17 @@ function OnlineDot({ online }) {
|
|
|
2911
2865
|
}
|
|
2912
2866
|
);
|
|
2913
2867
|
}
|
|
2914
|
-
function RemoteBadge() {
|
|
2915
|
-
return /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-full border border-accent/40 bg-accent/15 px-1.5 py-px text-[10px] font-semibold leading-4 text-accent", children: "Remote" });
|
|
2916
|
-
}
|
|
2917
2868
|
function LaneAgentPicker({
|
|
2918
2869
|
selected,
|
|
2919
2870
|
options,
|
|
2920
2871
|
onSelect,
|
|
2921
2872
|
canEdit = true,
|
|
2922
2873
|
lockedReason,
|
|
2923
|
-
onEditAgent,
|
|
2924
2874
|
className
|
|
2925
2875
|
}) {
|
|
2926
2876
|
const [open, setOpen] = useState(false);
|
|
2927
|
-
const [placement, setPlacement] = useState(null);
|
|
2928
2877
|
const rootRef = useRef(null);
|
|
2929
|
-
const triggerRef = useRef(null);
|
|
2930
2878
|
const editable = canEdit && !!onSelect && (options?.length ?? 0) > 0;
|
|
2931
|
-
const openMenu = () => {
|
|
2932
|
-
const el = triggerRef.current;
|
|
2933
|
-
if (el && typeof window !== "undefined") {
|
|
2934
|
-
const rect = el.getBoundingClientRect();
|
|
2935
|
-
const vw = window.innerWidth || 0;
|
|
2936
|
-
const vh = window.innerHeight || 0;
|
|
2937
|
-
const margin = 8;
|
|
2938
|
-
const gap = 4;
|
|
2939
|
-
const desired = 288;
|
|
2940
|
-
const width = Math.max(
|
|
2941
|
-
Math.min(rect.width || 224, Math.max(vw - margin * 2, 160)),
|
|
2942
|
-
160
|
|
2943
|
-
);
|
|
2944
|
-
const left = Math.min(
|
|
2945
|
-
Math.max(rect.left, margin),
|
|
2946
|
-
Math.max(vw - width - margin, margin)
|
|
2947
|
-
);
|
|
2948
|
-
const spaceBelow = vh - rect.bottom - gap - margin;
|
|
2949
|
-
const spaceAbove = rect.top - gap - margin;
|
|
2950
|
-
const flipUp = spaceBelow < desired && spaceAbove > spaceBelow;
|
|
2951
|
-
const maxHeight = Math.max(
|
|
2952
|
-
Math.min(desired, flipUp ? spaceAbove : spaceBelow),
|
|
2953
|
-
120
|
|
2954
|
-
);
|
|
2955
|
-
setPlacement(
|
|
2956
|
-
flipUp ? { bottom: Math.max(vh - rect.top + gap, margin), left, width, maxHeight } : { top: rect.bottom + gap, left, width, maxHeight }
|
|
2957
|
-
);
|
|
2958
|
-
} else {
|
|
2959
|
-
setPlacement(null);
|
|
2960
|
-
}
|
|
2961
|
-
setOpen(true);
|
|
2962
|
-
};
|
|
2963
2879
|
useEffect(() => {
|
|
2964
2880
|
if (!open) return;
|
|
2965
2881
|
const onDocDown = (e) => {
|
|
@@ -2993,28 +2909,14 @@ function LaneAgentPicker({
|
|
|
2993
2909
|
}
|
|
2994
2910
|
),
|
|
2995
2911
|
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: displayLabel }),
|
|
2996
|
-
kind === "remote" && /* @__PURE__ */ jsx(RemoteBadge, {}),
|
|
2997
2912
|
/* @__PURE__ */ jsx(OnlineDot, { online: selected?.online })
|
|
2998
2913
|
] });
|
|
2999
|
-
const grouped = AGENT_GROUPS.map((group) => ({
|
|
3000
|
-
...group,
|
|
3001
|
-
items: (options ?? []).filter((opt) => (opt.kind ?? "cadra") === group.kind)
|
|
3002
|
-
})).filter((group) => group.items.length > 0);
|
|
3003
|
-
const menuStyle = placement ? {
|
|
3004
|
-
position: "fixed",
|
|
3005
|
-
top: placement.top,
|
|
3006
|
-
bottom: placement.bottom,
|
|
3007
|
-
left: placement.left,
|
|
3008
|
-
width: placement.width,
|
|
3009
|
-
maxHeight: placement.maxHeight
|
|
3010
|
-
} : void 0;
|
|
3011
2914
|
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn9("relative mt-2 w-full", className), children: [
|
|
3012
2915
|
editable ? /* @__PURE__ */ jsxs(
|
|
3013
2916
|
"button",
|
|
3014
2917
|
{
|
|
3015
|
-
ref: triggerRef,
|
|
3016
2918
|
type: "button",
|
|
3017
|
-
onClick: () =>
|
|
2919
|
+
onClick: () => setOpen((o) => !o),
|
|
3018
2920
|
"aria-haspopup": "listbox",
|
|
3019
2921
|
"aria-expanded": open,
|
|
3020
2922
|
"aria-label": `Assigned agent: ${displayLabel}. Change`,
|
|
@@ -3092,106 +2994,47 @@ function LaneAgentPicker({
|
|
|
3092
2994
|
),
|
|
3093
2995
|
/* @__PURE__ */ jsx("span", { children: lockedReason })
|
|
3094
2996
|
] }),
|
|
3095
|
-
editable && open && /* @__PURE__ */
|
|
3096
|
-
"
|
|
2997
|
+
editable && open && /* @__PURE__ */ jsx(
|
|
2998
|
+
"ul",
|
|
3097
2999
|
{
|
|
3098
|
-
|
|
3000
|
+
role: "listbox",
|
|
3001
|
+
"aria-label": "Assign an agent",
|
|
3099
3002
|
className: cn9(
|
|
3100
|
-
"
|
|
3101
|
-
|
|
3003
|
+
"absolute z-20 mt-1 max-h-64 w-full overflow-y-auto rounded-lg border border-border",
|
|
3004
|
+
"bg-popover p-1 shadow-lg"
|
|
3102
3005
|
),
|
|
3103
|
-
children:
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
{
|
|
3107
|
-
role: "listbox",
|
|
3108
|
-
"aria-label": "Assign an agent",
|
|
3109
|
-
className: "min-h-0 flex-1 overflow-y-auto p-1",
|
|
3110
|
-
children: grouped.map((group) => /* @__PURE__ */ jsxs("li", { role: "presentation", children: [
|
|
3111
|
-
/* @__PURE__ */ jsx("div", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
3112
|
-
/* @__PURE__ */ jsx("ul", { role: "group", "aria-label": group.label, children: group.items.map((opt) => {
|
|
3113
|
-
const isSelected = opt.id === selected?.id;
|
|
3114
|
-
const optKind = opt.kind ?? "cadra";
|
|
3115
|
-
return /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxs(
|
|
3116
|
-
"button",
|
|
3117
|
-
{
|
|
3118
|
-
type: "button",
|
|
3119
|
-
onClick: () => {
|
|
3120
|
-
onSelect(opt.id);
|
|
3121
|
-
setOpen(false);
|
|
3122
|
-
},
|
|
3123
|
-
className: cn9(
|
|
3124
|
-
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
|
|
3125
|
-
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
3126
|
-
isSelected ? "text-primary" : "text-foreground"
|
|
3127
|
-
),
|
|
3128
|
-
children: [
|
|
3129
|
-
/* @__PURE__ */ jsx(
|
|
3130
|
-
"span",
|
|
3131
|
-
{
|
|
3132
|
-
className: cn9(
|
|
3133
|
-
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
3134
|
-
optKind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
3135
|
-
),
|
|
3136
|
-
children: /* @__PURE__ */ jsx(AgentKindIcon, { kind: optKind })
|
|
3137
|
-
}
|
|
3138
|
-
),
|
|
3139
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
|
|
3140
|
-
optKind === "remote" && /* @__PURE__ */ jsx(RemoteBadge, {}),
|
|
3141
|
-
/* @__PURE__ */ jsx(OnlineDot, { online: opt.online }),
|
|
3142
|
-
isSelected && /* @__PURE__ */ jsx(
|
|
3143
|
-
"svg",
|
|
3144
|
-
{
|
|
3145
|
-
viewBox: "0 0 24 24",
|
|
3146
|
-
fill: "none",
|
|
3147
|
-
stroke: "currentColor",
|
|
3148
|
-
strokeWidth: 2,
|
|
3149
|
-
strokeLinecap: "round",
|
|
3150
|
-
strokeLinejoin: "round",
|
|
3151
|
-
"aria-hidden": true,
|
|
3152
|
-
className: "h-3.5 w-3.5 shrink-0",
|
|
3153
|
-
children: /* @__PURE__ */ jsx("path", { d: "M20 6L9 17l-5-5" })
|
|
3154
|
-
}
|
|
3155
|
-
)
|
|
3156
|
-
]
|
|
3157
|
-
}
|
|
3158
|
-
) }, opt.id);
|
|
3159
|
-
}) })
|
|
3160
|
-
] }, group.kind))
|
|
3161
|
-
}
|
|
3162
|
-
),
|
|
3163
|
-
onEditAgent && selected && /* @__PURE__ */ jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxs(
|
|
3006
|
+
children: options.map((opt) => {
|
|
3007
|
+
const isSelected = opt.id === selected?.id;
|
|
3008
|
+
return /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxs(
|
|
3164
3009
|
"button",
|
|
3165
3010
|
{
|
|
3166
3011
|
type: "button",
|
|
3167
3012
|
onClick: () => {
|
|
3168
|
-
|
|
3013
|
+
onSelect(opt.id);
|
|
3169
3014
|
setOpen(false);
|
|
3170
3015
|
},
|
|
3171
3016
|
className: cn9(
|
|
3172
|
-
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium
|
|
3173
|
-
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
3017
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
|
|
3018
|
+
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
3019
|
+
isSelected ? "text-primary" : "text-foreground"
|
|
3174
3020
|
),
|
|
3175
3021
|
children: [
|
|
3176
3022
|
/* @__PURE__ */ jsx(
|
|
3177
|
-
"
|
|
3023
|
+
"span",
|
|
3178
3024
|
{
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
strokeLinejoin: "round",
|
|
3185
|
-
"aria-hidden": true,
|
|
3186
|
-
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
3187
|
-
children: /* @__PURE__ */ jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" })
|
|
3025
|
+
className: cn9(
|
|
3026
|
+
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
3027
|
+
(opt.kind ?? "cadra") === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
3028
|
+
),
|
|
3029
|
+
children: /* @__PURE__ */ jsx(AgentKindIcon, { kind: opt.kind ?? "cadra" })
|
|
3188
3030
|
}
|
|
3189
3031
|
),
|
|
3190
|
-
"
|
|
3032
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
|
|
3033
|
+
/* @__PURE__ */ jsx(OnlineDot, { online: opt.online })
|
|
3191
3034
|
]
|
|
3192
3035
|
}
|
|
3193
|
-
) })
|
|
3194
|
-
|
|
3036
|
+
) }, opt.id);
|
|
3037
|
+
})
|
|
3195
3038
|
}
|
|
3196
3039
|
)
|
|
3197
3040
|
] });
|
|
@@ -3216,12 +3059,6 @@ function KanbanColumn({
|
|
|
3216
3059
|
count,
|
|
3217
3060
|
agentPicker,
|
|
3218
3061
|
canEditHandlers,
|
|
3219
|
-
draggableCards,
|
|
3220
|
-
dragActive,
|
|
3221
|
-
dropAllowed,
|
|
3222
|
-
onCardDragStart,
|
|
3223
|
-
onCardDragEnd,
|
|
3224
|
-
onLaneDrop,
|
|
3225
3062
|
className
|
|
3226
3063
|
}) {
|
|
3227
3064
|
const headerCount = count ?? cards.length;
|
|
@@ -3232,21 +3069,9 @@ function KanbanColumn({
|
|
|
3232
3069
|
return /* @__PURE__ */ jsxs(
|
|
3233
3070
|
"div",
|
|
3234
3071
|
{
|
|
3235
|
-
onDragOver: dropAllowed ? (e) => {
|
|
3236
|
-
e.preventDefault();
|
|
3237
|
-
e.dataTransfer.dropEffect = "move";
|
|
3238
|
-
} : void 0,
|
|
3239
|
-
onDrop: dropAllowed && onLaneDrop ? (e) => {
|
|
3240
|
-
e.preventDefault();
|
|
3241
|
-
onLaneDrop(lane);
|
|
3242
|
-
} : void 0,
|
|
3243
3072
|
className: cn9(
|
|
3244
|
-
"flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border
|
|
3073
|
+
"flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border sm:w-72 sm:max-w-none",
|
|
3245
3074
|
isScheduled && "border-border",
|
|
3246
|
-
// MV4 drag affordance: allowed targets glow; other lanes dim so the
|
|
3247
|
-
// valid drop zones read at a glance.
|
|
3248
|
-
dragActive && dropAllowed && "border-primary/60 ring-2 ring-primary/40 bg-primary/5",
|
|
3249
|
-
dragActive && !dropAllowed && "opacity-55",
|
|
3250
3075
|
className
|
|
3251
3076
|
),
|
|
3252
3077
|
children: [
|
|
@@ -3315,10 +3140,7 @@ function KanbanColumn({
|
|
|
3315
3140
|
card,
|
|
3316
3141
|
onClick: onCardClick,
|
|
3317
3142
|
selected: card.id === selectedCardId,
|
|
3318
|
-
dueState: dueStateFor(card, lane, now)
|
|
3319
|
-
draggable: draggableCards,
|
|
3320
|
-
onDragStart: onCardDragStart,
|
|
3321
|
-
onDragEnd: onCardDragEnd
|
|
3143
|
+
dueState: dueStateFor(card, lane, now)
|
|
3322
3144
|
},
|
|
3323
3145
|
card.id
|
|
3324
3146
|
)),
|
|
@@ -3478,8 +3300,6 @@ function KanbanBoard({
|
|
|
3478
3300
|
laneAgentPicker,
|
|
3479
3301
|
scheduleStatus,
|
|
3480
3302
|
focusCardId,
|
|
3481
|
-
renderCardDrawer,
|
|
3482
|
-
enableDrag,
|
|
3483
3303
|
className
|
|
3484
3304
|
}) {
|
|
3485
3305
|
const {
|
|
@@ -3494,7 +3314,6 @@ function KanbanBoard({
|
|
|
3494
3314
|
createCard,
|
|
3495
3315
|
updateCard,
|
|
3496
3316
|
transitionCard,
|
|
3497
|
-
deleteCard,
|
|
3498
3317
|
handleRunTerminal
|
|
3499
3318
|
} = useKanbanBoard({ adapter, onError, onCardCreated, pollIntervalMs });
|
|
3500
3319
|
const lastFocusedRef = useRef(void 0);
|
|
@@ -3539,44 +3358,6 @@ function KanbanBoard({
|
|
|
3539
3358
|
(cardId, toLane) => transitionCard({ cardId, targetLane: toLane }),
|
|
3540
3359
|
[transitionCard]
|
|
3541
3360
|
);
|
|
3542
|
-
const [dragging, setDragging] = useState(null);
|
|
3543
|
-
const draggingRef = useRef(null);
|
|
3544
|
-
const dropInFlightRef = useRef(null);
|
|
3545
|
-
const allowedDropLanes = useMemo(() => {
|
|
3546
|
-
if (!dragging || !board?.transitions) return /* @__PURE__ */ new Set();
|
|
3547
|
-
return new Set(
|
|
3548
|
-
board.transitions.filter((t) => t.fromLane === dragging.lane && t.manualAllowed).map((t) => t.toLane)
|
|
3549
|
-
);
|
|
3550
|
-
}, [dragging, board?.transitions]);
|
|
3551
|
-
const handleCardDragStart = useCallback((card) => {
|
|
3552
|
-
const d = { id: card.id, lane: card.lane };
|
|
3553
|
-
draggingRef.current = d;
|
|
3554
|
-
setDragging(d);
|
|
3555
|
-
}, []);
|
|
3556
|
-
const handleCardDragEnd = useCallback(() => {
|
|
3557
|
-
draggingRef.current = null;
|
|
3558
|
-
setDragging(null);
|
|
3559
|
-
}, []);
|
|
3560
|
-
const handleLaneDrop = useCallback(
|
|
3561
|
-
(lane) => {
|
|
3562
|
-
const cur = draggingRef.current;
|
|
3563
|
-
draggingRef.current = null;
|
|
3564
|
-
setDragging(null);
|
|
3565
|
-
if (!cur || lane.key === cur.lane) return;
|
|
3566
|
-
if (dropInFlightRef.current === cur.id) return;
|
|
3567
|
-
const allowed = (board?.transitions ?? []).some(
|
|
3568
|
-
(t) => t.fromLane === cur.lane && t.toLane === lane.key && t.manualAllowed
|
|
3569
|
-
);
|
|
3570
|
-
if (!allowed) return;
|
|
3571
|
-
dropInFlightRef.current = cur.id;
|
|
3572
|
-
void Promise.resolve(transitionCard({ cardId: cur.id, targetLane: lane.key })).finally(
|
|
3573
|
-
() => {
|
|
3574
|
-
if (dropInFlightRef.current === cur.id) dropInFlightRef.current = null;
|
|
3575
|
-
}
|
|
3576
|
-
);
|
|
3577
|
-
},
|
|
3578
|
-
[board?.transitions, transitionCard]
|
|
3579
|
-
);
|
|
3580
3361
|
const initialLaneKey = lanes.find((l) => !l.terminal)?.key ?? lanes[0]?.key;
|
|
3581
3362
|
const selectedLane = selectedCard ? lanes.find((l) => l.key === selectedCard.lane) : void 0;
|
|
3582
3363
|
const canAdvanceSelected = !!selectedLane && !selectedLane.terminal;
|
|
@@ -3597,32 +3378,12 @@ function KanbanBoard({
|
|
|
3597
3378
|
showCreate: enableCreate && lane.key === initialLaneKey,
|
|
3598
3379
|
count: laneCounts?.[lane.key],
|
|
3599
3380
|
agentPicker: laneAgentPicker?.(lane),
|
|
3600
|
-
canEditHandlers
|
|
3601
|
-
draggableCards: enableDrag && enableAdvance,
|
|
3602
|
-
dragActive: !!dragging,
|
|
3603
|
-
dropAllowed: !!dragging && dragging.lane !== lane.key && allowedDropLanes.has(lane.key),
|
|
3604
|
-
onCardDragStart: handleCardDragStart,
|
|
3605
|
-
onCardDragEnd: handleCardDragEnd,
|
|
3606
|
-
onLaneDrop: handleLaneDrop
|
|
3381
|
+
canEditHandlers
|
|
3607
3382
|
},
|
|
3608
3383
|
lane.id ?? lane.key
|
|
3609
3384
|
))
|
|
3610
3385
|
] }),
|
|
3611
|
-
selectedCard &&
|
|
3612
|
-
card: selectedCard,
|
|
3613
|
-
adapter,
|
|
3614
|
-
lanes,
|
|
3615
|
-
transitions: enableAdvance ? board?.transitions : void 0,
|
|
3616
|
-
canAdvance: canAdvanceSelected,
|
|
3617
|
-
onClose: closeCard,
|
|
3618
|
-
onRetry: onRetryCard ? handleRetry : void 0,
|
|
3619
|
-
onUpdate: enableEdit ? handleUpdate : void 0,
|
|
3620
|
-
onAdvance: enableAdvance ? handleAdvance : void 0,
|
|
3621
|
-
onTransition: enableAdvance ? handleTransition : void 0,
|
|
3622
|
-
onDelete: adapter.deleteCard ? deleteCard : void 0,
|
|
3623
|
-
onRunTerminal: handleRunTerminal,
|
|
3624
|
-
onError
|
|
3625
|
-
}) : /* @__PURE__ */ jsx(
|
|
3386
|
+
selectedCard && /* @__PURE__ */ jsx(
|
|
3626
3387
|
CardDrawer,
|
|
3627
3388
|
{
|
|
3628
3389
|
card: selectedCard,
|
|
@@ -3638,7 +3399,7 @@ function KanbanBoard({
|
|
|
3638
3399
|
onRunTerminal: handleRunTerminal,
|
|
3639
3400
|
onError
|
|
3640
3401
|
}
|
|
3641
|
-
)
|
|
3402
|
+
)
|
|
3642
3403
|
] })
|
|
3643
3404
|
] });
|
|
3644
3405
|
}
|
|
@@ -3663,7 +3424,6 @@ function composeAdapter(source, boardConfigUuid, version, cardSource) {
|
|
|
3663
3424
|
transitionCard(input) {
|
|
3664
3425
|
return cardSource.transitionCard(input);
|
|
3665
3426
|
},
|
|
3666
|
-
...cardSource.deleteCard ? { deleteCard: (id) => cardSource.deleteCard(id) } : {},
|
|
3667
3427
|
...cardSource.createRunStream ? { createRunStream: (id) => cardSource.createRunStream(id) } : {}
|
|
3668
3428
|
};
|
|
3669
3429
|
}
|
|
@@ -4128,7 +3888,8 @@ var SSEClient = class {
|
|
|
4128
3888
|
"Authorization": this.config.authHeader,
|
|
4129
3889
|
"X-Org-Id": String(this.config.orgId),
|
|
4130
3890
|
"Accept": "text/event-stream",
|
|
4131
|
-
"Cache-Control": "no-cache"
|
|
3891
|
+
"Cache-Control": "no-cache",
|
|
3892
|
+
...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
|
|
4132
3893
|
},
|
|
4133
3894
|
signal: this.abortController.signal
|
|
4134
3895
|
});
|
|
@@ -4344,13 +4105,167 @@ function normalizeUrl(url) {
|
|
|
4344
4105
|
return url;
|
|
4345
4106
|
}
|
|
4346
4107
|
|
|
4108
|
+
// src/streaming/poll-policy.ts
|
|
4109
|
+
var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
|
|
4110
|
+
function asDict(value) {
|
|
4111
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
4112
|
+
}
|
|
4113
|
+
var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
|
|
4114
|
+
"rate_limit_error",
|
|
4115
|
+
"rate_limited",
|
|
4116
|
+
"rate_limit_exceeded",
|
|
4117
|
+
"too_many_requests"
|
|
4118
|
+
]);
|
|
4119
|
+
function isRateLimited(error) {
|
|
4120
|
+
if (typeof error === "string") return /\b429\b/.test(error);
|
|
4121
|
+
const err = asDict(error);
|
|
4122
|
+
if (!err) return false;
|
|
4123
|
+
for (const key of ["status", "statusCode", "httpStatus", "code"]) {
|
|
4124
|
+
if (err[key] === 429 || err[key] === "429") return true;
|
|
4125
|
+
}
|
|
4126
|
+
const response = asDict(err.response);
|
|
4127
|
+
if (response && (response.status === 429 || response.statusCode === 429)) return true;
|
|
4128
|
+
if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
|
|
4129
|
+
if (err.name === "RateLimitError") return true;
|
|
4130
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
4131
|
+
return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
|
|
4132
|
+
}
|
|
4133
|
+
function readHeader(bag, name) {
|
|
4134
|
+
if (bag === null || bag === void 0) return null;
|
|
4135
|
+
const getter = bag.get;
|
|
4136
|
+
if (typeof getter === "function") {
|
|
4137
|
+
try {
|
|
4138
|
+
const value = getter.call(bag, name);
|
|
4139
|
+
if (value !== null && value !== void 0) return String(value);
|
|
4140
|
+
} catch {
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
const dict = asDict(bag);
|
|
4144
|
+
if (!dict) return null;
|
|
4145
|
+
const wanted = name.toLowerCase();
|
|
4146
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
4147
|
+
if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
|
|
4148
|
+
return String(value);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
return null;
|
|
4152
|
+
}
|
|
4153
|
+
function parseRetryAfter(raw, now) {
|
|
4154
|
+
if (raw === null || raw === void 0) return null;
|
|
4155
|
+
if (typeof raw === "number") {
|
|
4156
|
+
return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
|
|
4157
|
+
}
|
|
4158
|
+
if (typeof raw !== "string") return null;
|
|
4159
|
+
const trimmed = raw.trim();
|
|
4160
|
+
if (trimmed === "") return null;
|
|
4161
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
4162
|
+
const at = Date.parse(trimmed);
|
|
4163
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4164
|
+
}
|
|
4165
|
+
function parseResetAt(raw, now) {
|
|
4166
|
+
if (raw === null || raw === void 0) return null;
|
|
4167
|
+
const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
|
|
4168
|
+
if (numeric !== null) {
|
|
4169
|
+
if (!Number.isFinite(numeric) || numeric < 0) return null;
|
|
4170
|
+
if (numeric > 1e11) return Math.max(0, numeric - now);
|
|
4171
|
+
if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
|
|
4172
|
+
return numeric * 1e3;
|
|
4173
|
+
}
|
|
4174
|
+
if (typeof raw !== "string") return null;
|
|
4175
|
+
const at = Date.parse(raw.trim());
|
|
4176
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4177
|
+
}
|
|
4178
|
+
function getRetryAfterMs(error, now = Date.now()) {
|
|
4179
|
+
const err = asDict(error);
|
|
4180
|
+
if (!err) return null;
|
|
4181
|
+
const details = asDict(err.details);
|
|
4182
|
+
const response = asDict(err.response);
|
|
4183
|
+
const body = asDict(err.body) ?? asDict(response?.data);
|
|
4184
|
+
const bodyError = asDict(body?.error);
|
|
4185
|
+
const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
|
|
4186
|
+
for (const source of [err, details, bodyDetails]) {
|
|
4187
|
+
const ms = source?.retryAfterMs;
|
|
4188
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
|
|
4189
|
+
}
|
|
4190
|
+
for (const source of [err, details, bodyDetails, bodyError]) {
|
|
4191
|
+
const parsed = parseRetryAfter(source?.retryAfter, now);
|
|
4192
|
+
if (parsed !== null) return parsed;
|
|
4193
|
+
}
|
|
4194
|
+
const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
|
|
4195
|
+
for (const bag of headerBags) {
|
|
4196
|
+
const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
|
|
4197
|
+
if (parsed !== null) return parsed;
|
|
4198
|
+
}
|
|
4199
|
+
for (const source of [details, bodyDetails, err]) {
|
|
4200
|
+
for (const key of ["resetAt", "reset", "rateLimitReset"]) {
|
|
4201
|
+
const parsed = parseResetAt(source?.[key], now);
|
|
4202
|
+
if (parsed !== null) return parsed;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
for (const bag of headerBags) {
|
|
4206
|
+
for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
4207
|
+
const parsed = parseResetAt(readHeader(bag, header), now);
|
|
4208
|
+
if (parsed !== null) return parsed;
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
return null;
|
|
4212
|
+
}
|
|
4213
|
+
function getHttpStatus(error) {
|
|
4214
|
+
const err = asDict(error);
|
|
4215
|
+
if (!err) return null;
|
|
4216
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
4217
|
+
const value = err[key];
|
|
4218
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4219
|
+
if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
|
|
4220
|
+
}
|
|
4221
|
+
const response = asDict(err.response);
|
|
4222
|
+
for (const key of ["status", "statusCode"]) {
|
|
4223
|
+
const value = response?.[key];
|
|
4224
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4225
|
+
}
|
|
4226
|
+
return null;
|
|
4227
|
+
}
|
|
4228
|
+
function isTerminalClientError(error) {
|
|
4229
|
+
const status = getHttpStatus(error);
|
|
4230
|
+
if (status === null) return false;
|
|
4231
|
+
return status >= 400 && status < 500 && status !== 429 && status !== 408;
|
|
4232
|
+
}
|
|
4233
|
+
var FAILURE_GROWTH_FACTOR = 2;
|
|
4234
|
+
var IDLE_GROWTH_FACTOR = 1.5;
|
|
4235
|
+
function nextFailureDelayMs(currentMs, maxMs) {
|
|
4236
|
+
return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
|
|
4237
|
+
}
|
|
4238
|
+
function nextIdleDelayMs(currentMs, maxMs) {
|
|
4239
|
+
return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
|
|
4240
|
+
}
|
|
4241
|
+
function applyJitter(delayMs, ratio, random = Math.random) {
|
|
4242
|
+
if (ratio <= 0) return delayMs;
|
|
4243
|
+
return Math.round(delayMs * (1 + random() * ratio));
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4347
4246
|
// src/streaming/stream-controller.ts
|
|
4348
4247
|
var DEFAULT_STREAMING_CONFIG = {
|
|
4349
4248
|
maxReconnectAttempts: 5,
|
|
4350
4249
|
reconnectBaseDelayMs: 1e3,
|
|
4351
4250
|
reconnectMaxDelayMs: 1e4,
|
|
4251
|
+
// NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
|
|
4252
|
+
// SSE connection after 30s of silence and routes it through the same
|
|
4253
|
+
// handleConnectionError path as a real transport failure. Ordinary LLM
|
|
4254
|
+
// latency exceeds 30s routinely, so it fires against working streams — that
|
|
4255
|
+
// is what drives sessions into the polling fallback in the first place. The
|
|
4256
|
+
// fix is a server-side SSE heartbeat, not a bigger number here; raising this
|
|
4257
|
+
// is a guess against a moving target. Everything below only bounds what the
|
|
4258
|
+
// fallback costs once it has been entered.
|
|
4352
4259
|
activityTimeoutMs: 3e4,
|
|
4353
|
-
pollingIntervalMs:
|
|
4260
|
+
pollingIntervalMs: 5e3,
|
|
4261
|
+
pollingMaxIntervalMs: 3e4,
|
|
4262
|
+
pollingMaxConsecutiveFailures: 10,
|
|
4263
|
+
pollingMaxConsecutiveRateLimits: 3,
|
|
4264
|
+
pollingMaxConsecutiveClientErrors: 3,
|
|
4265
|
+
pollingMaxDurationMs: 18e5,
|
|
4266
|
+
pollingMaxRetryAfterMs: 3e5,
|
|
4267
|
+
pollingJitterRatio: 0.1,
|
|
4268
|
+
sseRetryIntervalMs: 6e4
|
|
4354
4269
|
};
|
|
4355
4270
|
var DEFAULT_POLLING_ENABLED = true;
|
|
4356
4271
|
var ExecutionStreamControllerImpl = class {
|
|
@@ -4363,9 +4278,24 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4363
4278
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
4364
4279
|
_lastEventId = null;
|
|
4365
4280
|
activityTimeoutId = null;
|
|
4366
|
-
pollingIntervalId = null;
|
|
4367
4281
|
finalExecution = null;
|
|
4368
4282
|
finalError = null;
|
|
4283
|
+
// --- Fallback polling state ---
|
|
4284
|
+
// A self-rescheduling timeout, not setInterval: the delay has to change
|
|
4285
|
+
// between ticks (that is what backoff IS), and a timeout chain also cannot
|
|
4286
|
+
// stack overlapping requests the way a fixed interval does when the API is
|
|
4287
|
+
// slow to answer.
|
|
4288
|
+
pollTimeoutId = null;
|
|
4289
|
+
pollDelayMs = 0;
|
|
4290
|
+
pollConsecutiveFailures = 0;
|
|
4291
|
+
pollConsecutiveRateLimits = 0;
|
|
4292
|
+
pollConsecutiveClientErrors = 0;
|
|
4293
|
+
/** When the loop last observed anything new — the clock the give-up bound runs on. */
|
|
4294
|
+
pollLastProgressAt = 0;
|
|
4295
|
+
pollInFlight = false;
|
|
4296
|
+
lastPolledStatus = null;
|
|
4297
|
+
sseRetryTimeoutId = null;
|
|
4298
|
+
sseRetryInFlight = false;
|
|
4369
4299
|
// Event callbacks
|
|
4370
4300
|
listeners = {
|
|
4371
4301
|
log: [],
|
|
@@ -4394,6 +4324,39 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4394
4324
|
this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
|
|
4395
4325
|
this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
|
|
4396
4326
|
}
|
|
4327
|
+
// Resolved fallback-poll settings. Read through `??` rather than off the
|
|
4328
|
+
// spread config so an explicit `undefined` in a caller's partial config still
|
|
4329
|
+
// lands on the default instead of NaN-ing the arithmetic.
|
|
4330
|
+
get pollBaseIntervalMs() {
|
|
4331
|
+
return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
|
|
4332
|
+
}
|
|
4333
|
+
get pollMaxIntervalMs() {
|
|
4334
|
+
return Math.max(
|
|
4335
|
+
this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
|
|
4336
|
+
this.pollBaseIntervalMs
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
get pollMaxConsecutiveFailures() {
|
|
4340
|
+
return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
|
|
4341
|
+
}
|
|
4342
|
+
get pollMaxConsecutiveRateLimits() {
|
|
4343
|
+
return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
|
|
4344
|
+
}
|
|
4345
|
+
get pollMaxConsecutiveClientErrors() {
|
|
4346
|
+
return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
|
|
4347
|
+
}
|
|
4348
|
+
get pollMaxDurationMs() {
|
|
4349
|
+
return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
|
|
4350
|
+
}
|
|
4351
|
+
get pollMaxRetryAfterMs() {
|
|
4352
|
+
return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
|
|
4353
|
+
}
|
|
4354
|
+
get pollJitterRatio() {
|
|
4355
|
+
return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
|
|
4356
|
+
}
|
|
4357
|
+
get sseRetryIntervalMs() {
|
|
4358
|
+
return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
|
|
4359
|
+
}
|
|
4397
4360
|
/**
|
|
4398
4361
|
* Get the last event ID (for resume support)
|
|
4399
4362
|
*/
|
|
@@ -4484,28 +4447,45 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4484
4447
|
console.log("[SDK:StreamController] executionId:", this.options.executionId);
|
|
4485
4448
|
console.log("[SDK:StreamController] orgId:", this.options.orgId);
|
|
4486
4449
|
console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
|
|
4487
|
-
this.sseClient
|
|
4450
|
+
if (this.sseClient) {
|
|
4451
|
+
this.sseClient.disconnect();
|
|
4452
|
+
this.sseClient = null;
|
|
4453
|
+
}
|
|
4454
|
+
const client = new SSEClient({
|
|
4488
4455
|
url,
|
|
4489
4456
|
authHeader: `Bearer ${authToken}`,
|
|
4490
4457
|
orgId: this.options.orgId,
|
|
4491
4458
|
fetch: this.options.fetch,
|
|
4492
|
-
|
|
4459
|
+
// Resume from where this session left off. On a first connect this is
|
|
4460
|
+
// null and the header is omitted.
|
|
4461
|
+
lastEventId: this._lastEventId,
|
|
4462
|
+
onEvent: (event) => {
|
|
4463
|
+
if (this.sseClient !== client) return;
|
|
4464
|
+
this.handleEvent(event);
|
|
4465
|
+
},
|
|
4493
4466
|
onError: (error) => {
|
|
4467
|
+
if (this.sseClient !== client) return;
|
|
4494
4468
|
console.error("[SDK:StreamController] SSE error:", error);
|
|
4495
4469
|
this.handleConnectionError(error);
|
|
4496
4470
|
},
|
|
4497
4471
|
onConnectionChange: (connected) => {
|
|
4472
|
+
if (this.sseClient !== client) return;
|
|
4498
4473
|
console.log("[SDK:StreamController] SSE connection change:", connected);
|
|
4499
4474
|
if (connected) {
|
|
4500
4475
|
this.status = "connected";
|
|
4501
4476
|
this.reconnectAttempts = 0;
|
|
4477
|
+
this.stopPolling();
|
|
4502
4478
|
this.resetActivityTimeout();
|
|
4503
4479
|
}
|
|
4504
|
-
this.emit("connectionChange", {
|
|
4480
|
+
this.emit("connectionChange", {
|
|
4481
|
+
connected,
|
|
4482
|
+
usingFallback: this.status === "fallback_polling"
|
|
4483
|
+
});
|
|
4505
4484
|
}
|
|
4506
4485
|
});
|
|
4486
|
+
this.sseClient = client;
|
|
4507
4487
|
try {
|
|
4508
|
-
await
|
|
4488
|
+
await client.connect();
|
|
4509
4489
|
console.log("[SDK:StreamController] SSE connect() completed");
|
|
4510
4490
|
} catch (error) {
|
|
4511
4491
|
console.error("[SDK:StreamController] SSE connect() threw:", error);
|
|
@@ -4744,6 +4724,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4744
4724
|
handleComplete(data) {
|
|
4745
4725
|
this.status = "closed";
|
|
4746
4726
|
this.stopActivityTimeout();
|
|
4727
|
+
this.stopPolling();
|
|
4747
4728
|
const sessionId = data.sessionId ?? this.options.sessionId;
|
|
4748
4729
|
const execution = {
|
|
4749
4730
|
uuid: this.options.executionId,
|
|
@@ -4770,6 +4751,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4770
4751
|
handleError(error) {
|
|
4771
4752
|
this.status = "closed";
|
|
4772
4753
|
this.stopActivityTimeout();
|
|
4754
|
+
this.stopPolling();
|
|
4773
4755
|
this.finalError = error;
|
|
4774
4756
|
this.emit("error", error);
|
|
4775
4757
|
if (this.waitReject) {
|
|
@@ -4796,6 +4778,10 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4796
4778
|
this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
|
|
4797
4779
|
return;
|
|
4798
4780
|
}
|
|
4781
|
+
if (this.status === "fallback_polling") {
|
|
4782
|
+
this.startPolling();
|
|
4783
|
+
return;
|
|
4784
|
+
}
|
|
4799
4785
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
4800
4786
|
this.status = "reconnecting";
|
|
4801
4787
|
this.reconnectAttempts++;
|
|
@@ -4865,42 +4851,216 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4865
4851
|
}
|
|
4866
4852
|
}
|
|
4867
4853
|
/**
|
|
4868
|
-
* Start polling fallback
|
|
4854
|
+
* Start polling fallback.
|
|
4855
|
+
*
|
|
4856
|
+
* Idempotent — calling it while a loop is already running is a no-op, so no
|
|
4857
|
+
* path can end up with two loops racing the same execution.
|
|
4869
4858
|
*/
|
|
4870
4859
|
startPolling() {
|
|
4871
|
-
this.
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4860
|
+
if (this.pollTimeoutId !== null || this.pollInFlight) {
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4863
|
+
this.pollLastProgressAt = Date.now();
|
|
4864
|
+
this.pollDelayMs = this.pollBaseIntervalMs;
|
|
4865
|
+
this.pollConsecutiveFailures = 0;
|
|
4866
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4867
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4868
|
+
this.lastPolledStatus = null;
|
|
4869
|
+
this.stopActivityTimeout();
|
|
4870
|
+
this.scheduleSSERetry();
|
|
4871
|
+
this.schedulePoll(this.pollDelayMs);
|
|
4872
|
+
}
|
|
4873
|
+
/**
|
|
4874
|
+
* Queue the next poll tick.
|
|
4875
|
+
*/
|
|
4876
|
+
schedulePoll(delayMs) {
|
|
4877
|
+
if (this.status !== "fallback_polling") {
|
|
4878
|
+
return;
|
|
4879
|
+
}
|
|
4880
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
4881
|
+
this.pollTimeoutId = null;
|
|
4882
|
+
void this.runPoll();
|
|
4883
|
+
}, Math.max(0, Math.round(delayMs)));
|
|
4884
|
+
}
|
|
4885
|
+
/**
|
|
4886
|
+
* One poll tick: fetch state, emit what is new, then decide how long to wait
|
|
4887
|
+
* before the next one — or whether there should be a next one at all.
|
|
4888
|
+
*/
|
|
4889
|
+
async runPoll() {
|
|
4890
|
+
if (this.status !== "fallback_polling") {
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4893
|
+
if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
|
|
4894
|
+
this.failPolling(
|
|
4895
|
+
`Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
|
|
4896
|
+
"polling_timeout"
|
|
4897
|
+
);
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
this.pollInFlight = true;
|
|
4901
|
+
try {
|
|
4902
|
+
const execution = await this.options.getExecution(this.options.executionId);
|
|
4903
|
+
this.pollConsecutiveFailures = 0;
|
|
4904
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4905
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4906
|
+
let sawNewInfo = false;
|
|
4907
|
+
for (const log of execution.logs ?? []) {
|
|
4908
|
+
if (!this.seenEventIds.has(log.uuid)) {
|
|
4909
|
+
this.seenEventIds.add(log.uuid);
|
|
4910
|
+
this.emit("log", log);
|
|
4911
|
+
sawNewInfo = true;
|
|
4889
4912
|
}
|
|
4890
|
-
} catch (error) {
|
|
4891
|
-
console.error("Polling error:", error);
|
|
4892
4913
|
}
|
|
4893
|
-
|
|
4914
|
+
if (execution.status !== this.lastPolledStatus) {
|
|
4915
|
+
this.lastPolledStatus = execution.status;
|
|
4916
|
+
sawNewInfo = true;
|
|
4917
|
+
}
|
|
4918
|
+
if (sawNewInfo) {
|
|
4919
|
+
this.pollLastProgressAt = Date.now();
|
|
4920
|
+
}
|
|
4921
|
+
if (execution.status === "completed") {
|
|
4922
|
+
this.stopPolling();
|
|
4923
|
+
this.handleComplete(execution);
|
|
4924
|
+
return;
|
|
4925
|
+
}
|
|
4926
|
+
if (execution.status === "failed" || execution.status === "cancelled") {
|
|
4927
|
+
this.stopPolling();
|
|
4928
|
+
this.handleError({
|
|
4929
|
+
error: execution.error ?? `Execution ${execution.status}`,
|
|
4930
|
+
errorCode: execution.status
|
|
4931
|
+
});
|
|
4932
|
+
return;
|
|
4933
|
+
}
|
|
4934
|
+
this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4935
|
+
} catch (error) {
|
|
4936
|
+
if (!this.handlePollError(error)) {
|
|
4937
|
+
return;
|
|
4938
|
+
}
|
|
4939
|
+
} finally {
|
|
4940
|
+
this.pollInFlight = false;
|
|
4941
|
+
}
|
|
4942
|
+
this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
|
|
4943
|
+
}
|
|
4944
|
+
/**
|
|
4945
|
+
* Apply backpressure for a failed poll.
|
|
4946
|
+
*
|
|
4947
|
+
* @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
|
|
4948
|
+
* the loop has been terminated.
|
|
4949
|
+
*/
|
|
4950
|
+
handlePollError(error) {
|
|
4951
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
4952
|
+
const rateLimited = isRateLimited(error) || retryAfterMs !== null;
|
|
4953
|
+
if (rateLimited) {
|
|
4954
|
+
this.pollConsecutiveRateLimits++;
|
|
4955
|
+
console.warn(
|
|
4956
|
+
"[SDK:StreamController] Poll rate-limited",
|
|
4957
|
+
retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
|
|
4958
|
+
);
|
|
4959
|
+
if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
|
|
4960
|
+
this.failPolling(
|
|
4961
|
+
`Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
|
|
4962
|
+
"rate_limited"
|
|
4963
|
+
);
|
|
4964
|
+
return false;
|
|
4965
|
+
}
|
|
4966
|
+
if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
|
|
4967
|
+
this.failPolling(
|
|
4968
|
+
"Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
|
|
4969
|
+
"rate_limited"
|
|
4970
|
+
);
|
|
4971
|
+
return false;
|
|
4972
|
+
}
|
|
4973
|
+
this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
|
|
4974
|
+
return true;
|
|
4975
|
+
}
|
|
4976
|
+
if (isTerminalClientError(error)) {
|
|
4977
|
+
this.pollConsecutiveClientErrors++;
|
|
4978
|
+
const status = getHttpStatus(error);
|
|
4979
|
+
console.warn(
|
|
4980
|
+
`[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
|
|
4981
|
+
);
|
|
4982
|
+
if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
|
|
4983
|
+
this.failPolling(
|
|
4984
|
+
`Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
|
|
4985
|
+
"polling_unrecoverable"
|
|
4986
|
+
);
|
|
4987
|
+
return false;
|
|
4988
|
+
}
|
|
4989
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4990
|
+
return true;
|
|
4991
|
+
}
|
|
4992
|
+
this.pollConsecutiveFailures++;
|
|
4993
|
+
console.warn(
|
|
4994
|
+
`[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
|
|
4995
|
+
error
|
|
4996
|
+
);
|
|
4997
|
+
if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
|
|
4998
|
+
this.failPolling(
|
|
4999
|
+
`Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
|
|
5000
|
+
"polling_failed"
|
|
5001
|
+
);
|
|
5002
|
+
return false;
|
|
5003
|
+
}
|
|
5004
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
5005
|
+
return true;
|
|
4894
5006
|
}
|
|
4895
5007
|
/**
|
|
4896
|
-
*
|
|
5008
|
+
* Terminate the fallback loop and surface the reason.
|
|
5009
|
+
*/
|
|
5010
|
+
failPolling(error, errorCode) {
|
|
5011
|
+
this.stopPolling();
|
|
5012
|
+
this.handleError({ error, errorCode });
|
|
5013
|
+
}
|
|
5014
|
+
/**
|
|
5015
|
+
* Stop polling, including the periodic SSE re-attempt that belongs to it.
|
|
4897
5016
|
*/
|
|
4898
5017
|
stopPolling() {
|
|
4899
|
-
if (this.
|
|
4900
|
-
|
|
4901
|
-
this.
|
|
5018
|
+
if (this.pollTimeoutId !== null) {
|
|
5019
|
+
clearTimeout(this.pollTimeoutId);
|
|
5020
|
+
this.pollTimeoutId = null;
|
|
5021
|
+
}
|
|
5022
|
+
if (this.sseRetryTimeoutId !== null) {
|
|
5023
|
+
clearTimeout(this.sseRetryTimeoutId);
|
|
5024
|
+
this.sseRetryTimeoutId = null;
|
|
4902
5025
|
}
|
|
4903
5026
|
}
|
|
5027
|
+
/**
|
|
5028
|
+
* Queue the next attempt to get the real stream back.
|
|
5029
|
+
*/
|
|
5030
|
+
scheduleSSERetry() {
|
|
5031
|
+
if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
|
|
5032
|
+
return;
|
|
5033
|
+
}
|
|
5034
|
+
this.sseRetryTimeoutId = setTimeout(() => {
|
|
5035
|
+
this.sseRetryTimeoutId = null;
|
|
5036
|
+
this.attemptSSERecovery();
|
|
5037
|
+
}, this.sseRetryIntervalMs);
|
|
5038
|
+
}
|
|
5039
|
+
/**
|
|
5040
|
+
* Re-attempt SSE from inside the fallback, so a transient outage costs the
|
|
5041
|
+
* session a few polls rather than its stream for the lifetime of the tab.
|
|
5042
|
+
*
|
|
5043
|
+
* Success is NOT observed here: `SSEClient.connect()` resolves when the
|
|
5044
|
+
* stream ENDS, not when it opens. The open is observed by
|
|
5045
|
+
* `onConnectionChange(true)`, which flips status and stops this loop. The
|
|
5046
|
+
* promise below only tells us the attempt is over so the next one can be
|
|
5047
|
+
* queued.
|
|
5048
|
+
*/
|
|
5049
|
+
attemptSSERecovery() {
|
|
5050
|
+
if (this.status !== "fallback_polling") {
|
|
5051
|
+
return;
|
|
5052
|
+
}
|
|
5053
|
+
if (this.sseRetryInFlight) {
|
|
5054
|
+
this.scheduleSSERetry();
|
|
5055
|
+
return;
|
|
5056
|
+
}
|
|
5057
|
+
this.sseRetryInFlight = true;
|
|
5058
|
+
void this.connectSSE().catch(() => {
|
|
5059
|
+
}).finally(() => {
|
|
5060
|
+
this.sseRetryInFlight = false;
|
|
5061
|
+
this.scheduleSSERetry();
|
|
5062
|
+
});
|
|
5063
|
+
}
|
|
4904
5064
|
/**
|
|
4905
5065
|
* Reset activity timeout
|
|
4906
5066
|
*/
|
|
@@ -5031,12 +5191,23 @@ async function collectImagesFromStream(stream, options = {}) {
|
|
|
5031
5191
|
});
|
|
5032
5192
|
}
|
|
5033
5193
|
|
|
5194
|
+
// src/utils/api-paths.ts
|
|
5195
|
+
var API_V1 = "/api/v1";
|
|
5196
|
+
var EXECUTION_PATH = `${API_V1}/executions`;
|
|
5197
|
+
function executionPath(executionId) {
|
|
5198
|
+
return `${EXECUTION_PATH}/${executionId}`;
|
|
5199
|
+
}
|
|
5200
|
+
function executionCancelPath(executionId) {
|
|
5201
|
+
return `${EXECUTION_PATH}/${executionId}/cancel`;
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5034
5204
|
// src/resources/agents.ts
|
|
5035
5205
|
var AgentsAPI = class extends BaseResourceAPI {
|
|
5036
5206
|
constructor(http, getStreamConfig) {
|
|
5037
5207
|
super(http, "/api/v1/agents");
|
|
5038
5208
|
this.getStreamConfig = getStreamConfig;
|
|
5039
5209
|
}
|
|
5210
|
+
getStreamConfig;
|
|
5040
5211
|
// ===== LIST Operations =====
|
|
5041
5212
|
/**
|
|
5042
5213
|
* List all agents in the organization
|
|
@@ -5293,7 +5464,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5293
5464
|
* ```
|
|
5294
5465
|
*/
|
|
5295
5466
|
async getExecution(executionId) {
|
|
5296
|
-
return this.http.get(
|
|
5467
|
+
return this.http.get(executionPath(executionId));
|
|
5297
5468
|
}
|
|
5298
5469
|
// ===== EDIT Operations =====
|
|
5299
5470
|
/**
|
|
@@ -5348,7 +5519,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5348
5519
|
*/
|
|
5349
5520
|
async cancelExecution(executionId) {
|
|
5350
5521
|
return this.http.post(
|
|
5351
|
-
|
|
5522
|
+
executionCancelPath(executionId),
|
|
5352
5523
|
void 0,
|
|
5353
5524
|
{ skipRetry: true }
|
|
5354
5525
|
);
|
|
@@ -5849,6 +6020,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
|
|
|
5849
6020
|
}
|
|
5850
6021
|
return this.http.request({
|
|
5851
6022
|
method: "POST",
|
|
6023
|
+
// KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
|
|
5852
6024
|
path: `/executions/${executionId}/artifacts`,
|
|
5853
6025
|
body: formData,
|
|
5854
6026
|
headers: {
|
|
@@ -5942,6 +6114,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
5942
6114
|
super(http, "/api/v1/sessions");
|
|
5943
6115
|
this.getStreamConfig = getStreamConfig;
|
|
5944
6116
|
}
|
|
6117
|
+
getStreamConfig;
|
|
5945
6118
|
// ===== LIST Operations =====
|
|
5946
6119
|
/**
|
|
5947
6120
|
* List all sessions in the organization
|
|
@@ -6121,7 +6294,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
6121
6294
|
* Get execution result
|
|
6122
6295
|
*/
|
|
6123
6296
|
async getExecution(executionId) {
|
|
6124
|
-
return this.http.get(
|
|
6297
|
+
return this.http.get(executionPath(executionId));
|
|
6125
6298
|
}
|
|
6126
6299
|
// ===== EDIT Operations =====
|
|
6127
6300
|
/**
|
|
@@ -6261,6 +6434,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6261
6434
|
super(http, "/api/v1/kanban");
|
|
6262
6435
|
this.getStreamConfig = getStreamConfig;
|
|
6263
6436
|
}
|
|
6437
|
+
getStreamConfig;
|
|
6264
6438
|
// ===== BOARD =====
|
|
6265
6439
|
/**
|
|
6266
6440
|
* Get the current org's board (lanes in declared order). Auto-provisions
|
|
@@ -6395,7 +6569,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6395
6569
|
* @returns Execution details.
|
|
6396
6570
|
*/
|
|
6397
6571
|
async getCardRun(executionId) {
|
|
6398
|
-
return this.http.get(
|
|
6572
|
+
return this.http.get(executionPath(executionId));
|
|
6399
6573
|
}
|
|
6400
6574
|
/**
|
|
6401
6575
|
* Create a stream controller for a card's run, keyed on the card's
|
|
@@ -6462,6 +6636,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6462
6636
|
super(http, "/api/v1/remote-agents");
|
|
6463
6637
|
this.getStreamConfig = getStreamConfig;
|
|
6464
6638
|
}
|
|
6639
|
+
getStreamConfig;
|
|
6465
6640
|
/**
|
|
6466
6641
|
* List all remote agents in the organization
|
|
6467
6642
|
*
|
|
@@ -6595,7 +6770,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6595
6770
|
apiKey: config.apiKey,
|
|
6596
6771
|
tokenProvider: config.tokenProvider,
|
|
6597
6772
|
orgId: config.orgId,
|
|
6598
|
-
getExecution: (id) => this.http.get(
|
|
6773
|
+
getExecution: (id) => this.http.get(executionPath(id))
|
|
6599
6774
|
});
|
|
6600
6775
|
return controller;
|
|
6601
6776
|
}
|