@tryinget/pi-agent-registry 0.3.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/LICENSE +78 -0
- package/README.md +238 -0
- package/docs/engineering.local.md +90 -0
- package/docs/project/2026-08-27-agent-registry.md +287 -0
- package/docs/project/foundation.md +31 -0
- package/docs/project/vision.md +18 -0
- package/examples/.gitkeep +0 -0
- package/extensions/pi-agent-registry.ts +378 -0
- package/package.json +105 -0
- package/policy/engineering-lane.json +34 -0
- package/policy/security-policy.json +10 -0
- package/prompts/implementation-planning.md +20 -0
- package/prompts/security-review.md +20 -0
- package/scripts/fleet-lint.mjs +82 -0
- package/src/.gitkeep +0 -0
- package/src/agent-skill-resolver.ts +50 -0
- package/src/asc-execution-surface.ts +64 -0
- package/src/dispatch-authorization.ts +237 -0
- package/src/dispatch-contract.ts +89 -0
- package/src/dispatch-receipt.ts +326 -0
- package/src/dispatch-request.ts +135 -0
- package/src/dispatch.ts +498 -0
- package/src/ec-profiles.ts +392 -0
- package/src/fleet-git-snapshot.ts +323 -0
- package/src/fleet-lint-provenance.ts +356 -0
- package/src/fleet-lint-repository.ts +450 -0
- package/src/fleet-lint-skills.ts +131 -0
- package/src/fleet-lint-types.ts +113 -0
- package/src/fleet-lint-utils.ts +66 -0
- package/src/fleet-lint.ts +375 -0
- package/src/fleet-prompt-compiler.ts +155 -0
- package/src/manifest.ts +678 -0
- package/src/registry-discovery.ts +225 -0
- package/src/registry.ts +280 -0
- package/src/sessions-dir.ts +30 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: registers the agent_registry and dispatch_agent tools plus the /agents command for standing-agent manifests.
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing the extension entrypoint, tool schemas, or registry lifecycle wiring.
|
|
5
|
+
// ---
|
|
6
|
+
|
|
7
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
8
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { Type } from "typebox";
|
|
10
|
+
import { dispatchAgent } from "../src/dispatch.ts";
|
|
11
|
+
import { lintAgentFleet } from "../src/fleet-lint.ts";
|
|
12
|
+
import { type AgentRegistry, AgentRegistryError, createAgentRegistry } from "../src/registry.ts";
|
|
13
|
+
|
|
14
|
+
interface RegistryHandle {
|
|
15
|
+
registry: AgentRegistry;
|
|
16
|
+
loadedAt: string;
|
|
17
|
+
error?: undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface RegistryFailure {
|
|
21
|
+
registry?: undefined;
|
|
22
|
+
loadedAt: string;
|
|
23
|
+
error: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type RegistryState = RegistryHandle | RegistryFailure;
|
|
27
|
+
|
|
28
|
+
function textResult(text: string, details: Record<string, unknown>) {
|
|
29
|
+
return { content: [{ type: "text" as const, text }], details };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatAgentListing(agent: {
|
|
33
|
+
name: string;
|
|
34
|
+
display_name?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
role?: string;
|
|
37
|
+
creation_task?: string;
|
|
38
|
+
tools: string[];
|
|
39
|
+
skills: { profile?: string; extra?: string[] };
|
|
40
|
+
defaults: { model: string | null; thinking: string };
|
|
41
|
+
activities: string[];
|
|
42
|
+
manifestPath: string;
|
|
43
|
+
}): string {
|
|
44
|
+
const parts = [
|
|
45
|
+
`- ${agent.name}${agent.display_name ? ` (${agent.display_name})` : ""}${agent.version ? ` v${agent.version}` : ""}`,
|
|
46
|
+
` role: ${agent.role ?? "missing"} | creation_task: ${agent.creation_task ?? "missing"}`,
|
|
47
|
+
` tools: ${agent.tools.length > 0 ? agent.tools.join(",") : "none (read-only declaration)"}`,
|
|
48
|
+
` skills: ${
|
|
49
|
+
[
|
|
50
|
+
agent.skills.profile ? `profile=${agent.skills.profile}` : undefined,
|
|
51
|
+
agent.skills.extra ? `extra=[${agent.skills.extra.join(", ")}]` : undefined,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" ") || "none"
|
|
55
|
+
}`,
|
|
56
|
+
` model: ${agent.defaults.model ?? "inherit"} | thinking: ${agent.defaults.thinking}`,
|
|
57
|
+
];
|
|
58
|
+
if (agent.activities.length > 0) {
|
|
59
|
+
parts.push(` activities: ${agent.activities.join(", ")}`);
|
|
60
|
+
}
|
|
61
|
+
parts.push(` manifest: ${agent.manifestPath}`);
|
|
62
|
+
return parts.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default function (pi: ExtensionAPI) {
|
|
66
|
+
let registryState: RegistryState | undefined;
|
|
67
|
+
let registryPromise: Promise<RegistryState> | undefined;
|
|
68
|
+
|
|
69
|
+
const loadRegistry = (): Promise<RegistryState> => {
|
|
70
|
+
registryPromise ??= createAgentRegistry()
|
|
71
|
+
.then((registry): RegistryState => ({ registry, loadedAt: new Date().toISOString() }))
|
|
72
|
+
.catch(
|
|
73
|
+
(error): RegistryState => ({
|
|
74
|
+
loadedAt: new Date().toISOString(),
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
return registryPromise;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const getRegistry = async (): Promise<AgentRegistry> => {
|
|
82
|
+
if (!registryState) {
|
|
83
|
+
registryState = await loadRegistry();
|
|
84
|
+
}
|
|
85
|
+
const state = registryState;
|
|
86
|
+
if (!state.registry) {
|
|
87
|
+
throw new AgentRegistryError(`agent registry failed to load: ${state.error}`);
|
|
88
|
+
}
|
|
89
|
+
return state.registry;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
pi.registerCommand("agents", {
|
|
93
|
+
description: "List standing agents registered from agent.json manifests",
|
|
94
|
+
handler: async (_args, ctx) => {
|
|
95
|
+
try {
|
|
96
|
+
const registry = await getRegistry();
|
|
97
|
+
const agents = registry.list();
|
|
98
|
+
const lines =
|
|
99
|
+
agents.length > 0
|
|
100
|
+
? agents.map(formatAgentListing)
|
|
101
|
+
: ["No agent.json manifests found.", `Roots scanned: ${registry.roots.join(", ")}`];
|
|
102
|
+
if (ctx.hasUI) {
|
|
103
|
+
ctx.ui.notify(`Registered agents (${agents.length}):\n${lines.join("\n")}`, "info");
|
|
104
|
+
} else {
|
|
105
|
+
console.log(`Registered agents (${agents.length}):\n${lines.join("\n")}`);
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
109
|
+
if (ctx.hasUI) {
|
|
110
|
+
ctx.ui.notify(message, "error");
|
|
111
|
+
} else {
|
|
112
|
+
console.error(message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
pi.registerTool({
|
|
119
|
+
name: "agent_registry",
|
|
120
|
+
label: "Agent Registry",
|
|
121
|
+
description: `Inspect standing agents declared by agent.json manifests (ai-society.agent/1 convention).
|
|
122
|
+
|
|
123
|
+
Actions:
|
|
124
|
+
- list: registered agent names, tool allowlists, skill profiles, defaults, and manifest paths.
|
|
125
|
+
- show: one agent's resolved launch metadata (system prompt source, rendered scope, skills, tools, model/thinking).
|
|
126
|
+
- validate: compatibility check for already loaded manifests and resolved metadata.
|
|
127
|
+
- lint: aggregate every canonical agent-* repository, including missing/malformed manifests, into one immutable-observation fleet report with stable diagnostics and no execution effects.
|
|
128
|
+
- refresh: reload manifests from the configured roots.
|
|
129
|
+
|
|
130
|
+
Fleet layout: ONE STANDALONE REPO PER AGENT. The canonical fleet home is ~/ai-society/agents/agent-* (conventions: softwareco-agents/docs/agent-registry.md); company/lane agent-* homes are forward-compatible extras. agent.json lives at each agent-repo root; never nested inside product repos. Override discovery with PI_AGENT_REGISTRY_ROOTS (colon-separated patterns).
|
|
131
|
+
Engineering-core skill profiles come from PI_AGENT_REGISTRY_EC_PROFILES or ~/ai-society/core/engineering-core/skills/profiles.json.
|
|
132
|
+
|
|
133
|
+
Fail-closed: unknown skill names, unknown EC profiles, missing files, or schema mismatches surface as resolution errors; use validate to see them without dispatching.`,
|
|
134
|
+
promptSnippet:
|
|
135
|
+
"List, inspect, or immutably lint standing-agent fleet manifests without dispatching.",
|
|
136
|
+
promptGuidelines: [
|
|
137
|
+
"Use agent_registry for read-only discovery, validation, and fleet lint; dispatch_agent executes only the Fleet Phase-2 exact-task read-only contract.",
|
|
138
|
+
"Use agent_registry action=lint for aggregate fleet health, immutable revision, prompt freshness, role/creation-task, profile, collision, provenance, and advisory staleness diagnostics; it never dispatches or runs fleet scripts.",
|
|
139
|
+
"Never treat the advisory scope.repos as a sandbox; it is rendered into the system prompt only.",
|
|
140
|
+
],
|
|
141
|
+
parameters: Type.Object({
|
|
142
|
+
action: StringEnum(["list", "show", "validate", "lint", "refresh"] as const, {
|
|
143
|
+
description: "Registry inspection action",
|
|
144
|
+
}),
|
|
145
|
+
agent: Type.Optional(Type.String({ description: "Agent name (required for action=show)" })),
|
|
146
|
+
}),
|
|
147
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
148
|
+
const action = params.action;
|
|
149
|
+
if (action === "refresh") {
|
|
150
|
+
registryPromise = undefined;
|
|
151
|
+
registryState = undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (action === "lint") {
|
|
155
|
+
let report: Awaited<ReturnType<typeof lintAgentFleet>>;
|
|
156
|
+
try {
|
|
157
|
+
report = await lintAgentFleet();
|
|
158
|
+
} catch {
|
|
159
|
+
throw new AgentRegistryError(
|
|
160
|
+
"fleet lint failed before a bounded immutable-observation report could be produced",
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const displayed = report.diagnostics.slice(0, 20);
|
|
164
|
+
return textResult(
|
|
165
|
+
[
|
|
166
|
+
`fleet lint ${report.summary.status}: repositories=${report.summary.includedRepositories}/${report.summary.candidateRepositories}, manifests=${report.summary.manifests}, errors=${report.summary.errors}, warnings=${report.summary.warnings}, digest=${report.reportSha256}`,
|
|
167
|
+
`profile source: ${report.profileSource.status} ${report.profileSource.schema} ${report.profileSource.rawSha256}`,
|
|
168
|
+
...displayed.map(
|
|
169
|
+
(entry) =>
|
|
170
|
+
`- ${entry.severity.toUpperCase()} ${entry.repo} ${entry.code}${entry.path ? ` (${entry.path})` : ""}: ${entry.message}`,
|
|
171
|
+
),
|
|
172
|
+
...(report.diagnostics.length > displayed.length
|
|
173
|
+
? [
|
|
174
|
+
`- ... ${report.diagnostics.length - displayed.length} additional diagnostic(s) in details`,
|
|
175
|
+
]
|
|
176
|
+
: []),
|
|
177
|
+
"Observation only: no agent was selected, authorized, dispatched, claimed active, or retired.",
|
|
178
|
+
].join("\n"),
|
|
179
|
+
report as unknown as Record<string, unknown>,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const registry = await getRegistry();
|
|
184
|
+
|
|
185
|
+
if (action === "list" || action === "refresh") {
|
|
186
|
+
const agents = registry.list();
|
|
187
|
+
const lines =
|
|
188
|
+
agents.length > 0
|
|
189
|
+
? agents.map(formatAgentListing)
|
|
190
|
+
: [
|
|
191
|
+
"No agent.json manifests found.",
|
|
192
|
+
`Roots scanned: ${registry.roots.join(", ")}`,
|
|
193
|
+
"Set PI_AGENT_REGISTRY_ROOTS (colon-separated agent-repo patterns) to scan other roots.",
|
|
194
|
+
];
|
|
195
|
+
return textResult(lines.join("\n"), {
|
|
196
|
+
action,
|
|
197
|
+
agents: agents.map((agent) => agent.name),
|
|
198
|
+
roots: registry.roots,
|
|
199
|
+
ecProfilesPath: registry.ec.path,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (action === "show") {
|
|
204
|
+
const name = params.agent?.trim();
|
|
205
|
+
if (!name) {
|
|
206
|
+
return textResult("action=show requires an agent name.", {
|
|
207
|
+
action,
|
|
208
|
+
error: "missing_agent",
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
const manifest = registry.get(name);
|
|
212
|
+
if (!manifest) {
|
|
213
|
+
return textResult(
|
|
214
|
+
`unknown agent: ${name} (registered: ${[...registry.agents.keys()].sort().join(", ") || "none"})`,
|
|
215
|
+
{ action, error: "unknown_agent" },
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const launch = await registry.resolve(name);
|
|
219
|
+
try {
|
|
220
|
+
return textResult(
|
|
221
|
+
[
|
|
222
|
+
`agent: ${launch.name}${manifest.display_name ? ` (${manifest.display_name})` : ""}`,
|
|
223
|
+
`role: ${manifest.role ?? "missing"} | creation_task: ${manifest.creation_task ?? "missing"}`,
|
|
224
|
+
`system_prompt_file: ${manifest.system_prompt_file} (${launch.systemPrompt.length} chars composed)`,
|
|
225
|
+
`tools: ${launch.tools}`,
|
|
226
|
+
`thinking: ${launch.thinking} | model: ${launch.model ?? "inherit"}`,
|
|
227
|
+
`extensions: ${launch.extensions.join(", ") || "none"}`,
|
|
228
|
+
`skills: ${launch.loadedSkills.join(", ") || "none"}`,
|
|
229
|
+
`scope.repos: ${launch.scopeRepos.join(", ") || "none"}`,
|
|
230
|
+
`scope.forbidden: ${launch.scopeForbidden.join(", ") || "none"}`,
|
|
231
|
+
`activities: ${launch.activities.join(", ") || "none"}`,
|
|
232
|
+
`manifest: ${manifest.manifestPath}`,
|
|
233
|
+
].join("\n"),
|
|
234
|
+
{
|
|
235
|
+
action,
|
|
236
|
+
agent: launch.name,
|
|
237
|
+
role: manifest.role,
|
|
238
|
+
creationTask: manifest.creation_task,
|
|
239
|
+
systemPromptChars: launch.systemPrompt.length,
|
|
240
|
+
tools: launch.tools,
|
|
241
|
+
thinking: launch.thinking,
|
|
242
|
+
model: launch.model,
|
|
243
|
+
extensions: launch.extensions,
|
|
244
|
+
loadedSkills: launch.loadedSkills,
|
|
245
|
+
scopeRepos: launch.scopeRepos,
|
|
246
|
+
scopeForbidden: launch.scopeForbidden,
|
|
247
|
+
activities: launch.activities,
|
|
248
|
+
manifestPath: manifest.manifestPath,
|
|
249
|
+
},
|
|
250
|
+
);
|
|
251
|
+
} finally {
|
|
252
|
+
await launch.cleanup();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// action === "validate"
|
|
257
|
+
const results: Array<{ agent: string; ok: boolean; error?: string }> = [];
|
|
258
|
+
for (const name of [...registry.agents.keys()].sort()) {
|
|
259
|
+
let launch: Awaited<ReturnType<AgentRegistry["resolve"]>> | undefined;
|
|
260
|
+
try {
|
|
261
|
+
launch = await registry.resolve(name);
|
|
262
|
+
results.push({ agent: name, ok: true });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
results.push({
|
|
265
|
+
agent: name,
|
|
266
|
+
ok: false,
|
|
267
|
+
error: error instanceof Error ? error.message : String(error),
|
|
268
|
+
});
|
|
269
|
+
} finally {
|
|
270
|
+
await launch?.cleanup();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const failed = results.filter((entry) => !entry.ok);
|
|
274
|
+
return textResult(
|
|
275
|
+
[
|
|
276
|
+
`validated ${results.length} agent manifest(s): ${results.length - failed.length} ok, ${failed.length} failed`,
|
|
277
|
+
...results.map((entry) =>
|
|
278
|
+
entry.ok ? `- ${entry.agent}: ok` : `- ${entry.agent}: FAILED — ${entry.error}`,
|
|
279
|
+
),
|
|
280
|
+
].join("\n"),
|
|
281
|
+
{ action, results, cwd: ctx.cwd },
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
pi.registerTool({
|
|
287
|
+
name: "dispatch_agent",
|
|
288
|
+
label: "Dispatch Standing Agent (Phase 2)",
|
|
289
|
+
description: `Dispatch ONE registered standing agent for ONE exact claimed AK task, read-only, through the ASC-owned execution runtime (Fleet Phase 2).
|
|
290
|
+
|
|
291
|
+
Contract (every gate fails closed with confirmed_no_effects before any spawn):
|
|
292
|
+
- agent: registered agent.json name; declared tools must be a non-empty subset of [read, bash];
|
|
293
|
+
- task: exact AK task id that must be claimed with a live lease for this repository;
|
|
294
|
+
- objective: bounded read-only objective; the child task contract enforces mutationPolicy=read_only;
|
|
295
|
+
- the agent repository must be clean so the dispatch binds an immutable revision (commit + tree + manifest/prompt blob digests);
|
|
296
|
+
- one SETTLED dispatch per (agent, exact task) pair: failed attempts stay as immutable receipts, bounded to three;
|
|
297
|
+
- the dispatch-origin repository is observed (HEAD + porcelain digest) across the dispatch window; mutation observed → no AK evidence;
|
|
298
|
+
- a settled, provably-read-only dispatch publishes one write-once receipt (0o400, canonical sha256) and records one typed AK evidence row (check-type standing-agent-dispatch).
|
|
299
|
+
|
|
300
|
+
Phase-2 boundaries: standing-agent dispatch is one level deep (children carry PI_PROVENANCE_STANDING_AGENT_DISPATCH); sessions/capacity/spawn machinery stays ASC-owned; visible Ghostty standing agents, lifecycle-v2 permits, and orchestrator fleet integration remain later fleet phases.`,
|
|
301
|
+
promptSnippet:
|
|
302
|
+
"Dispatch one read-only standing agent for one exact claimed AK task with an immutable receipt and AK evidence.",
|
|
303
|
+
promptGuidelines: [
|
|
304
|
+
"Use agent_registry for discovery/validation first; dispatch_agent requires an exact claimed AK task id and a read-only agent.",
|
|
305
|
+
"One dispatch per (agent, exact task): a completed pair is rejected with its receipt digest.",
|
|
306
|
+
"Do not route standing agents through fork_peer_spawn, scout_peer_spawn, candidate_peer_spawn, or loop_execute; those are separate capabilities, not standing-agent routes.",
|
|
307
|
+
],
|
|
308
|
+
parameters: Type.Object({
|
|
309
|
+
agent: Type.String({ description: "Registered standing-agent name." }),
|
|
310
|
+
task: Type.Integer({
|
|
311
|
+
description: "Exact AK task id that authorizes this one read-only dispatch.",
|
|
312
|
+
minimum: 1,
|
|
313
|
+
}),
|
|
314
|
+
objective: Type.String({
|
|
315
|
+
description:
|
|
316
|
+
"Bounded read-only objective for the dispatched standing agent (wrapped in the Phase-2 read-only task contract).",
|
|
317
|
+
maxLength: 100_000,
|
|
318
|
+
}),
|
|
319
|
+
}),
|
|
320
|
+
async execute(_toolCallId, params, signal, onUpdate, ctx) {
|
|
321
|
+
const registry = await getRegistry();
|
|
322
|
+
const outcome = await dispatchAgent(
|
|
323
|
+
{ agent: params.agent, task: params.task, objective: params.objective },
|
|
324
|
+
{ registry },
|
|
325
|
+
{
|
|
326
|
+
cwd: ctx.cwd,
|
|
327
|
+
model: ctx.model,
|
|
328
|
+
thinkingLevel: ctx.thinkingLevel,
|
|
329
|
+
sessionManager: ctx.sessionManager,
|
|
330
|
+
},
|
|
331
|
+
(update) =>
|
|
332
|
+
onUpdate?.({
|
|
333
|
+
content: [{ type: "text" as const, text: update.text }],
|
|
334
|
+
details: update.details as Record<string, unknown>,
|
|
335
|
+
}),
|
|
336
|
+
signal,
|
|
337
|
+
);
|
|
338
|
+
if (outcome.ok) {
|
|
339
|
+
return textResult(
|
|
340
|
+
[
|
|
341
|
+
`standing-agent dispatch settled: ${outcome.receipt.agent.name} for AK task ${outcome.receipt.task.id}`,
|
|
342
|
+
`receipt: ${outcome.receiptPath} (sha256 ${outcome.receipt.receiptSha256})`,
|
|
343
|
+
`asc: dispatchId=${outcome.receipt.dispatch.asc.dispatchId} session=${outcome.receipt.dispatch.asc.sessionName} effect=${outcome.receipt.dispatch.asc.effectDisposition}`,
|
|
344
|
+
`observation: noMutationObserved=${outcome.receipt.observation.noMutationObserved} agentRevision=${outcome.receipt.agent.agentRepo.commit}`,
|
|
345
|
+
...(outcome.evidenceId !== undefined
|
|
346
|
+
? [`ak evidence: #${outcome.evidenceId} (check-type standing-agent-dispatch)`]
|
|
347
|
+
: []),
|
|
348
|
+
"--- child output ---",
|
|
349
|
+
outcome.output,
|
|
350
|
+
].join("\n"),
|
|
351
|
+
{
|
|
352
|
+
ok: true,
|
|
353
|
+
phase: "fleet_phase_2",
|
|
354
|
+
reason: undefined,
|
|
355
|
+
receipt: outcome.receipt as unknown as Record<string, unknown>,
|
|
356
|
+
receiptPath: outcome.receiptPath,
|
|
357
|
+
...(outcome.evidenceId !== undefined ? { evidenceId: outcome.evidenceId } : {}),
|
|
358
|
+
},
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
content: [{ type: "text" as const, text: outcome.message }],
|
|
363
|
+
details: {
|
|
364
|
+
ok: false,
|
|
365
|
+
phase: "fleet_phase_2",
|
|
366
|
+
reason: outcome.reason,
|
|
367
|
+
effectDisposition: outcome.effectDisposition,
|
|
368
|
+
spawnAttempted: outcome.spawnAttempted,
|
|
369
|
+
...(outcome.receipt
|
|
370
|
+
? { receipt: outcome.receipt as unknown as Record<string, unknown> }
|
|
371
|
+
: {}),
|
|
372
|
+
...(outcome.receiptPath ? { receiptPath: outcome.receiptPath } : {}),
|
|
373
|
+
},
|
|
374
|
+
isError: true,
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tryinget/pi-agent-registry",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Pi standing-agent manifest inspection, immutable fleet lint, and the Fleet Phase-2 exact-task read-only dispatch contract",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/tryingET/pi-extensions.git",
|
|
10
|
+
"directory": "packages/pi-agent-registry"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/tryingET/pi-extensions/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/tryingET/pi-extensions/tree/main/packages/pi-agent-registry",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package",
|
|
18
|
+
"pi-extension",
|
|
19
|
+
"pi-agent-registry",
|
|
20
|
+
"monorepo",
|
|
21
|
+
"agent-manifest",
|
|
22
|
+
"subagent-dispatch"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org/",
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22.19.0"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"pi-agent-registry-lint": "scripts/fleet-lint.mjs"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"fix": "bash ./scripts/quality-gate.sh fix",
|
|
36
|
+
"lint": "bash ./scripts/quality-gate.sh lint",
|
|
37
|
+
"typecheck": "bash ./scripts/quality-gate.sh typecheck",
|
|
38
|
+
"quality:pre-commit": "bash ./scripts/quality-gate.sh pre-commit",
|
|
39
|
+
"quality:pre-push": "bash ./scripts/quality-gate.sh pre-push",
|
|
40
|
+
"quality:ci": "bash ./scripts/quality-gate.sh ci",
|
|
41
|
+
"check": "npm run quality:ci",
|
|
42
|
+
"test": "npm run quality:ci",
|
|
43
|
+
"fleet:lint": "node ./scripts/fleet-lint.mjs",
|
|
44
|
+
"docs:list": "node ~/ai-society/core/agent-scripts/scripts/docs-list.mjs",
|
|
45
|
+
"docs:list:workspace": "node ~/ai-society/core/agent-scripts/scripts/docs-list.mjs --workspace --discover",
|
|
46
|
+
"docs:list:json": "node ~/ai-society/core/agent-scripts/scripts/docs-list.mjs --json",
|
|
47
|
+
"release:check": "bash ./scripts/release-check.sh",
|
|
48
|
+
"release:check:quick": "SKIP_PI_SMOKE=1 bash ./scripts/release-check.sh",
|
|
49
|
+
"loop-doctor": "bash -lc 'node --version; npm --version; npm pkg get name version >/dev/null; git status --short -- . || true; exit 0'",
|
|
50
|
+
"loop-verify-fast": "npm run quality:pre-commit",
|
|
51
|
+
"loop-impact-plan": "bash -lc 'echo \"loop-impact-plan: package-local impact planner is coarse; run npm run loop-impact-run for the full package gate.\"; git status --short -- . || true'",
|
|
52
|
+
"loop-impact-run": "npm run check",
|
|
53
|
+
"loop-impact-wide": "npm run check",
|
|
54
|
+
"loop-landing-check": "npm run check",
|
|
55
|
+
"prepack": "node ../pi-interaction/scripts/prepare-publish-manifest.mjs prepack",
|
|
56
|
+
"postpack": "node ../pi-interaction/scripts/prepare-publish-manifest.mjs postpack"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"extensions/pi-agent-registry.ts",
|
|
60
|
+
"src",
|
|
61
|
+
"scripts/fleet-lint.mjs",
|
|
62
|
+
"prompts",
|
|
63
|
+
"examples",
|
|
64
|
+
"policy/security-policy.json",
|
|
65
|
+
"policy/engineering-lane.json",
|
|
66
|
+
"docs/engineering.local.md",
|
|
67
|
+
"docs/project/vision.md",
|
|
68
|
+
"docs/project/foundation.md",
|
|
69
|
+
"docs/project/2026-08-27-agent-registry.md"
|
|
70
|
+
],
|
|
71
|
+
"pi": {
|
|
72
|
+
"extensions": [
|
|
73
|
+
"./extensions/pi-agent-registry.ts"
|
|
74
|
+
],
|
|
75
|
+
"prompts": [
|
|
76
|
+
"./prompts"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"x-pi-template": {
|
|
80
|
+
"scaffoldMode": "simple-package",
|
|
81
|
+
"workspacePath": "packages/pi-agent-registry",
|
|
82
|
+
"releaseComponent": "pi-agent-registry",
|
|
83
|
+
"releaseConfigMode": "component"
|
|
84
|
+
},
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"@biomejs/biome": "2.3.14",
|
|
87
|
+
"@earendil-works/pi-agent-core": "0.84.3",
|
|
88
|
+
"@earendil-works/pi-ai": "0.84.3",
|
|
89
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
90
|
+
"@types/node": "^25.3.3",
|
|
91
|
+
"@typescript/native-preview": "7.0.0-dev.20260417.1"
|
|
92
|
+
},
|
|
93
|
+
"overrides": {
|
|
94
|
+
"fast-xml-parser": "5.7.0"
|
|
95
|
+
},
|
|
96
|
+
"peerDependencies": {
|
|
97
|
+
"@earendil-works/pi-ai": "*",
|
|
98
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
99
|
+
"typebox": "*"
|
|
100
|
+
},
|
|
101
|
+
"dependencies": {
|
|
102
|
+
"@tryinget/pi-autonomous-session-control": "0.7.0",
|
|
103
|
+
"typebox": "1.3.7"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lane": "ts",
|
|
3
|
+
"engineering_core": {
|
|
4
|
+
"tool": "engineering-core",
|
|
5
|
+
"lane": "pi-ts",
|
|
6
|
+
"repository": "https://github.com/tryingET/core_engineering-core",
|
|
7
|
+
"ref": "workspace-local-unpinned",
|
|
8
|
+
"command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core show pi-ts",
|
|
9
|
+
"catalog_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core catalog --pretty",
|
|
10
|
+
"list_disciplines_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-disciplines",
|
|
11
|
+
"list_templates_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-templates",
|
|
12
|
+
"disciplines": [
|
|
13
|
+
"validation",
|
|
14
|
+
"testing",
|
|
15
|
+
"security-privacy",
|
|
16
|
+
"documentation",
|
|
17
|
+
"dependency-governance",
|
|
18
|
+
"specification-and-dsls",
|
|
19
|
+
"engineering-reasoning"
|
|
20
|
+
],
|
|
21
|
+
"loop_validation": {
|
|
22
|
+
"version": "repo-loop-validation-v1",
|
|
23
|
+
"contract_doc": "docs/engineering.local.md#repo-loop-validation",
|
|
24
|
+
"commands": {
|
|
25
|
+
"loop-doctor": "npm run loop-doctor",
|
|
26
|
+
"loop-verify-fast": "npm run loop-verify-fast",
|
|
27
|
+
"loop-impact-plan": "npm run loop-impact-plan",
|
|
28
|
+
"loop-impact-run": "npm run loop-impact-run",
|
|
29
|
+
"loop-impact-wide": "npm run loop-impact-wide",
|
|
30
|
+
"loop-landing-check": "npm run loop-landing-check"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Monorepo package implementation planning prompt template."
|
|
3
|
+
read_when:
|
|
4
|
+
- "Using or updating the monorepo package implementation-planning prompt template."
|
|
5
|
+
description: Draft an implementation plan for a requested change
|
|
6
|
+
system4d:
|
|
7
|
+
container: "Prompt template for implementation planning."
|
|
8
|
+
compass: "Turn requests into actionable, risk-aware plans."
|
|
9
|
+
engine: "Scope -> tasks -> validation -> rollout."
|
|
10
|
+
fog: "Hidden constraints unless assumptions are surfaced."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Create an implementation plan for this request: $@
|
|
14
|
+
|
|
15
|
+
Include:
|
|
16
|
+
- Scope and non-goals
|
|
17
|
+
- Key risks and mitigations
|
|
18
|
+
- Step-by-step implementation tasks
|
|
19
|
+
- Validation commands and expected outcomes
|
|
20
|
+
- Rollout and rollback notes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Monorepo package security review prompt template."
|
|
3
|
+
read_when:
|
|
4
|
+
- "Using or updating the monorepo package security-review prompt template."
|
|
5
|
+
description: Review a change for security risks and mitigations
|
|
6
|
+
system4d:
|
|
7
|
+
container: "Prompt template for security-focused review."
|
|
8
|
+
compass: "Identify practical vulnerabilities before release."
|
|
9
|
+
engine: "Threats -> impact -> mitigations -> verification."
|
|
10
|
+
fog: "Partial context can hide exploit paths."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Review this change for security concerns: $@
|
|
14
|
+
|
|
15
|
+
Focus on:
|
|
16
|
+
- Input validation and injection risk
|
|
17
|
+
- Privilege boundaries and secret handling
|
|
18
|
+
- Dependency and supply-chain risk
|
|
19
|
+
- Safe failure modes and logging
|
|
20
|
+
- Concrete remediations with priority
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// summary: emits one read-only immutable-observation fleet lint report and exits nonzero on unhealthy fleet state.
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { loadEcProfiles } from "../src/ec-profiles.ts";
|
|
5
|
+
import { FleetLintInfrastructureError, lintAgentFleet } from "../src/fleet-lint.ts";
|
|
6
|
+
|
|
7
|
+
class FleetLintCliError extends Error {}
|
|
8
|
+
|
|
9
|
+
function usage() {
|
|
10
|
+
console.error(`usage: pi-agent-registry-lint [options]
|
|
11
|
+
|
|
12
|
+
options:
|
|
13
|
+
--root PATH agent repo root or agent-* pattern (repeatable)
|
|
14
|
+
--ec-profiles PATH engineering-core skills/profiles.json
|
|
15
|
+
--observed-at RFC3339 deterministic observation time override
|
|
16
|
+
--stale-after-days N advisory lifecycle threshold (default 90)
|
|
17
|
+
--max-repositories N bounded candidate count (default 5000)
|
|
18
|
+
--pretty pretty-print JSON
|
|
19
|
+
--allow-unhealthy exit zero when report status is unhealthy
|
|
20
|
+
--help show this help`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const { values } = parseArgs({
|
|
25
|
+
options: {
|
|
26
|
+
root: { type: "string", multiple: true },
|
|
27
|
+
"ec-profiles": { type: "string" },
|
|
28
|
+
"observed-at": { type: "string" },
|
|
29
|
+
"stale-after-days": { type: "string" },
|
|
30
|
+
"max-repositories": { type: "string" },
|
|
31
|
+
pretty: { type: "boolean" },
|
|
32
|
+
"allow-unhealthy": { type: "boolean" },
|
|
33
|
+
help: { type: "boolean" },
|
|
34
|
+
},
|
|
35
|
+
allowPositionals: false,
|
|
36
|
+
strict: true,
|
|
37
|
+
});
|
|
38
|
+
if (values.help) {
|
|
39
|
+
usage();
|
|
40
|
+
process.exitCode = 0;
|
|
41
|
+
} else {
|
|
42
|
+
const integer = (value, label) => {
|
|
43
|
+
if (value === undefined) return undefined;
|
|
44
|
+
const parsed = Number.parseInt(value, 10);
|
|
45
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0 || String(parsed) !== value) {
|
|
46
|
+
throw new FleetLintCliError(`${label} must be a positive safe integer`);
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
};
|
|
50
|
+
let ec;
|
|
51
|
+
if (values["ec-profiles"]) {
|
|
52
|
+
try {
|
|
53
|
+
ec = await loadEcProfiles(values["ec-profiles"]);
|
|
54
|
+
} catch {
|
|
55
|
+
throw new FleetLintInfrastructureError(
|
|
56
|
+
"profile.source_load_failed",
|
|
57
|
+
"engineering-core skill profile source could not be loaded",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const report = await lintAgentFleet({
|
|
62
|
+
...(values.root ? { roots: values.root } : {}),
|
|
63
|
+
...(ec ? { ec } : {}),
|
|
64
|
+
...(values["observed-at"] ? { observedAt: values["observed-at"] } : {}),
|
|
65
|
+
...(values["stale-after-days"]
|
|
66
|
+
? { staleAfterDays: integer(values["stale-after-days"], "--stale-after-days") }
|
|
67
|
+
: {}),
|
|
68
|
+
...(values["max-repositories"]
|
|
69
|
+
? { maxRepositories: integer(values["max-repositories"], "--max-repositories") }
|
|
70
|
+
: {}),
|
|
71
|
+
});
|
|
72
|
+
console.log(JSON.stringify(report, null, values.pretty ? 2 : 0));
|
|
73
|
+
process.exitCode = report.summary.status === "healthy" || values["allow-unhealthy"] ? 0 : 1;
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const message =
|
|
77
|
+
error instanceof FleetLintInfrastructureError || error instanceof FleetLintCliError
|
|
78
|
+
? error.message
|
|
79
|
+
: "invalid command-line arguments or fleet lint infrastructure failure";
|
|
80
|
+
console.error(`pi-agent-registry-lint failed: ${message}`);
|
|
81
|
+
process.exitCode = 2;
|
|
82
|
+
}
|
package/src/.gitkeep
ADDED
|
File without changes
|