vgxness 1.4.0 → 1.5.1

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.
@@ -57,13 +57,14 @@ Areas:
57
57
  Approval commands resolve explicit run/preflight approval records; they do not change agent seed permissions or globally allow shell/provider access.
58
58
 
59
59
  mcp setup --preview --provider opencode|claude [--db <path>]
60
- mcp install opencode --plan [--scope user|project] [--db <path>] [--mcp-only|--no-agents]
61
- mcp install opencode --yes [--scope user|project] [--db <path>] [--mcp-only|--no-agents]
60
+ mcp install opencode --plan [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
61
+ mcp install opencode --yes [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
62
62
  mcp doctor opencode [--scope user|project] [--project-root <path>]
63
63
  mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
64
64
  MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
65
65
  Without --db, MCP install and setup commands use the vgxness global default database; pass --db .vgx/memory.sqlite for project-local compatibility.
66
66
  OpenCode install defaults to user/global scope and installs mcp.vgxness plus vgxness-manager and hidden vgxness-sdd-* agents; use --mcp-only for legacy MCP-only config.
67
+ Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
67
68
  It writes only after --yes. The default target is $HOME/.config/opencode/opencode.json; use --scope project to target .opencode/opencode.json explicitly.
68
69
  Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
69
70
 
@@ -19,6 +19,7 @@ export function runMcpInstallCommand(parsed, environment) {
19
19
  if (!scope.ok)
20
20
  return resultFailure(scope);
21
21
  const mcpOnly = parsed.flags['mcp-only'] === true || parsed.flags['no-agents'] === true;
22
+ const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
22
23
  if (parsed.flags.plan === true) {
23
24
  return jsonResult({
24
25
  ok: true,
@@ -29,6 +30,7 @@ export function runMcpInstallCommand(parsed, environment) {
29
30
  scope: scope.value,
30
31
  env: environment.env,
31
32
  mcpOnly,
33
+ overwriteVgxness,
32
34
  }),
33
35
  });
34
36
  }
@@ -40,6 +42,7 @@ export function runMcpInstallCommand(parsed, environment) {
40
42
  env: environment.env,
41
43
  confirmed: parsed.flags.yes === true,
42
44
  mcpOnly,
45
+ overwriteVgxness,
43
46
  });
44
47
  return result.status === 'installed'
45
48
  ? jsonResult({ ok: true, value: result })
@@ -143,6 +143,7 @@ export async function applySetupPlanInput(input, environment) {
143
143
  env: environment.env,
144
144
  confirmed: true,
145
145
  mcpOnly: input.installMode === 'mcp-only',
146
+ ...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
146
147
  });
147
148
  return result.status === 'installed'
