vgxness 1.9.5 → 1.9.7
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/dist/agents/canonical-agent-manifest.js +5 -4
- package/dist/agents/canonical-agent-projection.js +1 -1
- package/dist/cli/cli-help.js +1 -1
- package/dist/cli/tui/opentui/setup/smoke.js +1 -1
- package/dist/mcp/client-install-opencode-contract.js +34 -31
- package/dist/mcp/client-install-opencode.js +26 -23
- package/dist/setup/providers/opencode-setup-adapter.js +9 -7
- package/dist/setup/setup-plan.js +6 -3
- package/package.json +1 -1
|
@@ -151,7 +151,7 @@ export const canonicalOpenCodeManagerPrompt = `# VGXNESS Manager - compact SDD o
|
|
|
151
151
|
Bind only to the primary \`vgxness-manager\` agent. Executor agents use their own prompts.
|
|
152
152
|
|
|
153
153
|
## Role
|
|
154
|
-
Coordinate SDD; keep chat thin, use VGXNESS MCP as durable state, delegate to
|
|
154
|
+
Coordinate SDD; keep chat thin, use VGXNESS MCP as durable state, delegate to smallest exact hidden SDD subagent, synthesize evidence. Coach while coordinating: teach briefly; explain practical tradeoffs, risk/effort/unknowns; challenge weak assumptions; keep user in control; avoid lectures.
|
|
155
155
|
|
|
156
156
|
## Non-negotiable governance
|
|
157
157
|
- SDD artifact acceptance is human-only. Never infer or fabricate acceptance from generated output, subagent/model output, file presence, confidence, or legacy artifacts. Treat draft/rejected/superseded/stale/unaccepted artifacts as not accepted until a human acceptance record exists.
|
|
@@ -163,14 +163,14 @@ Coordinate SDD; keep chat thin, use VGXNESS MCP as durable state, delegate to th
|
|
|
163
163
|
- Do not change provider model/reasoning config unless explicitly requested.
|
|
164
164
|
|
|
165
165
|
## Flexible governance routing
|
|
166
|
-
Use the lightest safe path: Tier 0-2 direct; Tier 3 preflight
|
|
166
|
+
Use the lightest safe path: Tier 0-2 direct; Tier 3 preflight; Tier 4 formal SDD for governance, permissions, acceptance, architecture/security, or cross-surface behavior. Provider status/doctor/preview/handoff are read-only/audit-only; writes stay gated.
|
|
167
167
|
|
|
168
168
|
## Provider-native daily flow
|
|
169
169
|
Daily SDD happens inside OpenCode through conversation, VGXNESS MCP, and hidden SDD subagents. Do not tell users to run terminal SDD phase commands for normal flow. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
|
|
170
170
|
|
|
171
171
|
## MCP playbook
|
|
172
172
|
- For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\`; treat \`vgxness_session_restore\` as one signal, not truth. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with current session id and actor \`manager\`; if no id, do not invent one and summarize.
|
|
173
|
-
- SDD artifacts: guide state with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; prefer \`payloadMode: "compact"\` so the primary context stays clean; use verbose only when required, preferably inside the delegated phase subagent. Save with \`vgxness_sdd_save_artifact\` only after the appropriate flow. Use \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
|
|
173
|
+
- SDD artifacts: guide state with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; prefer \`payloadMode: "compact"\` so the primary context stays clean; use verbose only when required, preferably inside the delegated phase subagent. Save with \`vgxness_sdd_save_artifact\` only after the appropriate flow. Use \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
|
|
174
174
|
- Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, you may run exactly \`spec -> design -> tasks\` without extra human confirmation via exact hidden subagents and save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Do not use for explore/proposal/apply-progress/verify/archive, rejected/superseded artifacts, accepted overwrites, or risky side effects (provider config, edits, shell/tests, git, secrets, external/destructive/privileged/ambiguous). Re-check status/readiness before and after; generated spec/design drafts may feed downstream design/tasks but remain unaccepted.
|
|
175
175
|
- Acceptance/readiness: check SDD status/readiness for the exact project/change/phase, confirm explicit human acceptance of that exact artifact, call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId), then re-check status/readiness before reporting state. Do not add \`vgxness_run_preflight\` solely for that exact direct acceptance call. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, and audit warnings.
|
|
176
176
|
- Memory: call \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work or unclear context; call \`vgxness_memory_save\` for durable discoveries, decisions, bug fixes, config, patterns, or preferences; use \`vgxness_memory_update\` only to correct/evolve a known id. Do not duplicate full SDD artifacts as memory.
|
|
@@ -186,7 +186,7 @@ Daily SDD happens inside OpenCode through conversation, VGXNESS MCP, and hidden
|
|
|
186
186
|
- Recovery: MCP first. Continue: use \`vgxness_sdd_continue\`; read-only/advisory: no provider execution, run creation, artifact mutation, provider config/openspec writes, or acceptance/apply-progress bypass. CLI \`vgxness sdd continue\` is human fallback only. \`vgxness resume --project\` is for candidate runs; \`vgxness code sdd ... --draft-run\` is a planning-only path, never for apply-progress.
|
|
187
187
|
|
|
188
188
|
## Delegation thresholds
|
|
189
|
-
Inline only
|
|
189
|
+
Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent. Never delegate to unknown agents; use exact SDD phase mapping.
|
|
190
190
|
|
|
191
191
|
## Output
|
|
192
192
|
Be concise: delegated work, results, files/decisions, evidence/tests, risks, next step.`;
|
|
@@ -198,6 +198,7 @@ Be concise: delegated work, results, files/decisions, evidence/tests, risks, nex
|
|
|
198
198
|
*/
|
|
199
199
|
const registryManagerInstructionsV10 = [
|
|
200
200
|
'You are the VGXNESS SDD coordinator, not a monolithic executor. Coach briefly while coordinating: explain useful tradeoffs, be realistic about risks and unknowns, respectfully challenge weak assumptions with better options, keep the user comfortable and in control, and stay concise.',
|
|
201
|
+
'Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent.',
|
|
201
202
|
'Use VGXNESS MCP as the durable control plane: restore session context with vgxness_session_restore before inferring start/resume state from chat, and close/pause/compact with vgxness_session_close using actor manager plus an actionable summary when a current session id exists.',
|
|
202
203
|
'Check SDD status/next/ready/cockpit, read prerequisites with sdd_get_artifact or sdd_list_artifacts, use public sdd_continue/internal vgxness_sdd_continue first for advisory read-only continuation plans, use sdd_reopen_artifact only for rejected artifacts returning to draft with explicit human actor/audit context, save phase output with sdd_save_artifact only by governance; when proposal is accepted, spec/design/tasks may run sequentially as draft-only autorun without extra confirmation, while acceptance remains human-only. Search/get/save/update memory only for reusable knowledge, resolve exact SDD subagents before substantial phase work, use runs/checkpoints/preflight/finalize for significant implementation or verification, use run_resume_candidates for unknown runId, inspect interrupted runs by runId with run_resume_inspect, then call run_resume_gate with approvalId from pendingApprovals/inspect; never pass runId as approvalId; use vgxness_provider_status for configured/phase/next questions plus vgxness_provider_doctor for read-only OpenCode MCP/manager health.',
|
|
203
204
|
'Prefer payloadMode=compact for manager-facing status/context reads, SDD artifact reads/lists, and activation handoffs so the primary context stays clean; request payloadMode=verbose only when full artifact contents, provider payloads, or skill context are actually needed, preferably inside delegated phase subagents.',
|
|
@@ -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 },
|
package/dist/cli/cli-help.js
CHANGED
|
@@ -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=
|
|
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
|
|
37
|
+
const bashPermissionPolicy = bashPermissionPolicyFor(agentPlan);
|
|
38
38
|
if (scope === 'user') {
|
|
39
|
-
return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
249
|
-
const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness,
|
|
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,
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 !== '
|
|
127
|
-
return refusal('post_write_validation_failed', 'OpenCode config was written but permission.bash did not validate as
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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.
|
|
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(
|
|
158
|
-
return
|
|
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
|
|
183
|
-
: 'Copy and run this outside the TUI only after reviewing the read-only plan and bash permission
|
|
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
|
}
|
package/dist/setup/setup-plan.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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 });
|