@qilin-agent/client-ui-plan 0.0.0-stage → 3.1.3
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/LICENSE +21 -0
- package/README.i18n.yaml +46 -0
- package/README.md +99 -2
- package/README.zh.md +100 -0
- package/lib/client.js +704 -0
- package/lib/index.js +13 -0
- package/lib/types/client/PlanCard.d.ts +26 -0
- package/lib/types/client/PlanModeControl.d.ts +11 -0
- package/lib/types/client/PlanPreview.d.ts +16 -0
- package/lib/types/client/failure-line.d.ts +11 -0
- package/lib/types/client/index.d.ts +25 -0
- package/lib/types/client/locales.d.ts +48 -0
- package/lib/types/client/plan-definition.d.ts +11 -0
- package/lib/types/client/plan-resource.d.ts +32 -0
- package/lib/types/client/plan.d.ts +39 -0
- package/lib/types/client/review-preview.d.ts +25 -0
- package/lib/types/client/review-store.d.ts +15 -0
- package/lib/types/index.d.ts +11 -0
- package/package.json +81 -3
package/lib/client.js
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@qilin-agent/client-ui-plan",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _qilin_agent_client_ui_primitives = require("@qilin-agent/client-ui-primitives");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react = require("react");
|
|
10
|
+
let _qilin_agent_client_store = require("@qilin-agent/client-store");
|
|
11
|
+
require("@qilin-agent/kylin");
|
|
12
|
+
//#region ../../util/crypto/src/index.ts
|
|
13
|
+
/**
|
|
14
|
+
* Random v4 UUID, minted from `crypto.getRandomValues`.
|
|
15
|
+
* @returns the UUID string.
|
|
16
|
+
*/
|
|
17
|
+
function randomUUID() {
|
|
18
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
19
|
+
const hex = Array.from(bytes, (byte, index) => {
|
|
20
|
+
return (index === 6 ? byte & 15 | 64 : index === 8 ? byte & 63 | 128 : byte).toString(16).padStart(2, "0");
|
|
21
|
+
}).join("");
|
|
22
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-plan/src/client/PlanPreview.module.css.mjs
|
|
26
|
+
const css$1 = ".yVhkTW_cards{flex-direction:column;gap:10px;display:flex}.yVhkTW_card{--plan-card-fill:var(--qilin-static-neutral-50);--plan-card-hover:var(--qilin-static-neutral-100);box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l1);background:var(--plan-card-fill);width:100%;min-width:0;height:60px;color:var(--qilin-alias-label-primary);font:inherit;text-align:left;cursor:pointer;border-radius:18px;align-items:center;gap:10px;margin:0;padding:8px 10px;transition:background-color .12s;display:flex}body[data-ds-dark-theme] .yVhkTW_card{--plan-card-fill:var(--qilin-static-neutral-850);--plan-card-hover:var(--qilin-static-neutral-800)}.yVhkTW_card:hover{background:var(--plan-card-hover)}.yVhkTW_card:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:3px}.yVhkTW_iconButton:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary))}.yVhkTW_cardIcon{box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l1);background:var(--plan-card-fill);border-radius:10px;flex:none;place-items:center;width:40px;height:40px;display:grid}.yVhkTW_cardDetails{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.yVhkTW_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;line-height:20px;overflow:hidden}.yVhkTW_cardDescription{text-overflow:ellipsis;white-space:nowrap;color:var(--qilin-alias-label-tertiary);font-size:10px;line-height:16px;overflow:hidden}.yVhkTW_cardOpen{box-sizing:border-box;border:.5px solid var(--qilin-alias-border-l3);background:var(--qilin-alias-button-floating-fill);border-radius:10px;flex:none;align-items:center;height:28px;padding:4px 8px;font-size:12px;line-height:18px;display:inline-flex}.yVhkTW_reviewLink{color:var(--qilin-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:4px;padding:0;display:inline-flex}.yVhkTW_reviewLink:hover{color:var(--qilin-alias-label-primary)}.yVhkTW_reviewLink:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:3px}.yVhkTW_iconButton{width:28px;height:28px;color:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;display:flex}.yVhkTW_iconButton:hover{background:var(--qilin-alias-interactive-bg-hover)}.yVhkTW_preview{box-sizing:border-box;height:100%;padding:20px 24px 40px;position:relative;overflow:auto}.yVhkTW_toolbar{justify-content:flex-end;margin-bottom:8px;display:flex}.yVhkTW_document{color:var(--qilin-alias-label-primary);overflow-wrap:anywhere;font-size:14px;line-height:1.75}.yVhkTW_message{color:var(--qilin-alias-label-secondary);padding:24px;font-size:14px}.yVhkTW_titleIcon{flex:none;display:inline-flex}@media (width<=767px){.yVhkTW_preview{padding:16px 18px 32px}}";
|
|
27
|
+
const tagId$1 = "@qilin-agent/client-ui-plan/PlanPreview.module.css";
|
|
28
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
29
|
+
const tag = document.createElement("style");
|
|
30
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-plan";
|
|
31
|
+
tag.dataset.pluginCss = tagId$1;
|
|
32
|
+
tag.textContent = css$1;
|
|
33
|
+
document.head.appendChild(tag);
|
|
34
|
+
}
|
|
35
|
+
var PlanPreview_module_css_default = {
|
|
36
|
+
"card": "yVhkTW_card",
|
|
37
|
+
"cardDescription": "yVhkTW_cardDescription",
|
|
38
|
+
"cardDetails": "yVhkTW_cardDetails",
|
|
39
|
+
"cardIcon": "yVhkTW_cardIcon",
|
|
40
|
+
"cardOpen": "yVhkTW_cardOpen",
|
|
41
|
+
"cardTitle": "yVhkTW_cardTitle",
|
|
42
|
+
"cards": "yVhkTW_cards",
|
|
43
|
+
"document": "yVhkTW_document",
|
|
44
|
+
"iconButton": "yVhkTW_iconButton",
|
|
45
|
+
"message": "yVhkTW_message",
|
|
46
|
+
"preview": "yVhkTW_preview",
|
|
47
|
+
"reviewLink": "yVhkTW_reviewLink",
|
|
48
|
+
"titleIcon": "yVhkTW_titleIcon",
|
|
49
|
+
"toolbar": "yVhkTW_toolbar"
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region lib/types/client/PlanCard.js
|
|
53
|
+
/** Persistent transcript cards and pending-review sidebar navigation. */
|
|
54
|
+
/**
|
|
55
|
+
* Render the completed Turn's submitted plans in invocation order.
|
|
56
|
+
* @param props - Logged plan, localized copy, and Session-bound navigation.
|
|
57
|
+
* @returns keyboard-accessible plan cards, or null for a Turn without plans.
|
|
58
|
+
*/
|
|
59
|
+
function PlanCards({ turn, useChat, openPlan, t }) {
|
|
60
|
+
const plans = useChat((snapshot) => snapshot.nodes.values().filter((node) => node.kind === "submitted-plan" && (node.location.kind === "turn" || node.location.kind === "step") && node.location.turn.turn === turn.turn).sort((a, b) => a.anchorSeq - b.anchorSeq), _qilin_agent_client_store.shallowEqual);
|
|
61
|
+
if (plans.length === 0) return null;
|
|
62
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
63
|
+
className: PlanPreview_module_css_default.cards,
|
|
64
|
+
"data-plan-artifacts": true,
|
|
65
|
+
children: plans.map(({ data: plan }) => (0, react_jsx_runtime.jsxs)("button", {
|
|
66
|
+
type: "button",
|
|
67
|
+
className: PlanPreview_module_css_default.card,
|
|
68
|
+
"data-plan-card": plan.callId,
|
|
69
|
+
"aria-label": t("preview.openNamed", { title: plan.title }),
|
|
70
|
+
onClick: () => {
|
|
71
|
+
openPlan(plan.callId);
|
|
72
|
+
},
|
|
73
|
+
children: [
|
|
74
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
75
|
+
className: PlanPreview_module_css_default.cardIcon,
|
|
76
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.FileTypeIcon, {
|
|
77
|
+
kind: "markdown",
|
|
78
|
+
size: 20
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
82
|
+
className: PlanPreview_module_css_default.cardDetails,
|
|
83
|
+
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
84
|
+
className: PlanPreview_module_css_default.cardTitle,
|
|
85
|
+
children: plan.title
|
|
86
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
87
|
+
className: PlanPreview_module_css_default.cardDescription,
|
|
88
|
+
children: t("preview.document")
|
|
89
|
+
})]
|
|
90
|
+
}),
|
|
91
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
92
|
+
className: PlanPreview_module_css_default.cardOpen,
|
|
93
|
+
children: t("preview.action")
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}, plan.callId))
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Open each pending plan automatically and retain a manual opener without answering it.
|
|
101
|
+
* @param props - Review identity, Session store, localized copy, and navigation.
|
|
102
|
+
* @returns an opener for either logged or temporary plan text.
|
|
103
|
+
*/
|
|
104
|
+
function PlanReviewOpen({ review, requestKey, openReview, t, useStore, actions }) {
|
|
105
|
+
const identity = review.callId === void 0 ? `review:${requestKey}` : `call:${review.callId}`;
|
|
106
|
+
const opened = useStore((state) => state.opened[identity] === true);
|
|
107
|
+
(0, react.useEffect)(() => {
|
|
108
|
+
if (opened) return;
|
|
109
|
+
openReview(review, requestKey);
|
|
110
|
+
actions.markOpened(identity);
|
|
111
|
+
}, [
|
|
112
|
+
identity,
|
|
113
|
+
opened,
|
|
114
|
+
openReview,
|
|
115
|
+
review,
|
|
116
|
+
requestKey,
|
|
117
|
+
actions
|
|
118
|
+
]);
|
|
119
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
120
|
+
type: "button",
|
|
121
|
+
className: PlanPreview_module_css_default.reviewLink,
|
|
122
|
+
title: t("preview.open"),
|
|
123
|
+
"aria-label": t("preview.open"),
|
|
124
|
+
onClick: () => {
|
|
125
|
+
openReview(review, requestKey);
|
|
126
|
+
},
|
|
127
|
+
children: [t("preview.full"), (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconChevronRightOutline14, { size: 14 })]
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region lib/types/client/review-preview.js
|
|
132
|
+
/**
|
|
133
|
+
* Name one temporary review within its browser lifetime and Session.
|
|
134
|
+
* @param sessionId - Session displaying the review.
|
|
135
|
+
* @param requestKey - Browser-unique pending request identity.
|
|
136
|
+
* @returns the address used to focus or reopen its preview.
|
|
137
|
+
*/
|
|
138
|
+
function reviewPreviewAddress(sessionId, requestKey) {
|
|
139
|
+
return `qilin-resource://plan-review/${encodeURIComponent(sessionId)}/${encodeURIComponent(requestKey)}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Recognize temporary plan navigation without interpreting it as logged history.
|
|
143
|
+
* @param address - Saved or caller-supplied navigation address.
|
|
144
|
+
* @returns whether the address identifies a temporary review preview.
|
|
145
|
+
*/
|
|
146
|
+
function isReviewPreviewAddress(address) {
|
|
147
|
+
return /^qilin-resource:\/\/plan-review\/[^/?#]+\/[^/?#]+$/.test(address);
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region lib/types/client/failure-line.js
|
|
151
|
+
/**
|
|
152
|
+
* Explain a failed plan read in the current locale.
|
|
153
|
+
* @param t - Plan namespace translator.
|
|
154
|
+
* @param failure - Failure reported by the resource provider.
|
|
155
|
+
* @returns localized plan copy, or the external failure's diagnostic.
|
|
156
|
+
*/
|
|
157
|
+
function planFailureLine(t, failure) {
|
|
158
|
+
switch (failure.code) {
|
|
159
|
+
case "plan/invalid-address": return t("preview.invalidAddress");
|
|
160
|
+
case "plan/unavailable": return t("preview.historyUnavailable");
|
|
161
|
+
case "plan/not-found": return t("preview.notFound");
|
|
162
|
+
default: return failure.message;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region lib/types/client/PlanPreview.js
|
|
167
|
+
/** Read-only Markdown viewer for logged plans and temporary review documents. */
|
|
168
|
+
/**
|
|
169
|
+
* Render the submitted plan with its complete Markdown and a copy action.
|
|
170
|
+
* @param props - Framework-bound tab identity, resource, and copy.
|
|
171
|
+
* @returns the plan document or a localized loading/failure state.
|
|
172
|
+
*/
|
|
173
|
+
function PlanPreview({ useTabInfo, useResource, t }) {
|
|
174
|
+
const tab = useTabInfo();
|
|
175
|
+
const resource = useResource(tab.tab.navigation.address);
|
|
176
|
+
const temporary = isReviewPreviewAddress(tab.tab.navigation.address);
|
|
177
|
+
const params = tab.tab.navigation.params;
|
|
178
|
+
const plan = temporary ? params !== void 0 && "planReview" in params ? params.planReview : void 0 : resource.value;
|
|
179
|
+
const labels = (0, react.useMemo)(() => ({
|
|
180
|
+
code: {
|
|
181
|
+
copyLabel: t("copy"),
|
|
182
|
+
copiedLabel: t("copied")
|
|
183
|
+
},
|
|
184
|
+
mermaid: {
|
|
185
|
+
diagramLabel: t("markdown.diagram"),
|
|
186
|
+
enlargedLabel: t("markdown.enlarged")
|
|
187
|
+
},
|
|
188
|
+
footnotes: t("markdown.footnotes")
|
|
189
|
+
}), [t]);
|
|
190
|
+
if (plan === void 0) return (0, react_jsx_runtime.jsxs)("div", {
|
|
191
|
+
className: PlanPreview_module_css_default.message,
|
|
192
|
+
role: "status",
|
|
193
|
+
children: [temporary ? t("preview.expired") : resource.status === "none" ? t("preview.unavailable") : resource.status === "failed" ? t("preview.failed") : t("preview.loading"), !temporary && resource.failure !== void 0 && (0, react_jsx_runtime.jsx)("p", { children: planFailureLine(t, resource.failure) })]
|
|
194
|
+
});
|
|
195
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
196
|
+
className: PlanPreview_module_css_default.preview,
|
|
197
|
+
"data-plan-preview": "callId" in plan ? plan.callId : tab.tab.navigation.address,
|
|
198
|
+
"aria-label": plan.title,
|
|
199
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
200
|
+
className: PlanPreview_module_css_default.toolbar,
|
|
201
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
202
|
+
type: "button",
|
|
203
|
+
className: PlanPreview_module_css_default.iconButton,
|
|
204
|
+
"aria-label": t("copy"),
|
|
205
|
+
onClick: () => {
|
|
206
|
+
(0, _qilin_agent_client_ui_primitives.writeClipboard)(plan.markdown);
|
|
207
|
+
},
|
|
208
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCopyOutline16, {})
|
|
209
|
+
})
|
|
210
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
211
|
+
className: PlanPreview_module_css_default.document,
|
|
212
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.MarkdownText, {
|
|
213
|
+
text: plan.markdown,
|
|
214
|
+
labels
|
|
215
|
+
})
|
|
216
|
+
})]
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Display a plan icon and the heading in its tab after resource recovery.
|
|
221
|
+
* @param props - Framework-bound tab identity and resource reader.
|
|
222
|
+
* @returns a decorative plan icon followed by the recovered title or initial localized label.
|
|
223
|
+
*/
|
|
224
|
+
function PlanTitle({ useTabInfo, useResource }) {
|
|
225
|
+
const tab = useTabInfo();
|
|
226
|
+
const resource = useResource(tab.tab.navigation.address);
|
|
227
|
+
const params = tab.tab.navigation.params;
|
|
228
|
+
const plan = isReviewPreviewAddress(tab.tab.navigation.address) ? params !== void 0 && "planReview" in params ? params.planReview : void 0 : resource.value;
|
|
229
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
|
|
230
|
+
className: PlanPreview_module_css_default.titleIcon,
|
|
231
|
+
"aria-hidden": "true",
|
|
232
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconPlanOutline14, { size: 16 })
|
|
233
|
+
}), plan?.title ?? tab.tab.title] });
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region lib/types/client/plan.js
|
|
237
|
+
function record(value) {
|
|
238
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Read a complete plan from untrusted logged arguments.
|
|
242
|
+
* @param event - Native call or PTC dispatch event from Session history.
|
|
243
|
+
* @returns the submitted plan, or undefined for unrelated or malformed data.
|
|
244
|
+
*/
|
|
245
|
+
function submittedPlan(event) {
|
|
246
|
+
if (event.type !== "tool/call" && event.type !== "tool/ptc-dispatch-start" && event.type !== "tool/ptc-dispatch") return void 0;
|
|
247
|
+
const data = event.data;
|
|
248
|
+
if (!record(data) || data.name !== "exit_plan_mode") return void 0;
|
|
249
|
+
const callId = event.type === "tool/call" ? data.callId : data.subCallId;
|
|
250
|
+
if (typeof callId !== "string" || callId === "") return void 0;
|
|
251
|
+
let args = data.arguments;
|
|
252
|
+
if (event.type === "tool/call") {
|
|
253
|
+
if (typeof args !== "string") return void 0;
|
|
254
|
+
try {
|
|
255
|
+
args = JSON.parse(args);
|
|
256
|
+
} catch (_error) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!record(args) || typeof args.plan !== "string") return void 0;
|
|
261
|
+
const markdown = args.plan;
|
|
262
|
+
const title = /^#\s+(\S[^\r\n]*)/.exec(markdown.trim())?.[1];
|
|
263
|
+
return title === void 0 ? void 0 : {
|
|
264
|
+
callId,
|
|
265
|
+
markdown,
|
|
266
|
+
title
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Encode the durable identity of a plan without retaining its text in layout storage.
|
|
271
|
+
* @param target - Session and tool-call identity.
|
|
272
|
+
* @returns the plan resource address.
|
|
273
|
+
*/
|
|
274
|
+
function planAddress(target) {
|
|
275
|
+
const { session, callId } = target;
|
|
276
|
+
return `qilin-resource://plan/${(session.kind === "session" ? [session.sessionId, callId] : [
|
|
277
|
+
"subagent",
|
|
278
|
+
session.parentSessionId,
|
|
279
|
+
session.childSessionId,
|
|
280
|
+
session.mode,
|
|
281
|
+
callId
|
|
282
|
+
]).map(encodeURIComponent).join("/")}`;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Validate a saved or caller-supplied plan resource address.
|
|
286
|
+
* @param address - Address submitted to the sidebar or resource provider.
|
|
287
|
+
* @returns the decoded identity, or undefined for an unsupported address.
|
|
288
|
+
*/
|
|
289
|
+
function parsePlanAddress(address) {
|
|
290
|
+
const match = /^qilin-resource:\/\/plan\/([^?#]+)$/.exec(address);
|
|
291
|
+
if (match === null) return void 0;
|
|
292
|
+
try {
|
|
293
|
+
const parts = match[1].split("/").map(decodeURIComponent);
|
|
294
|
+
if (parts.some((part) => part === "")) return void 0;
|
|
295
|
+
if (parts.length === 2) return {
|
|
296
|
+
session: {
|
|
297
|
+
kind: "session",
|
|
298
|
+
sessionId: parts[0]
|
|
299
|
+
},
|
|
300
|
+
callId: parts[1]
|
|
301
|
+
};
|
|
302
|
+
if (parts.length === 5 && parts[0] === "subagent" && (parts[3] === "one-shot" || parts[3] === "continuable")) return {
|
|
303
|
+
session: {
|
|
304
|
+
kind: "subagent",
|
|
305
|
+
parentSessionId: parts[1],
|
|
306
|
+
childSessionId: parts[2],
|
|
307
|
+
mode: parts[3]
|
|
308
|
+
},
|
|
309
|
+
callId: parts[4]
|
|
310
|
+
};
|
|
311
|
+
return;
|
|
312
|
+
} catch (_error) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region lib/types/client/plan-definition.js
|
|
318
|
+
/** One card per invocation; a later PTC settlement retains the original card position. */
|
|
319
|
+
const planDefinition = {
|
|
320
|
+
kind: "submitted-plan",
|
|
321
|
+
target: "chat",
|
|
322
|
+
match: (event) => {
|
|
323
|
+
const plan = submittedPlan(event);
|
|
324
|
+
return plan === void 0 ? null : {
|
|
325
|
+
id: plan.callId,
|
|
326
|
+
role: event.type === "tool/ptc-dispatch" ? "update" : "start"
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
start: (_context, match) => submittedPlan(match.event),
|
|
330
|
+
update: (context) => context.state,
|
|
331
|
+
buildViewNode: (context) => {
|
|
332
|
+
const start = context.start ?? context.matches[0];
|
|
333
|
+
const data = context.state ?? (start === void 0 ? void 0 : submittedPlan(start.event));
|
|
334
|
+
if (data === void 0 || start === void 0) return null;
|
|
335
|
+
return {
|
|
336
|
+
key: context.key,
|
|
337
|
+
kind: "submitted-plan",
|
|
338
|
+
id: context.id,
|
|
339
|
+
target: "chat",
|
|
340
|
+
anchorSeq: start.event.seq,
|
|
341
|
+
location: start.location,
|
|
342
|
+
visibility: "hidden",
|
|
343
|
+
data
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region ../../typert/protocol/src/remote-error.ts
|
|
349
|
+
/**
|
|
350
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
351
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
352
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
353
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
354
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
355
|
+
*/
|
|
356
|
+
var RemoteError = class extends Error {
|
|
357
|
+
code;
|
|
358
|
+
details;
|
|
359
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
360
|
+
isQILINRemoteError = true;
|
|
361
|
+
/**
|
|
362
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
363
|
+
* @param message - human diagnostic carried across the wire.
|
|
364
|
+
* @param details - structured payload typed by the code.
|
|
365
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
366
|
+
*/
|
|
367
|
+
constructor(code, message, details, options) {
|
|
368
|
+
super(message, options);
|
|
369
|
+
this.code = code;
|
|
370
|
+
this.details = details;
|
|
371
|
+
this.name = "RemoteError";
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Structurally identify a RemoteError thrown across module or realm copies of
|
|
376
|
+
* this class. Mechanism-internal: the Gateway and test assertions use it;
|
|
377
|
+
* business code receives typed failures and never needs it.
|
|
378
|
+
* @param value - a caught value.
|
|
379
|
+
* @returns the failure when the marker matches, otherwise undefined.
|
|
380
|
+
*/
|
|
381
|
+
function remoteErrorOf(value) {
|
|
382
|
+
if (typeof value === "object" && value !== null && value.isQILINRemoteError === true && typeof value.code === "string") return value;
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region ../../typert/protocol/src/index.ts
|
|
386
|
+
/**
|
|
387
|
+
* Remote decorators and explicit Gateway bindings backed by versioned
|
|
388
|
+
* descriptors carried on decorated class prototypes. Strict reflection
|
|
389
|
+
* remains a Typert compiler responsibility.
|
|
390
|
+
* @module @qilin-agent/typert-protocol
|
|
391
|
+
*/
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region lib/types/client/plan-resource.js
|
|
394
|
+
/**
|
|
395
|
+
* Bind plan reads to the generated Session Remote face.
|
|
396
|
+
* Opening a follow reads projections and may activate a prepared Session on the Host.
|
|
397
|
+
* Generated Remote streams can throw carrier failures; the provider reports failed
|
|
398
|
+
* reads as resource failure frames and preserves Remote error codes.
|
|
399
|
+
* @param remote - Existing Session history API.
|
|
400
|
+
* @returns a provider whose reads stop after finding the exact invocation.
|
|
401
|
+
*/
|
|
402
|
+
function planResourceProvider(remote) {
|
|
403
|
+
return {
|
|
404
|
+
protocol: "plan",
|
|
405
|
+
async *open(address, { signal }) {
|
|
406
|
+
const aborted = () => signal.aborted;
|
|
407
|
+
if (aborted()) return;
|
|
408
|
+
const target = parsePlanAddress(address);
|
|
409
|
+
if (target === void 0) {
|
|
410
|
+
yield {
|
|
411
|
+
ok: false,
|
|
412
|
+
error: new RemoteError("plan/invalid-address", "Invalid plan resource address.", {})
|
|
413
|
+
};
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const sessionAddress = target.session;
|
|
417
|
+
try {
|
|
418
|
+
let snapshot;
|
|
419
|
+
for await (const frame of remote.follow({ address: sessionAddress }, signal)) if (frame.type === "snapshot") {
|
|
420
|
+
snapshot = frame;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
if (aborted()) return;
|
|
424
|
+
if (snapshot === void 0) throw new RemoteError("plan/unavailable", "Session history ended before the plan could be read.", {});
|
|
425
|
+
let page = {
|
|
426
|
+
records: snapshot.records,
|
|
427
|
+
hasMore: snapshot.hasMore
|
|
428
|
+
};
|
|
429
|
+
while (true) {
|
|
430
|
+
for (const entry of page.records) {
|
|
431
|
+
const plan = submittedPlan(entry.event);
|
|
432
|
+
if (plan?.callId === target.callId) {
|
|
433
|
+
yield {
|
|
434
|
+
ok: true,
|
|
435
|
+
value: plan
|
|
436
|
+
};
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const beforeSeq = page.records[0]?.event.seq;
|
|
441
|
+
if (!page.hasMore || beforeSeq === void 0) break;
|
|
442
|
+
const next = await remote.page({
|
|
443
|
+
address: sessionAddress,
|
|
444
|
+
throughSeq: snapshot.cursor,
|
|
445
|
+
beforeSeq
|
|
446
|
+
}, signal);
|
|
447
|
+
if (aborted()) return;
|
|
448
|
+
if (!next.ok) {
|
|
449
|
+
yield next;
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
page = next.value;
|
|
453
|
+
}
|
|
454
|
+
yield {
|
|
455
|
+
ok: false,
|
|
456
|
+
error: new RemoteError("plan/not-found", "The submitted plan was not found in this Session.", {})
|
|
457
|
+
};
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (!aborted()) yield {
|
|
460
|
+
ok: false,
|
|
461
|
+
error: remoteErrorOf(error) ?? new RemoteError("plan/read-failed", error instanceof Error ? error.message : String(error), {})
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region lib/types/client/review-store.js
|
|
469
|
+
/** Session-owned memory of pending plans already opened automatically. */
|
|
470
|
+
/**
|
|
471
|
+
* Keep manual sidebar closure effective across review component remounts.
|
|
472
|
+
* @returns a transient store handle whose instances belong to Session scopes.
|
|
473
|
+
*/
|
|
474
|
+
function createPlanReviewStore() {
|
|
475
|
+
return (0, _qilin_agent_client_store.defineStore)({
|
|
476
|
+
init: () => ({ opened: {} }),
|
|
477
|
+
actions: { markOpened: (draft, reviewKey) => {
|
|
478
|
+
draft.opened[reviewKey] = true;
|
|
479
|
+
} }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
//#endregion
|
|
483
|
+
//#region \0qilin-css:/home/runner/work/QiLin/QiLin/packages/client/ui-plan/src/client/PlanModeControl.module.css.mjs
|
|
484
|
+
const css = ".PzyYZa_wrap{align-items:center;gap:6px;display:inline-flex}.PzyYZa_chip{corner-shape:round;background:var(--qilin-alias-state-warn-tertiary);min-width:34px;color:var(--qilin-alias-state-warn-label);cursor:pointer;border:none;border-radius:999px;align-items:center;gap:4px;padding:2px 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.PzyYZa_chip:hover:not(:disabled){color:var(--qilin-alias-state-warn-primary)}.PzyYZa_chip:focus-visible{outline:var(--qilin-focus-ring-width) solid var(--qilin-focus-ring-color,var(--qilin-alias-state-business-primary));outline-offset:2px}.PzyYZa_chip:disabled{opacity:.6;cursor:default}.PzyYZa_close{color:currentColor;align-items:center;display:inline-flex}.PzyYZa_error{color:var(--qilin-alias-state-error-primary);font-size:12px;line-height:18px}";
|
|
485
|
+
const tagId = "@qilin-agent/client-ui-plan/PlanModeControl.module.css";
|
|
486
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
487
|
+
const tag = document.createElement("style");
|
|
488
|
+
tag.dataset.plugin = "@qilin-agent/client-ui-plan";
|
|
489
|
+
tag.dataset.pluginCss = tagId;
|
|
490
|
+
tag.textContent = css;
|
|
491
|
+
document.head.appendChild(tag);
|
|
492
|
+
}
|
|
493
|
+
var PlanModeControl_module_css_default = {
|
|
494
|
+
"chip": "PzyYZa_chip",
|
|
495
|
+
"close": "PzyYZa_close",
|
|
496
|
+
"error": "PzyYZa_error",
|
|
497
|
+
"wrap": "PzyYZa_wrap"
|
|
498
|
+
};
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region lib/types/client/PlanModeControl.js
|
|
501
|
+
/**
|
|
502
|
+
* Plan-mode status over the host-computed `plan` projection. The chip renders
|
|
503
|
+
* only while the effective target is plan mode (`pending ? !active : active`
|
|
504
|
+
* — a folded host value, not client optimism) and executes /plan off.
|
|
505
|
+
*/
|
|
506
|
+
function PlanChip({ useProjection, locked, exitPlanMode, t }) {
|
|
507
|
+
const plan = useProjection("plan");
|
|
508
|
+
const [leaving, setLeaving] = (0, react.useState)(false);
|
|
509
|
+
const [error, setError] = (0, react.useState)(null);
|
|
510
|
+
const aliveRef = (0, react.useRef)(true);
|
|
511
|
+
(0, react.useEffect)(() => {
|
|
512
|
+
aliveRef.current = true;
|
|
513
|
+
return () => {
|
|
514
|
+
aliveRef.current = false;
|
|
515
|
+
};
|
|
516
|
+
}, []);
|
|
517
|
+
if (plan === void 0) return null;
|
|
518
|
+
if (!(plan.pending ? !plan.active : plan.active)) return null;
|
|
519
|
+
const off = () => {
|
|
520
|
+
setLeaving(true);
|
|
521
|
+
setError(null);
|
|
522
|
+
exitPlanMode().then((failure) => {
|
|
523
|
+
if (!aliveRef.current) return;
|
|
524
|
+
setLeaving(false);
|
|
525
|
+
setError(failure);
|
|
526
|
+
}, (reason) => {
|
|
527
|
+
if (!aliveRef.current) return;
|
|
528
|
+
setLeaving(false);
|
|
529
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
return (0, react_jsx_runtime.jsxs)("span", {
|
|
533
|
+
className: PlanModeControl_module_css_default.wrap,
|
|
534
|
+
children: [(0, react_jsx_runtime.jsxs)("button", {
|
|
535
|
+
type: "button",
|
|
536
|
+
className: PlanModeControl_module_css_default.chip,
|
|
537
|
+
"aria-label": t("chip.on.aria"),
|
|
538
|
+
title: t("chip.on.title"),
|
|
539
|
+
disabled: locked || leaving,
|
|
540
|
+
onClick: off,
|
|
541
|
+
children: [t("chip.label"), (0, react_jsx_runtime.jsx)("span", {
|
|
542
|
+
className: PlanModeControl_module_css_default.close,
|
|
543
|
+
"aria-hidden": true,
|
|
544
|
+
children: (0, react_jsx_runtime.jsx)(_qilin_agent_client_ui_primitives.IconCloseFill14, { size: 12 })
|
|
545
|
+
})]
|
|
546
|
+
}), error !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
547
|
+
className: PlanModeControl_module_css_default.error,
|
|
548
|
+
role: "status",
|
|
549
|
+
title: error,
|
|
550
|
+
children: t("chip.exitFailed")
|
|
551
|
+
})]
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region lib/types/client/locales.js
|
|
556
|
+
/** `plan` namespace dictionaries (the composer plan chip's copy). */
|
|
557
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
558
|
+
const zh = {
|
|
559
|
+
"chip.label": "Plan",
|
|
560
|
+
"preview.title": "计划",
|
|
561
|
+
"preview.document": "计划 · Markdown",
|
|
562
|
+
"preview.action": "打开",
|
|
563
|
+
"preview.open": "在侧边栏打开计划",
|
|
564
|
+
"preview.full": "查看全文",
|
|
565
|
+
"preview.openNamed": "打开计划:{title}",
|
|
566
|
+
"preview.loading": "正在读取计划…",
|
|
567
|
+
"preview.failed": "无法读取计划",
|
|
568
|
+
"preview.invalidAddress": "计划地址无效",
|
|
569
|
+
"preview.historyUnavailable": "无法读取会话历史",
|
|
570
|
+
"preview.notFound": "未找到这份计划",
|
|
571
|
+
"preview.unavailable": "计划预览不可用",
|
|
572
|
+
"preview.expired": "临时计划预览已失效,请从仍在等待审批的卡片重新打开。",
|
|
573
|
+
"chip.on.aria": "plan mode 已开启,按下关闭",
|
|
574
|
+
"chip.on.title": "plan mode 已开启 — 点击关闭(/plan off)",
|
|
575
|
+
"chip.off.aria": "plan mode 已关闭,按下开启",
|
|
576
|
+
"chip.off.title": "plan mode 已关闭 — 点击开启(/plan)",
|
|
577
|
+
"chip.exitFailed": "退出 plan mode 失败"
|
|
578
|
+
};
|
|
579
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
580
|
+
const en = {
|
|
581
|
+
"chip.label": "Plan",
|
|
582
|
+
"preview.title": "Plan",
|
|
583
|
+
"preview.document": "Plan · Markdown",
|
|
584
|
+
"preview.action": "Open",
|
|
585
|
+
"preview.open": "Open plan in sidebar",
|
|
586
|
+
"preview.full": "View full plan",
|
|
587
|
+
"preview.openNamed": "Open plan: {title}",
|
|
588
|
+
"preview.loading": "Loading plan…",
|
|
589
|
+
"preview.failed": "Could not load plan",
|
|
590
|
+
"preview.invalidAddress": "Invalid plan address",
|
|
591
|
+
"preview.historyUnavailable": "Session history is unavailable",
|
|
592
|
+
"preview.notFound": "This plan was not found",
|
|
593
|
+
"preview.unavailable": "Plan preview is unavailable",
|
|
594
|
+
"preview.expired": "This temporary plan preview has expired. Reopen it from the pending review card.",
|
|
595
|
+
"chip.on.aria": "Plan mode on, press to turn off",
|
|
596
|
+
"chip.on.title": "Plan mode on — click to turn off (/plan off)",
|
|
597
|
+
"chip.off.aria": "Plan mode off, press to turn on",
|
|
598
|
+
"chip.off.title": "Plan mode off — click to turn on (/plan)",
|
|
599
|
+
"chip.exitFailed": "Failed to exit plan mode"
|
|
600
|
+
};
|
|
601
|
+
//#endregion
|
|
602
|
+
//#region lib/types/client/index.js
|
|
603
|
+
/** Dictionary namespace owned by this plugin. */
|
|
604
|
+
const NS = "plan";
|
|
605
|
+
/** Services for plan controls, Conversation projection, and resource navigation. */
|
|
606
|
+
const inject = [
|
|
607
|
+
"slots",
|
|
608
|
+
"remote",
|
|
609
|
+
"remote.commands",
|
|
610
|
+
"remote.session",
|
|
611
|
+
"sessions",
|
|
612
|
+
"locale",
|
|
613
|
+
"uiConversation",
|
|
614
|
+
"resources",
|
|
615
|
+
"sidebarRight",
|
|
616
|
+
"sidebarRightTabs"
|
|
617
|
+
];
|
|
618
|
+
/**
|
|
619
|
+
* Register plan controls, permanent Chat cards, and sidebar document reading.
|
|
620
|
+
* @param ctx - client root context.
|
|
621
|
+
*/
|
|
622
|
+
function apply(ctx) {
|
|
623
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
624
|
+
zh,
|
|
625
|
+
en
|
|
626
|
+
}), "ui-plan: dictionaries");
|
|
627
|
+
const previewId = "@qilin-agent/client-ui-plan";
|
|
628
|
+
const t = ctx.locale.bind(NS);
|
|
629
|
+
ctx.effect(() => ctx.uiConversation.events.register(planDefinition), "ui-plan: conversation definition");
|
|
630
|
+
ctx.effect(() => ctx.resources.register(planResourceProvider(ctx.remote.session)), "ui-plan: resources");
|
|
631
|
+
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
632
|
+
id: previewId,
|
|
633
|
+
kind: "plan",
|
|
634
|
+
patterns: ["qilin-resource://plan/**", "qilin-resource://plan-review/**"],
|
|
635
|
+
priority: "builtin",
|
|
636
|
+
canOpen: (address) => parsePlanAddress(address) !== void 0 || isReviewPreviewAddress(address),
|
|
637
|
+
title: () => t("preview.title")
|
|
638
|
+
}), "ui-plan: sidebar type");
|
|
639
|
+
const open = (sessionId) => ({ openPlan: (callId) => {
|
|
640
|
+
const child = ctx.sessions.subagentAddress(sessionId);
|
|
641
|
+
const session = child === void 0 ? {
|
|
642
|
+
kind: "session",
|
|
643
|
+
sessionId
|
|
644
|
+
} : {
|
|
645
|
+
kind: "subagent",
|
|
646
|
+
...child
|
|
647
|
+
};
|
|
648
|
+
ctx.sidebarRight.openResourceIn(sessionId, planAddress({
|
|
649
|
+
session,
|
|
650
|
+
callId
|
|
651
|
+
}));
|
|
652
|
+
} });
|
|
653
|
+
const reviewWindow = randomUUID();
|
|
654
|
+
const reviewStore = createPlanReviewStore();
|
|
655
|
+
ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
|
|
656
|
+
name: "conversation.chat.turnTail",
|
|
657
|
+
id: previewId,
|
|
658
|
+
locale: NS,
|
|
659
|
+
inject: open
|
|
660
|
+
}, PlanCards));
|
|
661
|
+
ctx.slots.inject("conversation.plan-review.actions", () => ctx.slots.register({
|
|
662
|
+
name: "conversation.plan-review.actions",
|
|
663
|
+
id: previewId,
|
|
664
|
+
locale: NS,
|
|
665
|
+
store: reviewStore,
|
|
666
|
+
inject: (sessionId) => ({ openReview: (review, requestKey) => {
|
|
667
|
+
if (review.callId !== void 0) {
|
|
668
|
+
open(sessionId).openPlan(review.callId);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
ctx.sidebarRight.openResourceIn(sessionId, reviewPreviewAddress(sessionId, `${reviewWindow}:${requestKey}`), { params: { planReview: {
|
|
672
|
+
markdown: review.plan,
|
|
673
|
+
title: (0, _qilin_agent_client_ui_primitives.extractMarkdownPlainText)(review.plan, { mode: "first-line" })
|
|
674
|
+
} } });
|
|
675
|
+
} })
|
|
676
|
+
}, PlanReviewOpen));
|
|
677
|
+
ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
678
|
+
name: "sidebar.right.pane.tab",
|
|
679
|
+
key: previewId,
|
|
680
|
+
locale: NS
|
|
681
|
+
}, PlanPreview));
|
|
682
|
+
ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
|
|
683
|
+
name: "sidebar.right.pane.tab.title",
|
|
684
|
+
key: previewId
|
|
685
|
+
}, PlanTitle));
|
|
686
|
+
ctx.slots.inject("conversation.input.plan", () => ctx.slots.register({
|
|
687
|
+
name: "conversation.input.plan",
|
|
688
|
+
locale: NS,
|
|
689
|
+
inject: (sessionId) => ({ exitPlanMode: async () => {
|
|
690
|
+
const result = await ctx.remote.commands.execute(sessionId, "/plan off", []);
|
|
691
|
+
if (!result.ok) return `${result.error.message} (${result.error.code})`;
|
|
692
|
+
if (result.value === void 0) return "unknown command: /plan off";
|
|
693
|
+
return null;
|
|
694
|
+
} })
|
|
695
|
+
}, PlanChip));
|
|
696
|
+
}
|
|
697
|
+
//#endregion
|
|
698
|
+
exports.apply = apply;
|
|
699
|
+
exports.inject = inject;
|
|
700
|
+
return module.exports;
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
//# sourceMappingURL=client.js.map
|