agy-worker-mcp 0.3.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/CHANGELOG.md +141 -0
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/commands/agy-ceiling.md +31 -0
- package/dist/broker/blockers.d.ts +86 -0
- package/dist/broker/blockers.d.ts.map +1 -0
- package/dist/broker/blockers.js +390 -0
- package/dist/broker/blockers.js.map +1 -0
- package/dist/broker/outcome.d.ts +85 -0
- package/dist/broker/outcome.d.ts.map +1 -0
- package/dist/broker/outcome.js +147 -0
- package/dist/broker/outcome.js.map +1 -0
- package/dist/broker/reconcile.d.ts +74 -0
- package/dist/broker/reconcile.d.ts.map +1 -0
- package/dist/broker/reconcile.js +415 -0
- package/dist/broker/reconcile.js.map +1 -0
- package/dist/broker/result.d.ts +88 -0
- package/dist/broker/result.d.ts.map +1 -0
- package/dist/broker/result.js +234 -0
- package/dist/broker/result.js.map +1 -0
- package/dist/broker/verify.d.ts +52 -0
- package/dist/broker/verify.d.ts.map +1 -0
- package/dist/broker/verify.js +174 -0
- package/dist/broker/verify.js.map +1 -0
- package/dist/contract/errors.d.ts +119 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/errors.js +99 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/index.d.ts +10 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +10 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/paths.d.ts +139 -0
- package/dist/contract/paths.d.ts.map +1 -0
- package/dist/contract/paths.js +359 -0
- package/dist/contract/paths.js.map +1 -0
- package/dist/contract/schema.sql +64 -0
- package/dist/contract/types.d.ts +1113 -0
- package/dist/contract/types.d.ts.map +1 -0
- package/dist/contract/types.js +114 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/events/cursor.d.ts +65 -0
- package/dist/events/cursor.d.ts.map +1 -0
- package/dist/events/cursor.js +286 -0
- package/dist/events/cursor.js.map +1 -0
- package/dist/events/detect.d.ts +58 -0
- package/dist/events/detect.d.ts.map +1 -0
- package/dist/events/detect.js +183 -0
- package/dist/events/detect.js.map +1 -0
- package/dist/events/normalize.d.ts +30 -0
- package/dist/events/normalize.d.ts.map +1 -0
- package/dist/events/normalize.js +158 -0
- package/dist/events/normalize.js.map +1 -0
- package/dist/events/parse.d.ts +41 -0
- package/dist/events/parse.d.ts.map +1 -0
- package/dist/events/parse.js +89 -0
- package/dist/events/parse.js.map +1 -0
- package/dist/gate/bind.d.ts +28 -0
- package/dist/gate/bind.d.ts.map +1 -0
- package/dist/gate/bind.js +154 -0
- package/dist/gate/bind.js.map +1 -0
- package/dist/gate/gate.d.ts +139 -0
- package/dist/gate/gate.d.ts.map +1 -0
- package/dist/gate/gate.js +488 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/hooks-file.d.ts +55 -0
- package/dist/gate/hooks-file.d.ts.map +1 -0
- package/dist/gate/hooks-file.js +104 -0
- package/dist/gate/hooks-file.js.map +1 -0
- package/dist/gate.d.ts +3 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +20 -0
- package/dist/gate.js.map +1 -0
- package/dist/policy/ceiling-review.d.ts +22 -0
- package/dist/policy/ceiling-review.d.ts.map +1 -0
- package/dist/policy/ceiling-review.js +170 -0
- package/dist/policy/ceiling-review.js.map +1 -0
- package/dist/policy/ceiling.d.ts +128 -0
- package/dist/policy/ceiling.d.ts.map +1 -0
- package/dist/policy/ceiling.js +273 -0
- package/dist/policy/ceiling.js.map +1 -0
- package/dist/policy/containment.d.ts +100 -0
- package/dist/policy/containment.d.ts.map +1 -0
- package/dist/policy/containment.js +1071 -0
- package/dist/policy/containment.js.map +1 -0
- package/dist/policy/hard-deny.d.ts +28 -0
- package/dist/policy/hard-deny.d.ts.map +1 -0
- package/dist/policy/hard-deny.js +78 -0
- package/dist/policy/hard-deny.js.map +1 -0
- package/dist/policy/profiles.d.ts +82 -0
- package/dist/policy/profiles.d.ts.map +1 -0
- package/dist/policy/profiles.js +317 -0
- package/dist/policy/profiles.js.map +1 -0
- package/dist/policy/rules.d.ts +138 -0
- package/dist/policy/rules.d.ts.map +1 -0
- package/dist/policy/rules.js +1180 -0
- package/dist/policy/rules.js.map +1 -0
- package/dist/policy/seatbelt.d.ts +32 -0
- package/dist/policy/seatbelt.d.ts.map +1 -0
- package/dist/policy/seatbelt.js +59 -0
- package/dist/policy/seatbelt.js.map +1 -0
- package/dist/policy/tools.d.ts +36 -0
- package/dist/policy/tools.d.ts.map +1 -0
- package/dist/policy/tools.js +94 -0
- package/dist/policy/tools.js.map +1 -0
- package/dist/runner/gate-watchdog.d.ts +93 -0
- package/dist/runner/gate-watchdog.d.ts.map +1 -0
- package/dist/runner/gate-watchdog.js +153 -0
- package/dist/runner/gate-watchdog.js.map +1 -0
- package/dist/runner/idle.d.ts +51 -0
- package/dist/runner/idle.d.ts.map +1 -0
- package/dist/runner/idle.js +81 -0
- package/dist/runner/idle.js.map +1 -0
- package/dist/runner/inbox.d.ts +56 -0
- package/dist/runner/inbox.d.ts.map +1 -0
- package/dist/runner/inbox.js +134 -0
- package/dist/runner/inbox.js.map +1 -0
- package/dist/runner/reap.d.ts +65 -0
- package/dist/runner/reap.d.ts.map +1 -0
- package/dist/runner/reap.js +168 -0
- package/dist/runner/reap.js.map +1 -0
- package/dist/runner/runner.d.ts +51 -0
- package/dist/runner/runner.d.ts.map +1 -0
- package/dist/runner/runner.js +289 -0
- package/dist/runner/runner.js.map +1 -0
- package/dist/runner/spawn.d.ts +98 -0
- package/dist/runner/spawn.d.ts.map +1 -0
- package/dist/runner/spawn.js +230 -0
- package/dist/runner/spawn.js.map +1 -0
- package/dist/runner/verify.d.ts +24 -0
- package/dist/runner/verify.d.ts.map +1 -0
- package/dist/runner/verify.js +108 -0
- package/dist/runner/verify.js.map +1 -0
- package/dist/runner.d.ts +3 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +15 -0
- package/dist/runner.js.map +1 -0
- package/dist/server/context.d.ts +41 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +59 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/instructions.d.ts +13 -0
- package/dist/server/instructions.d.ts.map +1 -0
- package/dist/server/instructions.js +121 -0
- package/dist/server/instructions.js.map +1 -0
- package/dist/server/server.d.ts +20 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +55 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/tools/cancel.d.ts +24 -0
- package/dist/server/tools/cancel.d.ts.map +1 -0
- package/dist/server/tools/cancel.js +59 -0
- package/dist/server/tools/cancel.js.map +1 -0
- package/dist/server/tools/capabilities.d.ts +36 -0
- package/dist/server/tools/capabilities.d.ts.map +1 -0
- package/dist/server/tools/capabilities.js +116 -0
- package/dist/server/tools/capabilities.js.map +1 -0
- package/dist/server/tools/ceiling.d.ts +31 -0
- package/dist/server/tools/ceiling.d.ts.map +1 -0
- package/dist/server/tools/ceiling.js +121 -0
- package/dist/server/tools/ceiling.js.map +1 -0
- package/dist/server/tools/index.d.ts +38 -0
- package/dist/server/tools/index.d.ts.map +1 -0
- package/dist/server/tools/index.js +115 -0
- package/dist/server/tools/index.js.map +1 -0
- package/dist/server/tools/listJobs.d.ts +30 -0
- package/dist/server/tools/listJobs.d.ts.map +1 -0
- package/dist/server/tools/listJobs.js +37 -0
- package/dist/server/tools/listJobs.js.map +1 -0
- package/dist/server/tools/logs.d.ts +31 -0
- package/dist/server/tools/logs.d.ts.map +1 -0
- package/dist/server/tools/logs.js +96 -0
- package/dist/server/tools/logs.js.map +1 -0
- package/dist/server/tools/result.d.ts +32 -0
- package/dist/server/tools/result.d.ts.map +1 -0
- package/dist/server/tools/result.js +95 -0
- package/dist/server/tools/result.js.map +1 -0
- package/dist/server/tools/send.d.ts +37 -0
- package/dist/server/tools/send.d.ts.map +1 -0
- package/dist/server/tools/send.js +71 -0
- package/dist/server/tools/send.js.map +1 -0
- package/dist/server/tools/sessions.d.ts +28 -0
- package/dist/server/tools/sessions.d.ts.map +1 -0
- package/dist/server/tools/sessions.js +54 -0
- package/dist/server/tools/sessions.js.map +1 -0
- package/dist/server/tools/start.d.ts +107 -0
- package/dist/server/tools/start.d.ts.map +1 -0
- package/dist/server/tools/start.js +482 -0
- package/dist/server/tools/start.js.map +1 -0
- package/dist/server/tools/wait.d.ts +29 -0
- package/dist/server/tools/wait.d.ts.map +1 -0
- package/dist/server/tools/wait.js +120 -0
- package/dist/server/tools/wait.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +17 -0
- package/dist/server.js.map +1 -0
- package/dist/setup/install.d.ts +52 -0
- package/dist/setup/install.d.ts.map +1 -0
- package/dist/setup/install.js +108 -0
- package/dist/setup/install.js.map +1 -0
- package/dist/setup.d.ts +3 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +24 -0
- package/dist/setup.js.map +1 -0
- package/dist/store/db.d.ts +49 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +101 -0
- package/dist/store/db.js.map +1 -0
- package/dist/store/jobs.d.ts +45 -0
- package/dist/store/jobs.d.ts.map +1 -0
- package/dist/store/jobs.js +150 -0
- package/dist/store/jobs.js.map +1 -0
- package/dist/store/locks.d.ts +60 -0
- package/dist/store/locks.d.ts.map +1 -0
- package/dist/store/locks.js +172 -0
- package/dist/store/locks.js.map +1 -0
- package/dist/store/sessions.d.ts +37 -0
- package/dist/store/sessions.d.ts.map +1 -0
- package/dist/store/sessions.js +117 -0
- package/dist/store/sessions.js.map +1 -0
- package/dist/trace/digest.d.ts +77 -0
- package/dist/trace/digest.d.ts.map +1 -0
- package/dist/trace/digest.js +481 -0
- package/dist/trace/digest.js.map +1 -0
- package/package.json +68 -0
- package/skills/agy-ceiling/SKILL.md +72 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { LockRow, LockScope } from '../contract/types.js';
|
|
2
|
+
import type { Store } from './db.js';
|
|
3
|
+
/** Default per-project concurrency ceiling. */
|
|
4
|
+
export declare const DEFAULT_MAX_RUNNING = 3;
|
|
5
|
+
/** Everything one job needs, acquired together or not at all. */
|
|
6
|
+
export interface JobLockRequest {
|
|
7
|
+
jobId: string;
|
|
8
|
+
/** Canonical cwd. Only taken when `writeMode` is true — readers run in parallel. */
|
|
9
|
+
cwd: string;
|
|
10
|
+
writeMode: boolean;
|
|
11
|
+
/** Session id. Enforces one running turn per agy conversation. */
|
|
12
|
+
sessionId?: string | null;
|
|
13
|
+
/** Ceiling on `lifecycle IN ('starting','running')` jobs. */
|
|
14
|
+
maxRunning?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface AcquiredLocks {
|
|
17
|
+
jobId: string;
|
|
18
|
+
rows: LockRow[];
|
|
19
|
+
/** Stale rows reclaimed on the way in, for the audit trail. */
|
|
20
|
+
reclaimed: LockRow[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Is the job holding this lock still the process we recorded?
|
|
24
|
+
*
|
|
25
|
+
* True while the holder is still in its start grace window (pid not published
|
|
26
|
+
* yet, but young enough that it plausibly will be). False once the pid is
|
|
27
|
+
* gone, once the grace window has elapsed with still no pid, or when the pid
|
|
28
|
+
* is alive but `proc_start_time` differs, which means the number was reused by
|
|
29
|
+
* an unrelated process.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isHolderAlive(store: Store, holderJobId: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Acquire every lock the job needs inside one `BEGIN IMMEDIATE` transaction.
|
|
34
|
+
*
|
|
35
|
+
* The procedure is fixed and must not be split across
|
|
36
|
+
* transactions — a check in one and an insert in another is a TOCTOU race:
|
|
37
|
+
*
|
|
38
|
+
* 1. select the `(scope, key)` rows
|
|
39
|
+
* 2. for each existing row, look up the holder job and decide staleness with
|
|
40
|
+
* **both** pid liveness and `proc_start_time` equality. pid alone is wrong:
|
|
41
|
+
* a recycled pid reads as a live holder and the lock never frees.
|
|
42
|
+
* 3. count `starting|running` jobs against `maxRunning`
|
|
43
|
+
* 4. `INSERT INTO locks` — a primary-key collision *is* the lost race
|
|
44
|
+
* 5. `COMMIT`
|
|
45
|
+
*
|
|
46
|
+
* @throws {import('../contract/errors.js').LockConflictError} when a live holder
|
|
47
|
+
* or the ceiling blocks it. Losing must be an error, never quiet serialization,
|
|
48
|
+
* and the error carries the holder so the caller can wait on
|
|
49
|
+
* it or cancel it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function acquireJobLocks(store: Store, req: JobLockRequest): AcquiredLocks;
|
|
52
|
+
/** Release every lock held by a job. Idempotent; returns how many rows went away. */
|
|
53
|
+
export declare function releaseJobLocks(store: Store, jobId: string): number;
|
|
54
|
+
export declare function listLocks(store: Store, scope?: LockScope): LockRow[];
|
|
55
|
+
/**
|
|
56
|
+
* Drop rows whose holder is provably gone. Called from `reconcile`; also runs
|
|
57
|
+
* inline during {@link acquireJobLocks} so a dead holder never deadlocks anyone.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reapStaleLocks(store: Store): LockRow[];
|
|
60
|
+
//# sourceMappingURL=locks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.d.ts","sourceRoot":"","sources":["../../src/store/locks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAK9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAGpC,+CAA+C;AAC/C,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,+DAA+D;IAC/D,SAAS,EAAE,OAAO,EAAE,CAAA;CACrB;AAiCD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAWxE;AAsBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,GAAG,aAAa,CAmEhF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,OAAO,EAAE,CAKpE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,EAAE,CAYtD"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { LockConflictError } from '../contract/errors.js';
|
|
2
|
+
import { canonicalize } from '../contract/paths.js';
|
|
3
|
+
import { isPidAlive, isSameProcess } from '../runner/reap.js';
|
|
4
|
+
import { transaction, now } from './db.js';
|
|
5
|
+
import { countLiveJobs, tryGetJob, LIVE_LIFECYCLES } from './jobs.js';
|
|
6
|
+
/** Default per-project concurrency ceiling. */
|
|
7
|
+
export const DEFAULT_MAX_RUNNING = 3;
|
|
8
|
+
function toLockRow(raw) {
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
function getLockRow(store, scope, key) {
|
|
12
|
+
const row = store.db
|
|
13
|
+
.prepare('SELECT * FROM locks WHERE scope = ? AND key = ?')
|
|
14
|
+
.get(scope, key);
|
|
15
|
+
return row ? toLockRow(row) : null;
|
|
16
|
+
}
|
|
17
|
+
function deleteLockRow(store, scope, key) {
|
|
18
|
+
store.db.prepare('DELETE FROM locks WHERE scope = ? AND key = ?').run(scope, key);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* How long a holder with no pid yet is still treated as alive: `agy_start`
|
|
22
|
+
* acquires locks, creates the rows, writes job files and only then spawns the
|
|
23
|
+
* detached runner — tens to hundreds of ms during which `job.pid` is
|
|
24
|
+
* legitimately still null. Without this grace window every lock acquired in
|
|
25
|
+
* that window reads as "dead holder" and is stolen (finding 2).
|
|
26
|
+
*/
|
|
27
|
+
const START_GRACE_MS = 30_000;
|
|
28
|
+
/**
|
|
29
|
+
* Is the job holding this lock still the process we recorded?
|
|
30
|
+
*
|
|
31
|
+
* True while the holder is still in its start grace window (pid not published
|
|
32
|
+
* yet, but young enough that it plausibly will be). False once the pid is
|
|
33
|
+
* gone, once the grace window has elapsed with still no pid, or when the pid
|
|
34
|
+
* is alive but `proc_start_time` differs, which means the number was reused by
|
|
35
|
+
* an unrelated process.
|
|
36
|
+
*/
|
|
37
|
+
export function isHolderAlive(store, holderJobId) {
|
|
38
|
+
const job = tryGetJob(store, holderJobId);
|
|
39
|
+
if (!job)
|
|
40
|
+
return false;
|
|
41
|
+
if (job.pid === null) {
|
|
42
|
+
if (job.lifecycle === 'queued' || job.lifecycle === 'starting') {
|
|
43
|
+
return now() - job.created_at < START_GRACE_MS;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (!isPidAlive(job.pid))
|
|
48
|
+
return false;
|
|
49
|
+
return isSameProcess(job.pid, job.proc_start_time);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve one existing lock row: alive → the row itself (caller must treat as a
|
|
53
|
+
* conflict), stale → deleted and returned via `reclaimed`, absent → null both ways.
|
|
54
|
+
*/
|
|
55
|
+
function resolveExisting(store, scope, key, reclaimed) {
|
|
56
|
+
const existing = getLockRow(store, scope, key);
|
|
57
|
+
if (!existing)
|
|
58
|
+
return null;
|
|
59
|
+
if (isHolderAlive(store, existing.holder_job_id)) {
|
|
60
|
+
return existing;
|
|
61
|
+
}
|
|
62
|
+
deleteLockRow(store, scope, key);
|
|
63
|
+
reclaimed.push(existing);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Acquire every lock the job needs inside one `BEGIN IMMEDIATE` transaction.
|
|
68
|
+
*
|
|
69
|
+
* The procedure is fixed and must not be split across
|
|
70
|
+
* transactions — a check in one and an insert in another is a TOCTOU race:
|
|
71
|
+
*
|
|
72
|
+
* 1. select the `(scope, key)` rows
|
|
73
|
+
* 2. for each existing row, look up the holder job and decide staleness with
|
|
74
|
+
* **both** pid liveness and `proc_start_time` equality. pid alone is wrong:
|
|
75
|
+
* a recycled pid reads as a live holder and the lock never frees.
|
|
76
|
+
* 3. count `starting|running` jobs against `maxRunning`
|
|
77
|
+
* 4. `INSERT INTO locks` — a primary-key collision *is* the lost race
|
|
78
|
+
* 5. `COMMIT`
|
|
79
|
+
*
|
|
80
|
+
* @throws {import('../contract/errors.js').LockConflictError} when a live holder
|
|
81
|
+
* or the ceiling blocks it. Losing must be an error, never quiet serialization,
|
|
82
|
+
* and the error carries the holder so the caller can wait on
|
|
83
|
+
* it or cancel it.
|
|
84
|
+
*/
|
|
85
|
+
export function acquireJobLocks(store, req) {
|
|
86
|
+
return transaction(store.db, () => {
|
|
87
|
+
const reclaimed = [];
|
|
88
|
+
const wanted = [];
|
|
89
|
+
if (req.writeMode) {
|
|
90
|
+
wanted.push({ scope: 'cwd_write', key: canonicalize(req.cwd) });
|
|
91
|
+
}
|
|
92
|
+
if (req.sessionId) {
|
|
93
|
+
wanted.push({ scope: 'session', key: req.sessionId });
|
|
94
|
+
}
|
|
95
|
+
for (const w of wanted) {
|
|
96
|
+
const held = resolveExisting(store, w.scope, w.key, reclaimed);
|
|
97
|
+
if (held) {
|
|
98
|
+
const holder = tryGetJob(store, held.holder_job_id);
|
|
99
|
+
const detail = {
|
|
100
|
+
scope: w.scope,
|
|
101
|
+
key: w.key,
|
|
102
|
+
reason: 'held',
|
|
103
|
+
holder_job_id: held.holder_job_id,
|
|
104
|
+
holder_pid: holder?.pid ?? null,
|
|
105
|
+
holder_started_at: holder?.started_at ?? null,
|
|
106
|
+
acquired_at: held.acquired_at,
|
|
107
|
+
running_job_ids: [],
|
|
108
|
+
limit: null,
|
|
109
|
+
};
|
|
110
|
+
throw new LockConflictError(detail);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const maxRunning = req.maxRunning ?? DEFAULT_MAX_RUNNING;
|
|
114
|
+
const liveCount = countLiveJobs(store);
|
|
115
|
+
if (liveCount >= maxRunning) {
|
|
116
|
+
const runningJobIds = store.db
|
|
117
|
+
.prepare(`SELECT job_id FROM jobs WHERE lifecycle IN (${LIVE_LIFECYCLES.map(() => '?').join(', ')})`)
|
|
118
|
+
.all(...LIVE_LIFECYCLES)
|
|
119
|
+
.map((r) => r.job_id);
|
|
120
|
+
const detail = {
|
|
121
|
+
scope: 'running_limit',
|
|
122
|
+
key: '*',
|
|
123
|
+
reason: 'limit',
|
|
124
|
+
holder_job_id: null,
|
|
125
|
+
holder_pid: null,
|
|
126
|
+
holder_started_at: null,
|
|
127
|
+
acquired_at: null,
|
|
128
|
+
running_job_ids: runningJobIds,
|
|
129
|
+
limit: maxRunning,
|
|
130
|
+
};
|
|
131
|
+
throw new LockConflictError(detail);
|
|
132
|
+
}
|
|
133
|
+
const acquiredAt = now();
|
|
134
|
+
const rows = [];
|
|
135
|
+
for (const w of wanted) {
|
|
136
|
+
store.db
|
|
137
|
+
.prepare('INSERT INTO locks (scope, key, holder_job_id, acquired_at) VALUES (?, ?, ?, ?)')
|
|
138
|
+
.run(w.scope, w.key, req.jobId, acquiredAt);
|
|
139
|
+
rows.push({ scope: w.scope, key: w.key, holder_job_id: req.jobId, acquired_at: acquiredAt });
|
|
140
|
+
}
|
|
141
|
+
return { jobId: req.jobId, rows, reclaimed };
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** Release every lock held by a job. Idempotent; returns how many rows went away. */
|
|
145
|
+
export function releaseJobLocks(store, jobId) {
|
|
146
|
+
const result = store.db.prepare('DELETE FROM locks WHERE holder_job_id = ?').run(jobId);
|
|
147
|
+
return Number(result.changes);
|
|
148
|
+
}
|
|
149
|
+
export function listLocks(store, scope) {
|
|
150
|
+
const rows = scope
|
|
151
|
+
? store.db.prepare('SELECT * FROM locks WHERE scope = ?').all(scope)
|
|
152
|
+
: store.db.prepare('SELECT * FROM locks').all();
|
|
153
|
+
return rows.map(toLockRow);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Drop rows whose holder is provably gone. Called from `reconcile`; also runs
|
|
157
|
+
* inline during {@link acquireJobLocks} so a dead holder never deadlocks anyone.
|
|
158
|
+
*/
|
|
159
|
+
export function reapStaleLocks(store) {
|
|
160
|
+
return transaction(store.db, () => {
|
|
161
|
+
const all = listLocks(store);
|
|
162
|
+
const reclaimed = [];
|
|
163
|
+
for (const row of all) {
|
|
164
|
+
if (!isHolderAlive(store, row.holder_job_id)) {
|
|
165
|
+
deleteLockRow(store, row.scope, row.key);
|
|
166
|
+
reclaimed.push(row);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return reclaimed;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=locks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locks.js","sourceRoot":"","sources":["../../src/store/locks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Profile, SessionRow, SessionState } from '../contract/types.js';
|
|
2
|
+
import type { Store } from './db.js';
|
|
3
|
+
export interface NewSession {
|
|
4
|
+
/** Omit to mint one. */
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
cwd: string;
|
|
7
|
+
model?: string | null;
|
|
8
|
+
effort?: string | null;
|
|
9
|
+
profile?: Profile | null;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionFilter {
|
|
12
|
+
state?: SessionState;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
limit?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function createSession(store: Store, input: NewSession): SessionRow;
|
|
17
|
+
export declare function getSession(store: Store, sessionId: string): SessionRow | null;
|
|
18
|
+
/** Reverse lookup used by the gate after it binds a conversation to a job. */
|
|
19
|
+
export declare function findSessionByConversation(store: Store, conversationId: string): SessionRow | null;
|
|
20
|
+
/**
|
|
21
|
+
* Attach the conversation id captured from the first `init` event.
|
|
22
|
+
*
|
|
23
|
+
* Bound lazily: at `agy_start` time we do not have one yet (agy mints it), so the
|
|
24
|
+
* column stays NULL until the events file has its first line.
|
|
25
|
+
*/
|
|
26
|
+
export declare function bindConversationId(store: Store, sessionId: string, conversationId: string): SessionRow;
|
|
27
|
+
/** Update `last_used_at`, `last_job_id`, and any supplied columns. */
|
|
28
|
+
export declare function touchSession(store: Store, sessionId: string, patch?: Partial<Pick<SessionRow, 'last_job_id' | 'model' | 'effort' | 'profile' | 'turn_count'>>): SessionRow;
|
|
29
|
+
/** Advance `turn_count` by the number of `result` events a finished job produced. */
|
|
30
|
+
export declare function addTurns(store: Store, sessionId: string, n: number): SessionRow;
|
|
31
|
+
export declare function listSessions(store: Store, filter?: SessionFilter): SessionRow[];
|
|
32
|
+
/**
|
|
33
|
+
* Mark a session closed. The conversation itself is not destroyed — agy resume is
|
|
34
|
+
* lossless, so a closed session is a bookkeeping state, not a deletion.
|
|
35
|
+
*/
|
|
36
|
+
export declare function closeSession(store: Store, sessionId: string): SessionRow;
|
|
37
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/store/sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAE7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAKpC,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,YAAY,CAAA;IACpB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA4BD,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,UAAU,CAuBzE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAK7E;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAKjG;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,UAAU,CAQtG;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC,CAAC,GAC/F,UAAU,CAaZ;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAU/E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,UAAU,EAAE,CAiB/E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CAQxE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { newSessionId } from '../contract/paths.js';
|
|
2
|
+
import { now } from './db.js';
|
|
3
|
+
function toSessionRow(raw) {
|
|
4
|
+
return raw;
|
|
5
|
+
}
|
|
6
|
+
function requireSession(store, sessionId) {
|
|
7
|
+
const row = getSession(store, sessionId);
|
|
8
|
+
if (!row) {
|
|
9
|
+
throw new Error(`no session "${sessionId}" in project ${store.root}`);
|
|
10
|
+
}
|
|
11
|
+
return row;
|
|
12
|
+
}
|
|
13
|
+
export function createSession(store, input) {
|
|
14
|
+
const sessionId = input.sessionId || newSessionId();
|
|
15
|
+
const ts = now();
|
|
16
|
+
store.db
|
|
17
|
+
.prepare(`INSERT INTO sessions (
|
|
18
|
+
session_id, conversation_id, cwd, model, effort, profile,
|
|
19
|
+
turn_count, last_job_id, created_at, last_used_at, state
|
|
20
|
+
) VALUES (
|
|
21
|
+
@session_id, NULL, @cwd, @model, @effort, @profile,
|
|
22
|
+
0, NULL, @created_at, @last_used_at, 'active'
|
|
23
|
+
)`)
|
|
24
|
+
.run({
|
|
25
|
+
session_id: sessionId,
|
|
26
|
+
cwd: input.cwd,
|
|
27
|
+
model: input.model ?? null,
|
|
28
|
+
effort: input.effort ?? null,
|
|
29
|
+
profile: input.profile ?? null,
|
|
30
|
+
created_at: ts,
|
|
31
|
+
last_used_at: ts,
|
|
32
|
+
});
|
|
33
|
+
return requireSession(store, sessionId);
|
|
34
|
+
}
|
|
35
|
+
export function getSession(store, sessionId) {
|
|
36
|
+
const row = store.db.prepare('SELECT * FROM sessions WHERE session_id = ?').get(sessionId);
|
|
37
|
+
return row ? toSessionRow(row) : null;
|
|
38
|
+
}
|
|
39
|
+
/** Reverse lookup used by the gate after it binds a conversation to a job. */
|
|
40
|
+
export function findSessionByConversation(store, conversationId) {
|
|
41
|
+
const row = store.db
|
|
42
|
+
.prepare('SELECT * FROM sessions WHERE conversation_id = ?')
|
|
43
|
+
.get(conversationId);
|
|
44
|
+
return row ? toSessionRow(row) : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Attach the conversation id captured from the first `init` event.
|
|
48
|
+
*
|
|
49
|
+
* Bound lazily: at `agy_start` time we do not have one yet (agy mints it), so the
|
|
50
|
+
* column stays NULL until the events file has its first line.
|
|
51
|
+
*/
|
|
52
|
+
export function bindConversationId(store, sessionId, conversationId) {
|
|
53
|
+
const result = store.db
|
|
54
|
+
.prepare('UPDATE sessions SET conversation_id = ? WHERE session_id = ?')
|
|
55
|
+
.run(conversationId, sessionId);
|
|
56
|
+
if (result.changes === 0) {
|
|
57
|
+
throw new Error(`no session "${sessionId}" in project ${store.root}`);
|
|
58
|
+
}
|
|
59
|
+
return requireSession(store, sessionId);
|
|
60
|
+
}
|
|
61
|
+
/** Update `last_used_at`, `last_job_id`, and any supplied columns. */
|
|
62
|
+
export function touchSession(store, sessionId, patch) {
|
|
63
|
+
const entries = Object.entries(patch ?? {});
|
|
64
|
+
const setClauses = ['last_used_at = @last_used_at', ...entries.map(([k]) => `${k} = @${k}`)];
|
|
65
|
+
const params = { session_id: sessionId, last_used_at: now() };
|
|
66
|
+
for (const [k, v] of entries)
|
|
67
|
+
params[k] = v;
|
|
68
|
+
const result = store.db
|
|
69
|
+
.prepare(`UPDATE sessions SET ${setClauses.join(', ')} WHERE session_id = @session_id`)
|
|
70
|
+
.run(params);
|
|
71
|
+
if (result.changes === 0) {
|
|
72
|
+
throw new Error(`no session "${sessionId}" in project ${store.root}`);
|
|
73
|
+
}
|
|
74
|
+
return requireSession(store, sessionId);
|
|
75
|
+
}
|
|
76
|
+
/** Advance `turn_count` by the number of `result` events a finished job produced. */
|
|
77
|
+
export function addTurns(store, sessionId, n) {
|
|
78
|
+
const result = store.db
|
|
79
|
+
.prepare('UPDATE sessions SET turn_count = turn_count + @n, last_used_at = @last_used_at WHERE session_id = @session_id')
|
|
80
|
+
.run({ session_id: sessionId, n, last_used_at: now() });
|
|
81
|
+
if (result.changes === 0) {
|
|
82
|
+
throw new Error(`no session "${sessionId}" in project ${store.root}`);
|
|
83
|
+
}
|
|
84
|
+
return requireSession(store, sessionId);
|
|
85
|
+
}
|
|
86
|
+
export function listSessions(store, filter) {
|
|
87
|
+
const clauses = [];
|
|
88
|
+
const params = {};
|
|
89
|
+
if (filter?.state) {
|
|
90
|
+
clauses.push('state = @state');
|
|
91
|
+
params.state = filter.state;
|
|
92
|
+
}
|
|
93
|
+
if (filter?.cwd) {
|
|
94
|
+
clauses.push('cwd = @cwd');
|
|
95
|
+
params.cwd = filter.cwd;
|
|
96
|
+
}
|
|
97
|
+
const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
|
|
98
|
+
const limit = filter?.limit && filter.limit > 0 ? Math.floor(filter.limit) : 100;
|
|
99
|
+
const rows = store.db
|
|
100
|
+
.prepare(`SELECT * FROM sessions ${where} ORDER BY last_used_at DESC LIMIT ${limit}`)
|
|
101
|
+
.all(params);
|
|
102
|
+
return rows.map(toSessionRow);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Mark a session closed. The conversation itself is not destroyed — agy resume is
|
|
106
|
+
* lossless, so a closed session is a bookkeeping state, not a deletion.
|
|
107
|
+
*/
|
|
108
|
+
export function closeSession(store, sessionId) {
|
|
109
|
+
const result = store.db
|
|
110
|
+
.prepare("UPDATE sessions SET state = 'closed', last_used_at = @last_used_at WHERE session_id = @session_id")
|
|
111
|
+
.run({ session_id: sessionId, last_used_at: now() });
|
|
112
|
+
if (result.changes === 0) {
|
|
113
|
+
throw new Error(`no session "${sessionId}" in project ${store.root}`);
|
|
114
|
+
}
|
|
115
|
+
return requireSession(store, sessionId);
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/store/sessions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution trace digest.
|
|
3
|
+
*
|
|
4
|
+
* Distills raw job logs (gate-log.jsonl, events.ndjson, broker-result.json)
|
|
5
|
+
* into a compact, human-readable summary of commands, files touched,
|
|
6
|
+
* gate denials, and tool errors. Pure functions, no I/O except the explicit
|
|
7
|
+
* loader `loadJobDigest`.
|
|
8
|
+
*/
|
|
9
|
+
export interface DigestCommand {
|
|
10
|
+
command: string;
|
|
11
|
+
count: number;
|
|
12
|
+
denied: number;
|
|
13
|
+
}
|
|
14
|
+
export interface DigestFiles {
|
|
15
|
+
read: string[];
|
|
16
|
+
edited: string[];
|
|
17
|
+
read_count: number;
|
|
18
|
+
edited_count: number;
|
|
19
|
+
}
|
|
20
|
+
export interface DigestDenial {
|
|
21
|
+
tool: string;
|
|
22
|
+
command: string | null;
|
|
23
|
+
stage: string;
|
|
24
|
+
required_rule: string | null;
|
|
25
|
+
count: number;
|
|
26
|
+
}
|
|
27
|
+
export interface DigestToolError {
|
|
28
|
+
step_idx: number | null;
|
|
29
|
+
tool: string;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
export interface JobDigest {
|
|
33
|
+
job_id: string;
|
|
34
|
+
outcome: string | null;
|
|
35
|
+
exit_code: number | null;
|
|
36
|
+
duration_ms: number | null;
|
|
37
|
+
turns: number | null;
|
|
38
|
+
commands: DigestCommand[];
|
|
39
|
+
files: DigestFiles;
|
|
40
|
+
denials: DigestDenial[];
|
|
41
|
+
tool_errors: DigestToolError[];
|
|
42
|
+
trace: 'full' | 'no_gate_log' | 'empty';
|
|
43
|
+
}
|
|
44
|
+
export interface ProjectDigest {
|
|
45
|
+
jobs: number;
|
|
46
|
+
outcomes: Record<string, number>;
|
|
47
|
+
denied_rules: Array<{
|
|
48
|
+
required_rule: string;
|
|
49
|
+
count: number;
|
|
50
|
+
last_job_id: string;
|
|
51
|
+
last_ts: number;
|
|
52
|
+
}>;
|
|
53
|
+
top_commands: DigestCommand[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Pure digest computation over in-memory string captures.
|
|
57
|
+
*/
|
|
58
|
+
export declare function digestJob(input: {
|
|
59
|
+
job_id: string;
|
|
60
|
+
workspace: string | null;
|
|
61
|
+
gateLog: string | null;
|
|
62
|
+
events: string | null;
|
|
63
|
+
brokerResult: unknown | null;
|
|
64
|
+
}): JobDigest;
|
|
65
|
+
/**
|
|
66
|
+
* Cross-job roll-up across multiple job digests and their gate logs.
|
|
67
|
+
*/
|
|
68
|
+
export declare function digestProject(digests: JobDigest[], gateLogsById: Record<string, string | null>): ProjectDigest;
|
|
69
|
+
/**
|
|
70
|
+
* Load a job's files from its job directory and compute its digest.
|
|
71
|
+
*/
|
|
72
|
+
export declare function loadJobDigest(jobDir: string, workspace: string | null): JobDigest;
|
|
73
|
+
/**
|
|
74
|
+
* Render a JobDigest into human-readable text layout under ~40 lines.
|
|
75
|
+
*/
|
|
76
|
+
export declare function renderDigest(d: JobDigest): string;
|
|
77
|
+
//# sourceMappingURL=digest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../../src/trace/digest.ts"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,aAAa,EAAE,CAAA;IACzB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,WAAW,EAAE,eAAe,EAAE,CAAA;IAC9B,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAA;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,YAAY,EAAE,KAAK,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnG,YAAY,EAAE,aAAa,EAAE,CAAA;CAC9B;AA8CD;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,YAAY,EAAE,OAAO,GAAG,IAAI,CAAA;CAC7B,GAAG,SAAS,CA+OZ;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,SAAS,EAAE,EACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC1C,aAAa,CA2Ef;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAuDjF;AA2BD;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAwDjD"}
|