@pellux/goodvibes-agent 0.1.18 → 0.1.20
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 +94 -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.20 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- c0eca13 Block settings mutation tool in agent runtime
|
|
8
|
+
|
|
9
|
+
## 0.1.19 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- a4255d5 Restrict durable workflow tool mutations in agent runtime
|
|
12
|
+
|
|
5
13
|
## 0.1.18 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 1fd7729 Restrict state tool mutations 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.20",
|
|
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",
|
|
@@ -85,6 +85,11 @@ const READ_ONLY_STATE_MEMORY_ACTIONS = ['list', 'get'] as const;
|
|
|
85
85
|
const READ_ONLY_STATE_HOOK_ACTIONS = ['list'] as const;
|
|
86
86
|
const READ_ONLY_STATE_MODE_ACTIONS = ['get', 'list'] as const;
|
|
87
87
|
const READ_ONLY_STATE_ANALYTICS_ACTIONS = ['summary', 'query', 'dashboard'] as const;
|
|
88
|
+
const READ_ONLY_TASK_TOOL_MODES = ['list', 'show', 'handoffs'] as const;
|
|
89
|
+
const READ_ONLY_TEAM_TOOL_MODES = ['list', 'show'] as const;
|
|
90
|
+
const READ_ONLY_WORKLIST_TOOL_MODES = ['list', 'show'] as const;
|
|
91
|
+
const READ_ONLY_PACKET_TOOL_MODES = ['list', 'show'] as const;
|
|
92
|
+
const READ_ONLY_QUERY_TOOL_MODES = ['list', 'show'] as const;
|
|
88
93
|
const READ_ONLY_REMOTE_TOOL_MODE_SET = new Set<string>(READ_ONLY_REMOTE_TOOL_MODES);
|
|
89
94
|
const READ_ONLY_CHANNEL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CHANNEL_TOOL_MODES);
|
|
90
95
|
const READ_ONLY_MCP_TOOL_MODE_SET = new Set<string>(READ_ONLY_MCP_TOOL_MODES);
|
|
@@ -94,6 +99,11 @@ const READ_ONLY_STATE_MEMORY_ACTION_SET = new Set<string>(READ_ONLY_STATE_MEMORY
|
|
|
94
99
|
const READ_ONLY_STATE_HOOK_ACTION_SET = new Set<string>(READ_ONLY_STATE_HOOK_ACTIONS);
|
|
95
100
|
const READ_ONLY_STATE_MODE_ACTION_SET = new Set<string>(READ_ONLY_STATE_MODE_ACTIONS);
|
|
96
101
|
const READ_ONLY_STATE_ANALYTICS_ACTION_SET = new Set<string>(READ_ONLY_STATE_ANALYTICS_ACTIONS);
|
|
102
|
+
const READ_ONLY_TASK_TOOL_MODE_SET = new Set<string>(READ_ONLY_TASK_TOOL_MODES);
|
|
103
|
+
const READ_ONLY_TEAM_TOOL_MODE_SET = new Set<string>(READ_ONLY_TEAM_TOOL_MODES);
|
|
104
|
+
const READ_ONLY_WORKLIST_TOOL_MODE_SET = new Set<string>(READ_ONLY_WORKLIST_TOOL_MODES);
|
|
105
|
+
const READ_ONLY_PACKET_TOOL_MODE_SET = new Set<string>(READ_ONLY_PACKET_TOOL_MODES);
|
|
106
|
+
const READ_ONLY_QUERY_TOOL_MODE_SET = new Set<string>(READ_ONLY_QUERY_TOOL_MODES);
|
|
97
107
|
|
|
98
108
|
const LOCAL_AGENT_DENIAL = [
|
|
99
109
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
@@ -143,6 +153,18 @@ const STATE_MUTATION_DENIAL = [
|
|
|
143
153
|
'Use Agent-owned memory, skills, personas, routines, and explicit CLI/slash commands for intentional local state changes.',
|
|
144
154
|
].join(' ');
|
|
145
155
|
|
|
156
|
+
const SETTINGS_MUTATION_DENIAL = [
|
|
157
|
+
'GoodVibes Agent does not mutate configuration through model tools in the main conversation.',
|
|
158
|
+
'Use explicit Agent CLI/slash settings commands for intentional config changes.',
|
|
159
|
+
'Secrets, tokens, passwords, daemon lifecycle settings, and service exposure settings require explicit user action outside the model tool surface.',
|
|
160
|
+
].join(' ');
|
|
161
|
+
|
|
162
|
+
const DURABLE_WORKFLOW_MUTATION_DENIAL = [
|
|
163
|
+
'GoodVibes Agent only inspects copied durable workflow tools from the main conversation.',
|
|
164
|
+
'Task, team, worklist, packet, and query creation or lifecycle mutation is disabled here.',
|
|
165
|
+
'Use explicit Agent CLI/slash commands or GoodVibes TUI delegation for intentional workflow changes.',
|
|
166
|
+
].join(' ');
|
|
167
|
+
|
|
146
168
|
export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
|
|
147
169
|
const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
|
|
148
170
|
if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
|
|
@@ -176,6 +198,48 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
176
198
|
wrapFetchToolForAgentPolicy(tool);
|
|
177
199
|
} else if (tool.definition.name === 'state') {
|
|
178
200
|
wrapStateToolForAgentPolicy(tool);
|
|
201
|
+
} else if (tool.definition.name === 'goodvibes_settings') {
|
|
202
|
+
wrapBlockedSettingsToolForAgentPolicy(tool);
|
|
203
|
+
} else if (tool.definition.name === 'task') {
|
|
204
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
205
|
+
allowedModes: READ_ONLY_TASK_TOOL_MODES,
|
|
206
|
+
modeSet: READ_ONLY_TASK_TOOL_MODE_SET,
|
|
207
|
+
description: 'Read-only task/workflow inspection for GoodVibes Agent. Task creation, status changes, dependencies, cancellation, and handoff mutation are disabled in the main conversation.',
|
|
208
|
+
denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
|
|
209
|
+
removedProperties: ['title', 'label', 'status', 'dependsOnSessionId', 'dependsOnTaskId', 'reason', 'toSessionId'],
|
|
210
|
+
});
|
|
211
|
+
} else if (tool.definition.name === 'team') {
|
|
212
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
213
|
+
allowedModes: READ_ONLY_TEAM_TOOL_MODES,
|
|
214
|
+
modeSet: READ_ONLY_TEAM_TOOL_MODE_SET,
|
|
215
|
+
description: 'Read-only team inspection for GoodVibes Agent. Team creation, membership changes, lane changes, and deletion are disabled in the main conversation.',
|
|
216
|
+
denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
|
|
217
|
+
removedProperties: ['name', 'summary', 'memberId', 'role', 'lanes'],
|
|
218
|
+
});
|
|
219
|
+
} else if (tool.definition.name === 'worklist') {
|
|
220
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
221
|
+
allowedModes: READ_ONLY_WORKLIST_TOOL_MODES,
|
|
222
|
+
modeSet: READ_ONLY_WORKLIST_TOOL_MODE_SET,
|
|
223
|
+
description: 'Read-only worklist inspection for GoodVibes Agent. Worklist creation and item lifecycle changes are disabled in the main conversation.',
|
|
224
|
+
denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
|
|
225
|
+
removedProperties: ['title', 'itemId', 'text', 'owner', 'priority'],
|
|
226
|
+
});
|
|
227
|
+
} else if (tool.definition.name === 'packet') {
|
|
228
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
229
|
+
allowedModes: READ_ONLY_PACKET_TOOL_MODES,
|
|
230
|
+
modeSet: READ_ONLY_PACKET_TOOL_MODE_SET,
|
|
231
|
+
description: 'Read-only operator packet inspection for GoodVibes Agent. Packet creation, revision, and publishing are disabled in the main conversation.',
|
|
232
|
+
denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
|
|
233
|
+
removedProperties: ['title', 'summary', 'goals', 'constraints', 'risks', 'audience'],
|
|
234
|
+
});
|
|
235
|
+
} else if (tool.definition.name === 'query') {
|
|
236
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
237
|
+
allowedModes: READ_ONLY_QUERY_TOOL_MODES,
|
|
238
|
+
modeSet: READ_ONLY_QUERY_TOOL_MODE_SET,
|
|
239
|
+
description: 'Read-only operator query inspection for GoodVibes Agent. Asking, answering, and closing copied workflow queries are disabled in the main conversation.',
|
|
240
|
+
denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
|
|
241
|
+
removedProperties: ['prompt', 'askedBy', 'target', 'answer', 'resolution'],
|
|
242
|
+
});
|
|
179
243
|
} else if (BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET.has(tool.definition.name)) {
|
|
180
244
|
wrapBlockedMainConversationToolForAgentPolicy(tool);
|
|
181
245
|
}
|
|
@@ -241,6 +305,21 @@ export function wrapStateToolForAgentPolicy(tool: Tool): void {
|
|
|
241
305
|
};
|
|
242
306
|
}
|
|
243
307
|
|
|
308
|
+
export function wrapBlockedSettingsToolForAgentPolicy(tool: Tool): void {
|
|
309
|
+
tool.definition.description = [
|
|
310
|
+
'Blocked in GoodVibes Agent main conversation: configuration mutation.',
|
|
311
|
+
'Use explicit Agent CLI/slash settings commands for intentional config changes.',
|
|
312
|
+
'Daemon lifecycle and service exposure remain externally managed by GoodVibes TUI/daemon.',
|
|
313
|
+
].join(' ');
|
|
314
|
+
tool.definition.sideEffects = [];
|
|
315
|
+
tool.definition.parameters = {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {},
|
|
318
|
+
additionalProperties: false,
|
|
319
|
+
};
|
|
320
|
+
tool.execute = async () => ({ success: false, error: SETTINGS_MUTATION_DENIAL });
|
|
321
|
+
}
|
|
322
|
+
|
|
244
323
|
export function validateExecToolInvocationForAgentPolicy(args: ExecToolArgs): string | null {
|
|
245
324
|
if (args.parallel === true) return BACKGROUND_EXEC_DENIAL;
|
|
246
325
|
if (Array.isArray(args.file_ops) && args.file_ops.length > 0) return BACKGROUND_EXEC_DENIAL;
|
|
@@ -330,10 +409,12 @@ type ModeRestrictedToolPolicy = {
|
|
|
330
409
|
readonly modeSet: ReadonlySet<string>;
|
|
331
410
|
readonly description: string;
|
|
332
411
|
readonly denial: string;
|
|
412
|
+
readonly removedProperties?: readonly string[];
|
|
333
413
|
};
|
|
334
414
|
|
|
335
415
|
export function wrapModeRestrictedToolForAgentPolicy(tool: Tool, policy: ModeRestrictedToolPolicy): void {
|
|
336
416
|
narrowModeToolDefinitionForAgentPolicy(tool, policy.allowedModes, policy.description);
|
|
417
|
+
if (policy.removedProperties) removeToolDefinitionProperties(tool, policy.removedProperties);
|
|
337
418
|
const originalExecute = tool.execute.bind(tool);
|
|
338
419
|
tool.execute = async (args) => {
|
|
339
420
|
const denial = validateModeRestrictedToolInvocationForAgentPolicy(args as ModeToolArgs, policy.modeSet, policy.denial);
|
|
@@ -384,11 +465,18 @@ export const AGENT_READ_ONLY_STATE_MEMORY_ACTIONS = READ_ONLY_STATE_MEMORY_ACTIO
|
|
|
384
465
|
export const AGENT_READ_ONLY_STATE_HOOK_ACTIONS = READ_ONLY_STATE_HOOK_ACTIONS;
|
|
385
466
|
export const AGENT_READ_ONLY_STATE_MODE_ACTIONS = READ_ONLY_STATE_MODE_ACTIONS;
|
|
386
467
|
export const AGENT_READ_ONLY_STATE_ANALYTICS_ACTIONS = READ_ONLY_STATE_ANALYTICS_ACTIONS;
|
|
468
|
+
export const AGENT_READ_ONLY_TASK_TOOL_MODES = READ_ONLY_TASK_TOOL_MODES;
|
|
469
|
+
export const AGENT_READ_ONLY_TEAM_TOOL_MODES = READ_ONLY_TEAM_TOOL_MODES;
|
|
470
|
+
export const AGENT_READ_ONLY_WORKLIST_TOOL_MODES = READ_ONLY_WORKLIST_TOOL_MODES;
|
|
471
|
+
export const AGENT_READ_ONLY_PACKET_TOOL_MODES = READ_ONLY_PACKET_TOOL_MODES;
|
|
472
|
+
export const AGENT_READ_ONLY_QUERY_TOOL_MODES = READ_ONLY_QUERY_TOOL_MODES;
|
|
387
473
|
export const AGENT_REMOTE_MUTATION_DENIAL_MESSAGE = REMOTE_MUTATION_DENIAL;
|
|
388
474
|
export const AGENT_CHANNEL_ACTION_DENIAL_MESSAGE = CHANNEL_ACTION_DENIAL;
|
|
389
475
|
export const AGENT_MCP_SECURITY_MUTATION_DENIAL_MESSAGE = MCP_SECURITY_MUTATION_DENIAL;
|
|
390
476
|
export const AGENT_FETCH_NETWORK_MUTATION_DENIAL_MESSAGE = FETCH_NETWORK_MUTATION_DENIAL;
|
|
391
477
|
export const AGENT_STATE_MUTATION_DENIAL_MESSAGE = STATE_MUTATION_DENIAL;
|
|
478
|
+
export const AGENT_SETTINGS_MUTATION_DENIAL_MESSAGE = SETTINGS_MUTATION_DENIAL;
|
|
479
|
+
export const AGENT_DURABLE_WORKFLOW_MUTATION_DENIAL_MESSAGE = DURABLE_WORKFLOW_MUTATION_DENIAL;
|
|
392
480
|
|
|
393
481
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
394
482
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -542,6 +630,12 @@ function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readon
|
|
|
542
630
|
}
|
|
543
631
|
}
|
|
544
632
|
|
|
633
|
+
function removeToolDefinitionProperties(tool: Tool, keys: readonly string[]): void {
|
|
634
|
+
const properties = tool.definition.parameters.properties;
|
|
635
|
+
if (!isRecord(properties)) return;
|
|
636
|
+
for (const key of keys) delete properties[key];
|
|
637
|
+
}
|
|
638
|
+
|
|
545
639
|
function narrowStringEnumProperty(
|
|
546
640
|
properties: Record<string, unknown>,
|
|
547
641
|
key: string,
|
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.20';
|
|
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 {
|