@pellux/goodvibes-tui 0.26.0 → 0.27.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +12 -7
  3. package/docs/foundation-artifacts/operator-contract.json +4 -1
  4. package/package.json +2 -2
  5. package/src/cli/bundle-command.ts +2 -1
  6. package/src/cli/service-posture.ts +2 -1
  7. package/src/core/conversation-rendering.ts +2 -2
  8. package/src/core/conversation.ts +49 -2
  9. package/src/core/session-recovery.ts +24 -18
  10. package/src/core/system-message-router.ts +42 -26
  11. package/src/core/turn-event-wiring.ts +12 -16
  12. package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
  13. package/src/daemon/handlers/inbox/index.ts +2 -1
  14. package/src/daemon/handlers/inbox/poller.ts +2 -1
  15. package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
  16. package/src/daemon/handlers/inbox/providers/email.ts +2 -1
  17. package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
  18. package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
  19. package/src/daemon/handlers/triage/integration.ts +2 -1
  20. package/src/daemon/handlers/triage/pipeline.ts +2 -1
  21. package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
  22. package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
  23. package/src/export/gist-uploader.ts +3 -1
  24. package/src/input/command-registry.ts +1 -0
  25. package/src/input/commands/cloudflare-runtime.ts +2 -1
  26. package/src/input/commands/guidance-runtime.ts +2 -4
  27. package/src/input/commands/health-runtime.ts +13 -7
  28. package/src/input/commands/knowledge.ts +2 -1
  29. package/src/input/commands/runtime-services.ts +40 -10
  30. package/src/input/handler-command-route.ts +1 -1
  31. package/src/input/handler-interactions.ts +2 -1
  32. package/src/input/handler-modal-routes.ts +2 -1
  33. package/src/input/handler-onboarding-cloudflare.ts +2 -1
  34. package/src/input/handler-onboarding.ts +8 -8
  35. package/src/input/handler-ui-state.ts +1 -1
  36. package/src/input/tts-settings-actions.ts +2 -1
  37. package/src/main.ts +52 -59
  38. package/src/panels/agent-inspector-panel.ts +72 -2
  39. package/src/panels/agent-logs-panel.ts +127 -19
  40. package/src/panels/base-panel.ts +2 -1
  41. package/src/panels/builtin/agent.ts +3 -1
  42. package/src/panels/builtin/development.ts +1 -0
  43. package/src/panels/builtin/session.ts +1 -1
  44. package/src/panels/context-visualizer-panel.ts +24 -14
  45. package/src/panels/cost-tracker-panel.ts +7 -13
  46. package/src/panels/debug-panel.ts +11 -22
  47. package/src/panels/docs-panel.ts +14 -24
  48. package/src/panels/file-explorer-panel.ts +2 -1
  49. package/src/panels/file-preview-panel.ts +2 -1
  50. package/src/panels/git-panel.ts +10 -17
  51. package/src/panels/marketplace-panel.ts +2 -1
  52. package/src/panels/memory-panel.ts +2 -1
  53. package/src/panels/project-planning-panel.ts +5 -4
  54. package/src/panels/provider-health-panel.ts +56 -67
  55. package/src/panels/schedule-panel.ts +4 -7
  56. package/src/panels/session-browser-panel.ts +13 -21
  57. package/src/panels/skills-panel.ts +2 -1
  58. package/src/panels/tasks-panel.ts +2 -7
  59. package/src/panels/thinking-panel.ts +6 -11
  60. package/src/panels/token-budget-panel.ts +31 -15
  61. package/src/panels/tool-inspector-panel.ts +10 -18
  62. package/src/panels/work-plan-panel.ts +2 -1
  63. package/src/panels/wrfc-panel.ts +37 -35
  64. package/src/renderer/agent-detail-modal.ts +2 -2
  65. package/src/renderer/modal-utils.ts +0 -10
  66. package/src/renderer/process-modal.ts +2 -2
  67. package/src/runtime/bootstrap-command-context.ts +3 -0
  68. package/src/runtime/bootstrap-command-parts.ts +3 -1
  69. package/src/runtime/bootstrap-core.ts +31 -31
  70. package/src/runtime/bootstrap-shell.ts +1 -0
  71. package/src/runtime/onboarding/apply.ts +4 -3
  72. package/src/runtime/onboarding/snapshot.ts +4 -3
  73. package/src/runtime/process-lifecycle.ts +195 -0
  74. package/src/runtime/services.ts +4 -1
  75. package/src/runtime/wrfc-persistence.ts +20 -5
  76. package/src/shell/ui-openers.ts +3 -2
  77. package/src/verification/live-verifier.ts +4 -3
  78. package/src/version.ts +1 -1
  79. package/src/core/context-auto-compact.ts +0 -110
