@pellux/goodvibes-tui 0.19.83 → 0.19.84

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
@@ -4,6 +4,11 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [0.19.84] — 2026-05-09
8
+
9
+ ### Changes
10
+ - 5aeb69ce Fix agent WRFC normalization
11
+
7
12
  ## [0.19.83] — 2026-05-09
8
13
 
9
14
  ### Changes
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-0.19.83-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-0.19.84-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "0.19.83",
3
+ "version": "0.19.84",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -3,19 +3,34 @@ import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
3
3
 
4
4
  type AgentToolArgs = {
5
5
  readonly mode?: unknown;
6
+ readonly task?: unknown;
6
7
  readonly template?: unknown;
7
8
  readonly reviewMode?: unknown;
8
9
  readonly dangerously_disable_wrfc?: unknown;
9
10
  readonly tasks?: unknown;
11
+ readonly [key: string]: unknown;
10
12
  };
11
13
 
12
14
  type AgentTaskArgs = {
15
+ readonly task?: unknown;
13
16
  readonly template?: unknown;
14
17
  readonly reviewMode?: unknown;
15
18
  readonly dangerously_disable_wrfc?: unknown;
19
+ readonly [key: string]: unknown;
16
20
  };
17
21
 
18
22
  const OWNER_BLOCKED_TEMPLATES = new Set(['reviewer', 'review', 'verifier', 'tester', 'test']);
23
+ const OWNER_BLOCKED_TASK_PREFIXES = [
24
+ 'review ',
25
+ 'review:',
26
+ 'review the ',
27
+ 'verify ',
28
+ 'verify:',
29
+ 'verify the ',
30
+ 'test ',
31
+ 'test:',
32
+ 'test the ',
33
+ ];
19
34
 