148
149
  ? {
@@ -172,6 +173,7 @@ export function setupPlanInputFromFlags(parsed, environment) {
172
173
  const installMode = setupInstallModeFlag(parsed.flags);
173
174
  if (!installMode.ok)
174
175
  return installMode;
176
+ const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
175
177
  return {
176
178
  ok: true,
177
179
  value: {
@@ -183,6 +185,7 @@ export function setupPlanInputFromFlags(parsed, environment) {
183
185
  provider: provider.value,
184
186
  scope: scope.value,
185
187
  installMode: installMode.value,
188
+ overwriteVgxness,
186
189
  },
187
190
  };
188
191
  }
@@ -196,6 +199,7 @@ function defaultWizardInput(environment) {
196
199
  provider: vgxnessSetupDefaults.defaultProvider,
197
200
  scope: vgxnessSetupDefaults.defaultOpenCodeScope,
198
201
  installMode: vgxnessSetupDefaults.defaultInstallMode,
202
+ overwriteVgxness: false,
199
203
  };
200
204
  }
201
205
  function defaulted(value, fallback) {
@@ -281,6 +285,8 @@ export function promptSetupWizard(environment, defaults) {
281
285
  : undefined;
282
286
  if (installMode === undefined)
283
287
  return validationFailure('Install mode must be MCP + manager/SDD agents or MCP only.');
288
+ const overwriteText = defaulted(await promptLine(environment, 'Reinstall VGXNESS entries: overwrite existing mcp.vgxness and VGXNESS agents after final confirmation; preserve unrelated OpenCode config [no]: '), 'no');
289
+ const overwriteVgxness = overwriteText.toLowerCase() === 'yes' || overwriteText.toLowerCase() === 'y' || overwriteText.toLowerCase() === 'true';
284
290
  const input = {
285
291
  project,
286
292
  workspaceRoot: environment.cwd,
@@ -290,6 +296,7 @@ export function promptSetupWizard(environment, defaults) {
290
296
  provider,
291
297
  scope,
292
298
  installMode,
299
+ overwriteVgxness,
293
300
  };
294
301
  const flags = {
295
302
  project,
@@ -298,6 +305,7 @@ export function promptSetupWizard(environment, defaults) {
298
305
  scope,
299
306
  mode: installMode,
300
307
  yes: true,
308
+ ...(overwriteVgxness ? { 'overwrite-vgxness': true } : {}),
301
309
  ...(databasePath === undefined ? {} : { 'db-path': databasePath }),
302
310
  };
303
311
  return { ok: true, value: { input, flags } };
@@ -355,6 +363,7 @@ export async function runSetupTuiCommand(environment, input) {
355
363
  provider: vgxnessSetupDefaults.defaultProvider,
356
364
  scope: vgxnessSetupDefaults.defaultOpenCodeScope,
357
365
  installMode: vgxnessSetupDefaults.defaultInstallMode,
366
+ overwriteVgxness: false,
358
367
  },
359
368
  };
360
369
  try {
@@ -23,6 +23,7 @@ function renderOpenCode(plan) {
23
23
  `- Action: ${plan.opencode.action}`,
24
24
  `- Target path: ${target}`,
25
25
  `- Installs agents: ${String(plan.opencode.installsAgents)}`,
26
+ `- Reinstall VGXNESS entries: ${String(plan.opencode.overwriteVgxness)}`,
26
27
  `- Agent names: ${plan.opencode.agentNames.length === 0 ? 'none' : plan.opencode.agentNames.join(', ')}`,
27
28
  ];
28
29
  }
@@ -59,20 +59,24 @@ function canApplyCurrentState(previous, reduced) {
59
59
  if (plan?.provider !== 'opencode' || plan.status !== 'ready' || plan.opencode === undefined)
60
60
  return false;
61
61
  const installMode = plan.opencode.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents';
62
- return plan.opencode.scope === reduced.selections.scope && installMode === reduced.selections.installMode;
62
+ return (plan.opencode.scope === reduced.selections.scope &&
63
+ installMode === reduced.selections.installMode &&
64
+ (plan.opencode.overwriteVgxness === true) === (reduced.selections.overwriteVgxness === true));
63
65
  }
64
66
  function shouldRefreshPlan(previous, next, action) {
65
67
  if (action.type !== 'select-focused-choice' &&
66
68
  action.type !== 'select-database-mode' &&
67
69
  action.type !== 'select-provider' &&
68
70
  action.type !== 'select-scope' &&
69
- action.type !== 'select-install-mode')
71
+ action.type !== 'select-install-mode' &&
72
+ action.type !== 'toggle-overwrite-vgxness')
70
73
  return false;
71
74
  return (previous.selections.databaseMode !== next.selections.databaseMode ||
72
75
  previous.selections.databasePath !== next.selections.databasePath ||
73
76
  previous.selections.provider !== next.selections.provider ||
74
77
  previous.selections.scope !== next.selections.scope ||
75
- previous.selections.installMode !== next.selections.installMode);
78
+ previous.selections.installMode !== next.selections.installMode ||
79
+ (previous.selections.overwriteVgxness === true) !== (next.selections.overwriteVgxness === true));
76
80
  }
77
81
  function refreshPlan(state, services, runtime) {
78
82
  const plan = services.createPlan(setupPlanInputFromTui(runtime));
@@ -59,7 +59,9 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
59
59
  backupLabel: backups.length === 0 ? 'Backup: none planned' : `Backup planned: ${backups.join('; ')}`,
60
60
  nextCommands: plan?.nextCommands ?? ['vgxness setup plan'],
61
61
  canAutoApply: plan?.provider === 'opencode' && plan.status === 'ready' && state?.selections.provider !== 'none' && plan.opencode !== undefined,
62
- safetyWarning: 'Final confirmation is required before any provider config write. OpenCode config may be modified and backed up.',
62
+ safetyWarning: isOpenCode && (selections?.overwriteVgxness === true || opencode?.overwriteVgxness === true)
63
+ ? 'Final confirmation is required. Reinstall is enabled: VGXNESS OpenCode entries will be overwritten after a managed backup when the target exists; unrelated config is preserved.'
64
+ : 'Final confirmation is required before any provider config write. OpenCode config may be modified and backed up.',
63
65
  frameLabel: 'VGXNESS Setup Assistant workspace',
64
66
  progressLabel: progressLabel(state?.screen),
65
67
  previewLabel,
@@ -89,6 +91,11 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
89
91
  choice('install:mcp-only', 'MCP only', 'Install only the MCP server entry after confirmation.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-only', state?.focusedChoiceId, [badgeLabels.writeAfterConfirm]),
90
92
  ]
91
93
  : [],
94
+ overwriteChoices: isOpenCode
95
+ ? [
96
+ choice('overwrite:vgxness', 'Reinstall VGXNESS entries', 'Default off. When enabled, final confirmation overwrites existing VGXNESS OpenCode entries and preserves unrelated OpenCode config.', selections?.overwriteVgxness ?? opencode?.overwriteVgxness ?? false, state?.focusedChoiceId, [badgeLabels.warning, badgeLabels.writeAfterConfirm]),
97
+ ]
98
+ : [],
92
99
  };
93
100
  }
94
101
  function previewDetailLines(input) {
@@ -101,6 +108,7 @@ function previewDetailLines(input) {
101
108
  `Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
102
109
  `Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
103
110
  `Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
111
+ `Reinstall VGXNESS entries: ${input.isOpenCode ? String(opencode?.overwriteVgxness ?? false) : 'disabled for manual/none provider'}`,
104
112
  `Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude deferred/not installable.' : 'No provider installable; OpenCode controls disabled and Claude deferred.'}`,
105
113
  `Agent readiness: ${agentReadinessFromPlan(plan)}`,
106
114
  `Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
@@ -40,6 +40,7 @@ function screenLines(input, width) {
40
40
  'OpenCode plan ' + badgeLabels.writeAfterConfirm,
41
41
  ...choiceLines(vm.scopeChoices),
42
42
  ...choiceLines(vm.installModeChoices),
43
+ ...choiceLines(vm.overwriteChoices),
43
44
  `Scope: ${vm.scopeLabel}`,
44
45
  `Install mode: ${vm.installModeLabel}`,
45
46
  `Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
@@ -70,6 +71,7 @@ function screenLines(input, width) {
70
71
  'Confirm OpenCode setup',
71
72
  ...planLines(vm, width),
72
73
  'WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned.',
74
+ vm.safetyWarning,
73
75
  'After confirmation: run doctor, then restart OpenCode to reload MCP configuration.',
74
76
  'Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing.',
75
77
  ], width, [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel]);
@@ -98,6 +100,7 @@ function planLines(vm, width) {
98
100
  `Database path: ${compactPath(vm.databasePathLabel, width - 15)}`,
99
101
  `OpenCode scope: ${vm.scopeLabel}`,
100
102
  `Install mode: ${vm.installModeLabel}`,
103
+ vm.safetyWarning,
101
104
  `Provider installability: ${vm.providerInstallabilityLabel}`,
102
105
  `Agent readiness: ${vm.agentReadinessLabel}`,
103
106
  `Agent detail: ${vm.agentReadinessDetail}`,
@@ -11,6 +11,7 @@ export function setupPlanInputFromTui(input) {
11
11
  provider: input.selections.provider,
12
12
  scope: input.selections.scope,
13
13
  installMode: input.selections.installMode,
14
+ ...(input.selections.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.selections.overwriteVgxness }),
14
15
  };
15
16
  }
