@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/types/index.js
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
|
|
@@ -72,26 +71,6 @@ export function resolveConfig(config) {
|
|
|
72
71
|
}
|
|
73
72
|
return { section };
|
|
74
73
|
}
|
|
75
|
-
/**
|
|
76
|
-
* Whether plan mode is active after the first `end` events. The last
|
|
77
|
-
* `plan/mode` wins; a prefix with none is inactive.
|
|
78
|
-
*
|
|
79
|
-
* @param events The session log or any prefix of it.
|
|
80
|
-
* @param end Fold `events[0, end)`; defaults to the whole log.
|
|
81
|
-
* @returns Whether plan mode is active.
|
|
82
|
-
*/
|
|
83
|
-
export function foldPlanMode(events, end = events.length) {
|
|
84
|
-
let active = false;
|
|
85
|
-
let index = 0;
|
|
86
|
-
for (const event of events) {
|
|
87
|
-
if (index >= end)
|
|
88
|
-
break;
|
|
89
|
-
index++;
|
|
90
|
-
if (event.type === 'plan/mode')
|
|
91
|
-
active = event.data.active;
|
|
92
|
-
}
|
|
93
|
-
return active;
|
|
94
|
-
}
|
|
95
74
|
const planUnitStateSchema = zod.object({
|
|
96
75
|
active: zod.boolean(),
|
|
97
76
|
wanted: zod.boolean().nullable(),
|
|
@@ -99,43 +78,55 @@ const planUnitStateSchema = zod.object({
|
|
|
99
78
|
commandId: zod.string(),
|
|
100
79
|
wanted: zod.boolean(),
|
|
101
80
|
}).strict().nullable(),
|
|
81
|
+
activeAtLastHeader: zod.boolean().nullable(),
|
|
102
82
|
}).strict();
|
|
103
83
|
/** Wire payload schema of the `plan` projection. */
|
|
104
84
|
const planProjectionSchema = zod.object({
|
|
105
85
|
active: zod.boolean(),
|
|
106
86
|
pending: zod.boolean(),
|
|
107
87
|
});
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
88
|
+
/** Projection of logged plan selections and committed mode. */
|
|
89
|
+
export const planProjectionDefinition = {
|
|
90
|
+
key: 'plan',
|
|
91
|
+
stateVersion: 3,
|
|
92
|
+
stateSchema: planUnitStateSchema,
|
|
93
|
+
init: () => ({ active: false, wanted: null, running: null, activeAtLastHeader: null }),
|
|
94
|
+
apply: (state, event) => {
|
|
95
|
+
if (event.type === 'command/run' && event.data.name === 'plan') {
|
|
96
|
+
if (event.data.args === undefined)
|
|
97
|
+
return state;
|
|
98
|
+
const wanted = event.data.args.trim() !== 'off';
|
|
99
|
+
return { ...state, running: { commandId: event.data.commandId, wanted } };
|
|
100
|
+
}
|
|
101
|
+
if (event.type === 'command/done' && event.data.commandId === state.running?.commandId) {
|
|
102
|
+
const wanted = event.data.kind === 'success' && state.running.wanted !== state.active
|
|
103
|
+
? state.running.wanted
|
|
104
|
+
: null;
|
|
105
|
+
return { ...state, wanted, running: null };
|
|
106
|
+
}
|
|
107
|
+
if (event.type === 'plan/mode') {
|
|
108
|
+
return { ...state, active: event.data.active, wanted: null };
|
|
109
|
+
}
|
|
110
|
+
if (event.type === 'request/header') {
|
|
111
|
+
return { ...state, activeAtLastHeader: state.active };
|
|
112
|
+
}
|
|
113
|
+
return state;
|
|
114
|
+
},
|
|
115
|
+
wire: {
|
|
116
|
+
viewSchema: planProjectionSchema,
|
|
117
|
+
view: (state) => {
|
|
118
|
+
const wanted = state.running?.wanted ?? state.wanted;
|
|
119
|
+
return { active: state.active, pending: wanted !== null && wanted !== state.active };
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
132
123
|
/**
|
|
133
124
|
* `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start,
|
|
134
125
|
* the `plan:policy` section, the `/plan` command, and the stable exit tool.
|
|
135
|
-
*
|
|
126
|
+
* Client carriers expose the projection's cropped `{ active, pending }` view.
|
|
136
127
|
*/
|
|
137
128
|
export class PlanModeController extends Service {
|
|
138
|
-
static inject = ['tools', 'systemPrompt'];
|
|
129
|
+
static inject = ['tools', 'systemPrompt', 'sessionProjections'];
|
|
139
130
|
/** Validated deployment-owned guidance. */
|
|
140
131
|
section;
|
|
141
132
|
/**
|
|
@@ -172,55 +163,15 @@ export class PlanModeController extends Service {
|
|
|
172
163
|
ctx.effect(() => () => { disposed = true; }, 'dsh-plan-mode: close service lifetime');
|
|
173
164
|
ctx.systemPrompt.section({
|
|
174
165
|
name: 'plan:policy',
|
|
175
|
-
order:
|
|
166
|
+
order: ctx.systemPrompt.getSectionOrder('PLAN_POLICY'),
|
|
176
167
|
text: (context) => {
|
|
177
168
|
if (context.agent === undefined)
|
|
178
169
|
return '';
|
|
179
170
|
const pending = this.pendingIntents.get(context.agent.session);
|
|
180
|
-
return (pending?.active ??
|
|
171
|
+
return (pending?.active ?? this.loggedActive(context.agent.session)) ? this.section : '';
|
|
181
172
|
},
|
|
182
173
|
});
|
|
183
|
-
|
|
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
|
|
188
|
-
// replay quantity: host restarts, other tabs, and cold reads all recover
|
|
189
|
-
// it from the log alone. The unit child activates only when a projection
|
|
190
|
-
// registry is composed (headless assemblies stay unaffected).
|
|
191
|
-
ctx.inject(['sessionProjections'], (projectionCtx) => {
|
|
192
|
-
projectionCtx.sessionProjections.register({
|
|
193
|
-
key: 'plan',
|
|
194
|
-
stateSchema: planUnitStateSchema,
|
|
195
|
-
init: () => ({ active: false, wanted: null, running: null }),
|
|
196
|
-
apply: (state, event) => {
|
|
197
|
-
if (event.type === 'command/run' && event.data.name === 'plan') {
|
|
198
|
-
if (event.data.args === undefined)
|
|
199
|
-
return state;
|
|
200
|
-
const wanted = event.data.args.trim() !== 'off';
|
|
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 };
|
|
208
|
-
}
|
|
209
|
-
if (event.type === 'plan/mode') {
|
|
210
|
-
return { ...state, active: event.data.active, wanted: null };
|
|
211
|
-
}
|
|
212
|
-
return state;
|
|
213
|
-
},
|
|
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,
|
|
222
|
-
});
|
|
223
|
-
});
|
|
174
|
+
ctx.sessionProjections.register(planProjectionDefinition);
|
|
224
175
|
// The command child activates only when a command registry is composed.
|
|
225
176
|
ctx.inject(['commands'], (commandCtx) => {
|
|
226
177
|
commandCtx.commands.register({
|
|
@@ -244,7 +195,7 @@ export class PlanModeController extends Service {
|
|
|
244
195
|
// Repeat the queued wording while an exit still awaits the
|
|
245
196
|
// next accepted pre-step; only a truly inactive session reads
|
|
246
197
|
// idempotent.
|
|
247
|
-
return
|
|
198
|
+
return this.loggedActive(agent.session)
|
|
248
199
|
? { kind: 'success', text: 'Leaving plan mode (applies from the next step).' }
|
|
249
200
|
: { kind: 'success', text: 'Plan mode is already inactive.' };
|
|
250
201
|
}
|
|
@@ -288,7 +239,7 @@ export class PlanModeController extends Service {
|
|
|
288
239
|
const agent = exec.agent;
|
|
289
240
|
if (agent === undefined)
|
|
290
241
|
throw new Error(`${EXIT_PLAN_MODE} requires a calling agent (no session to switch)`);
|
|
291
|
-
if (!
|
|
242
|
+
if (!this.loggedActive(agent.session)) {
|
|
292
243
|
throw new Error(`${EXIT_PLAN_MODE} is only available in plan mode`);
|
|
293
244
|
}
|
|
294
245
|
if (!/^#\s+\S/.test(args.plan.trim())) {
|
|
@@ -359,6 +310,25 @@ export class PlanModeController extends Service {
|
|
|
359
310
|
}),
|
|
360
311
|
}));
|
|
361
312
|
}
|
|
313
|
+
loggedActive(session) {
|
|
314
|
+
return this.planState(session).active;
|
|
315
|
+
}
|
|
316
|
+
hasOpenTurn(session) {
|
|
317
|
+
const state = this.ctx.sessionProjections.stateOf(session, 'turnBoundary');
|
|
318
|
+
if (state === undefined)
|
|
319
|
+
throw new Error('plan-mode requires the turnBoundary session projection');
|
|
320
|
+
return state.openTurnStartSeq !== null;
|
|
321
|
+
}
|
|
322
|
+
loggedActiveAtLastHeader(session) {
|
|
323
|
+
return this.planState(session).activeAtLastHeader ?? undefined;
|
|
324
|
+
}
|
|
325
|
+
/** Read the required plan projection state or fail at the first service access. */
|
|
326
|
+
planState(session) {
|
|
327
|
+
const state = this.ctx.sessionProjections.stateOf(session, 'plan');
|
|
328
|
+
if (state === undefined)
|
|
329
|
+
throw new Error('plan-mode requires the plan session projection');
|
|
330
|
+
return state;
|
|
331
|
+
}
|
|
362
332
|
/**
|
|
363
333
|
* Read the logged plan state and any selected state awaiting the next
|
|
364
334
|
* accepted in-turn pre-step.
|
|
@@ -367,7 +337,7 @@ export class PlanModeController extends Service {
|
|
|
367
337
|
* @returns Current logged state plus a pending selection, when present.
|
|
368
338
|
*/
|
|
369
339
|
get(agent) {
|
|
370
|
-
const active =
|
|
340
|
+
const active = this.loggedActive(agent.session);
|
|
371
341
|
const pending = this.pendingIntents.get(agent.session);
|
|
372
342
|
return pending === undefined ? { active } : { active, pending: pending.active };
|
|
373
343
|
}
|
|
@@ -390,16 +360,16 @@ export class PlanModeController extends Service {
|
|
|
390
360
|
set(agent, active) {
|
|
391
361
|
const session = agent.session;
|
|
392
362
|
const pending = this.pendingIntents.get(session);
|
|
393
|
-
const target = pending?.active ??
|
|
363
|
+
const target = pending?.active ?? this.loggedActive(session);
|
|
394
364
|
if (active === target)
|
|
395
365
|
return 'noop';
|
|
396
|
-
if (hasOpenTurn(session
|
|
366
|
+
if (this.hasOpenTurn(session)) {
|
|
397
367
|
this.pendingIntents.set(session, { active, narrate: true });
|
|
398
|
-
return
|
|
368
|
+
return this.loggedActive(session) === active ? 'cancelled' : 'queued';
|
|
399
369
|
}
|
|
400
370
|
// No open turn: commit now. Delete only after append succeeds so a
|
|
401
371
|
// failed durable write leaves the selection retryable, not dropped.
|
|
402
|
-
if (active ===
|
|
372
|
+
if (active === this.loggedActive(session)) {
|
|
403
373
|
this.pendingIntents.delete(session);
|
|
404
374
|
return 'cancelled';
|
|
405
375
|
}
|
|
@@ -416,7 +386,7 @@ export class PlanModeController extends Service {
|
|
|
416
386
|
if (pending === undefined)
|
|
417
387
|
return;
|
|
418
388
|
const target = pending.active;
|
|
419
|
-
if (target ===
|
|
389
|
+
if (target === this.loggedActive(session)) {
|
|
420
390
|
this.pendingIntents.delete(session);
|
|
421
391
|
return;
|
|
422
392
|
}
|
|
@@ -427,7 +397,7 @@ export class PlanModeController extends Service {
|
|
|
427
397
|
}
|
|
428
398
|
/** Build a user-switch notice when the last logged header described the other mode. */
|
|
429
399
|
narration(session, target) {
|
|
430
|
-
const told =
|
|
400
|
+
const told = this.loggedActiveAtLastHeader(session);
|
|
431
401
|
if (told === undefined || told === target)
|
|
432
402
|
return;
|
|
433
403
|
const text = target
|
package/lib/types/types.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure types of the plan domain: the ONE home of the `plan` projection-key
|
|
3
|
-
* declaration, free of this package's host-side value imports (cordis
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* declaration, free of this package's host-side value imports (cordis,
|
|
4
|
+
* dsh-tools, dsh-agent). Two namespace projections serve it — `./types` for
|
|
5
|
+
* host consumers and `./client` for client aggregates — with zero content
|
|
6
|
+
* duplication.
|
|
7
7
|
*
|
|
8
8
|
* @module @deepseek-ai/dsh-plan-mode/types
|
|
9
9
|
*/
|
|
10
|
+
import type { CommandId } from '@deepseek-ai/dsh-commands/brand';
|
|
10
11
|
/**
|
|
11
12
|
* The plan projection's wire value. `active` is the logged state in force
|
|
12
13
|
* (the last `plan/mode`, inactive before the first); `pending` is true while
|
|
@@ -19,7 +20,25 @@ export interface PlanProjection {
|
|
|
19
20
|
active: boolean;
|
|
20
21
|
pending: boolean;
|
|
21
22
|
}
|
|
23
|
+
/** Host state used to derive {@link PlanProjection}. */
|
|
24
|
+
export interface PlanUnitState {
|
|
25
|
+
/** Logged plan mode. */
|
|
26
|
+
active: boolean;
|
|
27
|
+
/** The selection's target mode; null when no selection is outstanding. */
|
|
28
|
+
wanted: boolean | null;
|
|
29
|
+
/** The latest plan command awaiting its paired settlement. */
|
|
30
|
+
running: {
|
|
31
|
+
commandId: CommandId;
|
|
32
|
+
wanted: boolean;
|
|
33
|
+
} | null;
|
|
34
|
+
/** Active state recorded by the latest `request/header`, or null. */
|
|
35
|
+
activeAtLastHeader: boolean | null;
|
|
36
|
+
}
|
|
22
37
|
declare module '@deepseek-ai/dsh-session-projection/types' {
|
|
38
|
+
interface SessionProjectionStateMap {
|
|
39
|
+
/** Host plan-mode fold state. */
|
|
40
|
+
plan: PlanUnitState;
|
|
41
|
+
}
|
|
23
42
|
interface SessionProjectionMap {
|
|
24
43
|
/** Plan collaboration state folded from the plan command lifecycle and `plan/mode` events. */
|
|
25
44
|
plan: PlanProjection;
|
package/lib/types/types.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure types of the plan domain: the ONE home of the `plan` projection-key
|
|
3
|
-
* declaration, free of this package's host-side value imports (cordis
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* declaration, free of this package's host-side value imports (cordis,
|
|
4
|
+
* dsh-tools, dsh-agent). Two namespace projections serve it — `./types` for
|
|
5
|
+
* host consumers and `./client` for client aggregates — with zero content
|
|
6
|
+
* duplication.
|
|
7
7
|
*
|
|
8
8
|
* @module @deepseek-ai/dsh-plan-mode/types
|
|
9
9
|
*/
|
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.2-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -41,16 +41,16 @@
|
|
|
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-invariants": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
50
|
-
"@deepseek-ai/dsh-
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
44
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.2-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
|
|
53
|
+
"@deepseek-ai/cordis": "^4.0.2"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"@deepseek-ai/dsh-commands": {
|
|
@@ -61,17 +61,17 @@
|
|
|
61
61
|
"zod": "^4.4.3"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-invariants": "^0.1.
|
|
69
|
-
"@deepseek-ai/dsh-
|
|
70
|
-
"@deepseek-ai/dsh-
|
|
71
|
-
"@deepseek-ai/dsh-session-projection": "^0.1.
|
|
72
|
-
"@deepseek-ai/dsh-system-prompt": "^0.1.
|
|
73
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
74
|
-
"@deepseek-ai/
|
|
75
|
-
"@deepseek-ai/
|
|
64
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-code-runtime": "^0.1.2-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-commands": "^0.1.2-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
|
|
70
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
|
|
71
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.2",
|
|
72
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.2",
|
|
74
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
75
|
+
"@deepseek-ai/dsh-user-questions": "^0.1.2-alpha.2"
|
|
76
76
|
}
|
|
77
77
|
}
|