@cadraos/sdk 0.3.8 → 0.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board-host/index.d.cts +74 -6
- package/dist/board-host/index.d.ts +74 -6
- package/dist/board-host/server/index.d.cts +81 -6
- package/dist/board-host/server/index.d.ts +81 -6
- package/dist/chat/index.cjs +2 -47
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +8 -9
- package/dist/chat/index.d.ts +8 -9
- package/dist/chat/index.js +2 -47
- package/dist/chat/index.js.map +1 -1
- package/dist/index.cjs +0 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +0 -43
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +113 -31
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.d.cts +112 -6
- package/dist/kanban/engine/index.d.ts +112 -6
- package/dist/kanban/engine/index.js +113 -31
- package/dist/kanban/engine/index.js.map +1 -1
- package/dist/kanban/index.cjs +807 -158
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.d.cts +382 -11
- package/dist/kanban/index.d.ts +382 -11
- package/dist/kanban/index.js +802 -159
- package/dist/kanban/index.js.map +1 -1
- package/dist/teams/index.cjs +0 -43
- package/dist/teams/index.cjs.map +1 -1
- package/dist/teams/index.d.cts +7 -0
- package/dist/teams/index.d.ts +7 -0
- package/dist/teams/index.js +0 -43
- package/dist/teams/index.js.map +1 -1
- package/package.json +1 -1
package/dist/kanban/index.cjs
CHANGED
|
@@ -23,6 +23,10 @@ function mapHandler(h) {
|
|
|
23
23
|
type: h.type,
|
|
24
24
|
agentUuid: h.agent_uuid,
|
|
25
25
|
teamUuid: h.team_uuid,
|
|
26
|
+
// FINDING-6: carry the remote_agent binding through the wire parse. The
|
|
27
|
+
// engine's remote_agent dispatch reads `remoteAgentUuid`; dropping it here
|
|
28
|
+
// made every published remote_agent lane dispatch-dead.
|
|
29
|
+
remoteAgentUuid: h.remote_agent_uuid,
|
|
26
30
|
agentRef: h.agent_ref,
|
|
27
31
|
webhook: h.webhook ? {
|
|
28
32
|
url: h.webhook.url,
|
|
@@ -301,7 +305,7 @@ function useKanbanBoard(options) {
|
|
|
301
305
|
try {
|
|
302
306
|
const full = await adapter.getCard(cardId);
|
|
303
307
|
setSelectedCard(
|
|
304
|
-
(prev) => prev
|
|
308
|
+
(prev) => prev == null || prev.id === cardId ? full : prev
|
|
305
309
|
);
|
|
306
310
|
} catch (err) {
|
|
307
311
|
if (!known) fail(err, "Failed to open card");
|
|
@@ -410,6 +414,27 @@ function useKanbanBoard(options) {
|
|
|
410
414
|
},
|
|
411
415
|
[refresh]
|
|
412
416
|
);
|
|
417
|
+
const deleteCard = react.useCallback(
|
|
418
|
+
async (cardId) => {
|
|
419
|
+
if (!adapter.deleteCard) return false;
|
|
420
|
+
setError(null);
|
|
421
|
+
const prevCards = cards;
|
|
422
|
+
const prevSelected = selectedCard;
|
|
423
|
+
setCards((prev) => prev.filter((c) => c.id !== cardId));
|
|
424
|
+
setSelectedCard((prev) => prev && prev.id === cardId ? null : prev);
|
|
425
|
+
try {
|
|
426
|
+
await adapter.deleteCard(cardId);
|
|
427
|
+
void silentRefresh();
|
|
428
|
+
return true;
|
|
429
|
+
} catch (err) {
|
|
430
|
+
setCards(prevCards);
|
|
431
|
+
setSelectedCard(prevSelected);
|
|
432
|
+
fail(err, "Failed to delete card");
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
[adapter, cards, selectedCard, silentRefresh, fail]
|
|
437
|
+
);
|
|
413
438
|
return {
|
|
414
439
|
board,
|
|
415
440
|
lanes: board?.lanes ?? [],
|
|
@@ -425,6 +450,7 @@ function useKanbanBoard(options) {
|
|
|
425
450
|
createCard,
|
|
426
451
|
updateCard,
|
|
427
452
|
transitionCard,
|
|
453
|
+
deleteCard,
|
|
428
454
|
handleRunTerminal
|
|
429
455
|
};
|
|
430
456
|
}
|
|
@@ -2300,35 +2326,40 @@ function useCardRun(options) {
|
|
|
2300
2326
|
function cn9(...classes) {
|
|
2301
2327
|
return classes.filter(Boolean).join(" ");
|
|
2302
2328
|
}
|
|
2303
|
-
function
|
|
2304
|
-
switch (
|
|
2305
|
-
case "
|
|
2306
|
-
return
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
case "
|
|
2316
|
-
return
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
case "cancelled":
|
|
2321
|
-
return {
|
|
2322
|
-
label: "Cancelled",
|
|
2323
|
-
className: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
|
|
2324
|
-
};
|
|
2329
|
+
function badgeVariantClass(variant) {
|
|
2330
|
+
switch (variant) {
|
|
2331
|
+
case "default":
|
|
2332
|
+
return "bg-primary text-primary-foreground";
|
|
2333
|
+
case "secondary":
|
|
2334
|
+
return "border border-border bg-secondary text-secondary-foreground";
|
|
2335
|
+
case "outline":
|
|
2336
|
+
return "border border-border text-foreground";
|
|
2337
|
+
case "info":
|
|
2338
|
+
return "border border-info/30 bg-info/15 text-info";
|
|
2339
|
+
case "success":
|
|
2340
|
+
return "border border-success/30 bg-success/15 text-success";
|
|
2341
|
+
case "warning":
|
|
2342
|
+
return "border border-warning/40 bg-warning/15 text-warning";
|
|
2343
|
+
case "destructive":
|
|
2344
|
+
return "border border-destructive/40 bg-destructive/15 text-destructive";
|
|
2345
|
+
case "neutral":
|
|
2325
2346
|
default:
|
|
2326
|
-
return
|
|
2327
|
-
label: "Pending",
|
|
2328
|
-
className: "bg-amber-50 text-amber-700 dark:bg-amber-950/40 dark:text-amber-300"
|
|
2329
|
-
};
|
|
2347
|
+
return "border border-border bg-secondary text-muted-foreground";
|
|
2330
2348
|
}
|
|
2331
2349
|
}
|
|
2350
|
+
function runStatusTone(status) {
|
|
2351
|
+
const map = {
|
|
2352
|
+
running: { label: "Running", variant: "info" },
|
|
2353
|
+
completed: { label: "Completed", variant: "success" },
|
|
2354
|
+
failed: { label: "Failed", variant: "destructive" },
|
|
2355
|
+
cancelled: { label: "Cancelled", variant: "neutral" }
|
|
2356
|
+
};
|
|
2357
|
+
const { label, variant } = map[status ?? ""] ?? {
|
|
2358
|
+
label: "Pending",
|
|
2359
|
+
variant: "neutral"
|
|
2360
|
+
};
|
|
2361
|
+
return { label, variant, className: badgeVariantClass(variant) };
|
|
2362
|
+
}
|
|
2332
2363
|
function CardDrawer({
|
|
2333
2364
|
card,
|
|
2334
2365
|
adapter,
|
|
@@ -2447,11 +2478,11 @@ function CardDrawer({
|
|
|
2447
2478
|
role: "dialog",
|
|
2448
2479
|
"aria-label": `Card: ${card.title}`,
|
|
2449
2480
|
className: cn9(
|
|
2450
|
-
"flex h-full w-full sm:max-w-sm flex-col bg-
|
|
2481
|
+
"flex h-full w-full sm:max-w-sm flex-col bg-card border-l border-border shadow-xl",
|
|
2451
2482
|
className
|
|
2452
2483
|
),
|
|
2453
2484
|
children: [
|
|
2454
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-
|
|
2485
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3 border-b border-border px-4 py-3", children: [
|
|
2455
2486
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2456
2487
|
isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2457
2488
|
"input",
|
|
@@ -2461,14 +2492,14 @@ function CardDrawer({
|
|
|
2461
2492
|
onChange: (e) => setEditTitle(e.target.value),
|
|
2462
2493
|
"aria-label": "Card title",
|
|
2463
2494
|
placeholder: "Card title",
|
|
2464
|
-
className: "w-full rounded-md border border-
|
|
2495
|
+
className: "w-full rounded-md border border-input bg-background px-2 py-1 text-base font-semibold text-foreground focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
2465
2496
|
}
|
|
2466
2497
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2467
2498
|
"h2",
|
|
2468
2499
|
{
|
|
2469
2500
|
className: cn9(
|
|
2470
|
-
"text-base font-semibold text-
|
|
2471
|
-
onUpdate && "cursor-text rounded hover:bg-
|
|
2501
|
+
"text-base font-semibold text-foreground truncate",
|
|
2502
|
+
onUpdate && "cursor-text rounded hover:bg-muted/60 -mx-1 px-1"
|
|
2472
2503
|
),
|
|
2473
2504
|
onClick: onUpdate ? () => setIsEditing(true) : void 0,
|
|
2474
2505
|
title: onUpdate ? "Click to edit" : void 0,
|
|
@@ -2476,7 +2507,7 @@ function CardDrawer({
|
|
|
2476
2507
|
}
|
|
2477
2508
|
),
|
|
2478
2509
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
|
|
2479
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-
|
|
2510
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: card.lane }),
|
|
2480
2511
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2481
2512
|
"span",
|
|
2482
2513
|
{
|
|
@@ -2496,7 +2527,7 @@ function CardDrawer({
|
|
|
2496
2527
|
type: "button",
|
|
2497
2528
|
onClick: () => setIsEditing(true),
|
|
2498
2529
|
"aria-label": "Edit card",
|
|
2499
|
-
className: "rounded p-1 text-
|
|
2530
|
+
className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
2500
2531
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2501
2532
|
"svg",
|
|
2502
2533
|
{
|
|
@@ -2522,7 +2553,7 @@ function CardDrawer({
|
|
|
2522
2553
|
type: "button",
|
|
2523
2554
|
onClick: onClose,
|
|
2524
2555
|
"aria-label": "Close",
|
|
2525
|
-
className: "rounded p-1 text-
|
|
2556
|
+
className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
2526
2557
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2527
2558
|
"svg",
|
|
2528
2559
|
{
|
|
@@ -2544,8 +2575,8 @@ function CardDrawer({
|
|
|
2544
2575
|
)
|
|
2545
2576
|
] })
|
|
2546
2577
|
] }),
|
|
2547
|
-
isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-
|
|
2548
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-xs font-medium text-
|
|
2578
|
+
isEditing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border px-4 py-3", children: [
|
|
2579
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Description" }),
|
|
2549
2580
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2550
2581
|
"textarea",
|
|
2551
2582
|
{
|
|
@@ -2554,7 +2585,7 @@ function CardDrawer({
|
|
|
2554
2585
|
"aria-label": "Card description",
|
|
2555
2586
|
rows: 4,
|
|
2556
2587
|
placeholder: "Add a description\u2026",
|
|
2557
|
-
className: "w-full resize-y rounded-md border border-
|
|
2588
|
+
className: "w-full resize-y rounded-md border border-input bg-background px-2 py-1.5 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
2558
2589
|
}
|
|
2559
2590
|
),
|
|
2560
2591
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-end gap-2", children: [
|
|
@@ -2564,7 +2595,7 @@ function CardDrawer({
|
|
|
2564
2595
|
type: "button",
|
|
2565
2596
|
onClick: handleCancel,
|
|
2566
2597
|
disabled: isSaving,
|
|
2567
|
-
className: "rounded-md px-3 py-1.5 text-sm font-medium text-
|
|
2598
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground hover:bg-muted disabled:opacity-50",
|
|
2568
2599
|
children: "Cancel"
|
|
2569
2600
|
}
|
|
2570
2601
|
),
|
|
@@ -2574,26 +2605,27 @@ function CardDrawer({
|
|
|
2574
2605
|
type: "button",
|
|
2575
2606
|
onClick: () => void handleSave(),
|
|
2576
2607
|
disabled: isSaving,
|
|
2577
|
-
className: "rounded-md bg-
|
|
2608
|
+
className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
|
|
2578
2609
|
children: isSaving ? "Saving\u2026" : "Save"
|
|
2579
2610
|
}
|
|
2580
2611
|
)
|
|
2581
2612
|
] })
|
|
2582
|
-
] }) : (card.description || onUpdate) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-
|
|
2613
|
+
] }) : (card.description || onUpdate) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-border px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2583
2614
|
"p",
|
|
2584
2615
|
{
|
|
2585
2616
|
className: cn9(
|
|
2586
2617
|
"text-sm whitespace-pre-wrap",
|
|
2587
|
-
card.description ? "text-
|
|
2588
|
-
onUpdate && "cursor-text rounded hover:bg-
|
|
2618
|
+
card.description ? "text-muted-foreground" : "italic text-muted-foreground/70",
|
|
2619
|
+
onUpdate && "cursor-text rounded hover:bg-muted/60 -mx-1 px-1"
|
|
2589
2620
|
),
|
|
2590
2621
|
onClick: onUpdate ? () => setIsEditing(true) : void 0,
|
|
2591
2622
|
title: onUpdate ? "Click to edit" : void 0,
|
|
2592
2623
|
children: card.description || "Add a description\u2026"
|
|
2593
2624
|
}
|
|
2594
2625
|
) }),
|
|
2595
|
-
showTransitionButtons ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-b border-
|
|
2596
|
-
isHumanGate &&
|
|
2626
|
+
showTransitionButtons ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2 border-b border-border px-4 py-2", children: [
|
|
2627
|
+
isHumanGate && // Human gate = needs-you → warning (amber is reserved for this, F3).
|
|
2628
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-auto text-xs font-medium text-warning", children: "Awaiting your approval" }),
|
|
2597
2629
|
manualEdges.map((t, i) => {
|
|
2598
2630
|
const toLabel = laneByKey.get(t.toLane)?.label ?? t.toLane;
|
|
2599
2631
|
const isPrimary = isHumanGate ? i === 0 : false;
|
|
@@ -2606,26 +2638,27 @@ function CardDrawer({
|
|
|
2606
2638
|
onClick: () => void handleTransition(t.toLane),
|
|
2607
2639
|
disabled: pendingLane !== null,
|
|
2608
2640
|
className: cn9(
|
|
2609
|
-
|
|
2610
|
-
|
|
2641
|
+
// ≥44px touch (M12/F5) — density kept via padding, not height.
|
|
2642
|
+
"inline-flex min-h-11 items-center justify-center rounded-md px-3 py-1.5 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
|
|
2643
|
+
isPrimary ? "border border-border bg-secondary text-secondary-foreground hover:bg-muted" : "border border-border text-foreground hover:bg-muted"
|
|
2611
2644
|
),
|
|
2612
2645
|
children: busy ? "Moving\u2026" : label
|
|
2613
2646
|
},
|
|
2614
2647
|
t.id
|
|
2615
2648
|
);
|
|
2616
2649
|
})
|
|
2617
|
-
] }) : onAdvance && canAdvance && !isEditing && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end border-b border-
|
|
2650
|
+
] }) : onAdvance && canAdvance && !isEditing && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-end border-b border-border px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2618
2651
|
"button",
|
|
2619
2652
|
{
|
|
2620
2653
|
type: "button",
|
|
2621
2654
|
onClick: () => void handleAdvance(),
|
|
2622
2655
|
disabled: isAdvancing,
|
|
2623
|
-
className: "rounded-md border border-
|
|
2656
|
+
className: "inline-flex min-h-11 items-center justify-center rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50 disabled:opacity-50",
|
|
2624
2657
|
children: isAdvancing ? "Advancing\u2026" : "Advance \u2192"
|
|
2625
2658
|
}
|
|
2626
2659
|
) }),
|
|
2627
2660
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
|
|
2628
|
-
!hasExecution ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-
|
|
2661
|
+
!hasExecution ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "No run yet for this card." }) }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2629
2662
|
MessageList,
|
|
2630
2663
|
{
|
|
2631
2664
|
messages: run.messages,
|
|
@@ -2638,14 +2671,14 @@ function CardDrawer({
|
|
|
2638
2671
|
className: "flex-1"
|
|
2639
2672
|
}
|
|
2640
2673
|
),
|
|
2641
|
-
failed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-
|
|
2642
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-
|
|
2674
|
+
failed && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-destructive/40 bg-destructive/10 px-4 py-3", children: [
|
|
2675
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-destructive", children: run.error ?? "Run failed." }),
|
|
2643
2676
|
onRetry && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2644
2677
|
"button",
|
|
2645
2678
|
{
|
|
2646
2679
|
type: "button",
|
|
2647
2680
|
onClick: () => onRetry(card),
|
|
2648
|
-
className: "shrink-0 rounded-md bg-
|
|
2681
|
+
className: "inline-flex min-h-11 shrink-0 items-center justify-center rounded-md border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
2649
2682
|
children: "Retry"
|
|
2650
2683
|
}
|
|
2651
2684
|
)
|
|
@@ -2687,10 +2720,54 @@ function Spinner({ className }) {
|
|
|
2687
2720
|
}
|
|
2688
2721
|
);
|
|
2689
2722
|
}
|
|
2690
|
-
function
|
|
2723
|
+
function ClockIcon2() {
|
|
2724
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2725
|
+
"svg",
|
|
2726
|
+
{
|
|
2727
|
+
viewBox: "0 0 24 24",
|
|
2728
|
+
fill: "none",
|
|
2729
|
+
stroke: "currentColor",
|
|
2730
|
+
strokeWidth: "2",
|
|
2731
|
+
strokeLinecap: "round",
|
|
2732
|
+
strokeLinejoin: "round",
|
|
2733
|
+
"aria-hidden": true,
|
|
2734
|
+
className: "h-2.5 w-2.5",
|
|
2735
|
+
children: [
|
|
2736
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2737
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 7v5l3 2" })
|
|
2738
|
+
]
|
|
2739
|
+
}
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
function KanbanCard({
|
|
2743
|
+
card,
|
|
2744
|
+
onClick,
|
|
2745
|
+
selected,
|
|
2746
|
+
dueState,
|
|
2747
|
+
draggable,
|
|
2748
|
+
onDragStart,
|
|
2749
|
+
onDragEnd,
|
|
2750
|
+
className
|
|
2751
|
+
}) {
|
|
2691
2752
|
const runStatus = card.run?.status;
|
|
2692
|
-
const
|
|
2693
|
-
const
|
|
2753
|
+
const isWorking = runStatus === "running";
|
|
2754
|
+
const failed = runStatus === "failed";
|
|
2755
|
+
let chip = null;
|
|
2756
|
+
if (failed) {
|
|
2757
|
+
chip = { label: "Failed", variant: "destructive" };
|
|
2758
|
+
} else if (dueState === "ready") {
|
|
2759
|
+
chip = { label: "Ready", variant: "success" };
|
|
2760
|
+
} else if (runStatus === "completed") {
|
|
2761
|
+
const t = runStatusTone(runStatus);
|
|
2762
|
+
chip = { label: t.label, variant: t.variant };
|
|
2763
|
+
} else if (dueState === "due") {
|
|
2764
|
+
chip = { label: "Due", variant: "neutral", icon: "clock" };
|
|
2765
|
+
} else if (dueState === "waiting") {
|
|
2766
|
+
chip = { label: "Waiting", variant: "neutral" };
|
|
2767
|
+
} else if (runStatus) {
|
|
2768
|
+
const t = runStatusTone(runStatus);
|
|
2769
|
+
chip = { label: t.label, variant: t.variant };
|
|
2770
|
+
}
|
|
2694
2771
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2695
2772
|
"button",
|
|
2696
2773
|
{
|
|
@@ -2698,47 +2775,438 @@ function KanbanCard({ card, onClick, selected, className }) {
|
|
|
2698
2775
|
onClick: () => onClick?.(card),
|
|
2699
2776
|
"aria-pressed": selected,
|
|
2700
2777
|
"aria-busy": isWorking,
|
|
2778
|
+
draggable,
|
|
2779
|
+
onDragStart: draggable ? (e) => {
|
|
2780
|
+
e.dataTransfer.effectAllowed = "move";
|
|
2781
|
+
e.dataTransfer.setData("text/plain", card.id);
|
|
2782
|
+
onDragStart?.(card);
|
|
2783
|
+
} : void 0,
|
|
2784
|
+
onDragEnd: draggable ? () => onDragEnd?.() : void 0,
|
|
2701
2785
|
className: cn9(
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
"
|
|
2705
|
-
|
|
2786
|
+
// Solid card: fill + border + a faint token top-sheen (adapts to theme),
|
|
2787
|
+
// matching the host's cockpit cards (work-shell v2 visual pass).
|
|
2788
|
+
"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",
|
|
2789
|
+
"hover:shadow-md hover:border-primary/40",
|
|
2790
|
+
"focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
2791
|
+
draggable && "cursor-grab active:cursor-grabbing",
|
|
2792
|
+
// The working card carries the SINGLE focal motion on the board (F11).
|
|
2793
|
+
isWorking ? "border-info/60 ring-2 ring-info/40 animate-pulse" : selected ? "border-primary/60 ring-2 ring-primary/30" : "border-border",
|
|
2706
2794
|
className
|
|
2707
2795
|
),
|
|
2708
2796
|
children: [
|
|
2709
|
-
isWorking && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2710
|
-
"span",
|
|
2711
|
-
{
|
|
2712
|
-
"aria-hidden": true,
|
|
2713
|
-
className: "pointer-events-none absolute -top-1 -right-1 flex h-3 w-3",
|
|
2714
|
-
children: [
|
|
2715
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-amber-400 opacity-75" }),
|
|
2716
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex h-3 w-3 rounded-full bg-amber-500" })
|
|
2717
|
-
]
|
|
2718
|
-
}
|
|
2719
|
-
),
|
|
2720
2797
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
2721
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-
|
|
2722
|
-
isWorking ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2723
|
-
/* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
|
|
2724
|
-
"Working"
|
|
2725
|
-
] }) : tone && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2798
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-foreground line-clamp-2", children: card.title }),
|
|
2799
|
+
isWorking ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2726
2800
|
"span",
|
|
2727
2801
|
{
|
|
2728
2802
|
className: cn9(
|
|
2729
|
-
"shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
|
|
2730
|
-
|
|
2803
|
+
"shrink-0 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
|
|
2804
|
+
badgeVariantClass("info")
|
|
2731
2805
|
),
|
|
2732
|
-
children:
|
|
2806
|
+
children: [
|
|
2807
|
+
/* @__PURE__ */ jsxRuntime.jsx(Spinner, {}),
|
|
2808
|
+
"Working"
|
|
2809
|
+
]
|
|
2810
|
+
}
|
|
2811
|
+
) : chip && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2812
|
+
"span",
|
|
2813
|
+
{
|
|
2814
|
+
className: cn9(
|
|
2815
|
+
"shrink-0 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide",
|
|
2816
|
+
badgeVariantClass(chip.variant)
|
|
2817
|
+
),
|
|
2818
|
+
children: [
|
|
2819
|
+
chip.icon === "clock" && /* @__PURE__ */ jsxRuntime.jsx(ClockIcon2, {}),
|
|
2820
|
+
chip.label
|
|
2821
|
+
]
|
|
2733
2822
|
}
|
|
2734
2823
|
)
|
|
2735
2824
|
] }),
|
|
2736
|
-
card.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-xs text-
|
|
2737
|
-
card.type && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block rounded bg-
|
|
2825
|
+
card.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-xs text-muted-foreground line-clamp-2", children: card.description }),
|
|
2826
|
+
card.type && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-block rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: card.type }) })
|
|
2738
2827
|
]
|
|
2739
2828
|
}
|
|
2740
2829
|
);
|
|
2741
2830
|
}
|
|
2831
|
+
function CapMeter({ count, cap, className }) {
|
|
2832
|
+
const pct = cap > 0 ? Math.min(100, Math.round(count / cap * 100)) : 0;
|
|
2833
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2834
|
+
"div",
|
|
2835
|
+
{
|
|
2836
|
+
className: cn9(
|
|
2837
|
+
"mt-2 flex items-center gap-2 text-[11px] text-muted-foreground",
|
|
2838
|
+
className
|
|
2839
|
+
),
|
|
2840
|
+
children: [
|
|
2841
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums font-medium", children: [
|
|
2842
|
+
count,
|
|
2843
|
+
"/",
|
|
2844
|
+
cap
|
|
2845
|
+
] }),
|
|
2846
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2847
|
+
"span",
|
|
2848
|
+
{
|
|
2849
|
+
role: "progressbar",
|
|
2850
|
+
"aria-valuemin": 0,
|
|
2851
|
+
"aria-valuemax": cap,
|
|
2852
|
+
"aria-valuenow": count,
|
|
2853
|
+
"aria-label": `${count} of ${cap} used`,
|
|
2854
|
+
className: "h-1 flex-1 overflow-hidden rounded-full bg-secondary",
|
|
2855
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2856
|
+
"span",
|
|
2857
|
+
{
|
|
2858
|
+
className: "block h-full rounded-full bg-primary",
|
|
2859
|
+
style: { width: `${pct}%` }
|
|
2860
|
+
}
|
|
2861
|
+
)
|
|
2862
|
+
}
|
|
2863
|
+
)
|
|
2864
|
+
]
|
|
2865
|
+
}
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
var AGENT_GROUPS = [
|
|
2869
|
+
{ kind: "cadra", label: "Cadra agents" },
|
|
2870
|
+
{ kind: "remote", label: "Your own agents" },
|
|
2871
|
+
{ kind: "human", label: "Teammates" }
|
|
2872
|
+
];
|
|
2873
|
+
function AgentKindIcon({ kind }) {
|
|
2874
|
+
const common = {
|
|
2875
|
+
viewBox: "0 0 24 24",
|
|
2876
|
+
fill: "none",
|
|
2877
|
+
stroke: "currentColor",
|
|
2878
|
+
strokeWidth: 2,
|
|
2879
|
+
strokeLinecap: "round",
|
|
2880
|
+
strokeLinejoin: "round",
|
|
2881
|
+
className: "h-3.5 w-3.5",
|
|
2882
|
+
"aria-hidden": true
|
|
2883
|
+
};
|
|
2884
|
+
if (kind === "remote") {
|
|
2885
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...common, children: [
|
|
2886
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2" }),
|
|
2887
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 21h8M12 17v4" })
|
|
2888
|
+
] });
|
|
2889
|
+
}
|
|
2890
|
+
if (kind === "human") {
|
|
2891
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...common, children: [
|
|
2892
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
2893
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "7", r: "4" })
|
|
2894
|
+
] });
|
|
2895
|
+
}
|
|
2896
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...common, children: [
|
|
2897
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "11", width: "18", height: "10", rx: "2" }),
|
|
2898
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "2" }),
|
|
2899
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 7v4M8 16h.01M16 16h.01" })
|
|
2900
|
+
] });
|
|
2901
|
+
}
|
|
2902
|
+
function OnlineDot({ online }) {
|
|
2903
|
+
if (online === void 0) return null;
|
|
2904
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2905
|
+
"span",
|
|
2906
|
+
{
|
|
2907
|
+
"aria-label": online ? "connected" : "offline",
|
|
2908
|
+
className: cn9(
|
|
2909
|
+
"h-1.5 w-1.5 shrink-0 rounded-full",
|
|
2910
|
+
online ? "bg-success" : "bg-muted-foreground"
|
|
2911
|
+
)
|
|
2912
|
+
}
|
|
2913
|
+
);
|
|
2914
|
+
}
|
|
2915
|
+
function RemoteBadge() {
|
|
2916
|
+
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" });
|
|
2917
|
+
}
|
|
2918
|
+
function LaneAgentPicker({
|
|
2919
|
+
selected,
|
|
2920
|
+
options,
|
|
2921
|
+
onSelect,
|
|
2922
|
+
canEdit = true,
|
|
2923
|
+
lockedReason,
|
|
2924
|
+
onEditAgent,
|
|
2925
|
+
className
|
|
2926
|
+
}) {
|
|
2927
|
+
const [open, setOpen] = react.useState(false);
|
|
2928
|
+
const [placement, setPlacement] = react.useState(null);
|
|
2929
|
+
const rootRef = react.useRef(null);
|
|
2930
|
+
const triggerRef = react.useRef(null);
|
|
2931
|
+
const editable = canEdit && !!onSelect && (options?.length ?? 0) > 0;
|
|
2932
|
+
const openMenu = () => {
|
|
2933
|
+
const el = triggerRef.current;
|
|
2934
|
+
if (el && typeof window !== "undefined") {
|
|
2935
|
+
const rect = el.getBoundingClientRect();
|
|
2936
|
+
const vw = window.innerWidth || 0;
|
|
2937
|
+
const vh = window.innerHeight || 0;
|
|
2938
|
+
const margin = 8;
|
|
2939
|
+
const gap = 4;
|
|
2940
|
+
const desired = 288;
|
|
2941
|
+
const width = Math.max(
|
|
2942
|
+
Math.min(rect.width || 224, Math.max(vw - margin * 2, 160)),
|
|
2943
|
+
160
|
|
2944
|
+
);
|
|
2945
|
+
const left = Math.min(
|
|
2946
|
+
Math.max(rect.left, margin),
|
|
2947
|
+
Math.max(vw - width - margin, margin)
|
|
2948
|
+
);
|
|
2949
|
+
const spaceBelow = vh - rect.bottom - gap - margin;
|
|
2950
|
+
const spaceAbove = rect.top - gap - margin;
|
|
2951
|
+
const flipUp = spaceBelow < desired && spaceAbove > spaceBelow;
|
|
2952
|
+
const maxHeight = Math.max(
|
|
2953
|
+
Math.min(desired, flipUp ? spaceAbove : spaceBelow),
|
|
2954
|
+
120
|
|
2955
|
+
);
|
|
2956
|
+
setPlacement(
|
|
2957
|
+
flipUp ? { bottom: Math.max(vh - rect.top + gap, margin), left, width, maxHeight } : { top: rect.bottom + gap, left, width, maxHeight }
|
|
2958
|
+
);
|
|
2959
|
+
} else {
|
|
2960
|
+
setPlacement(null);
|
|
2961
|
+
}
|
|
2962
|
+
setOpen(true);
|
|
2963
|
+
};
|
|
2964
|
+
react.useEffect(() => {
|
|
2965
|
+
if (!open) return;
|
|
2966
|
+
const onDocDown = (e) => {
|
|
2967
|
+
if (rootRef.current && !rootRef.current.contains(e.target)) {
|
|
2968
|
+
setOpen(false);
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
const onKey = (e) => {
|
|
2972
|
+
if (e.key === "Escape") setOpen(false);
|
|
2973
|
+
};
|
|
2974
|
+
document.addEventListener("mousedown", onDocDown);
|
|
2975
|
+
document.addEventListener("touchstart", onDocDown);
|
|
2976
|
+
document.addEventListener("keydown", onKey);
|
|
2977
|
+
return () => {
|
|
2978
|
+
document.removeEventListener("mousedown", onDocDown);
|
|
2979
|
+
document.removeEventListener("touchstart", onDocDown);
|
|
2980
|
+
document.removeEventListener("keydown", onKey);
|
|
2981
|
+
};
|
|
2982
|
+
}, [open]);
|
|
2983
|
+
const kind = selected?.kind ?? "cadra";
|
|
2984
|
+
const displayLabel = selected?.label ?? "Unassigned";
|
|
2985
|
+
const face = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2986
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2987
|
+
"span",
|
|
2988
|
+
{
|
|
2989
|
+
className: cn9(
|
|
2990
|
+
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
2991
|
+
kind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
2992
|
+
),
|
|
2993
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AgentKindIcon, { kind })
|
|
2994
|
+
}
|
|
2995
|
+
),
|
|
2996
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: displayLabel }),
|
|
2997
|
+
kind === "remote" && /* @__PURE__ */ jsxRuntime.jsx(RemoteBadge, {}),
|
|
2998
|
+
/* @__PURE__ */ jsxRuntime.jsx(OnlineDot, { online: selected?.online })
|
|
2999
|
+
] });
|
|
3000
|
+
const grouped = AGENT_GROUPS.map((group) => ({
|
|
3001
|
+
...group,
|
|
3002
|
+
items: (options ?? []).filter((opt) => (opt.kind ?? "cadra") === group.kind)
|
|
3003
|
+
})).filter((group) => group.items.length > 0);
|
|
3004
|
+
const menuStyle = placement ? {
|
|
3005
|
+
position: "fixed",
|
|
3006
|
+
top: placement.top,
|
|
3007
|
+
bottom: placement.bottom,
|
|
3008
|
+
left: placement.left,
|
|
3009
|
+
width: placement.width,
|
|
3010
|
+
maxHeight: placement.maxHeight
|
|
3011
|
+
} : void 0;
|
|
3012
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn9("relative mt-2 w-full", className), children: [
|
|
3013
|
+
editable ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3014
|
+
"button",
|
|
3015
|
+
{
|
|
3016
|
+
ref: triggerRef,
|
|
3017
|
+
type: "button",
|
|
3018
|
+
onClick: () => open ? setOpen(false) : openMenu(),
|
|
3019
|
+
"aria-haspopup": "listbox",
|
|
3020
|
+
"aria-expanded": open,
|
|
3021
|
+
"aria-label": `Assigned agent: ${displayLabel}. Change`,
|
|
3022
|
+
className: cn9(
|
|
3023
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-lg border border-border bg-card px-2",
|
|
3024
|
+
"text-xs font-semibold text-foreground",
|
|
3025
|
+
"hover:border-primary/50 focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
3026
|
+
),
|
|
3027
|
+
children: [
|
|
3028
|
+
face,
|
|
3029
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3030
|
+
"svg",
|
|
3031
|
+
{
|
|
3032
|
+
viewBox: "0 0 24 24",
|
|
3033
|
+
fill: "none",
|
|
3034
|
+
stroke: "currentColor",
|
|
3035
|
+
strokeWidth: 2,
|
|
3036
|
+
strokeLinecap: "round",
|
|
3037
|
+
strokeLinejoin: "round",
|
|
3038
|
+
"aria-hidden": true,
|
|
3039
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
3040
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 9l6 6 6-6" })
|
|
3041
|
+
}
|
|
3042
|
+
)
|
|
3043
|
+
]
|
|
3044
|
+
}
|
|
3045
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3046
|
+
"div",
|
|
3047
|
+
{
|
|
3048
|
+
role: "group",
|
|
3049
|
+
"aria-label": `Assigned agent: ${displayLabel}`,
|
|
3050
|
+
className: cn9(
|
|
3051
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-lg border border-border bg-muted/60 px-2",
|
|
3052
|
+
"text-xs font-semibold text-muted-foreground"
|
|
3053
|
+
),
|
|
3054
|
+
children: [
|
|
3055
|
+
face,
|
|
3056
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3057
|
+
"svg",
|
|
3058
|
+
{
|
|
3059
|
+
viewBox: "0 0 24 24",
|
|
3060
|
+
fill: "none",
|
|
3061
|
+
stroke: "currentColor",
|
|
3062
|
+
strokeWidth: 2,
|
|
3063
|
+
strokeLinecap: "round",
|
|
3064
|
+
strokeLinejoin: "round",
|
|
3065
|
+
"aria-hidden": true,
|
|
3066
|
+
className: "h-3.5 w-3.5 shrink-0",
|
|
3067
|
+
children: [
|
|
3068
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "11", width: "14", height: "10", rx: "2" }),
|
|
3069
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
|
|
3070
|
+
]
|
|
3071
|
+
}
|
|
3072
|
+
)
|
|
3073
|
+
]
|
|
3074
|
+
}
|
|
3075
|
+
),
|
|
3076
|
+
!editable && lockedReason && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1.5 flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
3077
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3078
|
+
"svg",
|
|
3079
|
+
{
|
|
3080
|
+
viewBox: "0 0 24 24",
|
|
3081
|
+
fill: "none",
|
|
3082
|
+
stroke: "currentColor",
|
|
3083
|
+
strokeWidth: 2,
|
|
3084
|
+
strokeLinecap: "round",
|
|
3085
|
+
strokeLinejoin: "round",
|
|
3086
|
+
"aria-hidden": true,
|
|
3087
|
+
className: "h-3 w-3 shrink-0",
|
|
3088
|
+
children: [
|
|
3089
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "11", width: "14", height: "10", rx: "2" }),
|
|
3090
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11V7a4 4 0 0 1 8 0v4" })
|
|
3091
|
+
]
|
|
3092
|
+
}
|
|
3093
|
+
),
|
|
3094
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: lockedReason })
|
|
3095
|
+
] }),
|
|
3096
|
+
editable && open && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3097
|
+
"div",
|
|
3098
|
+
{
|
|
3099
|
+
style: menuStyle,
|
|
3100
|
+
className: cn9(
|
|
3101
|
+
"flex flex-col overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
|
|
3102
|
+
menuStyle ? "z-50" : "absolute z-20 mt-1 max-h-64 w-full"
|
|
3103
|
+
),
|
|
3104
|
+
children: [
|
|
3105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3106
|
+
"ul",
|
|
3107
|
+
{
|
|
3108
|
+
role: "listbox",
|
|
3109
|
+
"aria-label": "Assign an agent",
|
|
3110
|
+
className: "min-h-0 flex-1 overflow-y-auto p-1",
|
|
3111
|
+
children: grouped.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("li", { role: "presentation", children: [
|
|
3112
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground", children: group.label }),
|
|
3113
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { role: "group", "aria-label": group.label, children: group.items.map((opt) => {
|
|
3114
|
+
const isSelected = opt.id === selected?.id;
|
|
3115
|
+
const optKind = opt.kind ?? "cadra";
|
|
3116
|
+
return /* @__PURE__ */ jsxRuntime.jsx("li", { role: "option", "aria-selected": isSelected, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3117
|
+
"button",
|
|
3118
|
+
{
|
|
3119
|
+
type: "button",
|
|
3120
|
+
onClick: () => {
|
|
3121
|
+
onSelect(opt.id);
|
|
3122
|
+
setOpen(false);
|
|
3123
|
+
},
|
|
3124
|
+
className: cn9(
|
|
3125
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium",
|
|
3126
|
+
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50",
|
|
3127
|
+
isSelected ? "text-primary" : "text-foreground"
|
|
3128
|
+
),
|
|
3129
|
+
children: [
|
|
3130
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3131
|
+
"span",
|
|
3132
|
+
{
|
|
3133
|
+
className: cn9(
|
|
3134
|
+
"grid h-5 w-5 shrink-0 place-items-center rounded-full",
|
|
3135
|
+
optKind === "cadra" ? "bg-primary/15 text-primary" : "bg-secondary text-foreground"
|
|
3136
|
+
),
|
|
3137
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(AgentKindIcon, { kind: optKind })
|
|
3138
|
+
}
|
|
3139
|
+
),
|
|
3140
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: opt.label }),
|
|
3141
|
+
optKind === "remote" && /* @__PURE__ */ jsxRuntime.jsx(RemoteBadge, {}),
|
|
3142
|
+
/* @__PURE__ */ jsxRuntime.jsx(OnlineDot, { online: opt.online }),
|
|
3143
|
+
isSelected && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3144
|
+
"svg",
|
|
3145
|
+
{
|
|
3146
|
+
viewBox: "0 0 24 24",
|
|
3147
|
+
fill: "none",
|
|
3148
|
+
stroke: "currentColor",
|
|
3149
|
+
strokeWidth: 2,
|
|
3150
|
+
strokeLinecap: "round",
|
|
3151
|
+
strokeLinejoin: "round",
|
|
3152
|
+
"aria-hidden": true,
|
|
3153
|
+
className: "h-3.5 w-3.5 shrink-0",
|
|
3154
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6L9 17l-5-5" })
|
|
3155
|
+
}
|
|
3156
|
+
)
|
|
3157
|
+
]
|
|
3158
|
+
}
|
|
3159
|
+
) }, opt.id);
|
|
3160
|
+
}) })
|
|
3161
|
+
] }, group.kind))
|
|
3162
|
+
}
|
|
3163
|
+
),
|
|
3164
|
+
onEditAgent && selected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border p-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3165
|
+
"button",
|
|
3166
|
+
{
|
|
3167
|
+
type: "button",
|
|
3168
|
+
onClick: () => {
|
|
3169
|
+
onEditAgent(selected.id);
|
|
3170
|
+
setOpen(false);
|
|
3171
|
+
},
|
|
3172
|
+
className: cn9(
|
|
3173
|
+
"flex min-h-11 w-full items-center gap-1.5 rounded-md px-2 text-left text-xs font-medium text-foreground",
|
|
3174
|
+
"hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring/50"
|
|
3175
|
+
),
|
|
3176
|
+
children: [
|
|
3177
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3178
|
+
"svg",
|
|
3179
|
+
{
|
|
3180
|
+
viewBox: "0 0 24 24",
|
|
3181
|
+
fill: "none",
|
|
3182
|
+
stroke: "currentColor",
|
|
3183
|
+
strokeWidth: 2,
|
|
3184
|
+
strokeLinecap: "round",
|
|
3185
|
+
strokeLinejoin: "round",
|
|
3186
|
+
"aria-hidden": true,
|
|
3187
|
+
className: "h-3.5 w-3.5 shrink-0 text-muted-foreground",
|
|
3188
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" })
|
|
3189
|
+
}
|
|
3190
|
+
),
|
|
3191
|
+
"Edit this agent"
|
|
3192
|
+
]
|
|
3193
|
+
}
|
|
3194
|
+
) })
|
|
3195
|
+
]
|
|
3196
|
+
}
|
|
3197
|
+
)
|
|
3198
|
+
] });
|
|
3199
|
+
}
|
|
3200
|
+
function dueStateFor(card, lane, now) {
|
|
3201
|
+
const rs = card.run?.status;
|
|
3202
|
+
if (rs === "completed" && !lane.terminal) return "ready";
|
|
3203
|
+
if (card.nextDueAt) {
|
|
3204
|
+
const t = Date.parse(card.nextDueAt);
|
|
3205
|
+
if (!Number.isNaN(t) && t <= now) return "due";
|
|
3206
|
+
}
|
|
3207
|
+
if (lane.trigger === "scheduled" && !rs) return "waiting";
|
|
3208
|
+
return void 0;
|
|
3209
|
+
}
|
|
2742
3210
|
function KanbanColumn({
|
|
2743
3211
|
lane,
|
|
2744
3212
|
cards,
|
|
@@ -2746,29 +3214,100 @@ function KanbanColumn({
|
|
|
2746
3214
|
selectedCardId,
|
|
2747
3215
|
onCreateCard,
|
|
2748
3216
|
showCreate,
|
|
3217
|
+
count,
|
|
3218
|
+
agentPicker,
|
|
3219
|
+
canEditHandlers,
|
|
3220
|
+
draggableCards,
|
|
3221
|
+
dragActive,
|
|
3222
|
+
dropAllowed,
|
|
3223
|
+
onCardDragStart,
|
|
3224
|
+
onCardDragEnd,
|
|
3225
|
+
onLaneDrop,
|
|
2749
3226
|
className
|
|
2750
3227
|
}) {
|
|
3228
|
+
const headerCount = count ?? cards.length;
|
|
3229
|
+
const isScheduled = lane.trigger === "scheduled";
|
|
3230
|
+
const showCapMeter = count !== void 0 && typeof lane.cap === "number" && lane.cap > 0;
|
|
3231
|
+
const showAgentBadge = lane.handler?.type === "cadra_agent" && !agentPicker;
|
|
3232
|
+
const now = Date.now();
|
|
2751
3233
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2752
3234
|
"div",
|
|
2753
3235
|
{
|
|
3236
|
+
onDragOver: dropAllowed ? (e) => {
|
|
3237
|
+
e.preventDefault();
|
|
3238
|
+
e.dataTransfer.dropEffect = "move";
|
|
3239
|
+
} : void 0,
|
|
3240
|
+
onDrop: dropAllowed && onLaneDrop ? (e) => {
|
|
3241
|
+
e.preventDefault();
|
|
3242
|
+
onLaneDrop(lane);
|
|
3243
|
+
} : void 0,
|
|
2754
3244
|
className: cn9(
|
|
2755
|
-
"flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-
|
|
3245
|
+
"flex w-[85vw] max-w-sm snap-start shrink-0 flex-col rounded-xl bg-muted/40 border border-border transition sm:w-72 sm:max-w-none",
|
|
3246
|
+
isScheduled && "border-border",
|
|
3247
|
+
// MV4 drag affordance: allowed targets glow; other lanes dim so the
|
|
3248
|
+
// valid drop zones read at a glance.
|
|
3249
|
+
dragActive && dropAllowed && "border-primary/60 ring-2 ring-primary/40 bg-primary/5",
|
|
3250
|
+
dragActive && !dropAllowed && "opacity-55",
|
|
2756
3251
|
className
|
|
2757
3252
|
),
|
|
2758
3253
|
children: [
|
|
2759
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
2760
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2
|
|
2761
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
3254
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-border px-3 py-2.5", children: [
|
|
3255
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
3256
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
3257
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold text-foreground truncate", children: lane.label }),
|
|
3258
|
+
showAgentBadge && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3259
|
+
"span",
|
|
3260
|
+
{
|
|
3261
|
+
title: lane.handler?.agentRef ? `Agent: ${lane.handler.agentRef}` : "Agent lane",
|
|
3262
|
+
className: cn9(
|
|
3263
|
+
// A static lane marker — NOT a run state. Uses neutral tokens,
|
|
3264
|
+
// never `info` (which means "running" in the F3 vocabulary).
|
|
3265
|
+
"rounded px-1.5 py-0.5 text-[10px] font-medium",
|
|
3266
|
+
badgeVariantClass("neutral")
|
|
3267
|
+
),
|
|
3268
|
+
children: "agent"
|
|
3269
|
+
}
|
|
3270
|
+
)
|
|
3271
|
+
] }),
|
|
3272
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground tabular-nums", children: headerCount })
|
|
2770
3273
|
] }),
|
|
2771
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
3274
|
+
isScheduled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex items-center gap-2 flex-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3275
|
+
"span",
|
|
3276
|
+
{
|
|
3277
|
+
className: cn9(
|
|
3278
|
+
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[11px] font-semibold",
|
|
3279
|
+
badgeVariantClass("neutral")
|
|
3280
|
+
),
|
|
3281
|
+
children: [
|
|
3282
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3283
|
+
"svg",
|
|
3284
|
+
{
|
|
3285
|
+
viewBox: "0 0 24 24",
|
|
3286
|
+
fill: "none",
|
|
3287
|
+
stroke: "currentColor",
|
|
3288
|
+
strokeWidth: "2",
|
|
3289
|
+
strokeLinecap: "round",
|
|
3290
|
+
strokeLinejoin: "round",
|
|
3291
|
+
"aria-hidden": true,
|
|
3292
|
+
className: "h-3 w-3",
|
|
3293
|
+
children: [
|
|
3294
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9" }),
|
|
3295
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 7v5l3 2" })
|
|
3296
|
+
]
|
|
3297
|
+
}
|
|
3298
|
+
),
|
|
3299
|
+
"runs on schedule"
|
|
3300
|
+
]
|
|
3301
|
+
}
|
|
3302
|
+
) }),
|
|
3303
|
+
showCapMeter && /* @__PURE__ */ jsxRuntime.jsx(CapMeter, { count: headerCount, cap: lane.cap }),
|
|
3304
|
+
agentPicker && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3305
|
+
LaneAgentPicker,
|
|
3306
|
+
{
|
|
3307
|
+
...agentPicker,
|
|
3308
|
+
canEdit: agentPicker.canEdit ?? canEditHandlers
|
|
3309
|
+
}
|
|
3310
|
+
)
|
|
2772
3311
|
] }),
|
|
2773
3312
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-2 overflow-y-auto p-2", children: [
|
|
2774
3313
|
cards.map((card) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2776,21 +3315,26 @@ function KanbanColumn({
|
|
|
2776
3315
|
{
|
|
2777
3316
|
card,
|
|
2778
3317
|
onClick: onCardClick,
|
|
2779
|
-
selected: card.id === selectedCardId
|
|
3318
|
+
selected: card.id === selectedCardId,
|
|
3319
|
+
dueState: dueStateFor(card, lane, now),
|
|
3320
|
+
draggable: draggableCards,
|
|
3321
|
+
onDragStart: onCardDragStart,
|
|
3322
|
+
onDragEnd: onCardDragEnd
|
|
2780
3323
|
},
|
|
2781
3324
|
card.id
|
|
2782
3325
|
)),
|
|
2783
|
-
cards.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 py-4 text-center text-xs text-
|
|
3326
|
+
cards.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 py-4 text-center text-xs text-muted-foreground", children: "No cards" }),
|
|
2784
3327
|
showCreate && onCreateCard && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2785
3328
|
"button",
|
|
2786
3329
|
{
|
|
2787
3330
|
type: "button",
|
|
2788
3331
|
onClick: () => onCreateCard(lane),
|
|
2789
3332
|
className: cn9(
|
|
2790
|
-
|
|
2791
|
-
"
|
|
2792
|
-
"
|
|
2793
|
-
"
|
|
3333
|
+
// ≥44px touch (M12/F5) — density kept via padding, not height.
|
|
3334
|
+
"mt-1 inline-flex min-h-11 w-full items-center justify-center rounded-lg border border-dashed border-border px-3 py-2",
|
|
3335
|
+
"text-sm font-medium text-muted-foreground",
|
|
3336
|
+
"hover:border-primary/50 hover:text-foreground transition",
|
|
3337
|
+
"focus:outline-none focus:ring-2 focus:ring-ring/40"
|
|
2794
3338
|
),
|
|
2795
3339
|
children: "+ New card"
|
|
2796
3340
|
}
|
|
@@ -2805,14 +3349,14 @@ function SkeletonCard() {
|
|
|
2805
3349
|
"div",
|
|
2806
3350
|
{
|
|
2807
3351
|
className: cn9(
|
|
2808
|
-
"w-full rounded-lg border border-
|
|
2809
|
-
"bg-
|
|
3352
|
+
"w-full rounded-lg border border-border",
|
|
3353
|
+
"bg-card p-3 shadow-sm"
|
|
2810
3354
|
),
|
|
2811
3355
|
"aria-hidden": "true",
|
|
2812
3356
|
children: [
|
|
2813
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3.5 w-3/4 rounded bg-
|
|
2814
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 h-2.5 w-1/2 rounded bg-
|
|
2815
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 h-4 w-1/3 rounded bg-
|
|
3357
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3.5 w-3/4 rounded bg-muted" }),
|
|
3358
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 h-2.5 w-1/2 rounded bg-muted" }),
|
|
3359
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 h-4 w-1/3 rounded bg-muted" })
|
|
2816
3360
|
]
|
|
2817
3361
|
}
|
|
2818
3362
|
);
|
|
@@ -2823,14 +3367,14 @@ function SkeletonColumn({ cardCount }) {
|
|
|
2823
3367
|
{
|
|
2824
3368
|
className: cn9(
|
|
2825
3369
|
"flex w-72 shrink-0 flex-col rounded-xl",
|
|
2826
|
-
"bg-
|
|
2827
|
-
"border border-
|
|
3370
|
+
"bg-muted/40",
|
|
3371
|
+
"border border-border"
|
|
2828
3372
|
),
|
|
2829
3373
|
"aria-hidden": "true",
|
|
2830
3374
|
children: [
|
|
2831
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5 border-b border-
|
|
2832
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3.5 w-24 rounded bg-
|
|
2833
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-6 rounded-full bg-
|
|
3375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 px-3 py-2.5 border-b border-border", children: [
|
|
3376
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3.5 w-24 rounded bg-muted" }),
|
|
3377
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-6 rounded-full bg-muted" })
|
|
2834
3378
|
] }),
|
|
2835
3379
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col gap-2 p-2", children: Array.from({ length: cardCount }).map((_, i) => (
|
|
2836
3380
|
// eslint-disable-next-line react/no-array-index-key
|
|
@@ -2859,6 +3403,67 @@ function KanbanBoardSkeleton({
|
|
|
2859
3403
|
}
|
|
2860
3404
|
);
|
|
2861
3405
|
}
|
|
3406
|
+
function formatCountdown(nextTickAt) {
|
|
3407
|
+
if (!nextTickAt) return null;
|
|
3408
|
+
const target = Date.parse(nextTickAt);
|
|
3409
|
+
if (Number.isNaN(target)) return null;
|
|
3410
|
+
const ms = target - Date.now();
|
|
3411
|
+
if (ms <= 0) return "any moment";
|
|
3412
|
+
const mins = Math.round(ms / 6e4);
|
|
3413
|
+
if (mins < 1) return "under a minute";
|
|
3414
|
+
if (mins < 60) return `${mins}m`;
|
|
3415
|
+
const hrs = Math.floor(mins / 60);
|
|
3416
|
+
const rem = mins % 60;
|
|
3417
|
+
return rem > 0 ? `${hrs}h ${rem}m` : `${hrs}h`;
|
|
3418
|
+
}
|
|
3419
|
+
var DOT_TONE = {
|
|
3420
|
+
"on-schedule": "bg-success",
|
|
3421
|
+
due: "bg-info",
|
|
3422
|
+
paused: "bg-muted-foreground"
|
|
3423
|
+
};
|
|
3424
|
+
var DEFAULT_LABEL = {
|
|
3425
|
+
"on-schedule": "On schedule",
|
|
3426
|
+
due: "Running now",
|
|
3427
|
+
paused: "Paused"
|
|
3428
|
+
};
|
|
3429
|
+
function ScheduleStatus({
|
|
3430
|
+
nextTickAt,
|
|
3431
|
+
dueByLane,
|
|
3432
|
+
label,
|
|
3433
|
+
tone = "on-schedule",
|
|
3434
|
+
className
|
|
3435
|
+
}) {
|
|
3436
|
+
const countdown = formatCountdown(nextTickAt);
|
|
3437
|
+
const totalDue = dueByLane ? Object.values(dueByLane).reduce((a, b) => a + b, 0) : 0;
|
|
3438
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3439
|
+
"div",
|
|
3440
|
+
{
|
|
3441
|
+
className: cn9(
|
|
3442
|
+
"flex items-center gap-2 text-xs text-muted-foreground",
|
|
3443
|
+
className
|
|
3444
|
+
),
|
|
3445
|
+
children: [
|
|
3446
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3447
|
+
"span",
|
|
3448
|
+
{
|
|
3449
|
+
"aria-hidden": true,
|
|
3450
|
+
className: cn9("h-2 w-2 shrink-0 rounded-full", DOT_TONE[tone])
|
|
3451
|
+
}
|
|
3452
|
+
),
|
|
3453
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: label ?? DEFAULT_LABEL[tone] }),
|
|
3454
|
+
countdown && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums", children: [
|
|
3455
|
+
"\xB7 next run in ",
|
|
3456
|
+
countdown
|
|
3457
|
+
] }),
|
|
3458
|
+
totalDue > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums", children: [
|
|
3459
|
+
"\xB7 ",
|
|
3460
|
+
totalDue,
|
|
3461
|
+
" due"
|
|
3462
|
+
] })
|
|
3463
|
+
]
|
|
3464
|
+
}
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
2862
3467
|
function KanbanBoard({
|
|
2863
3468
|
adapter,
|
|
2864
3469
|
onCreateCard,
|
|
@@ -2869,6 +3474,13 @@ function KanbanBoard({
|
|
|
2869
3474
|
enableEdit = true,
|
|
2870
3475
|
enableAdvance = true,
|
|
2871
3476
|
pollIntervalMs,
|
|
3477
|
+
laneCounts,
|
|
3478
|
+
canEditHandlers,
|
|
3479
|
+
laneAgentPicker,
|
|
3480
|
+
scheduleStatus,
|
|
3481
|
+
focusCardId,
|
|
3482
|
+
renderCardDrawer,
|
|
3483
|
+
enableDrag,
|
|
2872
3484
|
className
|
|
2873
3485
|
}) {
|
|
2874
3486
|
const {
|
|
@@ -2883,8 +3495,19 @@ function KanbanBoard({
|
|
|
2883
3495
|
createCard,
|
|
2884
3496
|
updateCard,
|
|
2885
3497
|
transitionCard,
|
|
3498
|
+
deleteCard,
|
|
2886
3499
|
handleRunTerminal
|
|
2887
3500
|
} = useKanbanBoard({ adapter, onError, onCardCreated, pollIntervalMs });
|
|
3501
|
+
const lastFocusedRef = react.useRef(void 0);
|
|
3502
|
+
react.useEffect(() => {
|
|
3503
|
+
if (!focusCardId) {
|
|
3504
|
+
lastFocusedRef.current = void 0;
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3507
|
+
if (lastFocusedRef.current === focusCardId) return;
|
|
3508
|
+
lastFocusedRef.current = focusCardId;
|
|
3509
|
+
void openCard(focusCardId);
|
|
3510
|
+
}, [focusCardId]);
|
|
2888
3511
|
const handleCardClick = react.useCallback(
|
|
2889
3512
|
(card) => {
|
|
2890
3513
|
void openCard(card.id);
|
|
@@ -2917,14 +3540,53 @@ function KanbanBoard({
|
|
|
2917
3540
|
(cardId, toLane) => transitionCard({ cardId, targetLane: toLane }),
|
|
2918
3541
|
[transitionCard]
|
|
2919
3542
|
);
|
|
3543
|
+
const [dragging, setDragging] = react.useState(null);
|
|
3544
|
+
const draggingRef = react.useRef(null);
|
|
3545
|
+
const dropInFlightRef = react.useRef(null);
|
|
3546
|
+
const allowedDropLanes = react.useMemo(() => {
|
|
3547
|
+
if (!dragging || !board?.transitions) return /* @__PURE__ */ new Set();
|
|
3548
|
+
return new Set(
|
|
3549
|
+
board.transitions.filter((t) => t.fromLane === dragging.lane && t.manualAllowed).map((t) => t.toLane)
|
|
3550
|
+
);
|
|
3551
|
+
}, [dragging, board?.transitions]);
|
|
3552
|
+
const handleCardDragStart = react.useCallback((card) => {
|
|
3553
|
+
const d = { id: card.id, lane: card.lane };
|
|
3554
|
+
draggingRef.current = d;
|
|
3555
|
+
setDragging(d);
|
|
3556
|
+
}, []);
|
|
3557
|
+
const handleCardDragEnd = react.useCallback(() => {
|
|
3558
|
+
draggingRef.current = null;
|
|
3559
|
+
setDragging(null);
|
|
3560
|
+
}, []);
|
|
3561
|
+
const handleLaneDrop = react.useCallback(
|
|
3562
|
+
(lane) => {
|
|
3563
|
+
const cur = draggingRef.current;
|
|
3564
|
+
draggingRef.current = null;
|
|
3565
|
+
setDragging(null);
|
|
3566
|
+
if (!cur || lane.key === cur.lane) return;
|
|
3567
|
+
if (dropInFlightRef.current === cur.id) return;
|
|
3568
|
+
const allowed = (board?.transitions ?? []).some(
|
|
3569
|
+
(t) => t.fromLane === cur.lane && t.toLane === lane.key && t.manualAllowed
|
|
3570
|
+
);
|
|
3571
|
+
if (!allowed) return;
|
|
3572
|
+
dropInFlightRef.current = cur.id;
|
|
3573
|
+
void Promise.resolve(transitionCard({ cardId: cur.id, targetLane: lane.key })).finally(
|
|
3574
|
+
() => {
|
|
3575
|
+
if (dropInFlightRef.current === cur.id) dropInFlightRef.current = null;
|
|
3576
|
+
}
|
|
3577
|
+
);
|
|
3578
|
+
},
|
|
3579
|
+
[board?.transitions, transitionCard]
|
|
3580
|
+
);
|
|
2920
3581
|
const initialLaneKey = lanes.find((l) => !l.terminal)?.key ?? lanes[0]?.key;
|
|
2921
3582
|
const selectedLane = selectedCard ? lanes.find((l) => l.key === selectedCard.lane) : void 0;
|
|
2922
3583
|
const canAdvanceSelected = !!selectedLane && !selectedLane.terminal;
|
|
2923
3584
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn9("flex h-full w-full flex-col", className), children: [
|
|
2924
|
-
error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "m-3 rounded-md border border-
|
|
3585
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "m-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: error }),
|
|
3586
|
+
scheduleStatus && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-b border-border px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(ScheduleStatus, { ...scheduleStatus }) }),
|
|
2925
3587
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
2926
3588
|
lanes.length === 0 && isLoading ? /* @__PURE__ */ jsxRuntime.jsx(KanbanBoardSkeleton, { className: "flex-1 overflow-x-auto p-4" }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 items-start gap-4 overflow-x-auto p-4 snap-x snap-mandatory no-scrollbar sm:snap-none", children: [
|
|
2927
|
-
lanes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-
|
|
3589
|
+
lanes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "No lanes configured." }),
|
|
2928
3590
|
lanes.map((lane) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2929
3591
|
KanbanColumn,
|
|
2930
3592
|
{
|
|
@@ -2933,12 +3595,35 @@ function KanbanBoard({
|
|
|
2933
3595
|
onCardClick: handleCardClick,
|
|
2934
3596
|
selectedCardId: selectedCard?.id,
|
|
2935
3597
|
onCreateCard: handleCreate,
|
|
2936
|
-
showCreate: enableCreate && lane.key === initialLaneKey
|
|
3598
|
+
showCreate: enableCreate && lane.key === initialLaneKey,
|
|
3599
|
+
count: laneCounts?.[lane.key],
|
|
3600
|
+
agentPicker: laneAgentPicker?.(lane),
|
|
3601
|
+
canEditHandlers,
|
|
3602
|
+
draggableCards: enableDrag && enableAdvance,
|
|
3603
|
+
dragActive: !!dragging,
|
|
3604
|
+
dropAllowed: !!dragging && dragging.lane !== lane.key && allowedDropLanes.has(lane.key),
|
|
3605
|
+
onCardDragStart: handleCardDragStart,
|
|
3606
|
+
onCardDragEnd: handleCardDragEnd,
|
|
3607
|
+
onLaneDrop: handleLaneDrop
|
|
2937
3608
|
},
|
|
2938
3609
|
lane.id ?? lane.key
|
|
2939
3610
|
))
|
|
2940
3611
|
] }),
|
|
2941
|
-
selectedCard &&
|
|
3612
|
+
selectedCard && (renderCardDrawer ? renderCardDrawer({
|
|
3613
|
+
card: selectedCard,
|
|
3614
|
+
adapter,
|
|
3615
|
+
lanes,
|
|
3616
|
+
transitions: enableAdvance ? board?.transitions : void 0,
|
|
3617
|
+
canAdvance: canAdvanceSelected,
|
|
3618
|
+
onClose: closeCard,
|
|
3619
|
+
onRetry: onRetryCard ? handleRetry : void 0,
|
|
3620
|
+
onUpdate: enableEdit ? handleUpdate : void 0,
|
|
3621
|
+
onAdvance: enableAdvance ? handleAdvance : void 0,
|
|
3622
|
+
onTransition: enableAdvance ? handleTransition : void 0,
|
|
3623
|
+
onDelete: adapter.deleteCard ? deleteCard : void 0,
|
|
3624
|
+
onRunTerminal: handleRunTerminal,
|
|
3625
|
+
onError
|
|
3626
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2942
3627
|
CardDrawer,
|
|
2943
3628
|
{
|
|
2944
3629
|
card: selectedCard,
|
|
@@ -2954,7 +3639,7 @@ function KanbanBoard({
|
|
|
2954
3639
|
onRunTerminal: handleRunTerminal,
|
|
2955
3640
|
onError
|
|
2956
3641
|
}
|
|
2957
|
-
)
|
|
3642
|
+
))
|
|
2958
3643
|
] })
|
|
2959
3644
|
] });
|
|
2960
3645
|
}
|
|
@@ -2979,6 +3664,7 @@ function composeAdapter(source, boardConfigUuid, version, cardSource) {
|
|
|
2979
3664
|
transitionCard(input) {
|
|
2980
3665
|
return cardSource.transitionCard(input);
|
|
2981
3666
|
},
|
|
3667
|
+
...cardSource.deleteCard ? { deleteCard: (id) => cardSource.deleteCard(id) } : {},
|
|
2982
3668
|
...cardSource.createRunStream ? { createRunStream: (id) => cardSource.createRunStream(id) } : {}
|
|
2983
3669
|
};
|
|
2984
3670
|
}
|
|
@@ -3675,15 +4361,6 @@ var ExecutionStreamControllerImpl = class {
|
|
|
3675
4361
|
sseClient = null;
|
|
3676
4362
|
status = "connecting";
|
|
3677
4363
|
reconnectAttempts = 0;
|
|
3678
|
-
// CAD-39 / webchat dual-plane create-connect race. `hasConnected` flips true on
|
|
3679
|
-
// the first successful SSE open; `notFoundRetries` bounds retries of a 404
|
|
3680
|
-
// encountered BEFORE we ever connect (the run row is persisted slightly AFTER
|
|
3681
|
-
// the client opens the stream — the norm on the webchat path where msg-api owns
|
|
3682
|
-
// the executionId and cadra persists the agent_executions row via an async
|
|
3683
|
-
// worker callback). See handleConnectionError.
|
|
3684
|
-
hasConnected = false;
|
|
3685
|
-
notFoundRetries = 0;
|
|
3686
|
-
maxConnectRaceRetries = 3;
|
|
3687
4364
|
seenEventIds = /* @__PURE__ */ new Set();
|
|
3688
4365
|
_lastEventId = null;
|
|
3689
4366
|
activityTimeoutId = null;
|
|
@@ -3823,8 +4500,6 @@ var ExecutionStreamControllerImpl = class {
|
|
|
3823
4500
|
if (connected) {
|
|
3824
4501
|
this.status = "connected";
|
|
3825
4502
|
this.reconnectAttempts = 0;
|
|
3826
|
-
this.hasConnected = true;
|
|
3827
|
-
this.notFoundRetries = 0;
|
|
3828
4503
|
this.resetActivityTimeout();
|
|
3829
4504
|
}
|
|
3830
4505
|
this.emit("connectionChange", { connected, usingFallback: false });
|
|
@@ -4114,38 +4789,6 @@ var ExecutionStreamControllerImpl = class {
|
|
|
4114
4789
|
return;
|
|
4115
4790
|
}
|
|
4116
4791
|
const msg = error instanceof Error ? error.message : String(error);
|
|
4117
|
-
const isNotFound = /\b404\b/.test(msg);
|
|
4118
|
-
const isAuthFailure = /\b40[13]\b/.test(msg) || /INVALID_API_KEY|INVALID_REQUEST/.test(msg);
|
|
4119
|
-
if (isNotFound && !isAuthFailure && !this.hasConnected) {
|
|
4120
|
-
if (this.notFoundRetries < this.maxConnectRaceRetries) {
|
|
4121
|
-
this.notFoundRetries++;
|
|
4122
|
-
this.status = "reconnecting";
|
|
4123
|
-
const delay = Math.min(
|
|
4124
|
-
this.config.reconnectBaseDelayMs * this.notFoundRetries,
|
|
4125
|
-
this.config.reconnectMaxDelayMs
|
|
4126
|
-
);
|
|
4127
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
4128
|
-
if (this.status === "closed") {
|
|
4129
|
-
return;
|
|
4130
|
-
}
|
|
4131
|
-
try {
|
|
4132
|
-
await this.connectSSE();
|
|
4133
|
-
} catch (retryError) {
|
|
4134
|
-
await this.handleConnectionError(retryError);
|
|
4135
|
-
}
|
|
4136
|
-
return;
|
|
4137
|
-
}
|
|
4138
|
-
if (this.pollingEnabled) {
|
|
4139
|
-
if (this.sseClient) {
|
|
4140
|
-
this.sseClient.disconnect();
|
|
4141
|
-
this.sseClient = null;
|
|
4142
|
-
}
|
|
4143
|
-
this.status = "fallback_polling";
|
|
4144
|
-
this.emit("connectionChange", { connected: false, usingFallback: true });
|
|
4145
|
-
this.startPolling();
|
|
4146
|
-
return;
|
|
4147
|
-
}
|
|
4148
|
-
}
|
|
4149
4792
|
if (/\b4\d\d\b/.test(msg) || /INVALID_API_KEY|INVALID_REQUEST/.test(msg)) {
|
|
4150
4793
|
if (this.sseClient) {
|
|
4151
4794
|
this.sseClient.disconnect();
|
|
@@ -6196,12 +6839,18 @@ function createDefaultKanbanAdapter(config) {
|
|
|
6196
6839
|
exports.AgentBoard = AgentBoard;
|
|
6197
6840
|
exports.BoardConfigClient = BoardConfigClient;
|
|
6198
6841
|
exports.CadraLifecycleSource = CadraLifecycleSource;
|
|
6842
|
+
exports.CapMeter = CapMeter;
|
|
6199
6843
|
exports.CardDrawer = CardDrawer;
|
|
6200
6844
|
exports.KanbanBoard = KanbanBoard;
|
|
6201
6845
|
exports.KanbanCardTile = KanbanCard;
|
|
6202
6846
|
exports.KanbanColumn = KanbanColumn;
|
|
6847
|
+
exports.LaneAgentPicker = LaneAgentPicker;
|
|
6848
|
+
exports.ScheduleStatus = ScheduleStatus;
|
|
6849
|
+
exports.badgeVariantClass = badgeVariantClass;
|
|
6850
|
+
exports.cn = cn9;
|
|
6203
6851
|
exports.createDefaultKanbanAdapter = createDefaultKanbanAdapter;
|
|
6204
6852
|
exports.mapDefinitionToBoard = mapDefinitionToBoard;
|
|
6853
|
+
exports.runStatusTone = runStatusTone;
|
|
6205
6854
|
exports.toResolvedLifecycle = toResolvedLifecycle;
|
|
6206
6855
|
exports.useCardRun = useCardRun;
|
|
6207
6856
|
exports.useKanbanBoard = useKanbanBoard;
|