@pellux/goodvibes-agent 0.1.24 → 0.1.25
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/agent-find-policy.ts +124 -0
- package/src/tools/wrfc-agent-guard.ts +11 -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.25",
|
|
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,124 @@
|
|
|
1
|
+
import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
|
|
2
|
+
|
|
3
|
+
type FindQueryArgs = {
|
|
4
|
+
readonly follow_symlinks?: unknown;
|
|
5
|
+
readonly include_hidden?: unknown;
|
|
6
|
+
readonly respect_gitignore?: unknown;
|
|
7
|
+
readonly [key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type FindOutputArgs = {
|
|
11
|
+
readonly format?: unknown;
|
|
12
|
+
readonly [key: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type FindToolArgs = {
|
|
16
|
+
readonly queries?: unknown;
|
|
17
|
+
readonly output?: unknown;
|
|
18
|
+
readonly parallel?: unknown;
|
|
19
|
+
readonly [key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const READ_ONLY_FIND_OUTPUT_FORMATS = [
|
|
23
|
+
'count_only',
|
|
24
|
+
'files_only',
|
|
25
|
+
'locations',
|
|
26
|
+
'matches',
|
|
27
|
+
'context',
|
|
28
|
+
'with_stats',
|
|
29
|
+
'signatures',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
const READ_ONLY_FIND_OUTPUT_FORMAT_SET = new Set<string>(READ_ONLY_FIND_OUTPUT_FORMATS);
|
|
33
|
+
|
|
34
|
+
const FIND_POLICY_DENIAL = [
|
|
35
|
+
'GoodVibes Agent only exposes serial, project-root, gitignore-respecting search from the main conversation.',
|
|
36
|
+
'Hidden-file scans, symlink traversal, gitignore bypass, broad file previews, full symbol dumps, and parallel search are disabled here.',
|
|
37
|
+
'Use explicit Agent CLI/slash commands or GoodVibes TUI delegation for deeper local inspection.',
|
|
38
|
+
].join(' ');
|
|
39
|
+
|
|
40
|
+
export const AGENT_READ_ONLY_FIND_OUTPUT_FORMATS = READ_ONLY_FIND_OUTPUT_FORMATS;
|
|
41
|
+
export const AGENT_FIND_POLICY_DENIAL_MESSAGE = FIND_POLICY_DENIAL;
|
|
42
|
+
|
|
43
|
+
export function wrapFindToolForAgentPolicy(tool: Tool): void {
|
|
44
|
+
narrowFindToolDefinitionForAgentPolicy(tool);
|
|
45
|
+
const originalExecute = tool.execute.bind(tool);
|
|
46
|
+
tool.execute = async (args) => {
|
|
47
|
+
const findArgs = args as FindToolArgs;
|
|
48
|
+
const denial = validateFindToolInvocationForAgentPolicy(findArgs);
|
|
49
|
+
if (denial) return { success: false, error: denial };
|
|
50
|
+
return originalExecute(normalizeFindToolInvocationForAgentPolicy(findArgs) as Parameters<Tool['execute']>[0]);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function validateFindToolInvocationForAgentPolicy(args: FindToolArgs): string | null {
|
|
55
|
+
if (args.parallel === true) return FIND_POLICY_DENIAL;
|
|
56
|
+
|
|
57
|
+
if (Array.isArray(args.queries)) {
|
|
58
|
+
for (const query of args.queries) {
|
|
59
|
+
if (!isRecord(query)) continue;
|
|
60
|
+
const queryArgs = query as FindQueryArgs;
|
|
61
|
+
if (queryArgs.follow_symlinks === true) return FIND_POLICY_DENIAL;
|
|
62
|
+
if (queryArgs.include_hidden === true) return FIND_POLICY_DENIAL;
|
|
63
|
+
if (queryArgs.respect_gitignore === false) return FIND_POLICY_DENIAL;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isRecord(args.output)) {
|
|
68
|
+
const output = args.output as FindOutputArgs;
|
|
69
|
+
if (typeof output.format === 'string' && !READ_ONLY_FIND_OUTPUT_FORMAT_SET.has(output.format)) {
|
|
70
|
+
return FIND_POLICY_DENIAL;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function normalizeFindToolInvocationForAgentPolicy(args: FindToolArgs): FindToolArgs {
|
|
78
|
+
return {
|
|
79
|
+
...args,
|
|
80
|
+
parallel: false,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function narrowFindToolDefinitionForAgentPolicy(tool: Tool): void {
|
|
85
|
+
tool.definition.description = [
|
|
86
|
+
'Search the project for GoodVibes Agent using serial, gitignore-respecting read-only queries.',
|
|
87
|
+
'Hidden-file scans, symlink traversal, gitignore bypass, broad previews, full symbol dumps, and parallel search are disabled in the main conversation.',
|
|
88
|
+
].join(' ');
|
|
89
|
+
tool.definition.sideEffects = ['read_fs'];
|
|
90
|
+
tool.definition.concurrency = 'serial';
|
|
91
|
+
|
|
92
|
+
const properties = tool.definition.parameters.properties;
|
|
93
|
+
if (!isRecord(properties)) return;
|
|
94
|
+
delete properties.parallel;
|
|
95
|
+
|
|
96
|
+
const queries = properties.queries;
|
|
97
|
+
if (isRecord(queries)) {
|
|
98
|
+
const itemSchema = queries.items;
|
|
99
|
+
if (isRecord(itemSchema)) {
|
|
100
|
+
const queryProperties = itemSchema.properties;
|
|
101
|
+
if (isRecord(queryProperties)) {
|
|
102
|
+
delete queryProperties.follow_symlinks;
|
|
103
|
+
delete queryProperties.include_hidden;
|
|
104
|
+
delete queryProperties.respect_gitignore;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const output = properties.output;
|
|
110
|
+
if (!isRecord(output)) return;
|
|
111
|
+
const outputProperties = output.properties;
|
|
112
|
+
if (!isRecord(outputProperties)) return;
|
|
113
|
+
|
|
114
|
+
const format = outputProperties.format;
|
|
115
|
+
if (isRecord(format)) {
|
|
116
|
+
format.enum = [...READ_ONLY_FIND_OUTPUT_FORMATS];
|
|
117
|
+
format.description = 'Bounded output format allowed by GoodVibes Agent main-conversation search policy.';
|
|
118
|
+
}
|
|
119
|
+
delete outputProperties.preview_lines;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
123
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
124
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
wrapAnalyzeToolForAgentPolicy,
|
|
5
5
|
wrapRegistryToolForAgentPolicy,
|
|
6
6
|
} from './agent-analysis-registry-policy.ts';
|
|
7
|
+
import { wrapFindToolForAgentPolicy } from './agent-find-policy.ts';
|
|
7
8
|
import { wrapWebSearchToolForAgentPolicy } from './agent-web-search-policy.ts';
|
|
8
9
|
|
|
9
10
|
type AgentToolArgs = {
|
|
@@ -231,6 +232,8 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
|
|
|
231
232
|
wrapAnalyzeToolForAgentPolicy(tool);
|
|
232
233
|
} else if (tool.definition.name === 'registry') {
|
|
233
234
|
wrapRegistryToolForAgentPolicy(tool);
|
|
235
|
+
} else if (tool.definition.name === 'find') {
|
|
236
|
+
wrapFindToolForAgentPolicy(tool);
|
|
234
237
|
} else if (tool.definition.name === 'web_search') {
|
|
235
238
|
wrapWebSearchToolForAgentPolicy(tool);
|
|
236
239
|
} else if (tool.definition.name === 'control') {
|
|
@@ -556,6 +559,14 @@ export {
|
|
|
556
559
|
wrapRegistryToolForAgentPolicy,
|
|
557
560
|
} from './agent-analysis-registry-policy.ts';
|
|
558
561
|
|
|
562
|
+
export {
|
|
563
|
+
AGENT_FIND_POLICY_DENIAL_MESSAGE,
|
|
564
|
+
AGENT_READ_ONLY_FIND_OUTPUT_FORMATS,
|
|
565
|
+
normalizeFindToolInvocationForAgentPolicy,
|
|
566
|
+
validateFindToolInvocationForAgentPolicy,
|
|
567
|
+
wrapFindToolForAgentPolicy,
|
|
568
|
+
} from './agent-find-policy.ts';
|
|
569
|
+
|
|
559
570
|
export {
|
|
560
571
|
AGENT_MAX_WEB_SEARCH_EVIDENCE_TOP_N,
|
|
561
572
|
AGENT_MAX_WEB_SEARCH_RESULTS,
|
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.25';
|
|
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 {
|