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 +8 -3
- package/dist/agents/agent-activation-service.js +13 -4
- package/dist/agents/agent-registry-service.js +8 -2
- package/dist/agents/agent-seed-upgrade-service.js +231 -0
- package/dist/agents/boot-upgrade.js +59 -0
- package/dist/agents/canonical-agent-projection.js +7 -0
- package/dist/agents/manager-profile-overlay-service.js +14 -0
- package/dist/agents/repositories/agent-seed-history.js +128 -0
- package/dist/cli/commands/mcp-dispatcher.js +7 -0
- package/dist/cli/dispatcher.js +2 -0
- package/dist/mcp/client-install-claude-code-contract.js +19 -4
- package/dist/mcp/client-install-claude-code.js +2 -2
- package/dist/mcp/control-plane.js +22 -5
- package/dist/mcp/provider-status.js +3 -7
- package/dist/mcp/schema.js +18 -2
- package/dist/mcp/stdio-server.js +2 -0
- package/dist/mcp/validation.js +38 -2
- package/dist/memory/sqlite/migrations/016_agent_seed_history.sql +15 -0
- package/dist/runs/schema.js +4 -0
- package/dist/sdd/schema.js +12 -0
- package/dist/sdd/sdd-workflow-service.js +43 -2
- package/docs/architecture.md +8 -0
- package/docs/cli.md +5 -5
- package/docs/contributing.md +1 -1
- package/docs/glossary.md +1 -1
- package/docs/mcp.md +2 -2
- package/docs/project-health-audit-v1.9.1.md +126 -0
- package/docs/providers.md +4 -4
- package/docs/safety.md +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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({
|
package/dist/cli/dispatcher.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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);
|