@syrin/iris 0.4.0 → 0.5.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/server.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // ../server/dist/index.js
2
- import { join as join2 } from "path";
2
+ import { join as join5 } from "path";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
 
5
5
  // ../protocol/dist/constants.js
@@ -47,6 +47,7 @@ var CRAWL_DEFAULTS = {
47
47
  /** HTTP status at/above which a response counts as a failed request. */
48
48
  FAILED_STATUS: 400
49
49
  };
50
+ var UpdateCheckIntervalMs = 24 * 60 * 60 * 1e3;
50
51
  var CONTRACT_FILE_VERSION = 1;
51
52
  var FROM_DISK_ARG = "fromDisk";
52
53
  var ContractReadError = {
@@ -649,7 +650,94 @@ var AnnotationSchema = z2.discriminatedUnion("kind", [
649
650
  })
650
651
  ]);
651
652
 
653
+ // ../server/dist/http-server.js
654
+ import * as http from "http";
655
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
656
+
657
+ // ../server/dist/log.js
658
+ function log(event, fields = {}) {
659
+ const line = JSON.stringify({ event, ...fields });
660
+ process.stderr.write(`${line}
661
+ `);
662
+ }
663
+
664
+ // ../server/dist/http-server.js
665
+ var MCP_SSE_PATH = "/mcp/sse";
666
+ var MCP_MESSAGE_PATH = "/mcp/message";
667
+ function createSharedServer() {
668
+ let mcpFactory;
669
+ const transports = /* @__PURE__ */ new Map();
670
+ const httpServer = http.createServer((req, res) => {
671
+ const url = req.url ?? "/";
672
+ if (req.method === "GET" && url === MCP_SSE_PATH) {
673
+ if (mcpFactory === void 0) {
674
+ res.writeHead(503, { "Content-Type": "text/plain" });
675
+ res.end("MCP server not ready");
676
+ return;
677
+ }
678
+ const mcpServer = mcpFactory();
679
+ const transport = new SSEServerTransport(MCP_MESSAGE_PATH, res);
680
+ const sid = transport.sessionId;
681
+ transports.set(sid, transport);
682
+ res.on("close", () => {
683
+ transports.delete(sid);
684
+ transport.close().catch(() => void 0);
685
+ mcpServer.close().catch(() => void 0);
686
+ log("mcp_client_disconnected", { sessionId: sid });
687
+ });
688
+ mcpServer.connect(transport).then(() => {
689
+ log("mcp_client_connected", { sessionId: sid });
690
+ }).catch((err) => {
691
+ const message = err instanceof Error ? err.message : String(err);
692
+ log("mcp_connect_error", { error: message });
693
+ });
694
+ return;
695
+ }
696
+ if (req.method === "POST" && url.startsWith(MCP_MESSAGE_PATH)) {
697
+ const parsed = new URL(url, "http://localhost");
698
+ const sessionId = parsed.searchParams.get("sessionId");
699
+ if (sessionId === null) {
700
+ res.writeHead(400, { "Content-Type": "text/plain" });
701
+ res.end("missing sessionId");
702
+ return;
703
+ }
704
+ const transport = transports.get(sessionId);
705
+ if (transport === void 0) {
706
+ res.writeHead(404, { "Content-Type": "text/plain" });
707
+ res.end("session not found");
708
+ return;
709
+ }
710
+ transport.handlePostMessage(req, res).catch((err) => {
711
+ const message = err instanceof Error ? err.message : String(err);
712
+ log("mcp_message_error", { error: message });
713
+ });
714
+ return;
715
+ }
716
+ res.writeHead(404, { "Content-Type": "text/plain" });
717
+ res.end("not found");
718
+ });
719
+ function attachMcp(factory) {
720
+ mcpFactory = factory;
721
+ }
722
+ async function close() {
723
+ for (const transport of transports.values()) {
724
+ await transport.close();
725
+ }
726
+ transports.clear();
727
+ await new Promise((resolve, reject) => {
728
+ httpServer.close((err) => {
729
+ if (err !== void 0 && err !== null)
730
+ reject(err);
731
+ else
732
+ resolve();
733
+ });
734
+ });
735
+ }
736
+ return { httpServer, attachMcp, close };
737
+ }
738
+
652
739
  // ../server/dist/bridge.js
740
+ import * as http2 from "http";
653
741
  import { WebSocketServer } from "ws";
654
742
 
655
743
  // ../server/dist/events/ring-buffer.js
@@ -1071,7 +1159,16 @@ var SessionManager = class {
1071
1159
  }
1072
1160
  /**
1073
1161
  * Resolve the target session. With an explicit id, returns it. With none and exactly
1074
- * one connected, returns that. Otherwise throws a clear, agent-readable error.
1162
+ * one connected, returns that.
1163
+ *
1164
+ * With none and multiple connected, applies smart auto-selection:
1165
+ * 1. Prefer non-throttled sessions (not hidden + recently heard from).
1166
+ * 2. Within each tier, prefer lowest lastSeenMs (most recently active SDK heartbeat).
1167
+ * 3. If two or more non-throttled sessions are within 1 s of each other, throw —
1168
+ * genuinely ambiguous, agent must specify sessionId.
1169
+ * 4. If ALL sessions are throttled (e.g. user is working in their editor on another
1170
+ * desktop), skip the gap check and pick the freshest heartbeat. This lets the agent
1171
+ * keep working in the background without requiring sessionId every time.
1075
1172
  */
