@pellux/goodvibes-agent 0.1.17 → 0.1.19

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.19 - 2026-05-31
6
+
7
+ - a4255d5 Restrict durable workflow tool mutations in agent runtime
8
+
9
+ ## 0.1.18 - 2026-05-31
10
+
11
+ - 1fd7729 Restrict state tool mutations in agent runtime
12
+
5
13
  ## 0.1.17 - 2026-05-31
6
14
 
7
15
  - f148186 Restrict fetch side effects in agent runtime
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
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,30 @@ 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;
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;
63
93
  const READ_ONLY_REMOTE_TOOL_MODE_SET = new Set<string>(READ_ONLY_REMOTE_TOOL_MODES);
64
94
  const READ_ONLY_CHANNEL_TOOL_MODE_SET = new Set<string>(READ_ONLY_CHANNEL_TOOL_MODES);
65
95
  const READ_ONLY_MCP_TOOL_MODE_SET = new Set<string>(READ_ONLY_MCP_TOOL_MODES);
66
96
  const READ_ONLY_FETCH_METHOD_SET = new Set<string>(READ_ONLY_FETCH_METHODS);
97
+ const READ_ONLY_STATE_TOOL_MODE_SET = new Set<string>(READ_ONLY_STATE_TOOL_MODES);
98
+ const READ_ONLY_STATE_MEMORY_ACTION_SET = new Set<string>(READ_ONLY_STATE_MEMORY_ACTIONS);
99
+ const READ_ONLY_STATE_HOOK_ACTION_SET = new Set<string>(READ_ONLY_STATE_HOOK_ACTIONS);
100
+ const READ_ONLY_STATE_MODE_ACTION_SET = new Set<string>(READ_ONLY_STATE_MODE_ACTIONS);
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);
67
107
 
68
108
  const LOCAL_AGENT_DENIAL = [
69
109
  'GoodVibes Agent does not spawn local Engineer/Reviewer/Tester/Verifier roots or run local WRFC chains.',
@@ -107,6 +147,18 @@ const FETCH_NETWORK_MUTATION_DENIAL = [
107
147
  'Network writes or credentialed external calls require an explicit Agent approval flow before they can run.',
108
148
  ].join(' ');
109
149
 
150
+ const STATE_MUTATION_DENIAL = [
151
+ 'GoodVibes Agent only inspects copied runtime state from the main conversation.',
152
+ 'Arbitrary state set/clear, copied memory writes, hook mutation, output-mode mutation, and analytics writes are disabled here.',
153
+ 'Use Agent-owned memory, skills, personas, routines, and explicit CLI/slash commands for intentional local state changes.',
154
+ ].join(' ');
155
+
156
+ const DURABLE_WORKFLOW_MUTATION_DENIAL = [
157
+ 'GoodVibes Agent only inspects copied durable workflow tools from the main conversation.',
158
+ 'Task, team, worklist, packet, and query creation or lifecycle mutation is disabled here.',
159
+ 'Use explicit Agent CLI/slash commands or GoodVibes TUI delegation for intentional workflow changes.',
160
+ ].join(' ');
161
+
110
162
  export function installAgentToolPolicyGuard(registry: ToolRegistry, options: AgentToolPolicyGuardOptions = {}): void {
111
163
  const agentTool = registry.list().find((tool) => tool.definition.name === 'agent');
112
164
  if (!agentTool) throw new Error('Agent tool policy guard could not find the agent tool.');
@@ -138,6 +190,48 @@ export function installAgentToolPolicyGuard(registry: ToolRegistry, options: Age
138
190
  });
139
191
  } else if (tool.definition.name === 'fetch') {
140
192
  wrapFetchToolForAgentPolicy(tool);
193
+ } else if (tool.definition.name === 'state') {
194
+ wrapStateToolForAgentPolicy(tool);
195
+ } else if (tool.definition.name === 'task') {
196
+ wrapModeRestrictedToolForAgentPolicy(tool, {
197
+ allowedModes: READ_ONLY_TASK_TOOL_MODES,
198
+ modeSet: READ_ONLY_TASK_TOOL_MODE_SET,
199
+ description: 'Read-only task/workflow inspection for GoodVibes Agent. Task creation, status changes, dependencies, cancellation, and handoff mutation are disabled in the main conversation.',
200
+ denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
201
+ removedProperties: ['title', 'label', 'status', 'dependsOnSessionId', 'dependsOnTaskId', 'reason', 'toSessionId'],
202
+ });
203
+ } else if (tool.definition.name === 'team') {
204
+ wrapModeRestrictedToolForAgentPolicy(tool, {
205
+ allowedModes: READ_ONLY_TEAM_TOOL_MODES,
206
+ modeSet: READ_ONLY_TEAM_TOOL_MODE_SET,
207
+ description: 'Read-only team inspection for GoodVibes Agent. Team creation, membership changes, lane changes, and deletion are disabled in the main conversation.',
208
+ denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
209
+ removedProperties: ['name', 'summary', 'memberId', 'role', 'lanes'],
210
+ });
211
+ } else if (tool.definition.name === 'worklist') {
212
+ wrapModeRestrictedToolForAgentPolicy(tool, {
213
+ allowedModes: READ_ONLY_WORKLIST_TOOL_MODES,
214
+ modeSet: READ_ONLY_WORKLIST_TOOL_MODE_SET,
215
+ description: 'Read-only worklist inspection for GoodVibes Agent. Worklist creation and item lifecycle changes are disabled in the main conversation.',
216
+ denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
217
+ removedProperties: ['title', 'itemId', 'text', 'owner', 'priority'],
218
+ });
219
+ } else if (tool.definition.name === 'packet') {
220
+ wrapModeRestrictedToolForAgentPolicy(tool, {
221
+ allowedModes: READ_ONLY_PACKET_TOOL_MODES,
222
+ modeSet: READ_ONLY_PACKET_TOOL_MODE_SET,
223
+ description: 'Read-only operator packet inspection for GoodVibes Agent. Packet creation, revision, and publishing are disabled in the main conversation.',
224
+ denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
225
+ removedProperties: ['title', 'summary', 'goals', 'constraints', 'risks', 'audience'],
226
+ });
227
+ } else if (tool.definition.name === 'query') {
228
+ wrapModeRestrictedToolForAgentPolicy(tool, {
229
+ allowedModes: READ_ONLY_QUERY_TOOL_MODES,
230
+ modeSet: READ_ONLY_QUERY_TOOL_MODE_SET,
231
+ description: 'Read-only operator query inspection for GoodVibes Agent. Asking, answering, and closing copied workflow queries are disabled in the main conversation.',
232
+ denial: DURABLE_WORKFLOW_MUTATION_DENIAL,
233
+ removedProperties: ['prompt', 'askedBy', 'target', 'answer', 'resolution'],
234
+ });
141
235
  } else if (BLOCKED_MAIN_CONVERSATION_TOOL_NAME_SET.has(tool.definition.name)) {
142
236
  wrapBlockedMainConversationToolForAgentPolicy(tool);
143
237
  }
