webhelm 1.0.14 → 1.0.16
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/package.json
CHANGED
package/runtime/cli.mjs
CHANGED
|
@@ -75,6 +75,7 @@ async function runPlatform(args) {
|
|
|
75
75
|
const config = loadConfig(args);
|
|
76
76
|
const mode = validateMode(platform, action, args, config);
|
|
77
77
|
const sid = arg(args, '--session-id');
|
|
78
|
+
if (sid) await requireCliSession(sid);
|
|
78
79
|
const auto = has(args, '--auto-session');
|
|
79
80
|
validateConfig(platform, action, config);
|
|
80
81
|
if (sid && auto) throw Error('--session-id and --auto-session are mutually exclusive');
|
|
@@ -94,7 +95,6 @@ async function runPlatform(args) {
|
|
|
94
95
|
};
|
|
95
96
|
}
|
|
96
97
|
if (mode === 'sync' && (!sid || auto)) throw Error('sync new-turn requires a caller-owned --session-id');
|
|
97
|
-
if (sid) await requireCliSession(sid);
|
|
98
98
|
if (!sid && !auto) throw Error('platform run requires --session-id; use --auto-session only for one-shot actions');
|
|
99
99
|
return request({op:'platform-run', platform, action, config: {...config, ...(mode ? {mode} : {})},
|
|
100
100
|
session_id:sid, auto_session:auto, url:arg(args, '--url'), workspace:CLI_WORKSPACE});
|
|
@@ -119,6 +119,7 @@ async function requireCliSession(sessionId) {
|
|
|
119
119
|
async function main(args = process.argv.slice(2)) {
|
|
120
120
|
const [group, command] = args;
|
|
121
121
|
if (!group || ['--help', '-h', 'help'].includes(group) || has(args, '--help') || has(args, '-h')) return help();
|
|
122
|
+
rejectWorkspaceOverride(args);
|
|
122
123
|
if (group === 'migrate') {
|
|
123
124
|
await ensureActions(); // ~/.local/webhelm is the Actions Git checkout, never create an empty replacement.
|
|
124
125
|
const legacy=await migrateConfig();
|
|
@@ -135,7 +136,20 @@ async function main(args = process.argv.slice(2)) {
|
|
|
135
136
|
const login_guard=installLoginGuard({cliPath,nodePath:process.execPath,sourceMode});
|
|
136
137
|
return emit({status:'ready',...repo,daemon_pid:chrome.pid,managed_chrome:chrome.managed_chrome,login_guard});
|
|
137
138
|
}
|
|
138
|
-
if (group === 'ensure')
|
|
139
|
+
if (group === 'ensure') {
|
|
140
|
+
try { return emit(await request({op:'guard-check'})); }
|
|
141
|
+
catch(error) {
|
|
142
|
+
// An older, still-busy daemon can hold the same socket during a
|
|
143
|
+
// rolling source upgrade. Preserve its Sessions; never hot-swap it.
|
|
144
|
+
if (!String(error.message).includes('unknown op: guard-check')) throw error;
|
|
145
|
+
const legacy=await request({op:'ping'});
|
|
146
|
+
if (legacy.session_count>0)
|
|
147
|
+
return emit({ok:true,deferred_upgrade:true,reason:'legacy_daemon_has_active_sessions',
|
|
148
|
+
session_count:legacy.session_count,daemon_version:legacy.version||'unknown',
|
|
149
|
+
chrome_health:'not_verified'});
|
|
150
|
+
throw Error('Legacy WebHelm daemon lacks health guard; drain and migrate it before activating the new source Runtime');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
139
153
|
if (group === 'status') {
|
|
140
154
|
const daemon = await ensureDaemon();
|
|
141
155
|
return emit({version:daemon.version, platform_dir:PLATFORMS, root:ROOT,
|
|
@@ -152,7 +166,6 @@ async function main(args = process.argv.slice(2)) {
|
|
|
152
166
|
return emit(await request(payload));
|
|
153
167
|
}
|
|
154
168
|
if (group === 'session') {
|
|
155
|
-
rejectWorkspaceOverride(args);
|
|
156
169
|
if (command === 'start') return emit(await request({op:'start', workspace:CLI_WORKSPACE, url:arg(args,'--url')}));
|
|
157
170
|
if (command === 'list') {
|
|
158
171
|
const result=await request({op:'list'});
|
|
@@ -102,6 +102,8 @@ globalThis.browserWorkspaceManagerRpc = async (request) => {
|
|
|
102
102
|
const APPLICATION_ONLY_METHODS = new Set([
|
|
103
103
|
"workspace.create", "workspace.resize", "workspace.delete",
|
|
104
104
|
"workspace.ensure", "workspace.migrateDefault",
|
|
105
|
+
"workspace.acquire", "workspace.acquireIdentity",
|
|
106
|
+
"workspace.release", "workspace.releaseAll",
|
|
105
107
|
]);
|
|
106
108
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
107
109
|
if (APPLICATION_ONLY_METHODS.has(request?.method)) {
|
|
@@ -552,9 +552,12 @@ export class WorkspaceManager {
|
|
|
552
552
|
const normalizedName = normalizeWorkspaceName(name);
|
|
553
553
|
// Leasing may allocate a Tab inside an existing Workspace, but must never
|
|
554
554
|
// provision a new Workspace as a side effect. Only app-owned creation can.
|
|
555
|
-
const
|
|
556
|
-
if (!
|
|
555
|
+
const { entry } = await this.configuredWorkspace(normalizedName);
|
|
556
|
+
if (!entry)
|
|
557
557
|
throw new Error(`Workspace ${normalizedName} does not exist; an application must provision it first`);
|
|
558
|
+
// Previously provisioned workspaces may restore a missing physical group
|
|
559
|
+
// from their durable configuration; this never creates a new name.
|
|
560
|
+
const ready = await this.ensureWorkspace(normalizedName);
|
|
558
561
|
const tabs = await this.groupTabs(ready.groupId);
|
|
559
562
|
const { idle, leased } = this.classify(normalizedName, tabs);
|
|
560
563
|
if (!idle.length && leased.length >= ready.poolSize) {
|
|
@@ -597,9 +600,12 @@ export class WorkspaceManager {
|
|
|
597
600
|
|
|
598
601
|
// Leasing may allocate a Tab inside an existing Workspace, but must never
|
|
599
602
|
// provision a new Workspace as a side effect. Only app-owned creation can.
|
|
600
|
-
const
|
|
601
|
-
if (!
|
|
603
|
+
const { entry } = await this.configuredWorkspace(normalizedName);
|
|
604
|
+
if (!entry)
|
|
602
605
|
throw new Error(`Workspace ${normalizedName} does not exist; an application must provision it first`);
|
|
606
|
+
// Previously provisioned workspaces may restore a missing physical group
|
|
607
|
+
// from their durable configuration; this never creates a new name.
|
|
608
|
+
const ready = await this.ensureWorkspace(normalizedName);
|
|
603
609
|
const tabs = await this.groupTabs(ready.groupId);
|
|
604
610
|
const { idle, leased } = this.classify(normalizedName, tabs);
|
|
605
611
|
if (!idle.length && leased.length >= ready.poolSize) {
|
package/runtime/node-daemon.mjs
CHANGED
|
@@ -312,36 +312,48 @@ async function extensionCallSerialized(_session,method,args={}) {
|
|
|
312
312
|
`globalThis.browserWorkspaceManagerRpc(${request})`);
|
|
313
313
|
}catch(error){debuggerSession=null;throw error;}
|
|
314
314
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
let ready=false;
|
|
315
|
+
// Pages (including an agent-opened extension page) cannot mutate groups.
|
|
316
|
+
// Wake the MV3 worker using a read-only message, then invoke the actual
|
|
317
|
+
// operation directly in the worker over daemon-owned CDP.
|
|
318
|
+
const page=targetInfos.find(t=>t.type==='page'&&t.url.startsWith(EXTENSION_PREFIX)&&
|
|
319
|
+
(t.url.includes('/workspace.html')||t.url.includes('/helm.html')));
|
|
320
|
+
if(!page && !extensionConfigured())
|
|
321
|
+
throw Error('WebHelm workspace extension is not installed or enabled');
|
|
322
|
+
const wakeTarget=page?.targetId||(await browser.send('Target.createTarget',{
|
|
323
|
+
url:`${EXTENSION_PREFIX}helm.html?webhelm_wakeup=1`,background:true})).targetId;
|
|
324
|
+
const temporary=!page;
|
|
325
|
+
let wakeSession;
|
|
326
|
+
try {
|
|
327
|
+
({sessionId:wakeSession}=await browser.send('Target.attachToTarget',{targetId:wakeTarget,flatten:true}));
|
|
328
|
+
let awake=false;
|
|
330
329
|
for(let attempt=0;attempt<20;attempt++){
|
|
331
330
|
try{
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
331
|
+
await evaluateExtension(browser,wakeSession,
|
|
332
|
+
'chrome.runtime.sendMessage({method:"workspace.list"})');
|
|
333
|
+
awake=true;break;
|
|
334
|
+
}catch(error){
|
|
335
|
+
if(attempt===19)throw error;
|
|
336
|
+
await delay(100);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if(!awake)throw Error('WebHelm Extension did not awaken');
|
|
340
|
+
} finally {
|
|
341
|
+
if(wakeSession)await browser.send('Target.detachFromTarget',{sessionId:wakeSession}).catch(()=>{});
|
|
342
|
+
if(temporary)await browser.send('Target.closeTarget',{targetId:wakeTarget}).catch(()=>{});
|
|
343
|
+
}
|
|
344
|
+
for(let attempt=0;attempt<30;attempt++){
|
|
345
|
+
const {targetInfos:updated=[]}=await browser.send('Target.getTargets');
|
|
346
|
+
const active=updated.find(t=>t.type==='service_worker'&&t.url.startsWith(EXTENSION_PREFIX));
|
|
347
|
+
if(active){
|
|
348
|
+
const {sessionId}=await browser.send('Target.attachToTarget',{targetId:active.targetId,flatten:true});
|
|
349
|
+
debuggerSession={targetId:active.targetId,sessionId};
|
|
350
|
+
try{return await evaluateExtension(browser,sessionId,
|
|
351
|
+
`globalThis.browserWorkspaceManagerRpc(${request})`);}
|
|
352
|
+
catch(error){debuggerSession=null;throw error;}
|
|
338
353
|
}
|
|
339
|
-
|
|
340
|
-
return await evaluateExtension(browser,sessionId,`chrome.runtime.sendMessage(${request})`);
|
|
341
|
-
}finally{
|
|
342
|
-
if(sessionId)await browser.send('Target.detachFromTarget',{sessionId}).catch(()=>{});
|
|
343
|
-
await browser.send('Target.closeTarget',{targetId}).catch(()=>{});
|
|
354
|
+
await delay(100);
|
|
344
355
|
}
|
|
356
|
+
throw Error('WebHelm Extension Service Worker did not become available after wake');
|
|
345
357
|
}
|
|
346
358
|
|
|
347
359
|
function jsonSafe(value){try{JSON.stringify(value);return {value}}catch{return {value_repr:String(value)}}}
|