ultra-builder-pro-cli 0.24.1 → 0.25.0
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/CHANGELOG.md +63 -0
- package/README.md +10 -0
- package/adapters/claude.js +104 -0
- package/adapters/codex.js +363 -40
- package/adapters/grok.js +1254 -58
- package/adapters/kimi.js +682 -13
- package/adapters/opencode.js +132 -11
- package/agents/code-reviewer.md +4 -5
- package/agents/debugger.md +8 -8
- package/docs/AGENT-CONTEXT.md +30 -4
- package/docs/ARCHITECTURE.md +24 -5
- package/docs/ARTIFACT-AUTHORITY.md +13 -0
- package/docs/COMMIT-HASH-BACKFILL.md +25 -18
- package/docs/RUNTIME-COMPAT-MATRIX.md +18 -2
- package/docs/STATE-DB-ACCESS-POLICY.md +8 -1
- package/mcp-server/lib/artifact-registry.cjs +188 -107
- package/mcp-server/lib/baseline-workflow.cjs +160 -23
- package/mcp-server/lib/change-packet.cjs +4 -32
- package/mcp-server/lib/change-workflow.cjs +36 -2755
- package/mcp-server/lib/context-envelope.cjs +124 -51
- package/mcp-server/lib/decision-records.cjs +164 -78
- package/mcp-server/lib/kernel-change-workflow.cjs +2029 -0
- package/mcp-server/lib/legacy-change-workflow.cjs +3032 -0
- package/mcp-server/lib/managed-file-write.cjs +56 -0
- package/mcp-server/lib/prd-parser.cjs +2 -2
- package/mcp-server/lib/stage-checkpoints.cjs +163 -9
- package/mcp-server/lib/state-db.cjs +186 -8
- package/mcp-server/lib/state-ops.cjs +214 -31
- package/mcp-server/lib/task-contract.cjs +404 -0
- package/mcp-server/lib/task-expander.cjs +8 -3
- package/mcp-server/lib/task-ledger.cjs +175 -62
- package/mcp-server/lib/ultra-facade.cjs +1895 -172
- package/mcp-server/lib/worker-packet.cjs +2 -2
- package/mcp-server/server.cjs +24 -5
- package/orchestrator/parallel-orchestrator.cjs +114 -27
- package/orchestrator/session-runner.cjs +25 -136
- package/package.json +1 -1
- package/skills/ultra-change/SKILL.md +6 -1
- package/skills/ultra-dev/SKILL.md +9 -4
- package/skills/ultra-plan/SKILL.md +13 -4
- package/skills/ultra-plan/references/semantic-preflight.md +6 -4
- package/spec/mcp-tools.yaml +114 -9
- package/spec/schemas/context-file.v4.5.schema.json +2 -2
- package/spec/schemas/state-db.sql +10 -6
- package/spec/schemas/task-ledger.v2.schema.json +23 -8
- package/spec/schemas/tasks.v4.5.schema.json +34 -12
package/adapters/codex.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const os = require('node:os');
|
|
21
21
|
const path = require('node:path');
|
|
22
|
+
const crypto = require('node:crypto');
|
|
22
23
|
const { spawnSync } = require('node:child_process');
|
|
23
24
|
|
|
24
25
|
const {
|
|
@@ -177,19 +178,127 @@ function shouldRunPluginCli(ctx = {}) {
|
|
|
177
178
|
return ctx.scope === 'global' && !ctx.configDir;
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
function shouldRunHostCli(ctx = {}) {
|
|
182
|
+
if (typeof ctx.runHostCli === 'boolean') return ctx.runHostCli;
|
|
183
|
+
return shouldRunPluginCli(ctx);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function runCodexCli(args, ctx = {}) {
|
|
187
|
+
const result = spawnSync(ctx.codexBin || 'codex', args, {
|
|
188
|
+
encoding: 'utf8',
|
|
189
|
+
timeout: ctx.hostCliTimeoutMs || 30000,
|
|
190
|
+
killSignal: 'SIGKILL',
|
|
191
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
192
|
+
env: {
|
|
193
|
+
...process.env,
|
|
194
|
+
HOME: resolveHomeDir(ctx),
|
|
195
|
+
CODEX_HOME: resolveTarget(ctx),
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
if (result.error) {
|
|
199
|
+
if (result.error.code === 'ETIMEDOUT') {
|
|
200
|
+
const error = new Error(`codex ${args.join(' ')} timed out`);
|
|
201
|
+
error.code = 'HOST_CLI_TIMEOUT';
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
throw result.error;
|
|
205
|
+
}
|
|
206
|
+
if (result.status !== 0) {
|
|
207
|
+
const detail = (result.stderr || result.stdout || '').trim();
|
|
208
|
+
throw new Error(`codex ${args.join(' ')} failed${detail ? `: ${detail}` : ''}`);
|
|
209
|
+
}
|
|
210
|
+
return result.stdout;
|
|
211
|
+
}
|
|
212
|
+
|
|
180
213
|
function runPluginCli(action, marketplaceName, ctx = {}) {
|
|
181
|
-
const codexBin = ctx.codexBin || 'codex';
|
|
182
214
|
const selector = `${PLUGIN_NAME}@${marketplaceName}`;
|
|
183
215
|
const args = action === 'add'
|
|
184
216
|
? ['plugin', 'add', selector, '--json']
|
|
185
|
-
: ['plugin', 'remove', selector];
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
: ['plugin', 'remove', selector, '--json'];
|
|
218
|
+
return { selector, stdout: runCodexCli(args, ctx).trim() };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function snapshotPluginRegistration(marketplaceName, ctx = {}) {
|
|
222
|
+
const selector = `${PLUGIN_NAME}@${marketplaceName}`;
|
|
223
|
+
const payload = JSON.parse(runCodexCli(['plugin', 'list', '--json'], ctx));
|
|
224
|
+
if (!payload || !Array.isArray(payload.installed)) {
|
|
225
|
+
throw new Error('codex plugin list --json did not return an installed array');
|
|
226
|
+
}
|
|
227
|
+
const entry = payload.installed.find((plugin) => (
|
|
228
|
+
plugin?.pluginId === selector
|
|
229
|
+
|| (plugin?.name === PLUGIN_NAME && plugin?.marketplaceName === marketplaceName)
|
|
230
|
+
)) || null;
|
|
231
|
+
return {
|
|
232
|
+
selector,
|
|
233
|
+
marketplaceName,
|
|
234
|
+
installed: !!entry && entry.installed !== false,
|
|
235
|
+
enabled: !!entry && entry.enabled === true,
|
|
236
|
+
entry,
|
|
237
|
+
config: captureFiles([path.join(resolveTarget(ctx), 'config.toml')])[0],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function restorePluginRegistration(snapshot, ctx = {}) {
|
|
242
|
+
const action = snapshot.installed ? 'add' : 'remove';
|
|
243
|
+
let actionError = null;
|
|
244
|
+
let result = null;
|
|
245
|
+
try {
|
|
246
|
+
result = runPluginCli(action, snapshot.marketplaceName, ctx);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
actionError = error;
|
|
249
|
+
}
|
|
250
|
+
let configError = null;
|
|
251
|
+
try {
|
|
252
|
+
restoreFiles([snapshot.config]);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
configError = error;
|
|
255
|
+
}
|
|
256
|
+
if (actionError || configError) {
|
|
257
|
+
throw new AggregateError(
|
|
258
|
+
[actionError, configError].filter(Boolean),
|
|
259
|
+
`Codex ${action} registration rollback failed`,
|
|
260
|
+
);
|
|
191
261
|
}
|
|
192
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
...result,
|
|
264
|
+
restored: {
|
|
265
|
+
installed: snapshot.installed,
|
|
266
|
+
enabled: snapshot.enabled,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function inspectCodexHost(ctx = {}, runtimeManifest = null, expectedPluginVersion = null) {
|
|
272
|
+
const payload = JSON.parse(runCodexCli(['plugin', 'list', '--json'], ctx));
|
|
273
|
+
if (!payload || !Array.isArray(payload.installed)) {
|
|
274
|
+
throw new Error('codex plugin list --json did not return an installed array');
|
|
275
|
+
}
|
|
276
|
+
const marketplaceName = runtimeManifest?.marketplace?.name || 'personal';
|
|
277
|
+
const pluginId = `${PLUGIN_NAME}@${marketplaceName}`;
|
|
278
|
+
const plugin = payload.installed.find((entry) => (
|
|
279
|
+
entry?.pluginId === pluginId
|
|
280
|
+
|| (entry?.name === PLUGIN_NAME && entry?.marketplaceName === marketplaceName)
|
|
281
|
+
));
|
|
282
|
+
const pluginOk = !!plugin
|
|
283
|
+
&& plugin.installed === true
|
|
284
|
+
&& plugin.enabled === true
|
|
285
|
+
&& (!(expectedPluginVersion || runtimeManifest?.plugin?.version)
|
|
286
|
+
|| plugin.version === (expectedPluginVersion || runtimeManifest.plugin.version));
|
|
287
|
+
const mcpPayload = JSON.parse(runCodexCli(['mcp', 'list', '--json'], ctx));
|
|
288
|
+
if (!Array.isArray(mcpPayload)) {
|
|
289
|
+
throw new Error('codex mcp list --json did not return an array');
|
|
290
|
+
}
|
|
291
|
+
const expected = JSON.parse(
|
|
292
|
+
fs.readFileSync(path.join(resolvePluginRoot(ctx), '.mcp.json'), 'utf8'),
|
|
293
|
+
).mcpServers?.[MCP_SERVER_NAME];
|
|
294
|
+
const mcp = mcpPayload.find((entry) => entry?.name === MCP_SERVER_NAME);
|
|
295
|
+
const mcpOk = !!mcp
|
|
296
|
+
&& mcp.enabled === true
|
|
297
|
+
&& mcp.disabled_reason === null
|
|
298
|
+
&& mcp.transport?.type === 'stdio'
|
|
299
|
+
&& mcp.transport.command === expected?.command
|
|
300
|
+
&& JSON.stringify(mcp.transport.args) === JSON.stringify(expected?.args);
|
|
301
|
+
return { pluginId, plugin, pluginOk, mcp, mcpOk };
|
|
193
302
|
}
|
|
194
303
|
|
|
195
304
|
function pluginCacheRoot(configDir, marketplaceName) {
|
|
@@ -211,14 +320,18 @@ function normalizeHookCacheVersions(versions) {
|
|
|
211
320
|
return [...normalized].sort();
|
|
212
321
|
}
|
|
213
322
|
|
|
214
|
-
function
|
|
323
|
+
function listHookCacheVersionsOnDisk(configDir, marketplaceName) {
|
|
215
324
|
const root = pluginCacheRoot(configDir, marketplaceName);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
325
|
+
if (!fs.existsSync(root)) return [];
|
|
326
|
+
return normalizeHookCacheVersions(
|
|
327
|
+
fs.readdirSync(root, { withFileTypes: true })
|
|
219
328
|
.filter((entry) => entry.isDirectory() || entry.isSymbolicLink())
|
|
220
|
-
.map((entry) => entry.name)
|
|
221
|
-
|
|
329
|
+
.map((entry) => entry.name),
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function listKnownHookCacheVersions(configDir, marketplaceName, manifest = null) {
|
|
334
|
+
const versions = listHookCacheVersionsOnDisk(configDir, marketplaceName);
|
|
222
335
|
|
|
223
336
|
if (manifest && manifest.source === SOURCE_TAG && manifest.adapter === 'codex') {
|
|
224
337
|
if (manifest.plugin && manifest.plugin.version !== undefined) {
|
|
@@ -234,6 +347,94 @@ function listKnownHookCacheVersions(configDir, marketplaceName, manifest = null)
|
|
|
234
347
|
return normalizeHookCacheVersions(versions);
|
|
235
348
|
}
|
|
236
349
|
|
|
350
|
+
function readPluginManifest(pluginRoot) {
|
|
351
|
+
const file = path.join(pluginRoot, '.codex-plugin', 'plugin.json');
|
|
352
|
+
const contents = fs.readFileSync(file);
|
|
353
|
+
const manifest = JSON.parse(contents.toString('utf8'));
|
|
354
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)
|
|
355
|
+
|| manifest.name !== PLUGIN_NAME) {
|
|
356
|
+
throw new Error(`invalid Codex plugin manifest: ${file}`);
|
|
357
|
+
}
|
|
358
|
+
normalizeHookCacheVersions([manifest.version]);
|
|
359
|
+
return { file, contents, manifest };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function codexVersionParts(version) {
|
|
363
|
+
if (typeof version !== 'string') return null;
|
|
364
|
+
const match = /^(.+)\+codex\.([A-Za-z0-9][A-Za-z0-9._-]*)$/.exec(version);
|
|
365
|
+
return match ? { base: match[1], cachebuster: match[2] } : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function bindCachebusterProvenance({
|
|
369
|
+
report,
|
|
370
|
+
runtimeManifest,
|
|
371
|
+
sourceManifest,
|
|
372
|
+
host,
|
|
373
|
+
configDir,
|
|
374
|
+
marketplaceName,
|
|
375
|
+
}) {
|
|
376
|
+
const previousVersion = runtimeManifest?.plugin?.version;
|
|
377
|
+
const currentVersion = sourceManifest?.manifest?.version;
|
|
378
|
+
const previous = codexVersionParts(previousVersion);
|
|
379
|
+
const current = codexVersionParts(currentVersion);
|
|
380
|
+
if (!previous || !current || previous.base !== current.base
|
|
381
|
+
|| previousVersion === currentVersion) {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const mismatch = report.issues.filter((issue) => (
|
|
386
|
+
issue.code === 'ASSET_HASH_MISMATCH'
|
|
387
|
+
&& issue.root === 'plugin'
|
|
388
|
+
&& issue.path === path.join('.codex-plugin', 'plugin.json')
|
|
389
|
+
));
|
|
390
|
+
if (mismatch.length !== 1
|
|
391
|
+
|| !host?.pluginOk
|
|
392
|
+
|| host.plugin?.version !== currentVersion) {
|
|
393
|
+
report.checks.cachebuster_binding = { status: 'fail' };
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const originalManifest = {
|
|
398
|
+
...sourceManifest.manifest,
|
|
399
|
+
version: previousVersion,
|
|
400
|
+
};
|
|
401
|
+
const originalHash = crypto.createHash('sha256')
|
|
402
|
+
.update(`${JSON.stringify(originalManifest, null, 2)}\n`)
|
|
403
|
+
.digest('hex');
|
|
404
|
+
const cacheManifestFile = path.join(
|
|
405
|
+
pluginCacheRoot(configDir, marketplaceName),
|
|
406
|
+
currentVersion,
|
|
407
|
+
'.codex-plugin',
|
|
408
|
+
'plugin.json',
|
|
409
|
+
);
|
|
410
|
+
let cacheMatchesSource = false;
|
|
411
|
+
try {
|
|
412
|
+
cacheMatchesSource = crypto.createHash('sha256')
|
|
413
|
+
.update(fs.readFileSync(cacheManifestFile))
|
|
414
|
+
.digest('hex')
|
|
415
|
+
=== crypto.createHash('sha256')
|
|
416
|
+
.update(sourceManifest.contents)
|
|
417
|
+
.digest('hex');
|
|
418
|
+
} catch (error) {
|
|
419
|
+
if (error.code !== 'ENOENT') throw error;
|
|
420
|
+
}
|
|
421
|
+
if (originalHash !== mismatch[0].expected || !cacheMatchesSource) {
|
|
422
|
+
report.checks.cachebuster_binding = { status: 'fail' };
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
report.issues = report.issues.filter((issue) => issue !== mismatch[0]);
|
|
427
|
+
report.checks.assets.failed -= 1;
|
|
428
|
+
if (report.checks.assets.failed === 0) report.checks.assets.status = 'pass';
|
|
429
|
+
report.checks.cachebuster_binding = {
|
|
430
|
+
status: 'pass',
|
|
431
|
+
previous_version: previousVersion,
|
|
432
|
+
current_version: currentVersion,
|
|
433
|
+
cache_manifest: cacheManifestFile,
|
|
434
|
+
};
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
|
|
237
438
|
function hookAdaptersForVersions(configDir, marketplaceName, versions) {
|
|
238
439
|
const root = pluginCacheRoot(configDir, marketplaceName);
|
|
239
440
|
return normalizeHookCacheVersions(versions)
|
|
@@ -379,6 +580,8 @@ function install(ctx = {}) {
|
|
|
379
580
|
let movedPrevious = false;
|
|
380
581
|
let published = false;
|
|
381
582
|
let registration = null;
|
|
583
|
+
let registrationSnapshot = null;
|
|
584
|
+
let registrationAttempted = false;
|
|
382
585
|
let hookCompatibility = null;
|
|
383
586
|
let legacy = { configUpdated: false, removed: [] };
|
|
384
587
|
try {
|
|
@@ -418,7 +621,16 @@ function install(ctx = {}) {
|
|
|
418
621
|
writeAtomic(marketplaceFile, JSON.stringify(marketplacePayload, null, 2) + '\n');
|
|
419
622
|
|
|
420
623
|
if (shouldRunPluginCli(ctx)) {
|
|
624
|
+
registrationSnapshot = snapshotPluginRegistration(marketplace.name, ctx);
|
|
625
|
+
registrationAttempted = true;
|
|
421
626
|
registration = runPluginCli('add', marketplace.name, ctx);
|
|
627
|
+
if (typeof ctx.afterPluginRegistration === 'function') {
|
|
628
|
+
ctx.afterPluginRegistration({
|
|
629
|
+
action: 'add',
|
|
630
|
+
before: registrationSnapshot,
|
|
631
|
+
registration,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
422
634
|
hookCompatibility = restoreCachedHookAdapters(
|
|
423
635
|
previousAdapters,
|
|
424
636
|
currentHookAdapter(configDir, marketplace.name, plugin),
|
|
@@ -480,17 +692,29 @@ function install(ctx = {}) {
|
|
|
480
692
|
if (published && fs.existsSync(pluginRoot)) removeTree(pluginRoot);
|
|
481
693
|
else if (fs.existsSync(stagingRoot)) removeTree(stagingRoot);
|
|
482
694
|
if (movedPrevious && fs.existsSync(backupRoot)) fs.renameSync(backupRoot, pluginRoot);
|
|
695
|
+
let rollbackError = null;
|
|
483
696
|
try {
|
|
484
|
-
restoreFiles(snapshots);
|
|
485
697
|
const currentCache = marketplace && plugin
|
|
486
698
|
? path.join(pluginCacheRoot(configDir, marketplace.name), plugin.version)
|
|
487
699
|
: null;
|
|
488
700
|
if (currentCache && !previousVersions.includes(plugin.version) && fs.existsSync(currentCache)) {
|
|
489
701
|
removeTree(currentCache);
|
|
490
702
|
}
|
|
703
|
+
if (registrationAttempted && registrationSnapshot) {
|
|
704
|
+
restorePluginRegistration(registrationSnapshot, ctx);
|
|
705
|
+
}
|
|
706
|
+
restoreFiles(snapshots);
|
|
707
|
+
} catch (caught) {
|
|
708
|
+
rollbackError = caught;
|
|
491
709
|
} finally {
|
|
492
710
|
if (fs.existsSync(agentStagingConfig)) removeTree(agentStagingConfig);
|
|
493
711
|
}
|
|
712
|
+
if (rollbackError) {
|
|
713
|
+
throw new AggregateError(
|
|
714
|
+
[error, rollbackError],
|
|
715
|
+
'Codex install and rollback both failed',
|
|
716
|
+
);
|
|
717
|
+
}
|
|
494
718
|
throw error;
|
|
495
719
|
}
|
|
496
720
|
}
|
|
@@ -536,12 +760,82 @@ function doctor(ctx = {}) {
|
|
|
536
760
|
}
|
|
537
761
|
report.checks.marketplace = { status: marketplaceOk ? 'pass' : 'fail' };
|
|
538
762
|
|
|
763
|
+
let sourceManifest = null;
|
|
764
|
+
try {
|
|
765
|
+
sourceManifest = readPluginManifest(pluginRoot);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
report.issues.push({ code: 'PLUGIN_MANIFEST_INVALID', message: error.message });
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
let host = null;
|
|
771
|
+
if (shouldRunHostCli(ctx)) {
|
|
772
|
+
try {
|
|
773
|
+
host = inspectCodexHost(
|
|
774
|
+
ctx,
|
|
775
|
+
runtimeManifest,
|
|
776
|
+
sourceManifest?.manifest?.version || null,
|
|
777
|
+
);
|
|
778
|
+
report.checks.host_plugin = { status: host.pluginOk ? 'pass' : 'fail' };
|
|
779
|
+
report.checks.host_mcp = { status: host.mcpOk ? 'pass' : 'fail' };
|
|
780
|
+
if (!host.pluginOk) {
|
|
781
|
+
report.issues.push({
|
|
782
|
+
code: 'HOST_PLUGIN_NOT_DISCOVERED',
|
|
783
|
+
plugin_id: host.pluginId,
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
if (!host.mcpOk) {
|
|
787
|
+
report.issues.push({
|
|
788
|
+
code: 'HOST_MCP_NOT_DISCOVERED',
|
|
789
|
+
plugin_id: host.pluginId,
|
|
790
|
+
server: MCP_SERVER_NAME,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
} catch (error) {
|
|
794
|
+
report.checks.host_plugin = { status: 'fail' };
|
|
795
|
+
report.checks.host_mcp = { status: 'fail' };
|
|
796
|
+
report.issues.push({
|
|
797
|
+
code: error.code === 'HOST_CLI_TIMEOUT'
|
|
798
|
+
? 'HOST_CLI_TIMEOUT'
|
|
799
|
+
: 'HOST_CLI_INSPECTION_FAILED',
|
|
800
|
+
message: error.message,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const marketplaceName = runtimeManifest?.marketplace?.name || 'personal';
|
|
806
|
+
if (runtimeManifestOk && sourceManifest && host) {
|
|
807
|
+
bindCachebusterProvenance({
|
|
808
|
+
report,
|
|
809
|
+
runtimeManifest,
|
|
810
|
+
sourceManifest,
|
|
811
|
+
host,
|
|
812
|
+
configDir,
|
|
813
|
+
marketplaceName,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
539
817
|
let hookTargetsOk = runtimeManifestOk;
|
|
540
818
|
if (runtimeManifestOk) {
|
|
541
|
-
const cacheRoot = pluginCacheRoot(configDir,
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
819
|
+
const cacheRoot = pluginCacheRoot(configDir, marketplaceName);
|
|
820
|
+
let requiredVersions = [];
|
|
821
|
+
try {
|
|
822
|
+
requiredVersions = listHookCacheVersionsOnDisk(configDir, marketplaceName);
|
|
823
|
+
const activeVersion = host?.plugin?.installed === true
|
|
824
|
+
? host.plugin.version
|
|
825
|
+
: (shouldRunPluginCli(ctx)
|
|
826
|
+
? sourceManifest?.manifest?.version || runtimeManifest.plugin.version
|
|
827
|
+
: null);
|
|
828
|
+
if (activeVersion) {
|
|
829
|
+
requiredVersions = normalizeHookCacheVersions([
|
|
830
|
+
...requiredVersions,
|
|
831
|
+
activeVersion,
|
|
832
|
+
]);
|
|
833
|
+
}
|
|
834
|
+
} catch (error) {
|
|
835
|
+
hookTargetsOk = false;
|
|
836
|
+
report.issues.push({ code: 'HOOK_CACHE_INVALID', message: error.message });
|
|
837
|
+
}
|
|
838
|
+
for (const version of requiredVersions) {
|
|
545
839
|
const adapter = path.join(cacheRoot, version, HOOK_ADAPTER_RELATIVE);
|
|
546
840
|
if (!fs.existsSync(adapter)) {
|
|
547
841
|
hookTargetsOk = false;
|
|
@@ -586,30 +880,55 @@ function uninstall(ctx = {}) {
|
|
|
586
880
|
const marketplaceFile = resolveMarketplaceFile(ctx);
|
|
587
881
|
const manifest = readRuntimeManifest(configDir);
|
|
588
882
|
const marketplaceBefore = loadMarketplace(marketplaceFile);
|
|
883
|
+
let registration = null;
|
|
884
|
+
let registrationSnapshot = null;
|
|
885
|
+
let registrationAttempted = false;
|
|
886
|
+
try {
|
|
887
|
+
if (shouldRunPluginCli(ctx)) {
|
|
888
|
+
registrationSnapshot = snapshotPluginRegistration(marketplaceBefore.name, ctx);
|
|
889
|
+
registrationAttempted = true;
|
|
890
|
+
registration = runPluginCli('remove', marketplaceBefore.name, ctx);
|
|
891
|
+
if (typeof ctx.afterPluginRegistration === 'function') {
|
|
892
|
+
ctx.afterPluginRegistration({
|
|
893
|
+
action: 'remove',
|
|
894
|
+
before: registrationSnapshot,
|
|
895
|
+
registration,
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const agents = removeManagedAgents(configDir, manifest);
|
|
589
900
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
901
|
+
let pluginRemoved = false;
|
|
902
|
+
if (fs.existsSync(pluginRoot) && fs.existsSync(path.join(pluginRoot, '.ubp-managed'))) {
|
|
903
|
+
removeTree(pluginRoot);
|
|
904
|
+
pluginRemoved = true;
|
|
905
|
+
}
|
|
906
|
+
const marketplace = removeMarketplaceEntry(marketplaceFile);
|
|
907
|
+
const legacy = cleanupLegacyConfig(configDir);
|
|
908
|
+
const manifestDir = path.join(configDir, RUNTIME_MANIFEST_DIR);
|
|
909
|
+
if (fs.existsSync(manifestDir)) removeTree(manifestDir);
|
|
594
910
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
911
|
+
return {
|
|
912
|
+
target: configDir,
|
|
913
|
+
removed: { plugin: pluginRemoved, agents },
|
|
914
|
+
marketplace,
|
|
915
|
+
registration,
|
|
916
|
+
legacy,
|
|
917
|
+
config: { updated: legacy.configUpdated },
|
|
918
|
+
};
|
|
919
|
+
} catch (error) {
|
|
920
|
+
if (registrationAttempted && registrationSnapshot) {
|
|
921
|
+
try {
|
|
922
|
+
restorePluginRegistration(registrationSnapshot, ctx);
|
|
923
|
+
} catch (rollbackError) {
|
|
924
|
+
throw new AggregateError(
|
|
925
|
+
[error, rollbackError],
|
|
926
|
+
'Codex uninstall and registration rollback both failed',
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
throw error;
|
|
599
931
|
}
|
|
600
|
-
const marketplace = removeMarketplaceEntry(marketplaceFile);
|
|
601
|
-
const legacy = cleanupLegacyConfig(configDir);
|
|
602
|
-
const manifestDir = path.join(configDir, RUNTIME_MANIFEST_DIR);
|
|
603
|
-
if (fs.existsSync(manifestDir)) removeTree(manifestDir);
|
|
604
|
-
|
|
605
|
-
return {
|
|
606
|
-
target: configDir,
|
|
607
|
-
removed: { plugin: pluginRemoved, agents },
|
|
608
|
-
marketplace,
|
|
609
|
-
registration,
|
|
610
|
-
legacy,
|
|
611
|
-
config: { updated: legacy.configUpdated },
|
|
612
|
-
};
|
|
613
932
|
}
|
|
614
933
|
|
|
615
934
|
module.exports = {
|
|
@@ -633,6 +952,10 @@ module.exports = {
|
|
|
633
952
|
upsertMarketplace,
|
|
634
953
|
removeMarketplaceEntry,
|
|
635
954
|
runPluginCli,
|
|
955
|
+
runCodexCli,
|
|
956
|
+
snapshotPluginRegistration,
|
|
957
|
+
restorePluginRegistration,
|
|
958
|
+
inspectCodexHost,
|
|
636
959
|
pluginCacheRoot,
|
|
637
960
|
listKnownHookCacheVersions,
|
|
638
961
|
listCachedHookAdapters,
|