vgxness 1.9.1 → 1.9.2

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/README.md CHANGED
@@ -8,6 +8,8 @@ This package is proprietary software. The npm package ships inspectable JavaScri
8
8
 
9
9
  OpenCode is the primary supported provider. Other providers remain preview/manual only. Provider config writes require explicit CLI confirmation.
10
10
 
11
+ VGXNESS v1.9.1 has a canonical project health audit with validated evidence: 38 MCP tools, 106 test files, and the official Bun validation path passing, including package evidence with `releaseReadiness: pass`. See [Project health audit v1.9.1](./docs/project-health-audit-v1.9.1.md) for the versioned matrix and safety taxonomy.
12
+
11
13
  ## Requirements
12
14
 
13
15
  - Bun >= 1.3.14 for the installed `vgxness`/`vgx` CLI/MCP runtime and repository verification
@@ -25,6 +27,7 @@ bun install --frozen-lockfile
25
27
  bun run check:bun-lock
26
28
  bun run verify:typecheck
27
29
  bun run verify:test
30
+ bun run verify:test:bun-storage
28
31
  bun run verify:bun-sqlite
29
32
  bun run verify:package
30
33
  bun run package:bun:evidence -- --require-pass
@@ -86,7 +89,7 @@ Release-candidate checklist, still non-publishing:
86
89
 
87
90
  1. `bun install --frozen-lockfile` completes on a clean checkout.
88
91
  2. `bun run check:bun-lock` passes.
89
- 3. `bun run verify:typecheck`, `bun run verify:test`, and `bun run verify:bun-sqlite` pass.
92
+ 3. `bun run verify:typecheck`, `bun run verify:test`, `bun run verify:test:bun-storage`, and `bun run verify:bun-sqlite` pass.
90
93
  4. `bun run package:bun:evidence -- --require-pass` passes on supported CI OSes.
91
94
  5. JSON evidence shows `status: pass`, artifact/install smoke pass, no retired bridge scripts, no `package-lock.json`, `publicationAttempted: false`, and `releaseReadiness.status: pass`.
92
95
  6. Version, release notes, proprietary license boundary, and rollback criteria are reviewed.
@@ -156,7 +159,7 @@ Apply only after reviewing the plan:
156
159
  vgxness setup apply --yes
157
160
  ```
158
161
 
159
- `vgxness setup plan` and `vgxness setup status` are human-readable and read-only by default. `vgxness doctor`, `vgxness sdd status`, `vgxness sdd next`, and `vgxness sdd accept-artifact` are also human-readable by default. Pass `--json` when you need parseable automation output. `vgxness setup apply --yes` is the explicit provider-config write path.
162
+ `vgxness setup plan` and `vgxness setup status` are human-readable and do not write provider config by default; local VGXNESS store initialization may occur when a command needs the selected SQLite store. `vgxness doctor`, `vgxness sdd status`, `vgxness sdd next`, and `vgxness sdd accept-artifact` are also human-readable by default. Pass `--json` when you need parseable automation output. `vgxness setup apply --yes` is the explicit provider-config write path.
160
163
 
161
164
  ## Code runtime (`vgxness code`)
162
165
 
@@ -173,7 +176,7 @@ Edits, shell, network, git mutations, SDD persistence, and memory saves route th
173
176
 
174
177
  ## Safety model
175
178
 
176
- - Preview, status, and plan commands are read-only.
179
+ - Preview, status, and plan commands do not write provider config; local VGXNESS store initialization may occur where the command needs SQLite-backed state.
177
180
  - Provider config writes require explicit `--yes` confirmation.
178
181
  - Setup/status TUI surfaces are preview-oriented; run copied commands explicitly when you choose to act.
179
182
  - SDD artifacts are SQLite-backed through VGXNESS services. Do not create or write `openspec/`.
@@ -236,6 +239,7 @@ bun install --frozen-lockfile
236
239
  bun run check:bun-lock
237
240
  bun run verify:typecheck
238
241
  bun run verify:test
242
+ bun run verify:test:bun-storage
239
243
  bun run verify:bun-sqlite
240
244
  bun run package:bun:evidence -- --require-pass
241
245
  ```
@@ -255,6 +259,7 @@ Remove any OpenCode config entries and local/global VGXNESS data manually if you
255
259
  ## More docs
