@antelopejs/dms-ai 0.2.0 → 0.3.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.
Files changed (41) hide show
  1. package/README.md +3 -1
  2. package/dist/constants/frontend-module.d.ts +2 -0
  3. package/dist/constants/frontend-module.js +6 -1
  4. package/dist/constants/frontend-module.js.map +1 -1
  5. package/dist/constants/pages.d.ts +4 -4
  6. package/dist/constants/pages.js +4 -4
  7. package/dist/constants/pages.js.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/pages/skills/page.js +0 -2
  11. package/dist/pages/skills/page.js.map +1 -1
  12. package/frontend-vue/app/components/ActivityDetail.vue +11 -13
  13. package/frontend-vue/app/components/ChangesView.vue +56 -49
  14. package/frontend-vue/app/plugins/ai.client.ts +16 -6
  15. package/frontend-vue/app/runtime/assistant-activity.ts +38 -21
  16. package/frontend-vue/app/runtime/sidecar-status.ts +78 -56
  17. package/frontend-vue/app/views/ChangeSetPanel.vue +13 -18
  18. package/frontend-vue/app/views/UnavailableState.vue +1 -1
  19. package/frontend-vue/app/views/composables/useAiData.ts +116 -0
  20. package/frontend-vue/app/views/composables/useAssistant.ts +8 -2
  21. package/frontend-vue/app/views/composables/useChangeSetActions.ts +7 -6
  22. package/frontend-vue/app/views/constants.ts +1 -1
  23. package/frontend-vue/app/views/utils/change-sets.ts +0 -7
  24. package/frontend-vue/package.json +1 -1
  25. package/frontend-vue/tests/assistant-activity.test.ts +69 -0
  26. package/frontend-vue/tests/chat/markdown.test.ts +1 -0
  27. package/frontend-vue/tests/chat-panel.test.ts +1 -0
  28. package/frontend-vue/tests/host-commands.test.ts +1 -0
  29. package/frontend-vue/tests/navigation.test.ts +1 -0
  30. package/frontend-vue/tests/palette-assistant.test.ts +1 -0
  31. package/frontend-vue/tests/panel-states.test.ts +6 -1
  32. package/frontend-vue/tests/sidecar-guest.test.ts +1 -1
  33. package/frontend-vue/tests/sidecar-status.test.ts +93 -15
  34. package/frontend-vue/tests/support/dms-frontend-module.ts +9 -0
  35. package/frontend-vue/tests/views/api-data-reads.test.ts +445 -0
  36. package/frontend-vue/tests/views/diff-view.test.ts +1 -0
  37. package/frontend-vue/tests/views/use-assistant.test.ts +53 -0
  38. package/frontend-vue/vitest.config.ts +14 -1
  39. package/package.json +3 -3
  40. package/sidecar/package.json +2 -1
  41. package/frontend-vue/app/views/composables/useAiRequest.ts +0 -89
package/README.md CHANGED
@@ -17,7 +17,7 @@ Codex is selectable once its CLI is installed.
17
17
 
18
18
  ## Installation
19
19
 
