@pellux/goodvibes-agent 1.1.3 → 1.1.4

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.
@@ -0,0 +1,314 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import type { ConfigKey, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
3
+ import { setHarnessSetting } from '../agent/harness-control.ts';
4
+ import { isExternalHostOwnedSettingKey } from '../config/agent-settings-policy.ts';
5
+ import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
6
+ import type { CommandContext } from './command-registry.ts';
7
+ import type {
8
+ AgentWorkspaceAction,
9
+ AgentWorkspaceActionResult,
10
+ AgentWorkspaceLocalEditor,
11
+ AgentWorkspaceRuntimeSnapshot,
12
+ } from './agent-workspace-types.ts';
13
+
14
+ const GOODVIBES_TUI_SURFACE_ROOT = 'tui';
15
+
16
+ const TUI_IMPORTABLE_SETTING_PREFIXES = [
17
+ 'display.',
18
+ 'provider.',
19
+ 'behavior.',
20
+ 'storage.',
21
+ 'permissions.',
22
+ 'ui.',
23
+ 'tts.',
24
+ 'surfaces.',
25
+ 'helper.',
26
+ 'tools.',
27
+ 'release.',
28
+ 'automation.',
29
+ ] as const;
30
+
31
+ type SettingActionEffect =
32
+ | {
33
+ readonly kind: 'result';
34
+ readonly status: string;
35
+ readonly result: AgentWorkspaceActionResult;
36
+ }
37
+ | {
38
+ readonly kind: 'apply';
39
+ readonly setting: ConfigSetting;
40
+ readonly value: unknown;
41
+ }
42
+ | {
43
+ readonly kind: 'editor';
44
+ readonly editor: AgentWorkspaceLocalEditor;
45
+ readonly status: string;
46
+ readonly result: AgentWorkspaceActionResult;
47
+ };
48
+
49
+ export interface SettingMutationOutcome {
50
+ readonly status: string;
51
+ readonly result: AgentWorkspaceActionResult;
52
+ }
53
+
54
+ export interface TuiSettingsImportOutcome extends SettingMutationOutcome {
55
+ readonly runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null;
56
+ }
57
+
58
+ function isRecord(value: unknown): value is Record<string, unknown> {
59
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
60
+ }
61
+
62
+ function readJsonRecord(path: string): Record<string, unknown> | null {
63
+ if (!existsSync(path)) return null;
64
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
65
+ return isRecord(parsed) ? parsed : null;
66
+ }
67
+
68
+ function readNestedSettingValue(record: Record<string, unknown>, key: string): unknown {
69
+ let cursor: unknown = record;
70
+ for (const part of key.split('.')) {
71
+ if (!isRecord(cursor) || !(part in cursor)) return undefined;
72
+ cursor = cursor[part];
73
+ }
74
+ return cursor;
75
+ }
76
+
77
+ function canImportTuiSetting(key: string): boolean {
78
+ return !isExternalHostOwnedSettingKey(key)
79
+ && TUI_IMPORTABLE_SETTING_PREFIXES.some((prefix) => key.startsWith(prefix));
80
+ }
81
+
82
+ function valuesMatch(left: unknown, right: unknown): boolean {
83
+ return JSON.stringify(left) === JSON.stringify(right);
84
+ }
85
+
86
+ export function agentWorkspaceSettingSchema(context: CommandContext | null, key: string): ConfigSetting | null {
87
+ return context?.platform?.configManager
88
+ ?.getSchema()
89
+ .find((setting) => setting.key === key) ?? null;
90
+ }
91
+
92
+ export function isAgentWorkspaceActionVisible(context: CommandContext | null, action: AgentWorkspaceAction): boolean {
93
+ const key = action.visibleWhenSettingKey?.trim();
94
+ if (!key) return true;
95
+ const configManager = context?.platform?.configManager;
96
+ if (!configManager) return false;
97
+ return configManager.get(key as ConfigKey) === action.visibleWhenSettingValue;
98
+ }
99
+
100
+ export function buildAgentWorkspaceSettingActionEffect(
101
+ context: CommandContext | null,
102
+ action: AgentWorkspaceAction,
103
+ ): SettingActionEffect {
104
+ const settingKey = action.settingKey?.trim();
105
+ const configManager = context?.platform?.configManager;
106
+ if (!settingKey || !configManager) {
107
+ return {
108
+ kind: 'result',
109
+ status: 'Setting is unavailable in this runtime.',
110
+ result: {
111
+ kind: 'error',
112
+ title: 'Setting unavailable',
113
+ detail: action.detail,
114
+ safety: action.safety,
115
+ },
116
+ };
117
+ }
118
+ const setting = agentWorkspaceSettingSchema(context, settingKey);
119
+ if (!setting) {
120
+ return {
121
+ kind: 'result',
122
+ status: `Unknown setting: ${settingKey}`,
123
+ result: {
124
+ kind: 'error',
125
+ title: 'Unknown setting',
126
+ detail: `No Agent setting exists for ${settingKey}.`,
127
+ safety: action.safety,
128
+ },
129
+ };
130
+ }
131
+
132
+ if (action.settingValueHint !== undefined) {
133
+ return { kind: 'apply', setting, value: action.settingValueHint };
134
+ }
135
+
136
+ const currentValue = configManager.get(setting.key as ConfigKey);
137
+ if (setting.type === 'boolean') {
138
+ return { kind: 'apply', setting, value: !Boolean(currentValue) };
139
+ }
140
+ if (setting.type === 'enum' && setting.enumValues && setting.enumValues.length > 0) {
141
+ const currentIndex = Math.max(0, setting.enumValues.indexOf(String(currentValue)));
142
+ return { kind: 'apply', setting, value: setting.enumValues[(currentIndex + 1) % setting.enumValues.length]! };
143
+ }
144
+
145
+ return {
146
+ kind: 'editor',
147
+ editor: createSettingEditor(setting, String(currentValue ?? ''), action),
148
+ status: `Editing ${setting.key}.`,
149
+ result: {
150
+ kind: 'guidance',
151
+ title: `Edit ${setting.key}`,
152
+ detail: setting.description,
153
+ safety: action.safety,
154
+ },
155
+ };
156
+ }
157
+
158
+ export async function applyAgentWorkspaceSettingValue(
159
+ context: CommandContext | null,
160
+ setting: ConfigSetting,
161
+ value: unknown,
162
+ ): Promise<SettingMutationOutcome> {
163
+ const configManager = context?.platform?.configManager;
164
+ if (!configManager) {
165
+ return {
166
+ status: 'Setting is unavailable in this runtime.',
167
+ result: {
168
+ kind: 'error',
169
+ title: `${setting.key} update failed`,
170
+ detail: 'The Agent workspace has no config manager for this runtime.',
171
+ safety: 'safe',
172
+ },
173
+ };
174
+ }
175
+ try {
176
+ const result = await setHarnessSetting(configManager, context?.platform?.secretsManager, setting.key, value);
177
+ return {
178
+ status: `${result.key} set.`,
179
+ result: {
180
+ kind: 'refreshed',
181
+ title: `${result.key} updated`,
182
+ detail: `Current value: ${String(result.current)}`,
183
+ safety: 'safe',
184
+ },
185
+ };
186
+ } catch (error) {
187
+ const detail = error instanceof Error ? error.message : String(error);
188
+ return {
189
+ status: detail,
190
+ result: {
191
+ kind: 'error',
192
+ title: `${setting.key} update failed`,
193
+ detail,
194
+ safety: 'safe',
195
+ },
196
+ };
197
+ }
198
+ }
199
+
200
+ export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
201
+ const shellPaths = context?.workspace?.shellPaths;
202
+ const configManager = context?.platform?.configManager;
203
+ if (!shellPaths || !configManager) {
204
+ return {
205
+ status: 'GoodVibes TUI settings import is unavailable in this runtime.',
206
+ runtimeSnapshot: null,
207
+ result: {
208
+ kind: 'error',
209
+ title: 'Import unavailable',
210
+ detail: 'The workspace cannot locate shell paths or the Agent config manager.',
211
+ safety: 'safe',
212
+ },
213
+ };
214
+ }
215
+
216
+ const sources = [
217
+ { label: 'user', path: shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json') },
218
+ { label: 'project', path: shellPaths.resolveProjectPath(GOODVIBES_TUI_SURFACE_ROOT, 'settings.json') },
219
+ ];
220
+ const values = new Map<string, { readonly value: unknown; readonly source: string }>();
221
+ const parseErrors: string[] = [];
222
+ for (const source of sources) {
223
+ try {
224
+ const record = readJsonRecord(source.path);
225
+ if (!record) continue;
226
+ for (const setting of configManager.getSchema()) {
227
+ if (!canImportTuiSetting(setting.key)) continue;
228
+ const value = readNestedSettingValue(record, setting.key);
229
+ if (value !== undefined) values.set(setting.key, { value, source: source.label });
230
+ }
231
+ } catch (error) {
232
+ parseErrors.push(`${source.label}: ${error instanceof Error ? error.message : String(error)}`);
233
+ }
234
+ }
235
+
236
+ if (values.size === 0) {
237
+ const detail = parseErrors.length > 0
238
+ ? `No importable settings found. ${parseErrors.join('; ')}`
239
+ : 'No GoodVibes TUI settings file with importable Agent-owned settings was found.';
240
+ return {
241
+ status: 'No GoodVibes TUI settings imported.',
242
+ runtimeSnapshot: null,
243
+ result: {
244
+ kind: parseErrors.length > 0 ? 'error' : 'guidance',
245
+ title: 'Nothing imported',
246
+ detail,
247
+ safety: 'safe',
248
+ },
249
+ };
250
+ }
251
+
252
+ const imported: string[] = [];
253
+ const unchanged: string[] = [];
254
+ const skipped: string[] = [];
255
+ for (const [key, entry] of values) {
256
+ const setting = agentWorkspaceSettingSchema(context, key);
257
+ if (!setting) continue;
258
+ if (valuesMatch(configManager.get(setting.key as ConfigKey), entry.value)) {
259
+ unchanged.push(setting.key);
260
+ continue;
261
+ }
262
+ try {
263
+ await setHarnessSetting(configManager, context?.platform?.secretsManager, setting.key, entry.value);
264
+ imported.push(`${setting.key} (${entry.source})`);
265
+ } catch (error) {
266
+ skipped.push(`${setting.key}: ${error instanceof Error ? error.message : String(error)}`);
267
+ }
268
+ }
269
+
270
+ return {
271
+ status: imported.length > 0 ? `Imported ${imported.length} GoodVibes TUI setting(s).` : 'No GoodVibes TUI settings changed.',
272
+ runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
273
+ result: {
274
+ kind: skipped.length > 0 && imported.length === 0 ? 'error' : imported.length > 0 ? 'refreshed' : 'guidance',
275
+ title: imported.length > 0 ? 'GoodVibes TUI settings imported' : 'No settings changed',
276
+ detail: [
277
+ imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(', ')}${imported.length > 10 ? `, +${imported.length - 10} more` : ''}.` : '',
278
+ unchanged.length > 0 ? `${unchanged.length} setting(s) already matched.` : '',
279
+ skipped.length > 0 ? `Skipped: ${skipped.slice(0, 5).join('; ')}${skipped.length > 5 ? `; +${skipped.length - 5} more` : ''}.` : '',
280
+ parseErrors.length > 0 ? `Parse issues: ${parseErrors.join('; ')}.` : '',
281
+ ].filter((line) => line.length > 0).join(' '),
282
+ safety: 'safe',
283
+ },
284
+ };
285
+ }
286
+
287
+ function createSettingEditor(setting: ConfigSetting, currentValue: string, action: AgentWorkspaceAction): AgentWorkspaceLocalEditor {
288
+ const valueHint = setting.type === 'number'
289
+ ? 'Enter a number.'
290
+ : setting.type === 'string'
291
+ ? 'Enter a value. Leave empty to clear it.'
292
+ : setting.enumValues
293
+ ? `Allowed values: ${setting.enumValues.join(', ')}.`
294
+ : action.detail;
295
+ return {
296
+ kind: 'setting-set',
297
+ mode: 'update',
298
+ recordId: setting.key,
299
+ title: `Set ${setting.key}`,
300
+ selectedFieldIndex: 0,
301
+ message: action.detail,
302
+ fields: [
303
+ {
304
+ id: 'value',
305
+ label: setting.key,
306
+ value: currentValue,
307
+ required: false,
308
+ multiline: false,
309
+ hint: action.settingValueHint ?? valueHint,
310
+ redact: /(?:secret|token|password|api[-_.]?key|signing)/i.test(setting.key),
311
+ },
312
+ ],
313
+ };
314
+ }
@@ -10,6 +10,14 @@ export type AgentWorkspaceFocusPane = 'categories' | 'actions';
10
10
  export const AGENT_WORKSPACE_CATEGORY_IDS = [
11
11
  'home',
12
12
  'setup',
13
+ 'account-model',
14
+ 'assistant-behavior',
15
+ 'tools-permissions',
16
+ 'onboarding-display',
17
+ 'onboarding-channels',
18
+ 'onboarding-voice-media',
19
+ 'onboarding-context',
20
+ 'onboarding-verify',
13
21
  'research',
14
22
  'artifacts',
15
23
  'conversation',
@@ -32,7 +40,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
32
40
 
33
41
  export type AgentWorkspaceCategoryId = (typeof AGENT_WORKSPACE_CATEGORY_IDS)[number];
34
42
 
35
- export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'local-selection' | 'local-operation' | 'onboarding-complete';
43
+ export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'setting' | 'settings-import' | 'local-selection' | 'local-operation' | 'onboarding-complete';
36
44
 
37
45
  export type AgentWorkspaceLocalEditorKind = 'memory' | 'note' | 'persona' | 'skill' | 'routine' | 'profile';
38
46
 
@@ -178,6 +186,7 @@ export type AgentWorkspaceEditorKind =
178
186
  | 'schedule-receipt'
179
187
  | 'mode-preset'
180
188
  | 'mode-domain'
189
+ | 'setting-set'
181
190
  | 'model-pin'
182
191
  | 'model-unpin'
183
192
  | 'delegate-task'
@@ -247,6 +256,10 @@ export interface AgentWorkspaceAction {
247
256
  readonly command?: string;
248
257
  readonly targetCategoryId?: AgentWorkspaceCategoryId;
249
258
  readonly editorKind?: AgentWorkspaceEditorKind;
259
+ readonly settingKey?: string;
260
+ readonly settingValueHint?: string;
261
+ readonly visibleWhenSettingKey?: string;
262
+ readonly visibleWhenSettingValue?: string | boolean | number;
250
263
  readonly localKind?: AgentWorkspaceLocalEditorKind;
251
264
  readonly selectionDelta?: number;
252
265
  readonly localOperation?: AgentWorkspaceLocalOperation;
@@ -1,5 +1,6 @@
1
1
  import type { MemoryApi } from '@pellux/goodvibes-sdk/platform/knowledge';
2
2
  import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
3
+ import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
3
4
  import type { ShellPathService } from '@/runtime/index.ts';
4
5
  import type { CommandContext } from './command-registry.ts';
5
6
  import { AgentNoteRegistry } from '../agent/note-registry.ts';
@@ -18,6 +19,7 @@ import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } fr
18
19
  import { jumpAgentWorkspaceSelection, moveAgentWorkspaceSelection, selectAgentWorkspaceCategory } from './agent-workspace-navigation.ts';
19
20
  import { buildAgentWorkspaceRequirements } from './agent-workspace-requirements.ts';
20
21
  import { appendAgentWorkspaceActionSearchText, backspaceAgentWorkspaceActionSearch, beginAgentWorkspaceActionSearch, clearAgentWorkspaceActionSearch, commitAgentWorkspaceActionSearchSelection, searchAgentWorkspaceActions } from './agent-workspace-search.ts';
22
+ import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionEffect, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible } from './agent-workspace-settings.ts';
21
23
  import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
22
24
  import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
23
25
  import { writeOnboardingCheckMarker, writeOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
@@ -39,14 +41,7 @@ export class AgentWorkspace {
39
41
  public localEditor: AgentWorkspaceLocalEditor | null = null;
40
42
  public actionSearchActive = false;
41
43
  public actionSearchQuery = '';
42
- public readonly selectedLibraryItemIndexes: AgentWorkspaceLocalSelectionIndexes = {
43
- memory: 0,
44
- note: 0,
45
- persona: 0,
46
- skill: 0,
47
- routine: 0,
48
- profile: 0,
49
- };
44
+ public readonly selectedLibraryItemIndexes: AgentWorkspaceLocalSelectionIndexes = { memory: 0, note: 0, persona: 0, skill: 0, routine: 0, profile: 0 };
50
45
  private context: CommandContext | null = null;
51
46
  private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
52
47
  private dispatchPrompt: AgentWorkspacePromptDispatcher | null = null;
@@ -63,7 +58,10 @@ export class AgentWorkspace {
63
58
  this.localEditor = null;
64
59
  this.actionSearchActive = false;
65
60
  this.actionSearchQuery = '';
66
- if (categoryId && !this.selectCategory(categoryId)) {
61
+ if (!categoryId) {
62
+ this.selectedCategoryIndex = 0;
63
+ this.selectedActionIndex = 0;
64
+ } else if (!this.selectCategory(categoryId)) {
67
65
  const normalized = categoryId.trim();
68
66
  this.status = `Unknown Agent workspace area: ${normalized}`;
69
67
  this.lastActionResult = {
@@ -98,7 +96,7 @@ export class AgentWorkspace {
98
96
 
99
97
  get actions(): readonly AgentWorkspaceAction[] {
100
98
  if (this.actionSearchActive) return this.actionSearchResults.map((result) => result.action);
101
- return this.selectedCategory.actions;
99
+ return this.selectedCategory.actions.filter((action) => isAgentWorkspaceActionVisible(this.context, action));
102
100
  }
103
101
 
104
102
  get selectedAction(): AgentWorkspaceAction | null {
@@ -111,7 +109,12 @@ export class AgentWorkspace {
111
109
  }
112
110
 
113
111
  get actionSearchResults(): readonly AgentWorkspaceActionSearchResult[] {
114
- return this.actionSearchActive ? searchAgentWorkspaceActions(this.categories, this.actionSearchQuery) : [];
112
+ if (!this.actionSearchActive) return [];
113
+ const categories = this.categories.map((category) => ({
114
+ ...category,
115
+ actions: category.actions.filter((action) => isAgentWorkspaceActionVisible(this.context, action)),
116
+ }));
117
+ return searchAgentWorkspaceActions(categories, this.actionSearchQuery);
115
118
  }
116
119
 
117
120
  get selectedActionSearchResult(): AgentWorkspaceActionSearchResult | null {
@@ -178,11 +181,7 @@ export class AgentWorkspace {
178
181
  }
179
182
  this.runtimeSnapshot = buildAgentWorkspaceRuntimeSnapshot(this.context);
180
183
  this.status = 'Runtime context refreshed.';
181
- this.lastActionResult = {
182
- kind: 'refreshed',
183
- title: 'Runtime context refreshed',
184
- detail: 'Provider, model, session, local memory, runtime endpoint, and Agent knowledge route posture were re-read from the live command context.',
185
- };
184
+ this.lastActionResult = { kind: 'refreshed', title: 'Runtime context refreshed', detail: 'Provider, model, session, local memory, runtime endpoint, and Agent knowledge route posture were re-read from the live command context.' };
186
185
  }
187
186
 
188
187
  cancelLocalEditor(): void {
@@ -321,32 +320,37 @@ export class AgentWorkspace {
321
320
  }
322
321
 
323
322
  try {
324
- const marker = {
325
- scope: 'user',
326
- source: 'wizard',
327
- mode: 'new',
328
- workspaceRoot: shellPaths.workingDirectory,
329
- } as const;
323
+ const marker = { scope: 'user', source: 'wizard', mode: 'new', workspaceRoot: shellPaths.workingDirectory } as const;
330
324
  writeOnboardingCheckMarker(shellPaths, marker);
331
325
  writeOnboardingCompletionMarker(shellPaths, marker);
332
326
  this.status = 'Onboarding applied and closed.';
333
- this.lastActionResult = {
334
- kind: 'refreshed',
335
- title: 'Onboarding complete',
336
- detail: 'Saved the user onboarding completion marker. Future normal launches start in the main conversation.',
337
- safety: 'safe',
338
- };
327
+ this.lastActionResult = { kind: 'refreshed', title: 'Onboarding complete', detail: 'Saved the user onboarding completion marker. Future normal launches start in the main conversation.', safety: 'safe' };
339
328
  if (!this.context?.dismissAgentWorkspace?.()) this.close();
340
329
  } catch (error) {
341
330
  const detail = error instanceof Error ? error.message : String(error);
342
331
  this.status = 'Onboarding completion failed.';
343
- this.lastActionResult = {
344
- kind: 'error',
345
- title: 'Onboarding completion failed',
346
- detail,
347
- safety: 'safe',
348
- };
332
+ this.lastActionResult = { kind: 'error', title: 'Onboarding completion failed', detail, safety: 'safe' };
333
+ }
334
+ }
335
+
336
+ applySettingAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
337
+ const effect = buildAgentWorkspaceSettingActionEffect(this.context, action);
338
+ if (effect.kind === 'result') {
339
+ this.status = effect.status;
340
+ this.lastActionResult = effect.result;
341
+ return;
349
342
  }
343
+ if (effect.kind === 'editor') {
344
+ this.localEditor = effect.editor;
345
+ this.status = effect.status;
346
+ this.lastActionResult = effect.result;
347
+ return;
348
+ }
349
+ void this.applySettingValue(effect.setting, effect.value, requestRender);
350
+ }
351
+
352
+ importTuiSettings(requestRender?: () => void): void {
353
+ void this.importTuiSettingsAsync(requestRender);
350
354
  }
351
355
 
352
356
  private selectedItemForOperation(operation: AgentWorkspaceLocalOperation): AgentWorkspaceLocalLibraryItem | null {
@@ -357,6 +361,24 @@ export class AgentWorkspace {
357
361
  return this.selectedLocalLibraryItem('routine');
358
362
  }
359
363
 
364
+ private async applySettingValue(setting: ConfigSetting, value: unknown, requestRender?: () => void): Promise<void> {
365
+ const outcome = await applyAgentWorkspaceSettingValue(this.context, setting, value);
366
+ this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
367
+ this.clampSelection();
368
+ this.status = outcome.status;
369
+ this.lastActionResult = outcome.result;
370
+ requestRender?.();
371
+ }
372
+
373
+ private async importTuiSettingsAsync(requestRender?: () => void): Promise<void> {
374
+ const outcome = await importAgentWorkspaceTuiSettings(this.context);
375
+ this.runtimeSnapshot = outcome.runtimeSnapshot ?? this.runtimeSnapshot;
376
+ this.clampSelection();
377
+ this.status = outcome.status;
378
+ this.lastActionResult = outcome.result;
379
+ requestRender?.();
380
+ }
381
+
360
382
  private memoryApi(): MemoryApi {
361
383
  const memory = this.context?.clients?.agentKnowledgeApi?.memory;
362
384
  if (!memory) throw new Error('Agent Memory API is unavailable; refusing default knowledge or non-Agent fallback.');
@@ -429,6 +451,19 @@ export class AgentWorkspace {
429
451
  requestRender?.();
430
452
  return;
431
453
  }
454
+ if (editor.kind === 'setting-set') {
455
+ const setting = editor.recordId ? agentWorkspaceSettingSchema(this.context, editor.recordId) : null;
456
+ if (!setting) {
457
+ this.localEditor = { ...editor, message: 'Unknown setting; cannot save.' };
458
+ this.status = 'Unknown setting; cannot save.';
459
+ requestRender?.();
460
+ return;
461
+ }
462
+ const value = this.editorField('value');
463
+ this.localEditor = null;
464
+ void this.applySettingValue(setting, value, requestRender);
465
+ return;
466
+ }
432
467
  if (editor.kind === 'memory') {
433
468
  if (editor.mode === 'delete') {
434
469
  try {
@@ -17,7 +17,7 @@ import {
17
17
  WORKSPACE_PALETTE as PALETTE,
18
18
  type WorkspaceRow,
19
19
  } from './fullscreen-workspace.ts';
20
- import { actionResultColor, safetyColor, setupStatusColor, type AgentWorkspaceContextLine as ContextLine } from './agent-workspace-style.ts';
20
+ import { actionResultColor, setupStatusColor, type AgentWorkspaceContextLine as ContextLine } from './agent-workspace-style.ts';
21
21
 
22
22
  function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[] {
23
23
  const rows: WorkspaceRow[] = [];
@@ -57,6 +57,8 @@ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[
57
57
  function actionCommand(action: AgentWorkspaceAction): string {
58
58
  if (action.kind === 'workspace') return action.targetCategoryId ? `open ${action.targetCategoryId}` : '(workspace)';
59
59
  if (action.kind === 'editor') return action.editorKind ? `edit ${action.editorKind}` : '(editor)';
60
+ if (action.kind === 'setting') return action.settingKey ? `set ${action.settingKey}` : 'set setting';
61
+ if (action.kind === 'settings-import') return 'import GoodVibes settings';
60
62
  if (action.kind === 'local-selection') return action.selectionDelta && action.selectionDelta < 0 ? 'select previous' : 'select next';
61
63
  if (action.kind === 'local-operation') return action.localOperation ?? '(local action)';
62
64
  return action.command ?? '(guidance)';
@@ -91,8 +93,8 @@ function compactText(text: string, maxWidth = 104): string {
91
93
 
92
94
  function actionMetaLine(action: AgentWorkspaceAction): ContextLine {
93
95
  return {
94
- text: `${actionCommand(action)}; ${action.safety}`,
95
- fg: action.kind === 'command' ? PALETTE.info : safetyColor(action),
96
+ text: `Does: ${actionCommand(action)}`,
97
+ fg: action.safety === 'blocked' ? PALETTE.warn : action.kind === 'command' ? PALETTE.info : PALETTE.muted,
96
98
  };
97
99
  }
98
100
 
@@ -116,7 +118,7 @@ function setupOverviewLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLin
116
118
  const counts = setupCounts(snapshot);
117
119
  const nextItems = setupAttentionItems(snapshot, 3);
118
120
  const lines: ContextLine[] = [
119
- { text: 'Setup Overview', fg: PALETTE.title, bold: true },
121
+ { text: 'Onboarding', fg: PALETTE.title, bold: true },
120
122
  { text: `${counts.ready}/${snapshot.setupChecklist.length} ready; ${counts.recommended} recommended; ${counts.optional} optional; ${counts.blocked} blocked.`, fg: counts.blocked > 0 ? PALETTE.warn : PALETTE.info },
121
123
  { text: `Chat: ${snapshot.provider} / ${snapshot.modelDisplayName}.`, fg: PALETTE.info },
122
124
  { text: `Local: ${snapshot.localPersonaCount} personas, ${snapshot.localSkillCount} skills, ${snapshot.localRoutineCount} routines, ${snapshot.localMemoryCount} memories.`, fg: PALETTE.info },
@@ -309,7 +311,7 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
309
311
  companionAccessLine(snapshot),
310
312
  { text: `Channels: ${readyCount}/${snapshot.channels.length} ready; ${enabledCount} enabled; ${configuredDefaults} target(s).`, fg: PALETTE.info },
311
313
  { 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 },
314
+ { text: 'Secrets hidden; sends require explicit action.', fg: PALETTE.warn },
313
315
  );
314
316
  } else if (category.id === 'knowledge') {
315
317
  base.push(
@@ -536,9 +538,8 @@ function buildEditorFieldRows(editor: AgentWorkspaceLocalEditor, index: number,
536
538
  function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
537
539
  if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
538
540
  const rows: WorkspaceRow[] = [];
539
- const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
540
- const safetyWidth = 10;
541
- const commandWidth = Math.max(10, width - labelWidth - safetyWidth - 9);
541
+ const labelWidth = Math.min(34, Math.max(18, Math.floor(width * 0.38)));
542
+ const commandWidth = Math.max(10, width - labelWidth - 6);
542
543
  if (workspace.actionSearchActive) {
543
544
  rows.push({
544
545
  text: ` Search: ${workspace.actionSearchQuery || '(type to filter actions)'}`,
@@ -547,7 +548,7 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
547
548
  });
548
549
  }
549
550
  rows.push({
550
- text: ` ${padDisplay(workspace.actionSearchActive ? 'Result' : 'Action', labelWidth)} ${padDisplay('Safety', safetyWidth)} ${padDisplay('Command', commandWidth)}`,
551
+ text: ` ${padDisplay(workspace.actionSearchActive ? 'Result' : 'Action', labelWidth)} ${padDisplay('Does', commandWidth)}`,
551
552
  fg: PALETTE.muted,
552
553
  bold: true,
553
554
  });
@@ -564,9 +565,9 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
564
565
  const label = searchResult ? `${searchResult.category.label} / ${action.label}` : action.label;
565
566
  const marker = selected ? GLYPHS.navigation.selected : ' ';
566
567
  rows.push({
567
- text: `${marker} ${padDisplay(label, labelWidth)} ${padDisplay(action.safety, safetyWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
568
+ text: `${marker} ${padDisplay(label, labelWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
568
569
  selected: selected && workspace.focusPane === 'actions',
569
- fg: safetyColor(action),
570
+ fg: action.safety === 'blocked' ? PALETTE.warn : selected ? PALETTE.text : PALETTE.info,
570
571
  bold: selected,
571
572
  });
572
573
  }
@@ -620,7 +621,7 @@ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, h
620
621
  leftHeader: 'Operator Areas',
621
622
  mainHeader: workspace.actionSearchActive
622
623
  ? `Search actions · ${workspace.actions.length} result(s)`
623
- : `${category.label} · ${category.actions.length} action(s)`,
624
+ : `${category.label} · ${workspace.actions.length} action(s)`,
624
625
  leftRows: buildLeftRows(workspace, metrics.bodyRows),
625
626
  contextRows: buildContextRows(workspace, category, action, metrics.contextWidth),
626
627
  controlRows: buildActionRows(workspace, metrics.contextWidth, metrics.controlRows),
@@ -327,7 +327,7 @@ const UI_SURFACES: readonly UiSurfaceDefinition[] = [
327
327
  kind: 'picker',
328
328
  summary: 'Reasoning-effort selector for models that expose effort levels.',
329
329
  command: '/effort',
330
- preferredModelRoute: `Use ${agentHarnessModes('settings', 'get_setting', 'set_setting')} for provider.reasoningEffort when a concrete level is known, or mode:"run_workspace_action" setup-effort with confirmation.`,
330
+ preferredModelRoute: `Use ${agentHarnessModes('settings', 'get_setting', 'set_setting')} for provider.reasoningEffort when a concrete level is known, or mode:"run_workspace_action" setup-reasoning-effort with confirmation.`,
331
331
  available: (context) => typeof context.openReasoningEffortPicker === 'function',
332
332
  open: (context) => {
333
333
  const surface = findSurfaceById('reasoning-effort-picker')!;
@@ -55,7 +55,7 @@ function readString(value: unknown): string {
55
55
  function readLimit(value: unknown, fallback: number): number {
56
56
  const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
57
57
  if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
58
- return Math.max(1, Math.min(500, Math.trunc(parsed)));
58
+ return Math.max(1, Math.min(1000, Math.trunc(parsed)));
59
59
  }
60
60
 
61
61
  function readFieldMap(value: unknown): Readonly<Record<string, string>> {
@@ -256,7 +256,7 @@ export function listWorkspaceActions(
256
256
  ): readonly Record<string, unknown>[] {
257
257
  const query = readString(args.query);
258
258
  const categoryId = readString(args.categoryId || args.category);
259
- const limit = readLimit(args.limit, 500);
259
+ const limit = readLimit(args.limit, 1000);
260
260
  const includeEditor = args.includeParameters === true;
261
261
  const editorContext = includeEditor ? buildWorkspaceEditorContext(context, args) : null;
262
262
  const source = query
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.1.3';
9
+ let _version = '1.1.4';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
12
12
  readonly version?: unknown;