@tokenfactory/acc-runner 0.38.2 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-companion.d.ts +101 -0
- package/dist/chat-companion.d.ts.map +1 -0
- package/dist/chat-companion.js +183 -0
- package/dist/chat-companion.js.map +1 -0
- package/dist/chat-lane.d.ts +286 -0
- package/dist/chat-lane.d.ts.map +1 -0
- package/dist/chat-lane.js +328 -0
- package/dist/chat-lane.js.map +1 -0
- package/dist/chat-transport.d.ts +51 -0
- package/dist/chat-transport.d.ts.map +1 -0
- package/dist/chat-transport.js +105 -0
- package/dist/chat-transport.js.map +1 -0
- package/dist/cli.js +45 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +30 -0
- package/dist/doctor.js.map +1 -1
- package/dist/runtime/chat-circuit-breaker.d.ts +66 -0
- package/dist/runtime/chat-circuit-breaker.d.ts.map +1 -0
- package/dist/runtime/chat-circuit-breaker.js +128 -0
- package/dist/runtime/chat-circuit-breaker.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC-1B (TURN-LANE): the companion claim loop.
|
|
3
|
+
*
|
|
4
|
+
* A COMPANION is a runner attached to one or more operator chats that serves
|
|
5
|
+
* their queued turns, distinct from a fleet runner draining the task lane
|
|
6
|
+
* (WORKSPACE_COMMAND_CHAT_CONTRACTS §2). This module is the chat-lane analogue of
|
|
7
|
+
* the batch `pump()` in watch.ts: it repeatedly claims queued turns across its
|
|
8
|
+
* conversations and drives each with `driveChatTurn`, subject to two guards that
|
|
9
|
+
* keep it from ever starving the code/review lanes:
|
|
10
|
+
*
|
|
11
|
+
* 1. MAX-SHARE — `computeChatClaimBudget` caps how many concurrency slots chat
|
|
12
|
+
* may hold (always leaving ≥1 for code/review), reading the batch lane's
|
|
13
|
+
* live in-flight count via the injected `activeCodeTasks()`.
|
|
14
|
+
* 2. CIRCUIT BREAKER — while the chat circuit is open (a `claude_exit_1`-class
|
|
15
|
+
* fault tripped it), the budget is 0 so no chat turn is claimed; the code
|
|
16
|
+
* lane is untouched.
|
|
17
|
+
*
|
|
18
|
+
* The loop owns per-turn `AbortController`s so an operator cancel (or runner
|
|
19
|
+
* shutdown) SIGTERMs the in-flight engine child and the driver requeues the turn
|
|
20
|
+
* cleanly — no duplicate completion. Everything (transport, engine, clock,
|
|
21
|
+
* active-task count, delegation minting) is injected, so the loop is exercised
|
|
22
|
+
* deterministically without a network or a `claude` binary.
|
|
23
|
+
*/
|
|
24
|
+
import { type ChatLaneTransport, type ChatTurnDriveResult, type ClaimedChatTurn } from "./chat-lane.js";
|
|
25
|
+
import type { ChatCircuitBreaker } from "./runtime/chat-circuit-breaker.js";
|
|
26
|
+
import type { ConformantAgentEngine } from "./engines/types.js";
|
|
27
|
+
export interface ChatCompanionOptions {
|
|
28
|
+
/** This runner's id (claimant of turns). */
|
|
29
|
+
runnerId: string;
|
|
30
|
+
/** The chat-conformant engine driving replies. */
|
|
31
|
+
engine: ConformantAgentEngine;
|
|
32
|
+
/** The wire seam for claim/stream/complete/requeue. */
|
|
33
|
+
transport: ChatLaneTransport;
|
|
34
|
+
/** The chat-capability circuit breaker (shared with `doctor` / boot hydration). */
|
|
35
|
+
breaker: ChatCircuitBreaker;
|
|
36
|
+
/** Runner concurrency ceiling (shared with the batch lane). */
|
|
37
|
+
concurrencyLimit: number;
|
|
38
|
+
/** Chat-lane max share of that ceiling (0..1); ≥1 still reserves 1 code slot. */
|
|
39
|
+
maxShare: number;
|
|
40
|
+
/** Requested lease per claim (server clamps). */
|
|
41
|
+
leaseMs: number;
|
|
42
|
+
/** Conversations this companion currently serves (registration list). */
|
|
43
|
+
conversations: () => string[];
|
|
44
|
+
/** Live count of code/review tasks in flight on this runner (from watch.ts pump). */
|
|
45
|
+
activeCodeTasks: () => number;
|
|
46
|
+
/** Working directory forwarded to the engine per turn. */
|
|
47
|
+
cwd?: string;
|
|
48
|
+
/** Mint a per-turn delegation token (narrowly-scoped identity) — optional. */
|
|
49
|
+
mintDelegation?: (turn: ClaimedChatTurn) => Promise<string | undefined>;
|
|
50
|
+
/** Clock (test seam). */
|
|
51
|
+
now?: () => number;
|
|
52
|
+
/** Best-effort observability sink for terminal turn results. */
|
|
53
|
+
onResult?: (result: ChatTurnDriveResult) => void;
|
|
54
|
+
/** Best-effort log sink. */
|
|
55
|
+
onLog?: (line: string) => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The companion claim loop. Call `pumpOnce()` to claim + drive as many turns as
|
|
59
|
+
* the max-share budget currently allows (returns the turns launched this pump);
|
|
60
|
+
* `start()` schedules `pumpOnce` on an interval; `cancel(turnId)` aborts an
|
|
61
|
+
* in-flight turn (clean requeue); `stop()` aborts all in-flight turns.
|
|
62
|
+
*/
|
|
63
|
+
export declare class ChatCompanion {
|
|
64
|
+
private readonly opts;
|
|
65
|
+
private readonly running;
|
|
66
|
+
private timer;
|
|
67
|
+
private stopped;
|
|
68
|
+
private rrCursor;
|
|
69
|
+
constructor(opts: ChatCompanionOptions);
|
|
70
|
+
/** Number of chat turns currently in flight on this companion. */
|
|
71
|
+
activeChatTurns(): number;
|
|
72
|
+
/** Turn ids currently in flight (stable snapshot). */
|
|
73
|
+
inFlightTurnIds(): string[];
|
|
74
|
+
/**
|
|
75
|
+
* The chat-lane claim budget right now: how many NEW turns may be claimed
|
|
76
|
+
* given concurrency, max-share, in-flight chat + code load, and the breaker.
|
|
77
|
+
*/
|
|
78
|
+
budget(): number;
|
|
79
|
+
/**
|
|
80
|
+
* Claim + launch up to `budget()` turns across the served conversations,
|
|
81
|
+
* round-robin so no single busy conversation monopolises the budget. Returns
|
|
82
|
+
* the turns launched this pump (empty when the budget is 0 or every
|
|
83
|
+
* conversation's queue is empty).
|
|
84
|
+
*/
|
|
85
|
+
pumpOnce(): Promise<ClaimedChatTurn[]>;
|
|
86
|
+
/** Start the periodic pump. `intervalMs` is the poll cadence between pumps. */
|
|
87
|
+
start(intervalMs?: number): void;
|
|
88
|
+
/**
|
|
89
|
+
* Abort one in-flight turn (operator cancel). The driver observes the signal,
|
|
90
|
+
* stops streaming, and requeues the turn — the "kill mid-stream ⇒ clean
|
|
91
|
+
* requeue, no dup side effects" path. Returns the settled result, or null when
|
|
92
|
+
* the turn is not in flight here.
|
|
93
|
+
*/
|
|
94
|
+
cancel(turnId: string): Promise<ChatTurnDriveResult | null>;
|
|
95
|
+
/** Stop the loop: cancel the timer and abort every in-flight turn (requeue). */
|
|
96
|
+
stop(): Promise<ChatTurnDriveResult[]>;
|
|
97
|
+
private claim;
|
|
98
|
+
private launch;
|
|
99
|
+
private drive;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=chat-companion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-companion.d.ts","sourceRoot":"","sources":["../src/chat-companion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,MAAM,EAAE,qBAAqB,CAAC;IAC9B,uDAAuD;IACvD,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mFAAmF;IACnF,OAAO,EAAE,kBAAkB,CAAC;IAC5B,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,aAAa,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B,qFAAqF;IACrF,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACxE,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACjD,4BAA4B;IAC5B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAQD;;;;;GAKG;AACH,qBAAa,aAAa;IAMZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAC1D,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;gBAEQ,IAAI,EAAE,oBAAoB;IAEvD,kEAAkE;IAClE,eAAe,IAAI,MAAM;IAIzB,sDAAsD;IACtD,eAAe,IAAI,MAAM,EAAE;IAI3B;;;OAGG;IACH,MAAM,IAAI,MAAM;IAUhB;;;;;OAKG;IACG,QAAQ,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B5C,+EAA+E;IAC/E,KAAK,CAAC,UAAU,SAAQ,GAAG,IAAI;IAS/B;;;;;OAKG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAOjE,gFAAgF;IAC1E,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;YAW9B,KAAK;IAiBnB,OAAO,CAAC,MAAM;YAWA,KAAK;CAwBpB"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC-1B (TURN-LANE): the companion claim loop.
|
|
3
|
+
*
|
|
4
|
+
* A COMPANION is a runner attached to one or more operator chats that serves
|
|
5
|
+
* their queued turns, distinct from a fleet runner draining the task lane
|
|
6
|
+
* (WORKSPACE_COMMAND_CHAT_CONTRACTS §2). This module is the chat-lane analogue of
|
|
7
|
+
* the batch `pump()` in watch.ts: it repeatedly claims queued turns across its
|
|
8
|
+
* conversations and drives each with `driveChatTurn`, subject to two guards that
|
|
9
|
+
* keep it from ever starving the code/review lanes:
|
|
10
|
+
*
|
|
11
|
+
* 1. MAX-SHARE — `computeChatClaimBudget` caps how many concurrency slots chat
|
|
12
|
+
* may hold (always leaving ≥1 for code/review), reading the batch lane's
|
|
13
|
+
* live in-flight count via the injected `activeCodeTasks()`.
|
|
14
|
+
* 2. CIRCUIT BREAKER — while the chat circuit is open (a `claude_exit_1`-class
|
|
15
|
+
* fault tripped it), the budget is 0 so no chat turn is claimed; the code
|
|
16
|
+
* lane is untouched.
|
|
17
|
+
*
|
|
18
|
+
* The loop owns per-turn `AbortController`s so an operator cancel (or runner
|
|
19
|
+
* shutdown) SIGTERMs the in-flight engine child and the driver requeues the turn
|
|
20
|
+
* cleanly — no duplicate completion. Everything (transport, engine, clock,
|
|
21
|
+
* active-task count, delegation minting) is injected, so the loop is exercised
|
|
22
|
+
* deterministically without a network or a `claude` binary.
|
|
23
|
+
*/
|
|
24
|
+
import { computeChatClaimBudget, driveChatTurn, } from "./chat-lane.js";
|
|
25
|
+
/**
|
|
26
|
+
* The companion claim loop. Call `pumpOnce()` to claim + drive as many turns as
|
|
27
|
+
* the max-share budget currently allows (returns the turns launched this pump);
|
|
28
|
+
* `start()` schedules `pumpOnce` on an interval; `cancel(turnId)` aborts an
|
|
29
|
+
* in-flight turn (clean requeue); `stop()` aborts all in-flight turns.
|
|
30
|
+
*/
|
|
31
|
+
export class ChatCompanion {
|
|
32
|
+
opts;
|
|
33
|
+
running = new Map();
|
|
34
|
+
timer = null;
|
|
35
|
+
stopped = false;
|
|
36
|
+
rrCursor = 0;
|
|
37
|
+
constructor(opts) {
|
|
38
|
+
this.opts = opts;
|
|
39
|
+
}
|
|
40
|
+
/** Number of chat turns currently in flight on this companion. */
|
|
41
|
+
activeChatTurns() {
|
|
42
|
+
return this.running.size;
|
|
43
|
+
}
|
|
44
|
+
/** Turn ids currently in flight (stable snapshot). */
|
|
45
|
+
inFlightTurnIds() {
|
|
46
|
+
return [...this.running.keys()];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The chat-lane claim budget right now: how many NEW turns may be claimed
|
|
50
|
+
* given concurrency, max-share, in-flight chat + code load, and the breaker.
|
|
51
|
+
*/
|
|
52
|
+
budget() {
|
|
53
|
+
return computeChatClaimBudget({
|
|
54
|
+
concurrencyLimit: this.opts.concurrencyLimit,
|
|
55
|
+
maxShare: this.opts.maxShare,
|
|
56
|
+
activeChatTurns: this.running.size,
|
|
57
|
+
activeCodeTasks: this.opts.activeCodeTasks(),
|
|
58
|
+
circuitOpen: this.opts.breaker.isOpen(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Claim + launch up to `budget()` turns across the served conversations,
|
|
63
|
+
* round-robin so no single busy conversation monopolises the budget. Returns
|
|
64
|
+
* the turns launched this pump (empty when the budget is 0 or every
|
|
65
|
+
* conversation's queue is empty).
|
|
66
|
+
*/
|
|
67
|
+
async pumpOnce() {
|
|
68
|
+
if (this.stopped)
|
|
69
|
+
return [];
|
|
70
|
+
const launched = [];
|
|
71
|
+
let budget = this.budget();
|
|
72
|
+
if (budget <= 0)
|
|
73
|
+
return launched;
|
|
74
|
+
const convos = this.opts.conversations();
|
|
75
|
+
if (convos.length === 0)
|
|
76
|
+
return launched;
|
|
77
|
+
// Round-robin start point rotates each pump so a claim-empty conversation
|
|
78
|
+
// does not always shadow the ones after it.
|
|
79
|
+
let attemptsWithoutClaim = 0;
|
|
80
|
+
let idx = this.rrCursor % convos.length;
|
|
81
|
+
while (budget > 0 && attemptsWithoutClaim < convos.length) {
|
|
82
|
+
const conversationId = convos[idx % convos.length];
|
|
83
|
+
idx += 1;
|
|
84
|
+
const turn = await this.claim(conversationId);
|
|
85
|
+
if (!turn) {
|
|
86
|
+
attemptsWithoutClaim += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
attemptsWithoutClaim = 0;
|
|
90
|
+
budget -= 1;
|
|
91
|
+
launched.push(turn);
|
|
92
|
+
this.launch(turn);
|
|
93
|
+
}
|
|
94
|
+
this.rrCursor = idx % Math.max(1, convos.length);
|
|
95
|
+
return launched;
|
|
96
|
+
}
|
|
97
|
+
/** Start the periodic pump. `intervalMs` is the poll cadence between pumps. */
|
|
98
|
+
start(intervalMs = 1_000) {
|
|
99
|
+
if (this.timer)
|
|
100
|
+
return;
|
|
101
|
+
this.timer = setInterval(() => {
|
|
102
|
+
void this.pumpOnce();
|
|
103
|
+
}, intervalMs);
|
|
104
|
+
this.timer.unref?.();
|
|
105
|
+
void this.pumpOnce();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Abort one in-flight turn (operator cancel). The driver observes the signal,
|
|
109
|
+
* stops streaming, and requeues the turn — the "kill mid-stream ⇒ clean
|
|
110
|
+
* requeue, no dup side effects" path. Returns the settled result, or null when
|
|
111
|
+
* the turn is not in flight here.
|
|
112
|
+
*/
|
|
113
|
+
async cancel(turnId) {
|
|
114
|
+
const rt = this.running.get(turnId);
|
|
115
|
+
if (!rt)
|
|
116
|
+
return null;
|
|
117
|
+
rt.controller.abort();
|
|
118
|
+
return rt.done;
|
|
119
|
+
}
|
|
120
|
+
/** Stop the loop: cancel the timer and abort every in-flight turn (requeue). */
|
|
121
|
+
async stop() {
|
|
122
|
+
this.stopped = true;
|
|
123
|
+
if (this.timer) {
|
|
124
|
+
clearInterval(this.timer);
|
|
125
|
+
this.timer = null;
|
|
126
|
+
}
|
|
127
|
+
const pending = [...this.running.values()];
|
|
128
|
+
for (const rt of pending)
|
|
129
|
+
rt.controller.abort();
|
|
130
|
+
return Promise.all(pending.map((rt) => rt.done));
|
|
131
|
+
}
|
|
132
|
+
async claim(conversationId) {
|
|
133
|
+
try {
|
|
134
|
+
return await this.opts.transport.claim({
|
|
135
|
+
conversationId,
|
|
136
|
+
runnerId: this.opts.runnerId,
|
|
137
|
+
engineId: this.opts.engine.id,
|
|
138
|
+
tier: this.opts.engine.capabilities.tier,
|
|
139
|
+
leaseMs: this.opts.leaseMs,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
this.opts.onLog?.(`[acc-runner] chat claim(${conversationId}) failed: ${err.message}`);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
launch(turn) {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const done = this.drive(turn, controller.signal).finally(() => {
|
|
150
|
+
this.running.delete(turn.id);
|
|
151
|
+
// Freed a slot — re-pump so a queued turn (or a requeued one) is picked up
|
|
152
|
+
// immediately rather than waiting for the next interval tick.
|
|
153
|
+
if (!this.stopped)
|
|
154
|
+
void this.pumpOnce();
|
|
155
|
+
});
|
|
156
|
+
this.running.set(turn.id, { turn, controller, done });
|
|
157
|
+
}
|
|
158
|
+
async drive(turn, signal) {
|
|
159
|
+
const delegationToken = this.opts.mintDelegation
|
|
160
|
+
? await this.opts.mintDelegation(turn).catch(() => undefined)
|
|
161
|
+
: undefined;
|
|
162
|
+
const result = await driveChatTurn(turn, {
|
|
163
|
+
engine: this.opts.engine,
|
|
164
|
+
transport: this.opts.transport,
|
|
165
|
+
signal,
|
|
166
|
+
...(this.opts.cwd !== undefined ? { cwd: this.opts.cwd } : {}),
|
|
167
|
+
...(delegationToken !== undefined ? { delegationToken } : {}),
|
|
168
|
+
...(this.opts.now ? { now: this.opts.now } : {}),
|
|
169
|
+
onCircuitTrip: async (info) => {
|
|
170
|
+
await this.opts.breaker.trip({
|
|
171
|
+
cause: info.cause,
|
|
172
|
+
turnId: turn.id,
|
|
173
|
+
conversationId: turn.conversation_id,
|
|
174
|
+
detail: info.detail,
|
|
175
|
+
...(this.opts.now ? { now: this.opts.now } : {}),
|
|
176
|
+
});
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
this.opts.onResult?.(result);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=chat-companion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-companion.js","sourceRoot":"","sources":["../src/chat-companion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC-1B (TURN-LANE): the runner-side chat claim lane + turn lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* The chat lane is a SECOND lane beside the batch task/review lanes
|
|
5
|
+
* (docs/acc/WORKSPACE_COMMAND_CHAT_CONTRACTS.md §1). A *turn* is one operator
|
|
6
|
+
* prompt → one streamed agent reply. Turns are queued server-side; a companion
|
|
7
|
+
* runner CLAIMS a queued turn under a time-boxed lease (acc.claim_chat_turn,
|
|
8
|
+
* advisory-lock per conversation — migration 0254), STREAMS the reply as deltas,
|
|
9
|
+
* and COMPLETES it — or REQUEUES it when it loses the lease/capacity, so another
|
|
10
|
+
* companion finishes the turn without burning an operator turn.
|
|
11
|
+
*
|
|
12
|
+
* This module owns:
|
|
13
|
+
* 1. The turn LIFECYCLE state machine (queued → claimed → streaming →
|
|
14
|
+
* awaiting_confirmation → done | capacity_paused | failed | cancelled),
|
|
15
|
+
* with a transition validator.
|
|
16
|
+
* 2. The failure → disposition mapping: infra failures REQUEUE (never burn a
|
|
17
|
+
* turn); `claude_exit_1`-class failures open the chat circuit breaker AND
|
|
18
|
+
* requeue; a genuine agent error COMPLETES the turn as `error`.
|
|
19
|
+
* 3. `driveChatTurn` — the per-turn driver, an engine-agnostic loop over a
|
|
20
|
+
* `ConformantAgentEngine.streamTurn` that emits exactly ONE terminal
|
|
21
|
+
* transport call (complete OR requeue) so an abort mid-stream produces a
|
|
22
|
+
* clean requeue with no duplicate side effects.
|
|
23
|
+
* 4. `computeChatClaimBudget` — the chat-lane max-share knob so chat turns can
|
|
24
|
+
* never starve the code/review lanes of concurrency slots.
|
|
25
|
+
*
|
|
26
|
+
* Every side effect is behind the injectable `ChatLaneTransport` + the engine +
|
|
27
|
+
* an optional abort signal, so the whole lifecycle is exercised deterministically
|
|
28
|
+
* (tests/unit/chat-lane*.test.ts) with no `claude` binary and no network.
|
|
29
|
+
*/
|
|
30
|
+
import { type ClaudeExitClass, type ClassifiedExit } from "./failure-classifier.js";
|
|
31
|
+
import type { ConformantAgentEngine, EngineCapabilityTier } from "./engines/types.js";
|
|
32
|
+
/** Chat-lane protocol version (mirrors the web-app constant of the same name). */
|
|
33
|
+
export declare const CHAT_LANE_PROTOCOL_VERSION: 1;
|
|
34
|
+
/** Terminal disposition of a completed turn (mirror of ChatTurnOutcome). */
|
|
35
|
+
export type ChatTurnOutcome = "ok" | "error" | "cancelled";
|
|
36
|
+
/** Why a turn was returned to the queue instead of completed. */
|
|
37
|
+
export type ChatTurnRequeueReason = "lease_expired" | "capacity_lost" | "engine_unavailable" | "credential_lost" | "operator_cancelled" | "runner_shutdown";
|
|
38
|
+
/** runner → server: one streamed delta of the assistant turn's output. */
|
|
39
|
+
export interface ChatTurnStreamPayload {
|
|
40
|
+
seq: number;
|
|
41
|
+
delta: string;
|
|
42
|
+
done: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** runner → server: the turn reached a terminal state. */
|
|
45
|
+
export interface ChatTurnCompletePayload {
|
|
46
|
+
outcome: ChatTurnOutcome;
|
|
47
|
+
total_seq: number;
|
|
48
|
+
error?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Additive to the wire complete payload: true when the reply proposes an
|
|
51
|
+
* action the operator must confirm before it applies. The turn still
|
|
52
|
+
* completes `ok` (the reply streamed fine); the pending confirmation lives in
|
|
53
|
+
* acc.proposed_actions. A pre-CC-1B consumer ignores this field.
|
|
54
|
+
*/
|
|
55
|
+
awaiting_confirmation?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** server ↔ runner: return the turn to the queue for another claimant. */
|
|
58
|
+
export interface ChatTurnRequeuePayload {
|
|
59
|
+
reason: ChatTurnRequeueReason;
|
|
60
|
+
requeue_count: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A turn as claimed from acc.claim_chat_turn (the columns the driver needs).
|
|
64
|
+
* Mirrors the acc.chat_turns row the RPC returns.
|
|
65
|
+
*/
|
|
66
|
+
export interface ClaimedChatTurn {
|
|
67
|
+
id: string;
|
|
68
|
+
conversation_id: string;
|
|
69
|
+
org_id: string;
|
|
70
|
+
turn_ordinal: number | null;
|
|
71
|
+
prompt: string;
|
|
72
|
+
tier: EngineCapabilityTier;
|
|
73
|
+
engine_id: string;
|
|
74
|
+
runner_id: string;
|
|
75
|
+
requeue_count: number;
|
|
76
|
+
lease_expires_at: string | null;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The runner-side turn lifecycle. A superset of the acc.chat_turns DB status
|
|
80
|
+
* enum: `awaiting_confirmation` and `capacity_paused` are RUNNER phases (the DB
|
|
81
|
+
* has no such status — a confirmation-pending turn is still `done` at the DB
|
|
82
|
+
* with a proposed_actions row; a capacity-paused turn is `requeued`). Modeling
|
|
83
|
+
* them here lets the driver + companion report the full operator-visible
|
|
84
|
+
* lifecycle without a migration.
|
|
85
|
+
*/
|
|
86
|
+
export type ChatTurnLifecycle = "queued" | "claimed" | "streaming" | "awaiting_confirmation" | "done" | "capacity_paused" | "failed" | "cancelled" | "requeued";
|
|
87
|
+
/** Terminal lifecycle states — no further transition is valid from these. */
|
|
88
|
+
export declare const TERMINAL_CHAT_LIFECYCLE: readonly ChatTurnLifecycle[];
|
|
89
|
+
/** True when `to` is a legal next lifecycle state from `from`. */
|
|
90
|
+
export declare function isValidChatTransition(from: ChatTurnLifecycle, to: ChatTurnLifecycle): boolean;
|
|
91
|
+
/** True when the lifecycle state is terminal (the turn is settled). */
|
|
92
|
+
export declare function isTerminalChatLifecycle(state: ChatTurnLifecycle): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* What the driver does with a failed turn:
|
|
95
|
+
* - `requeue` — an infra failure (recoverable capacity / transient
|
|
96
|
+
* engine unavailability). The turn goes back to the
|
|
97
|
+
* queue; NO operator turn is burned, the chat circuit
|
|
98
|
+
* stays closed.
|
|
99
|
+
* - `quarantine_requeue` — a `claude_exit_1`-class fault (broken credential /
|
|
100
|
+
* host / hard billing wall). OPEN the chat circuit
|
|
101
|
+
* breaker AND requeue the turn (survives on another
|
|
102
|
+
* companion) — but this runner stops claiming chat.
|
|
103
|
+
* - `error` — a genuine agent/task error. COMPLETE the turn as
|
|
104
|
+
* `error`; the operator turn IS spent (a real reply
|
|
105
|
+
* failure, not infra), so it must not silently loop.
|
|
106
|
+
*/
|
|
107
|
+
export type ChatFailureDisposition = {
|
|
108
|
+
action: "requeue";
|
|
109
|
+
reason: ChatTurnRequeueReason;
|
|
110
|
+
quarantine: false;
|
|
111
|
+
detail: string;
|
|
112
|
+
} | {
|
|
113
|
+
action: "quarantine_requeue";
|
|
114
|
+
reason: ChatTurnRequeueReason;
|
|
115
|
+
cause: ClaudeExitClass;
|
|
116
|
+
detail: string;
|
|
117
|
+
} | {
|
|
118
|
+
action: "error";
|
|
119
|
+
detail: string;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Map a shared-taxonomy `ClassifiedExit` onto a chat-lane disposition. The split
|
|
123
|
+
* mirrors the batch lane's machine-vs-task rule but reroutes the terminal action
|
|
124
|
+
* to the chat lane's requeue/circuit model:
|
|
125
|
+
*
|
|
126
|
+
* capacity_exhausted | subscription_exhausted → requeue (capacity_lost) [infra]
|
|
127
|
+
* claude_unavailable (transient) → requeue (engine_unavailable) [infra]
|
|
128
|
+
* auth_expired → quarantine_requeue (credential_lost)
|
|
129
|
+
* env_broken → quarantine_requeue (engine_unavailable)
|
|
130
|
+
* usage_limit (hard wall) → quarantine_requeue (capacity_lost)
|
|
131
|
+
* task_error → error (turn completes 'error')
|
|
132
|
+
*/
|
|
133
|
+
export declare function classifyChatDisposition(classified: ClassifiedExit): ChatFailureDisposition;
|
|
134
|
+
/**
|
|
135
|
+
* The chat-lane wire seam. Claim goes through the service-role acc.claim_chat_turn
|
|
136
|
+
* RPC (advisory-lock per conversation); stream/complete/requeue carry the
|
|
137
|
+
* lifecycle messages. Behind this interface a production adapter broadcasts on
|
|
138
|
+
* `chat:<conversation_id>` and/or posts to the control-plane API; the driver
|
|
139
|
+
* neither knows nor cares, so tests inject a recording fake.
|
|
140
|
+
*/
|
|
141
|
+
export interface ChatLaneTransport {
|
|
142
|
+
/** Claim the oldest queued turn on a conversation, or null when the queue is empty. */
|
|
143
|
+
claim(args: {
|
|
144
|
+
conversationId: string;
|
|
145
|
+
runnerId: string;
|
|
146
|
+
engineId: string;
|
|
147
|
+
tier: EngineCapabilityTier;
|
|
148
|
+
leaseMs: number;
|
|
149
|
+
}): Promise<ClaimedChatTurn | null>;
|
|
150
|
+
/** Mark a claimed turn as streaming (claimed → streaming). */
|
|
151
|
+
markStreaming?(turn: ClaimedChatTurn): Promise<void>;
|
|
152
|
+
/** Emit one streamed delta for a turn. */
|
|
153
|
+
stream(turn: ClaimedChatTurn, payload: ChatTurnStreamPayload): Promise<void>;
|
|
154
|
+
/** Terminally complete a turn (ok | error | cancelled). */
|
|
155
|
+
complete(turn: ClaimedChatTurn, payload: ChatTurnCompletePayload): Promise<void>;
|
|
156
|
+
/** Return a turn to the queue for another claimant. */
|
|
157
|
+
requeue(turn: ClaimedChatTurn, payload: ChatTurnRequeuePayload): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
export interface DriveChatTurnDeps {
|
|
160
|
+
/** The chat-conformant engine that streams the reply. */
|
|
161
|
+
engine: ConformantAgentEngine;
|
|
162
|
+
/** The wire seam for stream/complete/requeue. */
|
|
163
|
+
transport: ChatLaneTransport;
|
|
164
|
+
/** Working directory forwarded to the engine (repo checkout for the turn). */
|
|
165
|
+
cwd?: string;
|
|
166
|
+
/** Per-turn delegation Bearer token forwarded to the acc-mcp bus. */
|
|
167
|
+
delegationToken?: string;
|
|
168
|
+
/** Abort signal; aborting mid-stream produces a clean requeue (no dup complete). */
|
|
169
|
+
signal?: AbortSignal;
|
|
170
|
+
/** Requeue reason to use when the turn is aborted (default operator_cancelled). */
|
|
171
|
+
abortReason?: ChatTurnRequeueReason;
|
|
172
|
+
/**
|
|
173
|
+
* Decide whether the fully-streamed reply proposes an action needing operator
|
|
174
|
+
* confirmation (→ awaiting_confirmation). Default: never (no confirmation).
|
|
175
|
+
*/
|
|
176
|
+
requiresConfirmation?: (fullText: string, turn: ClaimedChatTurn) => boolean;
|
|
177
|
+
/**
|
|
178
|
+
* Detect a SILENT engine failure: a clean stream that produced zero visible
|
|
179
|
+
* text. Mirrors the batch lane's `claude_unavailable → health probe` rule —
|
|
180
|
+
* if the probe says the host/binary is broken, the empty turn is really an
|
|
181
|
+
* env_broken fault (→ quarantine_requeue) rather than a genuine empty reply.
|
|
182
|
+
* Default: treat an empty reply as a real (`ok`) empty reply.
|
|
183
|
+
*/
|
|
184
|
+
silentFailureProbe?: () => Promise<ClassifiedExit | null>;
|
|
185
|
+
/** Clock (test seam). */
|
|
186
|
+
now?: () => number;
|
|
187
|
+
/** Called when the driver opens the chat circuit (companion trips its breaker). */
|
|
188
|
+
onCircuitTrip?: (info: {
|
|
189
|
+
cause: ClaudeExitClass;
|
|
190
|
+
detail: string;
|
|
191
|
+
}) => Promise<void> | void;
|
|
192
|
+
/** Best-effort structured log sink. */
|
|
193
|
+
onEvent?: (event: ChatTurnDriveEvent) => void;
|
|
194
|
+
}
|
|
195
|
+
/** A structured lifecycle event emitted by the driver (observability seam). */
|
|
196
|
+
export interface ChatTurnDriveEvent {
|
|
197
|
+
turnId: string;
|
|
198
|
+
conversationId: string;
|
|
199
|
+
lifecycle: ChatTurnLifecycle;
|
|
200
|
+
detail?: string;
|
|
201
|
+
}
|
|
202
|
+
/** The terminal result of driving one turn. */
|
|
203
|
+
export interface ChatTurnDriveResult {
|
|
204
|
+
turnId: string;
|
|
205
|
+
conversationId: string;
|
|
206
|
+
/** The terminal lifecycle state the turn settled in. */
|
|
207
|
+
lifecycle: Extract<ChatTurnLifecycle, "done" | "awaiting_confirmation" | "failed" | "cancelled" | "requeued" | "capacity_paused">;
|
|
208
|
+
/** Present when the turn was completed (done/awaiting_confirmation/failed). */
|
|
209
|
+
outcome?: ChatTurnOutcome;
|
|
210
|
+
/** Present when the turn was requeued. */
|
|
211
|
+
requeueReason?: ChatTurnRequeueReason;
|
|
212
|
+
/** Total deltas emitted (for consumer gap detection). */
|
|
213
|
+
totalSeq: number;
|
|
214
|
+
/** True when driving this turn opened the chat circuit breaker. */
|
|
215
|
+
quarantined: boolean;
|
|
216
|
+
/** Human-readable detail (error/requeue reason). */
|
|
217
|
+
detail?: string;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Drive one claimed turn to a terminal state. Emits EXACTLY ONE terminal
|
|
221
|
+
* transport call (complete OR requeue), guarded by `settled`, so a mid-stream
|
|
222
|
+
* abort/failure can never both requeue AND complete the same turn — the
|
|
223
|
+
* "kill mid-stream ⇒ clean requeue, no dup side effects" invariant.
|
|
224
|
+
*/
|
|
225
|
+
export declare function driveChatTurn(turn: ClaimedChatTurn, deps: DriveChatTurnDeps): Promise<ChatTurnDriveResult>;
|
|
226
|
+
/** Env var carrying the chat-lane max-share fraction (0..1). */
|
|
227
|
+
export declare const CHAT_MAX_SHARE_ENV = "ACC_RUNNER_CHAT_MAX_SHARE";
|
|
228
|
+
/** Default chat-lane max share of runner concurrency. */
|
|
229
|
+
export declare const DEFAULT_CHAT_MAX_SHARE = 0.5;
|
|
230
|
+
/**
|
|
231
|
+
* Resolve the chat-lane max-share fraction from a raw env value. Blank/absent ⇒
|
|
232
|
+
* default; a value ≤0 disables chat claiming; a value ≥1 is clamped so at least
|
|
233
|
+
* one slot always remains for the code/review lanes (chat can never take the
|
|
234
|
+
* whole runner). Non-numeric ⇒ default (a typo must not starve either lane).
|
|
235
|
+
*/
|
|
236
|
+
export declare function resolveChatMaxShare(raw: string | undefined | null): number;
|
|
237
|
+
/**
|
|
238
|
+
* Compute how many NEW chat turns the runner may claim right now, given its
|
|
239
|
+
* concurrency ceiling and current mixed load. The max-share invariant guarantees
|
|
240
|
+
* the code/review lanes never starve:
|
|
241
|
+
*
|
|
242
|
+
* chatCeiling = clamp(floor(concurrency * maxShare), 0, concurrency - 1)
|
|
243
|
+
*
|
|
244
|
+
* i.e. chat may occupy at most `chatCeiling` slots, and that ceiling is capped at
|
|
245
|
+
* `concurrency - 1` so at least one slot is ALWAYS reservable for a code/review
|
|
246
|
+
* task even at maxShare = 1. The live budget then subtracts the chat turns
|
|
247
|
+
* already in flight and is further bounded by the runner's actual free capacity
|
|
248
|
+
* (concurrency − everything running). Returns 0 while the chat circuit is open or
|
|
249
|
+
* chat is disabled (maxShare ≤ 0).
|
|
250
|
+
*/
|
|
251
|
+
export declare function computeChatClaimBudget(opts: {
|
|
252
|
+
concurrencyLimit: number;
|
|
253
|
+
maxShare: number;
|
|
254
|
+
activeChatTurns: number;
|
|
255
|
+
activeCodeTasks: number;
|
|
256
|
+
circuitOpen?: boolean;
|
|
257
|
+
}): number;
|
|
258
|
+
/** Env var that opts the runner into the chat lane (a companion). */
|
|
259
|
+
export declare const CHAT_LANE_ENABLED_ENV = "ACC_RUNNER_CHAT_LANE";
|
|
260
|
+
/** Env var carrying the requested per-turn lease (ms). */
|
|
261
|
+
export declare const CHAT_LEASE_MS_ENV = "ACC_RUNNER_CHAT_LEASE_MS";
|
|
262
|
+
/** Default per-turn lease; mirrors acc.claim_chat_turn's default (60s). */
|
|
263
|
+
export declare const DEFAULT_CHAT_LEASE_MS = 60000;
|
|
264
|
+
/**
|
|
265
|
+
* Resolve the requested per-turn lease. Blank/absent/non-numeric ⇒ default;
|
|
266
|
+
* numeric values clamp to the RPC's own accepted window (1s..10m) so the runner
|
|
267
|
+
* never asks for a lease the server would silently clamp anyway.
|
|
268
|
+
*/
|
|
269
|
+
export declare function resolveChatLeaseMs(raw: string | undefined | null): number;
|
|
270
|
+
/** The resolved chat-lane knobs (companion enablement + share + lease). */
|
|
271
|
+
export interface ChatLaneRunnerConfig {
|
|
272
|
+
/** Whether this runner serves the chat lane at all. */
|
|
273
|
+
enabled: boolean;
|
|
274
|
+
/** Chat-lane max share of runner concurrency (0..1). */
|
|
275
|
+
maxShare: number;
|
|
276
|
+
/** Requested per-turn lease (ms). */
|
|
277
|
+
leaseMs: number;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Resolve the chat-lane knobs from the environment in ONE place so the companion
|
|
281
|
+
* loop and any caps/telemetry read the same values. A share of 0 (or the flag
|
|
282
|
+
* absent) means the runner does not claim chat turns even when enabled — the
|
|
283
|
+
* code/review lanes keep the whole runner.
|
|
284
|
+
*/
|
|
285
|
+
export declare function resolveChatLaneConfig(env?: NodeJS.ProcessEnv): ChatLaneRunnerConfig;
|
|
286
|
+
//# sourceMappingURL=chat-lane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-lane.d.ts","sourceRoot":"","sources":["../src/chat-lane.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAsB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAExG,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,oBAAoB,CAAC;AAW5B,kFAAkF;AAClF,eAAO,MAAM,0BAA0B,EAAG,CAAU,CAAC;AAErD,4EAA4E;AAC5E,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,OAAO,GAAG,WAAW,CAAC;AAE3D,iEAAiE;AACjE,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,eAAe,GACf,oBAAoB,GACpB,iBAAiB,GACjB,oBAAoB,GACpB,iBAAiB,CAAC;AAEtB,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;CACf;AAED,0DAA0D;AAC1D,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,oBAAoB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAMD;;;;;;;GAOG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,uBAAuB,GACvB,MAAM,GACN,iBAAiB,GACjB,QAAQ,GACR,WAAW,GACX,UAAU,CAAC;AAEf,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,EAAE,SAAS,iBAAiB,EAM/D,CAAC;AAqBF,kEAAkE;AAClE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAE7F;AAED,uEAAuE;AACvE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAEzE;AAMD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,UAAU,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvF;IAAE,MAAM,EAAE,oBAAoB,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAC;IAAC,KAAK,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,cAAc,GAAG,sBAAsB,CAoB1F;AAMD;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,uFAAuF;IACvF,KAAK,CAAC,IAAI,EAAE;QACV,cAAc,EAAE,MAAM,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,oBAAoB,CAAC;QAC3B,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEpC,8DAA8D;IAC9D,aAAa,CAAC,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD,0CAA0C;IAC1C,MAAM,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7E,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF,uDAAuD;IACvD,OAAO,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF;AAMD,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,MAAM,EAAE,qBAAqB,CAAC;IAC9B,iDAAiD;IACjD,SAAS,EAAE,iBAAiB,CAAC;IAC7B,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mFAAmF;IACnF,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,KAAK,OAAO,CAAC;IAC5E;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC1D,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,mFAAmF;IACnF,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,eAAe,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3F,uCAAuC;IACvC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CAC/C;AAED,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,SAAS,EAAE,OAAO,CAChB,iBAAiB,EACjB,MAAM,GAAG,uBAAuB,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,iBAAiB,CAC3F,CAAC;IACF,+EAA+E;IAC/E,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,0CAA0C;IAC1C,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,oDAAoD;IACpD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwBD;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAqH9B;AAMD,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,8BAA8B,CAAC;AAC9D,yDAAyD;AACzD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAQ1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,MAAM,CAeT;AAMD,qEAAqE;AACrE,eAAO,MAAM,qBAAqB,yBAAyB,CAAC;AAC5D,0DAA0D;AAC1D,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAC5D,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAQ5C;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAIzE;AAED,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,oBAAoB,CAOhG"}
|