@pellux/goodvibes-agent 0.1.76 → 0.1.78

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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.78 - 2026-06-01
6
+
7
+ - Expanded the Agent channel workspace into a concrete readiness matrix with setup state, missing runtime config keys, default-target posture, and safe next steps for each externally owned channel.
8
+ - Kept channel setup read-only inside Agent: no runtime lifecycle ownership, no hidden sends, and no secret values rendered.
9
+
10
+ ## 0.1.77 - 2026-06-01
11
+
12
+ - Added a first-class Agent profile creation form inside the fullscreen operator workspace instead of dispatching a placeholder command template.
13
+ - Surfaced profile and starter-template summaries directly in the Profiles workspace so day-one setup can pick an isolated Agent home from the TUI.
14
+ - Made Agent runtime profile creation refuse existing profile homes to protect local profile state across CLI, onboarding, and workspace flows.
15
+
5
16
  ## 0.1.76 - 2026-05-31
6
17
 
7
18
  - Added optional starter profile creation to first-run onboarding so setup can create an isolated Agent home seeded with persona, skill, and routine templates.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
@@ -727,6 +727,9 @@ export function listAgentRuntimeProfiles(baseHomeDirectory: string): readonly Ag
727
727
 
728
728
  export function createAgentRuntimeProfile(baseHomeDirectory: string, profileName: string, options: CreateAgentRuntimeProfileOptions = {}): AgentRuntimeProfileInfo {
729
729
  const resolution = resolveAgentRuntimeProfileHome(baseHomeDirectory, profileName);
730
+ if (existsSync(resolution.homeDirectory)) {
731
+ throw new Error(`Agent profile already exists: ${resolution.id}`);
732
+ }
730
733
  mkdirSync(resolution.homeDirectory, { recursive: true });
731
734
  const createdAt = new Date().toISOString();
732
735
  const appliedTemplate = options.templateId
@@ -98,7 +98,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
98
98
  { id: 'profile-sync-list', label: 'Profile sync list', detail: 'Inspect saved config profiles available for export/import.', command: '/profilesync list', kind: 'command', safety: 'read-only' },
99
99
  { id: 'profile-sync-export', label: 'Export profile sync', detail: 'Export config profiles to a portable bundle. Requires a real path and explicit --yes.', command: '/profilesync export <path> --yes', kind: 'command', safety: 'safe' },
100
100
  { id: 'setup-transfer-export', label: 'Export setup transfer', detail: 'Export Agent setup transfer data from the current home. Requires a real path and explicit --yes.', command: '/setup transfer export <path> --yes', kind: 'command', safety: 'safe' },
101
- { id: 'runtime-profile-create', label: 'Create Agent profile', detail: 'Create an isolated Agent profile from a built-in or local starter. Requires a real name and explicit --yes.', command: '/agent-profile create <name> --template <id> --yes', kind: 'command', safety: 'safe' },
101
+ { id: 'runtime-profile-create', label: 'Create Agent profile', detail: 'Open an in-workspace form that creates an isolated Agent home from a built-in or local starter.', editorKind: 'profile', kind: 'editor', safety: 'safe' },
102
102
  { id: 'runtime-profile-template-edit', label: 'Customize starter', detail: 'Export a starter JSON file, edit it, import it as a local starter, then create a profile from it.', command: '/agent-profile template export <id> <path> --yes', kind: 'command', safety: 'safe' },
103
103
  { id: 'runtime-profile-switch', label: 'Switch Agent profile', detail: 'Launch goodvibes-agent --agent-profile <name> to use that isolated Agent home. This workspace cannot switch the current process home after startup.', kind: 'guidance', safety: 'safe' },
104
104
  ],
@@ -7,11 +7,17 @@ export interface AgentWorkspaceChannelStatus {
7
7
  readonly label: string;
8
8
  readonly enabled: boolean;
9
9
  readonly ready: boolean;
10
+ readonly requiredKeys: readonly string[];
11
+ readonly missingRequiredKeys: readonly string[];
10
12
  readonly missingConfigCount: number;
13
+ readonly defaultTargetKeys: readonly string[];
14
+ readonly configuredDefaultTargetKeys: readonly string[];
11
15
  readonly defaultTarget: 'configured' | 'missing' | 'not-required';
12
16
  readonly delivery: 'disabled' | 'blocked' | 'explicit-target' | 'default-ready';
13
17
  readonly risk: AgentWorkspaceChannelRisk;
14
18
  readonly riskLabel: string;
19
+ readonly setupState: 'disabled' | 'needs-config' | 'needs-target' | 'ready';
20
+ readonly nextStep: string;
15
21
  }