16
17
  export function createDefaultSetupTuiServices(input) {
@@ -44,6 +45,7 @@ export async function applySetupTuiPlan(planInput, runtime) {
44
45
  env: runtime.env,
45
46
  confirmed: true,
46
47
  mcpOnly: planInput.installMode === 'mcp-only',
48
+ ...(planInput.overwriteVgxness === undefined ? {} : { overwriteVgxness: planInput.overwriteVgxness }),
47
49
  });
48
50
  if (installed.status !== 'installed')
49
51
  return { ok: false, error: { code: 'validation_failed', message: `${installed.reason}: ${installed.message}` } };
@@ -4,7 +4,7 @@ const screenChoiceIds = {
4
4
  welcome: [],
5
5
  'project-database': ['database:global', 'database:project-local', 'database:custom'],
6
6
  provider: ['provider:opencode', 'provider:none'],
7
- 'opencode-details': ['scope:project', 'scope:user', 'install:mcp-plus-agents', 'install:mcp-only'],
7
+ 'opencode-details': ['scope:project', 'scope:user', 'install:mcp-plus-agents', 'install:mcp-only', 'overwrite:vgxness'],
8
8
  'plan-review': [],
9
9
  'final-confirmation': [],
10
10
  applying: [],
@@ -20,6 +20,7 @@ export function createSetupTuiState(input = {}) {
20
20
  provider: vgxnessSetupDefaults.defaultProvider,
21
21
  scope: vgxnessSetupDefaults.defaultOpenCodeScope,
22
22
  installMode: vgxnessSetupDefaults.defaultInstallMode,
23
+ overwriteVgxness: false,
23
24
  },
24
25
  ...(input.focusedChoiceId === undefined ? {} : { focusedChoiceId: input.focusedChoiceId }),
25
26
  helpVisible: input.helpVisible ?? false,
@@ -55,6 +56,8 @@ export function reduceSetupTuiState(state, action) {
55
56
  return { ...state, selections: { ...state.selections, scope: action.scope } };
56
57
  case 'select-install-mode':
57
58
  return { ...state, selections: { ...state.selections, installMode: action.installMode } };
59
+ case 'toggle-overwrite-vgxness':
60
+ return { ...state, selections: { ...state.selections, overwriteVgxness: state.selections.overwriteVgxness !== true } };
58
61
  case 'refresh-plan-start':
59
62
  return { ...state, previewRefresh: 'refreshing' };
60
63
  case 'refresh-plan-success':
@@ -131,6 +134,8 @@ function selectFocusedChoice(state) {
131
134
  return reduceSetupTuiState(state, { type: 'select-install-mode', installMode: 'mcp-plus-agents' });
132
135
  case 'install:mcp-only':
133
136
  return reduceSetupTuiState(state, { type: 'select-install-mode', installMode: 'mcp-only' });
137
+ case 'overwrite:vgxness':
138
+ return reduceSetupTuiState(state, { type: 'toggle-overwrite-vgxness' });
134
139
  default:
135
140
  return state;
136
141
  }
@@ -154,7 +159,9 @@ function canAutoApplyFromState(state) {
154
159
  if (state.plan?.provider !== 'opencode' || state.plan.status !== 'ready' || state.plan.opencode === undefined)
155
160
  return false;
156
161
  const installMode = state.plan.opencode.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents';
157
- return state.plan.opencode.scope === state.selections.scope && installMode === state.selections.installMode;
162
+ return (state.plan.opencode.scope === state.selections.scope &&
163
+ installMode === state.selections.installMode &&
164
+ (state.plan.opencode.overwriteVgxness === true) === (state.selections.overwriteVgxness === true));
158
165
  }
159
166
  function advance(state) {
160
167
  const index = setupTuiScreenOrder.indexOf(state.screen);
@@ -33,8 +33,9 @@ export function planOpenCodeMcpInstall(input) {
33
33
  const agentPlan = createOpenCodeDefaultAgentInstallPlan({
34
34
  mcpOnly: input.mcpOnly,
35
35
  });
36
+ const overwriteVgxness = input.overwriteVgxness === true;
36
37
  if (scope === 'user') {
37
- return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan);
38
+ return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness);
38
39
  }
39
40
  const existingTargets = supportedConfigTargets
40
41
  .map((relativePath) => ({
@@ -48,11 +49,11 @@ export function planOpenCodeMcpInstall(input) {
48
49
  kind: 'manual-check',
49
50
  message: 'Remove ambiguity by keeping exactly one OpenCode project config target before installing.',
50
51
  },
51
- ], agentPlan);
52
+ ], agentPlan, overwriteVgxness);
52
53
  }
53
54
  if (existingTargets.length === 0) {
54
55
  return {
55
- ...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan),
56
+ ...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan, overwriteVgxness),
56
57
  status: 'would_install',
57
58
  action: 'create',
58
59
  targetPath: join(input.cwd, '.opencode', 'opencode.json'),
@@ -64,31 +65,34 @@ export function planOpenCodeMcpInstall(input) {
64
65
  }
65
66
  const [target] = existingTargets;
66
67
  if (target === undefined)
67
- return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope);
68
+ return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope, undefined, [], undefined, overwriteVgxness);
68
69
  if (target.relativePath.endsWith('.jsonc')) {
69
- 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);
70
+ 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);
70
71
  }
71
72
  const parsed = parseConfig(target.absolutePath);
72
73
  if (!parsed.ok)
73
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
74
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
74
75
  const config = parsed.value;
75
76
  if (config.mcp !== undefined && !isRecord(config.mcp)) {
76
- 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);
77
+ 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);
77
78
  }
78
- if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness')) {
79
- return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
79
+ if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
80
+ 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);
80
81
  }
81
82
  const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
