@pellux/goodvibes-agent 1.0.43 → 1.1.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.
@@ -5,9 +5,9 @@ import type {
5
5
  AgentWorkspaceLocalEditor,
6
6
  AgentWorkspaceRuntimeSnapshot,
7
7
  } from '../input/agent-workspace.ts';
8
- import { formatAgentRecordReviewState, formatAgentRecordSource } from '../agent/record-labels.ts';
8
+ import { formatAgentRecordReviewState } from '../agent/record-labels.ts';
9
9
  import type { Line } from '../types/grid.ts';
10
- import { wrapText } from '../utils/terminal-width.ts';
10
+ import { truncateDisplay, wrapText } from '../utils/terminal-width.ts';
11
11
  import { GLYPHS } from './ui-primitives.ts';
12
12
  import {
13
13
  getFullscreenWorkspaceMetrics,
@@ -81,6 +81,21 @@ function setupStatusLabel(status: AgentWorkspaceRuntimeSnapshot['setupChecklist'
81
81
  : 'Optional';
82
82
  }
83
83
 
84
+ function compactText(text: string, maxWidth = 104): string {
85
+ const normalized = text.replace(/\s+/g, ' ').trim();
86
+ if (normalized.length === 0) return '';
87
+ const firstSentence = normalized.match(/^.*?[.!?](?:\s|$)/)?.[0]?.trim();
88
+ const source = firstSentence && firstSentence.length <= maxWidth ? firstSentence : normalized;
89
+ return truncateDisplay(source, maxWidth, '...');
90
+ }
91
+
92
+ function actionMetaLine(action: AgentWorkspaceAction): ContextLine {
93
+ return {
94
+ text: `${actionCommand(action)}; ${action.safety}`,
95
+ fg: action.kind === 'command' ? PALETTE.info : safetyColor(action),
96
+ };
97
+ }
98
+
84
99
  function setupAttentionItems(snapshot: AgentWorkspaceRuntimeSnapshot, limit: number): AgentWorkspaceRuntimeSnapshot['setupChecklist'] {
85
100
  return [
86
101
  ...snapshot.setupChecklist.filter((item) => item.status === 'blocked'),
@@ -145,54 +160,41 @@ function companionAccessLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLi
145
160
  : 'missing';
146
161
  const error = access.tokenError ? `; token read error ${access.tokenError}` : '';
147
162
  return {
148
- text: `Companion: ${access.surface}; token ${tokenState}; QR ${access.qrCommand}; manual token text hidden${error}.`,
163
+ text: `Companion: ${access.surface}; token ${tokenState}; QR ${access.qrCommand}${error}.`,
149
164
  fg: access.pairingReady ? PALETTE.good : PALETTE.warn,
150
165
  };
151
166
  }
152
167
 
153
- function localLibraryLines(
168
+ function compactLocalLibraryLines(
154
169
  title: string,
155
170
  items: readonly AgentWorkspaceRuntimeSnapshot['localPersonas'][number][],
156
171
  emptyText: string,
157
172
  selectedId: string | null,
158
173
  ): ContextLine[] {
159
- const lines: ContextLine[] = [
160
- { text: title, fg: PALETTE.title, bold: true },
161
- ];
174
+ const lines: ContextLine[] = [];
162
175
  if (items.length === 0) {
163
- lines.push({ text: emptyText, fg: PALETTE.warn });
176
+ lines.push({ text: `${title}: 0. ${emptyText}`, fg: PALETTE.warn });
164
177
  return lines;
165
178
  }
166
- for (const item of items.slice(0, 8)) {
167
- const selected = item.id === selectedId;
168
- const status = [
169
- selected ? 'selected' : '',
170
- item.active ? 'active' : '',
171
- item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
172
- item.scope && item.cls ? `${item.scope}/${item.cls}` : '',
173
- item.confidence !== undefined ? `${item.confidence}%` : '',
174
- item.requirementCount !== undefined && item.requirementCount > 0
175
- ? (item.missingRequirementCount && item.missingRequirementCount > 0 ? `needs ${item.missingRequirementCount}/${item.requirementCount}` : `ready ${item.requirementCount}/${item.requirementCount}`)
176
- : '',
177
- formatAgentRecordReviewState(item.reviewState),
178
- item.source ? `origin ${item.source}` : '',
179
- item.startCount !== undefined ? `starts ${item.startCount}` : '',
180
- ].filter(Boolean).join(' / ');
181
- const tags = item.tags.length > 0 ? ` tags ${item.tags.join(',')}` : '';
182
- const triggers = item.triggers.length > 0 ? ` triggers ${item.triggers.join(',')}` : '';
183
- const marker = selected ? `${GLYPHS.navigation.selected} ` : '';
184
- lines.push({
185
- text: `${marker}${item.id}: ${item.name} (${status})`,
186
- fg: item.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
187
- bold: selected || item.active === true,
188
- });
189
- lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
190
- if (item.missingRequirements && item.missingRequirements.length > 0) {
191
- lines.push({ text: ` missing setup: ${item.missingRequirements.join(', ')}`, fg: PALETTE.warn });
192
- }
193
- }
194
- if (items.length > 8) {
195
- lines.push({ text: `${items.length - 8} more item(s). Open the library command for the full list.`, fg: PALETTE.dim });
179
+ const selected = items.find((item) => item.id === selectedId) ?? items[0]!;
180
+ const status = [
181
+ selected.active ? 'active' : '',
182
+ selected.enabled === true ? 'enabled' : selected.enabled === false ? 'disabled' : '',
183
+ selected.scope && selected.cls ? `${selected.scope}/${selected.cls}` : '',
184
+ selected.confidence !== undefined ? `${selected.confidence}%` : '',
185
+ selected.requirementCount !== undefined && selected.requirementCount > 0
186
+ ? (selected.missingRequirementCount && selected.missingRequirementCount > 0 ? `needs ${selected.missingRequirementCount}/${selected.requirementCount}` : `ready ${selected.requirementCount}/${selected.requirementCount}`)
187
+ : '',
188
+ formatAgentRecordReviewState(selected.reviewState),
189
+ selected.startCount !== undefined ? `starts ${selected.startCount}` : '',
190
+ ].filter(Boolean).join(', ');
191
+ lines.push({
192
+ text: `${title}: ${items.length}; selected ${selected.name}${status ? ` (${status})` : ''}.`,
193
+ fg: selected.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
194
+ bold: selected.active === true,
195
+ });
196
+ if (selected.missingRequirements && selected.missingRequirements.length > 0) {
197
+ lines.push({ text: `Missing setup: ${selected.missingRequirements.join(', ')}`, fg: PALETTE.warn });
196
198
  }
197
199
  return lines;
198
200
  }
@@ -239,23 +241,18 @@ function routineNextActionLine(snapshot: AgentWorkspaceRuntimeSnapshot): Context
239
241
  return { text: 'Next routine action: Start selected in the main conversation, inspect receipts, or reconcile connected schedules.', fg: PALETTE.info, bold: true };
240
242
  }
241
243
 
242
- function routineScheduleReceiptLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
244
+ function compactRoutineReceiptLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine {
243
245
  const latest = snapshot.latestRoutineScheduleReceipt;
244
- const lines: ContextLine[] = [
245
- {
246
- text: `Promotion receipts: ${snapshot.routineScheduleReceiptCount}; created: ${snapshot.successfulRoutineScheduleReceiptCount}; failed: ${snapshot.failedRoutineScheduleReceiptCount}`,
247
- fg: snapshot.failedRoutineScheduleReceiptCount > 0 ? PALETTE.warn : PALETTE.info,
248
- },
249
- ];
250
- if (latest) {
251
- lines.push({
252
- text: `Latest receipt: ${latest.id} ${latest.status} routine=${latest.routineId} schedule="${latest.scheduleName}" ${latest.scheduleKind} ${latest.scheduleValue}`,
253
- fg: latest.status === 'failed' ? PALETTE.warn : PALETTE.good,
254
- });
255
- } else {
256
- lines.push({ text: 'No schedule promotion receipts yet. Confirm a routine promotion from Automation when ready.', fg: PALETTE.muted });
246
+ if (!latest) {
247
+ return {
248
+ text: `Promotion receipts: ${snapshot.routineScheduleReceiptCount}; none created yet.`,
249
+ fg: PALETTE.muted,
250
+ };
257
251
  }
258
- return lines;
252
+ return {
253
+ text: `Promotion receipts: ${snapshot.routineScheduleReceiptCount}; latest ${latest.status} ${latest.routineId}.`,
254
+ fg: latest.status === 'failed' ? PALETTE.warn : PALETTE.good,
255
+ };
259
256
  }
260
257
 
261
258
  function automationNextActionLine(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine {
@@ -277,57 +274,6 @@ function automationNextActionLine(snapshot: AgentWorkspaceRuntimeSnapshot): Cont
277
274
  return { text: 'Next automation action: Create a reminder, or create/import a routine before recurring workflow promotion.', fg: PALETTE.warn, bold: true };
278
275
  }
279
276
 
280
- function profileLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
281
- const lines: ContextLine[] = [
282
- { text: 'Agent Profiles', fg: PALETTE.title, bold: true },
283
- ];
284
- if (snapshot.runtimeProfiles.length === 0) {
285
- lines.push({ text: 'No isolated Agent profiles yet. Use Create Agent profile in this workspace.', fg: PALETTE.warn });
286
- return lines;
287
- }
288
- for (const profile of snapshot.runtimeProfiles.slice(0, 6)) {
289
- const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : ' starter=none';
290
- const created = profile.createdAt ? ` created ${profile.createdAt.slice(0, 10)}` : '';
291
- const states = [
292
- profile.id === snapshot.activeRuntimeProfile ? 'active' : '',
293
- profile.id === snapshot.selectedRuntimeProfile ? 'default' : '',
294
- ].filter(Boolean).join(', ');
295
- const stateText = states ? ` [${states}]` : '';
296
- lines.push({
297
- text: `${profile.id}${stateText}${starter}${created}`,
298
- fg: profile.id === snapshot.selectedRuntimeProfile ? PALETTE.good : PALETTE.info,
299
- bold: profile.id === snapshot.activeRuntimeProfile || profile.id === snapshot.selectedRuntimeProfile,
300
- });
301
- lines.push({ text: ` home: ${profile.homeDirectory}`, fg: PALETTE.muted });
302
- }
303
- if (snapshot.runtimeProfiles.length > 6) {
304
- lines.push({ text: `${snapshot.runtimeProfiles.length - 6} more profile(s).`, fg: PALETTE.dim });
305
- }
306
- return lines;
307
- }
308
-
309
- function starterTemplateLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
310
- const lines: ContextLine[] = [
311
- { text: 'Starter Templates', fg: PALETTE.title, bold: true },
312
- ];
313
- for (const template of snapshot.runtimeStarterTemplates.slice(0, 6)) {
314
- const origin = template.source === 'local' ? 'Local' : formatAgentRecordSource(template.source);
315
- lines.push({
316
- text: `${template.id}: ${template.name} [${origin}]`,
317
- fg: template.source === 'local' ? PALETTE.good : PALETTE.info,
318
- bold: template.id === 'research',
319
- });
320
- lines.push({
321
- text: ` ${template.description} Persona ${template.personaName}; skills ${template.skillNames.join(', ')}; routines ${template.routineNames.join(', ')}.`,
322
- fg: PALETTE.muted,
323
- });
324
- }
325
- if (snapshot.runtimeStarterTemplates.length > 6) {
326
- lines.push({ text: `${snapshot.runtimeStarterTemplates.length - 6} more starter template(s).`, fg: PALETTE.dim });
327
- }
328
- return lines;
329
- }
330
-
331
277
  function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
332
278
  if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
333
279
  const base: ContextLine[] = [];
@@ -346,175 +292,89 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
346
292
  } else if (category.id === 'artifacts') {
347
293
  const mediaReady = snapshot.voiceMediaReadiness.readyMediaProviderCount;
348
294
  base.push(
349
- { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
350
- { text: `Agent Knowledge route: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
351
- { text: `Media providers: ${mediaReady}/${snapshot.mediaProviderCount} ready; generation-capable ${snapshot.mediaGenerationProviderCount}.`, fg: mediaReady > 0 ? PALETTE.good : PALETTE.warn },
352
- { text: 'Use this area for concrete files and generated output: attach images, export transcripts, ingest reviewed sources, inspect source records, and generate media.', fg: PALETTE.good },
353
- { text: 'Agent Knowledge ingest writes only to the isolated Agent segment. Default knowledge and non-Agent routes are not fallback storage.', fg: PALETTE.warn },
354
- { text: 'Generated media is stored as artifacts and referenced by id; the TUI should not print inline base64.', fg: PALETTE.muted },
355
- { text: `Workspace path: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
295
+ { text: `Chat: ${snapshot.provider} / ${snapshot.modelDisplayName}; Knowledge: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
296
+ { text: `Media: ${mediaReady}/${snapshot.mediaProviderCount} ready; generation ${snapshot.mediaGenerationProviderCount}.`, fg: mediaReady > 0 ? PALETTE.good : PALETTE.warn },
297
+ { text: 'Files: attach, export, inspect, ingest reviewed sources, or generate media.', fg: PALETTE.good },
298
+ { text: 'Knowledge ingest and media generation require explicit actions.', fg: PALETTE.warn },
356
299
  );
357
300
  } else if (category.id === 'channels') {
358
301
  const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
359
302
  const readyCount = snapshot.channels.filter((channel) => channel.ready).length;
360
303
  const configuredDefaults = snapshot.channels.filter((channel) => channel.defaultTarget === 'configured').length;
361
- const readyChannels = snapshot.channels.filter((channel) => channel.ready).map((channel) => channel.label);
362
304
  const needsTarget = snapshot.channels.filter((channel) => channel.setupState === 'needs-target');
363
305
  const needsConfig = snapshot.channels.filter((channel) => channel.setupState === 'needs-config');
364
306
  const nextAttentionChannel = needsConfig[0] ?? needsTarget[0] ?? snapshot.channels.find((channel) => !channel.enabled);
365
- const disabledChannels = snapshot.channels.filter((channel) => !channel.enabled).map((channel) => channel.label);
366
- const disabledPreview = disabledChannels.slice(0, 6).join(', ');
367
- const disabledSuffix = disabledChannels.length > 6 ? `, +${disabledChannels.length - 6} more` : '';
368
- const orderedChannels = [
369
- ...snapshot.channels.filter((channel) => channel.enabled),
370
- ...snapshot.channels.filter((channel) => !channel.enabled),
371
- ].slice(0, 3);
372
307
  base.push(
373
- { text: `GoodVibes API: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
308
+ { text: `API: ${snapshot.runtimeBaseUrl}`, fg: PALETTE.info },
374
309
  companionAccessLine(snapshot),
375
- { text: `Readiness: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} default target(s) configured.`, fg: PALETTE.info },
376
- { text: 'Setup path: pair companion -> inspect readiness -> review accounts/policies/status -> fix one channel -> add explicit notification target if needed.', fg: PALETTE.good },
377
- { text: `Next channel action: ${nextAttentionChannel ? `${nextAttentionChannel.label} - ${nextAttentionChannel.nextStep}` : 'All enabled channels are ready; keep delivery explicit and review policies before sending.'}`, fg: nextAttentionChannel ? PALETTE.warn : PALETTE.good },
378
- { text: `Ready channels: ${readyChannels.join(', ') || 'none'}.`, fg: readyChannels.length > 0 ? PALETTE.good : PALETTE.warn },
379
- { text: `Needs default target: ${needsTarget.map((channel) => `${channel.label} -> ${channel.defaultTargetKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsTarget.length > 0 ? PALETTE.warn : PALETTE.muted },
380
- { text: `Needs config: ${needsConfig.map((channel) => `${channel.label} -> ${channel.missingRequiredKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsConfig.length > 0 ? PALETTE.warn : PALETTE.muted },
381
- { text: `Disabled channels: ${disabledPreview || 'none'}${disabledSuffix}.`, fg: PALETTE.dim },
382
- { text: 'Safety: no secret values; sends and public exposure require explicit user action and Agent policy.', fg: PALETTE.warn },
310
+ { text: `Channels: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} target(s).`, fg: PALETTE.info },
311
+ { text: `Next: ${nextAttentionChannel ? `${nextAttentionChannel.label} - ${compactText(nextAttentionChannel.nextStep)}` : 'All enabled channels ready.'}`, fg: nextAttentionChannel ? PALETTE.warn : PALETTE.good },
312
+ { text: 'Safety: secrets hidden; sends require explicit action.', fg: PALETTE.warn },
383
313
  );
384
- for (const channel of orderedChannels) {
385
- const ready = channel.ready ? 'ready' : `${channel.missingConfigCount} missing`;
386
- base.push({
387
- text: `${channel.label}: ${channel.setupState}; ${ready}; target ${channel.defaultTarget}; delivery ${channel.delivery}; risk ${channel.risk}.`,
388
- fg: channel.ready ? PALETTE.good : channel.enabled ? PALETTE.warn : PALETTE.dim,
389
- });
390
- }
391
314
  } else if (category.id === 'knowledge') {
392
315
  base.push(
393
- { text: `Route family: ${snapshot.knowledgeRoute}/{status,ask,search}`, fg: PALETTE.info },
394
- { text: `Isolation: ${snapshot.knowledgeIsolation}; no default knowledge or non-Agent fallback`, fg: PALETTE.good },
395
- { text: 'Ingest: URL, URL-list, and bookmark imports require explicit --yes and write only to Agent Knowledge.', fg: PALETTE.info },
396
- { text: 'Review: queue, issues, candidates, reports, reindex, and consolidation stay inside the Agent segment.', fg: PALETTE.muted },
397
- { text: 'Agent-owned content appears here only after explicit Agent knowledge ingestion.', fg: PALETTE.muted },
316
+ { text: `Route: ${snapshot.knowledgeRoute}; isolation ${snapshot.knowledgeIsolation}.`, fg: PALETTE.info },
317
+ { text: 'Ask/search, ingest, review, reindex, and reports stay Agent-owned.', fg: PALETTE.good },
318
+ { text: 'Ingest requires explicit confirmation.', fg: PALETTE.warn },
398
319
  );
399
320
  } else if (category.id === 'research') {
400
321
  base.push(
401
- { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
402
- { text: `Agent Knowledge route: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
403
- { text: `Browser tools: ${snapshot.voiceMediaReadiness.browserToolState}; public base URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
404
- { text: snapshot.voiceMediaReadiness.browserToolNextStep, fg: PALETTE.muted },
405
- { text: 'Research requests are submitted to the main conversation. Agent may use connected read-only web tools when the user asks.', fg: PALETTE.good },
406
- { text: 'URL references and web research do not ingest into Agent Knowledge. Ingestion is a separate confirmed Agent Knowledge action.', fg: PALETTE.warn },
407
- { text: 'Use Agent Knowledge ask/search first for known source-backed context; use web research for current or external sources.', fg: PALETTE.info },
408
- { text: 'External sends, browser-side effects, package installs, and connected-host mutations are outside this read-only research lane.', fg: PALETTE.warn },
322
+ { text: `Chat: ${snapshot.provider} / ${snapshot.modelDisplayName}; Knowledge: ${snapshot.knowledgeRoute}`, fg: PALETTE.info },
323
+ { text: `Browser: ${snapshot.voiceMediaReadiness.browserToolState}; public URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
324
+ { text: 'Research is read-only. Knowledge ingest is a separate confirmed action.', fg: PALETTE.good },
325
+ { text: compactText(snapshot.voiceMediaReadiness.browserToolNextStep), fg: PALETTE.muted },
409
326
  );
410
327
  } else if (category.id === 'tools') {
411
328
  base.push(
412
329
  { text: `MCP servers: ${snapshot.mcpConnectedServerCount}/${snapshot.mcpServerCount} connected; quarantined ${snapshot.mcpQuarantinedServerCount}; allow-all ${snapshot.mcpAllowAllServerCount}.`, fg: snapshot.mcpQuarantinedServerCount > 0 || snapshot.mcpAllowAllServerCount > 0 ? PALETTE.warn : PALETTE.info },
413
- { text: 'Open MCP workspace for live server status, tool inventory, config paths, and confirmed add/remove/reload actions.', fg: PALETTE.info },
414
- { text: 'Add/update requires typed confirmation and dispatches through the TUI command router.', fg: PALETTE.good },
415
- { text: 'Trust changes remain explicit; allow-all is kept behind the settings workspace.', fg: PALETTE.warn },
416
- { text: 'Useful first actions: /mcp review, /mcp tools, /mcp config, and Add MCP server.', fg: PALETTE.muted },
417
- { text: 'Normal assistant chat can use tools serially when policy allows; onboarding does not start hidden tool work.', fg: PALETTE.muted },
330
+ { text: 'Add/update/reload and trust changes require confirmation.', fg: PALETTE.good },
331
+ { text: 'Start: /mcp review, /mcp tools, /mcp config, Add MCP server.', fg: PALETTE.muted },
418
332
  );
419
333
  } else if (category.id === 'voice-media') {
420
334
  const readiness = snapshot.voiceMediaReadiness;
421
- const voiceRows = readiness.voiceProviders.slice(0, 6);
422
- const mediaRows = readiness.mediaProviders.slice(0, 6);
423
- base.push(
424
- { text: `Voice providers: ${snapshot.voiceProviderCount}; streaming TTS: ${snapshot.voiceStreamingProviderCount}; STT: ${snapshot.voiceSttProviderCount}; realtime: ${snapshot.voiceRealtimeProviderCount}.`, fg: PALETTE.info },
425
- { text: `Voice interaction: ${snapshot.voiceSurfaceEnabled ? 'enabled' : 'disabled'}; ready providers ${readiness.readyVoiceProviderCount}/${snapshot.voiceProviderCount}.`, fg: snapshot.voiceSurfaceEnabled ? PALETTE.warn : PALETTE.muted },
426
- { text: `TTS config: provider ${snapshot.ttsProvider}; voice ${snapshot.ttsVoice}; response model ${snapshot.ttsResponseModel}.`, fg: PALETTE.info },
427
- { text: `Selected TTS readiness: ${readiness.selectedTtsProviderLabel} -> ${readiness.selectedTtsProviderStatus}; voice ${readiness.ttsVoiceConfigured ? 'configured' : 'default'}; response route ${readiness.ttsResponseRouteConfigured ? 'configured' : 'chat route'}.`, fg: readiness.selectedTtsProviderStatus === 'ready' ? PALETTE.good : PALETTE.warn },
428
- { text: `Media providers: ${snapshot.mediaProviderCount}; understanding: ${snapshot.mediaUnderstandingProviderCount}; generation: ${snapshot.mediaGenerationProviderCount}.`, fg: PALETTE.info },
429
- { text: `Ready media providers: ${readiness.readyMediaProviderCount}/${snapshot.mediaProviderCount}.`, fg: readiness.readyMediaProviderCount > 0 ? PALETTE.good : PALETTE.warn },
430
- { text: `Browser tools: ${readiness.browserToolState}; public base URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
431
- { text: readiness.browserToolNextStep, fg: PALETTE.muted },
432
- { text: 'Voice provider readiness', fg: PALETTE.title, bold: true },
433
- );
434
- for (const provider of voiceRows) {
435
- const selected = provider.selected ? 'selected; ' : '';
436
- const missing = provider.missingSecretKeyOptions.length > 0 ? `; needs ${provider.missingSecretKeyOptions.join('|')}` : '';
437
- base.push({
438
- text: `${provider.label}: ${selected}${provider.setupState}; ${provider.features.join(', ') || 'registered'}${missing}.`,
439
- fg: provider.setupState === 'ready' ? PALETTE.good : provider.setupState === 'needs-secret' ? PALETTE.warn : PALETTE.muted,
440
- });
441
- }
442
- if (snapshot.voiceProviderCount > voiceRows.length) base.push({ text: `${snapshot.voiceProviderCount - voiceRows.length} more voice provider(s).`, fg: PALETTE.dim });
443
- base.push({ text: 'Media provider readiness', fg: PALETTE.title, bold: true });
444
- for (const provider of mediaRows) {
445
- const missing = provider.missingSecretKeyOptions.length > 0 ? `; needs ${provider.missingSecretKeyOptions.join('|')}` : '';
446
- base.push({
447
- text: `${provider.label}: ${provider.setupState}; ${provider.features.join(', ') || 'registered'}${missing}.`,
448
- fg: provider.setupState === 'ready' ? PALETTE.good : provider.setupState === 'needs-secret' ? PALETTE.warn : PALETTE.muted,
449
- });
450
- }
451
- if (snapshot.mediaProviderCount > mediaRows.length) base.push({ text: `${snapshot.mediaProviderCount - mediaRows.length} more media provider(s).`, fg: PALETTE.dim });
452
- for (const step of readiness.nextSteps.slice(0, 4)) base.push({ text: `Next: ${step}`, fg: PALETTE.info });
453
335
  base.push(
454
- { text: 'No secret values are rendered. Voice, browser, and generated media side effects require explicit user action.', fg: PALETTE.warn },
455
- { text: 'Image input uses prompt attachments; media generation/provider setup stays behind explicit commands and configured providers.', fg: PALETTE.muted },
336
+ { text: `Voice: ${readiness.readyVoiceProviderCount}/${snapshot.voiceProviderCount} ready; TTS ${snapshot.ttsProvider}; voice ${snapshot.ttsVoice}.`, fg: readiness.readyVoiceProviderCount > 0 ? PALETTE.good : PALETTE.warn },
337
+ { text: `Media: ${readiness.readyMediaProviderCount}/${snapshot.mediaProviderCount} ready; generation ${snapshot.mediaGenerationProviderCount}.`, fg: readiness.readyMediaProviderCount > 0 ? PALETTE.good : PALETTE.warn },
338
+ { text: `Browser: ${readiness.browserToolState}; public URL ${snapshot.browserToolPublicBaseUrl}.`, fg: snapshot.browserToolExposureEnabled ? PALETTE.warn : PALETTE.muted },
339
+ { text: readiness.nextSteps[0] ? `Next: ${compactText(readiness.nextSteps[0])}` : 'Next: voice/media setup is ready.', fg: readiness.nextSteps.length > 0 ? PALETTE.info : PALETTE.good },
340
+ { text: 'Secrets hidden; voice, browser, and media side effects require explicit action.', fg: PALETTE.warn },
456
341
  );
457
342
  } else if (category.id === 'profiles') {
458
343
  const defaultProfile = snapshot.selectedRuntimeProfile
459
344
  ? `${snapshot.selectedRuntimeProfile}${snapshot.selectedRuntimeProfileExists ? '' : ' (missing)'}`
460
345
  : '(base Agent home)';
461
346
  base.push(
462
- { text: `Active Agent profile: ${snapshot.activeRuntimeProfile}`, fg: PALETTE.info },
463
- { text: `Default for next launch: ${defaultProfile}`, fg: snapshot.selectedRuntimeProfileExists || !snapshot.selectedRuntimeProfile ? PALETTE.info : PALETTE.warn },
464
- { text: `Agent profiles under this home: ${snapshot.runtimeProfileCount}`, fg: PALETTE.info },
465
- { text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
466
- { text: `Starter ids: ${snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none'}`, fg: PALETTE.info },
467
- { text: 'Starter Templates', fg: PALETTE.title, bold: true },
468
- { text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
469
- { text: '' },
470
- ...profileLines(snapshot),
471
- { text: '' },
472
- ...starterTemplateLines(snapshot),
473
- { text: '' },
474
- { text: 'Named Agent profiles isolate local config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
475
- { text: 'Starter authoring: browse, export, edit, import, and create Agent profiles from inside this workspace.', fg: PALETTE.info },
476
- { text: 'The connected GoodVibes host stays shared unless that host is configured separately.', fg: PALETTE.warn },
477
- { text: 'Portable bundles require explicit export/import commands with real paths and --yes.', fg: PALETTE.muted },
347
+ { text: `Profiles: active ${snapshot.activeRuntimeProfile}; default ${defaultProfile}.`, fg: snapshot.selectedRuntimeProfileExists || !snapshot.selectedRuntimeProfile ? PALETTE.info : PALETTE.warn },
348
+ { text: `Local profiles: ${snapshot.runtimeProfileCount}; starters ${snapshot.runtimeStarterTemplateCount}; custom ${snapshot.localStarterTemplateCount}.`, fg: PALETTE.info },
349
+ { text: `Starter ids: ${truncateDisplay(snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none', 96, '...')}`, fg: PALETTE.muted },
350
+ { text: 'Profiles isolate local Agent config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
478
351
  );
479
352
  } else if (category.id === 'memory') {
480
353
  base.push(
481
- { text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
482
- { text: `Agent memory: ${snapshot.localMemoryCount}; prompt-active: ${snapshot.localMemoryPromptActiveCount}; review queue: ${snapshot.localMemoryReviewQueueCount}`, fg: PALETTE.info },
483
- { text: `Scratchpad notes: ${snapshot.localNoteCount}; review queue: ${snapshot.localNoteReviewQueueCount}`, fg: PALETTE.info },
484
- { text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
485
- { text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
486
- { text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
487
- { text: 'Durable memory, scratchpad notes, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
488
- { text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
489
- { text: '' },
490
- ...localLibraryLines('Agent Memory', snapshot.localMemories, 'No Agent memory yet. Create one here with Create memory.', workspace.selectedLocalLibraryItem('memory')?.id ?? null),
354
+ { text: `Memory: ${snapshot.localMemoryCount}; prompt ${snapshot.localMemoryPromptActiveCount}; queue ${snapshot.localMemoryReviewQueueCount}; session ${snapshot.sessionMemoryCount}.`, fg: PALETTE.info },
355
+ { text: `Notes: ${snapshot.localNoteCount}; skills ${snapshot.localSkillCount}/${snapshot.enabledSkillCount}; routines ${snapshot.localRoutineCount}/${snapshot.enabledRoutineCount}; personas ${snapshot.localPersonaCount}.`, fg: PALETTE.info },
356
+ { text: `Active persona: ${snapshot.activePersonaName}.`, fg: PALETTE.info },
357
+ ...compactLocalLibraryLines('Agent Memory', snapshot.localMemories, 'Create one with Create memory.', workspace.selectedLocalLibraryItem('memory')?.id ?? null),
358
+ { text: 'Secrets are rejected or redacted; use secret references.', fg: PALETTE.warn },
491
359
  );
492
360
  } else if (category.id === 'notes') {
493
361
  base.push(
494
362
  { text: `Scratchpad notes: ${snapshot.localNoteCount}; review queue: ${snapshot.localNoteReviewQueueCount}`, fg: PALETTE.info },
495
- { text: 'Notes are Agent-local working context for source triage, temporary decisions, and operator handoff.', fg: PALETTE.good },
496
- { text: 'Notes do not become memory and are not ingested into Agent Knowledge unless the user takes a separate explicit action.', fg: PALETTE.warn },
497
- { text: 'Use reviewed notes to prefill durable memory, skills, routines, or personas, or to decide that a reviewed source deserves Agent Knowledge ingest.', fg: PALETTE.muted },
498
- { text: '' },
499
- ...localLibraryLines('Scratchpad Notes', snapshot.localNotes, 'No local notes yet. Create one here with Create note.', workspace.selectedLocalLibraryItem('note')?.id ?? null),
363
+ ...compactLocalLibraryLines('Scratchpad Notes', snapshot.localNotes, 'Create one with Create note.', workspace.selectedLocalLibraryItem('note')?.id ?? null),
364
+ { text: 'Notes stay local unless promoted by explicit action.', fg: PALETTE.warn },
500
365
  );
501
366
  } else if (category.id === 'personas') {
502
367
  base.push(
503
368
  { text: `Personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
504
- { text: 'Personas are local behavior profiles for the serial main-conversation assistant, not separate Agent jobs.', fg: PALETTE.good },
505
- { text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
506
- { text: '' },
507
- ...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one here with Create persona.', workspace.selectedLocalLibraryItem('persona')?.id ?? null),
369
+ ...compactLocalLibraryLines('Persona Library', snapshot.localPersonas, 'Create one with Create persona.', workspace.selectedLocalLibraryItem('persona')?.id ?? null),
370
+ { text: 'Personas shape the serial main-conversation assistant.', fg: PALETTE.good },
508
371
  );
509
372
  } else if (category.id === 'skills') {
510
373
  base.push(
511
374
  { text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; enabled bundles: ${snapshot.enabledSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
512
- { text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
513
- { text: 'Enabled skills and enabled bundles are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
514
- { text: '' },
515
- ...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one here with Create skill.', workspace.selectedLocalLibraryItem('skill')?.id ?? null),
516
- { text: '' },
517
- ...localLibraryLines('Skill Bundles', snapshot.localSkillBundles, 'No local skill bundles yet. Use Skill bundles and Create bundle after creating skills.', null),
375
+ ...compactLocalLibraryLines('Skill Library', snapshot.localSkills, 'Create one with Create skill.', workspace.selectedLocalLibraryItem('skill')?.id ?? null),
376
+ ...compactLocalLibraryLines('Skill Bundles', snapshot.localSkillBundles, 'Create one after adding skills.', null),
377
+ { text: 'Enabled skills/bundles become operating guidance; secrets are rejected.', fg: PALETTE.warn },
518
378
  );
519
379
  } else if (category.id === 'routines') {
520
380
  const ready = readyRoutineItems(snapshot);
@@ -523,42 +383,40 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
523
383
  base.push(
524
384
  { text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
525
385
  { 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 },
526
- { text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
527
- { text: 'Scheduling a reviewed routine is explicit and requires a confirmed schedule command.', fg: PALETTE.warn },
528
386
  routineNextActionLine(snapshot),
529
- ...routineScheduleReceiptLines(snapshot),
530
- { text: '' },
531
- ...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one here with Create routine.', workspace.selectedLocalLibraryItem('routine')?.id ?? null),
387
+ compactRoutineReceiptLine(snapshot),
388
+ ...compactLocalLibraryLines('Routine Library', snapshot.localRoutines, 'Create one with Create routine.', workspace.selectedLocalLibraryItem('routine')?.id ?? null),
389
+ { text: 'Scheduling requires a confirmed action.', fg: PALETTE.warn },
532
390
  );
533
391
  } else if (category.id === 'work') {
534
392
  base.push(
535
- { text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
536
- { text: 'This workspace does not approve, deny, cancel, or mutate requests by selection alone.', fg: PALETTE.good },
537
- { text: 'Approve, deny, and cancel forms require an approval id and typed confirmation.', fg: PALETTE.warn },
393
+ { text: 'Work plans and approvals are read or explicitly confirmed.', fg: PALETTE.info },
394
+ { text: 'Selection alone does not approve, deny, cancel, or mutate requests.', fg: PALETTE.good },
395
+ { text: 'Approval actions require id plus typed confirmation.', fg: PALETTE.warn },
538
396
  );
539
397
  } else if (category.id === 'automation') {
540
398
  const ready = readyRoutineItems(snapshot);
541
399
  base.push(
542
- { text: 'Automation and schedules default to read-only observability; side effects require confirmed forms.', fg: PALETTE.info },
543
- { text: 'Confirmed reminders and routine promotion use connected schedules only.', fg: PALETTE.info },
544
- { text: 'Local scheduler mutation controls remain blocked; job/run/schedule controls go through the connected host.', fg: PALETTE.warn },
545
- { text: 'Reminder path: Create reminder -> choose at/every/cron -> optional delivery target -> confirm yes.', fg: PALETTE.good },
546
- { text: 'Operator path: choose job/run/schedule action -> enter id -> type yes -> dispatch once.', fg: PALETTE.good },
547
- { text: 'Routine path: Routines -> resolve setup -> review selected -> Promote routine -> Reconcile schedules.', fg: PALETTE.good },
548
- { text: `Schedule-ready routines: ${ready.length}; local promotion receipts: ${snapshot.routineScheduleReceiptCount}`, fg: ready.length > 0 ? PALETTE.good : PALETTE.warn },
400
+ { text: `Automation: ${ready.length} schedule-ready routine(s); receipts ${snapshot.routineScheduleReceiptCount}.`, fg: ready.length > 0 ? PALETTE.good : PALETTE.warn },
549
401
  automationNextActionLine(snapshot),
550
- ...routineScheduleReceiptLines(snapshot),
402
+ compactRoutineReceiptLine(snapshot),
403
+ { text: 'Reminders and routine promotion require confirmation.', fg: PALETTE.warn },
551
404
  );
552
405
  } else if (category.id === 'delegate') {
553
406
  base.push(
554
- { text: 'Build/fix/review work is handed to GoodVibes TUI/shared-session contracts.', fg: PALETTE.info },
407
+ { text: 'Build/fix/review work is handed to GoodVibes TUI.', fg: PALETTE.info },
555
408
  { text: `Delegated review policy: ${snapshot.delegatedReviewPolicy}`, fg: PALETTE.warn },
556
- { text: 'Agent does not create coding-role Agent jobs.', fg: PALETTE.good },
409
+ { text: 'No coding-role Agent jobs are created here.', fg: PALETTE.good },
410
+ );
411
+ } else if (category.id === 'finish') {
412
+ base.push(
413
+ { text: 'Apply & close marks onboarding finished for this user.', fg: PALETTE.good },
414
+ { text: 'Future normal launches start in the main conversation.', fg: PALETTE.info },
415
+ { text: 'Use /agent, /setup, or /onboarding to reopen this workspace later.', fg: PALETTE.muted },
557
416
  );
558
417
  }
559
418
  if (snapshot.warnings.length > 0) {
560
- base.push({ text: '', dim: true }, { text: 'Warnings', fg: PALETTE.warn, bold: true });
561
- for (const warning of snapshot.warnings) base.push({ text: warning, fg: PALETTE.warn });
419
+ base.push({ text: `Warnings: ${snapshot.warnings.map((warning) => compactText(warning, 60)).join('; ')}`, fg: PALETTE.warn });
562
420
  }
563
421
  return base;
564
422
  }
@@ -567,30 +425,23 @@ function editorContextLines(editor: AgentWorkspaceLocalEditor): ContextLine[] {
567
425
  const selected = editor.fields[editor.selectedFieldIndex];
568
426
  const lines: ContextLine[] = [
569
427
  { text: editor.title, fg: PALETTE.title, bold: true },
570
- { text: editor.message, fg: editor.message.includes('required') || editor.message.includes('cannot') || editor.message.includes('Cannot') ? PALETTE.warn : PALETTE.info },
571
- { text: 'Enter advances fields and saves from the final field. Ctrl-J adds a line inside multiline fields. Esc cancels without writing.', fg: PALETTE.muted },
428
+ { text: compactText(editor.message), fg: editor.message.includes('required') || editor.message.includes('cannot') || editor.message.includes('Cannot') ? PALETTE.warn : PALETTE.info },
429
+ { text: 'Enter next/save; Ctrl-J newline; Esc cancel.', fg: PALETTE.muted },
572
430
  ];
573
431
  if (selected) {
574
432
  lines.push(
575
- { text: '' },
576
433
  { text: `Editing: ${selected.label}${selected.required ? ' (required)' : ''}`, fg: PALETTE.title, bold: true },
577
- { text: selected.hint, fg: PALETTE.muted },
434
+ { text: compactText(selected.hint), fg: PALETTE.muted },
578
435
  );
579
436
  }
580
437
  return lines;
581
438
  }
582
439
 
583
440
  function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
584
- const compactPrimarySurface = category.id === 'home' || category.id === 'setup';
585
441
  const lines: ContextLine[] = [
586
442
  { text: category.label, fg: PALETTE.title, bold: true },
587
443
  { text: category.summary, fg: PALETTE.subtitle },
588
- ...(compactPrimarySurface ? [] : [
589
- { text: '' },
590
- { text: category.detail, fg: PALETTE.text },
591
- ] satisfies ContextLine[]),
592
444
  ...(workspace.actionSearchActive ? [
593
- { text: '' },
594
445
  { text: 'Action Search', fg: PALETTE.title, bold: true },
595
446
  {
596
447
  text: workspace.actionSearchQuery.length > 0
@@ -598,35 +449,25 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
598
449
  : 'Type to search every Agent workspace action.',
599
450
  fg: workspace.actionSearchQuery.length > 0 && workspace.actionSearchResults.length === 0 ? PALETTE.warn : PALETTE.info,
600
451
  },
601
- { text: 'Enter opens the selected result. Esc clears search and returns to normal workspace navigation.', fg: PALETTE.muted },
602
- { text: '' },
452
+ { text: 'Enter opens; Esc clears.', fg: PALETTE.muted },
603
453
  ] satisfies ContextLine[] : []),
604
454
  ...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
605
- ...(workspace.localEditor ? [{ text: '' }] : []),
606
455
  ];
607
456
 
608
457
  const selectedActionLines: ContextLine[] = action
609
458
  ? [
610
- { text: '' },
611
459
  { text: `Selected: ${action.label}`, fg: PALETTE.title, bold: true },
612
- { text: action.detail, fg: PALETTE.text },
613
- { text: `Command: ${actionCommand(action)}`, fg: action.kind === 'command' ? PALETTE.info : PALETTE.muted },
614
- { text: `Safety: ${action.safety}`, fg: safetyColor(action) },
460
+ actionMetaLine(action),
615
461
  ]
616
462
  : [];
617
- const snapshotContextLines: ContextLine[] = [
618
- { text: '' },
619
- ...snapshotLines(workspace, category, workspace.runtimeSnapshot),
620
- ];
621
- if (compactPrimarySurface) lines.push(...selectedActionLines, ...snapshotContextLines);
622
- else lines.push(...snapshotContextLines, ...selectedActionLines);
463
+ const snapshotContextLines = snapshotLines(workspace, category, workspace.runtimeSnapshot);
464
+ lines.push(...selectedActionLines, ...snapshotContextLines);
623
465
 
624
466
  if (workspace.lastActionResult) {
625
467
  lines.push(
626
- { text: '' },
627
468
  { text: 'Action Result', fg: PALETTE.title, bold: true },
628
469
  { text: workspace.lastActionResult.title, fg: actionResultColor(workspace.lastActionResult), bold: true },
629
- { text: workspace.lastActionResult.detail, fg: PALETTE.text },
470
+ { text: compactText(workspace.lastActionResult.detail), fg: PALETTE.text },
630
471
  );
631
472
  if (workspace.lastActionResult.command) {
632
473
  lines.push({ text: `Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
@@ -762,12 +603,11 @@ function footerText(workspace: AgentWorkspace): string {
762
603
  export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, height: number): Line[] {
763
604
  const category = workspace.selectedActionCategory;
764
605
  const action = workspace.selectedAction;
765
- const compactPrimarySurface = category.id === 'home' || category.id === 'setup';
766
606
  const layoutOptions = {
767
607
  width,
768
608
  height,
769
609
  leftWidth: width < 90 ? undefined : 30,
770
- contextRatio: compactPrimarySurface ? 0.4 : 0.62,
610
+ contextRatio: 0.4,
771
611
  minContextRows: 10,
772
612
  };
773
613
  const metrics = getFullscreenWorkspaceMetrics(layoutOptions);
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.0.43';
9
+ let _version = '1.1.0';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;