@pellux/goodvibes-agent 0.1.9 → 0.1.10
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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/release-and-publishing.md +2 -2
- package/package.json +4 -1
- package/src/cli/agent-knowledge-command.ts +18 -19
- package/src/cli/help.ts +13 -0
- package/src/cli/management-commands.ts +3 -3
- package/src/cli/management.ts +7 -1
- package/src/cli/parser.ts +3 -0
- package/src/cli/service-posture.ts +6 -6
- package/src/cli/status.ts +9 -9
- package/src/cli/surface-command.ts +3 -3
- package/src/cli/types.ts +2 -0
- package/src/input/commands/experience-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +30 -2
- package/src/input/handler.ts +1 -0
- package/src/input/onboarding/onboarding-runtime-status.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-apply.ts +13 -53
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +4 -4
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +7 -7
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +4 -4
- package/src/input/onboarding/onboarding-wizard-steps.ts +13 -13
- package/src/input/settings-modal-agent-policy.ts +18 -0
- package/src/input/settings-modal-types.ts +17 -0
- package/src/input/settings-modal.ts +30 -29
- package/src/main.ts +3 -26
- package/src/renderer/process-modal.ts +17 -8
- package/src/renderer/settings-modal.ts +12 -8
- package/src/renderer/ui-factory.ts +4 -32
- package/src/runtime/bootstrap-shell.ts +0 -13
- package/src/runtime/bootstrap.ts +0 -10
- package/src/runtime/onboarding/derivation.ts +6 -6
- package/src/verification/live-verifier.ts +148 -13
- package/src/version.ts +10 -3
- package/src/input/commands/quit-shared.ts +0 -162
- package/src/renderer/git-status.ts +0 -89
|
@@ -30,13 +30,13 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
|
|
|
30
30
|
id: 'network-access',
|
|
31
31
|
label: 'Let other devices use GoodVibes',
|
|
32
32
|
selected: false,
|
|
33
|
-
detail: '
|
|
33
|
+
detail: 'Review external daemon surfaces that are reachable from other devices on your LAN. Local authentication is required.',
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
id: 'webhook-events',
|
|
37
37
|
label: 'Receive webhooks or events from other tools',
|
|
38
38
|
selected: false,
|
|
39
|
-
detail: '
|
|
39
|
+
detail: 'Review the external HTTP listener required for incoming webhooks, callbacks, and automation events.',
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
id: 'external-integrations',
|
|
@@ -48,7 +48,7 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
|
|
|
48
48
|
id: 'cloudflare-batch',
|
|
49
49
|
label: 'Use Cloudflare for batch or remote daemon work',
|
|
50
50
|
selected: false,
|
|
51
|
-
detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs.
|
|
51
|
+
detail: 'Optionally configure Cloudflare Workers and Queues for explicit or eligible background batch jobs. The external daemon still owns execution.',
|
|
52
52
|
},
|
|
53
53
|
];
|
|
54
54
|
|
|
@@ -60,13 +60,13 @@ export const REASONING_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
|
60
60
|
];
|
|
61
61
|
|
|
62
62
|
export const NETWORK_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
63
|
-
{ id: 'local-network-default', label: 'Local Network (Default)', hint: '
|
|
64
|
-
{ id: 'custom', label: 'Custom', hint: '
|
|
63
|
+
{ id: 'local-network-default', label: 'Local Network (Default)', hint: 'Review the default LAN-facing external daemon setup for browser, control-plane, and event features.' },
|
|
64
|
+
{ id: 'custom', label: 'Custom', hint: 'Review IP addresses and ports for each external daemon surface.' },
|
|
65
65
|
];
|
|
66
66
|
|
|
67
67
|
export const HITL_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
68
68
|
{ id: 'quiet', label: 'Quiet', hint: 'Only interrupt for important attention requests.' },
|
|
69
|
-
{ id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning
|
|
69
|
+
{ id: 'balanced', label: 'Balanced', hint: 'Show important activity without turning Agent into a log stream.' },
|
|
70
70
|
{ id: 'operator', label: 'Operator', hint: 'Keep operational activity visible for hands-on supervision.' },
|
|
71
71
|
];
|
|
72
72
|
|
|
@@ -90,7 +90,7 @@ export const SECRET_POLICY_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
|
90
90
|
|
|
91
91
|
export const TELEGRAM_MODE_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
92
92
|
{ id: 'webhook', label: 'Webhook', hint: 'Receive Telegram updates through a webhook.' },
|
|
93
|
-
{ id: 'polling', label: 'Polling', hint: '
|
|
93
|
+
{ id: 'polling', label: 'Polling', hint: 'The external daemon polls Telegram for updates.' },
|
|
94
94
|
];
|
|
95
95
|
|
|
96
96
|
export const WHATSAPP_PROVIDER_OPTIONS: readonly OnboardingWizardRadioOption[] = [
|
|
@@ -105,8 +105,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
|
|
|
105
105
|
kind: 'text',
|
|
106
106
|
label: 'Home Assistant device ID',
|
|
107
107
|
hint: 'Stable device identifier exposed by the GoodVibes daemon.',
|
|
108
|
-
placeholder: 'goodvibes-
|
|
109
|
-
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-
|
|
108
|
+
placeholder: 'goodvibes-agent',
|
|
109
|
+
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceId ?? 'goodvibes-agent',
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
id: 'external-services.homeassistant.device-name',
|
|
@@ -114,8 +114,8 @@ export const HOME_ASSISTANT_SURFACE_SPEC: ExternalSurfaceSpec = {
|
|
|
114
114
|
kind: 'text',
|
|
115
115
|
label: 'Home Assistant device name',
|
|
116
116
|
hint: 'Display name for the GoodVibes daemon device in Home Assistant.',
|
|
117
|
-
placeholder: 'GoodVibes
|
|
118
|
-
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes
|
|
117
|
+
placeholder: 'GoodVibes Agent',
|
|
118
|
+
defaultValue: (snapshot) => snapshot?.config.surfaces.homeassistant.deviceName ?? 'GoodVibes Agent',
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
id: 'external-services.homeassistant.event-type',
|
|
@@ -436,7 +436,7 @@ function buildExternalSurfaceStep(
|
|
|
436
436
|
id: `external-surface:${surface.id}` as OnboardingWizardExternalSurfaceStepId,
|
|
437
437
|
title,
|
|
438
438
|
shortLabel: surface.label.replace(/ surface$/i, ''),
|
|
439
|
-
description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the
|
|
439
|
+
description: `Configure ${surface.label}. Settings are saved either way; Agent does not start or own the external daemon service.`,
|
|
440
440
|
summaryTitle: `${surface.label} setup`,
|
|
441
441
|
summaryLines: [
|
|
442
442
|
`External activation requested: ${autoStartValue === 'yes' ? 'yes' : 'no'}`,
|
|
@@ -540,8 +540,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
540
540
|
const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
|
|
541
541
|
kind: 'checklist',
|
|
542
542
|
id: 'network.shared-ip',
|
|
543
|
-
label: 'Use the same IP address for all
|
|
544
|
-
hint: 'When included, browser,
|
|
543
|
+
label: 'Use the same IP address for all external daemon surfaces',
|
|
544
|
+
hint: 'When included, browser, external daemon control plane, and webhook listener network bindings share one IP address in the daemon host configuration.',
|
|
545
545
|
defaultValue: controller.getSharedIpDefault(networkEnabled),
|
|
546
546
|
};
|
|
547
547
|
const sharedIp = controller.getBooleanFieldValue(sharedIpField.id, sharedIpField.defaultValue);
|
|
@@ -561,8 +561,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
561
561
|
fields.push({
|
|
562
562
|
kind: 'text',
|
|
563
563
|
id: 'network.service-port',
|
|
564
|
-
label: '
|
|
565
|
-
hint: 'Port
|
|
564
|
+
label: 'External daemon control-plane port',
|
|
565
|
+
hint: 'Port exposed by the external daemon control plane.',
|
|
566
566
|
placeholder: '3421',
|
|
567
567
|
defaultValue: String(bindSettings?.controlPlane.port ?? 3421),
|
|
568
568
|
});
|
|
@@ -570,8 +570,8 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
570
570
|
fields.push({
|
|
571
571
|
kind: 'text',
|
|
572
572
|
id: 'network.service-ip',
|
|
573
|
-
label: '
|
|
574
|
-
hint: 'IP address
|
|
573
|
+
label: 'External daemon control-plane IP address',
|
|
574
|
+
hint: 'IP address exposed by the external daemon control plane.',
|
|
575
575
|
placeholder: '0.0.0.0',
|
|
576
576
|
defaultValue: normalizeText(bindSettings?.controlPlane.host) || '0.0.0.0',
|
|
577
577
|
});
|
|
@@ -625,7 +625,7 @@ export function buildNetworkStep(controller: OnboardingWizardController): Onboar
|
|
|
625
625
|
id: 'network',
|
|
626
626
|
title: 'Network setup',
|
|
627
627
|
shortLabel: 'Network',
|
|
628
|
-
description: '
|
|
628
|
+
description: 'Review LAN defaults or IP addresses and ports for the external daemon browser, control-plane, and listener surfaces. Agent does not apply daemon bind changes.',
|
|
629
629
|
summaryTitle: 'Bind posture',
|
|
630
630
|
summaryLines: [
|
|
631
631
|
`Mode: ${custom ? 'custom' : 'local network default'}`,
|
|
@@ -653,7 +653,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
653
653
|
id: 'accounts.admin-username',
|
|
654
654
|
label: 'Local auth admin username',
|
|
655
655
|
hint: needsAuthBootstrap
|
|
656
|
-
? 'Required before
|
|
656
|
+
? 'Required before the external daemon exposes browser, control-plane, or listener surfaces.'
|
|
657
657
|
: 'Optional. Enter an existing admin username to rotate its password, or a new username to create another admin.',
|
|
658
658
|
placeholder: defaultAdminUsername,
|
|
659
659
|
defaultValue: defaultAdminUsername,
|
|
@@ -666,7 +666,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
666
666
|
hint: needsAuthBootstrap
|
|
667
667
|
? controller.hasBootstrapCredentialPresent()
|
|
668
668
|
? 'Creates or updates the named local admin, removes the bootstrap credential file, and retires the bootstrap admin when it is a different user.'
|
|
669
|
-
: 'Creates the first local admin user and an initial session before
|
|
669
|
+
: 'Creates the first local admin user and an initial session before external daemon network settings are used by the daemon owner.'
|
|
670
670
|
: 'Optional. Leave blank to keep existing local auth unchanged; enter a password to create or rotate the named admin user.',
|
|
671
671
|
placeholder: needsAuthBootstrap ? 'password required' : 'leave blank to keep unchanged',
|
|
672
672
|
defaultValue: '',
|
|
@@ -724,7 +724,7 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
724
724
|
title: 'Subscriptions and auth review',
|
|
725
725
|
shortLabel: 'Accounts',
|
|
726
726
|
description: needsAuthBootstrap
|
|
727
|
-
? 'Create
|
|
727
|
+
? 'Create local auth state before external daemon LAN, browser, service, or listener settings are applied by the daemon owner.'
|
|
728
728
|
: 'Review existing subscription and local auth state. Existing local auth is kept unless you change it elsewhere.',
|
|
729
729
|
summaryTitle: 'Stored account state',
|
|
730
730
|
summaryLines: [
|
|
@@ -732,8 +732,8 @@ export function buildAccountsStep(controller: OnboardingWizardController): Onboa
|
|
|
732
732
|
`Auth: ${auth?.userCount ?? 0} users / ${auth?.sessionCount ?? 0} sessions`,
|
|
733
733
|
needsAuthBootstrap
|
|
734
734
|
? controller.hasBootstrapCredentialPresent()
|
|
735
|
-
? 'Bootstrap credentials will be replaced before network settings are
|
|
736
|
-
: 'Local admin will be created before network settings are
|
|
735
|
+
? 'Bootstrap credentials will be replaced before external network settings are used'
|
|
736
|
+
: 'Local admin will be created before external network settings are used'
|
|
737
737
|
: controller.hasLocalAuthUser() ? 'Existing local auth will be kept' : 'Local auth is not required for this setup',
|
|
738
738
|
],
|
|
739
739
|
fields,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON = 'GoodVibes Agent connects to an external daemon. Change this from GoodVibes TUI or the daemon host; Agent settings are read-only for daemon lifecycle and bind posture.';
|
|
2
|
+
|
|
3
|
+
const EXTERNAL_DAEMON_SETTING_PREFIXES = [
|
|
4
|
+
'service.',
|
|
5
|
+
'controlPlane.',
|
|
6
|
+
'httpListener.',
|
|
7
|
+
'web.',
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
const EXTERNAL_DAEMON_SETTING_KEYS = new Set<string>([
|
|
11
|
+
'danger.daemon',
|
|
12
|
+
'danger.httpListener',
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
export function isExternalDaemonOwnedSettingKey(key: string): boolean {
|
|
16
|
+
return EXTERNAL_DAEMON_SETTING_KEYS.has(key)
|
|
17
|
+
|| EXTERNAL_DAEMON_SETTING_PREFIXES.some((prefix) => key.startsWith(prefix));
|
|
18
|
+
}
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
3
|
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@/runtime/index.ts';
|
|
3
4
|
import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
|
|
4
5
|
|
|
6
|
+
export interface SettingsModalChange {
|
|
7
|
+
readonly key: ConfigKey;
|
|
8
|
+
readonly previousValue: unknown;
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SettingsModalChangeResult {
|
|
13
|
+
readonly message?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
|
|
17
|
+
|
|
18
|
+
export interface SettingsModalOpenOptions {
|
|
19
|
+
readonly onSettingApplied?: SettingsModalChangeHandler;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
export type SettingsCategory =
|
|
6
23
|
| 'display'
|
|
7
24
|
| 'ui'
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SettingsModal — state management for the /settings and /config fullscreen workspace.
|
|
3
|
-
*
|
|
4
|
-
* Loads CONFIG_SCHEMA, groups settings by category, and tracks UI state:
|
|
5
|
-
* - Active category (Tab to cycle)
|
|
6
|
-
* - Selected setting index within category (↑↓)
|
|
7
|
-
* - Editing mode for inline string/number input
|
|
8
|
-
* - Feature flags tab with runtime toggle support
|
|
9
|
-
*
|
|
10
|
-
* Saves changes via configManager.set(key, value) or featureFlagManager methods.
|
|
11
|
-
*/
|
|
1
|
+
/** SettingsModal state for the /settings and /config fullscreen workspace. */
|
|
12
2
|
|
|
13
3
|
import { CONFIG_SCHEMA, type ConfigKey, type PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config';
|
|
14
4
|
import type { ModelPickerTarget } from './model-picker.ts';
|
|
@@ -40,24 +30,11 @@ import {
|
|
|
40
30
|
type SettingEntry,
|
|
41
31
|
type SettingsCategory,
|
|
42
32
|
type SettingsFocusPane,
|
|
33
|
+
type SettingsModalChangeHandler,
|
|
34
|
+
type SettingsModalOpenOptions,
|
|
43
35
|
type SubscriptionEntry,
|
|
44
36
|
} from './settings-modal-types.ts';
|
|
45
|
-
|
|
46
|
-
export interface SettingsModalChange {
|
|
47
|
-
readonly key: ConfigKey;
|
|
48
|
-
readonly previousValue: unknown;
|
|
49
|
-
readonly value: unknown;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface SettingsModalChangeResult {
|
|
53
|
-
readonly message?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
|
|
57
|
-
|
|
58
|
-
export interface SettingsModalOpenOptions {
|
|
59
|
-
readonly onSettingApplied?: SettingsModalChangeHandler;
|
|
60
|
-
}
|
|
37
|
+
import { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
|
|
61
38
|
|
|
62
39
|
export {
|
|
63
40
|
SETTINGS_CATEGORIES,
|
|
@@ -67,8 +44,13 @@ export {
|
|
|
67
44
|
type SettingEntry,
|
|
68
45
|
type SettingsCategory,
|
|
69
46
|
type SettingsFocusPane,
|
|
47
|
+
type SettingsModalChange,
|
|
48
|
+
type SettingsModalChangeHandler,
|
|
49
|
+
type SettingsModalChangeResult,
|
|
50
|
+
type SettingsModalOpenOptions,
|
|
70
51
|
type SubscriptionEntry,
|
|
71
52
|
} from './settings-modal-types.ts';
|
|
53
|
+
export { AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON, isExternalDaemonOwnedSettingKey } from './settings-modal-agent-policy.ts';
|
|
72
54
|
|
|
73
55
|
// ---------------------------------------------------------------------------
|
|
74
56
|
// SettingsModal
|
|
@@ -369,6 +351,7 @@ export class SettingsModal {
|
|
|
369
351
|
|
|
370
352
|
const entry = this.getSelected();
|
|
371
353
|
if (!entry || !this.configManager) return;
|
|
354
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return;
|
|
372
355
|
|
|
373
356
|
const { setting } = entry;
|
|
374
357
|
|
|
@@ -433,6 +416,7 @@ export class SettingsModal {
|
|
|
433
416
|
|
|
434
417
|
const entry = this.getSelected();
|
|
435
418
|
if (!entry || !this.configManager) return;
|
|
419
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return;
|
|
436
420
|
const { setting } = entry;
|
|
437
421
|
|
|
438
422
|
if (setting.type === 'boolean') {
|
|
@@ -554,6 +538,11 @@ export class SettingsModal {
|
|
|
554
538
|
|
|
555
539
|
const entry = this.getSelected();
|
|
556
540
|
if (!entry || !this.configManager) return false;
|
|
541
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) {
|
|
542
|
+
this.editingMode = false;
|
|
543
|
+
this.editBuffer = '';
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
557
546
|
|
|
558
547
|
const { setting } = entry;
|
|
559
548
|
let parsed: unknown = this.editBuffer;
|
|
@@ -600,6 +589,7 @@ export class SettingsModal {
|
|
|
600
589
|
if (this.editingMode || !this.configManager) return null;
|
|
601
590
|
const entry = this.getSelected();
|
|
602
591
|
if (!entry) return null;
|
|
592
|
+
if (this._blockExternalDaemonOwnedSetting(entry)) return null;
|
|
603
593
|
const key = entry.setting.key as ConfigKey;
|
|
604
594
|
this._setValue(key, entry.setting.default);
|
|
605
595
|
if (isSecretConfigKey(key) && this.secretsManager) {
|
|
@@ -635,15 +625,16 @@ export class SettingsModal {
|
|
|
635
625
|
const cat = rawCat as SettingsCategory;
|
|
636
626
|
const currentValue = configManager.get(setting.key as ConfigKey);
|
|
637
627
|
const resolved = getResolvedSettingLookup(configManager, setting.key as ConfigKey)?.entry;
|
|
628
|
+
const daemonOwned = isExternalDaemonOwnedSettingKey(setting.key);
|
|
638
629
|
const entry: SettingEntry = {
|
|
639
630
|
setting,
|
|
640
631
|
currentValue,
|
|
641
632
|
isDefault: currentValue === setting.default,
|
|
642
633
|
effectiveSource: resolved?.effectiveSource,
|
|
643
|
-
locked: resolved?.locked,
|
|
634
|
+
locked: daemonOwned || resolved?.locked,
|
|
644
635
|
conflict: resolved?.conflict,
|
|
645
636
|
sourceLabel: resolved?.sourceLabel,
|
|
646
|
-
lockReason: resolved?.lockReason,
|
|
637
|
+
lockReason: daemonOwned ? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON : resolved?.lockReason,
|
|
647
638
|
};
|
|
648
639
|
if (this.groups.has(cat)) this.groups.get(cat)!.push(entry);
|
|
649
640
|
if ((rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') && this.groups.has('network')) {
|
|
@@ -752,6 +743,10 @@ export class SettingsModal {
|
|
|
752
743
|
|
|
753
744
|
private _setValue(key: ConfigKey, value: unknown): void {
|
|
754
745
|
if (!this.configManager) return;
|
|
746
|
+
if (isExternalDaemonOwnedSettingKey(key)) {
|
|
747
|
+
this.lastSettingEffectMessage = AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
755
750
|
// Diff previous value before writing — avoids false restart notices on no-op saves
|
|
756
751
|
const previousValue = this.configManager.get(key);
|
|
757
752
|
const isRestartKey = ['host', 'port', 'hostMode', 'enabled'].includes(key.split('.')[1] ?? '');
|
|
@@ -790,4 +785,10 @@ export class SettingsModal {
|
|
|
790
785
|
}
|
|
791
786
|
}
|
|
792
787
|
|
|
788
|
+
private _blockExternalDaemonOwnedSetting(entry: SettingEntry): boolean {
|
|
789
|
+
if (!isExternalDaemonOwnedSettingKey(entry.setting.key)) return false;
|
|
790
|
+
this.lastSettingEffectMessage = entry.lockReason ?? AGENT_EXTERNAL_DAEMON_SETTING_LOCK_REASON;
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
793
794
|
}
|
package/src/main.ts
CHANGED
|
@@ -20,8 +20,6 @@ import { registerBuiltinCommands } from './input/commands.ts';
|
|
|
20
20
|
import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
21
21
|
import { InputHistory } from './input/input-history.ts';
|
|
22
22
|
import { getTierPromptSupplement, getTierForContextWindow } from '@pellux/goodvibes-sdk/platform/providers';
|
|
23
|
-
import { GitStatusProvider } from './renderer/git-status.ts';
|
|
24
|
-
import type { GitHeaderInfo } from './renderer/git-status.ts';
|
|
25
23
|
import { createShellLayout } from './renderer/layout-engine.ts';
|
|
26
24
|
import { buildShellFooter, estimateShellFooterHeight } from './renderer/shell-surface.ts';
|
|
27
25
|
import { buildConversationViewport } from './renderer/conversation-layout.ts';
|
|
@@ -53,7 +51,6 @@ import { attachSpokenTurnModelRouting, createSpokenTurnInputOptions } from './au
|
|
|
53
51
|
import { allowTerminalWrite, installTuiTerminalOutputGuard } from './runtime/terminal-output-guard.ts';
|
|
54
52
|
import { ProjectPlanningCoordinator } from './planning/project-planning-coordinator.ts';
|
|
55
53
|
import { buildCommandArgsHint } from './input/command-args-hint.ts';
|
|
56
|
-
import { summarizeRunningAgents } from './renderer/process-summary.ts';
|
|
57
54
|
import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
|
|
58
55
|
|
|
59
56
|
const ALT_SCREEN_ENTER = '\x1b[?1049h';
|
|
@@ -93,8 +90,6 @@ async function main() {
|
|
|
93
90
|
commandRegistry,
|
|
94
91
|
inputHistory,
|
|
95
92
|
hookDispatcher,
|
|
96
|
-
gitStatusProvider,
|
|
97
|
-
lastGitInfoRef,
|
|
98
93
|
bootstrapUnsubs,
|
|
99
94
|
agentStatusIntervalRef,
|
|
100
95
|
orchestratorRefs,
|
|
@@ -133,15 +128,12 @@ async function main() {
|
|
|
133
128
|
const sessionSnapshot = uiServices.readModels.session.getSnapshot();
|
|
134
129
|
const tasksSnapshot = uiServices.readModels.tasks.getSnapshot();
|
|
135
130
|
const remoteSnapshot = uiServices.readModels.remote.getSnapshot();
|
|
136
|
-
const worktreeSnapshot = uiServices.readModels.worktrees.getSnapshot();
|
|
137
131
|
return {
|
|
138
132
|
pendingApprovals: sessionSnapshot.pendingApproval ? 1 : 0,
|
|
139
133
|
activeTasks: tasksSnapshot.tasks.filter((task) => task.status === 'running' || task.status === 'queued').length,
|
|
140
134
|
blockedTasks: tasksSnapshot.tasks.filter((task) => task.status === 'blocked').length,
|
|
141
135
|
remoteContracts: remoteSnapshot.contracts.length,
|
|
142
136
|
remoteRunners: remoteSnapshot.contracts.slice(0, 4).map((contract) => contract.runnerId),
|
|
143
|
-
worktreeCount: worktreeSnapshot.records.length,
|
|
144
|
-
worktreePaths: worktreeSnapshot.records.slice(0, 3).map((record) => record.path),
|
|
145
137
|
openPanels: panelManager.getAllOpen().map((panel) => panel.id),
|
|
146
138
|
};
|
|
147
139
|
};
|
|
@@ -478,15 +470,9 @@ async function main() {
|
|
|
478
470
|
// Cache the current model for consistent values across the entire render frame
|
|
479
471
|
const currentModel = providerRegistry.getCurrentModel();
|
|
480
472
|
const sessionSnapshot = uiServices.readModels.session.getSnapshot();
|
|
481
|
-
const agentSnapshot = uiServices.readModels.agents.getSnapshot();
|
|
482
473
|
|
|
483
|
-
const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined
|
|
484
|
-
const
|
|
485
|
-
(a) => a.status === 'running' || a.status === 'pending',
|
|
486
|
-
);
|
|
487
|
-
const runtimeAgents = agentSnapshot.active;
|
|
488
|
-
const runningAgentSummary = summarizeRunningAgents(managerAgents, runtimeAgents, ctx.services.wrfcController.listChains());
|
|
489
|
-
const runningAgentCount = runningAgentSummary.count;
|
|
474
|
+
const headerLines = UIFactory.createHeader(width, currentModel.id, currentModel.provider, conversation.title || undefined);
|
|
475
|
+
const runningAgentCount = 0;
|
|
490
476
|
const runningProcessCount = processManager.list().filter((p) => !p.status.startsWith('done')).length;
|
|
491
477
|
const cw = getPromptContentWidth();
|
|
492
478
|
const promptInfo = input.getWrappedPromptInfo(cw);
|
|
@@ -533,7 +519,7 @@ async function main() {
|
|
|
533
519
|
runningAgentCount,
|
|
534
520
|
runningProcessCount,
|
|
535
521
|
indicatorFocused: input.indicatorFocused,
|
|
536
|
-
runningAgentProgress:
|
|
522
|
+
runningAgentProgress: undefined,
|
|
537
523
|
composerMode: composerState.modeLabel,
|
|
538
524
|
composerStatus: composerState.statusLabel,
|
|
539
525
|
composerFlags: composerState.flags,
|
|
@@ -684,8 +670,6 @@ async function main() {
|
|
|
684
670
|
});
|
|
685
671
|
|
|
686
672
|
// --- Streaming speed + tool preview wiring ---
|
|
687
|
-
const refreshGit = () => gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
|
|
688
|
-
// Refresh git status after each turn completes or after tool results arrive
|
|
689
673
|
unsubs.push(uiServices.events.turns.on('TURN_COMPLETED', () => {
|
|
690
674
|
// Auto-save after every LLM turn so kills don't lose the session
|
|
691
675
|
try {
|
|
@@ -701,13 +685,6 @@ async function main() {
|
|
|
701
685
|
);
|
|
702
686
|
hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
|
|
703
687
|
} catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
|
|
704
|
-
refreshGit();
|
|
705
|
-
}));
|
|
706
|
-
unsubs.push(uiServices.events.tools.on('TOOL_SUCCEEDED', () => {
|
|
707
|
-
refreshGit();
|
|
708
|
-
}));
|
|
709
|
-
unsubs.push(uiServices.events.tools.on('TOOL_FAILED', () => {
|
|
710
|
-
refreshGit();
|
|
711
688
|
}));
|
|
712
689
|
|
|
713
690
|
unsubs.push(uiServices.events.turns.on('STREAM_START', () => {
|
|
@@ -45,6 +45,12 @@ export interface ProcessModalDeps {
|
|
|
45
45
|
readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
|
|
46
46
|
readonly processManager: Pick<ProcessManager, 'list' | 'getStatus' | 'stop'>;
|
|
47
47
|
readonly wrfcController: Pick<WrfcController, 'getChain'> & Partial<Pick<WrfcController, 'listChains'>>;
|
|
48
|
+
/**
|
|
49
|
+
* GoodVibes Agent must not present local AgentManager records as an owned
|
|
50
|
+
* execution lane. Tests for copied TUI primitives can opt into read-only
|
|
51
|
+
* display, but product runtime should keep this hidden.
|
|
52
|
+
*/
|
|
53
|
+
readonly agentEntries: 'hidden' | 'read-only';
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
type WrfcChainLike = {
|
|
@@ -504,14 +510,17 @@ export class ProcessModal {
|
|
|
504
510
|
const now = Date.now();
|
|
505
511
|
const result: ProcessEntry[] = [];
|
|
506
512
|
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
513
|
+
// Local AgentManager activity is hidden in the Agent product. Build/fix/review
|
|
514
|
+
// execution belongs to explicit GoodVibes TUI delegation, not a local lane.
|
|
515
|
+
if (this.deps.agentEntries === 'read-only') {
|
|
516
|
+
result.push(...buildAgentEntries(
|
|
517
|
+
manager.list(),
|
|
518
|
+
this.deps,
|
|
519
|
+
now,
|
|
520
|
+
(key) => this.groupOrder.get(key),
|
|
521
|
+
(key) => this.ensureGroupOrder(key),
|
|
522
|
+
));
|
|
523
|
+
}
|
|
515
524
|
|
|
516
525
|
// Background exec processes — only show running
|
|
517
526
|
const pm = this.deps.processManager;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Line } from '../types/grid.ts';
|
|
9
9
|
import type { SettingsModal, SettingEntry, FlagEntry, McpEntry, SubscriptionEntry, SettingsCategory } from '../input/settings-modal.ts';
|
|
10
|
-
import { SETTINGS_CATEGORIES, SETTINGS_CATEGORY_GROUPS } from '../input/settings-modal.ts';
|
|
10
|
+
import { isExternalDaemonOwnedSettingKey, SETTINGS_CATEGORIES, SETTINGS_CATEGORY_GROUPS } from '../input/settings-modal.ts';
|
|
11
11
|
import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
12
12
|
import { CATEGORY_LABELS, describeUiRouting, formatValue, getSettingLabel, inferSubscriptionRouteReason, valueColor } from './settings-modal-helpers.ts';
|
|
13
13
|
import { isSecretConfigKey } from '../config/secret-config.ts';
|
|
@@ -34,10 +34,10 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
34
34
|
wrfc: 'WRFC is external to normal Agent operation. Review these copied compatibility values only for explicit GoodVibes TUI build delegation.',
|
|
35
35
|
helper: 'Helper model defaults used by helper subsystems when they do not use the main chat route.',
|
|
36
36
|
tts: 'Text-to-speech provider, voice, and optional spoken-turn LLM overrides.',
|
|
37
|
-
service: '
|
|
38
|
-
controlPlane: '
|
|
39
|
-
httpListener: 'HTTP listener settings for webhook and integration ingress.',
|
|
40
|
-
web: '
|
|
37
|
+
service: 'External daemon service posture. Agent shows these copied compatibility keys for inspection only and does not install, start, stop, restart, or autostart services.',
|
|
38
|
+
controlPlane: 'External daemon control-plane settings for local admin/API access. Agent connects to this daemon and does not mutate its bind posture.',
|
|
39
|
+
httpListener: 'External HTTP listener settings for webhook and integration ingress. Agent does not start or expose the listener.',
|
|
40
|
+
web: 'External browser surface settings. Agent does not own the web listener or network bind lifecycle.',
|
|
41
41
|
batch: 'Batch execution settings, including local vs Cloudflare queue behavior.',
|
|
42
42
|
automation: 'Scheduled and automated run settings, concurrency, timeout, catch-up, cooldown, and retention behavior.',
|
|
43
43
|
watchers: 'File/process watcher heartbeat, polling, and recovery-window behavior.',
|
|
@@ -49,10 +49,10 @@ const CATEGORY_INFO: Record<SettingsCategory, string> = {
|
|
|
49
49
|
surfaces: 'External app surfaces such as Slack, Discord, ntfy, Home Assistant, Telegram, webhooks, chat bridges, and messaging providers.',
|
|
50
50
|
cloudflare: 'Optional Cloudflare control plane, batch queue, Worker, Tunnel, Access, DNS, KV, Durable Objects, Secrets Store, and R2 settings.',
|
|
51
51
|
release: 'Release-channel preference.',
|
|
52
|
-
danger: 'High-impact switches
|
|
52
|
+
danger: 'High-impact daemon and listener switches. Agent renders daemon-owned switches read-only; use GoodVibes TUI or the daemon host to change them.',
|
|
53
53
|
tools: 'Tool LLM and helper model routing. Empty provider/model values inherit the active chat route unless a specific helper/tool route is set.',
|
|
54
54
|
flags: 'Feature flags are SDK runtime gates. They are separate from normal config keys because they enable or disable staged runtime behavior.',
|
|
55
|
-
network: '
|
|
55
|
+
network: 'Read-only view of external daemon control-plane, HTTP listener, and browser web bind posture plus editable non-daemon network settings.',
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const ENUM_VALUE_DESCRIPTIONS: Record<string, Record<string, string>> = {
|
|
@@ -479,6 +479,10 @@ function footerText(modal: SettingsModal): string {
|
|
|
479
479
|
if (modal.currentCategory === 'subscriptions') return 'Focus settings · Up/Down provider · Left categories · Tab pane · Enter review/sign out · Esc close';
|
|
480
480
|
if (modal.currentCategory === 'mcp') return 'Focus settings · Up/Down server · Left categories · Tab pane · Enter edit trust · Esc close';
|
|
481
481
|
if (modal.currentCategory === 'flags') return 'Focus feature flags · Up/Down flag · Left categories · Tab pane · Enter/Space toggle · Esc close';
|
|
482
|
+
const selected = modal.getSelected();
|
|
483
|
+
if (selected && isExternalDaemonOwnedSettingKey(selected.setting.key)) {
|
|
484
|
+
return 'Read-only external daemon setting · Change from GoodVibes TUI or daemon host · Esc close';
|
|
485
|
+
}
|
|
482
486
|
return 'Focus settings · Up/Down setting · Left categories · Tab pane · Enter/Space edit/toggle · R reset · Esc close';
|
|
483
487
|
}
|
|
484
488
|
|
|
@@ -488,7 +492,7 @@ export function renderSettingsModal(
|
|
|
488
492
|
viewportHeight = 24,
|
|
489
493
|
): Line[] {
|
|
490
494
|
const notices = [
|
|
491
|
-
...(modal.lastSaveTriggeredRestart ? [`
|
|
495
|
+
...(modal.lastSaveTriggeredRestart ? [`External daemon owner must restart ${modal.lastSaveTriggeredRestart}`] : []),
|
|
492
496
|
...(modal.lastSettingEffectMessage ? [modal.lastSettingEffectMessage] : []),
|
|
493
497
|
];
|
|
494
498
|
const metrics = getFullscreenWorkspaceMetrics({ width, height: viewportHeight });
|
|
@@ -2,7 +2,6 @@ import { type Line, type Cell, createEmptyLine, createEmptyCell } from '../types
|
|
|
2
2
|
import { LAYOUT } from './layout.ts';
|
|
3
3
|
import { VERSION } from '../version.ts';
|
|
4
4
|
import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor } from '../utils/terminal-width.ts';
|
|
5
|
-
import type { GitHeaderInfo } from './git-status.ts';
|
|
6
5
|
import { renderConversationFragment, renderConversationStatusLine, type ConversationStatusSegment } from './conversation-surface.ts';
|
|
7
6
|
import { GLYPHS } from './ui-primitives.ts';
|
|
8
7
|
|
|
@@ -11,22 +10,6 @@ const GRADIENT_CYCLE_FRAMES = 50;
|
|
|
11
10
|
/** Number of frames before rotating to the next thinking phrase (~30 seconds at 80ms/frame). */
|
|
12
11
|
const PHRASE_ROTATION_FRAMES = 375;
|
|
13
12
|
|
|
14
|
-
/** Build the git segment string and its display width. Single source of truth for header layout. */
|
|
15
|
-
function buildGitSegment(gitInfo: GitHeaderInfo): { text: string; width: number } {
|
|
16
|
-
const branch = ` git:${gitInfo.branch}`;
|
|
17
|
-
if (gitInfo.dirty) {
|
|
18
|
-
const text = `${branch} * `;
|
|
19
|
-
return { text, width: getDisplayWidth(text) };
|
|
20
|
-
}
|
|
21
|
-
if (gitInfo.ahead > 0 || gitInfo.behind > 0) {
|
|
22
|
-
const arrows = (gitInfo.ahead > 0 ? ` +${gitInfo.ahead}` : '') + (gitInfo.behind > 0 ? ` -${gitInfo.behind}` : '');
|
|
23
|
-
const text = `${branch}${arrows} `;
|
|
24
|
-
return { text, width: getDisplayWidth(text) };
|
|
25
|
-
}
|
|
26
|
-
const text = `${branch} `;
|
|
27
|
-
return { text, width: getDisplayWidth(text) };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
13
|
/** Format a number: up to 999, then 1.0k, 1.0M, 1.0B, 1.0T */
|
|
31
14
|
function fmtNum(n: number): string {
|
|
32
15
|
if (n < 1000) return String(n);
|
|
@@ -40,7 +23,7 @@ function fmtNum(n: number): string {
|
|
|
40
23
|
* UIFactory - Generates standard UI fragments without needing Ink/React overhead.
|
|
41
24
|
*/
|
|
42
25
|
export class UIFactory {
|
|
43
|
-
public static createHeader(width: number, model: string, provider: string, title?: string
|
|
26
|
+
public static createHeader(width: number, model: string, provider: string, title?: string): Line[] {
|
|
44
27
|
const lines: Line[] = [];
|
|
45
28
|
const CYAN = '#00ffff';
|
|
46
29
|
const GREY = '244';
|
|
@@ -56,9 +39,8 @@ export class UIFactory {
|
|
|
56
39
|
// Optional conversation title — shown after brand/ver, truncated to fit
|
|
57
40
|
if (title) {
|
|
58
41
|
const titleStr = `│ ${title} `;
|
|
59
|
-
// Reserve space for
|
|
60
|
-
const
|
|
61
|
-
const rightReserved = getDisplayWidth(stats + prov) + gitReserved;
|
|
42
|
+
// Reserve space for model/provider on the right.
|
|
43
|
+
const rightReserved = getDisplayWidth(stats + prov);
|
|
62
44
|
const maxTitleW = width - curX - rightReserved - 1;
|
|
63
45
|
let displayTitle: string;
|
|
64
46
|
if (getDisplayWidth(titleStr) <= maxTitleW) {
|
|
@@ -76,19 +58,9 @@ export class UIFactory {
|
|
|
76
58
|
}
|
|
77
59
|
for (const char of displayTitle) { if (curX < width) line[curX++] = { char, fg: TITLE_COLOR, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
|
|
78
60
|
}
|
|
79
|
-
// Build git info segment
|
|
80
|
-
let gitStr = '';
|
|
81
|
-
let gitFg = '238';
|
|
82
|
-
if (gitInfo) {
|
|
83
|
-
gitStr = buildGitSegment(gitInfo).text;
|
|
84
|
-
if (gitInfo.dirty || gitInfo.ahead > 0 || gitInfo.behind > 0) {
|
|
85
|
-
gitFg = '220'; // yellow when dirty or out-of-sync
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
61
|
const rightSideText = stats + prov;
|
|
89
|
-
const rightSideW = getDisplayWidth(rightSideText)
|
|
62
|
+
const rightSideW = getDisplayWidth(rightSideText);
|
|
90
63
|
let rightX = width - rightSideW;
|
|
91
|
-
for (const char of gitStr) { if (rightX >= 0 && rightX < width) line[rightX++] = { char, fg: gitFg, bg: '', bold: false, dim: !gitInfo?.dirty && !(gitInfo?.ahead || gitInfo?.behind), underline: false, italic: false, strikethrough: false }; }
|
|
92
64
|
for (const char of stats) { if (rightX < width) line[rightX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
|
|
93
65
|
for (const char of prov) { if (rightX < width) line[rightX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
|
|
94
66
|
lines.push(line);
|
|
@@ -11,8 +11,6 @@ import type { OpsControlPlane } from '@/runtime/index.ts';
|
|
|
11
11
|
import { CommandRegistry } from '../input/command-registry.ts';
|
|
12
12
|
import { registerBuiltinCommands } from '../input/commands.ts';
|
|
13
13
|
import { InputHistory } from '../input/input-history.ts';
|
|
14
|
-
import { GitStatusProvider } from '../renderer/git-status.ts';
|
|
15
|
-
import type { GitHeaderInfo } from '../renderer/git-status.ts';
|
|
16
14
|
import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/permissions';
|
|
17
15
|
import { registerBuiltinPanels } from '../panels/builtin-panels.ts';
|
|
18
16
|
import { SystemMessagesPanel } from '../panels/system-messages-panel.ts';
|
|
@@ -38,8 +36,6 @@ import { createKnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
|
38
36
|
export interface BootstrapShellState {
|
|
39
37
|
readonly commandRegistry: CommandRegistry;
|
|
40
38
|
readonly commandContext: CommandContext;
|
|
41
|
-
readonly gitStatusProvider: GitStatusProvider;
|
|
42
|
-
readonly lastGitInfoRef: { value: GitHeaderInfo | undefined };
|
|
43
39
|
readonly inputHistory: InputHistory;
|
|
44
40
|
readonly systemMessageRouter: SystemMessageRouter;
|
|
45
41
|
}
|
|
@@ -260,13 +256,6 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
260
256
|
});
|
|
261
257
|
commandContextRef = commandContext;
|
|
262
258
|
|
|
263
|
-
const gitStatusProvider = new GitStatusProvider(services.workingDirectory);
|
|
264
|
-
const lastGitInfoRef = { value: undefined as GitHeaderInfo | undefined };
|
|
265
|
-
gitStatusProvider.getStatus().then((info) => {
|
|
266
|
-
lastGitInfoRef.value = info;
|
|
267
|
-
requestRender();
|
|
268
|
-
}).catch(() => { /* non-fatal */ });
|
|
269
|
-
|
|
270
259
|
const saveHistory = configManager.get('behavior.saveHistory') as boolean;
|
|
271
260
|
const inputHistory = new InputHistory({
|
|
272
261
|
historyPath: services.shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'input-history.json'),
|
|
@@ -276,8 +265,6 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
276
265
|
return {
|
|
277
266
|
commandRegistry,
|
|
278
267
|
commandContext,
|
|
279
|
-
gitStatusProvider,
|
|
280
|
-
lastGitInfoRef,
|
|
281
268
|
inputHistory,
|
|
282
269
|
systemMessageRouter,
|
|
283
270
|
};
|