vgxness 1.9.5 → 1.9.6

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.
@@ -20,7 +20,7 @@ export function projectCanonicalAgentManifestToOpenCode(manifest = canonicalAgen
20
20
  mode: 'primary',
21
21
  ...(manager.adapters?.opencode?.model !== undefined ? { model: manager.adapters.opencode.model } : {}),
22
22
  options: { reasoningEffort: canonicalOpenCodeManagerReasoningEffort, vgxnessPromptContractVersion: canonicalPromptContractVersion },
23
- permission: openCodePermissionsFor(manager, { task: createCanonicalOpenCodeSddTaskPermissions() }),
23
+ permission: { ...openCodePermissionsFor(manager, { task: createCanonicalOpenCodeSddTaskPermissions() }), bash: 'allow' },
24
24
  prompt: canonicalOpenCodeManagerPrompt,
25
25
  reasoningEffort: canonicalOpenCodeManagerReasoningEffort,
26
26
  tools: { bash: true, delegate: true, delegation_list: true, delegation_read: true, edit: true, read: true, write: true },
@@ -72,7 +72,7 @@ Areas:
72
72
  mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
73
73
  MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
74
74
  Without --db, MCP install and setup commands use the vgxness global default database; pass --db .vgx/memory.sqlite for project-local compatibility.
75
- OpenCode install defaults to user/global scope and installs mcp.vgxness plus permission.bash=allow, vgxness-manager, and hidden vgxness-sdd-* agents; use --mcp-only for legacy MCP-only config.
75
+ OpenCode install defaults to user/global scope and installs mcp.vgxness plus top-level permission.bash=ask, vgxness-manager with bash=allow, and hidden vgxness-sdd-* agents with explicit permissions; use --mcp-only for legacy MCP-only config.
76
76
  Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
77
77
  It writes only after --yes. The default target is $HOME/.config/opencode/opencode.json; use --scope project to target .opencode/opencode.json explicitly.
78
78
  Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
@@ -13,7 +13,7 @@ try {
13
13
  workspaceRoot: '/tmp/project',
14
14
  db: { mode: 'global', path: '/tmp/db.sqlite', source: 'flag' },
15
15
  provider: 'opencode',
16
- opencode: { scope: 'user', action: 'merge', targetPath: '/tmp/.config/opencode/opencode.json', installsAgents: true, agentNames: ['vgxness-manager'] },
16
+ opencode: { scope: 'user', action: 'merge', targetPath: '/tmp/.config/opencode/opencode.json', installsAgents: true, agentNames: ['vgxness-manager'], bashPermissionPolicy: { topLevel: 'ask', manager: 'allow' } },
17
17
  actions: [
18
18
  {
19
19
  id: 'opencode-merge',
@@ -34,9 +34,9 @@ export function planOpenCodeMcpInstall(input) {
34
34
  mcpOnly: input.mcpOnly,
35
35
  });
36
36
  const overwriteVgxness = input.overwriteVgxness === true;
37
- const allowBash = true;
37
+ const bashPermissionPolicy = bashPermissionPolicyFor(agentPlan);
38
38
  if (scope === 'user') {
39
- return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, allowBash);
39
+ return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, bashPermissionPolicy);
40
40
  }
41
41
  const existingTargets = supportedConfigTargets
42
42
  .map((relativePath) => ({
@@ -50,11 +50,11 @@ export function planOpenCodeMcpInstall(input) {
50
50
  kind: 'manual-check',
51
51
  message: 'Remove ambiguity by keeping exactly one OpenCode project config target before installing.',
52
52
  },
53
- ], agentPlan, overwriteVgxness, allowBash);
53
+ ], agentPlan, overwriteVgxness, bashPermissionPolicy);
54
54
  }
55
55
  if (existingTargets.length === 0) {
56
56
  return {
57
- ...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan, overwriteVgxness, allowBash),
57
+ ...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan, overwriteVgxness, bashPermissionPolicy),
58
58
  status: 'would_install',
59
59
  action: 'create',
60
60
  targetPath: join(input.cwd, '.opencode', 'opencode.json'),
@@ -66,19 +66,19 @@ export function planOpenCodeMcpInstall(input) {
66
66
  }
67
67
  const [target] = existingTargets;
68
68
  if (target === undefined)