@@ -193,6 +287,16 @@ export function wrapFetchToolForAgentPolicy(tool: Tool): void {
193
287
  };
194
288
  }
195
289
 
290
+ export function wrapStateToolForAgentPolicy(tool: Tool): void {
291
+ narrowStateToolDefinitionForAgentPolicy(tool);
292
+ const originalExecute = tool.execute.bind(tool);
293
+ tool.execute = async (args) => {
294
+ const denial = validateStateToolInvocationForAgentPolicy(args as StateToolArgs);
295
+ if (denial) return { success: false, error: denial };
296
+ return originalExecute(args);
297
+ };
298
+ }
299
+
196
300
  export function validateExecToolInvocationForAgentPolicy(args: ExecToolArgs): string | null {
197
301
  if (args.parallel === true) return BACKGROUND_EXEC_DENIAL;
198
302
  if (Array.isArray(args.file_ops) && args.file_ops.length > 0) return BACKGROUND_EXEC_DENIAL;
@@ -240,15 +344,54 @@ export function normalizeFetchToolInvocationForAgentPolicy(args: FetchToolArgs):
240
344
  return { ...args, parallel: false };
241
345
  }
242
346
 
347
+ export function validateStateToolInvocationForAgentPolicy(args: StateToolArgs): string | null {
348
+ if (isPresent(args.values) || isPresent(args.clearKeys)) return STATE_MUTATION_DENIAL;
349
+ if (typeof args.mode === 'string' && !READ_ONLY_STATE_TOOL_MODE_SET.has(args.mode)) return STATE_MUTATION_DENIAL;
350
+
351
+ if (args.mode === 'memory') {
352
+ const action = typeof args.memoryAction === 'string' ? args.memoryAction : 'list';
353
+ if (!READ_ONLY_STATE_MEMORY_ACTION_SET.has(action) || isPresent(args.memoryValue)) return STATE_MUTATION_DENIAL;
354
+ }
355
+
356
+ if (args.mode === 'hooks') {
357
+ const action = typeof args.hookAction === 'string' ? args.hookAction : 'list';
358
+ if (!READ_ONLY_STATE_HOOK_ACTION_SET.has(action) || isPresent(args.hookDefinition)) return STATE_MUTATION_DENIAL;
359
+ }
360
+
361
+ if (args.mode === 'mode') {
362
+ const action = typeof args.modeAction === 'string' ? args.modeAction : 'get';
363
+ if (!READ_ONLY_STATE_MODE_ACTION_SET.has(action) || isPresent(args.modeName)) return STATE_MUTATION_DENIAL;
364
+ }
365
+
366
+ if (args.mode === 'analytics') {
367
+ const action = typeof args.analyticsAction === 'string' ? args.analyticsAction : 'summary';
368
+ if (!READ_ONLY_STATE_ANALYTICS_ACTION_SET.has(action)) return STATE_MUTATION_DENIAL;
369
+ if (
370
+ isPresent(args.analyticsTool)
371
+ || isPresent(args.analyticsArgs)
372
+ || isPresent(args.analyticsResult)
373
+ || isPresent(args.analyticsDuration)
374
+ || isPresent(args.analyticsTokens)
375
+ || isPresent(args.analyticsFormat)
376
+ ) {
377
+ return STATE_MUTATION_DENIAL;
378
+ }
379
+ }
380
+
381
+ return null;
382
+ }
383
+
243
384
  type ModeRestrictedToolPolicy = {
244
385
  readonly allowedModes: readonly string[];
245
386
  readonly modeSet: ReadonlySet<string>;
246
387
  readonly description: string;
247
388
  readonly denial: string;
389
+ readonly removedProperties?: readonly string[];
248
390
  };