@@ -25,6 +25,7 @@ import { digestSender, toBodyPreview, toSubjectPreview } from '../mapping.ts';
25
25
  import { resolveRouteId } from './route-util.ts';
26
26
  import { ImapClient } from './imap-client.ts';
27
27
  import type { ImapConfig, ImapEnvelope } from './imap-client.ts';
28
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
28
29
 
29
30
  export const EMAIL_PROVIDER_ID = 'email';
30
31
  export const EMAIL_HOST_KEY = 'surfaces.email.imapHost';
@@ -147,5 +148,5 @@ function unavailable(error: string): ProviderPollResult {
147
148
  }
148
149
 
149
150
  function errMsg(error: unknown): string {
150
- return error instanceof Error ? error.message : String(error);
151
+ return summarizeError(error);
151
152
  }
@@ -3,6 +3,7 @@
3
3
  // surface) so a route lookup can never crash a provider poll.
4
4
 
5
5
  import type { AdapterContext, InboundChannelItem } from '../provider-adapter.ts';
6
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
7
 
7
8
  export async function resolveRouteId(
8
9
  ctx: AdapterContext,
@@ -16,7 +17,7 @@ export async function resolveRouteId(
16
17
  } catch (error) {
17
18
  ctx.logger.warn('route resolution failed', {
18
19
  provider,
19
- error: error instanceof Error ? error.message : String(error),
20
+ error: summarizeError(error),
20
21
  });
21
22
  return undefined;
22
23
  }
@@ -30,6 +30,7 @@ import type {
30
30
  import { POLL_CADENCE_MS } from '../provider-adapter.ts';
31
31
  import { digestSender, toBodyPreview, toSubjectPreview } from '../mapping.ts';
32
32
  import { resolveRouteId } from './route-util.ts';
33
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
33
34
 
34
35
  const SLACK_API = 'https://slack.com/api';
35
36
  export const SLACK_PROVIDER_ID = 'slack';
@@ -258,5 +259,5 @@ function unavailable(error: string): ProviderPollResult {
258
259
  }
259
260
 
260
261
  function errMsg(error: unknown): string {
261
- return error instanceof Error ? error.message : String(error);
262
+ return summarizeError(error);
262
263
  }
@@ -47,6 +47,7 @@ import {
47
47
  type TriageTagger,
48
48
  type TriageTaggerOptions,
49
49
  } from './tagger/index.ts';
50
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
50
51
 
51
52
  /**
52
53
  * Canonical id of the inbox list method whose handler we decorate. This is the
@@ -151,7 +152,7 @@ function withInboxEnrichment(ctx: HandlerContext): EnrichmentProxy {
151
152
  // Triage is best-effort: a missing/locked store must never break the
152
153
  // read-only inbox feed. Log and return the un-enriched result.
153
154
  ctx.logger.warn('triage: inbox enrichment skipped', {
154
- message: error instanceof Error ? error.message : String(error),
155
+ message: summarizeError(error),
155
156
  });
156
157
  return result;
157
158
  }
@@ -23,6 +23,7 @@ import {
23
23
  type TriageScore,
24
24
  type TriageScorerOptions,
25
25
  } from './scorer.ts';
26
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
26
27
 
27
28
  export const TRIAGE_STORE_FILE = 'inbox-triage.sqlite';
28
29
 
@@ -158,7 +159,7 @@ export async function runInboxTriage(
158
159
  if (ownsStore) await store.save();
159
160
  } catch (error) {
160
161
  ctx.logger.error('triage: failed to persist scores', {
161
- message: error instanceof Error ? error.message : String(error),
162
+ message: summarizeError(error),
162
163
  });
163
164
  throw error;
164
165
  } finally {
@@ -19,6 +19,7 @@ import { HandlerError } from '../../errors.ts';
19
19
  import type { InboundChannelItem } from '../types.ts';
20
20
  import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
21
21
  import { discordEmojiForTag, stringMeta } from './shared.ts';
22
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
23
 
23
24
  export async function applyDiscord(
24
25
  item: InboundChannelItem,
@@ -179,7 +180,7 @@ async function fetchDiscordAppliedTags(
179
180
  return current.filter((t): t is string => typeof t === 'string');
180
181
  } catch (error) {
181
182
  ctx.logger.warn('triage: discord thread tag read errored', {
182
- message: error instanceof Error ? error.message : String(error),
183
+ message: summarizeError(error),
183
184
  });
184
185
  return null;
185
186
  }
@@ -14,6 +14,7 @@ import type { DaemonCredentialStore } from '../../credentials.ts';
14
14
  import type { InboundChannelItem } from '../types.ts';
15
15
  import type { ApplyTagsResult, TaggerProviderConfig } from './shared.ts';
16
16
  import { imapKeywordForTag } from './shared.ts';
17
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
17
18
 
18
19
  export interface ImapStoreArgs {
19
20
  host: string;
@@ -227,7 +228,7 @@ export function imapStoreFlagOverTls(
227
228
  reject(
228
229
  err instanceof ImapStoreError
229
230
  ? err
230
- : new ImapStoreError(err instanceof Error ? err.message : String(err), false),
231
+ : new ImapStoreError(summarizeError(err), false),
231
232
  );
232
233
  return;
233
234
  }
@@ -288,7 +289,7 @@ export function imapStoreFlagOverTls(
288
289
  finish(
289
290
  err instanceof ImapStoreError
290
291
  ? err
291
- : new ImapStoreError(err instanceof Error ? err.message : String(err), false),
292
+ : new ImapStoreError(summarizeError(err), false),
292
293
  ),
293
294
  );
294
295
  return;
@@ -318,7 +319,7 @@ export function imapStoreFlagOverTls(
318
319
  };
319
320
 
320
321
  socket.on('error', (err) =>
321
- finish(new ImapStoreError(err instanceof Error ? err.message : String(err), true)),
322
+ finish(new ImapStoreError(summarizeError(err), true)),
322
323
  );
323
324
  socket.on('close', () => {
324
325
  if (completed) finish();
@@ -16,6 +16,8 @@
16
16
  // the URL can view it).
17
17
  // ---------------------------------------------------------------------------
18
18
 
19
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
20
+
19
21
  export type UploadResult =
20
22
  | { ok: true; url: string }
21
23
  | { ok: false; error: string };
@@ -96,7 +98,7 @@ export class GistUploadTarget implements UploadTarget {
96
98
  body,
97
99
  });
98
100
  } catch (fetchErr: unknown) {
99
- const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
101
+ const msg = summarizeError(fetchErr);
100
102
  return { ok: false, error: `Network error: ${msg}` };
101
103
  }
102
104
 
@@ -139,6 +139,7 @@ export interface CommandSessionServices {
139
139
  readonly sessionManager?: import('@pellux/goodvibes-sdk/platform/sessions').SessionManager;
140
140
  readonly sessionMemoryStore?: import('@pellux/goodvibes-sdk/platform/core').SessionMemoryStore;
141
141
  readonly sessionLineageTracker?: import('@pellux/goodvibes-sdk/platform/core').SessionLineageTracker;
142
+ readonly wrfcController?: import('@pellux/goodvibes-sdk/platform/agents').WrfcController;
142
143
  readonly changeTracker?: import('@pellux/goodvibes-sdk/platform/sessions').SessionChangeTracker;
143
144
  }
144
145
 
@@ -11,6 +11,7 @@ import {
11
11
  } from '../../runtime/cloudflare-control-plane.ts';
12
12
  import type { CommandContext, CommandRegistry } from '../command-registry.ts';
13
13
  import { requireShellPaths } from './runtime-services.ts';
14
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
14
15
 
15
16
  interface ParsedCloudflareArgs {
16
17
  readonly positional: readonly string[];
@@ -366,5 +367,5 @@ function formatCloudflareError(error: unknown): string {
366
367
  if (error instanceof CloudflareDaemonRouteError) {
367
368
  return `${error.message} (HTTP ${error.status}, ${error.code})`;
368
369
  }
369
- return error instanceof Error ? error.message : String(error);
370
+ return summarizeError(error);
370
371
  }
@@ -2,7 +2,7 @@ import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core'
2
2
  import { evaluateSessionMaintenance, formatSessionMaintenanceLines, getGuidanceMode } from '@/runtime/index.ts';
3
3
  import { dismissGuidance, evaluateContextualGuidance, formatGuidanceItems, resetGuidance } from '@/runtime/index.ts';
4
4
  import type { CommandRegistry } from '../command-registry.ts';
5
- import { requireProviderApi, requireReadModels, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
5
+ import { requireReadModels, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
6
6
 
7
7
  export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void {
8
8
  registry.register({
@@ -68,8 +68,6 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
68
68
  return;
69
69
  }
70
70
 
71
- const providerApi = requireProviderApi(ctx);
72
- const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
73
71
  const llmMessages = ctx.session.conversationManager.getMessagesForLLM();
74
72
  const readModels = requireReadModels(ctx);
75
73
  const session = readModels.session.getSnapshot();
@@ -80,7 +78,7 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
80
78
  const maintenance = evaluateSessionMaintenance({
81
79
  configManager: ctx.platform.configManager,
82
80
  currentTokens: estimateConversationTokens(llmMessages),
83
- contextWindow: currentModel?.contextWindow ?? 0,
81
+ contextWindow: ctx.provider.providerRegistry.getContextWindowForModel(ctx.provider.providerRegistry.getCurrentModel()),
84
82
  messageCount: llmMessages.length,
85
83
  sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
86
84
  session: session.session,
@@ -13,7 +13,6 @@ import {
13
13
  openCommandPanel,
14
14
  requireLocalUserAuthManager,
15
15
  requireOperatorClient,
16
- requireProviderApi,
17
16
  requireReadModels,
18
17
  requireSecretsManager,
19
18
  requireServiceRegistry,
@@ -240,15 +239,19 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
240
239
 
241
240
  if (sub === 'maintenance') {
242
241
  const session = readModels.session.getSnapshot();
243
- const providerApi = requireProviderApi(ctx);
244
- const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
242
+ const providerRegistry = ctx.provider.providerRegistry;
243
+ // Resolve the context window the same way the Tokens panel does so the
244
+ // maintenance usage %/remaining agree across every diagnostics surface.
245
+ const contextWindow = providerRegistry.getContextWindowForModel(
246
+ providerRegistry.getCurrentModel(),
247
+ );
245
248
  const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
246
249
  ? ctx.session.conversationManager.getMessagesForLLM()
247
250
  : [];
248
251
  const maintenance = evaluateSessionMaintenance({
249
252
  configManager: ctx.platform.configManager,
250
253
  currentTokens: estimateConversationTokens(llmMessages),
251
- contextWindow: currentModel?.contextWindow ?? 0,
254
+ contextWindow,
252
255
  messageCount: llmMessages.length,
253
256
  sessionMemoryCount: requireSessionMemoryStore(ctx).list().length,
254
257
  session: session.session,
@@ -386,12 +389,15 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
386
389
  }
387
390
 
388
391
  const session = readModels.session.getSnapshot();
389
- const providerApi = requireProviderApi(ctx);
390
- const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
392
+ const providerRegistry = ctx.provider.providerRegistry;
391
393
  const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
392
394
  ? ctx.session.conversationManager.getMessagesForLLM()
393
395
  : [];
394
- const contextWindow = currentModel?.contextWindow ?? 0;
396
+ // Resolve the context window the same way the Tokens panel does so the
397
+ // maintenance usage %/remaining agree across every diagnostics surface.
398
+ const contextWindow = providerRegistry.getContextWindowForModel(
399
+ providerRegistry.getCurrentModel(),
400
+ );
395
401
  const maintenance = evaluateSessionMaintenance({
396
402
  configManager: ctx.platform.configManager,
397
403
  currentTokens: estimateConversationTokens(llmMessages),
@@ -1,5 +1,6 @@
1
1
  import type { KnowledgeService } from '@pellux/goodvibes-sdk/platform/knowledge';
2
2
  import type { CommandContext, SlashCommand } from '../command-registry.ts';
3
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
3
4
 
4
5
  const KNOWLEDGE_REVIEW_ACTIONS = ['accept', 'reject', 'resolve', 'reopen', 'edit', 'forget'] as const;
5
6
 
@@ -397,7 +398,7 @@ export const knowledgeCommand: SlashCommand = {
397
398
  ...(result.appliedFacts ? [` applied facts: ${Object.keys(result.appliedFacts).join(', ') || 'none'}`] : []),
398
399
  ].join('\n'));
399
400
  } catch (error) {
400
- context.print(`[knowledge] ${error instanceof Error ? error.message : String(error)}`);
401
+ context.print(`[knowledge] ${summarizeError(error)}`);
401
402
  }
402
403
  break;
403
404
  }
@@ -7,7 +7,7 @@ import type {
7
7
  CommandSessionServices,
8
8
  CommandWorkspaceServices,
9
9
  } from '../command-registry.ts';
10
- import { getLastCompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
10
+ import { compactSmallWindow, getLastCompactionEvent, SMALL_WINDOW_THRESHOLD } from '@pellux/goodvibes-sdk/platform/core';
11
11
  import type { CompactionContext, CompactionEvent } from '@pellux/goodvibes-sdk/platform/core';
12
12
  import type { UiReadModels } from '../../runtime/ui-read-models.ts';
13
13
  import type { ShellPathService } from '@/runtime/index.ts';
@@ -244,23 +244,53 @@ export function requireProviderApi(context: CommandContext): ProviderApi {
244
244
  * change).
245
245
  */
246
246
  export async function compactConversation(context: CommandContext): Promise<CompactionEvent | null> {
247
+ const conversationManager = context.session.conversationManager;
248
+ const providerRegistry = context.provider.providerRegistry;
249
+ // Resolve the live context window for the current model so manual /compact
250
+ // matches the auto-compaction path (which scales behaviour by window size)
251
+ // instead of passing a meaningless 0.
252
+ const contextWindow = providerRegistry.getContextWindowForModel(
253
+ providerRegistry.getCurrentModel(),
254
+ );
255
+
256
+ // Small-window models lack room for the structured (multi-LLM) extraction
257
+ // pipeline, so mirror the SDK auto-compaction path and degrade to the
258
+ // simplified "keep the most recent messages" compaction.
259
+ if (contextWindow > 0 && contextWindow < SMALL_WINDOW_THRESHOLD) {
260
+ const compacted = compactSmallWindow(conversationManager.getMessagesForLLM(), 10);
261
+ conversationManager.replaceMessagesForLLM(compacted);
262
+ return null;
263
+ }
264
+
247
265
  const eventBefore = getLastCompactionEvent();
248
266
  const sessionMemories = context.session.sessionMemoryStore?.list() ?? [];
267
+ const lineageTracker = context.session.sessionLineageTracker;
268
+ const agentManager = context.ops.agentManager;
269
+ const planManager = context.ops.planManager;
249
270
  const compactionCtx: CompactionContext = {
250
- messages: context.session.conversationManager.getMessagesForLLM(),
271
+ messages: conversationManager.getMessagesForLLM(),
251
272
  sessionMemories,
252
- agents: [],
253
- wrfcChains: [],
254
- activePlan: null,
255
- lineageEntries: [],
256
- compactionCount: 0,
257
- contextWindow: 0,
273
+ // Mirror buildAutoCompactionContext: pass the running/pending agents, the
274
+ // active execution plan, and the session-lineage log so the handoff summary
275
+ // and lineage section are populated correctly (not "compaction #0").
276
+ agents:
277
+ agentManager
278
+ ?.exportState()
279
+ .filter((agent) => agent.status === 'running' || agent.status === 'pending') ?? [],
280
+ // Mirror buildAutoCompactionContext: include the live WRFC chains (wired
281
+ // through context.session.wrfcController) so the handoff summary's
282
+ // orchestration section matches the SDK auto-compaction path.
283
+ wrfcChains: context.session.wrfcController?.listChains() ?? [],
284
+ activePlan: planManager?.getActive(context.session.runtime.sessionId) ?? null,
285
+ lineageEntries: lineageTracker?.getEntries() ?? [],
286
+ compactionCount: lineageTracker?.getCompactionCount() ?? 0,
287
+ contextWindow,
258
288
  trigger: 'manual',
259
289
  extractionModelId: context.session.runtime.model,
260
290
  extractionProvider: context.session.runtime.provider,
261
291
  };
262
- await context.session.conversationManager.compact(
263
- context.provider.providerRegistry,
292
+ await conversationManager.compact(
293
+ providerRegistry,
264
294
  context.session.runtime.model,
265
295
  'manual',
266
296
  context.session.runtime.provider,
@@ -117,7 +117,7 @@ export function handleCommandModeToken(state: CommandModeRouteState, token: Inpu
117
117
  return true;
118
118
  }
119
119
 
120
- return token.logicalName !== 'left' && token.logicalName !== 'right';
120
+ return token.logicalName !== 'left' && token.logicalName !== 'right' && token.logicalName !== 'home' && token.logicalName !== 'end';
121
121
  }
122
122
 
123
123
  function withPanelFocusSync(context: CommandContext, state: CommandModeRouteState): CommandContext {
@@ -5,6 +5,7 @@ import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy,
5
5
  import { clearModalStack, handleEscape, modalOpened } from './handler-modal-stack.ts';
6
6
  import { openOnboardingWizardState, type OpenOnboardingWizardOptions } from './handler-ui-state.ts';
7
7
  import type { InputHandlerLike as InputHandler } from './handler-types.ts';
8
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
8
9
 
9
10
  export function openOnboardingWizardForHandler(
10
11
  handler: InputHandler,
@@ -49,7 +50,7 @@ export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: Inpu
49
50
  handler.requestRender();
50
51
  } catch (error) {
51
52
  if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
52
- const message = error instanceof Error ? error.message : String(error);
53
+ const message = summarizeError(error);
53
54
  handler.onboardingWizard.failRuntimeHydration(message);
54
55
  handler.commandContext?.print?.(`Onboarding runtime snapshot failed: ${message}`);
55
56
  handler.requestRender();
@@ -3,6 +3,7 @@ import type { SelectionResult, SelectionAction } from './selection-modal.ts';
3
3
  import type { CommandContext } from './command-registry.ts';
4
4
  import { openTtsProviderPicker, openTtsVoicePicker } from './tts-settings-actions.ts';
5
5
  import { isTextBackspace } from './delete-key-policy.ts';
6
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
6
7
 
7
8
  type SelectionRouteState = {
8
9
  selectionModal: {
@@ -285,7 +286,7 @@ function consumeSettingsPickerRequest(state: SettingsRouteState): void {
285
286
  return;
286
287
  }
287
288
  void openTtsVoicePicker(state.commandContext).catch((error: unknown) => {
288
- state.commandContext?.print(`Unable to list TTS voices: ${error instanceof Error ? error.message : String(error)}`);
289
+ state.commandContext?.print(`Unable to list TTS voices: ${summarizeError(error)}`);
289
290
  state.requestRender();
290
291
  });
291
292
  return;
@@ -22,6 +22,7 @@ import {
22
22
  getCloudflareSetupSource,
23
23
  shouldShowCloudflareStep,
24
24
  } from './onboarding/onboarding-wizard-cloudflare.ts';
25
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
25
26
 
26
27
  type CloudflareOnboardingAction = Extract<OnboardingWizardAction,
27
28
  | 'cloudflare-token-requirements'
@@ -44,7 +45,7 @@ function normalizeCloudflareActionError(error: unknown): string {
44
45
  if (error instanceof CloudflareDaemonRouteError) {
45
46
  return `${error.message} (HTTP ${error.status}, ${error.code})`;
46
47
  }
47
- return error instanceof Error ? error.message : String(error);
48
+ return summarizeError(error);
48
49
  }
49
50
 
50
51
  function setCloudflareWizardStatusForHandler(
@@ -1,6 +1,6 @@
1
1
  import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config';
3
- import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
3
+ import { summarizeError, openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
4
4
  import { getProviderIdFromModel } from '../config/provider-model.ts';
5
5
  import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
6
6
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardApplyFeedback } from './onboarding/onboarding-wizard.ts';
@@ -258,7 +258,7 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
258
258
  deleteWizardProgress(handler.uiServices.environment.shellPaths);
259
259
  } catch (markerError) {
260
260
  handler.commandContext?.print?.(
261
- `Onboarding check marker could not be written: ${markerError instanceof Error ? markerError.message : String(markerError)}`,
261
+ `Onboarding check marker could not be written: ${summarizeError(markerError)}`,
262
262
  );
263
263
  }
264
264
  const activationVerification = await handler.restartOnboardingExternalServicesIfNeeded(request);
@@ -275,7 +275,7 @@ export async function handleOnboardingActionForHandler(handler: InputHandler, ac
275
275
  severity: 'error',
276
276
  title: 'Apply failed',
277
277
  summary: 'The wizard could not persist these settings. No service restart was attempted.',
278
- messages: [error instanceof Error ? error.message : String(error)],
278
+ messages: [summarizeError(error)],
279
279
  });
280
280
  return;
281
281
  } finally {
@@ -346,7 +346,7 @@ export async function refreshOnboardingHydrationForHandler(handler: InputHandler
346
346
  handler.requestRender();
347
347
  } catch (error) {
348
348
  if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
349
- const message = error instanceof Error ? error.message : String(error);
349
+ const message = summarizeError(error);
350
350
  handler.onboardingWizard.failRuntimeHydration(message);
351
351
  handler.requestRender();
352
352
  }
@@ -405,7 +405,7 @@ export async function handleOpenAiSubscriptionStartForHandler(handler: InputHand
405
405
  listener?.close();
406
406
  handler.commandContext?.print?.([
407
407
  'OpenAI subscription sign-in could not start.',
408
- ` ${error instanceof Error ? error.message : String(error)}`,
408
+ ` ${summarizeError(error)}`,
409
409
  ].join('\n'));
410
410
  handler.requestRender();
411
411
  } finally {
@@ -449,7 +449,7 @@ export async function completeOpenAiSubscriptionFromListenerForHandler(
449
449
  } catch (error) {
450
450
  handler.commandContext?.print?.([
451
451
  'OpenAI subscription listener could not complete automatically.',
452
- ` listener: ${error instanceof Error ? error.message : String(error)}`,
452
+ ` listener: ${summarizeError(error)}`,
453
453
  'Paste the callback code or URL into the OpenAI callback field to finish in onboarding.',
454
454
  ].join('\n'));
455
455
  handler.requestRender();
@@ -501,7 +501,7 @@ export async function handleOpenAiSubscriptionFinishForHandler(handler: InputHan
501
501
  } catch (error) {
502
502
  handler.commandContext?.print?.([
503
503
  'OpenAI subscription sign-in could not finish.',
504
- ` ${error instanceof Error ? error.message : String(error)}`,
504
+ ` ${summarizeError(error)}`,
505
505
  ].join('\n'));
506
506
  handler.requestRender();
507
507
  } finally {
@@ -663,7 +663,7 @@ export async function restartOnboardingExternalServicesIfNeededForHandler(handle
663
663
  return [{
664
664
  id: 'runtime:activation-restart',
665
665
  status: 'fail',
666
- message: `Background services could not restart: ${error instanceof Error ? error.message : String(error)}`,
666
+ message: `Background services could not restart: ${summarizeError(error)}`,
667
667
  target: 'service',
668
668
  }];
669
669
  }
@@ -345,7 +345,7 @@ export function handleHistorySearchToken(state: HistorySearchRouteState, token:
345
345
  if (token.logicalName === 'escape' || (token.ctrl && token.logicalName === 'g')) {
346
346
  state.prompt = state.historySearch.cancel();
347
347
  state.cursorPos = state.prompt.length;
348
- } else if (token.logicalName === 'return') {
348
+ } else if (token.logicalName === 'enter' || token.logicalName === 'return') {
349
349
  state.prompt = state.historySearch.accept();
350
350
  state.cursorPos = state.prompt.length;
351
351
  } else if (token.logicalName === 'backspace') {
@@ -1,6 +1,7 @@
1
1
  import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { CommandContext } from './command-registry.ts';
3
3
  import type { SelectionItem } from './selection-modal.ts';
4
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
5
 
5
6
  function getStreamingTtsProviders(ctx: CommandContext): Array<{ id: string; label: string; capabilities: readonly string[] }> {
6
7
  const registry = ctx.platform.voiceProviderRegistry;
@@ -94,7 +95,7 @@ export async function openTtsVoicePicker(ctx: CommandContext, providerArg?: stri
94
95
  });
95
96
  return true;
96
97
  } catch (error) {
97
- ctx.print(`Unable to list TTS voices: ${error instanceof Error ? error.message : String(error)}`);
98
+ ctx.print(`Unable to list TTS voices: ${summarizeError(error)}`);
98
99
  return true;
99
100
  }
100
101
  }