@pellux/goodvibes-agent 1.5.5 → 1.5.6
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 +9 -0
- package/README.md +1 -1
- package/dist/package/main.js +94130 -79335
- package/docs/tools-and-commands.md +2 -1
- package/package.json +6 -3
- package/src/agent/autonomy-schedule-format.ts +1 -1
- package/src/agent/autonomy-schedule.ts +3 -3
- package/src/agent/calendar/calendar-oauth-service.ts +324 -0
- package/src/agent/calendar-subscription-registry.ts +497 -0
- package/src/agent/email/email-service.ts +57 -0
- package/src/agent/email/smtp-client.ts +30 -10
- package/src/agent/memory-prompt.ts +143 -12
- package/src/agent/operator-actions.ts +26 -22
- package/src/agent/prompt-context-receipts.ts +45 -18
- package/src/agent/reminder-schedule-format.ts +1 -1
- package/src/agent/reminder-schedule.ts +3 -3
- package/src/agent/routine-schedule-format.ts +2 -2
- package/src/agent/routine-schedule-promotion.ts +5 -5
- package/src/agent/routine-schedule-receipts.ts +2 -2
- package/src/agent/schedule-edit-format.ts +1 -1
- package/src/agent/schedule-edit.ts +5 -5
- package/src/agent/session-registration.ts +281 -0
- package/src/agent/vibe-file.ts +50 -0
- package/src/cli/local-library-command-shared.ts +146 -0
- package/src/cli/management.ts +2 -1
- package/src/cli/personas-command.ts +252 -0
- package/src/cli/resume-relaunch-notice.ts +151 -0
- package/src/cli/routines-command.ts +25 -2
- package/src/cli/service-posture.ts +3 -2
- package/src/cli/skill-bundle-command.ts +175 -0
- package/src/cli/skills-command.ts +309 -0
- package/src/cli/status.ts +43 -7
- package/src/cli/tui-startup.ts +24 -1
- package/src/config/agent-settings-policy.ts +0 -1
- package/src/config/secret-config.ts +4 -0
- package/src/core/conversation-rendering.ts +20 -11
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/system-message-noise.ts +108 -0
- package/src/core/system-message-router.ts +54 -1
- package/src/core/thinking-overlay.ts +83 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +75 -108
- package/src/input/agent-workspace-access-command-editors.ts +108 -128
- package/src/input/agent-workspace-activation.ts +15 -0
- package/src/input/agent-workspace-basic-command-editor-submission.ts +292 -513
- package/src/input/agent-workspace-basic-command-editors.ts +433 -564
- package/src/input/agent-workspace-calendar-connect-editor.ts +116 -0
- package/src/input/agent-workspace-calendar-oauth-editor.ts +152 -0
- package/src/input/agent-workspace-calendar-subscribe-editor.ts +128 -0
- package/src/input/agent-workspace-categories.ts +7 -2
- package/src/input/agent-workspace-channel-command-editor-submission.ts +38 -52
- package/src/input/agent-workspace-channel-command-editors.ts +42 -46
- package/src/input/agent-workspace-command-editor-engine.ts +133 -0
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-direct-editor-submission.ts +59 -0
- package/src/input/agent-workspace-email-connect-editor.ts +162 -0
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +52 -81
- package/src/input/agent-workspace-knowledge-command-editors.ts +71 -71
- package/src/input/agent-workspace-library-command-editor-submission.ts +10 -28
- package/src/input/agent-workspace-library-command-editors.ts +16 -2
- package/src/input/agent-workspace-live-counters.ts +55 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +60 -82
- package/src/input/agent-workspace-mcp-command-editors.ts +52 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +12 -39
- package/src/input/agent-workspace-media-command-editors.ts +10 -8
- package/src/input/agent-workspace-memory-command-editor-submission.ts +76 -151
- package/src/input/agent-workspace-memory-command-editors.ts +116 -141
- package/src/input/agent-workspace-operations-command-editor-submission.ts +131 -184
- package/src/input/agent-workspace-operations-command-editors.ts +150 -162
- package/src/input/agent-workspace-provider-command-editor-submission.ts +60 -106
- package/src/input/agent-workspace-provider-command-editors.ts +58 -68
- package/src/input/agent-workspace-search.ts +8 -1
- package/src/input/agent-workspace-session-command-editor-submission.ts +104 -132
- package/src/input/agent-workspace-session-command-editors.ts +160 -195
- package/src/input/agent-workspace-settings.ts +40 -1
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +55 -71
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +65 -69
- package/src/input/agent-workspace-snapshot-builders.ts +431 -0
- package/src/input/agent-workspace-snapshot-config.ts +43 -0
- package/src/input/agent-workspace-snapshot.ts +198 -432
- package/src/input/agent-workspace-task-command-editor-submission.ts +34 -44
- package/src/input/agent-workspace-task-command-editors.ts +27 -28
- package/src/input/agent-workspace-types.ts +35 -1
- package/src/input/agent-workspace.ts +5 -14
- package/src/input/command-registry.ts +23 -1
- package/src/input/commands/calendar-connect-runtime.ts +226 -0
- package/src/input/commands/calendar-runtime.ts +110 -7
- package/src/input/commands/calendar-subscription-runtime.ts +225 -0
- package/src/input/commands/email-runtime.ts +100 -40
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/network-scan-runtime.ts +75 -0
- package/src/input/commands/operator-actions-runtime.ts +9 -6
- package/src/input/commands/personas-runtime.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +13 -1
- package/src/input/commands/session-workflow.ts +15 -20
- package/src/input/commands/session.ts +3 -1
- package/src/input/commands/shell-core.ts +20 -6
- package/src/input/commands.ts +2 -2
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +10 -0
- package/src/input/handler-feed.ts +87 -0
- package/src/input/handler-modal-routes.ts +6 -1
- package/src/input/handler.ts +5 -0
- package/src/input/panel-paste-flood-guard.ts +94 -0
- package/src/input/settings-modal-types.ts +5 -3
- package/src/input/settings-modal.ts +21 -0
- package/src/main.ts +36 -36
- package/src/permissions/approval-posture.ts +141 -0
- package/src/renderer/agent-workspace-context-lines.ts +10 -2
- package/src/renderer/compositor.ts +27 -4
- package/src/renderer/diff.ts +61 -18
- package/src/renderer/fullscreen-primitives.ts +37 -18
- package/src/renderer/markdown.ts +20 -10
- package/src/renderer/modal-factory.ts +25 -15
- package/src/renderer/overlay-box.ts +23 -12
- package/src/renderer/process-indicator.ts +8 -3
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -0
- package/src/renderer/startup-theme-probe.ts +35 -0
- package/src/renderer/status-glyphs.ts +11 -15
- package/src/renderer/system-message.ts +17 -2
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/terminal-bg-probe.ts +373 -0
- package/src/renderer/terminal-escapes.ts +24 -0
- package/src/renderer/theme-mode-config.ts +87 -0
- package/src/renderer/theme.ts +241 -0
- package/src/renderer/thinking.ts +12 -3
- package/src/renderer/tool-call.ts +7 -3
- package/src/renderer/ui-factory.ts +92 -36
- package/src/renderer/ui-primitives.ts +33 -93
- package/src/runtime/bootstrap-core.ts +15 -0
- package/src/runtime/bootstrap-hook-bridge.ts +6 -0
- package/src/runtime/bootstrap-shell.ts +2 -0
- package/src/runtime/bootstrap.ts +68 -5
- package/src/runtime/calendar-boot-refresh.ts +105 -0
- package/src/runtime/lan-scan-consent.ts +253 -0
- package/src/runtime/services.ts +127 -2
- package/src/runtime/session-spine-rest-transport.ts +160 -0
- package/src/runtime/terminal-output-guard.ts +6 -1
- package/src/runtime/ui-services.ts +3 -0
- package/src/shell/agent-workspace-fullscreen.ts +5 -0
- package/src/shell/terminal-focus-mode.ts +120 -0
- package/src/shell/ui-openers.ts +13 -4
- package/src/tools/agent-harness-autonomy-intake.ts +5 -5
- package/src/tools/agent-harness-autonomy-queue.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +2 -1
- package/src/tools/agent-harness-metadata.ts +16 -8
- package/src/tools/agent-harness-operator-methods.ts +44 -8
- package/src/tools/agent-harness-personal-ops-discovery.ts +57 -10
- package/src/tools/agent-harness-personal-ops-lanes.ts +17 -5
- package/src/tools/agent-harness-personal-ops-operations.ts +7 -7
- package/src/tools/agent-harness-personal-ops-types.ts +6 -0
- package/src/tools/agent-harness-prompt-context.ts +26 -12
- package/src/tools/agent-harness-workspace-actions.ts +4 -0
- package/src/tools/agent-local-registry-args.ts +117 -0
- package/src/tools/agent-local-registry-memory.ts +227 -0
- package/src/tools/agent-local-registry-tool.ts +19 -237
- package/src/tools/agent-operator-briefing-tool.ts +2 -2
- package/src/tools/agent-operator-method-tool.ts +13 -0
- package/src/tools/agent-policy-explanation.ts +39 -4
- package/src/tools/agent-schedule-tool.ts +5 -5
- package/src/utils/terminal-width.ts +98 -1
- package/src/version.ts +1 -1
- package/src/cli/local-library-command.ts +0 -825
|
@@ -17,6 +17,12 @@ import {
|
|
|
17
17
|
type SettingsSecretsManager,
|
|
18
18
|
} from './settings-modal-secrets.ts';
|
|
19
19
|
import { buildSubscriptionEntries } from './settings-modal-subscriptions.ts';
|
|
20
|
+
import {
|
|
21
|
+
coerceThemeModeSetting,
|
|
22
|
+
THEME_MODE_CONFIG_KEY,
|
|
23
|
+
THEME_MODE_DEFAULT,
|
|
24
|
+
THEME_MODE_SYNTHETIC_SETTING,
|
|
25
|
+
} from '../renderer/theme-mode-config.ts';
|
|
20
26
|
import type { FeatureFlagManager } from '@/runtime/index.ts';
|
|
21
27
|
import type { FeatureFlag, FlagState } from '@/runtime/index.ts';
|
|
22
28
|
import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
@@ -634,6 +640,21 @@ export class SettingsModal {
|
|
|
634
640
|
if (this.groups.has(cat)) this.groups.get(cat)!.push(entry);
|
|
635
641
|
}
|
|
636
642
|
|
|
643
|
+
// W4-R4: inject the synthetic display.themeMode enum (auto|dark|light) —
|
|
644
|
+
// agent-local key stored under the existing `display` section (not in the
|
|
645
|
+
// SDK ConfigKey union; setDynamic/get round-trip it), the TUI's
|
|
646
|
+
// settings-modal-data.ts synthetic-setting pattern. Cycles like any other
|
|
647
|
+
// enum entry; forced modes are applied by the ui-openers change hook.
|
|
648
|
+
const displayEntries = this.groups.get('display');
|
|
649
|
+
if (displayEntries && !displayEntries.some((e) => e.setting.key === (THEME_MODE_CONFIG_KEY as ConfigKey))) {
|
|
650
|
+
const themeModeValue = coerceThemeModeSetting(configManager.get(THEME_MODE_CONFIG_KEY as ConfigKey));
|
|
651
|
+
displayEntries.push({
|
|
652
|
+
setting: THEME_MODE_SYNTHETIC_SETTING,
|
|
653
|
+
currentValue: themeModeValue,
|
|
654
|
+
isDefault: themeModeValue === THEME_MODE_DEFAULT,
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
|
|
637
658
|
const uiEntries = this.groups.get('ui');
|
|
638
659
|
if (uiEntries) {
|
|
639
660
|
const uiPriority: Record<string, number> = {
|
package/src/main.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { Compositor } from './renderer/compositor.ts';
|
|
4
|
+
import { installStartupThemeProbe } from './renderer/startup-theme-probe.ts';
|
|
5
|
+
import { ThinkingStallClock, buildThinkingOverlay } from './core/thinking-overlay.ts';
|
|
4
6
|
import { UIFactory } from './renderer/ui-factory.ts';
|
|
5
7
|
import { Orchestrator } from './core/orchestrator';
|
|
6
8
|
import { conversationMessagesAsSessionRecords } from './core/conversation-message-snapshot.ts';
|
|
@@ -47,6 +49,7 @@ import { wireShellUiOpeners } from './shell/ui-openers.ts';
|
|
|
47
49
|
import { deriveComposerState } from './core/composer-state.ts';
|
|
48
50
|
import { buildPersistedSessionContext } from '@/runtime/index.ts';
|
|
49
51
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
52
|
+
import { FOCUS_ENABLE, FOCUS_DISABLE, installFocusModeExitGuard, markFocusModeEnabled, wrapRequestPermissionWithApprovalAlert } from './shell/terminal-focus-mode.ts';
|
|
50
53
|
import { prepareShellCliRuntime } from './cli/entrypoint.ts';
|
|
51
54
|
import { applyInitialTuiCliState, formatFatalStartupErrorForLog, formatFatalStartupErrorForUser, getInteractiveTerminalLaunchError } from './cli/tui-startup.ts';
|
|
52
55
|
import { wireSpokenTurnRuntime } from './audio/spoken-turn-wiring.ts';
|
|
@@ -56,6 +59,7 @@ import { buildCommandArgsHint } from './input/command-args-hint.ts';
|
|
|
56
59
|
import { GOODVIBES_AGENT_PAIRING_SURFACE } from './config/surface.ts';
|
|
57
60
|
import { createAutonomySurfacing, buildCalendarEventsLister, buildSkillDraftProposer } from './shell/autonomy-surfacing.ts';
|
|
58
61
|
import { startHardwareProbe } from './core/hardware-profile.ts';
|
|
62
|
+
import { readApprovalPostureFromConfig } from './permissions/approval-posture.ts';
|
|
59
63
|
|
|
60
64
|
const ALT_SCREEN_ENTER = '\x1b[?1049h', ALT_SCREEN_EXIT = '\x1b[?1049l', MOUSE_ENABLE = '\x1b[?1000h\x1b[?1002h\x1b[?1006h', MOUSE_DISABLE = '\x1b[?1006l\x1b[?1002l\x1b[?1000l', CURSOR_HIDE = '\x1b[?25l', CURSOR_SHOW = '\x1b[?25h', CLEAR_SCREEN = '\x1b[2J\x1b[3J\x1b[H';
|
|
61
65
|
const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\x1b[>4;0m' + '\x1b[?1l', PASTE_ENABLE = '\x1b[?2004h', PASTE_DISABLE = '\x1b[?2004l';
|
|
@@ -63,6 +67,7 @@ const KEYBOARD_EXT_ENABLE = '\x1b[>4;2m' + '\x1b[?1u', KEYBOARD_EXT_DISABLE = '\
|
|
|
63
67
|
async function main() {
|
|
64
68
|
const stdout = process.stdout;
|
|
65
69
|
const stdin = process.stdin;
|
|
70
|
+
installFocusModeExitGuard(stdout); // W4-R3 — see shell/terminal-focus-mode.ts
|
|
66
71
|
const { cli, configManager, bootstrapWorkingDir, bootstrapHomeDirectory } = await prepareShellCliRuntime(process.argv.slice(2), {
|
|
67
72
|
defaultWorkingDirectory: process.env['GOODVIBES_WORKING_DIR'] ?? process.cwd(),
|
|
68
73
|
homeDirectory: process.env['GOODVIBES_AGENT_HOME'] ?? homedir(),
|
|
@@ -154,6 +159,8 @@ async function main() {
|
|
|
154
159
|
|
|
155
160
|
let streamTokenSpeed = 0;
|
|
156
161
|
|
|
162
|
+
const thinkingClock = new ThinkingStallClock(); // W4-R4 thinking-indicator stall clock
|
|
163
|
+
|
|
157
164
|
let scrollTop = 0;
|
|
158
165
|
let scrollLocked = true;
|
|
159
166
|
|
|
@@ -265,7 +272,7 @@ async function main() {
|
|
|
265
272
|
process.removeListener('SIGINT', sigintHandler);
|
|
266
273
|
process.removeListener('unhandledRejection', unhandledRejectionHandler);
|
|
267
274
|
const exitScreen = cli.flags.noAltScreen ? CLEAR_SCREEN : CLEAR_SCREEN + ALT_SCREEN_EXIT;
|
|
268
|
-
allowTerminalWrite(() => stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + CURSOR_SHOW + exitScreen));
|
|
275
|
+
allowTerminalWrite(() => stdout.write(PASTE_DISABLE + KEYBOARD_EXT_DISABLE + MOUSE_DISABLE + FOCUS_DISABLE + CURSOR_SHOW + exitScreen));
|
|
269
276
|
terminalOutputGuard.dispose();
|
|
270
277
|
stdin.setRawMode(false);
|
|
271
278
|
process.exit(0);
|
|
@@ -394,14 +401,11 @@ async function main() {
|
|
|
394
401
|
sidebarOverride = !(sidebarWidthFor(width) > 0);
|
|
395
402
|
render();
|
|
396
403
|
};
|
|
397
|
-
permissionPromptRef.requestPermission = (request) =>
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
};
|
|
403
|
-
render();
|
|
404
|
-
});
|
|
404
|
+
const rawRequestPermission: typeof permissionPromptRef.requestPermission = (request) => new Promise((resolve) => { // W4-R3: see shell/terminal-focus-mode.ts
|
|
405
|
+
pendingPermission = { ...request, resolve: (approved: boolean, remember = false) => resolve({ approved, remember }) };
|
|
406
|
+
render();
|
|
407
|
+
});
|
|
408
|
+
permissionPromptRef.requestPermission = wrapRequestPermissionWithApprovalAlert(rawRequestPermission, { focusTracker: ctx.services.focusTracker });
|
|
405
409
|
|
|
406
410
|
const input: InputHandler = new InputHandler(
|
|
407
411
|
() => render(),
|
|
@@ -428,6 +432,7 @@ async function main() {
|
|
|
428
432
|
tokenAuditor: ctx.services.tokenAuditor,
|
|
429
433
|
replayEngine: ctx.services.replayEngine,
|
|
430
434
|
webhookNotifier: ctx.services.webhookNotifier,
|
|
435
|
+
focusTracker: ctx.services.focusTracker,
|
|
431
436
|
policyRuntimeState: ctx.services.policyRuntimeState,
|
|
432
437
|
externalServices: uiServices.platform.externalServices,
|
|
433
438
|
},
|
|
@@ -531,16 +536,12 @@ async function main() {
|
|
|
531
536
|
provider: runtime.provider,
|
|
532
537
|
contextWindow: currentModel.contextWindow,
|
|
533
538
|
compactThreshold: configManager.get('behavior.autoCompactThreshold') as number,
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
if (Object.values(tools).every(v => v === 'allow')) return true;
|
|
541
|
-
}
|
|
542
|
-
return false;
|
|
543
|
-
})(),
|
|
539
|
+
// Single source of truth for "will this bypass the approval prompt?" —
|
|
540
|
+
// computed the same way cli/status.ts and the policy-explain tool
|
|
541
|
+
// compute it, so the footer can never disagree with them or with the
|
|
542
|
+
// permission gate itself (behavior.autoApprove first, then
|
|
543
|
+
// permissions.mode).
|
|
544
|
+
dangerMode: readApprovalPostureFromConfig(configManager).bypassesPrompts,
|
|
544
545
|
lastInputTokens: orchestrator.lastInputTokens,
|
|
545
546
|
commandArgsHint,
|
|
546
547
|
hitlMode: modeManager.getHITLMode(),
|
|
@@ -597,21 +598,11 @@ async function main() {
|
|
|
597
598
|
scrollTop = conversationViewport.nextScrollTop;
|
|
598
599
|
let viewport = conversationViewport.viewport;
|
|
599
600
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
conversationWidth,
|
|
606
|
-
orchestrator.getSpinner(),
|
|
607
|
-
orchestrator.thinkingFrame,
|
|
608
|
-
showSpeed ? streamTokenSpeed : undefined,
|
|
609
|
-
showPreview ? partialToolPreview : undefined,
|
|
610
|
-
orchestrator.streamingInputTokens > 0 ? orchestrator.streamingInputTokens : undefined,
|
|
611
|
-
orchestrator.streamingOutputTokens > 0 ? orchestrator.streamingOutputTokens : undefined,
|
|
612
|
-
);
|
|
613
|
-
viewport.push(...thinking);
|
|
614
|
-
}
|
|
601
|
+
viewport.push(...buildThinkingOverlay({ // honest waiting state; [] when not thinking
|
|
602
|
+
orchestrator, configManager, streamTokenSpeed, clock: thinkingClock,
|
|
603
|
+
streamToolPreview: sessionSnapshot.streamToolPreview,
|
|
604
|
+
approvalPending: pendingPermission !== null, width: conversationWidth,
|
|
605
|
+
}));
|
|
615
606
|
|
|
616
607
|
if (pendingPermission) {
|
|
617
608
|
viewport.push(...PermissionPromptUI.createPromptLines(conversationWidth, pendingPermission));
|
|
@@ -699,7 +690,13 @@ async function main() {
|
|
|
699
690
|
stdin.setRawMode(true);
|
|
700
691
|
stdin.resume();
|
|
701
692
|
stdin.setEncoding('utf8');
|
|
702
|
-
allowTerminalWrite(() => stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE));
|
|
693
|
+
allowTerminalWrite(() => { markFocusModeEnabled(); return stdout.write((cli.flags.noAltScreen ? '' : ALT_SCREEN_ENTER) + CLEAR_SCREEN + CURSOR_HIDE + MOUSE_ENABLE + KEYBOARD_EXT_ENABLE + PASTE_ENABLE + FOCUS_ENABLE); });
|
|
694
|
+
|
|
695
|
+
// W4-R4: forced dark/light before first paint; auto (TTY) probes + repaints once if light.
|
|
696
|
+
const themeProbe = installStartupThemeProbe({
|
|
697
|
+
configManager, stdout, writeAllowed: allowTerminalWrite,
|
|
698
|
+
resetDiff: () => compositor.resetDiff(), render,
|
|
699
|
+
});
|
|
703
700
|
|
|
704
701
|
applyInitialTuiCliState({
|
|
705
702
|
cli,
|
|
@@ -710,7 +707,10 @@ async function main() {
|
|
|
710
707
|
render,
|
|
711
708
|
});
|
|
712
709
|
|
|
713
|
-
stdin.on('data', (
|
|
710
|
+
stdin.on('data', (raw: string) => {
|
|
711
|
+
// Strip any OSC 11 background-probe reply before the input pipeline sees it.
|
|
712
|
+
const data = themeProbe.filterInput(raw);
|
|
713
|
+
if (data.length === 0) return;
|
|
714
714
|
const blocking = handleBlockingShellInput({
|
|
715
715
|
data,
|
|
716
716
|
pendingPermission,
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { isAutoApproveEnabled } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ApprovalPosture — the single source of truth for "what will happen when a
|
|
6
|
+
* tool runs right now", displayed identically everywhere the Agent surfaces
|
|
7
|
+
* approval posture (cli status, cli doctor, the security policy-explain tool,
|
|
8
|
+
* and the footer's danger indicator).
|
|
9
|
+
*
|
|
10
|
+
* Mirrors PermissionManager.checkDetailed()'s exact precedence (SDK
|
|
11
|
+
* dist/platform/permissions/manager.js:103-138), which this module does not
|
|
12
|
+
* modify — the gate is correct; the historical bug was surfaces disagreeing
|
|
13
|
+
* about what the gate does:
|
|
14
|
+
* 1. behavior.autoApprove === true -> every tool call is approved
|
|
15
|
+
* automatically, before permissions.mode is even read.
|
|
16
|
+
* 2. permissions.mode === 'allow-all' -> every tool call is approved
|
|
17
|
+
* automatically.
|
|
18
|
+
* 3. permissions.mode === 'custom' -> each tool category is allowed,
|
|
19
|
+
* prompted, or denied per its own configured rule; bypassesPrompts is
|
|
20
|
+
* true only when EVERY configured tool category resolves to 'allow'
|
|
21
|
+
* (the only custom configuration where nothing ever prompts).
|
|
22
|
+
* 4. permissions.mode === 'prompt' (the default) -> read-only actions are
|
|
23
|
+
* auto-allowed; write, execute, and delegate actions prompt.
|
|
24
|
+
*
|
|
25
|
+
* Any surface that wants to say "will this prompt me?" must call
|
|
26
|
+
* computeApprovalPosture (or the config-reading convenience below) rather
|
|
27
|
+
* than re-deriving the precedence locally — that re-derivation is exactly
|
|
28
|
+
* how cli/status.ts drifted from the gate (it read permissions.mode alone
|
|
29
|
+
* and never looked at behavior.autoApprove).
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export type ApprovalPostureKind = 'auto-approve' | 'allow-all' | 'custom' | 'prompt';
|
|
33
|
+
|
|
34
|
+
export interface ApprovalPostureInput {
|
|
35
|
+
/** behavior.autoApprove, read via the SDK's isAutoApproveEnabled (or an equivalent duck-typed read). */
|
|
36
|
+
readonly autoApprove: boolean;
|
|
37
|
+
/** permissions.mode, as read from config (may be unknown/malformed input from a loose config source). */
|
|
38
|
+
readonly mode: unknown;
|
|
39
|
+
/**
|
|
40
|
+
* permissions.tools, only consulted when mode === 'custom'. Values are the
|
|
41
|
+
* per-tool-category actions ('allow' | 'prompt' | 'deny'); anything else
|
|
42
|
+
* (missing/unrecognized) counts as NOT allow for the bypass computation.
|
|
43
|
+
*/
|
|
44
|
+
readonly customTools?: Readonly<Record<string, unknown>>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ApprovalPosture {
|
|
48
|
+
/** Which precedence branch produced this posture. */
|
|
49
|
+
readonly kind: ApprovalPostureKind;
|
|
50
|
+
/** The raw permissions.mode value, normalized to a known mode string (defaults to 'prompt'). */
|
|
51
|
+
readonly mode: 'prompt' | 'allow-all' | 'custom';
|
|
52
|
+
/** The raw behavior.autoApprove value that drove this posture. */
|
|
53
|
+
readonly autoApprove: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* True when NO tool call will ever hit a Human-in-the-Loop prompt under the
|
|
56
|
+
* current configuration — mirrors the gate's "auto-approve everything"
|
|
57
|
+
* branches (autoApprove, allow-all, and custom-all-allow).
|
|
58
|
+
*/
|
|
59
|
+
readonly bypassesPrompts: boolean;
|
|
60
|
+
/** A short, honest, human-facing label — always names auto-approve explicitly when it is what is actually gating tool calls. */
|
|
61
|
+
readonly label: string;
|
|
62
|
+
/** A one-sentence explanation of the mechanism, suitable for a doctor/status detail line. */
|
|
63
|
+
readonly detail: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function normalizeMode(mode: unknown): 'prompt' | 'allow-all' | 'custom' {
|
|
67
|
+
if (mode === 'allow-all' || mode === 'custom') return mode;
|
|
68
|
+
return 'prompt';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Pure precedence computation — no config access. Every display surface
|
|
73
|
+
* should route its "what is the approval posture" question through this
|
|
74
|
+
* function so they provably agree with each other and with the gate.
|
|
75
|
+
*/
|
|
76
|
+
export function computeApprovalPosture(input: ApprovalPostureInput): ApprovalPosture {
|
|
77
|
+
const mode = normalizeMode(input.mode);
|
|
78
|
+
|
|
79
|
+
if (input.autoApprove) {
|
|
80
|
+
return {
|
|
81
|
+
kind: 'auto-approve',
|
|
82
|
+
mode,
|
|
83
|
+
autoApprove: true,
|
|
84
|
+
bypassesPrompts: true,
|
|
85
|
+
label: 'Auto-approve ON — powerful actions run without asking',
|
|
86
|
+
detail: 'behavior.autoApprove is enabled: every tool call is approved automatically, regardless of permissions.mode or any custom per-tool rule.',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (mode === 'allow-all') {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'allow-all',
|
|
93
|
+
mode,
|
|
94
|
+
autoApprove: false,
|
|
95
|
+
bypassesPrompts: true,
|
|
96
|
+
label: 'Allow everything',
|
|
97
|
+
detail: 'permissions.mode is allow-all: every tool call is approved automatically.',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (mode === 'custom') {
|
|
102
|
+
const values = Object.values(input.customTools ?? {});
|
|
103
|
+
const allAllow = values.length > 0 && values.every((value) => value === 'allow');
|
|
104
|
+
return {
|
|
105
|
+
kind: 'custom',
|
|
106
|
+
mode,
|
|
107
|
+
autoApprove: false,
|
|
108
|
+
bypassesPrompts: allAllow,
|
|
109
|
+
label: allAllow ? 'Custom rules (every category allows — no prompts)' : 'Custom rules',
|
|
110
|
+
detail: allAllow
|
|
111
|
+
? 'permissions.mode is custom and every configured tool category is set to allow: no tool call prompts.'
|
|
112
|
+
: 'permissions.mode is custom: each tool category is allowed, prompted, or denied per its own configured rule.',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
kind: 'prompt',
|
|
118
|
+
mode: 'prompt',
|
|
119
|
+
autoApprove: false,
|
|
120
|
+
bypassesPrompts: false,
|
|
121
|
+
label: 'Ask before powerful actions',
|
|
122
|
+
detail: 'permissions.mode is prompt (default): read-only actions are auto-allowed; write, execute, and delegate actions prompt for approval.',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Convenience for callers holding a real ConfigManager (or any subset
|
|
128
|
+
* exposing `get` + `getCategory`): reads behavior.autoApprove and
|
|
129
|
+
* permissions.mode/tools the same way the gate does and computes the posture.
|
|
130
|
+
*/
|
|
131
|
+
export function readApprovalPostureFromConfig(
|
|
132
|
+
configManager: Pick<ConfigManager, 'get' | 'getCategory'>,
|
|
133
|
+
): ApprovalPosture {
|
|
134
|
+
const autoApprove = isAutoApproveEnabled(configManager);
|
|
135
|
+
const permissions = configManager.getCategory('permissions');
|
|
136
|
+
return computeApprovalPosture({
|
|
137
|
+
autoApprove,
|
|
138
|
+
mode: permissions.mode,
|
|
139
|
+
customTools: { ...permissions.tools },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -28,6 +28,14 @@ function formatMegabytes(bytes: number): string {
|
|
|
28
28
|
return `${Math.round(bytes / (1024 * 1024))} MB`;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// W4-A6: the memory/routine counts below are re-derived live on every
|
|
32
|
+
// repaint (AgentWorkspace.syncLiveCountersForRender). If that live read
|
|
33
|
+
// fails, liveCountersStale flags it so we say so instead of asserting a
|
|
34
|
+
// number the disk might already contradict.
|
|
35
|
+
function liveCounterStaleSuffix(snapshot: AgentWorkspaceRuntimeSnapshot): string {
|
|
36
|
+
return snapshot.liveCountersStale ? ' (refreshing...)' : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
31
39
|
export function compactText(text: string, maxWidth = 104): string {
|
|
32
40
|
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
33
41
|
if (normalized.length === 0) return '';
|
|
@@ -546,7 +554,7 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
|
|
|
546
554
|
...snapshot.localRoutines.filter((item) => item.enabled),
|
|
547
555
|
].filter((item) => item.reviewState !== 'reviewed').length;
|
|
548
556
|
base.push(
|
|
549
|
-
{ text: `Memory: ${snapshot.localMemoryCount}; prompt ${snapshot.localMemoryPromptActiveCount}; queue ${snapshot.localMemoryReviewQueueCount}; session ${snapshot.sessionMemoryCount}
|
|
557
|
+
{ text: `Memory: ${snapshot.localMemoryCount}; prompt ${snapshot.localMemoryPromptActiveCount}; queue ${snapshot.localMemoryReviewQueueCount}; session ${snapshot.sessionMemoryCount}.${liveCounterStaleSuffix(snapshot)}`, fg: PALETTE.info },
|
|
550
558
|
{ text: `Notes: ${snapshot.localNoteCount}; skills ${snapshot.localSkillCount}/${snapshot.enabledSkillCount}; routines ${snapshot.localRoutineCount}/${snapshot.enabledRoutineCount}; personas ${snapshot.localPersonaCount}.`, fg: PALETTE.info },
|
|
551
559
|
{ text: `Learning curator: memory queue ${snapshot.localMemoryReviewQueueCount}; note queue ${snapshot.localNoteReviewQueueCount}; setup gaps ${behaviorNeedsSetup}; injected review ${injectedNeedsReview}.`, fg: behaviorNeedsSetup > 0 || injectedNeedsReview > 0 ? PALETTE.warn : PALETTE.good },
|
|
552
560
|
{ text: 'Prompt plan: score reviewed context, show suppressed records, and route review before behavior expands.', fg: PALETTE.good },
|
|
@@ -581,7 +589,7 @@ export function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspac
|
|
|
581
589
|
const needsSetup = routinesNeedingSetup(snapshot);
|
|
582
590
|
const needsReview = routinesNeedingReview(snapshot);
|
|
583
591
|
base.push(
|
|
584
|
-
{ text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
592
|
+
{ text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}${liveCounterStaleSuffix(snapshot)}`, fg: PALETTE.info },
|
|
585
593
|
{ text: `Schedule-ready routines: ${ready.length}; setup gaps: ${needsSetup.length}; review needed: ${needsReview.length}`, fg: needsSetup.length > 0 || needsReview.length > 0 ? PALETTE.warn : PALETTE.good },
|
|
586
594
|
routineNextActionLine(snapshot),
|
|
587
595
|
compactRoutineReceiptLine(snapshot),
|
|
@@ -4,6 +4,8 @@ import { type Line, createEmptyCell, createEmptyLine, createStyledCell } from '.
|
|
|
4
4
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
5
5
|
import type { SearchManager } from '../input/search.ts';
|
|
6
6
|
import { allowTerminalWrite } from '../runtime/terminal-output-guard.ts';
|
|
7
|
+
import { probeTermCaps, type TermColorCaps } from './term-caps.ts';
|
|
8
|
+
import { activeTheme } from './theme.ts';
|
|
7
9
|
|
|
8
10
|
export interface SelectionInfo {
|
|
9
11
|
isCellSelected: (col: number, absoluteRow: number) => boolean;
|
|
@@ -43,9 +45,24 @@ export class Compositor {
|
|
|
43
45
|
/** Double-buffer reuse: back is written, front is the last-rendered reference. */
|
|
44
46
|
private frontBuffer: TerminalBuffer | null = null;
|
|
45
47
|
private backBuffer: TerminalBuffer | null = null;
|
|
46
|
-
private
|
|
48
|
+
private readonly caps: TermColorCaps;
|
|
49
|
+
private diffEngine: DiffEngine;
|
|
50
|
+
|
|
51
|
+
constructor(private stdout: NodeJS.WriteStream) {
|
|
52
|
+
// Probe terminal color capabilities once at construction time so the
|
|
53
|
+
// DiffEngine downsamples every emitted SGR to the terminal's real level.
|
|
54
|
+
// The hardcoded truecolor search-highlight hex below (and any future theme
|
|
55
|
+
// colors) is therefore cap-gated — no raw #rrggbb leaks on a non-truecolor
|
|
56
|
+
// terminal. (R4 later replaces the hardcoded hex with live activeTheme()
|
|
57
|
+
// reads in its tone-read region; this R2 region owns only the caps wiring.)
|
|
58
|
+
this.caps = probeTermCaps(stdout);
|
|
59
|
+
this.diffEngine = new DiffEngine(this.caps);
|
|
60
|
+
}
|
|
47
61
|
|
|
48
|
-
|
|
62
|
+
/** Exposed for unit tests — returns the detected color capability. */
|
|
63
|
+
public get termCapsForTest(): TermColorCaps {
|
|
64
|
+
return this.caps;
|
|
65
|
+
}
|
|
49
66
|
|
|
50
67
|
/** Exposed for unit tests — returns the last composited buffer. */
|
|
51
68
|
public get lastBufferForTest(): TerminalBuffer | null {
|
|
@@ -86,6 +103,12 @@ export class Compositor {
|
|
|
86
103
|
const lineCount = selection?.lineCount ?? 0;
|
|
87
104
|
const offset = Math.max(0, vHeight - lineCount);
|
|
88
105
|
|
|
106
|
+
// R4 tone-read region (the compositor is the pre-ruled R2→R4 shared file;
|
|
107
|
+
// R2 owns the DiffEngine caps wiring above, R4 owns these live theme reads).
|
|
108
|
+
// Read the search-highlight tones live per frame so they flip in light mode;
|
|
109
|
+
// dark is byte-identical (the searchCurrent/searchMatch tones resolve to the
|
|
110
|
+
// prior hardcoded yellow/gold pair). The separator stays a neutral dim grey.
|
|
111
|
+
const T = activeTheme();
|
|
89
112
|
const sepFg = '238';
|
|
90
113
|
|
|
91
114
|
viewport.forEach((line, i) => {
|
|
@@ -147,9 +170,9 @@ export class Compositor {
|
|
|
147
170
|
const isCurrent = search.manager.isCurrentMatch(absoluteRow, match.col);
|
|
148
171
|
for (let x = match.col; x < match.col + match.length && x < leftWidth; x++) {
|
|
149
172
|
if (isCurrent) {
|
|
150
|
-
newBuffer.setCell(x, screenY, { bg:
|
|
173
|
+
newBuffer.setCell(x, screenY, { bg: T.searchCurrentBg, fg: T.searchCurrentFg, bold: true, dim: false });
|
|
151
174
|
} else {
|
|
152
|
-
newBuffer.setCell(x, screenY, { bg:
|
|
175
|
+
newBuffer.setCell(x, screenY, { bg: T.searchMatchBg, fg: T.searchMatchFg, bold: false, dim: false });
|
|
153
176
|
}
|
|
154
177
|
}
|
|
155
178
|
}
|
package/src/renderer/diff.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { TerminalBuffer } from './buffer.ts';
|
|
2
2
|
import { type Cell } from '../types/grid.ts';
|
|
3
|
+
import {
|
|
4
|
+
type TermColorCaps,
|
|
5
|
+
downsampleColor,
|
|
6
|
+
wrapSynced,
|
|
7
|
+
} from './term-caps.ts';
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* DiffEngine - Generates minimal ANSI updates between two buffers.
|
|
11
|
+
*
|
|
12
|
+
* Accepts a TermColorCaps probe result so that color sequences are
|
|
13
|
+
* downsampled to the terminal's actual capability level (truecolor /
|
|
14
|
+
* ansi256 / basic16 / none), and frames are wrapped in DEC 2026
|
|
15
|
+
* synchronized-output markers when supported.
|
|
6
16
|
*/
|
|
7
17
|
export class DiffEngine {
|
|
8
18
|
private lastFg = '';
|
|
@@ -13,6 +23,11 @@ export class DiffEngine {
|
|
|
13
23
|
private lastItalic = false;
|
|
14
24
|
private lastStrikethrough = false;
|
|
15
25
|
private lastLink = '';
|
|
26
|
+
private caps: TermColorCaps;
|
|
27
|
+
|
|
28
|
+
constructor(caps: TermColorCaps = { capability: 'truecolor', syncedOutput: true }) {
|
|
29
|
+
this.caps = caps;
|
|
30
|
+
}
|
|
16
31
|
|
|
17
32
|
public reset(): void {
|
|
18
33
|
this.lastFg = '';
|
|
@@ -56,7 +71,7 @@ export class DiffEngine {
|
|
|
56
71
|
this.lastLink = '';
|
|
57
72
|
}
|
|
58
73
|
|
|
59
|
-
return output;
|
|
74
|
+
return wrapSynced(output, this.caps);
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
private isCellDifferent(a: Cell | undefined, b: Cell): boolean {
|
|
@@ -66,6 +81,11 @@ export class DiffEngine {
|
|
|
66
81
|
(a.link ?? '') !== (b.link ?? '');
|
|
67
82
|
}
|
|
68
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Convert a raw color string (hex or r;g;b) to the "r;g;b" form used for
|
|
86
|
+
* change-detection against lastFg/lastBg. Palette indices pass through as-is.
|
|
87
|
+
* Capability downsampling happens in applyStyles via downsampleColor.
|
|
88
|
+
*/
|
|
69
89
|
private sanitizeColor(color: string): string {
|
|
70
90
|
if (color.startsWith('#')) {
|
|
71
91
|
const r = parseInt(color.slice(1, 3), 16);
|
|
@@ -77,7 +97,7 @@ export class DiffEngine {
|
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
private applyStyles(cell: Cell): string {
|
|
80
|
-
|
|
100
|
+
// Normalize hex → r;g;b (for change-detection against lastFg/lastBg)
|
|
81
101
|
const fg = this.sanitizeColor(cell.fg);
|
|
82
102
|
const bg = this.sanitizeColor(cell.bg);
|
|
83
103
|
const link = cell.link ?? '';
|
|
@@ -87,21 +107,40 @@ export class DiffEngine {
|
|
|
87
107
|
cell.underline !== this.lastUnderline || cell.italic !== this.lastItalic ||
|
|
88
108
|
cell.strikethrough !== this.lastStrikethrough;
|
|
89
109
|
|
|
110
|
+
let style = '';
|
|
111
|
+
|
|
90
112
|
if (changed) {
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const isRgb = fg.includes(';');
|
|
100
|
-
style += isRgb ? `\x1b[38;2;${fg}m` : `\x1b[38;5;${fg}m`;
|
|
113
|
+
// Reset all attributes first (only when the terminal renders color/SGR).
|
|
114
|
+
if (this.caps.capability !== 'none') {
|
|
115
|
+
style += '\x1b[0m';
|
|
116
|
+
if (cell.bold) style += '\x1b[1m';
|
|
117
|
+
if (cell.dim) style += '\x1b[2m';
|
|
118
|
+
if (cell.italic) style += '\x1b[3m';
|
|
119
|
+
if (cell.underline) style += '\x1b[4m';
|
|
120
|
+
if (cell.strikethrough) style += '\x1b[9m';
|
|
101
121
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
|
|
123
|
+
// Foreground color — capability-downsampled (no raw hex leak on a
|
|
124
|
+
// truecolor-incapable terminal; null → emit nothing).
|
|
125
|
+
const fgOut = downsampleColor(fg, this.caps, 'fg');
|
|
126
|
+
if (fgOut !== null) {
|
|
127
|
+
if (this.caps.capability === 'basic16') {
|
|
128
|
+
style += `\x1b[${fgOut}m`;
|
|
129
|
+
} else {
|
|
130
|
+
const isRgb = fgOut.includes(';');
|
|
131
|
+
style += isRgb ? `\x1b[38;2;${fgOut}m` : `\x1b[38;5;${fgOut}m`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Background color — capability-downsampled
|
|
136
|
+
const bgOut = downsampleColor(bg, this.caps, 'bg');
|
|
137
|
+
if (bgOut !== null) {
|
|
138
|
+
if (this.caps.capability === 'basic16') {
|
|
139
|
+
style += `\x1b[${bgOut}m`;
|
|
140
|
+
} else {
|
|
141
|
+
const isRgb = bgOut.includes(';');
|
|
142
|
+
style += isRgb ? `\x1b[48;2;${bgOut}m` : `\x1b[48;5;${bgOut}m`;
|
|
143
|
+
}
|
|
105
144
|
}
|
|
106
145
|
|
|
107
146
|
this.lastFg = fg;
|
|
@@ -113,16 +152,20 @@ export class DiffEngine {
|
|
|
113
152
|
this.lastStrikethrough = cell.strikethrough;
|
|
114
153
|
}
|
|
115
154
|
|
|
116
|
-
// OSC 8 hyperlink: emit open/close/change sequences only when link changes
|
|
117
|
-
|
|
155
|
+
// OSC 8 hyperlink: emit open/close/change sequences only when link changes.
|
|
156
|
+
// Suppressed in no-color mode (dumb/no-color terminals cannot render them).
|
|
157
|
+
if (link !== this.lastLink && this.caps.capability !== 'none') {
|
|
118
158
|
if (link) {
|
|
119
159
|
// Open new hyperlink (close previous if any was open)
|
|
120
160
|
style += `\x1b]8;;${link}\x1b\\`;
|
|
121
161
|
} else {
|
|
122
162
|
// Close hyperlink
|
|
123
|
-
style +=
|
|
163
|
+
style += '\x1b]8;;\x1b\\';
|
|
124
164
|
}
|
|
125
165
|
this.lastLink = link;
|
|
166
|
+
} else if (link !== this.lastLink) {
|
|
167
|
+
// capability === 'none': track state but emit nothing
|
|
168
|
+
this.lastLink = link;
|
|
126
169
|
}
|
|
127
170
|
|
|
128
171
|
return style;
|
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine, createStyledCell } from '../types/grid.ts';
|
|
3
3
|
import { getDisplayWidth } from '../utils/terminal-width.ts';
|
|
4
|
-
import { GLYPHS
|
|
4
|
+
import { GLYPHS } from './ui-primitives.ts';
|
|
5
|
+
import { activeUiTones, registerThemeRefresh } from './theme.ts';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
// Built from the mode-resolved chrome tones (activeUiTones) and rebuilt IN PLACE
|
|
8
|
+
// on a mode flip via the registered refresher (read by reference across the
|
|
9
|
+
// fullscreen workspace surfaces). This is an OPAQUE dark panel: fg/bg roles stay
|
|
10
|
+
// dark in the SDK light tones (only state.* flips), so dark is byte-identical.
|
|
11
|
+
// title / categoryBg / contextBg / controlsBg are agent-local fullscreen values
|
|
12
|
+
// with no shared tone equivalent — kept as-is (opaque dark panel surfaces).
|
|
13
|
+
function buildFullscreenPalette(): Record<string, string> {
|
|
14
|
+
const t = activeUiTones();
|
|
15
|
+
return {
|
|
16
|
+
border: t.border,
|
|
17
|
+
title: '#67e8f9',
|
|
18
|
+
subtitle: t.accent.conversation,
|
|
19
|
+
text: t.fg.primary,
|
|
20
|
+
muted: t.fg.muted,
|
|
21
|
+
dim: t.border,
|
|
22
|
+
selectedBg: t.bg.selected,
|
|
23
|
+
categoryBg: '#141b25',
|
|
24
|
+
contextBg: '#121923',
|
|
25
|
+
controlsBg: '#0f141d',
|
|
26
|
+
footerBg: t.bg.footer,
|
|
27
|
+
good: t.state.good,
|
|
28
|
+
warn: t.state.warn,
|
|
29
|
+
bad: t.state.bad,
|
|
30
|
+
info: t.state.info,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const FULLSCREEN_PALETTE = buildFullscreenPalette() as {
|
|
35
|
+
readonly border: string; readonly title: string; readonly subtitle: string;
|
|
36
|
+
readonly text: string; readonly muted: string; readonly dim: string;
|
|
37
|
+
readonly selectedBg: string; readonly categoryBg: string; readonly contextBg: string;
|
|
38
|
+
readonly controlsBg: string; readonly footerBg: string; readonly good: string;
|
|
39
|
+
readonly warn: string; readonly bad: string; readonly info: string;
|
|
40
|
+
};
|
|
41
|
+
registerThemeRefresh(() => Object.assign(FULLSCREEN_PALETTE as Record<string, string>, buildFullscreenPalette()));
|
|
23
42
|
|
|
24
43
|
export type FullscreenTextStyle = Partial<Omit<Line[number], 'char'>>;
|
|
25
44
|
|