@pellux/goodvibes-agent 0.1.7 → 0.1.8

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.
@@ -1,8 +1,28 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
2
  import type { RuntimeTask, TaskLifecycleState } from '@/runtime/index.ts';
3
3
  import { reviewWorktreeAttachments } from '@/runtime/index.ts';
4
- import { requireOperatorClient, requireOpsApi, requirePanelManager, requireShellPaths } from './runtime-services.ts';
5
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
+ import { requireOperatorClient, requirePanelManager, requireShellPaths } from './runtime-services.ts';
5
+
6
+ const BLOCKED_TASK_MUTATIONS: ReadonlySet<string> = new Set([
7
+ 'create',
8
+ 'update',
9
+ 'complete',
10
+ 'fail',
11
+ 'cancel',
12
+ 'pause',
13
+ 'resume',
14
+ 'retry',
15
+ ]);
16
+
17
+ function printTaskMutationBlocked(print: (text: string) => void, subcommand: string): void {
18
+ print([
19
+ `Task mutation "${subcommand}" is blocked in GoodVibes Agent.`,
20
+ ' policy: runtime tasks are read-only from the Agent surface; normal work stays in the main conversation.',
21
+ ' durable tasks: use /workplan for visible planning and task tracking.',
22
+ ' build/fix/review: use /delegate <task> to hand explicit implementation work to GoodVibes TUI.',
23
+ ' result: no local runtime task state was changed.',
24
+ ].join('\n'));
25
+ }
6
26
 
7
27
  function sortRuntimeTasks(tasks: RuntimeTask[]): RuntimeTask[] {
8
28
  const statusOrder: TaskLifecycleState[] = ['running', 'queued', 'blocked', 'failed', 'completed', 'cancelled'];
@@ -32,8 +52,8 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
32
52
  registry.register({
33
53
  name: 'tasks',
34
54
  aliases: ['task'],
35
- description: 'Inspect and control runtime tasks',
36
- usage: '[list [status|kind] | show <taskId> | output <taskId> | create <kind> <owner> <title...> | update <taskId> <title|description|result> <value...> | complete <taskId> [result] | fail <taskId> <error...> | cancel <taskId> [note] | pause <taskId> [note] | resume <taskId> [note] | retry <taskId> [note]]',
55
+ description: 'Inspect runtime tasks without starting or mutating local background work',
56
+ usage: '[list [status|kind] | show <taskId> | output <taskId>]',
37
57
  handler(args, ctx) {
38
58
  if (args.length === 0) {
39
59
  if (ctx.showPanel) ctx.showPanel('tasks');
@@ -96,7 +116,7 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
96
116
  return worktrees.total > 0
97
117
  ? [
98
118
  ` worktrees: ${worktrees.total} tracked (${worktrees.active} active / ${worktrees.paused} paused / ${worktrees.pendingCleanup} cleanup)`,
99
- ` worktree next: /worktree task ${task.id}`,
119
+ ' worktree next: open GoodVibes TUI in the target workspace for recovery.',
100
120
  ]
101
121
  : [];
102
122
  })(),
@@ -125,106 +145,12 @@ export function registerTasksRuntimeCommands(registry: CommandRegistry): void {
125
145
  return;
126
146
  }
127
147
 
128
- if (subcommand === 'create') {
129
- const opsApi = requireOpsApi(ctx);
130
- const kind = args[1];
131
- const owner = args[2];
132
- const title = args.slice(3).join(' ').trim();
133
- if (!kind || !owner || !title) {
134
- ctx.print('Usage: /tasks create <kind> <owner> <title...>');
135
- return;
136
- }
137
- const validKinds = new Set(['exec', 'agent', 'acp', 'scheduler', 'daemon', 'mcp', 'plugin', 'integration']);
138
- if (!validKinds.has(kind)) {
139
- ctx.print(`Unknown task kind: ${kind}`);
140
- return;
141
- }
142
- const task = opsApi.tasks.create({
143
- kind: kind as import('@/runtime/index.ts').TaskKind,
144
- owner,
145
- title,
146
- description: title,
147
- });
148
- ctx.print(`Created task ${task.id} (${task.kind}) for ${task.owner}.`);
149
- return;
150
- }
151
-
152
- if (subcommand === 'update') {
153
- const opsApi = requireOpsApi(ctx);
154
- const taskId = args[1];
155
- const field = args[2];
156
- const value = args.slice(3).join(' ').trim();
157
- if (!taskId || !field || !value) {
158
- ctx.print('Usage: /tasks update <taskId> <title|description|result> <value...>');
159
- return;
160
- }
161
- if (field !== 'title' && field !== 'description' && field !== 'result') {
162
- ctx.print(`Unsupported task update field: ${field}`);
163
- return;
164
- }
165
- opsApi.tasks.update(taskId, field === 'result' ? { result: value } : { [field]: value });
166
- ctx.print(`Updated task ${taskId} field ${field}.`);
167
- return;
168
- }
169
-
170
- if (subcommand === 'complete') {
171
- const opsApi = requireOpsApi(ctx);
172
- const taskId = args[1];
173
- if (!taskId) {
174
- ctx.print('Usage: /tasks complete <taskId> [result]');
175
- return;
176
- }
177
- const result = args.slice(2).join(' ').trim() || undefined;
178
- opsApi.tasks.complete(taskId, result);
179
- ctx.print(`Completed task ${taskId}.`);
180
- return;
181
- }
182
-
183
- if (subcommand === 'fail') {
184
- const opsApi = requireOpsApi(ctx);
185
- const taskId = args[1];
186
- const errorText = args.slice(2).join(' ').trim();
187
- if (!taskId || !errorText) {
188
- ctx.print('Usage: /tasks fail <taskId> <error...>');
189
- return;
190
- }
191
- opsApi.tasks.fail(taskId, { error: errorText });
192
- ctx.print(`Failed task ${taskId}.`);
148
+ if (BLOCKED_TASK_MUTATIONS.has(subcommand)) {
149
+ printTaskMutationBlocked(ctx.print, subcommand);
193
150
  return;
194
151
  }
195
152
 
196
- const taskId = args[1];
197
- const note = args.slice(2).join(' ').trim() || undefined;
198
- if (!taskId) {
199
- ctx.print(`Usage: /tasks ${subcommand} <taskId> [note]`);
200
- return;
201
- }
202
- const opsApi = requireOpsApi(ctx);
203
- try {
204
- switch (subcommand) {
205
- case 'cancel':
206
- opsApi.tasks.cancel(taskId, note);
207
- ctx.print(`Cancelled task ${taskId}.`);
208
- return;
209
- case 'pause':
210
- opsApi.tasks.pause(taskId, note);
211
- ctx.print(`Paused task ${taskId}.`);
212
- return;
213
- case 'resume':
214
- opsApi.tasks.resume(taskId, note);
215
- ctx.print(`Resumed task ${taskId}.`);
216
- return;
217
- case 'retry':
218
- opsApi.tasks.retry(taskId, note);
219
- ctx.print(`Re-queued task ${taskId}.`);
220
- return;
221
- default:
222
- ctx.print(`Unknown tasks subcommand: ${subcommand}`);
223
- return;
224
- }
225
- } catch (error) {
226
- ctx.print(summarizeError(error));
227
- }
153
+ ctx.print(`Unknown tasks subcommand: ${subcommand}`);
228
154
  },
229
155
  });
230
156
  }
@@ -57,6 +57,7 @@ import { registerAgentExternalizedTuiCommands } from './commands/agent-externali
57
57
  import { registerDelegationRuntimeCommands } from './commands/delegation-runtime.ts';
58
58
  import { registerPersonasRuntimeCommands } from './commands/personas-runtime.ts';
59
59
  import { registerAgentSkillsRuntimeCommands } from './commands/agent-skills-runtime.ts';
60
+ import { registerRoutinesRuntimeCommands } from './commands/routines-runtime.ts';
60
61
 
61
62
  /**
62
63
  * registerBuiltinCommands - Register all built-in slash commands into the registry.
@@ -67,6 +68,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
67
68
  registerAgentWorkspaceRuntimeCommands(registry);
68
69
  registerPersonasRuntimeCommands(registry);
69
70
  registerAgentSkillsRuntimeCommands(registry);
71
+ registerRoutinesRuntimeCommands(registry);
70
72
  registerDelegationRuntimeCommands(registry);
71
73
  registerConfigCommand(registry);
72
74
  registerOperatorRuntimeCommands(registry);
@@ -256,7 +256,7 @@ type LiveTailRouteState = {
256
256
  active: boolean;
257
257
  scrollUp: () => void;
258
258
  scrollDown: () => void;
259
- killProcess: () => void;
259
+ killProcess: () => boolean;
260
260
  close: () => void;
261
261
  };
262
262
  processModal: {
@@ -270,8 +270,7 @@ export function handleLiveTailToken(state: LiveTailRouteState, token: InputToken
270
270
  if (!state.liveTailModal.active) return false;
271
271
 
272
272
  const killAndReturn = (): void => {
273
- state.liveTailModal.killProcess();
274
- state.handleEscape();
273
+ if (state.liveTailModal.killProcess()) state.handleEscape();
275
274
  };
276
275
 
277
276
  if (token.type === 'key') {
@@ -200,15 +200,15 @@ export function buildProviderHealthDomainSummaries(
200
200
  summary: worktreeSummary.total === 0
201
201
  ? 'no persisted worktrees'
202
202
  : `${worktreeSummary.total} tracked / ${worktreeIssues} need review`,
203
- next: worktreeIssues > 0 ? '/worktree recover <session|task> <id>' : '/worktree review',
203
+ next: 'externalized to GoodVibes TUI',
204
204
  details: [
205
205
  worktreeSummary.paused > 0 ? `${worktreeSummary.paused} paused worktree(s)` : '',
206
206
  worktreeSummary.pendingCleanup > 0 ? `${worktreeSummary.pendingCleanup} cleanup pending` : '',
207
207
  worktreeSummary.discard > 0 ? `${worktreeSummary.discard} marked discard` : '',
208
208
  ].filter(Boolean),
209
209
  nextSteps: worktreeIssues > 0
210
- ? ['/worktree review', '/worktree recover <session|task> <id>']
211
- : ['/worktree review'],
210
+ ? ['Open GoodVibes TUI in the target workspace for recovery', '/delegate <task> for explicit build/fix/review recovery']
211
+ : ['No Agent worktree action available; use GoodVibes TUI when repository recovery is needed'],
212
212
  });
213
213
 
214
214
  return summaries;
@@ -97,9 +97,10 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
97
97
  } else if (category.id === 'memory') {
98
98
  base.push(
99
99
  { text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
100
+ { text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
100
101
  { text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
101
102
  { text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
102
- { text: 'Durable memory, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
103
+ { text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
103
104
  { text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
104
105
  );
105
106
  } else if (category.id === 'work') {
@@ -6,7 +6,7 @@ import type { ProcessEntry } from './process-modal.ts';
6
6
  import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
7
7
 
8
8
  export interface LiveTailModalDeps {
9
- readonly agentManager: Pick<AgentManager, 'cancel' | 'getStatus'>;
9
+ readonly agentManager: Pick<AgentManager, 'getStatus'>;
10
10
  readonly processManager: Pick<ProcessManager, 'stop' | 'getOutput'>;
11
11
  }
12
12
 
@@ -15,7 +15,7 @@ export interface LiveTailModalDeps {
15
15
  /**
16
16
  * LiveTailModal — manages state for the live output peek modal.
17
17
  *
18
- * Shows streaming stdout/stderr from a selected background process or agent
18
+ * Shows streaming stdout/stderr from a selected shell process or agent
19
19
  * progress notes. Auto-scrolls to the bottom unless the user scrolled up.
20
20
  */
21
21
  export class LiveTailModal {
@@ -48,17 +48,17 @@ export class LiveTailModal {
48
48
  }
49
49
 
50
50
  /**
51
- * Kill the current process.
52
- * Returns true if the process was found and stopped.
51
+ * Stop the current shell process when it is an exec entry.
52
+ * Agent entries are read-only in GoodVibes Agent; build execution and
53
+ * cancellation belong to GoodVibes TUI/shared-session owners.
53
54
  */
54
55
  killProcess(): boolean {
55
56
  if (!this.entry) return false;
56
57
 
57
58
  if (this.entry.type === 'exec') {
58
59
  return this.deps.processManager.stop(this.entry.id);
59
- } else {
60
- return this.deps.agentManager.cancel(this.entry.id);
61
60
  }
61
+ return false;
62
62
  }
63
63
 
64
64
  /** Retrieve the current output text for the watched process. */
@@ -151,6 +151,6 @@ export function renderLiveTailModal(
151
151
  margin: 2,
152
152
  targetContentRows,
153
153
  sections,
154
- hints: ['[Up/Down] Scroll', '[k] Kill', '[Esc] Back'],
154
+ hints: ['[Up/Down] Scroll', '[k] Stop exec only', '[Esc] Back'],
155
155
  }, width);
156
156
  }
@@ -17,12 +17,12 @@ function truncateToWidth(text: string, maxWidth: number): string {
17
17
  }
18
18
 
19
19
  /**
20
- * renderProcessIndicator — shows a one-line summary of active background
21
- * processes below the input area.
20
+ * renderProcessIndicator — shows a one-line summary of active runtime
21
+ * activity below the input area.
22
22
  *
23
- * Dimmed when no processes are active, highlighted (cyan) when agents or
24
- * background exec processes are running. Includes an `Enter to view` hint
25
- * when active.
23
+ * Dimmed when no entries are active, highlighted (cyan) when delegated agent
24
+ * records or shell exec processes are running. Includes an `Enter to view`
25
+ * hint when active.
26
26
  */
27
27
  export function renderProcessIndicator(
28
28
  width: number,
@@ -62,16 +62,16 @@ export function renderProcessIndicator(
62
62
  if (agentCount > 0) parts.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
63
63
  if (toolCount > 0) parts.push(`${toolCount} tool${toolCount !== 1 ? 's' : ''} running`);
64
64
  const label = total === 0
65
- ? `No background processes ${GLYPHS.status.pending} back to input`
65
+ ? `No runtime activity ${GLYPHS.status.pending} back to input`
66
66
  : `${parts.join(` ${GLYPHS.navigation.pipeSeparator} `)} ${GLYPHS.status.pending} Enter to open ${GLYPHS.status.pending} back to input`;
67
67
  return renderFocusedStatus(label);
68
68
  }
69
69
 
70
70
  if (total === 0) {
71
- return renderPlainStatus('No background processes', { fg: '238', dim: true });
71
+ return renderPlainStatus('No runtime activity', { fg: '238', dim: true });
72
72
  }
73
73
 
74
- // Build the label: "bg: 2 agents | Turn 3 | write - src/foo.ts"
74
+ // Build the label: "2 agents | Turn 3 | write - src/foo.ts"
75
75
  const parts: string[] = [];
76
76
  if (agentCount > 0) {
77
77
  parts.push(`${agentCount} agent${agentCount !== 1 ? 's' : ''}`);
@@ -42,7 +42,7 @@ const WRFC_ROLE_ORDER: Record<string, number> = {
42
42
  };
43
43
 
44
44
  export interface ProcessModalDeps {
45
- readonly agentManager: Pick<AgentManager, 'list' | 'getStatus' | 'cancel'>;
45
+ readonly agentManager: Pick<AgentManager, 'list' | 'getStatus'>;
46
46
  readonly processManager: Pick<ProcessManager, 'list' | 'getStatus' | 'stop'>;
47
47
  readonly wrfcController: Pick<WrfcController, 'getChain'> & Partial<Pick<WrfcController, 'listChains'>>;
48
48
  }
@@ -558,8 +558,9 @@ export class ProcessModal {
558
558
  }
559
559
 
560
560
  /**
561
- * Kill the selected process.
562
- * Returns true if a process was killed, false otherwise.
561
+ * Stop the selected shell process.
562
+ * Agent entries are read-only in GoodVibes Agent; build execution and
563
+ * cancellation belong to GoodVibes TUI/shared-session owners.
563
564
  */
564
565
  killSelected(): boolean {
565
566
  const entry = this.getSelected();
@@ -567,9 +568,8 @@ export class ProcessModal {
567
568
 
568
569
  if (entry.type === 'exec') {
569
570
  return this.deps.processManager.stop(entry.id);
570
- } else {
571
- return this.deps.agentManager.cancel(entry.id);
572
571
  }
572
+ return false;
573
573
  }
574
574
  }
575
575
 
@@ -598,12 +598,12 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
598
598
 
599
599
  if (modal.entries.length === 0) {
600
600
  return ModalFactory.createModal({
601
- title: 'Background Processes',
601
+ title: 'Runtime Activity',
602
602
  width: boxW,
603
603
  margin: boxMargin,
604
604
  targetContentRows,
605
605
  sections: [
606
- { type: 'text', content: 'No background processes running.' },
606
+ { type: 'text', content: 'No runtime activity.' },
607
607
  ],
608
608
  hints: ['[Esc] Close'],
609
609
  }, width);
@@ -643,7 +643,7 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
643
643
  }
644
644
 
645
645
  return ModalFactory.createModal({
646
- title: 'Background Processes',
646
+ title: 'Runtime Activity',
647
647
  width: boxW,
648
648
  margin: boxMargin,
649
649
  targetContentRows,
@@ -651,6 +651,6 @@ export function renderProcessModal(modal: ProcessModal, width: number, viewportH
651
651
  helpers: modal.entries.length > maxVisibleRows
652
652
  ? [{ content: `[${window.start + 1}-${window.end} of ${modal.entries.length}]` }]
653
653
  : undefined,
654
- hints: ['[Up/Down] Navigate', '[Enter] Peek output', '[k] Kill', '[Esc] Close'],
654
+ hints: ['[Up/Down] Navigate', '[Enter] Details/output', '[k] Stop exec only', '[Esc] Close'],
655
655
  }, width);
656
656
  }
@@ -44,6 +44,7 @@ import { startMcpConfigAutoReload } from '../mcp/runtime-reload.ts';
44
44
  import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
45
45
  import { buildActivePersonaPrompt } from '../agent/persona-registry.ts';
46
46
  import { buildEnabledSkillsPrompt } from '../agent/skill-registry.ts';
47
+ import { buildEnabledRoutinesPrompt } from '../agent/routine-registry.ts';
47
48
 
48
49
  const GOODVIBES_AGENT_OPERATOR_POLICY = [
49
50
  '## GoodVibes Agent Operator Policy',
@@ -210,6 +211,7 @@ export async function bootstrapRuntime(
210
211
  return joinPromptParts(
211
212
  runtime.systemPrompt,
212
213
  GOODVIBES_AGENT_OPERATOR_POLICY,
214
+ buildEnabledRoutinesPrompt(services.shellPaths),
213
215
  buildEnabledSkillsPrompt(services.shellPaths),
214
216
  buildActivePersonaPrompt(services.shellPaths),
215
217
  supplement,
@@ -88,7 +88,6 @@ import {
88
88
  } from '@pellux/goodvibes-sdk/platform/tools';
89
89
  import { WorkPlanStore } from '../work-plans/work-plan-store.ts';
90
90
 
91
- const REGULAR_KNOWLEDGE_DB_FILE = 'knowledge-wiki.sqlite';
92
91
  const HOME_GRAPH_KNOWLEDGE_DB_FILE = 'knowledge-home-graph.sqlite';
93
92
 
94
93
  function buildFallbackModelDefinition(provider: string, modelId: string): ModelDefinition {
@@ -170,6 +169,7 @@ export interface RuntimeServices {
170
169
  readonly automationManager: AutomationManager;
171
170
  readonly gatewayMethods: GatewayMethodCatalog;
172
171
  readonly artifactStore: ArtifactStore;
172
+ /** Compatibility alias that intentionally points at the isolated Agent Knowledge service, not default Knowledge/Wiki. */
173
173
  readonly knowledgeService: KnowledgeService;
174
174
  readonly agentKnowledgeService: KnowledgeService;
175
175
  readonly homeGraphService: HomeGraphService;
@@ -395,10 +395,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
395
395
  ].join(' '));
396
396
  },
397
397
  });
398
- const knowledgeStore = new KnowledgeStore({
399
- configManager,
400
- dbFileName: REGULAR_KNOWLEDGE_DB_FILE,
401
- });
402
398
  const agentKnowledgeStore = new KnowledgeStore({
403
399
  configManager,
404
400
  dbFileName: GOODVIBES_AGENT_KNOWLEDGE_DB_FILE,
@@ -411,10 +407,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
411
407
  timeoutMs: 20_000,
412
408
  maxConcurrent: 1,
413
409
  });
414
- const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, {
415
- llm: knowledgeSemanticLlm,
416
- maxLlmSourcesPerReindex: 3,
417
- });
418
410
  const homeGraphSemanticService = new KnowledgeSemanticService(homeGraphKnowledgeStore, {
419
411
  llm: knowledgeSemanticLlm,
420
412
  maxLlmSourcesPerReindex: 3,
@@ -424,12 +416,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
424
416
  llm: knowledgeSemanticLlm,
425
417
  maxLlmSourcesPerReindex: 3,
426
418
  });
427
- const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, {
428
- memoryRegistry,
429
- runtimeBus: options.runtimeBus,
430
- semanticService: knowledgeSemanticService,
431
- });
432
- knowledgeService.attachRuntimeBus(options.runtimeBus);
433
419
  const agentKnowledgeService = new KnowledgeService(agentKnowledgeStore, artifactStore, undefined, {
434
420
  memoryRegistry,
435
421
  runtimeBus: options.runtimeBus,
@@ -459,10 +445,6 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
459
445
  serviceRegistry,
460
446
  featureFlags,
461
447
  });
462
- knowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
463
- searchService: webSearchService,
464
- ingestService: knowledgeService,
465
- }));
466
448
  agentKnowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
467
449
  searchService: webSearchService,
468
450
  ingestService: agentKnowledgeService,
@@ -597,7 +579,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
597
579
  automationManager,
598
580
  gatewayMethods,
599
581
  artifactStore,
600
- knowledgeService,
582
+ knowledgeService: agentKnowledgeService,
601
583
  agentKnowledgeService,
602
584
  homeGraphService,
603
585
  projectPlanningService,
@@ -10,6 +10,20 @@ type AgentToolPolicyGuardOptions = {
10
10
  readonly getLastUserMessage?: () => string | null;
11
11
  };
12
12
 
13
+ const READ_ONLY_AGENT_TOOL_MODES = [
14
+ 'status',
15
+ 'list',
16
+ 'templates',
17
+ 'get',
18
+ 'budget',
19
+ 'wrfc-chains',
20
+ 'wrfc-history',
21
+ 'cohort-status',
22
+ 'cohort-report',
23
+ ] as const;
24
+
25
+ const READ_ONLY_AGENT_TOOL_MODE_SET = new Set<string>(READ_ONLY_AGENT_TOOL_MODES);
26
+
13
27
  const LOCAL_AGENT_DENIAL = [
14
28
  'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
15
29
  'Keep ordinary assistant work serial in the main conversation.',
@@ -23,6 +37,7 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
23
37
  }
24
38
 
25
39
  export function wrapAgentToolForAgentPolicy(tool: Tool, _options: AgentToolPolicyGuardOptions = {}): void {
40
+ narrowAgentToolDefinitionForAgentPolicy(tool);
26
41
  const originalExecute = tool.execute.bind(tool);
27
42
  tool.execute = async (args) => {
28
43
  const denial = validateAgentToolInvocationForAgentPolicy(args as AgentToolArgs);
@@ -32,7 +47,7 @@ export function wrapAgentToolForAgentPolicy(tool: Tool, _options: AgentToolPolic
32
47
  }
33
48
 
34
49
  export function validateAgentToolInvocationForAgentPolicy(args: AgentToolArgs): string | null {
35
- if (args.mode === 'spawn' || args.mode === 'batch-spawn') return LOCAL_AGENT_DENIAL;
50
+ if (typeof args.mode === 'string' && !READ_ONLY_AGENT_TOOL_MODE_SET.has(args.mode)) return LOCAL_AGENT_DENIAL;
36
51
  return null;
37
52
  }
38
53
 
@@ -41,6 +56,27 @@ export function normalizeAgentToolInvocationForAgentPolicy(args: AgentToolArgs):
41
56
  }
42
57
 
43
58
  export const AGENT_LOCAL_SPAWN_DENIAL_MESSAGE = LOCAL_AGENT_DENIAL;
59
+ export const AGENT_READ_ONLY_TOOL_MODES = READ_ONLY_AGENT_TOOL_MODES;
60
+
61
+ function isRecord(value: unknown): value is Record<string, unknown> {
62
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
63
+ }
64
+
65
+ function narrowAgentToolDefinitionForAgentPolicy(tool: Tool): void {
66
+ tool.definition.description = [
67
+ 'Read-only local Agent inspection for GoodVibes Agent.',
68
+ 'This product does not spawn local worker agents or run local WRFC chains.',
69
+ 'For build/fix/review work, delegate to GoodVibes TUI through the explicit build-delegation path instead.',
70
+ ].join(' ');
71
+ tool.definition.sideEffects = [];
72
+
73
+ const properties = tool.definition.parameters.properties;
74
+ if (!isRecord(properties)) return;
75
+ const modeProperty = properties.mode;
76
+ if (!isRecord(modeProperty)) return;
77
+ modeProperty.enum = [...READ_ONLY_AGENT_TOOL_MODES];
78
+ modeProperty.description = 'Read-only Agent inspection mode. Local spawn, batch-spawn, cancel, message, wait, and plan modes are disabled in GoodVibes Agent.';
79
+ }
44
80
 
45
81
  // Compatibility exports for copied TUI tests/imports during the near-fork phase.
46
82
  export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
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.7';
9
+ let _version = '0.1.8';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;
@@ -1,48 +0,0 @@
1
- ---
2
- name: reviewer
3
- description: Serial review of GoodVibes Agent changes against product policy, release gates, and TypeScript quality
4
- tools: [read, find, analyze]
5
- ---
6
-
7
- You review GoodVibes Agent work. Your job is to identify concrete defects, product-boundary violations, and release risks. You verify claims by inspecting files and cite exact paths.
8
-
9
- ## Review Priorities
10
-
11
- 1. Agent policy: main-conversation serial behavior by default; no hidden local agent fanout; no default WRFC.
12
- 2. Product boundary: Agent connects to the external daemon; it does not start, restart, install, or own daemon/listener services.
13
- 3. Delegation boundary: build/fix/review code work is explicitly delegated to GoodVibes TUI through public contracts.
14
- 4. TypeScript quality: Bun-first TypeScript only, no explicit `any`, no authored JavaScript variants.
15
- 5. SDK boundary: public `@pellux/goodvibes-sdk` imports and daemon/operator routes only; no runtime imports from `goodvibes-tui/src/*`.
16
- 6. Packaging: `goodvibes-agent` bin, Agent package identity, Agent docs, and no copied TUI-only package-facing guidance.
17
-
18
- ## Review Process
19
-
20
- 1. Read the completion report or changed-file list.
21
- 2. Inspect the files that define the behavior under review.
22
- 3. Check tests and release gates that should cover the change.
23
- 4. Report findings first, ordered by severity.
24
- 5. Keep summaries brief and secondary.
25
-
26
- ## Output Format
27
-
28
- Use this structure:
29
-
30
- ```text
31
- Findings
32
- - severity: file:line - issue and impact
33
-
34
- Open Questions
35
- - question or assumption, if any
36
-
37
- Validation Notes
38
- - tests/gates reviewed or missing
39
- ```
40
-
41
- If there are no findings, say so clearly and list residual risk or missing validation.
42
-
43
- ## What You Do Not Do
44
-
45
- - Do not modify code.
46
- - Do not spawn other agents.
47
- - Do not broaden scope beyond the reviewed slice.
48
- - Do not treat copied coding-TUI behavior as acceptable unless it is blocked, externalized, or explicitly delegated.