@jay-framework/aiditor 0.16.4 → 0.16.5

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.
@@ -8,7 +8,6 @@ inputSchema:
8
8
  renderedUrl?: string
9
9
  visualTask?: string
10
10
  videoFramesOnly?: string
11
- screenshot_full?: file
12
11
  screenshot_markers_only?: file
13
12
  screenshot_clean?: file
14
13
  video?: file
@@ -6,7 +6,6 @@ export interface SubmitTaskActionInput {
6
6
  renderedUrl?: string;
7
7
  visualTask?: string;
8
8
  videoFramesOnly?: string;
9
- screenshot_full?: JayFile;
10
9
  screenshot_markers_only?: JayFile;
11
10
  screenshot_clean?: JayFile;
12
11
  video?: JayFile;
@@ -19,10 +19,10 @@ function countAttachmentFiles(m) {
19
19
  function validateMultipartPartBudget(options) {
20
20
  const { video, distinctMoments, attachmentFileCount } = options;
21
21
  const n = distinctMoments;
22
- const p = (video ? 1 : 0) + 3 * n + attachmentFileCount;
22
+ const p = (video ? 1 : 0) + 2 * n + attachmentFileCount;
23
23
  if (p > CHANNEL_MAX_FILE_PARTS) {
24
24
  const videoPhrase = video ? "1 video + " : "";
25
- return `Too many files for one task (${p} parts). Max is ${CHANNEL_MAX_FILE_PARTS} (${videoPhrase}3 × ${n} moment(s) + ${attachmentFileCount} attachment(s)). Reduce distinct moments or attachments.`;
25
+ return `Too many files for one task (${p} parts). Max is ${CHANNEL_MAX_FILE_PARTS} (${videoPhrase}2 × ${n} moment(s) + ${attachmentFileCount} attachment(s)). Reduce distinct moments or attachments.`;
26
26
  }
27
27
  return null;
28
28
  }
@@ -38,7 +38,7 @@ function submitAndStreamTask(notes, visual, callbacks) {
38
38
  const includeVideo = Boolean(v.video);
39
39
  const err = validateMultipartPartBudget({
40
40
  video: includeVideo,
41
- distinctMoments: v.frameTriples.length,
41
+ distinctMoments: v.frameDuals.length,
42
42
  attachmentFileCount: attCount
43
43
  });
44
44
  if (err) {
@@ -55,13 +55,8 @@ function submitAndStreamTask(notes, visual, callbacks) {
55
55
  }
56
56
  if (!includeVideo) input.videoFramesOnly = "1";
57
57
  const extraFiles = {};
58
- for (let i = 0; i < v.frameTriples.length; i++) {
59
- const t = v.frameTriples[i];
60
- extraFiles[`frame_${i}_full`] = new File(
61
- [t.full],
62
- `frame-${i}-full.png`,
63
- { type: "image/png" }
64
- );
58
+ for (let i = 0; i < v.frameDuals.length; i++) {
59
+ const t = v.frameDuals[i];
65
60
  extraFiles[`frame_${i}_markers_only`] = new File(
66
61
  [t.markersOnly],
67
62
  `frame-${i}-markers.png`,
@@ -81,20 +76,17 @@ function submitAndStreamTask(notes, visual, callbacks) {
81
76
  }
82
77
  }
83
78
  input.extraFiles = extraFiles;
84
- } else if (visual?.triple) {
79
+ } else if (visual?.dual) {
85
80
  const v = visual;
86
81
  input.visualTask = visualModeToForm[v.mode];
87
82
  input.pageRoute = v.pageRoute;
88
83
  input.renderedUrl = v.renderedUrl;
89
- input.screenshot_full = new File([v.triple.full], "full.png", {
90
- type: "image/png"
91
- });
92
84
  input.screenshot_markers_only = new File(
93
- [v.triple.markersOnly],
85
+ [v.dual.markersOnly],
94
86
  "markers-only.png",
95
87
  { type: "image/png" }
96
88
  );
97
- input.screenshot_clean = new File([v.triple.clean], "clean.png", {
89
+ input.screenshot_clean = new File([v.dual.clean], "clean.png", {
98
90
  type: "image/png"
99
91
  });
100
92
  const extraFiles = {};
@@ -8178,11 +8170,10 @@ function canvasToPng(canvas) {
8178
8170
  );
8179
8171
  });
8180
8172
  }
8181
- async function capturePreviewTriple(rootEl) {
8182
- const full = await capturePreviewForTask(rootEl, "full");
8183
- const markersOnly = await capturePreviewForTask(rootEl, "markersOnly");
8173
+ async function capturePreviewDual(rootEl) {
8184
8174
  const clean = await capturePreviewForTask(rootEl, "clean");
8185
- return { full, markersOnly, clean };
8175
+ const markersOnly = await capturePreviewForTask(rootEl, "markersOnly");
8176
+ return { clean, markersOnly };
8186
8177
  }
8187
8178
  const DEFAULT_SEEK_TIMEOUT_MS = 15e3;
8188
8179
  async function seekVideoToTime(video, timeSec, options) {
@@ -8292,15 +8283,14 @@ async function capturePreviewForVideoFrame(rootEl, videoEl, mode = "full") {
8292
8283
  if (mode === "full" && popoversEl) await drawLayer(popoversEl);
8293
8284
  return canvasToPng(canvas);
8294
8285
  }
8295
- async function captureVideoInstantTriple(rootEl, videoEl) {
8296
- const full = await capturePreviewForVideoFrame(rootEl, videoEl, "full");
8286
+ async function captureVideoInstantDual(rootEl, videoEl) {
8287
+ const clean = await capturePreviewForVideoFrame(rootEl, videoEl, "clean");
8297
8288
  const markersOnly = await capturePreviewForVideoFrame(
8298
8289
  rootEl,
8299
8290
  videoEl,
8300
8291
  "markersOnly"
8301
8292
  );
8302
- const clean = await capturePreviewForVideoFrame(rootEl, videoEl, "clean");
8303
- return { full, markersOnly, clean };
8293
+ return { clean, markersOnly };
8304
8294
  }
8305
8295
  function getRestrictionTargetCtor() {
8306
8296
  const g = globalThis;
@@ -9919,7 +9909,7 @@ function aiditorConstructor(_props, refs) {
9919
9909
  if (!gotRoot || !rootEl) {
9920
9910
  throw new Error("Preview container not ready.");
9921
9911
  }
9922
- const triple = await capturePreviewTriple(rootEl);
9912
+ const dual = await capturePreviewDual(rootEl);
9923
9913
  const attachmentsByAnnotationId = /* @__PURE__ */ new Map();
9924
9914
  for (const a2 of list) {
9925
9915
  if (a2.attachments.length > 0) {
@@ -9931,11 +9921,7 @@ function aiditorConstructor(_props, refs) {
9931
9921
  mode: "multi",
9932
9922
  pageRoute: route,
9933
9923
  renderedUrl: rendered,
9934
- triple: {
9935
- full: triple.full,
9936
- markersOnly: triple.markersOnly,
9937
- clean: triple.clean
9938
- },
9924
+ dual,
9939
9925
  attachmentsByAnnotationId: attachmentsByAnnotationId.size > 0 ? attachmentsByAnnotationId : void 0
9940
9926
  });
9941
9927
  } catch (err) {
@@ -10388,15 +10374,15 @@ function aiditorConstructor(_props, refs) {
10388
10374
  setIsRunning(true);
10389
10375
  setVideoSubmitError("");
10390
10376
  setVideoSubmitProgress("");
10391
- const frameTriples = [];
10377
+ const frameDuals = [];
10392
10378
  const nFrames = distinctTimes.length;
10393
10379
  let frameIdx = 0;
10394
10380
  for (const t of distinctTimes) {
10395
10381
  frameIdx += 1;
10396
10382
  setVideoSubmitProgress(`Capturing annotated frames (${frameIdx}/${nFrames})…`);
10397
10383
  await seekVideoToTime(videoEl, t);
10398
- const triple = await captureVideoInstantTriple(captureRoot, videoEl);
10399
- frameTriples.push({ timeSec: t, ...triple });
10384
+ const dual = await captureVideoInstantDual(captureRoot, videoEl);
10385
+ frameDuals.push({ timeSec: t, ...dual });
10400
10386
  }
10401
10387
  const attachmentsByPin = /* @__PURE__ */ new Map();
10402
10388
  for (const a2 of list) {
@@ -10417,7 +10403,7 @@ function aiditorConstructor(_props, refs) {
10417
10403
  pageRoute: route,
10418
10404
  renderedUrl: rendered,
10419
10405
  ...uploadFullRecording ? { video: blob, videoFileName: "recording.webm" } : {},
10420
- frameTriples,
10406
+ frameDuals,
10421
10407
  attachmentsByPin: attachmentsByPin.size > 0 ? attachmentsByPin : void 0
10422
10408
  });
10423
10409
  } catch (err) {
package/dist/index.d.ts CHANGED
@@ -412,7 +412,6 @@ interface SubmitTaskActionInput {
412
412
  renderedUrl?: string;
413
413
  visualTask?: string;
414
414
  videoFramesOnly?: string;
415
- screenshot_full?: JayFile;
416
415
  screenshot_markers_only?: JayFile;
417
416
  screenshot_clean?: JayFile;
418
417
  video?: JayFile;
@@ -429,7 +428,7 @@ interface SubmitTaskActionOutput {
429
428
  duration?: number;
430
429
  }
431
430
 
432
- declare const submitTaskAction: _jay_framework_fullstack_component.JayStreamAction<SubmitTaskActionInput, SubmitTaskActionOutput> & _jay_framework_fullstack_component.JayStreamActionDefinition<SubmitTaskActionInput, SubmitTaskActionOutput, [_jay_framework_dev_server.DevServerService]>;
431
+ declare const submitTaskAction: _jay_framework_fullstack_component.JayStreamAction<SubmitTaskActionInput, SubmitTaskActionOutput> & _jay_framework_fullstack_component.JayStreamActionDefinition<SubmitTaskActionInput, SubmitTaskActionOutput, []>;
433
432
 
434
433
  interface AiditorShellCarry {
435
434
  headline: string;
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { makeJayQuery, makeJayStream, makeJayStackComponent, phaseOutput, RenderPipeline } from "@jay-framework/fullstack-component";
2
2
  import { DEV_SERVER_SERVICE } from "@jay-framework/dev-server";
3
- import { readFile } from "fs/promises";
3
+ import fs, { readFile } from "fs/promises";
4
4
  import path, { extname } from "path";
5
5
  import { query } from "@anthropic-ai/claude-agent-sdk";
6
- import fs from "fs";
6
+ import fs$1 from "fs";
7
+ import crypto from "crypto";
7
8
  import { setActionCallerOptions } from "@jay-framework/stack-client-runtime";
8
9
  const getAiditorBootstrap = makeJayQuery(
9
10
  "aiditor.getAiditorBootstrap"
@@ -118,6 +119,112 @@ function* transformSDKMessage(message) {
118
119
  };
119
120
  }
120
121
  }
122
+ const inFlightRoutes = /* @__PURE__ */ new Set();
123
+ function tryBeginRouteQuery(routeKey) {
124
+ if (inFlightRoutes.has(routeKey)) {
125
+ return false;
126
+ }
127
+ inFlightRoutes.add(routeKey);
128
+ return true;
129
+ }
130
+ function endRouteQuery(routeKey) {
131
+ inFlightRoutes.delete(routeKey);
132
+ }
133
+ const ROUTE_SESSION_MAP_VERSION = 1;
134
+ function normalizePageRoute(raw) {
135
+ let s = (raw ?? "/").trim();
136
+ if (s === "") {
137
+ return "/";
138
+ }
139
+ if (!s.startsWith("/")) {
140
+ s = `/${s}`;
141
+ }
142
+ const q = s.indexOf("?");
143
+ if (q !== -1) {
144
+ s = s.slice(0, q);
145
+ }
146
+ while (s.length > 1 && s.endsWith("/")) {
147
+ s = s.slice(0, -1);
148
+ }
149
+ return s || "/";
150
+ }
151
+ function getRouteSessionsFilePath(projectDir) {
152
+ return path.join(projectDir, "build", "aditor", "route-sessions.json");
153
+ }
154
+ function emptyMap() {
155
+ return { version: ROUTE_SESSION_MAP_VERSION, routes: {} };
156
+ }
157
+ let mapMutexChain = Promise.resolve();
158
+ async function withRouteSessionMapLock(fn) {
159
+ const previous = mapMutexChain;
160
+ let release;
161
+ mapMutexChain = new Promise((resolve) => {
162
+ release = resolve;
163
+ });
164
+ await previous;
165
+ try {
166
+ return await fn();
167
+ } finally {
168
+ release();
169
+ }
170
+ }
171
+ async function readMap(projectDir) {
172
+ const filePath = getRouteSessionsFilePath(projectDir);
173
+ try {
174
+ const raw = await fs.readFile(filePath, "utf-8");
175
+ const parsed = JSON.parse(raw);
176
+ if (parsed.version !== ROUTE_SESSION_MAP_VERSION || typeof parsed.routes !== "object" || parsed.routes === null) {
177
+ return emptyMap();
178
+ }
179
+ const routes = {};
180
+ for (const [k, v] of Object.entries(parsed.routes)) {
181
+ if (typeof v === "string") {
182
+ routes[k] = v.toLowerCase();
183
+ }
184
+ }
185
+ return {
186
+ version: ROUTE_SESSION_MAP_VERSION,
187
+ routes
188
+ };
189
+ } catch (e) {
190
+ const code = e && typeof e === "object" && "code" in e ? e.code : void 0;
191
+ if (code === "ENOENT") {
192
+ return emptyMap();
193
+ }
194
+ throw e;
195
+ }
196
+ }
197
+ async function writeRouteSessionMapAtomic(projectDir, map) {
198
+ const filePath = getRouteSessionsFilePath(projectDir);
199
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
200
+ const tmp = `${filePath}.${process.pid}.${Date.now()}.tmp`;
201
+ const body = `${JSON.stringify(map, null, 2)}
202
+ `;
203
+ await fs.writeFile(tmp, body, "utf-8");
204
+ await fs.rename(tmp, filePath);
205
+ }
206
+ async function getOrCreateSessionIdForRoute(projectDir, routeKey) {
207
+ return withRouteSessionMapLock(async () => {
208
+ const map = await readMap(projectDir);
209
+ const existing = map.routes[routeKey];
210
+ if (existing) {
211
+ return { sessionId: existing, useResume: true };
212
+ }
213
+ const sessionId = crypto.randomUUID();
214
+ map.routes[routeKey] = sessionId;
215
+ await writeRouteSessionMapAtomic(projectDir, map);
216
+ return { sessionId, useResume: false };
217
+ });
218
+ }
219
+ async function replaceRouteSessionAfterStaleResume(projectDir, routeKey) {
220
+ return withRouteSessionMapLock(async () => {
221
+ const map = await readMap(projectDir);
222
+ const sessionId = crypto.randomUUID();
223
+ map.routes[routeKey] = sessionId;
224
+ await writeRouteSessionMapAtomic(projectDir, map);
225
+ return { sessionId };
226
+ });
227
+ }
121
228
  function buildNonVisualPrompt(config, notes, imagePath) {
122
229
  const lines = [
123
230
  `Project directory: ${config.projectDir}`,
@@ -185,21 +292,20 @@ function parseNotesField(notes) {
185
292
  }
186
293
  return empty();
187
294
  }
188
- function buildVisualPromptTriple(config, pageRoute, renderedUrl, triple, parsed, attachmentPathsByAnnotationId) {
295
+ function buildVisualPromptDual(config, pageRoute, renderedUrl, dual, parsed, attachmentPathsByAnnotationId) {
189
296
  const preamble = [
190
297
  `Project directory: ${config.projectDir}`,
191
298
  "",
192
299
  `Implement the requested changes on page route ${pageRoute}. The captures correspond to this rendered URL: ${renderedUrl}.`,
193
300
  "",
194
- "Multimodal reference images (use together):",
195
- `- Screenshot 1 — Full annotations (markers and on-image messages): ${triple.full}`,
196
- `- Screenshot 2 — Numbered markers only (use with Pin list; ignore overlay text from Screenshot 1 for saliency): ${triple.markersOnly}`,
197
- `- Screenshot 3 — Clean page (no markers; use to see underlying layout and align pin positions from Screenshot 2): ${triple.clean}`,
301
+ "Reference images (use together):",
302
+ `- Screenshot 1 — Clean page (no markers; shows the current page layout and content): ${dual.clean}`,
303
+ `- Screenshot 2 — Markers only (numbered annotation markers on the page; use to locate which UI element each annotation refers to): ${dual.markersOnly}`,
198
304
  "",
199
305
  "How to use these screenshots:",
200
- "- Use Screenshot 1 for what the user wrote on the page.",
201
- "- Use Screenshot 2 to decide which UI region each Pin N refers to.",
202
- "- Use Screenshot 3 to understand unmarked typography and structure when overlays obscure content.",
306
+ "- Use Screenshot 1 to understand the current page structure, typography, and layout.",
307
+ "- Use Screenshot 2 to identify which UI element each numbered annotation targets.",
308
+ "- The annotation instructions and attachments are listed below in text do NOT rely on screenshot text.",
203
309
  "",
204
310
  ANNOTATION_TARGETING_RULES,
205
311
  ""
@@ -217,7 +323,7 @@ function buildVisualPromptTriple(config, pageRoute, renderedUrl, triple, parsed,
217
323
  body.push(`Instruction: ${ann.instruction.trim()}`);
218
324
  if (paths.length > 0) {
219
325
  body.push(
220
- "Attachments (read these files; they belong to this pin only):"
326
+ "Attachments (read these files; they belong to this annotation only):"
221
327
  );
222
328
  for (const p of paths) {
223
329
  body.push(`- ${p}`);
@@ -272,13 +378,12 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
272
378
  for (const [i, a] of pinOrder(sv.annotations).entries()) {
273
379
  pinById.set(a.id, String(i + 1));
274
380
  }
275
- const body = ["Per-moment captures (triple screenshots):", ""];
381
+ const body = ["Per-moment captures:", ""];
276
382
  for (const fr of frames) {
277
383
  body.push(
278
384
  `Moment ${fr.frameIndex + 1} — t=${fr.timeSec.toFixed(2)}s`,
279
- `- Full (markers + on-image messages): ${fr.full}`,
385
+ `- Clean page (no markers): ${fr.clean}`,
280
386
  `- Markers only (numbered pins): ${fr.markersOnly}`,
281
- `- Clean (no markers): ${fr.clean}`,
282
387
  ""
283
388
  );
284
389
  const atT = sv.annotations.filter((a) => a.timeSec === fr.timeSec);
@@ -310,17 +415,17 @@ function buildVisualPromptVideo(config, pageRoute, renderedUrl, videoPath, frame
310
415
  return [...preamble, ...body].join("\n");
311
416
  }
312
417
  function persistFile(file, destDir, fileName) {
313
- fs.mkdirSync(destDir, { recursive: true });
418
+ fs$1.mkdirSync(destDir, { recursive: true });
314
419
  const dest = path.join(destDir, fileName ?? file.name);
315
- fs.copyFileSync(file.path, dest);
420
+ fs$1.copyFileSync(file.path, dest);
316
421
  return dest;
317
422
  }
318
- const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SERVER_SERVICE).withFiles({ maxFileSize: 2e7, maxFiles: 300 }).withHandler(async function* (input, devServer) {
423
+ const submitTaskAction = makeJayStream("aiditor.submitTask").withFiles({ maxFileSize: 2e7, maxFiles: 300 }).withHandler(async function* (input) {
319
424
  const projectDir = process.cwd();
320
- const buildFolder = devServer.buildFolder ?? path.join(projectDir, "build");
425
+ const buildFolder = path.join(projectDir, "build");
321
426
  const taskId = Math.random().toString(36).slice(2, 10);
322
427
  const taskDir = path.join(buildFolder, "aditor", taskId);
323
- fs.mkdirSync(taskDir, { recursive: true });
428
+ fs$1.mkdirSync(taskDir, { recursive: true });
324
429
  yield { type: "status", message: "Preparing task..." };
325
430
  const config = { projectDir };
326
431
  const parsed = parseNotesField(input.notes);
@@ -336,10 +441,9 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SE
336
441
  const extra = input.extraFiles ?? {};
337
442
  const frames = [];
338
443
  for (let i = 0; ; i++) {
339
- const full = extra[`frame_${i}_full`];
340
444
  const markers = extra[`frame_${i}_markers_only`];
341
445
  const clean = extra[`frame_${i}_clean`];
342
- if (!full) break;
446
+ if (!markers && !clean) break;
343
447
  const frameDir = path.join(taskDir, "frames", String(i));
344
448
  const timeSec = parsed.structuredVideo?.annotations.find(
345
449
  (a, idx) => idx === i || a.timeSec !== void 0
@@ -347,9 +451,8 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SE
347
451
  frames.push({
348
452
  timeSec,
349
453
  frameIndex: i,
350
- full: persistFile(full, frameDir, "full.png"),
351
- markersOnly: markers ? persistFile(markers, frameDir, "markers-only.png") : "",
352
- clean: clean ? persistFile(clean, frameDir, "clean.png") : ""
454
+ clean: clean ? persistFile(clean, frameDir, "clean.png") : "",
455
+ markersOnly: markers ? persistFile(markers, frameDir, "markers-only.png") : ""
353
456
  });
354
457
  }
355
458
  const attachmentPathsByPin = /* @__PURE__ */ new Map();
@@ -371,20 +474,19 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SE
371
474
  parsed,
372
475
  attachmentPathsByPin
373
476
  );
374
- } else if (isVisual && input.screenshot_full) {
477
+ } else if (isVisual && input.screenshot_markers_only) {
375
478
  const ssDir = path.join(taskDir, "screenshots");
376
- const triple = {
377
- full: persistFile(input.screenshot_full, ssDir, "full.png"),
378
- markersOnly: input.screenshot_markers_only ? persistFile(
479
+ const dual = {
480
+ clean: input.screenshot_clean ? persistFile(input.screenshot_clean, ssDir, "clean.png") : "",
481
+ markersOnly: persistFile(
379
482
  input.screenshot_markers_only,
380
483
  ssDir,
381
484
  "markers-only.png"
382
- ) : "",
383
- clean: input.screenshot_clean ? persistFile(input.screenshot_clean, ssDir, "clean.png") : ""
485
+ )
384
486
  };
385
- const extraTriple = input.extraFiles ?? {};
487
+ const extraFiles = input.extraFiles ?? {};
386
488
  const attachmentPathsByAnnotationId = /* @__PURE__ */ new Map();
387
- for (const [key, file] of Object.entries(extraTriple)) {
489
+ for (const [key, file] of Object.entries(extraFiles)) {
388
490
  if (!key.startsWith("attachment_")) continue;
389
491
  const pinId = key.split("_")[1];
390
492
  const attDir = path.join(taskDir, `annotation-${pinId}`);
@@ -393,11 +495,11 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SE
393
495
  existing.push(filePath);
394
496
  attachmentPathsByAnnotationId.set(pinId, existing);
395
497
  }
396
- promptContent = buildVisualPromptTriple(
498
+ promptContent = buildVisualPromptDual(
397
499
  config,
398
500
  input.pageRoute ?? "/",
399
501
  input.renderedUrl ?? "",
400
- triple,
502
+ dual,
401
503
  parsed,
402
504
  attachmentPathsByAnnotationId
403
505
  );
@@ -412,30 +514,81 @@ const submitTaskAction = makeJayStream("aiditor.submitTask").withServices(DEV_SE
412
514
  "designer",
413
515
  "INSTRUCTIONS.md"
414
516
  );
415
- if (fs.existsSync(agentKitPath)) {
416
- systemPrompt = fs.readFileSync(agentKitPath, "utf-8");
517
+ if (fs$1.existsSync(agentKitPath)) {
518
+ systemPrompt = fs$1.readFileSync(agentKitPath, "utf-8");
519
+ }
520
+ const routeKey = normalizePageRoute(input.pageRoute);
521
+ const sessionPlan = await getOrCreateSessionIdForRoute(
522
+ projectDir,
523
+ routeKey
524
+ );
525
+ if (!tryBeginRouteQuery(routeKey)) {
526
+ yield {
527
+ type: "error",
528
+ message: "Another agent task is already running for this page. Wait for it to finish, then try again."
529
+ };
530
+ yield { type: "done" };
531
+ return;
417
532
  }
533
+ const sharedOptions = {
534
+ cwd: projectDir,
535
+ tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
536
+ allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
537
+ maxTurns: 30,
538
+ persistSession: true,
539
+ systemPrompt
540
+ };
418
541
  try {
419
- for await (const message of query({
420
- prompt: promptContent,
421
- options: {
422
- cwd: projectDir,
423
- tools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
424
- allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
425
- maxTurns: 30,
426
- persistSession: false,
427
- systemPrompt
542
+ async function* streamAgentQuery(opts) {
543
+ for await (const message of query({
544
+ prompt: promptContent,
545
+ options: {
546
+ ...sharedOptions,
547
+ ...opts
548
+ }
549
+ })) {
550
+ for (const chunk of transformSDKMessage(message)) {
551
+ yield chunk;
552
+ }
428
553
  }
429
- })) {
430
- for (const chunk of transformSDKMessage(message)) {
431
- yield chunk;
554
+ }
555
+ let didStaleResumeRetry = false;
556
+ try {
557
+ if (sessionPlan.useResume) {
558
+ yield* streamAgentQuery({ resume: sessionPlan.sessionId });
559
+ } else {
560
+ yield* streamAgentQuery({
561
+ sessionId: sessionPlan.sessionId,
562
+ title: routeKey
563
+ });
564
+ }
565
+ } catch (err) {
566
+ if (sessionPlan.useResume && !didStaleResumeRetry) {
567
+ didStaleResumeRetry = true;
568
+ const fresh = await replaceRouteSessionAfterStaleResume(
569
+ projectDir,
570
+ routeKey
571
+ );
572
+ try {
573
+ yield* streamAgentQuery({
574
+ sessionId: fresh.sessionId,
575
+ title: routeKey
576
+ });
577
+ } catch (err2) {
578
+ yield {
579
+ type: "error",
580
+ message: err2 instanceof Error ? err2.message : String(err2)
581
+ };
582
+ }
583
+ } else {
584
+ yield {
585
+ type: "error",
586
+ message: err instanceof Error ? err.message : String(err)
587
+ };
432
588
  }
433
589
  }
434
- } catch (err) {
435
- yield {
436
- type: "error",
437
- message: err instanceof Error ? err.message : String(err)
438
- };
590
+ } finally {
591
+ endRouteQuery(routeKey);
439
592
  }
440
593
  yield { type: "done" };
441
594
  });
@@ -85,7 +85,7 @@
85
85
  .visual-error { font-size: 12px; color: #f48771; }
86
86
  .visual-submit-error { font-size: 12px; color: #f48771; margin: 0; padding: 0 2px; }
87
87
  .visual-submit-progress { font-size: 12px; color: #9cdcfe; margin: 0; padding: 0 2px; }
88
- .bottom-panel { flex-shrink: 0; display: flex; flex-direction: column; background: #1e1e1e; border-top: 1px solid #3e3e3e; min-height: 32px; max-height: 80vh; }
88
+ .bottom-panel { flex-shrink: 0; display: flex; flex-direction: column; background: #1e1e1e; border-top: 1px solid #3e3e3e; min-height: 32px; max-height: 80vh; height: 200px; }
89
89
  .bottom-panel-collapsed { max-height: 32px; overflow: hidden; }
90
90
  .bottom-panel-resize { height: 4px; cursor: ns-resize; background: transparent; flex-shrink: 0; }
91
91
  .bottom-panel-resize:hover, .bottom-panel-resize:active { background: #007acc; }
@@ -98,7 +98,7 @@
98
98
  .bottom-panel-spacer { flex: 1; }
99
99
  .bottom-panel-clear { background: none; border: none; color: #666; font-size: 11px; cursor: pointer; padding: 2px 6px; }
100
100
  .bottom-panel-clear:hover { color: #ccc; }
101
- .output-panel { overflow-y: auto; padding: 8px 12px; font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; max-height: calc(5 * 1.5em + 16px); }
101
+ .output-panel { overflow-y: auto; padding: 8px 12px; font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace; font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; flex: 1; min-height: 0; }
102
102
  .chunk { color: #d4d4d4; }
103
103
  .chunk-status { color: #888; font-style: italic; }
104
104
  .chunk-tool { color: #569cd6; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/aiditor",
3
- "version": "0.16.4",
3
+ "version": "0.16.5",
4
4
  "type": "module",
5
5
  "description": "AIditor — visual AI-driven code editor plugin for Jay Framework",
6
6
  "main": "dist/index.js",
@@ -38,24 +38,25 @@
38
38
  "build:types": "tsup lib/index.ts lib/index.client.ts --dts-only --format esm",
39
39
  "build:check-types": "tsc",
40
40
  "clean": "rimraf dist",
41
- "test": ":"
41
+ "test": "vitest run"
42
42
  },
43
43
  "dependencies": {
44
44
  "@anthropic-ai/claude-agent-sdk": "0.2.119",
45
- "@jay-framework/fullstack-component": "^0.16.4",
46
- "@jay-framework/stack-client-runtime": "^0.16.4",
47
- "@jay-framework/stack-server-runtime": "^0.16.4",
45
+ "@jay-framework/fullstack-component": "^0.16.5",
46
+ "@jay-framework/stack-client-runtime": "^0.16.5",
47
+ "@jay-framework/stack-server-runtime": "^0.16.5",
48
48
  "busboy": "^1.6.0",
49
49
  "html2canvas": "^1.4.1",
50
50
  "zod": "^4.3.6"
51
51
  },
52
52
  "devDependencies": {
53
- "@jay-framework/compiler-jay-stack": "^0.16.4",
54
- "@jay-framework/jay-cli": "^0.16.4",
53
+ "@jay-framework/compiler-jay-stack": "^0.16.5",
54
+ "@jay-framework/jay-cli": "^0.16.5",
55
55
  "@types/busboy": "^1",
56
56
  "rimraf": "^5.0.5",
57
57
  "tsup": "^8.5.1",
58
58
  "typescript": "^5.3.3",
59
- "vite": "^5.0.11"
59
+ "vite": "^5.0.11",
60
+ "vitest": "^3.2.4"
60
61
  }
61
62
  }
@@ -1,331 +0,0 @@
1
- import {JayElement, RenderElement, HTMLElementCollectionProxy, HTMLElementProxy, RenderElementOptions, JayContract} from "@jay-framework/runtime";
2
-
3
- import './page.css';
4
-
5
- export interface ChunkOfPageViewState {
6
- text: string,
7
- cssClass: string,
8
- filePath: string,
9
- toolName: string
10
- }
11
-
12
- export interface PageSelectOptionOfPageViewState {
13
- url: string,
14
- label: string
15
- }
16
-
17
- export interface PreviewPathOptionOfPageViewState {
18
- path: string
19
- }
20
-
21
- export interface VisualPointDisplayItemOfPageViewState {
22
- id: string,
23
- leftPct: number,
24
- topPct: number,
25
- indexLabel: string
26
- }
27
-
28
- export interface VisualAreaDisplayItemOfPageViewState {
29
- id: string,
30
- leftPct: number,
31
- topPct: number,
32
- widthPct: number,
33
- heightPct: number,
34
- indexLabel: string
35
- }
36
-
37
- export interface VisualArrowDisplayItemOfPageViewState {
38
- id: string,
39
- x1Pct: number,
40
- y1Pct: number,
41
- x2Pct: number,
42
- y2Pct: number
43
- }
44
-
45
- export interface VisualArrowPinItemOfPageViewState {
46
- id: string,
47
- leftPct: number,
48
- topPct: number,
49
- indexLabel: string
50
- }
51
-
52
- export interface AttachmentChipOfVisualAnnotationRowOfPageViewState {
53
- key: string,
54
- name: string,
55
- thumbUrl: string,
56
- annotationId: string
57
- }
58
-
59
- export interface VisualAnnotationRowOfPageViewState {
60
- id: string,
61
- kindLabel: string,
62
- instruction: string,
63
- runDisabled: boolean,
64
- leftPct: number,
65
- topPct: number,
66
- popoverFlipX: boolean,
67
- popoverFlipY: boolean,
68
- attachmentChips: Array<AttachmentChipOfVisualAnnotationRowOfPageViewState>
69
- }
70
-
71
- export interface AttachmentChipOfRecordingDraftUiOfPageViewState {
72
- key: string,
73
- name: string,
74
- thumbUrl: string,
75
- annotationId: string
76
- }
77
-
78
- export interface RecordingDraftUiOfPageViewState {
79
- id: string,
80
- kindLabel: string,
81
- instruction: string,
82
- leftPct: number,
83
- topPct: number,
84
- popoverFlipX: boolean,
85
- popoverFlipY: boolean,
86
- attachmentChips: Array<AttachmentChipOfRecordingDraftUiOfPageViewState>
87
- }
88
-
89
- export interface RecordingDraftAttachmentChipOfPageViewState {
90
- key: string,
91
- name: string,
92
- thumbUrl: string,
93
- annotationId: string
94
- }
95
-
96
- export interface VideoPointDisplayItemOfPageViewState {
97
- id: string,
98
- leftPct: number,
99
- topPct: number,
100
- indexLabel: string
101
- }
102
-
103
- export interface VideoAreaDisplayItemOfPageViewState {
104
- id: string,
105
- leftPct: number,
106
- topPct: number,
107
- widthPct: number,
108
- heightPct: number,
109
- indexLabel: string
110
- }
111
-
112
- export interface VideoArrowDisplayItemOfPageViewState {
113
- id: string,
114
- x1Pct: number,
115
- y1Pct: number,
116
- x2Pct: number,
117
- y2Pct: number
118
- }
119
-
120
- export interface VideoArrowPinItemOfPageViewState {
121
- id: string,
122
- leftPct: number,
123
- topPct: number,
124
- indexLabel: string
125
- }
126
-
127
- export interface AttachmentChipOfVideoAnnotationRowOfPageViewState {
128
- key: string,
129
- name: string,
130
- thumbUrl: string,
131
- annotationId: string
132
- }
133
-
134
- export interface VideoAnnotationRowOfPageViewState {
135
- id: string,
136
- kindLabel: string,
137
- instruction: string,
138
- runDisabled: boolean,
139
- leftPct: number,
140
- topPct: number,
141
- popoverFlipX: boolean,
142
- popoverFlipY: boolean,
143
- attachmentChips: Array<AttachmentChipOfVideoAnnotationRowOfPageViewState>
144
- }
145
-
146
- export interface VideoTimelineMarkerOfPageViewState {
147
- key: string,
148
- timeSec: number,
149
- leftPct: number,
150
- label: string
151
- }
152
-
153
- export interface PageViewState {
154
- isBootstrapping: boolean,
155
- showBootstrapError: boolean,
156
- bootstrapErrorText: string,
157
- canRetryBootstrap: boolean,
158
- isMainVisible: boolean,
159
- projectName: string,
160
- notes: string,
161
- hasImage: boolean,
162
- isRunning: boolean,
163
- chunks: Array<ChunkOfPageViewState>,
164
- hasChunks: boolean,
165
- outputEmpty: boolean,
166
- pageSelectOptions: Array<PageSelectOptionOfPageViewState>,
167
- selectedRouteSelectValue: string,
168
- hasPages: boolean,
169
- isPreviewMode: boolean,
170
- previewUrlBar: string,
171
- previewLoading: boolean,
172
- paramsLoadingHint: string,
173
- previewError: string,
174
- showPathSelect: boolean,
175
- previewPathOptions: Array<PreviewPathOptionOfPageViewState>,
176
- selectedPreviewPath: string,
177
- showPreviewIframe: boolean,
178
- previewSrc: string,
179
- showOutputChunks: boolean,
180
- showOutputEmpty: boolean,
181
- outputEmptyHint: string,
182
- bottomPanelClass: string,
183
- bottomPanelToggleGlyph: string,
184
- showBottomPanelRunning: boolean,
185
- showFilePreview: boolean,
186
- filePreviewPath: string,
187
- filePreviewContent: string,
188
- filePreviewIsImage: boolean,
189
- filePreviewImageSrc: string,
190
- filePreviewLoading: boolean,
191
- showVisualToolbar: boolean,
192
- visualToolNoneOn: boolean,
193
- visualToolPointOn: boolean,
194
- visualToolAreaOn: boolean,
195
- visualToolArrowOn: boolean,
196
- visualPointDisplayItems: Array<VisualPointDisplayItemOfPageViewState>,
197
- showVisualPointMarkers: boolean,
198
- visualAreaDisplayItems: Array<VisualAreaDisplayItemOfPageViewState>,
199
- showVisualAreaItems: boolean,
200
- areaDraftLeftPct: number,
201
- areaDraftTopPct: number,
202
- areaDraftWidthPct: number,
203
- areaDraftHeightPct: number,
204
- showAreaDraftRect: boolean,
205
- visualArrowDisplayItems: Array<VisualArrowDisplayItemOfPageViewState>,
206
- visualArrowPinItems: Array<VisualArrowPinItemOfPageViewState>,
207
- hasVisualArrowLines: boolean,
208
- showArrowPending: boolean,
209
- arrowPendingLeftPct: number,
210
- arrowPendingTopPct: number,
211
- visualAnnotationRows: Array<VisualAnnotationRowOfPageViewState>,
212
- recordingDraftUi: RecordingDraftUiOfPageViewState,
213
- recordingDraftAttachmentChips: Array<RecordingDraftAttachmentChipOfPageViewState>,
214
- showRecordingDraftPopover: boolean,
215
- showVisualAnnotationsPanel: boolean,
216
- showVisualSubmitError: boolean,
217
- visualSubmitError: string,
218
- visualOverlayPointerNone: boolean,
219
- showVideoRecordUi: boolean,
220
- isVideoRecording: boolean,
221
- videoRecordLabel: string,
222
- videoRecordDisabled: boolean,
223
- freezeDisabled: boolean,
224
- showVideoReviewModal: boolean,
225
- videoReviewPreparing: boolean,
226
- videoReviewReady: boolean,
227
- videoReviewError: boolean,
228
- videoReviewErrorText: string,
229
- videoReviewObjectUrl: string,
230
- videoModalToolNoneOn: boolean,
231
- videoModalToolPointOn: boolean,
232
- videoModalToolAreaOn: boolean,
233
- videoModalToolArrowOn: boolean,
234
- videoPointDisplayItems: Array<VideoPointDisplayItemOfPageViewState>,
235
- showVideoPointMarkers: boolean,
236
- videoAreaDisplayItems: Array<VideoAreaDisplayItemOfPageViewState>,
237
- showVideoAreaItems: boolean,
238
- videoAreaDraftLeftPct: number,
239
- videoAreaDraftTopPct: number,
240
- videoAreaDraftWidthPct: number,
241
- videoAreaDraftHeightPct: number,
242
- showVideoAreaDraftRect: boolean,
243
- videoArrowDisplayItems: Array<VideoArrowDisplayItemOfPageViewState>,
244
- videoArrowPinItems: Array<VideoArrowPinItemOfPageViewState>,
245
- hasVideoArrowLines: boolean,
246
- showVideoArrowPending: boolean,
247
- videoArrowPendingLeftPct: number,
248
- videoArrowPendingTopPct: number,
249
- videoAnnotationRows: Array<VideoAnnotationRowOfPageViewState>,
250
- showVideoAnnotationsPanel: boolean,
251
- videoModalOverlayPointerNone: boolean,
252
- videoSubmitError: string,
253
- showVideoSubmitError: boolean,
254
- videoSubmitProgress: string,
255
- showVideoSubmitProgress: boolean,
256
- videoTimelineMarkers: Array<VideoTimelineMarkerOfPageViewState>,
257
- showVideoAnnotationPopoversAtPlayhead: boolean,
258
- videoTimeLabel: string,
259
- videoSendDisabled: boolean,
260
- videoPlayPauseGlyph: string,
261
- breakpointDesktopOn: boolean,
262
- breakpointTabletOn: boolean,
263
- breakpointMobileOn: boolean
264
- }
265
-
266
-
267
- export interface PageElementRefs {
268
- retryBootstrapBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
269
- pageRouteSelect: HTMLElementProxy<PageViewState, HTMLSelectElement>,
270
- previewPathSelect: HTMLElementProxy<PageViewState, HTMLSelectElement>,
271
- breakpointDesktopBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
272
- breakpointTabletBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
273
- breakpointMobileBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
274
- visualToolNoneBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
275
- visualToolPointBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
276
- visualToolAreaBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
277
- visualToolArrowBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
278
- videoRecordBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
279
- freezeBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
280
- visualAttachFileInput: HTMLElementProxy<PageViewState, HTMLInputElement>,
281
- previewIframe: HTMLElementProxy<PageViewState, HTMLIFrameElement>,
282
- visualOverlay: HTMLElementProxy<PageViewState, HTMLDivElement>,
283
- recordingDraftPopover: HTMLElementProxy<PageViewState, HTMLDivElement>,
284
- previewCaptureRoot: HTMLElementProxy<PageViewState, HTMLDivElement>,
285
- bottomPanelResize: HTMLElementProxy<PageViewState, HTMLDivElement>,
286
- bottomPanelClearBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
287
- bottomPanelToggleBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
288
- bottomPanelHeader: HTMLElementProxy<PageViewState, HTMLDivElement>,
289
- outputScroll: HTMLElementProxy<PageViewState, HTMLDivElement>,
290
- filePreviewCloseBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
291
- filePreviewBackdrop: HTMLElementProxy<PageViewState, HTMLDivElement>,
292
- videoReviewCloseBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
293
- videoModalToolNoneBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
294
- videoModalToolPointBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
295
- videoModalToolAreaBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
296
- videoModalToolArrowBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
297
- videoReviewAttachFileInput: HTMLElementProxy<PageViewState, HTMLInputElement>,
298
- videoReviewPlayer: HTMLElementProxy<PageViewState, HTMLVideoElement>,
299
- videoReviewOverlay: HTMLElementProxy<PageViewState, HTMLDivElement>,
300
- videoReviewCaptureRoot: HTMLElementProxy<PageViewState, HTMLDivElement>,
301
- videoPlayPauseBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
302
- videoTimeline: HTMLElementProxy<PageViewState, HTMLInputElement>,
303
- videoReviewTimelineWrap: HTMLElementProxy<PageViewState, HTMLDivElement>,
304
- videoReviewDiscardBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
305
- videoReviewSendBtn: HTMLElementProxy<PageViewState, HTMLButtonElement>,
306
- videoReviewBackdrop: HTMLElementProxy<PageViewState, HTMLDivElement>,
307
- visualAnnotationRows: {
308
- annotationRow: HTMLElementCollectionProxy<VisualAnnotationRowOfPageViewState, HTMLDivElement>
309
- },
310
- videoAnnotationRows: {
311
- videoAnnotationRow: HTMLElementCollectionProxy<VideoAnnotationRowOfPageViewState, HTMLDivElement>
312
- }
313
- }
314
-
315
- export type PageSlowViewState = {};
316
- export type PageFastViewState = PageViewState;
317
- export type PageInteractiveViewState = PageViewState;
318
-
319
- export type PageElement = JayElement<PageViewState, PageElementRefs>
320
- export type PageElementRender = RenderElement<PageViewState, PageElementRefs, PageElement>
321
- export type PageElementPreRender = [PageElementRefs, PageElementRender]
322
- export type PageContract = JayContract<
323
- PageViewState,
324
- PageElementRefs,
325
- PageSlowViewState,
326
- PageFastViewState,
327
- PageInteractiveViewState
328
- >;
329
-
330
-
331
- export declare function render(options?: RenderElementOptions): PageElementPreRender