82
- if (agentConflict.length > 0) {
83
+ if (agentConflict.length > 0 && !overwriteVgxness) {
83
84
  return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${agentConflict.join(', ')}. Remove, rename, or manually reconcile them before installing.`, input.databasePath, databasePathSource, scope, target.absolutePath, [
84
85
  {
85
86
  kind: 'manual-check',
86
87
  message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
87
88
  },
88
- ], agentPlan);
89
+ ], agentPlan, overwriteVgxness);
90
+ }
91
+ if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
92
+ 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);
89
93
  }
90
94
  return {
91
- ...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan),
95
+ ...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan, overwriteVgxness),
92
96
  status: 'would_install',
93
97
  action: 'merge',
94
98
  targetPath: target.absolutePath,
@@ -98,22 +102,22 @@ export function planOpenCodeMcpInstall(input) {
98
102
  existingSchema: typeof config.$schema === 'string' ? config.$schema : opencodeConfigSchema,
99
103
  };
100
104
  }
101
- function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan) {
105
+ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness) {
102
106
  const target = resolveOpenCodeMcpInstallTarget({
103
107
  cwd: input.cwd,
104
108
  scope: 'user',
105
109
  env: input.env,
106
110
  });
107
111
  if (!target.ok) {
108
- return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user');
112
+ return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness);
109
113
  }
110
114
  const jsoncPath = `${target.path}c`;
111
115
  if (existsSync(jsoncPath)) {
112
- 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);
116
+ 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);
113
117
  }
114
118
  if (!existsSync(target.path)) {
115
119
  return {
116
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan),
120
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness),
117
121
  status: 'would_install',
118
122
  action: 'create',
119
123
  targetPath: target.path,
@@ -125,25 +129,28 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
125
129
  }
126
130
  const parsed = parseConfig(target.path);
127
131
  if (!parsed.ok)
128
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan);
132
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness);
129
133
  const config = parsed.value;
130
134
  if (config.mcp !== undefined && !isRecord(config.mcp)) {
131
- 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);
135
+ 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);
132
136
  }
133
- if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness')) {
134
- return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan);
137
+ if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
138
+ 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);
135
139
  }
136
140
  const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
137
- if (agentConflict.length > 0) {
141
+ if (agentConflict.length > 0 && !overwriteVgxness) {
138
142
  return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${agentConflict.join(', ')}. Remove, rename, or manually reconcile them before installing.`, input.databasePath, databasePathSource, 'user', target.path, [
139
143
  {
140
144
  kind: 'manual-check',
141
145
  message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
142
146
  },
143
- ], agentPlan);
147
+ ], agentPlan, overwriteVgxness);
148
+ }
149
+ if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
150
+ 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);
144
151
  }
145
152
  return {
146
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan),
153
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness),
147
154
  status: 'would_install',
148
155
  action: 'merge',
149
156
  targetPath: target.path,
@@ -206,7 +213,7 @@ function deepEqual(left, right) {
206
213
  const rightKeys = Object.keys(right).sort();
207
214
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
208
215
  }
209
- function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
216
+ function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false) {
210
217
  return {
211
218
  version: 1,
212
219
  kind: 'mcp-client-install-opencode',
@@ -221,16 +228,17 @@ function baseContract(databasePath, source, scope, targetPath, backupRequired, m
221
228
  mergePolicy,
222
229
  },
223
230
  scope,
224
- warnings: warningsForScope(scope),
231
+ warnings: warningsForScope(scope, overwriteVgxness, agentPlan),
225
232
  verificationHints: verificationHints(databasePath, source),
226
233
  manualTest: manualTestForScope(scope, databasePath, source),
227
234
  installsAgents: agentPlan.installsAgents,
228
235
  agentNames: agentPlan.agentNames,
236
+ overwriteVgxness,
229
237
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
230
238
  };
231
239
  }
232
- function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
233
- const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan);
240
+ function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false) {
241
+ const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness);
234
242
  return {
235
243
  ...contract,
236
244
  verificationHints: [...extraVerificationHints, ...contract.verificationHints],
@@ -240,12 +248,18 @@ function refusal(reason, message, databasePath, source, scope, targetPath, extra
240
248
  ...(targetPath !== undefined ? { targetPath } : {}),
241
249
  };
242
250
  }
243
- function warningsForScope(scope) {
251
+ function warningsForScope(scope, overwriteVgxness, agentPlan) {
252
+ const overwriteWarnings = overwriteVgxness
253
+ ? [
254
+ `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.`,
255
+ ]
256
+ : [];
244
257
  if (scope === 'project')
245
- return ['Restart OpenCode after installation so it reloads the project MCP config.'];
258
+ return ['Restart OpenCode after installation so it reloads the project MCP config.', ...overwriteWarnings];
246
259
  return [
247
260
  'Restart OpenCode after installation so it reloads the user MCP config.',
248
261
  'OpenCode project config may override user config for a workspace; check project-level config if vgxness is not visible.',
262
+ ...overwriteWarnings,
249
263
  ];
250
264
  }
251
265
  function manualTestForScope(scope, databasePath, source) {
@@ -25,9 +25,10 @@ export async function installOpenCodeMcpClient(input) {
25
25
  ...(input.scope !== undefined ? { scope: input.scope } : {}),
26
26
  ...(input.env !== undefined ? { env: input.env } : {}),
27
27
  ...(input.mcpOnly !== undefined ? { mcpOnly: input.mcpOnly } : {}),
28
+ ...(input.overwriteVgxness !== undefined ? { overwriteVgxness: input.overwriteVgxness } : {}),
28
29
  });
29
30
  if (!input.confirmed) {
30
- 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);
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);
31
32
  }
32
33
  if (plan.status === 'refused')
33
34
  return refusalFromPlan(plan, input.databasePath, databasePathSource, server);
@@ -35,28 +36,28 @@ export async function installOpenCodeMcpClient(input) {
35
36
  if (existsSync(plan.targetPath)) {
36
37
  const reparsed = parseConfig(plan.targetPath);
37
38
  if (!reparsed.ok)
38
- return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
39
+ return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
39
40
  const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan);
40
- if (conflictingAgents.length > 0)
41
+ if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
41
42
  return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
42
- 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);
43
+ 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);
43
44
  }
