@kabmo-labs/kabmo 0.5.2 → 0.6.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 (124) hide show
  1. package/dist/client/index.html +1 -1
  2. package/dist/client/index.js +359 -350
  3. package/dist/client/static/js/{index-zcvn50cf.js → index-ae9z0yvm.js} +703 -703
  4. package/dist/client/static/js/index-nzyjf92m.css +1 -0
  5. package/marketplace/plugins/stats/README.md +26 -4
  6. package/marketplace/plugins/stats/package.json +2 -2
  7. package/marketplace/plugins/stats/src/HeaderReadout.test.tsx +157 -0
  8. package/marketplace/plugins/stats/src/HeaderReadout.tsx +115 -0
  9. package/marketplace/plugins/stats/src/app.tsx +11 -0
  10. package/marketplace/plugins/stats/src/format.test.ts +27 -1
  11. package/marketplace/plugins/stats/src/format.ts +41 -0
  12. package/package.json +2 -2
  13. package/src/client/bootstrap.d.ts +11 -0
  14. package/src/client/components/common/header-controls/MoreMenu.tsx +38 -1
  15. package/src/client/components/common/header-controls/index.tsx +9 -2
  16. package/src/client/components/common/header-controls/plugin-entry.test.tsx +229 -0
  17. package/src/client/components/common/header-panel-buttons/entries.test.ts +46 -0
  18. package/src/client/components/common/header-panel-buttons/entries.ts +109 -0
  19. package/src/client/components/common/header-panel-buttons/index.tsx +71 -38
  20. package/src/client/components/common/server-switcher/index.test.tsx +150 -0
  21. package/src/client/components/common/server-switcher/index.tsx +281 -0
  22. package/src/client/components/common/window-controls/index.test.tsx +113 -0
  23. package/src/client/components/common/window-controls/index.tsx +66 -0
  24. package/src/client/components/layout/desktop-layout/LeftColumn.tsx +3 -6
  25. package/src/client/components/layout/desktop-layout/TopNavbar.tsx +59 -3
  26. package/src/client/components/layout/desktop-layout/index.tsx +34 -26
  27. package/src/client/components/layout/session-sidebar/Footer.tsx +12 -12
  28. package/src/client/components/layout/session-sidebar/Header.tsx +4 -2
  29. package/src/client/components/mobile/LayoutWrapper.tsx +36 -0
  30. package/src/client/components/mobile/RightSidebar.tsx +2 -2
  31. package/src/client/components/mobile/SessionSidebar.tsx +10 -15
  32. package/src/client/components/mobile/mobile-session-sidebar/Footer.tsx +15 -12
  33. package/src/client/components/mobile/mobile-session-sidebar/Item.tsx +9 -2
  34. package/src/client/components/mobile/mobile-session-sidebar/folder-gap.test.ts +41 -0
  35. package/src/client/components/mobile/mobile-session-sidebar/folder-gap.ts +39 -0
  36. package/src/client/components/mobile/mobile-session-sidebar/use-dock.ts +6 -6
  37. package/src/client/components/settings/categories/OmpSettings.tsx +25 -29
  38. package/src/client/components/settings/categories/omp-settings/PresetBar.tsx +187 -0
  39. package/src/client/components/settings/categories/omp-settings/RoleEditor.test.ts +260 -32
  40. package/src/client/components/settings/categories/omp-settings/RoleEditor.tsx +198 -102
  41. package/src/client/components/settings/categories/omp-settings/RoleRow.tsx +79 -0
  42. package/src/client/data/settings/omp-schema.json +2 -2
  43. package/src/client/hooks/chat/omp/session-list.test.ts +0 -24
  44. package/src/client/hooks/desktop/servers.test.ts +73 -0
  45. package/src/client/hooks/desktop/servers.ts +126 -0
  46. package/src/client/hooks/settings/model-roles.ts +156 -0
  47. package/src/client/hooks/ui/updates/shell-download.test.ts +157 -0
  48. package/src/client/hooks/ui/updates/shell-download.ts +220 -0
  49. package/src/client/hooks/workspace/keybindings.ts +44 -0
  50. package/src/client/hooks/workspace/open-panel.ts +42 -0
  51. package/src/client/lib/desktop/bridge.ts +158 -0
  52. package/src/client/lib/desktop/titlebar.test.ts +99 -0
  53. package/src/client/lib/desktop/titlebar.ts +97 -0
  54. package/src/client/lib/plugins/slots.test.ts +54 -0
  55. package/src/client/lib/plugins/slots.ts +37 -1
  56. package/src/server/db.server.test.ts +44 -3
  57. package/src/server/db.server.ts +26 -2
  58. package/src/server/lib/bundler/build-errors.test.ts +0 -6
  59. package/src/server/lib/bundler/titlebar-css.test.ts +75 -0
  60. package/src/server/lib/db/schema.ts +3 -0
  61. package/src/server/lib/model-roles/catalog.server.test.ts +75 -0
  62. package/src/server/lib/model-roles/catalog.server.ts +112 -0
  63. package/src/server/lib/model-roles/mock.server.ts +40 -0
  64. package/src/server/lib/model-roles/schema.server.ts +36 -0
  65. package/src/server/lib/model-roles/store.server.test.ts +218 -0
  66. package/src/server/lib/model-roles/store.server.ts +188 -0
  67. package/src/server/lib/omp/rpc/destroy-stream-row.test.ts +4 -4
  68. package/src/server/lib/omp/rpc/frame-fold.test.ts +30 -1
  69. package/src/server/lib/omp/rpc/frame-fold.ts +6 -1
  70. package/src/server/lib/omp/rpc/manager.ts +18 -13
  71. package/src/server/lib/omp/rpc/session-command-guards.ts +4 -0
  72. package/src/server/lib/omp/rpc/session-commands.test.ts +24 -0
  73. package/src/server/lib/omp/rpc/session-commands.ts +7 -0
  74. package/src/server/lib/omp/session/auto-title.server.test.ts +147 -93
  75. package/src/server/lib/omp/session/auto-title.server.ts +150 -120
  76. package/src/server/lib/omp/session/rename-with-ai.server.test.ts +18 -6
  77. package/src/server/lib/omp/session/rename-with-ai.server.ts +36 -20
  78. package/src/server/lib/omp/session/title-model-overlay.server.test.ts +29 -14
  79. package/src/server/lib/omp/session/title-model-overlay.server.ts +46 -25
  80. package/src/server/lib/omp/session/title-request.server.test.ts +183 -0
  81. package/src/server/lib/omp/session/title-request.server.ts +213 -0
  82. package/src/server/lib/panels/baseline.server.test.ts +4 -4
  83. package/src/server/lib/panels/compat.server.test.ts +4 -4
  84. package/src/server/lib/panels/registry-bundled.server.test.ts +4 -4
  85. package/src/server/lib/panels/registry.server.test.ts +4 -4
  86. package/src/server/lib/panels/state.server.test.ts +4 -4
  87. package/src/server/lib/panels/sync.server.test.ts +4 -4
  88. package/src/server/lib/plugins/background.test.ts +2 -2
  89. package/src/server/lib/plugins/e2e.test.ts +2 -2
  90. package/src/server/lib/plugins/platform.test.ts +2 -2
  91. package/src/server/lib/queue/delivery-dispatch.server.test.ts +34 -0
  92. package/src/server/lib/queue/delivery.server.ts +11 -2
  93. package/src/server/lib/updates/shell-release.test.ts +184 -0
  94. package/src/server/lib/updates/shell-release.ts +215 -0
  95. package/src/server/lib/waiting/store.server.test.ts +2 -2
  96. package/src/server/plugins/ssr.ts +13 -10
  97. package/src/server/routes/chat/stream.test.ts +4 -4
  98. package/src/server/routes/fs/git-merge.test.ts +0 -11
  99. package/src/server/routes/models/roles.ts +73 -14
  100. package/src/server/routes/panels/css.test.ts +4 -4
  101. package/src/server/routes/panels/panel-routes.test-util.ts +3 -3
  102. package/src/server/routes/panels/routes.test.ts +0 -7
  103. package/src/server/routes/plugins/routes.test.ts +4 -4
  104. package/src/server/routes/sessions/batch-delete.test.ts +4 -4
  105. package/src/server/routes/updates/android.ts +2 -2
  106. package/src/server/routes/updates/index.ts +2 -0
  107. package/src/server/routes/updates/ios.ts +26 -0
  108. package/src/server/routes/waiting/routes.test.ts +2 -2
  109. package/src/shared/lib/models/server-cache.ts +11 -0
  110. package/src/shared/lib/omp/config/model-roles.ts +129 -0
  111. package/src/shared/lib/updates/shell-ua.test.ts +56 -24
  112. package/src/shared/lib/updates/shell-ua.ts +41 -20
  113. package/src/shared/lib/workspace/mobile-dock.test.ts +7 -13
  114. package/src/shared/lib/workspace/mobile-dock.ts +15 -6
  115. package/src/shared/lib/workspace/panel-open.test.ts +56 -0
  116. package/src/shared/lib/workspace/panel-open.ts +76 -0
  117. package/src/shared/types/model-roles.ts +54 -0
  118. package/src/test-support/isolated-db.ts +17 -5
  119. package/dist/client/static/js/index-3qamd33d.css +0 -1
  120. package/src/client/components/layout/desktop-layout/left-column.test.tsx +0 -192
  121. package/src/client/hooks/ui/updates/android-download.test.ts +0 -112
  122. package/src/client/hooks/ui/updates/android-download.ts +0 -143
  123. package/src/server/lib/updates/android-release.test.ts +0 -138
  124. package/src/server/lib/updates/android-release.ts +0 -183
