@pellux/goodvibes-agent 0.1.12 → 0.1.14
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 +8 -0
- package/package.json +1 -1
- package/src/tools/wrfc-agent-guard.ts +111 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.14 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- d128004 Block background exec in agent runtime
|
|
8
|
+
|
|
9
|
+
## 0.1.13 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 989b048 Block local coding tools in agent runtime
|
|
12
|
+
|
|
5
13
|
## 0.1.12 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 1843a77 Handle external daemon SDK mismatch in live verification
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
|
|
6
6
|
"type": "module",
|
|
@@ -6,10 +6,27 @@ type AgentToolArgs = {
|
|
|
6
6
|
readonly [key: string]: unknown;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
+
type ExecCommandArgs = {
|
|
10
|
+
readonly cmd?: unknown;
|
|
11
|
+
readonly background?: unknown;
|
|
12
|
+
readonly until?: unknown;
|
|
13
|
+
readonly [key: string]: unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ExecToolArgs = {
|
|
17
|
+
readonly commands?: unknown;
|
|
18
|
+
readonly parallel?: unknown;
|
|
19
|
+
readonly file_ops?: unknown;
|
|
20
|
+
readonly [key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
|
|
9
23
|
type AgentToolPolicyGuardOptions = {
|
|
10
24
|
readonly getLastUserMessage?: () => string | null;
|
|
11
25
|
};
|
|
12
26
|
|
|
27
|
+
const BLOCKED_MAIN_CONVERSATION_TOOL_NAMES = ['write', 'edit', 'workflow', 'repl'] as const;
|
|
28
|
+
const AGENT_EXEC_BACKGROUND_COMMAND = /^\s*bg_(?:status|output|stop)\b/;
|
|
29
|
+
|
|
13
30
|
const READ_ONLY_AGENT_TOOL_MODES = [
|
|
14
31
|
'status',
|
|
15
32
|
'list',
|
|
@@ -23,6 +40,7 @@ const READ_ONLY_AGENT_TOOL_MODES = [
|
|
|
23
40
|
] as const;
|
|
24
41
|
|
|
25
42
|
const READ_ONLY_AGENT_TOOL_MODE_SET = new Set<string>(READ_ONLY_AGENT_TOOL_MODES);
|
|
43
|
+
const BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET = new Set<string>(BLOCKED_MAIN_CONVERSATION_TOOL_NAMES);
|
|
26
44
|
|
|
27
45
|
const LOCAL_AGENT_DENIAL = [
|
|
28
46
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
@@ -30,10 +48,29 @@ const LOCAL_AGENT_DENIAL = [
|
|
|
30
48
|
'For explicit build/fix/review work, delegate one request to GoodVibes TUI through the public shared-session/build-delegation contract with the full original user ask.',
|
|
31
49
|
].join(' ');
|
|
32
50
|
|
|
51
|
+
const LOCAL_CODING_TOOL_DENIAL = [
|
|
52
|
+
'GoodVibes Agent does not perform direct local file mutation, local WRFC workflow execution, or local sandbox/REPL execution from the main conversation.',
|
|
53
|
+
'For explicit build/fix/review/code execution work, delegate one request to GoodVibes TUI through the public shared-session/build-delegation contract with the full original user ask.',
|
|
54
|
+
'For durable Agent memory, skills, personas, routines, and knowledge, use the Agent-owned commands and isolated Agent Knowledge routes.',
|
|
55
|
+
].join(' ');
|
|
56
|
+
|
|
57
|
+
const BACKGROUND_EXEC_DENIAL = [
|
|
58
|
+
'GoodVibes Agent only runs foreground, serial command-line work from the main conversation.',
|
|
59
|
+
'Background processes, parallel command batches, background process controls, and exec pre-command file operations are disabled here.',
|
|
60
|
+
'For long-running build/fix/review work, delegate one request to GoodVibes TUI through the public shared-session/build-delegation contract.',
|
|
61
|
+
].join(' ');
|
|
62
|
+
|
|
33
63
|
export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
|
|
34
64
|
const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
|
|
35
65
|
if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
|
|
36
66
|
wrapAgentToolForAgentPolicy(agentTool, options);
|
|
67
|
+
for (const tool of registry.list()) {
|
|
68
|
+
if (tool.definition.name === 'exec') {
|
|
69
|
+
wrapExecToolForAgentPolicy(tool);
|
|
70
|
+
} else if (BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET.has(tool.definition.name)) {
|
|
71
|
+
wrapBlockedMainConversationToolForAgentPolicy(tool);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
37
74
|
}
|
|
38
75
|
|
|
39
76
|
export function wrapAgentToolForAgentPolicy(tool: Tool, _options: AgentToolPolicyGuardOptions = {}): void {
|
|
@@ -55,8 +92,53 @@ export function normalizeAgentToolInvocationForAgentPolicy(args: AgentToolArgs):
|
|
|
55
92
|
return args;
|
|
56
93
|
}
|
|
57
94
|
|
|
95
|
+
export function wrapBlockedMainConversationToolForAgentPolicy(tool: Tool): void {
|
|
96
|
+
tool.definition.description = [
|
|
97
|
+
`Blocked in GoodVibes Agent main conversation: ${tool.definition.name}.`,
|
|
98
|
+
'Use explicit GoodVibes TUI build delegation for build/fix/review/code execution work.',
|
|
99
|
+
'Use Agent-owned local registries and isolated Agent Knowledge routes for Agent memory and knowledge work.',
|
|
100
|
+
].join(' ');
|
|
101
|
+
tool.definition.sideEffects = [];
|
|
102
|
+
tool.execute = async () => ({ success: false, error: LOCAL_CODING_TOOL_DENIAL });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function wrapExecToolForAgentPolicy(tool: Tool): void {
|
|
106
|
+
narrowExecToolDefinitionForAgentPolicy(tool);
|
|
107
|
+
const originalExecute = tool.execute.bind(tool);
|
|
108
|
+
tool.execute = async (args) => {
|
|
109
|
+
const denial = validateExecToolInvocationForAgentPolicy(args as ExecToolArgs);
|
|
110
|
+
if (denial) return { success: false, error: denial };
|
|
111
|
+
return originalExecute(args);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function validateExecToolInvocationForAgentPolicy(args: ExecToolArgs): string | null {
|
|
116
|
+
if (args.parallel === true) return BACKGROUND_EXEC_DENIAL;
|
|
117
|
+
if (Array.isArray(args.file_ops) && args.file_ops.length > 0) return BACKGROUND_EXEC_DENIAL;
|
|
118
|
+
if (args.file_ops !== undefined && !Array.isArray(args.file_ops)) return BACKGROUND_EXEC_DENIAL;
|
|
119
|
+
if (!Array.isArray(args.commands)) return null;
|
|
120
|
+
|
|
121
|
+
for (const command of args.commands) {
|
|
122
|
+
if (!isRecord(command)) continue;
|
|
123
|
+
const commandArgs = command as ExecCommandArgs;
|
|
124
|
+
if (commandArgs.background === true) return BACKGROUND_EXEC_DENIAL;
|
|
125
|
+
if (typeof commandArgs.cmd === 'string' && AGENT_EXEC_BACKGROUND_COMMAND.test(commandArgs.cmd)) {
|
|
126
|
+
return BACKGROUND_EXEC_DENIAL;
|
|
127
|
+
}
|
|
128
|
+
if (isRecord(commandArgs.until)) {
|
|
129
|
+
const killAfter = commandArgs.until.kill_after;
|
|
130
|
+
if (killAfter !== true) return BACKGROUND_EXEC_DENIAL;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
58
137
|
export const AGENT_LOCAL_SPAWN_DENIAL_MESSAGE = LOCAL_AGENT_DENIAL;
|
|
59
138
|
export const AGENT_READ_ONLY_TOOL_MODES = READ_ONLY_AGENT_TOOL_MODES;
|
|
139
|
+
export const AGENT_BLOCKED_MAIN_CONVERSATION_TOOL_NAMES = BLOCKED_MAIN_CONVERSATION_TOOL_NAMES;
|
|
140
|
+
export const AGENT_MAIN_CONVERSATION_TOOL_DENIAL_MESSAGE = LOCAL_CODING_TOOL_DENIAL;
|
|
141
|
+
export const AGENT_EXEC_BACKGROUND_DENIAL_MESSAGE = BACKGROUND_EXEC_DENIAL;
|
|
60
142
|
|
|
61
143
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
62
144
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -78,6 +160,35 @@ function narrowAgentToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
|
78
160
|
modeProperty.description = 'Read-only Agent inspection mode. Local spawn, batch-spawn, cancel, message, wait, and plan modes are disabled in GoodVibes Agent.';
|
|
79
161
|
}
|
|
80
162
|
|
|
163
|
+
function narrowExecToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
164
|
+
tool.definition.description = [
|
|
165
|
+
'Execute foreground shell commands serially for GoodVibes Agent main-conversation work.',
|
|
166
|
+
'Background processes, parallel batches, background process controls, and exec file_ops are disabled by Agent policy.',
|
|
167
|
+
'Delegate long-running build/fix/review execution to GoodVibes TUI instead.',
|
|
168
|
+
].join(' ');
|
|
169
|
+
|
|
170
|
+
const properties = tool.definition.parameters.properties;
|
|
171
|
+
if (!isRecord(properties)) return;
|
|
172
|
+
delete properties.parallel;
|
|
173
|
+
delete properties.file_ops;
|
|
174
|
+
|
|
175
|
+
const commandsProperty = properties.commands;
|
|
176
|
+
if (!isRecord(commandsProperty)) return;
|
|
177
|
+
const itemSchema = commandsProperty.items;
|
|
178
|
+
if (!isRecord(itemSchema)) return;
|
|
179
|
+
const commandProperties = itemSchema.properties;
|
|
180
|
+
if (!isRecord(commandProperties)) return;
|
|
181
|
+
|
|
182
|
+
delete commandProperties.background;
|
|
183
|
+
const untilProperty = commandProperties.until;
|
|
184
|
+
if (isRecord(untilProperty)) {
|
|
185
|
+
untilProperty.description = [
|
|
186
|
+
'Pattern-based early termination.',
|
|
187
|
+
'GoodVibes Agent requires kill_after:true so until-mode does not promote the process to background.',
|
|
188
|
+
].join(' ');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
81
192
|
// Compatibility exports for copied TUI tests/imports during the near-fork phase.
|
|
82
193
|
export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
|
|
83
194
|
export const wrapWrfcAgentTool = wrapAgentToolForAgentPolicy;
|
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.
|
|
9
|
+
let _version = '0.1.14';
|
|
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 {
|