@nextclaw/ui 0.22.5 → 0.24.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/CHANGELOG.md +25 -0
- package/dist/assets/{api-aEu9cEI8.js → api-CoqPRKmF.js} +5 -5
- package/dist/assets/{appearance-settings-page-z4DaHc_E.js → appearance-settings-page-CPoFZLw8.js} +1 -1
- package/dist/assets/{book-open-DYRapW_h.js → book-open-jcUt0rr9.js} +1 -1
- package/dist/assets/{channels-list-page-x07sGqsT.js → channels-list-page-anMJAkfK.js} +2 -2
- package/dist/assets/{config-split-page-DJDp86Ne.js → config-split-page-AcuFnbIC.js} +1 -1
- package/dist/assets/{confirm-dialog-BALTnP33.js → confirm-dialog-B-atCfC5.js} +1 -1
- package/dist/assets/{createLucideIcon-CgM0rnsa.js → createLucideIcon-Blz0FzrV.js} +1 -1
- package/dist/assets/{desktop-capabilities-page-4-v3wKLV.js → desktop-capabilities-page-8lKF_Ob-.js} +1 -1
- package/dist/assets/desktop-update-config-DABXDpbj.js +1 -0
- package/dist/assets/{dist-BjYvUhPL.js → dist-D8kcEpTf.js} +1 -1
- package/dist/assets/{dist-N9zc5bkM.js → dist-eH-_j8yU.js} +1 -1
- package/dist/assets/doc-browser-DJEMr7Zk.js +1 -0
- package/dist/assets/{doc-browser-DwRLokCI.js → doc-browser-DzXRehlW.js} +1 -1
- package/dist/assets/{doc-browser-context-BDuypObS.js → doc-browser-context-BlbUcg2J.js} +1 -1
- package/dist/assets/{extensions-DFQXmSwG.js → extensions-mquwAJ2Z.js} +1 -1
- package/dist/assets/{form-actions-CzGmrPAv.js → form-actions-CaZDGEmB.js} +1 -1
- package/dist/assets/index-DyR5iR0O.css +1 -0
- package/dist/assets/{index-zuqzAvNx.js → index-XxXj8WDS.js} +10 -10
- package/dist/assets/loader-circle-0qPOOYQ5.js +1 -0
- package/dist/assets/{mcp-marketplace-page-COUopIOc.js → mcp-marketplace-page-gFtgyueL.js} +1 -1
- package/dist/assets/mcp-marketplace-page-uanamJ6L.js +1 -0
- package/dist/assets/{model-config-page-g9_-hZqZ.js → model-config-page-hFBpeRAO.js} +1 -1
- package/dist/assets/{navigation-link-Ci4Z7Phi.js → navigation-link-Hb5Z0UxZ.js} +1 -1
- package/dist/assets/plus-CYOvNvcV.js +1 -0
- package/dist/assets/privacy-settings-page-C18zo3ox.js +1 -0
- package/dist/assets/projects-C8Gm9RS8.js +1 -0
- package/dist/assets/providers-config-page-YU0-AOXJ.js +1 -0
- package/dist/assets/react-DF-kP7iN.js +8 -0
- package/dist/assets/{refresh-cw-C-3_thgr.js → refresh-cw-ZHKfE3yx.js} +1 -1
- package/dist/assets/remote-B_utkBra.js +1 -0
- package/dist/assets/{rotate-cw-DSFTq_yu.js → rotate-cw-CLhOXGwE.js} +1 -1
- package/dist/assets/runtime-config-page-Dmy1A_0Y.js +1 -0
- package/dist/assets/{save-CpPr78_S.js → save-CWuuZs77.js} +1 -1
- package/dist/assets/{search-msqk2-rd.js → search-DcmsKAn2.js} +1 -1
- package/dist/assets/{search-config-page-BqoDbKpM.js → search-config-page-CxBacx6W.js} +1 -1
- package/dist/assets/{secrets-config-page-GsahsEVw.js → secrets-config-page-B4py8-KR.js} +2 -2
- package/dist/assets/security-config-CTP4IQWw.js +1 -0
- package/dist/assets/{select-DPKCWajZ.js → select-BEr0Iy_f.js} +1 -1
- package/dist/assets/skeleton-B2NHluWf.js +1 -0
- package/dist/assets/{tag-chip-ClBWeQ4p.js → tag-chip-ByqDkn6g.js} +1 -1
- package/dist/assets/x-CENkFyK5.js +1 -0
- package/dist/index.html +21 -21
- package/package.json +6 -6
- package/src/app/configs/__tests__/app-navigation.config.test.ts +20 -0
- package/src/app/configs/app-navigation.config.ts +10 -0
- package/src/app/index.tsx +8 -1
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +29 -0
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +4 -2
- package/src/features/chat/components/layout/__tests__/chat-page-shell.test.tsx +21 -0
- package/src/features/chat/components/layout/chat-page-shell.tsx +19 -4
- package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +1 -1
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +0 -1
- package/src/features/chat/features/conversation/utils/__tests__/session-message-composer.utils.test.ts +11 -4
- package/src/features/chat/features/conversation/utils/__tests__/session-queued-input.utils.test.ts +0 -1
- package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +10 -11
- package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +1 -0
- package/src/features/chat/features/message/hooks/use-chat-message-actions.tsx +0 -2
- package/src/features/chat/features/runtime/utils/chat-runtime.utils.ts +0 -14
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +70 -22
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +79 -32
- package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +2 -0
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +0 -6
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +2 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +1 -0
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +27 -0
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +2 -14
- package/src/features/chat/types/chat-run.types.ts +0 -1
- package/src/features/panel-apps/managers/__tests__/panel-app-bridge.manager.test.ts +51 -0
- package/src/features/panel-apps/managers/panel-app-bridge.manager.ts +37 -2
- package/src/features/projects/AGENTS.md +6 -0
- package/src/features/projects/components/project-agreement.tsx +92 -0
- package/src/features/projects/components/project-artifacts.test.tsx +77 -0
- package/src/features/projects/components/project-artifacts.tsx +188 -0
- package/src/features/projects/components/project-overview.tsx +150 -0
- package/src/features/projects/components/project-requests.tsx +57 -0
- package/src/features/projects/components/project-runs.tsx +60 -0
- package/src/features/projects/components/project-section.tsx +60 -0
- package/src/features/projects/components/project-skills.tsx +53 -0
- package/src/features/projects/components/project-work-items.test.tsx +78 -0
- package/src/features/projects/components/project-work-items.tsx +272 -0
- package/src/features/projects/hooks/use-project-observation.test.tsx +93 -0
- package/src/features/projects/hooks/use-project-observation.ts +85 -0
- package/src/features/projects/index.ts +2 -0
- package/src/features/projects/pages/project-home-page.tsx +253 -0
- package/src/features/projects/pages/projects-page.test.tsx +366 -0
- package/src/features/projects/presenters/project-home.presenter.test.ts +74 -0
- package/src/features/projects/presenters/project-home.presenter.ts +55 -0
- package/src/features/projects/utils/project-artifact-view.utils.test.ts +63 -0
- package/src/features/projects/utils/project-artifact-view.utils.ts +62 -0
- package/src/features/projects/utils/project-request-response.utils.test.ts +44 -0
- package/src/features/projects/utils/project-request-response.utils.ts +39 -0
- package/src/shared/lib/i18n/index.ts +6 -2
- package/src/shared/lib/i18n/locales/en-US/projects.json +90 -0
- package/src/shared/lib/i18n/locales/zh-CN/projects.json +90 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +6 -0
- package/src/shared/lib/ui-document-title/index.ts +1 -0
- package/dist/assets/desktop-update-config-Cl-_XHqU.js +0 -1
- package/dist/assets/doc-browser-JlXVLNRe.js +0 -1
- package/dist/assets/index-CwCTiMM9.css +0 -1
- package/dist/assets/loader-circle-D9etMT9f.js +0 -1
- package/dist/assets/mcp-marketplace-page-DxPPmYqh.js +0 -1
- package/dist/assets/plus-DKGD04AO.js +0 -1
- package/dist/assets/privacy-settings-page-pNhQQT4r.js +0 -1
- package/dist/assets/providers-config-page-DftW_6KU.js +0 -1
- package/dist/assets/react-CVFx225w.js +0 -8
- package/dist/assets/remote-DS2TkMPm.js +0 -1
- package/dist/assets/runtime-config-page-Cd9sOYvn.js +0 -1
- package/dist/assets/security-config-BnHQyrtG.js +0 -1
- package/dist/assets/skeleton-BsR5rwB7.js +0 -1
- package/dist/assets/x-Dp5GAFmP.js +0 -1
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
resolveInlineTokensForText,
|
|
15
15
|
type ChatInlineTokenSource,
|
|
16
16
|
} from '@/features/chat/features/input/utils/chat-inline-token.utils';
|
|
17
|
-
import { normalizeRequestedSkills } from '@/features/chat/features/runtime/utils/chat-runtime.utils';
|
|
18
17
|
import type { SessionConversationComposerState } from '@/features/chat/features/conversation/hooks/use-session-conversation-input-state';
|
|
19
18
|
|
|
20
19
|
export type SessionMessageComposerSnapshot = SessionConversationComposerState & {
|
|
@@ -55,15 +54,6 @@ function buildTextNodes(
|
|
|
55
54
|
return nodes;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
function readRequestedSkills(metadata: Record<string, unknown>): string[] {
|
|
59
|
-
const raw = metadata.requested_skill_refs;
|
|
60
|
-
return normalizeRequestedSkills(
|
|
61
|
-
Array.isArray(raw)
|
|
62
|
-
? raw.filter((value): value is string => typeof value === 'string')
|
|
63
|
-
: undefined,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
57
|
export function buildSessionMessageComposerSnapshot(params: {
|
|
68
58
|
readonly attachmentIdPrefix: string;
|
|
69
59
|
readonly availableSkills: readonly { ref: string; name: string }[];
|
|
@@ -107,8 +97,17 @@ export function buildSessionMessageComposerSnapshot(params: {
|
|
|
107
97
|
});
|
|
108
98
|
|
|
109
99
|
const normalizedNodes = normalizeChatComposerNodes(nodes);
|
|
110
|
-
const
|
|
100
|
+
const skillTokens = metadataTokens.filter(
|
|
101
|
+
(token): token is Extract<ChatInlineTokenSource, { kind: 'skill' }> =>
|
|
102
|
+
token.kind === 'skill',
|
|
103
|
+
);
|
|
104
|
+
const selectedSkills = [...new Set(skillTokens.map((token) => token.ref))];
|
|
111
105
|
const skillNameByRef = new Map(availableSkills.map(({ ref, name }) => [ref, name]));
|
|
106
|
+
for (const token of skillTokens) {
|
|
107
|
+
if (!skillNameByRef.has(token.ref)) {
|
|
108
|
+
skillNameByRef.set(token.ref, token.name);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
112
111
|
return {
|
|
113
112
|
text: deriveChatComposerDraft(normalizedNodes),
|
|
114
113
|
nodes: normalizedNodes,
|
|
@@ -41,7 +41,6 @@ function buildEditedMessageMetadata(
|
|
|
41
41
|
snapshot: SessionMessageComposerSnapshot,
|
|
42
42
|
): Record<string, unknown> | undefined {
|
|
43
43
|
const metadata = { ...message.metadata };
|
|
44
|
-
delete metadata.requested_skill_refs;
|
|
45
44
|
delete metadata[CHAT_INLINE_TOKENS_METADATA_KEY];
|
|
46
45
|
const skillRecords = readInlineTokensFromMetadata(message.metadata).flatMap(
|
|
47
46
|
(token) =>
|
|
@@ -59,7 +58,6 @@ function buildEditedMessageMetadata(
|
|
|
59
58
|
);
|
|
60
59
|
Object.assign(metadata, buildChatRunMetadata({
|
|
61
60
|
composerNodes: [...snapshot.nodes],
|
|
62
|
-
requestedSkills: [...snapshot.selectedSkills],
|
|
63
61
|
skillRecords,
|
|
64
62
|
}));
|
|
65
63
|
return Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
@@ -216,20 +216,6 @@ class HistoryMessageBuilder {
|
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
export function normalizeRequestedSkills(value: string[] | undefined): string[] {
|
|
220
|
-
if (!Array.isArray(value)) {
|
|
221
|
-
return [];
|
|
222
|
-
}
|
|
223
|
-
const deduped = new Set<string>();
|
|
224
|
-
for (const item of value) {
|
|
225
|
-
const trimmed = item.trim();
|
|
226
|
-
if (trimmed) {
|
|
227
|
-
deduped.add(trimmed);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return [...deduped];
|
|
231
|
-
}
|
|
232
|
-
|
|
233
219
|
export function buildUiMessagesFromHistoryMessages(messages: SessionMessageView[]): UIMessage[] {
|
|
234
220
|
return new HistoryMessageBuilder().build(messages);
|
|
235
221
|
}
|
package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
2
2
|
import userEvent from "@testing-library/user-event";
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
-
import {
|
|
4
|
+
import { MemoryRouter, useLocation } from "react-router-dom";
|
|
5
|
+
import {
|
|
6
|
+
ChatSidebarProjectGroups,
|
|
7
|
+
type ChatSidebarProjectGroup,
|
|
8
|
+
} from "@/features/chat/features/session/components/chat-sidebar-project-groups";
|
|
5
9
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
6
10
|
|
|
7
11
|
const mocks = vi.hoisted(() => ({
|
|
@@ -16,7 +20,8 @@ vi.mock("@/features/chat/components/providers/chat-presenter.provider", () => ({
|
|
|
16
20
|
}),
|
|
17
21
|
}));
|
|
18
22
|
|
|
19
|
-
const projectGroup = {
|
|
23
|
+
const projectGroup: ChatSidebarProjectGroup = {
|
|
24
|
+
projectId: "project-analysis",
|
|
20
25
|
projectRoot: "/tmp/analysis-project",
|
|
21
26
|
projectName: "analysis-project",
|
|
22
27
|
items: [
|
|
@@ -35,17 +40,30 @@ const projectGroup = {
|
|
|
35
40
|
isPinned: false,
|
|
36
41
|
};
|
|
37
42
|
|
|
38
|
-
function
|
|
43
|
+
function LocationProbe() {
|
|
44
|
+
const location = useLocation();
|
|
45
|
+
return (
|
|
46
|
+
<output data-testid="location">{`${location.pathname}${location.search}`}</output>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderProjectGroups(
|
|
51
|
+
isPinned = false,
|
|
52
|
+
group: ChatSidebarProjectGroup = projectGroup,
|
|
53
|
+
) {
|
|
39
54
|
return render(
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
<MemoryRouter initialEntries={["/chat"]}>
|
|
56
|
+
<ChatSidebarProjectGroups
|
|
57
|
+
groups={[{ ...group, isPinned }]}
|
|
58
|
+
projectCronJobCountByRoot={new Map([[projectGroup.projectRoot, 2]])}
|
|
59
|
+
defaultSessionType="native"
|
|
60
|
+
sessionTypeOptions={[
|
|
61
|
+
{ value: "native", label: "Native", icon: null, ready: true },
|
|
62
|
+
]}
|
|
63
|
+
renderSessionItem={() => <div>Project session</div>}
|
|
64
|
+
/>
|
|
65
|
+
<LocationProbe />
|
|
66
|
+
</MemoryRouter>,
|
|
49
67
|
);
|
|
50
68
|
}
|
|
51
69
|
|
|
@@ -72,18 +90,23 @@ describe("ChatSidebarProjectGroups", () => {
|
|
|
72
90
|
const { container } = renderProjectGroups();
|
|
73
91
|
|
|
74
92
|
const header = screen.getByLabelText("Collapse project");
|
|
93
|
+
const projectLink = screen.getByRole("link", { name: "analysis-project" });
|
|
94
|
+
const projectRow = projectLink.parentElement?.parentElement;
|
|
75
95
|
expect(container.firstElementChild?.className).toBe("space-y-0.5");
|
|
76
|
-
expect(
|
|
77
|
-
expect(
|
|
78
|
-
|
|
79
|
-
expect(
|
|
80
|
-
|
|
81
|
-
).
|
|
96
|
+
expect(projectRow?.className).toContain("h-8");
|
|
97
|
+
expect(projectRow?.className).toContain("hover:bg-gray-200/60");
|
|
98
|
+
const projectContent = projectLink.parentElement;
|
|
99
|
+
expect(projectContent?.classList.contains("pr-14")).toBe(false);
|
|
100
|
+
expect(projectContent?.className).toContain("group-hover/project:pr-14");
|
|
101
|
+
expect(projectContent?.className).toContain(
|
|
102
|
+
"group-has-[[data-project-actions]:focus-within]/project:pr-14",
|
|
103
|
+
);
|
|
104
|
+
expect(projectLink.querySelector("span")?.className).not.toContain("uppercase");
|
|
105
|
+
expect(projectLink.querySelector("span")?.className).toContain("text-[13px]");
|
|
82
106
|
expect(
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
).toContain("opacity-0");
|
|
107
|
+
header.compareDocumentPosition(projectLink) &
|
|
108
|
+
Node.DOCUMENT_POSITION_FOLLOWING,
|
|
109
|
+
).toBeTruthy();
|
|
87
110
|
expect(screen.queryByText("Sessions")).toBeNull();
|
|
88
111
|
|
|
89
112
|
fireEvent.click(header);
|
|
@@ -97,6 +120,31 @@ describe("ChatSidebarProjectGroups", () => {
|
|
|
97
120
|
expect(screen.queryByText("Project session")).toBeNull();
|
|
98
121
|
});
|
|
99
122
|
|
|
123
|
+
it("opens the project home from the project name without changing expansion", () => {
|
|
124
|
+
renderProjectGroups();
|
|
125
|
+
|
|
126
|
+
fireEvent.click(screen.getByRole("link", { name: "analysis-project" }));
|
|
127
|
+
|
|
128
|
+
expect(mocks.toggleProjectCollapsed).not.toHaveBeenCalled();
|
|
129
|
+
expect(screen.getByTestId("location").textContent).toBe(
|
|
130
|
+
"/projects/project-analysis/overview",
|
|
131
|
+
);
|
|
132
|
+
expect(screen.getByText("Project session")).toBeTruthy();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("surfaces a running child session while its project is collapsed", () => {
|
|
136
|
+
renderProjectGroups(false, {
|
|
137
|
+
...projectGroup,
|
|
138
|
+
items: [{ ...projectGroup.items[0], runStatus: "running" as const }],
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(screen.queryByLabelText("Running")).toBeNull();
|
|
142
|
+
|
|
143
|
+
fireEvent.click(screen.getByLabelText("Collapse project"));
|
|
144
|
+
|
|
145
|
+
expect(screen.getByLabelText("Running")).toBeTruthy();
|
|
146
|
+
});
|
|
147
|
+
|
|
100
148
|
it("shows project context from the row on hover", async () => {
|
|
101
149
|
const user = userEvent.setup();
|
|
102
150
|
renderProjectGroups();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useMemo, useState, type ReactNode } from "react";
|
|
2
|
+
import { Link, useLocation } from "react-router-dom";
|
|
2
3
|
import {
|
|
3
4
|
AlarmClock,
|
|
4
5
|
ChevronDown,
|
|
@@ -19,6 +20,8 @@ import type { ChatSidebarProjectGroup } from "@/features/chat/features/session/u
|
|
|
19
20
|
import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
|
|
20
21
|
import { t } from "@/shared/lib/i18n";
|
|
21
22
|
import { ChatSidebarContextCard } from "@/features/chat/features/session/components/chat-sidebar-context-card";
|
|
23
|
+
import { SessionRunBadge } from "@/features/chat/features/session/components/session-run-badge";
|
|
24
|
+
import { cn } from "@/shared/lib/utils";
|
|
22
25
|
|
|
23
26
|
export type { ChatSidebarProjectGroup };
|
|
24
27
|
|
|
@@ -53,6 +56,7 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
53
56
|
projectCronJobCountByRoot,
|
|
54
57
|
} = props;
|
|
55
58
|
const presenter = usePresenter();
|
|
59
|
+
const location = useLocation();
|
|
56
60
|
const collapsedProjectRoots = useChatSessionListStore(
|
|
57
61
|
(state) => state.snapshot.collapsedProjectRoots,
|
|
58
62
|
);
|
|
@@ -72,13 +76,23 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
72
76
|
{groups.map((group) => {
|
|
73
77
|
const actionLabel = `${t("chatSidebarNewTask")} · ${group.projectName}`;
|
|
74
78
|
const isCollapsed = collapsedProjectRoots.includes(group.projectRoot);
|
|
79
|
+
const hasRunningSession = isCollapsed && group.items.some(
|
|
80
|
+
(item) => item.runStatus === "running",
|
|
81
|
+
);
|
|
82
|
+
const isSelected = Boolean(group.projectId) &&
|
|
83
|
+
location.pathname.startsWith(`/projects/${group.projectId}/`);
|
|
75
84
|
const pinLabel = t(
|
|
76
85
|
group.isPinned ? "chatSidebarUnpinProject" : "chatSidebarPinProject",
|
|
77
86
|
);
|
|
78
87
|
|
|
79
88
|
return (
|
|
80
89
|
<div key={group.projectRoot}>
|
|
81
|
-
<div
|
|
90
|
+
<div
|
|
91
|
+
className={cn(
|
|
92
|
+
"group/project relative h-8 rounded-lg px-1 text-muted-foreground transition-colors hover:bg-gray-200/60 hover:text-gray-900",
|
|
93
|
+
isSelected && "bg-gray-200/60 text-gray-900",
|
|
94
|
+
)}
|
|
95
|
+
>
|
|
82
96
|
<ChatSidebarContextCard
|
|
83
97
|
title={group.projectName}
|
|
84
98
|
metrics={[
|
|
@@ -100,42 +114,75 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
100
114
|
},
|
|
101
115
|
]}
|
|
102
116
|
>
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
isCollapsed
|
|
108
|
-
? "chatSidebarExpandProject"
|
|
109
|
-
: "chatSidebarCollapseProject",
|
|
117
|
+
<div
|
|
118
|
+
className={cn(
|
|
119
|
+
"flex h-full w-full min-w-0 items-center group-hover/project:pr-14 group-has-[[data-project-actions]:focus-within]/project:pr-14",
|
|
120
|
+
hasRunningSession && "pr-7",
|
|
110
121
|
)}
|
|
111
|
-
className="flex h-full w-full min-w-0 items-center gap-1.5 pr-14 text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
112
|
-
onClick={() =>
|
|
113
|
-
presenter.chatSessionListManager.toggleProjectCollapsed(
|
|
114
|
-
group.projectRoot,
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
122
|
>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
<IconActionButton
|
|
124
|
+
icon={
|
|
125
|
+
isCollapsed ? (
|
|
126
|
+
<ChevronRight className="h-3.5 w-3.5" />
|
|
127
|
+
) : (
|
|
128
|
+
<ChevronDown className="h-3.5 w-3.5" />
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
label={t(
|
|
132
|
+
isCollapsed
|
|
133
|
+
? "chatSidebarExpandProject"
|
|
134
|
+
: "chatSidebarCollapseProject",
|
|
135
|
+
)}
|
|
136
|
+
aria-expanded={!isCollapsed}
|
|
137
|
+
size="sm"
|
|
138
|
+
tone="surface"
|
|
139
|
+
tooltip={false}
|
|
140
|
+
onClick={() =>
|
|
141
|
+
presenter.chatSessionListManager.toggleProjectCollapsed(
|
|
142
|
+
group.projectRoot,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
/>
|
|
146
|
+
{group.projectId ? (
|
|
147
|
+
<Link
|
|
148
|
+
to={`/projects/${encodeURIComponent(group.projectId)}/overview`}
|
|
149
|
+
className="flex h-full min-w-0 flex-1 items-center gap-1.5 rounded-md px-1 text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
150
|
+
aria-current={isSelected ? "page" : undefined}
|
|
151
|
+
title={group.projectRoot}
|
|
152
|
+
>
|
|
153
|
+
<Folder
|
|
154
|
+
className="h-3.5 w-3.5 shrink-0"
|
|
155
|
+
aria-hidden="true"
|
|
156
|
+
/>
|
|
157
|
+
<span className="truncate text-[13px] font-medium">
|
|
158
|
+
{group.projectName}
|
|
159
|
+
</span>
|
|
160
|
+
</Link>
|
|
130
161
|
) : (
|
|
131
|
-
<
|
|
132
|
-
className="h-
|
|
133
|
-
|
|
134
|
-
|
|
162
|
+
<span
|
|
163
|
+
className="flex h-full min-w-0 flex-1 items-center gap-1.5 rounded-md px-1 text-left"
|
|
164
|
+
title={group.projectRoot}
|
|
165
|
+
>
|
|
166
|
+
<Folder
|
|
167
|
+
className="h-3.5 w-3.5 shrink-0"
|
|
168
|
+
aria-hidden="true"
|
|
169
|
+
/>
|
|
170
|
+
<span className="truncate text-[13px] font-medium">
|
|
171
|
+
{group.projectName}
|
|
172
|
+
</span>
|
|
173
|
+
</span>
|
|
135
174
|
)}
|
|
136
|
-
</
|
|
175
|
+
</div>
|
|
137
176
|
</ChatSidebarContextCard>
|
|
138
|
-
|
|
177
|
+
{hasRunningSession ? (
|
|
178
|
+
<span className="absolute right-2 top-1/2 inline-flex -translate-y-1/2 transition-opacity group-hover/project:opacity-0 group-has-[[data-project-actions]:focus-within]/project:opacity-0">
|
|
179
|
+
<SessionRunBadge status="running" />
|
|
180
|
+
</span>
|
|
181
|
+
) : null}
|
|
182
|
+
<div
|
|
183
|
+
data-project-actions
|
|
184
|
+
className="pointer-events-none absolute right-2 top-1/2 flex -translate-y-1/2 items-center opacity-0 transition-opacity group-hover/project:pointer-events-auto group-hover/project:opacity-100 focus-within:pointer-events-auto focus-within:opacity-100"
|
|
185
|
+
>
|
|
139
186
|
{supportsSessionTypeChoice ? (
|
|
140
187
|
<Popover
|
|
141
188
|
open={openProjectRoot === group.projectRoot}
|
package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { groupSessionsByProject } from "@/features/chat/features/session/utils/c
|
|
|
4
4
|
describe("groupSessionsByProject", () => {
|
|
5
5
|
it("uses registered projects as the project-first baseline", () => {
|
|
6
6
|
const groups = groupSessionsByProject([], new Set(), new Set(), [{
|
|
7
|
+
id: "project-knowledge",
|
|
7
8
|
name: "Knowledge",
|
|
8
9
|
rootPath: "/tmp/knowledge",
|
|
9
10
|
template: "knowledge-base",
|
|
@@ -12,6 +13,7 @@ describe("groupSessionsByProject", () => {
|
|
|
12
13
|
}]);
|
|
13
14
|
|
|
14
15
|
expect(groups).toEqual([{
|
|
16
|
+
projectId: "project-knowledge",
|
|
15
17
|
projectRoot: "/tmp/knowledge",
|
|
16
18
|
projectName: "Knowledge",
|
|
17
19
|
items: [],
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
createInlineTokensMetadata,
|
|
9
9
|
type ChatSkillReferenceSnapshot,
|
|
10
10
|
} from "@/features/chat/features/input/utils/chat-inline-token.utils";
|
|
11
|
-
import { normalizeRequestedSkills } from "@/features/chat/features/runtime/utils/chat-runtime.utils";
|
|
12
11
|
import { normalizeSessionProjectRootValue } from "@/shared/lib/session-project";
|
|
13
12
|
|
|
14
13
|
function createMetadataFields(
|
|
@@ -24,7 +23,6 @@ export function buildChatRunMetadata(payload: {
|
|
|
24
23
|
thinkingLevel?: string;
|
|
25
24
|
sessionType?: string;
|
|
26
25
|
projectRoot?: string | null;
|
|
27
|
-
requestedSkills?: string[];
|
|
28
26
|
skillRecords?: readonly ChatSkillReferenceSnapshot[];
|
|
29
27
|
composerNodes?: Parameters<typeof buildInlineTokensFromComposer>[0];
|
|
30
28
|
sessionMaterialization?: AgentRunSessionMaterializationMetadata | null;
|
|
@@ -37,10 +35,6 @@ export function buildChatRunMetadata(payload: {
|
|
|
37
35
|
...createMetadataFields(payload.agentId?.trim(), ["agentId", "agent_id"]),
|
|
38
36
|
...createMetadataFields(projectRoot ?? undefined, ["projectRoot", "project_root"]),
|
|
39
37
|
};
|
|
40
|
-
const requestedSkills = normalizeRequestedSkills(payload.requestedSkills);
|
|
41
|
-
if (requestedSkills.length > 0) {
|
|
42
|
-
metadata.requested_skill_refs = requestedSkills;
|
|
43
|
-
}
|
|
44
38
|
const inlineTokens = payload.composerNodes
|
|
45
39
|
? buildInlineTokensFromComposer(payload.composerNodes, payload.skillRecords)
|
|
46
40
|
: [];
|
|
@@ -9,6 +9,7 @@ export type ChatSidebarDateGroup = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export type ChatSidebarProjectGroup = {
|
|
12
|
+
projectId?: string;
|
|
12
13
|
projectRoot: string;
|
|
13
14
|
projectName: string;
|
|
14
15
|
items: NcpSessionListItemView[];
|
|
@@ -102,6 +103,7 @@ export function groupSessionsByProject(
|
|
|
102
103
|
|
|
103
104
|
for (const project of projects) {
|
|
104
105
|
grouped.set(project.rootPath, {
|
|
106
|
+
projectId: project.id,
|
|
105
107
|
projectRoot: project.rootPath,
|
|
106
108
|
projectName: project.name,
|
|
107
109
|
items: [],
|
|
@@ -60,6 +60,7 @@ describe("buildChatWelcomeProjectOptions", () => {
|
|
|
60
60
|
const options = buildChatWelcomeProjectOptions({
|
|
61
61
|
defaultProjectRoot: "/Users/demo/.nextclaw/workspace",
|
|
62
62
|
projects: [{
|
|
63
|
+
id: "project-knowledge",
|
|
63
64
|
name: "Knowledge",
|
|
64
65
|
rootPath: "/tmp/knowledge",
|
|
65
66
|
template: "knowledge-base",
|
|
@@ -230,6 +230,33 @@ describe("ChatSessionListManager draft and selection flow", () => {
|
|
|
230
230
|
});
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
+
it("keeps a project-bound initial prompt as an editable unsent draft", () => {
|
|
234
|
+
const uiManager = {
|
|
235
|
+
goToChatRoot: vi.fn(),
|
|
236
|
+
navigateTo: vi.fn(),
|
|
237
|
+
goToSession: vi.fn(),
|
|
238
|
+
isAtChatRoot: vi.fn(() => true),
|
|
239
|
+
} as unknown as ConstructorParameters<typeof ChatSessionListManager>[0];
|
|
240
|
+
|
|
241
|
+
const manager = new ChatSessionListManager(uiManager);
|
|
242
|
+
manager.createSession({
|
|
243
|
+
projectRoot: "/tmp/project-alpha",
|
|
244
|
+
prompt: "Use the project observation capability.",
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(uiManager.navigateTo).toHaveBeenCalledWith("/chat/draft", {
|
|
248
|
+
replace: true,
|
|
249
|
+
state: {
|
|
250
|
+
chatDraft: {
|
|
251
|
+
sessionType: "native",
|
|
252
|
+
projectRoot: "/tmp/project-alpha",
|
|
253
|
+
prompt: "Use the project observation capability.",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
expect(useChatThreadStore.getState().snapshot.hasSubmittedDraftMessage).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
|
|
233
260
|
it("does not eagerly replace the old selected session before the route finishes switching", () => {
|
|
234
261
|
const uiManager = {
|
|
235
262
|
goToChatRoot: vi.fn(),
|
|
@@ -180,19 +180,8 @@ describe('buildChatRunMetadata', () => {
|
|
|
180
180
|
).not.toHaveProperty('project_root');
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
-
it('
|
|
184
|
-
expect(
|
|
185
|
-
buildChatRunMetadata({
|
|
186
|
-
requestedSkills: ['project:/tmp/project-alpha/.agents/skills/review'],
|
|
187
|
-
}),
|
|
188
|
-
).toMatchObject({
|
|
189
|
-
requested_skill_refs: ['project:/tmp/project-alpha/.agents/skills/review'],
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('snapshots explicit skill references while keeping internal refs out of message text', () => {
|
|
183
|
+
it('snapshots skill token presentation without creating runtime selection metadata', () => {
|
|
194
184
|
expect(buildChatRunMetadata({
|
|
195
|
-
requestedSkills: ['workspace:/tmp/workspace/skills/review'],
|
|
196
185
|
skillRecords: [{
|
|
197
186
|
ref: 'workspace:/tmp/workspace/skills/review',
|
|
198
187
|
name: 'review',
|
|
@@ -204,8 +193,7 @@ describe('buildChatRunMetadata', () => {
|
|
|
204
193
|
tokenKey: 'workspace:/tmp/workspace/skills/review',
|
|
205
194
|
label: 'review',
|
|
206
195
|
})],
|
|
207
|
-
})).
|
|
208
|
-
requested_skill_refs: ['workspace:/tmp/workspace/skills/review'],
|
|
196
|
+
})).toEqual({
|
|
209
197
|
ui_inline_tokens: {
|
|
210
198
|
schemaVersion: 2,
|
|
211
199
|
items: [{
|
|
@@ -11,6 +11,9 @@ const mocks = vi.hoisted(() => ({
|
|
|
11
11
|
grantServiceActions: vi.fn(),
|
|
12
12
|
invokeServiceAction: vi.fn(),
|
|
13
13
|
listServiceActions: vi.fn(),
|
|
14
|
+
listVerificationRecords: vi.fn(),
|
|
15
|
+
getPortableRuntimeAcceptanceStatus: vi.fn(),
|
|
16
|
+
exportPortableRuntimeAcceptance: vi.fn(),
|
|
14
17
|
sendAgentMessage: vi.fn(),
|
|
15
18
|
requestAuthorization: vi.fn(),
|
|
16
19
|
}));
|
|
@@ -27,6 +30,9 @@ vi.mock('@/shared/lib/api', () => ({
|
|
|
27
30
|
grantServiceActions: mocks.grantServiceActions,
|
|
28
31
|
invokeServiceAction: mocks.invokeServiceAction,
|
|
29
32
|
listServiceActions: mocks.listServiceActions,
|
|
33
|
+
listVerificationRecords: mocks.listVerificationRecords,
|
|
34
|
+
getPortableRuntimeAcceptanceStatus: mocks.getPortableRuntimeAcceptanceStatus,
|
|
35
|
+
exportPortableRuntimeAcceptance: mocks.exportPortableRuntimeAcceptance,
|
|
30
36
|
revokeServiceAction: vi.fn(),
|
|
31
37
|
},
|
|
32
38
|
},
|
|
@@ -231,4 +237,49 @@ describe('PanelAppBridgeManager', () => {
|
|
|
231
237
|
bridgeSessionToken: 'token-3',
|
|
232
238
|
});
|
|
233
239
|
});
|
|
240
|
+
|
|
241
|
+
it('projects sandbox-safe verification and acceptance reads through the host bridge', async () => {
|
|
242
|
+
const manager = createManager();
|
|
243
|
+
const { postMessage, send } = createIframeHarness(manager);
|
|
244
|
+
mocks.listVerificationRecords.mockResolvedValue({ entries: [{ acceptanceId: 'PRT-ENTRY-001' }] });
|
|
245
|
+
mocks.getPortableRuntimeAcceptanceStatus.mockResolvedValue({ schemaVersion: 1, entries: [] });
|
|
246
|
+
mocks.exportPortableRuntimeAcceptance.mockResolvedValue({ schemaVersion: 1, entries: [] });
|
|
247
|
+
|
|
248
|
+
send({
|
|
249
|
+
method: 'verification.list',
|
|
250
|
+
payload: { appId: 'nextclaw.portable-runtime-lab', limit: 500 },
|
|
251
|
+
requestId: 'verification-1',
|
|
252
|
+
type: 'nextclaw:panel-app-service-actions:request',
|
|
253
|
+
});
|
|
254
|
+
send({
|
|
255
|
+
method: 'acceptance.status',
|
|
256
|
+
payload: { locale: 'zh-CN' },
|
|
257
|
+
requestId: 'acceptance-1',
|
|
258
|
+
type: 'nextclaw:panel-app-service-actions:request',
|
|
259
|
+
});
|
|
260
|
+
send({
|
|
261
|
+
method: 'acceptance.export',
|
|
262
|
+
payload: { locale: 'zh-CN' },
|
|
263
|
+
requestId: 'acceptance-2',
|
|
264
|
+
type: 'nextclaw:panel-app-service-actions:request',
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await waitFor(() => expect(postMessage).toHaveBeenCalledTimes(3));
|
|
268
|
+
expect(mocks.listVerificationRecords).toHaveBeenCalledWith({
|
|
269
|
+
acceptanceId: undefined,
|
|
270
|
+
appId: 'nextclaw.portable-runtime-lab',
|
|
271
|
+
bridgeSessionToken: 'token-1',
|
|
272
|
+
limit: 500,
|
|
273
|
+
});
|
|
274
|
+
expect(mocks.getPortableRuntimeAcceptanceStatus).toHaveBeenCalledWith({
|
|
275
|
+
appId: undefined,
|
|
276
|
+
bridgeSessionToken: 'token-1',
|
|
277
|
+
locale: 'zh-CN',
|
|
278
|
+
});
|
|
279
|
+
expect(mocks.exportPortableRuntimeAcceptance).toHaveBeenCalledWith({
|
|
280
|
+
appId: undefined,
|
|
281
|
+
bridgeSessionToken: 'token-1',
|
|
282
|
+
locale: 'zh-CN',
|
|
283
|
+
});
|
|
284
|
+
});
|
|
234
285
|
});
|