@antelopejs/dms-ai 0.0.5 → 0.0.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 CHANGED
@@ -33,6 +33,7 @@ Install dependencies and run the backend and sidecar checks from the repository
33
33
  ```bash
34
34
  pnpm install
35
35
  pnpm build
36
+ pnpm build:sidecar
36
37
  pnpm test
37
38
  ```
38
39
 
@@ -1,4 +1,8 @@
1
- import { defineDmsPlugin, useDmsRouter } from '#dms/frontend-module'
1
+ import {
2
+ type DmsAppContext,
3
+ defineDmsPlugin,
4
+ useDmsRouter,
5
+ } from '#dms/frontend-module'
2
6
  import {
3
7
  SIDECAR_HOST_NAME,
4
8
  SIDECAR_INFO_PATH,
@@ -14,6 +18,7 @@ import { registerLauncherAction } from '../runtime/header-action'
14
18
  import { createHostCommandDispatcher } from '../runtime/host-commands'
15
19
  import { installNavigationCompleteEmitter } from '../runtime/navigation-complete'
16
20
  import { injectOverlay } from '../runtime/overlay'
21
+ import { runWhenLoggedIn } from '../runtime/session-gate'
17
22
  import { installToggleShortcut } from '../runtime/shortcuts'
18
23
  import {
19
24
  createSidecarStatusController,
@@ -30,10 +35,7 @@ function buildIframeUrl(port: number, token: string): string {
30
35
  return `${SIDECAR_PROBE_HOST}:${port}/?theme=${mode}#token=${encodeURIComponent(token)}`
31
36
  }
32
37
 
33
- export default defineDmsPlugin(async ({ vueApp }) => {
34
- if (!import.meta.env.DEV) return
35
- const { loggedIn } = useUserSession()
36
- if (!loggedIn.value) return
38
+ async function startAssistant({ vueApp }: DmsAppContext): Promise<void> {
37
39
  const { $authFetch } = useAuthFetch()
38
40
  const controller = createSidecarStatusController(() =>
39
41
  $authFetch(SIDECAR_INFO_PATH),
@@ -106,4 +108,14 @@ export default defineDmsPlugin(async ({ vueApp }) => {
106
108
  }
107
109
  vueApp.onUnmount(stop)
108
110
  import.meta.hot?.dispose(stop)
111
+ }
112
+
113
+ export default defineDmsPlugin((context) => {
114
+ if (!import.meta.env.DEV) return
115
+ const { loggedIn } = useUserSession()
116
+ const stopGate = runWhenLoggedIn(
117
+ () => loggedIn.value,
118
+ () => void context.runWithContext(() => startAssistant(context)),
119
+ )
120
+ context.vueApp.onUnmount(stopGate)
109
121
  })
@@ -0,0 +1,22 @@
1
+ import { watch } from 'vue'
2
+
3
+ // Plugins run once, when the Vue app boots, on whatever page the browser landed
4
+ // on. Landing on the sign-in screen means there is no session yet, and signing
5
+ // in is a client-side navigation that never re-creates the app: a plugin that
6
+ // gave up there would stay silent until the next full page load. Wait for the
7
+ // session to appear instead, then run exactly once.
8
+ export function runWhenLoggedIn(
9
+ isLoggedIn: () => boolean,
10
+ start: () => void,
11
+ ): () => void {
12
+ if (isLoggedIn()) {
13
+ start()
14
+ return () => undefined
15
+ }
16
+ const stop = watch(isLoggedIn, (loggedIn) => {
17
+ if (!loggedIn) return
18
+ stop()
19
+ start()
20
+ })
21
+ return stop
22
+ }
@@ -0,0 +1,39 @@
1
+ import { nextTick, ref } from 'vue'
2
+ import { describe, expect, it, vi } from 'vitest'
3
+ import { runWhenLoggedIn } from '../app/runtime/session-gate'
4
+
5
+ describe('session gate', () => {
6
+ it('starts immediately when the session is already hydrated', () => {
7
+ const start = vi.fn()
8
+ runWhenLoggedIn(() => true, start)
9
+ expect(start).toHaveBeenCalledTimes(1)
10
+ })
11
+
12
+ it('starts once the session appears, and only once', async () => {
13
+ const loggedIn = ref(false)
14
+ const start = vi.fn()
15
+ runWhenLoggedIn(() => loggedIn.value, start)
16
+ expect(start).not.toHaveBeenCalled()
17
+
18
+ loggedIn.value = true
19
+ await nextTick()
20
+ expect(start).toHaveBeenCalledTimes(1)
21
+
22
+ loggedIn.value = false
23
+ await nextTick()
24
+ loggedIn.value = true
25
+ await nextTick()
26
+ expect(start).toHaveBeenCalledTimes(1)
27
+ })
28
+
29
+ it('never starts once the gate is stopped', async () => {
30
+ const loggedIn = ref(false)
31
+ const start = vi.fn()
32
+ const stop = runWhenLoggedIn(() => loggedIn.value, start)
33
+
34
+ stop()
35
+ loggedIn.value = true
36
+ await nextTick()
37
+ expect(start).not.toHaveBeenCalled()
38
+ })
39
+ })
@@ -1,13 +1,29 @@
1
1
  import { readFileSync } from 'node:fs'
2
2
  import { resolve } from 'node:path'
3
3
  import ts from 'typescript'
4
+ import { nextTick, ref, type Ref } from 'vue'
4
5
  import { expect, it, vi } from 'vitest'
6
+ import { runWhenLoggedIn } from '../app/runtime/session-gate'
5
7
 
6
8
  interface PluginExports {
7
- default: (context: unknown) => Promise<void>
9
+ default: (context: unknown) => void | Promise<void>
8
10
  }
9
11
 
10
- it('does not invoke authenticated fetching or inject the assistant for a guest', async () => {
12
+ interface PluginHarness {
13
+ useAuthFetch: ReturnType<typeof vi.fn>
14
+ run: () => void | Promise<void>
15
+ }
16
+
17
+ const SESSION_GATE_MODULE = '../runtime/session-gate'
18
+ const SIDECAR_STATUS_MODULE = '../runtime/sidecar-status'
19
+
20
+ // A first probe reporting "no assistant here" stops the startup right after the
21
+ // authenticated fetch, which is all these tests need to observe.
22
+ const silentController = {
23
+ createSidecarStatusController: () => ({ init: async () => false }),
24
+ }
25
+
26
+ function loadPlugin(loggedIn: Ref<boolean>): PluginHarness {
11
27
  const source = readFileSync(
12
28
  resolve(__dirname, '../app/plugins/ai.client.ts'),
13
29
  'utf8',
@@ -16,30 +32,48 @@ it('does not invoke authenticated fetching or inject the assistant for a guest',
16
32
  source
17
33
  .replace('import.meta.env.DEV', 'true')
18
34
  .replace('import.meta.hot', 'undefined'),
19
- {
20
- compilerOptions: { module: ts.ModuleKind.CommonJS },
21
- },
35
+ { compilerOptions: { module: ts.ModuleKind.CommonJS } },
22
36
  )
23
37
  const exports = {} as PluginExports
24
- const useAuthFetch = vi.fn(() => {
25
- throw new Error('A guest must not enter session recovery')
26
- })
27
- const requireModule = (name: string) =>
28
- name === '#dms/frontend-module'
29
- ? { defineDmsPlugin: (plugin: unknown) => plugin }
30
- : {}
38
+ const useAuthFetch = vi.fn(() => ({ $authFetch: vi.fn() }))
39
+ const requireModule = (name: string) => {
40
+ if (name === '#dms/frontend-module') {
41
+ return { defineDmsPlugin: (plugin: unknown) => plugin }
42
+ }
43
+ if (name === SESSION_GATE_MODULE) return { runWhenLoggedIn }
44
+ if (name === SIDECAR_STATUS_MODULE) return silentController
45
+ return {}
46
+ }
31
47
  new Function(
32
48
  'require',
33
49
  'exports',
34
50
  'useUserSession',
35
51
  'useAuthFetch',
36
52
  compiled.outputText,
37
- )(
38
- requireModule,
39
- exports,
40
- () => ({ loggedIn: { value: false } }),
53
+ )(requireModule, exports, () => ({ loggedIn }), useAuthFetch)
54
+ return {
41
55
  useAuthFetch,
42
- )
43
- await exports.default({})
44
- expect(useAuthFetch).not.toHaveBeenCalled()
56
+ run: () =>
57
+ exports.default({
58
+ vueApp: { onUnmount: vi.fn() },
59
+ runWithContext: (callback: () => unknown) => callback(),
60
+ }),
61
+ }
62
+ }
63
+
64
+ it('does not invoke authenticated fetching or inject the assistant for a guest', async () => {
65
+ const plugin = loadPlugin(ref(false))
66
+ await plugin.run()
67
+ expect(plugin.useAuthFetch).not.toHaveBeenCalled()
68
+ })
69
+
70
+ it('starts the assistant when a guest signs in without reloading the page', async () => {
71
+ const loggedIn = ref(false)
72
+ const plugin = loadPlugin(loggedIn)
73
+ await plugin.run()
74
+ expect(plugin.useAuthFetch).not.toHaveBeenCalled()
75
+
76
+ loggedIn.value = true
77
+ await nextTick()
78
+ expect(plugin.useAuthFetch).toHaveBeenCalledTimes(1)
45
79
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antelopejs/dms-ai",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,6 +38,7 @@
38
38
  "lint:fix": "oxlint --disable-nested-config --fix --max-warnings=1000000 && oxfmt --disable-nested-config . && cd frontend-vue && pnpm lint:fix && cd ../sidecar && pnpm lint:fix",
39
39
  "typecheck": "tsc --noEmit && cd frontend-vue && pnpm typecheck && cd ../sidecar && pnpm typecheck",
40
40
  "test": "cd sidecar && pnpm test && cd ../frontend-vue && pnpm test",
41
+ "test:sidecar-smoke": "pnpm run build:sidecar && node --test tests/sidecar-build.test.cjs",
41
42
  "test:frontend-registration": "pnpm run build && node --test tests/frontend-registration.test.cjs",
42
43
  "test:release-script": "node --test tests/release-script.test.cjs",
43
44
  "test:e2e": "cd sidecar && pnpm test:e2e",