1076
1173
  resolve(sessionId) {
1077
1174
  if (sessionId !== void 0) {
@@ -1085,25 +1182,33 @@ var SessionManager = class {
1085
1182
  if (this.#sessions.size === 0) {
1086
1183
  throw new Error("no browser session connected \u2014 is your app running with @syrin/iris-browser enabled?");
1087
1184
  }
1088
- if (this.#sessions.size > 1) {
1089
- const ids = [...this.#sessions.keys()].join(", ");
1090
- throw new Error(`multiple sessions connected (${ids}); pass sessionId to target one`);
1185
+ const all = [...this.#sessions.values()];
1186
+ if (all.length === 1) {
1187
+ const [only] = all;
1188
+ if (only === void 0)
1189
+ throw new Error("session lookup failed");
1190
+ only.markAgentActivity();
1191
+ return only;
1091
1192
  }
1092
- const [only] = this.#sessions.values();
1093
- if (only === void 0)
1193
+ const scored = all.map((s) => ({ s, score: s.throttled() ? 1 : 0, ms: s.lastSeenMs() }));
1194
+ const bestScore = Math.min(...scored.map((x) => x.score));
1195
+ const candidates = scored.filter((x) => x.score === bestScore);
1196
+ candidates.sort((a, b) => a.ms - b.ms);
1197
+ const [best, runnerUp] = candidates;
1198
+ if (best === void 0)
1094
1199
  throw new Error("session lookup failed");
1095
- only.markAgentActivity();
1096
- return only;
1200
+ const allThrottled = bestScore === 1;
1201
+ const RECENCY_GAP_MS = allThrottled ? 0 : 1e3;
1202
+ const clearWinner = runnerUp === void 0 || best.ms + RECENCY_GAP_MS < runnerUp.ms;
1203
+ if (!clearWinner) {
1204
+ const detail = all.map((s) => `${s.id} (${s.throttled() ? "throttled" : "active"}, lastSeenMs=${s.lastSeenMs()})`).join(", ");
1205
+ throw new Error(`multiple sessions connected \u2014 pass sessionId to target one: ${detail}`);
1206
+ }
1207
+ best.s.markAgentActivity();
1208
+ return best.s;
1097
1209
  }
1098
1210
  };
1099
1211
 
1100
- // ../server/dist/log.js
1101
- function log(event, fields = {}) {
1102
- const line = JSON.stringify({ event, ...fields });
1103
- process.stderr.write(`${line}
1104
- `);
1105
- }
1106
-
1107
1212
  // ../server/dist/bridge.js
1108
1213
  function rawToString(raw) {
1109
1214
  if (typeof raw === "string")
@@ -1122,16 +1227,30 @@ var Bridge = class {
1122
1227
  #clock;
1123
1228
  constructor(options) {
1124
1229
  this.#clock = options.clock ?? (() => Date.now());
1125
- this.#wss = new WebSocketServer({
1126
- port: options.port,
1127
- host: options.host ?? "127.0.0.1",
1128
- path: IRIS_WS_PATH
1129
- });
1130
- this.ready = new Promise((resolve) => {
1131
- this.#wss.on("listening", () => {
1132
- resolve(this.#wss.address().port);
1230
+ if (options.server !== void 0) {
1231
+ const srv = options.server;
1232
+ this.#wss = new WebSocketServer({ server: srv, path: IRIS_WS_PATH });
1233
+ this.ready = new Promise((resolve) => {
1234
+ if (srv.listening) {
1235
+ resolve(srv.address().port);
1236
+ } else {
1237
+ srv.once("listening", () => {
1238
+ resolve(srv.address().port);
1239
+ });
1240
+ }
1133
1241
  });
1134
- });
1242
+ } else {
1243
+ this.#wss = new WebSocketServer({
1244
+ port: options.port,
1245
+ host: options.host ?? "127.0.0.1",
1246
+ path: IRIS_WS_PATH
1247
+ });
1248
+ this.ready = new Promise((resolve) => {
1249
+ this.#wss.on("listening", () => {
1250
+ resolve(this.#wss.address().port);
1251
+ });
1252
+ });
1253
+ }
1135
1254
  this.#wss.on("connection", (socket) => {
1136
1255
  this.#onConnection(socket);
1137
1256
  });
@@ -1330,7 +1449,13 @@ var IrisTool = {
1330
1449
  /** Navigate the connected browser tab to a URL. */
1331
1450
  NAVIGATE: "iris_navigate",
1332
1451
  /** Reload the connected browser tab (soft or hard). */
1333
- REFRESH: "iris_refresh"
1452
+ REFRESH: "iris_refresh",
1453
+ /** Report running version, latest available, changelog, and breaking changes. */
1454
+ VERSION_INFO: "iris_version_info",
1455
+ /** Install the latest server version and restart (Claude Code reconnects automatically). */
1456
+ APPLY_UPDATE: "iris_apply_update",
1457
+ /** Restore the previous server version and restart. */
1458
+ ROLLBACK: "iris_rollback"
1334
1459
  };
1335
1460
 
1336
1461
  // ../server/dist/project/iris-dir.js
@@ -1360,11 +1485,11 @@ function isValidFlowName(name) {
1360
1485
  function baselinePath(root, name) {
1361
1486
  return join(root, IrisDir.BASELINES_SUBDIR, `${name}.json`);
1362
1487
  }
1363
- async function ensureIrisDir(fs, root) {
1488
+ async function ensureIrisDir(fs2, root) {
1364
1489
  const p = irisDirPaths(root);
1365
- await fs.mkdir(p.root);
1366
- await fs.mkdir(p.flows);
1367
- await fs.mkdir(p.baselines);
1490
+ await fs2.mkdir(p.root);
1491
+ await fs2.mkdir(p.flows);
1492
+ await fs2.mkdir(p.baselines);
1368
1493
  }
1369
1494
  var JSON_INDENT = 2;
1370
1495
  function stableSerialize(capabilities, generatedAt) {
@@ -1381,21 +1506,21 @@ function stableSerialize(capabilities, generatedAt) {
1381
1506
  return `${JSON.stringify(envelope, null, JSON_INDENT)}
1382
1507
  `;
1383
1508
  }
1384
- async function writeContract(fs, root, capabilities, now) {
1385
- await ensureIrisDir(fs, root);
1386
- await fs.writeFile(irisDirPaths(root).contract, stableSerialize(capabilities, now()));
1509
+ async function writeContract(fs2, root, capabilities, now) {
1510
+ await ensureIrisDir(fs2, root);
1511
+ await fs2.writeFile(irisDirPaths(root).contract, stableSerialize(capabilities, now()));
1387
1512
  }
1388
- async function readContract(fs, root) {
1513
+ async function readContract(fs2, root) {
1389
1514
  const path = irisDirPaths(root).contract;
1390
- if (!await fs.exists(path))
1515
+ if (!await fs2.exists(path))
1391
1516
  return { ok: false, reason: ContractReadError.MISSING };
1392
1517
  let text;
1393
1518
  try {
1394
- text = await fs.readFile(path);
1519
+ text = await fs2.readFile(path);
1395
1520
  } catch (error) {
1396
1521
  return {
1397
1522
  ok: false,
1398
- reason: fs.isNotFound(error) ? ContractReadError.MISSING : ContractReadError.MALFORMED
1523
+ reason: fs2.isNotFound(error) ? ContractReadError.MISSING : ContractReadError.MALFORMED
1399
1524
  };
1400
1525
  }
1401
1526
  let parsed;
@@ -1482,8 +1607,8 @@ var FlowStore = class {
1482
1607
  #fs;
1483
1608
  #root;
1484
1609
  #clock;
1485
- constructor(fs, root, clock) {
1486
- this.#fs = fs;
1610
+ constructor(fs2, root, clock) {
1611
+ this.#fs = fs2;
1487
1612
  this.#root = root;
1488
1613
  this.#clock = clock;
1489
1614
  }
@@ -1629,8 +1754,8 @@ var ProjectStore = class {
1629
1754
  #fs;
1630
1755
  #root;
1631
1756
  #clock;
1632
- constructor(fs, root, clock) {
1633
- this.#fs = fs;
1757
+ constructor(fs2, root, clock) {
1758
+ this.#fs = fs2;
1634
1759
  this.#root = root;
1635
1760
  this.#clock = clock;
1636
1761
  }
@@ -1809,10 +1934,10 @@ function createNodeFileSystem() {
1809
1934
 
1810
1935
  // ../server/dist/mcp.js
1811
1936
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1812
- import { z as z15 } from "zod";
1937
+ import { z as z16 } from "zod";
1813
1938
 
1814
1939
  // ../server/dist/tools/tools.js
1815
- import { z as z14 } from "zod";
1940
+ import { z as z15 } from "zod";
1816
1941
 
1817
1942
  // ../server/dist/input/real-input.js
1818
1943
  var DriveError = class extends Error {
@@ -3421,8 +3546,8 @@ async function diffPng(baselineBytes, currentBytes, opts = {}) {
3421
3546
  var VisualStore = class {
3422
3547
  #fs;
3423
3548
  #root;
3424
- constructor(fs, root) {
3425
- this.#fs = fs;
3549
+ constructor(fs2, root) {
3550
+ this.#fs = fs2;
3426
3551
  this.#root = root;
3427
3552
  }
3428
3553
  /** The absolute baseline path for `name` (for echoing back to the agent). */
@@ -4048,6 +4173,266 @@ function withControl(session, result) {
4048
4173
  return control === void 0 ? result : { ...result, control };
4049
4174
  }
4050
4175
 
4176
+ // ../server/dist/update/update-tools.js
4177
+ import { z as z14 } from "zod";
4178
+
4179
+ // ../server/dist/update/update-checker.js
4180
+ import * as fs from "fs";
4181
+ import * as https from "https";
4182
+ import { join as join2 } from "path";
4183
+ import { homedir } from "os";
4184
+ var IRIS_HOME = join2(homedir(), ".iris");
4185
+ var MANIFEST_PATH = join2(IRIS_HOME, "update-manifest.json");
4186
+ var NPM_REGISTRY = "https://registry.npmjs.org/@syrin/iris/latest";
4187
+ function loadManifest() {
4188
+ if (!fs.existsSync(MANIFEST_PATH))
4189
+ return null;
4190
+ try {
4191
+ const raw = fs.readFileSync(MANIFEST_PATH, "utf8");
4192
+ return JSON.parse(raw);
4193
+ } catch {
4194
+ return null;
4195
+ }
4196
+ }
4197
+ function saveManifest(manifest) {
4198
+ fs.mkdirSync(IRIS_HOME, { recursive: true });
4199
+ fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2), "utf8");
4200
+ }
4201
+ function isCacheFresh(manifest) {
4202
+ const checked = new Date(manifest.lastChecked).getTime();
4203
+ return Date.now() - checked < UpdateCheckIntervalMs;
4204
+ }
4205
+ function fetchNpmInfo() {
4206
+ return new Promise((resolve, reject) => {
4207
+ const req = https.get(NPM_REGISTRY, (res) => {
4208
+ let body = "";
4209
+ res.setEncoding("utf8");
4210
+ res.on("data", (chunk) => {
4211
+ body += chunk;
4212
+ });
4213
+ res.on("end", () => {
4214
+ try {
4215
+ resolve(JSON.parse(body));
4216
+ } catch (err) {
4217
+ reject(err instanceof Error ? err : new Error(String(err)));
4218
+ }
4219
+ });
4220
+ res.on("error", reject);
4221
+ });
4222
+ req.setTimeout(5e3, () => {
4223
+ req.destroy();
4224
+ reject(new Error("npm registry request timed out"));
4225
+ });
4226
+ req.on("error", reject);
4227
+ });
4228
+ }
4229
+ async function checkForUpdate(currentVersion) {
4230
+ const cached = loadManifest();
4231
+ if (cached !== null && cached.currentVersion === currentVersion && isCacheFresh(cached)) {
4232
+ return cached;
4233
+ }
4234
+ try {
4235
+ const info = await fetchNpmInfo();
4236
+ const updateAvailable = info.version !== currentVersion;
4237
+ const manifest = {
4238
+ currentVersion,
4239
+ latestVersion: info.version,
4240
+ updateAvailable,
4241
+ lastChecked: (/* @__PURE__ */ new Date()).toISOString(),
4242
+ ...info.iris?.changelog !== void 0 ? { changelog: info.iris.changelog } : {},
4243
+ ...info.iris?.breakingChanges !== void 0 ? { breakingChanges: info.iris.breakingChanges } : {},
4244
+ ...cached?.previousVersion !== void 0 ? { previousVersion: cached.previousVersion } : {}
4245
+ };
4246
+ saveManifest(manifest);
4247
+ return manifest;
4248
+ } catch (err) {
4249
+ log("iris_update_check_failed", {
4250
+ error: err instanceof Error ? err.message : String(err)
4251
+ });
4252
+ if (cached !== null)
4253
+ return { ...cached, currentVersion };
4254
+ return {
4255
+ currentVersion,
4256
+ updateAvailable: false,
4257
+ lastChecked: (/* @__PURE__ */ new Date()).toISOString()
4258
+ };
4259
+ }
4260
+ }
4261
+
4262
+ // ../server/dist/update/updater.js
4263
+ import { execFile } from "child_process";
4264
+ import { existsSync as existsSync2 } from "fs";
4265
+ import { platform } from "os";
4266
+ import { dirname, join as join3 } from "path";
4267
+ var NPM_BIN = platform() === "win32" ? "npm.cmd" : "npm";
4268
+ var NPM_TIMEOUT_MS = 12e4;
4269
+ var ExecutionKind = {
4270
+ /** Launched via `npx @syrin/iris` — npm re-resolves the package on restart. */
4271
+ NPX: "npx",
4272
+ /** Installed globally via `npm install -g`. */
4273
+ GLOBAL: "global",
4274
+ /** Installed as a local project dependency. */
4275
+ LOCAL: "local"
4276
+ };
4277
+ function detectExecutionKind() {
4278
+ const script = process.argv[1] ?? "";
4279
+ if (script.includes("/_npx/") || script.includes("\\_npx\\"))
4280
+ return ExecutionKind.NPX;
4281
+ if (script.includes("/node_modules/") || script.includes("\\node_modules\\")) {
4282
+ return ExecutionKind.LOCAL;
4283
+ }
4284
+ return ExecutionKind.GLOBAL;
4285
+ }
4286
+ function findLocalProjectRoot() {
4287
+ let dir = process.cwd();
4288
+ for (; ; ) {
4289
+ if (existsSync2(join3(dir, "package.json")))
4290
+ return dir;
4291
+ const parent = dirname(dir);
4292
+ if (parent === dir)
4293
+ return null;
4294
+ dir = parent;
4295
+ }
4296
+ }
4297
+ function runNpm(args, opts = {}) {
4298
+ return new Promise((resolve, reject) => {
4299
+ execFile(NPM_BIN, args, { timeout: NPM_TIMEOUT_MS, ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {} }, (err, _stdout, stderr) => {
4300
+ if (err !== null) {
4301
+ reject(new Error(`npm ${args.join(" ")} failed: ${stderr !== "" ? stderr : err.message}`));
4302
+ } else {
4303
+ resolve();
4304
+ }
4305
+ });
4306
+ });
4307
+ }
4308
+ async function installVersion(version, kind) {
4309
+ const pkg = `@syrin/iris@${version}`;
4310
+ if (kind === ExecutionKind.NPX) {
4311
+ log("iris_update_npx_strategy", {
4312
+ note: "Running via npx \u2014 exiting so Claude Code restarts and npx fetches the new version"
4313
+ });
4314
+ return;
4315
+ }
4316
+ if (kind === ExecutionKind.LOCAL) {
4317
+ const root = findLocalProjectRoot();
4318
+ if (root !== null) {
4319
+ await runNpm(["install", pkg], { cwd: root });
4320
+ return;
4321
+ }
4322
+ log("iris_update_local_no_root", { fallback: "global" });
4323
+ }
4324
+ await runNpm(["install", "-g", pkg]);
4325
+ }
4326
+ async function installVersionRollback(version, kind) {
4327
+ if (kind === ExecutionKind.NPX) {
4328
+ log("iris_rollback_npx_strategy", {
4329
+ note: "Running via npx \u2014 update your .mcp.json args to pin the version you want to restore"
4330
+ });
4331
+ return;
4332
+ }
4333
+ await installVersion(version, kind);
4334
+ }
4335
+ async function applyUpdate(targetVersion) {
4336
+ const manifest = loadManifest();
4337
+ if (manifest !== null) {
4338
+ saveManifest({ ...manifest, previousVersion: manifest.currentVersion });
4339
+ }
4340
+ const kind = detectExecutionKind();
4341
+ log("iris_update_applying", { version: targetVersion, executionKind: kind });
4342
+ await installVersion(targetVersion, kind);
4343
+ log("iris_update_applied", { version: targetVersion, executionKind: kind });
4344
+ process.exit(0);
4345
+ }
4346
+ async function rollback() {
4347
+ const manifest = loadManifest();
4348
+ if (manifest === null || manifest.previousVersion === void 0) {
4349
+ throw new Error("No previous version available for rollback");
4350
+ }
4351
+ const prev = manifest.previousVersion;
4352
+ const kind = detectExecutionKind();
4353
+ log("iris_rollback_applying", { version: prev, executionKind: kind });
4354
+ await installVersionRollback(prev, kind);
4355
+ log("iris_rollback_applied", { version: prev, executionKind: kind });
4356
+ process.exit(0);
4357
+ }
4358
+
4359
+ // ../server/dist/server-version.js
4360
+ import { createRequire } from "module";
4361
+ var _pkg = createRequire(import.meta.url)("../package.json");
4362
+ var SERVER_VERSION = _pkg.version;
4363
+
4364
+ // ../server/dist/update/update-tools.js
4365
+ var UPDATE_TOOLS = [
4366
+ {
4367
+ name: IrisTool.VERSION_INFO,
4368
+ description: "Returns the running Iris version, latest available version, release changelog, and any breaking changes. Call this at the start of a session or when unexpected tool behavior suggests a version mismatch.",
4369
+ inputSchema: {},
4370
+ outputSchema: {
4371
+ currentVersion: z14.string().describe("The Iris server version currently running."),
4372
+ latestVersion: z14.string().optional().describe("Latest published version on npm."),
4373
+ updateAvailable: z14.boolean().describe("True when a newer version is available to install."),
4374
+ executionKind: z14.string().describe('How iris was launched: "npx" (no install needed \u2014 restart applies update), "global" (npm install -g), or "local" (project node_modules).'),
4375
+ changelog: z14.string().optional().describe("Release notes for the latest version."),
4376
+ breakingChanges: z14.array(z14.string()).optional().describe("Breaking changes in the latest version that may affect your scripts."),
4377
+ rollbackAvailable: z14.boolean().describe("True when a previous version is stored and can be restored."),
4378
+ previousVersion: z14.string().optional().describe("The version that would be restored on rollback.")
4379
+ },
4380
+ handler: async (_deps) => {
4381
+ const manifest = await checkForUpdate(SERVER_VERSION);
4382
+ return {
4383
+ currentVersion: manifest.currentVersion,
4384
+ ...manifest.latestVersion !== void 0 ? { latestVersion: manifest.latestVersion } : {},
4385
+ updateAvailable: manifest.updateAvailable,
4386
+ executionKind: detectExecutionKind(),
4387
+ ...manifest.changelog !== void 0 ? { changelog: manifest.changelog } : {},
4388
+ ...manifest.breakingChanges !== void 0 ? { breakingChanges: manifest.breakingChanges } : {},
4389
+ rollbackAvailable: manifest.previousVersion !== void 0,
4390
+ ...manifest.previousVersion !== void 0 ? { previousVersion: manifest.previousVersion } : {}
4391
+ };
4392
+ }
4393
+ },
4394
+ {
4395
+ name: IrisTool.APPLY_UPDATE,
4396
+ description: 'Install the latest Iris server version and restart. Strategy depends on how iris was launched (check executionKind from iris_version_info): "global" and "local" installs run npm install then exit; "npx" just exits \u2014 Claude Code restarts and npx re-resolves the latest version from npm automatically. The MCP connection briefly drops during restart.',
4397
+ inputSchema: {
4398
+ confirm: z14.boolean().describe("Set to true to confirm the update should be applied. Required to prevent accidental upgrades.")
4399
+ },
4400
+ outputSchema: {
4401
+ ok: z14.boolean(),
4402
+ message: z14.string().optional()
4403
+ },
4404
+ handler: async (_deps, args) => {
4405
+ if (args["confirm"] !== true) {
4406
+ return { ok: false, message: "Set confirm:true to apply the update" };
4407
+ }
4408
+ const manifest = await checkForUpdate(SERVER_VERSION);
4409
+ if (!manifest.updateAvailable || manifest.latestVersion === void 0) {
4410
+ return { ok: false, message: "No update available \u2014 already on the latest version" };
4411
+ }
4412
+ await applyUpdate(manifest.latestVersion);
4413
+ return { ok: true };
4414
+ }
4415
+ },
4416
+ {
4417
+ name: IrisTool.ROLLBACK,
4418
+ description: "Restore the previous Iris server version and restart. Use when an update introduced a regression. The MCP connection will briefly drop \u2014 Claude Code restarts the process automatically with the restored binary.",
4419
+ inputSchema: {
4420
+ confirm: z14.boolean().describe("Set to true to confirm the rollback. Required to prevent accidental downgrades.")
4421
+ },
4422
+ outputSchema: {
4423
+ ok: z14.boolean(),
4424
+ message: z14.string().optional()
4425
+ },
4426
+ handler: async (_deps, args) => {
4427
+ if (args["confirm"] !== true) {
4428
+ return { ok: false, message: "Set confirm:true to apply the rollback" };
4429
+ }
4430
+ await rollback();
4431
+ return { ok: true };
4432
+ }
4433
+ }
4434
+ ];
4435
+
4051
4436
  // ../server/dist/tools/tools.js
4052
4437
  async function snapshotTree(deps, sessionId) {
4053
4438
  const session = deps.sessions.resolve(sessionId);
@@ -4058,7 +4443,7 @@ async function snapshotTree(deps, sessionId) {
4058
4443
  return { lines: normalizeLines(snap.tree ?? ""), route: snap.status?.route ?? "" };
4059
4444
  }
4060
4445
  var sessionIdShape6 = {
4061
- sessionId: z14.string().optional().describe("Active session ID from iris_sessions. Omit when only one browser session is open \u2014 Iris resolves it automatically.")
4446
+ sessionId: z15.string().optional().describe("Active session ID from iris_sessions. Omit when only one browser session is open \u2014 Iris resolves it automatically.")
4062
4447
  };
4063
4448
  async function commandOrThrow3(deps, sessionId, name, args) {
4064
4449
  const session = deps.sessions.resolve(sessionId);
@@ -4135,17 +4520,17 @@ var TOOLS = [
4135
4520
  description: "List connected browser sessions (tab url/title, sessionId, last-seen, health: hidden/focused/throttled, and `realInputAvailable` \u2014 true when native CDP/launched real input is driving this tab), plus a `recommendation` pointing to `iris drive` when a tab is hidden/throttled and may be un-scriptable from here.",
4136
4521
  inputSchema: {},
4137
4522
  outputSchema: {
4138
- sessions: z14.array(z14.object({
4139
- sessionId: z14.string(),
4140
- url: z14.string(),
4141
- title: z14.string().optional(),
4142
- lastSeenMs: z14.number(),
4143
- throttled: z14.boolean(),
4144
- focused: z14.boolean(),
4145
- hidden: z14.boolean(),
4146
- realInputAvailable: z14.boolean().optional(),
4147
- stale: z14.boolean().optional(),
4148
- recommendation: z14.string().optional()
4523
+ sessions: z15.array(z15.object({
4524
+ sessionId: z15.string(),
4525
+ url: z15.string(),
4526
+ title: z15.string().optional(),
4527
+ lastSeenMs: z15.number(),
4528
+ throttled: z15.boolean(),
4529
+ focused: z15.boolean(),
4530
+ hidden: z15.boolean(),
4531
+ realInputAvailable: z15.boolean().optional(),
4532
+ stale: z15.boolean().optional(),
4533
+ recommendation: z15.string().optional()
4149
4534
  })).describe("Connected browser sessions with health state.")
4150
4535
  },
4151
4536
  handler: async (deps) => {
@@ -4161,13 +4546,13 @@ var TOOLS = [
4161
4546
  name: IrisTool.SNAPSHOT,
4162
4547
  description: "Semantic accessibility snapshot of the page or a subtree. mode: full|interactive|status. Use to see what is on screen right now.",
4163
4548
  inputSchema: {
4164
- scope: z14.string().optional().describe("CSS selector or element ref to restrict the snapshot to a subtree. Omit to snapshot the whole page."),
4165
- mode: z14.nativeEnum(SnapshotMode).optional().describe("full = all elements; interactive = only clickable/focusable elements; status = only route + title. Default: full."),
4549
+ scope: z15.string().optional().describe("CSS selector or element ref to restrict the snapshot to a subtree. Omit to snapshot the whole page."),
4550
+ mode: z15.nativeEnum(SnapshotMode).optional().describe("full = all elements; interactive = only clickable/focusable elements; status = only route + title. Default: full."),
4166
4551
  ...sessionIdShape6
4167
4552
  },
4168
4553
  outputSchema: {
4169
- tree: z14.string().optional().describe("Indented ARIA tree of every element on the page (or the scoped subtree)."),
4170
- status: z14.object({ route: z14.string(), title: z14.string().optional() }).optional()
4554
+ tree: z15.string().optional().describe("Indented ARIA tree of every element on the page (or the scoped subtree)."),
4555
+ status: z15.object({ route: z15.string(), title: z15.string().optional() }).optional()
4171
4556
  },
4172
4557
  handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.SNAPSHOT, {
4173
4558
  scope: args["scope"],
@@ -4178,25 +4563,25 @@ var TOOLS = [
4178
4563
  name: IrisTool.QUERY,
4179
4564
  description: "Find elements by Testing-Library semantics. Pass `by` (role|text|label|placeholder|testid|alt) and `value` (the query string). Returns matching refs + descriptors + visibility. On zero matches, also returns hint:{ route, presentTestids[], knownEmptyState } so you can distinguish an empty state from a missing element WITHOUT taking a snapshot.",
4180
4565
  inputSchema: {
4181
- by: z14.string().describe("Query strategy: role | text | label | placeholder | testid | alt"),
4182
- value: z14.string().describe("Query value for the selected strategy (e.g. by=role value=button, or by=testid value=submit-btn)."),
4183
- name: z14.string().optional().describe("Accessible name filter \u2014 narrows results when `by` is role and the page has many elements of that role."),
4184
- scope: z14.string().optional().describe("CSS selector or element ref to restrict the search to a subtree."),
4566
+ by: z15.string().describe("Query strategy: role | text | label | placeholder | testid | alt"),
4567
+ value: z15.string().describe("Query value for the selected strategy (e.g. by=role value=button, or by=testid value=submit-btn)."),
4568
+ name: z15.string().optional().describe("Accessible name filter \u2014 narrows results when `by` is role and the page has many elements of that role."),
4569
+ scope: z15.string().optional().describe("CSS selector or element ref to restrict the search to a subtree."),
4185
4570
  ...sessionIdShape6
4186
4571
  },
4187
4572
  outputSchema: {
4188
- elements: z14.array(z14.object({
4189
- ref: z14.string(),
4190
- role: z14.string(),
4191
- name: z14.string(),
4192
- value: z14.string().optional(),
4193
- states: z14.array(z14.string()),
4194
- visible: z14.boolean()
4573
+ elements: z15.array(z15.object({
4574
+ ref: z15.string(),
4575
+ role: z15.string(),
4576
+ name: z15.string(),
4577
+ value: z15.string().optional(),
4578
+ states: z15.array(z15.string()),
4579
+ visible: z15.boolean()
4195
4580
  })),
4196
- hint: z14.object({
4197
- route: z14.string(),
4198
- presentTestids: z14.array(z14.string()),
4199
- knownEmptyState: z14.boolean()
4581
+ hint: z15.object({
4582
+ route: z15.string(),
4583
+ presentTestids: z15.array(z15.string()),
4584
+ knownEmptyState: z15.boolean()
4200
4585
  }).optional().describe("Present only on zero matches \u2014 tells you what IS on the page so you can diagnose the miss.")
4201
4586
  },
4202
4587
  handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.QUERY, {
@@ -4210,18 +4595,18 @@ var TOOLS = [
4210
4595
  name: IrisTool.INSPECT,
4211
4596
  description: "Deep info on one element by ref: full a11y props, visibility, box, and (with @syrin/iris-react) component stack + source file.",
4212
4597
  inputSchema: {
4213
- ref: z14.string().describe("Element ref from iris_snapshot or iris_query (e.g. 'e42')."),
4598
+ ref: z15.string().describe("Element ref from iris_snapshot or iris_query (e.g. 'e42')."),
4214
4599
  ...sessionIdShape6
4215
4600
  },
4216
4601
  outputSchema: {
4217
- ref: z14.string(),
4218
- role: z14.string(),
4219
- name: z14.string(),
4220
- value: z14.string().optional(),
4221
- states: z14.array(z14.string()),
4222
- visible: z14.boolean(),
4223
- box: z14.object({ x: z14.number(), y: z14.number(), width: z14.number(), height: z14.number() }).optional(),
4224
- component: z14.object({ name: z14.string(), sourceFile: z14.string().optional() }).optional()
4602
+ ref: z15.string(),
4603
+ role: z15.string(),
4604
+ name: z15.string(),
4605
+ value: z15.string().optional(),
4606
+ states: z15.array(z15.string()),
4607
+ visible: z15.boolean(),
4608
+ box: z15.object({ x: z15.number(), y: z15.number(), width: z15.number(), height: z15.number() }).optional(),
4609
+ component: z15.object({ name: z15.string().optional(), sourceFile: z15.string().optional() }).optional()
4225
4610
  },
4226
4611
  handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.INSPECT, {
4227
4612
  ref: args["ref"]
@@ -4231,19 +4616,19 @@ var TOOLS = [
4231
4616
  name: IrisTool.ACT,
4232
4617
  description: 'Execute one action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView. Returns immediately with a `since` cursor \u2014 observe the reaction with iris_observe. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. occluded=true means the click point is covered by another element (a real user could not click it) \u2014 synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured \u2014 pass args.native:true to force a trusted native click (file pickers, clipboard). inputModeReason explains any real\u2192synthetic choice so it is never silent. Full model (real-input, throttled tabs, `iris drive`): docs/usage.md \xA718.',
4233
4618
  inputSchema: {
4234
- ref: z14.string().describe("Element ref from iris_snapshot or iris_query (e.g. 'e42')."),
4235
- action: z14.string().describe("Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView"),
4236
- args: z14.record(z14.unknown()).optional().describe("Action-specific arguments: { value } for fill/select, { text } for type/press, { native: true } to force a trusted native click."),
4237
- refuseWhenThrottled: z14.boolean().optional().describe("Throw instead of silently sending synthetic events when the tab is throttled/backgrounded. Default: false (synthetic events are still sent)."),
4619
+ ref: z15.string().describe("Element ref from iris_snapshot or iris_query (e.g. 'e42')."),
4620
+ action: z15.string().describe("Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView"),
4621
+ args: z15.record(z15.unknown()).optional().describe("Action-specific arguments: { value } for fill/select, { text } for type/press, { native: true } to force a trusted native click."),
4622
+ refuseWhenThrottled: z15.boolean().optional().describe("Throw instead of silently sending synthetic events when the tab is throttled/backgrounded. Default: false (synthetic events are still sent)."),
4238
4623
  ...sessionIdShape6
4239
4624
  },
4240
4625
  outputSchema: {
4241
- since: z14.number().describe("Cursor \u2014 pass to iris_observe/iris_wait_for/iris_assert to scope reaction queries to this act."),
4242
- dispatched: z14.boolean(),
4243
- settled: z14.boolean().nullable(),
4244
- inputMode: z14.string(),
4245
- result: z14.unknown().optional(),
4246
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4626
+ since: z15.number().describe("Cursor \u2014 pass to iris_observe/iris_wait_for/iris_assert to scope reaction queries to this act."),
4627
+ dispatched: z15.boolean(),
4628
+ settled: z15.boolean().nullable(),
4629
+ inputMode: z15.string(),
4630
+ result: z15.unknown().optional(),
4631
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4247
4632
  },
4248
4633
  handler: async (deps, args) => {
4249
4634
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4299,14 +4684,14 @@ var TOOLS = [
4299
4684
  name: IrisTool.ACT_SEQUENCE,
4300
4685
  description: "Run multiple actions in order (fill -> fill -> submit) in one round-trip. Returns per-step effects[] (see iris_act).",
4301
4686
  inputSchema: {
4302
- steps: z14.array(z14.record(z14.unknown())).describe("Ordered list of { ref, action, args? } objects. Each step is equivalent to one iris_act call."),
4687
+ steps: z15.array(z15.record(z15.unknown())).describe("Ordered list of { ref, action, args? } objects. Each step is equivalent to one iris_act call."),
4303
4688
  ...sessionIdShape6
4304
4689
  },
4305
4690
  outputSchema: {
4306
- since: z14.number(),
4307
- dispatched: z14.boolean(),
4308
- result: z14.unknown().optional(),
4309
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4691
+ since: z15.number(),
4692
+ dispatched: z15.boolean(),
4693
+ result: z15.unknown().optional(),
4694
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4310
4695
  },
4311
4696
  handler: async (deps, args) => {
4312
4697
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4334,23 +4719,23 @@ var TOOLS = [
4334
4719
  name: IrisTool.ACT_AND_WAIT,
4335
4720
  description: "Act on a ref, then wait for a predicate to hold \u2014 one hop for the act->observe->assert loop. Returns { effect } (the action result), { verdict } (predicate pass/evidence/near-miss), and { trace } (the reaction report of everything the app did after the action). timeout_ms 0 evaluates the predicate once without waiting.",
4336
4721
  inputSchema: {
4337
- ref: z14.string().describe("Element ref from iris_snapshot or iris_query."),
4338
- action: z14.string().describe("Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView"),
4339
- args: z14.record(z14.unknown()).optional().describe("Action-specific arguments: { value } for fill/select, { text } for type/press."),
4722
+ ref: z15.string().describe("Element ref from iris_snapshot or iris_query."),
4723
+ action: z15.string().describe("Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView"),
4724
+ args: z15.record(z15.unknown()).optional().describe("Action-specific arguments: { value } for fill/select, { text } for type/press."),
4340
4725
  until: PredicateSchema.describe("Predicate to wait for after the action completes. Same shape accepted by iris_assert."),
4341
- timeout_ms: z14.number().optional().describe("Maximum wait time in milliseconds. 0 = evaluate once without waiting. Default: 4000."),
4342
- refuseWhenThrottled: z14.boolean().optional().describe("Throw if the tab is throttled. Default: false."),
4726
+ timeout_ms: z15.number().optional().describe("Maximum wait time in milliseconds. 0 = evaluate once without waiting. Default: 4000."),
4727
+ refuseWhenThrottled: z15.boolean().optional().describe("Throw if the tab is throttled. Default: false."),
4343
4728
  ...sessionIdShape6
4344
4729
  },
4345
4730
  outputSchema: {
4346
- effect: z14.unknown().describe("The iris_act result (dispatched, settled, inputMode, etc.)."),
4347
- verdict: z14.object({
4348
- pass: z14.boolean(),
4349
- evidence: z14.unknown().optional(),
4350
- failureReason: z14.string().optional()
4731
+ effect: z15.unknown().describe("The iris_act result (dispatched, settled, inputMode, etc.)."),
4732
+ verdict: z15.object({
4733
+ pass: z15.boolean(),
4734
+ evidence: z15.unknown().optional(),
4735
+ failureReason: z15.string().optional()
4351
4736
  }),
4352
- trace: z14.unknown().describe("Reaction report (same shape as iris_observe summary)."),
4353
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4737
+ trace: z15.unknown().describe("Reaction report (same shape as iris_observe summary)."),
4738
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4354
4739
  },
4355
4740
  handler: async (deps, args) => {
4356
4741
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4383,31 +4768,31 @@ var TOOLS = [
4383
4768
  name: IrisTool.OBSERVE,
4384
4769
  description: "Return the timeline of everything the app did in a window (DOM/network/route/console/animation/signal), with a summary. Use after an action. Pass `max_events` to cap the timeline to the most recent N (older events are dropped and counted in cost.droppedOldest). Every result carries a `cost:{events,bytes}` hint so you can self-budget your next call.",
4385
4770
  inputSchema: {
4386
- window_ms: z14.number().optional().describe("Time window to look back. Default: 2000ms. Ignored when `since` is provided."),
4387
- since: z14.number().optional().describe("Cursor from a prior iris_act or iris_observe call. Scopes the event window to exactly that span."),
4388
- filters: z14.array(z14.string()).optional().describe("Event type allowlist: dom | net | route | console | animation | signal. Omit to return all types."),
4389
- max_events: z14.number().optional().describe("Cap the timeline to the most recent N events. Older events are counted in cost.droppedOldest."),
4771
+ window_ms: z15.number().optional().describe("Time window to look back. Default: 2000ms. Ignored when `since` is provided."),
4772
+ since: z15.number().optional().describe("Cursor from a prior iris_act or iris_observe call. Scopes the event window to exactly that span."),
4773
+ filters: z15.array(z15.string()).optional().describe("Event type allowlist: dom | net | route | console | animation | signal. Omit to return all types."),
4774
+ max_events: z15.number().optional().describe("Cap the timeline to the most recent N events. Older events are counted in cost.droppedOldest."),
4390
4775
  ...sessionIdShape6
4391
4776
  },
4392
4777
  outputSchema: {
4393
- events: z14.array(z14.unknown()),
4394
- summary: z14.object({
4395
- total: z14.number(),
4396
- network: z14.number(),
4397
- domAdded: z14.number(),
4398
- domRemoved: z14.number(),
4399
- domChanged: z14.number(),
4400
- routeChanges: z14.number(),
4401
- consoleErrors: z14.number(),
4402
- animations: z14.number(),
4403
- signals: z14.number()
4778
+ events: z15.array(z15.unknown()),
4779
+ summary: z15.object({
4780
+ total: z15.number(),
4781
+ network: z15.number(),
4782
+ domAdded: z15.number(),
4783
+ domRemoved: z15.number(),
4784
+ domChanged: z15.number(),
4785
+ routeChanges: z15.number(),
4786
+ consoleErrors: z15.number(),
4787
+ animations: z15.number(),
4788
+ signals: z15.number()
4404
4789
  }),
4405
- cost: z14.object({
4406
- events: z14.number(),
4407
- bytes: z14.number(),
4408
- droppedOldest: z14.number().optional()
4790
+ cost: z15.object({
4791
+ events: z15.number(),
4792
+ bytes: z15.number(),
4793
+ droppedOldest: z15.number().optional()
4409
4794
  }),
4410
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4795
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4411
4796
  },
4412
4797
  handler: (deps, args) => {
4413
4798
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4430,15 +4815,15 @@ var TOOLS = [
4430
4815
  description: "Block until a predicate is satisfied (or already true in the recent buffer), else time out. Returns matching evidence or a near-miss diagnosis. By default it only counts events since your last act, so a signal buffered BEFORE the action can never fake a pass; pass `since` (an observe/act cursor) to widen or narrow that window explicitly.",
4431
4816
  inputSchema: {
4432
4817
  predicate: PredicateSchema.describe("Predicate to wait for: { signal }, { net }, { element } or a combination."),
4433
- timeout_ms: z14.number().optional().describe("Maximum wait in milliseconds. Default: 4000."),
4434
- since: z14.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the wait to events after that act."),
4818
+ timeout_ms: z15.number().optional().describe("Maximum wait in milliseconds. Default: 4000."),
4819
+ since: z15.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the wait to events after that act."),
4435
4820
  ...sessionIdShape6
4436
4821
  },
4437
4822
  outputSchema: {
4438
- pass: z14.boolean(),
4439
- evidence: z14.unknown().optional(),
4440
- failureReason: z14.string().optional(),
4441
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4823
+ pass: z15.boolean(),
4824
+ evidence: z15.unknown().optional(),
4825
+ failureReason: z15.string().optional(),
4826
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4442
4827
  },
4443
4828
  handler: async (deps, args) => {
4444
4829
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4453,15 +4838,15 @@ var TOOLS = [
4453
4838
  description: "Evaluate a predicate (optionally waiting up to timeout_ms). Returns { pass, evidence, failureReason? }. The end of every verify loop. By default it only counts events since your last act, so a stale buffered signal can never fake a pass; pass `since` (an observe/act cursor) to set the window explicitly.",
4454
4839
  inputSchema: {
4455
4840
  predicate: PredicateSchema.describe("Predicate to evaluate: { signal }, { net }, { element } or a combination."),
4456
- timeout_ms: z14.number().optional().describe("If > 0, wait up to this many milliseconds before failing. Default: 0 (evaluate once)."),
4457
- since: z14.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the assertion to events after that act."),
4841
+ timeout_ms: z15.number().optional().describe("If > 0, wait up to this many milliseconds before failing. Default: 0 (evaluate once)."),
4842
+ since: z15.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the assertion to events after that act."),
4458
4843
  ...sessionIdShape6
4459
4844
  },
4460
4845
  outputSchema: {
4461
- pass: z14.boolean(),
4462
- evidence: z14.unknown().optional(),
4463
- failureReason: z14.string().optional(),
4464
- session: z14.object({ lastSeenMs: z14.number(), throttled: z14.boolean(), focused: z14.boolean() }).optional()
4846
+ pass: z15.boolean(),
4847
+ evidence: z15.unknown().optional(),
4848
+ failureReason: z15.string().optional(),
4849
+ session: z15.object({ lastSeenMs: z15.number(), throttled: z15.boolean(), focused: z15.boolean() }).optional()
4465
4850
  },
4466
4851
  handler: async (deps, args) => {
4467
4852
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4476,15 +4861,15 @@ var TOOLS = [
4476
4861
  name: IrisTool.NETWORK,
4477
4862
  description: 'Filtered list of network calls. Fast path for "did POST /x return 200?". A zero-match filter returns a `hint` { totalInWindow, present[] } of the calls that DID fire, so a miss is diagnosable.',
4478
4863
  inputSchema: {
4479
- since: z14.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the query to requests fired after that act."),
4480
- method: z14.string().optional().describe("HTTP method filter: GET | POST | PUT | DELETE | PATCH etc."),
4481
- urlContains: z14.string().optional().describe("Substring that the request URL must contain."),
4482
- status: z14.number().optional().describe("HTTP status code filter (e.g. 200, 404, 500)."),
4864
+ since: z15.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the query to requests fired after that act."),
4865
+ method: z15.string().optional().describe("HTTP method filter: GET | POST | PUT | DELETE | PATCH etc."),
4866
+ urlContains: z15.string().optional().describe("Substring that the request URL must contain."),
4867
+ status: z15.number().optional().describe("HTTP status code filter (e.g. 200, 404, 500)."),
4483
4868
  ...sessionIdShape6
4484
4869
  },
4485
4870
  outputSchema: {
4486
- calls: z14.array(z14.unknown()),
4487
- hint: z14.object({ totalInWindow: z14.number(), present: z14.array(z14.string()) }).optional()
4871
+ calls: z15.array(z15.unknown()),
4872
+ hint: z15.object({ totalInWindow: z15.number(), present: z15.array(z15.string()) }).optional()
4488
4873
  },
4489
4874
  handler: (deps, args) => {
4490
4875
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4504,13 +4889,13 @@ var TOOLS = [
4504
4889
  name: IrisTool.CONSOLE,
4505
4890
  description: 'Console/error log. Fast path for "were there any errors during this flow?". When a level filter matches nothing, returns a `hint` { totalInWindow, byLevel } so 0 errors is distinguishable from a silent page.',
4506
4891
  inputSchema: {
4507
- level: z14.string().optional().describe("Log level filter: error | warn | info | log. Omit to return all levels."),
4508
- since: z14.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the query to log entries after that act."),
4892
+ level: z15.string().optional().describe("Log level filter: error | warn | info | log. Omit to return all levels."),
4893
+ since: z15.number().optional().describe("Cursor from a prior iris_act \u2014 scopes the query to log entries after that act."),
4509
4894
  ...sessionIdShape6
4510
4895
  },
4511
4896
  outputSchema: {
4512
- logs: z14.array(z14.unknown()),
4513
- hint: z14.object({ totalInWindow: z14.number(), byLevel: z14.record(z14.number()) }).optional()
4897
+ logs: z15.array(z15.unknown()),
4898
+ hint: z15.object({ totalInWindow: z15.number(), byLevel: z15.record(z15.number()) }).optional()
4514
4899
  },
4515
4900
  handler: (deps, args) => {
4516
4901
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4529,7 +4914,7 @@ var TOOLS = [
4529
4914
  description: "Currently running + recently completed animations with targets/timing.",
4530
4915
  inputSchema: { ...sessionIdShape6 },
4531
4916
  outputSchema: {
4532
- animations: z14.array(z14.unknown())
4917
+ animations: z15.array(z15.unknown())
4533
4918
  },
4534
4919
  handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.ANIMATIONS, {})
4535
4920
  },
@@ -4537,12 +4922,12 @@ var TOOLS = [
4537
4922
  name: IrisTool.BASELINE_SAVE,
4538
4923
  description: "Snapshot the current semantic state under a name, to diff against later (regression detection).",
4539
4924
  inputSchema: {
4540
- name: z14.string().describe('Label for this baseline snapshot (e.g. "dashboard-initial"). Use the same name in iris_diff to compare.'),
4925
+ name: z15.string().describe('Label for this baseline snapshot (e.g. "dashboard-initial"). Use the same name in iris_diff to compare.'),
4541
4926
  ...sessionIdShape6
4542
4927
  },
4543
4928
  outputSchema: {
4544
- baseline: z14.string().describe("Saved baseline name \u2014 pass to iris_diff to compare."),
4545
- lineCount: z14.number()
4929
+ baseline: z15.string().describe("Saved baseline name \u2014 pass to iris_diff to compare."),
4930
+ lineCount: z15.number()
4546
4931
  },
4547
4932
  handler: async (deps, args) => {
4548
4933
  const name = asString4(args["name"]) ?? "default";
@@ -4556,7 +4941,7 @@ var TOOLS = [
4556
4941
  description: "List saved baseline names.",
4557
4942
  inputSchema: {},
4558
4943
  outputSchema: {
4559
- baselines: z14.array(z14.string())
4944
+ baselines: z15.array(z15.string())
4560
4945
  },
4561
4946
  handler: (deps) => Promise.resolve({ baselines: deps.baselines.list() })
4562
4947
  },
@@ -4564,15 +4949,15 @@ var TOOLS = [
4564
4949
  name: IrisTool.DIFF,
4565
4950
  description: 'Diff current semantic state vs a saved baseline: REMOVED/ADDED elements + console-error count. Call iris_baseline_list to list saved baselines, iris_baseline_save to create one. Pass `baseline` (name from iris_baseline_list). Answers "did anything silently go missing/break?".',
4566
4951
  inputSchema: {
4567
- baseline: z14.string().describe("Baseline name to compare against. Call iris_baseline_list to get available names; names are created by iris_baseline_save."),
4952
+ baseline: z15.string().describe("Baseline name to compare against. Call iris_baseline_list to get available names; names are created by iris_baseline_save."),
4568
4953
  ...sessionIdShape6
4569
4954
  },
4570
4955
  outputSchema: {
4571
- baseline: z14.string(),
4572
- removed: z14.array(z14.string()),
4573
- added: z14.array(z14.string()),
4574
- consoleErrors: z14.number(),
4575
- routeChanged: z14.boolean()
4956
+ baseline: z15.string(),
4957
+ removed: z15.array(z15.string()),
4958
+ added: z15.array(z15.string()),
4959
+ consoleErrors: z15.number(),
4960
+ routeChanged: z15.boolean()
4576
4961
  },
4577
4962
  handler: async (deps, args) => {
4578
4963
  const name = asString4(args["baseline"]) ?? "default";
@@ -4590,12 +4975,12 @@ var TOOLS = [
4590
4975
  name: IrisTool.RECORD_START,
4591
4976
  description: "Start recording the event timeline under a name (for replay / a flow report).",
4592
4977
  inputSchema: {
4593
- recordingName: z14.string().describe("Identifier for this recording. Pass the same name to iris_record_stop and iris_replay."),
4978
+ recordingName: z15.string().describe("Identifier for this recording. Pass the same name to iris_record_stop and iris_replay."),
4594
4979
  ...sessionIdShape6
4595
4980
  },
4596
4981
  outputSchema: {
4597
- recordingName: z14.string(),
4598
- since: z14.number()
4982
+ recordingName: z15.string(),
4983
+ since: z15.number()
4599
4984
  },
4600
4985
  handler: (deps, args) => {
4601
4986
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4609,13 +4994,13 @@ var TOOLS = [
4609
4994
  name: IrisTool.RECORD_STOP,
4610
4995
  description: "Stop the recording identified by `recordingName` and return both the reaction report for the span and a compiled, replayable { program: { version, steps:[{tool,args,stable}] } } of the agent acts captured during it.",
4611
4996
  inputSchema: {
4612
- recordingName: z14.string().describe("Identifier of an active recording started with iris_record_start."),
4997
+ recordingName: z15.string().describe("Identifier of an active recording started with iris_record_start."),
4613
4998
  ...sessionIdShape6
4614
4999
  },
4615
5000
  outputSchema: {
4616
- recordingName: z14.string(),
4617
- program: z14.unknown(),
4618
- warning: z14.string().optional()
5001
+ recordingName: z15.string(),
5002
+ program: z15.unknown(),
5003
+ warning: z15.string().optional()
4619
5004
  },
4620
5005
  handler: (deps, args) => {
4621
5006
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4647,17 +5032,17 @@ var TOOLS = [
4647
5032
  name: IrisTool.REPLAY,
4648
5033
  description: "Re-execute a previously recorded program by recordingName. Re-resolves each step to its element by testid (falling back to the stored ref for unstable steps) and runs the actions in order against the live session. Stops at the first failure. Returns { ok, steps:[{tool,ok,error?,note?}] }.",
4649
5034
  inputSchema: {
4650
- recordingName: z14.string().describe("Name of a compiled recording (from iris_record_stop) to re-execute."),
5035
+ recordingName: z15.string().describe("Name of a compiled recording (from iris_record_stop) to re-execute."),
4651
5036
  ...sessionIdShape6
4652
5037
  },
4653
5038
  outputSchema: {
4654
- recordingName: z14.string(),
4655
- ok: z14.boolean(),
4656
- steps: z14.array(z14.object({
4657
- tool: z14.string(),
4658
- ok: z14.boolean(),
4659
- error: z14.string().optional(),
4660
- note: z14.string().optional()
5039
+ recordingName: z15.string(),
5040
+ ok: z15.boolean(),
5041
+ steps: z15.array(z15.object({
5042
+ tool: z15.string(),
5043
+ ok: z15.boolean(),
5044
+ error: z15.string().optional(),
5045
+ note: z15.string().optional()
4661
5046
  }))
4662
5047
  },
4663
5048
  handler: async (deps, args) => {
@@ -4675,30 +5060,31 @@ var TOOLS = [
4675
5060
  name: IrisTool.NARRATE,
4676
5061
  description: "Narrate your intent on the page (presenter HUD) so the human watching sees what you are about to do and why. Use a short sentence before a meaningful action.",
4677
5062
  inputSchema: {
4678
- text: z14.string().describe("Short sentence describing your next action, shown on the presenter HUD for the developer watching."),
4679
- level: z14.string().optional().describe("Display severity: info | warn | error. Default: info."),
5063
+ text: z15.string().describe("Short sentence describing your next action, shown on the presenter HUD for the developer watching."),
5064
+ level: z15.string().optional().describe("Display severity: info | warn | error. Default: info."),
4680
5065
  ...sessionIdShape6
4681
5066
  },
4682
- outputSchema: {
4683
- ok: z14.boolean()
4684
- },
4685
- handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.NARRATE, {
4686
- text: args["text"],
4687
- level: args["level"]
4688
- })
5067
+ outputSchema: { ok: z15.boolean() },
5068
+ handler: async (deps, args) => {
5069
+ const result = await commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.NARRATE, {
5070
+ text: args["text"],
5071
+ level: args["level"]
5072
+ });
5073
+ return { ok: true, ...result };
5074
+ }
4689
5075
  },
4690
5076
  {
4691
5077
  name: IrisTool.CLOCK,
4692
5078
  description: "Control a fake clock: { freeze:true } to freeze time, { advanceMs:N } to fast-forward timers (toasts, debounces, auto-dismiss), { reset:true } to restore. Lets you test time-gated UI deterministically.",
4693
5079
  inputSchema: {
4694
- freeze: z14.boolean().optional().describe("Freeze the fake clock. Time stops advancing until advanceMs or reset."),
4695
- advanceMs: z14.number().optional().describe("Fast-forward time by this many milliseconds \u2014 triggers debounces, toasts, auto-dismiss timers."),
4696
- reset: z14.boolean().optional().describe("Restore the real clock."),
5080
+ freeze: z15.boolean().optional().describe("Freeze the fake clock. Time stops advancing until advanceMs or reset."),
5081
+ advanceMs: z15.number().optional().describe("Fast-forward time by this many milliseconds \u2014 triggers debounces, toasts, auto-dismiss timers."),
5082
+ reset: z15.boolean().optional().describe("Restore the real clock."),
4697
5083
  ...sessionIdShape6
4698
5084
  },
4699
5085
  outputSchema: {
4700
- ok: z14.boolean(),
4701
- elapsed: z14.number().optional()
5086
+ ok: z15.boolean().optional(),
5087
+ elapsed: z15.number().optional()
4702
5088
  },
4703
5089
  handler: (deps, args) => commandOrThrow3(deps, asString4(args["sessionId"]), IrisCommand.CLOCK, {
4704
5090
  freeze: args["freeze"],
@@ -4710,18 +5096,18 @@ var TOOLS = [
4710
5096
  name: IrisTool.STATE,
4711
5097
  description: "Read live framework state without the app pre-broadcasting it. PREFERRED/RELIABLE: `store` reads a registered store (e.g. 'workspace'); omit `store` to read all stores. To avoid paying for a huge store, scope the read: `path` extracts a dot-path sub-tree (e.g. 'captionCache.v3', with numeric array indices), and `depth` collapses anything deeper than N levels to a size marker. A wrong `path` returns { found:false, availableKeys } so it is diagnosable. `ref` attempts a best-effort read of the nearest React component's hook state and is BOUNDED \u2014 on failure it returns component: { ok: false, reason: 'component-state-unavailable' }. Without path/depth: returns { stores, storeNames, component? }.",
4712
5098
  inputSchema: {
4713
- ref: z14.string().optional().describe("Element ref \u2014 attempts a best-effort read of the nearest React component's hook state."),
4714
- store: z14.string().optional().describe("Registered store name (e.g. 'workspace'). Omit to read all stores."),
4715
- path: z14.string().optional().describe("Dot-path into the store (e.g. 'captionCache.v3'). Numeric array indices are supported."),
4716
- depth: z14.number().optional().describe("Collapse anything deeper than N levels to a size marker \u2014 avoids huge outputs for large stores."),
5099
+ ref: z15.string().optional().describe("Element ref \u2014 attempts a best-effort read of the nearest React component's hook state."),
5100
+ store: z15.string().optional().describe("Registered store name (e.g. 'workspace'). Omit to read all stores."),
5101
+ path: z15.string().optional().describe("Dot-path into the store (e.g. 'captionCache.v3'). Numeric array indices are supported."),
5102
+ depth: z15.number().optional().describe("Collapse anything deeper than N levels to a size marker \u2014 avoids huge outputs for large stores."),
4717
5103
  ...sessionIdShape6
4718
5104
  },
4719
5105
  outputSchema: {
4720
- stores: z14.record(z14.unknown()).optional(),
4721
- storeNames: z14.array(z14.string()).optional(),
4722
- found: z14.boolean().optional(),
4723
- value: z14.unknown().optional(),
4724
- component: z14.object({ ok: z14.boolean(), reason: z14.string().optional(), state: z14.unknown().optional() }).optional()
5106
+ stores: z15.record(z15.unknown()).optional(),
5107
+ storeNames: z15.array(z15.string()).optional(),
5108
+ found: z15.boolean().optional(),
5109
+ value: z15.unknown().optional(),
5110
+ component: z15.object({ ok: z15.boolean(), reason: z15.string().optional(), state: z15.unknown().optional() }).optional()
4725
5111
  },
4726
5112
  handler: async (deps, args) => {
4727
5113
  const store = asString4(args["store"]);
@@ -4750,13 +5136,13 @@ var TOOLS = [
4750
5136
  name: IrisTool.EXPLORE,
4751
5137
  description: "Autonomous-exploration helper: list interactive elements (with refs) + current console-error count, so the agent can drive the app and report anomalies.",
4752
5138
  inputSchema: {
4753
- scope: z14.string().optional().describe("CSS selector or element ref to restrict the interactive element list to a subtree."),
5139
+ scope: z15.string().optional().describe("CSS selector or element ref to restrict the interactive element list to a subtree."),
4754
5140
  ...sessionIdShape6
4755
5141
  },
4756
5142
  outputSchema: {
4757
- interactive: z14.array(z14.unknown()),
4758
- consoleErrors: z14.number(),
4759
- hint: z14.string()
5143
+ interactive: z15.array(z15.unknown()),
5144
+ consoleErrors: z15.number(),
5145
+ hint: z15.string()
4760
5146
  },
4761
5147
  handler: async (deps, args) => {
4762
5148
  const session = deps.sessions.resolve(asString4(args["sessionId"]));
@@ -4794,7 +5180,9 @@ var TOOLS = [
4794
5180
  // Live-control: iris_end_session / iris_resume / iris_messages. See live-control-tools.ts.
4795
5181
  ...LIVE_CONTROL_TOOLS,
4796
5182
  // iris_navigate / iris_refresh — browser navigation tools. See browser-tools.ts.
4797
- ...BROWSER_TOOLS
5183
+ ...BROWSER_TOOLS,
5184
+ // iris_version_info / iris_apply_update / iris_rollback — update lifecycle tools.
5185
+ ...UPDATE_TOOLS
4798
5186
  ];
4799
5187
 
4800
5188
  // ../server/dist/tools/profiles.js
@@ -4940,7 +5328,7 @@ async function runTool(tool, deps, args) {
4940
5328
  }
4941
5329
 
4942
5330
  // ../server/dist/mcp.js
4943
- var SERVER_INFO = { name: "iris", version: "0.3.10" };
5331
+ var SERVER_INFO = { name: "iris", version: SERVER_VERSION };
4944
5332
  var ENCODING_ENV = "IRIS_ENCODING";
4945
5333
  var TOON_VALUE = "toon";
4946
5334
  function encodeResult(result, useToon) {
@@ -5054,6 +5442,47 @@ function createToolInvoker(deps) {
5054
5442
  };
5055
5443
  }
5056
5444
 
5445
+ // ../server/dist/daemon.js
5446
+ import { join as join4 } from "path";
5447
+ import { homedir as homedir2 } from "os";
5448
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync, openSync } from "fs";
5449
+ import { spawn } from "child_process";
5450
+ var IRIS_HOME2 = join4(homedir2(), ".iris");
5451
+ function pidPath(port) {
5452
+ return join4(IRIS_HOME2, `daemon-${port}.pid`);
5453
+ }
5454
+ function logPath(port) {
5455
+ return join4(IRIS_HOME2, `daemon-${port}.log`);
5456
+ }
5457
+ function readPid(port) {
5458
+ const path = pidPath(port);
5459
+ if (!existsSync3(path))
5460
+ return null;
5461
+ const n = parseInt(readFileSync2(path, "utf8").trim(), 10);
5462
+ return isNaN(n) ? null : n;
5463
+ }
5464
+ function isAlive(pid) {
5465
+ try {
5466
+ process.kill(pid, 0);
5467
+ return true;
5468
+ } catch {
5469
+ return false;
5470
+ }
5471
+ }
5472
+ function writePid(port) {
5473
+ mkdirSync2(IRIS_HOME2, { recursive: true });
5474
+ writeFileSync2(pidPath(port), String(process.pid), "utf8");
5475
+ }
5476
+ function removePid(port) {
5477
+ const path = pidPath(port);
5478
+ if (existsSync3(path))
5479
+ unlinkSync(path);
5480
+ }
5481
+ function isRunning(port) {
5482
+ const pid = readPid(port);
5483
+ return pid !== null && isAlive(pid);
5484
+ }
5485
+
5057
5486
  // ../server/dist/index.js
5058
5487
  async function start(options = {}) {
5059
5488
  const port = options.port ?? IRIS_DEFAULT_PORT;
@@ -5086,11 +5515,11 @@ async function start(options = {}) {
5086
5515
  }
5087
5516
  }
5088
5517
  if (options.mcp !== false) {
5089
- const fs = createNodeFileSystem();
5090
- const irisRoot = options.irisRoot ?? join2(process.cwd(), IrisDir.ROOT);
5518
+ const fs2 = createNodeFileSystem();
5519
+ const irisRoot = options.irisRoot ?? join5(process.cwd(), IrisDir.ROOT);
5091
5520
  const now = options.now ?? (() => Date.now());
5092
- const flows = new FlowStore(fs, irisRoot, { now });
5093
- const project = new ProjectStore(fs, irisRoot, { now });
5521
+ const flows = new FlowStore(fs2, irisRoot, { now });
5522
+ const project = new ProjectStore(fs2, irisRoot, { now });
5094
5523
  const annotations = new AnnotationStore();
5095
5524
  const deps = {
5096
5525
  sessions: bridge.sessions,
@@ -5099,7 +5528,7 @@ async function start(options = {}) {
5099
5528
  annotations,
5100
5529
  flows,
5101
5530
  project,
5102
- fs,
5531
+ fs: fs2,
5103
5532
  irisRoot,
5104
5533
  now
5105
5534
  };
@@ -5121,6 +5550,71 @@ async function start(options = {}) {
5121
5550
  }
5122
5551
  };
5123
5552
  }
5553
+ async function startDaemon(options = {}) {
5554
+ const port = options.port ?? IRIS_DEFAULT_PORT;
5555
+ const shared = createSharedServer();
5556
+ const bridge = new Bridge({ port, server: shared.httpServer });
5557
+ const reaper = new SessionReaper(bridge.sessions);
5558
+ reaper.start();
5559
+ let owned;
5560
+ let realInput;
5561
+ const driveUrl = options.driveUrl;
5562
+ if (driveUrl !== void 0 && driveUrl.length > 0) {
5563
+ const headless = options.headless ?? true;
5564
+ const factory = options.realInputFactory ?? ((opts) => new LaunchedRealInputProvider({ driveUrl: opts.driveUrl, headless: opts.headless }));
5565
+ const launched = factory({ driveUrl, headless });
5566
+ try {
5567
+ await launched.navigate();
5568
+ } catch (error) {
5569
+ await shared.close();
5570
+ throw error;
5571
+ }
5572
+ owned = launched;
5573
+ realInput = launched;
5574
+ } else {
5575
+ const cdpUrl = options.cdpUrl ?? process.env["IRIS_CDP_URL"];
5576
+ if (cdpUrl !== void 0 && cdpUrl.length > 0) {
5577
+ const cdp = new CdpRealInputProvider({ cdpUrl });
5578
+ owned = cdp;
5579
+ realInput = cdp;
5580
+ }
5581
+ }
5582
+ const fs2 = createNodeFileSystem();
5583
+ const irisRoot = options.irisRoot ?? join5(process.cwd(), IrisDir.ROOT);
5584
+ const now = options.now ?? (() => Date.now());
5585
+ const flows = new FlowStore(fs2, irisRoot, { now });
5586
+ const project = new ProjectStore(fs2, irisRoot, { now });
5587
+ const annotations = new AnnotationStore();
5588
+ const deps = {
5589
+ sessions: bridge.sessions,
5590
+ baselines: new BaselineStore(),
5591
+ recordings: new RecordingStore(),
5592
+ annotations,
5593
+ flows,
5594
+ project,
5595
+ fs: fs2,
5596
+ irisRoot,
5597
+ now
5598
+ };
5599
+ const profile = resolveToolProfile(options.toolProfile);
5600
+ const effectiveDeps = realInput !== void 0 ? { ...deps, realInput } : deps;
5601
+ shared.attachMcp(() => createMcpServer(effectiveDeps, profile));
5602
+ await new Promise((resolve) => {
5603
+ shared.httpServer.once("listening", resolve);
5604
+ shared.httpServer.listen(port, "127.0.0.1");
5605
+ });
5606
+ log("mcp_daemon_started", { port });
5607
+ return {
5608
+ bridge,
5609
+ ...realInput !== void 0 ? { realInput } : {},
5610
+ close: async () => {
5611
+ reaper.stop();
5612
+ await owned?.dispose();
5613
+ await bridge.close();
5614
+ await shared.close();
5615
+ }
5616
+ };
5617
+ }
5124
5618
  export {
5125
5619
  AnnotationStore,
5126
5620
  BaselineStore,
@@ -5131,6 +5625,8 @@ export {
5131
5625
  FlowStore,
5132
5626
  IrisTool,
5133
5627
  LaunchedRealInputProvider,
5628
+ MCP_MESSAGE_PATH,
5629
+ MCP_SSE_PATH,
5134
5630
  PredicateSchema,
5135
5631
  ProjectStore,
5136
5632
  RecordingStore,
@@ -5158,17 +5654,24 @@ export {
5158
5654
  filterTools,
5159
5655
  flowPath,
5160
5656
  irisDirPaths,
5657
+ isAlive,
5161
5658
  isPointerAction,
5659
+ isRunning,
5660
+ logPath,
5162
5661
  nearestTestid,
5163
5662
  normalizeLines,
5164
5663
  performGesture,
5165
5664
  readContract,
5665
+ readPid,
5166
5666
  recordedStepToFlowStep,
5667
+ removePid,
5167
5668
  replayFlow,
5168
5669
  resolveToolProfile,
5169
5670
  runTool,
5170
5671
  scrollToFind,
5171
5672
  start,
5673
+ startDaemon,
5172
5674
  waitForPredicate,
5173
- writeContract
5675
+ writeContract,
5676
+ writePid
5174
5677
  };