16
22
 
17
23
  interface AgentWorkspaceConfigReader {
@@ -182,10 +188,12 @@ function hasConfigValue(context: CommandContext, key: string): boolean {
182
188
 
183
189
  function buildChannelStatus(context: CommandContext, spec: AgentWorkspaceChannelSpec): AgentWorkspaceChannelStatus {
184
190
  const enabled = readConfigBoolean(context, spec.enabledKey, false);
185
- const missingConfigCount = spec.requiredKeys.filter((key) => !hasConfigValue(context, key)).length;
191
+ const missingRequiredKeys = spec.requiredKeys.filter((key) => !hasConfigValue(context, key));
192
+ const configuredDefaultTargetKeys = spec.defaultTargetKeys.filter((key) => hasConfigValue(context, key));
193
+ const missingConfigCount = missingRequiredKeys.length;
186
194
  const defaultTarget = spec.defaultTargetKeys.length === 0
187
195
  ? 'not-required'
188
- : spec.defaultTargetKeys.some((key) => hasConfigValue(context, key))
196
+ : configuredDefaultTargetKeys.length > 0
189
197
  ? 'configured'
190
198
  : 'missing';
191
199
  const ready = enabled && missingConfigCount === 0;
@@ -196,16 +204,36 @@ function buildChannelStatus(context: CommandContext, spec: AgentWorkspaceChannel
196
204
  : defaultTarget === 'configured'
197
205
  ? 'default-ready'
198
206
  : 'explicit-target';
207
+ const setupState = !enabled
208
+ ? 'disabled'
209
+ : missingConfigCount > 0
210
+ ? 'needs-config'
211
+ : defaultTarget === 'missing'
212
+ ? 'needs-target'
213
+ : 'ready';
214
+ const nextStep = setupState === 'disabled'
215
+ ? `Enable ${spec.label} in the owning GoodVibes runtime before Agent can use it.`
216
+ : setupState === 'needs-config'
217
+ ? `Configure ${missingRequiredKeys.join(', ')} in the owning runtime or secret manager.`
218
+ : setupState === 'needs-target'
219
+ ? `Provide an explicit delivery target per send, or configure one of ${spec.defaultTargetKeys.join(', ')}.`
220
+ : `Use explicit user action or runtime policy to send through ${spec.label}.`;
199
221
  return {
200
222
  id: spec.id,
201
223
  label: spec.label,
202
224
  enabled,
203
225
  ready,
226
+ requiredKeys: spec.requiredKeys,
227
+ missingRequiredKeys,
204
228
  missingConfigCount,
229
+ defaultTargetKeys: spec.defaultTargetKeys,
230
+ configuredDefaultTargetKeys,
205
231
  defaultTarget,
206
232
  delivery,
207
233
  risk: spec.risk,
208
234
  riskLabel: spec.riskLabel,
235
+ setupState,
236
+ nextStep,
209
237
  };
210
238
  }
211
239
 
@@ -1,9 +1,36 @@
1
1
  import type { AgentPersonaRecord } from '../agent/persona-registry.ts';
2
2
  import type { AgentRoutineRecord } from '../agent/routine-registry.ts';
3
3
  import type { AgentSkillRecord } from '../agent/skill-registry.ts';
4
- import type { AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem } from './agent-workspace-types.ts';
4
+ import type {
5
+ AgentWorkspaceLocalEditor,
6
+ AgentWorkspaceLocalEditorKind,
7
+ AgentWorkspaceLocalLibraryItem,
8
+ AgentWorkspaceRuntimeStarterTemplateItem,
9
+ } from './agent-workspace-types.ts';
10
+
11
+ export function createProfileEditor(templates: readonly AgentWorkspaceRuntimeStarterTemplateItem[]): AgentWorkspaceLocalEditor {
12
+ const defaultTemplate = templates.find((template) => template.id === 'research')?.id ?? templates[0]?.id ?? 'none';
13
+ const preview = templates.length === 0
14
+ ? 'No starter templates found; use none to create an empty isolated profile.'
15
+ : templates
16
+ .slice(0, 6)
17
+ .map((template) => `${template.id} (${template.name})`)
18
+ .join(', ');
19
+ return {
20
+ kind: 'profile',
21
+ mode: 'create',
22
+ title: 'Create Agent Profile',
23
+ selectedFieldIndex: 0,
24
+ message: 'Create an isolated Agent home seeded with a persona, skills, and routines. The current process keeps using its existing home until relaunched with --agent-profile.',
25
+ fields: [
26
+ { id: 'name', label: 'Profile name', value: '', required: true, multiline: false, hint: 'Short profile name. It normalizes to lowercase letters, numbers, dots, underscores, and dashes.' },
27
+ { id: 'template', label: 'Starter template', value: defaultTemplate, required: false, multiline: false, hint: `Template id or none. Available: ${preview}.` },
28
+ ],
29
+ };
30
+ }
5
31
 
6
32
  export function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalEditor {
33
+ if (kind === 'profile') return createProfileEditor([]);
7
34
  if (kind === 'persona') {
8
35
  return {
9
36
  kind,
@@ -137,6 +164,7 @@ export function isAffirmative(value: string): boolean {
137
164
  }
138
165
 
139
166
  export function editorCategoryId(kind: AgentWorkspaceLocalEditorKind): string {
167
+ if (kind === 'profile') return 'profiles';
140
168
  if (kind === 'persona') return 'personas';
141
169
  if (kind === 'skill') return 'skills';
142
170
  return 'routines';
@@ -6,7 +6,12 @@ import { AgentSkillRegistry, type AgentSkillRecord } from '../agent/skill-regist
6
6
  import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles, listAgentRuntimeProfileTemplates } from '../agent/runtime-profile.ts';
7
7
  import { buildAgentWorkspaceChannels } from './agent-workspace-channels.ts';
8
8
  import { buildAgentWorkspaceSetupChecklist } from './agent-workspace-setup.ts';
9
- import type { AgentWorkspaceLocalLibraryItem, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
9
+ import type {
10
+ AgentWorkspaceLocalLibraryItem,
11
+ AgentWorkspaceRuntimeProfileItem,
12
+ AgentWorkspaceRuntimeSnapshot,
13
+ AgentWorkspaceRuntimeStarterTemplateItem,
14
+ } from './agent-workspace-types.ts';
10
15
 
11
16
  type AgentWorkspaceConfigReader = {
12
17
  get(key: string): unknown;
@@ -94,6 +99,28 @@ function summarizeRoutineItem(routine: AgentRoutineRecord): AgentWorkspaceLocalL
94
99
  };
95
100
  }
96
101
 
102
+ function summarizeRuntimeProfile(profile: ReturnType<typeof listAgentRuntimeProfiles>[number]): AgentWorkspaceRuntimeProfileItem {
103
+ return {
104
+ id: profile.id,
105
+ homeDirectory: profile.homeDirectory,
106
+ createdAt: profile.createdAt,
107
+ starterTemplateId: profile.starterTemplateId,
108
+ starterTemplateName: profile.starterTemplateName,
109
+ };
110
+ }
111
+
112
+ function summarizeStarterTemplate(template: ReturnType<typeof listAgentRuntimeProfileTemplates>[number]): AgentWorkspaceRuntimeStarterTemplateItem {
113
+ return {
114
+ id: template.id,
115
+ name: template.name,
116
+ description: template.description,
117
+ personaName: template.personaName,
118
+ skillNames: template.skillNames,
119
+ routineNames: template.routineNames,
120
+ source: template.source,
121
+ };
122
+ }
123
+
97
124
  export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): AgentWorkspaceRuntimeSnapshot {
98
125
  const host = readConfigString(context, 'controlPlane.host', '127.0.0.1');
99
126
  const port = readConfigNumber(context, 'controlPlane.port', 3421);
@@ -255,9 +282,11 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
255
282
  browserSurfacePublicBaseUrl: readConfigString(context, 'web.publicBaseUrl', '(not configured)'),
256
283
  activeRuntimeProfile: inferActiveRuntimeProfile(context.workspace?.shellPaths?.homeDirectory ?? ''),
257
284
  runtimeProfileCount: runtimeProfiles.length,
285
+ runtimeProfiles: runtimeProfiles.map(summarizeRuntimeProfile),
258
286
  runtimeProfileRoot: getAgentRuntimeProfilesRoot(context.workspace?.shellPaths?.homeDirectory ?? ''),
259
287
  runtimeStarterTemplateCount: runtimeStarterTemplates.length,
260
288
  localStarterTemplateCount: runtimeStarterTemplates.filter((template) => template.source === 'local').length,
289
+ runtimeStarterTemplates: runtimeStarterTemplates.map(summarizeStarterTemplate),
261
290
  configProfileCount,
262
291
  setupChecklist,
263
292
  warnings,
@@ -7,7 +7,7 @@ export type AgentWorkspaceFocusPane = 'categories' | 'actions';
7
7
 
8
8
  export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'local-selection' | 'local-operation';
9
9
 
10
- export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine';
10
+ export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine' | 'profile';
11
11
 
12
12
  export type AgentWorkspaceLocalOperation =
13
13
  | 'persona-edit'
@@ -94,6 +94,24 @@ export interface AgentWorkspaceLocalLibraryItem {
94
94
  readonly startCount?: number;
95
95
  }
96
96
 
97
+ export interface AgentWorkspaceRuntimeProfileItem {
98
+ readonly id: string;
99
+ readonly homeDirectory: string;
100
+ readonly createdAt: string | null;
101
+ readonly starterTemplateId?: string;
102
+ readonly starterTemplateName?: string;
103
+ }
104
+
105
+ export interface AgentWorkspaceRuntimeStarterTemplateItem {
106
+ readonly id: string;
107
+ readonly name: string;
108
+ readonly description: string;
109
+ readonly personaName: string;
110
+ readonly skillNames: readonly string[];
111
+ readonly routineNames: readonly string[];
112
+ readonly source: string;
113
+ }
114
+
97
115
  export interface AgentWorkspaceRuntimeSnapshot {
98
116
  readonly provider: string;
99
117
  readonly model: string;
@@ -133,9 +151,11 @@ export interface AgentWorkspaceRuntimeSnapshot {
133
151
  readonly browserSurfacePublicBaseUrl: string;
134
152
  readonly activeRuntimeProfile: string;
135
153
  readonly runtimeProfileCount: number;
154
+ readonly runtimeProfiles: readonly AgentWorkspaceRuntimeProfileItem[];
136
155
  readonly runtimeProfileRoot: string;
137
156
  readonly runtimeStarterTemplateCount: number;
138
157
  readonly localStarterTemplateCount: number;
158
+ readonly runtimeStarterTemplates: readonly AgentWorkspaceRuntimeStarterTemplateItem[];
139
159
  readonly configProfileCount: number;
140
160
  readonly setupChecklist: readonly AgentWorkspaceSetupChecklistItem[];
141
161
  readonly warnings: readonly string[];
@@ -3,9 +3,10 @@ import type { ShellPathService } from '@/runtime/index.ts';
3
3
  import type { CommandContext } from './command-registry.ts';
4
4
  import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
5
5
  import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
6
+ import { createAgentRuntimeProfile, type AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
6
7
  import { AgentSkillRegistry } from '../agent/skill-registry.ts';
7
8
  import { AGENT_WORKSPACE_CATEGORIES } from './agent-workspace-categories.ts';
8
- import { createDeleteEditor, createLocalEditor, createPersonaUpdateEditor, createRoutineUpdateEditor, createSkillUpdateEditor, editorCategoryId, isAffirmative, splitList } from './agent-workspace-editors.ts';
9
+ import { createDeleteEditor, createLocalEditor, createPersonaUpdateEditor, createProfileEditor, createRoutineUpdateEditor, createSkillUpdateEditor, editorCategoryId, isAffirmative, splitList } from './agent-workspace-editors.ts';
9
10
  import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
10
11
  import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
11
12
 
@@ -25,7 +26,6 @@ export type {
25
26
  } from './agent-workspace-types.ts';
26
27
  export { AGENT_WORKSPACE_MODAL_NAME } from './agent-workspace-types.ts';
27
28
  export { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
28
-
29
29
  function parseCommand(command: string): { readonly name: string; readonly args: readonly string[] } {
30
30
  const trimmed = command.trim().replace(/^\//, '');
31
31
  if (!trimmed) return { name: '', args: [] };
@@ -46,6 +46,7 @@ export class AgentWorkspace {
46
46
  persona: 0,
47
47
  skill: 0,
48
48
  routine: 0,
49
+ profile: 0,
49
50
  };
50
51
  private context: CommandContext | null = null;
51
52
  private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
@@ -228,7 +229,9 @@ export class AgentWorkspace {
228
229
  const action = this.selectedAction;
229
230
  if (!action) return;
230
231
  if (action.kind === 'editor' && action.editorKind) {
231
- this.localEditor = createLocalEditor(action.editorKind);
232
+ this.localEditor = action.editorKind === 'profile'
233
+ ? createProfileEditor(this.runtimeSnapshot?.runtimeStarterTemplates ?? [])
234
+ : createLocalEditor(action.editorKind);
232
235
  this.status = `Editing ${this.localEditor.title}.`;
233
236
  this.lastActionResult = {
234
237
  kind: 'guidance',
@@ -347,6 +350,7 @@ export class AgentWorkspace {
347
350
  private localLibraryItems(kind: AgentWorkspaceLocalEditorKind): readonly AgentWorkspaceLocalLibraryItem[] {
348
351
  if (kind === 'persona') return this.runtimeSnapshot?.localPersonas ?? [];
349
352
  if (kind === 'skill') return this.runtimeSnapshot?.localSkills ?? [];
353
+ if (kind === 'profile') return [];
350
354
  return this.runtimeSnapshot?.localRoutines ?? [];
351
355
  }
352
356
 
@@ -564,7 +568,14 @@ export class AgentWorkspace {
564
568
  this.submitLocalDeleteEditor(shellPaths, editor);
565
569
  return;
566
570
  }
567
- if (editor.kind === 'persona') {
571
+ if (editor.kind === 'profile') {
572
+ const template = this.editorField('template');
573
+ const templateId = template && template.toLowerCase() !== 'none' ? template : undefined;
574
+ const profile = createAgentRuntimeProfile(shellPaths.homeDirectory, this.editorField('name'), {
575
+ ...(templateId ? { templateId } : {}),
576
+ });
577
+ this.finishProfileEditor(profile);
578
+ } else if (editor.kind === 'persona') {
568
579
  const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
569
580
  if (editor.mode === 'update' && editor.recordId) {
570
581
  const wasActive = registry.snapshot().activePersonaId === editor.recordId;
@@ -699,6 +710,26 @@ export class AgentWorkspace {
699
710
  this.clampSelection();
700
711
  }
701
712
 
713
+ private finishProfileEditor(profile: AgentRuntimeProfileInfo): void {
714
+ this.localEditor = null;
715
+ const categoryIndex = this.categories.findIndex((category) => category.id === 'profiles');
716
+ if (categoryIndex >= 0) {
717
+ this.selectedCategoryIndex = categoryIndex;
718
+ this.selectedActionIndex = this.categories[categoryIndex]?.actions.findIndex((action) => action.id === 'runtime-profile-create') ?? 0;
719
+ if (this.selectedActionIndex < 0) this.selectedActionIndex = 0;
720
+ }
721
+ this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
722
+ const starter = profile.starterTemplateId ? ` from ${profile.starterTemplateId}` : '';
723
+ this.status = `Created Agent profile: ${profile.id}.`;
724
+ this.lastActionResult = {
725
+ kind: 'refreshed',
726
+ title: 'Created Agent profile',
727
+ detail: `Created isolated Agent profile ${profile.id}${starter}. Launch it with goodvibes-agent --agent-profile ${profile.id}. The current TUI session was not switched.`,
728
+ safety: 'safe',
729
+ };
730
+ this.clampSelection();
731
+ }
732
+
702
733
  private finishLocalDelete(kind: AgentWorkspaceLocalEditorKind, id: string, name: string): void {
703
734
  this.localEditor = null;
704
735
  const categoryId = editorCategoryId(kind);
@@ -142,6 +142,47 @@ function localLibraryLines(
142
142
  return lines;
143
143
  }
144
144
 
145
+ function profileLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
146
+ const lines: ContextLine[] = [
147
+ { text: 'Agent Profiles', fg: PALETTE.title, bold: true },
148
+ ];
149
+ if (snapshot.runtimeProfiles.length === 0) {
150
+ lines.push({ text: 'No isolated Agent profiles yet. Use Create Agent profile in this workspace.', fg: PALETTE.warn });
151
+ return lines;
152
+ }
153
+ for (const profile of snapshot.runtimeProfiles.slice(0, 6)) {
154
+ const starter = profile.starterTemplateId ? ` starter=${profile.starterTemplateId}` : ' starter=none';
155
+ const created = profile.createdAt ? ` created=${profile.createdAt.slice(0, 10)}` : '';
156
+ lines.push({ text: `${profile.id}${starter}${created}`, fg: PALETTE.info, bold: profile.id === snapshot.activeRuntimeProfile });
157
+ lines.push({ text: ` home: ${profile.homeDirectory}`, fg: PALETTE.muted });
158
+ }
159
+ if (snapshot.runtimeProfiles.length > 6) {
160
+ lines.push({ text: `${snapshot.runtimeProfiles.length - 6} more profile(s).`, fg: PALETTE.dim });
161
+ }
162
+ return lines;
163
+ }
164
+
165
+ function starterTemplateLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLine[] {
166
+ const lines: ContextLine[] = [
167
+ { text: 'Starter Templates', fg: PALETTE.title, bold: true },
168
+ ];
169
+ for (const template of snapshot.runtimeStarterTemplates.slice(0, 6)) {
170
+ lines.push({
171
+ text: `${template.id}: ${template.name} [${template.source}]`,
172
+ fg: template.source === 'local' ? PALETTE.good : PALETTE.info,
173
+ bold: template.id === 'research',
174
+ });
175
+ lines.push({
176
+ text: ` ${template.description} Persona ${template.personaName}; skills ${template.skillNames.join(', ')}; routines ${template.routineNames.join(', ')}.`,
177
+ fg: PALETTE.muted,
178
+ });
179
+ }
180
+ if (snapshot.runtimeStarterTemplates.length > 6) {
181
+ lines.push({ text: `${snapshot.runtimeStarterTemplates.length - 6} more starter template(s).`, fg: PALETTE.dim });
182
+ }
183
+ return lines;
184
+ }
185
+
145
186
  function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
146
187
  if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
147
188
  const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
@@ -164,23 +205,34 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
164
205
  const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
165
206
  const readyCount = snapshot.channels.filter((channel) => channel.ready).length;
166
207
  const configuredDefaults = snapshot.channels.filter((channel) => channel.defaultTarget === 'configured').length;
167
- const disabledChannels = snapshot.channels.filter((channel) => !channel.enabled).map((channel) => channel.label).join(', ');
208
+ const readyChannels = snapshot.channels.filter((channel) => channel.ready).map((channel) => channel.label);
209
+ const needsTarget = snapshot.channels.filter((channel) => channel.setupState === 'needs-target');
210
+ const needsConfig = snapshot.channels.filter((channel) => channel.setupState === 'needs-config');
211
+ const disabledChannels = snapshot.channels.filter((channel) => !channel.enabled).map((channel) => channel.label);
212
+ const disabledPreview = disabledChannels.slice(0, 6).join(', ');
213
+ const disabledSuffix = disabledChannels.length > 6 ? `, +${disabledChannels.length - 6} more` : '';
214
+ const orderedChannels = [
215
+ ...snapshot.channels.filter((channel) => channel.enabled),
216
+ ...snapshot.channels.filter((channel) => !channel.enabled),
217
+ ].slice(0, 6);
168
218
  base.push(
169
219
  { text: `GoodVibes runtime: ${snapshot.daemonBaseUrl}`, fg: PALETTE.info },
170
220
  { text: `Readiness: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} default target(s) configured.`, fg: PALETTE.info },
171
- { text: `Disabled channels: ${disabledChannels || 'none'}.`, fg: PALETTE.dim },
172
- { text: 'Pairing: use /pair or /qrcode for companion setup.', fg: PALETTE.info },
173
- { text: 'Channel posture: inspect via /communication and /setup review.', fg: PALETTE.muted },
174
- { text: 'Safety: external delivery, unknown senders, and public exposure require explicit policy and user action.', fg: PALETTE.warn },
221
+ { text: `Ready channels: ${readyChannels.join(', ') || 'none'}.`, fg: readyChannels.length > 0 ? PALETTE.good : PALETTE.warn },
222
+ { text: `Needs default target: ${needsTarget.map((channel) => `${channel.label} -> ${channel.defaultTargetKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsTarget.length > 0 ? PALETTE.warn : PALETTE.muted },
223
+ { text: `Needs config: ${needsConfig.map((channel) => `${channel.label} -> ${channel.missingRequiredKeys.join('|')}`).join(', ') || 'none'}.`, fg: needsConfig.length > 0 ? PALETTE.warn : PALETTE.muted },
224
+ { text: `Disabled channels: ${disabledPreview || 'none'}${disabledSuffix}.`, fg: PALETTE.dim },
225
+ { text: 'Safety: no secret values; sends and public exposure require explicit user action and runtime policy.', fg: PALETTE.warn },
175
226
  );
176
- for (const channel of snapshot.channels) {
177
- const enabled = channel.enabled ? 'enabled' : 'disabled';
227
+ for (const channel of orderedChannels) {
178
228
  const ready = channel.ready ? 'ready' : `${channel.missingConfigCount} missing`;
179
229
  base.push({
180
- text: `${channel.label}: ${enabled}; ${ready}; default ${channel.defaultTarget}; delivery ${channel.delivery}; risk ${channel.riskLabel}.`,
230
+ text: `${channel.label}: ${channel.setupState}; ${ready}; target ${channel.defaultTarget}; delivery ${channel.delivery}; risk ${channel.risk}.`,
181
231
  fg: channel.ready ? PALETTE.good : channel.enabled ? PALETTE.warn : PALETTE.dim,
182
232
  });
183
233
  }
234
+ base.push({ text: 'Only config key names and readiness state are rendered here.', fg: PALETTE.muted });
235
+ base.push({ text: 'Pairing: use /pair or /qrcode; inspect routed activity with /communication and /setup review.', fg: PALETTE.info });
184
236
  } else if (category.id === 'knowledge') {
185
237
  base.push(
186
238
  { text: `Route family: ${snapshot.knowledgeRoute}/{status,ask,search}`, fg: PALETTE.info },
@@ -206,8 +258,14 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
206
258
  { text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
207
259
  { text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
208
260
  { text: `Config profiles: ${snapshot.configProfileCount}`, fg: PALETTE.info },
261
+ { text: `Starter ids: ${snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none'}`, fg: PALETTE.info },
262
+ { text: '' },
263
+ ...profileLines(snapshot),
264
+ { text: '' },
265
+ ...starterTemplateLines(snapshot),
266
+ { text: '' },
209
267
  { text: 'Named Agent profiles isolate local config, sessions, memory, personas, skills, routines, setup, and bundles.', fg: PALETTE.good },
210
- { text: 'Starter authoring: browse, export, edit, import, and create Agent profiles from inside this workspace via /agent-profile.', fg: PALETTE.info },
268
+ { text: 'Starter authoring: browse, export, edit, import, and create Agent profiles from inside this workspace.', fg: PALETTE.info },
211
269
  { text: 'The external GoodVibes runtime remains shared unless the owning host is configured separately.', fg: PALETTE.warn },
212
270
  { text: 'Portable bundles require explicit export/import commands with real paths and --yes.', fg: PALETTE.muted },
213
271
  );
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 = '0.1.76';
9
+ let _version = '0.1.78';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {