wicked-crew 0.5.0 → 0.7.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.
Files changed (167) hide show
  1. package/dist/api/audit.d.ts +70 -0
  2. package/dist/api/audit.d.ts.map +1 -0
  3. package/dist/api/audit.js +131 -0
  4. package/dist/api/audit.js.map +1 -0
  5. package/dist/api/auth.d.ts +192 -0
  6. package/dist/api/auth.d.ts.map +1 -0
  7. package/dist/api/auth.js +515 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/gate-cache.d.ts +8 -0
  10. package/dist/api/gate-cache.d.ts.map +1 -1
  11. package/dist/api/gate-cache.js +10 -0
  12. package/dist/api/gate-cache.js.map +1 -1
  13. package/dist/api/guidance-index.d.ts +39 -0
  14. package/dist/api/guidance-index.d.ts.map +1 -0
  15. package/dist/api/guidance-index.js +67 -0
  16. package/dist/api/guidance-index.js.map +1 -0
  17. package/dist/api/open-path.d.ts +34 -0
  18. package/dist/api/open-path.d.ts.map +1 -0
  19. package/dist/api/open-path.js +101 -0
  20. package/dist/api/open-path.js.map +1 -0
  21. package/dist/api/retry-index.d.ts +30 -0
  22. package/dist/api/retry-index.d.ts.map +1 -0
  23. package/dist/api/retry-index.js +45 -0
  24. package/dist/api/retry-index.js.map +1 -0
  25. package/dist/api/routes.d.ts +164 -1
  26. package/dist/api/routes.d.ts.map +1 -1
  27. package/dist/api/routes.js +769 -21
  28. package/dist/api/routes.js.map +1 -1
  29. package/dist/api/run-files.d.ts +63 -0
  30. package/dist/api/run-files.d.ts.map +1 -0
  31. package/dist/api/run-files.js +271 -0
  32. package/dist/api/run-files.js.map +1 -0
  33. package/dist/api/seat-health.d.ts +55 -0
  34. package/dist/api/seat-health.d.ts.map +1 -0
  35. package/dist/api/seat-health.js +273 -0
  36. package/dist/api/seat-health.js.map +1 -0
  37. package/dist/api/seat-signin.d.ts +27 -0
  38. package/dist/api/seat-signin.d.ts.map +1 -0
  39. package/dist/api/seat-signin.js +143 -0
  40. package/dist/api/seat-signin.js.map +1 -0
  41. package/dist/api/server.d.ts +190 -3
  42. package/dist/api/server.d.ts.map +1 -1
  43. package/dist/api/server.js +365 -11
  44. package/dist/api/server.js.map +1 -1
  45. package/dist/api/stall-watchdog.d.ts +62 -0
  46. package/dist/api/stall-watchdog.d.ts.map +1 -0
  47. package/dist/api/stall-watchdog.js +138 -0
  48. package/dist/api/stall-watchdog.js.map +1 -0
  49. package/dist/cli/index.js +171 -12
  50. package/dist/cli/index.js.map +1 -1
  51. package/dist/cli/mcp.d.ts +14 -0
  52. package/dist/cli/mcp.d.ts.map +1 -0
  53. package/dist/cli/mcp.js +119 -0
  54. package/dist/cli/mcp.js.map +1 -0
  55. package/dist/core/adapter.d.ts +87 -10
  56. package/dist/core/adapter.d.ts.map +1 -1
  57. package/dist/core/adapter.js +347 -28
  58. package/dist/core/adapter.js.map +1 -1
  59. package/dist/core/bridge-reaper.d.ts +134 -0
  60. package/dist/core/bridge-reaper.d.ts.map +1 -0
  61. package/dist/core/bridge-reaper.js +286 -0
  62. package/dist/core/bridge-reaper.js.map +1 -0
  63. package/dist/core/deliver.d.ts +118 -0
  64. package/dist/core/deliver.d.ts.map +1 -0
  65. package/dist/core/deliver.js +241 -0
  66. package/dist/core/deliver.js.map +1 -0
  67. package/dist/core/deliverable-floor.d.ts +103 -0
  68. package/dist/core/deliverable-floor.d.ts.map +1 -0
  69. package/dist/core/deliverable-floor.js +173 -0
  70. package/dist/core/deliverable-floor.js.map +1 -0
  71. package/dist/core/exec.d.ts +2 -0
  72. package/dist/core/exec.d.ts.map +1 -1
  73. package/dist/core/exec.js.map +1 -1
  74. package/dist/core/types.d.ts +92 -352
  75. package/dist/core/types.d.ts.map +1 -1
  76. package/dist/core/types.js +14 -4
  77. package/dist/core/types.js.map +1 -1
  78. package/dist/interactive/bridge-pool.d.ts +99 -0
  79. package/dist/interactive/bridge-pool.d.ts.map +1 -0
  80. package/dist/interactive/bridge-pool.js +244 -0
  81. package/dist/interactive/bridge-pool.js.map +1 -0
  82. package/dist/interactive/bridge-root.d.ts +36 -0
  83. package/dist/interactive/bridge-root.d.ts.map +1 -0
  84. package/dist/interactive/bridge-root.js +48 -0
  85. package/dist/interactive/bridge-root.js.map +1 -0
  86. package/dist/interactive/chat-events.d.ts +207 -0
  87. package/dist/interactive/chat-events.d.ts.map +1 -0
  88. package/dist/interactive/chat-events.js +769 -0
  89. package/dist/interactive/chat-events.js.map +1 -0
  90. package/dist/interactive/demo-events.d.ts +283 -0
  91. package/dist/interactive/demo-events.d.ts.map +1 -0
  92. package/dist/interactive/demo-events.js +889 -0
  93. package/dist/interactive/demo-events.js.map +1 -0
  94. package/dist/interactive/draft-events.d.ts +224 -0
  95. package/dist/interactive/draft-events.d.ts.map +1 -0
  96. package/dist/interactive/draft-events.js +793 -0
  97. package/dist/interactive/draft-events.js.map +1 -0
  98. package/dist/interactive/edit-events.d.ts +194 -0
  99. package/dist/interactive/edit-events.d.ts.map +1 -0
  100. package/dist/interactive/edit-events.js +601 -0
  101. package/dist/interactive/edit-events.js.map +1 -0
  102. package/dist/interactive/ledger.d.ts +39 -0
  103. package/dist/interactive/ledger.d.ts.map +1 -0
  104. package/dist/interactive/ledger.js +93 -0
  105. package/dist/interactive/ledger.js.map +1 -0
  106. package/dist/interactive/proxy-routes.d.ts +39 -0
  107. package/dist/interactive/proxy-routes.d.ts.map +1 -0
  108. package/dist/interactive/proxy-routes.js +189 -0
  109. package/dist/interactive/proxy-routes.js.map +1 -0
  110. package/dist/interactive/repo-snapshot.d.ts +100 -0
  111. package/dist/interactive/repo-snapshot.d.ts.map +1 -0
  112. package/dist/interactive/repo-snapshot.js +289 -0
  113. package/dist/interactive/repo-snapshot.js.map +1 -0
  114. package/dist/interactive/ws-relay.d.ts +85 -0
  115. package/dist/interactive/ws-relay.d.ts.map +1 -0
  116. package/dist/interactive/ws-relay.js +191 -0
  117. package/dist/interactive/ws-relay.js.map +1 -0
  118. package/dist/projects/activity.d.ts +29 -0
  119. package/dist/projects/activity.d.ts.map +1 -0
  120. package/dist/projects/activity.js +172 -0
  121. package/dist/projects/activity.js.map +1 -0
  122. package/dist/projects/charter.d.ts +28 -0
  123. package/dist/projects/charter.d.ts.map +1 -0
  124. package/dist/projects/charter.js +53 -0
  125. package/dist/projects/charter.js.map +1 -0
  126. package/dist/projects/events.d.ts +55 -0
  127. package/dist/projects/events.d.ts.map +1 -0
  128. package/dist/projects/events.js +141 -0
  129. package/dist/projects/events.js.map +1 -0
  130. package/dist/projects/graph-paths.d.ts +92 -0
  131. package/dist/projects/graph-paths.d.ts.map +1 -0
  132. package/dist/projects/graph-paths.js +130 -0
  133. package/dist/projects/graph-paths.js.map +1 -0
  134. package/dist/projects/graph.d.ts +179 -0
  135. package/dist/projects/graph.d.ts.map +1 -0
  136. package/dist/projects/graph.js +775 -0
  137. package/dist/projects/graph.js.map +1 -0
  138. package/dist/projects/membership-index.d.ts +25 -0
  139. package/dist/projects/membership-index.d.ts.map +1 -0
  140. package/dist/projects/membership-index.js +46 -0
  141. package/dist/projects/membership-index.js.map +1 -0
  142. package/dist/projects/routes.d.ts +97 -0
  143. package/dist/projects/routes.d.ts.map +1 -0
  144. package/dist/projects/routes.js +510 -0
  145. package/dist/projects/routes.js.map +1 -0
  146. package/dist/projects/settings.d.ts +32 -0
  147. package/dist/projects/settings.d.ts.map +1 -0
  148. package/dist/projects/settings.js +64 -0
  149. package/dist/projects/settings.js.map +1 -0
  150. package/dist/qe/acceptance.d.ts +137 -0
  151. package/dist/qe/acceptance.d.ts.map +1 -0
  152. package/dist/qe/acceptance.js +249 -0
  153. package/dist/qe/acceptance.js.map +1 -0
  154. package/dist/qe/gate-events.d.ts +111 -0
  155. package/dist/qe/gate-events.d.ts.map +1 -0
  156. package/dist/qe/gate-events.js +168 -0
  157. package/dist/qe/gate-events.js.map +1 -0
  158. package/dist/qe/ledger.d.ts +100 -0
  159. package/dist/qe/ledger.d.ts.map +1 -0
  160. package/dist/qe/ledger.js +154 -0
  161. package/dist/qe/ledger.js.map +1 -0
  162. package/dist/studio/assets/index-8p8uwCxG.js +530 -0
  163. package/dist/studio/assets/index-D6S9zUtO.css +32 -0
  164. package/dist/studio/index.html +5 -3
  165. package/package.json +10 -4
  166. package/dist/studio/assets/index-DaaUU8Ep.css +0 -32
  167. package/dist/studio/assets/index-Fu5DRC00.js +0 -423
@@ -0,0 +1,271 @@
1
+ // GET /runs/:id/files + GET /runs/:id/diff machinery (DES-FEEDBACK-002 CREW-1).
2
+ //
3
+ // The studio's in-app file & diff viewer cannot read the daemon host's filesystem, so the daemon
4
+ // serves the bytes — which is exactly why every path is contained FIRST against the same root set
5
+ // `POST /open` validates (`allowedRootsFor` + `isInsideRoot`, open-path.ts), every payload is
6
+ // capped, and git runs through `execCapped` with argv arrays only (never a shell string) and
7
+ // `--no-ext-diff` (a configured external diff driver must never execute here). Both routes are
8
+ // read-only by construction: `fs` reads and `git diff`/`git status` — zero write capability, a
9
+ // strictly smaller threat surface than `/open` handing the path to an OS opener.
10
+ import { promises as fsp } from 'node:fs';
11
+ import { execCapped, ExecOutputTooLarge } from '../core/exec.js';
12
+ /** File-content cap (DES-FEEDBACK-002 §3.3): past this, `content` holds the first 512 KB and
13
+ * `truncated: true` — the studio renders a labeled truncation banner, never a silent amputation. */
14
+ export const FILE_CONTENT_CAP_BYTES = 512 * 1024;
15
+ /** Binary sniff window: a NUL byte in the first 8 KB ⇒ `binary: true`, `content: ""` — the studio
16
+ * falls back to the external-open affordance rather than rendering mojibake. */
17
+ export const BINARY_SNIFF_BYTES = 8 * 1024;
18
+ /** Diff output cap (§3.3): past this the diff is cut at 1 MB and `truncated: true`. */
19
+ export const DIFF_OUTPUT_CAP_BYTES = 1024 * 1024;
20
+ /** The target exists but is not a regular file (directory, socket, …) — the route's 400, distinct
21
+ * from ENOENT's 404. Serving directory listings is explicitly out of scope (§3.3). */
22
+ export class NotARegularFileError extends Error {
23
+ path;
24
+ constructor(path) {
25
+ super(`not a regular file: ${path}`);
26
+ this.path = path;
27
+ this.name = 'NotARegularFileError';
28
+ }
29
+ }
30
+ /**
31
+ * Read at most `FILE_CONTENT_CAP_BYTES` of `target`. The open-then-fstat order (not stat-then-open)
32
+ * closes the classic swap race: the size and the bytes come from the SAME open file description.
33
+ * Callers must have contained `target` already — this function does filesystem work only.
34
+ */
35
+ export async function readFileCapped(target) {
36
+ const fh = await fsp.open(target, 'r');
37
+ try {
38
+ const st = await fh.stat();
39
+ if (!st.isFile())
40
+ throw new NotARegularFileError(target);
41
+ const size = st.size;
42
+ const toRead = Math.min(size, FILE_CONTENT_CAP_BYTES);
43
+ const buf = Buffer.alloc(toRead);
44
+ let off = 0;
45
+ while (off < toRead) {
46
+ const { bytesRead } = await fh.read(buf, off, toRead - off, off);
47
+ if (bytesRead === 0)
48
+ break; // file shrank underneath us — serve what exists
49
+ off += bytesRead;
50
+ }
51
+ const got = buf.subarray(0, off);
52
+ if (got.subarray(0, Math.min(off, BINARY_SNIFF_BYTES)).includes(0)) {
53
+ return { content: '', size, truncated: false, binary: true };
54
+ }
55
+ return { content: got.toString('utf8'), size, truncated: size > FILE_CONTENT_CAP_BYTES, binary: false };
56
+ }
57
+ finally {
58
+ await fh.close();
59
+ }
60
+ }
61
+ const GIT_TIMEOUT_MS = 10_000;
62
+ // ── Diff base (DES-UX-001 §8.1, CREW-UX-1) ─────────────────────────────────────────────────────
63
+ /** `?base=merge-base` — the one non-ref literal: diff against `git merge-base <default-branch>
64
+ * HEAD`, i.e. the run branch's fork point, so committed run work is visible (§8.1). */
65
+ export const MERGE_BASE_LITERAL = 'merge-base';
66
+ /** `base` is not a plain ref — a flag, a path, a revision range, a separator. Rejected BEFORE any
67
+ * git process sees it: the query parameter is a diff baseline, never a command surface (§8.1). */
68
+ export class InvalidDiffBaseError extends Error {
69
+ base;
70
+ constructor(base) {
71
+ super('`base` must be a plain git ref (branch, tag, or commit id) — ' +
72
+ 'flags, paths, revision ranges, and separators are rejected');
73
+ this.base = base;
74
+ this.name = 'InvalidDiffBaseError';
75
+ }
76
+ }
77
+ /** `base` is well-formed but does not resolve to a commit inside the run's repo — containment:
78
+ * a ref from another repo (or thin air) is a 400, never a git error surfaced as a 500. */
79
+ export class UnresolvableDiffBaseError extends Error {
80
+ base;
81
+ constructor(base, detail) {
82
+ super(`unresolvable \`base\`: ${detail}`);
83
+ this.base = base;
84
+ this.name = 'UnresolvableDiffBaseError';
85
+ }
86
+ }
87
+ /** A plain ref: git's own charset for the safe subset — alphanumeric start (excludes `-flag`,
88
+ * `.hidden`, `/abs/path`), then word/dot/slash/dash. `..` (ranges AND traversal), `~`/`^`/`@{}`
89
+ * (rev operators), `:`, whitespace, and `;` are all outside the charset or explicitly banned. */
90
+ const PLAIN_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/;
91
+ export function isPlainRef(base) {
92
+ return PLAIN_REF_RE.test(base) && !base.includes('..') && !base.endsWith('/');
93
+ }
94
+ /** One contained git read: argv array (never a shell string), capped, cwd-scoped to the run's
95
+ * workdir. Returns trimmed stdout, or `null` ONLY when git ran to completion and answered "no"
96
+ * (unresolvable ref, no merge base, …) — a plain non-zero exit. Operational failures are never
97
+ * `null`: ENOENT (git missing), "not a git repository", a timed-out/killed process (`killed`/
98
+ * `signal` set), and output-cap overflow all rethrow, so they surface as the route's 500 rather
99
+ * than masquerading as a 400 "unresolvable base" (Copilot, #307). */
100
+ async function gitQuery(workdir, args) {
101
+ try {
102
+ const { stdout } = await execCapped('git', args, { timeout: GIT_TIMEOUT_MS, cwd: workdir });
103
+ return stdout.trim();
104
+ }
105
+ catch (err) {
106
+ if (err.code === 'ENOENT')
107
+ throw err;
108
+ if (err instanceof ExecOutputTooLarge)
109
+ throw err;
110
+ const proc = err;
111
+ if (proc.killed === true || (proc.signal !== undefined && proc.signal !== null))
112
+ throw err;
113
+ const msg = err instanceof Error ? err.message : String(err);
114
+ if (msg.toLowerCase().includes('not a git repository'))
115
+ throw err;
116
+ return null;
117
+ }
118
+ }
119
+ /** SHA shapes rev-parse may emit (SHA-1 today, SHA-256 repos exist). Anything else is treated as
120
+ * "did not resolve" — the resolved value is what reaches `git diff`, never the caller's text. */
121
+ const COMMIT_SHA_RE = /^[0-9a-f]{40,64}$/;
122
+ /**
123
+ * Resolve `?base=` to a commit SHA inside the run's repo, or throw the route's named 400s.
124
+ * Two accepted spellings (§8.1): the literal `merge-base` → `git merge-base <default-branch>
125
+ * HEAD`; or a plain ref that `git rev-parse --verify` resolves IN THIS repo. Everything git
126
+ * ultimately diffs against is the resolved SHA — the raw parameter never reaches `git diff`,
127
+ * and `--end-of-options` keeps even a validated ref from ever parsing as a flag.
128
+ */
129
+ async function resolveDiffBase(workdir, base) {
130
+ if (base === MERGE_BASE_LITERAL) {
131
+ // Default branch: origin's HEAD when the repo has one, else local main/master.
132
+ let branch = null;
133
+ const symref = await gitQuery(workdir, ['symbolic-ref', '--quiet', 'refs/remotes/origin/HEAD']);
134
+ if (symref !== null && symref.startsWith('refs/remotes/')) {
135
+ branch = symref.slice('refs/remotes/'.length);
136
+ }
137
+ else {
138
+ for (const cand of ['main', 'master']) {
139
+ if ((await gitQuery(workdir, ['rev-parse', '--verify', `refs/heads/${cand}`])) !== null) {
140
+ branch = cand;
141
+ break;
142
+ }
143
+ }
144
+ }
145
+ if (branch === null) {
146
+ throw new UnresolvableDiffBaseError(base, 'no default branch found in this repo to merge-base against');
147
+ }
148
+ const sha = await gitQuery(workdir, ['merge-base', '--end-of-options', branch, 'HEAD']);
149
+ if (sha === null || !COMMIT_SHA_RE.test(sha)) {
150
+ throw new UnresolvableDiffBaseError(base, `no merge base between ${branch} and HEAD`);
151
+ }
152
+ return sha;
153
+ }
154
+ if (!isPlainRef(base))
155
+ throw new InvalidDiffBaseError(base);
156
+ const sha = await gitQuery(workdir, [
157
+ 'rev-parse',
158
+ '--verify',
159
+ '--end-of-options',
160
+ `${base}^{commit}`,
161
+ ]);
162
+ if (sha === null || !COMMIT_SHA_RE.test(sha)) {
163
+ throw new UnresolvableDiffBaseError(base, `ref does not resolve inside the run's repo: ${base}`);
164
+ }
165
+ return sha;
166
+ }
167
+ /** Git's ways of saying "there is nothing here to diff" — a real answer (`diff: ""`), not an
168
+ * error. Mirrors the git-history route's tolerance (routes.ts): a workdir that is not a repo, or
169
+ * a repo with no commits yet (`HEAD` unresolvable — every file is untracked and the untracked
170
+ * pass still runs for the commit-less case). */
171
+ function isEmptyDiffAnswer(msg) {
172
+ // Case-insensitive on the repo check: `git log` says "fatal: not a git repository" but
173
+ // `git diff` says "warning: Not a git repository. Use --no-index …" — measured, not assumed.
174
+ const lower = msg.toLowerCase();
175
+ return (lower.includes('not a git repository') ||
176
+ msg.includes('does not have any commits') ||
177
+ msg.includes("ambiguous argument 'HEAD'") ||
178
+ msg.includes('unknown revision'));
179
+ }
180
+ /**
181
+ * The run's worktree diff against `base` (default HEAD) — staged + unstaged in one answer — with
182
+ * untracked files appended as `git diff --no-index /dev/null <file>` all-addition hunks (§3.3: a
183
+ * run's *created* files must diff as additions rather than vanish). `relPath`, when given, narrows
184
+ * both passes to that one file; it is always a single argv element after `--` (the open-path rule
185
+ * — no shell string is ever composed). The literal `/dev/null` is git's own cross-platform
186
+ * null-device spelling (git special-cases it, including on Windows).
187
+ *
188
+ * `base` (CREW-UX-1, DES-UX-001 §8.1): the literal `merge-base` diffs from the run branch's fork
189
+ * point off the default branch — committed run work becomes visible; a plain ref diffs from that
190
+ * ref, resolved inside this repo first (`resolveDiffBase` — the raw parameter never reaches
191
+ * `git diff`). Throws `InvalidDiffBaseError` / `UnresolvableDiffBaseError` for the route's 400s.
192
+ */
193
+ export async function worktreeDiff(workdir, relPath, base) {
194
+ const limit = relPath === undefined ? [] : ['--', relPath];
195
+ let out = '';
196
+ // Resolve the baseline BEFORE any diff pass: a bad `base` must 400 without partial output.
197
+ let baseRev = 'HEAD';
198
+ if (base !== undefined && base.length > 0) {
199
+ try {
200
+ baseRev = await resolveDiffBase(workdir, base);
201
+ }
202
+ catch (err) {
203
+ if (err.code === 'ENOENT')
204
+ throw err; // git missing — 500
205
+ const msg = err instanceof Error ? err.message : String(err);
206
+ // The route's standing tolerance: a non-repo workdir answers an empty diff for any
207
+ // WELL-FORMED base — a malformed base has already thrown InvalidDiffBaseError above,
208
+ // and that 400 wins over the tolerance (Copilot, #307).
209
+ if (msg.toLowerCase().includes('not a git repository'))
210
+ return { diff: '', truncated: false };
211
+ throw err;
212
+ }
213
+ }
214
+ // Tracked changes (staged + unstaged, plus committed-since-base when a base was resolved):
215
+ // diff the worktree against the baseline.
216
+ try {
217
+ const { stdout } = await execCapped('git', ['diff', '--no-color', '--no-ext-diff', baseRev, ...limit], { timeout: GIT_TIMEOUT_MS, cwd: workdir });
218
+ out = stdout;
219
+ }
220
+ catch (err) {
221
+ if (err.code === 'ENOENT')
222
+ throw err; // git missing — the route's 500
223
+ const msg = err instanceof Error ? err.message : String(err);
224
+ if (msg.toLowerCase().includes('not a git repository'))
225
+ return { diff: '', truncated: false };
226
+ if (!isEmptyDiffAnswer(msg))
227
+ throw err;
228
+ // No commits yet: tracked diff is vacuously empty; fall through to the untracked pass.
229
+ }
230
+ // Untracked files → all-addition hunks. `-z` (NUL separators, no quoting) + `-uall` (individual
231
+ // files, never a collapsed `?? dir/` entry that --no-index could not diff).
232
+ const { stdout: statusOut } = await execCapped('git', ['status', '--porcelain', '-z', '-uall', ...limit], { timeout: GIT_TIMEOUT_MS, cwd: workdir });
233
+ const untracked = statusOut
234
+ .split('\0')
235
+ .filter((entry) => entry.startsWith('?? '))
236
+ .map((entry) => entry.slice(3));
237
+ for (const file of untracked) {
238
+ // Cap is in BYTES; `out.length` counts UTF-16 code units. `>=`: exactly-at-cap is
239
+ // already done — spawning one more git only to discard its output is waste (Copilot, #305).
240
+ if (Buffer.byteLength(out, 'utf8') >= DIFF_OUTPUT_CAP_BYTES)
241
+ break;
242
+ try {
243
+ const { stdout } = await execCapped('git', ['diff', '--no-color', '--no-ext-diff', '--no-index', '--', '/dev/null', file], { timeout: GIT_TIMEOUT_MS, cwd: workdir });
244
+ out += stdout;
245
+ }
246
+ catch (err) {
247
+ // `--no-index` exits 1 WHEN THE FILES DIFFER — which is every hit here. The diff is still
248
+ // on stdout; only a stdout-less failure is a real error.
249
+ const stdout = err.stdout;
250
+ if (typeof stdout === 'string' && stdout.length > 0)
251
+ out += stdout;
252
+ else if (err.code === 'ENOENT')
253
+ throw err;
254
+ else
255
+ throw err;
256
+ }
257
+ }
258
+ // Byte-accurate cap (Copilot, #305): `slice` on the string counts UTF-16 code units, which
259
+ // under-enforces for non-ASCII diffs. Cut on the UTF-8 byte buffer, then back off any
260
+ // incomplete trailing multibyte sequence — decoding a torn tail yields U+FFFD, which
261
+ // re-encodes LARGER and would breach the cap by a byte.
262
+ const outBytes = Buffer.from(out, 'utf8');
263
+ if (outBytes.byteLength > DIFF_OUTPUT_CAP_BYTES) {
264
+ let end = DIFF_OUTPUT_CAP_BYTES;
265
+ while (end > 0 && (outBytes[end] & 0xc0) === 0x80)
266
+ end--; // land on a sequence boundary
267
+ return { diff: outBytes.subarray(0, end).toString('utf8'), truncated: true };
268
+ }
269
+ return { diff: out, truncated: false };
270
+ }
271
+ //# sourceMappingURL=run-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-files.js","sourceRoot":"","sources":["../../src/api/run-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { CoreEvent, SeatHealth } from '../core/types.js';
2
+ export type { SeatHealth };
3
+ /** Rolling window for the repeated-acpFallback rule. */
4
+ export declare const FALLBACK_WINDOW_MS: number;
5
+ /** Fallbacks within the window that flip a seat inactive. */
6
+ export declare const FALLBACK_THRESHOLD = 3;
7
+ /**
8
+ * In-memory per-seat health, folded from live CoreEvents. In-memory ON PURPOSE: health is a
9
+ * statement about the running platform's ability to reach a CLI right now — a daemon restart
10
+ * genuinely does not know, and "assume active, let the next failure/probe speak" is the honest
11
+ * default (and the one the declarative-roster doctrine implies).
12
+ */
13
+ export declare class SeatHealthTracker {
14
+ private readonly entries;
15
+ /** `${session}:${ord}` → cli key. `unitOutputCaptured` carries no seat; this is the correlation. */
16
+ private readonly assignments;
17
+ /** cli key → recent failure-fallback timestamps (epoch ms), pruned to the rolling window. */
18
+ private readonly fallbacks;
19
+ /** Default `since` for seats that have never changed state. */
20
+ private readonly startedAt;
21
+ /** Fold one CoreEvent into the map. Safe on every event type; unknown types are ignored. */
22
+ ingest(event: CoreEvent): void;
23
+ /** Flip a seat ACTIVE: message cleared, `since` updated on a genuine transition. */
24
+ markActive(key: string, atMs?: number): void;
25
+ /** Flip a seat INACTIVE with the error excerpt; `since` survives while already inactive. */
26
+ markInactive(key: string, message: string, atMs?: number): void;
27
+ /** The seat's health — a seat never seen in an event is ACTIVE with no message (the default). */
28
+ healthFor(key: string): SeatHealth;
29
+ }
30
+ /** The roster slice the probe needs (a `RosterSeat` satisfies it structurally). */
31
+ export interface ProbeSeat {
32
+ key: string;
33
+ /** The seat's cheap liveness command, e.g. `["claude", "--version"]`. */
34
+ version_probe?: unknown;
35
+ [k: string]: unknown;
36
+ }
37
+ export interface SeatProbeOptions {
38
+ /** Probe cadence. Default 10 minutes — a recovery path, not a monitor. */
39
+ intervalMs?: number;
40
+ /** Per-probe timeout. Default 5s; a probe that hangs counts as still-down. */
41
+ timeoutMs?: number;
42
+ /** Injectable prober (tests). Resolve `true` ⇔ the command exited 0. */
43
+ runProbe?: (argv: string[], timeoutMs: number) => Promise<boolean>;
44
+ log?: (m: string) => void;
45
+ }
46
+ /**
47
+ * The low-frequency recovery probe (crew#274 §3): every `intervalMs`, for INACTIVE seats only,
48
+ * run the seat's `version_probe`; exit 0 flips the seat active with the message cleared. Quota
49
+ * resets and re-logins are thus detected without operator action. Callers own the guard against
50
+ * probing in tests (the daemon arms this from `createServer`, env-gated).
51
+ */
52
+ export declare function startSeatHealthProbe(tracker: SeatHealthTracker, roster: () => ProbeSeat[], opts?: SeatProbeOptions): {
53
+ stop(): void;
54
+ };
55
+ //# sourceMappingURL=seat-health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-health.d.ts","sourceRoot":"","sources":["../../src/api/seat-health.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9D,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AACjD,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAiCpC;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,oGAAoG;IACpG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IACzD,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,+DAA+D;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IAEtD,4FAA4F;IAC5F,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAoG9B,oFAAoF;IACpF,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,SAAa,GAAG,IAAI;IAUhD,4FAA4F;IAC5F,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,SAAa,GAAG,IAAI;IAWnE,iGAAiG;IACjG,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU;CAGnC;AAED,mFAAmF;AACnF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3B;AAcD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,MAAM,SAAS,EAAE,EACzB,IAAI,GAAE,gBAAqB,GAC1B;IAAE,IAAI,IAAI,IAAI,CAAA;CAAE,CAmDlB"}
@@ -0,0 +1,273 @@
1
+ // Seat health (crew#274): runtime health per council seat, folded from the live CoreEvent stream.
2
+ //
3
+ // The operator doctrine this implements (recorded in ~/.config/wicked-council/clis.toml): every
4
+ // CLI seat stays LISTED and ENABLED in config — quota/auth/runtime errors are RUNTIME state the
5
+ // platform detects and DISPLAYS (`inactive` + the error excerpt), never a hand-edited disable.
6
+ // The old pattern left stale disables behind: quotas that had long reset, and an "out of credits"
7
+ // comment masking what had become a `401 Unauthorized` needing a re-login.
8
+ //
9
+ // The fold (one rule per event, shared by every caller — the gate-cache posture):
10
+ //
11
+ // - `stepFailed` marks the seat INACTIVE when the failure is seat-level: `failureKind:
12
+ // "workerError"` (the CLI process itself failed — crew#277 asks exactly this stamp), or a
13
+ // `detail` naming the wrapped runner's "(cli `x` exited N)" message, an ACP "timeout waiting",
14
+ // or an auth/quota string (401/unauthorized/quota/rate-limit/credits). The seat is read from
15
+ // the detail when it names one, else from the unit's `unitDistributed` assignment.
16
+ // - `acpFallback` is NOT alone inactive — `session_died` falls back to single-shot and the unit
17
+ // can still succeed — but REPEATED fallback (3+ in 10 minutes) is a seat that cannot hold a
18
+ // session, and that is. `governance_requires_wrapped` is deliberate routing, not a failure,
19
+ // and never counts.
20
+ // - `unitOutputCaptured` with `stepStatus: "ok"` marks the assigned seat ACTIVE again and clears
21
+ // the message (the event carries no seat, so `unitDistributed`/`unitReassigned` are folded
22
+ // into a per-unit assignment map for the correlation).
23
+ //
24
+ // Recovery without an ok output comes from `startSeatHealthProbe`: every `intervalMs` (default
25
+ // 10 minutes), INACTIVE seats only, run the seat's `version_probe` command with a short timeout;
26
+ // exit 0 flips the seat active. Active seats are never probed — this is a recovery path, not a
27
+ // monitor.
28
+ import { execCapped } from '../core/exec.js';
29
+ /** Rolling window for the repeated-acpFallback rule. */
30
+ export const FALLBACK_WINDOW_MS = 10 * 60 * 1000;
31
+ /** Fallbacks within the window that flip a seat inactive. */
32
+ export const FALLBACK_THRESHOLD = 3;
33
+ /** Health messages are operator-facing chips, not transcripts — bound them hard. */
34
+ const EXCERPT_MAX = 240;
35
+ function excerpt(s) {
36
+ const flat = s.replace(/\s+/g, ' ').trim();
37
+ return flat.length <= EXCERPT_MAX ? flat : `${flat.slice(0, EXCERPT_MAX - 1)}…`;
38
+ }
39
+ /** The wrapped runner's seat-naming failure message: "(cli `x` exited N) …" (execute_wrapped.rs). */
40
+ const CLI_IN_DETAIL = /\(cli `([^`]+)` exited /;
41
+ /**
42
+ * Seat-level failure signatures in a `stepFailed` detail. Deliberately narrow: a unit that failed
43
+ * its WORK (missing deliverable, gate veto) says nothing about the seat's health, so only strings
44
+ * that name the CLI process, its transport, or its account state qualify.
45
+ */
46
+ const SEAT_FAILURE_PATTERNS = [
47
+ CLI_IN_DETAIL, // non-zero exit, seat named by the runner itself
48
+ /timeout waiting/i, // ACP "timeout waiting for response id=…" (acp_runner.rs)
49
+ /\b401\b|\bunauthorized\b/i, // auth: needs a re-login
50
+ /\bquota\b|rate.?limit|too many requests|\b429\b/i, // quota/rate ceiling
51
+ /\bout of credits\b|\binsufficient credits\b/i, // account balance
52
+ ];
53
+ /** `acpFallback` kinds that are deliberate routing rather than a failure — never counted. */
54
+ const BENIGN_FALLBACK_KINDS = new Set(['governance_requires_wrapped']);
55
+ function str(v) {
56
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
57
+ }
58
+ /**
59
+ * In-memory per-seat health, folded from live CoreEvents. In-memory ON PURPOSE: health is a
60
+ * statement about the running platform's ability to reach a CLI right now — a daemon restart
61
+ * genuinely does not know, and "assume active, let the next failure/probe speak" is the honest
62
+ * default (and the one the declarative-roster doctrine implies).
63
+ */
64
+ export class SeatHealthTracker {
65
+ entries = new Map();
66
+ /** `${session}:${ord}` → cli key. `unitOutputCaptured` carries no seat; this is the correlation. */
67
+ assignments = new Map();
68
+ /** cli key → recent failure-fallback timestamps (epoch ms), pruned to the rolling window. */
69
+ fallbacks = new Map();
70
+ /** Default `since` for seats that have never changed state. */
71
+ startedAt = new Date().toISOString();
72
+ /** Fold one CoreEvent into the map. Safe on every event type; unknown types are ignored. */
73
+ ingest(event) {
74
+ // Replayed log entries carry the engine's capture-time `ts`; live frames don't, and for
75
+ // those "now" IS the observation time (the gate-cache's observedAt rule).
76
+ const at = typeof event.ts === 'number' ? event.ts : Date.now();
77
+ const session = str(event.session);
78
+ const ord = typeof event.ord === 'number' ? event.ord : undefined;
79
+ switch (event.type) {
80
+ case 'unitDistributed': {
81
+ const cli = str(event.cli);
82
+ if (session !== undefined && ord !== undefined && cli !== undefined) {
83
+ this.assignments.set(`${session}:${ord}`, cli);
84
+ }
85
+ return;
86
+ }
87
+ case 'unitReassigned': {
88
+ // `newCli: null` means the council re-convenes — the follow-up unitDistributed will set
89
+ // the new seat. DROP the stale assignment meanwhile: leaving it in place would attribute
90
+ // the interregnum's events to the seat that was just taken off the unit (Copilot, #279).
91
+ const newCli = str(event.newCli);
92
+ if (session !== undefined && ord !== undefined && newCli === undefined) {
93
+ this.assignments.delete(`${session}:${ord}`);
94
+ }
95
+ if (session !== undefined && ord !== undefined && newCli !== undefined) {
96
+ this.assignments.set(`${session}:${ord}`, newCli);
97
+ }
98
+ return;
99
+ }
100
+ case 'unitOutputCaptured': {
101
+ if (event.stepStatus !== 'ok' || session === undefined || ord === undefined)
102
+ return;
103
+ const seat = this.assignments.get(`${session}:${ord}`);
104
+ if (seat !== undefined)
105
+ this.markActive(seat, at);
106
+ return;
107
+ }
108
+ case 'stepFailed': {
109
+ const detail = typeof event.detail === 'string' ? event.detail : '';
110
+ const failureKind = str(event.failureKind);
111
+ // The detail names the seat when the wrapped runner produced it; otherwise fall back to
112
+ // the unit's assignment (a workerError detail is the CLI's own output and rarely does).
113
+ const named = CLI_IN_DETAIL.exec(detail)?.[1];
114
+ const assigned = session !== undefined && ord !== undefined
115
+ ? this.assignments.get(`${session}:${ord}`)
116
+ : undefined;
117
+ const seat = named ?? assigned;
118
+ if (seat === undefined)
119
+ return;
120
+ const seatLevel = failureKind === 'workerError' || SEAT_FAILURE_PATTERNS.some((re) => re.test(detail));
121
+ if (seatLevel) {
122
+ const msg = excerpt(detail) || `seat failure (${failureKind ?? 'unreported'})`;
123
+ this.markInactive(seat, msg, at);
124
+ }
125
+ return;
126
+ }
127
+ case 'acpFallback': {
128
+ const cliKey = str(event.cliKey);
129
+ const fallbackKind = str(event.fallbackKind);
130
+ if (cliKey === undefined)
131
+ return;
132
+ if (fallbackKind !== undefined && BENIGN_FALLBACK_KINDS.has(fallbackKind))
133
+ return;
134
+ const fresh = (this.fallbacks.get(cliKey) ?? []).filter((t) => at - t < FALLBACK_WINDOW_MS);
135
+ fresh.push(at);
136
+ this.fallbacks.set(cliKey, fresh);
137
+ if (fresh.length >= FALLBACK_THRESHOLD) {
138
+ const reason = str(event.reason) ?? fallbackKind ?? 'unknown';
139
+ this.markInactive(cliKey, `repeated ACP fallback (${fresh.length} in 10 min): ${reason}`, at);
140
+ }
141
+ else {
142
+ // One fallback is not inactive (session death falls back and the unit can still work),
143
+ // but it IS an observed error — stamp lastErrorAt without flipping the status.
144
+ const prev = this.entries.get(cliKey);
145
+ this.entries.set(cliKey, {
146
+ status: prev?.status ?? 'active',
147
+ ...(prev?.message !== undefined ? { message: prev.message } : {}),
148
+ since: prev?.since ?? this.startedAt,
149
+ lastErrorAt: new Date(at).toISOString(),
150
+ });
151
+ }
152
+ return;
153
+ }
154
+ // A finished run's assignments can never activate/deactivate anyone again — drop them.
155
+ case 'sessionCompleted':
156
+ case 'sessionFailed':
157
+ case 'runCancelled': {
158
+ if (session === undefined)
159
+ return;
160
+ const prefix = `${session}:`;
161
+ for (const key of [...this.assignments.keys()]) {
162
+ if (key.startsWith(prefix))
163
+ this.assignments.delete(key);
164
+ }
165
+ return;
166
+ }
167
+ default:
168
+ return;
169
+ }
170
+ }
171
+ /** Flip a seat ACTIVE: message cleared, `since` updated on a genuine transition. */
172
+ markActive(key, atMs = Date.now()) {
173
+ const prev = this.entries.get(key);
174
+ this.entries.set(key, {
175
+ status: 'active',
176
+ since: prev?.status === 'active' ? prev.since : new Date(atMs).toISOString(),
177
+ ...(prev?.lastErrorAt !== undefined ? { lastErrorAt: prev.lastErrorAt } : {}),
178
+ });
179
+ this.fallbacks.delete(key); // an ok output resets the repeated-fallback window too
180
+ }
181
+ /** Flip a seat INACTIVE with the error excerpt; `since` survives while already inactive. */
182
+ markInactive(key, message, atMs = Date.now()) {
183
+ const prev = this.entries.get(key);
184
+ const iso = new Date(atMs).toISOString();
185
+ this.entries.set(key, {
186
+ status: 'inactive',
187
+ message: excerpt(message),
188
+ since: prev?.status === 'inactive' ? prev.since : iso,
189
+ lastErrorAt: iso,
190
+ });
191
+ }
192
+ /** The seat's health — a seat never seen in an event is ACTIVE with no message (the default). */
193
+ healthFor(key) {
194
+ return this.entries.get(key) ?? { status: 'active', since: this.startedAt };
195
+ }
196
+ }
197
+ /** Default prober: run the version command; exit 0 → true, anything else (incl. timeout) → false. */
198
+ async function defaultRunProbe(argv, timeoutMs) {
199
+ const [file, ...args] = argv;
200
+ if (file === undefined)
201
+ return false;
202
+ try {
203
+ await execCapped(file, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 });
204
+ return true;
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ /**
211
+ * The low-frequency recovery probe (crew#274 §3): every `intervalMs`, for INACTIVE seats only,
212
+ * run the seat's `version_probe`; exit 0 flips the seat active with the message cleared. Quota
213
+ * resets and re-logins are thus detected without operator action. Callers own the guard against
214
+ * probing in tests (the daemon arms this from `createServer`, env-gated).
215
+ */
216
+ export function startSeatHealthProbe(tracker, roster, opts = {}) {
217
+ const intervalMs = opts.intervalMs ?? FALLBACK_WINDOW_MS; // 10 min
218
+ const timeoutMs = opts.timeoutMs ?? 5_000;
219
+ const runProbe = opts.runProbe ?? defaultRunProbe;
220
+ const log = opts.log ?? (() => undefined);
221
+ let inFlight = false;
222
+ const tick = async () => {
223
+ if (inFlight)
224
+ return; // a slow round must never stack onto the next interval
225
+ inFlight = true;
226
+ try {
227
+ let seats;
228
+ try {
229
+ seats = roster();
230
+ }
231
+ catch (err) {
232
+ log(`[seat-health] roster read failed, skipping probe round: ${String(err)}`);
233
+ return;
234
+ }
235
+ for (const seat of seats) {
236
+ const key = str(seat.key);
237
+ if (key === undefined)
238
+ continue;
239
+ if (tracker.healthFor(key).status !== 'inactive')
240
+ continue; // recovery only
241
+ const probe = seat.version_probe;
242
+ const argv = Array.isArray(probe)
243
+ ? probe.filter((s) => typeof s === 'string' && s.length > 0)
244
+ : [];
245
+ if (argv.length === 0)
246
+ continue; // unprobeable seat: only an ok output can recover it
247
+ try {
248
+ if (await runProbe(argv, timeoutMs)) {
249
+ tracker.markActive(key);
250
+ log(`[seat-health] '${key}' recovered (version probe exited 0)`);
251
+ }
252
+ }
253
+ catch {
254
+ /* a throwing prober reads as still-down */
255
+ }
256
+ }
257
+ }
258
+ finally {
259
+ inFlight = false;
260
+ }
261
+ };
262
+ const handle = setInterval(() => {
263
+ void tick();
264
+ }, intervalMs);
265
+ // Never hold the process open for a probe — the daemon's server lifecycle owns shutdown.
266
+ handle.unref?.();
267
+ return {
268
+ stop() {
269
+ clearInterval(handle);
270
+ },
271
+ };
272
+ }
273
+ //# sourceMappingURL=seat-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-health.js","sourceRoot":"","sources":["../../src/api/seat-health.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,EAAE;AACF,gGAAgG;AAChG,gGAAgG;AAChG,+FAA+F;AAC/F,kGAAkG;AAClG,2EAA2E;AAC3E,EAAE;AACF,kFAAkF;AAClF,EAAE;AACF,uFAAuF;AACvF,4FAA4F;AAC5F,iGAAiG;AACjG,+FAA+F;AAC/F,qFAAqF;AACrF,gGAAgG;AAChG,8FAA8F;AAC9F,8FAA8F;AAC9F,sBAAsB;AACtB,iGAAiG;AACjG,6FAA6F;AAC7F,yDAAyD;AACzD,EAAE;AACF,+FAA+F;AAC/F,iGAAiG;AACjG,+FAA+F;AAC/F,WAAW;AAGX,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C,wDAAwD;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACjD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAEpC,oFAAoF;AACpF,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,SAAS,OAAO,CAAC,CAAS;IACxB,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,OAAO,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC;AAClF,CAAC;AAED,qGAAqG;AACrG,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAEhD;;;;GAIG;AACH,MAAM,qBAAqB,GAAa;IACtC,aAAa,EAAE,iDAAiD;IAChE,kBAAkB,EAAE,0DAA0D;IAC9E,2BAA2B,EAAE,yBAAyB;IACtD,kDAAkD,EAAE,qBAAqB;IACzE,8CAA8C,EAAE,kBAAkB;CACnE,CAAC;AAEF,6FAA6F;AAC7F,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC;AAEvE,SAAS,GAAG,CAAC,CAAU;IACrB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IACX,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;IACzD,oGAAoG;IACnF,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzD,6FAA6F;IAC5E,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IACzD,+DAA+D;IAC9C,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEtD,4FAA4F;IAC5F,MAAM,CAAC,KAAgB;QACrB,wFAAwF;QACxF,0EAA0E;QAC1E,MAAM,EAAE,GAAG,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QAChE,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;QAElE,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,iBAAiB,CAAC,CAAC,CAAC;gBACvB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC3B,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACpE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;gBACjD,CAAC;gBACD,OAAO;YACT,CAAC;YACD,KAAK,gBAAgB,CAAC,CAAC,CAAC;gBACtB,wFAAwF;gBACxF,yFAAyF;gBACzF,yFAAyF;gBACzF,MAAM,MAAM,GAAG,GAAG,CAAE,KAA8B,CAAC,MAAM,CAAC,CAAC;gBAC3D,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACvE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;gBAC/C,CAAC;gBACD,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;oBACvE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;gBACpD,CAAC;gBACD,OAAO;YACT,CAAC;YACD,KAAK,oBAAoB,CAAC,CAAC,CAAC;gBAC1B,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;oBAAE,OAAO;gBACpF,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;gBACvD,IAAI,IAAI,KAAK,SAAS;oBAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YACD,KAAK,YAAY,CAAC,CAAC,CAAC;gBAClB,MAAM,MAAM,GAAG,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBACpE,MAAM,WAAW,GAAG,GAAG,CAAE,KAAmC,CAAC,WAAW,CAAC,CAAC;gBAC1E,wFAAwF;gBACxF,wFAAwF;gBACxF,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAC9C,MAAM,QAAQ,GACZ,OAAO,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS;oBACxC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,OAAO,IAAI,GAAG,EAAE,CAAC;oBAC3C,CAAC,CAAC,SAAS,CAAC;gBAChB,MAAM,IAAI,GAAG,KAAK,IAAI,QAAQ,CAAC;gBAC/B,IAAI,IAAI,KAAK,SAAS;oBAAE,OAAO;gBAC/B,MAAM,SAAS,GACb,WAAW,KAAK,aAAa,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;gBACvF,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,iBAAiB,WAAW,IAAI,YAAY,GAAG,CAAC;oBAC/E,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;gBACnC,CAAC;gBACD,OAAO;YACT,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,MAAM,MAAM,GAAG,GAAG,CAAE,KAA8B,CAAC,MAAM,CAAC,CAAC;gBAC3D,MAAM,YAAY,GAAG,GAAG,CAAE,KAAoC,CAAC,YAAY,CAAC,CAAC;gBAC7E,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO;gBACjC,IAAI,YAAY,KAAK,SAAS,IAAI,qBAAqB,CAAC,GAAG,CAAC,YAAY,CAAC;oBAAE,OAAO;gBAClF,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CACrD,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,kBAAkB,CACnC,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACf,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBAClC,IAAI,KAAK,CAAC,MAAM,IAAI,kBAAkB,EAAE,CAAC;oBACvC,MAAM,MAAM,GAAG,GAAG,CAAE,KAA8B,CAAC,MAAM,CAAC,IAAI,YAAY,IAAI,SAAS,CAAC;oBACxF,IAAI,CAAC,YAAY,CACf,MAAM,EACN,0BAA0B,KAAK,CAAC,MAAM,gBAAgB,MAAM,EAAE,EAC9D,EAAE,CACH,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,uFAAuF;oBACvF,+EAA+E;oBAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;wBACvB,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,QAAQ;wBAChC,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACjE,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,SAAS;wBACpC,WAAW,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE;qBACxC,CAAC,CAAC;gBACL,CAAC;gBACD,OAAO;YACT,CAAC;YACD,uFAAuF;YACvF,KAAK,kBAAkB,CAAC;YACxB,KAAK,eAAe,CAAC;YACrB,KAAK,cAAc,CAAC,CAAC,CAAC;gBACpB,IAAI,OAAO,KAAK,SAAS;oBAAE,OAAO;gBAClC,MAAM,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC;gBAC7B,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;oBAC/C,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;wBAAE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC3D,CAAC;gBACD,OAAO;YACT,CAAC;YACD;gBACE,OAAO;QACX,CAAC;IACH,CAAC;IAED,oFAAoF;IACpF,UAAU,CAAC,GAAW,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;YACpB,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,IAAI,EAAE,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE;YAC5E,GAAG,CAAC,IAAI,EAAE,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9E,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,uDAAuD;IACrF,CAAC;IAED,4FAA4F;IAC5F,YAAY,CAAC,GAAW,EAAE,OAAe,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;YACpB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;YACzB,KAAK,EAAE,IAAI,EAAE,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG;YACrD,WAAW,EAAE,GAAG;SACjB,CAAC,CAAC;IACL,CAAC;IAED,iGAAiG;IACjG,SAAS,CAAC,GAAW;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;IAC9E,CAAC;CACF;AAoBD,qGAAqG;AACrG,KAAK,UAAU,eAAe,CAAC,IAAc,EAAE,SAAiB;IAC9D,MAAM,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;QAC7E,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA0B,EAC1B,MAAyB,EACzB,OAAyB,EAAE;IAE3B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,kBAAkB,CAAC,CAAC,SAAS;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,eAAe,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAS,EAAE,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,IAAI,QAAQ;YAAE,OAAO,CAAC,uDAAuD;QAC7E,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC;YACH,IAAI,KAAkB,CAAC;YACvB,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,EAAE,CAAC;YACnB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,2DAA2D,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC9E,OAAO;YACT,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC1B,IAAI,GAAG,KAAK,SAAS;oBAAE,SAAS;gBAChC,IAAI,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,UAAU;oBAAE,SAAS,CAAC,gBAAgB;gBAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC;gBACjC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;oBAC/B,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;oBACzE,CAAC,CAAC,EAAE,CAAC;gBACP,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS,CAAC,qDAAqD;gBACtF,IAAI,CAAC;oBACH,IAAI,MAAM,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;wBACpC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;wBACxB,GAAG,CAAC,kBAAkB,GAAG,sCAAsC,CAAC,CAAC;oBACnE,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,2CAA2C;gBAC7C,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;QAC9B,KAAK,IAAI,EAAE,CAAC;IACd,CAAC,EAAE,UAAU,CAAC,CAAC;IACf,yFAAyF;IACzF,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;IACjB,OAAO;QACL,IAAI;YACF,aAAa,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;KACF,CAAC;AACJ,CAAC"}