@skillstate/core 2.0.7 → 2.2.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/adapter-shared.d.ts +61 -0
- package/dist/adapter-shared.d.ts.map +1 -0
- package/dist/adapter-shared.js +96 -0
- package/dist/adapter-shared.js.map +1 -0
- package/dist/atomic-write.d.ts +17 -0
- package/dist/atomic-write.d.ts.map +1 -1
- package/dist/atomic-write.js +47 -0
- package/dist/atomic-write.js.map +1 -1
- package/dist/hook-runtime.d.ts +163 -0
- package/dist/hook-runtime.d.ts.map +1 -0
- package/dist/hook-runtime.js +364 -0
- package/dist/hook-runtime.js.map +1 -0
- package/dist/host-state.d.ts +3 -2
- package/dist/host-state.d.ts.map +1 -1
- package/dist/host-state.js +24 -11
- package/dist/host-state.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/prompt-contract.d.ts +82 -0
- package/dist/prompt-contract.d.ts.map +1 -0
- package/dist/prompt-contract.js +129 -0
- package/dist/prompt-contract.js.map +1 -0
- package/dist/prompt-transformer.d.ts +0 -4
- package/dist/prompt-transformer.d.ts.map +1 -1
- package/dist/prompt-transformer.js +8 -43
- package/dist/prompt-transformer.js.map +1 -1
- package/dist/session-meta.d.ts +58 -0
- package/dist/session-meta.d.ts.map +1 -0
- package/dist/session-meta.js +101 -0
- package/dist/session-meta.js.map +1 -0
- package/dist/state-manager.d.ts.map +1 -1
- package/dist/state-manager.js +6 -30
- package/dist/state-manager.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @non-paper Wave-5 shared adapter plumbing — the SINGLE SOURCE OF TRUTH
|
|
3
|
+
* for the state-path resolution, atomic persistence, and hooks-merge
|
|
4
|
+
* mechanics that were duplicated across the platform adapters (claude,
|
|
5
|
+
* codex). Adapters stay EVENT TABLES + brand constants + script/config
|
|
6
|
+
* generators; everything below is host-agnostic:
|
|
7
|
+
*
|
|
8
|
+
* - {@link resolveTarget}: the `string | StatePathRef` resolution every
|
|
9
|
+
* adapter save/generate methods shared (throws on `..` traversal);
|
|
10
|
+
* - {@link saveGenerated}: resolve + `atomicWriteFile` + return the dest —
|
|
11
|
+
* the body of every adapter `save` method;
|
|
12
|
+
* - {@link mergeHookGroups}: the hooks.json / settings.json merge —
|
|
13
|
+
* idempotent by skillstate commands, appends fresh groups, preserves
|
|
14
|
+
* every foreign group and top-level key. Format specifics (which groups
|
|
15
|
+
* to generate, which commands are "ours") are passed in by the adapter.
|
|
16
|
+
*/
|
|
17
|
+
import type { StatePathRef } from './atomic-write.js';
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a `string | StatePathRef` target via `resolveStatePath` — the
|
|
20
|
+
* exact logic every adapter's private `resolve` helper shared. Raw strings
|
|
21
|
+
* pass through; `{ root, name }` refs are confined to `root` (traversal
|
|
22
|
+
* throws).
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveTarget(target: string | StatePathRef): string;
|
|
25
|
+
/**
|
|
26
|
+
* Persist adapter-generated content to `target` via `atomicWriteFile`
|
|
27
|
+
* (temp sibling + fsync + rename) and return the absolute destination
|
|
28
|
+
* path. The shared body of the adapter `save*` methods.
|
|
29
|
+
*/
|
|
30
|
+
export declare function saveGenerated(target: string | StatePathRef, content: string): Promise<string>;
|
|
31
|
+
/** Parameters for {@link mergeHookGroups} (the adapter-provided specifics). */
|
|
32
|
+
export interface MergeHookGroupsParams {
|
|
33
|
+
/** The existing hooks document text (hooks.json / settings.json). */
|
|
34
|
+
existingJson: string;
|
|
35
|
+
/** The adapter-generated `{ Event: [group, …] }` groups to append. */
|
|
36
|
+
generatedGroups: Record<string, unknown[]>;
|
|
37
|
+
/**
|
|
38
|
+
* JSON-stringified skillstate commands (one per generated event script)
|
|
39
|
+
* — a handler whose stringified `command` is in this set marks the
|
|
40
|
+
* document as already wired.
|
|
41
|
+
*/
|
|
42
|
+
commandsOf: ReadonlySet<string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Merge the skillstate hook groups into an existing hooks document — the
|
|
46
|
+
* shared mechanics of the claude and codex `mergeHooksConfig` methods:
|
|
47
|
+
*
|
|
48
|
+
* - malformed/empty/non-object input starts from a fresh document (the
|
|
49
|
+
* CLI guards a live config before calling);
|
|
50
|
+
* - idempotent: if ANY skillstate command is already wired, the ORIGINAL
|
|
51
|
+
* text is returned byte-unchanged (no re-serialization, no duplicate
|
|
52
|
+
* groups, no surprise reformatting of a hand-written file);
|
|
53
|
+
* - otherwise every generated group is APPENDED per event: existing
|
|
54
|
+
* (non-skillstate) groups and every other top-level key survive, and a
|
|
55
|
+
* non-array event value is replaced by the fresh group.
|
|
56
|
+
*
|
|
57
|
+
* Returns the merged document re-serialized (2-space indent, newline-
|
|
58
|
+
* terminated) — or the untouched input on the already-wired path.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mergeHookGroups({ existingJson, generatedGroups, commandsOf, }: MergeHookGroupsParams): string;
|
|
61
|
+
//# sourceMappingURL=adapter-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-shared.d.ts","sourceRoot":"","sources":["../src/adapter-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,MAAM,CAInE;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,GAAG,YAAY,EAC7B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,qEAAqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C;;;;OAIG;IACH,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,EAC9B,YAAY,EACZ,eAAe,EACf,UAAU,GACX,EAAE,qBAAqB,GAAG,MAAM,CAoChC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @non-paper Wave-5 shared adapter plumbing — the SINGLE SOURCE OF TRUTH
|
|
3
|
+
* for the state-path resolution, atomic persistence, and hooks-merge
|
|
4
|
+
* mechanics that were duplicated across the platform adapters (claude,
|
|
5
|
+
* codex). Adapters stay EVENT TABLES + brand constants + script/config
|
|
6
|
+
* generators; everything below is host-agnostic:
|
|
7
|
+
*
|
|
8
|
+
* - {@link resolveTarget}: the `string | StatePathRef` resolution every
|
|
9
|
+
* adapter save/generate methods shared (throws on `..` traversal);
|
|
10
|
+
* - {@link saveGenerated}: resolve + `atomicWriteFile` + return the dest —
|
|
11
|
+
* the body of every adapter `save` method;
|
|
12
|
+
* - {@link mergeHookGroups}: the hooks.json / settings.json merge —
|
|
13
|
+
* idempotent by skillstate commands, appends fresh groups, preserves
|
|
14
|
+
* every foreign group and top-level key. Format specifics (which groups
|
|
15
|
+
* to generate, which commands are "ours") are passed in by the adapter.
|
|
16
|
+
*/
|
|
17
|
+
import { atomicWriteFile, resolveStatePath } from './atomic-write.js';
|
|
18
|
+
import { isPlainObject } from './hook-runtime.js';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a `string | StatePathRef` target via `resolveStatePath` — the
|
|
21
|
+
* exact logic every adapter's private `resolve` helper shared. Raw strings
|
|
22
|
+
* pass through; `{ root, name }` refs are confined to `root` (traversal
|
|
23
|
+
* throws).
|
|
24
|
+
*/
|
|
25
|
+
export function resolveTarget(target) {
|
|
26
|
+
return typeof target === 'string'
|
|
27
|
+
? target
|
|
28
|
+
: resolveStatePath(target.root, target.name);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Persist adapter-generated content to `target` via `atomicWriteFile`
|
|
32
|
+
* (temp sibling + fsync + rename) and return the absolute destination
|
|
33
|
+
* path. The shared body of the adapter `save*` methods.
|
|
34
|
+
*/
|
|
35
|
+
export async function saveGenerated(target, content) {
|
|
36
|
+
const dest = resolveTarget(target);
|
|
37
|
+
await atomicWriteFile(dest, content);
|
|
38
|
+
return dest;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Merge the skillstate hook groups into an existing hooks document — the
|
|
42
|
+
* shared mechanics of the claude and codex `mergeHooksConfig` methods:
|
|
43
|
+
*
|
|
44
|
+
* - malformed/empty/non-object input starts from a fresh document (the
|
|
45
|
+
* CLI guards a live config before calling);
|
|
46
|
+
* - idempotent: if ANY skillstate command is already wired, the ORIGINAL
|
|
47
|
+
* text is returned byte-unchanged (no re-serialization, no duplicate
|
|
48
|
+
* groups, no surprise reformatting of a hand-written file);
|
|
49
|
+
* - otherwise every generated group is APPENDED per event: existing
|
|
50
|
+
* (non-skillstate) groups and every other top-level key survive, and a
|
|
51
|
+
* non-array event value is replaced by the fresh group.
|
|
52
|
+
*
|
|
53
|
+
* Returns the merged document re-serialized (2-space indent, newline-
|
|
54
|
+
* terminated) — or the untouched input on the already-wired path.
|
|
55
|
+
*/
|
|
56
|
+
export function mergeHookGroups({ existingJson, generatedGroups, commandsOf, }) {
|
|
57
|
+
let doc = {};
|
|
58
|
+
try {
|
|
59
|
+
doc = JSON.parse(existingJson);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Missing or malformed input: start from a fresh document.
|
|
63
|
+
}
|
|
64
|
+
if (!isPlainObject(doc)) {
|
|
65
|
+
doc = {};
|
|
66
|
+
}
|
|
67
|
+
if (!isPlainObject(doc['hooks'])) {
|
|
68
|
+
doc['hooks'] = {};
|
|
69
|
+
}
|
|
70
|
+
const hooks = doc['hooks'];
|
|
71
|
+
let alreadyWired = false;
|
|
72
|
+
for (const groups of Object.values(hooks)) {
|
|
73
|
+
if (!Array.isArray(groups))
|
|
74
|
+
continue;
|
|
75
|
+
for (const group of groups) {
|
|
76
|
+
if (!isPlainObject(group) || !Array.isArray(group['hooks']))
|
|
77
|
+
continue;
|
|
78
|
+
for (const handler of group['hooks']) {
|
|
79
|
+
if (isPlainObject(handler) && commandsOf.has(JSON.stringify(handler['command']))) {
|
|
80
|
+
alreadyWired = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (alreadyWired) {
|
|
86
|
+
return existingJson;
|
|
87
|
+
}
|
|
88
|
+
for (const [event, groups] of Object.entries(generatedGroups)) {
|
|
89
|
+
const existing = Array.isArray(hooks[event])
|
|
90
|
+
? hooks[event]
|
|
91
|
+
: [];
|
|
92
|
+
hooks[event] = [...existing, ...groups];
|
|
93
|
+
}
|
|
94
|
+
return `${JSON.stringify(doc, null, 2)}\n`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=adapter-shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-shared.js","sourceRoot":"","sources":["../src/adapter-shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAA6B;IACzD,OAAO,OAAO,MAAM,KAAK,QAAQ;QAC/B,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAA6B,EAC7B,OAAe;IAEf,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,EAC9B,YAAY,EACZ,eAAe,EACf,UAAU,GACY;IACtB,IAAI,GAAG,GAA4B,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAe,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,2DAA2D;IAC7D,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,GAAG,GAAG,EAAE,CAAC;IACX,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IACpB,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAA4B,CAAC;IACtD,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,SAAS;QACrC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAAE,SAAS;YACtE,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrC,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjF,YAAY,GAAG,IAAI,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,YAAY,CAAC;IACtB,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,CAAC,CAAE,KAAK,CAAC,KAAK,CAAe;YAC7B,CAAC,CAAC,EAAE,CAAC;QACP,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC"}
|
package/dist/atomic-write.d.ts
CHANGED
|
@@ -30,6 +30,23 @@ export interface LockHandle {
|
|
|
30
30
|
* - Unreadable/missing locations (stat fails) → `null`, never throws.
|
|
31
31
|
*/
|
|
32
32
|
export declare function acquireLock(lockPath: string, ttlMs?: number): Promise<LockHandle | null>;
|
|
33
|
+
/** Default retry count for {@link withStateLock} (~10s at 50ms per try). */
|
|
34
|
+
export declare const DEFAULT_LOCK_RETRIES = 200;
|
|
35
|
+
/** Delay between {@link withStateLock} acquisition retries (50ms). */
|
|
36
|
+
export declare const LOCK_RETRY_DELAY_MS = 50;
|
|
37
|
+
/**
|
|
38
|
+
* Run `fn` while holding an exclusive cross-process lock on
|
|
39
|
+
* `statePath + '.lock'` ({@link acquireLock}, default TTL). Waits for a
|
|
40
|
+
* live holder: up to `retries` (default {@link DEFAULT_LOCK_RETRIES})
|
|
41
|
+
* attempts {@link LOCK_RETRY_DELAY_MS} apart, so 2-3 concurrent agent
|
|
42
|
+
* processes serialize instead of failing. The lock is acquired before
|
|
43
|
+
* `fn` starts and ALWAYS released in the `finally` block; the `fn`
|
|
44
|
+
* result (or failure) propagates after the release. Throws when the lock
|
|
45
|
+
* cannot be acquired within the retry budget. This is the async
|
|
46
|
+
* hot-path serialization primitive for the MCP server writes
|
|
47
|
+
* (patch/rollback/checkpoint/merge) and the host adapters.
|
|
48
|
+
*/
|
|
49
|
+
export declare function withStateLock<T>(statePath: string, fn: () => Promise<T> | T, ttlMs?: number, retries?: number): Promise<T>;
|
|
33
50
|
/**
|
|
34
51
|
* Resolve `name` inside `root` and return the absolute path. Throws when
|
|
35
52
|
* the result escapes `root` (`..` traversal or an absolute outsider).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"AAkBA,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,UAAU,GAC3B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAUD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAwB5B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnE"}
|
|
1
|
+
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"AAkBA,4DAA4D;AAC5D,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GAAG,UAAU,GAC3B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAUD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAwB5B;AAED,4EAA4E;AAC5E,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,CAAC,EACnC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,EACxB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,MAA6B,GACrC,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAeD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnE"}
|
package/dist/atomic-write.js
CHANGED
|
@@ -80,6 +80,53 @@ export async function acquireLock(lockPath, ttlMs) {
|
|
|
80
80
|
return makeHandle(lockPath);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
|
+
/** Default retry count for {@link withStateLock} (~10s at 50ms per try). */
|
|
84
|
+
export const DEFAULT_LOCK_RETRIES = 200;
|
|
85
|
+
/** Delay between {@link withStateLock} acquisition retries (50ms). */
|
|
86
|
+
export const LOCK_RETRY_DELAY_MS = 50;
|
|
87
|
+
/**
|
|
88
|
+
* Run `fn` while holding an exclusive cross-process lock on
|
|
89
|
+
* `statePath + '.lock'` ({@link acquireLock}, default TTL). Waits for a
|
|
90
|
+
* live holder: up to `retries` (default {@link DEFAULT_LOCK_RETRIES})
|
|
91
|
+
* attempts {@link LOCK_RETRY_DELAY_MS} apart, so 2-3 concurrent agent
|
|
92
|
+
* processes serialize instead of failing. The lock is acquired before
|
|
93
|
+
* `fn` starts and ALWAYS released in the `finally` block; the `fn`
|
|
94
|
+
* result (or failure) propagates after the release. Throws when the lock
|
|
95
|
+
* cannot be acquired within the retry budget. This is the async
|
|
96
|
+
* hot-path serialization primitive for the MCP server writes
|
|
97
|
+
* (patch/rollback/checkpoint/merge) and the host adapters.
|
|
98
|
+
*/
|
|
99
|
+
export async function withStateLock(statePath, fn, ttlMs, retries = DEFAULT_LOCK_RETRIES) {
|
|
100
|
+
const lockPath = `${statePath}.lock`;
|
|
101
|
+
await fs.promises.mkdir(path.dirname(lockPath), { recursive: true });
|
|
102
|
+
let handle = await acquireLockSafely(lockPath, ttlMs);
|
|
103
|
+
for (let attempt = 0; handle === null && attempt < retries; attempt++) {
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_DELAY_MS));
|
|
105
|
+
handle = await acquireLockSafely(lockPath, ttlMs);
|
|
106
|
+
}
|
|
107
|
+
if (!handle) {
|
|
108
|
+
throw new Error(`skillstate: could not acquire the state lock: ${lockPath}`);
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
return await fn();
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
handle.release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* {@link acquireLock} with the stale-takeover race collapsed to `null`:
|
|
119
|
+
* between `unlink` and the re-`writeFile`, another waiter may recreate
|
|
120
|
+
* the lockfile (`EEXIST`) — that loser must retry, never crash.
|
|
121
|
+
*/
|
|
122
|
+
async function acquireLockSafely(lockPath, ttlMs) {
|
|
123
|
+
try {
|
|
124
|
+
return await acquireLock(lockPath, ttlMs);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
83
130
|
/**
|
|
84
131
|
* Resolve `name` inside `root` and return the absolute path. Throws when
|
|
85
132
|
* the result escapes `root` (`..` traversal or an absolute outsider).
|
package/dist/atomic-write.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAY1C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAA4B;IAE5B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,GAAG,QAAQ,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;IACtB,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAOD,SAAS,UAAU,CAAC,QAAgB;IAClC,OAAO;QACL,OAAO,EAAE,GAAS,EAAE;YAClB,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAgB,EAChB,KAAc;IAEd,MAAM,GAAG,GAAG,KAAK,IAAI,mBAAmB,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACzD,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9C,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACzD,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QACH,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,sEAAsE;AACtE,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAiB,EACjB,EAAwB,EACxB,KAAc,EACd,OAAO,GAAW,oBAAoB;IAEtC,MAAM,QAAQ,GAAG,GAAG,SAAS,OAAO,CAAC;IACrC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,IAAI,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACtD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,IAAI,OAAO,GAAG,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;QACtE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;QACzE,MAAM,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,iDAAiD,QAAQ,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,iBAAiB,CAAC,QAAgB,EAAE,KAAc;IAC/D,IAAI,CAAC;QACH,OAAO,MAAM,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAAY;IACzD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @non-paper Wave-5 hook runtime — the SINGLE SOURCE OF TRUTH for every
|
|
3
|
+
* piece of logic embedded into self-contained generated hook scripts
|
|
4
|
+
* (Claude Code `.cjs` hooks, Codex `.cjs` hooks) and reused directly by
|
|
5
|
+
* the OpenCode plugin.
|
|
6
|
+
*
|
|
7
|
+
* CONSTRAINT: the generated scripts cannot import `@skillstate/*`, so the
|
|
8
|
+
* adapters inline these functions into the emitted `.cjs` via
|
|
9
|
+
* `fn.toString()` (see {@link hookRuntimeSnippet}). That means every
|
|
10
|
+
* function body below must be PLAIN JavaScript:
|
|
11
|
+
*
|
|
12
|
+
* - no imports and no `require` — dependencies are passed as parameters
|
|
13
|
+
* (`readFile`, `writeFile`, `home`), so the caller wires the real
|
|
14
|
+
* `node:fs`/`node:os`/`node:path` (or test mocks) at the call site;
|
|
15
|
+
* - type annotations live ONLY in the signatures — both `tsc` (dist) and
|
|
16
|
+
* the vitest/esbuild transform erase them, so `fn.toString()` yields a
|
|
17
|
+
* valid CJS snippet;
|
|
18
|
+
* - no references to module-scope helpers — each function is either fully
|
|
19
|
+
* self-contained or calls only its sibling functions from this module
|
|
20
|
+
* (which the snippet inlines together).
|
|
21
|
+
*
|
|
22
|
+
* The parity suite (`tests/core/hook-runtime-parity.test.ts`) evals the
|
|
23
|
+
* snippets extracted from the generated scripts and asserts byte-identical
|
|
24
|
+
* behavior against these originals, so the "embedded copy" can no longer
|
|
25
|
+
* drift from the source of truth.
|
|
26
|
+
*/
|
|
27
|
+
/** True for plain (non-null, non-array) objects. */
|
|
28
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* Agent-id sanitization: keep `[A-Za-z0-9_-]` runs, collapse everything
|
|
31
|
+
* else into single `-`, trim edge dashes, cap at 64 chars. Garbage input
|
|
32
|
+
* sanitizes to `''` — callers treat that as "no agent" (the main state).
|
|
33
|
+
*/
|
|
34
|
+
export declare function sanitizeAgentId(agentId: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Agent id from a host session id (Claude Code / Codex hook stdin carry
|
|
37
|
+
* `session_id`; opencode hooks carry `sessionID`): the short prefix — the
|
|
38
|
+
* first 8 characters — keeps agent directories bounded while remaining
|
|
39
|
+
* unique enough per session. Non-string/empty input yields `''`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveAgentIdFromSession(sessionId: unknown): string;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the per-project state file for a working directory — the pure,
|
|
44
|
+
* dependency-free mirror of `resolveHostStateForCwd`
|
|
45
|
+
* (`<cwd>/.skillstate/skillstate.json`; the global bucket
|
|
46
|
+
* `<home>/.skillstate/global/skillstate.json` when cwd equals home).
|
|
47
|
+
*
|
|
48
|
+
* AGENT-SCOPED STATE: a non-empty `agentId` (sanitized via
|
|
49
|
+
* {@link sanitizeAgentId}) scopes the file under an isolated
|
|
50
|
+
* `agents/<agentId>/` copy — parallel sub-agents (hook sessions) never
|
|
51
|
+
* share the main state file.
|
|
52
|
+
*
|
|
53
|
+
* String arithmetic only (POSIX): absolute paths are normalized like
|
|
54
|
+
* `path.resolve` (empty/`.` segments dropped, `..` popped, trailing
|
|
55
|
+
* slashes trimmed); relative inputs stay relative because there is no
|
|
56
|
+
* `process` access. Callers that may see relative paths resolve them
|
|
57
|
+
* first (`path.resolve(cwd)`) — the generated hook scripts do exactly
|
|
58
|
+
* that. `home` must be provided to detect the global bucket; when it is
|
|
59
|
+
* omitted the project path is returned.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveStatePathForCwd(cwd: string, home?: string, agentId?: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Read the state file through the injected `readFile` (real `fs` in
|
|
64
|
+
* generated scripts, mocks in tests). The on-disk envelope is
|
|
65
|
+
* `{ version: 1, state }`; a bare object is tolerated and treated as the
|
|
66
|
+
* state itself; anything else (missing file, corrupt JSON, arrays,
|
|
67
|
+
* scalars) yields `{}` — best-effort, never throws.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readStateEnvelope(statePath: string, readFile: (p: string) => string): unknown;
|
|
70
|
+
/**
|
|
71
|
+
* Persist the state through the injected `writeFile` as the
|
|
72
|
+
* `{ version: 1, state }` envelope (pretty-printed, newline-terminated).
|
|
73
|
+
* Throws on failure — the caller decides whether to swallow it (OpenCode
|
|
74
|
+
* plugin) or surface a `systemMessage` (PostToolUse hooks).
|
|
75
|
+
*/
|
|
76
|
+
export declare function saveStateEnvelope(statePath: string, state: object, writeFile: (p: string, data: string) => void): void;
|
|
77
|
+
/**
|
|
78
|
+
* Minimal `node:fs` surface {@link lockStateWrite} needs. Injections keep
|
|
79
|
+
* the hook-runtime pure: real `node:fs` in generated scripts and the
|
|
80
|
+
* plugin, mocks in tests (the established hook-runtime dependency style).
|
|
81
|
+
*/
|
|
82
|
+
export interface StateLockFs {
|
|
83
|
+
openSync(path: string, flags: string): number;
|
|
84
|
+
closeSync(fd: number): void;
|
|
85
|
+
statSync(path: string): {
|
|
86
|
+
mtimeMs: number;
|
|
87
|
+
};
|
|
88
|
+
unlinkSync(path: string): void;
|
|
89
|
+
mkdirSync(path: string): unknown;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Block the current thread for `ms` milliseconds — the sync sleep for the
|
|
93
|
+
* {@link lockStateWrite} retry loop. Prefers `Atomics.wait` on a shared
|
|
94
|
+
* integer (a real sleep); falls back to a busy spin when the primitive is
|
|
95
|
+
* unavailable. Plain JS — survives `fn.toString()` inlining.
|
|
96
|
+
*/
|
|
97
|
+
export declare function sleepSync(ms: number): void;
|
|
98
|
+
/**
|
|
99
|
+
* Cross-process state lock for the SELF-CONTAINED hook scripts (sync,
|
|
100
|
+
* no imports): exclusive `O_EXCL` lockfile at `statePath + '.lock'`,
|
|
101
|
+
* stale-TTL takeover (10s — crashed hook holders), and a retry loop
|
|
102
|
+
* (50ms × 40 ≈ 2s) while a live holder runs its critical section. The
|
|
103
|
+
* injected `fs` (real `node:fs` in generated scripts and the plugin,
|
|
104
|
+
* mocks in tests) performs the lockfile I/O and creates the parent
|
|
105
|
+
* directory. The injected `fn` runs inside the lock; the lockfile is
|
|
106
|
+
* ALWAYS removed in the `finally` block. `fn` failures propagate AFTER
|
|
107
|
+
* the release; lock exhaustion throws.
|
|
108
|
+
*/
|
|
109
|
+
export declare function lockStateWrite(statePath: string, fs: StateLockFs, fn: () => unknown): unknown;
|
|
110
|
+
/**
|
|
111
|
+
* Paper ⊕ merge: `null` deletes a key, nested plain objects merge
|
|
112
|
+
* recursively, everything else replaces. Pure — neither `state` nor
|
|
113
|
+
* `patch` is mutated.
|
|
114
|
+
*/
|
|
115
|
+
export declare function mergePatch(state: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
116
|
+
/**
|
|
117
|
+
* Coerce a tool response into text: strings pass through, plain objects
|
|
118
|
+
* expose their `content` then `text` string field, other objects are
|
|
119
|
+
* JSON-stringified, null/undefined becomes `""`, everything else is
|
|
120
|
+
* `String()`-ed.
|
|
121
|
+
*/
|
|
122
|
+
export declare function readResponseText(response: unknown): string;
|
|
123
|
+
/** Outcome of the patch extractors: a patch, an invalid attempt, or nothing. */
|
|
124
|
+
export type PatchLookup = {
|
|
125
|
+
patch: Record<string, unknown>;
|
|
126
|
+
} | {
|
|
127
|
+
invalid: true;
|
|
128
|
+
} | {
|
|
129
|
+
absent: true;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Look for a fenced ```json block: `{ patch }` when it parses and carries
|
|
133
|
+
* an object-shaped `state_patch`, `{ invalid: true }` when a block exists
|
|
134
|
+
* but is malformed (an open fence that never closes — truncated output —
|
|
135
|
+
* is still a patch attempt and classifies as invalid), `{ absent: true }`
|
|
136
|
+
* when there is no block at all.
|
|
137
|
+
*/
|
|
138
|
+
export declare function findFencedPatch(text: string): PatchLookup;
|
|
139
|
+
/**
|
|
140
|
+
* Fallback: a raw JSON object with `state_patch` anywhere in the text
|
|
141
|
+
* (first `{` … last `}`). Ordinary JSON output without a `state_patch`
|
|
142
|
+
* key is simply not a patch (`{ absent: true }`); a `state_patch` key
|
|
143
|
+
* holding a non-object is an invalid attempt.
|
|
144
|
+
*/
|
|
145
|
+
export declare function findRawPatch(text: string): PatchLookup;
|
|
146
|
+
/**
|
|
147
|
+
* Read the session-meta sidecar's status (`<dir>/.session-meta.json`,
|
|
148
|
+
* sibling of the state file). Plain JS — inlined into the generated
|
|
149
|
+
* SessionStart hooks via `fn.toString()`. Returns the `status` string for
|
|
150
|
+
* a readable object-shaped sidecar, else `null` (missing/corrupt/other
|
|
151
|
+
* shape — no lifecycle marker). The `readFile` dependency is injected at
|
|
152
|
+
* the call site (real `node:fs` in hooks, mocks in tests).
|
|
153
|
+
*/
|
|
154
|
+
export declare function readSessionMetaStatus(metaPath: string, readFile: (p: string) => string): string | null;
|
|
155
|
+
/**
|
|
156
|
+
* Assemble the CJS snippet embedded into every generated hook script:
|
|
157
|
+
* all sibling functions of this module, source-verbatim via
|
|
158
|
+
* `fn.toString()`. The adapters splice this block after their `require`
|
|
159
|
+
* preamble, which keeps the embedded logic byte-identical across hosts
|
|
160
|
+
* and impossible to drift from this module.
|
|
161
|
+
*/
|
|
162
|
+
export declare function hookRuntimeSnippet(): string;
|
|
163
|
+
//# sourceMappingURL=hook-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-runtime.d.ts","sourceRoot":"","sources":["../src/hook-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAGpE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA+B3F;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC9B,OAAO,CAaT;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAC3C,IAAI,CAEN;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAW1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,OAAO,GAAG,OAAO,CA8C7F;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAQ1D;AAED,gFAAgF;AAChF,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAClC;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAczD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA4BtD;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC9B,MAAM,GAAG,IAAI,CAUf;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAkB3C"}
|