@pellux/goodvibes-tui 0.19.64 → 0.19.65
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 +20 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/input/settings-modal.ts +39 -0
- package/src/main.ts +2 -0
- package/src/renderer/settings-modal.ts +5 -1
- package/src/shell/service-settings-sync.ts +273 -0
- package/src/shell/ui-openers.ts +11 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,26 @@ All notable changes to GoodVibes TUI.
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
+
## [0.19.65] — 2026-05-05
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Made `/config service` changes apply to the OS service layer instead of only writing `~/.goodvibes/tui/settings.json`.
|
|
15
|
+
- Enabling `service.autostart` now enables service mode if needed, writes the platform service definition, reloads systemd user units when applicable, and starts/enables the service.
|
|
16
|
+
- Disabling `service.autostart` or `service.enabled` now disables/removes the platform service definition instead of leaving stale OS autostart state behind.
|
|
17
|
+
- Service definition changes such as restart-on-failure, platform, service name, or log path now rewrite and restart the OS service when service mode and autostart are active.
|
|
18
|
+
- Added a visible `/config` status notice for service install/start/disable/update results after a setting change.
|
|
19
|
+
|
|
20
|
+
### Verified
|
|
21
|
+
- `bun test src/test/shell/service-settings-sync.test.ts src/test/input/settings-modal-network.test.ts`
|
|
22
|
+
- `bun run test`
|
|
23
|
+
- `bunx tsc --noEmit`
|
|
24
|
+
- `bun run build:prod`
|
|
25
|
+
- `bun run smoke:tui`
|
|
26
|
+
- `bun run smoke:daemon`
|
|
27
|
+
- `git diff --check`
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
11
31
|
## [0.19.64] — 2026-05-05
|
|
12
32
|
|
|
13
33
|
### Fixed
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.65",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -42,6 +42,22 @@ import {
|
|
|
42
42
|
type SubscriptionEntry,
|
|
43
43
|
} from './settings-modal-types.ts';
|
|
44
44
|
|
|
45
|
+
export interface SettingsModalChange {
|
|
46
|
+
readonly key: ConfigKey;
|
|
47
|
+
readonly previousValue: unknown;
|
|
48
|
+
readonly value: unknown;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SettingsModalChangeResult {
|
|
52
|
+
readonly message?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type SettingsModalChangeHandler = (change: SettingsModalChange) => SettingsModalChangeResult | void;
|
|
56
|
+
|
|
57
|
+
export interface SettingsModalOpenOptions {
|
|
58
|
+
readonly onSettingApplied?: SettingsModalChangeHandler;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
export {
|
|
46
62
|
SETTINGS_CATEGORIES,
|
|
47
63
|
type FlagEntry,
|
|
@@ -104,6 +120,7 @@ export class SettingsModal {
|
|
|
104
120
|
* Cleared on next open() or close().
|
|
105
121
|
*/
|
|
106
122
|
public lastSaveTriggeredRestart: 'control-plane' | 'http-listener' | 'web' | null = null;
|
|
123
|
+
public lastSettingEffectMessage: string | null = null;
|
|
107
124
|
|
|
108
125
|
private configManager: ConfigManager | null = null;
|
|
109
126
|
private secretsManager: SettingsSecretsManager | null = null;
|
|
@@ -111,6 +128,7 @@ export class SettingsModal {
|
|
|
111
128
|
private mcpRegistry: McpRegistry | null = null;
|
|
112
129
|
private subscriptionManager: SubscriptionManager | null = null;
|
|
113
130
|
private serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'> | null = null;
|
|
131
|
+
private onSettingApplied: SettingsModalChangeHandler | null = null;
|
|
114
132
|
|
|
115
133
|
/**
|
|
116
134
|
* Open the modal, loading current config values from configManager.
|
|
@@ -125,6 +143,7 @@ export class SettingsModal {
|
|
|
125
143
|
serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>,
|
|
126
144
|
mcpRegistry?: McpRegistry,
|
|
127
145
|
secretsManager?: SettingsSecretsManager,
|
|
146
|
+
options?: SettingsModalOpenOptions,
|
|
128
147
|
): void {
|
|
129
148
|
this.configManager = configManager;
|
|
130
149
|
this.secretsManager = secretsManager ?? null;
|
|
@@ -132,6 +151,7 @@ export class SettingsModal {
|
|
|
132
151
|
this.subscriptionManager = subscriptionManager;
|
|
133
152
|
this.serviceRegistry = serviceRegistry;
|
|
134
153
|
this.mcpRegistry = mcpRegistry ?? null;
|
|
154
|
+
this.onSettingApplied = options?.onSettingApplied ?? null;
|
|
135
155
|
this._loadGroups(configManager);
|
|
136
156
|
this._loadFlagEntries();
|
|
137
157
|
this._loadMcpEntries();
|
|
@@ -147,6 +167,7 @@ export class SettingsModal {
|
|
|
147
167
|
this.mcpAllowAllConfirmationTarget = null;
|
|
148
168
|
this.subscriptionLogoutConfirmationTarget = null;
|
|
149
169
|
this.lastSaveTriggeredRestart = null;
|
|
170
|
+
this.lastSettingEffectMessage = null;
|
|
150
171
|
this.active = true;
|
|
151
172
|
}
|
|
152
173
|
|
|
@@ -160,8 +181,10 @@ export class SettingsModal {
|
|
|
160
181
|
this.mcpAllowAllConfirmationTarget = null;
|
|
161
182
|
this.subscriptionLogoutConfirmationTarget = null;
|
|
162
183
|
this.lastSaveTriggeredRestart = null;
|
|
184
|
+
this.lastSettingEffectMessage = null;
|
|
163
185
|
this.serviceRegistry = null;
|
|
164
186
|
this.secretsManager = null;
|
|
187
|
+
this.onSettingApplied = null;
|
|
165
188
|
this.focusPane = 'settings';
|
|
166
189
|
}
|
|
167
190
|
|
|
@@ -715,6 +738,16 @@ export class SettingsModal {
|
|
|
715
738
|
return items;
|
|
716
739
|
}
|
|
717
740
|
|
|
741
|
+
private _refreshAllEntries(): void {
|
|
742
|
+
if (!this.configManager) return;
|
|
743
|
+
for (const entries of this.groups.values()) {
|
|
744
|
+
for (const entry of entries) {
|
|
745
|
+
entry.currentValue = this.configManager.get(entry.setting.key as ConfigKey);
|
|
746
|
+
entry.isDefault = entry.currentValue === entry.setting.default;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
718
751
|
private _setValue(key: ConfigKey, value: unknown): void {
|
|
719
752
|
if (!this.configManager) return;
|
|
720
753
|
// Diff previous value before writing — avoids false restart notices on no-op saves
|
|
@@ -744,8 +777,14 @@ export class SettingsModal {
|
|
|
744
777
|
entry.isDefault = entry.currentValue === entry.setting.default;
|
|
745
778
|
}
|
|
746
779
|
}
|
|
780
|
+
if (previousValue !== value && this.onSettingApplied) {
|
|
781
|
+
const result = this.onSettingApplied({ key, previousValue, value });
|
|
782
|
+
this.lastSettingEffectMessage = result?.message ?? null;
|
|
783
|
+
this._refreshAllEntries();
|
|
784
|
+
}
|
|
747
785
|
} catch (e) {
|
|
748
786
|
logger.error('SettingsModal: failed to set config value', { key, error: summarizeError(e) });
|
|
787
|
+
this.lastSettingEffectMessage = `Save failed: ${summarizeError(e)}`;
|
|
749
788
|
}
|
|
750
789
|
}
|
|
751
790
|
|
package/src/main.ts
CHANGED
|
@@ -678,6 +678,8 @@ async function main() {
|
|
|
678
678
|
subscriptionManager,
|
|
679
679
|
secretsManager,
|
|
680
680
|
serviceRegistry: ctx.services.serviceRegistry,
|
|
681
|
+
workingDirectory: workingDir,
|
|
682
|
+
homeDirectory,
|
|
681
683
|
getConfiguredProviderIds: ctx._getConfiguredProviderIds,
|
|
682
684
|
getPinned: ctx._getPinned,
|
|
683
685
|
render,
|
|
@@ -590,7 +590,11 @@ export function renderSettingsModal(
|
|
|
590
590
|
const header = contentLine(safeWidth, PALETTE.footerBg);
|
|
591
591
|
drawVertical(header, dividerX, PALETTE.footerBg);
|
|
592
592
|
writeText(header, leftStart + 1, leftWidth - 2, 'Categories', { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
|
|
593
|
-
const
|
|
593
|
+
const notices = [
|
|
594
|
+
...(modal.lastSaveTriggeredRestart ? [`Restarting ${modal.lastSaveTriggeredRestart}`] : []),
|
|
595
|
+
...(modal.lastSettingEffectMessage ? [modal.lastSettingEffectMessage] : []),
|
|
596
|
+
];
|
|
597
|
+
const headerText = `${CATEGORY_LABELS[modal.currentCategory]} (${categoryItemCount(modal, modal.currentCategory)})${notices.length > 0 ? ` · ${notices.join(' · ')}` : ''}`;
|
|
594
598
|
writeText(header, centerStart + 1, centerWidth - 2, headerText, { fg: PALETTE.subtitle, bold: true, bg: PALETTE.footerBg });
|
|
595
599
|
lines.push(header);
|
|
596
600
|
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
4
|
+
import type { ManagedServiceStatus } from '@pellux/goodvibes-sdk/platform/daemon';
|
|
5
|
+
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
6
|
+
import {
|
|
7
|
+
createPlatformServiceManager,
|
|
8
|
+
getServiceStateRoot,
|
|
9
|
+
type CliServiceRuntime,
|
|
10
|
+
} from '../cli/service-posture.ts';
|
|
11
|
+
|
|
12
|
+
type ManagedServiceAction = 'install' | 'uninstall' | 'start' | 'stop' | 'restart' | 'status';
|
|
13
|
+
|
|
14
|
+
export interface ServiceManagerLike {
|
|
15
|
+
status(): ManagedServiceStatus;
|
|
16
|
+
install(): ManagedServiceStatus;
|
|
17
|
+
uninstall(): ManagedServiceStatus;
|
|
18
|
+
start(): ManagedServiceStatus;
|
|
19
|
+
stop(): ManagedServiceStatus;
|
|
20
|
+
restart(): ManagedServiceStatus;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CommandResult {
|
|
24
|
+
readonly status: number | null;
|
|
25
|
+
readonly stdout?: string;
|
|
26
|
+
readonly stderr?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ServiceSettingsSyncChange {
|
|
30
|
+
readonly key: ConfigKey;
|
|
31
|
+
readonly previousValue: unknown;
|
|
32
|
+
readonly value: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ServiceSettingsSyncResult {
|
|
36
|
+
readonly handled: boolean;
|
|
37
|
+
readonly action?: ManagedServiceAction | 'install-start' | 'disable';
|
|
38
|
+
readonly status?: ManagedServiceStatus;
|
|
39
|
+
readonly message?: string;
|
|
40
|
+
readonly error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ServiceSettingsSyncOptions {
|
|
44
|
+
readonly createManager?: (runtime: CliServiceRuntime) => ServiceManagerLike;
|
|
45
|
+
readonly runCommand?: (command: string, args: readonly string[]) => CommandResult;
|
|
46
|
+
readonly mkdir?: typeof mkdirSync;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const SERVICE_DEFINITION_KEYS = new Set<ConfigKey>([
|
|
50
|
+
'service.restartOnFailure',
|
|
51
|
+
'service.platform',
|
|
52
|
+
'service.serviceName',
|
|
53
|
+
'service.logPath',
|
|
54
|
+
] as ConfigKey[]);
|
|
55
|
+
|
|
56
|
+
function runCommand(command: string, args: readonly string[], options: ServiceSettingsSyncOptions): CommandResult {
|
|
57
|
+
if (options.runCommand) return options.runCommand(command, args);
|
|
58
|
+
return spawnSync(command, [...args], { stdio: 'pipe', encoding: 'utf-8' });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function commandError(result: CommandResult): string | null {
|
|
62
|
+
if ((result.status ?? 1) === 0) return null;
|
|
63
|
+
return ((result.stderr ?? '') || (result.stdout ?? '') || `command exited with ${result.status}`).trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function serviceName(runtime: CliServiceRuntime, fallback = 'goodvibes'): string {
|
|
67
|
+
return String(runtime.configManager.get('service.serviceName') ?? fallback).trim() || fallback;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function runSystemd(runtime: CliServiceRuntime, args: readonly string[], options: ServiceSettingsSyncOptions): string | null {
|
|
71
|
+
const result = runCommand('systemctl', ['--user', ...args], options);
|
|
72
|
+
return commandError(result);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function reloadSystemdIfNeeded(
|
|
76
|
+
runtime: CliServiceRuntime,
|
|
77
|
+
status: ManagedServiceStatus,
|
|
78
|
+
options: ServiceSettingsSyncOptions,
|
|
79
|
+
): string | null {
|
|
80
|
+
if (status.platform !== 'systemd') return null;
|
|
81
|
+
return runSystemd(runtime, ['daemon-reload'], options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function disableSystemService(
|
|
85
|
+
runtime: CliServiceRuntime,
|
|
86
|
+
manager: ServiceManagerLike,
|
|
87
|
+
options: ServiceSettingsSyncOptions,
|
|
88
|
+
): ServiceSettingsSyncResult {
|
|
89
|
+
const before = manager.status();
|
|
90
|
+
let disableError: string | null = null;
|
|
91
|
+
if (before.platform === 'systemd') {
|
|
92
|
+
disableError = before.installed || before.running
|
|
93
|
+
? runSystemd(runtime, ['disable', '--now', `${serviceName(runtime)}.service`], options)
|
|
94
|
+
: null;
|
|
95
|
+
if (disableError) {
|
|
96
|
+
logger.warn('Settings service sync: systemd disable failed', { error: disableError });
|
|
97
|
+
}
|
|
98
|
+
} else if (before.running || before.installed) {
|
|
99
|
+
const stopped = manager.stop();
|
|
100
|
+
if (stopped.actionError) {
|
|
101
|
+
return {
|
|
102
|
+
handled: true,
|
|
103
|
+
action: 'stop',
|
|
104
|
+
status: stopped,
|
|
105
|
+
message: `Service disable failed: ${stopped.actionError}`,
|
|
106
|
+
error: stopped.actionError,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const uninstalled = manager.uninstall();
|
|
112
|
+
const reloadError = reloadSystemdIfNeeded(runtime, uninstalled, options);
|
|
113
|
+
const error = uninstalled.actionError ?? reloadError ?? disableError ?? undefined;
|
|
114
|
+
return {
|
|
115
|
+
handled: true,
|
|
116
|
+
action: 'disable',
|
|
117
|
+
status: uninstalled,
|
|
118
|
+
message: error ? `Service disable failed: ${error}` : 'OS service disabled',
|
|
119
|
+
...(error ? { error } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function installAndStartSystemService(
|
|
124
|
+
runtime: CliServiceRuntime,
|
|
125
|
+
manager: ServiceManagerLike,
|
|
126
|
+
options: ServiceSettingsSyncOptions,
|
|
127
|
+
): ServiceSettingsSyncResult {
|
|
128
|
+
(options.mkdir ?? mkdirSync)(getServiceStateRoot(runtime), { recursive: true });
|
|
129
|
+
const installed = manager.install();
|
|
130
|
+
if (installed.actionError) {
|
|
131
|
+
return {
|
|
132
|
+
handled: true,
|
|
133
|
+
action: 'install',
|
|
134
|
+
status: installed,
|
|
135
|
+
message: `Service install failed: ${installed.actionError}`,
|
|
136
|
+
error: installed.actionError,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const reloadError = reloadSystemdIfNeeded(runtime, installed, options);
|
|
141
|
+
if (reloadError) {
|
|
142
|
+
return {
|
|
143
|
+
handled: true,
|
|
144
|
+
action: 'install',
|
|
145
|
+
status: installed,
|
|
146
|
+
message: `Service install failed: ${reloadError}`,
|
|
147
|
+
error: reloadError,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const started = manager.start();
|
|
152
|
+
const error = started.actionError ?? undefined;
|
|
153
|
+
return {
|
|
154
|
+
handled: true,
|
|
155
|
+
action: 'install-start',
|
|
156
|
+
status: started,
|
|
157
|
+
message: error ? `Service start failed: ${error}` : 'OS service installed and started',
|
|
158
|
+
...(error ? { error } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function refreshInstalledSystemService(
|
|
163
|
+
runtime: CliServiceRuntime,
|
|
164
|
+
manager: ServiceManagerLike,
|
|
165
|
+
options: ServiceSettingsSyncOptions,
|
|
166
|
+
): ServiceSettingsSyncResult {
|
|
167
|
+
const before = manager.status();
|
|
168
|
+
if (!before.installed && runtime.configManager.get('service.autostart') !== true) {
|
|
169
|
+
return {
|
|
170
|
+
handled: true,
|
|
171
|
+
action: 'status',
|
|
172
|
+
status: before,
|
|
173
|
+
message: 'Service setting saved',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const installed = manager.install();
|
|
178
|
+
if (installed.actionError) {
|
|
179
|
+
return {
|
|
180
|
+
handled: true,
|
|
181
|
+
action: 'install',
|
|
182
|
+
status: installed,
|
|
183
|
+
message: `Service update failed: ${installed.actionError}`,
|
|
184
|
+
error: installed.actionError,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const reloadError = reloadSystemdIfNeeded(runtime, installed, options);
|
|
189
|
+
if (reloadError) {
|
|
190
|
+
return {
|
|
191
|
+
handled: true,
|
|
192
|
+
action: 'install',
|
|
193
|
+
status: installed,
|
|
194
|
+
message: `Service update failed: ${reloadError}`,
|
|
195
|
+
error: reloadError,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const next = before.running ? manager.restart() : manager.start();
|
|
200
|
+
const error = next.actionError ?? undefined;
|
|
201
|
+
return {
|
|
202
|
+
handled: true,
|
|
203
|
+
action: before.running ? 'restart' : 'start',
|
|
204
|
+
status: next,
|
|
205
|
+
message: error ? `Service update failed: ${error}` : 'OS service updated',
|
|
206
|
+
...(error ? { error } : {}),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function syncServiceSettingToPlatform(
|
|
211
|
+
runtime: CliServiceRuntime,
|
|
212
|
+
change: ServiceSettingsSyncChange,
|
|
213
|
+
options: ServiceSettingsSyncOptions = {},
|
|
214
|
+
): ServiceSettingsSyncResult {
|
|
215
|
+
if (!String(change.key).startsWith('service.')) return { handled: false };
|
|
216
|
+
if (change.previousValue === change.value) return { handled: true, message: 'Service setting unchanged' };
|
|
217
|
+
|
|
218
|
+
const manager = options.createManager?.(runtime) ?? createPlatformServiceManager(runtime);
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
if (change.key === 'service.autostart') {
|
|
222
|
+
if (change.value === true) {
|
|
223
|
+
if (runtime.configManager.get('service.enabled') !== true) {
|
|
224
|
+
runtime.configManager.setDynamic('service.enabled', true);
|
|
225
|
+
}
|
|
226
|
+
return installAndStartSystemService(runtime, manager, options);
|
|
227
|
+
}
|
|
228
|
+
return disableSystemService(runtime, manager, options);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (change.key === 'service.enabled') {
|
|
232
|
+
if (change.value === false) {
|
|
233
|
+
if (runtime.configManager.get('service.autostart') === true) {
|
|
234
|
+
runtime.configManager.setDynamic('service.autostart', false);
|
|
235
|
+
}
|
|
236
|
+
return disableSystemService(runtime, manager, options);
|
|
237
|
+
}
|
|
238
|
+
if (runtime.configManager.get('service.autostart') === true) {
|
|
239
|
+
return installAndStartSystemService(runtime, manager, options);
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
handled: true,
|
|
243
|
+
action: 'status',
|
|
244
|
+
status: manager.status(),
|
|
245
|
+
message: 'Service mode saved; enable autostart to install the OS service',
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (SERVICE_DEFINITION_KEYS.has(change.key)) {
|
|
250
|
+
if (runtime.configManager.get('service.enabled') === true && runtime.configManager.get('service.autostart') === true) {
|
|
251
|
+
return refreshInstalledSystemService(runtime, manager, options);
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
handled: true,
|
|
255
|
+
action: 'status',
|
|
256
|
+
status: manager.status(),
|
|
257
|
+
message: 'Service setting saved; enable autostart to install the OS service',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const summarized = summarizeError(error);
|
|
262
|
+
logger.error('Settings service sync failed', { key: change.key, error: summarized });
|
|
263
|
+
return {
|
|
264
|
+
handled: true,
|
|
265
|
+
action: 'status',
|
|
266
|
+
status: manager.status(),
|
|
267
|
+
message: `Service sync failed: ${summarized}`,
|
|
268
|
+
error: summarized,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { handled: false };
|
|
273
|
+
}
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config'
|
|
|
12
12
|
import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
13
13
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
14
14
|
import type { ModelPickerTargetInfo } from '../input/model-picker.ts';
|
|
15
|
+
import { syncServiceSettingToPlatform } from './service-settings-sync.ts';
|
|
15
16
|
|
|
16
17
|
type WireShellUiOpenersOptions = {
|
|
17
18
|
commandContext: CommandContext;
|
|
@@ -26,6 +27,8 @@ type WireShellUiOpenersOptions = {
|
|
|
26
27
|
subscriptionManager: SubscriptionManager;
|
|
27
28
|
secretsManager?: Pick<SecretsManager, 'delete' | 'get' | 'set'>;
|
|
28
29
|
serviceRegistry: Pick<ServiceInspectionQuery, 'getAll'>;
|
|
30
|
+
workingDirectory: string;
|
|
31
|
+
homeDirectory: string;
|
|
29
32
|
getConfiguredProviderIds: () => string[];
|
|
30
33
|
getPinned: () => Promise<string[]>;
|
|
31
34
|
render: () => void;
|
|
@@ -89,6 +92,8 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
89
92
|
subscriptionManager,
|
|
90
93
|
secretsManager,
|
|
91
94
|
serviceRegistry,
|
|
95
|
+
workingDirectory,
|
|
96
|
+
homeDirectory,
|
|
92
97
|
getConfiguredProviderIds,
|
|
93
98
|
getPinned,
|
|
94
99
|
render,
|
|
@@ -264,7 +269,12 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
264
269
|
|
|
265
270
|
commandContext.openSettingsModal = (target?: string) => {
|
|
266
271
|
input.modalOpened('settings');
|
|
267
|
-
input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager
|
|
272
|
+
input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager, {
|
|
273
|
+
onSettingApplied: (change) => syncServiceSettingToPlatform(
|
|
274
|
+
{ configManager, workingDirectory, homeDirectory },
|
|
275
|
+
change,
|
|
276
|
+
),
|
|
277
|
+
});
|
|
268
278
|
input.settingsModal.selectTarget(target);
|
|
269
279
|
render();
|
|
270
280
|
};
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.19.
|
|
9
|
+
let _version = '0.19.65';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|