44
45
  writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents));
45
- return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
46
+ return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
46
47
  }
47
48
  const parsed = parseConfig(plan.targetPath);
48
49
  if (!parsed.ok)
49
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
50
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
50
51
  const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan);
51
- if (conflictingAgents.length > 0)
52
+ if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
52
53
  return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
53
54
  const backup = createBackup(plan.targetPath, plan.scope);
54
55
  if (!backup.ok)
55
- return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
56
+ 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);
56
57
  const config = parsed.value;
57
58
  const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents);
58
59
  writeConfig(plan.targetPath, mergedConfig);
59
- return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
60
+ return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
60
61
  }
61
62
  function mergeVgxnessOpenCodeConfig(config, server, installsAgents) {
62
63
  const merged = {
@@ -101,7 +102,7 @@ function createBackup(path, scope) {
101
102
  description: 'Backup existing OpenCode config before merging VGXNESS MCP configuration.',
102
103
  });
103
104
  }
104
- function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
105
+ function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false) {
105
106
  const parsed = parseConfig(targetPath);
106
107
  if (!parsed.ok)
107
108
  return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
@@ -129,6 +130,7 @@ function validateInstalledResult(targetPath, backup, server, databasePath, sourc
129
130
  manualTest: manualTestGuidance,
130
131
  installsAgents: agentPlan.installsAgents,
131
132
  agentNames: agentPlan.agentNames,
133
+ overwriteVgxness,
132
134
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
133
135
  };
134
136
  }
@@ -153,7 +155,7 @@ function isOpenCodeLocalMcpServerConfig(value) {
153
155
  function isRecord(value) {
154
156
  return typeof value === 'object' && value !== null && !Array.isArray(value);
155
157
  }
156
- function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
158
+ 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) {
157
159
  return {
158
160
  version: 1,
159
161
  kind: 'mcp-client-install-opencode',
@@ -168,11 +170,12 @@ function refusal(reason, message, databasePath, source, server, safety, targetPa
168
170
  manualTest: manualTestGuidance,
169
171
  installsAgents: agentPlan.installsAgents,
170
172
  agentNames: agentPlan.agentNames,
173
+ overwriteVgxness,
171
174
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
172
175
  };
173
176
  }
174
177
  function refusalFromPlan(plan, databasePath, source, server) {
175
- return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan);
178
+ return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan, plan.overwriteVgxness);
176
179
  }
177
180
  function agentConflictRefusal(conflictingAgents, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
178
181
  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);
