@molecule/api-ai-tools 1.8.1 → 1.8.3
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/README.md +174 -18
- package/dist/command-position.d.ts +68 -2
- package/dist/command-position.d.ts.map +1 -1
- package/dist/command-position.js +309 -43
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/sleep-peek.d.ts +51 -0
- package/dist/sleep-peek.d.ts.map +1 -0
- package/dist/sleep-peek.js +76 -0
- package/dist/tools.d.ts +56 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +285 -48
- package/dist/trace-artifacts.d.ts.map +1 -1
- package/dist/trace-artifacts.js +24 -19
- package/dist/utilities.d.ts +38 -9
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +150 -51
- package/package.json +1 -1
package/dist/tools.d.ts
CHANGED
|
@@ -60,8 +60,47 @@ export declare const SUITE_OVER_BUDGET_ADVICE: string;
|
|
|
60
60
|
*/
|
|
61
61
|
export declare function moleculeDistReadNote(path: string): string | null;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
63
|
+
* What the filesystem says about a write target under `node_modules` — the half
|
|
64
|
+
* of the decision a path's spelling cannot make.
|
|
65
|
+
*
|
|
66
|
+
* @remarks
|
|
67
|
+
* A path that reads `node_modules` is not necessarily an install: a run links
|
|
68
|
+
* `node_modules` beside its e2e copy to borrow the hoisted tree, and that link
|
|
69
|
+
* is its own. X0 xr-molecule-flash-m106 (+07:58) made exactly that link, found
|
|
70
|
+
* it dangling (everything was hoisted to `/workspace/node_modules`), and was
|
|
71
|
+
* refused three times for trying to remove it — 40 s and a "3rd time this turn
|
|
72
|
+
* … ask the user" escalation, in an unattended run, over a broken symlink in
|
|
73
|
+
* the run's own scratch directory. So the guard asks what is there.
|
|
74
|
+
*/
|
|
75
|
+
export interface InstalledPackageFacts {
|
|
76
|
+
/** Whether the `node_modules` entry the path goes through is a symbolic link. */
|
|
77
|
+
readonly symlink: boolean;
|
|
78
|
+
/** Whether a package is really installed there: a `package.json` resolves under the entry. */
|
|
79
|
+
readonly installed: boolean;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Looks at the filesystem for {@link InstalledPackageFacts}.
|
|
83
|
+
*
|
|
84
|
+
* The guard cannot `stat` the path itself: the tools act through a backend (a
|
|
85
|
+
* sandbox over the network for the fleet), so the caller — which holds that
|
|
86
|
+
* backend — supplies the look. {@link shellInstalledPackageProbe} builds one
|
|
87
|
+
* from any shell runner.
|
|
88
|
+
*/
|
|
89
|
+
export type InstalledPackageProbe = (path: string) => Promise<InstalledPackageFacts>;
|
|
90
|
+
/**
|
|
91
|
+
* Build an {@link InstalledPackageProbe} from a shell runner (a backend's
|
|
92
|
+
* `run`, or a sandbox's `exec`): one command reports whether the entry is a
|
|
93
|
+
* link and whether a `package.json` resolves under it.
|
|
94
|
+
*
|
|
95
|
+
* @param run - Runs a command where the path lives.
|
|
96
|
+
* @returns The probe.
|
|
97
|
+
*/
|
|
98
|
+
export declare function shellInstalledPackageProbe(run: (command: string) => Promise<{
|
|
99
|
+
stdout?: string;
|
|
100
|
+
} | null | undefined>): InstalledPackageProbe;
|
|
101
|
+
/**
|
|
102
|
+
* The refusal for a write whose target is inside an INSTALLED package, or null
|
|
103
|
+
* when the path is not under `node_modules` or nothing is installed there.
|
|
65
104
|
*
|
|
66
105
|
* `node_modules` is not the project's code: nothing written there survives the
|
|
67
106
|
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
@@ -74,6 +113,19 @@ export declare function moleculeDistReadNote(path: string): string | null;
|
|
|
74
113
|
* putting the file back. Reading these files stays allowed (that is how the
|
|
75
114
|
* declarations above are read at all).
|
|
76
115
|
*
|
|
116
|
+
* What is protected is the INSTALL, not the spelling: the decision comes from
|
|
117
|
+
* {@link InstalledPackageFacts}, never from the path alone (m106 above). Three
|
|
118
|
+
* things are the run's own to remove or replace —
|
|
119
|
+
*
|
|
120
|
+
* - a `node_modules` that is a SYMLINK: `rm`/`unlink` removes the link, never the
|
|
121
|
+
* tree it points at, so a link the run made cannot clobber anything;
|
|
122
|
+
* - anything under the run's own `.mol-scratch/` directory;
|
|
123
|
+
* - a `node_modules` with no `package.json` under it (nothing was ever installed).
|
|
124
|
+
*
|
|
125
|
+
* A path INSIDE the link is still decided by what resolves there: a link into a
|
|
126
|
+
* real tree is refused, because removing or writing through it reaches the
|
|
127
|
+
* install.
|
|
128
|
+
*
|
|
77
129
|
* The restore route is named because the model that has ALREADY clobbered a file
|
|
78
130
|
* (this refusal is too late for it) is the one who needs it, and because the
|
|
79
131
|
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
@@ -81,9 +133,10 @@ export declare function moleculeDistReadNote(path: string): string | null;
|
|
|
81
133
|
* m91 used, and what both guards leave alone.
|
|
82
134
|
*
|
|
83
135
|
* @param path - An absolute path a tool is about to write.
|
|
136
|
+
* @param probe - Looks at the filesystem where the path lives.
|
|
84
137
|
* @returns The one-line refusal naming the package, or null when nothing is installed there.
|
|
85
138
|
*/
|
|
86
|
-
export declare function installedPackageWriteRefusal(path: string): string | null
|
|
139
|
+
export declare function installedPackageWriteRefusal(path: string, probe?: InstalledPackageProbe): Promise<string | null>;
|
|
87
140
|
/**
|
|
88
141
|
* Build a complete set of AI agent tools bound to an execution backend.
|
|
89
142
|
*
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAgJ9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAiOD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmEnE;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAEA,CAAA;AAE9B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,wBAAwB,QAI6C,CAAA;AAmBlF;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,qBAAqB;IACpC,iFAAiF;IACjF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAA;AAoBpF;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAkBvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAiCxB;AAgDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAu2ExF"}
|
package/dist/tools.js
CHANGED
|
@@ -10,6 +10,7 @@ import { commandCutRun, CUT_AT_BUDGET, cutRunSentence, suiteRunExitCode } from '
|
|
|
10
10
|
import { addChangeSpan, changedSpan, countLines, renderEditExcerpt, renderSmallFile, } from './edit-excerpt.js';
|
|
11
11
|
import { guardToolExecute, missingParamError, ToolInputError } from './input-normalizer.js';
|
|
12
12
|
import { TOOL_SCHEMAS } from './schemas.js';
|
|
13
|
+
import { leadingSleepPeek } from './sleep-peek.js';
|
|
13
14
|
/**
|
|
14
15
|
* Total waiting, per background task, after which wait_for_task reports it
|
|
15
16
|
* stuck instead of waiting again. Env-tunable so a test can exercise it.
|
|
@@ -44,6 +45,36 @@ const runByTask = new Map();
|
|
|
44
45
|
const FILTERED_EXIT_NOTE = 'No exit code is reported for this run: a stage after the test runner produced the pipeline’s ' +
|
|
45
46
|
'code (or the code says 0 beside a failure in the tally), so read the pass/fail tally in the ' +
|
|
46
47
|
'log instead.';
|
|
48
|
+
/**
|
|
49
|
+
* What a finished run says when its suite sits inside a `sh -c '<command>'` string: the stages are
|
|
50
|
+
* positions in THAT string, so the platform cannot point at one in the command's own text. Said
|
|
51
|
+
* instead of {@link FILTERED_EXIT_NOTE} — that note names a stage after the runner, and this run
|
|
52
|
+
* cannot be read as having one; a command with nothing piped after the runner keeps its code.
|
|
53
|
+
*/
|
|
54
|
+
const UNPLACED_KEPT_NOTE = 'The suite sits inside a `sh -c` string, so its stages cannot be placed in the command’s own ' +
|
|
55
|
+
'text; nothing follows the runner there, so the exit code above is the run’s own — read the ' +
|
|
56
|
+
'pass/fail tally in the log for the verdict.';
|
|
57
|
+
/** {@link UNPLACED_KEPT_NOTE} when between them a stage after the runner produced the code. */
|
|
58
|
+
const UNPLACED_PIPED_NOTE = 'The suite sits inside a `sh -c` string, so its stages cannot be placed in the command’s own ' +
|
|
59
|
+
'text, and a stage after the runner may have produced the pipeline’s code: none is reported, ' +
|
|
60
|
+
'so read the pass/fail tally in the log instead.';
|
|
61
|
+
/**
|
|
62
|
+
* The sentence a finished suite run's result carries about its exit code, or null when the code it
|
|
63
|
+
* reports is the run's own and needs no explanation.
|
|
64
|
+
*
|
|
65
|
+
* {@link FILTERED_EXIT_NOTE} names a stage after the runner, so it is said only when the parse
|
|
66
|
+
* found one; a run whose stages cannot be placed says THAT instead, and keeps the command's own
|
|
67
|
+
* code when nothing follows the runner.
|
|
68
|
+
*
|
|
69
|
+
* @param placement - Where the code came from ({@link suiteRunPipelinePlacement}).
|
|
70
|
+
* @param reported - The code the result reports, or null when it reports none.
|
|
71
|
+
* @returns The note, or null.
|
|
72
|
+
*/
|
|
73
|
+
function suiteExitCodeNote(placement, reported) {
|
|
74
|
+
if (placement.unplaced)
|
|
75
|
+
return reported === null ? UNPLACED_PIPED_NOTE : UNPLACED_KEPT_NOTE;
|
|
76
|
+
return reported === null ? FILTERED_EXIT_NOTE : null;
|
|
77
|
+
}
|
|
47
78
|
/**
|
|
48
79
|
* Task ids whose own-output file has already been looked for this process.
|
|
49
80
|
* {@link outputFileByTask} is empty for every task after a restart — it is
|
|
@@ -229,8 +260,106 @@ function runningNote(clauses) {
|
|
|
229
260
|
const joined = clauses.join('; ');
|
|
230
261
|
return `${joined[0].toUpperCase()}${joined.slice(1)}.`;
|
|
231
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* The background task a leading `sleep` was about to peek at, and its state now — or null when the
|
|
265
|
+
* command has no task of this process's to report, in which case the sleep runs as written (the
|
|
266
|
+
* tool's own budget bounds it, as before).
|
|
267
|
+
*
|
|
268
|
+
* X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) hand-polled with `sleep N; tail …`
|
|
269
|
+
* three times. The state the executor wanted was always one the platform already held: two tasks
|
|
270
|
+
* had ended 0.1 s in (exit 127, and `Error: No tests found`) and the third was live at 72 of 139
|
|
271
|
+
* with failures the log already carried. So a command that opens with a long sleep is answered
|
|
272
|
+
* from the same reads `wait_for_task` makes — the exit file, and the failure-lines-plus-tail slice
|
|
273
|
+
* of the log — and the sleep is dropped.
|
|
274
|
+
*
|
|
275
|
+
* The command names WHAT it is peeking at (`/tmp/<id>.log`, or the file the task redirects its own
|
|
276
|
+
* output to), and that task is the answer whether it has ended or not: the two dead peeks are
|
|
277
|
+
* exactly this shape. With no name to follow, the newest LIVE task is the answer — the point of
|
|
278
|
+
* the rule is that a sleep never outlives a run it could have been waiting on — and with no live
|
|
279
|
+
* task there is nothing to report.
|
|
280
|
+
*
|
|
281
|
+
* @param backend - The execution backend the task runs on.
|
|
282
|
+
* @param command - The command whose leading sleep is being replaced.
|
|
283
|
+
* @param seconds - The seconds that leading sleep asked for.
|
|
284
|
+
* @param ceilingMs - The wait ceiling a `wait_for_task` would have, so the ETA it states is the
|
|
285
|
+
* one an actual wait would act on.
|
|
286
|
+
* @returns One sentence naming the task's state and how to get its result, plus the log slice the
|
|
287
|
+
* caller attaches as `outputSoFar`; null when no task answers this command.
|
|
288
|
+
*/
|
|
289
|
+
async function backgroundTaskState(backend, command, seconds, ceilingMs) {
|
|
290
|
+
const ids = [...runByTask.keys()];
|
|
291
|
+
if (!ids.length)
|
|
292
|
+
return null;
|
|
293
|
+
const exitCodeOf = async (id) => {
|
|
294
|
+
try {
|
|
295
|
+
const raw = await backend.readFile(`/tmp/${id}.exit`);
|
|
296
|
+
const code = Number.parseInt(raw.trim(), 10);
|
|
297
|
+
return Number.isFinite(code) ? code : 0;
|
|
298
|
+
}
|
|
299
|
+
catch (_error) {
|
|
300
|
+
// No exit file yet: the task is still running. Not an error.
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const names = (id) => {
|
|
305
|
+
if (command.includes(`/tmp/${id}.log`))
|
|
306
|
+
return true;
|
|
307
|
+
const own = outputFileByTask.get(id);
|
|
308
|
+
return own !== undefined && command.includes(own);
|
|
309
|
+
};
|
|
310
|
+
const newestFirst = [...ids].reverse();
|
|
311
|
+
const namedId = newestFirst.find(names);
|
|
312
|
+
let target = namedId
|
|
313
|
+
? { id: namedId, exitCode: await exitCodeOf(namedId) }
|
|
314
|
+
: null;
|
|
315
|
+
if (!target) {
|
|
316
|
+
// Bounded: a long session can leave many finished tasks behind, and each look costs a read.
|
|
317
|
+
for (const id of newestFirst.slice(0, 8)) {
|
|
318
|
+
const exitCode = await exitCodeOf(id);
|
|
319
|
+
if (exitCode === null) {
|
|
320
|
+
target = { id, exitCode };
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (!target)
|
|
326
|
+
return null;
|
|
327
|
+
let slice = '';
|
|
328
|
+
try {
|
|
329
|
+
const r = await backend.run(runningOutputSliceCommand(`/tmp/${target.id}.log`, outputFileByTask.get(target.id)), { timeout: 5_000 });
|
|
330
|
+
slice = r.stdout ?? '';
|
|
331
|
+
}
|
|
332
|
+
catch (_error) {
|
|
333
|
+
// No slice: the note still says the task's state and how to get its result.
|
|
334
|
+
}
|
|
335
|
+
const secondsText = `Did not sleep ${Math.round(seconds)} s: background task \`${target.id}\``;
|
|
336
|
+
if (target.exitCode === null) {
|
|
337
|
+
const facts = waitProgressClauses({
|
|
338
|
+
taskId: target.id,
|
|
339
|
+
slice,
|
|
340
|
+
stillWriting: false,
|
|
341
|
+
bytes: null,
|
|
342
|
+
ceilingMs,
|
|
343
|
+
waitedMs: 0,
|
|
344
|
+
});
|
|
345
|
+
const failures = slice.trim() ? '; the failures already in its log are in outputSoFar' : '';
|
|
346
|
+
return {
|
|
347
|
+
note: `${secondsText} is live (${facts.join('; ')})${failures} — ` +
|
|
348
|
+
`\`wait_for_task ${target.id}\` returns its result the moment it ends.`,
|
|
349
|
+
slice,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const counter = logProgress(slice);
|
|
353
|
+
const progress = counter ? `, at ${counter.done} of ${counter.total} in its log` : '';
|
|
354
|
+
const failures = slice.trim() ? '; the failures in its log are in outputSoFar' : '';
|
|
355
|
+
return {
|
|
356
|
+
note: `${secondsText} had already ended (exit code ${target.exitCode})${progress}${failures} — ` +
|
|
357
|
+
`\`wait_for_task ${target.id}\` returns its result now.`,
|
|
358
|
+
slice,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
232
361
|
import { diagnosisTraceCommand } from './trace-artifacts.js';
|
|
233
|
-
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, earlyReturnCause, emptyCallError, failureCausesNote, findPathArgs, findPatternArgs, fusedLine, htmlOutline, isCommandTimeout, isEnvFilePath, isOutsideRoot, isPlaywrightRunCommand, isSuiteRunCommand, isValidGlob, linesWithinSerialized, literalDirPrefix, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_HTML_INLINE_CHARS, MAX_OUTPUT_SIZE, MAX_READ_CALL_CHARS, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, printsOnlySource, READ_RESULT_ENVELOPE_CHARS, redactSecrets, redactSecretsInCode, repeatingFailure, repeatingFailureNote, resolvePath, RUNNING_FAILURE_LINES, runningCauseCommand, runningOutputSliceCommand, searchTimeoutError, serializedChars, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars,
|
|
362
|
+
import { checkBlockedCommand, declaredBudgetSeconds, DEFAULT_SEARCH_EXCLUDED_DIRS, directoryReadHint, earlyReturnCause, emptyCallError, failureCausesNote, findPathArgs, findPatternArgs, fusedLine, htmlOutline, isCommandTimeout, isEnvFilePath, isOutsideRoot, isPlaywrightRunCommand, isSuiteRunCommand, isValidGlob, linesWithinSerialized, literalDirPrefix, MAX_BATCH_READ_FILES, MAX_FIND_RESULTS, MAX_HTML_INLINE_CHARS, MAX_OUTPUT_SIZE, MAX_READ_CALL_CHARS, MAX_READ_RETURN_CHARS, MAX_READ_SIZE, MAX_SEARCH_CONTEXT_LINES, MAX_SEARCH_RESULTS, MAX_WRITE_SIZE, outputIsWithheldUntilExit, outputRedirectTarget, parseCheckCommand, parseCheckProblem, pathArgError, printsOnlySource, READ_RESULT_ENVELOPE_CHARS, redactSecrets, redactSecretsInCode, repeatingFailure, repeatingFailureNote, resolvePath, RUNNING_FAILURE_LINES, runningCauseCommand, runningOutputSliceCommand, searchTimeoutError, serializedChars, shellQuote, sliceLines, SMALL_FILE_WHOLE_CHARS, streamingBackgroundCommand, stripControlChars, suiteRunPipelinePlacement, suiteRunPriorityLaunch, truncateMiddle, universalCause, whitespaceTolerantReplace, } from './utilities.js';
|
|
234
363
|
/**
|
|
235
364
|
* How long exec_command waits for a command it backgrounded only because the
|
|
236
365
|
* command asked for more than the tool's ceiling, before handing back a task id.
|
|
@@ -298,8 +427,51 @@ export function moleculeDistReadNote(path) {
|
|
|
298
427
|
: ' For one signature, read the matching .d.ts file.'));
|
|
299
428
|
}
|
|
300
429
|
/**
|
|
301
|
-
* The
|
|
302
|
-
* when the path
|
|
430
|
+
* The `node_modules` entry a path goes through, and the package name after it
|
|
431
|
+
* (`null` when the path IS the entry).
|
|
432
|
+
*
|
|
433
|
+
* @param path - An absolute path.
|
|
434
|
+
* @returns The entry and the name, or null when no `node_modules` component is in the path.
|
|
435
|
+
*/
|
|
436
|
+
function nodeModulesParts(path) {
|
|
437
|
+
// A path may sit in a NESTED node_modules (`app/node_modules/...`) as well as the
|
|
438
|
+
// hoisted root one: both can be installed trees.
|
|
439
|
+
const at = /(?:^|\/)node_modules(?:\/|$)/.exec(path);
|
|
440
|
+
if (!at)
|
|
441
|
+
return null;
|
|
442
|
+
const head = path.slice(0, at.index);
|
|
443
|
+
const entry = `${head}${head === '' || head.endsWith('/') ? '' : '/'}node_modules`;
|
|
444
|
+
const named = /node_modules\/((?:@[^/]+\/)?[^/]+)(?:\/|$)/.exec(path.slice(at.index));
|
|
445
|
+
return { entry, name: named?.[1] ?? null };
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Build an {@link InstalledPackageProbe} from a shell runner (a backend's
|
|
449
|
+
* `run`, or a sandbox's `exec`): one command reports whether the entry is a
|
|
450
|
+
* link and whether a `package.json` resolves under it.
|
|
451
|
+
*
|
|
452
|
+
* @param run - Runs a command where the path lives.
|
|
453
|
+
* @returns The probe.
|
|
454
|
+
*/
|
|
455
|
+
export function shellInstalledPackageProbe(run) {
|
|
456
|
+
return async (path) => {
|
|
457
|
+
const parts = nodeModulesParts(path);
|
|
458
|
+
if (!parts)
|
|
459
|
+
return { symlink: false, installed: false };
|
|
460
|
+
const { entry, name } = parts;
|
|
461
|
+
// A bare `@scope` is not a package, but removing it takes every package under
|
|
462
|
+
// it: treat the scope directory like the entry itself and look beneath it.
|
|
463
|
+
const scopeOnly = name !== null && /^@[^/]+$/.test(name);
|
|
464
|
+
const install = name !== null && !scopeOnly
|
|
465
|
+
? `if [ -e ${shellQuote(`${entry}/${name}/package.json`)} ]; then echo installed; fi`
|
|
466
|
+
: `if find ${shellQuote(name === null ? entry : `${entry}/${name}`)} -maxdepth 3 -name package.json -print -quit 2>/dev/null | grep -q .; then echo installed; fi`;
|
|
467
|
+
const out = String((await run(`if [ -L ${shellQuote(entry)} ]; then echo symlink; fi; ${install}`))?.stdout ??
|
|
468
|
+
'');
|
|
469
|
+
return { symlink: out.includes('symlink'), installed: out.includes('installed') };
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* The refusal for a write whose target is inside an INSTALLED package, or null
|
|
474
|
+
* when the path is not under `node_modules` or nothing is installed there.
|
|
303
475
|
*
|
|
304
476
|
* `node_modules` is not the project's code: nothing written there survives the
|
|
305
477
|
* next install, no deploy carries it, and a sandbox whose dependency tree is
|
|
@@ -312,6 +484,19 @@ export function moleculeDistReadNote(path) {
|
|
|
312
484
|
* putting the file back. Reading these files stays allowed (that is how the
|
|
313
485
|
* declarations above are read at all).
|
|
314
486
|
*
|
|
487
|
+
* What is protected is the INSTALL, not the spelling: the decision comes from
|
|
488
|
+
* {@link InstalledPackageFacts}, never from the path alone (m106 above). Three
|
|
489
|
+
* things are the run's own to remove or replace —
|
|
490
|
+
*
|
|
491
|
+
* - a `node_modules` that is a SYMLINK: `rm`/`unlink` removes the link, never the
|
|
492
|
+
* tree it points at, so a link the run made cannot clobber anything;
|
|
493
|
+
* - anything under the run's own `.mol-scratch/` directory;
|
|
494
|
+
* - a `node_modules` with no `package.json` under it (nothing was ever installed).
|
|
495
|
+
*
|
|
496
|
+
* A path INSIDE the link is still decided by what resolves there: a link into a
|
|
497
|
+
* real tree is refused, because removing or writing through it reaches the
|
|
498
|
+
* install.
|
|
499
|
+
*
|
|
315
500
|
* The restore route is named because the model that has ALREADY clobbered a file
|
|
316
501
|
* (this refusal is too late for it) is the one who needs it, and because the
|
|
317
502
|
* obvious repair — `npm install <pkg>` — is itself refused in the pre-installed
|
|
@@ -319,19 +504,34 @@ export function moleculeDistReadNote(path) {
|
|
|
319
504
|
* m91 used, and what both guards leave alone.
|
|
320
505
|
*
|
|
321
506
|
* @param path - An absolute path a tool is about to write.
|
|
507
|
+
* @param probe - Looks at the filesystem where the path lives.
|
|
322
508
|
* @returns The one-line refusal naming the package, or null when nothing is installed there.
|
|
323
509
|
*/
|
|
324
|
-
export function installedPackageWriteRefusal(path) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const at = /(?:^|\/)node_modules(?:\/|$)/.exec(path);
|
|
328
|
-
if (!at)
|
|
510
|
+
export async function installedPackageWriteRefusal(path, probe) {
|
|
511
|
+
const parts = nodeModulesParts(path);
|
|
512
|
+
if (!parts)
|
|
329
513
|
return null;
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
514
|
+
const { entry, name } = parts;
|
|
515
|
+
// The run's own scratch directory is the run's to clean: the platform tells it
|
|
516
|
+
// to put throwaway work there (system prompt, "Throwaway probes go in
|
|
517
|
+
// `.mol-scratch/`"), so nothing in it is the pre-installed tree.
|
|
518
|
+
if (/(?:^|\/)\.mol-scratch(?:\/|$)/.test(path))
|
|
519
|
+
return null;
|
|
520
|
+
// No probe, or a probe that could not answer: keep the old, conservative
|
|
521
|
+
// reading (everything under node_modules is an install).
|
|
522
|
+
const facts = probe ? await probe(path).catch(() => null) : null;
|
|
523
|
+
const symlink = facts?.symlink ?? false;
|
|
524
|
+
const installed = facts?.installed ?? true;
|
|
525
|
+
// The entry itself — the link (or a tree with nothing in it) the run may take
|
|
526
|
+
// away. Removing a symlink deletes the LINK only; its target is untouched.
|
|
527
|
+
if (name === null && (symlink || !installed))
|
|
528
|
+
return null;
|
|
529
|
+
// A path under the entry: `installed` resolved through any link, so it is true
|
|
530
|
+
// exactly when a write here would land in a real installation.
|
|
531
|
+
if (name !== null && !installed)
|
|
532
|
+
return null;
|
|
533
|
+
const dir = name ? `${entry}/${name}` : null;
|
|
534
|
+
const what = name ? `the installed package ${name}` : 'an installed node_modules tree';
|
|
335
535
|
const change = 'Installed packages are read-only: the next install replaces that file and a deploy never ' +
|
|
336
536
|
'carries it. Change its behaviour from your app instead (its options, your own handler, a ' +
|
|
337
537
|
'bond), and where the package itself is wrong, report it rather than patching it here. ' +
|
|
@@ -488,9 +688,11 @@ export function buildTools(backend, config) {
|
|
|
488
688
|
* @param path - Relative or absolute path requested by the tool input.
|
|
489
689
|
* @returns The normalized path, when writing it is allowed.
|
|
490
690
|
*/
|
|
491
|
-
function resolveWrite(path) {
|
|
691
|
+
async function resolveWrite(path) {
|
|
492
692
|
const resolved = resolve(path);
|
|
493
|
-
|
|
693
|
+
// The guard decides from what is installed where the path lives, so the
|
|
694
|
+
// backend is asked (one command, only for a path that names node_modules).
|
|
695
|
+
const refusal = await installedPackageWriteRefusal(resolved, shellInstalledPackageProbe((command) => backend.run(command, { timeout: 8000 })));
|
|
494
696
|
if (refusal)
|
|
495
697
|
throw new ToolInputError(refusal);
|
|
496
698
|
return resolved;
|
|
@@ -1046,7 +1248,7 @@ export function buildTools(backend, config) {
|
|
|
1046
1248
|
const argErr = pathArgError(input.path, 'write_file');
|
|
1047
1249
|
if (argErr)
|
|
1048
1250
|
return { error: argErr };
|
|
1049
|
-
const path = resolveWrite(input.path);
|
|
1251
|
+
const path = await resolveWrite(input.path);
|
|
1050
1252
|
const content = input.content;
|
|
1051
1253
|
if (content.length > MAX_WRITE_SIZE)
|
|
1052
1254
|
return {
|
|
@@ -1095,7 +1297,7 @@ export function buildTools(backend, config) {
|
|
|
1095
1297
|
const argErr = pathArgError(input.path, 'edit_file');
|
|
1096
1298
|
if (argErr)
|
|
1097
1299
|
return { error: argErr };
|
|
1098
|
-
const path = resolveWrite(input.path);
|
|
1300
|
+
const path = await resolveWrite(input.path);
|
|
1099
1301
|
// Support both formats: { replacements: [...] } (batch) and { old_string, new_string } (single, backwards-compatible)
|
|
1100
1302
|
let replacements = input.replacements;
|
|
1101
1303
|
// Accept the single format when BOTH are strings — `typeof` (not truthy) so
|
|
@@ -1729,7 +1931,7 @@ export function buildTools(backend, config) {
|
|
|
1729
1931
|
}
|
|
1730
1932
|
},
|
|
1731
1933
|
async create_directory(input) {
|
|
1732
|
-
const path = resolveWrite(input.path);
|
|
1934
|
+
const path = await resolveWrite(input.path);
|
|
1733
1935
|
const parentPath = path.substring(0, path.lastIndexOf('/')) || root;
|
|
1734
1936
|
const symlinkErr = await checkSymlink(parentPath);
|
|
1735
1937
|
if (symlinkErr)
|
|
@@ -1747,8 +1949,8 @@ export function buildTools(backend, config) {
|
|
|
1747
1949
|
async rename_file(input) {
|
|
1748
1950
|
// Both ends: moving a package's file OUT is as much a change to the installed
|
|
1749
1951
|
// package as moving one in.
|
|
1750
|
-
const oldPath = resolveWrite(input.old_path);
|
|
1751
|
-
const newPath = resolveWrite(input.new_path);
|
|
1952
|
+
const oldPath = await resolveWrite(input.old_path);
|
|
1953
|
+
const newPath = await resolveWrite(input.new_path);
|
|
1752
1954
|
const oldSymlinkErr = await checkSymlink(oldPath);
|
|
1753
1955
|
if (oldSymlinkErr)
|
|
1754
1956
|
return { error: oldSymlinkErr };
|
|
@@ -1770,7 +1972,7 @@ export function buildTools(backend, config) {
|
|
|
1770
1972
|
}
|
|
1771
1973
|
},
|
|
1772
1974
|
async delete_file(input) {
|
|
1773
|
-
const path = resolveWrite(input.path);
|
|
1975
|
+
const path = await resolveWrite(input.path);
|
|
1774
1976
|
const symlinkErr = await checkSymlink(path);
|
|
1775
1977
|
if (symlinkErr)
|
|
1776
1978
|
return { error: symlinkErr };
|
|
@@ -1820,7 +2022,28 @@ export function buildTools(backend, config) {
|
|
|
1820
2022
|
cwd,
|
|
1821
2023
|
readFile: (path) => backend.readFile(path),
|
|
1822
2024
|
});
|
|
1823
|
-
|
|
2025
|
+
let command = traced.command;
|
|
2026
|
+
// A leading `sleep N; <read of a background task's log>` is a hand-rolled poll, and the
|
|
2027
|
+
// platform knows the task's state better than the log's tail does (see
|
|
2028
|
+
// {@link backgroundTaskState}): X1 xr-molecule-flash-m105 slept 225 s this way, two of the
|
|
2029
|
+
// three on tasks that had already ended. The sleep is dropped and the rest of the command
|
|
2030
|
+
// runs at once; with no background task behind it, the sleep is left exactly as sent — the
|
|
2031
|
+
// tool's own budget is the existing bound.
|
|
2032
|
+
const sleepPeek = leadingSleepPeek(command);
|
|
2033
|
+
const peekedTask = sleepPeek
|
|
2034
|
+
? await backgroundTaskState(backend, command, sleepPeek.seconds, Math.max(5_000, (commandBudgetMs ?? 290_000) - 10_000))
|
|
2035
|
+
: null;
|
|
2036
|
+
const sleepNote = peekedTask?.note ?? null;
|
|
2037
|
+
if (sleepPeek && peekedTask) {
|
|
2038
|
+
command = sleepPeek.rest;
|
|
2039
|
+
if (!command) {
|
|
2040
|
+
return {
|
|
2041
|
+
status: 'task_state',
|
|
2042
|
+
note: peekedTask.note,
|
|
2043
|
+
...(peekedTask.slice.trim() ? { outputSoFar: sanitizeOutput(peekedTask.slice) } : {}),
|
|
2044
|
+
};
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1824
2047
|
// Detached: start it, hand back a handle, let the turn continue. A
|
|
1825
2048
|
// command that outlives the tool's ceiling is otherwise simply lost —
|
|
1826
2049
|
// measured across six agent runs, ten of them were killed at the
|
|
@@ -1937,18 +2160,22 @@ export function buildTools(backend, config) {
|
|
|
1937
2160
|
...(output.length > MAX_OUTPUT_SIZE
|
|
1938
2161
|
? { truncated: true, fullOutput: log }
|
|
1939
2162
|
: {}),
|
|
1940
|
-
error: `${
|
|
2163
|
+
error: (sleepNote ? `${sleepNote} ` : '') +
|
|
2164
|
+
`${cutRunSentence(cut)} ` +
|
|
1941
2165
|
(isSuiteRunCommand(command) ? SUITE_OVER_BUDGET_ADVICE : SPLIT_WORK_ADVICE),
|
|
1942
2166
|
};
|
|
1943
2167
|
}
|
|
1944
2168
|
// The exit file holds the PIPELINE's code: when a stage this rewrite could not
|
|
1945
2169
|
// prove is output-only still follows the suite runner (`| tee run.log`), that code
|
|
1946
|
-
// is the filter's — report none and let the log's tally decide (m101 +25:54).
|
|
1947
|
-
|
|
1948
|
-
const
|
|
2170
|
+
// is the filter's — report none and let the log's tally decide (m101 +25:54). The
|
|
2171
|
+
// note says this only when the parse found such a stage (m105 +08:12).
|
|
2172
|
+
const placement = suiteRunPipelinePlacement(streaming.command);
|
|
2173
|
+
const reported = suiteRunExitCode(code, output, placement.piped);
|
|
2174
|
+
const exitNote = suiteExitCodeNote(placement, reported);
|
|
1949
2175
|
const notes = [
|
|
2176
|
+
...(sleepNote ? [sleepNote] : []),
|
|
1950
2177
|
...streaming.changes,
|
|
1951
|
-
...(
|
|
2178
|
+
...(exitNote === null ? [] : [exitNote]),
|
|
1952
2179
|
];
|
|
1953
2180
|
return {
|
|
1954
2181
|
stdout,
|
|
@@ -1965,22 +2192,25 @@ export function buildTools(backend, config) {
|
|
|
1965
2192
|
taskId: id,
|
|
1966
2193
|
log,
|
|
1967
2194
|
exitFile,
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2195
|
+
...(peekedTask?.slice.trim() ? { outputSoFar: sanitizeOutput(peekedTask.slice) } : {}),
|
|
2196
|
+
note: (sleepNote ? `${sleepNote} ` : '') +
|
|
2197
|
+
(overCeiling
|
|
2198
|
+
? declaredSeconds > ceilingSeconds
|
|
2199
|
+
? `This command asks for ${declaredSeconds}s, more than this tool's ${ceilingSeconds}s, and its ` +
|
|
2200
|
+
'output only appears when it ends — so it was started in the background instead. '
|
|
2201
|
+
: `A Playwright run can take longer than this tool's ${ceilingSeconds}s, and behind a pipe its ` +
|
|
2202
|
+
'output only appears when it ends — so it was started in the background instead. '
|
|
2203
|
+
: '') +
|
|
1975
2204
|
`Started in the background. Its output is written to ${log} and comes back as the ` +
|
|
1976
|
-
`stdout of wait_for_task when the command finishes (read_file cannot reach /tmp
|
|
1977
|
-
`
|
|
1978
|
-
`
|
|
1979
|
-
`
|
|
2205
|
+
`stdout of wait_for_task when the command finishes (read_file cannot reach /tmp). If ` +
|
|
2206
|
+
`you have other work, do it and check back. If you need this result before you can ` +
|
|
2207
|
+
`continue, call wait_for_task with this taskId: it returns the moment the command ` +
|
|
2208
|
+
`ends, returns early when a failure cause repeats, and reports progress and the ` +
|
|
2209
|
+
`failures so far — never sleep and read the log by hand.` +
|
|
1980
2210
|
(streaming.changes.length ? ` ${streaming.changes.join(' ')}` : '') +
|
|
1981
2211
|
(prioritized.note ? ` ${prioritized.note}` : '') +
|
|
1982
2212
|
(ownFile
|
|
1983
|
-
? ` This command writes its own output to ${ownFile}, so the task log stays empty:
|
|
2213
|
+
? ` This command writes its own output to ${ownFile}, so the task log stays empty: wait_for_task reads that file when the command ends and reports its progress while it runs — never sleep and tail it by hand.`
|
|
1984
2214
|
: ''),
|
|
1985
2215
|
};
|
|
1986
2216
|
}
|
|
@@ -2063,7 +2293,8 @@ export function buildTools(backend, config) {
|
|
|
2063
2293
|
// The facts, so a reader that can also see the suite itself (its own `.hurl` file
|
|
2064
2294
|
// count) says the same sentence with the total the output alone cannot give.
|
|
2065
2295
|
cutRun: cut,
|
|
2066
|
-
error: `${
|
|
2296
|
+
error: (sleepNote ? `${sleepNote} ` : '') +
|
|
2297
|
+
`${cutLead} ` +
|
|
2067
2298
|
// The tool's own budget stopping a run that printed nothing: keep the sentence that
|
|
2068
2299
|
// says why the output is empty, since nothing else in the result can say it.
|
|
2069
2300
|
(cut.stoppedBy === 'tool-budget'
|
|
@@ -2078,8 +2309,11 @@ export function buildTools(backend, config) {
|
|
|
2078
2309
|
stdout,
|
|
2079
2310
|
stderr,
|
|
2080
2311
|
exitCode: result.exitCode,
|
|
2081
|
-
...(
|
|
2082
|
-
|
|
2312
|
+
...(peekedTask?.slice.trim() ? { outputSoFar: sanitizeOutput(peekedTask.slice) } : {}),
|
|
2313
|
+
...(sleepNote || ceilingNote || traced.change
|
|
2314
|
+
? {
|
|
2315
|
+
note: [sleepNote, ceilingNote, traced.change].filter(Boolean).join(' '),
|
|
2316
|
+
}
|
|
2083
2317
|
: {}),
|
|
2084
2318
|
};
|
|
2085
2319
|
}
|
|
@@ -2383,8 +2617,8 @@ export function buildTools(backend, config) {
|
|
|
2383
2617
|
status: 'running',
|
|
2384
2618
|
waitedMs,
|
|
2385
2619
|
...sliceField,
|
|
2386
|
-
note: `${progress} Call wait_for_task again to keep waiting
|
|
2387
|
-
`
|
|
2620
|
+
note: `${progress} Call wait_for_task again to keep waiting — it returns the moment the ` +
|
|
2621
|
+
`command ends and reports the failures as they appear.${midRun}`,
|
|
2388
2622
|
};
|
|
2389
2623
|
}
|
|
2390
2624
|
if (waitedBefore + waitedMs >= stuckAfterMs()) {
|
|
@@ -2484,10 +2718,13 @@ export function buildTools(backend, config) {
|
|
|
2484
2718
|
// Same shaping as a foreground exec result, so the executor reads both
|
|
2485
2719
|
// the same way. The exit file holds the PIPELINE's code: when a stage this rewrite could not
|
|
2486
2720
|
// prove is output-only still follows the suite runner (`| tee run.log`), that code is the
|
|
2487
|
-
// filter's — report none and let the log's tally decide (m101 +25:54).
|
|
2488
|
-
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2721
|
+
// filter's — report none and let the log's tally decide (m101 +25:54). The note says this
|
|
2722
|
+
// only when the parse found such a stage (m105 +08:12).
|
|
2723
|
+
const placement = run
|
|
2724
|
+
? suiteRunPipelinePlacement(run.command)
|
|
2725
|
+
: { piped: false, unplaced: false };
|
|
2726
|
+
const reported = suiteRunExitCode(exitCode, output, placement.piped);
|
|
2727
|
+
const noCodeNote = suiteExitCodeNote(placement, reported);
|
|
2491
2728
|
return {
|
|
2492
2729
|
taskId,
|
|
2493
2730
|
exitCode: reported,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;CACf;AAED,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,QAAO,MA+LvC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAyBnE;AAiBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B/F;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"trace-artifacts.d.ts","sourceRoot":"","sources":["../src/trace-artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAMH,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,iGAAiG;IACjG,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAA;CACf;AAED,iGAAiG;AACjG,MAAM,WAAW,aAAa;IAC5B,0FAA0F;IAC1F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,6FAA6F;IAC7F,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAEpC,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AAErC,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,oBAAoB,QAAO,MA+LvC,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAyBnE;AAiBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B/F;AAaD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,8BAA8B,CAAA;AA2G/D,oGAAoG;AACpG,MAAM,WAAW,qBAAqB;IACpC,0BAA0B;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5C,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAyBjC"}
|