@rosetears/aili-pi 0.1.6 → 0.1.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/README.md +9 -5
- package/THIRD_PARTY_NOTICES.md +6 -6
- package/licenses/pi-permission-modes-MIT.txt +21 -0
- package/manifests/adapter-evidence.json +10 -9
- package/manifests/live-verification.json +19 -11
- package/manifests/provenance.json +7 -7
- package/manifests/sbom.json +3 -3
- package/manifests/skill-compatibility.json +12 -12
- package/manifests/subagent-provenance.json +13 -3
- package/package.json +5 -3
- package/scripts/generate-provenance.ts +1 -1
- package/scripts/sync-permission-modes.ts +135 -0
- package/src/runtime/native-integrations.ts +5 -5
- package/src/runtime/package-resolution.ts +8 -0
- package/src/runtime/registry.ts +138 -9
- package/src/runtime/subagents.ts +85 -10
- package/src/vendor/pi-permission-modes/index.ts +692 -0
- package/src/vendor/pi-permission-modes/resolve.ts +142 -0
- package/upstream/pi-permission-modes.lock.json +47 -0
package/src/runtime/registry.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { access, readFile, readdir } from "node:fs/promises";
|
|
3
3
|
import { loadRoleProfiles, validateRoleProfiles } from "./roles.ts";
|
|
4
|
+
import { resolvePermissionModesPackageRoot } from "./package-resolution.ts";
|
|
4
5
|
|
|
5
6
|
const ROOT = new URL("../../", import.meta.url);
|
|
6
7
|
|
|
@@ -143,30 +144,158 @@ export async function validateLiveVerification(): Promise<string[]> {
|
|
|
143
144
|
const errors: string[] = [];
|
|
144
145
|
try {
|
|
145
146
|
const evidence = await json<{
|
|
146
|
-
schemaVersion?: number; platform?: string; piVersion?: string; status?: string;
|
|
147
|
-
probes?: Array<{
|
|
147
|
+
schemaVersion?: number; platform?: string; piVersion?: string; subagentVersion?: string; status?: string;
|
|
148
|
+
probes?: Array<{
|
|
149
|
+
id?: string;
|
|
150
|
+
status?: string;
|
|
151
|
+
changedFiles?: number | null;
|
|
152
|
+
backendRequest?: string;
|
|
153
|
+
resolvedBackend?: string | null;
|
|
154
|
+
}>;
|
|
148
155
|
implementation?: Record<string, string>;
|
|
149
156
|
}>("manifests/live-verification.json");
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
if (
|
|
158
|
+
evidence.schemaVersion !== 2 ||
|
|
159
|
+
evidence.platform !== "linux" ||
|
|
160
|
+
evidence.piVersion !== "0.81.1" ||
|
|
161
|
+
evidence.subagentVersion !== "0.4.8" ||
|
|
162
|
+
evidence.status !== "passed"
|
|
163
|
+
) {
|
|
164
|
+
errors.push("live verification: compatibility identity is incomplete or non-pass");
|
|
155
165
|
}
|
|
166
|
+
const fixtureProbe = evidence.probes?.find((probe) => probe.id === "generic-subagent-fixtures");
|
|
167
|
+
const defaultProbe = evidence.probes?.find((probe) => probe.id === "generic-agentless-default-path");
|
|
168
|
+
const credentialProbe = evidence.probes?.find((probe) => probe.id === "generic-credential-guard-explicit-headless");
|
|
169
|
+
if (
|
|
170
|
+
evidence.probes?.length !== 3 ||
|
|
171
|
+
fixtureProbe?.status !== "passed" ||
|
|
172
|
+
defaultProbe?.status !== "passed" ||
|
|
173
|
+
defaultProbe.backendRequest !== "omitted" ||
|
|
174
|
+
defaultProbe.resolvedBackend !== "headless" ||
|
|
175
|
+
defaultProbe.changedFiles !== 0 ||
|
|
176
|
+
credentialProbe?.status !== "passed" ||
|
|
177
|
+
credentialProbe.backendRequest !== "explicit-headless" ||
|
|
178
|
+
credentialProbe.resolvedBackend !== "headless" ||
|
|
179
|
+
credentialProbe.changedFiles !== 0
|
|
180
|
+
) {
|
|
181
|
+
errors.push("live verification: default-path/headless probes are missing, ambiguous, non-pass, or mutated files");
|
|
182
|
+
}
|
|
183
|
+
const requiredImplementation = [
|
|
184
|
+
"src/runtime/subagents.ts",
|
|
185
|
+
"tests/unit/subagents.test.ts",
|
|
186
|
+
"tests/integration/live-subagent.test.ts",
|
|
187
|
+
];
|
|
156
188
|
for (const [filePath, expected] of Object.entries(evidence.implementation ?? {})) {
|
|
157
189
|
const content = await readFile(new URL(filePath, ROOT), "utf8");
|
|
158
190
|
const actual = createHash("sha256").update(content).digest("hex");
|
|
159
191
|
if (actual !== expected) errors.push(`live verification: implementation drift ${filePath}`);
|
|
160
192
|
}
|
|
161
|
-
if (Object.keys(evidence.implementation ?? {}).
|
|
193
|
+
if (JSON.stringify(Object.keys(evidence.implementation ?? {}).sort()) !== JSON.stringify(requiredImplementation.sort())) {
|
|
194
|
+
errors.push("live verification: implementation binding must contain the exact default-path files");
|
|
195
|
+
}
|
|
162
196
|
} catch (error) {
|
|
163
197
|
errors.push(`live verification: ${error instanceof Error ? error.message : String(error)}`);
|
|
164
198
|
}
|
|
165
199
|
return errors;
|
|
166
200
|
}
|
|
167
201
|
|
|
168
|
-
export async function
|
|
202
|
+
export async function validatePermissionModeAdaptation(): Promise<string[]> {
|
|
169
203
|
const errors: string[] = [];
|
|
204
|
+
try {
|
|
205
|
+
const lock = await json<{
|
|
206
|
+
schemaVersion?: number;
|
|
207
|
+
package?: { name?: string; version?: string; revision?: string; license?: string };
|
|
208
|
+
upstreamFiles?: Array<{ path?: string; sha256?: string }>;
|
|
209
|
+
adaptedFiles?: Array<{ path?: string; sha256?: string }>;
|
|
210
|
+
localChanges?: string[];
|
|
211
|
+
generatedBy?: string;
|
|
212
|
+
verification?: string[];
|
|
213
|
+
}>("upstream/pi-permission-modes.lock.json");
|
|
214
|
+
const expectedLocalChanges = [
|
|
215
|
+
"Package-owned adapted entry redirects all unchanged sibling modules to the exact pi-permission-modes dependency while owning resolve.ts locally.",
|
|
216
|
+
"matchPattern compiles its anchored glob RegExp with dotAll so * and ? include ECMAScript line terminators.",
|
|
217
|
+
];
|
|
218
|
+
const expectedVerification = [
|
|
219
|
+
"npm run verify:permission-modes",
|
|
220
|
+
"tests/unit/permission-patterns.test.ts",
|
|
221
|
+
"tests/integration/permission-modes.test.ts",
|
|
222
|
+
];
|
|
223
|
+
if (
|
|
224
|
+
lock.schemaVersion !== 1 ||
|
|
225
|
+
lock.package?.name !== "pi-permission-modes" ||
|
|
226
|
+
lock.package.version !== "2.2.0" ||
|
|
227
|
+
lock.package.revision !== "23d65d10a53b67043cae42322acf9044d6edb196" ||
|
|
228
|
+
lock.package.license !== "MIT" ||
|
|
229
|
+
lock.upstreamFiles?.length !== 3 ||
|
|
230
|
+
lock.adaptedFiles?.length !== 3 ||
|
|
231
|
+
JSON.stringify(lock.localChanges) !== JSON.stringify(expectedLocalChanges) ||
|
|
232
|
+
lock.generatedBy !== "scripts/sync-permission-modes.ts" ||
|
|
233
|
+
JSON.stringify(lock.verification) !== JSON.stringify(expectedVerification)
|
|
234
|
+
) {
|
|
235
|
+
errors.push("permission adaptation: lock identity or inventory is incomplete");
|
|
236
|
+
return errors;
|
|
237
|
+
}
|
|
238
|
+
const expectedUpstream = {
|
|
239
|
+
"src/index.ts": "fd4462a3b7ba986af734c2e17ba8ea7178df56c933e87ed444ba90ba24c2fd5b",
|
|
240
|
+
"src/resolve.ts": "13f52a4a9c08d7a55f5f9d03f97302d864768838fb3e9fca2051cb7d94a0ae82",
|
|
241
|
+
LICENSE: "d87cb99b43f6bf8771e57be83485db11b977b9dfa21b6bd201b8d3d370bdce43",
|
|
242
|
+
};
|
|
243
|
+
const upstreamIdentity = Object.fromEntries((lock.upstreamFiles ?? []).map((record) => [record.path, record.sha256]));
|
|
244
|
+
if (JSON.stringify(upstreamIdentity) !== JSON.stringify(expectedUpstream)) {
|
|
245
|
+
errors.push("permission adaptation: upstream baseline hashes do not match the accepted 2.2.0 revision");
|
|
246
|
+
}
|
|
247
|
+
const expectedAdapted = {
|
|
248
|
+
"src/vendor/pi-permission-modes/index.ts": "8bfa0364967a2b76e9900cc72b8f434ea1cfa6520899c7827488220f2e288ee8",
|
|
249
|
+
"src/vendor/pi-permission-modes/resolve.ts": "f71688f847495da5122724f75c5ebe3b41066b3d3cac74cbe99f66b9906404f6",
|
|
250
|
+
"licenses/pi-permission-modes-MIT.txt": "d87cb99b43f6bf8771e57be83485db11b977b9dfa21b6bd201b8d3d370bdce43",
|
|
251
|
+
};
|
|
252
|
+
const adaptedIdentity = Object.fromEntries((lock.adaptedFiles ?? []).map((record) => [record.path, record.sha256]));
|
|
253
|
+
if (JSON.stringify(adaptedIdentity) !== JSON.stringify(expectedAdapted)) {
|
|
254
|
+
errors.push("permission adaptation: adapted hashes or file inventory do not match the accepted generated output");
|
|
255
|
+
}
|
|
256
|
+
const permissionPackageRoot = resolvePermissionModesPackageRoot();
|
|
257
|
+
const installedPackage = JSON.parse(await readFile(new URL("package.json", permissionPackageRoot), "utf8")) as {
|
|
258
|
+
name?: string;
|
|
259
|
+
version?: string;
|
|
260
|
+
license?: string;
|
|
261
|
+
};
|
|
262
|
+
if (installedPackage.name !== "pi-permission-modes" || installedPackage.version !== "2.2.0" || installedPackage.license !== "MIT") {
|
|
263
|
+
errors.push("permission adaptation: resolved dependency identity is not exact pi-permission-modes@2.2.0 MIT");
|
|
264
|
+
}
|
|
265
|
+
for (const [kind, records, sourceRoot] of [
|
|
266
|
+
["upstream", lock.upstreamFiles, permissionPackageRoot],
|
|
267
|
+
["adapted", lock.adaptedFiles, ROOT],
|
|
268
|
+
] as const) {
|
|
269
|
+
for (const record of records ?? []) {
|
|
270
|
+
const path = record.path ?? "";
|
|
271
|
+
if (!path || path.startsWith("/") || path.split("/").includes("..") || !/^[0-9a-f]{64}$/.test(record.sha256 ?? "")) {
|
|
272
|
+
errors.push(`permission adaptation: unsafe or incomplete ${kind} record ${path || "(missing)"}`);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const content = await readFile(new URL(path, sourceRoot), "utf8");
|
|
276
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
277
|
+
if (actual !== record.sha256) errors.push(`permission adaptation: ${kind} drift ${path}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const [nativeIntegration, adaptedResolve] = await Promise.all([
|
|
281
|
+
readFile(new URL("src/runtime/native-integrations.ts", ROOT), "utf8"),
|
|
282
|
+
readFile(new URL("src/vendor/pi-permission-modes/resolve.ts", ROOT), "utf8"),
|
|
283
|
+
]);
|
|
284
|
+
const adaptedEntryCount = nativeIntegration.match(/"\.\.\/vendor\/pi-permission-modes\/index\.ts"/g)?.length ?? 0;
|
|
285
|
+
if (adaptedEntryCount !== 1 || nativeIntegration.includes('"pi-permission-modes/src/index.ts"')) {
|
|
286
|
+
errors.push("permission adaptation: native integration is not bound exactly once and exclusively to the adapted entry");
|
|
287
|
+
}
|
|
288
|
+
if (!adaptedResolve.includes('return new RegExp(re, "s").test(t);') || adaptedResolve.includes("return new RegExp(re).test(t);")) {
|
|
289
|
+
errors.push("permission adaptation: line-terminator-safe matcher semantic is missing");
|
|
290
|
+
}
|
|
291
|
+
} catch (error) {
|
|
292
|
+
errors.push(`permission adaptation: ${error instanceof Error ? error.message : String(error)}`);
|
|
293
|
+
}
|
|
294
|
+
return errors;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function validateProvenance(): Promise<string[]> {
|
|
298
|
+
const errors: string[] = [...await validatePermissionModeAdaptation()];
|
|
170
299
|
try {
|
|
171
300
|
const provenance = await json<{ schemaVersion: number; sources: Array<{ name: string; revision: string; license: string; status: string; repository: string; sourceFiles: string[]; symbols: string[]; localChanges: string[]; verification: string[] }> }>("manifests/provenance.json");
|
|
172
301
|
const sbom = await json<{ spdxVersion?: string; packages?: Array<{ SPDXID?: string; name?: string; licenseDeclared?: string }> }>("manifests/sbom.json");
|
package/src/runtime/subagents.ts
CHANGED
|
@@ -22,6 +22,10 @@ type GenericTool = {
|
|
|
22
22
|
[key: string]: unknown;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
type SubagentCompatibilityPlan =
|
|
26
|
+
| { kind: "forward"; params: unknown }
|
|
27
|
+
| { kind: "reject"; requestedBackend: "inline" | "auto"; error: string };
|
|
28
|
+
|
|
25
29
|
type AgentHeading = {
|
|
26
30
|
label: "Agent" | "Agents";
|
|
27
31
|
summary: string;
|
|
@@ -29,6 +33,10 @@ type AgentHeading = {
|
|
|
29
33
|
|
|
30
34
|
const MAX_AGENT_NAME_LENGTH = 48;
|
|
31
35
|
const MAX_AGENT_SUMMARY_LENGTH = 120;
|
|
36
|
+
const INLINE_COMPATIBILITY_ERROR =
|
|
37
|
+
"inline backend is incompatible with Pi 0.81.1 and @agwab/pi-subagent 0.4.8; use backend \"headless\"";
|
|
38
|
+
const MIXED_PARALLEL_COMPATIBILITY_ERROR =
|
|
39
|
+
"auto backend cannot safely combine visible tasks with ordinary non-visible, non-sandboxed tasks on Pi 0.81.1 and @agwab/pi-subagent 0.4.8; split the fan-out or choose one explicit compatible backend";
|
|
32
40
|
|
|
33
41
|
function isRecord(value: unknown): value is UnknownRecord {
|
|
34
42
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -88,6 +96,69 @@ function withRequiredExtensions(value: unknown): unknown {
|
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
|
|
99
|
+
function sandboxRequested(value: unknown): boolean {
|
|
100
|
+
return value !== undefined && value !== null && value !== false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function taskSelector(
|
|
104
|
+
task: UnknownRecord,
|
|
105
|
+
parent: UnknownRecord,
|
|
106
|
+
): { visible: boolean; sandboxed: boolean; plain: boolean } {
|
|
107
|
+
const visible = task.visible === undefined ? parent.visible === true : task.visible === true;
|
|
108
|
+
const sandbox = Object.hasOwn(task, "sandbox") ? task.sandbox : parent.sandbox;
|
|
109
|
+
const sandboxed = sandboxRequested(sandbox);
|
|
110
|
+
return { visible, sandboxed, plain: !visible && !sandboxed };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Select a Pi-0.81.1-compatible backend without changing compatible explicit,
|
|
115
|
+
* visible, sandboxed, or lifecycle requests. Parallel auto calls are evaluated
|
|
116
|
+
* per task because task-level visible/sandbox values override their parent.
|
|
117
|
+
*/
|
|
118
|
+
export function normalizeSubagentCompatibility(params: unknown): SubagentCompatibilityPlan {
|
|
119
|
+
if (!isRecord(params) || (params.action !== undefined && params.action !== "run")) {
|
|
120
|
+
return { kind: "forward", params };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (params.backend === "inline") {
|
|
124
|
+
return { kind: "reject", requestedBackend: "inline", error: INLINE_COMPATIBILITY_ERROR };
|
|
125
|
+
}
|
|
126
|
+
if (params.backend !== undefined && params.backend !== "auto") return { kind: "forward", params };
|
|
127
|
+
|
|
128
|
+
if (Array.isArray(params.tasks)) {
|
|
129
|
+
if (params.tasks.length === 0 || params.tasks.some((task) => !isRecord(task))) {
|
|
130
|
+
return { kind: "forward", params };
|
|
131
|
+
}
|
|
132
|
+
const selectors = params.tasks.map((task) => taskSelector(task as UnknownRecord, params));
|
|
133
|
+
const hasPlain = selectors.some((selector) => selector.plain);
|
|
134
|
+
const hasVisible = selectors.some((selector) => selector.visible);
|
|
135
|
+
if (hasPlain && hasVisible) {
|
|
136
|
+
return { kind: "reject", requestedBackend: "auto", error: MIXED_PARALLEL_COMPATIBILITY_ERROR };
|
|
137
|
+
}
|
|
138
|
+
if (hasPlain) return { kind: "forward", params: { ...params, backend: "headless" } };
|
|
139
|
+
return { kind: "forward", params };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (params.visible === true || sandboxRequested(params.sandbox)) return { kind: "forward", params };
|
|
143
|
+
return { kind: "forward", params: { ...params, backend: "headless" } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function compatibilityFailure(plan: Extract<SubagentCompatibilityPlan, { kind: "reject" }>): UnknownRecord {
|
|
147
|
+
const payload = {
|
|
148
|
+
tool: "subagent",
|
|
149
|
+
...(plan.requestedBackend === "inline" ? { backend: "inline" } : {}),
|
|
150
|
+
requestedBackend: plan.requestedBackend,
|
|
151
|
+
status: "failed",
|
|
152
|
+
failureKind: "validation",
|
|
153
|
+
error: plan.error,
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
157
|
+
details: { compatibility: payload },
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
91
162
|
/**
|
|
92
163
|
* Adds the immutable child credential guard to every run path while preserving
|
|
93
164
|
* the ambient AILI permission-mode extension and all caller options.
|
|
@@ -113,7 +184,9 @@ export function wrapSubagentTool(tool: GenericTool): GenericTool {
|
|
|
113
184
|
...(typeof renderCall === "function"
|
|
114
185
|
? {
|
|
115
186
|
renderCall(args: unknown, theme: RenderTheme): Component {
|
|
116
|
-
const
|
|
187
|
+
const plan = normalizeSubagentCompatibility(args);
|
|
188
|
+
const effectiveArgs = plan.kind === "forward" ? plan.params : args;
|
|
189
|
+
const upstream = renderCall(effectiveArgs, theme);
|
|
117
190
|
const heading = requestedAgentHeading(args);
|
|
118
191
|
if (!heading) return upstream;
|
|
119
192
|
const container = new Container();
|
|
@@ -128,22 +201,24 @@ export function wrapSubagentTool(tool: GenericTool): GenericTool {
|
|
|
128
201
|
}
|
|
129
202
|
: {}),
|
|
130
203
|
async execute(...args: unknown[]) {
|
|
131
|
-
// Pi
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
const paramsIndex = args.length
|
|
204
|
+
// Current Pi uses execute(id, params, ...); older direct fixtures may call
|
|
205
|
+
// execute(params). Detect the object position rather than mistaking a
|
|
206
|
+
// signal/callback for params.
|
|
207
|
+
const paramsIndex = isRecord(args[0]) || args.length === 1 ? 0 : 1;
|
|
208
|
+
const plan = normalizeSubagentCompatibility(args[paramsIndex]);
|
|
209
|
+
if (plan.kind === "reject") return compatibilityFailure(plan);
|
|
135
210
|
const protectedArgs = [...args];
|
|
136
|
-
protectedArgs[paramsIndex] = protectSubagentParams(
|
|
211
|
+
protectedArgs[paramsIndex] = protectSubagentParams(plan.params);
|
|
137
212
|
return await execute(...protectedArgs);
|
|
138
213
|
},
|
|
139
214
|
};
|
|
140
215
|
}
|
|
141
216
|
|
|
142
217
|
/**
|
|
143
|
-
* Registers the upstream `subagent`
|
|
144
|
-
*
|
|
145
|
-
* injects non-removable credential
|
|
146
|
-
*
|
|
218
|
+
* Registers the upstream `subagent` name, schema, lifecycle, and worker engine.
|
|
219
|
+
* AILI wraps backend selection for the pinned SDK compatibility window, adds a
|
|
220
|
+
* bounded requested-Agent heading, and injects non-removable credential
|
|
221
|
+
* protection while each child loads permission modes exactly once.
|
|
147
222
|
*/
|
|
148
223
|
export async function registerSubagent(pi: ExtensionAPI): Promise<void> {
|
|
149
224
|
let genericToolRegistered = false;
|