@claudexor/orchestrator 1.0.1
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 +21 -0
- package/README.md +7 -0
- package/dist/attemptTelemetry.d.ts +82 -0
- package/dist/attemptTelemetry.d.ts.map +1 -0
- package/dist/attemptTelemetry.js +283 -0
- package/dist/attemptTelemetry.js.map +1 -0
- package/dist/diffReview.d.ts +32 -0
- package/dist/diffReview.d.ts.map +1 -0
- package/dist/diffReview.js +69 -0
- package/dist/diffReview.js.map +1 -0
- package/dist/finalVerifier.d.ts +25 -0
- package/dist/finalVerifier.d.ts.map +1 -0
- package/dist/finalVerifier.js +110 -0
- package/dist/finalVerifier.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction.d.ts +22 -0
- package/dist/interaction.d.ts.map +1 -0
- package/dist/interaction.js +117 -0
- package/dist/interaction.js.map +1 -0
- package/dist/modelGovernance.d.ts +22 -0
- package/dist/modelGovernance.d.ts.map +1 -0
- package/dist/modelGovernance.js +33 -0
- package/dist/modelGovernance.js.map +1 -0
- package/dist/orchestratePlanner.d.ts +12 -0
- package/dist/orchestratePlanner.d.ts.map +1 -0
- package/dist/orchestratePlanner.js +91 -0
- package/dist/orchestratePlanner.js.map +1 -0
- package/dist/orchestrator.d.ts +380 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +4927 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/reviewerPanel.d.ts +26 -0
- package/dist/reviewerPanel.d.ts.map +1 -0
- package/dist/reviewerPanel.js +146 -0
- package/dist/reviewerPanel.js.map +1 -0
- package/dist/runSupport.d.ts +178 -0
- package/dist/runSupport.d.ts.map +1 -0
- package/dist/runSupport.js +372 -0
- package/dist/runSupport.js.map +1 -0
- package/dist/runTerminals.d.ts +56 -0
- package/dist/runTerminals.d.ts.map +1 -0
- package/dist/runTerminals.js +124 -0
- package/dist/runTerminals.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FinalVerifier (INV-115): before a race winner becomes adoptable/
|
|
3
|
+
* applyable, its patch is applied onto a FRESH worktree at the winner's own
|
|
4
|
+
* base sha and the deterministic gates re-run there. This catches the class
|
|
5
|
+
* of "reviewed green in the candidate tree, broken against the real base"
|
|
6
|
+
* failures (stale envelope, gate side effects) with zero model spend.
|
|
7
|
+
* Deterministic-first: no model involvement; gates cost no USD, so the
|
|
8
|
+
* candidate ledger is trivially respected.
|
|
9
|
+
*/
|
|
10
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { FinalVerifyRecord } from "@claudexor/schema";
|
|
14
|
+
import { gatesPassed, runGates } from "@claudexor/review";
|
|
15
|
+
import { applyPatchProtected, branchDelete, worktreeAdd, worktreeRemove } from "@claudexor/workspace";
|
|
16
|
+
import { newId, redactSecrets } from "@claudexor/util";
|
|
17
|
+
/** FAIL-CLOSED verdict (INV-115): an attempted verify that did not PROVE
|
|
18
|
+
* applied_cleanly (false OR null/errored) or whose gates failed blocks the
|
|
19
|
+
* run. One owner — race and convergence consume the same rule. */
|
|
20
|
+
export function finalVerifyBlocks(finalVerify) {
|
|
21
|
+
return (finalVerify !== null &&
|
|
22
|
+
finalVerify.attempted &&
|
|
23
|
+
(finalVerify.applied_cleanly !== true || finalVerify.gates_passed === false));
|
|
24
|
+
}
|
|
25
|
+
/** The persisted decision must AGREE with a blocked terminal: status/outcome
|
|
26
|
+
* blocked, human_review recommendation, and an evidence fact naming the cause.
|
|
27
|
+
* One owner for the race and convergence decision writes. */
|
|
28
|
+
export function blockedDecisionOverride(evidenceFacts, finalVerify) {
|
|
29
|
+
const fact = finalVerifyBlocks(finalVerify)
|
|
30
|
+
? finalVerify?.applied_cleanly !== true
|
|
31
|
+
? `final verify failed: ${finalVerify?.reason ?? "patch did not survive a fresh tree at its base"}`
|
|
32
|
+
: "final verify failed: deterministic gates failed on the fresh verify tree"
|
|
33
|
+
: "reviewer escalated blocking NEEDS_HUMAN findings; a typed operator decision is required";
|
|
34
|
+
return {
|
|
35
|
+
status: "blocked",
|
|
36
|
+
outcome: "blocked",
|
|
37
|
+
apply_recommendation: "human_review",
|
|
38
|
+
evidence_facts: [...evidenceFacts, fact],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function finalVerifyPatch(execRoot, winner, specs, log) {
|
|
42
|
+
const started = Date.now();
|
|
43
|
+
const done = (fields) => FinalVerifyRecord.parse({
|
|
44
|
+
attempted: true,
|
|
45
|
+
base_sha: winner.baseSha ?? null,
|
|
46
|
+
duration_ms: Date.now() - started,
|
|
47
|
+
...fields,
|
|
48
|
+
});
|
|
49
|
+
if (!winner.baseSha) {
|
|
50
|
+
// FAIL CLOSED: the in-place exemption is a CALLER-level decision (the
|
|
51
|
+
// orchestrator skips in-place turns before calling). An ENVELOPE patch
|
|
52
|
+
// reaching this point without a recorded base sha cannot be proven
|
|
53
|
+
// against a clean base — that blocks like any other verifier error,
|
|
54
|
+
// never silently bypasses INV-115.
|
|
55
|
+
return FinalVerifyRecord.parse({
|
|
56
|
+
attempted: true,
|
|
57
|
+
applied_cleanly: null,
|
|
58
|
+
reason: "no base sha recorded for the winner envelope; cannot verify against a clean base",
|
|
59
|
+
duration_ms: Date.now() - started,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const verifyBase = mkdtempSync(join(tmpdir(), "claudexor-verify-"));
|
|
63
|
+
const verifyTree = join(verifyBase, "tree");
|
|
64
|
+
const branch = `claudexor/verify-${newId("fv").slice(3)}`;
|
|
65
|
+
try {
|
|
66
|
+
await worktreeAdd(execRoot, verifyTree, branch, winner.baseSha);
|
|
67
|
+
const applied = await applyPatchProtected(verifyTree, winner.diff);
|
|
68
|
+
if (!applied.ok) {
|
|
69
|
+
return done({ applied_cleanly: false, reason: applied.detail ?? "apply failed on the verify tree" });
|
|
70
|
+
}
|
|
71
|
+
if (specs.length === 0) {
|
|
72
|
+
return done({ applied_cleanly: true, gates_passed: null, reason: "no deterministic gates configured" });
|
|
73
|
+
}
|
|
74
|
+
const gates = await runGates(specs, { cwd: verifyTree });
|
|
75
|
+
log.emit("gate.completed", {
|
|
76
|
+
attempt_id: "final-verify",
|
|
77
|
+
gates: gates.map((g) => ({ id: g.id, status: g.status, exit_code: g.exit_code, duration_ms: g.duration_ms })),
|
|
78
|
+
passed: gatesPassed(gates),
|
|
79
|
+
});
|
|
80
|
+
return done({
|
|
81
|
+
applied_cleanly: true,
|
|
82
|
+
gates_passed: gatesPassed(gates),
|
|
83
|
+
gates: gates.map((g) => ({ id: g.id, status: g.status })),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return done({ applied_cleanly: null, reason: redactSecrets(err instanceof Error ? err.message : String(err)) });
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
try {
|
|
91
|
+
await worktreeRemove(execRoot, verifyTree);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
/* best-effort */
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await branchDelete(execRoot, branch);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* best-effort */
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
rmSync(verifyBase, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* best-effort */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=finalVerifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalVerifier.js","sourceRoot":"","sources":["../src/finalVerifier.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-attempt interaction channel. Emits the typed lifecycle events
|
|
3
|
+
* (`interaction.requested` / `interaction.answered` / `interaction.timeout`)
|
|
4
|
+
* around the caller-provided answer surface, enforcing the wait budget so a
|
|
5
|
+
* run can never hang forever on an unanswered question. Undefined when the
|
|
6
|
+
* caller provides no surface — the adapter then runs non-interactive.
|
|
7
|
+
*
|
|
8
|
+
* Capability gate: the channel is OFFERED only to routes whose manifest
|
|
9
|
+
* declares `interactive` — a non-interactive harness never gets a surface it
|
|
10
|
+
* cannot raise questions through.
|
|
11
|
+
*/
|
|
12
|
+
import type { InteractionChannel } from "@claudexor/core";
|
|
13
|
+
import type { InteractionAnswerSet } from "@claudexor/schema";
|
|
14
|
+
import type { EventLog } from "@claudexor/event-log";
|
|
15
|
+
import type { PendingInteractionContext } from "./orchestrator.js";
|
|
16
|
+
export interface InteractionChannelWiring {
|
|
17
|
+
onInteraction?: (ctx: PendingInteractionContext) => Promise<InteractionAnswerSet | null>;
|
|
18
|
+
interactionTimeoutMs?: number;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
export declare function interactionChannelFor(input: InteractionChannelWiring, log: EventLog, runId: string, taskId: string, attemptId: string, harnessId: string, supportsInteractive: boolean, defaultTimeoutMs: number): InteractionChannel | undefined;
|
|
22
|
+
//# sourceMappingURL=interaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.d.ts","sourceRoot":"","sources":["../src/interaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,oBAAoB,EAAsB,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACvC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACzF,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,wBAAwB,EAC/B,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,mBAAmB,EAAE,OAAO,EAC5B,gBAAgB,EAAE,MAAM,GACvB,kBAAkB,GAAG,SAAS,CA4GhC"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { nowIso } from "@claudexor/util";
|
|
2
|
+
export function interactionChannelFor(input, log, runId, taskId, attemptId, harnessId, supportsInteractive, defaultTimeoutMs) {
|
|
3
|
+
if (!supportsInteractive)
|
|
4
|
+
return undefined;
|
|
5
|
+
const handler = input.onInteraction;
|
|
6
|
+
if (!handler)
|
|
7
|
+
return undefined;
|
|
8
|
+
const timeoutMs = input.interactionTimeoutMs ?? defaultTimeoutMs;
|
|
9
|
+
// Waiting on a human is legitimate stream silence: the inactivity watchdog
|
|
10
|
+
// consults this count and re-arms instead of killing the "wedged" harness.
|
|
11
|
+
let pending = 0;
|
|
12
|
+
return {
|
|
13
|
+
pendingCount: () => pending,
|
|
14
|
+
request: async (request) => {
|
|
15
|
+
const requestedAt = nowIso();
|
|
16
|
+
const timeoutAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
17
|
+
pending += 1;
|
|
18
|
+
try {
|
|
19
|
+
// Invoke the answer surface BEFORE announcing the event: handlers
|
|
20
|
+
// register the pending question synchronously (daemon
|
|
21
|
+
// InteractionRegistry), so any subscriber that reacts to
|
|
22
|
+
// interaction.requested — `claudexor follow` checks pendingInteractions
|
|
23
|
+
// before prompting — finds the registry already populated. The reverse
|
|
24
|
+
// order would make that guarantee depend on event-loop timing.
|
|
25
|
+
// The handler is invoked SYNCHRONOUSLY (the registry-population contract
|
|
26
|
+
// below depends on it); only its failure handling is normalized — a
|
|
27
|
+
// synchronous throw becomes the same null-answer path as an async one.
|
|
28
|
+
let answersPromise;
|
|
29
|
+
try {
|
|
30
|
+
answersPromise = Promise.resolve(handler({
|
|
31
|
+
runId,
|
|
32
|
+
taskId,
|
|
33
|
+
attemptId,
|
|
34
|
+
harnessId,
|
|
35
|
+
request,
|
|
36
|
+
requestedAt,
|
|
37
|
+
timeoutAt,
|
|
38
|
+
})).catch(() => null);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
answersPromise = Promise.resolve(null);
|
|
42
|
+
}
|
|
43
|
+
log.emit("interaction.requested", {
|
|
44
|
+
interaction_id: request.interaction_id,
|
|
45
|
+
attempt_id: attemptId,
|
|
46
|
+
harness_id: harnessId,
|
|
47
|
+
source_tool: request.source_tool,
|
|
48
|
+
questions: request.questions,
|
|
49
|
+
requested_at: requestedAt,
|
|
50
|
+
timeout_at: timeoutAt,
|
|
51
|
+
});
|
|
52
|
+
let timer;
|
|
53
|
+
let onAbort;
|
|
54
|
+
const startedWaiting = Date.now();
|
|
55
|
+
const answers = await Promise.race([
|
|
56
|
+
answersPromise,
|
|
57
|
+
new Promise((resolve) => {
|
|
58
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
59
|
+
timer.unref?.();
|
|
60
|
+
}),
|
|
61
|
+
// A cancelled run must release the interaction wait IMMEDIATELY —
|
|
62
|
+
// the abort already kills the harness process, and sitting out the
|
|
63
|
+
// remaining timeout would park a dead run in waiting_on_user.
|
|
64
|
+
new Promise((resolve) => {
|
|
65
|
+
if (!input.signal)
|
|
66
|
+
return;
|
|
67
|
+
if (input.signal.aborted)
|
|
68
|
+
return resolve(null);
|
|
69
|
+
onAbort = () => resolve(null);
|
|
70
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
if (timer)
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
if (onAbort)
|
|
76
|
+
input.signal?.removeEventListener("abort", onAbort);
|
|
77
|
+
if (answers && answers.answers.length > 0) {
|
|
78
|
+
log.emit("interaction.answered", {
|
|
79
|
+
interaction_id: request.interaction_id,
|
|
80
|
+
attempt_id: attemptId,
|
|
81
|
+
harness_id: harnessId,
|
|
82
|
+
answer_count: answers.answers.length,
|
|
83
|
+
});
|
|
84
|
+
return answers;
|
|
85
|
+
}
|
|
86
|
+
log.emit("interaction.timeout", {
|
|
87
|
+
interaction_id: request.interaction_id,
|
|
88
|
+
attempt_id: attemptId,
|
|
89
|
+
harness_id: harnessId,
|
|
90
|
+
waited_ms: Date.now() - startedWaiting,
|
|
91
|
+
...(input.signal?.aborted ? { reason: "cancelled" } : {}),
|
|
92
|
+
});
|
|
93
|
+
// Late-answer honesty: the run already declined this
|
|
94
|
+
// interaction; an answer arriving AFTER the timeout must be visibly
|
|
95
|
+
// DISCARDED, not silently swallowed (the user typed it in good faith).
|
|
96
|
+
void answersPromise.then((late) => {
|
|
97
|
+
if (late && late.answers.length > 0) {
|
|
98
|
+
log.emit("interaction.answer_discarded", {
|
|
99
|
+
interaction_id: request.interaction_id,
|
|
100
|
+
attempt_id: attemptId,
|
|
101
|
+
harness_id: harnessId,
|
|
102
|
+
answer_count: late.answers.length,
|
|
103
|
+
reason: input.signal?.aborted ? "run_cancelled" : "timed_out",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
// ALWAYS release the suspension: a synchronous handler throw or a
|
|
111
|
+
// log.emit failure must not leave the watchdog suspended forever.
|
|
112
|
+
pending -= 1;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=interaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../src/interaction.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AASzC,MAAM,UAAU,qBAAqB,CACnC,KAA+B,EAC/B,GAAa,EACb,KAAa,EACb,MAAc,EACd,SAAiB,EACjB,SAAiB,EACjB,mBAA4B,EAC5B,gBAAwB;IAExB,IAAI,CAAC,mBAAmB;QAAE,OAAO,SAAS,CAAC;IAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,SAAS,GAAG,KAAK,CAAC,oBAAoB,IAAI,gBAAgB,CAAC;IACjE,2EAA2E;IAC3E,2EAA2E;IAC3E,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,OAAO;QACL,YAAY,EAAE,GAAG,EAAE,CAAC,OAAO;QAC3B,OAAO,EAAE,KAAK,EAAE,OAA2B,EAAwC,EAAE;YACnF,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;YACjE,OAAO,IAAI,CAAC,CAAC;YACb,IAAI,CAAC;gBACH,kEAAkE;gBAClE,sDAAsD;gBACtD,yDAAyD;gBACzD,wEAAwE;gBACxE,uEAAuE;gBACvE,+DAA+D;gBAC/D,yEAAyE;gBACzE,oEAAoE;gBACpE,uEAAuE;gBACvE,IAAI,cAAoD,CAAC;gBACzD,IAAI,CAAC;oBACH,cAAc,GAAG,OAAO,CAAC,OAAO,CAC9B,OAAO,CAAC;wBACN,KAAK;wBACL,MAAM;wBACN,SAAS;wBACT,SAAS;wBACT,OAAO;wBACP,WAAW;wBACX,SAAS;qBACV,CAAC,CACH,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;gBACtB,CAAC;gBAAC,MAAM,CAAC;oBACP,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBACzC,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,uBAAuB,EAAE;oBAChC,cAAc,EAAE,OAAO,CAAC,cAAc;oBACtC,UAAU,EAAE,SAAS;oBACrB,UAAU,EAAE,SAAS;oBACrB,WAAW,EAAE,OAAO,CAAC,WAAW;oBAChC,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,YAAY,EAAE,WAAW;oBACzB,UAAU,EAAE,SAAS;iBACtB,CAAC,CAAC;gBACH,IAAI,KAAiC,CAAC;gBACtC,IAAI,OAAiC,CAAC;gBACtC,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;oBACjC,cAAc;oBACd,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;wBAC5B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;wBACnD,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;oBAClB,CAAC,CAAC;oBACF,kEAAkE;oBAClE,mEAAmE;oBACnE,8DAA8D;oBAC9D,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;wBAC5B,IAAI,CAAC,KAAK,CAAC,MAAM;4BAAE,OAAO;wBAC1B,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO;4BAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;wBAC/C,OAAO,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;wBAC9B,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;oBAClE,CAAC,CAAC;iBACH,CAAC,CAAC;gBACH,IAAI,KAAK;oBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;gBAC/B,IAAI,OAAO;oBAAE,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;gBACjE,IAAI,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC1C,GAAG,CAAC,IAAI,CAAC,sBAAsB,EAAE;wBAC/B,cAAc,EAAE,OAAO,CAAC,cAAc;wBACtC,UAAU,EAAE,SAAS;wBACrB,UAAU,EAAE,SAAS;wBACrB,YAAY,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM;qBACrC,CAAC,CAAC;oBACH,OAAO,OAAO,CAAC;gBACjB,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,qBAAqB,EAAE;oBAC9B,cAAc,EAAE,OAAO,CAAC,cAAc;oBACtC,UAAU,EAAE,SAAS;oBACrB,UAAU,EAAE,SAAS;oBACrB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,cAAc;oBACtC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1D,CAAC,CAAC;gBACH,qDAAqD;gBACrD,oEAAoE;gBACpE,uEAAuE;gBACvE,KAAK,cAAc,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;oBAChC,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACpC,GAAG,CAAC,IAAI,CAAC,8BAA8B,EAAE;4BACvC,cAAc,EAAE,OAAO,CAAC,cAAc;4BACtC,UAAU,EAAE,SAAS;4BACrB,UAAU,EAAE,SAAS;4BACrB,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;4BACjC,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW;yBAC9D,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,OAAO,IAAI,CAAC;YACd,CAAC;oBAAS,CAAC;gBACT,kEAAkE;gBAClE,kEAAkE;gBAClE,OAAO,IAAI,CAAC,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STRICT run-preflight model gate (INV-104): every route that resolved an
|
|
3
|
+
* explicit model (per-run map or per-harness settings default) must pass its
|
|
4
|
+
* harness's model truth source — the live `models()` inventory when the
|
|
5
|
+
* adapter has one, else the manifest `known_models` list. A violation throws
|
|
6
|
+
* a typed error BEFORE any vendor CLI spawns; the orchestrator surfaces it
|
|
7
|
+
* through the routing-failure path, so failure.yaml names harness, model, and
|
|
8
|
+
* truth source. Fallback models get the same gate: a downgrade target that
|
|
9
|
+
* cannot run is a config bug better caught before the run than mid-run.
|
|
10
|
+
*/
|
|
11
|
+
import type { HarnessAdapter } from "@claudexor/core";
|
|
12
|
+
export interface ModelGovernedRoute {
|
|
13
|
+
adapter: HarnessAdapter;
|
|
14
|
+
/** Manifest model truth source (used when the adapter has no live models()). */
|
|
15
|
+
knownModels: readonly string[];
|
|
16
|
+
settings: {
|
|
17
|
+
defaultModel: string | null;
|
|
18
|
+
fallbackModel: string | null;
|
|
19
|
+
} | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function assertRouteModelsAllowed(routes: readonly ModelGovernedRoute[], models: Record<string, string> | undefined, cwd: string): Promise<void>;
|
|
22
|
+
//# sourceMappingURL=modelGovernance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelGovernance.d.ts","sourceRoot":"","sources":["../src/modelGovernance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGtD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,gFAAgF;IAChF,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,QAAQ,EAAE;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CAChF;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC1C,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CA6Bf"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HarnessUnavailableError, validateModel } from "@claudexor/core";
|
|
2
|
+
export async function assertRouteModelsAllowed(routes, models, cwd) {
|
|
3
|
+
const checked = new Set();
|
|
4
|
+
for (const routed of routes) {
|
|
5
|
+
const id = routed.adapter.id;
|
|
6
|
+
if (checked.has(id))
|
|
7
|
+
continue;
|
|
8
|
+
checked.add(id);
|
|
9
|
+
const resolved = models?.[id] ?? routed.settings?.defaultModel ?? null;
|
|
10
|
+
const candidates = [
|
|
11
|
+
{ role: "model", model: resolved },
|
|
12
|
+
{ role: "fallback_model", model: routed.settings?.fallbackModel ?? null },
|
|
13
|
+
].filter((c) => Boolean(c.model));
|
|
14
|
+
if (candidates.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
let truth;
|
|
17
|
+
if (typeof routed.adapter.models === "function") {
|
|
18
|
+
const inventory = await routed.adapter.models({ cwd });
|
|
19
|
+
truth = { list: inventory.map((m) => m.id), source: "api" };
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
truth = { list: routed.knownModels, source: "manifest" };
|
|
23
|
+
}
|
|
24
|
+
for (const { role, model } of candidates) {
|
|
25
|
+
const check = validateModel(model, truth.list, truth.source);
|
|
26
|
+
if (check.status !== "ok") {
|
|
27
|
+
throw new HarnessUnavailableError(`harness '${id}' refused ${role} '${model}' (truth source: ${truth.source}): ${check.message}; ` +
|
|
28
|
+
`run \`claudexor models --harness ${id}\``);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=modelGovernance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelGovernance.js","sourceRoot":"","sources":["../src/modelGovernance.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AASzE,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAAqC,EACrC,MAA0C,EAC1C,GAAW;IAEX,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,SAAS;QAC9B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,YAAY,IAAI,IAAI,CAAC;QACvE,MAAM,UAAU,GAAG;YACjB,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE;YAClC,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,aAAa,IAAI,IAAI,EAAE;SAC1E,CAAC,MAAM,CAAC,CAAC,CAAC,EAAwC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACtC,IAAI,KAA8D,CAAC;QACnE,IAAI,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAChD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACvD,KAAK,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC9D,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QAC3D,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,UAAU,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7D,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC1B,MAAM,IAAI,uBAAuB,CAC/B,YAAY,EAAE,aAAa,IAAI,KAAK,KAAK,oBAAoB,KAAK,CAAC,MAAM,MAAM,KAAK,CAAC,OAAO,IAAI;oBAC9F,oCAAoC,EAAE,IAAI,CAC7C,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt framing for the orchestrate PLANNER (plan-only). The tool belt comes
|
|
3
|
+
* from the typed OrchestrateContract — the prompt never invents actions the
|
|
4
|
+
* executor will not honor.
|
|
5
|
+
*/
|
|
6
|
+
import type { OrchestrateContract as OrchestrateContractT, OrchestratePlan as OrchestratePlanT } from "@claudexor/schema";
|
|
7
|
+
export declare function buildOrchestratePlannerPrompt(goal: string, pool: string[], crossFamily: boolean, contract: OrchestrateContractT): string;
|
|
8
|
+
export declare function extractOrchestratePlan(report: string): {
|
|
9
|
+
plan: OrchestratePlanT | null;
|
|
10
|
+
error: string;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=orchestratePlanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestratePlanner.d.ts","sourceRoot":"","sources":["../src/orchestratePlanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,IAAI,oBAAoB,EAAE,eAAe,IAAI,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1H,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EAAE,EACd,WAAW,EAAE,OAAO,EACpB,QAAQ,EAAE,oBAAoB,GAC7B,MAAM,CA4BR;AAuBD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CA2BvG"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { OrchestratePlan as OrchestratePlanSchema } from "@claudexor/schema";
|
|
2
|
+
import { redactSecrets } from "@claudexor/util";
|
|
3
|
+
function safeErrorMessage(err) {
|
|
4
|
+
return redactSecrets(err instanceof Error ? err.message : String(err));
|
|
5
|
+
}
|
|
6
|
+
export function buildOrchestratePlannerPrompt(goal, pool, crossFamily, contract) {
|
|
7
|
+
return [
|
|
8
|
+
`You are the Claudexor orchestration planner. Plan — do not implement.`,
|
|
9
|
+
``,
|
|
10
|
+
`## Goal`,
|
|
11
|
+
goal,
|
|
12
|
+
``,
|
|
13
|
+
`## Available harness pool (doctor-verified)`,
|
|
14
|
+
pool.length > 0
|
|
15
|
+
? pool.map((id) => `- ${id}`).join("\n")
|
|
16
|
+
: "- (none verified; plan must say what setup is needed)",
|
|
17
|
+
crossFamily
|
|
18
|
+
? `Cross-family race and cross-family review ARE available (2+ harnesses).`
|
|
19
|
+
: `Only single-route execution is available (fewer than 2 verified harnesses).`,
|
|
20
|
+
``,
|
|
21
|
+
`## Tool belt (the ONLY actions your plan may use)`,
|
|
22
|
+
...contract.tool_belt.map((t) => `- ${t}`),
|
|
23
|
+
``,
|
|
24
|
+
`## Required output`,
|
|
25
|
+
`1. A concise markdown orchestration plan (numbered steps; each step names ONE tool and its arguments).`,
|
|
26
|
+
'2. A fenced ```json block: {"tool_calls": [ … ]}. Each call puts the tool name AND its arguments at the TOP LEVEL — there is NO nested "args" object. Per-tool shapes (use only belt tools):',
|
|
27
|
+
' - start_run: {"tool":"start_run","prompt":"…","mode":"agent","harness":"<optional id>","why":"…"}',
|
|
28
|
+
' - race: {"tool":"race","prompt":"…","n":2,"why":"…"}',
|
|
29
|
+
' - review: {"tool":"review","run_id":"<id>","why":"…"}',
|
|
30
|
+
' - status: {"tool":"status","run_id":"<id>","why":"…"}',
|
|
31
|
+
' - apply: {"tool":"apply","run_id":"<id>","mode":"apply","why":"…"}',
|
|
32
|
+
`Keep the plan minimal and budget-aware. Do not propose tools outside the belt.`,
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Extract + validate the planner's typed plan from its markdown report (the
|
|
37
|
+
* fenced ```json block the orchestrate prompt requires). Structured-output
|
|
38
|
+
* parsing, not governance: validity is decided by the OrchestratePlan schema.
|
|
39
|
+
*/
|
|
40
|
+
/** Schema-constrained routes emit explicit `null` for OPTIONAL FIELDS (the
|
|
41
|
+
* strict-mode nullable recipe); Zod `.optional()` rejects null, so those are
|
|
42
|
+
* stripped before parsing — translational, never inventing values. Null
|
|
43
|
+
* ARRAY ELEMENTS are NOT part of that recipe: they are a malformed plan and
|
|
44
|
+
* must FAIL the Zod parse loudly (no silent truncation). */
|
|
45
|
+
function stripNulls(value) {
|
|
46
|
+
if (Array.isArray(value))
|
|
47
|
+
return value.map(stripNulls);
|
|
48
|
+
if (!value || typeof value !== "object")
|
|
49
|
+
return value;
|
|
50
|
+
const out = {};
|
|
51
|
+
for (const [k, v] of Object.entries(value)) {
|
|
52
|
+
if (v === null)
|
|
53
|
+
continue;
|
|
54
|
+
out[k] = stripNulls(v);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
export function extractOrchestratePlan(report) {
|
|
59
|
+
// STRUCTURED-FIRST: a schema-constrained route emits the plan as the
|
|
60
|
+
// bare final message — try the whole report as JSON before fence-hunting.
|
|
61
|
+
const bare = report.trim();
|
|
62
|
+
if (bare.startsWith("{") && bare.endsWith("}")) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = OrchestratePlanSchema.safeParse(stripNulls(JSON.parse(bare)));
|
|
65
|
+
if (parsed.success)
|
|
66
|
+
return { plan: parsed.data, error: "" };
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* not bare JSON — fall through to fenced parsing */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const fence = /```json\s*\n([\s\S]*?)\n```/g;
|
|
73
|
+
let lastBlock = null;
|
|
74
|
+
for (const match of report.matchAll(fence))
|
|
75
|
+
lastBlock = match[1] ?? null;
|
|
76
|
+
if (!lastBlock)
|
|
77
|
+
return { plan: null, error: "no fenced json block found in the planner report" };
|
|
78
|
+
try {
|
|
79
|
+
const parsed = OrchestratePlanSchema.safeParse(stripNulls(JSON.parse(lastBlock)));
|
|
80
|
+
if (!parsed.success)
|
|
81
|
+
return {
|
|
82
|
+
plan: null,
|
|
83
|
+
error: `plan block failed schema validation: ${parsed.error.issues[0]?.message ?? "invalid"}`,
|
|
84
|
+
};
|
|
85
|
+
return { plan: parsed.data, error: "" };
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return { plan: null, error: `plan block is not valid JSON: ${safeErrorMessage(err)}` };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=orchestratePlanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestratePlanner.js","sourceRoot":"","sources":["../src/orchestratePlanner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,SAAS,gBAAgB,CAAC,GAAY;IACpC,OAAO,aAAa,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,IAAY,EACZ,IAAc,EACd,WAAoB,EACpB,QAA8B;IAE9B,OAAO;QACL,uEAAuE;QACvE,EAAE;QACF,SAAS;QACT,IAAI;QACJ,EAAE;QACF,6CAA6C;QAC7C,IAAI,CAAC,MAAM,GAAG,CAAC;YACb,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACxC,CAAC,CAAC,uDAAuD;QAC3D,WAAW;YACT,CAAC,CAAC,yEAAyE;YAC3E,CAAC,CAAC,6EAA6E;QACjF,EAAE;QACF,mDAAmD;QACnD,GAAG,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,EAAE;QACF,oBAAoB;QACpB,wGAAwG;QACxG,8LAA8L;QAC9L,sGAAsG;QACtG,8DAA8D;QAC9D,6DAA6D;QAC7D,6DAA6D;QAC7D,2EAA2E;QAC3E,gFAAgF;KACjF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH;;;;4DAI4D;AAC5D,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC,KAAK,IAAI;YAAE,SAAS;QACzB,GAAG,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,qEAAqE;IACrE,0EAA0E;IAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC7E,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,oDAAoD;QACtD,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,8BAA8B,CAAC;IAC7C,IAAI,SAAS,GAAkB,IAAI,CAAC;IACpC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACzE,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,kDAAkD,EAAE,CAAC;IACjG,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,OAAO;YACjB,OAAO;gBACL,IAAI,EAAE,IAAI;gBACV,KAAK,EAAE,wCAAwC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,SAAS,EAAE;aAC9F,CAAC;QACJ,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,iCAAiC,gBAAgB,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;IACzF,CAAC;AACH,CAAC"}
|