@@ -63,6 +63,8 @@ export function callVgxTool(call, services) {
63
63
  return toEnvelope(validated.tool, services.memory.closeSession(validated.input, memoryContext(validated.input.sessionId)));
64
64
  case 'vgxness_session_restore':
65
65
  return toEnvelope(validated.tool, services.memory.restoreSession(validated.input));
66
+ case 'vgxness_context_cockpit':
67
+ return toEnvelope(validated.tool, services.memory.getContextCockpit(validated.input));
66
68
  case 'vgxness_agent_resolve':
67
69
  return toEnvelope(validated.tool, services.agents.resolveAgents(validated.input));
68
70
  case 'vgxness_agent_activate':
@@ -1,5 +1,5 @@
1
1
  export const vgxnessOpenCodeDefaultAgent = 'vgxness-manager';
2
- export const vgxnessOpenCodePromptContractVersion = 4;
2
+ export const vgxnessOpenCodePromptContractVersion = 6;
3
3
  export const vgxnessOpenCodeInstructionsPath = 'AGENTS.md';
4
4
  export const vgxnessOpenCodeSddSubagents = [
5
5
  'vgxness-sdd-explore',
@@ -65,7 +65,7 @@ export function createOpenCodeDefaultAgentConfig() {
65
65
  }
66
66
  function createSddSubagentPrompt(name) {
67
67
  const phase = name.replace('vgxness-sdd-', '');
68
- const common = `You are the VGXNESS SDD ${phase} executor, not the orchestrator. Do this phase's work yourself. Do NOT delegate, do NOT call task/delegate, and do NOT launch sub-agents. Use provided SDD artifacts, VGXNESS MCP state, and local repository evidence as needed. Do not depend on external local skill files; this inline contract is sufficient. Preserve unrelated user work, keep output concise, and report evidence.`;
68
+ const common = `You are the VGXNESS SDD ${phase} executor, not the orchestrator. Do this phase's work yourself. Do NOT delegate, do NOT call task/delegate, and do NOT launch sub-agents. Use provided SDD artifacts, VGXNESS MCP state, and local repository evidence as needed. Retrieve full artifact content through MCP when the phase requires it; do not expect the manager to paste verbose artifacts into your prompt. Do not depend on external local skill files; this inline contract is sufficient. Preserve unrelated user work, keep output concise, and report evidence.`;
69
69
  const contracts = {
70
70
  'vgxness-sdd-explore': 'Investigate codebase context, constraints, options, and risks. Do not implement code changes. Return findings and recommended next artifacts.',
71
71
  'vgxness-sdd-propose': 'Create or refine a focused SDD proposal from exploration evidence. Do not implement code changes. Include scope, tradeoffs, non-goals, and acceptance direction.',
@@ -102,8 +102,8 @@ Coach while coordinating: teach briefly when helpful, explain practical tradeoff
102
102
  Normal SDD progression happens inside OpenCode through conversation, VGXNESS MCP, and hidden SDD subagents. Do not tell users to run terminal SDD phase commands for daily flow. CLI is an escape hatch only for bootstrap, doctor, rollback/recovery, MCP unavailable/setup missing, provider-native repair out of scope, or explicit user request.
103
103
 
104
104
  ## MCP playbook
105
- - For starting, resuming, or recovering context: \`vgxness_session_restore\` with project + workspace before inferring state. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with current session id and actor \`manager\`; if no id, do not invent one, say so and include summary in final response.
106
- - SDD artifacts: list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; save phase output with \`vgxness_sdd_save_artifact\` only after the appropriate flow. SDD artifacts are not generic memory.
105
+ - For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\` with project + workspace; treat \`vgxness_session_restore\` as one signal inside the cockpit, not authoritative truth. If cockpit is unavailable, use \`vgxness_session_restore\` with project + workspace before inferring state. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with current session id and actor \`manager\`; if no id, do not invent one, say so and include summary in final response.
106
+ - SDD artifacts: list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; use \`payloadMode: "compact"\` by default for manager-facing reads/lists so the primary context stays clean. Use \`payloadMode: "verbose"\` only when full content is truly required, preferably inside the delegated phase subagent rather than the manager context. Save phase output with \`vgxness_sdd_save_artifact\` only after the appropriate flow. SDD artifacts are not generic memory.
107
107
  - Acceptance/readiness: confirm explicit human acceptance, use \`vgxness_sdd_accept_artifact\` only for explicit human acceptance, then \`vgxness_sdd_get_readiness\`/readiness tools before reporting state. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, and audit warnings before risky/ambiguous transitions and in apply/verify summaries.
108
108
  - Memory: call \`vgxness_memory_search\`/\`vgxness_memory_get\` when prior work or unclear project context is referenced; 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.
109
109
  - Agents/profile/payloads: resolve exact phase with \`vgxness_agent_resolve\`. \`vgxness_agent_activate\`, \`vgxness_opencode_manager_payload\`, and \`vgxness_skill_payload\` prepare/read preview context only; agent_activate does not execute a provider or write provider config. Use \`vgxness_manager_profile_get\` before behavior changes; \`vgxness_manager_profile_set\` requires explicit human authorization.
@@ -1,3 +1,4 @@
1
+ import { vgxnessOpenCodePromptContractVersion } from './opencode-default-agent-config.js';
1
2
  export const PROVIDER_HEALTH_SAFETY = {
2
3
  readOnly: true,
3
4
  writesProviderConfig: false,
@@ -29,6 +30,7 @@ export const REQUIRED_PROVIDER_NATIVE_MCP_TOOLS = [
29
30
  'vgxness_memory_search',
30
31
  'vgxness_memory_get',
31
32
  'vgxness_memory_save',
33
+ 'vgxness_context_cockpit',
32
34
  'vgxness_session_restore',
33
35
  'vgxness_session_close',
34
36
  ];
@@ -40,7 +42,7 @@ export function normalizeProviderHealthInput(input) {
40
42
  workspaceRoot: input.workspaceRoot?.trim() || process.cwd(),
41
43
  env: input.env ?? process.env,
42
44
  change: input.change?.trim() ?? '',
43
- expectedPromptContractVersion: input.expectedPromptContractVersion ?? 4,
45
+ expectedPromptContractVersion: input.expectedPromptContractVersion ?? vgxnessOpenCodePromptContractVersion,
44
46
  payloadMode: input.payloadMode ?? 'compact',
45
47
  };
46
48
  }
@@ -21,6 +21,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
21
21
  'vgxness_session_append_activity',
22
22
  'vgxness_session_close',
23
23
  'vgxness_session_restore',
24
+ 'vgxness_context_cockpit',
24
25
  'vgxness_agent_resolve',
25
26
  'vgxness_agent_activate',
26
27
  'vgxness_manager_profile_get',
@@ -60,6 +61,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
60
61
  'session_append_activity',
61
62
  'session_close',
62
63
  'session_restore',
64
+ 'context_cockpit',
63
65
  'agent_resolve',
64
66
  'agent_activate',
65
67
  'manager_profile_get',
@@ -257,6 +259,13 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
257
259
  directory: z.string().min(1).optional(),
258
260
  })
259
261
  .passthrough(),
262
+ vgxness_context_cockpit: z
263
+ .object({
264
+ project: z.string().min(1),
265
+ directory: z.string().min(1).optional(),
266
+ limit: z.number().int().min(1).max(100).optional(),
267
+ })
268
+ .passthrough(),
260
269
  vgxness_agent_resolve: z
261
270
  .object({
262
271
  project: z.string().min(1).optional(),
@@ -59,6 +59,8 @@ function descriptionForTool(publicToolName) {
59
59
  return 'Read-only run resume advisory inspect; plan-only and does not execute resume logic, invoke providers, write provider config, mutate retry/abandon/attempt state, or reconstruct sandboxes, worktrees, sessions, or transcripts.';
60
60
  if (publicToolName === 'run_resume_gate')
61
61
  return 'Read-only run resume gate advisory; plan-only and does not execute resume logic, invoke providers, write provider config, admit retries, abandon or mutate attempts, or reconstruct sandboxes, worktrees, sessions, or transcripts.';
62
+ if (publicToolName === 'context_cockpit')
63
+ return 'Read-only context cockpit for start/resume/recovery; returns latest restorable session plus bounded memory previews without traces, provider config writes, repository writes, runs, artifacts, or session mutations.';
62
64
  if (publicToolName === 'sdd_cockpit')
63
65
  return 'Read-only SDD cockpit summary with next decision, explicit acceptance state, metadata-only artifact summaries, and aggregate blockers.';
64
66
  const toolName = toInternalVgxMcpToolName(publicToolName);
@@ -66,6 +66,8 @@ export function validateVgxMcpToolCall(call) {
66
66
  return validationSuccess(tool.value, validateSessionCloseInput(input, tool.value));
67
67
  case 'vgxness_session_restore':
68
68
  return validationSuccess(tool.value, validateSessionRestoreInput(input, tool.value));
69
+ case 'vgxness_context_cockpit':
70
+ return validationSuccess(tool.value, validateContextCockpitInput(input, tool.value));
69
71
  case 'vgxness_agent_resolve':
70
72
  return validationSuccess(tool.value, validateAgentResolveInput(input, tool.value));
71
73
  case 'vgxness_agent_activate':
@@ -234,6 +236,26 @@ function validateSddCockpitInput(input, tool) {
234
236
  return record;
235
237
  return readProjectAndChange(record.value, tool);
236
238
  }
239
+ function validateContextCockpitInput(input, tool) {
240
+ const record = inputRecord(input, tool, ['project', 'directory', 'limit']);
241
+ if (!record.ok)
242
+ return record;
243
+ const project = readNonEmptyString(record.value, 'project', tool);
244
+ if (!project.ok)
245
+ return project;
246
+ const result = { project: project.value };
247
+ const directory = readOptionalNonEmptyString(record.value, 'directory', tool);
248
+ if (!directory.ok)
249
+ return directory;
250
+ if (directory.value !== undefined)
251
+ result.directory = directory.value;
252
+ if (record.value.limit !== undefined) {
253
+ if (typeof record.value.limit !== 'number' || !Number.isSafeInteger(record.value.limit) || record.value.limit < 1 || record.value.limit > 100)
254
+ return validationFailure('limit must be an integer between 1 and 100', tool);
255
+ result.limit = record.value.limit;
256
+ }
257
+ return { ok: true, value: result };
258
+ }
237
259
  function readProjectAndChange(record, tool) {
238
260
  const project = readNonEmptyString(record, 'project', tool);
239
261
  if (!project.ok)
@@ -50,6 +50,54 @@ export class MemoryService {
50
50
  const result = this.observations.searchPreviews(filters);
51
51
  return this.record(result, context, { operation: 'observation.search', targetType: 'observation', topicKey: filters.topicKey });
52
52
  }
53
+ /** Read memory previews without writing provenance traces. */
54
+ searchObservationPreviewsNoTrace(filters) {
55
+ return this.observations.searchPreviews(filters);
56
+ }
57
+ /** Read compact current context without writing traces or mutating durable state. */
58
+ getContextCockpit(input) {
59
+ const limit = normalizeContextLimit(input.limit);
60
+ const memories = this.searchObservationPreviewsNoTrace({ project: input.project, limit });
61
+ if (!memories.ok)
62
+ return { ok: false, error: memories.error };
63
+ const session = this.restoreSession(input.directory === undefined ? { project: input.project } : { project: input.project, directory: input.directory });
64
+ if (!session.ok && session.error.code !== 'not_found')
65
+ return { ok: false, error: session.error };
66
+ const latestRestorableSession = session.ok ? session.value : undefined;
67
+ const newestMemoryUpdatedAt = memories.value[0]?.updatedAt;
68
+ const latestSessionEndedAt = latestRestorableSession?.endedAt;
69
+ const stale = latestRestorableSession === undefined || (newestMemoryUpdatedAt !== undefined && latestSessionEndedAt !== undefined && newestMemoryUpdatedAt > latestSessionEndedAt);
70
+ const staleness = {
71
+ stale,
72
+ reason: latestRestorableSession === undefined ? 'no-restorable-session' : stale ? 'memory-newer-than-session' : 'current',
73
+ ...(newestMemoryUpdatedAt === undefined ? {} : { newestMemoryUpdatedAt }),
74
+ ...(latestSessionEndedAt === undefined ? {} : { latestSessionEndedAt }),
75
+ };
76
+ return {
77
+ ok: true,
78
+ value: {
79
+ version: 1,
80
+ kind: 'context-cockpit',
81
+ project: input.project,
82
+ ...(input.directory === undefined ? {} : { directory: input.directory }),
83
+ ...(latestRestorableSession === undefined ? {} : { latestRestorableSession }),
84
+ memoryPreviews: memories.value,
85
+ staleness,
86
+ optionalSectionsOmitted: ['sdd', 'runs'],
87
+ notes: ['SDD and run summaries are omitted because safe no-trace metadata-only aggregation is not part of this cockpit path yet.'],
88
+ safety: {
89
+ readOnly: true,
90
+ recordsTraces: false,
91
+ mutatesSessions: false,
92
+ mutatesMemories: false,
93
+ mutatesArtifacts: false,
94
+ mutatesRuns: false,
95
+ writesProviderConfig: false,
96
+ mutatesRepository: false,
97
+ },
98
+ },
99
+ };
100
+ }
53
101
  listObservationRevisions(id, context) {
54
102
  const result = this.observations.listRevisions(id);
55
103
  return this.record(result, context, { operation: 'observation.revisions.list', targetType: 'observation', targetId: id });
@@ -140,3 +188,10 @@ class MemoryServiceTransactionRollback extends Error {
140
188
  super('Memory service transaction rolled back');
141
189
  }
142
190
  }
191
+ function normalizeContextLimit(limit) {
192
+ if (limit === undefined)
193
+ return 5;
194
+ if (!Number.isFinite(limit))
195
+ return 5;
196
+ return Math.max(1, Math.min(100, Math.trunc(limit)));
197
+ }
@@ -39,7 +39,7 @@ export class SessionRepository {
39
39
  AND summary IS NOT NULL
40
40
  AND TRIM(summary) <> ''
41
41
  AND (@directory IS NULL OR directory = @directory)
42
- ORDER BY ended_at DESC
42
+ ORDER BY ended_at DESC, started_at DESC, id DESC
43
43
  LIMIT 1
44
44
  `)
45
45
  .get({ project: input.project, directory: input.directory ?? null });
@@ -49,9 +49,10 @@ export const openCodeSetupAdapter = {
49
49
  databasePath,
50
50
  ...(context.databasePathSource !== undefined ? { databasePathSource: context.databasePathSource } : {}),
51
51
  ...(context.env !== undefined ? { env: context.env } : {}),
52
+ ...(context.overwriteVgxness !== undefined ? { overwriteVgxness: context.overwriteVgxness } : {}),
52
53
  });
53
54
  const targetPath = 'targetPath' in contract ? contract.targetPath : undefined;
54
- const actions = contract.status === 'would_install' ? [externalInstallAction(contract.scope, targetPath)] : [];
55
+ const actions = contract.status === 'would_install' ? [externalInstallAction(contract.scope, targetPath, contract.overwriteVgxness)] : [];
55
56
  return {
56
57
  providerId: 'opencode',
57
58
  status: contract.status === 'would_install' ? 'available' : 'blocked',
@@ -59,7 +60,9 @@ export const openCodeSetupAdapter = {
59
60
  mutating: false,
60
61
  writesProviderConfig: false,
61
62
  summary: contract.status === 'would_install'
62
- ? `OpenCode ${contract.action} plan is available for external application; confirmed merges create managed VGXNESS backups.`
63
+ ? contract.overwriteVgxness
64
+ ? `OpenCode ${contract.action} reinstall plan is available; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, and creates managed backups for existing targets.`
65
+ : `OpenCode ${contract.action} plan is available for external application; confirmed merges create managed VGXNESS backups.`
63
66
  : contract.message,
64
67
  ...(targetPath !== undefined ? { targetPath } : {}),
65
68
  warnings: contract.warnings,
@@ -105,7 +108,11 @@ function openCodePlanPreview(visibility, plan) {
105
108
  if (!isOpenCodeInstallContract(source))
106
109
  return undefined;
107
110
  if (source.status === 'would_install') {
108
- const action = source.action === 'create' ? 'Install OpenCode MCP config' : 'Update OpenCode MCP config';
111
+ const action = source.overwriteVgxness
112
+ ? 'Reinstall VGXNESS OpenCode entries'
113
+ : source.action === 'create'
114
+ ? 'Install OpenCode MCP config'
115
+ : 'Update OpenCode MCP config';
109
116
  return {
110
117
  kind: source.action === 'create' ? 'install' : 'update',
111
118
  action,
@@ -115,7 +122,7 @@ function openCodePlanPreview(visibility, plan) {
115
122
  targetPath: source.targetPath,
116
123
  backupRequired: source.backupRequired,
117
124
  confirmationRequired: Boolean(plan?.actions.some((candidate) => candidate.safety.requiresExplicitConfirmation)),
118
- risks: source.action === 'create' ? createInstallRisks() : mergeInstallRisks(),
125
+ risks: source.overwriteVgxness ? overwriteInstallRisks(source.installsAgents) : source.action === 'create' ? createInstallRisks() : mergeInstallRisks(),
119
126
  warnings: source.warnings,
120
127
  };
121
128
  }
@@ -140,6 +147,13 @@ function mergeInstallRisks() {
140
147
  'OpenCode config discovery may still be affected by target precedence or malformed local config.',
141
148
  ];
142
149
  }
150
+ function overwriteInstallRisks(installsAgents) {
151
+ return [
152
+ `Existing VGXNESS-managed OpenCode entries will be replaced (${installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}).`,
153
+ 'Unrelated OpenCode config, unrelated agents, and unrelated MCP servers are preserved; existing targets still receive managed backups before writes.',
154
+ 'OpenCode must be restarted after external installation before it discovers the updated vgxness MCP server.',
155
+ ];
156
+ }
143
157
  function refusedRepairRisks(message) {
144
158
  return [
145
159
  `Install planner refused automatic repair: ${message}`,
@@ -155,13 +169,15 @@ function isOpenCodeInstallContract(value) {
155
169
  function databasePathOrPlaceholder(context) {
156
170
  return context.databasePath ?? '<database-path>';
157
171
  }
158
- function externalInstallAction(scope, targetPath) {
172
+ function externalInstallAction(scope, targetPath, overwriteVgxness = false) {
159
173
  return {
160
174
  id: `opencode-mcp-install-${scope}`,
161
175
  label: `Copy external OpenCode ${scope === 'user' ? 'user/global' : 'project'} install command`,
162
176
  kind: 'copy-command',
163
- command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes'],
164
- description: 'Copy and run this outside the TUI only after reviewing the read-only plan.',
177
+ command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes', ...(overwriteVgxness ? ['--overwrite-vgxness'] : [])],
178
+ description: overwriteVgxness
179
+ ? 'Copy and run this outside the TUI only after reviewing that VGXNESS entries will be overwritten and unrelated config preserved.'
180
+ : 'Copy and run this outside the TUI only after reviewing the read-only plan.',
165
181
  safety: externalProviderWriteSafety(targetPath),
166
182
  };
167
183
  }
@@ -54,6 +54,7 @@ export function createSetupPlan(input) {
54
54
  scope,
55
55
  env: input.env,
56
56
  mcpOnly: installMode === 'mcp-only',
57
+ ...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
57
58
  });
58
59
  return { ok: true, value: setupPlanFromOpenCode({ ...common, provider: 'opencode', installMode, scope, opencode }) };
59
60
  }
@@ -70,6 +71,7 @@ function setupPlanFromOpenCode(input) {
70
71
  ...(input.opencode.targetPath === undefined ? {} : { targetPath: input.opencode.targetPath }),
71
72
  installsAgents: input.opencode.installsAgents,
72
73
  agentNames: input.opencode.agentNames,
74
+ ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
73
75
  },
74
76
  actions: [],
75
77
  conflicts: [
@@ -96,11 +98,12 @@ function setupPlanFromOpenCode(input) {
96
98
  mcpServer: input.opencode.server,
97
99
  installsAgents: input.opencode.installsAgents,
98
100
  agentNames: input.opencode.agentNames,
101
+ ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
99
102
  },
100
103
  actions: [
101
104
  {
102
105
  id: `opencode-${input.opencode.action}`,
103
- description: `${input.opencode.action === 'create' ? 'Create' : 'Merge'} OpenCode config with mcp.vgxness using vgxness mcp start${input.installMode === 'mcp-plus-agents' ? ' and manager/SDD agents' : ''}.`,
106
+ 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' : ''}.`,
104
107
  mutating: false,
105
108
  targetPath: input.opencode.targetPath,
106
109
  backupRequired: input.opencode.backupRequired,
@@ -111,7 +114,9 @@ function setupPlanFromOpenCode(input) {
111
114
  ? [
112
115
  {
113
116
  targetPath: input.opencode.targetPath,
114
- reason: 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before merge.',
117
+ reason: input.opencode.overwriteVgxness
118
+ ? 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before VGXNESS entries are overwritten/reinstalled.'
119
+ : 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before merge.',
115
120
  },
116
121
  ]
117
122
  : [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
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": {