@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/cli.js +1112 -234
- package/dist/index.js +5 -0
- package/dist/server.d.ts +10 -1
- package/dist/server.js +718 -215
- package/dist/test.js +542 -222
- package/package.json +9 -9
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ../server/dist/index.js
|
|
2
|
-
import { join as
|
|
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.
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
|
1093
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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(
|
|
1488
|
+
async function ensureIrisDir(fs2, root) {
|
|
1364
1489
|
const p = irisDirPaths(root);
|
|
1365
|
-
await
|
|
1366
|
-
await
|
|
1367
|
-
await
|
|
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(
|
|
1385
|
-
await ensureIrisDir(
|
|
1386
|
-
await
|
|
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(
|
|
1513
|
+
async function readContract(fs2, root) {
|
|
1389
1514
|
const path = irisDirPaths(root).contract;
|
|
1390
|
-
if (!await
|
|
1515
|
+
if (!await fs2.exists(path))
|
|
1391
1516
|
return { ok: false, reason: ContractReadError.MISSING };
|
|
1392
1517
|
let text;
|
|
1393
1518
|
try {
|
|
1394
|
-
text = await
|
|
1519
|
+
text = await fs2.readFile(path);
|
|
1395
1520
|
} catch (error) {
|
|
1396
1521
|
return {
|
|
1397
1522
|
ok: false,
|
|
1398
|
-
reason:
|
|
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(
|
|
1486
|
-
this.#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(
|
|
1633
|
-
this.#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
|
|
1937
|
+
import { z as z16 } from "zod";
|
|
1813
1938
|
|
|
1814
1939
|
// ../server/dist/tools/tools.js
|
|
1815
|
-
import { z as
|
|
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(
|
|
3425
|
-
this.#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:
|
|
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:
|
|
4139
|
-
sessionId:
|
|
4140
|
-
url:
|
|
4141
|
-
title:
|
|
4142
|
-
lastSeenMs:
|
|
4143
|
-
throttled:
|
|
4144
|
-
focused:
|
|
4145
|
-
hidden:
|
|
4146
|
-
realInputAvailable:
|
|
4147
|
-
stale:
|
|
4148
|
-
recommendation:
|
|
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:
|
|
4165
|
-
mode:
|
|
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:
|
|
4170
|
-
status:
|
|
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:
|
|
4182
|
-
value:
|
|
4183
|
-
name:
|
|
4184
|
-
scope:
|
|
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:
|
|
4189
|
-
ref:
|
|
4190
|
-
role:
|
|
4191
|
-
name:
|
|
4192
|
-
value:
|
|
4193
|
-
states:
|
|
4194
|
-
visible:
|
|
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:
|
|
4197
|
-
route:
|
|
4198
|
-
presentTestids:
|
|
4199
|
-
knownEmptyState:
|
|
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:
|
|
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:
|
|
4218
|
-
role:
|
|
4219
|
-
name:
|
|
4220
|
-
value:
|
|
4221
|
-
states:
|
|
4222
|
-
visible:
|
|
4223
|
-
box:
|
|
4224
|
-
component:
|
|
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:
|
|
4235
|
-
action:
|
|
4236
|
-
args:
|
|
4237
|
-
refuseWhenThrottled:
|
|
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:
|
|
4242
|
-
dispatched:
|
|
4243
|
-
settled:
|
|
4244
|
-
inputMode:
|
|
4245
|
-
result:
|
|
4246
|
-
session:
|
|
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:
|
|
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:
|
|
4307
|
-
dispatched:
|
|
4308
|
-
result:
|
|
4309
|
-
session:
|
|
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:
|
|
4338
|
-
action:
|
|
4339
|
-
args:
|
|
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:
|
|
4342
|
-
refuseWhenThrottled:
|
|
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:
|
|
4347
|
-
verdict:
|
|
4348
|
-
pass:
|
|
4349
|
-
evidence:
|
|
4350
|
-
failureReason:
|
|
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:
|
|
4353
|
-
session:
|
|
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:
|
|
4387
|
-
since:
|
|
4388
|
-
filters:
|
|
4389
|
-
max_events:
|
|
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:
|
|
4394
|
-
summary:
|
|
4395
|
-
total:
|
|
4396
|
-
network:
|
|
4397
|
-
domAdded:
|
|
4398
|
-
domRemoved:
|
|
4399
|
-
domChanged:
|
|
4400
|
-
routeChanges:
|
|
4401
|
-
consoleErrors:
|
|
4402
|
-
animations:
|
|
4403
|
-
signals:
|
|
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:
|
|
4406
|
-
events:
|
|
4407
|
-
bytes:
|
|
4408
|
-
droppedOldest:
|
|
4790
|
+
cost: z15.object({
|
|
4791
|
+
events: z15.number(),
|
|
4792
|
+
bytes: z15.number(),
|
|
4793
|
+
droppedOldest: z15.number().optional()
|
|
4409
4794
|
}),
|
|
4410
|
-
session:
|
|
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:
|
|
4434
|
-
since:
|
|
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:
|
|
4439
|
-
evidence:
|
|
4440
|
-
failureReason:
|
|
4441
|
-
session:
|
|
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:
|
|
4457
|
-
since:
|
|
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:
|
|
4462
|
-
evidence:
|
|
4463
|
-
failureReason:
|
|
4464
|
-
session:
|
|
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:
|
|
4480
|
-
method:
|
|
4481
|
-
urlContains:
|
|
4482
|
-
status:
|
|
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:
|
|
4487
|
-
hint:
|
|
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:
|
|
4508
|
-
since:
|
|
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:
|
|
4513
|
-
hint:
|
|
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:
|
|
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:
|
|
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:
|
|
4545
|
-
lineCount:
|
|
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:
|
|
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:
|
|
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:
|
|
4572
|
-
removed:
|
|
4573
|
-
added:
|
|
4574
|
-
consoleErrors:
|
|
4575
|
-
routeChanged:
|
|
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:
|
|
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:
|
|
4598
|
-
since:
|
|
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:
|
|
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:
|
|
4617
|
-
program:
|
|
4618
|
-
warning:
|
|
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:
|
|
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:
|
|
4655
|
-
ok:
|
|
4656
|
-
steps:
|
|
4657
|
-
tool:
|
|
4658
|
-
ok:
|
|
4659
|
-
error:
|
|
4660
|
-
note:
|
|
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:
|
|
4679
|
-
level:
|
|
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
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
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:
|
|
4695
|
-
advanceMs:
|
|
4696
|
-
reset:
|
|
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:
|
|
4701
|
-
elapsed:
|
|
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:
|
|
4714
|
-
store:
|
|
4715
|
-
path:
|
|
4716
|
-
depth:
|
|
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:
|
|
4721
|
-
storeNames:
|
|
4722
|
-
found:
|
|
4723
|
-
value:
|
|
4724
|
-
component:
|
|
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:
|
|
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:
|
|
4758
|
-
consoleErrors:
|
|
4759
|
-
hint:
|
|
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:
|
|
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
|
|
5090
|
-
const irisRoot = options.irisRoot ??
|
|
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(
|
|
5093
|
-
const project = new ProjectStore(
|
|
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
|
};
|