@molecule/api-ai-tools 1.17.1 → 1.17.2

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 CHANGED
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
3
3
  Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
4
4
  Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
5
5
  To change this document, edit the module-level JSDoc in src/index.ts.
6
- Generated: 2026-11-06T15:20:37.703Z
6
+ Generated: 2026-11-06T17:48:40.314Z
7
7
  -->
8
8
 
9
9
  # @molecule/api-ai-tools
@@ -466,7 +466,7 @@ interface PlaywrightFeedbackResult {
466
466
  spec: string
467
467
  revision: number
468
468
  durationMs: number
469
- status: 'passed' | 'failed' | 'unavailable'
469
+ status: 'passed' | 'failed' | 'unavailable' | 'cancelled'
470
470
  failures: PlaywrightFeedbackFailure[]
471
471
  /** Native counts for this one diagnostic selection, never whole-suite proof. */
472
472
  nativeStats?: PlaywrightReportStats
@@ -486,6 +486,8 @@ interface PlaywrightFeedbackOptions {
486
486
  sources: string[],
487
487
  revision: number,
488
488
  emit: (result: PlaywrightFeedbackResult) => void,
489
+ /** Stop the owned native process group when cancelled; never wait for the tests to finish. */
490
+ signal: AbortSignal,
489
491
  ) => Promise<PlaywrightFeedbackResult>
490
492
  /** Only current native failures are delivered. Passing apps receive no note. */
491
493
  onFailures?: (result: PlaywrightFeedbackResult) => void
@@ -503,6 +505,8 @@ A nonblocking source-change subscription.
503
505
  interface PlaywrightFeedback {
504
506
  changed: (paths: readonly string[]) => void
505
507
  takeFailures: () => PlaywrightFeedbackResult[]
508
+ /** Cancel speculation before foreground work. Release after that work; nested callers are safe. */
509
+ pause: () => Promise<() => void>
506
510
  /** Wait only in tests or shutdown; file writes and deploys must never await this. */
507
511
  settled: () => Promise<void>
508
512
  close: () => Promise<void>
@@ -2733,6 +2737,7 @@ function runPlaywrightFeedback(
2733
2737
  timeoutMs?: number
2734
2738
  emit?: (result: PlaywrightFeedbackResult) => void
2735
2739
  pollMs?: number
2740
+ signal?: AbortSignal
2736
2741
  },
2737
2742
  ): Promise<PlaywrightFeedbackResult>
2738
2743
  ```
@@ -6006,7 +6011,12 @@ Peer dependencies:
6006
6011
  to batch source reads over either backend and `runPlaywrightFeedback` to run each
6007
6012
  supplied spec through its native command. Its incremental reporter supplies exact
6008
6013
  locator failures and the failure-time page snapshot. Healthy runs emit no note;
6009
- no suite schedules no runner. `settled()`/`close()` are for tests and shutdown,
6014
+ no suite schedules no runner. Pass the runner's cancellation signal to
6015
+ `runPlaywrightFeedback` in its options and call `pause()` before foreground tools,
6016
+ then invoke its returned release function afterwards. Speculation yields to
6017
+ independent native CLI tests/builds and uses OS idle CPU/I/O priority. Its
6018
+ private temporary reports are not a user test result or something to poll.
6019
+ New writes cancel stale native work. `settled()`/`close()` are for tests and shutdown,
6010
6020
  never a deployment barrier. Diagnoses are not whole-suite completion proof.
6011
6021
  - **Opaque command plans:** prepare each logical step with `streamingBackgroundCommand`,
6012
6022
  call `createCommandPlan`, and attach it to the executed wrapper with `commandWithPlan`.
package/dist/index.d.ts CHANGED
@@ -17,7 +17,12 @@
17
17
  * to batch source reads over either backend and `runPlaywrightFeedback` to run each
18
18
  * supplied spec through its native command. Its incremental reporter supplies exact
19
19
  * locator failures and the failure-time page snapshot. Healthy runs emit no note;
20
- * no suite schedules no runner. `settled()`/`close()` are for tests and shutdown,
20
+ * no suite schedules no runner. Pass the runner's cancellation signal to
21
+ * `runPlaywrightFeedback` in its options and call `pause()` before foreground tools,
22
+ * then invoke its returned release function afterwards. Speculation yields to
23
+ * independent native CLI tests/builds and uses OS idle CPU/I/O priority. Its
24
+ * private temporary reports are not a user test result or something to poll.
25
+ * New writes cancel stale native work. `settled()`/`close()` are for tests and shutdown,
21
26
  * never a deployment barrier. Diagnoses are not whole-suite completion proof.
22
27
  * - **Opaque command plans:** prepare each logical step with `streamingBackgroundCommand`,
23
28
  * call `createCommandPlan`, and attach it to the executed wrapper with `commandWithPlan`.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+JG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oCAAoC,CAAA;AAClD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoKG;AAEH,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AACrC,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,cAAc,CAAA;AAC5B,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kBAAkB,CAAA;AAChC,cAAc,oBAAoB,CAAA;AAClC,cAAc,yBAAyB,CAAA;AACvC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oCAAoC,CAAA;AAClD,cAAc,gCAAgC,CAAA;AAC9C,cAAc,wBAAwB,CAAA;AACtC,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,YAAY,CAAA;AAC1B,cAAc,sBAAsB,CAAA;AACpC,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -17,7 +17,12 @@
17
17
  * to batch source reads over either backend and `runPlaywrightFeedback` to run each
18
18
  * supplied spec through its native command. Its incremental reporter supplies exact
19
19
  * locator failures and the failure-time page snapshot. Healthy runs emit no note;
20
- * no suite schedules no runner. `settled()`/`close()` are for tests and shutdown,
20
+ * no suite schedules no runner. Pass the runner's cancellation signal to
21
+ * `runPlaywrightFeedback` in its options and call `pause()` before foreground tools,
22
+ * then invoke its returned release function afterwards. Speculation yields to
23
+ * independent native CLI tests/builds and uses OS idle CPU/I/O priority. Its
24
+ * private temporary reports are not a user test result or something to poll.
25
+ * New writes cancel stale native work. `settled()`/`close()` are for tests and shutdown,
21
26
  * never a deployment barrier. Diagnoses are not whole-suite completion proof.
22
27
  * - **Opaque command plans:** prepare each logical step with `streamingBackgroundCommand`,
23
28
  * call `createCommandPlan`, and attach it to the executed wrapper with `commandWithPlan`.
@@ -0,0 +1,14 @@
1
+ interface FeedbackProcessOptions {
2
+ command: string;
3
+ cwd: string;
4
+ root: string;
5
+ directory: string;
6
+ }
7
+ /**
8
+ * Compose a private native supervisor without putting the test command on its argv.
9
+ * @param config - The known command, working directory and owned scratch directory.
10
+ * @returns A self-contained native Node program.
11
+ */
12
+ export declare function feedbackProcessSource(config: FeedbackProcessOptions): string;
13
+ export {};
14
+ //# sourceMappingURL=playwright-feedback-process.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playwright-feedback-process.d.ts","sourceRoot":"","sources":["../src/playwright-feedback-process.ts"],"names":[],"mappings":"AASA,UAAU,sBAAsB;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CAClB;AAkLD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAE5E"}
@@ -0,0 +1,189 @@
1
+ /** Serialized into either execution backend; only standard Node/OS operations run there. */
2
+ function feedbackProcess(loadModule, config) {
3
+ const fs = loadModule('node:fs');
4
+ const cp = loadModule('node:child_process');
5
+ const os = loadModule('node:os');
6
+ const path = loadModule('node:path');
7
+ const stopFile = path.join(config.directory, 'stop');
8
+ const yielded = path.join(config.directory, 'yielded');
9
+ try {
10
+ os.setPriority(0, 19);
11
+ }
12
+ catch (_error) {
13
+ /* best effort on platforms without POSIX priorities */
14
+ }
15
+ let child = undefined;
16
+ let done = false;
17
+ const owned = new Map();
18
+ const rememberChildren = () => {
19
+ if (!child?.pid || process.platform !== 'linux')
20
+ return;
21
+ const processes = new Map();
22
+ for (const pid of fs.readdirSync('/proc')) {
23
+ if (!/^\d+$/.test(pid))
24
+ continue;
25
+ try {
26
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
27
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
28
+ processes.set(Number(pid), { parent: Number(fields[1]), ticks: fields[19] });
29
+ }
30
+ catch (_error) {
31
+ /* already exited */
32
+ }
33
+ }
34
+ for (const [pid, entry] of processes) {
35
+ const seen = new Set();
36
+ let ancestor = pid;
37
+ while (ancestor > 1 && !seen.has(ancestor)) {
38
+ const ticks = owned.get(ancestor);
39
+ if (ancestor === child.pid ||
40
+ (ticks !== undefined && ticks === processes.get(ancestor)?.ticks)) {
41
+ owned.set(pid, entry.ticks);
42
+ break;
43
+ }
44
+ seen.add(ancestor);
45
+ ancestor = processes.get(ancestor)?.parent ?? 0;
46
+ }
47
+ }
48
+ };
49
+ const stop = (foreground = false) => {
50
+ if (done)
51
+ return;
52
+ done = true;
53
+ if (foreground)
54
+ fs.writeFileSync(yielded, 'foreground');
55
+ rememberChildren();
56
+ // Chromium creates a separate process group. Remember descendants before
57
+ // killing the launcher and verify start ticks so a reused PID is never hit.
58
+ for (const [pid, ticks] of [...owned].reverse()) {
59
+ try {
60
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
61
+ if (stat.slice(stat.lastIndexOf(')') + 2).split(' ')[19] === ticks)
62
+ process.kill(pid, 'SIGKILL');
63
+ }
64
+ catch (_error) {
65
+ /* already exited */
66
+ }
67
+ }
68
+ // This process created the detached group. Never signal by command/name.
69
+ if (child?.pid) {
70
+ try {
71
+ process.kill(-child.pid, 'SIGKILL');
72
+ }
73
+ catch (_error) {
74
+ /* already exited */
75
+ }
76
+ }
77
+ };
78
+ const ancestors = new Set();
79
+ let parent = process.pid;
80
+ while (parent > 1 && !ancestors.has(parent)) {
81
+ ancestors.add(parent);
82
+ try {
83
+ const stat = fs.readFileSync(`/proc/${parent}/stat`, 'utf8');
84
+ parent = Number(stat.slice(stat.lastIndexOf(')') + 2).split(' ')[1]);
85
+ }
86
+ catch (_error) {
87
+ break;
88
+ }
89
+ }
90
+ // CLI agents can launch native tests without using our backend. /proc supplies
91
+ // process identity and cwd, so they need no hook, lock or instruction to yield.
92
+ // The host adapter cancels before its own tools; this catches independent CLI work.
93
+ const foreground = () => {
94
+ if (process.platform !== 'linux')
95
+ return false;
96
+ const project = fs.statSync(config.root);
97
+ for (const pid of fs.readdirSync('/proc')) {
98
+ if (!/^\d+$/.test(pid) || ancestors.has(Number(pid)))
99
+ continue;
100
+ try {
101
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
102
+ const fields = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
103
+ if (fields[0] === 'Z' ||
104
+ Number(fields[2]) === child?.pid ||
105
+ owned.get(Number(pid)) === fields[19])
106
+ continue;
107
+ const cwd = fs.readlinkSync(`/proc/${pid}/cwd`);
108
+ if (cwd !== config.root && !cwd.startsWith(config.root + path.sep))
109
+ continue;
110
+ // Separate mount namespaces can both call their project /workspace.
111
+ const other = fs.statSync(`/proc/${pid}/root${config.root}`);
112
+ if (other.dev !== project.dev || other.ino !== project.ino)
113
+ continue;
114
+ const argv = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8').split('\0').filter(Boolean);
115
+ const executable = path.basename(argv[0] || '');
116
+ // npm replaces argv with its process title before it starts the script.
117
+ if (/^(?:npm|npx|pnpm|yarn|bun)(?: run)? (?:test(?::[\w-]+)?|build|deploy|check|typecheck)(?: |$)/.test(executable))
118
+ return true;
119
+ const native = argv
120
+ .slice(0, 3)
121
+ .some((a) => /(?:^|\/)(?:@playwright\/test|playwright(?:-core)?|hurl)(?:\/|$)/.test(a));
122
+ if (native || /^(?:hurl|playwright|fly|flyctl|vercel|netlify)$/.test(executable))
123
+ return true;
124
+ if (argv.slice(1, 4).some((a) => a === 'deploy' || a === 'build'))
125
+ return true;
126
+ if (/^(?:npm|npx|pnpm|yarn|bun|corepack)(?:\.c?js)?$/.test(executable) ||
127
+ argv.some((a) => /\/(?:npm|npx)-cli\.js$/.test(a))) {
128
+ if (argv.some((a) => /^(?:test(?::[\w-]+)?|e2e|build|deploy|check|typecheck)$/.test(a)))
129
+ return true;
130
+ }
131
+ }
132
+ catch (_error) {
133
+ /* processes can exit between reads */
134
+ }
135
+ }
136
+ return false;
137
+ };
138
+ if (fs.existsSync(stopFile) || foreground()) {
139
+ fs.writeFileSync(yielded, 'foreground');
140
+ process.exitCode = 125;
141
+ return;
142
+ }
143
+ // OS idle CPU and I/O scheduling are inherited by the runner and Chromium.
144
+ // Fall back to nice(19) where chrt/ionice are unavailable.
145
+ const lowPriority = 'if command -v ionice >/dev/null 2>&1; then ionice -c 3 -p $$ 2>/dev/null; fi; if command -v chrt >/dev/null 2>&1; then chrt --idle --pid 0 $$ 2>/dev/null; fi; exec sh -c "$1"';
146
+ child = cp.spawn('sh', ['-c', lowPriority, 'feedback', config.command], {
147
+ cwd: config.cwd,
148
+ detached: true,
149
+ stdio: 'inherit',
150
+ });
151
+ const watch = fs.watch(config.directory, () => {
152
+ if (fs.existsSync(stopFile))
153
+ stop();
154
+ });
155
+ const poll = setInterval(() => {
156
+ rememberChildren();
157
+ if (foreground())
158
+ stop(true);
159
+ }, 100);
160
+ const terminate = () => stop();
161
+ process.on('SIGINT', terminate);
162
+ process.on('SIGTERM', terminate);
163
+ process.on('exit', terminate);
164
+ child.on('error', () => {
165
+ stop();
166
+ finish(1);
167
+ });
168
+ const finish = (code) => {
169
+ stop();
170
+ watch.close();
171
+ clearInterval(poll);
172
+ process.off('SIGINT', terminate);
173
+ process.off('SIGTERM', terminate);
174
+ process.off('exit', terminate);
175
+ process.exitCode = code;
176
+ };
177
+ child.on('close', (code) => finish(done ? 125 : (code ?? 1)));
178
+ // An abort can arrive between the first check and installing fs.watch.
179
+ if (fs.existsSync(stopFile))
180
+ stop();
181
+ }
182
+ /**
183
+ * Compose a private native supervisor without putting the test command on its argv.
184
+ * @param config - The known command, working directory and owned scratch directory.
185
+ * @returns A self-contained native Node program.
186
+ */
187
+ export function feedbackProcessSource(config) {
188
+ return `(${feedbackProcess.toString()})(require,${JSON.stringify(config)});\n`;
189
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-feedback-reporter.d.ts","sourceRoot":"","sources":["../src/playwright-feedback-reporter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAG/E;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAyB/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,GACpB,yBAAyB,EAAE,CA4C7B"}
1
+ {"version":3,"file":"playwright-feedback-reporter.d.ts","sourceRoot":"","sources":["../src/playwright-feedback-reporter.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAA;AAG/E;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAyB/C;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EAAE,GACpB,yBAAyB,EAAE,CAiD7B"}
@@ -58,6 +58,9 @@ export function nativeFeedbackFailures(text, sourceFiles) {
58
58
  const locator = /(?:Locator:\s*|waiting for )(locator\([^\n]+|getBy[^\n]+)/.exec(error)?.[1] ??
59
59
  /strict mode violation: ([^\n]+)/.exec(error)?.[1] ??
60
60
  /(?:^|\n)((?:Error: )?expect\([^\n]+|AssertionError:[^\n]+)/.exec(error)?.[1] ??
61
+ // Navigation waits can expire before the overall test budget. They have no
62
+ // locator, but still carry the native operation, test line and page snapshot.
63
+ /(?:^|\n)TimeoutError: ((?:page|locator)\.[\w.]+): Timeout \d+ms exceeded\./.exec(error)?.[1] ??
61
64
  // A UI loop can exhaust the test budget between actions. Playwright then names
62
65
  // page.waitForTimeout/locator.count rather than a locator. That is still the
63
66
  // supplied test's real failure, with its native location and page snapshot.
@@ -38,7 +38,7 @@ export interface PlaywrightFeedbackResult {
38
38
  spec: string;
39
39
  revision: number;
40
40
  durationMs: number;
41
- status: 'passed' | 'failed' | 'unavailable';
41
+ status: 'passed' | 'failed' | 'unavailable' | 'cancelled';
42
42
  failures: PlaywrightFeedbackFailure[];
43
43
  /** Native counts for this one diagnostic selection, never whole-suite proof. */
44
44
  nativeStats?: PlaywrightReportStats;
@@ -47,7 +47,9 @@ export interface PlaywrightFeedbackResult {
47
47
  /** Host operations for the same scheduler in a CLI process or a remote sandbox. */
48
48
  export interface PlaywrightFeedbackOptions {
49
49
  load: () => Promise<FeedbackProject>;
50
- run: (spec: FeedbackSpec, sources: string[], revision: number, emit: (result: PlaywrightFeedbackResult) => void) => Promise<PlaywrightFeedbackResult>;
50
+ run: (spec: FeedbackSpec, sources: string[], revision: number, emit: (result: PlaywrightFeedbackResult) => void,
51
+ /** Stop the owned native process group when cancelled; never wait for the tests to finish. */
52
+ signal: AbortSignal) => Promise<PlaywrightFeedbackResult>;
51
53
  /** Only current native failures are delivered. Passing apps receive no note. */
52
54
  onFailures?: (result: PlaywrightFeedbackResult) => void;
53
55
  /** Operational state is separate from defect feedback. */
@@ -58,6 +60,8 @@ export interface PlaywrightFeedbackOptions {
58
60
  export interface PlaywrightFeedback {
59
61
  changed: (paths: readonly string[]) => void;
60
62
  takeFailures: () => PlaywrightFeedbackResult[];
63
+ /** Cancel speculation before foreground work. Release after that work; nested callers are safe. */
64
+ pause: () => Promise<() => void>;
61
65
  /** Wait only in tests or shutdown; file writes and deploys must never await this. */
62
66
  settled: () => Promise<void>;
63
67
  close: () => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-feedback-types.d.ts","sourceRoot":"","sources":["../src/playwright-feedback-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEnE,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,yFAAyF;AACzF,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wFAAwF;IACxF,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,4FAA4F;AAC5F,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAA;IAC3C,QAAQ,EAAE,yBAAyB,EAAE,CAAA;IACrC,gFAAgF;IAChF,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;IACpC,GAAG,EAAE,CACH,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,KAC7C,OAAO,CAAC,wBAAwB,CAAC,CAAA;IACtC,gFAAgF;IAChF,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACvD,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACrD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,gDAAgD;AAChD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,MAAM,wBAAwB,EAAE,CAAA;IAC9C,qFAAqF;IACrF,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B"}
1
+ {"version":3,"file":"playwright-feedback-types.d.ts","sourceRoot":"","sources":["../src/playwright-feedback-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAEnE,oEAAoE;AACpE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,yFAAyF;AACzF,MAAM,WAAW,YAAa,SAAQ,cAAc;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,KAAK,EAAE,YAAY,EAAE,CAAA;CACtB;AAED,8EAA8E;AAC9E,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAA;IACf,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wFAAwF;IACxF,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,4FAA4F;AAC5F,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,aAAa,GAAG,WAAW,CAAA;IACzD,QAAQ,EAAE,yBAAyB,EAAE,CAAA;IACrC,gFAAgF;IAChF,WAAW,CAAC,EAAE,qBAAqB,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,mFAAmF;AACnF,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;IACpC,GAAG,EAAE,CACH,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE,MAAM,EAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI;IAChD,8FAA8F;IAC9F,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,wBAAwB,CAAC,CAAA;IACtC,gFAAgF;IAChF,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACvD,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACrD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,gDAAgD;AAChD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,MAAM,wBAAwB,EAAE,CAAA;IAC9C,mGAAmG;IACnG,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,IAAI,CAAC,CAAA;IAChC,qFAAqF;IACrF,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B"}
@@ -38,5 +38,6 @@ export declare function runPlaywrightFeedback(backend: ExecutionBackend, spec: F
38
38
  timeoutMs?: number;
39
39
  emit?: (result: PlaywrightFeedbackResult) => void;
40
40
  pollMs?: number;
41
+ signal?: AbortSignal;
41
42
  }): Promise<PlaywrightFeedbackResult>;
42
43
  //# sourceMappingURL=playwright-feedback.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-feedback.d.ts","sourceRoot":"","sources":["../src/playwright-feedback.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAEV,cAAc,EACd,YAAY,EACZ,kBAAkB,EAElB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAGlD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAyG/F;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,cAAc,EAAE,CAAC,CAkB3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACjD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GACA,OAAO,CAAC,wBAAwB,CAAC,CAwInC"}
1
+ {"version":3,"file":"playwright-feedback.d.ts","sourceRoot":"","sources":["../src/playwright-feedback.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,cAAc,EACd,YAAY,EACZ,kBAAkB,EAElB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,gCAAgC,CAAA;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAGlD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CA2I/F;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,gBAAgB,EACzB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAAC,cAAc,EAAE,CAAC,CAkB3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAA;IACpC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,wBAAwB,KAAK,IAAI,CAAA;IACjD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,GACA,OAAO,CAAC,wBAAwB,CAAC,CA+KnC"}
@@ -8,7 +8,9 @@
8
8
  * @module
9
9
  */
10
10
  import { randomUUID } from 'node:crypto';
11
+ import { tmpdir } from 'node:os';
11
12
  import { posix } from 'node:path';
13
+ import { feedbackProcessSource } from './playwright-feedback-process.js';
12
14
  import { feedbackReporterSource, nativeFeedbackFailures } from './playwright-feedback-reporter.js';
13
15
  import { selectFeedbackSpecs } from './playwright-feedback-selection.js';
14
16
  import { summarizePlaywrightReport } from './playwright-report.js';
@@ -28,15 +30,22 @@ export function isPlaywrightFeedbackSource(path) {
28
30
  */
29
31
  export function createPlaywrightFeedback(options) {
30
32
  let revision = 0, timer, active;
33
+ let running;
34
+ let cancellation;
35
+ let paused = 0;
31
36
  let stopped = false;
32
37
  const changed = new Set(), waiting = [];
33
38
  const delivered = new Map();
34
39
  const failing = new Set();
35
40
  const start = () => {
36
- if (active || stopped || changed.size === 0)
41
+ if (active || stopped || paused || changed.size === 0)
37
42
  return;
38
43
  const version = revision, paths = [...changed];
39
44
  changed.clear();
45
+ const abort = new AbortController();
46
+ cancellation = abort;
47
+ let yielded = false;
48
+ const current = () => !stopped && !abort.signal.aborted && version === revision;
40
49
  active = (async () => {
41
50
  let project;
42
51
  try {
@@ -45,16 +54,16 @@ export function createPlaywrightFeedback(options) {
45
54
  catch (_error) {
46
55
  return;
47
56
  }
48
- if (stopped || version !== revision)
57
+ if (!current())
49
58
  return;
50
59
  const sourcePaths = project.root ? paths.map((p) => posix.resolve(project.root, p)) : paths;
51
60
  // The native failures already name the files to rerun first after an edit.
52
61
  const selected = selectFeedbackSpecs(project, sourcePaths).sort((a, b) => Number(failing.has(b.spec.path)) - Number(failing.has(a.spec.path)));
53
62
  for (const { spec, sourceFiles } of selected) {
54
- if (stopped || version !== revision)
63
+ if (!current())
55
64
  break;
56
65
  const emit = (result) => {
57
- if (stopped || version !== revision || result.status !== 'failed')
66
+ if (!current() || result.status !== 'failed')
58
67
  return;
59
68
  failing.add(spec.path);
60
69
  const identities = delivered.get(spec.path) ?? new Set();
@@ -74,13 +83,23 @@ export function createPlaywrightFeedback(options) {
74
83
  };
75
84
  let result;
76
85
  try {
77
- result = await options.run(spec, sourceFiles, version, emit);
86
+ running = options.run(spec, sourceFiles, version, emit, abort.signal);
87
+ result = await running;
78
88
  }
79
89
  catch (_error) {
80
90
  continue;
91
+ }
92
+ finally {
93
+ running = undefined;
81
94
  } // runner availability is not a markup defect
82
- if (stopped || version !== revision)
95
+ if (!current())
96
+ break;
97
+ if (result.status === 'cancelled') {
98
+ yielded = true;
99
+ for (const path of paths)
100
+ changed.add(path);
83
101
  break;
102
+ }
84
103
  options.onResult?.(result);
85
104
  if (result.status === 'passed')
86
105
  failing.delete(spec.path);
@@ -90,21 +109,25 @@ export function createPlaywrightFeedback(options) {
90
109
  }
91
110
  })().finally(() => {
92
111
  active = undefined;
93
- if (!stopped && changed.size && !timer)
94
- schedule();
112
+ cancellation = undefined;
113
+ if (abort.signal.aborted && !stopped)
114
+ for (const path of paths)
115
+ changed.add(path);
116
+ if (!stopped && !paused && changed.size && !timer)
117
+ schedule(yielded ? 1000 : undefined);
95
118
  });
96
119
  };
97
- const schedule = () => {
120
+ const schedule = (delay = options.debounceMs ?? 250) => {
98
121
  if (timer)
99
122
  clearTimeout(timer);
100
123
  timer = setTimeout(() => {
101
124
  timer = undefined;
102
125
  start();
103
- }, options.debounceMs ?? 250);
126
+ }, delay);
104
127
  timer.unref?.();
105
128
  };
106
129
  const settled = async () => {
107
- while (timer || active || (!stopped && changed.size)) {
130
+ while (timer || active || (!stopped && !paused && changed.size)) {
108
131
  if (timer) {
109
132
  clearTimeout(timer);
110
133
  timer = undefined;
@@ -112,7 +135,7 @@ export function createPlaywrightFeedback(options) {
112
135
  }
113
136
  if (active)
114
137
  await active;
115
- else if (!stopped && changed.size)
138
+ else if (!stopped && !paused && changed.size)
116
139
  start();
117
140
  }
118
141
  };
@@ -127,14 +150,35 @@ export function createPlaywrightFeedback(options) {
127
150
  // A queued diagnosis can be invalidated before its host delivers it. A
128
151
  // new revision must be allowed to report the same still-failing locator.
129
152
  delivered.clear();
130
- schedule();
153
+ cancellation?.abort();
154
+ if (!paused)
155
+ schedule();
131
156
  },
132
157
  takeFailures() {
133
158
  return waiting.splice(0).filter((r) => r.revision === revision);
134
159
  },
160
+ async pause() {
161
+ paused++;
162
+ if (timer)
163
+ clearTimeout(timer);
164
+ timer = undefined;
165
+ cancellation?.abort();
166
+ // Loading metadata is not native work and must not become a foreground barrier.
167
+ await running?.catch(() => undefined);
168
+ let released = false;
169
+ return () => {
170
+ if (released)
171
+ return;
172
+ released = true;
173
+ paused--;
174
+ if (!paused && !stopped && changed.size)
175
+ schedule();
176
+ };
177
+ },
135
178
  settled,
136
179
  async close() {
137
180
  stopped = true;
181
+ cancellation?.abort();
138
182
  if (timer)
139
183
  clearTimeout(timer);
140
184
  timer = undefined;
@@ -185,7 +229,8 @@ for(const f of ${JSON.stringify([...sourceRoots, ...suiteFiles])})read(f);proces
185
229
  */
186
230
  export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision, options) {
187
231
  const started = Date.now();
188
- const dir = posix.join(options.scratchRoot ?? posix.join(backend.projectRoot, '.mol', 'test-feedback'), randomUUID());
232
+ const dir = posix.join(options.scratchRoot ??
233
+ posix.join(backend.hostFs ? tmpdir() : '/tmp', 'molecule-playwright-feedback'), randomUUID());
189
234
  const reportPath = posix.join(dir, 'report.json');
190
235
  const reporterPath = posix.join(dir, 'feedback-reporter.cjs');
191
236
  const eventsPath = posix.join(dir, 'failures.jsonl');
@@ -206,7 +251,16 @@ export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision
206
251
  ]);
207
252
  // The standard OS priority keeps background diagnosis behind the dev server
208
253
  // and builds when they share one CPU. Playwright owns its global deadline.
209
- const command = `export PLAYWRIGHT_JSON_OUTPUT_FILE=${shellQuote(reportPath)} MOL_TEST_FEEDBACK_EVENTS=${shellQuote(eventsPath)}; nice -n 10 sh -c ${shellQuote(native)}`;
254
+ const command = `export MOL_TEST_INTERNAL=1 PLAYWRIGHT_JSON_OUTPUT_FILE=${shellQuote(reportPath)} MOL_TEST_FEEDBACK_EVENTS=${shellQuote(eventsPath)}; node ${shellQuote(posix.join(dir, 'run.cjs'))}`;
255
+ let cancelling;
256
+ const cancel = () => {
257
+ cancelling ??= backend.writeFile(posix.join(dir, 'stop'), '').catch(() => undefined);
258
+ };
259
+ const cancelled = () => ({
260
+ ...base,
261
+ status: 'cancelled',
262
+ durationMs: Date.now() - started,
263
+ });
210
264
  let poll, reading, done = false;
211
265
  const emitted = new Set();
212
266
  const nativeFailures = new Map();
@@ -219,6 +273,8 @@ export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision
219
273
  if (emitted.has(identity))
220
274
  continue;
221
275
  emitted.add(identity);
276
+ if (options.signal?.aborted)
277
+ continue;
222
278
  options.emit?.({
223
279
  ...base,
224
280
  failures: [failure],
@@ -242,7 +298,23 @@ export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision
242
298
  });
243
299
  };
244
300
  try {
301
+ if (options.signal?.aborted)
302
+ return cancelled();
303
+ await backend.run(`node -e ${shellQuote(`require('node:fs').mkdirSync(${JSON.stringify(dir)},{recursive:true,mode:0o700})`)}`, { timeout: 5_000 });
245
304
  await backend.writeFile(reporterPath, feedbackReporterSource());
305
+ await backend.writeFile(posix.join(dir, 'run.cjs'), feedbackProcessSource({
306
+ command: native,
307
+ cwd: spec.cwd,
308
+ root: backend.projectRoot,
309
+ directory: dir,
310
+ }));
311
+ options.signal?.addEventListener('abort', cancel, { once: true });
312
+ if (options.signal?.aborted)
313
+ cancel();
314
+ if (cancelling) {
315
+ await cancelling;
316
+ return cancelled();
317
+ }
246
318
  if (options.emit)
247
319
  tick();
248
320
  const result = await backend.run(command, {
@@ -250,6 +322,15 @@ export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision
250
322
  timeout: timeout + 10_000,
251
323
  budgetMs: timeout + 5_000,
252
324
  });
325
+ if (options.signal?.aborted)
326
+ return cancelled();
327
+ try {
328
+ if ((await backend.readFile(posix.join(dir, 'yielded'))) === 'foreground')
329
+ return cancelled();
330
+ }
331
+ catch (_error1) {
332
+ /* the native run was not preempted */
333
+ }
253
334
  done = true;
254
335
  if (poll)
255
336
  clearTimeout(poll);
@@ -306,6 +387,9 @@ export async function runPlaywrightFeedback(backend, spec, sourceFiles, revision
306
387
  }
307
388
  finally {
308
389
  done = true;
390
+ options.signal?.removeEventListener('abort', cancel);
391
+ if (cancelling)
392
+ await cancelling;
309
393
  if (poll)
310
394
  clearTimeout(poll);
311
395
  if (reading)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molecule/api-ai-tools",
3
- "version": "1.17.1",
3
+ "version": "1.17.2",
4
4
  "description": "Shared AI agent tools with backend abstraction for sandbox and local execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",