@pellux/goodvibes-agent 0.1.21 → 0.1.23
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
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GoodVibes Agent will be recorded here.
|
|
4
4
|
|
|
5
|
+
## 0.1.23 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- 18d7381 Harden analyze and registry tool policy
|
|
8
|
+
|
|
9
|
+
## 0.1.22 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 07e4445 Mark control tool read-only in agent runtime
|
|
12
|
+
|
|
5
13
|
## 0.1.21 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- d83f325 Keep inspect scaffold dry-run-only 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.23",
|
|
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",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
|
|
3
|
+
type AnalyzeToolArgs = {
|
|
4
|
+
readonly mode?: unknown;
|
|
5
|
+
readonly [key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type RegistryToolArgs = {
|
|
9
|
+
readonly mode?: unknown;
|
|
10
|
+
readonly path?: unknown;
|
|
11
|
+
readonly [key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const READ_ONLY_ANALYZE_TOOL_MODES = [
|
|
15
|
+
'impact',
|
|
16
|
+
'dependencies',
|
|
17
|
+
'dead_code',
|
|
18
|
+
'security',
|
|
19
|
+
'coverage',
|
|
20
|
+
'bundle',
|
|
21
|
+
'preview',
|
|
22
|
+
'diff',
|
|
23
|
+
'surface',
|
|
24
|
+
'breaking',
|
|
25
|
+
'permissions',
|
|
26
|
+
'env_audit',
|
|
27
|
+
'test_find',
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
const READ_ONLY_REGISTRY_TOOL_MODES = ['search', 'recommend', 'dependencies', 'preview'] as const;
|
|
31
|
+
|
|
32
|
+
const READ_ONLY_ANALYZE_TOOL_MODE_SET = new Set<string>(READ_ONLY_ANALYZE_TOOL_MODES);
|
|
33
|
+
const READ_ONLY_REGISTRY_TOOL_MODE_SET = new Set<string>(READ_ONLY_REGISTRY_TOOL_MODES);
|
|
34
|
+
|
|
35
|
+
const ANALYZE_NETWORK_DENIAL = [
|
|
36
|
+
'GoodVibes Agent only exposes local, static analyze modes from the main conversation.',
|
|
37
|
+
'npm registry upgrade checks and hidden secondary LLM diff analysis are disabled here.',
|
|
38
|
+
'Use explicit Agent CLI/slash commands or GoodVibes TUI delegation for package-upgrade and code-review workflows.',
|
|
39
|
+
].join(' ');
|
|
40
|
+
|
|
41
|
+
const REGISTRY_CONTENT_DENIAL = [
|
|
42
|
+
'GoodVibes Agent only exposes registry discovery, recommendation, dependency, and bounded preview from the main conversation.',
|
|
43
|
+
'Full registry content materialization and arbitrary .goodvibes file reads are disabled here.',
|
|
44
|
+
'Use explicit Agent CLI/slash commands for intentional local registry changes or deeper inspection.',
|
|
45
|
+
].join(' ');
|
|
46
|
+
|
|
47
|
+
export const AGENT_READ_ONLY_ANALYZE_TOOL_MODES = READ_ONLY_ANALYZE_TOOL_MODES;
|
|
48
|
+
export const AGENT_READ_ONLY_REGISTRY_TOOL_MODES = READ_ONLY_REGISTRY_TOOL_MODES;
|
|
49
|
+
export const AGENT_ANALYZE_NETWORK_DENIAL_MESSAGE = ANALYZE_NETWORK_DENIAL;
|
|
50
|
+
export const AGENT_REGISTRY_CONTENT_DENIAL_MESSAGE = REGISTRY_CONTENT_DENIAL;
|
|
51
|
+
|
|
52
|
+
export function wrapAnalyzeToolForAgentPolicy(tool: Tool): void {
|
|
53
|
+
narrowAnalyzeToolDefinitionForAgentPolicy(tool);
|
|
54
|
+
const originalExecute = tool.execute.bind(tool);
|
|
55
|
+
tool.execute = async (args) => {
|
|
56
|
+
const denial = validateAnalyzeToolInvocationForAgentPolicy(args as AnalyzeToolArgs);
|
|
57
|
+
if (denial) return { success: false, error: denial };
|
|
58
|
+
return originalExecute(args);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function wrapRegistryToolForAgentPolicy(tool: Tool): void {
|
|
63
|
+
narrowRegistryToolDefinitionForAgentPolicy(tool);
|
|
64
|
+
const originalExecute = tool.execute.bind(tool);
|
|
65
|
+
tool.execute = async (args) => {
|
|
66
|
+
const denial = validateRegistryToolInvocationForAgentPolicy(args as RegistryToolArgs);
|
|
67
|
+
if (denial) return { success: false, error: denial };
|
|
68
|
+
return originalExecute(args);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function validateAnalyzeToolInvocationForAgentPolicy(args: AnalyzeToolArgs): string | null {
|
|
73
|
+
if (typeof args.mode === 'string' && !READ_ONLY_ANALYZE_TOOL_MODE_SET.has(args.mode)) return ANALYZE_NETWORK_DENIAL;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function validateRegistryToolInvocationForAgentPolicy(args: RegistryToolArgs): string | null {
|
|
78
|
+
if (typeof args.mode === 'string' && !READ_ONLY_REGISTRY_TOOL_MODE_SET.has(args.mode)) return REGISTRY_CONTENT_DENIAL;
|
|
79
|
+
if (args.mode === 'preview' && typeof args.path === 'string' && !isAllowedAgentRegistryPreviewPath(args.path)) {
|
|
80
|
+
return REGISTRY_CONTENT_DENIAL;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function narrowAnalyzeToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
86
|
+
tool.definition.description = [
|
|
87
|
+
'Run local, static project analysis for GoodVibes Agent.',
|
|
88
|
+
'npm registry upgrade checks and hidden secondary LLM diff analysis are disabled in the main conversation.',
|
|
89
|
+
'Delegate package-upgrade and review workflows to GoodVibes TUI when they become build/fix/review work.',
|
|
90
|
+
].join(' ');
|
|
91
|
+
tool.definition.sideEffects = ['read_fs'];
|
|
92
|
+
|
|
93
|
+
const properties = tool.definition.parameters.properties;
|
|
94
|
+
if (!isRecord(properties)) return;
|
|
95
|
+
const modeProperty = properties.mode;
|
|
96
|
+
if (isRecord(modeProperty)) {
|
|
97
|
+
modeProperty.enum = [...READ_ONLY_ANALYZE_TOOL_MODES];
|
|
98
|
+
modeProperty.description = 'Local static analysis mode allowed by GoodVibes Agent main-conversation policy.';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
delete properties.packages;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function narrowRegistryToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
105
|
+
tool.definition.description = [
|
|
106
|
+
'Discover and preview GoodVibes Agent skills, agents, and tools.',
|
|
107
|
+
'Full content materialization and arbitrary .goodvibes file reads are disabled in the main conversation.',
|
|
108
|
+
].join(' ');
|
|
109
|
+
tool.definition.sideEffects = ['read_fs'];
|
|
110
|
+
|
|
111
|
+
const properties = tool.definition.parameters.properties;
|
|
112
|
+
if (!isRecord(properties)) return;
|
|
113
|
+
const modeProperty = properties.mode;
|
|
114
|
+
if (isRecord(modeProperty)) {
|
|
115
|
+
modeProperty.enum = [...READ_ONLY_REGISTRY_TOOL_MODES];
|
|
116
|
+
modeProperty.description = 'Bounded registry discovery modes allowed by GoodVibes Agent main-conversation policy.';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isAllowedAgentRegistryPreviewPath(path: string): boolean {
|
|
121
|
+
const normalized = path.replace(/\\/g, '/');
|
|
122
|
+
return /(?:^|\/)\.goodvibes\/(?:skills|agents)\/.+(?:\.md|\/(?:SKILL|AGENT)\.md)$/i.test(normalized);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
126
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
127
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
2
|
import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
|
|
3
|
+
import {
|
|
4
|
+
wrapAnalyzeToolForAgentPolicy,
|
|
5
|
+
wrapRegistryToolForAgentPolicy,
|
|
6
|
+
} from './agent-analysis-registry-policy.ts';
|
|
3
7
|
|
|
4
8
|
type AgentToolArgs = {
|
|
5
9
|
readonly mode?: unknown;
|
|
@@ -96,6 +100,7 @@ const READ_ONLY_TEAM_TOOL_MODES = ['list', 'show'] as const;
|
|
|
96
100
|
const READ_ONLY_WORKLIST_TOOL_MODES = ['list', 'show'] as const;
|
|
97
101
|
const READ_ONLY_PACKET_TOOL_MODES = ['list', 'show'] as const;
|
|
98
102
|
const READ_ONLY_QUERY_TOOL_MODES = ['list', 'show'] as const;
|
|
103
|
+
const READ_ONLY_CONTROL_TOOL_MODES = ['commands', 'panels', 'subscriptions', 'sandbox-presets'] as const;
|
|
99
104
|
const READ_ONLY_REMOTE_TOOL_MODE_SET = new Set<string>(READ_ONLY_REMOTE_TOOL_MODES);
|
|
100
105
|
const READ_ONLY_CHANNEL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CHANNEL_TOOL_MODES);
|
|
101
106
|
const READ_ONLY_MCP_TOOL_MODE_SET = new Set<string>(READ_ONLY_MCP_TOOL_MODES);
|
|
@@ -110,6 +115,7 @@ const READ_ONLY_TEAM_TOOL_MODE_SET = new Set<string>(READ_ONLY_TEAM_TOOL_MODES);
|
|
|
110
115
|
const READ_ONLY_WORKLIST_TOOL_MODE_SET = new Set<string>(READ_ONLY_WORKLIST_TOOL_MODES);
|
|
111
116
|
const READ_ONLY_PACKET_TOOL_MODE_SET = new Set<string>(READ_ONLY_PACKET_TOOL_MODES);
|
|
112
117
|
const READ_ONLY_QUERY_TOOL_MODE_SET = new Set<string>(READ_ONLY_QUERY_TOOL_MODES);
|
|
118
|
+
const READ_ONLY_CONTROL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CONTROL_TOOL_MODES);
|
|
113
119
|
|
|
114
120
|
const LOCAL_AGENT_DENIAL = [
|
|
115
121
|
'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
|
|
@@ -177,6 +183,12 @@ const DURABLE_WORKFLOW_MUTATION_DENIAL = [
|
|
|
177
183
|
'Use explicit Agent CLI/slash commands or GoodVibes TUI delegation for intentional workflow changes.',
|
|
178
184
|
].join(' ');
|
|
179
185
|
|
|
186
|
+
const CONTROL_MUTATION_DENIAL = [
|
|
187
|
+
'GoodVibes Agent only inspects copied product-control surfaces from the main conversation.',
|
|
188
|
+
'Product-control mutation, daemon lifecycle, and service posture changes are disabled here.',
|
|
189
|
+
'Use explicit Agent CLI/slash commands for Agent-owned changes, and keep daemon lifecycle external.',
|
|
190
|
+
].join(' ');
|
|
191
|
+
|
|
180
192
|
export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
|
|
181
193
|
const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
|
|
182
194
|
if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
|
|
@@ -214,6 +226,20 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
214
226
|
wrapBlockedSettingsToolForAgentPolicy(tool);
|
|
215
227
|
} else if (tool.definition.name === 'inspect') {
|
|
216
228
|
wrapInspectToolForAgentPolicy(tool);
|
|
229
|
+
} else if (tool.definition.name === 'analyze') {
|
|
230
|
+
wrapAnalyzeToolForAgentPolicy(tool);
|
|
231
|
+
} else if (tool.definition.name === 'registry') {
|
|
232
|
+
wrapRegistryToolForAgentPolicy(tool);
|
|
233
|
+
} else if (tool.definition.name === 'control') {
|
|
234
|
+
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
235
|
+
allowedModes: READ_ONLY_CONTROL_TOOL_MODES,
|
|
236
|
+
modeSet: READ_ONLY_CONTROL_TOOL_MODE_SET,
|
|
237
|
+
description: [
|
|
238
|
+
'Read-only product-control inspection for GoodVibes Agent.',
|
|
239
|
+
'Command, panel, subscription, and sandbox preset catalogs can be inspected, but product-control mutation and daemon lifecycle are external.',
|
|
240
|
+
].join(' '),
|
|
241
|
+
denial: CONTROL_MUTATION_DENIAL,
|
|
242
|
+
});
|
|
217
243
|
} else if (tool.definition.name === 'task') {
|
|
218
244
|
wrapModeRestrictedToolForAgentPolicy(tool, {
|
|
219
245
|
allowedModes: READ_ONLY_TASK_TOOL_MODES,
|
|
@@ -505,6 +531,7 @@ export const AGENT_READ_ONLY_TEAM_TOOL_MODES = READ_ONLY_TEAM_TOOL_MODES;
|
|
|
505
531
|
export const AGENT_READ_ONLY_WORKLIST_TOOL_MODES = READ_ONLY_WORKLIST_TOOL_MODES;
|
|
506
532
|
export const AGENT_READ_ONLY_PACKET_TOOL_MODES = READ_ONLY_PACKET_TOOL_MODES;
|
|
507
533
|
export const AGENT_READ_ONLY_QUERY_TOOL_MODES = READ_ONLY_QUERY_TOOL_MODES;
|
|
534
|
+
export const AGENT_READ_ONLY_CONTROL_TOOL_MODES = READ_ONLY_CONTROL_TOOL_MODES;
|
|
508
535
|
export const AGENT_REMOTE_MUTATION_DENIAL_MESSAGE = REMOTE_MUTATION_DENIAL;
|
|
509
536
|
export const AGENT_CHANNEL_ACTION_DENIAL_MESSAGE = CHANNEL_ACTION_DENIAL;
|
|
510
537
|
export const AGENT_MCP_SECURITY_MUTATION_DENIAL_MESSAGE = MCP_SECURITY_MUTATION_DENIAL;
|
|
@@ -513,6 +540,18 @@ export const AGENT_STATE_MUTATION_DENIAL_MESSAGE = STATE_MUTATION_DENIAL;
|
|
|
513
540
|
export const AGENT_SETTINGS_MUTATION_DENIAL_MESSAGE = SETTINGS_MUTATION_DENIAL;
|
|
514
541
|
export const AGENT_INSPECT_WRITE_DENIAL_MESSAGE = INSPECT_WRITE_DENIAL;
|
|
515
542
|
export const AGENT_DURABLE_WORKFLOW_MUTATION_DENIAL_MESSAGE = DURABLE_WORKFLOW_MUTATION_DENIAL;
|
|
543
|
+
export const AGENT_CONTROL_MUTATION_DENIAL_MESSAGE = CONTROL_MUTATION_DENIAL;
|
|
544
|
+
|
|
545
|
+
export {
|
|
546
|
+
AGENT_ANALYZE_NETWORK_DENIAL_MESSAGE,
|
|
547
|
+
AGENT_READ_ONLY_ANALYZE_TOOL_MODES,
|
|
548
|
+
AGENT_READ_ONLY_REGISTRY_TOOL_MODES,
|
|
549
|
+
AGENT_REGISTRY_CONTENT_DENIAL_MESSAGE,
|
|
550
|
+
validateAnalyzeToolInvocationForAgentPolicy,
|
|
551
|
+
validateRegistryToolInvocationForAgentPolicy,
|
|
552
|
+
wrapAnalyzeToolForAgentPolicy,
|
|
553
|
+
wrapRegistryToolForAgentPolicy,
|
|
554
|
+
} from './agent-analysis-registry-policy.ts';
|
|
516
555
|
|
|
517
556
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
518
557
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -659,6 +698,7 @@ function narrowInspectToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
|
659
698
|
|
|
660
699
|
function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readonly string[], description: string): void {
|
|
661
700
|
tool.definition.description = description;
|
|
701
|
+
tool.definition.sideEffects = [];
|
|
662
702
|
|
|
663
703
|
const properties = tool.definition.parameters.properties;
|
|
664
704
|
if (!isRecord(properties)) return;
|
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.23';
|
|
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 {
|