@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.
@@ -11,132 +11,26 @@ window.__ModuleLoader__.load({
11
11
 
12
12
  const { useEffect, useMemo, useRef, useState } = React;
13
13
 
14
- /** Every fresh-session prompt switch defaults to on, like the Host Config. */
15
- const DEFAULT_SWITCHES = {
16
- unlimitedBudget: true,
17
- includeOriginalInstructions: true,
18
- includeMinimalContext: true,
19
- };
20
-
21
14
  /** Instructions state before the clear view reads the old session. */
22
15
  function emptyInstructions() {
23
16
  return { status: 'idle', texts: [], count: 0, chars: 0 };
24
17
  }
25
18
 
26
- /** Normalize the injected `readPromptSwitches` face; unreadable values stay on. */
27
- function normalizeSwitches(read) {
28
- if (typeof actionHelpers.normalizePromptSwitches === 'function') {
29
- try {
30
- return actionHelpers.normalizePromptSwitches(read);
31
- } catch {
32
- // Fall through to the local defaults.
33
- }
34
- }
35
- let value;
19
+ /** The fresh-message preview; the shared assembler throws ActionError('emptyPlan') for an empty plan. */
20
+ function freshPreviewOf(input) {
36
21
  try {
37
- value = typeof read === 'function' ? read() : undefined;
22
+ return actionHelpers.describeFreshPrompt(input);
38
23
  } catch {
39
- value = undefined;
40
- }
41
- if (typeof value === 'boolean') {
42
- return { unlimitedBudget: value, includeOriginalInstructions: true, includeMinimalContext: true };
43
- }
44
- if (value !== null && typeof value === 'object') {
45
24
  return {
46
- unlimitedBudget: value.unlimitedBudget !== false,
47
- includeOriginalInstructions: value.includeOriginalInstructions !== false,
48
- includeMinimalContext: value.includeMinimalContext !== false,
25
+ text: '',
26
+ blocks: [],
27
+ instructionCount: 0,
28
+ instructionChars: 0,
29
+ totalChars: 0,
30
+ approxTokens: 0,
31
+ oversized: false,
49
32
  };
50
33
  }
51
- return { ...DEFAULT_SWITCHES };
52
- }
53
-
54
- /** Read the current runtime facts for the preview; execution re-reads them. */
55
- function readRuntimeFactsForPreview() {
56
- if (typeof actionHelpers.readRuntimeFacts === 'function') {
57
- try {
58
- const facts = actionHelpers.readRuntimeFacts();
59
- if (facts !== null && typeof facts === 'object') return facts;
60
- } catch {
61
- // Fall through to the empty facts.
62
- }
63
- }
64
- return { system: undefined, time: undefined };
65
- }
66
-
67
- /** Localize an ActionError when the actions module knows the code. */
68
- function describeActionErrorSafe(t, caught) {
69
- if (typeof actionHelpers.describeActionError === 'function') {
70
- try {
71
- const described = actionHelpers.describeActionError(t, caught);
72
- if (typeof described === 'string' && described !== '') return described;
73
- } catch {
74
- // Fall through to the contextual fallback.
75
- }
76
- }
77
- return undefined;
78
- }
79
-
80
- /** Localized error text: the shared ActionError copy, else the panel fallback. */
81
- function describeCaught(t, caught, fallbackKey) {
82
- return describeActionErrorSafe(t, caught) ?? t(fallbackKey, { message: ui.messageOf(caught) });
83
- }
84
-
85
- /** Localize one compaction failure message; unknown text passes through. */
86
- function compactFailureMessage(t, failure) {
87
- if (typeof actionHelpers.describeCompactFailure === 'function') {
88
- try {
89
- const described = actionHelpers.describeCompactFailure(t, failure.message);
90
- if (typeof described === 'string' && described !== '') return described;
91
- } catch {
92
- // Fall through to the raw message.
93
- }
94
- }
95
- return failure.message;
96
- }
97
-
98
- /** Plan summary/full-length pair; never throws for an odd review. */
99
- function planInfoOf(review) {
100
- if (typeof actionHelpers.planPreviewText === 'function') {
101
- try {
102
- const info = actionHelpers.planPreviewText(review?.plan);
103
- if (info !== null && typeof info === 'object') {
104
- return {
105
- preview: typeof info.preview === 'string' ? info.preview : '',
106
- chars: typeof info.chars === 'number' ? info.chars : 0,
107
- };
108
- }
109
- } catch {
110
- // Fall through to the empty info.
111
- }
112
- }
113
- return { preview: '', chars: 0 };
114
- }
115
-
116
- /** Full plan text; empty when the review carries none. */
117
- function planTextOf(review) {
118
- return typeof review?.plan === 'string' ? review.plan : '';
119
- }
120
-
121
- /** The fresh-message preview; degraded shapes stay renderable. */
122
- function freshPreviewOf(input) {
123
- if (typeof actionHelpers.describeFreshPrompt === 'function') {
124
- try {
125
- const preview = actionHelpers.describeFreshPrompt(input);
126
- if (preview !== null && typeof preview === 'object') return preview;
127
- } catch {
128
- // Fall through to the empty preview.
129
- }
130
- }
131
- return {
132
- text: '',
133
- blocks: [],
134
- instructionCount: 0,
135
- instructionChars: 0,
136
- totalChars: 0,
137
- approxTokens: 0,
138
- oversized: false,
139
- };
140
34
  }
141
35
 
142
36
  /**
@@ -146,8 +40,8 @@ window.__ModuleLoader__.load({
146
40
  * region; the toggle only exists when the review carries plan text.
147
41
  */
148
42
  function planSectionOf({ review, t, expanded, onToggle }) {
149
- const info = planInfoOf(review);
150
- const body = planTextOf(review);
43
+ const info = actionHelpers.planPreviewText(review?.plan);
44
+ const body = typeof review?.plan === 'string' ? review.plan : '';
151
45
  const question = typeof review?.question === 'string' ? review.question.trim() : '';
152
46
  const expandable = body.trim() !== '';
153
47
  return h('div', {
@@ -214,7 +108,7 @@ window.__ModuleLoader__.load({
214
108
  title: t('trigger.aria'),
215
109
  onClick: () => setOpen(true),
216
110
  },
217
- h(ui.IconSliders, { className: 'gp-trigger-icon' }),
111
+ h(ui.IconSliders),
218
112
  h('span', { className: 'gp-trigger-label' }, t('trigger.label')),
219
113
  h(ui.IconChevronDown, { className: 'gp-trigger-chevron' }),
220
114
  ),
@@ -225,7 +119,6 @@ window.__ModuleLoader__.load({
225
119
  triggerRef,
226
120
  pending,
227
121
  review,
228
- requestKey,
229
122
  directory,
230
123
  actions,
231
124
  presetStore,
@@ -264,7 +157,7 @@ window.__ModuleLoader__.load({
264
157
  t,
265
158
  } = props;
266
159
  const directoryState = model.useDirectoryState(directory);
267
- const presetState = preset.usePresetState(presetStore.store);
160
+ const presetState = ui.useStore(presetStore.store);
268
161
  const [view, setView] = useState('root');
269
162
  const [busy, setBusy] = useState(null);
270
163
  const [error, setError] = useState(null);
@@ -291,22 +184,6 @@ window.__ModuleLoader__.load({
291
184
  }, []);
292
185
 
293
186
  useEffect(() => {
294
- if (!open) {
295
- setView('root');
296
- setBusy(null);
297
- setError(null);
298
- setCleanupWarning(null);
299
- setCompactFailure(null);
300
- setStatusLine(null);
301
- setPresetChoice(null);
302
- setRunSwitches(null);
303
- setPreviewFacts(null);
304
- setInstructionsState(emptyInstructions());
305
- setPlanExpanded(false);
306
- abortRef.current?.abort();
307
- instructionsAbortRef.current?.abort();
308
- return;
309
- }
310
187
  directory.load().catch(() => {});
311
188
  }, [open, directory]);
312
189
 
@@ -339,7 +216,7 @@ window.__ModuleLoader__.load({
339
216
  || busy === 'clear'
340
217
  || modelSelecting;
341
218
  const canRevise = typeof pending?.dismiss === 'function' && !blocked;
342
- const switches = runSwitches ?? normalizeSwitches(readPromptSwitches);
219
+ const switches = runSwitches ?? actionHelpers.normalizePromptSwitches(readPromptSwitches);
343
220
 
344
221
  const start = (kind) => {
345
222
  const controller = new AbortController();
@@ -404,8 +281,8 @@ window.__ModuleLoader__.load({
404
281
  setStatusLine(null);
405
282
  setCompactFailure(null);
406
283
  setCleanupWarning(null);
407
- setPreviewFacts(readRuntimeFactsForPreview());
408
- const initial = normalizeSwitches(readPromptSwitches);
284
+ setPreviewFacts(actionHelpers.readRuntimeFacts());
285
+ const initial = actionHelpers.normalizePromptSwitches(readPromptSwitches);
409
286
  setRunSwitches(initial);
410
287
  setInstructionsState(emptyInstructions());
411
288
  if (initial.includeOriginalInstructions) startInstructionsRead();
@@ -419,7 +296,7 @@ window.__ModuleLoader__.load({
419
296
  };
420
297
 
421
298
  const toggleSwitch = (name) => {
422
- const current = runSwitches ?? normalizeSwitches(readPromptSwitches);
299
+ const current = runSwitches ?? actionHelpers.normalizePromptSwitches(readPromptSwitches);
423
300
  const next = { ...current, [name]: !current[name] };
424
301
  setRunSwitches(next);
425
302
  if (name === 'includeOriginalInstructions'
@@ -430,38 +307,16 @@ window.__ModuleLoader__.load({
430
307
  }
431
308
  };
432
309
 
433
- const selectModel = async (group, item) => {
310
+ const applySelection = async (kind, payload) => {
434
311
  if (busy !== null) return;
435
- setBusy('model');
436
- setError(null);
437
- try {
438
- const result = await directory.select({ provider: group.id, model: item.id });
439
- if (!aliveRef.current) return;
440
- if (!result.ok) {
441
- setError(t('error.selectModelFailed', { message: `${result.error.message} (${result.error.code})` }));
442
- return;
443
- }
444
- setView('root');
445
- } catch (caught) {
446
- if (aliveRef.current) setError(t('error.selectModelFailed', { message: ui.messageOf(caught) }));
447
- } finally {
448
- finish();
449
- }
450
- };
451
-
452
- const selectEffort = async (effort) => {
453
- if (busy !== null || directoryState.current === null || directoryState.current === undefined) return;
454
- setBusy('effort');
312
+ if (kind === 'effort' && (directoryState.current === null || directoryState.current === undefined)) return;
313
+ setBusy(kind);
455
314
  setError(null);
456
315
  try {
457
- const result = await directory.select({
458
- provider: directoryState.current.provider,
459
- model: directoryState.current.model,
460
- ...effort === undefined ? {} : { reasoningEffort: effort },
461
- });
316
+ const result = await directory.select(payload);
462
317
  if (!aliveRef.current) return;
463
318
  if (!result.ok) {
464
- setError(t('error.selectModelFailed', { message: `${result.error.message} (${result.error.code})` }));
319
+ setError(t('error.selectModelFailed', { message: actionHelpers.formatRpcFailure(result) }));
465
320
  return;
466
321
  }
467
322
  setView('root');
@@ -472,26 +327,7 @@ window.__ModuleLoader__.load({
472
327
  }
473
328
  };
474
329
 
475
- const execute = async () => {
476
- if (busy !== null) return;
477
- if (pending === undefined) {
478
- setError(t('error.reviewGone'));
479
- return;
480
- }
481
- const controller = start('execute');
482
- try {
483
- await actions.approveReview({ pending, review });
484
- if (aliveRef.current) onClose();
485
- } catch (caught) {
486
- if (aliveRef.current && !controller.signal.aborted) {
487
- setError(describeCaught(t, caught, 'error.approveFailed'));
488
- }
489
- } finally {
490
- finish();
491
- }
492
- };
493
-
494
- const proceedAnyway = async () => {
330
+ const approvePending = async () => {
495
331
  if (busy !== null) return;
496
332
  if (pending === undefined) {
497
333
  setError(t('error.reviewGone'));
@@ -503,7 +339,7 @@ window.__ModuleLoader__.load({
503
339
  if (aliveRef.current) onClose();
504
340
  } catch (caught) {
505
341
  if (aliveRef.current && !controller.signal.aborted) {
506
- setError(describeCaught(t, caught, 'error.approveFailed'));
342
+ setError(actionHelpers.describeActionError(t, caught) ?? t('error.approveFailed', { message: ui.messageOf(caught) }));
507
343
  }
508
344
  } finally {
509
345
  finish();
@@ -537,7 +373,7 @@ window.__ModuleLoader__.load({
537
373
  } catch (caught) {
538
374
  if (aliveRef.current && !controller.signal.aborted) {
539
375
  setStatusLine(null);
540
- setError(describeCaught(t, caught, 'error.approveFailed'));
376
+ setError(actionHelpers.describeActionError(t, caught) ?? t('error.approveFailed', { message: ui.messageOf(caught) }));
541
377
  }
542
378
  }
543
379
  } finally {
@@ -562,7 +398,7 @@ window.__ModuleLoader__.load({
562
398
  await actions.dismissReview({ pending });
563
399
  if (aliveRef.current) onClose();
564
400
  } catch (caught) {
565
- if (aliveRef.current) setError(describeCaught(t, caught, 'error.dismissFailed'));
401
+ if (aliveRef.current) setError(actionHelpers.describeActionError(t, caught) ?? t('error.dismissFailed', { message: ui.messageOf(caught) }));
566
402
  } finally {
567
403
  finish();
568
404
  }
@@ -574,7 +410,7 @@ window.__ModuleLoader__.load({
574
410
  setError(t('error.reviewGone'));
575
411
  return;
576
412
  }
577
- const activeSwitches = runSwitches ?? normalizeSwitches(readPromptSwitches);
413
+ const activeSwitches = runSwitches ?? actionHelpers.normalizePromptSwitches(readPromptSwitches);
578
414
  if (activeSwitches.includeOriginalInstructions && instructionsState.status === 'loading') return;
579
415
  const controller = start('clear');
580
416
  try {
@@ -588,7 +424,6 @@ window.__ModuleLoader__.load({
588
424
  title,
589
425
  titleSuffix: t('clear.titleSuffix'),
590
426
  signal: controller.signal,
591
- t,
592
427
  instructions: activeSwitches.includeOriginalInstructions ? instructionsState.texts : undefined,
593
428
  switches: activeSwitches,
594
429
  stillPending: () => pendingRef.current !== undefined,
@@ -600,7 +435,7 @@ window.__ModuleLoader__.load({
600
435
  onClose();
601
436
  } catch (caught) {
602
437
  if (aliveRef.current && !controller.signal.aborted) {
603
- setError(describeCaught(t, caught, 'error.freshFailed'));
438
+ setError(actionHelpers.describeActionError(t, caught) ?? t('error.freshFailed', { message: ui.messageOf(caught) }));
604
439
  const cleanup = caught?.cleanupError;
605
440
  if (typeof cleanup === 'string' && cleanup !== '') {
606
441
  setCleanupWarning(t('error.cleanupFailed', { message: cleanup }));
@@ -633,33 +468,34 @@ window.__ModuleLoader__.load({
633
468
  preset: t('preset.title'),
634
469
  };
635
470
 
636
- let subtitle;
637
- if (view === 'model' || view === 'effort') subtitle = model.summaryOf(directoryState, t);
638
- else if (view === 'root') subtitle = t('panel.subtitle');
639
-
640
- const header = h('div', { className: 'gp-dialog-header' },
641
- view === 'root' ? null : h(ui.IconButton, {
642
- label: t('panel.back'),
643
- onClick: back,
644
- disabled: blocked,
645
- }, h(ui.IconChevronLeft)),
646
- h('div', { className: 'gp-dialog-title-wrap' },
647
- h('h2', {
648
- ref: titleRef,
649
- className: 'gp-dialog-title',
650
- id: 'gp-great-plan-title',
651
- tabIndex: -1,
652
- title: titles[view],
653
- }, titles[view]),
654
- subtitle === undefined ? null : h('div', { className: 'gp-dialog-subtitle' }, subtitle),
655
- ),
656
- h(ui.IconButton, {
657
- label: t('panel.close'),
658
- onClick: close,
659
- disabled: blocked,
660
- autoFocus: true,
661
- }, h(ui.IconClose)),
662
- );
471
+ const renderHeader = () => {
472
+ let subtitle;
473
+ if (view === 'model' || view === 'effort') subtitle = model.summaryOf(directoryState, t);
474
+ else if (view === 'root') subtitle = t('panel.subtitle');
475
+ return h('div', { className: 'gp-dialog-header' },
476
+ view === 'root' ? null : h(ui.IconButton, {
477
+ label: t('panel.back'),
478
+ onClick: back,
479
+ disabled: blocked,
480
+ }, h(ui.IconChevronLeft)),
481
+ h('div', { className: 'gp-dialog-title-wrap' },
482
+ h('h2', {
483
+ ref: titleRef,
484
+ className: 'gp-dialog-title',
485
+ id: 'gp-great-plan-title',
486
+ tabIndex: -1,
487
+ title: titles[view],
488
+ }, titles[view]),
489
+ subtitle === undefined ? null : h('div', { className: 'gp-dialog-subtitle' }, subtitle),
490
+ ),
491
+ h(ui.IconButton, {
492
+ label: t('panel.close'),
493
+ onClick: close,
494
+ disabled: blocked,
495
+ autoFocus: true,
496
+ }, h(ui.IconClose)),
497
+ );
498
+ };
663
499
 
664
500
  const planSection = planSectionOf({
665
501
  review,
@@ -668,45 +504,74 @@ window.__ModuleLoader__.load({
668
504
  onToggle: () => setPlanExpanded((value) => !value),
669
505
  });
670
506
 
671
- let body;
672
- if (view === 'model') {
673
- body = h('div', { className: 'gp-dialog-body' },
674
- error === null ? null : h(ui.ErrorText, null, error),
675
- h(model.ModelListView, {
676
- state: directoryState,
677
- t,
678
- disabled: controlBusy,
679
- onPick: selectModel,
680
- onRetry: () => directory.load().catch(() => {}),
681
- }),
682
- );
683
- } else if (view === 'effort') {
684
- body = h('div', { className: 'gp-dialog-body' },
685
- error === null ? null : h(ui.ErrorText, null, error),
686
- h(model.EffortListView, {
687
- state: directoryState,
688
- t,
689
- disabled: controlBusy,
690
- onPick: selectEffort,
691
- }),
692
- );
693
- } else if (view === 'preset') {
694
- body = h('div', { className: 'gp-dialog-body' },
695
- h(preset.PresetOptions, {
696
- state: presetState,
697
- t,
698
- selectedId: effectivePresetId,
699
- onPick: (id) => {
700
- setPresetChoice(id);
701
- setView('clear');
702
- },
703
- onRetry: () => presetStore.load(true).catch(() => {}),
704
- }),
507
+ const renderCompactFailure = () => compactFailure === null
508
+ ? null
509
+ : h('div', { className: 'gp-failure' },
510
+ h(ui.WarningBox, null, t('compact.failed', { message: actionHelpers.describeCompactFailure(t, compactFailure.message) })),
511
+ h(ui.Note, null, t('compact.failedHint')),
512
+ h('div', { className: 'gp-failure-actions' },
513
+ h(ui.Button, {
514
+ variant: 'outline',
515
+ disabled: controlBusy,
516
+ onClick: compact,
517
+ }, busy === 'compact' ? h(ui.Loading, null, t('compact.retrying')) : t('compact.retry')),
518
+ h(ui.Button, {
519
+ variant: 'primary',
520
+ disabled: controlBusy,
521
+ onClick: approvePending,
522
+ }, t('compact.proceed')),
523
+ h(ui.Button, {
524
+ variant: 'ghost',
525
+ disabled: controlBusy,
526
+ onClick: cancelCompactFailure,
527
+ }, t('compact.cancel')),
528
+ ),
705
529
  );
706
- } else if (view === 'clear') {
530
+
531
+ const renderModelView = () => h('div', { className: 'gp-dialog-body' },
532
+ error === null ? null : h(ui.ErrorText, null, error),
533
+ h(model.ModelListView, {
534
+ state: directoryState,
535
+ t,
536
+ disabled: controlBusy,
537
+ onPick: (group, item) => applySelection('model', { provider: group.id, model: item.id }),
538
+ onRetry: () => directory.load().catch(() => {}),
539
+ }),
540
+ );
541
+
542
+ const renderEffortView = () => h('div', { className: 'gp-dialog-body' },
543
+ error === null ? null : h(ui.ErrorText, null, error),
544
+ h(model.EffortListView, {
545
+ state: directoryState,
546
+ t,
547
+ disabled: controlBusy,
548
+ onPick: (effort) => {
549
+ if (directoryState.current === null || directoryState.current === undefined) return;
550
+ applySelection('effort', {
551
+ provider: directoryState.current.provider,
552
+ model: directoryState.current.model,
553
+ ...effort === undefined ? {} : { reasoningEffort: effort },
554
+ });
555
+ },
556
+ }),
557
+ );
558
+
559
+ const renderPresetView = () => h('div', { className: 'gp-dialog-body' },
560
+ h(preset.PresetOptions, {
561
+ state: presetState,
562
+ t,
563
+ selectedId: effectivePresetId,
564
+ onPick: (id) => {
565
+ setPresetChoice(id);
566
+ setView('clear');
567
+ },
568
+ onRetry: () => presetStore.load(true).catch(() => {}),
569
+ }),
570
+ );
571
+
572
+ const renderClearView = () => {
707
573
  const preview = freshPreviewOf({
708
574
  plan: review?.plan,
709
- t,
710
575
  presetId: effectivePresetId,
711
576
  instructions: instructionsState.texts,
712
577
  workspaceDirectory,
@@ -720,7 +585,7 @@ window.__ModuleLoader__.load({
720
585
  const minimalDisabled = effectivePresetId !== 'minimal';
721
586
  const runtimeIncluded = switches.includeMinimalContext === true && effectivePresetId === 'minimal';
722
587
  const confirmDisabled = controlBusy || directoryLoading || instructionsLoading;
723
- body = h('div', { className: 'gp-dialog-body' },
588
+ return h('div', { className: 'gp-dialog-body' },
724
589
  planSection,
725
590
  h(ui.WarningBox, null, t('clear.warning')),
726
591
  h(ui.SelectRow, {
@@ -799,85 +664,72 @@ window.__ModuleLoader__.load({
799
664
  ),
800
665
  h(ui.Note, { className: 'gp-footer-note' }, t('clear.kept')),
801
666
  );
802
- } else {
803
- body = h('div', { className: 'gp-dialog-body' },
804
- planSection,
805
- h(model.ModelRows, {
806
- state: directoryState,
807
- t,
808
- disabled: !modelAvailable || controlBusy,
809
- onOpenModel: () => setView('model'),
810
- onOpenEffort: () => setView('effort'),
811
- onRetry: () => directory.load().catch(() => {}),
812
- }),
813
- !modelAvailable ? h(ui.Note, null, t('panel.subagentUnavailable')) : null,
814
- compactFailure === null
815
- ? null
816
- : h('div', { className: 'gp-failure' },
817
- h(ui.WarningBox, null, t('compact.failed', { message: compactFailureMessage(t, compactFailure) })),
818
- h(ui.Note, null, t('compact.failedHint')),
819
- h('div', { className: 'gp-failure-actions' },
820
- h(ui.Button, {
821
- variant: 'outline',
822
- disabled: controlBusy,
823
- onClick: compact,
824
- }, busy === 'compact' ? h(ui.Loading, null, t('compact.retrying')) : t('compact.retry')),
825
- h(ui.Button, {
826
- variant: 'primary',
827
- disabled: controlBusy,
828
- onClick: proceedAnyway,
829
- }, t('compact.proceed')),
830
- h(ui.Button, {
831
- variant: 'ghost',
832
- disabled: controlBusy,
833
- onClick: cancelCompactFailure,
834
- }, t('compact.cancel')),
835
- ),
836
- ),
837
- statusLine === null ? null : h('div', { className: 'gp-status', role: 'status' }, statusLine),
838
- error === null ? null : h(ui.ErrorText, null, error),
839
- compactFailure === null
840
- ? h('div', null,
841
- h('div', { className: 'gp-separator' }),
842
- h('div', { className: 'gp-actions' },
843
- h(ui.ActionRow, {
844
- primary: true,
845
- icon: h(ui.IconPlay),
846
- title: t('panel.actions.execute.title'),
847
- description: t('panel.actions.execute.description'),
848
- disabled: controlBusy,
849
- onClick: execute,
850
- }),
851
- h(ui.ActionRow, {
852
- icon: h(ui.IconCompress),
853
- title: t('panel.actions.compact.title'),
854
- description: t('panel.actions.compact.description'),
855
- disabled: controlBusy,
856
- onClick: compact,
857
- }),
858
- h(ui.ActionRow, {
859
- icon: h(ui.IconNewSession),
860
- title: t('panel.actions.clear.title'),
861
- description: t('panel.actions.clear.description'),
862
- disabled: controlBusy || !modelAvailable,
863
- onClick: enterClear,
864
- }),
865
- ),
866
- )
867
- : null,
868
- canRevise
869
- ? h('div', { className: 'gp-actions' },
870
- h(ui.Button, {
871
- variant: 'ghost',
872
- full: true,
667
+ };
668
+
669
+ const renderRootView = () => h('div', { className: 'gp-dialog-body' },
670
+ planSection,
671
+ h(model.ModelRows, {
672
+ state: directoryState,
673
+ t,
674
+ disabled: !modelAvailable || controlBusy,
675
+ onOpenModel: () => setView('model'),
676
+ onOpenEffort: () => setView('effort'),
677
+ onRetry: () => directory.load().catch(() => {}),
678
+ }),
679
+ !modelAvailable ? h(ui.Note, null, t('panel.subagentUnavailable')) : null,
680
+ renderCompactFailure(),
681
+ statusLine === null ? null : h('div', { className: 'gp-status', role: 'status' }, statusLine),
682
+ error === null ? null : h(ui.ErrorText, null, error),
683
+ compactFailure === null
684
+ ? h('div', null,
685
+ h('div', { className: 'gp-separator' }),
686
+ h('div', { className: 'gp-actions' },
687
+ h(ui.ActionRow, {
688
+ primary: true,
689
+ icon: h(ui.IconPlay),
690
+ title: t('panel.actions.execute.title'),
691
+ description: t('panel.actions.execute.description'),
873
692
  disabled: controlBusy,
874
- onClick: revise,
875
- }, busy === 'dismiss' ? h(ui.Loading, null, t('panel.loading')) : t('panel.revise')),
876
- )
877
- : null,
878
- h(ui.Note, { className: 'gp-footer-note' }, t('panel.applyNote')),
879
- );
880
- }
693
+ onClick: approvePending,
694
+ }),
695
+ h(ui.ActionRow, {
696
+ icon: h(ui.IconCompress),
697
+ title: t('panel.actions.compact.title'),
698
+ description: t('panel.actions.compact.description'),
699
+ disabled: controlBusy,
700
+ onClick: compact,
701
+ }),
702
+ h(ui.ActionRow, {
703
+ icon: h(ui.IconNewSession),
704
+ title: t('panel.actions.clear.title'),
705
+ description: t('panel.actions.clear.description'),
706
+ disabled: controlBusy || !modelAvailable,
707
+ onClick: enterClear,
708
+ }),
709
+ ),
710
+ )
711
+ : null,
712
+ canRevise
713
+ ? h('div', { className: 'gp-actions' },
714
+ h(ui.Button, {
715
+ variant: 'ghost',
716
+ full: true,
717
+ disabled: controlBusy,
718
+ onClick: revise,
719
+ }, busy === 'dismiss' ? h(ui.Loading, null, t('panel.loading')) : t('panel.revise')),
720
+ )
721
+ : null,
722
+ h(ui.Note, { className: 'gp-footer-note' }, t('panel.applyNote')),
723
+ );
724
+
725
+ const views = {
726
+ model: renderModelView,
727
+ effort: renderEffortView,
728
+ preset: renderPresetView,
729
+ clear: renderClearView,
730
+ root: renderRootView,
731
+ };
732
+ const body = views[view]();
881
733
 
882
734
  return h(ui.Dialog, {
883
735
  open,
@@ -886,7 +738,7 @@ window.__ModuleLoader__.load({
886
738
  busy: busy !== null,
887
739
  anchorRef: triggerRef,
888
740
  labelledBy: 'gp-great-plan-title',
889
- }, header, body);
741
+ }, renderHeader(), body);
890
742
  }
891
743
 
892
744
  return {