@nextclaw/ui 0.27.4 → 0.27.5-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 +7 -0
- package/dist/.vite/manifest.json +45 -45
- package/dist/assets/{account-panel-Cfx3ocPY.js → account-panel-DgqicWMm.js} +1 -1
- package/dist/assets/{agents-BdeFtIOV.js → agents-CTSR4ZKb.js} +1 -1
- package/dist/assets/agents-i7167DcF.js +1 -0
- package/dist/assets/{app-presenter-provider-BB3Xl8b0.js → app-presenter-provider-DdD2Q7jZ.js} +17 -16
- package/dist/assets/{appearance-settings-page-DiSRSDN3.js → appearance-settings-page-CSWYLrsR.js} +1 -1
- package/dist/assets/apps-DUP3aPpu.js +1 -0
- package/dist/assets/cron-DPBUHn7j.js +1 -0
- package/dist/assets/doc-browser-C7kvXGc_.js +1 -0
- package/dist/assets/inbox-C09MQO9k.js +1 -0
- package/dist/assets/{keyboard-shortcuts-settings-page-BFBXVf20.js → keyboard-shortcuts-settings-page-BCxB9m69.js} +1 -1
- package/dist/assets/{main-Bp1iozNB.js → main-C6VGoYNO.js} +2 -2
- package/dist/assets/{marketplace-BTDWnHP7.js → marketplace-CXCLbyho.js} +1 -1
- package/dist/assets/mcp-marketplace-page-ZAm380SB.js +1 -0
- package/dist/assets/panel-apps-BQlyeAId.js +1 -0
- package/dist/assets/{projects-fPTOn2Wz.js → projects-BWM-oEo0.js} +1 -1
- package/dist/assets/remote-DngM8iep.js +1 -0
- package/dist/assets/{runtime-config-page-P5RUq3YK.js → runtime-config-page-Dx6lWn3_.js} +1 -1
- package/dist/assets/{service-apps-panel-BReYr_ni.js → service-apps-panel-9khaUSFI.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +7 -7
- package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +16 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +17 -0
- package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +3 -3
- package/src/features/chat/features/message/utils/chat-message.utils.ts +11 -4
- package/src/features/chat/features/message/utils/chat-user-question-timeline.utils.ts +42 -0
- package/dist/assets/agents-p_wcGhC3.js +0 -1
- package/dist/assets/apps-CKFrWb00.js +0 -1
- package/dist/assets/cron-UqsFT41c.js +0 -1
- package/dist/assets/doc-browser-2VjSkax1.js +0 -1
- package/dist/assets/inbox-D62apR2j.js +0 -1
- package/dist/assets/mcp-marketplace-page-oHCbBkS-.js +0 -1
- package/dist/assets/panel-apps-CniMi7QW.js +0 -1
- package/dist/assets/remote-CsJMF8OR.js +0 -1
package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts
CHANGED
|
@@ -20,6 +20,22 @@ const visibleMessage = {
|
|
|
20
20
|
} satisfies NcpMessage;
|
|
21
21
|
|
|
22
22
|
describe("chat message timeline visibility", () => {
|
|
23
|
+
it("places an async question at its tool call between assistant text parts", () => {
|
|
24
|
+
const source = { ...visibleMessage, parts: [
|
|
25
|
+
{ type: "text" as const, text: "先说明。" },
|
|
26
|
+
{ type: "tool-invocation" as const, toolCallId: "call-1", toolName: "request_user_input_async", state: "result" as const,
|
|
27
|
+
args: "{}", result: { accepted: true, questionIds: ["q-1"] } },
|
|
28
|
+
{ type: "text" as const, text: "稍后继续。" },
|
|
29
|
+
] } satisfies NcpMessage;
|
|
30
|
+
const question = { ...visibleMessage, id: "assistant-question-1", parts: [
|
|
31
|
+
{ type: "text" as const, text: "城市? (北京 / 上海)" },
|
|
32
|
+
{ type: "extension" as const, extensionType: "nextclaw.user-question", data: { questions: [{ id: "q-1", title: "城市?", options: ["北京", "上海"] }] } },
|
|
33
|
+
] } satisfies NcpMessage;
|
|
34
|
+
const projected = projectVisibleChatMessages([source, question]);
|
|
35
|
+
expect(projected).toHaveLength(1);
|
|
36
|
+
expect(projected[0]?.parts.map((part) => part.type)).toEqual(["text", "extension", "text"]);
|
|
37
|
+
expect(projected[0]?.parts[1]).toMatchObject({ data: { questions: [{ id: "q-1" }] } });
|
|
38
|
+
});
|
|
23
39
|
it("places context compaction between surrounding visible messages", () => {
|
|
24
40
|
const afterMessage = {
|
|
25
41
|
...visibleMessage,
|
|
@@ -118,6 +118,23 @@ it("maps context compaction extensions into stable process parts", () => {
|
|
|
118
118
|
}]);
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
it("keeps assistant prose on both sides of an unanchored question fallback", () => {
|
|
122
|
+
const data = { questions: [{ id: "question-1", title: "Which format?", options: ["PDF", "DOCX"] }] };
|
|
123
|
+
const adapted = adapt([{
|
|
124
|
+
id: "assistant-question-older",
|
|
125
|
+
role: "assistant",
|
|
126
|
+
parts: [
|
|
127
|
+
{ type: "text", text: "Before." },
|
|
128
|
+
{ type: "text", text: "Which format? (PDF / DOCX)" },
|
|
129
|
+
{ type: "extension", extensionType: "nextclaw.user-question", data },
|
|
130
|
+
{ type: "text", text: "After." },
|
|
131
|
+
],
|
|
132
|
+
}]);
|
|
133
|
+
expect(adapted[0]?.parts.map((part) => part.type)).toEqual(["markdown", "custom", "markdown"]);
|
|
134
|
+
expect(adapted[0]?.parts[0]).toMatchObject({ text: "Before." });
|
|
135
|
+
expect(adapted[0]?.parts[2]).toMatchObject({ text: "After." });
|
|
136
|
+
});
|
|
137
|
+
|
|
121
138
|
it("renders a structured user question at its message position without repeating fallback text", () => {
|
|
122
139
|
const data = { questions: [{ id: "question-1", title: "Which format?", options: ["PDF", "DOCX"] }] };
|
|
123
140
|
const adapted = adapt([{
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
readObservationEventPartData,
|
|
14
14
|
type ObservationEventPartData,
|
|
15
15
|
} from "@/features/chat/features/message/utils/chat-message-observation-event.utils";
|
|
16
|
+
import { placeUserQuestionsInAssistantBody } from "@/features/chat/features/message/utils/chat-user-question-timeline.utils";
|
|
16
17
|
|
|
17
18
|
const INHERITED_FROM_SESSION_METADATA_KEY = "inherited_from_session_id";
|
|
18
|
-
export const CONTEXT_COMPACTION_PART_EXTENSION_TYPE =
|
|
19
|
-
"nextclaw.context-compaction";
|
|
19
|
+
export const CONTEXT_COMPACTION_PART_EXTENSION_TYPE = "nextclaw.context-compaction";
|
|
20
20
|
|
|
21
21
|
export type ContextCompactionPartData = {
|
|
22
22
|
id: string;
|
|
@@ -280,7 +280,7 @@ function projectVisibleChatMessageState(
|
|
|
280
280
|
status: "pending",
|
|
281
281
|
};
|
|
282
282
|
}
|
|
283
|
-
return { messages,
|
|
283
|
+
return { ...placeUserQuestionsInAssistantBody(messages, observationEvents), inlineCompactionMessageIds };
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
export function projectVisibleChatMessages(
|
|
@@ -122,16 +122,23 @@ export function adaptChatMessage(
|
|
|
122
122
|
texts: params.texts,
|
|
123
123
|
}))
|
|
124
124
|
.filter((part) => part !== null);
|
|
125
|
-
const hasQuestionPart = parts.some(
|
|
126
|
-
(part) => part.type === "custom" && part.customType === "nextclaw.user-question",
|
|
127
|
-
);
|
|
128
125
|
const reply = message.meta?.userQuestionReply;
|
|
129
126
|
const displayParts: ChatMessageViewModel["parts"] = reply
|
|
130
127
|
? [
|
|
131
128
|
{ type: "custom", id: reply.questionId, customType: "nextclaw.user-question-reply", data: reply },
|
|
132
129
|
...(reply.answer ? [{ type: "markdown" as const, text: reply.answer }] : []),
|
|
133
130
|
]
|
|
134
|
-
:
|
|
131
|
+
: parts.filter((part, index) => {
|
|
132
|
+
const next = parts[index + 1];
|
|
133
|
+
if (part.type !== "markdown" || next?.type !== "custom" || next.customType !== "nextclaw.user-question") return true;
|
|
134
|
+
const entries = next.data && typeof next.data === "object" && "questions" in next.data ? next.data.questions : null;
|
|
135
|
+
if (!Array.isArray(entries)) return true;
|
|
136
|
+
const fallback = entries.map((entry) => {
|
|
137
|
+
if (!entry || typeof entry.title !== "string") return "";
|
|
138
|
+
return `${entry.title}${Array.isArray(entry.options) && entry.options.length ? ` (${entry.options.join(" / ")})` : ""}`;
|
|
139
|
+
}).join("\n");
|
|
140
|
+
return part.text !== fallback;
|
|
141
|
+
});
|
|
135
142
|
return {
|
|
136
143
|
id: message.id,
|
|
137
144
|
role: resolveUiRole(message.role),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { NcpMessage } from "@nextclaw/ncp";
|
|
2
|
+
|
|
3
|
+
function questionIdsFromToolPart(part: NcpMessage["parts"][number]): string[] {
|
|
4
|
+
if (part.type !== "tool-invocation" || part.toolName !== "request_user_input_async" ||
|
|
5
|
+
!part.result || typeof part.result !== "object" || Array.isArray(part.result)) return [];
|
|
6
|
+
const { accepted, questionIds } = part.result as { accepted?: unknown; questionIds?: unknown };
|
|
7
|
+
return accepted === true && Array.isArray(questionIds) && questionIds.every((id) => typeof id === "string")
|
|
8
|
+
? questionIds : [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function questionEntries(part: NcpMessage["parts"][number]): Array<{ id: string }> {
|
|
12
|
+
if (part.type !== "extension" || part.extensionType !== "nextclaw.user-question") return [];
|
|
13
|
+
const entries = part.data && typeof part.data === "object" && "questions" in part.data ? part.data.questions : null;
|
|
14
|
+
return Array.isArray(entries) ? entries.filter((entry): entry is { id: string } => Boolean(entry && typeof entry.id === "string")) : [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function placeUserQuestionsInAssistantBody<T extends { boundaryIndex: number }>(
|
|
18
|
+
messages: NcpMessage[], observationEvents: T[],
|
|
19
|
+
): { messages: NcpMessage[]; observationEvents: T[] } {
|
|
20
|
+
const entriesById = new Map(messages.flatMap((message) => message.parts.flatMap((part) => questionEntries(part).map((entry) => [entry.id, entry] as const))));
|
|
21
|
+
const anchoredIds = new Set(messages.flatMap((message) => message.parts.flatMap((part) =>
|
|
22
|
+
questionIdsFromToolPart(part).filter((id) => entriesById.has(id)))));
|
|
23
|
+
if (!anchoredIds.size) return { messages, observationEvents };
|
|
24
|
+
const removedIndices: number[] = [];
|
|
25
|
+
const projected = messages.flatMap((message, index) => {
|
|
26
|
+
const questionPart = message.parts.find((part) => questionEntries(part).length);
|
|
27
|
+
if (questionPart && message.id.startsWith("assistant-question-")) {
|
|
28
|
+
const remaining = questionEntries(questionPart).filter((entry) => !anchoredIds.has(entry.id));
|
|
29
|
+
if (!remaining.length) { removedIndices.push(index); return []; }
|
|
30
|
+
return [{ ...message, parts: [{ type: "extension" as const, extensionType: "nextclaw.user-question", data: { questions: remaining } }] }];
|
|
31
|
+
}
|
|
32
|
+
const parts = message.parts.flatMap((part): NcpMessage["parts"] => {
|
|
33
|
+
const questions = questionIdsFromToolPart(part).flatMap((id) => entriesById.get(id) ?? []);
|
|
34
|
+
return questions.length
|
|
35
|
+
? [{ type: "extension", extensionType: "nextclaw.user-question", data: { questions } }]
|
|
36
|
+
: [part];
|
|
37
|
+
});
|
|
38
|
+
return [{ ...message, parts }];
|
|
39
|
+
});
|
|
40
|
+
return { messages: projected, observationEvents: observationEvents.map((entry) => ({ ...entry,
|
|
41
|
+
boundaryIndex: entry.boundaryIndex - removedIndices.filter((index) => index < entry.boundaryIndex).length })) };
|
|
42
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./agents-BdeFtIOV.js";export{e as AgentsPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{ot as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as AppsPanel};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{K as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as CronJobResource};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{St as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as DocBrowser};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{z as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as InboxPage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{st as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as McpMarketplacePage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as e,v as t}from"./theme-provider-BsJ6HxeX.js";import"./app-presenter-provider-BB3Xl8b0.js";export{t as PanelAppHostProvider,e as PanelAppRuntimeSurface};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{F as e}from"./app-presenter-provider-BB3Xl8b0.js";export{e as RemoteAccessPage};
|