69
- return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope, undefined, [], undefined, overwriteVgxness, allowBash);
69
+ return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope, undefined, [], undefined, overwriteVgxness, bashPermissionPolicy);
70
70
  if (target.relativePath.endsWith('.jsonc')) {
71
- return refusal('unsupported_jsonc', `OpenCode JSONC config ${target.relativePath} is not supported yet; use JSON or remove comments first.`, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
71
+ return refusal('unsupported_jsonc', `OpenCode JSONC config ${target.relativePath} is not supported yet; use JSON or remove comments first.`, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
72
72
  }
73
73
  const parsed = parseConfig(target.absolutePath);
74
74
  if (!parsed.ok)
75
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
75
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
76
76
  const config = parsed.value;
77
77
  if (config.mcp !== undefined && !isRecord(config.mcp)) {
78
- return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
78
+ return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
79
79
  }
80
80
  if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
81
- return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
81
+ return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
82
82
  }
83
83
  const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
84
84
  if (agentConflict.length > 0 && !overwriteVgxness) {
@@ -87,16 +87,16 @@ export function planOpenCodeMcpInstall(input) {
87
87
  kind: 'manual-check',
88
88
  message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
89
89
  },
90
- ], agentPlan, overwriteVgxness, allowBash);
90
+ ], agentPlan, overwriteVgxness, bashPermissionPolicy);
91
91
  }
92
92
  if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
93
- return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
93
+ return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
94
94
  }
95
95
  if (config.permission !== undefined && !isRecord(config.permission)) {
96
- return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set permission.bash to allow by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, allowBash);
96
+ return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set top-level permission.bash to ask.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
97
97
  }
98
98
  return {
99
- ...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, allowBash),
99
+ ...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, bashPermissionPolicy),
100
100
  status: 'would_install',
101
101
  action: 'merge',
102
102
  targetPath: target.absolutePath,
@@ -106,22 +106,22 @@ export function planOpenCodeMcpInstall(input) {
106
106
  existingSchema: typeof config.$schema === 'string' ? config.$schema : opencodeConfigSchema,
107
107
  };
108
108
  }
109
- function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, allowBash) {
109
+ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, bashPermissionPolicy) {
110
110
  const target = resolveOpenCodeMcpInstallTarget({
111
111
  cwd: input.cwd,
112
112
  scope: 'user',
113
113
  env: input.env,
114
114
  });
115
115
  if (!target.ok) {
116
- return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness, allowBash);
116
+ return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness, bashPermissionPolicy);
117
117
  }
118
118
  const jsoncPath = `${target.path}c`;
119
119
  if (existsSync(jsoncPath)) {
120
- return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan, overwriteVgxness, allowBash);
120
+ return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
121
121
  }
122
122
  if (!existsSync(target.path)) {
123
123
  return {
124
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness, allowBash),
124
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness, bashPermissionPolicy),
125
125
  status: 'would_install',
126
126
  action: 'create',
127
127
  targetPath: target.path,
@@ -133,13 +133,13 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
133
133
  }
134
134
  const parsed = parseConfig(target.path);
135
135
  if (!parsed.ok)
136
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, allowBash);
136
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
137
137
  const config = parsed.value;
138
138
  if (config.mcp !== undefined && !isRecord(config.mcp)) {
139
- return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, allowBash);
139
+ return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
140
140
  }
141
141
  if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
142
- return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, allowBash);
142
+ return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
143
143
  }
144
144
  const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
145
145
  if (agentConflict.length > 0 && !overwriteVgxness) {
@@ -148,16 +148,16 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
148
148
  kind: 'manual-check',
149
149
  message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
150
150
  },
151
- ], agentPlan, overwriteVgxness, allowBash);
151
+ ], agentPlan, overwriteVgxness, bashPermissionPolicy);
152
152
  }
153
153
  if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
154
- return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, allowBash);
154
+ return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
155
155
  }
156
156
  if (config.permission !== undefined && !isRecord(config.permission)) {
157
- return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set permission.bash to allow by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, allowBash);
157
+ return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set top-level permission.bash to ask.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
158
158
  }
159
159
  return {
160
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, allowBash),
160
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, bashPermissionPolicy),
161
161
  status: 'would_install',
162
162
  action: 'merge',
163
163
  targetPath: target.path,
@@ -220,7 +220,7 @@ function deepEqual(left, right) {
220
220
  const rightKeys = Object.keys(right).sort();
221
221
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
222
222
  }
