@rosetears/aili-pi 0.1.9 → 0.1.10
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 +6 -6
- package/THIRD_PARTY_NOTICES.md +12 -12
- package/docs/persistent-agents.md +114 -0
- package/manifests/adapter-evidence.json +53 -20
- package/manifests/capabilities.json +3 -3
- package/manifests/live-verification.json +18 -19
- package/manifests/provenance.json +12 -12
- package/manifests/roles.json +270 -57
- package/manifests/sbom.json +1 -128
- package/manifests/skill-compatibility.json +57 -61
- package/manifests/subagent-provenance.json +8 -15
- package/package.json +3 -3
- package/roles/agent-evaluator.md +8 -3
- package/roles/ai-regression-scout.md +8 -3
- package/roles/browser-qa-runner.md +8 -3
- package/roles/code-reviewer.md +8 -3
- package/roles/code-scout.md +8 -3
- package/roles/convergence-reviewer.md +8 -3
- package/roles/doc-researcher.md +8 -3
- package/roles/e2e-artifact-runner.md +8 -3
- package/roles/general.md +49 -0
- package/roles/implementer.md +8 -3
- package/roles/opensource-sanitizer.md +8 -3
- package/roles/plan-auditor.md +8 -3
- package/roles/pr-test-analyzer.md +8 -3
- package/roles/security-auditor.md +8 -3
- package/roles/silent-failure-reviewer.md +8 -3
- package/roles/spec-miner.md +8 -3
- package/roles/test-coverage-reviewer.md +8 -3
- package/roles/test-engineer.md +8 -3
- package/roles/web-performance-auditor.md +8 -3
- package/roles/web-researcher.md +8 -3
- package/scripts/sync-roles.ts +186 -24
- package/src/runtime/doctor.ts +38 -10
- package/src/runtime/global-resources.ts +5 -1
- package/src/runtime/index.ts +2 -2
- package/src/runtime/persistent-agents/hub.ts +429 -0
- package/src/runtime/persistent-agents/model-selection.ts +363 -0
- package/src/runtime/persistent-agents/output-delivery.ts +356 -0
- package/src/runtime/persistent-agents/permission.ts +223 -0
- package/src/runtime/persistent-agents/policy.ts +311 -0
- package/src/runtime/persistent-agents/production.ts +569 -0
- package/src/runtime/persistent-agents/runtime.ts +164 -0
- package/src/runtime/persistent-agents/sandbox.ts +68 -0
- package/src/runtime/persistent-agents/scheduler.ts +190 -0
- package/src/runtime/persistent-agents/session-factory.ts +184 -0
- package/src/runtime/persistent-agents/storage.ts +775 -0
- package/src/runtime/persistent-agents/task-coordinator.ts +460 -0
- package/src/runtime/persistent-agents/task-schema.ts +141 -0
- package/src/runtime/persistent-agents/types.ts +134 -0
- package/src/runtime/persistent-agents/workspace.ts +335 -0
- package/src/runtime/registry.ts +28 -32
- package/src/runtime/roles.ts +211 -18
- package/src/runtime/rose-context.ts +1 -1
- package/templates/APPEND_SYSTEM.md +1 -1
- package/upstream/opencode-global-agents.lock.json +1 -1
- package/src/runtime/subagents.ts +0 -249
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
name: test-coverage-reviewer
|
|
3
3
|
description: Read-only QA reviewer for test coverage adequacy. Use when a diff, package, or release needs coverage-gap review, untested-path identification, or verification sufficiency analysis without writing tests.
|
|
4
4
|
tools: read,grep,find,ls
|
|
5
|
+
spawns: []
|
|
6
|
+
blocking: false
|
|
7
|
+
aili-profile-version: 2
|
|
8
|
+
aili-runtime-adapter-version: 2
|
|
9
|
+
aili-source-kind: canonical-adapter
|
|
5
10
|
aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
6
11
|
---
|
|
7
12
|
|
|
@@ -9,7 +14,7 @@ aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
|
9
14
|
|
|
10
15
|
## Role
|
|
11
16
|
|
|
12
|
-
You are a bounded
|
|
17
|
+
You are a bounded persistent Pi Agent role. Work only on the supplied assignment or follow-up turn within the same stable Agent identity. Your result is evidence for ROSE or the user, not final authority.
|
|
13
18
|
|
|
14
19
|
## Goal
|
|
15
20
|
|
|
@@ -41,7 +46,7 @@ Stop when permission is missing, the requested scope conflicts with repository r
|
|
|
41
46
|
|
|
42
47
|
## Pi adapter contract
|
|
43
48
|
|
|
44
|
-
You run
|
|
45
|
-
Use only the tools exposed by the
|
|
49
|
+
You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.
|
|
50
|
+
Child Agent spawning is disabled for this specialized profile. Use only the effective tools exposed by the parent/role/capability/policy intersection; a task packet may narrow and never broaden them.
|
|
46
51
|
Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.
|
|
47
52
|
Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.
|
package/roles/test-engineer.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
name: test-engineer
|
|
3
3
|
description: QA engineer specialized in test strategy, test writing, test execution, CLI/browser verification, verification logs, and coverage analysis. Use for designing test suites, writing tests for existing code, executing test plans, or evaluating test quality.
|
|
4
4
|
tools: read,write,edit
|
|
5
|
+
spawns: []
|
|
6
|
+
blocking: false
|
|
7
|
+
aili-profile-version: 2
|
|
8
|
+
aili-runtime-adapter-version: 2
|
|
9
|
+
aili-source-kind: canonical-adapter
|
|
5
10
|
aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
6
11
|
---
|
|
7
12
|
|
|
@@ -9,7 +14,7 @@ aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
|
9
14
|
|
|
10
15
|
## Role
|
|
11
16
|
|
|
12
|
-
You are a bounded
|
|
17
|
+
You are a bounded persistent Pi Agent role. Work only on the supplied assignment or follow-up turn within the same stable Agent identity. Your result is evidence for ROSE or the user, not final authority.
|
|
13
18
|
|
|
14
19
|
## Goal
|
|
15
20
|
|
|
@@ -42,7 +47,7 @@ Stop when permission is missing, the requested scope conflicts with repository r
|
|
|
42
47
|
|
|
43
48
|
## Pi adapter contract
|
|
44
49
|
|
|
45
|
-
You run
|
|
46
|
-
Use only the tools exposed by the
|
|
50
|
+
You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.
|
|
51
|
+
Child Agent spawning is disabled for this specialized profile. Use only the effective tools exposed by the parent/role/capability/policy intersection; a task packet may narrow and never broaden them.
|
|
47
52
|
Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.
|
|
48
53
|
Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
name: web-performance-auditor
|
|
3
3
|
description: Web performance engineer focused on Core Web Vitals, loading, rendering, and network optimization. Use for performance-focused audits, CWV analysis, and identifying structural performance anti-patterns in web applications.
|
|
4
4
|
tools: read,grep,find,ls
|
|
5
|
+
spawns: []
|
|
6
|
+
blocking: false
|
|
7
|
+
aili-profile-version: 2
|
|
8
|
+
aili-runtime-adapter-version: 2
|
|
9
|
+
aili-source-kind: canonical-adapter
|
|
5
10
|
aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
6
11
|
---
|
|
7
12
|
|
|
@@ -9,7 +14,7 @@ aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
|
9
14
|
|
|
10
15
|
## Role
|
|
11
16
|
|
|
12
|
-
You are a bounded
|
|
17
|
+
You are a bounded persistent Pi Agent role. Work only on the supplied assignment or follow-up turn within the same stable Agent identity. Your result is evidence for ROSE or the user, not final authority.
|
|
13
18
|
|
|
14
19
|
## Goal
|
|
15
20
|
|
|
@@ -41,7 +46,7 @@ Stop when permission is missing, the requested scope conflicts with repository r
|
|
|
41
46
|
|
|
42
47
|
## Pi adapter contract
|
|
43
48
|
|
|
44
|
-
You run
|
|
45
|
-
Use only the tools exposed by the
|
|
49
|
+
You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.
|
|
50
|
+
Child Agent spawning is disabled for this specialized profile. Use only the effective tools exposed by the parent/role/capability/policy intersection; a task packet may narrow and never broaden them.
|
|
46
51
|
Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.
|
|
47
52
|
Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.
|
package/roles/web-researcher.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
name: web-researcher
|
|
3
3
|
description: Read-only public web research subagent. Uses web search/fetch for current official documentation, public repositories, releases, package metadata, compatibility, and deprecation evidence; never reads local files, edits, runs commands, or delegates.
|
|
4
4
|
tools: []
|
|
5
|
+
spawns: []
|
|
6
|
+
blocking: false
|
|
7
|
+
aili-profile-version: 2
|
|
8
|
+
aili-runtime-adapter-version: 2
|
|
9
|
+
aili-source-kind: canonical-adapter
|
|
5
10
|
aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
6
11
|
---
|
|
7
12
|
|
|
@@ -9,7 +14,7 @@ aili-source-revision: 7eb35f357ad489f5841ee10dac1e44549c1bdb76
|
|
|
9
14
|
|
|
10
15
|
## Role
|
|
11
16
|
|
|
12
|
-
You are a bounded
|
|
17
|
+
You are a bounded persistent Pi Agent role. Work only on the supplied assignment or follow-up turn within the same stable Agent identity. Your result is evidence for ROSE or the user, not final authority.
|
|
13
18
|
|
|
14
19
|
## Goal
|
|
15
20
|
|
|
@@ -41,7 +46,7 @@ Stop when permission is missing, the requested scope conflicts with repository r
|
|
|
41
46
|
|
|
42
47
|
## Pi adapter contract
|
|
43
48
|
|
|
44
|
-
You run
|
|
45
|
-
Use only the tools exposed by the
|
|
49
|
+
You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.
|
|
50
|
+
Child Agent spawning is disabled for this specialized profile. Use only the effective tools exposed by the parent/role/capability/policy intersection; a task packet may narrow and never broaden them.
|
|
46
51
|
Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.
|
|
47
52
|
Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.
|
package/scripts/sync-roles.ts
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { execFile as execFileCallback } from "node:child_process";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
3
|
import { lstat, readdir, readFile, writeFile } from "node:fs/promises";
|
|
5
4
|
import { basename, join, resolve } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
6
|
|
|
7
7
|
const ROOT = resolve(import.meta.dirname, "..");
|
|
8
8
|
const ROLES_DIR = join(ROOT, "roles");
|
|
9
9
|
const MANIFEST_PATH = join(ROOT, "manifests", "roles.json");
|
|
10
10
|
const SOURCE_COMMIT = "7eb35f357ad489f5841ee10dac1e44549c1bdb76";
|
|
11
11
|
const SOURCE_REPOSITORY = "https://github.com/Rosetears520/aili-workflows.git";
|
|
12
|
+
const PROFILE_VERSION = 2;
|
|
13
|
+
const RUNTIME_ADAPTER_VERSION = 2;
|
|
12
14
|
const execFile = promisify(execFileCallback);
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const SPECIALIZED_ROLE_NAMES = [
|
|
15
17
|
"agent-evaluator", "ai-regression-scout", "browser-qa-runner", "code-reviewer",
|
|
16
18
|
"code-scout", "convergence-reviewer", "doc-researcher", "e2e-artifact-runner",
|
|
17
19
|
"implementer", "opensource-sanitizer", "plan-auditor", "pr-test-analyzer",
|
|
18
20
|
"security-auditor", "silent-failure-reviewer", "spec-miner", "test-coverage-reviewer",
|
|
19
21
|
"test-engineer", "web-performance-auditor", "web-researcher",
|
|
20
22
|
] as const;
|
|
23
|
+
const SPECIALIZED_SELECTORS = SPECIALIZED_ROLE_NAMES.map((name) => `aili.${name}`);
|
|
24
|
+
const BUNDLED_NAMES = [...SPECIALIZED_ROLE_NAMES, "general"] as const;
|
|
25
|
+
const BUNDLED_SELECTORS = [...SPECIALIZED_SELECTORS, "general"];
|
|
21
26
|
|
|
22
27
|
const OPTIONAL: Record<string, string[]> = {
|
|
23
28
|
"browser-qa-runner": ["browser.qa"],
|
|
@@ -40,13 +45,21 @@ const KNOWN_SOURCE_PERMISSION_KEYS = new Set([
|
|
|
40
45
|
|
|
41
46
|
interface RoleRecord {
|
|
42
47
|
name: string;
|
|
48
|
+
selector: string;
|
|
43
49
|
description: string;
|
|
44
50
|
profilePath: string;
|
|
45
51
|
profileHash: string;
|
|
46
|
-
|
|
52
|
+
profileVersion: 2;
|
|
53
|
+
runtimeAdapterVersion: 2;
|
|
54
|
+
sourceKind: "canonical-adapter" | "aili-owned";
|
|
55
|
+
sourcePath: string | null;
|
|
47
56
|
sourceHash: string;
|
|
48
57
|
tools: string[];
|
|
58
|
+
toolPolicy: "static" | "inherit-parent";
|
|
49
59
|
capabilities: string[];
|
|
60
|
+
spawns: string[];
|
|
61
|
+
blocking: boolean;
|
|
62
|
+
model?: string;
|
|
50
63
|
status: "adapted" | "optional" | "blocked";
|
|
51
64
|
compatibilityReason: string;
|
|
52
65
|
sourceFrontmatterDisposition: Record<string, string>;
|
|
@@ -96,27 +109,109 @@ function translatedPolicy(source: string): { tools: string[]; blocked: boolean;
|
|
|
96
109
|
};
|
|
97
110
|
}
|
|
98
111
|
|
|
99
|
-
function
|
|
112
|
+
function profileFrontmatter(args: {
|
|
113
|
+
name: string;
|
|
114
|
+
description: string;
|
|
115
|
+
tools: string[];
|
|
116
|
+
spawns: string[];
|
|
117
|
+
sourceRevision: string;
|
|
118
|
+
sourceKind: RoleRecord["sourceKind"];
|
|
119
|
+
}): string[] {
|
|
120
|
+
return [
|
|
121
|
+
"---",
|
|
122
|
+
`name: ${args.name}`,
|
|
123
|
+
`description: ${args.description}`,
|
|
124
|
+
`tools: ${args.tools.join(",") || "[]"}`,
|
|
125
|
+
`spawns: ${args.spawns.join(",") || "[]"}`,
|
|
126
|
+
"blocking: false",
|
|
127
|
+
`aili-profile-version: ${PROFILE_VERSION}`,
|
|
128
|
+
`aili-runtime-adapter-version: ${RUNTIME_ADAPTER_VERSION}`,
|
|
129
|
+
`aili-source-kind: ${args.sourceKind}`,
|
|
130
|
+
`aili-source-revision: ${args.sourceRevision}`,
|
|
131
|
+
"---",
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function specializedProfileContent(name: string, description: string, tools: string[], body: string): string {
|
|
100
136
|
const adaptedBody = body
|
|
101
137
|
.replaceAll("OpenCode subagent", "Pi child role")
|
|
102
138
|
.replaceAll("OpenCode", "Pi")
|
|
103
139
|
.replaceAll("Task tool", "AILI task capability")
|
|
140
|
+
.replace(
|
|
141
|
+
"You are a bounded, single-use Pi child role. Complete the supplied assignment once, return one terminal result or failure, and never resume this context.",
|
|
142
|
+
"You are a bounded persistent Pi Agent role. Work only on the supplied assignment or follow-up turn within the same stable Agent identity.",
|
|
143
|
+
)
|
|
104
144
|
.replace(/## Output\n[\s\S]*?(?=\n## Stop)/, "## Output\n\nReturn exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.");
|
|
105
145
|
return [
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
...profileFrontmatter({
|
|
147
|
+
name,
|
|
148
|
+
description,
|
|
149
|
+
tools,
|
|
150
|
+
spawns: [],
|
|
151
|
+
sourceRevision: SOURCE_COMMIT,
|
|
152
|
+
sourceKind: "canonical-adapter",
|
|
153
|
+
}),
|
|
112
154
|
"",
|
|
113
155
|
adaptedBody,
|
|
114
156
|
"",
|
|
115
157
|
"## Pi adapter contract",
|
|
116
158
|
"",
|
|
117
|
-
"You run
|
|
118
|
-
"Use only the tools exposed by the
|
|
159
|
+
"You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.",
|
|
160
|
+
"Child Agent spawning is disabled for this specialized profile. Use only the effective tools exposed by the parent/role/capability/policy intersection; a task packet may narrow and never broaden them.",
|
|
161
|
+
"Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.",
|
|
162
|
+
"Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.",
|
|
163
|
+
"",
|
|
164
|
+
].join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function generalProfileContent(): string {
|
|
168
|
+
const description = "General persistent Agent for focused delegated work using the parent's current active tool ceiling and explicit spawn policy.";
|
|
169
|
+
return [
|
|
170
|
+
...profileFrontmatter({
|
|
171
|
+
name: "general",
|
|
172
|
+
description,
|
|
173
|
+
tools: [],
|
|
174
|
+
spawns: SPECIALIZED_SELECTORS,
|
|
175
|
+
sourceRevision: "aili-owned-general-v1",
|
|
176
|
+
sourceKind: "aili-owned",
|
|
177
|
+
}),
|
|
178
|
+
"",
|
|
179
|
+
"# General Agent",
|
|
180
|
+
"",
|
|
181
|
+
"## Role",
|
|
182
|
+
"",
|
|
183
|
+
"You are AILI's general persistent worker Agent. Complete the current delegated assignment and retain relevant context for later follow-up turns under the same stable Agent identity.",
|
|
184
|
+
"",
|
|
185
|
+
"## Goal",
|
|
186
|
+
"",
|
|
187
|
+
"Deliver the requested bounded outcome using only the effective tools inherited from the parent and only the specialized child roles allowed by the runtime spawn policy.",
|
|
188
|
+
"",
|
|
189
|
+
"## Success criteria",
|
|
190
|
+
"",
|
|
191
|
+
"- Stay focused on the supplied task and explicit context.",
|
|
192
|
+
"- Use tools only when they materially advance the task.",
|
|
193
|
+
"- Delegate only to an allowed non-self specialized role when it has clear benefit.",
|
|
194
|
+
"- Return concise, evidence-grounded results and preserve unresolved blockers.",
|
|
195
|
+
"",
|
|
196
|
+
"## Constraints",
|
|
197
|
+
"",
|
|
198
|
+
"- Parent permissions, active tools, project trust, credential guards, recursion depth, and workspace policy always apply and can only narrow authority.",
|
|
199
|
+
"- Do not infer access to the parent's conversation history beyond explicit task/context and trusted resources.",
|
|
200
|
+
"- Do not write persistent configuration, delete history, or perform external/destructive operations without the required user confirmation.",
|
|
201
|
+
"- Never claim a child task, tool call, verification, or delivery succeeded without evidence.",
|
|
202
|
+
"",
|
|
203
|
+
"## Output",
|
|
204
|
+
"",
|
|
119
205
|
"Return exactly one JSON object with keys `status`, `summary`, `evidence`, `changedFiles`, `verification`, `blockers`, `risks`, and `confidence`.",
|
|
206
|
+
"",
|
|
207
|
+
"## Stop",
|
|
208
|
+
"",
|
|
209
|
+
"Stop when the assignment is complete, a material decision or permission is missing, required evidence is unavailable, or continuing would exceed the accepted task boundary.",
|
|
210
|
+
"",
|
|
211
|
+
"## Pi adapter contract",
|
|
212
|
+
"",
|
|
213
|
+
"You run in a parent-scoped persistent official Pi Agent session. Each turn has one supplied assignment or follow-up; an idle session may park and later revive with its retained transcript.",
|
|
214
|
+
"Use only the effective parent-active/capability/policy tool intersection. The runtime may expose `task` only for allowed non-self specialized selectors and within the accepted depth ceiling.",
|
|
120
215
|
"Do not include credentials, raw environment variables, authentication-store content, or unbounded command output.",
|
|
121
216
|
"",
|
|
122
217
|
].join("\n");
|
|
@@ -132,8 +227,9 @@ async function generate(sourceRoot: string): Promise<void> {
|
|
|
132
227
|
if (commit !== SOURCE_COMMIT) throw new Error(`source revision mismatch: ${commit}`);
|
|
133
228
|
if (origin !== SOURCE_REPOSITORY) throw new Error(`source origin mismatch: ${origin}`);
|
|
134
229
|
if (status !== "") throw new Error("source worktree must be clean");
|
|
230
|
+
|
|
135
231
|
const records: RoleRecord[] = [];
|
|
136
|
-
for (const name of
|
|
232
|
+
for (const name of SPECIALIZED_ROLE_NAMES) {
|
|
137
233
|
const sourcePath = join(sourceRoot, "agents", `${name}.md`);
|
|
138
234
|
if ((await lstat(sourcePath)).isSymbolicLink()) throw new Error(`${name}: source role must not be a symlink`);
|
|
139
235
|
const source = await readFile(sourcePath, "utf8");
|
|
@@ -143,59 +239,125 @@ async function generate(sourceRoot: string): Promise<void> {
|
|
|
143
239
|
if (name === "web-researcher") tools.length = 0;
|
|
144
240
|
const capabilities = ["repo.read", ...(tools.some((tool) => tool === "write" || tool === "edit") ? ["repo.write"] : []), ...(OPTIONAL[name] ?? [])];
|
|
145
241
|
if (name === "web-researcher") capabilities.splice(0, 1);
|
|
146
|
-
const profile =
|
|
242
|
+
const profile = specializedProfileContent(name, description, tools, body);
|
|
147
243
|
await writeFile(join(ROLES_DIR, `${name}.md`), profile, "utf8");
|
|
148
244
|
records.push({
|
|
149
245
|
name,
|
|
246
|
+
selector: `aili.${name}`,
|
|
150
247
|
description,
|
|
151
248
|
profilePath: `roles/${name}.md`,
|
|
152
249
|
profileHash: hash(profile),
|
|
250
|
+
profileVersion: PROFILE_VERSION,
|
|
251
|
+
runtimeAdapterVersion: RUNTIME_ADAPTER_VERSION,
|
|
252
|
+
sourceKind: "canonical-adapter",
|
|
153
253
|
sourcePath: `agents/${name}.md`,
|
|
154
254
|
sourceHash: hash(source),
|
|
155
255
|
tools,
|
|
256
|
+
toolPolicy: "static",
|
|
156
257
|
capabilities,
|
|
258
|
+
spawns: [],
|
|
259
|
+
blocking: false,
|
|
157
260
|
status: OPTIONAL[name] ? "optional" : policy.blocked ? "blocked" : "adapted",
|
|
158
261
|
compatibilityReason: OPTIONAL[name] ? `Optional capabilities are required: ${OPTIONAL[name].join(", ")}.` : policy.reason,
|
|
159
262
|
sourceFrontmatterDisposition: {
|
|
160
|
-
mode: "
|
|
263
|
+
mode: "translated into a parent-scoped persistent official Pi Agent session",
|
|
161
264
|
hidden: "not interpreted by Pi",
|
|
162
265
|
permission: "translated into the explicit Pi tool/capability ceiling; source YAML is never executed",
|
|
163
266
|
},
|
|
164
267
|
});
|
|
165
268
|
}
|
|
269
|
+
|
|
270
|
+
const general = generalProfileContent();
|
|
271
|
+
await writeFile(join(ROLES_DIR, "general.md"), general, "utf8");
|
|
272
|
+
records.push({
|
|
273
|
+
name: "general",
|
|
274
|
+
selector: "general",
|
|
275
|
+
description: "General persistent Agent for focused delegated work using the parent's current active tool ceiling and explicit spawn policy.",
|
|
276
|
+
profilePath: "roles/general.md",
|
|
277
|
+
profileHash: hash(general),
|
|
278
|
+
profileVersion: PROFILE_VERSION,
|
|
279
|
+
runtimeAdapterVersion: RUNTIME_ADAPTER_VERSION,
|
|
280
|
+
sourceKind: "aili-owned",
|
|
281
|
+
sourcePath: null,
|
|
282
|
+
sourceHash: hash(general),
|
|
283
|
+
tools: [],
|
|
284
|
+
toolPolicy: "inherit-parent",
|
|
285
|
+
capabilities: ["parent.active"],
|
|
286
|
+
spawns: SPECIALIZED_SELECTORS,
|
|
287
|
+
blocking: false,
|
|
288
|
+
status: "adapted",
|
|
289
|
+
compatibilityReason: "AILI-owned OMP-inspired general worker; effective tools inherit and can only narrow the parent active ceiling.",
|
|
290
|
+
sourceFrontmatterDisposition: {
|
|
291
|
+
mode: "AILI-owned persistent Agent profile",
|
|
292
|
+
hidden: "not applicable",
|
|
293
|
+
permission: "runtime parent-active/capability/policy intersection",
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
166
297
|
await writeFile(MANIFEST_PATH, `${JSON.stringify({
|
|
167
|
-
schemaVersion:
|
|
298
|
+
schemaVersion: 2,
|
|
299
|
+
runtimeAdapterVersion: RUNTIME_ADAPTER_VERSION,
|
|
168
300
|
source: { repository: SOURCE_REPOSITORY, commit: SOURCE_COMMIT },
|
|
301
|
+
bundledSelectors: BUNDLED_SELECTORS,
|
|
169
302
|
outputContract: ["status", "summary", "evidence", "changedFiles", "verification", "blockers", "risks", "confidence"],
|
|
303
|
+
turnAuditFields: ["selector", "profileHash", "sourceHash", "profileVersion", "runtimeAdapterVersion", "effectiveTools", "provider", "model", "thinking"],
|
|
170
304
|
records,
|
|
171
305
|
}, null, 2)}\n`, "utf8");
|
|
172
306
|
}
|
|
173
307
|
|
|
174
308
|
async function verify(): Promise<void> {
|
|
175
|
-
const manifest = JSON.parse(await readFile(MANIFEST_PATH, "utf8")) as {
|
|
309
|
+
const manifest = JSON.parse(await readFile(MANIFEST_PATH, "utf8")) as {
|
|
310
|
+
schemaVersion: number;
|
|
311
|
+
runtimeAdapterVersion: number;
|
|
312
|
+
source: { repository: string; commit: string };
|
|
313
|
+
bundledSelectors: string[];
|
|
314
|
+
turnAuditFields: string[];
|
|
315
|
+
records: RoleRecord[];
|
|
316
|
+
};
|
|
176
317
|
const errors: string[] = [];
|
|
177
|
-
if (manifest.schemaVersion !==
|
|
178
|
-
|
|
318
|
+
if (manifest.schemaVersion !== 2) errors.push("roles manifest schemaVersion must be 2");
|
|
319
|
+
if (manifest.runtimeAdapterVersion !== RUNTIME_ADAPTER_VERSION) errors.push("roles runtimeAdapterVersion mismatch");
|
|
320
|
+
if (manifest.source?.repository !== SOURCE_REPOSITORY || manifest.source?.commit !== SOURCE_COMMIT) errors.push("roles source provenance mismatch");
|
|
321
|
+
if (JSON.stringify(manifest.bundledSelectors) !== JSON.stringify(BUNDLED_SELECTORS)) errors.push("bundled selector inventory mismatch");
|
|
322
|
+
if (JSON.stringify(manifest.turnAuditFields) !== JSON.stringify(["selector", "profileHash", "sourceHash", "profileVersion", "runtimeAdapterVersion", "effectiveTools", "provider", "model", "thinking"])) errors.push("turn audit field inventory mismatch");
|
|
323
|
+
const expected = new Set(BUNDLED_NAMES);
|
|
179
324
|
const seen = new Set<string>();
|
|
325
|
+
const selectors = new Set<string>();
|
|
180
326
|
for (const role of manifest.records ?? []) {
|
|
181
|
-
if (!expected.has(role.name as typeof
|
|
327
|
+
if (!expected.has(role.name as typeof BUNDLED_NAMES[number])) errors.push(`${role.name}: unexpected role`);
|
|
182
328
|
if (seen.has(role.name)) errors.push(`${role.name}: duplicate role`);
|
|
329
|
+
if (selectors.has(role.selector)) errors.push(`${role.selector}: duplicate selector`);
|
|
183
330
|
seen.add(role.name);
|
|
331
|
+
selectors.add(role.selector);
|
|
332
|
+
if (role.profilePath !== `roles/${role.name}.md`) errors.push(`${role.name}: invalid profile path`);
|
|
184
333
|
const content = await readFile(join(ROOT, role.profilePath), "utf8").catch(() => "");
|
|
185
334
|
if (!content) errors.push(`${role.name}: missing profile`);
|
|
186
335
|
else {
|
|
187
336
|
if (hash(content) !== role.profileHash) errors.push(`${role.name}: profile hash drift`);
|
|
188
|
-
if (content.includes("OpenCode subagent") || content.includes(".
|
|
337
|
+
if (content.includes("OpenCode subagent") || content.includes("single-use") || content.includes("--no-session") || content.includes("Recursive AILI task dispatch is unavailable")) errors.push(`${role.name}: obsolete runtime adapter wording remains`);
|
|
338
|
+
if (!content.includes("parent-scoped persistent official Pi Agent session")) errors.push(`${role.name}: persistent adapter contract missing`);
|
|
189
339
|
if (!content.includes("Return exactly one JSON object")) errors.push(`${role.name}: output contract missing`);
|
|
190
340
|
}
|
|
191
|
-
if (
|
|
341
|
+
if (role.profileVersion !== PROFILE_VERSION || role.runtimeAdapterVersion !== RUNTIME_ADAPTER_VERSION) errors.push(`${role.name}: profile/runtime version mismatch`);
|
|
342
|
+
if (!Array.isArray(role.tools) || !Array.isArray(role.capabilities) || !Array.isArray(role.spawns)) errors.push(`${role.name}: invalid policy arrays`);
|
|
343
|
+
if (role.blocking !== false) errors.push(`${role.name}: unexpected blocking policy`);
|
|
192
344
|
if (!role.compatibilityReason) errors.push(`${role.name}: compatibility reason missing`);
|
|
193
345
|
if (role.status !== "adapted" && role.status !== "optional" && role.status !== "blocked") errors.push(`${role.name}: invalid compatibility status`);
|
|
346
|
+
if (role.name === "general") {
|
|
347
|
+
if (role.selector !== "general" || role.toolPolicy !== "inherit-parent") errors.push("general: selector/tool policy mismatch");
|
|
348
|
+
if (JSON.stringify(role.spawns) !== JSON.stringify(SPECIALIZED_SELECTORS)) errors.push("general: spawn inventory mismatch");
|
|
349
|
+
if (role.sourceKind !== "aili-owned" || role.sourcePath !== null) errors.push("general: source ownership mismatch");
|
|
350
|
+
if (content && role.sourceHash !== hash(content)) errors.push("general: source hash mismatch");
|
|
351
|
+
} else {
|
|
352
|
+
if (role.selector !== `aili.${role.name}` || role.toolPolicy !== "static") errors.push(`${role.name}: selector/tool policy mismatch`);
|
|
353
|
+
if (role.spawns.length !== 0) errors.push(`${role.name}: specialized spawns must be empty`);
|
|
354
|
+
if (role.sourceKind !== "canonical-adapter" || role.sourcePath !== `agents/${role.name}.md`) errors.push(`${role.name}: source ownership mismatch`);
|
|
355
|
+
}
|
|
194
356
|
}
|
|
195
357
|
for (const name of expected) if (!seen.has(name)) errors.push(`${name}: missing role`);
|
|
196
358
|
const files = (await readdir(ROLES_DIR)).filter((name) => name.endsWith(".md")).sort();
|
|
197
|
-
const expectedFiles =
|
|
198
|
-
if (JSON.stringify(files) !== JSON.stringify(expectedFiles)) errors.push(`role file inventory mismatch: ${files.length}/
|
|
359
|
+
const expectedFiles = BUNDLED_NAMES.map((name) => `${name}.md`).sort();
|
|
360
|
+
if (JSON.stringify(files) !== JSON.stringify(expectedFiles)) errors.push(`role file inventory mismatch: ${files.length}/20`);
|
|
199
361
|
if (errors.length > 0) throw new Error(errors.join("\n"));
|
|
200
362
|
console.log(`Role profiles verified: ${manifest.records.length}`);
|
|
201
363
|
}
|
package/src/runtime/doctor.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { detectLifecycleConflicts } from "./conflicts.js";
|
|
4
|
-
import { loadRegistry, validateProvenance, validateRegistry } from "./registry.js";
|
|
4
|
+
import { loadRegistry, validateLiveVerification, validateProvenance, validateRegistry } from "./registry.js";
|
|
5
5
|
import { inspectGlobalResources } from "./global-resources.js";
|
|
6
6
|
import { nativeIntegrationDiagnostics } from "./native-integrations.js";
|
|
7
|
-
import {
|
|
7
|
+
import { validateRoleProfiles } from "./roles.js";
|
|
8
8
|
|
|
9
9
|
export type DoctorStatus = "PASS" | "WARN" | "SKIP" | "ERROR" | "UNVERIFIED";
|
|
10
10
|
|
|
@@ -24,23 +24,23 @@ const ROOT = new URL("../../", import.meta.url);
|
|
|
24
24
|
|
|
25
25
|
export async function runDoctor(
|
|
26
26
|
pi: Pick<ExtensionAPI, "getCommands">,
|
|
27
|
-
options: { platform?: NodeJS.Platform } = {},
|
|
27
|
+
options: { platform?: NodeJS.Platform; home?: string } = {},
|
|
28
28
|
): Promise<DoctorReport> {
|
|
29
29
|
const results: DoctorResult[] = [];
|
|
30
30
|
try {
|
|
31
31
|
const packageJson = JSON.parse(await readFile(new URL("package.json", ROOT), "utf8")) as {
|
|
32
32
|
version?: string;
|
|
33
33
|
engines?: { node?: string };
|
|
34
|
-
pi?: { extensions?: string[]; prompts?: string[]; skills?: string[] };
|
|
34
|
+
pi?: { extensions?: string[]; prompts?: string[]; skills?: string[]; themes?: string[] };
|
|
35
35
|
};
|
|
36
36
|
const dependencies = (packageJson as { dependencies?: Record<string, string> }).dependencies ?? {};
|
|
37
|
-
const expectedDependencies = ["
|
|
37
|
+
const expectedDependencies = ["pi-permission-modes@2.2.0", "pi-quota-status@0.3.0", "pi-web-access@0.13.0"];
|
|
38
38
|
const dependencyState = expectedDependencies.every((entry) => {
|
|
39
39
|
const separator = entry.lastIndexOf("@");
|
|
40
40
|
return dependencies[entry.slice(0, separator)] === entry.slice(separator + 1);
|
|
41
41
|
});
|
|
42
42
|
results.push({ id: "package", status: dependencyState ? "PASS" : "ERROR", evidence: `version=${packageJson.version ?? "unverified"}; node=${packageJson.engines?.node ?? "unverified"}; native_dependencies=${dependencyState ? "exact" : "drift"}` });
|
|
43
|
-
const resources = [...(packageJson.pi?.extensions ?? []), ...(packageJson.pi?.prompts ?? []), ...(packageJson.pi?.skills ?? [])];
|
|
43
|
+
const resources = [...(packageJson.pi?.extensions ?? []), ...(packageJson.pi?.prompts ?? []), ...(packageJson.pi?.skills ?? []), ...(packageJson.pi?.themes ?? [])];
|
|
44
44
|
results.push({ id: "package.resources", status: resources.length === 11 ? "PASS" : "ERROR", evidence: `declared=${resources.length}` });
|
|
45
45
|
} catch (error) {
|
|
46
46
|
results.push({ id: "package", status: "ERROR", evidence: boundedError(error) });
|
|
@@ -55,7 +55,8 @@ export async function runDoctor(
|
|
|
55
55
|
results.push({ id: "skill.snapshot", status: "ERROR", evidence: boundedError(error) });
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
const
|
|
58
|
+
const commands = pi.getCommands();
|
|
59
|
+
const conflicts = detectLifecycleConflicts(commands);
|
|
59
60
|
results.push({ id: "rose.prompts", status: conflicts.length === 0 ? "PASS" : "ERROR", evidence: conflicts.length === 0 ? "five lifecycle/review prompts have unique ownership" : `conflicts=${conflicts.map((item) => item.name).join(",")}` });
|
|
60
61
|
|
|
61
62
|
try {
|
|
@@ -68,11 +69,38 @@ export async function runDoctor(
|
|
|
68
69
|
results.push({ id: "capability.registry", status: "ERROR", evidence: boundedError(error) });
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
try {
|
|
73
|
+
const [profileErrors, liveErrors] = await Promise.all([
|
|
74
|
+
validateRoleProfiles(),
|
|
75
|
+
validateLiveVerification(),
|
|
76
|
+
...[
|
|
77
|
+
"src/runtime/persistent-agents/runtime.ts",
|
|
78
|
+
"src/runtime/persistent-agents/storage.ts",
|
|
79
|
+
"src/runtime/persistent-agents/task-coordinator.ts",
|
|
80
|
+
"src/runtime/persistent-agents/hub.ts",
|
|
81
|
+
"src/runtime/persistent-agents/output-delivery.ts",
|
|
82
|
+
].map((path) => readFile(new URL(path, ROOT), "utf8")),
|
|
83
|
+
]);
|
|
84
|
+
results.push({
|
|
85
|
+
id: "roles.agents",
|
|
86
|
+
status: profileErrors.length === 0 ? "PASS" : "ERROR",
|
|
87
|
+
evidence: profileErrors.length === 0 ? "profiles=20; selectors=19 specialized + general" : profileErrors.slice(0, 3).join("; "),
|
|
88
|
+
});
|
|
89
|
+
results.push({
|
|
90
|
+
id: "agent.framework",
|
|
91
|
+
status: profileErrors.length === 0 && liveErrors.length === 0 ? "PASS" : profileErrors.length > 0 ? "ERROR" : "UNVERIFIED",
|
|
92
|
+
evidence: profileErrors.length > 0
|
|
93
|
+
? `public runtime registered but profile validation failed: ${profileErrors.slice(0, 3).join("; ")}`
|
|
94
|
+
: liveErrors.length > 0
|
|
95
|
+
? `public tools=task,hub; legacy subagent absent; ${liveErrors.slice(0, 2).join("; ")}`
|
|
96
|
+
: "public tools=task,hub; legacy subagent absent; deterministic and authorized provider/sandbox/external-workspace gates pass",
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
results.push({ id: "agent.framework", status: "ERROR", evidence: boundedError(error) });
|
|
100
|
+
}
|
|
73
101
|
results.push({ id: "permission.native", ...nativeIntegrationDiagnostics(pi.getCommands()) });
|
|
74
102
|
try {
|
|
75
|
-
const global = await inspectGlobalResources();
|
|
103
|
+
const global = await inspectGlobalResources(options.home);
|
|
76
104
|
const ready = global.appendSystem === "installed" && global.roles.missing.length === 0;
|
|
77
105
|
results.push({ id: "global.resources", status: ready ? "PASS" : "UNVERIFIED", evidence: `append=${global.appendSystem}; roles=${global.roles.installed}/${global.roles.expected}; stale=${global.roles.stale.length}; path=${global.roleDirectory}` });
|
|
78
106
|
} catch (error) {
|
|
@@ -55,7 +55,11 @@ async function requireRealDirectory(path: string): Promise<void> {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async function expectedRoles(): Promise<RoleProfile[]> {
|
|
58
|
-
|
|
58
|
+
// The 19 specialized profiles retain their existing global `aili.<role>`
|
|
59
|
+
// installation surface. `general` is owned by the new internal task runtime;
|
|
60
|
+
// installing it under agents/aili would create the forbidden `aili.general`
|
|
61
|
+
// alias.
|
|
62
|
+
return (await loadRoleProfiles()).filter((role) => role.selector !== "general");
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
export async function inspectGlobalResources(home = homedir()): Promise<GlobalResourceReport> {
|
package/src/runtime/index.ts
CHANGED
|
@@ -2,14 +2,14 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import type { RuntimeComponent } from "./contracts.js";
|
|
3
3
|
import { registerRoseContext } from "./rose-context.js";
|
|
4
4
|
import { registerDoctor } from "./doctor.js";
|
|
5
|
-
import {
|
|
5
|
+
import { registerPersistentAgentRuntime } from "./persistent-agents/production.js";
|
|
6
6
|
import { registerNativeIntegrations } from "./native-integrations.js";
|
|
7
7
|
import { registerGlobalResourceCommand } from "./global-resources.js";
|
|
8
8
|
|
|
9
9
|
export const runtimeComponents: readonly RuntimeComponent[] = [
|
|
10
10
|
{ id: "rose-context", availability: "available", register: registerRoseContext },
|
|
11
11
|
{ id: "lifecycle-routing", availability: "available" },
|
|
12
|
-
{ id: "task-runtime", availability: "available", register:
|
|
12
|
+
{ id: "task-runtime", availability: "available", register: registerPersistentAgentRuntime },
|
|
13
13
|
{ id: "native-integrations", availability: "available", register: registerNativeIntegrations },
|
|
14
14
|
{ id: "global-resources", availability: "available", register: registerGlobalResourceCommand },
|
|
15
15
|
{ id: "capability-registry", availability: "available" },
|