@pellux/goodvibes-tui 1.17.0 → 1.19.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 +208 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5025 -1636
- package/package.json +7 -7
- package/src/cli/endpoints.ts +50 -4
- package/src/cli/help.ts +29 -2
- package/src/cli/management-commands.ts +99 -23
- package/src/cli/management.ts +0 -2
- package/src/cli/service-posture.ts +5 -3
- package/src/cli/status.ts +27 -6
- package/src/cli/surface-command.ts +29 -15
- package/src/core/consolidation-receipt.ts +41 -0
- package/src/core/conversation-rendering.ts +14 -5
- package/src/core/memory-diagnostics-gateway.ts +99 -0
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/memory-status.ts +118 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +21 -0
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +39 -0
- package/src/core/turn-event-wiring.ts +6 -0
- package/src/core/voice-provision-gateway.ts +202 -0
- package/src/core/voice-provision-status.ts +197 -0
- package/src/daemon/cli.ts +127 -60
- package/src/input/command-registry.ts +21 -0
- package/src/input/commands/acp-runtime.ts +72 -0
- package/src/input/commands/devices-runtime.ts +109 -0
- package/src/input/commands/experience-runtime.ts +25 -4
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- package/src/input/commands/health-runtime.ts +15 -1
- package/src/input/commands/operator-rpc.ts +32 -11
- package/src/input/commands/power-runtime.ts +56 -0
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- package/src/input/commands/workstream-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +44 -25
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -0
- package/src/input/config-modal-types.ts +11 -0
- package/src/input/config-modal.ts +65 -2
- package/src/input/handler-shortcuts.ts +23 -0
- package/src/input/keybindings.ts +19 -1
- package/src/input/settings-modal-data.ts +31 -37
- package/src/input/settings-modal-types.ts +5 -2
- package/src/input/tts-settings-actions.ts +27 -8
- package/src/input/voice-local-settings.ts +52 -0
- package/src/main.ts +42 -42
- package/src/panels/builtin/operations.ts +54 -1
- package/src/panels/builtin/shared.ts +12 -0
- package/src/panels/builtin-modals.ts +49 -32
- package/src/panels/diff-panel.ts +34 -1
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +120 -4
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +60 -44
- package/src/panels/fleet-read-model.ts +34 -3
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-stop.ts +38 -2
- package/src/panels/fleet-tabs.ts +2 -2
- package/src/panels/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- package/src/panels/modals/memory-modal.ts +149 -5
- package/src/panels/modals/pairing-modal.ts +198 -68
- package/src/panels/modals/security-modal.ts +6 -0
- package/src/panels/workstream-graph-render.ts +123 -0
- package/src/renderer/composer-fragments.ts +69 -0
- package/src/renderer/settings-modal-helpers.ts +9 -0
- package/src/renderer/settings-modal.ts +4 -2
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/tool-call.ts +12 -6
- package/src/renderer/ui-factory.ts +32 -0
- package/src/runtime/bootstrap-command-context.ts +3 -1
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +6 -6
- package/src/runtime/bootstrap-shell.ts +7 -0
- package/src/runtime/code-index-services.ts +15 -1
- package/src/runtime/durability-services.ts +36 -1
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +2 -0
- package/src/runtime/interaction-seams.ts +109 -0
- package/src/runtime/knowledge-services.ts +94 -0
- package/src/runtime/legacy-daemon-migration.ts +38 -6
- package/src/runtime/legacy-daemon-reconcile.ts +439 -0
- package/src/runtime/memory-governance-services.ts +79 -0
- package/src/runtime/notification-dispatch.ts +28 -0
- package/src/runtime/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +105 -107
- package/src/runtime/ui-services.ts +3 -0
- package/src/runtime/voice-setup-services.ts +104 -0
- package/src/shell/blocking-input.ts +21 -141
- package/src/shell/recovery-input-helpers.ts +53 -166
- package/src/version.ts +11 -2
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// /queue — the mid-turn message queue as an editable list.
|
|
5
|
+
//
|
|
6
|
+
// Messages typed while a turn is streaming are QUEUED (they deliver after the
|
|
7
|
+
// turn). Until a message is delivered it stays editable and deletable via the
|
|
8
|
+
// SDK's editQueuedMessage / deleteQueuedMessage verbs; a delivered message has
|
|
9
|
+
// left the queue and is no longer listed — delivery is immutability. This
|
|
10
|
+
// command is the editable interface over the same list the composer renders.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export function registerQueueRuntimeCommands(registry: CommandRegistry): void {
|
|
14
|
+
registry.register({
|
|
15
|
+
name: 'queue',
|
|
16
|
+
aliases: ['q'],
|
|
17
|
+
description: 'Mid-turn message queue — list, edit, or delete still-undelivered messages',
|
|
18
|
+
usage: '[list | edit <n> <text> | delete <n>]',
|
|
19
|
+
argsHint: '[list|edit <n> <text>|delete <n>]',
|
|
20
|
+
handler(args, ctx) {
|
|
21
|
+
const list = ctx.listQueuedMessages?.() ?? [];
|
|
22
|
+
const sub = (args[0] ?? 'list').toLowerCase();
|
|
23
|
+
|
|
24
|
+
if (sub === 'list' || args.length === 0) {
|
|
25
|
+
if (list.length === 0) {
|
|
26
|
+
ctx.print('No queued messages. (Messages typed while a turn is streaming queue here until it finishes.)');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const lines = [`Queued messages (${list.length}) — editable until delivered:`];
|
|
30
|
+
list.forEach((m, i) => lines.push(` ${i + 1}. ${m.text}`));
|
|
31
|
+
lines.push('Use /queue edit <n> <new text> or /queue delete <n>.');
|
|
32
|
+
ctx.print(lines.join('\n'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Both edit and delete address a message by its 1-based list position.
|
|
37
|
+
const n = Number.parseInt(args[1] ?? '', 10);
|
|
38
|
+
if (!Number.isInteger(n) || n < 1 || n > list.length) {
|
|
39
|
+
ctx.print(list.length === 0
|
|
40
|
+
? 'No queued messages to act on.'
|
|
41
|
+
: `No queued message #${args[1] ?? ''}. Valid range: 1..${list.length}. Run /queue to list them.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const target = list[n - 1]!;
|
|
45
|
+
|
|
46
|
+
if (sub === 'delete') {
|
|
47
|
+
const ok = ctx.deleteQueuedMessage?.(target.id) ?? false;
|
|
48
|
+
ctx.print(ok
|
|
49
|
+
? `Deleted queued message #${n}.`
|
|
50
|
+
: `Could not delete message #${n} — it was already delivered (delivered messages are immutable).`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (sub === 'edit') {
|
|
55
|
+
const text = args.slice(2).join(' ');
|
|
56
|
+
if (!text.trim()) {
|
|
57
|
+
ctx.print('Usage: /queue edit <n> <new text>. Provide the replacement text.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const ok = ctx.editQueuedMessage?.(target.id, text) ?? false;
|
|
61
|
+
ctx.print(ok
|
|
62
|
+
? `Edited queued message #${n}.`
|
|
63
|
+
: `Could not edit message #${n} — it was already delivered (delivered messages are immutable).`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ctx.print('Usage: /queue [list | edit <n> <text> | delete <n>]');
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -25,6 +25,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
25
25
|
import type { CommandContext } from '../command-registry.ts';
|
|
26
26
|
import type { WorkstreamCommandService } from '../../runtime/workstream-services.ts';
|
|
27
27
|
import { requirePanelManager } from './runtime-services.ts';
|
|
28
|
+
import { describeFailureReason, isTurnBudgetReason } from '../../core/turn-budget-outcome.ts';
|
|
28
29
|
import { type AttemptGraphItem, renderDependentHoldLines } from './workstream-attempt-dependents.ts';
|
|
29
30
|
|
|
30
31
|
/** Resolve a workstream's items for dependent-hold display; null-safe against a stubbed engine. */
|
|
@@ -58,7 +59,11 @@ function resolveCandidate(group: HeldMergeGroup, ref: string): AttemptCandidate
|
|
|
58
59
|
|
|
59
60
|
function candidateLine(c: AttemptCandidate): string {
|
|
60
61
|
const files = c.diff ? `${c.diff.files.length} file(s)` : 'no diff';
|
|
61
|
-
const state = c.state === 'held-merge'
|
|
62
|
+
const state = c.state === 'held-merge'
|
|
63
|
+
? 'held (pick-ready)'
|
|
64
|
+
: isTurnBudgetReason(c.failureReason)
|
|
65
|
+
? describeFailureReason(c.failureReason) // a spent budget, not an infrastructure failure
|
|
66
|
+
: `failed${c.failureReason ? `: ${c.failureReason}` : ''}`;
|
|
62
67
|
return ` ${c.attemptIndex + 1}. [${state}] ${c.title} — ${files}, item ${shortId(c.itemId)}`;
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -102,6 +102,33 @@ async function checkFanoutQuotaWarning(ctx: CommandContext, draft: WorkstreamDra
|
|
|
102
102
|
].join('\n');
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Launch an already-approved draft through its gates (best-of-N attempt
|
|
107
|
+
* validation, then the fan-out quota warning unless `--force`), and print the
|
|
108
|
+
* outcome. Shared by `launch` and by `approve` (which launches in the same act
|
|
109
|
+
* — the one confirmed step that replaces the old approve-then-retype ceremony).
|
|
110
|
+
*/
|
|
111
|
+
async function launchApprovedDraft(ctx: CommandContext, service: WorkstreamCommandService, id: string, draft: WorkstreamDraft, force: boolean): Promise<void> {
|
|
112
|
+
const attemptsCheck = validateAttempts(draft.spec);
|
|
113
|
+
if (attemptsCheck.violations.length > 0) {
|
|
114
|
+
ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!force) {
|
|
118
|
+
const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
|
|
119
|
+
if (quotaWarning) {
|
|
120
|
+
ctx.print(quotaWarning);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const result = service.launchDraft(id);
|
|
125
|
+
if (!result) {
|
|
126
|
+
ctx.print(`Could not launch ${id}.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
ctx.print(`Launched workstream ${result.workstreamId} — track it with /workstream status ${result.workstreamId} or the Fleet panel.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
105
132
|
/**
|
|
106
133
|
* phase.role for a 'custom' phase is the free-text description passed to
|
|
107
134
|
* `/workstream insert-phase` — it is purely COSMETIC. Neither
|
|
@@ -377,7 +404,7 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
377
404
|
registry.register({
|
|
378
405
|
name: 'workstream',
|
|
379
406
|
description: 'Author and oversee multi-phase agent workstreams (orchestration engine)',
|
|
380
|
-
usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
|
|
407
|
+
usage: 'create [--isolation shared|worktree] <task...> | list | status [id] | insert-phase <id> <description...> | edit-item <id> <item#> <brief...> | remove-item <id> <item#> | move-item <id> <item#> <pos> | approve <id> [--no-launch] [--force] | edit <id> [--isolation shared|worktree] <task...> | launch <id> [--force] | cancel <id> | attempts list|diff|judge|pick',
|
|
381
408
|
argsHint: 'create [--isolation worktree] <task> | list | status [id] | edit-item | remove-item | move-item | approve | edit | launch | cancel | attempts',
|
|
382
409
|
handler: async (args, ctx: CommandContext) => {
|
|
383
410
|
const service = ctx.session.workstreamEngine;
|
|
@@ -470,9 +497,14 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
470
497
|
}
|
|
471
498
|
|
|
472
499
|
if (sub === 'approve') {
|
|
473
|
-
|
|
500
|
+
// Approve IS the one confirmed act: it approves AND launches (typing the
|
|
501
|
+
// verb with an explicit id is the confirmation). --no-launch keeps the
|
|
502
|
+
// old split for scripts; --force skips the fan-out quota warning.
|
|
503
|
+
const noLaunch = args.includes('--no-launch');
|
|
504
|
+
const force = args.includes('--force');
|
|
505
|
+
const id = args.find((arg, index) => index > 0 && arg !== '--no-launch' && arg !== '--force');
|
|
474
506
|
if (!id) {
|
|
475
|
-
ctx.print('Usage: /workstream approve <id>');
|
|
507
|
+
ctx.print('Usage: /workstream approve <id> [--no-launch] [--force]');
|
|
476
508
|
return;
|
|
477
509
|
}
|
|
478
510
|
const draft = service.approveDraft(id);
|
|
@@ -480,7 +512,11 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
480
512
|
ctx.print(draftNotFoundMessage(service, id));
|
|
481
513
|
return;
|
|
482
514
|
}
|
|
483
|
-
|
|
515
|
+
if (noLaunch) {
|
|
516
|
+
ctx.print(`Approved ${id} (held, not launched). Launch it with /workstream launch ${id}.`);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
await launchApprovedDraft(ctx, service, id, draft, force);
|
|
484
520
|
return;
|
|
485
521
|
}
|
|
486
522
|
|
|
@@ -554,27 +590,10 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
554
590
|
return;
|
|
555
591
|
}
|
|
556
592
|
if (!draft.approved) {
|
|
557
|
-
ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first.`);
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const attemptsCheck = validateAttempts(draft.spec);
|
|
561
|
-
if (attemptsCheck.violations.length > 0) {
|
|
562
|
-
ctx.print(`Cannot launch ${id} — best-of-N plan constraints are violated:\n${attemptsCheck.violations.map((v) => ` - ${v}`).join('\n')}\nFix the plan (or drop the attempts) and re-approve.`);
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
if (!force) {
|
|
566
|
-
const quotaWarning = await checkFanoutQuotaWarning(ctx, draft);
|
|
567
|
-
if (quotaWarning) {
|
|
568
|
-
ctx.print(quotaWarning);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
const result = service.launchDraft(id);
|
|
573
|
-
if (!result) {
|
|
574
|
-
ctx.print(`Could not launch ${id}.`);
|
|
593
|
+
ctx.print(`Proposal ${id} is not approved yet. Run /workstream approve ${id} first (or approve launches it directly).`);
|
|
575
594
|
return;
|
|
576
595
|
}
|
|
577
|
-
ctx
|
|
596
|
+
await launchApprovedDraft(ctx, service, id, draft, force);
|
|
578
597
|
return;
|
|
579
598
|
}
|
|
580
599
|
|
|
@@ -616,9 +635,9 @@ export function registerWorkstreamRuntimeCommands(registry: CommandRegistry): vo
|
|
|
616
635
|
+ ' /workstream edit-item <id> <item#> <new brief...>\n'
|
|
617
636
|
+ ' /workstream remove-item <id> <item#>\n'
|
|
618
637
|
+ ' /workstream move-item <id> <item#> <new-position#>\n'
|
|
619
|
-
+ ' /workstream approve <id
|
|
638
|
+
+ ' /workstream approve <id> [--no-launch] [--force] (approves AND launches in one act)\n'
|
|
620
639
|
+ ' /workstream edit <id> [--isolation shared|worktree] <new task...>\n'
|
|
621
|
-
+ ' /workstream launch <id> [--force]\n'
|
|
640
|
+
+ ' /workstream launch <id> [--force] (for a --no-launch-held or re-launched draft)\n'
|
|
622
641
|
+ ' /workstream cancel <id>',
|
|
623
642
|
);
|
|
624
643
|
},
|
|
@@ -145,19 +145,46 @@ export function registerWorktreeRuntimeCommands(registry: CommandRegistry): void
|
|
|
145
145
|
: `${header}: ${targetKind} ${targetId}\n No attached worktrees tracked.`);
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
|
-
if (sub === '
|
|
148
|
+
if (sub === 'discard') {
|
|
149
|
+
// Discard is a real destructive act, not a metadata flip: route it
|
|
150
|
+
// through the worktrees.discard operator verb (preservation commit,
|
|
151
|
+
// git worktree removal, honest receipt) — the SAME behavior as the
|
|
152
|
+
// Fleet panel's D key. Typing the verb with an explicit path is the
|
|
153
|
+
// confirmation.
|
|
154
|
+
const path = args[1];
|
|
155
|
+
if (!path) {
|
|
156
|
+
ctx.print('Usage: /worktree discard <path>');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const rpc = getOperatorRpc(ctx);
|
|
160
|
+
if (!rpc.available) {
|
|
161
|
+
ctx.print(`[worktree discard] ${rpc.reason}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const receipt = await rpc.sdk.operator.invoke('worktrees.discard', { path });
|
|
166
|
+
if (receipt.ok) {
|
|
167
|
+
ctx.print([
|
|
168
|
+
`[worktree discard] Discarded ${receipt.path}`,
|
|
169
|
+
` branch kept: ${receipt.branch || '(unknown)'}`,
|
|
170
|
+
` preservation commit: ${receipt.preservedCommit || '(none — nothing to preserve)'}`,
|
|
171
|
+
` ${receipt.detail}`,
|
|
172
|
+
].join('\n'));
|
|
173
|
+
} else {
|
|
174
|
+
ctx.print(`[worktree discard] refused for ${receipt.path}: ${receipt.detail}`);
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
ctx.print(`[worktree discard] round-trip request failed: ${describeOperatorRpcError(error)}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (sub === 'pause' || sub === 'resume' || sub === 'keep') {
|
|
149
182
|
const path = args[1];
|
|
150
183
|
if (!path) {
|
|
151
184
|
ctx.print(`Usage: /worktree ${sub} <path>`);
|
|
152
185
|
return;
|
|
153
186
|
}
|
|
154
|
-
const nextState = sub === 'resume'
|
|
155
|
-
? 'active'
|
|
156
|
-
: sub === 'pause'
|
|
157
|
-
? 'paused'
|
|
158
|
-
: sub === 'keep'
|
|
159
|
-
? 'kept'
|
|
160
|
-
: 'discard';
|
|
187
|
+
const nextState = sub === 'resume' ? 'active' : sub === 'pause' ? 'paused' : 'kept';
|
|
161
188
|
runtime.setState(path, nextState);
|
|
162
189
|
ctx.print(`Updated ${path} to state ${nextState}.`);
|
|
163
190
|
return;
|
package/src/input/commands.ts
CHANGED
|
@@ -17,6 +17,9 @@ import { registerOperatorRuntimeCommands } from './commands/operator-runtime.ts'
|
|
|
17
17
|
import { registerPluginRuntimeCommands } from './commands/plugin-runtime.ts';
|
|
18
18
|
import { registerDiffRuntimeCommands } from './commands/diff-runtime.ts';
|
|
19
19
|
import { registerGitRuntimeCommands } from './commands/git-runtime.ts';
|
|
20
|
+
import { registerQueueRuntimeCommands } from './commands/queue-runtime.ts';
|
|
21
|
+
import { registerPowerRuntimeCommands } from './commands/power-runtime.ts';
|
|
22
|
+
import { registerGraphRuntimeCommands } from './commands/graph-runtime.ts';
|
|
20
23
|
import { registerTestRuntimeCommands } from './commands/test-runtime.ts';
|
|
21
24
|
import { registerNotifyRuntimeCommands } from './commands/notify-runtime.ts';
|
|
22
25
|
import { registerReplayRuntimeCommands } from './commands/replay-runtime.ts';
|
|
@@ -55,11 +58,13 @@ import { registerLocalProviderRuntimeCommands } from './commands/local-provider-
|
|
|
55
58
|
import { registerHealthRuntimeCommands } from './commands/health-runtime.ts';
|
|
56
59
|
import { registerSettingsSyncRuntimeCommands } from './commands/settings-sync-runtime.ts';
|
|
57
60
|
import { registerWorktreeRuntimeCommands } from './commands/worktree-runtime.ts';
|
|
61
|
+
import { registerAcpRuntimeCommands } from './commands/acp-runtime.ts';
|
|
58
62
|
import { registerProviderAccountsRuntimeCommands } from './commands/provider-accounts-runtime.ts';
|
|
59
63
|
import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
|
|
60
64
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
61
65
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
62
66
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
67
|
+
import { registerDevicesRuntimeCommands } from './commands/devices-runtime.ts';
|
|
63
68
|
import { registerRelayRuntimeCommands } from './commands/relay-runtime.ts';
|
|
64
69
|
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
65
70
|
import { registerTtsRuntimeCommands } from './commands/tts-runtime.ts';
|
|
@@ -101,6 +106,9 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
101
106
|
{ category: 'Plugins', register: registerPluginRuntimeCommands },
|
|
102
107
|
{ category: 'Diff & Review', register: registerDiffRuntimeCommands },
|
|
103
108
|
{ category: 'Git', register: registerGitRuntimeCommands },
|
|
109
|
+
{ category: 'Shell & Session', register: registerQueueRuntimeCommands },
|
|
110
|
+
{ category: 'Shell & Session', register: registerPowerRuntimeCommands },
|
|
111
|
+
{ category: 'Automation', register: registerGraphRuntimeCommands },
|
|
104
112
|
{ category: 'Testing', register: registerTestRuntimeCommands },
|
|
105
113
|
{ category: 'Notifications', register: registerNotifyRuntimeCommands },
|
|
106
114
|
{ category: 'Sessions & Replay', register: registerReplayRuntimeCommands },
|
|
@@ -133,11 +141,13 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
133
141
|
{ category: 'Health', register: registerHealthRuntimeCommands },
|
|
134
142
|
{ category: 'Settings Sync', register: registerSettingsSyncRuntimeCommands },
|
|
135
143
|
{ category: 'Worktrees', register: registerWorktreeRuntimeCommands },
|
|
144
|
+
{ category: 'Fleet', register: registerAcpRuntimeCommands },
|
|
136
145
|
{ category: 'Provider Accounts', register: registerProviderAccountsRuntimeCommands },
|
|
137
146
|
{ category: 'Local Auth', register: registerLocalAuthRuntimeCommands },
|
|
138
147
|
{ category: 'Intelligence', register: registerIntelligenceRuntimeCommands },
|
|
139
148
|
{ category: 'Conversation', register: registerConversationRuntimeCommands },
|
|
140
149
|
{ category: 'QR Codes', register: registerQrcodeRuntimeCommands },
|
|
150
|
+
{ category: 'Security', register: registerDevicesRuntimeCommands },
|
|
141
151
|
{ category: 'Relay', register: registerRelayRuntimeCommands },
|
|
142
152
|
{ category: 'Onboarding', register: registerOnboardingRuntimeCommands },
|
|
143
153
|
{ category: 'Voice & TTS', register: registerTtsRuntimeCommands },
|
|
@@ -106,6 +106,17 @@ export interface ConfigModalActionContext {
|
|
|
106
106
|
readonly setStatus: (message: string) => void;
|
|
107
107
|
/** Close the modal (focus returns to whatever opened it). */
|
|
108
108
|
readonly close: () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Switch the active tab and select a row within it, by id — the in-surface
|
|
111
|
+
* "jump" affordance (e.g. the Memory modal's Proposals tab jumping to an
|
|
112
|
+
* affected record in the Review Queue tab, rather than opening a second
|
|
113
|
+
* modal). Host-owned, populated by ConfigModal itself (like `close`/
|
|
114
|
+
* `setStatus`) — a surface's `onAction` calls it, never constructs it. A
|
|
115
|
+
* no-op if either id is not present in the surface's current structure; the
|
|
116
|
+
* surface should check reachability itself and print an honest status line
|
|
117
|
+
* when it isn't, rather than relying on a silent no-op.
|
|
118
|
+
*/
|
|
119
|
+
readonly jumpToRow?: (tabId: string, rowId: string) => void;
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/** A declarative action bound to a key. */
|
|
@@ -339,7 +339,7 @@ export class ConfigModal {
|
|
|
339
339
|
* action (consumed). Handles the two-press confirm for destructive actions.
|
|
340
340
|
* This is an interaction boundary (syncs structure first).
|
|
341
341
|
*/
|
|
342
|
-
fireAction(key: string, ctx: Omit<ConfigModalActionContext, 'row' | 'tabId' | 'setStatus' | 'close' | 'requestRender'>): boolean {
|
|
342
|
+
fireAction(key: string, ctx: Omit<ConfigModalActionContext, 'row' | 'tabId' | 'setStatus' | 'close' | 'requestRender' | 'jumpToRow'>): boolean {
|
|
343
343
|
this.syncStructure();
|
|
344
344
|
const action = this.resolveAction(key);
|
|
345
345
|
if (!action) return false;
|
|
@@ -359,11 +359,36 @@ export class ConfigModal {
|
|
|
359
359
|
requestRender: this.requestRender,
|
|
360
360
|
setStatus: (m: string) => { this.statusMessage = m; },
|
|
361
361
|
close: () => this.close(),
|
|
362
|
+
jumpToRow: (tabId: string, rowId: string) => this.jumpToRow(tabId, rowId),
|
|
362
363
|
};
|
|
363
364
|
this.surface?.onAction?.(action.id, fullCtx);
|
|
364
365
|
return true;
|
|
365
366
|
}
|
|
366
367
|
|
|
368
|
+
/**
|
|
369
|
+
* In-surface "jump": switch the active tab (by id) and select a specific
|
|
370
|
+
* row (by id) within it — the mechanism a surface's own action handler
|
|
371
|
+
* uses to move the user from one tab to a specific row in another (e.g.
|
|
372
|
+
* the Memory modal's Proposals tab jumping to an affected record in the
|
|
373
|
+
* Review Queue tab). An interaction boundary (re-syncs structure first,
|
|
374
|
+
* same as `_switchTab`). A no-op if either id is absent from the
|
|
375
|
+
* freshly-synced structure.
|
|
376
|
+
*/
|
|
377
|
+
jumpToRow(tabId: string, rowId: string): void {
|
|
378
|
+
this._clearConfirm();
|
|
379
|
+
this.filterActive = false;
|
|
380
|
+
this.filterQuery = '';
|
|
381
|
+
this.syncStructure();
|
|
382
|
+
const tab = this.frozenView?.tabs.find((t) => t.id === tabId);
|
|
383
|
+
if (!tab) return;
|
|
384
|
+
if (!tab.rows.some((r) => r.id === rowId && r.selectable !== false)) return;
|
|
385
|
+
this.activeTabId = tabId;
|
|
386
|
+
this.selectedRowId = rowId;
|
|
387
|
+
this.scrollOffset = 0;
|
|
388
|
+
this.statusMessage = '';
|
|
389
|
+
this._clampScroll();
|
|
390
|
+
}
|
|
391
|
+
|
|
367
392
|
/** Clear a pending confirm when the user navigates or presses an unrelated key. */
|
|
368
393
|
clearConfirmOnMiss(): void {
|
|
369
394
|
this._clearConfirm();
|
|
@@ -442,7 +467,7 @@ export class ConfigModal {
|
|
|
442
467
|
});
|
|
443
468
|
|
|
444
469
|
const visible = this.visibleRows;
|
|
445
|
-
const windowed =
|
|
470
|
+
const windowed = this._windowRowsByLineBudget(allRows, this.scrollOffset, visible, labelWrapWidth);
|
|
446
471
|
|
|
447
472
|
// item 1: while filtering, the surface's own action/tab hints are
|
|
448
473
|
// unreliable (their printable-letter keys are captured by the filter
|
|
@@ -505,6 +530,44 @@ export class ConfigModal {
|
|
|
505
530
|
return clamped.join('\n');
|
|
506
531
|
}
|
|
507
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Modal sizing rule (owner, zero tolerance: a modal/list must never clip
|
|
535
|
+
* its full descriptive text — size to content or scroll, never clip).
|
|
536
|
+
* `scrollOffset`/`visible` are ROW-count based, which is exactly right when
|
|
537
|
+
* every row wraps to one line — but ModalFactory renders each row's WRAPPED
|
|
538
|
+
* lines and then bounds the whole section to a fixed content-row budget
|
|
539
|
+
* computed from `visible` (see renderConfigModal's targetContentRows). A
|
|
540
|
+
* row whose label wraps to MULTIPLE lines can therefore push the total past
|
|
541
|
+
* that budget, and the LAST row handed to the renderer gets cut off
|
|
542
|
+
* mid-line instead of being deferred to the next scroll page — the
|
|
543
|
+
* clipping hazard this closes.
|
|
544
|
+
*
|
|
545
|
+
* Windows `rows` starting at `scrollOffset`, greedily including whole rows
|
|
546
|
+
* while their CUMULATIVE wrapped-line count (at `wrapWidth`) stays within
|
|
547
|
+
* the `visible` line budget. Always includes at least the first candidate
|
|
548
|
+
* row so the view is never empty — a single row that alone exceeds the
|
|
549
|
+
* budget is shown in full rather than replaced with nothing; only a
|
|
550
|
+
* SUBSEQUENT row that would overflow is deferred to scrolling instead of
|
|
551
|
+
* being rendered partially.
|
|
552
|
+
*/
|
|
553
|
+
private _windowRowsByLineBudget(
|
|
554
|
+
rows: readonly ConfigModalRenderRow[],
|
|
555
|
+
scrollOffset: number,
|
|
556
|
+
visible: number,
|
|
557
|
+
wrapWidth: number,
|
|
558
|
+
): ConfigModalRenderRow[] {
|
|
559
|
+
const candidates = rows.slice(scrollOffset);
|
|
560
|
+
const out: ConfigModalRenderRow[] = [];
|
|
561
|
+
let usedLines = 0;
|
|
562
|
+
for (const row of candidates) {
|
|
563
|
+
const lineCount = Math.max(1, wrapText(row.label, wrapWidth).length);
|
|
564
|
+
if (out.length > 0 && usedLines + lineCount > visible) break;
|
|
565
|
+
out.push(row);
|
|
566
|
+
usedLines += lineCount;
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
|
|
508
571
|
/** Footer text for the active filter: the query + a truthful match count. */
|
|
509
572
|
private _filterStatusHint(matched: number, total: number): string {
|
|
510
573
|
const q = this.filterQuery;
|
|
@@ -119,6 +119,29 @@ export function handleGlobalShortcutToken(
|
|
|
119
119
|
state.handleCtrlC();
|
|
120
120
|
return true;
|
|
121
121
|
|
|
122
|
+
case 'cancel-tool-call':
|
|
123
|
+
// Cancel JUST the currently-running tool call (the live transcript row).
|
|
124
|
+
// No-op (still consumed) when nothing is running — the composer keeps the
|
|
125
|
+
// keystroke rather than letting it fall through as text.
|
|
126
|
+
state.commandContext?.cancelToolCall?.();
|
|
127
|
+
return true;
|
|
128
|
+
|
|
129
|
+
case 'toggle-memory-provenance':
|
|
130
|
+
// Expand/collapse the provenance chip's drill-in. No-op when the chip is
|
|
131
|
+
// not showing; still consumed so the composer never sees a stray Alt+M.
|
|
132
|
+
state.commandContext?.toggleMemoryProvenance?.();
|
|
133
|
+
return true;
|
|
134
|
+
|
|
135
|
+
case 'toggle-keep-awake': {
|
|
136
|
+
// Flip the daemon-held keep-awake toggle; the always-visible "sleep
|
|
137
|
+
// disabled" chip is the confirmation. Fire-and-forget (the seam renders on
|
|
138
|
+
// completion); errors are swallowed so a keystroke never throws.
|
|
139
|
+
const power = state.commandContext;
|
|
140
|
+
const current = power?.getPowerState?.().keepAwake ?? false;
|
|
141
|
+
void power?.setKeepAwake?.(!current);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
122
145
|
case 'screen-clear':
|
|
123
146
|
state.commandContext?.clearScreen?.();
|
|
124
147
|
return true;
|
package/src/input/keybindings.ts
CHANGED
|
@@ -68,7 +68,10 @@ export type KeyAction =
|
|
|
68
68
|
| 'kill-to-start'
|
|
69
69
|
| 'kill-word-forward'
|
|
70
70
|
| 'yank'
|
|
71
|
-
| 'yank-pop'
|
|
71
|
+
| 'yank-pop'
|
|
72
|
+
| 'cancel-tool-call'
|
|
73
|
+
| 'toggle-keep-awake'
|
|
74
|
+
| 'toggle-memory-provenance';
|
|
72
75
|
|
|
73
76
|
/** Human-readable description for each action (used in /keybindings display). */
|
|
74
77
|
export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
@@ -111,6 +114,9 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
|
111
114
|
'kill-word-forward': 'Kill word forward into kill ring (Alt+D)',
|
|
112
115
|
'yank': 'Yank (paste) from kill ring (Ctrl+Shift+Y)',
|
|
113
116
|
'yank-pop': 'Rotate kill ring and yank next entry (Alt+Y)',
|
|
117
|
+
'cancel-tool-call': 'Cancel the running tool call (the turn continues)',
|
|
118
|
+
'toggle-keep-awake': 'Toggle keep-awake (the "sleep disabled" chip)',
|
|
119
|
+
'toggle-memory-provenance': 'List/hide the memories a turn used (provenance chip drill-in)',
|
|
114
120
|
};
|
|
115
121
|
|
|
116
122
|
/** Default key bindings for all actions. */
|
|
@@ -189,6 +195,18 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
|
|
|
189
195
|
'yank': [{ key: 'y', ctrl: true, shift: true }],
|
|
190
196
|
// Alt+Y: yank-pop (rotate ring after yank)
|
|
191
197
|
'yank-pop': [{ key: 'y', alt: true }],
|
|
198
|
+
// Alt+C: cancel the currently-running tool call (the turn continues). Alt+C
|
|
199
|
+
// is unused by any other default and representable by the tokenizer's
|
|
200
|
+
// { key, alt } combo form. Ctrl+C stays whole-input clear/cancel; this is the
|
|
201
|
+
// narrower per-tool stop.
|
|
202
|
+
'cancel-tool-call': [{ key: 'c', alt: true }],
|
|
203
|
+
// Alt+A: toggle keep-awake (the one-key affordance for the "sleep disabled"
|
|
204
|
+
// chip). Alt+A is unused by any other default. The always-visible chip is the
|
|
205
|
+
// confirmation of the toggle's state.
|
|
206
|
+
'toggle-keep-awake': [{ key: 'a', alt: true }],
|
|
207
|
+
// Alt+M: expand/collapse the "used N memories" provenance chip's drill-in.
|
|
208
|
+
// Alt+M is unused by any other default. A no-op when the chip is not showing.
|
|
209
|
+
'toggle-memory-provenance': [{ key: 'm', alt: true }],
|
|
192
210
|
};
|
|
193
211
|
|
|
194
212
|
/** Resolved overrides type: each key can be a single combo or array. */
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from './worktree-setup-config.ts';
|
|
26
26
|
import { BUDGET_ALERT_USD_CONFIG_KEY, BUDGET_ALERT_USD_DEFAULT, readBudgetAlertUsd } from '../export/cost-utils.ts';
|
|
27
27
|
import { MEMORY_PROJECTION_DIR_CONFIG_KEY, buildMemoryProjectionDirSyntheticEntry } from './commands/recall-files-config.ts';
|
|
28
|
+
import { MEMORY_SHOW_PROVENANCE_CONFIG_KEY, buildMemoryProvenanceSyntheticEntry } from '../core/memory-provenance.ts';
|
|
28
29
|
import {
|
|
29
30
|
injectSandboxExecSyntheticEntries,
|
|
30
31
|
isSandboxExecListConfigKey,
|
|
@@ -120,11 +121,11 @@ export function buildSettingGroups(
|
|
|
120
121
|
if ((rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') && groups.has('network')) {
|
|
121
122
|
groups.get('network')!.push(entry);
|
|
122
123
|
}
|
|
124
|
+
// fleet.maxSize (was orchestration.maxActiveAgents) surfaces under orchestration.
|
|
125
|
+
if (rawCat === 'fleet' && groups.has('orchestration')) groups.get('orchestration')!.push(entry);
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
//
|
|
126
|
-
// key stored under the existing `display` section (see theme-mode-config.ts for why
|
|
127
|
-
// not `appearance`), same rationale as the other synthetic settings below.
|
|
128
|
+
// Synthetic display.themeMode enum (auto|dark|light) — TUI-local, see theme-mode-config.ts.
|
|
128
129
|
const displayEntries = groups.get('display');
|
|
129
130
|
if (displayEntries && !displayEntries.some((e) => e.setting.key === (THEME_MODE_CONFIG_KEY as ConfigKey))) {
|
|
130
131
|
displayEntries.push(buildThemeModeSyntheticEntry(configManager));
|
|
@@ -151,26 +152,19 @@ export function buildSettingGroups(
|
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
//
|
|
155
|
-
// tts.speed is not yet a ConfigKey in the SDK schema (pending SDK addition).
|
|
156
|
-
// The entry is surfaced here with an honest description caveat so users can
|
|
157
|
-
// see and understand the setting before the SDK schema catches up.
|
|
155
|
+
// Synthetic tts.speed (not yet an SDK ConfigKey) — surfaced with an honest caveat.
|
|
158
156
|
if (ttsEntries && !ttsEntries.some((e) => e.setting.key === ('tts.speed' as ConfigKey))) {
|
|
159
157
|
ttsEntries.push(buildTtsSpeedSyntheticEntry(configManager));
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
//
|
|
163
|
-
// category. This key is TUI-local (not in the SDK ConfigKey union) and
|
|
164
|
-
// controls the long-task push notification threshold.
|
|
160
|
+
// Synthetic behavior.notifyAfterSeconds (TUI-local): long-task push threshold.
|
|
165
161
|
const behaviorEntries = groups.get('behavior');
|
|
166
162
|
if (behaviorEntries && !behaviorEntries.some((e) => e.setting.key === ('behavior.notifyAfterSeconds' as ConfigKey))) {
|
|
167
163
|
behaviorEntries.push(buildNotifyAfterSecondsSyntheticEntry(configManager));
|
|
168
164
|
}
|
|
169
165
|
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
// previously left it with no schema-driven inspection surface at all: it
|
|
173
|
-
// never appeared in /config, and /settings-sync show rejects any key not
|
|
166
|
+
// Synthetic behavior.budgetAlertUsd (TUI-local): no schema surface previously,
|
|
167
|
+
// never in /config; /settings-sync show rejects any key not
|
|
174
168
|
// in CONFIG_KEYS. The Cost panel's 'b' key and /cost budget <usd> remain
|
|
175
169
|
// the primary way to change it; this entry makes the current effective
|
|
176
170
|
// value (and whether it's still the "no budget configured" default)
|
|
@@ -212,6 +206,12 @@ export function buildSettingGroups(
|
|
|
212
206
|
storageEntries.push(buildMemoryProjectionDirSyntheticEntry(configManager));
|
|
213
207
|
}
|
|
214
208
|
|
|
209
|
+
// Synthetic memory.showProvenance (TUI-local, default OFF — see memory-provenance.ts).
|
|
210
|
+
const memoryEntries = groups.get('memory');
|
|
211
|
+
if (memoryEntries && !memoryEntries.some((e) => e.setting.key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey))) {
|
|
212
|
+
memoryEntries.push(buildMemoryProvenanceSyntheticEntry(configManager) as unknown as SettingEntry);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
215
|
// Inject the worktree.setup.commands / worktree.setup.carryOverGlobs
|
|
216
216
|
// synthetic entries into the orchestration category (worktree isolation is
|
|
217
217
|
// an orchestration-engine concern — see WorkstreamIsolation 'worktree').
|
|
@@ -607,44 +607,34 @@ export function refreshEntryValues(
|
|
|
607
607
|
): void {
|
|
608
608
|
for (const entries of groups.values()) {
|
|
609
609
|
for (const entry of entries) {
|
|
610
|
-
//
|
|
611
|
-
// registers the 'worktree' section in DEFAULT_CONFIG, so a plain
|
|
612
|
-
// configManager.get returns the empty default rather than throwing; the
|
|
613
|
-
// helper's guard (belt-and-suspenders for an older SDK) keeps this
|
|
614
|
-
// refresh from crashing for EVERY setting change if the section is ever
|
|
615
|
-
// absent (see worktree-setup-config.ts).
|
|
610
|
+
// worktree.setup.* read through a defensive array-normalizing helper (see worktree-setup-config.ts).
|
|
616
611
|
if (isWorktreeSetupListConfigKey(entry.setting.key)) {
|
|
617
612
|
entry.currentValue = readWorktreeSetupList(configManager, entry.setting.key);
|
|
618
613
|
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
619
614
|
continue;
|
|
620
615
|
}
|
|
621
|
-
// sandbox
|
|
622
|
-
// DEFAULT_CONFIG section (has been since before this repack, backing the
|
|
623
|
-
// VM/REPL isolation settings), so a plain get() would not throw — but an
|
|
624
|
-
// unset leaf still reads back as `undefined`, not `[]`, so this still needs
|
|
625
|
-
// the same array-normalizing read as the worktree keys above (see
|
|
626
|
-
// sandbox-exec-config.ts).
|
|
616
|
+
// sandbox.* list leaves normalize an unset undefined to [] (see sandbox-exec-config.ts).
|
|
627
617
|
if (isSandboxExecListConfigKey(entry.setting.key)) {
|
|
628
618
|
entry.currentValue = readSandboxExecList(configManager, entry.setting.key);
|
|
629
619
|
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
630
620
|
continue;
|
|
631
621
|
}
|
|
632
|
-
// permissions.execEnvScrubAllowlist: same normalizing
|
|
633
|
-
// 'permissions' is a real section but this leaf is TUI-local (see
|
|
634
|
-
// exec-env-scrub-config.ts).
|
|
622
|
+
// permissions.execEnvScrubAllowlist: same array-normalizing read (see exec-env-scrub-config.ts).
|
|
635
623
|
if (isExecEnvScrubAllowlistConfigKey(entry.setting.key)) {
|
|
636
624
|
entry.currentValue = readExecEnvScrubAllowlist(configManager);
|
|
637
625
|
entry.isDefault = (entry.currentValue as string[]).length === 0;
|
|
638
626
|
continue;
|
|
639
627
|
}
|
|
640
|
-
// '
|
|
641
|
-
// so learning.consolidation.* keys read through the plain path below like any
|
|
642
|
-
// other schema-driven setting — no special case needed anymore.
|
|
643
|
-
// Same trap still applies here: 'memory' has no DEFAULT_CONFIG entry.
|
|
628
|
+
// 'memory' has no DEFAULT_CONFIG entry — its synthetic leaves read defensively below.
|
|
644
629
|
if (entry.setting.key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
|
|
645
630
|
const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
|
|
646
|
-
entry.currentValue = refreshed.currentValue;
|
|
647
|
-
|
|
631
|
+
entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
// memory.showProvenance: same 'memory'-has-no-DEFAULT_CONFIG trap — read defensively.
|
|
635
|
+
if (entry.setting.key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey)) {
|
|
636
|
+
const refreshed = buildMemoryProvenanceSyntheticEntry(configManager);
|
|
637
|
+
entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
|
|
648
638
|
continue;
|
|
649
639
|
}
|
|
650
640
|
const raw = configManager.get(entry.setting.key as ConfigKey);
|
|
@@ -677,8 +667,12 @@ export function updateEntryForKey(
|
|
|
677
667
|
if (entry) {
|
|
678
668
|
if (key === MEMORY_PROJECTION_DIR_CONFIG_KEY) {
|
|
679
669
|
const refreshed = buildMemoryProjectionDirSyntheticEntry(configManager);
|
|
680
|
-
entry.currentValue = refreshed.currentValue;
|
|
681
|
-
|
|
670
|
+
entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (key === (MEMORY_SHOW_PROVENANCE_CONFIG_KEY as ConfigKey)) {
|
|
674
|
+
const refreshed = buildMemoryProvenanceSyntheticEntry(configManager);
|
|
675
|
+
entry.currentValue = refreshed.currentValue; entry.isDefault = refreshed.isDefault;
|
|
682
676
|
continue;
|
|
683
677
|
}
|
|
684
678
|
if (isSandboxExecListConfigKey(key)) {
|