@hachej/boring-ask-user 0.1.64 → 0.1.65
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/front/index.d.ts +15 -3
- package/dist/front/index.js +572 -75
- package/dist/server/index.js +1 -0
- package/package.json +13 -13
package/dist/front/index.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import * as _hachej_boring_workspace_plugin from '@hachej/boring-workspace/plugin';
|
|
1
2
|
import { BoringFrontFactoryWithId } from '@hachej/boring-workspace/plugin';
|
|
3
|
+
import { WorkspaceAttentionBlocker } from '@hachej/boring-workspace';
|
|
2
4
|
|
|
5
|
+
declare const INBOX_DEMO_SESSION_ID = "showcase";
|
|
6
|
+
declare function createInboxDemoBlockers(now?: number): WorkspaceAttentionBlocker[];
|
|
7
|
+
declare const inboxDemoPlugin: _hachej_boring_workspace_plugin.BoringFrontFactoryWithId;
|
|
8
|
+
|
|
9
|
+
interface CreateAskUserPluginOptions {
|
|
10
|
+
/** Register the workspace Inbox button/overlay in the app-left rail. */
|
|
11
|
+
appLeftInbox?: boolean;
|
|
12
|
+
}
|
|
3
13
|
/**
|
|
4
|
-
* `BoringFrontFactoryWithId` for the ask-user plugin. Registers
|
|
14
|
+
* Creates a `BoringFrontFactoryWithId` for the ask-user plugin. Registers
|
|
5
15
|
* (1) a provider that owns the per-app questions runtime (apiBaseUrl,
|
|
6
16
|
* auth headers, in-memory pending-question store), (2) a "Questions"
|
|
7
17
|
* panel rendering the pending question form, and (3) a surface
|
|
@@ -10,8 +20,10 @@ import { BoringFrontFactoryWithId } from '@hachej/boring-workspace/plugin';
|
|
|
10
20
|
* Pass directly to `WorkspaceProvider.plugins`.
|
|
11
21
|
*
|
|
12
22
|
* The panel is opened via the surface resolver (kind: ASK_USER_SURFACE_KIND),
|
|
13
|
-
* which is how the server-side agent tool triggers it.
|
|
23
|
+
* which is how the server-side agent tool triggers it. The app-left Inbox
|
|
24
|
+
* button is opt-in via `appLeftInbox`.
|
|
14
25
|
*/
|
|
26
|
+
declare function createAskUserPlugin(options?: CreateAskUserPluginOptions): BoringFrontFactoryWithId;
|
|
15
27
|
declare const askUserPlugin: BoringFrontFactoryWithId;
|
|
16
28
|
|
|
17
|
-
export { askUserPlugin, askUserPlugin as default };
|
|
29
|
+
export { type CreateAskUserPluginOptions, INBOX_DEMO_SESSION_ID, askUserPlugin, createAskUserPlugin, createInboxDemoBlockers, askUserPlugin as default, inboxDemoPlugin };
|
package/dist/front/index.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
import { Button, EmptyState, Notice, Pane, PaneBody, PaneFooter, PaneHeader, PaneTitle } from "@hachej/boring-ui-kit";
|
|
5
5
|
import {
|
|
6
6
|
WORKSPACE_COMPOSER_STOP_EVENT as WORKSPACE_COMPOSER_STOP_EVENT2,
|
|
7
|
+
useWorkspaceAttention as useWorkspaceAttention4,
|
|
8
|
+
useWorkspaceContext,
|
|
7
9
|
workspaceComposerStopAppliesToSession as workspaceComposerStopAppliesToSession2
|
|
8
10
|
} from "@hachej/boring-workspace";
|
|
9
11
|
import {
|
|
10
|
-
definePlugin
|
|
12
|
+
definePlugin as definePlugin2
|
|
11
13
|
} from "@hachej/boring-workspace/plugin";
|
|
12
|
-
import { HelpCircle, XCircle } from "lucide-react";
|
|
13
|
-
import { useEffect as
|
|
14
|
+
import { HelpCircle, Inbox as Inbox2, XCircle } from "lucide-react";
|
|
15
|
+
import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef3, useSyncExternalStore, useState as useState3 } from "react";
|
|
14
16
|
|
|
15
17
|
// src/shared/constants.ts
|
|
16
18
|
var ASK_USER_PLUGIN_ID = "ask-user";
|
|
@@ -573,9 +575,18 @@ function useAskUserAttentionBlockers(runtime, pendingSnapshot) {
|
|
|
573
575
|
reason: "ask-user.question",
|
|
574
576
|
surfaceKind: ASK_USER_SURFACE_KIND,
|
|
575
577
|
target: hint.questionId,
|
|
576
|
-
label: "Answer the question in Questions to continue",
|
|
578
|
+
label: hydrated?.title ?? "Answer the question in Questions to continue",
|
|
577
579
|
sessionId: hint.sessionId,
|
|
578
580
|
sessionBadge: { kind: "question", label: "question", tone: "attention", priority: 10 },
|
|
581
|
+
pruneWhenSessionMissing: true,
|
|
582
|
+
focus: { closeWorkbenchLeftPane: true },
|
|
583
|
+
inbox: {
|
|
584
|
+
kind: "question",
|
|
585
|
+
sourceLabel: "question",
|
|
586
|
+
createdAt: hydrated?.createdAt,
|
|
587
|
+
updatedAt: hydrated?.updatedAt ?? hydrated?.createdAt,
|
|
588
|
+
priority: 10
|
|
589
|
+
},
|
|
579
590
|
actions
|
|
580
591
|
});
|
|
581
592
|
}
|
|
@@ -655,9 +666,8 @@ function useAskUserPendingRefresh(runtime, options) {
|
|
|
655
666
|
const sessionsToHydrate = /* @__PURE__ */ new Set();
|
|
656
667
|
if (activeSessionId) sessionsToHydrate.add(activeSessionId);
|
|
657
668
|
for (const hint of hints) {
|
|
658
|
-
|
|
669
|
+
sessionsToHydrate.add(hint.sessionId);
|
|
659
670
|
}
|
|
660
|
-
if (sessionsToHydrate.size === 0 && hints[0]) sessionsToHydrate.add(hints[0].sessionId);
|
|
661
671
|
await Promise.all([...sessionsToHydrate].map(async (sessionId) => {
|
|
662
672
|
try {
|
|
663
673
|
await runtime.refreshPending(sessionId);
|
|
@@ -707,10 +717,454 @@ function hasPendingStateSlot(state) {
|
|
|
707
717
|
return !!state && Object.prototype.hasOwnProperty.call(state, ASK_USER_UI_STATE_SLOTS.PENDING);
|
|
708
718
|
}
|
|
709
719
|
|
|
710
|
-
// src/front/
|
|
720
|
+
// src/front/inbox/InboxOverlay.tsx
|
|
721
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo3, useState as useState2 } from "react";
|
|
722
|
+
import { Inbox, MailOpen, X } from "lucide-react";
|
|
723
|
+
import { IconButton } from "@hachej/boring-ui-kit";
|
|
724
|
+
import { emitWorkspaceAttentionAction, useWorkspaceAttention as useWorkspaceAttention2, useAppLeftOverlayChrome, cn as cn3 } from "@hachej/boring-workspace";
|
|
725
|
+
|
|
726
|
+
// src/front/inbox/attentionBlockerAdapter.ts
|
|
727
|
+
import { workspaceAttentionSessionBadgeForBlocker } from "@hachej/boring-workspace";
|
|
728
|
+
var FALLBACK_TIMESTAMP = "1970-01-01T00:00:00.000Z";
|
|
729
|
+
function dateValue(value) {
|
|
730
|
+
if (value === void 0 || value === null) return null;
|
|
731
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
732
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
733
|
+
}
|
|
734
|
+
function blockerTimestamp(blocker) {
|
|
735
|
+
return dateValue(blocker.inbox?.updatedAt) ?? dateValue(blocker.inbox?.createdAt) ?? FALLBACK_TIMESTAMP;
|
|
736
|
+
}
|
|
737
|
+
function blockerKind(blocker) {
|
|
738
|
+
if (blocker.inbox?.kind) return blocker.inbox.kind;
|
|
739
|
+
const badge = workspaceAttentionSessionBadgeForBlocker(blocker);
|
|
740
|
+
if (badge?.kind === "question" || badge?.kind === "review" || badge?.kind === "approval" || badge?.kind === "notice") return badge.kind;
|
|
741
|
+
return "notice";
|
|
742
|
+
}
|
|
743
|
+
function blockerTitle(blocker) {
|
|
744
|
+
return blocker.label || blocker.reason || "Workspace item";
|
|
745
|
+
}
|
|
746
|
+
function blockerSourceLabel(blocker) {
|
|
747
|
+
return blocker.inbox?.sourceLabel ?? workspaceAttentionSessionBadgeForBlocker(blocker)?.label ?? "workspace";
|
|
748
|
+
}
|
|
749
|
+
function isInboxAttentionBlocker(blocker) {
|
|
750
|
+
return !!blocker.inbox;
|
|
751
|
+
}
|
|
752
|
+
function attentionBlockerToInboxItem(blocker) {
|
|
753
|
+
const updatedAt = blockerTimestamp(blocker);
|
|
754
|
+
return {
|
|
755
|
+
id: blocker.id,
|
|
756
|
+
kind: blockerKind(blocker),
|
|
757
|
+
status: "open",
|
|
758
|
+
title: blockerTitle(blocker),
|
|
759
|
+
description: blocker.reason,
|
|
760
|
+
source: { type: "plugin", pluginId: blocker.reason, label: blockerSourceLabel(blocker) },
|
|
761
|
+
sessionId: blocker.sessionId ?? null,
|
|
762
|
+
chatAvailable: blocker.pruneWhenSessionMissing === true && !!blocker.sessionId,
|
|
763
|
+
targetLabel: blocker.target ?? "",
|
|
764
|
+
artifact: blocker.surfaceKind ? {
|
|
765
|
+
type: "surface",
|
|
766
|
+
surfaceKind: blocker.surfaceKind,
|
|
767
|
+
target: blocker.target,
|
|
768
|
+
params: blocker.sessionId ? { sessionId: blocker.sessionId } : void 0
|
|
769
|
+
} : null,
|
|
770
|
+
createdAt: dateValue(blocker.inbox?.createdAt) ?? updatedAt,
|
|
771
|
+
updatedAt,
|
|
772
|
+
priority: blocker.inbox?.priority ?? workspaceAttentionSessionBadgeForBlocker(blocker)?.priority ?? 0,
|
|
773
|
+
actions: blocker.actions?.map((action) => ({ id: action.id, label: action.label })) ?? []
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/front/inbox/InboxFilterBar.tsx
|
|
778
|
+
import { cn } from "@hachej/boring-workspace";
|
|
711
779
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
780
|
+
function InboxFilterBar({
|
|
781
|
+
filter,
|
|
782
|
+
counts,
|
|
783
|
+
onFilterChange
|
|
784
|
+
}) {
|
|
785
|
+
return /* @__PURE__ */ jsx2("div", { className: "flex shrink-0 gap-1 border-b border-border/60 bg-[color:oklch(from_var(--background)_calc(l-0.012)_c_h)] px-4 py-2", children: [
|
|
786
|
+
["all", "All", counts.all],
|
|
787
|
+
["questions", "Questions", counts.questions],
|
|
788
|
+
["reviews", "Reviews", counts.reviews]
|
|
789
|
+
].map(([id, label2, count]) => /* @__PURE__ */ jsxs2(
|
|
790
|
+
"button",
|
|
791
|
+
{
|
|
792
|
+
type: "button",
|
|
793
|
+
onClick: () => onFilterChange(id),
|
|
794
|
+
className: cn(
|
|
795
|
+
"rounded-full px-2.5 py-1 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
796
|
+
filter === id ? "bg-[color:oklch(from_var(--accent)_l_c_h/0.14)] text-[color:var(--accent)]" : "bg-transparent text-muted-foreground hover:bg-foreground/[0.06] hover:text-foreground"
|
|
797
|
+
),
|
|
798
|
+
children: [
|
|
799
|
+
label2,
|
|
800
|
+
" ",
|
|
801
|
+
/* @__PURE__ */ jsx2("span", { className: "ml-1 opacity-70", children: count })
|
|
802
|
+
]
|
|
803
|
+
},
|
|
804
|
+
id
|
|
805
|
+
)) });
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// src/front/inbox/InboxSection.tsx
|
|
809
|
+
import { Pin } from "lucide-react";
|
|
810
|
+
|
|
811
|
+
// src/front/inbox/InboxRow.tsx
|
|
812
|
+
import { MessageSquare, Star } from "lucide-react";
|
|
813
|
+
import { cn as cn2 } from "@hachej/boring-workspace";
|
|
814
|
+
|
|
815
|
+
// src/front/inbox/inboxItemModel.ts
|
|
816
|
+
function inboxItemDate(item) {
|
|
817
|
+
const date = new Date(item.updatedAt || item.createdAt);
|
|
818
|
+
return Number.isNaN(date.getTime()) ? /* @__PURE__ */ new Date(0) : date;
|
|
819
|
+
}
|
|
820
|
+
function formatInboxTime(item, now = Date.now()) {
|
|
821
|
+
const date = inboxItemDate(item);
|
|
822
|
+
const diff = now - date.getTime();
|
|
823
|
+
if (diff < 6e4) return "now";
|
|
824
|
+
const minutes = Math.floor(diff / 6e4);
|
|
825
|
+
if (minutes < 60) return `${minutes}m`;
|
|
826
|
+
const hours = Math.floor(minutes / 60);
|
|
827
|
+
if (hours < 24) return `${hours}h`;
|
|
828
|
+
const days = Math.floor(hours / 24);
|
|
829
|
+
if (days < 7) return `${days}d`;
|
|
830
|
+
return date.toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
831
|
+
}
|
|
832
|
+
function inboxItemSender(item) {
|
|
833
|
+
return item.source.label || item.source.type;
|
|
834
|
+
}
|
|
835
|
+
function filterInboxItems(items, filter) {
|
|
836
|
+
if (filter === "all") return [...items];
|
|
837
|
+
return items.filter((item) => filter === "questions" ? item.kind === "question" : item.kind === "review");
|
|
838
|
+
}
|
|
839
|
+
function sortInboxItems(items) {
|
|
840
|
+
return [...items].sort((a, b) => {
|
|
841
|
+
const byTime = inboxItemDate(b).getTime() - inboxItemDate(a).getTime();
|
|
842
|
+
if (byTime !== 0) return byTime;
|
|
843
|
+
const byPriority = b.priority - a.priority;
|
|
844
|
+
if (byPriority !== 0) return byPriority;
|
|
845
|
+
return a.title.localeCompare(b.title);
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
function mergeInboxPinnedState(items, pinnedIds) {
|
|
849
|
+
return items.map((item) => ({ ...item, pinned: pinnedIds.has(item.id) }));
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// src/front/inbox/InboxRow.tsx
|
|
853
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
854
|
+
function badgeTone(kind) {
|
|
855
|
+
switch (kind) {
|
|
856
|
+
case "review":
|
|
857
|
+
return "bg-amber-500/12 text-amber-700 dark:text-amber-300";
|
|
858
|
+
case "approval":
|
|
859
|
+
return "bg-emerald-500/12 text-emerald-700 dark:text-emerald-300";
|
|
860
|
+
case "notice":
|
|
861
|
+
return "bg-foreground/[0.07] text-muted-foreground";
|
|
862
|
+
default:
|
|
863
|
+
return "bg-[color:var(--accent)]/12 text-[color:var(--accent)]";
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function InboxRow({
|
|
867
|
+
item,
|
|
868
|
+
onTogglePinned,
|
|
869
|
+
onOpenArtifact,
|
|
870
|
+
onOpenChat
|
|
871
|
+
}) {
|
|
872
|
+
const subtitle = [item.sessionId ? `Session ${item.sessionId}` : null, item.targetLabel || null].filter(Boolean).join(" \xB7 ");
|
|
873
|
+
return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsxs3(
|
|
874
|
+
"div",
|
|
875
|
+
{
|
|
876
|
+
role: "button",
|
|
877
|
+
tabIndex: 0,
|
|
878
|
+
className: "group flex h-11 w-full items-center gap-2 overflow-hidden px-4 text-left text-[12px] transition-colors hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
879
|
+
onClick: () => onOpenArtifact(item),
|
|
880
|
+
onKeyDown: (event) => {
|
|
881
|
+
if (event.currentTarget !== event.target) return;
|
|
882
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
883
|
+
event.preventDefault();
|
|
884
|
+
onOpenArtifact(item);
|
|
885
|
+
},
|
|
886
|
+
children: [
|
|
887
|
+
/* @__PURE__ */ jsx3("span", { className: "size-2 shrink-0 rounded-full bg-[color:var(--accent)]" }),
|
|
888
|
+
/* @__PURE__ */ jsx3("span", { className: "max-w-28 shrink-0 truncate font-semibold text-foreground", children: inboxItemSender(item) }),
|
|
889
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex min-w-0 flex-1 items-center gap-1 overflow-hidden whitespace-nowrap text-foreground", children: [
|
|
890
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate font-medium", children: item.title }),
|
|
891
|
+
/* @__PURE__ */ jsx3("span", { className: "shrink-0 text-muted-foreground", children: "\u2014" }),
|
|
892
|
+
/* @__PURE__ */ jsx3("span", { className: "truncate text-muted-foreground", children: subtitle || item.description })
|
|
893
|
+
] }),
|
|
894
|
+
/* @__PURE__ */ jsxs3("span", { className: "flex shrink-0 items-center gap-1.5", children: [
|
|
895
|
+
/* @__PURE__ */ jsx3("span", { className: cn2("rounded-full px-2 py-0.5 text-[10px] font-medium", badgeTone(item.kind)), children: item.kind }),
|
|
896
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[11px] font-medium text-muted-foreground", title: inboxItemDate(item).toLocaleString(), children: formatInboxTime(item) })
|
|
897
|
+
] }),
|
|
898
|
+
item.sessionId && item.chatAvailable ? /* @__PURE__ */ jsx3(
|
|
899
|
+
"button",
|
|
900
|
+
{
|
|
901
|
+
type: "button",
|
|
902
|
+
"aria-label": `Open chat session ${item.sessionId}`,
|
|
903
|
+
title: "Open chat session",
|
|
904
|
+
className: "grid size-6 shrink-0 place-items-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-foreground/[0.06] hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40 group-hover:opacity-100 group-focus-visible:opacity-100",
|
|
905
|
+
onClick: (event) => {
|
|
906
|
+
event.stopPropagation();
|
|
907
|
+
onOpenChat(item);
|
|
908
|
+
},
|
|
909
|
+
children: /* @__PURE__ */ jsx3(MessageSquare, { className: "size-3.5", strokeWidth: 1.75 })
|
|
910
|
+
}
|
|
911
|
+
) : null,
|
|
912
|
+
/* @__PURE__ */ jsx3(
|
|
913
|
+
"button",
|
|
914
|
+
{
|
|
915
|
+
type: "button",
|
|
916
|
+
"aria-label": `${item.pinned ? "Unpin" : "Pin"} ${item.title}`,
|
|
917
|
+
title: item.pinned ? "Unpin" : "Pin",
|
|
918
|
+
className: cn2(
|
|
919
|
+
"grid size-6 shrink-0 place-items-center rounded-md transition-colors hover:bg-foreground/[0.06] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40",
|
|
920
|
+
item.pinned ? "text-[color:var(--accent)] opacity-100" : "text-muted-foreground opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100"
|
|
921
|
+
),
|
|
922
|
+
onClick: (event) => {
|
|
923
|
+
event.stopPropagation();
|
|
924
|
+
onTogglePinned(item.id);
|
|
925
|
+
},
|
|
926
|
+
children: /* @__PURE__ */ jsx3(Star, { className: cn2("size-3.5", item.pinned && "fill-current"), strokeWidth: 1.75 })
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
]
|
|
930
|
+
}
|
|
931
|
+
) });
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/front/inbox/InboxSection.tsx
|
|
935
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
936
|
+
function InboxSection({
|
|
937
|
+
title,
|
|
938
|
+
items,
|
|
939
|
+
onTogglePinned,
|
|
940
|
+
onOpenArtifact,
|
|
941
|
+
onOpenChat
|
|
942
|
+
}) {
|
|
943
|
+
if (items.length === 0) return null;
|
|
944
|
+
return /* @__PURE__ */ jsxs4("section", { className: "py-1", children: [
|
|
945
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1 px-4 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/70", children: [
|
|
946
|
+
title === "Pinned" ? /* @__PURE__ */ jsx4(Pin, { className: "size-3", strokeWidth: 1.75 }) : null,
|
|
947
|
+
title
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsx4("ul", { role: "list", className: "divide-y divide-border/60 bg-card/80", children: items.map((item) => /* @__PURE__ */ jsx4(
|
|
950
|
+
InboxRow,
|
|
951
|
+
{
|
|
952
|
+
item,
|
|
953
|
+
onTogglePinned,
|
|
954
|
+
onOpenArtifact,
|
|
955
|
+
onOpenChat
|
|
956
|
+
},
|
|
957
|
+
item.id
|
|
958
|
+
)) })
|
|
959
|
+
] });
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// src/front/inbox/WorkspaceInboxShellContext.tsx
|
|
963
|
+
import { useMemo as useMemo2 } from "react";
|
|
964
|
+
import { useWorkspaceShellCapabilities } from "@hachej/boring-workspace";
|
|
965
|
+
function useWorkspaceInboxShell() {
|
|
966
|
+
const shell = useWorkspaceShellCapabilities();
|
|
967
|
+
return useMemo2(() => ({
|
|
968
|
+
openInboxArtifact: (item) => shell.openArtifact(item.artifact, {
|
|
969
|
+
// Row clicks open the workspace artifact/question only. The explicit chat
|
|
970
|
+
// icon owns chat opening; otherwise ask-user inbox rows jump to chat
|
|
971
|
+
// instead of showing the Questions form.
|
|
972
|
+
sessionId: null,
|
|
973
|
+
title: item.title,
|
|
974
|
+
instanceId: item.id
|
|
975
|
+
}),
|
|
976
|
+
openDetachedChat: shell.openDetachedChat
|
|
977
|
+
}), [shell]);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/front/inbox/InboxOverlay.tsx
|
|
981
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
982
|
+
function readPinnedIds(storageKey) {
|
|
983
|
+
if (!storageKey) return /* @__PURE__ */ new Set();
|
|
984
|
+
try {
|
|
985
|
+
const raw = globalThis.localStorage?.getItem(storageKey);
|
|
986
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
987
|
+
return new Set(Array.isArray(parsed?.ids) ? parsed.ids.filter((id) => typeof id === "string" && id.length > 0) : []);
|
|
988
|
+
} catch {
|
|
989
|
+
return /* @__PURE__ */ new Set();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function writePinnedIds(storageKey, ids) {
|
|
993
|
+
if (!storageKey) return;
|
|
994
|
+
try {
|
|
995
|
+
const values = Array.from(ids);
|
|
996
|
+
if (values.length === 0) globalThis.localStorage?.removeItem(storageKey);
|
|
997
|
+
else globalThis.localStorage?.setItem(storageKey, JSON.stringify({ ids: values }));
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
function InboxOverlay({ onClose, pinStorageKey }) {
|
|
1002
|
+
const { headerInsetStart, headerInsetEnd } = useAppLeftOverlayChrome();
|
|
1003
|
+
const { blockers } = useWorkspaceAttention2();
|
|
1004
|
+
const shell = useWorkspaceInboxShell();
|
|
1005
|
+
const [filter, setFilter] = useState2("all");
|
|
1006
|
+
const [shellError, setShellError] = useState2(null);
|
|
1007
|
+
const [selectedItemId, setSelectedItemId] = useState2(null);
|
|
1008
|
+
const [pinnedIds, setPinnedIds] = useState2(() => readPinnedIds(pinStorageKey));
|
|
1009
|
+
useEffect3(() => {
|
|
1010
|
+
setPinnedIds(readPinnedIds(pinStorageKey));
|
|
1011
|
+
}, [pinStorageKey]);
|
|
1012
|
+
const sorted = useMemo3(() => sortInboxItems(blockers.filter(isInboxAttentionBlocker).map(attentionBlockerToInboxItem)), [blockers]);
|
|
1013
|
+
const filtered = useMemo3(() => filterInboxItems(sorted, filter), [filter, sorted]);
|
|
1014
|
+
const items = useMemo3(() => mergeInboxPinnedState(filtered, pinnedIds), [filtered, pinnedIds]);
|
|
1015
|
+
const pinnedItems = useMemo3(() => items.filter((item) => item.pinned), [items]);
|
|
1016
|
+
const unpinnedItems = useMemo3(() => items.filter((item) => !item.pinned), [items]);
|
|
1017
|
+
const selectedItem = useMemo3(() => items.find((item) => item.id === selectedItemId) ?? null, [items, selectedItemId]);
|
|
1018
|
+
const selectedBlocker = useMemo3(() => selectedItem ? blockers.find((blocker) => blocker.id === selectedItem.id) : void 0, [blockers, selectedItem]);
|
|
1019
|
+
const counts = useMemo3(() => ({
|
|
1020
|
+
all: sorted.length,
|
|
1021
|
+
questions: filterInboxItems(sorted, "questions").length,
|
|
1022
|
+
reviews: filterInboxItems(sorted, "reviews").length
|
|
1023
|
+
}), [sorted]);
|
|
1024
|
+
const togglePinned = useCallback2((id) => {
|
|
1025
|
+
setPinnedIds((current) => {
|
|
1026
|
+
const next = new Set(current);
|
|
1027
|
+
if (next.has(id)) next.delete(id);
|
|
1028
|
+
else next.add(id);
|
|
1029
|
+
writePinnedIds(pinStorageKey, next);
|
|
1030
|
+
return next;
|
|
1031
|
+
});
|
|
1032
|
+
}, [pinStorageKey]);
|
|
1033
|
+
const handleShellResult = useCallback2((result) => {
|
|
1034
|
+
setShellError(result.success ? null : result.message);
|
|
1035
|
+
}, []);
|
|
1036
|
+
const openArtifact = useCallback2((item) => {
|
|
1037
|
+
if (!item.artifact) {
|
|
1038
|
+
setSelectedItemId(item.id);
|
|
1039
|
+
setShellError(null);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const result = shell.openInboxArtifact(item);
|
|
1043
|
+
handleShellResult(result);
|
|
1044
|
+
setSelectedItemId(result.success ? null : item.id);
|
|
1045
|
+
}, [handleShellResult, shell]);
|
|
1046
|
+
const openChat = useCallback2((item) => {
|
|
1047
|
+
if (!item.sessionId) return;
|
|
1048
|
+
handleShellResult(shell.openDetachedChat(item.sessionId, { title: item.title }));
|
|
1049
|
+
}, [handleShellResult, shell]);
|
|
1050
|
+
const handleDetailAction = useCallback2((actionId) => {
|
|
1051
|
+
if (!selectedItem || !selectedBlocker) return;
|
|
1052
|
+
emitWorkspaceAttentionAction({ blockerId: selectedBlocker.id, actionId, blocker: selectedBlocker, sessionId: selectedItem.sessionId ?? void 0 });
|
|
1053
|
+
if (actionId === "open") {
|
|
1054
|
+
if (selectedItem.artifact) openArtifact(selectedItem);
|
|
1055
|
+
else setShellError("This inbox item has no artifact target.");
|
|
1056
|
+
}
|
|
1057
|
+
}, [openArtifact, selectedBlocker, selectedItem]);
|
|
1058
|
+
return /* @__PURE__ */ jsxs5("div", { "data-boring-workspace-part": "inbox-overlay", className: "flex h-full min-h-0 flex-col bg-background", children: [
|
|
1059
|
+
/* @__PURE__ */ jsxs5("header", { className: cn3(
|
|
1060
|
+
"flex h-12 shrink-0 items-center justify-between border-b border-border/60",
|
|
1061
|
+
headerInsetStart ? "pl-12" : "pl-4",
|
|
1062
|
+
headerInsetEnd ? "pr-16" : "pr-4"
|
|
1063
|
+
), children: [
|
|
1064
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1065
|
+
/* @__PURE__ */ jsx5("span", { className: "grid size-7 place-items-center rounded-lg bg-[color:oklch(from_var(--accent)_l_c_h/0.14)] text-[color:var(--accent)]", children: /* @__PURE__ */ jsx5(Inbox, { className: "h-4 w-4", strokeWidth: 1.75, "aria-hidden": "true" }) }),
|
|
1066
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
1067
|
+
/* @__PURE__ */ jsx5("h2", { className: "truncate text-sm font-semibold tracking-tight text-foreground", children: "Inbox" }),
|
|
1068
|
+
/* @__PURE__ */ jsx5("p", { className: "truncate text-xs text-muted-foreground", children: "Questions, reviews, and owner decisions" })
|
|
1069
|
+
] })
|
|
1070
|
+
] }),
|
|
1071
|
+
/* @__PURE__ */ jsx5("div", { className: "flex shrink-0 items-center gap-0.5", children: /* @__PURE__ */ jsx5(IconButton, { type: "button", variant: "ghost", size: "icon-xs", onClick: onClose, "aria-label": "Close inbox", title: "Close", className: "text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx5(X, { className: "size-3", strokeWidth: 1.75 }) }) })
|
|
1072
|
+
] }),
|
|
1073
|
+
/* @__PURE__ */ jsx5(InboxFilterBar, { filter, counts, onFilterChange: setFilter }),
|
|
1074
|
+
shellError ? /* @__PURE__ */ jsx5("div", { className: "border-b border-border/60 bg-destructive/10 px-4 py-2 text-xs text-destructive", children: shellError }) : null,
|
|
1075
|
+
selectedItem ? /* @__PURE__ */ jsxs5("div", { className: "border-b border-border/60 bg-background px-4 py-3 text-xs", children: [
|
|
1076
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1077
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
|
|
1078
|
+
/* @__PURE__ */ jsx5("div", { className: "truncate font-medium text-foreground", children: selectedItem.title }),
|
|
1079
|
+
/* @__PURE__ */ jsx5("div", { className: "mt-1 line-clamp-2 text-muted-foreground", children: selectedItem.description })
|
|
1080
|
+
] }),
|
|
1081
|
+
/* @__PURE__ */ jsx5("button", { type: "button", className: "shrink-0 text-muted-foreground hover:text-foreground", onClick: () => setSelectedItemId(null), children: "Close" })
|
|
1082
|
+
] }),
|
|
1083
|
+
selectedItem.actions.length > 0 && selectedBlocker ? /* @__PURE__ */ jsx5("div", { className: "mt-3 flex flex-wrap gap-1.5", children: selectedItem.actions.map((action) => /* @__PURE__ */ jsx5(
|
|
1084
|
+
"button",
|
|
1085
|
+
{
|
|
1086
|
+
type: "button",
|
|
1087
|
+
className: "rounded-md border border-border/80 bg-background px-2 py-1 text-[11px] font-medium hover:bg-muted/60",
|
|
1088
|
+
onClick: () => handleDetailAction(action.id),
|
|
1089
|
+
children: action.label
|
|
1090
|
+
},
|
|
1091
|
+
action.id
|
|
1092
|
+
)) }) : null
|
|
1093
|
+
] }) : null,
|
|
1094
|
+
/* @__PURE__ */ jsx5("div", { className: "boring-scrollbar-discreet min-h-0 flex-1 overflow-y-auto bg-[color:oklch(from_var(--background)_calc(l-0.012)_c_h)] py-2", "aria-live": "polite", children: items.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "flex h-full min-h-[180px] items-center justify-center px-6 text-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsxs5("div", { children: [
|
|
1095
|
+
/* @__PURE__ */ jsx5(MailOpen, { className: "mx-auto mb-3 size-8 text-muted-foreground/70", strokeWidth: 1.75 }),
|
|
1096
|
+
/* @__PURE__ */ jsx5("div", { className: "font-medium text-foreground/80", children: "Inbox zero" }),
|
|
1097
|
+
/* @__PURE__ */ jsx5("p", { className: "mt-1 max-w-xs", children: "When plugins or external harnesses ask for a decision, it appears here." })
|
|
1098
|
+
] }) }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1099
|
+
/* @__PURE__ */ jsx5(InboxSection, { title: "Pinned", items: pinnedItems, onTogglePinned: togglePinned, onOpenArtifact: openArtifact, onOpenChat: openChat }),
|
|
1100
|
+
/* @__PURE__ */ jsx5(InboxSection, { title: "Inbox", items: unpinnedItems, onTogglePinned: togglePinned, onOpenArtifact: openArtifact, onOpenChat: openChat })
|
|
1101
|
+
] }) })
|
|
1102
|
+
] });
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/front/inbox/examples/inboxDemoPlugin.tsx
|
|
1106
|
+
import { useEffect as useEffect4 } from "react";
|
|
1107
|
+
import { WORKSPACE_OPEN_PATH_SURFACE_KIND, useWorkspaceAttention as useWorkspaceAttention3 } from "@hachej/boring-workspace";
|
|
1108
|
+
import { definePlugin } from "@hachej/boring-workspace/plugin";
|
|
1109
|
+
var INBOX_DEMO_SESSION_ID = "showcase";
|
|
1110
|
+
function createInboxDemoBlockers(now = Date.now()) {
|
|
1111
|
+
return [
|
|
1112
|
+
{
|
|
1113
|
+
id: "demo-question-deploy",
|
|
1114
|
+
reason: "ask-user.question",
|
|
1115
|
+
label: "Pick the deploy target for the release smoke",
|
|
1116
|
+
sessionId: INBOX_DEMO_SESSION_ID,
|
|
1117
|
+
pruneWhenSessionMissing: true,
|
|
1118
|
+
target: "README.md",
|
|
1119
|
+
surfaceKind: WORKSPACE_OPEN_PATH_SURFACE_KIND,
|
|
1120
|
+
inbox: {
|
|
1121
|
+
kind: "question",
|
|
1122
|
+
sourceLabel: "question",
|
|
1123
|
+
createdAt: now - 4 * 6e4,
|
|
1124
|
+
priority: 10
|
|
1125
|
+
},
|
|
1126
|
+
sessionBadge: { kind: "question", label: "question", tone: "attention", priority: 10 },
|
|
1127
|
+
actions: [{ id: "answer", label: "Answer" }, { id: "dismiss", label: "Dismiss" }]
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
id: "demo-review-ui",
|
|
1131
|
+
reason: "external-review.request",
|
|
1132
|
+
label: "Review Codex notes on workspace inbox flow",
|
|
1133
|
+
sessionId: "codex-42",
|
|
1134
|
+
target: "analysis.py",
|
|
1135
|
+
surfaceKind: WORKSPACE_OPEN_PATH_SURFACE_KIND,
|
|
1136
|
+
inbox: {
|
|
1137
|
+
kind: "review",
|
|
1138
|
+
sourceLabel: "review",
|
|
1139
|
+
createdAt: now - 2 * 60 * 6e4,
|
|
1140
|
+
priority: 8
|
|
1141
|
+
},
|
|
1142
|
+
sessionBadge: { kind: "review", label: "review", tone: "warning", priority: 8 },
|
|
1143
|
+
actions: [{ id: "open", label: "Open review" }]
|
|
1144
|
+
}
|
|
1145
|
+
];
|
|
1146
|
+
}
|
|
1147
|
+
function InboxDemoAttentionSeed() {
|
|
1148
|
+
const { addBlocker, removeBlocker } = useWorkspaceAttention3();
|
|
1149
|
+
useEffect4(() => {
|
|
1150
|
+
const blockers = createInboxDemoBlockers();
|
|
1151
|
+
for (const blocker of blockers) addBlocker(blocker);
|
|
1152
|
+
return () => blockers.forEach((blocker) => removeBlocker(blocker.id));
|
|
1153
|
+
}, [addBlocker, removeBlocker]);
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
var inboxDemoPlugin = definePlugin({
|
|
1157
|
+
id: "inbox-demo-playground",
|
|
1158
|
+
label: "Inbox Demo Playground",
|
|
1159
|
+
setup(api) {
|
|
1160
|
+
api.registerBinding({ id: "inbox-demo-attention-seed", component: InboxDemoAttentionSeed });
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
// src/front/index.tsx
|
|
1165
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
712
1166
|
function AskUserProvider({ apiBaseUrl, authHeaders, activeSessionId, openSessionIds, children }) {
|
|
713
|
-
const runtime =
|
|
1167
|
+
const runtime = useMemo4(() => ({
|
|
714
1168
|
...sharedQuestionsStore,
|
|
715
1169
|
apiBaseUrl,
|
|
716
1170
|
authHeaders,
|
|
@@ -728,11 +1182,14 @@ function AskUserProvider({ apiBaseUrl, authHeaders, activeSessionId, openSession
|
|
|
728
1182
|
useAskUserAttentionActions(runtime);
|
|
729
1183
|
useAskUserComposerStopCancel(runtime);
|
|
730
1184
|
useAskUserPendingRefresh(runtime, { activeSessionId, apiBaseUrl, authHeaders });
|
|
731
|
-
return /* @__PURE__ */
|
|
1185
|
+
return /* @__PURE__ */ jsx6(QuestionsRuntimeContext.Provider, { value: runtime, children });
|
|
732
1186
|
}
|
|
733
1187
|
function paneQuestionSessionId(runtime, params) {
|
|
734
1188
|
const activeSessionId = runtime.activeSessionId ?? null;
|
|
735
1189
|
if (activeSessionId && isPaneSessionVisible(runtime, activeSessionId) && hasReadyQuestion(runtime, activeSessionId)) return activeSessionId;
|
|
1190
|
+
if (params?.sessionId && hasReadyQuestion(runtime, params.sessionId)) return params.sessionId;
|
|
1191
|
+
const hintedSessionId = runtime.getPendingHints().find((hint) => !hint.status || hint.status === "ready")?.sessionId;
|
|
1192
|
+
if (hintedSessionId) return hintedSessionId;
|
|
736
1193
|
if (params?.sessionId && isPaneSessionVisible(runtime, params.sessionId)) return params.sessionId;
|
|
737
1194
|
return activeSessionId && isPaneSessionVisible(runtime, activeSessionId) ? activeSessionId : null;
|
|
738
1195
|
}
|
|
@@ -747,23 +1204,46 @@ function hasReadyQuestion(runtime, sessionId) {
|
|
|
747
1204
|
if (pending?.status === "ready") return true;
|
|
748
1205
|
return runtime.getPendingHints().some((hint) => hint.sessionId === sessionId && (!hint.status || hint.status === "ready"));
|
|
749
1206
|
}
|
|
1207
|
+
function InboxCountBadge() {
|
|
1208
|
+
const { blockers } = useWorkspaceAttention4();
|
|
1209
|
+
const count = blockers.filter(isInboxAttentionBlocker).length;
|
|
1210
|
+
if (count === 0) return null;
|
|
1211
|
+
const label2 = count > 99 ? "99+" : String(count);
|
|
1212
|
+
return /* @__PURE__ */ jsx6(
|
|
1213
|
+
"span",
|
|
1214
|
+
{
|
|
1215
|
+
"data-boring-workspace-part": "app-left-inbox-count",
|
|
1216
|
+
"aria-label": `${count} inbox item${count === 1 ? "" : "s"}`,
|
|
1217
|
+
className: "inline-flex min-w-5 items-center justify-center rounded-full bg-[color:var(--accent)] px-1.5 py-0.5 text-[10px] font-semibold leading-none text-white shadow-sm",
|
|
1218
|
+
children: label2
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
function AskUserInboxOverlay({ onClose }) {
|
|
1223
|
+
const { workspaceId } = useWorkspaceContext();
|
|
1224
|
+
return /* @__PURE__ */ jsx6(InboxOverlay, { onClose, pinStorageKey: `boring-workspace:inbox-pins:${workspaceId ?? "workspace"}` });
|
|
1225
|
+
}
|
|
750
1226
|
function QuestionsPane({ api, params, className }) {
|
|
751
1227
|
const runtime = useQuestionsRuntime();
|
|
1228
|
+
useSyncExternalStore(runtime.subscribe, () => pendingQuestionSnapshot(runtime), () => "none");
|
|
752
1229
|
const paneSessionId = paneQuestionSessionId(runtime, params);
|
|
753
1230
|
const pending = useSyncExternalStore(runtime.subscribe, () => runtime.getPending(paneSessionId), () => runtime.getPending(paneSessionId));
|
|
754
|
-
const [closedQuestionId, setClosedQuestionId] =
|
|
1231
|
+
const [closedQuestionId, setClosedQuestionId] = useState3(null);
|
|
755
1232
|
const retargetRefreshRef = useRef3(null);
|
|
756
|
-
const [error, setError] =
|
|
757
|
-
const [submitting, setSubmitting] =
|
|
1233
|
+
const [error, setError] = useState3(null);
|
|
1234
|
+
const [submitting, setSubmitting] = useState3(false);
|
|
758
1235
|
const question = pending?.questionId === closedQuestionId ? null : pending;
|
|
759
|
-
const client =
|
|
760
|
-
|
|
1236
|
+
const client = useMemo4(() => createQuestionsClient({ apiBaseUrl: runtime.apiBaseUrl, headers: runtime.authHeaders }), [runtime.apiBaseUrl, runtime.authHeaders]);
|
|
1237
|
+
useEffect5(() => {
|
|
761
1238
|
if (!params?.sessionId || !isPaneSessionKnownHidden(runtime, params.sessionId)) return;
|
|
1239
|
+
if (hasReadyQuestion(runtime, params.sessionId)) return;
|
|
1240
|
+
const hints = runtime.getPendingHints();
|
|
1241
|
+
if (hints.length === 0) return;
|
|
762
1242
|
const activeSessionId = runtime.activeSessionId ?? null;
|
|
763
1243
|
const canShowActiveQuestion = activeSessionId && isPaneSessionVisible(runtime, activeSessionId) && hasReadyQuestion(runtime, activeSessionId);
|
|
764
1244
|
if (!canShowActiveQuestion) api.close();
|
|
765
1245
|
}, [api, params?.sessionId, runtime]);
|
|
766
|
-
|
|
1246
|
+
useEffect5(() => {
|
|
767
1247
|
const onStop = (event) => {
|
|
768
1248
|
const detail = event.detail;
|
|
769
1249
|
if (!question || !workspaceComposerStopAppliesToSession2(detail, question.sessionId)) return;
|
|
@@ -774,7 +1254,7 @@ function QuestionsPane({ api, params, className }) {
|
|
|
774
1254
|
window.addEventListener(WORKSPACE_COMPOSER_STOP_EVENT2, onStop);
|
|
775
1255
|
return () => window.removeEventListener(WORKSPACE_COMPOSER_STOP_EVENT2, onStop);
|
|
776
1256
|
}, [api, question, runtime]);
|
|
777
|
-
|
|
1257
|
+
useEffect5(() => {
|
|
778
1258
|
if (!paneSessionId) return;
|
|
779
1259
|
const targetQuestionId = params?.questionId;
|
|
780
1260
|
if (!pending) {
|
|
@@ -791,13 +1271,13 @@ function QuestionsPane({ api, params, className }) {
|
|
|
791
1271
|
retargetRefreshRef.current = refreshKey;
|
|
792
1272
|
void runtime.refreshPending(paneSessionId).catch(() => void 0);
|
|
793
1273
|
}, [paneSessionId, params?.questionId, pending, runtime]);
|
|
794
|
-
return /* @__PURE__ */
|
|
795
|
-
/* @__PURE__ */
|
|
796
|
-
/* @__PURE__ */
|
|
1274
|
+
return /* @__PURE__ */ jsx6("div", { className: className ? `${className} min-h-0 overflow-hidden` : "h-full min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxs6(Pane, { className: "h-full min-h-0 overflow-hidden border-0 bg-background text-sm", children: [
|
|
1275
|
+
/* @__PURE__ */ jsx6(PaneHeader, { className: "border-b bg-background/95", children: /* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsxs6(PaneTitle, { className: "flex items-center gap-2", children: [
|
|
1276
|
+
/* @__PURE__ */ jsx6(HelpCircle, { className: "h-4 w-4 text-muted-foreground" }),
|
|
797
1277
|
" Agent needs input"
|
|
798
1278
|
] }) }) }),
|
|
799
|
-
!question ? /* @__PURE__ */
|
|
800
|
-
question?.status === "ready" && question.schema ? /* @__PURE__ */
|
|
1279
|
+
!question ? /* @__PURE__ */ jsx6(PaneBody, { className: "overflow-auto p-4", children: /* @__PURE__ */ jsx6(EmptyState, { icon: /* @__PURE__ */ jsx6(HelpCircle, { className: "h-5 w-5" }), title: "No pending questions", description: "When the agent needs a decision, the form will appear here.", className: "border border-dashed bg-muted/20" }) }) : null,
|
|
1280
|
+
question?.status === "ready" && question.schema ? /* @__PURE__ */ jsx6(QuestionFormProvider, { schema: question.schema, submitting, onSubmit: async (values) => {
|
|
801
1281
|
setSubmitting(true);
|
|
802
1282
|
setError(null);
|
|
803
1283
|
try {
|
|
@@ -825,72 +1305,89 @@ function QuestionsPane({ api, params, className }) {
|
|
|
825
1305
|
} finally {
|
|
826
1306
|
setSubmitting(false);
|
|
827
1307
|
}
|
|
828
|
-
}, children: /* @__PURE__ */
|
|
829
|
-
/* @__PURE__ */
|
|
830
|
-
/* @__PURE__ */
|
|
831
|
-
/* @__PURE__ */
|
|
832
|
-
/* @__PURE__ */
|
|
833
|
-
question.context ? /* @__PURE__ */
|
|
1308
|
+
}, children: /* @__PURE__ */ jsxs6(QuestionForm, { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1309
|
+
/* @__PURE__ */ jsx6(PaneBody, { className: "overflow-auto p-4", children: /* @__PURE__ */ jsxs6("div", { className: "space-y-4", children: [
|
|
1310
|
+
/* @__PURE__ */ jsxs6("section", { className: "rounded-md border border-border/60 bg-muted/30 p-4", children: [
|
|
1311
|
+
/* @__PURE__ */ jsx6("div", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Waiting for answer" }),
|
|
1312
|
+
/* @__PURE__ */ jsx6("h2", { className: "mt-2 text-balance text-sm font-semibold leading-5 text-foreground", children: question.title ?? "Question" }),
|
|
1313
|
+
question.context ? /* @__PURE__ */ jsx6("p", { className: "mt-2 max-w-prose text-sm leading-6 text-muted-foreground", children: question.context }) : null
|
|
834
1314
|
] }),
|
|
835
|
-
/* @__PURE__ */
|
|
836
|
-
error ? /* @__PURE__ */
|
|
1315
|
+
/* @__PURE__ */ jsx6("div", { className: "space-y-4", children: /* @__PURE__ */ jsx6(QuestionFields, {}) }),
|
|
1316
|
+
error ? /* @__PURE__ */ jsx6(Notice, { tone: "destructive", role: "alert", children: error }) : null
|
|
837
1317
|
] }) }),
|
|
838
|
-
/* @__PURE__ */
|
|
839
|
-
/* @__PURE__ */
|
|
840
|
-
/* @__PURE__ */
|
|
841
|
-
/* @__PURE__ */
|
|
842
|
-
/* @__PURE__ */
|
|
1318
|
+
/* @__PURE__ */ jsxs6(PaneFooter, { className: "justify-between border-t bg-background px-4 py-3", children: [
|
|
1319
|
+
/* @__PURE__ */ jsx6("p", { className: "min-w-0 text-xs text-muted-foreground", children: "Sends answers and closes the pane." }),
|
|
1320
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex gap-2", children: [
|
|
1321
|
+
/* @__PURE__ */ jsx6(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx6(QuestionCancelButton, { children: "Cancel" }) }),
|
|
1322
|
+
/* @__PURE__ */ jsx6(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx6(QuestionSubmitButton, { children: question.schema.submitLabel ?? "Send answers" }) })
|
|
843
1323
|
] })
|
|
844
1324
|
] })
|
|
845
1325
|
] }) }, question.questionId) : null,
|
|
846
|
-
question && question.status !== "ready" ? /* @__PURE__ */
|
|
847
|
-
/* @__PURE__ */
|
|
1326
|
+
question && question.status !== "ready" ? /* @__PURE__ */ jsx6(PaneBody, { className: "p-5", children: /* @__PURE__ */ jsx6(Notice, { children: /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-2", children: [
|
|
1327
|
+
/* @__PURE__ */ jsx6(XCircle, { className: "h-4 w-4 text-muted-foreground" }),
|
|
848
1328
|
"Question ",
|
|
849
1329
|
question.status
|
|
850
1330
|
] }) }) }) : null
|
|
851
1331
|
] }) });
|
|
852
1332
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
],
|
|
862
|
-
panels: [
|
|
863
|
-
{
|
|
864
|
-
id: ASK_USER_PANEL_ID,
|
|
865
|
-
label: ASK_USER_PANEL_TITLE,
|
|
866
|
-
icon: HelpCircle,
|
|
867
|
-
component: QuestionsPane,
|
|
868
|
-
placement: "center",
|
|
869
|
-
source: "builtin",
|
|
870
|
-
chromeless: true
|
|
871
|
-
}
|
|
872
|
-
],
|
|
873
|
-
surfaceResolvers: [
|
|
874
|
-
{
|
|
875
|
-
id: `${ASK_USER_PLUGIN_ID}.surface`,
|
|
876
|
-
kind: ASK_USER_SURFACE_KIND,
|
|
877
|
-
source: "builtin",
|
|
878
|
-
// No inner kind guard — the workspace's surface registry already
|
|
879
|
-
// pre-filters by the top-level `kind` field before calling resolve.
|
|
880
|
-
resolve(request) {
|
|
881
|
-
const sessionId = typeof request.meta === "object" && request.meta && typeof request.meta.sessionId === "string" ? request.meta.sessionId : void 0;
|
|
882
|
-
return {
|
|
883
|
-
component: ASK_USER_PANEL_ID,
|
|
884
|
-
id: ASK_USER_PANEL_ID,
|
|
885
|
-
title: ASK_USER_PANEL_TITLE,
|
|
886
|
-
params: { questionId: request.target, sessionId }
|
|
887
|
-
};
|
|
1333
|
+
function createAskUserPlugin(options = {}) {
|
|
1334
|
+
return definePlugin2({
|
|
1335
|
+
id: ASK_USER_PLUGIN_ID,
|
|
1336
|
+
label: ASK_USER_PANEL_TITLE,
|
|
1337
|
+
providers: [
|
|
1338
|
+
{
|
|
1339
|
+
id: `${ASK_USER_PLUGIN_ID}.provider`,
|
|
1340
|
+
component: AskUserProvider
|
|
888
1341
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1342
|
+
],
|
|
1343
|
+
panels: [
|
|
1344
|
+
{
|
|
1345
|
+
id: ASK_USER_PANEL_ID,
|
|
1346
|
+
label: ASK_USER_PANEL_TITLE,
|
|
1347
|
+
icon: HelpCircle,
|
|
1348
|
+
component: QuestionsPane,
|
|
1349
|
+
placement: "center",
|
|
1350
|
+
source: "builtin",
|
|
1351
|
+
chromeless: true
|
|
1352
|
+
}
|
|
1353
|
+
],
|
|
1354
|
+
appLeftActions: options.appLeftInbox ? [
|
|
1355
|
+
{
|
|
1356
|
+
id: "inbox",
|
|
1357
|
+
label: "Inbox",
|
|
1358
|
+
icon: Inbox2,
|
|
1359
|
+
trailing: InboxCountBadge,
|
|
1360
|
+
overlay: AskUserInboxOverlay,
|
|
1361
|
+
order: 10
|
|
1362
|
+
}
|
|
1363
|
+
] : [],
|
|
1364
|
+
surfaceResolvers: [
|
|
1365
|
+
{
|
|
1366
|
+
id: `${ASK_USER_PLUGIN_ID}.surface`,
|
|
1367
|
+
kind: ASK_USER_SURFACE_KIND,
|
|
1368
|
+
source: "builtin",
|
|
1369
|
+
// No inner kind guard — the workspace's surface registry already
|
|
1370
|
+
// pre-filters by the top-level `kind` field before calling resolve.
|
|
1371
|
+
resolve(request) {
|
|
1372
|
+
const sessionId = typeof request.meta === "object" && request.meta && typeof request.meta.sessionId === "string" ? request.meta.sessionId : void 0;
|
|
1373
|
+
return {
|
|
1374
|
+
component: ASK_USER_PANEL_ID,
|
|
1375
|
+
id: ASK_USER_PANEL_ID,
|
|
1376
|
+
title: ASK_USER_PANEL_TITLE,
|
|
1377
|
+
params: { questionId: request.target, sessionId }
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
]
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
var askUserPlugin = createAskUserPlugin();
|
|
892
1385
|
var front_default = askUserPlugin;
|
|
893
1386
|
export {
|
|
1387
|
+
INBOX_DEMO_SESSION_ID,
|
|
894
1388
|
askUserPlugin,
|
|
895
|
-
|
|
1389
|
+
createAskUserPlugin,
|
|
1390
|
+
createInboxDemoBlockers,
|
|
1391
|
+
front_default as default,
|
|
1392
|
+
inboxDemoPlugin
|
|
896
1393
|
};
|
package/dist/server/index.js
CHANGED
|
@@ -358,6 +358,7 @@ var AskUserRuntime = class {
|
|
|
358
358
|
}
|
|
359
359
|
async ask(request, signal) {
|
|
360
360
|
const ownerPrincipalId = request.ownerPrincipalId ?? this.ownerPrincipalId;
|
|
361
|
+
await this.abandonOrphanedPending([request.sessionId]);
|
|
361
362
|
this.assertAllowed(request.sessionId, ownerPrincipalId);
|
|
362
363
|
const question = this.createQuestion({ ...request, ownerPrincipalId });
|
|
363
364
|
const parsed = AskUserFormSchemaSchema.safeParse(request.schema);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-ask-user",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,28 +46,28 @@
|
|
|
46
46
|
"fastify": "^5.3.3",
|
|
47
47
|
"react": "^18.0.0 || ^19.0.0",
|
|
48
48
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
49
|
-
"@hachej/boring-workspace": "0.1.
|
|
49
|
+
"@hachej/boring-workspace": "0.1.65"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"lucide-react": "^1.
|
|
52
|
+
"lucide-react": "^1.21.0",
|
|
53
53
|
"zod": "^3.23.0",
|
|
54
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
54
|
+
"@hachej/boring-ui-kit": "0.1.65"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@testing-library/jest-dom": "^6.9.1",
|
|
58
58
|
"@testing-library/react": "^16.3.0",
|
|
59
|
-
"@types/node": "^22.
|
|
60
|
-
"@types/react": "^19.
|
|
59
|
+
"@types/node": "^22.20.0",
|
|
60
|
+
"@types/react": "^19.2.17",
|
|
61
61
|
"@types/react-dom": "^19.0.0",
|
|
62
62
|
"@vitejs/plugin-react": "^4.0.0",
|
|
63
|
-
"fastify": "^5.
|
|
64
|
-
"jsdom": "^29.
|
|
65
|
-
"react": "^19.
|
|
66
|
-
"react-dom": "^19.
|
|
63
|
+
"fastify": "^5.9.0",
|
|
64
|
+
"jsdom": "^29.1.1",
|
|
65
|
+
"react": "^19.2.7",
|
|
66
|
+
"react-dom": "^19.2.7",
|
|
67
67
|
"tsup": "^8.4.0",
|
|
68
|
-
"typescript": "~
|
|
69
|
-
"vitest": "^
|
|
70
|
-
"@hachej/boring-workspace": "0.1.
|
|
68
|
+
"typescript": "~6.0.3",
|
|
69
|
+
"vitest": "^4.1.9",
|
|
70
|
+
"@hachej/boring-workspace": "0.1.65"
|
|
71
71
|
},
|
|
72
72
|
"peerDependenciesMeta": {
|
|
73
73
|
"fastify": {
|