@pellux/goodvibes-agent 0.1.17 → 0.1.18
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 +4 -0
- package/package.json +1 -1
- package/src/tools/wrfc-agent-guard.ts +137 -0
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
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",
|
|
@@ -34,6 +34,26 @@ type FetchToolArgs = {
|
|
|
34
34
|
readonly [key: string]: unknown;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
type StateToolArgs = {
|
|
38
|
+
readonly mode?: unknown;
|
|
39
|
+
readonly memoryAction?: unknown;
|
|
40
|
+
readonly hookAction?: unknown;
|
|
41
|
+
readonly modeAction?: unknown;
|
|
42
|
+
readonly analyticsAction?: unknown;
|
|
43
|
+
readonly values?: unknown;
|
|
44
|
+
readonly clearKeys?: unknown;
|
|
45
|
+
readonly memoryValue?: unknown;
|
|
46
|
+
readonly hookDefinition?: unknown;
|
|
47
|
+
readonly modeName?: unknown;
|
|
48
|
+
readonly analyticsTool?: unknown;
|
|
49
|
+
readonly analyticsArgs?: unknown;
|
|
50
|
+
readonly analyticsResult?: unknown;
|
|
51
|
+
readonly analyticsDuration?: unknown;
|
|
52
|
+
readonly analyticsTokens?: unknown;
|
|
53
|
+
readonly analyticsFormat?: unknown;
|
|
54
|
+
readonly [key: string]: unknown;
|
|
55
|
+
};
|
|
56
|
+
|
|
37
57
|
type AgentToolPolicyGuardOptions = {
|
|
38
58
|
readonly getLastUserMessage?: () => string | null;
|
|
39
59
|
};
|
|
@@ -60,10 +80,20 @@ const READ_ONLY_REMOTE_TOOL_MODES = ['pools', 'contracts', 'artifacts', 'review'
|
|
|
60
80
|
const READ_ONLY_CHANNEL_TOOL_MODES = ['accounts', 'directory', 'resolve_target', 'capabilities', 'tools', 'agent_tools', 'actions'] as const;
|
|
61
81
|
const READ_ONLY_MCP_TOOL_MODES = ['servers', 'tools', 'schema', 'resources', 'security', 'auth'] as const;
|
|
62
82
|
const READ_ONLY_FETCH_METHODS = ['GET', 'HEAD', 'OPTIONS'] as const;
|
|
83
|
+
const READ_ONLY_STATE_TOOL_MODES = ['get', 'list', 'budget', 'context', 'memory', 'telemetry', 'hooks', 'mode', 'analytics'] as const;
|
|
84
|
+
const READ_ONLY_STATE_MEMORY_ACTIONS = ['list', 'get'] as const;
|
|
85
|
+
const READ_ONLY_STATE_HOOK_ACTIONS = ['list'] as const;
|
|
86
|
+
const READ_ONLY_STATE_MODE_ACTIONS = ['get', 'list'] as const;
|
|
87
|
+
const READ_ONLY_STATE_ANALYTICS_ACTIONS = ['summary', 'query', 'dashboard'] as const;
|
|
63
88
|
const READ_ONLY_REMOTE_TOOL_MODE_SET = new Set<string>(READ_ONLY_REMOTE_TOOL_MODES);
|
|
64
89
|
const READ_ONLY_CHANNEL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CHANNEL_TOOL_MODES);
|
|
65
90
|
const READ_ONLY_MCP_TOOL_MODE_SET = new Set<string>(READ_ONLY_MCP_TOOL_MODES);
|
|
66
91
|
const READ_ONLY_FETCH_METHOD_SET = new Set<string>(READ_ONLY_FETCH_METHODS);
|
|
92
|
+
const READ_ONLY_STATE_TOOL_MODE_SET = new Set<string>(READ_ONLY_STATE_TOOL_MODES);
|
|
93
|
+
const READ_ONLY_STATE_MEMORY_ACTION_SET = new Set<string>(READ_ONLY_STATE_MEMORY_ACTIONS);
|
|
94
|
+
const READ_ONLY_STATE_HOOK_ACTION_SET = new Set<string>(READ_ONLY_STATE_HOOK_ACTIONS);
|
|
95
|
+
const READ_ONLY_STATE_MODE_ACTION_SET = new Set<string>(READ_ONLY_STATE_MODE_ACTIONS);
|
|
96
|
+
const READ_ONLY_STATE_ANALYTICS_ACTION_SET = new Set<string>(READ_ONLY_STATE_ANALYTICS_ACTIONS);
|
|
67
97
|
|
|
68
98
|
const LOCAL_AGENT_DENIAL = [
|
|
69
99
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
@@ -107,6 +137,12 @@ const FETCH_NETWORK_MUTATION_DENIAL = [
|
|
|
107
137
|
'Network writes or credentialed external calls require an explicit Agent approval flow before they can run.',
|
|
108
138
|
].join(' ');
|
|
109
139
|
|
|
140
|
+
const STATE_MUTATION_DENIAL = [
|
|
141
|
+
'GoodVibes Agent only inspects copied runtime state from the main conversation.',
|
|
142
|
+
'Arbitrary state set/clear, copied memory writes, hook mutation, output-mode mutation, and analytics writes are disabled here.',
|
|
143
|
+
'Use Agent-owned memory, skills, personas, routines, and explicit CLI/slash commands for intentional local state changes.',
|
|
144
|
+
].join(' ');
|
|
145
|
+
|
|
110
146
|
export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
|
|
111
147
|
const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
|
|
112
148
|
if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
|
|
@@ -138,6 +174,8 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
138
174
|
});
|
|
139
175
|
} else if (tool.definition.name === 'fetch') {
|
|
140
176
|
wrapFetchToolForAgentPolicy(tool);
|
|
177
|
+
} else if (tool.definition.name === 'state') {
|
|
178
|
+
wrapStateToolForAgentPolicy(tool);
|
|
141
179
|
} else if (BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET.has(tool.definition.name)) {
|
|
142
180
|
wrapBlockedMainConversationToolForAgentPolicy(tool);
|
|
143
181
|
}
|
|
@@ -193,6 +231,16 @@ export function wrapFetchToolForAgentPolicy(tool: Tool): void {
|
|
|
193
231
|
};
|
|
194
232
|
}
|
|
195
233
|
|
|
234
|
+
export function wrapStateToolForAgentPolicy(tool: Tool): void {
|
|
235
|
+
narrowStateToolDefinitionForAgentPolicy(tool);
|
|
236
|
+
const originalExecute = tool.execute.bind(tool);
|
|
237
|
+
tool.execute = async (args) => {
|
|
238
|
+
const denial = validateStateToolInvocationForAgentPolicy(args as StateToolArgs);
|
|
239
|
+
if (denial) return { success: false, error: denial };
|
|
240
|
+
return originalExecute(args);
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
196
244
|
export function validateExecToolInvocationForAgentPolicy(args: ExecToolArgs): string | null {
|
|
197
245
|
if (args.parallel === true) return BACKGROUND_EXEC_DENIAL;
|
|
198
246
|
if (Array.isArray(args.file_ops) && args.file_ops.length > 0) return BACKGROUND_EXEC_DENIAL;
|
|
@@ -240,6 +288,43 @@ export function normalizeFetchToolInvocationForAgentPolicy(args: FetchToolArgs):
|
|
|
240
288
|
return { ...args, parallel: false };
|
|
241
289
|
}
|
|
242
290
|
|
|
291
|
+
export function validateStateToolInvocationForAgentPolicy(args: StateToolArgs): string | null {
|
|
292
|
+
if (isPresent(args.values) || isPresent(args.clearKeys)) return STATE_MUTATION_DENIAL;
|
|
293
|
+
if (typeof args.mode === 'string' && !READ_ONLY_STATE_TOOL_MODE_SET.has(args.mode)) return STATE_MUTATION_DENIAL;
|
|
294
|
+
|
|
295
|
+
if (args.mode === 'memory') {
|
|
296
|
+
const action = typeof args.memoryAction === 'string' ? args.memoryAction : 'list';
|
|
297
|
+
if (!READ_ONLY_STATE_MEMORY_ACTION_SET.has(action) || isPresent(args.memoryValue)) return STATE_MUTATION_DENIAL;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (args.mode === 'hooks') {
|
|
301
|
+
const action = typeof args.hookAction === 'string' ? args.hookAction : 'list';
|
|
302
|
+
if (!READ_ONLY_STATE_HOOK_ACTION_SET.has(action) || isPresent(args.hookDefinition)) return STATE_MUTATION_DENIAL;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (args.mode === 'mode') {
|
|
306
|
+
const action = typeof args.modeAction === 'string' ? args.modeAction : 'get';
|
|
307
|
+
if (!READ_ONLY_STATE_MODE_ACTION_SET.has(action) || isPresent(args.modeName)) return STATE_MUTATION_DENIAL;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (args.mode === 'analytics') {
|
|
311
|
+
const action = typeof args.analyticsAction === 'string' ? args.analyticsAction : 'summary';
|
|
312
|
+
if (!READ_ONLY_STATE_ANALYTICS_ACTION_SET.has(action)) return STATE_MUTATION_DENIAL;
|
|
313
|
+
if (
|
|
314
|
+
isPresent(args.analyticsTool)
|
|
315
|
+
|| isPresent(args.analyticsArgs)
|
|
316
|
+
|| isPresent(args.analyticsResult)
|
|
317
|
+
|| isPresent(args.analyticsDuration)
|
|
318
|
+
|| isPresent(args.analyticsTokens)
|
|
319
|
+
|| isPresent(args.analyticsFormat)
|
|
320
|
+
) {
|
|
321
|
+
return STATE_MUTATION_DENIAL;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
243
328
|
type ModeRestrictedToolPolicy = {
|
|
244
329
|
readonly allowedModes: readonly string[];
|
|
245
330
|
readonly modeSet: ReadonlySet<string>;
|
|
@@ -294,10 +379,16 @@ export const AGENT_READ_ONLY_REMOTE_TOOL_MODES = READ_ONLY_REMOTE_TOOL_MODES;
|
|
|
294
379
|
export const AGENT_READ_ONLY_CHANNEL_TOOL_MODES = READ_ONLY_CHANNEL_TOOL_MODES;
|
|
295
380
|
export const AGENT_READ_ONLY_MCP_TOOL_MODES = READ_ONLY_MCP_TOOL_MODES;
|
|
296
381
|
export const AGENT_READ_ONLY_FETCH_METHODS = READ_ONLY_FETCH_METHODS;
|
|
382
|
+
export const AGENT_READ_ONLY_STATE_TOOL_MODES = READ_ONLY_STATE_TOOL_MODES;
|
|
383
|
+
export const AGENT_READ_ONLY_STATE_MEMORY_ACTIONS = READ_ONLY_STATE_MEMORY_ACTIONS;
|
|
384
|
+
export const AGENT_READ_ONLY_STATE_HOOK_ACTIONS = READ_ONLY_STATE_HOOK_ACTIONS;
|
|
385
|
+
export const AGENT_READ_ONLY_STATE_MODE_ACTIONS = READ_ONLY_STATE_MODE_ACTIONS;
|
|
386
|
+
export const AGENT_READ_ONLY_STATE_ANALYTICS_ACTIONS = READ_ONLY_STATE_ANALYTICS_ACTIONS;
|
|
297
387
|
export const AGENT_REMOTE_MUTATION_DENIAL_MESSAGE = REMOTE_MUTATION_DENIAL;
|
|
298
388
|
export const AGENT_CHANNEL_ACTION_DENIAL_MESSAGE = CHANNEL_ACTION_DENIAL;
|
|
299
389
|
export const AGENT_MCP_SECURITY_MUTATION_DENIAL_MESSAGE = MCP_SECURITY_MUTATION_DENIAL;
|
|
300
390
|
export const AGENT_FETCH_NETWORK_MUTATION_DENIAL_MESSAGE = FETCH_NETWORK_MUTATION_DENIAL;
|
|
391
|
+
export const AGENT_STATE_MUTATION_DENIAL_MESSAGE = STATE_MUTATION_DENIAL;
|
|
301
392
|
|
|
302
393
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
303
394
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -397,6 +488,40 @@ function narrowFetchToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
|
397
488
|
delete urlProperties.auth;
|
|
398
489
|
}
|
|
399
490
|
|
|
491
|
+
function narrowStateToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
492
|
+
tool.definition.description = [
|
|
493
|
+
'Inspect copied runtime state for GoodVibes Agent.',
|
|
494
|
+
'State mutation, copied memory writes, hook changes, output-mode changes, and analytics writes are disabled in the main conversation.',
|
|
495
|
+
'Use Agent-owned commands for intentional memory, skill, persona, and routine changes.',
|
|
496
|
+
].join(' ');
|
|
497
|
+
|
|
498
|
+
const properties = tool.definition.parameters.properties;
|
|
499
|
+
if (!isRecord(properties)) return;
|
|
500
|
+
const modeProperty = properties.mode;
|
|
501
|
+
if (isRecord(modeProperty)) {
|
|
502
|
+
modeProperty.enum = [...READ_ONLY_STATE_TOOL_MODES];
|
|
503
|
+
modeProperty.description = 'Read-only copied runtime state mode. set and clear are disabled in GoodVibes Agent.';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
delete properties.values;
|
|
507
|
+
delete properties.clearKeys;
|
|
508
|
+
delete properties.memoryValue;
|
|
509
|
+
delete properties.hookDefinition;
|
|
510
|
+
delete properties.hookName;
|
|
511
|
+
delete properties.modeName;
|
|
512
|
+
delete properties.analyticsTool;
|
|
513
|
+
delete properties.analyticsArgs;
|
|
514
|
+
delete properties.analyticsResult;
|
|
515
|
+
delete properties.analyticsDuration;
|
|
516
|
+
delete properties.analyticsTokens;
|
|
517
|
+
delete properties.analyticsFormat;
|
|
518
|
+
|
|
519
|
+
narrowStringEnumProperty(properties, 'memoryAction', READ_ONLY_STATE_MEMORY_ACTIONS, 'Read-only copied memory actions allowed by GoodVibes Agent.');
|
|
520
|
+
narrowStringEnumProperty(properties, 'hookAction', READ_ONLY_STATE_HOOK_ACTIONS, 'Read-only hook action allowed by GoodVibes Agent.');
|
|
521
|
+
narrowStringEnumProperty(properties, 'modeAction', READ_ONLY_STATE_MODE_ACTIONS, 'Read-only mode actions allowed by GoodVibes Agent.');
|
|
522
|
+
narrowStringEnumProperty(properties, 'analyticsAction', READ_ONLY_STATE_ANALYTICS_ACTIONS, 'Read-only analytics actions allowed by GoodVibes Agent.');
|
|
523
|
+
}
|
|
524
|
+
|
|
400
525
|
function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readonly string[], description: string): void {
|
|
401
526
|
tool.definition.description = description;
|
|
402
527
|
|
|
@@ -417,6 +542,18 @@ function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readon
|
|
|
417
542
|
}
|
|
418
543
|
}
|
|
419
544
|
|
|
545
|
+
function narrowStringEnumProperty(
|
|
546
|
+
properties: Record<string, unknown>,
|
|
547
|
+
key: string,
|
|
548
|
+
values: readonly string[],
|
|
549
|
+
description: string,
|
|
550
|
+
): void {
|
|
551
|
+
const property = properties[key];
|
|
552
|
+
if (!isRecord(property)) return;
|
|
553
|
+
property.enum = [...values];
|
|
554
|
+
property.description = description;
|
|
555
|
+
}
|
|
556
|
+
|
|
420
557
|
// Compatibility exports for copied TUI tests/imports during the near-fork phase.
|
|
421
558
|
export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
|
|
422
559
|
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.18';
|
|
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 {
|