@pellux/goodvibes-tui 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/README.md +6 -6
- package/bin/goodvibes +6 -0
- package/bin/goodvibes-daemon +6 -0
- package/bin/launcher-support.js +34 -1
- package/docs/foundation-artifacts/operator-contract.json +8076 -2047
- package/package.json +15 -11
- package/scripts/postinstall.js +73 -48
- package/src/cli/bundle-command.ts +5 -5
- package/src/cli/completion.ts +1 -1
- package/src/cli/completions/generate.ts +15 -3
- package/src/cli/doctor.ts +385 -0
- package/src/cli/entrypoint.ts +72 -1
- package/src/cli/help.ts +29 -9
- package/src/cli/hooks-command.ts +74 -0
- package/src/cli/hooks-report.ts +167 -0
- package/src/cli/index.ts +3 -0
- package/src/cli/management.ts +13 -1
- package/src/cli/parser.ts +10 -2
- package/src/cli/plugin-bundles-command.ts +228 -0
- package/src/cli/plugin-command.ts +179 -0
- package/src/cli/service-posture.ts +10 -0
- package/src/cli/status.ts +275 -2
- package/src/cli/tui-startup.ts +119 -1
- package/src/cli/types.ts +3 -1
- package/src/config/index.ts +16 -0
- package/src/config/tui-extension-settings.ts +169 -0
- package/src/core/approval-alert.ts +15 -2
- package/src/core/compaction-receipt.ts +127 -0
- package/src/core/model-routing-chip.ts +82 -0
- package/src/core/permission-mode.ts +89 -0
- package/src/core/rewind-receipt.ts +83 -0
- package/src/core/rewind-turn-anchors.ts +160 -0
- package/src/core/scriptable-statusline.ts +141 -0
- package/src/core/session-auto-titler.ts +110 -0
- package/src/core/stream-event-wiring.ts +20 -0
- package/src/core/system-message-router.ts +1 -1
- package/src/core/terminal-notifier.ts +102 -0
- package/src/core/turn-event-wiring.ts +80 -1
- package/src/input/command-reference.ts +96 -0
- package/src/input/command-registry.ts +25 -0
- package/src/input/commands/channel-pairing.ts +245 -0
- package/src/input/commands/channel-runtime.ts +152 -6
- package/src/input/commands/checkin-runtime.ts +89 -0
- package/src/input/commands/checkpoint-runtime.ts +7 -179
- package/src/input/commands/ci-runtime.ts +207 -0
- package/src/input/commands/cost-attribution-format.ts +52 -0
- package/src/input/commands/cost-runtime.ts +44 -4
- package/src/input/commands/editor-runtime.ts +22 -0
- package/src/input/commands/flags-runtime.ts +328 -0
- package/src/input/commands/health-metrics-format.ts +67 -0
- package/src/input/commands/health-runtime.ts +55 -1
- package/src/input/commands/memory.ts +23 -0
- package/src/input/commands/operator-rpc.ts +95 -0
- package/src/input/commands/palette-runtime.ts +22 -0
- package/src/input/commands/permissions-provenance.ts +264 -0
- package/src/input/commands/permissions-runtime.ts +21 -0
- package/src/input/commands/planning-runtime.ts +28 -1
- package/src/input/commands/platform-access-runtime.ts +2 -97
- package/src/input/commands/principals-runtime.ts +212 -0
- package/src/input/commands/product-runtime.ts +43 -3
- package/src/input/commands/recall-files-config.ts +54 -0
- package/src/input/commands/recall-files-git.ts +57 -0
- package/src/input/commands/recall-files.ts +185 -0
- package/src/input/commands/recall-query.ts +8 -3
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/recall-shared.ts +13 -0
- package/src/input/commands/relay-runtime.ts +86 -0
- package/src/input/commands/review-runtime.ts +160 -0
- package/src/input/commands/rewind-runtime.ts +362 -0
- package/src/input/commands/schedule-nl.ts +147 -0
- package/src/input/commands/schedule-runtime.ts +29 -10
- package/src/input/commands/secret-runtime.ts +55 -0
- package/src/input/commands/session-content.ts +19 -8
- package/src/input/commands/session-workflow.ts +10 -0
- package/src/input/commands/update-runtime.ts +363 -0
- package/src/input/commands/workstream-attempt-dependents.ts +103 -0
- package/src/input/commands/workstream-attempts.ts +244 -0
- package/src/input/commands/workstream-runtime.ts +164 -26
- package/src/input/commands/worktree-runtime.ts +56 -2
- package/src/input/commands.ts +135 -80
- package/src/input/composer-capture.ts +67 -0
- package/src/input/composer-editor.ts +134 -0
- package/src/input/concealed-input.ts +94 -0
- package/src/input/exec-env-scrub-config.ts +84 -0
- package/src/input/feature-unit-layout.ts +142 -0
- package/src/input/feed-context-factory.ts +2 -0
- package/src/input/handler-feed-routes.ts +17 -0
- package/src/input/handler-feed.ts +3 -0
- package/src/input/handler-modal-routes.ts +5 -2
- package/src/input/handler-shortcuts.ts +37 -0
- package/src/input/handler.ts +24 -2
- package/src/input/keybindings.ts +11 -2
- package/src/input/onboarding/onboarding-feature-units.ts +536 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +40 -6
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +324 -629
- package/src/input/onboarding/onboarding-wizard-steps.ts +5 -5
- package/src/input/onboarding/onboarding-wizard-types.ts +6 -0
- package/src/input/relay-settings-descriptions.ts +35 -0
- package/src/input/sandbox-exec-config.ts +107 -0
- package/src/input/settings-modal-adjustment.ts +4 -7
- package/src/input/settings-modal-data.ts +178 -8
- package/src/input/settings-modal-mutations.ts +41 -13
- package/src/input/settings-modal-types.ts +42 -5
- package/src/input/settings-modal.ts +67 -15
- package/src/input/submission-router.ts +4 -1
- package/src/input/worktree-setup-config.ts +93 -0
- package/src/main.ts +43 -43
- package/src/panels/builtin/development.ts +14 -0
- package/src/panels/diff-review-model.ts +189 -0
- package/src/panels/diff-review-panel.ts +395 -0
- package/src/panels/fleet-panel-format.ts +114 -2
- package/src/panels/fleet-panel.ts +65 -78
- package/src/panels/fleet-read-model.ts +91 -11
- package/src/panels/fleet-stop.ts +19 -4
- package/src/panels/modals/memory-modal.ts +11 -2
- package/src/permissions/hunk-selection.ts +86 -5
- package/src/permissions/prompt.ts +108 -7
- package/src/permissions/sandbox-exec-gate.ts +162 -0
- package/src/renderer/settings-modal-helpers.ts +21 -1
- package/src/renderer/settings-modal.ts +70 -44
- package/src/renderer/shell-surface.ts +23 -1
- package/src/renderer/system-message.ts +4 -0
- package/src/renderer/tool-result-summary.ts +95 -5
- package/src/renderer/ui-factory.ts +22 -0
- package/src/runtime/bootstrap-command-context.ts +15 -1
- package/src/runtime/bootstrap-command-parts.ts +14 -2
- package/src/runtime/bootstrap-core.ts +38 -38
- package/src/runtime/bootstrap-notifier-sync.ts +41 -0
- package/src/runtime/bootstrap-shell.ts +8 -0
- package/src/runtime/bootstrap.ts +21 -23
- package/src/runtime/context-accounting-source.ts +103 -0
- package/src/runtime/conversation-rewind-port.ts +130 -0
- package/src/runtime/fleet-needs-input-push.ts +70 -0
- package/src/runtime/install-self-check-startup.ts +36 -0
- package/src/runtime/install-self-check.ts +149 -0
- package/src/runtime/onboarding/fast-path.ts +93 -0
- package/src/runtime/process-lifecycle.ts +32 -24
- package/src/runtime/relay-reachability-bridge.ts +53 -0
- package/src/runtime/release-artifacts.ts +79 -0
- package/src/runtime/services.ts +38 -37
- package/src/runtime/trust/workspace-registration.ts +184 -0
- package/src/runtime/trust/workspace-trust.ts +202 -0
- package/src/runtime/ui-services.ts +2 -1
- package/src/runtime/update-check.ts +113 -0
- package/src/runtime/workspace-checkpointing.ts +32 -0
- package/src/runtime/workstream-attempts-validation.ts +77 -0
- package/src/runtime/workstream-draft-edits.ts +95 -0
- package/src/runtime/workstream-draft-store.ts +101 -0
- package/src/runtime/workstream-draft-types.ts +42 -0
- package/src/runtime/workstream-services.ts +70 -39
- package/src/shell/blocking-input.ts +29 -0
- package/src/shell/ui-openers.ts +65 -0
- package/src/verification/verification-ledger.ts +56 -2
- package/src/version.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
- package/src/runtime/render-scheduler.ts +0 -90
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isExternalSurfaceSelectedByDefault,
|
|
9
9
|
type ExternalSurfaceSpec,
|
|
10
10
|
} from './onboarding-wizard-external-surfaces.ts';
|
|
11
|
+
import { buildFeatureUnitSteps } from './onboarding-feature-units.ts';
|
|
11
12
|
import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
|
|
12
13
|
import { getDaemonSource, pushDaemonAdoptionFields, pushLegacyDaemonMigrationFields } from './onboarding-wizard-network-adopt.ts';
|
|
13
14
|
import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
|
|
@@ -37,10 +38,9 @@ export function buildOnboardingWizardSteps(controller: OnboardingWizardControlle
|
|
|
37
38
|
if (shouldShowCloudflareStep(controller)) {
|
|
38
39
|
steps.push(buildCloudflareStep(controller));
|
|
39
40
|
}
|
|
40
|
-
steps.push(buildProviderAccessStep(controller));
|
|
41
|
-
|
|
42
|
-
steps.push(
|
|
43
|
-
steps.push(buildReviewStep(controller));
|
|
41
|
+
steps.push(buildProviderAccessStep(controller), buildDefaultModelStep(controller), buildExperienceStep(controller));
|
|
42
|
+
// Guided feature coverage: every remaining feature flag is reachable here as a unit.
|
|
43
|
+
steps.push(...buildFeatureUnitSteps(controller), buildReviewStep(controller));
|
|
44
44
|
return steps.map(addApplyAndContinueAction);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -627,7 +627,7 @@ export function buildNetworkStep(controller: OnboardingWizardControllerLike): On
|
|
|
627
627
|
const cpOff = controlPlaneRemote && String(controller.runtimeSnapshot?.config.controlPlane?.tls?.mode ?? 'off') === 'off';
|
|
628
628
|
const hlOff = listenerEnabled && String(controller.runtimeSnapshot?.config.httpListener?.tls?.mode ?? 'off') === 'off';
|
|
629
629
|
if (cpOff || hlOff) { const a = [cpOff ? 'control plane' : '', hlOff ? 'HTTP listener' : ''].filter(Boolean).join(' and '); fields.push({ kind: 'status', id: 'network.tls-warn', label: `TLS off — ${a} transmits plaintext`, defaultValue: 'Warning', hint: `The ${a} is network-reachable but TLS is off. Traffic travels in plaintext. Enable TLS or use a terminating reverse proxy.` }); }
|
|
630
|
-
if (listenerEnabled) { fields.push({ kind: 'status', id: 'network.cors-note', label: 'CORS
|
|
630
|
+
if (listenerEnabled) { fields.push({ kind: 'status', id: 'network.cors-note', label: 'Browser CORS is off by default', defaultValue: 'Info', hint: 'The webhook/control-plane HTTP listener honors controlPlane.cors.enabled and controlPlane.cors.allowedOrigins. Set both from Settings > Control Plane (or /config controlPlane) to allow browser origins, then restart the daemon. CORS stays off until you enable it.' }); }
|
|
631
631
|
}
|
|
632
632
|
return {
|
|
633
633
|
id: 'network',
|
|
@@ -26,6 +26,12 @@ export type OnboardingWizardStepId =
|
|
|
26
26
|
| 'provider-access'
|
|
27
27
|
| 'default-model'
|
|
28
28
|
| 'experience'
|
|
29
|
+
| 'features-safety'
|
|
30
|
+
| 'features-context'
|
|
31
|
+
| 'features-telemetry'
|
|
32
|
+
| 'features-automation'
|
|
33
|
+
| 'features-provider'
|
|
34
|
+
| 'features-advanced'
|
|
29
35
|
| 'review';
|
|
30
36
|
|
|
31
37
|
export type OnboardingWizardFieldKind =
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* relay-settings-descriptions.ts — appends the relay threat-model one-liner to
|
|
3
|
+
* the `relay.*` CONFIG_SCHEMA descriptions shown in /config and the settings
|
|
4
|
+
* modal.
|
|
5
|
+
*
|
|
6
|
+
* `relay.*` is a real SDK CONFIG_SCHEMA domain (schema-domain-runtime.ts) —
|
|
7
|
+
* the settings-modal-data.ts CONFIG_SCHEMA loop already surfaces it once
|
|
8
|
+
* `relay` is listed in SETTINGS_CATEGORIES (see settings-modal-types.ts). The
|
|
9
|
+
* SDK's own descriptions explain WHAT each key does but not the trust
|
|
10
|
+
* boundary the outbound relay path crosses; this enrichment appends that
|
|
11
|
+
* honestly, once, without duplicating or overriding the SDK's wording. Entries
|
|
12
|
+
* are shallow-cloned (never mutating the shared `ConfigSetting` objects the
|
|
13
|
+
* SDK exports from CONFIG_SCHEMA) so re-running this on the same groups map
|
|
14
|
+
* is idempotent.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { SettingEntry, SettingsCategory } from './settings-modal-types.ts';
|
|
18
|
+
|
|
19
|
+
/** The threat-model note every relay.* setting description gets appended with. */
|
|
20
|
+
export const RELAY_THREAT_MODEL_NOTE =
|
|
21
|
+
'The relay operator sees only ciphertext and connection metadata — it cannot read message contents. Self-host your own relay for full control.';
|
|
22
|
+
|
|
23
|
+
/** Append the threat-model note to every relay.* entry's description, in place. */
|
|
24
|
+
export function enrichRelaySettingDescriptions(groups: Map<SettingsCategory, SettingEntry[]>): void {
|
|
25
|
+
const relayEntries = groups.get('relay');
|
|
26
|
+
if (!relayEntries) return;
|
|
27
|
+
for (let i = 0; i < relayEntries.length; i++) {
|
|
28
|
+
const entry = relayEntries[i];
|
|
29
|
+
if (!entry || !entry.setting.key.startsWith('relay.') || entry.setting.description.includes(RELAY_THREAT_MODEL_NOTE)) continue;
|
|
30
|
+
relayEntries[i] = {
|
|
31
|
+
...entry,
|
|
32
|
+
setting: { ...entry.setting, description: `${entry.setting.description} ${RELAY_THREAT_MODEL_NOTE}` },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sandbox-exec-config.ts — TUI-local synthetic settings entries for
|
|
3
|
+
* sandbox.egressAllowlist / sandbox.workspaceWritable.
|
|
4
|
+
*
|
|
5
|
+
* The SDK's per-command exec sandbox (bubblewrap on Linux, gated by the
|
|
6
|
+
* graduation-tracked `exec-sandbox` feature flag) reads its full configuration
|
|
7
|
+
* straight off `configManager.getCategory('sandbox')` inside
|
|
8
|
+
* `registerAllTools` (platform/tools/index.ts) — `sandbox.enabled`,
|
|
9
|
+
* `sandbox.egressAllowlist`, `sandbox.workspaceWritable`. `sandbox.enabled` is
|
|
10
|
+
* already a real CONFIG_SCHEMA key (with its own honest "requires bubblewrap
|
|
11
|
+
* on Linux, reports unavailable elsewhere" description) and needs no synthetic
|
|
12
|
+
* entry — the CONFIG_SCHEMA loop in settings-modal-data.ts already surfaces it.
|
|
13
|
+
* `egressAllowlist`/`workspaceWritable` are NOT in CONFIG_SCHEMA, so this file
|
|
14
|
+
* mirrors worktree-setup-config.ts's synthetic-entry pattern for them.
|
|
15
|
+
*
|
|
16
|
+
* UNLIKE worktree.setup.* / learning.consolidation.*, no defensive try/catch is
|
|
17
|
+
* needed here: DEFAULT_CONFIG.sandbox already carries both fields (as empty
|
|
18
|
+
* arrays), so ConfigManager.resolvePath finds the `sandbox` section on a plain
|
|
19
|
+
* get()/set() without throwing — only a missing SECTION throws, and `sandbox`
|
|
20
|
+
* has existed since before this repack (it backs the older VM/REPL isolation
|
|
21
|
+
* settings). Writing through these entries takes effect for real: the exec
|
|
22
|
+
* sandbox reads the same `sandbox` config category at tool-registration time.
|
|
23
|
+
*
|
|
24
|
+
* Both keys hold a JSON array of strings on disk. The settings modal's inline
|
|
25
|
+
* editor is a single-line text field, so — matching worktree.setup.* and
|
|
26
|
+
* controlPlane.cors.allowedOrigins — they are displayed and edited as a
|
|
27
|
+
* comma-separated list and parsed back into an array on commit (see
|
|
28
|
+
* isSandboxExecListConfigKey's use in settings-modal.ts#commitEdit).
|
|
29
|
+
*/
|
|
30
|
+
import type { ConfigKey, ConfigManager, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
31
|
+
import type { SettingEntry, SettingsCategory } from './settings-modal-types.ts';
|
|
32
|
+
|
|
33
|
+
export const SANDBOX_EGRESS_ALLOWLIST_CONFIG_KEY = 'sandbox.egressAllowlist' as ConfigKey;
|
|
34
|
+
export const SANDBOX_WORKSPACE_WRITABLE_CONFIG_KEY = 'sandbox.workspaceWritable' as ConfigKey;
|
|
35
|
+
|
|
36
|
+
const SANDBOX_EXEC_LIST_CONFIG_KEYS: ReadonlySet<ConfigKey> = new Set([
|
|
37
|
+
SANDBOX_EGRESS_ALLOWLIST_CONFIG_KEY,
|
|
38
|
+
SANDBOX_WORKSPACE_WRITABLE_CONFIG_KEY,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/** True for the two sandbox exec-boundary list keys, whose stored value is a string array edited as a comma-separated list. */
|
|
42
|
+
export function isSandboxExecListConfigKey(key: ConfigKey): boolean {
|
|
43
|
+
return SANDBOX_EXEC_LIST_CONFIG_KEYS.has(key);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Read a config value as a string array (non-array/malformed degrades to empty). */
|
|
47
|
+
export function readSandboxExecList(configManager: Pick<ConfigManager, 'get'>, key: ConfigKey): string[] {
|
|
48
|
+
const raw = configManager.get(key);
|
|
49
|
+
if (!Array.isArray(raw)) return [];
|
|
50
|
+
return raw.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Parse a comma-separated edit-buffer submission back into a string array (trimmed, empties dropped). Shared with worktree.setup.* — same convention. */
|
|
54
|
+
export function parseSandboxExecListInput(text: string): string[] {
|
|
55
|
+
return text.split(',').map((part) => part.trim()).filter((part) => part.length > 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const SANDBOX_EGRESS_ALLOWLIST_SYNTHETIC_SETTING: ConfigSetting = {
|
|
59
|
+
key: SANDBOX_EGRESS_ALLOWLIST_CONFIG_KEY,
|
|
60
|
+
type: 'string',
|
|
61
|
+
default: [],
|
|
62
|
+
description: 'Comma-separated command base names (or "*" for all) whose network access is re-enabled inside the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants network"). Empty = the sandbox disables network for every command. Only takes effect when sandbox.enabled is on, the graduation-tracked exec-sandbox feature flag is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const SANDBOX_WORKSPACE_WRITABLE_SYNTHETIC_SETTING: ConfigSetting = {
|
|
66
|
+
key: SANDBOX_WORKSPACE_WRITABLE_CONFIG_KEY,
|
|
67
|
+
type: 'string',
|
|
68
|
+
default: [],
|
|
69
|
+
description: 'Comma-separated absolute paths outside the workspace bound writable into the per-command exec sandbox boundary, as a named escalation the approval flow surfaces ("wants path outside workspace"). Empty = only the workspace (and an isolated /tmp) is writable. Only takes effect when sandbox.enabled is on, the graduation-tracked exec-sandbox feature flag is on, and bubblewrap is available (Linux only — reports unavailable elsewhere).',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
function buildSandboxExecListEntry(configManager: Pick<ConfigManager, 'get'>, setting: ConfigSetting): SettingEntry {
|
|
73
|
+
const currentValue = readSandboxExecList(configManager, setting.key);
|
|
74
|
+
return {
|
|
75
|
+
setting,
|
|
76
|
+
currentValue,
|
|
77
|
+
isDefault: currentValue.length === 0,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildSandboxEgressAllowlistSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
|
|
82
|
+
return buildSandboxExecListEntry(configManager, SANDBOX_EGRESS_ALLOWLIST_SYNTHETIC_SETTING);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function buildSandboxWorkspaceWritableSyntheticEntry(configManager: Pick<ConfigManager, 'get'>): SettingEntry {
|
|
86
|
+
return buildSandboxExecListEntry(configManager, SANDBOX_WORKSPACE_WRITABLE_SYNTHETIC_SETTING);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Inject both synthetic entries into the sandbox category's SettingEntry list
|
|
91
|
+
* (alongside the real sandbox.enabled CONFIG_SCHEMA entry the caller's own
|
|
92
|
+
* CONFIG_SCHEMA loop already added), idempotently. No-op when the category
|
|
93
|
+
* isn't present in `groups` at all.
|
|
94
|
+
*/
|
|
95
|
+
export function injectSandboxExecSyntheticEntries(
|
|
96
|
+
groups: Map<SettingsCategory, SettingEntry[]>,
|
|
97
|
+
configManager: Pick<ConfigManager, 'get'>,
|
|
98
|
+
): void {
|
|
99
|
+
const sandboxEntries = groups.get('sandbox');
|
|
100
|
+
if (!sandboxEntries) return;
|
|
101
|
+
if (!sandboxEntries.some((e) => e.setting.key === SANDBOX_EGRESS_ALLOWLIST_CONFIG_KEY)) {
|
|
102
|
+
sandboxEntries.push(buildSandboxEgressAllowlistSyntheticEntry(configManager));
|
|
103
|
+
}
|
|
104
|
+
if (!sandboxEntries.some((e) => e.setting.key === SANDBOX_WORKSPACE_WRITABLE_CONFIG_KEY)) {
|
|
105
|
+
sandboxEntries.push(buildSandboxWorkspaceWritableSyntheticEntry(configManager));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -45,13 +45,10 @@ export function adjustSelected(
|
|
|
45
45
|
): void {
|
|
46
46
|
if (ctx.editingMode) return;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (flagEntry.state !== targetState) applyFlagState(flagEntry, targetState, ctx.featureFlagManager, ctx.configManager);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
48
|
+
// Feature-unit toggle headers (setting.type === 'boolean', key featureFlags.<id>)
|
|
49
|
+
// now live across topical categories, not a single flags tab. They fall through
|
|
50
|
+
// to the boolean branch below, which routes through setValue → the modal's
|
|
51
|
+
// flag-toggle chokepoint. No category-special-case is needed here anymore.
|
|
55
52
|
|
|
56
53
|
if (ctx.currentCategory === 'mcp') {
|
|
57
54
|
const entry = ctx.getSelectedMcp();
|
|
@@ -15,7 +15,26 @@ import { buildSubscriptionEntries } from './settings-modal-subscriptions.ts';
|
|
|
15
15
|
import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
16
16
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
17
17
|
import { CODE_INDEX_ENABLED_CONFIG_KEY } from '../runtime/code-index-services.ts';
|
|
18
|
+
import {
|
|
19
|
+
WORKTREE_SETUP_CARRY_OVER_GLOBS_CONFIG_KEY,
|
|
20
|
+
WORKTREE_SETUP_COMMANDS_CONFIG_KEY,
|
|
21
|
+
buildWorktreeSetupCarryOverGlobsSyntheticEntry,
|
|
22
|
+
buildWorktreeSetupCommandsSyntheticEntry,
|
|
23
|
+
isWorktreeSetupListConfigKey,
|
|
24
|
+
readWorktreeSetupList,
|
|
25
|
+
} from './worktree-setup-config.ts';
|
|
18
26
|
import { BUDGET_ALERT_USD_CONFIG_KEY, BUDGET_ALERT_USD_DEFAULT, readBudgetAlertUsd } from '../export/cost-utils.ts';
|
|
27
|
+
import { MEMORY_PROJECTION_DIR_CONFIG_KEY, buildMemoryProjectionDirSyntheticEntry } from './commands/recall-files-config.ts';
|
|
28
|
+
import {
|
|
29
|
+
injectSandboxExecSyntheticEntries,
|
|
30
|
+
isSandboxExecListConfigKey,
|
|
31
|
+
readSandboxExecList,
|
|
32
|
+
} from './sandbox-exec-config.ts';
|
|
33
|
+
import {
|
|
34
|
+
injectExecEnvScrubSyntheticEntry,
|
|
35
|
+
isExecEnvScrubAllowlistConfigKey,
|
|
36
|
+
readExecEnvScrubAllowlist,
|
|
37
|
+
} from './exec-env-scrub-config.ts';
|
|
19
38
|
import {
|
|
20
39
|
THEME_MODE_CONFIG_KEY,
|
|
21
40
|
THEME_MODE_VALUES,
|
|
@@ -31,6 +50,8 @@ import {
|
|
|
31
50
|
type SettingsCategory,
|
|
32
51
|
type SubscriptionEntry,
|
|
33
52
|
} from './settings-modal-types.ts';
|
|
53
|
+
import { enrichRelaySettingDescriptions } from './relay-settings-descriptions.ts';
|
|
54
|
+
import { applyFeatureUnitLayout } from './feature-unit-layout.ts';
|
|
34
55
|
|
|
35
56
|
// ---------------------------------------------------------------------------
|
|
36
57
|
// deepEqual — structural equality for isDefault comparisons
|
|
@@ -74,12 +95,13 @@ export function deepEqual(a: unknown, b: unknown): boolean {
|
|
|
74
95
|
|
|
75
96
|
export function buildSettingGroups(
|
|
76
97
|
configManager: ConfigManager,
|
|
98
|
+
featureFlagManager?: FeatureFlagManager | null,
|
|
77
99
|
): Map<SettingsCategory, SettingEntry[]> {
|
|
78
100
|
const groups = new Map<SettingsCategory, SettingEntry[]>();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
// Every category starts empty (including 'flags', now the Advanced Features
|
|
102
|
+
// bucket, and the new fetch/agents/security/integrations/policy/notifications
|
|
103
|
+
// namespaces) and is filled from CONFIG_SCHEMA below.
|
|
104
|
+
for (const cat of SETTINGS_CATEGORIES) groups.set(cat, []);
|
|
83
105
|
|
|
84
106
|
for (const setting of CONFIG_SCHEMA) {
|
|
85
107
|
const rawCat = setting.key.split('.')[0] as string;
|
|
@@ -167,6 +189,13 @@ export function buildSettingGroups(
|
|
|
167
189
|
behaviorEntries.push(entry);
|
|
168
190
|
}
|
|
169
191
|
}
|
|
192
|
+
// In-terminal (OSC 9) notification toggles + audible bell — same category,
|
|
193
|
+
// per-key defaults (see terminal-notifier.ts).
|
|
194
|
+
for (const entry of buildTerminalNotifySyntheticEntries(configManager)) {
|
|
195
|
+
if (!behaviorEntries.some((e) => e.setting.key === entry.setting.key)) {
|
|
196
|
+
behaviorEntries.push(entry);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
170
199
|
}
|
|
171
200
|
|
|
172
201
|
// Inject the storage.codeIndexEnabled toggle into the
|
|
@@ -178,6 +207,50 @@ export function buildSettingGroups(
|
|
|
178
207
|
storageEntries.push(buildCodeIndexEnabledSyntheticEntry(configManager));
|
|
179
208
|
}
|
|
180
209
|
|
|
210
|
+
// Inject the memory.projection.dir synthetic entry into the storage
|
|
211
|
+
// category (alongside codeIndexEnabled — both are local-storage posture
|
|
212
|
+
// settings). Not in the SDK ConfigKey union, same rationale as above.
|
|
213
|
+
if (storageEntries && !storageEntries.some((e) => e.setting.key === MEMORY_PROJECTION_DIR_CONFIG_KEY)) {
|
|
214
|
+
storageEntries.push(buildMemoryProjectionDirSyntheticEntry(configManager));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Inject the worktree.setup.commands / worktree.setup.carryOverGlobs
|
|
218
|
+
// synthetic entries into the orchestration category (worktree isolation is
|
|
219
|
+
// an orchestration-engine concern — see WorkstreamIsolation 'worktree').
|
|
220
|
+
// Neither key is in the SDK ConfigKey union (see worktree-setup-config.ts),
|
|
221
|
+
// same rationale as the other synthetic settings above.
|
|
222
|
+
const orchestrationEntries = groups.get('orchestration');
|
|
223
|
+
if (orchestrationEntries) {
|
|
224
|
+
if (!orchestrationEntries.some((e) => e.setting.key === WORKTREE_SETUP_COMMANDS_CONFIG_KEY)) {
|
|
225
|
+
orchestrationEntries.push(buildWorktreeSetupCommandsSyntheticEntry(configManager));
|
|
226
|
+
}
|
|
227
|
+
if (!orchestrationEntries.some((e) => e.setting.key === WORKTREE_SETUP_CARRY_OVER_GLOBS_CONFIG_KEY)) {
|
|
228
|
+
orchestrationEntries.push(buildWorktreeSetupCarryOverGlobsSyntheticEntry(configManager));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Inject the sandbox.egressAllowlist / sandbox.workspaceWritable and
|
|
233
|
+
// permissions.execEnvScrubAllowlist synthetic entries — see
|
|
234
|
+
// sandbox-exec-config.ts / exec-env-scrub-config.ts for why they are not in
|
|
235
|
+
// CONFIG_SCHEMA, same rationale as the other synthetic settings above.
|
|
236
|
+
injectSandboxExecSyntheticEntries(groups, configManager);
|
|
237
|
+
injectExecEnvScrubSyntheticEntry(groups, configManager);
|
|
238
|
+
|
|
239
|
+
// relay.* is a real SDK CONFIG_SCHEMA domain; append the threat-model note
|
|
240
|
+
// the SDK's own descriptions don't carry (see relay-settings-descriptions.ts).
|
|
241
|
+
enrichRelaySettingDescriptions(groups);
|
|
242
|
+
|
|
243
|
+
// Feature-unit presentation: turn each flag into ONE unit (toggle + the config
|
|
244
|
+
// keys it tunes) hosted in its topical category, folding no-config flags into
|
|
245
|
+
// the Advanced Features bucket. Sourced from FEATURE_FLAG_CONFIG. Runs AFTER
|
|
246
|
+
// synthetic injection so non-flag-owned rows stay as orphans; skipped on the
|
|
247
|
+
// flagless test path.
|
|
248
|
+
if (featureFlagManager) applyFeatureUnitLayout(groups, buildFlagEntries(featureFlagManager));
|
|
249
|
+
|
|
250
|
+
// learning.consolidation.* is a real SDK config domain now, so its keys arrive
|
|
251
|
+
// through the CONFIG_SCHEMA loop above; the old TUI-local synthetic entries were
|
|
252
|
+
// removed for this reason.
|
|
253
|
+
|
|
181
254
|
return groups;
|
|
182
255
|
}
|
|
183
256
|
|
|
@@ -395,6 +468,48 @@ export function buildNotifyAlertSyntheticEntries(configManager: Pick<ConfigManag
|
|
|
395
468
|
return NOTIFY_ALERT_SYNTHETIC_SETTINGS.map((spec) => buildBooleanSyntheticEntry(configManager, spec.key, spec.description, true));
|
|
396
469
|
}
|
|
397
470
|
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// In-terminal (OSC 9) notification settings — see core/terminal-notifier.ts
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The OSC 9 in-terminal desktop-notification toggles + the audible bell.
|
|
477
|
+
* TUI-local synthetic booleans (not in the SDK ConfigKey union), each carrying
|
|
478
|
+
* its OWN default (unlike the always-on W2.3 alert toggles above): approval-wait
|
|
479
|
+
* on, turn-end/agent-blocked off, bell off. Read generically by
|
|
480
|
+
* core/terminal-notifier.ts (readBooleanConfig); this is only the
|
|
481
|
+
* settings-modal-visible surface. These are independent of the master
|
|
482
|
+
* `notifyOnlyWhenUnfocused` gate — an in-terminal notification is always
|
|
483
|
+
* focus-gated on its own (only fires for an unfocused/unknown terminal).
|
|
484
|
+
*/
|
|
485
|
+
const TERMINAL_NOTIFY_SYNTHETIC_SETTINGS: ReadonlyArray<{ readonly key: string; readonly description: string; readonly defaultOn: boolean }> = [
|
|
486
|
+
{
|
|
487
|
+
key: 'behavior.terminalNotifyApprovalWait',
|
|
488
|
+
description: 'Send an in-terminal (OSC 9) desktop notification when a tool call is waiting on your approval. Fires only when the terminal window is unfocused. Works over SSH/tmux where OS notifications do not.',
|
|
489
|
+
defaultOn: true,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
key: 'behavior.terminalNotifyTurnEnd',
|
|
493
|
+
description: 'Send an in-terminal (OSC 9) desktop notification when a turn finishes. Off by default. Fires only when the terminal window is unfocused.',
|
|
494
|
+
defaultOn: false,
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
key: 'behavior.terminalNotifyAgentBlocked',
|
|
498
|
+
description: 'Send an in-terminal (OSC 9) desktop notification when a delegated agent blocks waiting for your input. Off by default. Fires only when the terminal window is unfocused.',
|
|
499
|
+
defaultOn: false,
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
key: 'behavior.terminalBell',
|
|
503
|
+
description: 'Also ring the terminal bell alongside in-terminal notifications. Off by default.',
|
|
504
|
+
defaultOn: false,
|
|
505
|
+
},
|
|
506
|
+
];
|
|
507
|
+
|
|
508
|
+
/** Build the OSC 9 in-terminal notification SettingEntry rows for the behavior category. */
|
|
509
|
+
export function buildTerminalNotifySyntheticEntries(configManager: Pick<ConfigManager, 'get'>): SettingEntry[] {
|
|
510
|
+
return TERMINAL_NOTIFY_SYNTHETIC_SETTINGS.map((spec) => buildBooleanSyntheticEntry(configManager, spec.key, spec.description, spec.defaultOn));
|
|
511
|
+
}
|
|
512
|
+
|
|
398
513
|
// ---------------------------------------------------------------------------
|
|
399
514
|
// storage.codeIndexEnabled synthetic setting
|
|
400
515
|
// ---------------------------------------------------------------------------
|
|
@@ -422,10 +537,9 @@ export function buildCodeIndexEnabledSyntheticEntry(configManager: Pick<ConfigMa
|
|
|
422
537
|
|
|
423
538
|
export function buildFlagEntries(featureFlagManager: FeatureFlagManager | null): FlagEntry[] {
|
|
424
539
|
if (!featureFlagManager) return [];
|
|
425
|
-
return Array.from(featureFlagManager.getAll().values()).map(
|
|
426
|
-
flag,
|
|
427
|
-
|
|
428
|
-
}));
|
|
540
|
+
return Array.from(featureFlagManager.getAll().values()).map(
|
|
541
|
+
({ flag, state, persistedState, pendingRestart }) => ({ flag, state, persistedState, pendingRestart }),
|
|
542
|
+
);
|
|
429
543
|
}
|
|
430
544
|
|
|
431
545
|
// ---------------------------------------------------------------------------
|
|
@@ -492,6 +606,46 @@ export function refreshEntryValues(
|
|
|
492
606
|
): void {
|
|
493
607
|
for (const entries of groups.values()) {
|
|
494
608
|
for (const entry of entries) {
|
|
609
|
+
// The worktree.setup.* keys read through a defensive helper. The SDK now
|
|
610
|
+
// registers the 'worktree' section in DEFAULT_CONFIG, so a plain
|
|
611
|
+
// configManager.get returns the empty default rather than throwing; the
|
|
612
|
+
// helper's guard (belt-and-suspenders for an older SDK) keeps this
|
|
613
|
+
// refresh from crashing for EVERY setting change if the section is ever
|
|
614
|
+
// absent (see worktree-setup-config.ts).
|
|
615
|
+
if (isWorktreeSetupListConfigKey(entry.setting.key)) {
|
|
616
|
+
entry.currentValue = readWorktreeSetupList(configManager, entry.setting.key);
|
|
617
|
+
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
// sandbox.egressAllowlist / sandbox.workspaceWritable: 'sandbox' IS a real
|
|
621
|
+
// DEFAULT_CONFIG section (has been since before this repack, backing the
|
|
622
|
+
// VM/REPL isolation settings), so a plain get() would not throw — but an
|
|
623
|
+
// unset leaf still reads back as `undefined`, not `[]`, so this still needs
|
|
624
|
+
// the same array-normalizing read as the worktree keys above (see
|
|
625
|
+
// sandbox-exec-config.ts).
|
|
626
|
+
if (isSandboxExecListConfigKey(entry.setting.key)) {
|
|
627
|
+
entry.currentValue = readSandboxExecList(configManager, entry.setting.key);
|
|
628
|
+
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
// permissions.execEnvScrubAllowlist: same normalizing-read trap as above —
|
|
632
|
+
// 'permissions' is a real section but this leaf is TUI-local (see
|
|
633
|
+
// exec-env-scrub-config.ts).
|
|
634
|
+
if (isExecEnvScrubAllowlistConfigKey(entry.setting.key)) {
|
|
635
|
+
entry.currentValue = readExecEnvScrubAllowlist(configManager);
|
|
636
|
+
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
// 'learning' is now a real DEFAULT_CONFIG section (schema-domain-learning.ts),
|
|
640
|
+
// so learning.consolidation.* keys read through the plain path below like any
|
|
641
|
+
// other schema-driven setting — no special case needed anymore.
|
|
642
|
+
// Same trap still applies here: 'memory' has no DEFAULT_CONFIG entry.
|
|
643
|
+
if (entry.setting.key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
|
|
644
|
+
const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
|
|
645
|
+
entry.currentValue = refreshed.currentValue;
|
|
646
|
+
entry.isDefault = refreshed.isDefault;
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
495
649
|
const raw = configManager.get(entry.setting.key as ConfigKey);
|
|
496
650
|
// Synthetic entries (e.g. tts.speed) that have no SDK schema key return
|
|
497
651
|
// undefined from configManager. Normalize using the same logic used at
|
|
@@ -520,6 +674,22 @@ export function updateEntryForKey(
|
|
|
520
674
|
for (const entries of groups.values()) {
|
|
521
675
|
const entry = entries.find((candidate) => candidate.setting.key === key);
|
|
522
676
|
if (entry) {
|
|
677
|
+
if (key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
|
|
678
|
+
const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
|
|
679
|
+
entry.currentValue = refreshed.currentValue;
|
|
680
|
+
entry.isDefault = refreshed.isDefault;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
if (isSandboxExecListConfigKey(key)) {
|
|
684
|
+
entry.currentValue = readSandboxExecList(configManager, key);
|
|
685
|
+
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (isExecEnvScrubAllowlistConfigKey(key)) {
|
|
689
|
+
entry.currentValue = readExecEnvScrubAllowlist(configManager);
|
|
690
|
+
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
523
693
|
const raw = configManager.get(key);
|
|
524
694
|
// Synthetic entries: normalize using the same fallback logic as construction.
|
|
525
695
|
entry.currentValue = key === ('tts.speed' as ConfigKey)
|
|
@@ -54,7 +54,19 @@ export function applySettingValue({
|
|
|
54
54
|
/** Called after applying the value so the caller can re-read currentValues. */
|
|
55
55
|
refreshGroups: () => void;
|
|
56
56
|
}): ApplyValueResult {
|
|
57
|
-
|
|
57
|
+
// Defensive: a handful of TUI-local synthetic keys (see worktree-setup-config.ts)
|
|
58
|
+
// live under a config section CONFIG_SCHEMA/DEFAULT_CONFIG has never populated
|
|
59
|
+
// (e.g. 'worktree' as of the SDK 1.6.1 repack), so configManager.get can throw
|
|
60
|
+
// "Invalid config path" here even though the write attempt below is already
|
|
61
|
+
// guarded. Without this, that read — which runs unconditionally, before the
|
|
62
|
+
// write's own try/catch — would crash the whole settings modal on save instead
|
|
63
|
+
// of surfacing the honest "Save failed" message the write path below produces.
|
|
64
|
+
let previousValue: unknown;
|
|
65
|
+
try {
|
|
66
|
+
previousValue = configManager.get(key);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
logger.error('SettingsModal: failed to read previous config value', { key, error: summarizeError(e) });
|
|
69
|
+
}
|
|
58
70
|
// REQUIRES_RESTART: SDK's ConfigSetting has no requiresRestart field yet (see
|
|
59
71
|
// goodvibes-sdk HANDOFF-FROM-TUI-SESSION-20260611.md §Item 8). Until it does,
|
|
60
72
|
// we detect restart-triggering keys by sub-key name heuristic below.
|
|
@@ -139,21 +151,37 @@ export function applyFlagState(
|
|
|
139
151
|
): void {
|
|
140
152
|
const flag: FeatureFlag = flagEntry.flag;
|
|
141
153
|
|
|
142
|
-
if (!flag.runtimeToggleable) {
|
|
143
|
-
persistFlagState(configManager, flag.id, newState, flag.defaultState as FlagState);
|
|
144
|
-
flagEntry.state = newState;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
154
|
try {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
// Persist first so the on-disk override is the source of truth, then apply
|
|
156
|
+
// the same value to the live manager through applyConfigState. That single
|
|
157
|
+
// call keeps both paths consistent with the SDK's config→flag bridge:
|
|
158
|
+
// - runtime-toggleable flags apply immediately (firing flag subscribers
|
|
159
|
+
// exactly as a direct enable()/disable() would), and
|
|
160
|
+
// - startup-gated flags are NOT faked live — the effective state is left
|
|
161
|
+
// untouched and a pending-restart marker is recorded instead.
|
|
154
162
|
persistFlagState(configManager, flag.id, newState, flag.defaultState as FlagState);
|
|
155
|
-
|
|
163
|
+
featureFlagManager.applyConfigState(flag.id, newState);
|
|
156
164
|
} catch (e) {
|
|
157
165
|
logger.error('SettingsModal: failed to toggle feature flag', { flag: flag.id, error: summarizeError(e) });
|
|
158
166
|
}
|
|
167
|
+
|
|
168
|
+
// Reflect the manager's real state, never the requested target: for a
|
|
169
|
+
// startup-gated flag `state` stays at the unchanged effective value and
|
|
170
|
+
// `pendingRestart` reports that a restart is needed for `persistedState`.
|
|
171
|
+
syncFlagEntryFromManager(flagEntry, featureFlagManager);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Refresh a FlagEntry's live/persisted/pending fields from the manager's
|
|
176
|
+
* authoritative snapshot so the settings UI never displays a guessed state.
|
|
177
|
+
*/
|
|
178
|
+
export function syncFlagEntryFromManager(
|
|
179
|
+
flagEntry: FlagEntry,
|
|
180
|
+
featureFlagManager: FeatureFlagManager,
|
|
181
|
+
): void {
|
|
182
|
+
const snapshot = featureFlagManager.getAll().get(flagEntry.flag.id);
|
|
183
|
+
if (!snapshot) return;
|
|
184
|
+
flagEntry.state = snapshot.state;
|
|
185
|
+
flagEntry.persistedState = snapshot.persistedState;
|
|
186
|
+
flagEntry.pendingRestart = snapshot.pendingRestart;
|
|
159
187
|
}
|
|
@@ -9,6 +9,7 @@ export type SettingsCategory =
|
|
|
9
9
|
| 'subscriptions'
|
|
10
10
|
| 'behavior'
|
|
11
11
|
| 'storage'
|
|
12
|
+
| 'atRest'
|
|
12
13
|
| 'permissions'
|
|
13
14
|
| 'orchestration'
|
|
14
15
|
| 'planner'
|
|
@@ -22,16 +23,26 @@ export type SettingsCategory =
|
|
|
22
23
|
| 'httpListener'
|
|
23
24
|
| 'web'
|
|
24
25
|
| 'network'
|
|
26
|
+
| 'relay'
|
|
25
27
|
| 'surfaces'
|
|
26
28
|
| 'cloudflare'
|
|
27
29
|
| 'batch'
|
|
28
30
|
| 'automation'
|
|
31
|
+
| 'checkin'
|
|
29
32
|
| 'watchers'
|
|
30
33
|
| 'runtime'
|
|
31
34
|
| 'telemetry'
|
|
32
35
|
| 'cache'
|
|
36
|
+
| 'diagnostics'
|
|
33
37
|
| 'sandbox'
|
|
34
38
|
| 'mcp'
|
|
39
|
+
| 'learning'
|
|
40
|
+
| 'fetch'
|
|
41
|
+
| 'agents'
|
|
42
|
+
| 'security'
|
|
43
|
+
| 'integrations'
|
|
44
|
+
| 'policy'
|
|
45
|
+
| 'notifications'
|
|
35
46
|
| 'flags'
|
|
36
47
|
| 'release'
|
|
37
48
|
| 'danger';
|
|
@@ -42,12 +53,12 @@ export const SETTINGS_CATEGORY_GROUPS: ReadonlyArray<{
|
|
|
42
53
|
readonly label: string;
|
|
43
54
|
readonly categories: readonly SettingsCategory[];
|
|
44
55
|
}> = [
|
|
45
|
-
{ label: 'Interface', categories: ['display', 'ui', 'behavior', 'permissions'] },
|
|
56
|
+
{ label: 'Interface', categories: ['display', 'ui', 'behavior', 'notifications', 'permissions', 'policy', 'security'] },
|
|
46
57
|
{ label: 'AI Routing', categories: ['provider', 'subscriptions', 'helper', 'tools', 'tts'] },
|
|
47
|
-
{ label: 'Service & Network', categories: ['service', 'daemon', 'network', 'controlPlane', 'httpListener', 'web'] },
|
|
48
|
-
{ label: 'Surfaces & Cloud', categories: ['surfaces', 'mcp', 'cloudflare'] },
|
|
49
|
-
{ label: 'Automation', categories: ['batch', 'automation', 'watchers', 'orchestration', 'planner', 'wrfc'] },
|
|
50
|
-
{ label: 'Runtime & Data', categories: ['storage', 'sandbox', 'runtime', 'cache', 'telemetry'] },
|
|
58
|
+
{ label: 'Service & Network', categories: ['service', 'daemon', 'network', 'controlPlane', 'httpListener', 'web', 'relay'] },
|
|
59
|
+
{ label: 'Surfaces & Cloud', categories: ['surfaces', 'integrations', 'mcp', 'cloudflare'] },
|
|
60
|
+
{ label: 'Automation', categories: ['batch', 'automation', 'checkin', 'watchers', 'orchestration', 'planner', 'wrfc'] },
|
|
61
|
+
{ label: 'Runtime & Data', categories: ['storage', 'atRest', 'sandbox', 'fetch', 'agents', 'runtime', 'cache', 'telemetry', 'diagnostics', 'learning'] },
|
|
51
62
|
{ label: 'Advanced', categories: ['flags', 'release', 'danger'] },
|
|
52
63
|
];
|
|
53
64
|
|
|
@@ -62,11 +73,37 @@ export interface SettingEntry {
|
|
|
62
73
|
conflict?: boolean;
|
|
63
74
|
sourceLabel?: string;
|
|
64
75
|
lockReason?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Present when this row is a feature-unit toggle header (a feature flag
|
|
78
|
+
* rendered as one unit with its config keys beneath it). The row's
|
|
79
|
+
* setting.key is `featureFlags.<id>` and setting.type is 'boolean'; toggling
|
|
80
|
+
* routes to the feature-flag manager, not a plain config write.
|
|
81
|
+
*/
|
|
82
|
+
flag?: FlagEntry;
|
|
83
|
+
/**
|
|
84
|
+
* Present when this row is a config sub-option OWNED by a feature unit — the
|
|
85
|
+
* id of the owning flag. Drives the indented rendering beneath its header and
|
|
86
|
+
* marks the row as claimed so it does not double-list as an orphan.
|
|
87
|
+
*/
|
|
88
|
+
ownerFlagId?: string;
|
|
65
89
|
}
|
|
66
90
|
|
|
67
91
|
export interface FlagEntry {
|
|
68
92
|
flag: FeatureFlag;
|
|
93
|
+
/** Live effective state as the runtime currently sees it. */
|
|
69
94
|
state: FlagState;
|
|
95
|
+
/**
|
|
96
|
+
* Last known config-layer (persisted) value. Equals `state` unless a
|
|
97
|
+
* startup-gated flag was toggled this session — then it holds the value that
|
|
98
|
+
* will take effect on the next launch.
|
|
99
|
+
*/
|
|
100
|
+
persistedState: FlagState;
|
|
101
|
+
/**
|
|
102
|
+
* True when a startup-gated flag's persisted value differs from its effective
|
|
103
|
+
* state, i.e. a restart is required before the change takes effect. Sourced
|
|
104
|
+
* from FeatureFlagManager.getAll(), never guessed.
|
|
105
|
+
*/
|
|
106
|
+
pendingRestart: boolean;
|
|
70
107
|
}
|
|
71
108
|
|
|
72
109
|
export interface McpEntry {
|