vibepulse 0.2.2 → 0.3.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -0
- package/.next/build-manifest.json +2 -2
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +8 -0
- package/.next/server/app/_global-error/page.js +1 -1
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/sessions/route.js +4 -2
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +1 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__31d19c5c._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__56f5f249._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__98073dd6._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__b7b717eb._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__d8e61048._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__f441109e._.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js.map +1 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js.map +1 -1
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js.map +1 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js.map → [root-of-the-server]__c91a8380._.js.map} +1 -1
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +8 -0
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/node/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/node/sessions/route.js +5 -3
- package/.next/standalone/.next/server/app/api/node/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/opencode-config/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/apply/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/profiles/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/archive/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js +3 -2
- package/.next/standalone/.next/server/app/api/sessions/[id]/delete/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/open-editor/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js +8 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/sessions/[id]/restore/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/sessions/route.js +4 -2
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +1 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__31d19c5c._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__56f5f249._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5e0a0e38._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__98073dd6._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b7b717eb._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8e61048._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f441109e._.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_sessions_[id]_restore_route_actions_af7d6b6c.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_2edc9589.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_7f178d4a.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_aca45402.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_b054aff3.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_d0c0f338.js +3 -0
- package/.next/standalone/.next/server/chunks/src_lib_session-providers_claudeCode_ts_0f9590ed._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__631e12d0._.js → [root-of-the-server]__c91a8380._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/src_app_page_tsx_a7111f3e._.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/standalone/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/standalone/README.md +29 -5
- package/.next/standalone/docs/session-status-detection.md +36 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-09-claude-capability-alignment-design.md +39 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.test.ts +60 -1
- package/.next/standalone/src/app/api/node/sessions/[id]/archive/route.ts +77 -22
- package/.next/standalone/src/app/api/node/sessions/route.test.ts +282 -0
- package/.next/standalone/src/app/api/node/sessions/route.ts +141 -17
- package/.next/standalone/src/app/api/opencode-events/route.test.ts +3 -1
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.test.ts +101 -0
- package/.next/standalone/src/app/api/sessions/[id]/archive/route.ts +47 -12
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.test.ts +92 -0
- package/.next/standalone/src/app/api/sessions/[id]/delete/route.ts +45 -10
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.test.ts +74 -0
- package/.next/standalone/src/app/api/sessions/[id]/open-editor/route.ts +22 -2
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.test.ts +186 -0
- package/.next/standalone/src/app/api/sessions/[id]/restore/route.ts +184 -0
- package/.next/standalone/src/app/api/sessions/route.test.ts +1889 -107
- package/.next/standalone/src/app/api/sessions/route.ts +365 -981
- package/.next/standalone/src/components/KanbanBoard.test.tsx +307 -1
- package/.next/standalone/src/components/KanbanBoard.tsx +105 -18
- package/.next/standalone/src/components/ProjectCard.test.tsx +416 -2
- package/.next/standalone/src/components/ProjectCard.tsx +238 -86
- package/.next/standalone/src/components/SessionCard.test.tsx +253 -2
- package/.next/standalone/src/components/SessionCard.tsx +182 -76
- package/.next/standalone/src/hooks/useOpencodeSync.test.ts +321 -1
- package/.next/standalone/src/hooks/useOpencodeSync.ts +16 -12
- package/.next/standalone/src/lib/claudeSessionOverrides.test.ts +75 -0
- package/.next/standalone/src/lib/claudeSessionOverrides.ts +169 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.test.ts +2288 -0
- package/.next/standalone/src/lib/session-providers/claudeCode.ts +1083 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.test.ts +322 -0
- package/.next/standalone/src/lib/session-providers/localAggregator.ts +302 -0
- package/.next/standalone/src/lib/session-providers/opencodeProvider.ts +723 -0
- package/.next/standalone/src/lib/session-providers/providerIds.test.ts +337 -0
- package/.next/standalone/src/lib/session-providers/providerIds.ts +176 -0
- package/.next/standalone/src/lib/session-providers/types.ts +131 -0
- package/.next/standalone/src/lib/transform.test.ts +253 -0
- package/.next/standalone/src/lib/transform.ts +96 -37
- package/.next/standalone/src/types/index.ts +23 -17
- package/.next/static/chunks/b3bc362202331708.css +3 -0
- package/.next/static/chunks/{65d5354ba0add961.js → c1294e057d8d4681.js} +3 -3
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +9 -0
- package/README.md +29 -5
- package/package.json +1 -1
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__2f981540._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__3745b314._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__6c428a24._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__73a00b88._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__db285678._.js.map +0 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2f981540._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3745b314._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c428a24._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__73a00b88._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__db285678._.js +0 -3
- package/.next/standalone/.next/static/chunks/f42202943f6742e5.css +0 -3
- package/.next/static/chunks/f42202943f6742e5.css +0 -3
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_buildManifest.js +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{5kq9DtuBFVxu4jsgmL5Q- → bsWNvgDS7Zp38Yt9q0DUg}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
isClaudeUuid,
|
|
4
|
+
namespaceClaudeRawId,
|
|
5
|
+
extractRawIdFromNamespaced,
|
|
6
|
+
detectProviderFromRawId,
|
|
7
|
+
normalizeProviderRawId,
|
|
8
|
+
composeProviderSourceKey,
|
|
9
|
+
extractProviderRawId,
|
|
10
|
+
isSessionProvider,
|
|
11
|
+
mergeProviderContext,
|
|
12
|
+
DEFAULT_PROVIDER_CONTEXT,
|
|
13
|
+
READONLY_PROVIDER_CONTEXT,
|
|
14
|
+
} from './providerIds';
|
|
15
|
+
|
|
16
|
+
describe('isClaudeUuid', () => {
|
|
17
|
+
it('returns true for standard UUIDs', () => {
|
|
18
|
+
expect(isClaudeUuid('550e8400-e29b-41d4-a716-446655440000')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns true for uppercase UUIDs', () => {
|
|
22
|
+
expect(isClaudeUuid('550E8400-E29B-41D4-A716-446655440000')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('returns false for OpenCode-style IDs', () => {
|
|
26
|
+
expect(isClaudeUuid('ses_1744181234567_build')).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('returns false for plain alphanumeric IDs', () => {
|
|
30
|
+
expect(isClaudeUuid('session123')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('namespaceClaudeRawId', () => {
|
|
35
|
+
it('prefixes Claude UUIDs with claude~', () => {
|
|
36
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
37
|
+
expect(namespaceClaudeRawId(uuid)).toBe('claude~550e8400-e29b-41d4-a716-446655440000');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('leaves OpenCode IDs unchanged', () => {
|
|
41
|
+
expect(namespaceClaudeRawId('ses_1744181234567_build')).toBe('ses_1744181234567_build');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('extractRawIdFromNamespaced', () => {
|
|
46
|
+
it('extracts UUID from namespaced Claude ID', () => {
|
|
47
|
+
expect(extractRawIdFromNamespaced('claude~550e8400-e29b-41d4-a716-446655440000')).toBe('550e8400-e29b-41d4-a716-446655440000');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('preserves reserved-looking non-UUID ids unchanged', () => {
|
|
51
|
+
expect(extractRawIdFromNamespaced('claude~ses_1744181234567_build')).toBe('claude~ses_1744181234567_build');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns OpenCode IDs unchanged', () => {
|
|
55
|
+
expect(extractRawIdFromNamespaced('ses_1744181234567_build')).toBe('ses_1744181234567_build');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('detectProviderFromRawId', () => {
|
|
60
|
+
it('detects namespaced Claude IDs with standard UUID format', () => {
|
|
61
|
+
expect(detectProviderFromRawId('claude~550e8400-e29b-41d4-a716-446655440000')).toBe('claude-code');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('detects namespaced Claude source keys with host prefixes', () => {
|
|
65
|
+
expect(detectProviderFromRawId('local:claude~550E8400-E29B-41D4-A716-446655440000')).toBe('claude-code');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('detects scoped Claude sidechain ids as claude-code', () => {
|
|
69
|
+
expect(detectProviderFromRawId('550e8400-e29b-41d4-a716-446655440000__agent-a123')).toBe('claude-code');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('detects host-prefixed scoped Claude sidechain ids as claude-code', () => {
|
|
73
|
+
expect(detectProviderFromRawId('local:550e8400-e29b-41d4-a716-446655440000__agent-a123')).toBe('claude-code');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('does not treat non-uuid scoped agent ids as claude-code', () => {
|
|
77
|
+
expect(detectProviderFromRawId('session123__agent-a123')).toBe('opencode');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('defaults to opencode for plain UUID-like ids without claude namespace', () => {
|
|
81
|
+
expect(detectProviderFromRawId('550e8400-e29b-41d4-a716-446655440000')).toBe('opencode');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('defaults to opencode for OpenCode-style IDs with underscores', () => {
|
|
85
|
+
expect(detectProviderFromRawId('ses_1744181234567_build')).toBe('opencode');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('defaults to opencode for plain alphanumeric IDs', () => {
|
|
89
|
+
expect(detectProviderFromRawId('session123')).toBe('opencode');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('defaults to opencode for IDs with dashes but not UUID format', () => {
|
|
93
|
+
expect(detectProviderFromRawId('my-session-id')).toBe('opencode');
|
|
94
|
+
expect(detectProviderFromRawId('session-123-abc')).toBe('opencode');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('normalizeProviderRawId', () => {
|
|
99
|
+
it('namespaces Claude UUIDs when provider is explicitly claude-code', () => {
|
|
100
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
101
|
+
const result = normalizeProviderRawId(uuid, 'claude-code');
|
|
102
|
+
expect(result.normalizedId).toBe('claude~550e8400-e29b-41d4-a716-446655440000');
|
|
103
|
+
expect(result.provider).toBe('claude-code');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('preserves OpenCode IDs as-is with default provider detection', () => {
|
|
107
|
+
const id = 'ses_1744181234567_build';
|
|
108
|
+
const result = normalizeProviderRawId(id);
|
|
109
|
+
expect(result.normalizedId).toBe(id);
|
|
110
|
+
expect(result.provider).toBe('opencode');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('treats plain UUID-like ids as opencode unless provider is explicit', () => {
|
|
114
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
115
|
+
const result = normalizeProviderRawId(uuid);
|
|
116
|
+
expect(result.normalizedId).toBe(uuid);
|
|
117
|
+
expect(result.provider).toBe('opencode');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('composeProviderSourceKey', () => {
|
|
122
|
+
it('creates composite key for OpenCode sessions with writable default', () => {
|
|
123
|
+
const result = composeProviderSourceKey('local', 'ses_1744181234567_build');
|
|
124
|
+
expect(result.sourceKey).toBe('local:ses_1744181234567_build');
|
|
125
|
+
expect(result.provider).toBe('opencode');
|
|
126
|
+
expect(result.readOnly).toBe(false);
|
|
127
|
+
expect(result.capabilities).toEqual({
|
|
128
|
+
openProject: true,
|
|
129
|
+
openEditor: true,
|
|
130
|
+
archive: true,
|
|
131
|
+
delete: true,
|
|
132
|
+
});
|
|
133
|
+
expect(result.topology).toEqual({
|
|
134
|
+
childSessions: 'authoritative',
|
|
135
|
+
});
|
|
136
|
+
expect(result.providerRawId).toBe('ses_1744181234567_build');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('creates composite key for explicit Claude sessions with claude~ prefix and read-only default', () => {
|
|
140
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
141
|
+
const result = composeProviderSourceKey('local', uuid, { provider: 'claude-code' });
|
|
142
|
+
expect(result.sourceKey).toBe('local:claude~550e8400-e29b-41d4-a716-446655440000');
|
|
143
|
+
expect(result.provider).toBe('claude-code');
|
|
144
|
+
expect(result.readOnly).toBe(true);
|
|
145
|
+
expect(result.capabilities).toEqual({
|
|
146
|
+
openProject: true,
|
|
147
|
+
openEditor: false,
|
|
148
|
+
archive: true,
|
|
149
|
+
delete: true,
|
|
150
|
+
});
|
|
151
|
+
expect(result.topology).toEqual({
|
|
152
|
+
childSessions: 'flat',
|
|
153
|
+
});
|
|
154
|
+
expect(result.providerRawId).toBe(uuid);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('allows overriding readOnly for Claude sessions', () => {
|
|
158
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
159
|
+
const result = composeProviderSourceKey('local', uuid, { provider: 'claude-code', readOnly: false });
|
|
160
|
+
expect(result.readOnly).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('allows overriding readOnly for OpenCode sessions', () => {
|
|
164
|
+
const result = composeProviderSourceKey('local', 'ses_123', { readOnly: true });
|
|
165
|
+
expect(result.readOnly).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('preserves hostId in composite key for remote hosts', () => {
|
|
169
|
+
const result = composeProviderSourceKey('remote-1', 'ses_1744181234567_build');
|
|
170
|
+
expect(result.sourceKey).toBe('remote-1:ses_1744181234567_build');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('maintains exactly one colon in final app ID for OpenCode', () => {
|
|
174
|
+
const result = composeProviderSourceKey('local', 'ses_123');
|
|
175
|
+
const colonCount = (result.sourceKey.match(/:/g) || []).length;
|
|
176
|
+
expect(colonCount).toBe(1);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('maintains exactly one colon in final app ID for explicit Claude sessions (namespace is part of sessionId)', () => {
|
|
180
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
181
|
+
const result = composeProviderSourceKey('local', uuid, { provider: 'claude-code' });
|
|
182
|
+
const colonCount = (result.sourceKey.match(/:/g) || []).length;
|
|
183
|
+
expect(colonCount).toBe(1);
|
|
184
|
+
expect(result.sourceKey).toBe('local:claude~550e8400-e29b-41d4-a716-446655440000');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('maintains hostId:sessionId contract for all provider types', () => {
|
|
188
|
+
const openCodeResult = composeProviderSourceKey('local', 'ses_123');
|
|
189
|
+
const claudeResult = composeProviderSourceKey('local', '550e8400-e29b-41d4-a716-446655440000', { provider: 'claude-code' });
|
|
190
|
+
|
|
191
|
+
expect(openCodeResult.sourceKey).toMatch(/^[^:]+:[^:]+$/);
|
|
192
|
+
expect(claudeResult.sourceKey).toMatch(/^[^:]+:[^:]+$/);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('extractProviderRawId', () => {
|
|
197
|
+
it('extracts raw ID from OpenCode composite key', () => {
|
|
198
|
+
expect(extractProviderRawId('local:ses_123')).toBe('ses_123');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('extracts original UUID from namespaced Claude composite key', () => {
|
|
202
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
203
|
+
expect(extractProviderRawId(`local:claude~${uuid}`)).toBe(uuid);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('returns plain OpenCode ID as-is when no host prefix', () => {
|
|
207
|
+
expect(extractProviderRawId('ses_123')).toBe('ses_123');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('returns original UUID from plain Claude ID as-is', () => {
|
|
211
|
+
const uuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
212
|
+
expect(extractProviderRawId(uuid)).toBe(uuid);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('isSessionProvider', () => {
|
|
217
|
+
it('returns true for opencode', () => {
|
|
218
|
+
expect(isSessionProvider('opencode')).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('returns true for claude-code', () => {
|
|
222
|
+
expect(isSessionProvider('claude-code')).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('returns false for invalid values', () => {
|
|
226
|
+
expect(isSessionProvider('claude')).toBe(false);
|
|
227
|
+
expect(isSessionProvider('invalid')).toBe(false);
|
|
228
|
+
expect(isSessionProvider(null)).toBe(false);
|
|
229
|
+
expect(isSessionProvider(undefined)).toBe(false);
|
|
230
|
+
expect(isSessionProvider(123)).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('mergeProviderContext', () => {
|
|
235
|
+
it('uses defaults when no context provided', () => {
|
|
236
|
+
const result = mergeProviderContext({});
|
|
237
|
+
expect(result.provider).toBe(DEFAULT_PROVIDER_CONTEXT.provider);
|
|
238
|
+
expect(result.readOnly).toBe(DEFAULT_PROVIDER_CONTEXT.readOnly);
|
|
239
|
+
expect(result.capabilities).toEqual(DEFAULT_PROVIDER_CONTEXT.capabilities);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('overrides provider when specified', () => {
|
|
243
|
+
const result = mergeProviderContext({ provider: 'claude-code' });
|
|
244
|
+
expect(result.provider).toBe('claude-code');
|
|
245
|
+
expect(result.readOnly).toBe(READONLY_PROVIDER_CONTEXT.readOnly);
|
|
246
|
+
expect(result.capabilities).toEqual(READONLY_PROVIDER_CONTEXT.capabilities);
|
|
247
|
+
expect(result.topology).toEqual(READONLY_PROVIDER_CONTEXT.topology);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('overrides readOnly when specified', () => {
|
|
251
|
+
const result = mergeProviderContext({ readOnly: true });
|
|
252
|
+
expect(result.readOnly).toBe(true);
|
|
253
|
+
expect(result.provider).toBe(DEFAULT_PROVIDER_CONTEXT.provider);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('overrides both when specified', () => {
|
|
257
|
+
const result = mergeProviderContext({ provider: 'claude-code', readOnly: true });
|
|
258
|
+
expect(result.provider).toBe('claude-code');
|
|
259
|
+
expect(result.readOnly).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('allows explicit authoritative topology overrides without changing Claude defaults', () => {
|
|
263
|
+
const result = mergeProviderContext({
|
|
264
|
+
provider: 'claude-code',
|
|
265
|
+
topology: { childSessions: 'authoritative' },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(result.provider).toBe('claude-code');
|
|
269
|
+
expect(result.readOnly).toBe(READONLY_PROVIDER_CONTEXT.readOnly);
|
|
270
|
+
expect(result.capabilities).toEqual(READONLY_PROVIDER_CONTEXT.capabilities);
|
|
271
|
+
expect(result.topology).toEqual({ childSessions: 'authoritative' });
|
|
272
|
+
expect(READONLY_PROVIDER_CONTEXT.topology).toEqual({ childSessions: 'flat' });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('DEFAULT_PROVIDER_CONTEXT', () => {
|
|
277
|
+
it('defaults to opencode provider', () => {
|
|
278
|
+
expect(DEFAULT_PROVIDER_CONTEXT.provider).toBe('opencode');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('defaults to writable', () => {
|
|
282
|
+
expect(DEFAULT_PROVIDER_CONTEXT.readOnly).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('defaults to authoritative child topology', () => {
|
|
286
|
+
expect(DEFAULT_PROVIDER_CONTEXT.topology).toEqual({ childSessions: 'authoritative' });
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('READONLY_PROVIDER_CONTEXT', () => {
|
|
291
|
+
it('uses claude-code provider', () => {
|
|
292
|
+
expect(READONLY_PROVIDER_CONTEXT.provider).toBe('claude-code');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('is read-only', () => {
|
|
296
|
+
expect(READONLY_PROVIDER_CONTEXT.readOnly).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('defaults to flat child topology until authoritative linkage is provided', () => {
|
|
300
|
+
expect(READONLY_PROVIDER_CONTEXT.topology).toEqual({ childSessions: 'flat' });
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe('collision safety', () => {
|
|
305
|
+
it('OpenCode and Claude IDs with same base do not collide when namespaced', () => {
|
|
306
|
+
const openCodeId = 'ses-123-456';
|
|
307
|
+
const claudeId = '12345678-1234-1234-1234-123456789abc';
|
|
308
|
+
|
|
309
|
+
const openCodeResult = composeProviderSourceKey('local', openCodeId);
|
|
310
|
+
const claudeResult = composeProviderSourceKey('local', claudeId, { provider: 'claude-code' });
|
|
311
|
+
|
|
312
|
+
expect(openCodeResult.sourceKey).not.toBe(claudeResult.sourceKey);
|
|
313
|
+
expect(openCodeResult.provider).toBe('opencode');
|
|
314
|
+
expect(claudeResult.provider).toBe('claude-code');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('different hosts create different keys for same raw ID', () => {
|
|
318
|
+
const rawId = 'ses_123';
|
|
319
|
+
const localResult = composeProviderSourceKey('local', rawId);
|
|
320
|
+
const remoteResult = composeProviderSourceKey('remote-1', rawId);
|
|
321
|
+
|
|
322
|
+
expect(localResult.sourceKey).not.toBe(remoteResult.sourceKey);
|
|
323
|
+
expect(localResult.sourceKey).toBe('local:ses_123');
|
|
324
|
+
expect(remoteResult.sourceKey).toBe('remote-1:ses_123');
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('rejects hypothetical OpenCode IDs in the reserved claude namespace', () => {
|
|
328
|
+
const claudeUuid = '550e8400-e29b-41d4-a716-446655440000';
|
|
329
|
+
const hypotheticalOpenCodeId = 'claude~550e8400-e29b-41d4-a716-446655440000';
|
|
330
|
+
|
|
331
|
+
const claudeResult = composeProviderSourceKey('local', claudeUuid, { provider: 'claude-code' });
|
|
332
|
+
|
|
333
|
+
expect(claudeResult.sourceKey).toBe('local:claude~550e8400-e29b-41d4-a716-446655440000');
|
|
334
|
+
expect(claudeResult.provider).toBe('claude-code');
|
|
335
|
+
expect(() => composeProviderSourceKey('local', hypotheticalOpenCodeId)).toThrow(/reserved claude namespace/i);
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { SessionCapabilities, SessionProvider } from '@/types';
|
|
2
|
+
import { composeSourceKey, getSessionIdFromSourceKey } from '../hostIdentity';
|
|
3
|
+
import type { ProviderTopology } from './types';
|
|
4
|
+
|
|
5
|
+
export type ProviderContext = {
|
|
6
|
+
provider: SessionProvider;
|
|
7
|
+
readOnly: boolean;
|
|
8
|
+
capabilities: SessionCapabilities;
|
|
9
|
+
topology: ProviderTopology;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const OPENCODE_CAPABILITIES: SessionCapabilities = {
|
|
13
|
+
openProject: true,
|
|
14
|
+
openEditor: true,
|
|
15
|
+
archive: true,
|
|
16
|
+
delete: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const CLAUDE_CAPABILITIES: SessionCapabilities = {
|
|
20
|
+
openProject: true,
|
|
21
|
+
openEditor: false,
|
|
22
|
+
archive: true,
|
|
23
|
+
delete: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const OPENCODE_TOPOLOGY: ProviderTopology = {
|
|
27
|
+
childSessions: 'authoritative',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const CLAUDE_TOPOLOGY: ProviderTopology = {
|
|
31
|
+
childSessions: 'flat',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_PROVIDER_CONTEXT: ProviderContext = {
|
|
35
|
+
provider: 'opencode',
|
|
36
|
+
readOnly: false,
|
|
37
|
+
capabilities: OPENCODE_CAPABILITIES,
|
|
38
|
+
topology: OPENCODE_TOPOLOGY,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const READONLY_PROVIDER_CONTEXT: ProviderContext = {
|
|
42
|
+
provider: 'claude-code',
|
|
43
|
+
readOnly: true,
|
|
44
|
+
capabilities: CLAUDE_CAPABILITIES,
|
|
45
|
+
topology: CLAUDE_TOPOLOGY,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function getDefaultProviderContext(provider: SessionProvider): ProviderContext {
|
|
49
|
+
return provider === 'claude-code' ? READONLY_PROVIDER_CONTEXT : DEFAULT_PROVIDER_CONTEXT;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const CLAUDE_NAMESPACE_PREFIX = 'claude~';
|
|
53
|
+
|
|
54
|
+
function isReservedClaudeNamespacedUuid(rawId: string): boolean {
|
|
55
|
+
if (!rawId.startsWith(CLAUDE_NAMESPACE_PREFIX)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return isClaudeUuid(rawId.slice(CLAUDE_NAMESPACE_PREFIX.length));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isScopedClaudeSidechainSessionId(rawId: string): boolean {
|
|
63
|
+
const separatorIndex = rawId.indexOf('__');
|
|
64
|
+
if (separatorIndex <= 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const parentSessionId = rawId.slice(0, separatorIndex);
|
|
69
|
+
const sidechainSessionId = rawId.slice(separatorIndex + 2);
|
|
70
|
+
if (!sidechainSessionId.startsWith('agent-')) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return isClaudeUuid(parentSessionId) || isReservedClaudeNamespacedUuid(parentSessionId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function isClaudeUuid(rawId: string): boolean {
|
|
78
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(rawId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function detectProviderFromRawId(rawId: string): SessionProvider {
|
|
82
|
+
const sourceSessionId = getSessionIdFromSourceKey(rawId);
|
|
83
|
+
const candidate = sourceSessionId ?? rawId;
|
|
84
|
+
|
|
85
|
+
if (isReservedClaudeNamespacedUuid(candidate) || isScopedClaudeSidechainSessionId(candidate)) {
|
|
86
|
+
return 'claude-code';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return 'opencode';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function namespaceClaudeRawId(rawId: string): string {
|
|
93
|
+
if (isClaudeUuid(rawId)) {
|
|
94
|
+
return `${CLAUDE_NAMESPACE_PREFIX}${rawId}`;
|
|
95
|
+
}
|
|
96
|
+
return rawId;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function extractRawIdFromNamespaced(namespacedId: string): string {
|
|
100
|
+
if (isReservedClaudeNamespacedUuid(namespacedId)) {
|
|
101
|
+
return namespacedId.slice(CLAUDE_NAMESPACE_PREFIX.length);
|
|
102
|
+
}
|
|
103
|
+
return namespacedId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeProviderRawId(rawId: string, providerHint?: SessionProvider): {
|
|
107
|
+
normalizedId: string;
|
|
108
|
+
provider: SessionProvider;
|
|
109
|
+
} {
|
|
110
|
+
if (isReservedClaudeNamespacedUuid(rawId)) {
|
|
111
|
+
throw new Error(`Invalid raw session id: reserved claude namespace (${rawId})`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const provider = providerHint ?? detectProviderFromRawId(rawId);
|
|
115
|
+
|
|
116
|
+
if (provider === 'claude-code') {
|
|
117
|
+
return { normalizedId: namespaceClaudeRawId(rawId), provider };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { normalizedId: rawId, provider };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function composeProviderSourceKey(
|
|
124
|
+
hostId: string,
|
|
125
|
+
rawId: string,
|
|
126
|
+
overrides?: Partial<ProviderContext>
|
|
127
|
+
): {
|
|
128
|
+
sourceKey: string;
|
|
129
|
+
provider: SessionProvider;
|
|
130
|
+
readOnly: boolean;
|
|
131
|
+
capabilities: SessionCapabilities;
|
|
132
|
+
topology: ProviderTopology;
|
|
133
|
+
providerRawId: string;
|
|
134
|
+
} {
|
|
135
|
+
const { normalizedId, provider } = normalizeProviderRawId(rawId, overrides?.provider);
|
|
136
|
+
const providerDefaults = getDefaultProviderContext(provider);
|
|
137
|
+
const defaultReadOnly = providerDefaults.readOnly;
|
|
138
|
+
const readOnly = overrides?.readOnly ?? defaultReadOnly;
|
|
139
|
+
const capabilities = overrides?.capabilities ?? providerDefaults.capabilities;
|
|
140
|
+
const topology = overrides?.topology ?? providerDefaults.topology;
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
sourceKey: composeSourceKey(hostId, normalizedId),
|
|
144
|
+
provider,
|
|
145
|
+
readOnly,
|
|
146
|
+
capabilities,
|
|
147
|
+
topology,
|
|
148
|
+
providerRawId: rawId,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function extractProviderRawId(sessionId: string): string {
|
|
153
|
+
const fromSourceKey = getSessionIdFromSourceKey(sessionId);
|
|
154
|
+
if (fromSourceKey) {
|
|
155
|
+
return extractRawIdFromNamespaced(fromSourceKey);
|
|
156
|
+
}
|
|
157
|
+
return extractRawIdFromNamespaced(sessionId);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function isSessionProvider(value: unknown): value is SessionProvider {
|
|
161
|
+
return value === 'opencode' || value === 'claude-code';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function mergeProviderContext(
|
|
165
|
+
context: Partial<ProviderContext>
|
|
166
|
+
): ProviderContext {
|
|
167
|
+
const provider = context.provider ?? DEFAULT_PROVIDER_CONTEXT.provider;
|
|
168
|
+
const providerDefaults = getDefaultProviderContext(provider);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
provider,
|
|
172
|
+
readOnly: context.readOnly ?? providerDefaults.readOnly,
|
|
173
|
+
capabilities: context.capabilities ?? providerDefaults.capabilities,
|
|
174
|
+
topology: context.topology ?? providerDefaults.topology,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { BuiltInHostSource, RemoteHostConfig, SessionCapabilities, SessionProvider } from '@/types';
|
|
2
|
+
|
|
3
|
+
export type SessionLike = {
|
|
4
|
+
id: string;
|
|
5
|
+
slug?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
directory: string;
|
|
8
|
+
debugReason?: string;
|
|
9
|
+
parentID?: string;
|
|
10
|
+
time?: {
|
|
11
|
+
created: number;
|
|
12
|
+
updated: number;
|
|
13
|
+
archived?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type SessionSource = BuiltInHostSource | (RemoteHostConfig & { hostKind: 'remote' });
|
|
18
|
+
|
|
19
|
+
export type ChildTopologySupport = 'flat' | 'authoritative';
|
|
20
|
+
|
|
21
|
+
export type ProviderTopology = {
|
|
22
|
+
childSessions: ChildTopologySupport;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type HostAwareFields = {
|
|
26
|
+
hostId?: string;
|
|
27
|
+
hostLabel?: string;
|
|
28
|
+
hostKind?: SessionSource['hostKind'];
|
|
29
|
+
hostBaseUrl?: string;
|
|
30
|
+
provider?: SessionProvider;
|
|
31
|
+
providerRawId?: string;
|
|
32
|
+
rawSessionId?: string;
|
|
33
|
+
sourceSessionKey?: string;
|
|
34
|
+
readOnly?: boolean;
|
|
35
|
+
capabilities?: SessionCapabilities;
|
|
36
|
+
topology?: ProviderTopology;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ChildEntry = HostAwareFields & {
|
|
40
|
+
id: string;
|
|
41
|
+
slug?: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
directory?: string;
|
|
44
|
+
debugReason?: string;
|
|
45
|
+
parentID?: string;
|
|
46
|
+
time?: { created: number; updated: number; archived?: number };
|
|
47
|
+
realTimeStatus: string;
|
|
48
|
+
waitingForUser: boolean;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type EnrichedSession = SessionLike & HostAwareFields & {
|
|
52
|
+
projectName: string;
|
|
53
|
+
branch: string | null;
|
|
54
|
+
realTimeStatus: 'idle' | 'busy' | 'retry';
|
|
55
|
+
waitingForUser: boolean;
|
|
56
|
+
children: ChildEntry[];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type ProcessHint = {
|
|
60
|
+
pid: number;
|
|
61
|
+
directory: string;
|
|
62
|
+
projectName: string;
|
|
63
|
+
reason: 'process_without_api_port';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type SessionHostStatus = {
|
|
67
|
+
hostId: string;
|
|
68
|
+
hostLabel: string;
|
|
69
|
+
hostKind: SessionSource['hostKind'];
|
|
70
|
+
online: boolean;
|
|
71
|
+
degraded?: boolean;
|
|
72
|
+
reason?: string;
|
|
73
|
+
baseUrl?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type SourceResultMeta = {
|
|
77
|
+
online: boolean;
|
|
78
|
+
degraded?: boolean;
|
|
79
|
+
reason?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type SessionsSuccessPayload = {
|
|
83
|
+
sessions: EnrichedSession[];
|
|
84
|
+
processHints: ProcessHint[];
|
|
85
|
+
failedPorts?: Array<{ port: number; reason: string }>;
|
|
86
|
+
degraded?: boolean;
|
|
87
|
+
hosts?: SessionHostStatus[];
|
|
88
|
+
hostStatuses?: SessionHostStatus[];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type SessionsRouteResult = {
|
|
92
|
+
payload: SessionsSuccessPayload | Record<string, unknown>;
|
|
93
|
+
status?: number;
|
|
94
|
+
sourceMeta?: SourceResultMeta;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export type StableRealtimeStatus = 'idle' | 'busy' | 'retry';
|
|
98
|
+
|
|
99
|
+
export type MessageStateStatus = string;
|
|
100
|
+
|
|
101
|
+
export type MessagePart = {
|
|
102
|
+
state?: {
|
|
103
|
+
status?: unknown;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type SessionStatusStabilizationTarget = {
|
|
108
|
+
id: string;
|
|
109
|
+
time?: {
|
|
110
|
+
archived?: number;
|
|
111
|
+
};
|
|
112
|
+
realTimeStatus: string;
|
|
113
|
+
waitingForUser: boolean;
|
|
114
|
+
children: Array<{
|
|
115
|
+
id: string;
|
|
116
|
+
time?: {
|
|
117
|
+
archived?: number;
|
|
118
|
+
};
|
|
119
|
+
realTimeStatus: string;
|
|
120
|
+
waitingForUser: boolean;
|
|
121
|
+
}>;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export type LocalSessionProviderContext = {
|
|
125
|
+
stickyBusyDelayMs: number;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type LocalSessionProvider = {
|
|
129
|
+
id: SessionProvider;
|
|
130
|
+
getSessionsResult(context: LocalSessionProviderContext): Promise<SessionsRouteResult>;
|
|
131
|
+
};
|