@shepai/cli 1.148.0 → 1.149.0-pr464.731ba3a
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/dist/src/presentation/web/app/api/agent-events/route.js +1 -1
- package/dist/src/presentation/web/app/api/sessions/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/sessions/route.js +2 -268
- package/dist/src/presentation/web/app/api/sessions-batch/route.d.ts +17 -0
- package/dist/src/presentation/web/app/api/sessions-batch/route.d.ts.map +1 -0
- package/dist/src/presentation/web/app/api/sessions-batch/route.js +61 -0
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js +29 -2
- package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.js +15 -73
- package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.stories.d.ts.map +1 -1
- package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.stories.js +18 -17
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center-inner.js +18 -0
- package/dist/src/presentation/web/components/features/control-center/control-center.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/control-center.js +2 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.d.ts.map +1 -1
- package/dist/src/presentation/web/components/features/control-center/use-control-center-state.js +4 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.d.ts.map +1 -1
- package/dist/src/presentation/web/components/layouts/app-sidebar/app-sidebar.js +32 -33
- package/dist/src/presentation/web/hooks/sessions-provider.d.ts +12 -0
- package/dist/src/presentation/web/hooks/sessions-provider.d.ts.map +1 -0
- package/dist/src/presentation/web/hooks/sessions-provider.js +57 -0
- package/dist/src/presentation/web/hooks/use-deploy-action.d.ts.map +1 -1
- package/dist/src/presentation/web/hooks/use-deploy-action.js +8 -54
- package/dist/src/presentation/web/lib/session-scanner.d.ts +27 -0
- package/dist/src/presentation/web/lib/session-scanner.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/session-scanner.js +255 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +3 -3
- package/web/.next/required-server-files.json +3 -3
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +36 -36
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +26 -26
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +3 -3
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
- package/web/.next/server/app/api/evidence/route.js.nft.json +1 -1
- package/web/.next/server/app/api/graph-data/route.js.nft.json +1 -1
- package/web/.next/server/app/api/sessions/route.js +2 -3
- package/web/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/web/.next/server/app/api/sessions-batch/route/app-paths-manifest.json +3 -0
- package/web/.next/server/app/api/sessions-batch/route/build-manifest.json +11 -0
- package/web/.next/server/app/api/sessions-batch/route/server-reference-manifest.json +4 -0
- package/web/.next/server/app/api/sessions-batch/route.js +7 -0
- package/web/.next/server/app/api/sessions-batch/route.js.map +5 -0
- package/web/.next/server/app/api/sessions-batch/route.js.nft.json +1 -0
- package/web/.next/server/app/api/sessions-batch/route_client-reference-manifest.js +2 -0
- package/web/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +8 -8
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +3 -3
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_4d623b8e.js.map +1 -1
- package/web/.next/server/chunks/744ca_web__next-internal_server_app_api_sessions-batch_route_actions_4859f283.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__0d33c29e._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__0d33c29e._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__2f61738a._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__2f61738a._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__a402b567._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js +1 -1
- package/web/.next/server/chunks/ssr/744ca_web_components_common_control-center-drawer_create-drawer-client_tsx_5e26fc0a._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__2138fa7e._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__29580090._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__357d99f9._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__3ef34e4c._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__43f51aa6._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__815546bd._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js +3 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__aad040c0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__c094882b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__d48c5b11._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__dac5dbf1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__fae8b355._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_05c23ad9._.js +1 -1
- package/web/.next/server/chunks/ssr/_05c23ad9._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0c5f56e3._.js +2 -2
- package/web/.next/server/chunks/ssr/_0c5f56e3._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_16eb4fec._.js +1 -1
- package/web/.next/server/chunks/ssr/_16eb4fec._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_1b719e7f._.js +1 -1
- package/web/.next/server/chunks/ssr/_1b719e7f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_37e8548b._.js +1 -1
- package/web/.next/server/chunks/ssr/_37e8548b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_fe63a7f9._.js → _491e2e93._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_fe63a7f9._.js.map → _491e2e93._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_55d763e2._.js +1 -1
- package/web/.next/server/chunks/ssr/_55d763e2._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_6256a985._.js +1 -1
- package/web/.next/server/chunks/ssr/_6256a985._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_64bdfc6f._.js +2 -2
- package/web/.next/server/chunks/ssr/_64bdfc6f._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_8fcc39d4._.js +1 -1
- package/web/.next/server/chunks/ssr/_af346da4._.js +4 -0
- package/web/.next/server/chunks/ssr/_af346da4._.js.map +1 -0
- package/web/.next/server/chunks/ssr/_b71645b4._.js +1 -1
- package/web/.next/server/chunks/ssr/_b71645b4._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js +1 -1
- package/web/.next/server/chunks/ssr/_d8575088._.js.map +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js +1 -1
- package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_settings_settings-page-client_tsx_6ed9d5f8._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{src_presentation_web_7b2fda40._.js → src_presentation_web_1fdc270a._.js} +2 -2
- package/web/.next/server/chunks/ssr/{src_presentation_web_7b2fda40._.js.map → src_presentation_web_1fdc270a._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_skills_page_actions_1b176e3c.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_tools_page_actions_bd9f0dda.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_baaca5d5._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_e599bb8c._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_7ac3562e._.js.map +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +44 -44
- package/web/.next/static/chunks/{c731682077fbac4f.js → 034f751d23d24bf4.js} +1 -1
- package/web/.next/static/chunks/40c990f6fbc336be.js +5 -0
- package/web/.next/static/chunks/41d96f271ce621bd.js +2 -0
- package/web/.next/static/chunks/4f7c4ec7c621877b.js +1 -0
- package/web/.next/static/chunks/{9dad6769d10a32df.js → 5148bbeb4a0ca2aa.js} +1 -1
- package/web/.next/static/chunks/5e0da0747fe6a333.js +1 -0
- package/web/.next/static/chunks/{7c5131e33516a325.js → 668a24d05d581b48.js} +1 -1
- package/web/.next/static/chunks/965a33e37a1c9dba.js +1 -0
- package/web/.next/static/chunks/{063a24b49d9818a0.js → b193c58e9c563740.js} +1 -1
- package/web/.next/static/chunks/c413bee958e7b764.js +1 -0
- package/web/.next/static/chunks/{48850e202dd814ac.js → c6c9fd49766a0f86.js} +1 -1
- package/web/.next/static/chunks/{6f76e63ead3fac2e.js → d87bb2eaea273518.js} +1 -1
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_ff60e4a5.js +0 -3
- package/web/.next/server/chunks/403f9_next_dist_esm_build_templates_app-route_ff60e4a5.js.map +0 -1
- package/web/.next/server/chunks/[externals]__448264a3._.js +0 -3
- package/web/.next/server/chunks/ssr/_4533d6f8._.js +0 -4
- package/web/.next/server/chunks/ssr/_4533d6f8._.js.map +0 -1
- package/web/.next/static/chunks/0137d4850cab3c45.js +0 -5
- package/web/.next/static/chunks/04869f1d3f5d9071.js +0 -1
- package/web/.next/static/chunks/21e82fee1a7e1668.js +0 -1
- package/web/.next/static/chunks/682563e4503cbd58.js +0 -1
- package/web/.next/static/chunks/683b1d85e789c2eb.js +0 -2
- package/web/.next/static/chunks/d62ae5e449d87057.js +0 -1
- /package/web/.next/server/chunks/{[externals]__448264a3._.js.map → 744ca_web__next-internal_server_app_api_sessions-batch_route_actions_4859f283.js.map} +0 -0
- /package/web/.next/static/{zYKuE1zbe1UWwAJv5EVwg → yfocGiJV-35vG32zlzozn}/_buildManifest.js +0 -0
- /package/web/.next/static/{zYKuE1zbe1UWwAJv5EVwg → yfocGiJV-35vG32zlzozn}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{zYKuE1zbe1UWwAJv5EVwg → yfocGiJV-35vG32zlzozn}/_ssgManifest.js +0 -0
|
@@ -18,7 +18,7 @@ import { AgentRunStatus, SdlcLifecycle, NotificationEventType, NotificationSever
|
|
|
18
18
|
import { isProcessAlive } from '../../../../../../packages/core/src/infrastructure/services/process/is-process-alive.js';
|
|
19
19
|
// Force dynamic — SSE streams must never be statically optimized or cached
|
|
20
20
|
export const dynamic = 'force-dynamic';
|
|
21
|
-
const POLL_INTERVAL_MS =
|
|
21
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
22
22
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
23
23
|
/**
|
|
24
24
|
* Maps SdlcLifecycle values to agent graph node names so the client
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/sessions/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/sessions/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC;;;;;GAKG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO;;;;;;;;;;;;;;IAqBzC"}
|
|
@@ -1,264 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import {
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { scanSessionsForPath } from '../../../lib/session-scanner.js';
|
|
6
3
|
export const dynamic = 'force-dynamic';
|
|
7
|
-
// ── Path encoding helpers ─────────────────────────────────────────────
|
|
8
|
-
/**
|
|
9
|
-
* Claude Code encodes paths by replacing '/', '\', '.' with '-'.
|
|
10
|
-
* e.g. /home/user/.shep/repos/abc → -home-user--shep-repos-abc
|
|
11
|
-
*/
|
|
12
|
-
function claudeEncodePath(p) {
|
|
13
|
-
return p.replace(/[/\\.]/g, '-');
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Cursor encodes paths by stripping the leading '/', removing dots,
|
|
17
|
-
* and replacing '/' and '\' with '-'.
|
|
18
|
-
* e.g. /home/user/.shep/repos/abc → home-user-shep-repos-abc
|
|
19
|
-
*/
|
|
20
|
-
function cursorEncodePath(p) {
|
|
21
|
-
return p.replace(/^\//, '').replace(/\./g, '').replace(/[/\\]/g, '-');
|
|
22
|
-
}
|
|
23
|
-
// ── Claude Code session scanner ───────────────────────────────────────
|
|
24
|
-
/**
|
|
25
|
-
* Collect .jsonl session files from a single Claude project directory.
|
|
26
|
-
*/
|
|
27
|
-
async function collectJsonlFiles(projectDir) {
|
|
28
|
-
let entries;
|
|
29
|
-
try {
|
|
30
|
-
entries = await readdir(projectDir);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
const jsonlFiles = entries.filter((e) => e.endsWith('.jsonl'));
|
|
36
|
-
const fileInfos = await Promise.allSettled(jsonlFiles.map(async (name) => {
|
|
37
|
-
const filePath = join(projectDir, name);
|
|
38
|
-
const s = await stat(filePath);
|
|
39
|
-
return { name, filePath, mtime: s.mtime.getTime() };
|
|
40
|
-
}));
|
|
41
|
-
return fileInfos
|
|
42
|
-
.filter((r) => r.status === 'fulfilled')
|
|
43
|
-
.map((r) => r.value);
|
|
44
|
-
}
|
|
45
|
-
async function scanClaudeSessions(repositoryPath, limit, includeWorktrees = false) {
|
|
46
|
-
const dirName = claudeEncodePath(repositoryPath);
|
|
47
|
-
const projectsRoot = join(homedir(), '.claude', 'projects');
|
|
48
|
-
// Collect files from the exact directory
|
|
49
|
-
const primaryDir = join(projectsRoot, dirName);
|
|
50
|
-
let allFiles = await collectJsonlFiles(primaryDir);
|
|
51
|
-
// When includeWorktrees is set, also scan:
|
|
52
|
-
// 1. Directories whose name starts with the encoded repo path (git worktrees, .worktrees)
|
|
53
|
-
// 2. Shep worktree directories (~/.shep/repos/<hash>/wt/*) which use a hash of the repo path
|
|
54
|
-
if (includeWorktrees) {
|
|
55
|
-
try {
|
|
56
|
-
const allDirs = await readdir(projectsRoot);
|
|
57
|
-
// Match git-style worktrees (same prefix as repo path)
|
|
58
|
-
const prefixMatches = allDirs.filter((d) => d !== dirName && d.startsWith(dirName));
|
|
59
|
-
// Match shep worktrees: compute repo hash → find dirs starting with encoded shep path
|
|
60
|
-
const normalizedRepoPath = repositoryPath.replace(/\\/g, '/');
|
|
61
|
-
const repoHash = createHash('sha256').update(normalizedRepoPath).digest('hex').slice(0, 16);
|
|
62
|
-
const shepHome = join(homedir(), '.shep').replace(/\\/g, '/');
|
|
63
|
-
const shepWorktreePrefix = claudeEncodePath(join(shepHome, 'repos', repoHash));
|
|
64
|
-
const shepMatches = allDirs.filter((d) => d.startsWith(shepWorktreePrefix) && !prefixMatches.includes(d) && d !== dirName);
|
|
65
|
-
const worktreeDirs = [...prefixMatches, ...shepMatches];
|
|
66
|
-
const worktreeResults = await Promise.all(worktreeDirs.map((d) => collectJsonlFiles(join(projectsRoot, d))));
|
|
67
|
-
for (const files of worktreeResults) {
|
|
68
|
-
allFiles = allFiles.concat(files);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
// projectsRoot doesn't exist — no sessions at all
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
const valid = allFiles.sort((a, b) => b.mtime - a.mtime).slice(0, limit);
|
|
76
|
-
// Parse each file
|
|
77
|
-
const results = await Promise.allSettled(valid.map(async (fi) => parseClaudeSession(fi.filePath, fi.name, fi.mtime, repositoryPath)));
|
|
78
|
-
return results
|
|
79
|
-
.filter((r) => r.status === 'fulfilled')
|
|
80
|
-
.map((r) => r.value)
|
|
81
|
-
.filter((s) => s !== null);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Read the first N bytes of a file to extract preview and timestamps
|
|
85
|
-
* without loading the entire (potentially multi-MB) session file.
|
|
86
|
-
*/
|
|
87
|
-
const PREVIEW_READ_BYTES = 8_192; // 8KB is enough for first few messages
|
|
88
|
-
async function parseClaudeSession(filePath, fileName, mtime, repositoryPath) {
|
|
89
|
-
const { createReadStream } = await import('node:fs');
|
|
90
|
-
const id = fileName.replace('.jsonl', '');
|
|
91
|
-
// Read only the first chunk to extract preview and first timestamp
|
|
92
|
-
let preview = null;
|
|
93
|
-
let firstTimestamp = null;
|
|
94
|
-
let messageCount = 0;
|
|
95
|
-
const head = await new Promise((resolve) => {
|
|
96
|
-
const chunks = [];
|
|
97
|
-
let size = 0;
|
|
98
|
-
const stream = createReadStream(filePath, { end: PREVIEW_READ_BYTES - 1 });
|
|
99
|
-
stream.on('data', (chunk) => {
|
|
100
|
-
chunks.push(chunk);
|
|
101
|
-
size += chunk.length;
|
|
102
|
-
});
|
|
103
|
-
stream.on('end', () => resolve(Buffer.concat(chunks, size).toString('utf-8')));
|
|
104
|
-
stream.on('error', () => resolve(''));
|
|
105
|
-
});
|
|
106
|
-
if (!head)
|
|
107
|
-
return null;
|
|
108
|
-
const lines = head.split('\n').filter((l) => l.trim());
|
|
109
|
-
for (const line of lines) {
|
|
110
|
-
try {
|
|
111
|
-
const entry = JSON.parse(line);
|
|
112
|
-
if (entry.type === 'user' || entry.type === 'assistant') {
|
|
113
|
-
const role = entry.message?.role;
|
|
114
|
-
if (role === 'user' || role === 'assistant') {
|
|
115
|
-
messageCount++;
|
|
116
|
-
if (entry.timestamp) {
|
|
117
|
-
firstTimestamp ??= entry.timestamp;
|
|
118
|
-
}
|
|
119
|
-
if (role === 'user' && preview === null) {
|
|
120
|
-
preview = extractText(entry.message?.content);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
if (messageCount === 0)
|
|
130
|
-
return null;
|
|
131
|
-
// Use mtime as lastMessageAt — avoids reading entire file for last line
|
|
132
|
-
const mtimeIso = new Date(mtime).toISOString();
|
|
133
|
-
return {
|
|
134
|
-
id,
|
|
135
|
-
agentType: 'claude-code',
|
|
136
|
-
preview,
|
|
137
|
-
messageCount,
|
|
138
|
-
firstMessageAt: firstTimestamp,
|
|
139
|
-
lastMessageAt: mtimeIso,
|
|
140
|
-
createdAt: firstTimestamp ?? mtimeIso,
|
|
141
|
-
projectPath: repositoryPath,
|
|
142
|
-
filePath,
|
|
143
|
-
_mtime: mtime,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
// ── Cursor session scanner ────────────────────────────────────────────
|
|
147
|
-
async function scanCursorSessions(repositoryPath, limit) {
|
|
148
|
-
const dirName = cursorEncodePath(repositoryPath);
|
|
149
|
-
const transcriptsDir = join(homedir(), '.cursor', 'projects', dirName, 'agent-transcripts');
|
|
150
|
-
let entries;
|
|
151
|
-
try {
|
|
152
|
-
entries = await readdir(transcriptsDir);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return [];
|
|
156
|
-
}
|
|
157
|
-
// Cursor has two session structures:
|
|
158
|
-
// 1. Flat: agent-transcripts/<uuid>.jsonl
|
|
159
|
-
// 2. Nested: agent-transcripts/<uuid>/<uuid>.jsonl
|
|
160
|
-
const fileInfos = await Promise.allSettled(entries.map(async (entry) => {
|
|
161
|
-
const entryPath = join(transcriptsDir, entry);
|
|
162
|
-
const s = await stat(entryPath);
|
|
163
|
-
if (s.isFile() && entry.endsWith('.jsonl')) {
|
|
164
|
-
// Flat structure
|
|
165
|
-
return { name: entry, filePath: entryPath, mtime: s.mtime.getTime() };
|
|
166
|
-
}
|
|
167
|
-
if (s.isDirectory()) {
|
|
168
|
-
// Nested structure — look for <uuid>.jsonl inside
|
|
169
|
-
const jsonlPath = join(entryPath, `${entry}.jsonl`);
|
|
170
|
-
try {
|
|
171
|
-
const jsonlStat = await stat(jsonlPath);
|
|
172
|
-
return {
|
|
173
|
-
name: `${entry}.jsonl`,
|
|
174
|
-
filePath: jsonlPath,
|
|
175
|
-
mtime: jsonlStat.mtime.getTime(),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return null;
|
|
183
|
-
}));
|
|
184
|
-
const valid = fileInfos
|
|
185
|
-
.filter((r) => r.status === 'fulfilled')
|
|
186
|
-
.map((r) => r.value)
|
|
187
|
-
.filter((v) => v !== null)
|
|
188
|
-
.sort((a, b) => b.mtime - a.mtime)
|
|
189
|
-
.slice(0, limit);
|
|
190
|
-
const results = await Promise.allSettled(valid.map(async (fi) => parseCursorSession(fi.filePath, fi.name, fi.mtime, repositoryPath)));
|
|
191
|
-
return results
|
|
192
|
-
.filter((r) => r.status === 'fulfilled')
|
|
193
|
-
.map((r) => r.value)
|
|
194
|
-
.filter((s) => s !== null);
|
|
195
|
-
}
|
|
196
|
-
async function parseCursorSession(filePath, fileName, mtime, repositoryPath) {
|
|
197
|
-
const { createReadStream } = await import('node:fs');
|
|
198
|
-
const id = fileName.replace('.jsonl', '');
|
|
199
|
-
// Read only the first chunk for preview extraction
|
|
200
|
-
const head = await new Promise((resolve) => {
|
|
201
|
-
const chunks = [];
|
|
202
|
-
let size = 0;
|
|
203
|
-
const stream = createReadStream(filePath, { end: PREVIEW_READ_BYTES - 1 });
|
|
204
|
-
stream.on('data', (chunk) => {
|
|
205
|
-
chunks.push(chunk);
|
|
206
|
-
size += chunk.length;
|
|
207
|
-
});
|
|
208
|
-
stream.on('end', () => resolve(Buffer.concat(chunks, size).toString('utf-8')));
|
|
209
|
-
stream.on('error', () => resolve(''));
|
|
210
|
-
});
|
|
211
|
-
if (!head)
|
|
212
|
-
return null;
|
|
213
|
-
let preview = null;
|
|
214
|
-
let messageCount = 0;
|
|
215
|
-
const lines = head.split('\n').filter((l) => l.trim());
|
|
216
|
-
for (const line of lines) {
|
|
217
|
-
try {
|
|
218
|
-
const entry = JSON.parse(line);
|
|
219
|
-
if (entry.role === 'user' || entry.role === 'assistant') {
|
|
220
|
-
messageCount++;
|
|
221
|
-
if (entry.role === 'user' && preview === null) {
|
|
222
|
-
preview = extractText(entry.message?.content);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
catch {
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (messageCount === 0)
|
|
231
|
-
return null;
|
|
232
|
-
const mtimeIso = new Date(mtime).toISOString();
|
|
233
|
-
return {
|
|
234
|
-
id,
|
|
235
|
-
agentType: 'cursor',
|
|
236
|
-
preview,
|
|
237
|
-
messageCount,
|
|
238
|
-
firstMessageAt: mtimeIso,
|
|
239
|
-
lastMessageAt: mtimeIso,
|
|
240
|
-
createdAt: mtimeIso,
|
|
241
|
-
projectPath: repositoryPath,
|
|
242
|
-
filePath,
|
|
243
|
-
_mtime: mtime,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
// ── Shared helpers ────────────────────────────────────────────────────
|
|
247
|
-
function extractText(content) {
|
|
248
|
-
if (typeof content === 'string')
|
|
249
|
-
return content;
|
|
250
|
-
if (Array.isArray(content)) {
|
|
251
|
-
for (const block of content) {
|
|
252
|
-
if (typeof block === 'object' && block !== null) {
|
|
253
|
-
const b = block;
|
|
254
|
-
if (b.type === 'text' && typeof b.text === 'string')
|
|
255
|
-
return b.text;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return null;
|
|
260
|
-
}
|
|
261
|
-
// ── Route handler ─────────────────────────────────────────────────────
|
|
262
4
|
/**
|
|
263
5
|
* GET /api/sessions?repositoryPath=<path>&limit=<n>
|
|
264
6
|
*
|
|
@@ -274,15 +16,7 @@ export async function GET(request) {
|
|
|
274
16
|
return NextResponse.json({ error: 'repositoryPath is required' }, { status: 400 });
|
|
275
17
|
}
|
|
276
18
|
try {
|
|
277
|
-
|
|
278
|
-
const [claudeSessions, cursorSessions] = await Promise.all([
|
|
279
|
-
scanClaudeSessions(repositoryPath, limit, includeWorktrees),
|
|
280
|
-
scanCursorSessions(repositoryPath, limit),
|
|
281
|
-
]);
|
|
282
|
-
// Merge and sort by mtime descending, apply limit
|
|
283
|
-
const allSessions = [...claudeSessions, ...cursorSessions]
|
|
284
|
-
.sort((a, b) => b._mtime - a._mtime)
|
|
285
|
-
.slice(0, Math.min(limit, 50));
|
|
19
|
+
const allSessions = await scanSessionsForPath(repositoryPath, limit, includeWorktrees);
|
|
286
20
|
return NextResponse.json({
|
|
287
21
|
sessions: allSessions.map(({ _mtime, ...s }) => s),
|
|
288
22
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { type SessionResult } from '../../../lib/session-scanner.js';
|
|
3
|
+
export declare const dynamic = "force-dynamic";
|
|
4
|
+
type SessionSummaryFromBatch = Omit<SessionResult, '_mtime'>;
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/sessions-batch
|
|
7
|
+
*
|
|
8
|
+
* No parameters needed — resolves all repos and features from the DI container,
|
|
9
|
+
* scans sessions for each, and returns { sessionsByPath: Record<string, SessionSummary[]> }.
|
|
10
|
+
*/
|
|
11
|
+
export declare function GET(): Promise<NextResponse<{
|
|
12
|
+
sessionsByPath: Record<string, SessionSummaryFromBatch[]>;
|
|
13
|
+
}> | NextResponse<{
|
|
14
|
+
error: string;
|
|
15
|
+
}>>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/sessions-batch/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEhF,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC,KAAK,uBAAuB,GAAG,IAAI,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;AAW7D;;;;;GAKG;AACH,wBAAsB,GAAG;;;;IAsDxB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { resolve } from '../../../lib/server-container.js';
|
|
3
|
+
import { scanSessionsForPath } from '../../../lib/session-scanner.js';
|
|
4
|
+
export const dynamic = 'force-dynamic';
|
|
5
|
+
const SESSIONS_PER_PATH = 5;
|
|
6
|
+
// ── Server-side cache ─────────────────────────────────────────────────
|
|
7
|
+
const CACHE_TTL_MS = 30_000;
|
|
8
|
+
let cache = null;
|
|
9
|
+
// ── Route handler ─────────────────────────────────────────────────────
|
|
10
|
+
/**
|
|
11
|
+
* GET /api/sessions-batch
|
|
12
|
+
*
|
|
13
|
+
* No parameters needed — resolves all repos and features from the DI container,
|
|
14
|
+
* scans sessions for each, and returns { sessionsByPath: Record<string, SessionSummary[]> }.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET() {
|
|
17
|
+
// Return cache if fresh
|
|
18
|
+
if (cache && Date.now() - cache.createdAt < CACHE_TTL_MS) {
|
|
19
|
+
return NextResponse.json({ sessionsByPath: cache.data });
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const listRepos = resolve('ListRepositoriesUseCase');
|
|
23
|
+
const listFeatures = resolve('ListFeaturesUseCase');
|
|
24
|
+
const [repositories, features] = await Promise.all([
|
|
25
|
+
listRepos.execute(),
|
|
26
|
+
listFeatures.execute({ includeArchived: false }),
|
|
27
|
+
]);
|
|
28
|
+
// Build unique path specs: repos with includeWorktrees, features with their worktree path
|
|
29
|
+
const pathSpecs = [];
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
for (const repo of repositories) {
|
|
32
|
+
if (repo.path && !seen.has(repo.path)) {
|
|
33
|
+
seen.add(repo.path);
|
|
34
|
+
pathSpecs.push({ path: repo.path, includeWorktrees: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const feature of features) {
|
|
38
|
+
const sessionPath = feature.worktreePath ?? feature.repositoryPath;
|
|
39
|
+
if (sessionPath && !seen.has(sessionPath)) {
|
|
40
|
+
seen.add(sessionPath);
|
|
41
|
+
pathSpecs.push({ path: sessionPath, includeWorktrees: false });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Scan all paths in parallel
|
|
45
|
+
const results = await Promise.all(pathSpecs.map(async ({ path, includeWorktrees }) => {
|
|
46
|
+
const sessions = await scanSessionsForPath(path, SESSIONS_PER_PATH, includeWorktrees);
|
|
47
|
+
return { path, sessions: sessions.map(({ _mtime, ...s }) => s) };
|
|
48
|
+
}));
|
|
49
|
+
const sessionsByPath = {};
|
|
50
|
+
for (const { path, sessions } of results) {
|
|
51
|
+
sessionsByPath[path] = sessions;
|
|
52
|
+
}
|
|
53
|
+
cache = { data: sessionsByPath, createdAt: Date.now() };
|
|
54
|
+
return NextResponse.json({ sessionsByPath });
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.error('[API] GET /api/sessions-batch error:', error);
|
|
59
|
+
return NextResponse.json({ error: String(error) }, { status: 500 });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"feature-drawer-client.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/control-center-drawer/feature-drawer-client.tsx"],"names":[],"mappings":"AAkDA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAK/D,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,wBAAwB,2CAiuB1F"}
|
package/dist/src/presentation/web/components/common/control-center-drawer/feature-drawer-client.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
4
4
|
import { useRouter, usePathname } from 'next/navigation';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
|
-
import { Loader2, Trash2, Play, Square, Copy, Check, Code2, ExternalLink } from 'lucide-react';
|
|
6
|
+
import { Loader2, Trash2, Play, Square, Copy, Check, Code2, ExternalLink, Archive, ArchiveRestore, } from 'lucide-react';
|
|
7
7
|
import { approveFeature } from '../../../app/actions/approve-feature.js';
|
|
8
8
|
import { resumeFeature } from '../../../app/actions/resume-feature.js';
|
|
9
9
|
import { startFeature } from '../../../app/actions/start-feature.js';
|
|
@@ -106,6 +106,19 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
|
|
|
106
106
|
// ── Delete state ───────────────────────────────────────────────────────
|
|
107
107
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
108
108
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
109
|
+
// ── Archive state ─────────────────────────────────────────────────────
|
|
110
|
+
const [isArchiving, setIsArchiving] = useState(false);
|
|
111
|
+
// Reset archive loading spinner when the feature state or feature ID
|
|
112
|
+
// changes (e.g. state flips to 'archived' / 'done' after the server
|
|
113
|
+
// action, or the user navigates to a different feature drawer).
|
|
114
|
+
const archiveResetKey = `${featureNode?.featureId}:${featureNode?.state}`;
|
|
115
|
+
const prevArchiveResetKeyRef = useRef(archiveResetKey);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (archiveResetKey !== prevArchiveResetKeyRef.current) {
|
|
118
|
+
prevArchiveResetKeyRef.current = archiveResetKey;
|
|
119
|
+
setIsArchiving(false);
|
|
120
|
+
}
|
|
121
|
+
}, [archiveResetKey]);
|
|
109
122
|
// ── Shared reject state ────────────────────────────────────────────────
|
|
110
123
|
const [isRejecting, setIsRejecting] = useState(false);
|
|
111
124
|
const isRejectingRef = useRef(false);
|
|
@@ -290,6 +303,20 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
|
|
|
290
303
|
}));
|
|
291
304
|
router.push('/');
|
|
292
305
|
}, [router]);
|
|
306
|
+
const handleArchive = useCallback((featureId) => {
|
|
307
|
+
setIsArchiving(true);
|
|
308
|
+
window.dispatchEvent(new CustomEvent('shep:feature-archive-requested', {
|
|
309
|
+
detail: { featureId },
|
|
310
|
+
}));
|
|
311
|
+
router.push('/');
|
|
312
|
+
}, [router]);
|
|
313
|
+
const handleUnarchive = useCallback((featureId) => {
|
|
314
|
+
setIsArchiving(true);
|
|
315
|
+
window.dispatchEvent(new CustomEvent('shep:feature-unarchive-requested', {
|
|
316
|
+
detail: { featureId },
|
|
317
|
+
}));
|
|
318
|
+
router.push('/');
|
|
319
|
+
}, [router]);
|
|
293
320
|
const handleRetry = useCallback(async (featureId) => {
|
|
294
321
|
const result = await resumeFeature(featureId);
|
|
295
322
|
if (result.error) {
|
|
@@ -373,7 +400,7 @@ export function FeatureDrawerClient({ view: initialView, urlTab }) {
|
|
|
373
400
|
const repoName = featureNode.repositoryName ??
|
|
374
401
|
featureNode.repositoryPath.split('/').filter(Boolean).at(-1) ??
|
|
375
402
|
'';
|
|
376
|
-
header = (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-header", children: [_jsx(DrawerTitle, { children: featureNode.name }), repoName ? (_jsxs("div", { className: "flex items-center gap-1.5 pt-0.5", children: [_jsx(Code2, { className: "text-muted-foreground size-3.5 shrink-0" }), featureNode.remoteUrl ? (_jsxs("a", { href: featureNode.remoteUrl, target: "_blank", rel: "noopener noreferrer", className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs transition-colors", "data-testid": "feature-drawer-repo-link", children: [repoName, _jsx(ExternalLink, { className: "size-3" })] })) : (_jsx("span", { className: "text-muted-foreground text-xs", children: repoName }))] })) : null, _jsx(DrawerDescription, { className: "sr-only", children: featureNode.name })] }), featureActionsInput ? (_jsxs("div", { className: "flex items-center gap-2 pt-2", "data-testid": "feature-drawer-actions", children: [_jsx(OpenActionMenu, { actions: featureActions, repositoryPath: featureActionsInput.repositoryPath, worktreePath: featureActionsInput.worktreePath, showSpecs: !!featureActionsInput.specPath }), featureFlags.envDeploy && featureDeployTarget ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isFeatureDeployActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isFeatureDeployActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isFeatureDeployActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url, targetId: featureDeployTarget?.targetId })) : null] })) : null, _jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [_jsx("code", { className: "bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs", children: shortId }), _jsx("button", { type: "button", onClick: handleCopyId, className: "text-muted-foreground hover:text-foreground inline-flex items-center rounded p-0.5 transition-colors", "aria-label": "Copy feature ID", "data-testid": "feature-drawer-copy-id", children: idCopied ? (_jsx(Check, { className: "size-3.5 text-green-600" })) : (_jsx(Copy, { className: "size-3.5" })) })] }), featureNode.featureId ? (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Delete feature", disabled: isDeleting, className: "text-muted-foreground hover:text-destructive", "data-testid": "feature-drawer-delete", onClick: () => setDeleteDialogOpen(true), children: isDeleting ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(Trash2, { className: "size-4" })) }), _jsx(DeleteFeatureDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, onConfirm: (cleanup, cascadeDelete, closePr) => handleDelete(featureNode.featureId, cleanup, cascadeDelete, closePr), isDeleting: isDeleting, featureName: featureNode.name, featureId: featureNode.featureId, hasChildren: featureNode.hasChildren, hasOpenPr: !!featureNode.pr && featureNode.pr.status === 'Open' })] })) : null] })) : null] }));
|
|
403
|
+
header = (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": "feature-drawer-header", children: [_jsx(DrawerTitle, { children: featureNode.name }), repoName ? (_jsxs("div", { className: "flex items-center gap-1.5 pt-0.5", children: [_jsx(Code2, { className: "text-muted-foreground size-3.5 shrink-0" }), featureNode.remoteUrl ? (_jsxs("a", { href: featureNode.remoteUrl, target: "_blank", rel: "noopener noreferrer", className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs transition-colors", "data-testid": "feature-drawer-repo-link", children: [repoName, _jsx(ExternalLink, { className: "size-3" })] })) : (_jsx("span", { className: "text-muted-foreground text-xs", children: repoName }))] })) : null, _jsx(DrawerDescription, { className: "sr-only", children: featureNode.name })] }), featureActionsInput ? (_jsxs("div", { className: "flex items-center gap-2 pt-2", "data-testid": "feature-drawer-actions", children: [_jsx(OpenActionMenu, { actions: featureActions, repositoryPath: featureActionsInput.repositoryPath, worktreePath: featureActionsInput.worktreePath, showSpecs: !!featureActionsInput.specPath }), featureFlags.envDeploy && featureDeployTarget ? (_jsxs(_Fragment, { children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { children: _jsx(ActionButton, { label: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server', onClick: isFeatureDeployActive ? deployAction.stop : deployAction.deploy, loading: deployAction.deployLoading || deployAction.stopLoading, error: !!deployAction.deployError, icon: isFeatureDeployActive ? Square : Play, iconOnly: true, variant: "outline", size: "icon-sm" }) }) }), _jsx(TooltipContent, { children: isFeatureDeployActive ? 'Stop Dev Server' : 'Start Dev Server' })] }) }), isFeatureDeployActive ? (_jsx(DeploymentStatusBadge, { status: deployAction.status, url: deployAction.url, targetId: featureDeployTarget?.targetId })) : null] })) : null, _jsxs("div", { className: "ml-auto flex items-center gap-1.5", children: [_jsx("code", { className: "bg-muted text-muted-foreground rounded px-1.5 py-0.5 font-mono text-xs", children: shortId }), _jsx("button", { type: "button", onClick: handleCopyId, className: "text-muted-foreground hover:text-foreground inline-flex items-center rounded p-0.5 transition-colors", "aria-label": "Copy feature ID", "data-testid": "feature-drawer-copy-id", children: idCopied ? (_jsx(Check, { className: "size-3.5 text-green-600" })) : (_jsx(Copy, { className: "size-3.5" })) })] }), featureNode.featureId ? (_jsxs(_Fragment, { children: [featureNode.state === 'archived' ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Unarchive feature", disabled: isArchiving, className: "text-muted-foreground hover:text-primary", "data-testid": "feature-drawer-unarchive", onClick: () => handleUnarchive(featureNode.featureId), children: isArchiving ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(ArchiveRestore, { className: "size-4" })) }) }), _jsx(TooltipContent, { children: "Unarchive feature" })] }) })) : featureNode.state !== 'deleting' ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Archive feature", disabled: isArchiving, className: "text-muted-foreground hover:text-foreground", "data-testid": "feature-drawer-archive", onClick: () => handleArchive(featureNode.featureId), children: isArchiving ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(Archive, { className: "size-4" })) }) }), _jsx(TooltipContent, { children: "Archive feature" })] }) })) : null, _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Delete feature", disabled: isDeleting, className: "text-muted-foreground hover:text-destructive", "data-testid": "feature-drawer-delete", onClick: () => setDeleteDialogOpen(true), children: isDeleting ? (_jsx(Loader2, { className: "size-4 animate-spin" })) : (_jsx(Trash2, { className: "size-4" })) }), _jsx(DeleteFeatureDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, onConfirm: (cleanup, cascadeDelete, closePr) => handleDelete(featureNode.featureId, cleanup, cascadeDelete, closePr), isDeleting: isDeleting, featureName: featureNode.name, featureId: featureNode.featureId, hasChildren: featureNode.hasChildren, hasOpenPr: !!featureNode.pr && featureNode.pr.status === 'Open' })] })) : null] })) : null] }));
|
|
377
404
|
}
|
|
378
405
|
// ── Body ──────────────────────────────────────────────────────────────
|
|
379
406
|
let body = null;
|
package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts
CHANGED
|
@@ -18,6 +18,6 @@ interface FeatureSessionsDropdownProps {
|
|
|
18
18
|
/** Callback to create a feature from a session. Only shown on repo nodes. */
|
|
19
19
|
onCreateFromSession?: (session: SessionSummary, sessionFilePath: string) => void;
|
|
20
20
|
}
|
|
21
|
-
export declare function FeatureSessionsDropdown({ repositoryPath, className,
|
|
21
|
+
export declare function FeatureSessionsDropdown({ repositoryPath, className, onCreateFromSession, }: FeatureSessionsDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
22
22
|
export {};
|
|
23
23
|
//# sourceMappingURL=feature-sessions-dropdown.d.ts.map
|
package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-sessions-dropdown.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,4BAA4B;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CAClF;AAmDD,wBAAgB,uBAAuB,CAAC,EACtC,cAAc,EACd,SAAS,EACT,
|
|
1
|
+
{"version":3,"file":"feature-sessions-dropdown.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.tsx"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,4BAA4B;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;CAClF;AAmDD,wBAAgB,uBAAuB,CAAC,EACtC,cAAc,EACd,SAAS,EACT,mBAAmB,GACpB,EAAE,4BAA4B,2CA6F9B"}
|
package/dist/src/presentation/web/components/common/feature-node/feature-sessions-dropdown.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback
|
|
4
|
-
import { History, Copy, ExternalLink, Terminal, MessageSquare, Clock,
|
|
3
|
+
import { useState, useCallback } from 'react';
|
|
4
|
+
import { History, Copy, ExternalLink, Terminal, MessageSquare, Clock, ChevronDown, Sparkles, } from 'lucide-react';
|
|
5
5
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuPortal, } from '../../ui/dropdown-menu.js';
|
|
6
6
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip.js';
|
|
7
7
|
import { cn } from '../../../lib/utils.js';
|
|
8
8
|
import { getAgentTypeIcon } from '../../common/feature-node/agent-type-icons.js';
|
|
9
|
+
import { useSessionsContext } from '../../../hooks/sessions-provider.js';
|
|
9
10
|
const ACTIVE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
11
|
const PREVIEW_COUNT = 3;
|
|
11
12
|
function isSessionActive(session) {
|
|
@@ -56,88 +57,29 @@ async function copyToClipboard(text) {
|
|
|
56
57
|
function stopNodeEvent(e) {
|
|
57
58
|
e.stopPropagation();
|
|
58
59
|
}
|
|
59
|
-
export function FeatureSessionsDropdown({ repositoryPath, className,
|
|
60
|
-
const [sessions, setSessions] = useState([]);
|
|
61
|
-
const [loading, setLoading] = useState(false);
|
|
62
|
-
const [fetched, setFetched] = useState(false);
|
|
63
|
-
const [hasActiveSessions, setHasActiveSessions] = useState(false);
|
|
60
|
+
export function FeatureSessionsDropdown({ repositoryPath, className, onCreateFromSession, }) {
|
|
64
61
|
const [expanded, setExpanded] = useState(false);
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
setSessions([]);
|
|
71
|
-
setFetched(false);
|
|
72
|
-
setHasActiveSessions(false);
|
|
73
|
-
setExpanded(false);
|
|
74
|
-
}
|
|
75
|
-
}, [repositoryPath]);
|
|
76
|
-
// Fetch sessions on mount. Fast because we only scan the matching project directory.
|
|
77
|
-
// Populates count badge + active indicator, and pre-loads the dropdown.
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
let cancelled = false;
|
|
80
|
-
const params = new URLSearchParams({
|
|
81
|
-
repositoryPath,
|
|
82
|
-
limit: '10',
|
|
83
|
-
...(includeWorktrees && { includeWorktrees: 'true' }),
|
|
84
|
-
});
|
|
85
|
-
fetch(`/api/sessions?${params.toString()}`)
|
|
86
|
-
.then((res) => (res.ok ? res.json() : null))
|
|
87
|
-
.then((data) => {
|
|
88
|
-
if (!cancelled && data?.sessions) {
|
|
89
|
-
setSessions(data.sessions);
|
|
90
|
-
setHasActiveSessions(data.sessions.some(isSessionActive));
|
|
91
|
-
setFetched(true);
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
.catch(() => undefined);
|
|
95
|
-
return () => {
|
|
96
|
-
cancelled = true;
|
|
97
|
-
};
|
|
98
|
-
}, [repositoryPath, includeWorktrees]);
|
|
99
|
-
// Re-fetch on dropdown open if not already loaded (e.g. path changed)
|
|
100
|
-
const doFetch = useCallback(async () => {
|
|
101
|
-
if (fetched)
|
|
102
|
-
return;
|
|
103
|
-
setLoading(true);
|
|
104
|
-
try {
|
|
105
|
-
const params = new URLSearchParams({
|
|
106
|
-
repositoryPath,
|
|
107
|
-
limit: '10',
|
|
108
|
-
...(includeWorktrees && { includeWorktrees: 'true' }),
|
|
109
|
-
});
|
|
110
|
-
const res = await fetch(`/api/sessions?${params.toString()}`);
|
|
111
|
-
if (res.ok) {
|
|
112
|
-
const data = (await res.json());
|
|
113
|
-
setSessions(data.sessions);
|
|
114
|
-
setHasActiveSessions(data.sessions.some(isSessionActive));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// Silently fail
|
|
119
|
-
}
|
|
120
|
-
finally {
|
|
121
|
-
setLoading(false);
|
|
122
|
-
setFetched(true);
|
|
123
|
-
}
|
|
124
|
-
}, [repositoryPath, fetched, includeWorktrees]);
|
|
62
|
+
// Read sessions from the centralized SessionsProvider context.
|
|
63
|
+
// Sessions are batch-fetched every 30s — no per-instance HTTP calls.
|
|
64
|
+
const { getSessionsForPath, hasActiveSessions: hasActiveForPath } = useSessionsContext();
|
|
65
|
+
const sessions = getSessionsForPath(repositoryPath);
|
|
66
|
+
const active = hasActiveForPath(repositoryPath);
|
|
125
67
|
const handleOpenChange = useCallback((open) => {
|
|
126
|
-
if (open)
|
|
127
|
-
|
|
128
|
-
}, [
|
|
68
|
+
if (!open)
|
|
69
|
+
setExpanded(false);
|
|
70
|
+
}, []);
|
|
129
71
|
const visibleSessions = expanded ? sessions : sessions.slice(0, PREVIEW_COUNT);
|
|
130
72
|
const hasMore = sessions.length > PREVIEW_COUNT;
|
|
131
|
-
return (_jsxs(DropdownMenu, { modal: false, onOpenChange: handleOpenChange, children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": "View sessions", "data-testid": "feature-node-sessions-button", className: cn('nodrag relative flex h-5 cursor-pointer items-center gap-0.5 rounded px-0.5 text-[10px] transition-colors', 'text-muted-foreground hover:text-foreground hover:bg-muted', className), onClick: stopNodeEvent, onPointerDown: stopNodeEvent, children: [_jsx(History, { className: "h-3 w-3 shrink-0" }), sessions.length > 0 ? (_jsx("span", { "data-testid": "feature-node-sessions-count", children: sessions.length })) : null,
|
|
73
|
+
return (_jsxs(DropdownMenu, { modal: false, onOpenChange: handleOpenChange, children: [_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": "View sessions", "data-testid": "feature-node-sessions-button", className: cn('nodrag relative flex h-5 cursor-pointer items-center gap-0.5 rounded px-0.5 text-[10px] transition-colors', 'text-muted-foreground hover:text-foreground hover:bg-muted', className), onClick: stopNodeEvent, onPointerDown: stopNodeEvent, children: [_jsx(History, { className: "h-3 w-3 shrink-0" }), sessions.length > 0 ? (_jsx("span", { "data-testid": "feature-node-sessions-count", children: sessions.length })) : null, active ? (_jsx("span", { className: "absolute -top-0.5 -right-0.5 h-1.5 w-1.5 rounded-full bg-emerald-500" })) : null] }) }) }), _jsx(TooltipContent, { side: "top", children: active ? 'Sessions (active)' : 'Sessions' })] }) }), _jsxs(DropdownMenuContent, { align: "start", side: "bottom", className: "w-80", onClick: stopNodeEvent, onPointerDown: stopNodeEvent, children: [_jsxs(DropdownMenuLabel, { className: "flex items-center gap-1.5 text-xs", children: [_jsx(History, { className: "h-3 w-3" }), "Agent Sessions"] }), _jsx(DropdownMenuSeparator, {}), sessions.length === 0 ? (_jsx("div", { className: "text-muted-foreground py-4 text-center text-xs", children: "No sessions found" })) : (_jsxs(_Fragment, { children: [visibleSessions.map((session) => (_jsx(SessionRow, { session: session, repositoryPath: repositoryPath, onCreateFromSession: onCreateFromSession }, session.id))), hasMore ? (_jsxs(DropdownMenuItem, { className: "text-muted-foreground justify-center gap-1 py-1.5 text-[10px]", onClick: (e) => {
|
|
132
74
|
e.preventDefault();
|
|
133
75
|
setExpanded((v) => !v);
|
|
134
76
|
}, children: [_jsx(ChevronDown, { className: cn('h-3 w-3 transition-transform', expanded && 'rotate-180') }), expanded ? 'Show less' : `Show ${sessions.length - PREVIEW_COUNT} more`] })) : null] }))] })] }));
|
|
135
77
|
}
|
|
136
78
|
// ── Session row component ─────────────────────────────────────────────
|
|
137
79
|
function SessionRow({ session, repositoryPath, onCreateFromSession, }) {
|
|
138
|
-
const
|
|
80
|
+
const sessionActive = isSessionActive(session);
|
|
139
81
|
const AgentIcon = getAgentTypeIcon(session.agentType);
|
|
140
|
-
return (_jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { className: "flex items-start gap-2 py-2 pr-2", children: [_jsxs("div", { className: "relative mt-0.5 shrink-0", children: [_jsx(AgentIcon, { className: "h-4 w-4" }),
|
|
82
|
+
return (_jsxs(DropdownMenuSub, { children: [_jsxs(DropdownMenuSubTrigger, { className: "flex items-start gap-2 py-2 pr-2", children: [_jsxs("div", { className: "relative mt-0.5 shrink-0", children: [_jsx(AgentIcon, { className: "h-4 w-4" }), sessionActive ? (_jsx("span", { className: "border-background absolute -right-0.5 -bottom-0.5 h-2 w-2 rounded-full border bg-emerald-500" })) : null] }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [_jsx("span", { className: "truncate text-xs leading-tight", children: truncatePreview(session.preview) }), _jsxs("div", { className: "text-muted-foreground flex items-center gap-2 text-[10px] leading-tight", children: [_jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(MessageSquare, { className: "h-2.5 w-2.5" }), session.messageCount] }), session.firstMessageAt ? (_jsxs("span", { className: "flex items-center gap-0.5", children: [_jsx(Clock, { className: "h-2.5 w-2.5" }), new Date(session.firstMessageAt).toLocaleDateString()] })) : null, session.lastMessageAt ? (_jsx("span", { className: cn('ml-auto shrink-0', sessionActive ? 'font-medium text-emerald-600' : ''), children: formatRelativeTime(session.lastMessageAt) })) : null] })] })] }), _jsx(DropdownMenuPortal, { children: _jsxs(DropdownMenuSubContent, { onClick: stopNodeEvent, onPointerDown: stopNodeEvent, children: [_jsxs(DropdownMenuItem, { className: "gap-2 text-xs", onClick: () => void copyToClipboard(`claude --resume ${session.id} --project ${repositoryPath}`), children: [_jsx(Terminal, { className: "h-3.5 w-3.5" }), "Copy resume command"] }), _jsxs(DropdownMenuItem, { className: "gap-2 text-xs", onClick: () => void copyToClipboard(session.id), children: [_jsx(Copy, { className: "h-3.5 w-3.5" }), "Copy session ID"] }), _jsxs(DropdownMenuItem, { className: "gap-2 text-xs", onClick: () => {
|
|
141
83
|
const vscodeUri = `vscode://file${repositoryPath}`;
|
|
142
84
|
window.open(vscodeUri, '_blank');
|
|
143
85
|
}, children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), "Open in IDE"] }), onCreateFromSession && session.filePath ? (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "gap-2 text-xs font-medium text-violet-700 focus:bg-violet-50 focus:text-violet-800", onClick: () => onCreateFromSession(session, session.filePath), children: [_jsx(Sparkles, { className: "h-3.5 w-3.5 text-violet-500" }), "Create feature from session"] })] })) : null] }) })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature-sessions-dropdown.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"feature-sessions-dropdown.stories.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/components/common/feature-node/feature-sessions-dropdown.stories.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AAsEtE,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,uBAAuB,CAM9C,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,uBAAuB,CAAC,CAAC;AAEtD,eAAO,MAAM,YAAY,EAAE,KAE1B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,KAEhC,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,KAEnB,CAAC"}
|