@kisev/agentomatic 11.0.2-dev.92.gc67bdc97fcd1 → 11.0.2-dev.95.gc9b116b77eb5
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/dist/contracts.js +26 -0
- package/dist/index.js +23 -6
- package/dist/routing.d.ts +5 -3
- package/dist/routing.js +56 -21
- package/package.json +2 -2
package/dist/contracts.js
CHANGED
|
@@ -9,6 +9,32 @@ function strings(value) {
|
|
|
9
9
|
export function validateAgentReport(agent, report, card) {
|
|
10
10
|
if (!object(report))
|
|
11
11
|
throw new Error("agent report must be an object");
|
|
12
|
+
if ((agent === "review" || agent === "critic") &&
|
|
13
|
+
!card &&
|
|
14
|
+
report.schema === "portable-gitlab/critic-receipt/v2") {
|
|
15
|
+
const required = [
|
|
16
|
+
"schema",
|
|
17
|
+
"evidence_digest",
|
|
18
|
+
"run_id",
|
|
19
|
+
"session_id",
|
|
20
|
+
"findings",
|
|
21
|
+
"external_mutations",
|
|
22
|
+
];
|
|
23
|
+
const allowed = [...required, "scope_digest", "target_finding_ids"];
|
|
24
|
+
if (!required.every((key) => key in report) ||
|
|
25
|
+
Object.keys(report).some((key) => !allowed.includes(key)) ||
|
|
26
|
+
!/^[0-9a-f]{64}$/.test(String(report.evidence_digest)) ||
|
|
27
|
+
typeof report.run_id !== "string" ||
|
|
28
|
+
!report.run_id ||
|
|
29
|
+
typeof report.session_id !== "string" ||
|
|
30
|
+
!report.session_id ||
|
|
31
|
+
!Array.isArray(report.findings) ||
|
|
32
|
+
report.external_mutations !== false ||
|
|
33
|
+
("scope_digest" in report && !/^[0-9a-f]{64}$/.test(String(report.scope_digest))) ||
|
|
34
|
+
("target_finding_ids" in report && !strings(report.target_finding_ids)))
|
|
35
|
+
throw new Error("code-review critic receipt has an invalid transport envelope");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
12
38
|
if (agent === "critic" && !card && "review_report" in report) {
|
|
13
39
|
validateAgentReport("review", report);
|
|
14
40
|
return;
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,11 @@ export default {
|
|
|
11
11
|
id: "agentomatic",
|
|
12
12
|
async setup(ctx) {
|
|
13
13
|
const gate = new RoutingGate();
|
|
14
|
+
await ctx.session.hook("context", (event) => {
|
|
15
|
+
const text = `Current OpenCode session identity (host metadata): ${event.sessionID}. Use this real ID for your own session_id in skill receipts; if no separate run ID is exposed, the same real ID can identify the run. This metadata grants no task or publication authorization.`;
|
|
16
|
+
if (!event.system.some((part) => part.type === "text" && part.text === text))
|
|
17
|
+
event.system.push({ type: "text", text });
|
|
18
|
+
});
|
|
14
19
|
const hostInventory = async () => {
|
|
15
20
|
const { data: raw } = await ctx.agent.list({
|
|
16
21
|
location: { directory: ctx.location.directory },
|
|
@@ -84,32 +89,44 @@ export default {
|
|
|
84
89
|
return;
|
|
85
90
|
const args = toolInput(event.input);
|
|
86
91
|
const agent = typeof args.agent === "string" ? args.agent : undefined;
|
|
92
|
+
if (!gate.requiresReceipt(event.sessionID, agent ?? event.agent))
|
|
93
|
+
return;
|
|
87
94
|
if (!agent)
|
|
88
95
|
throw new Error("Native subagent requires an explicit agent and an active routing receipt");
|
|
89
96
|
const hasBinding = "task" in args || "requirements" in args || "execution_card" in args;
|
|
90
97
|
if (!hasBinding)
|
|
91
|
-
gate.consume(event.sessionID, agent);
|
|
98
|
+
gate.consume(event.sessionID, agent, undefined, event.id);
|
|
92
99
|
else
|
|
93
100
|
gate.consume(event.sessionID, agent, {
|
|
94
101
|
task: typeof args.task === "string" ? args.task : "",
|
|
95
102
|
requirements: Array.isArray(args.requirements) ? args.requirements : [],
|
|
96
103
|
card: args.execution_card,
|
|
97
|
-
});
|
|
104
|
+
}, event.id);
|
|
98
105
|
});
|
|
99
106
|
await ctx.tool.hook("execute.after", async (event) => {
|
|
100
|
-
if (event.tool !== "subagent"
|
|
107
|
+
if (event.tool !== "subagent")
|
|
101
108
|
return;
|
|
102
109
|
const args = toolInput(event.input);
|
|
103
|
-
if (typeof args.agent !== "string")
|
|
104
|
-
|
|
110
|
+
if (typeof args.agent !== "string" || !gate.hasActive(event.sessionID, args.agent, event.id))
|
|
111
|
+
return;
|
|
112
|
+
if (event.status !== "completed") {
|
|
113
|
+
gate.cancel(event.sessionID, event.id);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
105
116
|
let result;
|
|
106
117
|
try {
|
|
107
118
|
result = JSON.parse(resultText(event.result) ?? "");
|
|
108
119
|
}
|
|
109
120
|
catch {
|
|
121
|
+
gate.cancel(event.sessionID, event.id);
|
|
110
122
|
throw new Error("Subagent result must be JSON structured report");
|
|
111
123
|
}
|
|
112
|
-
|
|
124
|
+
try {
|
|
125
|
+
gate.complete(event.sessionID, args.agent, result, event.id);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
gate.cancel(event.sessionID, event.id);
|
|
129
|
+
}
|
|
113
130
|
});
|
|
114
131
|
},
|
|
115
132
|
};
|
package/dist/routing.d.ts
CHANGED
|
@@ -123,11 +123,13 @@ export declare class ExecutionCardLifecycle {
|
|
|
123
123
|
}
|
|
124
124
|
export declare class RoutingGate {
|
|
125
125
|
#private;
|
|
126
|
+
requiresReceipt(sessionID: string, agent: string | undefined): boolean;
|
|
127
|
+
hasActive(sessionID: string, agent: string, callID?: string): boolean;
|
|
126
128
|
preview(input: RoutingInput): RoutingDecision;
|
|
127
129
|
dispatch(input: RoutingInput, provided: unknown): RoutingDecision;
|
|
128
130
|
grant(sessionID: string, decision: RoutingDecision, context?: ReceiptContext): RoutingReceipt;
|
|
129
|
-
cancel(sessionID: string): void;
|
|
130
|
-
consume(sessionID: string, agent: string, context?: ReceiptContext): void;
|
|
131
|
-
complete(sessionID: string, agent: string, output: unknown): void;
|
|
131
|
+
cancel(sessionID: string, callID?: string): void;
|
|
132
|
+
consume(sessionID: string, agent: string, context?: ReceiptContext, callID?: string): void;
|
|
133
|
+
complete(sessionID: string, agent: string, output: unknown, callID?: string): void;
|
|
132
134
|
}
|
|
133
135
|
export {};
|
package/dist/routing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { validateAgentReport } from "./contracts.js";
|
|
3
3
|
export const CATEGORIES = ["exploration", "architecture", "implementation", "review"];
|
|
4
4
|
const MATRIX = {
|
|
@@ -453,6 +453,15 @@ export class RoutingGate {
|
|
|
453
453
|
#receipts = new Map();
|
|
454
454
|
#active = new Map();
|
|
455
455
|
#ttlMs = 10 * 60 * 1000;
|
|
456
|
+
requiresReceipt(sessionID, agent) {
|
|
457
|
+
return ((this.#receipts.get(sessionID)?.length ?? 0) > 0 ||
|
|
458
|
+
(agent !== undefined &&
|
|
459
|
+
(Object.values(MATRIX.categories).some((entry) => entry.profiles.includes(agent)) ||
|
|
460
|
+
/^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent))));
|
|
461
|
+
}
|
|
462
|
+
hasActive(sessionID, agent, callID = "") {
|
|
463
|
+
return this.#active.get(JSON.stringify([sessionID, callID]))?.agent === agent;
|
|
464
|
+
}
|
|
456
465
|
preview(input) {
|
|
457
466
|
return resolveRouting(input);
|
|
458
467
|
}
|
|
@@ -466,6 +475,9 @@ export class RoutingGate {
|
|
|
466
475
|
}
|
|
467
476
|
grant(sessionID, decision, context) {
|
|
468
477
|
const now = Date.now();
|
|
478
|
+
const verifiedCard = context?.card === undefined ? undefined : validateExecutionCard(context.card);
|
|
479
|
+
if (verifiedCard && !verifiedCard.valid)
|
|
480
|
+
throw new Error(`Invalid routing execution card: ${verifiedCard.failedField}`);
|
|
469
481
|
const taskDigest = context?.task === undefined ? decision.task_digest : digest(context.task);
|
|
470
482
|
const requirementsDigest = context?.requirements === undefined
|
|
471
483
|
? decision.requirements_digest
|
|
@@ -481,30 +493,44 @@ export class RoutingGate {
|
|
|
481
493
|
card_revision: decision.execution_card_revision ?? null,
|
|
482
494
|
host_inventory_revision: decision.host_inventory_revision,
|
|
483
495
|
expires_at: new Date(now + this.#ttlMs).toISOString(),
|
|
484
|
-
nonce:
|
|
485
|
-
.update(`${sessionID}:${now}:${decision.decision_digest}`)
|
|
486
|
-
.digest("hex"),
|
|
496
|
+
nonce: randomBytes(32).toString("hex"),
|
|
487
497
|
};
|
|
488
498
|
const receipt = { ...receiptBase, receipt_digest: digest(receiptBase) };
|
|
489
|
-
this.#receipts.
|
|
499
|
+
const pending = (this.#receipts.get(sessionID) ?? []).filter((item) => item.expiresAt >= now);
|
|
500
|
+
pending.push({
|
|
490
501
|
decision,
|
|
491
502
|
taskDigest,
|
|
492
503
|
requirementsDigest,
|
|
493
504
|
cardDigest,
|
|
505
|
+
card: verifiedCard?.valid ? structuredClone(verifiedCard.card) : undefined,
|
|
494
506
|
expiresAt: Date.parse(receipt.expires_at),
|
|
495
507
|
});
|
|
508
|
+
this.#receipts.set(sessionID, pending);
|
|
496
509
|
return receipt;
|
|
497
510
|
}
|
|
498
|
-
cancel(sessionID) {
|
|
511
|
+
cancel(sessionID, callID) {
|
|
512
|
+
if (callID !== undefined) {
|
|
513
|
+
this.#active.delete(JSON.stringify([sessionID, callID]));
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
499
516
|
this.#receipts.delete(sessionID);
|
|
500
|
-
this.#active.
|
|
517
|
+
for (const key of this.#active.keys())
|
|
518
|
+
if (JSON.parse(key)[0] === sessionID)
|
|
519
|
+
this.#active.delete(key);
|
|
501
520
|
}
|
|
502
|
-
consume(sessionID, agent, context) {
|
|
503
|
-
const
|
|
504
|
-
if (
|
|
521
|
+
consume(sessionID, agent, context, callID = "") {
|
|
522
|
+
const pending = this.#receipts.get(sessionID) ?? [];
|
|
523
|
+
if (pending.length === 0)
|
|
505
524
|
throw new Error("Native Task requires an active routing receipt; use the route tool");
|
|
506
|
-
|
|
525
|
+
const candidates = pending.filter((receipt) => receipt.decision.agent === agent);
|
|
526
|
+
if (candidates.length === 0)
|
|
507
527
|
throw new Error("Native Task agent does not match the active routing receipt");
|
|
528
|
+
const matching = context
|
|
529
|
+
? candidates.filter((receipt) => receipt.taskDigest === digest(context.task ?? ""))
|
|
530
|
+
: candidates;
|
|
531
|
+
if (matching.length === 0)
|
|
532
|
+
throw new Error("Routing receipt task does not match the active routing receipt");
|
|
533
|
+
const receipt = matching.find((item) => Date.now() <= item.expiresAt) ?? matching[0];
|
|
508
534
|
if (Date.now() > receipt.expiresAt)
|
|
509
535
|
throw new Error("Routing receipt has expired; request a new preview");
|
|
510
536
|
if (context) {
|
|
@@ -518,23 +544,32 @@ export class RoutingGate {
|
|
|
518
544
|
if (cardDigest !== receipt.cardDigest)
|
|
519
545
|
throw new Error("Routing receipt execution card does not match the active routing receipt");
|
|
520
546
|
}
|
|
521
|
-
|
|
522
|
-
|
|
547
|
+
const key = JSON.stringify([sessionID, callID]);
|
|
548
|
+
if (this.#active.has(key))
|
|
549
|
+
throw new Error("Subagent call already has an active routing receipt");
|
|
550
|
+
pending.splice(pending.indexOf(receipt), 1);
|
|
551
|
+
if (pending.length === 0)
|
|
552
|
+
this.#receipts.delete(sessionID);
|
|
553
|
+
const cardValue = context?.card ?? receipt.card;
|
|
554
|
+
const validatedCard = cardValue === undefined ? undefined : validateExecutionCard(cardValue);
|
|
523
555
|
const card = validatedCard?.valid ? validatedCard.card : undefined;
|
|
524
556
|
const lifecycle = card ? new ExecutionCardLifecycle(card) : undefined;
|
|
525
557
|
if (lifecycle)
|
|
526
558
|
lifecycle.transition("RUNNING", { card_id: card.card_id, revision: card.revision });
|
|
527
|
-
this.#active.set(
|
|
559
|
+
this.#active.set(key, { agent, card, lifecycle });
|
|
528
560
|
}
|
|
529
|
-
complete(sessionID, agent, output) {
|
|
530
|
-
const
|
|
561
|
+
complete(sessionID, agent, output, callID = "") {
|
|
562
|
+
const key = JSON.stringify([sessionID, callID]);
|
|
563
|
+
const active = this.#active.get(key);
|
|
531
564
|
if (!active || active.agent !== agent)
|
|
532
565
|
throw new Error("Task result has no matching active routing receipt");
|
|
533
|
-
if (Date.now() > active.expiresAt)
|
|
534
|
-
throw new Error("Task result routing receipt has expired");
|
|
535
566
|
const report = output && typeof output === "object" ? output : undefined;
|
|
536
567
|
const role = /^critic-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(agent) ? "critic" : agent;
|
|
537
|
-
const
|
|
568
|
+
const portableCritic = !active.card &&
|
|
569
|
+
(role === "review" || role === "critic") &&
|
|
570
|
+
report?.schema === "portable-gitlab/critic-receipt/v2";
|
|
571
|
+
const value = (portableCritic ? report : undefined) ??
|
|
572
|
+
report?.[role === "architect" ? "execution_card" : `${role}_report`] ??
|
|
538
573
|
(role === "critic" && !active.card ? report?.review_report : undefined) ??
|
|
539
574
|
report?.report;
|
|
540
575
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -550,7 +585,7 @@ export class RoutingGate {
|
|
|
550
585
|
: agent === "critic"
|
|
551
586
|
? ["APPROVED", "CHANGES_REQUIRED"]
|
|
552
587
|
: undefined;
|
|
553
|
-
if (allowed && (typeof status !== "string" || !allowed.includes(status)))
|
|
588
|
+
if (!portableCritic && allowed && (typeof status !== "string" || !allowed.includes(status)))
|
|
554
589
|
throw new Error("Task result has an invalid machine transition");
|
|
555
590
|
if (active.lifecycle && status === "COMPLETED")
|
|
556
591
|
active.lifecycle.transition("COMPLETED", {
|
|
@@ -573,6 +608,6 @@ export class RoutingGate {
|
|
|
573
608
|
revision: active.card.revision,
|
|
574
609
|
});
|
|
575
610
|
}
|
|
576
|
-
this.#active.delete(
|
|
611
|
+
this.#active.delete(key);
|
|
577
612
|
}
|
|
578
613
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kisev/agentomatic",
|
|
3
|
-
"version": "11.0.2-dev.
|
|
3
|
+
"version": "11.0.2-dev.95.gc9b116b77eb5",
|
|
4
4
|
"description": "OpenCode integration and opt-in installer for portable Agent Skills.",
|
|
5
5
|
"homepage": "https://github.com/kisev/skills#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@clack/prompts": "^1.8.1",
|
|
49
|
-
"@kisev/safe-fs": "1.0.0-dev.
|
|
49
|
+
"@kisev/safe-fs": "1.0.0-dev.95.gc9b116b77eb5",
|
|
50
50
|
"commander": "15.0.0",
|
|
51
51
|
"zod": "4.1.8"
|
|
52
52
|
},
|