@@ -1,183 +0,0 @@
1
- /**
2
- * Which Android APK a browser should download, and whether this one is behind.
3
- *
4
- * The APK is NOT an npm package (see `.github/workflows/publish-android.yml`), so
5
- * the npm probe that answers for the app and for `omp` cannot answer here. The
6
- * only record of a published APK is a GitHub release asset, which means one
7
- * unauthenticated REST read of the release LIST — not `/releases/latest`, which
8
- * answers the newest release of ANY kind (see `fetchAndroidRelease`).
9
- *
10
- * Two facts travel back, and they answer different questions:
11
- *
12
- * - `url` is the asset to download. It is resolved from the release's OWN asset
13
- * list rather than built as `releases/latest/download/<name>`, because that
14
- * path needs the asset name to match exactly and the name carries the tag
15
- * (`kabmo-android-v1.2.3.apk`) — a name built here would be a second copy of
16
- * the workflow's naming rule, and the first release that renames it 404s.
17
- * - `latest` is the version that asset ships, read from the asset name. The
18
- * release TAG cannot be used: the APK is attached to the app's release, whose
19
- * tag names the SERVER version, and the two move independently.
20
- *
21
- * Failure is a normal answer here (`null`), never a throw: an offline box or a
22
- * rate-limited GitHub must not take the sidebar down, and a button that appears
23
- * because a probe failed would offer a download that does not exist.
24
- */
25
-
26
- import { REPO } from '@/server/lib/updates/release-url';
27
-
28
- /** One published APK, as the console needs it. */
29
- export interface AndroidRelease {
30
- /** Asset download URL, or null when the release carries no APK. */
31
- url: string | null;
32
- /** Version the asset name declares (`v1.2.3` → `1.2.3`), or null. */
33
- latest: string | null;
34
- /** The release page, for a "what changed" link. */
35
- releaseUrl: string | null;
36
- }
37
-
38
- /** The asset-name prefix the release workflow writes. */
39
- const ASSET_PREFIX = 'kabmo-android-';
40
-
41
- /**
42
- * `kabmo-android-v1.2.3.apk` → `1.2.3`.
43
- *
44
- * The `v` is optional because a hand-uploaded asset may omit it, and a version
45
- * that cannot be read is not evidence of an update — the caller treats null as
46
- * "no new version".
47
- */
48
- export function versionFromAssetName(name: string): string | null {
49
- if (!name.startsWith(ASSET_PREFIX) || !name.endsWith('.apk')) return null;
50
- const middle = name.slice(ASSET_PREFIX.length, -'.apk'.length);
51
- const version = middle.startsWith('v') ? middle.slice(1) : middle;
52
- return /^\d+\.\d+\.\d+/.test(version) ? version : null;
53
- }
54
-
55
- /**
56
- * The newest release that actually carries an APK.
57
- *
58
- * NOT `/releases/latest`, which answers the newest release of ANY kind. This
59
- * repository publishes three artifacts from one push, and the packages are
60
- * released FIRST (`packages → marketplace → android → app` in `release.yml`),
61
- * so their `kabmo-sdk/v*` / `kabmo-ui/v*` releases are newer than the app
62
- * release that carries the APK. `@semantic-release/github` runs in both
63
- * pipelines, so those are real releases — measured against a stub that answered
64
- * a package release: `/releases/latest` produced `{url: null, latest: null}` and
65
- * the sidebar's button disappeared even though the APK sat on the release
66
- * directly below.
67
- *
68
- * One request, cached for five minutes on `globalThis` for the same reason the
69
- * database handle is anchored there: `bun --hot` re-evaluates this module while
70
- * the cache it would otherwise hold lives on. GitHub allows 60 unauthenticated
71
- * reads an hour per address, and the sidebar asks on every mount.
72
- */
73
- export async function fetchAndroidRelease(fetchImpl: typeof fetch = fetch): Promise<AndroidRelease | null> {
74
- const cache = cacheSlot();
75
- const now = Date.now();
76
- if (cache.entry && now - cache.at < CACHE_MS) return cache.entry;
77
-
78
- try {
79
- const response = await fetchImpl(`${githubApiBase()}/repos/${REPO}/releases?per_page=${RELEASE_SCAN}`, {
80
- headers: {
81
- accept: 'application/vnd.github+json',
82
- // GitHub rejects a request with no User-Agent.
83
- 'user-agent': 'kabmo-console',
84
- },
85
- });
86
- if (!response.ok) return cache.entry ?? null;
87
-
88
- const body = (await response.json()) as unknown;
89
- // A body that is not the release list is a failed probe, not an empty
90
- // repository: answering `{url: null, latest: null}` here would be
91
- // indistinguishable from "no release carries an APK", and the caller would
92
- // cache that mistake for five minutes.
93
- if (!Array.isArray(body)) return cache.entry ?? null;
94
-
95
- // Drafts are not published and prereleases are not what a console offers an
96
- // installed app — the same two the `latest` endpoint excluded.
97
- const published = body.filter(
98
- (entry): entry is Record<string, unknown> =>
99
- entry !== null && typeof entry === 'object' && entry.draft !== true && entry.prerelease !== true,
100
- );
101
-
102
- const release = published
103
- .map((entry) => ({ entry, asset: apkAsset(entry.assets) }))
104
- .find((candidate) => candidate.asset !== null);
105
-
106
- if (!release?.asset) {
107
- const fallback: AndroidRelease = { url: null, latest: null, releaseUrl: null };
108
- cache.entry = fallback;
109
- cache.at = now;
110
- return fallback;
111
- }
112
-
113
- const resolved: AndroidRelease = {
114
- url: typeof release.asset.browser_download_url === 'string' ? release.asset.browser_download_url : null,
115
- latest: versionFromAssetName(String(release.asset.name)),
116
- releaseUrl: typeof release.entry.html_url === 'string' ? release.entry.html_url : null,
117
- };
118
-
119
- cache.entry = resolved;
120
- cache.at = now;
121
- return resolved;
122
- } catch {
123
- // Keep the last good answer rather than replacing it with a failure: a
124
- // momentary outage must not remove a download button that was valid.
125
- return cache.entry ?? null;
126
- }
127
- }
128
-
129
- /** How many releases to look back through for the APK. */
130
- const RELEASE_SCAN = 20;
131
-
132
- /** The first asset of a release that names an APK, or null. */
133
- function apkAsset(assets: unknown): Record<string, unknown> | null {
134
- if (!Array.isArray(assets)) return null;
135
-
136
- for (const entry of assets) {
137
- if (entry === null || typeof entry !== 'object') continue;
138
- const asset = entry as Record<string, unknown>;
139
- if (typeof asset.name === 'string' && versionFromAssetName(asset.name) !== null) return asset;
140
- }
141
-
142
- return null;
143
- }
144
-
145
- const CACHE_MS = 5 * 60_000;
146
-
147
- /**
148
- * Where the release read goes.
149
- *
150
- * Overridable for the same reason the npm probe honours `NPM_CONFIG_REGISTRY`: a
151
- * test and a rehearsal need to exercise the real parsing against a stub instead
152
- * of a live GitHub call, and hardcoding the host makes that impossible without
153
- * intercepting `fetch` globally.
154
- */
155
- function githubApiBase(): string {
156
- const configured = Bun.env.KABMO_GITHUB_API ?? '';
157
- return configured.trim().replace(/\/+$/, '') || 'https://api.github.com';
158
- }
159
-
160
- interface CacheSlot {
161
- entry: AndroidRelease | null;
162
- at: number;
163
- }
164
-
165
- function cacheSlot(): CacheSlot {
166
- const host = globalThis as typeof globalThis & { __kabmoAndroidRelease?: CacheSlot };
167
- host.__kabmoAndroidRelease ??= { entry: null, at: 0 };
168
- return host.__kabmoAndroidRelease;
169
- }
170
-
171
- /**
172
- * Drop the cached answer.
173
- *
174
- * The cache lives on `globalThis` so `bun --hot` cannot orphan it, which also
175
- * means it outlives a test file: without this seam the second case in a suite
176
- * reads the first case's answer and passes for the wrong reason (measured — the
177
- * "malformed body" case returned the previous test's release). Production has no
178
- * caller; it exists because the cache is process-wide by design.
179
- */
180
- export function resetAndroidReleaseCache(): void {
181
- cacheSlot().entry = null;
182
- cacheSlot().at = 0;
183
- }