@molecule/api-ai-tools 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,51 @@
1
+ /**
2
+ * A leading `sleep` in an executor's command: the seconds it would have waited, and the command
3
+ * that follows it.
4
+ *
5
+ * X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) spent 225 s of its 60-minute clock
6
+ * inside `exec_command` calls of the shape `sleep N; tail <task log>`. The platform's own launch
7
+ * note said "peek with exec_command: tail <log>" beside "never sleep", and the only concrete
8
+ * instruction was the peek, so the executor built the poll by hand. Two of its three sleeps were
9
+ * provably dead — the task had ended 0.1 s in (`playwright: not found`, exit 127) and another had
10
+ * already printed `Error: No tests found` — and the third spent 120 s asleep beside a live
11
+ * 139-test run whose failures were already in the log.
12
+ *
13
+ * The platform does not have to answer "what is the task doing" from a sleep: `wait_for_task`
14
+ * already returns the moment the task ends, early on a proven repeating cause, and with the
15
+ * progress and failures the log holds. This module answers only the parsing question — is the
16
+ * command's FIRST statement a `sleep` of {@link MIN_PEEK_SLEEP_SECONDS} seconds or more, and what
17
+ * follows it — from the package's own shell parse ({@link locatedCommandSegments}), so a `sleep`
18
+ * named in an argument (`grep -n 'sleep 60' notes.md`) or written after another statement
19
+ * (`cd x && sleep 60`) is not one.
20
+ *
21
+ * @module
22
+ */
23
+ /**
24
+ * The shortest leading sleep the platform drops: below it the wall clock it costs is noise, and a
25
+ * short settle (`sleep 2; curl …`) is a legitimate thing to run.
26
+ */
27
+ export declare const MIN_PEEK_SLEEP_SECONDS = 5;
28
+ /** The leading `sleep` a command opens with, and the command that follows it. */
29
+ export interface LeadingSleep {
30
+ /** How many seconds the statement would have slept (GNU sleep sums its operands). */
31
+ seconds: number;
32
+ /** What follows the leading statement, trimmed — '' when the sleep was the whole command. */
33
+ rest: string;
34
+ }
35
+ /**
36
+ * The leading `sleep` of a command, when the first statement is one and it asks for at least
37
+ * {@link MIN_PEEK_SLEEP_SECONDS}.
38
+ *
39
+ * The statement is read by {@link locatedCommandSegments} — the same parse every other command
40
+ * classifier uses — so only a `sleep` in command position of the FIRST statement counts. The end
41
+ * of that statement is then found by walking forward from its own start: the statement is
42
+ * `sleep <duration>`, whose words carry no quotes and open no substitution, so the first unquoted
43
+ * `;`, newline or `&` ends it. A `|` is refused: dropping the left side of a pipeline would hand
44
+ * the right side its stdin instead, and a `||` fallback would run on a success that never did.
45
+ *
46
+ * @param command - The shell command the executor sent.
47
+ * @returns The seconds and the remaining command, or null when the command does not open with a
48
+ * long `sleep`.
49
+ */
50
+ export declare function leadingSleepPeek(command: string): LeadingSleep | null;
51
+ //# sourceMappingURL=sleep-peek.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep-peek.d.ts","sourceRoot":"","sources":["../src/sleep-peek.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAA;IACf,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAyBrE"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * A leading `sleep` in an executor's command: the seconds it would have waited, and the command
3
+ * that follows it.
4
+ *
5
+ * X1 xr-molecule-flash-m105 (+08:49 → +10:53, 2026-10-17) spent 225 s of its 60-minute clock
6
+ * inside `exec_command` calls of the shape `sleep N; tail <task log>`. The platform's own launch
7
+ * note said "peek with exec_command: tail <log>" beside "never sleep", and the only concrete
8
+ * instruction was the peek, so the executor built the poll by hand. Two of its three sleeps were
9
+ * provably dead — the task had ended 0.1 s in (`playwright: not found`, exit 127) and another had
10
+ * already printed `Error: No tests found` — and the third spent 120 s asleep beside a live
11
+ * 139-test run whose failures were already in the log.
12
+ *
13
+ * The platform does not have to answer "what is the task doing" from a sleep: `wait_for_task`
14
+ * already returns the moment the task ends, early on a proven repeating cause, and with the
15
+ * progress and failures the log holds. This module answers only the parsing question — is the
16
+ * command's FIRST statement a `sleep` of {@link MIN_PEEK_SLEEP_SECONDS} seconds or more, and what
17
+ * follows it — from the package's own shell parse ({@link locatedCommandSegments}), so a `sleep`
18
+ * named in an argument (`grep -n 'sleep 60' notes.md`) or written after another statement
19
+ * (`cd x && sleep 60`) is not one.
20
+ *
21
+ * @module
22
+ */
23
+ import { durationSeconds, locatedCommandSegments } from './command-position.js';
24
+ /**
25
+ * The shortest leading sleep the platform drops: below it the wall clock it costs is noise, and a
26
+ * short settle (`sleep 2; curl …`) is a legitimate thing to run.
27
+ */
28
+ export const MIN_PEEK_SLEEP_SECONDS = 5;
29
+ /**
30
+ * The leading `sleep` of a command, when the first statement is one and it asks for at least
31
+ * {@link MIN_PEEK_SLEEP_SECONDS}.
32
+ *
33
+ * The statement is read by {@link locatedCommandSegments} — the same parse every other command
34
+ * classifier uses — so only a `sleep` in command position of the FIRST statement counts. The end
35
+ * of that statement is then found by walking forward from its own start: the statement is
36
+ * `sleep <duration>`, whose words carry no quotes and open no substitution, so the first unquoted
37
+ * `;`, newline or `&` ends it. A `|` is refused: dropping the left side of a pipeline would hand
38
+ * the right side its stdin instead, and a `||` fallback would run on a success that never did.
39
+ *
40
+ * @param command - The shell command the executor sent.
41
+ * @returns The seconds and the remaining command, or null when the command does not open with a
42
+ * long `sleep`.
43
+ */
44
+ export function leadingSleepPeek(command) {
45
+ if (typeof command !== 'string')
46
+ return null;
47
+ const first = locatedCommandSegments(command)[0];
48
+ if (!first || first.start === null || first.segment === null)
49
+ return null;
50
+ if (first.segment.program !== 'sleep')
51
+ return null;
52
+ // GNU sleep sums its operands (`sleep 45 15`); every one of them has to be a duration.
53
+ let seconds = 0;
54
+ for (const arg of first.segment.args) {
55
+ const part = durationSeconds(arg);
56
+ if (part === null)
57
+ return null;
58
+ seconds += part;
59
+ }
60
+ if (seconds < MIN_PEEK_SLEEP_SECONDS)
61
+ return null;
62
+ let end = command.length;
63
+ for (let i = first.start; i < command.length; i++) {
64
+ const char = command[i] ?? '';
65
+ if (char === '|')
66
+ return null;
67
+ if (char === ';' || char === '\n' || char === '&') {
68
+ end = i;
69
+ break;
70
+ }
71
+ }
72
+ let at = end;
73
+ while (at < command.length && /[;&\s]/.test(command[at] ?? ''))
74
+ at += 1;
75
+ return { seconds, rest: command.slice(at).trim() };
76
+ }
@@ -50,12 +50,44 @@ export declare function withoutHeredocs(command: string): string;
50
50
  * @returns The target key.