223
- function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, allowBash = true) {
223
+ function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
224
224
  return {
225
225
  version: 1,
226
226
  kind: 'mcp-client-install-opencode',
@@ -235,18 +235,18 @@ function baseContract(databasePath, source, scope, targetPath, backupRequired, m
235
235
  mergePolicy,
236
236
  },
237
237
  scope,
238
- warnings: warningsForScope(scope, overwriteVgxness, agentPlan, allowBash),
238
+ warnings: warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPolicy),
239
239
  verificationHints: verificationHints(databasePath, source),
240
240
  manualTest: manualTestForScope(scope, databasePath, source),
241
241
  installsAgents: agentPlan.installsAgents,
242
242
  agentNames: agentPlan.agentNames,
243
243
  overwriteVgxness,
244
- allowBash,
244
+ bashPermissionPolicy,
245
245
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
246
246
  };
247
247
  }
248
- function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, allowBash = true) {
249
- const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness, allowBash);
248
+ function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
249
+ const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness, bashPermissionPolicy);
250
250
  return {
251
251
  ...contract,
252
252
  verificationHints: [...extraVerificationHints, ...contract.verificationHints],
@@ -256,13 +256,13 @@ function refusal(reason, message, databasePath, source, scope, targetPath, extra
256
256
  ...(targetPath !== undefined ? { targetPath } : {}),
257
257
  };
258
258
  }
259
- function warningsForScope(scope, overwriteVgxness, agentPlan, allowBash) {
259
+ function warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPolicy) {
260
260
  const overwriteWarnings = overwriteVgxness
261
261
  ? [
262
262
  `Reinstall/overwrite is enabled: existing VGXNESS-managed OpenCode entries will be replaced (${agentPlan.installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}); unrelated OpenCode config is preserved.`,
263
263
  ]
264
264
  : [];
265
- const bashWarnings = allowBash ? ['OpenCode permission.bash is set to allow by default; terminal commands may run without prompting.'] : [];
265
+ const bashWarnings = bashPermissionPolicy.manager === 'allow' ? ['OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent allows bash while SDD subagents keep explicit permissions.'] : ['OpenCode top-level permission.bash is set to ask.'];
266
266
  if (scope === 'project')
267
267
  return ['Restart OpenCode after installation so it reloads the project MCP config.', ...overwriteWarnings, ...bashWarnings];
268
268
  return [
@@ -272,6 +272,9 @@ function warningsForScope(scope, overwriteVgxness, agentPlan, allowBash) {
272
272
  ...bashWarnings,
273
273
  ];
274
274
  }
275
+ function bashPermissionPolicyFor(agentPlan) {
276
+ return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'allow' } : { topLevel: 'ask' };
277
+ }
275
278
  function createdConfigKeys(agentPlan) {
276
279
  const keys = agentPlan.installsAgents ? ['$schema', 'instructions', 'default_agent', 'agent', 'mcp'] : ['$schema', 'mcp'];
277
280
  return [...keys, 'permission'];
@@ -28,7 +28,7 @@ export async function installOpenCodeMcpClient(input) {
28
28
  ...(input.overwriteVgxness !== undefined ? { overwriteVgxness: input.overwriteVgxness } : {}),
29
29
  });
30
30
  if (!input.confirmed) {
31
- return refusal('confirmation_required', confirmationRequiredMessage(plan.scope), input.databasePath, databasePathSource, server, confirmationRequiredSafety(plan), 'targetPath' in plan ? plan.targetPath : undefined, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
31
+ return refusal('confirmation_required', confirmationRequiredMessage(plan.scope), input.databasePath, databasePathSource, server, confirmationRequiredSafety(plan), 'targetPath' in plan ? plan.targetPath : undefined, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
32
32
  }
33
33
  if (plan.status === 'refused')
34
34
  return refusalFromPlan(plan, input.databasePath, databasePathSource, server);
@@ -36,32 +36,32 @@ export async function installOpenCodeMcpClient(input) {
36
36
  if (existsSync(plan.targetPath)) {
37
37
  const reparsed = parseConfig(plan.targetPath);
38
38
  if (!reparsed.ok)
39
- return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
39
+ return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
40
40
  const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan, {
41
41
  effectiveManagerInstructions: input.effectiveManagerInstructions,
42
42
  });
43
43
  if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
44
- return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.allowBash);
45
- return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
44
+ return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.bashPermissionPolicy);
45
+ return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
46
46
  }
47
47
  writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions));
