@wenathlan/extension 1.1.59 → 1.1.60
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/README.md +4 -3
- package/dist/cli.js +19 -2
- package/dist/environments.d.ts +96 -0
- package/dist/environments.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +559 -1
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +55 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +32 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +46 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/runstate.d.ts +127 -0
- package/dist/runstate.d.ts.map +1 -0
- package/dist/sandboxframe.d.ts +45 -0
- package/dist/sandboxframe.d.ts.map +1 -0
- package/dist/types.d.ts +168 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +919 -4
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +16 -2
- package/extension/dist/offscreen.html +7 -0
- package/extension/dist/offscreen.js +87 -0
- package/extension/dist/offscreen.js.map +7 -0
- package/extension/dist/pagebridge.js +7 -0
- package/extension/dist/pagebridge.js.map +2 -2
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +38 -0
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sandbox.html +28 -0
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +293 -0
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/manifest.json +16 -2
- package/package.json +1 -1
|
@@ -1914,6 +1914,170 @@ function swarmoverview(input) {
|
|
|
1914
1914
|
};
|
|
1915
1915
|
}
|
|
1916
1916
|
|
|
1917
|
+
// runstate.ts
|
|
1918
|
+
function openrun(input) {
|
|
1919
|
+
if (input.runid.trim() === "" || input.sessionid.trim() === "") throw new Error("The run state needs its run and session ids.");
|
|
1920
|
+
if (!Number.isFinite(input.interval) || input.interval <= 0) throw new Error("The keepalive heartbeat interval stays a positive user value in milliseconds.");
|
|
1921
|
+
return {
|
|
1922
|
+
runid: input.runid,
|
|
1923
|
+
sessionid: input.sessionid,
|
|
1924
|
+
planid: input.planid,
|
|
1925
|
+
profileid: input.profileid,
|
|
1926
|
+
state: "active",
|
|
1927
|
+
urlhistory: [],
|
|
1928
|
+
environments: {},
|
|
1929
|
+
turnarounds: {},
|
|
1930
|
+
keepalive: { runid: input.runid, sessionid: input.sessionid, state: "active", startedat: input.now, interval: input.interval, beats: 0, lastbeatat: input.now, portopen: true, events: [{ kind: "start", at: input.now, detail: `The keepalive port opens for the run ${input.runid} and beats every ${input.interval} milliseconds.` }] },
|
|
1931
|
+
updatedat: input.now
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
function beatrun(state, now) {
|
|
1935
|
+
if (state.keepalive.state !== "active") throw new Error(`The run ${state.runid} is ${state.keepalive.state}; a stopped run emits no heartbeat.`);
|
|
1936
|
+
return {
|
|
1937
|
+
...state,
|
|
1938
|
+
keepalive: { ...state.keepalive, beats: state.keepalive.beats + 1, lastbeatat: now, events: [...state.keepalive.events, { kind: "heartbeat", at: now }].slice(-200) },
|
|
1939
|
+
updatedat: now
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
function closerun(state, now) {
|
|
1943
|
+
if (state.keepalive.state === "stopped") throw new Error(`The run ${state.runid} already stopped its keepalive port.`);
|
|
1944
|
+
return {
|
|
1945
|
+
...state,
|
|
1946
|
+
state: "completed",
|
|
1947
|
+
keepalive: { ...state.keepalive, state: "stopped", stoppedat: now, portopen: false, events: [...state.keepalive.events, { kind: "stop", at: now, detail: "The run reached a terminal state and the keepalive port closed." }].slice(-200) },
|
|
1948
|
+
updatedat: now
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
function reattachrun(state, now) {
|
|
1952
|
+
if (state.keepalive.state !== "active") throw new Error(`The run ${state.runid} is ${state.keepalive.state}; a stopped run never reattaches.`);
|
|
1953
|
+
return {
|
|
1954
|
+
...state,
|
|
1955
|
+
state: "recovered",
|
|
1956
|
+
keepalive: { ...state.keepalive, portopen: true, events: [...state.keepalive.events, { kind: "reattach", at: now, detail: "The service worker restarted and the keepalive port reattached from the persisted run state." }].slice(-200) },
|
|
1957
|
+
updatedat: now
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function recordurl(state, entry) {
|
|
1961
|
+
if (entry.url.trim() === "") throw new Error("The url history entry needs its url.");
|
|
1962
|
+
const record2 = { url: entry.url, stepid: entry.stepid, at: entry.now };
|
|
1963
|
+
return { ...state, urlhistory: [...state.urlhistory.filter((item) => !(item.url === record2.url && item.stepid === record2.stepid)), record2], updatedat: entry.now };
|
|
1964
|
+
}
|
|
1965
|
+
function recordenvironment(state, input) {
|
|
1966
|
+
if (input.stepid.trim() === "") throw new Error("The environment record needs its step id.");
|
|
1967
|
+
const provenance = { origin: input.origin, stepid: input.stepid, environment: input.environment };
|
|
1968
|
+
return { ...state, environments: { ...state.environments, [input.stepid]: input.environment }, lastprovenance: provenance, updatedat: input.now };
|
|
1969
|
+
}
|
|
1970
|
+
function recordturnaround(state, input) {
|
|
1971
|
+
if (input.stepid.trim() === "") throw new Error("The turnaround record needs its step id.");
|
|
1972
|
+
if (!Number.isFinite(input.milliseconds) || input.milliseconds < 0) throw new Error("The worker turnaround stays a non-negative duration in milliseconds.");
|
|
1973
|
+
return { ...state, turnarounds: { ...state.turnarounds, [input.stepid]: input.milliseconds }, updatedat: input.now };
|
|
1974
|
+
}
|
|
1975
|
+
function markpending(state, stepid, now) {
|
|
1976
|
+
return { ...state, ...stepid !== void 0 && stepid.trim() !== "" ? { pendingstepid: stepid } : {}, updatedat: now };
|
|
1977
|
+
}
|
|
1978
|
+
function recoveryplan(state) {
|
|
1979
|
+
if (state.state === "completed") return { runid: state.runid, recoverable: false, reason: `The run ${state.runid} completed before the restart; nothing resumes.` };
|
|
1980
|
+
if (state.state === "reaped") return { runid: state.runid, recoverable: false, reason: `The run ${state.runid} was reaped as a zombie; the user starts a fresh reviewed run.` };
|
|
1981
|
+
if (state.pendingstepid === void 0 || state.pendingstepid.trim() === "") return { runid: state.runid, recoverable: false, reason: `The run ${state.runid} carries no pending step; a fresh reviewed run starts over instead of guessing.` };
|
|
1982
|
+
return { runid: state.runid, pendingstepid: state.pendingstepid, recoverable: true, reason: `The executor resumes the pending step ${state.pendingstepid} of the run ${state.runid} from the persisted run state.` };
|
|
1983
|
+
}
|
|
1984
|
+
function zombiesweep(input) {
|
|
1985
|
+
if (!Number.isFinite(input.interval) || input.interval <= 0) throw new Error("The zombie sweep needs its heartbeat interval as a positive user value.");
|
|
1986
|
+
if (!Number.isInteger(input.missedlimit) || input.missedlimit < 1) throw new Error("The zombie tolerance stays a positive whole number of silent intervals.");
|
|
1987
|
+
const silentfor = input.interval * input.missedlimit;
|
|
1988
|
+
const zombies = input.states.filter((state) => state.keepalive.state === "active" && input.now - state.keepalive.lastbeatat > silentfor);
|
|
1989
|
+
if (zombies.length === 0) return { states: input.states, reaped: [] };
|
|
1990
|
+
const ids = new Set(zombies.map((state) => state.runid));
|
|
1991
|
+
return { states: input.states.map((state) => ids.has(state.runid) ? { ...state, state: "reaped", keepalive: { ...state.keepalive, state: "stopped", portopen: false, ...state.keepalive.stoppedat === void 0 ? { stoppedat: input.now } : {}, events: [...state.keepalive.events, { kind: "stop", at: input.now, detail: "The zombie reaper closed the silent run." }] } } : state), reaped: [...ids] };
|
|
1992
|
+
}
|
|
1993
|
+
function acquirerunlock(input) {
|
|
1994
|
+
if (input.sessionid.trim() === "" || input.runid.trim() === "") throw new Error("The run lock needs its session and run ids.");
|
|
1995
|
+
const live = input.locks.filter((lock2) => lock2.sessionid === input.sessionid && (lock2.expiresat === void 0 || lock2.expiresat > input.now));
|
|
1996
|
+
const held = live.find((lock2) => lock2.runid !== input.runid);
|
|
1997
|
+
if (held) return { locks: input.locks, acquired: false, reason: `The session ${input.sessionid} already holds the run ${held.runid}; a session never carries two concurrent runs.` };
|
|
1998
|
+
const own = input.locks.find((lock2) => lock2.sessionid === input.sessionid && lock2.runid === input.runid);
|
|
1999
|
+
if (own) return { locks: input.locks, acquired: true, reason: `The run ${input.runid} of the session ${input.sessionid} already holds its lock.` };
|
|
2000
|
+
const lock = { sessionid: input.sessionid, runid: input.runid, holder: input.holder, acquiredat: input.now, ...input.expiresat !== void 0 ? { expiresat: input.expiresat } : {} };
|
|
2001
|
+
return { locks: [...input.locks.filter((entry) => entry.sessionid !== input.sessionid), lock], acquired: true, reason: `The run ${input.runid} locked the session ${input.sessionid} against concurrent runs.` };
|
|
2002
|
+
}
|
|
2003
|
+
function releaserunlock(input) {
|
|
2004
|
+
const lock = input.locks.find((entry) => entry.sessionid === input.sessionid);
|
|
2005
|
+
if (!lock || lock.runid !== input.runid) return { locks: input.locks, released: false, reason: `The run ${input.runid} holds no lock of the session ${input.sessionid}.` };
|
|
2006
|
+
return { locks: input.locks.filter((entry) => entry.sessionid !== input.sessionid), released: true, reason: `The run ${input.runid} released the run lock of the session ${input.sessionid}.` };
|
|
2007
|
+
}
|
|
2008
|
+
function expirerunlocks(locks, now) {
|
|
2009
|
+
const expired = locks.filter((lock) => lock.expiresat !== void 0 && now > lock.expiresat);
|
|
2010
|
+
if (expired.length === 0) return { locks, expired: [] };
|
|
2011
|
+
const ids = new Set(expired.map((lock) => lock.sessionid));
|
|
2012
|
+
return { locks: locks.filter((lock) => !ids.has(lock.sessionid)), expired: [...ids] };
|
|
2013
|
+
}
|
|
2014
|
+
function serializesteps(input) {
|
|
2015
|
+
const shared = /* @__PURE__ */ new Set();
|
|
2016
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2017
|
+
for (const branch of input.branches) for (const step of branch.steps) counts.set(step.tabid, (counts.get(step.tabid) ?? 0) + 1);
|
|
2018
|
+
for (const [tabid2, count] of counts) if (count > 1) shared.add(tabid2);
|
|
2019
|
+
const order = [];
|
|
2020
|
+
let cursor = 0;
|
|
2021
|
+
for (const branch of input.branches) {
|
|
2022
|
+
for (const step of branch.steps) {
|
|
2023
|
+
if (shared.has(step.tabid)) {
|
|
2024
|
+
order.push({ branchid: branch.branchid, stepid: step.stepid, tabid: step.tabid, order: cursor });
|
|
2025
|
+
cursor += 1;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
for (const branch of input.branches) {
|
|
2030
|
+
for (const step of branch.steps) {
|
|
2031
|
+
if (!shared.has(step.tabid)) {
|
|
2032
|
+
order.push({ branchid: branch.branchid, stepid: step.stepid, tabid: step.tabid, order: cursor });
|
|
2033
|
+
cursor += 1;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return order;
|
|
2038
|
+
}
|
|
2039
|
+
async function sealrunstate(state) {
|
|
2040
|
+
const payload = JSON.stringify(state);
|
|
2041
|
+
const digest = await sha256(payload);
|
|
2042
|
+
return { payload, algorithm: "sha-256", digest, sealedat: state.updatedat };
|
|
2043
|
+
}
|
|
2044
|
+
async function openseal(sealed) {
|
|
2045
|
+
const digest = await sha256(sealed.payload);
|
|
2046
|
+
if (digest !== sealed.digest) throw new Error("The sealed run state fails its integrity digest; a tampered run state never reaches the recovery.");
|
|
2047
|
+
const parsed = JSON.parse(sealed.payload);
|
|
2048
|
+
if (typeof parsed.runid !== "string" || typeof parsed.sessionid !== "string") throw new Error("The sealed run state carries no run record.");
|
|
2049
|
+
return parsed;
|
|
2050
|
+
}
|
|
2051
|
+
async function sha256(value) {
|
|
2052
|
+
const bytes = new TextEncoder().encode(value);
|
|
2053
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
2054
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2055
|
+
}
|
|
2056
|
+
function prunerunstates(input) {
|
|
2057
|
+
if (!Number.isFinite(input.ceiling) || input.ceiling <= 0) return { states: input.states, pruned: [], reason: "No storage ceiling is configured, so the run states stay under the user choice alone." };
|
|
2058
|
+
if (input.used <= input.ceiling) return { states: input.states, pruned: [], reason: `The run state storage sits at ${input.used} of the ${input.ceiling} bytes the user configured; no pressure exists.` };
|
|
2059
|
+
const removable = input.states.filter((state) => state.state === "completed" || state.state === "reaped").sort((one, two) => one.updatedat - two.updatedat);
|
|
2060
|
+
const states = [...input.states];
|
|
2061
|
+
const pruned = [];
|
|
2062
|
+
for (const candidate of removable) {
|
|
2063
|
+
pruned.push(candidate.runid);
|
|
2064
|
+
const index = states.findIndex((state) => state.runid === candidate.runid);
|
|
2065
|
+
if (index >= 0) states.splice(index, 1);
|
|
2066
|
+
if (pruned.length >= Math.max(1, Math.ceil(input.states.length / 2))) break;
|
|
2067
|
+
}
|
|
2068
|
+
return { states, pruned, reason: `The storage pressure at ${input.used} of ${input.ceiling} bytes pruned the ${pruned.length} oldest finished run state${pruned.length === 1 ? "" : "s"} while every active run keeps its state.` };
|
|
2069
|
+
}
|
|
2070
|
+
function exportrunstate(states, now) {
|
|
2071
|
+
return {
|
|
2072
|
+
runs: states.length,
|
|
2073
|
+
urls: states.reduce((total, state) => total + state.urlhistory.length, 0),
|
|
2074
|
+
environments: states.reduce((total, state) => total + Object.keys(state.environments).length, 0),
|
|
2075
|
+
offloaded: states.reduce((total, state) => total + Object.values(state.turnarounds).length, 0),
|
|
2076
|
+
beats: states.reduce((total, state) => total + state.keepalive.beats, 0),
|
|
2077
|
+
exportedat: now
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
|
|
1917
2081
|
// memory.ts
|
|
1918
2082
|
var sessionmemory = class {
|
|
1919
2083
|
constructor(adapter) {
|
|
@@ -4258,6 +4422,121 @@ var sessionmemory = class {
|
|
|
4258
4422
|
async getswarmcosts() {
|
|
4259
4423
|
return await this.adapter.get("swarmcosts") ?? [];
|
|
4260
4424
|
}
|
|
4425
|
+
/**
|
|
4426
|
+
* Execution environment persistence of the 1.1.60 family.
|
|
4427
|
+
* The run state store seals every persisted run state with its sha-256 integrity digest through the storage api (the browser offers no at-rest encryption for its storage areas, so the honest derivation is the integrity seal that makes tampering detectable before any recovery uses the record), scopes every run state per profile so parallel profiles never share it, expires stale run state past the user configured window while the keepalive summaries survive, tracks the storage quota usage of the run state and prunes the oldest finished run states under pressure.
|
|
4428
|
+
* The adapter seam keeps every accessor a one line storage delegation so a future worker state backend replaces the adapter only.
|
|
4429
|
+
*/
|
|
4430
|
+
/** Returns the environment grant list of the active session; an absent list keeps the documented default posture. */
|
|
4431
|
+
async getenvironmentgrants() {
|
|
4432
|
+
return (await this.getsession())?.environmentgrants;
|
|
4433
|
+
}
|
|
4434
|
+
/** Replaces the environment grant list of the active session so the environment grants join the origin grants in the session record. */
|
|
4435
|
+
async setenvironmentgrants(grants) {
|
|
4436
|
+
const session = await this.getsession();
|
|
4437
|
+
if (!session) throw new Error("The environment grants need an active session to join.");
|
|
4438
|
+
await this.setsession({ ...session, environmentgrants: grants });
|
|
4439
|
+
}
|
|
4440
|
+
/** Seals and stores the run state of one profile: the payload travels beside its sha-256 digest so a tampered record at rest stays detectable before any recovery uses it. */
|
|
4441
|
+
async setrunstate(profileid, state) {
|
|
4442
|
+
const sealed = await sealrunstate(state);
|
|
4443
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4444
|
+
await this.adapter.set(`runstate:${profileid}`, sealed);
|
|
4445
|
+
if (!index.includes(profileid)) await this.adapter.set("runstateindex", [...index, profileid]);
|
|
4446
|
+
}
|
|
4447
|
+
/** Opens the sealed run state of one profile; a missing or tampered seal returns undefined so the recovery never trusts a broken record. */
|
|
4448
|
+
async getrunstate(profileid) {
|
|
4449
|
+
const sealed = await this.adapter.get(`runstate:${profileid}`);
|
|
4450
|
+
if (!sealed) return void 0;
|
|
4451
|
+
try {
|
|
4452
|
+
return await openseal(sealed);
|
|
4453
|
+
} catch {
|
|
4454
|
+
return void 0;
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
/** Removes the run state of one profile from the store and the index: the per profile key takes an empty seal that never opens, so the quota pruning drops the pruned records whole. */
|
|
4458
|
+
async removerunstate(profileid) {
|
|
4459
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4460
|
+
await this.adapter.set("runstateindex", index.filter((entry) => entry !== profileid));
|
|
4461
|
+
await this.adapter.set(`runstate:${profileid}`, { payload: "", algorithm: "sha-256", digest: "", sealedat: 0 });
|
|
4462
|
+
}
|
|
4463
|
+
/** Lists the stored run state records of every profile, oldest update first. */
|
|
4464
|
+
async listrunstates() {
|
|
4465
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4466
|
+
const states = [];
|
|
4467
|
+
for (const profileid of index) {
|
|
4468
|
+
const state = await this.getrunstate(profileid);
|
|
4469
|
+
if (state) states.push(state);
|
|
4470
|
+
}
|
|
4471
|
+
return states.sort((one, two) => one.updatedat - two.updatedat);
|
|
4472
|
+
}
|
|
4473
|
+
/** Expires the stale run states past the user configured window: the expired records reduce to their keepalive summaries while an absent window keeps every run state whole. */
|
|
4474
|
+
async expirerunstates(window2, now) {
|
|
4475
|
+
if (window2 === void 0) return await this.listrunstates();
|
|
4476
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4477
|
+
const kept = [];
|
|
4478
|
+
for (const profileid of index) {
|
|
4479
|
+
const state = await this.getrunstate(profileid);
|
|
4480
|
+
if (!state) continue;
|
|
4481
|
+
if (now - state.updatedat > window2 && state.keepalive.state === "stopped") {
|
|
4482
|
+
const summary = { runid: state.runid, sessionid: state.sessionid, planid: state.planid, profileid: state.profileid, state: "expired", urlhistory: [], environments: {}, turnarounds: {}, keepalive: state.keepalive, updatedat: now };
|
|
4483
|
+
const sealed = await sealrunstate(summary);
|
|
4484
|
+
await this.adapter.set(`runstate:${profileid}`, sealed);
|
|
4485
|
+
} else {
|
|
4486
|
+
kept.push(state);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
return kept;
|
|
4490
|
+
}
|
|
4491
|
+
/** Records one worker spawn or teardown event with its provenance beside the step outcomes. */
|
|
4492
|
+
async addworkerevent(event) {
|
|
4493
|
+
await this.adapter.set("workerevents", [event, ...await this.adapter.get("workerevents") ?? []].slice(0, 500));
|
|
4494
|
+
}
|
|
4495
|
+
/** Returns the recorded worker spawn and teardown events, newest first. */
|
|
4496
|
+
async getworkerevents() {
|
|
4497
|
+
return await this.adapter.get("workerevents") ?? [];
|
|
4498
|
+
}
|
|
4499
|
+
/** Records one spawned offscreen document with its reasons and justification in the registry. */
|
|
4500
|
+
async addoffscreenentry(entry) {
|
|
4501
|
+
await this.adapter.set("offscreenregistry", [entry, ...await this.adapter.get("offscreenregistry") ?? []]);
|
|
4502
|
+
}
|
|
4503
|
+
/** Replaces one registry entry after its offscreen document closes. */
|
|
4504
|
+
async updateoffscreenentry(entry) {
|
|
4505
|
+
await this.adapter.set("offscreenregistry", (await this.adapter.get("offscreenregistry") ?? []).map((candidate) => candidate.runid === entry.runid ? entry : candidate));
|
|
4506
|
+
}
|
|
4507
|
+
/** Returns the offscreen document registry with the reasons and justification of every spawn. */
|
|
4508
|
+
async getoffscreenentries() {
|
|
4509
|
+
return await this.adapter.get("offscreenregistry") ?? [];
|
|
4510
|
+
}
|
|
4511
|
+
/** Records one sandbox render with its provenance, source origin and nonce. */
|
|
4512
|
+
async addsandboxrender(render) {
|
|
4513
|
+
await this.adapter.set("sandboxrenders", [render, ...await this.adapter.get("sandboxrenders") ?? []].slice(0, 500));
|
|
4514
|
+
}
|
|
4515
|
+
/** Returns the recorded sandbox renders with their provenance, newest first. */
|
|
4516
|
+
async getsandboxrenders() {
|
|
4517
|
+
return await this.adapter.get("sandboxrenders") ?? [];
|
|
4518
|
+
}
|
|
4519
|
+
/** Replaces the stored run locks after one acquisition, release or expiry sweep. */
|
|
4520
|
+
async setrunlocks(locks) {
|
|
4521
|
+
return this.adapter.set("runlocks", locks);
|
|
4522
|
+
}
|
|
4523
|
+
/** Returns the held run locks with their sessions, runs and expiries. */
|
|
4524
|
+
async getrunlocks() {
|
|
4525
|
+
return await this.adapter.get("runlocks") ?? [];
|
|
4526
|
+
}
|
|
4527
|
+
/** Tracks the storage quota usage of the run state: the last measured bytes stay beside the user configured ceiling so the pruning reads both. */
|
|
4528
|
+
async trackrunstatequota(used) {
|
|
4529
|
+
const settings = await this.getsettings();
|
|
4530
|
+
await this.adapter.set("runstatequota", { used, ...settings?.runstatebytes !== void 0 ? { ceiling: settings.runstatebytes } : {}, trackedat: Date.now() });
|
|
4531
|
+
}
|
|
4532
|
+
/** Returns the last tracked storage quota usage of the run state with its ceiling when the user configured one. */
|
|
4533
|
+
async getrunstatequota() {
|
|
4534
|
+
return this.adapter.get("runstatequota");
|
|
4535
|
+
}
|
|
4536
|
+
/** Exports every stored run state as one single audit record through the runstate export envelope. */
|
|
4537
|
+
async exportrunstates() {
|
|
4538
|
+
return exportrunstate(await this.listrunstates(), Date.now());
|
|
4539
|
+
}
|
|
4261
4540
|
};
|
|
4262
4541
|
function mediakindof(record2) {
|
|
4263
4542
|
if ("pages" in record2) return "pdf";
|
|
@@ -4301,6 +4580,121 @@ function randomid() {
|
|
|
4301
4580
|
return crypto.randomUUID();
|
|
4302
4581
|
}
|
|
4303
4582
|
|
|
4583
|
+
// environments.ts
|
|
4584
|
+
var offloadfamilies = [
|
|
4585
|
+
{ task: "htmlsnapshot", kinds: ["readhtml", "parsehtml", "readertree", "readoutline", "classifypage"] },
|
|
4586
|
+
{ task: "jsonpayload", kinds: ["readjson", "parsejson"] },
|
|
4587
|
+
{ task: "tablerows", kinds: ["readtable", "scrapetable", "detecttables", "deduperows", "transformvalues"] },
|
|
4588
|
+
{ task: "a11ytree", kinds: ["a11ytree"] },
|
|
4589
|
+
{ task: "complexselector", kinds: ["resolvexpath", "deriveselector", "detectvirtual"] },
|
|
4590
|
+
{ task: "stitchshots", kinds: ["contactsheet", "timelapse", "makethumbs"] }
|
|
4591
|
+
];
|
|
4592
|
+
function offfamilyof(kind) {
|
|
4593
|
+
return offloadfamilies.find((family) => family.kinds.includes(kind))?.task;
|
|
4594
|
+
}
|
|
4595
|
+
function environmentsof(step) {
|
|
4596
|
+
if (markuprenderstep(step)) return ["sandboxframe"];
|
|
4597
|
+
if (step.kind === "evaluate") return ["isolatedworld"];
|
|
4598
|
+
if (offfamilyof(step.kind) !== void 0) return ["pagecontext", "offscreenworker"];
|
|
4599
|
+
return ["pagecontext"];
|
|
4600
|
+
}
|
|
4601
|
+
function defaultenvironment(step) {
|
|
4602
|
+
if (markuprenderstep(step)) return "sandboxframe";
|
|
4603
|
+
if (step.kind === "evaluate") return "isolatedworld";
|
|
4604
|
+
return "pagecontext";
|
|
4605
|
+
}
|
|
4606
|
+
function offamilyeligible(kind) {
|
|
4607
|
+
return offloadfamilies.some((family) => family.kinds.includes(kind));
|
|
4608
|
+
}
|
|
4609
|
+
function markuprenderstep(step) {
|
|
4610
|
+
if (!step.options) return false;
|
|
4611
|
+
try {
|
|
4612
|
+
const parsed = JSON.parse(step.options);
|
|
4613
|
+
return Boolean(parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.markup === "string" && parsed.markup.trim() !== "");
|
|
4614
|
+
} catch {
|
|
4615
|
+
return false;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
function environmentrequirementsof(kinds) {
|
|
4619
|
+
return kinds.map((kind) => {
|
|
4620
|
+
const bare = { kind };
|
|
4621
|
+
const environments = environmentsof(bare);
|
|
4622
|
+
return { kind, environments, defaultenvironment: defaultenvironment(bare) };
|
|
4623
|
+
});
|
|
4624
|
+
}
|
|
4625
|
+
function executorregistry() {
|
|
4626
|
+
return [
|
|
4627
|
+
{ environment: "pagecontext", adapter: "pagebridge", description: "The page bridge executes dom actions inside the live page because page events only fire there." },
|
|
4628
|
+
{ environment: "isolatedworld", adapter: "scriptingapi", description: "The scripting api injects step logic inside the isolated world where page globals stay unreachable from step code." },
|
|
4629
|
+
{ environment: "offscreenworker", adapter: "offscreendocument", description: "The offscreen document hosts the worker pool that parses heavy payloads away from the page; the capability gate keeps it behind the optional offscreen grant with an inline fallback." },
|
|
4630
|
+
{ environment: "sandboxframe", adapter: "sandboxpage", description: "The sandboxed page renders untrusted markup with scripts and handlers stripped before render and posts its result back through a per render nonce." }
|
|
4631
|
+
];
|
|
4632
|
+
}
|
|
4633
|
+
function routeenvironment(step, input) {
|
|
4634
|
+
const allowed = environmentsof(step);
|
|
4635
|
+
const named = step.environment;
|
|
4636
|
+
if (named !== void 0) {
|
|
4637
|
+
if (!allowed.includes(named)) return { environment: defaultenvironment(step), fallback: false, reason: `The ${named} environment sits outside the ${allowed.join(", ")} the ${step.kind} kind permits, so the executor routes to the ${defaultenvironment(step)} default.` };
|
|
4638
|
+
return { environment: named, fallback: false, reason: `The reviewed step names its ${named} environment and the ${step.kind} kind permits it.` };
|
|
4639
|
+
}
|
|
4640
|
+
if (markuprenderstep(step)) return { environment: "sandboxframe", fallback: false, reason: `The ${step.kind} step carries untrusted markup, so it renders inside the sandboxframe only.` };
|
|
4641
|
+
if (step.kind === "evaluate") return { environment: "isolatedworld", fallback: false, reason: "The evaluate kind runs inside the isolated world where page globals stay unreachable from step code." };
|
|
4642
|
+
if (offamilyeligible(step.kind)) {
|
|
4643
|
+
if (!input.offload) return { environment: "pagecontext", fallback: false, reason: `The ${step.kind} step stays inside the page because the user keeps the parse offload off.` };
|
|
4644
|
+
if (!input.granted) return { environment: "pagecontext", fallback: true, reason: `The ${step.kind} step falls back to inline parsing inside the page because the offscreen capability grant stays absent.` };
|
|
4645
|
+
return { environment: "offscreenworker", fallback: false, reason: `The ${step.kind} step offloads into the offscreen worker pool under the granted capability.` };
|
|
4646
|
+
}
|
|
4647
|
+
return { environment: "pagecontext", fallback: false, reason: `The ${step.kind} step keeps the pagecontext because page events only fire inside the live page.` };
|
|
4648
|
+
}
|
|
4649
|
+
function workerrequestof(input) {
|
|
4650
|
+
const task = offfamilyof(input.kind);
|
|
4651
|
+
if (task === void 0) throw new Error(`The ${input.kind} kind stays outside the offscreen worker pool families.`);
|
|
4652
|
+
if (input.payload.trim() === "") throw new Error("The worker request needs its payload reference.");
|
|
4653
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, task, payload: input.payload, transferables: transferablekeys(input.options ?? {}), sentat: input.sentat };
|
|
4654
|
+
}
|
|
4655
|
+
function transferablekeys(options) {
|
|
4656
|
+
return Object.keys(options).filter((key) => options[key] instanceof ArrayBuffer);
|
|
4657
|
+
}
|
|
4658
|
+
function poolplan(input) {
|
|
4659
|
+
if (input.size !== void 0) {
|
|
4660
|
+
if (!Number.isFinite(input.size) || input.size < 1 || !Number.isInteger(input.size)) return { workers: input.current, added: 0, retired: 0, reason: "The configured pool size stays a positive whole number the user chose; the pool keeps its current workers." };
|
|
4661
|
+
const target = input.size;
|
|
4662
|
+
if (target > input.current) return { workers: target, added: target - input.current, retired: 0, reason: `The user configured pool size ${target} adds ${target - input.current} worker${target - input.current === 1 ? "" : "s"} to the pool.` };
|
|
4663
|
+
if (target < input.current) return { workers: target, added: 0, retired: input.current - target, reason: `The user configured pool size ${target} retires ${input.current - target} worker${input.current - target === 1 ? "" : "s"} from the pool.` };
|
|
4664
|
+
return { workers: target, added: 0, retired: 0, reason: `The pool holds the ${target} workers the user configured.` };
|
|
4665
|
+
}
|
|
4666
|
+
if (input.pending > input.current) return { workers: input.pending, added: input.pending - input.current, retired: 0, reason: `The ${input.pending} pending parses grow the pool by ${input.pending - input.current} worker${input.pending - input.current === 1 ? "" : "s"}; no engine cap exists.` };
|
|
4667
|
+
if (input.current > input.pending) return { workers: input.pending, added: 0, retired: input.current - input.pending, reason: `The ${input.current - input.pending} idle worker${input.current - input.pending === 1 ? "" : "s"} retire down to the ${input.pending} pending parse${input.pending === 1 ? "" : "s"}.` };
|
|
4668
|
+
return { workers: input.current, added: 0, retired: 0, reason: `The ${input.current} workers match the ${input.pending} pending parses; the pool stays unchanged.` };
|
|
4669
|
+
}
|
|
4670
|
+
function openoffscreen(input) {
|
|
4671
|
+
if (input.document.trim() === "") throw new Error("The offscreen document needs its user configured path.");
|
|
4672
|
+
if (input.reasons.length === 0) throw new Error("The offscreen document needs the reasons the user reviewed.");
|
|
4673
|
+
if (input.justification.trim() === "") throw new Error("The offscreen document needs its justification in plain language.");
|
|
4674
|
+
const open = input.registry.find((entry2) => entry2.runid === input.runid && entry2.closedat === void 0);
|
|
4675
|
+
if (open) return { registry: input.registry, entry: open, reused: true };
|
|
4676
|
+
const entry = { document: input.document, runid: input.runid, reasons: [...input.reasons], justification: input.justification, createdat: input.now };
|
|
4677
|
+
return { registry: [entry, ...input.registry], entry, reused: false };
|
|
4678
|
+
}
|
|
4679
|
+
function closeoffscreen(registry, runid, now) {
|
|
4680
|
+
const open = registry.find((entry) => entry.runid === runid && entry.closedat === void 0);
|
|
4681
|
+
if (!open) return { registry, closed: false };
|
|
4682
|
+
return { registry: registry.map((entry) => entry === open ? { ...entry, closedat: now } : entry), closed: true };
|
|
4683
|
+
}
|
|
4684
|
+
function isolatedinjection(step) {
|
|
4685
|
+
if (step.kind !== "evaluate") throw new Error("The isolated world injection serves the evaluate kind only.");
|
|
4686
|
+
if (!step.value || step.value.trim() === "") throw new Error("The evaluate step needs its reviewed expression.");
|
|
4687
|
+
let args = [];
|
|
4688
|
+
if (step.options) {
|
|
4689
|
+
try {
|
|
4690
|
+
const parsed = JSON.parse(step.options);
|
|
4691
|
+
if (Array.isArray(parsed)) args = parsed.filter((item) => typeof item === "string");
|
|
4692
|
+
} catch {
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
return { world: "ISOLATED", code: step.value, args };
|
|
4696
|
+
}
|
|
4697
|
+
|
|
4304
4698
|
// toolcatalog.ts
|
|
4305
4699
|
var toolcatalogversion = 1;
|
|
4306
4700
|
var toolnamespaces = ["browser", "workflow", "memory", "system"];
|
|
@@ -9127,6 +9521,48 @@ function mergeegressgrade(input) {
|
|
|
9127
9521
|
if (input.carriespagecontent) return { allowed: true, reason: `The export of the report ${input.report.title} carries page content from the sources ${input.report.sources.join(", ")} and grades as a data egress event in the audit trail.` };
|
|
9128
9522
|
return { allowed: true, reason: `The export of the report ${input.report.title} carries no page content and stays a plain report export.` };
|
|
9129
9523
|
}
|
|
9524
|
+
function stepenvironmentvalid(step) {
|
|
9525
|
+
if (step.environment !== void 0 && step.environment !== "pagecontext" && step.environment !== "isolatedworld" && step.environment !== "offscreenworker" && step.environment !== "sandboxframe") return { allowed: false, reason: "The step environment stays one of pagecontext, isolatedworld, offscreenworker and sandboxframe." };
|
|
9526
|
+
const allowed = environmentsof(step);
|
|
9527
|
+
if (step.environment !== void 0 && !allowed.includes(step.environment)) return { allowed: false, reason: `The ${step.environment} environment sits outside the ${allowed.join(", ")} the ${step.kind} kind permits; the review sees the environment of every step.` };
|
|
9528
|
+
return { allowed: true, reason: step.environment === void 0 ? `The ${step.kind} step carries no environment field and routes to its ${defaultenvironment(step)} default.` : `The ${step.environment} environment of the ${step.kind} step sits inside the ${allowed.join(", ")} the kind permits.` };
|
|
9529
|
+
}
|
|
9530
|
+
function environmentgrantgate(step, grants) {
|
|
9531
|
+
if (grants === void 0 || grants.length === 0) return { allowed: true, reason: `The session carries no environment grant list, so the ${defaultenvironment(step)} default of the ${step.kind} step stays the documented posture behind the same review.` };
|
|
9532
|
+
const environment = step.environment ?? defaultenvironment(step);
|
|
9533
|
+
if (!grants.includes(environment)) return { allowed: false, reason: `The ${environment} environment sits outside the ${grants.join(", ")} the session granted; no step ever widens the environment grants.` };
|
|
9534
|
+
return { allowed: true, reason: `The ${environment} environment of the ${step.kind} step sits inside the ${grants.join(", ")} the session granted.` };
|
|
9535
|
+
}
|
|
9536
|
+
function offscreencapabilitygate(input) {
|
|
9537
|
+
if (input.environment !== "offscreenworker") return { allowed: true, reason: `The ${input.environment} environment needs no offscreen capability grant.` };
|
|
9538
|
+
if (!input.granted) return { allowed: false, reason: "The offscreen worker pool runs only under the user granted offscreen capability; the step falls back to inline parsing inside the page." };
|
|
9539
|
+
return { allowed: true, reason: "The offscreen worker pool runs under the user granted offscreen capability." };
|
|
9540
|
+
}
|
|
9541
|
+
function keepalivegate(input) {
|
|
9542
|
+
if (!input.session) return { allowed: false, reason: "The keepalive port opens only inside an active session." };
|
|
9543
|
+
if (input.session.stoppedat !== void 0) return { allowed: false, reason: "The keepalive port stays closed for a stopped session." };
|
|
9544
|
+
if (input.session.pausedat !== void 0) return { allowed: false, reason: "The keepalive port stays closed while the session pauses; a resumed run reattaches it." };
|
|
9545
|
+
if (input.now > input.session.expiresat) return { allowed: false, reason: "The keepalive port stays closed for an expired session." };
|
|
9546
|
+
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "The keepalive port opens only behind an active reviewed plan; unreviewed work never holds the service worker alive." };
|
|
9547
|
+
return { allowed: true, reason: `The approved plan ${input.plan.id} of the active session holds the keepalive port open for its whole run.` };
|
|
9548
|
+
}
|
|
9549
|
+
function keepaliveintervalvalid(interval) {
|
|
9550
|
+
if (!Number.isFinite(interval) || interval <= 0) return { allowed: false, reason: "The keepalive heartbeat interval stays a positive user value in milliseconds." };
|
|
9551
|
+
return { allowed: true, reason: `The keepalive heartbeat interval ${interval} milliseconds stays the user configured value; the roadmap documents thirty seconds while the choice stays the user's.` };
|
|
9552
|
+
}
|
|
9553
|
+
function workerpoolsizevalid(size) {
|
|
9554
|
+
if (size === void 0) return { allowed: true, reason: "No worker pool size is configured, so the pool follows the pending parse queue alone with no engine cap." };
|
|
9555
|
+
if (!Number.isInteger(size) || size < 1) return { allowed: false, reason: "The worker pool size stays a positive whole number the user configured; no engine cap exists." };
|
|
9556
|
+
return { allowed: true, reason: `The worker pool size ${size} stays the user configured value; no engine cap exists.` };
|
|
9557
|
+
}
|
|
9558
|
+
function sandboxorigingate(input) {
|
|
9559
|
+
if (input.origin.trim() === "") return { allowed: false, reason: "The sandbox render needs the source origin of its untrusted markup." };
|
|
9560
|
+
if (input.allowed.length > 0 && !input.allowed.includes(input.origin)) return { allowed: false, reason: `The origin ${input.origin} sits outside the origins the user allows to render untrusted markup: ${input.allowed.join(", ")}.` };
|
|
9561
|
+
return { allowed: true, reason: input.allowed.length === 0 ? `The origin ${input.origin} renders untrusted markup under the documented open origin list the user chose not to narrow.` : `The origin ${input.origin} sits inside the origins the user allows to render untrusted markup.` };
|
|
9562
|
+
}
|
|
9563
|
+
function environmentrequirements() {
|
|
9564
|
+
return environmentrequirementsof([...allowedactions]);
|
|
9565
|
+
}
|
|
9130
9566
|
|
|
9131
9567
|
// progress.ts
|
|
9132
9568
|
function emptyprogress(planid, now) {
|
|
@@ -9141,6 +9577,14 @@ function recordoutcome(progress, planid, outcome, now) {
|
|
|
9141
9577
|
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9142
9578
|
return { ...base, outcomes: [...base.outcomes ?? [], outcome], updatedat: now };
|
|
9143
9579
|
}
|
|
9580
|
+
function recordenvironment2(progress, planid, stepid, environment, now) {
|
|
9581
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9582
|
+
return { ...base, environments: { ...base.environments ?? {}, [stepid]: environment }, updatedat: now };
|
|
9583
|
+
}
|
|
9584
|
+
function recordturnaround2(progress, planid, stepid, milliseconds, now) {
|
|
9585
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9586
|
+
return { ...base, turnarounds: { ...base.turnarounds ?? {}, [stepid]: milliseconds }, updatedat: now };
|
|
9587
|
+
}
|
|
9144
9588
|
function iscomplete(progress, plan) {
|
|
9145
9589
|
if (!progress || progress.planid !== plan.id) return false;
|
|
9146
9590
|
const required = plan.steps.map((step) => step.id);
|
|
@@ -9306,7 +9750,7 @@ function recordtoolcall(progress, planid, stepid, entry, now) {
|
|
|
9306
9750
|
}
|
|
9307
9751
|
|
|
9308
9752
|
// version.ts
|
|
9309
|
-
var packageversion = "1.1.
|
|
9753
|
+
var packageversion = "1.1.60";
|
|
9310
9754
|
|
|
9311
9755
|
// types.ts
|
|
9312
9756
|
var protocolversion = packageversion;
|
|
@@ -10270,6 +10714,16 @@ function runhistoryquery(value) {
|
|
|
10270
10714
|
function runhistoryreport(input) {
|
|
10271
10715
|
return { version: protocolversion, entries: input.entries, query: input.query ?? {} };
|
|
10272
10716
|
}
|
|
10717
|
+
function environmentreport(input) {
|
|
10718
|
+
return {
|
|
10719
|
+
version: protocolversion,
|
|
10720
|
+
environments: Object.entries(input.environments).map(([stepid, environment]) => ({ stepid, environment })),
|
|
10721
|
+
turnarounds: Object.entries(input.turnarounds ?? {}).map(([stepid, milliseconds]) => ({ stepid, milliseconds })),
|
|
10722
|
+
offscreen: input.offscreen ?? [],
|
|
10723
|
+
workers: input.workers ?? 0,
|
|
10724
|
+
...input.keepalive !== void 0 ? { keepalive: input.keepalive } : {}
|
|
10725
|
+
};
|
|
10726
|
+
}
|
|
10273
10727
|
|
|
10274
10728
|
// capture.ts
|
|
10275
10729
|
var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
|
|
@@ -12211,6 +12665,41 @@ function unreadcount(mailboxes, agentid) {
|
|
|
12211
12665
|
return mailboxof(mailboxes, agentid).unread;
|
|
12212
12666
|
}
|
|
12213
12667
|
|
|
12668
|
+
// sandboxframe.ts
|
|
12669
|
+
function stripscripts(markup) {
|
|
12670
|
+
return markup.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<script\b[^>]*\/>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/javascript:/gi, "").trim();
|
|
12671
|
+
}
|
|
12672
|
+
function nonceof(seed) {
|
|
12673
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
12674
|
+
let hash = 0;
|
|
12675
|
+
for (let index = 0; index < seed.length; index += 1) hash = hash * 31 + seed.charCodeAt(index) >>> 0;
|
|
12676
|
+
let nonce = "";
|
|
12677
|
+
let state = hash === 0 ? 2654435769 : hash;
|
|
12678
|
+
for (let index = 0; index < 16; index += 1) {
|
|
12679
|
+
state = state * 1664525 + 1013904223 >>> 0;
|
|
12680
|
+
nonce += alphabet[state % alphabet.length];
|
|
12681
|
+
}
|
|
12682
|
+
return nonce;
|
|
12683
|
+
}
|
|
12684
|
+
function sandboxrenderof(input) {
|
|
12685
|
+
if (input.markup.trim() === "") throw new Error("The sandbox render needs its untrusted markup.");
|
|
12686
|
+
if (input.sourceorigin.trim() === "") throw new Error("The sandbox render needs the source origin of its untrusted markup.");
|
|
12687
|
+
if (input.stepid.trim() === "") throw new Error("The sandbox render names the reviewed step it renders for.");
|
|
12688
|
+
return { id: input.id, nonce: nonceof(`${input.id}:${input.now}`), markup: stripscripts(input.markup), sourceorigin: input.sourceorigin, stepid: input.stepid, renderedat: input.now };
|
|
12689
|
+
}
|
|
12690
|
+
function rendermessage(render) {
|
|
12691
|
+
return { channel: "devthinksandbox", type: "render", nonce: render.nonce, markup: render.markup };
|
|
12692
|
+
}
|
|
12693
|
+
function acceptrenderresult(input) {
|
|
12694
|
+
if (input.message.channel !== "devthinksandbox") return { accepted: false, reason: "The sandbox message travels the devthinksandbox channel only." };
|
|
12695
|
+
if (input.message.type !== "renderresult") return { accepted: false, reason: "The sandbox message answers with the renderresult type only." };
|
|
12696
|
+
const render = input.renders.find((entry) => entry.nonce === input.message.nonce && entry.renderedat <= input.now);
|
|
12697
|
+
if (!render) return { accepted: false, reason: "The sandbox message carries no nonce of a known render; a stale or replayed message never passes." };
|
|
12698
|
+
const text2 = (input.message.text ?? "").replace(/<[^>]*>/g, "");
|
|
12699
|
+
const result = { nonce: render.nonce, ok: input.message.ok !== false, text: text2, summary: input.message.summary?.trim() || `The sandbox frame rendered the markup of the step ${render.stepid} and returned its inert text.`, at: input.now };
|
|
12700
|
+
return { accepted: true, result, reason: `The render result of the step ${render.stepid} answers the nonce of its render; the text stays inside the frame.` };
|
|
12701
|
+
}
|
|
12702
|
+
|
|
12214
12703
|
// modelroute.ts
|
|
12215
12704
|
function routevalid(route) {
|
|
12216
12705
|
if (route.kind.trim() === "") return { allowed: false, reason: "The model route needs its task kind." };
|
|
@@ -13516,6 +14005,260 @@ async function dispatchpagestep(step, tabid2, origin, plan) {
|
|
|
13516
14005
|
await harvestdialogs(session, activeplan, step.id, tabid2);
|
|
13517
14006
|
return result[0]?.result;
|
|
13518
14007
|
}
|
|
14008
|
+
var roadmapheartbeat = 3e4;
|
|
14009
|
+
var roadmapzombieintervals = 3;
|
|
14010
|
+
var runstateprofile = "default";
|
|
14011
|
+
var keepaliveport;
|
|
14012
|
+
var workerpoolcounts = /* @__PURE__ */ new Map();
|
|
14013
|
+
async function offscreengranted() {
|
|
14014
|
+
try {
|
|
14015
|
+
return await chrome.permissions.contains({ permissions: ["offscreen"] });
|
|
14016
|
+
} catch {
|
|
14017
|
+
return false;
|
|
14018
|
+
}
|
|
14019
|
+
}
|
|
14020
|
+
async function offscreendeclaration() {
|
|
14021
|
+
try {
|
|
14022
|
+
const response = await fetch(chrome.runtime.getURL("manifest.json"));
|
|
14023
|
+
const manifest = await response.json();
|
|
14024
|
+
if (!manifest.offscreen?.document || !manifest.offscreen.reasons?.length || !manifest.offscreen.justification?.trim()) return void 0;
|
|
14025
|
+
return { document: manifest.offscreen.document, reasons: manifest.offscreen.reasons, justification: manifest.offscreen.justification };
|
|
14026
|
+
} catch {
|
|
14027
|
+
return void 0;
|
|
14028
|
+
}
|
|
14029
|
+
}
|
|
14030
|
+
async function requestoffscreengrant() {
|
|
14031
|
+
try {
|
|
14032
|
+
return await chrome.permissions.request({ permissions: ["offscreen"] });
|
|
14033
|
+
} catch {
|
|
14034
|
+
return false;
|
|
14035
|
+
}
|
|
14036
|
+
}
|
|
14037
|
+
async function ensureoffscreendocument(runid) {
|
|
14038
|
+
const offscreen = chrome.offscreen;
|
|
14039
|
+
if (!offscreen) return false;
|
|
14040
|
+
const declaration = await offscreendeclaration();
|
|
14041
|
+
if (!declaration) return false;
|
|
14042
|
+
const opened = openoffscreen({ registry: await memory.getoffscreenentries(), document: declaration.document, runid, reasons: declaration.reasons, justification: declaration.justification, now: Date.now() });
|
|
14043
|
+
if (!opened.reused) {
|
|
14044
|
+
await memory.addoffscreenentry(opened.entry);
|
|
14045
|
+
await audit("worker", `The offscreen document ${declaration.document} spawned for the run ${runid} under the reasons ${declaration.reasons.join(", ")} with the reviewed justification.`, {});
|
|
14046
|
+
}
|
|
14047
|
+
try {
|
|
14048
|
+
if (!await offscreen.hasDocument()) await offscreen.createDocument({ url: chrome.runtime.getURL(declaration.document), reasons: declaration.reasons, justification: declaration.justification });
|
|
14049
|
+
return true;
|
|
14050
|
+
} catch {
|
|
14051
|
+
return false;
|
|
14052
|
+
}
|
|
14053
|
+
}
|
|
14054
|
+
async function closeoffscreendocument(runid) {
|
|
14055
|
+
const offscreen = chrome.offscreen;
|
|
14056
|
+
const outcome = closeoffscreen(await memory.getoffscreenentries(), runid, Date.now());
|
|
14057
|
+
if (!outcome.closed) return;
|
|
14058
|
+
const entry = outcome.registry.find((candidate) => candidate.runid === runid && candidate.closedat !== void 0);
|
|
14059
|
+
if (entry) await memory.updateoffscreenentry(entry);
|
|
14060
|
+
try {
|
|
14061
|
+
if (offscreen && await offscreen.hasDocument()) await offscreen.closeDocument();
|
|
14062
|
+
} catch {
|
|
14063
|
+
}
|
|
14064
|
+
await audit("worker", `The offscreen document of the run ${runid} closed with its worker pool; the registry keeps the reasons and justification of the spawn for the audit.`, {});
|
|
14065
|
+
}
|
|
14066
|
+
function startkeepaliveport(runid) {
|
|
14067
|
+
if (keepaliveport) return;
|
|
14068
|
+
try {
|
|
14069
|
+
keepaliveport = chrome.runtime.connect({ name: `devthinkkeepalive:${runid}` });
|
|
14070
|
+
keepaliveport.onDisconnect.addListener(() => {
|
|
14071
|
+
keepaliveport = void 0;
|
|
14072
|
+
});
|
|
14073
|
+
} catch {
|
|
14074
|
+
}
|
|
14075
|
+
}
|
|
14076
|
+
async function openplanrun(session, plan) {
|
|
14077
|
+
const settings = await memory.getsettings();
|
|
14078
|
+
const interval = settings?.keepaliveinterval ?? roadmapheartbeat;
|
|
14079
|
+
const gate = keepalivegate({ session, plan, now: Date.now() });
|
|
14080
|
+
if (!gate.allowed) return;
|
|
14081
|
+
const existing = await memory.getrunstate(runstateprofile);
|
|
14082
|
+
if (existing && existing.keepalive.state === "active") return;
|
|
14083
|
+
const state = openrun({ runid: plan.id, sessionid: session.id, planid: plan.id, profileid: runstateprofile, interval, now: Date.now() });
|
|
14084
|
+
await memory.setrunstate(runstateprofile, state);
|
|
14085
|
+
const lock = acquirerunlock({ locks: await memory.getrunlocks(), sessionid: session.id, runid: plan.id, holder: "planrun", now: Date.now() });
|
|
14086
|
+
if (lock.acquired) await memory.setrunlocks(lock.locks);
|
|
14087
|
+
startkeepaliveport(plan.id);
|
|
14088
|
+
await audit("environment", `The run state of the plan ${plan.id} opened with its keepalive port beating every ${interval} milliseconds${lock.acquired ? " and the session locked against concurrent runs" : ""}.`, { sessionid: session.id, planid: plan.id });
|
|
14089
|
+
}
|
|
14090
|
+
async function markpendingstep(plan, stepid) {
|
|
14091
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14092
|
+
if (!state || state.runid !== plan.id || state.keepalive.state !== "active") return;
|
|
14093
|
+
await memory.setrunstate(runstateprofile, markpending(state, stepid, Date.now()));
|
|
14094
|
+
}
|
|
14095
|
+
async function recordstepenvironment(step, environment, session, plan, origin, turnaround) {
|
|
14096
|
+
if (session && plan) {
|
|
14097
|
+
await memory.setprogress(recordenvironment2(await memory.getprogress(), plan.id, step.id, environment, Date.now()));
|
|
14098
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14099
|
+
if (state && state.runid === plan.id) {
|
|
14100
|
+
let next = recordenvironment(state, { stepid: step.id, environment, origin, now: Date.now() });
|
|
14101
|
+
if (turnaround !== void 0) next = recordturnaround(next, { stepid: step.id, milliseconds: turnaround, now: Date.now() });
|
|
14102
|
+
if (["navigate", "openlink", "followlink", "back", "forward"].includes(step.kind) && step.value) next = recordurl(next, { url: step.value, stepid: step.id, now: Date.now() });
|
|
14103
|
+
await memory.setrunstate(runstateprofile, next);
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
14106
|
+
if (plan && turnaround !== void 0) await memory.setprogress(recordturnaround2(await memory.getprogress(), plan.id, step.id, turnaround, Date.now()));
|
|
14107
|
+
}
|
|
14108
|
+
async function closeplanrun(planid, sessionid) {
|
|
14109
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14110
|
+
if (!state || state.runid !== planid || state.keepalive.state !== "active") return;
|
|
14111
|
+
await memory.setrunstate(runstateprofile, closerun(state, Date.now()));
|
|
14112
|
+
const release = releaserunlock({ locks: await memory.getrunlocks(), sessionid, runid: planid, now: Date.now() });
|
|
14113
|
+
if (release.released) await memory.setrunlocks(release.locks);
|
|
14114
|
+
try {
|
|
14115
|
+
keepaliveport?.disconnect();
|
|
14116
|
+
} catch {
|
|
14117
|
+
}
|
|
14118
|
+
keepaliveport = void 0;
|
|
14119
|
+
await closeoffscreendocument(planid);
|
|
14120
|
+
await audit("environment", `The run state of the plan ${planid} closed at its terminal state and the keepalive port released.`, { ...sessionid !== "" ? { sessionid } : {}, planid });
|
|
14121
|
+
}
|
|
14122
|
+
async function executeisolatedevaluate(step, tabid2, origin) {
|
|
14123
|
+
const injection = isolatedinjection(step);
|
|
14124
|
+
const result = await chrome.scripting.executeScript({ target: { tabId: tabid2 }, world: "ISOLATED", func: (code, args, expectedorigin) => {
|
|
14125
|
+
if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before the evaluate step ran." };
|
|
14126
|
+
try {
|
|
14127
|
+
let outcome;
|
|
14128
|
+
try {
|
|
14129
|
+
outcome = new Function(`"use strict"; return (${code});`)(...args);
|
|
14130
|
+
} catch {
|
|
14131
|
+
outcome = new Function(`"use strict"; ${code}`)(...args);
|
|
14132
|
+
}
|
|
14133
|
+
return { ok: true, summary: `Reviewed expression returned ${outcome === void 0 ? "no value" : "a value"} inside the isolated world.`, details: { result: String(outcome) } };
|
|
14134
|
+
} catch (error) {
|
|
14135
|
+
return { ok: false, summary: `Reviewed expression failed inside the isolated world: ${error instanceof Error ? error.message : String(error)}` };
|
|
14136
|
+
}
|
|
14137
|
+
}, args: [injection.code, injection.args, origin] });
|
|
14138
|
+
return result[0]?.result ?? { ok: false, summary: "The isolated world returned no result." };
|
|
14139
|
+
}
|
|
14140
|
+
async function executesandboxrender(step, session, plan, origin) {
|
|
14141
|
+
const options = stepoptions2(step);
|
|
14142
|
+
const markup = typeof options.markup === "string" ? options.markup : "";
|
|
14143
|
+
const sourceorigin = typeof options.sourceorigin === "string" ? options.sourceorigin : origin;
|
|
14144
|
+
const settings = await memory.getsettings();
|
|
14145
|
+
const origingate = sandboxorigingate({ origin: sourceorigin, allowed: settings?.sandboxorigins ?? [] });
|
|
14146
|
+
if (!origingate.allowed) throw new Error(origingate.reason);
|
|
14147
|
+
const render = sandboxrenderof({ id: randomid(), markup, sourceorigin, stepid: step.id, now: Date.now() });
|
|
14148
|
+
await memory.addsandboxrender(render);
|
|
14149
|
+
const hostready = await ensureoffscreendocument(plan?.id ?? step.id);
|
|
14150
|
+
let answer;
|
|
14151
|
+
if (hostready) {
|
|
14152
|
+
try {
|
|
14153
|
+
answer = await chrome.runtime.sendMessage({ kind: "offscreen", action: "sandboxrender", render: { id: render.id, nonce: render.nonce, markup: render.markup, sourceorigin: render.sourceorigin, stepid: render.stepid } });
|
|
14154
|
+
} catch {
|
|
14155
|
+
}
|
|
14156
|
+
}
|
|
14157
|
+
if (!answer) {
|
|
14158
|
+
try {
|
|
14159
|
+
answer = await chrome.runtime.sendMessage({ kind: "environments", action: "sandboxhost", render: rendermessage(render) });
|
|
14160
|
+
} catch {
|
|
14161
|
+
}
|
|
14162
|
+
}
|
|
14163
|
+
if (!answer) throw new Error("The sandbox frame needs an open host surface: grant the offscreen capability or keep the review panel open so the sandboxed page can render the untrusted markup.");
|
|
14164
|
+
const accepted = acceptrenderresult({ renders: [render], message: { channel: "devthinksandbox", type: "renderresult", nonce: render.nonce, ok: answer.ok, ...answer.text !== void 0 ? { text: answer.text } : {}, summary: answer.summary }, now: Date.now() });
|
|
14165
|
+
if (!accepted.accepted || !accepted.result) throw new Error(accepted.reason);
|
|
14166
|
+
await audit("sandbox", `The step ${step.id} rendered untrusted markup from ${sourceorigin} inside the sandbox frame under the nonce ${render.nonce}; scripts and handlers were stripped before the render and the text never reentered the dom outside the frame.`, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
14167
|
+
await recordstepenvironment(step, "sandboxframe", session, plan, sourceorigin, void 0);
|
|
14168
|
+
return { ok: accepted.result.ok, summary: accepted.result.summary, details: { text: accepted.result.text, nonce: render.nonce, sourceorigin } };
|
|
14169
|
+
}
|
|
14170
|
+
async function offloadparsetoworker(step, output, session, plan, origin) {
|
|
14171
|
+
const runid = plan?.id ?? step.id;
|
|
14172
|
+
const ready = await ensureoffscreendocument(runid);
|
|
14173
|
+
if (!ready) return { output, turnaround: void 0 };
|
|
14174
|
+
const payload = JSON.stringify({ summary: output?.summary ?? "", details: output?.details ?? {} });
|
|
14175
|
+
const request = workerrequestof({ id: randomid(), runid, stepid: step.id, kind: step.kind, payload, options: stepoptions2(step), sentat: Date.now() });
|
|
14176
|
+
const started = Date.now();
|
|
14177
|
+
let answer;
|
|
14178
|
+
try {
|
|
14179
|
+
answer = await chrome.runtime.sendMessage({ kind: "offscreen", action: "parse", request: { id: request.id, runid: request.runid, stepid: request.stepid, task: request.task, payload: request.payload, transferables: request.transferables } });
|
|
14180
|
+
} catch {
|
|
14181
|
+
}
|
|
14182
|
+
const turnaround = Date.now() - started;
|
|
14183
|
+
if (!answer) return { output, turnaround: void 0 };
|
|
14184
|
+
const provenance = { origin, stepid: step.id, environment: "offscreenworker" };
|
|
14185
|
+
await memory.addworkerevent({ id: randomid(), runid, kind: "spawn", workers: workerpoolcounts.get(runid) ?? 1, reason: `The ${request.task} parse of the step ${step.id} ran inside the offscreen worker pool with the transferable keys ${request.transferables.length > 0 ? request.transferables.join(", ") : "none"}.`, provenance, at: Date.now() });
|
|
14186
|
+
await audit("worker", `The ${request.task} parse of the step ${step.id} ran inside the offscreen worker pool and answered in ${turnaround} milliseconds${answer.ok === false ? " with a refusal the inline parse covers" : ""}.`, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
14187
|
+
const parsed = { ok: answer.ok !== false && (output?.ok ?? true), summary: answer.summary ?? output?.summary ?? "The offscreen worker answered the parse.", details: { ...output?.details ?? {}, workerparse: answer.result ?? "", task: request.task, turnaround } };
|
|
14188
|
+
return { output: parsed, turnaround };
|
|
14189
|
+
}
|
|
14190
|
+
async function environmentviewof() {
|
|
14191
|
+
const session = await memory.getsession();
|
|
14192
|
+
const settings = await memory.getsettings();
|
|
14193
|
+
const states = await memory.listrunstates();
|
|
14194
|
+
const swept = zombiesweep({ states, now: Date.now(), interval: settings?.keepaliveinterval ?? roadmapheartbeat, missedlimit: settings?.zombieintervals ?? roadmapzombieintervals });
|
|
14195
|
+
const open = states.find((state) => state.keepalive.state === "active") ?? states[0];
|
|
14196
|
+
const workers = (await chrome.runtime.sendMessage({ kind: "offscreen", action: "pool" }).catch(() => void 0))?.workers ?? 0;
|
|
14197
|
+
return {
|
|
14198
|
+
report: environmentreport({ environments: open?.environments ?? {}, turnarounds: open?.turnarounds ?? {}, offscreen: await memory.getoffscreenentries(), workers, ...open !== void 0 ? { keepalive: { runid: open.keepalive.runid, state: open.keepalive.state, beats: open.keepalive.beats, lastbeatat: open.keepalive.lastbeatat, portopen: open.keepalive.portopen } } : {} }),
|
|
14199
|
+
...session?.environmentgrants !== void 0 ? { grants: session.environmentgrants } : {},
|
|
14200
|
+
requirements: environmentrequirements(),
|
|
14201
|
+
registry: executorregistry(),
|
|
14202
|
+
offscreengranted: await offscreengranted(),
|
|
14203
|
+
parseoffload: settings?.parseoffload === true,
|
|
14204
|
+
...settings?.workerpoolsize !== void 0 ? { workerpoolsize: settings.workerpoolsize } : {},
|
|
14205
|
+
...settings?.sandboxorigins !== void 0 ? { sandboxorigins: settings.sandboxorigins } : {},
|
|
14206
|
+
workerevents: (await memory.getworkerevents()).map((event) => ({ id: event.id, runid: event.runid, kind: event.kind, workers: event.workers, reason: event.reason, at: event.at })),
|
|
14207
|
+
runstates: states.map((state) => ({ runid: state.runid, sessionid: state.sessionid, state: state.state, beats: state.keepalive.beats, lastbeatat: state.keepalive.lastbeatat, ...state.pendingstepid !== void 0 ? { pendingstepid: state.pendingstepid } : {}, urls: state.urlhistory.length })),
|
|
14208
|
+
locks: await memory.getrunlocks(),
|
|
14209
|
+
zombies: swept.reaped,
|
|
14210
|
+
urls: (open?.urlhistory ?? []).slice(-50),
|
|
14211
|
+
...open !== void 0 ? { recovery: recoveryplan(open) } : {},
|
|
14212
|
+
...open !== void 0 && open.state === "recovered" ? { restartnotice: `The service worker restarted and the run ${open.runid} reattached from its persisted run state; the pending step ${open.pendingstepid ?? "none"} waits for the user.` } : {}
|
|
14213
|
+
};
|
|
14214
|
+
}
|
|
14215
|
+
async function keepalivetick() {
|
|
14216
|
+
const settings = await memory.getsettings();
|
|
14217
|
+
const interval = settings?.keepaliveinterval ?? roadmapheartbeat;
|
|
14218
|
+
const tolerance = settings?.zombieintervals ?? roadmapzombieintervals;
|
|
14219
|
+
for (const state of await memory.listrunstates()) {
|
|
14220
|
+
if (state.keepalive.state !== "active") continue;
|
|
14221
|
+
await memory.setrunstate(state.profileid, beatrun(state, Date.now()));
|
|
14222
|
+
startkeepaliveport(state.runid);
|
|
14223
|
+
}
|
|
14224
|
+
const swept = zombiesweep({ states: await memory.listrunstates(), now: Date.now(), interval, missedlimit: tolerance });
|
|
14225
|
+
for (const runid of swept.reaped) {
|
|
14226
|
+
await closeoffscreendocument(runid);
|
|
14227
|
+
await audit("environment", `The zombie reaper closed the run ${runid} whose heartbeat fell silent past the ${tolerance} tolerated interval${tolerance === 1 ? "" : "s"}; the popup offers the reap action.`, {});
|
|
14228
|
+
}
|
|
14229
|
+
for (const state of swept.states) await memory.setrunstate(state.profileid, state);
|
|
14230
|
+
await memory.expirerunstates(settings?.runstateretention, Date.now());
|
|
14231
|
+
const locks = expirerunlocks(await memory.getrunlocks(), Date.now());
|
|
14232
|
+
if (locks.expired.length > 0) await memory.setrunlocks(locks.locks);
|
|
14233
|
+
try {
|
|
14234
|
+
const estimate = await navigator.storage.estimate();
|
|
14235
|
+
if (estimate.usage !== void 0) await memory.trackrunstatequota(estimate.usage);
|
|
14236
|
+
if (settings?.runstatebytes !== void 0) {
|
|
14237
|
+
const quota = await memory.getrunstatequota();
|
|
14238
|
+
if (quota && quota.used > settings.runstatebytes) {
|
|
14239
|
+
const pruned = prunerunstates({ states: await memory.listrunstates(), used: quota.used, ceiling: settings.runstatebytes });
|
|
14240
|
+
for (const runid of pruned.pruned) {
|
|
14241
|
+
const state = await memory.listrunstates();
|
|
14242
|
+
const target = state.find((entry) => entry.runid === runid);
|
|
14243
|
+
if (target) await memory.removerunstate(target.profileid);
|
|
14244
|
+
}
|
|
14245
|
+
if (pruned.pruned.length > 0) await audit("environment", pruned.reason, {});
|
|
14246
|
+
}
|
|
14247
|
+
}
|
|
14248
|
+
} catch {
|
|
14249
|
+
}
|
|
14250
|
+
}
|
|
14251
|
+
async function restorerunstates() {
|
|
14252
|
+
const settings = await memory.getsettings();
|
|
14253
|
+
const states = await memory.listrunstates();
|
|
14254
|
+
for (const state of states) {
|
|
14255
|
+
if (state.keepalive.state !== "active") continue;
|
|
14256
|
+
await memory.setrunstate(state.profileid, reattachrun(state, Date.now()));
|
|
14257
|
+
startkeepaliveport(state.runid);
|
|
14258
|
+
await audit("environment", `The service worker restarted and the run ${state.runid} reattached its keepalive port from the persisted run state; ${recoveryplan(state).reason}`, {});
|
|
14259
|
+
}
|
|
14260
|
+
void settings;
|
|
14261
|
+
}
|
|
13519
14262
|
async function startsession() {
|
|
13520
14263
|
const { tab, origin } = await activecontext();
|
|
13521
14264
|
const session = { id: randomid(), tabid: tab.id, origin, startedat: Date.now(), expiresat: Date.now() + sessionduration, grants: [origin] };
|
|
@@ -18394,6 +19137,18 @@ async function executestep(stepid) {
|
|
|
18394
19137
|
async function executeaction(step, session, plan, tabid2, origin, settings, verdicts, mode) {
|
|
18395
19138
|
const gate = canexecute({ session, plan, step, tabid: tabid2, origin, ...verdicts !== void 0 && verdicts.length > 0 ? { verdicts } : {}, ...settings !== void 0 ? { settings } : {} });
|
|
18396
19139
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
19140
|
+
const environmentverdict = stepenvironmentvalid(step);
|
|
19141
|
+
if (!environmentverdict.allowed) throw new Error(environmentverdict.reason);
|
|
19142
|
+
const environmentgrantverdict = environmentgrantgate(step, session?.environmentgrants);
|
|
19143
|
+
if (!environmentgrantverdict.allowed) throw new Error(environmentgrantverdict.reason);
|
|
19144
|
+
const offgranted = await offscreengranted();
|
|
19145
|
+
const routing = routeenvironment(step, { offload: settings?.parseoffload === true, granted: offgranted });
|
|
19146
|
+
const capabilityverdict = offscreencapabilitygate({ environment: routing.environment, granted: offgranted });
|
|
19147
|
+
if (!capabilityverdict.allowed) throw new Error(capabilityverdict.reason);
|
|
19148
|
+
if (routing.fallback && plan) await audit("environment", `The ${step.kind} step ${step.id} fell back to inline parsing inside the page because the offscreen capability grant stays absent.`, { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id });
|
|
19149
|
+
if (session && plan && plan.state === "approved" && mode === "plan") await openplanrun(session, plan);
|
|
19150
|
+
if (plan && plan.state === "approved") await markpendingstep(plan, step.id);
|
|
19151
|
+
if (routing.environment === "sandboxframe") return executesandboxrender(step, session, plan, origin);
|
|
18397
19152
|
const capability = requiredcapability(step.kind);
|
|
18398
19153
|
if (capability) {
|
|
18399
19154
|
const granted = await chrome.permissions.contains({ permissions: [capability] });
|
|
@@ -18481,8 +19236,12 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18481
19236
|
return output;
|
|
18482
19237
|
};
|
|
18483
19238
|
const runplan = plan;
|
|
19239
|
+
const isolatedresult = routing.environment === "isolatedworld" ? await executeisolatedevaluate(step, tabid2, origin) : void 0;
|
|
19240
|
+
let workerturnaround;
|
|
18484
19241
|
const capturepolicystate = await runcapturepolicy();
|
|
18485
|
-
if (
|
|
19242
|
+
if (isolatedresult !== void 0) {
|
|
19243
|
+
output = isolatedresult;
|
|
19244
|
+
} else if (capturepolicystate === "beforeafter" && session && step.risk !== "read" && beforeafterwrapallowed(step.kind)) {
|
|
18486
19245
|
const before = await grabstateshot(step, session, runplan, tabid2, "before");
|
|
18487
19246
|
output = await dispatchreviewedstep();
|
|
18488
19247
|
if (output?.ok) {
|
|
@@ -18498,8 +19257,14 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18498
19257
|
} else {
|
|
18499
19258
|
output = await dispatchreviewedstep();
|
|
18500
19259
|
}
|
|
19260
|
+
if (routing.environment === "offscreenworker" && workerturnaround === void 0) {
|
|
19261
|
+
const offloaded = await offloadparsetoworker(step, output, session, plan, origin);
|
|
19262
|
+
output = offloaded.output ?? output;
|
|
19263
|
+
workerturnaround = offloaded.turnaround;
|
|
19264
|
+
}
|
|
18501
19265
|
if (["navigate", "back", "forward"].includes(step.kind)) await recordnavigation(step, session, tabid2);
|
|
18502
19266
|
await recordevidence(step, output, session, plan, origin);
|
|
19267
|
+
await recordstepenvironment(step, routing.environment, session, plan, origin, workerturnaround);
|
|
18503
19268
|
if (output?.ok && plan && typeof output.details?.tabid === "number") {
|
|
18504
19269
|
await memory.setprogress(assigntasktab(await memory.getprogress(), plan.id, output.details.tabid, Date.now()));
|
|
18505
19270
|
}
|
|
@@ -18508,7 +19273,7 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18508
19273
|
if (resolved) {
|
|
18509
19274
|
await memory.addresolution({ stepid: step.id, mode: resolved.mode, selector: resolved.selector, label: resolved.label, at: Date.now() });
|
|
18510
19275
|
}
|
|
18511
|
-
const outcome = { stepid: step.id, ok: Boolean(output?.ok), summary, ...output?.details ? { details: output.details } : {}, at: Date.now() };
|
|
19276
|
+
const outcome = { stepid: step.id, ok: Boolean(output?.ok), summary, environment: routing.environment, ...output?.details ? { details: output.details } : {}, at: Date.now() };
|
|
18512
19277
|
const auditkind = stepauditkind(step, Boolean(output?.ok));
|
|
18513
19278
|
await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
18514
19279
|
await memory.addoutcome(outcome);
|
|
@@ -18540,6 +19305,7 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18540
19305
|
});
|
|
18541
19306
|
const done = { ...plan, state: "completed", completedat: Date.now() };
|
|
18542
19307
|
await memory.setplan(done);
|
|
19308
|
+
await closeplanrun(done.id, session?.id ?? "");
|
|
18543
19309
|
await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
|
|
18544
19310
|
}
|
|
18545
19311
|
}
|
|
@@ -18725,7 +19491,7 @@ async function handlerequest(message, sender) {
|
|
|
18725
19491
|
const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
|
|
18726
19492
|
const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
|
|
18727
19493
|
const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
|
|
18728
|
-
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, trigger: triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() }), triggerretention: runsettings?.triggerretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, runhistoryretention: runsettings?.runhistoryretention, editor: editorstate({ versions: await memory.listworkflowversions(), diffs: await memory.listversiondiffs(), history: await memory.gethistory(), overrides: await memory.listsiteoverrides(), imports: (await memory.listworkflowimports()).map((entry) => ({ id: entry.id, workflowid: entry.record.id, name: entry.record.name, version: entry.record.version, steps: entry.record.steps.length, risk: entry.record.risk, importedat: entry.importedat, ...entry.filename !== void 0 ? { filename: entry.filename } : {} })), backgroundruns: await memory.getbackgroundruns(), watchdog: { ...runsettings?.watchdog !== void 0 ? { config: runsettings.watchdog } : {}, events: await memory.listwatchdogevents() } }), ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {}, mcp: await mcpstateof(), llm: await llmstateof(), swarm: await swarmstateof() };
|
|
19494
|
+
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, trigger: triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() }), triggerretention: runsettings?.triggerretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, runhistoryretention: runsettings?.runhistoryretention, editor: editorstate({ versions: await memory.listworkflowversions(), diffs: await memory.listversiondiffs(), history: await memory.gethistory(), overrides: await memory.listsiteoverrides(), imports: (await memory.listworkflowimports()).map((entry) => ({ id: entry.id, workflowid: entry.record.id, name: entry.record.name, version: entry.record.version, steps: entry.record.steps.length, risk: entry.record.risk, importedat: entry.importedat, ...entry.filename !== void 0 ? { filename: entry.filename } : {} })), backgroundruns: await memory.getbackgroundruns(), watchdog: { ...runsettings?.watchdog !== void 0 ? { config: runsettings.watchdog } : {}, events: await memory.listwatchdogevents() } }), ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {}, mcp: await mcpstateof(), llm: await llmstateof(), swarm: await swarmstateof(), environments: await environmentviewof() };
|
|
18729
19495
|
}
|
|
18730
19496
|
case "capabilities":
|
|
18731
19497
|
return refreshcapabilities();
|
|
@@ -21772,6 +22538,149 @@ async function handlerequest(message, sender) {
|
|
|
21772
22538
|
}
|
|
21773
22539
|
throw new Error("The swarm merge request carries no merge, report, export, compare, lesson, costs, timeline, replay or snapshot action.");
|
|
21774
22540
|
}
|
|
22541
|
+
case "environments": {
|
|
22542
|
+
const input2 = message;
|
|
22543
|
+
const now = Date.now();
|
|
22544
|
+
const session = await memory.getsession();
|
|
22545
|
+
const settings = await memory.getsettings();
|
|
22546
|
+
if (input2.requestcapability === true) {
|
|
22547
|
+
const granted = await requestoffscreengrant();
|
|
22548
|
+
await audit("environment", granted ? "The user granted the optional offscreen capability through the permissions api; the worker pool may spawn under the manifest declaration." : "The offscreen capability request stayed refused; the parse offload keeps its inline fallback inside the page.", { ...session ? { sessionid: session.id } : {} });
|
|
22549
|
+
return { ...await environmentviewof(), requested: granted };
|
|
22550
|
+
}
|
|
22551
|
+
if (input2.grants !== void 0) {
|
|
22552
|
+
if (!session) throw new Error("The environment grants need an active session to join.");
|
|
22553
|
+
const valid = /* @__PURE__ */ new Set(["pagecontext", "isolatedworld", "offscreenworker", "sandboxframe"]);
|
|
22554
|
+
const requested = input2.grants.filter((environment) => valid.has(environment));
|
|
22555
|
+
const unknown = input2.grants.filter((environment) => !valid.has(environment));
|
|
22556
|
+
if (unknown.length > 0) throw new Error(`The environments ${unknown.join(", ")} sit outside the four execution environments.`);
|
|
22557
|
+
await memory.setenvironmentgrants(requested);
|
|
22558
|
+
await audit("environment", `The user set the environment grants of the session to ${requested.length > 0 ? requested.join(", ") : "the documented default posture"}; no step ever widens them.`, { sessionid: session.id });
|
|
22559
|
+
return environmentviewof();
|
|
22560
|
+
}
|
|
22561
|
+
if (input2.pool !== void 0) {
|
|
22562
|
+
const sizegate = workerpoolsizevalid(input2.pool.size ?? settings?.workerpoolsize);
|
|
22563
|
+
if (!sizegate.allowed) throw new Error(sizegate.reason);
|
|
22564
|
+
const runid = (await memory.getplan())?.id ?? "pool";
|
|
22565
|
+
const current = workerpoolcounts.get(runid) ?? 0;
|
|
22566
|
+
const plan = poolplan({ pending: input2.pool.pending ?? 0, current, ...input2.pool.size !== void 0 ? { size: input2.pool.size } : {} });
|
|
22567
|
+
workerpoolcounts.set(runid, plan.workers);
|
|
22568
|
+
const provenance = { origin: session?.origin ?? "", stepid: "", environment: "offscreenworker" };
|
|
22569
|
+
await memory.addworkerevent({ id: randomid(), runid, kind: plan.added > 0 ? "spawn" : "teardown", workers: plan.workers, reason: plan.reason, provenance, at: now });
|
|
22570
|
+
await audit("worker", `The worker pool of the run ${runid} holds ${plan.workers} worker${plan.workers === 1 ? "" : "s"}: ${plan.reason}`, { ...session ? { sessionid: session.id } : {} });
|
|
22571
|
+
return { ...await environmentviewof(), pool: plan };
|
|
22572
|
+
}
|
|
22573
|
+
if (input2.offscreenclose === true) {
|
|
22574
|
+
const runid = (await memory.getplan())?.id ?? "pool";
|
|
22575
|
+
await closeoffscreendocument(runid);
|
|
22576
|
+
return environmentviewof();
|
|
22577
|
+
}
|
|
22578
|
+
if (input2.settings !== void 0) {
|
|
22579
|
+
const patch = { ...settings };
|
|
22580
|
+
if (input2.settings.parseoffload !== void 0) patch.parseoffload = input2.settings.parseoffload === true;
|
|
22581
|
+
if (input2.settings.workerpoolsize !== void 0) {
|
|
22582
|
+
const sizegate = workerpoolsizevalid(input2.settings.workerpoolsize);
|
|
22583
|
+
if (!sizegate.allowed) throw new Error(sizegate.reason);
|
|
22584
|
+
patch.workerpoolsize = input2.settings.workerpoolsize;
|
|
22585
|
+
}
|
|
22586
|
+
if (input2.settings.sandboxorigins !== void 0) patch.sandboxorigins = input2.settings.sandboxorigins.map((origin) => origin.trim()).filter((origin) => origin !== "");
|
|
22587
|
+
if (input2.settings.keepaliveinterval !== void 0) {
|
|
22588
|
+
const intervalgate = keepaliveintervalvalid(input2.settings.keepaliveinterval);
|
|
22589
|
+
if (!intervalgate.allowed) throw new Error(intervalgate.reason);
|
|
22590
|
+
patch.keepaliveinterval = input2.settings.keepaliveinterval;
|
|
22591
|
+
}
|
|
22592
|
+
if (input2.settings.zombieintervals !== void 0) patch.zombieintervals = input2.settings.zombieintervals;
|
|
22593
|
+
if (input2.settings.runstateretention !== void 0) patch.runstateretention = input2.settings.runstateretention;
|
|
22594
|
+
if (input2.settings.runstatebytes !== void 0) patch.runstatebytes = input2.settings.runstatebytes;
|
|
22595
|
+
await memory.setsettings(patch);
|
|
22596
|
+
await audit("environment", `The user updated the environment settings: parse offload ${patch.parseoffload === true ? "on" : "off"}, worker pool size ${patch.workerpoolsize ?? "queue driven"}, sandbox origins ${patch.sandboxorigins?.length ?? 0} configured, keepalive interval ${patch.keepaliveinterval ?? "the roadmap thirty seconds"}, zombie tolerance ${patch.zombieintervals ?? "the roadmap three intervals"}.`, {});
|
|
22597
|
+
return environmentviewof();
|
|
22598
|
+
}
|
|
22599
|
+
if (input2.render !== void 0) {
|
|
22600
|
+
const result = await executesandboxrender({ id: input2.render.stepid ?? randomid(), kind: "setattribute", summary: "Render untrusted markup inside the sandbox frame.", risk: "sensitive", ...input2.render.markup !== void 0 ? { value: input2.render.markup } : {}, options: JSON.stringify({ markup: input2.render.markup ?? "", ...input2.render.origin !== void 0 ? { sourceorigin: input2.render.origin } : {} }) }, session, await memory.getplan(), input2.render.origin ?? session?.origin ?? "");
|
|
22601
|
+
return { ...await environmentviewof(), render: result };
|
|
22602
|
+
}
|
|
22603
|
+
return environmentviewof();
|
|
22604
|
+
}
|
|
22605
|
+
case "runstate": {
|
|
22606
|
+
const input2 = message;
|
|
22607
|
+
const now = Date.now();
|
|
22608
|
+
const session = await memory.getsession();
|
|
22609
|
+
const plan = await memory.getplan();
|
|
22610
|
+
const settings = await memory.getsettings();
|
|
22611
|
+
const states = await memory.listrunstates();
|
|
22612
|
+
if (input2.start === true) {
|
|
22613
|
+
if (!session || !plan) throw new Error("The run state opens behind an active session with an approved plan.");
|
|
22614
|
+
const gate = keepalivegate({ session, plan, now });
|
|
22615
|
+
if (!gate.allowed) throw new Error(gate.reason);
|
|
22616
|
+
await openplanrun(session, plan);
|
|
22617
|
+
return { ...await environmentviewof(), opened: plan.id };
|
|
22618
|
+
}
|
|
22619
|
+
if (input2.stop === true) {
|
|
22620
|
+
const open = states.find((state) => state.keepalive.state === "active");
|
|
22621
|
+
if (open) await closeplanrun(open.runid, open.sessionid);
|
|
22622
|
+
return environmentviewof();
|
|
22623
|
+
}
|
|
22624
|
+
if (input2.beat === true) {
|
|
22625
|
+
await keepalivetick();
|
|
22626
|
+
return environmentviewof();
|
|
22627
|
+
}
|
|
22628
|
+
if (input2.recover === true) {
|
|
22629
|
+
const open = states.find((state) => state.keepalive.state === "active" || state.state === "recovered");
|
|
22630
|
+
if (!open) throw new Error("No interrupted run waits for its recovery.");
|
|
22631
|
+
const recovery = recoveryplan(open);
|
|
22632
|
+
await memory.setrunstate(open.profileid, reattachrun(open, now));
|
|
22633
|
+
await audit("environment", recovery.reason, { sessionid: open.sessionid });
|
|
22634
|
+
if (recovery.recoverable && recovery.pendingstepid !== void 0 && plan && plan.id === open.planid && plan.state === "approved") {
|
|
22635
|
+
const outcome = await executestep(recovery.pendingstepid);
|
|
22636
|
+
return { ...await environmentviewof(), recovery, resumed: outcome.summary };
|
|
22637
|
+
}
|
|
22638
|
+
return { ...await environmentviewof(), recovery };
|
|
22639
|
+
}
|
|
22640
|
+
if (input2.reap === true) {
|
|
22641
|
+
const swept = zombiesweep({ states, now, interval: settings?.keepaliveinterval ?? roadmapheartbeat, missedlimit: settings?.zombieintervals ?? roadmapzombieintervals });
|
|
22642
|
+
for (const state of swept.states) await memory.setrunstate(state.profileid, state);
|
|
22643
|
+
for (const runid of swept.reaped) await closeoffscreendocument(runid);
|
|
22644
|
+
if (swept.reaped.length > 0) await audit("environment", `The user reaped the zombie run${swept.reaped.length === 1 ? "" : "s"} ${swept.reaped.join(", ")} from the popup warning.`, {});
|
|
22645
|
+
return { ...await environmentviewof(), reaped: swept.reaped };
|
|
22646
|
+
}
|
|
22647
|
+
if (input2.lock !== void 0) {
|
|
22648
|
+
if (input2.lock.acquire !== void 0) {
|
|
22649
|
+
if (!session) throw new Error("The run lock needs its active session.");
|
|
22650
|
+
const runid = input2.lock.acquire.runid?.trim() ?? plan?.id ?? "";
|
|
22651
|
+
const outcome = acquirerunlock({ locks: await memory.getrunlocks(), sessionid: session.id, runid, holder: input2.lock.acquire.holder?.trim() ?? "user", ...input2.lock.acquire.expiresat !== void 0 ? { expiresat: input2.lock.acquire.expiresat } : {}, now });
|
|
22652
|
+
if (!outcome.acquired) throw new Error(outcome.reason);
|
|
22653
|
+
await memory.setrunlocks(outcome.locks);
|
|
22654
|
+
await audit("environment", outcome.reason, { sessionid: session.id });
|
|
22655
|
+
return { ...await environmentviewof(), lock: outcome.reason };
|
|
22656
|
+
}
|
|
22657
|
+
if (input2.lock.release !== void 0) {
|
|
22658
|
+
if (!session) throw new Error("The run lock release needs its active session.");
|
|
22659
|
+
const outcome = releaserunlock({ locks: await memory.getrunlocks(), sessionid: session.id, runid: input2.lock.release.runid?.trim() ?? plan?.id ?? "", now });
|
|
22660
|
+
if (!outcome.released) throw new Error(outcome.reason);
|
|
22661
|
+
await memory.setrunlocks(outcome.locks);
|
|
22662
|
+
await audit("environment", outcome.reason, { sessionid: session.id });
|
|
22663
|
+
return { ...await environmentviewof(), lock: outcome.reason };
|
|
22664
|
+
}
|
|
22665
|
+
}
|
|
22666
|
+
if (input2.serialize !== void 0) {
|
|
22667
|
+
const branches = (input2.serialize.branches ?? []).map((branch) => ({ branchid: branch.branchid?.trim() ?? "", steps: (branch.steps ?? []).filter((step) => step.stepid !== void 0 && step.tabid !== void 0).map((step) => ({ stepid: step.stepid, tabid: step.tabid })) })).filter((branch) => branch.branchid !== "");
|
|
22668
|
+
const order = serializesteps({ branches });
|
|
22669
|
+
await audit("environment", `The executor serialized ${order.length} step${order.length === 1 ? "" : "s"} across parallel branches so steps sharing one tab never run inside the same beat; every other step keeps its branch order.`, {});
|
|
22670
|
+
return { order };
|
|
22671
|
+
}
|
|
22672
|
+
if (input2.export === true) {
|
|
22673
|
+
const exported = await memory.exportrunstates();
|
|
22674
|
+
await audit("environment", `The run states were exported as one audit record: ${exported.runs} run${exported.runs === 1 ? "" : "s"}, ${exported.urls} url entr${exported.urls === 1 ? "y" : "ies"}, ${exported.environments} environment record${exported.environments === 1 ? "" : "s"} and ${exported.offloaded} worker turnaround${exported.offloaded === 1 ? "" : "s"}.`, {});
|
|
22675
|
+
return { export: exported, states: states.map((state) => ({ runid: state.runid, sessionid: state.sessionid, state: state.state, beats: state.keepalive.beats, lastbeatat: state.keepalive.lastbeatat, pendingstepid: state.pendingstepid, urlhistory: state.urlhistory, environments: state.environments, turnarounds: state.turnarounds })) };
|
|
22676
|
+
}
|
|
22677
|
+
if (input2.profileid !== void 0) {
|
|
22678
|
+
const state = await memory.getrunstate(input2.profileid);
|
|
22679
|
+
if (!state) throw new Error(`No run state exists for the profile ${input2.profileid}.`);
|
|
22680
|
+
return { state };
|
|
22681
|
+
}
|
|
22682
|
+
return environmentviewof();
|
|
22683
|
+
}
|
|
21775
22684
|
default:
|
|
21776
22685
|
throw new Error("Unknown Devthink request.");
|
|
21777
22686
|
}
|
|
@@ -22565,6 +23474,10 @@ setInterval(() => {
|
|
|
22565
23474
|
void evaluatelistedtriggers().then(() => draintriggerqueue()).then(() => runwatchdog()).then(() => restorebackgroundruns()).catch(() => {
|
|
22566
23475
|
});
|
|
22567
23476
|
}, 3e4);
|
|
23477
|
+
setInterval(() => {
|
|
23478
|
+
void keepalivetick().catch(() => {
|
|
23479
|
+
});
|
|
23480
|
+
}, roadmapheartbeat);
|
|
22568
23481
|
async function restoretriggers() {
|
|
22569
23482
|
await registermenurules();
|
|
22570
23483
|
await evaluatelistedtriggers();
|
|
@@ -22576,4 +23489,6 @@ async function restoretriggers() {
|
|
|
22576
23489
|
}
|
|
22577
23490
|
restoretriggers().catch(() => {
|
|
22578
23491
|
});
|
|
23492
|
+
restorerunstates().catch(() => {
|
|
23493
|
+
});
|
|
22579
23494
|
//# sourceMappingURL=background.js.map
|