51
51
  */
52
52
  export declare function suiteCommandTarget(command: string): string;
53
+ /**
54
+ * Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
55
+ * {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
56
+ *
57
+ * @param command - A command, or the Playwright statement inside it.
58
+ * @returns True when the statement loads a config of its own.
59
+ */
60
+ export declare function namesSuiteConfig(command: string): boolean;
61
+ /**
62
+ * The command with a `--config` added to the Playwright invocation it starts — or unchanged when
63
+ * it already names one, or starts no Playwright run.
64
+ *
65
+ * The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
66
+ * it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
67
+ *
68
+ * xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
69
+ * `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
70
+ * NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
71
+ * that says nothing about the app. The platform composes commands back to the executor too (the
72
+ * narrowed re-run of a finished run's failures), and a command it hands back has to carry the
73
+ * config the run was measured under, never the shape that caused that finding.
74
+ *
75
+ * @param command - The command as the executor sent it.
76
+ * @param config - The config the run loads, when the caller knows it.
77
+ * @returns The command with the flag, or the command unchanged.
78
+ */
79
+ export declare function withSuiteConfig(command: string, config: string | null | undefined): string;
53
80
  /**
54
81
  * The whole-suite command made scoped to the tests it should re-run instead: the same
55
82
  * invocation — same directory, same settings — with the named `file:line`s in place of the whole
56
83
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
57
84
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
58
85
  *
86
+ * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
87
+ * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
88
+ * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
89
+ * `Cannot navigate to invalid URL` because the re-run loaded no base URL).
90
+ *
59
91
  * @param command - The whole-suite run's command.
60
92
  * @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
61
93
  * for a run that started outside the suite, the absolute form Playwright matches each file
@@ -64,9 +96,11 @@ export declare function suiteCommandTarget(command: string): string;
64
96
  * (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
65
97
  * xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
66
98
  * a test the request had already said does not count.
99
+ * @param config - The config the run loaded, when the caller knows it; added when the command
100
+ * does not name one.
67
101
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
68
102
  */
