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
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
|
|
2
|
-
import { parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
|
|
2
|
+
import { ActionSessionReference, parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
|
|
3
3
|
import { listNodeRecords } from '@/lib/nodeRegistry';
|
|
4
4
|
import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
|
|
5
|
+
import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
clearSessionForceUnarchived,
|
|
8
|
+
markSessionStickyStatusBlocked,
|
|
8
9
|
} from '@/lib/sessionArchiveOverrides';
|
|
10
|
+
import { markClaudeSessionArchived } from '@/lib/claudeSessionOverrides';
|
|
9
11
|
|
|
10
12
|
const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
|
|
11
13
|
|
|
@@ -23,6 +25,20 @@ function createSessionNotFoundResponse() {
|
|
|
23
25
|
);
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
function createUnsupportedCapabilityResponse(capability: 'archive', sessionId: string) {
|
|
29
|
+
const provider = detectProviderFromRawId(sessionId);
|
|
30
|
+
|
|
31
|
+
return Response.json(
|
|
32
|
+
{
|
|
33
|
+
error: 'Session action not supported by provider',
|
|
34
|
+
reason: 'provider_capability_unsupported',
|
|
35
|
+
provider,
|
|
36
|
+
capability,
|
|
37
|
+
},
|
|
38
|
+
{ status: 403 }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
function createArchiveFailureResponse(
|
|
27
43
|
status: number,
|
|
28
44
|
message?: string,
|
|
@@ -87,17 +103,36 @@ async function forwardRemoteArchive(hostId: string, sessionId: string): Promise<
|
|
|
87
103
|
|
|
88
104
|
export async function POST(_: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
89
105
|
const { id } = await params;
|
|
90
|
-
|
|
106
|
+
let actionTarget: ActionSessionReference;
|
|
91
107
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
108
|
+
try {
|
|
109
|
+
actionTarget = parseActionSessionReference(id);
|
|
110
|
+
} catch {
|
|
111
|
+
return createInvalidActionSessionIdResponse();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const provider = detectProviderFromRawId(actionTarget.sessionId);
|
|
115
|
+
if (!getDefaultProviderContext(provider).capabilities.archive) {
|
|
116
|
+
return createUnsupportedCapabilityResponse('archive', actionTarget.sessionId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (provider === 'claude-code' && actionTarget.isRemote) {
|
|
120
|
+
return createUnsupportedCapabilityResponse('archive', actionTarget.sessionId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (provider === 'claude-code') {
|
|
124
|
+
await markClaudeSessionArchived(extractProviderRawId(actionTarget.sessionId));
|
|
125
|
+
const localSessionId = resolveLocalActionSessionId(id);
|
|
126
|
+
if (localSessionId) {
|
|
127
|
+
clearSessionForceUnarchived(localSessionId);
|
|
128
|
+
markSessionStickyStatusBlocked(localSessionId);
|
|
100
129
|
}
|
|
130
|
+
return Response.json({ success: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const sessionId = resolveLocalActionSessionId(id);
|
|
134
|
+
if (!sessionId && actionTarget.isRemote) {
|
|
135
|
+
return forwardRemoteArchive(actionTarget.hostId, actionTarget.sessionId);
|
|
101
136
|
}
|
|
102
137
|
|
|
103
138
|
if (!sessionId) {
|
|
@@ -17,15 +17,21 @@ vi.mock('@/lib/sessionArchiveOverrides', () => ({
|
|
|
17
17
|
clearSessionStickyStatusBlocked: vi.fn(),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
+
vi.mock('@/lib/claudeSessionOverrides', () => ({
|
|
21
|
+
markClaudeSessionDeleted: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
20
24
|
import { createOpencodeClient } from '@opencode-ai/sdk';
|
|
21
25
|
import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
|
|
22
26
|
import { listNodeRecords } from '@/lib/nodeRegistry';
|
|
27
|
+
import { markClaudeSessionDeleted } from '@/lib/claudeSessionOverrides';
|
|
23
28
|
|
|
24
29
|
import { POST } from './route';
|
|
25
30
|
|
|
26
31
|
const mockCreateOpencodeClient: any = createOpencodeClient;
|
|
27
32
|
const mockDiscoverPortsWithMeta: any = discoverOpencodePortsWithMeta;
|
|
28
33
|
const mockListNodeRecords: any = listNodeRecords;
|
|
34
|
+
const mockMarkClaudeSessionDeleted: any = markClaudeSessionDeleted;
|
|
29
35
|
const mockSessionDelete = vi.fn();
|
|
30
36
|
|
|
31
37
|
describe('/api/sessions/[id]/delete', () => {
|
|
@@ -110,4 +116,90 @@ describe('/api/sessions/[id]/delete', () => {
|
|
|
110
116
|
message: '404 not found',
|
|
111
117
|
});
|
|
112
118
|
});
|
|
119
|
+
|
|
120
|
+
it('deletes Claude sessions through local override storage before any OpenCode execution', async () => {
|
|
121
|
+
const mockFetch = vi.fn();
|
|
122
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
123
|
+
|
|
124
|
+
const response = await POST(new Request('http://localhost/api/sessions/local:claude~550e8400-e29b-41d4-a716-446655440000/delete', { method: 'POST' }), {
|
|
125
|
+
params: Promise.resolve({ id: 'local:claude~550e8400-e29b-41d4-a716-446655440000' }),
|
|
126
|
+
});
|
|
127
|
+
const data = await response.json();
|
|
128
|
+
|
|
129
|
+
expect(response.status).toBe(200);
|
|
130
|
+
expect(data).toEqual({ success: true });
|
|
131
|
+
expect(mockMarkClaudeSessionDeleted).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
|
|
132
|
+
expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
|
|
133
|
+
expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
|
|
134
|
+
expect(mockSessionDelete).not.toHaveBeenCalled();
|
|
135
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('deletes scoped Claude sidechain sessions through local override storage', async () => {
|
|
139
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
140
|
+
const mockFetch = vi.fn();
|
|
141
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
142
|
+
|
|
143
|
+
const response = await POST(new Request(`http://localhost/api/sessions/local:${scopedSessionId}/delete`, { method: 'POST' }), {
|
|
144
|
+
params: Promise.resolve({ id: `local:${scopedSessionId}` }),
|
|
145
|
+
});
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
|
|
148
|
+
expect(response.status).toBe(200);
|
|
149
|
+
expect(data).toEqual({ success: true });
|
|
150
|
+
expect(mockMarkClaudeSessionDeleted).toHaveBeenCalledWith(scopedSessionId);
|
|
151
|
+
expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
|
|
152
|
+
expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
|
|
153
|
+
expect(mockSessionDelete).not.toHaveBeenCalled();
|
|
154
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('rejects remote Claude delete requests before local override or node execution', async () => {
|
|
158
|
+
const mockFetch = vi.fn();
|
|
159
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
160
|
+
|
|
161
|
+
const response = await POST(new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/delete', { method: 'POST' }), {
|
|
162
|
+
params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }),
|
|
163
|
+
});
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
|
|
166
|
+
expect(response.status).toBe(403);
|
|
167
|
+
expect(data).toEqual({
|
|
168
|
+
error: 'Session action not supported by provider',
|
|
169
|
+
reason: 'provider_capability_unsupported',
|
|
170
|
+
provider: 'claude-code',
|
|
171
|
+
capability: 'delete',
|
|
172
|
+
});
|
|
173
|
+
expect(mockMarkClaudeSessionDeleted).not.toHaveBeenCalled();
|
|
174
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
175
|
+
expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
|
|
176
|
+
expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
|
|
177
|
+
expect(mockSessionDelete).not.toHaveBeenCalled();
|
|
178
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('rejects remote scoped Claude sidechain deletes before node execution', async () => {
|
|
182
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
183
|
+
const mockFetch = vi.fn();
|
|
184
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
185
|
+
|
|
186
|
+
const response = await POST(new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/delete`, { method: 'POST' }), {
|
|
187
|
+
params: Promise.resolve({ id: `node-1:${scopedSessionId}` }),
|
|
188
|
+
});
|
|
189
|
+
const data = await response.json();
|
|
190
|
+
|
|
191
|
+
expect(response.status).toBe(403);
|
|
192
|
+
expect(data).toEqual({
|
|
193
|
+
error: 'Session action not supported by provider',
|
|
194
|
+
reason: 'provider_capability_unsupported',
|
|
195
|
+
provider: 'claude-code',
|
|
196
|
+
capability: 'delete',
|
|
197
|
+
});
|
|
198
|
+
expect(mockMarkClaudeSessionDeleted).not.toHaveBeenCalled();
|
|
199
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
200
|
+
expect(mockDiscoverPortsWithMeta).not.toHaveBeenCalled();
|
|
201
|
+
expect(mockCreateOpencodeClient).not.toHaveBeenCalled();
|
|
202
|
+
expect(mockSessionDelete).not.toHaveBeenCalled();
|
|
203
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
113
205
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createOpencodeClient } from '@opencode-ai/sdk';
|
|
2
2
|
import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
|
|
3
|
-
import { parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
|
|
3
|
+
import { ActionSessionReference, parseActionSessionReference, resolveLocalActionSessionId } from '@/lib/hostIdentity';
|
|
4
4
|
import { listNodeRecords } from '@/lib/nodeRegistry';
|
|
5
5
|
import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
|
|
6
|
+
import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
|
|
6
7
|
import {
|
|
7
8
|
clearSessionForceUnarchived,
|
|
8
9
|
clearSessionStickyStatusBlocked,
|
|
9
10
|
} from '@/lib/sessionArchiveOverrides';
|
|
11
|
+
import { markClaudeSessionDeleted } from '@/lib/claudeSessionOverrides';
|
|
10
12
|
|
|
11
13
|
const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
|
|
12
14
|
|
|
@@ -28,6 +30,20 @@ function createSessionNotFoundResponse(message?: string) {
|
|
|
28
30
|
);
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
function createUnsupportedCapabilityResponse(capability: 'delete', sessionId: string) {
|
|
34
|
+
const provider = detectProviderFromRawId(sessionId);
|
|
35
|
+
|
|
36
|
+
return Response.json(
|
|
37
|
+
{
|
|
38
|
+
error: 'Session action not supported by provider',
|
|
39
|
+
reason: 'provider_capability_unsupported',
|
|
40
|
+
provider,
|
|
41
|
+
capability,
|
|
42
|
+
},
|
|
43
|
+
{ status: 403 }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
async function forwardRemoteDelete(hostId: string, sessionId: string): Promise<Response> {
|
|
32
48
|
const nodeRecords = await listNodeRecords();
|
|
33
49
|
const nodeRecord = nodeRecords.find((node) => node.nodeId === hostId);
|
|
@@ -77,17 +93,36 @@ async function forwardRemoteDelete(hostId: string, sessionId: string): Promise<R
|
|
|
77
93
|
|
|
78
94
|
export async function POST(_: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
79
95
|
const { id } = await params;
|
|
80
|
-
|
|
96
|
+
let actionTarget: ActionSessionReference;
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
try {
|
|
99
|
+
actionTarget = parseActionSessionReference(id);
|
|
100
|
+
} catch {
|
|
101
|
+
return createInvalidActionSessionIdResponse();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const provider = detectProviderFromRawId(actionTarget.sessionId);
|
|
105
|
+
if (!getDefaultProviderContext(provider).capabilities.delete) {
|
|
106
|
+
return createUnsupportedCapabilityResponse('delete', actionTarget.sessionId);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (provider === 'claude-code' && actionTarget.isRemote) {
|
|
110
|
+
return createUnsupportedCapabilityResponse('delete', actionTarget.sessionId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (provider === 'claude-code') {
|
|
114
|
+
await markClaudeSessionDeleted(extractProviderRawId(actionTarget.sessionId));
|
|
115
|
+
const localSessionId = resolveLocalActionSessionId(id);
|
|
116
|
+
if (localSessionId) {
|
|
117
|
+
clearSessionForceUnarchived(localSessionId);
|
|
118
|
+
clearSessionStickyStatusBlocked(localSessionId);
|
|
90
119
|
}
|
|
120
|
+
return Response.json({ success: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const sessionId = resolveLocalActionSessionId(id);
|
|
124
|
+
if (!sessionId && actionTarget.isRemote) {
|
|
125
|
+
return forwardRemoteDelete(actionTarget.hostId, actionTarget.sessionId);
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
if (!sessionId) {
|
|
@@ -215,4 +215,78 @@ describe('/api/sessions/[id]/open-editor', () => {
|
|
|
215
215
|
expect(response.status).toBe(400);
|
|
216
216
|
expect(data).toEqual({ error: 'Invalid action session id', reason: 'invalid_action_session_id' });
|
|
217
217
|
});
|
|
218
|
+
|
|
219
|
+
it('rejects unsupported Claude open-editor requests before remote node execution', async () => {
|
|
220
|
+
const mockFetch = vi.fn();
|
|
221
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
222
|
+
|
|
223
|
+
const response = await POST(
|
|
224
|
+
new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/open-editor', {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: { 'Content-Type': 'application/json' },
|
|
227
|
+
body: JSON.stringify({ tool: 'vscode' }),
|
|
228
|
+
}),
|
|
229
|
+
{ params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }) }
|
|
230
|
+
);
|
|
231
|
+
const data = await response.json();
|
|
232
|
+
|
|
233
|
+
expect(response.status).toBe(403);
|
|
234
|
+
expect(data).toEqual({
|
|
235
|
+
error: 'Session action not supported by provider',
|
|
236
|
+
reason: 'provider_capability_unsupported',
|
|
237
|
+
provider: 'claude-code',
|
|
238
|
+
capability: 'openEditor',
|
|
239
|
+
});
|
|
240
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
241
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('rejects unsupported scoped Claude sidechain open-editor requests before remote node execution', async () => {
|
|
245
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
246
|
+
const mockFetch = vi.fn();
|
|
247
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
248
|
+
|
|
249
|
+
const response = await POST(
|
|
250
|
+
new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/open-editor`, {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: { 'Content-Type': 'application/json' },
|
|
253
|
+
body: JSON.stringify({ tool: 'vscode' }),
|
|
254
|
+
}),
|
|
255
|
+
{ params: Promise.resolve({ id: `node-1:${scopedSessionId}` }) }
|
|
256
|
+
);
|
|
257
|
+
const data = await response.json();
|
|
258
|
+
|
|
259
|
+
expect(response.status).toBe(403);
|
|
260
|
+
expect(data).toEqual({
|
|
261
|
+
error: 'Session action not supported by provider',
|
|
262
|
+
reason: 'provider_capability_unsupported',
|
|
263
|
+
provider: 'claude-code',
|
|
264
|
+
capability: 'openEditor',
|
|
265
|
+
});
|
|
266
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
267
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('rejects local scoped Claude sidechain open-editor requests as unsupported capability', async () => {
|
|
271
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
272
|
+
|
|
273
|
+
const response = await POST(
|
|
274
|
+
new Request(`http://localhost/api/sessions/local:${scopedSessionId}/open-editor`, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: { 'Content-Type': 'application/json' },
|
|
277
|
+
body: JSON.stringify({ tool: 'vscode' }),
|
|
278
|
+
}),
|
|
279
|
+
{ params: Promise.resolve({ id: `local:${scopedSessionId}` }) }
|
|
280
|
+
);
|
|
281
|
+
const data = await response.json();
|
|
282
|
+
|
|
283
|
+
expect(response.status).toBe(403);
|
|
284
|
+
expect(data).toEqual({
|
|
285
|
+
error: 'Session action not supported by provider',
|
|
286
|
+
reason: 'provider_capability_unsupported',
|
|
287
|
+
provider: 'claude-code',
|
|
288
|
+
capability: 'openEditor',
|
|
289
|
+
});
|
|
290
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
291
|
+
});
|
|
218
292
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseActionSessionReference } from '@/lib/hostIdentity';
|
|
1
|
+
import { ActionSessionReference, parseActionSessionReference } from '@/lib/hostIdentity';
|
|
2
2
|
import { listNodeRecords } from '@/lib/nodeRegistry';
|
|
3
3
|
import { createNodeRequestHeaders } from '@/lib/nodeProtocol';
|
|
4
|
+
import { detectProviderFromRawId, extractProviderRawId, getDefaultProviderContext } from '@/lib/session-providers/providerIds';
|
|
4
5
|
|
|
5
6
|
const REMOTE_NODE_ACTION_TIMEOUT_MS = 5_000;
|
|
6
7
|
|
|
@@ -18,16 +19,35 @@ function createSessionNotFoundResponse() {
|
|
|
18
19
|
);
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
function createUnsupportedCapabilityResponse(capability: 'openEditor', sessionId: string) {
|
|
23
|
+
const provider = detectProviderFromRawId(sessionId);
|
|
24
|
+
|
|
25
|
+
return Response.json(
|
|
26
|
+
{
|
|
27
|
+
error: 'Session action not supported by provider',
|
|
28
|
+
reason: 'provider_capability_unsupported',
|
|
29
|
+
provider,
|
|
30
|
+
capability,
|
|
31
|
+
},
|
|
32
|
+
{ status: 403 }
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
22
37
|
const { id } = await params;
|
|
23
38
|
|
|
24
|
-
let actionTarget;
|
|
39
|
+
let actionTarget: ActionSessionReference;
|
|
25
40
|
try {
|
|
26
41
|
actionTarget = parseActionSessionReference(id);
|
|
27
42
|
} catch {
|
|
28
43
|
return createInvalidActionSessionIdResponse();
|
|
29
44
|
}
|
|
30
45
|
|
|
46
|
+
const provider = detectProviderFromRawId(actionTarget.sessionId);
|
|
47
|
+
if (!getDefaultProviderContext(provider).capabilities.openEditor) {
|
|
48
|
+
return createUnsupportedCapabilityResponse('openEditor', actionTarget.sessionId);
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
if (!actionTarget.isRemote) {
|
|
32
52
|
return createInvalidActionSessionIdResponse();
|
|
33
53
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('@/lib/opencodeDiscovery', () => ({
|
|
4
|
+
discoverOpencodePortsWithMeta: vi.fn(() => ({ ports: [], timedOut: false })),
|
|
5
|
+
}));
|
|
6
|
+
|
|
7
|
+
vi.mock('@/lib/nodeRegistry', () => ({
|
|
8
|
+
listNodeRecords: vi.fn(async () => []),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock('@/lib/sessionArchiveOverrides', () => ({
|
|
12
|
+
markSessionForceUnarchived: vi.fn(),
|
|
13
|
+
clearSessionStickyStatusBlocked: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock('@/lib/claudeSessionOverrides', () => ({
|
|
17
|
+
clearClaudeSessionArchived: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
import { POST } from './route';
|
|
21
|
+
import { discoverOpencodePortsWithMeta } from '@/lib/opencodeDiscovery';
|
|
22
|
+
import { listNodeRecords } from '@/lib/nodeRegistry';
|
|
23
|
+
import { clearClaudeSessionArchived } from '@/lib/claudeSessionOverrides';
|
|
24
|
+
import {
|
|
25
|
+
clearSessionStickyStatusBlocked,
|
|
26
|
+
markSessionForceUnarchived,
|
|
27
|
+
} from '@/lib/sessionArchiveOverrides';
|
|
28
|
+
|
|
29
|
+
const mockDiscoverOpencodePortsWithMeta: any = discoverOpencodePortsWithMeta;
|
|
30
|
+
const mockListNodeRecords: any = listNodeRecords;
|
|
31
|
+
const mockClearClaudeSessionArchived: any = clearClaudeSessionArchived;
|
|
32
|
+
const mockMarkSessionForceUnarchived: any = markSessionForceUnarchived;
|
|
33
|
+
const mockClearSessionStickyStatusBlocked: any = clearSessionStickyStatusBlocked;
|
|
34
|
+
|
|
35
|
+
describe('/api/sessions/[id]/restore', () => {
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
vi.clearAllMocks();
|
|
38
|
+
mockDiscoverOpencodePortsWithMeta.mockReturnValue({ ports: [], timedOut: false });
|
|
39
|
+
mockListNodeRecords.mockResolvedValue([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('restores Claude sessions through local override storage', async () => {
|
|
43
|
+
const response = await POST(new Request('http://localhost/api/sessions/local:claude~550e8400-e29b-41d4-a716-446655440000/restore', { method: 'POST' }), {
|
|
44
|
+
params: Promise.resolve({ id: 'local:claude~550e8400-e29b-41d4-a716-446655440000' }),
|
|
45
|
+
});
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
|
|
48
|
+
expect(response.status).toBe(200);
|
|
49
|
+
expect(data).toEqual({ success: true });
|
|
50
|
+
expect(mockMarkSessionForceUnarchived).toHaveBeenCalledWith('claude~550e8400-e29b-41d4-a716-446655440000');
|
|
51
|
+
expect(mockClearSessionStickyStatusBlocked).toHaveBeenCalledWith('claude~550e8400-e29b-41d4-a716-446655440000');
|
|
52
|
+
expect(mockClearClaudeSessionArchived).toHaveBeenCalledWith('550e8400-e29b-41d4-a716-446655440000');
|
|
53
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
54
|
+
expect(mockDiscoverOpencodePortsWithMeta).not.toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('restores scoped Claude sidechain sessions through local override storage', async () => {
|
|
58
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
59
|
+
const response = await POST(new Request(`http://localhost/api/sessions/local:${scopedSessionId}/restore`, { method: 'POST' }), {
|
|
60
|
+
params: Promise.resolve({ id: `local:${scopedSessionId}` }),
|
|
61
|
+
});
|
|
62
|
+
const data = await response.json();
|
|
63
|
+
|
|
64
|
+
expect(response.status).toBe(200);
|
|
65
|
+
expect(data).toEqual({ success: true });
|
|
66
|
+
expect(mockMarkSessionForceUnarchived).toHaveBeenCalledWith(scopedSessionId);
|
|
67
|
+
expect(mockClearSessionStickyStatusBlocked).toHaveBeenCalledWith(scopedSessionId);
|
|
68
|
+
expect(mockClearClaudeSessionArchived).toHaveBeenCalledWith(scopedSessionId);
|
|
69
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
70
|
+
expect(mockDiscoverOpencodePortsWithMeta).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('rejects remote Claude restore requests before local override or node execution', async () => {
|
|
74
|
+
const mockFetch = vi.fn();
|
|
75
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
76
|
+
|
|
77
|
+
const response = await POST(new Request('http://localhost/api/sessions/node-1:claude~550e8400-e29b-41d4-a716-446655440000/restore', { method: 'POST' }), {
|
|
78
|
+
params: Promise.resolve({ id: 'node-1:claude~550e8400-e29b-41d4-a716-446655440000' }),
|
|
79
|
+
});
|
|
80
|
+
const data = await response.json();
|
|
81
|
+
|
|
82
|
+
expect(response.status).toBe(403);
|
|
83
|
+
expect(data).toEqual({
|
|
84
|
+
error: 'Session action not supported by provider',
|
|
85
|
+
reason: 'provider_capability_unsupported',
|
|
86
|
+
provider: 'claude-code',
|
|
87
|
+
capability: 'archive',
|
|
88
|
+
});
|
|
89
|
+
expect(mockClearClaudeSessionArchived).not.toHaveBeenCalled();
|
|
90
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
91
|
+
expect(mockDiscoverOpencodePortsWithMeta).not.toHaveBeenCalled();
|
|
92
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('rejects remote scoped Claude sidechain restore requests before node execution', async () => {
|
|
96
|
+
const scopedSessionId = '550e8400-e29b-41d4-a716-446655440000__agent-a123';
|
|
97
|
+
const mockFetch = vi.fn();
|
|
98
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
99
|
+
|
|
100
|
+
const response = await POST(new Request(`http://localhost/api/sessions/node-1:${scopedSessionId}/restore`, { method: 'POST' }), {
|
|
101
|
+
params: Promise.resolve({ id: `node-1:${scopedSessionId}` }),
|
|
102
|
+
});
|
|
103
|
+
const data = await response.json();
|
|
104
|
+
|
|
105
|
+
expect(response.status).toBe(403);
|
|
106
|
+
expect(data).toEqual({
|
|
107
|
+
error: 'Session action not supported by provider',
|
|
108
|
+
reason: 'provider_capability_unsupported',
|
|
109
|
+
provider: 'claude-code',
|
|
110
|
+
capability: 'archive',
|
|
111
|
+
});
|
|
112
|
+
expect(mockClearClaudeSessionArchived).not.toHaveBeenCalled();
|
|
113
|
+
expect(mockListNodeRecords).not.toHaveBeenCalled();
|
|
114
|
+
expect(mockDiscoverOpencodePortsWithMeta).not.toHaveBeenCalled();
|
|
115
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('forwards remote opencode restores to node archive DELETE endpoint', async () => {
|
|
119
|
+
mockListNodeRecords.mockResolvedValue([
|
|
120
|
+
{
|
|
121
|
+
nodeId: 'node-1',
|
|
122
|
+
nodeLabel: 'Node 1',
|
|
123
|
+
baseUrl: 'https://node-1.test',
|
|
124
|
+
enabled: true,
|
|
125
|
+
token: 'node-token',
|
|
126
|
+
createdAt: new Date().toISOString(),
|
|
127
|
+
updatedAt: new Date().toISOString(),
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
const mockFetch = vi.fn(async () => new Response(JSON.stringify({ success: true }), { status: 200 }));
|
|
132
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
133
|
+
|
|
134
|
+
const response = await POST(new Request('http://localhost/api/sessions/node-1:abc/restore', { method: 'POST' }), {
|
|
135
|
+
params: Promise.resolve({ id: 'node-1:abc' }),
|
|
136
|
+
});
|
|
137
|
+
const data = await response.json();
|
|
138
|
+
|
|
139
|
+
expect(response.status).toBe(200);
|
|
140
|
+
expect(data).toEqual({ success: true });
|
|
141
|
+
expect(mockFetch).toHaveBeenCalledWith('https://node-1.test/api/node/sessions/abc/archive', expect.objectContaining({ method: 'DELETE' }));
|
|
142
|
+
expect(mockClearClaudeSessionArchived).not.toHaveBeenCalled();
|
|
143
|
+
expect(mockDiscoverOpencodePortsWithMeta).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('clears sticky blocked state when local opencode restore succeeds', async () => {
|
|
147
|
+
mockDiscoverOpencodePortsWithMeta.mockReturnValue({ ports: [3456], timedOut: false });
|
|
148
|
+
const mockFetch = vi.fn(async () => new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
149
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
150
|
+
|
|
151
|
+
const response = await POST(new Request('http://localhost/api/sessions/local:session-1/restore', { method: 'POST' }), {
|
|
152
|
+
params: Promise.resolve({ id: 'local:session-1' }),
|
|
153
|
+
});
|
|
154
|
+
const data = await response.json();
|
|
155
|
+
|
|
156
|
+
expect(response.status).toBe(200);
|
|
157
|
+
expect(data).toEqual({ success: true });
|
|
158
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3456/session/session-1', expect.objectContaining({ method: 'PATCH' }));
|
|
159
|
+
expect(mockMarkSessionForceUnarchived).toHaveBeenCalledWith('session-1');
|
|
160
|
+
expect(mockClearSessionStickyStatusBlocked).toHaveBeenCalledWith('session-1');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('returns the latest non-404 upstream failure when restore attempts fail', async () => {
|
|
164
|
+
mockDiscoverOpencodePortsWithMeta.mockReturnValue({ ports: [3456, 3457], timedOut: false });
|
|
165
|
+
const mockFetch = vi.fn(async (input: RequestInfo | URL) => {
|
|
166
|
+
const url = String(input);
|
|
167
|
+
if (url.includes(':3456/')) {
|
|
168
|
+
return new Response('upstream boom', { status: 503 });
|
|
169
|
+
}
|
|
170
|
+
return new Response('not found', { status: 404 });
|
|
171
|
+
});
|
|
172
|
+
vi.stubGlobal('fetch', mockFetch);
|
|
173
|
+
|
|
174
|
+
const response = await POST(new Request('http://localhost/api/sessions/local:session-2/restore', { method: 'POST' }), {
|
|
175
|
+
params: Promise.resolve({ id: 'local:session-2' }),
|
|
176
|
+
});
|
|
177
|
+
const data = await response.json();
|
|
178
|
+
|
|
179
|
+
expect(response.status).toBe(503);
|
|
180
|
+
expect(data).toEqual(expect.objectContaining({
|
|
181
|
+
error: 'Failed to restore session',
|
|
182
|
+
reason: 'restore_request_failed',
|
|
183
|
+
message: 'upstream boom',
|
|
184
|
+
}));
|
|
185
|
+
});
|
|
186
|
+
});
|