@pellux/goodvibes-tui 1.7.0 → 1.9.1
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 +11 -0
- package/README.md +10 -9
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/config/credential-availability.ts +122 -0
- package/src/config/index.ts +15 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +71 -14
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +68 -1
- package/src/core/turn-cancellation.ts +7 -2
- package/src/core/turn-event-wiring.ts +10 -2
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +46 -4
- package/src/input/commands/codebase-runtime.ts +46 -6
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/health-runtime.ts +9 -1
- package/src/input/commands/memory.ts +68 -35
- package/src/input/commands/operator-panel-runtime.ts +31 -7
- package/src/input/commands/planning-runtime.ts +95 -6
- package/src/input/commands/qrcode-runtime.ts +25 -5
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/settings-sync-runtime.ts +15 -3
- package/src/input/commands/shell-core.ts +10 -1
- package/src/input/commands/workstream-runtime.ts +168 -18
- package/src/input/config-modal-types.ts +15 -1
- package/src/input/config-modal.ts +227 -12
- package/src/input/feed-context-factory.ts +3 -0
- package/src/input/handler-command-route.ts +10 -3
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +43 -121
- package/src/input/handler-feed.ts +32 -4
- package/src/input/handler-modal-routes.ts +78 -13
- package/src/input/handler-modal-stack.ts +11 -2
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +15 -8
- package/src/input/handler-shortcuts.ts +59 -9
- package/src/input/handler.ts +6 -1
- package/src/input/keybindings.ts +6 -5
- package/src/input/model-picker.ts +19 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +51 -2
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +28 -27
- package/src/panels/builtin/shared.ts +9 -3
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +89 -131
- package/src/panels/fleet-read-model.ts +43 -0
- package/src/panels/fleet-steer.ts +35 -2
- package/src/panels/fleet-stop.ts +29 -1
- package/src/panels/modals/keybindings-modal.ts +16 -1
- package/src/panels/modals/modal-theme.ts +35 -29
- package/src/panels/modals/pairing-modal.ts +25 -6
- package/src/panels/modals/planning-modal.ts +43 -21
- package/src/panels/modals/work-plan-modal.ts +28 -0
- package/src/panels/panel-manager.ts +15 -4
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -13
- package/src/panels/types.ts +24 -0
- package/src/permissions/prompt.ts +160 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +19 -5
- package/src/renderer/footer-tips.ts +5 -1
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +3 -1
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +18 -3
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +22 -3
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +6 -0
- package/src/runtime/bootstrap-command-parts.ts +8 -4
- package/src/runtime/bootstrap-core.ts +46 -24
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +19 -1
- package/src/runtime/bootstrap.ts +118 -5
- package/src/runtime/code-index-services.ts +25 -2
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +55 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +10 -0
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +12 -8
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +19 -3
- package/src/runtime/workstream-services.ts +160 -28
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +46 -3
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +42 -9
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { COMMON_COMMAND_NAMES, type SlashCommand } from './command-registry.ts';
|
|
2
2
|
import type { CommandRegistry } from './command-registry.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -18,6 +18,14 @@ export interface AutocompleteState {
|
|
|
18
18
|
query: string;
|
|
19
19
|
results: AutocompleteResult[];
|
|
20
20
|
selectedIndex: number;
|
|
21
|
+
/**
|
|
22
|
+
* UX-C (item 4): how many leading entries of `results` belong to the
|
|
23
|
+
* curated "common" tier — only meaningful when `query === ''` (0
|
|
24
|
+
* otherwise, since a typed filter searches everything and the common/rest
|
|
25
|
+
* split no longer applies). The renderer uses this to draw a separator
|
|
26
|
+
* before the alphabetical rest.
|
|
27
|
+
*/
|
|
28
|
+
commonCount: number;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
/**
|
|
@@ -35,6 +43,7 @@ export class AutocompleteEngine {
|
|
|
35
43
|
query: '',
|
|
36
44
|
results: [],
|
|
37
45
|
selectedIndex: 0,
|
|
46
|
+
commonCount: 0,
|
|
38
47
|
};
|
|
39
48
|
|
|
40
49
|
constructor(private registry: CommandRegistry) {}
|
|
@@ -47,6 +56,22 @@ export class AutocompleteEngine {
|
|
|
47
56
|
this.state.query = query;
|
|
48
57
|
this.state.results = this.registry.fuzzyMatch(query);
|
|
49
58
|
this.state.active = true;
|
|
59
|
+
// UX-C: fuzzyMatch('') sorts the curated common tier first (score 2),
|
|
60
|
+
// then the alphabetical rest (score 1) — both tiers alphabetical within
|
|
61
|
+
// themselves. Count the leading common run to tell the renderer where to
|
|
62
|
+
// draw the separator. A non-empty query means every command was scored on
|
|
63
|
+
// its actual match quality, not tier, so the common/rest split does not
|
|
64
|
+
// apply — the resulting 0 is not a real answer to "how many are common"
|
|
65
|
+
// and is unused because renderAutocompleteOverlay itself gates on the
|
|
66
|
+
// query string too, but zeroing here keeps the field internally honest.
|
|
67
|
+
let commonCount = 0;
|
|
68
|
+
if (query === '') {
|
|
69
|
+
for (const result of this.state.results) {
|
|
70
|
+
if (!COMMON_COMMAND_NAMES.has(result.command.name)) break;
|
|
71
|
+
commonCount++;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.state.commonCount = commonCount;
|
|
50
75
|
// Clamp selection to new result count
|
|
51
76
|
if (this.state.selectedIndex >= this.state.results.length) {
|
|
52
77
|
this.state.selectedIndex = Math.max(0, this.state.results.length - 1);
|
|
@@ -79,6 +104,7 @@ export class AutocompleteEngine {
|
|
|
79
104
|
query: '',
|
|
80
105
|
results: [],
|
|
81
106
|
selectedIndex: 0,
|
|
107
|
+
commonCount: 0,
|
|
82
108
|
};
|
|
83
109
|
}
|
|
84
110
|
|
|
@@ -8,7 +8,7 @@ import type { PermissionRequestHandler } from '@pellux/goodvibes-sdk/platform/pe
|
|
|
8
8
|
import type { SelectionItem, SelectionResult, SelectionAction } from './selection-modal.ts';
|
|
9
9
|
import type { FileUndoManager } from '@pellux/goodvibes-sdk/platform/state';
|
|
10
10
|
import type { WorkspaceCheckpointManager } from '@pellux/goodvibes-sdk/platform/workspace';
|
|
11
|
-
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
11
|
+
import type { PanelManager, PanelDeepLinkTarget } from '../panels/panel-manager.ts';
|
|
12
12
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
13
13
|
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
14
14
|
import type { OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
@@ -48,6 +48,13 @@ export interface CommandRuntimeState {
|
|
|
48
48
|
systemPrompt: string;
|
|
49
49
|
reasoningEffort: string;
|
|
50
50
|
sessionId: string;
|
|
51
|
+
/**
|
|
52
|
+
* Cumulative count of direct terminal writes the output guard intercepted
|
|
53
|
+
* this session — surfaced by /debug. Optional so the SDK's MutableRuntimeState
|
|
54
|
+
* (which has no such field) stays assignable; the guard sets it lazily on the
|
|
55
|
+
* shared runtime object. (UX-B item 1a.)
|
|
56
|
+
*/
|
|
57
|
+
terminalWritesIntercepted?: number;
|
|
51
58
|
}
|
|
52
59
|
|
|
53
60
|
/**
|
|
@@ -115,7 +122,7 @@ export interface CommandShellUiOpeners {
|
|
|
115
122
|
openSelection?: (
|
|
116
123
|
title: string,
|
|
117
124
|
items: SelectionItem[],
|
|
118
|
-
opts: { preSelectId?: string; allowSearch?: boolean; customActions?: Map<string, SelectionAction
|
|
125
|
+
opts: { preSelectId?: string; allowSearch?: boolean; customActions?: Map<string, SelectionAction>; primaryVerbLabel?: string } | undefined,
|
|
119
126
|
callback: (result: SelectionResult | null) => void,
|
|
120
127
|
) => void;
|
|
121
128
|
openSettingsModal?: (target?: string) => void;
|
|
@@ -131,7 +138,16 @@ export interface CommandShellUiOpeners {
|
|
|
131
138
|
openShortcutsOverlay?: () => void;
|
|
132
139
|
getScrollTop?: () => number;
|
|
133
140
|
openPanelPicker?: () => void;
|
|
134
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Open (and optionally focus) a panel. UX-C focus rule: the command path is
|
|
143
|
+
* "the user is mid-command-flow" — opening a panel this way leaves keyboard
|
|
144
|
+
* focus in the composer by default. Pass `{ focus: true }` for a caller that
|
|
145
|
+
* genuinely wants to grab focus (chords use panelManager.focusPanels()
|
|
146
|
+
* directly instead of this method, so no current call site needs it — but
|
|
147
|
+
* the intent is explicit rather than implicit here). `target` (DEBT-5) is a
|
|
148
|
+
* fleet deep-link jump target forwarded to PanelManager.open.
|
|
149
|
+
*/
|
|
150
|
+
showPanel?: (panelId: string, pane?: 'top' | 'bottom', target?: PanelDeepLinkTarget, opts?: { focus?: boolean }) => void;
|
|
135
151
|
focusPanels?: () => void;
|
|
136
152
|
focusPrompt?: () => void;
|
|
137
153
|
openOpsPanel?: () => void;
|
|
@@ -180,6 +196,10 @@ export interface CommandSessionServices {
|
|
|
180
196
|
readonly workstreamEngine?: import('../runtime/workstream-services.ts').WorkstreamCommandService;
|
|
181
197
|
/** Wave 5 (wo804): the repo source-tree code index — see runtime/code-index-services.ts. */
|
|
182
198
|
readonly codeIndexStore?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexStore;
|
|
199
|
+
/** Wave-5 Stage B: tool-site reindex scheduler — `/codebase status` reports its last activity. */
|
|
200
|
+
readonly codeIndexReindexScheduler?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexReindexScheduler;
|
|
201
|
+
/** Wave-5 Stage B: whether the (default-off) `agent-passive-code-injection` flag is on — for `/codebase status`. */
|
|
202
|
+
readonly isPassiveCodeInjectionFlagEnabled?: () => boolean;
|
|
183
203
|
/**
|
|
184
204
|
* Wave 5 (wo805): the MAIN interactive session's per-turn passive-injection
|
|
185
205
|
* honesty ring — `Orchestrator.getTurnInjections()`, the main-session
|
|
@@ -272,6 +292,23 @@ export interface CommandContext
|
|
|
272
292
|
};
|
|
273
293
|
}
|
|
274
294
|
|
|
295
|
+
/**
|
|
296
|
+
* UX-C palette curation (item 4): the "common" first tier the slash-command
|
|
297
|
+
* autocomplete dropdown shows before the alphabetical rest when it opens with
|
|
298
|
+
* no filter typed yet (bare '/') — the "132-command palette unranked"
|
|
299
|
+
* evaluator finding. Typed filtering (any non-empty query) is completely
|
|
300
|
+
* unaffected — fuzzyMatch still searches every registered command exactly as
|
|
301
|
+
* before; this only reorders the empty-query case. Curated for breadth across
|
|
302
|
+
* the product's main workflows rather than raw usage frequency: help/config
|
|
303
|
+
* (orientation), panel/model (workspace + provider), recall/codebase/search
|
|
304
|
+
* (knowledge), workstream/checkpoint (control-plane), imagine (generation),
|
|
305
|
+
* sessions (continuity), quit (exit).
|
|
306
|
+
*/
|
|
307
|
+
export const COMMON_COMMAND_NAMES: ReadonlySet<string> = new Set([
|
|
308
|
+
'help', 'config', 'panel', 'model', 'recall', 'codebase',
|
|
309
|
+
'workstream', 'checkpoint', 'search', 'imagine', 'sessions', 'quit',
|
|
310
|
+
]);
|
|
311
|
+
|
|
275
312
|
/**
|
|
276
313
|
* SlashCommand - A single slash command definition.
|
|
277
314
|
*/
|
|
@@ -374,7 +411,12 @@ export class CommandRegistry {
|
|
|
374
411
|
}
|
|
375
412
|
|
|
376
413
|
if (bestScore > 0 || q === '') {
|
|
377
|
-
|
|
414
|
+
// UX-C: with no query yet, rank the curated common tier (score 2)
|
|
415
|
+
// ahead of everything else (score 1) — the tie-break below then sorts
|
|
416
|
+
// each tier alphabetically, so the result is "common tier, then the
|
|
417
|
+
// alphabetical rest" rather than one flat alphabetical list.
|
|
418
|
+
const score = q === '' ? (COMMON_COMMAND_NAMES.has(cmd.name) ? 2 : 1) : bestScore;
|
|
419
|
+
results.push({ command: cmd, score });
|
|
378
420
|
}
|
|
379
421
|
}
|
|
380
422
|
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
// node while it runs), status (counts, skips, degradation state, last
|
|
15
15
|
// build), search <query> (explicit retrieval, results labeled
|
|
16
16
|
// 'lexical'|'semantic' honestly — never implied as more precise than they
|
|
17
|
-
// are).
|
|
18
|
-
//
|
|
17
|
+
// are). Stage B (now landed) adds passive auto-injection into coding turns and
|
|
18
|
+
// tool-site incremental reindex; `status` surfaces both honestly (the
|
|
19
|
+
// auto-injection flag+setting gate, and the last reindex activity).
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
|
|
21
|
-
import type { CodeIndexStats, CodeContextResult } from '@pellux/goodvibes-sdk/platform/state';
|
|
22
|
+
import type { CodeIndexStats, CodeContextResult, CodeIndexReindexActivity } from '@pellux/goodvibes-sdk/platform/state';
|
|
22
23
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
23
24
|
import { isCodeIndexAutoStartEnabled, CODE_INDEX_MAX_FILES, CODE_INDEX_MAX_FILE_BYTES, CODE_INDEX_MAX_TOTAL_BYTES } from '../../runtime/code-index-services.ts';
|
|
24
25
|
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
@@ -33,7 +34,40 @@ function formatBytes(bytes: number): string {
|
|
|
33
34
|
return `${bytes}B`;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Honest one-line auto-injection state (Stage B): both the (default-off)
|
|
39
|
+
* `agent-passive-code-injection` feature flag AND storage.codeIndexEnabled must
|
|
40
|
+
* be on for passive code injection to run — state which is off, and why.
|
|
41
|
+
*/
|
|
42
|
+
function renderAutoInjectionState(flagEnabled: boolean, settingEnabled: boolean): string {
|
|
43
|
+
if (flagEnabled && settingEnabled) {
|
|
44
|
+
return ' auto-injection: on (code hits may be injected into coding turns within the shared knowledge budget)';
|
|
45
|
+
}
|
|
46
|
+
const reasons: string[] = [];
|
|
47
|
+
if (!flagEnabled) reasons.push('agent-passive-code-injection flag off (default off) — enable in the settings modal flags section');
|
|
48
|
+
if (!settingEnabled) reasons.push('storage.codeIndexEnabled off — /config to change');
|
|
49
|
+
return ` auto-injection: off (${reasons.join('; ')})`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Honest last-reindex activity line (Stage B tool-site incremental reindex). */
|
|
53
|
+
function renderReindexActivity(activity: CodeIndexReindexActivity | null, now: number): string {
|
|
54
|
+
if (!activity) return ' last reindex: none this session (writes/edits reindex touched files once the index is built)';
|
|
55
|
+
const agoSec = Math.max(0, Math.round((now - activity.at) / 1000));
|
|
56
|
+
const detail = activity.status === 'error'
|
|
57
|
+
? `error: ${activity.error ?? 'unknown'}`
|
|
58
|
+
: activity.status === 'skipped'
|
|
59
|
+
? `skipped (${activity.mode ?? 'empty'})`
|
|
60
|
+
: `indexed (${activity.mode ?? 'symbols'})`;
|
|
61
|
+
return ` last reindex: ${activity.path} — ${detail}, ${agoSec}s ago`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface CodeIndexStatusExtras {
|
|
65
|
+
readonly flagEnabled: boolean;
|
|
66
|
+
readonly reindexActivity: CodeIndexReindexActivity | null;
|
|
67
|
+
readonly now?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function renderCodeIndexStatus(stats: CodeIndexStats, configManager: Pick<ConfigManager, 'get'>, extras: CodeIndexStatusExtras): string {
|
|
37
71
|
const lines: string[] = [];
|
|
38
72
|
lines.push(`Code index — backend: ${stats.backend}, available: ${stats.available ? 'yes' : 'no'}`);
|
|
39
73
|
lines.push(` path: ${stats.path}`);
|
|
@@ -48,6 +82,9 @@ function renderCodeIndexStatus(stats: CodeIndexStats, configManager: Pick<Config
|
|
|
48
82
|
lines.push(
|
|
49
83
|
` auto-build on startup: ${autoStart ? 'on' : 'off'} (storage.codeIndexEnabled, default off — /config to change)`,
|
|
50
84
|
);
|
|
85
|
+
// Stage B surfacing: passive code auto-injection state (flag + setting) and the last tool-site reindex.
|
|
86
|
+
lines.push(renderAutoInjectionState(extras.flagEnabled, autoStart));
|
|
87
|
+
lines.push(renderReindexActivity(extras.reindexActivity, extras.now ?? Date.now()));
|
|
51
88
|
lines.push(
|
|
52
89
|
` bounds: max ${CODE_INDEX_MAX_FILES} files (maxFiles), ${formatBytes(CODE_INDEX_MAX_FILE_BYTES)} per file (maxFileBytes),`
|
|
53
90
|
+ ` ${formatBytes(CODE_INDEX_MAX_TOTAL_BYTES)} total per build (maxTotalBytes)`,
|
|
@@ -133,7 +170,7 @@ const USAGE = 'Usage:\n'
|
|
|
133
170
|
export function registerCodebaseRuntimeCommands(registry: CommandRegistry): void {
|
|
134
171
|
registry.register({
|
|
135
172
|
name: 'codebase',
|
|
136
|
-
description: 'Repo source-tree code index — build, inspect, and search
|
|
173
|
+
description: 'Repo source-tree code index — build, inspect, and search',
|
|
137
174
|
usage: 'build | status | search <query...> [--limit n]',
|
|
138
175
|
argsHint: 'build | status | search <query>',
|
|
139
176
|
handler(args: string[], ctx: CommandContext) {
|
|
@@ -146,7 +183,10 @@ export function registerCodebaseRuntimeCommands(registry: CommandRegistry): void
|
|
|
146
183
|
const sub = args[0];
|
|
147
184
|
|
|
148
185
|
if (!sub || sub === 'status') {
|
|
149
|
-
ctx.print(renderCodeIndexStatus(store.stats(), ctx.platform.configManager
|
|
186
|
+
ctx.print(renderCodeIndexStatus(store.stats(), ctx.platform.configManager, {
|
|
187
|
+
flagEnabled: ctx.session.isPassiveCodeInjectionFlagEnabled?.() ?? false,
|
|
188
|
+
reindexActivity: ctx.session.codeIndexReindexScheduler?.lastActivity() ?? null,
|
|
189
|
+
}));
|
|
150
190
|
return;
|
|
151
191
|
}
|
|
152
192
|
|
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
import { setActiveThemeMode, type ThemeMode } from '../../renderer/theme.ts';
|
|
2
4
|
|
|
3
5
|
export function registerConfigCommand(registry: CommandRegistry): void {
|
|
4
6
|
registry.register({
|
|
5
7
|
name: 'config',
|
|
6
8
|
aliases: ['cfg'],
|
|
7
|
-
description: 'Open the fullscreen configuration workspace',
|
|
8
|
-
usage: '[category|key]',
|
|
9
|
-
argsHint: '[category|key]',
|
|
9
|
+
description: 'Open the fullscreen configuration workspace, or set a key directly',
|
|
10
|
+
usage: '[category|key] | set <key> <value>',
|
|
11
|
+
argsHint: '[category|key] | set <key> <value>',
|
|
10
12
|
handler(args, ctx) {
|
|
13
|
+
// Batch replay D2: `/config set <key> <value>` used to fall through to
|
|
14
|
+
// the workspace with the assignment silently ignored — the dishonest-
|
|
15
|
+
// fallthrough class. `set` is now a real verb.
|
|
16
|
+
if (args[0] === 'set') {
|
|
17
|
+
const key = args[1];
|
|
18
|
+
const value = args.slice(2).join(' ');
|
|
19
|
+
if (!key || value === '') {
|
|
20
|
+
ctx.print('Usage: /config set <key> <value> — e.g. /config set display.themeMode light');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const before = ctx.platform.configManager.get(key as ConfigKey);
|
|
25
|
+
ctx.platform.configManager.setDynamic(key as ConfigKey, coerceConfigValue(value));
|
|
26
|
+
const after = ctx.platform.configManager.get(key as ConfigKey);
|
|
27
|
+
ctx.print(`Set ${key}: ${JSON.stringify(before)} → ${JSON.stringify(after)}`);
|
|
28
|
+
// Forced theme modes apply immediately (matches the settings-modal
|
|
29
|
+
// path); 'auto' re-probes on the next launch (stated honestly).
|
|
30
|
+
if (key === 'display.themeMode') {
|
|
31
|
+
if (after === 'dark' || after === 'light') {
|
|
32
|
+
setActiveThemeMode(after as ThemeMode);
|
|
33
|
+
ctx.print('Theme applied. Note: transcript, modal, and header/footer/thinking chrome all flip; only the background colour follows your terminal.');
|
|
34
|
+
} else {
|
|
35
|
+
ctx.print('Theme mode "auto" probes the terminal background on the next launch.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
ctx.print(`Could not set ${key}: ${e instanceof Error ? e.message : String(e)}`);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
11
43
|
if (ctx.openSettingsModal) {
|
|
12
44
|
ctx.openSettingsModal(args[0]);
|
|
13
45
|
return;
|
|
@@ -16,3 +48,11 @@ export function registerConfigCommand(registry: CommandRegistry): void {
|
|
|
16
48
|
},
|
|
17
49
|
});
|
|
18
50
|
}
|
|
51
|
+
|
|
52
|
+
/** Coerce CLI text to the JSON-ish scalar the config schema expects. */
|
|
53
|
+
function coerceConfigValue(raw: string): unknown {
|
|
54
|
+
if (raw === 'true') return true;
|
|
55
|
+
if (raw === 'false') return false;
|
|
56
|
+
if (raw !== '' && Number.isFinite(Number(raw))) return Number(raw);
|
|
57
|
+
return raw;
|
|
58
|
+
}
|
|
@@ -39,7 +39,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
39
39
|
name: 'health',
|
|
40
40
|
aliases: ['doctor'],
|
|
41
41
|
description: 'Health workspace for startup posture, service readiness, sandbox posture, and provider health',
|
|
42
|
-
usage: '[
|
|
42
|
+
usage: '[report|review|open|setup|services|sandbox|provider|accounts|auth|settings|intelligence|remote|mcp|continuity|worktrees|maintenance|term|repair [domain]] — bare and report stay a cross-domain transcript report (see also /health provider for the providers modal)',
|
|
43
43
|
async handler(args, ctx) {
|
|
44
44
|
const sub = (args[0] ?? 'review').toLowerCase();
|
|
45
45
|
const readModels = requireReadModels(ctx);
|
|
@@ -424,6 +424,14 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
424
424
|
|
|
425
425
|
ctx.print([
|
|
426
426
|
'Health Review',
|
|
427
|
+
// DEBT-5 item 3: /health stays a cross-domain transcript report (it
|
|
428
|
+
// genuinely spans settings/accounts/auth/sandbox/etc — no single
|
|
429
|
+
// modal owns this data), but the providers domain DOES have one, so
|
|
430
|
+
// point at it honestly. "/provider" (no domain) is a DIFFERENT
|
|
431
|
+
// command (switch/manage custom providers) — the real front door to
|
|
432
|
+
// the providers modal is /health provider (see sub === 'provider'
|
|
433
|
+
// above), so that is what this hint names.
|
|
434
|
+
' see also: the providers modal — /health provider',
|
|
427
435
|
` session: ${snapshot.sessionId}`,
|
|
428
436
|
` setup issues: ${snapshot.issues.length}`,
|
|
429
437
|
` service issues: ${snapshot.serviceIssues.length}`,
|
|
@@ -22,16 +22,77 @@ import { VALID_CLASSES, VALID_REVIEW_STATES, VALID_SCOPES } from './recall-share
|
|
|
22
22
|
|
|
23
23
|
// ── Top-level command ─────────────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* DEBT-5 item 3 divergence note (historical): the work order that shipped
|
|
27
|
+
* this command named its front door "/memory", but at the time `/memory` was
|
|
28
|
+
* already a distinct, unrelated command (session-pinned sticky notes,
|
|
29
|
+
* src/input/commands/session-content.ts) with no modal surface — so that
|
|
30
|
+
* work order deliberately did NOT touch /memory and used /recall instead.
|
|
31
|
+
*
|
|
32
|
+
* W6-C3 update (Wave 6 core-verb pass, MEMORY fragmentation — worst-class
|
|
33
|
+
* collision #2): the agent's own `/memory` command was a plain alias for its
|
|
34
|
+
* `/recall`-equivalent the whole time, meaning "/memory" meant two unrelated
|
|
35
|
+
* things depending which surface you were on. The session-notes command was
|
|
36
|
+
* renamed to `/note` (session-content.ts) to free the word, and `/memory` is
|
|
37
|
+
* now registered here as a real alias of `/recall` — the word means the same
|
|
38
|
+
* durable Project Memory Substrate on both surfaces. The modal that exists
|
|
39
|
+
* for this data — the Project Memory Substrate — is still `memory-modal.ts`,
|
|
40
|
+
* owned by THIS command, confirmed by the panel-id redirect
|
|
41
|
+
* `registerModalRedirect('memory', 'memory-modal')` in builtin-modals.ts.
|
|
42
|
+
*/
|
|
43
|
+
function printRecallUsage(context: CommandContext): void {
|
|
44
|
+
const usage = [
|
|
45
|
+
'Usage: /recall <subcommand>',
|
|
46
|
+
' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
|
|
47
|
+
` classes: ${VALID_CLASSES.join(', ')}`,
|
|
48
|
+
' capture incident <id|latest> — Capture a forensics incident as durable memory',
|
|
49
|
+
' capture policy — Capture the latest policy preflight review as durable memory',
|
|
50
|
+
' capture mcp <server> — Capture MCP trust/quarantine posture as durable memory',
|
|
51
|
+
' capture plugin <name> — Capture plugin trust/quarantine posture as durable memory',
|
|
52
|
+
' search [query] [--semantic] [--cls <class>] [--scope <scope>] [--limit <n>] — Full-text or sqlite-vec semantic search',
|
|
53
|
+
' vector [status|doctor|rebuild] — Inspect or rebuild the sqlite-vec memory index',
|
|
54
|
+
' get <id> — Show record with provenance + links',
|
|
55
|
+
' link <fromId> <toId> <relation> — Create a directed relation between records',
|
|
56
|
+
' queue [limit] — Show the operator review queue',
|
|
57
|
+
' review <id> <state> [--confidence <n>] [--by <name>] [--reason <text>]',
|
|
58
|
+
' stale <id> [reason...] — Mark a record stale with an operator reason',
|
|
59
|
+
' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
|
|
60
|
+
' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
|
|
61
|
+
' injections [agentId] — Show per-turn passive knowledge injection records; no id shows the main session, an id shows that spawned agent',
|
|
62
|
+
' promote <id> <scope> — Promote a memory record into session|project|team scope',
|
|
63
|
+
' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
|
|
64
|
+
' import <path> — Import a durable knowledge bundle',
|
|
65
|
+
' handoff-export <path> [--scope <scope>] — Export a reviewable handoff bundle for team/shared use',
|
|
66
|
+
' handoff-inspect <path> — Inspect a handoff bundle before import',
|
|
67
|
+
' handoff-import <path> — Import a handoff bundle into durable memory',
|
|
68
|
+
' list [class] [--scope <scope>] — List all records grouped by class',
|
|
69
|
+
' remove <id> — Delete a record',
|
|
70
|
+
].join('\n');
|
|
71
|
+
context.print(usage);
|
|
72
|
+
}
|
|
73
|
+
|
|
25
74
|
export const recallCommand: SlashCommand = {
|
|
26
75
|
name: 'recall',
|
|
27
|
-
aliases: ['rc'],
|
|
28
|
-
description: '
|
|
29
|
-
usage: '<subcommand> [args]',
|
|
30
|
-
argsHint: 'add|search|link|get|list|remove',
|
|
76
|
+
aliases: ['rc', 'memory', 'mem'],
|
|
77
|
+
description: 'Bare opens the Memory modal; project memory subcommands add decisions, constraints, incidents, and patterns with provenance',
|
|
78
|
+
usage: '[<subcommand> [args]] — bare opens the modal; report prints the subcommand usage text',
|
|
79
|
+
argsHint: 'add|search|link|get|list|remove|report',
|
|
31
80
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
32
81
|
const [sub, ...rest] = args;
|
|
33
82
|
|
|
83
|
+
// DEBT-5 item 3: bare `/recall` opens the memory-modal surface — the old
|
|
84
|
+
// bare/unknown-subcommand usage block moved to an explicit `report`
|
|
85
|
+
// subcommand (scriptability preserved: /recall report).
|
|
86
|
+
if (sub === undefined) {
|
|
87
|
+
context.openModal?.('memory-modal');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
34
91
|
switch (sub) {
|
|
92
|
+
case 'report':
|
|
93
|
+
printRecallUsage(context);
|
|
94
|
+
break;
|
|
95
|
+
|
|
35
96
|
case 'add':
|
|
36
97
|
await handleRecallAdd(rest, context);
|
|
37
98
|
break;
|
|
@@ -120,37 +181,9 @@ export const recallCommand: SlashCommand = {
|
|
|
120
181
|
await handleRecallHandoffImport(rest, context);
|
|
121
182
|
break;
|
|
122
183
|
|
|
123
|
-
default:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
' add <class> <summary> [--scope <session|project|team>] [--detail <text>] [--tags <t,t>] [--session <id>] [--task <id>] [--file <path>]',
|
|
127
|
-
` classes: ${VALID_CLASSES.join(', ')}`,
|
|
128
|
-
' capture incident <id|latest> — Capture a forensics incident as durable memory',
|
|
129
|
-
' capture policy — Capture the latest policy preflight review as durable memory',
|
|
130
|
-
' capture mcp <server> — Capture MCP trust/quarantine posture as durable memory',
|
|
131
|
-
' capture plugin <name> — Capture plugin trust/quarantine posture as durable memory',
|
|
132
|
-
' search [query] [--semantic] [--cls <class>] [--scope <scope>] [--limit <n>] — Full-text or sqlite-vec semantic search',
|
|
133
|
-
' vector [status|doctor|rebuild] — Inspect or rebuild the sqlite-vec memory index',
|
|
134
|
-
' get <id> — Show record with provenance + links',
|
|
135
|
-
' link <fromId> <toId> <relation> — Create a directed relation between records',
|
|
136
|
-
' queue [limit] — Show the operator review queue',
|
|
137
|
-
' review <id> <state> [--confidence <n>] [--by <name>] [--reason <text>]',
|
|
138
|
-
' stale <id> [reason...] — Mark a record stale with an operator reason',
|
|
139
|
-
' contradict <id> [reason...] — Mark a record contradicted with an operator reason',
|
|
140
|
-
' explain <task...> [--scope <path> ...] — Show the knowledge records that would be injected for a task',
|
|
141
|
-
' injections [agentId] — Show per-turn passive knowledge injection records (Wave-5); no id shows the main session, an id shows that spawned agent',
|
|
142
|
-
' promote <id> <scope> — Promote a memory record into session|project|team scope',
|
|
143
|
-
' export <path> [--scope <scope>] [--cls <class>] — Export a durable knowledge bundle',
|
|
144
|
-
' import <path> — Import a durable knowledge bundle',
|
|
145
|
-
' handoff-export <path> [--scope <scope>] — Export a reviewable handoff bundle for team/shared use',
|
|
146
|
-
' handoff-inspect <path> — Inspect a handoff bundle before import',
|
|
147
|
-
' handoff-import <path> — Import a handoff bundle into durable memory',
|
|
148
|
-
' list [class] [--scope <scope>] — List all records grouped by class',
|
|
149
|
-
' remove <id> — Delete a record',
|
|
150
|
-
].join('\n');
|
|
151
|
-
context.print(usage);
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
184
|
+
default:
|
|
185
|
+
printRecallUsage(context);
|
|
186
|
+
break;
|
|
154
187
|
}
|
|
155
188
|
},
|
|
156
189
|
};
|
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import type { PanelDeepLinkTarget } from '../../panels/panel-manager.ts';
|
|
2
3
|
import { requirePanelManager } from './runtime-services.ts';
|
|
3
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Parse `--target <id>[:<kind>]` out of the args following `<panel-id>
|
|
8
|
+
* [top|bottom]` (DEBT-5 item 4). Splits the args array in place (removing the
|
|
9
|
+
* flag + its value) so positional `pane` parsing downstream is unaffected by
|
|
10
|
+
* where the flag appears. Only `open fleet` currently has a deep-link
|
|
11
|
+
* consumer (FleetPanel.receiveDeepLink) — other panel ids just ignore an
|
|
12
|
+
* unused target via PanelManager's optional-chaining delivery.
|
|
13
|
+
*/
|
|
14
|
+
function extractTargetFlag(rest: string[]): PanelDeepLinkTarget | undefined {
|
|
15
|
+
const flagIdx = rest.indexOf('--target');
|
|
16
|
+
if (flagIdx < 0) return undefined;
|
|
17
|
+
const raw = rest[flagIdx + 1];
|
|
18
|
+
rest.splice(flagIdx, 2);
|
|
19
|
+
if (!raw) return undefined;
|
|
20
|
+
const sep = raw.indexOf(':');
|
|
21
|
+
return sep >= 0 ? { id: raw.slice(0, sep), kind: raw.slice(sep + 1) } : { id: raw };
|
|
22
|
+
}
|
|
23
|
+
|
|
5
24
|
export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
6
25
|
registry.register({
|
|
7
26
|
name: 'panel',
|
|
8
27
|
aliases: ['panels'],
|
|
9
28
|
description: 'Open, place, resize, or list panels. Usage: /panel [open <id> [top|bottom]|close <id>|list|toggle|move|focus|split|width|height]',
|
|
10
|
-
usage: '[open <id> [top|bottom]|close <id>|list|toggle|move <top|bottom|other> [id]|focus <top|bottom|toggle>|split [show|hide|toggle]|width <left|right|reset>|height <up|down|reset>]',
|
|
29
|
+
usage: '[open <id> [top|bottom] [--target <id>[:<kind>]]|close <id>|list|toggle|move <top|bottom|other> [id]|focus <top|bottom|toggle>|split [show|hide|toggle]|width <left|right|reset>|height <up|down|reset>]',
|
|
11
30
|
argsHint: '<open|close|list|toggle|move|focus|split|width|height> [id]',
|
|
12
31
|
handler(args, ctx) {
|
|
13
32
|
const pm = requirePanelManager(ctx);
|
|
@@ -38,10 +57,12 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
38
57
|
ctx.print(lines.length > 0 ? lines.join('\n') : 'No panels registered.');
|
|
39
58
|
} else if (sub === 'open') {
|
|
40
59
|
const id = args[1];
|
|
41
|
-
const
|
|
60
|
+
const rest = args.slice(2);
|
|
61
|
+
const target = extractTargetFlag(rest);
|
|
62
|
+
const pane = rest[0]?.toLowerCase();
|
|
42
63
|
if (!id) { ctx.print('Usage: /panel open <panel-id>'); return; }
|
|
43
64
|
if (pane && pane !== 'top' && pane !== 'bottom') {
|
|
44
|
-
ctx.print('Usage: /panel open <panel-id> [top|bottom]');
|
|
65
|
+
ctx.print('Usage: /panel open <panel-id> [top|bottom] [--target <id>[:<kind>]]');
|
|
45
66
|
return;
|
|
46
67
|
}
|
|
47
68
|
try {
|
|
@@ -50,11 +71,13 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
50
71
|
// no panel lands in the workspace. Report that honestly rather than
|
|
51
72
|
// claiming "Panel opened: <id>".
|
|
52
73
|
const redirectTarget = pm.getModalRedirect(id);
|
|
53
|
-
|
|
74
|
+
// UX-C focus rule 1a: the command path leaves focus in the composer
|
|
75
|
+
// ("mid-command-flow") — showPanel does not grab panel focus here.
|
|
76
|
+
// DEBT-5: forward the deep-link target so the panel lands on the row.
|
|
77
|
+
if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined, target);
|
|
54
78
|
else {
|
|
55
|
-
pm.open(id, pane as 'top' | 'bottom' | undefined);
|
|
79
|
+
pm.open(id, pane as 'top' | 'bottom' | undefined, target);
|
|
56
80
|
pm.show();
|
|
57
|
-
ctx.focusPanels?.();
|
|
58
81
|
ctx.renderRequest();
|
|
59
82
|
}
|
|
60
83
|
if (redirectTarget) {
|
|
@@ -146,11 +169,12 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
146
169
|
} else {
|
|
147
170
|
const id = args[0]!;
|
|
148
171
|
try {
|
|
172
|
+
// UX-C: bare `/panel <id>` is the same command path as `/panel open
|
|
173
|
+
// <id>` — composer stays focused.
|
|
149
174
|
if (ctx.showPanel) ctx.showPanel(id);
|
|
150
175
|
else {
|
|
151
176
|
pm.open(id);
|
|
152
177
|
pm.show();
|
|
153
|
-
ctx.focusPanels?.();
|
|
154
178
|
ctx.renderRequest();
|
|
155
179
|
}
|
|
156
180
|
} catch {
|