@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.
@@ -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 BUILTIN_NAMES = {
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 : BUILTIN_NAMES[option.id];
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 : BUILTIN_DESCRIPTIONS[option.id];
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: readBudgetValue(snapshot),
100
- includeOriginalInstructions: readOriginalInstructionsValue(snapshot),
101
- includeMinimalContext: readMinimalContextValue(snapshot),
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', { className: 'gp-check-label' }, label),
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
- /** The plan-review tool call that marks the pending plan review surface node. */
4
- export const EXIT_PLAN_MODE_TOOL = 'exit_plan_mode';
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
- export function createCompactCommand({ getProjections, getCompaction, getBalancedAfter }) {
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 rawInput = typeof invocation?.rawInput === 'string' ? invocation.rawInput.trim() : '';
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 } = invocation ?? {};
87
+ const { agent, signal } = args;
49
88
 
50
89
  let plan;
51
90
  try {
52
91
  const projections = getProjections();
53
- if (projections?.stateOf === undefined) return { kind: 'error', text: MESSAGES.planStateUnavailable };
54
- plan = projections.stateOf(agent.session, 'plan');
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 !== undefined && FAILURES[code]) || messageOf(error) };
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?.planReviewTool === 'string'
13
+ const planReviewTool = typeof options.planReviewTool === 'string'
11
14
  ? options.planReviewTool
12
- : 'exit_plan_mode';
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.5.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",