@lannguyensi/harness 0.50.0 → 0.52.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/CHANGELOG.md +247 -0
- package/dist/cli/approve/understanding.d.ts +97 -0
- package/dist/cli/approve/understanding.js +67 -24
- package/dist/cli/approve/understanding.js.map +1 -1
- package/dist/cli/audit.d.ts +29 -0
- package/dist/cli/audit.js +130 -17
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/delegate/index.d.ts +63 -0
- package/dist/cli/delegate/index.js +313 -0
- package/dist/cli/delegate/index.js.map +1 -0
- package/dist/cli/doctor/auto-approve-mode.d.ts +37 -0
- package/dist/cli/doctor/auto-approve-mode.js +80 -0
- package/dist/cli/doctor/auto-approve-mode.js.map +1 -0
- package/dist/cli/doctor/claude-mcp.d.ts +14 -0
- package/dist/cli/doctor/claude-mcp.js +7 -1
- package/dist/cli/doctor/claude-mcp.js.map +1 -1
- package/dist/cli/doctor/codex-config-drift.d.ts +28 -0
- package/dist/cli/doctor/codex-config-drift.js +240 -0
- package/dist/cli/doctor/codex-config-drift.js.map +1 -0
- package/dist/cli/doctor/format.js +67 -1
- package/dist/cli/doctor/format.js.map +1 -1
- package/dist/cli/doctor/index.d.ts +20 -0
- package/dist/cli/doctor/index.js +84 -1
- package/dist/cli/doctor/index.js.map +1 -1
- package/dist/cli/doctor/settings-drift.d.ts +25 -0
- package/dist/cli/doctor/settings-drift.js +203 -0
- package/dist/cli/doctor/settings-drift.js.map +1 -0
- package/dist/cli/doctor/types.d.ts +69 -0
- package/dist/cli/doctor/types.js.map +1 -1
- package/dist/cli/doctor/ug-auto-approvals.d.ts +40 -0
- package/dist/cli/doctor/ug-auto-approvals.js +116 -0
- package/dist/cli/doctor/ug-auto-approvals.js.map +1 -0
- package/dist/cli/doctor/ug-delegations.d.ts +26 -0
- package/dist/cli/doctor/ug-delegations.js +112 -0
- package/dist/cli/doctor/ug-delegations.js.map +1 -0
- package/dist/cli/doctor/understanding-mode-env.d.ts +12 -0
- package/dist/cli/doctor/understanding-mode-env.js +16 -1
- package/dist/cli/doctor/understanding-mode-env.js.map +1 -1
- package/dist/cli/index.js +100 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/pack/approve-escape.js +16 -7
- package/dist/cli/pack/approve-escape.js.map +1 -1
- package/dist/cli/pack/auto-approve-path.d.ts +135 -0
- package/dist/cli/pack/auto-approve-path.js +400 -0
- package/dist/cli/pack/auto-approve-path.js.map +1 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.d.ts +9 -0
- package/dist/cli/pack/hook-codex-pre-tool-use.js +118 -4
- package/dist/cli/pack/hook-codex-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/hook-pre-tool-use.d.ts +36 -0
- package/dist/cli/pack/hook-pre-tool-use.js +402 -3
- package/dist/cli/pack/hook-pre-tool-use.js.map +1 -1
- package/dist/cli/pack/transcript-report-scan.d.ts +71 -0
- package/dist/cli/pack/transcript-report-scan.js +359 -0
- package/dist/cli/pack/transcript-report-scan.js.map +1 -0
- package/dist/cli/smoke/index.d.ts +18 -1
- package/dist/cli/smoke/index.js +54 -0
- package/dist/cli/smoke/index.js.map +1 -1
- package/dist/cli/validate/checks.js +81 -0
- package/dist/cli/validate/checks.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.d.ts +210 -0
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js +361 -0
- package/dist/policy-packs/builtin/understanding-before-execution/auto-approve.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.d.ts +223 -0
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js +633 -0
- package/dist/policy-packs/builtin/understanding-before-execution/delegation-markers.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/index.d.ts +3 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js +10 -1
- package/dist/policy-packs/builtin/understanding-before-execution/index.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.d.ts +29 -0
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js +82 -0
- package/dist/policy-packs/builtin/understanding-before-execution/measured-permission-modes.js.map +1 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.d.ts +23 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js +29 -0
- package/dist/policy-packs/builtin/understanding-before-execution/persisted-reports.js.map +1 -1
- package/dist/policy-packs/builtin/understanding-before-execution.d.ts +42 -0
- package/dist/policy-packs/builtin/understanding-before-execution.js +56 -1
- package/dist/policy-packs/builtin/understanding-before-execution.js.map +1 -1
- package/dist/runtime/approval-signing.d.ts +22 -0
- package/dist/runtime/approval-signing.js +83 -1
- package/dist/runtime/approval-signing.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export interface TranscriptReportScanArgs {
|
|
2
|
+
/** The `transcript_path` from the hook payload. The ONLY file this scan opens. */
|
|
3
|
+
transcriptPath: string;
|
|
4
|
+
/** The session being decided; an entry's own `sessionId` must equal this. */
|
|
5
|
+
sessionId: string;
|
|
6
|
+
/** Bound of the poll, from `auto_approve.report_scan.max_wait`. */
|
|
7
|
+
maxWaitMs: number;
|
|
8
|
+
/** Re-read interval. */
|
|
9
|
+
pollMs?: number;
|
|
10
|
+
/** Injectable clock (epoch ms). */
|
|
11
|
+
now?: () => number;
|
|
12
|
+
/** Injectable sleep, so tests never wait for real time. */
|
|
13
|
+
sleep?: (ms: number) => Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Entry ids (see {@link TranscriptReportScanResult}'s `entryId`) this
|
|
16
|
+
* session has ALREADY adopted. A hit whose id is in here is skipped as
|
|
17
|
+
* if it were not a report at all, and a transcript that carries nothing
|
|
18
|
+
* else times out with `adoptedOnly: true`. Absent means "nothing
|
|
19
|
+
* adopted yet", the first-call case.
|
|
20
|
+
*/
|
|
21
|
+
adopted?: ReadonlySet<string>;
|
|
22
|
+
}
|
|
23
|
+
export type TranscriptReportScanResult = {
|
|
24
|
+
found: true;
|
|
25
|
+
/** The report markdown, from its heading line to the end of the text block that carried it. */
|
|
26
|
+
markdown: string;
|
|
27
|
+
/** Zero-based index of the JSONL line the report was found on. */
|
|
28
|
+
lineIndex: number;
|
|
29
|
+
/**
|
|
30
|
+
* Stable identity of the adopted entry, for the caller's
|
|
31
|
+
* once-per-session adoption ledger: the entry's own `uuid` when it
|
|
32
|
+
* carries a usable one, otherwise a digest of the adopted markdown
|
|
33
|
+
* and its line index. Never contains a newline, so the caller can
|
|
34
|
+
* store one id per line.
|
|
35
|
+
*/
|
|
36
|
+
entryId: string;
|
|
37
|
+
waitedMs: number;
|
|
38
|
+
} | {
|
|
39
|
+
found: false;
|
|
40
|
+
/**
|
|
41
|
+
* `timeout`: the bound elapsed with no eligible report (this
|
|
42
|
+
* includes a transcript file that never appeared, since the file
|
|
43
|
+
* may still be created while the poll runs).
|
|
44
|
+
* `unreadable`: the file IS there and could not be read (a
|
|
45
|
+
* permission error, a directory at that path, a symlink loop).
|
|
46
|
+
*/
|
|
47
|
+
reason: "timeout" | "unreadable";
|
|
48
|
+
/**
|
|
49
|
+
* Set when the ONLY report(s) in the transcript were entries this
|
|
50
|
+
* session had already adopted. The outcome is the same block, but
|
|
51
|
+
* the caller can say so: the child must emit a FRESH report, not
|
|
52
|
+
* retry against the one it already spent.
|
|
53
|
+
*/
|
|
54
|
+
adoptedOnly?: true;
|
|
55
|
+
waitedMs: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Poll the payload's own transcript for this session's Understanding
|
|
59
|
+
* Report until it appears or `maxWaitMs` elapses. See the module header
|
|
60
|
+
* for the filters and why each one is there.
|
|
61
|
+
*
|
|
62
|
+
* The poll timer is deliberately NOT `unref`'d. A hook process whose
|
|
63
|
+
* stdin has been drained has no other ref'd handle, so an unref'd timer
|
|
64
|
+
* lets Node exit mid-poll: the process would end with an empty stdout,
|
|
65
|
+
* which Claude Code reads as ALLOW: a silent fail-open on precisely the
|
|
66
|
+
* delegation path this scan exists for. The loop is bounded twice over
|
|
67
|
+
* (`maxWaitMs`, itself capped by the config's 5 s ceiling, and the
|
|
68
|
+
* `maxPolls` count below), so it can never hold the process open beyond
|
|
69
|
+
* the bound.
|
|
70
|
+
*/
|
|
71
|
+
export declare function scanTranscriptForReport(args: TranscriptReportScanArgs): Promise<TranscriptReportScanResult>;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
// Session-scoped, role-aware transcript scan for the child's own
|
|
2
|
+
// Understanding Report: slice 3 of
|
|
3
|
+
// docs/decisions/2026-08-27-ug-auto-mode-approval.md ("Report capture
|
|
4
|
+
// under `-p`", agent-tasks 37ad0b05).
|
|
5
|
+
//
|
|
6
|
+
// WHY THIS EXISTS. Under `claude -p` there is no operator to run
|
|
7
|
+
// `harness approve understanding`, and the Stop hook fires only at the
|
|
8
|
+
// end of the run, so neither channel can supply key two (the child's own
|
|
9
|
+
// report) at the instant the first gated tool call is decided. The
|
|
10
|
+
// session transcript is the remaining channel, and it LAGS: at the
|
|
11
|
+
// PreToolUse instant the report the agent just emitted is not in the
|
|
12
|
+
// JSONL yet; it lands tens of milliseconds later (measured, see
|
|
13
|
+
// `docs/okf/understanding-gate-auto-mode-signals.md` and
|
|
14
|
+
// `dogfood/ug-auto-mode-signals/`). This module is the bounded poll the
|
|
15
|
+
// ADR chose over a lone block-and-retry: it waits for the flush itself,
|
|
16
|
+
// for at most `auto_approve.report_scan.max_wait`, and then gives up.
|
|
17
|
+
//
|
|
18
|
+
// FAIL CLOSED. There is no branch here that turns an absent report into
|
|
19
|
+
// anything but `found: false`. The caller blocks on that; the timeout is
|
|
20
|
+
// a block, never an allow.
|
|
21
|
+
//
|
|
22
|
+
// WHAT IT WILL NOT DO, AND WHY EACH REFUSAL IS LOAD-BEARING:
|
|
23
|
+
//
|
|
24
|
+
// - It opens exactly ONE file, the `transcript_path` the payload names,
|
|
25
|
+
// and never a directory. A directory scan could reach another
|
|
26
|
+
// session's transcript, and (per the ADR's subagent measurement) the
|
|
27
|
+
// separate `agent-<agent_id>.jsonl` a subagent's turns live in.
|
|
28
|
+
// - Only `type: "assistant"` entries count. A report the agent quoted
|
|
29
|
+
// back inside a user message is the PROMPT's text, not the model's
|
|
30
|
+
// own report, and adopting it would let the launcher's prompt supply
|
|
31
|
+
// key two.
|
|
32
|
+
// - Only entries whose own `sessionId` equals the session being decided
|
|
33
|
+
// count. The payload names the file, but a file can hold entries from
|
|
34
|
+
// more than one session id (a resumed / forked transcript), and an
|
|
35
|
+
// entry stamped with a foreign id is not this session's report.
|
|
36
|
+
// - `isSidechain: true` entries are excluded. The measured shape never
|
|
37
|
+
// puts one in the payload's own transcript (subagent turns live in a
|
|
38
|
+
// separate file), so this is defence in depth against a shape the
|
|
39
|
+
// measurement did not produce, not the mechanism the separation
|
|
40
|
+
// relies on.
|
|
41
|
+
// - EACH TRANSCRIPT ENTRY IS ADOPTED AT MOST ONCE PER SESSION: the
|
|
42
|
+
// caller passes the entry ids it has already spent (`adopted`) and
|
|
43
|
+
// this scan returns the newest NON-adopted hit, so one report cannot
|
|
44
|
+
// mint a second marker after the first one expired or a task boundary
|
|
45
|
+
// cleared it. Without that rule the delegation's TTL would silently
|
|
46
|
+
// replace the marker's, which is the lifetime the ADR's two-key
|
|
47
|
+
// design deliberately keeps short.
|
|
48
|
+
//
|
|
49
|
+
// HOW IT READS. The first poll reads the whole file; every later poll
|
|
50
|
+
// reads only the bytes appended since the previous one, carrying a
|
|
51
|
+
// partial trailing line over, and keeps the best hit found so far. A
|
|
52
|
+
// transcript that SHRANK between polls (truncated or replaced by a
|
|
53
|
+
// shorter file), or whose underlying file identity changed (inode/device,
|
|
54
|
+
// from `fs.fstatSync`, even when the new file is the same size or LARGER,
|
|
55
|
+
// as in a rename swapping in a different file in place), resets the
|
|
56
|
+
// reader and it is re-read from the top. Size alone cannot catch the
|
|
57
|
+
// second case: a same-size-or-larger replacement passes the size check
|
|
58
|
+
// but its bytes at the old offset are not a continuation of anything
|
|
59
|
+
// this reader has seen.
|
|
60
|
+
//
|
|
61
|
+
// The clock and the sleep are injected so the tests drive the poll
|
|
62
|
+
// deterministically and never sleep for real.
|
|
63
|
+
import * as fs from "node:fs";
|
|
64
|
+
import { sha256Hex } from "../../runtime/approval-signing.js";
|
|
65
|
+
/**
|
|
66
|
+
* The report heading the scan looks for, anchored to a whole line.
|
|
67
|
+
*
|
|
68
|
+
* ANY heading level, `#` through `######`: the deny text the child is
|
|
69
|
+
* given (`renderReportSchemaHint`) says "any heading level (#, ##, ###)"
|
|
70
|
+
* and shows a `##` example, so a scan that accepted only `#` would refuse
|
|
71
|
+
* exactly the shape its own instruction asked for.
|
|
72
|
+
*
|
|
73
|
+
* Deliberately `[ \t]` rather than `\s` around the words: `\s` matches
|
|
74
|
+
* newlines, so `^#\s*Understanding Report\s*$` under the `m` flag would
|
|
75
|
+
* also match a bare `#` on one line followed by the words on the next,
|
|
76
|
+
* which is not a heading. Case-insensitive on the words themselves (an
|
|
77
|
+
* agent that writes `# UNDERSTANDING REPORT` has still emitted the
|
|
78
|
+
* section the deny text asked for); a trailing `\r` is tolerated for a
|
|
79
|
+
* CRLF-written transcript.
|
|
80
|
+
*/
|
|
81
|
+
const REPORT_HEADING = /^#{1,6}[ \t]*understanding[ \t]+report[ \t]*\r?$/i;
|
|
82
|
+
/**
|
|
83
|
+
* The text blocks of one entry's `message.content`, in order. A string
|
|
84
|
+
* `content` counts as exactly one text block; in the array shape only
|
|
85
|
+
* `{ type: "text", text: <string> }` blocks contribute, so `tool_use` and
|
|
86
|
+
* `thinking` blocks are ignored (a report the model only ever "thought"
|
|
87
|
+
* is not a report it emitted).
|
|
88
|
+
*/
|
|
89
|
+
function textBlocksOf(entry) {
|
|
90
|
+
const message = entry.message;
|
|
91
|
+
if (message === null || typeof message !== "object" || Array.isArray(message))
|
|
92
|
+
return [];
|
|
93
|
+
const content = message.content;
|
|
94
|
+
if (typeof content === "string")
|
|
95
|
+
return [content];
|
|
96
|
+
if (!Array.isArray(content))
|
|
97
|
+
return [];
|
|
98
|
+
const blocks = [];
|
|
99
|
+
for (const block of content) {
|
|
100
|
+
if (block === null || typeof block !== "object" || Array.isArray(block))
|
|
101
|
+
continue;
|
|
102
|
+
const b = block;
|
|
103
|
+
if (b.type !== "text")
|
|
104
|
+
continue;
|
|
105
|
+
if (typeof b.text !== "string")
|
|
106
|
+
continue;
|
|
107
|
+
blocks.push(b.text);
|
|
108
|
+
}
|
|
109
|
+
return blocks;
|
|
110
|
+
}
|
|
111
|
+
/** The report markdown inside one text block, or null when it carries no heading. */
|
|
112
|
+
function reportInBlock(block) {
|
|
113
|
+
const lines = block.split("\n");
|
|
114
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
115
|
+
if (REPORT_HEADING.test(lines[i])) {
|
|
116
|
+
return lines.slice(i).join("\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/** Longest `uuid` accepted verbatim as an adoption id; anything longer falls back to the digest. */
|
|
122
|
+
const ENTRY_UUID_MAX_LENGTH = 200;
|
|
123
|
+
/**
|
|
124
|
+
* Stable identity of one adopted entry. The transcript's own `uuid` is
|
|
125
|
+
* preferred (Claude Code stamps one on every entry, and it survives the
|
|
126
|
+
* file being rewritten or resumed); a value that is missing, empty,
|
|
127
|
+
* over-long, or carries a line break (all of which would corrupt the
|
|
128
|
+
* caller's one-id-per-line ledger) falls back to a digest of the adopted
|
|
129
|
+
* markdown plus its line index. The two forms are namespaced so a crafted
|
|
130
|
+
* `uuid` cannot impersonate a digest id.
|
|
131
|
+
*/
|
|
132
|
+
function entryIdFor(entry, markdown, lineIndex) {
|
|
133
|
+
const uuid = entry.uuid;
|
|
134
|
+
if (typeof uuid === "string" &&
|
|
135
|
+
uuid.length > 0 &&
|
|
136
|
+
uuid.length <= ENTRY_UUID_MAX_LENGTH &&
|
|
137
|
+
!/[\r\n]/.test(uuid)) {
|
|
138
|
+
return `uuid:${uuid}`;
|
|
139
|
+
}
|
|
140
|
+
return `sha256:${sha256Hex(`${lineIndex}\n${markdown}`)}`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The eligible report on ONE JSONL line, or null. Unparseable and blank
|
|
144
|
+
* lines yield null rather than aborting the scan: a transcript being
|
|
145
|
+
* written while it is read can end in a half-flushed line, and refusing
|
|
146
|
+
* the whole file for that would turn a routine race into a block.
|
|
147
|
+
*/
|
|
148
|
+
function reportHitInLine(line, lineIndex, sessionId) {
|
|
149
|
+
const trimmed = line.trim();
|
|
150
|
+
if (trimmed.length === 0)
|
|
151
|
+
return null;
|
|
152
|
+
let entry;
|
|
153
|
+
try {
|
|
154
|
+
entry = JSON.parse(trimmed);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
160
|
+
return null;
|
|
161
|
+
if (entry.type !== "assistant")
|
|
162
|
+
return null;
|
|
163
|
+
if (entry.isSidechain === true)
|
|
164
|
+
return null;
|
|
165
|
+
if (entry.sessionId !== sessionId)
|
|
166
|
+
return null;
|
|
167
|
+
const blocks = textBlocksOf(entry);
|
|
168
|
+
for (let b = blocks.length - 1; b >= 0; b -= 1) {
|
|
169
|
+
const markdown = reportInBlock(blocks[b]);
|
|
170
|
+
if (markdown !== null) {
|
|
171
|
+
return { markdown, lineIndex, entryId: entryIdFor(entry, markdown, lineIndex) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const DEFAULT_POLL_MS = 50;
|
|
177
|
+
const NEWLINE = 0x0a;
|
|
178
|
+
const CLOSING_BRACE = 0x7d;
|
|
179
|
+
const EMPTY_ADOPTED = new Set();
|
|
180
|
+
function newScanState() {
|
|
181
|
+
return {
|
|
182
|
+
offset: 0,
|
|
183
|
+
carry: Buffer.alloc(0),
|
|
184
|
+
lineIndex: 0,
|
|
185
|
+
best: null,
|
|
186
|
+
tentative: null,
|
|
187
|
+
adoptedSkipped: false,
|
|
188
|
+
ino: null,
|
|
189
|
+
dev: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/** A hit the caller may still take: skips (and records) an already-adopted one. */
|
|
193
|
+
function eligible(hit, adopted, state) {
|
|
194
|
+
if (hit === null)
|
|
195
|
+
return null;
|
|
196
|
+
if (adopted.has(hit.entryId)) {
|
|
197
|
+
state.adoptedSkipped = true;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return hit;
|
|
201
|
+
}
|
|
202
|
+
/** Read `length` bytes from `offset`, tolerating a short `readSync`. */
|
|
203
|
+
function readAt(fd, offset, length) {
|
|
204
|
+
const buf = Buffer.allocUnsafe(length);
|
|
205
|
+
let read = 0;
|
|
206
|
+
while (read < length) {
|
|
207
|
+
const n = fs.readSync(fd, buf, read, length - read, offset + read);
|
|
208
|
+
if (n <= 0)
|
|
209
|
+
break;
|
|
210
|
+
read += n;
|
|
211
|
+
}
|
|
212
|
+
return read === length ? buf : buf.subarray(0, read);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The trailing, newline-less bytes as a PROVISIONAL hit for this poll
|
|
216
|
+
* only. A writer that has flushed a complete JSONL entry but not yet its
|
|
217
|
+
* newline is the one case the append-only reader would otherwise miss
|
|
218
|
+
* until the next flush, and the whole-file reader this replaced did see
|
|
219
|
+
* it. Only attempted when the carry ends in `}`, which both keeps the
|
|
220
|
+
* cost off every poll's partial line and guarantees the decoded bytes end
|
|
221
|
+
* on a character boundary.
|
|
222
|
+
*/
|
|
223
|
+
function scanCarry(state, sessionId, adopted) {
|
|
224
|
+
const carry = state.carry;
|
|
225
|
+
if (carry.length === 0 || carry[carry.length - 1] !== CLOSING_BRACE)
|
|
226
|
+
return;
|
|
227
|
+
state.tentative = eligible(reportHitInLine(carry.toString("utf8"), state.lineIndex, sessionId), adopted, state);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Consume everything appended to the transcript since the previous poll,
|
|
231
|
+
* updating `state` in place.
|
|
232
|
+
*
|
|
233
|
+
* ENOENT is "not there YET": under `-p` the hook can fire before the
|
|
234
|
+
* transcript file exists, and it may appear inside the bound. Every other
|
|
235
|
+
* failure, and anything at that path that is not a regular file, means
|
|
236
|
+
* something IS there and cannot be read as a transcript, which is a
|
|
237
|
+
* distinct, immediately-reported condition rather than something waiting
|
|
238
|
+
* will fix.
|
|
239
|
+
*/
|
|
240
|
+
function pollTranscript(transcriptPath, state, sessionId, adopted) {
|
|
241
|
+
// Provisional only for the poll that produced it: a file that vanished
|
|
242
|
+
// (or shrank) between polls must not leave a hit behind.
|
|
243
|
+
state.tentative = null;
|
|
244
|
+
let fd;
|
|
245
|
+
try {
|
|
246
|
+
fd = fs.openSync(transcriptPath, "r");
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
if (err.code === "ENOENT")
|
|
250
|
+
return "absent";
|
|
251
|
+
return "unreadable";
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const stat = fs.fstatSync(fd);
|
|
255
|
+
if (!stat.isFile())
|
|
256
|
+
return "unreadable";
|
|
257
|
+
// Identity change: the file at this path was replaced (rename, or a
|
|
258
|
+
// recreate) between polls. Checked BEFORE the size comparison and
|
|
259
|
+
// independently of it, because a replacement can be the same size or
|
|
260
|
+
// larger, which the size check alone would read straight through as
|
|
261
|
+
// ordinary growth.
|
|
262
|
+
const identityChanged = state.ino !== null && (stat.ino !== state.ino || stat.dev !== state.dev);
|
|
263
|
+
if (identityChanged || stat.size < state.offset) {
|
|
264
|
+
// The file shrank, or was replaced in place: truncated, or a
|
|
265
|
+
// different file now sits at this path. The bytes the offset
|
|
266
|
+
// pointed past are stale (shrink) or belong to a different file
|
|
267
|
+
// entirely (identity change), so start over from the top.
|
|
268
|
+
Object.assign(state, newScanState());
|
|
269
|
+
}
|
|
270
|
+
state.ino = stat.ino;
|
|
271
|
+
state.dev = stat.dev;
|
|
272
|
+
if (stat.size > state.offset) {
|
|
273
|
+
const chunk = readAt(fd, state.offset, stat.size - state.offset);
|
|
274
|
+
state.offset += chunk.length;
|
|
275
|
+
const data = state.carry.length > 0 ? Buffer.concat([state.carry, chunk]) : chunk;
|
|
276
|
+
let start = 0;
|
|
277
|
+
for (;;) {
|
|
278
|
+
const nl = data.indexOf(NEWLINE, start);
|
|
279
|
+
if (nl === -1)
|
|
280
|
+
break;
|
|
281
|
+
const hit = eligible(reportHitInLine(data.subarray(start, nl).toString("utf8"), state.lineIndex, sessionId), adopted, state);
|
|
282
|
+
if (hit !== null)
|
|
283
|
+
state.best = hit;
|
|
284
|
+
state.lineIndex += 1;
|
|
285
|
+
start = nl + 1;
|
|
286
|
+
}
|
|
287
|
+
state.carry = start < data.length ? Buffer.from(data.subarray(start)) : Buffer.alloc(0);
|
|
288
|
+
}
|
|
289
|
+
scanCarry(state, sessionId, adopted);
|
|
290
|
+
return "ok";
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return "unreadable";
|
|
294
|
+
}
|
|
295
|
+
finally {
|
|
296
|
+
fs.closeSync(fd);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function defaultSleep(ms) {
|
|
300
|
+
return new Promise((resolve) => {
|
|
301
|
+
setTimeout(resolve, ms);
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Poll the payload's own transcript for this session's Understanding
|
|
306
|
+
* Report until it appears or `maxWaitMs` elapses. See the module header
|
|
307
|
+
* for the filters and why each one is there.
|
|
308
|
+
*
|
|
309
|
+
* The poll timer is deliberately NOT `unref`'d. A hook process whose
|
|
310
|
+
* stdin has been drained has no other ref'd handle, so an unref'd timer
|
|
311
|
+
* lets Node exit mid-poll: the process would end with an empty stdout,
|
|
312
|
+
* which Claude Code reads as ALLOW: a silent fail-open on precisely the
|
|
313
|
+
* delegation path this scan exists for. The loop is bounded twice over
|
|
314
|
+
* (`maxWaitMs`, itself capped by the config's 5 s ceiling, and the
|
|
315
|
+
* `maxPolls` count below), so it can never hold the process open beyond
|
|
316
|
+
* the bound.
|
|
317
|
+
*/
|
|
318
|
+
export async function scanTranscriptForReport(args) {
|
|
319
|
+
const now = args.now ?? (() => Date.now());
|
|
320
|
+
const sleep = args.sleep ?? defaultSleep;
|
|
321
|
+
const pollMs = args.pollMs !== undefined && args.pollMs > 0 ? args.pollMs : DEFAULT_POLL_MS;
|
|
322
|
+
const maxWaitMs = Number.isFinite(args.maxWaitMs) && args.maxWaitMs > 0 ? args.maxWaitMs : 0;
|
|
323
|
+
const adopted = args.adopted ?? EMPTY_ADOPTED;
|
|
324
|
+
const start = now();
|
|
325
|
+
// Belt and braces against a clock that does not advance (an injected
|
|
326
|
+
// one that forgets to, a suspended VM): the poll count is bounded on
|
|
327
|
+
// its own, so this can never become an unbounded loop inside a hook.
|
|
328
|
+
// Generous enough that it never pre-empts the real time bound.
|
|
329
|
+
const maxPolls = Math.ceil(maxWaitMs / pollMs) + 2;
|
|
330
|
+
const state = newScanState();
|
|
331
|
+
for (let poll = 0;; poll += 1) {
|
|
332
|
+
const outcome = pollTranscript(args.transcriptPath, state, args.sessionId, adopted);
|
|
333
|
+
if (outcome === "unreadable") {
|
|
334
|
+
return { found: false, reason: "unreadable", waitedMs: now() - start };
|
|
335
|
+
}
|
|
336
|
+
// The provisional trailing-line hit is newer than any complete one.
|
|
337
|
+
const hit = state.tentative ?? state.best;
|
|
338
|
+
if (hit !== null) {
|
|
339
|
+
return {
|
|
340
|
+
found: true,
|
|
341
|
+
markdown: hit.markdown,
|
|
342
|
+
lineIndex: hit.lineIndex,
|
|
343
|
+
entryId: hit.entryId,
|
|
344
|
+
waitedMs: now() - start,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const elapsed = now() - start;
|
|
348
|
+
if (elapsed >= maxWaitMs || poll >= maxPolls) {
|
|
349
|
+
return {
|
|
350
|
+
found: false,
|
|
351
|
+
reason: "timeout",
|
|
352
|
+
...(state.adoptedSkipped ? { adoptedOnly: true } : {}),
|
|
353
|
+
waitedMs: elapsed,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
await sleep(pollMs);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=transcript-report-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-report-scan.js","sourceRoot":"","sources":["../../../src/cli/pack/transcript-report-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apply } from "../apply/index.js";
|
|
2
|
+
import { issueDelegation } from "../delegate/index.js";
|
|
2
3
|
import { type AssertionFailure, type SmokeExpectations } from "./assertions.js";
|
|
3
4
|
import { type RunClaudeOptions } from "./runner.js";
|
|
4
5
|
import { type StreamSummary } from "./stream-parser.js";
|
|
@@ -23,11 +24,27 @@ export interface SmokeOptions {
|
|
|
23
24
|
spawn?: RunClaudeOptions["spawn"];
|
|
24
25
|
/** Test seam for the manifest-apply step. Defaults to the real `apply`. */
|
|
25
26
|
applyImpl?: typeof apply;
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Override cwd for the spawned claude. Defaults to the parent
|
|
29
|
+
* process's own cwd: when unset, `runOpts.cwd` is left unset below
|
|
30
|
+
* and `runClaude` leaves `cwd` unset on the spawn options too, so
|
|
31
|
+
* Node inherits this process's cwd exactly like an unset `cwd` always
|
|
32
|
+
* does.
|
|
33
|
+
*/
|
|
27
34
|
spawnCwd?: string;
|
|
28
35
|
/** Stdout/stderr writers (defaults to process.stdout / stderr). */
|
|
29
36
|
stdout?: (s: string) => void;
|
|
30
37
|
stderr?: (s: string) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Skip the slice-3 pre-spawn delegation (ADR
|
|
40
|
+
* docs/decisions/2026-08-27-ug-auto-mode-approval.md, "TTL, cwd, and
|
|
41
|
+
* subagents": the smoke runner is the delegation's first consumer).
|
|
42
|
+
* Default: false (delegate). Set for launchers that want the
|
|
43
|
+
* pre-slice-3 shape, or when the caller issues its own delegation.
|
|
44
|
+
*/
|
|
45
|
+
noDelegate?: boolean;
|
|
46
|
+
/** Test seam for the delegation step. Defaults to the real `issueDelegation`. */
|
|
47
|
+
issueDelegationImpl?: typeof issueDelegation;
|
|
31
48
|
}
|
|
32
49
|
export interface SmokeResult {
|
|
33
50
|
/** EX_OK on green, EX_FAIL on an assertion miss, EX_UNAVAILABLE on missing claude. */
|
package/dist/cli/smoke/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { randomUUID } from "node:crypto";
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
15
|
import { apply, SETTINGS_BASENAME } from "../apply/index.js";
|
|
16
|
+
import { issueDelegation } from "../delegate/index.js";
|
|
16
17
|
import { EX_FAIL, EX_UNAVAILABLE, EX_USAGE, HarnessExitError } from "../exit-codes.js";
|
|
17
18
|
import { evaluateExpectations, formatFailures, } from "./assertions.js";
|
|
18
19
|
import { runClaude } from "./runner.js";
|
|
@@ -99,6 +100,59 @@ export async function runSmoke(opts) {
|
|
|
99
100
|
}
|
|
100
101
|
const sessionId = opts.sessionId ?? randomUUID();
|
|
101
102
|
const timeoutMs = opts.timeoutMs ?? 60_000;
|
|
103
|
+
// Slice 3 delegation (ADR docs/decisions/2026-08-27-ug-auto-mode-
|
|
104
|
+
// approval.md, "TTL, cwd, and subagents": "The harness smoke runner
|
|
105
|
+
// ... is the natural first consumer"). Issued for the session id
|
|
106
|
+
// this run already chose, bound to the cwd the child actually spawns
|
|
107
|
+
// into (`opts.spawnCwd`, defaulting to this process's own cwd exactly
|
|
108
|
+
// like `runClaude`'s own unset-cwd default does), no task. Parent
|
|
109
|
+
// resolves through `issueDelegation`'s own precedence chain (flag >
|
|
110
|
+
// env > staged `.pending-approval`), this runner never overrides it,
|
|
111
|
+
// so the delegation is always issued on behalf of whatever session
|
|
112
|
+
// actually invoked `harness smoke`.
|
|
113
|
+
//
|
|
114
|
+
// TTL is the run's own wall-clock budget plus one minute of slack
|
|
115
|
+
// (`Math.ceil(timeoutMs / 1000) + 60`), never `issueDelegation`'s own
|
|
116
|
+
// one-hour default: a 60s smoke run has no business minting a
|
|
117
|
+
// pre-authorization that outlives the run by an hour. If that value
|
|
118
|
+
// exceeds the applied pack's own ceiling, `issueDelegationImpl`
|
|
119
|
+
// refuses with `ttl-above-max-age`, which prints exactly like any
|
|
120
|
+
// other refusal below; nothing here special-cases it.
|
|
121
|
+
//
|
|
122
|
+
// Never blocks the run: every refusal (no parent marker, no signing
|
|
123
|
+
// key, an unresolved parent session id, an over-ceiling TTL) AND
|
|
124
|
+
// every thrown error (e.g. `resolvePaths`'s real-home-dir guard when
|
|
125
|
+
// neither `--config` nor a home dir is set outside the real
|
|
126
|
+
// `harness` binary) prints one line and the run proceeds exactly as
|
|
127
|
+
// it did before this delegation step existed, the same shape
|
|
128
|
+
// `--no-delegate` opts back into explicitly.
|
|
129
|
+
const stdoutWrite = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
130
|
+
if (!opts.noDelegate) {
|
|
131
|
+
const issueDelegationImpl = opts.issueDelegationImpl ?? issueDelegation;
|
|
132
|
+
const childCwd = opts.spawnCwd ?? process.cwd();
|
|
133
|
+
const delegationOpts = {
|
|
134
|
+
childSessionId: sessionId,
|
|
135
|
+
cwd: childCwd,
|
|
136
|
+
ttlSeconds: Math.ceil(timeoutMs / 1000) + 60,
|
|
137
|
+
};
|
|
138
|
+
if (opts.configPath)
|
|
139
|
+
delegationOpts.configPath = opts.configPath;
|
|
140
|
+
if (opts.project)
|
|
141
|
+
delegationOpts.project = opts.project;
|
|
142
|
+
try {
|
|
143
|
+
const delegationResult = await issueDelegationImpl(delegationOpts);
|
|
144
|
+
if (delegationResult.ok) {
|
|
145
|
+
stdoutWrite(`delegation: ✓ ${delegationResult.filePath} (child ${delegationResult.childSessionId}, parent ${delegationResult.parentSessionId}, expires ${delegationResult.expiresAt})\n`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
stdoutWrite(`delegation: skipped (${delegationResult.reason}: ${delegationResult.detail})\n`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
stdoutWrite(`delegation: skipped (error: ${message})\n`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
102
156
|
const runOpts = {
|
|
103
157
|
claudeBin,
|
|
104
158
|
prompt: opts.prompt,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/smoke/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,+BAA+B;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,oBAAoB,EACpB,cAAc,GAIf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAA+C,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cli/smoke/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,wEAAwE;AACxE,qEAAqE;AACrE,qEAAqE;AACrE,sEAAsE;AACtE,8DAA8D;AAC9D,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,+BAA+B;AAE/B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EACL,oBAAoB,EACpB,cAAc,GAIf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAA+C,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;AAkE1E,SAAS,gBAAgB,CAAC,IAAkB;IAC1C,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC;IAC1C,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IAC1D,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAW;IACxC,sEAAsE;IACtE,oEAAoE;IACpE,oEAAoE;IACpE,4BAA4B;IAC5B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACxB,6CAA6C,GAAG,EAAE,EAClD,cAAc,CACf,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnE,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACtC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO;IACvC,CAAC;IACD,MAAM,IAAI,gBAAgB,CACxB,kBAAkB,GAAG,yDAAyD,EAC9E,cAAc,CACf,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAS;IACjC,OAAO,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,MAAM,CAAC;AACvD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB;IAC/C,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,gBAAgB,CAAC,qCAAqC,EAAE,QAAQ,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,gBAAgB,CACxB,yCAAyC,EACzC,QAAQ,CACT,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,YAAY,EAAE,cAAc,KAAK,SAAS,EAAE,CAAC;QACpD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,EAAE,CAAC;YACxD,MAAM,IAAI,gBAAgB,CACxB,iEAAiE,EACjE,QAAQ,CACT,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzC,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAEjC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;IAElE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,SAAS,GAAgC;QAC7C,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,IAAI;KACZ,CAAC;IACF,IAAI,IAAI,CAAC,UAAU;QAAE,SAAS,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;IAC5D,IAAI,IAAI,CAAC,OAAO;QAAE,SAAS,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;IACnD,MAAM,WAAW,GAAG,MAAM,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/C,sEAAsE;IACtE,kEAAkE;IAClE,sEAAsE;IACtE,uDAAuD;IACvD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;QAC/B,cAAc;QACd,mBAAmB;QACnB,sBAAsB;KACvB,CAAC,CAAC;IACH,IAAI,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,gBAAgB,CACxB,8CAA8C,WAAW,CAAC,OAAO,oCAAoC,EACrG,OAAO,CACR,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,UAAU,EAAE,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC;IAE3C,kEAAkE;IAClE,oEAAoE;IACpE,iEAAiE;IACjE,qEAAqE;IACrE,sEAAsE;IACtE,kEAAkE;IAClE,oEAAoE;IACpE,qEAAqE;IACrE,mEAAmE;IACnE,oCAAoC;IACpC,EAAE;IACF,kEAAkE;IAClE,sEAAsE;IACtE,8DAA8D;IAC9D,oEAAoE;IACpE,gEAAgE;IAChE,kEAAkE;IAClE,sDAAsD;IACtD,EAAE;IACF,oEAAoE;IACpE,iEAAiE;IACjE,qEAAqE;IACrE,4DAA4D;IAC5D,oEAAoE;IACpE,6DAA6D;IAC7D,6CAA6C;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5E,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,MAAM,mBAAmB,GAAG,IAAI,CAAC,mBAAmB,IAAI,eAAe,CAAC;QACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAChD,MAAM,cAAc,GAA0C;YAC5D,cAAc,EAAE,SAAS;YACzB,GAAG,EAAE,QAAQ;YACb,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;SAC7C,CAAC;QACF,IAAI,IAAI,CAAC,UAAU;YAAE,cAAc,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QACjE,IAAI,IAAI,CAAC,OAAO;YAAE,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,gBAAgB,GAAG,MAAM,mBAAmB,CAAC,cAAc,CAAC,CAAC;YACnE,IAAI,gBAAgB,CAAC,EAAE,EAAE,CAAC;gBACxB,WAAW,CACT,iBAAiB,gBAAgB,CAAC,QAAQ,WAAW,gBAAgB,CAAC,cAAc,YAAY,gBAAgB,CAAC,eAAe,aAAa,gBAAgB,CAAC,SAAS,KAAK,CAC7K,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,WAAW,CACT,wBAAwB,gBAAgB,CAAC,MAAM,KAAK,gBAAgB,CAAC,MAAM,KAAK,CACjF,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,WAAW,CAAC,+BAA+B,OAAO,KAAK,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAqB;QAChC,SAAS;QACT,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,YAAY;QACZ,SAAS;QACT,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS;KACV,CAAC;IACF,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IAC3C,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;IAE7D,IAAI,SAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,gBAAgB,CACxB,uCAAwC,GAAa,CAAC,OAAO,EAAE,EAC/D,cAAc,CACf,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,gBAAgB,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;IAExE,kEAAkE;IAClE,4DAA4D;IAC5D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,QAAQ,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,iCAAiC;YAC3C,MAAM,EAAE,uBAAuB,SAAS,IAAI;YAC5C,MAAM,EAAE,sCAAsC,SAAS,uDAAuD;SAC/G,CAAC,CAAC;IACL,CAAC;IACD,oEAAoE;IACpE,mEAAmE;IACnE,gEAAgE;IAChE,mEAAmE;IACnE,IACE,QAAQ,CAAC,MAAM,KAAK,CAAC;QACrB,CAAC,SAAS,CAAC,QAAQ;QACnB,SAAS,CAAC,QAAQ,KAAK,IAAI;QAC3B,SAAS,CAAC,QAAQ,KAAK,CAAC;QACxB,OAAO,CAAC,MAAM,KAAK,IAAI,EACvB,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,aAAa;YACnB,QAAQ,EAAE,sCAAsC;YAChD,MAAM,EAAE,iBAAiB,SAAS,CAAC,QAAQ,kCAAkC;YAC7E,MAAM,EACJ,gCAAgC,SAAS,CAAC,QAAQ,0BAA0B;gBAC5E,2FAA2F;SAC9F,CAAC,CAAC;IACL,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACrD,OAAO;QACL,QAAQ;QACR,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,YAAY;QACZ,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,OAAO;QACP,cAAc,EAAE,SAAS,CAAC,QAAQ;QAClC,cAAc,EAAE,SAAS,CAAC,QAAQ;QAClC,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,QAAQ;QACR,UAAU,EAAE,SAAS,CAAC,IAAI;KAC3B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAmB;IACnD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,UAAU,KAAK,CAAC,CAAC;IACxG,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAClD,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,IAAI,iBAAiB,EAAE,CAAC,CAAC;IACtF,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,KAAK,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,IAAI,mBAAmB,EAAE,CAAC,CAAC;IAC3F,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACjC,CAAC"}
|