@nextclaw/ui 0.13.11-beta.0 → 0.13.12
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/CHANGELOG.md +35 -0
- package/dist/assets/{api-C0F4luAh.js → api-DmxQnm30.js} +1 -1
- package/dist/assets/{app-presenter-provider-3K24vYTD.js → app-presenter-provider-CDSjiqfE.js} +1 -1
- package/dist/assets/arrow-left-Dd45Mvr1.js +1 -0
- package/dist/assets/channels-list-page-D-WYFmcG.js +8 -0
- package/dist/assets/chat-page-D1X8YNTC.js +105 -0
- package/dist/assets/{config-split-page-bO0K6uQv.js → config-split-page-CdJEkWxv.js} +1 -1
- package/dist/assets/{confirm-dialog-a7YIzprP.js → confirm-dialog-BWRNsRgb.js} +1 -1
- package/dist/assets/{desktop-update-config-CbJZyLND.js → desktop-update-config-DOi5R1fX.js} +1 -1
- package/dist/assets/{dist-T6rf86Nf.js → dist-CyWdaJvr.js} +1 -1
- package/dist/assets/{dist-DQoV-62W.js → dist-DzM2ZEVN.js} +1 -1
- package/dist/assets/doc-browser-D5FJcAyz.js +1 -0
- package/dist/assets/{doc-browser-CTFB6ACN.js → doc-browser-DewXa6oX.js} +1 -1
- package/dist/assets/{doc-browser-context-CP2tI-Br.js → doc-browser-context-CpL2m4Sp.js} +1 -1
- package/dist/assets/{ellipsis-B7CbNPNr.js → ellipsis-BDgTYNDX.js} +1 -1
- package/dist/assets/{es2015-BSPOGpH4.js → es2015-WqekV0OK.js} +1 -1
- package/dist/assets/{external-link-CefT35zP.js → external-link-BLjo8z2a.js} +1 -1
- package/dist/assets/index-Q891JiF_.css +1 -0
- package/dist/assets/{index-DBOXf-UA.js → index-ZDJP_uJc.js} +15 -15
- package/dist/assets/{key-round-BpM3GZn4.js → key-round-Du8x3sWa.js} +1 -1
- package/dist/assets/{loader-circle-_edjOFsw.js → loader-circle-Dz4FssYC.js} +1 -1
- package/dist/assets/mcp-marketplace-page-LmMhKH5a.js +1 -0
- package/dist/assets/{mcp-marketplace-page-IhNyQ9Hh.js → mcp-marketplace-page-RqISBSqO.js} +2 -2
- package/dist/assets/{model-config-Z7oAQmlP.js → model-config-JUsW1Khb.js} +1 -1
- package/dist/assets/{notice-card-DhE1Fafy.js → notice-card-3a7FmAz-.js} +1 -1
- package/dist/assets/play-BziM-Y34.js +1 -0
- package/dist/assets/plus-DrpyArC8.js +1 -0
- package/dist/assets/{popover-D_pJfOlM.js → popover-BlJLguiZ.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-BfhiS3GD.js → provider-scoped-model-input-IMD4W0jD.js} +1 -1
- package/dist/assets/providers-list-BsDpxNzy.js +1 -0
- package/dist/assets/{react-Bl8iJUcF.js → react-BVjl1ZaK.js} +1 -1
- package/dist/assets/{refresh-cw-CqfS1EhK.js → refresh-cw-DFR87zei.js} +1 -1
- package/dist/assets/remote-DjhVMpVd.js +1 -0
- package/dist/assets/rotate-cw-DMBnsYKm.js +1 -0
- package/dist/assets/{runtime-config-page-BvLypM3f.js → runtime-config-page-DzbTVebD.js} +1 -1
- package/dist/assets/{save-CQERCODg.js → save-BDmIlHvN.js} +1 -1
- package/dist/assets/search-CjHGYvTI.js +1 -0
- package/dist/assets/{search-config-mD9kAsXy.js → search-config-CJ6OPxAf.js} +1 -1
- package/dist/assets/{secrets-config-Dvd4VEDw.js → secrets-config-CYOHWL7P.js} +2 -2
- package/dist/assets/{status-dot-DY0xUUvK.js → status-dot-SVPjIyG5.js} +1 -1
- package/dist/assets/{tabs-C77i1w90.js → tabs-Czd2mj6z.js} +1 -1
- package/dist/assets/{tabs-custom-CFpR54-m.js → tabs-custom-Ix4nkp2h.js} +1 -1
- package/dist/assets/{tag-chip-BFTpu75w.js → tag-chip-Cdwb3JSH.js} +1 -1
- package/dist/assets/{tooltip-fs7VQG7d.js → tooltip-CKGxLGnG.js} +1 -1
- package/dist/assets/{trash-2-Bb8mTTLl.js → trash-2-yLA8BEcE.js} +1 -1
- package/dist/assets/x-dpDISboO.js +1 -0
- package/dist/index.html +25 -25
- package/package.json +8 -8
- package/src/features/chat/components/chat-session-workspace-file-preview.test.tsx +39 -0
- package/src/features/chat/components/chat-session-workspace-file-preview.tsx +11 -4
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +86 -64
- package/src/features/chat/components/chat-session-workspace-panel.tsx +17 -25
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +7 -3
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -0
- package/src/features/chat/hooks/use-ncp-chat-derived-state.ts +2 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +4 -0
- package/src/features/chat/stores/chat-thread.store.ts +2 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +21 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +6 -0
- package/src/shared/components/cron-config.tsx +367 -91
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/cron/cron-job-view.utils.ts +124 -6
- package/src/shared/lib/i18n/README.md +7 -0
- package/src/shared/lib/i18n/cron-labels.utils.ts +32 -0
- package/src/shared/lib/i18n/index.ts +2 -25
- package/dist/assets/arrow-left-CI0Ocu9p.js +0 -1
- package/dist/assets/channels-list-page-DT8gjnFw.js +0 -8
- package/dist/assets/chat-page-By1I_JyH.js +0 -105
- package/dist/assets/doc-browser-yi02ASOL.js +0 -1
- package/dist/assets/index-CevK3jug.css +0 -1
- package/dist/assets/mcp-marketplace-page-C2O8-sSE.js +0 -1
- package/dist/assets/play-DD0bg50i.js +0 -1
- package/dist/assets/plus-zZ2GC_AJ.js +0 -1
- package/dist/assets/providers-list-DpAMol1o.js +0 -1
- package/dist/assets/remote-hKy7hvBh.js +0 -1
- package/dist/assets/rotate-cw-C7EvaSlb.js +0 -1
- package/dist/assets/search-C0b2yzmt.js +0 -1
- package/dist/assets/x-BV4iiea8.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AlarmClock, FileCode2, MessageSquareText, X } from "lucide-react";
|
|
1
|
+
import { AlarmClock, ArrowLeft, FileCode2, MessageSquareText, X } from "lucide-react";
|
|
2
2
|
import type { ResolvedChildSessionTab } from "@/features/chat/hooks/use-ncp-child-session-tabs-view";
|
|
3
3
|
import type { ChatWorkspaceFileTab } from "@/features/chat/stores/chat-thread.store";
|
|
4
4
|
import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
|
|
@@ -117,10 +117,7 @@ function WorkspaceTabIcon({ agentId, kind }: Pick<WorkspaceTabViewModel, "agentI
|
|
|
117
117
|
|
|
118
118
|
if (agentId) {
|
|
119
119
|
return (
|
|
120
|
-
<AgentIdentityAvatar
|
|
121
|
-
agentId={agentId}
|
|
122
|
-
className="h-3.5 w-3.5 shrink-0"
|
|
123
|
-
/>
|
|
120
|
+
<AgentIdentityAvatar agentId={agentId} className="h-3.5 w-3.5 shrink-0" />
|
|
124
121
|
);
|
|
125
122
|
}
|
|
126
123
|
|
|
@@ -129,82 +126,107 @@ function WorkspaceTabIcon({ agentId, kind }: Pick<WorkspaceTabViewModel, "agentI
|
|
|
129
126
|
|
|
130
127
|
function WorkspaceTabItem({ tab }: { tab: WorkspaceTabViewModel }) {
|
|
131
128
|
return (
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
129
|
+
<Tooltip>
|
|
130
|
+
<TooltipTrigger asChild>
|
|
131
|
+
<div
|
|
132
|
+
className={cn(
|
|
133
|
+
"group flex max-w-[180px] min-w-0 items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
|
|
134
|
+
tab.active
|
|
135
|
+
? "border-b-primary bg-white text-gray-900"
|
|
136
|
+
: "border-b-transparent bg-gray-50/85 text-gray-500 hover:bg-gray-100",
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
{tab.onClose ? (
|
|
143
140
|
<button
|
|
144
141
|
type="button"
|
|
145
|
-
onClick={
|
|
146
|
-
|
|
142
|
+
onClick={(event) => {
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
tab.onClose?.();
|
|
145
|
+
}}
|
|
146
|
+
className="relative inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700"
|
|
147
|
+
aria-label={t("chatWorkspaceCloseFile")}
|
|
147
148
|
>
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
{tab.title}
|
|
149
|
+
<span className="flex items-center justify-center group-hover:hidden">
|
|
150
|
+
<WorkspaceTabIcon kind={tab.kind} agentId={tab.agentId} />
|
|
151
151
|
</span>
|
|
152
|
-
|
|
153
|
-
<span className="shrink-0 rounded border border-amber-200 bg-amber-50 px-1 py-0 text-[9px] font-medium uppercase tracking-[0.08em] text-amber-700">
|
|
154
|
-
{t("chatWorkspaceDiff")}
|
|
155
|
-
</span>
|
|
156
|
-
) : null}
|
|
157
|
-
{tab.showUnreadDot ? (
|
|
158
|
-
<span
|
|
159
|
-
aria-label={t("chatSessionUnread")}
|
|
160
|
-
className="h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
161
|
-
/>
|
|
162
|
-
) : null}
|
|
152
|
+
<X className="hidden h-3.5 w-3.5 group-hover:block" />
|
|
163
153
|
</button>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
>
|
|
177
|
-
|
|
178
|
-
</
|
|
154
|
+
) : (
|
|
155
|
+
<WorkspaceTabIcon kind={tab.kind} agentId={tab.agentId} />
|
|
156
|
+
)}
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={tab.onSelect}
|
|
160
|
+
className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
|
|
161
|
+
>
|
|
162
|
+
<span className="min-w-0 truncate text-[12px] font-medium">
|
|
163
|
+
{tab.title}
|
|
164
|
+
</span>
|
|
165
|
+
{tab.kind === "file" && tab.viewMode === "diff" ? (
|
|
166
|
+
<span className="shrink-0 rounded border border-amber-200 bg-amber-50 px-1 py-0 text-[9px] font-medium uppercase tracking-[0.08em] text-amber-700">
|
|
167
|
+
{t("chatWorkspaceDiff")}
|
|
168
|
+
</span>
|
|
179
169
|
) : null}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
170
|
+
{tab.showUnreadDot ? (
|
|
171
|
+
<span
|
|
172
|
+
aria-label={t("chatSessionUnread")}
|
|
173
|
+
className="h-2 w-2 shrink-0 rounded-full bg-primary"
|
|
174
|
+
/>
|
|
175
|
+
) : null}
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
</TooltipTrigger>
|
|
179
|
+
<TooltipContent side="bottom" className="max-w-[320px] text-xs">
|
|
180
|
+
{tab.tooltip}
|
|
181
|
+
</TooltipContent>
|
|
182
|
+
</Tooltip>
|
|
187
183
|
);
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
export function WorkspaceTabsBar({
|
|
187
|
+
onBack,
|
|
188
|
+
onClose,
|
|
191
189
|
tabs,
|
|
192
190
|
}: {
|
|
191
|
+
onBack?: () => void;
|
|
192
|
+
onClose: () => void;
|
|
193
193
|
tabs: readonly WorkspaceTabViewModel[];
|
|
194
194
|
}) {
|
|
195
195
|
return (
|
|
196
|
-
<
|
|
197
|
-
data-testid="workspace-tabs-bar"
|
|
198
|
-
className="workspace-horizontal-scrollbar overflow-x-auto overflow-y-hidden border-b border-gray-200/70 bg-gray-50/85"
|
|
199
|
-
>
|
|
196
|
+
<TooltipProvider delayDuration={250}>
|
|
200
197
|
<div
|
|
201
|
-
data-testid="workspace-tabs-
|
|
202
|
-
className="flex min-w-
|
|
198
|
+
data-testid="workspace-tabs-bar"
|
|
199
|
+
className="flex min-w-0 items-stretch border-b border-gray-200/70 bg-gray-50/85"
|
|
203
200
|
>
|
|
204
|
-
|
|
205
|
-
<
|
|
206
|
-
|
|
201
|
+
<div className="workspace-horizontal-scrollbar min-w-0 flex-1 overflow-x-auto overflow-y-hidden">
|
|
202
|
+
<div
|
|
203
|
+
data-testid="workspace-tabs-scroll"
|
|
204
|
+
className="flex min-w-max items-stretch"
|
|
205
|
+
>
|
|
206
|
+
{tabs.map((tab) => (
|
|
207
|
+
<WorkspaceTabItem key={tab.key} tab={tab} />
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
{onBack ? (
|
|
212
|
+
<button
|
|
213
|
+
type="button"
|
|
214
|
+
onClick={onBack}
|
|
215
|
+
className="mx-1 flex h-7 w-7 shrink-0 self-center items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
216
|
+
aria-label={t("chatBackToParent")}
|
|
217
|
+
>
|
|
218
|
+
<ArrowLeft className="h-4 w-4" />
|
|
219
|
+
</button>
|
|
220
|
+
) : null}
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
onClick={onClose}
|
|
224
|
+
className="mx-1 flex h-7 w-7 shrink-0 self-center items-center justify-center rounded-md text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
225
|
+
aria-label={t("chatWorkspaceClosePanel")}
|
|
226
|
+
>
|
|
227
|
+
<X className="h-4 w-4" />
|
|
228
|
+
</button>
|
|
207
229
|
</div>
|
|
208
|
-
</
|
|
230
|
+
</TooltipProvider>
|
|
209
231
|
);
|
|
210
232
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { FolderGit2, Loader2 } from "lucide-react";
|
|
3
3
|
import type {
|
|
4
4
|
ChatFileOpenActionViewModel,
|
|
5
5
|
ChatToolActionViewModel,
|
|
@@ -43,6 +43,7 @@ type ChatSessionWorkspacePanelProps = {
|
|
|
43
43
|
activePanelKind?: "child-session" | "file" | "cron" | null;
|
|
44
44
|
sessionCronJobs?: readonly CronJobView[];
|
|
45
45
|
sessionProjectRoot: string | null;
|
|
46
|
+
sessionWorkingDir: string | null;
|
|
46
47
|
displayMode?: "docked" | "overlay";
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -249,6 +250,7 @@ export function ChatSessionWorkspacePanel({
|
|
|
249
250
|
activePanelKind,
|
|
250
251
|
sessionCronJobs = [],
|
|
251
252
|
sessionProjectRoot,
|
|
253
|
+
sessionWorkingDir,
|
|
252
254
|
displayMode = "docked",
|
|
253
255
|
}: ChatSessionWorkspacePanelProps) {
|
|
254
256
|
const presenter = usePresenter();
|
|
@@ -269,7 +271,10 @@ export function ChatSessionWorkspacePanel({
|
|
|
269
271
|
);
|
|
270
272
|
|
|
271
273
|
useEffect(() => {
|
|
272
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
activeSelection?.kind !== "child-session" ||
|
|
276
|
+
activeSelection.tab.runStatus === "running"
|
|
277
|
+
) {
|
|
273
278
|
return;
|
|
274
279
|
}
|
|
275
280
|
const activeTabReadAt = activeSelection.tab.lastMessageAt?.trim() ?? null;
|
|
@@ -326,29 +331,15 @@ export function ChatSessionWorkspacePanel({
|
|
|
326
331
|
maxWidth={860}
|
|
327
332
|
overlay={displayMode === "overlay"}
|
|
328
333
|
>
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<ArrowLeft className="h-3.5 w-3.5" />
|
|
339
|
-
<span>{t("chatBackToParent")}</span>
|
|
340
|
-
</button>
|
|
341
|
-
<button
|
|
342
|
-
type="button"
|
|
343
|
-
onClick={presenter.chatThreadManager.closeWorkspacePanel}
|
|
344
|
-
className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
|
345
|
-
aria-label={t("chatWorkspaceClosePanel")}
|
|
346
|
-
>
|
|
347
|
-
<X className="h-4 w-4" />
|
|
348
|
-
</button>
|
|
349
|
-
</div>
|
|
350
|
-
|
|
351
|
-
<WorkspaceTabsBar tabs={workspaceTabs} />
|
|
334
|
+
<WorkspaceTabsBar
|
|
335
|
+
tabs={workspaceTabs}
|
|
336
|
+
onBack={
|
|
337
|
+
hasParentSession
|
|
338
|
+
? presenter.chatThreadManager.goToParentSession
|
|
339
|
+
: undefined
|
|
340
|
+
}
|
|
341
|
+
onClose={presenter.chatThreadManager.closeWorkspacePanel}
|
|
342
|
+
/>
|
|
352
343
|
|
|
353
344
|
<div className="flex min-h-0 flex-1 flex-col bg-white">
|
|
354
345
|
{activeSelection.kind === "child-session" ? (
|
|
@@ -368,6 +359,7 @@ export function ChatSessionWorkspacePanel({
|
|
|
368
359
|
<ChatSessionWorkspaceFilePreview
|
|
369
360
|
file={activeSelection.file}
|
|
370
361
|
sessionProjectRoot={sessionProjectRoot}
|
|
362
|
+
sessionWorkingDir={sessionWorkingDir}
|
|
371
363
|
onFileOpen={presenter.chatThreadManager.openFilePreview}
|
|
372
364
|
/>
|
|
373
365
|
) : (
|
|
@@ -552,6 +552,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
552
552
|
workspaceFileTabs={[]}
|
|
553
553
|
activeWorkspaceFileKey={null}
|
|
554
554
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
555
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
555
556
|
/>,
|
|
556
557
|
);
|
|
557
558
|
|
|
@@ -622,6 +623,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
622
623
|
workspaceFileTabs={[]}
|
|
623
624
|
activeWorkspaceFileKey={null}
|
|
624
625
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
626
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
625
627
|
/>,
|
|
626
628
|
);
|
|
627
629
|
|
|
@@ -645,6 +647,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
645
647
|
]}
|
|
646
648
|
activeWorkspaceFileKey="parent-session-1::preview::README.md"
|
|
647
649
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
650
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
648
651
|
/>,
|
|
649
652
|
);
|
|
650
653
|
|
|
@@ -653,9 +656,9 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
653
656
|
expect(screen.getByTestId("workspace-file-preview").textContent).toBe(
|
|
654
657
|
"README.md",
|
|
655
658
|
);
|
|
656
|
-
expect(
|
|
657
|
-
"workspace-
|
|
658
|
-
);
|
|
659
|
+
expect(
|
|
660
|
+
screen.getByTestId("workspace-tabs-scroll").parentElement?.className,
|
|
661
|
+
).toContain("workspace-horizontal-scrollbar");
|
|
659
662
|
});
|
|
660
663
|
|
|
661
664
|
it("renders session cron jobs in the workspace sidebar and deletes with a neutral confirmation", async () => {
|
|
@@ -691,6 +694,7 @@ describe("ChatSessionWorkspacePanel", () => {
|
|
|
691
694
|
activePanelKind="cron"
|
|
692
695
|
sessionCronJobs={[job]}
|
|
693
696
|
sessionProjectRoot="/Users/demo/project-alpha"
|
|
697
|
+
sessionWorkingDir="/Users/demo/project-alpha"
|
|
694
698
|
/>,
|
|
695
699
|
);
|
|
696
700
|
|
|
@@ -366,6 +366,7 @@ function ChatSessionWorkspacePanelContainer({
|
|
|
366
366
|
activePanelKind={snapshot.activeWorkspacePanelKind ?? null}
|
|
367
367
|
sessionCronJobs={sessionCronJobs}
|
|
368
368
|
sessionProjectRoot={snapshot.sessionProjectRoot ?? null}
|
|
369
|
+
sessionWorkingDir={snapshot.sessionWorkingDir ?? snapshot.sessionProjectRoot ?? null}
|
|
369
370
|
displayMode={layoutMode === "mobile" ? "overlay" : "docked"}
|
|
370
371
|
/>
|
|
371
372
|
);
|
|
@@ -119,6 +119,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
119
119
|
availableAgents: AgentProfileView[];
|
|
120
120
|
currentSessionDisplayName?: string;
|
|
121
121
|
effectiveSessionProjectRoot: string | null;
|
|
122
|
+
effectiveSessionWorkingDir: string | null;
|
|
122
123
|
effectiveSessionProjectName: string | null;
|
|
123
124
|
selectedSession: SessionEntryView | null;
|
|
124
125
|
threadRef: MutableRefObject<HTMLDivElement | null>;
|
|
@@ -159,6 +160,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
159
160
|
availableAgents: params.availableAgents,
|
|
160
161
|
sessionDisplayName: params.currentSessionDisplayName,
|
|
161
162
|
sessionProjectRoot: params.effectiveSessionProjectRoot,
|
|
163
|
+
sessionWorkingDir: params.effectiveSessionWorkingDir,
|
|
162
164
|
sessionProjectName: params.effectiveSessionProjectName,
|
|
163
165
|
canDeleteSession: Boolean(params.selectedSession),
|
|
164
166
|
threadRef: params.threadRef,
|
|
@@ -195,6 +195,8 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
|
|
|
195
195
|
const effectiveSessionProjectRoot = hasSessionProjectRootOverride
|
|
196
196
|
? pendingProjectRoot
|
|
197
197
|
: (selectedSession?.projectRoot ?? null);
|
|
198
|
+
const effectiveSessionWorkingDir =
|
|
199
|
+
selectedSession?.workingDir ?? effectiveSessionProjectRoot;
|
|
198
200
|
const effectiveSessionProjectName = hasSessionProjectRootOverride
|
|
199
201
|
? getSessionProjectName(effectiveSessionProjectRoot)
|
|
200
202
|
: (selectedSession?.projectName ??
|
|
@@ -224,6 +226,7 @@ function useNcpChatPageState(presenter: NcpChatPresenter) {
|
|
|
224
226
|
...baseState,
|
|
225
227
|
availableAgents,
|
|
226
228
|
effectiveSessionProjectRoot,
|
|
229
|
+
effectiveSessionWorkingDir,
|
|
227
230
|
effectiveSessionProjectName,
|
|
228
231
|
isSending: agent.isSending || currentSessionRunning,
|
|
229
232
|
isAwaitingAssistantOutput: currentSessionRunning,
|
|
@@ -438,6 +441,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
438
441
|
availableAgents: state.availableAgents,
|
|
439
442
|
currentSessionDisplayName: state.currentSessionDisplayName,
|
|
440
443
|
effectiveSessionProjectRoot: state.effectiveSessionProjectRoot,
|
|
444
|
+
effectiveSessionWorkingDir: state.effectiveSessionWorkingDir,
|
|
441
445
|
effectiveSessionProjectName: state.effectiveSessionProjectName,
|
|
442
446
|
selectedSession: state.selectedSession,
|
|
443
447
|
threadRef: state.threadRef,
|
|
@@ -47,6 +47,7 @@ export type ChatThreadSnapshot = {
|
|
|
47
47
|
availableAgents?: AgentProfileView[];
|
|
48
48
|
sessionDisplayName?: string;
|
|
49
49
|
sessionProjectRoot?: string | null;
|
|
50
|
+
sessionWorkingDir?: string | null;
|
|
50
51
|
sessionProjectName?: string | null;
|
|
51
52
|
canDeleteSession: boolean;
|
|
52
53
|
isDeletePending: boolean;
|
|
@@ -86,6 +87,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
86
87
|
availableAgents: [],
|
|
87
88
|
sessionDisplayName: undefined,
|
|
88
89
|
sessionProjectRoot: null,
|
|
90
|
+
sessionWorkingDir: null,
|
|
89
91
|
sessionProjectName: null,
|
|
90
92
|
canDeleteSession: false,
|
|
91
93
|
isDeletePending: false,
|
|
@@ -22,6 +22,7 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
22
22
|
createSummary({
|
|
23
23
|
agentId: 'engineer',
|
|
24
24
|
lastMessageAt: '2026-03-18T00:00:00.000Z',
|
|
25
|
+
workingDir: '/Users/demo/workspace/project-alpha',
|
|
25
26
|
metadata: {
|
|
26
27
|
label: 'NCP Planning Thread',
|
|
27
28
|
model: 'openai/gpt-5',
|
|
@@ -40,6 +41,7 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
40
41
|
preferredModel: 'openai/gpt-5',
|
|
41
42
|
preferredThinking: 'medium',
|
|
42
43
|
projectRoot: '/Users/demo/workspace/project-alpha',
|
|
44
|
+
workingDir: '/Users/demo/workspace/project-alpha',
|
|
43
45
|
projectName: 'project-alpha',
|
|
44
46
|
lastMessageAt: '2026-03-18T00:00:00.000Z',
|
|
45
47
|
readAt: '2026-03-17T23:59:00.000Z',
|
|
@@ -50,6 +52,25 @@ describe('adaptNcpSessionSummary', () => {
|
|
|
50
52
|
});
|
|
51
53
|
});
|
|
52
54
|
|
|
55
|
+
it('uses workingDir as the local file base even when projectRoot is absent', () => {
|
|
56
|
+
const adapted = adaptNcpSessionSummary(
|
|
57
|
+
createSummary({
|
|
58
|
+
workingDir: '/Users/demo/workspace',
|
|
59
|
+
metadata: {
|
|
60
|
+
label: 'Workspace Thread',
|
|
61
|
+
session_type: 'native',
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(adapted).toMatchObject({
|
|
67
|
+
key: 'ncp-session-1',
|
|
68
|
+
label: 'Workspace Thread',
|
|
69
|
+
workingDir: '/Users/demo/workspace',
|
|
70
|
+
});
|
|
71
|
+
expect(adapted.projectRoot).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
53
74
|
it('marks child sessions from parent_session_id metadata and keeps the request link', () => {
|
|
54
75
|
const adapted = adaptNcpSessionSummary(
|
|
55
76
|
createSummary({
|
|
@@ -120,6 +120,10 @@ function readNcpSessionProjectRoot(summary: NcpSessionSummaryView): string | nul
|
|
|
120
120
|
return normalizeSessionProjectRootValue(metadata.project_root ?? metadata.projectRoot);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
function readNcpSessionWorkingDir(summary: NcpSessionSummaryView): string | null {
|
|
124
|
+
return readOptionalString(summary.workingDir);
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
function readNcpSessionReadAt(summary: NcpSessionSummaryView): string | null {
|
|
124
128
|
const metadata = readMetadata(summary);
|
|
125
129
|
if (!metadata) {
|
|
@@ -308,6 +312,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
308
312
|
const preferredModel = readNcpSessionPreferredModel(summary);
|
|
309
313
|
const preferredThinking = readNcpSessionPreferredThinking(summary);
|
|
310
314
|
const projectRoot = readNcpSessionProjectRoot(summary);
|
|
315
|
+
const workingDir = readNcpSessionWorkingDir(summary) ?? projectRoot;
|
|
311
316
|
const readAt = readNcpSessionReadAt(summary);
|
|
312
317
|
const lastMessageAt = readOptionalString(summary.lastMessageAt);
|
|
313
318
|
const projectName = getSessionProjectName(projectRoot);
|
|
@@ -328,6 +333,7 @@ export function adaptNcpSessionSummary(summary: NcpSessionSummaryView): SessionE
|
|
|
328
333
|
...(preferredModel ? { preferredModel } : {}),
|
|
329
334
|
...(preferredThinking ? { preferredThinking } : {}),
|
|
330
335
|
...(projectRoot ? { projectRoot } : {}),
|
|
336
|
+
...(workingDir ? { workingDir } : {}),
|
|
331
337
|
...(projectName ? { projectName } : {}),
|
|
332
338
|
sessionType: readNcpSessionType(summary),
|
|
333
339
|
sessionTypeMutable: false,
|