@app-vitals/agent-runtime 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/setup.integration.test.ts +101 -0
- package/setup.ts +66 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@app-vitals/agent-runtime",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/app-vitals/agent.git",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"./workspace-paths": "./workspace-paths.ts"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@app-vitals/agent-admin": "1.2.
|
|
44
|
-
"@app-vitals/agent-lib": "1.2.
|
|
43
|
+
"@app-vitals/agent-admin": "1.2.7",
|
|
44
|
+
"@app-vitals/agent-lib": "1.2.7",
|
|
45
45
|
"@octokit/auth-app": "^8.2.0",
|
|
46
46
|
"@sentry/bun": "^10.63.0",
|
|
47
47
|
"@slack/bolt": "^5.0.0",
|
|
@@ -1489,3 +1489,104 @@ describe("installPlugins — extra marketplace discovery", () => {
|
|
|
1489
1489
|
expect(calls).toHaveLength(3);
|
|
1490
1490
|
});
|
|
1491
1491
|
});
|
|
1492
|
+
|
|
1493
|
+
describe("installPlugins stale shipwright marketplace healing", () => {
|
|
1494
|
+
type Exec = (
|
|
1495
|
+
cmd: string,
|
|
1496
|
+
args: string[],
|
|
1497
|
+
opts: { cwd: string },
|
|
1498
|
+
) => Promise<{ stdout: string; exitCode: number }>;
|
|
1499
|
+
const record = () => {
|
|
1500
|
+
const calls: string[][] = [];
|
|
1501
|
+
const exec: Exec = async (_cmd, args) => {
|
|
1502
|
+
calls.push(args);
|
|
1503
|
+
return { stdout: "", exitCode: 0 };
|
|
1504
|
+
};
|
|
1505
|
+
return { calls, exec };
|
|
1506
|
+
};
|
|
1507
|
+
const noMarketplaceRoot = () => join(testHome, "no-marketplace-here");
|
|
1508
|
+
const bakedShipwright = () => {
|
|
1509
|
+
const dir = join(testHome, "baked", "shipwright");
|
|
1510
|
+
mkdirSync(join(dir, ".claude-plugin"), { recursive: true });
|
|
1511
|
+
writeFileSync(join(dir, ".claude-plugin", "marketplace.json"), "{}");
|
|
1512
|
+
return dir;
|
|
1513
|
+
};
|
|
1514
|
+
const writeRegistry = (content: string) => {
|
|
1515
|
+
mkdirSync(join(testHome, "plugins"), { recursive: true });
|
|
1516
|
+
writeFileSync(
|
|
1517
|
+
join(testHome, "plugins", "known_marketplaces.json"),
|
|
1518
|
+
content,
|
|
1519
|
+
);
|
|
1520
|
+
};
|
|
1521
|
+
const run = (exec: Exec, root: string, dirs: string[]) =>
|
|
1522
|
+
installPlugins(
|
|
1523
|
+
exec,
|
|
1524
|
+
testHome,
|
|
1525
|
+
[],
|
|
1526
|
+
root,
|
|
1527
|
+
join(testHome, "plugins", "installed_plugins.json"),
|
|
1528
|
+
dirs,
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
it("skips the repo-root add when only a baked shipwright marketplace exists", async () => {
|
|
1532
|
+
const { calls, exec } = record();
|
|
1533
|
+
const baked = bakedShipwright();
|
|
1534
|
+
await run(exec, noMarketplaceRoot(), [baked]);
|
|
1535
|
+
expect(calls).toEqual([["plugin", "marketplace", "add", baked]]);
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
it("removes a stale registration before adding the baked marketplace", async () => {
|
|
1539
|
+
const { calls, exec } = record();
|
|
1540
|
+
const baked = bakedShipwright();
|
|
1541
|
+
const stale = join(testHome, "stale-app");
|
|
1542
|
+
mkdirSync(stale, { recursive: true });
|
|
1543
|
+
writeRegistry(JSON.stringify({ shipwright: { installLocation: stale } }));
|
|
1544
|
+
await run(exec, noMarketplaceRoot(), [baked]);
|
|
1545
|
+
expect(calls).toEqual([
|
|
1546
|
+
["plugin", "marketplace", "remove", "shipwright"],
|
|
1547
|
+
["plugin", "marketplace", "add", baked],
|
|
1548
|
+
]);
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it("does not remove when the registered location still has a marketplace", async () => {
|
|
1552
|
+
const { calls, exec } = record();
|
|
1553
|
+
const baked = bakedShipwright();
|
|
1554
|
+
writeRegistry(JSON.stringify({ shipwright: { installLocation: baked } }));
|
|
1555
|
+
await run(exec, noMarketplaceRoot(), [baked]);
|
|
1556
|
+
expect(calls).toEqual([["plugin", "marketplace", "add", baked]]);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it("attempts no remove and warns when the registry is malformed", async () => {
|
|
1560
|
+
const { calls, exec } = record();
|
|
1561
|
+
const baked = bakedShipwright();
|
|
1562
|
+
writeRegistry("{not json");
|
|
1563
|
+
const warn = console.warn;
|
|
1564
|
+
const warnings: string[] = [];
|
|
1565
|
+
console.warn = (...a: unknown[]) => {
|
|
1566
|
+
warnings.push(a.join(" "));
|
|
1567
|
+
};
|
|
1568
|
+
try {
|
|
1569
|
+
await run(exec, noMarketplaceRoot(), [baked]);
|
|
1570
|
+
} finally {
|
|
1571
|
+
console.warn = warn;
|
|
1572
|
+
}
|
|
1573
|
+
expect(calls).toEqual([["plugin", "marketplace", "add", baked]]);
|
|
1574
|
+
expect(warnings.some((w) => w.includes("known_marketplaces.json"))).toBe(
|
|
1575
|
+
true,
|
|
1576
|
+
);
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
it("attempts no remove when the registry is absent", async () => {
|
|
1580
|
+
const { calls, exec } = record();
|
|
1581
|
+
const baked = bakedShipwright();
|
|
1582
|
+
await run(exec, noMarketplaceRoot(), [baked]);
|
|
1583
|
+
expect(calls).toEqual([["plugin", "marketplace", "add", baked]]);
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
it("keeps the coding-image sequence unchanged when the repo root is a marketplace", async () => {
|
|
1587
|
+
const { calls, exec } = record();
|
|
1588
|
+
const root = repoRoot();
|
|
1589
|
+
await run(exec, root, []);
|
|
1590
|
+
expect(calls).toEqual([["plugin", "marketplace", "add", root]]);
|
|
1591
|
+
});
|
|
1592
|
+
});
|
package/setup.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
unlinkSync,
|
|
21
21
|
writeFileSync,
|
|
22
22
|
} from "node:fs";
|
|
23
|
-
import { join, resolve } from "node:path";
|
|
23
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
24
24
|
import type { AgentPlugin } from "@app-vitals/agent-admin";
|
|
25
25
|
import {
|
|
26
26
|
resolveDefaultSpecialtyName,
|
|
@@ -382,6 +382,33 @@ export function findStalePluginSpecs(
|
|
|
382
382
|
});
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Reads the Claude CLI's known-marketplaces registry
|
|
387
|
+
* (`known_marketplaces.json`, a sibling of `installed_plugins.json`) and
|
|
388
|
+
* returns the install location it records for `name`. Defensive: returns
|
|
389
|
+
* `undefined` for a missing or unparsable registry or an unknown name, and
|
|
390
|
+
* warns when the file exists but cannot be read.
|
|
391
|
+
*/
|
|
392
|
+
function readRegisteredMarketplaceLocation(
|
|
393
|
+
registryPath: string,
|
|
394
|
+
name: string,
|
|
395
|
+
): string | undefined {
|
|
396
|
+
if (!existsSync(registryPath)) return undefined;
|
|
397
|
+
try {
|
|
398
|
+
const registry = JSON.parse(readFileSync(registryPath, "utf8")) as Record<
|
|
399
|
+
string,
|
|
400
|
+
{ installLocation?: unknown } | null
|
|
401
|
+
>;
|
|
402
|
+
const location = registry?.[name]?.installLocation;
|
|
403
|
+
return typeof location === "string" ? location : undefined;
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.warn(
|
|
406
|
+
`[agent] could not read ${registryPath} (nothing to heal): ${(err as Error).message}`,
|
|
407
|
+
);
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
385
412
|
/**
|
|
386
413
|
* Installs the plugins in the agent's own AgentPlugin rows (seeded from the
|
|
387
414
|
* specialty manifest at create time). No plugin is installed by default. All commands are idempotent (exit 0 when already
|
|
@@ -432,7 +459,35 @@ export async function installPlugins(
|
|
|
432
459
|
|
|
433
460
|
// Register extra baked marketplaces BEFORE the built-in shipwright marketplace.
|
|
434
461
|
// Idempotent — safe to call on every startup.
|
|
462
|
+
const registryPath = join(
|
|
463
|
+
dirname(pluginManifestPath),
|
|
464
|
+
"known_marketplaces.json",
|
|
465
|
+
);
|
|
435
466
|
for (const dir of extraDirs) {
|
|
467
|
+
// Heal a stale registration: a persistent ~/.claude may still map this
|
|
468
|
+
// marketplace name to a directory that no longer holds a marketplace
|
|
469
|
+
// (e.g. `shipwright` -> /app on the specialist image). Remove it so the
|
|
470
|
+
// add below registers the baked path.
|
|
471
|
+
const name = basename(dir);
|
|
472
|
+
const registered = readRegisteredMarketplaceLocation(registryPath, name);
|
|
473
|
+
if (
|
|
474
|
+
registered !== undefined &&
|
|
475
|
+
!existsSync(join(registered, ".claude-plugin", "marketplace.json"))
|
|
476
|
+
) {
|
|
477
|
+
console.log(
|
|
478
|
+
`[agent] marketplace ${name} is registered at ${registered} with no marketplace.json — removing before re-adding ${dir}`,
|
|
479
|
+
);
|
|
480
|
+
const remove = await execFn(
|
|
481
|
+
"claude",
|
|
482
|
+
["plugin", "marketplace", "remove", name],
|
|
483
|
+
{ cwd },
|
|
484
|
+
);
|
|
485
|
+
if (remove.exitCode !== 0) {
|
|
486
|
+
console.warn(
|
|
487
|
+
`[agent] claude plugin marketplace remove ${name} exited ${remove.exitCode}: ${remove.stdout}`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
436
491
|
const extraAdd = await execFn(
|
|
437
492
|
"claude",
|
|
438
493
|
["plugin", "marketplace", "add", dir],
|
|
@@ -447,9 +502,18 @@ export async function installPlugins(
|
|
|
447
502
|
|
|
448
503
|
// Register the local (built-in) shipwright marketplace so `claude plugin install`
|
|
449
504
|
// can resolve plugins by name. Idempotent — safe to call on every startup.
|
|
505
|
+
const hasRepoMarketplace = existsSync(
|
|
506
|
+
join(shipwrightRepoRoot, ".claude-plugin", "marketplace.json"),
|
|
507
|
+
);
|
|
450
508
|
if (
|
|
451
|
-
|
|
509
|
+
!hasRepoMarketplace &&
|
|
510
|
+
extraDirs.some((dir) => basename(dir) === "shipwright")
|
|
452
511
|
) {
|
|
512
|
+
console.log(
|
|
513
|
+
`[agent] ${shipwrightRepoRoot} has no marketplace.json — using baked shipwright marketplace`,
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
if (hasRepoMarketplace) {
|
|
453
517
|
const add = await execFn(
|
|
454
518
|
"claude",
|
|
455
519
|
["plugin", "marketplace", "add", shipwrightRepoRoot],
|