@vostride/agent-qa-plugin-kit 0.0.1 → 0.3.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.
package/dist/index.js ADDED
@@ -0,0 +1,800 @@
1
+ import { z, z as z$1 } from "zod";
2
+ import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync, writeSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { format, inspect, isDeepStrictEqual } from "node:util";
5
+ // src/credential.ts
6
+ function credential(description) {
7
+ return z$1.string().meta({
8
+ credential: true,
9
+ ...description ? { description } : {}
10
+ });
11
+ }
12
+ // src/protocol/messages.ts
13
+ const PLUGIN_METHODS = [
14
+ "initialize",
15
+ "check",
16
+ "acquire",
17
+ "uploadApp",
18
+ "started",
19
+ "end",
20
+ "release",
21
+ "shutdown"
22
+ ];
23
+ const OPTIONAL_PLUGIN_METHODS = [
24
+ "check",
25
+ "uploadApp",
26
+ "started",
27
+ "end"
28
+ ];
29
+ const PLUGIN_SERVES = [
30
+ "playwright",
31
+ "cdp",
32
+ "appium"
33
+ ];
34
+ const PLUGIN_RUN_FACTS = [
35
+ "testName",
36
+ "runId",
37
+ "failure"
38
+ ];
39
+ const PLUGIN_FAILURE_KINDS = [
40
+ "unreachable",
41
+ "auth",
42
+ "capacity",
43
+ "timeout",
44
+ "version",
45
+ "refused",
46
+ "ended",
47
+ "lost"
48
+ ];
49
+ const PLUGIN_ERROR_CODES = {
50
+ parse: -32700,
51
+ invalidRequest: -32600,
52
+ methodNotFound: -32601,
53
+ invalidParams: -32602,
54
+ plugin: -32e3,
55
+ cancelled: -32800
56
+ };
57
+ const PROGRESS_METHOD = "$/progress";
58
+ const MAX_MESSAGE_BYTES = 1048576;
59
+ const JsonObjectSchema = z$1.record(z$1.string(), z$1.unknown());
60
+ const HandleSchema = z$1.json();
61
+ const RequestIdSchema = z$1.union([z$1.string(), z$1.number()]);
62
+ const InitializeParamsSchema = z$1.object({
63
+ protocolVersions: z$1.array(z$1.number().int()),
64
+ host: z$1.object({
65
+ pid: z$1.number().int(),
66
+ agentQaVersion: z$1.string(),
67
+ playwrightVersion: z$1.string(),
68
+ webdriverioVersion: z$1.string()
69
+ }),
70
+ entry: z$1.string(),
71
+ options: JsonObjectSchema,
72
+ runFacts: z$1.object({ runId: z$1.string().optional() }).optional()
73
+ });
74
+ z$1.object({
75
+ protocolVersion: z$1.number().int(),
76
+ plugin: z$1.object({
77
+ name: z$1.string(),
78
+ version: z$1.string()
79
+ }),
80
+ serves: z$1.array(z$1.enum(PLUGIN_SERVES)),
81
+ methods: z$1.array(z$1.enum(OPTIONAL_PLUGIN_METHODS))
82
+ });
83
+ const CheckParamsSchema = z$1.object({ remotes: z$1.array(z$1.object({
84
+ name: z$1.string(),
85
+ settings: JsonObjectSchema
86
+ })) });
87
+ const CheckResultSchema = z$1.object({
88
+ state: z$1.enum(["ok", "warn"]),
89
+ lines: z$1.array(z$1.string())
90
+ });
91
+ const WebSessionSchema = z$1.object({
92
+ platform: z$1.literal("web"),
93
+ engine: z$1.enum([
94
+ "chromium",
95
+ "firefox",
96
+ "webkit"
97
+ ]),
98
+ context: z$1.enum(["new", "reuse"])
99
+ });
100
+ const DeviceSessionSchema = z$1.object({
101
+ platform: z$1.enum(["android", "ios"]),
102
+ capabilities: JsonObjectSchema
103
+ });
104
+ const AcquireParamsSchema = z$1.object({
105
+ remote: z$1.string(),
106
+ settings: JsonObjectSchema,
107
+ session: z$1.discriminatedUnion("platform", [WebSessionSchema, DeviceSessionSchema]),
108
+ run: z$1.object({ testName: z$1.string().optional() }).optional()
109
+ });
110
+ const AcquireResultSchema = z$1.object({
111
+ handle: HandleSchema,
112
+ protocol: z$1.enum(PLUGIN_SERVES),
113
+ url: z$1.string(),
114
+ headers: z$1.record(z$1.string(), z$1.string()).optional(),
115
+ capabilities: JsonObjectSchema.optional(),
116
+ mask: z$1.array(z$1.string()).optional(),
117
+ link: z$1.string().optional()
118
+ });
119
+ const UploadAppParamsSchema = z$1.object({
120
+ remote: z$1.string(),
121
+ settings: JsonObjectSchema,
122
+ platform: z$1.enum(["android", "ios"]),
123
+ path: z$1.string(),
124
+ sha256: z$1.string(),
125
+ size: z$1.number().int().nonnegative()
126
+ });
127
+ const UploadAppResultSchema = z$1.object({ ref: z$1.string() });
128
+ const StartedParamsSchema = z$1.object({
129
+ handle: HandleSchema,
130
+ sessionId: z$1.string().optional()
131
+ });
132
+ const StartedResultSchema = z$1.object({
133
+ link: z$1.string().optional(),
134
+ appiumVersion: z$1.string().optional()
135
+ });
136
+ const EndParamsSchema = z$1.object({
137
+ handle: HandleSchema,
138
+ outcome: z$1.enum([
139
+ "passed",
140
+ "failed",
141
+ "error",
142
+ "cancelled"
143
+ ]),
144
+ reason: z$1.string().optional()
145
+ });
146
+ const ReleaseParamsSchema = z$1.object({ handle: HandleSchema });
147
+ const CancelParamsSchema = z$1.object({ id: RequestIdSchema });
148
+ z$1.object({
149
+ id: RequestIdSchema,
150
+ message: z$1.string(),
151
+ queued: z$1.number().int().nonnegative().optional(),
152
+ percent: z$1.number().min(0).max(100).optional()
153
+ });
154
+ z$1.object({
155
+ kind: z$1.enum(PLUGIN_FAILURE_KINDS),
156
+ path: z$1.string().optional(),
157
+ handle: HandleSchema.optional(),
158
+ supported: z$1.array(z$1.number().int()).optional(),
159
+ requested: z$1.array(z$1.number().int()).optional()
160
+ });
161
+ // src/define-plugin.ts
162
+ var PluginError = class extends Error {
163
+ kind;
164
+ path;
165
+ handle;
166
+ constructor(message, options) {
167
+ super(message);
168
+ this.name = "PluginError";
169
+ this.kind = options.kind;
170
+ this.path = options.path;
171
+ this.handle = options.handle;
172
+ }
173
+ };
174
+ function definePlugin(spec) {
175
+ const methods = OPTIONAL_PLUGIN_METHODS.filter((method) => typeof spec[method] === "function");
176
+ return {
177
+ ...spec,
178
+ methods
179
+ };
180
+ }
181
+ // src/package-root.ts
182
+ function findPackageRoot(fromDir) {
183
+ let dir = fromDir;
184
+ for (;;) {
185
+ if (existsSync(join(dir, "package.json"))) return dir;
186
+ const parent = dirname(dir);
187
+ if (parent === dir) return void 0;
188
+ dir = parent;
189
+ }
190
+ }
191
+ function readPackageJson(packageRoot) {
192
+ const parsed = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
193
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
194
+ }
195
+ // src/protocol/framing.ts
196
+ function encodeMessage(message) {
197
+ return `${JSON.stringify(message)}\n`;
198
+ }
199
+ const NEWLINE = 10;
200
+ function createLineDecoder({ maxLineBytes, onLine, onOverflow }) {
201
+ const encoder = new TextEncoder();
202
+ const decoder = new TextDecoder();
203
+ let parts = [];
204
+ let size = 0;
205
+ let discarding = false;
206
+ const append = (segment) => {
207
+ if (discarding || segment.length === 0) return;
208
+ if (size + segment.length > maxLineBytes) {
209
+ discarding = true;
210
+ parts = [];
211
+ size = 0;
212
+ onOverflow();
213
+ return;
214
+ }
215
+ parts.push(new Uint8Array(segment));
216
+ size += segment.length;
217
+ };
218
+ const finishLine = () => {
219
+ const wasDiscarding = discarding;
220
+ const lineParts = parts;
221
+ const lineSize = size;
222
+ parts = [];
223
+ size = 0;
224
+ discarding = false;
225
+ if (wasDiscarding || lineSize === 0) return;
226
+ const bytes = new Uint8Array(lineSize);
227
+ let offset = 0;
228
+ for (const part of lineParts) {
229
+ bytes.set(part, offset);
230
+ offset += part.length;
231
+ }
232
+ const line = decoder.decode(bytes).replace(/\r$/, "");
233
+ if (line.trim() !== "") onLine(line);
234
+ };
235
+ return {
236
+ push(chunk) {
237
+ const bytes = typeof chunk === "string" ? encoder.encode(chunk) : chunk;
238
+ let start = 0;
239
+ while (start <= bytes.length) {
240
+ const newline = bytes.indexOf(NEWLINE, start);
241
+ if (newline === -1) {
242
+ append(bytes.subarray(start));
243
+ return;
244
+ }
245
+ append(bytes.subarray(start, newline));
246
+ finishLine();
247
+ start = newline + 1;
248
+ }
249
+ },
250
+ end() {
251
+ finishLine();
252
+ }
253
+ };
254
+ }
255
+ // src/run-plugin.ts
256
+ const CANCEL_ANSWER_GRACE_MS = 4e3;
257
+ const HOST_PID_POLL_MS = 1e3;
258
+ const RELEASE_ON_CLOSE_BOUND_MS = 3e4;
259
+ const RELEASE_ON_SIGTERM_BOUND_MS = 4e3;
260
+ const SUPPORTED_PROTOCOL_VERSIONS = [1];
261
+ var ProtocolFailure = class extends Error {
262
+ code;
263
+ data;
264
+ constructor(code, message, data) {
265
+ super(message);
266
+ this.code = code;
267
+ this.data = data;
268
+ }
269
+ };
270
+ function resolveIo(io) {
271
+ return {
272
+ stdin: io.stdin ?? process.stdin,
273
+ stdout: io.stdout ?? process.stdout,
274
+ exit: io.exit ?? ((code) => process.exit(code)),
275
+ entryPath: io.entryPath ?? process.argv[1],
276
+ cancelAnswerGraceMs: io.cancelAnswerGraceMs ?? 4e3,
277
+ hostPidPollMs: io.hostPidPollMs ?? 1e3
278
+ };
279
+ }
280
+ function routeStrayOutputToStderr() {
281
+ const toLog = (...args) => {
282
+ process.stderr.write(`${format(...args)}\n`);
283
+ };
284
+ console.log = toLog;
285
+ console.info = toLog;
286
+ console.debug = toLog;
287
+ console.warn = toLog;
288
+ process.stdout.write = process.stderr.write.bind(process.stderr);
289
+ }
290
+ function hostGone(hostPid, startPpid) {
291
+ if (process.ppid !== startPpid) return true;
292
+ try {
293
+ process.kill(hostPid, 0);
294
+ return false;
295
+ } catch (error) {
296
+ return error.code === "ESRCH";
297
+ }
298
+ }
299
+ function handleKey(handle) {
300
+ const canonical = (value) => {
301
+ if (Array.isArray(value)) return value.map(canonical);
302
+ if (value === null || typeof value !== "object") return value;
303
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
304
+ };
305
+ return JSON.stringify(canonical(handle));
306
+ }
307
+ function within(work, boundMs) {
308
+ let timer;
309
+ const bound = new Promise((resolve) => {
310
+ timer = setTimeout(() => resolve(void 0), boundMs);
311
+ });
312
+ return Promise.race([work, bound]).finally(() => clearTimeout(timer));
313
+ }
314
+ function firstLine(message) {
315
+ return message.split(/\r?\n/, 1)[0] ?? "";
316
+ }
317
+ function isRecord$1(value) {
318
+ return typeof value === "object" && value !== null && !Array.isArray(value);
319
+ }
320
+ function isRequestId(value) {
321
+ return typeof value === "string" || typeof value === "number";
322
+ }
323
+ function isPluginMethod(method) {
324
+ return PLUGIN_METHODS.includes(method);
325
+ }
326
+ function isOptionalMethod(method) {
327
+ return OPTIONAL_PLUGIN_METHODS.includes(method);
328
+ }
329
+ function issuePath(issue) {
330
+ return issue.path.map(String).join(".");
331
+ }
332
+ function parseParams(schema, params, method) {
333
+ const parsed = schema.safeParse(params ?? {});
334
+ if (parsed.success) return parsed.data;
335
+ const issue = parsed.error.issues[0];
336
+ const where = issue === void 0 ? "" : issuePath(issue);
337
+ throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidParams, `${method} params${where === "" ? "" : ` at ${where}`}: ${issue?.message ?? "invalid"}`);
338
+ }
339
+ function refuse(schema, value, prefix) {
340
+ const parsed = schema.safeParse(value);
341
+ if (parsed.success) return parsed.data;
342
+ const issue = parsed.error.issues[0];
343
+ const inner = issue === void 0 ? "" : issuePath(issue);
344
+ const path = inner === "" ? void 0 : `${prefix}.${inner}`;
345
+ throw new PluginError(`${path ?? prefix}: ${issue?.message ?? "invalid"}`, {
346
+ kind: "refused",
347
+ ...path === void 0 ? {} : { path }
348
+ });
349
+ }
350
+ function checkedResult(schema, value, method) {
351
+ const parsed = schema.safeParse(value);
352
+ if (parsed.success) return value;
353
+ const issue = parsed.error.issues[0];
354
+ const where = issue === void 0 ? "" : issuePath(issue);
355
+ const handle = method === "acquire" && isRecord$1(value) && value.handle !== void 0 ? value.handle : void 0;
356
+ throw new PluginError(`${method} answered a malformed result${where === "" ? "" : ` at ${where}`}: ${issue?.message ?? "invalid"}`, {
357
+ kind: "refused",
358
+ ...handle === void 0 ? {} : { handle }
359
+ });
360
+ }
361
+ function pluginIdentity(entryPath) {
362
+ const unknown = {
363
+ name: "unknown",
364
+ version: "unknown"
365
+ };
366
+ if (entryPath === void 0) return unknown;
367
+ try {
368
+ const root = findPackageRoot(dirname(realpathSync(entryPath)));
369
+ if (root === void 0) return unknown;
370
+ const pkg = readPackageJson(root);
371
+ return {
372
+ name: typeof pkg.name === "string" ? pkg.name : unknown.name,
373
+ version: typeof pkg.version === "string" ? pkg.version : unknown.version
374
+ };
375
+ } catch {
376
+ return unknown;
377
+ }
378
+ }
379
+ function runPlugin(definition, ioInput = {}) {
380
+ const io = resolveIo(ioInput);
381
+ const write = io.stdout.write.bind(io.stdout);
382
+ const ownsProcess = ioInput.stdout === void 0;
383
+ if (ownsProcess) {
384
+ routeStrayOutputToStderr();
385
+ process.stdout.on("error", () => {});
386
+ process.stderr.on("error", () => {});
387
+ process.on("SIGINT", () => {});
388
+ process.on("uncaughtException", (error) => {
389
+ try {
390
+ writeSync(2, `${inspect(error)}\n`);
391
+ } catch {}
392
+ process.exit(1);
393
+ });
394
+ }
395
+ const send = (message) => {
396
+ if (leaving) return;
397
+ write(encodeMessage(message));
398
+ };
399
+ const answer = (id, result) => send({
400
+ jsonrpc: "2.0",
401
+ id,
402
+ result
403
+ });
404
+ const fail = (id, code, message, data) => send({
405
+ jsonrpc: "2.0",
406
+ id,
407
+ error: {
408
+ code,
409
+ message: firstLine(message),
410
+ ...data === void 0 ? {} : { data }
411
+ }
412
+ });
413
+ let options;
414
+ let runFacts;
415
+ let initialized = false;
416
+ let initializing = false;
417
+ let shutdownRan = false;
418
+ let leaving = false;
419
+ let hostPoll;
420
+ const startPpid = process.ppid;
421
+ const pending = /* @__PURE__ */ new Map();
422
+ const inFlight = /* @__PURE__ */ new Set();
423
+ const held = /* @__PURE__ */ new Map();
424
+ const releasing = /* @__PURE__ */ new Map();
425
+ const detachedContext = () => ({
426
+ signal: new AbortController().signal,
427
+ progress: () => {},
428
+ runFacts
429
+ });
430
+ const hold = (handle) => held.set(handleKey(handle), handle);
431
+ const releaseOnce = (handle, ctx) => {
432
+ const key = handleKey(handle);
433
+ const running = releasing.get(key);
434
+ if (running !== void 0) return running;
435
+ const work = (async () => {
436
+ try {
437
+ await definition.release({
438
+ handle,
439
+ options
440
+ }, ctx);
441
+ held.delete(key);
442
+ } finally {
443
+ releasing.delete(key);
444
+ }
445
+ })();
446
+ releasing.set(key, work);
447
+ return work;
448
+ };
449
+ const releaseHeld = (attempted) => Promise.allSettled([...held].filter(([key]) => !attempted.has(key)).map(([key, handle]) => {
450
+ attempted.add(key);
451
+ return releaseOnce(handle, detachedContext());
452
+ }));
453
+ const settings = (value) => refuse(definition.settings, value, "settings");
454
+ const leave = async (boundMs) => {
455
+ if (leaving) return;
456
+ leaving = true;
457
+ clearInterval(hostPoll);
458
+ const sequence = async () => {
459
+ if (!initialized) return;
460
+ for (const entry of pending.values()) if (entry.method !== "release" && entry.method !== "shutdown") entry.controller.abort();
461
+ const attempted = /* @__PURE__ */ new Set();
462
+ await Promise.allSettled([releaseHeld(attempted), ...inFlight]);
463
+ await releaseHeld(attempted);
464
+ if (shutdownRan) return;
465
+ shutdownRan = true;
466
+ await definition.shutdown?.({ options }, detachedContext());
467
+ };
468
+ await within(sequence().catch(() => {}), boundMs);
469
+ write("", () => io.exit(0));
470
+ };
471
+ const watchHost = (hostPid) => {
472
+ hostPoll = setInterval(() => {
473
+ if (hostGone(hostPid, startPpid)) leave(RELEASE_ON_CLOSE_BOUND_MS);
474
+ }, io.hostPidPollMs);
475
+ hostPoll.unref();
476
+ };
477
+ const initialize = async (params, ctx) => {
478
+ if (initialized || initializing) throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidRequest, "initialize was already called");
479
+ const input = parseParams(InitializeParamsSchema, params, "initialize");
480
+ const offered = input.protocolVersions.filter((version) => SUPPORTED_PROTOCOL_VERSIONS.includes(version));
481
+ if (offered.length === 0) throw new ProtocolFailure(PLUGIN_ERROR_CODES.plugin, `this plugin speaks protocol ${SUPPORTED_PROTOCOL_VERSIONS.join(", ")}, and the host offered ${input.protocolVersions.join(", ") || "none"}`, {
482
+ kind: "version",
483
+ supported: SUPPORTED_PROTOCOL_VERSIONS,
484
+ requested: input.protocolVersions
485
+ });
486
+ const parsedOptions = refuse(definition.options, input.options, "options");
487
+ initializing = true;
488
+ try {
489
+ await definition.initialize?.({
490
+ options: parsedOptions,
491
+ host: input.host,
492
+ entry: input.entry,
493
+ runFacts: input.runFacts
494
+ }, {
495
+ ...ctx,
496
+ runFacts: input.runFacts
497
+ });
498
+ } finally {
499
+ initializing = false;
500
+ }
501
+ options = parsedOptions;
502
+ runFacts = input.runFacts;
503
+ initialized = true;
504
+ watchHost(input.host.pid);
505
+ return {
506
+ protocolVersion: Math.max(...offered),
507
+ plugin: pluginIdentity(io.entryPath),
508
+ serves: [...definition.serves],
509
+ methods: [...definition.methods]
510
+ };
511
+ };
512
+ const call = async (method, params, ctx) => {
513
+ if (method === "initialize") return initialize(params, ctx);
514
+ if (!isPluginMethod(method) || isOptionalMethod(method) && (!definition.methods.includes(method) || typeof definition[method] !== "function")) throw new ProtocolFailure(PLUGIN_ERROR_CODES.methodNotFound, `${method} is not a method of this plugin`);
515
+ if (!initialized) throw new ProtocolFailure(PLUGIN_ERROR_CODES.invalidRequest, `${method} came before initialize`);
516
+ const current = options;
517
+ switch (method) {
518
+ case "acquire": {
519
+ const input = parseParams(AcquireParamsSchema, params, method);
520
+ const result = await definition.acquire({
521
+ ...input,
522
+ settings: settings(input.settings),
523
+ options: current
524
+ }, ctx);
525
+ return checkedResult(AcquireResultSchema, result, method);
526
+ }
527
+ case "release": {
528
+ const input = parseParams(ReleaseParamsSchema, params, method);
529
+ await releaseOnce(input.handle, ctx);
530
+ return {};
531
+ }
532
+ case "shutdown":
533
+ shutdownRan = true;
534
+ await releaseHeld(/* @__PURE__ */ new Set());
535
+ await definition.shutdown?.({ options: current }, ctx);
536
+ return {};
537
+ case "check": {
538
+ const remotes = parseParams(CheckParamsSchema, params, method).remotes.map((remote) => ({
539
+ name: remote.name,
540
+ settings: settings(remote.settings)
541
+ }));
542
+ const result = await definition.check?.({
543
+ remotes,
544
+ options: current
545
+ }, ctx);
546
+ return checkedResult(CheckResultSchema, result, method);
547
+ }
548
+ case "uploadApp": {
549
+ const input = parseParams(UploadAppParamsSchema, params, method);
550
+ const result = await definition.uploadApp?.({
551
+ ...input,
552
+ settings: settings(input.settings),
553
+ options: current
554
+ }, ctx);
555
+ return checkedResult(UploadAppResultSchema, result, method);
556
+ }
557
+ case "started": {
558
+ const input = parseParams(StartedParamsSchema, params, method);
559
+ const result = await definition.started?.({
560
+ ...input,
561
+ options: current
562
+ }, ctx);
563
+ return checkedResult(StartedResultSchema, result, method);
564
+ }
565
+ case "end": {
566
+ const input = parseParams(EndParamsSchema, params, method);
567
+ await definition.end?.({
568
+ ...input,
569
+ options: current
570
+ }, ctx);
571
+ return {};
572
+ }
573
+ default: throw new ProtocolFailure(PLUGIN_ERROR_CODES.methodNotFound, `${method} is not a method of this plugin`);
574
+ }
575
+ };
576
+ const createdHandle = (method, outcome) => {
577
+ if (outcome.ok) {
578
+ if (method !== "acquire" || !isRecord$1(outcome.value)) return void 0;
579
+ return outcome.value.handle;
580
+ }
581
+ return outcome.error instanceof PluginError ? outcome.error.handle : void 0;
582
+ };
583
+ const answerFailure = (id, error) => {
584
+ if (error instanceof ProtocolFailure) fail(id, error.code, error.message, error.data);
585
+ else if (error instanceof PluginError) fail(id, PLUGIN_ERROR_CODES.plugin, error.message, {
586
+ kind: error.kind,
587
+ ...error.path === void 0 ? {} : { path: error.path },
588
+ ...error.handle === void 0 ? {} : { handle: error.handle }
589
+ });
590
+ else {
591
+ const message = error instanceof Error ? error.message : String(error);
592
+ fail(id, PLUGIN_ERROR_CODES.plugin, message, { kind: "unreachable" });
593
+ }
594
+ };
595
+ const dispatch = async (id, method, params) => {
596
+ const entry = {
597
+ method,
598
+ controller: new AbortController(),
599
+ answered: false
600
+ };
601
+ pending.set(id, entry);
602
+ const ctx = {
603
+ signal: entry.controller.signal,
604
+ progress: (update) => {
605
+ if (entry.answered) return;
606
+ send({
607
+ jsonrpc: "2.0",
608
+ method: PROGRESS_METHOD,
609
+ params: {
610
+ id,
611
+ ...update
612
+ }
613
+ });
614
+ },
615
+ runFacts
616
+ };
617
+ let outcome;
618
+ try {
619
+ outcome = {
620
+ ok: true,
621
+ value: await call(method, params, ctx)
622
+ };
623
+ } catch (error) {
624
+ outcome = {
625
+ ok: false,
626
+ error
627
+ };
628
+ }
629
+ clearTimeout(entry.graceTimer);
630
+ if (pending.get(id) === entry) pending.delete(id);
631
+ const handle = createdHandle(method, outcome);
632
+ const tracked = initialized ? handle : void 0;
633
+ if (tracked !== void 0) hold(tracked);
634
+ if (entry.answered) {
635
+ if (tracked !== void 0) await releaseOnce(tracked, detachedContext()).catch(() => {});
636
+ return;
637
+ }
638
+ if (leaving) return;
639
+ entry.answered = true;
640
+ if (outcome.ok) answer(id, outcome.value);
641
+ else if (entry.controller.signal.aborted) {
642
+ const message = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
643
+ fail(id, PLUGIN_ERROR_CODES.cancelled, message, {
644
+ kind: "timeout",
645
+ ...handle === void 0 ? {} : { handle }
646
+ });
647
+ } else answerFailure(id, outcome.error);
648
+ };
649
+ const cancel = (params) => {
650
+ const parsed = CancelParamsSchema.safeParse(params);
651
+ if (!parsed.success) return;
652
+ const { id } = parsed.data;
653
+ const entry = pending.get(id);
654
+ if (entry === void 0 || entry.controller.signal.aborted) return;
655
+ entry.controller.abort();
656
+ entry.graceTimer = setTimeout(() => {
657
+ if (entry.answered) return;
658
+ entry.answered = true;
659
+ fail(id, PLUGIN_ERROR_CODES.cancelled, `${entry.method} did not stop within ${io.cancelAnswerGraceMs} ms of its cancel`, { kind: "timeout" });
660
+ }, io.cancelAnswerGraceMs);
661
+ };
662
+ const handleLine = (line) => {
663
+ let message;
664
+ try {
665
+ message = JSON.parse(line);
666
+ } catch {
667
+ fail(null, PLUGIN_ERROR_CODES.parse, "the line is not JSON");
668
+ return;
669
+ }
670
+ if (!isRecord$1(message)) {
671
+ fail(null, PLUGIN_ERROR_CODES.invalidRequest, "a message must be a JSON-RPC 2.0 object");
672
+ return;
673
+ }
674
+ const id = isRequestId(message.id) ? message.id : null;
675
+ if (message.method === void 0 && ("result" in message || "error" in message)) return;
676
+ if (message.jsonrpc !== "2.0" || typeof message.method !== "string") {
677
+ fail(id, PLUGIN_ERROR_CODES.invalidRequest, "a request needs jsonrpc \"2.0\" and a method");
678
+ return;
679
+ }
680
+ if (!("id" in message)) {
681
+ if (message.method === "$/cancel") cancel(message.params);
682
+ return;
683
+ }
684
+ if (id === null) {
685
+ fail(null, PLUGIN_ERROR_CODES.invalidRequest, "a request id is a string or a number");
686
+ return;
687
+ }
688
+ if (leaving) return;
689
+ const run = dispatch(id, message.method, message.params);
690
+ inFlight.add(run);
691
+ run.catch(() => {}).finally(() => inFlight.delete(run));
692
+ };
693
+ const decoder = createLineDecoder({
694
+ maxLineBytes: MAX_MESSAGE_BYTES,
695
+ onLine: handleLine,
696
+ onOverflow: () => fail(null, PLUGIN_ERROR_CODES.invalidRequest, `a message is longer than ${MAX_MESSAGE_BYTES} bytes`)
697
+ });
698
+ const stdinClosed = () => {
699
+ if (leaving) return;
700
+ decoder.end();
701
+ leave(RELEASE_ON_CLOSE_BOUND_MS);
702
+ };
703
+ if (ownsProcess) process.on("SIGTERM", () => void leave(RELEASE_ON_SIGTERM_BOUND_MS));
704
+ io.stdin.on("data", (chunk) => decoder.push(chunk));
705
+ io.stdin.on("end", stdinClosed);
706
+ io.stdin.on("error", stdinClosed);
707
+ }
708
+ // src/protocol/manifest.ts
709
+ const PLUGIN_MANIFEST_FIELD = "agent-qa";
710
+ const PackagePathSchema = z$1.string().min(1);
711
+ const RemotePluginManifestSchema = z$1.strictObject({
712
+ kind: z$1.literal("remote"),
713
+ runs: z$1.literal("process"),
714
+ protocol: z$1.number().int().positive(),
715
+ entry: PackagePathSchema,
716
+ serves: z$1.array(z$1.enum(PLUGIN_SERVES)).nonempty(),
717
+ methods: z$1.array(z$1.enum(OPTIONAL_PLUGIN_METHODS)),
718
+ options: PackagePathSchema,
719
+ settings: PackagePathSchema,
720
+ runFacts: z$1.array(z$1.enum(PLUGIN_RUN_FACTS)).optional(),
721
+ skill: PackagePathSchema.optional()
722
+ });
723
+ const ModelAuthManifestSchema = z$1.strictObject({
724
+ kind: z$1.literal("model-auth"),
725
+ runs: z$1.literal("in-process"),
726
+ entry: PackagePathSchema,
727
+ options: PackagePathSchema.optional()
728
+ });
729
+ const ModelProviderManifestSchema = z$1.strictObject({
730
+ kind: z$1.literal("model-provider"),
731
+ runs: z$1.literal("in-process"),
732
+ entry: PackagePathSchema,
733
+ options: PackagePathSchema.optional()
734
+ });
735
+ const PluginManifestSchema = z$1.discriminatedUnion("kind", [
736
+ RemotePluginManifestSchema,
737
+ ModelAuthManifestSchema,
738
+ ModelProviderManifestSchema
739
+ ]);
740
+ // src/emit.ts
741
+ const OPTIONS_SCHEMA_PATH = "./dist/options.schema.json";
742
+ const SETTINGS_SCHEMA_PATH = "./dist/settings.schema.json";
743
+ function pluginManifest(definition) {
744
+ const manifest = PluginManifestSchema.parse({
745
+ protocol: 1,
746
+ kind: "remote",
747
+ runs: "process",
748
+ entry: "./dist/main.js",
749
+ serves: [...definition.serves],
750
+ methods: [...definition.methods],
751
+ options: OPTIONS_SCHEMA_PATH,
752
+ settings: SETTINGS_SCHEMA_PATH,
753
+ ...definition.runFacts === void 0 ? {} : { runFacts: [...definition.runFacts] },
754
+ ...definition.skill === void 0 ? {} : { skill: definition.skill }
755
+ });
756
+ if (manifest.kind !== "remote") throw new Error("a plugin built with the kit is remote");
757
+ return manifest;
758
+ }
759
+ function schemaText(schema) {
760
+ return `${JSON.stringify(z$1.toJSONSchema(schema, {
761
+ io: "input",
762
+ target: "draft-2020-12"
763
+ }), null, 2)}\n`;
764
+ }
765
+ function isRecord(value) {
766
+ return typeof value === "object" && value !== null && !Array.isArray(value);
767
+ }
768
+ function emitPluginFiles(input) {
769
+ const { definition, packageDir } = input;
770
+ const manifest = pluginManifest(definition);
771
+ const optionsSchemaFile = join(packageDir, OPTIONS_SCHEMA_PATH);
772
+ const settingsSchemaFile = join(packageDir, SETTINGS_SCHEMA_PATH);
773
+ mkdirSync(join(packageDir, "dist"), { recursive: true });
774
+ writeFileSync(optionsSchemaFile, schemaText(definition.options));
775
+ writeFileSync(settingsSchemaFile, schemaText(definition.settings));
776
+ const packageJsonFile = join(packageDir, "package.json");
777
+ const parsed = JSON.parse(readFileSync(packageJsonFile, "utf8"));
778
+ if (!isRecord(parsed)) throw new Error(`${packageJsonFile} is not a JSON object`);
779
+ const field = isRecord(parsed["agent-qa"]) ? parsed[PLUGIN_MANIFEST_FIELD] : {};
780
+ const packageJsonChanged = !isDeepStrictEqual(field.plugin, manifest);
781
+ if (packageJsonChanged) {
782
+ const next = {
783
+ ...parsed,
784
+ [PLUGIN_MANIFEST_FIELD]: {
785
+ ...field,
786
+ plugin: manifest
787
+ }
788
+ };
789
+ writeFileSync(packageJsonFile, `${JSON.stringify(next, null, 2)}\n`);
790
+ }
791
+ return {
792
+ manifest,
793
+ optionsSchemaFile,
794
+ settingsSchemaFile,
795
+ packageJsonChanged
796
+ };
797
+ }
798
+ export { CANCEL_ANSWER_GRACE_MS, HOST_PID_POLL_MS, PluginError, credential, definePlugin, emitPluginFiles, pluginManifest, runPlugin, z };
799
+
800
+ //# sourceMappingURL=index.js.map