69
- export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[]): string;
103
+ export declare function scopedRerunCommand(command: string, locations: readonly string[], excluded?: readonly string[], config?: string | null): string;
70
104
  /**
71
105
  * One suite run, as the rule that decides whether a second may start beside it reads it.
72
106
  *
@@ -1 +1 @@
1
- {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,GAC/B,MAAM,CA2BR;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
1
+ {"version":3,"file":"suite-overlap.d.ts","sourceRoot":"","sources":["../src/suite-overlap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAMH;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoB1D;AAKD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAc1F;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,QAAQ,GAAE,SAAS,MAAM,EAAO,EAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,MAAM,CA8BR;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,SAAS,EAAE,OAAO,CAAA;IAClB,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,gBAAgB,EACzD,SAAS,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ,EAAE,SAAS,CAAC,EAAE,GACrB,CAAC,GAAG,IAAI,CAMV;AAqDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQvD"}
@@ -74,12 +74,62 @@ export function suiteCommandTarget(command) {
74
74
  }
75
75
  return 'local';
76
76
  }
77
+ /** Whether a Playwright statement already names its own config (`-c`, `--config`, `--config=`). */
78
+ const NAMES_CONFIG = /(?:^|\s)(?:-c|--config)(?:\s+|=)/;
79
+ /**
80
+ * Whether a Playwright statement names its own config — the reading {@link withSuiteConfig} and
81
+ * {@link scopedRerunCommand} share, so neither adds a second `--config` to a command that has one.
82
+ *
83
+ * @param command - A command, or the Playwright statement inside it.
84
+ * @returns True when the statement loads a config of its own.
85
+ */
86
+ export function namesSuiteConfig(command) {
87
+ return NAMES_CONFIG.test(command);
88
+ }
89
+ /**
90
+ * The command with a `--config` added to the Playwright invocation it starts — or unchanged when
91
+ * it already names one, or starts no Playwright run.
92
+ *
93
+ * The flag rides the same statement the runner does: a `cd`, an env prefix and a `timeout` before
94
+ * it are left exactly as they were, and so is everything chained after the run (`| tail -50`).
95
+ *
96
+ * xr-molecule-flash-m113 (+19:09): the model's fix-round re-run dropped the
97
+ * `--config playwright.conduit.config.ts` both earlier runs had carried, so Playwright started with
98
+ * NO base URL and all 10 failures were `page.goto: Cannot navigate to invalid URL` — a failure list
99
+ * that says nothing about the app. The platform composes commands back to the executor too (the
100
+ * narrowed re-run of a finished run's failures), and a command it hands back has to carry the
101
+ * config the run was measured under, never the shape that caused that finding.
102
+ *
103
+ * @param command - The command as the executor sent it.
104
+ * @param config - The config the run loads, when the caller knows it.
105
+ * @returns The command with the flag, or the command unchanged.
106
+ */
107
+ export function withSuiteConfig(command, config) {
108
+ if (!config)
109
+ return command;
110
+ const at = command.search(/\bplaywright\s+test\b/);
111
+ if (at === -1)
112
+ return command;
113
+ const head = command.slice(0, at);
114
+ const rest = command.slice(at);
115
+ const end = rest.search(/&&|\|\||;|\|/);
116
+ const statement = end === -1 ? rest : rest.slice(0, end);
117
+ const tail = end === -1 ? '' : rest.slice(end);
118
+ if (namesSuiteConfig(statement))
119
+ return command;
120
+ return `${head}${statement.replace(/^(playwright\s+test)\b/, `$1 --config ${shellQuote(config)}`)}${tail}`;
121
+ }
77
122
  /**
78
123
  * The whole-suite command made scoped to the tests it should re-run instead: the same
79
124
  * invocation — same directory, same settings — with the named `file:line`s in place of the whole
80
125
  * suite, `--last-failed` dropped (it re-runs the tests that never ran too, X1 flash-m60) and
81
126
  * `--retries=0` set (a re-run of a known failure does not need to pay for retries).
82
127
  *
128
+ * `config` is the config the run was measured under ({@link withSuiteConfig}): the narrowed re-run
129
+ * carries it whenever the command itself does not, so a run that dropped `--config` cannot be
130
+ * handed back a command with the same defect (xr-molecule-flash-m113: every failing test was
131
+ * `Cannot navigate to invalid URL` because the re-run loaded no base URL).
132
+ *
83
133
  * @param command - The whole-suite run's command.
84
134
  * @param locations - The failing `file:line`s to re-run, as the run's own log printed them — or,
85
135
  * for a run that started outside the suite, the absolute form Playwright matches each file
@@ -88,9 +138,11 @@ export function suiteCommandTarget(command) {
88
138
  * (`--grep-invert`) so a re-run of the failing locations cannot report one of them again —
89
139
  * xr-molecule-flash-m101 re-ran the whole 139-test suite and spent ~4 minutes to be told about
90
140
  * a test the request had already said does not count.
141
+ * @param config - The config the run loaded, when the caller knows it; added when the command
142
+ * does not name one.
91
143
  * @returns The scoped command, or the command unchanged when it runs no Playwright suite.
92
144
  */
