@opencraw/driver 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,8 +17,8 @@ npx opencraw-driver # then write JSON-RPC lines to it
17
17
  ## The conversation
18
18
 
19
19
  ```jsonc
20
- → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.0.0"}}
21
- ← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.1.0","protocolVersion":"1.0.0","methods":["initialize","validate","run","cancel","shutdown"]}}
20
+ → {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientName":"opencraw-python","clientVersion":"1.0.0","protocolVersion":"1.2.0"}}
21
+ ← {"jsonrpc":"2.0","id":1,"result":{"driverVersion":"0.2.0","protocolVersion":"1.2.0","methods":["initialize","validate","run","work","cancel","probe","diff","export","shutdown"]}}
22
22
  → {"jsonrpc":"2.0","id":2,"method":"run","params":{"paths":["recipes/"],"hooks":["tidy"]}}
23
23
  ← {"jsonrpc":"2.0","id":2,"result":{"runId":"run-1"}}
24
24
  ← {"jsonrpc":"2.0","method":"event","params":{"runId":"run-1","event":{"type":"recipe:start",…}}}
@@ -33,13 +33,19 @@ npx opencraw-driver # then write JSON-RPC lines to it
33
33
  |---|---|---|
34
34
  | `initialize` | client | Must come first. The driver refuses a protocol of another major. |
35
35
  | `validate` | client | Loads recipes (`paths` or inline `recipes`) without running them: the inputs that would run, every hook they call, and every problem with its path. |
36
- | `run` | client | Starts a run and answers its `runId` at once; the crawl goes on in the background. `hooks` names the hooks the client answers; `out` writes records to a file instead of sending them. |
36
+ | `run` | client | Starts a run and answers its `runId` at once; the crawl goes on in the background. `hooks`, `captchaSolvers` and `accessPlugins` name what the client answers; `accessConfig` and `access` pick the access config and profile; `out` writes records to a file instead of sending them. |
37
+ | `work` | client | Starts a worker pool and answers its `runId` at once. Its windows ask the client for items (`work.next`) and keep the steps marked `keep` between them; `windows` is a count or core's `WindowsPolicy`. Takes the same crawler fields as `run`. |
37
38
  | `cancel` | client | Stops a run (its crawler is closed); it still ends with `finished`. |
39
+ | `probe` | client | Fetches a page (or renders it, with `browser`) and says where its data lives: the CLI's and MCP's probe, through the same access fields as `run`. |
40
+ | `diff` | client | Compares two runs' JSON Lines files by key: counts, the report a person reads, and the changes up to `limit`. |
41
+ | `export` | client | Writes a run's JSON Lines file in another format (the extension of `out`), with an output recipe's columns. |
38
42
  | `shutdown` | client | Closes every run, answers, then exits. Closing stdin does the same. |
39
43
  | `event` | driver | Every crawl event, as core reports it. |
40
44
  | `record` | driver | Each record, unless the run has `out`. |
41
- | `finished` | driver | The run is over: its report, or why it could not run. |
42
- | `callout` | driver | A hook the client serves. The request and its answer are core's [callout contract](../core/README.md): `ok`, `error`, or `pending` with `retryAfterMs`, asked again with the same idempotency key. |
45
+ | `finished` | driver | The run is over: its report (for `work`, the work report), or why it could not run. |
46
+ | `work.next` | driver | A window wants an item: answer `{ "item": { "id", "vars", "recipe"? } }`, holding the answer until there is one, or `{ "item": null }` to end the work. An id already queued or running runs once. |
47
+ | `work.done`, `work.failed` | driver | How an item ended: its report and records, or its outcome (`failure`, `neutral`). |
48
+ | `callout` | driver | A hook, captcha solver or access plugin the client serves (`request.kind`: `hook`, `captcha`, `access`). The request and its answer are core's [callout contract](../core/README.md): `ok`, `error`, or `pending` with `retryAfterMs`, asked again with the same idempotency key. |
43
49
 
44
50
  Every answer comes back before the driver stops, even when a client writes its last requests and closes
45
51
  stdin at once. Answers may arrive out of order; match them by `id`.
package/dist/index.esm.js CHANGED
@@ -1,11 +1,18 @@
1
1
  // src/server/serve-driver.use-case.ts
2
- import { once } from "node:events";
2
+ import { once as once2 } from "node:events";
3
3
 
4
4
  // src/protocol/driver-message.contract.ts
5
5
  import { calloutRequestSchema, calloutResponseSchema } from "@opencraw/core";
6
6
  import { z } from "zod";
7
- var PROTOCOL_VERSION = "1.0.0";
7
+ var PROTOCOL_VERSION = "1.2.0";
8
8
  var recipeObject = z.record(z.string(), z.unknown());
9
+ var coreObject = z.record(z.string(), z.unknown());
10
+ var accessFields = {
11
+ accessConfig: z.string().optional(),
12
+ access: z.string().optional(),
13
+ /** Access plugins the client serves: a profile `{ "kind": "plugin", "name": … }` naming one leases through a `callout` to the client. */
14
+ accessPlugins: z.array(z.string()).optional()
15
+ };
9
16
  var recipeSource = {
10
17
  paths: z.array(z.string()).min(1).optional(),
11
18
  recipes: z.array(recipeObject).min(1).optional()
@@ -41,17 +48,87 @@ var runParamsSchema = z.strictObject({
41
48
  parallel: z.number().int().min(1).max(16).optional(),
42
49
  browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
43
50
  /** The hooks the client answers itself: each becomes a `callout` request to the client when a recipe calls it. */
44
- hooks: z.array(z.string()).optional()
51
+ hooks: z.array(z.string()).optional(),
52
+ /** Captcha solvers the client serves, by name: a challenge goes to the client as a `captcha` callout. */
53
+ captchaSolvers: z.array(z.string()).optional(),
54
+ ...accessFields
45
55
  });
46
56
  var runResultSchema = z.strictObject({ runId: z.string() });
57
+ var windowsSchema = z.union([z.number().int().min(1).max(64), z.record(z.string(), z.unknown())]);
58
+ var workParamsSchema = z.strictObject({
59
+ ...recipeSource,
60
+ only: z.array(z.string()).optional(),
61
+ out: z.string().optional(),
62
+ append: z.boolean().optional(),
63
+ browser: z.strictObject({ headless: z.boolean().optional(), executablePath: z.string().optional(), ignoreHTTPSErrors: z.boolean().optional() }).optional(),
64
+ hooks: z.array(z.string()).optional(),
65
+ captchaSolvers: z.array(z.string()).optional(),
66
+ ...accessFields,
67
+ windows: windowsSchema.optional()
68
+ });
69
+ var varValue = z.union([z.string(), z.number(), z.boolean()]);
70
+ var workItemSchema = z.strictObject({
71
+ id: z.string().min(1),
72
+ vars: z.record(z.string(), varValue),
73
+ recipe: z.string().optional()
74
+ });
47
75
  var cancelParamsSchema = z.strictObject({ runId: z.string() });
48
76
  var cancelResultSchema = z.strictObject({ cancelled: z.boolean() });
77
+ var probeParamsSchema = z.strictObject({
78
+ url: z.string().min(1),
79
+ /** Render the page in Chromium first (for pages built by scripts). */
80
+ browser: z.boolean().optional(),
81
+ browserPath: z.string().optional(),
82
+ insecureTls: z.boolean().optional(),
83
+ userAgent: z.string().optional(),
84
+ ...accessFields
85
+ });
86
+ var probeResultSchema = coreObject;
87
+ var diffParamsSchema = z.strictObject({
88
+ /** The earlier run's JSON Lines file. */
89
+ previous: z.string().min(1),
90
+ current: z.string().min(1),
91
+ /** The fields that identify a record; default: each line's `_key`. */
92
+ key: z.array(z.string()).optional(),
93
+ /** Fields left out of the comparison. */
94
+ ignore: z.array(z.string()).optional(),
95
+ /** Changes returned at most (default 200); the counts cover all of them. */
96
+ limit: z.number().int().min(0).optional()
97
+ });
98
+ var diffResultSchema = z.strictObject({
99
+ added: z.number(),
100
+ removed: z.number(),
101
+ changed: z.number(),
102
+ unchanged: z.number(),
103
+ /** Set when the current run lost most of the previous run's records. */
104
+ shrunk: z.strictObject({ previous: z.number(), current: z.number() }).optional(),
105
+ /** The report a person reads, one line per change. */
106
+ summary: z.array(z.string()),
107
+ /** The changes, up to `limit`: each `added`, `removed` or `changed` (with each field's before and after). */
108
+ changes: z.array(coreObject),
109
+ /** More changes than `limit` were found. */
110
+ truncated: z.boolean()
111
+ });
112
+ var exportParamsSchema = z.strictObject({
113
+ /** A run's JSON Lines file. */
114
+ records: z.string().min(1),
115
+ /** The output recipe that gives the columns. */
116
+ output: z.string().min(1),
117
+ /** The file to write; its extension names the format, as for `run`. */
118
+ out: z.string().min(1)
119
+ });
120
+ var exportResultSchema = z.strictObject({ written: z.number(), location: z.string() });
49
121
  var shutdownResultSchema = z.strictObject({});
122
+ var workNextParamsSchema = z.strictObject({ runId: z.string() });
123
+ var workNextResultSchema = z.strictObject({ item: workItemSchema.nullable() });
124
+ var workRecordSchema = z.strictObject({ key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
125
+ var workDoneParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, records: z.array(workRecordSchema) });
126
+ var workFailedParamsSchema = z.strictObject({ runId: z.string(), item: workItemSchema, report: coreObject, outcome: z.enum(["failure", "neutral"]) });
50
127
  var eventParamsSchema = z.strictObject({ runId: z.string(), event: z.record(z.string(), z.unknown()) });
51
128
  var recordParamsSchema = z.strictObject({ runId: z.string(), recipeId: z.string(), key: z.string().nullable(), data: z.record(z.string(), z.unknown()) });
52
129
  var finishedParamsSchema = z.strictObject({
53
130
  runId: z.string(),
54
- /** The crawl report (`CrawlReport`), absent when the run could not start. */
131
+ /** The crawl report (`CrawlReport`), or for `work` the work report (`WorkReport`); absent when the run could not start. */
55
132
  report: z.record(z.string(), z.unknown()).optional(),
56
133
  /** Why the run could not start, or stopped as a whole. */
57
134
  error: z.string().optional()
@@ -59,15 +136,22 @@ var finishedParamsSchema = z.strictObject({
59
136
  var calloutParamsSchema = z.strictObject({ runId: z.string(), request: calloutRequestSchema });
60
137
  var calloutResultSchema = calloutResponseSchema;
61
138
  var MESSAGES = {
62
- initialize: { direction: "client-to-driver", params: initializeParamsSchema, result: initializeResultSchema },
63
- validate: { direction: "client-to-driver", params: validateParamsSchema, result: validateResultSchema },
64
- run: { direction: "client-to-driver", params: runParamsSchema, result: runResultSchema },
65
- cancel: { direction: "client-to-driver", params: cancelParamsSchema, result: cancelResultSchema },
66
- shutdown: { direction: "client-to-driver", params: void 0, result: shutdownResultSchema },
67
- callout: { direction: "driver-to-client", params: calloutParamsSchema, result: calloutResultSchema },
68
- event: { direction: "driver-to-client", params: eventParamsSchema, result: void 0 },
69
- record: { direction: "driver-to-client", params: recordParamsSchema, result: void 0 },
70
- finished: { direction: "driver-to-client", params: finishedParamsSchema, result: void 0 }
139
+ "initialize": { direction: "client-to-driver", params: initializeParamsSchema, result: initializeResultSchema },
140
+ "validate": { direction: "client-to-driver", params: validateParamsSchema, result: validateResultSchema },
141
+ "run": { direction: "client-to-driver", params: runParamsSchema, result: runResultSchema },
142
+ "work": { direction: "client-to-driver", params: workParamsSchema, result: runResultSchema },
143
+ "cancel": { direction: "client-to-driver", params: cancelParamsSchema, result: cancelResultSchema },
144
+ "probe": { direction: "client-to-driver", params: probeParamsSchema, result: probeResultSchema },
145
+ "diff": { direction: "client-to-driver", params: diffParamsSchema, result: diffResultSchema },
146
+ "export": { direction: "client-to-driver", params: exportParamsSchema, result: exportResultSchema },
147
+ "shutdown": { direction: "client-to-driver", params: void 0, result: shutdownResultSchema },
148
+ "callout": { direction: "driver-to-client", params: calloutParamsSchema, result: calloutResultSchema },
149
+ "work.next": { direction: "driver-to-client", params: workNextParamsSchema, result: workNextResultSchema },
150
+ "work.done": { direction: "driver-to-client", params: workDoneParamsSchema, result: void 0 },
151
+ "work.failed": { direction: "driver-to-client", params: workFailedParamsSchema, result: void 0 },
152
+ "event": { direction: "driver-to-client", params: eventParamsSchema, result: void 0 },
153
+ "record": { direction: "driver-to-client", params: recordParamsSchema, result: void 0 },
154
+ "finished": { direction: "driver-to-client", params: finishedParamsSchema, result: void 0 }
71
155
  };
72
156
 
73
157
  // src/protocol/driver-json-schema.mapper.ts
@@ -99,19 +183,127 @@ function driverProtocolJsonSchema() {
99
183
  };
100
184
  }
101
185
 
186
+ // src/record-files/diff-record-files.use-case.ts
187
+ import { diffReport } from "@opencraw/cli";
188
+ import { diffRecords, readRecordsFile } from "@opencraw/core";
189
+ var CHANGE_LIMIT = 200;
190
+ async function diffRecordFiles(params) {
191
+ const [previous, current] = await Promise.all([readRecordsFile(params.previous), readRecordsFile(params.current)]);
192
+ const diff = diffRecords(previous, current, { key: params.key, ignore: params.ignore });
193
+ const limit = params.limit ?? CHANGE_LIMIT;
194
+ return {
195
+ added: diff.added,
196
+ removed: diff.removed,
197
+ changed: diff.changed,
198
+ unchanged: diff.unchanged,
199
+ ...diff.shrunk !== void 0 && { shrunk: diff.shrunk },
200
+ summary: diffReport(diff),
201
+ changes: diff.changes.slice(0, limit).map((change) => ({ ...change })),
202
+ truncated: diff.changes.length > limit
203
+ };
204
+ }
205
+
206
+ // src/record-files/export-record-files.use-case.ts
207
+ import { readFile } from "node:fs/promises";
208
+ import { fileSink, outputRecordOf, parseOutputRecipe, readRecordsFile as readRecordsFile2 } from "@opencraw/core";
209
+ async function exportRecordFiles(params) {
210
+ const output = parseOutputRecipe(JSON.parse(await readFile(params.output, "utf8")), params.output);
211
+ const stored = await readRecordsFile2(params.records);
212
+ const sink = fileSink(params.out, { browser: { executablePath: process.env.OPENCRAW_CHROMIUM } });
213
+ await sink.open(output);
214
+ for (const line of stored) await sink.write(outputRecordOf(line));
215
+ const summary = await sink.close();
216
+ return { written: summary.written, location: summary.location ?? params.out };
217
+ }
218
+
102
219
  // src/session/run-session.use-case.ts
103
- import { createCrawler, fileSink, RecipeSet } from "@opencraw/core";
220
+ import { createCrawler, fileSink as fileSink2, RecipeSet } from "@opencraw/core";
104
221
 
105
222
  // src/callout-bridge/client-hooks.client.ts
106
- import { calloutHook, calloutResponseSchema as calloutResponseSchema2 } from "@opencraw/core";
107
- function clientTransport(hookName, runId, ask) {
223
+ import { accessPluginVia, calloutHook, calloutResponseSchema as calloutResponseSchema2, captchaSolverVia } from "@opencraw/core";
224
+ function clientTransport(label, runId, ask) {
108
225
  return {
109
- label: `client hook ${hookName}`,
226
+ label,
110
227
  call: async (request) => calloutResponseSchema2.parse(await ask("callout", { runId, request }))
111
228
  };
112
229
  }
113
230
  function clientHooks(names, runId, ask) {
114
- return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(name, runId, ask))]));
231
+ return Object.fromEntries(names.map((name) => [name, calloutHook(name, clientTransport(`client hook ${name}`, runId, ask))]));
232
+ }
233
+ function clientCaptchaSolvers(names, runId, ask) {
234
+ return names.map((name) => captchaSolverVia(name, clientTransport(`client captcha solver ${name}`, runId, ask)));
235
+ }
236
+ function clientAccessPlugins(names, runId, ask) {
237
+ return names.map((name) => accessPluginVia(name, clientTransport(`client access plugin ${name}`, runId, ask)));
238
+ }
239
+
240
+ // src/site-access/resolve-site-access.use-case.ts
241
+ import { resolveAccess } from "@opencraw/cli";
242
+ async function resolveSiteAccess(params, runId, ask, env = process.env) {
243
+ const file = params.accessConfig ?? env.OPENCRAW_ACCESS;
244
+ if (file === void 0 && params.access !== void 0) throw new Error(`access profile "${params.access}" needs an access config: give "accessConfig", or set OPENCRAW_ACCESS where the driver runs`);
245
+ const access = await resolveAccess({ insecureTls: false, access: file, accessProfile: params.access });
246
+ return { access, accessPlugins: clientAccessPlugins(params.accessPlugins ?? [], runId, ask) };
247
+ }
248
+
249
+ // src/work-bridge/client-work-source.client.ts
250
+ import { once } from "node:events";
251
+ function clientWorkSource(runId, ask, notify) {
252
+ const kept = [];
253
+ let ended = false;
254
+ const askClient = async () => {
255
+ const { item } = workNextResultSchema.parse(await ask("work.next", { runId }));
256
+ if (item === null) {
257
+ ended = true;
258
+ return void 0;
259
+ }
260
+ return item;
261
+ };
262
+ const keepLate = async (answer) => {
263
+ try {
264
+ const item = await answer;
265
+ if (item !== void 0) kept.push(item);
266
+ } catch {
267
+ }
268
+ };
269
+ return {
270
+ next: async (options) => {
271
+ const waiting = kept.shift();
272
+ if (waiting !== void 0) return waiting;
273
+ if (ended) return;
274
+ const answer = askClient();
275
+ const signal = options?.signal;
276
+ if (signal === void 0) return await answer;
277
+ signal.throwIfAborted();
278
+ const settled = new AbortController();
279
+ const answered = (async () => {
280
+ const item = await answer;
281
+ return { item };
282
+ })();
283
+ const retired = (async () => {
284
+ try {
285
+ await once(signal, "abort", { signal: settled.signal });
286
+ return "retired";
287
+ } catch {
288
+ return "answered";
289
+ }
290
+ })();
291
+ try {
292
+ const first = await Promise.race([answered, retired]);
293
+ if (first !== "retired" && first !== "answered") return first.item;
294
+ void keepLate(answer);
295
+ throw signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason));
296
+ } finally {
297
+ settled.abort();
298
+ }
299
+ },
300
+ done: (item, report, records) => {
301
+ notify("work.done", { runId, item, report, records: records.map((record) => ({ key: record.key, data: record.data })) });
302
+ },
303
+ failed: (item, report, outcome) => {
304
+ notify("work.failed", { runId, item, report, outcome });
305
+ }
306
+ };
115
307
  }
116
308
 
117
309
  // src/session/recipe-loading.use-case.ts
@@ -155,36 +347,53 @@ function streamingSink() {
155
347
  function runSessions(notify, ask) {
156
348
  const running = /* @__PURE__ */ new Map();
157
349
  let next = 1;
158
- const crawl = async (runId, crawler, set) => {
159
- try {
160
- const report = await crawler.run(set);
161
- notify("finished", { runId, report });
162
- } catch (error) {
163
- notify("finished", { runId, error: error instanceof Error ? error.message : String(error) });
164
- } finally {
165
- running.delete(runId);
166
- await crawler.close();
167
- }
350
+ const crawlerFor = async (runId, params) => {
351
+ const { access, accessPlugins } = await resolveSiteAccess(params, runId, ask);
352
+ return createCrawler({
353
+ sink: params.out === void 0 ? streamingSink() : fileSink2(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
354
+ hooks: clientHooks(params.hooks ?? [], runId, ask),
355
+ captchaSolvers: clientCaptchaSolvers(params.captchaSolvers ?? [], runId, ask),
356
+ access,
357
+ accessPlugins,
358
+ profilesDir: process.env.OPENCRAW_PROFILES,
359
+ parallel: params.parallel,
360
+ resume: params.resume,
361
+ debug: params.dryRun,
362
+ browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
363
+ onEvent: (event) => {
364
+ notify("event", { runId, event });
365
+ if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
366
+ }
367
+ });
368
+ };
369
+ const track = (runId, crawler, crawling) => {
370
+ running.set(runId, crawler);
371
+ void (async () => {
372
+ try {
373
+ notify("finished", { runId, report: await crawling() });
374
+ } catch (error) {
375
+ notify("finished", { runId, error: error instanceof Error ? error.message : String(error) });
376
+ } finally {
377
+ running.delete(runId);
378
+ await crawler.close();
379
+ }
380
+ })();
168
381
  };
169
382
  return {
170
383
  start: async (params) => {
171
384
  const loaded = await loadRecipes(params);
172
385
  const set = params.dryRun === true ? new RecipeSet(loaded.output, loaded.inputs.map((input) => ({ ...input, limits: { ...input.limits, maxRecords: 1 } }))) : loaded;
173
386
  const runId = `run-${next++}`;
174
- const crawler = createCrawler({
175
- sink: params.out === void 0 ? streamingSink() : fileSink(params.out, { append: params.append, browser: { executablePath: params.browser?.executablePath } }),
176
- hooks: clientHooks(params.hooks ?? [], runId, ask),
177
- parallel: params.parallel,
178
- resume: params.resume,
179
- debug: params.dryRun,
180
- browser: { headless: params.browser?.headless ?? true, executablePath: params.browser?.executablePath, ignoreHTTPSErrors: params.browser?.ignoreHTTPSErrors },
181
- onEvent: (event) => {
182
- notify("event", { runId, event });
183
- if (event.type === "record:emit" && params.out === void 0) notify("record", { runId, recipeId: event.recipeId, key: event.key, data: event.data });
184
- }
185
- });
186
- running.set(runId, crawler);
187
- void crawl(runId, crawler, set);
387
+ const crawler = await crawlerFor(runId, params);
388
+ track(runId, crawler, async () => await crawler.run(set));
389
+ return runId;
390
+ },
391
+ startWork: async (params) => {
392
+ const set = await loadRecipes(params);
393
+ const runId = `run-${next++}`;
394
+ const crawler = await crawlerFor(runId, params);
395
+ const windows = params.windows;
396
+ track(runId, crawler, async () => await crawler.work(set, clientWorkSource(runId, ask, notify), { windows }));
188
397
  return runId;
189
398
  },
190
399
  cancel: async (runId) => {
@@ -204,6 +413,21 @@ function runSessions(notify, ask) {
204
413
  };
205
414
  }
206
415
 
416
+ // src/site-probe/probe-site.use-case.ts
417
+ import { probeUrl } from "@opencraw/cli";
418
+ async function probeSite(params, probeId, ask) {
419
+ const { access, accessPlugins } = await resolveSiteAccess(params, probeId, ask);
420
+ const result = await probeUrl(params.url, {
421
+ browser: params.browser ?? false,
422
+ browserPath: params.browserPath ?? process.env.OPENCRAW_CHROMIUM,
423
+ insecureTls: params.insecureTls ?? false,
424
+ userAgent: params.userAgent,
425
+ accessConfig: access,
426
+ accessPlugins
427
+ });
428
+ return { ...result };
429
+ }
430
+
207
431
  // src/transport/line-channel.client.ts
208
432
  import { createInterface } from "node:readline";
209
433
 
@@ -310,7 +534,7 @@ function openLineChannel(input, output, handle) {
310
534
  }
311
535
 
312
536
  // src/server/serve-driver.use-case.ts
313
- var METHODS = ["initialize", "validate", "run", "cancel", "shutdown"];
537
+ var METHODS = ["initialize", "validate", "run", "work", "cancel", "probe", "diff", "export", "shutdown"];
314
538
  var major = (version) => version.split(".", 1)[0];
315
539
  function parsed(schema, params) {
316
540
  const result = schema.safeParse(params ?? {});
@@ -320,6 +544,7 @@ function parsed(schema, params) {
320
544
  async function serveDriver(input, output, identity) {
321
545
  let initialized = false;
322
546
  const stopping = new AbortController();
547
+ let probes = 0;
323
548
  const channel = openLineChannel(input, output, async (method, params) => {
324
549
  if (method === "initialize") {
325
550
  const hello = parsed(initializeParamsSchema, params);
@@ -332,7 +557,11 @@ async function serveDriver(input, output, identity) {
332
557
  if (!initialized) throw new RpcFailure(RPC_ERRORS.server, "call initialize first");
333
558
  if (method === "validate") return await validateRecipes(parsed(validateParamsSchema, params));
334
559
  if (method === "run") return { runId: await sessions.start(parsed(runParamsSchema, params)) };
560
+ if (method === "work") return { runId: await sessions.startWork(parsed(workParamsSchema, params)) };
335
561
  if (method === "cancel") return { cancelled: await sessions.cancel(parsed(cancelParamsSchema, params).runId) };
562
+ if (method === "probe") return await probeSite(parsed(probeParamsSchema, params), `probe-${++probes}`, channel.request);
563
+ if (method === "diff") return await diffRecordFiles(parsed(diffParamsSchema, params));
564
+ if (method === "export") return await exportRecordFiles(parsed(exportParamsSchema, params));
336
565
  await sessions.closeAll();
337
566
  setImmediate(() => {
338
567
  stopping.abort();
@@ -340,7 +569,7 @@ async function serveDriver(input, output, identity) {
340
569
  return {};
341
570
  });
342
571
  const sessions = runSessions(channel.notify, channel.request);
343
- await Promise.race([channel.closed, once(stopping.signal, "abort")]);
572
+ await Promise.race([channel.closed, once2(stopping.signal, "abort")]);
344
573
  await channel.drained();
345
574
  await sessions.closeAll();
346
575
  }
@@ -353,13 +582,25 @@ export {
353
582
  calloutResultSchema,
354
583
  cancelParamsSchema,
355
584
  cancelResultSchema,
585
+ clientAccessPlugins,
586
+ clientCaptchaSolvers,
356
587
  clientHooks,
588
+ clientWorkSource,
589
+ diffParamsSchema,
590
+ diffRecordFiles,
591
+ diffResultSchema,
357
592
  driverProtocolJsonSchema,
358
593
  eventParamsSchema,
594
+ exportParamsSchema,
595
+ exportRecordFiles,
596
+ exportResultSchema,
359
597
  finishedParamsSchema,
360
598
  initializeParamsSchema,
361
599
  initializeResultSchema,
362
600
  openLineChannel,
601
+ probeParamsSchema,
602
+ probeResultSchema,
603
+ probeSite,
363
604
  recordParamsSchema,
364
605
  runParamsSchema,
365
606
  runResultSchema,
@@ -368,6 +609,12 @@ export {
368
609
  streamingSink,
369
610
  validateParamsSchema,
370
611
  validateRecipes,
371
- validateResultSchema
612
+ validateResultSchema,
613
+ workDoneParamsSchema,
614
+ workFailedParamsSchema,
615
+ workItemSchema,
616
+ workNextParamsSchema,
617
+ workNextResultSchema,
618
+ workParamsSchema
372
619
  };
373
620
  //# sourceMappingURL=index.esm.js.map