@app-vitals/agent-runtime 0.16.2 → 1.0.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/Dockerfile +1 -1
- package/default-specialty.shipped.integration.test.ts +18 -26
- package/package.json +3 -3
- package/setup.fixture.integration.test.ts +3 -12
- package/setup.ts +19 -29
- package/setup.unit.test.ts +7 -11
- package/workspace/SPECIALTY.md.template.content.test.ts +1 -1
- package/fixtures/specialties/fixture/manifest.yaml +0 -35
package/Dockerfile
CHANGED
|
@@ -175,7 +175,7 @@ RUN if [ -f bun.lock ]; then bun install --frozen-lockfile; else bun install; fi
|
|
|
175
175
|
COPY runtime/ ./runtime/
|
|
176
176
|
COPY admin/ ./admin/
|
|
177
177
|
COPY lib/ ./lib/
|
|
178
|
-
# Agent-type definitions (setup.ts reads
|
|
178
|
+
# Agent-type definitions (setup.ts reads each type's templates/ from them).
|
|
179
179
|
COPY agent-types/ ./agent-types/
|
|
180
180
|
# Composition root spawned by runtime/entrypoint.ts (../index.ts).
|
|
181
181
|
COPY index.ts ./index.ts
|
|
@@ -7,43 +7,29 @@
|
|
|
7
7
|
import { describe, expect, it } from "bun:test";
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import { SpecialtyTypes } from "@app-vitals/agent-admin/specialty-types";
|
|
11
11
|
import {
|
|
12
12
|
resolveDefaultSpecialtyName,
|
|
13
|
-
resolveSpecialtiesDir,
|
|
14
13
|
SHIPPED_AGENT_TYPES_DIR,
|
|
15
14
|
} from "@app-vitals/agent-lib/default-specialty";
|
|
15
|
+
import { SPECIALIST_TYPE } from "@app-vitals/agent-lib/specialist-type";
|
|
16
16
|
import { resolveTemplatesDir } from "./setup.ts";
|
|
17
17
|
|
|
18
18
|
const EMPTY_ENV: Record<string, string | undefined> = {};
|
|
19
19
|
|
|
20
20
|
describe("platform-only boot with empty env", () => {
|
|
21
|
-
it("
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
const manifest = registry.getManifest(
|
|
29
|
-
resolveDefaultSpecialtyName(EMPTY_ENV),
|
|
30
|
-
);
|
|
31
|
-
expect(manifest.metadata.name).toBe("specialist");
|
|
32
|
-
expect(warnings).toEqual([]);
|
|
33
|
-
expect(registry.listTypes().map((t) => t.name)).toContain("specialist");
|
|
21
|
+
it("the default type resolves to the built-in specialist type", () => {
|
|
22
|
+
const defaultName = resolveDefaultSpecialtyName(EMPTY_ENV);
|
|
23
|
+
const types = new SpecialtyTypes({ defaultTypeName: defaultName });
|
|
24
|
+
expect(defaultName).toBe("specialist");
|
|
25
|
+
expect(types.tryGetType(defaultName)).toBe(SPECIALIST_TYPE);
|
|
26
|
+
expect(types.listTypes().map((t) => t.name)).toEqual(["specialist"]);
|
|
34
27
|
});
|
|
35
28
|
|
|
36
|
-
it("
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
defaultTypeName: "specialist",
|
|
41
|
-
warn: (m) => warnings.push(m),
|
|
42
|
-
});
|
|
43
|
-
expect(registry.getManifest("no-such-type").metadata.name).toBe(
|
|
44
|
-
"specialist",
|
|
45
|
-
);
|
|
46
|
-
expect(warnings).toHaveLength(1);
|
|
29
|
+
it("a stored legacy type still resolves to the specialist floor, but is not a valid new type", () => {
|
|
30
|
+
const types = new SpecialtyTypes({ defaultTypeName: "specialist" });
|
|
31
|
+
expect(types.getType("no-such-type")).toBe(SPECIALIST_TYPE);
|
|
32
|
+
expect(types.tryGetType("no-such-type")).toBeUndefined();
|
|
47
33
|
});
|
|
48
34
|
|
|
49
35
|
it("runtime resolveTemplatesDir agrees: the default type's shipped templates, no warning", () => {
|
|
@@ -56,6 +42,12 @@ describe("platform-only boot with empty env", () => {
|
|
|
56
42
|
);
|
|
57
43
|
expect(warnings).toEqual([]);
|
|
58
44
|
expect(dir).toBe(join(SHIPPED_AGENT_TYPES_DIR, "specialist", "templates"));
|
|
45
|
+
expect(dir).toBe(
|
|
46
|
+
join(import.meta.dir, "..", SPECIALIST_TYPE.templatesDir).replace(
|
|
47
|
+
/\/$/,
|
|
48
|
+
"",
|
|
49
|
+
),
|
|
50
|
+
);
|
|
59
51
|
expect(existsSync(join(dir, "CLAUDE.md.template"))).toBe(true);
|
|
60
52
|
});
|
|
61
53
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@app-vitals/agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
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.
|
|
44
|
-
"@app-vitals/agent-lib": "0.
|
|
43
|
+
"@app-vitals/agent-admin": "1.0.0",
|
|
44
|
+
"@app-vitals/agent-lib": "1.0.0",
|
|
45
45
|
"@octokit/auth-app": "^8.2.0",
|
|
46
46
|
"@sentry/bun": "^10.63.0",
|
|
47
47
|
"@slack/bolt": "^5.0.0",
|
|
@@ -92,18 +92,9 @@ describe("fixture specialty templates", () => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
describe("coding is not special-cased (PCD-2.2)", () => {
|
|
95
|
-
|
|
95
|
+
it("resolves coding through the templates/ convention under the specialties dir", () => {
|
|
96
96
|
const specialties = join(root, "specialties");
|
|
97
|
-
|
|
98
|
-
writeFileSync(
|
|
99
|
-
join(specialties, "coding", "manifest.yaml"),
|
|
100
|
-
`identity:\n templatesDir: ${templatesDir}\n`,
|
|
101
|
-
);
|
|
102
|
-
return specialties;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
it("resolves coding through its manifest identity.templatesDir", () => {
|
|
106
|
-
const tpl = join(root, "coding-tpl");
|
|
97
|
+
const tpl = join(specialties, "coding", "templates");
|
|
107
98
|
mkdirSync(tpl, { recursive: true });
|
|
108
99
|
for (const f of [
|
|
109
100
|
"CLAUDE.md.template",
|
|
@@ -113,7 +104,7 @@ describe("coding is not special-cased (PCD-2.2)", () => {
|
|
|
113
104
|
writeFileSync(join(tpl, f), "x");
|
|
114
105
|
const warnings: string[] = [];
|
|
115
106
|
const dir = resolveTemplatesDir("coding", root, (m) => warnings.push(m), {
|
|
116
|
-
SHIPWRIGHT_SPECIALTIES_DIR:
|
|
107
|
+
SHIPWRIGHT_SPECIALTIES_DIR: specialties,
|
|
117
108
|
});
|
|
118
109
|
expect(dir).toBe(tpl);
|
|
119
110
|
expect(warnings).toEqual([]);
|
package/setup.ts
CHANGED
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
unlinkSync,
|
|
21
21
|
writeFileSync,
|
|
22
22
|
} from "node:fs";
|
|
23
|
-
import {
|
|
23
|
+
import { join, resolve } from "node:path";
|
|
24
24
|
import type { AgentPlugin } from "@app-vitals/agent-admin";
|
|
25
25
|
import {
|
|
26
26
|
resolveDefaultSpecialtyName,
|
|
27
27
|
resolveSpecialtiesDir,
|
|
28
28
|
} from "@app-vitals/agent-lib/default-specialty";
|
|
29
|
+
import { SPECIALIST_TYPE } from "@app-vitals/agent-lib/specialist-type";
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
// Template loading
|
|
@@ -69,17 +70,17 @@ const REQUIRED_TEMPLATES: readonly string[] = [
|
|
|
69
70
|
/**
|
|
70
71
|
* Resolves the workspace template directory for a specialty (ARD-6.1).
|
|
71
72
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* (
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
73
|
+
* A type's templates live at `<specialtiesDir>/<specialty>/templates/`, where
|
|
74
|
+
* specialtiesDir is SHIPWRIGHT_SPECIALTIES_DIR or the shipped `agent-types/`
|
|
75
|
+
* (shared helper: @app-vitals/agent-lib/default-specialty). The built-in
|
|
76
|
+
* specialist type additionally resolves its record's `templatesDir`
|
|
77
|
+
* (lib/specialist-type.ts) against the repo root. An absent type resolves as
|
|
78
|
+
* the default type (`specialist`, or SHIPWRIGHT_DEFAULT_AGENT_TYPE). Returns
|
|
79
|
+
* the built-in (generic) workspace/ dir for any type that does not resolve
|
|
80
|
+
* (the directory is missing or lacks a required template), logging a warning.
|
|
81
|
+
* An unknown requested type first falls back to the default type's templates
|
|
82
|
+
* (with a warning) before the built-in dir. Never throws: a template lookup
|
|
83
|
+
* must not fail boot.
|
|
83
84
|
*/
|
|
84
85
|
export function resolveTemplatesDir(
|
|
85
86
|
requestedType: string | undefined,
|
|
@@ -93,24 +94,13 @@ export function resolveTemplatesDir(
|
|
|
93
94
|
const specialtiesDir = resolveSpecialtiesDir(env);
|
|
94
95
|
|
|
95
96
|
const lookup = (name: string): string => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
};
|
|
100
|
-
const templatesDir = manifest?.identity?.templatesDir;
|
|
101
|
-
if (typeof templatesDir !== "string" || templatesDir === "") {
|
|
102
|
-
throw new Error("manifest has no identity.templatesDir");
|
|
97
|
+
const candidates = [join(specialtiesDir, name, "templates")];
|
|
98
|
+
if (name === SPECIALIST_TYPE.name) {
|
|
99
|
+
candidates.push(resolve(repoRoot, SPECIALIST_TYPE.templatesDir));
|
|
103
100
|
}
|
|
104
|
-
// Manifests spell templatesDir relative to the repo root, or (shipped
|
|
105
|
-
// agent-types/, where the repo root may not be the image root) relative to
|
|
106
|
-
// the specialties dir's parent; a self-contained specialty (e.g. the
|
|
107
|
-
// platform test fixture) spells it relative to its own directory.
|
|
108
101
|
const dir =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
resolve(dirname(specialtiesDir), templatesDir),
|
|
112
|
-
].find((candidate) => existsSync(candidate)) ??
|
|
113
|
-
resolve(dirname(manifestPath), templatesDir);
|
|
102
|
+
candidates.find((candidate) => existsSync(candidate)) ??
|
|
103
|
+
(candidates[0] as string);
|
|
114
104
|
if (!statSync(dir).isDirectory()) {
|
|
115
105
|
throw new Error(`${dir} is not a directory`);
|
|
116
106
|
}
|
|
@@ -165,7 +155,7 @@ function readTemplate(
|
|
|
165
155
|
`Missing workspace template: ${path}. The runtime ships no product ` +
|
|
166
156
|
"templates of its own: set SHIPWRIGHT_SPECIALTIES_DIR to a directory " +
|
|
167
157
|
"of specialties and SHIPWRIGHT_DEFAULT_AGENT_TYPE to the specialty " +
|
|
168
|
-
"whose
|
|
158
|
+
"whose templates/ directory provides it.",
|
|
169
159
|
);
|
|
170
160
|
}
|
|
171
161
|
}
|
package/setup.unit.test.ts
CHANGED
|
@@ -162,7 +162,7 @@ describe("discoverBakedMarketplaces", () => {
|
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
describe("resolveTemplatesDir option resolution (AEP-1.2)", () => {
|
|
165
|
-
it("resolves the shipped specialist
|
|
165
|
+
it("resolves the shipped specialist type to agent-types/specialist/templates/ with no warning (SAI-1.2)", async () => {
|
|
166
166
|
const { resolveTemplatesDir } = await import("./setup.ts");
|
|
167
167
|
const repoRoot = join(import.meta.dir, "..");
|
|
168
168
|
const warnings: string[] = [];
|
|
@@ -234,10 +234,10 @@ describe("resolveTemplatesDir option resolution (AEP-1.2)", () => {
|
|
|
234
234
|
SHIPWRIGHT_SPECIALTIES_DIR: "/fixture/types",
|
|
235
235
|
},
|
|
236
236
|
);
|
|
237
|
-
// Missing fixture
|
|
237
|
+
// Missing fixture templates dir → warns naming the type, falls back built-in.
|
|
238
238
|
expect(warnings).toHaveLength(1);
|
|
239
239
|
expect(warnings[0]).toContain('"base"');
|
|
240
|
-
expect(warnings[0]).toContain("/fixture/types/base/
|
|
240
|
+
expect(warnings[0]).toContain("/fixture/types/base/templates");
|
|
241
241
|
expect(dir).toMatch(/workspace$/);
|
|
242
242
|
});
|
|
243
243
|
});
|
|
@@ -288,22 +288,18 @@ describe("SHIPWRIGHT_REPO_ROOT override (PCB-1.1)", () => {
|
|
|
288
288
|
const makeRoot = () => {
|
|
289
289
|
const root = mkdtempSync(join(tmpdir(), "repo-root-"));
|
|
290
290
|
const dir = join(root, "types", "foo");
|
|
291
|
-
mkdirSync(join(dir, "
|
|
292
|
-
writeFileSync(
|
|
293
|
-
join(dir, "manifest.yaml"),
|
|
294
|
-
"identity:\n templatesDir: tpl\n",
|
|
295
|
-
);
|
|
291
|
+
mkdirSync(join(dir, "templates"), { recursive: true });
|
|
296
292
|
for (const f of [
|
|
297
293
|
"CLAUDE.md.template",
|
|
298
294
|
"SOUL.md.template",
|
|
299
295
|
"IDENTITY.md.template",
|
|
300
296
|
]) {
|
|
301
|
-
writeFileSync(join(dir, "
|
|
297
|
+
writeFileSync(join(dir, "templates", f), "x");
|
|
302
298
|
}
|
|
303
|
-
return { root, tpl: join(dir, "
|
|
299
|
+
return { root, tpl: join(dir, "templates") };
|
|
304
300
|
};
|
|
305
301
|
|
|
306
|
-
it("resolves
|
|
302
|
+
it("resolves a type's templates/ under SHIPWRIGHT_SPECIALTIES_DIR when SHIPWRIGHT_REPO_ROOT is set", () => {
|
|
307
303
|
const { root, tpl } = makeRoot();
|
|
308
304
|
try {
|
|
309
305
|
const warnings: string[] = [];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The platform's SPECIALTY.md.template is the generic fallback for every agent
|
|
5
5
|
* type, so it must stay a neutral stub. Specialty-specific pipeline content
|
|
6
6
|
* (e.g. the coding specialty's Shipwright workflow) is shipped by the specialty
|
|
7
|
-
* itself
|
|
7
|
+
* itself in its templates/ directory.
|
|
8
8
|
*
|
|
9
9
|
* Content-assertion only: readFileSync, no I/O beyond local file reads.
|
|
10
10
|
*/
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Minimal fixture specialty (AGR-1.9). Platform tests point
|
|
2
|
-
# SHIPWRIGHT_SPECIALTIES_DIR at the parent of this directory and
|
|
3
|
-
# SHIPWRIGHT_DEFAULT_AGENT_TYPE at "fixture", so the platform test suite needs
|
|
4
|
-
# no coding specialty. It is test data, not a product specialty: no crons, no
|
|
5
|
-
# plugins, no GitHub scope.
|
|
6
|
-
apiVersion: shipwright.dev/v1alpha1
|
|
7
|
-
kind: Specialty
|
|
8
|
-
metadata:
|
|
9
|
-
name: fixture
|
|
10
|
-
displayName: Fixture Agent
|
|
11
|
-
description: Minimal specialty used by the platform test suite.
|
|
12
|
-
version: 1.0.0
|
|
13
|
-
skills: []
|
|
14
|
-
identity:
|
|
15
|
-
# Resolved against the repo root first, then against this manifest's own directory.
|
|
16
|
-
templatesDir: templates/
|
|
17
|
-
crons: []
|
|
18
|
-
plugins:
|
|
19
|
-
- fixture-plugin
|
|
20
|
-
tools:
|
|
21
|
-
- Bash
|
|
22
|
-
- WebSearch
|
|
23
|
-
env:
|
|
24
|
-
required:
|
|
25
|
-
- key: CLAUDE_CODE_OAUTH_TOKEN
|
|
26
|
-
description: Claude Code OAuth token used to authenticate the agent's Claude sessions.
|
|
27
|
-
secret: true
|
|
28
|
-
optional:
|
|
29
|
-
- key: GH_TOKEN
|
|
30
|
-
description: GitHub Personal Access Token.
|
|
31
|
-
secret: true
|
|
32
|
-
members: []
|
|
33
|
-
repos: []
|
|
34
|
-
chat: true
|
|
35
|
-
voice: true
|