@akagilnc/pi-workflow-roles 0.1.3932 → 0.1.3975
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/CLAUDE.md +1 -1
- package/dist/acp-host/production-host.js +2389 -6018
- package/dist/analyst-gate-cycles-read.js +15 -19
- package/dist/audit-escalation.js +16 -46
- package/dist/auditor-dossier-tool.js +7 -62
- package/dist/collector-identity.js +0 -157
- package/dist/collector-ledger.js +2 -42
- package/dist/compliance-transport.js +40 -14
- package/dist/diarist-contracts.js +2 -40
- package/dist/doctor-contracts.js +39 -108
- package/dist/doctor-evidence.js +1 -5
- package/dist/engine-detour-tool.js +1 -1
- package/dist/gatekeeper-role.js +49 -38
- package/dist/headless-host/production-host.js +4814 -8450
- package/dist/host-contracts.js +0 -5
- package/dist/merger-contracts.js +4 -6
- package/dist/navigator-attendance.js +30 -52
- package/dist/navigator-invocation-identity.js +0 -1
- package/dist/navigator-public-session.js +2 -1
- package/dist/notary-contracts.js +4 -10
- package/dist/package-contracts/auditor-output.js +5 -23
- package/dist/package-contracts/fixer-output.js +25 -49
- package/dist/package-contracts/gatekeeper-output.js +3 -13
- package/dist/package-contracts/navigator-output.js +4 -7
- package/dist/package-contracts/reviewer-output.js +1 -95
- package/dist/package-contracts/terminating-tools.js +33 -144
- package/dist/package-contracts/worker-output.js +1 -3
- package/dist/public-cli/auto-resume.js +30 -68
- package/dist/public-cli/case-dossier-delivery.js +5 -17
- package/dist/public-cli/diarist-run.js +9 -4
- package/dist/public-cli/inspector-run.js +2 -8
- package/dist/public-cli/instruction-seat-run.js +3 -7
- package/dist/public-cli/invocation.js +5 -2
- package/dist/public-cli/main.js +6068 -6964
- package/dist/public-cli/notary-run.js +1 -8
- package/dist/public-cli/post-admission.js +314 -113
- package/dist/public-cli/run-lifecycle.js +14 -38
- package/dist/public-cli/settlement.js +209 -390
- package/dist/public-cli/terminal.js +51 -45
- package/dist/public-role-summons.js +1 -12
- package/dist/reviewer-dispatch.js +12 -258
- package/dist/submission-correctable-error.js +0 -12
- package/dist/submission-errors.js +6 -1
- package/dist/submission-ledger.js +125 -168
- package/dist/ticket-provenance-contracts.js +2 -49
- package/dist/ticket-provenance.js +82 -31
- package/extensions/role-runtime.ts +1 -6
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +18 -0
- package/src/acp-host/role-turn-host.ts +8 -3
- package/src/analyst-gate-cycles-read.ts +17 -29
- package/src/audit-escalation.ts +20 -50
- package/src/auditor-dossier-tool.ts +5 -103
- package/src/canonical-skill-binding.ts +3 -2
- package/src/collector-identity.ts +0 -179
- package/src/collector-ledger.ts +3 -46
- package/src/collector-role.ts +32 -61
- package/src/collector-tool-schemas.ts +20 -14
- package/src/compliance-transport.ts +55 -17
- package/src/countersign-role.ts +17 -8
- package/src/diarist-contracts.ts +3 -36
- package/src/diarist-role.ts +13 -7
- package/src/diarist.ts +4 -16
- package/src/doctor-contracts.ts +73 -94
- package/src/doctor-evidence.ts +1 -1
- package/src/doctor-role.ts +3 -3
- package/src/engine-detour-tool.ts +4 -1
- package/src/external-host-turn-loop.ts +9 -3
- package/src/gatekeeper-pass-envelope.ts +7 -12
- package/src/gatekeeper-role.ts +56 -50
- package/src/gleaner-left-role.ts +4 -2
- package/src/headless-host/role-turn-host.ts +4 -5
- package/src/host-contracts.ts +3 -8
- package/src/judge-role.ts +19 -13
- package/src/merger-contracts.ts +10 -6
- package/src/merger-role.ts +1 -5
- package/src/navigator-attendance.ts +37 -74
- package/src/navigator-invocation-identity.ts +0 -6
- package/src/navigator-public-session.ts +2 -1
- package/src/notary-contracts.ts +7 -10
- package/src/package-contracts/auditor-output.ts +4 -21
- package/src/package-contracts/fixer-output.ts +34 -52
- package/src/package-contracts/gatekeeper-output.ts +5 -22
- package/src/package-contracts/navigator-output.ts +7 -7
- package/src/package-contracts/reviewer-output.ts +2 -150
- package/src/package-contracts/terminating-tools.ts +32 -147
- package/src/package-contracts/worker-output.ts +0 -1
- package/src/pi/adapter.ts +3 -8
- package/src/public-cli/auto-resume.ts +41 -94
- package/src/public-cli/case-dossier-delivery.ts +5 -15
- package/src/public-cli/countersign-run.ts +14 -6
- package/src/public-cli/diarist-run.ts +11 -9
- package/src/public-cli/inspector-run.ts +2 -18
- package/src/public-cli/instruction-seat-run.ts +3 -17
- package/src/public-cli/invocation.ts +5 -2
- package/src/public-cli/merger-run.ts +1 -1
- package/src/public-cli/notary-run.ts +1 -18
- package/src/public-cli/post-admission.ts +385 -115
- package/src/public-cli/run-lifecycle.ts +13 -44
- package/src/public-cli/settlement.ts +314 -562
- package/src/public-cli/terminal.ts +66 -72
- package/src/public-role-summons.ts +5 -30
- package/src/reviewer-dispatch.ts +29 -331
- package/src/reviewer-role.ts +16 -83
- package/src/role-envelope.ts +16 -24
- package/src/role-runtime-dependencies.ts +1 -6
- package/src/role-runtime.ts +65 -201
- package/src/submission-correctable-error.ts +0 -14
- package/src/submission-errors.ts +6 -1
- package/src/submission-ledger.ts +187 -187
- package/src/ticket-provenance-contracts.ts +4 -60
- package/src/ticket-provenance.ts +98 -32
- package/src/worker-role.ts +35 -36
- package/dist/activation-ledger-session.js +0 -105
- package/dist/activation-ledger.js +0 -139
- package/dist/activation-trace.js +0 -33
- package/dist/atomic-write.js +0 -23
- package/dist/auditor-role.js +0 -12
- package/dist/canonical-skill-binding.js +0 -61
- package/dist/collector-handbook.js +0 -142
- package/dist/collector-receipt.js +0 -107
- package/dist/collector-role.js +0 -566
- package/dist/collector-tool-schemas.js +0 -107
- package/dist/countersign-role.js +0 -28
- package/dist/diarist-role.js +0 -44
- package/dist/diarist.js +0 -66
- package/dist/doctor-role.js +0 -75
- package/dist/fixer-bash-seatbelt.js +0 -13
- package/dist/gatekeeper-pass-envelope.js +0 -77
- package/dist/gleaner-left-role.js +0 -26
- package/dist/inspector-role.js +0 -29
- package/dist/judge-role.js +0 -104
- package/dist/merger-role.js +0 -55
- package/dist/navigator-role.js +0 -12
- package/dist/notary-role.js +0 -134
- package/dist/oauth-keepalive.js +0 -158
- package/dist/pi/adapter.js +0 -223
- package/dist/reviewer-agent.js +0 -122
- package/dist/reviewer-child-executor.js +0 -194
- package/dist/reviewer-construction.js +0 -103
- package/dist/reviewer-execution-ledger.js +0 -166
- package/dist/reviewer-failure-diagnostic.js +0 -17
- package/dist/reviewer-role.js +0 -159
- package/dist/reviewer-scope-prompt.js +0 -21
- package/dist/reviewer-settlement.js +0 -66
- package/dist/reviewer-workspace.js +0 -132
- package/dist/role-runtime.js +0 -1604
- package/dist/stderr-jsonl.js +0 -26
- package/dist/tool-execution-observation.js +0 -141
- package/dist/worker-role.js +0 -345
- package/dist/worker-submission-gates.js +0 -305
- package/src/collector-receipt.ts +0 -140
- package/src/diagnostic-tail.ts +0 -18
- package/src/reviewer-agent.ts +0 -135
- package/src/reviewer-child-executor.ts +0 -252
- package/src/reviewer-construction.ts +0 -195
- package/src/reviewer-execution-ledger.ts +0 -212
- package/src/reviewer-failure-diagnostic.ts +0 -18
- package/src/reviewer-scope-prompt.ts +0 -24
- package/src/reviewer-settlement.ts +0 -74
- package/src/reviewer-workspace.ts +0 -105
- package/src/shape-unreadable-failure.ts +0 -49
package/dist/notary-role.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public Notary role runtime — direct officer seat (not through Gatekeeper province).
|
|
3
|
-
* Caller supplies only a source-run locator; Notary self-fetches authoritative materials.
|
|
4
|
-
* Ticket flag register/read/session bind is envelope-owned (ADR 0018 / #582).
|
|
5
|
-
* This module keeps evidence assembly + projection only.
|
|
6
|
-
*/
|
|
7
|
-
import { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, notaryOutputSchema, } from "./notary-contracts.js";
|
|
8
|
-
export { NOTARY_ACCEPTED_TEXT, NOTARY_OUTPUT_TOOL_NAME, NOTARY_SOURCE_RUN_FLAG, NOTARY_TICKET_FLAG, };
|
|
9
|
-
/** Optional ticket flag: absent/blank = unbound; non-empty invalid = honest fail. */
|
|
10
|
-
export function readNotaryTicketFlag(flag) {
|
|
11
|
-
if (flag === undefined)
|
|
12
|
-
return undefined;
|
|
13
|
-
if (typeof flag !== "string") {
|
|
14
|
-
throw new Error("Notary ak-notary-ticket-number is present but not a safe positive integer string");
|
|
15
|
-
}
|
|
16
|
-
if (flag.trim() === "")
|
|
17
|
-
return undefined;
|
|
18
|
-
const n = Number(flag);
|
|
19
|
-
if (!Number.isSafeInteger(n) || n < 1) {
|
|
20
|
-
throw new Error("Notary ak-notary-ticket-number is present but not a safe positive integer string");
|
|
21
|
-
}
|
|
22
|
-
return n;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Typed session bound (locator + optional ticket).
|
|
26
|
-
* Sole production projection for agent-start material + session custom entry.
|
|
27
|
-
*/
|
|
28
|
-
export function projectNotarySessionBound(input) {
|
|
29
|
-
return {
|
|
30
|
-
sourceRun: input.sourceRun,
|
|
31
|
-
...(input.ticketNumber === undefined ? {} : { ticketNumber: input.ticketNumber }),
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Session custom-entry type for typed notary bound (written by shared envelope).
|
|
36
|
-
* Role module only projects; lifecycle write is envelope-owned (ADR 0018).
|
|
37
|
-
*/
|
|
38
|
-
export const NOTARY_SESSION_BOUND_ENTRY = "notary-session-bound";
|
|
39
|
-
/**
|
|
40
|
-
* Project notary bound from host flags (envelope consumption seam).
|
|
41
|
-
* undefined when source-run flag absent/blank.
|
|
42
|
-
* Ticket invalid → throws (honest fail; envelope owns the read).
|
|
43
|
-
*/
|
|
44
|
-
export function projectNotaryBoundFromFlags(getFlag) {
|
|
45
|
-
const path = getFlag(NOTARY_SOURCE_RUN_FLAG.name);
|
|
46
|
-
if (typeof path !== "string" || path.trim() === "")
|
|
47
|
-
return undefined;
|
|
48
|
-
const ticketNumber = readNotaryTicketFlag(getFlag(NOTARY_TICKET_FLAG.name));
|
|
49
|
-
return {
|
|
50
|
-
sourceRunPath: path,
|
|
51
|
-
...(ticketNumber === undefined ? {} : { ticketNumber }),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
/** Assemble systemPrompt body from base + soul only (NO embedded bound JSON).
|
|
55
|
-
* The typed session bound travels separately as `readingMaterial`; adapters fold
|
|
56
|
-
* it into the provider-visible prompt at the send boundary. */
|
|
57
|
-
export function assembleNotaryAgentStartPrompt(input) {
|
|
58
|
-
return `${input.baseSystemPrompt}\n\n<notary_soul>\n${input.soul}\n</notary_soul>`;
|
|
59
|
-
}
|
|
60
|
-
export function createNotaryRoleRuntime(pi, dependencies, host) {
|
|
61
|
-
let activation;
|
|
62
|
-
let registered = false;
|
|
63
|
-
// Baseline source-run flag registration (pre-#582). Ticket flag is envelope-owned.
|
|
64
|
-
pi.registerFlag(NOTARY_SOURCE_RUN_FLAG.name, NOTARY_SOURCE_RUN_FLAG.definition);
|
|
65
|
-
return {
|
|
66
|
-
/**
|
|
67
|
-
* @param admitted Optional ticket from envelope flag decode (ADR 0018).
|
|
68
|
-
* Role module never getFlag's the ticket — envelope owns that read.
|
|
69
|
-
*/
|
|
70
|
-
async activate(admitted) {
|
|
71
|
-
const path = pi.getFlag(NOTARY_SOURCE_RUN_FLAG.name);
|
|
72
|
-
if (typeof path !== "string" || path.trim() === "") {
|
|
73
|
-
throw new Error("Notary requires --ak-notary-source-run");
|
|
74
|
-
}
|
|
75
|
-
const soul = (await dependencies.loadSoul()).trim();
|
|
76
|
-
if (soul.length === 0)
|
|
77
|
-
throw new Error("Notary soul is empty");
|
|
78
|
-
const sourceRun = await dependencies.loadSourceRunLocator(path);
|
|
79
|
-
activation = {
|
|
80
|
-
soul,
|
|
81
|
-
sourceRun,
|
|
82
|
-
...(admitted?.ticketNumber === undefined
|
|
83
|
-
? {}
|
|
84
|
-
: { ticketNumber: admitted.ticketNumber }),
|
|
85
|
-
};
|
|
86
|
-
if (!registered) {
|
|
87
|
-
registered = true;
|
|
88
|
-
pi.registerTool({
|
|
89
|
-
name: NOTARY_OUTPUT_TOOL_NAME,
|
|
90
|
-
label: "符宝郎输出",
|
|
91
|
-
description: "提交引文保真与票面对齐的 typed pass/bounce/escalate 决议。",
|
|
92
|
-
promptSnippet: "提交符宝郎决议",
|
|
93
|
-
parameters: notaryOutputSchema,
|
|
94
|
-
async execute(toolCallId, parameters, _signal, _onUpdate, ctx) {
|
|
95
|
-
if (activation === undefined) {
|
|
96
|
-
throw new Error("符宝郎未激活");
|
|
97
|
-
}
|
|
98
|
-
// #753: handler only records — params as submitted, no findings/disposition rewrite.
|
|
99
|
-
// #541 infra declaration + sole-final barrier are ledger-owned (#575).
|
|
100
|
-
return {
|
|
101
|
-
content: [{ type: "text", text: NOTARY_ACCEPTED_TEXT }],
|
|
102
|
-
details: parameters,
|
|
103
|
-
terminate: true,
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
// Evidence assembly only (ADR 0018): soul + bound projection.
|
|
108
|
-
// Ticket value is envelope-admitted at activate — not re-read from flags here.
|
|
109
|
-
pi.on("before_agent_start", (event) => {
|
|
110
|
-
if (activation === undefined) {
|
|
111
|
-
throw new Error("符宝郎未激活");
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
systemPrompt: assembleNotaryAgentStartPrompt({
|
|
115
|
-
baseSystemPrompt: event.systemPrompt,
|
|
116
|
-
soul: activation.soul,
|
|
117
|
-
}),
|
|
118
|
-
readingMaterial: projectNotarySessionBound({
|
|
119
|
-
sourceRun: activation.sourceRun,
|
|
120
|
-
...(activation.ticketNumber === undefined
|
|
121
|
-
? {}
|
|
122
|
-
: { ticketNumber: activation.ticketNumber }),
|
|
123
|
-
}),
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
// Evidence role: keep Pi default tools + notary output (ADR 0064 unrestricted).
|
|
128
|
-
const all = pi.getAllTools().map((tool) => tool.name);
|
|
129
|
-
if (all.filter((name) => name === NOTARY_OUTPUT_TOOL_NAME).length !== 1) {
|
|
130
|
-
throw new Error(`Notary required tool collision or missing: ${NOTARY_OUTPUT_TOOL_NAME}`);
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
package/dist/oauth-keepalive.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extension-side OAuth credential keepalive (#351).
|
|
3
|
-
*
|
|
4
|
-
* Periodically calls the public `ctx.modelRegistry.refresh` facade so expired
|
|
5
|
-
* OAuth tokens (e.g. kimi-coding 900s TTL) are renewed during long sessions.
|
|
6
|
-
* Does not read auth.json, touch pi-ai private APIs, or reuse the 183s idle gate.
|
|
7
|
-
*/
|
|
8
|
-
import { readFileSync } from "node:fs";
|
|
9
|
-
import { homedir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
export const DEFAULT_OAUTH_KEEPALIVE_PROVIDERS = ["kimi-coding"];
|
|
12
|
-
export const OAUTH_KEEPALIVE_INTERVAL_MS = 60_000;
|
|
13
|
-
/** Persistent extension setting filename under PI_CODING_AGENT_DIR / ~/.pi/agent. */
|
|
14
|
-
export const OAUTH_KEEPALIVE_SETTING_FILENAME = "oauth-keepalive.json";
|
|
15
|
-
/** Path to the static provider-list extension setting (default ["kimi-coding"]). */
|
|
16
|
-
export function oauthKeepaliveSettingPath() {
|
|
17
|
-
return join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), OAUTH_KEEPALIVE_SETTING_FILENAME);
|
|
18
|
-
}
|
|
19
|
-
function isExactRecord(value) {
|
|
20
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Read the static keepalive provider list from the extension setting file.
|
|
24
|
-
* Missing file → default ["kimi-coding"]. Malformed content fails closed.
|
|
25
|
-
* Production extension root is the sole caller that feeds this into keepalive.
|
|
26
|
-
*/
|
|
27
|
-
export function readOAuthKeepaliveProviders(path = oauthKeepaliveSettingPath()) {
|
|
28
|
-
try {
|
|
29
|
-
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
30
|
-
if (!isExactRecord(raw) || !Array.isArray(raw.providers)) {
|
|
31
|
-
throw new Error("OAuth keepalive setting is malformed: expected { providers: string[] }");
|
|
32
|
-
}
|
|
33
|
-
const providers = raw.providers.map((id, index) => {
|
|
34
|
-
if (typeof id !== "string" || id.trim() === "") {
|
|
35
|
-
throw new Error(`OAuth keepalive setting providers[${index}] must be a non-empty string`);
|
|
36
|
-
}
|
|
37
|
-
// Accept via trim-non-empty; return the normalized id so refresh can hit real providers.
|
|
38
|
-
return id.trim();
|
|
39
|
-
});
|
|
40
|
-
return Object.freeze(providers);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
// Absent optional setting uses the documented default; all other causes propagate.
|
|
44
|
-
if (error instanceof Error &&
|
|
45
|
-
"code" in error &&
|
|
46
|
-
error.code === "ENOENT") {
|
|
47
|
-
return DEFAULT_OAUTH_KEEPALIVE_PROVIDERS;
|
|
48
|
-
}
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const defaultScheduler = {
|
|
53
|
-
every(ms, tick) {
|
|
54
|
-
const timer = setInterval(tick, ms);
|
|
55
|
-
// Allow the process to exit if a caller forgets stop (same discipline as trajectory).
|
|
56
|
-
timer.unref?.();
|
|
57
|
-
return () => clearInterval(timer);
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
function errorClassName(error) {
|
|
61
|
-
if (error instanceof Error && error.name.trim() !== "")
|
|
62
|
-
return error.name;
|
|
63
|
-
if (error !== null && typeof error === "object" && "name" in error) {
|
|
64
|
-
const name = error.name;
|
|
65
|
-
if (typeof name === "string" && name.trim() !== "")
|
|
66
|
-
return name;
|
|
67
|
-
}
|
|
68
|
-
return "Error";
|
|
69
|
-
}
|
|
70
|
-
function formatWarning(providerId, error) {
|
|
71
|
-
const cls = errorClassName(error);
|
|
72
|
-
const detail = error instanceof Error
|
|
73
|
-
? error.message
|
|
74
|
-
: typeof error === "string"
|
|
75
|
-
? error
|
|
76
|
-
: String(error);
|
|
77
|
-
return `oauth-keepalive: refresh failed for provider ${providerId} (${cls}): ${detail}`;
|
|
78
|
-
}
|
|
79
|
-
function emitWarning(ctx, providerId, error) {
|
|
80
|
-
const text = formatWarning(providerId, error);
|
|
81
|
-
// Exactly one visible surface per failure: prefer ui.notify when present;
|
|
82
|
-
// console.warn only when notify is unavailable (print/json / headless)
|
|
83
|
-
// or when notify itself throws (still one surface; carry the notify cause).
|
|
84
|
-
const notify = ctx.ui?.notify;
|
|
85
|
-
if (typeof notify === "function") {
|
|
86
|
-
try {
|
|
87
|
-
notify(text, "warning");
|
|
88
|
-
}
|
|
89
|
-
catch (notifyError) {
|
|
90
|
-
// Notify threw: fall back once so refresh failure stays visible.
|
|
91
|
-
console.warn(text, notifyError);
|
|
92
|
-
}
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
console.warn(text);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Session-lifecycle owner for periodic OAuth refresh via the public modelRegistry facade.
|
|
99
|
-
* Single-flight: overlapping ticks are skipped. stop() cancels the interval and aborts in-flight work.
|
|
100
|
-
*/
|
|
101
|
-
export function createOAuthKeepalive(options = {}) {
|
|
102
|
-
const providers = Object.freeze((options.providers ?? DEFAULT_OAUTH_KEEPALIVE_PROVIDERS).map((id) => id));
|
|
103
|
-
const intervalMs = options.intervalMs ?? OAUTH_KEEPALIVE_INTERVAL_MS;
|
|
104
|
-
const scheduler = options.scheduler ?? defaultScheduler;
|
|
105
|
-
let cancel;
|
|
106
|
-
let abortController;
|
|
107
|
-
/** Bare single-flight gate; survives stop/start until the in-flight call settles. */
|
|
108
|
-
let inFlight = false;
|
|
109
|
-
const stop = () => {
|
|
110
|
-
cancel?.();
|
|
111
|
-
cancel = undefined;
|
|
112
|
-
abortController?.abort();
|
|
113
|
-
abortController = undefined;
|
|
114
|
-
// Do not clear unsettled inFlight — single-flight is continuous across stop/start.
|
|
115
|
-
};
|
|
116
|
-
const runTick = async (ctx) => {
|
|
117
|
-
if (inFlight)
|
|
118
|
-
return;
|
|
119
|
-
const ac = abortController;
|
|
120
|
-
if (ac === undefined || ac.signal.aborted)
|
|
121
|
-
return;
|
|
122
|
-
inFlight = true;
|
|
123
|
-
try {
|
|
124
|
-
const result = await ctx.modelRegistry.refresh({
|
|
125
|
-
providers: [...providers],
|
|
126
|
-
allowNetwork: true,
|
|
127
|
-
signal: ac.signal,
|
|
128
|
-
});
|
|
129
|
-
if (ac.signal.aborted || result.aborted)
|
|
130
|
-
return;
|
|
131
|
-
for (const [providerId, error] of result.errors) {
|
|
132
|
-
emitWarning(ctx, providerId, error);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
if (ac.signal.aborted)
|
|
137
|
-
return;
|
|
138
|
-
// Whole-call failure: one warning per configured provider (ticket error surface).
|
|
139
|
-
for (const providerId of providers) {
|
|
140
|
-
emitWarning(ctx, providerId, error);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
inFlight = false;
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
return {
|
|
148
|
-
start(ctx) {
|
|
149
|
-
// Reload / re-start: tear down any prior interval before hanging a new one.
|
|
150
|
-
stop();
|
|
151
|
-
abortController = new AbortController();
|
|
152
|
-
cancel = scheduler.every(intervalMs, () => {
|
|
153
|
-
void runTick(ctx);
|
|
154
|
-
});
|
|
155
|
-
},
|
|
156
|
-
stop,
|
|
157
|
-
};
|
|
158
|
-
}
|
package/dist/pi/adapter.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { parseSkillBlock, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { requireGatekeeperPass } from "../gatekeeper-pass-envelope.js";
|
|
3
|
-
import { createOAuthKeepalive } from "../oauth-keepalive.js";
|
|
4
|
-
import { createRoleRuntimeExtension } from "../role-runtime.js";
|
|
5
|
-
import { renderAgentStartMaterials } from "../agent-start-materials.js";
|
|
6
|
-
import { mechanicalSubmissionRejectionResumeMessage } from "../submission-correctable-error.js";
|
|
7
|
-
const piContexts = new WeakMap();
|
|
8
|
-
/** Pi-only entrypoint adapter around the host-neutral shared composition. */
|
|
9
|
-
export function createPiRoleRuntimeExtension(dependencies, options = {}) {
|
|
10
|
-
return (pi) => createRoleRuntimeExtension(dependencies)(createPiRoleHostAdapter(pi, options));
|
|
11
|
-
}
|
|
12
|
-
/** Project Pi's activation context onto the package-owned host contract. */
|
|
13
|
-
function projectPiContext(context, transcriptFromContext) {
|
|
14
|
-
const sessionManager = context.sessionManager;
|
|
15
|
-
const host = {
|
|
16
|
-
cwd: context.cwd,
|
|
17
|
-
mode: context.mode,
|
|
18
|
-
model: context.model === undefined ? undefined : { provider: context.model.provider },
|
|
19
|
-
sessionManager: {
|
|
20
|
-
getLeafEntry: () => context.sessionManager.getLeafEntry(),
|
|
21
|
-
getLeafId: () => context.sessionManager.getLeafId(),
|
|
22
|
-
getEntries: () => context.sessionManager.getEntries(),
|
|
23
|
-
getSessionDir: () => context.sessionManager.getSessionDir(),
|
|
24
|
-
getSessionFile: () => context.sessionManager.getSessionFile(),
|
|
25
|
-
getHeader: () => context.sessionManager.getHeader(),
|
|
26
|
-
setSessionFile: (path) => sessionManager.setSessionFile(path),
|
|
27
|
-
appendCustomEntry: (customType, data) => sessionManager.appendCustomEntry(customType, data),
|
|
28
|
-
},
|
|
29
|
-
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
30
|
-
...(context.ui === undefined ? {} : { ui: { notify: (message, type) => context.ui.notify(message, type) } }),
|
|
31
|
-
...(transcriptFromContext === undefined ? {} : { transcript: () => transcriptFromContext(context) }),
|
|
32
|
-
abort: () => context.abort(),
|
|
33
|
-
};
|
|
34
|
-
piContexts.set(host, context);
|
|
35
|
-
return host;
|
|
36
|
-
}
|
|
37
|
-
/** Recover the Pi context only at the Pi composition boundary. */
|
|
38
|
-
export function toPiContext(context) {
|
|
39
|
-
const piContext = piContexts.get(context);
|
|
40
|
-
if (piContext === undefined)
|
|
41
|
-
throw new Error("Host context is not backed by the Pi adapter");
|
|
42
|
-
return piContext;
|
|
43
|
-
}
|
|
44
|
-
/** Gatekeeper receives HostContext directly (#518 §1③) without recovering raw ExtensionContext via WeakMap. */
|
|
45
|
-
function requirePiGatekeeperPass(options) {
|
|
46
|
-
return requireGatekeeperPass({
|
|
47
|
-
context: options.context,
|
|
48
|
-
subject: options.subject,
|
|
49
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
50
|
-
hostActions: {
|
|
51
|
-
failInfrastructure(error, _context, toolCallId) {
|
|
52
|
-
options.hostActions.failInfrastructure(error, options.context, toolCallId);
|
|
53
|
-
},
|
|
54
|
-
bindSubmissionNonPass: options.hostActions.bindSubmissionNonPass,
|
|
55
|
-
},
|
|
56
|
-
toolCallId: options.toolCallId,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
function toPiResult(result) {
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
/** Fold a host before_agent_start return: push readingMaterial into the provider-visible
|
|
63
|
-
* systemPrompt via the shared renderer and strip the typed field before Pi sees it. */
|
|
64
|
-
function foldBeforeAgentStartReturn(result) {
|
|
65
|
-
if (result === undefined || result === null || typeof result !== "object")
|
|
66
|
-
return undefined;
|
|
67
|
-
const record = result;
|
|
68
|
-
const { systemPrompt, readingMaterial } = record;
|
|
69
|
-
const folded = readingMaterial === undefined
|
|
70
|
-
? systemPrompt
|
|
71
|
-
: renderAgentStartMaterials(systemPrompt ?? "", [readingMaterial]);
|
|
72
|
-
return folded === undefined ? {} : { systemPrompt: folded };
|
|
73
|
-
}
|
|
74
|
-
function toPiToolDefinition(tool, projectContext) {
|
|
75
|
-
return {
|
|
76
|
-
name: tool.name,
|
|
77
|
-
label: tool.label,
|
|
78
|
-
description: tool.description,
|
|
79
|
-
...(tool.promptSnippet === undefined ? {} : { promptSnippet: tool.promptSnippet }),
|
|
80
|
-
parameters: tool.parameters,
|
|
81
|
-
execute: async (toolCallId, params, signal, update, context) => toPiResult(await tool.execute(toolCallId, params, signal, update === undefined ? undefined : (result) => update(toPiResult(result)), projectContext(context))),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
/** Pi composition boundary. Each consumed capability is adapted explicitly. */
|
|
85
|
-
export function createPiRoleHostAdapter(pi, options = {}) {
|
|
86
|
-
const keepalive = createOAuthKeepalive(options.oauthKeepalive);
|
|
87
|
-
const host = {
|
|
88
|
-
deliverSubmissionRejection(rejection) {
|
|
89
|
-
pi.sendMessage({
|
|
90
|
-
customType: "ak-role-submission-rejection",
|
|
91
|
-
content: mechanicalSubmissionRejectionResumeMessage(rejection.code),
|
|
92
|
-
display: true,
|
|
93
|
-
details: rejection,
|
|
94
|
-
}, { triggerTurn: true, deliverAs: "followUp" });
|
|
95
|
-
},
|
|
96
|
-
capabilities: {
|
|
97
|
-
skillExpansion(prompt) {
|
|
98
|
-
const parsed = parseSkillBlock(prompt);
|
|
99
|
-
if (parsed == null)
|
|
100
|
-
return undefined;
|
|
101
|
-
const userMessage = parsed.userMessage ?? "";
|
|
102
|
-
return Object.freeze({
|
|
103
|
-
name: parsed.name,
|
|
104
|
-
location: parsed.location,
|
|
105
|
-
content: parsed.content,
|
|
106
|
-
userMessage,
|
|
107
|
-
});
|
|
108
|
-
},
|
|
109
|
-
/** Recover plain original request from Pi-native `/skill:` turn text (ADR 0082). */
|
|
110
|
-
skillOriginalRequest(name, text) {
|
|
111
|
-
const token = `/skill:${name}`;
|
|
112
|
-
const trimmed = text.trimStart();
|
|
113
|
-
if (trimmed === token
|
|
114
|
-
|| trimmed.startsWith(`${token} `)
|
|
115
|
-
|| trimmed.startsWith(`${token}\n`)) {
|
|
116
|
-
return text.slice(text.indexOf(token) + token.length).trim();
|
|
117
|
-
}
|
|
118
|
-
return text.trim();
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
registerFlag: (name, definition) => pi.registerFlag(name, definition),
|
|
122
|
-
getFlag: (name) => pi.getFlag(name),
|
|
123
|
-
registerTool: (tool) => pi.registerTool(toPiToolDefinition(tool, (context) => projectPiContext(context, options.transcriptFromContext))),
|
|
124
|
-
getAllTools: () => pi.getAllTools().map(({ name, sourceInfo }) => ({
|
|
125
|
-
name,
|
|
126
|
-
...(sourceInfo?.path === undefined ? {} : { sourceInfo: { path: sourceInfo.path } }),
|
|
127
|
-
})),
|
|
128
|
-
setActiveTools: (names) => pi.setActiveTools(names),
|
|
129
|
-
getActiveTools: () => pi.getActiveTools(),
|
|
130
|
-
requireGatekeeperPass: requirePiGatekeeperPass,
|
|
131
|
-
on(...registration) {
|
|
132
|
-
const context = (value) => projectPiContext(value, options.transcriptFromContext);
|
|
133
|
-
if (registration[0] === "before_agent_start") {
|
|
134
|
-
const [, handler] = registration;
|
|
135
|
-
pi.on("before_agent_start", (value, ctx) => {
|
|
136
|
-
const result = handler({ prompt: value.prompt, systemPrompt: value.systemPrompt, systemPromptOptions: value.systemPromptOptions }, context(ctx));
|
|
137
|
-
if (result instanceof Promise)
|
|
138
|
-
return result.then(foldBeforeAgentStartReturn);
|
|
139
|
-
return foldBeforeAgentStartReturn(result);
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
else if (registration[0] === "input") {
|
|
143
|
-
const [, handler] = registration;
|
|
144
|
-
pi.on("input", (value, ctx) => handler({ text: value.text, ...(value.images === undefined ? {} : { images: value.images }), source: value.source }, context(ctx)));
|
|
145
|
-
}
|
|
146
|
-
else if (registration[0] === "tool_call") {
|
|
147
|
-
const [, handler] = registration;
|
|
148
|
-
pi.on("tool_call", (value, ctx) => handler({ toolName: value.toolName, toolCallId: value.toolCallId, input: value.input }, context(ctx)));
|
|
149
|
-
}
|
|
150
|
-
else if (registration[0] === "tool_result") {
|
|
151
|
-
const [, handler] = registration;
|
|
152
|
-
pi.on("tool_result", (value, ctx) => handler({ toolName: value.toolName, toolCallId: value.toolCallId, isError: value.isError, content: (value.content ?? []).map((part) => part.type === "text" ? { type: "text", text: part.text } : { type: "image", data: part.data, mimeType: part.mimeType }), details: value.details }, context(ctx)));
|
|
153
|
-
}
|
|
154
|
-
else if (registration[0] === "session_start") {
|
|
155
|
-
const [, handler] = registration;
|
|
156
|
-
pi.on("session_start", (value, ctx) => handler({ reason: value.reason }, context(ctx)));
|
|
157
|
-
}
|
|
158
|
-
else if (registration[0] === "session_shutdown") {
|
|
159
|
-
const [, handler] = registration;
|
|
160
|
-
pi.on("session_shutdown", (_value, ctx) => handler({}, context(ctx)));
|
|
161
|
-
}
|
|
162
|
-
else if (registration[0] === "after_provider_response") {
|
|
163
|
-
const [, handler] = registration;
|
|
164
|
-
pi.on("after_provider_response", (value, ctx) => handler({ status: value.status }, context(ctx)));
|
|
165
|
-
}
|
|
166
|
-
else if (registration[0] === "agent_end") {
|
|
167
|
-
const [, handler] = registration;
|
|
168
|
-
pi.on("agent_end", (value, ctx) => handler({
|
|
169
|
-
messages: value.messages.map((message) => ({
|
|
170
|
-
role: message.role,
|
|
171
|
-
content: "content" in message && Array.isArray(message.content) ? message.content.map((part) => part.type === "text"
|
|
172
|
-
? { type: "text", text: part.text }
|
|
173
|
-
: part.type === "toolCall"
|
|
174
|
-
? { type: "toolCall", id: part.id, name: part.name, arguments: part.arguments }
|
|
175
|
-
: { type: part.type }) : [],
|
|
176
|
-
...("toolName" in message && typeof message.toolName === "string" ? { toolName: message.toolName } : {}),
|
|
177
|
-
...("isError" in message && typeof message.isError === "boolean" ? { isError: message.isError } : {}),
|
|
178
|
-
...("stopReason" in message && typeof message.stopReason === "string" ? { stopReason: message.stopReason } : {}),
|
|
179
|
-
})),
|
|
180
|
-
}, context(ctx)));
|
|
181
|
-
}
|
|
182
|
-
else if (registration[0] === "turn_end") {
|
|
183
|
-
const [, handler] = registration;
|
|
184
|
-
pi.on("turn_end", (value, ctx) => handler({
|
|
185
|
-
turnIndex: value.turnIndex,
|
|
186
|
-
calls: value.toolResults.map((result) => ({
|
|
187
|
-
toolCallId: result.toolCallId,
|
|
188
|
-
toolName: result.toolName,
|
|
189
|
-
})),
|
|
190
|
-
}, context(ctx)));
|
|
191
|
-
}
|
|
192
|
-
else if (registration[0] === "agent_settled") {
|
|
193
|
-
const [, handler] = registration;
|
|
194
|
-
pi.on("agent_settled", (_value, ctx) => handler({}, context(ctx)));
|
|
195
|
-
}
|
|
196
|
-
else if (registration[0] === "tool_execution_start") {
|
|
197
|
-
const [, handler] = registration;
|
|
198
|
-
pi.on("tool_execution_start", (value, ctx) => handler({ toolName: value.toolName, toolCallId: value.toolCallId }, context(ctx)));
|
|
199
|
-
}
|
|
200
|
-
else if (registration[0] === "tool_execution_update") {
|
|
201
|
-
const [, handler] = registration;
|
|
202
|
-
pi.on("tool_execution_update", (value, ctx) => handler({ toolName: value.toolName, toolCallId: value.toolCallId, partialResult: value.partialResult }, context(ctx)));
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const [, handler] = registration;
|
|
206
|
-
pi.on("tool_execution_end", (value, ctx) => handler({ toolName: value.toolName, toolCallId: value.toolCallId, isError: value.isError }, context(ctx)));
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
...(typeof pi.getCommands === "function" ? { getCommands: () => pi.getCommands().map(({ name }) => ({ name })) } : {}),
|
|
210
|
-
};
|
|
211
|
-
return {
|
|
212
|
-
host,
|
|
213
|
-
appendEntry: (customType, data) => pi.appendEntry(customType, data),
|
|
214
|
-
sendMessage: (message, sendOptions) => pi.sendMessage({
|
|
215
|
-
customType: message.customType,
|
|
216
|
-
content: message.content,
|
|
217
|
-
display: message.display ?? false,
|
|
218
|
-
details: message.details,
|
|
219
|
-
}, sendOptions),
|
|
220
|
-
startKeepalive: (context) => keepalive.start(toPiContext(context)),
|
|
221
|
-
stopKeepalive: () => keepalive.stop(),
|
|
222
|
-
};
|
|
223
|
-
}
|
package/dist/reviewer-agent.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { isReviewerPromptText } from "./reviewer-prompt-identity.js";
|
|
2
|
-
import { executeReviewerChild } from "./reviewer-child-executor.js";
|
|
3
|
-
import { createReviewerWorkspaceOwner } from "./reviewer-workspace.js";
|
|
4
|
-
import { normalizeReviewerFailureDiagnostic } from "./reviewer-failure-diagnostic.js";
|
|
5
|
-
function reviewerDispatchFailureMessage(outcome) {
|
|
6
|
-
const diagnostics = [...new Set(Object.values(outcome.legs)
|
|
7
|
-
.filter((leg) => leg?.status === "failed")
|
|
8
|
-
.map((leg) => leg.diagnostic.trim())
|
|
9
|
-
.filter((diagnostic) => diagnostic.length > 0))];
|
|
10
|
-
if (diagnostics.length === 0)
|
|
11
|
-
return "Reviewer dispatch execution failed";
|
|
12
|
-
return diagnostics.length === 1 ? diagnostics[0] : diagnostics.join("; ");
|
|
13
|
-
}
|
|
14
|
-
export class ReviewerDispatchExecutionError extends Error {
|
|
15
|
-
outcome;
|
|
16
|
-
constructor(outcome) {
|
|
17
|
-
super(reviewerDispatchFailureMessage(outcome));
|
|
18
|
-
this.outcome = outcome;
|
|
19
|
-
this.name = "ReviewerDispatchExecutionError";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function classify(error, signal) { if (signal?.aborted)
|
|
23
|
-
return "cancelled"; if (typeof error === "object" && error !== null && "reviewerFailure" in error)
|
|
24
|
-
return error.reviewerFailure; return "unknown"; }
|
|
25
|
-
function failed(error, target, prompt, signal, retained) {
|
|
26
|
-
const attached = typeof error === "object" && error !== null ? error : {};
|
|
27
|
-
const failure = classify(error, signal);
|
|
28
|
-
const diagnostic = normalizeReviewerFailureDiagnostic(error, failure);
|
|
29
|
-
return Object.freeze({
|
|
30
|
-
status: "failed",
|
|
31
|
-
failure,
|
|
32
|
-
diagnostic,
|
|
33
|
-
cause: error,
|
|
34
|
-
target: attached.targetSnapshot ?? target,
|
|
35
|
-
prompt,
|
|
36
|
-
workspaceDisposition: retained === undefined ? attached.workspaceDisposition ?? "not-created" : { retained },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
export function createReviewerAgentRunner(dependencies = {}) {
|
|
40
|
-
const workspaceOwner = createReviewerWorkspaceOwner(dependencies.fault === undefined ? {} : { fault: dependencies.fault });
|
|
41
|
-
let accepted = false;
|
|
42
|
-
return {
|
|
43
|
-
async run(dispatch, options) {
|
|
44
|
-
if (dispatch.recipe !== "reviewer-common-bundle-v1" || dispatch.legs.length < 1 || dispatch.legs.length > 2 || dispatch.legs[0]?.axis !== "standards" || (dispatch.legs.length === 2 && dispatch.legs[1]?.axis !== "spec"))
|
|
45
|
-
throw new Error("Invalid accepted Reviewer dispatch cardinality or axes");
|
|
46
|
-
if (accepted)
|
|
47
|
-
throw new Error("Reviewer runner accepts exactly one dispatch");
|
|
48
|
-
accepted = true;
|
|
49
|
-
for (const leg of dispatch.legs)
|
|
50
|
-
if (!isReviewerPromptText(leg.prompt))
|
|
51
|
-
throw new Error("Accepted Reviewer prompt evidence mismatch");
|
|
52
|
-
let batch;
|
|
53
|
-
try {
|
|
54
|
-
batch = await workspaceOwner.prepare(dispatch.targetSnapshot, dispatch.legs.map(l => l.axis), options.signal);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
const prepared = typeof error === "object" && error !== null && "preparedWorkspaces" in error ? error.preparedWorkspaces ?? [] : [];
|
|
58
|
-
const failedIndex = prepared.length;
|
|
59
|
-
const legs = Object.fromEntries(dispatch.legs.map((leg, index) => [leg.axis, failed(error, dispatch.targetSnapshot, leg.prompt, options.signal, index < failedIndex ? prepared[index].path : undefined)]));
|
|
60
|
-
// Siblings not yet attempted have no workspace; do not borrow the failed leg's retained path.
|
|
61
|
-
for (let index = failedIndex + 1; index < dispatch.legs.length; index += 1) {
|
|
62
|
-
const axis = dispatch.legs[index].axis;
|
|
63
|
-
legs[axis] = Object.freeze({ ...legs[axis], workspaceDisposition: "not-created" });
|
|
64
|
-
}
|
|
65
|
-
const target = Object.values(legs)[0].target;
|
|
66
|
-
throw new ReviewerDispatchExecutionError(Object.freeze({ identity: dispatch.identity, target, legs: Object.freeze(legs) }));
|
|
67
|
-
}
|
|
68
|
-
const settled = await Promise.allSettled(batch.workspaces.map(async (workspace) => {
|
|
69
|
-
const leg = dispatch.legs.find(candidate => candidate.axis === workspace.axis);
|
|
70
|
-
try {
|
|
71
|
-
const child = await executeReviewerChild(workspace.path, leg, options.context, {
|
|
72
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
73
|
-
...(dependencies.credentialScratchParent === undefined
|
|
74
|
-
? {}
|
|
75
|
-
: { credentialScratchParent: dependencies.credentialScratchParent }),
|
|
76
|
-
...(dependencies.packageRoot === undefined
|
|
77
|
-
? {}
|
|
78
|
-
: { packageRoot: dependencies.packageRoot }),
|
|
79
|
-
...(options.getFlag === undefined ? {} : { getFlag: options.getFlag }),
|
|
80
|
-
});
|
|
81
|
-
const disposition = await workspaceOwner.dispose(workspace);
|
|
82
|
-
return [leg.axis, Object.freeze({ status: "successful", report: child.report, usage: child.usage, target: batch.target, prompt: child.prompt, workspaceDisposition: disposition })];
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
// Failed legs retain their workspace for the durable failure evidence and caller cleanup.
|
|
86
|
-
return [leg.axis, failed(error, batch.target, leg.prompt, options.signal, workspace.path)];
|
|
87
|
-
}
|
|
88
|
-
}));
|
|
89
|
-
const pairs = settled.map(item => item.status === "fulfilled" ? item.value : (() => { throw item.reason; })());
|
|
90
|
-
const outcome = Object.freeze({ identity: dispatch.identity, target: batch.target, legs: Object.freeze(Object.fromEntries(pairs)) });
|
|
91
|
-
if (Object.values(outcome.legs).some(leg => leg?.status === "failed"))
|
|
92
|
-
throw new ReviewerDispatchExecutionError(outcome);
|
|
93
|
-
return outcome;
|
|
94
|
-
},
|
|
95
|
-
shutdown: () => workspaceOwner.shutdown(),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Host-level Reviewer binding: each accepted dispatch gets a fresh one-shot runner.
|
|
100
|
-
* Auto-resume turns must not reuse the inner one-shot slot.
|
|
101
|
-
*/
|
|
102
|
-
export function createPerDispatchReviewerAgent(dependencies = {}) {
|
|
103
|
-
const active = new Set();
|
|
104
|
-
return {
|
|
105
|
-
async run(dispatch, options) {
|
|
106
|
-
const runner = createReviewerAgentRunner(dependencies);
|
|
107
|
-
active.add(runner);
|
|
108
|
-
try {
|
|
109
|
-
return await runner.run(dispatch, options);
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
active.delete(runner);
|
|
113
|
-
await runner.shutdown();
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
async shutdown() {
|
|
117
|
-
const runners = [...active];
|
|
118
|
-
active.clear();
|
|
119
|
-
await Promise.all(runners.map((runner) => runner.shutdown()));
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|