@wenathlan/extension 1.1.59 → 1.1.61
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 +5 -3
- package/dist/cli.js +19 -2
- package/dist/environments.d.ts +96 -0
- package/dist/environments.d.ts.map +1 -0
- package/dist/immutablelog.d.ts +73 -0
- package/dist/immutablelog.d.ts.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1092 -1
- package/dist/index.js.map +4 -4
- package/dist/maskinputs.d.ts +52 -0
- package/dist/maskinputs.d.ts.map +1 -0
- package/dist/memory.d.ts +119 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/originpolicy.d.ts +150 -0
- package/dist/originpolicy.d.ts.map +1 -0
- package/dist/policy.d.ts +87 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +121 -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 +312 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1629 -50
- 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 +98 -28
- package/extension/dist/pagebridge.js.map +3 -3
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +77 -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 +572 -0
- package/extension/dist/sidepanel.js.map +4 -4
- package/extension/manifest.json +16 -2
- package/package.json +1 -1
|
@@ -1914,6 +1914,237 @@ 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
|
+
|
|
2081
|
+
// immutablelog.ts
|
|
2082
|
+
async function sha2562(payload) {
|
|
2083
|
+
const bytes = new TextEncoder().encode(payload);
|
|
2084
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
2085
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
2086
|
+
}
|
|
2087
|
+
function entrybody(entry) {
|
|
2088
|
+
return JSON.stringify({ id: entry.id, runid: entry.runid, kind: entry.kind, summary: entry.summary, origin: entry.origin, ...entry.stepid !== void 0 ? { stepid: entry.stepid } : {}, at: entry.at });
|
|
2089
|
+
}
|
|
2090
|
+
async function entryhashof(input) {
|
|
2091
|
+
return { previous: input.previous, current: await sha2562(`${input.previous}
|
|
2092
|
+
${entrybody(input.entry)}`), algorithm: "sha-256" };
|
|
2093
|
+
}
|
|
2094
|
+
async function logentryof(input) {
|
|
2095
|
+
if (input.summary.trim() === "") throw new Error("The log entry needs its summary in plain language.");
|
|
2096
|
+
if (input.origin.trim() === "") throw new Error("The log entry needs its origin provenance.");
|
|
2097
|
+
const entry = { id: input.id ?? randomid(), runid: input.runid, kind: input.kind, summary: input.summary, origin: input.origin, ...input.stepid !== void 0 ? { stepid: input.stepid } : {}, at: input.at };
|
|
2098
|
+
return { ...entry, hash: await entryhashof({ previous: input.previous, entry }) };
|
|
2099
|
+
}
|
|
2100
|
+
function openrunlog(input) {
|
|
2101
|
+
if (input.runid.trim() === "" || input.sessionid.trim() === "") throw new Error("The run log needs its run and session ids.");
|
|
2102
|
+
return { runid: input.runid, sessionid: input.sessionid, entries: [], updatedat: input.now };
|
|
2103
|
+
}
|
|
2104
|
+
function lasthashof(log) {
|
|
2105
|
+
const entry = log.entries[log.entries.length - 1];
|
|
2106
|
+
return entry === void 0 ? "0".repeat(64) : entry.hash.current;
|
|
2107
|
+
}
|
|
2108
|
+
async function appendlogentry(input) {
|
|
2109
|
+
if (input.log.seal !== void 0) throw new Error(`The run log of ${input.log.runid} sealed at ${input.log.seal.sealedat} and accepts no append; the seal is terminal.`);
|
|
2110
|
+
const entry = await logentryof({ ...input.id !== void 0 ? { id: input.id } : {}, runid: input.log.runid, kind: input.kind, summary: input.summary, origin: input.origin, ...input.stepid !== void 0 ? { stepid: input.stepid } : {}, at: input.at, previous: lasthashof(input.log) });
|
|
2111
|
+
return { ...input.log, entries: [...input.log.entries, entry], updatedat: input.at };
|
|
2112
|
+
}
|
|
2113
|
+
async function sealrunlog(log, now) {
|
|
2114
|
+
if (log.seal !== void 0) throw new Error(`The run log of ${log.runid} already sealed at ${log.seal.sealedat}; the seal is terminal.`);
|
|
2115
|
+
if (log.entries.length === 0) throw new Error("The run log seals at completion with at least one entry.");
|
|
2116
|
+
const sealhash = await entryhashof({ previous: lasthashof(log), entry: { id: `seal:${log.runid}`, runid: log.runid, kind: "seal", summary: `The run ${log.runid} completed and the log sealed with ${log.entries.length} entries.`, origin: log.entries[log.entries.length - 1]?.origin ?? log.runid, at: now } });
|
|
2117
|
+
const seal = { runid: log.runid, entries: log.entries.length, sealhash, sealedat: now };
|
|
2118
|
+
return { log: { ...log, seal, updatedat: now }, seal };
|
|
2119
|
+
}
|
|
2120
|
+
async function verifylogchain(entries) {
|
|
2121
|
+
let previous = "0".repeat(64);
|
|
2122
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
2123
|
+
const entry = entries[index];
|
|
2124
|
+
if (entry === void 0) continue;
|
|
2125
|
+
if (entry.hash.previous !== previous) return { valid: false, brokenat: index, reason: `The chain link of entry ${index} carries the previous hash ${entry.hash.previous} while its predecessor hashes to ${previous}; the chain reports tamper evidence.` };
|
|
2126
|
+
const expected = await entryhashof({ previous, entry: { id: entry.id, runid: entry.runid, kind: entry.kind, summary: entry.summary, origin: entry.origin, ...entry.stepid !== void 0 ? { stepid: entry.stepid } : {}, at: entry.at } });
|
|
2127
|
+
if (entry.hash.current !== expected.current) return { valid: false, brokenat: index, reason: `The entry hash of entry ${index} matches neither its body nor its predecessor hash; the chain reports tamper evidence.` };
|
|
2128
|
+
previous = entry.hash.current;
|
|
2129
|
+
}
|
|
2130
|
+
return { valid: true, reason: `The hash chain of ${entries.length} entr${entries.length === 1 ? "y" : "ies"} verifies from the genesis hash to the last entry.` };
|
|
2131
|
+
}
|
|
2132
|
+
async function readverifiedlog(log) {
|
|
2133
|
+
const verification = await verifylogchain(log.entries);
|
|
2134
|
+
if (!verification.valid) return { ok: false, entries: [], reason: verification.reason };
|
|
2135
|
+
return { ok: true, entries: [...log.entries], reason: verification.reason };
|
|
2136
|
+
}
|
|
2137
|
+
async function chainreportof(log) {
|
|
2138
|
+
const verification = await verifylogchain(log.entries);
|
|
2139
|
+
if (!verification.valid) return { runid: log.runid, valid: false, entries: log.entries.length, ...verification.brokenat !== void 0 ? { brokenat: verification.brokenat } : {}, reason: verification.reason };
|
|
2140
|
+
return { runid: log.runid, valid: true, entries: log.entries.length, reason: verification.reason, ...log.seal !== void 0 ? { sealhash: log.seal.sealhash.current, sealedat: log.seal.sealedat } : {} };
|
|
2141
|
+
}
|
|
2142
|
+
async function exportlogchain(log) {
|
|
2143
|
+
const read = await readverifiedlog(log);
|
|
2144
|
+
if (!read.ok) return { runid: log.runid, entries: 0, chainvalid: false, reason: read.reason, log: [] };
|
|
2145
|
+
return { runid: log.runid, entries: read.entries.length, chainvalid: true, reason: read.reason, ...log.seal !== void 0 ? { sealhash: log.seal.sealhash.current, sealedat: log.seal.sealedat } : {}, log: read.entries };
|
|
2146
|
+
}
|
|
2147
|
+
|
|
1917
2148
|
// memory.ts
|
|
1918
2149
|
var sessionmemory = class {
|
|
1919
2150
|
constructor(adapter) {
|
|
@@ -4258,6 +4489,254 @@ var sessionmemory = class {
|
|
|
4258
4489
|
async getswarmcosts() {
|
|
4259
4490
|
return await this.adapter.get("swarmcosts") ?? [];
|
|
4260
4491
|
}
|
|
4492
|
+
/**
|
|
4493
|
+
* Execution environment persistence of the 1.1.60 family.
|
|
4494
|
+
* 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.
|
|
4495
|
+
* The adapter seam keeps every accessor a one line storage delegation so a future worker state backend replaces the adapter only.
|
|
4496
|
+
*/
|
|
4497
|
+
/** Returns the environment grant list of the active session; an absent list keeps the documented default posture. */
|
|
4498
|
+
async getenvironmentgrants() {
|
|
4499
|
+
return (await this.getsession())?.environmentgrants;
|
|
4500
|
+
}
|
|
4501
|
+
/** Replaces the environment grant list of the active session so the environment grants join the origin grants in the session record. */
|
|
4502
|
+
async setenvironmentgrants(grants) {
|
|
4503
|
+
const session = await this.getsession();
|
|
4504
|
+
if (!session) throw new Error("The environment grants need an active session to join.");
|
|
4505
|
+
await this.setsession({ ...session, environmentgrants: grants });
|
|
4506
|
+
}
|
|
4507
|
+
/** 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. */
|
|
4508
|
+
async setrunstate(profileid, state) {
|
|
4509
|
+
const sealed = await sealrunstate(state);
|
|
4510
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4511
|
+
await this.adapter.set(`runstate:${profileid}`, sealed);
|
|
4512
|
+
if (!index.includes(profileid)) await this.adapter.set("runstateindex", [...index, profileid]);
|
|
4513
|
+
}
|
|
4514
|
+
/** Opens the sealed run state of one profile; a missing or tampered seal returns undefined so the recovery never trusts a broken record. */
|
|
4515
|
+
async getrunstate(profileid) {
|
|
4516
|
+
const sealed = await this.adapter.get(`runstate:${profileid}`);
|
|
4517
|
+
if (!sealed) return void 0;
|
|
4518
|
+
try {
|
|
4519
|
+
return await openseal(sealed);
|
|
4520
|
+
} catch {
|
|
4521
|
+
return void 0;
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
/** 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. */
|
|
4525
|
+
async removerunstate(profileid) {
|
|
4526
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4527
|
+
await this.adapter.set("runstateindex", index.filter((entry) => entry !== profileid));
|
|
4528
|
+
await this.adapter.set(`runstate:${profileid}`, { payload: "", algorithm: "sha-256", digest: "", sealedat: 0 });
|
|
4529
|
+
}
|
|
4530
|
+
/** Lists the stored run state records of every profile, oldest update first. */
|
|
4531
|
+
async listrunstates() {
|
|
4532
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4533
|
+
const states = [];
|
|
4534
|
+
for (const profileid of index) {
|
|
4535
|
+
const state = await this.getrunstate(profileid);
|
|
4536
|
+
if (state) states.push(state);
|
|
4537
|
+
}
|
|
4538
|
+
return states.sort((one, two) => one.updatedat - two.updatedat);
|
|
4539
|
+
}
|
|
4540
|
+
/** 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. */
|
|
4541
|
+
async expirerunstates(window2, now) {
|
|
4542
|
+
if (window2 === void 0) return await this.listrunstates();
|
|
4543
|
+
const index = await this.adapter.get("runstateindex") ?? [];
|
|
4544
|
+
const kept = [];
|
|
4545
|
+
for (const profileid of index) {
|
|
4546
|
+
const state = await this.getrunstate(profileid);
|
|
4547
|
+
if (!state) continue;
|
|
4548
|
+
if (now - state.updatedat > window2 && state.keepalive.state === "stopped") {
|
|
4549
|
+
const summary = { runid: state.runid, sessionid: state.sessionid, planid: state.planid, profileid: state.profileid, state: "expired", urlhistory: [], environments: {}, turnarounds: {}, keepalive: state.keepalive, updatedat: now };
|
|
4550
|
+
const sealed = await sealrunstate(summary);
|
|
4551
|
+
await this.adapter.set(`runstate:${profileid}`, sealed);
|
|
4552
|
+
} else {
|
|
4553
|
+
kept.push(state);
|
|
4554
|
+
}
|
|
4555
|
+
}
|
|
4556
|
+
return kept;
|
|
4557
|
+
}
|
|
4558
|
+
/** Records one worker spawn or teardown event with its provenance beside the step outcomes. */
|
|
4559
|
+
async addworkerevent(event) {
|
|
4560
|
+
await this.adapter.set("workerevents", [event, ...await this.adapter.get("workerevents") ?? []].slice(0, 500));
|
|
4561
|
+
}
|
|
4562
|
+
/** Returns the recorded worker spawn and teardown events, newest first. */
|
|
4563
|
+
async getworkerevents() {
|
|
4564
|
+
return await this.adapter.get("workerevents") ?? [];
|
|
4565
|
+
}
|
|
4566
|
+
/** Records one spawned offscreen document with its reasons and justification in the registry. */
|
|
4567
|
+
async addoffscreenentry(entry) {
|
|
4568
|
+
await this.adapter.set("offscreenregistry", [entry, ...await this.adapter.get("offscreenregistry") ?? []]);
|
|
4569
|
+
}
|
|
4570
|
+
/** Replaces one registry entry after its offscreen document closes. */
|
|
4571
|
+
async updateoffscreenentry(entry) {
|
|
4572
|
+
await this.adapter.set("offscreenregistry", (await this.adapter.get("offscreenregistry") ?? []).map((candidate) => candidate.runid === entry.runid ? entry : candidate));
|
|
4573
|
+
}
|
|
4574
|
+
/** Returns the offscreen document registry with the reasons and justification of every spawn. */
|
|
4575
|
+
async getoffscreenentries() {
|
|
4576
|
+
return await this.adapter.get("offscreenregistry") ?? [];
|
|
4577
|
+
}
|
|
4578
|
+
/** Records one sandbox render with its provenance, source origin and nonce. */
|
|
4579
|
+
async addsandboxrender(render) {
|
|
4580
|
+
await this.adapter.set("sandboxrenders", [render, ...await this.adapter.get("sandboxrenders") ?? []].slice(0, 500));
|
|
4581
|
+
}
|
|
4582
|
+
/** Returns the recorded sandbox renders with their provenance, newest first. */
|
|
4583
|
+
async getsandboxrenders() {
|
|
4584
|
+
return await this.adapter.get("sandboxrenders") ?? [];
|
|
4585
|
+
}
|
|
4586
|
+
/** Replaces the stored run locks after one acquisition, release or expiry sweep. */
|
|
4587
|
+
async setrunlocks(locks) {
|
|
4588
|
+
return this.adapter.set("runlocks", locks);
|
|
4589
|
+
}
|
|
4590
|
+
/** Returns the held run locks with their sessions, runs and expiries. */
|
|
4591
|
+
async getrunlocks() {
|
|
4592
|
+
return await this.adapter.get("runlocks") ?? [];
|
|
4593
|
+
}
|
|
4594
|
+
/** Tracks the storage quota usage of the run state: the last measured bytes stay beside the user configured ceiling so the pruning reads both. */
|
|
4595
|
+
async trackrunstatequota(used) {
|
|
4596
|
+
const settings = await this.getsettings();
|
|
4597
|
+
await this.adapter.set("runstatequota", { used, ...settings?.runstatebytes !== void 0 ? { ceiling: settings.runstatebytes } : {}, trackedat: Date.now() });
|
|
4598
|
+
}
|
|
4599
|
+
/** Returns the last tracked storage quota usage of the run state with its ceiling when the user configured one. */
|
|
4600
|
+
async getrunstatequota() {
|
|
4601
|
+
return this.adapter.get("runstatequota");
|
|
4602
|
+
}
|
|
4603
|
+
/** Exports every stored run state as one single audit record through the runstate export envelope. */
|
|
4604
|
+
async exportrunstates() {
|
|
4605
|
+
return exportrunstate(await this.listrunstates(), Date.now());
|
|
4606
|
+
}
|
|
4607
|
+
/**
|
|
4608
|
+
* Security part one persistence of the 1.1.61 family.
|
|
4609
|
+
* The trust boundary records live here: the per origin automation allowlist scoped per profile workspace with one exact origin per entry, the per site originprofiles with their kind grants and denials, the active consentwindows with their expiry timestamps that expire closed past their boundary, the mid run revokerun events with the halted step ids that stay visible for later consent prompts, the fresh class consents per origin, the mask rules for field shapes per origin, and the sealed immutable run logs with their final hash.
|
|
4610
|
+
* The run log store exposes no update or delete path: appends land whole, the seal closes a log with its final hash and the read path verifies the chain before returning a single entry so a broken link refuses the read.
|
|
4611
|
+
* The adapter seam keeps every accessor a one line storage delegation so a future append only backend replaces the adapter only; the current storage areas offer no append only hardware, so the honest derivation is the hash chain that makes any rewrite detectable at read time.
|
|
4612
|
+
*/
|
|
4613
|
+
/** Replaces the per origin automation allowlist of the profile workspaces; every entry carries one exact origin with no wildcard expansion. */
|
|
4614
|
+
async setautomationallowlist(entries) {
|
|
4615
|
+
return this.adapter.set("automationallowlist", entries);
|
|
4616
|
+
}
|
|
4617
|
+
/** Returns the per origin automation allowlist entries, oldest grant first. */
|
|
4618
|
+
async getautomationallowlist() {
|
|
4619
|
+
return await this.adapter.get("automationallowlist") ?? [];
|
|
4620
|
+
}
|
|
4621
|
+
/** Adds one exact origin to the automation allowlist of a profile workspace; a duplicate origin keeps its first grant. */
|
|
4622
|
+
async addallowlistorigin(entry) {
|
|
4623
|
+
const entries = await this.getautomationallowlist();
|
|
4624
|
+
if (entries.some((candidate) => candidate.origin === entry.origin && candidate.profileid === entry.profileid)) return;
|
|
4625
|
+
await this.setautomationallowlist([...entries, entry]);
|
|
4626
|
+
}
|
|
4627
|
+
/** Removes one origin from the automation allowlist; the denydefault posture refuses the origin again after the removal. */
|
|
4628
|
+
async removeallowlistorigin(origin, profileid) {
|
|
4629
|
+
await this.setautomationallowlist((await this.getautomationallowlist()).filter((entry) => !(entry.origin === origin && entry.profileid === profileid)));
|
|
4630
|
+
}
|
|
4631
|
+
/** Replaces the per site origin profiles with their kind grants and denials; one profile per origin. */
|
|
4632
|
+
async setoriginprofiles(profiles) {
|
|
4633
|
+
return this.adapter.set("originprofiles", profiles);
|
|
4634
|
+
}
|
|
4635
|
+
/** Returns the stored per site origin profiles, oldest update first. */
|
|
4636
|
+
async getoriginprofiles() {
|
|
4637
|
+
return await this.adapter.get("originprofiles") ?? [];
|
|
4638
|
+
}
|
|
4639
|
+
/** Upserts one origin profile: a profile of the same origin replaces its grants and denials while a new origin joins the list. */
|
|
4640
|
+
async saveoriginprofile(profile) {
|
|
4641
|
+
const profiles = await this.getoriginprofiles();
|
|
4642
|
+
await this.setoriginprofiles(profiles.some((candidate) => candidate.origin === profile.origin) ? profiles.map((candidate) => candidate.origin === profile.origin ? profile : candidate) : [...profiles, profile]);
|
|
4643
|
+
}
|
|
4644
|
+
/** Replaces the consent windows; active windows keep their expiry timestamps and closed windows stay for the audit trail. */
|
|
4645
|
+
async setconsentwindows(windows) {
|
|
4646
|
+
return this.adapter.set("consentwindows", windows);
|
|
4647
|
+
}
|
|
4648
|
+
/** Returns the stored consent windows, newest start first. */
|
|
4649
|
+
async getconsentwindows() {
|
|
4650
|
+
return await this.adapter.get("consentwindows") ?? [];
|
|
4651
|
+
}
|
|
4652
|
+
/** Expires every consent window past its duration boundary: the closed windows keep their records while their grants bind no step anymore. */
|
|
4653
|
+
async expireconsentwindows(now) {
|
|
4654
|
+
const windows = await this.getconsentwindows();
|
|
4655
|
+
const expired = windows.map((window2) => window2.state === "active" && now >= window2.expiresat ? { ...window2, state: "closed", closedat: now } : window2);
|
|
4656
|
+
await this.setconsentwindows(expired);
|
|
4657
|
+
return expired;
|
|
4658
|
+
}
|
|
4659
|
+
/** Records one mid run revocation with its halted step ids; the history stays visible for later consent prompts. */
|
|
4660
|
+
async addrevocation(event) {
|
|
4661
|
+
await this.adapter.set("revocations", [event, ...await this.adapter.get("revocations") ?? []].slice(0, 500));
|
|
4662
|
+
}
|
|
4663
|
+
/** Returns the recorded mid run revocations with their halted step ids, newest first. */
|
|
4664
|
+
async getrevocations() {
|
|
4665
|
+
return await this.adapter.get("revocations") ?? [];
|
|
4666
|
+
}
|
|
4667
|
+
/** Replaces the fresh class consents per origin. */
|
|
4668
|
+
async setclassconsents(consents) {
|
|
4669
|
+
return this.adapter.set("classconsents", consents);
|
|
4670
|
+
}
|
|
4671
|
+
/** Returns the fresh class consents per origin, newest grant first. */
|
|
4672
|
+
async getclassconsents() {
|
|
4673
|
+
return await this.adapter.get("classconsents") ?? [];
|
|
4674
|
+
}
|
|
4675
|
+
/** Records one fresh class consent per origin; the prompt of one class never widens another class. */
|
|
4676
|
+
async addclassconsent(consent) {
|
|
4677
|
+
const consents = (await this.getclassconsents()).filter((candidate) => !(candidate.origin === consent.origin && candidate.sensitiveclass === consent.sensitiveclass));
|
|
4678
|
+
await this.setclassconsents([consent, ...consents]);
|
|
4679
|
+
}
|
|
4680
|
+
/** Replaces the mask rules for sensitive field shapes per origin. */
|
|
4681
|
+
async setmaskrules(rules) {
|
|
4682
|
+
return this.adapter.set("maskrules", rules);
|
|
4683
|
+
}
|
|
4684
|
+
/** Returns the stored mask rules for sensitive field shapes per origin, oldest rule first. */
|
|
4685
|
+
async getmaskrules() {
|
|
4686
|
+
return await this.adapter.get("maskrules") ?? [];
|
|
4687
|
+
}
|
|
4688
|
+
/** Adds one mask rule for field shapes, optionally scoped to one origin. */
|
|
4689
|
+
async addmaskrule(rule) {
|
|
4690
|
+
await this.setmaskrules([...await this.getmaskrules(), rule]);
|
|
4691
|
+
}
|
|
4692
|
+
/** Removes one mask rule by its id. */
|
|
4693
|
+
async removemaskrule(id) {
|
|
4694
|
+
await this.setmaskrules((await this.getmaskrules()).filter((rule) => rule.id !== id));
|
|
4695
|
+
}
|
|
4696
|
+
/** Stores the whole run log of one run: the append lands in one storage transaction so the entries and their chain links persist together. */
|
|
4697
|
+
async setimmutablelog(log) {
|
|
4698
|
+
return this.adapter.set(`immutablelog:${log.runid}`, log);
|
|
4699
|
+
}
|
|
4700
|
+
/** Returns the stored run log of one run; an absent log returns undefined. */
|
|
4701
|
+
async getimmutablelog(runid) {
|
|
4702
|
+
return this.adapter.get(`immutablelog:${runid}`);
|
|
4703
|
+
}
|
|
4704
|
+
/** Lists the stored run logs, oldest update first, with the sealed logs carrying their final hash. */
|
|
4705
|
+
async listimmutablelogs() {
|
|
4706
|
+
const index = await this.adapter.get("immutablelogindex") ?? [];
|
|
4707
|
+
const logs = [];
|
|
4708
|
+
for (const runid of index) {
|
|
4709
|
+
const log = await this.getimmutablelog(runid);
|
|
4710
|
+
if (log) logs.push(log);
|
|
4711
|
+
}
|
|
4712
|
+
return logs.sort((one, two) => one.updatedat - two.updatedat);
|
|
4713
|
+
}
|
|
4714
|
+
/** Stores the run log index entry of one run so the log listing reads every stored log. */
|
|
4715
|
+
async trackimmutablelog(runid) {
|
|
4716
|
+
const index = await this.adapter.get("immutablelogindex") ?? [];
|
|
4717
|
+
if (!index.includes(runid)) await this.adapter.set("immutablelogindex", [...index, runid]);
|
|
4718
|
+
}
|
|
4719
|
+
/** Exports the verified log chain of one run for the audit file: the read path verifies the whole hash chain first and a broken link refuses the export with no entries served. */
|
|
4720
|
+
async exportverifiedrunlog(runid) {
|
|
4721
|
+
const log = await this.getimmutablelog(runid);
|
|
4722
|
+
if (!log) throw new Error(`No run log exists for the run ${runid}.`);
|
|
4723
|
+
return exportlogchain(log);
|
|
4724
|
+
}
|
|
4725
|
+
/** Expires the sealed run logs past the user configured retention: the entries reduce to their chain summaries while the seal hash always survives. */
|
|
4726
|
+
async expireimmutablelogs(retention, now) {
|
|
4727
|
+
const logs = await this.listimmutablelogs();
|
|
4728
|
+
if (retention === void 0) return logs;
|
|
4729
|
+
const kept = [];
|
|
4730
|
+
for (const log of logs) {
|
|
4731
|
+
if (log.seal !== void 0 && now - log.seal.sealedat > retention) {
|
|
4732
|
+
const summary = { runid: log.runid, sessionid: log.sessionid, entries: [], seal: { ...log.seal, entries: log.seal.entries }, updatedat: now };
|
|
4733
|
+
await this.setimmutablelog(summary);
|
|
4734
|
+
} else {
|
|
4735
|
+
kept.push(log);
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
return kept;
|
|
4739
|
+
}
|
|
4261
4740
|
};
|
|
4262
4741
|
function mediakindof(record2) {
|
|
4263
4742
|
if ("pages" in record2) return "pdf";
|
|
@@ -4301,6 +4780,245 @@ function randomid() {
|
|
|
4301
4780
|
return crypto.randomUUID();
|
|
4302
4781
|
}
|
|
4303
4782
|
|
|
4783
|
+
// originpolicy.ts
|
|
4784
|
+
function exactorigin(origin, entry) {
|
|
4785
|
+
return origin.trim() !== "" && origin === entry;
|
|
4786
|
+
}
|
|
4787
|
+
function wildcardentry(entry) {
|
|
4788
|
+
return entry.includes("*") || entry.includes("://*.") || entry.trim() === "" || entry.trim() === "https://" || entry.trim() === "http://";
|
|
4789
|
+
}
|
|
4790
|
+
function allowlistcheck(input) {
|
|
4791
|
+
if (input.origin.trim() === "") return { allowed: false, reason: "The step needs the exact origin it targets." };
|
|
4792
|
+
for (const entry of input.allowlist) {
|
|
4793
|
+
if (wildcardentry(entry.origin)) return { allowed: false, reason: `The allowlist entry ${entry.origin} carries a wildcard; every grant binds to one exact origin with no wildcard expansion.` };
|
|
4794
|
+
}
|
|
4795
|
+
const scoped = input.profileid === void 0 ? input.allowlist : input.allowlist.filter((entry) => entry.profileid === input.profileid);
|
|
4796
|
+
const granted = scoped.some((entry) => exactorigin(input.origin, entry.origin));
|
|
4797
|
+
if (granted) return { allowed: true, reason: `The origin ${input.origin} sits inside the automation allowlist the user granted.` };
|
|
4798
|
+
if (input.sessionorigin !== void 0 && exactorigin(input.origin, input.sessionorigin)) return { allowed: true, reason: `The active tab grant covers ${input.origin} as exactly one explicit single origin grant.` };
|
|
4799
|
+
return { allowed: false, reason: `The denydefault posture refuses ${input.origin} because the origin sits absent from the automation allowlist; grant the origin first.` };
|
|
4800
|
+
}
|
|
4801
|
+
function originprofileof(input) {
|
|
4802
|
+
if (input.origin.trim() === "") throw new Error("The origin profile needs its exact origin.");
|
|
4803
|
+
return { profileid: input.profileid ?? randomid(), origin: input.origin, grants: [...input.grants ?? []], denials: [...input.denials ?? []], createdat: input.now, updatedat: input.now };
|
|
4804
|
+
}
|
|
4805
|
+
function profilekind(input) {
|
|
4806
|
+
if (input.profile.grants.includes(input.kind) && input.decision === "grant") return input.profile;
|
|
4807
|
+
if (input.profile.denials.includes(input.kind) && input.decision === "deny") return input.profile;
|
|
4808
|
+
const grants = input.decision === "grant" ? [.../* @__PURE__ */ new Set([...input.profile.grants, input.kind])] : input.profile.grants.filter((kind) => kind !== input.kind);
|
|
4809
|
+
const denials = input.decision === "deny" ? [.../* @__PURE__ */ new Set([...input.profile.denials, input.kind])] : input.profile.denials.filter((kind) => kind !== input.kind);
|
|
4810
|
+
return { ...input.profile, grants, denials, updatedat: input.now };
|
|
4811
|
+
}
|
|
4812
|
+
function profilegrade(input) {
|
|
4813
|
+
if (!input.sensitive) return { allowed: true, consult: false, reason: `The ${input.kind} kind grades non-sensitive and the origin profile needs no consult.` };
|
|
4814
|
+
if (input.profile === void 0) return { allowed: true, consult: true, reason: `No origin profile exists for the ${input.kind} kind, so the fresh class consent gate alone routes the sensitive step.` };
|
|
4815
|
+
if (input.profile.denials.includes(input.kind)) return { allowed: false, consult: true, reason: `The origin profile of ${input.profile.origin} denies the ${input.kind} kind; a denied kind never runs on that origin.` };
|
|
4816
|
+
if (input.profile.grants.includes(input.kind)) return { allowed: true, consult: true, reason: `The origin profile of ${input.profile.origin} grants the ${input.kind} kind the user reviewed.` };
|
|
4817
|
+
return { allowed: true, consult: true, reason: `The origin profile of ${input.profile.origin} carries no ${input.kind} decision, so the fresh class consent gate alone routes the sensitive step.` };
|
|
4818
|
+
}
|
|
4819
|
+
function stepoptions(step) {
|
|
4820
|
+
if (!step.options) return {};
|
|
4821
|
+
try {
|
|
4822
|
+
const parsed = JSON.parse(step.options);
|
|
4823
|
+
return Boolean(parsed) && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
4824
|
+
} catch {
|
|
4825
|
+
return {};
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
var paymentkinds = /* @__PURE__ */ new Set(["fillcard", "fillcode"]);
|
|
4829
|
+
var credentialkinds = /* @__PURE__ */ new Set(["consentpassword", "saveapikey", "handleauth", "authflow"]);
|
|
4830
|
+
var deletekinds = /* @__PURE__ */ new Set(["discardtab", "closepattern", "clearcookies", "removeattribute", "cleanupartifacts"]);
|
|
4831
|
+
var publishkinds = /* @__PURE__ */ new Set(["postform", "postfiles", "sendmessage", "submitform", "submitsearch", "writeclipboard"]);
|
|
4832
|
+
var defaultsensitivekinds = /* @__PURE__ */ new Set(["attachfile", "uploadfile", "uploadfiles", "downloadfile", "downloadimages", "batchdownload", "pausedownload", "resumedownload", "quarantinedownload", "evaluate"]);
|
|
4833
|
+
function sensitiveclassesof(step) {
|
|
4834
|
+
const options = stepoptions(step);
|
|
4835
|
+
const fields = Array.isArray(options.fields) ? options.fields.filter((item) => Boolean(item) && typeof item === "object") : [];
|
|
4836
|
+
const names = [...fields.map((field) => typeof field.name === "string" ? field.name : ""), typeof options.field === "string" ? options.field : "", typeof options.target === "string" ? options.target : ""].map((name) => name.toLowerCase());
|
|
4837
|
+
const carries = (shape) => names.some((name) => name.includes(shape));
|
|
4838
|
+
const classes = /* @__PURE__ */ new Set();
|
|
4839
|
+
if (paymentkinds.has(step.kind) || carries("card") || carries("cvc") || carries("cvv")) classes.add("payment");
|
|
4840
|
+
const credentialshape = carries("password") || carries("token") || carries("secret") || carries("apikey") || carries("passphrase");
|
|
4841
|
+
const submits = step.kind === "submitform" || step.kind === "postform" || step.kind === "submitsearch" || step.kind === "fillform" || step.kind === "filllabel" || step.kind === "fillplaceholder";
|
|
4842
|
+
if (credentialkinds.has(step.kind) || submits && credentialshape) classes.add("credential");
|
|
4843
|
+
if (deletekinds.has(step.kind)) classes.add("delete");
|
|
4844
|
+
if (publishkinds.has(step.kind) || step.kind === "callrest" || step.kind === "callgraphql") {
|
|
4845
|
+
const verb = typeof options.method === "string" ? options.method.trim().toUpperCase() : typeof options.verb === "string" ? options.verb.trim().toUpperCase() : "";
|
|
4846
|
+
if (step.kind === "callrest" || step.kind === "callgraphql") {
|
|
4847
|
+
if (verb !== "" && !["GET", "HEAD", "OPTIONS"].includes(verb)) classes.add("publish");
|
|
4848
|
+
} else classes.add("publish");
|
|
4849
|
+
}
|
|
4850
|
+
const bydefault = defaultsensitivekinds.has(step.kind);
|
|
4851
|
+
const list = [...classes];
|
|
4852
|
+
if (list.length === 0 && !bydefault) return { classes: [], bydefault: false, sensitive: false, reason: `The ${step.kind} kind carries no sensitive class and no default sensitive grade.` };
|
|
4853
|
+
return { classes: list, bydefault, sensitive: true, reason: `The ${step.kind} kind grades sensitive${list.length > 0 ? ` through the ${list.join(", ")} class${list.length === 1 ? "" : "es"}` : ""}${bydefault ? " by default" : ""}.` };
|
|
4854
|
+
}
|
|
4855
|
+
function classconsentcovers(consents, origin, sensitiveclass, now) {
|
|
4856
|
+
return consents.some((consent) => consent.origin === origin && consent.sensitiveclass === sensitiveclass && consent.grantedat <= now && (consent.expiresat === void 0 || now < consent.expiresat));
|
|
4857
|
+
}
|
|
4858
|
+
function missingclassconsents(input) {
|
|
4859
|
+
const missing = input.classes.filter((kind) => !classconsentcovers(input.consents, input.origin, kind, input.now));
|
|
4860
|
+
if (missing.length > 0) return { needed: true, missing, reason: `The sensitive classes ${missing.join(", ")} need one fresh consent prompt each on ${input.origin}.` };
|
|
4861
|
+
if (input.bydefault && input.classes.length === 0) return { needed: true, missing: [], reason: `The ${input.origin} step grades sensitive by default and needs its fresh consent window prompt.` };
|
|
4862
|
+
return { needed: false, missing: [], reason: `The fresh class consents of ${input.origin} cover every class the step names.` };
|
|
4863
|
+
}
|
|
4864
|
+
function openconsentwindow(input) {
|
|
4865
|
+
if (input.sessionid.trim() === "" || input.origin.trim() === "") throw new Error("The consent window needs its session and its exact origin.");
|
|
4866
|
+
if (!Number.isFinite(input.duration) || input.duration <= 0) throw new Error("The consent window needs its duration as a positive user value; no window defaults to unlimited.");
|
|
4867
|
+
return { id: input.id ?? randomid(), sessionid: input.sessionid, origin: input.origin, startedat: input.now, duration: input.duration, expiresat: input.now + input.duration, boundary: input.boundary?.trim() !== "" && input.boundary !== void 0 ? input.boundary : `${input.duration} milliseconds the user chose`, kinds: [...new Set(input.kinds)], state: "active" };
|
|
4868
|
+
}
|
|
4869
|
+
function consentwindowstate(window2, now) {
|
|
4870
|
+
if (window2.state === "closed" || now >= window2.expiresat) return { state: "expired", remaining: 0, reason: `The consent window of ${window2.origin} closed at its ${window2.boundary} boundary; the run suspends until a new explicit prompt renews it.` };
|
|
4871
|
+
return { state: "active", remaining: window2.expiresat - now, reason: `The consent window of ${window2.origin} stays active with ${window2.expiresat - now} milliseconds left of its ${window2.boundary} boundary.` };
|
|
4872
|
+
}
|
|
4873
|
+
function windowgatesstep(input) {
|
|
4874
|
+
if (input.window === void 0) return { allowed: false, suspended: false, reason: `No active consent window covers ${input.origin}; the consent prompt opens one before any step dispatches.` };
|
|
4875
|
+
if (input.window.sessionid !== input.sessionid) return { allowed: false, suspended: false, reason: `The consent window scopes to the session ${input.window.sessionid} only and never widens to another session.` };
|
|
4876
|
+
if (input.window.origin !== input.origin) return { allowed: false, suspended: false, reason: `The consent window scopes to the origin ${input.window.origin} only and never widens to another origin.` };
|
|
4877
|
+
const state = consentwindowstate(input.window, input.now);
|
|
4878
|
+
if (state.state === "expired") return { allowed: false, suspended: true, reason: state.reason };
|
|
4879
|
+
return { allowed: true, suspended: false, reason: state.reason };
|
|
4880
|
+
}
|
|
4881
|
+
function renewconsentwindow(input) {
|
|
4882
|
+
const closed = input.window.state === "active" ? { ...input.window, state: "closed", closedat: input.now } : input.window;
|
|
4883
|
+
const renewed = openconsentwindow({ sessionid: input.window.sessionid, origin: input.window.origin, duration: input.duration, kinds: input.kinds.length > 0 ? input.kinds : input.window.kinds, now: input.now });
|
|
4884
|
+
return { renewed, closed };
|
|
4885
|
+
}
|
|
4886
|
+
function revokerun(input) {
|
|
4887
|
+
if (input.sessionid.trim() === "" || input.runid.trim() === "") throw new Error("The revocation needs its session and run ids.");
|
|
4888
|
+
if (input.actor.trim() === "") throw new Error("The revocation names the acting user.");
|
|
4889
|
+
const halted = [...input.pendingstepid !== void 0 ? [input.pendingstepid] : [], ...input.queuedstepids ?? []];
|
|
4890
|
+
if (halted.length === 0) throw new Error("The revocation halts at least the pending step of the run.");
|
|
4891
|
+
return { id: input.id ?? randomid(), sessionid: input.sessionid, runid: input.runid, haltedstepids: halted, actor: input.actor, reason: input.reason?.trim() !== "" && input.reason !== void 0 ? input.reason : "The user revoked the consent mid run.", at: input.now };
|
|
4892
|
+
}
|
|
4893
|
+
function scopegrantof(input) {
|
|
4894
|
+
if (input.origin.trim() === "") throw new Error("The consent scope needs its exact origin.");
|
|
4895
|
+
if (input.kinds.length === 0) throw new Error("The consent scope names the kinds it covers.");
|
|
4896
|
+
if (input.boundary.trim() === "") throw new Error("The consent scope names its boundary; no grant defaults to unlimited.");
|
|
4897
|
+
return { origin: input.origin, kinds: [...new Set(input.kinds)], boundary: input.boundary, grantedat: input.now };
|
|
4898
|
+
}
|
|
4899
|
+
function deniedevidenceof(input) {
|
|
4900
|
+
return { origin: input.origin, kind: input.kind, reason: input.reason, at: input.now };
|
|
4901
|
+
}
|
|
4902
|
+
function consentprompttext(input) {
|
|
4903
|
+
const label = input.classes.length > 0 ? `the ${input.classes.join(" and ")} class${input.classes.length === 1 ? "" : "es"}` : "a sensitive by default grade";
|
|
4904
|
+
return `Allow the ${input.kind} step on ${input.origin} graded as ${label} for ${input.duration} milliseconds? The consent window closes at that boundary; no grant ever defaults to unlimited.`;
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4907
|
+
// environments.ts
|
|
4908
|
+
var offloadfamilies = [
|
|
4909
|
+
{ task: "htmlsnapshot", kinds: ["readhtml", "parsehtml", "readertree", "readoutline", "classifypage"] },
|
|
4910
|
+
{ task: "jsonpayload", kinds: ["readjson", "parsejson"] },
|
|
4911
|
+
{ task: "tablerows", kinds: ["readtable", "scrapetable", "detecttables", "deduperows", "transformvalues"] },
|
|
4912
|
+
{ task: "a11ytree", kinds: ["a11ytree"] },
|
|
4913
|
+
{ task: "complexselector", kinds: ["resolvexpath", "deriveselector", "detectvirtual"] },
|
|
4914
|
+
{ task: "stitchshots", kinds: ["contactsheet", "timelapse", "makethumbs"] }
|
|
4915
|
+
];
|
|
4916
|
+
function offfamilyof(kind) {
|
|
4917
|
+
return offloadfamilies.find((family) => family.kinds.includes(kind))?.task;
|
|
4918
|
+
}
|
|
4919
|
+
function environmentsof(step) {
|
|
4920
|
+
if (markuprenderstep(step)) return ["sandboxframe"];
|
|
4921
|
+
if (step.kind === "evaluate") return ["isolatedworld"];
|
|
4922
|
+
if (offfamilyof(step.kind) !== void 0) return ["pagecontext", "offscreenworker"];
|
|
4923
|
+
return ["pagecontext"];
|
|
4924
|
+
}
|
|
4925
|
+
function defaultenvironment(step) {
|
|
4926
|
+
if (markuprenderstep(step)) return "sandboxframe";
|
|
4927
|
+
if (step.kind === "evaluate") return "isolatedworld";
|
|
4928
|
+
return "pagecontext";
|
|
4929
|
+
}
|
|
4930
|
+
function offamilyeligible(kind) {
|
|
4931
|
+
return offloadfamilies.some((family) => family.kinds.includes(kind));
|
|
4932
|
+
}
|
|
4933
|
+
function markuprenderstep(step) {
|
|
4934
|
+
if (!step.options) return false;
|
|
4935
|
+
try {
|
|
4936
|
+
const parsed = JSON.parse(step.options);
|
|
4937
|
+
return Boolean(parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.markup === "string" && parsed.markup.trim() !== "");
|
|
4938
|
+
} catch {
|
|
4939
|
+
return false;
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
function environmentrequirementsof(kinds) {
|
|
4943
|
+
return kinds.map((kind) => {
|
|
4944
|
+
const bare = { kind };
|
|
4945
|
+
const environments = environmentsof(bare);
|
|
4946
|
+
return { kind, environments, defaultenvironment: defaultenvironment(bare) };
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4949
|
+
function executorregistry() {
|
|
4950
|
+
return [
|
|
4951
|
+
{ environment: "pagecontext", adapter: "pagebridge", description: "The page bridge executes dom actions inside the live page because page events only fire there." },
|
|
4952
|
+
{ environment: "isolatedworld", adapter: "scriptingapi", description: "The scripting api injects step logic inside the isolated world where page globals stay unreachable from step code." },
|
|
4953
|
+
{ 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." },
|
|
4954
|
+
{ 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." }
|
|
4955
|
+
];
|
|
4956
|
+
}
|
|
4957
|
+
function routeenvironment(step, input) {
|
|
4958
|
+
const allowed = environmentsof(step);
|
|
4959
|
+
const named = step.environment;
|
|
4960
|
+
if (named !== void 0) {
|
|
4961
|
+
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.` };
|
|
4962
|
+
return { environment: named, fallback: false, reason: `The reviewed step names its ${named} environment and the ${step.kind} kind permits it.` };
|
|
4963
|
+
}
|
|
4964
|
+
if (markuprenderstep(step)) return { environment: "sandboxframe", fallback: false, reason: `The ${step.kind} step carries untrusted markup, so it renders inside the sandboxframe only.` };
|
|
4965
|
+
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." };
|
|
4966
|
+
if (offamilyeligible(step.kind)) {
|
|
4967
|
+
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.` };
|
|
4968
|
+
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.` };
|
|
4969
|
+
return { environment: "offscreenworker", fallback: false, reason: `The ${step.kind} step offloads into the offscreen worker pool under the granted capability.` };
|
|
4970
|
+
}
|
|
4971
|
+
return { environment: "pagecontext", fallback: false, reason: `The ${step.kind} step keeps the pagecontext because page events only fire inside the live page.` };
|
|
4972
|
+
}
|
|
4973
|
+
function workerrequestof(input) {
|
|
4974
|
+
const task = offfamilyof(input.kind);
|
|
4975
|
+
if (task === void 0) throw new Error(`The ${input.kind} kind stays outside the offscreen worker pool families.`);
|
|
4976
|
+
if (input.payload.trim() === "") throw new Error("The worker request needs its payload reference.");
|
|
4977
|
+
return { id: input.id, runid: input.runid, stepid: input.stepid, task, payload: input.payload, transferables: transferablekeys(input.options ?? {}), sentat: input.sentat };
|
|
4978
|
+
}
|
|
4979
|
+
function transferablekeys(options) {
|
|
4980
|
+
return Object.keys(options).filter((key) => options[key] instanceof ArrayBuffer);
|
|
4981
|
+
}
|
|
4982
|
+
function poolplan(input) {
|
|
4983
|
+
if (input.size !== void 0) {
|
|
4984
|
+
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." };
|
|
4985
|
+
const target = input.size;
|
|
4986
|
+
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.` };
|
|
4987
|
+
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.` };
|
|
4988
|
+
return { workers: target, added: 0, retired: 0, reason: `The pool holds the ${target} workers the user configured.` };
|
|
4989
|
+
}
|
|
4990
|
+
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.` };
|
|
4991
|
+
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"}.` };
|
|
4992
|
+
return { workers: input.current, added: 0, retired: 0, reason: `The ${input.current} workers match the ${input.pending} pending parses; the pool stays unchanged.` };
|
|
4993
|
+
}
|
|
4994
|
+
function openoffscreen(input) {
|
|
4995
|
+
if (input.document.trim() === "") throw new Error("The offscreen document needs its user configured path.");
|
|
4996
|
+
if (input.reasons.length === 0) throw new Error("The offscreen document needs the reasons the user reviewed.");
|
|
4997
|
+
if (input.justification.trim() === "") throw new Error("The offscreen document needs its justification in plain language.");
|
|
4998
|
+
const open = input.registry.find((entry2) => entry2.runid === input.runid && entry2.closedat === void 0);
|
|
4999
|
+
if (open) return { registry: input.registry, entry: open, reused: true };
|
|
5000
|
+
const entry = { document: input.document, runid: input.runid, reasons: [...input.reasons], justification: input.justification, createdat: input.now };
|
|
5001
|
+
return { registry: [entry, ...input.registry], entry, reused: false };
|
|
5002
|
+
}
|
|
5003
|
+
function closeoffscreen(registry, runid, now) {
|
|
5004
|
+
const open = registry.find((entry) => entry.runid === runid && entry.closedat === void 0);
|
|
5005
|
+
if (!open) return { registry, closed: false };
|
|
5006
|
+
return { registry: registry.map((entry) => entry === open ? { ...entry, closedat: now } : entry), closed: true };
|
|
5007
|
+
}
|
|
5008
|
+
function isolatedinjection(step) {
|
|
5009
|
+
if (step.kind !== "evaluate") throw new Error("The isolated world injection serves the evaluate kind only.");
|
|
5010
|
+
if (!step.value || step.value.trim() === "") throw new Error("The evaluate step needs its reviewed expression.");
|
|
5011
|
+
let args = [];
|
|
5012
|
+
if (step.options) {
|
|
5013
|
+
try {
|
|
5014
|
+
const parsed = JSON.parse(step.options);
|
|
5015
|
+
if (Array.isArray(parsed)) args = parsed.filter((item) => typeof item === "string");
|
|
5016
|
+
} catch {
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
return { world: "ISOLATED", code: step.value, args };
|
|
5020
|
+
}
|
|
5021
|
+
|
|
4304
5022
|
// toolcatalog.ts
|
|
4305
5023
|
var toolcatalogversion = 1;
|
|
4306
5024
|
var toolnamespaces = ["browser", "workflow", "memory", "system"];
|
|
@@ -9127,6 +9845,84 @@ function mergeegressgrade(input) {
|
|
|
9127
9845
|
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
9846
|
return { allowed: true, reason: `The export of the report ${input.report.title} carries no page content and stays a plain report export.` };
|
|
9129
9847
|
}
|
|
9848
|
+
function stepenvironmentvalid(step) {
|
|
9849
|
+
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." };
|
|
9850
|
+
const allowed = environmentsof(step);
|
|
9851
|
+
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.` };
|
|
9852
|
+
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.` };
|
|
9853
|
+
}
|
|
9854
|
+
function environmentgrantgate(step, grants) {
|
|
9855
|
+
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.` };
|
|
9856
|
+
const environment = step.environment ?? defaultenvironment(step);
|
|
9857
|
+
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.` };
|
|
9858
|
+
return { allowed: true, reason: `The ${environment} environment of the ${step.kind} step sits inside the ${grants.join(", ")} the session granted.` };
|
|
9859
|
+
}
|
|
9860
|
+
function offscreencapabilitygate(input) {
|
|
9861
|
+
if (input.environment !== "offscreenworker") return { allowed: true, reason: `The ${input.environment} environment needs no offscreen capability grant.` };
|
|
9862
|
+
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." };
|
|
9863
|
+
return { allowed: true, reason: "The offscreen worker pool runs under the user granted offscreen capability." };
|
|
9864
|
+
}
|
|
9865
|
+
function keepalivegate(input) {
|
|
9866
|
+
if (!input.session) return { allowed: false, reason: "The keepalive port opens only inside an active session." };
|
|
9867
|
+
if (input.session.stoppedat !== void 0) return { allowed: false, reason: "The keepalive port stays closed for a stopped session." };
|
|
9868
|
+
if (input.session.pausedat !== void 0) return { allowed: false, reason: "The keepalive port stays closed while the session pauses; a resumed run reattaches it." };
|
|
9869
|
+
if (input.now > input.session.expiresat) return { allowed: false, reason: "The keepalive port stays closed for an expired session." };
|
|
9870
|
+
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." };
|
|
9871
|
+
return { allowed: true, reason: `The approved plan ${input.plan.id} of the active session holds the keepalive port open for its whole run.` };
|
|
9872
|
+
}
|
|
9873
|
+
function keepaliveintervalvalid(interval) {
|
|
9874
|
+
if (!Number.isFinite(interval) || interval <= 0) return { allowed: false, reason: "The keepalive heartbeat interval stays a positive user value in milliseconds." };
|
|
9875
|
+
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.` };
|
|
9876
|
+
}
|
|
9877
|
+
function workerpoolsizevalid(size) {
|
|
9878
|
+
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." };
|
|
9879
|
+
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." };
|
|
9880
|
+
return { allowed: true, reason: `The worker pool size ${size} stays the user configured value; no engine cap exists.` };
|
|
9881
|
+
}
|
|
9882
|
+
function sandboxorigingate(input) {
|
|
9883
|
+
if (input.origin.trim() === "") return { allowed: false, reason: "The sandbox render needs the source origin of its untrusted markup." };
|
|
9884
|
+
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(", ")}.` };
|
|
9885
|
+
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.` };
|
|
9886
|
+
}
|
|
9887
|
+
function environmentrequirements() {
|
|
9888
|
+
return environmentrequirementsof([...allowedactions]);
|
|
9889
|
+
}
|
|
9890
|
+
function automationallowlistgate(input) {
|
|
9891
|
+
const verdict = allowlistcheck({ origin: input.origin, allowlist: input.allowlist, ...input.session !== void 0 ? { sessionorigin: input.session.origin } : {} });
|
|
9892
|
+
if (!verdict.allowed) return { allowed: false, reason: verdict.reason };
|
|
9893
|
+
return { allowed: true, reason: verdict.reason };
|
|
9894
|
+
}
|
|
9895
|
+
function originprofilegate(input) {
|
|
9896
|
+
const verdict = profilegrade(input);
|
|
9897
|
+
if (!verdict.allowed) return { allowed: false, reason: verdict.reason };
|
|
9898
|
+
return { allowed: true, reason: verdict.reason };
|
|
9899
|
+
}
|
|
9900
|
+
function consentwindowgate(input) {
|
|
9901
|
+
if (!input.sensitive) return { allowed: true, reason: "The non-sensitive step rides the session origin grants and needs no consent window of its own." };
|
|
9902
|
+
const verdict = windowgatesstep({ window: input.window, sessionid: input.sessionid, origin: input.origin, now: input.now });
|
|
9903
|
+
if (!verdict.allowed) return { allowed: false, reason: verdict.reason };
|
|
9904
|
+
return { allowed: true, reason: verdict.reason };
|
|
9905
|
+
}
|
|
9906
|
+
function revokerungate(input) {
|
|
9907
|
+
if (input.revocation === void 0) return { allowed: true, reason: "No revocation halted the run; the steps keep their reviewed order." };
|
|
9908
|
+
if (input.revocation.sessionid !== input.sessionid) return { allowed: true, reason: "The revocation belongs to another session and halts nothing here." };
|
|
9909
|
+
if (input.revocation.runid !== input.runid) return { allowed: true, reason: "The revocation belongs to another run and halts nothing here." };
|
|
9910
|
+
return { allowed: false, reason: `The revocation of ${input.revocation.actor} halted the pending step and ${Math.max(0, input.revocation.haltedstepids.length - 1)} queued step${Math.max(0, input.revocation.haltedstepids.length - 1) === 1 ? "" : "s"} without executing them: ${input.revocation.haltedstepids.join(", ")}.` };
|
|
9911
|
+
}
|
|
9912
|
+
function sensitiveclassgate(input) {
|
|
9913
|
+
if (!input.sensitive) return { allowed: true, reason: "The step carries no sensitive class and needs no fresh consent prompt." };
|
|
9914
|
+
const verdict = missingclassconsents({ origin: input.origin, classes: input.classes, bydefault: input.bydefault, consents: input.consents, now: input.now });
|
|
9915
|
+
if (verdict.needed) return { allowed: false, reason: verdict.reason };
|
|
9916
|
+
return { allowed: true, reason: verdict.reason };
|
|
9917
|
+
}
|
|
9918
|
+
function consentdurationvalid(duration) {
|
|
9919
|
+
if (!Number.isFinite(duration) || duration <= 0) return { allowed: false, reason: "The consent window duration stays a positive user value in milliseconds; no grant ever defaults to unlimited." };
|
|
9920
|
+
return { allowed: true, reason: `The consent window duration ${duration} milliseconds stays the user configured boundary the prompt names.` };
|
|
9921
|
+
}
|
|
9922
|
+
function logreadgate(input) {
|
|
9923
|
+
if (!input.valid) return { allowed: false, reason: input.brokenat !== void 0 ? `The log chain breaks at entry ${input.brokenat}; the audit accessor refuses the read of a forged record.` : "The log chain fails its verification; the audit accessor refuses the read of a forged record." };
|
|
9924
|
+
return { allowed: true, reason: "The log chain verifies from the genesis hash to the last entry; the audit accessor serves the entries." };
|
|
9925
|
+
}
|
|
9130
9926
|
|
|
9131
9927
|
// progress.ts
|
|
9132
9928
|
function emptyprogress(planid, now) {
|
|
@@ -9141,6 +9937,14 @@ function recordoutcome(progress, planid, outcome, now) {
|
|
|
9141
9937
|
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9142
9938
|
return { ...base, outcomes: [...base.outcomes ?? [], outcome], updatedat: now };
|
|
9143
9939
|
}
|
|
9940
|
+
function recordenvironment2(progress, planid, stepid, environment, now) {
|
|
9941
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9942
|
+
return { ...base, environments: { ...base.environments ?? {}, [stepid]: environment }, updatedat: now };
|
|
9943
|
+
}
|
|
9944
|
+
function recordturnaround2(progress, planid, stepid, milliseconds, now) {
|
|
9945
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
9946
|
+
return { ...base, turnarounds: { ...base.turnarounds ?? {}, [stepid]: milliseconds }, updatedat: now };
|
|
9947
|
+
}
|
|
9144
9948
|
function iscomplete(progress, plan) {
|
|
9145
9949
|
if (!progress || progress.planid !== plan.id) return false;
|
|
9146
9950
|
const required = plan.steps.map((step) => step.id);
|
|
@@ -9304,9 +10108,68 @@ function recordtoolcall(progress, planid, stepid, entry, now) {
|
|
|
9304
10108
|
const outcome = { stepid, ok: entry.ok, summary: `The ${entry.tool} tool call of the client ${entry.clientid} ${entry.ok ? "ran behind the consent gates" : `was refused${entry.code !== void 0 ? ` with the ${entry.code} error` : ""}`}.`, details: { tool: entry }, at: now };
|
|
9305
10109
|
return recordoutcome(base, planid, outcome, now);
|
|
9306
10110
|
}
|
|
10111
|
+
function recorddenied(progress, planid, stepid, entry, now) {
|
|
10112
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
10113
|
+
const outcome = { stepid, ok: false, summary: `The ${entry.kind} step on ${entry.origin} was denied: ${entry.reason}`, details: { denied: entry }, at: now };
|
|
10114
|
+
return recordoutcome(base, planid, outcome, now);
|
|
10115
|
+
}
|
|
10116
|
+
function recordrevocation(progress, planid, stepid, entry, now) {
|
|
10117
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
10118
|
+
const outcome = { stepid, ok: false, summary: `The run halted at the step ${stepid}${entry.haltedstepids.length > 1 ? ` with ${entry.haltedstepids.length - 1} queued step${entry.haltedstepids.length === 2 ? "" : "s"} rolled back` : ""}: ${entry.reason}`, details: { revoked: entry }, at: now };
|
|
10119
|
+
return recordoutcome(base, planid, outcome, now);
|
|
10120
|
+
}
|
|
10121
|
+
|
|
10122
|
+
// maskinputs.ts
|
|
10123
|
+
var defaultmaskshapes = ["password", "token", "card", "secret"];
|
|
10124
|
+
var maskmarker = "[redacted]";
|
|
10125
|
+
function fieldshapekind(name) {
|
|
10126
|
+
const lowered = name.toLowerCase();
|
|
10127
|
+
if (lowered.includes("password") || lowered.includes("passwd") || lowered.includes("pwd") || lowered.includes("passphrase")) return "password";
|
|
10128
|
+
if (lowered.includes("token") || lowered.includes("apikey") || lowered.includes("api_key") || lowered.includes("auth") || lowered.includes("bearer")) return "token";
|
|
10129
|
+
if (lowered.includes("card") || lowered.includes("cvc") || lowered.includes("cvv") || lowered.includes("expiry") || lowered.includes("pan")) return "card";
|
|
10130
|
+
if (lowered.includes("secret")) return "secret";
|
|
10131
|
+
return void 0;
|
|
10132
|
+
}
|
|
10133
|
+
function shapesof(input) {
|
|
10134
|
+
const shapes = new Set(defaultmaskshapes);
|
|
10135
|
+
for (const shape of input.settings?.maskshapes ?? []) if (shape.trim() !== "") shapes.add(shape.trim().toLowerCase());
|
|
10136
|
+
for (const rule of input.rules) {
|
|
10137
|
+
const scoped = rule.origin === void 0 || rule.origin === "" || input.origin !== void 0 && rule.origin === input.origin;
|
|
10138
|
+
if (scoped) {
|
|
10139
|
+
for (const shape of rule.shapes) if (shape.trim() !== "") shapes.add(shape.trim().toLowerCase());
|
|
10140
|
+
}
|
|
10141
|
+
}
|
|
10142
|
+
return [...shapes];
|
|
10143
|
+
}
|
|
10144
|
+
function maskingfield(name, shapes) {
|
|
10145
|
+
if (fieldshapekind(name) !== void 0) return true;
|
|
10146
|
+
const lowered = name.toLowerCase();
|
|
10147
|
+
return shapes.some((shape) => shape !== "" && lowered.includes(shape));
|
|
10148
|
+
}
|
|
10149
|
+
function maskvalue(value) {
|
|
10150
|
+
return value === "" ? "" : maskmarker;
|
|
10151
|
+
}
|
|
10152
|
+
function maskfield(input) {
|
|
10153
|
+
return maskingfield(input.name, input.shapes) ? maskvalue(input.value) : input.value;
|
|
10154
|
+
}
|
|
10155
|
+
function maskrecord(record2, shapes) {
|
|
10156
|
+
const masked = {};
|
|
10157
|
+
for (const [key, value] of Object.entries(record2)) {
|
|
10158
|
+
if (typeof value === "string") {
|
|
10159
|
+
const sibling = record2.name;
|
|
10160
|
+
masked[key] = key === "value" && typeof sibling === "string" ? maskfield({ name: sibling, value, shapes }) : maskfield({ name: key, value, shapes });
|
|
10161
|
+
} else if (Array.isArray(value)) masked[key] = value.map((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item) ? maskrecord(item, shapes) : item);
|
|
10162
|
+
else if (Boolean(value) && typeof value === "object") masked[key] = maskrecord(value, shapes);
|
|
10163
|
+
else masked[key] = value;
|
|
10164
|
+
}
|
|
10165
|
+
return masked;
|
|
10166
|
+
}
|
|
10167
|
+
function maskexport(record2, shapes) {
|
|
10168
|
+
return maskrecord(record2, shapes);
|
|
10169
|
+
}
|
|
9307
10170
|
|
|
9308
10171
|
// version.ts
|
|
9309
|
-
var packageversion = "1.1.
|
|
10172
|
+
var packageversion = "1.1.61";
|
|
9310
10173
|
|
|
9311
10174
|
// types.ts
|
|
9312
10175
|
var protocolversion = packageversion;
|
|
@@ -10270,6 +11133,16 @@ function runhistoryquery(value) {
|
|
|
10270
11133
|
function runhistoryreport(input) {
|
|
10271
11134
|
return { version: protocolversion, entries: input.entries, query: input.query ?? {} };
|
|
10272
11135
|
}
|
|
11136
|
+
function environmentreport(input) {
|
|
11137
|
+
return {
|
|
11138
|
+
version: protocolversion,
|
|
11139
|
+
environments: Object.entries(input.environments).map(([stepid, environment]) => ({ stepid, environment })),
|
|
11140
|
+
turnarounds: Object.entries(input.turnarounds ?? {}).map(([stepid, milliseconds]) => ({ stepid, milliseconds })),
|
|
11141
|
+
offscreen: input.offscreen ?? [],
|
|
11142
|
+
workers: input.workers ?? 0,
|
|
11143
|
+
...input.keepalive !== void 0 ? { keepalive: input.keepalive } : {}
|
|
11144
|
+
};
|
|
11145
|
+
}
|
|
10273
11146
|
|
|
10274
11147
|
// capture.ts
|
|
10275
11148
|
var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
|
|
@@ -10773,7 +11646,7 @@ async function readcapabilities() {
|
|
|
10773
11646
|
]);
|
|
10774
11647
|
return { tabs, downloads, clipboardread, clipboardwrite, reportedat: Date.now() };
|
|
10775
11648
|
}
|
|
10776
|
-
function
|
|
11649
|
+
function stepoptions2(step) {
|
|
10777
11650
|
if (!step.options) return {};
|
|
10778
11651
|
try {
|
|
10779
11652
|
const parsed = JSON.parse(step.options);
|
|
@@ -10786,7 +11659,7 @@ function tabid(step) {
|
|
|
10786
11659
|
return Number.parseInt(step.value ?? "", 10);
|
|
10787
11660
|
}
|
|
10788
11661
|
async function runbrowseraction(step, sessiontabid, windowid) {
|
|
10789
|
-
const options =
|
|
11662
|
+
const options = stepoptions2(step);
|
|
10790
11663
|
switch (step.kind) {
|
|
10791
11664
|
case "tablist": {
|
|
10792
11665
|
const tabs = await chrome.tabs.query({});
|
|
@@ -12211,6 +13084,41 @@ function unreadcount(mailboxes, agentid) {
|
|
|
12211
13084
|
return mailboxof(mailboxes, agentid).unread;
|
|
12212
13085
|
}
|
|
12213
13086
|
|
|
13087
|
+
// sandboxframe.ts
|
|
13088
|
+
function stripscripts(markup) {
|
|
13089
|
+
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();
|
|
13090
|
+
}
|
|
13091
|
+
function nonceof(seed) {
|
|
13092
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
13093
|
+
let hash = 0;
|
|
13094
|
+
for (let index = 0; index < seed.length; index += 1) hash = hash * 31 + seed.charCodeAt(index) >>> 0;
|
|
13095
|
+
let nonce = "";
|
|
13096
|
+
let state = hash === 0 ? 2654435769 : hash;
|
|
13097
|
+
for (let index = 0; index < 16; index += 1) {
|
|
13098
|
+
state = state * 1664525 + 1013904223 >>> 0;
|
|
13099
|
+
nonce += alphabet[state % alphabet.length];
|
|
13100
|
+
}
|
|
13101
|
+
return nonce;
|
|
13102
|
+
}
|
|
13103
|
+
function sandboxrenderof(input) {
|
|
13104
|
+
if (input.markup.trim() === "") throw new Error("The sandbox render needs its untrusted markup.");
|
|
13105
|
+
if (input.sourceorigin.trim() === "") throw new Error("The sandbox render needs the source origin of its untrusted markup.");
|
|
13106
|
+
if (input.stepid.trim() === "") throw new Error("The sandbox render names the reviewed step it renders for.");
|
|
13107
|
+
return { id: input.id, nonce: nonceof(`${input.id}:${input.now}`), markup: stripscripts(input.markup), sourceorigin: input.sourceorigin, stepid: input.stepid, renderedat: input.now };
|
|
13108
|
+
}
|
|
13109
|
+
function rendermessage(render) {
|
|
13110
|
+
return { channel: "devthinksandbox", type: "render", nonce: render.nonce, markup: render.markup };
|
|
13111
|
+
}
|
|
13112
|
+
function acceptrenderresult(input) {
|
|
13113
|
+
if (input.message.channel !== "devthinksandbox") return { accepted: false, reason: "The sandbox message travels the devthinksandbox channel only." };
|
|
13114
|
+
if (input.message.type !== "renderresult") return { accepted: false, reason: "The sandbox message answers with the renderresult type only." };
|
|
13115
|
+
const render = input.renders.find((entry) => entry.nonce === input.message.nonce && entry.renderedat <= input.now);
|
|
13116
|
+
if (!render) return { accepted: false, reason: "The sandbox message carries no nonce of a known render; a stale or replayed message never passes." };
|
|
13117
|
+
const text2 = (input.message.text ?? "").replace(/<[^>]*>/g, "");
|
|
13118
|
+
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 };
|
|
13119
|
+
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.` };
|
|
13120
|
+
}
|
|
13121
|
+
|
|
12214
13122
|
// modelroute.ts
|
|
12215
13123
|
function routevalid(route) {
|
|
12216
13124
|
if (route.kind.trim() === "") return { allowed: false, reason: "The model route needs its task kind." };
|
|
@@ -13423,7 +14331,7 @@ function extensionpage(sender) {
|
|
|
13423
14331
|
async function audit(kind, summary, extra = {}) {
|
|
13424
14332
|
await memory.addaudi({ id: randomid(), kind, at: Date.now(), summary, ...extra });
|
|
13425
14333
|
}
|
|
13426
|
-
function
|
|
14334
|
+
function stepoptions3(step) {
|
|
13427
14335
|
try {
|
|
13428
14336
|
return parseoptions(step);
|
|
13429
14337
|
} catch {
|
|
@@ -13516,11 +14424,333 @@ async function dispatchpagestep(step, tabid2, origin, plan) {
|
|
|
13516
14424
|
await harvestdialogs(session, activeplan, step.id, tabid2);
|
|
13517
14425
|
return result[0]?.result;
|
|
13518
14426
|
}
|
|
14427
|
+
var roadmapheartbeat = 3e4;
|
|
14428
|
+
var roadmapzombieintervals = 3;
|
|
14429
|
+
var runstateprofile = "default";
|
|
14430
|
+
var keepaliveport;
|
|
14431
|
+
var workerpoolcounts = /* @__PURE__ */ new Map();
|
|
14432
|
+
async function offscreengranted() {
|
|
14433
|
+
try {
|
|
14434
|
+
return await chrome.permissions.contains({ permissions: ["offscreen"] });
|
|
14435
|
+
} catch {
|
|
14436
|
+
return false;
|
|
14437
|
+
}
|
|
14438
|
+
}
|
|
14439
|
+
async function offscreendeclaration() {
|
|
14440
|
+
try {
|
|
14441
|
+
const response = await fetch(chrome.runtime.getURL("manifest.json"));
|
|
14442
|
+
const manifest = await response.json();
|
|
14443
|
+
if (!manifest.offscreen?.document || !manifest.offscreen.reasons?.length || !manifest.offscreen.justification?.trim()) return void 0;
|
|
14444
|
+
return { document: manifest.offscreen.document, reasons: manifest.offscreen.reasons, justification: manifest.offscreen.justification };
|
|
14445
|
+
} catch {
|
|
14446
|
+
return void 0;
|
|
14447
|
+
}
|
|
14448
|
+
}
|
|
14449
|
+
async function requestoffscreengrant() {
|
|
14450
|
+
try {
|
|
14451
|
+
return await chrome.permissions.request({ permissions: ["offscreen"] });
|
|
14452
|
+
} catch {
|
|
14453
|
+
return false;
|
|
14454
|
+
}
|
|
14455
|
+
}
|
|
14456
|
+
async function ensureoffscreendocument(runid) {
|
|
14457
|
+
const offscreen = chrome.offscreen;
|
|
14458
|
+
if (!offscreen) return false;
|
|
14459
|
+
const declaration = await offscreendeclaration();
|
|
14460
|
+
if (!declaration) return false;
|
|
14461
|
+
const opened = openoffscreen({ registry: await memory.getoffscreenentries(), document: declaration.document, runid, reasons: declaration.reasons, justification: declaration.justification, now: Date.now() });
|
|
14462
|
+
if (!opened.reused) {
|
|
14463
|
+
await memory.addoffscreenentry(opened.entry);
|
|
14464
|
+
await audit("worker", `The offscreen document ${declaration.document} spawned for the run ${runid} under the reasons ${declaration.reasons.join(", ")} with the reviewed justification.`, {});
|
|
14465
|
+
}
|
|
14466
|
+
try {
|
|
14467
|
+
if (!await offscreen.hasDocument()) await offscreen.createDocument({ url: chrome.runtime.getURL(declaration.document), reasons: declaration.reasons, justification: declaration.justification });
|
|
14468
|
+
return true;
|
|
14469
|
+
} catch {
|
|
14470
|
+
return false;
|
|
14471
|
+
}
|
|
14472
|
+
}
|
|
14473
|
+
async function closeoffscreendocument(runid) {
|
|
14474
|
+
const offscreen = chrome.offscreen;
|
|
14475
|
+
const outcome = closeoffscreen(await memory.getoffscreenentries(), runid, Date.now());
|
|
14476
|
+
if (!outcome.closed) return;
|
|
14477
|
+
const entry = outcome.registry.find((candidate) => candidate.runid === runid && candidate.closedat !== void 0);
|
|
14478
|
+
if (entry) await memory.updateoffscreenentry(entry);
|
|
14479
|
+
try {
|
|
14480
|
+
if (offscreen && await offscreen.hasDocument()) await offscreen.closeDocument();
|
|
14481
|
+
} catch {
|
|
14482
|
+
}
|
|
14483
|
+
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.`, {});
|
|
14484
|
+
}
|
|
14485
|
+
function startkeepaliveport(runid) {
|
|
14486
|
+
if (keepaliveport) return;
|
|
14487
|
+
try {
|
|
14488
|
+
keepaliveport = chrome.runtime.connect({ name: `devthinkkeepalive:${runid}` });
|
|
14489
|
+
keepaliveport.onDisconnect.addListener(() => {
|
|
14490
|
+
keepaliveport = void 0;
|
|
14491
|
+
});
|
|
14492
|
+
} catch {
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
async function openplanrun(session, plan) {
|
|
14496
|
+
const settings = await memory.getsettings();
|
|
14497
|
+
const interval = settings?.keepaliveinterval ?? roadmapheartbeat;
|
|
14498
|
+
const gate = keepalivegate({ session, plan, now: Date.now() });
|
|
14499
|
+
if (!gate.allowed) return;
|
|
14500
|
+
const existing = await memory.getrunstate(runstateprofile);
|
|
14501
|
+
if (existing && existing.keepalive.state === "active") return;
|
|
14502
|
+
const state = openrun({ runid: plan.id, sessionid: session.id, planid: plan.id, profileid: runstateprofile, interval, now: Date.now() });
|
|
14503
|
+
await memory.setrunstate(runstateprofile, state);
|
|
14504
|
+
const lock = acquirerunlock({ locks: await memory.getrunlocks(), sessionid: session.id, runid: plan.id, holder: "planrun", now: Date.now() });
|
|
14505
|
+
if (lock.acquired) await memory.setrunlocks(lock.locks);
|
|
14506
|
+
startkeepaliveport(plan.id);
|
|
14507
|
+
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 });
|
|
14508
|
+
}
|
|
14509
|
+
async function markpendingstep(plan, stepid) {
|
|
14510
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14511
|
+
if (!state || state.runid !== plan.id || state.keepalive.state !== "active") return;
|
|
14512
|
+
await memory.setrunstate(runstateprofile, markpending(state, stepid, Date.now()));
|
|
14513
|
+
}
|
|
14514
|
+
async function recordstepenvironment(step, environment, session, plan, origin, turnaround) {
|
|
14515
|
+
if (session && plan) {
|
|
14516
|
+
await memory.setprogress(recordenvironment2(await memory.getprogress(), plan.id, step.id, environment, Date.now()));
|
|
14517
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14518
|
+
if (state && state.runid === plan.id) {
|
|
14519
|
+
let next = recordenvironment(state, { stepid: step.id, environment, origin, now: Date.now() });
|
|
14520
|
+
if (turnaround !== void 0) next = recordturnaround(next, { stepid: step.id, milliseconds: turnaround, now: Date.now() });
|
|
14521
|
+
if (["navigate", "openlink", "followlink", "back", "forward"].includes(step.kind) && step.value) next = recordurl(next, { url: step.value, stepid: step.id, now: Date.now() });
|
|
14522
|
+
await memory.setrunstate(runstateprofile, next);
|
|
14523
|
+
}
|
|
14524
|
+
}
|
|
14525
|
+
if (plan && turnaround !== void 0) await memory.setprogress(recordturnaround2(await memory.getprogress(), plan.id, step.id, turnaround, Date.now()));
|
|
14526
|
+
}
|
|
14527
|
+
async function closeplanrun(planid, sessionid) {
|
|
14528
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
14529
|
+
if (!state || state.runid !== planid || state.keepalive.state !== "active") return;
|
|
14530
|
+
await memory.setrunstate(runstateprofile, closerun(state, Date.now()));
|
|
14531
|
+
const release = releaserunlock({ locks: await memory.getrunlocks(), sessionid, runid: planid, now: Date.now() });
|
|
14532
|
+
if (release.released) await memory.setrunlocks(release.locks);
|
|
14533
|
+
try {
|
|
14534
|
+
keepaliveport?.disconnect();
|
|
14535
|
+
} catch {
|
|
14536
|
+
}
|
|
14537
|
+
keepaliveport = void 0;
|
|
14538
|
+
await closeoffscreendocument(planid);
|
|
14539
|
+
await audit("environment", `The run state of the plan ${planid} closed at its terminal state and the keepalive port released.`, { ...sessionid !== "" ? { sessionid } : {}, planid });
|
|
14540
|
+
}
|
|
14541
|
+
async function appendrunevent(kind, summary, session, origin, stepid) {
|
|
14542
|
+
if (!session) return;
|
|
14543
|
+
const now = Date.now();
|
|
14544
|
+
let log = await memory.getimmutablelog(session.id);
|
|
14545
|
+
if (!log) {
|
|
14546
|
+
log = openrunlog({ runid: session.id, sessionid: session.id, now });
|
|
14547
|
+
await memory.trackimmutablelog(session.id);
|
|
14548
|
+
}
|
|
14549
|
+
log = await appendlogentry({ log, kind, summary, origin, ...stepid !== void 0 ? { stepid } : {}, at: now });
|
|
14550
|
+
await memory.setimmutablelog(log);
|
|
14551
|
+
}
|
|
14552
|
+
async function securitystepgate(step, session, origin, settings) {
|
|
14553
|
+
const classification = sensitiveclassesof(step);
|
|
14554
|
+
if (!session) return { allowed: true, suspended: false, reason: "The step runs behind the session review chain; a sessionless preview never dispatches.", classification };
|
|
14555
|
+
const now = Date.now();
|
|
14556
|
+
const allowverdict = automationallowlistgate({ origin, allowlist: await memory.getautomationallowlist(), session });
|
|
14557
|
+
if (!allowverdict.allowed) return { allowed: false, suspended: false, reason: allowverdict.reason ?? "", classification };
|
|
14558
|
+
const profile = (await memory.getoriginprofiles()).find((candidate) => candidate.origin === origin);
|
|
14559
|
+
const profileverdict = originprofilegate({ profile, kind: step.kind, sensitive: classification.sensitive });
|
|
14560
|
+
if (!profileverdict.allowed) return { allowed: false, suspended: false, reason: profileverdict.reason ?? "", classification };
|
|
14561
|
+
const windows = await memory.expireconsentwindows(now);
|
|
14562
|
+
const window2 = windows.find((candidate) => candidate.state === "active" && candidate.sessionid === session.id && candidate.origin === origin);
|
|
14563
|
+
const windowverdict = consentwindowgate({ window: window2, sessionid: session.id, origin, sensitive: classification.sensitive, now });
|
|
14564
|
+
if (!windowverdict.allowed) return { allowed: false, suspended: windowverdict.reason?.includes("suspends") ?? false, reason: windowverdict.reason ?? "", classification };
|
|
14565
|
+
const consentverdict = sensitiveclassgate({ origin, classes: classification.classes, bydefault: classification.bydefault, sensitive: classification.sensitive, consents: await memory.getclassconsents(), now });
|
|
14566
|
+
if (!consentverdict.allowed) return { allowed: false, suspended: false, reason: `${classification.reason} ${consentverdict.reason ?? ""}`, classification };
|
|
14567
|
+
const plan = await memory.getplan();
|
|
14568
|
+
const revocation = plan === void 0 ? void 0 : (await memory.getrevocations()).find((candidate) => candidate.sessionid === session.id && candidate.runid === plan.id);
|
|
14569
|
+
const revokeverdict = revokerungate({ revocation, sessionid: session.id, runid: plan?.id ?? "" });
|
|
14570
|
+
if (!revokeverdict.allowed) return { allowed: false, suspended: false, reason: revokeverdict.reason ?? "", classification };
|
|
14571
|
+
return { allowed: true, suspended: false, reason: `${classification.reason} ${allowverdict.reason ?? ""} ${windowverdict.reason ?? ""} ${consentverdict.reason ?? ""}`, classification };
|
|
14572
|
+
}
|
|
14573
|
+
async function sealsessionrunlog(sessionid) {
|
|
14574
|
+
const log = await memory.getimmutablelog(sessionid);
|
|
14575
|
+
if (!log || log.seal !== void 0 || log.entries.length === 0) return;
|
|
14576
|
+
const sealed = await sealrunlog(log, Date.now());
|
|
14577
|
+
await memory.setimmutablelog(sealed.log);
|
|
14578
|
+
await audit("seal", `The immutable run log sealed at completion with ${sealed.log.entries.length} entries and the final hash ${sealed.seal.sealhash.current}.`, { sessionid });
|
|
14579
|
+
}
|
|
14580
|
+
async function securityviewof() {
|
|
14581
|
+
const now = Date.now();
|
|
14582
|
+
const settings = await memory.getsettings();
|
|
14583
|
+
const session = await memory.getsession();
|
|
14584
|
+
const logs = await memory.listimmutablelogs();
|
|
14585
|
+
const chain = [];
|
|
14586
|
+
for (const log of logs) chain.push(await chainreportof(log));
|
|
14587
|
+
return {
|
|
14588
|
+
allowlist: await memory.getautomationallowlist(),
|
|
14589
|
+
profiles: await memory.getoriginprofiles(),
|
|
14590
|
+
windows: await memory.expireconsentwindows(now),
|
|
14591
|
+
consents: await memory.getclassconsents(),
|
|
14592
|
+
revocations: await memory.getrevocations(),
|
|
14593
|
+
maskrules: await memory.getmaskrules(),
|
|
14594
|
+
chain,
|
|
14595
|
+
posture: "denydefault",
|
|
14596
|
+
...session ? { sessionorigin: session.origin } : {},
|
|
14597
|
+
...settings?.consentduration !== void 0 ? { promptduration: settings.consentduration } : {},
|
|
14598
|
+
...settings?.logretention !== void 0 ? { logretention: settings.logretention } : {},
|
|
14599
|
+
...settings?.maskshapes !== void 0 ? { maskshapes: settings.maskshapes } : {}
|
|
14600
|
+
};
|
|
14601
|
+
}
|
|
14602
|
+
async function executeisolatedevaluate(step, tabid2, origin) {
|
|
14603
|
+
const injection = isolatedinjection(step);
|
|
14604
|
+
const result = await chrome.scripting.executeScript({ target: { tabId: tabid2 }, world: "ISOLATED", func: (code, args, expectedorigin) => {
|
|
14605
|
+
if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before the evaluate step ran." };
|
|
14606
|
+
try {
|
|
14607
|
+
let outcome;
|
|
14608
|
+
try {
|
|
14609
|
+
outcome = new Function(`"use strict"; return (${code});`)(...args);
|
|
14610
|
+
} catch {
|
|
14611
|
+
outcome = new Function(`"use strict"; ${code}`)(...args);
|
|
14612
|
+
}
|
|
14613
|
+
return { ok: true, summary: `Reviewed expression returned ${outcome === void 0 ? "no value" : "a value"} inside the isolated world.`, details: { result: String(outcome) } };
|
|
14614
|
+
} catch (error) {
|
|
14615
|
+
return { ok: false, summary: `Reviewed expression failed inside the isolated world: ${error instanceof Error ? error.message : String(error)}` };
|
|
14616
|
+
}
|
|
14617
|
+
}, args: [injection.code, injection.args, origin] });
|
|
14618
|
+
return result[0]?.result ?? { ok: false, summary: "The isolated world returned no result." };
|
|
14619
|
+
}
|
|
14620
|
+
async function executesandboxrender(step, session, plan, origin) {
|
|
14621
|
+
const options = stepoptions3(step);
|
|
14622
|
+
const markup = typeof options.markup === "string" ? options.markup : "";
|
|
14623
|
+
const sourceorigin = typeof options.sourceorigin === "string" ? options.sourceorigin : origin;
|
|
14624
|
+
const settings = await memory.getsettings();
|
|
14625
|
+
const origingate = sandboxorigingate({ origin: sourceorigin, allowed: settings?.sandboxorigins ?? [] });
|
|
14626
|
+
if (!origingate.allowed) throw new Error(origingate.reason);
|
|
14627
|
+
const render = sandboxrenderof({ id: randomid(), markup, sourceorigin, stepid: step.id, now: Date.now() });
|
|
14628
|
+
await memory.addsandboxrender(render);
|
|
14629
|
+
const hostready = await ensureoffscreendocument(plan?.id ?? step.id);
|
|
14630
|
+
let answer;
|
|
14631
|
+
if (hostready) {
|
|
14632
|
+
try {
|
|
14633
|
+
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 } });
|
|
14634
|
+
} catch {
|
|
14635
|
+
}
|
|
14636
|
+
}
|
|
14637
|
+
if (!answer) {
|
|
14638
|
+
try {
|
|
14639
|
+
answer = await chrome.runtime.sendMessage({ kind: "environments", action: "sandboxhost", render: rendermessage(render) });
|
|
14640
|
+
} catch {
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14643
|
+
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.");
|
|
14644
|
+
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() });
|
|
14645
|
+
if (!accepted.accepted || !accepted.result) throw new Error(accepted.reason);
|
|
14646
|
+
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 });
|
|
14647
|
+
await recordstepenvironment(step, "sandboxframe", session, plan, sourceorigin, void 0);
|
|
14648
|
+
return { ok: accepted.result.ok, summary: accepted.result.summary, details: { text: accepted.result.text, nonce: render.nonce, sourceorigin } };
|
|
14649
|
+
}
|
|
14650
|
+
async function offloadparsetoworker(step, output, session, plan, origin) {
|
|
14651
|
+
const runid = plan?.id ?? step.id;
|
|
14652
|
+
const ready = await ensureoffscreendocument(runid);
|
|
14653
|
+
if (!ready) return { output, turnaround: void 0 };
|
|
14654
|
+
const payload = JSON.stringify({ summary: output?.summary ?? "", details: output?.details ?? {} });
|
|
14655
|
+
const request = workerrequestof({ id: randomid(), runid, stepid: step.id, kind: step.kind, payload, options: stepoptions3(step), sentat: Date.now() });
|
|
14656
|
+
const started = Date.now();
|
|
14657
|
+
let answer;
|
|
14658
|
+
try {
|
|
14659
|
+
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 } });
|
|
14660
|
+
} catch {
|
|
14661
|
+
}
|
|
14662
|
+
const turnaround = Date.now() - started;
|
|
14663
|
+
if (!answer) return { output, turnaround: void 0 };
|
|
14664
|
+
const provenance = { origin, stepid: step.id, environment: "offscreenworker" };
|
|
14665
|
+
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() });
|
|
14666
|
+
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 });
|
|
14667
|
+
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 } };
|
|
14668
|
+
return { output: parsed, turnaround };
|
|
14669
|
+
}
|
|
14670
|
+
async function environmentviewof() {
|
|
14671
|
+
const session = await memory.getsession();
|
|
14672
|
+
const settings = await memory.getsettings();
|
|
14673
|
+
const states = await memory.listrunstates();
|
|
14674
|
+
const swept = zombiesweep({ states, now: Date.now(), interval: settings?.keepaliveinterval ?? roadmapheartbeat, missedlimit: settings?.zombieintervals ?? roadmapzombieintervals });
|
|
14675
|
+
const open = states.find((state) => state.keepalive.state === "active") ?? states[0];
|
|
14676
|
+
const workers = (await chrome.runtime.sendMessage({ kind: "offscreen", action: "pool" }).catch(() => void 0))?.workers ?? 0;
|
|
14677
|
+
return {
|
|
14678
|
+
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 } } : {} }),
|
|
14679
|
+
...session?.environmentgrants !== void 0 ? { grants: session.environmentgrants } : {},
|
|
14680
|
+
requirements: environmentrequirements(),
|
|
14681
|
+
registry: executorregistry(),
|
|
14682
|
+
offscreengranted: await offscreengranted(),
|
|
14683
|
+
parseoffload: settings?.parseoffload === true,
|
|
14684
|
+
...settings?.workerpoolsize !== void 0 ? { workerpoolsize: settings.workerpoolsize } : {},
|
|
14685
|
+
...settings?.sandboxorigins !== void 0 ? { sandboxorigins: settings.sandboxorigins } : {},
|
|
14686
|
+
workerevents: (await memory.getworkerevents()).map((event) => ({ id: event.id, runid: event.runid, kind: event.kind, workers: event.workers, reason: event.reason, at: event.at })),
|
|
14687
|
+
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 })),
|
|
14688
|
+
locks: await memory.getrunlocks(),
|
|
14689
|
+
zombies: swept.reaped,
|
|
14690
|
+
urls: (open?.urlhistory ?? []).slice(-50),
|
|
14691
|
+
...open !== void 0 ? { recovery: recoveryplan(open) } : {},
|
|
14692
|
+
...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.` } : {}
|
|
14693
|
+
};
|
|
14694
|
+
}
|
|
14695
|
+
async function keepalivetick() {
|
|
14696
|
+
const settings = await memory.getsettings();
|
|
14697
|
+
const interval = settings?.keepaliveinterval ?? roadmapheartbeat;
|
|
14698
|
+
const tolerance = settings?.zombieintervals ?? roadmapzombieintervals;
|
|
14699
|
+
for (const state of await memory.listrunstates()) {
|
|
14700
|
+
if (state.keepalive.state !== "active") continue;
|
|
14701
|
+
await memory.setrunstate(state.profileid, beatrun(state, Date.now()));
|
|
14702
|
+
startkeepaliveport(state.runid);
|
|
14703
|
+
}
|
|
14704
|
+
const swept = zombiesweep({ states: await memory.listrunstates(), now: Date.now(), interval, missedlimit: tolerance });
|
|
14705
|
+
for (const runid of swept.reaped) {
|
|
14706
|
+
await closeoffscreendocument(runid);
|
|
14707
|
+
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.`, {});
|
|
14708
|
+
}
|
|
14709
|
+
for (const state of swept.states) await memory.setrunstate(state.profileid, state);
|
|
14710
|
+
await memory.expirerunstates(settings?.runstateretention, Date.now());
|
|
14711
|
+
const locks = expirerunlocks(await memory.getrunlocks(), Date.now());
|
|
14712
|
+
if (locks.expired.length > 0) await memory.setrunlocks(locks.locks);
|
|
14713
|
+
try {
|
|
14714
|
+
const estimate = await navigator.storage.estimate();
|
|
14715
|
+
if (estimate.usage !== void 0) await memory.trackrunstatequota(estimate.usage);
|
|
14716
|
+
if (settings?.runstatebytes !== void 0) {
|
|
14717
|
+
const quota = await memory.getrunstatequota();
|
|
14718
|
+
if (quota && quota.used > settings.runstatebytes) {
|
|
14719
|
+
const pruned = prunerunstates({ states: await memory.listrunstates(), used: quota.used, ceiling: settings.runstatebytes });
|
|
14720
|
+
for (const runid of pruned.pruned) {
|
|
14721
|
+
const state = await memory.listrunstates();
|
|
14722
|
+
const target = state.find((entry) => entry.runid === runid);
|
|
14723
|
+
if (target) await memory.removerunstate(target.profileid);
|
|
14724
|
+
}
|
|
14725
|
+
if (pruned.pruned.length > 0) await audit("environment", pruned.reason, {});
|
|
14726
|
+
}
|
|
14727
|
+
}
|
|
14728
|
+
} catch {
|
|
14729
|
+
}
|
|
14730
|
+
}
|
|
14731
|
+
async function restorerunstates() {
|
|
14732
|
+
const settings = await memory.getsettings();
|
|
14733
|
+
const states = await memory.listrunstates();
|
|
14734
|
+
for (const state of states) {
|
|
14735
|
+
if (state.keepalive.state !== "active") continue;
|
|
14736
|
+
await memory.setrunstate(state.profileid, reattachrun(state, Date.now()));
|
|
14737
|
+
startkeepaliveport(state.runid);
|
|
14738
|
+
await audit("environment", `The service worker restarted and the run ${state.runid} reattached its keepalive port from the persisted run state; ${recoveryplan(state).reason}`, {});
|
|
14739
|
+
}
|
|
14740
|
+
void settings;
|
|
14741
|
+
}
|
|
13519
14742
|
async function startsession() {
|
|
13520
14743
|
const { tab, origin } = await activecontext();
|
|
13521
14744
|
const session = { id: randomid(), tabid: tab.id, origin, startedat: Date.now(), expiresat: Date.now() + sessionduration, grants: [origin] };
|
|
13522
14745
|
await memory.setsession(session);
|
|
14746
|
+
await memory.addallowlistorigin({ origin, profileid: runstateprofile, grantedat: session.startedat });
|
|
14747
|
+
const scope = scopegrantof({ origin, kinds: ["observe"], boundary: `the session expiry at ${new Date(session.expiresat).toISOString()}`, now: session.startedat });
|
|
14748
|
+
let runlog = openrunlog({ runid: session.id, sessionid: session.id, now: session.startedat });
|
|
14749
|
+
runlog = await appendlogentry({ log: runlog, kind: "grant", summary: `The session started for ${origin} with the consentscope grant: the origin ${scope.origin}, the ${scope.kinds.join(", ")} kinds of the observation baseline and the boundary ${scope.boundary}; the active tab counts as exactly one explicit single origin grant.`, origin, at: session.startedat });
|
|
14750
|
+
await memory.trackimmutablelog(session.id);
|
|
14751
|
+
await memory.setimmutablelog(runlog);
|
|
13523
14752
|
await audit("session", `Session started for ${origin}.`, { sessionid: session.id });
|
|
14753
|
+
await audit("grant", `The consentscope grant of ${origin} was written into the immutable log with the boundary ${scope.boundary}.`, { sessionid: session.id });
|
|
13524
14754
|
const policy = await memory.getdialogpolicy();
|
|
13525
14755
|
if (policy) {
|
|
13526
14756
|
try {
|
|
@@ -13625,7 +14855,7 @@ function stepauditkind(step, ok) {
|
|
|
13625
14855
|
return ok ? "action" : "error";
|
|
13626
14856
|
}
|
|
13627
14857
|
function resolvedinnerstep(step, plan) {
|
|
13628
|
-
const options =
|
|
14858
|
+
const options = stepoptions3(step);
|
|
13629
14859
|
if (typeof options.stepid === "string" && options.stepid.trim()) {
|
|
13630
14860
|
return plan.steps.find((candidate) => candidate.id === options.stepid) ?? null;
|
|
13631
14861
|
}
|
|
@@ -13634,7 +14864,7 @@ function resolvedinnerstep(step, plan) {
|
|
|
13634
14864
|
async function executekeyhold(step, session, plan, tabid2, origin) {
|
|
13635
14865
|
const output = await dispatchpagestep(step, tabid2, origin, plan);
|
|
13636
14866
|
if (!output?.ok) return output ?? { ok: false, summary: "The key hold was not delivered." };
|
|
13637
|
-
const options =
|
|
14867
|
+
const options = stepoptions3(step);
|
|
13638
14868
|
const holdid = typeof options.holdid === "string" && options.holdid.trim() ? options.holdid : randomid();
|
|
13639
14869
|
const modifiers = Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
|
|
13640
14870
|
const hold = { holdid, key: step.value ?? "", ...modifiers.length > 0 ? { modifiers } : {}, tabid: tabid2, stepid: step.id, pressedat: Date.now() };
|
|
@@ -13667,7 +14897,7 @@ async function executedismissdialog(step, session, plan, tabid2, origin) {
|
|
|
13667
14897
|
return { ok: true, summary: `Dialog handler armed${answer} for the next confirm, alert or prompt.` };
|
|
13668
14898
|
}
|
|
13669
14899
|
async function executeretryaction(step, session, plan, tabid2, origin) {
|
|
13670
|
-
const rule =
|
|
14900
|
+
const rule = stepoptions3(step).retryrule;
|
|
13671
14901
|
const inner = resolvedinnerstep(step, plan);
|
|
13672
14902
|
if (!inner) throw new Error("The reviewed wrapper step could not be resolved.");
|
|
13673
14903
|
const innergate = validatestep(inner, origin);
|
|
@@ -13703,8 +14933,8 @@ async function executeenterframe(step, plan, tabid2, origin) {
|
|
|
13703
14933
|
if (!inner) throw new Error("The reviewed frame wrapper step could not be resolved.");
|
|
13704
14934
|
const innergate = validatestep(inner, origin);
|
|
13705
14935
|
if (!innergate.allowed) throw new Error(`The wrapped step is not allowed: ${innergate.reason}`);
|
|
13706
|
-
const options =
|
|
13707
|
-
const inneroptions = inner.options ?
|
|
14936
|
+
const options = stepoptions3(step);
|
|
14937
|
+
const inneroptions = inner.options ? stepoptions3(inner) : void 0;
|
|
13708
14938
|
const derived = { ...step, options: JSON.stringify({ ...options, kind: inner.kind, ...inner.target ? { target: inner.target } : {}, ...inner.value ? { value: inner.value } : {}, ...inneroptions ? { options: inneroptions } : {} }) };
|
|
13709
14939
|
return dispatchpagestep(derived, tabid2, origin, plan);
|
|
13710
14940
|
}
|
|
@@ -13716,7 +14946,7 @@ function detailarray(details, key) {
|
|
|
13716
14946
|
return Array.isArray(value) ? value : [];
|
|
13717
14947
|
}
|
|
13718
14948
|
async function executediffsnapshots(step, session, plan, tabid2, origin) {
|
|
13719
|
-
const options =
|
|
14949
|
+
const options = stepoptions3(step);
|
|
13720
14950
|
const versions = Array.isArray(options.versions) ? options.versions.filter((item) => typeof item === "number") : [];
|
|
13721
14951
|
const baseversion = versions[0];
|
|
13722
14952
|
const targetversion = versions[1];
|
|
@@ -13739,7 +14969,7 @@ async function executediffsnapshots(step, session, plan, tabid2, origin) {
|
|
|
13739
14969
|
return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, versions: [baseversion, targetversion] } };
|
|
13740
14970
|
}
|
|
13741
14971
|
async function executewatchstep(step, session, plan, tabid2, origin) {
|
|
13742
|
-
const options =
|
|
14972
|
+
const options = stepoptions3(step);
|
|
13743
14973
|
const watchid = typeof options.watchid === "string" && options.watchid.trim() ? options.watchid : randomid();
|
|
13744
14974
|
const scopes = Array.isArray(options.scopes) ? options.scopes.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
13745
14975
|
const events = Array.isArray(options.events) ? options.events.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
@@ -14421,7 +15651,7 @@ function commandtabids(step, options) {
|
|
|
14421
15651
|
return listed.length > 0 ? listed : single;
|
|
14422
15652
|
}
|
|
14423
15653
|
async function executetabscommand(step, session, plan, sessiontabid) {
|
|
14424
|
-
const options =
|
|
15654
|
+
const options = stepoptions3(step);
|
|
14425
15655
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
14426
15656
|
const windowid = step.value && /^\d+$/.test(step.value) ? Number.parseInt(step.value, 10) : 0;
|
|
14427
15657
|
const layoutgate = layoutmutationgranted(session, Date.now());
|
|
@@ -14697,7 +15927,7 @@ async function executetabscommand(step, session, plan, sessiontabid) {
|
|
|
14697
15927
|
}
|
|
14698
15928
|
}
|
|
14699
15929
|
async function executesaveprofiles(step, session, origin) {
|
|
14700
|
-
const options =
|
|
15930
|
+
const options = stepoptions3(step);
|
|
14701
15931
|
const record2 = parseformrecord(options.formrecord);
|
|
14702
15932
|
const name = typeof options.name === "string" ? options.name : "";
|
|
14703
15933
|
if (!name || !record2) throw new Error("A reviewed profile name and form record are required.");
|
|
@@ -14717,7 +15947,7 @@ async function executeasksubmit(step, session, plan, tabid2, origin) {
|
|
|
14717
15947
|
return { ok: true, summary: `Asksubmit prompt opened for form ${ticket.form || "the reviewed form"}; the submission waits for your approval.`, details: { ticket, values } };
|
|
14718
15948
|
}
|
|
14719
15949
|
async function executesubmitform(step, session, plan, tabid2, origin) {
|
|
14720
|
-
const consentref = typeof
|
|
15950
|
+
const consentref = typeof stepoptions3(step).consentref === "string" ? stepoptions3(step).consentref : "";
|
|
14721
15951
|
const ticket = (await memory.gettickets()).find((item) => item.approved === true && (item.consentref === consentref || item.id === consentref));
|
|
14722
15952
|
if (!ticket) throw new Error("No approved asksubmit ticket matches the reviewed consent ref; approve the submission in the review panel first.");
|
|
14723
15953
|
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The form submission returned no result." };
|
|
@@ -14741,7 +15971,7 @@ async function executeretryform(step, session, plan, tabid2, origin) {
|
|
|
14741
15971
|
return { ok: Boolean(output?.ok), summary: output?.summary ?? "The retried submission returned no result.", details: { attempts, windows, ok: Boolean(output?.ok) } };
|
|
14742
15972
|
}
|
|
14743
15973
|
async function executeconsentpassword(step, session, plan, tabid2, origin) {
|
|
14744
|
-
const consentref = typeof
|
|
15974
|
+
const consentref = typeof stepoptions3(step).consentref === "string" ? stepoptions3(step).consentref : "";
|
|
14745
15975
|
const gate = passwordconsentgranted(step);
|
|
14746
15976
|
if (!gate.allowed) throw new Error(gate.reason ?? "A password fill requires a reviewed consent ref.");
|
|
14747
15977
|
const output = await dispatchpagestep(step, tabid2, origin, plan);
|
|
@@ -14753,7 +15983,7 @@ async function executeattachfile(step, session, plan, tabid2, origin) {
|
|
|
14753
15983
|
const artifacts = await memory.getartifacts();
|
|
14754
15984
|
const artifact = artifacts.find((item) => item.name === name || item.id === name);
|
|
14755
15985
|
if (!artifact) throw new Error(`No generated artifact named ${name} exists in the run store yet.`);
|
|
14756
|
-
const derived = { ...step, options: JSON.stringify({ ...
|
|
15986
|
+
const derived = { ...step, options: JSON.stringify({ ...stepoptions3(step), artifact: artifact.id, artifactname: artifact.name }) };
|
|
14757
15987
|
const output = await dispatchpagestep(derived, tabid2, origin, plan);
|
|
14758
15988
|
await audit("fill", `Artifact ${artifact.name} of kind ${artifact.kind} attached to the reviewed file input inside the form submission.`, { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id });
|
|
14759
15989
|
return { ...output ?? { ok: false, summary: "The artifact attachment returned no result." }, details: { ...output?.details ?? {}, artifact } };
|
|
@@ -14786,7 +16016,7 @@ async function executeformstep(step, session, plan, tabid2, origin) {
|
|
|
14786
16016
|
return executecaptchahandoff(step, session, plan, tabid2, origin);
|
|
14787
16017
|
case "fillcode": {
|
|
14788
16018
|
const stored = await memory.getcodevalue();
|
|
14789
|
-
const source = typeof
|
|
16019
|
+
const source = typeof stepoptions3(step).source === "string" ? stepoptions3(step).source : "";
|
|
14790
16020
|
const derived = stored !== void 0 && source === "reviewed" ? { ...step, value: stored } : step;
|
|
14791
16021
|
const output = await dispatchpagestep(derived, tabid2, origin, plan);
|
|
14792
16022
|
await audit("fill", `One time code typed from the reviewed source ${source}${stored !== void 0 ? " through the consent gated code entry" : ""}.`, { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id });
|
|
@@ -14857,7 +16087,7 @@ async function storeexport(stepid, datasetvalue, format, delimiter, session, pla
|
|
|
14857
16087
|
return artifact;
|
|
14858
16088
|
}
|
|
14859
16089
|
async function executedatastep(step, session, plan, tabid2, origin) {
|
|
14860
|
-
const options =
|
|
16090
|
+
const options = stepoptions3(step);
|
|
14861
16091
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
14862
16092
|
switch (step.kind) {
|
|
14863
16093
|
case "scrapetable": {
|
|
@@ -15087,7 +16317,7 @@ async function verifyonerecord(record2, expected, extra) {
|
|
|
15087
16317
|
return { ok: verification.ok, summary: verification.summary, details: { verification: { ...verification.matches, state: record2.state, path: record2.path, checksum: record2.checksum, bytes: record2.bytes } } };
|
|
15088
16318
|
}
|
|
15089
16319
|
async function executefilesstep(step, session, plan, tabid2, origin) {
|
|
15090
|
-
const options =
|
|
16320
|
+
const options = stepoptions3(step);
|
|
15091
16321
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
15092
16322
|
switch (step.kind) {
|
|
15093
16323
|
case "batchdownload": {
|
|
@@ -15316,7 +16546,7 @@ reconcilmimefilter().catch(() => {
|
|
|
15316
16546
|
});
|
|
15317
16547
|
var stitchprogress = /* @__PURE__ */ new Map();
|
|
15318
16548
|
function stepcaptureoptions(step) {
|
|
15319
|
-
return captureoptionsof(
|
|
16549
|
+
return captureoptionsof(stepoptions3(step).capture);
|
|
15320
16550
|
}
|
|
15321
16551
|
async function blobtodataurl(blob) {
|
|
15322
16552
|
const buffer = new Uint8Array(await blob.arrayBuffer());
|
|
@@ -15440,7 +16670,7 @@ async function encodecanvas(width, height, draw, options) {
|
|
|
15440
16670
|
return canvasdataurl(canvas, options.format, options.quality);
|
|
15441
16671
|
}
|
|
15442
16672
|
async function capturenamefor(step, plan, kind, format) {
|
|
15443
|
-
const naming =
|
|
16673
|
+
const naming = stepoptions3(step).naming;
|
|
15444
16674
|
const rule = naming && typeof naming === "object" && !Array.isArray(naming) ? naming : { run: true, step: true, sequence: true, kind: true };
|
|
15445
16675
|
const counters = (await memory.getcapturecounters()).find((item) => item.taskid === plan.id);
|
|
15446
16676
|
const advanced = advancecounter(counters?.counters ?? {}, step.id);
|
|
@@ -15509,7 +16739,7 @@ async function executecapturestep(step, session, plan, tabid2, origin) {
|
|
|
15509
16739
|
return { ok: true, summary: `Captured the visible viewport at ${record2.width} by ${record2.height} pixels.`, details: { capture: { id: record2.id, kind: record2.kind, format: record2.format, width: record2.width, height: record2.height, bytes: (record2.bytes ?? "").length, exporttarget: stored.routed.target } } };
|
|
15510
16740
|
}
|
|
15511
16741
|
if (step.kind === "shotfullpage") {
|
|
15512
|
-
const rawoptions =
|
|
16742
|
+
const rawoptions = stepoptions3(step);
|
|
15513
16743
|
const settle2 = typeof rawoptions.settle === "number" ? rawoptions.settle : 150;
|
|
15514
16744
|
const overlap = typeof rawoptions.overlap === "number" ? rawoptions.overlap : 0;
|
|
15515
16745
|
const wait = typeof rawoptions.wait === "number" ? rawoptions.wait : void 0;
|
|
@@ -15546,7 +16776,7 @@ async function executecapturestep(step, session, plan, tabid2, origin) {
|
|
|
15546
16776
|
}
|
|
15547
16777
|
if (step.kind === "shotelement") {
|
|
15548
16778
|
const selector = step.target ?? "";
|
|
15549
|
-
const settle2 = typeof
|
|
16779
|
+
const settle2 = typeof stepoptions3(step).settle === "number" ? stepoptions3(step).settle : 150;
|
|
15550
16780
|
const measured = await bridgecall(tabid2, "measurepage");
|
|
15551
16781
|
const targetinfo = await bridgecall(tabid2, "elementrect", selector);
|
|
15552
16782
|
if (!targetinfo.ok || !targetinfo.rect) throw new Error(targetinfo.summary);
|
|
@@ -15587,7 +16817,7 @@ async function executecapturestep(step, session, plan, tabid2, origin) {
|
|
|
15587
16817
|
}
|
|
15588
16818
|
}
|
|
15589
16819
|
if (step.kind === "shotregion") {
|
|
15590
|
-
const rawoptions =
|
|
16820
|
+
const rawoptions = stepoptions3(step);
|
|
15591
16821
|
const rect = rawoptions.regionrect;
|
|
15592
16822
|
if (!rect) throw new Error("A reviewed regionrect is required in options.");
|
|
15593
16823
|
const container = typeof rawoptions.container === "string" ? rawoptions.container : void 0;
|
|
@@ -15628,7 +16858,7 @@ async function executecapturestep(step, session, plan, tabid2, origin) {
|
|
|
15628
16858
|
}
|
|
15629
16859
|
}
|
|
15630
16860
|
if (step.kind === "contactsheet") {
|
|
15631
|
-
const rawoptions =
|
|
16861
|
+
const rawoptions = stepoptions3(step);
|
|
15632
16862
|
const elements = (Array.isArray(rawoptions.elements) ? rawoptions.elements : []).filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
15633
16863
|
const layout = rawoptions.sheet && typeof rawoptions.sheet === "object" && !Array.isArray(rawoptions.sheet) ? rawoptions.sheet : { cellsize: 240, columns: 3, label: "both" };
|
|
15634
16864
|
const measured = await bridgecall(tabid2, "measurepage");
|
|
@@ -15739,7 +16969,7 @@ async function thumbonecapture(source, directive, plan, step) {
|
|
|
15739
16969
|
return { id: randomid(), runid: plan.id, stepid: step.id, kind: "makethumbs", format, width: geometry.width, height: geometry.height, capturedat: Date.now(), bytes: dataurl, name, target: source.id };
|
|
15740
16970
|
}
|
|
15741
16971
|
async function executemediastep(step, session, plan, tabid2, origin) {
|
|
15742
|
-
const options =
|
|
16972
|
+
const options = stepoptions3(step);
|
|
15743
16973
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
15744
16974
|
const gate = mediagate(session, tabid2, origin, Date.now());
|
|
15745
16975
|
if (!gate.allowed) throw new Error(gate.reason ?? "The media capture needs the active session tab grant.");
|
|
@@ -15998,7 +17228,7 @@ async function attachapikeys(names, origin) {
|
|
|
15998
17228
|
return { headers, keys: attached };
|
|
15999
17229
|
}
|
|
16000
17230
|
async function executehttpstep(step, session, plan, tabid2, origin) {
|
|
16001
|
-
const options =
|
|
17231
|
+
const options = stepoptions3(step);
|
|
16002
17232
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
16003
17233
|
if (step.kind === "fetchurl") {
|
|
16004
17234
|
const request = fetchrequestof(options.fetch);
|
|
@@ -16263,7 +17493,7 @@ async function closechannelsforrun(runid) {
|
|
|
16263
17493
|
channelbuses.clear();
|
|
16264
17494
|
}
|
|
16265
17495
|
async function executesocketstep(step, session, plan, tabid2, origin) {
|
|
16266
|
-
const options =
|
|
17496
|
+
const options = stepoptions3(step);
|
|
16267
17497
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
16268
17498
|
if (step.kind === "opensocket") {
|
|
16269
17499
|
const channel = channeloptionsof(options.socket);
|
|
@@ -16397,7 +17627,7 @@ async function executesocketstep(step, session, plan, tabid2, origin) {
|
|
|
16397
17627
|
throw new Error("Unsupported socket observation kind.");
|
|
16398
17628
|
}
|
|
16399
17629
|
async function executenetwatchstep(step, session, plan, tabid2, origin) {
|
|
16400
|
-
const options =
|
|
17630
|
+
const options = stepoptions3(step);
|
|
16401
17631
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
16402
17632
|
if (step.kind === "watchrequests") {
|
|
16403
17633
|
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
@@ -16549,7 +17779,7 @@ function timelinedetail(entry, runid) {
|
|
|
16549
17779
|
return { id: randomid(), runid, stepid: record2.stepid, time: record2.time, level: record2.level, source: record2.source, message: record2.message };
|
|
16550
17780
|
}
|
|
16551
17781
|
async function executetimelinestep(step, session, plan, tabid2, origin) {
|
|
16552
|
-
const options =
|
|
17782
|
+
const options = stepoptions3(step);
|
|
16553
17783
|
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
16554
17784
|
const watchwindow = typeof watch.window === "number" && Number.isFinite(watch.window) && watch.window >= 0 ? watch.window : 0;
|
|
16555
17785
|
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
@@ -16704,7 +17934,7 @@ function pauseframes(entry) {
|
|
|
16704
17934
|
});
|
|
16705
17935
|
}
|
|
16706
17936
|
async function executecdpstep(step, session, plan, tabid2, origin) {
|
|
16707
|
-
const options =
|
|
17937
|
+
const options = stepoptions3(step);
|
|
16708
17938
|
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
16709
17939
|
const debuggatecheck = debuggate(session, tabid2, origin, Date.now());
|
|
16710
17940
|
if (!debuggatecheck.allowed) throw new Error(debuggatecheck.reason ?? "The devtools protocol step stays outside the debug gate.");
|
|
@@ -16938,7 +18168,7 @@ async function sampleheapforstep(tracker, stepid, tabid2, origin, plan) {
|
|
|
16938
18168
|
if (trend.flaggedsteps.includes(stepid)) await memory.addtimelineentry({ id: randomid(), runid: tracker.runid, stepid, time: now, level: "warn", source: "longtask", message: `Heap growth of step ${stepid} exceeds the reviewed slope of ${tracker.slope} bytes per millisecond; the step is flagged in the memory trend.` });
|
|
16939
18169
|
}
|
|
16940
18170
|
async function executeprofilestep(step, session, plan, tabid2, origin) {
|
|
16941
|
-
const options =
|
|
18171
|
+
const options = stepoptions3(step);
|
|
16942
18172
|
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
16943
18173
|
const targets = profiletargetsof(options);
|
|
16944
18174
|
const grants = await memory.getdebuggergrants();
|
|
@@ -17267,7 +18497,7 @@ async function controlledfetch(runid, url, init, controller, window2, streamstat
|
|
|
17267
18497
|
return response;
|
|
17268
18498
|
}
|
|
17269
18499
|
async function executenetcontrolstep(step, session, plan, tabid2, origin) {
|
|
17270
|
-
const options =
|
|
18500
|
+
const options = stepoptions3(step);
|
|
17271
18501
|
const extra = { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id };
|
|
17272
18502
|
const ruleset = rulesetof(plan.id);
|
|
17273
18503
|
if (step.kind === "blockrequest") {
|
|
@@ -17514,7 +18744,7 @@ async function enforcewindowreview(step, session, plan) {
|
|
|
17514
18744
|
const progress = plan ? await memory.getprogress() : void 0;
|
|
17515
18745
|
const tasktabids = plan ? trackedtasktabs(progress, plan.id) : [];
|
|
17516
18746
|
const count = tasktabsinwindow(await livetabs(), windowid, tasktabids);
|
|
17517
|
-
const gate = windowclosegate(count,
|
|
18747
|
+
const gate = windowclosegate(count, stepoptions3(step).reviewed === true);
|
|
17518
18748
|
if (!gate.allowed) throw new Error(gate.reason ?? "The window close needs explicit review.");
|
|
17519
18749
|
if (session && count > 0) await audit("window", `Window ${windowid} closes while holding ${count} task tab${count === 1 ? "" : "s"} under the explicit reviewed flag.`, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
17520
18750
|
}
|
|
@@ -17600,7 +18830,7 @@ async function revertemulationforrun(runid, reason, tabid2) {
|
|
|
17600
18830
|
}
|
|
17601
18831
|
}
|
|
17602
18832
|
async function executeemulationstep(step, session, plan, tabid2, origin) {
|
|
17603
|
-
const options =
|
|
18833
|
+
const options = stepoptions3(step);
|
|
17604
18834
|
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
17605
18835
|
const revertplan = revertplanof(options.revertplan) ?? [];
|
|
17606
18836
|
const family = familyofkind(step.kind) ?? "device";
|
|
@@ -17732,7 +18962,7 @@ async function performrestore(record2, restore, session) {
|
|
|
17732
18962
|
return { restored, skippedorigins: grantscheck.skippedorigins };
|
|
17733
18963
|
}
|
|
17734
18964
|
async function executesessionstep(step, session, plan, tabid2, origin) {
|
|
17735
|
-
const options =
|
|
18965
|
+
const options = stepoptions3(step);
|
|
17736
18966
|
const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
|
|
17737
18967
|
if (step.kind === "persiststate") {
|
|
17738
18968
|
const progress = await memory.getprogress();
|
|
@@ -17877,7 +19107,7 @@ async function dispatchworkflowstep(step, context) {
|
|
|
17877
19107
|
return { ok: Boolean(output.ok), summary: output.summary, ...output.details !== void 0 ? { details: output.details } : {} };
|
|
17878
19108
|
}
|
|
17879
19109
|
async function executedelaystep(step) {
|
|
17880
|
-
const options =
|
|
19110
|
+
const options = stepoptions3({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.options !== void 0 ? { options: step.options } : {} });
|
|
17881
19111
|
const delay = delayof(options.delay);
|
|
17882
19112
|
const sampled = delayjitter(delay, hashseed(`${step.id}:${Date.now()}`));
|
|
17883
19113
|
const transport = await sleepreviewed(sampled, step.id);
|
|
@@ -17924,7 +19154,7 @@ async function sleepreviewed(sampled, stepid) {
|
|
|
17924
19154
|
return "timer";
|
|
17925
19155
|
}
|
|
17926
19156
|
async function executewaitelement(step, tabid2) {
|
|
17927
|
-
const options =
|
|
19157
|
+
const options = stepoptions3({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.target !== void 0 ? { target: step.target } : {}, ...step.options !== void 0 ? { options: step.options } : {} });
|
|
17928
19158
|
const wait = waitof(options.wait, step.target);
|
|
17929
19159
|
const startedat = Date.now();
|
|
17930
19160
|
const starttab = await chrome.tabs.get(tabid2).catch(() => void 0);
|
|
@@ -17952,7 +19182,7 @@ function waitof(value, target) {
|
|
|
17952
19182
|
return { selector, timeout, poll };
|
|
17953
19183
|
}
|
|
17954
19184
|
async function executecomputestep(step, session) {
|
|
17955
|
-
const options =
|
|
19185
|
+
const options = stepoptions3({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.options !== void 0 ? { options: step.options } : {} });
|
|
17956
19186
|
const expression = options.expression;
|
|
17957
19187
|
if (!expression || typeof expression !== "object") throw new Error("The compute step needs a reviewed expression.");
|
|
17958
19188
|
const scopes = runscopes(options.variables);
|
|
@@ -17961,7 +19191,7 @@ async function executecomputestep(step, session) {
|
|
|
17961
19191
|
return { ok: true, summary: `Computed ${expression.result} = ${typeof value === "string" ? `"${value}"` : String(value)} through the ${expression.operator} operator.`, details: { result: expression.result, kind: expression.resultkind, value } };
|
|
17962
19192
|
}
|
|
17963
19193
|
async function executeextractvarsstep(step, session) {
|
|
17964
|
-
const options =
|
|
19194
|
+
const options = stepoptions3({ id: step.id, kind: step.kind, summary: step.label, risk: "read", ...step.value !== void 0 ? { value: step.value } : {}, ...step.options !== void 0 ? { options: step.options } : {} });
|
|
17965
19195
|
const rule = options.rule;
|
|
17966
19196
|
if (!rule || typeof rule !== "object" || typeof rule.pattern !== "string") throw new Error("The variable extraction needs a reviewed regex rule.");
|
|
17967
19197
|
const text2 = typeof options.text === "string" ? options.text : step.value ?? "";
|
|
@@ -17974,7 +19204,7 @@ async function executeextractvarsstep(step, session) {
|
|
|
17974
19204
|
return { ok: true, summary: `Captured ${extraction.variables.length} variable${extraction.variables.length === 1 ? "" : "s"} from the reviewed text.`, details: { matched: true, variables: extraction.variables } };
|
|
17975
19205
|
}
|
|
17976
19206
|
async function executeworkflowstep(step, session, plan, tabid2, origin) {
|
|
17977
|
-
const options =
|
|
19207
|
+
const options = stepoptions3(step);
|
|
17978
19208
|
if (step.kind === "composeworkflow") {
|
|
17979
19209
|
const payload = options.workflow;
|
|
17980
19210
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("The workflow composition needs the reviewed workflow payload.");
|
|
@@ -18030,7 +19260,7 @@ function workflowstepofentry(value) {
|
|
|
18030
19260
|
return blockinvocationof(value);
|
|
18031
19261
|
}
|
|
18032
19262
|
async function executeworkflowrun(step, session, plan, tabid2, origin, dry) {
|
|
18033
|
-
const options =
|
|
19263
|
+
const options = stepoptions3(step);
|
|
18034
19264
|
const workflowid = typeof options.workflowid === "string" ? options.workflowid : "";
|
|
18035
19265
|
const storedrecord = await memory.getworkflowrecord(workflowid);
|
|
18036
19266
|
if (!storedrecord) throw new Error(`No composed workflow matches ${workflowid || "the reviewed id"}.`);
|
|
@@ -18193,7 +19423,7 @@ async function storetimeoutabort(run, step, message, budget) {
|
|
|
18193
19423
|
return { run: aborted, log: [entry] };
|
|
18194
19424
|
}
|
|
18195
19425
|
async function executetriggerstep(step, session, plan, tabid2, origin) {
|
|
18196
|
-
const options =
|
|
19426
|
+
const options = stepoptions3(step);
|
|
18197
19427
|
const family = triggerfamilyof(step.kind);
|
|
18198
19428
|
if (!family) throw new Error(`The ${step.kind} step is not a reviewed trigger kind.`);
|
|
18199
19429
|
const workflowid = typeof options.workflowid === "string" ? options.workflowid : "";
|
|
@@ -18394,6 +19624,34 @@ async function executestep(stepid) {
|
|
|
18394
19624
|
async function executeaction(step, session, plan, tabid2, origin, settings, verdicts, mode) {
|
|
18395
19625
|
const gate = canexecute({ session, plan, step, tabid: tabid2, origin, ...verdicts !== void 0 && verdicts.length > 0 ? { verdicts } : {}, ...settings !== void 0 ? { settings } : {} });
|
|
18396
19626
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
19627
|
+
const environmentverdict = stepenvironmentvalid(step);
|
|
19628
|
+
if (!environmentverdict.allowed) throw new Error(environmentverdict.reason);
|
|
19629
|
+
const environmentgrantverdict = environmentgrantgate(step, session?.environmentgrants);
|
|
19630
|
+
if (!environmentgrantverdict.allowed) throw new Error(environmentgrantverdict.reason);
|
|
19631
|
+
const offgranted = await offscreengranted();
|
|
19632
|
+
const routing = routeenvironment(step, { offload: settings?.parseoffload === true, granted: offgranted });
|
|
19633
|
+
const capabilityverdict = offscreencapabilitygate({ environment: routing.environment, granted: offgranted });
|
|
19634
|
+
if (!capabilityverdict.allowed) throw new Error(capabilityverdict.reason);
|
|
19635
|
+
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 });
|
|
19636
|
+
const securityverdict = await securitystepgate(step, session, origin, settings);
|
|
19637
|
+
if (!securityverdict.allowed) {
|
|
19638
|
+
if (session && plan) {
|
|
19639
|
+
await memory.setprogress(recorddenied(await memory.getprogress(), plan.id, step.id, deniedevidenceof({ origin, kind: step.kind, reason: securityverdict.reason, now: Date.now() }), Date.now())).catch(() => {
|
|
19640
|
+
});
|
|
19641
|
+
await appendrunevent("deny", `The ${step.kind} step ${step.id} on ${origin} was denied without navigation: ${securityverdict.reason}`, session, origin, step.id).catch(() => {
|
|
19642
|
+
});
|
|
19643
|
+
}
|
|
19644
|
+
await audit("deny", `The ${step.kind} step ${step.id} on ${origin} was denied without navigation: ${securityverdict.reason}`, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
19645
|
+
if (securityverdict.suspended && session) {
|
|
19646
|
+
await appendrunevent("expiry", `The consent window of ${origin} expired mid step and the run suspended at the step ${step.id}; a new explicit prompt renews it.`, session, origin, step.id).catch(() => {
|
|
19647
|
+
});
|
|
19648
|
+
await audit("expiry", `The consent window of ${origin} expired mid step and the run suspended at the step ${step.id}; the executor refuses to resume without a new explicit prompt.`, { sessionid: session.id, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
19649
|
+
}
|
|
19650
|
+
throw new Error(securityverdict.reason);
|
|
19651
|
+
}
|
|
19652
|
+
if (session && plan && plan.state === "approved" && mode === "plan") await openplanrun(session, plan);
|
|
19653
|
+
if (plan && plan.state === "approved") await markpendingstep(plan, step.id);
|
|
19654
|
+
if (routing.environment === "sandboxframe") return executesandboxrender(step, session, plan, origin);
|
|
18397
19655
|
const capability = requiredcapability(step.kind);
|
|
18398
19656
|
if (capability) {
|
|
18399
19657
|
const granted = await chrome.permissions.contains({ permissions: [capability] });
|
|
@@ -18481,8 +19739,12 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18481
19739
|
return output;
|
|
18482
19740
|
};
|
|
18483
19741
|
const runplan = plan;
|
|
19742
|
+
const isolatedresult = routing.environment === "isolatedworld" ? await executeisolatedevaluate(step, tabid2, origin) : void 0;
|
|
19743
|
+
let workerturnaround;
|
|
18484
19744
|
const capturepolicystate = await runcapturepolicy();
|
|
18485
|
-
if (
|
|
19745
|
+
if (isolatedresult !== void 0) {
|
|
19746
|
+
output = isolatedresult;
|
|
19747
|
+
} else if (capturepolicystate === "beforeafter" && session && step.risk !== "read" && beforeafterwrapallowed(step.kind)) {
|
|
18486
19748
|
const before = await grabstateshot(step, session, runplan, tabid2, "before");
|
|
18487
19749
|
output = await dispatchreviewedstep();
|
|
18488
19750
|
if (output?.ok) {
|
|
@@ -18498,8 +19760,14 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18498
19760
|
} else {
|
|
18499
19761
|
output = await dispatchreviewedstep();
|
|
18500
19762
|
}
|
|
19763
|
+
if (routing.environment === "offscreenworker" && workerturnaround === void 0) {
|
|
19764
|
+
const offloaded = await offloadparsetoworker(step, output, session, plan, origin);
|
|
19765
|
+
output = offloaded.output ?? output;
|
|
19766
|
+
workerturnaround = offloaded.turnaround;
|
|
19767
|
+
}
|
|
18501
19768
|
if (["navigate", "back", "forward"].includes(step.kind)) await recordnavigation(step, session, tabid2);
|
|
18502
19769
|
await recordevidence(step, output, session, plan, origin);
|
|
19770
|
+
await recordstepenvironment(step, routing.environment, session, plan, origin, workerturnaround);
|
|
18503
19771
|
if (output?.ok && plan && typeof output.details?.tabid === "number") {
|
|
18504
19772
|
await memory.setprogress(assigntasktab(await memory.getprogress(), plan.id, output.details.tabid, Date.now()));
|
|
18505
19773
|
}
|
|
@@ -18508,10 +19776,13 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18508
19776
|
if (resolved) {
|
|
18509
19777
|
await memory.addresolution({ stepid: step.id, mode: resolved.mode, selector: resolved.selector, label: resolved.label, at: Date.now() });
|
|
18510
19778
|
}
|
|
18511
|
-
const
|
|
19779
|
+
const maskshapes = shapesof({ ...settings !== void 0 ? { settings } : {}, rules: await memory.getmaskrules(), origin });
|
|
19780
|
+
const outcome = { stepid: step.id, ok: Boolean(output?.ok), summary, environment: routing.environment, ...output?.details ? { details: maskexport(output.details, maskshapes) } : {}, at: Date.now() };
|
|
18512
19781
|
const auditkind = stepauditkind(step, Boolean(output?.ok));
|
|
18513
19782
|
await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id });
|
|
18514
19783
|
await memory.addoutcome(outcome);
|
|
19784
|
+
if (session) await appendrunevent("step", `The ${step.kind} step ${step.id} ${outcome.ok ? "completed" : "failed"} on ${origin}: ${summary}${securityverdict.classification.sensitive ? ` The step grades ${securityverdict.classification.reason}` : ""}`, session, origin, step.id).catch(() => {
|
|
19785
|
+
});
|
|
18515
19786
|
if (output?.ok && plan && mode === "plan") {
|
|
18516
19787
|
const base = await memory.getprogress();
|
|
18517
19788
|
const completed = watchwindow ? recordwatchcompletion(base, plan.id, step.id, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, step.id, Date.now());
|
|
@@ -18540,6 +19811,7 @@ async function executeaction(step, session, plan, tabid2, origin, settings, verd
|
|
|
18540
19811
|
});
|
|
18541
19812
|
const done = { ...plan, state: "completed", completedat: Date.now() };
|
|
18542
19813
|
await memory.setplan(done);
|
|
19814
|
+
await closeplanrun(done.id, session?.id ?? "");
|
|
18543
19815
|
await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
|
|
18544
19816
|
}
|
|
18545
19817
|
}
|
|
@@ -18553,7 +19825,7 @@ async function previewstep(stepid) {
|
|
|
18553
19825
|
if (!step) throw new Error("Reviewed step was not found.");
|
|
18554
19826
|
const gate = canpreview({ session, plan, step, tabid: tab.id, origin });
|
|
18555
19827
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
18556
|
-
if (!step.target && !
|
|
19828
|
+
if (!step.target && !stepoptions3(step).targetref) throw new Error("Only a target-based step can be previewed.");
|
|
18557
19829
|
const result = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: (action, expectedorigin) => {
|
|
18558
19830
|
const bridge = globalThis.devthinkbridge;
|
|
18559
19831
|
if (!bridge) throw new Error("Devthink page bridge is unavailable.");
|
|
@@ -18725,7 +19997,7 @@ async function handlerequest(message, sender) {
|
|
|
18725
19997
|
const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
|
|
18726
19998
|
const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
|
|
18727
19999
|
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() };
|
|
20000
|
+
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(), security: await securityviewof() };
|
|
18729
20001
|
}
|
|
18730
20002
|
case "capabilities":
|
|
18731
20003
|
return refreshcapabilities();
|
|
@@ -19780,7 +21052,11 @@ async function handlerequest(message, sender) {
|
|
|
19780
21052
|
});
|
|
19781
21053
|
activerecordings.delete(id);
|
|
19782
21054
|
}
|
|
19783
|
-
if (session)
|
|
21055
|
+
if (session) {
|
|
21056
|
+
await memory.setsession({ ...session, stoppedat: Date.now() });
|
|
21057
|
+
await sealsessionrunlog(session.id).catch(() => {
|
|
21058
|
+
});
|
|
21059
|
+
}
|
|
19784
21060
|
const plan = await memory.getplan();
|
|
19785
21061
|
if (plan && ["pending", "approved"].includes(plan.state)) await memory.setplan({ ...plan, state: "cancelled" });
|
|
19786
21062
|
await audit("stop", "The user stopped the browser session.", { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {} });
|
|
@@ -21772,6 +23048,303 @@ async function handlerequest(message, sender) {
|
|
|
21772
23048
|
}
|
|
21773
23049
|
throw new Error("The swarm merge request carries no merge, report, export, compare, lesson, costs, timeline, replay or snapshot action.");
|
|
21774
23050
|
}
|
|
23051
|
+
case "security": {
|
|
23052
|
+
const input2 = message;
|
|
23053
|
+
const now = Date.now();
|
|
23054
|
+
const session = await memory.getsession();
|
|
23055
|
+
const settings = await memory.getsettings();
|
|
23056
|
+
if (input2.allowlist !== void 0) {
|
|
23057
|
+
if (input2.allowlist.add !== void 0) {
|
|
23058
|
+
const origin = input2.allowlist.add.origin?.trim() ?? "";
|
|
23059
|
+
if (origin === "") throw new Error("The allowlist grant needs its exact origin.");
|
|
23060
|
+
if (wildcardentry(origin)) throw new Error("The allowlist binds every grant to one exact origin; a wildcard entry never passes.");
|
|
23061
|
+
await memory.addallowlistorigin({ origin, profileid: runstateprofile, grantedat: now });
|
|
23062
|
+
await audit("grant", `The user added the exact origin ${origin} to the automation allowlist of the profile workspace; no wildcard expansion exists.`, { ...session ? { sessionid: session.id } : {} });
|
|
23063
|
+
return { ...await securityviewof(), granted: origin };
|
|
23064
|
+
}
|
|
23065
|
+
if (input2.allowlist.remove !== void 0) {
|
|
23066
|
+
const origin = input2.allowlist.remove.origin?.trim() ?? "";
|
|
23067
|
+
await memory.removeallowlistorigin(origin, runstateprofile);
|
|
23068
|
+
await audit("revoke", `The user removed the origin ${origin} from the automation allowlist; the denydefault posture refuses the origin again.`, { ...session ? { sessionid: session.id } : {} });
|
|
23069
|
+
return { ...await securityviewof(), removed: origin };
|
|
23070
|
+
}
|
|
23071
|
+
}
|
|
23072
|
+
if (input2.profile !== void 0) {
|
|
23073
|
+
const origin = input2.profile.origin?.trim() ?? session?.origin ?? "";
|
|
23074
|
+
const kind = input2.profile.kind?.trim() ?? "";
|
|
23075
|
+
const decision = input2.profile.decision === "deny" ? "deny" : "grant";
|
|
23076
|
+
if (origin === "" || kind === "") throw new Error("The origin profile decision needs its exact origin and its action kind.");
|
|
23077
|
+
const profiles = await memory.getoriginprofiles();
|
|
23078
|
+
const existing = profiles.find((candidate) => candidate.origin === origin);
|
|
23079
|
+
const updated = profilekind({ profile: existing ?? originprofileof({ origin, now }), kind, decision, now });
|
|
23080
|
+
await memory.saveoriginprofile(updated);
|
|
23081
|
+
await audit("grant", `The origin profile of ${origin} now ${decision === "grant" ? "grants" : "denies"} the ${kind} kind the user reviewed; ${updated.grants.length} grant${updated.grants.length === 1 ? "" : "s"} and ${updated.denials.length} denial${updated.denials.length === 1 ? "" : "s"} on the origin.`, { ...session ? { sessionid: session.id } : {} });
|
|
23082
|
+
return { ...await securityviewof(), profile: updated };
|
|
23083
|
+
}
|
|
23084
|
+
if (input2.consent !== void 0) {
|
|
23085
|
+
if (input2.consent.open !== void 0) {
|
|
23086
|
+
if (!session) throw new Error("The consent window opens inside an active session.");
|
|
23087
|
+
const duration = input2.consent.open.duration ?? settings?.consentduration;
|
|
23088
|
+
if (duration === void 0) throw new Error("The consent prompt needs its duration in milliseconds; no grant ever defaults to unlimited.");
|
|
23089
|
+
const durationgate = consentdurationvalid(duration);
|
|
23090
|
+
if (!durationgate.allowed) throw new Error(durationgate.reason);
|
|
23091
|
+
const origin = input2.consent.open.origin?.trim() !== "" && input2.consent.open.origin !== void 0 ? input2.consent.open.origin.trim() : session.origin;
|
|
23092
|
+
const kinds = (input2.consent.open.kinds ?? []).map((kind) => kind.trim()).filter((kind) => kind !== "");
|
|
23093
|
+
const window2 = openconsentwindow({ sessionid: session.id, origin, duration, kinds: kinds.length > 0 ? kinds : ["observe"], now });
|
|
23094
|
+
await memory.setconsentwindows([window2, ...(await memory.getconsentwindows()).map((candidate) => candidate.sessionid === session.id && candidate.origin === origin && candidate.state === "active" ? { ...candidate, state: "closed", closedat: now } : candidate)]);
|
|
23095
|
+
await appendrunevent("grant", `The consent prompt opened the window ${window2.id} for ${origin} with the boundary ${window2.boundary}.`, session, origin).catch(() => {
|
|
23096
|
+
});
|
|
23097
|
+
await audit("grant", `The user answered the consent prompt for ${origin} with the window ${window2.id} and the boundary ${window2.boundary}; no grant ever outlives its named boundary.`, { sessionid: session.id });
|
|
23098
|
+
return { ...await securityviewof(), window: window2 };
|
|
23099
|
+
}
|
|
23100
|
+
if (input2.consent.renew !== void 0) {
|
|
23101
|
+
if (!session) throw new Error("The consent window renewal opens inside an active session.");
|
|
23102
|
+
const windowid = input2.consent.renew.windowid?.trim() ?? "";
|
|
23103
|
+
const current = (await memory.getconsentwindows()).find((candidate) => candidate.id === windowid && candidate.sessionid === session.id);
|
|
23104
|
+
if (!current) throw new Error(`No consent window ${windowid} exists for the session.`);
|
|
23105
|
+
const duration = input2.consent.renew.duration ?? settings?.consentduration;
|
|
23106
|
+
if (duration === void 0) throw new Error("The renewal prompt needs its duration in milliseconds; a renewal only runs through a new explicit prompt.");
|
|
23107
|
+
const durationgate = consentdurationvalid(duration);
|
|
23108
|
+
if (!durationgate.allowed) throw new Error(durationgate.reason);
|
|
23109
|
+
const { renewed, closed } = renewconsentwindow({ window: current, duration, kinds: current.kinds, now });
|
|
23110
|
+
await memory.setconsentwindows([renewed, closed, ...(await memory.getconsentwindows()).filter((candidate) => candidate.id !== windowid)]);
|
|
23111
|
+
await appendrunevent("grant", `The consent window of ${current.origin} renewed through a new explicit prompt with the boundary ${renewed.boundary}; the old window stays closed in the history.`, session, current.origin).catch(() => {
|
|
23112
|
+
});
|
|
23113
|
+
await audit("grant", `The user renewed the consent window of ${current.origin} through a new explicit prompt with the boundary ${renewed.boundary}.`, { sessionid: session.id });
|
|
23114
|
+
return { ...await securityviewof(), window: renewed };
|
|
23115
|
+
}
|
|
23116
|
+
if (input2.consent.classes !== void 0) {
|
|
23117
|
+
const origin = input2.consent.classes.origin?.trim() || session?.origin || "";
|
|
23118
|
+
const classes = (input2.consent.classes.classes ?? []).map((kind) => kind.trim()).filter((kind) => kind !== "");
|
|
23119
|
+
if (origin === "" || classes.length === 0) throw new Error("The fresh class consent needs its origin and its sensitive classes.");
|
|
23120
|
+
for (const classname of classes) {
|
|
23121
|
+
await memory.addclassconsent({ id: randomid(), origin, sensitiveclass: classname, grantedat: now });
|
|
23122
|
+
await audit("grant", `The user gave one fresh consent prompt for the ${classname} class on ${origin}; the prompt of one class never widens another.`, { ...session ? { sessionid: session.id } : {} });
|
|
23123
|
+
}
|
|
23124
|
+
return { ...await securityviewof(), classes };
|
|
23125
|
+
}
|
|
23126
|
+
}
|
|
23127
|
+
if (input2.revoke !== void 0) {
|
|
23128
|
+
if (!session) throw new Error("The revocation needs its active session.");
|
|
23129
|
+
const plan2 = await memory.getplan();
|
|
23130
|
+
const runid = input2.revoke.runid?.trim() || plan2?.id || "";
|
|
23131
|
+
if (runid === "") throw new Error("The revocation needs its run.");
|
|
23132
|
+
const state = await memory.getrunstate(runstateprofile);
|
|
23133
|
+
const pendingstepid = state?.pendingstepid;
|
|
23134
|
+
const progress = await memory.getprogress();
|
|
23135
|
+
const completed = new Set(progress?.planid === runid ? progress.completedsteps : []);
|
|
23136
|
+
const queued = (plan2?.steps ?? []).map((step) => step.id).filter((id) => !completed.has(id));
|
|
23137
|
+
const revocation = revokerun({ sessionid: session.id, runid, ...pendingstepid !== void 0 ? { pendingstepid } : {}, ...queued.length > 0 ? { queuedstepids: queued } : {}, actor: "user", ...input2.revoke.reason !== void 0 ? { reason: input2.revoke.reason } : {}, now });
|
|
23138
|
+
await memory.addrevocation(revocation);
|
|
23139
|
+
if (plan2 && plan2.id === runid && ["pending", "approved"].includes(plan2.state)) await memory.setplan({ ...plan2, state: "cancelled" });
|
|
23140
|
+
if (plan2 && plan2.id === runid && pendingstepid !== void 0) await memory.setprogress(recordrevocation(progress, runid, pendingstepid, { haltedstepids: revocation.haltedstepids, revokedstepid: pendingstepid, reason: revocation.reason }, now));
|
|
23141
|
+
await appendrunevent("revoke", `The user revoked the run ${runid}: the pending step ${pendingstepid ?? "none"} and every queued step halted without executing (${revocation.haltedstepids.join(", ")}).`, session, session.origin, pendingstepid).catch(() => {
|
|
23142
|
+
});
|
|
23143
|
+
await audit("revoke", `The user revoked the run ${runid} mid step: ${revocation.haltedstepids.length} step${revocation.haltedstepids.length === 1 ? "" : "s"} halted without executing, and the run log records the terminal event.`, { sessionid: session.id, planid: runid, ...pendingstepid !== void 0 ? { stepid: pendingstepid } : {} });
|
|
23144
|
+
return { ...await securityviewof(), revocation };
|
|
23145
|
+
}
|
|
23146
|
+
if (input2.mask !== void 0) {
|
|
23147
|
+
if (input2.mask.add !== void 0) {
|
|
23148
|
+
const shapes = (input2.mask.add.shapes ?? []).map((shape) => shape.trim().toLowerCase()).filter((shape) => shape !== "");
|
|
23149
|
+
if (shapes.length === 0) throw new Error("The mask rule needs its field shapes.");
|
|
23150
|
+
const rule = { id: randomid(), ...input2.mask.add.origin !== void 0 && input2.mask.add.origin.trim() !== "" ? { origin: input2.mask.add.origin.trim() } : {}, shapes, createdat: now };
|
|
23151
|
+
await memory.addmaskrule(rule);
|
|
23152
|
+
await audit("mask", `The user added the mask rule ${rule.id} for the ${shapes.join(", ")} field shape${shapes.length === 1 ? "" : "s"}${rule.origin !== void 0 ? ` scoped to ${rule.origin}` : ""}; typed values behind the shapes never reach a record.`, { ...session ? { sessionid: session.id } : {} });
|
|
23153
|
+
return { ...await securityviewof(), rule };
|
|
23154
|
+
}
|
|
23155
|
+
if (input2.mask.remove !== void 0) {
|
|
23156
|
+
const id = input2.mask.remove.id?.trim() ?? "";
|
|
23157
|
+
await memory.removemaskrule(id);
|
|
23158
|
+
await audit("mask", `The user removed the mask rule ${id}.`, { ...session ? { sessionid: session.id } : {} });
|
|
23159
|
+
return { ...await securityviewof(), removed: id };
|
|
23160
|
+
}
|
|
23161
|
+
}
|
|
23162
|
+
if (input2.read !== void 0) {
|
|
23163
|
+
const runid = input2.read.runid?.trim() || session?.id || "";
|
|
23164
|
+
const log = await memory.getimmutablelog(runid);
|
|
23165
|
+
if (!log) throw new Error(`No run log exists for the run ${runid}.`);
|
|
23166
|
+
const read = await readverifiedlog(log);
|
|
23167
|
+
const readgate = logreadgate({ valid: read.ok });
|
|
23168
|
+
if (!readgate.allowed) throw new Error(readgate.reason);
|
|
23169
|
+
await audit("consent", `The run log of ${runid} was read through the audit accessor with ${read.entries.length} verified entries: ${read.reason}`, { ...session ? { sessionid: session.id } : {}, planid: runid });
|
|
23170
|
+
return { ...await securityviewof(), log: read.entries, verification: read.reason };
|
|
23171
|
+
}
|
|
23172
|
+
if (input2.export !== void 0) {
|
|
23173
|
+
const runid = input2.export.runid?.trim() || session?.id || "";
|
|
23174
|
+
const exported = await memory.exportverifiedrunlog(runid);
|
|
23175
|
+
if (!exported.chainvalid) throw new Error(exported.reason);
|
|
23176
|
+
await audit("export", `The verified run log of ${runid} was exported as an audit file with ${exported.entries} entries${exported.sealhash !== void 0 ? ` and the seal hash ${exported.sealhash}` : ""}: ${exported.reason}`, { ...session ? { sessionid: session.id } : {}, planid: runid });
|
|
23177
|
+
return { ...await securityviewof(), export: exported };
|
|
23178
|
+
}
|
|
23179
|
+
if (input2.settings !== void 0) {
|
|
23180
|
+
const patch = { ...settings };
|
|
23181
|
+
if (input2.settings.consentduration !== void 0) {
|
|
23182
|
+
const durationgate = consentdurationvalid(input2.settings.consentduration);
|
|
23183
|
+
if (!durationgate.allowed) throw new Error(durationgate.reason);
|
|
23184
|
+
patch.consentduration = input2.settings.consentduration;
|
|
23185
|
+
}
|
|
23186
|
+
if (input2.settings.logretention !== void 0) patch.logretention = input2.settings.logretention;
|
|
23187
|
+
if (input2.settings.maskshapes !== void 0) patch.maskshapes = input2.settings.maskshapes.map((shape) => shape.trim().toLowerCase()).filter((shape) => shape !== "");
|
|
23188
|
+
await memory.setsettings(patch);
|
|
23189
|
+
await audit("configure", `The user updated the security settings: consent duration ${patch.consentduration !== void 0 ? `${patch.consentduration} milliseconds` : "the prompt asks every time"}, log retention ${patch.logretention !== void 0 ? `${patch.logretention} milliseconds` : "every sealed log stays"}, mask shapes ${patch.maskshapes?.length ?? 0} configured.`, {});
|
|
23190
|
+
return { ...await securityviewof(), configured: true };
|
|
23191
|
+
}
|
|
23192
|
+
const plan = await memory.getplan();
|
|
23193
|
+
const pending = [];
|
|
23194
|
+
if (session && plan && ["pending", "approved"].includes(plan.state)) {
|
|
23195
|
+
for (const step of plan.steps) {
|
|
23196
|
+
const classification = sensitiveclassesof(step);
|
|
23197
|
+
if (!classification.sensitive) continue;
|
|
23198
|
+
const missing = classification.classes.length > 0 ? classification.classes : [];
|
|
23199
|
+
if (missing.length === 0 && !classification.bydefault) continue;
|
|
23200
|
+
pending.push({ stepid: step.id, kind: step.kind, origin: plan.origin, prompt: consentprompttext({ origin: plan.origin, kind: step.kind, classes: classification.classes, bydefault: classification.bydefault, duration: settings?.consentduration ?? session.expiresat - now }) });
|
|
23201
|
+
}
|
|
23202
|
+
}
|
|
23203
|
+
return { ...await securityviewof(), prompts: pending };
|
|
23204
|
+
}
|
|
23205
|
+
case "environments": {
|
|
23206
|
+
const input2 = message;
|
|
23207
|
+
const now = Date.now();
|
|
23208
|
+
const session = await memory.getsession();
|
|
23209
|
+
const settings = await memory.getsettings();
|
|
23210
|
+
if (input2.requestcapability === true) {
|
|
23211
|
+
const granted = await requestoffscreengrant();
|
|
23212
|
+
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 } : {} });
|
|
23213
|
+
return { ...await environmentviewof(), requested: granted };
|
|
23214
|
+
}
|
|
23215
|
+
if (input2.grants !== void 0) {
|
|
23216
|
+
if (!session) throw new Error("The environment grants need an active session to join.");
|
|
23217
|
+
const valid = /* @__PURE__ */ new Set(["pagecontext", "isolatedworld", "offscreenworker", "sandboxframe"]);
|
|
23218
|
+
const requested = input2.grants.filter((environment) => valid.has(environment));
|
|
23219
|
+
const unknown = input2.grants.filter((environment) => !valid.has(environment));
|
|
23220
|
+
if (unknown.length > 0) throw new Error(`The environments ${unknown.join(", ")} sit outside the four execution environments.`);
|
|
23221
|
+
await memory.setenvironmentgrants(requested);
|
|
23222
|
+
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 });
|
|
23223
|
+
return environmentviewof();
|
|
23224
|
+
}
|
|
23225
|
+
if (input2.pool !== void 0) {
|
|
23226
|
+
const sizegate = workerpoolsizevalid(input2.pool.size ?? settings?.workerpoolsize);
|
|
23227
|
+
if (!sizegate.allowed) throw new Error(sizegate.reason);
|
|
23228
|
+
const runid = (await memory.getplan())?.id ?? "pool";
|
|
23229
|
+
const current = workerpoolcounts.get(runid) ?? 0;
|
|
23230
|
+
const plan = poolplan({ pending: input2.pool.pending ?? 0, current, ...input2.pool.size !== void 0 ? { size: input2.pool.size } : {} });
|
|
23231
|
+
workerpoolcounts.set(runid, plan.workers);
|
|
23232
|
+
const provenance = { origin: session?.origin ?? "", stepid: "", environment: "offscreenworker" };
|
|
23233
|
+
await memory.addworkerevent({ id: randomid(), runid, kind: plan.added > 0 ? "spawn" : "teardown", workers: plan.workers, reason: plan.reason, provenance, at: now });
|
|
23234
|
+
await audit("worker", `The worker pool of the run ${runid} holds ${plan.workers} worker${plan.workers === 1 ? "" : "s"}: ${plan.reason}`, { ...session ? { sessionid: session.id } : {} });
|
|
23235
|
+
return { ...await environmentviewof(), pool: plan };
|
|
23236
|
+
}
|
|
23237
|
+
if (input2.offscreenclose === true) {
|
|
23238
|
+
const runid = (await memory.getplan())?.id ?? "pool";
|
|
23239
|
+
await closeoffscreendocument(runid);
|
|
23240
|
+
return environmentviewof();
|
|
23241
|
+
}
|
|
23242
|
+
if (input2.settings !== void 0) {
|
|
23243
|
+
const patch = { ...settings };
|
|
23244
|
+
if (input2.settings.parseoffload !== void 0) patch.parseoffload = input2.settings.parseoffload === true;
|
|
23245
|
+
if (input2.settings.workerpoolsize !== void 0) {
|
|
23246
|
+
const sizegate = workerpoolsizevalid(input2.settings.workerpoolsize);
|
|
23247
|
+
if (!sizegate.allowed) throw new Error(sizegate.reason);
|
|
23248
|
+
patch.workerpoolsize = input2.settings.workerpoolsize;
|
|
23249
|
+
}
|
|
23250
|
+
if (input2.settings.sandboxorigins !== void 0) patch.sandboxorigins = input2.settings.sandboxorigins.map((origin) => origin.trim()).filter((origin) => origin !== "");
|
|
23251
|
+
if (input2.settings.keepaliveinterval !== void 0) {
|
|
23252
|
+
const intervalgate = keepaliveintervalvalid(input2.settings.keepaliveinterval);
|
|
23253
|
+
if (!intervalgate.allowed) throw new Error(intervalgate.reason);
|
|
23254
|
+
patch.keepaliveinterval = input2.settings.keepaliveinterval;
|
|
23255
|
+
}
|
|
23256
|
+
if (input2.settings.zombieintervals !== void 0) patch.zombieintervals = input2.settings.zombieintervals;
|
|
23257
|
+
if (input2.settings.runstateretention !== void 0) patch.runstateretention = input2.settings.runstateretention;
|
|
23258
|
+
if (input2.settings.runstatebytes !== void 0) patch.runstatebytes = input2.settings.runstatebytes;
|
|
23259
|
+
await memory.setsettings(patch);
|
|
23260
|
+
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"}.`, {});
|
|
23261
|
+
return environmentviewof();
|
|
23262
|
+
}
|
|
23263
|
+
if (input2.render !== void 0) {
|
|
23264
|
+
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 ?? "");
|
|
23265
|
+
return { ...await environmentviewof(), render: result };
|
|
23266
|
+
}
|
|
23267
|
+
return environmentviewof();
|
|
23268
|
+
}
|
|
23269
|
+
case "runstate": {
|
|
23270
|
+
const input2 = message;
|
|
23271
|
+
const now = Date.now();
|
|
23272
|
+
const session = await memory.getsession();
|
|
23273
|
+
const plan = await memory.getplan();
|
|
23274
|
+
const settings = await memory.getsettings();
|
|
23275
|
+
const states = await memory.listrunstates();
|
|
23276
|
+
if (input2.start === true) {
|
|
23277
|
+
if (!session || !plan) throw new Error("The run state opens behind an active session with an approved plan.");
|
|
23278
|
+
const gate = keepalivegate({ session, plan, now });
|
|
23279
|
+
if (!gate.allowed) throw new Error(gate.reason);
|
|
23280
|
+
await openplanrun(session, plan);
|
|
23281
|
+
return { ...await environmentviewof(), opened: plan.id };
|
|
23282
|
+
}
|
|
23283
|
+
if (input2.stop === true) {
|
|
23284
|
+
const open = states.find((state) => state.keepalive.state === "active");
|
|
23285
|
+
if (open) await closeplanrun(open.runid, open.sessionid);
|
|
23286
|
+
return environmentviewof();
|
|
23287
|
+
}
|
|
23288
|
+
if (input2.beat === true) {
|
|
23289
|
+
await keepalivetick();
|
|
23290
|
+
return environmentviewof();
|
|
23291
|
+
}
|
|
23292
|
+
if (input2.recover === true) {
|
|
23293
|
+
const open = states.find((state) => state.keepalive.state === "active" || state.state === "recovered");
|
|
23294
|
+
if (!open) throw new Error("No interrupted run waits for its recovery.");
|
|
23295
|
+
const recovery = recoveryplan(open);
|
|
23296
|
+
await memory.setrunstate(open.profileid, reattachrun(open, now));
|
|
23297
|
+
await audit("environment", recovery.reason, { sessionid: open.sessionid });
|
|
23298
|
+
if (recovery.recoverable && recovery.pendingstepid !== void 0 && plan && plan.id === open.planid && plan.state === "approved") {
|
|
23299
|
+
const outcome = await executestep(recovery.pendingstepid);
|
|
23300
|
+
return { ...await environmentviewof(), recovery, resumed: outcome.summary };
|
|
23301
|
+
}
|
|
23302
|
+
return { ...await environmentviewof(), recovery };
|
|
23303
|
+
}
|
|
23304
|
+
if (input2.reap === true) {
|
|
23305
|
+
const swept = zombiesweep({ states, now, interval: settings?.keepaliveinterval ?? roadmapheartbeat, missedlimit: settings?.zombieintervals ?? roadmapzombieintervals });
|
|
23306
|
+
for (const state of swept.states) await memory.setrunstate(state.profileid, state);
|
|
23307
|
+
for (const runid of swept.reaped) await closeoffscreendocument(runid);
|
|
23308
|
+
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.`, {});
|
|
23309
|
+
return { ...await environmentviewof(), reaped: swept.reaped };
|
|
23310
|
+
}
|
|
23311
|
+
if (input2.lock !== void 0) {
|
|
23312
|
+
if (input2.lock.acquire !== void 0) {
|
|
23313
|
+
if (!session) throw new Error("The run lock needs its active session.");
|
|
23314
|
+
const runid = input2.lock.acquire.runid?.trim() ?? plan?.id ?? "";
|
|
23315
|
+
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 });
|
|
23316
|
+
if (!outcome.acquired) throw new Error(outcome.reason);
|
|
23317
|
+
await memory.setrunlocks(outcome.locks);
|
|
23318
|
+
await audit("environment", outcome.reason, { sessionid: session.id });
|
|
23319
|
+
return { ...await environmentviewof(), lock: outcome.reason };
|
|
23320
|
+
}
|
|
23321
|
+
if (input2.lock.release !== void 0) {
|
|
23322
|
+
if (!session) throw new Error("The run lock release needs its active session.");
|
|
23323
|
+
const outcome = releaserunlock({ locks: await memory.getrunlocks(), sessionid: session.id, runid: input2.lock.release.runid?.trim() ?? plan?.id ?? "", now });
|
|
23324
|
+
if (!outcome.released) throw new Error(outcome.reason);
|
|
23325
|
+
await memory.setrunlocks(outcome.locks);
|
|
23326
|
+
await audit("environment", outcome.reason, { sessionid: session.id });
|
|
23327
|
+
return { ...await environmentviewof(), lock: outcome.reason };
|
|
23328
|
+
}
|
|
23329
|
+
}
|
|
23330
|
+
if (input2.serialize !== void 0) {
|
|
23331
|
+
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 !== "");
|
|
23332
|
+
const order = serializesteps({ branches });
|
|
23333
|
+
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.`, {});
|
|
23334
|
+
return { order };
|
|
23335
|
+
}
|
|
23336
|
+
if (input2.export === true) {
|
|
23337
|
+
const exported = await memory.exportrunstates();
|
|
23338
|
+
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"}.`, {});
|
|
23339
|
+
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 })) };
|
|
23340
|
+
}
|
|
23341
|
+
if (input2.profileid !== void 0) {
|
|
23342
|
+
const state = await memory.getrunstate(input2.profileid);
|
|
23343
|
+
if (!state) throw new Error(`No run state exists for the profile ${input2.profileid}.`);
|
|
23344
|
+
return { state };
|
|
23345
|
+
}
|
|
23346
|
+
return environmentviewof();
|
|
23347
|
+
}
|
|
21775
23348
|
default:
|
|
21776
23349
|
throw new Error("Unknown Devthink request.");
|
|
21777
23350
|
}
|
|
@@ -22073,7 +23646,7 @@ async function raiseremoteapproval(clientid, toolname, params, step) {
|
|
|
22073
23646
|
return { content: `The approval gate ${request.id} holds the ${toolname} call; it executes once the user approves it in the panel.`, payload: { approvalid: request.id, state: "pending", ...request.timeoutat !== void 0 ? { timeoutat: request.timeoutat } : {} }, iserror: false };
|
|
22074
23647
|
}
|
|
22075
23648
|
async function executelistruns(step, session) {
|
|
22076
|
-
const options =
|
|
23649
|
+
const options = stepoptions3(step);
|
|
22077
23650
|
const statefilter = typeof options.state === "string" && options.state.trim() !== "" ? options.state : void 0;
|
|
22078
23651
|
const runs = await memory.listworkflowruns();
|
|
22079
23652
|
const selected = statefilter !== void 0 ? runs.filter((run) => run.state === statefilter) : runs;
|
|
@@ -22448,7 +24021,7 @@ async function maybeautosnapshot() {
|
|
|
22448
24021
|
await audit("session", `The reviewed auto snapshot interval stopped after ${state.interval.maxsnapshots} snapshot${state.interval.maxsnapshots === 1 ? "" : "s"}; the retention window of ${state.interval.expiry} millisecond${state.interval.expiry === 1 ? "" : "s"} expires them by user choice.`, { sessionid: session.id, planid: plan.id });
|
|
22449
24022
|
return;
|
|
22450
24023
|
}
|
|
22451
|
-
const options =
|
|
24024
|
+
const options = stepoptions3(step);
|
|
22452
24025
|
const snapshot2 = snapshotplanof(options.snapshot);
|
|
22453
24026
|
if (!snapshot2) return;
|
|
22454
24027
|
const record2 = await capturesessionrecord({ ...snapshot2, ...snapshot2.auto !== void 0 ? { auto: snapshot2.auto } : {} }, session, plan.id).catch(() => void 0);
|
|
@@ -22565,6 +24138,10 @@ setInterval(() => {
|
|
|
22565
24138
|
void evaluatelistedtriggers().then(() => draintriggerqueue()).then(() => runwatchdog()).then(() => restorebackgroundruns()).catch(() => {
|
|
22566
24139
|
});
|
|
22567
24140
|
}, 3e4);
|
|
24141
|
+
setInterval(() => {
|
|
24142
|
+
void keepalivetick().catch(() => {
|
|
24143
|
+
});
|
|
24144
|
+
}, roadmapheartbeat);
|
|
22568
24145
|
async function restoretriggers() {
|
|
22569
24146
|
await registermenurules();
|
|
22570
24147
|
await evaluatelistedtriggers();
|
|
@@ -22576,4 +24153,6 @@ async function restoretriggers() {
|
|
|
22576
24153
|
}
|
|
22577
24154
|
restoretriggers().catch(() => {
|
|
22578
24155
|
});
|
|
24156
|
+
restorerunstates().catch(() => {
|
|
24157
|
+
});
|
|
22579
24158
|
//# sourceMappingURL=background.js.map
|