@quanta-intellect/vessel-browser 0.1.32 → 0.1.33

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/out/main/index.js CHANGED
@@ -19087,11 +19087,21 @@ function stopMcpServer() {
19087
19087
  });
19088
19088
  });
19089
19089
  }
19090
+ const VALID_KIT_CATEGORIES = /* @__PURE__ */ new Set([
19091
+ "research",
19092
+ "shopping",
19093
+ "productivity",
19094
+ "forms"
19095
+ ]);
19090
19096
  const BUNDLED_KIT_IDS = /* @__PURE__ */ new Set([
19091
19097
  "research-collect",
19092
19098
  "price-scout",
19093
19099
  "form-filler"
19094
19100
  ]);
19101
+ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
19102
+ function isSafeAutomationKitId(id) {
19103
+ return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
19104
+ }
19095
19105
  function getUserKitsDir() {
19096
19106
  return path$1.join(electron.app.getPath("userData"), "kits");
19097
19107
  }
@@ -19101,10 +19111,16 @@ function ensureKitsDir() {
19101
19111
  fs$1.mkdirSync(dir, { recursive: true });
19102
19112
  }
19103
19113
  }
19114
+ function getKitFilePath(id) {
19115
+ if (!isSafeAutomationKitId(id)) return null;
19116
+ const kitsDir = path$1.resolve(getUserKitsDir());
19117
+ const target = path$1.resolve(kitsDir, `${id}.kit.json`);
19118
+ return target.startsWith(`${kitsDir}${path$1.sep}`) ? target : null;
19119
+ }
19104
19120
  function isValidKit(value) {
19105
19121
  if (!value || typeof value !== "object") return false;
19106
19122
  const k = value;
19107
- return typeof k.id === "string" && k.id.length > 0 && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
19123
+ return typeof k.id === "string" && isSafeAutomationKitId(k.id) && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.category === "string" && VALID_KIT_CATEGORIES.has(k.category) && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
19108
19124
  }
19109
19125
  function getInstalledKits() {
19110
19126
  ensureKitsDir();
@@ -19165,7 +19181,10 @@ async function installKitFromFile() {
19165
19181
  };
19166
19182
  }
19167
19183
  ensureKitsDir();
19168
- const dest = path$1.join(getUserKitsDir(), `${parsed.id}.kit.json`);
19184
+ const dest = getKitFilePath(parsed.id);
19185
+ if (!dest) {
19186
+ return { ok: false, error: "Kit id contains unsupported characters." };
19187
+ }
19169
19188
  try {
19170
19189
  fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
19171
19190
  } catch {
@@ -19173,12 +19192,21 @@ async function installKitFromFile() {
19173
19192
  }
19174
19193
  return { ok: true, kit: parsed };
19175
19194
  }
