haltija 1.12.2 → 1.12.6
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/CHANGELOG.md +222 -0
- package/apps/desktop/artifacts.js +5 -5
- package/apps/desktop/isolation.js +5 -5
- package/apps/desktop/package.json +6 -7
- package/apps/desktop/renderer/isolation.js +5 -5
- package/apps/desktop/resources/component.js +66 -23
- package/bin/cli-commands.mjs +8 -4
- package/bin/cli-subcommand.mjs +74 -4
- package/bin/format-test.mjs +8 -0
- package/bin/hints.json +4 -0
- package/bin/hints.mjs +8 -0
- package/bin/hj.mjs +82 -2
- package/bin/mcp-setup.mjs +2 -2
- package/bin/private-state.mjs +2 -2
- package/bin/project-origins.mjs +3 -3
- package/bin/semver.mjs +3 -3
- package/bin/server-list.mjs +4 -4
- package/bin/tab-liveness.mjs +27 -0
- package/bin/tmux-session.mjs +131 -0
- package/bin/version.mjs +1 -1
- package/bin/window-state.mjs +5 -5
- package/dist/.metadata_never_index +0 -0
- package/dist/api-schema.d.ts +61 -19
- package/dist/cli-commands.d.ts +1 -1
- package/dist/client.js +2 -2
- package/dist/codemirror-raw.js +19 -19
- package/dist/codemirror.js +19 -19
- package/dist/component.d.ts +33 -1
- package/dist/component.esm.js +66 -23
- package/dist/component.js +66 -23
- package/dist/drag.d.ts +2 -0
- package/dist/hj.js +295 -11
- package/dist/idle-timeout.d.ts +99 -0
- package/dist/index.js +911 -179
- package/dist/naming-probe.d.ts +97 -0
- package/dist/server.js +914 -182
- package/dist/tab-liveness.d.ts +23 -0
- package/dist/test-actions.d.ts +218 -17
- package/dist/test.js +10 -4
- package/dist/tmux-session.d.ts +125 -0
- package/dist/types.d.ts +30 -1
- package/dist/version.d.ts +1 -1
- package/dist/ws-url.d.ts +17 -0
- package/docs/CI-INTEGRATION.md +24 -19
- package/llms.txt +21 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,227 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.12.6
|
|
4
|
+
|
|
5
|
+
Security updates, three silent-failure fixes in the path from "server tells page how to reach me",
|
|
6
|
+
a new way to watch — and talk to — the agent driving your browser, and both halves of the
|
|
7
|
+
shared-browser trade: instances that never died, and tabs that answer while asleep.
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
|
|
11
|
+
- **Electron 40.6.1 → 43.4.1.** 40.6.1 sat inside the range for **two context-isolation bypasses**,
|
|
12
|
+
and context isolation is haltija's security boundary — the desktop app strips CSP and injects into
|
|
13
|
+
arbitrary pages. One bump clears eight advisories.
|
|
14
|
+
- **`apps/mcp` re-locked** off a cross-client data leak in `@modelcontextprotocol/sdk` 1.25.2
|
|
15
|
+
(→ 1.30.0), plus `hono`, `@hono/node-server` and `fast-uri`.
|
|
16
|
+
- **`electron-builder` is no longer a dependency.** It was pulling **271 transitive packages** into
|
|
17
|
+
`apps/desktop` — 285 before, 14 after — and supplied five advisories including a critical `tar`
|
|
18
|
+
one, all for DMG packaging nothing exercises. The `build:*` scripts invoke it on demand instead.
|
|
19
|
+
|
|
20
|
+
### Three ways the page could not reach the server, all silent
|
|
21
|
+
|
|
22
|
+
Each of these ended with no widget, `hj where` reporting `0 tabs`, and the reasonable conclusion
|
|
23
|
+
that your own setup was wrong.
|
|
24
|
+
|
|
25
|
+
- **Access over LAN or Bonjour never worked.** `/inject.js` and `/dev.js` handed the browser URLs
|
|
26
|
+
built as `localhost:<port>` — and `localhost` in a served script means the *browser's* machine. A
|
|
27
|
+
page opened from another device was told to connect to itself. Both now derive the host from the
|
|
28
|
+
request, which is exactly "how the client reached us", so LAN hostnames, `.local` names, fixed IPs
|
|
29
|
+
and tunnels all work. The generated embed snippet had the same bug and now uses
|
|
30
|
+
`location.hostname`.
|
|
31
|
+
- **Server-side recording was broken for every `wss://` configuration**, including ordinary HTTPS
|
|
32
|
+
localhost. `"wss://host/…".replace("ws:", "http:")` is a no-op — the substring is `wss:` — so the
|
|
33
|
+
derived URL stayed `wss://`, which `fetch` cannot use. Three places derived an HTTP base from a WS
|
|
34
|
+
URL and two were wrong; there is one function now.
|
|
35
|
+
- **The widget never sent its token.** Eight `fetch` call sites, zero `X-Haltija-Token`, so on a
|
|
36
|
+
`--token` server every page-side feature returned 401. That made "require a token" and "the page
|
|
37
|
+
can talk to the server" mutually exclusive — which matters most over a tunnel, exactly where a
|
|
38
|
+
token matters most.
|
|
39
|
+
|
|
40
|
+
### Two halves of the shared-browser trade (#39, #41)
|
|
41
|
+
|
|
42
|
+
Moving to shared tabs and servers stopped per-run Electron instances proliferating and stopped a
|
|
43
|
+
launch killing a peer's channel. It also swapped in two problems, reported together.
|
|
44
|
+
|
|
45
|
+
- **A `--private` instance now has a lifetime bound.** It exits after 8h with no client activity,
|
|
46
|
+
saying why on the way out — an instance that vanishes silently is indistinguishable from a crash.
|
|
47
|
+
The only previous bound was spawner-pid polling, which cannot help when teardown never *runs*: a
|
|
48
|
+
SIGKILLed session, a sleeping laptop, a crashed harness. #39 found one twelve days old at 5.7 GB
|
|
49
|
+
and ~150% CPU on a machine at load average 212, with the slowdown blamed on unrelated code.
|
|
50
|
+
Shared servers stay unbounded — their stickiness is the feature. `HALTIJA_IDLE_TIMEOUT_HOURS`
|
|
51
|
+
overrides in both directions; `0` disables.
|
|
52
|
+
|
|
53
|
+
Polling does **not** count as activity. The desktop app's own renderer hits `/status` every five
|
|
54
|
+
seconds forever, so under the obvious implementation a `--private --app` instance would have
|
|
55
|
+
refreshed its idle clock from its own UI and never expired — leaving exactly the configuration
|
|
56
|
+
#39 reported as the one case still immune.
|
|
57
|
+
|
|
58
|
+
- **A tab can report `visible` and not be painting, and now says so.** `visibilityState` means "is
|
|
59
|
+
this tab selected", not "is it being composited"; they diverge for occluded windows, offscreen
|
|
60
|
+
windows and a sleeping display. In that gap the tab answers confidently about content that never
|
|
61
|
+
rendered, and a screenshot shows a stale frame the compositor is holding. Every result now carries
|
|
62
|
+
`paintAgeMs`, and a stale one attaches a warning naming the cause. `hj doctor` reads the same
|
|
63
|
+
number rather than keeping its own opinion.
|
|
64
|
+
|
|
65
|
+
Not fixed: getting a foreground window cheaply, which is what #41 actually asks for. That needs a
|
|
66
|
+
shape decision and is tracked in `TODO.md`.
|
|
67
|
+
|
|
68
|
+
### `hj session` — watch the agent, and talk back
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
hj session attach <tmux-session> # opt in (read-only)
|
|
72
|
+
hj session read --follow # watch it work
|
|
73
|
+
hj session write "this is wrong" # needs --allow-input at attach
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Mirrors an agent running in tmux into the browser channel, so a **page** — including one on a VR
|
|
77
|
+
headset over a tunnel — can watch the agent work and say something to it. `capture-pane` returns
|
|
78
|
+
already-rendered text, so the page view is a `<pre>` rather than a terminal emulator, and tmux can
|
|
79
|
+
attach to a session that is **already running**.
|
|
80
|
+
|
|
81
|
+
Writing uses `send-keys`, so your sentence arrives on the agent's stdin as a normal turn — no queue,
|
|
82
|
+
no message API, no polling primitive.
|
|
83
|
+
|
|
84
|
+
**Reading and writing are separate grants**, because they are different risks: reading leaks what the
|
|
85
|
+
agent *prints*, writing decides what the agent *does* — including answering a permission prompt,
|
|
86
|
+
where this is indistinguishable from the operator typing. A mirror attached for watching refuses
|
|
87
|
+
writes; detaching revokes the grant. Attaching an **unauthenticated** server warns, because a mirror
|
|
88
|
+
turns "can reach this port" into "can read everything the agent prints".
|
|
89
|
+
|
|
90
|
+
## 1.12.5
|
|
91
|
+
|
|
92
|
+
**Installing this delivers 1.12.3 and 1.12.4 as well** — both were tagged but never published, so
|
|
93
|
+
npm went straight from 1.12.2 to here. Their entries below still describe what they contained.
|
|
94
|
+
|
|
95
|
+
### Embedding on an HTTPS dev server no longer fails silently ([#33](https://github.com/tonioloewald/haltija/issues/33))
|
|
96
|
+
|
|
97
|
+
An `https://` page cannot load an `http://` script: the browser blocks it as **mixed content** and
|
|
98
|
+
reports nothing that names the cause. You get no widget, `hj where` says `0 tabs`, and the natural
|
|
99
|
+
conclusion is that your own setup is wrong. The reporter worked it out from `--help` and source.
|
|
100
|
+
|
|
101
|
+
This is not an edge case — the tosijs-ui doc-system dev server is HTTPS by default (`bun run tls`),
|
|
102
|
+
so the default project setup hits it on the first attempt.
|
|
103
|
+
|
|
104
|
+
A static `src` cannot branch on the page's scheme, so the canonical embed snippet is now a
|
|
105
|
+
three-line loader that picks the matching transport, with the plain tag kept below as the equivalent
|
|
106
|
+
for HTTP pages. Serving an HTTPS page also needs `bunx haltija --server --both` and accepting the
|
|
107
|
+
self-signed cert once at `https://localhost:8701`.
|
|
108
|
+
|
|
109
|
+
Same root cause as [#32](https://github.com/tonioloewald/haltija/issues/32) from the other end:
|
|
110
|
+
there, a shared HTTP-only server silently denied another project's HTTPS pages; here, an HTTPS page
|
|
111
|
+
silently cannot reach an HTTP server. **Opening both transports by default would remove the class
|
|
112
|
+
rather than documenting it**, which is why that remains the top of the queue rather than something
|
|
113
|
+
more prose can fix.
|
|
114
|
+
|
|
115
|
+
## 1.12.4
|
|
116
|
+
|
|
117
|
+
Backlog work done while releases were paused. **Includes everything in 1.12.3**, which was tagged
|
|
118
|
+
but never published — installing 1.12.4 gets both.
|
|
119
|
+
|
|
120
|
+
### Three bugs found by closing a coverage hole
|
|
121
|
+
|
|
122
|
+
**Ten of seventeen step actions had no executable coverage in any lane** — including the
|
|
123
|
+
deprecated-alias branch and the `select-text` dispatch that shipped in 1.12.3. Nothing was red; the
|
|
124
|
+
lanes simply never ran those paths, and the failure mode there is a *silent success*. There is now a
|
|
125
|
+
blocking suite that asserts an **observable effect** per action (a checkbox actually checked, a
|
|
126
|
+
keydown that actually arrived), and it found:
|
|
127
|
+
|
|
128
|
+
- **`hj drag` does nothing to a native `<input type=range>` and reported success.** Measured: a 60px
|
|
129
|
+
drag leaves a native range at 0 while moving a custom div thumb 0 → 60px. Browsers drive native
|
|
130
|
+
controls from *trusted* input only; a custom implementation listens for `mousemove` on `document`,
|
|
131
|
+
which is why MUI sliders, resize handles and reorder lists are unaffected. `/drag` now returns a
|
|
132
|
+
warning naming the cause and the way round it.
|
|
133
|
+
- **`hj test validate` accepted an `assert` step with no `assertion` object** — the shape our own
|
|
134
|
+
`CLAUDE.md` documented. Such a step never checks anything: a guard that cannot fail.
|
|
135
|
+
- **`tests/haltija.test.ts` reported 11 passes for work it never did.** With no server it early-returned
|
|
136
|
+
from each test body, and bun records that as PASSED. Now 0 pass / 11 skip.
|
|
137
|
+
|
|
138
|
+
### The MCP server has been shipping 43 of 63 endpoints since January
|
|
139
|
+
|
|
140
|
+
`apps/mcp/build/endpoints.json` — what the committed MCP server imports at runtime — had drifted
|
|
141
|
+
seven months behind. Anyone on the MCP path had no `/map`, `/find`, `/wait`, `/key`, `/call`,
|
|
142
|
+
`/form`, `/fetch`, `/select`, `/recording`, `/test/suite`, or the `/network`, `/video` and `/dialog`
|
|
143
|
+
families. Twenty endpoints.
|
|
144
|
+
|
|
145
|
+
`docs-drift` could not catch it, and that is the lesson: the gate asserts a build leaves the tree
|
|
146
|
+
clean, and **nothing in the build wrote that file**. A drift gate only covers what the build
|
|
147
|
+
produces. Now it writes it, with a test asserting the runtime copy matches the generated one.
|
|
148
|
+
|
|
149
|
+
Also fixed: `hj --setup-mcp` pointed at `node_modules/tosijs-dev/…` in two of three lookups, a
|
|
150
|
+
package name that changed long ago. (`apps/mcp` is still not in the npm `files` list — that is a
|
|
151
|
+
packaging decision, deliberately left rather than guessed at.)
|
|
152
|
+
|
|
153
|
+
### `hj where` says which transports are open — and why one is not ([#32](https://github.com/tonioloewald/haltija/issues/32))
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
transports: http 8700 ✓ https 8701 ✗ (no certs in <path>)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The channel is shared across projects, so **one project's transport choice is paid for by another**:
|
|
160
|
+
an instance that came up HTTP-only leaves every `https` page with nothing to import, because mixed
|
|
161
|
+
content blocks the fallback. The reporter's doc site had no haltija at all while `hj where` said
|
|
162
|
+
"haltija 1.12.2, 1 tab" and everything looked healthy. A half-open channel and a full one were
|
|
163
|
+
indistinguishable from outside; now the reason is named (`not requested`, `no certs at <path>`, or
|
|
164
|
+
`port N is held`). A `--private` instance reports transports but not the cross-project warning — it
|
|
165
|
+
is isolated by construction and denies nobody.
|
|
166
|
+
|
|
167
|
+
Opening both transports by default (the other half of that issue) needs a release to exercise and is
|
|
168
|
+
queued rather than rushed.
|
|
169
|
+
|
|
170
|
+
### Also
|
|
171
|
+
|
|
172
|
+
- Four stale copies folded in, including a **fourth** hand-maintained step list in `api-schema.ts`
|
|
173
|
+
that had drifted to 8 of 17 and propagated to `API.md` and the MCP definitions.
|
|
174
|
+
- `CLAUDE.md`'s test-JSON example used the flat `assert` shape the runner ignores.
|
|
175
|
+
|
|
176
|
+
## 1.12.3
|
|
177
|
+
|
|
178
|
+
Documentation-drift machinery, a measured rename, and the six blockers a nine-lens review raised
|
|
179
|
+
against all of it. **Known open:** [#26](https://github.com/tonioloewald/haltija/issues/26) (a tab
|
|
180
|
+
becoming permanently undrivable — observed, not reproducible on any version) and
|
|
181
|
+
[#16](https://github.com/tonioloewald/haltija/issues/16) (native tosiAgent bridge, on hold).
|
|
182
|
+
|
|
183
|
+
**Recorded suites now use `select-text`, which servers before 1.12.3 reject.** If you record on an
|
|
184
|
+
updated machine and run against a pinned older haltija, that step fails with `unknown step action`.
|
|
185
|
+
The paved CI path (`bunx haltija@latest`) is unaffected.
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### `select` is now `select-text` — and `select` is being freed
|
|
189
|
+
|
|
190
|
+
Measured, not argued. Shown the bare step vocabulary with no descriptions, **3/3 agents reached for
|
|
191
|
+
`select` to choose an option from a dropdown, and 3/3 read it cold as "choose an `<option>`".
|
|
192
|
+
Nobody read it as text selection** — which is what it does. A name that unanimously means something
|
|
193
|
+
else to competent readers is not a documentation problem.
|
|
194
|
+
|
|
195
|
+
`select-text` says what it does. **`select` still works** as a deprecated alias, so existing suites
|
|
196
|
+
keep running; steps using it now carry a `warning` naming the replacement, rendered in the test
|
|
197
|
+
report rather than only present in `--json`.
|
|
198
|
+
|
|
199
|
+
The recorder also stopped emitting two illegal steps it had emitted since long before this release:
|
|
200
|
+
`set` (not a step action at all — recorded suites died with "Unsupported step action: set") and
|
|
201
|
+
`select` for **dropdowns**, which resolved to `select-text` and dispatched a text-selection event at
|
|
202
|
+
the `<select>`, passing while choosing nothing. Both now record an explicit `eval` that works, and a
|
|
203
|
+
test asserts every action the recorder can emit is legal and non-deprecated.
|
|
204
|
+
|
|
205
|
+
The point of the deprecation is the reuse: once the alias can be dropped, `select` is free to mean
|
|
206
|
+
what everyone already expects — pick an option from a `<select>` — which haltija cannot do at all
|
|
207
|
+
today. A test enforces that the two meanings can never overlap (`no alias may shadow a live
|
|
208
|
+
action`), because a word meaning two things depending on vintage is exactly the silent-wrong-action
|
|
209
|
+
trap the rename exists to remove.
|
|
210
|
+
|
|
211
|
+
**Correction, and it matters more than the rename.** An earlier draft of these notes acquitted
|
|
212
|
+
`check`, `verify` and `tabs-focus` as "measured and fine". That was wrong: the harness spawned each
|
|
213
|
+
agent **in this repo**, so every "first impression" had our own `CLAUDE.md` — which documents the
|
|
214
|
+
vocabulary under test — in context. Re-run from a neutral directory, `check` is cold-read as
|
|
215
|
+
*"asserts that some condition is true"* by **3/3** (the exact confusion it was cleared of), and
|
|
216
|
+
`verify` slips to 2/3. What survives is narrower and honest: with the full vocabulary in view agents
|
|
217
|
+
still pick `assert` for assertions and `check` for checkboxes, so `check` is safe **in context** and
|
|
218
|
+
misleading **in isolation**. `tabs-focus` holds at 3/3.
|
|
219
|
+
|
|
220
|
+
The `select` verdict is unaffected — contamination ran in its favour and it still lost 3/3, and the
|
|
221
|
+
clean re-run agrees. The probe now runs each sample in an empty temp directory, and its vocabularies
|
|
222
|
+
are derived from the registries rather than hand-copied (the old copy still listed `select`, stale
|
|
223
|
+
as of the commit it motivated). Harness: `tools/naming-probe.mjs`, run with bun.
|
|
224
|
+
|
|
3
225
|
## 1.12.2
|
|
4
226
|
|
|
5
227
|
Two fixes, both from an agent driving real apps. Each is a case where haltija reported success — or
|
|
@@ -40,12 +40,12 @@ var __export = (target, all) => {
|
|
|
40
40
|
// src/artifacts.ts
|
|
41
41
|
var exports_artifacts = {};
|
|
42
42
|
__export(exports_artifacts, {
|
|
43
|
-
|
|
44
|
-
pruneKind: () => pruneKind,
|
|
45
|
-
pruneArtifacts: () => pruneArtifacts,
|
|
46
|
-
parseDataUrl: () => parseDataUrl,
|
|
43
|
+
RETENTION: () => RETENTION,
|
|
47
44
|
artifactDir: () => artifactDir,
|
|
48
|
-
|
|
45
|
+
parseDataUrl: () => parseDataUrl,
|
|
46
|
+
pruneArtifacts: () => pruneArtifacts,
|
|
47
|
+
pruneKind: () => pruneKind,
|
|
48
|
+
saveDataUrl: () => saveDataUrl
|
|
49
49
|
});
|
|
50
50
|
module.exports = __toCommonJS(exports_artifacts);
|
|
51
51
|
var import_promises = require("fs/promises");
|
|
@@ -40,12 +40,12 @@ var __export = (target, all) => {
|
|
|
40
40
|
// src/desktop-isolation.ts
|
|
41
41
|
var exports_desktop_isolation = {};
|
|
42
42
|
__export(exports_desktop_isolation, {
|
|
43
|
-
|
|
44
|
-
resolvePublicUrl: () => resolvePublicUrl,
|
|
45
|
-
resolveInternalPort: () => resolveInternalPort,
|
|
46
|
-
isPrivateInstance: () => isPrivateInstance,
|
|
43
|
+
SHARED_INTERNAL_PORT: () => SHARED_INTERNAL_PORT,
|
|
47
44
|
SHARED_PUBLIC_URL: () => SHARED_PUBLIC_URL,
|
|
48
|
-
|
|
45
|
+
isPrivateInstance: () => isPrivateInstance,
|
|
46
|
+
resolveInternalPort: () => resolveInternalPort,
|
|
47
|
+
resolvePublicUrl: () => resolvePublicUrl,
|
|
48
|
+
resolveServerUrl: () => resolveServerUrl
|
|
49
49
|
});
|
|
50
50
|
module.exports = __toCommonJS(exports_desktop_isolation);
|
|
51
51
|
var SHARED_PUBLIC_URL = "http://localhost:8700";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "haltija-desktop",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.6",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Haltija Desktop - God Mode Browser for AI Agents",
|
|
6
6
|
"homepage": "https://github.com/tonioloewald/haltija",
|
|
@@ -18,15 +18,14 @@
|
|
|
18
18
|
"compile:server": "cd ../.. && bun run build && bun build --compile --target=bun-darwin-arm64 src/server.ts --outfile apps/desktop/resources/haltija-server-arm64 && bun build --compile --target=bun-darwin-x64 src/server.ts --outfile apps/desktop/resources/haltija-server-x64 && cp dist/component.js apps/desktop/resources/component.js && cp haltija-icon.svg apps/desktop/resources/icon.svg",
|
|
19
19
|
"compile:server:win": "cd ../.. && bun build --compile --target=bun-windows-x64 src/server.ts --outfile apps/desktop/resources/haltija-server-x64.exe",
|
|
20
20
|
"compile:server:linux": "cd ../.. && bun build --compile --target=bun-linux-x64 src/server.ts --outfile apps/desktop/resources/haltija-server-x64 && bun build --compile --target=bun-linux-arm64 src/server.ts --outfile apps/desktop/resources/haltija-server-arm64",
|
|
21
|
-
"build": "electron-builder",
|
|
22
|
-
"build:mac": "npm run sync-version && npm run icons && npm run compile:server && electron-builder --mac",
|
|
23
|
-
"build:win": "npm run sync-version && npm run compile:server:win && electron-builder --win",
|
|
24
|
-
"build:linux": "npm run sync-version && npm run compile:server:linux && electron-builder --linux"
|
|
21
|
+
"build": "npx --yes electron-builder@26",
|
|
22
|
+
"build:mac": "npm run sync-version && npm run icons && npm run compile:server && npx --yes electron-builder@26 --mac",
|
|
23
|
+
"build:win": "npm run sync-version && npm run compile:server:win && npx --yes electron-builder@26 --win",
|
|
24
|
+
"build:linux": "npm run sync-version && npm run compile:server:linux && npx --yes electron-builder@26 --linux"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"electron": "^
|
|
29
|
-
"electron-builder": "^24.9.1"
|
|
28
|
+
"electron": "^43.4.1"
|
|
30
29
|
},
|
|
31
30
|
"build": {
|
|
32
31
|
"appId": "com.haltija.desktop",
|
|
@@ -26,10 +26,10 @@ function resolveServerUrl(inputs) {
|
|
|
26
26
|
return injected || persisted || SHARED_PUBLIC_URL;
|
|
27
27
|
}
|
|
28
28
|
export {
|
|
29
|
-
|
|
30
|
-
resolvePublicUrl,
|
|
31
|
-
resolveInternalPort,
|
|
32
|
-
isPrivateInstance,
|
|
29
|
+
SHARED_INTERNAL_PORT,
|
|
33
30
|
SHARED_PUBLIC_URL,
|
|
34
|
-
|
|
31
|
+
isPrivateInstance,
|
|
32
|
+
resolveInternalPort,
|
|
33
|
+
resolvePublicUrl,
|
|
34
|
+
resolveServerUrl
|
|
35
35
|
};
|
|
@@ -40,13 +40,20 @@
|
|
|
40
40
|
// src/component.ts
|
|
41
41
|
var exports_component = {};
|
|
42
42
|
__export(exports_component, {
|
|
43
|
-
|
|
43
|
+
DevChannel: () => DevChannel,
|
|
44
44
|
VERSION: () => VERSION2,
|
|
45
|
-
|
|
45
|
+
inject: () => inject
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/version.ts
|
|
49
|
-
var VERSION = "1.12.
|
|
49
|
+
var VERSION = "1.12.6";
|
|
50
|
+
|
|
51
|
+
// src/ws-url.ts
|
|
52
|
+
function httpBaseFromWsUrl(wsUrl2, fallback = "http://localhost:8700") {
|
|
53
|
+
if (!wsUrl2)
|
|
54
|
+
return fallback;
|
|
55
|
+
return wsUrl2.replace(/^wss:/, "https:").replace(/^ws:/, "http:").replace("/ws/browser", "");
|
|
56
|
+
}
|
|
50
57
|
|
|
51
58
|
// src/text-selector.ts
|
|
52
59
|
var TEXT_PSEUDO_RE = /:(?:text-is|has-text|text)\(/;
|
|
@@ -1358,6 +1365,10 @@
|
|
|
1358
1365
|
...buildDomAffordances(opts.maxNodes)
|
|
1359
1366
|
};
|
|
1360
1367
|
}
|
|
1368
|
+
function serverHeaders(extra = {}) {
|
|
1369
|
+
const token = window.__haltija_config__?.token;
|
|
1370
|
+
return token ? { ...extra, "X-Haltija-Token": String(token) } : { ...extra };
|
|
1371
|
+
}
|
|
1361
1372
|
function isOwnWidget(el) {
|
|
1362
1373
|
if (el.tagName === TAG_NAME.toUpperCase())
|
|
1363
1374
|
return true;
|
|
@@ -3000,6 +3011,7 @@
|
|
|
3000
3011
|
};
|
|
3001
3012
|
document.addEventListener("visibilitychange", this.visibilityHandler);
|
|
3002
3013
|
}
|
|
3014
|
+
this.startPaintHeartbeat();
|
|
3003
3015
|
this.connect();
|
|
3004
3016
|
}
|
|
3005
3017
|
disconnectedCallback() {
|
|
@@ -3015,6 +3027,7 @@
|
|
|
3015
3027
|
document.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
3016
3028
|
this.visibilityHandler = null;
|
|
3017
3029
|
}
|
|
3030
|
+
this.stopPaintHeartbeat();
|
|
3018
3031
|
}
|
|
3019
3032
|
attributeChangedCallback(name, _old, value) {
|
|
3020
3033
|
if (name === "server") {
|
|
@@ -3594,7 +3607,7 @@
|
|
|
3594
3607
|
</div>
|
|
3595
3608
|
</div>
|
|
3596
3609
|
<div class="body"${this.isElectron ? ' style="display:none"' : ""}>
|
|
3597
|
-
<a href="javascript:(function(){fetch('${this.serverUrl
|
|
3610
|
+
<a href="javascript:(function(){fetch('${httpBaseFromWsUrl(this.serverUrl)}/inject.js').then(r=>r.text()).then(eval).catch(e=>alert('${PRODUCT_NAME}: Cannot reach server'))})();"
|
|
3598
3611
|
style="color: #6366f1; text-decoration: none;"
|
|
3599
3612
|
title="Drag to bookmarks bar"
|
|
3600
3613
|
class="bookmark-link">\uD83E\uDDDD bookmark</a>
|
|
@@ -3902,10 +3915,10 @@
|
|
|
3902
3915
|
async startRecordingViaServer() {
|
|
3903
3916
|
try {
|
|
3904
3917
|
const config = window.__haltija_config__;
|
|
3905
|
-
const serverUrl2 = config?.serverUrl
|
|
3918
|
+
const serverUrl2 = httpBaseFromWsUrl(config?.serverUrl);
|
|
3906
3919
|
const response = await fetch(`${serverUrl2}/recording`, {
|
|
3907
3920
|
method: "POST",
|
|
3908
|
-
headers: { "Content-Type": "application/json" },
|
|
3921
|
+
headers: serverHeaders({ "Content-Type": "application/json" }),
|
|
3909
3922
|
body: JSON.stringify({ action: "start", window: this.windowId })
|
|
3910
3923
|
});
|
|
3911
3924
|
const result = await response.json();
|
|
@@ -3930,10 +3943,10 @@
|
|
|
3930
3943
|
async stopRecordingViaServer() {
|
|
3931
3944
|
try {
|
|
3932
3945
|
const config = window.__haltija_config__;
|
|
3933
|
-
const serverUrl2 = config?.serverUrl
|
|
3946
|
+
const serverUrl2 = httpBaseFromWsUrl(config?.serverUrl);
|
|
3934
3947
|
const response = await fetch(`${serverUrl2}/recording`, {
|
|
3935
3948
|
method: "POST",
|
|
3936
|
-
headers: { "Content-Type": "application/json" },
|
|
3949
|
+
headers: serverHeaders({ "Content-Type": "application/json" }),
|
|
3937
3950
|
body: JSON.stringify({ action: "stop", window: this.windowId })
|
|
3938
3951
|
});
|
|
3939
3952
|
const result = await response.json();
|
|
@@ -4095,25 +4108,21 @@
|
|
|
4095
4108
|
let inputDesc = "";
|
|
4096
4109
|
if (isCleared) {
|
|
4097
4110
|
inputDesc = `Clear ${inputLabel}`;
|
|
4098
|
-
} else if (inputType === "range") {
|
|
4099
|
-
inputAction = "
|
|
4100
|
-
inputDesc = `Set ${inputLabel} to ${inputValue}`;
|
|
4101
|
-
} else if (inputType === "select-one" || inputType === "select-multiple") {
|
|
4102
|
-
inputAction = "select";
|
|
4103
|
-
inputDesc = `Select "${inputValue}" in ${inputLabel}`;
|
|
4111
|
+
} else if (inputType === "range" || inputType === "select-one" || inputType === "select-multiple" || inputType === "date" || inputType === "time" || inputType === "color") {
|
|
4112
|
+
inputAction = "eval";
|
|
4113
|
+
inputDesc = `Set ${inputLabel} to "${inputValue}"`;
|
|
4104
4114
|
} else if (inputType === "checkbox" || inputType === "radio") {
|
|
4105
4115
|
inputAction = "check";
|
|
4106
4116
|
inputDesc = `Check ${inputLabel}`;
|
|
4107
|
-
} else if (inputType === "date" || inputType === "time" || inputType === "color") {
|
|
4108
|
-
inputAction = "set";
|
|
4109
|
-
inputDesc = `Set ${inputLabel} to ${inputValue}`;
|
|
4110
4117
|
} else {
|
|
4111
4118
|
inputDesc = `Type "${inputValue}" in ${inputLabel}`;
|
|
4112
4119
|
}
|
|
4113
4120
|
steps.push(withFallback({
|
|
4114
4121
|
action: inputAction,
|
|
4115
4122
|
selector,
|
|
4116
|
-
...inputAction === "
|
|
4123
|
+
...inputAction === "eval" ? {
|
|
4124
|
+
code: `const el = document.querySelector(${JSON.stringify(selector)});` + ` el.value = ${JSON.stringify(inputValue)};` + ` el.dispatchEvent(new Event('input', { bubbles: true }));` + ` el.dispatchEvent(new Event('change', { bubbles: true }));`
|
|
4125
|
+
} : inputAction === "type" ? { text: inputValue } : { value: inputValue },
|
|
4117
4126
|
description: inputDesc,
|
|
4118
4127
|
...delay && delay > 50 ? { delay } : {}
|
|
4119
4128
|
}, event.target));
|
|
@@ -4146,7 +4155,7 @@
|
|
|
4146
4155
|
case "interaction:select":
|
|
4147
4156
|
const selectedText = this.cleanDescription(event.payload?.text || "", 50);
|
|
4148
4157
|
steps.push({
|
|
4149
|
-
action: "select",
|
|
4158
|
+
action: "select-text",
|
|
4150
4159
|
selector,
|
|
4151
4160
|
text: event.payload?.text || "",
|
|
4152
4161
|
description: `Select text "${selectedText}"`,
|
|
@@ -4249,7 +4258,7 @@
|
|
|
4249
4258
|
const serverUrl2 = this.serverUrl.replace("ws://", "http://").replace("wss://", "https://").replace("/ws/browser", "");
|
|
4250
4259
|
const response = await fetch(`${serverUrl2}/recording/${this.lastRecordingId}/send`, {
|
|
4251
4260
|
method: "POST",
|
|
4252
|
-
headers: { "Content-Type": "application/json" },
|
|
4261
|
+
headers: serverHeaders({ "Content-Type": "application/json" }),
|
|
4253
4262
|
body: JSON.stringify({ description, agentId })
|
|
4254
4263
|
});
|
|
4255
4264
|
const result = await response.json();
|
|
@@ -4689,7 +4698,7 @@
|
|
|
4689
4698
|
const serverUrl2 = this.serverUrl.replace("ws://", "http://").replace("wss://", "https://").replace("/ws/browser", "");
|
|
4690
4699
|
let agents = [];
|
|
4691
4700
|
try {
|
|
4692
|
-
const response = await fetch(`${serverUrl2}/terminal/agents
|
|
4701
|
+
const response = await fetch(`${serverUrl2}/terminal/agents`, { headers: serverHeaders() });
|
|
4693
4702
|
const data = await response.json();
|
|
4694
4703
|
agents = data.agents || [];
|
|
4695
4704
|
} catch (err) {
|
|
@@ -4859,7 +4868,7 @@ ${elementSummary}${moreText}`;
|
|
|
4859
4868
|
try {
|
|
4860
4869
|
const response = await fetch(`${serverUrl2}/selection/send`, {
|
|
4861
4870
|
method: "POST",
|
|
4862
|
-
headers: { "Content-Type": "application/json" },
|
|
4871
|
+
headers: serverHeaders({ "Content-Type": "application/json" }),
|
|
4863
4872
|
body: JSON.stringify({
|
|
4864
4873
|
agentId,
|
|
4865
4874
|
message: messageText,
|
|
@@ -6248,6 +6257,29 @@ ${elementSummary}${moreText}`;
|
|
|
6248
6257
|
});
|
|
6249
6258
|
this.render();
|
|
6250
6259
|
}
|
|
6260
|
+
lastPaintAt = Date.now();
|
|
6261
|
+
paintTimer = null;
|
|
6262
|
+
paintAgeOnArrival = new Map;
|
|
6263
|
+
startPaintHeartbeat() {
|
|
6264
|
+
if (typeof requestAnimationFrame !== "function" || typeof setTimeout !== "function")
|
|
6265
|
+
return;
|
|
6266
|
+
const tick = () => {
|
|
6267
|
+
requestAnimationFrame(() => {
|
|
6268
|
+
this.lastPaintAt = Date.now();
|
|
6269
|
+
});
|
|
6270
|
+
this.paintTimer = setTimeout(tick, 1000);
|
|
6271
|
+
};
|
|
6272
|
+
tick();
|
|
6273
|
+
}
|
|
6274
|
+
stopPaintHeartbeat() {
|
|
6275
|
+
if (this.paintTimer !== null) {
|
|
6276
|
+
clearTimeout(this.paintTimer);
|
|
6277
|
+
this.paintTimer = null;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
paintAgeMs() {
|
|
6281
|
+
return Date.now() - this.lastPaintAt;
|
|
6282
|
+
}
|
|
6251
6283
|
send(channel, action2, payload2, id) {
|
|
6252
6284
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
6253
6285
|
return;
|
|
@@ -6262,6 +6294,8 @@ ${elementSummary}${moreText}`;
|
|
|
6262
6294
|
this.ws.send(JSON.stringify(msg2));
|
|
6263
6295
|
}
|
|
6264
6296
|
respond(requestId, success, data, error) {
|
|
6297
|
+
const arrivalPaintAge = this.paintAgeOnArrival.get(requestId);
|
|
6298
|
+
this.paintAgeOnArrival.delete(requestId);
|
|
6265
6299
|
const local = this.localPending.get(requestId);
|
|
6266
6300
|
if (local) {
|
|
6267
6301
|
this.localPending.delete(requestId);
|
|
@@ -6277,7 +6311,8 @@ ${elementSummary}${moreText}`;
|
|
|
6277
6311
|
success,
|
|
6278
6312
|
data,
|
|
6279
6313
|
error,
|
|
6280
|
-
timestamp: Date.now()
|
|
6314
|
+
timestamp: Date.now(),
|
|
6315
|
+
paintAgeMs: arrivalPaintAge ?? this.paintAgeMs()
|
|
6281
6316
|
};
|
|
6282
6317
|
this.ws?.send(JSON.stringify(response));
|
|
6283
6318
|
}
|
|
@@ -6296,6 +6331,14 @@ ${elementSummary}${moreText}`;
|
|
|
6296
6331
|
});
|
|
6297
6332
|
}
|
|
6298
6333
|
handleMessage(msg2) {
|
|
6334
|
+
if (msg2.id) {
|
|
6335
|
+
if (this.paintAgeOnArrival.size > 200) {
|
|
6336
|
+
const oldest = this.paintAgeOnArrival.keys().next().value;
|
|
6337
|
+
if (oldest !== undefined)
|
|
6338
|
+
this.paintAgeOnArrival.delete(oldest);
|
|
6339
|
+
}
|
|
6340
|
+
this.paintAgeOnArrival.set(msg2.id, this.paintAgeMs());
|
|
6341
|
+
}
|
|
6299
6342
|
if (msg2.source === "agent" || msg2.source === "server") {
|
|
6300
6343
|
this.show();
|
|
6301
6344
|
}
|
package/bin/cli-commands.mjs
CHANGED
|
@@ -55,6 +55,10 @@ var ROUTED_COMMANDS = [
|
|
|
55
55
|
"test-run",
|
|
56
56
|
"test-validate",
|
|
57
57
|
"test-suite",
|
|
58
|
+
"session-attach",
|
|
59
|
+
"session-read",
|
|
60
|
+
"session-write",
|
|
61
|
+
"session-detach",
|
|
58
62
|
"send",
|
|
59
63
|
"send-message",
|
|
60
64
|
"send-selection",
|
|
@@ -120,9 +124,9 @@ function cliNameForEndpoint(path) {
|
|
|
120
124
|
return isKnownCommand(name) ? name : null;
|
|
121
125
|
}
|
|
122
126
|
export {
|
|
123
|
-
|
|
124
|
-
cliNameForEndpoint,
|
|
125
|
-
ROUTED_COMMANDS,
|
|
127
|
+
LOCAL_COMMANDS,
|
|
126
128
|
LOCAL_COMMAND_HELP,
|
|
127
|
-
|
|
129
|
+
ROUTED_COMMANDS,
|
|
130
|
+
cliNameForEndpoint,
|
|
131
|
+
isKnownCommand
|
|
128
132
|
};
|