@deepseek-ai/dsh-client-ui-plan 0.1.6-alpha.1 → 0.1.6-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +9 -1
- package/README.zh.md +9 -1
- package/lib/client.js +564 -4
- package/lib/types/client/PlanCard.d.ts +26 -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 +2 -2
- package/lib/types/client/locales.d.ts +26 -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/package.json +29 -14
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-plan/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: f7e7aede2c76717228d101153e4d3d2f8d3f88c4
|
|
6
|
+
README.zh.md: 9ab37a25dd7cb406b42bf5c531445357d6725e2e
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Plan mode lets you review a plan before implementation. Enter with `/plan` and leave with the composer chip. Submitted plans open automatically in the right sidebar for review and remain available from cards in the completed Turn’s final artifact area after approval, rejection, or dismissal. Reopening a plan focuses its existing tab, and browser reload restores the document from Session history.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -31,6 +31,12 @@ Mount this plugin alongside `ui-conversation` and `dsh-plan-mode`; the chip then
|
|
|
31
31
|
|
|
32
32
|
While the effective target is plan mode, the seat renders the warn-colored "Plan ×" status button, which executes `/plan off`. Otherwise the seat stays empty: a host without plan mode, or a Draft with no session, shows nothing. While plan mode is the effective target, the composer textarea's placeholder switches to the plan-task hint — "describe your task to generate plan" — unless the owning surface supplies its own placeholder.
|
|
33
33
|
|
|
34
|
+
### Reading submitted plans
|
|
35
|
+
|
|
36
|
+
When a Turn ends, each submitted plan appears in its final artifact area, using the file-delivery card treatment with a Markdown icon, title, and Open action. A pending plan opens automatically once per submission in the current browser session. Closing it stays effective across review remounts; a new submission opens its own plan. Historical cards open only when clicked. Use the card or review strip’s View full plan link to read and copy the complete Markdown. Plan tabs show a document-and-pen icon. Different submissions retain separate tabs; the review buttons alone decide whether implementation may begin.
|
|
37
|
+
|
|
38
|
+
A review without a logged invocation also opens automatically. Its complete text lives only in the tab’s navigation memory, and the pending review card can reopen it. Reloading the page loses that text; an expired preview directs the user back to a pending review.
|
|
39
|
+
|
|
34
40
|
### Failures
|
|
35
41
|
|
|
36
42
|
Admission failures (`matched: false`, business errors, transport faults) surface as an inline error and the chip stays until the projection confirms the exit.
|
|
@@ -45,6 +51,8 @@ Admission failures (`matched: false`, business errors, transport faults) surface
|
|
|
45
51
|
|
|
46
52
|
The chip occupies the conversation-declared `conversation.input.plan` single seat; the node half is an empty apply (the roster row). Reads ride the generic projection pair through the standard-kit `useProjection`: the effective target is `pending ? !active : active` — a folded host value, not client optimism, so an arriving frame corrects the chip either way. The seat's injected face carries one verb, `exitPlanMode`, which executes `/plan off` through `ctx.remote.commands.execute` and maps admission failures to an inline error line. The placeholder and hint text live in ui-conversation's `conversation` locale namespace and are shared verbatim with the claimed `/plan` command hint. The accessible description is "Plan mode on, press to turn off".
|
|
47
53
|
|
|
54
|
+
Plan cards derive from native `tool/call` or PTC dispatch arguments through a Conversation Definition, with each invocation’s resolved Turn location. They contribute to the additive `conversation.chat.turnTail` list alongside file deliveries. The plan resource address identifies the invocation and its complete ordinary or direct-parent subagent Session address; its provider reads existing Session history, including older pages, without storing document text in sidebar layout. The question plugin owns the review action slot and supplies its request key, complete text, and optional invocation identity. The [decision](../../../.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.md) explains why review lifetime and document lifetime remain separate.
|
|
55
|
+
|
|
48
56
|
</details>
|
|
49
57
|
|
|
50
58
|
-----
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
计划模式让你在实施前审阅计划。通过 `/plan` 进入,通过编辑器中的状态按钮退出。提交的计划自动在右侧边栏打开供审阅;批准、拒绝或关闭审批后,仍可通过已完成回合末尾的产物卡片查看。重新打开同一计划会聚焦已有标签页,刷新浏览器后会从会话历史恢复正文。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -31,6 +31,12 @@ kind: "package-reference"
|
|
|
31
31
|
|
|
32
32
|
当有效目标为 plan 模式时,该座位渲染 warn 色「Plan ×」状态按钮,执行 `/plan off`。否则座位保持为空:未组合 plan-mode 的宿主,或尚无会话的 Draft,都不显示任何内容。plan 模式为有效目标期间,composer 文本框的 placeholder 切换为 plan 任务提示——「describe your task to generate plan」——除非所属 surface 提供自己的 placeholder。
|
|
33
33
|
|
|
34
|
+
### 查看已提交的计划
|
|
35
|
+
|
|
36
|
+
回合结束后,每次提交的计划都会显示在该回合末尾的产物区域,并采用文件产物卡片的样式,包含 Markdown 图标、标题和“打开”操作。在当前浏览器会话中,每份待审计划自动打开一次;关闭后,重新挂载审批组件不会再次展开,新提交会打开其对应计划。历史卡片仅在点击后打开。通过卡片或审批条带上的“查看全文”链接可阅读和复制完整 Markdown。计划标签页显示文档加笔的图标。不同提交保留独立标签页;是否开始实施仍由审批按钮决定。
|
|
37
|
+
|
|
38
|
+
没有已记录调用标识的审批也会自动打开。完整正文只保存在标签页的导航内存中,待处理审批卡片可以重新打开它。刷新页面会丢失该正文;失效的预览会提示用户返回待处理审批。
|
|
39
|
+
|
|
34
40
|
### 失败
|
|
35
41
|
|
|
36
42
|
准入失败(`matched: false`、业务错误、传输故障)以内联错误呈现,徽章保持显示直至投影确认退出。
|
|
@@ -45,6 +51,8 @@ kind: "package-reference"
|
|
|
45
51
|
|
|
46
52
|
徽章占据 conversation 声明的 `conversation.input.plan` 单实例座位;node 半部是空 apply(roster 行)。读取经 standard-kit 的 `useProjection` 走通用投影对:有效目标是 `pending ? !active : active`——折叠的宿主值而非客户端乐观态,因此到达的帧无论哪个方向都会纠正徽章。座位注入面携带一个动词 `exitPlanMode`,经 `ctx.remote.commands.execute` 执行 `/plan off`,并把准入失败映射为一行内联错误。placeholder 与提示文案位于 ui-conversation 的 `conversation` locale 命名空间,与已认领 `/plan` 命令的提示逐字共用。无障碍描述是「Plan mode on, press to turn off」。
|
|
47
53
|
|
|
54
|
+
计划卡片通过 Conversation Definition 从原生 `tool/call` 或 PTC dispatch 参数派生,并使用每次调用已解析的回合位置。它们与文件产物一起贡献到可追加的 `conversation.chat.turnTail` 列表。计划资源地址标识调用及完整的普通会话地址或子会话直接父级地址;provider 读取已有会话历史及较早分页,不把正文存入侧边栏布局。提问插件拥有审批操作插槽,并提供请求键、完整正文和可选的调用标识。[决策记录](../../../.agents/notes/implemented/feature/2026-09-17-persistent-plan-cards.zh.md)说明审批与文档为何保持独立生命周期。
|
|
55
|
+
|
|
48
56
|
</details>
|
|
49
57
|
|
|
50
58
|
-----
|
package/lib/client.js
CHANGED
|
@@ -4,9 +4,478 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
7
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
9
|
let react = require("react");
|
|
9
|
-
let
|
|
10
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
11
|
+
require("@deepseek-ai/cordis");
|
|
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 \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-plan/src/client/PlanPreview.module.css.mjs
|
|
26
|
+
const css$1 = ".k74WwW_cards{flex-direction:column;gap:10px;display:flex}.k74WwW_card{--plan-card-fill:var(--dsw-static-neutral-50);--plan-card-hover:var(--dsw-static-neutral-100);box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l1);background:var(--plan-card-fill);width:100%;min-width:0;height:60px;color:var(--dsw-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] .k74WwW_card{--plan-card-fill:var(--dsw-static-neutral-850);--plan-card-hover:var(--dsw-static-neutral-800)}.k74WwW_card:hover{background:var(--plan-card-hover)}.k74WwW_card:focus-visible,.k74WwW_iconButton:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.k74WwW_cardIcon{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l1);background:var(--plan-card-fill);border-radius:10px;flex:none;place-items:center;width:40px;height:40px;display:grid}.k74WwW_cardDetails{flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.k74WwW_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;line-height:20px;overflow:hidden}.k74WwW_cardDescription{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px;overflow:hidden}.k74WwW_cardOpen{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-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}.k74WwW_reviewLink{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:4px;padding:0;display:inline-flex}.k74WwW_reviewLink:hover{color:var(--dsw-alias-label-primary)}.k74WwW_reviewLink:focus-visible{outline:2px solid var(--dsw-alias-label-primary);outline-offset:3px}.k74WwW_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}.k74WwW_iconButton:hover{background:var(--dsw-alias-interactive-bg-hover)}.k74WwW_preview{box-sizing:border-box;height:100%;padding:20px 24px 40px;position:relative;overflow:auto}.k74WwW_toolbar{justify-content:flex-end;margin-bottom:8px;display:flex}.k74WwW_document{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;font-size:14px;line-height:1.75}.k74WwW_message{color:var(--dsw-alias-label-secondary);padding:24px;font-size:14px}.k74WwW_titleIcon{flex:none;display:inline-flex}@media (width<=767px){.k74WwW_preview{padding:16px 18px 32px}}";
|
|
27
|
+
const tagId$1 = "@deepseek-ai/dsh-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 = "@deepseek-ai/dsh-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": "k74WwW_card",
|
|
37
|
+
"cardDescription": "k74WwW_cardDescription",
|
|
38
|
+
"cardDetails": "k74WwW_cardDetails",
|
|
39
|
+
"cardIcon": "k74WwW_cardIcon",
|
|
40
|
+
"cardOpen": "k74WwW_cardOpen",
|
|
41
|
+
"cardTitle": "k74WwW_cardTitle",
|
|
42
|
+
"cards": "k74WwW_cards",
|
|
43
|
+
"document": "k74WwW_document",
|
|
44
|
+
"iconButton": "k74WwW_iconButton",
|
|
45
|
+
"message": "k74WwW_message",
|
|
46
|
+
"preview": "k74WwW_preview",
|
|
47
|
+
"reviewLink": "k74WwW_reviewLink",
|
|
48
|
+
"titleIcon": "k74WwW_titleIcon",
|
|
49
|
+
"toolbar": "k74WwW_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), _deepseek_ai_dsh_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)(_deepseek_ai_dsh_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)(_deepseek_ai_dsh_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 `dsh-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 /^dsh-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
|
+
footnotes: t("markdown.footnotes")
|
|
185
|
+
}), [t]);
|
|
186
|
+
if (plan === void 0) return (0, react_jsx_runtime.jsxs)("div", {
|
|
187
|
+
className: PlanPreview_module_css_default.message,
|
|
188
|
+
role: "status",
|
|
189
|
+
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) })]
|
|
190
|
+
});
|
|
191
|
+
return (0, react_jsx_runtime.jsxs)("section", {
|
|
192
|
+
className: PlanPreview_module_css_default.preview,
|
|
193
|
+
"data-plan-preview": "callId" in plan ? plan.callId : tab.tab.navigation.address,
|
|
194
|
+
"aria-label": plan.title,
|
|
195
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
196
|
+
className: PlanPreview_module_css_default.toolbar,
|
|
197
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
198
|
+
type: "button",
|
|
199
|
+
className: PlanPreview_module_css_default.iconButton,
|
|
200
|
+
"aria-label": t("copy"),
|
|
201
|
+
onClick: () => {
|
|
202
|
+
(0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(plan.markdown);
|
|
203
|
+
},
|
|
204
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})
|
|
205
|
+
})
|
|
206
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
207
|
+
className: PlanPreview_module_css_default.document,
|
|
208
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
209
|
+
text: plan.markdown,
|
|
210
|
+
labels
|
|
211
|
+
})
|
|
212
|
+
})]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Display a plan icon and the heading in its tab after resource recovery.
|
|
217
|
+
* @param props - Framework-bound tab identity and resource reader.
|
|
218
|
+
* @returns a decorative plan icon followed by the recovered title or initial localized label.
|
|
219
|
+
*/
|
|
220
|
+
function PlanTitle({ useTabInfo, useResource }) {
|
|
221
|
+
const tab = useTabInfo();
|
|
222
|
+
const resource = useResource(tab.tab.navigation.address);
|
|
223
|
+
const params = tab.tab.navigation.params;
|
|
224
|
+
const plan = isReviewPreviewAddress(tab.tab.navigation.address) ? params !== void 0 && "planReview" in params ? params.planReview : void 0 : resource.value;
|
|
225
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsx)("span", {
|
|
226
|
+
className: PlanPreview_module_css_default.titleIcon,
|
|
227
|
+
"aria-hidden": "true",
|
|
228
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlanOutline14, { size: 16 })
|
|
229
|
+
}), plan?.title ?? tab.tab.title] });
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region lib/types/client/plan.js
|
|
233
|
+
function record(value) {
|
|
234
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Read a complete plan from untrusted logged arguments.
|
|
238
|
+
* @param event - Native call or PTC dispatch event from Session history.
|
|
239
|
+
* @returns the submitted plan, or undefined for unrelated or malformed data.
|
|
240
|
+
*/
|
|
241
|
+
function submittedPlan(event) {
|
|
242
|
+
if (event.type !== "tool/call" && event.type !== "tool/ptc-dispatch-start" && event.type !== "tool/ptc-dispatch") return void 0;
|
|
243
|
+
const data = event.data;
|
|
244
|
+
if (!record(data) || data.name !== "exit_plan_mode") return void 0;
|
|
245
|
+
const callId = event.type === "tool/call" ? data.callId : data.subCallId;
|
|
246
|
+
if (typeof callId !== "string" || callId === "") return void 0;
|
|
247
|
+
let args = data.arguments;
|
|
248
|
+
if (event.type === "tool/call") {
|
|
249
|
+
if (typeof args !== "string") return void 0;
|
|
250
|
+
try {
|
|
251
|
+
args = JSON.parse(args);
|
|
252
|
+
} catch (_error) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (!record(args) || typeof args.plan !== "string") return void 0;
|
|
257
|
+
const markdown = args.plan;
|
|
258
|
+
const title = /^#\s+(\S[^\r\n]*)/.exec(markdown.trim())?.[1];
|
|
259
|
+
return title === void 0 ? void 0 : {
|
|
260
|
+
callId,
|
|
261
|
+
markdown,
|
|
262
|
+
title
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Encode the durable identity of a plan without retaining its text in layout storage.
|
|
267
|
+
* @param target - Session and tool-call identity.
|
|
268
|
+
* @returns the plan resource address.
|
|
269
|
+
*/
|
|
270
|
+
function planAddress(target) {
|
|
271
|
+
const { session, callId } = target;
|
|
272
|
+
return `dsh-resource://plan/${(session.kind === "session" ? [session.sessionId, callId] : [
|
|
273
|
+
"subagent",
|
|
274
|
+
session.parentSessionId,
|
|
275
|
+
session.childSessionId,
|
|
276
|
+
session.mode,
|
|
277
|
+
callId
|
|
278
|
+
]).map(encodeURIComponent).join("/")}`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Validate a saved or caller-supplied plan resource address.
|
|
282
|
+
* @param address - Address submitted to the sidebar or resource provider.
|
|
283
|
+
* @returns the decoded identity, or undefined for an unsupported address.
|
|
284
|
+
*/
|
|
285
|
+
function parsePlanAddress(address) {
|
|
286
|
+
const match = /^dsh-resource:\/\/plan\/([^?#]+)$/.exec(address);
|
|
287
|
+
if (match === null) return void 0;
|
|
288
|
+
try {
|
|
289
|
+
const parts = match[1].split("/").map(decodeURIComponent);
|
|
290
|
+
if (parts.some((part) => part === "")) return void 0;
|
|
291
|
+
if (parts.length === 2) return {
|
|
292
|
+
session: {
|
|
293
|
+
kind: "session",
|
|
294
|
+
sessionId: parts[0]
|
|
295
|
+
},
|
|
296
|
+
callId: parts[1]
|
|
297
|
+
};
|
|
298
|
+
if (parts.length === 5 && parts[0] === "subagent" && (parts[3] === "one-shot" || parts[3] === "continuable")) return {
|
|
299
|
+
session: {
|
|
300
|
+
kind: "subagent",
|
|
301
|
+
parentSessionId: parts[1],
|
|
302
|
+
childSessionId: parts[2],
|
|
303
|
+
mode: parts[3]
|
|
304
|
+
},
|
|
305
|
+
callId: parts[4]
|
|
306
|
+
};
|
|
307
|
+
return;
|
|
308
|
+
} catch (_error) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region lib/types/client/plan-definition.js
|
|
314
|
+
/** One card per invocation; a later PTC settlement retains the original card position. */
|
|
315
|
+
const planDefinition = {
|
|
316
|
+
kind: "submitted-plan",
|
|
317
|
+
target: "chat",
|
|
318
|
+
match: (event) => {
|
|
319
|
+
const plan = submittedPlan(event);
|
|
320
|
+
return plan === void 0 ? null : {
|
|
321
|
+
id: plan.callId,
|
|
322
|
+
role: event.type === "tool/ptc-dispatch" ? "update" : "start"
|
|
323
|
+
};
|
|
324
|
+
},
|
|
325
|
+
start: (_context, match) => submittedPlan(match.event),
|
|
326
|
+
update: (context) => context.state,
|
|
327
|
+
buildViewNode: (context) => {
|
|
328
|
+
const start = context.start ?? context.matches[0];
|
|
329
|
+
const data = context.state ?? (start === void 0 ? void 0 : submittedPlan(start.event));
|
|
330
|
+
if (data === void 0 || start === void 0) return null;
|
|
331
|
+
return {
|
|
332
|
+
key: context.key,
|
|
333
|
+
kind: "submitted-plan",
|
|
334
|
+
id: context.id,
|
|
335
|
+
target: "chat",
|
|
336
|
+
anchorSeq: start.event.seq,
|
|
337
|
+
location: start.location,
|
|
338
|
+
visibility: "hidden",
|
|
339
|
+
data
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region ../../typert/protocol/src/remote-error.ts
|
|
345
|
+
/**
|
|
346
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
347
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
348
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
349
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
350
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
351
|
+
*/
|
|
352
|
+
var RemoteError = class extends Error {
|
|
353
|
+
code;
|
|
354
|
+
details;
|
|
355
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
356
|
+
isDSHRemoteError = true;
|
|
357
|
+
/**
|
|
358
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
359
|
+
* @param message - human diagnostic carried across the wire.
|
|
360
|
+
* @param details - structured payload typed by the code.
|
|
361
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
362
|
+
*/
|
|
363
|
+
constructor(code, message, details, options) {
|
|
364
|
+
super(message, options);
|
|
365
|
+
this.code = code;
|
|
366
|
+
this.details = details;
|
|
367
|
+
this.name = "RemoteError";
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
/**
|
|
371
|
+
* Structurally identify a RemoteError thrown across module or realm copies of
|
|
372
|
+
* this class. Mechanism-internal: the Gateway and test assertions use it;
|
|
373
|
+
* business code receives typed failures and never needs it.
|
|
374
|
+
* @param value - a caught value.
|
|
375
|
+
* @returns the failure when the marker matches, otherwise undefined.
|
|
376
|
+
*/
|
|
377
|
+
function remoteErrorOf(value) {
|
|
378
|
+
if (typeof value === "object" && value !== null && value.isDSHRemoteError === true && typeof value.code === "string") return value;
|
|
379
|
+
}
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region ../../typert/protocol/src/index.ts
|
|
382
|
+
/**
|
|
383
|
+
* Remote decorators and explicit Gateway bindings backed by versioned
|
|
384
|
+
* descriptors carried on decorated class prototypes. Strict reflection
|
|
385
|
+
* remains a Typert compiler responsibility.
|
|
386
|
+
* @module @deepseek-ai/dsh-typert-protocol
|
|
387
|
+
*/
|
|
388
|
+
//#endregion
|
|
389
|
+
//#region lib/types/client/plan-resource.js
|
|
390
|
+
/**
|
|
391
|
+
* Bind plan reads to the generated Session Remote face.
|
|
392
|
+
* Opening a follow reads projections and may activate a prepared Session on the Host.
|
|
393
|
+
* Generated Remote streams can throw carrier failures; the provider reports failed
|
|
394
|
+
* reads as resource failure frames and preserves Remote error codes.
|
|
395
|
+
* @param remote - Existing Session history API.
|
|
396
|
+
* @returns a provider whose reads stop after finding the exact invocation.
|
|
397
|
+
*/
|
|
398
|
+
function planResourceProvider(remote) {
|
|
399
|
+
return {
|
|
400
|
+
protocol: "plan",
|
|
401
|
+
async *open(address, { signal }) {
|
|
402
|
+
const aborted = () => signal.aborted;
|
|
403
|
+
if (aborted()) return;
|
|
404
|
+
const target = parsePlanAddress(address);
|
|
405
|
+
if (target === void 0) {
|
|
406
|
+
yield {
|
|
407
|
+
ok: false,
|
|
408
|
+
error: new RemoteError("plan/invalid-address", "Invalid plan resource address.", {})
|
|
409
|
+
};
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const sessionAddress = target.session;
|
|
413
|
+
try {
|
|
414
|
+
let snapshot;
|
|
415
|
+
for await (const frame of remote.follow({ address: sessionAddress }, signal)) if (frame.type === "snapshot") {
|
|
416
|
+
snapshot = frame;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
if (aborted()) return;
|
|
420
|
+
if (snapshot === void 0) throw new RemoteError("plan/unavailable", "Session history ended before the plan could be read.", {});
|
|
421
|
+
let page = {
|
|
422
|
+
records: snapshot.records,
|
|
423
|
+
hasMore: snapshot.hasMore
|
|
424
|
+
};
|
|
425
|
+
while (true) {
|
|
426
|
+
for (const entry of page.records) {
|
|
427
|
+
const plan = submittedPlan(entry.event);
|
|
428
|
+
if (plan?.callId === target.callId) {
|
|
429
|
+
yield {
|
|
430
|
+
ok: true,
|
|
431
|
+
value: plan
|
|
432
|
+
};
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const beforeSeq = page.records[0]?.event.seq;
|
|
437
|
+
if (!page.hasMore || beforeSeq === void 0) break;
|
|
438
|
+
const next = await remote.page({
|
|
439
|
+
address: sessionAddress,
|
|
440
|
+
throughSeq: snapshot.cursor,
|
|
441
|
+
beforeSeq
|
|
442
|
+
}, signal);
|
|
443
|
+
if (aborted()) return;
|
|
444
|
+
if (!next.ok) {
|
|
445
|
+
yield next;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
page = next.value;
|
|
449
|
+
}
|
|
450
|
+
yield {
|
|
451
|
+
ok: false,
|
|
452
|
+
error: new RemoteError("plan/not-found", "The submitted plan was not found in this Session.", {})
|
|
453
|
+
};
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (!aborted()) yield {
|
|
456
|
+
ok: false,
|
|
457
|
+
error: remoteErrorOf(error) ?? new RemoteError("plan/read-failed", error instanceof Error ? error.message : String(error), {})
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
464
|
+
//#region lib/types/client/review-store.js
|
|
465
|
+
/** Session-owned memory of pending plans already opened automatically. */
|
|
466
|
+
/**
|
|
467
|
+
* Keep manual sidebar closure effective across review component remounts.
|
|
468
|
+
* @returns a transient store handle whose instances belong to Session scopes.
|
|
469
|
+
*/
|
|
470
|
+
function createPlanReviewStore() {
|
|
471
|
+
return (0, _deepseek_ai_dsh_client_store.defineStore)({
|
|
472
|
+
init: () => ({ opened: {} }),
|
|
473
|
+
actions: { markOpened: (draft, reviewKey) => {
|
|
474
|
+
draft.opened[reviewKey] = true;
|
|
475
|
+
} }
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
10
479
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-plan/src/client/PlanModeControl.module.css.mjs
|
|
11
480
|
const css = ".rS3zOq_wrap{align-items:center;gap:6px;display:inline-flex}.rS3zOq_chip{corner-shape:round;background:var(--dsw-alias-state-warn-tertiary);min-width:34px;color:var(--dsw-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}.rS3zOq_chip:hover:not(:disabled){color:var(--dsw-alias-state-warn-primary)}.rS3zOq_chip:focus-visible{outline:2px solid var(--dsw-alias-state-warn-label);outline-offset:2px}.rS3zOq_chip:disabled{opacity:.6;cursor:default}.rS3zOq_close{color:currentColor;align-items:center;display:inline-flex}.rS3zOq_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}";
|
|
12
481
|
const tagId = "@deepseek-ai/dsh-client-ui-plan/PlanModeControl.module.css";
|
|
@@ -84,6 +553,19 @@ window.__ModuleLoader__.load({
|
|
|
84
553
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
85
554
|
const zh = {
|
|
86
555
|
"chip.label": "Plan",
|
|
556
|
+
"preview.title": "计划",
|
|
557
|
+
"preview.document": "计划 · Markdown",
|
|
558
|
+
"preview.action": "打开",
|
|
559
|
+
"preview.open": "在侧边栏打开计划",
|
|
560
|
+
"preview.full": "查看全文",
|
|
561
|
+
"preview.openNamed": "打开计划:{title}",
|
|
562
|
+
"preview.loading": "正在读取计划…",
|
|
563
|
+
"preview.failed": "无法读取计划",
|
|
564
|
+
"preview.invalidAddress": "计划地址无效",
|
|
565
|
+
"preview.historyUnavailable": "无法读取会话历史",
|
|
566
|
+
"preview.notFound": "未找到这份计划",
|
|
567
|
+
"preview.unavailable": "计划预览不可用",
|
|
568
|
+
"preview.expired": "临时计划预览已失效,请从仍在等待审批的卡片重新打开。",
|
|
87
569
|
"chip.on.aria": "plan mode 已开启,按下关闭",
|
|
88
570
|
"chip.on.title": "plan mode 已开启 — 点击关闭(/plan off)",
|
|
89
571
|
"chip.off.aria": "plan mode 已关闭,按下开启",
|
|
@@ -93,6 +575,19 @@ window.__ModuleLoader__.load({
|
|
|
93
575
|
/** English dictionary, checked complete against the zh key set. */
|
|
94
576
|
const en = {
|
|
95
577
|
"chip.label": "Plan",
|
|
578
|
+
"preview.title": "Plan",
|
|
579
|
+
"preview.document": "Plan · Markdown",
|
|
580
|
+
"preview.action": "Open",
|
|
581
|
+
"preview.open": "Open plan in sidebar",
|
|
582
|
+
"preview.full": "View full plan",
|
|
583
|
+
"preview.openNamed": "Open plan: {title}",
|
|
584
|
+
"preview.loading": "Loading plan…",
|
|
585
|
+
"preview.failed": "Could not load plan",
|
|
586
|
+
"preview.invalidAddress": "Invalid plan address",
|
|
587
|
+
"preview.historyUnavailable": "Session history is unavailable",
|
|
588
|
+
"preview.notFound": "This plan was not found",
|
|
589
|
+
"preview.unavailable": "Plan preview is unavailable",
|
|
590
|
+
"preview.expired": "This temporary plan preview has expired. Reopen it from the pending review card.",
|
|
96
591
|
"chip.on.aria": "Plan mode on, press to turn off",
|
|
97
592
|
"chip.on.title": "Plan mode on — click to turn off (/plan off)",
|
|
98
593
|
"chip.off.aria": "Plan mode off, press to turn on",
|
|
@@ -103,15 +598,21 @@ window.__ModuleLoader__.load({
|
|
|
103
598
|
//#region lib/types/client/index.js
|
|
104
599
|
/** Dictionary namespace owned by this plugin. */
|
|
105
600
|
const NS = "plan";
|
|
106
|
-
/**
|
|
601
|
+
/** Services for plan controls, Conversation projection, and resource navigation. */
|
|
107
602
|
const inject = [
|
|
108
603
|
"slots",
|
|
109
604
|
"remote",
|
|
110
605
|
"remote.commands",
|
|
111
|
-
"
|
|
606
|
+
"remote.session",
|
|
607
|
+
"sessions",
|
|
608
|
+
"locale",
|
|
609
|
+
"uiConversation",
|
|
610
|
+
"resources",
|
|
611
|
+
"sidebarRight",
|
|
612
|
+
"sidebarRightTabs"
|
|
112
613
|
];
|
|
113
614
|
/**
|
|
114
|
-
*
|
|
615
|
+
* Register plan controls, permanent Chat cards, and sidebar document reading.
|
|
115
616
|
* @param ctx - client root context.
|
|
116
617
|
*/
|
|
117
618
|
function apply(ctx) {
|
|
@@ -119,6 +620,65 @@ window.__ModuleLoader__.load({
|
|
|
119
620
|
zh,
|
|
120
621
|
en
|
|
121
622
|
}), "ui-plan: dictionaries");
|
|
623
|
+
const previewId = "@deepseek-ai/dsh-client-ui-plan";
|
|
624
|
+
const t = ctx.locale.bind(NS);
|
|
625
|
+
ctx.effect(() => ctx.uiConversation.events.register(planDefinition), "ui-plan: conversation definition");
|
|
626
|
+
ctx.effect(() => ctx.resources.register(planResourceProvider(ctx.remote.session)), "ui-plan: resources");
|
|
627
|
+
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
628
|
+
id: previewId,
|
|
629
|
+
kind: "plan",
|
|
630
|
+
patterns: ["dsh-resource://plan/**", "dsh-resource://plan-review/**"],
|
|
631
|
+
priority: "builtin",
|
|
632
|
+
canOpen: (address) => parsePlanAddress(address) !== void 0 || isReviewPreviewAddress(address),
|
|
633
|
+
title: () => t("preview.title")
|
|
634
|
+
}), "ui-plan: sidebar type");
|
|
635
|
+
const open = (sessionId) => ({ openPlan: (callId) => {
|
|
636
|
+
const child = ctx.sessions.subagentAddress(sessionId);
|
|
637
|
+
const session = child === void 0 ? {
|
|
638
|
+
kind: "session",
|
|
639
|
+
sessionId
|
|
640
|
+
} : {
|
|
641
|
+
kind: "subagent",
|
|
642
|
+
...child
|
|
643
|
+
};
|
|
644
|
+
ctx.sidebarRight.openResourceIn(sessionId, planAddress({
|
|
645
|
+
session,
|
|
646
|
+
callId
|
|
647
|
+
}));
|
|
648
|
+
} });
|
|
649
|
+
const reviewWindow = randomUUID();
|
|
650
|
+
const reviewStore = createPlanReviewStore();
|
|
651
|
+
ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
|
|
652
|
+
name: "conversation.chat.turnTail",
|
|
653
|
+
id: previewId,
|
|
654
|
+
locale: NS,
|
|
655
|
+
inject: open
|
|
656
|
+
}, PlanCards));
|
|
657
|
+
ctx.slots.inject("conversation.plan-review.actions", () => ctx.slots.register({
|
|
658
|
+
name: "conversation.plan-review.actions",
|
|
659
|
+
id: previewId,
|
|
660
|
+
locale: NS,
|
|
661
|
+
store: reviewStore,
|
|
662
|
+
inject: (sessionId) => ({ openReview: (review, requestKey) => {
|
|
663
|
+
if (review.callId !== void 0) {
|
|
664
|
+
open(sessionId).openPlan(review.callId);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
ctx.sidebarRight.openResourceIn(sessionId, reviewPreviewAddress(sessionId, `${reviewWindow}:${requestKey}`), { params: { planReview: {
|
|
668
|
+
markdown: review.plan,
|
|
669
|
+
title: (0, _deepseek_ai_dsh_client_ui_primitives.extractMarkdownPlainText)(review.plan, { mode: "first-line" })
|
|
670
|
+
} } });
|
|
671
|
+
} })
|
|
672
|
+
}, PlanReviewOpen));
|
|
673
|
+
ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
674
|
+
name: "sidebar.right.pane.tab",
|
|
675
|
+
key: previewId,
|
|
676
|
+
locale: NS
|
|
677
|
+
}, PlanPreview));
|
|
678
|
+
ctx.slots.inject("sidebar.right.pane.tab.title", () => ctx.slots.register({
|
|
679
|
+
name: "sidebar.right.pane.tab.title",
|
|
680
|
+
key: previewId
|
|
681
|
+
}, PlanTitle));
|
|
122
682
|
ctx.slots.inject("conversation.input.plan", () => ctx.slots.register({
|
|
123
683
|
name: "conversation.input.plan",
|
|
124
684
|
locale: NS,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { InjectFace, PropsLocale, PropsRuntime, PropsStore } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
|
|
3
|
+
import type { createPlanReviewStore } from './review-store.ts';
|
|
4
|
+
/** Session-bound navigation for logged plans. */
|
|
5
|
+
export interface PlanOpenInjected {
|
|
6
|
+
/** Open or focus the exact submitted plan. */
|
|
7
|
+
openPlan: (callId: ToolCallId) => void;
|
|
8
|
+
}
|
|
9
|
+
/** Session-bound preview navigation for one pending review. */
|
|
10
|
+
export interface PlanReviewOpenInjected {
|
|
11
|
+
/** Open the logged plan, or the request's temporary document when no invocation exists. */
|
|
12
|
+
openReview: (review: PropsRuntime<'conversation.plan-review.actions'>['review'], requestKey: string) => void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Render the completed Turn's submitted plans in invocation order.
|
|
16
|
+
* @param props - Logged plan, localized copy, and Session-bound navigation.
|
|
17
|
+
* @returns keyboard-accessible plan cards, or null for a Turn without plans.
|
|
18
|
+
*/
|
|
19
|
+
export declare function PlanCards({ turn, useChat, openPlan, t }: PropsRuntime<'conversation.chat.turnTail'> & InjectFace<PlanOpenInjected> & PropsLocale<'plan'>): import("react").JSX.Element | null;
|
|
20
|
+
/**
|
|
21
|
+
* Open each pending plan automatically and retain a manual opener without answering it.
|
|
22
|
+
* @param props - Review identity, Session store, localized copy, and navigation.
|
|
23
|
+
* @returns an opener for either logged or temporary plan text.
|
|
24
|
+
*/
|
|
25
|
+
export declare function PlanReviewOpen({ review, requestKey, openReview, t, useStore, actions }: PropsRuntime<'conversation.plan-review.actions'> & InjectFace<PlanReviewOpenInjected> & PropsLocale<'plan'> & PropsStore<ReturnType<typeof createPlanReviewStore>>): import("react").JSX.Element;
|
|
26
|
+
//# sourceMappingURL=PlanCard.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
type PlanPreviewProps = PropsRuntime<'sidebar.right.pane.tab'> & PropsLocale<'plan'>;
|
|
3
|
+
/**
|
|
4
|
+
* Render the submitted plan with its complete Markdown and a copy action.
|
|
5
|
+
* @param props - Framework-bound tab identity, resource, and copy.
|
|
6
|
+
* @returns the plan document or a localized loading/failure state.
|
|
7
|
+
*/
|
|
8
|
+
export declare function PlanPreview({ useTabInfo, useResource, t }: PlanPreviewProps): import("react").JSX.Element;
|
|
9
|
+
/**
|
|
10
|
+
* Display a plan icon and the heading in its tab after resource recovery.
|
|
11
|
+
* @param props - Framework-bound tab identity and resource reader.
|
|
12
|
+
* @returns a decorative plan icon followed by the recovered title or initial localized label.
|
|
13
|
+
*/
|
|
14
|
+
export declare function PlanTitle({ useTabInfo, useResource }: PropsRuntime<'sidebar.right.pane.tab.title'>): import("react").JSX.Element;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=PlanPreview.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Localized plan-owned failures and unmodified external diagnostics. */
|
|
2
|
+
import type { RemoteFailure } from '@deepseek-ai/dsh-typert-protocol';
|
|
3
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
|
|
4
|
+
/**
|
|
5
|
+
* Explain a failed plan read in the current locale.
|
|
6
|
+
* @param t - Plan namespace translator.
|
|
7
|
+
* @param failure - Failure reported by the resource provider.
|
|
8
|
+
* @returns localized plan copy, or the external failure's diagnostic.
|
|
9
|
+
*/
|
|
10
|
+
export declare function planFailureLine(t: TranslateNS<'plan'>, failure: RemoteFailure): string;
|
|
11
|
+
//# sourceMappingURL=failure-line.d.ts.map
|
|
@@ -15,10 +15,10 @@ export interface PlanChipInjected {
|
|
|
15
15
|
*/
|
|
16
16
|
exitPlanMode: () => Promise<string | null>;
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** Services for plan controls, Conversation projection, and resource navigation. */
|
|
19
19
|
export declare const inject: string[];
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Register plan controls, permanent Chat cards, and sidebar document reading.
|
|
22
22
|
* @param ctx - client root context.
|
|
23
23
|
*/
|
|
24
24
|
export declare function apply(ctx: ClientContext): void;
|
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
3
|
export declare const zh: {
|
|
4
4
|
'chip.label': string;
|
|
5
|
+
'preview.title': string;
|
|
6
|
+
'preview.document': string;
|
|
7
|
+
'preview.action': string;
|
|
8
|
+
'preview.open': string;
|
|
9
|
+
'preview.full': string;
|
|
10
|
+
'preview.openNamed': string;
|
|
11
|
+
'preview.loading': string;
|
|
12
|
+
'preview.failed': string;
|
|
13
|
+
'preview.invalidAddress': string;
|
|
14
|
+
'preview.historyUnavailable': string;
|
|
15
|
+
'preview.notFound': string;
|
|
16
|
+
'preview.unavailable': string;
|
|
17
|
+
'preview.expired': string;
|
|
5
18
|
'chip.on.aria': string;
|
|
6
19
|
'chip.on.title': string;
|
|
7
20
|
'chip.off.aria': string;
|
|
@@ -13,6 +26,19 @@ export type PlanKey = keyof typeof zh;
|
|
|
13
26
|
/** English dictionary, checked complete against the zh key set. */
|
|
14
27
|
export declare const en: {
|
|
15
28
|
'chip.label': string;
|
|
29
|
+
'preview.title': string;
|
|
30
|
+
'preview.document': string;
|
|
31
|
+
'preview.action': string;
|
|
32
|
+
'preview.open': string;
|
|
33
|
+
'preview.full': string;
|
|
34
|
+
'preview.openNamed': string;
|
|
35
|
+
'preview.loading': string;
|
|
36
|
+
'preview.failed': string;
|
|
37
|
+
'preview.invalidAddress': string;
|
|
38
|
+
'preview.historyUnavailable': string;
|
|
39
|
+
'preview.notFound': string;
|
|
40
|
+
'preview.unavailable': string;
|
|
41
|
+
'preview.expired': string;
|
|
16
42
|
'chip.on.aria': string;
|
|
17
43
|
'chip.on.title': string;
|
|
18
44
|
'chip.off.aria': string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ConversationNodeDefinition } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
2
|
+
import { type SubmittedPlan } from './plan.ts';
|
|
3
|
+
declare module '@deepseek-ai/dsh-client-ui-chat/client' {
|
|
4
|
+
interface ChatNodeDataMap {
|
|
5
|
+
/** Complete plan submitted through exit_plan_mode, including rejected or dismissed reviews. */
|
|
6
|
+
'submitted-plan': SubmittedPlan;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** One card per invocation; a later PTC settlement retains the original card position. */
|
|
10
|
+
export declare const planDefinition: ConversationNodeDefinition<SubmittedPlan>;
|
|
11
|
+
//# sourceMappingURL=plan-definition.d.ts.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Read immutable plan arguments from a Session snapshot and earlier history pages. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import type { ResourceProvider } from '@deepseek-ai/dsh-client-resources/client';
|
|
4
|
+
import { type SubmittedPlan } from './plan.ts';
|
|
5
|
+
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
6
|
+
interface RemoteErrorDetailsMap {
|
|
7
|
+
/** The resource URL does not identify a plan invocation. */
|
|
8
|
+
'plan/invalid-address': Record<string, never>;
|
|
9
|
+
/** Session history ended before an opening snapshot. */
|
|
10
|
+
'plan/unavailable': Record<string, never>;
|
|
11
|
+
/** The Session has no readable plan for this invocation. */
|
|
12
|
+
'plan/not-found': Record<string, never>;
|
|
13
|
+
/** The Session history read failed. */
|
|
14
|
+
'plan/read-failed': Record<string, never>;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
18
|
+
interface ResourceProtocolMap {
|
|
19
|
+
/** Immutable Markdown from a logged exit_plan_mode invocation. */
|
|
20
|
+
plan: SubmittedPlan;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bind plan reads to the generated Session Remote face.
|
|
25
|
+
* Opening a follow reads projections and may activate a prepared Session on the Host.
|
|
26
|
+
* Generated Remote streams can throw carrier failures; the provider reports failed
|
|
27
|
+
* reads as resource failure frames and preserves Remote error codes.
|
|
28
|
+
* @param remote - Existing Session history API.
|
|
29
|
+
* @returns a provider whose reads stop after finding the exact invocation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function planResourceProvider(remote: Pick<Context['remote']['session'], 'follow' | 'page'>): ResourceProvider<'plan'>;
|
|
32
|
+
//# sourceMappingURL=plan-resource.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Plan text and resource identities derived from logged native or PTC calls. */
|
|
2
|
+
import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
|
|
3
|
+
import type { SessionAddress } from '@deepseek-ai/dsh-api-session-controller/types';
|
|
4
|
+
/** Complete Markdown and the heading displayed by a plan preview. */
|
|
5
|
+
export interface PlanDocument {
|
|
6
|
+
readonly markdown: string;
|
|
7
|
+
readonly title: string;
|
|
8
|
+
}
|
|
9
|
+
/** One submitted plan, identified by its originating tool invocation. */
|
|
10
|
+
export interface SubmittedPlan extends PlanDocument {
|
|
11
|
+
readonly callId: ToolCallId;
|
|
12
|
+
}
|
|
13
|
+
/** A saved sidebar resource names one invocation in one Session. */
|
|
14
|
+
export interface PlanAddress {
|
|
15
|
+
readonly session: SessionAddress;
|
|
16
|
+
readonly callId: ToolCallId;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read a complete plan from untrusted logged arguments.
|
|
20
|
+
* @param event - Native call or PTC dispatch event from Session history.
|
|
21
|
+
* @returns the submitted plan, or undefined for unrelated or malformed data.
|
|
22
|
+
*/
|
|
23
|
+
export declare function submittedPlan(event: {
|
|
24
|
+
readonly type: string;
|
|
25
|
+
readonly data: unknown;
|
|
26
|
+
}): SubmittedPlan | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Encode the durable identity of a plan without retaining its text in layout storage.
|
|
29
|
+
* @param target - Session and tool-call identity.
|
|
30
|
+
* @returns the plan resource address.
|
|
31
|
+
*/
|
|
32
|
+
export declare function planAddress(target: PlanAddress): string;
|
|
33
|
+
/**
|
|
34
|
+
* Validate a saved or caller-supplied plan resource address.
|
|
35
|
+
* @param address - Address submitted to the sidebar or resource provider.
|
|
36
|
+
* @returns the decoded identity, or undefined for an unsupported address.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parsePlanAddress(address: string): PlanAddress | undefined;
|
|
39
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Temporary review previews carry their document in in-memory tab navigation. */
|
|
2
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
+
import type { PlanDocument } from './plan.ts';
|
|
4
|
+
declare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {
|
|
5
|
+
interface SidebarRightResourceParamsMap {
|
|
6
|
+
/** Review text without a logged invocation; never persisted in sidebar layout. */
|
|
7
|
+
'plan-review': {
|
|
8
|
+
planReview: PlanDocument;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Name one temporary review within its browser lifetime and Session.
|
|
14
|
+
* @param sessionId - Session displaying the review.
|
|
15
|
+
* @param requestKey - Browser-unique pending request identity.
|
|
16
|
+
* @returns the address used to focus or reopen its preview.
|
|
17
|
+
*/
|
|
18
|
+
export declare function reviewPreviewAddress(sessionId: SessionId, requestKey: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Recognize temporary plan navigation without interpreting it as logged history.
|
|
21
|
+
* @param address - Saved or caller-supplied navigation address.
|
|
22
|
+
* @returns whether the address identifies a temporary review preview.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isReviewPreviewAddress(address: string): boolean;
|
|
25
|
+
//# sourceMappingURL=review-preview.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Session-owned memory of pending plans already opened automatically. */
|
|
2
|
+
import { type EngineStoreHandle } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
interface PlanReviewState {
|
|
4
|
+
opened: Partial<Record<string, true>>;
|
|
5
|
+
}
|
|
6
|
+
type PlanReviewActions = {
|
|
7
|
+
markOpened: (draft: PlanReviewState, reviewKey: string) => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Keep manual sidebar closure effective across review component remounts.
|
|
11
|
+
* @returns a transient store handle whose instances belong to Session scopes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPlanReviewStore(): EngineStoreHandle<PlanReviewState, PlanReviewActions>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=review-store.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-plan",
|
|
3
|
-
"description": "Plan
|
|
4
|
-
"version": "0.1.6-alpha.
|
|
3
|
+
"description": "Plan mode controls, persistent transcript plan cards, and sidebar Markdown previews",
|
|
4
|
+
"version": "0.1.6-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -30,7 +30,12 @@
|
|
|
30
30
|
"inject": [
|
|
31
31
|
"@deepseek-ai/dsh-api-remotes",
|
|
32
32
|
"@deepseek-ai/dsh-client-locale",
|
|
33
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-chat",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-user-questions",
|
|
37
|
+
"@deepseek-ai/dsh-client-resources",
|
|
38
|
+
"@deepseek-ai/dsh-api-session-controller"
|
|
34
39
|
],
|
|
35
40
|
"platform": "web"
|
|
36
41
|
}
|
|
@@ -42,17 +47,27 @@
|
|
|
42
47
|
"devDependencies": {
|
|
43
48
|
"@types/react": "~18.3.1",
|
|
44
49
|
"react": "^18.2.0",
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-client-
|
|
48
|
-
"@deepseek-ai/dsh-client-
|
|
49
|
-
"@deepseek-ai/dsh-client-ui-
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/
|
|
53
|
-
"@deepseek-ai/dsh-session": "^0.1.6-alpha.
|
|
54
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.
|
|
55
|
-
"@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.
|
|
50
|
+
"@deepseek-ai/dsh-client-store": "^0.1.6-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.6-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.6-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.6-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.6-alpha.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.6-alpha.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.6-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-user-questions": "^0.1.6-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-resources": "^0.1.6-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.6-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-util-crypto": "^0.1.6-alpha.2",
|
|
70
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
56
71
|
},
|
|
57
72
|
"files": [
|
|
58
73
|
"lib/index.js",
|