20
35
  export function installWrfcAgentToolGuard(registry: ToolRegistry): void {
21
36
  const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
@@ -28,13 +43,13 @@ export function wrapWrfcAgentTool(tool: Tool): void {
28
43
  tool.execute = async (args) => {
29
44
  const denial = validateWrfcAgentToolInvocation(args as AgentToolArgs);
30
45
  if (denial) return { success: false, error: denial };
31
- return originalExecute(args);
46
+ return originalExecute(normalizeWrfcAgentToolInvocation(args as AgentToolArgs) as Parameters<Tool['execute']>[0]);
32
47
  };
33
48
  }
34
49
 
35
50
  export function validateWrfcAgentToolInvocation(args: AgentToolArgs): string | null {
36
51
  if (args.mode === 'spawn') {
37
- if (isWrfcEnabled(args, args) && isBlockedOwnerTemplate(args.template)) {
52
+ if (isExplicitWrfcTask(args, args) && isBlockedRootTask(args, args)) {
38
53
  return [
39
54
  'WRFC spawn blocked: a WRFC root task must be an owner/engineer task, not a reviewer/verifier/tester task.',
40
55
  'Spawn one engineer/general owner with reviewMode:"wrfc"; WRFC creates reviewer and fixer agents only after owner output exists.',
@@ -44,40 +59,108 @@ export function validateWrfcAgentToolInvocation(args: AgentToolArgs): string | n
44
59
  }
45
60
 
46
61
  if (args.mode !== 'batch-spawn') return null;
47
- const tasks = Array.isArray(args.tasks) ? args.tasks.filter(isRecord) : [];
48
- const wrfcTasks = tasks.filter((task) => isWrfcEnabled(task, args));
49
- if (wrfcTasks.length === 0) return null;
50
-
51
- if (tasks.length !== 1 || wrfcTasks.length !== 1) {
52
- return [
53
- 'WRFC batch-spawn blocked: WRFC must start as exactly one owner task.',
54
- 'Do not batch an engineer with reviewer/verifier/tester tasks.',
55
- 'Spawn one engineer/general owner with reviewMode:"wrfc"; the WRFC controller creates review/fix agents after the owner deliverable exists.',
56
- ].join(' ');
62
+ return null;
63
+ }
64
+
65
+ export function normalizeWrfcAgentToolInvocation(args: AgentToolArgs): AgentToolArgs {
66
+ if (args.mode === 'spawn') {
67
+ if (isExplicitWrfcTask(args, args)) return { ...args, reviewMode: 'wrfc', dangerously_disable_wrfc: false };
68
+ return { ...args, reviewMode: 'none', dangerously_disable_wrfc: true };
57
69
  }
58
70
 
59
- const [task] = wrfcTasks;
60
- if (isBlockedOwnerTemplate(task.template ?? args.template)) {
61
- return [
62
- 'WRFC batch-spawn blocked: the single WRFC task must be an owner/engineer task, not a reviewer/verifier/tester task.',
63
- 'Use template:"engineer" or template:"general" with reviewMode:"wrfc".',
64
- ].join(' ');
71
+ if (args.mode !== 'batch-spawn') return args;
72
+ const tasks = Array.isArray(args.tasks) ? args.tasks.filter(isRecord) : [];
73
+ const wrfcTasks = tasks.filter((task) => isExplicitWrfcTask(task, args));
74
+ if (wrfcTasks.length === 0) {
75
+ return {
76
+ ...args,
77
+ reviewMode: args.reviewMode === 'none' ? args.reviewMode : 'none',
78
+ dangerously_disable_wrfc: true,
79
+ tasks: tasks.map((task) => task.dangerously_disable_wrfc === true
80
+ ? task
81
+ : { ...task, reviewMode: 'none', dangerously_disable_wrfc: true }),
82
+ };
83
+ }
84
+ if (tasks.length === 1 && wrfcTasks.length === 1 && !isBlockedRootTask(tasks[0], args)) {
85
+ return {
86
+ ...args,
87
+ reviewMode: 'wrfc',
88
+ dangerously_disable_wrfc: false,
89
+ tasks: [{ ...tasks[0], reviewMode: 'wrfc', dangerously_disable_wrfc: false }],
90
+ };
65
91
  }
66
92
 
67
- return null;
93
+ const ownerTask = buildCollapsedWrfcOwnerTask(args, tasks);
94
+ return {
95
+ ...args,
96
+ template: normalizeOwnerTemplate(args.template),
97
+ reviewMode: 'wrfc',
98
+ dangerously_disable_wrfc: false,
99
+ tasks: [ownerTask],
100
+ };
68
101
  }
69
102
 
70
103
  function isRecord(value: unknown): value is AgentTaskArgs {
71
104
  return Boolean(value) && typeof value === 'object';
72
105
  }
73
106
 
74
- function isWrfcEnabled(task: AgentTaskArgs, root: AgentToolArgs): boolean {
107
+ function isExplicitWrfcTask(task: AgentTaskArgs, root: AgentToolArgs): boolean {
75
108
  const disabled = task.dangerously_disable_wrfc === true || root.dangerously_disable_wrfc === true;
76
109
  if (disabled) return false;
77
- return task.reviewMode === 'wrfc' || root.reviewMode === 'wrfc';
110
+ return task.reviewMode === 'wrfc'
111
+ || root.reviewMode === 'wrfc'
112
+ || containsWrfcSignal(task.task)
113
+ || containsWrfcSignal(root.task);
78
114
  }
79
115
 
80
116
  function isBlockedOwnerTemplate(value: unknown): boolean {
81
117
  if (typeof value !== 'string') return false;
82
118
  return OWNER_BLOCKED_TEMPLATES.has(value.trim().toLowerCase());
83
119
  }
120
+
121
+ function isBlockedRootTask(task: AgentTaskArgs, root: AgentToolArgs): boolean {
122
+ if (isBlockedOwnerTemplate(task.template ?? root.template)) return true;
123
+ if (typeof task.task !== 'string') return false;
124
+ const normalized = task.task.trim().toLowerCase();
125
+ return OWNER_BLOCKED_TASK_PREFIXES.some((prefix) => normalized.startsWith(prefix));
126
+ }
127
+
128
+ function normalizeOwnerTemplate(value: unknown): string {
129
+ if (isBlockedOwnerTemplate(value)) return 'engineer';
130
+ return typeof value === 'string' && value.trim().length > 0 ? value : 'engineer';
131
+ }
132
+
133
+ function buildCollapsedWrfcOwnerTask(root: AgentToolArgs, taskList: AgentTaskArgs[]): AgentTaskArgs {
134
+ const firstOwner = taskList.find((task) => isExplicitWrfcTask(task, root) && !isBlockedRootTask(task, root))
135
+ ?? taskList.find((task) => !isBlockedRootTask(task, root))
136
+ ?? taskList[0]
137
+ ?? {};
138
+ const taskLines = taskList.map((task, index) => {
139
+ const body = typeof task.task === 'string' && task.task.trim().length > 0 ? task.task.trim() : '(missing task text)';
140
+ const template = typeof task.template === 'string' && task.template.trim().length > 0 ? task.template.trim() : 'default';
141
+ return `${index + 1}. [${template}] ${body}`;
142
+ });
143
+ const rootTask = typeof root.task === 'string' && root.task.trim().length > 0
144
+ ? `\n\nRoot task:\n${root.task.trim()}`
145
+ : '';
146
+ return {
147
+ ...firstOwner,
148
+ task: [
149
+ 'Complete the requested work as a single WRFC owner chain.',
150
+ 'Do not spawn reviewer, verifier, tester, or parallel root WRFC agents yourself.',
151
+ 'Use the attempted batch items below as context for one coherent owner deliverable; the WRFC controller will create review/fix agents after owner output exists.',
152
+ rootTask,
153
+ '',
154
+ 'Attempted batch items:',
155
+ ...taskLines,
156
+ ].join('\n'),
157
+ template: normalizeOwnerTemplate(firstOwner.template ?? root.template),
158
+ reviewMode: 'wrfc',
159
+ dangerously_disable_wrfc: false,
160
+ };
161
+ }
162
+
163
+ function containsWrfcSignal(value: unknown): boolean {
164
+ if (typeof value !== 'string') return false;
165
+ return /\bwrfc\b|work[-\s]*review[-\s]*fix/i.test(value);
166
+ }
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.19.83';
9
+ let _version = '0.19.84';
10
10
  try {
11
11
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
12
12
  _version = pkg.version ?? _version;