@pellux/goodvibes-agent 0.1.14 → 0.1.16
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 +124 -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.16 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- bea1197 Restrict MCP tool mutations in agent runtime
|
|
8
|
+
|
|
9
|
+
## 0.1.15 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 67de700 Restrict remote and channel tools in agent runtime
|
|
12
|
+
|
|
5
13
|
## 0.1.14 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- d128004 Block background exec in agent runtime
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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",
|
|
@@ -20,6 +20,12 @@ type ExecToolArgs = {
|
|
|
20
20
|
readonly [key: string]: unknown;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
type ModeToolArgs = {
|
|
24
|
+
readonly mode?: unknown;
|
|
25
|
+
readonly createIfMissing?: unknown;
|
|
26
|
+
readonly [key: string]: unknown;
|
|
27
|
+
};
|
|
28
|
+
|
|
23
29
|
type AgentToolPolicyGuardOptions = {
|
|
24
30
|
readonly getLastUserMessage?: () => string | null;
|
|
25
31
|
};
|
|
@@ -42,6 +48,13 @@ const READ_ONLY_AGENT_TOOL_MODES = [
|
|
|
42
48
|
const READ_ONLY_AGENT_TOOL_MODE_SET = new Set<string>(READ_ONLY_AGENT_TOOL_MODES);
|
|
43
49
|
const BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET = new Set<string>(BLOCKED_MAIN_CONVERSATION_TOOL_NAMES);
|
|
44
50
|
|
|
51
|
+
const READ_ONLY_REMOTE_TOOL_MODES = ['pools', 'contracts', 'artifacts', 'review'] as const;
|
|
52
|
+
const READ_ONLY_CHANNEL_TOOL_MODES = ['accounts', 'directory', 'resolve_target', 'capabilities', 'tools', 'agent_tools', 'actions'] as const;
|
|
53
|
+
const READ_ONLY_MCP_TOOL_MODES = ['servers', 'tools', 'schema', 'resources', 'security', 'auth'] as const;
|
|
54
|
+
const READ_ONLY_REMOTE_TOOL_MODE_SET = new Set<string>(READ_ONLY_REMOTE_TOOL_MODES);
|
|
55
|
+
const READ_ONLY_CHANNEL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CHANNEL_TOOL_MODES);
|
|
56
|
+
const READ_ONLY_MCP_TOOL_MODE_SET = new Set<string>(READ_ONLY_MCP_TOOL_MODES);
|
|
57
|
+
|
|
45
58
|
const LOCAL_AGENT_DENIAL = [
|
|
46
59
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
47
60
|
'Keep ordinary assistant work serial in the main conversation.',
|
|
@@ -60,6 +73,24 @@ const BACKGROUND_EXEC_DENIAL = [
|
|
|
60
73
|
'For long-running build/fix/review work, delegate one request to GoodVibes TUI through the public shared-session/build-delegation contract.',
|
|
61
74
|
].join(' ');
|
|
62
75
|
|
|
76
|
+
const REMOTE_MUTATION_DENIAL = [
|
|
77
|
+
'GoodVibes Agent only inspects remote runner pools, contracts, artifacts, and review summaries from the main conversation.',
|
|
78
|
+
'Remote pool creation, assignment, unassignment, and artifact import are disabled here.',
|
|
79
|
+
'Use explicit GoodVibes TUI delegation or a future Agent approval flow for remote execution changes.',
|
|
80
|
+
].join(' ');
|
|
81
|
+
|
|
82
|
+
const CHANNEL_ACTION_DENIAL = [
|
|
83
|
+
'GoodVibes Agent only inspects channel accounts, directories, capabilities, tools, and actions from the main conversation.',
|
|
84
|
+
'Channel account actions, tool runs, operator action runs, authorization, and target auto-creation are disabled here.',
|
|
85
|
+
'External channel side effects require an explicit Agent approval flow before they can run.',
|
|
86
|
+
].join(' ');
|
|
87
|
+
|
|
88
|
+
const MCP_SECURITY_MUTATION_DENIAL = [
|
|
89
|
+
'GoodVibes Agent only inspects MCP servers, tools, schemas, resources, security, and auth state from the main conversation.',
|
|
90
|
+
'MCP quarantine approval, trust changes, and role changes are disabled here.',
|
|
91
|
+
'MCP security mutations require an explicit Agent approval flow before they can run.',
|
|
92
|
+
].join(' ');
|
|
93
|
+
|
|
63
94
|
export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
|
|
64
95
|
const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
|
|
65
96
|
if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
|
|
@@ -67,6 +98,28 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
67
98
|
for (const tool of registry.list()) {
|
|
68
99
|
if (tool.definition.name === 'exec') {
|
|
69
100
|
wrapExecToolForAgentPolicy(tool);
|
|
101
|
+
} else if (tool.definition.name === 'remote') {
|
|
102
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
103
|
+
allowedModes: READ_ONLY_REMOTE_TOOL_MODES,
|
|
104
|
+
modeSet: READ_ONLY_REMOTE_TOOL_MODE_SET,
|
|
105
|
+
description: [
|
|
106
|
+
'Read-only remote runner inspection for GoodVibes Agent.',
|
|
107
|
+
'Pool creation, runner assignment, unassignment, and artifact import are disabled in the main conversation.',
|
|
108
|
+
].join(' '),
|
|
109
|
+
denial: REMOTE_MUTATION_DENIAL,
|
|
110
|
+
});
|
|
111
|
+
} else if (tool.definition.name === 'channel') {
|
|
112
|
+
wrapChannelToolForAgentPolicy(tool);
|
|
113
|
+
} else if (tool.definition.name === 'mcp') {
|
|
114
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
115
|
+
allowedModes: READ_ONLY_MCP_TOOL_MODES,
|
|
116
|
+
modeSet: READ_ONLY_MCP_TOOL_MODE_SET,
|
|
117
|
+
description: [
|
|
118
|
+
'Read-only MCP inspection for GoodVibes Agent.',
|
|
119
|
+
'Quarantine approval, trust mutation, and role mutation are disabled in the main conversation.',
|
|
120
|
+
].join(' '),
|
|
121
|
+
denial: MCP_SECURITY_MUTATION_DENIAL,
|
|
122
|
+
});
|
|
70
123
|
} else if (BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET.has(tool.definition.name)) {
|
|
71
124
|
wrapBlockedMainConversationToolForAgentPolicy(tool);
|
|
72
125
|
}
|
|
@@ -134,11 +187,62 @@ export function validateExecToolInvocationForAgentPolicy(args: ExecToolArgs): st
|
|
|
134
187
|
return null;
|
|
135
188
|
}
|
|
136
189
|
|
|
190
|
+
type ModeRestrictedToolPolicy = {
|
|
191
|
+
readonly allowedModes: readonly string[];
|
|
192
|
+
readonly modeSet: ReadonlySet<string>;
|
|
193
|
+
readonly description: string;
|
|
194
|
+
readonly denial: string;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export function wrapModeRestrictedToolForAgentPolicy(tool: Tool, policy: ModeRestrictedToolPolicy): void {
|
|
198
|
+
narrowModeToolDefinitionForAgentPolicy(tool, policy.allowedModes, policy.description);
|
|
199
|
+
const originalExecute = tool.execute.bind(tool);
|
|
200
|
+
tool.execute = async (args) => {
|
|
201
|
+
const denial = validateModeRestrictedToolInvocationForAgentPolicy(args as ModeToolArgs, policy.modeSet, policy.denial);
|
|
202
|
+
if (denial) return { success: false, error: denial };
|
|
203
|
+
return originalExecute(args);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function wrapChannelToolForAgentPolicy(tool: Tool): void {
|
|
208
|
+
narrowModeToolDefinitionForAgentPolicy(tool, READ_ONLY_CHANNEL_TOOL_MODES, [
|
|
209
|
+
'Read-only channel inspection for GoodVibes Agent.',
|
|
210
|
+
'Running channel tools/actions, account lifecycle actions, authorization, and target creation are disabled in the main conversation.',
|
|
211
|
+
].join(' '));
|
|
212
|
+
const originalExecute = tool.execute.bind(tool);
|
|
213
|
+
tool.execute = async (args) => {
|
|
214
|
+
const denial = validateModeRestrictedToolInvocationForAgentPolicy(args as ModeToolArgs, READ_ONLY_CHANNEL_TOOL_MODE_SET, CHANNEL_ACTION_DENIAL)
|
|
215
|
+
?? validateChannelToolInvocationForAgentPolicy(args as ModeToolArgs);
|
|
216
|
+
if (denial) return { success: false, error: denial };
|
|
217
|
+
return originalExecute(args);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function validateModeRestrictedToolInvocationForAgentPolicy(
|
|
222
|
+
args: ModeToolArgs,
|
|
223
|
+
modeSet: ReadonlySet<string>,
|
|
224
|
+
denial: string,
|
|
225
|
+
): string | null {
|
|
226
|
+
if (typeof args.mode === 'string' && !modeSet.has(args.mode)) return denial;
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function validateChannelToolInvocationForAgentPolicy(args: ModeToolArgs): string | null {
|
|
231
|
+
if (args.mode === 'resolve_target' && args.createIfMissing === true) return CHANNEL_ACTION_DENIAL;
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
|
|
137
235
|
export const AGENT_LOCAL_SPAWN_DENIAL_MESSAGE = LOCAL_AGENT_DENIAL;
|
|
138
236
|
export const AGENT_READ_ONLY_TOOL_MODES = READ_ONLY_AGENT_TOOL_MODES;
|
|
139
237
|
export const AGENT_BLOCKED_MAIN_CONVERSATION_TOOL_NAMES = BLOCKED_MAIN_CONVERSATION_TOOL_NAMES;
|
|
140
238
|
export const AGENT_MAIN_CONVERSATION_TOOL_DENIAL_MESSAGE = LOCAL_CODING_TOOL_DENIAL;
|
|
141
239
|
export const AGENT_EXEC_BACKGROUND_DENIAL_MESSAGE = BACKGROUND_EXEC_DENIAL;
|
|
240
|
+
export const AGENT_READ_ONLY_REMOTE_TOOL_MODES = READ_ONLY_REMOTE_TOOL_MODES;
|
|
241
|
+
export const AGENT_READ_ONLY_CHANNEL_TOOL_MODES = READ_ONLY_CHANNEL_TOOL_MODES;
|
|
242
|
+
export const AGENT_READ_ONLY_MCP_TOOL_MODES = READ_ONLY_MCP_TOOL_MODES;
|
|
243
|
+
export const AGENT_REMOTE_MUTATION_DENIAL_MESSAGE = REMOTE_MUTATION_DENIAL;
|
|
244
|
+
export const AGENT_CHANNEL_ACTION_DENIAL_MESSAGE = CHANNEL_ACTION_DENIAL;
|
|
245
|
+
export const AGENT_MCP_SECURITY_MUTATION_DENIAL_MESSAGE = MCP_SECURITY_MUTATION_DENIAL;
|
|
142
246
|
|
|
143
247
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
144
248
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -189,6 +293,26 @@ function narrowExecToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
|
189
293
|
}
|
|
190
294
|
}
|
|
191
295
|
|
|
296
|
+
function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readonly string[], description: string): void {
|
|
297
|
+
tool.definition.description = description;
|
|
298
|
+
|
|
299
|
+
const properties = tool.definition.parameters.properties;
|
|
300
|
+
if (!isRecord(properties)) return;
|
|
301
|
+
const modeProperty = properties.mode;
|
|
302
|
+
if (isRecord(modeProperty)) {
|
|
303
|
+
modeProperty.enum = [...allowedModes];
|
|
304
|
+
modeProperty.description = 'Read-only modes allowed by GoodVibes Agent main-conversation policy.';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (tool.definition.name === 'channel') {
|
|
308
|
+
delete properties.accountAction;
|
|
309
|
+
delete properties.toolId;
|
|
310
|
+
delete properties.actionId;
|
|
311
|
+
delete properties.actorId;
|
|
312
|
+
delete properties.createIfMissing;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
192
316
|
// Compatibility exports for copied TUI tests/imports during the near-fork phase.
|
|
193
317
|
export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
|
|
194
318
|
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.16';
|
|
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 {
|