93
- export function scopedRerunCommand(command, locations, excluded = []) {
145
+ export function scopedRerunCommand(command, locations, excluded = [], config) {
94
146
  const at = command.search(/\bplaywright\s+test\b/);
95
147
  if (at === -1)
96
148
  return command;
@@ -110,10 +162,13 @@ export function scopedRerunCommand(command, locations, excluded = []) {
110
162
  .map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
111
163
  .join('|'))}`
112
164
  : '';
165
+ // The run's own config: the command's `--config` when it has one, else the config the caller
166
+ // recorded for the run. A re-run without it measures nothing (m113).
167
+ const carry = config && !namesSuiteConfig(statement) ? ` --config ${shellQuote(config)}` : '';
113
168
  const scoped = statement
114
169
  .replace(/\s*--last-failed\b/g, '')
115
170
  .replace(/\s*--retries(?:[= ]\d+)?/g, '')
116
- .replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${invert}`);
171
+ .replace(/^(playwright\s+test)\b/, `$1 ${locations.join(' ')} --retries=0${carry}${invert}`);
117
172
  return `${head}${scoped}${tail}`;
118
173
  }
119
174
  /**
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
- * The refusal for a write whose target is inside an installed package, or null
64
- * when the path is not under a `node_modules` directory.
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
  *
@@ -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;AA0G9C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAahG;AAyHD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAkEnE;;;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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAsBxE;AAgDD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,eAAe,GAAG,MAAM,EAAE,CAyzExF"}
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"}