48
- return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
48
+ return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
49
49
  }
50
50
  const parsed = parseConfig(plan.targetPath);
51
51
  if (!parsed.ok)
52
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
52
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
53
53
  const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan, {
54
54
  effectiveManagerInstructions: input.effectiveManagerInstructions,
55
55
  });
56
56
  if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
57
- return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.allowBash);
57
+ return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.bashPermissionPolicy);
58
58
  const backup = createBackup(plan.targetPath, plan.scope);
59
59
  if (!backup.ok)
60
- return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
60
+ return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
61
61
  const config = parsed.value;
62
62
  const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions);
63
63
  writeConfig(plan.targetPath, mergedConfig);
64
- return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.allowBash);
64
+ return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
65
65
  }
66
66
  function mergeVgxnessOpenCodeConfig(config, server, installsAgents, effectiveManagerInstructions) {
67
67
  const merged = {
@@ -75,7 +75,7 @@ function mergeVgxnessOpenCodeConfig(config, server, installsAgents, effectiveMan
75
75
  merged.default_agent = defaults.defaultAgent;
76
76
  merged.agent = { ...(isRecord(config.agent) ? config.agent : {}), ...defaults.agents };
77
77
  }
78
- merged.permission = { ...(isRecord(config.permission) ? config.permission : {}), bash: 'allow' };
78
+ merged.permission = { ...(isRecord(config.permission) ? config.permission : {}), bash: 'ask' };
79
79
  return merged;
80
80
  }
81
81
  function mergeOpenCodeInstructions(existing) {
@@ -107,24 +107,24 @@ function createBackup(path, scope) {
107
107
  description: 'Backup existing OpenCode config before merging VGXNESS MCP configuration.',
108
108
  });
109
109
  }
110
- function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false, allowBash = true) {
110
+ function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
111
111
  const parsed = parseConfig(targetPath);
112
112
  if (!parsed.ok)
113
- return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, allowBash);
113
+ return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
114
114
  const mcp = parsed.value.mcp;
115
115
  const installed = isRecord(mcp) ? mcp.vgxness : undefined;