256
260
 
257
261
  - [CLI reference](./docs/cli.md)
262
+ - [Project health audit v1.9.1](./docs/project-health-audit-v1.9.1.md)
258
263
  - [Architecture](./docs/architecture.md)
259
264
  - [Code runtime](./docs/code-runtime.md)
260
265
  - [MCP tools](./docs/mcp.md)
@@ -98,14 +98,23 @@ export class AgentActivationService {
98
98
  return this.dependencies.agents.getAgentByName(input.project, input.scope, defaultAgentName);
99
99
  }
100
100
  failAfterRun(runId, code, message) {
101
- this.dependencies.runs.appendEvent({
101
+ const nestedFailures = [];
102
+ const eventResult = this.dependencies.runs.appendEvent({
102
103
  runId,
103
104
  kind: 'timeline',
104
105
  title: 'Agent activation failed',
105
- payload: { error: { code: String(code), message }, nextSafeActionReturned: false },
106
+ payload: toJson({ error: { code: String(code), message }, nextSafeActionReturned: false }),
106
107
  });
107
- this.dependencies.runs.updateFinalStatus({ runId, status: 'failed', outcome: 'failure', outcomeReason: message });
108
- return { ok: false, error: { code, message } };
108
+ if (!eventResult.ok) {
109
+ nestedFailures.push({ step: 'append-event', code: String(eventResult.error.code), message: eventResult.error.message });
110
+ }
111
+ const finalStatusResult = this.dependencies.runs.updateFinalStatus({ runId, status: 'failed', outcome: 'failure', outcomeReason: message });
112
+ if (!finalStatusResult.ok) {
113
+ nestedFailures.push({ step: 'update-final-status', code: String(finalStatusResult.error.code), message: finalStatusResult.error.message });
114
+ }
115
+ return nestedFailures.length === 0
116
+ ? { ok: false, error: { code, message } }
117
+ : { ok: false, error: { code, message, cause: { nestedFailures } } };
109
118
  }
110
119
  }
111
120
  function validateActivationInput(input) {
@@ -32,8 +32,14 @@ export class AgentRegistryService {
32
32
  this.database.close();
33
33
  }
34
34
  listAgentDefinitions(input) {
35
- void input;
36
- const summaries = this.agents.list();
35
+ const filters = {};
36
+ if (input.project !== undefined)
37
+ filters.project = input.project;
38
+ if (input.scope !== undefined)
39
+ filters.scope = input.scope;
40
+ if (input.mode !== undefined)
41
+ filters.mode = input.mode;
42
+ const summaries = this.agents.list(filters);
37
43
  if (!summaries.ok)
38
44
  return summaries;
39
45
  const definitions = [];
@@ -0,0 +1,231 @@
1
+ import { canonicalAgentManifest, canonicalPromptContractVersion, canonicalSddSubagentNames } from './canonical-agent-manifest.js';
2
+ import { projectCanonicalAgentManifestToSeed } from './canonical-agent-projection.js';
3
+ export class AgentSeedUpgradeService {
4
+ agents;
5
+ history;
6
+ database;
7
+ constructor(dependencies) {
8
+ this.agents = dependencies.agents;
9
+ this.history = dependencies.history;
10
+ this.database = dependencies.database;
11
+ }
12
+ upgrade(input) {
13
+ if (!input.project.trim())
14
+ return validationFailure('Agent seed upgrade project is required');
15
+ if (input.scope !== 'project' && input.scope !== 'personal')
16
+ return validationFailure('Agent seed upgrade scope is invalid');
17
+ const seed = projectCanonicalAgentManifestToSeed();
18
+ const canonicalByName = new Map();
19
+ for (const agent of seed.agents)
20
+ canonicalByName.set(agent.name, agent);
21
+ for (const subagent of seed.subagents)
22
+ canonicalByName.set(subagent.name, subagent);
23
+ const source = input.source ?? 'boot-upgrade';
24
+ const summary = {
25
+ created: [],
26
+ upgraded: [],
27
+ noop: [],
28
+ overwritten: [],
29
+ errors: [],
30
+ skipped: false,
31
+ };
32
+ const expected = canonicalPromptContractVersion;
33
+ const transaction = this.database.transaction(() => {
34
+ for (const [name, definition] of canonicalByName) {
35
+ const outcome = this.upgradeOne(input.project, input.scope, name, definition, source, expected);
36
+ if (outcome.ok) {
37
+ switch (outcome.value.outcome) {
38
+ case 'created':
39
+ summary.created.push(name);
40
+ break;
41
+ case 'upgraded':
42
+ summary.upgraded.push(name);
43
+ break;
44
+ case 'overwrote-custom-instructions':
45
+ summary.overwritten.push(name);
46
+ break;
47
+ case 'noop':
48
+ summary.noop.push(name);
49
+ break;
50
+ }
51
+ }
52
+ else {
53
+ const failureOutcome = outcome.error.code === 'validation_failed' ? 'validation_failed' : 'db_error';
54
+ summary.errors.push({ agentName: name, outcome: failureOutcome, reason: outcome.error.message });
55
+ }
56
+ }
57
+ return summary;
58
+ });
59
+ if (!transaction.ok) {
60
+ return { ok: false, error: transaction.error };
61
+ }
62
+ return transaction;
63
+ }
64
+ hasDrift(input) {
65
+ const expected = input.expectedVersion ?? canonicalPromptContractVersion;
66
+ try {
67
+ const canonicalNames = [canonicalAgentManifest.defaultAgentName, ...canonicalSddSubagentNames];
68
+ const placeholders = canonicalNames.map(() => '?').join(', ');
69
+ const row = this.database.connection
70
+ .prepare(`
71
+ SELECT COUNT(*) AS stale_count
72
+ FROM agents
73
+ WHERE project = ? AND scope = ? AND name IN (${placeholders})
74
+ AND json_extract(adapters_json, '$.opencode.config.options.vgxnessPromptContractVersion') <> ?
75
+ `)
76
+ .get(input.project, input.scope, ...canonicalNames, expected);
77
+ return ok((row?.stale_count ?? 0) > 0);
78
+ }
79
+ catch (cause) {
80
+ return fail('Failed to detect agent seed drift', cause);
81
+ }
82
+ }
83
+ upgradeOne(project, scope, name, definition, source, expected) {
84
+ const existing = this.agents.getByName(project, scope, name);
85
+ const fromVersion = existing.ok ? readPromptContractVersion(existing.value) : null;
86
+ if (existing.ok && fromVersion === expected && isByteEqualToCanonical(existing.value, definition)) {
87
+ return ok({ outcome: 'noop', fromVersion: expected });
88
+ }
89
+ const parentAgentId = 'parentAgentName' in definition ? this.resolveParentId(project, scope, definition.parentAgentName) : undefined;
90
+ if ('parentAgentName' in definition && parentAgentId === undefined) {
91
+ return validationFailure(`Unresolved parentAgentName: ${definition.parentAgentName}`);
92
+ }
93
+ const registerInput = toRegisterInput(project, scope, definition, parentAgentId);
94
+ const registered = this.agents.register(registerInput);
95
+ if (!registered.ok) {
96
+ this.appendHistory({
97
+ project,
98
+ scope,
99
+ agentName: name,
100
+ fromVersion,
101
+ toVersion: expected,
102
+ outcome: registered.error.code === 'validation_failed' ? 'validation_failed' : 'db_error',
103
+ reason: registered.error.message,
104
+ source,
105
+ });
106
+ return registered;
107
+ }
108
+ let outcome;
109
+ if (!existing.ok) {
110
+ outcome = 'created';
111
+ }
112
+ else if (fromVersion !== null && fromVersion !== expected) {
113
+ outcome = 'upgraded';
114
+ }
115
+ else {
116
+ outcome = 'overwrote-custom-instructions';
117
+ }
118
+ this.appendHistory({
119
+ project,
120
+ scope,
121
+ agentName: name,
122
+ fromVersion,
123
+ toVersion: expected,
124
+ outcome,
125
+ reason: outcomeReason(outcome, fromVersion, expected, existing.ok),
126
+ source,
127
+ });
128
+ return ok({ outcome, fromVersion });
129
+ }
130
+ resolveParentId(project, scope, parentAgentName) {
131
+ const parent = this.agents.getByName(project, scope, parentAgentName);
132
+ if (!parent.ok || parent.value.mode !== 'agent')
133
+ return undefined;
134
+ return parent.value.id;
135
+ }
136
+ appendHistory(input) {
137
+ this.history.append({
138
+ project: input.project,
139
+ scope: input.scope,
140
+ agentName: input.agentName,
141
+ fromVersion: input.fromVersion,
142
+ toVersion: input.toVersion,
143
+ outcome: input.outcome,
144
+ reason: input.reason,
145
+ source: input.source,
146
+ });
147
+ }
148
+ }
149
+ function toRegisterInput(project, scope, definition, parentAgentId) {
150
+ const input = {
151
+ project,
152
+ scope,
153
+ mode: 'parentAgentName' in definition ? 'subagent' : 'agent',
154
+ name: definition.name,
155
+ description: definition.description,
156
+ instructions: definition.instructions,
157
+ };
158
+ if (definition.capabilities !== undefined)
159
+ input.capabilities = definition.capabilities;
160
+ if (definition.permissions !== undefined)
161
+ input.permissions = definition.permissions;
162
+ if (definition.memory !== undefined)
163
+ input.memory = definition.memory;
164
+ if (definition.workflows !== undefined)
165
+ input.workflows = definition.workflows;
166
+ if (definition.skills !== undefined)
167
+ input.skills = definition.skills;
168
+ if (definition.adapters !== undefined)
169
+ input.adapters = definition.adapters;
170
+ if (parentAgentId !== undefined)
171
+ input.parentAgentId = parentAgentId;
172
+ return input;
173
+ }
174
+ function isByteEqualToCanonical(existing, definition) {
175
+ if (existing.description !== definition.description)
176
+ return false;
177
+ if (existing.instructions.value !== definition.instructions.value)
178
+ return false;
179
+ if (!adaptersEqual(existing.adapters, definition.adapters ?? {}))
180
+ return false;
181
+ if (!arraysEqual(existing.capabilities, definition.capabilities ?? []))
182
+ return false;
183
+ if (!arraysEqual(existing.workflows, definition.workflows ?? []))
184
+ return false;
185
+ if (!arraysEqual(existing.skills, definition.skills ?? []))
186
+ return false;
187
+ return true;
188
+ }
189
+ function adaptersEqual(a, b) {
190
+ return JSON.stringify(a ?? {}) === JSON.stringify(b ?? {});
191
+ }
192
+ function arraysEqual(a, b) {
193
+ if (a.length !== b.length)
194
+ return false;
195
+ for (let i = 0; i < a.length; i += 1) {
196
+ if (a[i] !== b[i])
197
+ return false;
198
+ }
199
+ return true;
200
+ }
201
+ function readPromptContractVersion(agent) {
202
+ const opencode = agent.adapters?.opencode;
203
+ if (opencode === undefined)
204
+ return null;
205
+ const options = opencode.config?.options;
206
+ const value = options?.vgxnessPromptContractVersion;
207
+ if (typeof value !== 'number' || !Number.isInteger(value))
208
+ return null;
209
+ return value;
210
+ }
211
+ function outcomeReason(outcome, fromVersion, toVersion, existed) {
212
+ if (outcome === 'created')
213
+ return `created canonical row at v${toVersion}`;
214
+ if (outcome === 'upgraded')
215
+ return `upgraded v${fromVersion ?? 'unknown'} -> v${toVersion}`;
216
+ if (outcome === 'overwrote-custom-instructions')
217
+ return `overwrote customized row (was v${fromVersion ?? 'unknown'}) with v${toVersion} canonical`;
218
+ return existed ? `noop at v${toVersion}` : `noop at v${toVersion}`;
219
+ }
220
+ function ok(value) {
221
+ return { ok: true, value };
222
+ }
223
+ function validationFailure(message) {
224
+ return { ok: false, error: { code: 'validation_failed', message } };
225
+ }
226
+ function fail(message, cause) {
227
+ const error = { code: 'validation_failed', message };
228
+ if (cause !== undefined)
229
+ error.cause = cause;
230
+ return { ok: false, error };
231
+ }
@@ -0,0 +1,59 @@
1
+ import { AgentSeedUpgradeService } from './agent-seed-upgrade-service.js';
2
+ import { AgentRepository } from './repositories/agents.js';
3
+ import { AgentSeedHistoryRepository } from './repositories/agent-seed-history.js';
4
+ import { canonicalAgentManifest } from './canonical-agent-manifest.js';
5
+ const skipEnvVar = 'VGXNESS_SKIP_AGENT_SEED_AUTO_UPGRADE';
6
+ export function runBootAgentSeedUpgrade(database, env = process.env, project = canonicalAgentManifest.project, scope = canonicalAgentManifest.scope) {
7
+ if (isOptOut(env)) {
8
+ return { ok: true, skipped: true, created: 0, upgraded: 0, overwritten: 0, noop: 0, errors: 0 };
9
+ }
10
+ try {
11
+ const service = new AgentSeedUpgradeService({
12
+ agents: new AgentRepository(database),
13
+ history: new AgentSeedHistoryRepository(database),
14
+ database,
15
+ });
16
+ const result = service.upgrade({ project, scope, source: 'boot-upgrade' });
17
+ if (!result.ok) {
18
+ const message = `boot-upgrade failed: ${result.error.message} (code=${result.error.code})`;
19
+ process.stderr.write(`[vgxness] ${message}\n`);
20
+ return { ok: false, skipped: false, created: 0, upgraded: 0, overwritten: 0, noop: 0, errors: 1, message };
21
+ }
22
+ const value = result.value;
23
+ const touched = value.created.length + value.upgraded.length + value.overwritten.length + value.errors.length;
24
+ if (touched > 0) {
25
+ const parts = [];
26
+ if (value.created.length > 0)
27
+ parts.push(`created ${value.created.length}`);
28
+ if (value.upgraded.length > 0)
29
+ parts.push(`upgraded ${value.upgraded.length}`);
30
+ if (value.overwritten.length > 0)
31
+ parts.push(`overwrote ${value.overwritten.length}`);
32
+ if (value.errors.length > 0)
33
+ parts.push(`errors ${value.errors.length}`);
34
+ process.stderr.write(`[vgxness] boot-upgrade: ${parts.join(', ')}\n`);
35
+ for (const error of value.errors) {
36
+ process.stderr.write(`[vgxness] boot-upgrade: ${error.agentName} ${error.outcome}: ${error.reason}\n`);
37
+ }
38
+ }
39
+ return {
40
+ ok: true,
41
+ skipped: false,
42
+ created: value.created.length,
43
+ upgraded: value.upgraded.length,
44
+ overwritten: value.overwritten.length,
45
+ noop: value.noop.length,
46
+ errors: value.errors.length,
47
+ };
48
+ }
49
+ catch (cause) {
50
+ const detail = cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
51
+ const message = `boot-upgrade threw: ${detail}`;
52
+ process.stderr.write(`[vgxness] ${message}\n`);
53
+ return { ok: false, skipped: false, created: 0, upgraded: 0, overwritten: 0, noop: 0, errors: 1, message };
54
+ }
55
+ }
56
+ function isOptOut(env) {
57
+ const value = env[skipEnvVar];
58
+ return value === '1' || value === 'true';
59
+ }
@@ -73,6 +73,13 @@ export function projectCanonicalAgentManifestToClaudeCode(manifest = canonicalAg
73
73
  }
74
74
  return { defaultAgent: canonicalDefaultAgentName, agents };
75
75
  }
76
+ export function withEffectiveManagerInstructions(projection, instructions) {
77
+ const trimmed = instructions?.trim() ?? '';
78
+ if (trimmed === '')
79
+ return projection;
80
+ const agents = projection.agents.map((agent) => agent.canonicalName === projection.defaultAgent ? { ...agent, instructions: trimmed } : agent);
81
+ return { defaultAgent: projection.defaultAgent, agents };
82
+ }
76
83
  export function projectCanonicalAgentManifestToClaudeProjectMemory(manifest = canonicalAgentManifest) {
77
84
  assertValidCanonicalManifest(manifest);
78
85
  return {
@@ -1,3 +1,5 @@
1
+ import { AgentRegistryService } from './agent-registry-service.js';
2
+ import { ManagerProfileOverlayRepository } from './repositories/manager-profile-overlays.js';
1
3
  export class ManagerProfileOverlayService {
2
4
  dependencies;
3
5
  constructor(dependencies) {
@@ -36,3 +38,15 @@ function ok(value) {
36
38
  function validationFailure(message) {
37
39
  return { ok: false, error: { code: 'validation_failed', message } };
38
40
  }
41
+ export function computeEffectiveManagerInstructions(database, project = 'vgxness', scope = 'project', managerName = 'vgxness-manager') {
42
+ const registry = new AgentRegistryService(database);
43
+ const overlays = new ManagerProfileOverlayRepository(database);
44
+ const service = new ManagerProfileOverlayService({ agents: registry, overlays });
45
+ const resolved = service.resolveEffectiveManager({ project, scope, managerName });
46
+ if (!resolved.ok || resolved.value.overlay === undefined)
47
+ return undefined;
48
+ const overlayInstructions = resolved.value.overlay.instructions.trim();
49
+ if (overlayInstructions === '')
50
+ return undefined;
51
+ return overlayInstructions;
52
+ }
@@ -0,0 +1,128 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class AgentSeedHistoryRepository {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ append(input) {
8
+ const validation = validate(input);
9
+ if (!validation.ok)
10
+ return validation;
11
+ try {
12
+ const id = randomUUID();
13
+ const appliedAt = new Date().toISOString();
14
+ this.db.connection
15
+ .prepare(`
16
+ INSERT INTO agent_seed_history(id, applied_at, from_version, to_version, agent_name, project, scope, outcome, reason, source)
17
+ VALUES (@id, @appliedAt, @fromVersion, @toVersion, @agentName, @project, @scope, @outcome, @reason, @source)
18
+ `)
19
+ .run({
20
+ id,
21
+ appliedAt,
22
+ fromVersion: input.fromVersion,
23
+ toVersion: input.toVersion,
24
+ agentName: input.agentName,
25
+ project: input.project,
26
+ scope: input.scope,
27
+ outcome: input.outcome,
28
+ reason: input.reason ?? null,
29
+ source: input.source,
30
+ });
31
+ const entry = {
32
+ id,
33
+ appliedAt,
34
+ fromVersion: input.fromVersion,
35
+ toVersion: input.toVersion,
36
+ agentName: input.agentName,
37
+ project: input.project,
38
+ scope: input.scope,
39
+ outcome: input.outcome,
40
+ reason: input.reason ?? null,
41
+ source: input.source,
42
+ };
43
+ return ok(entry);
44
+ }
45
+ catch (cause) {
46
+ return fail('Failed to append agent_seed_history entry', cause);
47
+ }
48
+ }
49
+ listRecent(filters = {}) {
50
+ try {
51
+ const where = [];
52
+ const parameters = {};
53
+ for (const [key, column] of [
54
+ ['project', 'project'],
55
+ ['scope', 'scope'],
56
+ ['agentName', 'agent_name'],
57
+ ]) {
58
+ const value = filters[key];
59
+ if (value !== undefined) {
60
+ where.push(`${column}=@${key}`);
61
+ parameters[key] = value;
62
+ }
63
+ }
64
+ const limit = Math.max(1, Math.min(filters.limit ?? 100, 1000));
65
+ const rows = this.db.connection
66
+ .prepare(`
67
+ SELECT * FROM agent_seed_history
68
+ ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
69
+ ORDER BY applied_at DESC
70
+ LIMIT ${limit}
71
+ `)
72
+ .all(parameters);
73
+ return ok(rows.map(map));
74
+ }
75
+ catch (cause) {
76
+ return fail('Failed to list agent_seed_history entries', cause);
77
+ }
78
+ }
79
+ }
80
+ function validate(input) {
81
+ if (!input.project.trim())
82
+ return validationFailure('agent_seed_history project is required');
83
+ if (input.scope !== 'project' && input.scope !== 'personal')
84
+ return validationFailure('agent_seed_history scope is invalid');
85
+ if (!input.agentName.trim())
86
+ return validationFailure('agent_seed_history agentName is required');
87
+ if (!Number.isInteger(input.toVersion) || input.toVersion <= 0)
88
+ return validationFailure('agent_seed_history toVersion must be a positive integer');
89
+ if (input.fromVersion !== null && (!Number.isInteger(input.fromVersion) || input.fromVersion < 0))
90
+ return validationFailure('agent_seed_history fromVersion must be null or non-negative integer');
91
+ if (!input.source.trim())
92
+ return validationFailure('agent_seed_history source is required');
93
+ if (input.outcome !== 'created' &&
94
+ input.outcome !== 'upgraded' &&
95
+ input.outcome !== 'noop' &&
96
+ input.outcome !== 'overwrote-custom-instructions' &&
97
+ input.outcome !== 'validation_failed' &&
98
+ input.outcome !== 'db_error') {
99
+ return validationFailure(`agent_seed_history outcome is invalid: ${input.outcome}`);
100
+ }
101
+ return ok(undefined);
102
+ }
103
+ function map(row) {
104
+ return {
105
+ id: String(row.id),
106
+ appliedAt: String(row.applied_at),
107
+ fromVersion: row.from_version === null || row.from_version === undefined ? null : Number(row.from_version),
108
+ toVersion: Number(row.to_version),
109
+ agentName: String(row.agent_name),
110
+ project: String(row.project),
111
+ scope: row.scope,
112
+ outcome: String(row.outcome),
113
+ reason: row.reason === null || row.reason === undefined ? null : String(row.reason),
114
+ source: String(row.source),
115
+ };
116
+ }
117
+ function ok(value) {
118
+ return { ok: true, value };
119
+ }
120
+ function validationFailure(message) {
121
+ return { ok: false, error: { code: 'validation_failed', message } };
122
+ }
123
+ function fail(message, cause) {
124
+ const error = { code: 'validation_failed', message };
125
+ if (cause !== undefined)
126
+ error.cause = cause;
127
+ return { ok: false, error };
128
+ }
@@ -6,7 +6,9 @@ import { createMcpClientSetupPreview, isMcpClientSetupProvider } from '../../mcp
6
6
  import { createMcpDoctorReport } from '../../mcp/doctor.js';
7
7
  import { createOpenCodeMcpVisibilityReport } from '../../mcp/opencode-visibility.js';
8
8
  import { resolveClaudeCodeScope } from '../../mcp/claude-code-scope.js';
9
+ import { computeEffectiveManagerInstructions } from '../../agents/manager-profile-overlay-service.js';
9
10
  import { RunService } from '../../runs/run-service.js';
11
+ import { isTerminalRunStatus } from '../../runs/schema.js';
10
12
  import { databasePathFor, databasePathSelectionFor, opencodeInstallScopeFlag, optionalNumberFlag, optionalStringFlag } from '../cli-flags.js';
11
13
  import { usageFailure, validationFailure } from '../cli-help.js';
12
14
  import { jsonResult, openCliDatabase, resultFailure } from '../cli-helpers.js';
@@ -42,6 +44,7 @@ export function runMcpInstallCommand(parsed, environment) {
42
44
  return resultFailure(opened);
43
45
  try {
44
46
  const preflight = createClaudeCodeInstallPreflight(parsed, opened.value, environment);
47
+ const effectiveManagerInstructions = computeEffectiveManagerInstructions(opened.value);
45
48
  const result = await installClaudeCodeMcpClient({
46
49
  cwd: environment.cwd,
47
50
  databasePath: databasePath.value.path,
@@ -50,6 +53,7 @@ export function runMcpInstallCommand(parsed, environment) {
50
53
  overwriteVgxness,
51
54
  scope: claudeScope.value,
52
55
  preflight,
56
+ ...(effectiveManagerInstructions === undefined ? {} : { effectiveManagerInstructions }),
53
57
  });
54
58
  return result.status === 'installed' ? jsonResult({ ok: true, value: result }) : resultFailure({ ok: false, error: { code: 'validation_failed', message: `${result.reason}: ${result.message}` } });
55
59
  }
@@ -107,6 +111,9 @@ function createClaudeCodeInstallPreflight(parsed, database, environment) {
107
111
  const details = service.getRun(runId);
108
112
  if (!details.ok)
109
113
  return details;
114
+ if (isTerminalRunStatus(details.value.status)) {
115
+ return validationFailure(`VGXNESS run ${runId} is in terminal state '${details.value.status}'; Claude Code provider config writes require a live run (created/planned/running/needs-human). Start a new run with \`vgxness run start\` and pass its --run-id.`);
116
+ }
110
117
  const phase = optionalStringFlag(parsed.flags, 'phase') ?? details.value.phase;
111
118
  const agentId = optionalStringFlag(parsed.flags, 'agent-id') ?? details.value.selectedAgentId;
112
119
  const preflight = service.preflightOperation({
@@ -4,6 +4,7 @@ import { isWorkflowId } from '../workflows/schema.js';
4
4
  import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
5
5
  import { okText, usageFailure, visibleHelpText } from './cli-help.js';
6
6
  import { openCliDatabase, resultFailure } from './cli-helpers.js';
7
+ import { runBootAgentSeedUpgrade } from '../agents/boot-upgrade.js';
7
8
  import { runAgentCommand, runApprovalsCommand, runCodeCliCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
8
9
  const _promptBuffers = new WeakMap();
9
10
  const require = createRequire(import.meta.url);
@@ -48,6 +49,7 @@ export function dispatchCli(argv, environment) {
48
49
  const opened = openCliDatabase(databasePath);
49
50
  if (!opened.ok)
50
51
  return resultFailure(opened);
52
+ runBootAgentSeedUpgrade(opened.value, environment.env);
51
53
  try {
52
54
  if (isWorkflowId(area) && command === 'run')
53
55
  return runWorkflowRunCommand(area, parsed, opened.value);
@@ -1,3 +1,4 @@
1
+ import { withEffectiveManagerInstructions } from '../agents/canonical-agent-projection.js';
1
2
  import { expectedClaudeCodeAgentFiles, inspectClaudeCodeAgents, renderClaudeCodeAgentMarkdown } from './claude-code-agent-config.js';
2
3
  import { buildClaudeCodeMcpAddCommand } from './claude-code-cli.js';
3
4
  import { createClaudeCodeMcpDoctorCommand, createClaudeCodeMcpServerConfig, inspectClaudeCodeMcpConfig, resolveClaudeCodeMcpJsonPath } from './claude-code-config.js';
@@ -63,11 +64,25 @@ export function planClaudeCodeMcpInstall(input) {
63
64
  status: 'would_install',
64
65
  };
65
66
  }
66
- export function expectedClaudeCodeRenderedAgents(workspaceRoot) {
67
- return expectedClaudeCodeAgentFiles(workspaceRoot).map((agent) => ({ path: agent.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) }));
67
+ export function expectedClaudeCodeRenderedAgents(workspaceRoot, options) {
68
+ const files = expectedClaudeCodeAgentFiles(workspaceRoot);
69
+ const projection = withEffectiveManagerInstructions({ defaultAgent: 'vgxness-manager', agents: files.map(({ path: _path, ...rest }) => rest) }, options?.effectiveManagerInstructions);
70
+ return projection.agents.map((agent) => {
71
+ const file = files.find((entry) => entry.name === agent.name);
72
+ if (file === undefined)
73
+ throw new Error(`Expected Claude agent file missing for ${agent.name}`);
74
+ return { path: file.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) };
75
+ });
68
76
  }
69
- export function expectedClaudeCodeRenderedUserAgents(workspaceRoot, env) {
70
- return expectedClaudeCodeAgentFiles({ workspaceRoot, scope: 'user', ...(env === undefined ? {} : { env }) }).map((agent) => ({ path: agent.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) }));
77
+ export function expectedClaudeCodeRenderedUserAgents(workspaceRoot, env, options) {
78
+ const files = expectedClaudeCodeAgentFiles({ workspaceRoot, scope: 'user', ...(env === undefined ? {} : { env }) });
79
+ const projection = withEffectiveManagerInstructions({ defaultAgent: 'vgxness-manager', agents: files.map(({ path: _path, ...rest }) => rest) }, options?.effectiveManagerInstructions);
80
+ return projection.agents.map((agent) => {
81
+ const file = files.find((entry) => entry.name === agent.name);
82
+ if (file === undefined)
83
+ throw new Error(`Expected Claude user agent file missing for ${agent.name}`);
84
+ return { path: file.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) };
85
+ });
71
86
  }
72
87
  function planUserInstall(input, server, overwriteVgxness, cliCommand, scopeWarnings) {
73
88
  const mcpState = inspectClaudeCodeUserMcpConfig(input.env);