@app-vitals/agent-runtime 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app-vitals/agent-runtime",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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": "0.6.0",
44
- "@app-vitals/agent-lib": "0.6.0",
43
+ "@app-vitals/agent-admin": "0.6.1",
44
+ "@app-vitals/agent-lib": "0.6.1",
45
45
  "@octokit/auth-app": "^8.2.0",
46
46
  "@sentry/bun": "^10.63.0",
47
47
  "@slack/bolt": "^5.0.0",
package/setup.ts CHANGED
@@ -40,7 +40,18 @@ const WORKSPACE_TEMPLATE_DIR =
40
40
  // Repo root — two levels up from agent/src/. In Docker this is /app; on the Pi
41
41
  // it resolves to the checkout root. Uses a local marketplace so plugins are always
42
42
  // available regardless of whether the GitHub repo is accessible.
43
- const SHIPWRIGHT_REPO_ROOT = join(import.meta.dir, "..", "..", "..");
43
+ const DEFAULT_SHIPWRIGHT_REPO_ROOT = join(import.meta.dir, "..", "..", "..");
44
+
45
+ /**
46
+ * The repo root used for the built-in marketplace and specialty lookup.
47
+ * `SHIPWRIGHT_REPO_ROOT` overrides the computed default (which resolves to `/`
48
+ * in the platform image layout); unset or empty keeps the computed default.
49
+ */
50
+ export function resolveShipwrightRepoRoot(
51
+ env: Record<string, string | undefined> = process.env,
52
+ ): string {
53
+ return env.SHIPWRIGHT_REPO_ROOT || DEFAULT_SHIPWRIGHT_REPO_ROOT;
54
+ }
44
55
 
45
56
  // Local marketplace name — matches the `name` field in .claude-plugin/marketplace.json.
46
57
  const SHIPWRIGHT_MARKETPLACE = "shipwright";
@@ -73,10 +84,11 @@ const REQUIRED_TEMPLATES: readonly string[] = [
73
84
  */
74
85
  export function resolveTemplatesDir(
75
86
  requestedType: string | undefined,
76
- repoRoot: string = SHIPWRIGHT_REPO_ROOT,
87
+ repoRootOverride?: string,
77
88
  warn: (message: string) => void = (m) => console.warn(m),
78
89
  env: Record<string, string | undefined> = process.env,
79
90
  ): string {
91
+ const repoRoot = repoRootOverride ?? resolveShipwrightRepoRoot(env);
80
92
  const typeName =
81
93
  requestedType ||
82
94
  env.SHIPWRIGHT_DEFAULT_AGENT_TYPE ||
@@ -393,7 +405,7 @@ export async function installPlugins(
393
405
  execFn: ExecFn = defaultExec,
394
406
  cwd: string = process.cwd(),
395
407
  agentPlugins: AgentPlugin[] = [],
396
- shipwrightRepoRoot: string = SHIPWRIGHT_REPO_ROOT,
408
+ shipwrightRepoRootOverride?: string,
397
409
  pluginManifestPath: string = join(
398
410
  process.env.HOME ?? "/root",
399
411
  ".claude",
@@ -402,7 +414,10 @@ export async function installPlugins(
402
414
  ),
403
415
  extraMarketplaceDirs?: string[],
404
416
  extraMarketplacesRoot: string = BAKED_MARKETPLACES_ROOT,
417
+ env: Record<string, string | undefined> = process.env,
405
418
  ): Promise<void> {
419
+ const shipwrightRepoRoot =
420
+ shipwrightRepoRootOverride ?? resolveShipwrightRepoRoot(env);
406
421
  try {
407
422
  // Determine which extra marketplace dirs to register.
408
423
  // Explicit list (injected via tests) takes precedence over discovery.
@@ -5,7 +5,15 @@
5
5
  */
6
6
 
7
7
  import { describe, expect, it } from "bun:test";
8
- import { discoverBakedMarketplaces, installPlugins } from "./setup.ts";
8
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import {
12
+ discoverBakedMarketplaces,
13
+ installPlugins,
14
+ resolveShipwrightRepoRoot,
15
+ resolveTemplatesDir,
16
+ } from "./setup.ts";
9
17
 
10
18
  // Explicit empty extra-marketplace list — every call in this describe block
11
19
  // must pass this (or an intentional array) instead of omitting the arg.
@@ -284,3 +292,94 @@ describe("resolveTemplatesDir option resolution (AEP-1.2)", () => {
284
292
  expect(dir).toMatch(/workspace$/);
285
293
  });
286
294
  });
295
+
296
+ describe("SHIPWRIGHT_REPO_ROOT override (PCB-1.1)", () => {
297
+ const recordExec = () => {
298
+ const calls: string[][] = [];
299
+ const exec = async (_cmd: string, args: string[]) => {
300
+ calls.push(args);
301
+ return { stdout: "", exitCode: 0 };
302
+ };
303
+ return { calls, exec };
304
+ };
305
+ const registerRoot = async (env: Record<string, string | undefined>) => {
306
+ const { calls, exec } = recordExec();
307
+ await installPlugins(
308
+ exec,
309
+ "/tmp/cwd",
310
+ [],
311
+ undefined,
312
+ "/tmp/nonexistent-manifest.json",
313
+ NO_EXTRA_MARKETPLACES,
314
+ "/tmp/nonexistent-baked-root",
315
+ env,
316
+ );
317
+ return calls[0][3];
318
+ };
319
+
320
+ it("installPlugins registers the marketplace from SHIPWRIGHT_REPO_ROOT when set", async () => {
321
+ expect(await registerRoot({ SHIPWRIGHT_REPO_ROOT: "/opt/custom" })).toBe(
322
+ "/opt/custom",
323
+ );
324
+ });
325
+
326
+ it("installPlugins keeps the computed default when unset or empty", async () => {
327
+ const unset = await registerRoot({});
328
+ expect(unset).toBe(resolveShipwrightRepoRoot({}));
329
+ expect(unset).not.toBe("/opt/custom");
330
+ expect(await registerRoot({ SHIPWRIGHT_REPO_ROOT: "" })).toBe(unset);
331
+ });
332
+
333
+ describe("resolveTemplatesDir", () => {
334
+ const makeRoot = () => {
335
+ const root = mkdtempSync(join(tmpdir(), "repo-root-"));
336
+ const dir = join(root, "specialties", "foo");
337
+ mkdirSync(join(dir, "tpl"), { recursive: true });
338
+ writeFileSync(join(dir, "manifest.yaml"), "identity:\n templatesDir: tpl\n");
339
+ for (const f of [
340
+ "CLAUDE.md.template",
341
+ "SOUL.md.template",
342
+ "IDENTITY.md.template",
343
+ ]) {
344
+ writeFileSync(join(dir, "tpl", f), "x");
345
+ }
346
+ return { root, tpl: join(dir, "tpl") };
347
+ };
348
+
349
+ it("looks up specialties under SHIPWRIGHT_REPO_ROOT when set", () => {
350
+ const { root, tpl } = makeRoot();
351
+ try {
352
+ const warnings: string[] = [];
353
+ const dir = resolveTemplatesDir(
354
+ "foo",
355
+ undefined,
356
+ (m) => warnings.push(m),
357
+ { SHIPWRIGHT_REPO_ROOT: root },
358
+ );
359
+ expect(warnings).toEqual([]);
360
+ expect(dir).toBe(tpl);
361
+ } finally {
362
+ rmSync(root, { recursive: true, force: true });
363
+ }
364
+ });
365
+
366
+ it("does not use the override when unset or empty", () => {
367
+ const { root, tpl } = makeRoot();
368
+ try {
369
+ for (const env of [{}, { SHIPWRIGHT_REPO_ROOT: "" }]) {
370
+ const warnings: string[] = [];
371
+ const dir = resolveTemplatesDir(
372
+ "foo",
373
+ undefined,
374
+ (m) => warnings.push(m),
375
+ env,
376
+ );
377
+ expect(dir).not.toBe(tpl);
378
+ expect(warnings).toHaveLength(1);
379
+ }
380
+ } finally {
381
+ rmSync(root, { recursive: true, force: true });
382
+ }
383
+ });
384
+ });
385
+ });