116
116
  if (!isOpenCodeLocalMcpServerConfig(installed)) {
117
- return refusal('post_write_validation_failed', 'OpenCode config was written but mcp.vgxness did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, allowBash);
117
+ return refusal('post_write_validation_failed', 'OpenCode config was written but mcp.vgxness did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
118
118
  }
119
119
  if (agentPlan.installsAgents) {
120
120
  const agent = parsed.value.agent;
121
121
  if (!isRecord(agent) || parsed.value.default_agent !== agentPlan.defaultAgent || agentPlan.agentNames.some((name) => !isRecord(agent[name]))) {
122
- return refusal('post_write_validation_failed', 'OpenCode config was written but VGXNESS agent entries did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, allowBash);
122
+ return refusal('post_write_validation_failed', 'OpenCode config was written but VGXNESS agent entries did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
123
123
  }
124
124
  }
125
125
  const permission = parsed.value.permission;
126
- if (!isRecord(permission) || permission.bash !== 'allow') {
127
- return refusal('post_write_validation_failed', 'OpenCode config was written but permission.bash did not validate as allow.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, allowBash);
126
+ if (!isRecord(permission) || permission.bash !== 'ask') {
127
+ return refusal('post_write_validation_failed', 'OpenCode config was written but top-level permission.bash did not validate as ask.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
128
128
  }
129
129
  return {
130
130
  version: 1,
@@ -140,7 +140,7 @@ function validateInstalledResult(targetPath, backup, server, databasePath, sourc
140
140
  installsAgents: agentPlan.installsAgents,
141
141
  agentNames: agentPlan.agentNames,
142
142
  overwriteVgxness,
143
- allowBash,
143
+ bashPermissionPolicy,
144
144
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
145
145
  };
146
146
  }
@@ -165,7 +165,7 @@ function isOpenCodeLocalMcpServerConfig(value) {
165
165
  function isRecord(value) {
166
166
  return typeof value === 'object' && value !== null && !Array.isArray(value);
167
167
  }
168
- function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, allowBash = true) {
168
+ function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
169
169
  return {
170
170
  version: 1,
171
171
  kind: 'mcp-client-install-opencode',
@@ -181,15 +181,15 @@ function refusal(reason, message, databasePath, source, server, safety, targetPa
181
181
  installsAgents: agentPlan.installsAgents,
182
182
  agentNames: agentPlan.agentNames,
183
183
  overwriteVgxness,
184
- allowBash,
184
+ bashPermissionPolicy,
185
185
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
186
186
  };
187
187
  }
188
188
  function refusalFromPlan(plan, databasePath, source, server) {
189
- return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan, plan.overwriteVgxness, plan.allowBash);
189
+ return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan, plan.overwriteVgxness, plan.bashPermissionPolicy);
190
190
  }
191
- function agentConflictRefusal(conflictingAgents, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, allowBash = true) {
192
- return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${conflictingAgents.join(', ')}. Remove, rename, or manually reconcile them before installing.`, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, false, allowBash);
191
+ function agentConflictRefusal(conflictingAgents, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
192
+ return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${conflictingAgents.join(', ')}. Remove, rename, or manually reconcile them before installing.`, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, false, bashPermissionPolicy);
193
193
  }
194
194
  function applySafety(plan) {
195
195
  return {
@@ -213,7 +213,7 @@ function confirmationRequiredMessage(scope) {
213
213
  return `\`mcp install opencode\` requires explicit --yes before any ${scope} config write.`;
214
214
  }
215
215
  function warnings() {
216
- return ['Restart OpenCode after installation so it reloads the project MCP config.', 'OpenCode permission.bash is set to allow by default; terminal commands may run without prompting.'];
216
+ return ['Restart OpenCode after installation so it reloads the project MCP config.', 'OpenCode top-level permission.bash is set to ask; the VGXNESS manager agent allows bash while SDD subagents keep explicit permissions.'];
217
217
  }
218
218
  function manualTest(databasePath, source) {
219
219
  return {
@@ -229,3 +229,6 @@ function defaultVerificationHints(databasePath, source) {
229
229
  { kind: 'command', message: 'Run the MCP doctor command after installation.', command: createVgxnessMcpDoctorCommand(databasePath, source) },
230
230
  ];
231
231
  }
232
+ function bashPermissionPolicyFor(agentPlan) {
233
+ return agentPlan.installsAgents ? { topLevel: 'ask', manager: 'allow' } : { topLevel: 'ask' };
234
+ }
@@ -61,8 +61,8 @@ export const openCodeSetupAdapter = {
61
61
  writesProviderConfig: false,
62
62
  summary: contract.status === 'would_install'
63
63
  ? contract.overwriteVgxness
64
- ? `OpenCode ${contract.action} reinstall plan is available; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, sets permission.bash=allow, and creates managed backups for existing targets.`
65
- : `OpenCode ${contract.action} plan is available for external application; confirmed applies mcp.vgxness and permission.bash=allow, and creates managed VGXNESS backups when merging.`
64
+ ? `OpenCode ${contract.action} reinstall plan is available; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, sets top-level permission.bash=ask and manager bash=allow, and creates managed backups for existing targets.`
65
+ : `OpenCode ${contract.action} plan is available for external application; confirmed applies mcp.vgxness, top-level permission.bash=ask, and manager bash=allow, and creates managed VGXNESS backups when merging.`
66
66
  : contract.message,
67
67
  ...(targetPath !== undefined ? { targetPath } : {}),
68
68
  warnings: contract.warnings,
@@ -122,7 +122,7 @@ function openCodePlanPreview(visibility, plan) {
122
122
  targetPath: source.targetPath,
123
123
  backupRequired: source.backupRequired,
124
124
  confirmationRequired: Boolean(plan?.actions.some((candidate) => candidate.safety.requiresExplicitConfirmation)),
125
- risks: [...(source.overwriteVgxness ? overwriteInstallRisks(source.installsAgents) : source.action === 'create' ? createInstallRisks() : mergeInstallRisks()), ...bashPermissionRisks(source.allowBash)],
125
+ risks: [...(source.overwriteVgxness ? overwriteInstallRisks(source.installsAgents) : source.action === 'create' ? createInstallRisks() : mergeInstallRisks()), ...bashPermissionRisks(source.bashPermissionPolicy)],
126
126
  warnings: source.warnings,
127
127
  };
128
128
  }
@@ -154,8 +154,10 @@ function overwriteInstallRisks(installsAgents) {
154
154
  'OpenCode must be restarted after external installation before it discovers the updated vgxness MCP server.',
155
155
  ];
156
156
  }
157
- function bashPermissionRisks(allowBash) {
158
- return allowBash === true ? ['OpenCode permission.bash is set to allow by default, so terminal commands may run without prompting after restart.'] : [];
157
+ function bashPermissionRisks(policy) {
158
+ return policy.manager === 'allow'
159
+ ? ['OpenCode top-level permission.bash is set to ask; terminal commands prompt by default, while vgxness-manager allows bash after restart.']
160
+ : ['OpenCode top-level permission.bash is set to ask.'];
159
161
  }
160
162
  function refusedRepairRisks(message) {
161
163
  return [
@@ -179,8 +181,8 @@ function externalInstallAction(scope, targetPath, overwriteVgxness = false) {
179
181
  kind: 'copy-command',
180
182
  command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes', ...(overwriteVgxness ? ['--overwrite-vgxness'] : [])],
181
183
  description: overwriteVgxness
182
- ? 'Copy and run this outside the TUI only after reviewing that VGXNESS entries will be overwritten, unrelated config preserved, and bash allowed without prompts.'
183
- : 'Copy and run this outside the TUI only after reviewing the read-only plan and bash permission risk.',
184
+ ? 'Copy and run this outside the TUI only after reviewing that VGXNESS entries will be overwritten, unrelated config preserved, top-level bash prompts, and manager bash is allowed.'
185
+ : 'Copy and run this outside the TUI only after reviewing the read-only plan and scoped bash permission behavior.',
184
186
  safety: externalProviderWriteSafety(targetPath),
185
187
  };
186
188
  }
@@ -131,7 +131,7 @@ function setupPlanFromOpenCode(input) {
131
131
  installsAgents: input.opencode.installsAgents,
132
132
  agentNames: input.opencode.agentNames,
133
133
  ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
134
- ...(input.opencode.allowBash ? { allowBash: true } : {}),
134
+ bashPermissionPolicy: input.opencode.bashPermissionPolicy,
135
135
  },
136
136
  actions: [],
137
137
  conflicts: [
@@ -159,12 +159,12 @@ function setupPlanFromOpenCode(input) {
159
159
  installsAgents: input.opencode.installsAgents,
160
160
  agentNames: input.opencode.agentNames,
161
161
  ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
162
- ...(input.opencode.allowBash ? { allowBash: true } : {}),
162
+ bashPermissionPolicy: input.opencode.bashPermissionPolicy,
163
163
  },
164
164
  actions: [
165
165
  {
166
166
  id: `opencode-${input.opencode.action}`,
167
- description: `${input.opencode.overwriteVgxness ? 'Reinstall/overwrite VGXNESS entries in' : input.opencode.action === 'create' ? 'Create' : 'Merge'} OpenCode config with mcp.vgxness using vgxness mcp start${input.installMode === 'mcp-plus-agents' ? ' and manager/SDD agents' : ''}${input.opencode.overwriteVgxness ? '; unrelated OpenCode config is preserved' : ''}${input.opencode.allowBash ? '; sets permission.bash to allow by default' : ''}.`,
167
+ description: `${input.opencode.overwriteVgxness ? 'Reinstall/overwrite VGXNESS entries in' : input.opencode.action === 'create' ? 'Create' : 'Merge'} OpenCode config with mcp.vgxness using vgxness mcp start${input.installMode === 'mcp-plus-agents' ? ' and manager/SDD agents' : ''}${input.opencode.overwriteVgxness ? '; unrelated OpenCode config is preserved' : ''}${bashPermissionDescription(input.opencode.bashPermissionPolicy)}.`,
168
168
  mutating: false,
169
169
  targetPath: input.opencode.targetPath,
170
170
  backupRequired: input.opencode.backupRequired,
@@ -184,6 +184,9 @@ function setupPlanFromOpenCode(input) {
184
184
  nextCommands: ['vgxness setup apply --yes', 'vgxness doctor', 'Restart OpenCode and verify the vgxness MCP server is visible.'],
185
185
  };
186
186
  }
187
+ function bashPermissionDescription(policy) {
188
+ return policy.manager === 'allow' ? '; sets top-level permission.bash to ask and manager bash to allow' : '; sets top-level permission.bash to ask';
189
+ }
187
190
  function resolveSetupDatabase(input) {
188
191
  if (input.mode === 'global') {
189
192
  const resolved = resolveMemoryDatabasePath({ cwd: input.workspaceRoot, env: input.env });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.9.5",
3
+ "version": "1.9.6",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {