@deepseek-ai/dsh-plan-mode 0.1.0-rc.7 → 0.1.1-rc.1
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 +4 -4
- package/README.zh.md +10 -6
- package/lib/index.js +49 -15
- package/lib/types/index.d.ts +22 -0
- package/lib/types/index.js +45 -19
- package/lib/types/types.d.ts +5 -4
- package/package.json +21 -21
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/plan/plan-mode/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 3eabe2cb3f04b434b7f908f7beca869f1022a59e
|
|
6
|
+
README.zh.md: b3548da880af5d0cacfebba4486294d9d5085626
|
package/README.md
CHANGED
|
@@ -16,13 +16,13 @@ While active, `plan:policy` renders the configured `section`. The plugin always
|
|
|
16
16
|
|
|
17
17
|
The review question declares the `plan-review` presentation intent, naming `Approve` as the label that approves it, so a capable UI presents the plan as a decision instead of a generic question; the answer the tool reads is the same either way. A dismissed review — the user closing the request to speak instead — is reported to the model as such, telling it to stay in plan mode and wait for the message; every other review failure keeps the seam's own message.
|
|
18
18
|
|
|
19
|
-
When `ctx.commands` is composed, the package registers `/plan [message]` and reserves the exact argument `off` for direct exit. Bare `/plan` selects plan mode; any other non-empty argument selects it first and is then submitted through `agent.steer()`, so it becomes the next step's ordinary logged user message under plan guidance. `/plan off` selects inactive without sending model input; it also cancels a pending entry before plan mode reaches a request.
|
|
19
|
+
When `ctx.commands` is composed, the package registers `/plan [message]` and reserves the exact argument `off` for direct exit. Bare `/plan` selects plan mode; any other non-empty argument selects it first and is then submitted through `agent.steer()`, so it becomes the next step's ordinary logged user message under plan guidance. `/plan off` selects inactive without sending model input; it also cancels a pending entry before plan mode reaches a request. The command declares `input.images`: composer image attachments ride the steered message ahead of its text block. Bare `/plan` with images steers an image-only user message, while `/plan off` with images returns a direct error before any mode change so the composer keeps them.
|
|
20
20
|
|
|
21
21
|
The Web client consumes the plugin-owned `/plan` command; other entry points may drive the same service directly without defining a second mode vocabulary.
|
|
22
22
|
|
|
23
23
|
## Session projection
|
|
24
24
|
|
|
25
|
-
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `plan` projection unit under an injected child.
|
|
25
|
+
When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `plan` projection unit under an injected child. A `command/run` record named `plan` with recorded `args` starts a candidate target (`off` → inactive, anything else → active); its paired `command/done` retains a successful selection and drops an error; `plan/mode` commits the logged state and clears the retained selection. Every other event returns the same state reference. `view` derives `{ active, pending }`, where `pending` is true only while an unsettled or successful selection differs from the logged state. This remains a pure replay quantity, so host restarts, other tabs, and cold reads recover it from the log alone, and a rejected `/plan off` with images cannot leave a pending exit. The key merges into `SessionProjectionMap` from `src/types.ts` (served to host consumers via `./types` and client aggregates via `./client`); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected.
|
|
26
26
|
|
|
27
27
|
## Configuration
|
|
28
28
|
|
|
@@ -65,11 +65,11 @@ The section is stable within plan mode, but entering or leaving changes the syst
|
|
|
65
65
|
|
|
66
66
|
#### What the model sees
|
|
67
67
|
|
|
68
|
-
`/plan`, `/plan off`, and their terminal results stay outside model history. A non-empty suffix other than the exact `off` argument becomes one
|
|
68
|
+
`/plan`, `/plan off`, and their terminal results stay outside model history. A non-empty suffix other than the exact `off` argument becomes one user message through `agent.steer()` after plan mode is selected: any admitted image attachments as leading image blocks, then the trimmed text block. Bare `/plan` with admitted images steers one user message containing only those image blocks. An active `/plan off` selection contributes the standard logged user-switch notice only when the last request header described plan mode; cancelling a pending entry contributes none because no request observed it.
|
|
69
69
|
|
|
70
70
|
#### Token effect
|
|
71
71
|
|
|
72
|
-
The optional message costs the same history tokens as submitting that
|
|
72
|
+
The optional message costs the same history tokens as submitting that content separately. Bare `/plan` without images and `/plan off` add none; bare `/plan` with images has the normal image-prompt cost. A narrated active exit adds the small retained switch notice.
|
|
73
73
|
|
|
74
74
|
#### KV Cache effect
|
|
75
75
|
|
package/README.zh.md
CHANGED
|
@@ -10,19 +10,21 @@
|
|
|
10
10
|
|
|
11
11
|
`ctx.planMode.set(agent, active)` 会在 agent 空闲时立即追加独立的 `plan/mode` 事件,因为下一个提示词之前不会运行轮内 pre-step。agent 运行时,该方法会保留待生效选择,直到下一个被接受的轮内 pre-step。返回值区分 `committed`、`queued`、表示反转的 `cancelled` 和 `noop`。`get(agent)` 返回 `{ active, pending? }`,将用于组装当前步骤的日志状态与用户的轮中选择分开。初始与续步 pre-step 都会应用待生效选择;同一步骤的请求恢复重试会复用已冻结的 assembly,并将该选择保留到下一个被接受的轮内 pre-step。当最后记录的请求头描述了另一状态时,用户选择的变更会贡献一条插件来源的 `user/message` 通知(两条提交路径皆然)。
|
|
12
12
|
|
|
13
|
+
<a id="model-and-human-interactions"></a>
|
|
14
|
+
|
|
13
15
|
## 模型与人类交互
|
|
14
16
|
|
|
15
17
|
激活时,`plan:policy` 会渲染已配置的 `section`。插件始终注册 `exit_plan_mode`,使工具 schema 在转换期间保持稳定;其 execute 路径只接受已激活的 plan mode,且只有通过 `ctx.userQuestions` 获得用户明确批准后才退出。
|
|
16
18
|
|
|
17
19
|
评审问题声明 `plan-review` 呈现意图,并指名 `Approve` 为表示批准的标签,因此有能力的 UI 会把计划呈现为一次决定而非通用问题;两种情况下该工具读到的回答完全相同。放弃审阅——用户关闭请求,转而发言——会如实报告给模型,要求它留在 plan mode 中等待那条消息;其余每一种评审失败都保留 seam 自身的消息。
|
|
18
20
|
|
|
19
|
-
组合 `ctx.commands` 时,该包会注册 `/plan [message]`,并将参数恰好为 `off` 的情况保留给直接退出。不带参数的 `/plan` 会启用 plan mode;任何其他非空参数都会先启用 plan mode,再通过 `agent.steer()` 提交,因此它会在 plan 引导下成为下一步骤的常规已记录用户消息。`/plan off` 会选择停用状态,不发送模型输入;它还可以在启用 plan mode 的待处理选择由轮内 pre-step
|
|
21
|
+
组合 `ctx.commands` 时,该包会注册 `/plan [message]`,并将参数恰好为 `off` 的情况保留给直接退出。不带参数的 `/plan` 会启用 plan mode;任何其他非空参数都会先启用 plan mode,再通过 `agent.steer()` 提交,因此它会在 plan 引导下成为下一步骤的常规已记录用户消息。`/plan off` 会选择停用状态,不发送模型输入;它还可以在启用 plan mode 的待处理选择由轮内 pre-step 追加之前将其取消。该命令声明了 `input.images`:composer 图片附件会随被 steer 的消息一起提交,位于文本块之前。不带参数的 `/plan` 若附有图片,会 steer 一条只含图片的用户消息;`/plan off` 若附有图片,会在任何模式变更前直接返回错误,composer 保留图片。
|
|
20
22
|
|
|
21
23
|
Web 客户端使用该插件提供的 `/plan` 命令;其他入口可以直接驱动同一服务,无需定义第二套 mode 词汇。
|
|
22
24
|
|
|
23
25
|
## 会话投影
|
|
24
26
|
|
|
25
|
-
当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md))时,本包会在一个注入的子插件中注册 `plan`
|
|
27
|
+
当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.zh.md))时,本包会在一个注入的子插件中注册 `plan` 投影单元。名为 `plan` 且携带已记录 `args` 的 `command/run` 记录会开始一个候选目标(`off` → 未激活,其余 → 激活);与它配对的 `command/done` 保留成功选择并丢弃错误选择;`plan/mode` 提交已记录状态并清除已保留的选择。其他任何事件都返回同一个状态引用。`view` 推导 `{ active, pending }`,其中 `pending` 仅在未结算或已成功的选择与已记录状态不同时为 true。该值仍完全由日志回放得出,因此 host 重启、其他标签页和冷读都能仅凭日志恢复它,被拒绝的带图 `/plan off` 也不会留下待退出状态。key 由 `src/types.ts` 通过声明合并加入 `SessionProjectionMap`:host 消费方经 `./types` 获取,client 聚合经 `./client` 获取。框架负责驱动该单元,载体通过历史尾页和 `session/projection` 推送帧提供其值。未挂载注册表的组合不受影响。
|
|
26
28
|
|
|
27
29
|
## 配置
|
|
28
30
|
|
|
@@ -37,7 +39,7 @@ Web 客户端使用该插件提供的 `/plan` 命令;其他入口可以直接
|
|
|
37
39
|
|
|
38
40
|
`section` 必填且非空。出现未知键时,插件会加载失败。该包不接受任意命名的 mode、工具过滤器、沙箱设置或批准策略。
|
|
39
41
|
|
|
40
|
-
设计:[plan 专用协作状态](../../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)。
|
|
42
|
+
设计:[plan 专用协作状态](../../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md)。
|
|
41
43
|
|
|
42
44
|
## 模型体验
|
|
43
45
|
|
|
@@ -65,11 +67,11 @@ You are in plan mode. Explore and design before presenting the complete plan thr
|
|
|
65
67
|
|
|
66
68
|
#### 模型所见内容
|
|
67
69
|
|
|
68
|
-
`/plan`、`/plan off` 及其终端结果留在模型历史之外。除恰好为 `off` 以外的非空后缀会在选择 plan mode 后,通过 `agent.steer()`
|
|
70
|
+
`/plan`、`/plan off` 及其终端结果留在模型历史之外。除恰好为 `off` 以外的非空后缀会在选择 plan mode 后,通过 `agent.steer()` 成为一条用户消息:任何已准入的图片附件作为前置图片块,之后是已去除首尾空白的文本块。不带参数的 `/plan` 若带有已准入图片,会 steer 一条只含这些图片块的用户消息。plan mode 已激活时,选择 `/plan off` 只会在最后一个请求头描述了 plan mode 的情况下追加标准的已记录用户切换通知;取消待生效进入不会贡献通知,因为没有请求观测到它。
|
|
69
71
|
|
|
70
72
|
#### Token 影响
|
|
71
73
|
|
|
72
|
-
可选消息的历史 token
|
|
74
|
+
可选消息的历史 token 成本与单独提交该内容相同。不带图片和参数的 `/plan` 与 `/plan off` 不增加 token;不带参数但带图的 `/plan` 产生常规图片提示词成本。一次带有切换通知的已激活状态退出会追加一条简短且会保留的通知。
|
|
73
75
|
|
|
74
76
|
#### KV Cache 影响
|
|
75
77
|
|
|
@@ -79,7 +81,7 @@ You are in plan mode. Explore and design before presenting the complete plan thr
|
|
|
79
81
|
|
|
80
82
|
#### 模型所见内容
|
|
81
83
|
|
|
82
|
-
[`exit_plan_mode` schema](../../../docs/tool-catalog.md#deepseek-aidsh-plan-mode) 在两种状态下均可用;在 plan mode 外执行会失败,而 plan mode 内经批准的评审会返回规范的 `{ approved: true }` 值,并渲染既有的确认文本。拒绝仍是携带评审反馈的失败调用,放弃审阅则是一次指明用户接手的失败调用。
|
|
84
|
+
[`exit_plan_mode` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-plan-mode) 在两种状态下均可用;在 plan mode 外执行会失败,而 plan mode 内经批准的评审会返回规范的 `{ approved: true }` 值,并渲染既有的确认文本。拒绝仍是携带评审反馈的失败调用,放弃审阅则是一次指明用户接手的失败调用。
|
|
83
85
|
|
|
84
86
|
#### Token 影响
|
|
85
87
|
|
|
@@ -89,6 +91,8 @@ You are in plan mode. Explore and design before presenting the complete plan thr
|
|
|
89
91
|
|
|
90
92
|
mode 转换不改变工具目录;plan 参数与评审结果按常规方式扩展对话。
|
|
91
93
|
|
|
94
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
95
|
+
|
|
92
96
|
## 已知限制与暂缓事项
|
|
93
97
|
|
|
94
98
|
- Plan mode 只进行引导,而不强制执行;需要强制限制的部署必须分别配置沙箱与批准控制。
|
package/lib/index.js
CHANGED
|
@@ -80,6 +80,14 @@ function foldPlanMode(events, end = events.length) {
|
|
|
80
80
|
}
|
|
81
81
|
return active;
|
|
82
82
|
}
|
|
83
|
+
const planUnitStateSchema = z.object({
|
|
84
|
+
active: z.boolean(),
|
|
85
|
+
wanted: z.boolean().nullable(),
|
|
86
|
+
running: z.object({
|
|
87
|
+
commandId: z.string(),
|
|
88
|
+
wanted: z.boolean()
|
|
89
|
+
}).strict().nullable()
|
|
90
|
+
}).strict();
|
|
83
91
|
/** Wire payload schema of the `plan` projection. */
|
|
84
92
|
const planProjectionSchema = z.object({
|
|
85
93
|
active: z.boolean(),
|
|
@@ -152,40 +160,66 @@ var PlanModeController = class extends Service {
|
|
|
152
160
|
ctx.inject(["sessionProjections"], (projectionCtx) => {
|
|
153
161
|
projectionCtx.sessionProjections.register({
|
|
154
162
|
key: "plan",
|
|
155
|
-
|
|
163
|
+
stateSchema: planUnitStateSchema,
|
|
156
164
|
init: () => ({
|
|
157
165
|
active: false,
|
|
158
|
-
wanted: null
|
|
166
|
+
wanted: null,
|
|
167
|
+
running: null
|
|
159
168
|
}),
|
|
160
169
|
apply: (state, event) => {
|
|
161
170
|
if (event.type === "command/run" && event.data.name === "plan") {
|
|
162
171
|
if (event.data.args === void 0) return state;
|
|
163
172
|
const wanted = event.data.args.trim() !== "off";
|
|
164
|
-
return
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
return {
|
|
174
|
+
...state,
|
|
175
|
+
running: {
|
|
176
|
+
commandId: event.data.commandId,
|
|
177
|
+
wanted
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (event.type === "command/done" && event.data.commandId === state.running?.commandId) {
|
|
182
|
+
const wanted = event.data.kind === "success" && state.running.wanted !== state.active ? state.running.wanted : null;
|
|
183
|
+
return {
|
|
184
|
+
...state,
|
|
185
|
+
wanted,
|
|
186
|
+
running: null
|
|
167
187
|
};
|
|
168
188
|
}
|
|
169
189
|
if (event.type === "plan/mode") return {
|
|
190
|
+
...state,
|
|
170
191
|
active: event.data.active,
|
|
171
192
|
wanted: null
|
|
172
193
|
};
|
|
173
194
|
return state;
|
|
174
195
|
},
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
196
|
+
wire: {
|
|
197
|
+
viewSchema: planProjectionSchema,
|
|
198
|
+
view: (state) => {
|
|
199
|
+
const wanted = state.running?.wanted ?? state.wanted;
|
|
200
|
+
return {
|
|
201
|
+
active: state.active,
|
|
202
|
+
pending: wanted !== null && wanted !== state.active
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
stateVersion: 2
|
|
180
207
|
});
|
|
181
208
|
});
|
|
182
209
|
ctx.inject(["commands"], (commandCtx) => {
|
|
183
210
|
commandCtx.commands.register({
|
|
184
211
|
name: "plan",
|
|
185
212
|
description: "Enter or leave plan mode",
|
|
186
|
-
input: {
|
|
187
|
-
|
|
213
|
+
input: {
|
|
214
|
+
hint: "[off|message]",
|
|
215
|
+
images: true
|
|
216
|
+
},
|
|
217
|
+
handler: ({ agent, rawInput, attachments }) => {
|
|
188
218
|
const message = rawInput.trim();
|
|
219
|
+
if (message === "off" && attachments.length > 0) return {
|
|
220
|
+
kind: "error",
|
|
221
|
+
text: "Image attachments cannot accompany /plan off."
|
|
222
|
+
};
|
|
189
223
|
if (message === "off") switch (this.set(agent, false)) {
|
|
190
224
|
case "committed": return {
|
|
191
225
|
kind: "success",
|
|
@@ -208,11 +242,11 @@ var PlanModeController = class extends Service {
|
|
|
208
242
|
};
|
|
209
243
|
}
|
|
210
244
|
const outcome = this.set(agent, true);
|
|
211
|
-
if (message !== "") agent.steer(createUserMessage({
|
|
212
|
-
content: [{
|
|
245
|
+
if (message !== "" || attachments.length > 0) agent.steer(createUserMessage({
|
|
246
|
+
content: [...attachments, ...message === "" ? [] : [{
|
|
213
247
|
type: "text",
|
|
214
248
|
text: message
|
|
215
|
-
}],
|
|
249
|
+
}]],
|
|
216
250
|
source: { kind: "user" }
|
|
217
251
|
}));
|
|
218
252
|
return {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
26
26
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
27
27
|
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
28
|
+
import type { CommandId } from '@deepseek-ai/dsh-commands';
|
|
28
29
|
export type * from './types.ts';
|
|
29
30
|
declare module '@deepseek-ai/dsh-session/types' {
|
|
30
31
|
interface SessionEventMap {
|
|
@@ -70,6 +71,27 @@ export declare function resolveConfig(config: PlanModeConfig): PlanModeConfig;
|
|
|
70
71
|
* @returns Whether plan mode is active.
|
|
71
72
|
*/
|
|
72
73
|
export declare function foldPlanMode(events: readonly SessionEvent[], end?: number): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Projection unit state: the logged mode, the latest successful `/plan`
|
|
76
|
+
* selection not yet resolved by a `plan/mode` commit, and an execution whose
|
|
77
|
+
* paired `command/done` has not settled. Plain JSON (persisted-cache
|
|
78
|
+
* precondition).
|
|
79
|
+
*/
|
|
80
|
+
interface PlanUnitState {
|
|
81
|
+
active: boolean;
|
|
82
|
+
/** The selection's target mode; null when no selection is outstanding. */
|
|
83
|
+
wanted: boolean | null;
|
|
84
|
+
/** The latest plan command awaiting its paired settlement. */
|
|
85
|
+
running: {
|
|
86
|
+
commandId: CommandId;
|
|
87
|
+
wanted: boolean;
|
|
88
|
+
} | null;
|
|
89
|
+
}
|
|
90
|
+
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
91
|
+
interface SessionProjectionStateMap {
|
|
92
|
+
plan: PlanUnitState;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
73
95
|
/**
|
|
74
96
|
* `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start,
|
|
75
97
|
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
|
package/lib/types/index.js
CHANGED
|
@@ -92,6 +92,14 @@ export function foldPlanMode(events, end = events.length) {
|
|
|
92
92
|
}
|
|
93
93
|
return active;
|
|
94
94
|
}
|
|
95
|
+
const planUnitStateSchema = zod.object({
|
|
96
|
+
active: zod.boolean(),
|
|
97
|
+
wanted: zod.boolean().nullable(),
|
|
98
|
+
running: zod.object({
|
|
99
|
+
commandId: zod.string(),
|
|
100
|
+
wanted: zod.boolean(),
|
|
101
|
+
}).strict().nullable(),
|
|
102
|
+
}).strict();
|
|
95
103
|
/** Wire payload schema of the `plan` projection. */
|
|
96
104
|
const planProjectionSchema = zod.object({
|
|
97
105
|
active: zod.boolean(),
|
|
@@ -172,37 +180,45 @@ export class PlanModeController extends Service {
|
|
|
172
180
|
return (pending?.active ?? foldPlanMode(context.agent.session.events)) ? this.section : '';
|
|
173
181
|
},
|
|
174
182
|
});
|
|
175
|
-
// The plan projection unit (session-projection RFC): a pure
|
|
176
|
-
//
|
|
177
|
-
// records the user's logged /plan selection
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
// and clears it. Pending is thereby a pure
|
|
183
|
+
// The plan projection unit (session-projection RFC): a pure event fold
|
|
184
|
+
// serving clients the whole {active, pending} value. `command/run`
|
|
185
|
+
// records the user's logged /plan selection, its paired `command/done`
|
|
186
|
+
// keeps only successful selections, and `plan/mode` records that
|
|
187
|
+
// selection and clears it. Pending is thereby a pure
|
|
181
188
|
// replay quantity: host restarts, other tabs, and cold reads all recover
|
|
182
189
|
// it from the log alone. The unit child activates only when a projection
|
|
183
190
|
// registry is composed (headless assemblies stay unaffected).
|
|
184
191
|
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
|
185
192
|
projectionCtx.sessionProjections.register({
|
|
186
193
|
key: 'plan',
|
|
187
|
-
|
|
188
|
-
init: () => ({ active: false, wanted: null }),
|
|
194
|
+
stateSchema: planUnitStateSchema,
|
|
195
|
+
init: () => ({ active: false, wanted: null, running: null }),
|
|
189
196
|
apply: (state, event) => {
|
|
190
197
|
if (event.type === 'command/run' && event.data.name === 'plan') {
|
|
191
198
|
if (event.data.args === undefined)
|
|
192
199
|
return state;
|
|
193
200
|
const wanted = event.data.args.trim() !== 'off';
|
|
194
|
-
return
|
|
201
|
+
return { ...state, running: { commandId: event.data.commandId, wanted } };
|
|
202
|
+
}
|
|
203
|
+
if (event.type === 'command/done' && event.data.commandId === state.running?.commandId) {
|
|
204
|
+
const wanted = event.data.kind === 'success' && state.running.wanted !== state.active
|
|
205
|
+
? state.running.wanted
|
|
206
|
+
: null;
|
|
207
|
+
return { ...state, wanted, running: null };
|
|
195
208
|
}
|
|
196
209
|
if (event.type === 'plan/mode') {
|
|
197
|
-
return { active: event.data.active, wanted: null };
|
|
210
|
+
return { ...state, active: event.data.active, wanted: null };
|
|
198
211
|
}
|
|
199
212
|
return state;
|
|
200
213
|
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
wire: {
|
|
215
|
+
viewSchema: planProjectionSchema,
|
|
216
|
+
view: (state) => {
|
|
217
|
+
const wanted = state.running?.wanted ?? state.wanted;
|
|
218
|
+
return { active: state.active, pending: wanted !== null && wanted !== state.active };
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
stateVersion: 2,
|
|
206
222
|
});
|
|
207
223
|
});
|
|
208
224
|
// The command child activates only when a command registry is composed.
|
|
@@ -210,9 +226,12 @@ export class PlanModeController extends Service {
|
|
|
210
226
|
commandCtx.commands.register({
|
|
211
227
|
name: 'plan',
|
|
212
228
|
description: 'Enter or leave plan mode',
|
|
213
|
-
input: { hint: '[off|message]' },
|
|
214
|
-
handler: ({ agent, rawInput }) => {
|
|
229
|
+
input: { hint: '[off|message]', images: true },
|
|
230
|
+
handler: ({ agent, rawInput, attachments }) => {
|
|
215
231
|
const message = rawInput.trim();
|
|
232
|
+
if (message === 'off' && attachments.length > 0) {
|
|
233
|
+
return { kind: 'error', text: 'Image attachments cannot accompany /plan off.' };
|
|
234
|
+
}
|
|
216
235
|
if (message === 'off') {
|
|
217
236
|
switch (this.set(agent, false)) {
|
|
218
237
|
case 'committed':
|
|
@@ -231,8 +250,15 @@ export class PlanModeController extends Service {
|
|
|
231
250
|
}
|
|
232
251
|
}
|
|
233
252
|
const outcome = this.set(agent, true);
|
|
234
|
-
if (message !== '')
|
|
235
|
-
agent.steer(createUserMessage({
|
|
253
|
+
if (message !== '' || attachments.length > 0) {
|
|
254
|
+
agent.steer(createUserMessage({
|
|
255
|
+
content: [
|
|
256
|
+
...attachments,
|
|
257
|
+
...(message === '' ? [] : [{ type: 'text', text: message }]),
|
|
258
|
+
],
|
|
259
|
+
source: { kind: 'user' },
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
236
262
|
return {
|
|
237
263
|
kind: 'success',
|
|
238
264
|
text: outcome === 'committed'
|
package/lib/types/types.d.ts
CHANGED
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
/**
|
|
11
11
|
* The plan projection's wire value. `active` is the logged state in force
|
|
12
12
|
* (the last `plan/mode`, inactive before the first); `pending` is true while
|
|
13
|
-
* a logged `/plan` selection
|
|
14
|
-
* `
|
|
15
|
-
* absence (plan-mode not composed) is the
|
|
13
|
+
* a logged `/plan` selection targets a state other than `active`, has not
|
|
14
|
+
* failed through its paired `command/done`, and no later `plan/mode` event has
|
|
15
|
+
* recorded that state. Capability absence (plan-mode not composed) is the
|
|
16
|
+
* key's absence, never a value.
|
|
16
17
|
*/
|
|
17
18
|
export interface PlanProjection {
|
|
18
19
|
active: boolean;
|
|
@@ -20,7 +21,7 @@ export interface PlanProjection {
|
|
|
20
21
|
}
|
|
21
22
|
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
22
23
|
interface SessionProjectionMap {
|
|
23
|
-
/** Plan collaboration state folded from
|
|
24
|
+
/** Plan collaboration state folded from the plan command lifecycle and `plan/mode` events. */
|
|
24
25
|
plan: PlanProjection;
|
|
25
26
|
}
|
|
26
27
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-plan-mode",
|
|
3
3
|
"description": "Logged per-agent plan mode with deployment guidance, a direct slash command, and a user-reviewed exit",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
],
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-commands": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-
|
|
49
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-user-questions": "^0.1.
|
|
44
|
+
"@deepseek-ai/dsh-agent": "^0.1.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.1-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.1-rc.1",
|
|
53
53
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
@@ -61,17 +61,17 @@
|
|
|
61
61
|
"zod": "^4.4.3"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-agent
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
71
|
-
"@deepseek-ai/dsh-
|
|
72
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
64
|
+
"@deepseek-ai/dsh-code-runtime": "^0.1.1-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-agent": "^0.1.1-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-commands": "^0.1.1-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
70
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.1-rc.1",
|
|
72
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
|
|
73
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
74
74
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
75
|
-
"@deepseek-ai/dsh-user-questions": "^0.1.
|
|
75
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.1-rc.1"
|
|
76
76
|
}
|
|
77
77
|
}
|