@pellux/goodvibes-tui 1.16.0 → 1.17.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 +95 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1091 -271
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +4 -4
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/stream-event-wiring.ts +8 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +8 -8
- package/src/daemon/cli.ts +20 -5
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +34 -9
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-runtime.ts +2 -2
- package/src/input/commands.ts +0 -2
- package/src/input/config-modal-types.ts +3 -3
- package/src/input/config-modal.ts +10 -11
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +6 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +2 -2
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +22 -21
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +23 -16
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +48 -48
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +29 -10
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/builtin-modals.ts +10 -10
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +5 -5
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-panel-format.ts +44 -8
- package/src/panels/fleet-panel.ts +16 -16
- package/src/panels/fleet-read-model.ts +52 -13
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +3 -3
- package/src/panels/fleet-tabs.ts +13 -14
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +4 -4
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +2 -2
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +1 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/onboarding/onboarding-wizard.ts +22 -8
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +102 -24
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +14 -5
- package/src/renderer/settings-modal.ts +127 -39
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +1 -1
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +6 -1
- package/src/runtime/bootstrap-command-context.ts +4 -1
- package/src/runtime/bootstrap-command-parts.ts +6 -4
- package/src/runtime/bootstrap-core.ts +35 -35
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +6 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +55 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/index.ts +4 -21
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +81 -82
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +3 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +102 -6
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -4,6 +4,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
4
4
|
import type { CommandRegistry, CommandContext } from '../command-registry.ts';
|
|
5
5
|
import type { SelectionItem } from '../selection-modal.ts';
|
|
6
6
|
import type { ContentPart } from '@pellux/goodvibes-sdk/platform/providers';
|
|
7
|
+
import { formatModelDiscoveryReport } from '@pellux/goodvibes-sdk/platform/providers';
|
|
7
8
|
import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
|
|
8
9
|
import { BUILTIN_SECRET_PROVIDER_SOURCES, describeSecretRef, isSecretRefInput, resolveSecretRef } from '@pellux/goodvibes-sdk/platform/config';
|
|
9
10
|
import { openCommandPanel, requireBookmarkManager, requireProviderApi, requireSecretsManager } from './runtime-services.ts';
|
|
@@ -70,7 +71,7 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
70
71
|
handler(args, ctx) {
|
|
71
72
|
const sub = (args[0] ?? '').toLowerCase();
|
|
72
73
|
if (sub === 'panel' || sub === 'review') {
|
|
73
|
-
//
|
|
74
|
+
// (the purge): 'tools'/ToolInspectorPanel was DELETE-disposition
|
|
74
75
|
// (no surviving human surface — tool results render inline in the
|
|
75
76
|
// transcript, plus a per-node tool list in Fleet). There is no alias
|
|
76
77
|
// to resolve through, so this prints an honest notice and opens
|
|
@@ -200,10 +201,42 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
200
201
|
const flags = new Set(rest.filter((value) => value.startsWith('--')));
|
|
201
202
|
const valueParts = rest.filter((value) => !value.startsWith('--'));
|
|
202
203
|
const [key, ...rawValueParts] = valueParts;
|
|
203
|
-
if (!key
|
|
204
|
+
if (!key) {
|
|
204
205
|
ctx.print(`[secrets] Usage: /secrets ${sub} <KEY> <${sub === 'link' ? 'secret-ref' : 'value'}> [--user|--project] [--secure|--plaintext]`);
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
208
|
+
const scope = flags.has('--user') ? 'user' : 'project';
|
|
209
|
+
const medium = flags.has('--plaintext') ? 'plaintext' : 'secure';
|
|
210
|
+
// /secrets set KEY (no value) drops into the concealed prompt so the
|
|
211
|
+
// value is typed with the keystrokes masked and never enters history.
|
|
212
|
+
if (sub === 'set' && rawValueParts.length === 0) {
|
|
213
|
+
if (!ctx.beginConcealedInput) {
|
|
214
|
+
ctx.print('[secrets] Usage: /secrets set <KEY> <value> [--user|--project] [--secure|--plaintext]');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
ctx.print(`[secrets] Enter value for ${key} (input is masked; press Enter to store, Esc to cancel).`);
|
|
218
|
+
ctx.beginConcealedInput({
|
|
219
|
+
label: key,
|
|
220
|
+
onSubmit: (value) => {
|
|
221
|
+
if (value.length === 0) {
|
|
222
|
+
ctx.print(`[secrets] ${key} not set (empty value).`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
void (async () => {
|
|
226
|
+
try {
|
|
227
|
+
await mgr.set(key, value, { scope, medium });
|
|
228
|
+
ctx.print(`[secrets] Stored: ${key} (${scope}, ${medium})`);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
ctx.print(`[secrets] Could not store ${key}: ${summarizeError(error)}`);
|
|
231
|
+
}
|
|
232
|
+
})();
|
|
233
|
+
},
|
|
234
|
+
onCancel: () => {
|
|
235
|
+
ctx.print(`[secrets] ${key} cancelled.`);
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
207
240
|
const value = rawValueParts.join(' ');
|
|
208
241
|
if (sub === 'link' && !isGoodVibesSecretRefInput(value)) {
|
|
209
242
|
ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
|
|
@@ -213,8 +246,6 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
213
246
|
ctx.print('[secrets] Invalid secret reference. Use /secrets providers for examples.');
|
|
214
247
|
return;
|
|
215
248
|
}
|
|
216
|
-
const scope = flags.has('--user') ? 'user' : 'project';
|
|
217
|
-
const medium = flags.has('--plaintext') ? 'plaintext' : 'secure';
|
|
218
249
|
await mgr.set(key, value, { scope, medium });
|
|
219
250
|
ctx.print(sub === 'link'
|
|
220
251
|
? `[secrets] Linked: ${key} -> ${describeSecretRef(value)} (${scope}, ${medium})`
|
|
@@ -337,7 +368,24 @@ export function registerLocalRuntimeCommands(registry: CommandRegistry): void {
|
|
|
337
368
|
} catch (e) {
|
|
338
369
|
ctx.print(`Token limits refresh failed: ${summarizeError(e)}`);
|
|
339
370
|
}
|
|
340
|
-
|
|
371
|
+
ctx.print('Refreshing live provider model lists...');
|
|
372
|
+
let discoveryOk = false;
|
|
373
|
+
try {
|
|
374
|
+
// Explicit user refresh → force past each provider's TTL cache and
|
|
375
|
+
// report the delta ("3 new, 1 retired") or the honest fallback reason.
|
|
376
|
+
const reports = await providerApi.refreshLiveModelDiscovery(undefined, { force: true });
|
|
377
|
+
discoveryOk = true;
|
|
378
|
+
if (reports.length === 0) {
|
|
379
|
+
ctx.print('No providers perform live model discovery.');
|
|
380
|
+
} else {
|
|
381
|
+
for (const report of reports) {
|
|
382
|
+
ctx.print(` ${formatModelDiscoveryReport(report.providerId, report)}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
} catch (e) {
|
|
386
|
+
ctx.print(`Live model discovery failed: ${summarizeError(e)}`);
|
|
387
|
+
}
|
|
388
|
+
if (!catalogOk || !benchmarksOk || !limitsOk || !discoveryOk) ctx.print('Some refreshes failed — see messages above.');
|
|
341
389
|
},
|
|
342
390
|
});
|
|
343
391
|
|
|
@@ -24,7 +24,7 @@ import { handleRecallFilesApply, handleRecallFilesReview, handleRecallFilesSync
|
|
|
24
24
|
// ── Top-level command ─────────────────────────────────────────────────────────
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
27
|
+
* item 3 divergence note (historical): the work order that shipped
|
|
28
28
|
* this command named its front door "/memory", but at the time `/memory` was
|
|
29
29
|
* already a distinct, unrelated command (session-pinned sticky notes,
|
|
30
30
|
* src/input/commands/session-content.ts) with no modal surface — so that
|
|
@@ -84,7 +84,7 @@ export const recallCommand: SlashCommand = {
|
|
|
84
84
|
handler: async (args: string[], context: CommandContext): Promise<void> => {
|
|
85
85
|
const [sub, ...rest] = args;
|
|
86
86
|
|
|
87
|
-
//
|
|
87
|
+
// item 3: bare `/recall` opens the memory-modal surface — the old
|
|
88
88
|
// bare/unknown-subcommand usage block moved to an explicit `report`
|
|
89
89
|
// subcommand (scriptability preserved: /recall report).
|
|
90
90
|
if (sub === undefined) {
|
|
@@ -5,7 +5,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parse `--target <id>[:<kind>]` out of the args following `<panel-id>
|
|
8
|
-
* [top|bottom]` (
|
|
8
|
+
* [top|bottom]` (item 4). Splits the args array in place (removing the
|
|
9
9
|
* flag + its value) so positional `pane` parsing downstream is unaffected by
|
|
10
10
|
* where the flag appears. Only `open fleet` currently has a deep-link
|
|
11
11
|
* consumer (FleetPanel.receiveDeepLink) — other panel ids just ignore an
|
|
@@ -32,7 +32,7 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
32
32
|
const pm = requirePanelManager(ctx);
|
|
33
33
|
const sub = args[0]?.toLowerCase() ?? '';
|
|
34
34
|
if (!sub || sub === 'toggle') {
|
|
35
|
-
//
|
|
35
|
+
// (the purge): 'panel-list' (the browse-all-panels picker) was
|
|
36
36
|
// DELETE-disposition — a picker over a handful of panels is dead
|
|
37
37
|
// weight now (see the Ctrl+P selectionModal repoint in
|
|
38
38
|
// shell/ui-openers.ts for its interactive replacement). Bare
|
|
@@ -71,9 +71,9 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
71
71
|
// no panel lands in the workspace. Report that honestly rather than
|
|
72
72
|
// claiming "Panel opened: <id>".
|
|
73
73
|
const redirectTarget = pm.getModalRedirect(id);
|
|
74
|
-
//
|
|
74
|
+
// focus rule 1a: the command path leaves focus in the composer
|
|
75
75
|
// ("mid-command-flow") — showPanel does not grab panel focus here.
|
|
76
|
-
//
|
|
76
|
+
// forward the deep-link target so the panel lands on the row.
|
|
77
77
|
if (ctx.showPanel) ctx.showPanel(id, pane as 'top' | 'bottom' | undefined, target);
|
|
78
78
|
else {
|
|
79
79
|
pm.open(id, pane as 'top' | 'bottom' | undefined, target);
|
|
@@ -169,7 +169,7 @@ export function registerOperatorPanelCommand(registry: CommandRegistry): void {
|
|
|
169
169
|
} else {
|
|
170
170
|
const id = args[0]!;
|
|
171
171
|
try {
|
|
172
|
-
//
|
|
172
|
+
// bare `/panel <id>` is the same command path as `/panel open
|
|
173
173
|
// <id>` — composer stays focused.
|
|
174
174
|
if (ctx.showPanel) ctx.showPanel(id);
|
|
175
175
|
else {
|
|
@@ -63,7 +63,7 @@ const PERMISSION_KEYS: readonly { readonly key: string; readonly label: string;
|
|
|
63
63
|
{ key: 'permissions.mode', label: 'Permission mode', note: 'also the current session mode' },
|
|
64
64
|
{ key: 'permissions.backgroundAgents', label: 'Background-agent mode' },
|
|
65
65
|
{ key: 'behavior.autoApprove', label: 'Auto-approve everything' },
|
|
66
|
-
{ key: '
|
|
66
|
+
{ key: 'permissions.engine', label: 'Permission evaluator', note: 'baseline or policy-engine' },
|
|
67
67
|
{ key: 'permissions.tools.read', label: 'Tool rule: read' },
|
|
68
68
|
{ key: 'permissions.tools.write', label: 'Tool rule: write' },
|
|
69
69
|
{ key: 'permissions.tools.edit', label: 'Tool rule: edit' },
|
|
@@ -1,21 +1,112 @@
|
|
|
1
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
1
|
+
import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import type { SelectionItem, SelectionAction } from '../selection-modal.ts';
|
|
2
3
|
import { buildPermissionProvenance, renderPermissionProvenance } from './permissions-provenance.ts';
|
|
4
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
|
+
|
|
6
|
+
/** One remembered-rule row: what it does, and how/when it was remembered. */
|
|
7
|
+
function describeRuleRow(record: {
|
|
8
|
+
rule: { id: string; description?: string | undefined; effect: 'allow' | 'deny' };
|
|
9
|
+
tier: string;
|
|
10
|
+
tool: string;
|
|
11
|
+
createdAt: number;
|
|
12
|
+
}): { label: string; detail: string } {
|
|
13
|
+
const what = record.rule.description ?? record.rule.id;
|
|
14
|
+
return {
|
|
15
|
+
label: `${record.tool}: ${what}`,
|
|
16
|
+
detail: `${record.rule.effect} · remembered ${record.tier} · ${new Date(record.createdAt).toLocaleString()}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function showRememberedRules(ctx: CommandContext): void {
|
|
21
|
+
const store = ctx.clients?.userPermissionRuleStore;
|
|
22
|
+
if (!store) {
|
|
23
|
+
ctx.print('Remembered permission rules are not available on this surface.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const records = store.list();
|
|
27
|
+
if (records.length === 0) {
|
|
28
|
+
ctx.print('No remembered permission rules. Approving an ask with a remember tier stores one here.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (ctx.openSelection) {
|
|
32
|
+
const revoke = new Map<string, SelectionAction>([['d', 'delete']]);
|
|
33
|
+
const items: SelectionItem[] = records.map((record) => {
|
|
34
|
+
const { label, detail } = describeRuleRow(record);
|
|
35
|
+
return { id: record.rule.id, label, detail, actions: '[d] revoke' };
|
|
36
|
+
});
|
|
37
|
+
ctx.openSelection('Remembered permission rules', items, { allowSearch: true, customActions: revoke }, (result) => {
|
|
38
|
+
if (!result || result.action !== 'delete') return;
|
|
39
|
+
void (async () => {
|
|
40
|
+
try {
|
|
41
|
+
const removed = await store.delete(result.item.id);
|
|
42
|
+
ctx.print(removed ? `Revoked remembered rule: ${result.item.label}` : `Rule already gone: ${result.item.label}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
ctx.print(`Could not revoke the rule: ${summarizeError(error)}`);
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// No selection surface: list them honestly with the revoke command to run.
|
|
51
|
+
ctx.print([
|
|
52
|
+
'Remembered permission rules:',
|
|
53
|
+
...records.map((record) => {
|
|
54
|
+
const { label, detail } = describeRuleRow(record);
|
|
55
|
+
return ` ${record.rule.id} ${label} (${detail})`;
|
|
56
|
+
}),
|
|
57
|
+
'',
|
|
58
|
+
'Revoke one with: /permissions revoke <rule-id>',
|
|
59
|
+
].join('\n'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function revokeRememberedRule(ctx: CommandContext, ruleId: string): Promise<void> {
|
|
63
|
+
const store = ctx.clients?.userPermissionRuleStore;
|
|
64
|
+
if (!store) {
|
|
65
|
+
ctx.print('Remembered permission rules are not available on this surface.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!ruleId) {
|
|
69
|
+
ctx.print('Usage: /permissions revoke <rule-id> (see /permissions rules for ids)');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const removed = await store.delete(ruleId);
|
|
74
|
+
ctx.print(removed ? `Revoked remembered rule: ${ruleId}` : `No remembered rule with id: ${ruleId}`);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
ctx.print(`Could not revoke the rule: ${summarizeError(error)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
3
79
|
|
|
4
80
|
/**
|
|
5
|
-
* `/permissions` — the permission
|
|
6
|
-
* permission-relevant setting in effect and where each value came from
|
|
7
|
-
* (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
81
|
+
* `/permissions` — the permission settings surface. With no arguments it prints
|
|
82
|
+
* every permission-relevant setting in effect and where each value came from
|
|
83
|
+
* (provenance; read-only). `rules` lists the durable remembered approvals with a
|
|
84
|
+
* press-Enter revoke; `revoke <id>` removes one by id. Revokes hit the same
|
|
85
|
+
* in-process rule store the evaluator reads, so they take effect live.
|
|
10
86
|
*/
|
|
11
87
|
export function registerPermissionsRuntimeCommands(registry: CommandRegistry): void {
|
|
12
88
|
registry.register({
|
|
13
89
|
name: 'permissions',
|
|
14
90
|
aliases: ['perms'],
|
|
15
|
-
description: 'Show permission settings
|
|
16
|
-
|
|
91
|
+
description: 'Show permission settings and provenance, and list or revoke remembered approval rules',
|
|
92
|
+
usage: '[rules | revoke <rule-id>]',
|
|
93
|
+
argsHint: '[rules|revoke]',
|
|
94
|
+
async handler(args, ctx) {
|
|
95
|
+
const sub = (args[0] ?? '').toLowerCase();
|
|
96
|
+
if (sub === 'rules' || sub === 'rule') {
|
|
97
|
+
showRememberedRules(ctx);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (sub === 'revoke' || sub === 'forget') {
|
|
101
|
+
await revokeRememberedRule(ctx, args[1] ?? '');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
17
104
|
const provenance = buildPermissionProvenance(ctx.platform.configManager);
|
|
18
105
|
ctx.print(renderPermissionProvenance(provenance));
|
|
106
|
+
const ruleCount = ctx.clients?.userPermissionRuleStore?.list().length ?? 0;
|
|
107
|
+
ctx.print(ruleCount > 0
|
|
108
|
+
? `\n${ruleCount} remembered approval rule${ruleCount === 1 ? '' : 's'} in effect. Use /permissions rules to review or revoke them.`
|
|
109
|
+
: '\nNo remembered approval rules. Use /permissions rules once you have some.');
|
|
19
110
|
},
|
|
20
111
|
});
|
|
21
112
|
}
|
|
@@ -13,7 +13,7 @@ import { togglePlanMode, permissionModeLabel, type PermissionModeValue } from '.
|
|
|
13
13
|
* A lone one of these is refused rather than seeded as a goal, so a stray verb
|
|
14
14
|
* can never overwrite the project goal with itself.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* `dismiss` and `answer` are now REAL subcommands (handled above this
|
|
17
17
|
* guard), so they were removed from the refuse-list. `pause`/`stop`/`cancel`
|
|
18
18
|
* remain here — they still have no backing verb and must not seed a goal.
|
|
19
19
|
*/
|
|
@@ -178,7 +178,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// /plan dismiss — archive the current plan. Dismisses the active
|
|
182
182
|
// execution plan (ExecutionPlanManager.dismiss, honest per-state) AND
|
|
183
183
|
// deactivates the project-planning interview state shown in the modal so a
|
|
184
184
|
// later /plan <goal> starts fresh. Mid-execution is refused outright.
|
|
@@ -223,7 +223,7 @@ export function registerPlanningRuntimeCommands(registry: CommandRegistry): void
|
|
|
223
223
|
return;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
//
|
|
226
|
+
// /plan answer <n|question-id> <text> — record a real answer to an
|
|
227
227
|
// open planning question (moves open → answered, consumed on next refine).
|
|
228
228
|
if (args[0] === 'answer') {
|
|
229
229
|
if (!projectPlanningService || !projectId) {
|
|
@@ -98,7 +98,7 @@ export function registerPlatformSandboxRuntimeCommands(registry: CommandRegistry
|
|
|
98
98
|
const shellPaths = requireShellPaths(ctx);
|
|
99
99
|
const sub = args[0] ?? 'open';
|
|
100
100
|
if (sub === 'open' || sub === 'panel') {
|
|
101
|
-
ctx.openModal?.('sandbox-modal'); //
|
|
101
|
+
ctx.openModal?.('sandbox-modal'); // sandbox panel -> config modal
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
if (sub === 'review') {
|
|
@@ -3,11 +3,13 @@ import type {
|
|
|
3
3
|
ProviderAccountRecord,
|
|
4
4
|
ProviderAccountSnapshot,
|
|
5
5
|
} from '@/runtime/index.ts';
|
|
6
|
+
import type { SelectionItem } from '../selection-modal.ts';
|
|
6
7
|
import {
|
|
7
|
-
openCommandPanel,
|
|
8
8
|
requireOperatorClient,
|
|
9
9
|
} from './runtime-services.ts';
|
|
10
10
|
|
|
11
|
+
type RecommendedAction = ProviderAccountRecord['recommendedActions'][number];
|
|
12
|
+
|
|
11
13
|
async function loadProviderAccountSnapshot(context: CommandContext): Promise<ProviderAccountSnapshot> {
|
|
12
14
|
return await requireOperatorClient(context).providers.accountSnapshot();
|
|
13
15
|
}
|
|
@@ -20,6 +22,71 @@ function findProviderAccountRecord(
|
|
|
20
22
|
return snapshot.providers.find((entry) => entry.providerId === providerId);
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Present a record's recommended actions as executable rows. A recommended
|
|
27
|
+
* action that carries a structured command becomes a press-Enter row that RUNS
|
|
28
|
+
* that command through the command registry — never a line the user has to
|
|
29
|
+
* retype. Actions with no command are genuinely out-of-product steps, so they
|
|
30
|
+
* stay as plain (non-executing) rows. When there is no interactive selection
|
|
31
|
+
* surface, each action is printed honestly instead.
|
|
32
|
+
*/
|
|
33
|
+
export function presentRecommendedActions(
|
|
34
|
+
ctx: CommandContext,
|
|
35
|
+
providerId: string,
|
|
36
|
+
actions: readonly RecommendedAction[],
|
|
37
|
+
): void {
|
|
38
|
+
if (actions.length === 0) {
|
|
39
|
+
ctx.print('No active repair actions suggested.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (ctx.openSelection) {
|
|
43
|
+
const items: SelectionItem[] = actions.map((action, index) => {
|
|
44
|
+
if (action.command) {
|
|
45
|
+
const argsPart = action.command.args.length > 0 ? ` ${action.command.args.join(' ')}` : '';
|
|
46
|
+
return {
|
|
47
|
+
id: String(index),
|
|
48
|
+
label: action.description,
|
|
49
|
+
detail: `runs /${action.command.name}${argsPart}`,
|
|
50
|
+
primaryAction: 'select',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
id: String(index),
|
|
55
|
+
label: action.description,
|
|
56
|
+
detail: 'manual step (nothing to run)',
|
|
57
|
+
primaryAction: 'select',
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
ctx.openSelection(
|
|
61
|
+
`Repair ${providerId}`,
|
|
62
|
+
items,
|
|
63
|
+
{ allowSearch: false, primaryVerbLabel: 'Run' },
|
|
64
|
+
(result) => {
|
|
65
|
+
if (!result) return;
|
|
66
|
+
const action = actions[Number(result.item.id)];
|
|
67
|
+
if (!action) return;
|
|
68
|
+
if (action.command) {
|
|
69
|
+
const args = [...action.command.args];
|
|
70
|
+
void (ctx.executeCommand?.(action.command.name, args)
|
|
71
|
+
?? Promise.resolve(ctx.print(`Cannot run /${action.command.name} — command execution is unavailable here.`)));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
ctx.print(action.description);
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// No selection surface (headless/command-only): list actions honestly.
|
|
80
|
+
ctx.print([
|
|
81
|
+
'Recommended actions:',
|
|
82
|
+
...actions.map((action) => (
|
|
83
|
+
action.command
|
|
84
|
+
? ` • ${action.description} — /${action.command.name}${action.command.args.length > 0 ? ` ${action.command.args.join(' ')}` : ''}`
|
|
85
|
+
: ` • ${action.description} (manual step)`
|
|
86
|
+
)),
|
|
87
|
+
].join('\n'));
|
|
88
|
+
}
|
|
89
|
+
|
|
23
90
|
export function registerProviderAccountsRuntimeCommands(registry: CommandRegistry): void {
|
|
24
91
|
registry.register({
|
|
25
92
|
name: 'accounts',
|
|
@@ -29,7 +96,7 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
|
|
|
29
96
|
async handler(args, ctx) {
|
|
30
97
|
const sub = (args[0] ?? 'review').toLowerCase();
|
|
31
98
|
if (sub === 'panel' || sub === 'open') {
|
|
32
|
-
ctx.openModal?.('providers-modal'); //
|
|
99
|
+
ctx.openModal?.('providers-modal'); // accounts alias -> provider-health modal
|
|
33
100
|
return;
|
|
34
101
|
}
|
|
35
102
|
const snapshot = await loadProviderAccountSnapshot(ctx);
|
|
@@ -63,10 +130,8 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
|
|
|
63
130
|
` preferred route: ${record.preferredRoute}`,
|
|
64
131
|
...(record.fallbackRisk ? [` fallback: ${record.fallbackRisk}`] : []),
|
|
65
132
|
...(record.issues.map((issue) => ` issue: ${issue}`)),
|
|
66
|
-
...(record.recommendedActions.length > 0
|
|
67
|
-
? [' next:', ...record.recommendedActions.map((action) => ` ${action}`)]
|
|
68
|
-
: [' No active repair actions suggested.']),
|
|
69
133
|
].join('\n'));
|
|
134
|
+
presentRecommendedActions(ctx, record.providerId, record.recommendedActions);
|
|
70
135
|
return;
|
|
71
136
|
}
|
|
72
137
|
if (sub === 'show') {
|
|
@@ -95,8 +160,8 @@ export function registerProviderAccountsRuntimeCommands(registry: CommandRegistr
|
|
|
95
160
|
...record.usageWindows.map((entry) => ` window: ${entry.label} — ${entry.detail}`),
|
|
96
161
|
...record.issues.map((issue) => ` issue: ${issue}`),
|
|
97
162
|
...record.notes.map((note) => ` note: ${note}`),
|
|
98
|
-
...record.recommendedActions.map((action) => ` next: ${action}`),
|
|
99
163
|
].join('\n'));
|
|
164
|
+
presentRecommendedActions(ctx, record.providerId, record.recommendedActions);
|
|
100
165
|
return;
|
|
101
166
|
}
|
|
102
167
|
ctx.print([
|
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { SlashCommand, CommandContext } from '../command-registry.ts';
|
|
18
|
-
import type { ConfigKey } from '../../config/index.ts';
|
|
19
18
|
import type { RouteExplanation } from '@pellux/goodvibes-sdk/platform/providers';
|
|
20
19
|
import type { FallbackTestResult, FallbackTransition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
21
20
|
import type { ProviderApiModelRecord } from '@pellux/goodvibes-sdk/platform/providers';
|
|
22
21
|
import { requireProviderApi } from './runtime-services.ts';
|
|
22
|
+
import { featureEnablementWrite } from '../../runtime/feature-settings.ts';
|
|
23
23
|
|
|
24
24
|
const PROVIDER_OPTIMIZER_FLAG = 'provider-optimizer';
|
|
25
|
-
const PROVIDER_OPTIMIZER_CONFIG_KEY = `featureFlags.${PROVIDER_OPTIMIZER_FLAG}` as ConfigKey;
|
|
26
25
|
|
|
27
26
|
// ---------------------------------------------------------------------------
|
|
28
27
|
// Formatting helpers
|
|
@@ -95,9 +94,12 @@ function handleOptimizerToggle(
|
|
|
95
94
|
const wasEnabled = optimizer.enabled;
|
|
96
95
|
optimizer.setEnabled(enable);
|
|
97
96
|
|
|
98
|
-
// Persist to config so the setting survives restart
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
// Persist to config so the setting survives restart: the optimizer is
|
|
98
|
+
// governed by the provider.optimizerMode settings key (off = disabled;
|
|
99
|
+
// manual/auto/pinned = active), and the settings bridge forwards the same
|
|
100
|
+
// write to the runtime gate.
|
|
101
|
+
const write = featureEnablementWrite(PROVIDER_OPTIMIZER_FLAG, enable);
|
|
102
|
+
if (write) context.platform.configManager.setDynamic(write.key, write.value);
|
|
101
103
|
|
|
102
104
|
if (enable && !wasEnabled) {
|
|
103
105
|
context.print('[provider] Optimizer enabled.');
|
|
@@ -1,57 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* recall-files-git.ts — the
|
|
2
|
+
* recall-files-git.ts — the git seam for memory file projection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* for `add`/`commit` so the seam matches the SDK's synchronous contract.
|
|
4
|
+
* The SDK owns the production seam now (`createMemoryProjectionGit`,
|
|
5
|
+
* platform/state): it shells out to the system git, commits with a neutral
|
|
6
|
+
* projection identity (never the operator's), tolerates an unchanged tree,
|
|
7
|
+
* and — through `projectMemoryToFiles`'s ownership gate — only ever commits
|
|
8
|
+
* to a repository whose toplevel IS the projection directory, initializing
|
|
9
|
+
* one there when needed. A projection directory nested inside some other
|
|
10
|
+
* checkout can never pollute that checkout. This module is a thin local
|
|
11
|
+
* seam factory so command code keeps a single import site.
|
|
13
12
|
*/
|
|
13
|
+
import { createMemoryProjectionGit } from '@pellux/goodvibes-sdk/platform/state';
|
|
14
14
|
import type { MemoryProjectionGit } from '@pellux/goodvibes-sdk/platform/state';
|
|
15
|
-
import { GitService } from '@pellux/goodvibes-sdk/platform/git';
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Build a synchronous git seam rooted at `repoRoot`. `commit()` treats a
|
|
23
|
-
* "nothing to commit" exit as a benign no-op (not an error) so
|
|
24
|
-
* `/recall files sync` stays safely re-runnable when the projection files
|
|
25
|
-
* did not actually change since the last sync — any OTHER commit failure
|
|
26
|
-
* (no git identity configured, hook rejection, etc.) still throws, since the
|
|
27
|
-
* seam's contract has no way to report partial failure back to the caller
|
|
28
|
-
* other than throwing.
|
|
29
|
-
*/
|
|
30
|
-
export function createSyncGitSeam(repoRoot: string): MemoryProjectionGit {
|
|
31
|
-
return {
|
|
32
|
-
add(dir: string): void {
|
|
33
|
-
const result = Bun.spawnSync(['git', 'add', dir], { cwd: repoRoot });
|
|
34
|
-
if (result.exitCode !== 0) {
|
|
35
|
-
throw new Error(`git add failed: ${decode(result.stderr) || 'unknown error'}`);
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
commit(dir: string, message: string): void {
|
|
39
|
-
const result = Bun.spawnSync(['git', 'commit', '-m', message, '--', dir], { cwd: repoRoot });
|
|
40
|
-
if (result.exitCode === 0) return;
|
|
41
|
-
const output = `${decode(result.stdout)} ${decode(result.stderr)}`;
|
|
42
|
-
if (/nothing to commit/i.test(output)) return; // benign — no new/changed files this run
|
|
43
|
-
throw new Error(`git commit failed: ${output.trim() || 'unknown error'}`);
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Resolve the git seam for `dir`, or null when `dir` is not inside a git
|
|
50
|
-
* repository. Never auto-initializes a repository — that is a much bigger
|
|
51
|
-
* side effect than a memory sync command should take unasked.
|
|
52
|
-
*/
|
|
53
|
-
export function resolveSyncGitSeam(dir: string): { seam: MemoryProjectionGit; repoRoot: string } | null {
|
|
54
|
-
const repoRoot = GitService.getRepoRoot(dir);
|
|
55
|
-
if (!repoRoot) return null;
|
|
56
|
-
return { seam: createSyncGitSeam(repoRoot), repoRoot };
|
|
16
|
+
/** The projection git seam (see module note — SDK-owned behavior). */
|
|
17
|
+
export function createSyncGitSeam(): MemoryProjectionGit {
|
|
18
|
+
return createMemoryProjectionGit();
|
|
57
19
|
}
|
|
@@ -39,7 +39,7 @@ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
|
39
39
|
import { requireShellPaths } from './runtime-services.ts';
|
|
40
40
|
import { getMemorySpine } from './recall-query.ts';
|
|
41
41
|
import { readMemoryProjectionDir } from './recall-files-config.ts';
|
|
42
|
-
import {
|
|
42
|
+
import { createSyncGitSeam } from './recall-files-git.ts';
|
|
43
43
|
|
|
44
44
|
const PROJECTION_LIST_LIMIT = 5000;
|
|
45
45
|
|
|
@@ -58,19 +58,16 @@ export async function handleRecallFilesSync(args: string[], context: CommandCont
|
|
|
58
58
|
|
|
59
59
|
const dir = resolveProjectionDir(args, context);
|
|
60
60
|
const records = await memory.list({ limit: PROJECTION_LIST_LIMIT });
|
|
61
|
-
// Resolve the git seam from the project working directory, not `dir` itself
|
|
62
|
-
// — `dir` may not exist yet on a first sync, and `git -C <dir>` requires the
|
|
63
|
-
// directory to already exist. The working directory is always real.
|
|
64
|
-
const gitSeam = resolveSyncGitSeam(requireShellPaths(context).workingDirectory);
|
|
65
61
|
|
|
66
62
|
try {
|
|
67
|
-
|
|
63
|
+
// The SDK's ownership gate commits only to a repository whose toplevel IS
|
|
64
|
+
// the projection directory (initializing one there when needed) — never
|
|
65
|
+
// to a checkout the directory merely sits inside.
|
|
66
|
+
const report = projectMemoryToFiles(records, dir, { git: createSyncGitSeam() });
|
|
68
67
|
context.print(`[recall] Projected ${report.written.length} record(s) to ${report.dir}`);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
context.print(` not committed: ${dir} is not inside a git repository`);
|
|
73
|
-
}
|
|
68
|
+
context.print(report.committed
|
|
69
|
+
? ` committed in ${report.dir} (the projection's own repository)`
|
|
70
|
+
: ' not committed (git seam did not run)');
|
|
74
71
|
} catch (error) {
|
|
75
72
|
context.print(`[recall] Projection sync failed: ${summarizeError(error)}`);
|
|
76
73
|
}
|
|
@@ -112,9 +112,9 @@ export function handleRecallExplain(args: string[], context: CommandContext): vo
|
|
|
112
112
|
* `exportState()` returns the same full `AgentRecord[]` (it's what
|
|
113
113
|
* compactConversation() already uses for the same reason).
|
|
114
114
|
*
|
|
115
|
-
* With NO agent id (
|
|
115
|
+
* With NO agent id (wiring), renders the MAIN interactive session's own
|
|
116
116
|
* per-turn injection ring — `Orchestrator.getTurnInjections()`, threaded onto
|
|
117
|
-
* the command context as `session.getMainSessionTurnInjections`. As of
|
|
117
|
+
* the command context as `session.getMainSessionTurnInjections`. As of the
|
|
118
118
|
* main session DOES route through the passive-injection engine, so this is the
|
|
119
119
|
* honest main-session default (previously there was no record source, so the
|
|
120
120
|
* no-id path could only print a usage hint). When the accessor isn't wired
|
|
@@ -47,7 +47,7 @@ async function renderStatus(ctx: CommandContext): Promise<void> {
|
|
|
47
47
|
async function renderPair(ctx: CommandContext): Promise<void> {
|
|
48
48
|
const cfg = relayConfigured(ctx);
|
|
49
49
|
if (!cfg.enabled || !cfg.flagOn) {
|
|
50
|
-
ctx.print('Relay is disabled —
|
|
50
|
+
ctx.print('Relay is disabled — turn on relay.enabled in /config relay first.');
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
const externalServices = ctx.platform.externalServices;
|
|
@@ -48,7 +48,7 @@ export function registerRemoteRuntimeCommands(registry: CommandRegistry): void {
|
|
|
48
48
|
async handler(args, ctx) {
|
|
49
49
|
if (args.length === 0) {
|
|
50
50
|
if (ctx.openModal) {
|
|
51
|
-
ctx.openModal('remote-modal'); //
|
|
51
|
+
ctx.openModal('remote-modal'); // remote panel -> config modal
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
ctx.print('Remote surface is not available in this runtime.');
|
|
@@ -104,8 +104,8 @@ export function openCommandPanel(
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
* ctx.openModal seam (ui-openers wires it; the
|
|
107
|
+
* (group-B migration): open a config-modal surface by name via the
|
|
108
|
+
* ctx.openModal seam (ui-openers wires it; the host replaces the interim
|
|
109
109
|
* "not available yet" implementation with real dispatch). Front-doors for
|
|
110
110
|
* panels that migrated to modals call this instead of openCommandPanel — the
|
|
111
111
|
* modal is the surface's new home. Stubbed in tests by setting ctx.openModal.
|
|
@@ -256,7 +256,7 @@ export function requireProviderApi(context: CommandContext): ProviderApi {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
/** Result of a manual /compact run: the SDK's CompactionEvent, plus an
|
|
259
|
-
* out-of-band quality score (
|
|
259
|
+
* out-of-band quality score () when one could be computed. */
|
|
260
260
|
export interface CompactionRunResult {
|
|
261
261
|
readonly event: CompactionEvent;
|
|
262
262
|
readonly qualityScore: CompactionQualityScore | null;
|
|
@@ -328,7 +328,7 @@ export async function compactConversation(context: CommandContext): Promise<Comp
|
|
|
328
328
|
const eventAfter = getLastCompactionEvent();
|
|
329
329
|
// Return the new event only if it differs from the one recorded before the call.
|
|
330
330
|
if (eventAfter !== null && eventAfter !== eventBefore) {
|
|
331
|
-
|
|
331
|
+
//: score this run out-of-band (see compaction-quality.ts for why
|
|
332
332
|
// this can't just subscribe to the SDK's own CompactionManager pipeline)
|
|
333
333
|
// and keep the score keyed by this event's timestamp for /compact-history.
|
|
334
334
|
const qualityScore = scoreCompactionRun({
|