@antelopejs/dms-ai 0.1.4 → 0.1.6
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 +55 -29
- package/dist/channels/bridge.d.ts +2 -8
- package/dist/channels/bridge.js +26 -42
- package/dist/channels/bridge.js.map +1 -1
- package/dist/channels/channel-service.d.ts +2 -2
- package/dist/channels/channel-service.js +12 -31
- package/dist/channels/channel-service.js.map +1 -1
- package/dist/channels/registry.d.ts +2 -2
- package/dist/channels/registry.js +7 -7
- package/dist/channels/registry.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/constants/channels.d.ts +5 -10
- package/dist/constants/channels.js +6 -10
- package/dist/constants/channels.js.map +1 -1
- package/dist/constants/sidecar.d.ts +5 -1
- package/dist/constants/sidecar.js +6 -2
- package/dist/constants/sidecar.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/build-id.d.ts +1 -0
- package/dist/lifecycle/build-id.js +32 -0
- package/dist/lifecycle/build-id.js.map +1 -0
- package/dist/lifecycle/sidecar-owner.d.ts +13 -0
- package/dist/lifecycle/sidecar-owner.js +89 -0
- package/dist/lifecycle/sidecar-owner.js.map +1 -0
- package/dist/lifecycle/sidecar-socket.d.ts +1 -1
- package/dist/lifecycle/sidecar-socket.js +2 -2
- package/dist/lifecycle/sidecar-socket.js.map +1 -1
- package/dist/lifecycle/spawn-sidecar.d.ts +0 -1
- package/dist/lifecycle/spawn-sidecar.js +51 -159
- package/dist/lifecycle/spawn-sidecar.js.map +1 -1
- package/dist/lifecycle/terminate-process.d.ts +1 -0
- package/dist/lifecycle/terminate-process.js +34 -0
- package/dist/lifecycle/terminate-process.js.map +1 -0
- package/dist/routes/channels.d.ts +3 -3
- package/dist/routes/channels.js +11 -12
- package/dist/routes/channels.js.map +1 -1
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/index.js +0 -1
- package/dist/routes/index.js.map +1 -1
- package/frontend-vue/.prettierignore +3 -0
- package/frontend-vue/.prettierrc +10 -0
- package/frontend-vue/app/chat/components/ChatView.vue +582 -0
- package/frontend-vue/app/chat/components/ComposerInput.vue +742 -0
- package/frontend-vue/app/chat/components/ConnectionBanner.vue +105 -0
- package/frontend-vue/app/chat/components/ConversationDrawer.vue +266 -0
- package/frontend-vue/app/chat/components/MarkdownContent.vue +196 -0
- package/frontend-vue/app/chat/components/MessageList.vue +474 -0
- package/frontend-vue/app/chat/components/PermissionRequests.vue +323 -0
- package/frontend-vue/app/chat/components/QuestionPrompt.vue +275 -0
- package/frontend-vue/app/chat/components/QueuedMessages.vue +109 -0
- package/frontend-vue/app/chat/components/TodoList.vue +291 -0
- package/frontend-vue/app/chat/components/ToolCallEntry.vue +218 -0
- package/frontend-vue/app/chat/components/ToolCluster.vue +282 -0
- package/frontend-vue/app/chat/composables/useChatChannel.ts +116 -0
- package/frontend-vue/app/chat/composables/useConversation.ts +838 -0
- package/frontend-vue/app/chat/composables/useConversationId.ts +34 -0
- package/frontend-vue/app/chat/composables/useConversationList.ts +56 -0
- package/frontend-vue/app/chat/composables/usePermissionQueue.ts +59 -0
- package/frontend-vue/app/chat/composables/useQuestionQueue.ts +50 -0
- package/frontend-vue/app/chat/composables/useRunClock.ts +28 -0
- package/frontend-vue/app/chat/composables/useSettings.ts +67 -0
- package/frontend-vue/app/chat/constants/attachments.ts +16 -0
- package/frontend-vue/app/chat/constants/connection-banner.ts +7 -0
- package/frontend-vue/app/chat/constants/conversation-drawer.ts +13 -0
- package/frontend-vue/app/chat/constants/conversation.ts +75 -0
- package/frontend-vue/app/chat/constants/permissions.ts +50 -0
- package/frontend-vue/app/chat/constants/protocol.ts +48 -0
- package/frontend-vue/app/chat/constants/run-status.ts +50 -0
- package/frontend-vue/app/chat/constants/settings.ts +60 -0
- package/frontend-vue/app/chat/types/conversation.ts +87 -0
- package/frontend-vue/app/chat/types/permission.ts +7 -0
- package/frontend-vue/app/chat/types/question.ts +17 -0
- package/frontend-vue/app/chat/types/settings.ts +27 -0
- package/frontend-vue/app/chat/utils/attachments.ts +62 -0
- package/frontend-vue/app/chat/utils/format.ts +14 -0
- package/frontend-vue/app/chat/utils/ids.ts +34 -0
- package/frontend-vue/app/chat/utils/markdown.ts +188 -0
- package/frontend-vue/app/chat/utils/run-status.ts +90 -0
- package/frontend-vue/app/chat/utils/todos.ts +64 -0
- package/frontend-vue/app/chat/utils/tool-summary.ts +53 -0
- package/frontend-vue/app/components/ActivityView.vue +34 -34
- package/frontend-vue/app/components/ChatPanel.vue +276 -0
- package/frontend-vue/app/components/SettingsView.vue +120 -112
- package/frontend-vue/app/components/SkillsView.vue +55 -53
- package/frontend-vue/app/plugins/ai.client.ts +145 -129
- package/frontend-vue/app/runtime/assistant-session.ts +21 -0
- package/frontend-vue/app/runtime/backoff.ts +11 -14
- package/frontend-vue/app/runtime/channel-client.ts +130 -141
- package/frontend-vue/app/runtime/chat-transport.ts +25 -55
- package/frontend-vue/app/runtime/constants.ts +69 -59
- package/frontend-vue/app/runtime/current-page.ts +49 -15
- package/frontend-vue/app/runtime/event-stream.ts +35 -35
- package/frontend-vue/app/runtime/frame-router.ts +30 -0
- package/frontend-vue/app/runtime/header-action.ts +18 -18
- package/frontend-vue/app/runtime/host-commands.ts +57 -56
- package/frontend-vue/app/runtime/listeners.ts +31 -0
- package/frontend-vue/app/runtime/navigation-complete.ts +7 -7
- package/frontend-vue/app/runtime/overlay-prefs.ts +36 -36
- package/frontend-vue/app/runtime/panel-state.ts +88 -0
- package/frontend-vue/app/runtime/session-gate.ts +8 -8
- package/frontend-vue/app/runtime/shortcuts.ts +12 -12
- package/frontend-vue/app/runtime/sidecar-status.ts +61 -61
- package/frontend-vue/app/runtime/typed-message.ts +11 -0
- package/frontend-vue/dms.frontend.ts +12 -12
- package/frontend-vue/eslint.config.mjs +13 -13
- package/frontend-vue/package.json +11 -4
- package/frontend-vue/pnpm-lock.yaml +298 -96
- package/frontend-vue/tests/backoff.test.ts +26 -30
- package/frontend-vue/tests/channel-client.test.ts +187 -188
- package/frontend-vue/tests/chat/markdown.test.ts +271 -0
- package/frontend-vue/tests/chat/useChatChannel.test.ts +124 -0
- package/frontend-vue/tests/chat/useConversation.test.ts +283 -0
- package/frontend-vue/tests/chat-panel.test.ts +354 -0
- package/frontend-vue/tests/chat-transport.test.ts +84 -57
- package/frontend-vue/tests/host-commands.test.ts +126 -87
- package/frontend-vue/tests/navigation.test.ts +65 -34
- package/frontend-vue/tests/overlay-prefs.test.ts +49 -49
- package/frontend-vue/tests/panel-state.test.ts +58 -0
- package/frontend-vue/tests/session-gate.test.ts +33 -33
- package/frontend-vue/tests/sidecar-guest.test.ts +46 -46
- package/frontend-vue/tests/sidecar-status.test.ts +29 -29
- package/frontend-vue/tsconfig.chat.json +16 -0
- package/frontend-vue/vitest.config.ts +6 -4
- package/package.json +6 -5
- package/sidecar/dist/agent/effective-mode.d.ts +2 -2
- package/sidecar/dist/agent/effective-mode.js +1 -1
- package/sidecar/dist/agent/effective-mode.js.map +1 -1
- package/sidecar/dist/agent/host-context.js +7 -9
- package/sidecar/dist/agent/host-context.js.map +1 -1
- package/sidecar/dist/agent/permission-bus.d.ts +1 -1
- package/sidecar/dist/agent/permission-bus.js +2 -2
- package/sidecar/dist/agent/permission-bus.js.map +1 -1
- package/sidecar/dist/agent/provider.d.ts +2 -2
- package/sidecar/dist/agent/question-bus.d.ts +1 -1
- package/sidecar/dist/agent/question-bus.js +2 -2
- package/sidecar/dist/agent/question-bus.js.map +1 -1
- package/sidecar/dist/agent/runner.d.ts +2 -2
- package/sidecar/dist/agent/runner.js +24 -8
- package/sidecar/dist/agent/runner.js.map +1 -1
- package/sidecar/dist/agent/session.d.ts +2 -2
- package/sidecar/dist/agent/session.js +49 -9
- package/sidecar/dist/agent/session.js.map +1 -1
- package/sidecar/dist/agent/switching-runner.js +10 -5
- package/sidecar/dist/agent/switching-runner.js.map +1 -1
- package/sidecar/dist/agent/turn-end-reasons.d.ts +2 -0
- package/sidecar/dist/agent/turn-end-reasons.js +6 -1
- package/sidecar/dist/agent/turn-end-reasons.js.map +1 -1
- package/sidecar/dist/constants/agent.d.ts +4 -1
- package/sidecar/dist/constants/agent.js +4 -1
- package/sidecar/dist/constants/agent.js.map +1 -1
- package/sidecar/dist/constants/cli.d.ts +0 -1
- package/sidecar/dist/constants/cli.js +0 -1
- package/sidecar/dist/constants/cli.js.map +1 -1
- package/sidecar/dist/constants/codex.d.ts +5 -2
- package/sidecar/dist/constants/codex.js +5 -2
- package/sidecar/dist/constants/codex.js.map +1 -1
- package/sidecar/dist/constants/http.d.ts +0 -9
- package/sidecar/dist/constants/http.js +0 -19
- package/sidecar/dist/constants/http.js.map +1 -1
- package/sidecar/dist/constants/mcp.d.ts +1 -1
- package/sidecar/dist/constants/mcp.js +1 -1
- package/sidecar/dist/constants/mcp.js.map +1 -1
- package/sidecar/dist/constants/paths.d.ts +0 -1
- package/sidecar/dist/constants/paths.js +0 -1
- package/sidecar/dist/constants/paths.js.map +1 -1
- package/sidecar/dist/constants/ws.d.ts +3 -4
- package/sidecar/dist/constants/ws.js +3 -4
- package/sidecar/dist/constants/ws.js.map +1 -1
- package/sidecar/dist/index.d.ts +4 -1
- package/sidecar/dist/index.js +13 -13
- package/sidecar/dist/index.js.map +1 -1
- package/sidecar/dist/mcp/tools/find-pages-using.js +5 -10
- package/sidecar/dist/mcp/tools/find-pages-using.js.map +1 -1
- package/sidecar/dist/mcp/types.d.ts +1 -1
- package/sidecar/dist/pages/page-filepath.d.ts +4 -0
- package/sidecar/dist/pages/page-filepath.js +28 -0
- package/sidecar/dist/pages/page-filepath.js.map +1 -0
- package/sidecar/dist/prompts/agent.d.ts +1 -1
- package/sidecar/dist/prompts/agent.js +1 -1
- package/sidecar/dist/prompts/agent.js.map +1 -1
- package/sidecar/dist/protocol/events.js +2 -2
- package/sidecar/dist/protocol/events.js.map +1 -1
- package/sidecar/dist/protocol/messages.d.ts +4 -15
- package/sidecar/dist/protocol/messages.js +4 -11
- package/sidecar/dist/protocol/messages.js.map +1 -1
- package/sidecar/dist/providers/claude/config.d.ts +2 -2
- package/sidecar/dist/providers/claude/config.js +2 -2
- package/sidecar/dist/providers/claude/config.js.map +1 -1
- package/sidecar/dist/providers/claude/provider.js +1 -1
- package/sidecar/dist/providers/claude/provider.js.map +1 -1
- package/sidecar/dist/providers/codex/config.d.ts +3 -2
- package/sidecar/dist/providers/codex/config.js +3 -3
- package/sidecar/dist/providers/codex/config.js.map +1 -1
- package/sidecar/dist/providers/codex/process.js +107 -74
- package/sidecar/dist/providers/codex/process.js.map +1 -1
- package/sidecar/dist/providers/codex/provider.js +68 -31
- package/sidecar/dist/providers/codex/provider.js.map +1 -1
- package/sidecar/dist/providers/codex/skills.d.ts +1 -0
- package/sidecar/dist/providers/codex/skills.js +4 -1
- package/sidecar/dist/providers/codex/skills.js.map +1 -1
- package/sidecar/dist/providers/codex/stop.d.ts +4 -0
- package/sidecar/dist/providers/codex/stop.js +71 -0
- package/sidecar/dist/providers/codex/stop.js.map +1 -0
- package/sidecar/dist/server/{iframe-socket-registry.d.ts → chat-socket-registry.d.ts} +3 -2
- package/sidecar/dist/server/{iframe-socket-registry.js → chat-socket-registry.js} +4 -3
- package/sidecar/dist/server/chat-socket-registry.js.map +1 -0
- package/sidecar/dist/server/client-auth.js +2 -14
- package/sidecar/dist/server/client-auth.js.map +1 -1
- package/sidecar/dist/server/host-command-router.d.ts +5 -0
- package/sidecar/dist/server/host-command-router.js +4 -0
- package/sidecar/dist/server/host-command-router.js.map +1 -0
- package/sidecar/dist/server/host-socket-registry.d.ts +1 -1
- package/sidecar/dist/server/host-socket-registry.js +16 -13
- package/sidecar/dist/server/host-socket-registry.js.map +1 -1
- package/sidecar/dist/server/http.d.ts +0 -1
- package/sidecar/dist/server/http.js +1 -44
- package/sidecar/dist/server/http.js.map +1 -1
- package/sidecar/dist/server/routing.d.ts +5 -6
- package/sidecar/dist/server/routing.js +18 -28
- package/sidecar/dist/server/routing.js.map +1 -1
- package/sidecar/dist/server/ws.d.ts +2 -2
- package/sidecar/dist/server/ws.js +41 -39
- package/sidecar/dist/server/ws.js.map +1 -1
- package/sidecar/dist/state/host-state.d.ts +10 -2
- package/sidecar/dist/state/host-state.js +13 -3
- package/sidecar/dist/state/host-state.js.map +1 -1
- package/sidecar/dist/state/settings-store.js +2 -2
- package/sidecar/dist/state/settings-store.js.map +1 -1
- package/sidecar/dist/state/settings-types.d.ts +3 -3
- package/sidecar/dist/state/settings-types.js +1 -1
- package/sidecar/dist/state/settings-types.js.map +1 -1
- package/sidecar/package.json +3 -5
- package/dist/chatbox/passthrough.d.ts +0 -3
- package/dist/chatbox/passthrough.js +0 -53
- package/dist/chatbox/passthrough.js.map +0 -1
- package/dist/constants/chatbox.d.ts +0 -10
- package/dist/constants/chatbox.js +0 -35
- package/dist/constants/chatbox.js.map +0 -1
- package/dist/routes/chatbox.d.ts +0 -7
- package/dist/routes/chatbox.js +0 -47
- package/dist/routes/chatbox.js.map +0 -1
- package/frontend-vue/app/runtime/overlay.ts +0 -400
- package/frontend-vue/app/runtime/theme-bridge.ts +0 -189
- package/sidecar/chatbox/dist/assets/index-C8u7mkHn.css +0 -1
- package/sidecar/chatbox/dist/assets/index-tdZvY7ee.js +0 -52
- package/sidecar/chatbox/dist/index.html +0 -13
- package/sidecar/dist/server/iframe-socket-registry.js.map +0 -1
|
@@ -1,32 +1,66 @@
|
|
|
1
|
-
import { router } from
|
|
2
|
-
import { HOST_STATE_UPDATE_TYPE } from
|
|
1
|
+
import { router } from "@inertiajs/vue3";
|
|
2
|
+
import { HOST_STATE_UPDATE_TYPE } from "./constants";
|
|
3
3
|
|
|
4
4
|
interface CurrentPagePayload {
|
|
5
|
-
path: string
|
|
5
|
+
path: string;
|
|
6
|
+
title?: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
interface HostStateUpdateMessage {
|
|
9
|
-
type: typeof HOST_STATE_UPDATE_TYPE
|
|
10
|
-
currentPage: CurrentPagePayload
|
|
10
|
+
type: typeof HOST_STATE_UPDATE_TYPE;
|
|
11
|
+
currentPage: CurrentPagePayload;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
interface InstallCurrentPageTrackerOptions {
|
|
14
|
-
send: (msg: HostStateUpdateMessage) => void
|
|
15
|
+
send: (msg: HostStateUpdateMessage) => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const TITLE_OBSERVER_OPTIONS: MutationObserverInit = {
|
|
19
|
+
subtree: true,
|
|
20
|
+
childList: true,
|
|
21
|
+
characterData: true,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function readTitle(): string | undefined {
|
|
25
|
+
const title = document.title.trim();
|
|
26
|
+
return title === "" ? undefined : title;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildCurrentPage(url: string): CurrentPagePayload {
|
|
30
|
+
const path = new URL(url, window.location.origin).pathname;
|
|
31
|
+
const title = readTitle();
|
|
32
|
+
return title === undefined ? { path } : { path, title };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The state update announcing the page at `url`, titled after the document. */
|
|
18
36
|
export function buildCurrentPageUpdate(url: string): HostStateUpdateMessage {
|
|
19
|
-
return {
|
|
20
|
-
type: HOST_STATE_UPDATE_TYPE,
|
|
21
|
-
currentPage: { path: new URL(url, window.location.origin).pathname },
|
|
22
|
-
}
|
|
37
|
+
return { type: HOST_STATE_UPDATE_TYPE, currentPage: buildCurrentPage(url) };
|
|
23
38
|
}
|
|
24
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Reports the displayed page on every Inertia navigation, and again whenever
|
|
42
|
+
* the document title changes after the page has rendered.
|
|
43
|
+
*/
|
|
25
44
|
export function installCurrentPageTracker(
|
|
26
45
|
options: InstallCurrentPageTrackerOptions,
|
|
27
46
|
): () => void {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
let currentUrl = window.location.href;
|
|
48
|
+
let sentTitle = readTitle();
|
|
49
|
+
const push = (): void => {
|
|
50
|
+
sentTitle = readTitle();
|
|
51
|
+
options.send(buildCurrentPageUpdate(currentUrl));
|
|
52
|
+
};
|
|
53
|
+
const stopNavigation = router.on("navigate", (event) => {
|
|
54
|
+
currentUrl = event.detail.page.url;
|
|
55
|
+
push();
|
|
56
|
+
});
|
|
57
|
+
const titleObserver = new MutationObserver(() => {
|
|
58
|
+
if (readTitle() !== sentTitle) push();
|
|
59
|
+
});
|
|
60
|
+
titleObserver.observe(document.head, TITLE_OBSERVER_OPTIONS);
|
|
61
|
+
push();
|
|
62
|
+
return () => {
|
|
63
|
+
stopNavigation();
|
|
64
|
+
titleObserver.disconnect();
|
|
65
|
+
};
|
|
32
66
|
}
|
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
export interface StreamEvent {
|
|
2
|
-
name: string
|
|
3
|
-
data: string
|
|
2
|
+
name: string;
|
|
3
|
+
data: string;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
const DEFAULT_EVENT_NAME =
|
|
7
|
-
const FRAME_SEPARATOR = /\r?\n\r?\n
|
|
8
|
-
const LINE_SEPARATOR = /\r?\n
|
|
9
|
-
const FIELD_SEPARATOR =
|
|
10
|
-
const EVENT_FIELD =
|
|
11
|
-
const DATA_FIELD =
|
|
6
|
+
const DEFAULT_EVENT_NAME = "message";
|
|
7
|
+
const FRAME_SEPARATOR = /\r?\n\r?\n/;
|
|
8
|
+
const LINE_SEPARATOR = /\r?\n/;
|
|
9
|
+
const FIELD_SEPARATOR = ":";
|
|
10
|
+
const EVENT_FIELD = "event";
|
|
11
|
+
const DATA_FIELD = "data";
|
|
12
12
|
|
|
13
13
|
interface Field {
|
|
14
|
-
name: string
|
|
15
|
-
value: string
|
|
14
|
+
name: string;
|
|
15
|
+
value: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function parseField(line: string): Field {
|
|
19
|
-
const boundary = line.indexOf(FIELD_SEPARATOR)
|
|
20
|
-
if (boundary === -1) return { name: line, value:
|
|
21
|
-
const raw = line.slice(boundary + FIELD_SEPARATOR.length)
|
|
19
|
+
const boundary = line.indexOf(FIELD_SEPARATOR);
|
|
20
|
+
if (boundary === -1) return { name: line, value: "" };
|
|
21
|
+
const raw = line.slice(boundary + FIELD_SEPARATOR.length);
|
|
22
22
|
return {
|
|
23
23
|
name: line.slice(0, boundary),
|
|
24
|
-
value: raw.startsWith(
|
|
25
|
-
}
|
|
24
|
+
value: raw.startsWith(" ") ? raw.slice(1) : raw,
|
|
25
|
+
};
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function parseFrame(frame: string): StreamEvent | null {
|
|
29
|
-
let name = DEFAULT_EVENT_NAME
|
|
30
|
-
const data: string[] = []
|
|
29
|
+
let name = DEFAULT_EVENT_NAME;
|
|
30
|
+
const data: string[] = [];
|
|
31
31
|
for (const line of frame.split(LINE_SEPARATOR)) {
|
|
32
|
-
const field = parseField(line)
|
|
33
|
-
if (field.name === EVENT_FIELD) name = field.value
|
|
34
|
-
if (field.name === DATA_FIELD) data.push(field.value)
|
|
32
|
+
const field = parseField(line);
|
|
33
|
+
if (field.name === EVENT_FIELD) name = field.value;
|
|
34
|
+
if (field.name === DATA_FIELD) data.push(field.value);
|
|
35
35
|
}
|
|
36
|
-
if (data.length === 0) return null
|
|
37
|
-
return { name, data: data.join(
|
|
36
|
+
if (data.length === 0) return null;
|
|
37
|
+
return { name, data: data.join("\n") };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function dispatchFrames(
|
|
41
41
|
buffer: string,
|
|
42
42
|
onEvent: (event: StreamEvent) => void,
|
|
43
43
|
): string {
|
|
44
|
-
const frames = buffer.split(FRAME_SEPARATOR)
|
|
45
|
-
const rest = frames.pop() ??
|
|
44
|
+
const frames = buffer.split(FRAME_SEPARATOR);
|
|
45
|
+
const rest = frames.pop() ?? "";
|
|
46
46
|
for (const frame of frames) {
|
|
47
|
-
const event = parseFrame(frame)
|
|
48
|
-
if (event !== null) onEvent(event)
|
|
47
|
+
const event = parseFrame(frame);
|
|
48
|
+
if (event !== null) onEvent(event);
|
|
49
49
|
}
|
|
50
|
-
return rest
|
|
50
|
+
return rest;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -59,17 +59,17 @@ export async function readEventStream(
|
|
|
59
59
|
body: ReadableStream<Uint8Array>,
|
|
60
60
|
onEvent: (event: StreamEvent) => void,
|
|
61
61
|
): Promise<void> {
|
|
62
|
-
const reader = body.getReader()
|
|
63
|
-
const decoder = new TextDecoder()
|
|
64
|
-
let buffer =
|
|
62
|
+
const reader = body.getReader();
|
|
63
|
+
const decoder = new TextDecoder();
|
|
64
|
+
let buffer = "";
|
|
65
65
|
try {
|
|
66
66
|
while (true) {
|
|
67
|
-
const chunk = await reader.read()
|
|
68
|
-
if (chunk.done) return
|
|
69
|
-
buffer += decoder.decode(chunk.value, { stream: true })
|
|
70
|
-
buffer = dispatchFrames(buffer, onEvent)
|
|
67
|
+
const chunk = await reader.read();
|
|
68
|
+
if (chunk.done) return;
|
|
69
|
+
buffer += decoder.decode(chunk.value, { stream: true });
|
|
70
|
+
buffer = dispatchFrames(buffer, onEvent);
|
|
71
71
|
}
|
|
72
72
|
} finally {
|
|
73
|
-
reader.releaseLock()
|
|
73
|
+
reader.releaseLock();
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { HOST_COMMAND_TYPES } from "./constants";
|
|
2
|
+
import { isTypedMessage, type TypedMessage } from "./typed-message";
|
|
3
|
+
|
|
4
|
+
export interface FrameRoutes {
|
|
5
|
+
host: (msg: TypedMessage) => void;
|
|
6
|
+
chat: (msg: TypedMessage) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseFrame(raw: string): TypedMessage | null {
|
|
10
|
+
try {
|
|
11
|
+
const parsed: unknown = JSON.parse(raw);
|
|
12
|
+
return isTypedMessage(parsed) ? parsed : null;
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The dashboard and its chat share the tab's one sidecar socket: each frame is
|
|
20
|
+
* parsed once, then handed to the dashboard when it is a host command and to
|
|
21
|
+
* the chat otherwise. A frame that is not a typed message reaches neither.
|
|
22
|
+
*/
|
|
23
|
+
export function createFrameRouter(routes: FrameRoutes): (raw: string) => void {
|
|
24
|
+
return (raw) => {
|
|
25
|
+
const msg = parseFrame(raw);
|
|
26
|
+
if (msg === null) return;
|
|
27
|
+
const route = HOST_COMMAND_TYPES.has(msg.type) ? routes.host : routes.chat;
|
|
28
|
+
route(msg);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useDmsState as useState } from
|
|
1
|
+
import { useDmsState as useState } from "#dms/frontend-module";
|
|
2
2
|
import {
|
|
3
3
|
HEADER_ACTIONS_STATE_KEY,
|
|
4
4
|
LAUNCHER_ACTION_ID,
|
|
@@ -7,14 +7,14 @@ import {
|
|
|
7
7
|
LAUNCHER_ORDER,
|
|
8
8
|
TOGGLE_SHORTCUT_LABEL_MAC,
|
|
9
9
|
TOGGLE_SHORTCUT_LABEL_OTHER,
|
|
10
|
-
} from
|
|
10
|
+
} from "./constants";
|
|
11
11
|
|
|
12
12
|
interface UserAgentData {
|
|
13
|
-
platform?: string
|
|
13
|
+
platform?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
interface NavigatorUserAgentData {
|
|
17
|
-
userAgentData?: UserAgentData
|
|
17
|
+
userAgentData?: UserAgentData;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// The shortcut combo accepts meta OR ctrl, so show the modifier each platform
|
|
@@ -22,35 +22,35 @@ interface NavigatorUserAgentData {
|
|
|
22
22
|
function isApplePlatform(): boolean {
|
|
23
23
|
const nav = globalThis.navigator as
|
|
24
24
|
| (Navigator & NavigatorUserAgentData)
|
|
25
|
-
| undefined
|
|
26
|
-
if (nav === undefined) return false
|
|
27
|
-
const platform = nav.userAgentData?.platform ?? nav.platform ??
|
|
28
|
-
return /mac|iphone|ipad|ipod/i.test(platform)
|
|
25
|
+
| undefined;
|
|
26
|
+
if (nav === undefined) return false;
|
|
27
|
+
const platform = nav.userAgentData?.platform ?? nav.platform ?? "";
|
|
28
|
+
return /mac|iphone|ipad|ipod/i.test(platform);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function launcherLabel(): string {
|
|
32
32
|
const shortcut = isApplePlatform()
|
|
33
33
|
? TOGGLE_SHORTCUT_LABEL_MAC
|
|
34
|
-
: TOGGLE_SHORTCUT_LABEL_OTHER
|
|
35
|
-
return `${LAUNCHER_LABEL} (${shortcut})
|
|
34
|
+
: TOGGLE_SHORTCUT_LABEL_OTHER;
|
|
35
|
+
return `${LAUNCHER_LABEL} (${shortcut})`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Our view of the shared header-action shape the DMS core renders. Declared
|
|
39
39
|
// locally so this layer has no build-time dependency on the core layer; the
|
|
40
40
|
// only contract is the state key and these fields.
|
|
41
41
|
interface HeaderAction {
|
|
42
|
-
id: string
|
|
43
|
-
icon: string
|
|
44
|
-
label: string
|
|
45
|
-
onSelect: () => void
|
|
46
|
-
order?: number
|
|
42
|
+
id: string;
|
|
43
|
+
icon: string;
|
|
44
|
+
label: string;
|
|
45
|
+
onSelect: () => void;
|
|
46
|
+
order?: number;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
// Register the assistant launcher in the core's generic header-action registry.
|
|
50
50
|
// Idempotent so a re-running plugin can't add a duplicate button.
|
|
51
51
|
export function registerLauncherAction(onSelect: () => void): void {
|
|
52
|
-
const actions = useState<HeaderAction[]>(HEADER_ACTIONS_STATE_KEY, () => [])
|
|
53
|
-
if (actions.value.some((action) => action.id === LAUNCHER_ACTION_ID)) return
|
|
52
|
+
const actions = useState<HeaderAction[]>(HEADER_ACTIONS_STATE_KEY, () => []);
|
|
53
|
+
if (actions.value.some((action) => action.id === LAUNCHER_ACTION_ID)) return;
|
|
54
54
|
actions.value = [
|
|
55
55
|
...actions.value,
|
|
56
56
|
{
|
|
@@ -60,5 +60,5 @@ export function registerLauncherAction(onSelect: () => void): void {
|
|
|
60
60
|
order: LAUNCHER_ORDER,
|
|
61
61
|
onSelect,
|
|
62
62
|
},
|
|
63
|
-
]
|
|
63
|
+
];
|
|
64
64
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { HOST_COMMAND_NAVIGATE_TYPE, LOG_PREFIX } from
|
|
1
|
+
import { HOST_COMMAND_NAVIGATE_TYPE, LOG_PREFIX } from "./constants";
|
|
2
|
+
import { isTypedMessage } from "./typed-message";
|
|
2
3
|
|
|
3
4
|
interface RouterLike {
|
|
4
|
-
push: (path: string) => unknown
|
|
5
|
+
push: (path: string) => unknown;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -10,22 +11,23 @@ interface RouterLike {
|
|
|
10
11
|
* timeout.
|
|
11
12
|
*/
|
|
12
13
|
export interface DevReloadWaiter {
|
|
13
|
-
awaitRoute: (path: string) => Promise<boolean
|
|
14
|
+
awaitRoute: (path: string) => Promise<boolean>;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface HostCommandContext {
|
|
17
|
-
router: RouterLike
|
|
18
|
-
devReload: DevReloadWaiter
|
|
18
|
+
router: RouterLike;
|
|
19
|
+
devReload: DevReloadWaiter;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export interface HostCommandNavigateMessage {
|
|
22
|
-
type: typeof HOST_COMMAND_NAVIGATE_TYPE
|
|
23
|
-
path: string
|
|
23
|
+
type: typeof HOST_COMMAND_NAVIGATE_TYPE;
|
|
24
|
+
path: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export type HostCommandMessage = HostCommandNavigateMessage
|
|
27
|
+
export type HostCommandMessage = HostCommandNavigateMessage;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
/** Takes a sidecar message already parsed; anything that is not a host command is ignored. */
|
|
30
|
+
export type HostCommandDispatcher = (msg: unknown) => void;
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Commands run one at a time on `tail`. `nextPath` holds the newest navigation
|
|
@@ -33,20 +35,22 @@ export type HostCommandDispatcher = (raw: unknown) => void
|
|
|
33
35
|
* the latest path instead of racing and landing on the oldest one.
|
|
34
36
|
*/
|
|
35
37
|
interface DispatcherState {
|
|
36
|
-
ctx: HostCommandContext
|
|
37
|
-
tail: Promise<void
|
|
38
|
-
nextPath: string | null
|
|
38
|
+
ctx: HostCommandContext;
|
|
39
|
+
tail: Promise<void>;
|
|
40
|
+
nextPath: string | null;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
type HostCommandHandler = (
|
|
42
44
|
msg: HostCommandMessage,
|
|
43
45
|
state: DispatcherState,
|
|
44
|
-
) => void
|
|
46
|
+
) => void;
|
|
47
|
+
|
|
48
|
+
const IN_APP_PATH = /^\/(?![/\\])/;
|
|
45
49
|
|
|
46
50
|
function enqueue(state: DispatcherState, job: () => Promise<void>): void {
|
|
47
51
|
state.tail = state.tail.then(job).catch((err: unknown) => {
|
|
48
|
-
console.error(`${LOG_PREFIX} host command failed`, err)
|
|
49
|
-
})
|
|
52
|
+
console.error(`${LOG_PREFIX} host command failed`, err);
|
|
53
|
+
});
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
// The agent asks to navigate right after writing the page, while the host is
|
|
@@ -60,52 +64,50 @@ async function awaitRouteThenPush(
|
|
|
60
64
|
ctx: HostCommandContext,
|
|
61
65
|
): Promise<void> {
|
|
62
66
|
try {
|
|
63
|
-
await ctx.devReload.awaitRoute(path)
|
|
67
|
+
await ctx.devReload.awaitRoute(path);
|
|
64
68
|
} catch (err: unknown) {
|
|
65
|
-
console.error(`${LOG_PREFIX} dev reload wait failed for ${path}`, err)
|
|
69
|
+
console.error(`${LOG_PREFIX} dev reload wait failed for ${path}`, err);
|
|
66
70
|
}
|
|
67
|
-
void ctx.router.push(path)
|
|
71
|
+
void ctx.router.push(path);
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
async function runNavigate(state: DispatcherState): Promise<void> {
|
|
71
|
-
const path = state.nextPath
|
|
75
|
+
const path = state.nextPath;
|
|
72
76
|
// A newer navigate already claimed this slot's target: this one is superseded
|
|
73
77
|
// and must not drag the user back to the older path.
|
|
74
|
-
if (path === null) return
|
|
75
|
-
state.nextPath = null
|
|
76
|
-
await awaitRouteThenPush(path, state.ctx)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function handleNavigate(
|
|
80
|
-
msg: HostCommandMessage,
|
|
81
|
-
state: DispatcherState,
|
|
82
|
-
): void {
|
|
83
|
-
if (msg.type !== HOST_COMMAND_NAVIGATE_TYPE) return
|
|
84
|
-
state.nextPath = msg.path
|
|
85
|
-
enqueue(state, () => runNavigate(state))
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const COMMAND_HANDLERS: Record<string, HostCommandHandler> = {
|
|
89
|
-
[HOST_COMMAND_NAVIGATE_TYPE]: handleNavigate,
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isHostCommand(value: unknown): value is HostCommandMessage {
|
|
93
|
-
if (value === null || typeof value !== 'object') return false
|
|
94
|
-
const candidate = value as { type?: unknown }
|
|
95
|
-
return typeof candidate.type === 'string'
|
|
78
|
+
if (path === null) return;
|
|
79
|
+
state.nextPath = null;
|
|
80
|
+
await awaitRouteThenPush(path, state.ctx);
|
|
96
81
|
}
|
|
97
82
|
|
|
98
|
-
|
|
99
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Whether the agent may send the dashboard to `path`. What it asks for comes
|
|
85
|
+
* from model output, so it may only ever lead to a page of this origin.
|
|
86
|
+
*/
|
|
87
|
+
export function isInAppPath(path: unknown): path is string {
|
|
88
|
+
if (typeof path !== "string" || !IN_APP_PATH.test(path)) return false;
|
|
100
89
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
return parsed
|
|
90
|
+
const origin = globalThis.location.origin;
|
|
91
|
+
return new URL(path, origin).origin === origin;
|
|
104
92
|
} catch {
|
|
105
|
-
return
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleNavigate(msg: HostCommandMessage, state: DispatcherState): void {
|
|
98
|
+
if (msg.type !== HOST_COMMAND_NAVIGATE_TYPE) return;
|
|
99
|
+
if (!isInAppPath(msg.path)) {
|
|
100
|
+
console.warn(`${LOG_PREFIX} refused to navigate off the dashboard`);
|
|
101
|
+
return;
|
|
106
102
|
}
|
|
103
|
+
state.nextPath = msg.path;
|
|
104
|
+
enqueue(state, () => runNavigate(state));
|
|
107
105
|
}
|
|
108
106
|
|
|
107
|
+
const COMMAND_HANDLERS: Record<string, HostCommandHandler> = {
|
|
108
|
+
[HOST_COMMAND_NAVIGATE_TYPE]: handleNavigate,
|
|
109
|
+
};
|
|
110
|
+
|
|
109
111
|
export function createHostCommandDispatcher(
|
|
110
112
|
ctx: HostCommandContext,
|
|
111
113
|
): HostCommandDispatcher {
|
|
@@ -113,15 +115,14 @@ export function createHostCommandDispatcher(
|
|
|
113
115
|
ctx,
|
|
114
116
|
tail: Promise.resolve(),
|
|
115
117
|
nextPath: null,
|
|
116
|
-
}
|
|
117
|
-
return (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const handler = COMMAND_HANDLERS[msg.type]
|
|
118
|
+
};
|
|
119
|
+
return (msg: unknown): void => {
|
|
120
|
+
if (!isTypedMessage<HostCommandMessage>(msg)) return;
|
|
121
|
+
const handler = COMMAND_HANDLERS[msg.type];
|
|
121
122
|
if (handler === undefined) {
|
|
122
|
-
console.warn(`${LOG_PREFIX} unknown host command type=${msg.type}`)
|
|
123
|
-
return
|
|
123
|
+
console.warn(`${LOG_PREFIX} unknown host command type=${msg.type}`);
|
|
124
|
+
return;
|
|
124
125
|
}
|
|
125
|
-
handler(msg, state)
|
|
126
|
-
}
|
|
126
|
+
handler(msg, state);
|
|
127
|
+
};
|
|
127
128
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LOG_PREFIX } from "./constants";
|
|
2
|
+
|
|
3
|
+
export type Listener<T> = (value: T) => void;
|
|
4
|
+
|
|
5
|
+
export interface Listeners<T> {
|
|
6
|
+
add: (listener: Listener<T>) => () => void;
|
|
7
|
+
emit: Listener<T>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A set of listeners notified in order. A listener belongs to a component that
|
|
12
|
+
* may already be failing: one throwing must not stop the others.
|
|
13
|
+
*/
|
|
14
|
+
export function createListeners<T>(): Listeners<T> {
|
|
15
|
+
const listeners = new Set<Listener<T>>();
|
|
16
|
+
return {
|
|
17
|
+
add: (listener) => {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
return () => listeners.delete(listener);
|
|
20
|
+
},
|
|
21
|
+
emit: (value) => {
|
|
22
|
+
for (const listener of listeners) {
|
|
23
|
+
try {
|
|
24
|
+
listener(value);
|
|
25
|
+
} catch (error: unknown) {
|
|
26
|
+
console.error(`${LOG_PREFIX} listener failed`, error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { router } from
|
|
2
|
-
import { HOST_NAVIGATION_COMPLETE_TYPE } from
|
|
1
|
+
import { router } from "@inertiajs/vue3";
|
|
2
|
+
import { HOST_NAVIGATION_COMPLETE_TYPE } from "./constants";
|
|
3
3
|
|
|
4
4
|
export interface HostNavigationCompleteMessage {
|
|
5
|
-
type: typeof HOST_NAVIGATION_COMPLETE_TYPE
|
|
6
|
-
path: string
|
|
5
|
+
type: typeof HOST_NAVIGATION_COMPLETE_TYPE;
|
|
6
|
+
path: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
interface InstallNavigationCompleteEmitterOptions {
|
|
10
|
-
send: (msg: HostNavigationCompleteMessage) => void
|
|
10
|
+
send: (msg: HostNavigationCompleteMessage) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function installNavigationCompleteEmitter(
|
|
14
14
|
options: InstallNavigationCompleteEmitterOptions,
|
|
15
15
|
): () => void {
|
|
16
|
-
return router.on(
|
|
16
|
+
return router.on("navigate", (event) =>
|
|
17
17
|
options.send({
|
|
18
18
|
type: HOST_NAVIGATION_COMPLETE_TYPE,
|
|
19
19
|
path: new URL(event.detail.page.url, window.location.origin).pathname,
|
|
20
20
|
}),
|
|
21
|
-
)
|
|
21
|
+
);
|
|
22
22
|
}
|
|
@@ -1,89 +1,89 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OVERLAY_PREFS_DEBOUNCE_MS,
|
|
3
3
|
OVERLAY_PREFS_STORAGE_KEY,
|
|
4
|
-
} from
|
|
4
|
+
} from "./constants";
|
|
5
5
|
|
|
6
6
|
export interface OverlayPrefs {
|
|
7
|
-
width: number
|
|
8
|
-
isOpen: boolean
|
|
7
|
+
width: number;
|
|
8
|
+
isOpen: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
let pendingTimer: ReturnType<typeof setTimeout> | null = null
|
|
12
|
-
let pendingPrefs: OverlayPrefs | null = null
|
|
11
|
+
let pendingTimer: ReturnType<typeof setTimeout> | null = null;
|
|
12
|
+
let pendingPrefs: OverlayPrefs | null = null;
|
|
13
13
|
|
|
14
14
|
function tryReadStorage(): string | null {
|
|
15
15
|
try {
|
|
16
|
-
return globalThis.localStorage.getItem(OVERLAY_PREFS_STORAGE_KEY)
|
|
16
|
+
return globalThis.localStorage.getItem(OVERLAY_PREFS_STORAGE_KEY);
|
|
17
17
|
} catch {
|
|
18
|
-
return null
|
|
18
|
+
return null;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function tryWriteStorage(payload: string): void {
|
|
23
23
|
try {
|
|
24
|
-
globalThis.localStorage.setItem(OVERLAY_PREFS_STORAGE_KEY, payload)
|
|
24
|
+
globalThis.localStorage.setItem(OVERLAY_PREFS_STORAGE_KEY, payload);
|
|
25
25
|
} catch {
|
|
26
|
-
return
|
|
26
|
+
return;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function isFiniteNumber(value: unknown): value is number {
|
|
31
|
-
return typeof value ===
|
|
31
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function isValidPrefs(value: unknown): value is OverlayPrefs {
|
|
35
|
-
if (value === null || typeof value !==
|
|
36
|
-
const candidate = value as Record<string, unknown
|
|
37
|
-
if (!isFiniteNumber(candidate.width)) return false
|
|
38
|
-
if (typeof candidate.isOpen !==
|
|
39
|
-
return true
|
|
35
|
+
if (value === null || typeof value !== "object") return false;
|
|
36
|
+
const candidate = value as Record<string, unknown>;
|
|
37
|
+
if (!isFiniteNumber(candidate.width)) return false;
|
|
38
|
+
if (typeof candidate.isOpen !== "boolean") return false;
|
|
39
|
+
return true;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function parsePrefs(raw: string): OverlayPrefs | null {
|
|
43
43
|
try {
|
|
44
|
-
const parsed: unknown = JSON.parse(raw)
|
|
45
|
-
return isValidPrefs(parsed) ? parsed : null
|
|
44
|
+
const parsed: unknown = JSON.parse(raw);
|
|
45
|
+
return isValidPrefs(parsed) ? parsed : null;
|
|
46
46
|
} catch {
|
|
47
|
-
return null
|
|
47
|
+
return null;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function readPrefs(): OverlayPrefs | null {
|
|
52
|
-
const raw = tryReadStorage()
|
|
53
|
-
if (raw === null) return null
|
|
54
|
-
return parsePrefs(raw)
|
|
52
|
+
const raw = tryReadStorage();
|
|
53
|
+
if (raw === null) return null;
|
|
54
|
+
return parsePrefs(raw);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function flushPending(): void {
|
|
58
|
-
if (pendingPrefs === null) return
|
|
59
|
-
tryWriteStorage(JSON.stringify(pendingPrefs))
|
|
60
|
-
pendingPrefs = null
|
|
61
|
-
pendingTimer = null
|
|
58
|
+
if (pendingPrefs === null) return;
|
|
59
|
+
tryWriteStorage(JSON.stringify(pendingPrefs));
|
|
60
|
+
pendingPrefs = null;
|
|
61
|
+
pendingTimer = null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export function writePrefsDebounced(prefs: OverlayPrefs): void {
|
|
65
|
-
pendingPrefs = prefs
|
|
66
|
-
if (pendingTimer !== null) clearTimeout(pendingTimer)
|
|
67
|
-
pendingTimer = setTimeout(flushPending, OVERLAY_PREFS_DEBOUNCE_MS)
|
|
65
|
+
pendingPrefs = prefs;
|
|
66
|
+
if (pendingTimer !== null) clearTimeout(pendingTimer);
|
|
67
|
+
pendingTimer = setTimeout(flushPending, OVERLAY_PREFS_DEBOUNCE_MS);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export function flushPrefsForTesting(): void {
|
|
71
71
|
if (pendingTimer !== null) {
|
|
72
|
-
clearTimeout(pendingTimer)
|
|
73
|
-
pendingTimer = null
|
|
72
|
+
clearTimeout(pendingTimer);
|
|
73
|
+
pendingTimer = null;
|
|
74
74
|
}
|
|
75
|
-
flushPending()
|
|
75
|
+
flushPending();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export function resetPrefsForTesting(): void {
|
|
79
79
|
if (pendingTimer !== null) {
|
|
80
|
-
clearTimeout(pendingTimer)
|
|
81
|
-
pendingTimer = null
|
|
80
|
+
clearTimeout(pendingTimer);
|
|
81
|
+
pendingTimer = null;
|
|
82
82
|
}
|
|
83
|
-
pendingPrefs = null
|
|
83
|
+
pendingPrefs = null;
|
|
84
84
|
try {
|
|
85
|
-
globalThis.localStorage.removeItem(OVERLAY_PREFS_STORAGE_KEY)
|
|
85
|
+
globalThis.localStorage.removeItem(OVERLAY_PREFS_STORAGE_KEY);
|
|
86
86
|
} catch {
|
|
87
|
-
return
|
|
87
|
+
return;
|
|
88
88
|
}
|
|
89
89
|
}
|