249
391
 
250
392
  export function wrapModeRestrictedToolForAgentPolicy(tool: Tool, policy: ModeRestrictedToolPolicy): void {
251
393
  narrowModeToolDefinitionForAgentPolicy(tool, policy.allowedModes, policy.description);
394
+ if (policy.removedProperties) removeToolDefinitionProperties(tool, policy.removedProperties);
252
395
  const originalExecute = tool.execute.bind(tool);
253
396
  tool.execute = async (args) => {
254
397
  const denial = validateModeRestrictedToolInvocationForAgentPolicy(args as ModeToolArgs, policy.modeSet, policy.denial);
@@ -294,10 +437,22 @@ export const AGENT_READ_ONLY_REMOTE_TOOL_MODES = READ_ONLY_REMOTE_TOOL_MODES;
294
437
  export const AGENT_READ_ONLY_CHANNEL_TOOL_MODES = READ_ONLY_CHANNEL_TOOL_MODES;
295
438
  export const AGENT_READ_ONLY_MCP_TOOL_MODES = READ_ONLY_MCP_TOOL_MODES;
296
439
  export const AGENT_READ_ONLY_FETCH_METHODS = READ_ONLY_FETCH_METHODS;
440
+ export const AGENT_READ_ONLY_STATE_TOOL_MODES = READ_ONLY_STATE_TOOL_MODES;
441
+ export const AGENT_READ_ONLY_STATE_MEMORY_ACTIONS = READ_ONLY_STATE_MEMORY_ACTIONS;
442
+ export const AGENT_READ_ONLY_STATE_HOOK_ACTIONS = READ_ONLY_STATE_HOOK_ACTIONS;
443
+ export const AGENT_READ_ONLY_STATE_MODE_ACTIONS = READ_ONLY_STATE_MODE_ACTIONS;
444
+ export const AGENT_READ_ONLY_STATE_ANALYTICS_ACTIONS = READ_ONLY_STATE_ANALYTICS_ACTIONS;
445
+ export const AGENT_READ_ONLY_TASK_TOOL_MODES = READ_ONLY_TASK_TOOL_MODES;
446
+ export const AGENT_READ_ONLY_TEAM_TOOL_MODES = READ_ONLY_TEAM_TOOL_MODES;
447
+ export const AGENT_READ_ONLY_WORKLIST_TOOL_MODES = READ_ONLY_WORKLIST_TOOL_MODES;
448
+ export const AGENT_READ_ONLY_PACKET_TOOL_MODES = READ_ONLY_PACKET_TOOL_MODES;
449
+ export const AGENT_READ_ONLY_QUERY_TOOL_MODES = READ_ONLY_QUERY_TOOL_MODES;
297
450
  export const AGENT_REMOTE_MUTATION_DENIAL_MESSAGE = REMOTE_MUTATION_DENIAL;
298
451
  export const AGENT_CHANNEL_ACTION_DENIAL_MESSAGE = CHANNEL_ACTION_DENIAL;
299
452
  export const AGENT_MCP_SECURITY_MUTATION_DENIAL_MESSAGE = MCP_SECURITY_MUTATION_DENIAL;
300
453
  export const AGENT_FETCH_NETWORK_MUTATION_DENIAL_MESSAGE = FETCH_NETWORK_MUTATION_DENIAL;
454
+ export const AGENT_STATE_MUTATION_DENIAL_MESSAGE = STATE_MUTATION_DENIAL;
455
+ export const AGENT_DURABLE_WORKFLOW_MUTATION_DENIAL_MESSAGE = DURABLE_WORKFLOW_MUTATION_DENIAL;
301
456
 
302
457
  function isRecord(value: unknown): value is Record<string, unknown> {
303
458
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -397,6 +552,40 @@ function narrowFetchToolDefinitionForAgentPolicy(tool: Tool): void {
397
552
  delete urlProperties.auth;
398
553
  }
399
554
 
555
+ function narrowStateToolDefinitionForAgentPolicy(tool: Tool): void {
556
+ tool.definition.description = [
557
+ 'Inspect copied runtime state for GoodVibes Agent.',
558
+ 'State mutation, copied memory writes, hook changes, output-mode changes, and analytics writes are disabled in the main conversation.',
559
+ 'Use Agent-owned commands for intentional memory, skill, persona, and routine changes.',
560
+ ].join(' ');
561
+
562
+ const properties = tool.definition.parameters.properties;
563
+ if (!isRecord(properties)) return;
564
+ const modeProperty = properties.mode;
565
+ if (isRecord(modeProperty)) {
566
+ modeProperty.enum = [...READ_ONLY_STATE_TOOL_MODES];
567
+ modeProperty.description = 'Read-only copied runtime state mode. set and clear are disabled in GoodVibes Agent.';
568
+ }
569
+
570
+ delete properties.values;
571
+ delete properties.clearKeys;
572
+ delete properties.memoryValue;
573
+ delete properties.hookDefinition;
574
+ delete properties.hookName;
575
+ delete properties.modeName;
576
+ delete properties.analyticsTool;
577
+ delete properties.analyticsArgs;
578
+ delete properties.analyticsResult;
579
+ delete properties.analyticsDuration;
580
+ delete properties.analyticsTokens;
581
+ delete properties.analyticsFormat;
582
+
583
+ narrowStringEnumProperty(properties, 'memoryAction', READ_ONLY_STATE_MEMORY_ACTIONS, 'Read-only copied memory actions allowed by GoodVibes Agent.');
584
+ narrowStringEnumProperty(properties, 'hookAction', READ_ONLY_STATE_HOOK_ACTIONS, 'Read-only hook action allowed by GoodVibes Agent.');
585
+ narrowStringEnumProperty(properties, 'modeAction', READ_ONLY_STATE_MODE_ACTIONS, 'Read-only mode actions allowed by GoodVibes Agent.');
586
+ narrowStringEnumProperty(properties, 'analyticsAction', READ_ONLY_STATE_ANALYTICS_ACTIONS, 'Read-only analytics actions allowed by GoodVibes Agent.');
587
+ }
588
+
400
589
  function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readonly string[], description: string): void {
401
590
  tool.definition.description = description;
402
591
 
@@ -417,6 +606,24 @@ function narrowModeToolDefinitionForAgentPolicy(tool: Tool, allowedModes: readon
417
606
  }
418
607
  }
419
608
 
609
+ function removeToolDefinitionProperties(tool: Tool, keys: readonly string[]): void {
610
+ const properties = tool.definition.parameters.properties;
611
+ if (!isRecord(properties)) return;
612
+ for (const key of keys) delete properties[key];
613
+ }
614
+
615
+ function narrowStringEnumProperty(
616
+ properties: Record<string, unknown>,
617
+ key: string,
618
+ values: readonly string[],
619
+ description: string,
620
+ ): void {
621
+ const property = properties[key];
622
+ if (!isRecord(property)) return;
623
+ property.enum = [...values];
624
+ property.description = description;
625
+ }
626
+
420
627
  // Compatibility exports for copied TUI tests/imports during the near-fork phase.
421
628
  export const installWrfcAgentToolGuard = installAgentToolPolicyGuard;
422
629
  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.17';
9
+ let _version = '0.1.19';
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 {