blun-king-cli 9.1.509 → 9.1.511
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/CHANGELOG.md +12 -0
- package/LIESMICH.txt +12 -1
- package/README.md +12 -1
- package/agent-spine-plugin/.claude-plugin/marketplace.json +1 -1
- package/agent-spine-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-spine-plugin/.codex-plugin/plugin.json +2 -1
- package/agent-spine-plugin/CHANGELOG.md +70 -8
- package/agent-spine-plugin/README.md +1 -1
- package/agent-spine-plugin/blun.plugin.json +33 -33
- package/agent-spine-plugin/docs/acceptance.md +2 -2
- package/agent-spine-plugin/docs/gateway-runtime.md +8 -1
- package/agent-spine-plugin/docs/host-integration.md +34 -34
- package/agent-spine-plugin/docs/preflight-recall.md +69 -0
- package/agent-spine-plugin/docs/relationships.md +6 -0
- package/agent-spine-plugin/hooks/codex.json +47 -0
- package/agent-spine-plugin/hooks/hooks.json +11 -0
- package/agent-spine-plugin/hooks/version.json +2 -2
- package/agent-spine-plugin/package.json +4 -4
- package/agent-spine-plugin/scripts/check-hosts.js +53 -51
- package/agent-spine-plugin/scripts/check-install.js +53 -35
- package/agent-spine-plugin/scripts/release-check.js +11 -10
- package/agent-spine-plugin/skills/agent-spine/SKILL.md +1 -1
- package/agent-spine-plugin/src/cli.js +46 -1
- package/agent-spine-plugin/src/hook.js +168 -90
- package/agent-spine-plugin/src/index.js +6 -0
- package/agent-spine-plugin/src/lib/acceptance.js +40 -0
- package/agent-spine-plugin/src/lib/audit.js +9 -2
- package/agent-spine-plugin/src/lib/graph.js +22 -4
- package/agent-spine-plugin/src/lib/persona-runtime.js +103 -31
- package/agent-spine-plugin/src/lib/preflight.js +678 -0
- package/agent-spine-plugin/src/lib/source-roots.js +32 -32
- package/agent-spine-plugin/src/version.js +1 -1
- package/agent-spine-plugin/src/worker.js +20 -3
- package/bin/read-batch-policy.cjs +32 -0
- package/bin/turn-tool-performance-policy.cjs +1 -0
- package/blun.mjs +58 -2
- package/package.json +3 -2
|
@@ -228,6 +228,86 @@ function nextEventTypes(previous, binding) {
|
|
|
228
228
|
return types;
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
function runtimeEntityAttributes(persona, binding, existing) {
|
|
232
|
+
return {
|
|
233
|
+
...(existing?.attributes || {}),
|
|
234
|
+
runtimeKind: persona.kind,
|
|
235
|
+
identityBindingId: persona.bindingId,
|
|
236
|
+
identityStatus: persona.status,
|
|
237
|
+
sourceBinding: binding?.sourceBinding || null
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function sameRuntimeEntity(entity, persona, binding) {
|
|
242
|
+
if (!entity) return false;
|
|
243
|
+
const expectedKind = persona.kind === "bot" ? "agent" : persona.kind;
|
|
244
|
+
const expectedPrivacy = persona.groupId ? "group" : "shared";
|
|
245
|
+
const attributes = runtimeEntityAttributes(persona, binding, entity);
|
|
246
|
+
return entity.kind === expectedKind && entity.displayName === persona.displayName
|
|
247
|
+
&& entity.privacy === expectedPrivacy
|
|
248
|
+
&& JSON.stringify(entity.attributes) === JSON.stringify(attributes);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function reconcilePersonaGraph(paths, policy, runtime) {
|
|
252
|
+
let { graph } = await loadGraph(paths.catalog.root);
|
|
253
|
+
const changes = { groupsCreated: 0, entitiesUpdated: 0, membershipsAdded: 0, membershipsRemoved: 0 };
|
|
254
|
+
const activeGroupIds = [...new Set(runtime.personas
|
|
255
|
+
.filter((item) => item.status === "active" && item.groupId !== null)
|
|
256
|
+
.map((item) => item.groupId))].sort();
|
|
257
|
+
|
|
258
|
+
for (const groupId of activeGroupIds) {
|
|
259
|
+
const existing = graph.entities.find((item) => item.id === groupId);
|
|
260
|
+
if (existing && existing.kind !== "group") {
|
|
261
|
+
throw new Error(`authenticated persona group conflicts with a non-group entity: ${groupId}`);
|
|
262
|
+
}
|
|
263
|
+
if (existing?.privacy === "private") {
|
|
264
|
+
throw new Error(`authenticated persona group cannot use private relationship visibility: ${groupId}`);
|
|
265
|
+
}
|
|
266
|
+
if (!existing) {
|
|
267
|
+
await upsertEntity({ root: paths.catalog.root, id: groupId, kind: "group", privacy: "group",
|
|
268
|
+
attributes: { identitySource: "authenticated-persona-roster" }, confidence: 1 });
|
|
269
|
+
changes.groupsCreated += 1;
|
|
270
|
+
graph = (await loadGraph(paths.catalog.root)).graph;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for (const persona of runtime.personas) {
|
|
275
|
+
const binding = policy.bindings.find((item) => item.id === persona.bindingId);
|
|
276
|
+
let existing = graph.entities.find((item) => item.id === persona.personaId);
|
|
277
|
+
if (existing && existing.kind !== (persona.kind === "bot" ? "agent" : persona.kind)) {
|
|
278
|
+
throw new Error(`authenticated persona conflicts with an existing entity kind: ${persona.personaId}`);
|
|
279
|
+
}
|
|
280
|
+
if (!sameRuntimeEntity(existing, persona, binding)) {
|
|
281
|
+
await upsertEntity({ root: paths.catalog.root, id: persona.personaId,
|
|
282
|
+
kind: persona.kind === "bot" ? "agent" : persona.kind, displayName: persona.displayName,
|
|
283
|
+
aliases: existing?.aliases || [], attributes: runtimeEntityAttributes(persona, binding, existing),
|
|
284
|
+
sourceDocument: existing?.sourceDocument
|
|
285
|
+
&& paths.catalog.documents.some((item) => item.relativePath === existing.sourceDocument)
|
|
286
|
+
? existing.sourceDocument : null,
|
|
287
|
+
privacy: persona.groupId ? "group" : "shared", confidence: 1 });
|
|
288
|
+
changes.entitiesUpdated += 1;
|
|
289
|
+
graph = (await loadGraph(paths.catalog.root)).graph;
|
|
290
|
+
existing = graph.entities.find((item) => item.id === persona.personaId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const memberships = graph.entityEdges.filter((edge) => edge.from === persona.personaId && edge.relation === "member-of");
|
|
294
|
+
for (const edge of memberships.filter((item) => persona.status !== "active" || item.to !== persona.groupId)) {
|
|
295
|
+
await unlinkEntities({ root: paths.catalog.root, from: edge.from, to: edge.to, relation: edge.relation });
|
|
296
|
+
changes.membershipsRemoved += 1;
|
|
297
|
+
graph = (await loadGraph(paths.catalog.root)).graph;
|
|
298
|
+
}
|
|
299
|
+
if (persona.status === "active" && persona.groupId !== null
|
|
300
|
+
&& !graph.entityEdges.some((edge) => edge.from === persona.personaId && edge.to === persona.groupId
|
|
301
|
+
&& edge.relation === "member-of" && edge.privacy === "group")) {
|
|
302
|
+
await linkEntities({ root: paths.catalog.root, from: persona.personaId, to: persona.groupId,
|
|
303
|
+
relation: "member-of", reason: "Authenticated roster membership; context only.", confidence: 1, privacy: "group" });
|
|
304
|
+
changes.membershipsAdded += 1;
|
|
305
|
+
graph = (await loadGraph(paths.catalog.root)).graph;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return changes;
|
|
309
|
+
}
|
|
310
|
+
|
|
231
311
|
export async function applyPersonaRoster({ root = process.cwd(), bindings, rosterScopes = [], confirmation, now = new Date() }) {
|
|
232
312
|
if (confirmation !== CONFIRMATION) throw new Error("persona roster changes require explicit local owner confirmation");
|
|
233
313
|
if (!Array.isArray(bindings) || bindings.length > 256 || (!bindings.length && !rosterScopes.length)) {
|
|
@@ -242,7 +322,6 @@ export async function applyPersonaRoster({ root = process.cwd(), bindings, roste
|
|
|
242
322
|
const observedAt = at(now);
|
|
243
323
|
const normalized = bindings.map((item) => normalizeBinding(item, observedAt));
|
|
244
324
|
let changed = false;
|
|
245
|
-
const membershipRemovals = [];
|
|
246
325
|
const scopedRosterKeys = new Set([
|
|
247
326
|
...normalized.map((item) => [item.authenticator, item.issuer, item.tenantId, item.host, item.profileId].join("\0")),
|
|
248
327
|
...rosterScopes.map((item) => [item.authenticator, item.issuer, item.tenantId, item.host, item.profileId].join("\0"))
|
|
@@ -272,9 +351,6 @@ export async function applyPersonaRoster({ root = process.cwd(), bindings, roste
|
|
|
272
351
|
policy.bindings = policy.bindings.filter((item) => item.id !== binding.id);
|
|
273
352
|
policy.bindings.push(binding);
|
|
274
353
|
const previous = runtime.personas.find((item) => item.personaId === binding.personaId);
|
|
275
|
-
if (previous?.groupId && (previous.groupId !== binding.groupId || !binding.active)) {
|
|
276
|
-
membershipRemovals.push({ personaId: binding.personaId, groupId: previous.groupId });
|
|
277
|
-
}
|
|
278
354
|
const types = nextEventTypes(previous, binding);
|
|
279
355
|
let sequence = previous?.sequence || 0;
|
|
280
356
|
for (const type of types) {
|
|
@@ -293,30 +369,18 @@ export async function applyPersonaRoster({ root = process.cwd(), bindings, roste
|
|
|
293
369
|
sequence, updatedAt: observedAt, authority: "identity-state-only" });
|
|
294
370
|
}
|
|
295
371
|
}
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
for (const membership of membershipRemovals) {
|
|
304
|
-
await unlinkEntities({ root: paths.catalog.root, from: membership.personaId, to: membership.groupId, relation: "member-of" });
|
|
305
|
-
}
|
|
306
|
-
for (const persona of runtime.personas.filter((item) => item.status === "active")) {
|
|
307
|
-
const binding = policy.bindings.find((item) => item.id === persona.bindingId);
|
|
308
|
-
await upsertEntity({ root: paths.catalog.root, id: persona.personaId, kind: persona.kind === "bot" ? "agent" : persona.kind,
|
|
309
|
-
displayName: persona.displayName, attributes: { runtimeKind: persona.kind, identityBindingId: persona.bindingId,
|
|
310
|
-
identityStatus: persona.status, sourceBinding: binding?.sourceBinding || null }, privacy: persona.groupId ? "group" : "shared", confidence: 1 });
|
|
311
|
-
if (persona.groupId) {
|
|
312
|
-
const { graph } = await loadGraph(paths.catalog.root);
|
|
313
|
-
if (graph.entities.some((item) => item.id === persona.groupId && item.kind === "group")) {
|
|
314
|
-
await linkEntities({ root: paths.catalog.root, from: persona.personaId, to: persona.groupId,
|
|
315
|
-
relation: "member-of", reason: "Authenticated roster membership; context only.", confidence: 1, privacy: "group" });
|
|
316
|
-
}
|
|
317
|
-
}
|
|
372
|
+
if (changed) {
|
|
373
|
+
policy.bindings.sort((a, b) => a.id.localeCompare(b.id));
|
|
374
|
+
runtime.personas.sort((a, b) => a.personaId.localeCompare(b.personaId));
|
|
375
|
+
runtime.events.sort((a, b) => a.observedAt.localeCompare(b.observedAt) || a.eventId.localeCompare(b.eventId));
|
|
376
|
+
policy.revision += 1;
|
|
377
|
+
runtime.revision += 1;
|
|
378
|
+
await Promise.all([writeJson(paths.personaPolicyPath, policy), writeJson(paths.personaRuntimePath, runtime)]);
|
|
318
379
|
}
|
|
319
|
-
|
|
380
|
+
const graphChanges = await reconcilePersonaGraph(paths, policy, runtime);
|
|
381
|
+
const graphReconciled = Object.values(graphChanges).some((value) => value > 0);
|
|
382
|
+
return { policy, runtime, personaPolicyPath: paths.personaPolicyPath,
|
|
383
|
+
personaRuntimePath: paths.personaRuntimePath, duplicate: !changed, graphReconciled, graphChanges };
|
|
320
384
|
});
|
|
321
385
|
}
|
|
322
386
|
|
|
@@ -334,9 +398,9 @@ function normalizeNativeDiscovery(value) {
|
|
|
334
398
|
}
|
|
335
399
|
|
|
336
400
|
function nativeAgentDirectory(scope, catalog, env) {
|
|
337
|
-
if (scope.scope === "project") return join(catalog.root, scope.host === "claude" ? ".claude" : ".codex", "agents");
|
|
338
|
-
const home = scope.host === "claude" ? env.CLAUDE_CONFIG_DIR
|
|
339
|
-
: env.CODEX_HOME || env.BLUN_HOME;
|
|
401
|
+
if (scope.scope === "project") return join(catalog.root, scope.host === "claude" ? ".claude" : ".codex", "agents");
|
|
402
|
+
const home = scope.host === "claude" ? env.CLAUDE_CONFIG_DIR
|
|
403
|
+
: env.CODEX_HOME || env.BLUN_HOME;
|
|
340
404
|
if (home) {
|
|
341
405
|
if (!isAbsolute(home)) throw new Error(scope.host + " home override must be absolute");
|
|
342
406
|
return join(home, "agents");
|
|
@@ -434,7 +498,9 @@ export async function syncPersonaRosterFromEnvironment({ root = process.cwd(), e
|
|
|
434
498
|
tenantId: scope.tenantId, host: scope.host, profileId: scope.profileId }));
|
|
435
499
|
const result = await applyPersonaRoster({ root: catalog.root, bindings, rosterScopes,
|
|
436
500
|
confirmation: CONFIRMATION, now: value.observedAt || now });
|
|
437
|
-
return { configured: true, changed: !result.duplicate
|
|
501
|
+
return { configured: true, changed: !result.duplicate || result.graphReconciled,
|
|
502
|
+
rosterChanged: !result.duplicate, graphReconciled: result.graphReconciled, graphChanges: result.graphChanges,
|
|
503
|
+
revision: value.revision,
|
|
438
504
|
personas: result.runtime.personas.length, nativeManifests: nativeBindings.length,
|
|
439
505
|
rosterDigest: digest(JSON.stringify(value)) };
|
|
440
506
|
}
|
|
@@ -466,7 +532,13 @@ export function personaRuntimeFindings(policy, runtime, graph = null) {
|
|
|
466
532
|
const entity = graph.entities.find((item) => item.id === persona.personaId);
|
|
467
533
|
const expectedKind = persona.kind === "bot" ? "agent" : persona.kind;
|
|
468
534
|
if (!entity || entity.kind !== expectedKind) findings.push("persona-graph-mismatch:" + persona.personaId);
|
|
535
|
+
if (entity?.attributes?.identityStatus !== persona.status) findings.push("persona-graph-status-mismatch:" + persona.personaId);
|
|
469
536
|
const memberships = graph.entityEdges.filter((edge) => edge.from === persona.personaId && edge.relation === "member-of");
|
|
537
|
+
const group = persona.groupId === null ? null : graph.entities.find((item) => item.id === persona.groupId);
|
|
538
|
+
if (persona.status === "active" && persona.groupId !== null
|
|
539
|
+
&& (!group || group.kind !== "group" || group.privacy === "private")) {
|
|
540
|
+
findings.push("persona-group-entity-mismatch:" + persona.personaId);
|
|
541
|
+
}
|
|
470
542
|
if (persona.status === "active" && persona.groupId !== null
|
|
471
543
|
&& !memberships.some((edge) => edge.to === persona.groupId && edge.privacy === "group")) {
|
|
472
544
|
findings.push("persona-group-membership-mismatch:" + persona.personaId);
|