@deepseek-ai/dsh-plan-mode 0.1.1-rc.2 → 0.1.2-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 +127 -24
- package/README.zh.md +136 -37
- package/lib/index.js +104 -117
- package/lib/types/index.d.ts +34 -40
- package/lib/types/index.js +74 -104
- package/lib/types/types.d.ts +23 -4
- package/lib/types/types.js +4 -4
- package/package.json +23 -23
package/lib/index.js
CHANGED
|
@@ -12,12 +12,11 @@ import { UserQuestionError } from "@deepseek-ai/dsh-user-questions";
|
|
|
12
12
|
* policy enforce restrictions independently and do not read or write plan
|
|
13
13
|
* state.
|
|
14
14
|
*
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* retries reuse their assembly.
|
|
15
|
+
* The `plan` projection folds the session log, so resume and fork restore the
|
|
16
|
+
* state. User selections remain pending until the next accepted in-turn
|
|
17
|
+
* pre-step. The service includes the selected state in the proposed step
|
|
18
|
+
* assembly, then appends `plan/mode` from `agent/pre-step` only when the step
|
|
19
|
+
* is accepted. Same-step request retries reuse their assembly.
|
|
21
20
|
*
|
|
22
21
|
* The exit tool remains registered while plan mode is inactive, so entering
|
|
23
22
|
* or leaving plan mode changes only the prompt section, not the request tool
|
|
@@ -62,62 +61,81 @@ function resolveConfig(config) {
|
|
|
62
61
|
if (unknown.length > 0) throw new Error(`PlanModeConfig has unknown key(s) ${unknown.join(", ")} — config is { section }`);
|
|
63
62
|
return { section };
|
|
64
63
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
64
|
+
/** Projection of logged plan selections and committed mode. */
|
|
65
|
+
const planProjectionDefinition = {
|
|
66
|
+
key: "plan",
|
|
67
|
+
stateVersion: 3,
|
|
68
|
+
stateSchema: z.object({
|
|
69
|
+
active: z.boolean(),
|
|
70
|
+
wanted: z.boolean().nullable(),
|
|
71
|
+
running: z.object({
|
|
72
|
+
commandId: z.string(),
|
|
73
|
+
wanted: z.boolean()
|
|
74
|
+
}).strict().nullable(),
|
|
75
|
+
activeAtLastHeader: z.boolean().nullable()
|
|
76
|
+
}).strict(),
|
|
77
|
+
init: () => ({
|
|
78
|
+
active: false,
|
|
79
|
+
wanted: null,
|
|
80
|
+
running: null,
|
|
81
|
+
activeAtLastHeader: null
|
|
82
|
+
}),
|
|
83
|
+
apply: (state, event) => {
|
|
84
|
+
if (event.type === "command/run" && event.data.name === "plan") {
|
|
85
|
+
if (event.data.args === void 0) return state;
|
|
86
|
+
const wanted = event.data.args.trim() !== "off";
|
|
87
|
+
return {
|
|
88
|
+
...state,
|
|
89
|
+
running: {
|
|
90
|
+
commandId: event.data.commandId,
|
|
91
|
+
wanted
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (event.type === "command/done" && event.data.commandId === state.running?.commandId) {
|
|
96
|
+
const wanted = event.data.kind === "success" && state.running.wanted !== state.active ? state.running.wanted : null;
|
|
97
|
+
return {
|
|
98
|
+
...state,
|
|
99
|
+
wanted,
|
|
100
|
+
running: null
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (event.type === "plan/mode") return {
|
|
104
|
+
...state,
|
|
105
|
+
active: event.data.active,
|
|
106
|
+
wanted: null
|
|
107
|
+
};
|
|
108
|
+
if (event.type === "request/header") return {
|
|
109
|
+
...state,
|
|
110
|
+
activeAtLastHeader: state.active
|
|
111
|
+
};
|
|
112
|
+
return state;
|
|
113
|
+
},
|
|
114
|
+
wire: {
|
|
115
|
+
viewSchema: z.object({
|
|
116
|
+
active: z.boolean(),
|
|
117
|
+
pending: z.boolean()
|
|
118
|
+
}),
|
|
119
|
+
view: (state) => {
|
|
120
|
+
const wanted = state.running?.wanted ?? state.wanted;
|
|
121
|
+
return {
|
|
122
|
+
active: state.active,
|
|
123
|
+
pending: wanted !== null && wanted !== state.active
|
|
124
|
+
};
|
|
125
|
+
}
|
|
110
126
|
}
|
|
111
|
-
|
|
112
|
-
return foldPlanMode(events, lastHeader + 1);
|
|
113
|
-
}
|
|
127
|
+
};
|
|
114
128
|
/**
|
|
115
129
|
* `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start,
|
|
116
130
|
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
|
|
117
|
-
*
|
|
131
|
+
* Client carriers expose the projection's cropped `{ active, pending }` view.
|
|
118
132
|
*/
|
|
119
133
|
var PlanModeController = class extends Service {
|
|
120
|
-
static inject = [
|
|
134
|
+
static inject = [
|
|
135
|
+
"tools",
|
|
136
|
+
"systemPrompt",
|
|
137
|
+
"sessionProjections"
|
|
138
|
+
];
|
|
121
139
|
/** Validated deployment-owned guidance. */
|
|
122
140
|
section;
|
|
123
141
|
/**
|
|
@@ -151,61 +169,13 @@ var PlanModeController = class extends Service {
|
|
|
151
169
|
}, "dsh-plan-mode: close service lifetime");
|
|
152
170
|
ctx.systemPrompt.section({
|
|
153
171
|
name: "plan:policy",
|
|
154
|
-
order:
|
|
172
|
+
order: ctx.systemPrompt.getSectionOrder("PLAN_POLICY"),
|
|
155
173
|
text: (context) => {
|
|
156
174
|
if (context.agent === void 0) return "";
|
|
157
|
-
return this.pendingIntents.get(context.agent.session)?.active ??
|
|
175
|
+
return this.pendingIntents.get(context.agent.session)?.active ?? this.loggedActive(context.agent.session) ? this.section : "";
|
|
158
176
|
}
|
|
159
177
|
});
|
|
160
|
-
ctx.
|
|
161
|
-
projectionCtx.sessionProjections.register({
|
|
162
|
-
key: "plan",
|
|
163
|
-
stateSchema: planUnitStateSchema,
|
|
164
|
-
init: () => ({
|
|
165
|
-
active: false,
|
|
166
|
-
wanted: null,
|
|
167
|
-
running: null
|
|
168
|
-
}),
|
|
169
|
-
apply: (state, event) => {
|
|
170
|
-
if (event.type === "command/run" && event.data.name === "plan") {
|
|
171
|
-
if (event.data.args === void 0) return state;
|
|
172
|
-
const wanted = event.data.args.trim() !== "off";
|
|
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
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
if (event.type === "plan/mode") return {
|
|
190
|
-
...state,
|
|
191
|
-
active: event.data.active,
|
|
192
|
-
wanted: null
|
|
193
|
-
};
|
|
194
|
-
return state;
|
|
195
|
-
},
|
|
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
|
|
207
|
-
});
|
|
208
|
-
});
|
|
178
|
+
ctx.sessionProjections.register(planProjectionDefinition);
|
|
209
179
|
ctx.inject(["commands"], (commandCtx) => {
|
|
210
180
|
commandCtx.commands.register({
|
|
211
181
|
name: "plan",
|
|
@@ -233,7 +203,7 @@ var PlanModeController = class extends Service {
|
|
|
233
203
|
kind: "success",
|
|
234
204
|
text: "Plan mode entry cancelled."
|
|
235
205
|
};
|
|
236
|
-
case "noop": return
|
|
206
|
+
case "noop": return this.loggedActive(agent.session) ? {
|
|
237
207
|
kind: "success",
|
|
238
208
|
text: "Leaving plan mode (applies from the next step)."
|
|
239
209
|
} : {
|
|
@@ -282,7 +252,7 @@ var PlanModeController = class extends Service {
|
|
|
282
252
|
execute: async (args, exec) => {
|
|
283
253
|
const agent = exec.agent;
|
|
284
254
|
if (agent === void 0) throw new Error(`${EXIT_PLAN_MODE} requires a calling agent (no session to switch)`);
|
|
285
|
-
if (!
|
|
255
|
+
if (!this.loggedActive(agent.session)) throw new Error(`${EXIT_PLAN_MODE} is only available in plan mode`);
|
|
286
256
|
if (!/^#\s+\S/.test(args.plan.trim())) throw new Error(`${EXIT_PLAN_MODE} requires a non-empty markdown plan starting with a # heading`);
|
|
287
257
|
const interaction = ctx.get("userQuestions");
|
|
288
258
|
if (interaction === void 0) throw new Error("no user-questions channel is available to review the plan; ask the user to switch the session mode instead");
|
|
@@ -339,6 +309,23 @@ var PlanModeController = class extends Service {
|
|
|
339
309
|
})
|
|
340
310
|
}));
|
|
341
311
|
}
|
|
312
|
+
loggedActive(session) {
|
|
313
|
+
return this.planState(session).active;
|
|
314
|
+
}
|
|
315
|
+
hasOpenTurn(session) {
|
|
316
|
+
const state = this.ctx.sessionProjections.stateOf(session, "turnBoundary");
|
|
317
|
+
if (state === void 0) throw new Error("plan-mode requires the turnBoundary session projection");
|
|
318
|
+
return state.openTurnStartSeq !== null;
|
|
319
|
+
}
|
|
320
|
+
loggedActiveAtLastHeader(session) {
|
|
321
|
+
return this.planState(session).activeAtLastHeader ?? void 0;
|
|
322
|
+
}
|
|
323
|
+
/** Read the required plan projection state or fail at the first service access. */
|
|
324
|
+
planState(session) {
|
|
325
|
+
const state = this.ctx.sessionProjections.stateOf(session, "plan");
|
|
326
|
+
if (state === void 0) throw new Error("plan-mode requires the plan session projection");
|
|
327
|
+
return state;
|
|
328
|
+
}
|
|
342
329
|
/**
|
|
343
330
|
* Read the logged plan state and any selected state awaiting the next
|
|
344
331
|
* accepted in-turn pre-step.
|
|
@@ -347,7 +334,7 @@ var PlanModeController = class extends Service {
|
|
|
347
334
|
* @returns Current logged state plus a pending selection, when present.
|
|
348
335
|
*/
|
|
349
336
|
get(agent) {
|
|
350
|
-
const active =
|
|
337
|
+
const active = this.loggedActive(agent.session);
|
|
351
338
|
const pending = this.pendingIntents.get(agent.session);
|
|
352
339
|
return pending === void 0 ? { active } : {
|
|
353
340
|
active,
|
|
@@ -372,15 +359,15 @@ var PlanModeController = class extends Service {
|
|
|
372
359
|
*/
|
|
373
360
|
set(agent, active) {
|
|
374
361
|
const session = agent.session;
|
|
375
|
-
if (active === (this.pendingIntents.get(session)?.active ??
|
|
376
|
-
if (hasOpenTurn(session
|
|
362
|
+
if (active === (this.pendingIntents.get(session)?.active ?? this.loggedActive(session))) return "noop";
|
|
363
|
+
if (this.hasOpenTurn(session)) {
|
|
377
364
|
this.pendingIntents.set(session, {
|
|
378
365
|
active,
|
|
379
366
|
narrate: true
|
|
380
367
|
});
|
|
381
|
-
return
|
|
368
|
+
return this.loggedActive(session) === active ? "cancelled" : "queued";
|
|
382
369
|
}
|
|
383
|
-
if (active ===
|
|
370
|
+
if (active === this.loggedActive(session)) {
|
|
384
371
|
this.pendingIntents.delete(session);
|
|
385
372
|
return "cancelled";
|
|
386
373
|
}
|
|
@@ -395,7 +382,7 @@ var PlanModeController = class extends Service {
|
|
|
395
382
|
const pending = this.pendingIntents.get(session);
|
|
396
383
|
if (pending === void 0) return;
|
|
397
384
|
const target = pending.active;
|
|
398
|
-
if (target ===
|
|
385
|
+
if (target === this.loggedActive(session)) {
|
|
399
386
|
this.pendingIntents.delete(session);
|
|
400
387
|
return;
|
|
401
388
|
}
|
|
@@ -404,7 +391,7 @@ var PlanModeController = class extends Service {
|
|
|
404
391
|
}
|
|
405
392
|
/** Build a user-switch notice when the last logged header described the other mode. */
|
|
406
393
|
narration(session, target) {
|
|
407
|
-
const told =
|
|
394
|
+
const told = this.loggedActiveAtLastHeader(session);
|
|
408
395
|
if (told === void 0 || told === target) return;
|
|
409
396
|
const text = target ? "The user switched this session to plan mode." : "The user switched this session back to the default mode.";
|
|
410
397
|
return createUserMessage({
|
|
@@ -422,4 +409,4 @@ var PlanModeController = class extends Service {
|
|
|
422
409
|
}
|
|
423
410
|
};
|
|
424
411
|
//#endregion
|
|
425
|
-
export { EXIT_PLAN_MODE, PlanModeController, PlanModeController as default,
|
|
412
|
+
export { EXIT_PLAN_MODE, PlanModeController, PlanModeController as default, planProjectionDefinition, resolveConfig };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
* policy enforce restrictions independently and do not read or write plan
|
|
7
7
|
* state.
|
|
8
8
|
*
|
|
9
|
-
* The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* retries reuse their assembly.
|
|
9
|
+
* The `plan` projection folds the session log, so resume and fork restore the
|
|
10
|
+
* state. User selections remain pending until the next accepted in-turn
|
|
11
|
+
* pre-step. The service includes the selected state in the proposed step
|
|
12
|
+
* assembly, then appends `plan/mode` from `agent/pre-step` only when the step
|
|
13
|
+
* is accepted. Same-step request retries reuse their assembly.
|
|
15
14
|
*
|
|
16
15
|
* The exit tool remains registered while plan mode is inactive, so entering
|
|
17
16
|
* or leaving plan mode changes only the prompt section, not the request tool
|
|
@@ -23,16 +22,16 @@
|
|
|
23
22
|
* @module @deepseek-ai/dsh-plan-mode
|
|
24
23
|
*/
|
|
25
24
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
25
|
+
import { z as zod } from 'zod';
|
|
26
26
|
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
27
|
-
import type {
|
|
28
|
-
import type { CommandId } from '@deepseek-ai/dsh-commands';
|
|
27
|
+
import type { PlanProjection, PlanUnitState } from './types.ts';
|
|
29
28
|
export type * from './types.ts';
|
|
30
29
|
declare module '@deepseek-ai/dsh-session/types' {
|
|
31
30
|
interface SessionEventMap {
|
|
32
31
|
/**
|
|
33
32
|
* Whether plan mode is in force from this point on: log-only, non-surface,
|
|
34
33
|
* whole-value replace. The last `plan/mode` wins; a log with none folds to
|
|
35
|
-
* inactive through
|
|
34
|
+
* inactive through the projection unit's fold.
|
|
36
35
|
*/
|
|
37
36
|
'plan/mode': {
|
|
38
37
|
active: boolean;
|
|
@@ -62,40 +61,30 @@ export interface PlanModeConfig {
|
|
|
62
61
|
* @returns A detached validated config.
|
|
63
62
|
*/
|
|
64
63
|
export declare function resolveConfig(config: PlanModeConfig): PlanModeConfig;
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}
|
|
64
|
+
/** Projection of logged plan selections and committed mode. */
|
|
65
|
+
export declare const planProjectionDefinition: {
|
|
66
|
+
key: "plan";
|
|
67
|
+
stateVersion: number;
|
|
68
|
+
stateSchema: zod.ZodType<PlanUnitState, unknown, zod.core.$ZodTypeInternals<PlanUnitState, unknown>>;
|
|
69
|
+
init: () => {
|
|
70
|
+
active: false;
|
|
71
|
+
wanted: null;
|
|
72
|
+
running: null;
|
|
73
|
+
activeAtLastHeader: null;
|
|
74
|
+
};
|
|
75
|
+
apply: (state: NoInfer<PlanUnitState>, event: import("@deepseek-ai/dsh-session").SessionEvent) => PlanUnitState;
|
|
76
|
+
wire: {
|
|
77
|
+
viewSchema: zod.ZodType<PlanProjection, unknown, zod.core.$ZodTypeInternals<PlanProjection, unknown>>;
|
|
78
|
+
view: (state: NoInfer<PlanUnitState>) => {
|
|
79
|
+
active: boolean;
|
|
80
|
+
pending: boolean;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
};
|
|
95
84
|
/**
|
|
96
85
|
* `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start,
|
|
97
86
|
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
|
|
98
|
-
*
|
|
87
|
+
* Client carriers expose the projection's cropped `{ active, pending }` view.
|
|
99
88
|
*/
|
|
100
89
|
export declare class PlanModeController extends Service {
|
|
101
90
|
static inject: string[];
|
|
@@ -108,6 +97,11 @@ export declare class PlanModeController extends Service {
|
|
|
108
97
|
*/
|
|
109
98
|
private readonly pendingIntents;
|
|
110
99
|
constructor(ctx: Context, config?: PlanModeConfig);
|
|
100
|
+
private loggedActive;
|
|
101
|
+
private hasOpenTurn;
|
|
102
|
+
private loggedActiveAtLastHeader;
|
|
103
|
+
/** Read the required plan projection state or fail at the first service access. */
|
|
104
|
+
private planState;
|
|
111
105
|
/**
|
|
112
106
|
* Read the logged plan state and any selected state awaiting the next
|
|
113
107
|
* accepted in-turn pre-step.
|