@pinpatch/core 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1289 @@
1
+ // src/contracts/artifacts.ts
2
+ import { z as z2 } from "zod";
3
+
4
+ // src/contracts/provider.ts
5
+ import { z } from "zod";
6
+ var ProviderNameSchema = z.enum(["codex", "claude", "cursor"]);
7
+ var ProviderProgressStatusSchema = z.enum([
8
+ "queued",
9
+ "running",
10
+ "completed",
11
+ "error",
12
+ "cancelled",
13
+ "timeout"
14
+ ]);
15
+ var ProviderTerminalStatusSchema = z.enum(["completed", "error", "cancelled", "timeout"]);
16
+ var ProviderProgressSchema = z.object({
17
+ taskId: z.string().min(1),
18
+ sessionId: z.string().min(1),
19
+ status: ProviderProgressStatusSchema,
20
+ message: z.string().min(1),
21
+ percent: z.number().min(0).max(100).optional(),
22
+ timestamp: z.string().datetime()
23
+ });
24
+ var ProviderResultSchema = z.object({
25
+ taskId: z.string().min(1),
26
+ sessionId: z.string().min(1),
27
+ status: ProviderTerminalStatusSchema,
28
+ summary: z.string().min(1),
29
+ changedFiles: z.array(z.string()),
30
+ errorCode: z.string().optional(),
31
+ errorMessage: z.string().optional()
32
+ });
33
+ var ProviderErrorCodes = {
34
+ ProviderUnavailable: "PROVIDER_UNAVAILABLE",
35
+ ProviderNotEnabled: "PROVIDER_NOT_ENABLED",
36
+ ProviderTimeout: "PROVIDER_TIMEOUT",
37
+ ProcessFailed: "PROVIDER_PROCESS_FAILED",
38
+ ValidationFailed: "PROVIDER_VALIDATION_FAILED",
39
+ Unknown: "UNKNOWN"
40
+ };
41
+
42
+ // src/contracts/artifacts.ts
43
+ var PinStateSchema = z2.enum([
44
+ "idle",
45
+ "queued",
46
+ "running",
47
+ "completed",
48
+ "error",
49
+ "cancelled",
50
+ "timeout"
51
+ ]);
52
+ var ViewportSchema = z2.object({
53
+ width: z2.number().int().positive(),
54
+ height: z2.number().int().positive()
55
+ });
56
+ var BoundingBoxSchema = z2.object({
57
+ x: z2.number(),
58
+ y: z2.number(),
59
+ width: z2.number(),
60
+ height: z2.number()
61
+ });
62
+ var ElementDescriptorSchema = z2.object({
63
+ tag: z2.string().min(1),
64
+ role: z2.string().nullable().optional(),
65
+ text: z2.string().nullable().optional(),
66
+ attributes: z2.record(z2.union([z2.string(), z2.null()])),
67
+ boundingBox: BoundingBoxSchema
68
+ });
69
+ var UiChangePacketSchema = z2.object({
70
+ id: z2.string().min(1),
71
+ timestamp: z2.string().datetime(),
72
+ url: z2.string().min(1),
73
+ viewport: ViewportSchema,
74
+ element: ElementDescriptorSchema,
75
+ nearbyText: z2.array(z2.string()),
76
+ domSnippet: z2.string(),
77
+ computedStyleSummary: z2.record(z2.string()),
78
+ screenshotPath: z2.string().min(1),
79
+ userRequest: z2.string().min(1)
80
+ });
81
+ var TaskStatusSchema = z2.enum([
82
+ "created",
83
+ "queued",
84
+ "running",
85
+ "completed",
86
+ "error",
87
+ "cancelled",
88
+ "timeout"
89
+ ]);
90
+ var TaskPinSchema = z2.object({
91
+ x: z2.number(),
92
+ y: z2.number(),
93
+ body: z2.string().min(1)
94
+ });
95
+ var TaskRecordSchema = z2.object({
96
+ taskId: z2.string().min(1),
97
+ createdAt: z2.string().datetime(),
98
+ updatedAt: z2.string().datetime(),
99
+ status: TaskStatusSchema,
100
+ url: z2.string().min(1),
101
+ viewport: ViewportSchema,
102
+ pin: TaskPinSchema,
103
+ uiChangePacket: UiChangePacketSchema,
104
+ screenshotPath: z2.string().min(1),
105
+ provider: ProviderNameSchema.optional(),
106
+ model: z2.string().optional(),
107
+ latestSessionId: z2.string().optional(),
108
+ sessions: z2.array(z2.string()),
109
+ summary: z2.string().optional(),
110
+ changedFiles: z2.array(z2.string()).default([]),
111
+ errorCode: z2.string().optional(),
112
+ errorMessage: z2.string().optional()
113
+ });
114
+ var SessionEventSchema = z2.object({
115
+ status: ProviderProgressStatusSchema,
116
+ message: z2.string().min(1),
117
+ percent: z2.number().min(0).max(100).optional(),
118
+ timestamp: z2.string().datetime()
119
+ });
120
+ var SessionRecordSchema = z2.object({
121
+ sessionId: z2.string().min(1),
122
+ taskId: z2.string().min(1),
123
+ provider: ProviderNameSchema,
124
+ model: z2.string().min(1),
125
+ status: ProviderProgressStatusSchema,
126
+ dryRun: z2.boolean(),
127
+ startedAt: z2.string().datetime(),
128
+ updatedAt: z2.string().datetime(),
129
+ endedAt: z2.string().datetime().optional(),
130
+ events: z2.array(SessionEventSchema),
131
+ summary: z2.string().optional(),
132
+ changedFiles: z2.array(z2.string()).default([]),
133
+ errorCode: z2.string().optional(),
134
+ errorMessage: z2.string().optional()
135
+ });
136
+ var RuntimeLogLevelSchema = z2.enum(["debug", "info", "warn", "error"]);
137
+ var RuntimeLogEventSchema = z2.object({
138
+ timestamp: z2.string().datetime(),
139
+ level: RuntimeLogLevelSchema,
140
+ component: z2.string().min(1),
141
+ taskId: z2.string().optional(),
142
+ sessionId: z2.string().optional(),
143
+ event: z2.string().min(1),
144
+ message: z2.string().min(1),
145
+ meta: z2.record(z2.unknown()).optional()
146
+ });
147
+ var PinpatchConfigSchema = z2.object({
148
+ provider: ProviderNameSchema.default("codex"),
149
+ model: z2.string().default("gpt-5.3-codex-spark"),
150
+ target: z2.number().int().positive().default(3e3),
151
+ debug: z2.boolean().default(false),
152
+ bridgePort: z2.number().int().positive().default(7331),
153
+ proxyPort: z2.number().int().positive().default(3030)
154
+ });
155
+
156
+ // src/contracts/bridge.ts
157
+ import { z as z3 } from "zod";
158
+ var CreateTaskRequestSchema = z3.object({
159
+ sessionId: z3.string().min(1),
160
+ url: z3.string().min(1),
161
+ viewport: ViewportSchema,
162
+ pin: z3.object({
163
+ x: z3.number(),
164
+ y: z3.number(),
165
+ body: z3.string().min(1)
166
+ }),
167
+ uiChangePacket: UiChangePacketSchema,
168
+ screenshotPath: z3.string().min(1),
169
+ screenshotDataUrl: z3.string().startsWith("data:image/").optional(),
170
+ clientTaskId: z3.string().min(1).optional()
171
+ });
172
+ var CreateTaskResponseSchema = z3.object({
173
+ taskId: z3.string(),
174
+ sessionId: z3.string(),
175
+ status: z3.literal("created"),
176
+ taskPath: z3.string(),
177
+ eventsUrl: z3.string()
178
+ });
179
+ var SubmitTaskRequestSchema = z3.object({
180
+ sessionId: z3.string().min(1),
181
+ provider: ProviderNameSchema,
182
+ model: z3.string().min(1),
183
+ dryRun: z3.boolean().default(false),
184
+ debug: z3.boolean().default(false),
185
+ followUpBody: z3.string().trim().min(1).optional()
186
+ });
187
+ var SubmitTaskResponseSchema = z3.object({
188
+ taskId: z3.string().min(1),
189
+ sessionId: z3.string().min(1),
190
+ status: z3.literal("queued"),
191
+ acceptedAt: z3.string().datetime(),
192
+ eventsUrl: z3.string().min(1)
193
+ });
194
+ var SseProgressEventSchema = z3.object({
195
+ type: z3.literal("progress"),
196
+ taskId: z3.string().min(1),
197
+ sessionId: z3.string().min(1),
198
+ status: z3.enum([
199
+ "queued",
200
+ "running",
201
+ "completed",
202
+ "error",
203
+ "cancelled",
204
+ "timeout"
205
+ ]),
206
+ message: z3.string().min(1),
207
+ percent: z3.number().min(0).max(100).optional(),
208
+ timestamp: z3.string().datetime()
209
+ });
210
+ var SseTerminalEventSchema = z3.object({
211
+ type: z3.literal("terminal"),
212
+ taskId: z3.string().min(1),
213
+ sessionId: z3.string().min(1),
214
+ status: z3.enum(["completed", "error", "cancelled", "timeout"]),
215
+ summary: z3.string().min(1),
216
+ changedFiles: z3.array(z3.string()),
217
+ errorCode: z3.string().optional(),
218
+ errorMessage: z3.string().optional(),
219
+ timestamp: z3.string().datetime()
220
+ });
221
+ var SseHeartbeatEventSchema = z3.object({
222
+ type: z3.literal("heartbeat"),
223
+ timestamp: z3.string().datetime()
224
+ });
225
+ var SseEventSchema = z3.union([
226
+ SseProgressEventSchema,
227
+ SseTerminalEventSchema,
228
+ SseHeartbeatEventSchema
229
+ ]);
230
+
231
+ // src/config.ts
232
+ import path from "path";
233
+ import { promises as fs } from "fs";
234
+ var DEFAULT_CONFIG = {
235
+ provider: "codex",
236
+ model: "gpt-5.3-codex-spark",
237
+ target: 3e3,
238
+ debug: false,
239
+ bridgePort: 7331,
240
+ proxyPort: 3030
241
+ };
242
+ var DEFAULT_CLAUDE_MODEL = "sonnet";
243
+ var resolveConfigPath = (cwd) => path.join(cwd, ".pinpatch", "config.json");
244
+ var omitUndefined = (value) => {
245
+ return Object.fromEntries(Object.entries(value).filter(([, fieldValue]) => fieldValue !== void 0));
246
+ };
247
+ var readConfigFile = async (cwd) => {
248
+ const configPath = resolveConfigPath(cwd);
249
+ try {
250
+ const raw = await fs.readFile(configPath, "utf8");
251
+ const parsed = JSON.parse(raw);
252
+ return PinpatchConfigSchema.partial().parse(parsed);
253
+ } catch (error) {
254
+ if (error.code === "ENOENT") {
255
+ return {};
256
+ }
257
+ return {};
258
+ }
259
+ };
260
+ var resolveConfig = async (cwd, overrides = {}) => {
261
+ const fileConfig = await readConfigFile(cwd);
262
+ const overrideConfig = omitUndefined(overrides);
263
+ const hasCliModelOverride = Object.prototype.hasOwnProperty.call(overrideConfig, "model");
264
+ const hasFileModel = Object.prototype.hasOwnProperty.call(fileConfig, "model");
265
+ const hasFileProvider = Object.prototype.hasOwnProperty.call(fileConfig, "provider");
266
+ const isBaselineFileModel = hasFileModel && hasFileProvider && fileConfig.model === DEFAULT_CONFIG.model && fileConfig.provider === DEFAULT_CONFIG.provider;
267
+ const merged = {
268
+ ...DEFAULT_CONFIG,
269
+ ...fileConfig,
270
+ ...overrideConfig
271
+ };
272
+ const shouldUseClaudeDefault = merged.provider === "claude" && merged.model === DEFAULT_CONFIG.model && !hasCliModelOverride && (!hasFileModel || isBaselineFileModel);
273
+ if (shouldUseClaudeDefault) {
274
+ merged.model = DEFAULT_CLAUDE_MODEL;
275
+ }
276
+ return PinpatchConfigSchema.parse(merged);
277
+ };
278
+ var ensureConfigFile = async (cwd) => {
279
+ const configPath = resolveConfigPath(cwd);
280
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
281
+ try {
282
+ const raw = await fs.readFile(configPath, "utf8");
283
+ return PinpatchConfigSchema.parse(JSON.parse(raw));
284
+ } catch {
285
+ await fs.writeFile(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}
286
+ `, "utf8");
287
+ return DEFAULT_CONFIG;
288
+ }
289
+ };
290
+ var getConfigPath = resolveConfigPath;
291
+
292
+ // src/storage/artifact-store.ts
293
+ import path3 from "path";
294
+ import { promises as fs3 } from "fs";
295
+
296
+ // src/utils/fs.ts
297
+ import { promises as fs2 } from "fs";
298
+ import path2 from "path";
299
+ import crypto from "crypto";
300
+ var ensureDir = async (dirPath) => {
301
+ await fs2.mkdir(dirPath, { recursive: true });
302
+ };
303
+ var writeJsonAtomic = async (filePath, payload) => {
304
+ const dir = path2.dirname(filePath);
305
+ await ensureDir(dir);
306
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.${crypto.randomUUID()}.tmp`;
307
+ await fs2.writeFile(tempPath, `${JSON.stringify(payload, null, 2)}
308
+ `, "utf8");
309
+ await fs2.rename(tempPath, filePath);
310
+ };
311
+ var readJsonIfExists = async (filePath) => {
312
+ try {
313
+ const raw = await fs2.readFile(filePath, "utf8");
314
+ return JSON.parse(raw);
315
+ } catch (error) {
316
+ if (error.code === "ENOENT") {
317
+ return void 0;
318
+ }
319
+ throw error;
320
+ }
321
+ };
322
+ var listJsonFiles = async (dirPath) => {
323
+ try {
324
+ const entries = await fs2.readdir(dirPath, { withFileTypes: true });
325
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path2.join(dirPath, entry.name));
326
+ } catch (error) {
327
+ if (error.code === "ENOENT") {
328
+ return [];
329
+ }
330
+ throw error;
331
+ }
332
+ };
333
+
334
+ // src/storage/artifact-store.ts
335
+ var ArtifactStore = class {
336
+ cwd;
337
+ rootDir;
338
+ tasksDir;
339
+ sessionsDir;
340
+ screenshotsDir;
341
+ runtimeDir;
342
+ logsDir;
343
+ configPath;
344
+ constructor(cwd) {
345
+ this.cwd = cwd;
346
+ this.rootDir = path3.join(cwd, ".pinpatch");
347
+ this.tasksDir = path3.join(this.rootDir, "tasks");
348
+ this.sessionsDir = path3.join(this.rootDir, "sessions");
349
+ this.screenshotsDir = path3.join(this.rootDir, "screenshots");
350
+ this.runtimeDir = path3.join(this.rootDir, "runtime");
351
+ this.logsDir = path3.join(this.runtimeDir, "logs");
352
+ this.configPath = path3.join(this.rootDir, "config.json");
353
+ }
354
+ async ensureStructure() {
355
+ await ensureDir(this.tasksDir);
356
+ await ensureDir(this.sessionsDir);
357
+ await ensureDir(this.screenshotsDir);
358
+ await ensureDir(this.logsDir);
359
+ const existingConfig = await readJsonIfExists(this.configPath);
360
+ if (!existingConfig) {
361
+ await writeJsonAtomic(this.configPath, DEFAULT_CONFIG);
362
+ }
363
+ }
364
+ async ensureGitignoreEntry() {
365
+ const gitignorePath = path3.join(this.cwd, ".gitignore");
366
+ try {
367
+ const content = await fs3.readFile(gitignorePath, "utf8");
368
+ if (!content.includes(".pinpatch/")) {
369
+ await fs3.appendFile(gitignorePath, "\n.pinpatch/\n", "utf8");
370
+ }
371
+ } catch (error) {
372
+ if (error.code === "ENOENT") {
373
+ await fs3.writeFile(gitignorePath, ".pinpatch/\n", "utf8");
374
+ return;
375
+ }
376
+ throw error;
377
+ }
378
+ }
379
+ getTaskPath(taskId) {
380
+ return path3.join(this.tasksDir, `${taskId}.json`);
381
+ }
382
+ getSessionPath(sessionId) {
383
+ return path3.join(this.sessionsDir, `${sessionId}.json`);
384
+ }
385
+ getRelativePath(absolutePath) {
386
+ return path3.relative(this.cwd, absolutePath);
387
+ }
388
+ async readConfig() {
389
+ const data = await readJsonIfExists(this.configPath);
390
+ return PinpatchConfigSchema.parse({ ...DEFAULT_CONFIG, ...data });
391
+ }
392
+ async writeConfig(config) {
393
+ const validated = PinpatchConfigSchema.parse(config);
394
+ await writeJsonAtomic(this.configPath, validated);
395
+ }
396
+ async createTask(task) {
397
+ const validated = TaskRecordSchema.parse(task);
398
+ await writeJsonAtomic(this.getTaskPath(validated.taskId), validated);
399
+ return validated;
400
+ }
401
+ async getTask(taskId) {
402
+ const raw = await readJsonIfExists(this.getTaskPath(taskId));
403
+ if (!raw) {
404
+ return void 0;
405
+ }
406
+ return TaskRecordSchema.parse(raw);
407
+ }
408
+ async updateTask(taskId, updater) {
409
+ const current = await this.getTask(taskId);
410
+ if (!current) {
411
+ throw new Error(`Task ${taskId} does not exist`);
412
+ }
413
+ const updated = TaskRecordSchema.parse(updater(current));
414
+ await writeJsonAtomic(this.getTaskPath(taskId), updated);
415
+ return updated;
416
+ }
417
+ async listTasks() {
418
+ const files = await listJsonFiles(this.tasksDir);
419
+ const rows = await Promise.all(
420
+ files.map(async (filePath) => {
421
+ const raw = await readJsonIfExists(filePath);
422
+ return raw ? TaskRecordSchema.parse(raw) : void 0;
423
+ })
424
+ );
425
+ return rows.filter((row) => row !== void 0);
426
+ }
427
+ async createSession(session) {
428
+ const validated = SessionRecordSchema.parse(session);
429
+ await writeJsonAtomic(this.getSessionPath(validated.sessionId), validated);
430
+ return validated;
431
+ }
432
+ async getSession(sessionId) {
433
+ const raw = await readJsonIfExists(this.getSessionPath(sessionId));
434
+ if (!raw) {
435
+ return void 0;
436
+ }
437
+ return SessionRecordSchema.parse(raw);
438
+ }
439
+ async updateSession(sessionId, updater) {
440
+ const current = await this.getSession(sessionId);
441
+ if (!current) {
442
+ throw new Error(`Session ${sessionId} does not exist`);
443
+ }
444
+ const updated = SessionRecordSchema.parse(updater(current));
445
+ await writeJsonAtomic(this.getSessionPath(sessionId), updated);
446
+ return updated;
447
+ }
448
+ async listSessions() {
449
+ const files = await listJsonFiles(this.sessionsDir);
450
+ const rows = await Promise.all(
451
+ files.map(async (filePath) => {
452
+ const raw = await readJsonIfExists(filePath);
453
+ return raw ? SessionRecordSchema.parse(raw) : void 0;
454
+ })
455
+ );
456
+ return rows.filter((row) => row !== void 0);
457
+ }
458
+ async writeScreenshot(taskId, screenshotDataUrl) {
459
+ const matches = screenshotDataUrl.match(/^data:image\/(png|jpeg|jpg);base64,(?<bytes>.+)$/);
460
+ if (!matches?.groups?.bytes) {
461
+ throw new Error("Invalid screenshot payload");
462
+ }
463
+ const filePath = path3.join(this.screenshotsDir, `${taskId}.png`);
464
+ const buffer = Buffer.from(matches.groups.bytes, "base64");
465
+ await fs3.writeFile(filePath, buffer);
466
+ return this.getRelativePath(filePath);
467
+ }
468
+ async appendLog(logPath, event) {
469
+ const validated = RuntimeLogEventSchema.parse(event);
470
+ await fs3.appendFile(logPath, `${JSON.stringify(validated)}
471
+ `, "utf8");
472
+ }
473
+ async prune(options) {
474
+ const logsOlderThanDays = options?.logsOlderThanDays ?? 14;
475
+ const orphanSessionAgeHours = options?.orphanSessionAgeHours ?? 24;
476
+ const logFiles = await fs3.readdir(this.logsDir).catch(() => []);
477
+ const cutoffLogs = Date.now() - logsOlderThanDays * 24 * 60 * 60 * 1e3;
478
+ let removedLogs = 0;
479
+ for (const file of logFiles) {
480
+ const fullPath = path3.join(this.logsDir, file);
481
+ const stats = await fs3.stat(fullPath).catch(() => void 0);
482
+ if (!stats) {
483
+ continue;
484
+ }
485
+ if (stats.mtimeMs < cutoffLogs) {
486
+ await fs3.unlink(fullPath);
487
+ removedLogs += 1;
488
+ }
489
+ }
490
+ const tasks = await this.listTasks();
491
+ const taskIds = new Set(tasks.map((task) => task.taskId));
492
+ const sessions = await this.listSessions();
493
+ const cutoffSessions = Date.now() - orphanSessionAgeHours * 60 * 60 * 1e3;
494
+ let removedSessions = 0;
495
+ for (const session of sessions) {
496
+ const updatedAtMs = Date.parse(session.updatedAt);
497
+ const isOrphan = !taskIds.has(session.taskId);
498
+ if (isOrphan && updatedAtMs < cutoffSessions) {
499
+ await fs3.unlink(this.getSessionPath(session.sessionId));
500
+ removedSessions += 1;
501
+ }
502
+ }
503
+ return {
504
+ removedLogs,
505
+ removedSessions
506
+ };
507
+ }
508
+ };
509
+
510
+ // src/runtime/ids.ts
511
+ import crypto2 from "crypto";
512
+ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
513
+ var generateSessionId = () => crypto2.randomUUID();
514
+ var generateTaskId = () => {
515
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
516
+ const suffix = crypto2.randomBytes(3).toString("hex");
517
+ return `${date}-${suffix}`;
518
+ };
519
+
520
+ // src/runtime/task-runner.ts
521
+ var toMillis = (timestamp) => {
522
+ const value = Date.parse(timestamp);
523
+ return Number.isNaN(value) ? Date.now() : value;
524
+ };
525
+ var forceMonotonicTimestamp = (nextTs, lastTs) => {
526
+ const parsed = toMillis(nextTs);
527
+ if (parsed > lastTs) {
528
+ return { timestamp: new Date(parsed).toISOString(), millis: parsed };
529
+ }
530
+ const shifted = lastTs + 1;
531
+ return { timestamp: new Date(shifted).toISOString(), millis: shifted };
532
+ };
533
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
534
+ "completed",
535
+ "error",
536
+ "cancelled",
537
+ "timeout"
538
+ ]);
539
+ var isTerminalStatus = (status) => TERMINAL_STATUSES.has(status);
540
+ var TaskRunner = class {
541
+ cwd;
542
+ store;
543
+ logger;
544
+ eventBus;
545
+ getProviderAdapter;
546
+ defaultTimeoutMs;
547
+ dryRunTimeoutMs;
548
+ inFlight = /* @__PURE__ */ new Map();
549
+ constructor(options) {
550
+ this.cwd = options.cwd;
551
+ this.store = options.store;
552
+ this.logger = options.logger;
553
+ this.eventBus = options.eventBus;
554
+ this.getProviderAdapter = options.getProviderAdapter;
555
+ this.defaultTimeoutMs = options.defaultTimeoutMs ?? 10 * 60 * 1e3;
556
+ this.dryRunTimeoutMs = options.dryRunTimeoutMs ?? 2 * 60 * 1e3;
557
+ }
558
+ key(taskId, sessionId) {
559
+ return `${taskId}:${sessionId}`;
560
+ }
561
+ async cancelTask(taskId, sessionId) {
562
+ const key = this.key(taskId, sessionId);
563
+ const entry = this.inFlight.get(key);
564
+ if (!entry) {
565
+ return;
566
+ }
567
+ await entry.adapter.cancelTask(taskId, sessionId);
568
+ this.logger.info("Cancel request forwarded to provider", {
569
+ component: "runner",
570
+ taskId,
571
+ sessionId,
572
+ event: "task.cancel"
573
+ });
574
+ }
575
+ async runTask(input) {
576
+ const { taskId, sessionId, provider, model, dryRun, debug } = input;
577
+ const task = await this.store.getTask(taskId);
578
+ if (!task) {
579
+ throw new Error(`Task ${taskId} does not exist`);
580
+ }
581
+ const queuedTimestamp = nowIso();
582
+ await this.store.updateTask(taskId, (current) => ({
583
+ ...current,
584
+ status: "queued",
585
+ provider,
586
+ model,
587
+ latestSessionId: sessionId,
588
+ sessions: Array.from(/* @__PURE__ */ new Set([...current.sessions, sessionId])),
589
+ updatedAt: queuedTimestamp
590
+ }));
591
+ await this.store.createSession({
592
+ sessionId,
593
+ taskId,
594
+ provider,
595
+ model,
596
+ status: "queued",
597
+ dryRun,
598
+ startedAt: queuedTimestamp,
599
+ updatedAt: queuedTimestamp,
600
+ events: [
601
+ {
602
+ status: "queued",
603
+ message: "Task queued",
604
+ timestamp: queuedTimestamp
605
+ }
606
+ ],
607
+ changedFiles: []
608
+ });
609
+ const adapter = this.getProviderAdapter(provider);
610
+ if (!adapter) {
611
+ const unavailable = {
612
+ taskId,
613
+ sessionId,
614
+ status: "error",
615
+ summary: `Provider ${provider} is not available in this runtime`,
616
+ changedFiles: [],
617
+ errorCode: ProviderErrorCodes.ProviderUnavailable,
618
+ errorMessage: `Provider ${provider} was not registered`
619
+ };
620
+ await this.persistTerminalState(
621
+ task,
622
+ unavailable,
623
+ provider,
624
+ model,
625
+ dryRun
626
+ );
627
+ return unavailable;
628
+ }
629
+ const queuedEvent = {
630
+ type: "progress",
631
+ taskId,
632
+ sessionId,
633
+ status: "queued",
634
+ message: "Task queued",
635
+ timestamp: queuedTimestamp
636
+ };
637
+ this.eventBus.publish(taskId, sessionId, queuedEvent);
638
+ const key = this.key(taskId, sessionId);
639
+ this.inFlight.set(key, {
640
+ adapter,
641
+ startedAt: Date.now()
642
+ });
643
+ let lastProgressTime = toMillis(queuedTimestamp);
644
+ const timeoutMs = dryRun ? this.dryRunTimeoutMs : this.defaultTimeoutMs;
645
+ let terminalCommitted = false;
646
+ const handleProgress = async (event) => {
647
+ if (terminalCommitted) {
648
+ return;
649
+ }
650
+ const monotonic = forceMonotonicTimestamp(
651
+ event.timestamp,
652
+ lastProgressTime
653
+ );
654
+ lastProgressTime = monotonic.millis;
655
+ const normalizedEvent = {
656
+ ...event,
657
+ timestamp: monotonic.timestamp
658
+ };
659
+ await this.store.updateSession(sessionId, (session) => {
660
+ if (isTerminalStatus(session.status)) {
661
+ return session;
662
+ }
663
+ return {
664
+ ...session,
665
+ status: normalizedEvent.status,
666
+ updatedAt: normalizedEvent.timestamp,
667
+ events: [
668
+ ...session.events,
669
+ {
670
+ status: normalizedEvent.status,
671
+ message: normalizedEvent.message,
672
+ percent: normalizedEvent.percent,
673
+ timestamp: normalizedEvent.timestamp
674
+ }
675
+ ]
676
+ };
677
+ });
678
+ if (terminalCommitted) {
679
+ return;
680
+ }
681
+ if (normalizedEvent.status === "running" || normalizedEvent.status === "queued") {
682
+ await this.store.updateTask(taskId, (current) => {
683
+ if (isTerminalStatus(current.status)) {
684
+ return current;
685
+ }
686
+ return {
687
+ ...current,
688
+ status: normalizedEvent.status,
689
+ updatedAt: normalizedEvent.timestamp
690
+ };
691
+ });
692
+ }
693
+ if (terminalCommitted) {
694
+ return;
695
+ }
696
+ this.eventBus.publish(taskId, sessionId, {
697
+ type: "progress",
698
+ taskId,
699
+ sessionId,
700
+ status: normalizedEvent.status,
701
+ message: normalizedEvent.message,
702
+ percent: normalizedEvent.percent,
703
+ timestamp: normalizedEvent.timestamp
704
+ });
705
+ this.logger.debug(normalizedEvent.message, {
706
+ component: "runner",
707
+ taskId,
708
+ sessionId,
709
+ event: "task.progress",
710
+ meta: {
711
+ status: normalizedEvent.status,
712
+ percent: normalizedEvent.percent
713
+ }
714
+ });
715
+ };
716
+ try {
717
+ const providerTaskPromise = adapter.submitTask(
718
+ {
719
+ taskId,
720
+ sessionId,
721
+ task,
722
+ prompt: this.buildPrompt(task),
723
+ model,
724
+ dryRun,
725
+ debug,
726
+ cwd: this.cwd,
727
+ timeoutMs
728
+ },
729
+ (event) => {
730
+ void handleProgress(event);
731
+ }
732
+ );
733
+ let timeoutHandle;
734
+ const timeoutPromise = new Promise((resolve) => {
735
+ timeoutHandle = setTimeout(() => {
736
+ resolve({
737
+ taskId,
738
+ sessionId,
739
+ status: "timeout",
740
+ summary: `Task timed out after ${timeoutMs}ms`,
741
+ changedFiles: [],
742
+ errorCode: ProviderErrorCodes.ProviderTimeout,
743
+ errorMessage: `Provider timed out after ${timeoutMs}ms`
744
+ });
745
+ }, timeoutMs);
746
+ });
747
+ const result = await Promise.race([providerTaskPromise, timeoutPromise]);
748
+ const normalizedResult = {
749
+ ...result,
750
+ taskId,
751
+ sessionId
752
+ };
753
+ terminalCommitted = true;
754
+ if (timeoutHandle) {
755
+ clearTimeout(timeoutHandle);
756
+ }
757
+ await this.persistTerminalState(
758
+ task,
759
+ normalizedResult,
760
+ provider,
761
+ model,
762
+ dryRun
763
+ );
764
+ this.inFlight.delete(key);
765
+ return normalizedResult;
766
+ } catch (error) {
767
+ const message = error instanceof Error ? error.message : "Unknown provider error";
768
+ const failedResult = {
769
+ taskId,
770
+ sessionId,
771
+ status: "error",
772
+ summary: "Provider execution failed",
773
+ changedFiles: [],
774
+ errorCode: ProviderErrorCodes.ProcessFailed,
775
+ errorMessage: message
776
+ };
777
+ terminalCommitted = true;
778
+ await this.persistTerminalState(
779
+ task,
780
+ failedResult,
781
+ provider,
782
+ model,
783
+ dryRun
784
+ );
785
+ this.inFlight.delete(key);
786
+ return failedResult;
787
+ }
788
+ }
789
+ buildPrompt(task) {
790
+ const selectedAttributes = JSON.stringify(
791
+ task.uiChangePacket.element.attributes
792
+ );
793
+ return [
794
+ "You are implementing a UI change request from Pinpatch.",
795
+ "Scope guardrails (must follow):",
796
+ "- Implement only the requested UI change.",
797
+ "- Treat the selected element as the primary edit target by default.",
798
+ "- Do not move the change to ancestors, siblings, or page/global wrappers unless the request explicitly asks for a page-level or app-wide change or the selected element cannot satisfy the request without broader changes.",
799
+ "- If the request is ambiguous, prefer changing the selected element directly.",
800
+ "- Do not edit, reformat, or reorganize unrelated files.",
801
+ "- Never revert, overwrite, or clean up unrelated repo changes (other agents may be editing in parallel).",
802
+ "- If unrelated files are modified or dirty, leave them untouched.",
803
+ "- If you cannot complete the request without touching unrelated areas, stop and report the exact blocker.",
804
+ `User request: ${task.pin.body}`,
805
+ `Page URL: ${task.url}`,
806
+ `Element: <${task.uiChangePacket.element.tag}> text="${task.uiChangePacket.element.text ?? ""}"`,
807
+ `Element attributes: ${selectedAttributes}`,
808
+ `Bounding box: ${JSON.stringify(task.uiChangePacket.element.boundingBox)}`,
809
+ `Nearby text: ${task.uiChangePacket.nearbyText.join(" | ")}`,
810
+ `DOM snippet: ${task.uiChangePacket.domSnippet}`,
811
+ `Computed style summary: ${JSON.stringify(task.uiChangePacket.computedStyleSummary)}`,
812
+ `Screenshot path: ${task.screenshotPath}`,
813
+ "Apply the change in local files.",
814
+ "Output format (must follow):",
815
+ "- For each modified file, print a line exactly as: CHANGED: <path>",
816
+ "- Final line must be exactly one sentence summarizing the changes made."
817
+ ].join("\n");
818
+ }
819
+ async persistTerminalState(task, result, provider, model, dryRun) {
820
+ const finishedAt = nowIso();
821
+ await this.store.updateSession(result.sessionId, (session) => ({
822
+ ...session,
823
+ provider,
824
+ model,
825
+ dryRun,
826
+ status: result.status,
827
+ summary: result.summary,
828
+ changedFiles: result.changedFiles,
829
+ errorCode: result.errorCode,
830
+ errorMessage: result.errorMessage,
831
+ endedAt: finishedAt,
832
+ updatedAt: finishedAt,
833
+ events: [
834
+ ...session.events,
835
+ {
836
+ status: result.status,
837
+ message: result.summary,
838
+ timestamp: finishedAt
839
+ }
840
+ ]
841
+ }));
842
+ await this.store.updateTask(task.taskId, (current) => ({
843
+ ...current,
844
+ status: result.status,
845
+ updatedAt: finishedAt,
846
+ provider,
847
+ model,
848
+ summary: result.summary,
849
+ changedFiles: result.changedFiles,
850
+ errorCode: result.errorCode,
851
+ errorMessage: result.errorMessage
852
+ }));
853
+ this.eventBus.publish(task.taskId, result.sessionId, {
854
+ type: "terminal",
855
+ taskId: task.taskId,
856
+ sessionId: result.sessionId,
857
+ status: result.status,
858
+ summary: result.summary,
859
+ changedFiles: result.changedFiles,
860
+ errorCode: result.errorCode,
861
+ errorMessage: result.errorMessage,
862
+ timestamp: finishedAt
863
+ });
864
+ this.logger.info(result.summary, {
865
+ component: "runner",
866
+ taskId: task.taskId,
867
+ sessionId: result.sessionId,
868
+ event: "task.terminal",
869
+ meta: {
870
+ status: result.status,
871
+ changedFiles: result.changedFiles,
872
+ errorCode: result.errorCode
873
+ }
874
+ });
875
+ }
876
+ };
877
+
878
+ // src/bridge/event-bus.ts
879
+ import { EventEmitter } from "events";
880
+ var keyFor = (taskId, sessionId) => `${taskId}:${sessionId}`;
881
+ var TaskEventBus = class {
882
+ emitter = new EventEmitter();
883
+ subscribe(taskId, sessionId, listener) {
884
+ const key = keyFor(taskId, sessionId);
885
+ this.emitter.on(key, listener);
886
+ return () => {
887
+ this.emitter.off(key, listener);
888
+ };
889
+ }
890
+ publish(taskId, sessionId, event) {
891
+ this.emitter.emit(keyFor(taskId, sessionId), event);
892
+ }
893
+ };
894
+
895
+ // src/bridge/server.ts
896
+ import { createServer } from "http";
897
+ import path4 from "path";
898
+ import { promises as fs4 } from "fs";
899
+ import express from "express";
900
+ import cors from "cors";
901
+ var sanitizeTaskId = (candidate) => candidate.replace(/[^a-zA-Z0-9-_]/g, "-").slice(0, 64);
902
+ var serializeSse = (eventName, payload) => {
903
+ return `event: ${eventName}
904
+ data: ${JSON.stringify(payload)}
905
+
906
+ `;
907
+ };
908
+ var fallbackOverlayScript = `
909
+ (function(){
910
+ if (window.__PINPATCH_OVERLAY_FALLBACK__) return;
911
+ window.__PINPATCH_OVERLAY_FALLBACK__ = true;
912
+ console.warn('[pinpatch] overlay bundle is missing. Build apps/overlay first.');
913
+ })();
914
+ `;
915
+ var resolveAvailableTaskId = async (store, initialTaskId) => {
916
+ if (initialTaskId) {
917
+ const candidate = sanitizeTaskId(initialTaskId);
918
+ const existing = await store.getTask(candidate);
919
+ if (!existing) {
920
+ return candidate;
921
+ }
922
+ }
923
+ let tries = 0;
924
+ while (tries < 10) {
925
+ const candidate = generateTaskId();
926
+ const existing = await store.getTask(candidate);
927
+ if (!existing) {
928
+ return candidate;
929
+ }
930
+ tries += 1;
931
+ }
932
+ throw new Error("Failed to allocate a unique task id");
933
+ };
934
+ var createBridgeServer = (options) => {
935
+ const app = express();
936
+ const eventBus = new TaskEventBus();
937
+ const taskRunner = new TaskRunner({
938
+ cwd: options.cwd,
939
+ store: options.store,
940
+ logger: options.logger,
941
+ eventBus,
942
+ getProviderAdapter: options.getProviderAdapter
943
+ });
944
+ app.use(cors());
945
+ app.use(express.json({ limit: "25mb" }));
946
+ app.get("/health", (_req, res) => {
947
+ res.status(200).json({ ok: true });
948
+ });
949
+ app.get("/overlay.js", async (_req, res) => {
950
+ const requestedPath = options.overlayScriptPath;
951
+ if (requestedPath) {
952
+ try {
953
+ const script = await fs4.readFile(path4.resolve(requestedPath), "utf8");
954
+ res.setHeader("content-type", "application/javascript; charset=utf-8");
955
+ res.status(200).send(script);
956
+ return;
957
+ } catch {
958
+ options.logger.warn(
959
+ "Overlay bundle not found, serving fallback overlay script",
960
+ {
961
+ component: "bridge",
962
+ event: "overlay.fallback",
963
+ meta: {
964
+ overlayScriptPath: requestedPath
965
+ }
966
+ }
967
+ );
968
+ }
969
+ }
970
+ res.setHeader("content-type", "application/javascript; charset=utf-8");
971
+ res.status(200).send(fallbackOverlayScript);
972
+ });
973
+ app.post("/api/tasks", async (req, res) => {
974
+ const parsed = CreateTaskRequestSchema.safeParse(req.body);
975
+ if (!parsed.success) {
976
+ res.status(400).json({
977
+ error: "Invalid request",
978
+ details: parsed.error.flatten()
979
+ });
980
+ return;
981
+ }
982
+ const payload = parsed.data;
983
+ const taskId = await resolveAvailableTaskId(
984
+ options.store,
985
+ payload.clientTaskId
986
+ );
987
+ let screenshotPath = payload.screenshotPath;
988
+ if (payload.screenshotDataUrl) {
989
+ screenshotPath = await options.store.writeScreenshot(
990
+ taskId,
991
+ payload.screenshotDataUrl
992
+ );
993
+ }
994
+ const createdAt = nowIso();
995
+ const taskRecord = {
996
+ taskId,
997
+ createdAt,
998
+ updatedAt: createdAt,
999
+ status: "created",
1000
+ url: payload.url,
1001
+ viewport: payload.viewport,
1002
+ pin: payload.pin,
1003
+ uiChangePacket: {
1004
+ ...payload.uiChangePacket,
1005
+ screenshotPath,
1006
+ userRequest: payload.pin.body
1007
+ },
1008
+ screenshotPath,
1009
+ sessions: [payload.sessionId],
1010
+ latestSessionId: payload.sessionId,
1011
+ changedFiles: []
1012
+ };
1013
+ await options.store.createTask(taskRecord);
1014
+ options.logger.info("Task created", {
1015
+ component: "bridge",
1016
+ taskId,
1017
+ sessionId: payload.sessionId,
1018
+ event: "task.created",
1019
+ meta: {
1020
+ screenshotPath
1021
+ }
1022
+ });
1023
+ res.status(201).json({
1024
+ taskId,
1025
+ sessionId: payload.sessionId,
1026
+ status: "created",
1027
+ taskPath: `.pinpatch/tasks/${taskId}.json`,
1028
+ eventsUrl: `/api/tasks/${taskId}/events?sessionId=${payload.sessionId}`
1029
+ });
1030
+ });
1031
+ app.post("/api/tasks/:taskId/submit", async (req, res) => {
1032
+ const taskId = String(req.params.taskId ?? "");
1033
+ if (!taskId) {
1034
+ res.status(400).json({ error: "taskId is required" });
1035
+ return;
1036
+ }
1037
+ const task = await options.store.getTask(taskId);
1038
+ if (!task) {
1039
+ res.status(404).json({ error: "Task not found" });
1040
+ return;
1041
+ }
1042
+ const parsed = SubmitTaskRequestSchema.safeParse(req.body);
1043
+ if (!parsed.success) {
1044
+ res.status(400).json({
1045
+ error: "Invalid request",
1046
+ details: parsed.error.flatten()
1047
+ });
1048
+ return;
1049
+ }
1050
+ const payload = parsed.data;
1051
+ if (payload.followUpBody) {
1052
+ const updatedAt = nowIso();
1053
+ const followUpBody = payload.followUpBody;
1054
+ await options.store.updateTask(taskId, (current) => ({
1055
+ ...current,
1056
+ updatedAt,
1057
+ pin: {
1058
+ ...current.pin,
1059
+ body: followUpBody
1060
+ },
1061
+ uiChangePacket: {
1062
+ ...current.uiChangePacket,
1063
+ userRequest: followUpBody
1064
+ }
1065
+ }));
1066
+ }
1067
+ void taskRunner.runTask({
1068
+ taskId,
1069
+ sessionId: payload.sessionId,
1070
+ provider: payload.provider,
1071
+ model: payload.model,
1072
+ dryRun: payload.dryRun,
1073
+ debug: payload.debug
1074
+ }).catch((error) => {
1075
+ options.logger.error("Provider task execution failed", {
1076
+ component: "bridge",
1077
+ taskId,
1078
+ sessionId: payload.sessionId,
1079
+ event: "task.run.error",
1080
+ meta: {
1081
+ error: error instanceof Error ? error.message : String(error)
1082
+ }
1083
+ });
1084
+ });
1085
+ const acceptedAt = nowIso();
1086
+ res.status(202).json({
1087
+ taskId,
1088
+ sessionId: payload.sessionId,
1089
+ status: "queued",
1090
+ acceptedAt,
1091
+ eventsUrl: `/api/tasks/${taskId}/events?sessionId=${payload.sessionId}`
1092
+ });
1093
+ });
1094
+ app.post("/api/tasks/:taskId/cancel", async (req, res) => {
1095
+ const taskId = String(req.params.taskId ?? "");
1096
+ const sessionId = String(req.body?.sessionId ?? "");
1097
+ if (!taskId || !sessionId) {
1098
+ res.status(400).json({ error: "taskId and sessionId are required" });
1099
+ return;
1100
+ }
1101
+ await taskRunner.cancelTask(taskId, sessionId);
1102
+ res.status(202).json({ taskId, sessionId, status: "cancelled" });
1103
+ });
1104
+ app.get("/api/tasks/:taskId/events", async (req, res) => {
1105
+ const taskId = String(req.params.taskId ?? "");
1106
+ const sessionId = String(req.query.sessionId ?? "");
1107
+ if (!taskId || !sessionId) {
1108
+ res.status(400).json({ error: "taskId and sessionId query params are required" });
1109
+ return;
1110
+ }
1111
+ res.setHeader("Content-Type", "text/event-stream");
1112
+ res.setHeader("Cache-Control", "no-cache");
1113
+ res.setHeader("Connection", "keep-alive");
1114
+ res.flushHeaders();
1115
+ const push = (event) => {
1116
+ const type = event.type === "terminal" ? "terminal" : event.type === "heartbeat" ? "heartbeat" : "progress";
1117
+ res.write(serializeSse(type, event));
1118
+ };
1119
+ push({
1120
+ type: "heartbeat",
1121
+ timestamp: nowIso()
1122
+ });
1123
+ const unsubscribe = eventBus.subscribe(taskId, sessionId, push);
1124
+ const heartbeat = setInterval(() => {
1125
+ push({
1126
+ type: "heartbeat",
1127
+ timestamp: nowIso()
1128
+ });
1129
+ }, 15e3);
1130
+ req.on("close", () => {
1131
+ clearInterval(heartbeat);
1132
+ unsubscribe();
1133
+ res.end();
1134
+ });
1135
+ });
1136
+ const server = createServer(app);
1137
+ return {
1138
+ app,
1139
+ server,
1140
+ eventBus,
1141
+ taskRunner,
1142
+ async start() {
1143
+ await new Promise((resolve, reject) => {
1144
+ server.once("error", reject);
1145
+ server.listen(options.port, () => {
1146
+ server.off("error", reject);
1147
+ resolve();
1148
+ });
1149
+ });
1150
+ options.logger.info(
1151
+ `Bridge listening on http://localhost:${options.port}`,
1152
+ {
1153
+ component: "bridge",
1154
+ event: "bridge.started"
1155
+ }
1156
+ );
1157
+ },
1158
+ async stop() {
1159
+ await new Promise((resolve) => {
1160
+ server.close(() => resolve());
1161
+ });
1162
+ options.logger.info("Bridge stopped", {
1163
+ component: "bridge",
1164
+ event: "bridge.stopped"
1165
+ });
1166
+ }
1167
+ };
1168
+ };
1169
+
1170
+ // src/logging/logger.ts
1171
+ import os from "os";
1172
+ import path5 from "path";
1173
+ import { existsSync, statSync } from "fs";
1174
+ var redactValue = (value) => {
1175
+ if (typeof value === "string") {
1176
+ const home = os.homedir();
1177
+ return value.replaceAll(home, "~").replace(/(Bearer\s+)[A-Za-z0-9._-]+/gi, "$1[REDACTED]").replace(/(token=)[^\s&]+/gi, "$1[REDACTED]");
1178
+ }
1179
+ if (Array.isArray(value)) {
1180
+ return value.map((entry) => redactValue(entry));
1181
+ }
1182
+ if (value && typeof value === "object") {
1183
+ const output = {};
1184
+ for (const [key, fieldValue] of Object.entries(value)) {
1185
+ if (["token", "authorization", "auth", "apiKey", "apikey"].includes(key.toLowerCase())) {
1186
+ output[key] = "[REDACTED]";
1187
+ } else {
1188
+ output[key] = redactValue(fieldValue);
1189
+ }
1190
+ }
1191
+ return output;
1192
+ }
1193
+ return value;
1194
+ };
1195
+ var getDateKey = (dateIso) => dateIso.slice(0, 10);
1196
+ var resolveLogPath = (logsDir, timestamp, maxFileSizeBytes) => {
1197
+ const dateKey = getDateKey(timestamp);
1198
+ let index = 0;
1199
+ while (true) {
1200
+ const candidateName = index === 0 ? `${dateKey}.jsonl` : `${dateKey}-${index}.jsonl`;
1201
+ const candidatePath = path5.join(logsDir, candidateName);
1202
+ if (!existsSync(candidatePath)) {
1203
+ return candidatePath;
1204
+ }
1205
+ const size = statSync(candidatePath).size;
1206
+ if (size < maxFileSizeBytes) {
1207
+ return candidatePath;
1208
+ }
1209
+ index += 1;
1210
+ }
1211
+ };
1212
+ var createLogger = (options) => {
1213
+ const { store, debugEnabled, component, maxFileSizeBytes = 2 * 1024 * 1024 } = options;
1214
+ const emit = (level, message, details) => {
1215
+ if (level === "debug" && !debugEnabled) {
1216
+ return;
1217
+ }
1218
+ const timestamp = nowIso();
1219
+ const payload = RuntimeLogEventSchema.parse({
1220
+ timestamp,
1221
+ level,
1222
+ component: details?.component ?? component,
1223
+ taskId: details?.taskId,
1224
+ sessionId: details?.sessionId,
1225
+ event: details?.event ?? "log",
1226
+ message,
1227
+ meta: redactValue(details?.meta ?? {})
1228
+ });
1229
+ const line = `[${payload.timestamp}] ${payload.level.toUpperCase()} ${payload.component}: ${payload.message}`;
1230
+ if (level === "error") {
1231
+ console.error(line);
1232
+ } else if (level === "warn") {
1233
+ console.warn(line);
1234
+ } else {
1235
+ console.log(line);
1236
+ }
1237
+ const logPath = resolveLogPath(store.logsDir, timestamp, maxFileSizeBytes);
1238
+ void store.appendLog(logPath, payload);
1239
+ };
1240
+ return {
1241
+ debug: (message, details) => emit("debug", message, details),
1242
+ info: (message, details) => emit("info", message, details),
1243
+ warn: (message, details) => emit("warn", message, details),
1244
+ error: (message, details) => emit("error", message, details)
1245
+ };
1246
+ };
1247
+ export {
1248
+ ArtifactStore,
1249
+ BoundingBoxSchema,
1250
+ CreateTaskRequestSchema,
1251
+ CreateTaskResponseSchema,
1252
+ DEFAULT_CONFIG,
1253
+ ElementDescriptorSchema,
1254
+ PinStateSchema,
1255
+ PinpatchConfigSchema,
1256
+ ProviderErrorCodes,
1257
+ ProviderNameSchema,
1258
+ ProviderProgressSchema,
1259
+ ProviderProgressStatusSchema,
1260
+ ProviderResultSchema,
1261
+ ProviderTerminalStatusSchema,
1262
+ RuntimeLogEventSchema,
1263
+ RuntimeLogLevelSchema,
1264
+ SessionEventSchema,
1265
+ SessionRecordSchema,
1266
+ SseEventSchema,
1267
+ SseHeartbeatEventSchema,
1268
+ SseProgressEventSchema,
1269
+ SseTerminalEventSchema,
1270
+ SubmitTaskRequestSchema,
1271
+ SubmitTaskResponseSchema,
1272
+ TaskEventBus,
1273
+ TaskPinSchema,
1274
+ TaskRecordSchema,
1275
+ TaskRunner,
1276
+ TaskStatusSchema,
1277
+ UiChangePacketSchema,
1278
+ ViewportSchema,
1279
+ createBridgeServer,
1280
+ createLogger,
1281
+ ensureConfigFile,
1282
+ generateSessionId,
1283
+ generateTaskId,
1284
+ getConfigPath,
1285
+ nowIso,
1286
+ readConfigFile,
1287
+ resolveConfig
1288
+ };
1289
+ //# sourceMappingURL=index.js.map