webhelm 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +1 -1
- package/runtime/cli.mjs +47 -20
- package/runtime/extension/manifest.json +1 -1
- package/runtime/extension/service-worker.mjs +11 -0
- package/runtime/extension/workspace-manager.mjs +10 -2
- package/runtime/extension-manager.mjs +7 -1
- package/runtime/login-guard.mjs +6 -6
- package/runtime/node-daemon.mjs +14 -6
package/README.md
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
The npm package **does not include** the Skill or site Actions. The Skill's executable uses `npm exec --yes --package=webhelm@latest -- webhelm` to avoid inherited npm lifecycle variable conflicts; its contents may update without a Runtime release.
|
|
13
13
|
|
|
14
|
+
The agent-facing `webhelm` CLI uses only the `WebHelm` Workspace. It intentionally does not accept `--workspace` or expose Workspace management helpers in `session exec`. `session stop --all` only stops WebHelm Sessions. Application services such as Relay provision their own named Workspaces through the daemon IPC `workspace-create`, `workspace-resize`, and `workspace-delete`; they may start Sessions via the daemon `start` operation for those pre-provisioned names. Leasing a missing Workspace fails rather than creating it.
|
|
15
|
+
|
|
16
|
+
Neo Master development uses the Git checkout directly via local symlinks for the CLI, Skill, and Extension; production nodes use published npm Runtime installations. Set `WEBHELM_SOURCE_MODE=1` when running the checkout directly; this loads the Extension from the repository and creates a LaunchAgent that references the repository's CLI without staging an npm Runtime copy.
|
|
17
|
+
|
|
14
18
|
## Install and use
|
|
15
19
|
|
|
16
20
|
```sh
|
package/package.json
CHANGED
package/runtime/cli.mjs
CHANGED
|
@@ -4,13 +4,14 @@ import path from 'node:path';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { randomUUID } from 'node:crypto';
|
|
7
|
-
import { ensureDaemon, request,
|
|
7
|
+
import { ensureDaemon, request, SOCKET } from './node-daemon.mjs';
|
|
8
8
|
import { PLATFORMS, listPlatforms, platformProfile, actionPath, validateConfig } from './platforms.mjs';
|
|
9
9
|
import { ACTIONS_DIR, ensureActions, syncActions, publishActions } from './actions-repo.mjs';
|
|
10
10
|
import { migrateConfig, migrateChromeProfile } from './migrate.mjs';
|
|
11
11
|
import { installLoginGuard, stageRuntimeForLoginGuard } from './login-guard.mjs';
|
|
12
12
|
|
|
13
13
|
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const CLI_WORKSPACE = 'WebHelm';
|
|
14
15
|
const emit = value => console.log(JSON.stringify(value));
|
|
15
16
|
const arg = (items, name) => {
|
|
16
17
|
const i = items.indexOf(name);
|
|
@@ -26,8 +27,8 @@ Usage:
|
|
|
26
27
|
webhelm setup
|
|
27
28
|
webhelm status
|
|
28
29
|
webhelm ensure # verify/recover managed Chrome and WebHelm group, not just daemon ping
|
|
29
|
-
webhelm capacity
|
|
30
|
-
webhelm session start [--
|
|
30
|
+
webhelm capacity # WebHelm workspace only
|
|
31
|
+
webhelm session start [--url URL] # WebHelm workspace only
|
|
31
32
|
webhelm session list
|
|
32
33
|
webhelm session heartbeat SESSION_ID # keep a working Session alive
|
|
33
34
|
webhelm session exec SESSION_ID [--code-file FILE] # JavaScript
|
|
@@ -93,9 +94,26 @@ async function runPlatform(args) {
|
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
if (mode === 'sync' && (!sid || auto)) throw Error('sync new-turn requires a caller-owned --session-id');
|
|
97
|
+
if (sid) await requireCliSession(sid);
|
|
96
98
|
if (!sid && !auto) throw Error('platform run requires --session-id; use --auto-session only for one-shot actions');
|
|
97
99
|
return request({op:'platform-run', platform, action, config: {...config, ...(mode ? {mode} : {})},
|
|
98
|
-
session_id:sid, auto_session:auto, url:arg(args, '--url')});
|
|
100
|
+
session_id:sid, auto_session:auto, url:arg(args, '--url'), workspace:CLI_WORKSPACE});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The agent CLI is not a Workspace provisioning interface. Other workspaces
|
|
104
|
+
// belong to application code using the explicit daemon IPC API.
|
|
105
|
+
function rejectWorkspaceOverride(args) {
|
|
106
|
+
if (has(args, '--workspace') || args.some(a=>a.startsWith('--workspace=')))
|
|
107
|
+
throw Error('CLI is restricted to the WebHelm workspace; applications must provision custom workspaces through the daemon API');
|
|
108
|
+
}
|
|
109
|
+
function onlyCliWorkspace(sessions) {
|
|
110
|
+
return (sessions || []).filter(item=>item.workspace === CLI_WORKSPACE);
|
|
111
|
+
}
|
|
112
|
+
async function requireCliSession(sessionId) {
|
|
113
|
+
if (!sessionId) throw Error('session id is required');
|
|
114
|
+
const listed = await request({op:'list'});
|
|
115
|
+
if (!onlyCliWorkspace(listed.sessions).some(item=>item.session_id===sessionId))
|
|
116
|
+
throw Error(`Session ${sessionId} is not owned by the WebHelm CLI workspace`);
|
|
99
117
|
}
|
|
100
118
|
|
|
101
119
|
async function main(args = process.argv.slice(2)) {
|
|
@@ -110,8 +128,11 @@ async function main(args = process.argv.slice(2)) {
|
|
|
110
128
|
if (group === 'setup') {
|
|
111
129
|
const repo=await ensureActions();
|
|
112
130
|
const chrome=await ensureDaemon();
|
|
113
|
-
const
|
|
114
|
-
const
|
|
131
|
+
const sourceMode=process.env.WEBHELM_SOURCE_MODE==='1';
|
|
132
|
+
const cliPath=sourceMode
|
|
133
|
+
? fileURLToPath(new URL('../bin/webhelm.mjs',import.meta.url))
|
|
134
|
+
: path.join(stageRuntimeForLoginGuard().path,'bin/webhelm.mjs');
|
|
135
|
+
const login_guard=installLoginGuard({cliPath,nodePath:process.execPath,sourceMode});
|
|
115
136
|
return emit({status:'ready',...repo,daemon_pid:chrome.pid,managed_chrome:chrome.managed_chrome,login_guard});
|
|
116
137
|
}
|
|
117
138
|
if (group === 'ensure') return emit(await request({op:'guard-check'}));
|
|
@@ -124,26 +145,31 @@ async function main(args = process.argv.slice(2)) {
|
|
|
124
145
|
daemon:{running:true, pid:daemon.pid, session_count:daemon.session_count, queue_paused:daemon.queue_paused, socket:SOCKET}});
|
|
125
146
|
}
|
|
126
147
|
if (group === 'capacity') {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!String(error.message).includes('unknown op: capacity')) throw error;
|
|
133
|
-
emit(await handle(payload));
|
|
134
|
-
return process.exit(0); // close this CLI's one-off CDP websocket
|
|
135
|
-
}
|
|
148
|
+
rejectWorkspaceOverride(args);
|
|
149
|
+
const payload={op:'capacity',workspace:CLI_WORKSPACE};
|
|
150
|
+
// Never run a second in-process browser controller as a fallback: it may
|
|
151
|
+
// reload an older daemon's Extension while its Sessions are still active.
|
|
152
|
+
return emit(await request(payload));
|
|
136
153
|
}
|
|
137
154
|
if (group === 'session') {
|
|
138
|
-
|
|
139
|
-
if (command === '
|
|
155
|
+
rejectWorkspaceOverride(args);
|
|
156
|
+
if (command === 'start') return emit(await request({op:'start', workspace:CLI_WORKSPACE, url:arg(args,'--url')}));
|
|
157
|
+
if (command === 'list') {
|
|
158
|
+
const result=await request({op:'list'});
|
|
159
|
+
const sessions=onlyCliWorkspace(result.sessions);
|
|
160
|
+
return emit({session_count:sessions.length,sessions});
|
|
161
|
+
}
|
|
140
162
|
if (command === 'heartbeat') {
|
|
141
|
-
|
|
163
|
+
await requireCliSession(args[2]);
|
|
142
164
|
return emit(await request({op:'heartbeat',session_id:args[2]}));
|
|
143
165
|
}
|
|
144
|
-
if (command === 'stop')
|
|
166
|
+
if (command === 'stop') {
|
|
167
|
+
if (has(args,'--all')) return emit(await request({op:'stop-workspace',workspace:CLI_WORKSPACE}));
|
|
168
|
+
await requireCliSession(args[2]);
|
|
169
|
+
return emit(await request({op:'stop',session_id:args[2]}));
|
|
170
|
+
}
|
|
145
171
|
if (command === 'exec') {
|
|
146
|
-
|
|
172
|
+
await requireCliSession(args[2]);
|
|
147
173
|
const file = arg(args, '--code-file');
|
|
148
174
|
const code = file ? fs.readFileSync(file, 'utf8') : fs.readFileSync(0, 'utf8');
|
|
149
175
|
const result = await request({op:'exec', session_id:args[2], code});
|
|
@@ -153,6 +179,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
153
179
|
}
|
|
154
180
|
}
|
|
155
181
|
if (group === 'platform') {
|
|
182
|
+
rejectWorkspaceOverride(args);
|
|
156
183
|
if (command === 'sync') return emit(await syncActions());
|
|
157
184
|
if (command === 'publish') return emit(await publishActions(arg(args, '--message')));
|
|
158
185
|
await ensureActions();
|
|
@@ -96,7 +96,18 @@ globalThis.browserWorkspaceManagerRpc = async (request) => {
|
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
+
// Only the daemon's direct Service Worker RPC may provision, resize, delete,
|
|
100
|
+
// or restore Workspace identities. A browser Session on an extension page must
|
|
101
|
+
// not gain management rights through chrome.runtime.sendMessage().
|
|
102
|
+
const APPLICATION_ONLY_METHODS = new Set([
|
|
103
|
+
"workspace.create", "workspace.resize", "workspace.delete",
|
|
104
|
+
"workspace.ensure", "workspace.migrateDefault",
|
|
105
|
+
]);
|
|
99
106
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
107
|
+
if (APPLICATION_ONLY_METHODS.has(request?.method)) {
|
|
108
|
+
sendResponse({ok:false,error:{message:"Workspace management requires an application daemon API"}});
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
100
111
|
if (request?.method?.startsWith("workspace.control.") &&
|
|
101
112
|
(sender?.url !== chrome.runtime.getURL("popup.html") ||
|
|
102
113
|
!["workspace.control.requestStop", "workspace.control.status"].includes(request.method))) {
|
|
@@ -550,7 +550,11 @@ export class WorkspaceManager {
|
|
|
550
550
|
|
|
551
551
|
async acquireIdentity(name) {
|
|
552
552
|
const normalizedName = normalizeWorkspaceName(name);
|
|
553
|
-
|
|
553
|
+
// Leasing may allocate a Tab inside an existing Workspace, but must never
|
|
554
|
+
// provision a new Workspace as a side effect. Only app-owned creation can.
|
|
555
|
+
const ready = await this.status(normalizedName);
|
|
556
|
+
if (!ready.initialized)
|
|
557
|
+
throw new Error(`Workspace ${normalizedName} does not exist; an application must provision it first`);
|
|
554
558
|
const tabs = await this.groupTabs(ready.groupId);
|
|
555
559
|
const { idle, leased } = this.classify(normalizedName, tabs);
|
|
556
560
|
if (!idle.length && leased.length >= ready.poolSize) {
|
|
@@ -591,7 +595,11 @@ export class WorkspaceManager {
|
|
|
591
595
|
throw new Error("Workspace acquire requires an http(s) or chrome-extension URL");
|
|
592
596
|
}
|
|
593
597
|
|
|
594
|
-
|
|
598
|
+
// Leasing may allocate a Tab inside an existing Workspace, but must never
|
|
599
|
+
// provision a new Workspace as a side effect. Only app-owned creation can.
|
|
600
|
+
const ready = await this.status(normalizedName);
|
|
601
|
+
if (!ready.initialized)
|
|
602
|
+
throw new Error(`Workspace ${normalizedName} does not exist; an application must provision it first`);
|
|
595
603
|
const tabs = await this.groupTabs(ready.groupId);
|
|
596
604
|
const { idle, leased } = this.classify(normalizedName, tabs);
|
|
597
605
|
if (!idle.length && leased.length >= ready.poolSize) {
|
|
@@ -6,8 +6,10 @@ import {fileURLToPath} from 'node:url';
|
|
|
6
6
|
import {LOCAL_ROOT} from './config-paths.mjs';
|
|
7
7
|
|
|
8
8
|
export const EXTENSION_ID='kkkpgmhpoeceojalkpfdidffdfnlgdog';
|
|
9
|
-
export const EXTENSION_HOME=path.resolve(process.env.WEBHELM_EXTENSION_HOME || path.join(LOCAL_ROOT,'extension'));
|
|
10
9
|
const SOURCE=fileURLToPath(new URL('./extension/',import.meta.url));
|
|
10
|
+
const SOURCE_MODE=process.env.WEBHELM_SOURCE_MODE==='1';
|
|
11
|
+
export const EXTENSION_HOME=path.resolve(process.env.WEBHELM_EXTENSION_HOME ||
|
|
12
|
+
(SOURCE_MODE ? SOURCE : path.join(LOCAL_ROOT,'extension')));
|
|
11
13
|
const FILES=[
|
|
12
14
|
'manifest.json','service-worker.mjs','workspace-manager.mjs','workspace.html','helm.html','workspace-page.mjs',
|
|
13
15
|
'control-channel.mjs','workspace-summary.mjs','session-badge.mjs','popup.html','popup.mjs',
|
|
@@ -50,6 +52,10 @@ function excludeFromActionsGit(dir){
|
|
|
50
52
|
export function stageExtension(dir=EXTENSION_HOME){
|
|
51
53
|
const target=path.resolve(dir);
|
|
52
54
|
const bundled=manifest(SOURCE);
|
|
55
|
+
// Neo Master works from its Git checkout. Never stage or overwrite source
|
|
56
|
+
// files, and never accept arbitrary symlinks as a deployment directory.
|
|
57
|
+
if(SOURCE_MODE && canonicalPath(target)===canonicalPath(SOURCE))
|
|
58
|
+
return {path:SOURCE,version:bundled.version,updated:false,sourceMode:true};
|
|
53
59
|
if(fs.existsSync(target)&&fs.lstatSync(target).isSymbolicLink())throw Error('Refusing to replace extension symlink');
|
|
54
60
|
if(fs.existsSync(target)&&identical(target))return {path:target,version:bundled.version,updated:false};
|
|
55
61
|
if(fs.existsSync(target))manifest(target); // Never replace unrelated content.
|
package/runtime/login-guard.mjs
CHANGED
|
@@ -36,23 +36,23 @@ export function stageRuntimeForLoginGuard({home=os.homedir(),source=fileURLToPat
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
const escapeXml=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
39
|
-
export function loginGuardDefinition(home=os.homedir(),{cliPath=path.join(home,'.local/bin/webhelm'),nodePath=null}={}) {
|
|
39
|
+
export function loginGuardDefinition(home=os.homedir(),{cliPath=path.join(home,'.local/bin/webhelm'),nodePath=null,sourceMode=false}={}) {
|
|
40
40
|
const cli=cliPath;
|
|
41
41
|
const program=nodePath||cli;
|
|
42
42
|
const args=nodePath?`<string>${escapeXml(cli)}</string><string>ensure</string>`:'<string>ensure</string>';
|
|
43
43
|
const environment=[path.join(home,'.local/bin'),path.join(home,'.local/opt/node22/bin'),
|
|
44
44
|
'/opt/homebrew/bin','/usr/local/bin','/usr/bin','/bin'].join(':');
|
|
45
|
-
// Use
|
|
46
|
-
//
|
|
45
|
+
// Use a pinned local Runtime (or the source checkout on Neo Master), not
|
|
46
|
+
// npm/network at login or after Chrome updates. This must call
|
|
47
47
|
// 'ensure', not 'status': a live daemon does not imply a live Chrome.
|
|
48
|
-
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${LABEL}</string>\n<key>ProgramArguments</key><array><string>${escapeXml(program)}</string>${args}</array>\n<key>EnvironmentVariables</key><dict><key>PATH</key><string>${escapeXml(environment)}</string><key>HOME</key><string>${escapeXml(home)}</string
|
|
48
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n<key>Label</key><string>${LABEL}</string>\n<key>ProgramArguments</key><array><string>${escapeXml(program)}</string>${args}</array>\n<key>EnvironmentVariables</key><dict><key>PATH</key><string>${escapeXml(environment)}</string><key>HOME</key><string>${escapeXml(home)}</string>${sourceMode?'<key>WEBHELM_SOURCE_MODE</key><string>1</string>':''}</dict>\n<key>RunAtLoad</key><true/>\n<key>StartInterval</key><integer>30</integer>\n<key>StandardOutPath</key><string>/dev/null</string>\n<key>StandardErrorPath</key><string>/dev/null</string>\n</dict></plist>\n`;
|
|
49
49
|
}
|
|
50
|
-
export function installLoginGuard({home=os.homedir(), platform=process.platform, apply=true,cliPath,nodePath}={}){
|
|
50
|
+
export function installLoginGuard({home=os.homedir(), platform=process.platform, apply=true,cliPath,nodePath,sourceMode=false}={}){
|
|
51
51
|
if(platform!=='darwin')return {status:'unsupported',label:LABEL};
|
|
52
52
|
const cli=cliPath||path.join(home,'.local/bin/webhelm');
|
|
53
53
|
if(!fs.existsSync(cli))return {status:'skipped',reason:'WebHelm runtime launcher missing',label:LABEL};
|
|
54
54
|
const dest=path.join(home,'Library/LaunchAgents',`${LABEL}.plist`);
|
|
55
|
-
const content=loginGuardDefinition(home,{cliPath,nodePath});
|
|
55
|
+
const content=loginGuardDefinition(home,{cliPath,nodePath,sourceMode});
|
|
56
56
|
fs.mkdirSync(path.dirname(dest),{recursive:true});
|
|
57
57
|
if(fs.existsSync(dest)){
|
|
58
58
|
const old=fs.readFileSync(dest,'utf8');
|
package/runtime/node-daemon.mjs
CHANGED
|
@@ -232,9 +232,6 @@ function makeHelpers(s){
|
|
|
232
232
|
async workspace_status(){return extensionCall(s,'workspace.status',{name:s.workspace})},
|
|
233
233
|
async workspace_capacity(){const r=await h.workspace_status();const idle=Array.isArray(r.idleTabIds)?r.idleTabIds.length:null,leased=Array.isArray(r.leasedTabIds)?r.leasedTabIds.length:null;return {name:r.name,poolSize:r.poolSize??null,maxCapacity:r.maxCapacity??r.poolSize??null,physicalTabs:r.physicalTabs??(idle!==null&&leased!==null?idle+leased:null),idle,leased,available:Number.isInteger(r.maxCapacity??r.poolSize)&&leased!==null?Math.max(0,(r.maxCapacity??r.poolSize)-leased):null}} ,
|
|
234
234
|
async workspace_list(){return extensionCall(s,'workspace.list')},
|
|
235
|
-
async workspace_create(name,poolSize=5){return extensionCall(s,'workspace.create',{name,poolSize})},
|
|
236
|
-
async workspace_resize(name,poolSize){return extensionCall(s,'workspace.resize',{name,poolSize})},
|
|
237
|
-
async workspace_delete(name,force=false){return extensionCall(s,'workspace.delete',{name,force})},
|
|
238
235
|
async wait(seconds){return delay(seconds*1000)},
|
|
239
236
|
async sleep(milliseconds){return delay(milliseconds)},
|
|
240
237
|
async type_text(text){return cdp(s,'Input.insertText',{text:String(text)})},
|
|
@@ -507,7 +504,18 @@ function heartbeatSession(sid){
|
|
|
507
504
|
return {session_id:sid,alive:true,last_used_at:s.last_used_at};
|
|
508
505
|
}
|
|
509
506
|
|
|
510
|
-
|
|
507
|
+
// Applications (Relay, Neo, etc.) provision Workspaces through the daemon IPC,
|
|
508
|
+
// never through the agent-facing CLI or its session JavaScript namespace.
|
|
509
|
+
async function workspaceOp(req) {
|
|
510
|
+
if (typeof req.name !== 'string' || !req.name.trim()) throw Error('workspace name required');
|
|
511
|
+
if (req.op === 'workspace-create')
|
|
512
|
+
return extensionCall(null,'workspace.create',{name:req.name,poolSize:req.pool_size ?? 8});
|
|
513
|
+
if (req.op === 'workspace-resize')
|
|
514
|
+
return extensionCall(null,'workspace.resize',{name:req.name,poolSize:req.pool_size});
|
|
515
|
+
if (req.op === 'workspace-delete')
|
|
516
|
+
return extensionCall(null,'workspace.delete',{name:req.name,force:Boolean(req.force)});
|
|
517
|
+
throw Error(`unknown workspace operation: ${req.op}`);
|
|
518
|
+
}
|
|
511
519
|
function getChatGPTQueue() {
|
|
512
520
|
if (chatgptQueue) return chatgptQueue;
|
|
513
521
|
chatgptQueue = new ChatGPTQueue({
|
|
@@ -605,7 +613,7 @@ export function actionOutput(logs, value) {
|
|
|
605
613
|
async function runPlatform(req) {
|
|
606
614
|
const platform=req.platform, action=req.action, config=req.config || {};
|
|
607
615
|
const auto=Boolean(req.auto_session);
|
|
608
|
-
const started=auto ? await createSession({url:req.url}) : {session_id:req.session_id};
|
|
616
|
+
const started=auto ? await createSession({url:req.url,workspace:req.workspace}) : {session_id:req.session_id};
|
|
609
617
|
const sid=started.session_id;
|
|
610
618
|
if (!sid) throw Error('platform run requires a session_id or auto_session');
|
|
611
619
|
const session=sessions.get(sid);
|
|
@@ -646,7 +654,7 @@ async function runPlatform(req) {
|
|
|
646
654
|
self_heal_capable:!auto,result};
|
|
647
655
|
}
|
|
648
656
|
|
|
649
|
-
export async function handle(req){switch(req.op){case'ping':return {ok:true,pid:process.pid,version:PACKAGE_VERSION,queue_paused:process.env.WEBHELM_CHATGPT_QUEUE_PAUSED==='1',session_count:sessions.size,managed_chrome:managedChrome||{profile_dir:PROFILE_DIR, endpoint:process.env.WEBHELM_CDP_URL||'http://127.0.0.1:9222',reused:true},chrome_guard:managedChromeGuard.status()};case'guard-check':{let status=await managedChromeGuard.tick();for(let retry=0;retry<6 && status.status!=='healthy' && !status.skipped;retry++){await delay(800);status=await managedChromeGuard.tick();}if(status.status!=='healthy' && !status.skipped)throw Error(`WebHelm Chrome health degraded: ${status.error||status.status}`);return {ok:true,chrome_guard:status};}case'heartbeat':return heartbeatSession(req.session_id);case'list':return {session_count:sessions.size,sessions:[...sessions.values()].map(s=>({session_id:s.session_id,workspace:s.workspace,target_id:s.targetId,last_used_at:s.last_used_at}))};case'start':return createSession(req);case'exec':return execute(req);case'stop':return stopSession(req.session_id);case'stop-all':return stopAllSessions();case'capacity':return workspaceCapacity(req);case'workspace-create':case'workspace-delete':return workspaceOp(req);case'chatgpt-new-turn-enqueue':return getChatGPTQueue().enqueue(req.config);case'platform-run':return runPlatform(req);default:throw Error(`unknown op: ${req.op}`)}}
|
|
657
|
+
export async function handle(req){switch(req.op){case'ping':return {ok:true,pid:process.pid,version:PACKAGE_VERSION,queue_paused:process.env.WEBHELM_CHATGPT_QUEUE_PAUSED==='1',session_count:sessions.size,managed_chrome:managedChrome||{profile_dir:PROFILE_DIR, endpoint:process.env.WEBHELM_CDP_URL||'http://127.0.0.1:9222',reused:true},chrome_guard:managedChromeGuard.status()};case'guard-check':{let status=await managedChromeGuard.tick();for(let retry=0;retry<6 && status.status!=='healthy' && !status.skipped;retry++){await delay(800);status=await managedChromeGuard.tick();}if(status.status!=='healthy' && !status.skipped)throw Error(`WebHelm Chrome health degraded: ${status.error||status.status}`);return {ok:true,chrome_guard:status};}case'heartbeat':return heartbeatSession(req.session_id);case'list':return {session_count:sessions.size,sessions:[...sessions.values()].map(s=>({session_id:s.session_id,workspace:s.workspace,target_id:s.targetId,last_used_at:s.last_used_at}))};case'start':return createSession(req);case'exec':return execute(req);case'stop':return stopSession(req.session_id);case'stop-all':return stopAllSessions();case'stop-workspace':{if(typeof req.workspace!=='string'||!req.workspace.trim())throw Error('workspace required');return stopWorkspaceSessions(req.workspace)}case'capacity':return workspaceCapacity(req);case'workspace-create':case'workspace-resize':case'workspace-delete':return workspaceOp(req);case'chatgpt-new-turn-enqueue':return getChatGPTQueue().enqueue(req.config);case'platform-run':return runPlatform(req);default:throw Error(`unknown op: ${req.op}`)}}
|
|
650
658
|
function serve(conn) {
|
|
651
659
|
let buffer = '';
|
|
652
660
|
conn.setEncoding('utf8');
|