20
- Add the module to a project that already uses `@antelopejs/dms`:
20
+ Add the module to a project that already uses `@antelopejs/dms` 0.9 (with `@antelopejs/interface-dms` 0.7 and `@antelopejs/dms-frontend` 0.6: the views read their data with its `useApiData`, and the module's routes under `/ai/**` reach the backend through the frontend server, which the module declares as a backend prefix with the session):
21
21
 
22
22
  ```bash
23
23
  ajs project modules add @antelopejs/dms-ai
@@ -234,6 +234,8 @@ pnpm --dir frontend-vue build
234
234
  pnpm --dir frontend-vue typecheck
235
235
  ```
236
236
 
237
+ `pnpm test:node` runs every backend suite below in one go, after a single build (`DMS_PREBUILT=1` skips the build when `dist/` is already fresh, as in CI). The sidecar's opt-in Codex acceptance suite runs on its own with `CODEX_ACCEPTANCE=1 pnpm --dir sidecar test:acceptance`.
238
+
237
239
  `pnpm test:frontend-registration` checks backend registration and sidecar inputs with the sidecar launcher mocked, and that every page, block and child of the Assistant module is titled with `$dms_ai` keys present in both locales.
238
240
  `pnpm test:routes` drives the `/ai/*` routes against a stand-in sidecar: query translation, `503` while it is down, settings conversions, Undo stamping and the CSV export.
239
241
  `pnpm test:sidecar-lifecycle` starts a stand-in sidecar to check that a hot reload keeps it, a restart replaces it, a sidecar left by an earlier run is stopped, and Ctrl+C on the backend stops it.
@@ -1,3 +1,5 @@
1
+ import type { FrontendBackendProxy } from "@antelopejs/interface-dms/page";
1
2
  export declare const FRONTEND_MODULE_NAME = "@antelopejs/dms-ai-frontend-vue";
2
3
  export declare const FRONTEND_MODULE_DIR = "../frontend-vue";
3
4
  export declare const FRONTEND_MODULE_PRIORITY = 100;
5
+ export declare const FRONTEND_BACKEND_PROXY: FrontendBackendProxy;
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FRONTEND_MODULE_PRIORITY = exports.FRONTEND_MODULE_DIR = exports.FRONTEND_MODULE_NAME = void 0;
3
+ exports.FRONTEND_BACKEND_PROXY = exports.FRONTEND_MODULE_PRIORITY = exports.FRONTEND_MODULE_DIR = exports.FRONTEND_MODULE_NAME = void 0;
4
+ const module_1 = require("./module");
4
5
  exports.FRONTEND_MODULE_NAME = "@antelopejs/dms-ai-frontend-vue";
5
6
  exports.FRONTEND_MODULE_DIR = "../frontend-vue";
6
7
  exports.FRONTEND_MODULE_PRIORITY = 100;
8
+ exports.FRONTEND_BACKEND_PROXY = {
9
+ path: `${module_1.ROUTE_PREFIX}/**`,
10
+ auth: "session",
11
+ };
7
12
  //# sourceMappingURL=frontend-module.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"frontend-module.js","sourceRoot":"","sources":["../../src/constants/frontend-module.ts"],"names":[],"mappings":";;;AAAa,QAAA,oBAAoB,GAAG,iCAAiC,CAAC;AACzD,QAAA,mBAAmB,GAAG,iBAAiB,CAAC;AACxC,QAAA,wBAAwB,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"frontend-module.js","sourceRoot":"","sources":["../../src/constants/frontend-module.ts"],"names":[],"mappings":";;;AACA,qCAAwC;AAE3B,QAAA,oBAAoB,GAAG,iCAAiC,CAAC;AACzD,QAAA,mBAAmB,GAAG,iBAAiB,CAAC;AACxC,QAAA,wBAAwB,GAAG,GAAG,CAAC;AAO/B,QAAA,sBAAsB,GAAyB;IAC1D,IAAI,EAAE,GAAG,qBAAY,KAAK;IAC1B,IAAI,EAAE,SAAS;CAChB,CAAC"}
@@ -32,9 +32,9 @@ export declare const PAGE_ROUTES: {
32
32
  readonly USAGE_SUMMARY: "/ai/metrics/usage/summary?days=14";
33
33
  readonly CHANGES: "/ai/changes";
34
34
  readonly RECENT_CHANGES: "/ai/recent-changes";
35
- readonly CHANGE_UNDO: "/ai/changes/{_id}/undo";
36
- readonly CHANGE_UNDO_CONFIRM: "/ai/changes/{id}/undo-confirm";
37
- readonly CHANGE_REDO: "/ai/changes/{_id}/redo";
35
+ readonly CHANGE_UNDO: "/ai/changes/{{row._id}}/undo";
36
+ readonly CHANGE_UNDO_CONFIRM: "/ai/changes/{{row._id}}/undo-confirm";
37
+ readonly CHANGE_REDO: "/ai/changes/{{row._id}}/redo";
38
38
  readonly ACTIVITY: "/ai/activity";
39
39
  readonly ACTIVITY_EXPORT: "/ai/activity/export.csv";
40
40
  readonly SKILLS_CATALOG: "/ai/skills/catalog";
@@ -43,7 +43,7 @@ export declare const PAGE_ROUTES: {
43
43
  };
44
44
  export declare const PAGE_LINKS: {
45
45
  readonly CHANGES: "/modules/ai/changes";
46
- readonly CHANGE_SET: "/modules/ai/changes?set={_id}";
46
+ readonly CHANGE_SET: "/modules/ai/changes?set={{row._id}}";
47
47
  readonly ACTIVITY: "/modules/ai/activity";
48
48
  readonly SETTINGS: "/modules/ai/settings";
49
49
  readonly SKILL_SETTINGS: "/modules/ai/settings#dms-form-settings-section-skills";
@@ -39,9 +39,9 @@ exports.PAGE_ROUTES = {
39
39
  USAGE_SUMMARY: `${module_1.ROUTE_PREFIX}/metrics/usage/summary?days=${USAGE_DAYS}`,
40
40
  CHANGES: `${module_1.ROUTE_PREFIX}/changes`,
41
41
  RECENT_CHANGES: `${module_1.ROUTE_PREFIX}/recent-changes`,
42
- CHANGE_UNDO: `${module_1.ROUTE_PREFIX}/changes/{_id}/undo`,
43
- CHANGE_UNDO_CONFIRM: `${module_1.ROUTE_PREFIX}/changes/{id}/undo-confirm`,
44
- CHANGE_REDO: `${module_1.ROUTE_PREFIX}/changes/{_id}/redo`,
42
+ CHANGE_UNDO: `${module_1.ROUTE_PREFIX}/changes/{{row._id}}/undo`,
43
+ CHANGE_UNDO_CONFIRM: `${module_1.ROUTE_PREFIX}/changes/{{row._id}}/undo-confirm`,
44
+ CHANGE_REDO: `${module_1.ROUTE_PREFIX}/changes/{{row._id}}/redo`,
45
45
  ACTIVITY: `${module_1.ROUTE_PREFIX}/activity`,
46
46
  ACTIVITY_EXPORT: `${module_1.ROUTE_PREFIX}/activity/export.csv`,
47
47
  SKILLS_CATALOG: `${module_1.ROUTE_PREFIX}/skills/catalog`,
@@ -50,7 +50,7 @@ exports.PAGE_ROUTES = {
50
50
  };
51
51
  exports.PAGE_LINKS = {
52
52
  CHANGES: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.CHANGES}`,
53
- CHANGE_SET: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.CHANGES}?set={_id}`,
53
+ CHANGE_SET: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.CHANGES}?set={{row._id}}`,
54
54
  ACTIVITY: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.ACTIVITY}`,
55
55
  SETTINGS: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.SETTINGS}`,
56
56
  SKILL_SETTINGS: `${MODULE_PAGES_PATH}/${exports.PAGE_IDS.SETTINGS}#dms-form-${SETTINGS_FORM_KEY}-section-${exports.SETTINGS_SKILLS_SECTION}`,
@@ -1 +1 @@
1
- {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/constants/pages.ts"],"names":[],"mappings":";;;AAAA,qCAAwC;AAE3B,QAAA,cAAc,GAAG,cAAc,CAAC;AAChC,QAAA,sBAAsB,GAAG,UAAU,CAAC;AAGjD,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAE3B,QAAA,QAAQ,GAAG;IACtB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,UAAU,GAAG;IACxB,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;IACT,QAAQ,EAAE,CAAC;CACH,CAAC;AAGE,QAAA,qBAAqB,GAAG,aAAa,CAAC;AAGtC,QAAA,kBAAkB,GAAG,CAAC,CAAC;AAEvB,QAAA,eAAe,GAAG,CAAC,CAAC;AAEpB,QAAA,kBAAkB,GAAG,EAAE,CAAC;AAErC,MAAM,UAAU,GAAG,EAAE,CAAC;AAGtB,MAAM,iBAAiB,GAAG,UAAU,CAAC;AACxB,QAAA,uBAAuB,GAAG,QAAQ,CAAC;AAGnC,QAAA,WAAW,GAAG;IACzB,MAAM,EAAE,GAAG,qBAAY,SAAS;IAChC,YAAY,EAAE,GAAG,qBAAY,eAAe;IAC5C,aAAa,EAAE,GAAG,qBAAY,gBAAgB;IAC9C,eAAe,EAAE,GAAG,qBAAY,kBAAkB;IAClD,GAAG,EAAE,GAAG,qBAAY,cAAc;IAClC,MAAM,EAAE,GAAG,qBAAY,iBAAiB;IACxC,OAAO,EAAE,GAAG,qBAAY,kBAAkB;IAC1C,SAAS,EAAE,GAAG,qBAAY,4BAA4B,uBAAe,EAAE;IACvE,KAAK,EAAE,GAAG,qBAAY,uBAAuB,UAAU,EAAE;IACzD,aAAa,EAAE,GAAG,qBAAY,+BAA+B,UAAU,EAAE;IACzE,OAAO,EAAE,GAAG,qBAAY,UAAU;IAClC,cAAc,EAAE,GAAG,qBAAY,iBAAiB;IAChD,WAAW,EAAE,GAAG,qBAAY,qBAAqB;IACjD,mBAAmB,EAAE,GAAG,qBAAY,4BAA4B;IAChE,WAAW,EAAE,GAAG,qBAAY,qBAAqB;IACjD,QAAQ,EAAE,GAAG,qBAAY,WAAW;IACpC,eAAe,EAAE,GAAG,qBAAY,sBAAsB;IACtD,cAAc,EAAE,GAAG,qBAAY,iBAAiB;IAChD,eAAe,EAAE,GAAG,qBAAY,mBAAmB;IACnD,QAAQ,EAAE,GAAG,qBAAY,WAAW;CAC5B,CAAC;AAGE,QAAA,UAAU,GAAG;IACxB,OAAO,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,OAAO,EAAE;IACnD,UAAU,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,OAAO,YAAY;IAChE,QAAQ,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,EAAE;IACrD,QAAQ,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,EAAE;IACrD,cAAc,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,aAAa,iBAAiB,YAAY,+BAAuB,EAAE;CACpH,CAAC"}
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/constants/pages.ts"],"names":[],"mappings":";;;AAAA,qCAAwC;AAE3B,QAAA,cAAc,GAAG,cAAc,CAAC;AAChC,QAAA,sBAAsB,GAAG,UAAU,CAAC;AAGjD,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAE3B,QAAA,QAAQ,GAAG;IACtB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;CACZ,CAAC;AAEE,QAAA,UAAU,GAAG;IACxB,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;IACX,MAAM,EAAE,CAAC;IACT,QAAQ,EAAE,CAAC;CACH,CAAC;AAGE,QAAA,qBAAqB,GAAG,aAAa,CAAC;AAGtC,QAAA,kBAAkB,GAAG,CAAC,CAAC;AAEvB,QAAA,eAAe,GAAG,CAAC,CAAC;AAEpB,QAAA,kBAAkB,GAAG,EAAE,CAAC;AAErC,MAAM,UAAU,GAAG,EAAE,CAAC;AAGtB,MAAM,iBAAiB,GAAG,UAAU,CAAC;AACxB,QAAA,uBAAuB,GAAG,QAAQ,CAAC;AAGnC,QAAA,WAAW,GAAG;IACzB,MAAM,EAAE,GAAG,qBAAY,SAAS;IAChC,YAAY,EAAE,GAAG,qBAAY,eAAe;IAC5C,aAAa,EAAE,GAAG,qBAAY,gBAAgB;IAC9C,eAAe,EAAE,GAAG,qBAAY,kBAAkB;IAClD,GAAG,EAAE,GAAG,qBAAY,cAAc;IAClC,MAAM,EAAE,GAAG,qBAAY,iBAAiB;IACxC,OAAO,EAAE,GAAG,qBAAY,kBAAkB;IAC1C,SAAS,EAAE,GAAG,qBAAY,4BAA4B,uBAAe,EAAE;IACvE,KAAK,EAAE,GAAG,qBAAY,uBAAuB,UAAU,EAAE;IACzD,aAAa,EAAE,GAAG,qBAAY,+BAA+B,UAAU,EAAE;IACzE,OAAO,EAAE,GAAG,qBAAY,UAAU;IAClC,cAAc,EAAE,GAAG,qBAAY,iBAAiB;IAChD,WAAW,EAAE,GAAG,qBAAY,2BAA2B;IACvD,mBAAmB,EAAE,GAAG,qBAAY,mCAAmC;IACvE,WAAW,EAAE,GAAG,qBAAY,2BAA2B;IACvD,QAAQ,EAAE,GAAG,qBAAY,WAAW;IACpC,eAAe,EAAE,GAAG,qBAAY,sBAAsB;IACtD,cAAc,EAAE,GAAG,qBAAY,iBAAiB;IAChD,eAAe,EAAE,GAAG,qBAAY,mBAAmB;IACnD,QAAQ,EAAE,GAAG,qBAAY,WAAW;CAC5B,CAAC;AAGE,QAAA,UAAU,GAAG;IACxB,OAAO,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,OAAO,EAAE;IACnD,UAAU,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,OAAO,kBAAkB;IACtE,QAAQ,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,EAAE;IACrD,QAAQ,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,EAAE;IACrD,cAAc,EAAE,GAAG,iBAAiB,IAAI,gBAAQ,CAAC,QAAQ,aAAa,iBAAiB,YAAY,+BAAuB,EAAE;CACpH,CAAC"}
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ async function start() {
30
30
  sourcePath: node_path_1.default.join(__dirname, frontend_module_1.FRONTEND_MODULE_DIR),
31
31
  renderer: { name: "vue", version: "3" },
32
32
  priority: frontend_module_1.FRONTEND_MODULE_PRIORITY,
33
+ backendProxies: [frontend_module_1.FRONTEND_BACKEND_PROXY],
33
34
  });
34
35
  const [moduleRoots, skillDirs, builderEnabled] = await Promise.all([
35
36
  (0, module_roots_1.collectModuleRoots)(),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAmBA,8BAEC;AAED,sBAyBC;AAOD,0BAEC;AAzDD,0DAA6B;AAC7B,gEAA6D;AAC7D,yDAAmE;AACnE,iDAAwD;AACxD,kDAAsD;AACtD,qCAA6D;AAC7D,iEAIqC;AACrC,iDAAyD;AACzD,2DAA8D;AAC9D,6DAAgE;AAChE,6DAAyD;AACzD,qDAAuD;AACvD,mBAAiB;AACjB,oBAAkB;AAElB,SAAgB,SAAS,CAAC,MAAe;IACvC,IAAA,kBAAS,EAAC,IAAA,oBAAW,EAAC,MAAM,CAAC,CAAC,CAAC;AACjC,CAAC;AAEM,KAAK,UAAU,KAAK;IACzB,IAAA,4BAAe,GAAE,CAAC;IAClB,MAAM,IAAA,wBAAiB,EAAC;QACtB,IAAI,EAAE,sCAAoB;QAC1B,UAAU,EAAE,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,qCAAmB,CAAC;QACrD,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;QACvC,QAAQ,EAAE,0CAAwB;KACnC,CAAC,CAAC;IAIH,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjE,IAAA,iCAAkB,GAAE;QACpB,IAAA,mCAAmB,GAAE;QACrB,IAAA,6BAAkB,GAAE;KACrB,CAAC,CAAC;IACH,KAAK,IAAA,4BAAY,EAAC;QAChB,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE;QAC9B,GAAG,IAAA,kBAAS,GAAE;QACd,WAAW;QACX,SAAS;QACT,cAAc;KACf,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACf,iBAAO,CAAC,KAAK,CAAC,GAAG,4BAAkB,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC;AAOD,SAAgB,OAAO;IACrB,IAAA,0BAAe,GAAE,CAAC;AACpB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAoBA,8BAEC;AAED,sBA0BC;AAOD,0BAEC;AA3DD,0DAA6B;AAC7B,gEAA6D;AAC7D,yDAAmE;AACnE,iDAAwD;AACxD,kDAAsD;AACtD,qCAA6D;AAC7D,iEAKqC;AACrC,iDAAyD;AACzD,2DAA8D;AAC9D,6DAAgE;AAChE,6DAAyD;AACzD,qDAAuD;AACvD,mBAAiB;AACjB,oBAAkB;AAElB,SAAgB,SAAS,CAAC,MAAe;IACvC,IAAA,kBAAS,EAAC,IAAA,oBAAW,EAAC,MAAM,CAAC,CAAC,CAAC;AACjC,CAAC;AAEM,KAAK,UAAU,KAAK;IACzB,IAAA,4BAAe,GAAE,CAAC;IAClB,MAAM,IAAA,wBAAiB,EAAC;QACtB,IAAI,EAAE,sCAAoB;QAC1B,UAAU,EAAE,mBAAI,CAAC,IAAI,CAAC,SAAS,EAAE,qCAAmB,CAAC;QACrD,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE;QACvC,QAAQ,EAAE,0CAAwB;QAClC,cAAc,EAAE,CAAC,wCAAsB,CAAC;KACzC,CAAC,CAAC;IAIH,MAAM,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjE,IAAA,iCAAkB,GAAE;QACpB,IAAA,mCAAmB,GAAE;QACrB,IAAA,6BAAkB,GAAE;KACrB,CAAC,CAAC;IACH,KAAK,IAAA,4BAAY,EAAC;QAChB,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE;QAC9B,GAAG,IAAA,kBAAS,GAAE;QACd,WAAW;QACX,SAAS;QACT,cAAc;KACf,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACf,iBAAO,CAAC,KAAK,CAAC,GAAG,4BAAkB,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC;AAOD,SAAgB,OAAO;IACrB,IAAA,0BAAe,GAAE,CAAC;AACpB,CAAC"}
@@ -82,8 +82,6 @@ let AISkillsPage = class AISkillsPage extends (0, page_1.PageController)(pages_1
82
82
  labelKey: "name",
83
83
  searchPlaceholder: text("search"),
84
84
  displays: [{ id: CARDS_DISPLAY }],
85
- tableDisplay: false,
86
- defaultDisplay: CARDS_DISPLAY,
87
85
  pageSize: CARDS_PAGE_SIZE,
88
86
  defaultSort: { field: "uses30d", desc: true },
89
87
  card: {
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/pages/skills/page.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/pages/skills/page.ts"],"names":[],"mappings":";;;;;;;;;AAAA,yDAIwC;AACxC,kEAAwE;AACxE,0EAA6E;AAC7E,oEAAuE;AAEvE,yDAA8E;AAC9E,+CAAqD;AACrD,iDAK+B;AAC/B,iDAK0B;AAC1B,kCAAoE;AAEpE,MAAM,OAAO,GAAG,oBAAa,CAAC,MAAM,CAAC;AACrC,MAAM,aAAa,GAAG,OAAO,CAAC;AAC9B,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,SAAS,IAAI,CAAC,GAAG,QAAkB;IACjC,OAAO,IAAA,oBAAO,EAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,OAAO,GAA0C;IACrD,IAAI,EAAE;QACJ,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC;QAC7B,IAAI,EAAE,IAAI,6BAAgB,CAAC,UAAU,EAAE;QACvC,OAAO,EAAE,IAAI,sBAAe,CAAC,WAAW,EAAE;QAC1C,IAAI,EAAE,gBAAgB;QACtB,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD,WAAW,EAAE;QACX,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC;QACpC,IAAI,EAAE,IAAI,6BAAgB,CAAC,UAAU,EAAE;QACvC,KAAK,EAAE,CAAC;KACT;IACD,MAAM,EAAE;QACN,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC;QAC/B,IAAI,EAAE,IAAI,6BAAgB,CAAC,UAAU,CAAC;YACpC,KAAK,EAAE,IAAA,8BAAiB,EAAC,OAAO,EAAE,QAAQ,EAAE,0BAAa,CAAC;SAC3D,CAAC;QACF,OAAO,EAAE,IAAI,sBAAe,CAAC,iBAAiB,CAAC;YAC7C,KAAK,EAAE,IAAA,4BAAe,EAAC,0BAAa,CAAC;YACrC,QAAQ,EAAE,QAAQ;SACnB,CAAC;QACF,UAAU,EAAE,IAAI;QAChB,KAAK,EAAE,CAAC;KACT;IACD,OAAO,EAAE;QACP,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC;QAC7B,IAAI,EAAE,IAAI,6BAAgB,CAAC,UAAU,EAAE;QACvC,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD,UAAU,EAAE;QACV,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC;QAClC,IAAI,EAAE,IAAI,6BAAgB,CAAC,QAAQ,EAAE;QACrC,OAAO,EAAE,IAAI,sBAAe,CAAC,mBAAmB,CAAC;YAC/C,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC;SAC/B,CAAC;QACF,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;CACF,CAAC;AAUK,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,IAAA,qBAAc,EAC9C,gBAAQ,CAAC,MAAM,EACf,IAAA,eAAQ,EAAC,gBAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,kBAAU,CAAC,MAAM,CAAC,EAC1D,IAAA,uBAAa,EAAC;IACZ,aAAa,EAAE;QACb;YACE,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC;YAC5B,IAAI,EAAE,eAAe;YACrB,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAU,CAAC,cAAc,EAAE;SACzD;KACF;CACF,CAAC,CACH;IACC,MAAM,CAAC,SAAS,GAAG,IAAA,aAAM,EAAC,EAAE,QAAQ,EAAE,mBAAW,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CACvE,IAAA,gBAAS,EAAC,kBAAkB,EAAE,cAAc,CAAC,CAC9C,CAAC;IAEF,MAAM,CAAC,OAAO,GAAG,gBAAS,CAAC,UAAU,CAAC;QACpC,QAAQ,EAAE,mBAAW,CAAC,cAAc;QACpC,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;QAC9B,OAAO,EAAE,OAAO;QAChB,QAAQ,EAAE,MAAM;QAChB,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC;QACjC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC;QACjC,QAAQ,EAAE,eAAe;QACzB,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;QAC7C,IAAI,EAAE;YACJ,SAAS,EAAE,IAAA,wBAAe,EAAC,gBAAgB,CAAC,CAAC,IAAI,CAC/C,IAAA,gBAAS,EAAC,aAAa,EAAE,YAAY,CAAC,CACvC;SACF;QACD,IAAI,EAAE;YACJ,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE;YAC7D,GAAG,MAAM,CAAC,OAAO,CAAC,0BAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzD,EAAE,EAAE,MAAM;gBACV,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;gBAC3B,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE;aAC7D,CAAC,CAAC;SACJ;QACD,UAAU,EAAE;YACV,MAAM,EAAE;gBACN;oBACE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC;oBAC7B,IAAI,EAAE,qBAAqB;oBAC3B,SAAS,EAAE,IAAI;oBACf,QAAQ,EAAE,IAAI;oBACd,MAAM,EAAE;wBACN,IAAI,EAAE,QAAQ;wBACd,SAAS,EAAE,IAAA,wBAAe,EAAC,kBAAkB,CAAC,CAAC,IAAI,CACjD,IAAA,gBAAS,EAAC,eAAe,EAAE,qBAAqB,CAAC,CAClD;wBACD,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;wBAC9B,SAAS,EAAE,2BAAoB;qBAChC;iBACF;aACF;SACF;QACD,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;QACrC,WAAW,EAAE;YACX,QAAQ,EAAE;gBACR,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC;gBAC7B,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC;gBACzC,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE;oBACP,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,EAAE,EAAE,kBAAU,CAAC,cAAc,EAAE;iBAClE;aACF;YACD,KAAK,EAAE;gBACL,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC;gBAC/B,WAAW,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC;gBAC3C,IAAI,EAAE,YAAY;aACnB;SACF;KACF,CAAC,CAAC,IAAI,CAAC,IAAA,gBAAS,EAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC;;AA1ExC,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAY,GAAE;GACF,YAAY,CA2ExB"}
@@ -1,11 +1,11 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch } from "vue";
2
+ import { computed, ref } from "vue";
3
3
  import DecisionLabel from "../views/DecisionLabel.vue";
4
4
  import DiffView from "../views/DiffView.vue";
5
5
  import RowStepper from "../views/RowStepper.vue";
6
6
  import ResultPill from "../views/ResultPill.vue";
7
7
  import UnavailableState from "../views/UnavailableState.vue";
8
- import { useAiRequest } from "../views/composables/useAiRequest";
8
+ import { useAiData } from "../views/composables/useAiData";
9
9
  import { useAssistant } from "../views/composables/useAssistant";
10
10
  import { useViewFormat } from "../views/composables/useViewFormat";
11
11
  import { AI_PAGES, AI_ROUTES, CHANGE_SET_QUERY_KEY } from "../views/constants";
@@ -30,29 +30,27 @@ interface Props {
30
30
  const props = defineProps<Props>();
31
31
 
32
32
  const { t } = useI18n();
33
- const { $authFetch } = useAuthFetch();
34
33
  const { moment, toolLabel, clock } = useViewFormat();
35
34
  const assistant = useAssistant();
36
- const detail = useAiRequest<ActivityDetailPayload>();
37
35
  const isRawShown = ref(false);
38
36
 
39
37
  const OUTPUT_MAX_LINES = 14;
40
38
  const RAW_MAX_LINES = 20;
41
39
 
42
40
  const rowId = computed(() => props.rowData?.id ?? props.rowData?._id);
41
+ // Read with the page: a deep link (`?record=<id>`) renders the entry on the
42
+ // server and hands it over. The drawer opened again shows the entry read
43
+ // before and reads it again, as a pending call may have ended since.
44
+ const detail = useAiData<ActivityDetailPayload>(() =>
45
+ rowId.value
46
+ ? `${AI_ROUTES.activity}/${encodeURIComponent(rowId.value)}`
47
+ : null,
48
+ );
43
49
 
44
50
  function load(): void {
45
- const id = rowId.value;
46
- if (!id) return;
47
- void detail.run(() =>
48
- $authFetch<ActivityDetailPayload>(
49
- `${AI_ROUTES.activity}/${encodeURIComponent(id)}`,
50
- ),
51
- );
51
+ void detail.refresh();
52
52
  }
53
53
 
54
- watch(rowId, load, { immediate: true });
55
-
56
54
  const row = computed(() => props.rowData);
57
55
  const eyebrow = computed(() => {
58
56
  const current = row.value;
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, ref, watch } from "vue";
2
+ import { computed, ref, watch } from "vue";
3
3
  import { useDmsRoute } from "#dms/frontend-module";
4
4
  import ChangeSetPanel from "../views/ChangeSetPanel.vue";
5
5
  import UnavailableState from "../views/UnavailableState.vue";
6
- import { useAiRequest } from "../views/composables/useAiRequest";
6
+ import { useAiData, useAiPagedData } from "../views/composables/useAiData";
7
7
  import { useAssistant } from "../views/composables/useAssistant";
8
8
  import { useViewFormat } from "../views/composables/useViewFormat";
9
9
  import {
@@ -20,34 +20,18 @@ import type {
20
20
  import {
21
21
  changeSetIcon,
22
22
  matchesSearch,
23
- readSelectedSet,
24
23
  writeSelectedSet,
25
24
  } from "../views/utils/change-sets";
26
25
 
27
26
  type ScopeFilter = ScopeId | "all";
28
27
 
29
28
  const { t } = useI18n();
30
- const { $authFetch } = useAuthFetch();
31
29
  const { moment } = useViewFormat();
32
30
  const assistant = useAssistant();
33
31
  const route = useDmsRoute();
34
32
 
35
- const list = useAiRequest<ListPayload<ChangeSetSummary>>();
36
- const status = useAiRequest<AssistantStatus>();
37
- const changeSets = ref<ChangeSetSummary[]>([]);
38
- const total = ref(0);
39
33
  const scope = ref<ScopeFilter>("all");
40
34
  const search = ref("");
41
- const selectedId = ref<string | undefined>(readSelectedSet());
42
- // The list loads on the client only: until its first answer the server and
43
- // the hydrating client both draw the skeleton, never a premature empty state.
44
- const hasLoaded = ref(false);
45
-
46
- const SCOPE_ITEMS = computed(() => [
47
- { value: "all", label: t("dms_ai.views.changes.filter_all") },
48
- { value: "safe", label: t("dms_ai.views.changes.filter_safe") },
49
- { value: "vibe", label: t("dms_ai.views.changes.filter_code") },
50
- ]);
51
35
 
52
36
  function listQuery(offset: number): Record<string, string | number> {
53
37
  const query: Record<string, string | number> = {
@@ -58,46 +42,69 @@ function listQuery(offset: number): Record<string, string | number> {
58
42
  return { ...query, scope: scope.value, filter_scope: `is:${scope.value}` };
59
43
  }
60
44
 
61
- async function loadPage(offset: number): Promise<void> {
62
- const page = await list.run(() =>
63
- $authFetch<ListPayload<ChangeSetSummary>>(AI_ROUTES.changes, {
64
- query: listQuery(offset),
45
+ type ChangesPage = ListPayload<ChangeSetSummary>;
46
+
47
+ /** The sets the pages hold, in order. */
48
+ function loadedCount(pages: ChangesPage[]): number {
49
+ return pages.reduce((count, page) => count + page.results.length, 0);
50
+ }
51
+
52
+ // The status and the list's first page are read with the page: a server
53
+ // render shows them, and the selected set's panel, at once. "Load more"
54
+ // appends a page under the scope's key; the page's refreshes read every
55
+ // page loaded again.
56
+ const status = useAiData<AssistantStatus>(AI_ROUTES.status);
57
+ const list = useAiPagedData<ChangesPage, number>(
58
+ () => `dms-ai:changes:${scope.value}`,
59
+ ({ $authFetch, cursor, signal }) =>
60
+ $authFetch<ChangesPage>(AI_ROUTES.changes, {
61
+ query: listQuery(cursor ?? 0),
62
+ signal,
65
63
  }),
66
- );
67
- hasLoaded.value = true;
68
- if (!page) return;
69
- changeSets.value =
70
- offset === 0 ? page.results : [...changeSets.value, ...page.results];
71
- total.value = page.total;
72
- if (selectedId.value === undefined) selectedId.value = page.results[0]?.id;
64
+ {
65
+ nextCursor: (page, pages) => {
66
+ const loaded = loadedCount(pages);
67
+ return page.results.length > 0 && loaded < page.total ? loaded : null;
68
+ },
69
+ },
70
+ );
71
+ const changeSets = computed(() =>
72
+ list.pages.value.flatMap((page) => page.results),
73
+ );
74
+ const total = computed(() => list.pages.value.at(-1)?.total ?? 0);
75
+
76
+ function selectedFromRoute(): string | undefined {
77
+ const id = route.query[CHANGE_SET_QUERY_KEY];
78
+ return typeof id === "string" && id ? id : undefined;
73
79
  }
74
80
 
81
+ /** The set picked here or named by the URL (`?set=`); the first one until then. */
82
+ const pickedId = ref<string | undefined>(selectedFromRoute());
83
+ const selectedId = computed(() => pickedId.value ?? changeSets.value[0]?.id);
84
+
85
+ const SCOPE_ITEMS = computed(() => [
86
+ { value: "all", label: t("dms_ai.views.changes.filter_all") },
87
+ { value: "safe", label: t("dms_ai.views.changes.filter_safe") },
88
+ { value: "vibe", label: t("dms_ai.views.changes.filter_code") },
89
+ ]);
90
+
75
91
  function reload(): Promise<void> {
76
- return loadPage(0);
92
+ return list.refresh();
77
93
  }
78
94
 
95
+ /** Appends the next page; a failure keeps the sets listed and the button. */
79
96
  function loadMore(): Promise<void> {
80
- return loadPage(changeSets.value.length);
97
+ return list.loadMore();
81
98
  }
82
99
 
83
- onMounted(() => {
84
- void status.run(() => $authFetch<AssistantStatus>(AI_ROUTES.status));
85
- void reload();
100
+ watch(selectedFromRoute, (id) => {
101
+ if (id) pickedId.value = id;
86
102
  });
87
103
 
88
- watch(scope, reload);
89
-
90
- watch(
91
- () => route.query[CHANGE_SET_QUERY_KEY],
92
- (id) => {
93
- if (id) selectedId.value = id;
94
- },
95
- );
96
-
97
104
  function select(id: string | number | undefined): void {
98
105
  if (id === undefined) return;
99
- selectedId.value = String(id);
100
- writeSelectedSet(selectedId.value);
106
+ pickedId.value = String(id);
107
+ writeSelectedSet(pickedId.value);
101
108
  }
102
109
 
103
110
  function sublabel(set: ChangeSetSummary): string {
@@ -134,9 +141,10 @@ const isCheckpointOff = computed(
134
141
  );
135
142
  const isFirstLoad = computed(
136
143
  () =>
137
- (!hasLoaded.value || list.isLoading.value) && changeSets.value.length === 0,
144
+ changeSets.value.length === 0 &&
145
+ (list.pending.value || list.status.value === "idle"),
138
146
  );
139
- const hasMore = computed(() => changeSets.value.length < total.value);
147
+ const hasMore = computed(() => list.hasMore.value);
140
148
  const listLabel = computed(() =>
141
149
  t("dms_ai.views.changes.list_label", { count: total.value }),
142
150
  );
@@ -230,7 +238,6 @@ const emptyActions = computed(() =>
230
238
  v-if="selectedId"
231
239
  :key="selectedId"
232
240
  :change-set-id="selectedId"
233
- @changed="reload"
234
241
  />
235
242
  </DmsMasterDetail>
236
243
  <div v-if="hasMore" class="flex lg:w-[380px] lg:justify-center">
@@ -239,7 +246,7 @@ const emptyActions = computed(() =>
239
246
  variant="outline"
240
247
  size="sm"
241
248
  icon="i-ph-caret-down"
242
- :loading="list.isLoading.value"
249
+ :loading="list.loadingMore.value"
243
250
  :label="t('dms_ai.views.common.load_more')"
244
251
  @click="loadMore"
245
252
  />
@@ -70,6 +70,7 @@ import { runWhenLoggedIn } from "../runtime/session-gate";
70
70
  import { installToggleShortcut } from "../runtime/shortcuts";
71
71
  import {
72
72
  createSidecarStatusController,
73
+ type SidecarInfoPayload,
73
74
  type SidecarStatus,
74
75
  type SidecarStatusController,
75
76
  } from "../runtime/sidecar-status";
@@ -395,8 +396,13 @@ function createRuntime(
395
396
  const holds = createStreamHolds();
396
397
  const sidecarStatus = followSidecarStatus(controller, client);
397
398
  const activity = createAssistantActivity({
398
- fetchStatus: () =>
399
- deps.$authFetch(STATUS_PATH) as Promise<AssistantStatusPayload>,
399
+ // The application's read: polled for as long as the app runs, while
400
+ // the interval says so.
401
+ readStatus: (interval) =>
402
+ useApiData<AssistantStatusPayload>(STATUS_PATH, {
403
+ refreshInterval: interval,
404
+ refreshOnPageRefresh: false,
405
+ }),
400
406
  chat: chat.transport,
401
407
  shouldPoll: () =>
402
408
  !panel.isOpen.value ||
@@ -413,7 +419,6 @@ function createRuntime(
413
419
  activity,
414
420
  currentPage,
415
421
  });
416
- void activity.refresh();
417
422
  const stops = [
418
423
  installToggleShortcut({ onToggle: panel.toggle }),
419
424
  followPanel({ panel, activity, holds }, client),
@@ -462,8 +467,13 @@ async function startAssistant(
462
467
  devReload: useDevReload(),
463
468
  router: useDmsRouter(),
464
469
  };
465
- const controller = createSidecarStatusController(() =>
466
- deps.$authFetch(SIDECAR_INFO_PATH),
470
+ // Read once the gate opens, then polled while the sidecar comes back.
471
+ const controller = createSidecarStatusController((interval) =>
472
+ useApiData<SidecarInfoPayload>(SIDECAR_INFO_PATH, {
473
+ immediate: false,
474
+ refreshInterval: interval,
475
+ refreshOnPageRefresh: false,
476
+ }),
467
477
  );
468
478
  if (!(await controller.init())) {
469
479
  context.runWithContext(() => unregisterSidePanel(SIDE_PANEL_ID));
@@ -496,7 +506,7 @@ export default defineDmsPlugin((context) => {
496
506
  const appMounted = new Promise<void>((resolve) =>
497
507
  context.hook("app:mounted", resolve),
498
508
  );
499
- const { loggedIn } = useUserSession();
509
+ const { loggedIn } = useDmsUserSession();
500
510
  const stopGate = runWhenLoggedIn(
501
511
  () => loggedIn.value,
502
512
  () =>
@@ -1,4 +1,4 @@
1
- import { computed, type ComputedRef, type Ref, ref } from "vue";
1
+ import { computed, type ComputedRef, type Ref, ref, watch } from "vue";
2
2
  import type { ChatTransport } from "./chat-transport";
3
3
  import { STATUS_POLL_MS } from "./constants";
4
4
  import { isTypedMessage } from "./typed-message";
@@ -35,10 +35,24 @@ export interface AssistantActivity {
35
35
  stop: () => void;
36
36
  }
37
37
 
38
+ /** The read of the backend's status: `useApiData`, polled every `interval`. */
39
+ export interface AssistantStatusRead {
40
+ data: Readonly<Ref<AssistantStatusPayload | null>>;
41
+ refresh: () => Promise<void>;
42
+ }
43
+
44
+ /**
45
+ * Opens the status read, polled every `interval` ms while it is above 0 and
46
+ * not polled while it is 0.
47
+ */
48
+ export type OpenAssistantStatusRead = (
49
+ interval: () => number,
50
+ ) => AssistantStatusRead;
51
+
38
52
  export interface AssistantActivityOptions {
39
- fetchStatus: () => Promise<AssistantStatusPayload>;
53
+ readStatus: OpenAssistantStatusRead;
40
54
  chat: ChatTransport;
41
- /** Whether polling is useful now: the tab shows and the panel is closed. */
55
+ /** Whether polling is useful now: the panel is closed, or its stream is down. */
42
56
  shouldPoll: () => boolean;
43
57
  }
44
58
 
@@ -55,8 +69,9 @@ function sum(
55
69
 
56
70
  /**
57
71
  * What the tab knows of the assistant's work across chats, from the backend's
58
- * status (polled while the panel is closed) and from the conversation lists
59
- * the sidecar broadcasts while the stream is open.
72
+ * status (polled while the panel is closed or its stream down: the polling
73
+ * follows `shouldPoll`) and from the conversation lists the sidecar
74
+ * broadcasts while the stream is open.
60
75
  */
61
76
  export function createAssistantActivity(
62
77
  options: AssistantActivityOptions,
@@ -65,16 +80,21 @@ export function createAssistantActivity(
65
80
  const workingConversations = ref(0);
66
81
  const lastError = ref<string | null>(null);
67
82
 
68
- const refresh = async (): Promise<void> => {
69
- try {
70
- const status = await options.fetchStatus();
71
- pendingApprovals.value = status.pendingApprovals ?? 0;
72
- workingConversations.value = status.workingConversations ?? 0;
73
- lastError.value = status.lastError ?? null;
74
- } catch {
75
- return;
76
- }
77
- };
83
+ const isStopped = ref(false);
84
+ const status = options.readStatus(() =>
85
+ !isStopped.value && options.shouldPoll() ? STATUS_POLL_MS : 0,
86
+ );
87
+ // A failed read keeps the last answer: the counts stay as they were.
88
+ const stopFollowing = watch(
89
+ status.data,
90
+ (payload) => {
91
+ if (!payload) return;
92
+ pendingApprovals.value = payload.pendingApprovals ?? 0;
93
+ workingConversations.value = payload.workingConversations ?? 0;
94
+ lastError.value = payload.lastError ?? null;
95
+ },
96
+ { immediate: true },
97
+ );
78
98
 
79
99
  const stopListening = options.chat.onMessage((msg) => {
80
100
  if (!isListFrame(msg)) return;
@@ -85,10 +105,6 @@ export function createAssistantActivity(
85
105
  );
86
106
  });
87
107
 
88
- const timer = setInterval(() => {
89
- if (options.shouldPoll()) void refresh();
90
- }, STATUS_POLL_MS);
91
-
92
108
  return {
93
109
  pendingApprovals,
94
110
  workingConversations,
@@ -96,9 +112,10 @@ export function createAssistantActivity(
96
112
  () => pendingApprovals.value > 0 || workingConversations.value > 0,
97
113
  ),
98
114
  lastError,
99
- refresh,
115
+ refresh: () => status.refresh(),
100
116
  stop: () => {
101
- clearInterval(timer);
117
+ isStopped.value = true;
118
+ stopFollowing();
102
119
  stopListening();
103
120
  },
104
121
  };