19176
- function uninstallKit(id) {
19195
+ function uninstallKit(id, scheduledKitIds) {
19177
19196
  if (BUNDLED_KIT_IDS.has(id)) {
19178
19197
  return { ok: false, error: "Built-in kits cannot be removed." };
19179
19198
  }
19199
+ if (scheduledKitIds?.has(id)) {
19200
+ return {
19201
+ ok: false,
19202
+ error: "This kit has active scheduled jobs. Delete or reassign them first."
19203
+ };
19204
+ }
19180
19205
  ensureKitsDir();
19181
- const target = path$1.join(getUserKitsDir(), `${id}.kit.json`);
19206
+ const target = getKitFilePath(id);
19207
+ if (!target) {
19208
+ return { ok: false, error: "Kit id contains unsupported characters." };
19209
+ }
19182
19210
  if (!fs$1.existsSync(target)) {
19183
19211
  return { ok: false, error: "Kit not found." };
19184
19212
  }
@@ -19192,6 +19220,9 @@ function uninstallKit(id) {
19192
19220
  let jobs = [];
19193
19221
  let removeIdleListener = null;
19194
19222
  let broadcastFn = null;
19223
+ function getScheduledKitIds() {
19224
+ return new Set(jobs.filter((j) => j.enabled).map((j) => j.kitId));
19225
+ }
19195
19226
  function getJobsPath() {
19196
19227
  return path$1.join(electron.app.getPath("userData"), "scheduled-jobs.json");
19197
19228
  }
@@ -19361,29 +19392,40 @@ async function fireJob(job, windowState, runtime2) {
19361
19392
  }
19362
19393
  function tick(windowState, runtime2) {
19363
19394
  if (isAIStreamActive()) return;
19364
- const now = /* @__PURE__ */ new Date();
19365
- let changed = false;
19366
- for (const job of jobs) {
19367
- if (!job.enabled) continue;
19368
- if (now < new Date(job.nextRunAt)) continue;
19369
- if (!tryBeginAIStream("scheduled")) break;
19370
- void fireJob(job, windowState, runtime2).finally(() => {
19395
+ const dueIds = jobs.filter((job) => job.enabled && /* @__PURE__ */ new Date() >= new Date(job.nextRunAt)).map((job) => job.id);
19396
+ if (dueIds.length === 0) return;
19397
+ if (!tryBeginAIStream("scheduled")) return;
19398
+ let idx = 0;
19399
+ const fireNext = () => {
19400
+ if (idx >= dueIds.length) {
19371
19401
  endAIStream("scheduled");
19372
19402
  queueMicrotask(() => tick(windowState, runtime2));
19373
- });
19374
- job.lastRunAt = now.toISOString();
19403
+ return;
19404
+ }
19405
+ const jobId = dueIds[idx++];
19406
+ const job = jobs.find((candidate) => candidate.id === jobId);
19407
+ if (!job || !job.enabled) {
19408
+ fireNext();
19409
+ return;
19410
+ }
19411
+ const firedAt = /* @__PURE__ */ new Date();
19412
+ if (firedAt < new Date(job.nextRunAt)) {
19413
+ fireNext();
19414
+ return;
19415
+ }
19416
+ job.lastRunAt = firedAt.toISOString();
19375
19417
  if (job.schedule.type === "once") {
19376
19418
  job.enabled = false;
19377
19419
  } else {
19378
- job.nextRunAt = computeNextRun(job.schedule, now).toISOString();
19420
+ job.nextRunAt = computeNextRun(job.schedule, firedAt).toISOString();
19379
19421
  }
19380
- changed = true;
19381
- break;
19382
- }
19383
- if (changed) {
19384
19422
  saveJobs();
19385
19423
  broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
19386
- }
19424
+ void fireJob(job, windowState, runtime2).catch((err) => {
19425
+ console.warn("[scheduler] Unexpected error firing job:", err);
19426
+ }).finally(fireNext);
19427
+ };
19428
+ fireNext();
19387
19429
  }
19388
19430
  function registerScheduleHandlers(windowState, runtime2, sendToAll) {
19389
19431
  broadcastFn = sendToAll;
@@ -19646,7 +19688,7 @@ function registerIpcHandlers(windowState, runtime2) {
19646
19688
  Channels.AI_STREAM_CHUNK,
19647
19689
  "Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
19648
19690
  );
19649
- sendToRendererViews(Channels.AI_STREAM_END);
19691
+ sendToRendererViews(Channels.AI_STREAM_END, "failed");
19650
19692
  return { accepted: true };
19651
19693
  }
19652
19694
  if (!tryBeginAIStream("manual")) {
@@ -19663,7 +19705,7 @@ function registerIpcHandlers(windowState, runtime2) {
19663
19705
  activeChatProvider,
19664
19706
  activeTab?.view.webContents,
19665
19707
  (chunk) => sendToRendererViews(Channels.AI_STREAM_CHUNK, chunk),
19666
- () => sendToRendererViews(Channels.AI_STREAM_END),
19708
+ () => sendToRendererViews(Channels.AI_STREAM_END, "completed"),
19667
19709
  tabManager,
19668
19710
  runtime2,
19669
19711
  history
@@ -19672,7 +19714,7 @@ function registerIpcHandlers(windowState, runtime2) {
19672
19714
  const msg = err instanceof Error ? err.message : "Unknown error";
19673
19715
  sendToRendererViews(Channels.AI_STREAM_CHUNK, `
19674
19716
  [Error: ${msg}]`);
19675
- sendToRendererViews(Channels.AI_STREAM_END);
19717
+ sendToRendererViews(Channels.AI_STREAM_END, "failed");
19676
19718
  } finally {
19677
19719
  activeChatProvider = null;
19678
19720
  endAIStream("manual");
@@ -20094,7 +20136,7 @@ function registerIpcHandlers(windowState, runtime2) {
20094
20136
  });
20095
20137
  electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (_event, id) => {
20096
20138
  assertString(id, "id");
20097
- return uninstallKit(id);
20139
+ return uninstallKit(id, getScheduledKitIds());
20098
20140
  });
20099
20141
  registerScheduleHandlers(windowState, runtime2, sendToRendererViews);
20100
20142
  }
@@ -137,7 +137,7 @@ const api = {
137
137
  return () => electron.ipcRenderer.removeListener(Channels.AI_STREAM_CHUNK, handler);
138
138
  },
139
139
  onStreamEnd: (cb) => {
140
- const handler = () => cb();
140
+ const handler = (_, status = "completed") => cb(status);
141
141
  electron.ipcRenderer.on(Channels.AI_STREAM_END, handler);
142
142
  return () => electron.ipcRenderer.removeListener(Channels.AI_STREAM_END, handler);
143
143
  },
@@ -2324,11 +2324,14 @@ const [hasFirstChunk, setHasFirstChunk] = createSignal(false);
2324
2324
  const [streamStartedAt, setStreamStartedAt] = createSignal(null);
2325
2325
  const [recentQueries, setRecentQueries] = createSignal([]);
2326
2326
  const [pendingQueries, setPendingQueries] = createSignal([]);
2327
+ const [pendingQueryActivities, setPendingQueryActivities] = createSignal([]);
2327
2328
  const [queueNotice, setQueueNotice] = createSignal(null);
2328
2329
  const [automationActivities, setAutomationActivities] = createSignal([]);
2329
2330
  let initialized$1 = false;
2330
2331
  let pendingDrainScheduled = false;
2331
2332
  let listenerCleanups = [];
2333
+ let pendingAutomationActivity = null;
2334
+ let activeAutomationActivityId = null;
2332
2335
  function trimMessages(next) {
2333
2336
  return next.length > MAX_MESSAGE_HISTORY ? next.slice(-MAX_MESSAGE_HISTORY) : next;
2334
2337
  }
@@ -2348,17 +2351,23 @@ async function dispatchQuery(prompt) {
2348
2351
  const result = await window.vessel.ai.query(prompt, buildHistory());
2349
2352
  return result.accepted;
2350
2353
  }
2351
- async function dispatchQueuedPrompt(prompt) {
2354
+ async function dispatchQueuedPrompt(prompt, activity) {
2355
+ pendingAutomationActivity = activity;
2352
2356
  const accepted = await dispatchQuery(prompt);
2353
2357
  if (!accepted) {
2358
+ pendingAutomationActivity = null;
2354
2359
  const queued = enqueuePendingPrompt(pendingQueries(), prompt, { atFront: true });
2355
2360
  setPendingQueries(queued.queue);
2356
2361
  setQueueNotice(queued.notice);
2362
+ if (queued.status === "queued") {
2363
+ setPendingQueryActivities((prev) => [activity, ...prev]);
2364
+ }
2357
2365
  }
2358
2366
  }
2359
2367
  function schedulePendingDrain() {
2360
2368
  if (pendingDrainScheduled || isStreaming()) return;
2361
2369
  if (pendingQueries().length === 0) {
2370
+ setPendingQueryActivities([]);
2362
2371
  setQueueNotice(null);
2363
2372
  return;
2364
2373
  }
@@ -2367,10 +2376,12 @@ function schedulePendingDrain() {
2367
2376
  pendingDrainScheduled = false;
2368
2377
  if (isStreaming()) return;
2369
2378
  const next = dequeuePendingPrompt(pendingQueries());
2379
+ const [nextActivity = null, ...remainingActivities] = pendingQueryActivities();
2370
2380
  setPendingQueries(next.queue);
2381
+ setPendingQueryActivities(remainingActivities);
2371
2382
  setQueueNotice(next.notice);
2372
2383
  if (next.nextPrompt) {
2373
- void dispatchQueuedPrompt(next.nextPrompt);
2384
+ void dispatchQueuedPrompt(next.nextPrompt, nextActivity);
2374
2385
  }
2375
2386
  });
2376
2387
  }
@@ -2386,14 +2397,35 @@ function init$1() {
2386
2397
  setIsStreaming(true);
2387
2398
  setHasFirstChunk(false);
2388
2399
  setStreamStartedAt(Date.now());
2400
+ if (pendingAutomationActivity) {
2401
+ const activity = pendingAutomationActivity;
2402
+ activeAutomationActivityId = activity.id;
2403
+ setAutomationActivities(
2404
+ (prev) => startAutomationActivity(prev, {
2405
+ id: activity.id,
2406
+ source: "scheduled",
2407
+ title: activity.title,
2408
+ icon: activity.icon,
2409
+ status: "running",
2410
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2411
+ })
2412
+ );
2413
+ pendingAutomationActivity = null;
2414
+ }
2389
2415
  }));
2390
2416
  listenerCleanups.push(window.vessel.ai.onStreamChunk((chunk) => {
2391
2417
  if (!hasFirstChunk()) {
2392
2418
  setHasFirstChunk(true);
2393
2419
  }
2394
2420
  setStreamingText((prev) => prev + chunk);
2421
+ if (activeAutomationActivityId) {
2422
+ const activityId = activeAutomationActivityId;
2423
+ setAutomationActivities(
2424
+ (prev) => appendAutomationActivityChunk(prev, activityId, chunk)
2425
+ );
2426
+ }
2395
2427
  }));
2396
- listenerCleanups.push(window.vessel.ai.onStreamEnd(() => {
2428
+ listenerCleanups.push(window.vessel.ai.onStreamEnd((status) => {
2397
2429
  const finalText = streamingText();
2398
2430
  if (finalText) {
2399
2431
  setMessages((prev) => {
@@ -2401,6 +2433,19 @@ function init$1() {
2401
2433
  return trimMessages(next);
2402
2434
  });
2403
2435
  }
2436
+ if (activeAutomationActivityId) {
2437
+ const activityId = activeAutomationActivityId;
2438
+ setAutomationActivities(
2439
+ (prev) => finishAutomationActivity(
2440
+ prev,
2441
+ activityId,
2442
+ status,
2443
+ (/* @__PURE__ */ new Date()).toISOString()
2444
+ )
2445
+ );
2446
+ activeAutomationActivityId = null;
2447
+ }
2448
+ pendingAutomationActivity = null;
2404
2449
  setStreamingText("");
2405
2450
  setIsStreaming(false);
2406
2451
  setHasFirstChunk(false);
@@ -2426,6 +2471,32 @@ function init$1() {
2426
2471
  }
2427
2472
  function useAI() {
2428
2473
  init$1();
2474
+ const query = async (prompt, activity = null) => {
2475
+ recordRecentQuery(prompt);
2476
+ if (isStreaming()) {
2477
+ const queued = enqueuePendingPrompt(pendingQueries(), prompt);
2478
+ setPendingQueries(queued.queue);
2479
+ setQueueNotice(queued.notice);
2480
+ if (queued.status === "queued") {
2481
+ setPendingQueryActivities((prev) => [...prev, activity]);
2482
+ }
2483
+ return queued.status;
2484
+ }
2485
+ setQueueNotice(null);
2486
+ pendingAutomationActivity = activity;
2487
+ const accepted = await dispatchQuery(prompt);
2488
+ if (!accepted) {
2489
+ pendingAutomationActivity = null;
2490
+ const queued = enqueuePendingPrompt(pendingQueries(), prompt, { atFront: true });
2491
+ setPendingQueries(queued.queue);
2492
+ setQueueNotice(queued.notice);
2493
+ if (queued.status === "queued") {
2494
+ setPendingQueryActivities((prev) => [activity, ...prev]);
2495
+ }
2496
+ return queued.status;
2497
+ }
2498
+ return "started";
2499
+ };
2429
2500
  return {
2430
2501
  messages,
2431
2502
  streamingText,
@@ -2438,39 +2509,28 @@ function useAI() {
2438
2509
  pendingQueryCount: () => pendingQueries().length,
2439
2510
  pendingQueryLimit: MAX_PENDING_QUERIES,
2440
2511
  queueNotice,
2441
- query: async (prompt) => {
2442
- recordRecentQuery(prompt);
2443
- if (isStreaming()) {
2444
- const queued = enqueuePendingPrompt(pendingQueries(), prompt);
2445
- setPendingQueries(queued.queue);
2446
- setQueueNotice(queued.notice);
2447
- return queued.status;
2448
- }
2449
- setQueueNotice(null);
2450
- const accepted = await dispatchQuery(prompt);
2451
- if (!accepted) {
2452
- const queued = enqueuePendingPrompt(pendingQueries(), prompt, { atFront: true });
2453
- setPendingQueries(queued.queue);
2454
- setQueueNotice(queued.notice);
2455
- return queued.status;
2456
- }
2457
- return "started";
2458
- },
2512
+ query,
2513
+ runAutomationPrompt: async (prompt, activity) => query(prompt, activity),
2459
2514
  cancel: () => window.vessel.ai.cancel(),
2460
2515
  removePendingQuery: (index) => {
2461
2516
  const next = removePendingPrompt(pendingQueries(), index);
2462
2517
  setPendingQueries(next.queue);
2518
+ setPendingQueryActivities(
2519
+ (prev) => prev.filter((_, itemIndex) => itemIndex !== index)
2520
+ );
2463
2521
  setQueueNotice(next.notice);
2464
2522
  },
2465
2523
  clearPendingQueries: () => {
2466
2524
  const next = clearPendingPromptQueue();
2467
2525
  setPendingQueries(next.queue);
2526
+ setPendingQueryActivities([]);
2468
2527
  setQueueNotice(next.notice);
2469
2528
  },
2470
2529
  clearHistory: () => {
2471
2530
  setMessages([]);
2472
2531
  const next = clearPendingPromptQueue();
2473
2532
  setPendingQueries(next.queue);
2533
+ setPendingQueryActivities([]);
2474
2534
  setQueueNotice(next.notice);
2475
2535
  }
2476
2536
  };
@@ -4440,6 +4500,11 @@ var Zap = (props) => createComponent(Icon_default, mergeProps(props, {
4440
4500
  name: "zap"
4441
4501
  }));
4442
4502
  var zap_default = Zap;
4503
+ const BUNDLED_KIT_IDS = /* @__PURE__ */ new Set([
4504
+ "research-collect",
4505
+ "price-scout",
4506
+ "form-filler"
4507
+ ]);
4443
4508
  const BUNDLED_KITS = [
4444
4509
  {
4445
4510
  id: "research-collect",
@@ -4571,6 +4636,13 @@ Steps:
4571
4636
  }
4572
4637
  ];
4573
4638
  function renderKitPrompt(kit, values) {
4639
+ for (const input of kit.inputs) {
4640
+ if (input.required && !values[input.key]?.trim()) {
4641
+ console.warn(
4642
+ `[automation-kits] Required field "${input.key}" is empty for kit "${kit.id}".`
4643
+ );
4644
+ }
4645
+ }
4574
4646
  return kit.promptTemplate.replace(
4575
4647
  /\{\{(\w+)\}\}/g,
4576
4648
  (_, key) => values[key] ?? ""
@@ -4599,7 +4671,6 @@ const KitIcon = (props) => {
4599
4671
  }
4600
4672
  });
4601
4673
  };
4602
- const BUNDLED_KIT_IDS = new Set(BUNDLED_KITS.map((k) => k.id));
4603
4674
  const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
4604
4675
  function formatScheduleLabel(job) {
4605
4676
  const {
@@ -4650,7 +4721,7 @@ function toLocalDateTimeInput(iso) {
4650
4721
  }
4651
4722
  const AutomationTab = (props) => {
4652
4723
  const {
4653
- query,
4724
+ runAutomationPrompt,
4654
4725
  isStreaming: isStreaming2,
4655
4726
  automationActivities: automationActivities2
4656
4727
  } = useAI();
@@ -4739,9 +4810,15 @@ const AutomationTab = (props) => {
4739
4810
  const kit = selectedKit();
4740
4811
  if (!kit || !canRun()) return;
4741
4812
  const prompt = renderKitPrompt(kit, fieldValues());
4813
+ const activityId = `adhoc:${kit.id}:${Date.now()}`;
4814
+ const result = await runAutomationPrompt(prompt, {
4815
+ id: activityId,
4816
+ title: kit.name,
4817
+ icon: kit.icon
4818
+ });
4819
+ if (result === "rejected") return;
4742
4820
  setSelectedKit(null);
4743
4821
  props.onRun();
4744
- await query(prompt);
4745
4822
  };
4746
4823
  const handleSchedule = async () => {
4747
4824
  const kit = selectedKit();
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' data:;" />
7
7
  <title>Vessel</title>
8
- <script type="module" crossorigin src="./assets/index-DVD9XuhC.js"></script>
8
+ <script type="module" crossorigin src="./assets/index-BFdOm6Op.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="./assets/index-eS3ccAls.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quanta-intellect/vessel-browser",
3
3
  "mcpName": "io.github.unmodeled-tyler/vessel-browser",
4
- "version": "0.1.32",
4
+ "version": "0.1.33",
5
5
  "description": "AI-native web browser runtime for autonomous agents with human supervision",
6
6
  "main": "./out/main/index.js",
7
7
  "bin": {