@onefl666/dsh-great-plan 0.5.0 → 0.6.0
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.md +10 -7
- package/README.zh-CN.md +10 -7
- package/{client.actions.js → lib/client.actions.js} +58 -126
- package/{client.js → lib/client.js} +0 -12
- package/{client.panel.js → lib/client.panel.js} +189 -337
- package/{client.preset.js → lib/client.preset.js} +7 -19
- package/{client.settings.js → lib/client.settings.js} +3 -61
- package/{client.ui.js → lib/client.ui.js} +1 -7
- package/{host → lib/host}/apply.js +2 -1
- package/{host → lib/host}/plan-compaction.js +49 -10
- package/lib/host/plan-evidence.js +195 -0
- package/{host → lib/host}/plan-range.js +5 -2
- package/package.json +8 -7
- package/host/plan-evidence.js +0 -252
- /package/{client.model.js → lib/client.model.js} +0 -0
- /package/{host → lib/host}/config.js +0 -0
- /package/{index.js → lib/index.js} +0 -0
|
@@ -6,18 +6,11 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
const h = React.createElement;
|
|
7
7
|
const ui = require('@onefl666/dsh-great-plan/client.ui.js');
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
standard: 'preset.standard',
|
|
11
|
-
ptc: 'preset.ptc',
|
|
12
|
-
minimal: 'preset.minimal',
|
|
13
|
-
cordis: 'preset.cordis',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const BUILTIN_DESCRIPTIONS = {
|
|
17
|
-
standard: 'preset.standardDescription',
|
|
18
|
-
ptc: 'preset.ptcDescription',
|
|
19
|
-
minimal: 'preset.minimalDescription',
|
|
20
|
-
cordis: 'preset.cordisDescription',
|
|
9
|
+
const BUILTINS = {
|
|
10
|
+
standard: { name: 'preset.standard', description: 'preset.standardDescription' },
|
|
11
|
+
ptc: { name: 'preset.ptc', description: 'preset.ptcDescription' },
|
|
12
|
+
minimal: { name: 'preset.minimal', description: 'preset.minimalDescription' },
|
|
13
|
+
cordis: { name: 'preset.cordis', description: 'preset.cordisDescription' },
|
|
21
14
|
};
|
|
22
15
|
|
|
23
16
|
function createPresetStore(ctx) {
|
|
@@ -64,19 +57,15 @@ window.__ModuleLoader__.load({
|
|
|
64
57
|
return { store, load };
|
|
65
58
|
}
|
|
66
59
|
|
|
67
|
-
function usePresetState(store) {
|
|
68
|
-
return ui.useStore(store);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
60
|
function presetDisplay(option, t) {
|
|
72
61
|
if (option?.name !== undefined && option.name !== '') return option.name;
|
|
73
|
-
const key = option === undefined ? undefined :
|
|
62
|
+
const key = option === undefined ? undefined : BUILTINS[option.id]?.name;
|
|
74
63
|
return key === undefined ? (option?.id ?? '') : t(key);
|
|
75
64
|
}
|
|
76
65
|
|
|
77
66
|
function presetDescription(option, t) {
|
|
78
67
|
if (option?.description !== undefined && option.description !== '') return option.description;
|
|
79
|
-
const key = option === undefined ? undefined :
|
|
68
|
+
const key = option === undefined ? undefined : BUILTINS[option.id]?.description;
|
|
80
69
|
return key === undefined ? undefined : t(key);
|
|
81
70
|
}
|
|
82
71
|
|
|
@@ -117,7 +106,6 @@ window.__ModuleLoader__.load({
|
|
|
117
106
|
|
|
118
107
|
return {
|
|
119
108
|
createPresetStore,
|
|
120
|
-
usePresetState,
|
|
121
109
|
presetDisplay,
|
|
122
110
|
presetDescription,
|
|
123
111
|
defaultPresetId,
|
|
@@ -14,7 +14,6 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
minimalContext: 'includeMinimalRuntimeContext',
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
/** Settings rows, in display order. */
|
|
18
17
|
const ROWS = [
|
|
19
18
|
{ field: FIELDS.brainstorming, title: 'settings.brainstorm.title', description: 'settings.brainstorm.description' },
|
|
20
19
|
{ field: FIELDS.budget, title: 'settings.budget.title', description: 'settings.budget.description' },
|
|
@@ -27,26 +26,6 @@ window.__ModuleLoader__.load({
|
|
|
27
26
|
return snapshot?.value?.[field] !== false;
|
|
28
27
|
}
|
|
29
28
|
|
|
30
|
-
/** Read the brainstorming switch value from a configuration form snapshot. */
|
|
31
|
-
function readBrainstormingValue(snapshot) {
|
|
32
|
-
return readSwitchValue(snapshot, FIELDS.brainstorming);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Read the unlimited-budget switch value from a configuration form snapshot. */
|
|
36
|
-
function readBudgetValue(snapshot) {
|
|
37
|
-
return readSwitchValue(snapshot, FIELDS.budget);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Read the original-instruction switch value from a configuration form snapshot. */
|
|
41
|
-
function readOriginalInstructionsValue(snapshot) {
|
|
42
|
-
return readSwitchValue(snapshot, FIELDS.originalInstructions);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Read the minimal-runtime-context switch value from a configuration form snapshot. */
|
|
46
|
-
function readMinimalContextValue(snapshot) {
|
|
47
|
-
return readSwitchValue(snapshot, FIELDS.minimalContext);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
29
|
/** Read the shared settings form snapshot, or undefined when the capability is unavailable. */
|
|
51
30
|
function readFormSnapshot(configForms) {
|
|
52
31
|
if (configForms === undefined || configForms === null || typeof configForms.get !== 'function') return undefined;
|
|
@@ -55,36 +34,6 @@ window.__ModuleLoader__.load({
|
|
|
55
34
|
return form.getSnapshot();
|
|
56
35
|
}
|
|
57
36
|
|
|
58
|
-
/**
|
|
59
|
-
* Read one switch through the shared configuration mirror.
|
|
60
|
-
*
|
|
61
|
-
* A missing settings capability, an unreadable form, or an unready snapshot
|
|
62
|
-
* keeps the Host default (enabled) so execution never fails because of
|
|
63
|
-
* configuration.
|
|
64
|
-
*/
|
|
65
|
-
function readSwitch(configForms, field) {
|
|
66
|
-
try {
|
|
67
|
-
return readSwitchValue(readFormSnapshot(configForms), field);
|
|
68
|
-
} catch {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Read the unlimited-budget switch; defaults to enabled. */
|
|
74
|
-
function readBudgetSwitch(configForms) {
|
|
75
|
-
return readSwitch(configForms, FIELDS.budget);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Read the original-instruction switch; defaults to enabled. */
|
|
79
|
-
function readOriginalInstructionsSwitch(configForms) {
|
|
80
|
-
return readSwitch(configForms, FIELDS.originalInstructions);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Read the minimal-runtime-context switch; defaults to enabled. */
|
|
84
|
-
function readMinimalContextSwitch(configForms) {
|
|
85
|
-
return readSwitch(configForms, FIELDS.minimalContext);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
37
|
/**
|
|
89
38
|
* Read every fresh-session prompt switch from one snapshot.
|
|
90
39
|
*
|
|
@@ -96,9 +45,9 @@ window.__ModuleLoader__.load({
|
|
|
96
45
|
try {
|
|
97
46
|
const snapshot = readFormSnapshot(configForms);
|
|
98
47
|
return {
|
|
99
|
-
unlimitedBudget:
|
|
100
|
-
includeOriginalInstructions:
|
|
101
|
-
includeMinimalContext:
|
|
48
|
+
unlimitedBudget: readSwitchValue(snapshot, FIELDS.budget),
|
|
49
|
+
includeOriginalInstructions: readSwitchValue(snapshot, FIELDS.originalInstructions),
|
|
50
|
+
includeMinimalContext: readSwitchValue(snapshot, FIELDS.minimalContext),
|
|
102
51
|
};
|
|
103
52
|
} catch {
|
|
104
53
|
return { unlimitedBudget: true, includeOriginalInstructions: true, includeMinimalContext: true };
|
|
@@ -211,13 +160,6 @@ window.__ModuleLoader__.load({
|
|
|
211
160
|
FIELDS,
|
|
212
161
|
ROWS,
|
|
213
162
|
readSwitchValue,
|
|
214
|
-
readBrainstormingValue,
|
|
215
|
-
readBudgetValue,
|
|
216
|
-
readOriginalInstructionsValue,
|
|
217
|
-
readMinimalContextValue,
|
|
218
|
-
readBudgetSwitch,
|
|
219
|
-
readOriginalInstructionsSwitch,
|
|
220
|
-
readMinimalContextSwitch,
|
|
221
163
|
readFreshPromptSwitches,
|
|
222
164
|
GreatPlanSection,
|
|
223
165
|
GreatPlanForm,
|
|
@@ -323,7 +323,6 @@ window.__ModuleLoader__.load({
|
|
|
323
323
|
},
|
|
324
324
|
h('input', {
|
|
325
325
|
type: 'checkbox',
|
|
326
|
-
className: 'gp-check-input',
|
|
327
326
|
checked: checked === true,
|
|
328
327
|
disabled,
|
|
329
328
|
onChange: (event) => {
|
|
@@ -331,7 +330,7 @@ window.__ModuleLoader__.load({
|
|
|
331
330
|
},
|
|
332
331
|
}),
|
|
333
332
|
h('span', { className: 'gp-check-copy' },
|
|
334
|
-
h('span',
|
|
333
|
+
h('span', null, label),
|
|
335
334
|
hint === undefined || hint === null ? null : h('span', { className: 'gp-check-hint' }, hint),
|
|
336
335
|
),
|
|
337
336
|
);
|
|
@@ -406,7 +405,6 @@ window.__ModuleLoader__.load({
|
|
|
406
405
|
}, [open, anchorRef, panelRef, setPosition]);
|
|
407
406
|
}
|
|
408
407
|
|
|
409
|
-
/** Collect the panel's focusable elements in DOM order. */
|
|
410
408
|
function focusableIn(panel) {
|
|
411
409
|
if (panel === null || panel === undefined || typeof panel.querySelectorAll !== 'function') return [];
|
|
412
410
|
return [...panel.querySelectorAll('button:not([disabled]),input:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])')]
|
|
@@ -504,15 +502,12 @@ window.__ModuleLoader__.load({
|
|
|
504
502
|
useStore,
|
|
505
503
|
createStore,
|
|
506
504
|
IconSliders,
|
|
507
|
-
IconChevronRight,
|
|
508
505
|
IconChevronDown,
|
|
509
506
|
IconChevronLeft,
|
|
510
507
|
IconClose,
|
|
511
|
-
IconCheck,
|
|
512
508
|
IconPlay,
|
|
513
509
|
IconCompress,
|
|
514
510
|
IconNewSession,
|
|
515
|
-
IconWarning,
|
|
516
511
|
IconButton,
|
|
517
512
|
Button,
|
|
518
513
|
ActionRow,
|
|
@@ -526,7 +521,6 @@ window.__ModuleLoader__.load({
|
|
|
526
521
|
WarningText,
|
|
527
522
|
WarningBox,
|
|
528
523
|
Loading,
|
|
529
|
-
focusableIn,
|
|
530
524
|
handleDialogKeyDown,
|
|
531
525
|
Dialog,
|
|
532
526
|
};
|
|
@@ -5,7 +5,8 @@ export function apply(ctx, config) {
|
|
|
5
5
|
ctx.inject(['commands'], (scope) => {
|
|
6
6
|
return scope.commands.register(createCompactCommand({
|
|
7
7
|
getProjections: () => ctx.get('sessionProjections'),
|
|
8
|
-
getCompaction: () => ctx.get('compaction'),
|
|
8
|
+
getCompaction: (agent) => ctx.get('compaction'),
|
|
9
|
+
getAgentPresets: () => ctx.get('agentPresets'),
|
|
9
10
|
getBalancedAfter: async () => (await import('@deepseek-ai/dsh-compaction')).toolPairingBalancedAfter,
|
|
10
11
|
}));
|
|
11
12
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { selectPlanCompactionRange } from './plan-range.js';
|
|
1
|
+
import { EXIT_PLAN_MODE_TOOL, selectPlanCompactionRange } from './plan-range.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export
|
|
3
|
+
// The canonical name lives in plan-range.js; re-exported for existing importers.
|
|
4
|
+
export { EXIT_PLAN_MODE_TOOL };
|
|
5
5
|
|
|
6
6
|
/** The command accepts no arguments; this is its user-visible usage line. */
|
|
7
7
|
export const USAGE = 'Usage: /great-plan-compact (no arguments)';
|
|
@@ -38,20 +38,59 @@ function messageOf(error) {
|
|
|
38
38
|
return error instanceof Error ? error.message : String(error);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the composed compaction service for one agent.
|
|
43
|
+
*
|
|
44
|
+
* Presets isolate their mounted services, so the agent's preset group owns its
|
|
45
|
+
* `compaction` instance while the host-plane read only sees globally mounted
|
|
46
|
+
* compositions. The agent-scoped context is the middle step; the preset
|
|
47
|
+
* registry is the authoritative source when it exposes the service.
|
|
48
|
+
*
|
|
49
|
+
* @param {object | undefined} agent the agent executing the command.
|
|
50
|
+
* @param {object} [accessors] service readers supplied by the composition.
|
|
51
|
+
* @param {() => object | undefined} [accessors.getAgentPresets] host-plane preset registry reader.
|
|
52
|
+
* @param {(agent: object | undefined) => object | undefined} [accessors.getCompaction] host-plane compaction reader.
|
|
53
|
+
* @returns {object | undefined} the first service that exposes `compactRegion`.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveCompactionService(agent, { getAgentPresets, getCompaction } = {}) {
|
|
56
|
+
if (agent !== undefined && agent !== null) {
|
|
57
|
+
// 1. The service mounted in the agent's preset group.
|
|
58
|
+
try {
|
|
59
|
+
const scoped = typeof getAgentPresets === 'function'
|
|
60
|
+
? getAgentPresets()?.serviceFor?.(agent, 'compaction')
|
|
61
|
+
: undefined;
|
|
62
|
+
if (scoped?.compactRegion !== undefined) return scoped;
|
|
63
|
+
} catch {
|
|
64
|
+
// An absent or unreadable preset registry falls through.
|
|
65
|
+
}
|
|
66
|
+
// 2. The service mounted in the agent's scoped context.
|
|
67
|
+
try {
|
|
68
|
+
const scoped = agent?.ctx?.get?.('compaction');
|
|
69
|
+
if (scoped?.compactRegion !== undefined) return scoped;
|
|
70
|
+
} catch {
|
|
71
|
+
// An unreadable scoped context falls through.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 3. The host-plane service; a throwing read stays visible to the caller.
|
|
75
|
+
const fallback = typeof getCompaction === 'function' ? getCompaction(agent) : undefined;
|
|
76
|
+
return fallback?.compactRegion !== undefined ? fallback : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createCompactCommand({ getProjections, getCompaction, getBalancedAfter, getAgentPresets }) {
|
|
42
80
|
return {
|
|
43
81
|
name: 'great-plan-compact',
|
|
44
82
|
description: 'Compact older plan history before executing (used by Great Plan).',
|
|
45
83
|
async handler(invocation) {
|
|
46
|
-
const
|
|
84
|
+
const args = invocation ?? {};
|
|
85
|
+
const rawInput = typeof args.rawInput === 'string' ? args.rawInput.trim() : '';
|
|
47
86
|
if (rawInput !== '') return { kind: 'error', text: USAGE };
|
|
48
|
-
const { agent, signal } =
|
|
87
|
+
const { agent, signal } = args;
|
|
49
88
|
|
|
50
89
|
let plan;
|
|
51
90
|
try {
|
|
52
91
|
const projections = getProjections();
|
|
53
|
-
if (projections?.
|
|
54
|
-
plan = projections.
|
|
92
|
+
if (typeof projections?.snapshot !== 'function') return { kind: 'error', text: MESSAGES.planStateUnavailable };
|
|
93
|
+
plan = projections.snapshot(agent.session, ['plan'])?.values?.plan;
|
|
55
94
|
} catch (error) {
|
|
56
95
|
return { kind: 'error', text: `Plan state is unavailable: ${messageOf(error)}` };
|
|
57
96
|
}
|
|
@@ -59,7 +98,7 @@ export function createCompactCommand({ getProjections, getCompaction, getBalance
|
|
|
59
98
|
|
|
60
99
|
let compaction;
|
|
61
100
|
try {
|
|
62
|
-
compaction = getCompaction
|
|
101
|
+
compaction = resolveCompactionService(agent, { getAgentPresets, getCompaction });
|
|
63
102
|
} catch (error) {
|
|
64
103
|
return { kind: 'error', text: `Compaction is unavailable: ${messageOf(error)}` };
|
|
65
104
|
}
|
|
@@ -89,7 +128,7 @@ export function createCompactCommand({ getProjections, getCompaction, getBalance
|
|
|
89
128
|
} catch (error) {
|
|
90
129
|
if (signal?.aborted) return { kind: 'error', text: FAILURES.cancelled };
|
|
91
130
|
const code = typeof error?.code === 'string' ? error.code : undefined;
|
|
92
|
-
return { kind: 'error', text: (code
|
|
131
|
+
return { kind: 'error', text: Object.hasOwn(FAILURES, code) ? FAILURES[code] : messageOf(error) };
|
|
93
132
|
}
|
|
94
133
|
},
|
|
95
134
|
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-first plan handoff prompt section.
|
|
3
|
+
*
|
|
4
|
+
* Plan mode already renders the deployment's native `plan:policy` guidance.
|
|
5
|
+
* This plugin adds one independent section immediately after it, asking the
|
|
6
|
+
* planner to hand over its investigation together with the plan. It also carries
|
|
7
|
+
* an optional unlimited-token-budget reminder and a brainstorming paragraph,
|
|
8
|
+
* each independently switchable from the Settings page. The native policy text
|
|
9
|
+
* is never replaced or duplicated.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Prompt section name; unique in the global prompt layer. */
|
|
13
|
+
export const PLAN_EVIDENCE_SECTION = 'great-plan:plan-evidence';
|
|
14
|
+
|
|
15
|
+
/** Used when the instance cannot resolve the centrally owned PLAN_POLICY order. */
|
|
16
|
+
export const PLAN_EVIDENCE_ORDER_FALLBACK = 501;
|
|
17
|
+
|
|
18
|
+
/** Model-facing collaborative-discovery and Team-Lead guidance; the Settings page can switch it off. */
|
|
19
|
+
export const PLAN_BRAINSTORMING_GUIDANCE = `Discovery and delegation:
|
|
20
|
+
- Assess the goal, success criteria, constraints, and current state; do not quiz the user with a checklist.
|
|
21
|
+
- If an answer could materially change scope or approach, ask one concise question per turn and wait. If the request is clear or the user delegates the choice, proceed.
|
|
22
|
+
- You are the Team Lead. Use \`spawn_teammate\` to start at least 2 exploration teammates in parallel, each with one distinct, concrete scope, and require concise findings cited as path:line-range. Do not investigate their scopes yourself; collect all results and resolve conflicts.
|
|
23
|
+
- Then spawn 1 separate planning teammate to draft a candidate plan from the consolidated evidence. Verify the evidence and the draft yourself before submitting.
|
|
24
|
+
- If \`spawn_teammate\` is unavailable or fails, say so in the plan and continue alone; never invent teammate work.
|
|
25
|
+
- Show alternatives and tradeoffs only when they help the user decide; state the key reason for your recommendation.
|
|
26
|
+
- Separate user requirements from your recommendations, assumptions, and unverified points. Give a brief rationale, not step-by-step chain-of-thought.`;
|
|
27
|
+
|
|
28
|
+
/** Model-facing unlimited-token-budget reminder; the Settings page can switch it off. */
|
|
29
|
+
export const PLAN_UNLIMITED_BUDGET_GUIDANCE = 'Token budget: unlimited. Never cut investigation, tool calls, or needed questions to save tokens; keep the final plan concise.';
|
|
30
|
+
|
|
31
|
+
/** Model-facing evidence and executor handoff guidance; always rendered in plan mode. */
|
|
32
|
+
export const PLAN_HANDOFF_GUIDANCE = `Plan as handoff (the executor may get only this plan or a compacted history, so make it self-contained):
|
|
33
|
+
- Background: request, current behavior, constraints, assumptions.
|
|
34
|
+
- Evidence: files/symbols with line ranges (e.g. path/file.js:12-30), searches and commands with key results, docs/skills/URLs used, and how conflicts were resolved. Cite evidence for each design claim; mark anything unverified as an assumption or open question.
|
|
35
|
+
- Handoff: what is settled and must not be re-investigated; which unknowns to verify first.
|
|
36
|
+
Never present a guess as fact or invent paths, APIs, command output, or test results. Re-check citations before submitting. Keep evidence compact: precise pointers, not raw logs or whole files.`;
|
|
37
|
+
|
|
38
|
+
/** Default guidance (every switch on) appended while plan mode is active. */
|
|
39
|
+
export const PLAN_EVIDENCE_GUIDANCE = `${PLAN_UNLIMITED_BUDGET_GUIDANCE}\n\n${PLAN_BRAINSTORMING_GUIDANCE}\n\n${PLAN_HANDOFF_GUIDANCE}`;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compose the plan-mode guidance from its independently switchable parts.
|
|
43
|
+
*
|
|
44
|
+
* The budget reminder comes first as a general instruction, then the optional
|
|
45
|
+
* brainstorming guidance, and the evidence/handoff guidance always closes the
|
|
46
|
+
* section. Disabled parts are omitted entirely, never replaced by placeholders.
|
|
47
|
+
* With every switch on the precomputed `PLAN_EVIDENCE_GUIDANCE` constant is
|
|
48
|
+
* returned so the common path allocates nothing per prompt assembly.
|
|
49
|
+
*/
|
|
50
|
+
export function composePlanGuidance({ brainstorming = true, unlimitedBudget = true } = {}) {
|
|
51
|
+
if (brainstorming !== false && unlimitedBudget !== false) return PLAN_EVIDENCE_GUIDANCE;
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (unlimitedBudget !== false) parts.push(PLAN_UNLIMITED_BUDGET_GUIDANCE);
|
|
54
|
+
if (brainstorming !== false) parts.push(PLAN_BRAINSTORMING_GUIDANCE);
|
|
55
|
+
parts.push(PLAN_HANDOFF_GUIDANCE);
|
|
56
|
+
return parts.join('\n\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read one switch from the plugin config.
|
|
61
|
+
*
|
|
62
|
+
* A volatile field arrives as a reference object; plain booleans are accepted
|
|
63
|
+
* for tests and hand-written rows. Every missing, malformed, or unreadable
|
|
64
|
+
* value keeps the fallback so prompt assembly never fails.
|
|
65
|
+
*/
|
|
66
|
+
function readConfigSwitch(config, field, fallback) {
|
|
67
|
+
if (config === undefined || config === null || typeof config !== 'object') return fallback;
|
|
68
|
+
let value;
|
|
69
|
+
try {
|
|
70
|
+
value = config[field];
|
|
71
|
+
} catch {
|
|
72
|
+
return fallback;
|
|
73
|
+
}
|
|
74
|
+
if (value === undefined || value === null) return fallback;
|
|
75
|
+
if (typeof value === 'boolean') return value;
|
|
76
|
+
if (typeof value.get === 'function') {
|
|
77
|
+
try {
|
|
78
|
+
const live = value.get();
|
|
79
|
+
return typeof live === 'boolean' ? live : fallback;
|
|
80
|
+
} catch {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return fallback;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Read the brainstorming switch; defaults to enabled. */
|
|
88
|
+
export function readBrainstormingEnabled(config) {
|
|
89
|
+
return readConfigSwitch(config, 'injectBrainstormingPrompt', true);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Read the unlimited-token-budget switch; defaults to enabled. */
|
|
93
|
+
export function readUnlimitedBudgetEnabled(config) {
|
|
94
|
+
return readConfigSwitch(config, 'injectUnlimitedBudgetPrompt', true);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve one boolean-validated plan-mode state from a planMode-like service.
|
|
99
|
+
*
|
|
100
|
+
* A usable state exposes a boolean `active`; a pending selection overrides the
|
|
101
|
+
* logged state. Missing, malformed, or throwing reads return `undefined` so the
|
|
102
|
+
* caller can try its next source.
|
|
103
|
+
*/
|
|
104
|
+
function readPlanModeState(readService, agent) {
|
|
105
|
+
try {
|
|
106
|
+
const service = readService();
|
|
107
|
+
if (service === undefined || service === null || typeof service.get !== 'function') return undefined;
|
|
108
|
+
const state = service.get(agent);
|
|
109
|
+
if (state !== undefined && state !== null && typeof state === 'object' && typeof state.active === 'boolean') {
|
|
110
|
+
return state.pending === undefined ? state.active : state.pending === true;
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Decide whether plan mode is active for the step being assembled.
|
|
120
|
+
*
|
|
121
|
+
* The Host plane cannot rely on its own `planMode` instance: agent presets
|
|
122
|
+
* commonly mount the plan-mode plugin in an isolated group, so the host-plane
|
|
123
|
+
* instance does not see the state that the preset drives. The agent's own
|
|
124
|
+
* scoped context (`agent.ctx`) carries the instance that owns the session's
|
|
125
|
+
* plan state and the pending selection's direction — the same data source the
|
|
126
|
+
* native `plan:policy` section reads. The host-plane instance is the fallback
|
|
127
|
+
* for compositions where plan mode is host-owned, and the shared `plan`
|
|
128
|
+
* session projection (the unit the compaction command also reads) is the final
|
|
129
|
+
* synchronous fallback. Every source degrades to inactive instead of throwing.
|
|
130
|
+
*/
|
|
131
|
+
export function readPlanModeActive(agent, getPlanMode, getSessionProjections) {
|
|
132
|
+
if (agent === undefined || agent === null) return false;
|
|
133
|
+
// 1. The agent-scoped planMode instance: `get()` returns the logged state
|
|
134
|
+
// plus a pending selection whose boolean is the selected direction.
|
|
135
|
+
const scoped = readPlanModeState(() => agent.ctx?.get?.('planMode'), agent);
|
|
136
|
+
if (scoped !== undefined) return scoped;
|
|
137
|
+
// 2. The host-plane planMode instance.
|
|
138
|
+
const host = readPlanModeState(() => (typeof getPlanMode === 'function' ? getPlanMode() : undefined), agent);
|
|
139
|
+
if (host !== undefined) return host;
|
|
140
|
+
// 3. The shared `plan` projection's cropped view { active, pending }. The
|
|
141
|
+
// pending direction is not exposed here, so a pending selection counts as
|
|
142
|
+
// active; by assembly time the pre-step has already applied pending
|
|
143
|
+
// selections, which makes this equivalent to the native section's gate.
|
|
144
|
+
try {
|
|
145
|
+
const projections = typeof getSessionProjections === 'function' ? getSessionProjections() : undefined;
|
|
146
|
+
const session = agent?.session;
|
|
147
|
+
if (projections !== undefined && projections !== null && session !== undefined && typeof projections.snapshot === 'function') {
|
|
148
|
+
const state = projections.snapshot(session, ['plan'])?.values?.plan;
|
|
149
|
+
if (state !== undefined && state !== null && typeof state === 'object' && typeof state.active === 'boolean') {
|
|
150
|
+
return state.active === true || state.pending === true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// No usable plan state anywhere; treat the step as outside plan mode.
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Register the section right after the native plan policy.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} options
|
|
163
|
+
* @param {object} options.systemPrompt the Host `systemPrompt` service.
|
|
164
|
+
* @param {() => object | undefined} options.getPlanMode optional host-plane `planMode` reader.
|
|
165
|
+
* @param {() => object | undefined} [options.getSessionProjections] optional projection service reader.
|
|
166
|
+
* @param {() => object | undefined} options.getConfig optional live plugin config reader.
|
|
167
|
+
*/
|
|
168
|
+
export function registerPlanEvidenceSection({ systemPrompt, getPlanMode, getSessionProjections, getConfig }) {
|
|
169
|
+
let order = PLAN_EVIDENCE_ORDER_FALLBACK;
|
|
170
|
+
try {
|
|
171
|
+
const resolved = typeof systemPrompt.getSectionOrder === 'function'
|
|
172
|
+
? systemPrompt.getSectionOrder('PLAN_POLICY')
|
|
173
|
+
: undefined;
|
|
174
|
+
if (Number.isFinite(resolved)) order = resolved + 1;
|
|
175
|
+
} catch {
|
|
176
|
+
// Keep the fallback order; an unavailable central table must not break the plugin.
|
|
177
|
+
}
|
|
178
|
+
return systemPrompt.section({
|
|
179
|
+
name: PLAN_EVIDENCE_SECTION,
|
|
180
|
+
order,
|
|
181
|
+
text: (context) => {
|
|
182
|
+
let config;
|
|
183
|
+
try {
|
|
184
|
+
config = typeof getConfig === 'function' ? getConfig() : undefined;
|
|
185
|
+
} catch {
|
|
186
|
+
config = undefined;
|
|
187
|
+
}
|
|
188
|
+
if (!readPlanModeActive(context?.agent, getPlanMode, getSessionProjections)) return '';
|
|
189
|
+
return composePlanGuidance({
|
|
190
|
+
brainstorming: readBrainstormingEnabled(config),
|
|
191
|
+
unlimitedBudget: readUnlimitedBudgetEnabled(config),
|
|
192
|
+
});
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** The plan-review tool call that marks the pending plan review surface node. */
|
|
2
|
+
export const EXIT_PLAN_MODE_TOOL = 'exit_plan_mode';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Pure plan-compaction range selection.
|
|
3
6
|
*
|
|
@@ -7,9 +10,9 @@
|
|
|
7
10
|
* node before that review, snapping backwards until the tool pairing holds.
|
|
8
11
|
*/
|
|
9
12
|
export function selectPlanCompactionRange(session, balancedAfter, options = {}) {
|
|
10
|
-
const planReviewTool = typeof options
|
|
13
|
+
const planReviewTool = typeof options.planReviewTool === 'string'
|
|
11
14
|
? options.planReviewTool
|
|
12
|
-
:
|
|
15
|
+
: EXIT_PLAN_MODE_TOOL;
|
|
13
16
|
const nodes = session.surface.nodes;
|
|
14
17
|
if (nodes.length === 0) return { error: 'empty' };
|
|
15
18
|
const last = session.eventAt(nodes[nodes.length - 1]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onefl666/dsh-great-plan",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "onefl666",
|
|
6
6
|
"keywords": [
|
|
@@ -24,20 +24,21 @@
|
|
|
24
24
|
"url": "https://github.com/onefl666/dsh-great-plan/issues"
|
|
25
25
|
},
|
|
26
26
|
"type": "module",
|
|
27
|
+
"main": "lib/index.js",
|
|
27
28
|
"description": "Choose the model and reasoning effort, then execute, compact-and-execute, or start fresh with a reviewed plan.",
|
|
28
29
|
"icon": "./icon.svg",
|
|
29
30
|
"exports": {
|
|
30
|
-
".": "./index.js",
|
|
31
|
-
"./client": "./client.js",
|
|
31
|
+
".": "./lib/index.js",
|
|
32
|
+
"./client": "./lib/client.js",
|
|
32
33
|
"./locale/*.json": "./locale/*.json",
|
|
33
34
|
"./icon": "./icon.svg",
|
|
34
35
|
"./package.json": "./package.json"
|
|
35
36
|
},
|
|
36
37
|
"files": [
|
|
37
|
-
"index.js",
|
|
38
|
-
"host/*.js",
|
|
39
|
-
"client.js",
|
|
40
|
-
"client.*.js",
|
|
38
|
+
"lib/index.js",
|
|
39
|
+
"lib/host/*.js",
|
|
40
|
+
"lib/client.js",
|
|
41
|
+
"lib/client.*.js",
|
|
41
42
|
"cordis.patch.yml",
|
|
42
43
|
"locale/*.json",
|
|
43
44
|
"icon.svg",
|