@schlessera/brain-ui-react 0.34.0 → 0.35.0
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/README.md +12 -0
- package/dist/components/activity/activity-page.d.ts.map +1 -1
- package/dist/components/activity/activity-page.js +8 -149
- package/dist/components/activity/activity-page.js.map +1 -1
- package/dist/components/activity/activity-rollups.d.ts +5 -0
- package/dist/components/activity/activity-rollups.d.ts.map +1 -0
- package/dist/components/activity/activity-rollups.js +39 -0
- package/dist/components/activity/activity-rollups.js.map +1 -0
- package/dist/components/activity/activity-run-detail.d.ts +11 -0
- package/dist/components/activity/activity-run-detail.d.ts.map +1 -0
- package/dist/components/activity/activity-run-detail.js +111 -0
- package/dist/components/activity/activity-run-detail.js.map +1 -0
- package/dist/components/activity/activity-run-list.d.ts +15 -0
- package/dist/components/activity/activity-run-list.d.ts.map +1 -0
- package/dist/components/activity/activity-run-list.js +25 -0
- package/dist/components/activity/activity-run-list.js.map +1 -0
- package/dist/components/chat/brain-markdown-code.d.ts +4 -0
- package/dist/components/chat/brain-markdown-code.d.ts.map +1 -0
- package/dist/components/chat/brain-markdown-code.js +47 -0
- package/dist/components/chat/brain-markdown-code.js.map +1 -0
- package/dist/components/chat/brain-markdown-entities.d.ts +14 -0
- package/dist/components/chat/brain-markdown-entities.d.ts.map +1 -0
- package/dist/components/chat/brain-markdown-entities.js +93 -0
- package/dist/components/chat/brain-markdown-entities.js.map +1 -0
- package/dist/components/chat/brain-markdown-links.d.ts +36 -0
- package/dist/components/chat/brain-markdown-links.d.ts.map +1 -0
- package/dist/components/chat/brain-markdown-links.js +176 -0
- package/dist/components/chat/brain-markdown-links.js.map +1 -0
- package/dist/components/chat/brain-markdown-share.d.ts +15 -0
- package/dist/components/chat/brain-markdown-share.d.ts.map +1 -0
- package/dist/components/chat/brain-markdown-share.js +40 -0
- package/dist/components/chat/brain-markdown-share.js.map +1 -0
- package/dist/components/chat/brain-markdown.d.ts +1 -34
- package/dist/components/chat/brain-markdown.d.ts.map +1 -1
- package/dist/components/chat/brain-markdown.js +9 -402
- package/dist/components/chat/brain-markdown.js.map +1 -1
- package/dist/components/chat/composer.d.ts.map +1 -1
- package/dist/components/chat/composer.js +14 -1
- package/dist/components/chat/composer.js.map +1 -1
- package/dist/components/chat/session-drawer.d.ts.map +1 -1
- package/dist/components/chat/session-drawer.js +25 -10
- package/dist/components/chat/session-drawer.js.map +1 -1
- package/dist/components/chat/tool-views.d.ts +2 -2
- package/dist/components/chat/tool-views.d.ts.map +1 -1
- package/dist/components/chat/tool-views.js +2 -16
- package/dist/components/chat/tool-views.js.map +1 -1
- package/dist/components/chat/use-markdown-highlight.d.ts +12 -0
- package/dist/components/chat/use-markdown-highlight.d.ts.map +1 -0
- package/dist/components/chat/use-markdown-highlight.js +56 -0
- package/dist/components/chat/use-markdown-highlight.js.map +1 -0
- package/dist/components/connectivity/connection-gate.d.ts.map +1 -1
- package/dist/components/connectivity/connection-gate.js +94 -8
- package/dist/components/connectivity/connection-gate.js.map +1 -1
- package/dist/components/connectivity/connection-state.d.ts +12 -0
- package/dist/components/connectivity/connection-state.d.ts.map +1 -0
- package/dist/components/connectivity/connection-state.js +23 -0
- package/dist/components/connectivity/connection-state.js.map +1 -0
- package/dist/components/graph/graph-canvas-lazy.d.ts +2 -0
- package/dist/components/graph/graph-canvas-lazy.d.ts.map +1 -0
- package/dist/components/graph/graph-canvas-lazy.js +4 -0
- package/dist/components/graph/graph-canvas-lazy.js.map +1 -0
- package/dist/components/graph/graph-controls.js +4 -1
- package/dist/components/graph/graph-controls.js.map +1 -1
- package/dist/components/graph/graph-maintenance.d.ts +8 -0
- package/dist/components/graph/graph-maintenance.d.ts.map +1 -0
- package/dist/components/graph/graph-maintenance.js +115 -0
- package/dist/components/graph/graph-maintenance.js.map +1 -0
- package/dist/components/graph/graph-page.d.ts.map +1 -1
- package/dist/components/graph/graph-page.js +11 -306
- package/dist/components/graph/graph-page.js.map +1 -1
- package/dist/components/graph/graph-scene.d.ts +8 -0
- package/dist/components/graph/graph-scene.d.ts.map +1 -0
- package/dist/components/graph/graph-scene.js +208 -0
- package/dist/components/graph/graph-scene.js.map +1 -0
- package/dist/components/graph/graph-spinner.d.ts +2 -0
- package/dist/components/graph/graph-spinner.d.ts.map +1 -0
- package/dist/components/graph/graph-spinner.js +6 -0
- package/dist/components/graph/graph-spinner.js.map +1 -0
- package/dist/components/layout/app-shell.d.ts.map +1 -1
- package/dist/components/layout/app-shell.js +2 -1
- package/dist/components/layout/app-shell.js.map +1 -1
- package/dist/components/quick-actions/add-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/add-modal.js +47 -4
- package/dist/components/quick-actions/add-modal.js.map +1 -1
- package/dist/components/quick-actions/search-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/search-modal.js +46 -18
- package/dist/components/quick-actions/search-modal.js.map +1 -1
- package/dist/components/quick-actions/streaming-modal.d.ts.map +1 -1
- package/dist/components/quick-actions/streaming-modal.js +4 -1
- package/dist/components/quick-actions/streaming-modal.js.map +1 -1
- package/dist/components/settings/devices-agents-tab.d.ts +5 -0
- package/dist/components/settings/devices-agents-tab.d.ts.map +1 -0
- package/dist/components/settings/devices-agents-tab.js +96 -0
- package/dist/components/settings/devices-agents-tab.js.map +1 -0
- package/dist/components/settings/one-time-agent-credential.d.ts +3 -0
- package/dist/components/settings/one-time-agent-credential.d.ts.map +1 -0
- package/dist/components/settings/one-time-agent-credential.js +55 -0
- package/dist/components/settings/one-time-agent-credential.js.map +1 -0
- package/dist/components/settings/settings-panel.d.ts.map +1 -1
- package/dist/components/settings/settings-panel.js +16 -3
- package/dist/components/settings/settings-panel.js.map +1 -1
- package/dist/hooks/use-vpn-status.d.ts +6 -1
- package/dist/hooks/use-vpn-status.d.ts.map +1 -1
- package/dist/hooks/use-vpn-status.js +67 -30
- package/dist/hooks/use-vpn-status.js.map +1 -1
- package/dist/hooks/use-websocket.d.ts +3 -2
- package/dist/hooks/use-websocket.d.ts.map +1 -1
- package/dist/hooks/use-websocket.js +65 -254
- package/dist/hooks/use-websocket.js.map +1 -1
- package/dist/hooks/websocket-handlers/activity.d.ts +5 -0
- package/dist/hooks/websocket-handlers/activity.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/activity.js +10 -0
- package/dist/hooks/websocket-handlers/activity.js.map +1 -0
- package/dist/hooks/websocket-handlers/chat.d.ts +17 -0
- package/dist/hooks/websocket-handlers/chat.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/chat.js +175 -0
- package/dist/hooks/websocket-handlers/chat.js.map +1 -0
- package/dist/hooks/websocket-handlers/connection.d.ts +6 -0
- package/dist/hooks/websocket-handlers/connection.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/connection.js +27 -0
- package/dist/hooks/websocket-handlers/connection.js.map +1 -0
- package/dist/hooks/websocket-handlers/index.d.ts +4 -0
- package/dist/hooks/websocket-handlers/index.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/index.js +19 -0
- package/dist/hooks/websocket-handlers/index.js.map +1 -0
- package/dist/hooks/websocket-handlers/mask.d.ts +4 -0
- package/dist/hooks/websocket-handlers/mask.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/mask.js +14 -0
- package/dist/hooks/websocket-handlers/mask.js.map +1 -0
- package/dist/hooks/websocket-handlers/provider.d.ts +4 -0
- package/dist/hooks/websocket-handlers/provider.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/provider.js +31 -0
- package/dist/hooks/websocket-handlers/provider.js.map +1 -0
- package/dist/hooks/websocket-handlers/types.d.ts +24 -0
- package/dist/hooks/websocket-handlers/types.d.ts.map +1 -0
- package/dist/hooks/websocket-handlers/types.js +2 -0
- package/dist/hooks/websocket-handlers/types.js.map +1 -0
- package/dist/lib/api-client.d.ts +32 -0
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +11 -0
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/format-time.d.ts +3 -0
- package/dist/lib/format-time.d.ts.map +1 -0
- package/dist/lib/format-time.js +25 -0
- package/dist/lib/format-time.js.map +1 -0
- package/dist/lib/push-registration.d.ts +7 -0
- package/dist/lib/push-registration.d.ts.map +1 -0
- package/dist/lib/push-registration.js +26 -0
- package/dist/lib/push-registration.js.map +1 -0
- package/dist/stores/activity-store-helpers.d.ts +50 -0
- package/dist/stores/activity-store-helpers.d.ts.map +1 -0
- package/dist/stores/activity-store-helpers.js +143 -0
- package/dist/stores/activity-store-helpers.js.map +1 -0
- package/dist/stores/activity-store-types.d.ts +48 -0
- package/dist/stores/activity-store-types.d.ts.map +1 -0
- package/dist/stores/activity-store-types.js +2 -0
- package/dist/stores/activity-store-types.js.map +1 -0
- package/dist/stores/activity-store.d.ts +2 -78
- package/dist/stores/activity-store.d.ts.map +1 -1
- package/dist/stores/activity-store.js +22 -142
- package/dist/stores/activity-store.js.map +1 -1
- package/dist/stores/connection-store.d.ts +14 -2
- package/dist/stores/connection-store.d.ts.map +1 -1
- package/dist/stores/connection-store.js +17 -0
- package/dist/stores/connection-store.js.map +1 -1
- package/dist/stores/file-store.d.ts.map +1 -1
- package/dist/stores/file-store.js +54 -18
- package/dist/stores/file-store.js.map +1 -1
- package/dist/stores/principal-store.d.ts +16 -0
- package/dist/stores/principal-store.d.ts.map +1 -0
- package/dist/stores/principal-store.js +29 -0
- package/dist/stores/principal-store.js.map +1 -0
- package/dist/stores/ui-store.d.ts +1 -1
- package/dist/stores/ui-store.d.ts.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/components/activity/activity-page.tsx +6 -477
- package/src/components/activity/activity-rollups.tsx +94 -0
- package/src/components/activity/activity-run-detail.tsx +301 -0
- package/src/components/activity/activity-run-list.tsx +97 -0
- package/src/components/chat/brain-markdown-code.tsx +60 -0
- package/src/components/chat/brain-markdown-entities.tsx +116 -0
- package/src/components/chat/brain-markdown-links.tsx +247 -0
- package/src/components/chat/brain-markdown-share.ts +47 -0
- package/src/components/chat/brain-markdown.tsx +27 -535
- package/src/components/chat/composer.tsx +14 -1
- package/src/components/chat/session-drawer.tsx +29 -10
- package/src/components/chat/tool-views.tsx +2 -13
- package/src/components/chat/use-markdown-highlight.ts +63 -0
- package/src/components/connectivity/connection-gate.tsx +128 -14
- package/src/components/connectivity/connection-state.ts +37 -0
- package/src/components/graph/graph-canvas-lazy.tsx +4 -0
- package/src/components/graph/graph-controls.tsx +4 -1
- package/src/components/graph/graph-maintenance.tsx +304 -0
- package/src/components/graph/graph-page.tsx +11 -840
- package/src/components/graph/graph-scene.tsx +548 -0
- package/src/components/graph/graph-spinner.tsx +9 -0
- package/src/components/layout/app-shell.tsx +2 -0
- package/src/components/quick-actions/add-modal.tsx +49 -4
- package/src/components/quick-actions/search-modal.tsx +46 -18
- package/src/components/quick-actions/streaming-modal.tsx +3 -1
- package/src/components/settings/devices-agents-tab.tsx +231 -0
- package/src/components/settings/one-time-agent-credential.tsx +131 -0
- package/src/components/settings/settings-panel.tsx +20 -5
- package/src/hooks/use-vpn-status.ts +71 -31
- package/src/hooks/use-websocket.ts +74 -275
- package/src/hooks/websocket-handlers/activity.ts +13 -0
- package/src/hooks/websocket-handlers/chat.ts +210 -0
- package/src/hooks/websocket-handlers/connection.ts +30 -0
- package/src/hooks/websocket-handlers/index.ts +59 -0
- package/src/hooks/websocket-handlers/mask.ts +17 -0
- package/src/hooks/websocket-handlers/provider.ts +35 -0
- package/src/hooks/websocket-handlers/types.ts +27 -0
- package/src/lib/api-client.ts +40 -1
- package/src/lib/format-time.ts +20 -0
- package/src/lib/push-registration.ts +29 -0
- package/src/stores/activity-store-helpers.ts +164 -0
- package/src/stores/activity-store-types.ts +55 -0
- package/src/stores/activity-store.ts +21 -217
- package/src/stores/connection-store.ts +33 -2
- package/src/stores/file-store.ts +42 -12
- package/src/stores/principal-store.ts +38 -0
- package/src/stores/ui-store.ts +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivitySpan,
|
|
3
|
+
ActivitySpanEvent,
|
|
4
|
+
ServerActivityDelta,
|
|
5
|
+
ServerActivitySnapshot,
|
|
6
|
+
} from "@schlessera/brain-ui-sdk/protocol";
|
|
7
|
+
import type { ActivityIntent } from "../lib/api-client.js";
|
|
8
|
+
|
|
9
|
+
export interface ActivityState {
|
|
10
|
+
/** server_hello said this host records activity. */
|
|
11
|
+
supported: boolean;
|
|
12
|
+
/** Session ids this connection has an activity subscription for. */
|
|
13
|
+
subscribed: Record<string, true>;
|
|
14
|
+
/** Bumped on every new connection so subscribe effects re-send. */
|
|
15
|
+
connectionEpoch: number;
|
|
16
|
+
/** Live span state per run, keyed runId -> spanId. */
|
|
17
|
+
spans: Record<string, Record<string, ActivitySpan>>;
|
|
18
|
+
/** Ordered events per span (already sorted by eventIndex). */
|
|
19
|
+
events: Record<string, ActivitySpanEvent[]>;
|
|
20
|
+
/** Per-run high-water seq from the snapshot; deltas at or below drop. */
|
|
21
|
+
highWater: Record<string, number>;
|
|
22
|
+
/**
|
|
23
|
+
* Newest APPLIED delta seq per run. Kept apart from `highWater` on
|
|
24
|
+
* purpose: the snapshot staleness guard compares against the max of both
|
|
25
|
+
* (a snapshot older than an applied delta must not roll it back), while
|
|
26
|
+
* the delta gate keeps comparing against the snapshot floor alone so
|
|
27
|
+
* out-of-order delta delivery stays tolerated (insert-if-absent absorbs it).
|
|
28
|
+
*/
|
|
29
|
+
deltaSeq: Record<string, number>;
|
|
30
|
+
/** spanId -> runId reverse index, for timing lookups by toolUseId. */
|
|
31
|
+
spanRun: Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/** Unacknowledged notification intents — the guaranteed-tier inbox. */
|
|
34
|
+
inbox: ActivityIntent[];
|
|
35
|
+
loadInbox: () => Promise<void>;
|
|
36
|
+
acknowledgeIntent: (id: number) => Promise<void>;
|
|
37
|
+
acknowledgeAllIntents: () => Promise<void>;
|
|
38
|
+
setSupported: (supported: boolean) => void;
|
|
39
|
+
/** New connection: server-side subscriptions are gone; re-subscribe lazily. */
|
|
40
|
+
resetSubscriptions: () => void;
|
|
41
|
+
bumpConnectionEpoch: () => void;
|
|
42
|
+
markSubscribed: (sessionId: string) => void;
|
|
43
|
+
applySnapshot: (msg: ServerActivitySnapshot) => void;
|
|
44
|
+
applyDelta: (msg: ServerActivityDelta) => void;
|
|
45
|
+
clear: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The five indexes that make up the mirror, mutated together during merges. */
|
|
49
|
+
export interface MirrorMaps {
|
|
50
|
+
spans: Record<string, Record<string, ActivitySpan>>;
|
|
51
|
+
events: Record<string, ActivitySpanEvent[]>;
|
|
52
|
+
highWater: Record<string, number>;
|
|
53
|
+
deltaSeq: Record<string, number>;
|
|
54
|
+
spanRun: Record<string, string>;
|
|
55
|
+
}
|
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
-
import
|
|
3
|
-
ActivitySpan,
|
|
4
|
-
ActivitySpanEvent,
|
|
5
|
-
ServerActivityDelta,
|
|
6
|
-
ServerActivitySnapshot,
|
|
7
|
-
} from "@schlessera/brain-ui-sdk/protocol";
|
|
8
|
-
import { api, type ActivityIntent } from "../lib/api-client.js";
|
|
2
|
+
import { api } from "../lib/api-client.js";
|
|
9
3
|
import { registerDevHandle } from "../config.js";
|
|
4
|
+
import type { ActivityState, MirrorMaps } from "./activity-store-types.js";
|
|
5
|
+
import {
|
|
6
|
+
evictRun,
|
|
7
|
+
insertEvent,
|
|
8
|
+
pruneTerminalRuns,
|
|
9
|
+
rootSpanOf,
|
|
10
|
+
} from "./activity-store-helpers.js";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
EMPTY_EVENTS,
|
|
14
|
+
childSpans,
|
|
15
|
+
eventsFor,
|
|
16
|
+
narrativeEventsFor,
|
|
17
|
+
payloadEventsFor,
|
|
18
|
+
runEvents,
|
|
19
|
+
runSpans,
|
|
20
|
+
spanForTool,
|
|
21
|
+
subagentSpans,
|
|
22
|
+
timingFor,
|
|
23
|
+
} from "./activity-store-helpers.js";
|
|
10
24
|
|
|
11
25
|
/** How many finished runs' details are fetched when a session opens. */
|
|
12
26
|
const HISTORY_RUN_LIMIT = 10;
|
|
13
27
|
|
|
14
|
-
/** Bound on the mirror: how many TERMINAL runs stay held. Live runs never count. */
|
|
15
|
-
const MAX_TERMINAL_RUNS = 50;
|
|
16
|
-
|
|
17
28
|
/**
|
|
18
29
|
* Client mirror of the server's activity record, fed by the snapshot-then-
|
|
19
30
|
* delta stream. The server computes; this store only holds and indexes what
|
|
@@ -34,45 +45,6 @@ const MAX_TERMINAL_RUNS = 50;
|
|
|
34
45
|
* evicted wholesale (spans, events, high-water, reverse index). Live runs
|
|
35
46
|
* are never evicted; evicted history remains fetchable over REST.
|
|
36
47
|
*/
|
|
37
|
-
interface ActivityState {
|
|
38
|
-
/** server_hello said this host records activity. */
|
|
39
|
-
supported: boolean;
|
|
40
|
-
/** Session ids this connection has an activity subscription for. */
|
|
41
|
-
subscribed: Record<string, true>;
|
|
42
|
-
/** Bumped on every new connection so subscribe effects re-send. */
|
|
43
|
-
connectionEpoch: number;
|
|
44
|
-
/** Live span state per run, keyed runId -> spanId. */
|
|
45
|
-
spans: Record<string, Record<string, ActivitySpan>>;
|
|
46
|
-
/** Ordered events per span (already sorted by eventIndex). */
|
|
47
|
-
events: Record<string, ActivitySpanEvent[]>;
|
|
48
|
-
/** Per-run high-water seq from the snapshot; deltas at or below drop. */
|
|
49
|
-
highWater: Record<string, number>;
|
|
50
|
-
/**
|
|
51
|
-
* Newest APPLIED delta seq per run. Kept apart from `highWater` on
|
|
52
|
-
* purpose: the snapshot staleness guard compares against the max of both
|
|
53
|
-
* (a snapshot older than an applied delta must not roll it back), while
|
|
54
|
-
* the delta gate keeps comparing against the snapshot floor alone so
|
|
55
|
-
* out-of-order delta delivery stays tolerated (insert-if-absent absorbs it).
|
|
56
|
-
*/
|
|
57
|
-
deltaSeq: Record<string, number>;
|
|
58
|
-
/** spanId -> runId reverse index, for timing lookups by toolUseId. */
|
|
59
|
-
spanRun: Record<string, string>;
|
|
60
|
-
|
|
61
|
-
/** Unacknowledged notification intents — the guaranteed-tier inbox. */
|
|
62
|
-
inbox: ActivityIntent[];
|
|
63
|
-
loadInbox: () => Promise<void>;
|
|
64
|
-
acknowledgeIntent: (id: number) => Promise<void>;
|
|
65
|
-
acknowledgeAllIntents: () => Promise<void>;
|
|
66
|
-
setSupported: (supported: boolean) => void;
|
|
67
|
-
/** New connection: server-side subscriptions are gone; re-subscribe lazily. */
|
|
68
|
-
resetSubscriptions: () => void;
|
|
69
|
-
bumpConnectionEpoch: () => void;
|
|
70
|
-
markSubscribed: (sessionId: string) => void;
|
|
71
|
-
applySnapshot: (msg: ServerActivitySnapshot) => void;
|
|
72
|
-
applyDelta: (msg: ServerActivityDelta) => void;
|
|
73
|
-
clear: () => void;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
48
|
export const useActivityStore = create<ActivityState>((set, get) => ({
|
|
77
49
|
supported: false,
|
|
78
50
|
subscribed: {},
|
|
@@ -309,174 +281,6 @@ export async function loadSpanPayloads(spanId: string): Promise<void> {
|
|
|
309
281
|
}
|
|
310
282
|
}
|
|
311
283
|
|
|
312
|
-
/** The four indexes that make up the mirror, mutated together during merges. */
|
|
313
|
-
interface MirrorMaps {
|
|
314
|
-
spans: Record<string, Record<string, ActivitySpan>>;
|
|
315
|
-
events: Record<string, ActivitySpanEvent[]>;
|
|
316
|
-
highWater: Record<string, number>;
|
|
317
|
-
deltaSeq: Record<string, number>;
|
|
318
|
-
spanRun: Record<string, string>;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function rootSpanOf(byId: Record<string, ActivitySpan>): ActivitySpan | undefined {
|
|
322
|
-
return Object.values(byId).find((span) => span.parentSpanId === undefined);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/** Drop a whole run from the mirror — spans, events, high-water, reverse index. */
|
|
326
|
-
function evictRun(maps: MirrorMaps, runId: string): void {
|
|
327
|
-
for (const spanId of Object.keys(maps.spans[runId] ?? {})) {
|
|
328
|
-
delete maps.events[spanId];
|
|
329
|
-
delete maps.spanRun[spanId];
|
|
330
|
-
}
|
|
331
|
-
delete maps.spans[runId];
|
|
332
|
-
delete maps.highWater[runId];
|
|
333
|
-
delete maps.deltaSeq[runId];
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Enforce the terminal-run bound in place. Returns whether anything was
|
|
338
|
-
* evicted. Runs whose root span is still open (or unknown) are never touched.
|
|
339
|
-
*/
|
|
340
|
-
function pruneTerminalRuns(maps: MirrorMaps): boolean {
|
|
341
|
-
const terminal: Array<{ runId: string; startedAt: number }> = [];
|
|
342
|
-
for (const [runId, byId] of Object.entries(maps.spans)) {
|
|
343
|
-
const root = rootSpanOf(byId);
|
|
344
|
-
if (root && root.outcome !== undefined) {
|
|
345
|
-
terminal.push({ runId, startedAt: root.startedAt });
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
if (terminal.length <= MAX_TERMINAL_RUNS) return false;
|
|
349
|
-
terminal.sort((a, b) => a.startedAt - b.startedAt);
|
|
350
|
-
for (const { runId } of terminal.slice(0, terminal.length - MAX_TERMINAL_RUNS)) {
|
|
351
|
-
evictRun(maps, runId);
|
|
352
|
-
}
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function insertEvent(
|
|
357
|
-
list: ActivitySpanEvent[] | undefined,
|
|
358
|
-
event: ActivitySpanEvent
|
|
359
|
-
): ActivitySpanEvent[] {
|
|
360
|
-
const existing = list ?? [];
|
|
361
|
-
if (existing.some((e) => e.eventIndex === event.eventIndex)) return existing;
|
|
362
|
-
const next = [...existing, event];
|
|
363
|
-
next.sort((a, b) => a.eventIndex - b.eventIndex);
|
|
364
|
-
return next;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// --- Selectors (plain functions over the state, like activeChat) ---
|
|
368
|
-
|
|
369
|
-
/** All spans of a run, roots first, then by start time. */
|
|
370
|
-
export function runSpans(state: ActivityState, runId: string): ActivitySpan[] {
|
|
371
|
-
const byId = state.spans[runId];
|
|
372
|
-
if (!byId) return [];
|
|
373
|
-
return Object.values(byId).sort(
|
|
374
|
-
(a, b) =>
|
|
375
|
-
(a.parentSpanId ? 1 : 0) - (b.parentSpanId ? 1 : 0) || a.startedAt - b.startedAt
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/** Direct children of a span (e.g. the tool calls inside a subagent). */
|
|
380
|
-
export function childSpans(state: ActivityState, parentSpanId: string): ActivitySpan[] {
|
|
381
|
-
const runId = state.spanRun[parentSpanId];
|
|
382
|
-
if (!runId) return [];
|
|
383
|
-
return Object.values(state.spans[runId] ?? {})
|
|
384
|
-
.filter((s) => s.parentSpanId === parentSpanId)
|
|
385
|
-
.sort((a, b) => a.startedAt - b.startedAt);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/** The subagent spans spawned by a turn's Agent tool calls, live status included. */
|
|
389
|
-
export function subagentSpans(state: ActivityState, sessionId: string): ActivitySpan[] {
|
|
390
|
-
const out: ActivitySpan[] = [];
|
|
391
|
-
for (const byId of Object.values(state.spans)) {
|
|
392
|
-
for (const span of Object.values(byId)) {
|
|
393
|
-
if (span.kind === "subagent" && span.sessionId === sessionId) out.push(span);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
return out.sort((a, b) => a.startedAt - b.startedAt);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/** Stable empty result for `eventsFor` — a fresh `[]` per call would defeat
|
|
400
|
-
* reference-equality checks in zustand selectors. */
|
|
401
|
-
export const EMPTY_EVENTS: ActivitySpanEvent[] = Object.freeze(
|
|
402
|
-
[] as ActivitySpanEvent[]
|
|
403
|
-
) as ActivitySpanEvent[];
|
|
404
|
-
|
|
405
|
-
/** Ordered events of a span; the shared frozen empty array when none. */
|
|
406
|
-
export function eventsFor(state: ActivityState, spanId: string): ActivitySpanEvent[] {
|
|
407
|
-
return state.events[spanId] ?? EMPTY_EVENTS;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/** Payload event types the tool expander owns — every OTHER type belongs to
|
|
411
|
-
* the narrative stream (`narrativeEventsFor`) so nothing recorded is
|
|
412
|
-
* rendered nowhere. */
|
|
413
|
-
const TOOL_PAYLOAD_TYPES = new Set(["tool_input", "tool_output"]);
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* The recorded input/output payload events of a tool span (AE7). Returns a
|
|
417
|
-
* fresh array per call — subscribe through `useShallow` (like `childSpans`).
|
|
418
|
-
*/
|
|
419
|
-
export function payloadEventsFor(state: ActivityState, spanId: string): ActivitySpanEvent[] {
|
|
420
|
-
const events = state.events[spanId];
|
|
421
|
-
if (!events) return EMPTY_EVENTS;
|
|
422
|
-
const payloads = events.filter((e) => TOOL_PAYLOAD_TYPES.has(e.eventType));
|
|
423
|
-
return payloads.length > 0 ? payloads : EMPTY_EVENTS;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Everything recorded against a span that is NOT a tool input/output payload:
|
|
428
|
-
* transcript excerpts, job output, and any span-sink event type a producer
|
|
429
|
-
* invents. Rendered as the span's narrative so an unknown type degrades to a
|
|
430
|
-
* labelled block rather than to invisibility.
|
|
431
|
-
*/
|
|
432
|
-
export function narrativeEventsFor(
|
|
433
|
-
state: ActivityState,
|
|
434
|
-
spanId: string
|
|
435
|
-
): ActivitySpanEvent[] {
|
|
436
|
-
const events = state.events[spanId];
|
|
437
|
-
if (!events) return EMPTY_EVENTS;
|
|
438
|
-
const rest = events.filter((e) => !TOOL_PAYLOAD_TYPES.has(e.eventType));
|
|
439
|
-
return rest.length > 0 ? rest : EMPTY_EVENTS;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
/** Every event recorded under a run, ordered by time — the run detail's
|
|
443
|
-
* narrative stream and the raw-trace dump both read through this. */
|
|
444
|
-
export function runEvents(state: ActivityState, runId: string): ActivitySpanEvent[] {
|
|
445
|
-
const byId = state.spans[runId];
|
|
446
|
-
if (!byId) return EMPTY_EVENTS;
|
|
447
|
-
const out: ActivitySpanEvent[] = [];
|
|
448
|
-
for (const spanId of Object.keys(byId)) {
|
|
449
|
-
const events = state.events[spanId];
|
|
450
|
-
if (events) out.push(...events);
|
|
451
|
-
}
|
|
452
|
-
if (out.length === 0) return EMPTY_EVENTS;
|
|
453
|
-
return out.sort((a, b) => a.ts - b.ts || a.eventIndex - b.eventIndex);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/** The span behind one tool call (span ids ARE toolUseIds), if streamed. */
|
|
457
|
-
export function spanForTool(state: ActivityState, toolUseId: string): ActivitySpan | null {
|
|
458
|
-
const runId = state.spanRun[toolUseId];
|
|
459
|
-
return runId ? (state.spans[runId]?.[toolUseId] ?? null) : null;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Server-stamped timing for a tool call. One clock for live and reloaded
|
|
464
|
-
* views: when the activity stream carries the span, its timestamps win over
|
|
465
|
-
* client stamps (AE2 — live and revisit must render identical timings).
|
|
466
|
-
* `waitUntil` marks the approval-wait/execution boundary.
|
|
467
|
-
*/
|
|
468
|
-
export function timingFor(
|
|
469
|
-
state: ActivityState,
|
|
470
|
-
toolUseId: string
|
|
471
|
-
): { startedAt: number; endedAt?: number } | null {
|
|
472
|
-
const span = spanForTool(state, toolUseId);
|
|
473
|
-
if (!span) return null;
|
|
474
|
-
return {
|
|
475
|
-
startedAt: span.waitUntil ?? span.startedAt,
|
|
476
|
-
...(span.endedAt !== undefined ? { endedAt: span.endedAt } : {}),
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
284
|
registerDevHandle(() => {
|
|
481
285
|
if (typeof window === "undefined") return;
|
|
482
286
|
(window as unknown as { __activityStore?: typeof useActivityStore }).__activityStore =
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
2
|
|
|
3
|
-
type WsStatus = "connecting" | "connected" | "disconnected";
|
|
4
|
-
type VpnStatus =
|
|
3
|
+
export type WsStatus = "connecting" | "connected" | "disconnected";
|
|
4
|
+
export type VpnStatus =
|
|
5
5
|
| "connected"
|
|
6
6
|
| "forbidden"
|
|
7
7
|
| "unauthorized"
|
|
@@ -27,9 +27,21 @@ export interface ConnectionError {
|
|
|
27
27
|
interface ConnectionState {
|
|
28
28
|
wsStatus: WsStatus;
|
|
29
29
|
vpnStatus: VpnStatus;
|
|
30
|
+
handshakeFailures: number;
|
|
31
|
+
lastCloseCode: number | null;
|
|
32
|
+
/**
|
|
33
|
+
* Monotonic count of sockets that reached `open`. A probe started before a
|
|
34
|
+
* socket opened compares this to decide whether its answer is still the
|
|
35
|
+
* newest evidence — comparing wsStatus twice cannot see a drop and a
|
|
36
|
+
* replacement open that both land inside one probe.
|
|
37
|
+
*/
|
|
38
|
+
socketOpens: number;
|
|
30
39
|
lastError: ConnectionError | null;
|
|
31
40
|
setWsStatus: (status: WsStatus) => void;
|
|
32
41
|
setVpnStatus: (status: VpnStatus) => void;
|
|
42
|
+
recordWsClose: (opened: boolean, code: number) => void;
|
|
43
|
+
/** A socket reached `open`: the refusal evidence so far is spent. */
|
|
44
|
+
noteSocketOpen: () => void;
|
|
33
45
|
reportError: (code: string, message: string) => void;
|
|
34
46
|
clearError: () => void;
|
|
35
47
|
}
|
|
@@ -37,9 +49,28 @@ interface ConnectionState {
|
|
|
37
49
|
export const useConnectionStore = create<ConnectionState>((set) => ({
|
|
38
50
|
wsStatus: "disconnected",
|
|
39
51
|
vpnStatus: "checking",
|
|
52
|
+
handshakeFailures: 0,
|
|
53
|
+
lastCloseCode: null,
|
|
54
|
+
socketOpens: 0,
|
|
40
55
|
lastError: null,
|
|
41
56
|
setWsStatus: (wsStatus) => set({ wsStatus }),
|
|
42
57
|
setVpnStatus: (vpnStatus) => set({ vpnStatus }),
|
|
58
|
+
recordWsClose: (opened, lastCloseCode) =>
|
|
59
|
+
set((state) => ({
|
|
60
|
+
lastCloseCode,
|
|
61
|
+
handshakeFailures: opened
|
|
62
|
+
? state.handshakeFailures
|
|
63
|
+
: state.handshakeFailures + 1,
|
|
64
|
+
})),
|
|
65
|
+
noteSocketOpen: () =>
|
|
66
|
+
set((state) => ({
|
|
67
|
+
handshakeFailures: 0,
|
|
68
|
+
// Cleared here, not on the next close: the server accepts the upgrade and
|
|
69
|
+
// THEN closes with 4008 when it is at its connection cap, so the code
|
|
70
|
+
// must survive an open to be read, and must not outlive the next one.
|
|
71
|
+
lastCloseCode: null,
|
|
72
|
+
socketOpens: state.socketOpens + 1,
|
|
73
|
+
})),
|
|
43
74
|
reportError: (code, message) => set({ lastError: { code, message, at: Date.now() } }),
|
|
44
75
|
clearError: () => set({ lastError: null }),
|
|
45
76
|
}));
|
package/src/stores/file-store.ts
CHANGED
|
@@ -86,8 +86,8 @@ async function fetchTree(path: string): Promise<FileEntry[]> {
|
|
|
86
86
|
return data.entries;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
async function fetchContent(path: string): Promise<FileContentResponse> {
|
|
90
|
-
const res = await fetch(`${apiBase()}/files/content?path=${encodeURIComponent(path)}
|
|
89
|
+
async function fetchContent(path: string, signal: AbortSignal): Promise<FileContentResponse> {
|
|
90
|
+
const res = await fetch(`${apiBase()}/files/content?path=${encodeURIComponent(path)}`, { signal });
|
|
91
91
|
if (!res.ok) {
|
|
92
92
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
93
93
|
const err = new Error(body.error || `HTTP ${res.status}`);
|
|
@@ -98,8 +98,8 @@ async function fetchContent(path: string): Promise<FileContentResponse> {
|
|
|
98
98
|
return res.json();
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
async function fetchResolve(path: string): Promise<FileResolveResponse> {
|
|
102
|
-
const res = await fetch(`${apiBase()}/files/resolve?path=${encodeURIComponent(path)}
|
|
101
|
+
async function fetchResolve(path: string, signal: AbortSignal): Promise<FileResolveResponse> {
|
|
102
|
+
const res = await fetch(`${apiBase()}/files/resolve?path=${encodeURIComponent(path)}`, { signal });
|
|
103
103
|
if (!res.ok) {
|
|
104
104
|
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
105
105
|
throw new Error(body.error || `HTTP ${res.status}`);
|
|
@@ -116,6 +116,10 @@ async function fetchWikilinks(): Promise<WikilinkMapResponse> {
|
|
|
116
116
|
return res.json();
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
let fileRequest: AbortController | null = null;
|
|
120
|
+
let directoryRequest: AbortController | null = null;
|
|
121
|
+
let cacheEpoch = 0;
|
|
122
|
+
|
|
119
123
|
export const useFileStore = create<FileState>((set, get) => ({
|
|
120
124
|
dirCache: {},
|
|
121
125
|
loadingDirs: new Set(),
|
|
@@ -135,6 +139,7 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
135
139
|
wikilinkLoading: false,
|
|
136
140
|
|
|
137
141
|
loadDir: async (path) => {
|
|
142
|
+
const epoch = cacheEpoch;
|
|
138
143
|
const { dirCache, loadingDirs } = get();
|
|
139
144
|
if (dirCache[path] || loadingDirs.has(path)) return;
|
|
140
145
|
const next = new Set(loadingDirs);
|
|
@@ -142,6 +147,7 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
142
147
|
set({ loadingDirs: next });
|
|
143
148
|
try {
|
|
144
149
|
const entries = await fetchTree(path);
|
|
150
|
+
if (epoch !== cacheEpoch) return;
|
|
145
151
|
set((s) => {
|
|
146
152
|
const loading = new Set(s.loadingDirs);
|
|
147
153
|
loading.delete(path);
|
|
@@ -154,6 +160,7 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
154
160
|
};
|
|
155
161
|
});
|
|
156
162
|
} catch (err) {
|
|
163
|
+
if (epoch !== cacheEpoch) return;
|
|
157
164
|
set((s) => {
|
|
158
165
|
const loading = new Set(s.loadingDirs);
|
|
159
166
|
loading.delete(path);
|
|
@@ -189,13 +196,19 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
189
196
|
},
|
|
190
197
|
|
|
191
198
|
openFile: async (path) => {
|
|
199
|
+
fileRequest?.abort();
|
|
200
|
+
directoryRequest?.abort();
|
|
201
|
+
const request = new AbortController();
|
|
202
|
+
fileRequest = request;
|
|
203
|
+
const ownsRequest = () => fileRequest === request && !request.signal.aborted;
|
|
192
204
|
const normalized = path.replace(/^\/+/, "").replace(/\\/g, "/");
|
|
193
205
|
set({ currentPath: normalized, contentLoading: true, contentError: null, currentContent: null, treeExpanded: false });
|
|
194
206
|
|
|
195
207
|
// Fire-and-await: load ancestor chain then content in parallel
|
|
196
208
|
const ancestorsPromise = (async () => {
|
|
197
209
|
try {
|
|
198
|
-
const r = await fetchResolve(normalized);
|
|
210
|
+
const r = await fetchResolve(normalized, request.signal);
|
|
211
|
+
if (!ownsRequest()) return;
|
|
199
212
|
// Expand each ancestor and root, load their contents
|
|
200
213
|
const dirsToLoad = ["", ...r.ancestors];
|
|
201
214
|
set((s) => {
|
|
@@ -210,12 +223,12 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
210
223
|
})();
|
|
211
224
|
|
|
212
225
|
try {
|
|
213
|
-
const content = await fetchContent(normalized);
|
|
226
|
+
const content = await fetchContent(normalized, request.signal);
|
|
214
227
|
// Drop a response the user has already navigated away from. Two rapid
|
|
215
228
|
// clicks race, and without this the SLOWER fetch wins: the viewer showed
|
|
216
229
|
// the newer file's path with the older file's content, which reads as
|
|
217
230
|
// corruption rather than as a stale load.
|
|
218
|
-
if (
|
|
231
|
+
if (!ownsRequest()) return;
|
|
219
232
|
set({ currentContent: content, contentLoading: false });
|
|
220
233
|
// Default mode: prefer preview when available; persist otherwise
|
|
221
234
|
const { viewMode } = get();
|
|
@@ -233,7 +246,7 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
233
246
|
else if (e.message === "invalid_path") msg = "Invalid path.";
|
|
234
247
|
// Same guard: a failure for a file the user already left must not
|
|
235
248
|
// replace the file they are now looking at with an error.
|
|
236
|
-
if (
|
|
249
|
+
if (!ownsRequest()) return;
|
|
237
250
|
set({ contentError: msg, contentLoading: false });
|
|
238
251
|
} finally {
|
|
239
252
|
await ancestorsPromise;
|
|
@@ -241,10 +254,15 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
241
254
|
},
|
|
242
255
|
|
|
243
256
|
closeFile: () => {
|
|
244
|
-
|
|
257
|
+
fileRequest?.abort();
|
|
258
|
+
fileRequest = null;
|
|
259
|
+
set({ currentPath: null, currentContent: null, contentError: null, contentLoading: false, treeExpanded: true });
|
|
245
260
|
},
|
|
246
261
|
|
|
247
262
|
openDir: async (path) => {
|
|
263
|
+
directoryRequest?.abort();
|
|
264
|
+
const request = new AbortController();
|
|
265
|
+
directoryRequest = request;
|
|
248
266
|
const normalized = path
|
|
249
267
|
.replace(/^\/+/, "")
|
|
250
268
|
.replace(/\\/g, "/")
|
|
@@ -258,7 +276,8 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
258
276
|
set({ treeExpanded: true, highlightedDir: normalized });
|
|
259
277
|
|
|
260
278
|
try {
|
|
261
|
-
const r = await fetchResolve(normalized);
|
|
279
|
+
const r = await fetchResolve(normalized, request.signal);
|
|
280
|
+
if (directoryRequest !== request || request.signal.aborted) return;
|
|
262
281
|
const dirs = ["", ...r.ancestors, normalized];
|
|
263
282
|
set((s) => {
|
|
264
283
|
const next = new Set(s.expandedDirs);
|
|
@@ -278,11 +297,13 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
278
297
|
setHighlightedDir: (path) => set({ highlightedDir: path }),
|
|
279
298
|
|
|
280
299
|
ensureWikilinks: async () => {
|
|
300
|
+
const epoch = cacheEpoch;
|
|
281
301
|
const { wikilinkLoaded, wikilinkLoading } = get();
|
|
282
302
|
if (wikilinkLoaded || wikilinkLoading) return;
|
|
283
303
|
set({ wikilinkLoading: true });
|
|
284
304
|
try {
|
|
285
305
|
const data = await fetchWikilinks();
|
|
306
|
+
if (epoch !== cacheEpoch) return;
|
|
286
307
|
set({
|
|
287
308
|
wikilinkMap: data.slugs,
|
|
288
309
|
wikilinkLoaded: true,
|
|
@@ -290,6 +311,7 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
290
311
|
});
|
|
291
312
|
} catch {
|
|
292
313
|
// Soft-fail — leave the map empty; wikilinks will render as plain text
|
|
314
|
+
if (epoch !== cacheEpoch) return;
|
|
293
315
|
set({ wikilinkLoading: false });
|
|
294
316
|
}
|
|
295
317
|
},
|
|
@@ -304,15 +326,23 @@ export const useFileStore = create<FileState>((set, get) => ({
|
|
|
304
326
|
return { frontmatterCollapsed: next };
|
|
305
327
|
}),
|
|
306
328
|
|
|
307
|
-
reset: () =>
|
|
329
|
+
reset: () => {
|
|
330
|
+
fileRequest?.abort();
|
|
331
|
+
directoryRequest?.abort();
|
|
332
|
+
fileRequest = null;
|
|
333
|
+
directoryRequest = null;
|
|
334
|
+
cacheEpoch++;
|
|
308
335
|
set({
|
|
336
|
+
dirCache: {}, loadingDirs: new Set(), dirErrors: {}, expandedDirs: new Set([""]),
|
|
337
|
+
highlightedDir: null, wikilinkMap: {}, wikilinkLoaded: false, wikilinkLoading: false,
|
|
309
338
|
currentPath: null,
|
|
310
339
|
currentContent: null,
|
|
311
340
|
contentLoading: false,
|
|
312
341
|
contentError: null,
|
|
313
342
|
viewMode: "preview",
|
|
314
343
|
treeExpanded: true,
|
|
315
|
-
})
|
|
344
|
+
});
|
|
345
|
+
},
|
|
316
346
|
}));
|
|
317
347
|
|
|
318
348
|
/**
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
import { api, type MintedAgent } from "../lib/api-client.js";
|
|
3
|
+
|
|
4
|
+
interface PrincipalState {
|
|
5
|
+
mintPending: boolean;
|
|
6
|
+
mintError: string | null;
|
|
7
|
+
oneTimeCredential: MintedAgent | null;
|
|
8
|
+
mintAgent: (label: string, ttlDays: number) => Promise<void>;
|
|
9
|
+
acknowledgeAgentCredential: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mint state lives above Settings because the response contains an
|
|
14
|
+
* unrecoverable, one-time value. Closing a panel or changing views must not
|
|
15
|
+
* discard either the request in flight or the value it eventually returns.
|
|
16
|
+
*/
|
|
17
|
+
export const usePrincipalStore = create<PrincipalState>((set, get) => ({
|
|
18
|
+
mintPending: false,
|
|
19
|
+
mintError: null,
|
|
20
|
+
oneTimeCredential: null,
|
|
21
|
+
|
|
22
|
+
mintAgent: async (label, ttlDays) => {
|
|
23
|
+
if (get().mintPending || get().oneTimeCredential) return;
|
|
24
|
+
set({ mintPending: true, mintError: null });
|
|
25
|
+
try {
|
|
26
|
+
const oneTimeCredential = await api.principalMint(label, ttlDays);
|
|
27
|
+
set({ mintPending: false, oneTimeCredential });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
set({
|
|
30
|
+
mintPending: false,
|
|
31
|
+
mintError:
|
|
32
|
+
error instanceof Error ? error.message : "Could not create agent access",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
acknowledgeAgentCredential: () => set({ oneTimeCredential: null }),
|
|
38
|
+
}));
|
package/src/stores/ui-store.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
2
|
|
|
3
3
|
/** Which tab the settings panel opens on. */
|
|
4
|
-
export type SettingsTab = "models" | "skills" | "security";
|
|
4
|
+
export type SettingsTab = "models" | "skills" | "security" | "devices";
|
|
5
5
|
|
|
6
6
|
/** Full-screen surface currently shown inside the AppShell. */
|
|
7
7
|
export type ActiveView = "chat" | "graph" | "activity";
|