@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.cjs
CHANGED
|
@@ -265,12 +265,9 @@ function useKanbanBoard(options) {
|
|
|
265
265
|
]);
|
|
266
266
|
setBoard(nextBoard);
|
|
267
267
|
setCards(cardList.cards);
|
|
268
|
-
setSelectedCard(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (!next) return prev;
|
|
272
|
-
return next.run ? next : { ...next, run: prev.run };
|
|
273
|
-
});
|
|
268
|
+
setSelectedCard(
|
|
269
|
+
(prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
|
|
270
|
+
);
|
|
274
271
|
} catch (err) {
|
|
275
272
|
fail(err, "Failed to load board");
|
|
276
273
|
} finally {
|
|
@@ -285,12 +282,9 @@ function useKanbanBoard(options) {
|
|
|
285
282
|
]);
|
|
286
283
|
setBoard(nextBoard);
|
|
287
284
|
setCards(cardList.cards);
|
|
288
|
-
setSelectedCard(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (!next) return prev;
|
|
292
|
-
return next.run ? next : { ...next, run: prev.run };
|
|
293
|
-
});
|
|
285
|
+
setSelectedCard(
|
|
286
|
+
(prev) => prev ? cardList.cards.find((c) => c.id === prev.id) ?? prev : prev
|
|
287
|
+
);
|
|
294
288
|
} catch {
|
|
295
289
|
}
|
|
296
290
|
}, [adapter]);
|
|
@@ -311,7 +305,7 @@ function useKanbanBoard(options) {
|
|
|
311
305
|
try {
|
|
312
306
|
const full = await adapter.getCard(cardId);
|
|
313
307
|
setSelectedCard(
|
|
314
|
-
(prev) => prev
|
|
308
|
+
(prev) => prev && prev.id === cardId ? full : prev
|
|
315
309
|
);
|
|
316
310
|
} catch (err) {
|
|
317
311
|
if (!known) fail(err, "Failed to open card");
|
|
@@ -420,27 +414,6 @@ function useKanbanBoard(options) {
|
|
|
420
414
|
},
|
|
421
415
|
[refresh]
|
|
422
416
|
);
|
|
423
|
-
const deleteCard = react.useCallback(
|
|
424
|
-
async (cardId) => {
|
|
425
|
-
if (!adapter.deleteCard) return false;
|
|
426
|
-
setError(null);
|
|
427
|
-
const prevCards = cards;
|
|
428
|
-
const prevSelected = selectedCard;
|
|
429
|
-
setCards((prev) => prev.filter((c) => c.id !== cardId));
|
|
430
|
-
setSelectedCard((prev) => prev && prev.id === cardId ? null : prev);
|
|
431
|
-
try {
|
|
432
|
-
await adapter.deleteCard(cardId);
|
|
433
|
-
void silentRefresh();
|
|
434
|
-
return true;
|
|
435
|
-
} catch (err) {
|
|
436
|
-
setCards(prevCards);
|
|
437
|
-
setSelectedCard(prevSelected);
|
|
438
|
-
fail(err, "Failed to delete card");
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
},
|
|
442
|
-
[adapter, cards, selectedCard, silentRefresh, fail]
|
|
443
|
-
);
|
|
444
417
|
return {
|
|
445
418
|
board,
|
|
446
419
|
lanes: board?.lanes ?? [],
|
|
@@ -456,7 +429,6 @@ function useKanbanBoard(options) {
|
|
|
456
429
|
createCard,
|
|
457
430
|
updateCard,
|
|
458
431
|
transitionCard,
|
|
459
|
-
deleteCard,
|
|
460
432
|
handleRunTerminal
|
|
461
433
|
};
|
|
462
434
|
}
|
|
@@ -2750,9 +2722,6 @@ function KanbanCard({
|
|
|
2750
2722
|
onClick,
|
|
2751
2723
|
selected,
|
|
2752
2724
|
dueState,
|
|
2753
|
-
draggable,
|
|
2754
|
-
onDragStart,
|
|
2755
|
-
onDragEnd,
|
|
2756
2725
|
className
|
|
2757
2726
|
}) {
|
|
2758
2727
|
const runStatus = card.run?.status;
|
|
@@ -2781,20 +2750,10 @@ function KanbanCard({
|
|
|
2781
2750
|
onClick: () => onClick?.(card),
|
|
2782
2751
|
"aria-pressed": selected,
|
|
2783
2752
|
"aria-busy": isWorking,
|
|
2784
|
-
draggable,
|
|
2785
|
-
onDragStart: draggable ? (e) => {
|
|
2786
|
-
e.dataTransfer.effectAllowed = "move";
|
|
2787
|
-
e.dataTransfer.setData("text/plain", card.id);
|
|
2788
|
-
onDragStart?.(card);
|
|
2789
|
-
} : void 0,
|
|
2790
|
-
onDragEnd: draggable ? () => onDragEnd?.() : void 0,
|
|
2791
2753
|
className: cn9(
|
|
2792
|
-
|
|
2793
|
-
// matching the host's cockpit cards (work-shell v2 visual pass).
|
|
2794
|
-
"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",
|
|
2754
|
+
"relative w-full text-left rounded-lg border bg-card p-3 shadow-sm transition",
|
|
2795
2755
|
"hover:shadow-md hover:border-primary/40",
|
|
2796
2756
|
"focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
2797
|
-
draggable && "cursor-grab active:cursor-grabbing",
|
|
2798
2757
|
// The working card carries the SINGLE focal motion on the board (F11).
|
|
2799
2758
|
isWorking ? "border-info/60 ring-2 ring-info/40 animate-pulse" : selected ? "border-primary/60 ring-2 ring-primary/30" : "border-border",
|
|
2800
2759
|
className
|
|
@@ -2871,11 +2830,6 @@ function CapMeter({ count, cap, className }) {
|
|
|
2871
2830
|
}
|
|
2872
2831
|
);
|
|
2873
2832
|
}
|
|
2874
|
-
var AGENT_GROUPS = [
|
|
2875
|
-
{ kind: "cadra", label: "Cadra agents" },
|
|
2876
|
-
{ kind: "remote", label: "Your own agents" },
|
|
2877
|
-
{ kind: "human", label: "Teammates" }
|
|
2878
|
-
];
|
|
2879
2833
|
function AgentKindIcon({ kind }) {
|
|
2880
2834
|
const common = {
|
|
2881
2835
|
viewBox: "0 0 24 24",
|
|
@@ -2918,55 +2872,17 @@ function OnlineDot({ online }) {
|
|
|
2918
2872
|
}
|
|
2919
2873
|
);
|
|
2920
2874
|
}
|
|
2921
|
-
function RemoteBadge() {
|
|
2922
|
-
return /* @__PURE__ */ jsxRuntime.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" });
|
|
2923
|
-
}
|
|
2924
2875
|
function LaneAgentPicker({
|
|
2925
2876
|
selected,
|
|
2926
2877
|
options,
|
|
2927
2878
|
onSelect,
|
|
2928
2879
|
canEdit = true,
|
|
2929
2880
|
lockedReason,
|
|
2930
|
-
onEditAgent,
|
|
2931
2881
|
className
|
|
2932
2882
|
}) {
|
|
2933
2883
|
const [open, setOpen] = react.useState(false);
|
|
2934
|
-
const [placement, setPlacement] = react.useState(null);
|
|
2935
2884
|
const rootRef = react.useRef(null);
|
|
2936
|
-
const triggerRef = react.useRef(null);
|
|
2937
2885
|
const editable = canEdit && !!onSelect && (options?.length ?? 0) > 0;
|
|
2938
|
-
const openMenu = () => {
|
|
2939
|
-
const el = triggerRef.current;
|
|
2940
|
-
if (el && typeof window !== "undefined") {
|
|
2941
|
-
const rect = el.getBoundingClientRect();
|
|
2942
|
-
const vw = window.innerWidth || 0;
|
|
2943
|
-
const vh = window.innerHeight || 0;
|
|
2944
|
-
const margin = 8;
|
|
2945
|
-
const gap = 4;
|
|
2946
|
-
const desired = 288;
|
|
2947
|
-
const width = Math.max(
|
|
2948
|
-
Math.min(rect.width || 224, Math.max(vw - margin * 2, 160)),
|
|
2949
|
-
160
|
|
2950
|
-
);
|
|
2951
|
-
const left = Math.min(
|
|
2952
|
-
Math.max(rect.left, margin),
|
|
2953
|
-
Math.max(vw - width - margin, margin)
|
|
2954
|
-
);
|
|
2955
|
-
const spaceBelow = vh - rect.bottom - gap - margin;
|
|
2956
|
-
const spaceAbove = rect.top - gap - margin;
|
|
2957
|
-
const flipUp = spaceBelow < desired && spaceAbove > spaceBelow;
|
|
2958
|
-
const maxHeight = Math.max(
|
|
2959
|
-
Math.min(desired, flipUp ? spaceAbove : spaceBelow),
|
|
2960
|
-
120
|
|
2961
|
-
);
|
|
2962
|
-
setPlacement(
|
|
2963
|
-
flipUp ? { bottom: Math.max(vh - rect.top + gap, margin), left, width, maxHeight } : { top: rect.bottom + gap, left, width, maxHeight }
|
|
2964
|
-
);
|
|
2965
|
-
} else {
|
|
2966
|
-
setPlacement(null);
|
|
2967
|
-
}
|
|
2968
|
-
setOpen(true);
|
|
2969
|
-
};
|
|
2970
2886
|
react.useEffect(() => {
|
|
2971
2887
|
if (!open) return;
|
|
2972
2888
|
const onDocDown = (e) => {
|
|
@@ -3000,28 +2916,14 @@ function LaneAgentPicker({
|
|
|
3000
2916
|
}
|
|
3001
2917
|
),
|
|
3002
2918
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: displayLabel }),
|
|
3003
|
-
kind === "remote" && /* @__PURE__ */ jsxRuntime.jsx(RemoteBadge, {}),
|
|
3004
2919
|
/* @__PURE__ */ jsxRuntime.jsx(OnlineDot, { online: selected?.online })
|
|
3005
2920
|
] });
|
|
3006
|
-
const grouped = AGENT_GROUPS.map((group) => ({
|
|
3007
|
-
...group,
|
|
3008
|
-
items: (options ?? []).filter((opt) => (opt.kind ?? "cadra") === group.kind)
|
|
3009
|
-
})).filter((group) => group.items.length > 0);
|
|
3010
|
-
const menuStyle = placement ? {
|
|
3011
|
-
position: "fixed",
|
|
3012
|
-
top: placement.top,
|
|
3013
|
-
bottom: placement.bottom,
|
|
3014
|
-
left: placement.left,
|
|
3015
|
-
width: placement.width,
|
|
3016
|
-
maxHeight: placement.maxHeight
|
|
3017
|
-
} : void 0;
|
|
3018
2921
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn9("relative mt-2 w-full", className), children: [
|
|
3019
2922
|
editable ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3020
2923
|
"button",
|
|
3021
2924
|
{
|
|
3022
|
-
ref: triggerRef,
|
|
3023
2925
|
type: "button",
|
|
3024
|
-
onClick: () =>
|
|
2926
|
+
onClick: () => setOpen((o) => !o),
|
|
3025
2927
|
"aria-haspopup": "listbox",
|
|
3026
2928
|
"aria-expanded": open,
|
|
3027
2929
|
"aria-label": `Assigned agent: ${displayLabel}. Change`,
|
|
@@ -3099,106 +3001,47 @@ function LaneAgentPicker({
|
|
|
3099
3001
|
),
|
|
3100
3002
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: lockedReason })
|
|
3101
3003
|
] }),
|
|
3102
|
-
editable && open && /* @__PURE__ */ jsxRuntime.
|
|
3103
|
-
"
|
|
3004
|
+
editable && open && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3005
|
+
"ul",
|
|
3104
3006
|
{
|
|
3105
|
-
|
|
3007
|
+
role: "listbox",
|
|
3008
|
+
"aria-label": "Assign an agent",
|
|
3106
3009
|
className: cn9(
|
|
3107
|
-
"
|
|
3108
|
-
|
|
3010
|
+
"absolute z-20 mt-1 max-h-64 w-full overflow-y-auto rounded-lg border border-border",
|
|
3011
|
+
"bg-popover p-1 shadow-lg"
|
|
3109
3012
|
),
|
|
3110
|
-
children:
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
{
|
|
3114
|
-
role: "listbox",
|
|
3115
|
-
"aria-label": "Assign an agent",
|
|
3116
|
-
className: "min-h-0 flex-1 overflow-y-auto p-1",
|
|
3117
|
-
children: grouped.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("li", { role: "presentation", children: [
|
|
3118
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
3119
|
-
/* @__PURE__ */ jsxRuntime.jsx("ul", { role: "group", "aria-label": group.label, children: group.items.map((opt) => {
|
|
3120
|
-
const isSelected = opt.id === selected?.id;
|
|
3121
|
-
const optKind = opt.kind ?? "cadra";
|
|
3122
|
-
return /* @__PURE__ */ jsxRuntime.jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3123
|
-
"button",
|
|
3124
|
-
{
|
|
3125
|
-
type: "button",
|
|
3126
|
-
onClick: () => {
|
|
3127
|
-
onSelect(opt.id);
|
|
3128
|
-
setOpen(false);
|
|
3129
|
-
},
|
|
3130
|
-
className: cn9(
|
|
3131
|
-
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
|
|
3132
|
-
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
3133
|
-
isSelected ? "text-primary" : "text-foreground"
|
|
3134
|
-
),
|
|
3135
|
-
children: [
|
|
3136
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3137
|
-
"span",
|
|
3138
|
-
{
|
|
3139
|
-
className: cn9(
|
|
3140
|
-
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
3141
|
-
optKind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
3142
|
-
),
|
|
3143
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(AgentKindIcon, { kind: optKind })
|
|
3144
|
-
}
|
|
3145
|
-
),
|
|
3146
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
|
|
3147
|
-
optKind === "remote" && /* @__PURE__ */ jsxRuntime.jsx(RemoteBadge, {}),
|
|
3148
|
-
/* @__PURE__ */ jsxRuntime.jsx(OnlineDot, { online: opt.online }),
|
|
3149
|
-
isSelected && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3150
|
-
"svg",
|
|
3151
|
-
{
|
|
3152
|
-
viewBox: "0 0 24 24",
|
|
3153
|
-
fill: "none",
|
|
3154
|
-
stroke: "currentColor",
|
|
3155
|
-
strokeWidth: 2,
|
|
3156
|
-
strokeLinecap: "round",
|
|
3157
|
-
strokeLinejoin: "round",
|
|
3158
|
-
"aria-hidden": true,
|
|
3159
|
-
className: "h-3.5 w-3.5 shrink-0",
|
|
3160
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6L9 17l-5-5" })
|
|
3161
|
-
}
|
|
3162
|
-
)
|
|
3163
|
-
]
|
|
3164
|
-
}
|
|
3165
|
-
) }, opt.id);
|
|
3166
|
-
}) })
|
|
3167
|
-
] }, group.kind))
|
|
3168
|
-
}
|
|
3169
|
-
),
|
|
3170
|
-
onEditAgent && selected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3013
|
+
children: options.map((opt) => {
|
|
3014
|
+
const isSelected = opt.id === selected?.id;
|
|
3015
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3171
3016
|
"button",
|
|
3172
3017
|
{
|
|
3173
3018
|
type: "button",
|
|
3174
3019
|
onClick: () => {
|
|
3175
|
-
|
|
3020
|
+
onSelect(opt.id);
|
|
3176
3021
|
setOpen(false);
|
|
3177
3022
|
},
|
|
3178
3023
|
className: cn9(
|
|
3179
|
-
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium
|
|
3180
|
-
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
3024
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
|
|
3025
|
+
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
3026
|
+
isSelected ? "text-primary" : "text-foreground"
|
|
3181
3027
|
),
|
|
3182
3028
|
children: [
|
|
3183
3029
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3184
|
-
"
|
|
3030
|
+
"span",
|
|
3185
3031
|
{
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
strokeLinejoin: "round",
|
|
3192
|
-
"aria-hidden": true,
|
|
3193
|
-
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
3194
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" })
|
|
3032
|
+
className: cn9(
|
|
3033
|
+
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
3034
|
+
(opt.kind ?? "cadra") === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
3035
|
+
),
|
|
3036
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AgentKindIcon, { kind: opt.kind ?? "cadra" })
|
|
3195
3037
|
}
|
|
3196
3038
|
),
|
|
3197
|
-
"
|
|
3039
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
|
|
3040
|
+
/* @__PURE__ */ jsxRuntime.jsx(OnlineDot, { online: opt.online })
|
|
3198
3041
|
]
|
|
3199
3042
|
}
|
|
3200
|
-
) })
|
|
3201
|
-
|
|
3043
|
+
) }, opt.id);
|
|
3044
|
+
})
|
|
3202
3045
|
}
|
|
3203
3046
|
)
|
|
3204
3047
|
] });
|
|
@@ -3223,12 +3066,6 @@ function KanbanColumn({
|
|
|
3223
3066
|
count,
|
|
3224
3067
|
agentPicker,
|
|
3225
3068
|
canEditHandlers,
|
|
3226
|
-
draggableCards,
|
|
3227
|
-
dragActive,
|
|
3228
|
-
dropAllowed,
|
|
3229
|
-
onCardDragStart,
|
|
3230
|
-
onCardDragEnd,
|
|
3231
|
-
onLaneDrop,
|
|
3232
3069
|
className
|
|
3233
3070
|
}) {
|
|
3234
3071
|
const headerCount = count ?? cards.length;
|
|
@@ -3239,21 +3076,9 @@ function KanbanColumn({
|
|
|
3239
3076
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3240
3077
|
"div",
|
|
3241
3078
|
{
|
|
3242
|
-
onDragOver: dropAllowed ? (e) => {
|
|
3243
|
-
e.preventDefault();
|
|
3244
|
-
e.dataTransfer.dropEffect = "move";
|
|
3245
|
-
} : void 0,
|
|
3246
|
-
onDrop: dropAllowed && onLaneDrop ? (e) => {
|
|
3247
|
-
e.preventDefault();
|
|
3248
|
-
onLaneDrop(lane);
|
|
3249
|
-
} : void 0,
|
|
3250
3079
|
className: cn9(
|
|
3251
|
-
"flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border
|
|
3080
|
+
"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",
|
|
3252
3081
|
isScheduled && "border-border",
|
|
3253
|
-
// MV4 drag affordance: allowed targets glow; other lanes dim so the
|
|
3254
|
-
// valid drop zones read at a glance.
|
|
3255
|
-
dragActive && dropAllowed && "border-primary/60 ring-2 ring-primary/40 bg-primary/5",
|
|
3256
|
-
dragActive && !dropAllowed && "opacity-55",
|
|
3257
3082
|
className
|
|
3258
3083
|
),
|
|
3259
3084
|
children: [
|
|
@@ -3322,10 +3147,7 @@ function KanbanColumn({
|
|
|
3322
3147
|
card,
|
|
3323
3148
|
onClick: onCardClick,
|
|
3324
3149
|
selected: card.id === selectedCardId,
|
|
3325
|
-
dueState: dueStateFor(card, lane, now)
|
|
3326
|
-
draggable: draggableCards,
|
|
3327
|
-
onDragStart: onCardDragStart,
|
|
3328
|
-
onDragEnd: onCardDragEnd
|
|
3150
|
+
dueState: dueStateFor(card, lane, now)
|
|
3329
3151
|
},
|
|
3330
3152
|
card.id
|
|
3331
3153
|
)),
|
|
@@ -3485,8 +3307,6 @@ function KanbanBoard({
|
|
|
3485
3307
|
laneAgentPicker,
|
|
3486
3308
|
scheduleStatus,
|
|
3487
3309
|
focusCardId,
|
|
3488
|
-
renderCardDrawer,
|
|
3489
|
-
enableDrag,
|
|
3490
3310
|
className
|
|
3491
3311
|
}) {
|
|
3492
3312
|
const {
|
|
@@ -3501,7 +3321,6 @@ function KanbanBoard({
|
|
|
3501
3321
|
createCard,
|
|
3502
3322
|
updateCard,
|
|
3503
3323
|
transitionCard,
|
|
3504
|
-
deleteCard,
|
|
3505
3324
|
handleRunTerminal
|
|
3506
3325
|
} = useKanbanBoard({ adapter, onError, onCardCreated, pollIntervalMs });
|
|
3507
3326
|
const lastFocusedRef = react.useRef(void 0);
|
|
@@ -3546,44 +3365,6 @@ function KanbanBoard({
|
|
|
3546
3365
|
(cardId, toLane) => transitionCard({ cardId, targetLane: toLane }),
|
|
3547
3366
|
[transitionCard]
|
|
3548
3367
|
);
|
|
3549
|
-
const [dragging, setDragging] = react.useState(null);
|
|
3550
|
-
const draggingRef = react.useRef(null);
|
|
3551
|
-
const dropInFlightRef = react.useRef(null);
|
|
3552
|
-
const allowedDropLanes = react.useMemo(() => {
|
|
3553
|
-
if (!dragging || !board?.transitions) return /* @__PURE__ */ new Set();
|
|
3554
|
-
return new Set(
|
|
3555
|
-
board.transitions.filter((t) => t.fromLane === dragging.lane && t.manualAllowed).map((t) => t.toLane)
|
|
3556
|
-
);
|
|
3557
|
-
}, [dragging, board?.transitions]);
|
|
3558
|
-
const handleCardDragStart = react.useCallback((card) => {
|
|
3559
|
-
const d = { id: card.id, lane: card.lane };
|
|
3560
|
-
draggingRef.current = d;
|
|
3561
|
-
setDragging(d);
|
|
3562
|
-
}, []);
|
|
3563
|
-
const handleCardDragEnd = react.useCallback(() => {
|
|
3564
|
-
draggingRef.current = null;
|
|
3565
|
-
setDragging(null);
|
|
3566
|
-
}, []);
|
|
3567
|
-
const handleLaneDrop = react.useCallback(
|
|
3568
|
-
(lane) => {
|
|
3569
|
-
const cur = draggingRef.current;
|
|
3570
|
-
draggingRef.current = null;
|
|
3571
|
-
setDragging(null);
|
|
3572
|
-
if (!cur || lane.key === cur.lane) return;
|
|
3573
|
-
if (dropInFlightRef.current === cur.id) return;
|
|
3574
|
-
const allowed = (board?.transitions ?? []).some(
|
|
3575
|
-
(t) => t.fromLane === cur.lane && t.toLane === lane.key && t.manualAllowed
|
|
3576
|
-
);
|
|
3577
|
-
if (!allowed) return;
|
|
3578
|
-
dropInFlightRef.current = cur.id;
|
|
3579
|
-
void Promise.resolve(transitionCard({ cardId: cur.id, targetLane: lane.key })).finally(
|
|
3580
|
-
() => {
|
|
3581
|
-
if (dropInFlightRef.current === cur.id) dropInFlightRef.current = null;
|
|
3582
|
-
}
|
|
3583
|
-
);
|
|
3584
|
-
},
|
|
3585
|
-
[board?.transitions, transitionCard]
|
|
3586
|
-
);
|
|
3587
3368
|
const initialLaneKey = lanes.find((l) => !l.terminal)?.key ?? lanes[0]?.key;
|
|
3588
3369
|
const selectedLane = selectedCard ? lanes.find((l) => l.key === selectedCard.lane) : void 0;
|
|
3589
3370
|
const canAdvanceSelected = !!selectedLane && !selectedLane.terminal;
|
|
@@ -3604,32 +3385,12 @@ function KanbanBoard({
|
|
|
3604
3385
|
showCreate: enableCreate && lane.key === initialLaneKey,
|
|
3605
3386
|
count: laneCounts?.[lane.key],
|
|
3606
3387
|
agentPicker: laneAgentPicker?.(lane),
|
|
3607
|
-
canEditHandlers
|
|
3608
|
-
draggableCards: enableDrag && enableAdvance,
|
|
3609
|
-
dragActive: !!dragging,
|
|
3610
|
-
dropAllowed: !!dragging && dragging.lane !== lane.key && allowedDropLanes.has(lane.key),
|
|
3611
|
-
onCardDragStart: handleCardDragStart,
|
|
3612
|
-
onCardDragEnd: handleCardDragEnd,
|
|
3613
|
-
onLaneDrop: handleLaneDrop
|
|
3388
|
+
canEditHandlers
|
|
3614
3389
|
},
|
|
3615
3390
|
lane.id ?? lane.key
|
|
3616
3391
|
))
|
|
3617
3392
|
] }),
|
|
3618
|
-
selectedCard &&
|
|
3619
|
-
card: selectedCard,
|
|
3620
|
-
adapter,
|
|
3621
|
-
lanes,
|
|
3622
|
-
transitions: enableAdvance ? board?.transitions : void 0,
|
|
3623
|
-
canAdvance: canAdvanceSelected,
|
|
3624
|
-
onClose: closeCard,
|
|
3625
|
-
onRetry: onRetryCard ? handleRetry : void 0,
|
|
3626
|
-
onUpdate: enableEdit ? handleUpdate : void 0,
|
|
3627
|
-
onAdvance: enableAdvance ? handleAdvance : void 0,
|
|
3628
|
-
onTransition: enableAdvance ? handleTransition : void 0,
|
|
3629
|
-
onDelete: adapter.deleteCard ? deleteCard : void 0,
|
|
3630
|
-
onRunTerminal: handleRunTerminal,
|
|
3631
|
-
onError
|
|
3632
|
-
}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3393
|
+
selectedCard && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3633
3394
|
CardDrawer,
|
|
3634
3395
|
{
|
|
3635
3396
|
card: selectedCard,
|
|
@@ -3645,7 +3406,7 @@ function KanbanBoard({
|
|
|
3645
3406
|
onRunTerminal: handleRunTerminal,
|
|
3646
3407
|
onError
|
|
3647
3408
|
}
|
|
3648
|
-
)
|
|
3409
|
+
)
|
|
3649
3410
|
] })
|
|
3650
3411
|
] });
|
|
3651
3412
|
}
|
|
@@ -3670,7 +3431,6 @@ function composeAdapter(source, boardConfigUuid, version, cardSource) {
|
|
|
3670
3431
|
transitionCard(input) {
|
|
3671
3432
|
return cardSource.transitionCard(input);
|
|
3672
3433
|
},
|
|
3673
|
-
...cardSource.deleteCard ? { deleteCard: (id) => cardSource.deleteCard(id) } : {},
|
|
3674
3434
|
...cardSource.createRunStream ? { createRunStream: (id) => cardSource.createRunStream(id) } : {}
|
|
3675
3435
|
};
|
|
3676
3436
|
}
|
|
@@ -4135,7 +3895,8 @@ var SSEClient = class {
|
|
|
4135
3895
|
"Authorization": this.config.authHeader,
|
|
4136
3896
|
"X-Org-Id": String(this.config.orgId),
|
|
4137
3897
|
"Accept": "text/event-stream",
|
|
4138
|
-
"Cache-Control": "no-cache"
|
|
3898
|
+
"Cache-Control": "no-cache",
|
|
3899
|
+
...this.config.lastEventId ? { "Last-Event-ID": this.config.lastEventId } : {}
|
|
4139
3900
|
},
|
|
4140
3901
|
signal: this.abortController.signal
|
|
4141
3902
|
});
|
|
@@ -4351,13 +4112,167 @@ function normalizeUrl(url) {
|
|
|
4351
4112
|
return url;
|
|
4352
4113
|
}
|
|
4353
4114
|
|
|
4115
|
+
// src/streaming/poll-policy.ts
|
|
4116
|
+
var UNNAMED_RATE_LIMIT_WAIT_MS = 6e4;
|
|
4117
|
+
function asDict(value) {
|
|
4118
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
4119
|
+
}
|
|
4120
|
+
var RATE_LIMIT_CODES = /* @__PURE__ */ new Set([
|
|
4121
|
+
"rate_limit_error",
|
|
4122
|
+
"rate_limited",
|
|
4123
|
+
"rate_limit_exceeded",
|
|
4124
|
+
"too_many_requests"
|
|
4125
|
+
]);
|
|
4126
|
+
function isRateLimited(error) {
|
|
4127
|
+
if (typeof error === "string") return /\b429\b/.test(error);
|
|
4128
|
+
const err = asDict(error);
|
|
4129
|
+
if (!err) return false;
|
|
4130
|
+
for (const key of ["status", "statusCode", "httpStatus", "code"]) {
|
|
4131
|
+
if (err[key] === 429 || err[key] === "429") return true;
|
|
4132
|
+
}
|
|
4133
|
+
const response = asDict(err.response);
|
|
4134
|
+
if (response && (response.status === 429 || response.statusCode === 429)) return true;
|
|
4135
|
+
if (typeof err.code === "string" && RATE_LIMIT_CODES.has(err.code.toLowerCase())) return true;
|
|
4136
|
+
if (err.name === "RateLimitError") return true;
|
|
4137
|
+
const message = typeof err.message === "string" ? err.message : "";
|
|
4138
|
+
return /\b429\b/.test(message) || /rate[\s_-]?limit/i.test(message) || /too many requests/i.test(message);
|
|
4139
|
+
}
|
|
4140
|
+
function readHeader(bag, name) {
|
|
4141
|
+
if (bag === null || bag === void 0) return null;
|
|
4142
|
+
const getter = bag.get;
|
|
4143
|
+
if (typeof getter === "function") {
|
|
4144
|
+
try {
|
|
4145
|
+
const value = getter.call(bag, name);
|
|
4146
|
+
if (value !== null && value !== void 0) return String(value);
|
|
4147
|
+
} catch {
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
const dict = asDict(bag);
|
|
4151
|
+
if (!dict) return null;
|
|
4152
|
+
const wanted = name.toLowerCase();
|
|
4153
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
4154
|
+
if (key.toLowerCase() === wanted && value !== null && value !== void 0) {
|
|
4155
|
+
return String(value);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
function parseRetryAfter(raw, now) {
|
|
4161
|
+
if (raw === null || raw === void 0) return null;
|
|
4162
|
+
if (typeof raw === "number") {
|
|
4163
|
+
return Number.isFinite(raw) && raw >= 0 ? raw * 1e3 : null;
|
|
4164
|
+
}
|
|
4165
|
+
if (typeof raw !== "string") return null;
|
|
4166
|
+
const trimmed = raw.trim();
|
|
4167
|
+
if (trimmed === "") return null;
|
|
4168
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed) * 1e3;
|
|
4169
|
+
const at = Date.parse(trimmed);
|
|
4170
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4171
|
+
}
|
|
4172
|
+
function parseResetAt(raw, now) {
|
|
4173
|
+
if (raw === null || raw === void 0) return null;
|
|
4174
|
+
const numeric = typeof raw === "number" ? raw : typeof raw === "string" && /^\d+(\.\d+)?$/.test(raw.trim()) ? Number(raw.trim()) : null;
|
|
4175
|
+
if (numeric !== null) {
|
|
4176
|
+
if (!Number.isFinite(numeric) || numeric < 0) return null;
|
|
4177
|
+
if (numeric > 1e11) return Math.max(0, numeric - now);
|
|
4178
|
+
if (numeric > 1e9) return Math.max(0, numeric * 1e3 - now);
|
|
4179
|
+
return numeric * 1e3;
|
|
4180
|
+
}
|
|
4181
|
+
if (typeof raw !== "string") return null;
|
|
4182
|
+
const at = Date.parse(raw.trim());
|
|
4183
|
+
return Number.isNaN(at) ? null : Math.max(0, at - now);
|
|
4184
|
+
}
|
|
4185
|
+
function getRetryAfterMs(error, now = Date.now()) {
|
|
4186
|
+
const err = asDict(error);
|
|
4187
|
+
if (!err) return null;
|
|
4188
|
+
const details = asDict(err.details);
|
|
4189
|
+
const response = asDict(err.response);
|
|
4190
|
+
const body = asDict(err.body) ?? asDict(response?.data);
|
|
4191
|
+
const bodyError = asDict(body?.error);
|
|
4192
|
+
const bodyDetails = asDict(bodyError?.details) ?? asDict(body?.details);
|
|
4193
|
+
for (const source of [err, details, bodyDetails]) {
|
|
4194
|
+
const ms = source?.retryAfterMs;
|
|
4195
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) return ms;
|
|
4196
|
+
}
|
|
4197
|
+
for (const source of [err, details, bodyDetails, bodyError]) {
|
|
4198
|
+
const parsed = parseRetryAfter(source?.retryAfter, now);
|
|
4199
|
+
if (parsed !== null) return parsed;
|
|
4200
|
+
}
|
|
4201
|
+
const headerBags = [err.headers, response?.headers, details?.headers, body?.headers];
|
|
4202
|
+
for (const bag of headerBags) {
|
|
4203
|
+
const parsed = parseRetryAfter(readHeader(bag, "retry-after"), now);
|
|
4204
|
+
if (parsed !== null) return parsed;
|
|
4205
|
+
}
|
|
4206
|
+
for (const source of [details, bodyDetails, err]) {
|
|
4207
|
+
for (const key of ["resetAt", "reset", "rateLimitReset"]) {
|
|
4208
|
+
const parsed = parseResetAt(source?.[key], now);
|
|
4209
|
+
if (parsed !== null) return parsed;
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
for (const bag of headerBags) {
|
|
4213
|
+
for (const header of ["x-ratelimit-reset", "ratelimit-reset"]) {
|
|
4214
|
+
const parsed = parseResetAt(readHeader(bag, header), now);
|
|
4215
|
+
if (parsed !== null) return parsed;
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
return null;
|
|
4219
|
+
}
|
|
4220
|
+
function getHttpStatus(error) {
|
|
4221
|
+
const err = asDict(error);
|
|
4222
|
+
if (!err) return null;
|
|
4223
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
4224
|
+
const value = err[key];
|
|
4225
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4226
|
+
if (typeof value === "string" && /^\d{3}$/.test(value)) return Number(value);
|
|
4227
|
+
}
|
|
4228
|
+
const response = asDict(err.response);
|
|
4229
|
+
for (const key of ["status", "statusCode"]) {
|
|
4230
|
+
const value = response?.[key];
|
|
4231
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4232
|
+
}
|
|
4233
|
+
return null;
|
|
4234
|
+
}
|
|
4235
|
+
function isTerminalClientError(error) {
|
|
4236
|
+
const status = getHttpStatus(error);
|
|
4237
|
+
if (status === null) return false;
|
|
4238
|
+
return status >= 400 && status < 500 && status !== 429 && status !== 408;
|
|
4239
|
+
}
|
|
4240
|
+
var FAILURE_GROWTH_FACTOR = 2;
|
|
4241
|
+
var IDLE_GROWTH_FACTOR = 1.5;
|
|
4242
|
+
function nextFailureDelayMs(currentMs, maxMs) {
|
|
4243
|
+
return Math.min(Math.max(currentMs, 1) * FAILURE_GROWTH_FACTOR, maxMs);
|
|
4244
|
+
}
|
|
4245
|
+
function nextIdleDelayMs(currentMs, maxMs) {
|
|
4246
|
+
return Math.min(Math.ceil(Math.max(currentMs, 1) * IDLE_GROWTH_FACTOR), maxMs);
|
|
4247
|
+
}
|
|
4248
|
+
function applyJitter(delayMs, ratio, random = Math.random) {
|
|
4249
|
+
if (ratio <= 0) return delayMs;
|
|
4250
|
+
return Math.round(delayMs * (1 + random() * ratio));
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4354
4253
|
// src/streaming/stream-controller.ts
|
|
4355
4254
|
var DEFAULT_STREAMING_CONFIG = {
|
|
4356
4255
|
maxReconnectAttempts: 5,
|
|
4357
4256
|
reconnectBaseDelayMs: 1e3,
|
|
4358
4257
|
reconnectMaxDelayMs: 1e4,
|
|
4258
|
+
// NOTE (unchanged, deliberately): this watchdog force-disconnects a HEALTHY
|
|
4259
|
+
// SSE connection after 30s of silence and routes it through the same
|
|
4260
|
+
// handleConnectionError path as a real transport failure. Ordinary LLM
|
|
4261
|
+
// latency exceeds 30s routinely, so it fires against working streams — that
|
|
4262
|
+
// is what drives sessions into the polling fallback in the first place. The
|
|
4263
|
+
// fix is a server-side SSE heartbeat, not a bigger number here; raising this
|
|
4264
|
+
// is a guess against a moving target. Everything below only bounds what the
|
|
4265
|
+
// fallback costs once it has been entered.
|
|
4359
4266
|
activityTimeoutMs: 3e4,
|
|
4360
|
-
pollingIntervalMs:
|
|
4267
|
+
pollingIntervalMs: 5e3,
|
|
4268
|
+
pollingMaxIntervalMs: 3e4,
|
|
4269
|
+
pollingMaxConsecutiveFailures: 10,
|
|
4270
|
+
pollingMaxConsecutiveRateLimits: 3,
|
|
4271
|
+
pollingMaxConsecutiveClientErrors: 3,
|
|
4272
|
+
pollingMaxDurationMs: 18e5,
|
|
4273
|
+
pollingMaxRetryAfterMs: 3e5,
|
|
4274
|
+
pollingJitterRatio: 0.1,
|
|
4275
|
+
sseRetryIntervalMs: 6e4
|
|
4361
4276
|
};
|
|
4362
4277
|
var DEFAULT_POLLING_ENABLED = true;
|
|
4363
4278
|
var ExecutionStreamControllerImpl = class {
|
|
@@ -4370,9 +4285,24 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4370
4285
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
4371
4286
|
_lastEventId = null;
|
|
4372
4287
|
activityTimeoutId = null;
|
|
4373
|
-
pollingIntervalId = null;
|
|
4374
4288
|
finalExecution = null;
|
|
4375
4289
|
finalError = null;
|
|
4290
|
+
// --- Fallback polling state ---
|
|
4291
|
+
// A self-rescheduling timeout, not setInterval: the delay has to change
|
|
4292
|
+
// between ticks (that is what backoff IS), and a timeout chain also cannot
|
|
4293
|
+
// stack overlapping requests the way a fixed interval does when the API is
|
|
4294
|
+
// slow to answer.
|
|
4295
|
+
pollTimeoutId = null;
|
|
4296
|
+
pollDelayMs = 0;
|
|
4297
|
+
pollConsecutiveFailures = 0;
|
|
4298
|
+
pollConsecutiveRateLimits = 0;
|
|
4299
|
+
pollConsecutiveClientErrors = 0;
|
|
4300
|
+
/** When the loop last observed anything new — the clock the give-up bound runs on. */
|
|
4301
|
+
pollLastProgressAt = 0;
|
|
4302
|
+
pollInFlight = false;
|
|
4303
|
+
lastPolledStatus = null;
|
|
4304
|
+
sseRetryTimeoutId = null;
|
|
4305
|
+
sseRetryInFlight = false;
|
|
4376
4306
|
// Event callbacks
|
|
4377
4307
|
listeners = {
|
|
4378
4308
|
log: [],
|
|
@@ -4401,6 +4331,39 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4401
4331
|
this.config = { ...DEFAULT_STREAMING_CONFIG, ...options.config };
|
|
4402
4332
|
this.pollingEnabled = options.pollingEnabled ?? DEFAULT_POLLING_ENABLED;
|
|
4403
4333
|
}
|
|
4334
|
+
// Resolved fallback-poll settings. Read through `??` rather than off the
|
|
4335
|
+
// spread config so an explicit `undefined` in a caller's partial config still
|
|
4336
|
+
// lands on the default instead of NaN-ing the arithmetic.
|
|
4337
|
+
get pollBaseIntervalMs() {
|
|
4338
|
+
return this.config.pollingIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingIntervalMs;
|
|
4339
|
+
}
|
|
4340
|
+
get pollMaxIntervalMs() {
|
|
4341
|
+
return Math.max(
|
|
4342
|
+
this.config.pollingMaxIntervalMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxIntervalMs,
|
|
4343
|
+
this.pollBaseIntervalMs
|
|
4344
|
+
);
|
|
4345
|
+
}
|
|
4346
|
+
get pollMaxConsecutiveFailures() {
|
|
4347
|
+
return this.config.pollingMaxConsecutiveFailures ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveFailures;
|
|
4348
|
+
}
|
|
4349
|
+
get pollMaxConsecutiveRateLimits() {
|
|
4350
|
+
return this.config.pollingMaxConsecutiveRateLimits ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveRateLimits;
|
|
4351
|
+
}
|
|
4352
|
+
get pollMaxConsecutiveClientErrors() {
|
|
4353
|
+
return this.config.pollingMaxConsecutiveClientErrors ?? DEFAULT_STREAMING_CONFIG.pollingMaxConsecutiveClientErrors;
|
|
4354
|
+
}
|
|
4355
|
+
get pollMaxDurationMs() {
|
|
4356
|
+
return this.config.pollingMaxDurationMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxDurationMs;
|
|
4357
|
+
}
|
|
4358
|
+
get pollMaxRetryAfterMs() {
|
|
4359
|
+
return this.config.pollingMaxRetryAfterMs ?? DEFAULT_STREAMING_CONFIG.pollingMaxRetryAfterMs;
|
|
4360
|
+
}
|
|
4361
|
+
get pollJitterRatio() {
|
|
4362
|
+
return this.config.pollingJitterRatio ?? DEFAULT_STREAMING_CONFIG.pollingJitterRatio;
|
|
4363
|
+
}
|
|
4364
|
+
get sseRetryIntervalMs() {
|
|
4365
|
+
return this.config.sseRetryIntervalMs ?? DEFAULT_STREAMING_CONFIG.sseRetryIntervalMs;
|
|
4366
|
+
}
|
|
4404
4367
|
/**
|
|
4405
4368
|
* Get the last event ID (for resume support)
|
|
4406
4369
|
*/
|
|
@@ -4491,28 +4454,45 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4491
4454
|
console.log("[SDK:StreamController] executionId:", this.options.executionId);
|
|
4492
4455
|
console.log("[SDK:StreamController] orgId:", this.options.orgId);
|
|
4493
4456
|
console.log("[SDK:StreamController] apiKey:", authToken ? `${authToken.substring(0, 20)}...` : "MISSING");
|
|
4494
|
-
this.sseClient
|
|
4457
|
+
if (this.sseClient) {
|
|
4458
|
+
this.sseClient.disconnect();
|
|
4459
|
+
this.sseClient = null;
|
|
4460
|
+
}
|
|
4461
|
+
const client = new SSEClient({
|
|
4495
4462
|
url,
|
|
4496
4463
|
authHeader: `Bearer ${authToken}`,
|
|
4497
4464
|
orgId: this.options.orgId,
|
|
4498
4465
|
fetch: this.options.fetch,
|
|
4499
|
-
|
|
4466
|
+
// Resume from where this session left off. On a first connect this is
|
|
4467
|
+
// null and the header is omitted.
|
|
4468
|
+
lastEventId: this._lastEventId,
|
|
4469
|
+
onEvent: (event) => {
|
|
4470
|
+
if (this.sseClient !== client) return;
|
|
4471
|
+
this.handleEvent(event);
|
|
4472
|
+
},
|
|
4500
4473
|
onError: (error) => {
|
|
4474
|
+
if (this.sseClient !== client) return;
|
|
4501
4475
|
console.error("[SDK:StreamController] SSE error:", error);
|
|
4502
4476
|
this.handleConnectionError(error);
|
|
4503
4477
|
},
|
|
4504
4478
|
onConnectionChange: (connected) => {
|
|
4479
|
+
if (this.sseClient !== client) return;
|
|
4505
4480
|
console.log("[SDK:StreamController] SSE connection change:", connected);
|
|
4506
4481
|
if (connected) {
|
|
4507
4482
|
this.status = "connected";
|
|
4508
4483
|
this.reconnectAttempts = 0;
|
|
4484
|
+
this.stopPolling();
|
|
4509
4485
|
this.resetActivityTimeout();
|
|
4510
4486
|
}
|
|
4511
|
-
this.emit("connectionChange", {
|
|
4487
|
+
this.emit("connectionChange", {
|
|
4488
|
+
connected,
|
|
4489
|
+
usingFallback: this.status === "fallback_polling"
|
|
4490
|
+
});
|
|
4512
4491
|
}
|
|
4513
4492
|
});
|
|
4493
|
+
this.sseClient = client;
|
|
4514
4494
|
try {
|
|
4515
|
-
await
|
|
4495
|
+
await client.connect();
|
|
4516
4496
|
console.log("[SDK:StreamController] SSE connect() completed");
|
|
4517
4497
|
} catch (error) {
|
|
4518
4498
|
console.error("[SDK:StreamController] SSE connect() threw:", error);
|
|
@@ -4751,6 +4731,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4751
4731
|
handleComplete(data) {
|
|
4752
4732
|
this.status = "closed";
|
|
4753
4733
|
this.stopActivityTimeout();
|
|
4734
|
+
this.stopPolling();
|
|
4754
4735
|
const sessionId = data.sessionId ?? this.options.sessionId;
|
|
4755
4736
|
const execution = {
|
|
4756
4737
|
uuid: this.options.executionId,
|
|
@@ -4777,6 +4758,7 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4777
4758
|
handleError(error) {
|
|
4778
4759
|
this.status = "closed";
|
|
4779
4760
|
this.stopActivityTimeout();
|
|
4761
|
+
this.stopPolling();
|
|
4780
4762
|
this.finalError = error;
|
|
4781
4763
|
this.emit("error", error);
|
|
4782
4764
|
if (this.waitReject) {
|
|
@@ -4803,6 +4785,10 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4803
4785
|
this.handleError({ error: msg, errorCode: "stream_unrecoverable" });
|
|
4804
4786
|
return;
|
|
4805
4787
|
}
|
|
4788
|
+
if (this.status === "fallback_polling") {
|
|
4789
|
+
this.startPolling();
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4806
4792
|
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
4807
4793
|
this.status = "reconnecting";
|
|
4808
4794
|
this.reconnectAttempts++;
|
|
@@ -4872,42 +4858,216 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4872
4858
|
}
|
|
4873
4859
|
}
|
|
4874
4860
|
/**
|
|
4875
|
-
* Start polling fallback
|
|
4861
|
+
* Start polling fallback.
|
|
4862
|
+
*
|
|
4863
|
+
* Idempotent — calling it while a loop is already running is a no-op, so no
|
|
4864
|
+
* path can end up with two loops racing the same execution.
|
|
4876
4865
|
*/
|
|
4877
4866
|
startPolling() {
|
|
4878
|
-
this.
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4867
|
+
if (this.pollTimeoutId !== null || this.pollInFlight) {
|
|
4868
|
+
return;
|
|
4869
|
+
}
|
|
4870
|
+
this.pollLastProgressAt = Date.now();
|
|
4871
|
+
this.pollDelayMs = this.pollBaseIntervalMs;
|
|
4872
|
+
this.pollConsecutiveFailures = 0;
|
|
4873
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4874
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4875
|
+
this.lastPolledStatus = null;
|
|
4876
|
+
this.stopActivityTimeout();
|
|
4877
|
+
this.scheduleSSERetry();
|
|
4878
|
+
this.schedulePoll(this.pollDelayMs);
|
|
4879
|
+
}
|
|
4880
|
+
/**
|
|
4881
|
+
* Queue the next poll tick.
|
|
4882
|
+
*/
|
|
4883
|
+
schedulePoll(delayMs) {
|
|
4884
|
+
if (this.status !== "fallback_polling") {
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4887
|
+
this.pollTimeoutId = setTimeout(() => {
|
|
4888
|
+
this.pollTimeoutId = null;
|
|
4889
|
+
void this.runPoll();
|
|
4890
|
+
}, Math.max(0, Math.round(delayMs)));
|
|
4891
|
+
}
|
|
4892
|
+
/**
|
|
4893
|
+
* One poll tick: fetch state, emit what is new, then decide how long to wait
|
|
4894
|
+
* before the next one — or whether there should be a next one at all.
|
|
4895
|
+
*/
|
|
4896
|
+
async runPoll() {
|
|
4897
|
+
if (this.status !== "fallback_polling") {
|
|
4898
|
+
return;
|
|
4899
|
+
}
|
|
4900
|
+
if (Date.now() - this.pollLastProgressAt >= this.pollMaxDurationMs) {
|
|
4901
|
+
this.failPolling(
|
|
4902
|
+
`Streaming fallback gave up: no execution progress for ${Math.round(this.pollMaxDurationMs / 1e3)}s`,
|
|
4903
|
+
"polling_timeout"
|
|
4904
|
+
);
|
|
4905
|
+
return;
|
|
4906
|
+
}
|
|
4907
|
+
this.pollInFlight = true;
|
|
4908
|
+
try {
|
|
4909
|
+
const execution = await this.options.getExecution(this.options.executionId);
|
|
4910
|
+
this.pollConsecutiveFailures = 0;
|
|
4911
|
+
this.pollConsecutiveRateLimits = 0;
|
|
4912
|
+
this.pollConsecutiveClientErrors = 0;
|
|
4913
|
+
let sawNewInfo = false;
|
|
4914
|
+
for (const log of execution.logs ?? []) {
|
|
4915
|
+
if (!this.seenEventIds.has(log.uuid)) {
|
|
4916
|
+
this.seenEventIds.add(log.uuid);
|
|
4917
|
+
this.emit("log", log);
|
|
4918
|
+
sawNewInfo = true;
|
|
4896
4919
|
}
|
|
4897
|
-
} catch (error) {
|
|
4898
|
-
console.error("Polling error:", error);
|
|
4899
4920
|
}
|
|
4900
|
-
|
|
4921
|
+
if (execution.status !== this.lastPolledStatus) {
|
|
4922
|
+
this.lastPolledStatus = execution.status;
|
|
4923
|
+
sawNewInfo = true;
|
|
4924
|
+
}
|
|
4925
|
+
if (sawNewInfo) {
|
|
4926
|
+
this.pollLastProgressAt = Date.now();
|
|
4927
|
+
}
|
|
4928
|
+
if (execution.status === "completed") {
|
|
4929
|
+
this.stopPolling();
|
|
4930
|
+
this.handleComplete(execution);
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (execution.status === "failed" || execution.status === "cancelled") {
|
|
4934
|
+
this.stopPolling();
|
|
4935
|
+
this.handleError({
|
|
4936
|
+
error: execution.error ?? `Execution ${execution.status}`,
|
|
4937
|
+
errorCode: execution.status
|
|
4938
|
+
});
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4941
|
+
this.pollDelayMs = sawNewInfo ? this.pollBaseIntervalMs : nextIdleDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4942
|
+
} catch (error) {
|
|
4943
|
+
if (!this.handlePollError(error)) {
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4946
|
+
} finally {
|
|
4947
|
+
this.pollInFlight = false;
|
|
4948
|
+
}
|
|
4949
|
+
this.schedulePoll(applyJitter(this.pollDelayMs, this.pollJitterRatio));
|
|
4950
|
+
}
|
|
4951
|
+
/**
|
|
4952
|
+
* Apply backpressure for a failed poll.
|
|
4953
|
+
*
|
|
4954
|
+
* @returns `true` to keep polling (with `pollDelayMs` updated), `false` when
|
|
4955
|
+
* the loop has been terminated.
|
|
4956
|
+
*/
|
|
4957
|
+
handlePollError(error) {
|
|
4958
|
+
const retryAfterMs = getRetryAfterMs(error);
|
|
4959
|
+
const rateLimited = isRateLimited(error) || retryAfterMs !== null;
|
|
4960
|
+
if (rateLimited) {
|
|
4961
|
+
this.pollConsecutiveRateLimits++;
|
|
4962
|
+
console.warn(
|
|
4963
|
+
"[SDK:StreamController] Poll rate-limited",
|
|
4964
|
+
retryAfterMs !== null ? `(server asked for ${Math.round(retryAfterMs / 1e3)}s)` : "(no reset named)"
|
|
4965
|
+
);
|
|
4966
|
+
if (retryAfterMs !== null && retryAfterMs > this.pollMaxRetryAfterMs) {
|
|
4967
|
+
this.failPolling(
|
|
4968
|
+
`Rate limited: the API asked for ${Math.round(retryAfterMs / 1e3)}s, longer than this stream will wait`,
|
|
4969
|
+
"rate_limited"
|
|
4970
|
+
);
|
|
4971
|
+
return false;
|
|
4972
|
+
}
|
|
4973
|
+
if (this.pollConsecutiveRateLimits >= this.pollMaxConsecutiveRateLimits) {
|
|
4974
|
+
this.failPolling(
|
|
4975
|
+
"Rate limited by the API; stopped the streaming fallback rather than keep spending the key quota",
|
|
4976
|
+
"rate_limited"
|
|
4977
|
+
);
|
|
4978
|
+
return false;
|
|
4979
|
+
}
|
|
4980
|
+
this.pollDelayMs = Math.max(retryAfterMs ?? UNNAMED_RATE_LIMIT_WAIT_MS, this.pollDelayMs);
|
|
4981
|
+
return true;
|
|
4982
|
+
}
|
|
4983
|
+
if (isTerminalClientError(error)) {
|
|
4984
|
+
this.pollConsecutiveClientErrors++;
|
|
4985
|
+
const status = getHttpStatus(error);
|
|
4986
|
+
console.warn(
|
|
4987
|
+
`[SDK:StreamController] Poll got HTTP ${status} (${this.pollConsecutiveClientErrors}/${this.pollMaxConsecutiveClientErrors})`
|
|
4988
|
+
);
|
|
4989
|
+
if (this.pollConsecutiveClientErrors >= this.pollMaxConsecutiveClientErrors) {
|
|
4990
|
+
this.failPolling(
|
|
4991
|
+
`Execution status endpoint returned HTTP ${status} ${this.pollConsecutiveClientErrors} times \u2014 cannot track this run`,
|
|
4992
|
+
"polling_unrecoverable"
|
|
4993
|
+
);
|
|
4994
|
+
return false;
|
|
4995
|
+
}
|
|
4996
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
4997
|
+
return true;
|
|
4998
|
+
}
|
|
4999
|
+
this.pollConsecutiveFailures++;
|
|
5000
|
+
console.warn(
|
|
5001
|
+
`[SDK:StreamController] Poll failed (${this.pollConsecutiveFailures}/${this.pollMaxConsecutiveFailures}):`,
|
|
5002
|
+
error
|
|
5003
|
+
);
|
|
5004
|
+
if (this.pollConsecutiveFailures >= this.pollMaxConsecutiveFailures) {
|
|
5005
|
+
this.failPolling(
|
|
5006
|
+
`Streaming fallback failed ${this.pollConsecutiveFailures} times in a row`,
|
|
5007
|
+
"polling_failed"
|
|
5008
|
+
);
|
|
5009
|
+
return false;
|
|
5010
|
+
}
|
|
5011
|
+
this.pollDelayMs = nextFailureDelayMs(this.pollDelayMs, this.pollMaxIntervalMs);
|
|
5012
|
+
return true;
|
|
4901
5013
|
}
|
|
4902
5014
|
/**
|
|
4903
|
-
*
|
|
5015
|
+
* Terminate the fallback loop and surface the reason.
|
|
5016
|
+
*/
|
|
5017
|
+
failPolling(error, errorCode) {
|
|
5018
|
+
this.stopPolling();
|
|
5019
|
+
this.handleError({ error, errorCode });
|
|
5020
|
+
}
|
|
5021
|
+
/**
|
|
5022
|
+
* Stop polling, including the periodic SSE re-attempt that belongs to it.
|
|
4904
5023
|
*/
|
|
4905
5024
|
stopPolling() {
|
|
4906
|
-
if (this.
|
|
4907
|
-
|
|
4908
|
-
this.
|
|
5025
|
+
if (this.pollTimeoutId !== null) {
|
|
5026
|
+
clearTimeout(this.pollTimeoutId);
|
|
5027
|
+
this.pollTimeoutId = null;
|
|
5028
|
+
}
|
|
5029
|
+
if (this.sseRetryTimeoutId !== null) {
|
|
5030
|
+
clearTimeout(this.sseRetryTimeoutId);
|
|
5031
|
+
this.sseRetryTimeoutId = null;
|
|
4909
5032
|
}
|
|
4910
5033
|
}
|
|
5034
|
+
/**
|
|
5035
|
+
* Queue the next attempt to get the real stream back.
|
|
5036
|
+
*/
|
|
5037
|
+
scheduleSSERetry() {
|
|
5038
|
+
if (this.sseRetryTimeoutId !== null || this.status !== "fallback_polling") {
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
5041
|
+
this.sseRetryTimeoutId = setTimeout(() => {
|
|
5042
|
+
this.sseRetryTimeoutId = null;
|
|
5043
|
+
this.attemptSSERecovery();
|
|
5044
|
+
}, this.sseRetryIntervalMs);
|
|
5045
|
+
}
|
|
5046
|
+
/**
|
|
5047
|
+
* Re-attempt SSE from inside the fallback, so a transient outage costs the
|
|
5048
|
+
* session a few polls rather than its stream for the lifetime of the tab.
|
|
5049
|
+
*
|
|
5050
|
+
* Success is NOT observed here: `SSEClient.connect()` resolves when the
|
|
5051
|
+
* stream ENDS, not when it opens. The open is observed by
|
|
5052
|
+
* `onConnectionChange(true)`, which flips status and stops this loop. The
|
|
5053
|
+
* promise below only tells us the attempt is over so the next one can be
|
|
5054
|
+
* queued.
|
|
5055
|
+
*/
|
|
5056
|
+
attemptSSERecovery() {
|
|
5057
|
+
if (this.status !== "fallback_polling") {
|
|
5058
|
+
return;
|
|
5059
|
+
}
|
|
5060
|
+
if (this.sseRetryInFlight) {
|
|
5061
|
+
this.scheduleSSERetry();
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
this.sseRetryInFlight = true;
|
|
5065
|
+
void this.connectSSE().catch(() => {
|
|
5066
|
+
}).finally(() => {
|
|
5067
|
+
this.sseRetryInFlight = false;
|
|
5068
|
+
this.scheduleSSERetry();
|
|
5069
|
+
});
|
|
5070
|
+
}
|
|
4911
5071
|
/**
|
|
4912
5072
|
* Reset activity timeout
|
|
4913
5073
|
*/
|
|
@@ -5038,12 +5198,23 @@ async function collectImagesFromStream(stream, options = {}) {
|
|
|
5038
5198
|
});
|
|
5039
5199
|
}
|
|
5040
5200
|
|
|
5201
|
+
// src/utils/api-paths.ts
|
|
5202
|
+
var API_V1 = "/api/v1";
|
|
5203
|
+
var EXECUTION_PATH = `${API_V1}/executions`;
|
|
5204
|
+
function executionPath(executionId) {
|
|
5205
|
+
return `${EXECUTION_PATH}/${executionId}`;
|
|
5206
|
+
}
|
|
5207
|
+
function executionCancelPath(executionId) {
|
|
5208
|
+
return `${EXECUTION_PATH}/${executionId}/cancel`;
|
|
5209
|
+
}
|
|
5210
|
+
|
|
5041
5211
|
// src/resources/agents.ts
|
|
5042
5212
|
var AgentsAPI = class extends BaseResourceAPI {
|
|
5043
5213
|
constructor(http, getStreamConfig) {
|
|
5044
5214
|
super(http, "/api/v1/agents");
|
|
5045
5215
|
this.getStreamConfig = getStreamConfig;
|
|
5046
5216
|
}
|
|
5217
|
+
getStreamConfig;
|
|
5047
5218
|
// ===== LIST Operations =====
|
|
5048
5219
|
/**
|
|
5049
5220
|
* List all agents in the organization
|
|
@@ -5300,7 +5471,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5300
5471
|
* ```
|
|
5301
5472
|
*/
|
|
5302
5473
|
async getExecution(executionId) {
|
|
5303
|
-
return this.http.get(
|
|
5474
|
+
return this.http.get(executionPath(executionId));
|
|
5304
5475
|
}
|
|
5305
5476
|
// ===== EDIT Operations =====
|
|
5306
5477
|
/**
|
|
@@ -5355,7 +5526,7 @@ var AgentsAPI = class extends BaseResourceAPI {
|
|
|
5355
5526
|
*/
|
|
5356
5527
|
async cancelExecution(executionId) {
|
|
5357
5528
|
return this.http.post(
|
|
5358
|
-
|
|
5529
|
+
executionCancelPath(executionId),
|
|
5359
5530
|
void 0,
|
|
5360
5531
|
{ skipRetry: true }
|
|
5361
5532
|
);
|
|
@@ -5856,6 +6027,7 @@ var ArtifactsAPI = class extends BaseResourceAPI {
|
|
|
5856
6027
|
}
|
|
5857
6028
|
return this.http.request({
|
|
5858
6029
|
method: "POST",
|
|
6030
|
+
// KNOWN BROKEN — same missing `/api/v1` prefix as listArtifacts above.
|
|
5859
6031
|
path: `/executions/${executionId}/artifacts`,
|
|
5860
6032
|
body: formData,
|
|
5861
6033
|
headers: {
|
|
@@ -5949,6 +6121,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
5949
6121
|
super(http, "/api/v1/sessions");
|
|
5950
6122
|
this.getStreamConfig = getStreamConfig;
|
|
5951
6123
|
}
|
|
6124
|
+
getStreamConfig;
|
|
5952
6125
|
// ===== LIST Operations =====
|
|
5953
6126
|
/**
|
|
5954
6127
|
* List all sessions in the organization
|
|
@@ -6128,7 +6301,7 @@ var SessionsAPI = class extends BaseResourceAPI {
|
|
|
6128
6301
|
* Get execution result
|
|
6129
6302
|
*/
|
|
6130
6303
|
async getExecution(executionId) {
|
|
6131
|
-
return this.http.get(
|
|
6304
|
+
return this.http.get(executionPath(executionId));
|
|
6132
6305
|
}
|
|
6133
6306
|
// ===== EDIT Operations =====
|
|
6134
6307
|
/**
|
|
@@ -6268,6 +6441,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6268
6441
|
super(http, "/api/v1/kanban");
|
|
6269
6442
|
this.getStreamConfig = getStreamConfig;
|
|
6270
6443
|
}
|
|
6444
|
+
getStreamConfig;
|
|
6271
6445
|
// ===== BOARD =====
|
|
6272
6446
|
/**
|
|
6273
6447
|
* Get the current org's board (lanes in declared order). Auto-provisions
|
|
@@ -6402,7 +6576,7 @@ var KanbanAPI = class extends BaseResourceAPI {
|
|
|
6402
6576
|
* @returns Execution details.
|
|
6403
6577
|
*/
|
|
6404
6578
|
async getCardRun(executionId) {
|
|
6405
|
-
return this.http.get(
|
|
6579
|
+
return this.http.get(executionPath(executionId));
|
|
6406
6580
|
}
|
|
6407
6581
|
/**
|
|
6408
6582
|
* Create a stream controller for a card's run, keyed on the card's
|
|
@@ -6469,6 +6643,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6469
6643
|
super(http, "/api/v1/remote-agents");
|
|
6470
6644
|
this.getStreamConfig = getStreamConfig;
|
|
6471
6645
|
}
|
|
6646
|
+
getStreamConfig;
|
|
6472
6647
|
/**
|
|
6473
6648
|
* List all remote agents in the organization
|
|
6474
6649
|
*
|
|
@@ -6602,7 +6777,7 @@ var RemoteAgentsAPI = class extends BaseResourceAPI {
|
|
|
6602
6777
|
apiKey: config.apiKey,
|
|
6603
6778
|
tokenProvider: config.tokenProvider,
|
|
6604
6779
|
orgId: config.orgId,
|
|
6605
|
-
getExecution: (id) => this.http.get(
|
|
6780
|
+
getExecution: (id) => this.http.get(executionPath(id))
|
|
6606
6781
|
});
|
|
6607
6782
|
return controller;
|
|
6608
6783
|
}
|