@pellux/goodvibes-tui 0.21.0 → 0.22.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 +23 -0
- package/README.md +1 -1
- package/package.json +2 -1
- package/src/cli/completions/generate.ts +4 -8
- package/src/cli/entrypoint.ts +6 -0
- package/src/cli/parser.ts +17 -0
- package/src/cli/types.ts +2 -0
- package/src/config/goodvibes-home-audit.ts +2 -0
- package/src/core/context-auto-compact.ts +77 -0
- package/src/core/turn-event-wiring.ts +124 -0
- package/src/daemon/cli.ts +5 -0
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/control-room-runtime.ts +5 -5
- package/src/input/commands/provider.ts +57 -3
- package/src/input/commands/session-workflow.ts +8 -16
- package/src/input/commands/session.ts +70 -20
- package/src/input/commands.ts +0 -2
- package/src/input/handler-modal-routes.ts +37 -0
- package/src/input/handler-modal-token-routes.ts +19 -5
- package/src/input/handler-onboarding.ts +18 -0
- package/src/input/handler.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +10 -0
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +14 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +6 -0
- package/src/input/onboarding/onboarding-wizard-validation.ts +77 -0
- package/src/input/settings-modal-behavior.ts +5 -0
- package/src/input/settings-modal-data.ts +77 -3
- package/src/input/settings-modal-mutations.ts +3 -0
- package/src/input/settings-modal-reset.ts +154 -0
- package/src/input/settings-modal.ts +55 -13
- package/src/main.ts +36 -28
- package/src/panels/agent-inspector-panel.ts +120 -18
- package/src/panels/agent-inspector-shared.ts +29 -0
- package/src/panels/builtin/development.ts +1 -0
- package/src/panels/builtin/knowledge.ts +14 -13
- package/src/panels/builtin/operations.ts +22 -1
- package/src/panels/builtin/shared.ts +7 -0
- package/src/panels/cockpit-panel.ts +123 -3
- package/src/panels/cockpit-read-model.ts +232 -0
- package/src/panels/index.ts +1 -1
- package/src/panels/knowledge-graph-panel.ts +84 -0
- package/src/panels/memory-panel.ts +370 -40
- package/src/panels/session-maintenance.ts +66 -15
- package/src/renderer/agent-detail-modal.ts +107 -3
- package/src/renderer/context-status-hint.ts +54 -0
- package/src/renderer/settings-modal.ts +14 -3
- package/src/renderer/shell-surface.ts +10 -0
- package/src/runtime/bootstrap-command-parts.ts +4 -0
- package/src/runtime/bootstrap-core.ts +24 -0
- package/src/runtime/bootstrap-shell.ts +11 -0
- package/src/runtime/bootstrap.ts +7 -0
- package/src/runtime/services.ts +6 -1
- package/src/version.ts +1 -1
- package/src/panels/knowledge-panel.ts +0 -343
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.22.0] — 2026-06-12
|
|
8
|
+
|
|
9
|
+
Second best-in-class program release: the backlog tail, the providers/failover track opened, and the release pipeline made fully honest. Every change passed independent review at 10/10 before commit.
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
- Added a Cockpit control room: live agent roster (status, stalled flag, real cost/tokens), inspect and confirm-gated cancel action keys on the shared orphan-free cancel path.
|
|
13
|
+
- Added /session as the single front-door for session work: lifecycle subcommands (list, rename, resume, fork, save, info, export, search, delete, events, groups, hotspots) and orchestration (link-task with cycle detection, handoff, graph, cancel with task/subtree/session scopes); alias /sess.
|
|
14
|
+
- Added a merged memory panel (records + review queue via Tab) with the knowledge-graph view on its own panel; /project-memory routes to records; session scratch surface renamed to notes.
|
|
15
|
+
- Added settings reset-category and reset-all (Shift+R / Ctrl+Shift+R, confirm-gated, runtime-synced), schema-derived unknown-key warnings at load, restart hints from a typed key table, and width-honest footers proven at 80 and 120 columns.
|
|
16
|
+
- Added wizard required-field gating: apply blocks with per-field messages and a focus jump to the first offender.
|
|
17
|
+
- Added the provider optimizer enable switch: /provider optimizer on|off drives the live instance and persists; /provider route auto says honestly when the optimizer is off.
|
|
18
|
+
- Added wizard security hardening: Cloudflare tunnel apply sets trustProxy with the residual risk named until SDK header validation ships; TLS plaintext hard-warn in the wizard and a startup banner; CORS guidance.
|
|
19
|
+
- Added context truth: auto-compact runs only when behavior.autoCompact is enabled (threshold from config, percent converted correctly), with a passive suggest-compact status hint and honest transcript notices.
|
|
20
|
+
- Added tts.speed to settings, bridged end-to-end today (modal to synthesis call); explicit tts.* defaults with truthful modified markers.
|
|
21
|
+
|
|
22
|
+
### Fixes
|
|
23
|
+
- Fixed CLI flag truth end-to-end: a drift-proof parity test extracts flags from parser source and asserts equality across help, completions, and docs; ghost completion flags removed; --output-format deprecated with a stderr warning; session lifecycle flags are mutually exclusive with a clear error.
|
|
24
|
+
- Fixed onboarding marker timing residue and wizard copy; sessions, recovery, and WRFC chains read through version-gated, quarantining loaders everywhere.
|
|
25
|
+
|
|
26
|
+
### Internal
|
|
27
|
+
- Performance gate is real: fail-closed in CI without a baseline, headless startup and frame benches sharing one methodology and one CI-safe budget source (p95 16ms, p99 110ms with runner headroom), committed ratchet baseline.
|
|
28
|
+
- Per-agent stall watchdog constants shared; agent cost/token surfaces fabricated-number-free; concurrency-safe test temp dirs; eval gate fail-closed with a committed baseline; 540 test files green.
|
|
29
|
+
|
|
7
30
|
## [0.21.0] — 2026-06-12
|
|
8
31
|
|
|
9
32
|
First release of the best-in-class program: a full UX-first review of the codebase followed by WRFC-gated remediation (every change passed independent review at 10/10 before commit).
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"build:prod": "bun run scripts/build.ts",
|
|
58
58
|
"build:all": "bun run scripts/build.ts --all",
|
|
59
59
|
"perf:check": "bun run scripts/perf-check.ts",
|
|
60
|
+
"perf:baseline": "GOODVIBES_PERF_SAVE_BASELINE=1 bun run scripts/perf-check.ts",
|
|
60
61
|
"architecture:check": "bun run scripts/check-architecture.ts",
|
|
61
62
|
"audit:home": "bun run scripts/audit-goodvibes-home.ts",
|
|
62
63
|
"foundation:artifacts": "bun run scripts/export-foundation-artifacts.ts",
|
|
@@ -137,12 +137,8 @@ export const GLOBAL_FLAGS: readonly CompletionFlag[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{ name: '--continue', takesValue: false, description: 'Continue the latest session' },
|
|
139
139
|
{ name: '--fork', takesValue: false, description: 'Fork session when supported' },
|
|
140
|
-
{ name: '--
|
|
141
|
-
{
|
|
142
|
-
name: '--accept-raw-output-risk',
|
|
143
|
-
takesValue: false,
|
|
144
|
-
description: 'Acknowledge raw output risk',
|
|
145
|
-
},
|
|
140
|
+
{ name: '--yes', short: '-y', takesValue: false, description: 'Auto-confirm prompts (non-interactive)' },
|
|
141
|
+
{ name: '--non-interactive', takesValue: false, description: 'Disable all interactive prompts (implies --yes)' },
|
|
146
142
|
] as const;
|
|
147
143
|
|
|
148
144
|
// ---------------------------------------------------------------------------
|
|
@@ -512,8 +508,8 @@ export function generateZsh(surface: CompletionSurface): string {
|
|
|
512
508
|
lines.push(` '(-s --session)'{-s,--session}'[Use a specific session]:id:' \\`);
|
|
513
509
|
lines.push(` '--continue[Continue the latest session]' \\`);
|
|
514
510
|
lines.push(` '--fork[Fork session when supported]' \\`);
|
|
515
|
-
lines.push(` '--
|
|
516
|
-
lines.push(` '--
|
|
511
|
+
lines.push(` '(-y --yes)'{-y,--yes}'[Auto-confirm prompts]' \\`);
|
|
512
|
+
lines.push(` '--non-interactive[Disable all interactive prompts]' \\`);
|
|
517
513
|
lines.push(` '1:command:->cmd' \\`);
|
|
518
514
|
lines.push(` '*:args:->args';`);
|
|
519
515
|
lines.push('');
|
package/src/cli/entrypoint.ts
CHANGED
|
@@ -61,6 +61,12 @@ export async function prepareShellCliRuntime(
|
|
|
61
61
|
process.exit(2);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
if (cli.warnings.length > 0) {
|
|
65
|
+
for (const warning of cli.warnings) {
|
|
66
|
+
console.warn(`[goodvibes] warning: ${warning}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
64
70
|
if (cli.flags.help || cli.command === 'help') {
|
|
65
71
|
const helpTopic = cli.command === 'help'
|
|
66
72
|
? cli.commandArgs[0]
|
package/src/cli/parser.ts
CHANGED
|
@@ -174,6 +174,7 @@ export function parseGoodVibesCli(
|
|
|
174
174
|
const commandArgs: string[] = [];
|
|
175
175
|
const positionals: string[] = [];
|
|
176
176
|
const errors: string[] = [];
|
|
177
|
+
const warnings: string[] = [];
|
|
177
178
|
let sawCommand = false;
|
|
178
179
|
let passthrough = false;
|
|
179
180
|
|
|
@@ -292,6 +293,9 @@ export function parseGoodVibesCli(
|
|
|
292
293
|
const consumed = getValue(argv, index, inlineValue, name, errors);
|
|
293
294
|
index = consumed.nextIndex;
|
|
294
295
|
flags = withFlag(flags, 'outputFormat', normalizeOutputFormat(consumed.value, name, errors));
|
|
296
|
+
if (name === '--output-format') {
|
|
297
|
+
warnings.push('--output-format is deprecated; use --output (or -o) instead.');
|
|
298
|
+
}
|
|
295
299
|
continue;
|
|
296
300
|
}
|
|
297
301
|
if (name === '--config' || name === '-c') {
|
|
@@ -353,6 +357,18 @@ export function parseGoodVibesCli(
|
|
|
353
357
|
errors.push(`Unknown command: ${rawCommand}`);
|
|
354
358
|
}
|
|
355
359
|
|
|
360
|
+
// Session lifecycle conflict detection — only one of --continue / --resume / --fork may be used.
|
|
361
|
+
const sessionLifecycleFlags = [
|
|
362
|
+
flags.continueLast ? '--continue' : undefined,
|
|
363
|
+
flags.resume !== undefined ? '--resume' : undefined,
|
|
364
|
+
flags.fork !== undefined ? '--fork' : undefined,
|
|
365
|
+
].filter((f): f is string => f !== undefined);
|
|
366
|
+
if (sessionLifecycleFlags.length > 1) {
|
|
367
|
+
errors.push(
|
|
368
|
+
`Conflicting session lifecycle flags: ${sessionLifecycleFlags.join(' and ')}. Use only one of --continue, --resume, or --fork.`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
356
372
|
return {
|
|
357
373
|
binary,
|
|
358
374
|
command,
|
|
@@ -361,5 +377,6 @@ export function parseGoodVibesCli(
|
|
|
361
377
|
positionals,
|
|
362
378
|
flags,
|
|
363
379
|
errors,
|
|
380
|
+
warnings,
|
|
364
381
|
};
|
|
365
382
|
}
|
package/src/cli/types.ts
CHANGED
|
@@ -68,6 +68,8 @@ export interface GoodVibesCliParseResult {
|
|
|
68
68
|
readonly positionals: readonly string[];
|
|
69
69
|
readonly flags: GoodVibesCliFlags;
|
|
70
70
|
readonly errors: readonly string[];
|
|
71
|
+
/** Deprecation and soft-warning messages (non-fatal). Callers should surface these to users. */
|
|
72
|
+
readonly warnings: readonly string[];
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export interface CliCommandRuntime {
|
|
@@ -117,6 +117,8 @@ const KNOWN_DYNAMIC_KEYS = [
|
|
|
117
117
|
/^featureFlags(?:\.|$)/,
|
|
118
118
|
/^notifications\.webhookUrls$/,
|
|
119
119
|
/^wrfc\.gates$/,
|
|
120
|
+
// TUI-bridged setting awaiting SDK schema registration (handoff Item 5b)
|
|
121
|
+
/^tts\.speed$/,
|
|
120
122
|
];
|
|
121
123
|
|
|
122
124
|
export const GOODVIBES_ALLOWED_WRITE_ROOTS = ['tui/', 'daemon/'] as const;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-compaction helper — TASK-058.
|
|
3
|
+
*
|
|
4
|
+
* Evaluates whether auto-compact should run after a turn completes and, if so,
|
|
5
|
+
* triggers compaction and posts an honest transcript notice.
|
|
6
|
+
*
|
|
7
|
+
* behavior.autoCompactThreshold: SDK schema range [10, 100], default 80.
|
|
8
|
+
* Auto-compact is active whenever the threshold is in its valid range (>0).
|
|
9
|
+
* The display/suggestion path (hint + meter) is always active regardless.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
13
|
+
import type { ConversationManager } from './conversation';
|
|
14
|
+
import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
|
|
15
|
+
import type { SystemMessageRouter } from './system-message-router';
|
|
16
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
17
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
18
|
+
|
|
19
|
+
export interface AutoCompactDeps {
|
|
20
|
+
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
21
|
+
readonly conversation: ConversationManager;
|
|
22
|
+
readonly providerRegistry: ProviderRegistry;
|
|
23
|
+
readonly systemMessageRouter: SystemMessageRouter;
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly provider: string;
|
|
26
|
+
readonly lastInputTokens: number;
|
|
27
|
+
readonly contextWindow: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run after each TURN_COMPLETED event.
|
|
32
|
+
*
|
|
33
|
+
* Reads behavior.autoCompactThreshold from config (SDK default: 80, range [10, 100]).
|
|
34
|
+
* When usage is at or above the threshold, compacts the conversation and posts
|
|
35
|
+
* an honest transcript notice so the user understands any summary discontinuity.
|
|
36
|
+
*
|
|
37
|
+
* This function is intentionally non-throwing; failures are logged and
|
|
38
|
+
* surfaced via the system message router.
|
|
39
|
+
*/
|
|
40
|
+
export async function maybeAutoCompact(deps: AutoCompactDeps): Promise<void> {
|
|
41
|
+
// SDK schema default is 80; valid range is [10, 100]. The ?? 0 fallback is a
|
|
42
|
+
// defensive guard for missing/null values only — not a normal operating state.
|
|
43
|
+
const rawThreshold = Number(deps.configManager.get('behavior.autoCompactThreshold') ?? 0);
|
|
44
|
+
const thresholdPct = Number.isFinite(rawThreshold) ? rawThreshold : 0;
|
|
45
|
+
|
|
46
|
+
// Defensive guard: skip only when threshold is missing/non-positive (real config defaults to 80).
|
|
47
|
+
if (thresholdPct <= 0 || deps.contextWindow <= 0) return;
|
|
48
|
+
|
|
49
|
+
const usagePct = Math.min(100, Math.round((Math.max(0, deps.lastInputTokens) / deps.contextWindow) * 100));
|
|
50
|
+
if (usagePct < thresholdPct) return;
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
logger.debug('auto-compact triggered', { usagePct, thresholdPct });
|
|
54
|
+
// Honest transcript notice — the user should always know when compaction
|
|
55
|
+
// runs automatically so they can understand any summary discontinuity.
|
|
56
|
+
deps.systemMessageRouter.routeSystemMessage(
|
|
57
|
+
`[Context] Auto-compacting conversation — usage reached ${usagePct}% (threshold ${thresholdPct}%). A summary will replace older turns to recover headroom.`,
|
|
58
|
+
'high',
|
|
59
|
+
);
|
|
60
|
+
await deps.conversation.compact(
|
|
61
|
+
deps.providerRegistry,
|
|
62
|
+
deps.model,
|
|
63
|
+
'auto',
|
|
64
|
+
deps.provider,
|
|
65
|
+
);
|
|
66
|
+
deps.systemMessageRouter.routeSystemMessage(
|
|
67
|
+
`[Context] Auto-compact complete — older turns summarised. Use /compact to compact again manually.`,
|
|
68
|
+
'low',
|
|
69
|
+
);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error('auto-compact failed', { error: summarizeError(err) });
|
|
72
|
+
deps.systemMessageRouter.routeSystemMessage(
|
|
73
|
+
`[Context] Auto-compact failed: ${summarizeError(err)}. Use /compact to try manually.`,
|
|
74
|
+
'high',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { UiRuntimeEvents } from '@/runtime/index.ts';
|
|
2
|
+
import { buildPersistedSessionContext, persistConversation } from '@/runtime/index.ts';
|
|
3
|
+
import { maybeAutoCompact } from './context-auto-compact.ts';
|
|
4
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
6
|
+
import type { HookDispatcher, HookPhase, HookCategory, HookEventPath } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
|
+
import type { ConversationManager } from './conversation.ts';
|
|
8
|
+
|
|
9
|
+
/** Infer the options param of persistConversation to pick up SessionManager correctly. */
|
|
10
|
+
type PersistOptions = NonNullable<Parameters<typeof persistConversation>[5]>;
|
|
11
|
+
|
|
12
|
+
/** Minimal orchestrator surface required by turn-event wiring. */
|
|
13
|
+
interface TurnOrchestrator {
|
|
14
|
+
readonly lastInputTokens: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Minimal provider registry surface required by turn-event wiring. */
|
|
18
|
+
interface TurnProviderRegistry {
|
|
19
|
+
getCurrentModel(): { readonly contextWindow: number };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Minimal config manager surface required by turn-event wiring. */
|
|
23
|
+
interface TurnConfigManager {
|
|
24
|
+
get(key: string): unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Minimal system message router surface required by turn-event wiring. */
|
|
28
|
+
interface TurnSystemMessageRouter {
|
|
29
|
+
high(message: string): void;
|
|
30
|
+
low(message: string): void;
|
|
31
|
+
routeSystemMessage(message: string, level: string): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WireTurnEventHandlersOptions {
|
|
35
|
+
readonly events: UiRuntimeEvents;
|
|
36
|
+
readonly conversation: ConversationManager;
|
|
37
|
+
readonly runtime: { sessionId: string; model: string; provider: string };
|
|
38
|
+
readonly orchestrator: TurnOrchestrator;
|
|
39
|
+
readonly configManager: TurnConfigManager;
|
|
40
|
+
readonly providerRegistry: TurnProviderRegistry;
|
|
41
|
+
readonly systemMessageRouter: TurnSystemMessageRouter;
|
|
42
|
+
readonly hookDispatcher: HookDispatcher;
|
|
43
|
+
readonly workingDir: string;
|
|
44
|
+
readonly homeDirectory: string;
|
|
45
|
+
readonly sessionManager: PersistOptions['sessionManager'];
|
|
46
|
+
readonly gitStatusProvider: { refresh(): Promise<unknown> };
|
|
47
|
+
readonly lastGitInfoRef: { value: unknown };
|
|
48
|
+
readonly buildSessionContinuityHints: () => Record<string, unknown>;
|
|
49
|
+
readonly render: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WireTurnEventHandlersResult {
|
|
53
|
+
/** Trigger a git status refresh; may be called from external code after tool execution. */
|
|
54
|
+
readonly refreshGit: () => void;
|
|
55
|
+
/** Unsubscribe functions to push into the parent unsubs array. */
|
|
56
|
+
readonly unsubs: ReadonlyArray<() => void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wire TURN_COMPLETED, TOOL_SUCCEEDED, and TOOL_FAILED runtime events.
|
|
61
|
+
*
|
|
62
|
+
* Responsibilities:
|
|
63
|
+
* - Auto-save conversation to persistent store after each LLM turn
|
|
64
|
+
* - Fire the Lifecycle:session:save hook
|
|
65
|
+
* - Trigger auto-compact when context usage exceeds the configured threshold
|
|
66
|
+
* - Refresh git status after turns and tool results
|
|
67
|
+
*
|
|
68
|
+
* Returns refreshGit (callable externally) and unsubs (push into parent unsubs).
|
|
69
|
+
*/
|
|
70
|
+
export function wireTurnEventHandlers(
|
|
71
|
+
options: WireTurnEventHandlersOptions,
|
|
72
|
+
): WireTurnEventHandlersResult {
|
|
73
|
+
const {
|
|
74
|
+
events, conversation, runtime, orchestrator, configManager,
|
|
75
|
+
providerRegistry, systemMessageRouter, hookDispatcher,
|
|
76
|
+
workingDir, homeDirectory, sessionManager, gitStatusProvider,
|
|
77
|
+
lastGitInfoRef, buildSessionContinuityHints, render,
|
|
78
|
+
} = options;
|
|
79
|
+
|
|
80
|
+
const unsubs: Array<() => void> = [];
|
|
81
|
+
|
|
82
|
+
const refreshGit = (): void => {
|
|
83
|
+
gitStatusProvider.refresh().then((info) => { lastGitInfoRef.value = info; render(); }).catch(() => { /* non-fatal */ });
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
unsubs.push(events.turns.on('TURN_COMPLETED', () => {
|
|
87
|
+
// Auto-save after every LLM turn so kills don't lose the session
|
|
88
|
+
try {
|
|
89
|
+
const snapshot = conversation.toJSON() as { messages: Array<import('./conversation.ts').ConversationMessageSnapshot>; timestamp?: number };
|
|
90
|
+
const persisted = buildPersistedSessionContext(snapshot.messages, conversation.getTitleSource(), buildSessionContinuityHints());
|
|
91
|
+
persistConversation(
|
|
92
|
+
runtime.sessionId,
|
|
93
|
+
{ ...snapshot, ...persisted },
|
|
94
|
+
runtime.model,
|
|
95
|
+
runtime.provider,
|
|
96
|
+
conversation.title || '',
|
|
97
|
+
{ workingDirectory: workingDir, homeDirectory, sessionManager },
|
|
98
|
+
);
|
|
99
|
+
hookDispatcher.fire({ path: 'Lifecycle:session:save' as HookEventPath, phase: 'Lifecycle' as HookPhase, category: 'session' as HookCategory, specific: 'save', sessionId: runtime.sessionId, timestamp: Date.now(), payload: { sessionId: runtime.sessionId } }).catch((err: unknown) => logger.debug('hook fire error', { error: summarizeError(err) }));
|
|
100
|
+
} catch (e) { logger.debug('auto-save on turn:complete failed', { error: summarizeError(e) }); }
|
|
101
|
+
// Auto-compact: check context usage and compact if threshold exceeded
|
|
102
|
+
const currentModelForCompact = providerRegistry.getCurrentModel();
|
|
103
|
+
maybeAutoCompact({
|
|
104
|
+
configManager: configManager as Parameters<typeof maybeAutoCompact>[0]['configManager'],
|
|
105
|
+
conversation,
|
|
106
|
+
providerRegistry: providerRegistry as Parameters<typeof maybeAutoCompact>[0]['providerRegistry'],
|
|
107
|
+
systemMessageRouter: systemMessageRouter as Parameters<typeof maybeAutoCompact>[0]['systemMessageRouter'],
|
|
108
|
+
model: runtime.model,
|
|
109
|
+
provider: runtime.provider,
|
|
110
|
+
lastInputTokens: orchestrator.lastInputTokens,
|
|
111
|
+
contextWindow: currentModelForCompact.contextWindow,
|
|
112
|
+
}).catch((err: unknown) => logger.debug('maybeAutoCompact error', { error: summarizeError(err) }));
|
|
113
|
+
refreshGit();
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
unsubs.push(events.tools.on('TOOL_SUCCEEDED', () => {
|
|
117
|
+
refreshGit();
|
|
118
|
+
}));
|
|
119
|
+
unsubs.push(events.tools.on('TOOL_FAILED', () => {
|
|
120
|
+
refreshGit();
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
return { refreshGit, unsubs };
|
|
124
|
+
}
|
package/src/daemon/cli.ts
CHANGED
|
@@ -104,6 +104,11 @@ async function main(): Promise<void> {
|
|
|
104
104
|
console.error(renderGoodVibesDaemonHelp('goodvibes-daemon'));
|
|
105
105
|
process.exit(2);
|
|
106
106
|
}
|
|
107
|
+
if (cli.warnings.length > 0) {
|
|
108
|
+
for (const warning of cli.warnings) {
|
|
109
|
+
console.warn(`[goodvibes-daemon] warning: ${warning}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
107
112
|
if (cli.flags.help || cli.command === 'help') {
|
|
108
113
|
console.log(renderGoodVibesDaemonHelp('goodvibes-daemon'));
|
|
109
114
|
process.exit(0);
|
|
@@ -118,6 +118,7 @@ export interface CommandShellUiOpeners {
|
|
|
118
118
|
openMcpWorkspace?: () => void;
|
|
119
119
|
openSecurityPanel?: () => void;
|
|
120
120
|
openKnowledgePanel?: () => void;
|
|
121
|
+
openMemoryPanel?: () => void;
|
|
121
122
|
openRemotePanel?: () => void;
|
|
122
123
|
openSubscriptionPanel?: () => void;
|
|
123
124
|
/**
|
|
@@ -203,11 +203,11 @@ export function registerControlRoomRuntimeCommands(registry: CommandRegistry): v
|
|
|
203
203
|
handler(args, ctx) {
|
|
204
204
|
const subcommand = (args[0] ?? 'open').toLowerCase();
|
|
205
205
|
if (subcommand === 'open') {
|
|
206
|
-
if (ctx.
|
|
207
|
-
ctx.
|
|
206
|
+
if (ctx.openMemoryPanel) {
|
|
207
|
+
ctx.openMemoryPanel();
|
|
208
208
|
return;
|
|
209
209
|
}
|
|
210
|
-
ctx.print('
|
|
210
|
+
ctx.print('Memory panel is not available in this runtime.');
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
213
|
const memory = getMemoryApi(ctx);
|
|
@@ -245,8 +245,8 @@ export function registerControlRoomRuntimeCommands(registry: CommandRegistry): v
|
|
|
245
245
|
ctx.print(prompt ?? 'No reviewed project knowledge matched that task.');
|
|
246
246
|
return;
|
|
247
247
|
}
|
|
248
|
-
if (ctx.
|
|
249
|
-
ctx.
|
|
248
|
+
if (ctx.openMemoryPanel) {
|
|
249
|
+
ctx.openMemoryPanel();
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
252
252
|
ctx.print(`Unknown project-memory subcommand: ${subcommand}`);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Implements the Provider Optimizer panel commands:
|
|
5
5
|
*
|
|
6
|
+
* /provider optimizer on|off — Enable or disable the provider optimizer
|
|
6
7
|
* /provider route auto|manual — Set optimizer routing mode
|
|
7
8
|
* /provider explain-route — Print current route explanation
|
|
8
9
|
* /provider pin <provider:model> — Pin routing to a specific provider/model
|
|
@@ -10,14 +11,19 @@
|
|
|
10
11
|
*
|
|
11
12
|
* When the optimizer is disabled, commands report its status and
|
|
12
13
|
* explain-route still works (reads current model capabilities).
|
|
14
|
+
* Enabling the optimizer persists the change to config so it survives restart.
|
|
13
15
|
*/
|
|
14
16
|
|
|
15
17
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
18
|
+
import type { ConfigKey } from '../../config/index.ts';
|
|
16
19
|
import type { RouteExplanation } from '@pellux/goodvibes-sdk/platform/providers';
|
|
17
20
|
import type { FallbackTestResult, FallbackTransition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
18
21
|
import type { ProviderApiModelRecord } from '@pellux/goodvibes-sdk/platform/providers';
|
|
19
22
|
import { requireProviderApi } from './runtime-services.ts';
|
|
20
23
|
|
|
24
|
+
const PROVIDER_OPTIMIZER_FLAG = 'provider-optimizer';
|
|
25
|
+
const PROVIDER_OPTIMIZER_CONFIG_KEY = `featureFlags.${PROVIDER_OPTIMIZER_FLAG}` as ConfigKey;
|
|
26
|
+
|
|
21
27
|
// ---------------------------------------------------------------------------
|
|
22
28
|
// Formatting helpers
|
|
23
29
|
// ---------------------------------------------------------------------------
|
|
@@ -65,6 +71,49 @@ function fmtExplanation(expl: RouteExplanation, context: CommandContext): void {
|
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// /provider optimizer on|off
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
function handleOptimizerToggle(
|
|
79
|
+
args: string[],
|
|
80
|
+
context: CommandContext,
|
|
81
|
+
): void {
|
|
82
|
+
const optimizer = requireProviderOptimizer(context);
|
|
83
|
+
if (!optimizer) return;
|
|
84
|
+
const sub = args[0];
|
|
85
|
+
|
|
86
|
+
if (sub !== 'on' && sub !== 'off') {
|
|
87
|
+
context.print('[provider] Usage: /provider optimizer on|off');
|
|
88
|
+
context.print(` Current state: optimizer is ${optimizer.enabled ? 'enabled' : 'disabled'}`);
|
|
89
|
+
context.print(' "on" — activates intelligent failover and auto-routing');
|
|
90
|
+
context.print(' "off" — disables optimizer; provider selection is manual only');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const enable = sub === 'on';
|
|
95
|
+
const wasEnabled = optimizer.enabled;
|
|
96
|
+
optimizer.setEnabled(enable);
|
|
97
|
+
|
|
98
|
+
// Persist to config so the setting survives restart.
|
|
99
|
+
const flagValue = enable ? 'enabled' : 'disabled';
|
|
100
|
+
context.platform.configManager.setDynamic(PROVIDER_OPTIMIZER_CONFIG_KEY, flagValue);
|
|
101
|
+
|
|
102
|
+
if (enable && !wasEnabled) {
|
|
103
|
+
context.print('[provider] Optimizer enabled.');
|
|
104
|
+
context.print(' Intelligent failover is now active: on a request error the optimizer');
|
|
105
|
+
context.print(' will attempt the next viable provider and surface a transcript notice');
|
|
106
|
+
context.print(' naming the from→to transition and reason before retrying.');
|
|
107
|
+
context.print(' Use "/provider route auto" to enable fully automatic routing.');
|
|
108
|
+
} else if (!enable && wasEnabled) {
|
|
109
|
+
context.print('[provider] Optimizer disabled.');
|
|
110
|
+
context.print(' Provider selection returns to manual-only mode. No automatic failover.');
|
|
111
|
+
context.print(' Pinned targets and fallback log are preserved; re-enable to resume.');
|
|
112
|
+
} else {
|
|
113
|
+
context.print(`[provider] Optimizer already ${enable ? 'enabled' : 'disabled'} — no change.`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
68
117
|
// ---------------------------------------------------------------------------
|
|
69
118
|
// /provider route auto|manual
|
|
70
119
|
// ---------------------------------------------------------------------------
|
|
@@ -85,9 +134,9 @@ function handleRoute(
|
|
|
85
134
|
|
|
86
135
|
if (!optimizer.enabled) {
|
|
87
136
|
context.print(
|
|
88
|
-
'[provider] Optimizer is
|
|
137
|
+
'[provider] Optimizer is off — routing mode recorded but failover will not fire until optimizer is enabled.',
|
|
89
138
|
);
|
|
90
|
-
context.print(
|
|
139
|
+
context.print(' Enable with: /provider optimizer on');
|
|
91
140
|
}
|
|
92
141
|
|
|
93
142
|
optimizer.setMode(sub);
|
|
@@ -318,11 +367,15 @@ export const providerCommand: SlashCommand = {
|
|
|
318
367
|
aliases: ['prov-opt'],
|
|
319
368
|
description: 'Manage provider routing optimizer (route, pin, explain, fallback).',
|
|
320
369
|
usage: '<subcommand> [args]',
|
|
321
|
-
argsHint: 'route|explain-route|pin|fallback',
|
|
370
|
+
argsHint: 'optimizer|route|explain-route|pin|fallback',
|
|
322
371
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
323
372
|
const [sub, ...rest] = args;
|
|
324
373
|
|
|
325
374
|
switch (sub) {
|
|
375
|
+
case 'optimizer':
|
|
376
|
+
handleOptimizerToggle(rest, context);
|
|
377
|
+
break;
|
|
378
|
+
|
|
326
379
|
case 'route':
|
|
327
380
|
handleRoute(rest, context);
|
|
328
381
|
break;
|
|
@@ -345,6 +398,7 @@ export const providerCommand: SlashCommand = {
|
|
|
345
398
|
if (!optimizer) return;
|
|
346
399
|
const lines = [
|
|
347
400
|
'Usage: /provider <subcommand>',
|
|
401
|
+
' optimizer on|off — Enable or disable the provider optimizer',
|
|
348
402
|
' route auto|manual — Set optimizer routing mode',
|
|
349
403
|
' explain-route — Show current route explanation',
|
|
350
404
|
' pin <provider:model> — Pin routing to specific provider/model',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
|
|
3
|
-
import type { CommandContext
|
|
3
|
+
import type { CommandContext } from '../command-registry.ts';
|
|
4
4
|
import { type SessionMeta } from '@pellux/goodvibes-sdk/platform/sessions';
|
|
5
5
|
import type { TranscriptEventKind } from '@pellux/goodvibes-sdk/platform/core';
|
|
6
6
|
import type { ConversationTitleSource } from '../../core/conversation';
|
|
@@ -441,21 +441,13 @@ export async function handleSessionWorkflowCommand(args: string[], ctx: CommandC
|
|
|
441
441
|
return false;
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
async handler(args, ctx) {
|
|
452
|
-
const handled = await handleSessionWorkflowCommand(args, ctx);
|
|
453
|
-
if (!handled) {
|
|
454
|
-
ctx.print('Unknown subcommand: ' + (args[0] ?? '') + '\nUsage: /session-mgmt [list | rename <name> | resume <id> | fork [name] | save [name] | info [id] | events [kind] | groups [kind] | hotspots | export <id> [format] | search <query> | delete <id>]');
|
|
455
|
-
}
|
|
456
|
-
},
|
|
457
|
-
});
|
|
458
|
-
}
|
|
444
|
+
// session-mgmt / smgmt was removed in TASK-032.
|
|
445
|
+
// All session lifecycle operations are now first-class subcommands of /session.
|
|
446
|
+
// Use /session list, /session resume, /session save, etc.
|
|
447
|
+
//
|
|
448
|
+
// CommandRegistry.register() throws on duplicate names/aliases, so this
|
|
449
|
+
// registration was intentionally deleted rather than left as dead code.
|
|
450
|
+
|
|
459
451
|
interface SessionExportData {
|
|
460
452
|
readonly messages: object[];
|
|
461
453
|
readonly timestamp?: number;
|