webhelm 1.0.10 → 1.0.11
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 +1 -1
- package/package.json +1 -1
- package/runtime/extension/manifest.json +1 -1
- package/runtime/extension/popup.html +21 -24
- package/runtime/extension/popup.mjs +14 -24
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Use `npx skills add lalalic/webhelm --skill webhelm` to install agent guidance.
|
|
|
33
33
|
|
|
34
34
|
The daemon-managed Chrome Extension is installed from the npm Runtime and does not require the Chrome Web Store. Click the WebHelm toolbar icon to see daemon connectivity, each configured workspace's available capacity and current Session count. The toolbar badge shows the number of **active daemon Sessions** across all workspaces (including `0` when idle). `!` means the daemon heartbeat is stale and `?` means the Session count is unknown. The popup separately shows pool availability and idle tabs.
|
|
35
35
|
|
|
36
|
-
The popup
|
|
36
|
+
The compact popup shows daemon connectivity and per-workspace capacity and Session counts, with confirmation before stopping a Session or clearing Sessions in one workspace. The popup does not include a global stop-all action. These actions are executed by the same Node daemon as the CLI, over the existing CDP-backed Extension RPC. No shell access or HTTP listener is granted to the Extension. The persistent ChatGPT queue is not deleted, and queued work may create new Sessions afterward.
|
|
37
37
|
|
|
38
38
|
New installations use the default Chrome tab group `WebHelm` and the idle page `helm.html`. Idle URLs use only `workspace` and `role=idle`; Lease URLs use Chrome's actual `tabId` for initial CDP target discovery. There is no separate slot identity. The default `WebHelm` tab group is placed at the first position after pinned tabs when created or restored, while Session allocation does not override later user rearrangement. Chrome-saved tab group shortcuts in the bookmarks bar are separate from active groups and cannot safely be deduplicated through the Extension API. Legacy `Harness` default groups migrate only when their tabs are idle. Custom workspace names are unchanged; the old `workspace.html` path and lease titles remain readable until their Sessions finish. Closing an allocated Session returns its managed tabs to the idle page, retaining physical Chrome targets, while unleased popup children are closed. Do not manually remove active managed tabs.
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
<style>
|
|
8
8
|
:root { color-scheme: light dark; --ink:#182234; --muted:#5f6d81; --border:#e1e7f0; --accent:#345fd5; --accent-light:#edf2ff; --warning:#ae3849; }
|
|
9
9
|
* { box-sizing: border-box; }
|
|
10
|
-
body { width:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
10
|
+
body { width:320px; margin:0; padding:12px 14px; background:light-dark(#fff,#172131); color:light-dark(var(--ink),#edf2f9); font:12px/1.4 system-ui,sans-serif; }
|
|
11
|
+
.panel-heading { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:5px; }
|
|
12
|
+
h1 { margin:0; font-size:17px; letter-spacing:-.02em; }
|
|
13
|
+
.daemon-state { font-size:11px; color:light-dark(var(--muted),#acb8c9); white-space:nowrap; }
|
|
14
|
+
.daemon-state::before { content:""; display:inline-block; vertical-align:middle; width:6px; height:6px; border-radius:50%; background:#8893a1; margin-right:5px; }
|
|
15
|
+
.daemon-state.online::before { background:#23915c; }
|
|
16
|
+
.daemon-state.offline::before { background:#ae3849; }
|
|
17
|
+
p { margin:3px 0 9px; color:light-dark(var(--muted),#acb8c9); }
|
|
17
18
|
button { width:100%; border:1px solid light-dark(var(--border),#40516a); background:light-dark(#fff,#22314a); color:inherit; border-radius:9px; padding:9px; cursor:pointer; font:inherit; font-weight:600; }
|
|
18
19
|
button:disabled { opacity:.5; cursor:not-allowed; }
|
|
19
20
|
button.danger { background:#ae3849; color:#fff; border-color:#ae3849; }
|
|
@@ -21,34 +22,30 @@
|
|
|
21
22
|
#confirmation { margin:8px 0; padding:11px; background:light-dark(#fff1f2,#43222a); border-radius:9px; }
|
|
22
23
|
#confirmation p { color:inherit; }
|
|
23
24
|
.buttons { display:flex; gap:8px; }
|
|
24
|
-
.session-
|
|
25
|
-
.
|
|
26
|
-
.workspace-row {
|
|
25
|
+
.session-list { max-height:360px; overflow-y:auto; }
|
|
26
|
+
.workspace-row { padding:9px 0; border-bottom:1px solid light-dark(var(--border),#334155); }
|
|
27
|
+
.workspace-row:last-child { border-bottom:0; }
|
|
27
28
|
.workspace-title { display:flex; align-items:center; gap:8px; justify-content:space-between; }
|
|
28
29
|
.workspace-title strong { font-size:13px; overflow-wrap:anywhere; }
|
|
29
|
-
.workspace-title button { width:auto; flex-shrink:0; padding:
|
|
30
|
-
.workspace-details { color:light-dark(var(--muted),#acb8c9); font-size:11px; margin:
|
|
31
|
-
.session-row { display:flex; align-items:center; gap:8px; padding:
|
|
30
|
+
.workspace-title button { width:auto; flex-shrink:0; padding:4px 8px; font-size:11px; }
|
|
31
|
+
.workspace-details { color:light-dark(var(--muted),#acb8c9); font-size:11px; margin:2px 0 0; white-space:nowrap; }
|
|
32
|
+
.session-row { display:flex; align-items:center; gap:8px; padding:5px 0 0; }
|
|
32
33
|
.session-info { min-width:0; flex:1; }
|
|
33
34
|
.session-info strong { display:block; font-size:12px; }
|
|
34
35
|
.session-info span { display:block; color:light-dark(var(--muted),#acb8c9); font-size:11px; }
|
|
35
36
|
.session-row button { width:auto; padding:5px 9px; font-size:11px; }
|
|
36
|
-
#message {
|
|
37
|
+
#message { margin-top:8px; font-size:11px; overflow-wrap:anywhere; }
|
|
38
|
+
#message:empty { display:none; }
|
|
37
39
|
[hidden] { display:none !important; }
|
|
38
40
|
</style>
|
|
39
41
|
<script type="module" src="popup.mjs"></script>
|
|
40
42
|
</head>
|
|
41
43
|
<body>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<div class="stat"><strong id="daemon">—</strong><span>Daemon</span></div>
|
|
48
|
-
</div>
|
|
49
|
-
<div class="session-header">Workspaces</div>
|
|
50
|
-
<div id="workspace-list" class="session-list" aria-live="polite">Connecting…</div>
|
|
51
|
-
<button id="clear">Clear all sessions in all workspaces</button>
|
|
44
|
+
<header class="panel-heading">
|
|
45
|
+
<h1>WebHelm</h1>
|
|
46
|
+
<span id="daemon-status" class="daemon-state" role="status">Connecting…</span>
|
|
47
|
+
</header>
|
|
48
|
+
<div id="workspace-list" class="session-list" aria-label="Workspaces" aria-live="polite">Connecting…</div>
|
|
52
49
|
<section id="confirmation" hidden aria-label="Confirm clear sessions">
|
|
53
50
|
<p id="confirm-text">Stop the selected WebHelm session(s)? Tasks in progress may be interrupted. Queued jobs remain and may create new sessions.</p>
|
|
54
51
|
<div class="buttons">
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const el = id => document.getElementById(id);
|
|
2
2
|
let confirming = false;
|
|
3
|
-
let
|
|
4
|
-
let chosen = {action: 'stop-all'};
|
|
3
|
+
let chosen = null;
|
|
5
4
|
let lastSignature = '';
|
|
6
5
|
let refreshRunning = false;
|
|
7
6
|
|
|
@@ -15,13 +14,12 @@ function showConfirmation(action, params = {}) {
|
|
|
15
14
|
chosen = {action, ...params};
|
|
16
15
|
confirming = true;
|
|
17
16
|
el('confirmation').hidden = false;
|
|
18
|
-
|
|
19
|
-
if (action === 'stop-all') {
|
|
20
|
-
el('confirm-text').textContent = 'Stop sessions in ALL workspaces? Active tasks will be interrupted; queued jobs may create new sessions.';
|
|
21
|
-
} else if (action === 'stop-workspace') {
|
|
17
|
+
if (action === 'stop-workspace') {
|
|
22
18
|
el('confirm-text').textContent = `Stop all sessions in "${params.workspace}"? Other workspaces are unaffected.`;
|
|
23
|
-
} else {
|
|
19
|
+
} else if (action === 'stop') {
|
|
24
20
|
el('confirm-text').textContent = `Stop session ${params.sessionId.slice(0,12)}…? Active work may be interrupted.`;
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error('Unsupported stop action');
|
|
25
23
|
}
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -52,13 +50,13 @@ function renderWorkspaces(workspaces, busy, connected) {
|
|
|
52
50
|
header.className = 'workspace-title';
|
|
53
51
|
const heading = document.createElement('strong');
|
|
54
52
|
heading.textContent = workspace.name;
|
|
55
|
-
header.append(heading, actionButton('Clear
|
|
53
|
+
header.append(heading, actionButton('Clear sessions', busy || !connected || !workspace.sessions.length,
|
|
56
54
|
() => showConfirmation('stop-workspace', {workspace:workspace.name})));
|
|
57
55
|
const info = document.createElement('div');
|
|
58
56
|
info.className = 'workspace-details';
|
|
59
57
|
info.textContent = workspace.initialized
|
|
60
|
-
? `${workspace.available}
|
|
61
|
-
: `Unavailable · ${workspace.sessions.length}
|
|
58
|
+
? `${workspace.available}/${workspace.poolSize} available · ${workspace.idleTabs} idle · ${workspace.sessions.length} session${workspace.sessions.length === 1 ? "" : "s"}`
|
|
59
|
+
: `Unavailable · ${workspace.sessions.length} session${workspace.sessions.length === 1 ? "" : "s"}`;
|
|
62
60
|
row.append(header, info);
|
|
63
61
|
for (const session of workspace.sessions) {
|
|
64
62
|
const sessionRow = document.createElement('div');
|
|
@@ -81,12 +79,10 @@ async function refresh() {
|
|
|
81
79
|
refreshRunning = true;
|
|
82
80
|
try {
|
|
83
81
|
const state = await rpc('workspace.control.status');
|
|
84
|
-
el('
|
|
85
|
-
el('
|
|
86
|
-
el('daemon').textContent = state.connected ? 'Online' : 'Offline';
|
|
82
|
+
el('daemon-status').textContent = state.connected ? 'Online' : 'Offline';
|
|
83
|
+
el('daemon-status').className = `daemon-state ${state.connected ? 'online' : 'offline'}`;
|
|
87
84
|
const busy = Boolean(state.request);
|
|
88
85
|
renderWorkspaces(state.workspaces || [], busy, state.connected);
|
|
89
|
-
el('clear').disabled = !state.connected || !state.sessionCount || busy;
|
|
90
86
|
el('confirm').disabled = !state.connected || busy;
|
|
91
87
|
if (busy) {
|
|
92
88
|
el('message').textContent = 'Stopping sessions…';
|
|
@@ -94,25 +90,20 @@ async function refresh() {
|
|
|
94
90
|
el('message').textContent = state.lastResult.ok
|
|
95
91
|
? `Last action: stopped ${state.lastResult.stoppedCount} session(s).`
|
|
96
92
|
: `Last action failed: ${state.lastResult.error}`;
|
|
97
|
-
requestId = null;
|
|
98
93
|
}
|
|
99
94
|
} catch (error) {
|
|
100
|
-
el('clear').disabled = true;
|
|
101
95
|
el('confirm').disabled = true;
|
|
102
|
-
el('daemon').textContent = 'Offline';
|
|
103
|
-
el('
|
|
104
|
-
el('sessions').textContent = '—';
|
|
96
|
+
el('daemon-status').textContent = 'Offline';
|
|
97
|
+
el('daemon-status').className = 'daemon-state offline';
|
|
105
98
|
el('message').textContent = error.message;
|
|
106
99
|
} finally {
|
|
107
100
|
refreshRunning = false;
|
|
108
101
|
}
|
|
109
102
|
}
|
|
110
103
|
|
|
111
|
-
el('clear').addEventListener('click', () => showConfirmation('stop-all'));
|
|
112
104
|
el('cancel').addEventListener('click', () => {
|
|
113
105
|
confirming = false;
|
|
114
106
|
el('confirmation').hidden = true;
|
|
115
|
-
el('clear').hidden = false;
|
|
116
107
|
refresh();
|
|
117
108
|
});
|
|
118
109
|
el('confirm').addEventListener('click', async () => {
|
|
@@ -120,15 +111,14 @@ el('confirm').addEventListener('click', async () => {
|
|
|
120
111
|
el('confirm').disabled = true;
|
|
121
112
|
try {
|
|
122
113
|
const result = await rpc('workspace.control.requestStop', chosen);
|
|
123
|
-
|
|
114
|
+
if (!result.id) throw new Error('Missing WebHelm stop request ID');
|
|
124
115
|
el('message').textContent = 'Waiting for WebHelm daemon…';
|
|
125
116
|
} catch (error) {
|
|
126
117
|
el('message').textContent = error.message;
|
|
127
118
|
} finally {
|
|
128
119
|
confirming = false;
|
|
129
120
|
el('confirmation').hidden = true;
|
|
130
|
-
|
|
131
|
-
refresh();
|
|
121
|
+
refresh();
|
|
132
122
|
}
|
|
133
123
|
});
|
|
134
124
|
|